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 17 package com.android.internal.os; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.UnsupportedAppUsage; 22 import android.os.ProxyFileDescriptorCallback; 23 import android.os.Handler; 24 import android.os.Message; 25 import android.os.ParcelFileDescriptor; 26 import android.system.ErrnoException; 27 import android.system.OsConstants; 28 import android.util.Log; 29 import android.util.SparseArray; 30 import com.android.internal.annotations.GuardedBy; 31 import com.android.internal.util.Preconditions; 32 import java.util.HashMap; 33 import java.util.LinkedList; 34 import java.util.Map; 35 import java.util.concurrent.ThreadFactory; 36 37 public class FuseAppLoop implements Handler.Callback { 38 private static final String TAG = "FuseAppLoop"; 39 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 40 public static final int ROOT_INODE = 1; 41 private static final int MIN_INODE = 2; 42 private static final ThreadFactory sDefaultThreadFactory = new ThreadFactory() { 43 @Override 44 public Thread newThread(Runnable r) { 45 return new Thread(r, TAG); 46 } 47 }; 48 private static final int FUSE_OK = 0; 49 private static final int ARGS_POOL_SIZE = 50; 50 51 private final Object mLock = new Object(); 52 private final int mMountPointId; 53 private final Thread mThread; 54 55 @GuardedBy("mLock") 56 private final SparseArray<CallbackEntry> mCallbackMap = new SparseArray<>(); 57 58 @GuardedBy("mLock") 59 private final BytesMap mBytesMap = new BytesMap(); 60 61 @GuardedBy("mLock") 62 private final LinkedList<Args> mArgsPool = new LinkedList<>(); 63 64 /** 65 * Sequential number can be used as file name and inode in AppFuse. 66 * 0 is regarded as an error, 1 is mount point. So we start the number from 2. 67 */ 68 @GuardedBy("mLock") 69 private int mNextInode = MIN_INODE; 70 71 @GuardedBy("mLock") 72 private long mInstance; 73 FuseAppLoop( int mountPointId, @NonNull ParcelFileDescriptor fd, @Nullable ThreadFactory factory)74 public FuseAppLoop( 75 int mountPointId, @NonNull ParcelFileDescriptor fd, @Nullable ThreadFactory factory) { 76 mMountPointId = mountPointId; 77 if (factory == null) { 78 factory = sDefaultThreadFactory; 79 } 80 mInstance = native_new(fd.detachFd()); 81 mThread = factory.newThread(() -> { 82 native_start(mInstance); 83 synchronized (mLock) { 84 native_delete(mInstance); 85 mInstance = 0; 86 mBytesMap.clear(); 87 } 88 }); 89 mThread.start(); 90 } 91 registerCallback(@onNull ProxyFileDescriptorCallback callback, @NonNull Handler handler)92 public int registerCallback(@NonNull ProxyFileDescriptorCallback callback, 93 @NonNull Handler handler) throws FuseUnavailableMountException { 94 synchronized (mLock) { 95 Preconditions.checkNotNull(callback); 96 Preconditions.checkNotNull(handler); 97 Preconditions.checkState( 98 mCallbackMap.size() < Integer.MAX_VALUE - MIN_INODE, "Too many opened files."); 99 Preconditions.checkArgument( 100 Thread.currentThread().getId() != handler.getLooper().getThread().getId(), 101 "Handler must be different from the current thread"); 102 if (mInstance == 0) { 103 throw new FuseUnavailableMountException(mMountPointId); 104 } 105 int id; 106 while (true) { 107 id = mNextInode; 108 mNextInode++; 109 if (mNextInode < 0) { 110 mNextInode = MIN_INODE; 111 } 112 if (mCallbackMap.get(id) == null) { 113 break; 114 } 115 } 116 mCallbackMap.put(id, new CallbackEntry( 117 callback, new Handler(handler.getLooper(), this))); 118 return id; 119 } 120 } 121 122 public void unregisterCallback(int id) { 123 synchronized (mLock) { 124 mCallbackMap.remove(id); 125 } 126 } 127 128 public int getMountPointId() { 129 return mMountPointId; 130 } 131 132 // Defined in fuse.h 133 private static final int FUSE_LOOKUP = 1; 134 private static final int FUSE_GETATTR = 3; 135 private static final int FUSE_OPEN = 14; 136 private static final int FUSE_READ = 15; 137 private static final int FUSE_WRITE = 16; 138 private static final int FUSE_RELEASE = 18; 139 private static final int FUSE_FSYNC = 20; 140 141 // Defined in FuseBuffer.h 142 private static final int FUSE_MAX_WRITE = 128 * 1024; 143 144 @Override 145 public boolean handleMessage(Message msg) { 146 final Args args = (Args) msg.obj; 147 final CallbackEntry entry = args.entry; 148 final long inode = args.inode; 149 final long unique = args.unique; 150 final int size = args.size; 151 final long offset = args.offset; 152 final byte[] data = args.data; 153 154 try { 155 switch (msg.what) { 156 case FUSE_LOOKUP: { 157 final long fileSize = entry.callback.onGetSize(); 158 synchronized (mLock) { 159 if (mInstance != 0) { 160 native_replyLookup(mInstance, unique, inode, fileSize); 161 } 162 recycleLocked(args); 163 } 164 break; 165 } 166 case FUSE_GETATTR: { 167 final long fileSize = entry.callback.onGetSize(); 168 synchronized (mLock) { 169 if (mInstance != 0) { 170 native_replyGetAttr(mInstance, unique, inode, fileSize); 171 } 172 recycleLocked(args); 173 } 174 break; 175 } 176 case FUSE_READ: 177 final int readSize = entry.callback.onRead( 178 offset, size, data); 179 synchronized (mLock) { 180 if (mInstance != 0) { 181 native_replyRead(mInstance, unique, readSize, data); 182 } 183 recycleLocked(args); 184 } 185 break; 186 case FUSE_WRITE: 187 final int writeSize = entry.callback.onWrite(offset, size, data); 188 synchronized (mLock) { 189 if (mInstance != 0) { 190 native_replyWrite(mInstance, unique, writeSize); 191 } 192 recycleLocked(args); 193 } 194 break; 195 case FUSE_FSYNC: 196 entry.callback.onFsync(); 197 synchronized (mLock) { 198 if (mInstance != 0) { 199 native_replySimple(mInstance, unique, FUSE_OK); 200 } 201 recycleLocked(args); 202 } 203 break; 204 case FUSE_RELEASE: 205 entry.callback.onRelease(); 206 synchronized (mLock) { 207 if (mInstance != 0) { 208 native_replySimple(mInstance, unique, FUSE_OK); 209 } 210 mBytesMap.stopUsing(entry.getThreadId()); 211 recycleLocked(args); 212 } 213 break; 214 default: 215 throw new IllegalArgumentException("Unknown FUSE command: " + msg.what); 216 } 217 } catch (Exception error) { 218 synchronized (mLock) { 219 Log.e(TAG, "", error); 220 replySimpleLocked(unique, getError(error)); 221 recycleLocked(args); 222 } 223 } 224 225 return true; 226 } 227 228 // Called by JNI. 229 @SuppressWarnings("unused") 230 @UnsupportedAppUsage 231 private void onCommand(int command, long unique, long inode, long offset, int size, 232 byte[] data) { 233 synchronized (mLock) { 234 try { 235 final Args args; 236 if (mArgsPool.size() == 0) { 237 args = new Args(); 238 } else { 239 args = mArgsPool.pop(); 240 } 241 args.unique = unique; 242 args.inode = inode; 243 args.offset = offset; 244 args.size = size; 245 args.data = data; 246 args.entry = getCallbackEntryOrThrowLocked(inode); 247 if (!args.entry.handler.sendMessage( 248 Message.obtain(args.entry.handler, command, 0, 0, args))) { 249 throw new ErrnoException("onCommand", OsConstants.EBADF); 250 } 251 } catch (Exception error) { 252 replySimpleLocked(unique, getError(error)); 253 } 254 } 255 } 256 257 // Called by JNI. 258 @SuppressWarnings("unused") 259 @UnsupportedAppUsage 260 private byte[] onOpen(long unique, long inode) { 261 synchronized (mLock) { 262 try { 263 final CallbackEntry entry = getCallbackEntryOrThrowLocked(inode); 264 if (entry.opened) { 265 throw new ErrnoException("onOpen", OsConstants.EMFILE); 266 } 267 if (mInstance != 0) { 268 native_replyOpen(mInstance, unique, /* fh */ inode); 269 entry.opened = true; 270 return mBytesMap.startUsing(entry.getThreadId()); 271 } 272 } catch (ErrnoException error) { 273 replySimpleLocked(unique, getError(error)); 274 } 275 return null; 276 } 277 } 278 279 private static int getError(@NonNull Exception error) { 280 if (error instanceof ErrnoException) { 281 final int errno = ((ErrnoException) error).errno; 282 if (errno != OsConstants.ENOSYS) { 283 return -errno; 284 } 285 } 286 return -OsConstants.EBADF; 287 } 288 289 @GuardedBy("mLock") 290 private CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException { 291 final CallbackEntry entry = mCallbackMap.get(checkInode(inode)); 292 if (entry == null) { 293 throw new ErrnoException("getCallbackEntryOrThrowLocked", OsConstants.ENOENT); 294 } 295 return entry; 296 } 297 298 @GuardedBy("mLock") 299 private void recycleLocked(Args args) { 300 if (mArgsPool.size() < ARGS_POOL_SIZE) { 301 mArgsPool.add(args); 302 } 303 } 304 305 @GuardedBy("mLock") 306 private void replySimpleLocked(long unique, int result) { 307 if (mInstance != 0) { 308 native_replySimple(mInstance, unique, result); 309 } 310 } 311 312 native long native_new(int fd); 313 native void native_delete(long ptr); 314 native void native_start(long ptr); 315 316 native void native_replySimple(long ptr, long unique, int result); 317 native void native_replyOpen(long ptr, long unique, long fh); 318 native void native_replyLookup(long ptr, long unique, long inode, long size); 319 native void native_replyGetAttr(long ptr, long unique, long inode, long size); 320 native void native_replyWrite(long ptr, long unique, int size); 321 native void native_replyRead(long ptr, long unique, int size, byte[] bytes); 322 323 private static int checkInode(long inode) { 324 Preconditions.checkArgumentInRange(inode, MIN_INODE, Integer.MAX_VALUE, "checkInode"); 325 return (int) inode; 326 } 327 328 public static class UnmountedException extends Exception {} 329 330 private static class CallbackEntry { 331 final ProxyFileDescriptorCallback callback; 332 final Handler handler; 333 boolean opened; 334 335 CallbackEntry(ProxyFileDescriptorCallback callback, Handler handler) { 336 this.callback = Preconditions.checkNotNull(callback); 337 this.handler = Preconditions.checkNotNull(handler); 338 } 339 340 long getThreadId() { 341 return handler.getLooper().getThread().getId(); 342 } 343 } 344 345 /** 346 * Entry for bytes map. 347 */ 348 private static class BytesMapEntry { 349 int counter = 0; 350 byte[] bytes = new byte[FUSE_MAX_WRITE]; 351 } 352 353 /** 354 * Map between Thread ID and byte buffer. 355 */ 356 private static class BytesMap { 357 final Map<Long, BytesMapEntry> mEntries = new HashMap<>(); 358 359 byte[] startUsing(long threadId) { 360 BytesMapEntry entry = mEntries.get(threadId); 361 if (entry == null) { 362 entry = new BytesMapEntry(); 363 mEntries.put(threadId, entry); 364 } 365 entry.counter++; 366 return entry.bytes; 367 } 368 369 void stopUsing(long threadId) { 370 final BytesMapEntry entry = mEntries.get(threadId); 371 Preconditions.checkNotNull(entry); 372 entry.counter--; 373 if (entry.counter <= 0) { 374 mEntries.remove(threadId); 375 } 376 } 377 378 void clear() { 379 mEntries.clear(); 380 } 381 } 382 383 private static class Args { 384 long unique; 385 long inode; 386 long offset; 387 int size; 388 byte[] data; 389 CallbackEntry entry; 390 } 391 } 392