Versioning for apex init.rc files

Support an "init.#rc" versioning scheme for apex init files.
chooses highest # <= current system sdk.
".rc" (aka the old init.rc) is treated as sdk 0
Document these semantics in README.md

Bug: 198186200
Test: booting, lots of logcat output
Change-Id: I2d0405be73dae9bafa6f22535b29ed6b053ddbc4
diff --git a/init/builtins.cpp b/init/builtins.cpp
index 994eed9..763a147 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -28,6 +28,7 @@
 #include <net/if.h>
 #include <sched.h>
 #include <signal.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -42,6 +43,7 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
+#include <map>
 #include <memory>
 
 #include <ApexProperties.sysprop.h>
@@ -1313,7 +1315,7 @@
 
 static Result<void> parse_apex_configs() {
     glob_t glob_result;
-    static constexpr char glob_pattern[] = "/apex/*/etc/*.rc";
+    static constexpr char glob_pattern[] = "/apex/*/etc/*rc";
     const int ret = glob(glob_pattern, GLOB_MARK, nullptr, &glob_result);
     if (ret != 0 && ret != GLOB_NOMATCH) {
         globfree(&glob_result);
@@ -1330,17 +1332,66 @@
         if (paths.size() >= 3 && paths[2].find('@') != std::string::npos) {
             continue;
         }
+        // Filter directories
+        if (path.back() == '/') {
+            continue;
+        }
         configs.push_back(path);
     }
     globfree(&glob_result);
 
-    bool success = true;
+    // Compare all files /apex/path.#rc and /apex/path.rc with the same "/apex/path" prefix,
+    // choosing the one with the highest # that doesn't exceed the system's SDK.
+    // (.rc == .0rc for ranking purposes)
+    //
+    int active_sdk = android::base::GetIntProperty("ro.build.version.sdk", INT_MAX);
+
+    std::map<std::string, std::pair<std::string, int>> script_map;
+
     for (const auto& c : configs) {
-        if (c.back() == '/') {
-            // skip if directory
+        int sdk = 0;
+        const std::vector<std::string> parts = android::base::Split(c, ".");
+        std::string base;
+        if (parts.size() < 2) {
             continue;
         }
-        success &= parser.ParseConfigFile(c);
+
+        // parts[size()-1], aka the suffix, should be "rc" or "#rc"
+        // any other pattern gets discarded
+
+        const auto& suffix = parts[parts.size() - 1];
+        if (suffix == "rc") {
+            sdk = 0;
+        } else {
+            char trailer[9] = {0};
+            int r = sscanf(suffix.c_str(), "%d%8s", &sdk, trailer);
+            if (r != 2) {
+                continue;
+            }
+            if (strlen(trailer) > 2 || strcmp(trailer, "rc") != 0) {
+                continue;
+            }
+        }
+
+        if (sdk < 0 || sdk > active_sdk) {
+            continue;
+        }
+
+        base = parts[0];
+        for (unsigned int i = 1; i < parts.size() - 1; i++) {
+            base = base + "." + parts[i];
+        }
+
+        // is this preferred over what we already have
+        auto it = script_map.find(base);
+        if (it == script_map.end() || it->second.second < sdk) {
+            script_map[base] = std::make_pair(c, sdk);
+        }
+    }
+
+    bool success = true;
+    for (const auto& m : script_map) {
+        success &= parser.ParseConfigFile(m.second.first);
     }
     ServiceList::GetInstance().MarkServicesUpdate();
     if (success) {