PARSE_REPEATED_ARGUMENT,
PARSE_MISSING_ARGUMENT,
PARSE_PATCH_MISTAKE,
+ PARSE_PROGRESS_BAD_KIND,
PARSE_BAD_KIND,
PARSE_BAD_SIZE,
PARSE_NO_PARSE,
const char *archive_file;
const char *patch_orig;
const char *dest_sha1;
+ const char *progress_info_file;
size_t dest_size;
bool write_all;
enum archive_kind kind;
const char *archive_file = NULL;
const char *patch_orig = NULL;
const char *dest_sha1 = NULL;
+ const char *progress_info_file = NULL;
long long int dest_size = 0;
enum archive_kind kind = -1;
bool help = false;
for (;;) {
const struct option long_options[] = {
- {"archive", required_argument, NULL, 0 },
- {"dest", required_argument, NULL, 1 },
- {"archive-file", required_argument, NULL, 2 },
- {"kind", required_argument, NULL, 3 },
- {"patch-orig", required_argument, NULL, 4 },
- {"dest-sha1", required_argument, NULL, 5 },
- {"dest-size", required_argument, NULL, 6 },
- {"no-write-all", no_argument, NULL, 'n'},
- {"help", no_argument, NULL, 'h'},
+ {"archive", required_argument, NULL, 0 },
+ {"dest", required_argument, NULL, 1 },
+ {"archive-file", required_argument, NULL, 2 },
+ {"kind", required_argument, NULL, 3 },
+ {"patch-orig", required_argument, NULL, 4 },
+ {"dest-sha1", required_argument, NULL, 5 },
+ {"dest-size", required_argument, NULL, 6 },
+ {"progress-info-file", required_argument, NULL, 7 },
+ {"no-write-all", no_argument, NULL, 'n'},
+ {"help", no_argument, NULL, 'h'},
{0}
};
int option = getopt_long(argc, argv, "hn", long_options, NULL);
return (struct parse_result) { .result = PARSE_BAD_SIZE };
break;
+ case 7: // progress-info-file
+ if (progress_info_file != NULL)
+ return (struct parse_result) { .result = PARSE_REPEATED_ARGUMENT };
+ progress_info_file = optarg;
+ break;
+
case 'n': // no-write-all
if (!write_all)
return (struct parse_result) { .result = PARSE_REPEATED_ARGUMENT };
if (patch_orig != NULL && kind == KIND_RAW)
return (struct parse_result) { .result = PARSE_PATCH_MISTAKE };
+ if (progress_info_file != NULL && kind == KIND_RAW)
+ return (struct parse_result) { .result = PARSE_PROGRESS_BAD_KIND };
+
return (struct parse_result) {
.result = PARSE_OK,
.archive = archive,
.patch_orig = patch_orig,
.dest_sha1 = dest_sha1,
.dest_size = dest_size,
+ .progress_info_file = progress_info_file,
.kind = kind,
.write_all = write_all,
};
void help(char *name)
{
fprintf(stderr,
- "Usage: %s --archive ARCHIVE --archive-file FILE --dest DESTINATION --kind KIND [--patch-orig ORIGIN] [--dest-sha1 SHA1] [--dest-size SIZE] [--no-write-all]\n"
+ "Usage: %s --archive ARCHIVE --archive-file FILE --dest DESTINATION --kind KIND \
+ [--patch-orig ORIGIN] [--dest-sha1 SHA1] [--dest-size SIZE] [--no-write-all] [--progress-info-file FILE]\n"
"\n"
"Patches a partition using an archive.\n"
"It will look for a file named FILE in an archive called ARCHIVE; the archive should have an extension\n"
"Additionally, if SIZE is provided, it is used as the destination size; this matters for SHA1 comparision,\n"
"and also if ss_brotli_patch.\n"
"In case the KIND is ss_brotli_path and the DESTINATION is the clone of the ORIGIN you can specify\n"
- "the --no-write-all flag to not copy the data from the source, because it is already there.\n",
+ "the --no-write-all flag to not copy the data from the source, because it is already there.\n"
+ "Progress info file can be provided for deltas using ss_brotli_patch to report upgrade progress. The file contains block count of the delta.\n",
name);
}
return 0;
}
+int get_block_count_from_progress_file(const char* progress_info_file, const char *archive_file, uint64_t *block_count)
+{
+ const char *separators = " =\n";
+ FILE *fp = NULL;
+ char *buf = NULL;
+ int ret = 0;
+
+ fp = fopen(progress_info_file, "r");
+ if (fp == NULL) {
+ fprintf(stderr, "Failed to open %s\n", progress_info_file);
+ return -1;
+ }
+
+ fseek(fp, 0, SEEK_END);
+ off_t file_size = ftello(fp);
+ if (file_size == -1) {
+ fprintf(stderr, "Failed to check size of %s\n", progress_info_file);
+ ret = -1;
+ goto cleanup;
+ }
+ fseek(fp, 0 , SEEK_SET);
+
+ buf = (char *)malloc(file_size + 1);
+ if (buf == NULL) {
+ fprintf(stderr, "Malloc error\n");
+ ret = -1;
+ goto cleanup;
+ }
+
+ if (fread(buf, 1, file_size, fp) != file_size) {
+ fprintf(stderr, "Failed to read %s\n", progress_info_file);
+ ret = -1;
+ goto cleanup;
+ }
+
+ buf[file_size] = '\0';
+ int iter = 0;
+ char *saveptr;
+ char *read_word = strtok_r(buf, separators, &saveptr);
+
+ while (read_word) {
+ if (iter % 2 == 0) {
+ if (strcmp(read_word, archive_file) == 0) {
+ read_word = strtok_r(NULL, separators, &saveptr);
+
+ if (read_word == NULL) {
+ fprintf(stderr, "%s file is not compatible - value missing\n", progress_info_file);
+ ret = -1;
+ } else {
+ *block_count = (uint64_t) atoi(read_word);
+ fprintf(stderr, "Block count for %s: %ld\n", archive_file, *block_count);
+ ret = 0;
+ }
+
+ goto cleanup;
+ }
+ }
+ iter++;
+ read_word = strtok_r(NULL, separators, &saveptr);
+ }
+
+ fprintf(stderr, "Block count for %s was not found in %s\n", archive_file, progress_info_file);
+ ret = -1;
+
+cleanup:
+ if (fp != NULL)
+ fclose(fp);
+ if (buf != NULL)
+ free(buf);
+
+ return ret;
+}
+
int main(int argc, char **argv)
{
struct parse_result parsed = parse_args(argc, argv);
fprintf(stderr, "`patch-orig` parameter doesn't fit the kind\n");
help(argv[0]);
return EXIT_FAILURE;
+ case PARSE_PROGRESS_BAD_KIND:
+ fprintf(stderr, "`progress-info-file` parameter is only supported for ss_brotli_patch\n");
+ help(argv[0]);
+ return EXIT_FAILURE;
case PARSE_BAD_KIND:
fprintf(stderr, "Invalid `kind` parameter (possible: `raw`, `ss_brotli_patch`)\n");
help(argv[0]);
case KIND_BROTLI_PATCH:
{
+ uint64_t block_count;
+ uint64_t *block_count_ptr = &block_count;
+ if (parsed.progress_info_file != NULL) {
+ if (get_block_count_from_progress_file(parsed.progress_info_file,
+ parsed.archive_file, block_count_ptr) != 0) {
+ fprintf(stderr, "Failed to get block count for %s\n", parsed.archive_file);
+ return EXIT_FAILURE;
+ }
+ } else {
+ block_count_ptr = NULL;
+ }
+
struct dec_funcs funcs = { init_brotli, decompress_brotli, free_brotli };
- r = apply_patch(parsed.patch_orig, parsed.dest, tar, parsed.dest_size, NULL, parsed.write_all, &funcs);
+ r = apply_patch(parsed.patch_orig, parsed.dest, tar, parsed.dest_size, NULL,
+ parsed.write_all, &funcs, block_count_ptr);
break;
}
}
#define SSINT_LEN 8
#define BLOCK_COUNT_REPORT 5000
+#define PROGRESS_REPORT_FREQUENCY 300
+
const char SSDIFF_MAGIC[] = "SSDIFF40";
static void free_data(struct bs_data *data, struct dec_funcs *funcs)
return PF_OK;
}
-int apply_patch(const char *source_file, const char *dest_file, TAR *patch_tar, size_t dest_size, size_t *free_space, bool write_all, struct dec_funcs *funcs)
+int apply_patch(const char *source_file, const char *dest_file, TAR *patch_tar, size_t dest_size,
+ size_t *free_space, bool write_all, struct dec_funcs *funcs, uint64_t *block_count)
{
assert(source_file);
assert(dest_file);
if ((res = remmap(&data.dest, 0)) != PF_OK)
return res;
+ uint64_t progress_report_block_count = 0;
+ if (block_count != NULL) {
+ if (*block_count < PROGRESS_REPORT_FREQUENCY) {
+ progress_report_block_count = *block_count;
+ } else {
+ progress_report_block_count = *block_count / PROGRESS_REPORT_FREQUENCY;
+ }
+ }
+
uint64_t total_write = 0;
while (total_write < data.dest.len) {
total_write += write;
blocks++;
- if (blocks % BLOCK_COUNT_REPORT == 0) {
- fprintf(stderr, "Number of processed patch blocks: %"PRIu64"\n", blocks);
+
+ if (block_count != NULL) {
+ if (blocks % progress_report_block_count == 0) {
+ fprintf(stderr, "\rUpgrade progress: %.2f%%", (double) blocks / *block_count * 100);
+ fflush(stderr);
+ }
+ } else {
+ if (blocks % BLOCK_COUNT_REPORT == 0) {
+ fprintf(stderr, "Number of processed patch blocks: %"PRIu64"\n", blocks);
+ }
}
}
+ if (block_count != NULL) {
+ fprintf(stderr, "\rUpgrade progress: 100.00%%\n");
+ }
+
result = PF_OK;
exit: