Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5da317e5 authored by Doug Zongker's avatar Doug Zongker
Browse files

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.
parent f6a8bada
Loading
Loading
Loading
Loading
+173 −58
Original line number Diff line number Diff line
@@ -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:<partition>[:...]".  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 <bytes>\n"
            "   or  %s -l\n"
            "\n"
            "<src-file> or <file> may be of the form\n"
            "  MTD:<partition>:<len_1>:<sha1_1>:<len_2>:<sha1_2>:...\n"
            "to specify reading from an MTD partition.\n\n",
            "Filenames may be of the form\n"
            "  MTD:<partition>:<len_1>:<sha1_1>:<len_2>:<sha1_2>"
              ":...:<backup-file>\n"
            "to specify reading from or writing to an MTD partition.\n\n",
            argv[0], argv[0], argv[0], argv[0]);
    return 1;
  }
@@ -480,15 +558,6 @@ 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) {
    fprintf(stderr, "failed to parse tgt-sha1 \"%s\"\n", argv[3]);
    return 1;
@@ -557,9 +626,39 @@ int main(int argc, char** argv) {
    }
  }

  // Is there enough room in the target filesystem to hold the patched file?
  // Is there enough room in the target filesystem to hold the patched
  // file?

  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;
  } 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);
  int enough_space = free_space > (target_size * 3 / 2);  // 50% margin of error
    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);

@@ -591,6 +690,7 @@ int main(int argc, char** argv) {
      size_t free_space = FreeSpaceForFile(target_fs);
      printf("(now %ld bytes free for target)\n", (long)free_space);
    }
  }

  FileContents* source_to_use;
  const char* patch_filename;
@@ -602,14 +702,19 @@ int main(int argc, char** argv) {
    patch_filename = copy_patch_filename;
  }

  char* outname = NULL;
  if (strncmp(target_filename, "MTD:", 4) == 0) {
    outname = MTD_TARGET_TEMP_FILE;
  } else {
    // We write the decoded output to "<tgt-file>.patch".
  char* outname = (char*)malloc(strlen(target_filename) + 10);
    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,13 +770,22 @@ int main(int argc, char** argv) {
    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) {
    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;
    }
@@ -682,6 +796,7 @@ int main(int argc, char** argv) {
              target_filename, strerror(errno));
      return 1;
    }
  }

  // If this run of applypatch created the copy, and we're here, we
  // can delete it.
+5 −0
Original line number Diff line number Diff line
@@ -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);

+42 −22
Original line number Diff line number Diff line
@@ -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)
      if d is not None:
        print fn, tf.size, len(d), (float(len(d)) / tf.size)
      if len(d) > tf.size * OPTIONS.patch_threshold:
      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)