canned fs_config accepts multiple lines having the same path

Previously, each file/dir in a filesystem had to have exactly one
matching entry in the canned fs_config file. With this change, the
config file can have multiple entries for the same path. e.g.

/lib/libfoo.so 1000 1000 0644
/lib/libfoo.so 1000 2000 0644 capabilities=0x30

In this case, the last matching entry is chosen and used. This is to
make it possible to customize system-provided (and thus generic)
fs_config file with a user-provided (and thus context-specific) one.

Bug: 209971551
Test: m
Change-Id: I43902fed08db1b4968d02c75fac0a47976fff72a
diff --git a/libcutils/canned_fs_config.cpp b/libcutils/canned_fs_config.cpp
index a7686e3..b677949 100644
--- a/libcutils/canned_fs_config.cpp
+++ b/libcutils/canned_fs_config.cpp
@@ -21,7 +21,6 @@
 #include <android-base/strings.h>
 
 #include <errno.h>
-#include <fnmatch.h>
 #include <inttypes.h>
 #include <limits.h>
 #include <stdio.h>
@@ -67,10 +66,10 @@
         }
 
         // Historical: remove the leading '/' if exists.
-        std::string path = tokens[0].front() == '/' ? std::string(tokens[0], 1) : tokens[0];
+        std::string path(tokens[0].front() == '/' ? std::string(tokens[0], 1) : tokens[0]);
 
         Entry e{
-                .path = path,
+                .path = std::move(path),
                 .uid = static_cast<unsigned int>(atoi(tokens[1].c_str())),
                 .gid = static_cast<unsigned int>(atoi(tokens[2].c_str())),
                 // mode is in octal
@@ -93,8 +92,15 @@
         canned_data.emplace_back(std::move(e));
     }
 
-    std::sort(canned_data.begin(), canned_data.end(),
-              [](const Entry& a, const Entry& b) -> bool { return a.path < b.path; });
+    // Note: we used to sort the entries by path names. This was to improve the lookup performance
+    // by doing binary search. However, this is no longer the case. The lookup performance is not
+    // critical because this tool runs on the host, not on the device. Now, there can be multiple
+    // entries for the same path. Then the one that comes the last wins. This is to allow overriding
+    // platform provided fs_config with a user provided fs_config by appending the latter to the
+    // former.
+    //
+    // To implement the strategy, reverse the entries order, and search from the top.
+    std::reverse(canned_data.begin(), canned_data.end());
 
     std::cout << "loaded " << canned_data.size() << " fs_config entries" << std::endl;
     return 0;
@@ -105,12 +111,15 @@
                       unsigned* mode, uint64_t* capabilities) {
     if (path != nullptr && path[0] == '/') path++;  // canned paths lack the leading '/'
 
-    const Entry* found = static_cast<Entry*>(
-            bsearch(path, &canned_data[0], canned_data.size(), sizeof(Entry),
-                    [](const void* a, const void* b) -> int {
-                        return strcmp(static_cast<const char*>(a),
-                                      static_cast<const Entry*>(b)->path.c_str());
-                    }));
+    const Entry* found = nullptr;
+    // canned_data is already reversed. First match wins.
+    for (const auto& entry : canned_data) {
+        if (path == entry.path) {
+            found = &entry;
+            break;
+        }
+        continue;
+    }
 
     if (found == nullptr) {
         std::cerr << "failed to find " << path << " in canned fs_config" << std::endl;