Add D-Bus method to get the duration since an update completed.

This new API can be used by Chrome to automatically reboot the device
at N hours after updating. This is implemented as a D-Bus method that
returns the number of micro-seconds on the wall-clock since the update
completed. If the device has not updated, the D-Bus method returns an
error.

For robustness, durations are measured using the CLOCK_BOOTTIME clock
instead of the usual CLOCK_REALTIME clock. This avoids interference
with NTP adjustments, the RTC clock being wrong and other things.

BUG=chromium:218192
TEST=New unit test + unit tests pass + manual test on a device using
    the gdbus(1) command as the chronos user.

Change-Id: I51d44d69afe2d3024bb0780916c3c4e3f8ebb19e
Reviewed-on: https://chromium-review.googlesource.com/173032
Reviewed-by: David Zeuthen <[email protected]>
Commit-Queue: David Zeuthen <[email protected]>
Tested-by: David Zeuthen <[email protected]>
diff --git a/UpdateEngine.conf b/UpdateEngine.conf
index dee64ee..3688250 100644
--- a/UpdateEngine.conf
+++ b/UpdateEngine.conf
@@ -49,6 +49,9 @@
     <allow send_destination="org.chromium.UpdateEngine"
            send_interface="org.chromium.UpdateEngineInterface"
            send_member="GetUpdateOverCellularPermission"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetDurationSinceUpdate"/>
     <allow send_interface="org.chromium.UpdateEngineLibcrosProxyResolvedInterface" />
   </policy>
   <policy context="default">
diff --git a/clock.cc b/clock.cc
index 96979f9..ac8945b 100644
--- a/clock.cc
+++ b/clock.cc
@@ -16,7 +16,25 @@
   struct timespec now_ts;
   if (clock_gettime(CLOCK_MONOTONIC_RAW, &now_ts) != 0) {
     // Avoid logging this as an error as call-sites may call this very
-    // often and we don't want to fill up the disk...
+    // often and we don't want to fill up the disk. Note that this
+    // only fails if running on ancient kernels (CLOCK_MONOTONIC_RAW
+    // was added in Linux 2.6.28) so it never fails on a ChromeOS
+    // device.
+    return base::Time();
+  }
+  struct timeval now_tv;
+  now_tv.tv_sec = now_ts.tv_sec;
+  now_tv.tv_usec = now_ts.tv_nsec/base::Time::kNanosecondsPerMicrosecond;
+  return base::Time::FromTimeVal(now_tv);
+}
+
+base::Time Clock::GetBootTime() {
+  struct timespec now_ts;
+  if (clock_gettime(CLOCK_BOOTTIME, &now_ts) != 0) {
+    // Avoid logging this as an error as call-sites may call this very
+    // often and we don't want to fill up the disk. Note that this
+    // only fails if running on ancient kernels (CLOCK_BOOTTIME was
+    // added in Linux 2.6.39) so it never fails on a ChromeOS device.
     return base::Time();
   }
   struct timeval now_tv;
diff --git a/clock.h b/clock.h
index 027bcff..8cfe366 100644
--- a/clock.h
+++ b/clock.h
@@ -18,6 +18,8 @@
 
   virtual base::Time GetMonotonicTime();
 
+  virtual base::Time GetBootTime();
+
  private:
 
   DISALLOW_COPY_AND_ASSIGN(Clock);
diff --git a/clock_interface.h b/clock_interface.h
index c2b1a04..d9e6124 100644
--- a/clock_interface.h
+++ b/clock_interface.h
@@ -27,6 +27,13 @@
   // (This is a simple wrapper around clock_gettime(2) / CLOCK_MONOTONIC_RAW.)
   virtual base::Time GetMonotonicTime() = 0;
 
+  // Returns monotonic time since some unspecified starting point. It
+  // is increased when the system is sleeping but it's not affected
+  // by NTP or the user changing the time.
+  //
+  // (This is a simple wrapper around clock_gettime(2) / CLOCK_BOOTTIME.)
+  virtual base::Time GetBootTime() = 0;
+
   virtual ~ClockInterface() {}
 };
 
diff --git a/dbus_service.cc b/dbus_service.cc
index 42bcfa3..956b73b 100644
--- a/dbus_service.cc
+++ b/dbus_service.cc
@@ -10,6 +10,7 @@
 #include <base/logging.h>
 #include <policy/device_policy.h>
 
+#include "update_engine/clock_interface.h"
 #include "update_engine/connection_manager.h"
 #include "update_engine/dbus_constants.h"
 #include "update_engine/hardware_interface.h"
@@ -347,6 +348,20 @@
   return TRUE;
 }
 
+gboolean update_engine_service_get_duration_since_update(
+    UpdateEngineService* self,
+    gint64* out_usec_wallclock,
+    GError **/*error*/) {
+
+  base::Time time;
+  if (!self->system_state_->update_attempter()->GetBootTimeAtUpdate(&time))
+    return FALSE;
+
+  chromeos_update_engine::ClockInterface *clock = self->system_state_->clock();
+  *out_usec_wallclock = (clock->GetBootTime() - time).InMicroseconds();
+  return TRUE;
+}
+
 gboolean update_engine_service_emit_status_update(
     UpdateEngineService* self,
     gint64 last_checked_time,
diff --git a/dbus_service.h b/dbus_service.h
index 6d449df..5d0f500 100644
--- a/dbus_service.h
+++ b/dbus_service.h
@@ -128,6 +128,14 @@
     gboolean* allowed,
     GError **error);
 
+// Returns the duration since the last successful update, as the
+// duration on the wallclock. Returns an error if the device has not
+// updated.
+gboolean update_engine_service_get_duration_since_update(
+    UpdateEngineService* self,
+    gint64* out_usec_wallclock,
+    GError **error);
+
 gboolean update_engine_service_emit_status_update(
     UpdateEngineService* self,
     gint64 last_checked_time,
diff --git a/fake_clock.h b/fake_clock.h
index 105d664..d920aaf 100644
--- a/fake_clock.h
+++ b/fake_clock.h
@@ -22,6 +22,10 @@
     return monotonic_time_;
   }
 
+  virtual base::Time GetBootTime() {
+    return boot_time_;
+  }
+
   void SetWallclockTime(const base::Time &time) {
     wallclock_time_ = time;
   }
@@ -30,9 +34,14 @@
     monotonic_time_ = time;
   }
 
+  void SetBootTime(const base::Time &time) {
+    boot_time_ = time;
+  }
+
  private:
   base::Time wallclock_time_;
   base::Time monotonic_time_;
+  base::Time boot_time_;
 
   DISALLOW_COPY_AND_ASSIGN(FakeClock);
 };
diff --git a/update_attempter.cc b/update_attempter.cc
index 69a625d..eb57644 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -9,8 +9,11 @@
 #include <vector>
 
 #include <base/file_util.h>
+#include <base/logging.h>
 #include <base/rand_util.h>
+#include <base/stringprintf.h>
 #include <chromeos/dbus/service_constants.h>
+
 #include <glib.h>
 #include <metrics/metrics_library.h>
 #include <policy/libpolicy.h>
@@ -38,8 +41,10 @@
 #include "update_engine/update_check_scheduler.h"
 #include "update_engine/utils.h"
 
+using base::Time;
 using base::TimeDelta;
 using base::TimeTicks;
+using base::StringPrintf;
 using google::protobuf::NewPermanentCallback;
 using std::make_pair;
 using std::tr1::shared_ptr;
@@ -799,6 +804,18 @@
   return true;
 }
 
+void UpdateAttempter::WriteUpdateCompletedMarker() {
+  if (update_completed_marker_.empty())
+    return;
+
+  int64_t value = system_state_->clock()->GetBootTime().ToInternalValue();
+  string contents = StringPrintf("%" PRIi64, value);
+
+  utils::WriteFile(update_completed_marker_.c_str(),
+                   contents.c_str(),
+                   contents.length());
+}
+
 // Delegate methods:
 void UpdateAttempter::ProcessingDone(const ActionProcessor* processor,
                                      ErrorCode code) {
@@ -825,8 +842,7 @@
   }
 
   if (code == kErrorCodeSuccess) {
-    if (!update_completed_marker_.empty())
-      utils::WriteFile(update_completed_marker_.c_str(), "", 0);
+    WriteUpdateCompletedMarker();
     prefs_->SetInt64(kPrefsDeltaUpdateFailures, 0);
     prefs_->SetString(kPrefsPreviousVersion,
                       omaha_request_params_->app_version());
@@ -1423,4 +1439,24 @@
   return true;
 }
 
+bool UpdateAttempter::GetBootTimeAtUpdate(base::Time *out_boot_time) {
+  if (update_completed_marker_.empty())
+    return false;
+
+  string contents;
+  if (!utils::ReadFile(update_completed_marker_, &contents))
+    return false;
+
+  char *endp;
+  int64_t stored_value = strtoll(contents.c_str(), &endp, 10);
+  if (*endp != '\0') {
+    LOG(ERROR) << "Error parsing file " << update_completed_marker_ << " "
+               << "with content '" << contents << "'";
+    return false;
+  }
+
+  *out_boot_time = Time::FromInternalValue(stored_value);
+  return true;
+}
+
 }  // namespace chromeos_update_engine
diff --git a/update_attempter.h b/update_attempter.h
index 74b73f6..70381d9 100644
--- a/update_attempter.h
+++ b/update_attempter.h
@@ -179,6 +179,10 @@
   // from the server asynchronously at its own frequency.
   void RefreshDevicePolicy();
 
+  // Returns the boottime (CLOCK_BOOTTIME) recorded at the last
+  // successful update. Returns false if the device has not updated.
+  bool GetBootTimeAtUpdate(base::Time *out_boot_time);
+
  private:
   // Update server URL for automated lab test.
   static const char* const kTestUpdateUrl;
@@ -203,6 +207,7 @@
   FRIEND_TEST(UpdateAttempterTest, ScheduleErrorEventActionTest);
   FRIEND_TEST(UpdateAttempterTest, UpdateTest);
   FRIEND_TEST(UpdateAttempterTest, ReportDailyMetrics);
+  FRIEND_TEST(UpdateAttempterTest, BootTimeInUpdateMarkerFile);
 
   // Ctor helper method.
   void Init(SystemState* system_state,
@@ -329,6 +334,10 @@
   // started and housekeeping was performed.
   bool StartP2PAtStartup();
 
+  // Writes to the processing completed marker. Does nothing if
+  // |update_completed_marker_| is empty.
+  void WriteUpdateCompletedMarker();
+
   // Last status notification timestamp used for throttling. Use monotonic
   // TimeTicks to ensure that notifications are sent even if the system clock is
   // set back in the middle of an update.
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
index 2e3c6b2..10af01e 100644
--- a/update_attempter_unittest.cc
+++ b/update_attempter_unittest.cc
@@ -1094,4 +1094,22 @@
   EXPECT_TRUE(utils::RecursiveUnlinkDir(temp_dir));
 }
 
+TEST_F(UpdateAttempterTest, BootTimeInUpdateMarkerFile) {
+  const string update_completed_marker = test_dir_ + "/update-completed-marker";
+  UpdateAttempterUnderTest attempter(&mock_system_state_, &dbus_,
+                                     update_completed_marker);
+
+  FakeClock fake_clock;
+  fake_clock.SetBootTime(Time::FromTimeT(42));
+  mock_system_state_.set_clock(&fake_clock);
+
+  Time boot_time;
+  EXPECT_FALSE(attempter.GetBootTimeAtUpdate(&boot_time));
+
+  attempter.WriteUpdateCompletedMarker();
+
+  EXPECT_TRUE(attempter.GetBootTimeAtUpdate(&boot_time));
+  EXPECT_EQ(boot_time.ToTimeT(), 42);
+}
+
 }  // namespace chromeos_update_engine
diff --git a/update_engine.xml b/update_engine.xml
index ad6c36b..5a2b722 100644
--- a/update_engine.xml
+++ b/update_engine.xml
@@ -65,6 +65,9 @@
     <method name="GetUpdateOverCellularPermission">
       <arg type="b" name="allowed" direction="out" />
     </method>
+    <method name="GetDurationSinceUpdate">
+      <arg type="x" name="usec_wallclock" direction="out" />
+    </method>
     <signal name="StatusUpdate">
       <arg type="x" name="last_checked_time" />
       <arg type="d" name="progress" />