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