utils: Refactor findFiles()

The findFiles() iterates the directory recursively. For CPU sensor it
was using depth 6 to iterate the PECI devices, and it results in
iterating all the devices in /sys/bus/platform because it contains a
symlink of `subsystem` to `/sys/bus/platform`.

Refactor this function and make sure it skips iterating if a subdir does
not match the regex.

Tested: On g220a, the iterated files reduce from about 40000 to 400,
        and the function execution time reduces from 30s+ to about 0.5s.

Signed-off-by: Lei YU <yulei.sh@bytedance.com>
Change-Id: I7eada897a0f2c76c405a4efc0a081e00b48070e1
diff --git a/src/Utils.cpp b/src/Utils.cpp
index 20f4c4e..6d017ec 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -192,28 +192,102 @@
     return true;
 }
 
-bool findFiles(const fs::path& dirPath, const std::string& matchString,
+bool findFiles(const fs::path& dirPath, std::string_view matchString,
                std::vector<fs::path>& foundPaths, int symlinkDepth)
 {
-    if (!fs::exists(dirPath))
+    std::error_code ec;
+    if (!fs::exists(dirPath, ec))
     {
         return false;
     }
 
-    std::regex search(matchString);
-    std::smatch match;
+    std::vector<std::regex> matchPieces;
+
+    size_t pos = 0;
+    std::string token;
+    // Generate the regex expressions list from the match we were given
+    while ((pos = matchString.find('/')) != std::string::npos)
+    {
+        token = matchString.substr(0, pos);
+        matchPieces.emplace_back(token);
+        matchString.remove_prefix(pos + 1);
+    }
+    matchPieces.emplace_back(std::string{matchString});
+
+    // Check if the match string contains directories, and skip the match of
+    // subdirectory if not
+    if (matchPieces.size() <= 1)
+    {
+        std::regex search(std::string{matchString});
+        std::smatch match;
+        for (auto p = fs::recursive_directory_iterator(
+                 dirPath, fs::directory_options::follow_directory_symlink);
+             p != fs::recursive_directory_iterator(); ++p)
+        {
+            std::string path = p->path().string();
+            if (!is_directory(*p))
+            {
+                if (std::regex_search(path, match, search))
+                {
+                    foundPaths.emplace_back(p->path());
+                }
+            }
+            if (p.depth() >= symlinkDepth)
+            {
+                p.disable_recursion_pending();
+            }
+        }
+        return true;
+    }
+
+    // The match string contains directories, verify each level of sub
+    // directories
     for (auto p = fs::recursive_directory_iterator(
              dirPath, fs::directory_options::follow_directory_symlink);
          p != fs::recursive_directory_iterator(); ++p)
     {
-        std::string path = p->path().string();
+        std::vector<std::regex>::iterator matchPiece = matchPieces.begin();
+        fs::path::iterator pathIt = p->path().begin();
+        for (const fs::path& dir : dirPath)
+        {
+            if (dir.empty())
+            {
+                // When the path ends with '/', it gets am empty path
+                // skip such case.
+                break;
+            }
+            pathIt++;
+        }
+
+        while (pathIt != p->path().end())
+        {
+            // Found a path deeper than match.
+            if (matchPiece == matchPieces.end())
+            {
+                p.disable_recursion_pending();
+                break;
+            }
+            std::smatch match;
+            std::string component = pathIt->string();
+            std::regex regexPiece(*matchPiece);
+            if (!std::regex_match(component, match, regexPiece))
+            {
+                // path prefix doesn't match, no need to iterate further
+                p.disable_recursion_pending();
+                break;
+            }
+            matchPiece++;
+            pathIt++;
+        }
+
         if (!is_directory(*p))
         {
-            if (std::regex_search(path, match, search))
+            if (matchPiece == matchPieces.end())
             {
                 foundPaths.emplace_back(p->path());
             }
         }
+
         if (p.depth() >= symlinkDepth)
         {
             p.disable_recursion_pending();
@@ -417,7 +491,7 @@
 {
     if (association)
     {
-        std::filesystem::path p(path);
+        fs::path p(path);
 
         std::vector<Association> associations;
         associations.emplace_back("chassis", "all_sensors",
@@ -434,7 +508,7 @@
 {
     if (association)
     {
-        std::filesystem::path p(path);
+        fs::path p(path);
         std::vector<Association> associations;
         std::string objPath(p.parent_path().string());
 
@@ -502,7 +576,7 @@
 }
 
 std::optional<std::tuple<std::string, std::string, std::string>>
-    splitFileName(const std::filesystem::path& filePath)
+    splitFileName(const fs::path& filePath)
 {
     if (filePath.has_filename())
     {