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