1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server.storage; 18 19import android.os.ParcelFileDescriptor; 20import android.system.ErrnoException; 21import android.system.Os; 22import android.util.SparseArray; 23import com.android.internal.annotations.GuardedBy; 24import com.android.internal.os.FuseUnavailableMountException; 25import com.android.internal.util.Preconditions; 26import com.android.server.NativeDaemonConnectorException; 27import libcore.io.IoUtils; 28import java.io.File; 29import java.io.FileNotFoundException; 30import java.util.concurrent.CountDownLatch; 31 32/** 33 * Runnable that delegates FUSE command from the kernel to application. 34 * run() blocks until all opened files on the FUSE mount point are closed. So this should be run in 35 * a separated thread. 36 */ 37public class AppFuseBridge implements Runnable { 38 public static final String TAG = "AppFuseBridge"; 39 40 /** 41 * The path AppFuse is mounted to. 42 * The first number is UID who is mounting the FUSE. 43 * THe second number is mount ID. 44 * The path must be sync with vold. 45 */ 46 private static final String APPFUSE_MOUNT_NAME_TEMPLATE = "/mnt/appfuse/%d_%d"; 47 48 @GuardedBy("this") 49 private final SparseArray<MountScope> mScopes = new SparseArray<>(); 50 51 @GuardedBy("this") 52 private long mNativeLoop; 53 54 public AppFuseBridge() { 55 mNativeLoop = native_new(); 56 } 57 58 public ParcelFileDescriptor addBridge(MountScope mountScope) 59 throws FuseUnavailableMountException, NativeDaemonConnectorException { 60 try { 61 synchronized (this) { 62 Preconditions.checkArgument(mScopes.indexOfKey(mountScope.mountId) < 0); 63 if (mNativeLoop == 0) { 64 throw new FuseUnavailableMountException(mountScope.mountId); 65 } 66 final int fd = native_add_bridge( 67 mNativeLoop, mountScope.mountId, mountScope.open().detachFd()); 68 if (fd == -1) { 69 throw new FuseUnavailableMountException(mountScope.mountId); 70 } 71 final ParcelFileDescriptor result = ParcelFileDescriptor.adoptFd(fd); 72 mScopes.put(mountScope.mountId, mountScope); 73 mountScope = null; 74 return result; 75 } 76 } finally { 77 IoUtils.closeQuietly(mountScope); 78 } 79 } 80 81 @Override 82 public void run() { 83 native_start_loop(mNativeLoop); 84 synchronized (this) { 85 native_delete(mNativeLoop); 86 mNativeLoop = 0; 87 } 88 } 89 90 public ParcelFileDescriptor openFile(int pid, int mountId, int fileId, int mode) 91 throws FuseUnavailableMountException, InterruptedException { 92 final MountScope scope; 93 synchronized (this) { 94 scope = mScopes.get(mountId); 95 if (scope == null) { 96 throw new FuseUnavailableMountException(mountId); 97 } 98 } 99 if (scope.pid != pid) { 100 throw new SecurityException("PID does not match"); 101 } 102 final boolean result = scope.waitForMount(); 103 if (result == false) { 104 throw new FuseUnavailableMountException(mountId); 105 } 106 try { 107 return ParcelFileDescriptor.open( 108 new File(scope.mountPoint, String.valueOf(fileId)), mode); 109 } catch (FileNotFoundException error) { 110 throw new FuseUnavailableMountException(mountId); 111 } 112 } 113 114 // Used by com_android_server_storage_AppFuse.cpp. 115 synchronized private void onMount(int mountId) { 116 final MountScope scope = mScopes.get(mountId); 117 if (scope != null) { 118 scope.setMountResultLocked(true); 119 } 120 } 121 122 // Used by com_android_server_storage_AppFuse.cpp. 123 synchronized private void onClosed(int mountId) { 124 final MountScope scope = mScopes.get(mountId); 125 if (scope != null) { 126 scope.setMountResultLocked(false); 127 IoUtils.closeQuietly(scope); 128 mScopes.remove(mountId); 129 } 130 } 131 132 public static abstract class MountScope implements AutoCloseable { 133 public final int uid; 134 public final int pid; 135 public final int mountId; 136 public final File mountPoint; 137 private final CountDownLatch mMounted = new CountDownLatch(1); 138 private boolean mMountResult = false; 139 140 public MountScope(int uid, int pid, int mountId) { 141 this.uid = uid; 142 this.pid = pid; 143 this.mountId = mountId; 144 this.mountPoint = new File(String.format(APPFUSE_MOUNT_NAME_TEMPLATE, uid, mountId)); 145 } 146 147 @GuardedBy("AppFuseBridge.this") 148 void setMountResultLocked(boolean result) { 149 if (mMounted.getCount() == 0) { 150 return; 151 } 152 mMountResult = result; 153 mMounted.countDown(); 154 } 155 156 boolean waitForMount() throws InterruptedException { 157 mMounted.await(); 158 return mMountResult; 159 } 160 161 public abstract ParcelFileDescriptor open() throws NativeDaemonConnectorException; 162 } 163 164 private native long native_new(); 165 private native void native_delete(long loop); 166 private native void native_start_loop(long loop); 167 private native int native_add_bridge(long loop, int mountId, int deviceId); 168} 169