Extract format_string function.

Extract format_string function and add a test.

Test: run linker-unit-tests
Change-Id: I794a29aaf62e184438ce1a9224b88aa0586c17b5
diff --git a/linker/linker_soinfo.cpp b/linker/linker_soinfo.cpp
index 6601dc1..fe3a6fb 100644
--- a/linker/linker_soinfo.cpp
+++ b/linker/linker_soinfo.cpp
@@ -81,29 +81,9 @@
 
   std::string origin = dirname(get_realpath());
   // FIXME: add $LIB and $PLATFORM.
-  std::pair<std::string, std::string> substs[] = {{"ORIGIN", origin}};
+  std::vector<std::pair<std::string, std::string>> params = {{"ORIGIN", origin}};
   for (auto&& s : runpaths) {
-    size_t pos = 0;
-    while (pos < s.size()) {
-      pos = s.find("$", pos);
-      if (pos == std::string::npos) break;
-      for (const auto& subst : substs) {
-        const std::string& token = subst.first;
-        const std::string& replacement = subst.second;
-        if (s.substr(pos + 1, token.size()) == token) {
-          s.replace(pos, token.size() + 1, replacement);
-          // -1 to compensate for the ++pos below.
-          pos += replacement.size() - 1;
-          break;
-        } else if (s.substr(pos + 1, token.size() + 2) == "{" + token + "}") {
-          s.replace(pos, token.size() + 3, replacement);
-          pos += replacement.size() - 1;
-          break;
-        }
-      }
-      // Skip $ in case it did not match any of the known substitutions.
-      ++pos;
-    }
+    format_string(&s, params);
   }
 
   resolve_paths(runpaths, &dt_runpath_);
diff --git a/linker/linker_utils.cpp b/linker/linker_utils.cpp
index 6df5f6d..5bf88e7 100644
--- a/linker/linker_utils.cpp
+++ b/linker/linker_utils.cpp
@@ -36,6 +36,30 @@
 #include <sys/stat.h>
 #include <unistd.h>
 
+void format_string(std::string* str, const std::vector<std::pair<std::string, std::string>>& params) {
+  size_t pos = 0;
+  while (pos < str->size()) {
+    pos = str->find("$", pos);
+    if (pos == std::string::npos) break;
+    for (const auto& param : params) {
+      const std::string& token = param.first;
+      const std::string& replacement = param.second;
+      if (str->substr(pos + 1, token.size()) == token) {
+        str->replace(pos, token.size() + 1, replacement);
+        // -1 to compensate for the ++pos below.
+        pos += replacement.size() - 1;
+        break;
+      } else if (str->substr(pos + 1, token.size() + 2) == "{" + token + "}") {
+        str->replace(pos, token.size() + 3, replacement);
+        pos += replacement.size() - 1;
+        break;
+      }
+    }
+    // Skip $ in case it did not match any of the known substitutions.
+    ++pos;
+  }
+}
+
 std::string dirname(const char* path) {
   const char* last_slash = strrchr(path, '/');
 
diff --git a/linker/linker_utils.h b/linker/linker_utils.h
index 5881688..740d04b 100644
--- a/linker/linker_utils.h
+++ b/linker/linker_utils.h
@@ -34,6 +34,8 @@
 
 extern const char* const kZipFileSeparator;
 
+void format_string(std::string* str, const std::vector<std::pair<std::string, std::string>>& params);
+
 bool file_is_in_dir(const std::string& file, const std::string& dir);
 bool file_is_under_dir(const std::string& file, const std::string& dir);
 bool normalize_path(const char* path, std::string* normalized_path);
diff --git a/linker/tests/linker_utils_test.cpp b/linker/tests/linker_utils_test.cpp
index 0cfdf40..dce223a 100644
--- a/linker/tests/linker_utils_test.cpp
+++ b/linker/tests/linker_utils_test.cpp
@@ -34,6 +34,13 @@
 
 #include "../linker_utils.h"
 
+TEST(linker_utils, format_string) {
+  std::vector<std::pair<std::string, std::string>> params = {{ "LIB", "lib32"}, { "SDKVER", "42"}};
+  std::string str_smoke = "LIB$LIB${LIB${SDKVER}SDKVER$TEST$";
+  format_string(&str_smoke, params);
+  ASSERT_EQ("LIBlib32${LIB42SDKVER$TEST$", str_smoke);
+}
+
 TEST(linker_utils, normalize_path_smoke) {
   std::string output;
   ASSERT_TRUE(normalize_path("/../root///dir/.///dir2/somedir/../zipfile!/dir/dir9//..///afile", &output));