diff options
| -rw-r--r-- | core/java/android/app/ContextImpl.java | 22 | ||||
| -rw-r--r-- | core/java/android/os/Environment.java | 64 | ||||
| -rw-r--r-- | core/java/android/os/storage/IMountService.java | 40 | ||||
| -rw-r--r-- | services/java/com/android/server/MountService.java | 82 |
4 files changed, 179 insertions, 29 deletions
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 8e9f3bb..0ba2ac5 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -92,6 +92,7 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.os.SystemVibrator; import android.os.UserManager; +import android.os.storage.IMountService; import android.os.storage.StorageManager; import android.print.IPrintManager; import android.print.PrintManager; @@ -864,7 +865,9 @@ class ContextImpl extends Context { if (mExternalObbDirs == null) { mExternalObbDirs = Environment.buildExternalStorageAppObbDirs(getPackageName()); } - return mExternalObbDirs; + + // Create dirs if needed + return ensureDirsExistOrFilter(mExternalObbDirs); } } @@ -2127,14 +2130,25 @@ class ContextImpl extends Context { * Ensure that given directories exist, trying to create them if missing. If * unable to create, they are filtered by replacing with {@code null}. */ - private static File[] ensureDirsExistOrFilter(File[] dirs) { + private File[] ensureDirsExistOrFilter(File[] dirs) { File[] result = new File[dirs.length]; for (int i = 0; i < dirs.length; i++) { File dir = dirs[i]; if (!dir.exists()) { if (!dir.mkdirs()) { - Log.w(TAG, "Failed to ensure directory: " + dir); - dir = null; + // Failing to mkdir() may be okay, since we might not have + // enough permissions; ask vold to create on our behalf. + final IMountService mount = IMountService.Stub.asInterface( + ServiceManager.getService("mount")); + int res = -1; + try { + res = mount.mkdirs(getPackageName(), dir.getAbsolutePath()); + } catch (RemoteException e) { + } + if (res != 0) { + Log.w(TAG, "Failed to ensure directory: " + dir); + dir = null; + } } } result[i] = dir; diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 5b36bca..fc53580 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -109,29 +109,36 @@ public class Environment { // TODO: generalize further to create package-specific environment // TODO: add support for secondary external storage - private final File[] mExternalDirs; - private final File mMediaDir; + /** External storage dirs, as visible to vold */ + private final File[] mExternalDirsForVold; + /** External storage dirs, as visible to apps */ + private final File[] mExternalDirsForApp; + /** Primary emulated storage dir for direct access */ + private final File mEmulatedDirForDirect; public UserEnvironment(int userId) { // See storage config details at http://source.android.com/tech/storage/ String rawExternalStorage = System.getenv(ENV_EXTERNAL_STORAGE); - String rawEmulatedStorageTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET); + String rawEmulatedSource = System.getenv(ENV_EMULATED_STORAGE_SOURCE); + String rawEmulatedTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET); String rawMediaStorage = System.getenv(ENV_MEDIA_STORAGE); if (TextUtils.isEmpty(rawMediaStorage)) { rawMediaStorage = "/data/media"; } - if (!TextUtils.isEmpty(rawEmulatedStorageTarget)) { + if (!TextUtils.isEmpty(rawEmulatedTarget)) { // Device has emulated storage; external storage paths should have // userId burned into them. final String rawUserId = Integer.toString(userId); - final File emulatedBase = new File(rawEmulatedStorageTarget); + final File emulatedSourceBase = new File(rawEmulatedSource); + final File emulatedTargetBase = new File(rawEmulatedTarget); final File mediaBase = new File(rawMediaStorage); // /storage/emulated/0 - mExternalDirs = new File[] { buildPath(emulatedBase, rawUserId) }; + mExternalDirsForVold = new File[] { buildPath(emulatedSourceBase, rawUserId) }; + mExternalDirsForApp = new File[] { buildPath(emulatedTargetBase, rawUserId) }; // /data/media/0 - mMediaDir = buildPath(mediaBase, rawUserId); + mEmulatedDirForDirect = buildPath(mediaBase, rawUserId); } else { // Device has physical external storage; use plain paths. @@ -141,15 +148,16 @@ public class Environment { } // /storage/sdcard0 - mExternalDirs = new File[] { new File(rawExternalStorage) }; + mExternalDirsForVold = new File[] { new File(rawExternalStorage) }; + mExternalDirsForApp = new File[] { new File(rawExternalStorage) }; // /data/media - mMediaDir = new File(rawMediaStorage); + mEmulatedDirForDirect = new File(rawMediaStorage); } } @Deprecated public File getExternalStorageDirectory() { - return mExternalDirs[0]; + return mExternalDirsForApp[0]; } @Deprecated @@ -157,44 +165,56 @@ public class Environment { return buildExternalStoragePublicDirs(type)[0]; } - public File[] getExternalDirs() { - return mExternalDirs; + public File[] getExternalDirsForVold() { + return mExternalDirsForVold; + } + + public File[] getExternalDirsForApp() { + return mExternalDirsForApp; } public File getMediaDir() { - return mMediaDir; + return mEmulatedDirForDirect; } public File[] buildExternalStoragePublicDirs(String type) { - return buildPaths(mExternalDirs, type); + return buildPaths(mExternalDirsForApp, type); } public File[] buildExternalStorageAndroidDataDirs() { - return buildPaths(mExternalDirs, DIR_ANDROID, DIR_DATA); + return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA); } public File[] buildExternalStorageAndroidObbDirs() { - return buildPaths(mExternalDirs, DIR_ANDROID, DIR_OBB); + return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB); } public File[] buildExternalStorageAppDataDirs(String packageName) { - return buildPaths(mExternalDirs, DIR_ANDROID, DIR_DATA, packageName); + return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName); + } + + public File[] buildExternalStorageAppDataDirsForVold(String packageName) { + return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_DATA, packageName); } public File[] buildExternalStorageAppMediaDirs(String packageName) { - return buildPaths(mExternalDirs, DIR_ANDROID, DIR_MEDIA, packageName); + return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_MEDIA, packageName); } public File[] buildExternalStorageAppObbDirs(String packageName) { - return buildPaths(mExternalDirs, DIR_ANDROID, DIR_OBB, packageName); + return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB, packageName); + } + + public File[] buildExternalStorageAppObbDirsForVold(String packageName) { + return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_OBB, packageName); } public File[] buildExternalStorageAppFilesDirs(String packageName) { - return buildPaths(mExternalDirs, DIR_ANDROID, DIR_DATA, packageName, DIR_FILES); + return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName, DIR_FILES); } public File[] buildExternalStorageAppCacheDirs(String packageName) { - return buildPaths(mExternalDirs, DIR_ANDROID, DIR_DATA, packageName, DIR_CACHE); + return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName, DIR_CACHE); } } @@ -344,7 +364,7 @@ public class Environment { */ public static File getExternalStorageDirectory() { throwIfUserRequired(); - return sCurrentUser.getExternalDirs()[0]; + return sCurrentUser.getExternalDirsForApp()[0]; } /** {@hide} */ diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java index fc18617..51ba2f6 100644 --- a/core/java/android/os/storage/IMountService.java +++ b/core/java/android/os/storage/IMountService.java @@ -20,9 +20,7 @@ import android.os.Binder; import android.os.IBinder; import android.os.IInterface; import android.os.Parcel; -import android.os.Parcelable; import android.os.RemoteException; -import android.os.storage.StorageVolume; /** * WARNING! Update IMountService.h and IMountService.cpp if you change this @@ -737,7 +735,25 @@ public interface IMountService extends IInterface { _data.recycle(); } return _result; + } + @Override + public int mkdirs(String callingPkg, String path) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + int _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(callingPkg); + _data.writeString(path); + mRemote.transact(Stub.TRANSACTION_mkdirs, _data, _reply, 0); + _reply.readException(); + _result = _reply.readInt(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; } } @@ -811,6 +827,8 @@ public interface IMountService extends IInterface { static final int TRANSACTION_fixPermissionsSecureContainer = IBinder.FIRST_CALL_TRANSACTION + 33; + static final int TRANSACTION_mkdirs = IBinder.FIRST_CALL_TRANSACTION + 34; + /** * Cast an IBinder object into an IMountService interface, generating a * proxy if needed. @@ -1154,6 +1172,15 @@ public interface IMountService extends IInterface { reply.writeInt(resultCode); return true; } + case TRANSACTION_mkdirs: { + data.enforceInterface(DESCRIPTOR); + String callingPkg = data.readString(); + String path = data.readString(); + int result = mkdirs(callingPkg, path); + reply.writeNoException(); + reply.writeInt(result); + return true; + } } return super.onTransact(code, data, reply, flags); } @@ -1376,4 +1403,13 @@ public interface IMountService extends IInterface { */ public int fixPermissionsSecureContainer(String id, int gid, String filename) throws RemoteException; + + /** + * Ensure that all directories along given path exist, creating parent + * directories as needed. Validates that given path is absolute and that it + * contains no relative "." or ".." paths or symlinks. Also ensures that + * path belongs to a volume managed by vold, and that path is either + * external storage data or OBB directory belonging to calling app. + */ + public int mkdirs(String callingPkg, String path) throws RemoteException; } diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java index 1facb80..6ab86f5 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -19,6 +19,7 @@ package com.android.server; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.Manifest; +import android.app.AppOpsManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -2128,6 +2129,85 @@ class MountService extends IMountService.Stub } @Override + public int mkdirs(String callingPkg, String appPath) { + final int userId = UserHandle.getUserId(Binder.getCallingUid()); + final UserEnvironment userEnv = new UserEnvironment(userId); + + // Validate that reported package name belongs to caller + final AppOpsManager appOps = (AppOpsManager) mContext.getSystemService( + Context.APP_OPS_SERVICE); + appOps.checkPackage(Binder.getCallingUid(), callingPkg); + + try { + appPath = new File(appPath).getCanonicalPath(); + } catch (IOException e) { + Slog.e(TAG, "Failed to resolve " + appPath + ": " + e); + return -1; + } + + // Try translating the app path into a vold path, but require that it + // belong to the calling package. + String voldPath = maybeTranslatePathForVold(appPath, + userEnv.buildExternalStorageAppDataDirs(callingPkg), + userEnv.buildExternalStorageAppDataDirsForVold(callingPkg)); + if (voldPath != null) { + try { + mConnector.execute("volume", "mkdirs", voldPath); + return 0; + } catch (NativeDaemonConnectorException e) { + return e.getCode(); + } + } + + voldPath = maybeTranslatePathForVold(appPath, + userEnv.buildExternalStorageAppObbDirs(callingPkg), + userEnv.buildExternalStorageAppObbDirsForVold(callingPkg)); + if (voldPath != null) { + try { + mConnector.execute("volume", "mkdirs", voldPath); + return 0; + } catch (NativeDaemonConnectorException e) { + return e.getCode(); + } + } + + throw new SecurityException("Invalid mkdirs path: " + appPath); + } + + /** + * Translate the given path from an app-visible path to a vold-visible path, + * but only if it's under the given whitelisted paths. + * + * @param path a canonicalized app-visible path. + * @param appPaths list of app-visible paths that are allowed. + * @param voldPaths list of vold-visible paths directly corresponding to the + * allowed app-visible paths argument. + * @return a vold-visible path representing the original path, or + * {@code null} if the given path didn't have an app-to-vold + * mapping. + */ + @VisibleForTesting + public static String maybeTranslatePathForVold( + String path, File[] appPaths, File[] voldPaths) { + if (appPaths.length != voldPaths.length) { + throw new IllegalStateException("Paths must be 1:1 mapping"); + } + + for (int i = 0; i < appPaths.length; i++) { + final String appPath = appPaths[i].getAbsolutePath(); + if (path.startsWith(appPath)) { + path = new File(voldPaths[i], path.substring(appPath.length() + 1)) + .getAbsolutePath(); + if (!path.endsWith("/")) { + path = path + "/"; + } + return path; + } + } + return null; + } + + @Override public StorageVolume[] getVolumeList() { final int callingUserId = UserHandle.getCallingUserId(); final boolean accessAll = (mContext.checkPermission( @@ -2651,7 +2731,7 @@ class MountService extends IMountService.Stub if (forVold) { return new File(Environment.getEmulatedStorageSource(userId), path).getAbsolutePath(); } else { - return new File(userEnv.getExternalDirs()[0], path).getAbsolutePath(); + return new File(userEnv.getExternalDirsForApp()[0], path).getAbsolutePath(); } } |
