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

Commit 4de48d03 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Allow apps to process ProxyFDCallback asynchrnously."

parents e1024bc3 4f156065
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -31895,6 +31895,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
@@ -34728,6 +34728,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
@@ -32032,6 +32032,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