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

Commit 9fb00183 authored by Daichi Hirono's avatar Daichi Hirono
Browse files

Add StorageManager#openProxyFileDescriptor.

The CL:

 * Adds StorageManager#openProxyFileDescriptor API.
 * Turns IProxyFileDescriptorCallback into a class so that it can provides
   default implementations.
 * Removes mActive state from FuseAppLoop, because the state will be managed at
   the MountService side.
 * Adds StorageManagerIntegrationTest to check if FUSE is correctly mounted or
   not. Since it's implementation details, CTS does not help to test the
   behavior.

Test: StorageManagerIntegrationTest
Bug: 29970149

Change-Id: Id78dd4abcf9325820e9c77c264f54bfa77b85a92
parent fdaa9c58
Loading
Loading
Loading
Loading
+19 −5
Original line number Diff line number Diff line
@@ -17,18 +17,21 @@
package android.os;

import android.system.ErrnoException;
import android.system.OsConstants;

/**
 * Callback that handles file system requests from ProxyFileDescriptor.
 * @hide
 */
public interface IProxyFileDescriptorCallback {
public abstract class ProxyFileDescriptorCallback {
    /**
     * Returns size of bytes provided by the file descriptor.
     * @return Size of bytes
     * @throws ErrnoException
     */
    long onGetSize() throws ErrnoException;
    public long onGetSize() throws ErrnoException {
        throw new ErrnoException("onGetSize", OsConstants.EBADF);
    }

    /**
     * Provides bytes read from file descriptor.
@@ -39,7 +42,9 @@ public interface IProxyFileDescriptorCallback {
     * @return Size of bytes returned by the function.
     * @throws ErrnoException
     */
    int onRead(long offset, int size, byte[] data) throws ErrnoException;
    public int onRead(long offset, int size, byte[] data) throws ErrnoException {
        throw new ErrnoException("onRead", OsConstants.EBADF);
    }

    /**
     * Handles bytes written to file descriptor.
@@ -49,11 +54,20 @@ public interface IProxyFileDescriptorCallback {
     * @return Size of bytes processed by the function.
     * @throws ErrnoException
     */
    int onWrite(long offset, int size, byte[] data) throws ErrnoException;
    public int onWrite(long offset, int size, byte[] data) throws ErrnoException {
        throw new ErrnoException("onWrite", OsConstants.EBADF);
    }

    /**
     * Processes fsync request.
     * @throws ErrnoException
     */
    void onFsync() throws ErrnoException;
    public void onFsync() throws ErrnoException {
        throw new ErrnoException("onFsync", OsConstants.EINVAL);
    }

    /**
     * Invoked after the file is closed.
     */
    abstract public void onRelease();
}
+3 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.os.storage.IObbActionListener;
import android.os.storage.StorageVolume;
import android.os.storage.VolumeInfo;
import android.os.storage.VolumeRecord;
import com.android.internal.os.AppFuseMount;

/**
 * WARNING! Update IMountService.h and IMountService.cpp if you change this
@@ -289,4 +290,6 @@ interface IStorageManager {
    void addUserKeyAuth(int userId, int serialNumber, in byte[] token, in byte[] secret) = 70;
    void fixateNewestUserKeyAuth(int userId) = 71;
    void fstrim(int flags) = 72;
    AppFuseMount mountProxyFileDescriptorBridge() = 73;
    ParcelFileDescriptor openProxyFileDescriptor(int mountPointId, int fileId, int mode) = 74;
}
+77 −1
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import android.os.Binder;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
import android.os.ProxyFileDescriptorCallback;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
@@ -44,7 +45,10 @@ import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.AppFuseMount;
import com.android.internal.os.FuseAppLoop;
import com.android.internal.os.RoSystemProperties;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.Preconditions;
@@ -62,6 +66,7 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
@@ -1322,6 +1327,77 @@ public class StorageManager {
        }
    }

    /** {@hide} */
    @VisibleForTesting
    public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
            int mode, ProxyFileDescriptorCallback callback, ThreadFactory factory)
                    throws IOException {
        // Retry is needed because the mount point mFuseAppLoop is using may be unmounted before
        // invoking StorageManagerService#openProxyFileDescriptor. In this case, we need to re-mount
        // the bridge by calling mountProxyFileDescriptorBridge.
        int retry = 3;
        while (retry-- > 0) {
            try {
                synchronized (mFuseAppLoopLock) {
                    if (mFuseAppLoop == null) {
                        final AppFuseMount mount = mStorageManager.mountProxyFileDescriptorBridge();
                        if (mount == null) {
                            Log.e(TAG, "Failed to open proxy file bridge.");
                            throw new IOException("Failed to open proxy file bridge.");
                        }
                        mFuseAppLoop = FuseAppLoop.open(mount.mountPointId, mount.fd, factory);
                    }

                    try {
                        final int fileId = mFuseAppLoop.registerCallback(callback);
                        final ParcelFileDescriptor pfd =
                                mStorageManager.openProxyFileDescriptor(
                                        mFuseAppLoop.getMountPointId(), fileId, mode);
                        if (pfd != null) {
                            return pfd;
                        }
                        // Probably the bridge is being unmounted but mFuseAppLoop has not been
                        // noticed it yet.
                        mFuseAppLoop.unregisterCallback(fileId);
                    } catch (FuseAppLoop.UnmountedException error) {
                        Log.d(TAG, "mFuseAppLoop has been already unmounted.");
                        mFuseAppLoop = null;
                        continue;
                    }
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    break;
                }
            } catch (RemoteException e) {
                e.rethrowFromSystemServer();
            }
        }

        throw new IOException("Failed to mount bridge.");
    }

    /** {@hide} */
    public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
            int mode, ProxyFileDescriptorCallback callback)
                    throws IOException {
        return openProxyFileDescriptor(mode, callback, null);
    }

    /** {@hide} */
    @VisibleForTesting
    public int getProxyFileDescriptorMountPointId() {
        synchronized (mFuseAppLoopLock) {
            return mFuseAppLoop != null ? mFuseAppLoop.getMountPointId() : -1;
        }
    }

    private final Object mFuseAppLoopLock = new Object();

    @GuardedBy("mFuseAppLoopLock")
    private @Nullable FuseAppLoop mFuseAppLoop = null;

    /// Consts to match the password types in cryptfs.h
    /** @hide */
    public static final int CRYPT_TYPE_PASSWORD = 0;
+19 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 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.internal.os;

parcelable AppFuseMount;
+18 −6
Original line number Diff line number Diff line
@@ -19,14 +19,26 @@ package com.android.internal.os;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import java.io.File;
import android.os.storage.IStorageManager;
import com.android.internal.util.Preconditions;

/**
 * Parcelable class representing AppFuse mount.
 * This conveys the result for IStorageManager#openProxyFileDescriptor.
 * @see IStorageManager#openProxyFileDescriptor
 */
public class AppFuseMount implements Parcelable {
    final public File mountPoint;
    final public int mountPointId;
    final public ParcelFileDescriptor fd;

    public AppFuseMount(File mountPoint, ParcelFileDescriptor fd) {
        this.mountPoint = mountPoint;
    /**
     * @param mountPointId Integer number for mount point that is unique in the lifetime of
     *     StorageManagerService.
     * @param fd File descriptor pointing /dev/fuse and tagged with the mount point.
     */
    public AppFuseMount(int mountPointId, ParcelFileDescriptor fd) {
        Preconditions.checkNotNull(fd);
        this.mountPointId = mountPointId;
        this.fd = fd;
    }

@@ -37,7 +49,7 @@ public class AppFuseMount implements Parcelable {

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.mountPoint.getPath());
        dest.writeInt(this.mountPointId);
        dest.writeParcelable(fd, flags);
    }

@@ -45,7 +57,7 @@ public class AppFuseMount implements Parcelable {
            new Parcelable.Creator<AppFuseMount>() {
        @Override
        public AppFuseMount createFromParcel(Parcel in) {
            return new AppFuseMount(new File(in.readString()), in.readParcelable(null));
            return new AppFuseMount(in.readInt(), in.readParcelable(null));
        }

        @Override
Loading