blob: 12405ebce057d294ec3528be6cb4ec14525d8ead [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;
Daichi Hirono9fb00182016-11-08 14:12:17 +090021import android.os.ProxyFileDescriptorCallback;
Daichi Hirono4f156062017-02-08 16:20:20 +090022import android.os.Handler;
Daichi Hirono5c2688a2017-04-03 13:18:40 +090023import android.os.Message;
Daichi Hirono878e86f2016-10-31 09:33:30 +090024import android.os.ParcelFileDescriptor;
25import android.system.ErrnoException;
26import android.system.OsConstants;
27import android.util.Log;
28import android.util.SparseArray;
29import com.android.internal.annotations.GuardedBy;
30import com.android.internal.util.Preconditions;
Daichi Hirono4f156062017-02-08 16:20:20 +090031import java.util.HashMap;
Daichi Hirono5c2688a2017-04-03 13:18:40 +090032import java.util.LinkedList;
Daichi Hirono4f156062017-02-08 16:20:20 +090033import java.util.Map;
Daichi Hirono9fb00182016-11-08 14:12:17 +090034import java.util.concurrent.ThreadFactory;
Daichi Hirono878e86f2016-10-31 09:33:30 +090035
Daichi Hirono5c2688a2017-04-03 13:18:40 +090036public class FuseAppLoop implements Handler.Callback {
Daichi Hirono878e86f2016-10-31 09:33:30 +090037 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;
Daichi Hirono9fb00182016-11-08 14:12:17 +090041 private static final ThreadFactory sDefaultThreadFactory = new ThreadFactory() {
42 @Override
43 public Thread newThread(Runnable r) {
44 return new Thread(r, TAG);
45 }
46 };
Daichi Hirono4f156062017-02-08 16:20:20 +090047 private static final int FUSE_OK = 0;
Daichi Hirono5c2688a2017-04-03 13:18:40 +090048 private static final int ARGS_POOL_SIZE = 50;
Daichi Hirono878e86f2016-10-31 09:33:30 +090049
50 private final Object mLock = new Object();
Daichi Hirono9fb00182016-11-08 14:12:17 +090051 private final int mMountPointId;
52 private final Thread mThread;
Daichi Hirono878e86f2016-10-31 09:33:30 +090053
54 @GuardedBy("mLock")
55 private final SparseArray<CallbackEntry> mCallbackMap = new SparseArray<>();
56
Daichi Hirono4f156062017-02-08 16:20:20 +090057 @GuardedBy("mLock")
58 private final BytesMap mBytesMap = new BytesMap();
59
Daichi Hirono5c2688a2017-04-03 13:18:40 +090060 @GuardedBy("mLock")
61 private final LinkedList<Args> mArgsPool = new LinkedList<>();
62
Daichi Hirono878e86f2016-10-31 09:33:30 +090063 /**
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
Daichi Hirono4f156062017-02-08 16:20:20 +090070 @GuardedBy("mLock")
71 private long mInstance;
72
73 public FuseAppLoop(
Daichi Hirono9fb00182016-11-08 14:12:17 +090074 int mountPointId, @NonNull ParcelFileDescriptor fd, @Nullable ThreadFactory factory) {
75 mMountPointId = mountPointId;
Daichi Hirono9fb00182016-11-08 14:12:17 +090076 if (factory == null) {
77 factory = sDefaultThreadFactory;
78 }
Daichi Hirono4f156062017-02-08 16:20:20 +090079 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();
Daichi Hirono878e86f2016-10-31 09:33:30 +090086 }
Daichi Hirono9fb00182016-11-08 14:12:17 +090087 });
Daichi Hirono4f156062017-02-08 16:20:20 +090088 mThread.start();
Daichi Hirono878e86f2016-10-31 09:33:30 +090089 }
90
Daichi Hirono4f156062017-02-08 16:20:20 +090091 public int registerCallback(@NonNull ProxyFileDescriptorCallback callback,
92 @NonNull Handler handler) throws FuseUnavailableMountException {
Daichi Hirono878e86f2016-10-31 09:33:30 +090093 synchronized (mLock) {
Daichi Hirono4f156062017-02-08 16:20:20 +090094 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);
Daichi Hirono878e86f2016-10-31 09:33:30 +0900103 }
Daichi Hirono9fb00182016-11-08 14:12:17 +0900104 int id;
Daichi Hirono878e86f2016-10-31 09:33:30 +0900105 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 }
Daichi Hirono5c2688a2017-04-03 13:18:40 +0900115 mCallbackMap.put(id, new CallbackEntry(
116 callback, new Handler(handler.getLooper(), this)));
Daichi Hirono9fb00182016-11-08 14:12:17 +0900117 return id;
Daichi Hirono878e86f2016-10-31 09:33:30 +0900118 }
119 }
120
Daichi Hirono9fb00182016-11-08 14:12:17 +0900121 public void unregisterCallback(int id) {
Daichi Hirono4f156062017-02-08 16:20:20 +0900122 synchronized (mLock) {
123 mCallbackMap.remove(id);
124 }
Daichi Hirono9fb00182016-11-08 14:12:17 +0900125 }
126
127 public int getMountPointId() {
128 return mMountPointId;
Daichi Hirono878e86f2016-10-31 09:33:30 +0900129 }
130
Daichi Hirono4f156062017-02-08 16:20:20 +0900131 // 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;
Daichi Hirono878e86f2016-10-31 09:33:30 +0900142
Daichi Hirono5c2688a2017-04-03 13:18:40 +0900143 @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
Daichi Hirono878e86f2016-10-31 09:33:30 +0900227 // Called by JNI.
228 @SuppressWarnings("unused")
Daichi Hirono4f156062017-02-08 16:20:20 +0900229 private void onCommand(int command, long unique, long inode, long offset, int size,
230 byte[] data) {
Daichi Hirono5c2688a2017-04-03 13:18:40 +0900231 synchronized (mLock) {
Daichi Hirono878e86f2016-10-31 09:33:30 +0900232 try {
Daichi Hirono5c2688a2017-04-03 13:18:40 +0900233 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) {
Daichi Hirono4f156062017-02-08 16:20:20 +0900250 replySimpleLocked(unique, getError(error));
Daichi Hirono878e86f2016-10-31 09:33:30 +0900251 }
252 }
253 }
254
255 // Called by JNI.
256 @SuppressWarnings("unused")
Daichi Hirono4f156062017-02-08 16:20:20 +0900257 private byte[] onOpen(long unique, long inode) {
258 synchronized (mLock) {
Daichi Hirono878e86f2016-10-31 09:33:30 +0900259 try {
260 final CallbackEntry entry = getCallbackEntryOrThrowLocked(inode);
261 if (entry.opened) {
262 throw new ErrnoException("onOpen", OsConstants.EMFILE);
263 }
Daichi Hirono4f156062017-02-08 16:20:20 +0900264 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));
Daichi Hirono878e86f2016-10-31 09:33:30 +0900271 }
Daichi Hirono4f156062017-02-08 16:20:20 +0900272 return null;
Daichi Hirono878e86f2016-10-31 09:33:30 +0900273 }
274 }
275
Daichi Hirono4f156062017-02-08 16:20:20 +0900276 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;
Daichi Hirono878e86f2016-10-31 09:33:30 +0900281 }
282 }
Daichi Hirono4f156062017-02-08 16:20:20 +0900283 return -OsConstants.EBADF;
284 }
285
Andreas Gampe3f24e692018-02-05 13:24:28 -0800286 @GuardedBy("mLock")
Daichi Hirono4f156062017-02-08 16:20:20 +0900287 private CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException {
288 final CallbackEntry entry = mCallbackMap.get(checkInode(inode));
289 if (entry == null) {
290 throw new ErrnoException("getCallbackEntryOrThrowLocked", OsConstants.ENOENT);
291 }
292 return entry;
293 }
294
Andreas Gampe3f24e692018-02-05 13:24:28 -0800295 @GuardedBy("mLock")
Daichi Hirono5c2688a2017-04-03 13:18:40 +0900296 private void recycleLocked(Args args) {
297 if (mArgsPool.size() < ARGS_POOL_SIZE) {
298 mArgsPool.add(args);
Daichi Hirono4f156062017-02-08 16:20:20 +0900299 }
Daichi Hirono878e86f2016-10-31 09:33:30 +0900300 }
301
Andreas Gampe3f24e692018-02-05 13:24:28 -0800302 @GuardedBy("mLock")
Daichi Hirono4f156062017-02-08 16:20:20 +0900303 private void replySimpleLocked(long unique, int result) {
304 if (mInstance != 0) {
305 native_replySimple(mInstance, unique, result);
Daichi Hirono878e86f2016-10-31 09:33:30 +0900306 }
307 }
308
Daichi Hirono4f156062017-02-08 16:20:20 +0900309 native long native_new(int fd);
310 native void native_delete(long ptr);
311 native void native_start(long ptr);
Daichi Hirono878e86f2016-10-31 09:33:30 +0900312
Daichi Hirono4f156062017-02-08 16:20:20 +0900313 native void native_replySimple(long ptr, long unique, int result);
314 native void native_replyOpen(long ptr, long unique, long fh);
315 native void native_replyLookup(long ptr, long unique, long inode, long size);
316 native void native_replyGetAttr(long ptr, long unique, long inode, long size);
317 native void native_replyWrite(long ptr, long unique, int size);
318 native void native_replyRead(long ptr, long unique, int size, byte[] bytes);
Daichi Hirono878e86f2016-10-31 09:33:30 +0900319
320 private static int checkInode(long inode) {
321 Preconditions.checkArgumentInRange(inode, MIN_INODE, Integer.MAX_VALUE, "checkInode");
322 return (int) inode;
323 }
324
325 public static class UnmountedException extends Exception {}
326
327 private static class CallbackEntry {
Daichi Hirono9fb00182016-11-08 14:12:17 +0900328 final ProxyFileDescriptorCallback callback;
Daichi Hirono4f156062017-02-08 16:20:20 +0900329 final Handler handler;
Daichi Hirono878e86f2016-10-31 09:33:30 +0900330 boolean opened;
Daichi Hirono4f156062017-02-08 16:20:20 +0900331
332 CallbackEntry(ProxyFileDescriptorCallback callback, Handler handler) {
333 this.callback = Preconditions.checkNotNull(callback);
334 this.handler = Preconditions.checkNotNull(handler);
335 }
336
337 long getThreadId() {
338 return handler.getLooper().getThread().getId();
339 }
Daichi Hirono4f156062017-02-08 16:20:20 +0900340 }
341
342 /**
343 * Entry for bytes map.
344 */
345 private static class BytesMapEntry {
346 int counter = 0;
347 byte[] bytes = new byte[FUSE_MAX_WRITE];
348 }
349
350 /**
351 * Map between Thread ID and byte buffer.
352 */
353 private static class BytesMap {
354 final Map<Long, BytesMapEntry> mEntries = new HashMap<>();
355
356 byte[] startUsing(long threadId) {
357 BytesMapEntry entry = mEntries.get(threadId);
358 if (entry == null) {
359 entry = new BytesMapEntry();
360 mEntries.put(threadId, entry);
361 }
362 entry.counter++;
363 return entry.bytes;
364 }
365
366 void stopUsing(long threadId) {
367 final BytesMapEntry entry = mEntries.get(threadId);
368 Preconditions.checkNotNull(entry);
369 entry.counter--;
370 if (entry.counter <= 0) {
371 mEntries.remove(threadId);
372 }
373 }
374
375 void clear() {
376 mEntries.clear();
Daichi Hirono878e86f2016-10-31 09:33:30 +0900377 }
378 }
Daichi Hirono5c2688a2017-04-03 13:18:40 +0900379
380 private static class Args {
381 long unique;
382 long inode;
383 long offset;
384 int size;
385 byte[] data;
386 CallbackEntry entry;
387 }
Daichi Hirono878e86f2016-10-31 09:33:30 +0900388}