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