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