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.HashMap;
32 import java.util.Objects;
33 
34 /**
35  * Starts a FUSE session to handle FUSE messages from the kernel.
36  */
37 public final class FuseDaemon extends Thread {
38     public static final String TAG = "FuseDaemonThread";
39     private static final int POLL_INTERVAL_MS = 100;
40     private static final int POLL_COUNT = 50;
41 
42     private final Object mLock = new Object();
43     private final MediaProvider mMediaProvider;
44     private final int mFuseDeviceFd;
45     private final String mPath;
46     private final boolean mUncachedMode;
47     private final String[] mSupportedTranscodingRelativePaths;
48     private final String[] mSupportedUncachedRelativePaths;
49     private final ExternalStorageServiceImpl mService;
50     @GuardedBy("mLock")
51     private long mPtr;
52 
FuseDaemon(@onNull MediaProvider mediaProvider, @NonNull ExternalStorageServiceImpl service, @NonNull ParcelFileDescriptor fd, @NonNull String sessionId, @NonNull String path, boolean uncachedMode, String[] supportedTranscodingRelativePaths, String[] supportedUncachedRelativePaths)53     public FuseDaemon(@NonNull MediaProvider mediaProvider,
54             @NonNull ExternalStorageServiceImpl service, @NonNull ParcelFileDescriptor fd,
55             @NonNull String sessionId, @NonNull String path, boolean uncachedMode,
56             String[] supportedTranscodingRelativePaths, String[] supportedUncachedRelativePaths) {
57         mMediaProvider = Objects.requireNonNull(mediaProvider);
58         mService = Objects.requireNonNull(service);
59         setName(Objects.requireNonNull(sessionId));
60         mFuseDeviceFd = Objects.requireNonNull(fd).detachFd();
61         mPath = Objects.requireNonNull(path);
62         mUncachedMode = uncachedMode;
63         mSupportedTranscodingRelativePaths
64                 = Objects.requireNonNull(supportedTranscodingRelativePaths);
65         mSupportedUncachedRelativePaths
66                 = Objects.requireNonNull(supportedUncachedRelativePaths);
67     }
68 
69     /** Starts a FUSE session. Does not return until the lower filesystem is unmounted. */
70     @Override
run()71     public void run() {
72         final long ptr;
73         synchronized (mLock) {
74             mPtr = native_new(mMediaProvider);
75             if (mPtr == 0) {
76                 throw new IllegalStateException("Unable to create native FUSE daemon");
77             }
78             ptr = mPtr;
79         }
80 
81         Log.i(TAG, "Starting thread for " + getName() + " ...");
82         native_start(ptr, mFuseDeviceFd, mPath, mUncachedMode,
83                 mSupportedTranscodingRelativePaths,
84                 mSupportedUncachedRelativePaths); // Blocks
85         Log.i(TAG, "Exiting thread for " + getName() + " ...");
86 
87         synchronized (mLock) {
88             native_delete(mPtr);
89             mPtr = 0;
90         }
91         mService.onExitSession(getName());
92         Log.i(TAG, "Exited thread for " + getName());
93     }
94 
95     @Override
start()96     public synchronized void start() {
97         super.start();
98 
99         // Wait for native_start
100         waitForStart();
101 
102         // Initialize device id
103         initializeDeviceId();
104     }
105 
initializeDeviceId()106     private void initializeDeviceId() {
107         synchronized (mLock) {
108             if (mPtr == 0) {
109                 Log.e(TAG, "initializeDeviceId failed, FUSE daemon unavailable");
110                 return;
111             }
112             String path = FileUtils.toFuseFile(new File(mPath)).getAbsolutePath();
113             native_initialize_device_id(mPtr, path);
114         }
115     }
116 
waitForStart()117     private void waitForStart() {
118         int count = POLL_COUNT;
119         while (count-- > 0) {
120             synchronized (mLock) {
121                 if (mPtr != 0 && native_is_started(mPtr)) {
122                     return;
123                 }
124             }
125             try {
126                 Log.v(TAG, "Waiting " + POLL_INTERVAL_MS + "ms for FUSE start. Count " + count);
127                 Thread.sleep(POLL_INTERVAL_MS);
128             } catch (InterruptedException e) {
129                 Thread.currentThread().interrupt();
130                 Log.e(TAG, "Interrupted while starting FUSE", e);
131             }
132         }
133         throw new IllegalStateException("Failed to start FUSE");
134     }
135 
136     /** Waits for any running FUSE sessions to return. */
waitForExit()137     public void waitForExit() {
138         int waitMs = POLL_COUNT * POLL_INTERVAL_MS;
139         Log.i(TAG, "Waiting " + waitMs + "ms for FUSE " + getName() + " to exit...");
140 
141         try {
142             join(waitMs);
143         } catch (InterruptedException e) {
144             Thread.currentThread().interrupt();
145             throw new IllegalStateException("Interrupted while terminating FUSE " + getName());
146         }
147 
148         if (isAlive()) {
149             throw new IllegalStateException("Failed to exit FUSE " + getName() + " successfully");
150         }
151 
152         Log.i(TAG, "Exited FUSE " + getName() + " successfully");
153     }
154 
155     /**
156      * Checks if file with {@code path} should be opened via FUSE to avoid cache inconcistencies.
157      * May place a F_RDLCK or F_WRLCK with fcntl(2) depending on {@code readLock}
158      *
159      * @return {@code true} if the file should be opened via FUSE, {@code false} otherwise
160      */
shouldOpenWithFuse(String path, boolean readLock, int fd)161     public boolean shouldOpenWithFuse(String path, boolean readLock, int fd) {
162         synchronized (mLock) {
163             if (mPtr == 0) {
164                 Log.i(TAG, "shouldOpenWithFuse failed, FUSE daemon unavailable");
165                 return false;
166             }
167             return native_should_open_with_fuse(mPtr, path, readLock, fd);
168         }
169     }
170 
171     /**
172      * Checks if the FuseDaemon uses the FUSE passthrough feature.
173      *
174      * @return {@code true} if the FuseDaemon uses FUSE passthrough, {@code false} otherwise
175      */
usesFusePassthrough()176     public boolean usesFusePassthrough() {
177         synchronized (mLock) {
178             if (mPtr == 0) {
179                 Log.i(TAG, "usesFusePassthrough failed, FUSE daemon unavailable");
180                 return false;
181             }
182             return native_uses_fuse_passthrough(mPtr);
183         }
184     }
185 
186     /**
187      * Invalidates FUSE VFS dentry cache for {@code path}
188      */
invalidateFuseDentryCache(String path)189     public void invalidateFuseDentryCache(String path) {
190         synchronized (mLock) {
191             if (mPtr == 0) {
192                 Log.i(TAG, "invalidateFuseDentryCache failed, FUSE daemon unavailable");
193                 return;
194             }
195             native_invalidate_fuse_dentry_cache(mPtr, path);
196         }
197     }
198 
checkFdAccess(ParcelFileDescriptor fileDescriptor, int uid)199     public FdAccessResult checkFdAccess(ParcelFileDescriptor fileDescriptor, int uid)
200             throws IOException {
201         synchronized (mLock) {
202             if (mPtr == 0) {
203                 throw new IOException("FUSE daemon unavailable");
204             }
205             return native_check_fd_access(mPtr, fileDescriptor.getFd(), uid);
206         }
207     }
208 
209     /**
210      * Sets up volume's database backup to external storage to recover during a rollback.
211      */
setupVolumeDbBackup()212     public void setupVolumeDbBackup() throws IOException {
213         synchronized (mLock) {
214             if (mPtr == 0) {
215                 throw new IOException("FUSE daemon unavailable");
216             }
217             native_setup_volume_db_backup(mPtr);
218         }
219     }
220 
221     /**
222      * Sets up public volume's database backup to external storage to recover during a rollback.
223      */
setupPublicVolumeDbBackup(String volumeName)224     public void setupPublicVolumeDbBackup(String volumeName) throws IOException {
225         synchronized (mLock) {
226             if (mPtr == 0) {
227                 throw new IOException("FUSE daemon unavailable");
228             }
229             native_setup_public_volume_db_backup(mPtr, volumeName);
230         }
231     }
232 
233     /**
234      * Deletes entry for given key from external storage.
235      */
deleteDbBackup(String key)236     public void deleteDbBackup(String key) throws IOException {
237         synchronized (mLock) {
238             if (mPtr == 0) {
239                 throw new IOException("FUSE daemon unavailable");
240             }
241             native_delete_db_backup(mPtr, key);
242         }
243     }
244 
245     /**
246      * Backs up given key-value pair in external storage for provided volume.
247      */
backupVolumeDbData(String volumeName, String key, String value)248     public void backupVolumeDbData(String volumeName, String key, String value) throws IOException {
249         synchronized (mLock) {
250             if (mPtr == 0) {
251                 throw new IOException("FUSE daemon unavailable");
252             }
253             native_backup_volume_db_data(mPtr, volumeName, key, value);
254         }
255     }
256 
257     /**
258      * Reads backed up file paths for given volume from external storage.
259      */
readBackedUpFilePaths(String volumeName, String lastReadValue, int limit)260     public String[] readBackedUpFilePaths(String volumeName, String lastReadValue, int limit)
261             throws IOException {
262         synchronized (mLock) {
263             if (mPtr == 0) {
264                 throw new IOException("FUSE daemon unavailable");
265             }
266             return native_read_backed_up_file_paths(mPtr, volumeName, lastReadValue, limit);
267         }
268     }
269 
270     /**
271      * Reads backed up data for given file from external storage.
272      */
readBackedUpData(String filePath)273     public String readBackedUpData(String filePath) throws IOException {
274         synchronized (mLock) {
275             if (mPtr == 0) {
276                 throw new IOException("FUSE daemon unavailable");
277             }
278             return native_read_backed_up_data(mPtr, filePath);
279         }
280     }
281 
282     /**
283      * Reads owner id for given owner package identifier from external storage.
284      */
readFromOwnershipBackup(String ownerPackageIdentifier)285     public String readFromOwnershipBackup(String ownerPackageIdentifier) throws IOException {
286         synchronized (mLock) {
287             if (mPtr == 0) {
288                 throw new IOException("FUSE daemon unavailable");
289             }
290             return native_read_ownership(mPtr, ownerPackageIdentifier);
291         }
292     }
293 
294     /**
295      * Creates owner id to owner package identifier and vice versa relation in external storage.
296      */
createOwnerIdRelation(String ownerId, String ownerPackageIdentifier)297     public void createOwnerIdRelation(String ownerId, String ownerPackageIdentifier)
298             throws IOException {
299         synchronized (mLock) {
300             if (mPtr == 0) {
301                 throw new IOException("FUSE daemon unavailable");
302             }
303             native_create_owner_id_relation(mPtr, ownerId, ownerPackageIdentifier);
304         }
305     }
306 
307     /**
308      * Removes owner id to owner package identifier and vice versa relation in external storage.
309      */
removeOwnerIdRelation(String ownerId, String ownerPackageIdentifier)310     public void removeOwnerIdRelation(String ownerId, String ownerPackageIdentifier)
311             throws IOException {
312         synchronized (mLock) {
313             if (mPtr == 0) {
314                 throw new IOException("FUSE daemon unavailable");
315             }
316             native_remove_owner_id_relation(mPtr, ownerId, ownerPackageIdentifier);
317         }
318     }
319 
320     /**
321      * Reads all owner id relations from external storage.
322      */
readOwnerIdRelations()323     public HashMap<String, String> readOwnerIdRelations() throws IOException {
324         synchronized (mLock) {
325             if (mPtr == 0) {
326                 throw new IOException("FUSE daemon unavailable");
327             }
328             return native_read_owner_relations(mPtr);
329         }
330     }
331 
native_new(MediaProvider mediaProvider)332     private native long native_new(MediaProvider mediaProvider);
333 
334     // Takes ownership of the passed in file descriptor!
native_start(long daemon, int deviceFd, String path, boolean uncachedMode, String[] supportedTranscodingRelativePaths, String[] supportedUncachedRelativePaths)335     private native void native_start(long daemon, int deviceFd, String path,
336             boolean uncachedMode, String[] supportedTranscodingRelativePaths,
337             String[] supportedUncachedRelativePaths);
338 
native_delete(long daemon)339     private native void native_delete(long daemon);
native_should_open_with_fuse(long daemon, String path, boolean readLock, int fd)340     private native boolean native_should_open_with_fuse(long daemon, String path, boolean readLock,
341             int fd);
native_uses_fuse_passthrough(long daemon)342     private native boolean native_uses_fuse_passthrough(long daemon);
native_invalidate_fuse_dentry_cache(long daemon, String path)343     private native void native_invalidate_fuse_dentry_cache(long daemon, String path);
native_is_started(long daemon)344     private native boolean native_is_started(long daemon);
native_check_fd_access(long daemon, int fd, int uid)345     private native FdAccessResult native_check_fd_access(long daemon, int fd, int uid);
native_initialize_device_id(long daemon, String path)346     private native void native_initialize_device_id(long daemon, String path);
native_setup_volume_db_backup(long daemon)347     private native void native_setup_volume_db_backup(long daemon);
native_setup_public_volume_db_backup(long daemon, String volumeName)348     private native void native_setup_public_volume_db_backup(long daemon, String volumeName);
native_delete_db_backup(long daemon, String key)349     private native void native_delete_db_backup(long daemon, String key);
native_backup_volume_db_data(long daemon, String volumeName, String key, String value)350     private native void native_backup_volume_db_data(long daemon, String volumeName, String key,
351             String value);
native_read_backed_up_file_paths(long daemon, String volumeName, String lastReadValue, int limit)352     private native String[] native_read_backed_up_file_paths(long daemon, String volumeName,
353             String lastReadValue, int limit);
native_read_backed_up_data(long daemon, String key)354     private native String native_read_backed_up_data(long daemon, String key);
native_read_ownership(long daemon, String ownerPackageIdentifier)355     private native String native_read_ownership(long daemon, String ownerPackageIdentifier);
native_create_owner_id_relation(long daemon, String ownerId, String ownerPackageIdentifier)356     private native void native_create_owner_id_relation(long daemon, String ownerId,
357             String ownerPackageIdentifier);
native_remove_owner_id_relation(long daemon, String ownerId, String ownerPackageIdentifier)358     private native void native_remove_owner_id_relation(long daemon, String ownerId,
359             String ownerPackageIdentifier);
native_read_owner_relations(long daemon)360     private native HashMap<String, String> native_read_owner_relations(long daemon);
native_is_fuse_thread()361     public static native boolean native_is_fuse_thread();
362 }
363