int main(int argc, char** argv) { if (argc < 2) { usage: printf( "usage: %s [-b <bonus-file>] <src-file> <tgt-file> <tgt-sha1> <tgt-size> " "[<src-sha1>:<patch> ...]\n" " or %s -c <file> [<sha1> ...]\n" " or %s -s <bytes>\n" " or %s -l\n" "\n" "Filenames may be of the form\n" " MTD:<partition>:<len_1>:<sha1_1>:<len_2>:<sha1_2>:...\n" "to specify reading from or writing to an MTD partition.\n\n", argv[0], argv[0], argv[0], argv[0]); return 2; } int result; if (strncmp(argv[1], "-l", 3) == 0) { result = ShowLicenses(); } else if (strncmp(argv[1], "-c", 3) == 0) { result = CheckMode(argc, argv); } else if (strncmp(argv[1], "-s", 3) == 0) { result = SpaceMode(argc, argv); } else if (strncmp(argv[1], "-t", 3) == 0) { result = TeeUpdateMode(argc, argv); } else { result = PatchMode(argc, argv); } if (result == 2) { goto usage; } return result; }
int applypatch(int argc, char** argv) { if (argc < 2) { return 2; } if (strncmp(argv[1], "-l", 3) == 0) { return ShowLicenses(); } if (strncmp(argv[1], "-c", 3) == 0) { return CheckMode(argc, argv); } if (strncmp(argv[1], "-s", 3) == 0) { if (argc != 3) { return 2; } size_t bytes = strtol(argv[2], NULL, 10); if (MakeFreeSpaceOnCache(bytes) < 0) { printf("unable to make %ld bytes available on /cache\n", (long)bytes); return 1; } else { return 0; } } uint8_t target_sha1[SHA_DIGEST_SIZE]; const char* source_filename = argv[1]; const char* target_filename = argv[2]; if (target_filename[0] == '-' && target_filename[1] == '\0') { target_filename = source_filename; } if (ParseSha1(argv[3], target_sha1) != 0) { fprintf(stderr, "failed to parse tgt-sha1 \"%s\"\n", argv[3]); return 1; } unsigned long target_size = strtoul(argv[4], NULL, 0); int num_patches; Patch* patches; if (ParseShaArgs(argc-5, argv+5, &patches, &num_patches) < 0) { return 1; } FileContents copy_file; FileContents source_file; const char* source_patch_filename = NULL; const char* copy_patch_filename = NULL; int made_copy = 0; // We try to load the target file into the source_file object. if (LoadFileContents(target_filename, &source_file) == 0) { if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_SIZE) == 0) { // The early-exit case: the patch was already applied, this file // has the desired hash, nothing for us to do. fprintf(stderr, "\"%s\" is already target; no patch needed\n", target_filename); return 0; } } if (source_file.data == NULL || (target_filename != source_filename && strcmp(target_filename, source_filename) != 0)) { // Need to load the source file: either we failed to load the // target file, or we did but it's different from the source file. free(source_file.data); LoadFileContents(source_filename, &source_file); } if (source_file.data != NULL) { const Patch* to_use = FindMatchingPatch(source_file.sha1, patches, num_patches); if (to_use != NULL) { source_patch_filename = to_use->patch_filename; } } if (source_patch_filename == NULL) { free(source_file.data); fprintf(stderr, "source file is bad; trying copy\n"); if (LoadFileContents(CACHE_TEMP_SOURCE, ©_file) < 0) { // fail. fprintf(stderr, "failed to read copy file\n"); return 1; } const Patch* to_use = FindMatchingPatch(copy_file.sha1, patches, num_patches); if (to_use != NULL) { copy_patch_filename = to_use->patch_filename; } if (copy_patch_filename == NULL) { // fail. fprintf(stderr, "copy file doesn't match source SHA-1s either\n"); return 1; } } // 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 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; const char* patch_filename; if (source_patch_filename != NULL) { source_to_use = &source_file; patch_filename = source_patch_filename; } else { source_to_use = ©_file; patch_filename = copy_patch_filename; } char* outname = NULL; FILE* output = NULL; MemorySinkInfo msi; SinkFn sink = NULL; void* token = NULL; if (strncmp(target_filename, "MTD:", 4) == 0) { // We store the decoded output in memory. msi.buffer = malloc(target_size); if (msi.buffer == NULL) { fprintf(stderr, "failed to alloc %ld bytes for output\n", (long)target_size); return 1; } msi.pos = 0; msi.size = target_size; sink = MemorySink; token = &msi; } else { // We write the decoded output to "<tgt-file>.patch". outname = (char*)malloc(strlen(target_filename) + 10); strcpy(outname, target_filename); strcat(outname, ".patch"); output = fopen(outname, "wb"); if (output == NULL) { fprintf(stderr, "failed to open output file %s: %s\n", outname, strerror(errno)); return 1; } sink = FileSink; token = output; } #define MAX_HEADER_LENGTH 8 unsigned char header[MAX_HEADER_LENGTH]; FILE* patchf = fopen(patch_filename, "rb"); if (patchf == NULL) { fprintf(stderr, "failed to open patch file %s: %s\n", patch_filename, strerror(errno)); return 1; } int header_bytes_read = fread(header, 1, MAX_HEADER_LENGTH, patchf); fclose(patchf); SHA_CTX ctx; SHA_init(&ctx); if (header_bytes_read >= 4 && header[0] == 0xd6 && header[1] == 0xc3 && header[2] == 0xc4 && header[3] == 0) { // xdelta3 patches begin "VCD" (with the high bits set) followed // by a zero byte (the version number). fprintf(stderr, "error: xdelta3 patches no longer supported\n"); return 1; } else if (header_bytes_read >= 8 && memcmp(header, "BSDIFF40", 8) == 0) { int result = ApplyBSDiffPatch(source_to_use->data, source_to_use->size, patch_filename, 0, sink, token, &ctx); if (result != 0) { fprintf(stderr, "ApplyBSDiffPatch failed\n"); return result; } } else if (header_bytes_read >= 8 && memcmp(header, "IMGDIFF", 7) == 0 && (header[7] == '1' || header[7] == '2')) { int result = ApplyImagePatch(source_to_use->data, source_to_use->size, patch_filename, sink, token, &ctx); if (result != 0) { fprintf(stderr, "ApplyImagePatch failed\n"); return result; } } else { fprintf(stderr, "Unknown patch file format\n"); return 1; } if (output != NULL) { fflush(output); fsync(fileno(output)); fclose(output); } const uint8_t* current_target_sha1 = SHA_final(&ctx); if (memcmp(current_target_sha1, target_sha1, SHA_DIGEST_SIZE) != 0) { fprintf(stderr, "patch did not produce expected sha1\n"); return 1; } if (output == NULL) { // Copy the temp file to the MTD partition. if (WriteToMTDPartition(msi.buffer, msi.pos, target_filename) != 0) { fprintf(stderr, "write of patched data to %s failed\n", target_filename); return 1; } free(msi.buffer); } 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; } } // 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; }