From 5da317e51d1832cb1ec67dd20fbcff7708bbadb5 Mon Sep 17 00:00:00 2001 From: Doug Zongker Date: Tue, 2 Jun 2009 13:38:17 -0700 Subject: [PATCH] support incremental updates of boot image Modify applypatch to be able to write MTD partitions as well as read them. Make applypatch save a backup copy of the contents of an MTD partition it reads in cache, to be used in case an update is interrupted while writing back to MTD. Modify OTA package creation script to send boot image updates in patch form. --- tools/applypatch/applypatch.c | 231 +++++++++++++++++------ tools/applypatch/applypatch.h | 5 + tools/releasetools/ota_from_target_files | 64 ++++--- 3 files changed, 220 insertions(+), 80 deletions(-) diff --git a/tools/applypatch/applypatch.c b/tools/applypatch/applypatch.c index 655953354b..db4c30b82c 100644 --- a/tools/applypatch/applypatch.c +++ b/tools/applypatch/applypatch.c @@ -27,9 +27,12 @@ #include "applypatch.h" #include "mtdutils/mtdutils.h" +int SaveFileContents(const char* filename, FileContents file); int LoadMTDContents(const char* filename, FileContents* file); int ParseSha1(const char* str, uint8_t* digest); +static int mtd_partitions_scanned = 0; + // Read a file into memory; store it and its associated metadata in // *file. Return 0 on success. int LoadFileContents(const char* filename, FileContents* file) { @@ -139,15 +142,14 @@ int LoadMTDContents(const char* filename, FileContents* file) { index[i] = i; } - // sort the index[] array so it indexs the pairs in order of + // sort the index[] array so it indexes the pairs in order of // increasing size. size_array = size; qsort(index, pairs, sizeof(int), compare_size_indices); - static int partitions_scanned = 0; - if (!partitions_scanned) { + if (!mtd_partitions_scanned) { mtd_scan_partitions(); - partitions_scanned = 1; + mtd_partitions_scanned = 1; } const MtdPartition* mtd = mtd_find_partition_by_name(partition); @@ -234,6 +236,11 @@ int LoadMTDContents(const char* filename, FileContents* file) { file->sha1[i] = sha_final[i]; } + // Fake some stat() info. + file->st.st_mode = 0644; + file->st.st_uid = 0; + file->st.st_gid = 0; + free(copy); free(index); free(size); @@ -275,6 +282,76 @@ int SaveFileContents(const char* filename, FileContents file) { return 0; } +// Copy the contents of source_file to target_mtd partition, a string +// of the form "MTD:[:...]". Return 0 on success. +int CopyToMTDPartition(const char* source_file, const char* target_mtd) { + char* partition = strchr(target_mtd, ':'); + if (partition == NULL) { + fprintf(stderr, "bad MTD target name \"%s\"\n", target_mtd); + return -1; + } + ++partition; + // Trim off anything after a colon, eg "MTD:boot:blah:blah:blah...". + // We want just the partition name "boot". + partition = strdup(partition); + char* end = strchr(partition, ':'); + if (end != NULL) + *end = '\0'; + + FILE* f = fopen(source_file, "rb"); + if (f == NULL) { + fprintf(stderr, "failed to open %s for reading: %s\n", + source_file, strerror(errno)); + return -1; + } + + if (!mtd_partitions_scanned) { + mtd_scan_partitions(); + mtd_partitions_scanned = 1; + } + + const MtdPartition* mtd = mtd_find_partition_by_name(partition); + if (mtd == NULL) { + fprintf(stderr, "mtd partition \"%s\" not found for writing\n", partition); + return -1; + } + + MtdWriteContext* ctx = mtd_write_partition(mtd); + if (ctx == NULL) { + fprintf(stderr, "failed to init mtd partition \"%s\" for writing\n", + partition); + return -1; + } + + const int buffer_size = 4096; + char buffer[buffer_size]; + size_t read; + while ((read = fread(buffer, 1, buffer_size, f)) > 0) { + size_t written = mtd_write_data(ctx, buffer, read); + if (written != read) { + fprintf(stderr, "only wrote %d of %d bytes to MTD %s\n", + written, read, partition); + mtd_write_close(ctx); + return -1; + } + } + + fclose(f); + if (mtd_erase_blocks(ctx, -1) < 0) { + fprintf(stderr, "error finishing mtd write of %s\n", partition); + mtd_write_close(ctx); + return -1; + } + + if (mtd_write_close(ctx)) { + fprintf(stderr, "error closing mtd write of %s\n", partition); + return -1; + } + + free(partition); + 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 @@ -443,9 +520,10 @@ int main(int argc, char** argv) { " or %s -s \n" " or %s -l\n" "\n" - " or may be of the form\n" - " MTD::::::...\n" - "to specify reading from an MTD partition.\n\n", + "Filenames may be of the form\n" + " MTD:::::" + ":...:\n" + "to specify reading from or writing to an MTD partition.\n\n", argv[0], argv[0], argv[0], argv[0]); return 1; } @@ -480,16 +558,7 @@ int main(int argc, char** argv) { target_filename = source_filename; } - // 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(). - char* target_fs = strdup(target_filename); - char* slash = strchr(target_fs+1, '/'); - if (slash != NULL) { - *slash = '\0'; - } - - if (ParseSha1(argv[3], target_sha1) != 0) { + if (ParseSha1(argv[3], target_sha1) != 0) { fprintf(stderr, "failed to parse tgt-sha1 \"%s\"\n", argv[3]); return 1; } @@ -557,39 +626,70 @@ int main(int argc, char** argv) { } } - // Is there enough room in the target filesystem to hold the patched file? - size_t free_space = FreeSpaceForFile(target_fs); - int enough_space = free_space > (target_size * 3 / 2); // 50% margin of error - printf("target %ld bytes; free space %ld bytes; enough %d\n", - (long)target_size, (long)free_space, enough_space); + // Is there enough room in the target filesystem to hold the patched + // file? - if (!enough_space && source_patch_filename != 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, "MTD:", 4) == 0) { - // It's impossible to free space on the target filesystem by - // deleting the source if the source is an MTD partition. If - // we're ever in a state where we need to do this, fail. - fprintf(stderr, "not enough free space for target but source is MTD\n"); - return 1; - } + if (strncmp(target_filename, "MTD:", 4) == 0) { + // If the target is an MTD 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 MTD + // write is interrupted. if (MakeFreeSpaceOnCache(source_file.size) < 0) { fprintf(stderr, "not enough free space on /cache\n"); return 1; } - if (SaveFileContents(CACHE_TEMP_SOURCE, source_file) < 0) { fprintf(stderr, "failed to back up source file\n"); return 1; } made_copy = 1; - unlink(source_filename); + } else { + // 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(). + char* target_fs = strdup(target_filename); + char* slash = strchr(target_fs+1, '/'); + if (slash != NULL) { + *slash = '\0'; + } size_t free_space = FreeSpaceForFile(target_fs); - printf("(now %ld bytes free for target)\n", (long)free_space); + int enough_space = + free_space > (target_size * 3 / 2); // 50% margin of error + printf("target %ld bytes; free space %ld bytes; enough %d\n", + (long)target_size, (long)free_space, enough_space); + + if (!enough_space && source_patch_filename != 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, "MTD:", 4) == 0) { + // It's impossible to free space on the target filesystem by + // deleting the source if the source is an MTD partition. If + // we're ever in a state where we need to do this, fail. + fprintf(stderr, "not enough free space for target but source is MTD\n"); + return 1; + } + + if (MakeFreeSpaceOnCache(source_file.size) < 0) { + fprintf(stderr, "not enough free space on /cache\n"); + return 1; + } + + if (SaveFileContents(CACHE_TEMP_SOURCE, source_file) < 0) { + fprintf(stderr, "failed to back up source file\n"); + return 1; + } + made_copy = 1; + unlink(source_filename); + + size_t free_space = FreeSpaceForFile(target_fs); + printf("(now %ld bytes free for target)\n", (long)free_space); + } } FileContents* source_to_use; @@ -602,14 +702,19 @@ int main(int argc, char** argv) { patch_filename = copy_patch_filename; } - // We write the decoded output to ".patch". - char* outname = (char*)malloc(strlen(target_filename) + 10); - strcpy(outname, target_filename); - strcat(outname, ".patch"); + char* outname = NULL; + if (strncmp(target_filename, "MTD:", 4) == 0) { + outname = MTD_TARGET_TEMP_FILE; + } else { + // We write the decoded output to ".patch". + outname = (char*)malloc(strlen(target_filename) + 10); + strcpy(outname, target_filename); + strcat(outname, ".patch"); + } FILE* output = fopen(outname, "wb"); if (output == NULL) { - fprintf(stderr, "failed to patch file %s: %s\n", - target_filename, strerror(errno)); + fprintf(stderr, "failed to open output file %s: %s\n", + outname, strerror(errno)); return 1; } @@ -665,22 +770,32 @@ int main(int argc, char** argv) { return 1; } - // Give the .patch file the same owner, group, and mode of the - // original source file. - if (chmod(outname, source_to_use->st.st_mode) != 0) { - fprintf(stderr, "chmod of \"%s\" failed: %s\n", outname, strerror(errno)); - return 1; - } - if (chown(outname, source_to_use->st.st_uid, source_to_use->st.st_gid) != 0) { - fprintf(stderr, "chown of \"%s\" failed: %s\n", outname, strerror(errno)); - return 1; - } + if (strcmp(outname, MTD_TARGET_TEMP_FILE) == 0) { + // Copy the temp file to the MTD partition. + if (CopyToMTDPartition(outname, target_filename) != 0) { + fprintf(stderr, "copy of %s to %s failed\n", outname, target_filename); + return 1; + } + unlink(outname); + } else { + // Give the .patch file the same owner, group, and mode of the + // original source file. + if (chmod(outname, source_to_use->st.st_mode) != 0) { + fprintf(stderr, "chmod of \"%s\" failed: %s\n", outname, strerror(errno)); + return 1; + } + if (chown(outname, source_to_use->st.st_uid, + source_to_use->st.st_gid) != 0) { + fprintf(stderr, "chown of \"%s\" failed: %s\n", outname, strerror(errno)); + return 1; + } - // Finally, rename the .patch file to replace the target file. - if (rename(outname, target_filename) != 0) { - fprintf(stderr, "rename of .patch to \"%s\" failed: %s\n", - target_filename, strerror(errno)); - return 1; + // Finally, rename the .patch file to replace the target file. + if (rename(outname, target_filename) != 0) { + fprintf(stderr, "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 diff --git a/tools/applypatch/applypatch.h b/tools/applypatch/applypatch.h index 041ac2e152..e0320fb7d2 100644 --- a/tools/applypatch/applypatch.h +++ b/tools/applypatch/applypatch.h @@ -38,6 +38,11 @@ typedef struct _FileContents { // and use it as the source instead. #define CACHE_TEMP_SOURCE "/cache/saved.file" +// When writing to an MTD partition, we first put the output in this +// temp file, then copy it to the partition once the patching is +// finished (and the target sha1 verified). +#define MTD_TARGET_TEMP_FILE "/tmp/mtd-temp" + // applypatch.c size_t FreeSpaceForFile(const char* filename); diff --git a/tools/releasetools/ota_from_target_files b/tools/releasetools/ota_from_target_files index cd063a19d6..7e36da2c19 100755 --- a/tools/releasetools/ota_from_target_files +++ b/tools/releasetools/ota_from_target_files @@ -427,11 +427,12 @@ def Difference(tf, sf, diff_program): cmd.append(ptemp.name) p = common.Run(cmd) _, err = p.communicate() - if err: - raise ExternalError("failure running %s:\n%s\n" % (diff_program, err)) + if err or p.returncode != 0: + print "WARNING: failure running %s:\n%s\n" % (diff_program, err) + return None diff = ptemp.read() - ptemp.close() finally: + ptemp.close() stemp.close() ttemp.close() @@ -478,8 +479,9 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): if tf.name.endswith(".gz"): diff_method = "imgdiff" d = Difference(tf, sf, diff_method) - print fn, tf.size, len(d), (float(len(d)) / tf.size) - if len(d) > tf.size * OPTIONS.patch_threshold: + if d is not None: + print fn, tf.size, len(d), (float(len(d)) / tf.size) + if d is None or len(d) > tf.size * OPTIONS.patch_threshold: # patch is almost as big as the file; don't bother patching tf.AddToZip(output_zip) verbatim_targets.append((fn, tf.size)) @@ -503,11 +505,13 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): '"ro.build.fingerprint=%s") == "true"') % (source_fp, target_fp)) - source_boot = common.BuildBootableImage( - os.path.join(OPTIONS.source_tmp, "BOOT")) - target_boot = common.BuildBootableImage( - os.path.join(OPTIONS.target_tmp, "BOOT")) - updating_boot = (source_boot != target_boot) + source_boot = File("/tmp/boot.img", + common.BuildBootableImage( + os.path.join(OPTIONS.source_tmp, "BOOT"))) + target_boot = File("/tmp/boot.img", + common.BuildBootableImage( + os.path.join(OPTIONS.target_tmp, "BOOT"))) + updating_boot = (source_boot.data != target_boot.data) source_recovery = File("system/recovery.img", common.BuildBootableImage( @@ -543,12 +547,6 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): script.append("run_program PACKAGE:applypatch -c /%s %s %s" % (fn, tf.sha1, sf.sha1)) - if patch_list: - script.append("run_program PACKAGE:applypatch -s %d" % - (largest_source_size,)) - script.append("copy_dir PACKAGE:patch CACHE:../tmp/patchtmp") - IncludeBinary("applypatch", target_zip, output_zip) - if updating_recovery: d = Difference(target_recovery, source_recovery, "imgdiff") print "recovery target: %d source: %d diff: %d" % ( @@ -561,6 +559,23 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): (source_recovery.size, source_recovery.sha1, target_recovery.size, target_recovery.sha1)) + if updating_boot: + d = Difference(target_boot, source_boot, "imgdiff") + print "boot target: %d source: %d diff: %d" % ( + target_boot.size, source_boot.size, len(d)) + + output_zip.writestr("patch/boot.img.p", d) + + script.append(("run_program PACKAGE:applypatch -c " + "MTD:boot:%d:%s:%d:%s") % + (source_boot.size, source_boot.sha1, + target_boot.size, target_boot.sha1)) + + if patch_list or updating_recovery or updating_boot: + script.append("run_program PACKAGE:applypatch -s %d" % + (largest_source_size,)) + script.append("copy_dir PACKAGE:patch CACHE:../tmp/patchtmp") + IncludeBinary("applypatch", target_zip, output_zip) script.append("\n# ---- start making changes here\n") @@ -570,8 +585,17 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): DeleteFiles(script, [SubstituteRoot(i[0]) for i in verbatim_targets]) if updating_boot: - script.append("format BOOT:") - output_zip.writestr("boot.img", target_boot) + # Produce the boot image by applying a patch to the current + # contents of the boot partition, and write it back to the + # partition. + script.append(("run_program PACKAGE:applypatch " + "MTD:boot:%d:%s:%d:%s - " + "%s %d %s:/tmp/patchtmp/boot.img.p") + % (source_boot.size, source_boot.sha1, + target_boot.size, target_boot.sha1, + target_boot.sha1, + target_boot.size, + source_boot.sha1)) print "boot image changed; including." else: print "boot image unchanged; skipping." @@ -654,10 +678,6 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): # permissions. script.extend(temp_script) - if updating_boot: - script.append("show_progress 0.1 5") - script.append("write_raw_image PACKAGE:boot.img BOOT:") - if OPTIONS.extra_script is not None: script.append(OPTIONS.extra_script)