int CacheSizeCheck(size_t bytes) { if (MakeFreeSpaceOnCache(bytes) < 0) { printf("unable to make %ld bytes available on /cache\n", (long)bytes); return 1; } else { return 0; } }
int applypatch(const char* source_filename, const char* target_filename, const char* target_sha1_str, size_t target_size, int num_patches, char** const patch_sha1_str, Value** patch_data) { printf("\napplying patch to %s\n", source_filename); if (target_filename[0] == '-' && target_filename[1] == '\0') { target_filename = source_filename; } uint8_t target_sha1[SHA_DIGEST_SIZE]; if (ParseSha1(target_sha1_str, target_sha1) != 0) { printf("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str); return 1; } FileContents copy_file; FileContents source_file; const Value* source_patch_value = NULL; const Value* copy_patch_value = NULL; int made_copy = 0; // We try to load the target file into the source_file object. if (LoadFileContents(target_filename, &source_file, RETOUCH_DO_MASK) == 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. printf("\"%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, RETOUCH_DO_MASK); } if (source_file.data != NULL) { int to_use = FindMatchingPatch(source_file.sha1, patch_sha1_str, num_patches); if (to_use >= 0) { source_patch_value = patch_data[to_use]; } } if (source_patch_value == NULL) { free(source_file.data); printf("source file is bad; trying copy\n"); if (LoadFileContents(CACHE_TEMP_SOURCE, ©_file, RETOUCH_DO_MASK) < 0) { // fail. printf("failed to read copy file\n"); return 1; } int to_use = FindMatchingPatch(copy_file.sha1, patch_sha1_str, num_patches); if (to_use >= 0) { copy_patch_value = patch_data[to_use]; } if (copy_patch_value == NULL) { // fail. printf("copy file doesn't match source SHA-1s either\n"); return 1; } } int retry = 1; SHA_CTX ctx; int output; MemorySinkInfo msi; FileContents* source_to_use; char* outname; // 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[strlen(target_filename)+1]; char* slash = strchr(target_filename+1, '/'); if (slash != NULL) { int count = slash - target_filename; strncpy(target_fs, target_filename, count); target_fs[count] = '\0'; } else { strcpy(target_fs, target_filename); } do { // Is there enough room in the target filesystem to hold the patched // file? if (strncmp(target_filename, "EMMC:", 5) == 0) { // 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.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); enough_space = (free_space > (256 << 10)) && // 256k (two-block) minimum (free_space > (target_size * 3 / 2)); // 50% margin of error printf("target %ld bytes; free space %ld bytes; retry %d; enough %d\n", (long)target_size, (long)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.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); printf("(now %ld bytes free for target)\n", (long)free_space); } } const Value* patch; if (source_patch_value != NULL) { source_to_use = &source_file; patch = source_patch_value; } else { source_to_use = ©_file; patch = copy_patch_value; } if (patch->type != VAL_BLOB) { printf("patch is not a blob\n"); return 1; } SinkFn sink = NULL; void* token = NULL; output = -1; outname = NULL; if (strncmp(target_filename, "EMMC:", 5) == 0) { // We store the decoded output in memory. msi.buffer = malloc(target_size); if (msi.buffer == NULL) { printf("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 = open(outname, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR); if (output < 0) { printf("failed to open output file %s: %s\n", outname, strerror(errno)); return 1; } sink = FileSink; token = &output; } char* header = patch->data; ssize_t header_bytes_read = patch->size; SHA_init(&ctx); int result; if (header_bytes_read >= 8 && memcmp(header, "BSDIFF40", 8) == 0) { result = ApplyBSDiffPatch(source_to_use->data, source_to_use->size, patch, 0, sink, token, &ctx); } else if (header_bytes_read >= 8 && memcmp(header, "IMGDIFF2", 8) == 0) { result = ApplyImagePatch(source_to_use->data, source_to_use->size, patch, sink, token, &ctx); } else { printf("Unknown patch file format\n"); return 1; } if (output >= 0) { fsync(output); close(output); } if (result != 0) { if (retry == 0) { printf("applying patch failed\n"); return result != 0; } else { printf("applying patch failed; retrying\n"); } if (outname != NULL) { unlink(outname); } } else { // succeeded; no need to retry break; } } while (retry-- > 0); const uint8_t* current_target_sha1 = SHA_final(&ctx); if (memcmp(current_target_sha1, target_sha1, SHA_DIGEST_SIZE) != 0) { printf("patch did not produce expected sha1\n"); return 1; } if (output < 0) { // Copy the temp file to the partition. if (WriteToPartition(msi.buffer, msi.pos, target_filename) != 0) { printf("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) { printf("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) { printf("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) { 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; }
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; }
static int GenerateTarget(FileContents* source_file, const Value* source_patch_value, FileContents* copy_file, const Value* copy_patch_value, const char* source_filename, const char* target_filename, const uint8_t target_sha1[SHA_DIGEST_SIZE], size_t target_size, const Value* bonus_data) { int retry = 1; SHA_CTX ctx; int output; MemorySinkInfo msi; FileContents* source_to_use; char* outname; int made_copy = 0; // 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[strlen(target_filename)+1]; char* slash = strchr(target_filename+1, '/'); if (slash != NULL) { int count = slash - target_filename; strncpy(target_fs, target_filename, count); target_fs[count] = '\0'; } else { strcpy(target_fs, target_filename); } do { // Is there enough room in the target filesystem to hold the patched // file? if (strncmp(target_filename, "MTD:", 4) == 0 || strncmp(target_filename, "EMMC:", 5) == 0) { // 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->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); 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, "MTD:", 4) == 0 || 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->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); printf("(now %zu bytes free for target) ", free_space); } } 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"); return 1; } SinkFn sink = NULL; void* token = NULL; output = -1; outname = NULL; if (strncmp(target_filename, "MTD:", 4) == 0 || strncmp(target_filename, "EMMC:", 5) == 0) { // We store the decoded output in memory. msi.buffer = reinterpret_cast<unsigned char*>(malloc(target_size)); if (msi.buffer == NULL) { printf("failed to alloc %zu bytes for output\n", 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 = reinterpret_cast<char*>(malloc(strlen(target_filename) + 10)); strcpy(outname, target_filename); strcat(outname, ".patch"); output = open(outname, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR); if (output < 0) { printf("failed to open output file %s: %s\n", outname, strerror(errno)); return 1; } sink = FileSink; token = &output; } char* header = patch->data; ssize_t header_bytes_read = patch->size; SHA_init(&ctx); int result; if (header_bytes_read >= 8 && memcmp(header, "BSDIFF40", 8) == 0) { result = ApplyBSDiffPatch(source_to_use->data, source_to_use->size, patch, 0, sink, token, &ctx); } else if (header_bytes_read >= 8 && memcmp(header, "IMGDIFF2", 8) == 0) { result = ApplyImagePatch(source_to_use->data, source_to_use->size, patch, sink, token, &ctx, bonus_data); } else { printf("Unknown patch file format\n"); return 1; } if (output >= 0) { if (fsync(output) != 0) { printf("failed to fsync file \"%s\" (%s)\n", outname, strerror(errno)); result = 1; } if (close(output) != 0) { printf("failed to close file \"%s\" (%s)\n", outname, strerror(errno)); result = 1; } } if (result != 0) { if (retry == 0) { printf("applying patch failed\n"); return result != 0; } else { printf("applying patch failed; retrying\n"); } if (outname != NULL) { unlink(outname); } } else { // succeeded; no need to retry break; } } while (retry-- > 0); const uint8_t* current_target_sha1 = SHA_final(&ctx); if (memcmp(current_target_sha1, target_sha1, SHA_DIGEST_SIZE) != 0) { printf("patch did not produce expected sha1\n"); return 1; } else { printf("now %s\n", short_sha1(target_sha1).c_str()); } if (output < 0) { // Copy the temp file to the partition. if (WriteToPartition(msi.buffer, msi.pos, target_filename) != 0) { printf("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) { printf("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) { printf("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) { 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; }