diff options
author | Amith Yamasani <yamasani@google.com> | 2011-04-19 10:41:20 -0700 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2011-04-19 10:41:20 -0700 |
commit | 25641ca1ac5b09727f86fe01389877332a00455d (patch) | |
tree | e18f66c852164f4fbc6fbdf3d7c689b9b2ac3d83 | |
parent | 08d1f937236230756bffde241ad6b335da368cf9 (diff) | |
parent | 0b285499db739ba50f2f839d633e763c70e67f96 (diff) | |
download | frameworks_base-25641ca1ac5b09727f86fe01389877332a00455d.zip frameworks_base-25641ca1ac5b09727f86fe01389877332a00455d.tar.gz frameworks_base-25641ca1ac5b09727f86fe01389877332a00455d.tar.bz2 |
Merge "Plumbing in PackageManager and installd for multi-user support."
15 files changed, 538 insertions, 74 deletions
diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c index 9c9c5c4..9aa70a4 100644 --- a/cmds/installd/commands.c +++ b/cmds/installd/commands.c @@ -78,15 +78,15 @@ int install(const char *pkgname, uid_t uid, gid_t gid) return 0; } -int uninstall(const char *pkgname) +int uninstall(const char *pkgname, uid_t persona) { char pkgdir[PKG_PATH_MAX]; - if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, 0)) + if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, persona)) return -1; - /* delete contents AND directory, no exceptions */ - return delete_dir_contents(pkgdir, 1, 0); + /* delete contents AND directory, no exceptions */ + return delete_dir_contents(pkgdir, 1, NULL); } int renamepkg(const char *oldpkgname, const char *newpkgname) @@ -106,17 +106,48 @@ int renamepkg(const char *oldpkgname, const char *newpkgname) return 0; } -int delete_user_data(const char *pkgname) +int delete_user_data(const char *pkgname, uid_t persona) { char pkgdir[PKG_PATH_MAX]; - if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, 0)) + if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, persona)) return -1; - /* delete contents, excluding "lib", but not the directory itself */ + /* delete contents, excluding "lib", but not the directory itself */ return delete_dir_contents(pkgdir, 0, "lib"); } +int make_user_data(const char *pkgname, uid_t uid, uid_t persona) +{ + char pkgdir[PKG_PATH_MAX]; + char real_libdir[PKG_PATH_MAX]; + + // Create the data dir for the package + if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, persona)) { + return -1; + } + if (mkdir(pkgdir, 0751) < 0) { + LOGE("cannot create dir '%s': %s\n", pkgdir, strerror(errno)); + return -errno; + } + if (chown(pkgdir, uid, uid) < 0) { + LOGE("cannot chown dir '%s': %s\n", pkgdir, strerror(errno)); + unlink(pkgdir); + return -errno; + } + return 0; +} + +int delete_persona(uid_t persona) +{ + char pkgdir[PKG_PATH_MAX]; + + if (create_persona_path(pkgdir, persona)) + return -1; + + return delete_dir_contents(pkgdir, 1, NULL); +} + int delete_cache(const char *pkgname) { char cachedir[PKG_PATH_MAX]; diff --git a/cmds/installd/installd.c b/cmds/installd/installd.c index e0d0f97..c062d36 100644 --- a/cmds/installd/installd.c +++ b/cmds/installd/installd.c @@ -49,7 +49,7 @@ static int do_rm_dex(char **arg, char reply[REPLY_MAX]) static int do_remove(char **arg, char reply[REPLY_MAX]) { - return uninstall(arg[0]); /* pkgname */ + return uninstall(arg[0], atoi(arg[1])); /* pkgname, userid */ } static int do_rename(char **arg, char reply[REPLY_MAX]) @@ -92,7 +92,17 @@ static int do_get_size(char **arg, char reply[REPLY_MAX]) static int do_rm_user_data(char **arg, char reply[REPLY_MAX]) { - return delete_user_data(arg[0]); /* pkgname */ + return delete_user_data(arg[0], atoi(arg[1])); /* pkgname, userid */ +} + +static int do_mk_user_data(char **arg, char reply[REPLY_MAX]) +{ + return make_user_data(arg[0], atoi(arg[1]), atoi(arg[2])); /* pkgname, uid, userid */ +} + +static int do_rm_user(char **arg, char reply[REPLY_MAX]) +{ + return delete_persona(atoi(arg[0])); /* userid */ } static int do_movefiles(char **arg, char reply[REPLY_MAX]) @@ -122,16 +132,18 @@ struct cmdinfo cmds[] = { { "dexopt", 3, do_dexopt }, { "movedex", 2, do_move_dex }, { "rmdex", 1, do_rm_dex }, - { "remove", 1, do_remove }, + { "remove", 2, do_remove }, { "rename", 2, do_rename }, { "freecache", 1, do_free_cache }, { "rmcache", 1, do_rm_cache }, { "protect", 2, do_protect }, { "getsize", 3, do_get_size }, - { "rmuserdata", 1, do_rm_user_data }, + { "rmuserdata", 2, do_rm_user_data }, { "movefiles", 0, do_movefiles }, { "linklib", 2, do_linklib }, { "unlinklib", 1, do_unlinklib }, + { "mkuserdata", 3, do_mk_user_data }, + { "rmuser", 1, do_rm_user }, }; static int readx(int s, void *_buf, int count) @@ -286,14 +298,50 @@ int initialize_globals() { return -1; } + // append "app/" to dirs[0] + char *system_app_path = build_string2(android_system_dirs.dirs[0].path, APP_SUBDIR); + android_system_dirs.dirs[0].path = system_app_path; + android_system_dirs.dirs[0].len = strlen(system_app_path); + // vendor // TODO replace this with an environment variable (doesn't exist yet) - android_system_dirs.dirs[1].path = "/vendor/"; + android_system_dirs.dirs[1].path = "/vendor/app/"; android_system_dirs.dirs[1].len = strlen(android_system_dirs.dirs[1].path); return 0; } +int initialize_directories() { + // /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, 0755) < 0) { + return -1; + } + if (chown(user_data_dir, AID_SYSTEM, AID_SYSTEM) < 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); + } + return ret; +} + int main(const int argc, const char *argv[]) { char buf[BUFFER_MAX]; struct sockaddr addr; @@ -305,6 +353,11 @@ int main(const int argc, const char *argv[]) { exit(1); } + if (initialize_directories() < 0) { + LOGE("Could not create directories; exiting.\n"); + exit(1); + } + lsocket = android_get_control_socket(SOCKET_PATH); if (lsocket < 0) { LOGE("Failed to get socket from environment: %s\n", strerror(errno)); diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h index cbca135..e5f6739 100644 --- a/cmds/installd/installd.h +++ b/cmds/installd/installd.h @@ -102,6 +102,9 @@ int create_pkg_path(char path[PKG_PATH_MAX], const char *postfix, uid_t persona); +int create_persona_path(char path[PKG_PATH_MAX], + uid_t persona); + int is_valid_package_name(const char* pkgname); int create_cache_path(char path[PKG_PATH_MAX], const char *src); @@ -124,12 +127,17 @@ int validate_apk_path(const char *path); int append_and_increment(char** dst, const char* src, size_t* dst_size); +char *build_string2(char *s1, char *s2); +char *build_string3(char *s1, char *s2, char *s3); + /* commands.c */ int install(const char *pkgname, uid_t uid, gid_t gid); -int uninstall(const char *pkgname); +int uninstall(const char *pkgname, uid_t persona); int renamepkg(const char *oldpkgname, const char *newpkgname); -int delete_user_data(const char *pkgname); +int delete_user_data(const char *pkgname, uid_t persona); +int make_user_data(const char *pkgname, uid_t uid, uid_t persona); +int delete_persona(uid_t persona); int delete_cache(const char *pkgname); int move_dex(const char *src, const char *dst); int rm_dex(const char *path); diff --git a/cmds/installd/utils.c b/cmds/installd/utils.c index f37a6fb..3099b83 100644 --- a/cmds/installd/utils.c +++ b/cmds/installd/utils.c @@ -96,6 +96,46 @@ int create_pkg_path(char path[PKG_PATH_MAX], } /** + * Create the path name for user data for a certain persona. + * Returns 0 on success, and -1 on failure. + */ +int create_persona_path(char path[PKG_PATH_MAX], + uid_t persona) +{ + size_t uid_len; + char* persona_prefix; + if (persona == 0) { + persona_prefix = PRIMARY_USER_PREFIX; + uid_len = 0; + } else { + persona_prefix = SECONDARY_USER_PREFIX; + uid_len = snprintf(NULL, 0, "%d", persona); + } + + char *dst = path; + size_t dst_size = PKG_PATH_MAX; + + if (append_and_increment(&dst, android_data_dir.path, &dst_size) < 0 + || append_and_increment(&dst, persona_prefix, &dst_size) < 0) { + LOGE("Error building prefix for user path"); + return -1; + } + + if (persona != 0) { + if (dst_size < uid_len + 1) { + LOGE("Error building user path"); + return -1; + } + int ret = snprintf(dst, dst_size, "%d", persona); + if (ret < 0 || (size_t) ret != uid_len) { + LOGE("Error appending persona id to path"); + return -1; + } + } + return 0; +} + +/** * Checks whether the package name is valid. Returns -1 on error and * 0 on success. */ @@ -408,3 +448,35 @@ int append_and_increment(char** dst, const char* src, size_t* dst_size) { *dst_size -= ret; return 0; } + +char *build_string2(char *s1, char *s2) { + if (s1 == NULL || s2 == NULL) return NULL; + + int len_s1 = strlen(s1); + int len_s2 = strlen(s2); + int len = len_s1 + len_s2 + 1; + char *result = malloc(len); + if (result == NULL) return NULL; + + strcpy(result, s1); + strcpy(result + len_s1, s2); + + return result; +} + +char *build_string3(char *s1, char *s2, char *s3) { + if (s1 == NULL || s2 == NULL || s3 == NULL) return NULL; + + int len_s1 = strlen(s1); + int len_s2 = strlen(s2); + int len_s3 = strlen(s3); + int len = len_s1 + len_s2 + len_s3 + 1; + char *result = malloc(len); + if (result == NULL) return NULL; + + strcpy(result, s1); + strcpy(result + len_s1, s2); + strcpy(result + len_s1 + len_s2, s3); + + return result; +} diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index d058e38..78a450c 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -35,9 +35,9 @@ import android.content.pm.PermissionInfo; import android.content.res.AssetManager; import android.content.res.Resources; import android.net.Uri; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; -import android.provider.Settings; import java.io.File; import java.lang.reflect.Field; @@ -60,6 +60,7 @@ public final class Pm { private static final String PM_NOT_RUNNING_ERR = "Error: Could not access the Package Manager. Is the system running?"; + private static final int ROOT_UID = 0; public static void main(String[] args) { new Pm().run(args); @@ -127,6 +128,16 @@ public final class Pm { return; } + if ("createUser".equals(op)) { + runCreateUser(); + return; + } + + if ("removeUser".equals(op)) { + runRemoveUser(); + return; + } + try { if (args.length == 1) { if (args[0].equalsIgnoreCase("-l")) { @@ -763,6 +774,63 @@ public final class Pm { } } + public void runCreateUser() { + // Need to be run as root + if (Process.myUid() != ROOT_UID) { + System.err.println("Error: createUser must be run as root"); + return; + } + String name; + String arg = nextArg(); + if (arg == null) { + System.err.println("Error: no user name specified."); + showUsage(); + return; + } + name = arg; + try { + if (mPm.createUser(name, 0) == null) { + System.err.println("Error: couldn't create user."); + showUsage(); + } + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + + } + + public void runRemoveUser() { + // Need to be run as root + if (Process.myUid() != ROOT_UID) { + System.err.println("Error: removeUser must be run as root"); + return; + } + int userId; + String arg = nextArg(); + if (arg == null) { + System.err.println("Error: no user id specified."); + showUsage(); + return; + } + try { + userId = Integer.parseInt(arg); + } catch (NumberFormatException e) { + System.err.println("Error: user id has to be a number."); + showUsage(); + return; + } + try { + if (!mPm.removeUser(userId)) { + System.err.println("Error: couldn't remove user."); + showUsage(); + } + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + } + class PackageDeleteObserver extends IPackageDeleteObserver.Stub { boolean finished; boolean result; @@ -1006,6 +1074,8 @@ public final class Pm { System.err.println(" pm enable PACKAGE_OR_COMPONENT"); System.err.println(" pm disable PACKAGE_OR_COMPONENT"); System.err.println(" pm setInstallLocation [0/auto] [1/internal] [2/external]"); + System.err.println(" pm createUser USER_NAME"); + System.err.println(" pm removeUser USER_ID"); System.err.println(""); System.err.println("The list packages command prints all packages, optionally only"); System.err.println("those whose package name contains the text in FILTER. Options:"); diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index ef8ba8e..85918cf 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -1113,7 +1113,11 @@ final class ApplicationPackageManager extends PackageManager { */ @Override public UserInfo createUser(String name, int flags) { - // TODO + try { + return mPM.createUser(name, flags); + } catch (RemoteException e) { + // Should never happen! + } return null; } @@ -1136,8 +1140,11 @@ final class ApplicationPackageManager extends PackageManager { */ @Override public boolean removeUser(int id) { - // TODO: - return false; + try { + return mPM.removeUser(id); + } catch (RemoteException e) { + return false; + } } /** diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index fbf8f92..11cd446 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -35,6 +35,7 @@ import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.pm.UserInfo; import android.net.Uri; import android.content.IntentSender; @@ -329,4 +330,7 @@ interface IPackageManager { boolean setInstallLocation(int loc); int getInstallLocation(); + + UserInfo createUser(in String name, int flags); + boolean removeUser(int userId); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 99c4c7f..ff817c1 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -662,10 +662,15 @@ public abstract class PackageManager { public static final int MOVE_EXTERNAL_MEDIA = 0x00000002; /** - * Feature for {@link #getSystemAvailableFeatures} and - * {@link #hasSystemFeature}: The device's audio pipeline is low-latency, - * more suitable for audio applications sensitive to delays or lag in - * sound input or output. + * Range of IDs allocated for a user. + * @hide + */ + public static final int PER_USER_RANGE = 100000; + + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device's + * audio pipeline is low-latency, more suitable for audio applications sensitive to delays or + * lag in sound input or output. */ @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_AUDIO_LOW_LATENCY = "android.hardware.audio.low_latency"; @@ -2387,4 +2392,37 @@ public abstract class PackageManager { * @hide */ public abstract void updateUserFlags(int id, int flags); + + /** + * Checks to see if the user id is the same for the two uids, i.e., they belong to the same + * user. + * @hide + */ + public static boolean isSameUser(int uid1, int uid2) { + return getUserId(uid1) == getUserId(uid2); + } + + /** + * Returns the user id for a given uid. + * @hide + */ + public static int getUserId(int uid) { + return uid / PER_USER_RANGE; + } + + /** + * Returns the uid that is composed from the userId and the appId. + * @hide + */ + public static int getUid(int userId, int appId) { + return userId * PER_USER_RANGE + (appId % PER_USER_RANGE); + } + + /** + * Returns the app id (or base uid) for a given uid, stripping out the user id from it. + * @hide + */ + public static int getAppId(int uid) { + return uid % PER_USER_RANGE; + } } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 54a8842..564f4f4 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -24,6 +24,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; +import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.PatternMatcher; diff --git a/core/java/android/content/pm/UserInfo.aidl b/core/java/android/content/pm/UserInfo.aidl new file mode 100644 index 0000000..2e7cb8f --- /dev/null +++ b/core/java/android/content/pm/UserInfo.aidl @@ -0,0 +1,20 @@ +/* +** +** Copyright 2011, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.content.pm; + +parcelable UserInfo; diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index 3704d3a..ba5331c 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -74,8 +74,7 @@ public class UserInfo implements Parcelable { @Override public String toString() { - return "UserInfo{" - + id + ":" + name + ":" + Integer.toHexString(flags) + "}"; + return "UserInfo{" + id + ":" + name + ":" + Integer.toHexString(flags) + "}"; } public int describeContents() { diff --git a/services/java/com/android/server/pm/Installer.java b/services/java/com/android/server/pm/Installer.java index da3ebaf..d10aa97 100644 --- a/services/java/com/android/server/pm/Installer.java +++ b/services/java/com/android/server/pm/Installer.java @@ -225,10 +225,12 @@ class Installer { return execute(builder.toString()); } - public int remove(String name) { + public int remove(String name, int userId) { StringBuilder builder = new StringBuilder("remove"); builder.append(' '); builder.append(name); + builder.append(' '); + builder.append(userId); return execute(builder.toString()); } @@ -248,10 +250,30 @@ class Installer { return execute(builder.toString()); } - public int clearUserData(String name) { + public int createUserData(String name, int uid, int userId) { + StringBuilder builder = new StringBuilder("mkuserdata"); + builder.append(' '); + builder.append(name); + builder.append(' '); + builder.append(uid); + builder.append(' '); + builder.append(userId); + return execute(builder.toString()); + } + + public int removeUserDataDirs(int userId) { + StringBuilder builder = new StringBuilder("rmuser"); + builder.append(' '); + builder.append(userId); + return execute(builder.toString()); + } + + public int clearUserData(String name, int userId) { StringBuilder builder = new StringBuilder("rmuserdata"); builder.append(' '); builder.append(name); + builder.append(' '); + builder.append(userId); return execute(builder.toString()); } diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index a9d49b4..6e1093f 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -65,6 +65,7 @@ import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.Signature; +import android.content.pm.UserInfo; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -208,6 +209,9 @@ public class PackageManagerService extends IPackageManager.Stub { // This is where all application persistent data goes. final File mAppDataDir; + // This is where all application persistent data goes for secondary users. + final File mUserAppDataDir; + // This is the object monitoring the framework dir. final FileObserver mFrameworkInstallObserver; @@ -359,6 +363,8 @@ public class PackageManagerService extends IPackageManager.Stub { // Delay time in millisecs static final int BROADCAST_DELAY = 10 * 1000; + final UserManager mUserManager; + final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection(); class DefaultContainerConnection implements ServiceConnection { @@ -797,8 +803,11 @@ public class PackageManagerService extends IPackageManager.Stub { File dataDir = Environment.getDataDirectory(); mAppDataDir = new File(dataDir, "data"); + mUserAppDataDir = new File(dataDir, "user"); mDrmAppPrivateInstallDir = new File(dataDir, "app-private"); + mUserManager = new UserManager(mInstaller, mUserAppDataDir); + if (mInstaller == null) { // Make sure these dirs exist, when we are running in // the simulator. @@ -806,6 +815,7 @@ public class PackageManagerService extends IPackageManager.Stub { File miscDir = new File(dataDir, "misc"); miscDir.mkdirs(); mAppDataDir.mkdirs(); + mUserAppDataDir.mkdirs(); mDrmAppPrivateInstallDir.mkdirs(); } @@ -974,7 +984,8 @@ public class PackageManagerService extends IPackageManager.Stub { + " no longer exists; wiping its data"; reportSettingsProblem(Log.WARN, msg); if (mInstaller != null) { - mInstaller.remove(ps.name); + mInstaller.remove(ps.name, 0); + mUserManager.removePackageForAllUsers(ps.name); } } } @@ -1059,10 +1070,12 @@ public class PackageManagerService extends IPackageManager.Stub { void cleanupInstallFailedPackage(PackageSetting ps) { Slog.i(TAG, "Cleaning up incompletely installed app: " + ps.name); if (mInstaller != null) { - int retCode = mInstaller.remove(ps.name); + int retCode = mInstaller.remove(ps.name, 0); if (retCode < 0) { Slog.w(TAG, "Couldn't remove app data directory for package: " + ps.name + ", retcode=" + retCode); + } else { + mUserManager.removePackageForAllUsers(ps.name); } } else { //for emulator @@ -1510,7 +1523,8 @@ public class PackageManagerService extends IPackageManager.Stub { ps.pkg.applicationInfo.flags = ps.pkgFlags; ps.pkg.applicationInfo.publicSourceDir = ps.resourcePathString; ps.pkg.applicationInfo.sourceDir = ps.codePathString; - ps.pkg.applicationInfo.dataDir = getDataPathForPackage(ps.pkg).getPath(); + ps.pkg.applicationInfo.dataDir = + getDataPathForPackage(ps.pkg.packageName, 0).getPath(); ps.pkg.applicationInfo.nativeLibraryDir = ps.nativeLibraryPathString; ps.pkg.mSetEnabled = ps.enabled; ps.pkg.mSetStopped = ps.stopped; @@ -2836,11 +2850,15 @@ public class PackageManagerService extends IPackageManager.Stub { return true; } - private File getDataPathForPackage(PackageParser.Package pkg) { - final File dataPath = new File(mAppDataDir, pkg.packageName); - return dataPath; + File getDataPathForUser(int userId) { + return new File(mUserAppDataDir.getAbsolutePath() + File.separator + userId); } - + + private File getDataPathForPackage(String packageName, int userId) { + return new File(mUserAppDataDir.getAbsolutePath() + File.separator + + userId + File.separator + packageName); + } + private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags, int scanMode, long currentTime) { File scanFile = new File(pkg.mScanPath); @@ -3162,7 +3180,7 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.dataDir = dataPath.getPath(); } else { // This is a normal package, need to make its data directory. - dataPath = getDataPathForPackage(pkg); + dataPath = getDataPathForPackage(pkg.packageName, 0); boolean uidError = false; @@ -3178,8 +3196,11 @@ public class PackageManagerService extends IPackageManager.Stub { // If this is a system app, we can at least delete its // current data so the application will still work. if (mInstaller != null) { - int ret = mInstaller.remove(pkgName); + int ret = mInstaller.remove(pkgName, 0); if (ret >= 0) { + // TODO: Kill the processes first + // Remove the data directories for all users + mUserManager.removePackageForAllUsers(pkgName); // Old data gone! String msg = "System package " + pkg.packageName + " has changed from uid: " @@ -3199,6 +3220,9 @@ public class PackageManagerService extends IPackageManager.Stub { mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; return null; } + // Create data directories for all users + mUserManager.installPackageForAllUsers(pkgName, + pkg.applicationInfo.uid); } } if (!recovered) { @@ -3235,11 +3259,13 @@ public class PackageManagerService extends IPackageManager.Stub { if (mInstaller != null) { int ret = mInstaller.install(pkgName, pkg.applicationInfo.uid, pkg.applicationInfo.uid); - if(ret < 0) { + if (ret < 0) { // Error from installer mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; return null; } + // Create data directories for all users + mUserManager.installPackageForAllUsers(pkgName, pkg.applicationInfo.uid); } else { dataPath.mkdirs(); if (dataPath.exists()) { @@ -5703,7 +5729,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Remember this for later, in case we need to rollback this install String pkgName = pkg.packageName; - boolean dataDirExists = getDataPathForPackage(pkg).exists(); + boolean dataDirExists = getDataPathForPackage(pkg.packageName, 0).exists(); res.name = pkgName; synchronized(mPackages) { if (mSettings.mRenamedPackages.containsKey(pkgName)) { @@ -6390,11 +6416,14 @@ public class PackageManagerService extends IPackageManager.Stub { } if ((flags&PackageManager.DONT_DELETE_DATA) == 0) { if (mInstaller != null) { - int retCode = mInstaller.remove(packageName); + int retCode = mInstaller.remove(packageName, 0); if (retCode < 0) { Slog.w(TAG, "Couldn't remove app data or cache directory for package: " + packageName + ", retcode=" + retCode); // we don't consider this to be a failure of the core package deletion + } else { + // TODO: Kill the processes first + mUserManager.removePackageForAllUsers(packageName); } } else { // for simulator @@ -6654,7 +6683,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } if (mInstaller != null) { - int retCode = mInstaller.clearUserData(packageName); + int retCode = mInstaller.clearUserData(packageName, 0); // TODO - correct userId if (retCode < 0) { Slog.w(TAG, "Couldn't remove cache files for package: " + packageName); @@ -8015,4 +8044,17 @@ public class PackageManagerService extends IPackageManager.Stub { android.provider.Settings.Secure.DEFAULT_INSTALL_LOCATION, PackageHelper.APP_INSTALL_AUTO); } + + public UserInfo createUser(String name, int flags) { + UserInfo userInfo = mUserManager.createUser(name, flags, getInstalledApplications(0)); + return userInfo; + } + + public boolean removeUser(int userId) { + if (userId == 0) { + return false; + } + mUserManager.removeUser(userId); + return true; + } } diff --git a/services/java/com/android/server/pm/UserDetails.java b/services/java/com/android/server/pm/UserManager.java index 2aeed7c..76fa5ab 100644 --- a/services/java/com/android/server/pm/UserDetails.java +++ b/services/java/com/android/server/pm/UserManager.java @@ -18,9 +18,13 @@ package com.android.server.pm; import com.android.internal.util.FastXmlSerializer; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.os.Environment; import android.os.FileUtils; +import android.os.SystemClock; +import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; @@ -37,7 +41,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; -public class UserDetails { +public class UserManager { private static final String TAG_NAME = "name"; private static final String ATTR_FLAGS = "flags"; @@ -48,22 +52,27 @@ public class UserDetails { private static final String TAG_USER = "user"; - private static final String TAG = "UserDetails"; + private static final String LOG_TAG = "UserManager"; - private static final String USER_INFO_DIR = "system/users"; + private static final String USER_INFO_DIR = "system" + File.separator + "users"; private static final String USER_LIST_FILENAME = "userlist.xml"; private SparseArray<UserInfo> mUsers; private final File mUsersDir; private final File mUserListFile; + private int[] mUserIds; + + private Installer mInstaller; + private File mBaseUserPath; /** * Available for testing purposes. */ - UserDetails(File dataDir) { + UserManager(File dataDir, File baseUserPath) { mUsersDir = new File(dataDir, USER_INFO_DIR); mUsersDir.mkdirs(); + mBaseUserPath = baseUserPath; FileUtils.setPermissions(mUsersDir.toString(), FileUtils.S_IRWXU|FileUtils.S_IRWXG |FileUtils.S_IROTH|FileUtils.S_IXOTH, @@ -72,8 +81,9 @@ public class UserDetails { readUserList(); } - public UserDetails() { - this(Environment.getDataDirectory()); + public UserManager(Installer installer, File baseUserPath) { + this(Environment.getDataDirectory(), baseUserPath); + mInstaller = installer; } public List<UserInfo> getUsers() { @@ -84,6 +94,15 @@ public class UserDetails { return users; } + /** + * Returns an array of user ids. This array is cached here for quick access, so do not modify or + * cache it elsewhere. + * @return the array of user ids. + */ + int[] getUserIds() { + return mUserIds; + } + private void readUserList() { mUsers = new SparseArray<UserInfo>(); if (!mUserListFile.exists()) { @@ -102,7 +121,7 @@ public class UserDetails { } if (type != XmlPullParser.START_TAG) { - Slog.e(TAG, "Unable to read user list"); + Slog.e(LOG_TAG, "Unable to read user list"); fallbackToSingleUser(); return; } @@ -116,6 +135,7 @@ public class UserDetails { } } } + updateUserIds(); } catch (IOException ioe) { fallbackToSingleUser(); } catch (XmlPullParserException pe) { @@ -128,6 +148,7 @@ public class UserDetails { UserInfo primary = new UserInfo(0, "Primary", UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY); mUsers.put(0, primary); + updateUserIds(); writeUserList(); writeUser(primary); @@ -164,7 +185,7 @@ public class UserDetails { serializer.endDocument(); } catch (IOException ioe) { - Slog.e(TAG, "Error writing user info " + userInfo.id + "\n" + ioe); + Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe); } } @@ -194,14 +215,13 @@ public class UserDetails { serializer.startTag(null, TAG_USER); serializer.attribute(null, ATTR_ID, Integer.toString(user.id)); serializer.endTag(null, TAG_USER); - Slog.e(TAG, "Wrote user " + user.id + " to userlist.xml"); } serializer.endTag(null, TAG_USERS); serializer.endDocument(); } catch (IOException ioe) { - Slog.e(TAG, "Error writing user list"); + Slog.e(LOG_TAG, "Error writing user list"); } } @@ -222,14 +242,14 @@ public class UserDetails { } if (type != XmlPullParser.START_TAG) { - Slog.e(TAG, "Unable to read user " + id); + Slog.e(LOG_TAG, "Unable to read user " + id); return null; } if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) { String storedId = parser.getAttributeValue(null, ATTR_ID); if (Integer.parseInt(storedId) != id) { - Slog.e(TAG, "User id does not match the file name"); + Slog.e(LOG_TAG, "User id does not match the file name"); return null; } String flagString = parser.getAttributeValue(null, ATTR_FLAGS); @@ -256,18 +276,25 @@ public class UserDetails { return null; } - public UserInfo createUser(String name, int flags) { - int id = getNextAvailableId(); - UserInfo userInfo = new UserInfo(id, name, flags); - if (!createPackageFolders(id)) { + public UserInfo createUser(String name, int flags, List<ApplicationInfo> apps) { + int userId = getNextAvailableId(); + UserInfo userInfo = new UserInfo(userId, name, flags); + File userPath = new File(mBaseUserPath, Integer.toString(userId)); + if (!createPackageFolders(userId, userPath, apps)) { return null; } - mUsers.put(id, userInfo); + mUsers.put(userId, userInfo); writeUserList(); writeUser(userInfo); + updateUserIds(); return userInfo; } + /** + * Removes a user and all data directories created for that user. This method should be called + * after the user's processes have been terminated. + * @param id the user's id + */ public void removeUser(int id) { // Remove from the list UserInfo userInfo = mUsers.get(id); @@ -277,11 +304,58 @@ public class UserDetails { // Remove user file File userFile = new File(mUsersDir, id + ".xml"); userFile.delete(); + // Update the user list writeUserList(); + // Remove the data directories for all packages for this user removePackageFolders(id); + updateUserIds(); + } + } + + public void installPackageForAllUsers(String packageName, int uid) { + for (int userId : mUserIds) { + // Don't do it for the primary user, it will become recursive. + if (userId == 0) + continue; + mInstaller.createUserData(packageName, PackageManager.getUid(userId, uid), + userId); + } + } + + public void clearUserDataForAllUsers(String packageName) { + for (int userId : mUserIds) { + // Don't do it for the primary user, it will become recursive. + if (userId == 0) + continue; + mInstaller.clearUserData(packageName, userId); + } + } + + public void removePackageForAllUsers(String packageName) { + for (int userId : mUserIds) { + // Don't do it for the primary user, it will become recursive. + if (userId == 0) + continue; + mInstaller.remove(packageName, userId); + } + } + + /** + * Caches the list of user ids in an array, adjusting the array size when necessary. + */ + private void updateUserIds() { + if (mUserIds == null || mUserIds.length != mUsers.size()) { + mUserIds = new int[mUsers.size()]; + } + for (int i = 0; i < mUsers.size(); i++) { + mUserIds[i] = mUsers.keyAt(i); } } + /** + * Returns the next available user id, filling in any holes in the ids. + * @return + */ private int getNextAvailableId() { int i = 0; while (i < Integer.MAX_VALUE) { @@ -293,13 +367,35 @@ public class UserDetails { return i; } - private boolean createPackageFolders(int id) { - // TODO: Create data directories for all the packages for a new user, w/ specified user id. + private boolean createPackageFolders(int id, File userPath, final List<ApplicationInfo> apps) { + // mInstaller may not be available for unit-tests. + if (mInstaller == null || apps == null) return true; + + final long startTime = SystemClock.elapsedRealtime(); + // Create the user path + userPath.mkdir(); + FileUtils.setPermissions(userPath.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG + | FileUtils.S_IXOTH, -1, -1); + + // Create the individual data directories + for (ApplicationInfo app : apps) { + if (app.uid > android.os.Process.FIRST_APPLICATION_UID + && app.uid < PackageManager.PER_USER_RANGE) { + mInstaller.createUserData(app.packageName, + PackageManager.getUid(id, app.uid), id); + } + } + final long stopTime = SystemClock.elapsedRealtime(); + Log.i(LOG_TAG, + "Time to create " + apps.size() + " packages = " + (stopTime - startTime) + "ms"); return true; } private boolean removePackageFolders(int id) { - // TODO: Remove all the data directories for the specified user. + // mInstaller may not be available for unit-tests. + if (mInstaller == null) return true; + + mInstaller.removeUserDataDirs(id); return true; } } diff --git a/services/tests/servicestests/src/com/android/server/pm/UserDetailsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index 7b77aac..e8188e7 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserDetailsTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -16,7 +16,7 @@ package com.android.server.pm; -import com.android.server.pm.UserDetails; +import com.android.server.pm.UserManager; import android.content.pm.UserInfo; import android.os.Debug; @@ -25,23 +25,24 @@ import android.test.AndroidTestCase; import java.util.List; -/** Test {@link UserDetails} functionality. */ -public class UserDetailsTest extends AndroidTestCase { +/** Test {@link UserManager} functionality. */ +public class UserManagerTest extends AndroidTestCase { - UserDetails mDetails = null; + UserManager mUserManager = null; @Override public void setUp() throws Exception { - mDetails = new UserDetails(Environment.getExternalStorageDirectory()); + mUserManager = new UserManager(Environment.getExternalStorageDirectory(), + Environment.getExternalStorageDirectory()); } @Override public void tearDown() throws Exception { - List<UserInfo> users = mDetails.getUsers(); + List<UserInfo> users = mUserManager.getUsers(); // Remove all except the primary user for (UserInfo user : users) { if (!user.isPrimary()) { - mDetails.removeUser(user.id); + mUserManager.removeUser(user.id); } } } @@ -51,9 +52,9 @@ public class UserDetailsTest extends AndroidTestCase { } public void testAddUser() throws Exception { - final UserDetails details = mDetails; + final UserManager details = mUserManager; - UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST); + UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST, null); assertTrue(userInfo != null); List<UserInfo> list = details.getUsers(); @@ -70,10 +71,10 @@ public class UserDetailsTest extends AndroidTestCase { } public void testAdd2Users() throws Exception { - final UserDetails details = mDetails; + final UserManager details = mUserManager; - UserInfo user1 = details.createUser("Guest 1", UserInfo.FLAG_GUEST); - UserInfo user2 = details.createUser("User 2", UserInfo.FLAG_ADMIN); + UserInfo user1 = details.createUser("Guest 1", UserInfo.FLAG_GUEST, null); + UserInfo user2 = details.createUser("User 2", UserInfo.FLAG_ADMIN, null); assertTrue(user1 != null); assertTrue(user2 != null); @@ -84,9 +85,9 @@ public class UserDetailsTest extends AndroidTestCase { } public void testRemoveUser() throws Exception { - final UserDetails details = mDetails; + final UserManager details = mUserManager; - UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST); + UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST, null); details.removeUser(userInfo.id); @@ -94,7 +95,7 @@ public class UserDetailsTest extends AndroidTestCase { } private boolean findUser(int id) { - List<UserInfo> list = mDetails.getUsers(); + List<UserInfo> list = mUserManager.getUsers(); for (UserInfo user : list) { if (user.id == id) { |