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

Commit d9de49d0 authored by Martijn Coenen's avatar Martijn Coenen Committed by Android (Google) Code Review
Browse files

Merge changes from topic "fuse_bindmount"

* changes:
  Deal with MediaProvider process dying.
  Don't delay starting sessions.
  Tell StorageSessionController when a user is stopping.
parents affe64c6 7391820c
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);
}
+31 −0
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ import static org.xmlpull.v1.XmlPullParser.START_TAG;
import android.Manifest;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.KeyguardManager;
@@ -253,6 +254,11 @@ class StorageManagerService extends IStorageManager.Stub
        public void onCleanupUser(int userHandle) {
            mStorageManagerService.onCleanupUser(userHandle);
        }

        @Override
        public void onStopUser(int userHandle) {
            mStorageManagerService.onStopUser(userHandle);
        }
    }

    private static final boolean DEBUG_EVENTS = false;
@@ -1075,6 +1081,15 @@ class StorageManagerService extends IStorageManager.Stub
        }
    }

    private void onStopUser(int userId) {
        Slog.i(TAG, "onStopUser " + userId);
        try {
            mStorageSessionController.onUserStopping(userId);
        } catch (Exception e) {
            Slog.wtf(TAG, e);
        }
    }

    private boolean supportsBlockCheckpoint() throws RemoteException {
        enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
        return mVold.supportsBlockCheckpoint();
@@ -1309,6 +1324,15 @@ class StorageManagerService extends IStorageManager.Stub
            Slog.d(TAG, "System booted in core-only mode; ignoring volume " + vol.getId());
            return;
        }
        final ActivityManagerInternal amInternal =
                LocalServices.getService(ActivityManagerInternal.class);

        if (mIsFuseEnabled && vol.mountUserId >= 0
                && !amInternal.isUserRunning(vol.mountUserId, 0)) {
            Slog.d(TAG, "Ignoring volume " + vol.getId() + " because user "
                    + Integer.toString(vol.mountUserId) + " is no longer running.");
            return;
        }

        if (vol.type == VolumeInfo.TYPE_EMULATED) {
            final StorageManager storage = mContext.getSystemService(StorageManager.class);
@@ -4092,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.
+18 −18
Original line number Diff line number Diff line
@@ -43,7 +43,6 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;

import java.io.FileDescriptor;
import java.io.IOException;

/**
 * Controls storage sessions for users initiated by the {@link StorageManagerService}.
@@ -101,18 +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());
        }

        // At boot, a volume can be mounted before user is unlocked, in that case, we create it
        // above and save it so that we can restart all sessions when the user is unlocked
        if (mExternalStorageServiceComponent != null) {
            connection.startSession(sessionId);
        } else {
            Slog.i(TAG, "Controller not initialised, session not started " + sessionId);
        }
    }

    /**
@@ -179,23 +170,32 @@ public final class StorageSessionController {
     * a session will be ignored.
     */
    public void onUnlockUser(int userId) throws ExternalStorageServiceException {
        if (!shouldHandle(null)) {
            return;
        }

        Slog.i(TAG, "On user unlock " + userId);
        if (userId == 0) {
        if (shouldHandle(null) && userId == 0) {
            initExternalStorageServiceComponent();
        }
    }

    /**
     * Called when a user is in the process is being stopped.
     *
     * Does nothing if {@link #shouldHandle} is {@code false}
     *
     * This call removes all sessions for the user that is being stopped;
     * this will make sure that we don't rebind to the service needlessly.
     */
    public void onUserStopping(int userId) throws ExternalStorageServiceException {
        if (!shouldHandle(null)) {
            return;
        }
        StorageUserConnection connection = null;
        synchronized (mLock) {
            connection = mConnections.get(userId);
        }

        if (connection != null) {
            Slog.i(TAG, "Restarting all sessions for user: " + userId);
            connection.startAllSessions();
            Slog.i(TAG, "Removing all sessions for user: " + userId);
            connection.removeAllSessions();
        } else {
            Slog.w(TAG, "No connection found for user: " + userId);
        }
+48 −65
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,16 +106,10 @@ public final class StorageUserConnection {
     **/
    public Session removeSession(String sessionId) {
        synchronized (mLock) {
            Session session = mSessions.remove(sessionId);
            if (session != null) {
                session.close();
                return session;
            }
            return null;
            return mSessions.remove(sessionId);
        }
    }


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

    /** Starts all available sessions for a user without blocking. Any failures will be ignored. */
    public void startAllSessions() {
        try {
            prepareRemote();
        } catch (ExternalStorageServiceException e) {
            Slog.e(TAG, "Failed to start all sessions for user: " + mUserId, e);
    /** Restarts all available sessions for a user without blocking.
     *
     * Any failures will be ignored.
     **/
    public void resetUserSessions() {
        synchronized (mLock) {
            if (mSessions.isEmpty()) {
                // Nothing to reset if we have no sessions to restart; we typically
                // hit this path if the user was consciously shut down.
                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);
    }

    /**
     * Removes all sessions, without waiting.
     */
    public void removeAllSessions() {
        synchronized (mLock) {
            Slog.i(TAG, "Removing  " + mSessions.size() + " sessions for user: " + mUserId + "...");
            mSessions.clear();
        }
    }

@@ -269,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;
@@ -390,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();
                    }
                };
            }
@@ -411,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: "