Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7391820c authored by Martijn Coenen's avatar Martijn Coenen
Browse files

Deal with MediaProvider process dying.

There is no way to recover the FUSE filesystem once the MediaProvider
process has died; the kernel and userspace are out of sync at that
point, and libfuse will return -EIO on all requests.

Now when the process dies, tell StoragemanagerService that it needs to
reset all volumes for a user. Since we currently don't have a way to do
this for one user, just call through to H_RESET. This can be optimized
later. This also means we no longer need to keep a hold of the FUSE fd
in system_server; just pass it along directly when we start a session.

Bug: 137890172
Test: atest AdoptableHostTest
Change-Id: I2e6952ccece6bf2945b4ed81c70330b278554d13
parent 7402191a
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -109,4 +109,13 @@ public abstract class StorageManagerInternal {
     */
    public abstract void onAppOpsChanged(int code, int uid,
            @Nullable String packageName, int mode);

    /**
     * Asks the StorageManager to reset all state for the provided user; this will result
     * in the unmounting for all volumes of the user, and, if the user is still running, the
     * volumes will be re-mounted as well.
     *
     * @param userId the userId for which to reset storage
     */
    public abstract void resetUser(int userId);
}
+7 −0
Original line number Diff line number Diff line
@@ -4116,6 +4116,13 @@ class StorageManagerService extends IStorageManager.Stub
            }
        }

        @Override
        public void resetUser(int userId) {
            // TODO(b/145931219): ideally, we only reset storage for the user in question,
            // but for now, reset everything.
            mHandler.obtainMessage(H_RESET).sendToTarget();
        }

        public boolean hasExternalStorage(int uid, String packageName) {
            // No need to check for system uid. This avoids a deadlock between
            // PackageManagerService and AppOpsService.
+3 −5
Original line number Diff line number Diff line
@@ -100,12 +100,10 @@ public final class StorageSessionController {
                connection = new StorageUserConnection(mContext, userId, this);
                mConnections.put(userId, connection);
            }
            Slog.i(TAG, "Creating session with id: " + sessionId);
            connection.createSession(sessionId, new ParcelFileDescriptor(deviceFd),
            Slog.i(TAG, "Creating and starting session with id: " + sessionId);
            connection.startSession(sessionId, new ParcelFileDescriptor(deviceFd),
                    vol.getPath().getPath(), vol.getInternalPath().getPath());
        }

        connection.startSession(sessionId);
    }

    /**
@@ -196,7 +194,7 @@ public final class StorageSessionController {
        }

        if (connection != null) {
            Slog.i(TAG, "Closing all sessions for user: " + userId);
            Slog.i(TAG, "Removing all sessions for user: " + userId);
            connection.removeAllSessions();
        } else {
            Slog.w(TAG, "No connection found for user: " + userId);
+37 −84
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import android.os.ParcelFileDescriptor;
import android.os.ParcelableException;
import android.os.RemoteCallback;
import android.os.UserHandle;
import android.os.storage.StorageManagerInternal;
import android.service.storage.ExternalStorageService;
import android.service.storage.IExternalStorageService;
import android.text.TextUtils;
@@ -41,6 +42,7 @@ import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;

import java.io.IOException;
import java.util.HashMap;
@@ -73,42 +75,25 @@ public final class StorageUserConnection {
    }

    /**
     * Creates and stores a storage {@link Session}.
     * Creates and starts a storage {@link Session}.
     *
     * They must also be cleaned up with {@link #removeSession}.
     *
     * @throws IllegalArgumentException if a {@code Session} with {@code sessionId} already exists
     */
    public void createSession(String sessionId, ParcelFileDescriptor pfd, String upperPath,
            String lowerPath) {
    public void startSession(String sessionId, ParcelFileDescriptor pfd, String upperPath,
            String lowerPath) throws ExternalStorageServiceException {
        Preconditions.checkNotNull(sessionId);
        Preconditions.checkNotNull(pfd);
        Preconditions.checkNotNull(upperPath);
        Preconditions.checkNotNull(lowerPath);

        synchronized (mLock) {
            Preconditions.checkArgument(!mSessions.containsKey(sessionId));
            mSessions.put(sessionId, new Session(sessionId, pfd, upperPath, lowerPath));
        }
    }

    /**
     * Starts an already created storage {@link Session} for {@code sessionId}.
     *
     * It is safe to call this multiple times, however if the session is already started,
     * subsequent calls will be ignored.
     *
     * @throws ExternalStorageServiceException if the session failed to start
     **/
    public void startSession(String sessionId) throws ExternalStorageServiceException {
        Session session;
        synchronized (mLock) {
            session = mSessions.get(sessionId);
        }

        prepareRemote();
        synchronized (mLock) {
            mActiveConnection.startSessionLocked(session);
            Preconditions.checkArgument(!mSessions.containsKey(sessionId));
            Session session = new Session(sessionId, upperPath, lowerPath);
            mSessions.put(sessionId, session);
            mActiveConnection.startSessionLocked(session, pfd);
        }
    }

@@ -121,15 +106,9 @@ public final class StorageUserConnection {
     **/
    public Session removeSession(String sessionId) {
        synchronized (mLock) {
            Session session = mSessions.remove(sessionId);
            if (session != null) {
                session.close();
                return session;
            return mSessions.remove(sessionId);
        }
            return null;
    }
    }


    /**
     * Removes a session and waits for exit
@@ -150,33 +129,20 @@ public final class StorageUserConnection {
        }
    }

    /** Starts all available sessions for a user without blocking. Any failures will be ignored. */
    public void startAllSessions() {
    /** Restarts all available sessions for a user without blocking.
     *
     * Any failures will be ignored.
     **/
    public void resetUserSessions() {
        synchronized (mLock) {
            if (mSessions.isEmpty()) {
                // No point bringing up the remote if we don't have any sessions to start
                // Nothing to reset if we have no sessions to restart; we typically
                // hit this path if the user was consciously shut down.
                return;
            }
        }

        try {
            prepareRemote();
        } catch (ExternalStorageServiceException e) {
            Slog.e(TAG, "Failed to start all sessions for user: " + mUserId, e);
            return;
        }

        synchronized (mLock) {
            Slog.i(TAG, "Starting " + mSessions.size() + " sessions for user: " + mUserId + "...");
            for (Session session : mSessions.values()) {
                try {
                    mActiveConnection.startSessionLocked(session);
                } catch (IllegalStateException | ExternalStorageServiceException e) {
                    // TODO: Don't crash process? We could get into process crash loop
                    Slog.e(TAG, "Failed to start " + session, e);
                }
            }
        }
        StorageManagerInternal sm = LocalServices.getService(StorageManagerInternal.class);
        sm.resetUser(mUserId);
    }

    /**
@@ -184,24 +150,11 @@ public final class StorageUserConnection {
     */
    public void removeAllSessions() {
        synchronized (mLock) {
            closeAllSessions();
            Slog.i(TAG, "Removing  " + mSessions.size() + " sessions for user: " + mUserId + "...");
            mSessions.clear();
        }
    }

    /**
     * Closes all sessions, but doesn't remove them or unbind the service.
     */
    public void closeAllSessions() {
        synchronized (mLock) {
            Slog.i(TAG, "Closing " + mSessions.size() + " sessions for user: " + mUserId + "...");
            for (Session session : mSessions.values()) {
                session.close();
            }
        }
    }

    /**
     * Closes the connection to the {@link ExternalStorageService}. The connection will typically
     * be restarted after close.
@@ -299,26 +252,37 @@ public final class StorageUserConnection {
            return true;
        }

        public void startSessionLocked(Session session) throws ExternalStorageServiceException {
        public void startSessionLocked(Session session, ParcelFileDescriptor fd)
                throws ExternalStorageServiceException {
            if (!isActiveLocked(session)) {
                try {
                    fd.close();
                } catch (IOException e) {
                    // ignore
                }
                return;
            }

            CountDownLatch latch = new CountDownLatch(1);
            try (ParcelFileDescriptor dupedPfd = session.pfd.dup()) {
            try {
                mRemote.startSession(session.sessionId,
                        FLAG_SESSION_TYPE_FUSE | FLAG_SESSION_ATTRIBUTE_INDEXABLE,
                        dupedPfd, session.upperPath, session.lowerPath, new RemoteCallback(result ->
                        fd, session.upperPath, session.lowerPath, new RemoteCallback(result ->
                                setResultLocked(latch, result)));
                waitForLatch(latch, "start_session " + session);
                maybeThrowExceptionLocked();
            } catch (Exception e) {
                throw new ExternalStorageServiceException("Failed to start session: " + session, e);
            } finally {
                try {
                    fd.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }

        public void endSessionLocked(Session session) throws ExternalStorageServiceException {
            session.close();
            if (!isActiveLocked(session)) {
                // Nothing to end, not started yet
                return;
@@ -420,7 +384,7 @@ public final class StorageUserConnection {
                        // will be called for any required mounts.
                        // Notify StorageManagerService so it can restart all necessary sessions
                        close();
                        new Thread(StorageUserConnection.this::startAllSessions).start();
                        resetUserSessions();
                    }
                };
            }
@@ -441,28 +405,17 @@ public final class StorageUserConnection {
        }
    }

    private static final class Session implements AutoCloseable {
    private static final class Session {
        public final String sessionId;
        public final ParcelFileDescriptor pfd;
        public final String lowerPath;
        public final String upperPath;

        Session(String sessionId, ParcelFileDescriptor pfd, String upperPath, String lowerPath) {
        Session(String sessionId, String upperPath, String lowerPath) {
            this.sessionId = sessionId;
            this.pfd = pfd;
            this.upperPath = upperPath;
            this.lowerPath = lowerPath;
        }

        @Override
        public void close() {
            try {
                pfd.close();
            } catch (IOException e) {
                Slog.i(TAG, "Failed to close session: " + this);
            }
        }

        @Override
        public String toString() {
            return "[SessionId: " + sessionId + ". UpperPath: " + upperPath + ". LowerPath: "