1 /*
2  * Copyright (C) 2016 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.server.storage;
18 
19 import android.os.FileUtils;
20 import android.os.ParcelFileDescriptor;
21 import android.system.ErrnoException;
22 import android.system.Os;
23 import android.util.SparseArray;
24 import com.android.internal.annotations.GuardedBy;
25 import com.android.internal.os.FuseUnavailableMountException;
26 import com.android.internal.util.Preconditions;
27 import com.android.server.NativeDaemonConnectorException;
28 import libcore.io.IoUtils;
29 import java.util.concurrent.CountDownLatch;
30 
31 /**
32  * Runnable that delegates FUSE command from the kernel to application.
33  * run() blocks until all opened files on the FUSE mount point are closed. So this should be run in
34  * a separated thread.
35  */
36 public class AppFuseBridge implements Runnable {
37     public static final String TAG = "AppFuseBridge";
38 
39     /**
40      * The path AppFuse is mounted to.
41      * The first number is UID who is mounting the FUSE.
42      * THe second number is mount ID.
43      * The path must be sync with vold.
44      */
45     private static final String APPFUSE_MOUNT_NAME_TEMPLATE = "/mnt/appfuse/%d_%d";
46 
47     @GuardedBy("this")
48     private final SparseArray<MountScope> mScopes = new SparseArray<>();
49 
50     @GuardedBy("this")
51     private long mNativeLoop;
52 
AppFuseBridge()53     public AppFuseBridge() {
54         mNativeLoop = native_new();
55     }
56 
addBridge(MountScope mountScope)57     public ParcelFileDescriptor addBridge(MountScope mountScope)
58             throws FuseUnavailableMountException, NativeDaemonConnectorException {
59         /*
60         ** Dead Lock between Java lock (AppFuseBridge.java) and Native lock (FuseBridgeLoop.cc)
61         **
62         **  (Thread A) Got Java lock (addBrdige) -> Try to get Native lock (native_add_brdige)
63         **  (Thread B)        Got Native lock (FuseBrdigeLoop.start) -> Try to get Java lock (onClosed)
64         **
65         ** Guarantee the lock order (native lock -> java lock) when adding Bridge.
66         */
67         native_lock();
68         try {
69             synchronized (this) {
70                 Preconditions.checkArgument(mScopes.indexOfKey(mountScope.mountId) < 0);
71                 if (mNativeLoop == 0) {
72                     throw new FuseUnavailableMountException(mountScope.mountId);
73                 }
74                 final int fd = native_add_bridge(
75                         mNativeLoop, mountScope.mountId, mountScope.open().detachFd());
76                 if (fd == -1) {
77                     throw new FuseUnavailableMountException(mountScope.mountId);
78                 }
79                 final ParcelFileDescriptor result = ParcelFileDescriptor.adoptFd(fd);
80                 mScopes.put(mountScope.mountId, mountScope);
81                 mountScope = null;
82                 return result;
83             }
84         } finally {
85             native_unlock();
86             IoUtils.closeQuietly(mountScope);
87         }
88     }
89 
90     @Override
91     public void run() {
92         native_start_loop(mNativeLoop);
93         synchronized (this) {
94             native_delete(mNativeLoop);
95             mNativeLoop = 0;
96         }
97     }
98 
99     public ParcelFileDescriptor openFile(int mountId, int fileId, int mode)
100             throws FuseUnavailableMountException, InterruptedException {
101         final MountScope scope;
102         synchronized (this) {
103             scope = mScopes.get(mountId);
104             if (scope == null) {
105                 throw new FuseUnavailableMountException(mountId);
106             }
107         }
108         final boolean result = scope.waitForMount();
109         if (result == false) {
110             throw new FuseUnavailableMountException(mountId);
111         }
112         try {
113             int flags = FileUtils.translateModePfdToPosix(mode);
114             return scope.openFile(mountId, fileId, flags);
115         } catch (NativeDaemonConnectorException error) {
116             throw new FuseUnavailableMountException(mountId);
117         }
118     }
119 
120     // Used by com_android_server_storage_AppFuse.cpp.
121     synchronized private void onMount(int mountId) {
122         final MountScope scope = mScopes.get(mountId);
123         if (scope != null) {
124             scope.setMountResultLocked(true);
125         }
126     }
127 
128     // Used by com_android_server_storage_AppFuse.cpp.
129     synchronized private void onClosed(int mountId) {
130         final MountScope scope = mScopes.get(mountId);
131         if (scope != null) {
132             scope.setMountResultLocked(false);
133             IoUtils.closeQuietly(scope);
134             mScopes.remove(mountId);
135         }
136     }
137 
138     public static abstract class MountScope implements AutoCloseable {
139         public final int uid;
140         public final int mountId;
141         private final CountDownLatch mMounted = new CountDownLatch(1);
142         private boolean mMountResult = false;
143 
144         public MountScope(int uid, int mountId) {
145             this.uid = uid;
146             this.mountId = mountId;
147         }
148 
149         @GuardedBy("AppFuseBridge.this")
150         void setMountResultLocked(boolean result) {
151             if (mMounted.getCount() == 0) {
152                 return;
153             }
154             mMountResult = result;
155             mMounted.countDown();
156         }
157 
158         boolean waitForMount() throws InterruptedException {
159             mMounted.await();
160             return mMountResult;
161         }
162 
163         public abstract ParcelFileDescriptor open() throws NativeDaemonConnectorException;
164         public abstract ParcelFileDescriptor openFile(int mountId, int fileId, int flags)
165                 throws NativeDaemonConnectorException;
166     }
167 
168     private native long native_new();
169     private native void native_delete(long loop);
170     private native void native_start_loop(long loop);
171     private native int native_add_bridge(long loop, int mountId, int deviceId);
172     private native void native_lock();
173     private native void native_unlock();
174 }
175