Merge "Start FUSE sessions before Vold mount returns"
This commit is contained in:
committed by
Android (Google) Code Review
commit
e0ad70ff43
@@ -80,6 +80,7 @@ import android.os.IBinder;
|
||||
import android.os.IStoraged;
|
||||
import android.os.IVold;
|
||||
import android.os.IVoldListener;
|
||||
import android.os.IVoldMountCallback;
|
||||
import android.os.IVoldTaskListener;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
@@ -348,10 +349,6 @@ class StorageManagerService extends IStorageManager.Stub
|
||||
@GuardedBy("mLock")
|
||||
private ArrayMap<String, CountDownLatch> mDiskScanLatches = new ArrayMap<>();
|
||||
|
||||
/** Map from volume ID to latches */
|
||||
@GuardedBy("mLock")
|
||||
private ArrayMap<String, CountDownLatch> mFuseVolumeReadyLatches = new ArrayMap<>();
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private IPackageMoveObserver mMoveCallback;
|
||||
@GuardedBy("mLock")
|
||||
@@ -464,17 +461,6 @@ class StorageManagerService extends IStorageManager.Stub
|
||||
}
|
||||
}
|
||||
|
||||
private CountDownLatch findOrCreateFuseVolumeReadyLatch(String volId) {
|
||||
synchronized (mLock) {
|
||||
CountDownLatch latch = mFuseVolumeReadyLatches.get(volId);
|
||||
if (latch == null) {
|
||||
latch = new CountDownLatch(1);
|
||||
mFuseVolumeReadyLatches.put(volId, latch);
|
||||
}
|
||||
return latch;
|
||||
}
|
||||
}
|
||||
|
||||
/** List of crypto types.
|
||||
* These must match CRYPT_TYPE_XXX in cryptfs.h AND their
|
||||
* corresponding commands in CommandListener.cpp */
|
||||
@@ -612,7 +598,6 @@ class StorageManagerService extends IStorageManager.Stub
|
||||
private static final int H_ABORT_IDLE_MAINT = 12;
|
||||
private static final int H_BOOT_COMPLETED = 13;
|
||||
private static final int H_COMPLETE_UNLOCK_USER = 14;
|
||||
private static final int H_VOLUME_READY = 15;
|
||||
|
||||
class StorageManagerServiceHandler extends Handler {
|
||||
public StorageManagerServiceHandler(Looper looper) {
|
||||
@@ -673,22 +658,6 @@ class StorageManagerService extends IStorageManager.Stub
|
||||
}
|
||||
break;
|
||||
}
|
||||
case H_VOLUME_READY: {
|
||||
final VolumeInfo vol = (VolumeInfo) msg.obj;
|
||||
try {
|
||||
mStorageSessionController.onVolumeReady(vol);
|
||||
|
||||
synchronized (mLock) {
|
||||
CountDownLatch latch = mFuseVolumeReadyLatches.remove(vol.id);
|
||||
if (latch != null) {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
} catch (IllegalStateException | ExternalStorageServiceException e) {
|
||||
Slog.i(TAG, "Failed to initialise volume " + vol, e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case H_VOLUME_MOUNT: {
|
||||
final VolumeInfo vol = (VolumeInfo) msg.obj;
|
||||
if (isMountDisallowed(vol)) {
|
||||
@@ -1431,13 +1400,6 @@ class StorageManagerService extends IStorageManager.Stub
|
||||
writeSettingsLocked();
|
||||
}
|
||||
|
||||
if (mIsFuseEnabled && newState == VolumeInfo.STATE_MOUNTED
|
||||
&& (vol.type == VolumeInfo.TYPE_PUBLIC || vol.type == VolumeInfo.TYPE_EMULATED)) {
|
||||
Slog.i(TAG, "Initialising volume " + vol + " ...");
|
||||
// TODO(b/144275217): Delay broadcasts till mount is really ready
|
||||
mHandler.obtainMessage(H_VOLUME_READY, vol).sendToTarget();
|
||||
}
|
||||
|
||||
mCallbacks.notifyVolumeStateChanged(vol, oldState, newState);
|
||||
|
||||
// Do not broadcast before boot has completed to avoid launching the
|
||||
@@ -1912,33 +1874,29 @@ class StorageManagerService extends IStorageManager.Stub
|
||||
throw new SecurityException("Mounting " + volId + " restricted by policy");
|
||||
}
|
||||
|
||||
CountDownLatch latch = null;
|
||||
if (mIsFuseEnabled && StorageSessionController.isEmulatedOrPublic(vol)) {
|
||||
latch = findOrCreateFuseVolumeReadyLatch(volId);
|
||||
}
|
||||
|
||||
mount(vol);
|
||||
|
||||
if (latch != null) {
|
||||
try {
|
||||
waitForLatch(latch, "mount " + volId, 3 * DateUtils.MINUTE_IN_MILLIS);
|
||||
} catch (TimeoutException e) {
|
||||
Slog.wtf(TAG, e);
|
||||
} finally {
|
||||
synchronized (mLock) {
|
||||
mFuseVolumeReadyLatches.remove(volId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void mount(VolumeInfo vol) {
|
||||
try {
|
||||
// TODO(b/135341433): Remove paranoid logging when FUSE is stable
|
||||
Slog.i(TAG, "Mounting volume " + vol);
|
||||
FileDescriptor fd = mVold.mount(vol.id, vol.mountFlags, vol.mountUserId);
|
||||
mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() {
|
||||
@Override
|
||||
public boolean onVolumeChecking(FileDescriptor deviceFd, String path,
|
||||
String internalPath) {
|
||||
vol.path = path;
|
||||
vol.internalPath = internalPath;
|
||||
try {
|
||||
mStorageSessionController.onVolumeMount(deviceFd, vol);
|
||||
return true;
|
||||
} catch (ExternalStorageServiceException e) {
|
||||
Slog.i(TAG, "Failed to mount volume " + vol, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
Slog.i(TAG, "Mounted volume " + vol);
|
||||
mStorageSessionController.onVolumeMount(fd, vol);
|
||||
} catch (Exception e) {
|
||||
Slog.wtf(TAG, e);
|
||||
}
|
||||
@@ -3643,7 +3601,7 @@ class StorageManagerService extends IStorageManager.Stub
|
||||
try {
|
||||
mObbState.volId = mVold.createObb(mObbState.canonicalPath, binderKey,
|
||||
mObbState.ownerGid);
|
||||
mVold.mount(mObbState.volId, 0, -1);
|
||||
mVold.mount(mObbState.volId, 0, -1, null);
|
||||
|
||||
if (DEBUG_OBB)
|
||||
Slog.d(TAG, "Successfully mounted OBB " + mObbState.canonicalPath);
|
||||
|
||||
@@ -69,15 +69,21 @@ public final class StorageSessionController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a storage session associated with {@code deviceFd} for {@code vol}. Sessions can be
|
||||
* started with {@link #onVolumeReady} and removed with {@link #onVolumeUnmount} or
|
||||
* {@link #onVolumeRemove}.
|
||||
* Creates and starts a storage session associated with {@code deviceFd} for {@code vol}.
|
||||
* Sessions can be started with {@link #onVolumeReady} and removed with {@link #onVolumeUnmount}
|
||||
* or {@link #onVolumeRemove}.
|
||||
*
|
||||
* Throws an {@link IllegalStateException} if a session for {@code vol} has already been created
|
||||
*
|
||||
* Does nothing if {@link #shouldHandle} is {@code false}
|
||||
*
|
||||
* Blocks until the session is started or fails
|
||||
*
|
||||
* @throws ExternalStorageServiceException if the session fails to start
|
||||
* @throws IllegalStateException if a session has already been created for {@code vol}
|
||||
*/
|
||||
public void onVolumeMount(FileDescriptor deviceFd, VolumeInfo vol) {
|
||||
public void onVolumeMount(FileDescriptor deviceFd, VolumeInfo vol)
|
||||
throws ExternalStorageServiceException {
|
||||
if (!shouldHandle(vol)) {
|
||||
return;
|
||||
}
|
||||
@@ -87,71 +93,22 @@ public final class StorageSessionController {
|
||||
String sessionId = vol.getId();
|
||||
int userId = vol.getMountUserId();
|
||||
|
||||
if (deviceFd == null) {
|
||||
Slog.w(TAG, "Null fd. Session not started for vol: " + vol);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get realpath for the fd, paths that are not /dev/null need additional
|
||||
// setup by the ExternalStorageService before they can be ready
|
||||
String realPath;
|
||||
try {
|
||||
realPath = ParcelFileDescriptor.getFile(deviceFd).getPath();
|
||||
} catch (IOException e) {
|
||||
Slog.wtf(TAG, "Could not get real path from fd: " + deviceFd, e);
|
||||
return;
|
||||
}
|
||||
|
||||
if ("/dev/null".equals(realPath)) {
|
||||
Slog.i(TAG, "Volume ready for use with id: " + sessionId);
|
||||
return;
|
||||
}
|
||||
|
||||
StorageUserConnection connection = null;
|
||||
synchronized (mLock) {
|
||||
StorageUserConnection connection = mConnections.get(userId);
|
||||
connection = mConnections.get(userId);
|
||||
if (connection == null) {
|
||||
Slog.i(TAG, "Creating connection for user: " + userId);
|
||||
connection = new StorageUserConnection(mContext, userId, this);
|
||||
mConnections.put(userId, connection);
|
||||
}
|
||||
Slog.i(TAG, "Creating session with id: " + sessionId);
|
||||
connection.createSession(sessionId, new ParcelFileDescriptor(deviceFd));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a storage session associated with {@code vol} after {@link #onVolumeMount}.
|
||||
*
|
||||
* Subsequent calls will attempt to start the storage session, but does nothing if already
|
||||
* started. If the user associated with {@code vol} is not yet ready, all pending sesssions
|
||||
* can be restarted with {@link onUnlockUser}.
|
||||
*
|
||||
* Does nothing if {@link #shouldHandle} is {@code false}
|
||||
*
|
||||
* Blocks until the session is started or fails
|
||||
*
|
||||
* @throws ExternalStorageServiceException if the session fails to start
|
||||
*/
|
||||
public void onVolumeReady(VolumeInfo vol) throws ExternalStorageServiceException {
|
||||
if (!shouldHandle(vol)) {
|
||||
return;
|
||||
connection.createSession(sessionId, new ParcelFileDescriptor(deviceFd),
|
||||
vol.getPath().getPath(), vol.getInternalPath().getPath());
|
||||
}
|
||||
|
||||
Slog.i(TAG, "On volume ready " + vol);
|
||||
String sessionId = vol.getId();
|
||||
|
||||
StorageUserConnection connection = null;
|
||||
synchronized (mLock) {
|
||||
connection = mConnections.get(vol.getMountUserId());
|
||||
if (connection == null) {
|
||||
Slog.i(TAG, "Volume ready but no associated connection");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
connection.initSession(sessionId, vol.getPath().getPath(),
|
||||
vol.getInternalPath().getPath());
|
||||
|
||||
if (isReady()) {
|
||||
// At boot, a volume can be mounted before user is unlocked, in that case, we create it
|
||||
// above and save it so that we can restart all sessions when the user is unlocked
|
||||
if (mExternalStorageServiceComponent != null) {
|
||||
connection.startSession(sessionId);
|
||||
} else {
|
||||
Slog.i(TAG, "Controller not initialised, session not started " + sessionId);
|
||||
@@ -205,14 +162,10 @@ public final class StorageSessionController {
|
||||
if (connection != null) {
|
||||
String sessionId = vol.getId();
|
||||
|
||||
if (isReady()) {
|
||||
try {
|
||||
connection.removeSessionAndWait(sessionId);
|
||||
} catch (ExternalStorageServiceException e) {
|
||||
Slog.e(TAG, "Failed to end session for vol with id: " + sessionId, e);
|
||||
}
|
||||
} else {
|
||||
Slog.i(TAG, "Controller not initialised, session not ended " + sessionId);
|
||||
try {
|
||||
connection.removeSessionAndWait(sessionId);
|
||||
} catch (ExternalStorageServiceException e) {
|
||||
Slog.e(TAG, "Failed to end session for vol with id: " + sessionId, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,7 +185,7 @@ public final class StorageSessionController {
|
||||
|
||||
Slog.i(TAG, "On user unlock " + userId);
|
||||
if (userId == 0) {
|
||||
init();
|
||||
initExternalStorageServiceComponent();
|
||||
}
|
||||
|
||||
StorageUserConnection connection = null;
|
||||
@@ -259,13 +212,6 @@ public final class StorageSessionController {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isReady()) {
|
||||
synchronized (mLock) {
|
||||
mConnections.clear();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
SparseArray<StorageUserConnection> connections = new SparseArray();
|
||||
synchronized (mLock) {
|
||||
mIsResetting = true;
|
||||
@@ -311,7 +257,7 @@ public final class StorageSessionController {
|
||||
}
|
||||
}
|
||||
|
||||
private void init() throws ExternalStorageServiceException {
|
||||
private void initExternalStorageServiceComponent() throws ExternalStorageServiceException {
|
||||
Slog.i(TAG, "Initialialising...");
|
||||
ProviderInfo provider = mContext.getPackageManager().resolveContentProvider(
|
||||
MediaStore.AUTHORITY, PackageManager.MATCH_DIRECT_BOOT_AWARE
|
||||
@@ -415,8 +361,4 @@ public final class StorageSessionController {
|
||||
private boolean shouldHandle(@Nullable VolumeInfo vol) {
|
||||
return mIsFuseEnabled && !mIsResetting && (vol == null || isEmulatedOrPublic(vol));
|
||||
}
|
||||
|
||||
private boolean isReady() {
|
||||
return mExternalStorageServiceComponent != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,46 +75,20 @@ public final class StorageUserConnection {
|
||||
/**
|
||||
* Creates and stores a storage {@link Session}.
|
||||
*
|
||||
* Created sessions must be initialised with {@link #initSession} before starting with
|
||||
* {@link #startSession}.
|
||||
*
|
||||
* They must also be cleaned up with {@link #removeSession}.
|
||||
*
|
||||
* @throws IllegalArgumentException if a {@code Session} with {@code sessionId} already exists
|
||||
*/
|
||||
public void createSession(String sessionId, ParcelFileDescriptor pfd) {
|
||||
public void createSession(String sessionId, ParcelFileDescriptor pfd, String upperPath,
|
||||
String lowerPath) {
|
||||
Preconditions.checkNotNull(sessionId);
|
||||
Preconditions.checkNotNull(pfd);
|
||||
Preconditions.checkNotNull(upperPath);
|
||||
Preconditions.checkNotNull(lowerPath);
|
||||
|
||||
synchronized (mLock) {
|
||||
Preconditions.checkArgument(!mSessions.containsKey(sessionId));
|
||||
mSessions.put(sessionId, new Session(sessionId, pfd));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise a storage {@link Session}.
|
||||
*
|
||||
* Initialised sessions can be started with {@link #startSession}.
|
||||
*
|
||||
* They must also be cleaned up with {@link #removeSession}.
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code sessionId} does not exist or is initialised
|
||||
*/
|
||||
public void initSession(String sessionId, String upperPath, String lowerPath) {
|
||||
synchronized (mLock) {
|
||||
Session session = mSessions.get(sessionId);
|
||||
if (session == null) {
|
||||
throw new IllegalStateException("Failed to initialise non existent session. Id: "
|
||||
+ sessionId + ". Upper path: " + upperPath + ". Lower path: " + lowerPath);
|
||||
} else if (session.isInitialisedLocked()) {
|
||||
throw new IllegalStateException("Already initialised session. Id: "
|
||||
+ sessionId + ". Upper path: " + upperPath + ". Lower path: " + lowerPath);
|
||||
} else {
|
||||
session.upperPath = upperPath;
|
||||
session.lowerPath = lowerPath;
|
||||
Slog.i(TAG, "Initialised session: " + session);
|
||||
}
|
||||
mSessions.put(sessionId, new Session(sessionId, pfd, upperPath, lowerPath));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,14 +414,14 @@ public final class StorageUserConnection {
|
||||
private static final class Session implements AutoCloseable {
|
||||
public final String sessionId;
|
||||
public final ParcelFileDescriptor pfd;
|
||||
@GuardedBy("mLock")
|
||||
public String lowerPath;
|
||||
@GuardedBy("mLock")
|
||||
public String upperPath;
|
||||
public final String lowerPath;
|
||||
public final String upperPath;
|
||||
|
||||
Session(String sessionId, ParcelFileDescriptor pfd) {
|
||||
Session(String sessionId, ParcelFileDescriptor pfd, String upperPath, String lowerPath) {
|
||||
this.sessionId = sessionId;
|
||||
this.pfd = pfd;
|
||||
this.upperPath = upperPath;
|
||||
this.lowerPath = lowerPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user