Merge "Parse process starttime."
diff --git a/include/procinfo/process.h b/include/procinfo/process.h
index 9278e18..ee245e4 100644
--- a/include/procinfo/process.h
+++ b/include/procinfo/process.h
@@ -26,6 +26,7 @@
 #include <string>
 #include <type_traits>
 
+#include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
 #include <android-base/unique_fd.h>
@@ -53,6 +54,9 @@
   pid_t tracer;
   uid_t uid;
   uid_t gid;
+
+  // Start time of the process since boot, measured in clock ticks.
+  uint64_t starttime;
 };
 
 // Parse the contents of /proc/<tid>/status into |process_info|.
diff --git a/process.cpp b/process.cpp
index 2efd49c..4831ae8 100644
--- a/process.cpp
+++ b/process.cpp
@@ -46,8 +46,8 @@
   return GetProcessInfoFromProcPidFd(dirfd.get(), process_info, error);
 }
 
-static ProcessState parse_state(const char* state) {
-  switch (*state) {
+static ProcessState parse_state(char state) {
+  switch (state) {
     case 'R':
       return kProcessStateRunning;
     case 'S':
@@ -83,7 +83,7 @@
   }
 
   int field_bitmap = 0;
-  static constexpr int finished_bitmap = 255;
+  static constexpr int finished_bitmap = 63;
   char* line = nullptr;
   size_t len = 0;
 
@@ -103,32 +103,81 @@
       process_info->name = std::move(name);
 
       field_bitmap |= 1;
+    }  else if (header == "Tgid:") {
+      process_info->pid = atoi(tab + 1);
+      field_bitmap |= 2;
     } else if (header == "Pid:") {
       process_info->tid = atoi(tab + 1);
-      field_bitmap |= 2;
-    } else if (header == "Tgid:") {
-      process_info->pid = atoi(tab + 1);
       field_bitmap |= 4;
-    } else if (header == "PPid:") {
-      process_info->ppid = atoi(tab + 1);
-      field_bitmap |= 8;
     } else if (header == "TracerPid:") {
       process_info->tracer = atoi(tab + 1);
-      field_bitmap |= 16;
+      field_bitmap |= 8;
     } else if (header == "Uid:") {
       process_info->uid = atoi(tab + 1);
-      field_bitmap |= 32;
+      field_bitmap |= 16;
     } else if (header == "Gid:") {
       process_info->gid = atoi(tab + 1);
-      field_bitmap |= 64;
-    } else if (header == "State:") {
-      process_info->state = parse_state(tab + 1);
-      field_bitmap |= 128;
+      field_bitmap |= 32;
     }
   }
 
   free(line);
-  return field_bitmap == finished_bitmap;
+  if (field_bitmap != finished_bitmap) {
+    *error = "failed to parse /proc/<pid>/status";
+    return false;
+  }
+
+  unique_fd stat_fd(openat(fd, "stat", O_RDONLY | O_CLOEXEC));
+  if (stat_fd == -1) {
+    *error = "failed to open /proc/<pid>/stat";
+  }
+
+  std::string stat;
+  if (!android::base::ReadFdToString(stat_fd, &stat)) {
+    *error = "failed to read /proc/<pid>/stat";
+    return false;
+  }
+
+  // See man 5 proc. There's no reason comm can't contain ' ' or ')',
+  // so we search backwards for the end of it.
+  const char* end_of_comm = strrchr(stat.c_str(), ')');
+
+  static constexpr const char* pattern =
+      "%c "    // state
+      "%d "    // ppid
+      "%*d "   // pgrp
+      "%*d "   // session
+      "%*d "   // tty_nr
+      "%*d "   // tpgid
+      "%*u "   // flags
+      "%*lu "  // minflt
+      "%*lu "  // cminflt
+      "%*lu "  // majflt
+      "%*lu "  // cmajflt
+      "%*lu "  // utime
+      "%*lu "  // stime
+      "%*ld "  // cutime
+      "%*ld "  // cstime
+      "%*ld "  // priority
+      "%*ld "  // nice
+      "%*ld "  // num_threads
+      "%*ld "  // itrealvalue
+      "%llu "  // starttime
+      ;
+
+  char state = '\0';
+  int ppid = 0;
+  unsigned long long start_time = 0;
+  int rc = sscanf(end_of_comm + 2, pattern, &state, &ppid, &start_time);
+  if (rc != 3) {
+    *error = "failed to parse /proc/<pid>/stat";
+    return false;
+  }
+
+  process_info->state = parse_state(state);
+  process_info->ppid = ppid;
+  process_info->starttime = start_time;
+  return true;
 }
 
 } /* namespace procinfo */
diff --git a/process_test.cpp b/process_test.cpp
index 9da9278..be00a17 100644
--- a/process_test.cpp
+++ b/process_test.cpp
@@ -28,6 +28,7 @@
 
 #include <gtest/gtest.h>
 
+#include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 
 using namespace std::chrono_literals;
@@ -116,3 +117,44 @@
 
   ASSERT_EQ(forkpid, waitpid(forkpid, nullptr, 0));
 }
+
+static uint64_t read_uptime_secs() {
+  std::string uptime;
+  if (!android::base::ReadFileToString("/proc/uptime", &uptime)) {
+    PLOG(FATAL) << "failed to read /proc/uptime";
+  }
+  return strtoll(uptime.c_str(), nullptr, 10);
+}
+
+TEST(process_info, process_start_time) {
+  uint64_t start = read_uptime_secs();
+  int pipefd[2];
+  ASSERT_EQ(0, pipe2(pipefd, O_CLOEXEC));
+
+  std::this_thread::sleep_for(1000ms);
+
+  pid_t forkpid = fork();
+
+  ASSERT_NE(-1, forkpid);
+  if (forkpid == 0) {
+    close(pipefd[1]);
+    char buf;
+    TEMP_FAILURE_RETRY(read(pipefd[0], &buf, 1));
+    _exit(0);
+  }
+
+  std::this_thread::sleep_for(1000ms);
+
+  uint64_t end = read_uptime_secs();
+
+  android::procinfo::ProcessInfo procinfo;
+  ASSERT_TRUE(android::procinfo::GetProcessInfo(forkpid, &procinfo));
+
+  // starttime is measured in clock ticks: uptime is in seconds:
+  uint64_t process_start = procinfo.starttime / sysconf(_SC_CLK_TCK);
+  ASSERT_LE(start, process_start);
+  ASSERT_LE(process_start, end);
+
+  ASSERT_EQ(0, kill(forkpid, SIGKILL));
+  ASSERT_EQ(forkpid, waitpid(forkpid, nullptr, 0));
+}