Merge "Delegate mkdirs() to vold when lacking perms." into klp-dev
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.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 @@
             if (mExternalObbDirs == null) {
                 mExternalObbDirs = Environment.buildExternalStorageAppObbDirs(getPackageName());
             }
-            return mExternalObbDirs;
+
+            // Create dirs if needed
+            return ensureDirsExistOrFilter(mExternalObbDirs);
         }
     }
 
@@ -2127,14 +2130,25 @@
      * 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 @@
         // 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 @@
                 }
 
                 // /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 @@
             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 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.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 @@
                     _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 @@
 
         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 @@
                     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 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 @@
 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 @@
     }
 
     @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 @@
         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();
         }
     }