blob: 5a1f473b5ea282e3b9d9a898cc7e8a003f3ff35d [file] [log] [blame]
/*
* 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.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.Os;
import com.android.internal.util.Preconditions;
import libcore.io.IoUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.concurrent.BlockingQueue;
/**
* 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, AutoCloseable {
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";
private final IMountScope mMountScope;
private final ParcelFileDescriptor mProxyFd;
private final BlockingQueue<Boolean> mChannel;
/**
* @param mountScope Listener to unmount mount point.
* @param proxyFd FD of socket pair. Ownership of FD is taken by AppFuseBridge.
* @param channel Channel that the runnable send mount result to.
*/
public AppFuseBridge(
IMountScope mountScope, ParcelFileDescriptor proxyFd, BlockingQueue<Boolean> channel) {
Preconditions.checkNotNull(mountScope);
Preconditions.checkNotNull(proxyFd);
Preconditions.checkNotNull(channel);
mMountScope = mountScope;
mProxyFd = proxyFd;
mChannel = channel;
}
@Override
public void run() {
try {
// deviceFd and proxyFd must be closed in native_start_loop.
native_start_loop(
mMountScope.getDeviceFileDescriptor().detachFd(),
mProxyFd.detachFd());
} finally {
close();
}
}
public static ParcelFileDescriptor openFile(int uid, int mountId, int fileId, int mode)
throws FileNotFoundException {
final File mountPoint = getMountPoint(uid, mountId);
try {
if (Os.stat(mountPoint.getPath()).st_ino != 1) {
throw new FileNotFoundException("Could not find bridge mount point.");
}
} catch (ErrnoException e) {
throw new FileNotFoundException(
"Failed to stat mount point: " + mountPoint.getParent());
}
return ParcelFileDescriptor.open(new File(mountPoint, String.valueOf(fileId)), mode);
}
private static File getMountPoint(int uid, int mountId) {
return new File(String.format(APPFUSE_MOUNT_NAME_TEMPLATE, uid, mountId));
}
@Override
public void close() {
IoUtils.closeQuietly(mMountScope);
IoUtils.closeQuietly(mProxyFd);
// Invoke countDown here in case where close is invoked before mount.
mChannel.offer(false);
}
// Used by com_android_server_storage_AppFuse.cpp.
private void onMount() {
mChannel.offer(true);
}
public static interface IMountScope extends AutoCloseable {
ParcelFileDescriptor getDeviceFileDescriptor();
}
private native boolean native_start_loop(int deviceFd, int proxyFd);
}