• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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