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

Commit dfc108a6 authored by Daichi Hirono's avatar Daichi Hirono Committed by android-build-merger
Browse files

Merge "Allow apps to process ProxyFDCallback asynchrnously." into oc-dev

am: 76c32a35

Change-Id: I9e17751f706f9e0869eefafb77bf1704335f5116
parents f73d8010 76c32a35
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -31898,6 +31898,7 @@ package android.os.storage {
    method public boolean isObbMounted(java.lang.String);
    method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
    method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
    method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback, android.os.Handler) throws java.io.IOException;
    method public void setCacheBehaviorGroup(java.io.File, boolean) throws java.io.IOException;
    method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
    method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
+1 −0
Original line number Diff line number Diff line
@@ -34731,6 +34731,7 @@ package android.os.storage {
    method public boolean isObbMounted(java.lang.String);
    method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
    method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
    method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback, android.os.Handler) throws java.io.IOException;
    method public void setCacheBehaviorGroup(java.io.File, boolean) throws java.io.IOException;
    method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
    method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
+1 −0
Original line number Diff line number Diff line
@@ -32035,6 +32035,7 @@ package android.os.storage {
    method public boolean isObbMounted(java.lang.String);
    method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
    method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
    method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback, android.os.Handler) throws java.io.IOException;
    method public void setCacheBehaviorGroup(java.io.File, boolean) throws java.io.IOException;
    method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
    method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
+51 −28
Original line number Diff line number Diff line
@@ -59,6 +59,8 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.os.AppFuseMount;
import com.android.internal.os.FuseAppLoop;
import com.android.internal.os.FuseAppLoop.UnmountedException;
import com.android.internal.os.FuseUnavailableMountException;
import com.android.internal.os.RoSystemProperties;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.Preconditions;
@@ -82,6 +84,7 @@ import java.util.List;
import java.util.Objects;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import libcore.io.IoUtils;

/**
 * StorageManager is the interface to the systems storage service. The storage
@@ -1390,53 +1393,52 @@ public class StorageManager {
    /** {@hide} */
    @VisibleForTesting
    public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
            int mode, ProxyFileDescriptorCallback callback, ThreadFactory factory)
            int mode, ProxyFileDescriptorCallback callback, Handler handler, ThreadFactory factory)
                    throws IOException {
        MetricsLogger.count(mContext, "storage_open_proxy_file_descriptor", 1);
        // 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) {
        while (true) {
            try {
                synchronized (mFuseAppLoopLock) {
                    boolean newlyCreated = false;
                    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.");
                            throw new IOException("Failed to mount proxy bridge");
                        }
                        mFuseAppLoop = FuseAppLoop.open(mount.mountPointId, mount.fd, factory);
                        mFuseAppLoop = new FuseAppLoop(mount.mountPointId, mount.fd, factory);
                        newlyCreated = true;
                    }
                    if (handler == null) {
                        handler = new Handler(Looper.getMainLooper());
                    }

                    try {
                        final int fileId = mFuseAppLoop.registerCallback(callback);
                        final ParcelFileDescriptor pfd =
                                mStorageManager.openProxyFileDescriptor(
                        final int fileId = mFuseAppLoop.registerCallback(callback, handler);
                        final ParcelFileDescriptor pfd = mStorageManager.openProxyFileDescriptor(
                                mFuseAppLoop.getMountPointId(), fileId, mode);
                        if (pfd != null) {
                        if (pfd == null) {
                            mFuseAppLoop.unregisterCallback(fileId);
                            throw new FuseUnavailableMountException(
                                    mFuseAppLoop.getMountPointId());
                        }
                        return pfd;
                    } catch (FuseUnavailableMountException exception) {
                        // The bridge is being unmounted. Tried to recreate it unless the bridge was
                        // just created.
                        if (newlyCreated) {
                            throw new IOException(exception);
                        }
                        // 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();
                // Cannot recover from remote exception.
                throw new IOException(e);
            }
        }

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

    /**
@@ -1448,16 +1450,37 @@ public class StorageManager {
     *     {@link ParcelFileDescriptor#MODE_WRITE_ONLY}, or
     *     {@link ParcelFileDescriptor#MODE_READ_WRITE}
     * @param callback Callback to process file operation requests issued on returned file
     *     descriptor. The callback is invoked on a thread managed by the framework.
     *     descriptor.
     * @return Seekable ParcelFileDescriptor.
     * @throws IOException
     */
    public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
            int mode, ProxyFileDescriptorCallback callback)
                    throws IOException {
        return openProxyFileDescriptor(mode, callback, null);
        return openProxyFileDescriptor(mode, callback, null, null);
    }

    /**
     * Opens seekable ParcelFileDescriptor that routes file operation requests to
     * ProxyFileDescriptorCallback.
     *
     * @param mode The desired access mode, must be one of
     *     {@link ParcelFileDescriptor#MODE_READ_ONLY},
     *     {@link ParcelFileDescriptor#MODE_WRITE_ONLY}, or
     *     {@link ParcelFileDescriptor#MODE_READ_WRITE}
     * @param callback Callback to process file operation requests issued on returned file
     *     descriptor.
     * @param handler Handler that invokes callback methods.
     * @return Seekable ParcelFileDescriptor.
     * @throws IOException
     */
    public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
            int mode, ProxyFileDescriptorCallback callback, Handler handler)
                    throws IOException {
        return openProxyFileDescriptor(mode, callback, handler, null);
    }


    /** {@hide} */
    @VisibleForTesting
    public int getProxyFileDescriptorMountPointId() {
+208 −95
Original line number Diff line number Diff line
@@ -19,16 +19,16 @@ package com.android.internal.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.ProxyFileDescriptorCallback;
import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.OsConstants;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ThreadFactory;

public class FuseAppLoop {
@@ -42,14 +42,21 @@ public class FuseAppLoop {
            return new Thread(r, TAG);
        }
    };
    private static final int FUSE_OK = 0;

    private final Object mLock = new Object();
    private final int mMountPointId;
    private final Thread mThread;
    private final Handler mDefaultHandler;

    private static final int CMD_FSYNC = 1;

    @GuardedBy("mLock")
    private final SparseArray<CallbackEntry> mCallbackMap = new SparseArray<>();

    @GuardedBy("mLock")
    private final BytesMap mBytesMap = new BytesMap();

    /**
     * Sequential number can be used as file name and inode in AppFuse.
     * 0 is regarded as an error, 1 is mount point. So we start the number from 2.
@@ -57,38 +64,40 @@ public class FuseAppLoop {
    @GuardedBy("mLock")
    private int mNextInode = MIN_INODE;

    private FuseAppLoop(
    @GuardedBy("mLock")
    private long mInstance;

    public FuseAppLoop(
            int mountPointId, @NonNull ParcelFileDescriptor fd, @Nullable ThreadFactory factory) {
        mMountPointId = mountPointId;
        final int rawFd = fd.detachFd();
        if (factory == null) {
            factory = sDefaultThreadFactory;
        }
        mThread = factory.newThread(new Runnable() {
            @Override
            public void run() {
                // rawFd is closed by native_start_loop. Java code does not need to close it.
                native_start_loop(rawFd);
        mInstance = native_new(fd.detachFd());
        mThread = factory.newThread(() -> {
            native_start(mInstance);
            synchronized (mLock) {
                native_delete(mInstance);
                mInstance = 0;
                mBytesMap.clear();
            }
        });
        mThread.start();
        mDefaultHandler = null;
    }

    public static @NonNull FuseAppLoop open(int mountPointId, @NonNull ParcelFileDescriptor fd,
            @Nullable ThreadFactory factory) {
        Preconditions.checkNotNull(fd);
        final FuseAppLoop loop = new FuseAppLoop(mountPointId, fd, factory);
        loop.mThread.start();
        return loop;
    }

    public int registerCallback(@NonNull ProxyFileDescriptorCallback callback)
            throws UnmountedException, IOException {
        if (mThread.getState() == Thread.State.TERMINATED) {
            throw new UnmountedException();
        }
    public int registerCallback(@NonNull ProxyFileDescriptorCallback callback,
            @NonNull Handler handler) throws FuseUnavailableMountException {
        synchronized (mLock) {
            if (mCallbackMap.size() >= Integer.MAX_VALUE - MIN_INODE) {
                throw new IOException("Too many opened files.");
            Preconditions.checkNotNull(callback);
            Preconditions.checkNotNull(handler);
            Preconditions.checkState(
                    mCallbackMap.size() < Integer.MAX_VALUE - MIN_INODE, "Too many opened files.");
            Preconditions.checkArgument(
                    Thread.currentThread().getId() != handler.getLooper().getThread().getId(),
                    "Handler must be different from the current thread");
            if (mInstance == 0) {
                throw new FuseUnavailableMountException(mMountPointId);
            }
            int id;
            while (true) {
@@ -101,118 +110,171 @@ public class FuseAppLoop {
                    break;
                }
            }
            mCallbackMap.put(id, new CallbackEntry(callback));
            mCallbackMap.put(id, new CallbackEntry(callback, handler));
            return id;
        }
    }

    public void unregisterCallback(int id) {
        synchronized (mLock) {
            mCallbackMap.remove(id);
        }
    }

    public int getMountPointId() {
        return mMountPointId;
    }

    private CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException {
        final CallbackEntry entry = mCallbackMap.get(checkInode(inode));
        if (entry != null) {
            return entry;
        } else {
            throw new ErrnoException("getCallbackEntry", OsConstants.ENOENT);
        }
    }
    // Defined in fuse.h
    private static final int FUSE_LOOKUP = 1;
    private static final int FUSE_GETATTR = 3;
    private static final int FUSE_OPEN = 14;
    private static final int FUSE_READ = 15;
    private static final int FUSE_WRITE = 16;
    private static final int FUSE_RELEASE = 18;
    private static final int FUSE_FSYNC = 20;

    // Defined in FuseBuffer.h
    private static final int FUSE_MAX_WRITE = 256 * 1024;

    // Called by JNI.
    @SuppressWarnings("unused")
    private long onGetSize(long inode) {
    private void onCommand(int command, long unique, long inode, long offset, int size,
            byte[] data) {
        synchronized(mLock) {
            try {
                return getCallbackEntryOrThrowLocked(inode).callback.onGetSize();
            } catch (ErrnoException exp) {
                return getError(exp);
                final CallbackEntry entry = getCallbackEntryOrThrowLocked(inode);
                entry.postRunnable(() -> {
                    try {
                        switch (command) {
                            case FUSE_LOOKUP: {
                                final long fileSize = entry.callback.onGetSize();
                                synchronized (mLock) {
                                    if (mInstance != 0) {
                                        native_replyLookup(mInstance, unique, inode, fileSize);
                                    }
                                }
                                break;
                            }
                            case FUSE_GETATTR: {
                                final long fileSize = entry.callback.onGetSize();
                                synchronized (mLock) {
                                    if (mInstance != 0) {
                                        native_replyGetAttr(mInstance, unique, inode, fileSize);
                                    }
                                }
                                break;
                            }
                            case FUSE_READ:
                                final int readSize = entry.callback.onRead(offset, size, data);
                                synchronized (mLock) {
                                    if (mInstance != 0) {
                                        native_replyRead(mInstance, unique, readSize, data);
                                    }
                                }
                                break;
                            case FUSE_WRITE:
                                final int writeSize = entry.callback.onWrite(offset, size, data);
                                synchronized (mLock) {
                                    if (mInstance != 0) {
                                        native_replyWrite(mInstance, unique, writeSize);
                                    }
                                }
                                break;
                            case FUSE_FSYNC:
                                entry.callback.onFsync();
                                synchronized (mLock) {
                                    if (mInstance != 0) {
                                        native_replySimple(mInstance, unique, FUSE_OK);
                                    }
                                }
                                break;
                            case FUSE_RELEASE:
                                entry.callback.onRelease();
                                synchronized (mLock) {
                                    if (mInstance != 0) {
                                        native_replySimple(mInstance, unique, FUSE_OK);
                                    }
                                    mBytesMap.stopUsing(entry.getThreadId());
                                }
                                break;
                            default:
                                throw new IllegalArgumentException(
                                        "Unknown FUSE command: " + command);
                        }
                    } catch (Exception error) {
                        Log.e(TAG, "", error);
                        replySimple(unique, getError(error));
                    }
                });
            } catch (ErrnoException error) {
                Log.e(TAG, "", error);
                replySimpleLocked(unique, getError(error));
            }
        }
    }

    // Called by JNI.
    @SuppressWarnings("unused")
    private int onOpen(long inode) {
    private byte[] onOpen(long unique, long inode) {
        synchronized (mLock) {
            try {
                final CallbackEntry entry = getCallbackEntryOrThrowLocked(inode);
                if (entry.opened) {
                    throw new ErrnoException("onOpen", OsConstants.EMFILE);
                }
                if (mInstance != 0) {
                    native_replyOpen(mInstance, unique, /* fh */ inode);
                    entry.opened = true;
                // Use inode as file handle. It's OK because AppFuse does not allow to open the same
                // file twice.
                return (int) inode;
            } catch (ErrnoException exp) {
                return getError(exp);
                    return mBytesMap.startUsing(entry.getThreadId());
                }
            } catch (ErrnoException error) {
                replySimpleLocked(unique, getError(error));
            }
            return null;
        }
    }

    // Called by JNI.
    @SuppressWarnings("unused")
    private int onFsync(long inode) {
        synchronized(mLock) {
            try {
                getCallbackEntryOrThrowLocked(inode).callback.onFsync();
                return 0;
            } catch (ErrnoException exp) {
                return getError(exp);
    private static int getError(@NonNull Exception error) {
        if (error instanceof ErrnoException) {
            final int errno = ((ErrnoException) error).errno;
            if (errno != OsConstants.ENOSYS) {
                return -errno;
            }
        }
        return -OsConstants.EBADF;
    }

    // Called by JNI.
    @SuppressWarnings("unused")
    private int onRelease(long inode) {
        synchronized(mLock) {
            try {
                getCallbackEntryOrThrowLocked(inode).callback.onRelease();
                return 0;
            } catch (ErrnoException exp) {
                return getError(exp);
            } finally {
                mCallbackMap.remove(checkInode(inode));
            }
    private CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException {
        final CallbackEntry entry = mCallbackMap.get(checkInode(inode));
        if (entry == null) {
            throw new ErrnoException("getCallbackEntryOrThrowLocked", OsConstants.ENOENT);
        }
        return entry;
    }

    // Called by JNI.
    @SuppressWarnings("unused")
    private int onRead(long inode, long offset, int size, byte[] bytes) {
    private void replySimple(long unique, int result) {
        synchronized (mLock) {
            try {
                return getCallbackEntryOrThrowLocked(inode).callback.onRead(offset, size, bytes);
            } catch (ErrnoException exp) {
                return getError(exp);
            }
            replySimpleLocked(unique, result);
        }
    }

    // Called by JNI.
    @SuppressWarnings("unused")
    private int onWrite(long inode, long offset, int size, byte[] bytes) {
        synchronized(mLock) {
            try {
                return getCallbackEntryOrThrowLocked(inode).callback.onWrite(offset, size, bytes);
            } catch (ErrnoException exp) {
                return getError(exp);
            }
    private void replySimpleLocked(long unique, int result) {
        if (mInstance != 0) {
            native_replySimple(mInstance, unique, result);
        }
    }

    private static int getError(@NonNull ErrnoException exp) {
        // Should not return ENOSYS because the kernel stops
        // dispatching the FUSE action once FUSE implementation returns ENOSYS for the action.
        return exp.errno != OsConstants.ENOSYS ? -exp.errno : -OsConstants.EIO;
    }
    native long native_new(int fd);
    native void native_delete(long ptr);
    native void native_start(long ptr);

    native boolean native_start_loop(int fd);
    native void native_replySimple(long ptr, long unique, int result);
    native void native_replyOpen(long ptr, long unique, long fh);
    native void native_replyLookup(long ptr, long unique, long inode, long size);
    native void native_replyGetAttr(long ptr, long unique, long inode, long size);
    native void native_replyWrite(long ptr, long unique, int size);
    native void native_replyRead(long ptr, long unique, int size, byte[] bytes);

    private static int checkInode(long inode) {
        Preconditions.checkArgumentInRange(inode, MIN_INODE, Integer.MAX_VALUE, "checkInode");
@@ -223,10 +285,61 @@ public class FuseAppLoop {

    private static class CallbackEntry {
        final ProxyFileDescriptorCallback callback;
        final Handler handler;
        boolean opened;
        CallbackEntry(ProxyFileDescriptorCallback callback) {
            Preconditions.checkNotNull(callback);
            this.callback = callback;

        CallbackEntry(ProxyFileDescriptorCallback callback, Handler handler) {
            this.callback = Preconditions.checkNotNull(callback);
            this.handler = Preconditions.checkNotNull(handler);
        }

        long getThreadId() {
            return handler.getLooper().getThread().getId();
        }

        void postRunnable(Runnable runnable) throws ErrnoException {
            final boolean result = handler.post(runnable);
            if (!result) {
                throw new ErrnoException("postRunnable", OsConstants.EBADF);
            }
        }
    }

    /**
     * Entry for bytes map.
     */
    private static class BytesMapEntry {
        int counter = 0;
        byte[] bytes = new byte[FUSE_MAX_WRITE];
    }

    /**
     * Map between Thread ID and byte buffer.
     */
    private static class BytesMap {
        final Map<Long, BytesMapEntry> mEntries = new HashMap<>();

        byte[] startUsing(long threadId) {
            BytesMapEntry entry = mEntries.get(threadId);
            if (entry == null) {
                entry = new BytesMapEntry();
                mEntries.put(threadId, entry);
            }
            entry.counter++;
            return entry.bytes;
        }

        void stopUsing(long threadId) {
            final BytesMapEntry entry = mEntries.get(threadId);
            Preconditions.checkNotNull(entry);
            entry.counter--;
            if (entry.counter <= 0) {
                mEntries.remove(threadId);
            }
        }

        void clear() {
            mEntries.clear();
        }
    }
}
Loading