| /* |
| * 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.server.storage; |
| |
| import android.os.FileUtils; |
| import android.os.ParcelFileDescriptor; |
| import android.system.ErrnoException; |
| import android.system.Os; |
| import android.util.SparseArray; |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.os.FuseUnavailableMountException; |
| import com.android.internal.util.Preconditions; |
| import com.android.server.NativeDaemonConnectorException; |
| import libcore.io.IoUtils; |
| import java.util.concurrent.CountDownLatch; |
| |
| /** |
| * Runnable that delegates FUSE command from the kernel to application. |
| * run() blocks until all opened files on the FUSE mount point are closed. So this should be run in |
| * a separated thread. |
| */ |
| public class AppFuseBridge implements Runnable { |
| public static final String TAG = "AppFuseBridge"; |
| |
| /** |
| * The path AppFuse is mounted to. |
| * The first number is UID who is mounting the FUSE. |
| * THe second number is mount ID. |
| * The path must be sync with vold. |
| */ |
| private static final String APPFUSE_MOUNT_NAME_TEMPLATE = "/mnt/appfuse/%d_%d"; |
| |
| @GuardedBy("this") |
| private final SparseArray<MountScope> mScopes = new SparseArray<>(); |
| |
| @GuardedBy("this") |
| private long mNativeLoop; |
| |
| public AppFuseBridge() { |
| mNativeLoop = native_new(); |
| } |
| |
| public ParcelFileDescriptor addBridge(MountScope mountScope) |
| throws FuseUnavailableMountException, NativeDaemonConnectorException { |
| try { |
| synchronized (this) { |
| Preconditions.checkArgument(mScopes.indexOfKey(mountScope.mountId) < 0); |
| if (mNativeLoop == 0) { |
| throw new FuseUnavailableMountException(mountScope.mountId); |
| } |
| final int fd = native_add_bridge( |
| mNativeLoop, mountScope.mountId, mountScope.open().detachFd()); |
| if (fd == -1) { |
| throw new FuseUnavailableMountException(mountScope.mountId); |
| } |
| final ParcelFileDescriptor result = ParcelFileDescriptor.adoptFd(fd); |
| mScopes.put(mountScope.mountId, mountScope); |
| mountScope = null; |
| return result; |
| } |
| } finally { |
| IoUtils.closeQuietly(mountScope); |
| } |
| } |
| |
| @Override |
| public void run() { |
| native_start_loop(mNativeLoop); |
| synchronized (this) { |
| native_delete(mNativeLoop); |
| mNativeLoop = 0; |
| } |
| } |
| |
| public ParcelFileDescriptor openFile(int mountId, int fileId, int mode) |
| throws FuseUnavailableMountException, InterruptedException { |
| final MountScope scope; |
| synchronized (this) { |
| scope = mScopes.get(mountId); |
| if (scope == null) { |
| throw new FuseUnavailableMountException(mountId); |
| } |
| } |
| final boolean result = scope.waitForMount(); |
| if (result == false) { |
| throw new FuseUnavailableMountException(mountId); |
| } |
| try { |
| int flags = FileUtils.translateModePfdToPosix(mode); |
| return scope.openFile(mountId, fileId, flags); |
| } catch (NativeDaemonConnectorException error) { |
| throw new FuseUnavailableMountException(mountId); |
| } |
| } |
| |
| // Used by com_android_server_storage_AppFuse.cpp. |
| synchronized private void onMount(int mountId) { |
| final MountScope scope = mScopes.get(mountId); |
| if (scope != null) { |
| scope.setMountResultLocked(true); |
| } |
| } |
| |
| // Used by com_android_server_storage_AppFuse.cpp. |
| synchronized private void onClosed(int mountId) { |
| final MountScope scope = mScopes.get(mountId); |
| if (scope != null) { |
| scope.setMountResultLocked(false); |
| IoUtils.closeQuietly(scope); |
| mScopes.remove(mountId); |
| } |
| } |
| |
| public static abstract class MountScope implements AutoCloseable { |
| public final int uid; |
| public final int mountId; |
| private final CountDownLatch mMounted = new CountDownLatch(1); |
| private boolean mMountResult = false; |
| |
| public MountScope(int uid, int mountId) { |
| this.uid = uid; |
| this.mountId = mountId; |
| } |
| |
| @GuardedBy("AppFuseBridge.this") |
| void setMountResultLocked(boolean result) { |
| if (mMounted.getCount() == 0) { |
| return; |
| } |
| mMountResult = result; |
| mMounted.countDown(); |
| } |
| |
| boolean waitForMount() throws InterruptedException { |
| mMounted.await(); |
| return mMountResult; |
| } |
| |
| public abstract ParcelFileDescriptor open() throws NativeDaemonConnectorException; |
| public abstract ParcelFileDescriptor openFile(int mountId, int fileId, int flags) |
| throws NativeDaemonConnectorException; |
| } |
| |
| private native long native_new(); |
| private native void native_delete(long loop); |
| private native void native_start_loop(long loop); |
| private native int native_add_bridge(long loop, int mountId, int deviceId); |
| } |