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.MediaProvider; 26 27 import java.util.Objects; 28 29 /** 30 * Starts a FUSE session to handle FUSE messages from the kernel. 31 */ 32 public final class FuseDaemon extends Thread { 33 public static final String TAG = "FuseDaemonThread"; 34 private static final int POLL_INTERVAL_MS = 1000; 35 private static final int POLL_COUNT = 5; 36 37 private final Object mLock = new Object(); 38 private final MediaProvider mMediaProvider; 39 private final int mFuseDeviceFd; 40 private final String mPath; 41 private final ExternalStorageServiceImpl mService; 42 @GuardedBy("mLock") 43 private long mPtr; 44 FuseDaemon(@onNull MediaProvider mediaProvider, @NonNull ExternalStorageServiceImpl service, @NonNull ParcelFileDescriptor fd, @NonNull String sessionId, @NonNull String path)45 public FuseDaemon(@NonNull MediaProvider mediaProvider, 46 @NonNull ExternalStorageServiceImpl service, @NonNull ParcelFileDescriptor fd, 47 @NonNull String sessionId, @NonNull String path) { 48 mMediaProvider = Objects.requireNonNull(mediaProvider); 49 mService = Objects.requireNonNull(service); 50 setName(Objects.requireNonNull(sessionId)); 51 mFuseDeviceFd = Objects.requireNonNull(fd).detachFd(); 52 mPath = Objects.requireNonNull(path); 53 } 54 55 /** Starts a FUSE session. Does not return until the lower filesystem is unmounted. */ 56 @Override run()57 public void run() { 58 final long ptr; 59 synchronized (mLock) { 60 mPtr = native_new(mMediaProvider); 61 if (mPtr == 0) { 62 throw new IllegalStateException("Unable to create native FUSE daemon"); 63 } 64 ptr = mPtr; 65 } 66 67 Log.i(TAG, "Starting thread for " + getName() + " ..."); 68 native_start(ptr, mFuseDeviceFd, mPath); // Blocks 69 Log.i(TAG, "Exiting thread for " + getName() + " ..."); 70 71 synchronized (mLock) { 72 native_delete(mPtr); 73 mPtr = 0; 74 } 75 mService.onExitSession(getName()); 76 Log.i(TAG, "Exited thread for " + getName()); 77 } 78 79 @Override start()80 public synchronized void start() { 81 super.start(); 82 83 // Wait for native_start 84 waitForStart(); 85 } 86 waitForStart()87 private void waitForStart() { 88 int count = POLL_COUNT; 89 while (count-- > 0) { 90 synchronized (mLock) { 91 if (mPtr != 0 && native_is_started(mPtr)) { 92 return; 93 } 94 } 95 try { 96 Log.v(TAG, "Waiting " + POLL_INTERVAL_MS + "ms for FUSE start. Count " + count); 97 Thread.sleep(POLL_INTERVAL_MS); 98 } catch (InterruptedException e) { 99 Thread.currentThread().interrupt(); 100 Log.e(TAG, "Interrupted while starting FUSE", e); 101 } 102 } 103 throw new IllegalStateException("Failed to start FUSE"); 104 } 105 106 /** Waits for any running FUSE sessions to return. */ waitForExit()107 public void waitForExit() { 108 int waitMs = POLL_COUNT * POLL_INTERVAL_MS; 109 Log.i(TAG, "Waiting " + waitMs + "ms for FUSE " + getName() + " to exit..."); 110 111 try { 112 join(waitMs); 113 } catch (InterruptedException e) { 114 Thread.currentThread().interrupt(); 115 throw new IllegalStateException("Interrupted while terminating FUSE " + getName()); 116 } 117 118 if (isAlive()) { 119 throw new IllegalStateException("Failed to exit FUSE " + getName() + " successfully"); 120 } 121 122 Log.i(TAG, "Exited FUSE " + getName() + " successfully"); 123 } 124 125 /** 126 * Checks if file with {@code path} should be opened via FUSE to avoid cache inconcistencies. 127 * May place a F_RDLCK or F_WRLCK with fcntl(2) depending on {@code readLock} 128 * 129 * @return {@code true} if the file should be opened via FUSE, {@code false} otherwise 130 */ shouldOpenWithFuse(String path, boolean readLock, int fd)131 public boolean shouldOpenWithFuse(String path, boolean readLock, int fd) { 132 synchronized (mLock) { 133 if (mPtr == 0) { 134 Log.i(TAG, "shouldOpenWithFuse failed, FUSE daemon unavailable"); 135 return false; 136 } 137 return native_should_open_with_fuse(mPtr, path, readLock, fd); 138 } 139 } 140 141 /** 142 * Invalidates FUSE VFS dentry cache for {@code path} 143 */ invalidateFuseDentryCache(String path)144 public void invalidateFuseDentryCache(String path) { 145 synchronized (mLock) { 146 if (mPtr == 0) { 147 Log.i(TAG, "invalidateFuseDentryCache failed, FUSE daemon unavailable"); 148 return; 149 } 150 native_invalidate_fuse_dentry_cache(mPtr, path); 151 } 152 } 153 native_new(MediaProvider mediaProvider)154 private native long native_new(MediaProvider mediaProvider); 155 156 // Takes ownership of the passed in file descriptor! native_start(long daemon, int deviceFd, String path)157 private native void native_start(long daemon, int deviceFd, String path); 158 native_delete(long daemon)159 private native void native_delete(long daemon); native_should_open_with_fuse(long daemon, String path, boolean readLock, int fd)160 private native boolean native_should_open_with_fuse(long daemon, String path, boolean readLock, 161 int fd); native_invalidate_fuse_dentry_cache(long daemon, String path)162 private native void native_invalidate_fuse_dentry_cache(long daemon, String path); native_is_started(long daemon)163 private native boolean native_is_started(long daemon); native_is_fuse_thread()164 public static native boolean native_is_fuse_thread(); 165 } 166