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

Commit 42f1e9f7 authored by Zim's avatar Zim
Browse files

Add StorageSessionController

The StorageSessionController has a StorageUserConnection to represent
the binding between the StoragemanagerService and the
ExternalStorageService for a user.

A StorageUserConnection can have multiple Sessions each representing a
mount for the user. The StorageUserConnection has an
ActiveConnection representing the active binding to the
ExternalStorageService.

If the ExternalStorageService goes down, the ActiveConnection becomes
'inactive' but the StorageManagerService will be notified to reset the
mount state of the device with the help of vold which will 're-activate'
the ActiveConnection.

This reset will among other things, kill any apps holding an open fd
on the FUSE mount and remount the FUSE mount, getting a new FUSE fd
as a result. The StorageSessionController will once again be notified
of new volume mounts and start sessions for them

Bug: 135341433
Test: adb shell setprop persist.sys.fuse && adb reboot && adb shell ls
/sdcard
Test: adb shell am crash com.android.providers.media && adb shell ls /sdcard
Change-Id: I72841a1d8012b1ca1cc88f29cb75127ea627c093
parent cd2dbe7d
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -616,10 +616,11 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
    public static File getFile(FileDescriptor fd) throws IOException {
        try {
            final String path = Os.readlink("/proc/self/fd/" + fd.getInt$());
            if (OsConstants.S_ISREG(Os.stat(path).st_mode)) {
            if (OsConstants.S_ISREG(Os.stat(path).st_mode)
                    || OsConstants.S_ISCHR(Os.stat(path).st_mode)) {
                return new File(path);
            } else {
                throw new IOException("Not a regular file: " + path);
                throw new IOException("Not a regular file or character device: " + path);
            }
        } catch (ErrnoException e) {
            throw e.rethrowAsIOException();
+33 −3
Original line number Diff line number Diff line
@@ -140,6 +140,7 @@ import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.storage.AppFuseBridge;
import com.android.server.storage.StorageSessionController;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal.ScreenObserver;

@@ -498,6 +499,9 @@ class StorageManagerService extends IStorageManager.Stub
    private final StorageManagerInternalImpl mStorageManagerInternal
            = new StorageManagerInternalImpl();

    // Not guarded by a lock.
    private final StorageSessionController mStorageSessionController;

    class ObbState implements IBinder.DeathRecipient {
        public ObbState(String rawPath, String canonicalPath, int callingUid,
                IObbActionListener token, int nonce, String volId) {
@@ -647,12 +651,20 @@ class StorageManagerService extends IStorageManager.Stub
                        Slog.i(TAG, "Ignoring mount " + vol.getId() + " due to policy");
                        break;
                    }
                    mount(vol);

                    // TODO(b/135341433): Remove paranoid logging when FUSE is stable
                    Slog.i(TAG, "Mounting volume " + vol);
                    // TODO(b/135341433): Update to use new vold API that gets or mounts fuse fd
                    // Ensure that we can pass user of a volume to the new API
                    mStorageSessionController.onVolumeMounted(mCurrentUserId, mount(vol), vol);
                    Slog.i(TAG, "Mounted volume " + vol);

                    break;
                }
                case H_VOLUME_UNMOUNT: {
                    final VolumeInfo vol = (VolumeInfo) msg.obj;
                    unmount(vol);
                    mStorageSessionController.onVolumeUnmounted(mCurrentUserId, vol);
                    break;
                }
                case H_VOLUME_BROADCAST: {
@@ -733,6 +745,7 @@ class StorageManagerService extends IStorageManager.Stub
                        }
                    }
                    mVold.onUserRemoved(userId);
                    mStorageSessionController.onUserRemoved(userId);
                }
            } catch (Exception e) {
                Slog.wtf(TAG, e);
@@ -951,7 +964,10 @@ class StorageManagerService extends IStorageManager.Stub
            }

            try {
                // TODO(b/135341433): Remove paranoid logging when FUSE is stable
                Slog.i(TAG, "Resetting vold");
                mVold.reset();
                Slog.i(TAG, "Reset vold");

                // Tell vold about all existing and started users
                for (UserInfo user : users) {
@@ -976,6 +992,7 @@ class StorageManagerService extends IStorageManager.Stub
        // staging area is ready so it's ready for zygote-forked apps to
        // bind mount against.
        try {
            mStorageSessionController.onUserStarted(userId);
            mVold.onUserStarted(userId);
            mStoraged.onUserStarted(userId);
        } catch (Exception e) {
@@ -1516,6 +1533,12 @@ class StorageManagerService extends IStorageManager.Stub
        // Add OBB Action Handler to StorageManagerService thread.
        mObbActionHandler = new ObbActionHandler(IoThread.get().getLooper());

        mStorageSessionController = new StorageSessionController(mContext,
                userId -> {
                    Slog.i(TAG, "Storage session ended for user: " + userId + ". Resetting...");
                    mHandler.obtainMessage(H_RESET).sendToTarget();
                });

        // Initialize the last-fstrim tracking if necessary
        File dataDir = Environment.getDataDirectory();
        File systemDir = new File(dataDir, "system");
@@ -1814,11 +1837,18 @@ class StorageManagerService extends IStorageManager.Stub
        mount(vol);
    }

    private void mount(VolumeInfo vol) {
    private FileDescriptor mount(VolumeInfo vol) {
        try {
            mVold.mount(vol.id, vol.mountFlags, vol.mountUserId);
            // TODO(b/135341433): Now, emulated (and private?) volumes are shared across users
            // This means the mountUserId on such volumes is USER_NULL. This breaks fuse which
            // requires a valid user to mount a volume. Create individual volumes per user in vold
            // and remove this property check
            int userId = SystemProperties.getBoolean("persist.sys.fuse", false)
                    ? mCurrentUserId : vol.mountUserId;
            return mVold.mount(vol.id, vol.mountFlags, userId);
        } catch (Exception e) {
            Slog.wtf(TAG, e);
            return null;
        }
    }

+217 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.storage;

import android.Manifest;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.ParcelFileDescriptor;
import android.os.storage.VolumeInfo;
import android.provider.MediaStore;
import android.service.storage.ExternalStorageService;
import android.util.Slog;
import android.util.SparseArray;

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}.
 * Each user on the device will be represented by a {@link StorageUserConnection}.
 */
public final class StorageSessionController {
    private static final String TAG = "StorageSessionController";

    private final Object mLock = new Object();
    private final Context mContext;
    private final Callback mCallback;
    @GuardedBy("mLock")
    private ComponentName mExternalStorageServiceComponent;
    @GuardedBy("mLock")
    private final SparseArray<StorageUserConnection> mConnections = new SparseArray<>();

    public StorageSessionController(Context context, Callback callback) {
        mContext = Preconditions.checkNotNull(context);
        mCallback = Preconditions.checkNotNull(callback);
    }

    /**
     * Starts a storage session associated with {@code deviceFd} for {@code vol}.
     * Does nothing if a session is already started or starting. If the user associated with
     * {@code vol} is not yet ready, the session will be retried {@link #onUserStarted}.
     *
     * A session must be ended with {@link #endSession} when no longer required.
     */
    public void onVolumeMounted(int userId, FileDescriptor deviceFd, VolumeInfo vol) {
        if (deviceFd == null) {
            Slog.w(TAG, "Null device fd. Session not started for " + vol);
            return;
        }

        // Get realpath for the fd, paths that are not /dev/null need additional
        // setup by the ExternalStorageService before they can be ready
        String realPath;
        try {
            realPath = ParcelFileDescriptor.getFile(deviceFd).getPath();
        } catch (IOException e) {
            Slog.wtf(TAG, "Could not get real path from fd: " + deviceFd, e);
            return;
        }

        if ("/dev/null".equals(realPath)) {
            Slog.i(TAG, "Volume ready for use: " + vol);
            return;
        }

        synchronized (mLock) {
            StorageUserConnection connection = mConnections.get(userId);
            if (connection == null) {
                Slog.i(TAG, "Creating new session for vol: " + vol);
                connection = new StorageUserConnection(mContext, userId, this);
                mConnections.put(userId, connection);
            }
            try {
                Slog.i(TAG, "Starting session for vol: " + vol);
                connection.startSession(deviceFd, vol);
            } catch (ExternalStorageServiceException e) {
                Slog.e(TAG, "Failed to start session for vol: " + vol, e);
            }
        }
    }

    /**
     * Ends a storage session for {@code vol}. Does nothing if the session is already
     * ended or ending. Ending a session discards all resources associated with that session.
     */
    public void onVolumeUnmounted(int userId, VolumeInfo vol) {
        synchronized (mLock) {
            StorageUserConnection connection = mConnections.get(userId);
            if (connection != null) {
                Slog.i(TAG, "Ending session for vol: " + vol);
                try {
                    if (connection.endSession(vol)) {
                        mConnections.remove(userId);
                    }
                } catch (ExternalStorageServiceException e) {
                    Slog.e(TAG, "Failed to end session for vol: " + vol, e);
                }
            } else {
                Slog.w(TAG, "Session already ended for vol: " + vol);
            }
        }
    }

    /** Restarts all sessions for {@code userId}. */
    public void onUserStarted(int userId) {
        synchronized (mLock) {
            StorageUserConnection connection = mConnections.get(userId);
            if (connection != null) {
                try {
                    Slog.i(TAG, "Restarting all sessions for user: " + userId);
                    connection.startAllSessions();
                } catch (ExternalStorageServiceException e) {
                    Slog.e(TAG, "Failed to start all sessions", e);
                }
            } else {
                // TODO(b/135341433): What does this mean in multi-user
            }
        }
    }

    /** Ends all sessions for {@code userId}. */
    public void onUserRemoved(int userId) {
        synchronized (mLock) {
            StorageUserConnection connection = mConnections.get(userId);
            if (connection != null) {
                try {
                    Slog.i(TAG, "Ending all sessions for user: " + userId);
                    connection.endAllSessions();
                    mConnections.remove(userId);
                } catch (ExternalStorageServiceException e) {
                    Slog.e(TAG, "Failed to end all sessions", e);
                }
            } else {
                // TODO(b/135341433): What does this mean in multi-user
            }
        }
    }

    /** Returns the {@link ExternalStorageService} component name. */
    @Nullable
    public ComponentName getExternalStorageServiceComponentName() {
        synchronized (mLock) {
            if (mExternalStorageServiceComponent == null) {
                ProviderInfo provider = mContext.getPackageManager().resolveContentProvider(
                        MediaStore.AUTHORITY, PackageManager.MATCH_DIRECT_BOOT_AWARE
                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                        | PackageManager.MATCH_SYSTEM_ONLY);

                if (provider == null) {
                    Slog.e(TAG, "No valid MediaStore provider found.");
                }
                String packageName = provider.applicationInfo.packageName;

                Intent intent = new Intent(ExternalStorageService.SERVICE_INTERFACE);
                intent.setPackage(packageName);
                ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
                        PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
                if (resolveInfo == null || resolveInfo.serviceInfo == null) {
                    Slog.e(TAG, "No valid ExternalStorageService component found.");
                    return null;
                }

                ServiceInfo serviceInfo = resolveInfo.serviceInfo;
                ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
                if (!Manifest.permission.BIND_EXTERNAL_STORAGE_SERVICE
                        .equals(serviceInfo.permission)) {
                    Slog.e(TAG, name.flattenToShortString() + " does not require permission "
                            + Manifest.permission.BIND_EXTERNAL_STORAGE_SERVICE);
                    return null;
                }
                mExternalStorageServiceComponent = name;
            }
            return mExternalStorageServiceComponent;
        }
    }

    /** Returns the {@link StorageManagerService} callback. */
    public Callback getCallback() {
        return mCallback;
    }

    /** Callback to listen to session events from the {@link StorageSessionController}. */
    public interface Callback {
        /** Called when a {@link StorageUserConnection} is disconnected. */
        void onUserDisconnected(int userId);
    }

    /** Exception thrown when communication with the {@link ExternalStorageService}. */
    public static class ExternalStorageServiceException extends Exception {
        public ExternalStorageServiceException(Throwable cause) {
            super(cause);
        }
    }
}
+319 −0

File added.

Preview size limit exceeded, changes collapsed.