• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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