Use packages.list to read profileability.

Bug: 153139002
Change-Id: Idc4d3bea3f6535765a323b9bf4bf62a16704e7db
diff --git a/Android.bp b/Android.bp
index 7ed5230..932a9b1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -86,6 +86,7 @@
     ":perfetto_src_profiling_memory_scoped_spinlock",
     ":perfetto_src_profiling_memory_wire_protocol",
     ":perfetto_src_protozero_protozero",
+    ":perfetto_src_traced_probes_packages_list_packages_list_parser",
     ":perfetto_src_tracing_common",
     ":perfetto_src_tracing_core_core",
     ":perfetto_src_tracing_ipc_common",
@@ -335,6 +336,7 @@
     ":perfetto_src_profiling_memory_scoped_spinlock",
     ":perfetto_src_profiling_memory_wire_protocol",
     ":perfetto_src_protozero_protozero",
+    ":perfetto_src_traced_probes_packages_list_packages_list_parser",
     ":perfetto_src_tracing_common",
     ":perfetto_src_tracing_core_core",
     ":perfetto_src_tracing_ipc_common",
@@ -518,6 +520,7 @@
     ":perfetto_src_traced_probes_initial_display_state_initial_display_state",
     ":perfetto_src_traced_probes_metatrace_metatrace",
     ":perfetto_src_traced_probes_packages_list_packages_list",
+    ":perfetto_src_traced_probes_packages_list_packages_list_parser",
     ":perfetto_src_traced_probes_power_power",
     ":perfetto_src_traced_probes_probes",
     ":perfetto_src_traced_probes_probes_src",
@@ -1058,6 +1061,7 @@
     ":perfetto_src_traced_probes_initial_display_state_initial_display_state",
     ":perfetto_src_traced_probes_metatrace_metatrace",
     ":perfetto_src_traced_probes_packages_list_packages_list",
+    ":perfetto_src_traced_probes_packages_list_packages_list_parser",
     ":perfetto_src_traced_probes_power_power",
     ":perfetto_src_traced_probes_probes_src",
     ":perfetto_src_traced_probes_ps_ps",
@@ -1312,6 +1316,7 @@
     ":perfetto_src_traced_probes_initial_display_state_initial_display_state",
     ":perfetto_src_traced_probes_metatrace_metatrace",
     ":perfetto_src_traced_probes_packages_list_packages_list",
+    ":perfetto_src_traced_probes_packages_list_packages_list_parser",
     ":perfetto_src_traced_probes_power_power",
     ":perfetto_src_traced_probes_probes_src",
     ":perfetto_src_traced_probes_ps_ps",
@@ -1716,6 +1721,7 @@
     ":perfetto_src_traced_probes_initial_display_state_initial_display_state",
     ":perfetto_src_traced_probes_metatrace_metatrace",
     ":perfetto_src_traced_probes_packages_list_packages_list",
+    ":perfetto_src_traced_probes_packages_list_packages_list_parser",
     ":perfetto_src_traced_probes_power_power",
     ":perfetto_src_traced_probes_probes_src",
     ":perfetto_src_traced_probes_ps_ps",
@@ -7966,11 +7972,19 @@
   ],
 }
 
+// GN: //src/traced/probes/packages_list:packages_list_parser
+filegroup {
+  name: "perfetto_src_traced_probes_packages_list_packages_list_parser",
+  srcs: [
+    "src/traced/probes/packages_list/packages_list_parser.cc",
+  ],
+}
+
 // GN: //src/traced/probes/packages_list:unittests
 filegroup {
   name: "perfetto_src_traced_probes_packages_list_unittests",
   srcs: [
-    "src/traced/probes/packages_list/packages_list_data_source_unittest.cc",
+    "src/traced/probes/packages_list/packages_list_unittest.cc",
   ],
 }
 
@@ -8619,6 +8633,7 @@
     ":perfetto_src_traced_probes_initial_display_state_unittests",
     ":perfetto_src_traced_probes_metatrace_metatrace",
     ":perfetto_src_traced_probes_packages_list_packages_list",
+    ":perfetto_src_traced_probes_packages_list_packages_list_parser",
     ":perfetto_src_traced_probes_packages_list_unittests",
     ":perfetto_src_traced_probes_power_power",
     ":perfetto_src_traced_probes_probes_src",
diff --git a/BUILD b/BUILD
index 8e63d57..f528de8 100644
--- a/BUILD
+++ b/BUILD
@@ -186,6 +186,7 @@
         ":src_traced_probes_initial_display_state_initial_display_state",
         ":src_traced_probes_metatrace_metatrace",
         ":src_traced_probes_packages_list_packages_list",
+        ":src_traced_probes_packages_list_packages_list_parser",
         ":src_traced_probes_power_power",
         ":src_traced_probes_probes",
         ":src_traced_probes_probes_src",
@@ -1379,6 +1380,15 @@
     ],
 )
 
+# GN target: //src/traced/probes/packages_list:packages_list_parser
+filegroup(
+    name = "src_traced_probes_packages_list_packages_list_parser",
+    srcs = [
+        "src/traced/probes/packages_list/packages_list_parser.cc",
+        "src/traced/probes/packages_list/packages_list_parser.h",
+    ],
+)
+
 # GN target: //src/traced/probes/power:power
 filegroup(
     name = "src_traced_probes_power_power",
diff --git a/src/profiling/memory/BUILD.gn b/src/profiling/memory/BUILD.gn
index c9d9715..3933798 100644
--- a/src/profiling/memory/BUILD.gn
+++ b/src/profiling/memory/BUILD.gn
@@ -239,6 +239,7 @@
     "../../../protos/perfetto/config/profiling:cpp",
     "../../base",
     "../../base:unix_socket",
+    "../../traced/probes/packages_list:packages_list_parser",
     "../../tracing/core",
     "../../tracing/ipc/producer",
     "../common:callstack_trie",
diff --git a/src/profiling/memory/heapprofd_producer.cc b/src/profiling/memory/heapprofd_producer.cc
index 22091f2..4b1d02f 100644
--- a/src/profiling/memory/heapprofd_producer.cc
+++ b/src/profiling/memory/heapprofd_producer.cc
@@ -26,9 +26,11 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include "perfetto/base/compiler.h"
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/optional.h"
+#include "perfetto/ext/base/string_splitter.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/thread_task_runner.h"
 #include "perfetto/ext/base/watchdog_posix.h"
@@ -40,6 +42,11 @@
 #include "src/profiling/common/profiler_guardrails.h"
 #include "src/profiling/memory/unwound_messages.h"
 #include "src/profiling/memory/wire_protocol.h"
+#include "src/traced/probes/packages_list/packages_list_parser.h"
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+#include <sys/system_properties.h>
+#endif
 
 namespace perfetto {
 namespace profiling {
@@ -464,6 +471,7 @@
   if (!HeapprofdConfigToClientConfiguration(heapprofd_config, &cli_config))
     return;
   data_source.config = heapprofd_config;
+  data_source.ds_config = ds_config;
   data_source.normalized_cmdlines = std::move(normalized_cmdlines.value());
   data_source.stop_timeout_ms = ds_config.stop_timeout_ms()
                                     ? ds_config.stop_timeout_ms()
@@ -935,6 +943,15 @@
   }
   RecordOtherSourcesAsRejected(data_source, process);
 
+  // In fork mode, right now we check whether the target is not profileable
+  // in the client, because we cannot read packages.list there.
+  if (mode_ == HeapprofdMode::kCentral &&
+      !CanProfile(data_source->ds_config, new_connection->peer_uid())) {
+    PERFETTO_ELOG("%d (%s) is not profileable.", process.pid,
+                  process.cmdline.c_str());
+    return;
+  }
+
   uint64_t shmem_size = data_source->config.shmem_size_bytes();
   if (!shmem_size)
     shmem_size = kDefaultShmemSize;
@@ -1239,5 +1256,61 @@
   }
 }
 
+bool CanProfile(const DataSourceConfig& ds_config, uint64_t uid) {
+#if !PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
+  base::ignore_result(ds_config);
+  base::ignore_result(uid);
+  return true;
+#else
+  char buf[PROP_VALUE_MAX + 1] = {};
+  int ret = __system_property_get("ro.build.type", buf);
+  PERFETTO_CHECK(ret >= 0);
+  return CanProfileAndroid(ds_config, uid, std::string(buf),
+                           "/data/system/packages.list");
+#endif
+}
+
+bool CanProfileAndroid(const DataSourceConfig& ds_config,
+                       uint64_t uid,
+                       const std::string& build_type,
+                       const std::string& packages_list_path) {
+  // These are replicated constants from libcutils android_filesystem_config.h
+  constexpr auto kAidAppStart = 10000;     // AID_APP_START
+  constexpr auto kAidAppEnd = 19999;       // AID_APP_END
+  constexpr auto kAidUserOffset = 100000;  // AID_USER_OFFSET
+
+  if (build_type != "user") {
+    return true;
+  }
+
+  if (ds_config.enable_extra_guardrails()) {
+    return false;  // no extra guardrails on user builds.
+  }
+
+  uint64_t uid_without_profile = uid % kAidUserOffset;
+  if (uid_without_profile < kAidAppStart || kAidAppEnd < uid_without_profile) {
+    // TODO(fmayer): relax this.
+    return false;  // no native services on user.
+  }
+
+  std::string content;
+  if (!base::ReadFile(packages_list_path, &content)) {
+    PERFETTO_ELOG("Failed to read %s.", packages_list_path.c_str());
+    return false;
+  }
+  for (base::StringSplitter ss(std::move(content), '\n'); ss.Next();) {
+    Package pkg;
+    if (!ReadPackagesListLine(ss.cur_token(), &pkg)) {
+      PERFETTO_ELOG("Failed to parse packages.list.");
+      return false;
+    }
+    if (pkg.uid == uid_without_profile &&
+        (pkg.profileable_from_shell || pkg.debuggable)) {
+      return true;
+    }
+  }
+  return false;
+}
+
 }  // namespace profiling
 }  // namespace perfetto
diff --git a/src/profiling/memory/heapprofd_producer.h b/src/profiling/memory/heapprofd_producer.h
index d913614..aa11ab7 100644
--- a/src/profiling/memory/heapprofd_producer.h
+++ b/src/profiling/memory/heapprofd_producer.h
@@ -35,6 +35,7 @@
 #include "perfetto/ext/tracing/core/tracing_service.h"
 #include "perfetto/tracing/core/data_source_config.h"
 
+#include "perfetto/tracing/core/forward_decls.h"
 #include "src/profiling/common/interning_output.h"
 #include "src/profiling/common/proc_utils.h"
 #include "src/profiling/common/profiler_guardrails.h"
@@ -79,6 +80,12 @@
     const HeapprofdConfig& heapprofd_config,
     ClientConfiguration* cli_config);
 
+bool CanProfile(const DataSourceConfig& ds_config, uint64_t uid);
+bool CanProfileAndroid(const DataSourceConfig& ds_config,
+                       uint64_t uid,
+                       const std::string& build_type,
+                       const std::string& packages_list_path);
+
 // Heap profiling producer. Can be instantiated in two modes, central and
 // child (also referred to as fork mode).
 //
@@ -230,6 +237,7 @@
 
     DataSourceInstanceID id;
     std::unique_ptr<TraceWriter> trace_writer;
+    DataSourceConfig ds_config;
     HeapprofdConfig config;
     ClientConfiguration client_configuration;
     std::vector<SystemProperties::Handle> properties;
diff --git a/src/profiling/memory/heapprofd_producer_unittest.cc b/src/profiling/memory/heapprofd_producer_unittest.cc
index 6509793..f9fcc9d 100644
--- a/src/profiling/memory/heapprofd_producer_unittest.cc
+++ b/src/profiling/memory/heapprofd_producer_unittest.cc
@@ -16,6 +16,8 @@
 
 #include "src/profiling/memory/heapprofd_producer.h"
 
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/temp_file.h"
 #include "perfetto/ext/tracing/core/basic_types.h"
 #include "perfetto/ext/tracing/core/commit_data_request.h"
 #include "perfetto/tracing/core/data_source_descriptor.h"
@@ -170,5 +172,95 @@
   EXPECT_FALSE(HeapprofdConfigToClientConfiguration(cfg, &cli_config));
 }
 
+TEST(CanProfileAndroidTest, NonUserSystemExtraGuardrails) {
+  DataSourceConfig ds_config;
+  ds_config.set_enable_extra_guardrails(true);
+  EXPECT_TRUE(CanProfileAndroid(ds_config, 1, "userdebug", "/dev/null"));
+}
+
+TEST(CanProfileAndroidTest, NonUserNonProfileableApp) {
+  DataSourceConfig ds_config;
+  ds_config.set_enable_extra_guardrails(false);
+  auto tmp = base::TempFile::Create();
+  constexpr char content[] =
+      "invalid.example.profileable 10001 0 "
+      "/data/user/0/invalid.example.profileable default:targetSdkVersion=10000 "
+      "none 0 1\n";
+  base::WriteAll(tmp.fd(), content, sizeof(content));
+  EXPECT_TRUE(CanProfileAndroid(ds_config, 10001, "userdebug", tmp.path()));
+}
+
+TEST(CanProfileAndroidTest, NonUserNonProfileableAppExtraGuardrails) {
+  DataSourceConfig ds_config;
+  ds_config.set_enable_extra_guardrails(true);
+  auto tmp = base::TempFile::Create();
+  constexpr char content[] =
+      "invalid.example.profileable 10001 0 "
+      "/data/user/0/invalid.example.profileable default:targetSdkVersion=10000 "
+      "none 0 1\n";
+  base::WriteAll(tmp.fd(), content, sizeof(content));
+  EXPECT_TRUE(CanProfileAndroid(ds_config, 10001, "userdebug", tmp.path()));
+}
+
+TEST(CanProfileAndroidTest, UserProfileableApp) {
+  DataSourceConfig ds_config;
+  ds_config.set_enable_extra_guardrails(false);
+  auto tmp = base::TempFile::Create();
+  constexpr char content[] =
+      "invalid.example.profileable 10001 0 "
+      "/data/user/0/invalid.example.profileable default:targetSdkVersion=10000 "
+      "none 1 1\n";
+  base::WriteAll(tmp.fd(), content, sizeof(content));
+  EXPECT_TRUE(CanProfileAndroid(ds_config, 10001, "user", tmp.path()));
+}
+
+TEST(CanProfileAndroidTest, UserProfileableAppExtraGuardrails) {
+  DataSourceConfig ds_config;
+  ds_config.set_enable_extra_guardrails(true);
+  auto tmp = base::TempFile::Create();
+  constexpr char content[] =
+      "invalid.example.profileable 10001 0 "
+      "/data/user/0/invalid.example.profileable default:targetSdkVersion=10000 "
+      "none 1 1\n";
+  base::WriteAll(tmp.fd(), content, sizeof(content));
+  EXPECT_FALSE(CanProfileAndroid(ds_config, 10001, "user", tmp.path()));
+}
+
+TEST(CanProfileAndroidTest, UserProfileableAppMultiuser) {
+  DataSourceConfig ds_config;
+  ds_config.set_enable_extra_guardrails(false);
+  auto tmp = base::TempFile::Create();
+  constexpr char content[] =
+      "invalid.example.profileable 10001 0 "
+      "/data/user/0/invalid.example.profileable default:targetSdkVersion=10000 "
+      "none 1 1\n";
+  base::WriteAll(tmp.fd(), content, sizeof(content));
+  EXPECT_TRUE(CanProfileAndroid(ds_config, 210001, "user", tmp.path()));
+}
+
+TEST(CanProfileAndroidTest, UserNonProfileableApp) {
+  DataSourceConfig ds_config;
+  ds_config.set_enable_extra_guardrails(false);
+  auto tmp = base::TempFile::Create();
+  constexpr char content[] =
+      "invalid.example.profileable 10001 0 "
+      "/data/user/0/invalid.example.profileable default:targetSdkVersion=10000 "
+      "none 0 1\n";
+  base::WriteAll(tmp.fd(), content, sizeof(content));
+  EXPECT_FALSE(CanProfileAndroid(ds_config, 10001, "user", tmp.path()));
+}
+
+TEST(CanProfileAndroidTest, UserDebuggableApp) {
+  DataSourceConfig ds_config;
+  ds_config.set_enable_extra_guardrails(false);
+  auto tmp = base::TempFile::Create();
+  constexpr char content[] =
+      "invalid.example.profileable 10001 1 "
+      "/data/user/0/invalid.example.profileable default:targetSdkVersion=10000 "
+      "none 0 1\n";
+  base::WriteAll(tmp.fd(), content, sizeof(content));
+  EXPECT_TRUE(CanProfileAndroid(ds_config, 10001, "user", tmp.path()));
+}
+
 }  // namespace profiling
 }  // namespace perfetto
diff --git a/src/traced/probes/packages_list/BUILD.gn b/src/traced/probes/packages_list/BUILD.gn
index 0f17596..669fa18 100644
--- a/src/traced/probes/packages_list/BUILD.gn
+++ b/src/traced/probes/packages_list/BUILD.gn
@@ -14,9 +14,21 @@
 
 import("../../../../gn/test.gni")
 
+source_set("packages_list_parser") {
+  deps = [
+    "../../../../gn:default_deps",
+    "../../../base",
+  ]
+  sources = [
+    "packages_list_parser.cc",
+    "packages_list_parser.h",
+  ]
+}
+
 source_set("packages_list") {
   public_deps = [ "../../../tracing/core" ]
   deps = [
+    ":packages_list_parser",
     "..:data_source",
     "../../../../gn:default_deps",
     "../../../../include/perfetto/ext/traced",
@@ -36,6 +48,7 @@
   testonly = true
   deps = [
     ":packages_list",
+    ":packages_list_parser",
     "../../../../gn:default_deps",
     "../../../../gn:gtest_and_gmock",
     "../../../../protos/perfetto/trace/android:cpp",
@@ -43,5 +56,5 @@
     "../../../../src/base:test_support",
     "../../../../src/tracing/test:test_support",
   ]
-  sources = [ "packages_list_data_source_unittest.cc" ]
+  sources = [ "packages_list_unittest.cc" ]
 }
diff --git a/src/traced/probes/packages_list/packages_list_data_source.cc b/src/traced/probes/packages_list/packages_list_data_source.cc
index 68fa662..f9919b8 100644
--- a/src/traced/probes/packages_list/packages_list_data_source.cc
+++ b/src/traced/probes/packages_list/packages_list_data_source.cc
@@ -22,6 +22,8 @@
 #include "perfetto/ext/tracing/core/trace_writer.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 
+#include "src/traced/probes/packages_list/packages_list_parser.h"
+
 using perfetto::protos::pbzero::PackagesListConfig;
 
 namespace perfetto {
@@ -57,60 +59,6 @@
   return parsed_fully;
 }
 
-bool ReadPackagesListLine(char* line, Package* package) {
-  size_t idx = 0;
-  for (base::StringSplitter ss(line, ' '); ss.Next();) {
-    switch (idx) {
-      case 0:
-        package->name = std::string(ss.cur_token(), ss.cur_token_size());
-        break;
-      case 1: {
-        char* end;
-        long long uid = strtoll(ss.cur_token(), &end, 10);
-        if ((*end != '\0' && *end != '\n') || *ss.cur_token() == '\0') {
-          PERFETTO_ELOG("Failed to parse packages.list uid.");
-          return false;
-        }
-        package->uid = static_cast<uint64_t>(uid);
-        break;
-      }
-      case 2: {
-        char* end;
-        long long debuggable = strtoll(ss.cur_token(), &end, 10);
-        if ((*end != '\0' && *end != '\n') || *ss.cur_token() == '\0') {
-          PERFETTO_ELOG("Failed to parse packages.list debuggable.");
-          return false;
-        }
-        package->debuggable = debuggable != 0;
-        break;
-      }
-      case 6: {
-        char* end;
-        long long profilable_from_shell = strtoll(ss.cur_token(), &end, 10);
-        if ((*end != '\0' && *end != '\n') || *ss.cur_token() == '\0') {
-          PERFETTO_ELOG("Failed to parse packages.list profilable_from_shell.");
-          return false;
-        }
-        package->profileable_from_shell = profilable_from_shell != 0;
-        break;
-      }
-      case 7: {
-        char* end;
-        long long version_code = strtoll(ss.cur_token(), &end, 10);
-        if ((*end != '\0' && *end != '\n') || *ss.cur_token() == '\0') {
-          PERFETTO_ELOG("Failed to parse packages.list version_code: %s.",
-                        ss.cur_token());
-          return false;
-        }
-        package->version_code = version_code;
-        break;
-      }
-    }
-    ++idx;
-  }
-  return true;
-}
-
 PackagesListDataSource::PackagesListDataSource(
     const DataSourceConfig& ds_config,
     TracingSessionID session_id,
diff --git a/src/traced/probes/packages_list/packages_list_data_source.h b/src/traced/probes/packages_list/packages_list_data_source.h
index 5dd3fdc..b19472b 100644
--- a/src/traced/probes/packages_list/packages_list_data_source.h
+++ b/src/traced/probes/packages_list/packages_list_data_source.h
@@ -35,15 +35,6 @@
 
 class TraceWriter;
 
-struct Package {
-  std::string name;
-  uint64_t uid = 0;
-  bool debuggable = false;
-  bool profileable_from_shell = false;
-  int64_t version_code = 0;
-};
-
-bool ReadPackagesListLine(char* line, Package* package);
 bool ParsePackagesListStream(protos::pbzero::PackagesList* packages_list,
                              const base::ScopedFstream& fs,
                              const std::set<std::string>& package_name_filter);
diff --git a/src/traced/probes/packages_list/packages_list_parser.cc b/src/traced/probes/packages_list/packages_list_parser.cc
new file mode 100644
index 0000000..bcbccaa
--- /dev/null
+++ b/src/traced/probes/packages_list/packages_list_parser.cc
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#include "src/traced/probes/packages_list/packages_list_parser.h"
+
+#include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/string_splitter.h"
+
+namespace perfetto {
+
+bool ReadPackagesListLine(char* line, Package* package) {
+  size_t idx = 0;
+  for (base::StringSplitter ss(line, ' '); ss.Next();) {
+    switch (idx) {
+      case 0:
+        package->name = std::string(ss.cur_token(), ss.cur_token_size());
+        break;
+      case 1: {
+        char* end;
+        long long uid = strtoll(ss.cur_token(), &end, 10);
+        if ((*end != '\0' && *end != '\n') || *ss.cur_token() == '\0') {
+          PERFETTO_ELOG("Failed to parse packages.list uid.");
+          return false;
+        }
+        package->uid = static_cast<uint64_t>(uid);
+        break;
+      }
+      case 2: {
+        char* end;
+        long long debuggable = strtoll(ss.cur_token(), &end, 10);
+        if ((*end != '\0' && *end != '\n') || *ss.cur_token() == '\0') {
+          PERFETTO_ELOG("Failed to parse packages.list debuggable.");
+          return false;
+        }
+        package->debuggable = debuggable != 0;
+        break;
+      }
+      case 6: {
+        char* end;
+        long long profilable_from_shell = strtoll(ss.cur_token(), &end, 10);
+        if ((*end != '\0' && *end != '\n') || *ss.cur_token() == '\0') {
+          PERFETTO_ELOG("Failed to parse packages.list profilable_from_shell.");
+          return false;
+        }
+        package->profileable_from_shell = profilable_from_shell != 0;
+        break;
+      }
+      case 7: {
+        char* end;
+        long long version_code = strtoll(ss.cur_token(), &end, 10);
+        if ((*end != '\0' && *end != '\n') || *ss.cur_token() == '\0') {
+          PERFETTO_ELOG("Failed to parse packages.list version_code: %s.",
+                        ss.cur_token());
+          return false;
+        }
+        package->version_code = version_code;
+        break;
+      }
+    }
+    ++idx;
+  }
+  return true;
+}
+
+}  // namespace perfetto
diff --git a/src/traced/probes/packages_list/packages_list_parser.h b/src/traced/probes/packages_list/packages_list_parser.h
new file mode 100644
index 0000000..3deb3c5
--- /dev/null
+++ b/src/traced/probes/packages_list/packages_list_parser.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 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 SRC_TRACED_PROBES_PACKAGES_LIST_PACKAGES_LIST_PARSER_H_
+#define SRC_TRACED_PROBES_PACKAGES_LIST_PACKAGES_LIST_PARSER_H_
+
+#include <inttypes.h>
+#include <string>
+
+namespace perfetto {
+
+struct Package {
+  std::string name;
+  uint64_t uid = 0;
+  bool debuggable = false;
+  bool profileable_from_shell = false;
+  int64_t version_code = 0;
+};
+
+bool ReadPackagesListLine(char* line, Package* package);
+}  // namespace perfetto
+
+#endif  // SRC_TRACED_PROBES_PACKAGES_LIST_PACKAGES_LIST_PARSER_H_
diff --git a/src/traced/probes/packages_list/packages_list_data_source_unittest.cc b/src/traced/probes/packages_list/packages_list_unittest.cc
similarity index 98%
rename from src/traced/probes/packages_list/packages_list_data_source_unittest.cc
rename to src/traced/probes/packages_list/packages_list_unittest.cc
index cb311d2..acfc791 100644
--- a/src/traced/probes/packages_list/packages_list_data_source_unittest.cc
+++ b/src/traced/probes/packages_list/packages_list_unittest.cc
@@ -25,6 +25,7 @@
 #include "perfetto/protozero/scattered_heap_buffer.h"
 #include "protos/perfetto/trace/android/packages_list.gen.h"
 #include "protos/perfetto/trace/android/packages_list.pbzero.h"
+#include "src/traced/probes/packages_list/packages_list_parser.h"
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {