blob: f8f9fa9d0e70a086b9af7ac5a079a80460bb0f36 [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;
Artur Satayevae5bcf22019-12-10 17:47:54 +000021import android.compat.annotation.UnsupportedAppUsage;
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;
Artur Satayevae5bcf22019-12-10 17:47:54 +000025import android.os.ProxyFileDescriptorCallback;
Daichi Hirono878e86f2016-10-31 09:33:30 +090026import android.system.ErrnoException;
27import android.system.OsConstants;
28import android.util.Log;
29import android.util.SparseArray;
Artur Satayevae5bcf22019-12-10 17:47:54 +000030
Daichi Hirono878e86f2016-10-31 09:33:30 +090031import com.android.internal.annotations.GuardedBy;
32import com.android.internal.util.Preconditions;
Artur Satayevae5bcf22019-12-10 17:47:54 +000033
Daichi Hirono4f156062017-02-08 16:20:20 +090034import java.util.HashMap;
Daichi Hirono5c2688a2017-04-03 13:18:40 +090035import java.util.LinkedList;
Daichi Hirono4f156062017-02-08 16:20:20 +090036import java.util.Map;
Daichi Hirono9fb00182016-11-08 14:12:17 +090037import java.util.concurrent.ThreadFactory;
Daichi Hirono878e86f2016-10-31 09:33:30 +090038
Daichi Hirono5c2688a2017-04-03 13:18:40 +090039public class FuseAppLoop implements Handler.Callback {
Daichi Hirono878e86f2016-10-31 09:33:30 +090040 private static final String TAG = "FuseAppLoop";
41 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
42 public static final int ROOT_INODE = 1;
43 private static final int MIN_INODE = 2;
Daichi Hirono9fb00182016-11-08 14:12:17 +090044 private static final ThreadFactory sDefaultThreadFactory = new ThreadFactory() {
45 @Override
46 public Thread newThread(Runnable r) {
47 return new Thread(r, TAG);
48 }
49 };
Daichi Hirono4f156062017-02-08 16:20:20 +090050 private static final int FUSE_OK = 0;
Daichi Hirono5c2688a2017-04-03 13:18:40 +090051 private static final int ARGS_POOL_SIZE = 50;
Daichi Hirono878e86f2016-10-31 09:33:30 +090052
53 private final Object mLock = new Object();
Daichi Hirono9fb00182016-11-08 14:12:17 +090054 private final int mMountPointId;
55 private final Thread mThread;
Daichi Hirono878e86f2016-10-31 09:33:30 +090056
57 @GuardedBy("mLock")
58 private final SparseArray<CallbackEntry> mCallbackMap = new SparseArray<>();
59
Daichi Hirono4f156062017-02-08 16:20:20 +090060 @GuardedBy("mLock")
61 private final BytesMap mBytesMap = new BytesMap();
62
Daichi Hirono5c2688a2017-04-03 13:18:40 +090063 @GuardedBy("mLock")
64 private final LinkedList<Args> mArgsPool = new LinkedList<>();
65
Daichi Hirono878e86f2016-10-31 09:33:30 +090066 /**
67 * Sequential number can be used as file name and inode in AppFuse.
68 * 0 is regarded as an error, 1 is mount point. So we start the number from 2.
69 */
70 @GuardedBy("mLock")
71 private int mNextInode = MIN_INODE;
72
Daichi Hirono4f156062017-02-08 16:20:20 +090073 @GuardedBy("mLock")
74 private long mInstance;
75
76 public FuseAppLoop(
Daichi Hirono9fb00182016-11-08 14:12:17 +090077 int mountPointId, @NonNull ParcelFileDescriptor fd, @Nullable ThreadFactory factory) {
78 mMountPointId = mountPointId;
Daichi Hirono9fb00182016-11-08 14:12:17 +090079 if (factory == null) {
80 factory = sDefaultThreadFactory;
81 }
Daichi Hirono4f156062017-02-08 16:20:20 +090082 mInstance = native_new(fd.detachFd());
83 mThread = factory.newThread(() -> {
84 native_start(mInstance);
85 synchronized (mLock) {
86 native_delete(mInstance);
87 mInstance = 0;
88 mBytesMap.clear();
Daichi Hirono878e86f2016-10-31 09:33:30 +090089 }
Daichi Hirono9fb00182016-11-08 14:12:17 +090090 });
Daichi Hirono4f156062017-02-08 16:20:20 +090091 mThread.start();
Daichi Hirono878e86f2016-10-31 09:33:30 +090092 }
93
Daichi Hirono4f156062017-02-08 16:20:20 +090094 public int registerCallback(@NonNull ProxyFileDescriptorCallback callback,
95 @NonNull Handler handler) throws FuseUnavailableMountException {
Daichi Hirono878e86f2016-10-31 09:33:30 +090096 synchronized (mLock) {
Daichi Hirono4f156062017-02-08 16:20:20 +090097 Preconditions.checkNotNull(callback);
98 Preconditions.checkNotNull(handler);
99 Preconditions.checkState(
100 mCallbackMap.size() < Integer.MAX_VALUE - MIN_INODE, "Too many opened files.");
101 Preconditions.checkArgument(
102 Thread.currentThread().getId() != handler.getLooper().getThread().getId(),
103 "Handler must be different from the current thread");
104 if (mInstance == 0) {
105 throw new FuseUnavailableMountException(mMountPointId);
Daichi Hirono878e86f2016-10-31 09:33:30 +0900106 }
Daichi Hirono9fb00182016-11-08 14:12:17 +0900107 int id;
Daichi Hirono878e86f2016-10-31 09:33:30 +0900108 while (true) {
109 id = mNextInode;
110 mNextInode++;
111 if (mNextInode < 0) {
112 mNextInode = MIN_INODE;
113 }
114 if (mCallbackMap.get(id) == null) {
115 break;
116 }
117 }
Daichi Hirono5c2688a2017-04-03 13:18:40 +0900118 mCallbackMap.put(id, new CallbackEntry(
119 callback, new Handler(handler.getLooper(), this)));
Daichi Hirono9fb00182016-11-08 14:12:17 +0900120 return id;
Daichi Hirono878e86f2016-10-31 09:33:30 +0900121 }
122 }
123
Daichi Hirono9fb00182016-11-08 14:12:17 +0900124 public void unregisterCallback(int id) {
Daichi Hirono4f156062017-02-08 16:20:20 +0900125 synchronized (mLock) {
126 mCallbackMap.remove(id);
127 }
Daichi Hirono9fb00182016-11-08 14:12:17 +0900128 }
129
130 public int getMountPointId() {
131 return mMountPointId;
Daichi Hirono878e86f2016-10-31 09:33:30 +0900132 }
133
Daichi Hirono4f156062017-02-08 16:20:20 +0900134 // Defined in fuse.h
135 private static final int FUSE_LOOKUP = 1;
136 private static final int FUSE_GETATTR = 3;
137 private static final int FUSE_OPEN = 14;
138 private static final int FUSE_READ = 15;
139 private static final int FUSE_WRITE = 16;
140 private static final int FUSE_RELEASE = 18;
141 private static final int FUSE_FSYNC = 20;
142
143 // Defined in FuseBuffer.h
Ryo Hashimoto5c14d492018-03-14 19:16:50 +0900144 private static final int FUSE_MAX_WRITE = 128 * 1024;
Daichi Hirono878e86f2016-10-31 09:33:30 +0900145
Daichi Hirono5c2688a2017-04-03 13:18:40 +0900146 @Override
147 public boolean handleMessage(Message msg) {
148 final Args args = (Args) msg.obj;
149 final CallbackEntry entry = args.entry;
150 final long inode = args.inode;
151 final long unique = args.unique;
152 final int size = args.size;
153 final long offset = args.offset;
154 final byte[] data = args.data;
155
156 try {
157 switch (msg.what) {
158 case FUSE_LOOKUP: {
159 final long fileSize = entry.callback.onGetSize();
160 synchronized (mLock) {
161 if (mInstance != 0) {
162 native_replyLookup(mInstance, unique, inode, fileSize);
163 }
164 recycleLocked(args);
165 }
166 break;
167 }
168 case FUSE_GETATTR: {
169 final long fileSize = entry.callback.onGetSize();
170 synchronized (mLock) {
171 if (mInstance != 0) {
172 native_replyGetAttr(mInstance, unique, inode, fileSize);
173 }
174 recycleLocked(args);
175 }
176 break;
177 }
178 case FUSE_READ:
179 final int readSize = entry.callback.onRead(
180 offset, size, data);
181 synchronized (mLock) {
182 if (mInstance != 0) {
183 native_replyRead(mInstance, unique, readSize, data);
184 }
185 recycleLocked(args);
186 }
187 break;
188 case FUSE_WRITE:
189 final int writeSize = entry.callback.onWrite(offset, size, data);
190 synchronized (mLock) {
191 if (mInstance != 0) {
192 native_replyWrite(mInstance, unique, writeSize);
193 }
194 recycleLocked(args);
195 }
196 break;
197 case FUSE_FSYNC:
198 entry.callback.onFsync();
199 synchronized (mLock) {
200 if (mInstance != 0) {
201 native_replySimple(mInstance, unique, FUSE_OK);
202 }
203 recycleLocked(args);
204 }
205 break;
206 case FUSE_RELEASE:
207 entry.callback.onRelease();
208 synchronized (mLock) {
209 if (mInstance != 0) {
210 native_replySimple(mInstance, unique, FUSE_OK);
211 }
212 mBytesMap.stopUsing(entry.getThreadId());
213 recycleLocked(args);
214 }
215 break;
216 default:
217 throw new IllegalArgumentException("Unknown FUSE command: " + msg.what);
218 }
219 } catch (Exception error) {
220 synchronized (mLock) {
221 Log.e(TAG, "", error);
222 replySimpleLocked(unique, getError(error));
223 recycleLocked(args);
224 }
225 }
226
227 return true;
228 }
229
Daichi Hirono878e86f2016-10-31 09:33:30 +0900230 // Called by JNI.
231 @SuppressWarnings("unused")
Mathew Inwoodc185f082018-08-20 14:28:54 +0100232 @UnsupportedAppUsage
Daichi Hirono4f156062017-02-08 16:20:20 +0900233 private void onCommand(int command, long unique, long inode, long offset, int size,
234 byte[] data) {
Daichi Hirono5c2688a2017-04-03 13:18:40 +0900235 synchronized (mLock) {
Daichi Hirono878e86f2016-10-31 09:33:30 +0900236 try {
Daichi Hirono5c2688a2017-04-03 13:18:40 +0900237 final Args args;
238 if (mArgsPool.size() == 0) {
239 args = new Args();
240 } else {
241 args = mArgsPool.pop();
242 }
243 args.unique = unique;
244 args.inode = inode;
245 args.offset = offset;
246 args.size = size;
247 args.data = data;
248 args.entry = getCallbackEntryOrThrowLocked(inode);
249 if (!args.entry.handler.sendMessage(
250 Message.obtain(args.entry.handler, command, 0, 0, args))) {
251 throw new ErrnoException("onCommand", OsConstants.EBADF);
252 }
253 } catch (Exception error) {
Daichi Hirono4f156062017-02-08 16:20:20 +0900254 replySimpleLocked(unique, getError(error));
Daichi Hirono878e86f2016-10-31 09:33:30 +0900255 }
256 }
257 }
258
259 // Called by JNI.
260 @SuppressWarnings("unused")
Mathew Inwoodc185f082018-08-20 14:28:54 +0100261 @UnsupportedAppUsage
Daichi Hirono4f156062017-02-08 16:20:20 +0900262 private byte[] onOpen(long unique, long inode) {
263 synchronized (mLock) {
Daichi Hirono878e86f2016-10-31 09:33:30 +0900264 try {
265 final CallbackEntry entry = getCallbackEntryOrThrowLocked(inode);
266 if (entry.opened) {
267 throw new ErrnoException("onOpen", OsConstants.EMFILE);
268 }
Daichi Hirono4f156062017-02-08 16:20:20 +0900269 if (mInstance != 0) {
270 native_replyOpen(mInstance, unique, /* fh */ inode);
271 entry.opened = true;
272 return mBytesMap.startUsing(entry.getThreadId());
273 }
274 } catch (ErrnoException error) {
275 replySimpleLocked(unique, getError(error));
Daichi Hirono878e86f2016-10-31 09:33:30 +0900276 }
Daichi Hirono4f156062017-02-08 16:20:20 +0900277 return null;
Daichi Hirono878e86f2016-10-31 09:33:30 +0900278 }
279 }
280
Daichi Hirono4f156062017-02-08 16:20:20 +0900281 private static int getError(@NonNull Exception error) {
282 if (error instanceof ErrnoException) {
283 final int errno = ((ErrnoException) error).errno;
284 if (errno != OsConstants.ENOSYS) {
285 return -errno;
Daichi Hirono878e86f2016-10-31 09:33:30 +0900286 }
287 }
Daichi Hirono4f156062017-02-08 16:20:20 +0900288 return -OsConstants.EBADF;
289 }
290
Andreas Gampe3f24e692018-02-05 13:24:28 -0800291 @GuardedBy("mLock")
Daichi Hirono4f156062017-02-08 16:20:20 +0900292 private CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException {
293 final CallbackEntry entry = mCallbackMap.get(checkInode(inode));
294 if (entry == null) {
295 throw new ErrnoException("getCallbackEntryOrThrowLocked", OsConstants.ENOENT);
296 }
297 return entry;
298 }
299
Andreas Gampe3f24e692018-02-05 13:24:28 -0800300 @GuardedBy("mLock")
Daichi Hirono5c2688a2017-04-03 13:18:40 +0900301 private void recycleLocked(Args args) {
302 if (mArgsPool.size() < ARGS_POOL_SIZE) {
303 mArgsPool.add(args);
Daichi Hirono4f156062017-02-08 16:20:20 +0900304 }
Daichi Hirono878e86f2016-10-31 09:33:30 +0900305 }
306
Andreas Gampe3f24e692018-02-05 13:24:28 -0800307 @GuardedBy("mLock")
Daichi Hirono4f156062017-02-08 16:20:20 +0900308 private void replySimpleLocked(long unique, int result) {
309 if (mInstance != 0) {
310 native_replySimple(mInstance, unique, result);
Daichi Hirono878e86f2016-10-31 09:33:30 +0900311 }
312 }
313
Daichi Hirono4f156062017-02-08 16:20:20 +0900314 native long native_new(int fd);
315 native void native_delete(long ptr);
316 native void native_start(long ptr);
Daichi Hirono878e86f2016-10-31 09:33:30 +0900317
Daichi Hirono4f156062017-02-08 16:20:20 +0900318 native void native_replySimple(long ptr, long unique, int result);
319 native void native_replyOpen(long ptr, long unique, long fh);
320 native void native_replyLookup(long ptr, long unique, long inode, long size);
321 native void native_replyGetAttr(long ptr, long unique, long inode, long size);
322 native void native_replyWrite(long ptr, long unique, int size);
323 native void native_replyRead(long ptr, long unique, int size, byte[] bytes);
Daichi Hirono878e86f2016-10-31 09:33:30 +0900324
325 private static int checkInode(long inode) {
326 Preconditions.checkArgumentInRange(inode, MIN_INODE, Integer.MAX_VALUE, "checkInode");
327 return (int) inode;
328 }
329
330 public static class UnmountedException extends Exception {}
331
332 private static class CallbackEntry {
Daichi Hirono9fb00182016-11-08 14:12:17 +0900333 final ProxyFileDescriptorCallback callback;
Daichi Hirono4f156062017-02-08 16:20:20 +0900334 final Handler handler;
Daichi Hirono878e86f2016-10-31 09:33:30 +0900335 boolean opened;
Daichi Hirono4f156062017-02-08 16:20:20 +0900336
337 CallbackEntry(ProxyFileDescriptorCallback callback, Handler handler) {
338 this.callback = Preconditions.checkNotNull(callback);
339 this.handler = Preconditions.checkNotNull(handler);
340 }
341
342 long getThreadId() {
343 return handler.getLooper().getThread().getId();
344 }
Daichi Hirono4f156062017-02-08 16:20:20 +0900345 }
346
347 /**
348 * Entry for bytes map.
349 */
350 private static class BytesMapEntry {
351 int counter = 0;
352 byte[] bytes = new byte[FUSE_MAX_WRITE];
353 }
354
355 /**
356 * Map between Thread ID and byte buffer.
357 */
358 private static class BytesMap {
359 final Map<Long, BytesMapEntry> mEntries = new HashMap<>();
360
361 byte[] startUsing(long threadId) {
362 BytesMapEntry entry = mEntries.get(threadId);
363 if (entry == null) {
364 entry = new BytesMapEntry();
365 mEntries.put(threadId, entry);
366 }
367 entry.counter++;
368 return entry.bytes;
369 }
370
371 void stopUsing(long threadId) {
372 final BytesMapEntry entry = mEntries.get(threadId);
373 Preconditions.checkNotNull(entry);
374 entry.counter--;
375 if (entry.counter <= 0) {
376 mEntries.remove(threadId);
377 }
378 }
379
380 void clear() {
381 mEntries.clear();
Daichi Hirono878e86f2016-10-31 09:33:30 +0900382 }
383 }
Daichi Hirono5c2688a2017-04-03 13:18:40 +0900384
385 private static class Args {
386 long unique;
387 long inode;
388 long offset;
389 int size;
390 byte[] data;
391 CallbackEntry entry;
392 }
Daichi Hirono878e86f2016-10-31 09:33:30 +0900393}