FuseAppLoop.java revision 9fb00183a04036a58ee208f5bfd6c9768982f0aa
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 17package com.android.internal.os; 18 19import android.annotation.NonNull; 20import android.annotation.Nullable; 21import android.os.ProxyFileDescriptorCallback; 22import android.os.ParcelFileDescriptor; 23import android.system.ErrnoException; 24import android.system.OsConstants; 25import android.util.Log; 26import android.util.SparseArray; 27import com.android.internal.annotations.GuardedBy; 28import com.android.internal.annotations.VisibleForTesting; 29import com.android.internal.util.Preconditions; 30 31import java.io.IOException; 32import java.util.concurrent.ThreadFactory; 33 34public class FuseAppLoop { 35 private static final String TAG = "FuseAppLoop"; 36 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 37 public static final int ROOT_INODE = 1; 38 private static final int MIN_INODE = 2; 39 private static final ThreadFactory sDefaultThreadFactory = new ThreadFactory() { 40 @Override 41 public Thread newThread(Runnable r) { 42 return new Thread(r, TAG); 43 } 44 }; 45 46 private final Object mLock = new Object(); 47 private final int mMountPointId; 48 private final Thread mThread; 49 50 @GuardedBy("mLock") 51 private final SparseArray<CallbackEntry> mCallbackMap = new SparseArray<>(); 52 53 /** 54 * Sequential number can be used as file name and inode in AppFuse. 55 * 0 is regarded as an error, 1 is mount point. So we start the number from 2. 56 */ 57 @GuardedBy("mLock") 58 private int mNextInode = MIN_INODE; 59 60 private FuseAppLoop( 61 int mountPointId, @NonNull ParcelFileDescriptor fd, @Nullable ThreadFactory factory) { 62 mMountPointId = mountPointId; 63 final int rawFd = fd.detachFd(); 64 if (factory == null) { 65 factory = sDefaultThreadFactory; 66 } 67 mThread = factory.newThread(new Runnable() { 68 @Override 69 public void run() { 70 // rawFd is closed by native_start_loop. Java code does not need to close it. 71 native_start_loop(rawFd); 72 } 73 }); 74 } 75 76 public static @NonNull FuseAppLoop open(int mountPointId, @NonNull ParcelFileDescriptor fd, 77 @Nullable ThreadFactory factory) { 78 Preconditions.checkNotNull(fd); 79 final FuseAppLoop loop = new FuseAppLoop(mountPointId, fd, factory); 80 loop.mThread.start(); 81 return loop; 82 } 83 84 public int registerCallback(@NonNull ProxyFileDescriptorCallback callback) 85 throws UnmountedException, IOException { 86 if (mThread.getState() == Thread.State.TERMINATED) { 87 throw new UnmountedException(); 88 } 89 synchronized (mLock) { 90 if (mCallbackMap.size() >= Integer.MAX_VALUE - MIN_INODE) { 91 throw new IOException("Too many opened files."); 92 } 93 int id; 94 while (true) { 95 id = mNextInode; 96 mNextInode++; 97 if (mNextInode < 0) { 98 mNextInode = MIN_INODE; 99 } 100 if (mCallbackMap.get(id) == null) { 101 break; 102 } 103 } 104 mCallbackMap.put(id, new CallbackEntry(callback)); 105 return id; 106 } 107 } 108 109 public void unregisterCallback(int id) { 110 mCallbackMap.remove(id); 111 } 112 113 public int getMountPointId() { 114 return mMountPointId; 115 } 116 117 private CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException { 118 final CallbackEntry entry = mCallbackMap.get(checkInode(inode)); 119 if (entry != null) { 120 return entry; 121 } else { 122 throw new ErrnoException("getCallbackEntry", OsConstants.ENOENT); 123 } 124 } 125 126 // Called by JNI. 127 @SuppressWarnings("unused") 128 private long onGetSize(long inode) { 129 synchronized(mLock) { 130 try { 131 return getCallbackEntryOrThrowLocked(inode).callback.onGetSize(); 132 } catch (ErrnoException exp) { 133 return getError(exp); 134 } 135 } 136 } 137 138 // Called by JNI. 139 @SuppressWarnings("unused") 140 private int onOpen(long inode) { 141 synchronized(mLock) { 142 try { 143 final CallbackEntry entry = getCallbackEntryOrThrowLocked(inode); 144 if (entry.opened) { 145 throw new ErrnoException("onOpen", OsConstants.EMFILE); 146 } 147 entry.opened = true; 148 // Use inode as file handle. It's OK because AppFuse does not allow to open the same 149 // file twice. 150 return (int) inode; 151 } catch (ErrnoException exp) { 152 return getError(exp); 153 } 154 } 155 } 156 157 // Called by JNI. 158 @SuppressWarnings("unused") 159 private int onFsync(long inode) { 160 synchronized(mLock) { 161 try { 162 getCallbackEntryOrThrowLocked(inode).callback.onFsync(); 163 return 0; 164 } catch (ErrnoException exp) { 165 return getError(exp); 166 } 167 } 168 } 169 170 // Called by JNI. 171 @SuppressWarnings("unused") 172 private int onRelease(long inode) { 173 synchronized(mLock) { 174 try { 175 getCallbackEntryOrThrowLocked(inode).callback.onRelease(); 176 return 0; 177 } catch (ErrnoException exp) { 178 return getError(exp); 179 } finally { 180 mCallbackMap.remove(checkInode(inode)); 181 } 182 } 183 } 184 185 // Called by JNI. 186 @SuppressWarnings("unused") 187 private int onRead(long inode, long offset, int size, byte[] bytes) { 188 synchronized(mLock) { 189 try { 190 return getCallbackEntryOrThrowLocked(inode).callback.onRead(offset, size, bytes); 191 } catch (ErrnoException exp) { 192 return getError(exp); 193 } 194 } 195 } 196 197 // Called by JNI. 198 @SuppressWarnings("unused") 199 private int onWrite(long inode, long offset, int size, byte[] bytes) { 200 synchronized(mLock) { 201 try { 202 return getCallbackEntryOrThrowLocked(inode).callback.onWrite(offset, size, bytes); 203 } catch (ErrnoException exp) { 204 return getError(exp); 205 } 206 } 207 } 208 209 private static int getError(@NonNull ErrnoException exp) { 210 // Should not return ENOSYS because the kernel stops 211 // dispatching the FUSE action once FUSE implementation returns ENOSYS for the action. 212 return exp.errno != OsConstants.ENOSYS ? -exp.errno : -OsConstants.EIO; 213 } 214 215 native boolean native_start_loop(int fd); 216 217 private static int checkInode(long inode) { 218 Preconditions.checkArgumentInRange(inode, MIN_INODE, Integer.MAX_VALUE, "checkInode"); 219 return (int) inode; 220 } 221 222 public static class UnmountedException extends Exception {} 223 224 private static class CallbackEntry { 225 final ProxyFileDescriptorCallback callback; 226 boolean opened; 227 CallbackEntry(ProxyFileDescriptorCallback callback) { 228 Preconditions.checkNotNull(callback); 229 this.callback = callback; 230 } 231 } 232} 233