From 864c668dd204e3cc7cdaa5d1c3d5b2da3d6f7f5e Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Mon, 7 May 2018 11:38:25 -0700 Subject: 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 --- updater/blockimg.cpp | 116 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 77 insertions(+), 39 deletions(-) diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp index d767d4467..b0ce2c3aa 100644 --- a/updater/blockimg.cpp +++ b/updater/blockimg.cpp @@ -116,6 +116,22 @@ static bool ParseLastCommandFile(int* last_command_index) { 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 @@ static bool UpdateLastCommandIndex(int command_index, const std::string& command 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 @@ static std::string GetStashFileName(const std::string& base, const std::string& 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 @@ static int WriteStash(const std::string& base, const std::string& id, int blocks } 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 @@ static int WriteStash(const std::string& base, const std::string& id, int blocks // 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(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 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, 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(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 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, 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 @@ pbiudone: // 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); -- cgit v1.2.3