blob: d08930b007ffb3c76f9d3ca51e6e928fe7f3f2b1 [file] [log] [blame]
Daichi Hirono878e86f2016-10-31 09:33:30 +09001/*
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
17package com.android.internal.os;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
Mathew Inwoodc185f082018-08-20 14:28:54 +010021import android.annotation.UnsupportedAppUsage;
Daichi Hirono9fb00182016-11-08 14:12:17 +090022import android.os.ProxyFileDescriptorCallback;
Daichi Hirono4f156062017-02-08 16:20:20 +090023import android.os.Handler;
Daichi Hirono5c2688a2017-04-03 13:18:40 +090024import android.os.Message;
Daichi Hirono878e86f2016-10-31 09:33:30 +090025import android.os.ParcelFileDescriptor;
26import android.system.ErrnoException;
27import android.system.OsConstants;
28import android.util.Log;
29import android.util.SparseArray;
30import com.android.internal.annotations.GuardedBy;
31import com.android.internal.util.Preconditions;
Daichi Hirono4f156062017-02-08 16:20:20 +090032import java.util.HashMap;
Daichi Hirono5c2688a2017-04-03 13:18:40 +090033import java.util.LinkedList;
Daichi Hirono4f156062017-02-08 16:20:20 +090034import java.util.Map;
Daichi Hirono9fb00182016-11-08 14:12:17 +090035import java.util.concurrent.ThreadFactory;
Daichi Hirono878e86f2016-10-31 09:33:30 +090036
Daichi Hirono5c2688a2017-04-03 13:18:40 +090037public class FuseAppLoop implements Handler.Callback {
Daichi Hirono878e86f2016-10-31 09:33:30 +090038 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;
Daichi Hirono9fb00182016-11-08 14:12:17 +090042 private static final ThreadFactory sDefaultThreadFactory = new ThreadFactory() {
43 @Override
44 public Thread newThread(Runnable r) {
45 return new Thread(r, TAG);
46 }
47 };
Daichi Hirono4f156062017-02-08 16:20:20 +090048 private static final int FUSE_OK = 0;
Daichi Hirono5c2688a2017-04-03 13:18:40 +090049 private static final int ARGS_POOL_SIZE = 50;
Daichi Hirono878e86f2016-10-31 09:33:30 +090050
51 private final Object mLock = new Object();
Daichi Hirono9fb00182016-11-08 14:12:17 +090052 private final int mMountPointId;
53 private final Thread mThread;
Daichi Hirono878e86f2016-10-31 09:33:30 +090054
55 @GuardedBy("mLock")
56 private final SparseArray<CallbackEntry> mCallbackMap = new SparseArray<>();
57
Daichi Hirono4f156062017-02-08 16:20:20 +090058 @GuardedBy("mLock")
59 private final BytesMap mBytesMap = new BytesMap();
60
Daichi Hirono5c2688a2017-04-03 13:18:40 +090061 @GuardedBy("mLock")
62 private final LinkedList<Args> mArgsPool = new LinkedList<>();
63
Daichi Hirono878e86f2016-10-31 09:33:30 +090064 /**
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
Daichi Hirono4f156062017-02-08 16:20:20 +090071 @GuardedBy("mLock")
72 private long mInstance;
73
74 public FuseAppLoop(
Daichi Hirono9fb00182016-11-08 14:12:17 +090075 int mountPointId, @NonNull ParcelFileDescriptor fd, @Nullable ThreadFactory factory) {
76 mMountPointId = mountPointId;
Daichi Hirono9fb00182016-11-08 14:12:17 +090077 if (factory == null) {
78 factory = sDefaultThreadFactory;
79 }
Daichi Hirono4f156062017-02-08 16:20:20 +090080 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();
Daichi Hirono878e86f2016-10-31 09:33:30 +090087 }
Daichi Hirono9fb00182016-11-08 14:12:17 +090088 });
Daichi Hirono4f156062017-02-08 16:20:20 +090089 mThread.start();
Daichi Hirono878e86f2016-10-31 09:33:30 +090090 }
91
Daichi Hirono4f156062017-02-08 16:20:20 +090092 public int registerCallback(@NonNull ProxyFileDescriptorCallback callback,
93 @NonNull Handler handler) throws FuseUnavailableMountException {
Daichi Hirono878e86f2016-10-31 09:33:30 +090094 synchronized (mLock) {
Daichi Hirono4f156062017-02-08 16:20:20 +090095 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);
Daichi Hirono878e86f2016-10-31 09:33:30 +0900104 }
Daichi Hirono9fb00182016-11-08 14:12:17 +0900105 int id;
Daichi Hirono878e86f2016-10-31 09:33:30 +0900106 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 }
Daichi Hirono5c2688a2017-04-03 13:18:40 +0900116 mCallbackMap.put(id, new CallbackEntry(
117 callback, new Handler(handler.getLooper(), this)));
Daichi Hirono9fb00182016-11-08 14:12:17 +0900118 return id;
Daichi Hirono878e86f2016-10-31 09:33:30 +0900119 }
120 }
121
Daichi Hirono9fb00182016-11-08 14:12:17 +0900122 public void unregisterCallback(int id) {
Daichi Hirono4f156062017-02-08 16:20:20 +0900123 synchronized (mLock) {
124 mCallbackMap.remove(id);
125 }
Daichi Hirono9fb00182016-11-08 14:12:17 +0900126 }
127
128 public int getMountPointId() {
129 return mMountPointId;
Daichi Hirono878e86f2016-10-31 09:33:30 +0900130 }
131
Daichi Hirono4f156062017-02-08 16:20:20 +0900132 // 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
Ryo Hashimoto5c14d492018-03-14 19:16:50 +0900142 private static final int FUSE_MAX_WRITE = 128 * 1024;
Daichi Hirono878e86f2016-10-31 09:33:30 +0900143
Daichi Hirono5c2688a2017-04-03 13:18:40 +0900144 @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
Daichi Hirono878e86f2016-10-31 09:33:30 +0900228 // Called by JNI.
229 @SuppressWarnings("unused")
Mathew Inwoodc185f082018-08-20 14:28:54 +0100230 @UnsupportedAppUsage
Daichi Hirono4f156062017-02-08 16:20:20 +0900231 private void onCommand(int command, long unique, long inode, long offset, int size,
232 byte[] data) {
Daichi Hirono5c2688a2017-04-03 13:18:40 +0900233 synchronized (mLock) {
Daichi Hirono878e86f2016-10-31 09:33:30 +0900234 try {
Daichi Hirono5c2688a2017-04-03 13:18:40 +0900235 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) {
Daichi Hirono4f156062017-02-08 16:20:20 +0900252 replySimpleLocked(unique, getError(error));
Daichi Hirono878e86f2016-10-31 09:33:30 +0900253 }
254 }
255 }
256
257 // Called by JNI.
258 @SuppressWarnings("unused")
Mathew Inwoodc185f082018-08-20 14:28:54 +0100259 @UnsupportedAppUsage
Daichi Hirono4f156062017-02-08 16:20:20 +0900260 private byte[] onOpen(long unique, long inode) {
261 synchronized (mLock) {
Daichi Hirono878e86f2016-10-31 09:33:30 +0900262 try {
263 final CallbackEntry entry = getCallbackEntryOrThrowLocked(inode);
264 if (entry.opened) {
265 throw new ErrnoException("onOpen", OsConstants.EMFILE);
266 }
Daichi Hirono4f156062017-02-08 16:20:20 +0900267 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));
Daichi Hirono878e86f2016-10-31 09:33:30 +0900274 }
Daichi Hirono4f156062017-02-08 16:20:20 +0900275 return null;
Daichi Hirono878e86f2016-10-31 09:33:30 +0900276 }
277 }
278
Daichi Hirono4f156062017-02-08 16:20:20 +0900279 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;
Daichi Hirono878e86f2016-10-31 09:33:30 +0900284 }
285 }
Daichi Hirono4f156062017-02-08 16:20:20 +0900286 return -OsConstants.EBADF;
287 }
288
Andreas Gampe3f24e692018-02-05 13:24:28 -0800289 @GuardedBy("mLock")
Daichi Hirono4f156062017-02-08 16:20:20 +0900290 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
Andreas Gampe3f24e692018-02-05 13:24:28 -0800298 @GuardedBy("mLock")
Daichi Hirono5c2688a2017-04-03 13:18:40 +0900299 private void recycleLocked(Args args) {
300 if (mArgsPool.size() < ARGS_POOL_SIZE) {
301 mArgsPool.add(args);
Daichi Hirono4f156062017-02-08 16:20:20 +0900302 }
Daichi Hirono878e86f2016-10-31 09:33:30 +0900303 }
304
Andreas Gampe3f24e692018-02-05 13:24:28 -0800305 @GuardedBy("mLock")
Daichi Hirono4f156062017-02-08 16:20:20 +0900306 private void replySimpleLocked(long unique, int result) {
307 if (mInstance != 0) {
308 native_replySimple(mInstance, unique, result);
Daichi Hirono878e86f2016-10-31 09:33:30 +0900309 }
310 }
311
Daichi Hirono4f156062017-02-08 16:20:20 +0900312 native long native_new(int fd);
313 native void native_delete(long ptr);
314 native void native_start(long ptr);
Daichi Hirono878e86f2016-10-31 09:33:30 +0900315
Daichi Hirono4f156062017-02-08 16:20:20 +0900316 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);
Daichi Hirono878e86f2016-10-31 09:33:30 +0900322
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 {
Daichi Hirono9fb00182016-11-08 14:12:17 +0900331 final ProxyFileDescriptorCallback callback;
Daichi Hirono4f156062017-02-08 16:20:20 +0900332 final Handler handler;
Daichi Hirono878e86f2016-10-31 09:33:30 +0900333 boolean opened;
Daichi Hirono4f156062017-02-08 16:20:20 +0900334
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 }
Daichi Hirono4f156062017-02-08 16:20:20 +0900343 }
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();
Daichi Hirono878e86f2016-10-31 09:33:30 +0900380 }
381 }
Daichi Hirono5c2688a2017-04-03 13:18:40 +0900382
383 private static class Args {
384 long unique;
385 long inode;
386 long offset;
387 int size;
388 byte[] data;
389 CallbackEntry entry;
390 }
Daichi Hirono878e86f2016-10-31 09:33:30 +0900391}