Merge "Fix overflow in path building" into nyc-dev
diff --git a/adb/commandline.cpp b/adb/commandline.cpp
index 5aa878b..3aa9199 100644
--- a/adb/commandline.cpp
+++ b/adb/commandline.cpp
@@ -65,6 +65,9 @@
 static auto& gProductOutPath = *new std::string();
 extern int gListenAll;
 
+static constexpr char BUGZ_OK_PREFIX[] = "OK:";
+static constexpr char BUGZ_FAIL_PREFIX[] = "FAIL:";
+
 static std::string product_file(const char *extra) {
     if (gProductOutPath.empty()) {
         fprintf(stderr, "adb: Product directory not specified; "
@@ -170,7 +173,7 @@
         "                                 (-g: grant all runtime permissions)\n"
         "  adb uninstall [-k] <package> - remove this app package from the device\n"
         "                                 ('-k' means keep the data and cache directories)\n"
-        "  adb bugreport                - return all information from the device\n"
+        "  adb bugreport [<zip_file>]   - return all information from the device\n"
         "                                 that should be included in a bug report.\n"
         "\n"
         "  adb backup [-f <file>] [-apk|-noapk] [-obb|-noobb] [-shared|-noshared] [-all] [-system|-nosystem] [<packages...>]\n"
@@ -285,7 +288,8 @@
 // this expects that incoming data will use the shell protocol, in which case
 // stdout/stderr are routed independently and the remote exit code will be
 // returned.
-static int read_and_dump(int fd, bool use_shell_protocol=false) {
+// if |output| is non-null, stdout will be appended to it instead.
+static int read_and_dump(int fd, bool use_shell_protocol=false, std::string* output=nullptr) {
     int exit_code = 0;
     std::unique_ptr<ShellProtocol> protocol;
     int length = 0;
@@ -330,8 +334,12 @@
             }
         }
 
-        fwrite(buffer_ptr, 1, length, outfile);
-        fflush(outfile);
+        if (output == nullptr) {
+            fwrite(buffer_ptr, 1, length, outfile);
+            fflush(outfile);
+        } else {
+            output->append(buffer_ptr, length);
+        }
     }
 
     return exit_code;
@@ -1121,7 +1129,8 @@
 // resulting output.
 static int send_shell_command(TransportType transport_type, const char* serial,
                               const std::string& command,
-                              bool disable_shell_protocol) {
+                              bool disable_shell_protocol,
+                              std::string* output=nullptr) {
     int fd;
     bool use_shell_protocol = false;
 
@@ -1156,7 +1165,7 @@
         }
     }
 
-    int exit_code = read_and_dump(fd, use_shell_protocol);
+    int exit_code = read_and_dump(fd, use_shell_protocol, output);
 
     if (adb_close(fd) < 0) {
         PLOG(ERROR) << "failure closing FD " << fd;
@@ -1165,6 +1174,40 @@
     return exit_code;
 }
 
+static int bugreport(TransportType transport_type, const char* serial, int argc,
+                     const char** argv) {
+    // No need for shell protocol with bugreport, always disable for simplicity.
+    if (argc == 1) return send_shell_command(transport_type, serial, "bugreport", true);
+    if (argc != 2) return usage();
+
+    // Zipped bugreport option - will call 'bugreportz', which prints the location of the generated
+    // file, then pull it to the destination file provided by the user.
+    const char* dest_file = argv[1];
+    std::string output;
+
+    int status = send_shell_command(transport_type, serial, "bugreportz", true, &output);
+    if (status != 0 || output.empty()) return status;
+    output = android::base::Trim(output);
+
+    if (android::base::StartsWith(output, BUGZ_OK_PREFIX)) {
+        const char* zip_file = &output[strlen(BUGZ_OK_PREFIX)];
+        std::vector<const char*> srcs{zip_file};
+        status = do_sync_pull(srcs, dest_file, true, dest_file) ? 0 : 1;
+        if (status != 0) {
+            fprintf(stderr, "Could not copy file '%s' to '%s'\n", zip_file, dest_file);
+        }
+        return status;
+    }
+    if (android::base::StartsWith(output, BUGZ_FAIL_PREFIX)) {
+        const char* error_message = &output[strlen(BUGZ_FAIL_PREFIX)];
+        fprintf(stderr, "device failed to take a zipped bugreport: %s\n", error_message);
+        return -1;
+    }
+    fprintf(stderr, "unexpected string (%s) returned by bugreportz, "
+            "device probably does not support -z option\n", output.c_str());
+    return -1;
+}
+
 static int logcat(TransportType transport, const char* serial, int argc, const char** argv) {
     char* log_tags = getenv("ANDROID_LOG_TAGS");
     std::string quoted = escape_arg(log_tags == nullptr ? "" : log_tags);
@@ -1694,12 +1737,8 @@
     } else if (!strcmp(argv[0], "root") || !strcmp(argv[0], "unroot")) {
         return adb_root(argv[0]) ? 0 : 1;
     } else if (!strcmp(argv[0], "bugreport")) {
-        if (argc != 1) return usage();
-        // No need for shell protocol with bugreport, always disable for
-        // simplicity.
-        return send_shell_command(transport_type, serial, "bugreport", true);
-    }
-    else if (!strcmp(argv[0], "forward") || !strcmp(argv[0], "reverse")) {
+        return bugreport(transport_type, serial, argc, argv);
+    } else if (!strcmp(argv[0], "forward") || !strcmp(argv[0], "reverse")) {
         bool reverse = !strcmp(argv[0], "reverse");
         ++argv;
         --argc;
diff --git a/adb/file_sync_client.cpp b/adb/file_sync_client.cpp
index 6a9e163..651e8ca 100644
--- a/adb/file_sync_client.cpp
+++ b/adb/file_sync_client.cpp
@@ -518,7 +518,8 @@
     return sc.CopyDone(lpath, rpath);
 }
 
-static bool sync_recv(SyncConnection& sc, const char* rpath, const char* lpath) {
+static bool sync_recv(SyncConnection& sc, const char* rpath, const char* lpath,
+                      const char* name=nullptr) {
     unsigned size = 0;
     if (!sync_stat(sc, rpath, nullptr, nullptr, &size)) return false;
 
@@ -574,7 +575,7 @@
 
         bytes_copied += msg.data.size;
 
-        sc.ReportProgress(rpath, bytes_copied, size);
+        sc.ReportProgress(name != nullptr ? name : rpath, bytes_copied, size);
     }
 
     adb_close(lfd);
@@ -927,7 +928,7 @@
 }
 
 bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst,
-                  bool copy_attrs) {
+                  bool copy_attrs, const char* name) {
     SyncConnection sc;
     if (!sc.IsValid()) return false;
 
@@ -1022,7 +1023,7 @@
         }
 
         sc.SetExpectedTotalBytes(src_size);
-        if (!sync_recv(sc, src_path, dst_path)) {
+        if (!sync_recv(sc, src_path, dst_path, name)) {
             success = false;
             continue;
         }
diff --git a/adb/file_sync_service.h b/adb/file_sync_service.h
index 460e9dc..0e25974 100644
--- a/adb/file_sync_service.h
+++ b/adb/file_sync_service.h
@@ -67,7 +67,7 @@
 bool do_sync_ls(const char* path);
 bool do_sync_push(const std::vector<const char*>& srcs, const char* dst);
 bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst,
-                  bool copy_attrs);
+                  bool copy_attrs, const char* name=nullptr);
 
 bool do_sync_sync(const std::string& lpath, const std::string& rpath, bool list_only);
 
diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp
index fa6f594..08fc5af 100644
--- a/bootstat/bootstat.cpp
+++ b/bootstat/bootstat.cpp
@@ -136,13 +136,41 @@
   return kUnknownBootReason;
 }
 
+// Returns the appropriate metric key prefix for the boot_complete metric such
+// that boot metrics after a system update are labeled as ota_boot_complete;
+// otherwise, they are labeled as boot_complete.  This method encapsulates the
+// bookkeeping required to track when a system update has occurred by storing
+// the UTC timestamp of the system build date and comparing against the current
+// system build date.
+std::string CalculateBootCompletePrefix() {
+  static const std::string kBuildDateKey = "build_date";
+  std::string boot_complete_prefix = "boot_complete";
+
+  std::string build_date_str = GetProperty("ro.build.date.utc");
+  int32_t build_date = std::stoi(build_date_str);
+
+  BootEventRecordStore boot_event_store;
+  BootEventRecordStore::BootEventRecord record;
+  if (!boot_event_store.GetBootEvent(kBuildDateKey, &record) ||
+      build_date != record.second) {
+    boot_complete_prefix = "ota_" + boot_complete_prefix;
+    boot_event_store.AddBootEventWithValue(kBuildDateKey, build_date);
+  }
+
+  return boot_complete_prefix;
+}
+
 // Records several metrics related to the time it takes to boot the device,
 // including disambiguating boot time on encrypted or non-encrypted devices.
 void RecordBootComplete() {
   BootEventRecordStore boot_event_store;
+  BootEventRecordStore::BootEventRecord record;
   time_t uptime = bootstat::ParseUptime();
 
-  BootEventRecordStore::BootEventRecord record;
+  // The boot_complete metric has two variants: boot_complete and
+  // ota_boot_complete.  The latter signifies that the device is booting after
+  // a system update.
+  std::string boot_complete_prefix = CalculateBootCompletePrefix();
 
   // post_decrypt_time_elapsed is only logged on encrypted devices.
   if (boot_event_store.GetBootEvent("post_decrypt_time_elapsed", &record)) {
@@ -153,18 +181,18 @@
 
     // Subtract the decryption time to normalize the boot cycle timing.
     time_t boot_complete = uptime - record.second;
-    boot_event_store.AddBootEventWithValue("boot_complete_post_decrypt",
+    boot_event_store.AddBootEventWithValue(boot_complete_prefix + "_post_decrypt",
                                            boot_complete);
 
 
   } else {
-    boot_event_store.AddBootEventWithValue("boot_complete_no_encryption",
+    boot_event_store.AddBootEventWithValue(boot_complete_prefix + "_no_encryption",
                                            uptime);
   }
 
   // Record the total time from device startup to boot complete, regardless of
   // encryption state.
-  boot_event_store.AddBootEventWithValue("boot_complete", uptime);
+  boot_event_store.AddBootEventWithValue(boot_complete_prefix, uptime);
 }
 
 // Records the boot_reason metric by querying the ro.boot.bootreason system
diff --git a/fs_mgr/fs_mgr.c b/fs_mgr/fs_mgr.c
index bc072bc..70acd38 100644
--- a/fs_mgr/fs_mgr.c
+++ b/fs_mgr/fs_mgr.c
@@ -626,11 +626,18 @@
             }
             encryptable = FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED;
         } else {
-            ERROR("Failed to mount an un-encryptable or wiped partition on"
-                   "%s at %s options: %s error: %s\n",
-                   fstab->recs[attempted_idx].blk_device, fstab->recs[attempted_idx].mount_point,
-                   fstab->recs[attempted_idx].fs_options, strerror(mount_errno));
-            ++error_count;
+            if (fs_mgr_is_nofail(&fstab->recs[attempted_idx])) {
+                ERROR("Ignoring failure to mount an un-encryptable or wiped partition on"
+                       "%s at %s options: %s error: %s\n",
+                       fstab->recs[attempted_idx].blk_device, fstab->recs[attempted_idx].mount_point,
+                       fstab->recs[attempted_idx].fs_options, strerror(mount_errno));
+            } else {
+                ERROR("Failed to mount an un-encryptable or wiped partition on"
+                       "%s at %s options: %s error: %s\n",
+                       fstab->recs[attempted_idx].blk_device, fstab->recs[attempted_idx].mount_point,
+                       fstab->recs[attempted_idx].fs_options, strerror(mount_errno));
+                ++error_count;
+            }
             continue;
         }
     }
diff --git a/fs_mgr/fs_mgr_fstab.c b/fs_mgr/fs_mgr_fstab.c
index c8c624d..6d44e06 100644
--- a/fs_mgr/fs_mgr_fstab.c
+++ b/fs_mgr/fs_mgr_fstab.c
@@ -76,6 +76,7 @@
     { "notrim",       MF_NOTRIM },
     { "formattable", MF_FORMATTABLE },
     { "slotselect",  MF_SLOTSELECT },
+    { "nofail",      MF_NOFAIL },
     { "defaults",    0 },
     { 0,             0 },
 };
@@ -502,3 +503,8 @@
 {
     return fstab->fs_mgr_flags & MF_SLOTSELECT;
 }
+
+int fs_mgr_is_nofail(struct fstab_rec *fstab)
+{
+    return fstab->fs_mgr_flags & MF_NOFAIL;
+}
diff --git a/fs_mgr/fs_mgr_priv.h b/fs_mgr/fs_mgr_priv.h
index 181b6cd..46975f1 100644
--- a/fs_mgr/fs_mgr_priv.h
+++ b/fs_mgr/fs_mgr_priv.h
@@ -83,6 +83,7 @@
 #define MF_FORMATTABLE  0x4000
 #define MF_SLOTSELECT   0x8000
 #define MF_FORCEFDEORFBE 0x10000
+#define MF_NOFAIL       0x40000
 
 #define DM_BUF_SIZE 4096
 
diff --git a/fs_mgr/include/fs_mgr.h b/fs_mgr/include/fs_mgr.h
index 0404dbd..6f4580e 100644
--- a/fs_mgr/include/fs_mgr.h
+++ b/fs_mgr/include/fs_mgr.h
@@ -106,6 +106,7 @@
 int fs_mgr_is_noemulatedsd(const struct fstab_rec *fstab);
 int fs_mgr_is_notrim(struct fstab_rec *fstab);
 int fs_mgr_is_formattable(struct fstab_rec *fstab);
+int fs_mgr_is_nofail(struct fstab_rec *fstab);
 int fs_mgr_swapon_all(struct fstab *fstab);
 
 int fs_mgr_do_format(struct fstab_rec *fstab);
diff --git a/init/init.cpp b/init/init.cpp
index 0ff55a5..84da2b9 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -141,59 +141,19 @@
             });
 }
 
-static void msg_start(const std::string& name)
-{
-    Service* svc = nullptr;
-    std::vector<std::string> vargs;
-
-    size_t colon_pos = name.find(':');
-    if (colon_pos == std::string::npos) {
-        svc = ServiceManager::GetInstance().FindServiceByName(name);
-    } else {
-        std::string service_name(name.substr(0, colon_pos));
-        std::string args(name.substr(colon_pos + 1));
-        vargs = android::base::Split(args, " ");
-
-        svc = ServiceManager::GetInstance().FindServiceByName(service_name);
-    }
-
-    if (svc) {
-        svc->Start(vargs);
-    } else {
-        ERROR("no such service '%s'\n", name.c_str());
-    }
-}
-
-static void msg_stop(const std::string& name)
-{
+void handle_control_message(const std::string& msg, const std::string& name) {
     Service* svc = ServiceManager::GetInstance().FindServiceByName(name);
-
-    if (svc) {
-        svc->Stop();
-    } else {
+    if (svc == nullptr) {
         ERROR("no such service '%s'\n", name.c_str());
+        return;
     }
-}
 
-static void msg_restart(const std::string& name)
-{
-    Service* svc = ServiceManager::GetInstance().FindServiceByName(name);
-
-    if (svc) {
-        svc->Restart();
-    } else {
-        ERROR("no such service '%s'\n", name.c_str());
-    }
-}
-
-void handle_control_message(const std::string& msg, const std::string& arg)
-{
     if (msg == "start") {
-        msg_start(arg);
+        svc->Start();
     } else if (msg == "stop") {
-        msg_stop(arg);
+        svc->Stop();
     } else if (msg == "restart") {
-        msg_restart(arg);
+        svc->Restart();
     } else {
         ERROR("unknown control msg '%s'\n", msg.c_str());
     }
diff --git a/init/service.cpp b/init/service.cpp
index bdecc32..f1ffa18 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -315,7 +315,7 @@
     return (this->*handler)(args, err);
 }
 
-bool Service::Start(const std::vector<std::string>& dynamic_args) {
+bool Service::Start() {
     // Starting a service removes it from the disabled or reset state and
     // immediately takes it out of the restarting state if it was in there.
     flags_ &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));
@@ -343,13 +343,6 @@
         return false;
     }
 
-    if ((!(flags_ & SVC_ONESHOT)) && !dynamic_args.empty()) {
-        ERROR("service '%s' must be one-shot to use dynamic args, disabling\n",
-              args_[0].c_str());
-        flags_ |= SVC_DISABLED;
-        return false;
-    }
-
     std::string scon;
     if (!seclabel_.empty()) {
         scon = seclabel_;
@@ -471,9 +464,6 @@
         for (const auto& s : args_) {
             strs.push_back(const_cast<char*>(s.c_str()));
         }
-        for (const auto& s : dynamic_args) {
-            strs.push_back(const_cast<char*>(s.c_str()));
-        }
         strs.push_back(nullptr);
         if (execve(args_[0].c_str(), (char**) &strs[0], (char**) ENV) < 0) {
             ERROR("cannot execve('%s'): %s\n", args_[0].c_str(), strerror(errno));
@@ -502,11 +492,6 @@
     return true;
 }
 
-bool Service::Start() {
-    const std::vector<std::string> null_dynamic_args;
-    return Start(null_dynamic_args);
-}
-
 bool Service::StartIfNotDisabled() {
     if (!(flags_ & SVC_DISABLED)) {
         return Start();
diff --git a/init/service.h b/init/service.h
index 35abde9..d9d18ef 100644
--- a/init/service.h
+++ b/init/service.h
@@ -76,7 +76,6 @@
             const std::string& seclabel,  const std::vector<std::string>& args);
 
     bool HandleLine(const std::vector<std::string>& args, std::string* err);
-    bool Start(const std::vector<std::string>& dynamic_args);
     bool Start();
     bool StartIfNotDisabled();
     bool Enable();
diff --git a/libcutils/sched_policy.c b/libcutils/sched_policy.c
index 1a26695..b399643 100644
--- a/libcutils/sched_policy.c
+++ b/libcutils/sched_policy.c
@@ -148,7 +148,7 @@
 }
 
 /*
- * Try to get the scheduler group.
+ * Returns the path under the requested cgroup subsystem (if it exists)
  *
  * The data from /proc/<pid>/cgroup looks (something) like:
  *  2:cpu:/bg_non_interactive
@@ -158,7 +158,7 @@
  * the default cgroup.  If the string is longer than "bufLen", the string
  * will be truncated.
  */
-static int getSchedulerGroup(int tid, char* buf, size_t bufLen)
+static int getCGroupSubsys(int tid, const char* subsys, char* buf, size_t bufLen)
 {
 #if defined(__ANDROID__)
     char pathBuf[32];
@@ -172,7 +172,7 @@
 
     while(fgets(lineBuf, sizeof(lineBuf) -1, fp)) {
         char *next = lineBuf;
-        char *subsys;
+        char *found_subsys;
         char *grp;
         size_t len;
 
@@ -181,11 +181,11 @@
             goto out_bad_data;
         }
 
-        if (!(subsys = strsep(&next, ":"))) {
+        if (!(found_subsys = strsep(&next, ":"))) {
             goto out_bad_data;
         }
 
-        if (strcmp(subsys, "cpu")) {
+        if (strcmp(found_subsys, subsys)) {
             /* Not the subsys we're looking for */
             continue;
         }
@@ -206,7 +206,7 @@
         return 0;
     }
 
-    SLOGE("Failed to find cpu subsys");
+    SLOGE("Failed to find subsys %s", subsys);
     fclose(fp);
     return -1;
  out_bad_data:
@@ -228,7 +228,23 @@
 
     if (__sys_supports_schedgroups) {
         char grpBuf[32];
-        if (getSchedulerGroup(tid, grpBuf, sizeof(grpBuf)) < 0)
+#ifdef USE_CPUSETS
+        if (getCGroupSubsys(tid, "cpuset", grpBuf, sizeof(grpBuf)) < 0)
+            return -1;
+        if (grpBuf[0] == '\0') {
+            *policy = SP_FOREGROUND;
+        } else if (!strcmp(grpBuf, "foreground")) {
+            *policy = SP_FOREGROUND;
+        } else if (!strcmp(grpBuf, "background")) {
+            *policy = SP_BACKGROUND;
+        } else if (!strcmp(grpBuf, "top-app")) {
+            *policy = SP_TOP_APP;
+        } else {
+            errno = ERANGE;
+            return -1;
+        }
+#else
+        if (getCGroupSubsys(tid, "cpu", grpBuf, sizeof(grpBuf)) < 0)
             return -1;
         if (grpBuf[0] == '\0') {
             *policy = SP_FOREGROUND;
@@ -238,6 +254,7 @@
             errno = ERANGE;
             return -1;
         }
+#endif
     } else {
         int rc = sched_getscheduler(tid);
         if (rc < 0)
diff --git a/libnativeloader/native_loader.cpp b/libnativeloader/native_loader.cpp
index ec7a941..f28a693 100644
--- a/libnativeloader/native_loader.cpp
+++ b/libnativeloader/native_loader.cpp
@@ -37,7 +37,8 @@
 namespace android {
 
 #if defined(__ANDROID__)
-static constexpr const char* kPublicNativeLibrariesConfig = "/system/etc/public.libraries.txt";
+static constexpr const char* kPublicNativeLibrariesSystemConfig = "/system/etc/public.libraries.txt";
+static constexpr const char* kPublicNativeLibrariesVendorConfig = "/vendor/etc/public.libraries.txt";
 
 static bool namespace_workaround_enabled(int32_t target_sdk_version) {
   return target_sdk_version <= 23;
@@ -107,35 +108,51 @@
   }
 
   void Initialize() {
+    std::vector<std::string> sonames;
+
+    LOG_ALWAYS_FATAL_IF(!ReadConfig(kPublicNativeLibrariesSystemConfig, &sonames),
+                        "Error reading public native library list from \"%s\": %s",
+                        kPublicNativeLibrariesSystemConfig, strerror(errno));
+    // This file is optional, quietly ignore if the file does not exist.
+    ReadConfig(kPublicNativeLibrariesVendorConfig, &sonames);
+
+    // android_init_namespaces() expects all the public libraries
+    // to be loaded so that they can be found by soname alone.
+    //
+    // TODO(dimitry): this is a bit misleading since we do not know
+    // if the vendor public library is going to be opened from /vendor/lib
+    // we might as well end up loading them from /system/lib
+    // For now we rely on CTS test to catch things like this but
+    // it should probably be addressed in the future.
+    for (const auto& soname : sonames) {
+      dlopen(soname.c_str(), RTLD_NOW | RTLD_NODELETE);
+    }
+
+    public_libraries_ = base::Join(sonames, ':');
+  }
+
+ private:
+  bool ReadConfig(const std::string& configFile, std::vector<std::string>* sonames) {
     // Read list of public native libraries from the config file.
     std::string file_content;
-    LOG_ALWAYS_FATAL_IF(!base::ReadFileToString(kPublicNativeLibrariesConfig, &file_content),
-                        "Error reading public native library list from \"%s\": %s",
-                        kPublicNativeLibrariesConfig, strerror(errno));
+    if(!base::ReadFileToString(configFile, &file_content)) {
+      return false;
+    }
 
     std::vector<std::string> lines = base::Split(file_content, "\n");
 
-    std::vector<std::string> sonames;
-
     for (const auto& line : lines) {
       auto trimmed_line = base::Trim(line);
       if (trimmed_line[0] == '#' || trimmed_line.empty()) {
         continue;
       }
 
-      sonames.push_back(trimmed_line);
+      sonames->push_back(trimmed_line);
     }
 
-    public_libraries_ = base::Join(sonames, ':');
-
-    // android_init_namespaces() expects all the public libraries
-    // to be loaded so that they can be found by soname alone.
-    for (const auto& soname : sonames) {
-      dlopen(soname.c_str(), RTLD_NOW | RTLD_NODELETE);
-    }
+    return true;
   }
 
- private:
   bool InitPublicNamespace(const char* library_path, int32_t target_sdk_version) {
     std::string publicNativeLibraries = public_libraries_;
 
diff --git a/rootdir/init.rc b/rootdir/init.rc
index a95eaa2..3e2f0a5 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -87,6 +87,7 @@
 
     # Symlink to keep legacy apps working in multi-user world
     symlink /storage/self/primary /sdcard
+    symlink /storage/self/primary /mnt/sdcard
     symlink /mnt/user/0/primary /mnt/runtime/default/self/primary
 
     # root memory control cgroup, used by lmkd