Merge "Versioning for apex init.rc files"
diff --git a/init/README.md b/init/README.md
index 58a8d6b..64c6b1c 100644
--- a/init/README.md
+++ b/init/README.md
@@ -77,6 +77,43 @@
 conflict resolution when multiple services are added to the system, as
 each one will go into a separate file.
 
+Versioned RC files within APEXs
+-------------------------------
+
+With the arrival of mainline on Android Q, the individual mainline
+modules carry their own init.rc files within their boundaries. Init
+processes these files according to the naming pattern `/apex/*/etc/*rc`.
+
+Because APEX modules must run on more than one release of Android,
+they may require different parameters as part of the services they
+define. This is achieved, starting in Android T, by incorporating
+the SDK version information in the name of the init file.  The suffix
+is changed from `.rc` to `.#rc` where # is the first SDK where that
+RC file is accepted. An init file specific to SDK=31 might be named
+`init.31rc`. With this scheme, an APEX may include multiple init files. An
+example is appropriate.
+
+For an APEX module with the following files in /apex/sample-module/apex/etc/:
+
+   1. init.rc
+   2. init.32rc
+   4. init.35rc
+
+The selection rule chooses the highest `.#rc` value that does not
+exceed the SDK of the currently running system. The unadorned `.rc`
+is interpreted as sdk=0.
+
+When this APEX is installed on a device with SDK <=31, the system will
+process init.rc.  When installed on a device running SDK 32, 33, or 34,
+it will use init.32rc.  When installed on a device running SDKs >= 35,
+it will choose init.35rc
+
+This versioning scheme is used only for the init files within APEX
+modules; it does not apply to the init files stored in /system/etc/init,
+/vendor/etc/init, or other directories.
+
+This naming scheme is available after Android S.
+
 Actions
 -------
 Actions are named sequences of commands.  Actions have a trigger which
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) {