Merge "Fix handle_opendir() in the sdcard daemon" into klp-dev
diff --git a/healthd/Android.mk b/healthd/Android.mk
index cff7a3b..473d375 100644
--- a/healthd/Android.mk
+++ b/healthd/Android.mk
@@ -3,6 +3,12 @@
 ifneq ($(BUILD_TINY_ANDROID),true)
 
 LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := healthd_board_default.cpp
+LOCAL_MODULE := libhealthd.default
+include $(BUILD_STATIC_LIBRARY)
+
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := \
@@ -17,12 +23,7 @@
 LOCAL_UNSTRIPPED_PATH := $(TARGET_ROOT_OUT_SBIN_UNSTRIPPED)
 
 LOCAL_STATIC_LIBRARIES :=  libbatteryservice libbinder libz libutils libstdc++ libcutils liblog libm libc
-
-ifdef BOARD_LIB_HEALTHD
-LOCAL_STATIC_LIBRARIES += $(BOARD_LIB_HEALTHD)
-else
-LOCAL_SRC_FILES += healthd_board_default.cpp
-endif
+LOCAL_HAL_STATIC_LIBRARIES := libhealthd
 
 include $(BUILD_EXECUTABLE)
 
diff --git a/healthd/BatteryMonitor.cpp b/healthd/BatteryMonitor.cpp
index 8492dce..688c7ff 100644
--- a/healthd/BatteryMonitor.cpp
+++ b/healthd/BatteryMonitor.cpp
@@ -181,33 +181,33 @@
     props.batteryCurrentNow = INT_MIN;
     props.batteryChargeCounter = INT_MIN;
 
-    if (!mBatteryPresentPath.isEmpty())
-        props.batteryPresent = getBooleanField(mBatteryPresentPath);
+    if (!mHealthdConfig->batteryPresentPath.isEmpty())
+        props.batteryPresent = getBooleanField(mHealthdConfig->batteryPresentPath);
     else
         props.batteryPresent = true;
 
-    props.batteryLevel = getIntField(mBatteryCapacityPath);
-    props.batteryVoltage = getIntField(mBatteryVoltagePath) / 1000;
+    props.batteryLevel = getIntField(mHealthdConfig->batteryCapacityPath);
+    props.batteryVoltage = getIntField(mHealthdConfig->batteryVoltagePath) / 1000;
 
-    if (!mBatteryCurrentNowPath.isEmpty())
-        props.batteryCurrentNow = getIntField(mBatteryCurrentNowPath) / 1000;
+    if (!mHealthdConfig->batteryCurrentNowPath.isEmpty())
+        props.batteryCurrentNow = getIntField(mHealthdConfig->batteryCurrentNowPath);
 
-    if (!mBatteryChargeCounterPath.isEmpty())
-        props.batteryChargeCounter = getIntField(mBatteryChargeCounterPath) / 1000;
+    if (!mHealthdConfig->batteryChargeCounterPath.isEmpty())
+        props.batteryChargeCounter = getIntField(mHealthdConfig->batteryChargeCounterPath);
 
-    props.batteryTemperature = getIntField(mBatteryTemperaturePath);
+    props.batteryTemperature = getIntField(mHealthdConfig->batteryTemperaturePath);
 
     const int SIZE = 128;
     char buf[SIZE];
     String8 btech;
 
-    if (readFromFile(mBatteryStatusPath, buf, SIZE) > 0)
+    if (readFromFile(mHealthdConfig->batteryStatusPath, buf, SIZE) > 0)
         props.batteryStatus = getBatteryStatus(buf);
 
-    if (readFromFile(mBatteryHealthPath, buf, SIZE) > 0)
+    if (readFromFile(mHealthdConfig->batteryHealthPath, buf, SIZE) > 0)
         props.batteryHealth = getBatteryHealth(buf);
 
-    if (readFromFile(mBatteryTechnologyPath, buf, SIZE) > 0)
+    if (readFromFile(mHealthdConfig->batteryTechnologyPath, buf, SIZE) > 0)
         props.batteryTechnology = String8(buf);
 
     unsigned int i;
@@ -252,10 +252,10 @@
                  abs(props.batteryTemperature % 10), props.batteryHealth,
                  props.batteryStatus);
 
-        if (!mBatteryCurrentNowPath.isEmpty()) {
+        if (!mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
             char b[20];
 
-            snprintf(b, sizeof(b), " c=%d", props.batteryCurrentNow);
+            snprintf(b, sizeof(b), " c=%d", props.batteryCurrentNow / 1000);
             strlcat(dmesgline, b, sizeof(dmesgline));
         }
 
@@ -272,9 +272,10 @@
             props.chargerWirelessOnline;
 }
 
-void BatteryMonitor::init(bool nosvcmgr) {
+void BatteryMonitor::init(struct healthd_config *hc, bool nosvcmgr) {
     String8 path;
 
+    mHealthdConfig = hc;
     DIR* dir = opendir(POWER_SUPPLY_SYSFS_PATH);
     if (dir == NULL) {
         KLOG_ERROR(LOG_TAG, "Could not open %s\n", POWER_SUPPLY_SYSFS_PATH);
@@ -302,59 +303,92 @@
                 break;
 
             case ANDROID_POWER_SUPPLY_TYPE_BATTERY:
-                path.clear();
-                path.appendFormat("%s/%s/status", POWER_SUPPLY_SYSFS_PATH, name);
-                if (access(path, R_OK) == 0)
-                    mBatteryStatusPath = path;
-                path.clear();
-                path.appendFormat("%s/%s/health", POWER_SUPPLY_SYSFS_PATH, name);
-                if (access(path, R_OK) == 0)
-                    mBatteryHealthPath = path;
-                path.clear();
-                path.appendFormat("%s/%s/present", POWER_SUPPLY_SYSFS_PATH, name);
-                if (access(path, R_OK) == 0)
-                    mBatteryPresentPath = path;
-                path.clear();
-                path.appendFormat("%s/%s/capacity", POWER_SUPPLY_SYSFS_PATH, name);
-                if (access(path, R_OK) == 0)
-                    mBatteryCapacityPath = path;
-
-                path.clear();
-                path.appendFormat("%s/%s/voltage_now", POWER_SUPPLY_SYSFS_PATH, name);
-                if (access(path, R_OK) == 0) {
-                    mBatteryVoltagePath = path;
-                } else {
+                if (mHealthdConfig->batteryStatusPath.isEmpty()) {
                     path.clear();
-                    path.appendFormat("%s/%s/batt_vol", POWER_SUPPLY_SYSFS_PATH, name);
+                    path.appendFormat("%s/%s/status", POWER_SUPPLY_SYSFS_PATH,
+                                      name);
                     if (access(path, R_OK) == 0)
-                            mBatteryVoltagePath = path;
+                        mHealthdConfig->batteryStatusPath = path;
                 }
 
-                path.clear();
-                path.appendFormat("%s/%s/current_now", POWER_SUPPLY_SYSFS_PATH, name);
-                if (access(path, R_OK) == 0)
-                    mBatteryCurrentNowPath = path;
-
-                path.clear();
-                path.appendFormat("%s/%s/charge_counter", POWER_SUPPLY_SYSFS_PATH, name);
-                if (access(path, R_OK) == 0)
-                    mBatteryChargeCounterPath = path;
-
-                path.clear();
-                path.appendFormat("%s/%s/temp", POWER_SUPPLY_SYSFS_PATH, name);
-                if (access(path, R_OK) == 0) {
-                    mBatteryTemperaturePath = path;
-                } else {
+                if (mHealthdConfig->batteryHealthPath.isEmpty()) {
                     path.clear();
-                    path.appendFormat("%s/%s/batt_temp", POWER_SUPPLY_SYSFS_PATH, name);
+                    path.appendFormat("%s/%s/health", POWER_SUPPLY_SYSFS_PATH,
+                                      name);
                     if (access(path, R_OK) == 0)
-                            mBatteryTemperaturePath = path;
+                        mHealthdConfig->batteryHealthPath = path;
                 }
 
-                path.clear();
-                path.appendFormat("%s/%s/technology", POWER_SUPPLY_SYSFS_PATH, name);
-                if (access(path, R_OK) == 0)
-                    mBatteryTechnologyPath = path;
+                if (mHealthdConfig->batteryPresentPath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/present", POWER_SUPPLY_SYSFS_PATH,
+                                      name);
+                    if (access(path, R_OK) == 0)
+                        mHealthdConfig->batteryPresentPath = path;
+                }
+
+                if (mHealthdConfig->batteryCapacityPath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/capacity", POWER_SUPPLY_SYSFS_PATH,
+                                      name);
+                    if (access(path, R_OK) == 0)
+                        mHealthdConfig->batteryCapacityPath = path;
+                }
+
+                if (mHealthdConfig->batteryVoltagePath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/voltage_now",
+                                      POWER_SUPPLY_SYSFS_PATH, name);
+                    if (access(path, R_OK) == 0) {
+                        mHealthdConfig->batteryVoltagePath = path;
+                    } else {
+                        path.clear();
+                        path.appendFormat("%s/%s/batt_vol",
+                                          POWER_SUPPLY_SYSFS_PATH, name);
+                        if (access(path, R_OK) == 0)
+                            mHealthdConfig->batteryVoltagePath = path;
+                    }
+                }
+
+                if (mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/current_now",
+                                      POWER_SUPPLY_SYSFS_PATH, name);
+                    if (access(path, R_OK) == 0)
+                        mHealthdConfig->batteryCurrentNowPath = path;
+                }
+
+                if (mHealthdConfig->batteryChargeCounterPath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/charge_counter",
+                                      POWER_SUPPLY_SYSFS_PATH, name);
+                    if (access(path, R_OK) == 0)
+                        mHealthdConfig->batteryChargeCounterPath = path;
+                }
+
+                if (mHealthdConfig->batteryTemperaturePath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/temp", POWER_SUPPLY_SYSFS_PATH,
+                                      name);
+                    if (access(path, R_OK) == 0) {
+                        mHealthdConfig->batteryTemperaturePath = path;
+                    } else {
+                        path.clear();
+                        path.appendFormat("%s/%s/batt_temp",
+                                          POWER_SUPPLY_SYSFS_PATH, name);
+                        if (access(path, R_OK) == 0)
+                            mHealthdConfig->batteryTemperaturePath = path;
+                    }
+                }
+
+                if (mHealthdConfig->batteryTechnologyPath.isEmpty()) {
+                    path.clear();
+                    path.appendFormat("%s/%s/technology",
+                                      POWER_SUPPLY_SYSFS_PATH, name);
+                    if (access(path, R_OK) == 0)
+                        mHealthdConfig->batteryTechnologyPath = path;
+                }
+
                 break;
 
             case ANDROID_POWER_SUPPLY_TYPE_UNKNOWN:
@@ -366,19 +400,19 @@
 
     if (!mChargerNames.size())
         KLOG_ERROR(LOG_TAG, "No charger supplies found\n");
-    if (mBatteryStatusPath.isEmpty())
+    if (mHealthdConfig->batteryStatusPath.isEmpty())
         KLOG_WARNING(LOG_TAG, "BatteryStatusPath not found\n");
-    if (mBatteryHealthPath.isEmpty())
-        KLOG_WARNING("BatteryHealthPath not found\n");
-    if (mBatteryPresentPath.isEmpty())
+    if (mHealthdConfig->batteryHealthPath.isEmpty())
+        KLOG_WARNING(LOG_TAG, "BatteryHealthPath not found\n");
+    if (mHealthdConfig->batteryPresentPath.isEmpty())
         KLOG_WARNING(LOG_TAG, "BatteryPresentPath not found\n");
-    if (mBatteryCapacityPath.isEmpty())
+    if (mHealthdConfig->batteryCapacityPath.isEmpty())
         KLOG_WARNING(LOG_TAG, "BatteryCapacityPath not found\n");
-    if (mBatteryVoltagePath.isEmpty())
+    if (mHealthdConfig->batteryVoltagePath.isEmpty())
         KLOG_WARNING(LOG_TAG, "BatteryVoltagePath not found\n");
-    if (mBatteryTemperaturePath.isEmpty())
+    if (mHealthdConfig->batteryTemperaturePath.isEmpty())
         KLOG_WARNING(LOG_TAG, "BatteryTemperaturePath not found\n");
-    if (mBatteryTechnologyPath.isEmpty())
+    if (mHealthdConfig->batteryTechnologyPath.isEmpty())
         KLOG_WARNING(LOG_TAG, "BatteryTechnologyPath not found\n");
 
     if (nosvcmgr == false) {
diff --git a/healthd/BatteryMonitor.h b/healthd/BatteryMonitor.h
index ff1ea1e..ba291af 100644
--- a/healthd/BatteryMonitor.h
+++ b/healthd/BatteryMonitor.h
@@ -21,6 +21,7 @@
 #include <utils/String8.h>
 #include <utils/Vector.h>
 
+#include "healthd.h"
 #include "BatteryPropertiesRegistrar.h"
 
 namespace android {
@@ -38,20 +39,11 @@
         ANDROID_POWER_SUPPLY_TYPE_BATTERY
     };
 
-    void init(bool nosvcmgr);
+    void init(struct healthd_config *hc, bool nosvcmgr);
     bool update(void);
 
   private:
-    String8 mBatteryStatusPath;
-    String8 mBatteryHealthPath;
-    String8 mBatteryPresentPath;
-    String8 mBatteryCapacityPath;
-    String8 mBatteryVoltagePath;
-    String8 mBatteryTemperaturePath;
-    String8 mBatteryTechnologyPath;
-    String8 mBatteryCurrentNowPath;
-    String8 mBatteryChargeCounterPath;
-
+    struct healthd_config *mHealthdConfig;
     Vector<String8> mChargerNames;
 
     sp<BatteryPropertiesRegistrar> mBatteryPropertiesRegistrar;
diff --git a/healthd/healthd.cpp b/healthd/healthd.cpp
index 158274c..1719c22 100644
--- a/healthd/healthd.cpp
+++ b/healthd/healthd.cpp
@@ -41,6 +41,15 @@
 static struct healthd_config healthd_config = {
     .periodic_chores_interval_fast = DEFAULT_PERIODIC_CHORES_INTERVAL_FAST,
     .periodic_chores_interval_slow = DEFAULT_PERIODIC_CHORES_INTERVAL_SLOW,
+    .batteryStatusPath = String8(String8::kEmptyString),
+    .batteryHealthPath = String8(String8::kEmptyString),
+    .batteryPresentPath = String8(String8::kEmptyString),
+    .batteryCapacityPath = String8(String8::kEmptyString),
+    .batteryVoltagePath = String8(String8::kEmptyString),
+    .batteryTemperaturePath = String8(String8::kEmptyString),
+    .batteryTechnologyPath = String8(String8::kEmptyString),
+    .batteryCurrentNowPath = String8(String8::kEmptyString),
+    .batteryChargeCounterPath = String8(String8::kEmptyString),
 };
 
 #define POWER_SUPPLY_SUBSYSTEM "power_supply"
@@ -266,7 +275,7 @@
     uevent_init();
     binder_init();
     gBatteryMonitor = new BatteryMonitor();
-    gBatteryMonitor->init(nosvcmgr);
+    gBatteryMonitor->init(&healthd_config, nosvcmgr);
 
     healthd_mainloop();
     return 0;
diff --git a/healthd/healthd.h b/healthd/healthd.h
index 895be40..5374fb1 100644
--- a/healthd/healthd.h
+++ b/healthd/healthd.h
@@ -18,6 +18,7 @@
 #define _HEALTHD_H_
 
 #include <batteryservice/BatteryService.h>
+#include <utils/String8.h>
 
 // periodic_chores_interval_fast, periodic_chores_interval_slow: intervals at
 // which healthd wakes up to poll health state and perform periodic chores,
@@ -32,18 +33,45 @@
 //    not connected to a charger (to watch for a battery drained to zero
 //    remaining capacity).  The default value is 600 (10 minutes).  Value -1
 //    tuns off periodic chores (and wakeups) in these conditions.
+//
+// power_supply sysfs attribute file paths.  Set these to specific paths
+// to use for the associated battery parameters.  healthd will search for
+// appropriate power_supply attribute files to use for any paths left empty:
+//
+//    batteryStatusPath: charging status (POWER_SUPPLY_PROP_STATUS)
+//    batteryHealthPath: battery health (POWER_SUPPLY_PROP_HEALTH)
+//    batteryPresentPath: battery present (POWER_SUPPLY_PROP_PRESENT)
+//    batteryCapacityPath: remaining capacity (POWER_SUPPLY_PROP_CAPACITY)
+//    batteryVoltagePath: battery voltage (POWER_SUPPLY_PROP_VOLTAGE_NOW)
+//    batteryTemperaturePath: battery temperature (POWER_SUPPLY_PROP_TEMP)
+//    batteryTechnologyPath: battery technology (POWER_SUPPLY_PROP_TECHNOLOGY)
+//    batteryCurrentNowPath: battery current (POWER_SUPPLY_PROP_CURRENT_NOW)
+//    batteryChargeCounterPath: battery accumulated charge
+//                                         (POWER_SUPPLY_PROP_CHARGE_COUNTER)
 
 struct healthd_config {
     int periodic_chores_interval_fast;
     int periodic_chores_interval_slow;
+
+    android::String8 batteryStatusPath;
+    android::String8 batteryHealthPath;
+    android::String8 batteryPresentPath;
+    android::String8 batteryCapacityPath;
+    android::String8 batteryVoltagePath;
+    android::String8 batteryTemperaturePath;
+    android::String8 batteryTechnologyPath;
+    android::String8 batteryCurrentNowPath;
+    android::String8 batteryChargeCounterPath;
 };
 
 // The following are implemented in libhealthd_board to handle board-specific
 // behavior.
 //
-//
-// To use the default values, this function can simply return without
-// modifying the parameters.
+// healthd_board_init() is called at startup time to modify healthd's
+// configuration according to board-specific requirements.  config
+// points to the healthd configuration values described above.  To use default
+// values, this function can simply return without modifying the fields of the
+// config parameter.
 
 void healthd_board_init(struct healthd_config *config);
 
diff --git a/include/private/android_filesystem_config.h b/include/private/android_filesystem_config.h
index 4a7d377..0ed0d78 100644
--- a/include/private/android_filesystem_config.h
+++ b/include/private/android_filesystem_config.h
@@ -34,8 +34,8 @@
 #endif
 
 /* This is the master Users and Groups config for the platform.
-** DO NOT EVER RENUMBER.
-*/
+ * DO NOT EVER RENUMBER
+ */
 
 #define AID_ROOT             0  /* traditional unix root user */
 
@@ -72,6 +72,10 @@
 #define AID_CLAT          1029  /* clat part of nat464 */
 #define AID_LOOP_RADIO    1030  /* loop radio devices */
 #define AID_MEDIA_DRM     1031  /* MediaDrm plugins */
+#define AID_PACKAGE_INFO  1032  /* access to installed package details */
+#define AID_SDCARD_PICS   1033  /* external storage photos access */
+#define AID_SDCARD_AV     1034  /* external storage audio/video access */
+#define AID_SDCARD_ALL    1035  /* access all users external storage */
 
 #define AID_SHELL         2000  /* adb and debug shell user */
 #define AID_CACHE         2001  /* cache access */
@@ -108,50 +112,61 @@
 };
 
 static const struct android_id_info android_ids[] = {
-    { "root",      AID_ROOT, },
-    { "system",    AID_SYSTEM, },
-    { "radio",     AID_RADIO, },
-    { "bluetooth", AID_BLUETOOTH, },
-    { "graphics",  AID_GRAPHICS, },
-    { "input",     AID_INPUT, },
-    { "audio",     AID_AUDIO, },
-    { "camera",    AID_CAMERA, },
-    { "log",       AID_LOG, },
-    { "compass",   AID_COMPASS, },
-    { "mount",     AID_MOUNT, },
-    { "wifi",      AID_WIFI, },
-    { "dhcp",      AID_DHCP, },
-    { "adb",       AID_ADB, },
-    { "install",   AID_INSTALL, },
-    { "media",     AID_MEDIA, },
-    { "drm",       AID_DRM, },
-    { "mdnsr",     AID_MDNSR, },
-    { "nfc",       AID_NFC, },
-    { "drmrpc",    AID_DRMRPC, },
-    { "shell",     AID_SHELL, },
-    { "cache",     AID_CACHE, },
-    { "diag",      AID_DIAG, },
-    { "net_bt_admin", AID_NET_BT_ADMIN, },
-    { "net_bt",    AID_NET_BT, },
-    { "net_bt_stack",    AID_NET_BT_STACK, },
-    { "sdcard_r",  AID_SDCARD_R, },
-    { "sdcard_rw", AID_SDCARD_RW, },
-    { "media_rw",  AID_MEDIA_RW, },
-    { "vpn",       AID_VPN, },
-    { "keystore",  AID_KEYSTORE, },
-    { "usb",       AID_USB, },
-    { "mtp",       AID_MTP, },
-    { "gps",       AID_GPS, },
-    { "inet",      AID_INET, },
-    { "net_raw",   AID_NET_RAW, },
-    { "net_admin", AID_NET_ADMIN, },
-    { "net_bw_stats", AID_NET_BW_STATS, },
-    { "net_bw_acct", AID_NET_BW_ACCT, },
-    { "loop_radio", AID_LOOP_RADIO, },
-    { "misc",      AID_MISC, },
-    { "nobody",    AID_NOBODY, },
-    { "clat",      AID_CLAT, },
-    { "mediadrm",  AID_MEDIA_DRM, },
+    { "root",          AID_ROOT, },
+
+    { "system",        AID_SYSTEM, },
+
+    { "radio",         AID_RADIO, },
+    { "bluetooth",     AID_BLUETOOTH, },
+    { "graphics",      AID_GRAPHICS, },
+    { "input",         AID_INPUT, },
+    { "audio",         AID_AUDIO, },
+    { "camera",        AID_CAMERA, },
+    { "log",           AID_LOG, },
+    { "compass",       AID_COMPASS, },
+    { "mount",         AID_MOUNT, },
+    { "wifi",          AID_WIFI, },
+    { "adb",           AID_ADB, },
+    { "install",       AID_INSTALL, },
+    { "media",         AID_MEDIA, },
+    { "dhcp",          AID_DHCP, },
+    { "sdcard_rw",     AID_SDCARD_RW, },
+    { "vpn",           AID_VPN, },
+    { "keystore",      AID_KEYSTORE, },
+    { "usb",           AID_USB, },
+    { "drm",           AID_DRM, },
+    { "mdnsr",         AID_MDNSR, },
+    { "gps",           AID_GPS, },
+    // AID_UNUSED1
+    { "media_rw",      AID_MEDIA_RW, },
+    { "mtp",           AID_MTP, },
+    // AID_UNUSED2
+    { "drmrpc",        AID_DRMRPC, },
+    { "nfc",           AID_NFC, },
+    { "sdcard_r",      AID_SDCARD_R, },
+    { "clat",          AID_CLAT, },
+    { "loop_radio",    AID_LOOP_RADIO, },
+    { "mediadrm",      AID_MEDIA_DRM, },
+    { "package_info",  AID_PACKAGE_INFO, },
+    { "sdcard_pics",   AID_SDCARD_PICS, },
+    { "sdcard_av",     AID_SDCARD_AV, },
+    { "sdcard_all",    AID_SDCARD_ALL, },
+
+    { "shell",         AID_SHELL, },
+    { "cache",         AID_CACHE, },
+    { "diag",          AID_DIAG, },
+
+    { "net_bt_admin",  AID_NET_BT_ADMIN, },
+    { "net_bt",        AID_NET_BT, },
+    { "inet",          AID_INET, },
+    { "net_raw",       AID_NET_RAW, },
+    { "net_admin",     AID_NET_ADMIN, },
+    { "net_bw_stats",  AID_NET_BW_STATS, },
+    { "net_bw_acct",   AID_NET_BW_ACCT, },
+    { "net_bt_stack",  AID_NET_BT_STACK, },
+
+    { "misc",          AID_MISC, },
+    { "nobody",        AID_NOBODY, },
 };
 
 #define android_id_count \
diff --git a/run-as/package.c b/run-as/package.c
index 27fc1eb..4762c5f 100644
--- a/run-as/package.c
+++ b/run-as/package.c
@@ -111,7 +111,7 @@
         goto EXIT;
 
     /* Ensure that the file is owned by the system user */
-    if ((st.st_uid != AID_SYSTEM) || (st.st_gid != AID_SYSTEM)) {
+    if ((st.st_uid != AID_SYSTEM) || (st.st_gid != AID_PACKAGE_INFO)) {
         goto EXIT;
     }
 
diff --git a/sdcard/Android.mk b/sdcard/Android.mk
index fb04d6d..4630db9 100644
--- a/sdcard/Android.mk
+++ b/sdcard/Android.mk
@@ -6,6 +6,6 @@
 LOCAL_MODULE:= sdcard
 LOCAL_CFLAGS := -Wall -Wno-unused-parameter
 
-LOCAL_SHARED_LIBRARIES := libc
+LOCAL_SHARED_LIBRARIES := libc libcutils
 
 include $(BUILD_EXECUTABLE)
diff --git a/sdcard/sdcard.c b/sdcard/sdcard.c
index 7e65a68..7349b22 100644
--- a/sdcard/sdcard.c
+++ b/sdcard/sdcard.c
@@ -30,6 +30,10 @@
 #include <pthread.h>
 #include <sys/time.h>
 #include <sys/resource.h>
+#include <sys/inotify.h>
+
+#include <cutils/hashmap.h>
+#include <cutils/multiuser.h>
 
 #include <private/android_filesystem_config.h>
 
@@ -57,6 +61,30 @@
  * - if an op that returns a fuse_entry fails writing the reply to the
  * kernel, you must rollback the refcount to reflect the reference the
  * kernel did not actually acquire
+ *
+ * This daemon can also derive custom filesystem permissions based on directory
+ * structure when requested. These custom permissions support several features:
+ *
+ * - Apps can access their own files in /Android/data/com.example/ without
+ * requiring any additional GIDs.
+ * - Separate permissions for protecting directories like Pictures and Music.
+ * - Multi-user separation on the same physical device.
+ *
+ * The derived permissions look like this:
+ *
+ * rwxrwx--x root:sdcard_rw     /
+ * rwxrwx--- root:sdcard_pics   /Pictures
+ * rwxrwx--- root:sdcard_av     /Music
+ *
+ * rwxrwx--x root:sdcard_rw     /Android
+ * rwxrwx--x root:sdcard_rw     /Android/data
+ * rwxrwx--- u0_a12:sdcard_rw   /Android/data/com.example
+ * rwxrwx--x root:sdcard_rw     /Android/obb/
+ * rwxrwx--- u0_a12:sdcard_rw   /Android/obb/com.example
+ *
+ * rwxrwx--- root:sdcard_all    /Android/user
+ * rwxrwx--x root:sdcard_rw     /Android/user/10
+ * rwxrwx--- u10_a12:sdcard_rw  /Android/user/10/Android/data/com.example
  */
 
 #define FUSE_TRACE 0
@@ -89,6 +117,39 @@
  * or that a reply has already been written. */
 #define NO_STATUS 1
 
+/* Path to system-provided mapping of package name to appIds */
+static const char* const kPackagesListFile = "/data/system/packages.list";
+
+/* Supplementary groups to execute with */
+static const gid_t kGroups[1] = { AID_PACKAGE_INFO };
+
+/* Permission mode for a specific node. Controls how file permissions
+ * are derived for children nodes. */
+typedef enum {
+    /* Nothing special; this node should just inherit from its parent. */
+    PERM_INHERIT,
+    /* This node is one level above a normal root; used for legacy layouts
+     * which use the first level to represent user_id. */
+    PERM_LEGACY_PRE_ROOT,
+    /* This node is "/" */
+    PERM_ROOT,
+    /* This node is "/Android" */
+    PERM_ANDROID,
+    /* This node is "/Android/data" */
+    PERM_ANDROID_DATA,
+    /* This node is "/Android/obb" */
+    PERM_ANDROID_OBB,
+    /* This node is "/Android/user" */
+    PERM_ANDROID_USER,
+} perm_t;
+
+/* Permissions structure to derive */
+typedef enum {
+    DERIVE_NONE,
+    DERIVE_LEGACY,
+    DERIVE_UNIFIED,
+} derive_t;
+
 struct handle {
     int fd;
 };
@@ -102,6 +163,13 @@
     __u64 nid;
     __u64 gen;
 
+    /* State derived based on current position in hierarchy. */
+    perm_t perm;
+    userid_t userid;
+    uid_t uid;
+    gid_t gid;
+    mode_t mode;
+
     struct node *next;          /* per-dir sibling list */
     struct node *child;         /* first contained file by this dir */
     struct node *parent;        /* containing directory */
@@ -114,16 +182,42 @@
      * namelen for both fields.
      */
     char *actual_name;
+
+    /* If non-null, an exact underlying path that should be grafted into this
+     * position. Used to support things like OBB. */
+    char* graft_path;
+    size_t graft_pathlen;
 };
 
+static int str_hash(void *key) {
+    return hashmapHash(key, strlen(key));
+}
+
+static bool str_equals(void *keyA, void *keyB) {
+    return strcmp(keyA, keyB) == 0;
+}
+
+static int int_hash(void *key) {
+    return (int) key;
+}
+
+static bool int_equals(void *keyA, void *keyB) {
+    return keyA == keyB;
+}
+
 /* Global data structure shared by all fuse handlers. */
 struct fuse {
     pthread_mutex_t lock;
 
     __u64 next_generation;
     int fd;
+    derive_t derive;
+    bool split_perms;
     struct node root;
-    char rootpath[PATH_MAX];
+    char obbpath[PATH_MAX];
+
+    Hashmap* package_to_appid;
+    Hashmap* appid_with_rw;
 };
 
 /* Private data used by a single fuse handler. */
@@ -208,15 +302,26 @@
  * Populates 'buf' with the path and returns the length of the path on success,
  * or returns -1 if the path is too long for the provided buffer.
  */
-static ssize_t get_node_path_locked(struct node* node, char* buf, size_t bufsize)
-{
-    size_t namelen = node->namelen;
+static ssize_t get_node_path_locked(struct node* node, char* buf, size_t bufsize) {
+    const char* name;
+    size_t namelen;
+    if (node->graft_path) {
+        name = node->graft_path;
+        namelen = node->graft_pathlen;
+    } else if (node->actual_name) {
+        name = node->actual_name;
+        namelen = node->namelen;
+    } else {
+        name = node->name;
+        namelen = node->namelen;
+    }
+
     if (bufsize < namelen + 1) {
         return -1;
     }
 
     ssize_t pathlen = 0;
-    if (node->parent) {
+    if (node->parent && node->graft_path == NULL) {
         pathlen = get_node_path_locked(node->parent, buf, bufsize - namelen - 2);
         if (pathlen < 0) {
             return -1;
@@ -224,7 +329,6 @@
         buf[pathlen++] = '/';
     }
 
-    const char* name = node->actual_name ? node->actual_name : node->name;
     memcpy(buf + pathlen, name, namelen + 1); /* include trailing \0 */
     return pathlen + namelen;
 }
@@ -258,7 +362,7 @@
         struct dirent* entry;
         DIR* dir = opendir(path);
         if (!dir) {
-            ERROR("opendir %s failed: %s", path, strerror(errno));
+            ERROR("opendir %s failed: %s\n", path, strerror(errno));
             return actual;
         }
         while ((entry = readdir(dir))) {
@@ -273,9 +377,9 @@
     return actual;
 }
 
-static void attr_from_stat(struct fuse_attr *attr, const struct stat *s, __u64 nid)
+static void attr_from_stat(struct fuse_attr *attr, const struct stat *s, const struct node* node)
 {
-    attr->ino = nid;
+    attr->ino = node->nid;
     attr->size = s->st_size;
     attr->blocks = s->st_blocks;
     attr->atime = s->st_atime;
@@ -287,19 +391,149 @@
     attr->mode = s->st_mode;
     attr->nlink = s->st_nlink;
 
-        /* force permissions to something reasonable:
-         * world readable
-         * writable by the sdcard group
-         */
-    if (attr->mode & 0100) {
-        attr->mode = (attr->mode & (~0777)) | 0775;
-    } else {
-        attr->mode = (attr->mode & (~0777)) | 0664;
+    attr->uid = node->uid;
+    attr->gid = node->gid;
+
+    /* Filter requested mode based on underlying file, and
+     * pass through file type. */
+    int owner_mode = s->st_mode & 0700;
+    int filtered_mode = node->mode & (owner_mode | (owner_mode >> 3) | (owner_mode >> 6));
+    attr->mode = (attr->mode & S_IFMT) | filtered_mode;
+}
+
+static void derive_permissions_locked(struct fuse* fuse, struct node *parent,
+        struct node *node) {
+    appid_t appid;
+
+    /* By default, each node inherits from its parent */
+    node->perm = PERM_INHERIT;
+    node->userid = parent->userid;
+    node->uid = parent->uid;
+    node->gid = parent->gid;
+    node->mode = parent->mode;
+
+    if (fuse->derive == DERIVE_NONE) {
+        return;
     }
 
-        /* all files owned by root.sdcard */
-    attr->uid = 0;
-    attr->gid = AID_SDCARD_RW;
+    /* Derive custom permissions based on parent and current node */
+    switch (parent->perm) {
+    case PERM_INHERIT:
+        /* Already inherited above */
+        break;
+    case PERM_LEGACY_PRE_ROOT:
+        /* Legacy internal layout places users at top level */
+        node->perm = PERM_ROOT;
+        node->userid = strtoul(node->name, NULL, 10);
+        break;
+    case PERM_ROOT:
+        /* Assume masked off by default. */
+        node->mode = 0770;
+        if (!strcmp(node->name, "Android")) {
+            /* App-specific directories inside; let anyone traverse */
+            node->perm = PERM_ANDROID;
+            node->mode = 0771;
+        } else if (fuse->split_perms) {
+            if (!strcmp(node->name, "DCIM")
+                    || !strcmp(node->name, "Pictures")) {
+                node->gid = AID_SDCARD_PICS;
+            } else if (!strcmp(node->name, "Alarms")
+                    || !strcmp(node->name, "Movies")
+                    || !strcmp(node->name, "Music")
+                    || !strcmp(node->name, "Notifications")
+                    || !strcmp(node->name, "Podcasts")
+                    || !strcmp(node->name, "Ringtones")) {
+                node->gid = AID_SDCARD_AV;
+            }
+        }
+        break;
+    case PERM_ANDROID:
+        if (!strcmp(node->name, "data")) {
+            /* App-specific directories inside; let anyone traverse */
+            node->perm = PERM_ANDROID_DATA;
+            node->mode = 0771;
+        } else if (!strcmp(node->name, "obb")) {
+            /* App-specific directories inside; let anyone traverse */
+            node->perm = PERM_ANDROID_OBB;
+            node->mode = 0771;
+            /* Single OBB directory is always shared */
+            node->graft_path = fuse->obbpath;
+            node->graft_pathlen = strlen(fuse->obbpath);
+        } else if (!strcmp(node->name, "user")) {
+            /* User directories must only be accessible to system, protected
+             * by sdcard_all. Zygote will bind mount the appropriate user-
+             * specific path. */
+            node->perm = PERM_ANDROID_USER;
+            node->gid = AID_SDCARD_ALL;
+            node->mode = 0770;
+        }
+        break;
+    case PERM_ANDROID_DATA:
+    case PERM_ANDROID_OBB:
+        appid = (appid_t) hashmapGet(fuse->package_to_appid, node->name);
+        if (appid != 0) {
+            node->uid = multiuser_get_uid(parent->userid, appid);
+        }
+        node->mode = 0770;
+        break;
+    case PERM_ANDROID_USER:
+        /* Root of a secondary user */
+        node->perm = PERM_ROOT;
+        node->userid = strtoul(node->name, NULL, 10);
+        node->gid = AID_SDCARD_R;
+        node->mode = 0771;
+        break;
+    }
+}
+
+/* Kernel has already enforced everything we returned through
+ * derive_permissions_locked(), so this is used to lock down access
+ * even further, such as enforcing that apps hold sdcard_rw. */
+static bool check_caller_access_to_name(struct fuse* fuse,
+        const struct fuse_in_header *hdr, const struct node* parent_node,
+        const char* name, int mode) {
+    /* Always block security-sensitive files at root */
+    if (parent_node && parent_node->perm == PERM_ROOT) {
+        if (!strcmp(name, "autorun.inf")
+                || !strcmp(name, ".android_secure")
+                || !strcmp(name, "android_secure")) {
+            return false;
+        }
+    }
+
+    /* No additional permissions enforcement */
+    if (fuse->derive == DERIVE_NONE) {
+        return true;
+    }
+
+    /* Root or shell always have access */
+    if (hdr->uid == 0 || hdr->uid == AID_SHELL) {
+        return true;
+    }
+
+    /* If asking to write, verify that caller either owns the
+     * parent or holds sdcard_rw. */
+    if (mode & W_OK) {
+        if (parent_node && hdr->uid == parent_node->uid) {
+            return true;
+        }
+
+        appid_t appid = multiuser_get_app_id(hdr->uid);
+
+        pthread_mutex_lock(&fuse->lock);
+        bool hasRw = hashmapContainsKey(fuse->appid_with_rw, (void*) appid);
+        pthread_mutex_unlock(&fuse->lock);
+
+        return hasRw;
+    }
+
+    /* No extra permissions to enforce */
+    return true;
+}
+
+static bool check_caller_access_to_node(struct fuse* fuse,
+        const struct fuse_in_header *hdr, const struct node* node, int mode) {
+    return check_caller_access_to_name(fuse, hdr, node->parent, node->name, mode);
 }
 
 struct node *create_node_locked(struct fuse* fuse,
@@ -330,6 +564,8 @@
     node->namelen = namelen;
     node->nid = ptr_to_id(node);
     node->gen = fuse->next_generation++;
+
+    derive_permissions_locked(fuse, parent, node);
     acquire_node_locked(node);
     add_node_to_parent_locked(node, parent);
     return node;
@@ -422,18 +658,54 @@
     return child;
 }
 
-static void fuse_init(struct fuse *fuse, int fd, const char *source_path)
-{
+static void fuse_init(struct fuse *fuse, int fd, const char *source_path,
+        gid_t fs_gid, derive_t derive, bool split_perms) {
     pthread_mutex_init(&fuse->lock, NULL);
 
     fuse->fd = fd;
     fuse->next_generation = 0;
+    fuse->derive = derive;
+    fuse->split_perms = split_perms;
 
     memset(&fuse->root, 0, sizeof(fuse->root));
     fuse->root.nid = FUSE_ROOT_ID; /* 1 */
     fuse->root.refcount = 2;
     fuse->root.namelen = strlen(source_path);
     fuse->root.name = strdup(source_path);
+    fuse->root.userid = 0;
+    fuse->root.uid = AID_ROOT;
+
+    /* Set up root node for various modes of operation */
+    switch (derive) {
+    case DERIVE_NONE:
+        /* Traditional behavior that treats entire device as being accessible
+         * to sdcard_rw, and no permissions are derived. */
+        fuse->root.perm = PERM_ROOT;
+        fuse->root.mode = 0775;
+        fuse->root.gid = AID_SDCARD_RW;
+        break;
+    case DERIVE_LEGACY:
+        /* Legacy behavior used to support internal multiuser layout which
+         * places user_id at the top directory level, with the actual roots
+         * just below that. Shared OBB path is also at top level. */
+        fuse->root.perm = PERM_LEGACY_PRE_ROOT;
+        fuse->root.mode = 0771;
+        fuse->root.gid = fs_gid;
+        fuse->package_to_appid = hashmapCreate(256, str_hash, str_equals);
+        fuse->appid_with_rw = hashmapCreate(128, int_hash, int_equals);
+        snprintf(fuse->obbpath, sizeof(fuse->obbpath), "%s/obb", source_path);
+        break;
+    case DERIVE_UNIFIED:
+        /* Unified multiuser layout which places secondary user_id under
+         * /Android/user and shared OBB path under /Android/obb. */
+        fuse->root.perm = PERM_ROOT;
+        fuse->root.mode = 0771;
+        fuse->root.gid = fs_gid;
+        fuse->package_to_appid = hashmapCreate(256, str_hash, str_equals);
+        fuse->appid_with_rw = hashmapCreate(128, int_hash, int_equals);
+        snprintf(fuse->obbpath, sizeof(fuse->obbpath), "%s/Android/obb", source_path);
+        break;
+    }
 }
 
 static void fuse_status(struct fuse *fuse, __u64 unique, int err)
@@ -475,7 +747,36 @@
     struct stat s;
 
     if (lstat(path, &s) < 0) {
-        return -errno;
+        /* But wait! We'll automatically create a directory if its
+         * a valid package name under data or obb, since apps may not
+         * have enough permissions to create for themselves. */
+        if (errno == ENOENT && (parent->perm == PERM_ANDROID_DATA
+                || parent->perm == PERM_ANDROID_OBB)) {
+            TRACE("automatically creating %s\n", path);
+
+            pthread_mutex_lock(&fuse->lock);
+            bool validPackage = hashmapContainsKey(fuse->package_to_appid, (char*) name);
+            pthread_mutex_unlock(&fuse->lock);
+
+            if (!validPackage) {
+                return -ENOENT;
+            }
+            if (mkdir(path, 0775) == -1) {
+                /* We might have raced with ourselves and already created */
+                if (errno != EEXIST) {
+                    ERROR("failed to mkdir(%s): %s\n", name, strerror(errno));
+                    return -ENOENT;
+                }
+            }
+
+            /* It should exist this time around! */
+            if (lstat(path, &s) < 0) {
+                ERROR("failed to lstat(%s): %s\n", name, strerror(errno));
+                return -errno;
+            }
+        } else {
+            return -errno;
+        }
     }
 
     pthread_mutex_lock(&fuse->lock);
@@ -485,7 +786,7 @@
         return -ENOMEM;
     }
     memset(&out, 0, sizeof(out));
-    attr_from_stat(&out.attr, &s, node->nid);
+    attr_from_stat(&out.attr, &s, node);
     out.attr_valid = 10;
     out.entry_valid = 10;
     out.nodeid = node->nid;
@@ -495,7 +796,7 @@
     return NO_STATUS;
 }
 
-static int fuse_reply_attr(struct fuse* fuse, __u64 unique, __u64 nid,
+static int fuse_reply_attr(struct fuse* fuse, __u64 unique, const struct node* node,
         const char* path)
 {
     struct fuse_attr_out out;
@@ -505,7 +806,7 @@
         return -errno;
     }
     memset(&out, 0, sizeof(out));
-    attr_from_stat(&out.attr, &s, nid);
+    attr_from_stat(&out.attr, &s, node);
     out.attr_valid = 10;
     fuse_reply(fuse, unique, &out, sizeof(out));
     return NO_STATUS;
@@ -530,6 +831,10 @@
             child_path, sizeof(child_path), 1))) {
         return -ENOENT;
     }
+    if (!check_caller_access_to_name(fuse, hdr, parent_node, name, R_OK)) {
+        return -EACCES;
+    }
+
     return fuse_reply_entry(fuse, hdr->unique, parent_node, name, actual_name, child_path);
 }
 
@@ -567,7 +872,11 @@
     if (!node) {
         return -ENOENT;
     }
-    return fuse_reply_attr(fuse, hdr->unique, hdr->nodeid, path);
+    if (!check_caller_access_to_node(fuse, hdr, node, R_OK)) {
+        return -EACCES;
+    }
+
+    return fuse_reply_attr(fuse, hdr->unique, node, path);
 }
 
 static int handle_setattr(struct fuse* fuse, struct fuse_handler* handler,
@@ -586,6 +895,9 @@
     if (!node) {
         return -ENOENT;
     }
+    if (!check_caller_access_to_node(fuse, hdr, node, W_OK)) {
+        return -EACCES;
+    }
 
     /* XXX: incomplete implementation on purpose.
      * chmod/chown should NEVER be implemented.*/
@@ -625,7 +937,7 @@
             return -errno;
         }
     }
-    return fuse_reply_attr(fuse, hdr->unique, hdr->nodeid, path);
+    return fuse_reply_attr(fuse, hdr->unique, node, path);
 }
 
 static int handle_mknod(struct fuse* fuse, struct fuse_handler* handler,
@@ -647,6 +959,9 @@
             child_path, sizeof(child_path), 1))) {
         return -ENOENT;
     }
+    if (!check_caller_access_to_name(fuse, hdr, parent_node, name, W_OK)) {
+        return -EACCES;
+    }
     __u32 mode = (req->mode & (~0777)) | 0664;
     if (mknod(child_path, mode, req->rdev) < 0) {
         return -errno;
@@ -673,6 +988,9 @@
             child_path, sizeof(child_path), 1))) {
         return -ENOENT;
     }
+    if (!check_caller_access_to_name(fuse, hdr, parent_node, name, W_OK)) {
+        return -EACCES;
+    }
     __u32 mode = (req->mode & (~0777)) | 0775;
     if (mkdir(child_path, mode) < 0) {
         return -errno;
@@ -698,6 +1016,9 @@
             child_path, sizeof(child_path), 1)) {
         return -ENOENT;
     }
+    if (!check_caller_access_to_name(fuse, hdr, parent_node, name, W_OK)) {
+        return -EACCES;
+    }
     if (unlink(child_path) < 0) {
         return -errno;
     }
@@ -722,6 +1043,9 @@
             child_path, sizeof(child_path), 1)) {
         return -ENOENT;
     }
+    if (!check_caller_access_to_name(fuse, hdr, parent_node, name, W_OK)) {
+        return -EACCES;
+    }
     if (rmdir(child_path) < 0) {
         return -errno;
     }
@@ -755,6 +1079,14 @@
         res = -ENOENT;
         goto lookup_error;
     }
+    if (!check_caller_access_to_name(fuse, hdr, old_parent_node, old_name, W_OK)) {
+        res = -EACCES;
+        goto lookup_error;
+    }
+    if (!check_caller_access_to_name(fuse, hdr, new_parent_node, new_name, W_OK)) {
+        res = -EACCES;
+        goto lookup_error;
+    }
     child_node = lookup_child_by_name_locked(old_parent_node, old_name);
     if (!child_node || get_node_path_locked(child_node,
             old_child_path, sizeof(old_child_path)) < 0) {
@@ -800,6 +1132,17 @@
     return res;
 }
 
+static int open_flags_to_access_mode(int open_flags) {
+    if ((open_flags & O_ACCMODE) == O_RDONLY) {
+        return R_OK;
+    } else if ((open_flags & O_ACCMODE) == O_WRONLY) {
+        return W_OK;
+    } else {
+        /* Probably O_RDRW, but treat as default to be safe */
+        return R_OK | W_OK;
+    }
+}
+
 static int handle_open(struct fuse* fuse, struct fuse_handler* handler,
         const struct fuse_in_header* hdr, const struct fuse_open_in* req)
 {
@@ -817,6 +1160,9 @@
     if (!node) {
         return -ENOENT;
     }
+    if (!check_caller_access_to_node(fuse, hdr, node, open_flags_to_access_mode(req->flags))) {
+        return -EACCES;
+    }
     h = malloc(sizeof(*h));
     if (!h) {
         return -ENOMEM;
@@ -961,6 +1307,9 @@
     if (!node) {
         return -ENOENT;
     }
+    if (!check_caller_access_to_node(fuse, hdr, node, R_OK)) {
+        return -EACCES;
+    }
     h = malloc(sizeof(*h));
     if (!h) {
         return -ENOMEM;
@@ -1208,6 +1557,123 @@
     return NULL;
 }
 
+static bool remove_str_to_int(void *key, void *value, void *context) {
+    Hashmap* map = context;
+    hashmapRemove(map, key);
+    free(key);
+    return true;
+}
+
+static bool remove_int_to_null(void *key, void *value, void *context) {
+    Hashmap* map = context;
+    hashmapRemove(map, key);
+    return true;
+}
+
+static int read_package_list(struct fuse *fuse) {
+    pthread_mutex_lock(&fuse->lock);
+
+    hashmapForEach(fuse->package_to_appid, remove_str_to_int, fuse->package_to_appid);
+    hashmapForEach(fuse->appid_with_rw, remove_int_to_null, fuse->appid_with_rw);
+
+    FILE* file = fopen(kPackagesListFile, "r");
+    if (!file) {
+        ERROR("failed to open package list: %s\n", strerror(errno));
+        pthread_mutex_unlock(&fuse->lock);
+        return -1;
+    }
+
+    char buf[512];
+    while (fgets(buf, sizeof(buf), file) != NULL) {
+        char package_name[512];
+        int appid;
+        char gids[512];
+
+        if (sscanf(buf, "%s %d %*d %*s %*s %s", package_name, &appid, gids) == 3) {
+            char* package_name_dup = strdup(package_name);
+            hashmapPut(fuse->package_to_appid, package_name_dup, (void*) appid);
+
+            char* token = strtok(gids, ",");
+            while (token != NULL) {
+                if (strtoul(token, NULL, 10) == AID_SDCARD_RW) {
+                    hashmapPut(fuse->appid_with_rw, (void*) appid, (void*) 1);
+                    break;
+                }
+                token = strtok(NULL, ",");
+            }
+        }
+    }
+
+    TRACE("read_package_list: found %d packages, %d with sdcard_rw\n",
+            hashmapSize(fuse->package_to_appid),
+            hashmapSize(fuse->appid_with_rw));
+    fclose(file);
+    pthread_mutex_unlock(&fuse->lock);
+    return 0;
+}
+
+static void watch_package_list(struct fuse* fuse) {
+    struct inotify_event *event;
+    char event_buf[512];
+
+    int nfd = inotify_init();
+    if (nfd < 0) {
+        ERROR("inotify_init failed: %s\n", strerror(errno));
+        return;
+    }
+
+    bool active = false;
+    while (1) {
+        if (!active) {
+            int res = inotify_add_watch(nfd, kPackagesListFile, IN_DELETE_SELF);
+            if (res == -1) {
+                if (errno == ENOENT || errno == EACCES) {
+                    /* Framework may not have created yet, sleep and retry */
+                    ERROR("missing packages.list; retrying\n");
+                    sleep(3);
+                    continue;
+                } else {
+                    ERROR("inotify_add_watch failed: %s\n", strerror(errno));
+                    return;
+                }
+            }
+
+            /* Watch above will tell us about any future changes, so
+             * read the current state. */
+            if (read_package_list(fuse) == -1) {
+                ERROR("read_package_list failed: %s\n", strerror(errno));
+                return;
+            }
+            active = true;
+        }
+
+        int event_pos = 0;
+        int res = read(nfd, event_buf, sizeof(event_buf));
+        if (res < (int) sizeof(*event)) {
+            if (errno == EINTR)
+                continue;
+            ERROR("failed to read inotify event: %s\n", strerror(errno));
+            return;
+        }
+
+        while (res >= (int) sizeof(*event)) {
+            int event_size;
+            event = (struct inotify_event *) (event_buf + event_pos);
+
+            TRACE("inotify event: %08x\n", event->mask);
+            if ((event->mask & IN_IGNORED) == IN_IGNORED) {
+                /* Previously watched file was deleted, probably due to move
+                 * that swapped in new data; re-arm the watch and read. */
+                active = false;
+            }
+
+            event_size = sizeof(*event) + event->len;
+            res -= event_size;
+            event_pos += event_size;
+        }
+    }
+}
+
 static int ignite_fuse(struct fuse* fuse, int num_threads)
 {
     struct fuse_handler* handlers;
@@ -1215,7 +1681,7 @@
 
     handlers = malloc(num_threads * sizeof(struct fuse_handler));
     if (!handlers) {
-        ERROR("cannot allocate storage for threads");
+        ERROR("cannot allocate storage for threads\n");
         return -ENOMEM;
     }
 
@@ -1224,16 +1690,25 @@
         handlers[i].token = i;
     }
 
-    for (i = 1; i < num_threads; i++) {
+    /* When deriving permissions, this thread is used to process inotify events,
+     * otherwise it becomes one of the FUSE handlers. */
+    i = (fuse->derive == DERIVE_NONE) ? 1 : 0;
+    for (; i < num_threads; i++) {
         pthread_t thread;
         int res = pthread_create(&thread, NULL, start_handler, &handlers[i]);
         if (res) {
-            ERROR("failed to start thread #%d, error=%d", i, res);
+            ERROR("failed to start thread #%d, error=%d\n", i, res);
             goto quit;
         }
     }
-    handle_fuse_requests(&handlers[0]);
-    ERROR("terminated prematurely");
+
+    if (fuse->derive == DERIVE_NONE) {
+        handle_fuse_requests(&handlers[0]);
+    } else {
+        watch_package_list(fuse);
+    }
+
+    ERROR("terminated prematurely\n");
 
     /* don't bother killing all of the other threads or freeing anything,
      * should never get here anyhow */
@@ -1243,14 +1718,20 @@
 
 static int usage()
 {
-    ERROR("usage: sdcard [-t<threads>] <source_path> <dest_path> <uid> <gid>\n"
-            "    -t<threads>: specify number of threads to use, default -t%d\n"
+    ERROR("usage: sdcard [OPTIONS] <source_path> <dest_path>\n"
+            "    -u: specify UID to run as\n"
+            "    -g: specify GID to run as\n"
+            "    -G: specify default GID for files (default sdcard_r, requires -d or -l)\n"
+            "    -t: specify number of threads to use (default %d)\n"
+            "    -d: derive file permissions based on path\n"
+            "    -l: derive file permissions based on legacy internal layout\n"
+            "    -s: split derived permissions for pics, av\n"
             "\n", DEFAULT_NUM_THREADS);
     return 1;
 }
 
-static int run(const char* source_path, const char* dest_path, uid_t uid, gid_t gid,
-        int num_threads) {
+static int run(const char* source_path, const char* dest_path, uid_t uid,
+        gid_t gid, gid_t fs_gid, int num_threads, derive_t derive, bool split_perms) {
     int fd;
     char opts[256];
     int res;
@@ -1261,7 +1742,7 @@
 
     fd = open("/dev/fuse", O_RDWR);
     if (fd < 0){
-        ERROR("cannot open fuse device (error %d)\n", errno);
+        ERROR("cannot open fuse device: %s\n", strerror(errno));
         return -1;
     }
 
@@ -1271,23 +1752,29 @@
 
     res = mount("/dev/fuse", dest_path, "fuse", MS_NOSUID | MS_NODEV, opts);
     if (res < 0) {
-        ERROR("cannot mount fuse filesystem (error %d)\n", errno);
+        ERROR("cannot mount fuse filesystem: %s\n", strerror(errno));
+        goto error;
+    }
+
+    res = setgroups(sizeof(kGroups) / sizeof(kGroups[0]), kGroups);
+    if (res < 0) {
+        ERROR("cannot setgroups: %s\n", strerror(errno));
         goto error;
     }
 
     res = setgid(gid);
     if (res < 0) {
-        ERROR("cannot setgid (error %d)\n", errno);
+        ERROR("cannot setgid: %s\n", strerror(errno));
         goto error;
     }
 
     res = setuid(uid);
     if (res < 0) {
-        ERROR("cannot setuid (error %d)\n", errno);
+        ERROR("cannot setuid: %s\n", strerror(errno));
         goto error;
     }
 
-    fuse_init(&fuse, fd, source_path);
+    fuse_init(&fuse, fd, source_path, fs_gid, derive, split_perms);
 
     umask(0);
     res = ignite_fuse(&fuse, num_threads);
@@ -1307,34 +1794,53 @@
     const char *dest_path = NULL;
     uid_t uid = 0;
     gid_t gid = 0;
+    gid_t fs_gid = AID_SDCARD_R;
     int num_threads = DEFAULT_NUM_THREADS;
+    derive_t derive = DERIVE_NONE;
+    bool split_perms = false;
     int i;
     struct rlimit rlim;
 
-    for (i = 1; i < argc; i++) {
+    int opt;
+    while ((opt = getopt(argc, argv, "u:g:G:t:dls")) != -1) {
+        switch (opt) {
+            case 'u':
+                uid = strtoul(optarg, NULL, 10);
+                break;
+            case 'g':
+                gid = strtoul(optarg, NULL, 10);
+                break;
+            case 'G':
+                fs_gid = strtoul(optarg, NULL, 10);
+                break;
+            case 't':
+                num_threads = strtoul(optarg, NULL, 10);
+                break;
+            case 'd':
+                derive = DERIVE_UNIFIED;
+                break;
+            case 'l':
+                derive = DERIVE_LEGACY;
+                break;
+            case 's':
+                split_perms = true;
+                break;
+            case '?':
+            default:
+                return usage();
+        }
+    }
+
+    for (i = optind; i < argc; i++) {
         char* arg = argv[i];
-        if (!strncmp(arg, "-t", 2))
-            num_threads = strtoul(arg + 2, 0, 10);
-        else if (!source_path)
+        if (!source_path) {
             source_path = arg;
-        else if (!dest_path)
+        } else if (!dest_path) {
             dest_path = arg;
-        else if (!uid) {
-            char* endptr = NULL;
-            errno = 0;
-            uid = strtoul(arg, &endptr, 10);
-            if (*endptr != '\0' || errno != 0) {
-                ERROR("Invalid uid");
-                return usage();
-            }
+        } else if (!uid) {
+            uid = strtoul(arg, NULL, 10);
         } else if (!gid) {
-            char* endptr = NULL;
-            errno = 0;
-            gid = strtoul(arg, &endptr, 10);
-            if (*endptr != '\0' || errno != 0) {
-                ERROR("Invalid gid");
-                return usage();
-            }
+            gid = strtoul(arg, NULL, 10);
         } else {
             ERROR("too many arguments\n");
             return usage();
@@ -1357,6 +1863,10 @@
         ERROR("number of threads must be at least 1\n");
         return usage();
     }
+    if (split_perms && derive == DERIVE_NONE) {
+        ERROR("cannot split permissions without deriving\n");
+        return usage();
+    }
 
     rlim.rlim_cur = 8192;
     rlim.rlim_max = 8192;
@@ -1364,6 +1874,6 @@
         ERROR("Error setting RLIMIT_NOFILE, errno = %d\n", errno);
     }
 
-    res = run(source_path, dest_path, uid, gid, num_threads);
+    res = run(source_path, dest_path, uid, gid, fs_gid, num_threads, derive, split_perms);
     return res < 0 ? 1 : 0;
 }