1 /* 2 * Copyright (C) 2019 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.providers.media.fuse; 18 19 import android.os.ParcelFileDescriptor; 20 import android.util.Log; 21 22 import androidx.annotation.NonNull; 23 24 import com.android.internal.annotations.GuardedBy; 25 import com.android.providers.media.FdAccessResult; 26 import com.android.providers.media.MediaProvider; 27 import com.android.providers.media.util.FileUtils; 28 29 import java.io.File; 30 import java.io.IOException; 31 import java.util.Objects; 32 33 /** 34 * Starts a FUSE session to handle FUSE messages from the kernel. 35 */ 36 public final class FuseDaemon extends Thread { 37 public static final String TAG = "FuseDaemonThread"; 38 private static final int POLL_INTERVAL_MS = 1000; 39 private static final int POLL_COUNT = 5; 40 41 private final Object mLock = new Object(); 42 private final MediaProvider mMediaProvider; 43 private final int mFuseDeviceFd; 44 private final String mPath; 45 private final boolean mUncachedMode; 46 private final String[] mSupportedTranscodingRelativePaths; 47 private final String[] mSupportedUncachedRelativePaths; 48 private final ExternalStorageServiceImpl mService; 49 @GuardedBy("mLock") 50 private long mPtr; 51 FuseDaemon(@onNull MediaProvider mediaProvider, @NonNull ExternalStorageServiceImpl service, @NonNull ParcelFileDescriptor fd, @NonNull String sessionId, @NonNull String path, boolean uncachedMode, String[] supportedTranscodingRelativePaths, String[] supportedUncachedRelativePaths)52 public FuseDaemon(@NonNull MediaProvider mediaProvider, 53 @NonNull ExternalStorageServiceImpl service, @NonNull ParcelFileDescriptor fd, 54 @NonNull String sessionId, @NonNull String path, boolean uncachedMode, 55 String[] supportedTranscodingRelativePaths, String[] supportedUncachedRelativePaths) { 56 mMediaProvider = Objects.requireNonNull(mediaProvider); 57 mService = Objects.requireNonNull(service); 58 setName(Objects.requireNonNull(sessionId)); 59 mFuseDeviceFd = Objects.requireNonNull(fd).detachFd(); 60 mPath = Objects.requireNonNull(path); 61 mUncachedMode = uncachedMode; 62 mSupportedTranscodingRelativePaths 63 = Objects.requireNonNull(supportedTranscodingRelativePaths); 64 mSupportedUncachedRelativePaths 65 = Objects.requireNonNull(supportedUncachedRelativePaths); 66 } 67 68 /** Starts a FUSE session. Does not return until the lower filesystem is unmounted. */ 69 @Override run()70 public void run() { 71 final long ptr; 72 synchronized (mLock) { 73 mPtr = native_new(mMediaProvider); 74 if (mPtr == 0) { 75 throw new IllegalStateException("Unable to create native FUSE daemon"); 76 } 77 ptr = mPtr; 78 } 79 80 Log.i(TAG, "Starting thread for " + getName() + " ..."); 81 native_start(ptr, mFuseDeviceFd, mPath, mUncachedMode, 82 mSupportedTranscodingRelativePaths, 83 mSupportedUncachedRelativePaths); // Blocks 84 Log.i(TAG, "Exiting thread for " + getName() + " ..."); 85 86 synchronized (mLock) { 87 native_delete(mPtr); 88 mPtr = 0; 89 } 90 mService.onExitSession(getName()); 91 Log.i(TAG, "Exited thread for " + getName()); 92 } 93 94 @Override start()95 public synchronized void start() { 96 super.start(); 97 98 // Wait for native_start 99 waitForStart(); 100 101 // Initialize device id 102 initializeDeviceId(); 103 } 104 initializeDeviceId()105 private void initializeDeviceId() { 106 synchronized (mLock) { 107 if (mPtr == 0) { 108 Log.e(TAG, "initializeDeviceId failed, FUSE daemon unavailable"); 109 return; 110 } 111 String path = FileUtils.toFuseFile(new File(mPath)).getAbsolutePath(); 112 native_initialize_device_id(mPtr, path); 113 } 114 } 115 waitForStart()116 private void waitForStart() { 117 int count = POLL_COUNT; 118 while (count-- > 0) { 119 synchronized (mLock) { 120 if (mPtr != 0 && native_is_started(mPtr)) { 121 return; 122 } 123 } 124 try { 125 Log.v(TAG, "Waiting " + POLL_INTERVAL_MS + "ms for FUSE start. Count " + count); 126 Thread.sleep(POLL_INTERVAL_MS); 127 } catch (InterruptedException e) { 128 Thread.currentThread().interrupt(); 129 Log.e(TAG, "Interrupted while starting FUSE", e); 130 } 131 } 132 throw new IllegalStateException("Failed to start FUSE"); 133 } 134 135 /** Waits for any running FUSE sessions to return. */ waitForExit()136 public void waitForExit() { 137 int waitMs = POLL_COUNT * POLL_INTERVAL_MS; 138 Log.i(TAG, "Waiting " + waitMs + "ms for FUSE " + getName() + " to exit..."); 139 140 try { 141 join(waitMs); 142 } catch (InterruptedException e) { 143 Thread.currentThread().interrupt(); 144 throw new IllegalStateException("Interrupted while terminating FUSE " + getName()); 145 } 146 147 if (isAlive()) { 148 throw new IllegalStateException("Failed to exit FUSE " + getName() + " successfully"); 149 } 150 151 Log.i(TAG, "Exited FUSE " + getName() + " successfully"); 152 } 153 154 /** 155 * Checks if file with {@code path} should be opened via FUSE to avoid cache inconcistencies. 156 * May place a F_RDLCK or F_WRLCK with fcntl(2) depending on {@code readLock} 157 * 158 * @return {@code true} if the file should be opened via FUSE, {@code false} otherwise 159 */ shouldOpenWithFuse(String path, boolean readLock, int fd)160 public boolean shouldOpenWithFuse(String path, boolean readLock, int fd) { 161 synchronized (mLock) { 162 if (mPtr == 0) { 163 Log.i(TAG, "shouldOpenWithFuse failed, FUSE daemon unavailable"); 164 return false; 165 } 166 return native_should_open_with_fuse(mPtr, path, readLock, fd); 167 } 168 } 169 170 /** 171 * Checks if the FuseDaemon uses the FUSE passthrough feature. 172 * 173 * @return {@code true} if the FuseDaemon uses FUSE passthrough, {@code false} otherwise 174 */ usesFusePassthrough()175 public boolean usesFusePassthrough() { 176 synchronized (mLock) { 177 if (mPtr == 0) { 178 Log.i(TAG, "usesFusePassthrough failed, FUSE daemon unavailable"); 179 return false; 180 } 181 return native_uses_fuse_passthrough(mPtr); 182 } 183 } 184 185 /** 186 * Invalidates FUSE VFS dentry cache for {@code path} 187 */ invalidateFuseDentryCache(String path)188 public void invalidateFuseDentryCache(String path) { 189 synchronized (mLock) { 190 if (mPtr == 0) { 191 Log.i(TAG, "invalidateFuseDentryCache failed, FUSE daemon unavailable"); 192 return; 193 } 194 native_invalidate_fuse_dentry_cache(mPtr, path); 195 } 196 } 197 checkFdAccess(ParcelFileDescriptor fileDescriptor, int uid)198 public FdAccessResult checkFdAccess(ParcelFileDescriptor fileDescriptor, int uid) 199 throws IOException { 200 synchronized (mLock) { 201 if (mPtr == 0) { 202 throw new IOException("FUSE daemon unavailable"); 203 } 204 return native_check_fd_access(mPtr, fileDescriptor.getFd(), uid); 205 } 206 } 207 native_new(MediaProvider mediaProvider)208 private native long native_new(MediaProvider mediaProvider); 209 210 // Takes ownership of the passed in file descriptor! native_start(long daemon, int deviceFd, String path, boolean uncachedMode, String[] supportedTranscodingRelativePaths, String[] supportedUncachedRelativePaths)211 private native void native_start(long daemon, int deviceFd, String path, 212 boolean uncachedMode, String[] supportedTranscodingRelativePaths, 213 String[] supportedUncachedRelativePaths); 214 native_delete(long daemon)215 private native void native_delete(long daemon); native_should_open_with_fuse(long daemon, String path, boolean readLock, int fd)216 private native boolean native_should_open_with_fuse(long daemon, String path, boolean readLock, 217 int fd); native_uses_fuse_passthrough(long daemon)218 private native boolean native_uses_fuse_passthrough(long daemon); native_invalidate_fuse_dentry_cache(long daemon, String path)219 private native void native_invalidate_fuse_dentry_cache(long daemon, String path); native_is_started(long daemon)220 private native boolean native_is_started(long daemon); native_check_fd_access(long daemon, int fd, int uid)221 private native FdAccessResult native_check_fd_access(long daemon, int fd, int uid); native_initialize_device_id(long daemon, String path)222 private native void native_initialize_device_id(long daemon, String path); native_is_fuse_thread()223 public static native boolean native_is_fuse_thread(); 224 } 225