Create a host side checker for property info file correctness

Bug: 36001741
Test: verify a valid property info file and fail due to various failures
Change-Id: Iadd38796aa619f87ec559fe5687bbe2009df8b2d
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 439ab39..7aa94b0 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -58,7 +58,6 @@
 
 #include "init.h"
 #include "persistent_properties.h"
-#include "space_tokenizer.h"
 #include "util.h"
 
 using android::base::ReadFileToString;
@@ -69,6 +68,7 @@
 using android::base::Trim;
 using android::base::WriteStringToFile;
 using android::properties::BuildTrie;
+using android::properties::ParsePropertyInfoFile;
 using android::properties::PropertyInfoAreaFile;
 using android::properties::PropertyInfoEntry;
 
@@ -728,22 +728,6 @@
     return 0;
 }
 
-Result<PropertyInfoEntry> ParsePropertyInfoLine(const std::string& line) {
-    auto tokenizer = SpaceTokenizer(line);
-
-    auto property = tokenizer.GetNext();
-    if (property.empty()) return Error() << "Did not find a property entry in '" << line << "'";
-
-    auto context = tokenizer.GetNext();
-    if (context.empty()) return Error() << "Did not find a context entry in '" << line << "'";
-
-    // It is not an error to not find these, as older files will not contain them.
-    auto exact_match = tokenizer.GetNext();
-    auto schema = tokenizer.GetRemaining();
-
-    return {property, context, schema, exact_match == "exact"};
-}
-
 bool LoadPropertyInfoFromFile(const std::string& filename,
                               std::vector<PropertyInfoEntry>* property_infos) {
     auto file_contents = std::string();
@@ -752,20 +736,14 @@
         return false;
     }
 
-    for (const auto& line : Split(file_contents, "\n")) {
-        auto trimmed_line = Trim(line);
-        if (trimmed_line.empty() || StartsWith(trimmed_line, "#")) {
-            continue;
-        }
-
-        auto property_info = ParsePropertyInfoLine(line);
-        if (!property_info) {
-            LOG(ERROR) << "Could not read line from '" << filename << "': " << property_info.error();
-            continue;
-        }
-
-        property_infos->emplace_back(*property_info);
+    auto errors = std::vector<std::string>{};
+    ParsePropertyInfoFile(file_contents, property_infos, &errors);
+    // Individual parsing errors are reported but do not cause a failed boot, which is what
+    // returning false would do here.
+    for (const auto& error : errors) {
+        LOG(ERROR) << "Could not read line from '" << filename << "': " << error;
     }
+
     return true;
 }
 
diff --git a/init/space_tokenizer.h b/init/space_tokenizer.h
deleted file mode 100644
index e7e22c5..0000000
--- a/init/space_tokenizer.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _INIT_SPACE_TOKENIZER_H
-#define _INIT_SPACE_TOKENIZER_H
-
-namespace android {
-namespace init {
-
-class SpaceTokenizer {
-  public:
-    SpaceTokenizer(const std::string& string)
-        : string_(string), it_(string_.begin()), end_(string_.end()) {}
-
-    std::string GetNext() {
-        auto next = std::string();
-        while (it_ != end_ && !isspace(*it_)) {
-            next.push_back(*it_++);
-        }
-        while (it_ != end_ && isspace(*it_)) {
-            it_++;
-        }
-        return next;
-    }
-
-    std::string GetRemaining() { return std::string(it_, end_); }
-
-  private:
-    std::string string_;
-    std::string::const_iterator it_;
-    std::string::const_iterator end_;
-};
-
-}  // namespace init
-}  // namespace android
-
-#endif
diff --git a/property_service/libpropertyinfoparser/Android.bp b/property_service/libpropertyinfoparser/Android.bp
index 3e732b5..c046417 100644
--- a/property_service/libpropertyinfoparser/Android.bp
+++ b/property_service/libpropertyinfoparser/Android.bp
@@ -1,5 +1,6 @@
 cc_library_static {
     name: "libpropertyinfoparser",
+    host_supported: true,
     srcs: ["property_info_parser.cpp"],
 
     cpp_std: "experimental",
diff --git a/property_service/libpropertyinfoparser/include/property_info_parser/property_info_parser.h b/property_service/libpropertyinfoparser/include/property_info_parser/property_info_parser.h
index 8c3507e..2ee8161 100644
--- a/property_service/libpropertyinfoparser/include/property_info_parser/property_info_parser.h
+++ b/property_service/libpropertyinfoparser/include/property_info_parser/property_info_parser.h
@@ -18,6 +18,7 @@
 #define PROPERTY_INFO_PARSER_H
 
 #include <stdint.h>
+#include <stdlib.h>
 
 namespace android {
 namespace properties {
diff --git a/property_service/libpropertyinfoserializer/Android.bp b/property_service/libpropertyinfoserializer/Android.bp
index 20e5e13..bc28e82 100644
--- a/property_service/libpropertyinfoserializer/Android.bp
+++ b/property_service/libpropertyinfoserializer/Android.bp
@@ -1,5 +1,6 @@
 cc_defaults {
     name: "propertyinfoserializer_defaults",
+    host_supported: true,
     cpp_std: "experimental",
     sanitize: {
         misc_undefined: ["signed-integer-overflow"],
@@ -19,6 +20,7 @@
     name: "libpropertyinfoserializer",
     defaults: ["propertyinfoserializer_defaults"],
     srcs: [
+        "property_info_file.cpp",
         "property_info_serializer.cpp",
         "trie_builder.cpp",
         "trie_serializer.cpp",
diff --git a/property_service/libpropertyinfoserializer/include/property_info_serializer/property_info_serializer.h b/property_service/libpropertyinfoserializer/include/property_info_serializer/property_info_serializer.h
index f7e708e..d2ec385 100644
--- a/property_service/libpropertyinfoserializer/include/property_info_serializer/property_info_serializer.h
+++ b/property_service/libpropertyinfoserializer/include/property_info_serializer/property_info_serializer.h
@@ -41,6 +41,10 @@
                const std::string& default_context, const std::string& default_schema,
                std::string* serialized_trie, std::string* error);
 
+void ParsePropertyInfoFile(const std::string& file_contents,
+                           std::vector<PropertyInfoEntry>* property_infos,
+                           std::vector<std::string>* errors);
+
 }  // namespace properties
 }  // namespace android
 
diff --git a/property_service/libpropertyinfoserializer/property_info_file.cpp b/property_service/libpropertyinfoserializer/property_info_file.cpp
new file mode 100644
index 0000000..702f219
--- /dev/null
+++ b/property_service/libpropertyinfoserializer/property_info_file.cpp
@@ -0,0 +1,62 @@
+#include <property_info_serializer/property_info_serializer.h>
+
+#include <android-base/strings.h>
+
+#include "space_tokenizer.h"
+
+using android::base::Split;
+using android::base::StartsWith;
+using android::base::Trim;
+
+namespace android {
+namespace properties {
+
+bool ParsePropertyInfoLine(const std::string& line, PropertyInfoEntry* out, std::string* error) {
+  auto tokenizer = SpaceTokenizer(line);
+
+  auto property = tokenizer.GetNext();
+  if (property.empty()) {
+    *error = "Did not find a property entry in '" + line + "'";
+    return false;
+  }
+
+  auto context = tokenizer.GetNext();
+  if (context.empty()) {
+    *error = "Did not find a context entry in '" + line + "'";
+    return false;
+  }
+
+  // It is not an error to not find these, as older files will not contain them.
+  auto exact_match = tokenizer.GetNext();
+  auto schema = tokenizer.GetRemaining();
+
+  *out = {property, context, schema, exact_match == "exact"};
+  return true;
+}
+
+void ParsePropertyInfoFile(const std::string& file_contents,
+                           std::vector<PropertyInfoEntry>* property_infos,
+                           std::vector<std::string>* errors) {
+  // Do not clear property_infos to allow this function to be called on multiple files, with
+  // their results concatenated.
+  errors->clear();
+
+  for (const auto& line : Split(file_contents, "\n")) {
+    auto trimmed_line = Trim(line);
+    if (trimmed_line.empty() || StartsWith(trimmed_line, "#")) {
+      continue;
+    }
+
+    auto property_info_entry = PropertyInfoEntry{};
+    auto parse_error = std::string{};
+    if (!ParsePropertyInfoLine(trimmed_line, &property_info_entry, &parse_error)) {
+      errors->emplace_back(parse_error);
+      continue;
+    }
+
+    property_infos->emplace_back(property_info_entry);
+  }
+}
+
+}  // namespace properties
+}  // namespace android
diff --git a/property_service/libpropertyinfoserializer/space_tokenizer.h b/property_service/libpropertyinfoserializer/space_tokenizer.h
new file mode 100644
index 0000000..fba0c58
--- /dev/null
+++ b/property_service/libpropertyinfoserializer/space_tokenizer.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef PROPERTY_INFO_SERIALIZER_SPACE_TOKENIZER_H
+#define PROPERTY_INFO_SERIALIZER_SPACE_TOKENIZER_H
+
+namespace android {
+namespace properties {
+
+class SpaceTokenizer {
+ public:
+  SpaceTokenizer(const std::string& string)
+      : string_(string), it_(string_.begin()), end_(string_.end()) {}
+
+  std::string GetNext() {
+    auto next = std::string();
+    while (it_ != end_ && !isspace(*it_)) {
+      next.push_back(*it_++);
+    }
+    while (it_ != end_ && isspace(*it_)) {
+      it_++;
+    }
+    return next;
+  }
+
+  std::string GetRemaining() { return std::string(it_, end_); }
+
+ private:
+  std::string string_;
+  std::string::const_iterator it_;
+  std::string::const_iterator end_;
+};
+
+}  // namespace properties
+}  // namespace android
+
+#endif
diff --git a/property_service/property_info_checker/Android.bp b/property_service/property_info_checker/Android.bp
new file mode 100644
index 0000000..2c9a2ba
--- /dev/null
+++ b/property_service/property_info_checker/Android.bp
@@ -0,0 +1,14 @@
+cc_binary {
+    name: "property_info_checker",
+    host_supported: true,
+    cpp_std: "experimental",
+    sanitize: {
+        misc_undefined: ["signed-integer-overflow"],
+    },
+    static_libs: [
+        "libpropertyinfoserializer",
+        "libpropertyinfoparser",
+        "libbase",
+    ],
+    srcs: ["property_info_checker.cpp"],
+}
\ No newline at end of file
diff --git a/property_service/property_info_checker/property_info_checker.cpp b/property_service/property_info_checker/property_info_checker.cpp
new file mode 100644
index 0000000..e4f8264
--- /dev/null
+++ b/property_service/property_info_checker/property_info_checker.cpp
@@ -0,0 +1,51 @@
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+
+#include <property_info_serializer/property_info_serializer.h>
+
+using android::base::ReadFileToString;
+using android::properties::BuildTrie;
+using android::properties::ParsePropertyInfoFile;
+using android::properties::PropertyInfoEntry;
+
+int main(int argc, char** argv) {
+  if (argc < 2) {
+    std::cerr << "A list of property info files to be checked is expected on the command line"
+              << std::endl;
+    return -1;
+  }
+
+  auto property_info_entries = std::vector<PropertyInfoEntry>{};
+
+  for (int i = 1; i < argc; ++i) {
+    auto filename = argv[i];
+    auto file_contents = std::string{};
+    if (!ReadFileToString(filename, &file_contents)) {
+      std::cerr << "Could not read properties from '" << filename << "'" << std::endl;
+      return -1;
+    }
+
+    auto errors = std::vector<std::string>{};
+    ParsePropertyInfoFile(file_contents, &property_info_entries, &errors);
+    if (!errors.empty()) {
+      for (const auto& error : errors) {
+        std::cerr << "Could not read line from '" << filename << "': " << error << std::endl;
+      }
+      return -1;
+    }
+  }
+
+  auto serialized_contexts = std::string{};
+  auto build_trie_error = std::string{};
+
+  if (!BuildTrie(property_info_entries, "u:object_r:default_prop:s0", "\\s*", &serialized_contexts,
+                 &build_trie_error)) {
+    std::cerr << "Unable to serialize property contexts: " << build_trie_error << std::endl;
+    return -1;
+  }
+
+  return 0;
+}