1 /* 2 * Copyright (C) 2015 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.mtp; 18 19 import android.annotation.WorkerThread; 20 import android.os.ParcelFileDescriptor; 21 import android.os.Process; 22 import android.os.storage.StorageManager; 23 import android.system.ErrnoException; 24 import android.system.OsConstants; 25 import android.util.Log; 26 import com.android.internal.annotations.VisibleForTesting; 27 import com.android.internal.util.Preconditions; 28 import com.android.mtp.annotations.UsedByNative; 29 import java.io.File; 30 import java.io.FileNotFoundException; 31 import java.io.IOException; 32 33 public class AppFuse { 34 static { 35 System.loadLibrary("appfuse_jni"); 36 } 37 38 private static final boolean DEBUG = false; 39 40 /** 41 * Max read amount specified at the FUSE kernel implementation. 42 * The value is copied from sdcard.c. 43 */ 44 @UsedByNative("com_android_mtp_AppFuse.cpp") 45 static final int MAX_READ = 128 * 1024; 46 47 @UsedByNative("com_android_mtp_AppFuse.cpp") 48 static final int MAX_WRITE = 256 * 1024; 49 50 private final String mName; 51 private final Callback mCallback; 52 53 /** 54 * Buffer for read bytes request. 55 * Don't use the buffer from the out of AppFuseMessageThread. 56 */ 57 private byte[] mBuffer = new byte[Math.max(MAX_READ, MAX_WRITE)]; 58 59 private Thread mMessageThread; 60 private ParcelFileDescriptor mDeviceFd; 61 AppFuse(String name, Callback callback)62 AppFuse(String name, Callback callback) { 63 mName = name; 64 mCallback = callback; 65 } 66 mount(StorageManager storageManager)67 void mount(StorageManager storageManager) throws IOException { 68 Preconditions.checkState(mDeviceFd == null); 69 mDeviceFd = storageManager.mountAppFuse(mName); 70 mMessageThread = new AppFuseMessageThread(mDeviceFd.dup().detachFd()); 71 mMessageThread.start(); 72 } 73 74 @VisibleForTesting close()75 void close() { 76 try { 77 // Remote side of ParcelFileDescriptor is tracking the close of mDeviceFd, and unmount 78 // the corresponding fuse file system. The mMessageThread will receive ENODEV, and 79 // then terminate itself. 80 mDeviceFd.close(); 81 mMessageThread.join(); 82 } catch (IOException exp) { 83 Log.e(MtpDocumentsProvider.TAG, "Failed to close device FD.", exp); 84 } catch (InterruptedException exp) { 85 Log.e(MtpDocumentsProvider.TAG, "Failed to terminate message thread.", exp); 86 } 87 } 88 89 /** 90 * Opens a file on app fuse and returns ParcelFileDescriptor. 91 * 92 * @param i ID for opened file. 93 * @param mode Mode for opening file. 94 * @see ParcelFileDescriptor#MODE_READ_ONLY 95 * @see ParcelFileDescriptor#MODE_WRITE_ONLY 96 */ openFile(int i, int mode)97 public ParcelFileDescriptor openFile(int i, int mode) throws FileNotFoundException { 98 Preconditions.checkArgument( 99 mode == ParcelFileDescriptor.MODE_READ_ONLY || 100 mode == (ParcelFileDescriptor.MODE_WRITE_ONLY | 101 ParcelFileDescriptor.MODE_TRUNCATE)); 102 return ParcelFileDescriptor.open(new File( 103 getMountPoint(), 104 Integer.toString(i)), 105 mode); 106 } 107 getMountPoint()108 File getMountPoint() { 109 return new File("/mnt/appfuse/" + Process.myUid() + "_" + mName); 110 } 111 112 static interface Callback { 113 /** 114 * Returns file size for the given inode. 115 * @param inode 116 * @return File size. Must not be negative. 117 * @throws FileNotFoundException 118 */ getFileSize(int inode)119 long getFileSize(int inode) throws FileNotFoundException; 120 121 /** 122 * Returns file bytes for the give inode. 123 * @param inode 124 * @param offset Offset for file bytes. 125 * @param size Size for file bytes. 126 * @param bytes Buffer to store file bytes. 127 * @return Number of read bytes. Must not be negative. 128 * @throws IOException 129 */ readObjectBytes(int inode, long offset, long size, byte[] bytes)130 long readObjectBytes(int inode, long offset, long size, byte[] bytes) throws IOException; 131 132 /** 133 * Handles writing bytes for the give inode. 134 * @param fileHandle 135 * @param inode 136 * @param offset Offset for file bytes. 137 * @param size Size for file bytes. 138 * @param bytes Buffer to store file bytes. 139 * @return Number of read bytes. Must not be negative. 140 * @throws IOException 141 */ writeObjectBytes(long fileHandle, int inode, long offset, int size, byte[] bytes)142 int writeObjectBytes(long fileHandle, int inode, long offset, int size, byte[] bytes) 143 throws IOException, ErrnoException; 144 145 /** 146 * Flushes bytes for file handle. 147 * @param fileHandle 148 * @throws IOException 149 * @throws ErrnoException 150 */ flushFileHandle(long fileHandle)151 void flushFileHandle(long fileHandle) throws IOException, ErrnoException; 152 153 /** 154 * Closes file handle. 155 * @param fileHandle 156 * @throws IOException 157 */ closeFileHandle(long fileHandle)158 void closeFileHandle(long fileHandle) throws IOException, ErrnoException; 159 } 160 161 @UsedByNative("com_android_mtp_AppFuse.cpp") 162 @WorkerThread getFileSize(int inode)163 private long getFileSize(int inode) { 164 try { 165 return mCallback.getFileSize(inode); 166 } catch (Exception error) { 167 return -getErrnoFromException(error); 168 } 169 } 170 171 @UsedByNative("com_android_mtp_AppFuse.cpp") 172 @WorkerThread readObjectBytes(int inode, long offset, long size)173 private long readObjectBytes(int inode, long offset, long size) { 174 if (offset < 0 || size < 0 || size > MAX_READ) { 175 return -OsConstants.EINVAL; 176 } 177 try { 178 // It's OK to share the same mBuffer among requests because the requests are processed 179 // by AppFuseMessageThread sequentially. 180 return mCallback.readObjectBytes(inode, offset, size, mBuffer); 181 } catch (Exception error) { 182 return -getErrnoFromException(error); 183 } 184 } 185 186 @UsedByNative("com_android_mtp_AppFuse.cpp") 187 @WorkerThread writeObjectBytes(long fileHandler, int inode, long offset, int size, byte[] bytes)188 private /* unsgined */ int writeObjectBytes(long fileHandler, 189 int inode, 190 /* unsigned */ long offset, 191 /* unsigned */ int size, 192 byte[] bytes) { 193 try { 194 return mCallback.writeObjectBytes(fileHandler, inode, offset, size, bytes); 195 } catch (Exception error) { 196 return -getErrnoFromException(error); 197 } 198 } 199 200 @UsedByNative("com_android_mtp_AppFuse.cpp") 201 @WorkerThread flushFileHandle(long fileHandle)202 private int flushFileHandle(long fileHandle) { 203 try { 204 mCallback.flushFileHandle(fileHandle); 205 return 0; 206 } catch (Exception error) { 207 return -getErrnoFromException(error); 208 } 209 } 210 211 @UsedByNative("com_android_mtp_AppFuse.cpp") 212 @WorkerThread closeFileHandle(long fileHandle)213 private int closeFileHandle(long fileHandle) { 214 try { 215 mCallback.closeFileHandle(fileHandle); 216 return 0; 217 } catch (Exception error) { 218 return -getErrnoFromException(error); 219 } 220 } 221 getErrnoFromException(Exception error)222 private static int getErrnoFromException(Exception error) { 223 if (DEBUG) { 224 Log.e(MtpDocumentsProvider.TAG, "AppFuse callbacks", error); 225 } 226 if (error instanceof FileNotFoundException) { 227 return OsConstants.ENOENT; 228 } else if (error instanceof IOException) { 229 return OsConstants.EIO; 230 } else if (error instanceof UnsupportedOperationException) { 231 return OsConstants.ENOTSUP; 232 } else if (error instanceof IllegalArgumentException) { 233 return OsConstants.EINVAL; 234 } else { 235 return OsConstants.EIO; 236 } 237 } 238 native_start_app_fuse_loop(int fd)239 private native void native_start_app_fuse_loop(int fd); 240 241 private class AppFuseMessageThread extends Thread { 242 /** 243 * File descriptor used by native loop. 244 * It's owned by native loop and does not need to close here. 245 */ 246 private final int mRawFd; 247 AppFuseMessageThread(int fd)248 AppFuseMessageThread(int fd) { 249 super("AppFuseMessageThread"); 250 mRawFd = fd; 251 } 252 253 @Override run()254 public void run() { 255 native_start_app_fuse_loop(mRawFd); 256 } 257 } 258 } 259