/* * Copyright (C) 2007 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; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.Manifest; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.content.res.ObbInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.hardware.usb.UsbManager; import android.net.Uri; import android.os.Binder; import android.os.Environment; import android.os.Environment.UserEnvironment; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.os.UserHandle; import android.os.storage.IMountService; import android.os.storage.IMountServiceListener; import android.os.storage.IMountShutdownObserver; import android.os.storage.IObbActionListener; import android.os.storage.OnObbStateChangeListener; import android.os.storage.StorageResultCode; import android.os.storage.StorageVolume; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Slog; import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IMediaContainerService; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.server.NativeDaemonConnector.Command; import com.android.server.am.ActivityManagerService; import com.android.server.pm.PackageManagerService; import com.android.server.pm.UserManagerService; import com.google.android.collect.Lists; import com.google.android.collect.Maps; import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.math.BigInteger; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; /** * MountService implements back-end services for platform storage * management. * @hide - Applications should use android.os.storage.StorageManager * to access the MountService. */ class MountService extends IMountService.Stub implements INativeDaemonConnectorCallbacks, Watchdog.Monitor { // TODO: listen for user creation/deletion private static final boolean LOCAL_LOGD = false; private static final boolean DEBUG_UNMOUNT = false; private static final boolean DEBUG_EVENTS = false; private static final boolean DEBUG_OBB = false; // Disable this since it messes up long-running cryptfs operations. private static final boolean WATCHDOG_ENABLE = false; private static final String TAG = "MountService"; private static final String VOLD_TAG = "VoldConnector"; /** Maximum number of ASEC containers allowed to be mounted. */ private static final int MAX_CONTAINERS = 250; /* * Internal vold volume state constants */ class VolumeState { public static final int Init = -1; public static final int NoMedia = 0; public static final int Idle = 1; public static final int Pending = 2; public static final int Checking = 3; public static final int Mounted = 4; public static final int Unmounting = 5; public static final int Formatting = 6; public static final int Shared = 7; public static final int SharedMnt = 8; } /* * Internal vold response code constants */ class VoldResponseCode { /* * 100 series - Requestion action was initiated; expect another reply * before proceeding with a new command. */ public static final int VolumeListResult = 110; public static final int AsecListResult = 111; public static final int StorageUsersListResult = 112; /* * 200 series - Requestion action has been successfully completed. */ public static final int ShareStatusResult = 210; public static final int AsecPathResult = 211; public static final int ShareEnabledResult = 212; /* * 400 series - Command was accepted, but the requested action * did not take place. */ public static final int OpFailedNoMedia = 401; public static final int OpFailedMediaBlank = 402; public static final int OpFailedMediaCorrupt = 403; public static final int OpFailedVolNotMounted = 404; public static final int OpFailedStorageBusy = 405; public static final int OpFailedStorageNotFound = 406; /* * 600 series - Unsolicited broadcasts. */ public static final int VolumeStateChange = 605; public static final int VolumeDiskInserted = 630; public static final int VolumeDiskRemoved = 631; public static final int VolumeBadRemoval = 632; } private Context mContext; private NativeDaemonConnector mConnector; private final Object mVolumesLock = new Object(); /** When defined, base template for user-specific {@link StorageVolume}. */ private StorageVolume mEmulatedTemplate; @GuardedBy("mVolumesLock") private final ArrayList mVolumes = Lists.newArrayList(); /** Map from path to {@link StorageVolume} */ @GuardedBy("mVolumesLock") private final HashMap mVolumesByPath = Maps.newHashMap(); /** Map from path to state */ @GuardedBy("mVolumesLock") private final HashMap mVolumeStates = Maps.newHashMap(); private volatile boolean mSystemReady = false; private PackageManagerService mPms; private boolean mUmsEnabling; private boolean mUmsAvailable = false; // Used as a lock for methods that register/unregister listeners. final private ArrayList mListeners = new ArrayList(); private final CountDownLatch mConnectedSignal = new CountDownLatch(1); private final CountDownLatch mAsecsScanned = new CountDownLatch(1); private boolean mSendUmsConnectedOnBoot = false; /** * Private hash of currently mounted secure containers. * Used as a lock in methods to manipulate secure containers. */ final private HashSet mAsecMountSet = new HashSet(); /** * The size of the crypto algorithm key in bits for OBB files. Currently * Twofish is used which takes 128-bit keys. */ private static final int CRYPTO_ALGORITHM_KEY_SIZE = 128; /** * The number of times to run SHA1 in the PBKDF2 function for OBB files. * 1024 is reasonably secure and not too slow. */ private static final int PBKDF2_HASH_ROUNDS = 1024; /** * Mounted OBB tracking information. Used to track the current state of all * OBBs. */ final private Map> mObbMounts = new HashMap>(); /** Map from raw paths to {@link ObbState}. */ final private Map mObbPathToStateMap = new HashMap(); class ObbState implements IBinder.DeathRecipient { public ObbState(String rawPath, String canonicalPath, int callingUid, IObbActionListener token, int nonce) { this.rawPath = rawPath; this.canonicalPath = canonicalPath.toString(); final int userId = UserHandle.getUserId(callingUid); this.ownerPath = buildObbPath(canonicalPath, userId, false); this.voldPath = buildObbPath(canonicalPath, userId, true); this.ownerGid = UserHandle.getSharedAppGid(callingUid); this.token = token; this.nonce = nonce; } final String rawPath; final String canonicalPath; final String ownerPath; final String voldPath; final int ownerGid; // Token of remote Binder caller final IObbActionListener token; // Identifier to pass back to the token final int nonce; public IBinder getBinder() { return token.asBinder(); } @Override public void binderDied() { ObbAction action = new UnmountObbAction(this, true); mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action)); } public void link() throws RemoteException { getBinder().linkToDeath(this, 0); } public void unlink() { getBinder().unlinkToDeath(this, 0); } @Override public String toString() { StringBuilder sb = new StringBuilder("ObbState{"); sb.append("rawPath=").append(rawPath); sb.append(",canonicalPath=").append(canonicalPath); sb.append(",ownerPath=").append(ownerPath); sb.append(",voldPath=").append(voldPath); sb.append(",ownerGid=").append(ownerGid); sb.append(",token=").append(token); sb.append(",binder=").append(getBinder()); sb.append('}'); return sb.toString(); } } // OBB Action Handler final private ObbActionHandler mObbActionHandler; // OBB action handler messages private static final int OBB_RUN_ACTION = 1; private static final int OBB_MCS_BOUND = 2; private static final int OBB_MCS_UNBIND = 3; private static final int OBB_MCS_RECONNECT = 4; private static final int OBB_FLUSH_MOUNT_STATE = 5; /* * Default Container Service information */ static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName( "com.android.defcontainer", "com.android.defcontainer.DefaultContainerService"); final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection(); class DefaultContainerConnection implements ServiceConnection { public void onServiceConnected(ComponentName name, IBinder service) { if (DEBUG_OBB) Slog.i(TAG, "onServiceConnected"); IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service); mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_MCS_BOUND, imcs)); } public void onServiceDisconnected(ComponentName name) { if (DEBUG_OBB) Slog.i(TAG, "onServiceDisconnected"); } }; // Used in the ObbActionHandler private IMediaContainerService mContainerService = null; // Handler messages private static final int H_UNMOUNT_PM_UPDATE = 1; private static final int H_UNMOUNT_PM_DONE = 2; private static final int H_UNMOUNT_MS = 3; private static final int H_SYSTEM_READY = 4; private static final int RETRY_UNMOUNT_DELAY = 30; // in ms private static final int MAX_UNMOUNT_RETRIES = 4; class UnmountCallBack { final String path; final boolean force; final boolean removeEncryption; int retries; UnmountCallBack(String path, boolean force, boolean removeEncryption) { retries = 0; this.path = path; this.force = force; this.removeEncryption = removeEncryption; } void handleFinished() { if (DEBUG_UNMOUNT) Slog.i(TAG, "Unmounting " + path); doUnmountVolume(path, true, removeEncryption); } } class UmsEnableCallBack extends UnmountCallBack { final String method; UmsEnableCallBack(String path, String method, boolean force) { super(path, force, false); this.method = method; } @Override void handleFinished() { super.handleFinished(); doShareUnshareVolume(path, method, true); } } class ShutdownCallBack extends UnmountCallBack { IMountShutdownObserver observer; ShutdownCallBack(String path, IMountShutdownObserver observer) { super(path, true, false); this.observer = observer; } @Override void handleFinished() { int ret = doUnmountVolume(path, true, removeEncryption); if (observer != null) { try { observer.onShutDownComplete(ret); } catch (RemoteException e) { Slog.w(TAG, "RemoteException when shutting down"); } } } } class MountServiceHandler extends Handler { ArrayList mForceUnmounts = new ArrayList(); boolean mUpdatingStatus = false; MountServiceHandler(Looper l) { super(l); } @Override public void handleMessage(Message msg) { switch (msg.what) { case H_UNMOUNT_PM_UPDATE: { if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_UPDATE"); UnmountCallBack ucb = (UnmountCallBack) msg.obj; mForceUnmounts.add(ucb); if (DEBUG_UNMOUNT) Slog.i(TAG, " registered = " + mUpdatingStatus); // Register only if needed. if (!mUpdatingStatus) { if (DEBUG_UNMOUNT) Slog.i(TAG, "Updating external media status on PackageManager"); mUpdatingStatus = true; mPms.updateExternalMediaStatus(false, true); } break; } case H_UNMOUNT_PM_DONE: { if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_DONE"); if (DEBUG_UNMOUNT) Slog.i(TAG, "Updated status. Processing requests"); mUpdatingStatus = false; int size = mForceUnmounts.size(); int sizeArr[] = new int[size]; int sizeArrN = 0; // Kill processes holding references first ActivityManagerService ams = (ActivityManagerService) ServiceManager.getService("activity"); for (int i = 0; i < size; i++) { UnmountCallBack ucb = mForceUnmounts.get(i); String path = ucb.path; boolean done = false; if (!ucb.force) { done = true; } else { int pids[] = getStorageUsers(path); if (pids == null || pids.length == 0) { done = true; } else { // Eliminate system process here? ams.killPids(pids, "unmount media", true); // Confirm if file references have been freed. pids = getStorageUsers(path); if (pids == null || pids.length == 0) { done = true; } } } if (!done && (ucb.retries < MAX_UNMOUNT_RETRIES)) { // Retry again Slog.i(TAG, "Retrying to kill storage users again"); mHandler.sendMessageDelayed( mHandler.obtainMessage(H_UNMOUNT_PM_DONE, ucb.retries++), RETRY_UNMOUNT_DELAY); } else { if (ucb.retries >= MAX_UNMOUNT_RETRIES) { Slog.i(TAG, "Failed to unmount media inspite of " + MAX_UNMOUNT_RETRIES + " retries. Forcibly killing processes now"); } sizeArr[sizeArrN++] = i; mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS, ucb)); } } // Remove already processed elements from list. for (int i = (sizeArrN-1); i >= 0; i--) { mForceUnmounts.remove(sizeArr[i]); } break; } case H_UNMOUNT_MS: { if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_MS"); UnmountCallBack ucb = (UnmountCallBack) msg.obj; ucb.handleFinished(); break; } case H_SYSTEM_READY: { try { handleSystemReady(); } catch (Exception ex) { Slog.e(TAG, "Boot-time mount exception", ex); } break; } } } }; private final HandlerThread mHandlerThread; private final Handler mHandler; void waitForAsecScan() { waitForLatch(mAsecsScanned); } private void waitForReady() { waitForLatch(mConnectedSignal); } private void waitForLatch(CountDownLatch latch) { for (;;) { try { if (latch.await(5000, TimeUnit.MILLISECONDS)) { return; } else { Slog.w(TAG, "Thread " + Thread.currentThread().getName() + " still waiting for MountService ready..."); } } catch (InterruptedException e) { Slog.w(TAG, "Interrupt while waiting for MountService to be ready."); } } } private void handleSystemReady() { // Snapshot current volume states since it's not safe to call into vold // while holding locks. final HashMap snapshot; synchronized (mVolumesLock) { snapshot = new HashMap(mVolumeStates); } for (Map.Entry entry : snapshot.entrySet()) { final String path = entry.getKey(); final String state = entry.getValue(); if (state.equals(Environment.MEDIA_UNMOUNTED)) { int rc = doMountVolume(path); if (rc != StorageResultCode.OperationSucceeded) { Slog.e(TAG, String.format("Boot-time mount failed (%d)", rc)); } } else if (state.equals(Environment.MEDIA_SHARED)) { /* * Bootstrap UMS enabled state since vold indicates * the volume is shared (runtime restart while ums enabled) */ notifyVolumeStateChange(null, path, VolumeState.NoMedia, VolumeState.Shared); } } // Push mounted state for all emulated storage synchronized (mVolumesLock) { for (StorageVolume volume : mVolumes) { if (volume.isEmulated()) { updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED); } } } /* * If UMS was connected on boot, send the connected event * now that we're up. */ if (mSendUmsConnectedOnBoot) { sendUmsIntent(true); mSendUmsConnectedOnBoot = false; } } private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); if (userId == -1) return; final UserHandle user = new UserHandle(userId); final String action = intent.getAction(); if (Intent.ACTION_USER_ADDED.equals(action)) { synchronized (mVolumesLock) { createEmulatedVolumeForUserLocked(user); } } else if (Intent.ACTION_USER_REMOVED.equals(action)) { synchronized (mVolumesLock) { final List toRemove = Lists.newArrayList(); for (StorageVolume volume : mVolumes) { if (user.equals(volume.getOwner())) { toRemove.add(volume); } } for (StorageVolume volume : toRemove) { removeVolumeLocked(volume); } } } } }; private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { boolean available = (intent.getBooleanExtra(UsbManager.USB_CONNECTED, false) && intent.getBooleanExtra(UsbManager.USB_FUNCTION_MASS_STORAGE, false)); notifyShareAvailabilityChange(available); } }; private final class MountServiceBinderListener implements IBinder.DeathRecipient { final IMountServiceListener mListener; MountServiceBinderListener(IMountServiceListener listener) { mListener = listener; } public void binderDied() { if (LOCAL_LOGD) Slog.d(TAG, "An IMountServiceListener has died!"); synchronized (mListeners) { mListeners.remove(this); mListener.asBinder().unlinkToDeath(this, 0); } } } private void doShareUnshareVolume(String path, String method, boolean enable) { // TODO: Add support for multiple share methods if (!method.equals("ums")) { throw new IllegalArgumentException(String.format("Method %s not supported", method)); } try { mConnector.execute("volume", enable ? "share" : "unshare", path, method); } catch (NativeDaemonConnectorException e) { Slog.e(TAG, "Failed to share/unshare", e); } } private void updatePublicVolumeState(StorageVolume volume, String state) { final String path = volume.getPath(); final String oldState; synchronized (mVolumesLock) { oldState = mVolumeStates.put(path, state); } if (state.equals(oldState)) { Slog.w(TAG, String.format("Duplicate state transition (%s -> %s) for %s", state, state, path)); return; } Slog.d(TAG, "volume state changed for " + path + " (" + oldState + " -> " + state + ")"); // Tell PackageManager about changes to primary volume state, but only // when not emulated. if (volume.isPrimary() && !volume.isEmulated()) { if (Environment.MEDIA_UNMOUNTED.equals(state)) { mPms.updateExternalMediaStatus(false, false); /* * Some OBBs might have been unmounted when this volume was * unmounted, so send a message to the handler to let it know to * remove those from the list of mounted OBBS. */ mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage( OBB_FLUSH_MOUNT_STATE, path)); } else if (Environment.MEDIA_MOUNTED.equals(state)) { mPms.updateExternalMediaStatus(true, false); } } synchronized (mListeners) { for (int i = mListeners.size() -1; i >= 0; i--) { MountServiceBinderListener bl = mListeners.get(i); try { bl.mListener.onStorageStateChanged(path, oldState, state); } catch (RemoteException rex) { Slog.e(TAG, "Listener dead"); mListeners.remove(i); } catch (Exception ex) { Slog.e(TAG, "Listener failed", ex); } } } } /** * Callback from NativeDaemonConnector */ public void onDaemonConnected() { /* * Since we'll be calling back into the NativeDaemonConnector, * we need to do our work in a new thread. */ new Thread("MountService#onDaemonConnected") { @Override public void run() { /** * Determine media state and UMS detection status */ try { final String[] vols = NativeDaemonEvent.filterMessageList( mConnector.executeForList("volume", "list"), VoldResponseCode.VolumeListResult); for (String volstr : vols) { String[] tok = volstr.split(" "); // FMT: