Apexd: Refactor directory scans

Add a helper to produce a vector, then use that function in most
APEX scans.

In preparation for extended session handling.

Bug: 119260955
Test: mmma system/apex/apexd
Test: atest apexservice_test
Change-Id: I663c52b0322a760affffefa838a9cd5c8b68b928
diff --git a/apexd/apexd.cpp b/apexd/apexd.cpp
index bae1f3d..9b20018 100644
--- a/apexd/apexd.cpp
+++ b/apexd/apexd.cpp
@@ -375,28 +375,49 @@
   return StatusOr<DmVerityDevice>(std::move(dev));
 }
 
-StatusOr<std::vector<std::string>> getApexRootSubFolders() {
+template <typename FilterFn>
+StatusOr<std::vector<std::string>> ReadDir(const std::string& path,
+                                           FilterFn fn) {
   // This code would be much shorter if C++17's std::filesystem were available,
   // which is not at the time of writing this.
-  auto d = std::unique_ptr<DIR, int (*)(DIR*)>(opendir(kApexRoot), closedir);
+  auto d = std::unique_ptr<DIR, int (*)(DIR*)>(opendir(path.c_str()), closedir);
   if (!d) {
     return StatusOr<std::vector<std::string>>::MakeError(
-        PStringLog() << "Can't open " << kApexRoot << " for reading.");
+        PStringLog() << "Can't open " << path << " for reading.");
   }
 
   std::vector<std::string> ret;
   struct dirent* dp;
   while ((dp = readdir(d.get())) != NULL) {
-    if (dp->d_type != DT_DIR || (strcmp(dp->d_name, ".") == 0) ||
-        (strcmp(dp->d_name, "..") == 0)) {
+    if ((strcmp(dp->d_name, ".") == 0) || (strcmp(dp->d_name, "..") == 0)) {
       continue;
     }
-    ret.push_back(dp->d_name);
+    if (!fn(dp->d_type, dp->d_name)) {
+      continue;
+    }
+    ret.push_back(path + "/" + dp->d_name);
   }
 
   return StatusOr<std::vector<std::string>>(std::move(ret));
 }
 
+template <char kTypeVal>
+bool DTypeFilter(unsigned char d_type, const char* d_name ATTRIBUTE_UNUSED) {
+  return d_type == kTypeVal;
+}
+
+StatusOr<std::vector<std::string>> FindApexFilesByName(const std::string& path,
+                                                       bool include_dirs) {
+  auto filter_fn = [include_dirs](unsigned char d_type, const char* d_name) {
+    if (d_type == DT_REG && EndsWith(d_name, kApexPackageSuffix)) {
+      return true;  // APEX file, take.
+    }
+    // Directory and asked to scan for flattened.
+    return d_type == DT_DIR && include_dirs;
+  };
+  return ReadDir(path, filter_fn);
+}
+
 Status mountNonFlattened(const ApexFile& apex, const std::string& mountPoint,
                          MountedApexData* apex_data) {
   const ApexManifest& manifest = apex.GetManifest();
@@ -766,7 +787,8 @@
   // becomes an actual daemon. Remove if that's the case.
   LOG(INFO) << "Scanning " << kApexRoot
             << " looking for packages already mounted.";
-  StatusOr<std::vector<std::string>> folders_status = getApexRootSubFolders();
+  StatusOr<std::vector<std::string>> folders_status =
+      ReadDir(kApexRoot, &DTypeFilter<DT_DIR>);
   if (!folders_status.Ok()) {
     LOG(ERROR) << folders_status.ErrorMessage();
     return;
@@ -777,8 +799,7 @@
   std::vector<std::string>& folders = *folders_status;
   std::sort(folders.begin(), folders.end());
 
-  for (const std::string& folder : folders) {
-    std::string full_path = std::string(kApexRoot).append("/").append(folder);
+  for (const std::string& full_path : folders) {
     LOG(INFO) << "Unmounting " << full_path;
     // Lazily try to umount whatever is mounted.
     if (umount2(full_path.c_str(), UMOUNT_NOFOLLOW | MNT_DETACH) != 0 &&
@@ -798,31 +819,22 @@
 
 void scanPackagesDirAndActivate(const char* apex_package_dir) {
   LOG(INFO) << "Scanning " << apex_package_dir << " looking for APEX packages.";
-  auto d =
-      std::unique_ptr<DIR, int (*)(DIR*)>(opendir(apex_package_dir), closedir);
 
-  if (!d) {
-    PLOG(WARNING) << "Package directory " << apex_package_dir
-                  << " not found, nothing to do.";
-    return;
-  }
   const bool scanSystemApexes =
       android::base::StartsWith(apex_package_dir, kApexPackageSystemDir);
-  struct dirent* dp;
-  while ((dp = readdir(d.get())) != NULL) {
-    const std::string name(dp->d_name);
-    if (name == "." || name == "..") {
-      continue;
-    }
-    const bool isApexFile =
-        dp->d_type == DT_REG && EndsWith(name, kApexPackageSuffix);
-    if (isApexFile || (dp->d_type == DT_DIR && scanSystemApexes)) {
-      LOG(INFO) << "Found " << name;
+  StatusOr<std::vector<std::string>> scan =
+      FindApexFilesByName(apex_package_dir, scanSystemApexes);
+  if (!scan.Ok()) {
+    LOG(WARNING) << scan.ErrorMessage();
+    return;
+  }
 
-      Status res = activatePackage(std::string(apex_package_dir) + "/" + name);
-      if (!res.Ok()) {
-        LOG(ERROR) << res.ErrorMessage();
-      }
+  for (const std::string& name : *scan) {
+    LOG(INFO) << "Found " << name;
+
+    Status res = activatePackage(name);
+    if (!res.Ok()) {
+      LOG(ERROR) << res.ErrorMessage();
     }
   }
 }
@@ -832,89 +844,57 @@
                                std::to_string(session_id);
   LOG(INFO) << "Scanning " << sessionDirPath
             << " looking for packages to be validated";
-  auto d = std::unique_ptr<DIR, int (*)(DIR*)>(opendir(sessionDirPath.c_str()),
-                                               closedir);
+  StatusOr<std::vector<std::string>> scan =
+      FindApexFilesByName(sessionDirPath, /* include_dirs=*/false);
+  if (!scan.Ok()) {
+    LOG(WARNING) << scan.ErrorMessage();
+    return StatusOr<std::vector<ApexFile>>::MakeError(scan.ErrorMessage());
+  }
 
-  if (!d) {
-    PLOG(WARNING) << "Session directory " << sessionDirPath
-                  << " not found, nothing to do.";
+  // TODO(b/118865310): support sessions of sessions i.e. multi-package
+  // sessions.
+  if (scan->size() > 1) {
     return StatusOr<std::vector<ApexFile>>::MakeError(
-        "Cannot scan session directory.");
+        "More than one APEX package found in the same session directory.");
   }
-  std::vector<std::string> pathsToVerify;
-  struct dirent* sessionDirP;
-  bool apexFileFound = false;
-  while ((sessionDirP = readdir(d.get())) != NULL) {
-    const std::string sessionFileName(sessionDirP->d_name);
-    if (sessionFileName == "." || sessionFileName == "..") {
-      continue;
-    }
-    // TODO(b/118865310): support sessions of sessions i.e. multi-package
-    // sessions.
-    const bool isApexFile =
-        sessionDirP->d_type == DT_REG &&
-        EndsWith(sessionFileName.c_str(), kApexPackageSuffix);
-    if (apexFileFound) {
-      return StatusOr<std::vector<ApexFile>>::MakeError(
-          "More than one APEX package found in the same session directory.");
-    }
-    apexFileFound = true;
-    if (isApexFile) {
-      pathsToVerify.push_back(android::base::StringPrintf(
-          "%s/%s", sessionDirPath.c_str(), sessionDirP->d_name));
-    }
-  }
-  return verifyPackages(pathsToVerify);
+
+  return verifyPackages(*scan);
 }
 
 void scanStagedSessionsDirAndStage() {
   LOG(INFO) << "Scanning " << kStagedSessionsDir
             << " looking for sessions to be activated.";
-  auto d = std::unique_ptr<DIR, int (*)(DIR*)>(opendir(kStagedSessionsDir),
-                                               closedir);
 
-  if (!d) {
-    PLOG(WARNING) << "Session directory " << kStagedSessionsDir
-                  << " not found, nothing to do.";
+  StatusOr<std::vector<std::string>> sessions =
+      ReadDir(kStagedSessionsDir, [](unsigned char d_type, const char* d_name) {
+        return (d_type == DT_DIR) && StartsWith(d_name, "session_");
+      });
+  if (!sessions.Ok()) {
+    LOG(WARNING) << sessions.ErrorMessage();
     return;
   }
-  struct dirent* dp;
-  while ((dp = readdir(d.get())) != NULL) {
-    const std::string name(dp->d_name);
-    if (name == "." || name == "..") {
+
+  for (const std::string sessionDirPath : *sessions) {
+    // TODO(b/118865310): support sessions of sessions i.e. multi-package
+    // sessions.
+    StatusOr<std::vector<std::string>> apexes =
+        FindApexFilesByName(sessionDirPath, /* include_dirs=*/false);
+    if (!apexes.Ok()) {
+      LOG(WARNING) << apexes.ErrorMessage();
       continue;
     }
-    const bool isSessionDir =
-        StartsWith(name, "session_") && (dp->d_type == DT_DIR);
-    if (isSessionDir) {
-      const std::string sessionDirPath =
-          android::base::StringPrintf("%s/%s", kStagedSessionsDir, dp->d_name);
-      auto sessionDir = std::unique_ptr<DIR, int (*)(DIR*)>(
-          opendir(sessionDirPath.c_str()), closedir);
-      struct dirent* sessionDirP;
-      while ((sessionDirP = readdir(sessionDir.get())) != NULL) {
-        const std::string sessionFileName(sessionDirP->d_name);
-        if (sessionFileName == "." || sessionFileName == "..") {
-          continue;
-        }
-        // TODO(b/118865310): support sessions of sessions i.e. multi-package
-        // sessions.
-        const bool isApexFile = sessionDirP->d_type == DT_REG &&
-                                EndsWith(sessionFileName, kApexPackageSuffix);
-        // TODO(b/118865310): double check that there is only one apex file per
-        // dir?
-        if (isApexFile) {
-          const std::string apexFilePath = android::base::StringPrintf(
-              "%s/%s/%s", kStagedSessionsDir, dp->d_name, sessionDirP->d_name);
-          const Status result =
-              stagePackages({apexFilePath}, /* linkPackages */ true);
-          if (!result.Ok()) {
-            LOG(ERROR) << "Activation failed for package " << apexFilePath
-                       << ": " << result.ErrorMessage();
-            // TODO(b/118865310): mark session as failed, rollback the changes
-            // and reboot the device
-          }
-        }
+
+    // TODO(b/118865310): double check that there is only one apex file per
+    // dir?
+
+    for (const std::string& apexFilePath : *apexes) {
+      const Status result =
+          stagePackages({apexFilePath}, /* linkPackages */ true);
+      if (!result.Ok()) {
+        LOG(ERROR) << "Activation failed for package " << apexFilePath << ": "
+                   << result.ErrorMessage();
+        // TODO(b/118865310): mark session as failed, rollback the changes
+        // and reboot the device
       }
     }
   }