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