• 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.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