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