updater: Skip an updated partition on retry.
Prior to the change, the BBOTA updater would try to re-run all the
commands for a given partition on retry, including creating stashes
according to the list of commands. This could fail a retry when the
previous update had moved on to next stage, with leftovers in /cache.
This CL creates a marker on /cache upon successfully updating a
partition. The update commands will be skipped when trying to apply
updates on an updated partition. Note that the marker is expected to be
removed while doing a normal boot (in particular, handled by
RecoverySystem#handleAftermath). If that didn't happen, the updater
would also remove the marker before starting next fresh update.
Alternatively, we can achieve the same goal by changing the OTA script,
which needs to additionally compare the checksum against the target
build. For example,
range_sha1("/system", "ranges") == SHA1_of_updated_system ||
block_image_update("/system");
The downside is that we need to pay that cost on each install, as the
edify script doesn't support caching the result in a variable.
Bug: 79165963
Test: Simulate the process on device (by triggering a reboot while
updating /vendor). Check the update log and result.
Change-Id: I731031fa336133e1221b33edfc469969706e8091
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index d767d44..b0ce2c3 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -116,6 +116,22 @@
return true;
}
+static bool FsyncDir(const std::string& dirname) {
+ android::base::unique_fd dfd(
+ TEMP_FAILURE_RETRY(ota_open(dirname.c_str(), O_RDONLY | O_DIRECTORY)));
+ if (dfd == -1) {
+ failure_type = kFileOpenFailure;
+ PLOG(ERROR) << "Failed to open " << dirname;
+ return false;
+ }
+ if (fsync(dfd) == -1) {
+ failure_type = kFsyncFailure;
+ PLOG(ERROR) << "Failed to fsync " << dirname;
+ return false;
+ }
+ return true;
+}
+
// Update the last command index in the last_command_file if the current command writes to the
// stash either explicitly or implicitly.
static bool UpdateLastCommandIndex(int command_index, const std::string& command_string) {
@@ -144,19 +160,22 @@
return false;
}
- std::string last_command_dir = android::base::Dirname(last_command_file);
- android::base::unique_fd dfd(
- TEMP_FAILURE_RETRY(ota_open(last_command_dir.c_str(), O_RDONLY | O_DIRECTORY)));
- if (dfd == -1) {
- PLOG(ERROR) << "Failed to open " << last_command_dir;
+ if (!FsyncDir(android::base::Dirname(last_command_file))) {
return false;
}
- if (fsync(dfd) == -1) {
- PLOG(ERROR) << "Failed to fsync " << last_command_dir;
+ return true;
+}
+
+static bool SetPartitionUpdatedMarker(const std::string& marker) {
+ if (!android::base::WriteStringToFile("", marker)) {
+ PLOG(ERROR) << "Failed to write to marker file " << marker;
return false;
}
-
+ if (!FsyncDir(android::base::Dirname(marker))) {
+ return false;
+ }
+ LOG(INFO) << "Wrote partition updated marker to " << marker;
return true;
}
@@ -676,7 +695,11 @@
if (base.empty()) {
return "";
}
- return Paths::Get().stash_directory_base() + "/" + base + "/" + id + postfix;
+ std::string filename = Paths::Get().stash_directory_base() + "/" + base;
+ if (id.empty() && postfix.empty()) {
+ return filename;
+ }
+ return filename + "/" + id + postfix;
}
// Does a best effort enumeration of stash files. Ignores possible non-file items in the stash
@@ -864,18 +887,9 @@
}
std::string dname = GetStashFileName(base, "", "");
- android::base::unique_fd dfd(TEMP_FAILURE_RETRY(ota_open(dname.c_str(),
- O_RDONLY | O_DIRECTORY)));
- if (dfd == -1) {
- failure_type = kFileOpenFailure;
- PLOG(ERROR) << "failed to open \"" << dname << "\" failed";
- return -1;
- }
-
- if (ota_fsync(dfd) == -1) {
- failure_type = kFsyncFailure;
- PLOG(ERROR) << "fsync \"" << dname << "\" failed";
- return -1;
+ if (!FsyncDir(dname)) {
+ failure_type = kFsyncFailure;
+ return -1;
}
return 0;
@@ -884,30 +898,18 @@
// Creates a directory for storing stash files and checks if the /cache partition
// hash enough space for the expected amount of blocks we need to store. Returns
// >0 if we created the directory, zero if it existed already, and <0 of failure.
-
-static int CreateStash(State* state, size_t maxblocks, const std::string& blockdev,
- std::string& base) {
- if (blockdev.empty()) {
- return -1;
- }
-
- // Stash directory should be different for each partition to avoid conflicts
- // when updating multiple partitions at the same time, so we use the hash of
- // the block device name as the base directory
- uint8_t digest[SHA_DIGEST_LENGTH];
- SHA1(reinterpret_cast<const uint8_t*>(blockdev.data()), blockdev.size(), digest);
- base = print_sha1(digest);
-
+static int CreateStash(State* state, size_t maxblocks, const std::string& base) {
std::string dirname = GetStashFileName(base, "", "");
struct stat sb;
int res = stat(dirname.c_str(), &sb);
- size_t max_stash_size = maxblocks * BLOCKSIZE;
-
if (res == -1 && errno != ENOENT) {
ErrorAbort(state, kStashCreationFailure, "stat \"%s\" failed: %s", dirname.c_str(),
strerror(errno));
return -1;
- } else if (res != 0) {
+ }
+
+ size_t max_stash_size = maxblocks * BLOCKSIZE;
+ if (res == -1) {
LOG(INFO) << "creating stash " << dirname;
res = mkdir(dirname.c_str(), STASH_DIRECTORY_MODE);
@@ -1595,6 +1597,35 @@
return StringValue("");
}
+ // Stash directory should be different for each partition to avoid conflicts when updating
+ // multiple partitions at the same time, so we use the hash of the block device name as the base
+ // directory.
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ SHA1(reinterpret_cast<const uint8_t*>(blockdev_filename->data.data()),
+ blockdev_filename->data.size(), digest);
+ params.stashbase = print_sha1(digest);
+
+ // Possibly do return early on retry, by checking the marker. If the update on this partition has
+ // been finished (but interrupted at a later point), there could be leftover on /cache that would
+ // fail the no-op retry.
+ std::string updated_marker = GetStashFileName(params.stashbase + ".UPDATED", "", "");
+ if (is_retry) {
+ struct stat sb;
+ int result = stat(updated_marker.c_str(), &sb);
+ if (result == 0) {
+ LOG(INFO) << "Skipping already updated partition " << blockdev_filename->data
+ << " based on marker";
+ return StringValue("t");
+ }
+ } else {
+ // Delete the obsolete marker if any.
+ std::string err;
+ if (!android::base::RemoveFileIfExists(updated_marker, &err)) {
+ LOG(ERROR) << "Failed to remove partition updated marker " << updated_marker << ": " << err;
+ return StringValue("");
+ }
+ }
+
if (params.canwrite) {
params.nti.za = za;
params.nti.entry = new_entry;
@@ -1662,7 +1693,7 @@
return StringValue("");
}
- int res = CreateStash(state, stash_max_blocks, blockdev_filename->data, params.stashbase);
+ int res = CreateStash(state, stash_max_blocks, params.stashbase);
if (res == -1) {
return StringValue("");
}
@@ -1799,6 +1830,13 @@
// to complete the update later.
DeleteStash(params.stashbase);
DeleteLastCommandFile();
+
+ // Create a marker on /cache partition, which allows skipping the update on this partition on
+ // retry. The marker will be removed once booting into normal boot, or before starting next
+ // fresh install.
+ if (!SetPartitionUpdatedMarker(updated_marker)) {
+ LOG(WARNING) << "Failed to set updated marker; continuing";
+ }
}
pthread_mutex_destroy(¶ms.nti.mu);