metrics: Add guest mode detection to metrics library and client

Change-Id: I2c27bd999330395ba3568820ea76198b202bd7f4

BUG=7203
TEST=Verify metrics_client -c and -g toggling consent and guest mode.

Review URL: http://codereview.chromium.org/3571009
diff --git a/metrics/metrics_client.cc b/metrics/metrics_client.cc
index e85c4d4..ce22e98 100644
--- a/metrics/metrics_client.cc
+++ b/metrics/metrics_client.cc
@@ -7,63 +7,29 @@
 
 #include "metrics_library.h"
 
-int main(int argc, char** argv) {
-  bool send_to_autotest = false;
-  bool send_to_chrome = true;
-  bool send_enum = false;
-  bool secs_to_msecs = false;
-  int name_index = 1;
-  bool print_usage = false;
+void ShowUsage() {
+  fprintf(stderr,
+          "Usage:  metrics_client [-ab] [-t] name sample min max nbuckets\n"
+          "        metrics_client [-ab] -e   name sample max\n"
+          "        metrics_client [-cg]\n"
+          "\n"
+          "  default: send metric with integer values to Chrome only\n"
+          "           |min| > 0, |min| <= sample < |max|\n"
+          "  -a: send metric (name/sample) to Autotest only\n"
+          "  -b: send metric to both Chrome and Autotest\n"
+          "  -c: return exit status 0 if user consents to stats, 1 otherwise\n"
+          "  -e: send linear/enumeration histogram data\n"
+          "  -g: return exit status 0 if machine in guest mode, 1 otherwise\n"
+          "  -t: convert sample from double seconds to int milliseconds\n");
+  exit(1);
+}
 
-  if (argc >= 3) {
-    // Parse arguments
-    int flag;
-    while ((flag = getopt(argc, argv, "abet")) != -1) {
-      switch (flag) {
-        case 'a':
-          send_to_autotest = true;
-          send_to_chrome = false;
-          break;
-        case 'b':
-          send_to_chrome = true;
-          send_to_autotest = true;
-          break;
-        case 'e':
-          send_enum = true;
-          break;
-        case 't':
-          secs_to_msecs = true;
-          break;
-        default:
-          print_usage = true;
-          break;
-      }
-    }
-    name_index = optind;
-  } else {
-    print_usage = true;
-  }
-
-  int num_args = send_enum ? 3 : 5;
-  if ((name_index + num_args) != argc ||
-      (send_enum && secs_to_msecs)) {
-    print_usage = true;
-  }
-
-  if (print_usage) {
-    fprintf(stderr,
-            "Usage:  metrics_client [-ab] [-t] name sample min max nbuckets\n"
-            "        metrics_client [-ab] -e   name sample max\n"
-            "\n"
-            "  default: send metric with integer values to Chrome only\n"
-            "           |min| > 0, |min| <= sample < |max|\n"
-            "  -a: send metric (name/sample) to Autotest only\n"
-            "  -b: send metric to both Chrome and Autotest\n"
-            "  -e: send linear/enumeration histogram data\n"
-            "  -t: convert sample from double seconds to int milliseconds\n");
-    return 1;
-  }
-
+static int SendStats(char* argv[],
+                     int name_index,
+                     bool send_enum,
+                     bool secs_to_msecs,
+                     bool send_to_autotest,
+                     bool send_to_chrome) {
   const char* name = argv[name_index];
   int sample;
   if (secs_to_msecs) {
@@ -92,3 +58,89 @@
   }
   return 0;
 }
+
+static int HasConsent() {
+  MetricsLibrary metrics_lib;
+  metrics_lib.Init();
+  return metrics_lib.AreMetricsEnabled() ? 0 : 1;
+}
+
+static int IsGuestMode() {
+  MetricsLibrary metrics_lib;
+  metrics_lib.Init();
+  return metrics_lib.IsGuestMode() ? 0 : 1;
+}
+
+int main(int argc, char** argv) {
+  enum Mode {
+    kModeSendStats,
+    kModeHasConsent,
+    kModeIsGuestMode
+  } mode = kModeSendStats;
+  bool send_to_autotest = false;
+  bool send_to_chrome = true;
+  bool send_enum = false;
+  bool secs_to_msecs = false;
+  bool print_usage = false;
+
+  // Parse arguments
+  int flag;
+  while ((flag = getopt(argc, argv, "abcegt")) != -1) {
+    switch (flag) {
+      case 'a':
+        mode = kModeSendStats;
+        send_to_autotest = true;
+        send_to_chrome = false;
+        break;
+      case 'b':
+        mode = kModeSendStats;
+        send_to_chrome = true;
+        send_to_autotest = true;
+        break;
+      case 'c':
+        mode = kModeHasConsent;
+        break;
+      case 'e':
+        send_enum = true;
+        break;
+      case 'g':
+        mode = kModeIsGuestMode;
+        break;
+      case 't':
+        secs_to_msecs = true;
+        break;
+      default:
+        print_usage = true;
+        break;
+    }
+  }
+  int name_index = optind;
+
+  int expected_args = 0;
+  if (mode == kModeSendStats)
+    expected_args = send_enum ? 3 : 5;
+
+  if ((name_index + expected_args) != argc) {
+    ShowUsage();
+  }
+
+  switch(mode) {
+    case kModeSendStats:
+      if (send_enum && secs_to_msecs) {
+        ShowUsage();
+      }
+      return SendStats(argv,
+                       name_index,
+                       send_enum,
+                       secs_to_msecs,
+                       send_to_autotest,
+                       send_to_chrome);
+    case kModeHasConsent:
+      return HasConsent();
+    case kModeIsGuestMode:
+      return IsGuestMode();
+    default:
+      ShowUsage();
+      return 0;
+  }
+}
diff --git a/metrics/metrics_daemon.cc b/metrics/metrics_daemon.cc
index 2f62330..a5059d9 100644
--- a/metrics/metrics_daemon.cc
+++ b/metrics/metrics_daemon.cc
@@ -210,7 +210,6 @@
 
   DeleteFrequencyCounters();
   ConfigureCrashFrequencyReporter(kMetricAnyCrashesDailyName);
-  ConfigureCrashFrequencyReporter(kMetricAnyCrashesDailyName);
   ConfigureCrashFrequencyReporter(kMetricAnyCrashesWeeklyName);
   ConfigureCrashFrequencyReporter(kMetricKernelCrashesDailyName);
   ConfigureCrashFrequencyReporter(kMetricKernelCrashesWeeklyName);
diff --git a/metrics/metrics_library.cc b/metrics/metrics_library.cc
index 3e3987d..f720972 100644
--- a/metrics/metrics_library.cc
+++ b/metrics/metrics_library.cc
@@ -44,13 +44,84 @@
     : uma_events_file_(NULL),
       consent_file_(kConsentFile) {}
 
+// We take buffer and buffer_size as parameters in order to simplify testing
+// of various alignments of the |device_name| with |buffer_size|.
+bool MetricsLibrary::IsDeviceMounted(const char* device_name,
+                                     const char* mounts_file,
+                                     char* buffer,
+                                     int buffer_size,
+                                     bool* result) {
+  if (buffer == NULL || buffer_size < 1)
+    return false;
+  int mounts_fd = open(mounts_file, O_RDONLY);
+  if (mounts_fd < 0)
+    return false;
+  // match_offset describes:
+  //   -1 -- not beginning of line
+  //   0..strlen(device_name)-1 -- this offset in device_name is next to match
+  //   strlen(device_name) -- matched full name, just need a space.
+  int match_offset = 0;
+  bool match = false;
+  while (!match) {
+    int read_size = read(mounts_fd, buffer, buffer_size);
+    if (read_size <= 0) {
+      if (errno == -EINTR)
+        continue;
+      break;
+    }
+    for (int i = 0; i < read_size; ++i) {
+      if (buffer[i] == '\n') {
+        match_offset = 0;
+        continue;
+      }
+      if (match_offset < 0) {
+        continue;
+      }
+      if (device_name[match_offset] == '\0') {
+        if (buffer[i] == ' ') {
+          match = true;
+          break;
+        }
+        match_offset = -1;
+        continue;
+      }
+
+      if (buffer[i] == device_name[match_offset]) {
+        ++match_offset;
+      } else {
+        match_offset = -1;
+      }
+    }
+  }
+  close(mounts_fd);
+  *result = match;
+  return true;
+}
+
+bool MetricsLibrary::IsGuestMode() {
+  char buffer[256];
+  bool result = false;
+  if (!IsDeviceMounted("guestfs",
+                       "/proc/mounts",
+                       buffer,
+                       sizeof(buffer),
+                       &result)) {
+    return false;
+  }
+  return result;
+}
+
 bool MetricsLibrary::AreMetricsEnabled() {
   static struct stat stat_buffer;
   time_t this_check_time = time(NULL);
 
   if (this_check_time != cached_enabled_time_) {
     cached_enabled_time_ = this_check_time;
-    cached_enabled_ = (stat(consent_file_, &stat_buffer) >= 0);
+    if (stat(consent_file_, &stat_buffer) >= 0 &&
+        !IsGuestMode())
+      cached_enabled_ = true;
+    else
+      cached_enabled_ = false;
   }
   return cached_enabled_;
 }
diff --git a/metrics/metrics_library.h b/metrics/metrics_library.h
index 52da94d..fb31c20 100644
--- a/metrics/metrics_library.h
+++ b/metrics/metrics_library.h
@@ -27,6 +27,9 @@
   // Initializes the library.
   void Init();
 
+  // Returns whether or not the machine is running in guest mode.
+  bool IsGuestMode();
+
   // Returns whether or not metrics collection is enabled.
   bool AreMetricsEnabled();
 
@@ -75,9 +78,18 @@
   FRIEND_TEST(MetricsLibraryTest, AreMetricsEnabled);
   FRIEND_TEST(MetricsLibraryTest, FormatChromeMessage);
   FRIEND_TEST(MetricsLibraryTest, FormatChromeMessageTooLong);
+  FRIEND_TEST(MetricsLibraryTest, IsDeviceMounted);
   FRIEND_TEST(MetricsLibraryTest, SendMessageToChrome);
   FRIEND_TEST(MetricsLibraryTest, SendMessageToChromeUMAEventsBadFileLocation);
 
+  // Sets |*result| to whether or not the |mounts_file| indicates that
+  // the |device_name| is currently mounted.  Uses |buffer| of
+  // |buffer_size| to read the file.  Returns false if any error.
+  bool IsDeviceMounted(const char* device_name,
+                       const char* mounts_file,
+                       char* buffer, int buffer_size,
+                       bool* result);
+
   // Sends message of size |length| to Chrome for transport to UMA and
   // returns true on success.
   bool SendMessageToChrome(int32_t length, const char* message);
diff --git a/metrics/metrics_library_test.cc b/metrics/metrics_library_test.cc
index cbf4cce..0cd695b 100644
--- a/metrics/metrics_library_test.cc
+++ b/metrics/metrics_library_test.cc
@@ -11,8 +11,8 @@
 #include "metrics_library.h"
 
 static const FilePath kTestUMAEventsFile("test-uma-events");
-
 static const char kTestConsent[] = "test-consent";
+static const char kTestMounts[] = "test-mounts";
 
 static void SetMetricsEnabled(bool enabled) {
   if (enabled)
@@ -35,6 +35,8 @@
   }
 
   virtual void TearDown() {
+    file_util::Delete(FilePath(kTestConsent), false);
+    file_util::Delete(FilePath(kTestMounts), false);
     file_util::Delete(kTestUMAEventsFile, false);
   }
 
@@ -44,6 +46,65 @@
   MetricsLibrary lib_;
 };
 
+TEST_F(MetricsLibraryTest, IsDeviceMounted) {
+  static const char kTestContents[] =
+      "0123456789abcde 0123456789abcde\nguestfs foo bar\n";
+  char buffer[1024];
+  int block_sizes[] = { 1, 2, 3, 4, 5, 6, 8, 12, 14, 16, 32, 1024 };
+  bool result;
+  EXPECT_FALSE(lib_.IsDeviceMounted("guestfs",
+                                    "nonexistent",
+                                    buffer,
+                                    1,
+                                    &result));
+  ASSERT_TRUE(file_util::WriteFile(FilePath(kTestMounts),
+                                   kTestContents,
+                                   strlen(kTestContents)));
+  EXPECT_FALSE(lib_.IsDeviceMounted("guestfs",
+                                    kTestMounts,
+                                    buffer,
+                                    0,
+                                    &result));
+  for (size_t i = 0; i < arraysize(block_sizes); ++i) {
+    EXPECT_TRUE(lib_.IsDeviceMounted("0123456789abcde",
+                                     kTestMounts,
+                                     buffer,
+                                     block_sizes[i],
+                                     &result));
+    EXPECT_TRUE(result);
+    EXPECT_TRUE(lib_.IsDeviceMounted("guestfs",
+                                     kTestMounts,
+                                     buffer,
+                                     block_sizes[i],
+                                     &result));
+    EXPECT_TRUE(result);
+    EXPECT_TRUE(lib_.IsDeviceMounted("0123456",
+                                     kTestMounts,
+                                     buffer,
+                                     block_sizes[i],
+                                     &result));
+    EXPECT_FALSE(result);
+    EXPECT_TRUE(lib_.IsDeviceMounted("9abcde",
+                                     kTestMounts,
+                                     buffer,
+                                     block_sizes[i],
+                                     &result));
+    EXPECT_FALSE(result);
+    EXPECT_TRUE(lib_.IsDeviceMounted("foo",
+                                     kTestMounts,
+                                     buffer,
+                                     block_sizes[i],
+                                     &result));
+    EXPECT_FALSE(result);
+    EXPECT_TRUE(lib_.IsDeviceMounted("bar",
+                                     kTestMounts,
+                                     buffer,
+                                     block_sizes[i],
+                                     &result));
+    EXPECT_FALSE(result);
+  }
+}
+
 TEST_F(MetricsLibraryTest, AreMetricsEnabledFalse) {
   SetMetricsEnabled(false);
   EXPECT_FALSE(lib_.AreMetricsEnabled());