diff options
-rw-r--r-- | applypatch/applypatch.cpp | 1044 | ||||
-rw-r--r-- | bootloader_message/bootloader_message.cpp | 69 | ||||
-rw-r--r-- | bootloader_message/include/bootloader_message/bootloader_message.h | 18 | ||||
-rw-r--r-- | otafault/ota_io.h | 10 | ||||
-rw-r--r-- | tests/Android.mk | 2 | ||||
-rw-r--r-- | tests/common/test_constants.h | 2 | ||||
-rw-r--r-- | tests/component/updater_test.cpp | 188 | ||||
-rw-r--r-- | update_verifier/Android.mk | 2 | ||||
-rw-r--r-- | updater/Android.mk | 2 | ||||
-rw-r--r-- | updater/install.cpp | 207 |
10 files changed, 890 insertions, 654 deletions
diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp index 9d505f9f6..9b84fa104 100644 --- a/applypatch/applypatch.cpp +++ b/applypatch/applypatch.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include "applypatch/applypatch.h" + #include <errno.h> #include <fcntl.h> #include <libgen.h> @@ -27,16 +29,18 @@ #include <memory> #include <string> +#include <utility> +#include <vector> +#include <android-base/parseint.h> #include <android-base/strings.h> +#include <openssl/sha.h> -#include "openssl/sha.h" -#include "applypatch/applypatch.h" #include "edify/expr.h" #include "ota_io.h" #include "print_sha1.h" -static int LoadPartitionContents(const char* filename, FileContents* file); +static int LoadPartitionContents(const std::string& filename, FileContents* file); static ssize_t FileSink(const unsigned char* data, ssize_t len, void* token); static int GenerateTarget(FileContents* source_file, const Value* source_patch_value, @@ -48,39 +52,34 @@ static int GenerateTarget(FileContents* source_file, size_t target_size, const Value* bonus_data); -// Read a file into memory; store the file contents and associated -// metadata in *file. -// +// Read a file into memory; store the file contents and associated metadata in *file. // Return 0 on success. int LoadFileContents(const char* filename, FileContents* file) { - // A special 'filename' beginning with "EMMC:" means to - // load the contents of a partition. - if (strncmp(filename, "EMMC:", 5) == 0) { - return LoadPartitionContents(filename, file); - } + // A special 'filename' beginning with "EMMC:" means to load the contents of a partition. + if (strncmp(filename, "EMMC:", 5) == 0) { + return LoadPartitionContents(filename, file); + } - if (stat(filename, &file->st) != 0) { - printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); - return -1; - } + if (stat(filename, &file->st) == -1) { + printf("failed to stat \"%s\": %s\n", filename, strerror(errno)); + return -1; + } - std::vector<unsigned char> data(file->st.st_size); - FILE* f = ota_fopen(filename, "rb"); - if (f == NULL) { - printf("failed to open \"%s\": %s\n", filename, strerror(errno)); - return -1; - } + std::vector<unsigned char> data(file->st.st_size); + std::unique_ptr<FILE, decltype(&ota_fclose)> f(ota_fopen(filename, "rb"), ota_fclose); + if (!f) { + printf("failed to open \"%s\": %s\n", filename, strerror(errno)); + return -1; + } - size_t bytes_read = ota_fread(data.data(), 1, data.size(), f); - if (bytes_read != data.size()) { - printf("short read of \"%s\" (%zu bytes of %zu)\n", filename, bytes_read, data.size()); - ota_fclose(f); - return -1; - } - ota_fclose(f); - file->data = std::move(data); - SHA1(file->data.data(), file->data.size(), file->sha1); - return 0; + size_t bytes_read = ota_fread(data.data(), 1, data.size(), f.get()); + if (bytes_read != data.size()) { + printf("short read of \"%s\" (%zu bytes of %zu)\n", filename, bytes_read, data.size()); + return -1; + } + file->data = std::move(data); + SHA1(file->data.data(), file->data.size(), file->sha1); + return 0; } // Load the contents of an EMMC partition into the provided @@ -97,284 +96,257 @@ int LoadFileContents(const char* filename, FileContents* file) { // "end-of-file" marker), so the caller must specify the possible // lengths and the hash of the data, and we'll do the load expecting // to find one of those hashes. -static int LoadPartitionContents(const char* filename, FileContents* file) { - std::string copy(filename); - std::vector<std::string> pieces = android::base::Split(copy, ":"); - if (pieces.size() < 4 || pieces.size() % 2 != 0) { - printf("LoadPartitionContents called with bad filename (%s)\n", filename); - return -1; - } - - if (pieces[0] != "EMMC") { - printf("LoadPartitionContents called with bad filename (%s)\n", filename); +static int LoadPartitionContents(const std::string& filename, FileContents* file) { + std::vector<std::string> pieces = android::base::Split(filename, ":"); + if (pieces.size() < 4 || pieces.size() % 2 != 0 || pieces[0] != "EMMC") { + printf("LoadPartitionContents called with bad filename \"%s\"\n", filename.c_str()); + return -1; + } + + size_t pair_count = (pieces.size() - 2) / 2; // # of (size, sha1) pairs in filename + std::vector<std::pair<size_t, std::string>> pairs; + for (size_t i = 0; i < pair_count; ++i) { + size_t size; + if (!android::base::ParseUint(pieces[i * 2 + 2], &size) || size == 0) { + printf("LoadPartitionContents called with bad size \"%s\"\n", pieces[i * 2 + 2].c_str()); + return -1; + } + pairs.push_back({ size, pieces[i * 2 + 3] }); + } + + // Sort the pairs array so that they are in order of increasing size. + std::sort(pairs.begin(), pairs.end()); + + const char* partition = pieces[1].c_str(); + std::unique_ptr<FILE, decltype(&ota_fclose)> dev(ota_fopen(partition, "rb"), ota_fclose); + if (!dev) { + printf("failed to open emmc partition \"%s\": %s\n", partition, strerror(errno)); + return -1; + } + + SHA_CTX sha_ctx; + SHA1_Init(&sha_ctx); + + // Allocate enough memory to hold the largest size. + std::vector<unsigned char> buffer(pairs[pair_count - 1].first); + unsigned char* buffer_ptr = buffer.data(); + size_t buffer_size = 0; // # bytes read so far + bool found = false; + + for (const auto& pair : pairs) { + size_t current_size = pair.first; + const std::string& current_sha1 = pair.second; + + // Read enough additional bytes to get us up to the next size. (Again, + // we're trying the possibilities in order of increasing size). + size_t next = current_size - buffer_size; + if (next > 0) { + size_t read = ota_fread(buffer_ptr, 1, next, dev.get()); + if (next != read) { + printf("short read (%zu bytes of %zu) for partition \"%s\"\n", read, next, partition); return -1; - } - const char* partition = pieces[1].c_str(); - - size_t pairs = (pieces.size() - 2) / 2; // # of (size, sha1) pairs in filename - std::vector<size_t> index(pairs); - std::vector<size_t> size(pairs); - std::vector<std::string> sha1sum(pairs); - - for (size_t i = 0; i < pairs; ++i) { - size[i] = strtol(pieces[i*2+2].c_str(), NULL, 10); - if (size[i] == 0) { - printf("LoadPartitionContents called with bad size (%s)\n", filename); - return -1; - } - sha1sum[i] = pieces[i*2+3].c_str(); - index[i] = i; + } + SHA1_Update(&sha_ctx, buffer_ptr, read); + buffer_size += read; + buffer_ptr += read; } - // Sort the index[] array so it indexes the pairs in order of increasing size. - sort(index.begin(), index.end(), - [&](const size_t& i, const size_t& j) { - return (size[i] < size[j]); - } - ); + // Duplicate the SHA context and finalize the duplicate so we can + // check it against this pair's expected hash. + SHA_CTX temp_ctx; + memcpy(&temp_ctx, &sha_ctx, sizeof(SHA_CTX)); + uint8_t sha_so_far[SHA_DIGEST_LENGTH]; + SHA1_Final(sha_so_far, &temp_ctx); - FILE* dev = ota_fopen(partition, "rb"); - if (dev == NULL) { - printf("failed to open emmc partition \"%s\": %s\n", partition, strerror(errno)); - return -1; - } - - SHA_CTX sha_ctx; - SHA1_Init(&sha_ctx); uint8_t parsed_sha[SHA_DIGEST_LENGTH]; - - // Allocate enough memory to hold the largest size. - std::vector<unsigned char> data(size[index[pairs-1]]); - char* p = reinterpret_cast<char*>(data.data()); - size_t data_size = 0; // # bytes read so far - bool found = false; - - for (size_t i = 0; i < pairs; ++i) { - // Read enough additional bytes to get us up to the next size. (Again, - // we're trying the possibilities in order of increasing size). - size_t next = size[index[i]] - data_size; - if (next > 0) { - size_t read = ota_fread(p, 1, next, dev); - if (next != read) { - printf("short read (%zu bytes of %zu) for partition \"%s\"\n", - read, next, partition); - return -1; - } - SHA1_Update(&sha_ctx, p, read); - data_size += read; - p += read; - } - - // Duplicate the SHA context and finalize the duplicate so we can - // check it against this pair's expected hash. - SHA_CTX temp_ctx; - memcpy(&temp_ctx, &sha_ctx, sizeof(SHA_CTX)); - uint8_t sha_so_far[SHA_DIGEST_LENGTH]; - SHA1_Final(sha_so_far, &temp_ctx); - - if (ParseSha1(sha1sum[index[i]].c_str(), parsed_sha) != 0) { - printf("failed to parse sha1 %s in %s\n", sha1sum[index[i]].c_str(), filename); - return -1; - } - - if (memcmp(sha_so_far, parsed_sha, SHA_DIGEST_LENGTH) == 0) { - // we have a match. stop reading the partition; we'll return - // the data we've read so far. - printf("partition read matched size %zu sha %s\n", - size[index[i]], sha1sum[index[i]].c_str()); - found = true; - break; - } + if (ParseSha1(current_sha1.c_str(), parsed_sha) != 0) { + printf("failed to parse SHA-1 %s in %s\n", current_sha1.c_str(), filename.c_str()); + return -1; } - ota_fclose(dev); - - if (!found) { - // Ran off the end of the list of (size,sha1) pairs without finding a match. - printf("contents of partition \"%s\" didn't match %s\n", partition, filename); - return -1; + if (memcmp(sha_so_far, parsed_sha, SHA_DIGEST_LENGTH) == 0) { + // We have a match. Stop reading the partition; we'll return the data we've read so far. + printf("partition read matched size %zu SHA-1 %s\n", current_size, current_sha1.c_str()); + found = true; + break; } + } - SHA1_Final(file->sha1, &sha_ctx); + if (!found) { + // Ran off the end of the list of (size, sha1) pairs without finding a match. + printf("contents of partition \"%s\" didn't match %s\n", partition, filename.c_str()); + return -1; + } - data.resize(data_size); - file->data = std::move(data); - // Fake some stat() info. - file->st.st_mode = 0644; - file->st.st_uid = 0; - file->st.st_gid = 0; + SHA1_Final(file->sha1, &sha_ctx); - return 0; + buffer.resize(buffer_size); + file->data = std::move(buffer); + // Fake some stat() info. + file->st.st_mode = 0644; + file->st.st_uid = 0; + file->st.st_gid = 0; + + return 0; } // Save the contents of the given FileContents object under the given // filename. Return 0 on success. int SaveFileContents(const char* filename, const FileContents* file) { - int fd = ota_open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR); - if (fd < 0) { - printf("failed to open \"%s\" for write: %s\n", filename, strerror(errno)); - return -1; - } + unique_fd fd(ota_open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR)); + if (fd == -1) { + printf("failed to open \"%s\" for write: %s\n", filename, strerror(errno)); + return -1; + } - ssize_t bytes_written = FileSink(file->data.data(), file->data.size(), &fd); - if (bytes_written != static_cast<ssize_t>(file->data.size())) { - printf("short write of \"%s\" (%zd bytes of %zu) (%s)\n", - filename, bytes_written, file->data.size(), strerror(errno)); - ota_close(fd); - return -1; - } - if (ota_fsync(fd) != 0) { - printf("fsync of \"%s\" failed: %s\n", filename, strerror(errno)); - return -1; - } - if (ota_close(fd) != 0) { - printf("close of \"%s\" failed: %s\n", filename, strerror(errno)); - return -1; - } + ssize_t bytes_written = FileSink(file->data.data(), file->data.size(), &fd); + if (bytes_written != static_cast<ssize_t>(file->data.size())) { + printf("short write of \"%s\" (%zd bytes of %zu): %s\n", filename, bytes_written, + file->data.size(), strerror(errno)); + return -1; + } + if (ota_fsync(fd) != 0) { + printf("fsync of \"%s\" failed: %s\n", filename, strerror(errno)); + return -1; + } + if (ota_close(fd) != 0) { + printf("close of \"%s\" failed: %s\n", filename, strerror(errno)); + return -1; + } - if (chmod(filename, file->st.st_mode) != 0) { - printf("chmod of \"%s\" failed: %s\n", filename, strerror(errno)); - return -1; - } - if (chown(filename, file->st.st_uid, file->st.st_gid) != 0) { - printf("chown of \"%s\" failed: %s\n", filename, strerror(errno)); - return -1; - } + if (chmod(filename, file->st.st_mode) != 0) { + printf("chmod of \"%s\" failed: %s\n", filename, strerror(errno)); + return -1; + } + if (chown(filename, file->st.st_uid, file->st.st_gid) != 0) { + printf("chown of \"%s\" failed: %s\n", filename, strerror(errno)); + return -1; + } - return 0; + return 0; } // Write a memory buffer to 'target' partition, a string of the form // "EMMC:<partition_device>[:...]". The target name // might contain multiple colons, but WriteToPartition() only uses the first // two and ignores the rest. Return 0 on success. -int WriteToPartition(const unsigned char* data, size_t len, const char* target) { - std::string copy(target); - std::vector<std::string> pieces = android::base::Split(copy, ":"); +int WriteToPartition(const unsigned char* data, size_t len, const std::string& target) { + std::vector<std::string> pieces = android::base::Split(target, ":"); + if (pieces.size() < 2 || pieces[0] != "EMMC") { + printf("WriteToPartition called with bad target (%s)\n", target.c_str()); + return -1; + } - if (pieces.size() < 2) { - printf("WriteToPartition called with bad target (%s)\n", target); + const char* partition = pieces[1].c_str(); + unique_fd fd(ota_open(partition, O_RDWR)); + if (fd == -1) { + printf("failed to open %s: %s\n", partition, strerror(errno)); + return -1; + } + + size_t start = 0; + bool success = false; + for (size_t attempt = 0; attempt < 2; ++attempt) { + if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) { + printf("failed seek on %s: %s\n", partition, strerror(errno)); + return -1; + } + while (start < len) { + size_t to_write = len - start; + if (to_write > 1 << 20) to_write = 1 << 20; + + ssize_t written = TEMP_FAILURE_RETRY(ota_write(fd, data + start, to_write)); + if (written == -1) { + printf("failed write writing to %s: %s\n", partition, strerror(errno)); return -1; + } + start += written; } - if (pieces[0] != "EMMC") { - printf("WriteToPartition called with bad target (%s)\n", target); - return -1; + if (ota_fsync(fd) != 0) { + printf("failed to sync to %s: %s\n", partition, strerror(errno)); + return -1; + } + if (ota_close(fd) != 0) { + printf("failed to close %s: %s\n", partition, strerror(errno)); + return -1; } - const char* partition = pieces[1].c_str(); - size_t start = 0; - bool success = false; - int fd = ota_open(partition, O_RDWR | O_SYNC); - if (fd < 0) { - printf("failed to open %s: %s\n", partition, strerror(errno)); - return -1; + fd.reset(ota_open(partition, O_RDONLY)); + if (fd == -1) { + printf("failed to reopen %s for verify: %s\n", partition, strerror(errno)); + return -1; } - for (size_t attempt = 0; attempt < 2; ++attempt) { - if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) { - printf("failed seek on %s: %s\n", partition, strerror(errno)); - return -1; - } - while (start < len) { - size_t to_write = len - start; - if (to_write > 1<<20) to_write = 1<<20; - - ssize_t written = TEMP_FAILURE_RETRY(ota_write(fd, data+start, to_write)); - if (written == -1) { - printf("failed write writing to %s: %s\n", partition, strerror(errno)); - return -1; - } - start += written; - } - if (ota_fsync(fd) != 0) { - printf("failed to sync to %s (%s)\n", partition, strerror(errno)); - return -1; - } - if (ota_close(fd) != 0) { - printf("failed to close %s (%s)\n", partition, strerror(errno)); - return -1; - } - fd = ota_open(partition, O_RDONLY); - if (fd < 0) { - printf("failed to reopen %s for verify (%s)\n", partition, strerror(errno)); - return -1; - } + // Drop caches so our subsequent verification read won't just be reading the cache. + sync(); + unique_fd dc(ota_open("/proc/sys/vm/drop_caches", O_WRONLY)); + if (TEMP_FAILURE_RETRY(ota_write(dc, "3\n", 2)) == -1) { + printf("write to /proc/sys/vm/drop_caches failed: %s\n", strerror(errno)); + } else { + printf(" caches dropped\n"); + } + ota_close(dc); + sleep(1); - // Drop caches so our subsequent verification read - // won't just be reading the cache. - sync(); - int dc = ota_open("/proc/sys/vm/drop_caches", O_WRONLY); - if (TEMP_FAILURE_RETRY(ota_write(dc, "3\n", 2)) == -1) { - printf("write to /proc/sys/vm/drop_caches failed: %s\n", strerror(errno)); - } else { - printf(" caches dropped\n"); - } - ota_close(dc); - sleep(1); + // Verify. + if (TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)) == -1) { + printf("failed to seek back to beginning of %s: %s\n", partition, strerror(errno)); + return -1; + } - // verify - if (TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)) == -1) { - printf("failed to seek back to beginning of %s: %s\n", - partition, strerror(errno)); - return -1; + unsigned char buffer[4096]; + start = len; + for (size_t p = 0; p < len; p += sizeof(buffer)) { + size_t to_read = len - p; + if (to_read > sizeof(buffer)) { + to_read = sizeof(buffer); + } + + size_t so_far = 0; + while (so_far < to_read) { + ssize_t read_count = TEMP_FAILURE_RETRY(ota_read(fd, buffer + so_far, to_read - so_far)); + if (read_count == -1) { + printf("verify read error %s at %zu: %s\n", partition, p, strerror(errno)); + return -1; + } else if (read_count == 0) { + printf("verify read reached unexpected EOF, %s at %zu\n", partition, p); + return -1; } - unsigned char buffer[4096]; - start = len; - for (size_t p = 0; p < len; p += sizeof(buffer)) { - size_t to_read = len - p; - if (to_read > sizeof(buffer)) { - to_read = sizeof(buffer); - } - - size_t so_far = 0; - while (so_far < to_read) { - ssize_t read_count = - TEMP_FAILURE_RETRY(ota_read(fd, buffer+so_far, to_read-so_far)); - if (read_count == -1) { - printf("verify read error %s at %zu: %s\n", - partition, p, strerror(errno)); - return -1; - } else if (read_count == 0) { - printf("verify read reached unexpected EOF, %s at %zu\n", partition, p); - return -1; - } - if (static_cast<size_t>(read_count) < to_read) { - printf("short verify read %s at %zu: %zd %zu %s\n", - partition, p, read_count, to_read, strerror(errno)); - } - so_far += read_count; - } - - if (memcmp(buffer, data+p, to_read) != 0) { - printf("verification failed starting at %zu\n", p); - start = p; - break; - } + if (static_cast<size_t>(read_count) < to_read) { + printf("short verify read %s at %zu: %zd %zu\n", partition, p, read_count, to_read); } + so_far += read_count; + } - if (start == len) { - printf("verification read succeeded (attempt %zu)\n", attempt+1); - success = true; - break; - } + if (memcmp(buffer, data + p, to_read) != 0) { + printf("verification failed starting at %zu\n", p); + start = p; + break; + } } - if (!success) { - printf("failed to verify after all attempts\n"); - return -1; + if (start == len) { + printf("verification read succeeded (attempt %zu)\n", attempt + 1); + success = true; + break; } + } - if (ota_close(fd) != 0) { - printf("error closing %s (%s)\n", partition, strerror(errno)); - return -1; - } - sync(); + if (!success) { + printf("failed to verify after all attempts\n"); + return -1; + } - return 0; -} + if (ota_close(fd) == -1) { + printf("error closing %s: %s\n", partition, strerror(errno)); + return -1; + } + sync(); + return 0; +} // Take a string 'str' of 40 hex digits and parse it into the 20 // byte array 'digest'. 'str' may contain only the digest or be of @@ -409,48 +381,46 @@ int ParseSha1(const char* str, uint8_t* digest) { // Return the index of the match on success, or -1 if no match is // found. int FindMatchingPatch(uint8_t* sha1, const std::vector<std::string>& patch_sha1_str) { - for (size_t i = 0; i < patch_sha1_str.size(); ++i) { - uint8_t patch_sha1[SHA_DIGEST_LENGTH]; - if (ParseSha1(patch_sha1_str[i].c_str(), patch_sha1) == 0 && - memcmp(patch_sha1, sha1, SHA_DIGEST_LENGTH) == 0) { - return i; - } - } - return -1; + for (size_t i = 0; i < patch_sha1_str.size(); ++i) { + uint8_t patch_sha1[SHA_DIGEST_LENGTH]; + if (ParseSha1(patch_sha1_str[i].c_str(), patch_sha1) == 0 && + memcmp(patch_sha1, sha1, SHA_DIGEST_LENGTH) == 0) { + return i; + } + } + return -1; } // Returns 0 if the contents of the file (argv[2]) or the cached file // match any of the sha1's on the command line (argv[3:]). Returns // nonzero otherwise. int applypatch_check(const char* filename, const std::vector<std::string>& patch_sha1_str) { - FileContents file; - - // It's okay to specify no sha1s; the check will pass if the - // LoadFileContents is successful. (Useful for reading - // partitions, where the filename encodes the sha1s; no need to - // check them twice.) - if (LoadFileContents(filename, &file) != 0 || - (patch_sha1_str.size() > 0 && FindMatchingPatch(file.sha1, patch_sha1_str) < 0)) { - printf("file \"%s\" doesn't have any of expected " - "sha1 sums; checking cache\n", filename); - - // If the source file is missing or corrupted, it might be because - // we were killed in the middle of patching it. A copy of it - // should have been made in CACHE_TEMP_SOURCE. If that file - // exists and matches the sha1 we're looking for, the check still - // passes. - - if (LoadFileContents(CACHE_TEMP_SOURCE, &file) != 0) { - printf("failed to load cache file\n"); - return 1; - } - - if (FindMatchingPatch(file.sha1, patch_sha1_str) < 0) { - printf("cache bits don't match any sha1 for \"%s\"\n", filename); - return 1; - } - } - return 0; + FileContents file; + + // It's okay to specify no sha1s; the check will pass if the + // LoadFileContents is successful. (Useful for reading + // partitions, where the filename encodes the sha1s; no need to + // check them twice.) + if (LoadFileContents(filename, &file) != 0 || + (!patch_sha1_str.empty() && FindMatchingPatch(file.sha1, patch_sha1_str) < 0)) { + printf("file \"%s\" doesn't have any of expected sha1 sums; checking cache\n", filename); + + // If the source file is missing or corrupted, it might be because + // we were killed in the middle of patching it. A copy of it + // should have been made in CACHE_TEMP_SOURCE. If that file + // exists and matches the sha1 we're looking for, the check still + // passes. + if (LoadFileContents(CACHE_TEMP_SOURCE, &file) != 0) { + printf("failed to load cache file\n"); + return 1; + } + + if (FindMatchingPatch(file.sha1, patch_sha1_str) < 0) { + printf("cache bits don't match any sha1 for \"%s\"\n", filename); + return 1; + } + } + return 0; } int ShowLicenses() { @@ -544,10 +514,8 @@ int applypatch(const char* source_filename, return 1; } - FileContents copy_file; FileContents source_file; const Value* source_patch_value = nullptr; - const Value* copy_patch_value = nullptr; // We try to load the target file into the source_file object. if (LoadFileContents(target_filename, &source_file) == 0) { @@ -575,6 +543,8 @@ int applypatch(const char* source_filename, } } + FileContents copy_file; + const Value* copy_patch_value = nullptr; if (source_patch_value == nullptr) { source_file.data.clear(); printf("source file is bad; trying copy\n"); @@ -612,50 +582,49 @@ int applypatch(const char* source_filename, */ int applypatch_flash(const char* source_filename, const char* target_filename, const char* target_sha1_str, size_t target_size) { - printf("flash %s: ", target_filename); - - uint8_t target_sha1[SHA_DIGEST_LENGTH]; - if (ParseSha1(target_sha1_str, target_sha1) != 0) { - printf("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str); - return 1; - } - - FileContents source_file; - std::string target_str(target_filename); - - std::vector<std::string> pieces = android::base::Split(target_str, ":"); - if (pieces.size() != 2 || pieces[0] != "EMMC") { - printf("invalid target name \"%s\"", target_filename); - return 1; - } - - // Load the target into the source_file object to see if already applied. - pieces.push_back(std::to_string(target_size)); - pieces.push_back(target_sha1_str); - std::string fullname = android::base::Join(pieces, ':'); - if (LoadPartitionContents(fullname.c_str(), &source_file) == 0 && - memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) { - // The early-exit case: the image was already applied, this partition - // has the desired hash, nothing for us to do. - printf("already %s\n", short_sha1(target_sha1).c_str()); - return 0; - } - - if (LoadFileContents(source_filename, &source_file) == 0) { - if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) { - // The source doesn't have desired checksum. - printf("source \"%s\" doesn't have expected sha1 sum\n", source_filename); - printf("expected: %s, found: %s\n", short_sha1(target_sha1).c_str(), - short_sha1(source_file.sha1).c_str()); - return 1; - } - } - - if (WriteToPartition(source_file.data.data(), target_size, target_filename) != 0) { - printf("write of copied data to %s failed\n", target_filename); - return 1; - } + printf("flash %s: ", target_filename); + + uint8_t target_sha1[SHA_DIGEST_LENGTH]; + if (ParseSha1(target_sha1_str, target_sha1) != 0) { + printf("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str); + return 1; + } + + std::string target_str(target_filename); + std::vector<std::string> pieces = android::base::Split(target_str, ":"); + if (pieces.size() != 2 || pieces[0] != "EMMC") { + printf("invalid target name \"%s\"", target_filename); + return 1; + } + + // Load the target into the source_file object to see if already applied. + pieces.push_back(std::to_string(target_size)); + pieces.push_back(target_sha1_str); + std::string fullname = android::base::Join(pieces, ':'); + FileContents source_file; + if (LoadPartitionContents(fullname, &source_file) == 0 && + memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) == 0) { + // The early-exit case: the image was already applied, this partition + // has the desired hash, nothing for us to do. + printf("already %s\n", short_sha1(target_sha1).c_str()); return 0; + } + + if (LoadFileContents(source_filename, &source_file) == 0) { + if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) { + // The source doesn't have desired checksum. + printf("source \"%s\" doesn't have expected sha1 sum\n", source_filename); + printf("expected: %s, found: %s\n", short_sha1(target_sha1).c_str(), + short_sha1(source_file.sha1).c_str()); + return 1; + } + } + + if (WriteToPartition(source_file.data.data(), target_size, target_filename) != 0) { + printf("write of copied data to %s failed\n", target_filename); + return 1; + } + return 0; } static int GenerateTarget(FileContents* source_file, @@ -667,221 +636,214 @@ static int GenerateTarget(FileContents* source_file, const uint8_t target_sha1[SHA_DIGEST_LENGTH], size_t target_size, const Value* bonus_data) { - int retry = 1; - SHA_CTX ctx; - std::string memory_sink_str; - FileContents* source_to_use; - int made_copy = 0; - - bool target_is_partition = (strncmp(target_filename, "EMMC:", 5) == 0); - const std::string tmp_target_filename = std::string(target_filename) + ".patch"; - - // assume that target_filename (eg "/system/app/Foo.apk") is located - // on the same filesystem as its top-level directory ("/system"). - // We need something that exists for calling statfs(). - std::string target_fs = target_filename; - auto slash_pos = target_fs.find('/', 1); - if (slash_pos != std::string::npos) { - target_fs.resize(slash_pos); - } - - const Value* patch; - if (source_patch_value != NULL) { - source_to_use = source_file; - patch = source_patch_value; - } else { - source_to_use = copy_file; - patch = copy_patch_value; - } - if (patch->type != VAL_BLOB) { - printf("patch is not a blob\n"); + // assume that target_filename (eg "/system/app/Foo.apk") is located + // on the same filesystem as its top-level directory ("/system"). + // We need something that exists for calling statfs(). + std::string target_fs = target_filename; + auto slash_pos = target_fs.find('/', 1); + if (slash_pos != std::string::npos) { + target_fs.resize(slash_pos); + } + + FileContents* source_to_use; + const Value* patch; + if (source_patch_value != nullptr) { + source_to_use = source_file; + patch = source_patch_value; + } else { + source_to_use = copy_file; + patch = copy_patch_value; + } + + if (patch->type != VAL_BLOB) { + printf("patch is not a blob\n"); + return 1; + } + + const char* header = &patch->data[0]; + size_t header_bytes_read = patch->data.size(); + bool use_bsdiff = false; + if (header_bytes_read >= 8 && memcmp(header, "BSDIFF40", 8) == 0) { + use_bsdiff = true; + } else if (header_bytes_read >= 8 && memcmp(header, "IMGDIFF2", 8) == 0) { + use_bsdiff = false; + } else { + printf("Unknown patch file format\n"); + return 1; + } + + bool target_is_partition = (strncmp(target_filename, "EMMC:", 5) == 0); + const std::string tmp_target_filename = std::string(target_filename) + ".patch"; + + int retry = 1; + bool made_copy = false; + SHA_CTX ctx; + std::string memory_sink_str; // Don't need to reserve space. + do { + // Is there enough room in the target filesystem to hold the patched file? + + if (target_is_partition) { + // If the target is a partition, we're actually going to + // write the output to /tmp and then copy it to the + // partition. statfs() always returns 0 blocks free for + // /tmp, so instead we'll just assume that /tmp has enough + // space to hold the file. + + // We still write the original source to cache, in case + // the partition write is interrupted. + if (MakeFreeSpaceOnCache(source_file->data.size()) < 0) { + printf("not enough free space on /cache\n"); return 1; - } - const char* header = &patch->data[0]; - size_t header_bytes_read = patch->data.size(); - bool use_bsdiff = false; - if (header_bytes_read >= 8 && memcmp(header, "BSDIFF40", 8) == 0) { - use_bsdiff = true; - } else if (header_bytes_read >= 8 && memcmp(header, "IMGDIFF2", 8) == 0) { - use_bsdiff = false; - } else { - printf("Unknown patch file format\n"); + } + if (SaveFileContents(CACHE_TEMP_SOURCE, source_file) < 0) { + printf("failed to back up source file\n"); return 1; - } - - do { - // Is there enough room in the target filesystem to hold the patched - // file? - - if (target_is_partition) { - // If the target is a partition, we're actually going to - // write the output to /tmp and then copy it to the - // partition. statfs() always returns 0 blocks free for - // /tmp, so instead we'll just assume that /tmp has enough - // space to hold the file. - - // We still write the original source to cache, in case - // the partition write is interrupted. - if (MakeFreeSpaceOnCache(source_file->data.size()) < 0) { - printf("not enough free space on /cache\n"); - return 1; - } - if (SaveFileContents(CACHE_TEMP_SOURCE, source_file) < 0) { - printf("failed to back up source file\n"); - return 1; - } - made_copy = 1; - retry = 0; - } else { - int enough_space = 0; - if (retry > 0) { - size_t free_space = FreeSpaceForFile(target_fs.c_str()); - enough_space = - (free_space > (256 << 10)) && // 256k (two-block) minimum - (free_space > (target_size * 3 / 2)); // 50% margin of error - if (!enough_space) { - printf("target %zu bytes; free space %zu bytes; retry %d; enough %d\n", - target_size, free_space, retry, enough_space); - } - } - - if (!enough_space) { - retry = 0; - } - - if (!enough_space && source_patch_value != NULL) { - // Using the original source, but not enough free space. First - // copy the source file to cache, then delete it from the original - // location. - - if (strncmp(source_filename, "EMMC:", 5) == 0) { - // It's impossible to free space on the target filesystem by - // deleting the source if the source is a partition. If - // we're ever in a state where we need to do this, fail. - printf("not enough free space for target but source is partition\n"); - return 1; - } - - if (MakeFreeSpaceOnCache(source_file->data.size()) < 0) { - printf("not enough free space on /cache\n"); - return 1; - } - - if (SaveFileContents(CACHE_TEMP_SOURCE, source_file) < 0) { - printf("failed to back up source file\n"); - return 1; - } - made_copy = 1; - unlink(source_filename); - - size_t free_space = FreeSpaceForFile(target_fs.c_str()); - printf("(now %zu bytes free for target) ", free_space); - } - } - - - SinkFn sink = NULL; - void* token = NULL; - int output_fd = -1; - if (target_is_partition) { - // We store the decoded output in memory. - sink = MemorySink; - token = &memory_sink_str; - } else { - // We write the decoded output to "<tgt-file>.patch". - output_fd = ota_open(tmp_target_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, - S_IRUSR | S_IWUSR); - if (output_fd < 0) { - printf("failed to open output file %s: %s\n", tmp_target_filename.c_str(), - strerror(errno)); - return 1; - } - sink = FileSink; - token = &output_fd; + } + made_copy = true; + retry = 0; + } else { + bool enough_space = false; + if (retry > 0) { + size_t free_space = FreeSpaceForFile(target_fs.c_str()); + enough_space = (free_space > (256 << 10)) && // 256k (two-block) minimum + (free_space > (target_size * 3 / 2)); // 50% margin of error + if (!enough_space) { + printf("target %zu bytes; free space %zu bytes; retry %d; enough %d\n", target_size, + free_space, retry, enough_space); } + } + if (!enough_space) { + retry = 0; + } - SHA1_Init(&ctx); + if (!enough_space && source_patch_value != nullptr) { + // Using the original source, but not enough free space. First + // copy the source file to cache, then delete it from the original + // location. - int result; - if (use_bsdiff) { - result = ApplyBSDiffPatch(source_to_use->data.data(), source_to_use->data.size(), - patch, 0, sink, token, &ctx); - } else { - result = ApplyImagePatch(source_to_use->data.data(), source_to_use->data.size(), - patch, sink, token, &ctx, bonus_data); + if (strncmp(source_filename, "EMMC:", 5) == 0) { + // It's impossible to free space on the target filesystem by + // deleting the source if the source is a partition. If + // we're ever in a state where we need to do this, fail. + printf("not enough free space for target but source is partition\n"); + return 1; } - if (!target_is_partition) { - if (ota_fsync(output_fd) != 0) { - printf("failed to fsync file \"%s\" (%s)\n", tmp_target_filename.c_str(), - strerror(errno)); - result = 1; - } - if (ota_close(output_fd) != 0) { - printf("failed to close file \"%s\" (%s)\n", tmp_target_filename.c_str(), - strerror(errno)); - result = 1; - } + if (MakeFreeSpaceOnCache(source_file->data.size()) < 0) { + printf("not enough free space on /cache\n"); + return 1; } - if (result != 0) { - if (retry == 0) { - printf("applying patch failed\n"); - return result != 0; - } else { - printf("applying patch failed; retrying\n"); - } - if (!target_is_partition) { - unlink(tmp_target_filename.c_str()); - } - } else { - // succeeded; no need to retry - break; + if (SaveFileContents(CACHE_TEMP_SOURCE, source_file) < 0) { + printf("failed to back up source file\n"); + return 1; } - } while (retry-- > 0); + made_copy = true; + unlink(source_filename); - uint8_t current_target_sha1[SHA_DIGEST_LENGTH]; - SHA1_Final(current_target_sha1, &ctx); - if (memcmp(current_target_sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) { - printf("patch did not produce expected sha1\n"); - return 1; - } else { - printf("now %s\n", short_sha1(target_sha1).c_str()); + size_t free_space = FreeSpaceForFile(target_fs.c_str()); + printf("(now %zu bytes free for target) ", free_space); + } } + SinkFn sink = nullptr; + void* token = nullptr; + unique_fd output_fd; if (target_is_partition) { - // Copy the temp file to the partition. - if (WriteToPartition(reinterpret_cast<const unsigned char*>(memory_sink_str.c_str()), - memory_sink_str.size(), target_filename) != 0) { - printf("write of patched data to %s failed\n", target_filename); - return 1; - } + // We store the decoded output in memory. + sink = MemorySink; + token = &memory_sink_str; } else { - // Give the .patch file the same owner, group, and mode of the - // original source file. - if (chmod(tmp_target_filename.c_str(), source_to_use->st.st_mode) != 0) { - printf("chmod of \"%s\" failed: %s\n", tmp_target_filename.c_str(), strerror(errno)); - return 1; - } - if (chown(tmp_target_filename.c_str(), source_to_use->st.st_uid, source_to_use->st.st_gid) != 0) { - printf("chown of \"%s\" failed: %s\n", tmp_target_filename.c_str(), strerror(errno)); - return 1; - } + // We write the decoded output to "<tgt-file>.patch". + output_fd.reset(ota_open(tmp_target_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, + S_IRUSR | S_IWUSR)); + if (output_fd == -1) { + printf("failed to open output file %s: %s\n", tmp_target_filename.c_str(), strerror(errno)); + return 1; + } + sink = FileSink; + token = &output_fd; + } - // Finally, rename the .patch file to replace the target file. - if (rename(tmp_target_filename.c_str(), target_filename) != 0) { - printf("rename of .patch to \"%s\" failed: %s\n", target_filename, strerror(errno)); - return 1; - } + SHA1_Init(&ctx); + + int result; + if (use_bsdiff) { + result = ApplyBSDiffPatch(source_to_use->data.data(), source_to_use->data.size(), patch, 0, + sink, token, &ctx); + } else { + result = ApplyImagePatch(source_to_use->data.data(), source_to_use->data.size(), patch, sink, + token, &ctx, bonus_data); } - // If this run of applypatch created the copy, and we're here, we - // can delete it. - if (made_copy) { - unlink(CACHE_TEMP_SOURCE); + if (!target_is_partition) { + if (ota_fsync(output_fd) != 0) { + printf("failed to fsync file \"%s\": %s\n", tmp_target_filename.c_str(), strerror(errno)); + result = 1; + } + if (ota_close(output_fd) != 0) { + printf("failed to close file \"%s\": %s\n", tmp_target_filename.c_str(), strerror(errno)); + result = 1; + } } - // Success! - return 0; + if (result != 0) { + if (retry == 0) { + printf("applying patch failed\n"); + return 1; + } else { + printf("applying patch failed; retrying\n"); + } + if (!target_is_partition) { + unlink(tmp_target_filename.c_str()); + } + } else { + // succeeded; no need to retry + break; + } + } while (retry-- > 0); + + uint8_t current_target_sha1[SHA_DIGEST_LENGTH]; + SHA1_Final(current_target_sha1, &ctx); + if (memcmp(current_target_sha1, target_sha1, SHA_DIGEST_LENGTH) != 0) { + printf("patch did not produce expected sha1\n"); + return 1; + } else { + printf("now %s\n", short_sha1(target_sha1).c_str()); + } + + if (target_is_partition) { + // Copy the temp file to the partition. + if (WriteToPartition(reinterpret_cast<const unsigned char*>(memory_sink_str.c_str()), + memory_sink_str.size(), target_filename) != 0) { + printf("write of patched data to %s failed\n", target_filename); + return 1; + } + } else { + // Give the .patch file the same owner, group, and mode of the original source file. + if (chmod(tmp_target_filename.c_str(), source_to_use->st.st_mode) != 0) { + printf("chmod of \"%s\" failed: %s\n", tmp_target_filename.c_str(), strerror(errno)); + return 1; + } + if (chown(tmp_target_filename.c_str(), source_to_use->st.st_uid, + source_to_use->st.st_gid) != 0) { + printf("chown of \"%s\" failed: %s\n", tmp_target_filename.c_str(), strerror(errno)); + return 1; + } + + // Finally, rename the .patch file to replace the target file. + if (rename(tmp_target_filename.c_str(), target_filename) != 0) { + printf("rename of .patch to \"%s\" failed: %s\n", target_filename, strerror(errno)); + return 1; + } + } + + // If this run of applypatch created the copy, and we're here, we can delete it. + if (made_copy) { + unlink(CACHE_TEMP_SOURCE); + } + + // Success! + return 0; } diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp index 16e8df14f..9a5671843 100644 --- a/bootloader_message/bootloader_message.cpp +++ b/bootloader_message/bootloader_message.cpp @@ -80,26 +80,23 @@ static bool wait_for_device(const std::string& blk_device, std::string* err) { return ret == 0; } -static bool read_misc_partition(void* p, size_t size, size_t offset, std::string* err) { - std::string misc_blk_device = get_misc_blk_device(err); - if (misc_blk_device.empty()) { - return false; - } +static bool read_misc_partition(void* p, size_t size, const std::string& misc_blk_device, + size_t offset, std::string* err) { if (!wait_for_device(misc_blk_device, err)) { return false; } android::base::unique_fd fd(open(misc_blk_device.c_str(), O_RDONLY)); - if (fd.get() == -1) { + if (fd == -1) { *err = android::base::StringPrintf("failed to open %s: %s", misc_blk_device.c_str(), strerror(errno)); return false; } - if (lseek(fd.get(), static_cast<off_t>(offset), SEEK_SET) != static_cast<off_t>(offset)) { + if (lseek(fd, static_cast<off_t>(offset), SEEK_SET) != static_cast<off_t>(offset)) { *err = android::base::StringPrintf("failed to lseek %s: %s", misc_blk_device.c_str(), strerror(errno)); return false; } - if (!android::base::ReadFully(fd.get(), p, size)) { + if (!android::base::ReadFully(fd, p, size)) { *err = android::base::StringPrintf("failed to read %s: %s", misc_blk_device.c_str(), strerror(errno)); return false; @@ -107,30 +104,25 @@ static bool read_misc_partition(void* p, size_t size, size_t offset, std::string return true; } -static bool write_misc_partition(const void* p, size_t size, size_t offset, std::string* err) { - std::string misc_blk_device = get_misc_blk_device(err); - if (misc_blk_device.empty()) { - return false; - } - android::base::unique_fd fd(open(misc_blk_device.c_str(), O_WRONLY | O_SYNC)); - if (fd.get() == -1) { +static bool write_misc_partition(const void* p, size_t size, const std::string& misc_blk_device, + size_t offset, std::string* err) { + android::base::unique_fd fd(open(misc_blk_device.c_str(), O_WRONLY)); + if (fd == -1) { *err = android::base::StringPrintf("failed to open %s: %s", misc_blk_device.c_str(), strerror(errno)); return false; } - if (lseek(fd.get(), static_cast<off_t>(offset), SEEK_SET) != static_cast<off_t>(offset)) { + if (lseek(fd, static_cast<off_t>(offset), SEEK_SET) != static_cast<off_t>(offset)) { *err = android::base::StringPrintf("failed to lseek %s: %s", misc_blk_device.c_str(), strerror(errno)); return false; } - if (!android::base::WriteFully(fd.get(), p, size)) { + if (!android::base::WriteFully(fd, p, size)) { *err = android::base::StringPrintf("failed to write %s: %s", misc_blk_device.c_str(), strerror(errno)); return false; } - - // TODO: O_SYNC and fsync duplicates each other? - if (fsync(fd.get()) == -1) { + if (fsync(fd) == -1) { *err = android::base::StringPrintf("failed to fsync %s: %s", misc_blk_device.c_str(), strerror(errno)); return false; @@ -138,12 +130,32 @@ static bool write_misc_partition(const void* p, size_t size, size_t offset, std: return true; } +bool read_bootloader_message_from(bootloader_message* boot, const std::string& misc_blk_device, + std::string* err) { + return read_misc_partition(boot, sizeof(*boot), misc_blk_device, + BOOTLOADER_MESSAGE_OFFSET_IN_MISC, err); +} + bool read_bootloader_message(bootloader_message* boot, std::string* err) { - return read_misc_partition(boot, sizeof(*boot), BOOTLOADER_MESSAGE_OFFSET_IN_MISC, err); + std::string misc_blk_device = get_misc_blk_device(err); + if (misc_blk_device.empty()) { + return false; + } + return read_bootloader_message_from(boot, misc_blk_device, err); +} + +bool write_bootloader_message_to(const bootloader_message& boot, const std::string& misc_blk_device, + std::string* err) { + return write_misc_partition(&boot, sizeof(boot), misc_blk_device, + BOOTLOADER_MESSAGE_OFFSET_IN_MISC, err); } bool write_bootloader_message(const bootloader_message& boot, std::string* err) { - return write_misc_partition(&boot, sizeof(boot), BOOTLOADER_MESSAGE_OFFSET_IN_MISC, err); + std::string misc_blk_device = get_misc_blk_device(err); + if (misc_blk_device.empty()) { + return false; + } + return write_bootloader_message_to(boot, misc_blk_device, err); } bool clear_bootloader_message(std::string* err) { @@ -178,12 +190,21 @@ bool write_reboot_bootloader(std::string* err) { } bool read_wipe_package(std::string* package_data, size_t size, std::string* err) { + std::string misc_blk_device = get_misc_blk_device(err); + if (misc_blk_device.empty()) { + return false; + } package_data->resize(size); - return read_misc_partition(&(*package_data)[0], size, WIPE_PACKAGE_OFFSET_IN_MISC, err); + return read_misc_partition(&(*package_data)[0], size, misc_blk_device, + WIPE_PACKAGE_OFFSET_IN_MISC, err); } bool write_wipe_package(const std::string& package_data, std::string* err) { - return write_misc_partition(package_data.data(), package_data.size(), + std::string misc_blk_device = get_misc_blk_device(err); + if (misc_blk_device.empty()) { + return false; + } + return write_misc_partition(package_data.data(), package_data.size(), misc_blk_device, WIPE_PACKAGE_OFFSET_IN_MISC, err); } diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h index 75d2acaf2..b3d2182df 100644 --- a/bootloader_message/include/bootloader_message/bootloader_message.h +++ b/bootloader_message/include/bootloader_message/bootloader_message.h @@ -179,15 +179,33 @@ static_assert(sizeof(struct bootloader_control) == #include <string> #include <vector> +// Read bootloader message into boot. Error message will be set in err. bool read_bootloader_message(bootloader_message* boot, std::string* err); + +// Read bootloader message from the specified misc device into boot. +bool read_bootloader_message_from(bootloader_message* boot, const std::string& misc_blk_device, + std::string* err); + +// Write bootloader message to BCB. bool write_bootloader_message(const bootloader_message& boot, std::string* err); + +// Write bootloader message to the specified BCB device. +bool write_bootloader_message_to(const bootloader_message& boot, + const std::string& misc_blk_device, std::string* err); + +// Write bootloader message (boots into recovery with the options) to BCB. bool write_bootloader_message(const std::vector<std::string>& options, std::string* err); + +// Clear BCB. bool clear_bootloader_message(std::string* err); // Writes the reboot-bootloader reboot reason to the bootloader_message. bool write_reboot_bootloader(std::string* err); +// Read the wipe package from BCB (from offset WIPE_PACKAGE_OFFSET_IN_MISC). bool read_wipe_package(std::string* package_data, size_t size, std::string* err); + +// Write the wipe package into BCB (to offset WIPE_PACKAGE_OFFSET_IN_MISC). bool write_wipe_package(const std::string& package_data, std::string* err); #else diff --git a/otafault/ota_io.h b/otafault/ota_io.h index 84187a76e..e119eef08 100644 --- a/otafault/ota_io.h +++ b/otafault/ota_io.h @@ -26,6 +26,8 @@ #include <stdio.h> #include <sys/stat.h> +#include <android-base/unique_fd.h> + #define OTAIO_CACHE_FNAME "/cache/saved.file" void ota_set_fault_files(); @@ -50,4 +52,12 @@ ssize_t ota_write(int fd, const void* buf, size_t nbyte); int ota_fsync(int fd); +struct OtaCloser { + static void Close(int fd) { + ota_close(fd); + } +}; + +using unique_fd = android::base::unique_fd_impl<OtaCloser>; + #endif diff --git a/tests/Android.mk b/tests/Android.mk index fdc947028..5f6a7ce0c 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -80,10 +80,12 @@ LOCAL_STATIC_LIBRARIES := \ libedify \ libotafault \ libupdater \ + libbootloader_message \ libverifier \ libminui \ libotautil \ libmounts \ + libfs_mgr \ liblog \ libselinux \ libext4_utils_static \ diff --git a/tests/common/test_constants.h b/tests/common/test_constants.h index 93e4ab5b2..f6b6922a4 100644 --- a/tests/common/test_constants.h +++ b/tests/common/test_constants.h @@ -22,6 +22,8 @@ // Zip entries in ziptest_valid.zip. static const std::string kATxtContents("abcdefghabcdefgh\n"); static const std::string kBTxtContents("abcdefgh\n"); +static const std::string kCTxtContents("abcdefghabcdefgh\n"); +static const std::string kDTxtContents("abcdefgh\n"); // echo -n -e "abcdefghabcdefgh\n" | sha1sum static const std::string kATxtSha1Sum("32c96a03dc8cd20097940f351bca6261ee5a1643"); diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp index a029cf449..f31f1f82a 100644 --- a/tests/component/updater_test.cpp +++ b/tests/component/updater_test.cpp @@ -23,6 +23,7 @@ #include <android-base/file.h> #include <android-base/properties.h> #include <android-base/test_utils.h> +#include <bootloader_message/bootloader_message.h> #include <gtest/gtest.h> #include <ziparchive/zip_archive.h> @@ -270,6 +271,102 @@ TEST_F(UpdaterTest, symlink) { ASSERT_EQ(0, unlink(src2.c_str())); } +TEST_F(UpdaterTest, package_extract_dir) { + // package_extract_dir expects 2 arguments. + expect(nullptr, "package_extract_dir()", kArgsParsingFailure); + expect(nullptr, "package_extract_dir(\"arg1\")", kArgsParsingFailure); + expect(nullptr, "package_extract_dir(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); + + std::string zip_path = from_testdata_base("ziptest_valid.zip"); + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle)); + + // Need to set up the ziphandle. + UpdaterInfo updater_info; + updater_info.package_zip = handle; + + // Extract "b/c.txt" and "b/d.txt" with package_extract_dir("b", "<dir>"). + TemporaryDir td; + std::string temp_dir(td.path); + std::string script("package_extract_dir(\"b\", \"" + temp_dir + "\")"); + expect("t", script.c_str(), kNoCause, &updater_info); + + // Verify. + std::string data; + std::string file_c = temp_dir + "/c.txt"; + ASSERT_TRUE(android::base::ReadFileToString(file_c, &data)); + ASSERT_EQ(kCTxtContents, data); + + std::string file_d = temp_dir + "/d.txt"; + ASSERT_TRUE(android::base::ReadFileToString(file_d, &data)); + ASSERT_EQ(kDTxtContents, data); + + // Modify the contents in order to retry. It's expected to be overwritten. + ASSERT_TRUE(android::base::WriteStringToFile("random", file_c)); + ASSERT_TRUE(android::base::WriteStringToFile("random", file_d)); + + // Extract again and verify. + expect("t", script.c_str(), kNoCause, &updater_info); + + ASSERT_TRUE(android::base::ReadFileToString(file_c, &data)); + ASSERT_EQ(kCTxtContents, data); + ASSERT_TRUE(android::base::ReadFileToString(file_d, &data)); + ASSERT_EQ(kDTxtContents, data); + + // Clean up the temp files under td. + ASSERT_EQ(0, unlink(file_c.c_str())); + ASSERT_EQ(0, unlink(file_d.c_str())); + + // Extracting "b/" (with slash) should give the same result. + script = "package_extract_dir(\"b/\", \"" + temp_dir + "\")"; + expect("t", script.c_str(), kNoCause, &updater_info); + + ASSERT_TRUE(android::base::ReadFileToString(file_c, &data)); + ASSERT_EQ(kCTxtContents, data); + ASSERT_TRUE(android::base::ReadFileToString(file_d, &data)); + ASSERT_EQ(kDTxtContents, data); + + ASSERT_EQ(0, unlink(file_c.c_str())); + ASSERT_EQ(0, unlink(file_d.c_str())); + + // Extracting "" is allowed. The entries will carry the path name. + script = "package_extract_dir(\"\", \"" + temp_dir + "\")"; + expect("t", script.c_str(), kNoCause, &updater_info); + + std::string file_a = temp_dir + "/a.txt"; + ASSERT_TRUE(android::base::ReadFileToString(file_a, &data)); + ASSERT_EQ(kATxtContents, data); + std::string file_b = temp_dir + "/b.txt"; + ASSERT_TRUE(android::base::ReadFileToString(file_b, &data)); + ASSERT_EQ(kBTxtContents, data); + std::string file_b_c = temp_dir + "/b/c.txt"; + ASSERT_TRUE(android::base::ReadFileToString(file_b_c, &data)); + ASSERT_EQ(kCTxtContents, data); + std::string file_b_d = temp_dir + "/b/d.txt"; + ASSERT_TRUE(android::base::ReadFileToString(file_b_d, &data)); + ASSERT_EQ(kDTxtContents, data); + + ASSERT_EQ(0, unlink(file_a.c_str())); + ASSERT_EQ(0, unlink(file_b.c_str())); + ASSERT_EQ(0, unlink(file_b_c.c_str())); + ASSERT_EQ(0, unlink(file_b_d.c_str())); + ASSERT_EQ(0, rmdir((temp_dir + "/b").c_str())); + + // Extracting non-existent entry should still give "t". + script = "package_extract_dir(\"doesntexist\", \"" + temp_dir + "\")"; + expect("t", script.c_str(), kNoCause, &updater_info); + + // Only relative zip_path is allowed. + script = "package_extract_dir(\"/b\", \"" + temp_dir + "\")"; + expect("", script.c_str(), kNoCause, &updater_info); + + // Only absolute dest_path is allowed. + script = "package_extract_dir(\"b\", \"path\")"; + expect("", script.c_str(), kNoCause, &updater_info); + + CloseArchive(handle); +} + // TODO: Test extracting to block device. TEST_F(UpdaterTest, package_extract_file) { // package_extract_file expects 1 or 2 arguments. @@ -322,3 +419,94 @@ TEST_F(UpdaterTest, package_extract_file) { CloseArchive(handle); } + +TEST_F(UpdaterTest, write_value) { + // write_value() expects two arguments. + expect(nullptr, "write_value()", kArgsParsingFailure); + expect(nullptr, "write_value(\"arg1\")", kArgsParsingFailure); + expect(nullptr, "write_value(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); + + // filename cannot be empty. + expect(nullptr, "write_value(\"value\", \"\")", kArgsParsingFailure); + + // Write some value to file. + TemporaryFile temp_file; + std::string value = "magicvalue"; + std::string script("write_value(\"" + value + "\", \"" + std::string(temp_file.path) + "\")"); + expect("t", script.c_str(), kNoCause); + + // Verify the content. + std::string content; + ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content)); + ASSERT_EQ(value, content); + + // Allow writing empty string. + script = "write_value(\"\", \"" + std::string(temp_file.path) + "\")"; + expect("t", script.c_str(), kNoCause); + + // Verify the content. + ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content)); + ASSERT_EQ("", content); + + // It should fail gracefully when write fails. + script = "write_value(\"value\", \"/proc/0/file1\")"; + expect("", script.c_str(), kNoCause); +} + +TEST_F(UpdaterTest, get_stage) { + // get_stage() expects one argument. + expect(nullptr, "get_stage()", kArgsParsingFailure); + expect(nullptr, "get_stage(\"arg1\", \"arg2\")", kArgsParsingFailure); + expect(nullptr, "get_stage(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); + + // Set up a local file as BCB. + TemporaryFile tf; + std::string temp_file(tf.path); + bootloader_message boot; + strlcpy(boot.stage, "2/3", sizeof(boot.stage)); + std::string err; + ASSERT_TRUE(write_bootloader_message_to(boot, temp_file, &err)); + + // Can read the stage value. + std::string script("get_stage(\"" + temp_file + "\")"); + expect("2/3", script.c_str(), kNoCause); + + // Bad BCB path. + script = "get_stage(\"doesntexist\")"; + expect("", script.c_str(), kNoCause); +} + +TEST_F(UpdaterTest, set_stage) { + // set_stage() expects two arguments. + expect(nullptr, "set_stage()", kArgsParsingFailure); + expect(nullptr, "set_stage(\"arg1\")", kArgsParsingFailure); + expect(nullptr, "set_stage(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); + + // Set up a local file as BCB. + TemporaryFile tf; + std::string temp_file(tf.path); + bootloader_message boot; + strlcpy(boot.command, "command", sizeof(boot.command)); + strlcpy(boot.stage, "2/3", sizeof(boot.stage)); + std::string err; + ASSERT_TRUE(write_bootloader_message_to(boot, temp_file, &err)); + + // Write with set_stage(). + std::string script("set_stage(\"" + temp_file + "\", \"1/3\")"); + expect(tf.path, script.c_str(), kNoCause); + + // Verify. + bootloader_message boot_verify; + ASSERT_TRUE(read_bootloader_message_from(&boot_verify, temp_file, &err)); + + // Stage should be updated, with command part untouched. + ASSERT_STREQ("1/3", boot_verify.stage); + ASSERT_STREQ(boot.command, boot_verify.command); + + // Bad BCB path. + script = "set_stage(\"doesntexist\", \"1/3\")"; + expect("", script.c_str(), kNoCause); + + script = "set_stage(\"/dev/full\", \"1/3\")"; + expect("", script.c_str(), kNoCause); +} diff --git a/update_verifier/Android.mk b/update_verifier/Android.mk index c822999f3..8449c758d 100644 --- a/update_verifier/Android.mk +++ b/update_verifier/Android.mk @@ -26,7 +26,7 @@ LOCAL_SHARED_LIBRARIES := \ libhardware \ liblog -LOCAL_C_INCLUDES += $(LOCAL_PATH)/.. LOCAL_CFLAGS := -Werror +LOCAL_C_INCLUDES += $(LOCAL_PATH)/.. include $(BUILD_EXECUTABLE) diff --git a/updater/Android.mk b/updater/Android.mk index 3c1d0d41f..5d328a3fa 100644 --- a/updater/Android.mk +++ b/updater/Android.mk @@ -27,12 +27,14 @@ updater_common_static_libraries := \ libedify \ libziparchive \ libotautil \ + libbootloader_message \ libutils \ libmounts \ libotafault \ libext4_utils_static \ libfec \ libfec_rs \ + libfs_mgr \ liblog \ libselinux \ libsparse_static \ diff --git a/updater/install.cpp b/updater/install.cpp index b885f864e..b9bc19e2c 100644 --- a/updater/install.cpp +++ b/updater/install.cpp @@ -39,11 +39,12 @@ #include <string> #include <vector> -#include <android-base/parseint.h> +#include <android-base/file.h> #include <android-base/parsedouble.h> +#include <android-base/parseint.h> #include <android-base/properties.h> -#include <android-base/strings.h> #include <android-base/stringprintf.h> +#include <android-base/strings.h> #include <cutils/android_reboot.h> #include <ext4_utils/make_ext4fs.h> #include <ext4_utils/wipe.h> @@ -453,28 +454,32 @@ Value* SetProgressFn(const char* name, State* state, int argc, Expr* argv[]) { return StringValue(frac_str); } -// package_extract_dir(package_path, destination_path) -Value* PackageExtractDirFn(const char* name, State* state, - int argc, Expr* argv[]) { - if (argc != 2) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc); - } +// package_extract_dir(package_dir, dest_dir) +// Extracts all files from the package underneath package_dir and writes them to the +// corresponding tree beneath dest_dir. Any existing files are overwritten. +// Example: package_extract_dir("system", "/system") +// +// Note: package_dir needs to be a relative path; dest_dir needs to be an absolute path. +Value* PackageExtractDirFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 2) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc); + } - std::vector<std::string> args; - if (!ReadArgs(state, 2, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); - } - const std::string& zip_path = args[0]; - const std::string& dest_path = args[1]; + std::vector<std::string> args; + if (!ReadArgs(state, 2, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + const std::string& zip_path = args[0]; + const std::string& dest_path = args[1]; - ZipArchiveHandle za = ((UpdaterInfo*)(state->cookie))->package_zip; + ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip; - // To create a consistent system image, never use the clock for timestamps. - struct utimbuf timestamp = { 1217592000, 1217592000 }; // 8/1/2008 default + // To create a consistent system image, never use the clock for timestamps. + constexpr struct utimbuf timestamp = { 1217592000, 1217592000 }; // 8/1/2008 default - bool success = ExtractPackageRecursive(za, zip_path, dest_path, ×tamp, sehandle); + bool success = ExtractPackageRecursive(za, zip_path, dest_path, ×tamp, sehandle); - return StringValue(success ? "t" : ""); + return StringValue(success ? "t" : ""); } // package_extract_file(package_file[, dest_file]) @@ -861,7 +866,6 @@ Value* GetPropFn(const char* name, State* state, int argc, Expr* argv[]) { return StringValue(value); } - // file_getprop(file, key) // // interprets 'file' as a getprop-style file (key=value pairs, one @@ -1155,6 +1159,33 @@ Value* ReadFileFn(const char* name, State* state, int argc, Expr* argv[]) { return v; } +// write_value(value, filename) +// Writes 'value' to 'filename'. +// Example: write_value("960000", "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq") +Value* WriteValueFn(const char* name, State* state, int argc, Expr* argv[]) { + if (argc != 2) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc); + } + + std::vector<std::string> args; + if (!ReadArgs(state, 2, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name); + } + + const std::string& filename = args[1]; + if (filename.empty()) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): Filename cannot be empty", name); + } + + const std::string& value = args[0]; + if (!android::base::WriteStringToFile(value, filename)) { + printf("%s: Failed to write to \"%s\": %s\n", name, filename.c_str(), strerror(errno)); + return StringValue(""); + } else { + return StringValue("t"); + } +} + // Immediately reboot the device. Recovery is not finished normally, // so if you reboot into recovery it will re-start applying the // current package (because nothing has cleared the copy of the @@ -1165,31 +1196,35 @@ Value* ReadFileFn(const char* name, State* state, int argc, Expr* argv[]) { // partition, or "" (empty string) to boot from the regular boot // partition. Value* RebootNowFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 2) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc); - } + if (argc != 2) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc); + } - std::vector<std::string> args; - if (!ReadArgs(state, 2, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); - } - const std::string& filename = args[0]; - const std::string& property = args[1]; - - // zero out the 'command' field of the bootloader message. - char buffer[80]; - memset(buffer, 0, sizeof(((struct bootloader_message*)0)->command)); - FILE* f = ota_fopen(filename.c_str(), "r+b"); - fseek(f, offsetof(struct bootloader_message, command), SEEK_SET); - ota_fwrite(buffer, sizeof(((struct bootloader_message*)0)->command), 1, f); - ota_fclose(f); + std::vector<std::string> args; + if (!ReadArgs(state, 2, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name); + } + const std::string& filename = args[0]; + const std::string& property = args[1]; + + // Zero out the 'command' field of the bootloader message. Leave the rest intact. + bootloader_message boot; + std::string err; + if (!read_bootloader_message_from(&boot, filename, &err)) { + printf("%s(): Failed to read from \"%s\": %s", name, filename.c_str(), err.c_str()); + return StringValue(""); + } + memset(boot.command, 0, sizeof(boot.command)); + if (!write_bootloader_message_to(boot, filename, &err)) { + printf("%s(): Failed to write to \"%s\": %s", name, filename.c_str(), err.c_str()); + return StringValue(""); + } - std::string reboot_cmd = "reboot,"; - reboot_cmd += property; - android::base::SetProperty(ANDROID_RB_PROPERTY, reboot_cmd); + const std::string reboot_cmd = "reboot," + property; + android::base::SetProperty(ANDROID_RB_PROPERTY, reboot_cmd); - sleep(5); - return ErrorAbort(state, kRebootFailure, "%s() failed to reboot", name); + sleep(5); + return ErrorAbort(state, kRebootFailure, "%s() failed to reboot", name); } // Store a string value somewhere that future invocations of recovery @@ -1203,62 +1238,57 @@ Value* RebootNowFn(const char* name, State* state, int argc, Expr* argv[]) { // second argument is the string to store; it should not exceed 31 // bytes. Value* SetStageFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 2) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc); - } + if (argc != 2) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %d", name, argc); + } - std::vector<std::string> args; - if (!ReadArgs(state, 2, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); - } - const std::string& filename = args[0]; - std::string& stagestr = args[1]; - - // Store this value in the misc partition, immediately after the - // bootloader message that the main recovery uses to save its - // arguments in case of the device restarting midway through - // package installation. - FILE* f = ota_fopen(filename.c_str(), "r+b"); - fseek(f, offsetof(struct bootloader_message, stage), SEEK_SET); - size_t to_write = stagestr.size(); - size_t max_size = sizeof(((struct bootloader_message*)0)->stage); - if (to_write > max_size) { - to_write = max_size; - stagestr = stagestr.substr(0, max_size-1); - } - size_t status = ota_fwrite(stagestr.c_str(), to_write, 1, f); - ota_fclose(f); + std::vector<std::string> args; + if (!ReadArgs(state, 2, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + const std::string& filename = args[0]; + const std::string& stagestr = args[1]; + + // Store this value in the misc partition, immediately after the + // bootloader message that the main recovery uses to save its + // arguments in case of the device restarting midway through + // package installation. + bootloader_message boot; + std::string err; + if (!read_bootloader_message_from(&boot, filename, &err)) { + printf("%s(): Failed to read from \"%s\": %s", name, filename.c_str(), err.c_str()); + return StringValue(""); + } + strlcpy(boot.stage, stagestr.c_str(), sizeof(boot.stage)); + if (!write_bootloader_message_to(boot, filename, &err)) { + printf("%s(): Failed to write to \"%s\": %s", name, filename.c_str(), err.c_str()); + return StringValue(""); + } - if (status != to_write) { - return StringValue(""); - } - return StringValue(filename); + return StringValue(filename); } // Return the value most recently saved with SetStageFn. The argument // is the block device for the misc partition. Value* GetStageFn(const char* name, State* state, int argc, Expr* argv[]) { - if (argc != 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %d", name, argc); - } + if (argc != 1) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %d", name, argc); + } - std::vector<std::string> args; - if (!ReadArgs(state, 1, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); - } - const std::string& filename = args[0]; + std::vector<std::string> args; + if (!ReadArgs(state, 1, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + const std::string& filename = args[0]; - char buffer[sizeof(((struct bootloader_message*)0)->stage)]; - FILE* f = ota_fopen(filename.c_str(), "rb"); - fseek(f, offsetof(struct bootloader_message, stage), SEEK_SET); - size_t status = ota_fread(buffer, sizeof(buffer), 1, f); - ota_fclose(f); - if (status != sizeof(buffer)) { - return StringValue(""); - } + bootloader_message boot; + std::string err; + if (!read_bootloader_message_from(&boot, filename, &err)) { + printf("%s(): Failed to read from \"%s\": %s", name, filename.c_str(), err.c_str()); + return StringValue(""); + } - buffer[sizeof(buffer)-1] = '\0'; - return StringValue(buffer); + return StringValue(boot.stage); } Value* WipeBlockDeviceFn(const char* name, State* state, int argc, Expr* argv[]) { @@ -1363,6 +1393,7 @@ void RegisterInstallFunctions() { RegisterFunction("read_file", ReadFileFn); RegisterFunction("sha1_check", Sha1CheckFn); RegisterFunction("rename", RenameFn); + RegisterFunction("write_value", WriteValueFn); RegisterFunction("wipe_cache", WipeCacheFn); |