Merge changes I1606b184,Id78dd4ab
* changes: Publish StorageManager#openProxyFileDescriptor API. Add StorageManager#openProxyFileDescriptor.
This commit is contained in:
committed by
Android (Google) Code Review
commit
b55db281da
@@ -29900,6 +29900,15 @@ package android.os {
|
||||
field public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; // 0xfffffff8
|
||||
}
|
||||
|
||||
public abstract class ProxyFileDescriptorCallback {
|
||||
ctor public ProxyFileDescriptorCallback();
|
||||
method public void onFsync() throws android.system.ErrnoException;
|
||||
method public long onGetSize() throws android.system.ErrnoException;
|
||||
method public int onRead(long, int, byte[]) throws android.system.ErrnoException;
|
||||
method public abstract void onRelease();
|
||||
method public int onWrite(long, int, byte[]) throws android.system.ErrnoException;
|
||||
}
|
||||
|
||||
public class RecoverySystem {
|
||||
method public static void installPackage(android.content.Context, java.io.File) throws java.io.IOException;
|
||||
method public static void rebootWipeCache(android.content.Context) throws java.io.IOException;
|
||||
@@ -30317,6 +30326,7 @@ package android.os.storage {
|
||||
method public boolean isEncrypted(java.io.File);
|
||||
method public boolean isObbMounted(java.lang.String);
|
||||
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
|
||||
method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
|
||||
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
|
||||
field public static final java.lang.String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
|
||||
}
|
||||
|
||||
@@ -32521,6 +32521,15 @@ package android.os {
|
||||
field public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; // 0xfffffff8
|
||||
}
|
||||
|
||||
public abstract class ProxyFileDescriptorCallback {
|
||||
ctor public ProxyFileDescriptorCallback();
|
||||
method public void onFsync() throws android.system.ErrnoException;
|
||||
method public long onGetSize() throws android.system.ErrnoException;
|
||||
method public int onRead(long, int, byte[]) throws android.system.ErrnoException;
|
||||
method public abstract void onRelease();
|
||||
method public int onWrite(long, int, byte[]) throws android.system.ErrnoException;
|
||||
}
|
||||
|
||||
public class RecoverySystem {
|
||||
method public static void cancelScheduledUpdate(android.content.Context) throws java.io.IOException;
|
||||
method public static void installPackage(android.content.Context, java.io.File) throws java.io.IOException;
|
||||
@@ -33033,6 +33042,7 @@ package android.os.storage {
|
||||
method public boolean isEncrypted(java.io.File);
|
||||
method public boolean isObbMounted(java.lang.String);
|
||||
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
|
||||
method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
|
||||
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
|
||||
field public static final java.lang.String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
|
||||
}
|
||||
|
||||
@@ -30010,6 +30010,15 @@ package android.os {
|
||||
field public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; // 0xfffffff8
|
||||
}
|
||||
|
||||
public abstract class ProxyFileDescriptorCallback {
|
||||
ctor public ProxyFileDescriptorCallback();
|
||||
method public void onFsync() throws android.system.ErrnoException;
|
||||
method public long onGetSize() throws android.system.ErrnoException;
|
||||
method public int onRead(long, int, byte[]) throws android.system.ErrnoException;
|
||||
method public abstract void onRelease();
|
||||
method public int onWrite(long, int, byte[]) throws android.system.ErrnoException;
|
||||
}
|
||||
|
||||
public class RecoverySystem {
|
||||
method public static void installPackage(android.content.Context, java.io.File) throws java.io.IOException;
|
||||
method public static void rebootWipeCache(android.content.Context) throws java.io.IOException;
|
||||
@@ -30428,6 +30437,7 @@ package android.os.storage {
|
||||
method public boolean isEncrypted(java.io.File);
|
||||
method public boolean isObbMounted(java.lang.String);
|
||||
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
|
||||
method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
|
||||
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
|
||||
field public static final java.lang.String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
|
||||
}
|
||||
|
||||
@@ -17,18 +17,20 @@
|
||||
package android.os;
|
||||
|
||||
import android.system.ErrnoException;
|
||||
import android.system.OsConstants;
|
||||
|
||||
/**
|
||||
* Callback that handles file system requests from ProxyFileDescriptor.
|
||||
* @hide
|
||||
*/
|
||||
public interface IProxyFileDescriptorCallback {
|
||||
public abstract class ProxyFileDescriptorCallback {
|
||||
/**
|
||||
* Returns size of bytes provided by the file descriptor.
|
||||
* @return Size of bytes
|
||||
* @throws ErrnoException
|
||||
*/
|
||||
long onGetSize() throws ErrnoException;
|
||||
public long onGetSize() throws ErrnoException {
|
||||
throw new ErrnoException("onGetSize", OsConstants.EBADF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides bytes read from file descriptor.
|
||||
@@ -39,7 +41,9 @@ public interface IProxyFileDescriptorCallback {
|
||||
* @return Size of bytes returned by the function.
|
||||
* @throws ErrnoException
|
||||
*/
|
||||
int onRead(long offset, int size, byte[] data) throws ErrnoException;
|
||||
public int onRead(long offset, int size, byte[] data) throws ErrnoException {
|
||||
throw new ErrnoException("onRead", OsConstants.EBADF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles bytes written to file descriptor.
|
||||
@@ -49,11 +53,20 @@ public interface IProxyFileDescriptorCallback {
|
||||
* @return Size of bytes processed by the function.
|
||||
* @throws ErrnoException
|
||||
*/
|
||||
int onWrite(long offset, int size, byte[] data) throws ErrnoException;
|
||||
public int onWrite(long offset, int size, byte[] data) throws ErrnoException {
|
||||
throw new ErrnoException("onWrite", OsConstants.EBADF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes fsync request.
|
||||
* @throws ErrnoException
|
||||
*/
|
||||
void onFsync() throws ErrnoException;
|
||||
public void onFsync() throws ErrnoException {
|
||||
throw new ErrnoException("onFsync", OsConstants.EINVAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked after the file is closed.
|
||||
*/
|
||||
abstract public void onRelease();
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import android.os.storage.IObbActionListener;
|
||||
import android.os.storage.StorageVolume;
|
||||
import android.os.storage.VolumeInfo;
|
||||
import android.os.storage.VolumeRecord;
|
||||
import com.android.internal.os.AppFuseMount;
|
||||
|
||||
/**
|
||||
* WARNING! Update IMountService.h and IMountService.cpp if you change this
|
||||
@@ -289,4 +290,6 @@ interface IStorageManager {
|
||||
void addUserKeyAuth(int userId, int serialNumber, in byte[] token, in byte[] secret) = 70;
|
||||
void fixateNewestUserKeyAuth(int userId) = 71;
|
||||
void fstrim(int flags) = 72;
|
||||
AppFuseMount mountProxyFileDescriptorBridge() = 73;
|
||||
ParcelFileDescriptor openProxyFileDescriptor(int mountPointId, int fileId, int mode) = 74;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import android.os.Binder;
|
||||
import android.os.Environment;
|
||||
import android.os.FileUtils;
|
||||
import android.os.Handler;
|
||||
import android.os.ProxyFileDescriptorCallback;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
@@ -44,7 +45,10 @@ import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.os.AppFuseMount;
|
||||
import com.android.internal.os.FuseAppLoop;
|
||||
import com.android.internal.os.RoSystemProperties;
|
||||
import com.android.internal.os.SomeArgs;
|
||||
import com.android.internal.util.Preconditions;
|
||||
@@ -62,6 +66,7 @@ import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
@@ -1322,6 +1327,90 @@ public class StorageManager {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** {@hide} */
|
||||
@VisibleForTesting
|
||||
public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
|
||||
int mode, ProxyFileDescriptorCallback callback, ThreadFactory factory)
|
||||
throws IOException {
|
||||
// Retry is needed because the mount point mFuseAppLoop is using may be unmounted before
|
||||
// invoking StorageManagerService#openProxyFileDescriptor. In this case, we need to re-mount
|
||||
// the bridge by calling mountProxyFileDescriptorBridge.
|
||||
int retry = 3;
|
||||
while (retry-- > 0) {
|
||||
try {
|
||||
synchronized (mFuseAppLoopLock) {
|
||||
if (mFuseAppLoop == null) {
|
||||
final AppFuseMount mount = mStorageManager.mountProxyFileDescriptorBridge();
|
||||
if (mount == null) {
|
||||
Log.e(TAG, "Failed to open proxy file bridge.");
|
||||
throw new IOException("Failed to open proxy file bridge.");
|
||||
}
|
||||
mFuseAppLoop = FuseAppLoop.open(mount.mountPointId, mount.fd, factory);
|
||||
}
|
||||
|
||||
try {
|
||||
final int fileId = mFuseAppLoop.registerCallback(callback);
|
||||
final ParcelFileDescriptor pfd =
|
||||
mStorageManager.openProxyFileDescriptor(
|
||||
mFuseAppLoop.getMountPointId(), fileId, mode);
|
||||
if (pfd != null) {
|
||||
return pfd;
|
||||
}
|
||||
// Probably the bridge is being unmounted but mFuseAppLoop has not been
|
||||
// noticed it yet.
|
||||
mFuseAppLoop.unregisterCallback(fileId);
|
||||
} catch (FuseAppLoop.UnmountedException error) {
|
||||
Log.d(TAG, "mFuseAppLoop has been already unmounted.");
|
||||
mFuseAppLoop = null;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
throw new IOException("Failed to mount bridge.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens seekable ParcelFileDescriptor that routes file operation requests to
|
||||
* ProxyFileDescriptorCallback.
|
||||
*
|
||||
* @param mode The desired access mode, must be one of
|
||||
* {@link ParcelFileDescriptor#MODE_READ_ONLY},
|
||||
* {@link ParcelFileDescriptor#MODE_WRITE_ONLY}, or
|
||||
* {@link ParcelFileDescriptor#MODE_READ_WRITE}
|
||||
* @param callback Callback to process file operation requests issued on returned file
|
||||
* descriptor. The callback is invoked on a thread managed by the framework.
|
||||
* @return Seekable ParcelFileDescriptor.
|
||||
* @throws IOException
|
||||
*/
|
||||
public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
|
||||
int mode, ProxyFileDescriptorCallback callback)
|
||||
throws IOException {
|
||||
return openProxyFileDescriptor(mode, callback, null);
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
@VisibleForTesting
|
||||
public int getProxyFileDescriptorMountPointId() {
|
||||
synchronized (mFuseAppLoopLock) {
|
||||
return mFuseAppLoop != null ? mFuseAppLoop.getMountPointId() : -1;
|
||||
}
|
||||
}
|
||||
|
||||
private final Object mFuseAppLoopLock = new Object();
|
||||
|
||||
@GuardedBy("mFuseAppLoopLock")
|
||||
private @Nullable FuseAppLoop mFuseAppLoop = null;
|
||||
|
||||
/// Consts to match the password types in cryptfs.h
|
||||
/** @hide */
|
||||
public static final int CRYPT_TYPE_PASSWORD = 0;
|
||||
|
||||
19
core/java/com/android/internal/os/AppFuseMount.aidl
Normal file
19
core/java/com/android/internal/os/AppFuseMount.aidl
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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.internal.os;
|
||||
|
||||
parcelable AppFuseMount;
|
||||
@@ -19,14 +19,26 @@ package com.android.internal.os;
|
||||
import android.os.Parcel;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Parcelable;
|
||||
import java.io.File;
|
||||
import android.os.storage.IStorageManager;
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
/**
|
||||
* Parcelable class representing AppFuse mount.
|
||||
* This conveys the result for IStorageManager#openProxyFileDescriptor.
|
||||
* @see IStorageManager#openProxyFileDescriptor
|
||||
*/
|
||||
public class AppFuseMount implements Parcelable {
|
||||
final public File mountPoint;
|
||||
final public int mountPointId;
|
||||
final public ParcelFileDescriptor fd;
|
||||
|
||||
public AppFuseMount(File mountPoint, ParcelFileDescriptor fd) {
|
||||
this.mountPoint = mountPoint;
|
||||
/**
|
||||
* @param mountPointId Integer number for mount point that is unique in the lifetime of
|
||||
* StorageManagerService.
|
||||
* @param fd File descriptor pointing /dev/fuse and tagged with the mount point.
|
||||
*/
|
||||
public AppFuseMount(int mountPointId, ParcelFileDescriptor fd) {
|
||||
Preconditions.checkNotNull(fd);
|
||||
this.mountPointId = mountPointId;
|
||||
this.fd = fd;
|
||||
}
|
||||
|
||||
@@ -37,7 +49,7 @@ public class AppFuseMount implements Parcelable {
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(this.mountPoint.getPath());
|
||||
dest.writeInt(this.mountPointId);
|
||||
dest.writeParcelable(fd, flags);
|
||||
}
|
||||
|
||||
@@ -45,7 +57,7 @@ public class AppFuseMount implements Parcelable {
|
||||
new Parcelable.Creator<AppFuseMount>() {
|
||||
@Override
|
||||
public AppFuseMount createFromParcel(Parcel in) {
|
||||
return new AppFuseMount(new File(in.readString()), in.readParcelable(null));
|
||||
return new AppFuseMount(in.readInt(), in.readParcelable(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,34 +18,38 @@ package com.android.internal.os;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.IProxyFileDescriptorCallback;
|
||||
import android.os.ProxyFileDescriptorCallback;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.OsConstants;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
public class FuseAppLoop {
|
||||
private static final String TAG = "FuseAppLoop";
|
||||
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
public static final int ROOT_INODE = 1;
|
||||
private static final int MIN_INODE = 2;
|
||||
private static final ThreadFactory sDefaultThreadFactory = new ThreadFactory() {
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
return new Thread(r, TAG);
|
||||
}
|
||||
};
|
||||
|
||||
private final Object mLock = new Object();
|
||||
private final File mParent;
|
||||
private final int mMountPointId;
|
||||
private final Thread mThread;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private final SparseArray<CallbackEntry> mCallbackMap = new SparseArray<>();
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private boolean mActive = true;
|
||||
|
||||
/**
|
||||
* Sequential number can be used as file name and inode in AppFuse.
|
||||
* 0 is regarded as an error, 1 is mount point. So we start the number from 2.
|
||||
@@ -53,35 +57,40 @@ public class FuseAppLoop {
|
||||
@GuardedBy("mLock")
|
||||
private int mNextInode = MIN_INODE;
|
||||
|
||||
private FuseAppLoop(@NonNull File parent) {
|
||||
mParent = parent;
|
||||
}
|
||||
|
||||
public static @NonNull FuseAppLoop open(
|
||||
@NonNull File parent, @NonNull ParcelFileDescriptor fd) {
|
||||
Preconditions.checkNotNull(parent);
|
||||
Preconditions.checkNotNull(fd);
|
||||
final FuseAppLoop bridge = new FuseAppLoop(parent);
|
||||
private FuseAppLoop(
|
||||
int mountPointId, @NonNull ParcelFileDescriptor fd, @Nullable ThreadFactory factory) {
|
||||
mMountPointId = mountPointId;
|
||||
final int rawFd = fd.detachFd();
|
||||
new Thread(new Runnable() {
|
||||
if (factory == null) {
|
||||
factory = sDefaultThreadFactory;
|
||||
}
|
||||
mThread = factory.newThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
bridge.native_start_loop(rawFd);
|
||||
// rawFd is closed by native_start_loop. Java code does not need to close it.
|
||||
native_start_loop(rawFd);
|
||||
}
|
||||
}, TAG).start();
|
||||
return bridge;
|
||||
});
|
||||
}
|
||||
|
||||
public @NonNull ParcelFileDescriptor openFile(int mode, IProxyFileDescriptorCallback callback)
|
||||
public static @NonNull FuseAppLoop open(int mountPointId, @NonNull ParcelFileDescriptor fd,
|
||||
@Nullable ThreadFactory factory) {
|
||||
Preconditions.checkNotNull(fd);
|
||||
final FuseAppLoop loop = new FuseAppLoop(mountPointId, fd, factory);
|
||||
loop.mThread.start();
|
||||
return loop;
|
||||
}
|
||||
|
||||
public int registerCallback(@NonNull ProxyFileDescriptorCallback callback)
|
||||
throws UnmountedException, IOException {
|
||||
int id;
|
||||
if (mThread.getState() == Thread.State.TERMINATED) {
|
||||
throw new UnmountedException();
|
||||
}
|
||||
synchronized (mLock) {
|
||||
if (!mActive) {
|
||||
throw new UnmountedException();
|
||||
}
|
||||
if (mCallbackMap.size() >= Integer.MAX_VALUE - MIN_INODE) {
|
||||
throw new IOException("Too many opened files.");
|
||||
}
|
||||
int id;
|
||||
while (true) {
|
||||
id = mNextInode;
|
||||
mNextInode++;
|
||||
@@ -92,24 +101,17 @@ public class FuseAppLoop {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Register callback after we succeed to create pfd.
|
||||
mCallbackMap.put(id, new CallbackEntry(callback));
|
||||
}
|
||||
try {
|
||||
return ParcelFileDescriptor.open(new File(mParent, String.valueOf(id)), mode);
|
||||
} catch (FileNotFoundException error) {
|
||||
synchronized (mLock) {
|
||||
mCallbackMap.remove(id);
|
||||
}
|
||||
throw error;
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable File getMountPoint() {
|
||||
synchronized (mLock) {
|
||||
return mActive ? mParent : null;
|
||||
}
|
||||
public void unregisterCallback(int id) {
|
||||
mCallbackMap.remove(id);
|
||||
}
|
||||
|
||||
public int getMountPointId() {
|
||||
return mMountPointId;
|
||||
}
|
||||
|
||||
private CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException {
|
||||
@@ -128,7 +130,7 @@ public class FuseAppLoop {
|
||||
try {
|
||||
return getCallbackEntryOrThrowLocked(inode).callback.onGetSize();
|
||||
} catch (ErrnoException exp) {
|
||||
return -exp.errno;
|
||||
return getError(exp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,7 +149,7 @@ public class FuseAppLoop {
|
||||
// file twice.
|
||||
return (int) inode;
|
||||
} catch (ErrnoException exp) {
|
||||
return -exp.errno;
|
||||
return getError(exp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,7 +162,7 @@ public class FuseAppLoop {
|
||||
getCallbackEntryOrThrowLocked(inode).callback.onFsync();
|
||||
return 0;
|
||||
} catch (ErrnoException exp) {
|
||||
return -exp.errno;
|
||||
return getError(exp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,12 +171,14 @@ public class FuseAppLoop {
|
||||
@SuppressWarnings("unused")
|
||||
private int onRelease(long inode) {
|
||||
synchronized(mLock) {
|
||||
mCallbackMap.remove(checkInode(inode));
|
||||
if (mCallbackMap.size() == 0) {
|
||||
mActive = false;
|
||||
return -1;
|
||||
try {
|
||||
getCallbackEntryOrThrowLocked(inode).callback.onRelease();
|
||||
return 0;
|
||||
} catch (ErrnoException exp) {
|
||||
return getError(exp);
|
||||
} finally {
|
||||
mCallbackMap.remove(checkInode(inode));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +189,7 @@ public class FuseAppLoop {
|
||||
try {
|
||||
return getCallbackEntryOrThrowLocked(inode).callback.onRead(offset, size, bytes);
|
||||
} catch (ErrnoException exp) {
|
||||
return -exp.errno;
|
||||
return getError(exp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,11 +201,17 @@ public class FuseAppLoop {
|
||||
try {
|
||||
return getCallbackEntryOrThrowLocked(inode).callback.onWrite(offset, size, bytes);
|
||||
} catch (ErrnoException exp) {
|
||||
return -exp.errno;
|
||||
return getError(exp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int getError(@NonNull ErrnoException exp) {
|
||||
// Should not return ENOSYS because the kernel stops
|
||||
// dispatching the FUSE action once FUSE implementation returns ENOSYS for the action.
|
||||
return exp.errno != OsConstants.ENOSYS ? -exp.errno : -OsConstants.EIO;
|
||||
}
|
||||
|
||||
native boolean native_start_loop(int fd);
|
||||
|
||||
private static int checkInode(long inode) {
|
||||
@@ -212,9 +222,9 @@ public class FuseAppLoop {
|
||||
public static class UnmountedException extends Exception {}
|
||||
|
||||
private static class CallbackEntry {
|
||||
final IProxyFileDescriptorCallback callback;
|
||||
final ProxyFileDescriptorCallback callback;
|
||||
boolean opened;
|
||||
CallbackEntry(IProxyFileDescriptorCallback callback) {
|
||||
CallbackEntry(ProxyFileDescriptorCallback callback) {
|
||||
Preconditions.checkNotNull(callback);
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,6 @@ private:
|
||||
JNIEnv* const mEnv;
|
||||
jobject const mSelf;
|
||||
ScopedLocalRef<jbyteArray> mJniBuffer;
|
||||
bool mActive;
|
||||
|
||||
template <typename T>
|
||||
T checkException(T result) const {
|
||||
@@ -67,8 +66,7 @@ public:
|
||||
Callback(JNIEnv* env, jobject self) :
|
||||
mEnv(env),
|
||||
mSelf(self),
|
||||
mJniBuffer(env, nullptr),
|
||||
mActive(true) {}
|
||||
mJniBuffer(env, nullptr) {}
|
||||
|
||||
bool Init() {
|
||||
mJniBuffer.reset(mEnv->NewByteArray(kBufferSize));
|
||||
@@ -76,7 +74,7 @@ public:
|
||||
}
|
||||
|
||||
bool IsActive() override {
|
||||
return mActive;
|
||||
return true;
|
||||
}
|
||||
|
||||
int64_t OnGetSize(uint64_t inode) override {
|
||||
@@ -92,10 +90,7 @@ public:
|
||||
}
|
||||
|
||||
int32_t OnRelease(uint64_t inode) override {
|
||||
if (checkException(mEnv->CallIntMethod(mSelf, gOnReleaseMethod, inode)) == -1) {
|
||||
mActive = false;
|
||||
}
|
||||
return fuse::kFuseSuccess;
|
||||
return checkException(mEnv->CallIntMethod(mSelf, gOnReleaseMethod, inode));
|
||||
}
|
||||
|
||||
int32_t OnRead(uint64_t inode, uint64_t offset, uint32_t size, void* buffer) override {
|
||||
|
||||
@@ -18,15 +18,20 @@ package android.os.storage;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
import android.os.ProxyFileDescriptorCallback;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.test.InstrumentationTestCase;
|
||||
import android.test.suitebuilder.annotation.LargeTest;
|
||||
import android.test.suitebuilder.annotation.Suppress;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.frameworks.coretests.R;
|
||||
|
||||
import com.android.internal.os.FuseAppLoop;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
||||
@@ -243,4 +248,47 @@ public class StorageManagerIntegrationTest extends StorageManagerBaseTest {
|
||||
verifyObb1Contents(filePath);
|
||||
unmountObb(filePath, DONT_FORCE);
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
public void testOpenProxyFileDescriptor() throws Exception {
|
||||
final ProxyFileDescriptorCallback callback = new ProxyFileDescriptorCallback() {
|
||||
@Override
|
||||
public long onGetSize() throws ErrnoException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRelease() {}
|
||||
};
|
||||
|
||||
final MyThreadFactory factory = new MyThreadFactory();
|
||||
int firstMountId;
|
||||
try (final ParcelFileDescriptor fd = mSm.openProxyFileDescriptor(
|
||||
ParcelFileDescriptor.MODE_READ_ONLY, callback, factory)) {
|
||||
assertNotSame(Thread.State.TERMINATED, factory.thread.getState());
|
||||
firstMountId = mSm.getProxyFileDescriptorMountPointId();
|
||||
assertNotSame(-1, firstMountId);
|
||||
}
|
||||
|
||||
// After closing descriptor, the loop should terminate.
|
||||
factory.thread.join(3000);
|
||||
assertEquals(Thread.State.TERMINATED, factory.thread.getState());
|
||||
|
||||
// StorageManager should mount another bridge on the next open request.
|
||||
try (final ParcelFileDescriptor fd = mSm.openProxyFileDescriptor(
|
||||
ParcelFileDescriptor.MODE_WRITE_ONLY, callback, factory)) {
|
||||
assertNotSame(Thread.State.TERMINATED, factory.thread.getState());
|
||||
assertNotSame(firstMountId, mSm.getProxyFileDescriptorMountPointId());
|
||||
}
|
||||
}
|
||||
|
||||
private static class MyThreadFactory implements ThreadFactory {
|
||||
Thread thread = null;
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
thread = new Thread(r);
|
||||
return thread;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,11 +88,13 @@ import android.util.AtomicFile;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.TimeUtils;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.app.IMediaContainerService;
|
||||
import com.android.internal.os.AppFuseMount;
|
||||
import com.android.internal.os.SomeArgs;
|
||||
import com.android.internal.os.Zygote;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
@@ -104,7 +106,7 @@ import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.server.NativeDaemonConnector.Command;
|
||||
import com.android.server.NativeDaemonConnector.SensitiveArg;
|
||||
import com.android.server.pm.PackageManagerService;
|
||||
|
||||
import com.android.server.storage.AppFuseBridge;
|
||||
import libcore.io.IoUtils;
|
||||
import libcore.util.EmptyArray;
|
||||
|
||||
@@ -135,6 +137,7 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -337,6 +340,15 @@ class StorageManagerService extends IStorageManager.Stub
|
||||
|
||||
private volatile int mCurrentUserId = UserHandle.USER_SYSTEM;
|
||||
|
||||
/** Holding lock for AppFuse business */
|
||||
private final Object mAppFuseLock = new Object();
|
||||
|
||||
@GuardedBy("mAppFuseLock")
|
||||
private int mNextAppFuseName = 0;
|
||||
|
||||
@GuardedBy("mAppFuseLock")
|
||||
private final SparseArray<Integer> mAppFusePids = new SparseArray<>();
|
||||
|
||||
private VolumeInfo findVolumeByIdOrThrow(String id) {
|
||||
synchronized (mLock) {
|
||||
final VolumeInfo vol = mVolumes.get(id);
|
||||
@@ -3010,6 +3022,128 @@ class StorageManagerService extends IStorageManager.Stub
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CloseableHolder<T extends AutoCloseable> implements AutoCloseable {
|
||||
@Nullable T mCloseable;
|
||||
|
||||
CloseableHolder(T closeable) {
|
||||
mCloseable = closeable;
|
||||
}
|
||||
|
||||
@Nullable T get() {
|
||||
return mCloseable;
|
||||
}
|
||||
|
||||
@Nullable T release() {
|
||||
final T result = mCloseable;
|
||||
mCloseable = null;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (mCloseable != null) {
|
||||
IoUtils.closeQuietly(mCloseable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AppFuseMountScope implements AppFuseBridge.IMountScope {
|
||||
final int mUid;
|
||||
final int mName;
|
||||
final ParcelFileDescriptor mDeviceFd;
|
||||
|
||||
AppFuseMountScope(int uid, int pid, int name) throws NativeDaemonConnectorException {
|
||||
final NativeDaemonEvent event = mConnector.execute(
|
||||
"appfuse", "mount", uid, Process.myPid(), name);
|
||||
mUid = uid;
|
||||
mName = name;
|
||||
synchronized (mLock) {
|
||||
mAppFusePids.put(name, pid);
|
||||
}
|
||||
if (event.getFileDescriptors() != null &&
|
||||
event.getFileDescriptors().length > 0) {
|
||||
mDeviceFd = new ParcelFileDescriptor(event.getFileDescriptors()[0]);
|
||||
} else {
|
||||
mDeviceFd = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws NativeDaemonConnectorException {
|
||||
try {
|
||||
IoUtils.closeQuietly(mDeviceFd);
|
||||
mConnector.execute(
|
||||
"appfuse", "unmount", mUid, Process.myPid(), mName);
|
||||
} finally {
|
||||
synchronized (mLock) {
|
||||
mAppFusePids.delete(mName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor getDeviceFileDescriptor() {
|
||||
return mDeviceFd;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AppFuseMount mountProxyFileDescriptorBridge() throws RemoteException {
|
||||
final int uid = Binder.getCallingUid();
|
||||
final int pid = Binder.getCallingPid();
|
||||
final int name;
|
||||
synchronized (mAppFuseLock) {
|
||||
name = mNextAppFuseName++;
|
||||
}
|
||||
try (CloseableHolder<AppFuseMountScope> mountScope =
|
||||
new CloseableHolder<>(new AppFuseMountScope(uid, pid, name))) {
|
||||
if (mountScope.get().getDeviceFileDescriptor() == null) {
|
||||
throw new RemoteException("Failed to obtain device FD");
|
||||
}
|
||||
|
||||
// Create communication channel.
|
||||
final ArrayBlockingQueue<Boolean> channel = new ArrayBlockingQueue<>(1);
|
||||
final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createSocketPair();
|
||||
try (CloseableHolder<ParcelFileDescriptor> remote = new CloseableHolder<>(fds[0])) {
|
||||
new Thread(
|
||||
new AppFuseBridge(mountScope.release(), fds[1], channel),
|
||||
AppFuseBridge.TAG).start();
|
||||
if (!channel.take()) {
|
||||
throw new RemoteException("Failed to init AppFuse mount point");
|
||||
}
|
||||
|
||||
return new AppFuseMount(name, remote.release());
|
||||
}
|
||||
} catch (NativeDaemonConnectorException e){
|
||||
throw e.rethrowAsParcelableException();
|
||||
} catch (IOException | InterruptedException error) {
|
||||
throw new RemoteException(error.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openProxyFileDescriptor(int mountId, int fileId, int mode) {
|
||||
final int uid = Binder.getCallingUid();
|
||||
final int pid = Binder.getCallingPid();
|
||||
try {
|
||||
synchronized (mAppFuseLock) {
|
||||
final int expectedPid = mAppFusePids.get(mountId, -1);
|
||||
if (expectedPid == -1) {
|
||||
Slog.i(TAG, "The mount point has already been unmounted");
|
||||
return null;
|
||||
}
|
||||
if (expectedPid != pid) {
|
||||
throw new SecurityException("Mount point was not created by this process.");
|
||||
}
|
||||
}
|
||||
return AppFuseBridge.openFile(uid, mountId, fileId, mode);
|
||||
} catch (FileNotFoundException error) {
|
||||
Slog.e(TAG, "Failed to openProxyFileDescriptor", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int mkdirs(String callingPkg, String appPath) {
|
||||
final int userId = UserHandle.getUserId(Binder.getCallingUid());
|
||||
|
||||
@@ -16,79 +16,95 @@
|
||||
|
||||
package com.android.server.storage;
|
||||
|
||||
import android.annotation.CallSuper;
|
||||
import android.annotation.WorkerThread;
|
||||
import android.os.Handler;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.system.OsConstants;
|
||||
import android.util.Log;
|
||||
import com.android.internal.os.AppFuseMount;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
public class AppFuseBridge implements Runnable {
|
||||
private static final String TAG = AppFuseBridge.class.getSimpleName();
|
||||
|
||||
private final FileDescriptor mDeviceFd;
|
||||
private final FileDescriptor mProxyFd;
|
||||
private final CountDownLatch mMountLatch = new CountDownLatch(1);
|
||||
/**
|
||||
* 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";
|
||||
|
||||
/**
|
||||
* @param deviceFd FD of /dev/fuse. Ownership of fd is taken by AppFuseBridge.
|
||||
* @param proxyFd FD of socket pair. Ownership of fd is taken by 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 AppFuseBridge(FileDescriptor deviceFd, FileDescriptor proxyFd) {
|
||||
mDeviceFd = deviceFd;
|
||||
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;
|
||||
}
|
||||
|
||||
public static AppFuseMount startMessageLoop(
|
||||
int uid,
|
||||
String name,
|
||||
FileDescriptor deviceFd,
|
||||
Handler handler,
|
||||
ParcelFileDescriptor.OnCloseListener listener)
|
||||
throws IOException, ErrnoException, InterruptedException {
|
||||
final FileDescriptor localFd = new FileDescriptor();
|
||||
final FileDescriptor remoteFd = new FileDescriptor();
|
||||
// Needs to specify OsConstants.SOCK_SEQPACKET to keep message boundaries.
|
||||
Os.socketpair(OsConstants.AF_UNIX, OsConstants.SOCK_SEQPACKET, 0, remoteFd, localFd);
|
||||
|
||||
// Caller must invoke #start() after instantiate AppFuseBridge.
|
||||
// Otherwise FDs will be leaked.
|
||||
final AppFuseBridge bridge = new AppFuseBridge(deviceFd, localFd);
|
||||
final Thread thread = new Thread(bridge, TAG);
|
||||
thread.start();
|
||||
try {
|
||||
bridge.mMountLatch.await();
|
||||
} catch (InterruptedException error) {
|
||||
throw error;
|
||||
}
|
||||
return new AppFuseMount(
|
||||
new File("/mnt/appfuse/" + uid + "_" + name),
|
||||
ParcelFileDescriptor.fromFd(remoteFd, handler, listener));
|
||||
mChannel = channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// deviceFd and proxyFd must be closed in native_start_loop.
|
||||
final int deviceFd = mDeviceFd.getInt$();
|
||||
final int proxyFd = mProxyFd.getInt$();
|
||||
mDeviceFd.setInt$(-1);
|
||||
mProxyFd.setInt$(-1);
|
||||
native_start_loop(deviceFd, proxyFd);
|
||||
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() {
|
||||
mMountLatch.countDown();
|
||||
mChannel.offer(true);
|
||||
}
|
||||
|
||||
public static interface IMountScope extends AutoCloseable {
|
||||
ParcelFileDescriptor getDeviceFileDescriptor();
|
||||
}
|
||||
|
||||
private native boolean native_start_loop(int deviceFd, int proxyFd);
|
||||
|
||||
Reference in New Issue
Block a user