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