MountService.java revision a6711ff6f09cc25c693cbb50452e3f807c6122f5
1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server; 18 19import static android.content.pm.PackageManager.PERMISSION_GRANTED; 20 21import android.Manifest; 22import android.app.ActivityManagerNative; 23import android.app.AppOpsManager; 24import android.content.BroadcastReceiver; 25import android.content.ComponentName; 26import android.content.Context; 27import android.content.Intent; 28import android.content.IntentFilter; 29import android.content.ServiceConnection; 30import android.content.pm.PackageManager; 31import android.content.pm.UserInfo; 32import android.content.res.Configuration; 33import android.content.res.ObbInfo; 34import android.content.res.Resources; 35import android.content.res.TypedArray; 36import android.content.res.XmlResourceParser; 37import android.hardware.usb.UsbManager; 38import android.net.Uri; 39import android.os.Binder; 40import android.os.Environment; 41import android.os.Environment.UserEnvironment; 42import android.os.Handler; 43import android.os.HandlerThread; 44import android.os.IBinder; 45import android.os.Looper; 46import android.os.Message; 47import android.os.RemoteException; 48import android.os.ServiceManager; 49import android.os.SystemClock; 50import android.os.SystemProperties; 51import android.os.UserHandle; 52import android.os.UserManager; 53import android.os.storage.IMountService; 54import android.os.storage.IMountServiceListener; 55import android.os.storage.IMountShutdownObserver; 56import android.os.storage.IObbActionListener; 57import android.os.storage.OnObbStateChangeListener; 58import android.os.storage.StorageManager; 59import android.os.storage.StorageResultCode; 60import android.os.storage.StorageVolume; 61import android.text.TextUtils; 62import android.util.AttributeSet; 63import android.util.Slog; 64import android.util.Xml; 65 66import com.android.internal.annotations.GuardedBy; 67import com.android.internal.annotations.VisibleForTesting; 68import com.android.internal.app.IMediaContainerService; 69import com.android.internal.util.IndentingPrintWriter; 70import com.android.internal.util.Preconditions; 71import com.android.internal.util.XmlUtils; 72import com.android.server.NativeDaemonConnector.Command; 73import com.android.server.NativeDaemonConnector.SensitiveArg; 74import com.android.server.am.ActivityManagerService; 75import com.android.server.pm.PackageManagerService; 76import com.android.server.pm.UserManagerService; 77import com.google.android.collect.Lists; 78import com.google.android.collect.Maps; 79 80import org.apache.commons.codec.binary.Hex; 81import org.apache.commons.codec.DecoderException; 82import org.xmlpull.v1.XmlPullParserException; 83 84import java.io.File; 85import java.io.FileDescriptor; 86import java.io.IOException; 87import java.io.PrintWriter; 88import java.math.BigInteger; 89import java.nio.charset.StandardCharsets; 90import java.security.NoSuchAlgorithmException; 91import java.security.spec.InvalidKeySpecException; 92import java.security.spec.KeySpec; 93import java.util.ArrayList; 94import java.util.HashMap; 95import java.util.HashSet; 96import java.util.Iterator; 97import java.util.LinkedList; 98import java.util.List; 99import java.util.Locale; 100import java.util.Map; 101import java.util.Map.Entry; 102import java.util.concurrent.atomic.AtomicInteger; 103import java.util.concurrent.CountDownLatch; 104import java.util.concurrent.TimeUnit; 105 106import javax.crypto.SecretKey; 107import javax.crypto.SecretKeyFactory; 108import javax.crypto.spec.PBEKeySpec; 109 110/** 111 * MountService implements back-end services for platform storage 112 * management. 113 * @hide - Applications should use android.os.storage.StorageManager 114 * to access the MountService. 115 */ 116class MountService extends IMountService.Stub 117 implements INativeDaemonConnectorCallbacks, Watchdog.Monitor { 118 119 // Static direct instance pointer for the tightly-coupled idle service to use 120 static MountService sSelf = null; 121 122 // TODO: listen for user creation/deletion 123 124 private static final boolean LOCAL_LOGD = false; 125 private static final boolean DEBUG_UNMOUNT = false; 126 private static final boolean DEBUG_EVENTS = false; 127 private static final boolean DEBUG_OBB = false; 128 129 // Disable this since it messes up long-running cryptfs operations. 130 private static final boolean WATCHDOG_ENABLE = false; 131 132 private static final String TAG = "MountService"; 133 134 private static final String VOLD_TAG = "VoldConnector"; 135 136 /** Maximum number of ASEC containers allowed to be mounted. */ 137 private static final int MAX_CONTAINERS = 250; 138 139 /* 140 * Internal vold volume state constants 141 */ 142 class VolumeState { 143 public static final int Init = -1; 144 public static final int NoMedia = 0; 145 public static final int Idle = 1; 146 public static final int Pending = 2; 147 public static final int Checking = 3; 148 public static final int Mounted = 4; 149 public static final int Unmounting = 5; 150 public static final int Formatting = 6; 151 public static final int Shared = 7; 152 public static final int SharedMnt = 8; 153 } 154 155 /* 156 * Internal vold response code constants 157 */ 158 class VoldResponseCode { 159 /* 160 * 100 series - Requestion action was initiated; expect another reply 161 * before proceeding with a new command. 162 */ 163 public static final int VolumeListResult = 110; 164 public static final int AsecListResult = 111; 165 public static final int StorageUsersListResult = 112; 166 public static final int CryptfsGetfieldResult = 113; 167 168 /* 169 * 200 series - Requestion action has been successfully completed. 170 */ 171 public static final int ShareStatusResult = 210; 172 public static final int AsecPathResult = 211; 173 public static final int ShareEnabledResult = 212; 174 175 /* 176 * 400 series - Command was accepted, but the requested action 177 * did not take place. 178 */ 179 public static final int OpFailedNoMedia = 401; 180 public static final int OpFailedMediaBlank = 402; 181 public static final int OpFailedMediaCorrupt = 403; 182 public static final int OpFailedVolNotMounted = 404; 183 public static final int OpFailedStorageBusy = 405; 184 public static final int OpFailedStorageNotFound = 406; 185 186 /* 187 * 600 series - Unsolicited broadcasts. 188 */ 189 public static final int VolumeStateChange = 605; 190 public static final int VolumeUuidChange = 613; 191 public static final int VolumeUserLabelChange = 614; 192 public static final int VolumeDiskInserted = 630; 193 public static final int VolumeDiskRemoved = 631; 194 public static final int VolumeBadRemoval = 632; 195 196 /* 197 * 700 series - fstrim 198 */ 199 public static final int FstrimCompleted = 700; 200 } 201 202 /** List of crypto types. 203 * These must match CRYPT_TYPE_XXX in cryptfs.h AND their 204 * corresponding commands in CommandListener.cpp */ 205 public static final String[] CRYPTO_TYPES 206 = { "password", "default", "pattern", "pin" }; 207 208 private final Context mContext; 209 private final NativeDaemonConnector mConnector; 210 211 private final Object mVolumesLock = new Object(); 212 213 /** When defined, base template for user-specific {@link StorageVolume}. */ 214 private StorageVolume mEmulatedTemplate; 215 216 // TODO: separate storage volumes on per-user basis 217 218 @GuardedBy("mVolumesLock") 219 private final ArrayList<StorageVolume> mVolumes = Lists.newArrayList(); 220 /** Map from path to {@link StorageVolume} */ 221 @GuardedBy("mVolumesLock") 222 private final HashMap<String, StorageVolume> mVolumesByPath = Maps.newHashMap(); 223 /** Map from path to state */ 224 @GuardedBy("mVolumesLock") 225 private final HashMap<String, String> mVolumeStates = Maps.newHashMap(); 226 227 private volatile boolean mSystemReady = false; 228 229 private PackageManagerService mPms; 230 private boolean mUmsEnabling; 231 private boolean mUmsAvailable = false; 232 // Used as a lock for methods that register/unregister listeners. 233 final private ArrayList<MountServiceBinderListener> mListeners = 234 new ArrayList<MountServiceBinderListener>(); 235 private final CountDownLatch mConnectedSignal = new CountDownLatch(1); 236 private final CountDownLatch mAsecsScanned = new CountDownLatch(1); 237 private boolean mSendUmsConnectedOnBoot = false; 238 239 /** 240 * Private hash of currently mounted secure containers. 241 * Used as a lock in methods to manipulate secure containers. 242 */ 243 final private HashSet<String> mAsecMountSet = new HashSet<String>(); 244 245 /** 246 * The size of the crypto algorithm key in bits for OBB files. Currently 247 * Twofish is used which takes 128-bit keys. 248 */ 249 private static final int CRYPTO_ALGORITHM_KEY_SIZE = 128; 250 251 /** 252 * The number of times to run SHA1 in the PBKDF2 function for OBB files. 253 * 1024 is reasonably secure and not too slow. 254 */ 255 private static final int PBKDF2_HASH_ROUNDS = 1024; 256 257 /** 258 * Mounted OBB tracking information. Used to track the current state of all 259 * OBBs. 260 */ 261 final private Map<IBinder, List<ObbState>> mObbMounts = new HashMap<IBinder, List<ObbState>>(); 262 263 /** Map from raw paths to {@link ObbState}. */ 264 final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>(); 265 266 class ObbState implements IBinder.DeathRecipient { 267 public ObbState(String rawPath, String canonicalPath, int callingUid, 268 IObbActionListener token, int nonce) { 269 this.rawPath = rawPath; 270 this.canonicalPath = canonicalPath.toString(); 271 272 final int userId = UserHandle.getUserId(callingUid); 273 this.ownerPath = buildObbPath(canonicalPath, userId, false); 274 this.voldPath = buildObbPath(canonicalPath, userId, true); 275 276 this.ownerGid = UserHandle.getSharedAppGid(callingUid); 277 this.token = token; 278 this.nonce = nonce; 279 } 280 281 final String rawPath; 282 final String canonicalPath; 283 final String ownerPath; 284 final String voldPath; 285 286 final int ownerGid; 287 288 // Token of remote Binder caller 289 final IObbActionListener token; 290 291 // Identifier to pass back to the token 292 final int nonce; 293 294 public IBinder getBinder() { 295 return token.asBinder(); 296 } 297 298 @Override 299 public void binderDied() { 300 ObbAction action = new UnmountObbAction(this, true); 301 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action)); 302 } 303 304 public void link() throws RemoteException { 305 getBinder().linkToDeath(this, 0); 306 } 307 308 public void unlink() { 309 getBinder().unlinkToDeath(this, 0); 310 } 311 312 @Override 313 public String toString() { 314 StringBuilder sb = new StringBuilder("ObbState{"); 315 sb.append("rawPath=").append(rawPath); 316 sb.append(",canonicalPath=").append(canonicalPath); 317 sb.append(",ownerPath=").append(ownerPath); 318 sb.append(",voldPath=").append(voldPath); 319 sb.append(",ownerGid=").append(ownerGid); 320 sb.append(",token=").append(token); 321 sb.append(",binder=").append(getBinder()); 322 sb.append('}'); 323 return sb.toString(); 324 } 325 } 326 327 // OBB Action Handler 328 final private ObbActionHandler mObbActionHandler; 329 330 // OBB action handler messages 331 private static final int OBB_RUN_ACTION = 1; 332 private static final int OBB_MCS_BOUND = 2; 333 private static final int OBB_MCS_UNBIND = 3; 334 private static final int OBB_MCS_RECONNECT = 4; 335 private static final int OBB_FLUSH_MOUNT_STATE = 5; 336 337 /* 338 * Default Container Service information 339 */ 340 static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName( 341 "com.android.defcontainer", "com.android.defcontainer.DefaultContainerService"); 342 343 final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection(); 344 345 class DefaultContainerConnection implements ServiceConnection { 346 public void onServiceConnected(ComponentName name, IBinder service) { 347 if (DEBUG_OBB) 348 Slog.i(TAG, "onServiceConnected"); 349 IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service); 350 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_MCS_BOUND, imcs)); 351 } 352 353 public void onServiceDisconnected(ComponentName name) { 354 if (DEBUG_OBB) 355 Slog.i(TAG, "onServiceDisconnected"); 356 } 357 }; 358 359 // Used in the ObbActionHandler 360 private IMediaContainerService mContainerService = null; 361 362 // Handler messages 363 private static final int H_UNMOUNT_PM_UPDATE = 1; 364 private static final int H_UNMOUNT_PM_DONE = 2; 365 private static final int H_UNMOUNT_MS = 3; 366 private static final int H_SYSTEM_READY = 4; 367 private static final int H_FSTRIM = 5; 368 369 private static final int RETRY_UNMOUNT_DELAY = 30; // in ms 370 private static final int MAX_UNMOUNT_RETRIES = 4; 371 372 class UnmountCallBack { 373 final String path; 374 final boolean force; 375 final boolean removeEncryption; 376 int retries; 377 378 UnmountCallBack(String path, boolean force, boolean removeEncryption) { 379 retries = 0; 380 this.path = path; 381 this.force = force; 382 this.removeEncryption = removeEncryption; 383 } 384 385 void handleFinished() { 386 if (DEBUG_UNMOUNT) Slog.i(TAG, "Unmounting " + path); 387 doUnmountVolume(path, true, removeEncryption); 388 } 389 } 390 391 class UmsEnableCallBack extends UnmountCallBack { 392 final String method; 393 394 UmsEnableCallBack(String path, String method, boolean force) { 395 super(path, force, false); 396 this.method = method; 397 } 398 399 @Override 400 void handleFinished() { 401 super.handleFinished(); 402 doShareUnshareVolume(path, method, true); 403 } 404 } 405 406 class ShutdownCallBack extends UnmountCallBack { 407 MountShutdownLatch mMountShutdownLatch; 408 ShutdownCallBack(String path, final MountShutdownLatch mountShutdownLatch) { 409 super(path, true, false); 410 mMountShutdownLatch = mountShutdownLatch; 411 } 412 413 @Override 414 void handleFinished() { 415 int ret = doUnmountVolume(path, true, removeEncryption); 416 Slog.i(TAG, "Unmount completed: " + path + ", result code: " + ret); 417 mMountShutdownLatch.countDown(); 418 } 419 } 420 421 static class MountShutdownLatch { 422 private IMountShutdownObserver mObserver; 423 private AtomicInteger mCount; 424 425 MountShutdownLatch(final IMountShutdownObserver observer, int count) { 426 mObserver = observer; 427 mCount = new AtomicInteger(count); 428 } 429 430 void countDown() { 431 boolean sendShutdown = false; 432 if (mCount.decrementAndGet() == 0) { 433 sendShutdown = true; 434 } 435 if (sendShutdown && mObserver != null) { 436 try { 437 mObserver.onShutDownComplete(StorageResultCode.OperationSucceeded); 438 } catch (RemoteException e) { 439 Slog.w(TAG, "RemoteException when shutting down"); 440 } 441 } 442 } 443 } 444 445 class MountServiceHandler extends Handler { 446 ArrayList<UnmountCallBack> mForceUnmounts = new ArrayList<UnmountCallBack>(); 447 boolean mUpdatingStatus = false; 448 449 MountServiceHandler(Looper l) { 450 super(l); 451 } 452 453 @Override 454 public void handleMessage(Message msg) { 455 switch (msg.what) { 456 case H_UNMOUNT_PM_UPDATE: { 457 if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_UPDATE"); 458 UnmountCallBack ucb = (UnmountCallBack) msg.obj; 459 mForceUnmounts.add(ucb); 460 if (DEBUG_UNMOUNT) Slog.i(TAG, " registered = " + mUpdatingStatus); 461 // Register only if needed. 462 if (!mUpdatingStatus) { 463 if (DEBUG_UNMOUNT) Slog.i(TAG, "Updating external media status on PackageManager"); 464 mUpdatingStatus = true; 465 mPms.updateExternalMediaStatus(false, true); 466 } 467 break; 468 } 469 case H_UNMOUNT_PM_DONE: { 470 if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_DONE"); 471 if (DEBUG_UNMOUNT) Slog.i(TAG, "Updated status. Processing requests"); 472 mUpdatingStatus = false; 473 int size = mForceUnmounts.size(); 474 int sizeArr[] = new int[size]; 475 int sizeArrN = 0; 476 // Kill processes holding references first 477 ActivityManagerService ams = (ActivityManagerService) 478 ServiceManager.getService("activity"); 479 for (int i = 0; i < size; i++) { 480 UnmountCallBack ucb = mForceUnmounts.get(i); 481 String path = ucb.path; 482 boolean done = false; 483 if (!ucb.force) { 484 done = true; 485 } else { 486 int pids[] = getStorageUsers(path); 487 if (pids == null || pids.length == 0) { 488 done = true; 489 } else { 490 // Eliminate system process here? 491 ams.killPids(pids, "unmount media", true); 492 // Confirm if file references have been freed. 493 pids = getStorageUsers(path); 494 if (pids == null || pids.length == 0) { 495 done = true; 496 } 497 } 498 } 499 if (!done && (ucb.retries < MAX_UNMOUNT_RETRIES)) { 500 // Retry again 501 Slog.i(TAG, "Retrying to kill storage users again"); 502 mHandler.sendMessageDelayed( 503 mHandler.obtainMessage(H_UNMOUNT_PM_DONE, 504 ucb.retries++), 505 RETRY_UNMOUNT_DELAY); 506 } else { 507 if (ucb.retries >= MAX_UNMOUNT_RETRIES) { 508 Slog.i(TAG, "Failed to unmount media inspite of " + 509 MAX_UNMOUNT_RETRIES + " retries. Forcibly killing processes now"); 510 } 511 sizeArr[sizeArrN++] = i; 512 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS, 513 ucb)); 514 } 515 } 516 // Remove already processed elements from list. 517 for (int i = (sizeArrN-1); i >= 0; i--) { 518 mForceUnmounts.remove(sizeArr[i]); 519 } 520 break; 521 } 522 case H_UNMOUNT_MS: { 523 if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_MS"); 524 UnmountCallBack ucb = (UnmountCallBack) msg.obj; 525 ucb.handleFinished(); 526 break; 527 } 528 case H_SYSTEM_READY: { 529 try { 530 handleSystemReady(); 531 } catch (Exception ex) { 532 Slog.e(TAG, "Boot-time mount exception", ex); 533 } 534 break; 535 } 536 case H_FSTRIM: { 537 waitForReady(); 538 Slog.i(TAG, "Running fstrim idle maintenance"); 539 try { 540 // This method must be run on the main (handler) thread, 541 // so it is safe to directly call into vold. 542 mConnector.execute("fstrim", "dotrim"); 543 EventLogTags.writeFstrimStart(SystemClock.elapsedRealtime()); 544 } catch (NativeDaemonConnectorException ndce) { 545 Slog.e(TAG, "Failed to run fstrim!"); 546 } 547 // invoke the completion callback, if any 548 Runnable callback = (Runnable) msg.obj; 549 if (callback != null) { 550 callback.run(); 551 } 552 break; 553 } 554 } 555 } 556 }; 557 558 private final Handler mHandler; 559 560 void waitForAsecScan() { 561 waitForLatch(mAsecsScanned); 562 } 563 564 private void waitForReady() { 565 waitForLatch(mConnectedSignal); 566 } 567 568 private void waitForLatch(CountDownLatch latch) { 569 for (;;) { 570 try { 571 if (latch.await(5000, TimeUnit.MILLISECONDS)) { 572 return; 573 } else { 574 Slog.w(TAG, "Thread " + Thread.currentThread().getName() 575 + " still waiting for MountService ready..."); 576 } 577 } catch (InterruptedException e) { 578 Slog.w(TAG, "Interrupt while waiting for MountService to be ready."); 579 } 580 } 581 } 582 583 private boolean isReady() { 584 try { 585 return mConnectedSignal.await(0, TimeUnit.MILLISECONDS); 586 } catch (InterruptedException e) { 587 return false; 588 } 589 } 590 591 private void handleSystemReady() { 592 // Snapshot current volume states since it's not safe to call into vold 593 // while holding locks. 594 final HashMap<String, String> snapshot; 595 synchronized (mVolumesLock) { 596 snapshot = new HashMap<String, String>(mVolumeStates); 597 } 598 599 for (Map.Entry<String, String> entry : snapshot.entrySet()) { 600 final String path = entry.getKey(); 601 final String state = entry.getValue(); 602 603 if (state.equals(Environment.MEDIA_UNMOUNTED)) { 604 int rc = doMountVolume(path); 605 if (rc != StorageResultCode.OperationSucceeded) { 606 Slog.e(TAG, String.format("Boot-time mount failed (%d)", 607 rc)); 608 } 609 } else if (state.equals(Environment.MEDIA_SHARED)) { 610 /* 611 * Bootstrap UMS enabled state since vold indicates 612 * the volume is shared (runtime restart while ums enabled) 613 */ 614 notifyVolumeStateChange(null, path, VolumeState.NoMedia, 615 VolumeState.Shared); 616 } 617 } 618 619 // Push mounted state for all emulated storage 620 synchronized (mVolumesLock) { 621 for (StorageVolume volume : mVolumes) { 622 if (volume.isEmulated()) { 623 updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED); 624 } 625 } 626 } 627 628 /* 629 * If UMS was connected on boot, send the connected event 630 * now that we're up. 631 */ 632 if (mSendUmsConnectedOnBoot) { 633 sendUmsIntent(true); 634 mSendUmsConnectedOnBoot = false; 635 } 636 637 /* 638 * Start scheduling nominally-daily fstrim operations 639 */ 640 MountServiceIdler.scheduleIdlePass(mContext); 641 } 642 643 private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() { 644 @Override 645 public void onReceive(Context context, Intent intent) { 646 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); 647 if (userId == -1) return; 648 final UserHandle user = new UserHandle(userId); 649 650 final String action = intent.getAction(); 651 if (Intent.ACTION_USER_ADDED.equals(action)) { 652 synchronized (mVolumesLock) { 653 createEmulatedVolumeForUserLocked(user); 654 } 655 656 } else if (Intent.ACTION_USER_REMOVED.equals(action)) { 657 synchronized (mVolumesLock) { 658 final List<StorageVolume> toRemove = Lists.newArrayList(); 659 for (StorageVolume volume : mVolumes) { 660 if (user.equals(volume.getOwner())) { 661 toRemove.add(volume); 662 } 663 } 664 for (StorageVolume volume : toRemove) { 665 removeVolumeLocked(volume); 666 } 667 } 668 } 669 } 670 }; 671 672 private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { 673 @Override 674 public void onReceive(Context context, Intent intent) { 675 boolean available = (intent.getBooleanExtra(UsbManager.USB_CONNECTED, false) && 676 intent.getBooleanExtra(UsbManager.USB_FUNCTION_MASS_STORAGE, false)); 677 notifyShareAvailabilityChange(available); 678 } 679 }; 680 681 private final class MountServiceBinderListener implements IBinder.DeathRecipient { 682 final IMountServiceListener mListener; 683 684 MountServiceBinderListener(IMountServiceListener listener) { 685 mListener = listener; 686 687 } 688 689 public void binderDied() { 690 if (LOCAL_LOGD) Slog.d(TAG, "An IMountServiceListener has died!"); 691 synchronized (mListeners) { 692 mListeners.remove(this); 693 mListener.asBinder().unlinkToDeath(this, 0); 694 } 695 } 696 } 697 698 void runIdleMaintenance(Runnable callback) { 699 mHandler.sendMessage(mHandler.obtainMessage(H_FSTRIM, callback)); 700 } 701 702 private void doShareUnshareVolume(String path, String method, boolean enable) { 703 // TODO: Add support for multiple share methods 704 if (!method.equals("ums")) { 705 throw new IllegalArgumentException(String.format("Method %s not supported", method)); 706 } 707 708 try { 709 mConnector.execute("volume", enable ? "share" : "unshare", path, method); 710 } catch (NativeDaemonConnectorException e) { 711 Slog.e(TAG, "Failed to share/unshare", e); 712 } 713 } 714 715 private void updatePublicVolumeState(StorageVolume volume, String state) { 716 final String path = volume.getPath(); 717 final String oldState; 718 synchronized (mVolumesLock) { 719 oldState = mVolumeStates.put(path, state); 720 volume.setState(state); 721 } 722 723 if (state.equals(oldState)) { 724 Slog.w(TAG, String.format("Duplicate state transition (%s -> %s) for %s", 725 state, state, path)); 726 return; 727 } 728 729 Slog.d(TAG, "volume state changed for " + path + " (" + oldState + " -> " + state + ")"); 730 731 // Tell PackageManager about changes to primary volume state, but only 732 // when not emulated. 733 if (volume.isPrimary() && !volume.isEmulated()) { 734 if (Environment.MEDIA_UNMOUNTED.equals(state)) { 735 mPms.updateExternalMediaStatus(false, false); 736 737 /* 738 * Some OBBs might have been unmounted when this volume was 739 * unmounted, so send a message to the handler to let it know to 740 * remove those from the list of mounted OBBS. 741 */ 742 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage( 743 OBB_FLUSH_MOUNT_STATE, path)); 744 } else if (Environment.MEDIA_MOUNTED.equals(state)) { 745 mPms.updateExternalMediaStatus(true, false); 746 } 747 } 748 749 synchronized (mListeners) { 750 for (int i = mListeners.size() -1; i >= 0; i--) { 751 MountServiceBinderListener bl = mListeners.get(i); 752 try { 753 bl.mListener.onStorageStateChanged(path, oldState, state); 754 } catch (RemoteException rex) { 755 Slog.e(TAG, "Listener dead"); 756 mListeners.remove(i); 757 } catch (Exception ex) { 758 Slog.e(TAG, "Listener failed", ex); 759 } 760 } 761 } 762 } 763 764 /** 765 * Callback from NativeDaemonConnector 766 */ 767 public void onDaemonConnected() { 768 /* 769 * Since we'll be calling back into the NativeDaemonConnector, 770 * we need to do our work in a new thread. 771 */ 772 new Thread("MountService#onDaemonConnected") { 773 @Override 774 public void run() { 775 /** 776 * Determine media state and UMS detection status 777 */ 778 try { 779 final String[] vols = NativeDaemonEvent.filterMessageList( 780 mConnector.executeForList("volume", "list", "broadcast"), 781 VoldResponseCode.VolumeListResult); 782 for (String volstr : vols) { 783 String[] tok = volstr.split(" "); 784 // FMT: <label> <mountpoint> <state> 785 String path = tok[1]; 786 String state = Environment.MEDIA_REMOVED; 787 788 final StorageVolume volume; 789 synchronized (mVolumesLock) { 790 volume = mVolumesByPath.get(path); 791 } 792 793 int st = Integer.parseInt(tok[2]); 794 if (st == VolumeState.NoMedia) { 795 state = Environment.MEDIA_REMOVED; 796 } else if (st == VolumeState.Idle) { 797 state = Environment.MEDIA_UNMOUNTED; 798 } else if (st == VolumeState.Mounted) { 799 state = Environment.MEDIA_MOUNTED; 800 Slog.i(TAG, "Media already mounted on daemon connection"); 801 } else if (st == VolumeState.Shared) { 802 state = Environment.MEDIA_SHARED; 803 Slog.i(TAG, "Media shared on daemon connection"); 804 } else { 805 throw new Exception(String.format("Unexpected state %d", st)); 806 } 807 808 if (state != null) { 809 if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state); 810 updatePublicVolumeState(volume, state); 811 } 812 } 813 } catch (Exception e) { 814 Slog.e(TAG, "Error processing initial volume state", e); 815 final StorageVolume primary = getPrimaryPhysicalVolume(); 816 if (primary != null) { 817 updatePublicVolumeState(primary, Environment.MEDIA_REMOVED); 818 } 819 } 820 821 /* 822 * Now that we've done our initialization, release 823 * the hounds! 824 */ 825 mConnectedSignal.countDown(); 826 827 // On an encrypted device we can't see system properties yet, so pull 828 // the system locale out of the mount service. 829 copyLocaleFromMountService(); 830 831 // Let package manager load internal ASECs. 832 mPms.scanAvailableAsecs(); 833 834 // Notify people waiting for ASECs to be scanned that it's done. 835 mAsecsScanned.countDown(); 836 } 837 }.start(); 838 } 839 840 private void copyLocaleFromMountService() { 841 String systemLocale; 842 try { 843 systemLocale = getField(StorageManager.SYSTEM_LOCALE_KEY); 844 } catch (RemoteException e) { 845 return; 846 } 847 if (TextUtils.isEmpty(systemLocale)) { 848 return; 849 } 850 851 Slog.d(TAG, "Got locale " + systemLocale + " from mount service"); 852 Locale locale = Locale.forLanguageTag(systemLocale); 853 Configuration config = new Configuration(); 854 config.setLocale(locale); 855 try { 856 ActivityManagerNative.getDefault().updateConfiguration(config); 857 } catch (RemoteException e) { 858 Slog.e(TAG, "Error setting system locale from mount service", e); 859 } 860 861 // Temporary workaround for http://b/17945169. 862 Slog.d(TAG, "Setting system properties to " + systemLocale + " from mount service"); 863 SystemProperties.set("persist.sys.language", locale.getLanguage()); 864 SystemProperties.set("persist.sys.country", locale.getCountry()); 865 } 866 867 /** 868 * Callback from NativeDaemonConnector 869 */ 870 public boolean onCheckHoldWakeLock(int code) { 871 return false; 872 } 873 874 /** 875 * Callback from NativeDaemonConnector 876 */ 877 public boolean onEvent(int code, String raw, String[] cooked) { 878 if (DEBUG_EVENTS) { 879 StringBuilder builder = new StringBuilder(); 880 builder.append("onEvent::"); 881 builder.append(" raw= " + raw); 882 if (cooked != null) { 883 builder.append(" cooked = " ); 884 for (String str : cooked) { 885 builder.append(" " + str); 886 } 887 } 888 Slog.i(TAG, builder.toString()); 889 } 890 if (code == VoldResponseCode.VolumeStateChange) { 891 /* 892 * One of the volumes we're managing has changed state. 893 * Format: "NNN Volume <label> <path> state changed 894 * from <old_#> (<old_str>) to <new_#> (<new_str>)" 895 */ 896 notifyVolumeStateChange( 897 cooked[2], cooked[3], Integer.parseInt(cooked[7]), 898 Integer.parseInt(cooked[10])); 899 } else if (code == VoldResponseCode.VolumeUuidChange) { 900 // Format: nnn <label> <path> <uuid> 901 final String path = cooked[2]; 902 final String uuid = (cooked.length > 3) ? cooked[3] : null; 903 904 final StorageVolume vol = mVolumesByPath.get(path); 905 if (vol != null) { 906 vol.setUuid(uuid); 907 } 908 909 } else if (code == VoldResponseCode.VolumeUserLabelChange) { 910 // Format: nnn <label> <path> <label> 911 final String path = cooked[2]; 912 final String userLabel = (cooked.length > 3) ? cooked[3] : null; 913 914 final StorageVolume vol = mVolumesByPath.get(path); 915 if (vol != null) { 916 vol.setUserLabel(userLabel); 917 } 918 919 } else if ((code == VoldResponseCode.VolumeDiskInserted) || 920 (code == VoldResponseCode.VolumeDiskRemoved) || 921 (code == VoldResponseCode.VolumeBadRemoval)) { 922 // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>) 923 // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>) 924 // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>) 925 String action = null; 926 final String label = cooked[2]; 927 final String path = cooked[3]; 928 int major = -1; 929 int minor = -1; 930 931 try { 932 String devComp = cooked[6].substring(1, cooked[6].length() -1); 933 String[] devTok = devComp.split(":"); 934 major = Integer.parseInt(devTok[0]); 935 minor = Integer.parseInt(devTok[1]); 936 } catch (Exception ex) { 937 Slog.e(TAG, "Failed to parse major/minor", ex); 938 } 939 940 final StorageVolume volume; 941 final String state; 942 synchronized (mVolumesLock) { 943 volume = mVolumesByPath.get(path); 944 state = mVolumeStates.get(path); 945 } 946 947 if (code == VoldResponseCode.VolumeDiskInserted) { 948 new Thread("MountService#VolumeDiskInserted") { 949 @Override 950 public void run() { 951 try { 952 int rc; 953 if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) { 954 Slog.w(TAG, String.format("Insertion mount failed (%d)", rc)); 955 } 956 } catch (Exception ex) { 957 Slog.w(TAG, "Failed to mount media on insertion", ex); 958 } 959 } 960 }.start(); 961 } else if (code == VoldResponseCode.VolumeDiskRemoved) { 962 /* 963 * This event gets trumped if we're already in BAD_REMOVAL state 964 */ 965 if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) { 966 return true; 967 } 968 /* Send the media unmounted event first */ 969 if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first"); 970 updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED); 971 sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL); 972 973 if (DEBUG_EVENTS) Slog.i(TAG, "Sending media removed"); 974 updatePublicVolumeState(volume, Environment.MEDIA_REMOVED); 975 action = Intent.ACTION_MEDIA_REMOVED; 976 } else if (code == VoldResponseCode.VolumeBadRemoval) { 977 if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first"); 978 /* Send the media unmounted event first */ 979 updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED); 980 sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL); 981 982 if (DEBUG_EVENTS) Slog.i(TAG, "Sending media bad removal"); 983 updatePublicVolumeState(volume, Environment.MEDIA_BAD_REMOVAL); 984 action = Intent.ACTION_MEDIA_BAD_REMOVAL; 985 } else if (code == VoldResponseCode.FstrimCompleted) { 986 EventLogTags.writeFstrimFinish(SystemClock.elapsedRealtime()); 987 } else { 988 Slog.e(TAG, String.format("Unknown code {%d}", code)); 989 } 990 991 if (action != null) { 992 sendStorageIntent(action, volume, UserHandle.ALL); 993 } 994 } else { 995 return false; 996 } 997 998 return true; 999 } 1000 1001 private void notifyVolumeStateChange(String label, String path, int oldState, int newState) { 1002 final StorageVolume volume; 1003 final String state; 1004 synchronized (mVolumesLock) { 1005 volume = mVolumesByPath.get(path); 1006 state = getVolumeState(path); 1007 } 1008 1009 if (DEBUG_EVENTS) Slog.i(TAG, "notifyVolumeStateChange::" + state); 1010 1011 String action = null; 1012 1013 if (oldState == VolumeState.Shared && newState != oldState) { 1014 if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_UNSHARED intent"); 1015 sendStorageIntent(Intent.ACTION_MEDIA_UNSHARED, volume, UserHandle.ALL); 1016 } 1017 1018 if (newState == VolumeState.Init) { 1019 } else if (newState == VolumeState.NoMedia) { 1020 // NoMedia is handled via Disk Remove events 1021 } else if (newState == VolumeState.Idle) { 1022 /* 1023 * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or 1024 * if we're in the process of enabling UMS 1025 */ 1026 if (!state.equals( 1027 Environment.MEDIA_BAD_REMOVAL) && !state.equals( 1028 Environment.MEDIA_NOFS) && !state.equals( 1029 Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) { 1030 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state for media bad removal nofs and unmountable"); 1031 updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED); 1032 action = Intent.ACTION_MEDIA_UNMOUNTED; 1033 } 1034 } else if (newState == VolumeState.Pending) { 1035 } else if (newState == VolumeState.Checking) { 1036 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state checking"); 1037 updatePublicVolumeState(volume, Environment.MEDIA_CHECKING); 1038 action = Intent.ACTION_MEDIA_CHECKING; 1039 } else if (newState == VolumeState.Mounted) { 1040 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state mounted"); 1041 updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED); 1042 action = Intent.ACTION_MEDIA_MOUNTED; 1043 } else if (newState == VolumeState.Unmounting) { 1044 action = Intent.ACTION_MEDIA_EJECT; 1045 } else if (newState == VolumeState.Formatting) { 1046 } else if (newState == VolumeState.Shared) { 1047 if (DEBUG_EVENTS) Slog.i(TAG, "Updating volume state media mounted"); 1048 /* Send the media unmounted event first */ 1049 updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED); 1050 sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL); 1051 1052 if (DEBUG_EVENTS) Slog.i(TAG, "Updating media shared"); 1053 updatePublicVolumeState(volume, Environment.MEDIA_SHARED); 1054 action = Intent.ACTION_MEDIA_SHARED; 1055 if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_SHARED intent"); 1056 } else if (newState == VolumeState.SharedMnt) { 1057 Slog.e(TAG, "Live shared mounts not supported yet!"); 1058 return; 1059 } else { 1060 Slog.e(TAG, "Unhandled VolumeState {" + newState + "}"); 1061 } 1062 1063 if (action != null) { 1064 sendStorageIntent(action, volume, UserHandle.ALL); 1065 } 1066 } 1067 1068 private int doMountVolume(String path) { 1069 int rc = StorageResultCode.OperationSucceeded; 1070 1071 final StorageVolume volume; 1072 synchronized (mVolumesLock) { 1073 volume = mVolumesByPath.get(path); 1074 } 1075 1076 if (!volume.isEmulated() && hasUserRestriction(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA)) { 1077 Slog.w(TAG, "User has restriction DISALLOW_MOUNT_PHYSICAL_MEDIA; cannot mount volume."); 1078 return StorageResultCode.OperationFailedInternalError; 1079 } 1080 1081 if (DEBUG_EVENTS) Slog.i(TAG, "doMountVolume: Mouting " + path); 1082 try { 1083 mConnector.execute("volume", "mount", path); 1084 } catch (NativeDaemonConnectorException e) { 1085 /* 1086 * Mount failed for some reason 1087 */ 1088 String action = null; 1089 int code = e.getCode(); 1090 if (code == VoldResponseCode.OpFailedNoMedia) { 1091 /* 1092 * Attempt to mount but no media inserted 1093 */ 1094 rc = StorageResultCode.OperationFailedNoMedia; 1095 } else if (code == VoldResponseCode.OpFailedMediaBlank) { 1096 if (DEBUG_EVENTS) Slog.i(TAG, " updating volume state :: media nofs"); 1097 /* 1098 * Media is blank or does not contain a supported filesystem 1099 */ 1100 updatePublicVolumeState(volume, Environment.MEDIA_NOFS); 1101 action = Intent.ACTION_MEDIA_NOFS; 1102 rc = StorageResultCode.OperationFailedMediaBlank; 1103 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) { 1104 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state media corrupt"); 1105 /* 1106 * Volume consistency check failed 1107 */ 1108 updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTABLE); 1109 action = Intent.ACTION_MEDIA_UNMOUNTABLE; 1110 rc = StorageResultCode.OperationFailedMediaCorrupt; 1111 } else { 1112 rc = StorageResultCode.OperationFailedInternalError; 1113 } 1114 1115 /* 1116 * Send broadcast intent (if required for the failure) 1117 */ 1118 if (action != null) { 1119 sendStorageIntent(action, volume, UserHandle.ALL); 1120 } 1121 } 1122 1123 return rc; 1124 } 1125 1126 /* 1127 * If force is not set, we do not unmount if there are 1128 * processes holding references to the volume about to be unmounted. 1129 * If force is set, all the processes holding references need to be 1130 * killed via the ActivityManager before actually unmounting the volume. 1131 * This might even take a while and might be retried after timed delays 1132 * to make sure we dont end up in an instable state and kill some core 1133 * processes. 1134 * If removeEncryption is set, force is implied, and the system will remove any encryption 1135 * mapping set on the volume when unmounting. 1136 */ 1137 private int doUnmountVolume(String path, boolean force, boolean removeEncryption) { 1138 if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) { 1139 return VoldResponseCode.OpFailedVolNotMounted; 1140 } 1141 1142 /* 1143 * Force a GC to make sure AssetManagers in other threads of the 1144 * system_server are cleaned up. We have to do this since AssetManager 1145 * instances are kept as a WeakReference and it's possible we have files 1146 * open on the external storage. 1147 */ 1148 Runtime.getRuntime().gc(); 1149 1150 // Redundant probably. But no harm in updating state again. 1151 mPms.updateExternalMediaStatus(false, false); 1152 try { 1153 final Command cmd = new Command("volume", "unmount", path); 1154 if (removeEncryption) { 1155 cmd.appendArg("force_and_revert"); 1156 } else if (force) { 1157 cmd.appendArg("force"); 1158 } 1159 mConnector.execute(cmd); 1160 // We unmounted the volume. None of the asec containers are available now. 1161 synchronized (mAsecMountSet) { 1162 mAsecMountSet.clear(); 1163 } 1164 return StorageResultCode.OperationSucceeded; 1165 } catch (NativeDaemonConnectorException e) { 1166 // Don't worry about mismatch in PackageManager since the 1167 // call back will handle the status changes any way. 1168 int code = e.getCode(); 1169 if (code == VoldResponseCode.OpFailedVolNotMounted) { 1170 return StorageResultCode.OperationFailedStorageNotMounted; 1171 } else if (code == VoldResponseCode.OpFailedStorageBusy) { 1172 return StorageResultCode.OperationFailedStorageBusy; 1173 } else { 1174 return StorageResultCode.OperationFailedInternalError; 1175 } 1176 } 1177 } 1178 1179 private int doFormatVolume(String path) { 1180 try { 1181 mConnector.execute("volume", "format", path); 1182 return StorageResultCode.OperationSucceeded; 1183 } catch (NativeDaemonConnectorException e) { 1184 int code = e.getCode(); 1185 if (code == VoldResponseCode.OpFailedNoMedia) { 1186 return StorageResultCode.OperationFailedNoMedia; 1187 } else if (code == VoldResponseCode.OpFailedMediaCorrupt) { 1188 return StorageResultCode.OperationFailedMediaCorrupt; 1189 } else { 1190 return StorageResultCode.OperationFailedInternalError; 1191 } 1192 } 1193 } 1194 1195 private boolean doGetVolumeShared(String path, String method) { 1196 final NativeDaemonEvent event; 1197 try { 1198 event = mConnector.execute("volume", "shared", path, method); 1199 } catch (NativeDaemonConnectorException ex) { 1200 Slog.e(TAG, "Failed to read response to volume shared " + path + " " + method); 1201 return false; 1202 } 1203 1204 if (event.getCode() == VoldResponseCode.ShareEnabledResult) { 1205 return event.getMessage().endsWith("enabled"); 1206 } else { 1207 return false; 1208 } 1209 } 1210 1211 private void notifyShareAvailabilityChange(final boolean avail) { 1212 synchronized (mListeners) { 1213 mUmsAvailable = avail; 1214 for (int i = mListeners.size() -1; i >= 0; i--) { 1215 MountServiceBinderListener bl = mListeners.get(i); 1216 try { 1217 bl.mListener.onUsbMassStorageConnectionChanged(avail); 1218 } catch (RemoteException rex) { 1219 Slog.e(TAG, "Listener dead"); 1220 mListeners.remove(i); 1221 } catch (Exception ex) { 1222 Slog.e(TAG, "Listener failed", ex); 1223 } 1224 } 1225 } 1226 1227 if (mSystemReady == true) { 1228 sendUmsIntent(avail); 1229 } else { 1230 mSendUmsConnectedOnBoot = avail; 1231 } 1232 1233 final StorageVolume primary = getPrimaryPhysicalVolume(); 1234 if (avail == false && primary != null 1235 && Environment.MEDIA_SHARED.equals(getVolumeState(primary.getPath()))) { 1236 final String path = primary.getPath(); 1237 /* 1238 * USB mass storage disconnected while enabled 1239 */ 1240 new Thread("MountService#AvailabilityChange") { 1241 @Override 1242 public void run() { 1243 try { 1244 int rc; 1245 Slog.w(TAG, "Disabling UMS after cable disconnect"); 1246 doShareUnshareVolume(path, "ums", false); 1247 if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) { 1248 Slog.e(TAG, String.format( 1249 "Failed to remount {%s} on UMS enabled-disconnect (%d)", 1250 path, rc)); 1251 } 1252 } catch (Exception ex) { 1253 Slog.w(TAG, "Failed to mount media on UMS enabled-disconnect", ex); 1254 } 1255 } 1256 }.start(); 1257 } 1258 } 1259 1260 private void sendStorageIntent(String action, StorageVolume volume, UserHandle user) { 1261 final Intent intent = new Intent(action, Uri.parse("file://" + volume.getPath())); 1262 intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, volume); 1263 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 1264 Slog.d(TAG, "sendStorageIntent " + intent + " to " + user); 1265 mContext.sendBroadcastAsUser(intent, user); 1266 } 1267 1268 private void sendUmsIntent(boolean c) { 1269 mContext.sendBroadcastAsUser( 1270 new Intent((c ? Intent.ACTION_UMS_CONNECTED : Intent.ACTION_UMS_DISCONNECTED)), 1271 UserHandle.ALL); 1272 } 1273 1274 private void validatePermission(String perm) { 1275 if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) { 1276 throw new SecurityException(String.format("Requires %s permission", perm)); 1277 } 1278 } 1279 1280 private boolean hasUserRestriction(String restriction) { 1281 UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 1282 return um.hasUserRestriction(restriction, Binder.getCallingUserHandle()); 1283 } 1284 1285 private void validateUserRestriction(String restriction) { 1286 if (hasUserRestriction(restriction)) { 1287 throw new SecurityException("User has restriction " + restriction); 1288 } 1289 } 1290 1291 // Storage list XML tags 1292 private static final String TAG_STORAGE_LIST = "StorageList"; 1293 private static final String TAG_STORAGE = "storage"; 1294 1295 private void readStorageListLocked() { 1296 mVolumes.clear(); 1297 mVolumeStates.clear(); 1298 1299 Resources resources = mContext.getResources(); 1300 1301 int id = com.android.internal.R.xml.storage_list; 1302 XmlResourceParser parser = resources.getXml(id); 1303 AttributeSet attrs = Xml.asAttributeSet(parser); 1304 1305 try { 1306 XmlUtils.beginDocument(parser, TAG_STORAGE_LIST); 1307 while (true) { 1308 XmlUtils.nextElement(parser); 1309 1310 String element = parser.getName(); 1311 if (element == null) break; 1312 1313 if (TAG_STORAGE.equals(element)) { 1314 TypedArray a = resources.obtainAttributes(attrs, 1315 com.android.internal.R.styleable.Storage); 1316 1317 String path = a.getString( 1318 com.android.internal.R.styleable.Storage_mountPoint); 1319 int descriptionId = a.getResourceId( 1320 com.android.internal.R.styleable.Storage_storageDescription, -1); 1321 CharSequence description = a.getText( 1322 com.android.internal.R.styleable.Storage_storageDescription); 1323 boolean primary = a.getBoolean( 1324 com.android.internal.R.styleable.Storage_primary, false); 1325 boolean removable = a.getBoolean( 1326 com.android.internal.R.styleable.Storage_removable, false); 1327 boolean emulated = a.getBoolean( 1328 com.android.internal.R.styleable.Storage_emulated, false); 1329 int mtpReserve = a.getInt( 1330 com.android.internal.R.styleable.Storage_mtpReserve, 0); 1331 boolean allowMassStorage = a.getBoolean( 1332 com.android.internal.R.styleable.Storage_allowMassStorage, false); 1333 // resource parser does not support longs, so XML value is in megabytes 1334 long maxFileSize = a.getInt( 1335 com.android.internal.R.styleable.Storage_maxFileSize, 0) * 1024L * 1024L; 1336 1337 Slog.d(TAG, "got storage path: " + path + " description: " + description + 1338 " primary: " + primary + " removable: " + removable + 1339 " emulated: " + emulated + " mtpReserve: " + mtpReserve + 1340 " allowMassStorage: " + allowMassStorage + 1341 " maxFileSize: " + maxFileSize); 1342 1343 if (emulated) { 1344 // For devices with emulated storage, we create separate 1345 // volumes for each known user. 1346 mEmulatedTemplate = new StorageVolume(null, descriptionId, true, false, 1347 true, mtpReserve, false, maxFileSize, null); 1348 1349 final UserManagerService userManager = UserManagerService.getInstance(); 1350 for (UserInfo user : userManager.getUsers(false)) { 1351 createEmulatedVolumeForUserLocked(user.getUserHandle()); 1352 } 1353 1354 } else { 1355 if (path == null || description == null) { 1356 Slog.e(TAG, "Missing storage path or description in readStorageList"); 1357 } else { 1358 final StorageVolume volume = new StorageVolume(new File(path), 1359 descriptionId, primary, removable, emulated, mtpReserve, 1360 allowMassStorage, maxFileSize, null); 1361 addVolumeLocked(volume); 1362 1363 // Until we hear otherwise, treat as unmounted 1364 mVolumeStates.put(volume.getPath(), Environment.MEDIA_UNMOUNTED); 1365 volume.setState(Environment.MEDIA_UNMOUNTED); 1366 } 1367 } 1368 1369 a.recycle(); 1370 } 1371 } 1372 } catch (XmlPullParserException e) { 1373 throw new RuntimeException(e); 1374 } catch (IOException e) { 1375 throw new RuntimeException(e); 1376 } finally { 1377 // Compute storage ID for each physical volume; emulated storage is 1378 // always 0 when defined. 1379 int index = isExternalStorageEmulated() ? 1 : 0; 1380 for (StorageVolume volume : mVolumes) { 1381 if (!volume.isEmulated()) { 1382 volume.setStorageId(index++); 1383 } 1384 } 1385 parser.close(); 1386 } 1387 } 1388 1389 /** 1390 * Create and add new {@link StorageVolume} for given {@link UserHandle} 1391 * using {@link #mEmulatedTemplate} as template. 1392 */ 1393 private void createEmulatedVolumeForUserLocked(UserHandle user) { 1394 if (mEmulatedTemplate == null) { 1395 throw new IllegalStateException("Missing emulated volume multi-user template"); 1396 } 1397 1398 final UserEnvironment userEnv = new UserEnvironment(user.getIdentifier()); 1399 final File path = userEnv.getExternalStorageDirectory(); 1400 final StorageVolume volume = StorageVolume.fromTemplate(mEmulatedTemplate, path, user); 1401 volume.setStorageId(0); 1402 addVolumeLocked(volume); 1403 1404 if (mSystemReady) { 1405 updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED); 1406 } else { 1407 // Place stub status for early callers to find 1408 mVolumeStates.put(volume.getPath(), Environment.MEDIA_MOUNTED); 1409 volume.setState(Environment.MEDIA_MOUNTED); 1410 } 1411 } 1412 1413 private void addVolumeLocked(StorageVolume volume) { 1414 Slog.d(TAG, "addVolumeLocked() " + volume); 1415 mVolumes.add(volume); 1416 final StorageVolume existing = mVolumesByPath.put(volume.getPath(), volume); 1417 if (existing != null) { 1418 throw new IllegalStateException( 1419 "Volume at " + volume.getPath() + " already exists: " + existing); 1420 } 1421 } 1422 1423 private void removeVolumeLocked(StorageVolume volume) { 1424 Slog.d(TAG, "removeVolumeLocked() " + volume); 1425 mVolumes.remove(volume); 1426 mVolumesByPath.remove(volume.getPath()); 1427 mVolumeStates.remove(volume.getPath()); 1428 } 1429 1430 private StorageVolume getPrimaryPhysicalVolume() { 1431 synchronized (mVolumesLock) { 1432 for (StorageVolume volume : mVolumes) { 1433 if (volume.isPrimary() && !volume.isEmulated()) { 1434 return volume; 1435 } 1436 } 1437 } 1438 return null; 1439 } 1440 1441 /** 1442 * Constructs a new MountService instance 1443 * 1444 * @param context Binder context for this service 1445 */ 1446 public MountService(Context context) { 1447 sSelf = this; 1448 1449 mContext = context; 1450 1451 synchronized (mVolumesLock) { 1452 readStorageListLocked(); 1453 } 1454 1455 // XXX: This will go away soon in favor of IMountServiceObserver 1456 mPms = (PackageManagerService) ServiceManager.getService("package"); 1457 1458 HandlerThread hthread = new HandlerThread(TAG); 1459 hthread.start(); 1460 mHandler = new MountServiceHandler(hthread.getLooper()); 1461 1462 // Watch for user changes 1463 final IntentFilter userFilter = new IntentFilter(); 1464 userFilter.addAction(Intent.ACTION_USER_ADDED); 1465 userFilter.addAction(Intent.ACTION_USER_REMOVED); 1466 mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler); 1467 1468 // Watch for USB changes on primary volume 1469 final StorageVolume primary = getPrimaryPhysicalVolume(); 1470 if (primary != null && primary.allowMassStorage()) { 1471 mContext.registerReceiver( 1472 mUsbReceiver, new IntentFilter(UsbManager.ACTION_USB_STATE), null, mHandler); 1473 } 1474 1475 // Add OBB Action Handler to MountService thread. 1476 mObbActionHandler = new ObbActionHandler(IoThread.get().getLooper()); 1477 1478 /* 1479 * Create the connection to vold with a maximum queue of twice the 1480 * amount of containers we'd ever expect to have. This keeps an 1481 * "asec list" from blocking a thread repeatedly. 1482 */ 1483 mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25, 1484 null); 1485 1486 Thread thread = new Thread(mConnector, VOLD_TAG); 1487 thread.start(); 1488 1489 // Add ourself to the Watchdog monitors if enabled. 1490 if (WATCHDOG_ENABLE) { 1491 Watchdog.getInstance().addMonitor(this); 1492 } 1493 } 1494 1495 public void systemReady() { 1496 mSystemReady = true; 1497 mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget(); 1498 } 1499 1500 /** 1501 * Exposed API calls below here 1502 */ 1503 1504 public void registerListener(IMountServiceListener listener) { 1505 synchronized (mListeners) { 1506 MountServiceBinderListener bl = new MountServiceBinderListener(listener); 1507 try { 1508 listener.asBinder().linkToDeath(bl, 0); 1509 mListeners.add(bl); 1510 } catch (RemoteException rex) { 1511 Slog.e(TAG, "Failed to link to listener death"); 1512 } 1513 } 1514 } 1515 1516 public void unregisterListener(IMountServiceListener listener) { 1517 synchronized (mListeners) { 1518 for(MountServiceBinderListener bl : mListeners) { 1519 if (bl.mListener.asBinder() == listener.asBinder()) { 1520 mListeners.remove(mListeners.indexOf(bl)); 1521 listener.asBinder().unlinkToDeath(bl, 0); 1522 return; 1523 } 1524 } 1525 } 1526 } 1527 1528 public void shutdown(final IMountShutdownObserver observer) { 1529 validatePermission(android.Manifest.permission.SHUTDOWN); 1530 1531 Slog.i(TAG, "Shutting down"); 1532 synchronized (mVolumesLock) { 1533 // Get all volumes to be unmounted. 1534 MountShutdownLatch mountShutdownLatch = new MountShutdownLatch(observer, 1535 mVolumeStates.size()); 1536 1537 for (String path : mVolumeStates.keySet()) { 1538 String state = mVolumeStates.get(path); 1539 1540 if (state.equals(Environment.MEDIA_SHARED)) { 1541 /* 1542 * If the media is currently shared, unshare it. 1543 * XXX: This is still dangerous!. We should not 1544 * be rebooting at *all* if UMS is enabled, since 1545 * the UMS host could have dirty FAT cache entries 1546 * yet to flush. 1547 */ 1548 setUsbMassStorageEnabled(false); 1549 } else if (state.equals(Environment.MEDIA_CHECKING)) { 1550 /* 1551 * If the media is being checked, then we need to wait for 1552 * it to complete before being able to proceed. 1553 */ 1554 // XXX: @hackbod - Should we disable the ANR timer here? 1555 int retries = 30; 1556 while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) { 1557 try { 1558 Thread.sleep(1000); 1559 } catch (InterruptedException iex) { 1560 Slog.e(TAG, "Interrupted while waiting for media", iex); 1561 break; 1562 } 1563 state = Environment.getExternalStorageState(); 1564 } 1565 if (retries == 0) { 1566 Slog.e(TAG, "Timed out waiting for media to check"); 1567 } 1568 } 1569 1570 if (state.equals(Environment.MEDIA_MOUNTED)) { 1571 // Post a unmount message. 1572 ShutdownCallBack ucb = new ShutdownCallBack(path, mountShutdownLatch); 1573 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb)); 1574 } else if (observer != null) { 1575 /* 1576 * Count down, since nothing will be done. The observer will be 1577 * notified when we are done so shutdown sequence can continue. 1578 */ 1579 mountShutdownLatch.countDown(); 1580 Slog.i(TAG, "Unmount completed: " + path + 1581 ", result code: " + StorageResultCode.OperationSucceeded); 1582 } 1583 } 1584 } 1585 } 1586 1587 private boolean getUmsEnabling() { 1588 synchronized (mListeners) { 1589 return mUmsEnabling; 1590 } 1591 } 1592 1593 private void setUmsEnabling(boolean enable) { 1594 synchronized (mListeners) { 1595 mUmsEnabling = enable; 1596 } 1597 } 1598 1599 public boolean isUsbMassStorageConnected() { 1600 waitForReady(); 1601 1602 if (getUmsEnabling()) { 1603 return true; 1604 } 1605 synchronized (mListeners) { 1606 return mUmsAvailable; 1607 } 1608 } 1609 1610 public void setUsbMassStorageEnabled(boolean enable) { 1611 waitForReady(); 1612 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 1613 validateUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER); 1614 1615 final StorageVolume primary = getPrimaryPhysicalVolume(); 1616 if (primary == null) return; 1617 1618 // TODO: Add support for multiple share methods 1619 1620 /* 1621 * If the volume is mounted and we're enabling then unmount it 1622 */ 1623 String path = primary.getPath(); 1624 String vs = getVolumeState(path); 1625 String method = "ums"; 1626 if (enable && vs.equals(Environment.MEDIA_MOUNTED)) { 1627 // Override for isUsbMassStorageEnabled() 1628 setUmsEnabling(enable); 1629 UmsEnableCallBack umscb = new UmsEnableCallBack(path, method, true); 1630 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, umscb)); 1631 // Clear override 1632 setUmsEnabling(false); 1633 } 1634 /* 1635 * If we disabled UMS then mount the volume 1636 */ 1637 if (!enable) { 1638 doShareUnshareVolume(path, method, enable); 1639 if (doMountVolume(path) != StorageResultCode.OperationSucceeded) { 1640 Slog.e(TAG, "Failed to remount " + path + 1641 " after disabling share method " + method); 1642 /* 1643 * Even though the mount failed, the unshare didn't so don't indicate an error. 1644 * The mountVolume() call will have set the storage state and sent the necessary 1645 * broadcasts. 1646 */ 1647 } 1648 } 1649 } 1650 1651 public boolean isUsbMassStorageEnabled() { 1652 waitForReady(); 1653 1654 final StorageVolume primary = getPrimaryPhysicalVolume(); 1655 if (primary != null) { 1656 return doGetVolumeShared(primary.getPath(), "ums"); 1657 } else { 1658 return false; 1659 } 1660 } 1661 1662 /** 1663 * @return state of the volume at the specified mount point 1664 */ 1665 public String getVolumeState(String mountPoint) { 1666 synchronized (mVolumesLock) { 1667 String state = mVolumeStates.get(mountPoint); 1668 if (state == null) { 1669 Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume"); 1670 if (SystemProperties.get("vold.encrypt_progress").length() != 0) { 1671 state = Environment.MEDIA_REMOVED; 1672 } else { 1673 throw new IllegalArgumentException(); 1674 } 1675 } 1676 1677 return state; 1678 } 1679 } 1680 1681 @Override 1682 public boolean isExternalStorageEmulated() { 1683 return mEmulatedTemplate != null; 1684 } 1685 1686 public int mountVolume(String path) { 1687 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 1688 waitForReady(); 1689 return doMountVolume(path); 1690 } 1691 1692 public void unmountVolume(String path, boolean force, boolean removeEncryption) { 1693 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 1694 waitForReady(); 1695 1696 String volState = getVolumeState(path); 1697 if (DEBUG_UNMOUNT) { 1698 Slog.i(TAG, "Unmounting " + path 1699 + " force = " + force 1700 + " removeEncryption = " + removeEncryption); 1701 } 1702 if (Environment.MEDIA_UNMOUNTED.equals(volState) || 1703 Environment.MEDIA_REMOVED.equals(volState) || 1704 Environment.MEDIA_SHARED.equals(volState) || 1705 Environment.MEDIA_UNMOUNTABLE.equals(volState)) { 1706 // Media already unmounted or cannot be unmounted. 1707 // TODO return valid return code when adding observer call back. 1708 return; 1709 } 1710 UnmountCallBack ucb = new UnmountCallBack(path, force, removeEncryption); 1711 mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb)); 1712 } 1713 1714 public int formatVolume(String path) { 1715 validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS); 1716 waitForReady(); 1717 1718 return doFormatVolume(path); 1719 } 1720 1721 public int[] getStorageUsers(String path) { 1722 validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); 1723 waitForReady(); 1724 try { 1725 final String[] r = NativeDaemonEvent.filterMessageList( 1726 mConnector.executeForList("storage", "users", path), 1727 VoldResponseCode.StorageUsersListResult); 1728 1729 // FMT: <pid> <process name> 1730 int[] data = new int[r.length]; 1731 for (int i = 0; i < r.length; i++) { 1732 String[] tok = r[i].split(" "); 1733 try { 1734 data[i] = Integer.parseInt(tok[0]); 1735 } catch (NumberFormatException nfe) { 1736 Slog.e(TAG, String.format("Error parsing pid %s", tok[0])); 1737 return new int[0]; 1738 } 1739 } 1740 return data; 1741 } catch (NativeDaemonConnectorException e) { 1742 Slog.e(TAG, "Failed to retrieve storage users list", e); 1743 return new int[0]; 1744 } 1745 } 1746 1747 private void warnOnNotMounted() { 1748 final StorageVolume primary = getPrimaryPhysicalVolume(); 1749 if (primary != null) { 1750 boolean mounted = false; 1751 try { 1752 mounted = Environment.MEDIA_MOUNTED.equals(getVolumeState(primary.getPath())); 1753 } catch (IllegalArgumentException e) { 1754 } 1755 1756 if (!mounted) { 1757 Slog.w(TAG, "getSecureContainerList() called when storage not mounted"); 1758 } 1759 } 1760 } 1761 1762 public String[] getSecureContainerList() { 1763 validatePermission(android.Manifest.permission.ASEC_ACCESS); 1764 waitForReady(); 1765 warnOnNotMounted(); 1766 1767 try { 1768 return NativeDaemonEvent.filterMessageList( 1769 mConnector.executeForList("asec", "list"), VoldResponseCode.AsecListResult); 1770 } catch (NativeDaemonConnectorException e) { 1771 return new String[0]; 1772 } 1773 } 1774 1775 public int createSecureContainer(String id, int sizeMb, String fstype, String key, 1776 int ownerUid, boolean external) { 1777 validatePermission(android.Manifest.permission.ASEC_CREATE); 1778 waitForReady(); 1779 warnOnNotMounted(); 1780 1781 int rc = StorageResultCode.OperationSucceeded; 1782 try { 1783 mConnector.execute("asec", "create", id, sizeMb, fstype, new SensitiveArg(key), 1784 ownerUid, external ? "1" : "0"); 1785 } catch (NativeDaemonConnectorException e) { 1786 rc = StorageResultCode.OperationFailedInternalError; 1787 } 1788 1789 if (rc == StorageResultCode.OperationSucceeded) { 1790 synchronized (mAsecMountSet) { 1791 mAsecMountSet.add(id); 1792 } 1793 } 1794 return rc; 1795 } 1796 1797 @Override 1798 public int resizeSecureContainer(String id, int sizeMb, String key) { 1799 validatePermission(android.Manifest.permission.ASEC_CREATE); 1800 waitForReady(); 1801 warnOnNotMounted(); 1802 1803 int rc = StorageResultCode.OperationSucceeded; 1804 try { 1805 mConnector.execute("asec", "resize", id, sizeMb, new SensitiveArg(key)); 1806 } catch (NativeDaemonConnectorException e) { 1807 rc = StorageResultCode.OperationFailedInternalError; 1808 } 1809 return rc; 1810 } 1811 1812 public int finalizeSecureContainer(String id) { 1813 validatePermission(android.Manifest.permission.ASEC_CREATE); 1814 warnOnNotMounted(); 1815 1816 int rc = StorageResultCode.OperationSucceeded; 1817 try { 1818 mConnector.execute("asec", "finalize", id); 1819 /* 1820 * Finalization does a remount, so no need 1821 * to update mAsecMountSet 1822 */ 1823 } catch (NativeDaemonConnectorException e) { 1824 rc = StorageResultCode.OperationFailedInternalError; 1825 } 1826 return rc; 1827 } 1828 1829 public int fixPermissionsSecureContainer(String id, int gid, String filename) { 1830 validatePermission(android.Manifest.permission.ASEC_CREATE); 1831 warnOnNotMounted(); 1832 1833 int rc = StorageResultCode.OperationSucceeded; 1834 try { 1835 mConnector.execute("asec", "fixperms", id, gid, filename); 1836 /* 1837 * Fix permissions does a remount, so no need to update 1838 * mAsecMountSet 1839 */ 1840 } catch (NativeDaemonConnectorException e) { 1841 rc = StorageResultCode.OperationFailedInternalError; 1842 } 1843 return rc; 1844 } 1845 1846 public int destroySecureContainer(String id, boolean force) { 1847 validatePermission(android.Manifest.permission.ASEC_DESTROY); 1848 waitForReady(); 1849 warnOnNotMounted(); 1850 1851 /* 1852 * Force a GC to make sure AssetManagers in other threads of the 1853 * system_server are cleaned up. We have to do this since AssetManager 1854 * instances are kept as a WeakReference and it's possible we have files 1855 * open on the external storage. 1856 */ 1857 Runtime.getRuntime().gc(); 1858 1859 int rc = StorageResultCode.OperationSucceeded; 1860 try { 1861 final Command cmd = new Command("asec", "destroy", id); 1862 if (force) { 1863 cmd.appendArg("force"); 1864 } 1865 mConnector.execute(cmd); 1866 } catch (NativeDaemonConnectorException e) { 1867 int code = e.getCode(); 1868 if (code == VoldResponseCode.OpFailedStorageBusy) { 1869 rc = StorageResultCode.OperationFailedStorageBusy; 1870 } else { 1871 rc = StorageResultCode.OperationFailedInternalError; 1872 } 1873 } 1874 1875 if (rc == StorageResultCode.OperationSucceeded) { 1876 synchronized (mAsecMountSet) { 1877 if (mAsecMountSet.contains(id)) { 1878 mAsecMountSet.remove(id); 1879 } 1880 } 1881 } 1882 1883 return rc; 1884 } 1885 1886 public int mountSecureContainer(String id, String key, int ownerUid, boolean readOnly) { 1887 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT); 1888 waitForReady(); 1889 warnOnNotMounted(); 1890 1891 synchronized (mAsecMountSet) { 1892 if (mAsecMountSet.contains(id)) { 1893 return StorageResultCode.OperationFailedStorageMounted; 1894 } 1895 } 1896 1897 int rc = StorageResultCode.OperationSucceeded; 1898 try { 1899 mConnector.execute("asec", "mount", id, new SensitiveArg(key), ownerUid, 1900 readOnly ? "ro" : "rw"); 1901 } catch (NativeDaemonConnectorException e) { 1902 int code = e.getCode(); 1903 if (code != VoldResponseCode.OpFailedStorageBusy) { 1904 rc = StorageResultCode.OperationFailedInternalError; 1905 } 1906 } 1907 1908 if (rc == StorageResultCode.OperationSucceeded) { 1909 synchronized (mAsecMountSet) { 1910 mAsecMountSet.add(id); 1911 } 1912 } 1913 return rc; 1914 } 1915 1916 public int unmountSecureContainer(String id, boolean force) { 1917 validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT); 1918 waitForReady(); 1919 warnOnNotMounted(); 1920 1921 synchronized (mAsecMountSet) { 1922 if (!mAsecMountSet.contains(id)) { 1923 return StorageResultCode.OperationFailedStorageNotMounted; 1924 } 1925 } 1926 1927 /* 1928 * Force a GC to make sure AssetManagers in other threads of the 1929 * system_server are cleaned up. We have to do this since AssetManager 1930 * instances are kept as a WeakReference and it's possible we have files 1931 * open on the external storage. 1932 */ 1933 Runtime.getRuntime().gc(); 1934 1935 int rc = StorageResultCode.OperationSucceeded; 1936 try { 1937 final Command cmd = new Command("asec", "unmount", id); 1938 if (force) { 1939 cmd.appendArg("force"); 1940 } 1941 mConnector.execute(cmd); 1942 } catch (NativeDaemonConnectorException e) { 1943 int code = e.getCode(); 1944 if (code == VoldResponseCode.OpFailedStorageBusy) { 1945 rc = StorageResultCode.OperationFailedStorageBusy; 1946 } else { 1947 rc = StorageResultCode.OperationFailedInternalError; 1948 } 1949 } 1950 1951 if (rc == StorageResultCode.OperationSucceeded) { 1952 synchronized (mAsecMountSet) { 1953 mAsecMountSet.remove(id); 1954 } 1955 } 1956 return rc; 1957 } 1958 1959 public boolean isSecureContainerMounted(String id) { 1960 validatePermission(android.Manifest.permission.ASEC_ACCESS); 1961 waitForReady(); 1962 warnOnNotMounted(); 1963 1964 synchronized (mAsecMountSet) { 1965 return mAsecMountSet.contains(id); 1966 } 1967 } 1968 1969 public int renameSecureContainer(String oldId, String newId) { 1970 validatePermission(android.Manifest.permission.ASEC_RENAME); 1971 waitForReady(); 1972 warnOnNotMounted(); 1973 1974 synchronized (mAsecMountSet) { 1975 /* 1976 * Because a mounted container has active internal state which cannot be 1977 * changed while active, we must ensure both ids are not currently mounted. 1978 */ 1979 if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) { 1980 return StorageResultCode.OperationFailedStorageMounted; 1981 } 1982 } 1983 1984 int rc = StorageResultCode.OperationSucceeded; 1985 try { 1986 mConnector.execute("asec", "rename", oldId, newId); 1987 } catch (NativeDaemonConnectorException e) { 1988 rc = StorageResultCode.OperationFailedInternalError; 1989 } 1990 1991 return rc; 1992 } 1993 1994 public String getSecureContainerPath(String id) { 1995 validatePermission(android.Manifest.permission.ASEC_ACCESS); 1996 waitForReady(); 1997 warnOnNotMounted(); 1998 1999 final NativeDaemonEvent event; 2000 try { 2001 event = mConnector.execute("asec", "path", id); 2002 event.checkCode(VoldResponseCode.AsecPathResult); 2003 return event.getMessage(); 2004 } catch (NativeDaemonConnectorException e) { 2005 int code = e.getCode(); 2006 if (code == VoldResponseCode.OpFailedStorageNotFound) { 2007 Slog.i(TAG, String.format("Container '%s' not found", id)); 2008 return null; 2009 } else { 2010 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 2011 } 2012 } 2013 } 2014 2015 public String getSecureContainerFilesystemPath(String id) { 2016 validatePermission(android.Manifest.permission.ASEC_ACCESS); 2017 waitForReady(); 2018 warnOnNotMounted(); 2019 2020 final NativeDaemonEvent event; 2021 try { 2022 event = mConnector.execute("asec", "fspath", id); 2023 event.checkCode(VoldResponseCode.AsecPathResult); 2024 return event.getMessage(); 2025 } catch (NativeDaemonConnectorException e) { 2026 int code = e.getCode(); 2027 if (code == VoldResponseCode.OpFailedStorageNotFound) { 2028 Slog.i(TAG, String.format("Container '%s' not found", id)); 2029 return null; 2030 } else { 2031 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 2032 } 2033 } 2034 } 2035 2036 public void finishMediaUpdate() { 2037 mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE); 2038 } 2039 2040 private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) { 2041 if (callerUid == android.os.Process.SYSTEM_UID) { 2042 return true; 2043 } 2044 2045 if (packageName == null) { 2046 return false; 2047 } 2048 2049 final int packageUid = mPms.getPackageUid(packageName, UserHandle.getUserId(callerUid)); 2050 2051 if (DEBUG_OBB) { 2052 Slog.d(TAG, "packageName = " + packageName + ", packageUid = " + 2053 packageUid + ", callerUid = " + callerUid); 2054 } 2055 2056 return callerUid == packageUid; 2057 } 2058 2059 public String getMountedObbPath(String rawPath) { 2060 Preconditions.checkNotNull(rawPath, "rawPath cannot be null"); 2061 2062 waitForReady(); 2063 warnOnNotMounted(); 2064 2065 final ObbState state; 2066 synchronized (mObbPathToStateMap) { 2067 state = mObbPathToStateMap.get(rawPath); 2068 } 2069 if (state == null) { 2070 Slog.w(TAG, "Failed to find OBB mounted at " + rawPath); 2071 return null; 2072 } 2073 2074 final NativeDaemonEvent event; 2075 try { 2076 event = mConnector.execute("obb", "path", state.voldPath); 2077 event.checkCode(VoldResponseCode.AsecPathResult); 2078 return event.getMessage(); 2079 } catch (NativeDaemonConnectorException e) { 2080 int code = e.getCode(); 2081 if (code == VoldResponseCode.OpFailedStorageNotFound) { 2082 return null; 2083 } else { 2084 throw new IllegalStateException(String.format("Unexpected response code %d", code)); 2085 } 2086 } 2087 } 2088 2089 @Override 2090 public boolean isObbMounted(String rawPath) { 2091 Preconditions.checkNotNull(rawPath, "rawPath cannot be null"); 2092 synchronized (mObbMounts) { 2093 return mObbPathToStateMap.containsKey(rawPath); 2094 } 2095 } 2096 2097 @Override 2098 public void mountObb( 2099 String rawPath, String canonicalPath, String key, IObbActionListener token, int nonce) { 2100 Preconditions.checkNotNull(rawPath, "rawPath cannot be null"); 2101 Preconditions.checkNotNull(canonicalPath, "canonicalPath cannot be null"); 2102 Preconditions.checkNotNull(token, "token cannot be null"); 2103 2104 final int callingUid = Binder.getCallingUid(); 2105 final ObbState obbState = new ObbState(rawPath, canonicalPath, callingUid, token, nonce); 2106 final ObbAction action = new MountObbAction(obbState, key, callingUid); 2107 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action)); 2108 2109 if (DEBUG_OBB) 2110 Slog.i(TAG, "Send to OBB handler: " + action.toString()); 2111 } 2112 2113 @Override 2114 public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce) { 2115 Preconditions.checkNotNull(rawPath, "rawPath cannot be null"); 2116 2117 final ObbState existingState; 2118 synchronized (mObbPathToStateMap) { 2119 existingState = mObbPathToStateMap.get(rawPath); 2120 } 2121 2122 if (existingState != null) { 2123 // TODO: separate state object from request data 2124 final int callingUid = Binder.getCallingUid(); 2125 final ObbState newState = new ObbState( 2126 rawPath, existingState.canonicalPath, callingUid, token, nonce); 2127 final ObbAction action = new UnmountObbAction(newState, force); 2128 mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action)); 2129 2130 if (DEBUG_OBB) 2131 Slog.i(TAG, "Send to OBB handler: " + action.toString()); 2132 } else { 2133 Slog.w(TAG, "Unknown OBB mount at " + rawPath); 2134 } 2135 } 2136 2137 @Override 2138 public int getEncryptionState() { 2139 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, 2140 "no permission to access the crypt keeper"); 2141 2142 waitForReady(); 2143 2144 final NativeDaemonEvent event; 2145 try { 2146 event = mConnector.execute("cryptfs", "cryptocomplete"); 2147 return Integer.parseInt(event.getMessage()); 2148 } catch (NumberFormatException e) { 2149 // Bad result - unexpected. 2150 Slog.w(TAG, "Unable to parse result from cryptfs cryptocomplete"); 2151 return ENCRYPTION_STATE_ERROR_UNKNOWN; 2152 } catch (NativeDaemonConnectorException e) { 2153 // Something bad happened. 2154 Slog.w(TAG, "Error in communicating with cryptfs in validating"); 2155 return ENCRYPTION_STATE_ERROR_UNKNOWN; 2156 } 2157 } 2158 2159 private String toHex(String password) { 2160 if (password == null) { 2161 return new String(); 2162 } 2163 byte[] bytes = password.getBytes(StandardCharsets.UTF_8); 2164 return new String(Hex.encodeHex(bytes)); 2165 } 2166 2167 private String fromHex(String hexPassword) { 2168 if (hexPassword == null) { 2169 return null; 2170 } 2171 2172 try { 2173 byte[] bytes = Hex.decodeHex(hexPassword.toCharArray()); 2174 return new String(bytes, StandardCharsets.UTF_8); 2175 } catch (DecoderException e) { 2176 return null; 2177 } 2178 } 2179 2180 @Override 2181 public int decryptStorage(String password) { 2182 if (TextUtils.isEmpty(password)) { 2183 throw new IllegalArgumentException("password cannot be empty"); 2184 } 2185 2186 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, 2187 "no permission to access the crypt keeper"); 2188 2189 waitForReady(); 2190 2191 if (DEBUG_EVENTS) { 2192 Slog.i(TAG, "decrypting storage..."); 2193 } 2194 2195 final NativeDaemonEvent event; 2196 try { 2197 event = mConnector.execute("cryptfs", "checkpw", new SensitiveArg(toHex(password))); 2198 2199 final int code = Integer.parseInt(event.getMessage()); 2200 if (code == 0) { 2201 // Decrypt was successful. Post a delayed message before restarting in order 2202 // to let the UI to clear itself 2203 mHandler.postDelayed(new Runnable() { 2204 public void run() { 2205 try { 2206 mConnector.execute("cryptfs", "restart"); 2207 } catch (NativeDaemonConnectorException e) { 2208 Slog.e(TAG, "problem executing in background", e); 2209 } 2210 } 2211 }, 1000); // 1 second 2212 } 2213 2214 return code; 2215 } catch (NativeDaemonConnectorException e) { 2216 // Decryption failed 2217 return e.getCode(); 2218 } 2219 } 2220 2221 public int encryptStorage(int type, String password) { 2222 if (TextUtils.isEmpty(password) && type != StorageManager.CRYPT_TYPE_DEFAULT) { 2223 throw new IllegalArgumentException("password cannot be empty"); 2224 } 2225 2226 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, 2227 "no permission to access the crypt keeper"); 2228 2229 waitForReady(); 2230 2231 if (DEBUG_EVENTS) { 2232 Slog.i(TAG, "encrypting storage..."); 2233 } 2234 2235 try { 2236 mConnector.execute("cryptfs", "enablecrypto", "inplace", CRYPTO_TYPES[type], 2237 new SensitiveArg(toHex(password))); 2238 } catch (NativeDaemonConnectorException e) { 2239 // Encryption failed 2240 return e.getCode(); 2241 } 2242 2243 return 0; 2244 } 2245 2246 /** Set the password for encrypting the master key. 2247 * @param type One of the CRYPTO_TYPE_XXX consts defined in StorageManager. 2248 * @param password The password to set. 2249 */ 2250 public int changeEncryptionPassword(int type, String password) { 2251 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, 2252 "no permission to access the crypt keeper"); 2253 2254 waitForReady(); 2255 2256 if (DEBUG_EVENTS) { 2257 Slog.i(TAG, "changing encryption password..."); 2258 } 2259 2260 try { 2261 NativeDaemonEvent event = mConnector.execute("cryptfs", "changepw", CRYPTO_TYPES[type], 2262 new SensitiveArg(toHex(password))); 2263 return Integer.parseInt(event.getMessage()); 2264 } catch (NativeDaemonConnectorException e) { 2265 // Encryption failed 2266 return e.getCode(); 2267 } 2268 } 2269 2270 /** 2271 * Validate a user-supplied password string with cryptfs 2272 */ 2273 @Override 2274 public int verifyEncryptionPassword(String password) throws RemoteException { 2275 // Only the system process is permitted to validate passwords 2276 if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) { 2277 throw new SecurityException("no permission to access the crypt keeper"); 2278 } 2279 2280 mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, 2281 "no permission to access the crypt keeper"); 2282 2283 if (TextUtils.isEmpty(password)) { 2284 throw new IllegalArgumentException("password cannot be empty"); 2285 } 2286 2287 waitForReady(); 2288 2289 if (DEBUG_EVENTS) { 2290 Slog.i(TAG, "validating encryption password..."); 2291 } 2292 2293 final NativeDaemonEvent event; 2294 try { 2295 event = mConnector.execute("cryptfs", "verifypw", new SensitiveArg(toHex(password))); 2296 Slog.i(TAG, "cryptfs verifypw => " + event.getMessage()); 2297 return Integer.parseInt(event.getMessage()); 2298 } catch (NativeDaemonConnectorException e) { 2299 // Encryption failed 2300 return e.getCode(); 2301 } 2302 } 2303 2304 /** 2305 * Get the type of encryption used to encrypt the master key. 2306 * @return The type, one of the CRYPT_TYPE_XXX consts from StorageManager. 2307 */ 2308 @Override 2309 public int getPasswordType() { 2310 2311 waitForReady(); 2312 2313 final NativeDaemonEvent event; 2314 try { 2315 event = mConnector.execute("cryptfs", "getpwtype"); 2316 for (int i = 0; i < CRYPTO_TYPES.length; ++i) { 2317 if (CRYPTO_TYPES[i].equals(event.getMessage())) 2318 return i; 2319 } 2320 2321 throw new IllegalStateException("unexpected return from cryptfs"); 2322 } catch (NativeDaemonConnectorException e) { 2323 throw e.rethrowAsParcelableException(); 2324 } 2325 } 2326 2327 /** 2328 * Set a field in the crypto header. 2329 * @param field field to set 2330 * @param contents contents to set in field 2331 */ 2332 @Override 2333 public void setField(String field, String contents) throws RemoteException { 2334 2335 waitForReady(); 2336 2337 final NativeDaemonEvent event; 2338 try { 2339 event = mConnector.execute("cryptfs", "setfield", field, contents); 2340 } catch (NativeDaemonConnectorException e) { 2341 throw e.rethrowAsParcelableException(); 2342 } 2343 } 2344 2345 /** 2346 * Gets a field from the crypto header. 2347 * @param field field to get 2348 * @return contents of field 2349 */ 2350 @Override 2351 public String getField(String field) throws RemoteException { 2352 2353 waitForReady(); 2354 2355 final NativeDaemonEvent event; 2356 try { 2357 final String[] contents = NativeDaemonEvent.filterMessageList( 2358 mConnector.executeForList("cryptfs", "getfield", field), 2359 VoldResponseCode.CryptfsGetfieldResult); 2360 String result = new String(); 2361 for (String content : contents) { 2362 result += content; 2363 } 2364 return result; 2365 } catch (NativeDaemonConnectorException e) { 2366 throw e.rethrowAsParcelableException(); 2367 } 2368 } 2369 2370 @Override 2371 public String getPassword() throws RemoteException { 2372 if (!isReady()) { 2373 return new String(); 2374 } 2375 2376 final NativeDaemonEvent event; 2377 try { 2378 event = mConnector.execute("cryptfs", "getpw"); 2379 return fromHex(event.getMessage()); 2380 } catch (NativeDaemonConnectorException e) { 2381 throw e.rethrowAsParcelableException(); 2382 } 2383 } 2384 2385 @Override 2386 public void clearPassword() throws RemoteException { 2387 if (!isReady()) { 2388 return; 2389 } 2390 2391 final NativeDaemonEvent event; 2392 try { 2393 event = mConnector.execute("cryptfs", "clearpw"); 2394 } catch (NativeDaemonConnectorException e) { 2395 throw e.rethrowAsParcelableException(); 2396 } 2397 } 2398 2399 @Override 2400 public int mkdirs(String callingPkg, String appPath) { 2401 final int userId = UserHandle.getUserId(Binder.getCallingUid()); 2402 final UserEnvironment userEnv = new UserEnvironment(userId); 2403 2404 // Validate that reported package name belongs to caller 2405 final AppOpsManager appOps = (AppOpsManager) mContext.getSystemService( 2406 Context.APP_OPS_SERVICE); 2407 appOps.checkPackage(Binder.getCallingUid(), callingPkg); 2408 2409 try { 2410 appPath = new File(appPath).getCanonicalPath(); 2411 } catch (IOException e) { 2412 Slog.e(TAG, "Failed to resolve " + appPath + ": " + e); 2413 return -1; 2414 } 2415 2416 if (!appPath.endsWith("/")) { 2417 appPath = appPath + "/"; 2418 } 2419 2420 // Try translating the app path into a vold path, but require that it 2421 // belong to the calling package. 2422 String voldPath = maybeTranslatePathForVold(appPath, 2423 userEnv.buildExternalStorageAppDataDirs(callingPkg), 2424 userEnv.buildExternalStorageAppDataDirsForVold(callingPkg)); 2425 if (voldPath != null) { 2426 try { 2427 mConnector.execute("volume", "mkdirs", voldPath); 2428 return 0; 2429 } catch (NativeDaemonConnectorException e) { 2430 return e.getCode(); 2431 } 2432 } 2433 2434 voldPath = maybeTranslatePathForVold(appPath, 2435 userEnv.buildExternalStorageAppObbDirs(callingPkg), 2436 userEnv.buildExternalStorageAppObbDirsForVold(callingPkg)); 2437 if (voldPath != null) { 2438 try { 2439 mConnector.execute("volume", "mkdirs", voldPath); 2440 return 0; 2441 } catch (NativeDaemonConnectorException e) { 2442 return e.getCode(); 2443 } 2444 } 2445 2446 voldPath = maybeTranslatePathForVold(appPath, 2447 userEnv.buildExternalStorageAppMediaDirs(callingPkg), 2448 userEnv.buildExternalStorageAppMediaDirsForVold(callingPkg)); 2449 if (voldPath != null) { 2450 try { 2451 mConnector.execute("volume", "mkdirs", voldPath); 2452 return 0; 2453 } catch (NativeDaemonConnectorException e) { 2454 return e.getCode(); 2455 } 2456 } 2457 2458 throw new SecurityException("Invalid mkdirs path: " + appPath); 2459 } 2460 2461 /** 2462 * Translate the given path from an app-visible path to a vold-visible path, 2463 * but only if it's under the given whitelisted paths. 2464 * 2465 * @param path a canonicalized app-visible path. 2466 * @param appPaths list of app-visible paths that are allowed. 2467 * @param voldPaths list of vold-visible paths directly corresponding to the 2468 * allowed app-visible paths argument. 2469 * @return a vold-visible path representing the original path, or 2470 * {@code null} if the given path didn't have an app-to-vold 2471 * mapping. 2472 */ 2473 @VisibleForTesting 2474 public static String maybeTranslatePathForVold( 2475 String path, File[] appPaths, File[] voldPaths) { 2476 if (appPaths.length != voldPaths.length) { 2477 throw new IllegalStateException("Paths must be 1:1 mapping"); 2478 } 2479 2480 for (int i = 0; i < appPaths.length; i++) { 2481 final String appPath = appPaths[i].getAbsolutePath() + "/"; 2482 if (path.startsWith(appPath)) { 2483 path = new File(voldPaths[i], path.substring(appPath.length())) 2484 .getAbsolutePath(); 2485 if (!path.endsWith("/")) { 2486 path = path + "/"; 2487 } 2488 return path; 2489 } 2490 } 2491 return null; 2492 } 2493 2494 @Override 2495 public StorageVolume[] getVolumeList() { 2496 final int callingUserId = UserHandle.getCallingUserId(); 2497 final boolean accessAll = (mContext.checkPermission( 2498 android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE, 2499 Binder.getCallingPid(), Binder.getCallingUid()) == PERMISSION_GRANTED); 2500 2501 synchronized (mVolumesLock) { 2502 final ArrayList<StorageVolume> filtered = Lists.newArrayList(); 2503 for (StorageVolume volume : mVolumes) { 2504 final UserHandle owner = volume.getOwner(); 2505 final boolean ownerMatch = owner == null || owner.getIdentifier() == callingUserId; 2506 if (accessAll || ownerMatch) { 2507 filtered.add(volume); 2508 } 2509 } 2510 return filtered.toArray(new StorageVolume[filtered.size()]); 2511 } 2512 } 2513 2514 private void addObbStateLocked(ObbState obbState) throws RemoteException { 2515 final IBinder binder = obbState.getBinder(); 2516 List<ObbState> obbStates = mObbMounts.get(binder); 2517 2518 if (obbStates == null) { 2519 obbStates = new ArrayList<ObbState>(); 2520 mObbMounts.put(binder, obbStates); 2521 } else { 2522 for (final ObbState o : obbStates) { 2523 if (o.rawPath.equals(obbState.rawPath)) { 2524 throw new IllegalStateException("Attempt to add ObbState twice. " 2525 + "This indicates an error in the MountService logic."); 2526 } 2527 } 2528 } 2529 2530 obbStates.add(obbState); 2531 try { 2532 obbState.link(); 2533 } catch (RemoteException e) { 2534 /* 2535 * The binder died before we could link it, so clean up our state 2536 * and return failure. 2537 */ 2538 obbStates.remove(obbState); 2539 if (obbStates.isEmpty()) { 2540 mObbMounts.remove(binder); 2541 } 2542 2543 // Rethrow the error so mountObb can get it 2544 throw e; 2545 } 2546 2547 mObbPathToStateMap.put(obbState.rawPath, obbState); 2548 } 2549 2550 private void removeObbStateLocked(ObbState obbState) { 2551 final IBinder binder = obbState.getBinder(); 2552 final List<ObbState> obbStates = mObbMounts.get(binder); 2553 if (obbStates != null) { 2554 if (obbStates.remove(obbState)) { 2555 obbState.unlink(); 2556 } 2557 if (obbStates.isEmpty()) { 2558 mObbMounts.remove(binder); 2559 } 2560 } 2561 2562 mObbPathToStateMap.remove(obbState.rawPath); 2563 } 2564 2565 private class ObbActionHandler extends Handler { 2566 private boolean mBound = false; 2567 private final List<ObbAction> mActions = new LinkedList<ObbAction>(); 2568 2569 ObbActionHandler(Looper l) { 2570 super(l); 2571 } 2572 2573 @Override 2574 public void handleMessage(Message msg) { 2575 switch (msg.what) { 2576 case OBB_RUN_ACTION: { 2577 final ObbAction action = (ObbAction) msg.obj; 2578 2579 if (DEBUG_OBB) 2580 Slog.i(TAG, "OBB_RUN_ACTION: " + action.toString()); 2581 2582 // If a bind was already initiated we don't really 2583 // need to do anything. The pending install 2584 // will be processed later on. 2585 if (!mBound) { 2586 // If this is the only one pending we might 2587 // have to bind to the service again. 2588 if (!connectToService()) { 2589 Slog.e(TAG, "Failed to bind to media container service"); 2590 action.handleError(); 2591 return; 2592 } 2593 } 2594 2595 mActions.add(action); 2596 break; 2597 } 2598 case OBB_MCS_BOUND: { 2599 if (DEBUG_OBB) 2600 Slog.i(TAG, "OBB_MCS_BOUND"); 2601 if (msg.obj != null) { 2602 mContainerService = (IMediaContainerService) msg.obj; 2603 } 2604 if (mContainerService == null) { 2605 // Something seriously wrong. Bail out 2606 Slog.e(TAG, "Cannot bind to media container service"); 2607 for (ObbAction action : mActions) { 2608 // Indicate service bind error 2609 action.handleError(); 2610 } 2611 mActions.clear(); 2612 } else if (mActions.size() > 0) { 2613 final ObbAction action = mActions.get(0); 2614 if (action != null) { 2615 action.execute(this); 2616 } 2617 } else { 2618 // Should never happen ideally. 2619 Slog.w(TAG, "Empty queue"); 2620 } 2621 break; 2622 } 2623 case OBB_MCS_RECONNECT: { 2624 if (DEBUG_OBB) 2625 Slog.i(TAG, "OBB_MCS_RECONNECT"); 2626 if (mActions.size() > 0) { 2627 if (mBound) { 2628 disconnectService(); 2629 } 2630 if (!connectToService()) { 2631 Slog.e(TAG, "Failed to bind to media container service"); 2632 for (ObbAction action : mActions) { 2633 // Indicate service bind error 2634 action.handleError(); 2635 } 2636 mActions.clear(); 2637 } 2638 } 2639 break; 2640 } 2641 case OBB_MCS_UNBIND: { 2642 if (DEBUG_OBB) 2643 Slog.i(TAG, "OBB_MCS_UNBIND"); 2644 2645 // Delete pending install 2646 if (mActions.size() > 0) { 2647 mActions.remove(0); 2648 } 2649 if (mActions.size() == 0) { 2650 if (mBound) { 2651 disconnectService(); 2652 } 2653 } else { 2654 // There are more pending requests in queue. 2655 // Just post MCS_BOUND message to trigger processing 2656 // of next pending install. 2657 mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND); 2658 } 2659 break; 2660 } 2661 case OBB_FLUSH_MOUNT_STATE: { 2662 final String path = (String) msg.obj; 2663 2664 if (DEBUG_OBB) 2665 Slog.i(TAG, "Flushing all OBB state for path " + path); 2666 2667 synchronized (mObbMounts) { 2668 final List<ObbState> obbStatesToRemove = new LinkedList<ObbState>(); 2669 2670 final Iterator<ObbState> i = mObbPathToStateMap.values().iterator(); 2671 while (i.hasNext()) { 2672 final ObbState state = i.next(); 2673 2674 /* 2675 * If this entry's source file is in the volume path 2676 * that got unmounted, remove it because it's no 2677 * longer valid. 2678 */ 2679 if (state.canonicalPath.startsWith(path)) { 2680 obbStatesToRemove.add(state); 2681 } 2682 } 2683 2684 for (final ObbState obbState : obbStatesToRemove) { 2685 if (DEBUG_OBB) 2686 Slog.i(TAG, "Removing state for " + obbState.rawPath); 2687 2688 removeObbStateLocked(obbState); 2689 2690 try { 2691 obbState.token.onObbResult(obbState.rawPath, obbState.nonce, 2692 OnObbStateChangeListener.UNMOUNTED); 2693 } catch (RemoteException e) { 2694 Slog.i(TAG, "Couldn't send unmount notification for OBB: " 2695 + obbState.rawPath); 2696 } 2697 } 2698 } 2699 break; 2700 } 2701 } 2702 } 2703 2704 private boolean connectToService() { 2705 if (DEBUG_OBB) 2706 Slog.i(TAG, "Trying to bind to DefaultContainerService"); 2707 2708 Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT); 2709 if (mContext.bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE)) { 2710 mBound = true; 2711 return true; 2712 } 2713 return false; 2714 } 2715 2716 private void disconnectService() { 2717 mContainerService = null; 2718 mBound = false; 2719 mContext.unbindService(mDefContainerConn); 2720 } 2721 } 2722 2723 abstract class ObbAction { 2724 private static final int MAX_RETRIES = 3; 2725 private int mRetries; 2726 2727 ObbState mObbState; 2728 2729 ObbAction(ObbState obbState) { 2730 mObbState = obbState; 2731 } 2732 2733 public void execute(ObbActionHandler handler) { 2734 try { 2735 if (DEBUG_OBB) 2736 Slog.i(TAG, "Starting to execute action: " + toString()); 2737 mRetries++; 2738 if (mRetries > MAX_RETRIES) { 2739 Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up"); 2740 mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND); 2741 handleError(); 2742 return; 2743 } else { 2744 handleExecute(); 2745 if (DEBUG_OBB) 2746 Slog.i(TAG, "Posting install MCS_UNBIND"); 2747 mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND); 2748 } 2749 } catch (RemoteException e) { 2750 if (DEBUG_OBB) 2751 Slog.i(TAG, "Posting install MCS_RECONNECT"); 2752 mObbActionHandler.sendEmptyMessage(OBB_MCS_RECONNECT); 2753 } catch (Exception e) { 2754 if (DEBUG_OBB) 2755 Slog.d(TAG, "Error handling OBB action", e); 2756 handleError(); 2757 mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND); 2758 } 2759 } 2760 2761 abstract void handleExecute() throws RemoteException, IOException; 2762 abstract void handleError(); 2763 2764 protected ObbInfo getObbInfo() throws IOException { 2765 ObbInfo obbInfo; 2766 try { 2767 obbInfo = mContainerService.getObbInfo(mObbState.ownerPath); 2768 } catch (RemoteException e) { 2769 Slog.d(TAG, "Couldn't call DefaultContainerService to fetch OBB info for " 2770 + mObbState.ownerPath); 2771 obbInfo = null; 2772 } 2773 if (obbInfo == null) { 2774 throw new IOException("Couldn't read OBB file: " + mObbState.ownerPath); 2775 } 2776 return obbInfo; 2777 } 2778 2779 protected void sendNewStatusOrIgnore(int status) { 2780 if (mObbState == null || mObbState.token == null) { 2781 return; 2782 } 2783 2784 try { 2785 mObbState.token.onObbResult(mObbState.rawPath, mObbState.nonce, status); 2786 } catch (RemoteException e) { 2787 Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged"); 2788 } 2789 } 2790 } 2791 2792 class MountObbAction extends ObbAction { 2793 private final String mKey; 2794 private final int mCallingUid; 2795 2796 MountObbAction(ObbState obbState, String key, int callingUid) { 2797 super(obbState); 2798 mKey = key; 2799 mCallingUid = callingUid; 2800 } 2801 2802 @Override 2803 public void handleExecute() throws IOException, RemoteException { 2804 waitForReady(); 2805 warnOnNotMounted(); 2806 2807 final ObbInfo obbInfo = getObbInfo(); 2808 2809 if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mCallingUid)) { 2810 Slog.w(TAG, "Denied attempt to mount OBB " + obbInfo.filename 2811 + " which is owned by " + obbInfo.packageName); 2812 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED); 2813 return; 2814 } 2815 2816 final boolean isMounted; 2817 synchronized (mObbMounts) { 2818 isMounted = mObbPathToStateMap.containsKey(mObbState.rawPath); 2819 } 2820 if (isMounted) { 2821 Slog.w(TAG, "Attempt to mount OBB which is already mounted: " + obbInfo.filename); 2822 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_ALREADY_MOUNTED); 2823 return; 2824 } 2825 2826 final String hashedKey; 2827 if (mKey == null) { 2828 hashedKey = "none"; 2829 } else { 2830 try { 2831 SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); 2832 2833 KeySpec ks = new PBEKeySpec(mKey.toCharArray(), obbInfo.salt, 2834 PBKDF2_HASH_ROUNDS, CRYPTO_ALGORITHM_KEY_SIZE); 2835 SecretKey key = factory.generateSecret(ks); 2836 BigInteger bi = new BigInteger(key.getEncoded()); 2837 hashedKey = bi.toString(16); 2838 } catch (NoSuchAlgorithmException e) { 2839 Slog.e(TAG, "Could not load PBKDF2 algorithm", e); 2840 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); 2841 return; 2842 } catch (InvalidKeySpecException e) { 2843 Slog.e(TAG, "Invalid key spec when loading PBKDF2 algorithm", e); 2844 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); 2845 return; 2846 } 2847 } 2848 2849 int rc = StorageResultCode.OperationSucceeded; 2850 try { 2851 mConnector.execute("obb", "mount", mObbState.voldPath, new SensitiveArg(hashedKey), 2852 mObbState.ownerGid); 2853 } catch (NativeDaemonConnectorException e) { 2854 int code = e.getCode(); 2855 if (code != VoldResponseCode.OpFailedStorageBusy) { 2856 rc = StorageResultCode.OperationFailedInternalError; 2857 } 2858 } 2859 2860 if (rc == StorageResultCode.OperationSucceeded) { 2861 if (DEBUG_OBB) 2862 Slog.d(TAG, "Successfully mounted OBB " + mObbState.voldPath); 2863 2864 synchronized (mObbMounts) { 2865 addObbStateLocked(mObbState); 2866 } 2867 2868 sendNewStatusOrIgnore(OnObbStateChangeListener.MOUNTED); 2869 } else { 2870 Slog.e(TAG, "Couldn't mount OBB file: " + rc); 2871 2872 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT); 2873 } 2874 } 2875 2876 @Override 2877 public void handleError() { 2878 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); 2879 } 2880 2881 @Override 2882 public String toString() { 2883 StringBuilder sb = new StringBuilder(); 2884 sb.append("MountObbAction{"); 2885 sb.append(mObbState); 2886 sb.append('}'); 2887 return sb.toString(); 2888 } 2889 } 2890 2891 class UnmountObbAction extends ObbAction { 2892 private final boolean mForceUnmount; 2893 2894 UnmountObbAction(ObbState obbState, boolean force) { 2895 super(obbState); 2896 mForceUnmount = force; 2897 } 2898 2899 @Override 2900 public void handleExecute() throws IOException { 2901 waitForReady(); 2902 warnOnNotMounted(); 2903 2904 final ObbInfo obbInfo = getObbInfo(); 2905 2906 final ObbState existingState; 2907 synchronized (mObbMounts) { 2908 existingState = mObbPathToStateMap.get(mObbState.rawPath); 2909 } 2910 2911 if (existingState == null) { 2912 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_NOT_MOUNTED); 2913 return; 2914 } 2915 2916 if (existingState.ownerGid != mObbState.ownerGid) { 2917 Slog.w(TAG, "Permission denied attempting to unmount OBB " + existingState.rawPath 2918 + " (owned by GID " + existingState.ownerGid + ")"); 2919 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED); 2920 return; 2921 } 2922 2923 int rc = StorageResultCode.OperationSucceeded; 2924 try { 2925 final Command cmd = new Command("obb", "unmount", mObbState.voldPath); 2926 if (mForceUnmount) { 2927 cmd.appendArg("force"); 2928 } 2929 mConnector.execute(cmd); 2930 } catch (NativeDaemonConnectorException e) { 2931 int code = e.getCode(); 2932 if (code == VoldResponseCode.OpFailedStorageBusy) { 2933 rc = StorageResultCode.OperationFailedStorageBusy; 2934 } else if (code == VoldResponseCode.OpFailedStorageNotFound) { 2935 // If it's not mounted then we've already won. 2936 rc = StorageResultCode.OperationSucceeded; 2937 } else { 2938 rc = StorageResultCode.OperationFailedInternalError; 2939 } 2940 } 2941 2942 if (rc == StorageResultCode.OperationSucceeded) { 2943 synchronized (mObbMounts) { 2944 removeObbStateLocked(existingState); 2945 } 2946 2947 sendNewStatusOrIgnore(OnObbStateChangeListener.UNMOUNTED); 2948 } else { 2949 Slog.w(TAG, "Could not unmount OBB: " + existingState); 2950 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_UNMOUNT); 2951 } 2952 } 2953 2954 @Override 2955 public void handleError() { 2956 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL); 2957 } 2958 2959 @Override 2960 public String toString() { 2961 StringBuilder sb = new StringBuilder(); 2962 sb.append("UnmountObbAction{"); 2963 sb.append(mObbState); 2964 sb.append(",force="); 2965 sb.append(mForceUnmount); 2966 sb.append('}'); 2967 return sb.toString(); 2968 } 2969 } 2970 2971 @VisibleForTesting 2972 public static String buildObbPath(final String canonicalPath, int userId, boolean forVold) { 2973 // TODO: allow caller to provide Environment for full testing 2974 // TODO: extend to support OBB mounts on secondary external storage 2975 2976 // Only adjust paths when storage is emulated 2977 if (!Environment.isExternalStorageEmulated()) { 2978 return canonicalPath; 2979 } 2980 2981 String path = canonicalPath.toString(); 2982 2983 // First trim off any external storage prefix 2984 final UserEnvironment userEnv = new UserEnvironment(userId); 2985 2986 // /storage/emulated/0 2987 final String externalPath = userEnv.getExternalStorageDirectory().getAbsolutePath(); 2988 // /storage/emulated_legacy 2989 final String legacyExternalPath = Environment.getLegacyExternalStorageDirectory() 2990 .getAbsolutePath(); 2991 2992 if (path.startsWith(externalPath)) { 2993 path = path.substring(externalPath.length() + 1); 2994 } else if (path.startsWith(legacyExternalPath)) { 2995 path = path.substring(legacyExternalPath.length() + 1); 2996 } else { 2997 return canonicalPath; 2998 } 2999 3000 // Handle special OBB paths on emulated storage 3001 final String obbPath = "Android/obb"; 3002 if (path.startsWith(obbPath)) { 3003 path = path.substring(obbPath.length() + 1); 3004 3005 if (forVold) { 3006 return new File(Environment.getEmulatedStorageObbSource(), path).getAbsolutePath(); 3007 } else { 3008 final UserEnvironment ownerEnv = new UserEnvironment(UserHandle.USER_OWNER); 3009 return new File(ownerEnv.buildExternalStorageAndroidObbDirs()[0], path) 3010 .getAbsolutePath(); 3011 } 3012 } 3013 3014 // Handle normal external storage paths 3015 if (forVold) { 3016 return new File(Environment.getEmulatedStorageSource(userId), path).getAbsolutePath(); 3017 } else { 3018 return new File(userEnv.getExternalDirsForApp()[0], path).getAbsolutePath(); 3019 } 3020 } 3021 3022 @Override 3023 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 3024 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); 3025 3026 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ", 160); 3027 3028 synchronized (mObbMounts) { 3029 pw.println("mObbMounts:"); 3030 pw.increaseIndent(); 3031 final Iterator<Entry<IBinder, List<ObbState>>> binders = mObbMounts.entrySet() 3032 .iterator(); 3033 while (binders.hasNext()) { 3034 Entry<IBinder, List<ObbState>> e = binders.next(); 3035 pw.println(e.getKey() + ":"); 3036 pw.increaseIndent(); 3037 final List<ObbState> obbStates = e.getValue(); 3038 for (final ObbState obbState : obbStates) { 3039 pw.println(obbState); 3040 } 3041 pw.decreaseIndent(); 3042 } 3043 pw.decreaseIndent(); 3044 3045 pw.println(); 3046 pw.println("mObbPathToStateMap:"); 3047 pw.increaseIndent(); 3048 final Iterator<Entry<String, ObbState>> maps = mObbPathToStateMap.entrySet().iterator(); 3049 while (maps.hasNext()) { 3050 final Entry<String, ObbState> e = maps.next(); 3051 pw.print(e.getKey()); 3052 pw.print(" -> "); 3053 pw.println(e.getValue()); 3054 } 3055 pw.decreaseIndent(); 3056 } 3057 3058 synchronized (mVolumesLock) { 3059 pw.println(); 3060 pw.println("mVolumes:"); 3061 pw.increaseIndent(); 3062 for (StorageVolume volume : mVolumes) { 3063 pw.println(volume); 3064 pw.increaseIndent(); 3065 pw.println("Current state: " + mVolumeStates.get(volume.getPath())); 3066 pw.decreaseIndent(); 3067 } 3068 pw.decreaseIndent(); 3069 } 3070 3071 pw.println(); 3072 pw.println("mConnection:"); 3073 pw.increaseIndent(); 3074 mConnector.dump(fd, pw, args); 3075 pw.decreaseIndent(); 3076 } 3077 3078 /** {@inheritDoc} */ 3079 public void monitor() { 3080 if (mConnector != null) { 3081 mConnector.monitor(); 3082 } 3083 } 3084} 3085