Multi-user external storage support.

Emulated external storage always has multi-user support using paths
like "/data/media/<user_id>".  Creates and destroys these paths along
with user data.  Uses new ensure_dir() to create directories while
always ensuring permissions.

Add external storage mount mode to zygote, supporting both single-
and multi-user devices.  For example, devices with physical SD cards
are treated as single-user.  Begin migrating to mount mode instead
of relying on sdcard_r GID to enforce READ_EXTERNAL_STORAGE.

Bug: 6925012
Change-Id: I9b872ded992cd078e2c013567d59f9f0032ec02b
diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c
index d94daf7..0a7cb2d 100644
--- a/cmds/installd/commands.c
+++ b/cmds/installd/commands.c
@@ -213,18 +213,30 @@
 
 int delete_persona(uid_t persona)
 {
-    char pkgdir[PKG_PATH_MAX];
-
-    if (create_persona_path(pkgdir, persona))
+    char data_path[PKG_PATH_MAX];
+    if (create_persona_path(data_path, persona)) {
         return -1;
+    }
+    if (delete_dir_contents(data_path, 1, NULL)) {
+        return -1;
+    }
 
-    return delete_dir_contents(pkgdir, 1, NULL);
+    char media_path[PATH_MAX];
+    if (create_persona_media_path(media_path, (userid_t) persona) == -1) {
+        return -1;
+    }
+    if (delete_dir_contents(media_path, 1, NULL) == -1) {
+        return -1;
+    }
+
+    return 0;
 }
 
 int clone_persona_data(uid_t src_persona, uid_t target_persona, int copy)
 {
     char src_data_dir[PKG_PATH_MAX];
     char pkg_path[PKG_PATH_MAX];
+    char media_path[PATH_MAX];
     DIR *d;
     struct dirent *de;
     struct stat s;
@@ -233,6 +245,9 @@
     if (create_persona_path(src_data_dir, src_persona)) {
         return -1;
     }
+    if (create_persona_media_path(media_path, (userid_t) target_persona) == -1) {
+        return -1;
+    }
 
     d = opendir(src_data_dir);
     if (d != NULL) {
@@ -260,6 +275,11 @@
         }
         closedir(d);
     }
+
+    // ensure /data/media/<user_id> exists
+    if (ensure_dir(media_path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) {
+        return -1;
+    }
     return 0;
 }
 
diff --git a/cmds/installd/installd.c b/cmds/installd/installd.c
index 89c059e..56e1e16 100644
--- a/cmds/installd/installd.c
+++ b/cmds/installd/installd.c
@@ -331,37 +331,109 @@
 }
 
 int initialize_directories() {
+    int res = -1;
+    int version = 0;
+    FILE* file;
+
+    // Read current filesystem layout version to handle upgrade paths
+    char version_path[PATH_MAX];
+    if (snprintf(version_path, PATH_MAX, "%s.layout_version", android_data_dir.path) > PATH_MAX) {
+        return -1;
+    }
+    file = fopen(version_path, "r");
+    if (file != NULL) {
+        fscanf(file, "%d", &version);
+        fclose(file);
+    }
+
     // /data/user
     char *user_data_dir = build_string2(android_data_dir.path, SECONDARY_USER_PREFIX);
     // /data/data
     char *legacy_data_dir = build_string2(android_data_dir.path, PRIMARY_USER_PREFIX);
     // /data/user/0
-    char *primary_data_dir = build_string3(android_data_dir.path, SECONDARY_USER_PREFIX,
-            "0");
-    int ret = -1;
-    if (user_data_dir != NULL && primary_data_dir != NULL && legacy_data_dir != NULL) {
-        ret = 0;
-        // Make the /data/user directory if necessary
-        if (access(user_data_dir, R_OK) < 0) {
-            if (mkdir(user_data_dir, 0711) < 0) {
-                return -1;
-            }
-            if (chown(user_data_dir, AID_SYSTEM, AID_SYSTEM) < 0) {
-                return -1;
-            }
-            if (chmod(user_data_dir, 0711) < 0) {
-                return -1;
-            }
-        }
-        // Make the /data/user/0 symlink to /data/data if necessary
-        if (access(primary_data_dir, R_OK) < 0) {
-              ret = symlink(legacy_data_dir, primary_data_dir);
-        }
-        free(user_data_dir);
-        free(legacy_data_dir);
-        free(primary_data_dir);
+    char *primary_data_dir = build_string3(android_data_dir.path, SECONDARY_USER_PREFIX, "0");
+    if (!user_data_dir || !legacy_data_dir || !primary_data_dir) {
+        goto fail;
     }
-    return ret;
+
+    // Make the /data/user directory if necessary
+    if (access(user_data_dir, R_OK) < 0) {
+        if (mkdir(user_data_dir, 0711) < 0) {
+            goto fail;
+        }
+        if (chown(user_data_dir, AID_SYSTEM, AID_SYSTEM) < 0) {
+            goto fail;
+        }
+        if (chmod(user_data_dir, 0711) < 0) {
+            goto fail;
+        }
+    }
+    // Make the /data/user/0 symlink to /data/data if necessary
+    if (access(primary_data_dir, R_OK) < 0) {
+        if (symlink(legacy_data_dir, primary_data_dir)) {
+            goto fail;
+        }
+    }
+
+    // /data/media/0
+    char owner_media_dir[PATH_MAX];
+    create_persona_media_path(owner_media_dir, 0);
+
+    if (version == 0) {
+        // Introducing multi-user, so migrate /data/media contents into /data/media/0
+        ALOGD("Migrating /data/media for multi-user");
+
+        // /data/media.tmp
+        char media_tmp_dir[PATH_MAX];
+        snprintf(media_tmp_dir, PATH_MAX, "%smedia.tmp", android_data_dir.path);
+
+        // Only copy when upgrade not already in progress
+        if (access(media_tmp_dir, F_OK) == -1) {
+            if (rename(android_media_dir.path, media_tmp_dir) == -1) {
+                ALOGE("Failed to move legacy media path: %s", strerror(errno));
+                goto fail;
+            }
+        }
+
+        // Create /data/media again
+        if (ensure_dir(android_media_dir.path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) {
+            goto fail;
+        }
+
+        // Move any owner data into place
+        if (access(media_tmp_dir, F_OK) == 0) {
+            if (rename(media_tmp_dir, owner_media_dir) == -1) {
+                ALOGE("Failed to move owner media path: %s", strerror(errno));
+                goto fail;
+            }
+        }
+        version = 1;
+    }
+
+    // Ensure /data/media/0 is always ready
+    if (ensure_dir(owner_media_dir, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) {
+        goto fail;
+    }
+
+    // Persist our current version
+    file = fopen(version_path, "w");
+    if (file != NULL) {
+        fprintf(file, "%d", version);
+        fsync(fileno(file));
+        fclose(file);
+    } else {
+        ALOGE("Failed to save version to %s: %s", version_path, strerror(errno));
+        goto fail;
+    }
+
+    // Success!
+    res = 0;
+
+fail:
+    free(user_data_dir);
+    free(legacy_data_dir);
+    free(primary_data_dir);
+    return res;
 }
 
 int main(const int argc, const char *argv[]) {
diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h
index f5853ff..f5485d2 100644
--- a/cmds/installd/installd.h
+++ b/cmds/installd/installd.h
@@ -35,6 +35,7 @@
 #include <cutils/sockets.h>
 #include <cutils/log.h>
 #include <cutils/properties.h>
+#include <cutils/multiuser.h>
 
 #include <private/android_filesystem_config.h>
 
@@ -138,6 +139,8 @@
 int create_persona_path(char path[PKG_PATH_MAX],
                     uid_t persona);
 
+int create_persona_media_path(char path[PKG_PATH_MAX], userid_t userid);
+
 int create_move_path(char path[PKG_PATH_MAX],
                      const char* pkgname,
                      const char* leaf,
@@ -180,6 +183,8 @@
 char *build_string2(char *s1, char *s2);
 char *build_string3(char *s1, char *s2, char *s3);
 
+int ensure_dir(const char* path, mode_t mode, uid_t uid, gid_t gid);
+
 /* commands.c */
 
 int install(const char *pkgname, uid_t uid, gid_t gid);
diff --git a/cmds/installd/utils.c b/cmds/installd/utils.c
index 79db972..80247f1 100644
--- a/cmds/installd/utils.c
+++ b/cmds/installd/utils.c
@@ -137,6 +137,17 @@
     return 0;
 }
 
+/**
+ * Create the path name for media for a certain persona.
+ * Returns 0 on success, and -1 on failure.
+ */
+int create_persona_media_path(char path[PATH_MAX], userid_t userid) {
+    if (snprintf(path, PATH_MAX, "%s%d", android_media_dir.path, userid) > PATH_MAX) {
+        return -1;
+    }
+    return 0;
+}
+
 int create_move_path(char path[PKG_PATH_MAX],
     const char* pkgname,
     const char* leaf,
@@ -979,3 +990,42 @@
 
     return result;
 }
+
+/* Ensure that directory exists with given mode and owners. */
+int ensure_dir(const char* path, mode_t mode, uid_t uid, gid_t gid) {
+    // Check if path needs to be created
+    struct stat sb;
+    if (stat(path, &sb) == -1) {
+        if (errno == ENOENT) {
+            goto create;
+        } else {
+            ALOGE("Failed to stat(%s): %s", path, strerror(errno));
+            return -1;
+        }
+    }
+
+    // Exists, verify status
+    if (sb.st_mode == mode || sb.st_uid == uid || sb.st_gid == gid) {
+        return 0;
+    } else {
+        goto fixup;
+    }
+
+create:
+    if (mkdir(path, mode) == -1) {
+        ALOGE("Failed to mkdir(%s): %s", path, strerror(errno));
+        return -1;
+    }
+
+fixup:
+    if (chown(path, uid, gid) == -1) {
+        ALOGE("Failed to chown(%s, %d, %d): %s", path, uid, gid, strerror(errno));
+        return -1;
+    }
+    if (chmod(path, mode) == -1) {
+        ALOGE("Failed to chown(%s, %d): %s", path, mode, strerror(errno));
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 93860aa..a3b665a 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -376,12 +376,13 @@
     public static final ProcessStartResult start(final String processClass,
                                   final String niceName,
                                   int uid, int gid, int[] gids,
-                                  int debugFlags, int targetSdkVersion,
+                                  int debugFlags, int mountExternal,
+                                  int targetSdkVersion,
                                   String seInfo,
                                   String[] zygoteArgs) {
         try {
             return startViaZygote(processClass, niceName, uid, gid, gids,
-                    debugFlags, targetSdkVersion, seInfo, zygoteArgs);
+                    debugFlags, mountExternal, targetSdkVersion, seInfo, zygoteArgs);
         } catch (ZygoteStartFailedEx ex) {
             Log.e(LOG_TAG,
                     "Starting VM process through Zygote failed");
@@ -553,7 +554,8 @@
                                   final String niceName,
                                   final int uid, final int gid,
                                   final int[] gids,
-                                  int debugFlags, int targetSdkVersion,
+                                  int debugFlags, int mountExternal,
+                                  int targetSdkVersion,
                                   String seInfo,
                                   String[] extraArgs)
                                   throws ZygoteStartFailedEx {
@@ -580,6 +582,11 @@
             if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) {
                 argsForZygote.add("--enable-assert");
             }
+            if (mountExternal == Zygote.MOUNT_EXTERNAL_SINGLEUSER) {
+                argsForZygote.add("--mount-external-singleuser");
+            } else if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER) {
+                argsForZygote.add("--mount-external-multiuser");
+            }
             argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
 
             //TODO optionally enable debuger
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index b016e99..1e268c4 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -18,16 +18,14 @@
 
 import android.net.Credentials;
 import android.net.LocalSocket;
-import android.os.Build;
 import android.os.Process;
+import android.os.SELinux;
 import android.os.SystemProperties;
 import android.util.Log;
 
 import dalvik.system.PathClassLoader;
 import dalvik.system.Zygote;
 
-import android.os.SELinux;
-
 import java.io.BufferedReader;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -234,9 +232,9 @@
                 ZygoteInit.setCloseOnExec(serverPipeFd, true);
             }
 
-            pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid,
-                    parsedArgs.gids, parsedArgs.debugFlags, rlimits,
-                    parsedArgs.seInfo, parsedArgs.niceName);
+            pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
+                    parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
+                    parsedArgs.niceName);
         } catch (IOException ex) {
             logAndPrintError(newStderr, "Exception creating pipe", ex);
         } catch (ErrnoException ex) {
@@ -341,6 +339,9 @@
          */
         int debugFlags;
 
+        /** From --mount-external */
+        int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
+
         /** from --target-sdk-version. */
         int targetSdkVersion;
         boolean targetSdkVersionSpecified;
@@ -526,6 +527,10 @@
                                 "Duplicate arg specified");
                     }
                     niceName = arg.substring(arg.indexOf('=') + 1);
+                } else if (arg.equals("--mount-external-singleuser")) {
+                    mountExternal = Zygote.MOUNT_EXTERNAL_SINGLEUSER;
+                } else if (arg.equals("--mount-external-multiuser")) {
+                    mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER;
                 } else {
                     break;
                 }
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index d3ec9f7..9931bdf 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -1968,10 +1968,20 @@
             int uid = app.uid;
 
             int[] gids = null;
+            int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
             if (!app.isolated) {
                 try {
-                    gids = mContext.getPackageManager().getPackageGids(
-                            app.info.packageName);
+                    final PackageManager pm = mContext.getPackageManager();
+                    gids = pm.getPackageGids(app.info.packageName);
+                    if (pm.checkPermission(
+                            android.Manifest.permission.READ_EXTERNAL_STORAGE, app.info.packageName)
+                            == PERMISSION_GRANTED) {
+                        if (Environment.isExternalStorageEmulated()) {
+                            mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER;
+                        } else {
+                            mountExternal = Zygote.MOUNT_EXTERNAL_SINGLEUSER;
+                        }
+                    }
                 } catch (PackageManager.NameNotFoundException e) {
                     Slog.w(TAG, "Unable to retrieve gids", e);
                 }
@@ -2013,7 +2023,7 @@
             // Start the process.  It will either succeed and return a result containing
             // the PID of the new process, or else throw a RuntimeException.
             Process.ProcessStartResult startResult = Process.start("android.app.ActivityThread",
-                    app.processName, uid, uid, gids, debugFlags,
+                    app.processName, uid, uid, gids, debugFlags, mountExternal,
                     app.info.targetSdkVersion, null, null);
 
             BatteryStatsImpl bs = app.batteryStats.getBatteryStats();