/*
|
* Copyright (C) 2016 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
|
package com.android.server.storage;
|
|
import android.os.FileUtils;
|
import android.os.ParcelFileDescriptor;
|
import android.system.ErrnoException;
|
import android.system.Os;
|
import android.util.SparseArray;
|
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.os.FuseUnavailableMountException;
|
import com.android.internal.util.Preconditions;
|
import com.android.server.NativeDaemonConnectorException;
|
import libcore.io.IoUtils;
|
import java.util.concurrent.CountDownLatch;
|
|
/**
|
* Runnable that delegates FUSE command from the kernel to application.
|
* run() blocks until all opened files on the FUSE mount point are closed. So this should be run in
|
* a separated thread.
|
*/
|
public class AppFuseBridge implements Runnable {
|
public static final String TAG = "AppFuseBridge";
|
|
/**
|
* The path AppFuse is mounted to.
|
* The first number is UID who is mounting the FUSE.
|
* THe second number is mount ID.
|
* The path must be sync with vold.
|
*/
|
private static final String APPFUSE_MOUNT_NAME_TEMPLATE = "/mnt/appfuse/%d_%d";
|
|
@GuardedBy("this")
|
private final SparseArray<MountScope> mScopes = new SparseArray<>();
|
|
@GuardedBy("this")
|
private long mNativeLoop;
|
|
public AppFuseBridge() {
|
mNativeLoop = native_new();
|
}
|
|
public ParcelFileDescriptor addBridge(MountScope mountScope)
|
throws FuseUnavailableMountException, NativeDaemonConnectorException {
|
try {
|
synchronized (this) {
|
Preconditions.checkArgument(mScopes.indexOfKey(mountScope.mountId) < 0);
|
if (mNativeLoop == 0) {
|
throw new FuseUnavailableMountException(mountScope.mountId);
|
}
|
final int fd = native_add_bridge(
|
mNativeLoop, mountScope.mountId, mountScope.open().detachFd());
|
if (fd == -1) {
|
throw new FuseUnavailableMountException(mountScope.mountId);
|
}
|
final ParcelFileDescriptor result = ParcelFileDescriptor.adoptFd(fd);
|
mScopes.put(mountScope.mountId, mountScope);
|
mountScope = null;
|
return result;
|
}
|
} finally {
|
IoUtils.closeQuietly(mountScope);
|
}
|
}
|
|
@Override
|
public void run() {
|
native_start_loop(mNativeLoop);
|
synchronized (this) {
|
native_delete(mNativeLoop);
|
mNativeLoop = 0;
|
}
|
}
|
|
public ParcelFileDescriptor openFile(int mountId, int fileId, int mode)
|
throws FuseUnavailableMountException, InterruptedException {
|
final MountScope scope;
|
synchronized (this) {
|
scope = mScopes.get(mountId);
|
if (scope == null) {
|
throw new FuseUnavailableMountException(mountId);
|
}
|
}
|
final boolean result = scope.waitForMount();
|
if (result == false) {
|
throw new FuseUnavailableMountException(mountId);
|
}
|
try {
|
int flags = FileUtils.translateModePfdToPosix(mode);
|
return scope.openFile(mountId, fileId, flags);
|
} catch (NativeDaemonConnectorException error) {
|
throw new FuseUnavailableMountException(mountId);
|
}
|
}
|
|
// Used by com_android_server_storage_AppFuse.cpp.
|
synchronized private void onMount(int mountId) {
|
final MountScope scope = mScopes.get(mountId);
|
if (scope != null) {
|
scope.setMountResultLocked(true);
|
}
|
}
|
|
// Used by com_android_server_storage_AppFuse.cpp.
|
synchronized private void onClosed(int mountId) {
|
final MountScope scope = mScopes.get(mountId);
|
if (scope != null) {
|
scope.setMountResultLocked(false);
|
IoUtils.closeQuietly(scope);
|
mScopes.remove(mountId);
|
}
|
}
|
|
public static abstract class MountScope implements AutoCloseable {
|
public final int uid;
|
public final int mountId;
|
private final CountDownLatch mMounted = new CountDownLatch(1);
|
private boolean mMountResult = false;
|
|
public MountScope(int uid, int mountId) {
|
this.uid = uid;
|
this.mountId = mountId;
|
}
|
|
@GuardedBy("AppFuseBridge.this")
|
void setMountResultLocked(boolean result) {
|
if (mMounted.getCount() == 0) {
|
return;
|
}
|
mMountResult = result;
|
mMounted.countDown();
|
}
|
|
boolean waitForMount() throws InterruptedException {
|
mMounted.await();
|
return mMountResult;
|
}
|
|
public abstract ParcelFileDescriptor open() throws NativeDaemonConnectorException;
|
public abstract ParcelFileDescriptor openFile(int mountId, int fileId, int flags)
|
throws NativeDaemonConnectorException;
|
}
|
|
private native long native_new();
|
private native void native_delete(long loop);
|
private native void native_start_loop(long loop);
|
private native int native_add_bridge(long loop, int mountId, int deviceId);
|
}
|