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" />