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 mBytesMap.stopUsing(inode); 215 recycleLocked(args); 216 } 217 break; 218 default: 219 throw new IllegalArgumentException("Unknown FUSE command: " + msg.what); 220 } 221 } catch (Exception error) { 222 synchronized (mLock) { 223 Log.e(TAG, "", error); 224 replySimpleLocked(unique, getError(error)); 225 recycleLocked(args); 226 } 227 } 228 229 return true; 230 } 231 232 // Called by JNI. 233 @SuppressWarnings("unused") 234 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 235 private void onCommand(int command, long unique, long inode, long offset, int size, 236 byte[] data) { 237 synchronized (mLock) { 238 try { 239 final Args args; 240 if (mArgsPool.size() == 0) { 241 args = new Args(); 242 } else { 243 args = mArgsPool.pop(); 244 } 245 args.unique = unique; 246 args.inode = inode; 247 args.offset = offset; 248 args.size = size; 249 args.data = data; 250 args.entry = getCallbackEntryOrThrowLocked(inode); 251 if (!args.entry.handler.sendMessage( 252 Message.obtain(args.entry.handler, command, 0, 0, args))) { 253 throw new ErrnoException("onCommand", OsConstants.EBADF); 254 } 255 } catch (Exception error) { 256 replySimpleLocked(unique, getError(error)); 257 } 258 } 259 } 260 261 // Called by JNI. 262 @SuppressWarnings("unused") 263 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 264 private byte[] onOpen(long unique, long inode) { 265 synchronized (mLock) { 266 try { 267 final CallbackEntry entry = getCallbackEntryOrThrowLocked(inode); 268 if (entry.opened) { 269 throw new ErrnoException("onOpen", OsConstants.EMFILE); 270 } 271 if (mInstance != 0) { 272 native_replyOpen(mInstance, unique, /* fh */ inode); 273 entry.opened = true; 274 return mBytesMap.startUsing(inode); 275 } 276 } catch (ErrnoException error) { 277 replySimpleLocked(unique, getError(error)); 278 } 279 return null; 280 } 281 } 282 283 private static int getError(@NonNull Exception error) { 284 if (error instanceof ErrnoException) { 285 final int errno = ((ErrnoException) error).errno; 286 if (errno != OsConstants.ENOSYS) { 287 return -errno; 288 } 289 } 290 return -OsConstants.EBADF; 291 } 292 293 @GuardedBy("mLock") 294 private CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException { 295 final CallbackEntry entry = mCallbackMap.get(checkInode(inode)); 296 if (entry == null) { 297 throw new ErrnoException("getCallbackEntryOrThrowLocked", OsConstants.ENOENT); 298 } 299 return entry; 300 } 301 302 @GuardedBy("mLock") 303 private void recycleLocked(Args args) { 304 if (mArgsPool.size() < ARGS_POOL_SIZE) { 305 mArgsPool.add(args); 306 } 307 } 308 309 @GuardedBy("mLock") 310 private void replySimpleLocked(long unique, int result) { 311 if (mInstance != 0) { 312 native_replySimple(mInstance, unique, result); 313 } 314 } 315 316 native long native_new(int fd); 317 native void native_delete(long ptr); 318 native void native_start(long ptr); 319 320 native void native_replySimple(long ptr, long unique, int result); 321 native void native_replyOpen(long ptr, long unique, long fh); 322 native void native_replyLookup(long ptr, long unique, long inode, long size); 323 native void native_replyGetAttr(long ptr, long unique, long inode, long size); 324 native void native_replyWrite(long ptr, long unique, int size); 325 native void native_replyRead(long ptr, long unique, int size, byte[] bytes); 326 327 private static int checkInode(long inode) { 328 Preconditions.checkArgumentInRange(inode, MIN_INODE, Integer.MAX_VALUE, "checkInode"); 329 return (int) inode; 330 } 331 332 public static class UnmountedException extends Exception {} 333 334 private static class CallbackEntry { 335 final ProxyFileDescriptorCallback callback; 336 final Handler handler; 337 boolean opened; 338 339 CallbackEntry(ProxyFileDescriptorCallback callback, Handler handler) { 340 this.callback = Objects.requireNonNull(callback); 341 this.handler = Objects.requireNonNull(handler); 342 } 343 344 long getThreadId() { 345 return handler.getLooper().getThread().getId(); 346 } 347 } 348 349 /** 350 * Entry for bytes map. 351 */ 352 private static class BytesMapEntry { 353 int counter = 0; 354 byte[] bytes = new byte[FUSE_MAX_WRITE]; 355 } 356 357 /** 358 * Map between inode and byte buffer. 359 */ 360 private static class BytesMap { 361 final Map<Long, BytesMapEntry> mEntries = new HashMap<>(); 362 363 byte[] startUsing(long inode) { 364 BytesMapEntry entry = mEntries.get(inode); 365 if (entry == null) { 366 entry = new BytesMapEntry(); 367 mEntries.put(inode, entry); 368 } 369 entry.counter++; 370 return entry.bytes; 371 } 372 373 void stopUsing(long inode) { 374 final BytesMapEntry entry = mEntries.get(inode); 375 Objects.requireNonNull(entry); 376 entry.counter--; 377 if (entry.counter <= 0) { 378 mEntries.remove(inode); 379 } 380 } 381 382 void clear() { 383 mEntries.clear(); 384 } 385 } 386 387 private static class Args { 388 long unique; 389 long inode; 390 long offset; 391 int size; 392 byte[] data; 393 CallbackEntry entry; 394 } 395 } 396