Improve atomiticy of update checkpointing

Current check point works by writing different prefs to different files
under a pending directory, and rename the pending directory to actual pref
directory afterwards to achieve atomicity. It has two pitfalls:

1. Before the rename() call, existing prefs dir must be rm -rf'ed , this
   deletion process isn't atomic. If device rebooted during rm -rf, we
   will end up with a partially deleted old pref.

2. fsync() on the parent directory is needed after rename()

This CL addresses both issues. For #1, we rename() the old pref dir to a
tmp dir first, and then rm -rf the tmp dir. Upon device restart, if the
current prefs dir is empty, we can simply rename() the pending directory
to actual pref directory.

Test: th
Bug: 295252766
Change-Id: Ic671a18245986c579b51d7443c3e8c10e206c448
diff --git a/common/prefs.cc b/common/prefs.cc
index 57d4dcd..af4d318 100644
--- a/common/prefs.cc
+++ b/common/prefs.cc
@@ -20,6 +20,7 @@
 #include <filesystem>
 #include <unistd.h>
 
+#include <android-base/file.h>
 #include <base/files/file_enumerator.h>
 #include <base/files/file_util.h>
 #include <base/logging.h>
@@ -27,7 +28,6 @@
 #include <base/strings/string_split.h>
 #include <base/strings/string_util.h>
 
-#include "update_engine/common/platform_constants.h"
 #include "update_engine/common/utils.h"
 
 using std::string;
@@ -74,7 +74,16 @@
   if (!GetString(key, &str_value))
     return false;
   base::TrimWhitespaceASCII(str_value, base::TRIM_ALL, &str_value);
-  TEST_AND_RETURN_FALSE(base::StringToInt64(str_value, value));
+  if (str_value.empty()) {
+    LOG(ERROR) << "When reading pref " << key
+               << ", got an empty value after trim";
+    return false;
+  }
+  if (!base::StringToInt64(str_value, value)) {
+    LOG(ERROR) << "When reading pref " << key << ", failed to convert value "
+               << str_value << " to integer";
+    return false;
+  }
   return true;
 }
 
@@ -206,18 +215,30 @@
 }
 
 bool Prefs::FileStorage::SwapPrefs() {
-  if (std::filesystem::exists(prefs_dir_.value())) {
-    std::filesystem::remove_all(prefs_dir_.value());
+  if (!utils::DeleteDirectory(prefs_dir_.value().c_str())) {
+    LOG(ERROR) << "Failed to remove prefs dir " << prefs_dir_;
+    return false;
   }
   if (rename(GetTemporaryDir().c_str(), prefs_dir_.value().c_str()) != 0) {
     LOG(ERROR) << "Error replacing prefs with prefs_tmp" << strerror(errno);
     return false;
   }
+  if (!utils::FsyncDirectory(
+          android::base::Dirname(prefs_dir_.value()).c_str())) {
+    PLOG(ERROR) << "Failed to fsync prefs parent dir after swapping prefs";
+  }
   return true;
 }
 
 bool Prefs::FileStorage::Init(const base::FilePath& prefs_dir) {
   prefs_dir_ = prefs_dir;
+  if (!std::filesystem::exists(prefs_dir_.value())) {
+    LOG(INFO) << "Prefs dir does not exist, possibly due to an interrupted "
+                 "transaction.";
+    if (std::filesystem::exists(GetTemporaryDir())) {
+      SwapPrefs();
+    }
+  }
 
   // Delete empty directories. Ignore errors when deleting empty directories.
   DeleteEmptyDirectories(prefs_dir_);