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