X-Git-Url: https://wimlib.net/git/?p=wimlib;a=blobdiff_plain;f=programs%2Fimagex.c;h=d6d261710f4e0e6adbba0086986d506ceabe71d0;hp=f7d8b66558389d97e16b7b16086588d1ebf37a34;hb=70aa58f2c54d32ab12bda83bfaa092be97b80fb5;hpb=abfc6acdbf39b8d8bd869ae26650ac983139cfe0 diff --git a/programs/imagex.c b/programs/imagex.c index f7d8b665..d6d26171 100644 --- a/programs/imagex.c +++ b/programs/imagex.c @@ -6,7 +6,7 @@ */ /* - * Copyright (C) 2012 Eric Biggers + * Copyright (C) 2012, 2013 Eric Biggers * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,6 +26,7 @@ #include "wimlib.h" +#include #include #include #include @@ -38,13 +39,17 @@ #include #include +#ifdef HAVE_ALLOCA_H +#include +#endif + #define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0])) #define for_opt(c, opts) while ((c = getopt_long_only(argc, (char**)argv, "", \ opts, NULL)) != -1) enum imagex_op_type { - APPEND, + APPEND = 0, APPLY, CAPTURE, DELETE, @@ -67,16 +72,18 @@ static const char *usage_strings[] = { "imagex append (DIRECTORY | NTFS_VOLUME) WIMFILE [IMAGE_NAME]\n" " [DESCRIPTION] [--boot] [--check] [--flags EDITION_ID]\n" " [--verbose] [--dereference] [--config=FILE]\n" -" [--threads=NUM_THREADS] [--rebuild]\n", +" [--threads=NUM_THREADS] [--rebuild] [--unix-data]\n" +" [--source-list]\n", [APPLY] = "imagex apply WIMFILE [IMAGE_NUM | IMAGE_NAME | all]\n" " (DIRECTORY | NTFS_VOLUME) [--check] [--hardlink]\n" -" [--symlink] [--verbose] [--ref=\"GLOB\"]\n", +" [--symlink] [--verbose] [--ref=\"GLOB\"] [--unix-data]\n", [CAPTURE] = "imagex capture (DIRECTORY | NTFS_VOLUME) WIMFILE [IMAGE_NAME]\n" " [DESCRIPTION] [--boot] [--check] [--compress=TYPE]\n" " [--flags EDITION_ID] [--verbose] [--dereference]\n" -" [--config=FILE] [--threads=NUM_THREADS]\n", +" [--config=FILE] [--threads=NUM_THREADS] [--unix-data]\n" +" [--source-list]\n", [DELETE] = "imagex delete WIMFILE (IMAGE_NUM | IMAGE_NAME | all) [--check] [--soft]\n", [DIR] = @@ -95,31 +102,26 @@ static const char *usage_strings[] = { [MOUNT] = "imagex mount WIMFILE (IMAGE_NUM | IMAGE_NAME) DIRECTORY\n" " [--check] [--debug] [--streams-interface=INTERFACE]\n" -" [--ref=\"GLOB\"]\n", +" [--ref=\"GLOB\"] [--unix-data] [--allow-other]\n", [MOUNTRW] = "imagex mountrw WIMFILE [IMAGE_NUM | IMAGE_NAME] DIRECTORY\n" " [--check] [--debug] [--streams-interface=INTERFACE]\n" -" [--staging-dir=DIR]\n", +" [--staging-dir=DIR] [--unix-data] [--allow-other]\n", [OPTIMIZE] = "imagex optimize WIMFILE [--check] [--recompress] [--compress=TYPE]\n", [SPLIT] = "imagex split WIMFILE SPLIT_WIMFILE PART_SIZE_MB [--check]\n", [UNMOUNT] = -"imagex unmount DIRECTORY [--commit] [--check]\n", -}; - -static const struct option common_options[] = { - {"help", 0, NULL, 'h'}, - {"version", 0, NULL, 'v'}, - {NULL, 0, NULL, 0}, +"imagex unmount DIRECTORY [--commit] [--check] [--rebuild]\n", }; static const struct option apply_options[] = { - {"check", no_argument, NULL, 'c'}, - {"hardlink", no_argument, NULL, 'h'}, - {"symlink", no_argument, NULL, 's'}, - {"verbose", no_argument, NULL, 'v'}, - {"ref", required_argument, NULL, 'r'}, + {"check", no_argument, NULL, 'c'}, + {"hardlink", no_argument, NULL, 'h'}, + {"symlink", no_argument, NULL, 's'}, + {"verbose", no_argument, NULL, 'v'}, + {"ref", required_argument, NULL, 'r'}, + {"unix-data", no_argument, NULL, 'U'}, {NULL, 0, NULL, 0}, }; static const struct option capture_or_append_options[] = { @@ -132,6 +134,8 @@ static const struct option capture_or_append_options[] = { {"verbose", no_argument, NULL, 'v'}, {"threads", required_argument, NULL, 't'}, {"rebuild", no_argument, NULL, 'R'}, + {"unix-data", no_argument, NULL, 'U'}, + {"source-list", no_argument, NULL, 'S'}, {NULL, 0, NULL, 0}, }; static const struct option delete_options[] = { @@ -172,6 +176,8 @@ static const struct option mount_options[] = { {"streams-interface", required_argument, NULL, 's'}, {"ref", required_argument, NULL, 'r'}, {"staging-dir", required_argument, NULL, 'D'}, + {"unix-data", no_argument, NULL, 'U'}, + {"allow-other", no_argument, NULL, 'A'}, {NULL, 0, NULL, 0}, }; @@ -187,8 +193,9 @@ static const struct option split_options[] = { }; static const struct option unmount_options[] = { - {"commit", no_argument, NULL, 'c'}, - {"check", no_argument, NULL, 'C'}, + {"commit", no_argument, NULL, 'c'}, + {"check", no_argument, NULL, 'C'}, + {"rebuild", no_argument, NULL, 'R'}, {NULL, 0, NULL, 0}, }; @@ -251,6 +258,7 @@ static int verify_image_exists_and_is_single(int image, const char *image_name, return ret; } +/* Parse the argument to --compress */ static int get_compression_type(const char *optarg) { if (strcasecmp(optarg, "maximum") == 0 || strcasecmp(optarg, "lzx") == 0) @@ -266,6 +274,8 @@ static int get_compression_type(const char *optarg) } } +/* Returns the size of a file given its name, or -1 if the file does not exist + * or its size cannot be determined. */ static off_t file_get_size(const char *filename) { struct stat st; @@ -275,23 +285,251 @@ static off_t file_get_size(const char *filename) return (off_t)-1; } +static const char *default_capture_config = +"[ExclusionList]\n" +"\\$ntfs.log\n" +"\\hiberfil.sys\n" +"\\pagefile.sys\n" +"\\System Volume Information\n" +"\\RECYCLER\n" +"\\Windows\\CSC\n" +"\n" +"[CompressionExclusionList]\n" +"*.mp3\n" +"*.zip\n" +"*.cab\n" +"\\WINDOWS\\inf\\*.pnf\n"; + +/* Read standard input until EOF and return the full contents in a malloc()ed + * buffer and the number of bytes of data in @len_ret. Returns NULL on read + * error. */ +static char *stdin_get_contents(size_t *len_ret) +{ + /* stdin can, of course, be a pipe or other non-seekable file, so the + * total length of the data cannot be pre-determined */ + char *buf = NULL; + size_t newlen = 1024; + size_t pos = 0; + size_t inc = 1024; + for (;;) { + char *p = realloc(buf, newlen); + size_t bytes_read, bytes_to_read; + if (!p) { + imagex_error("out of memory while reading stdin"); + break; + } + buf = p; + bytes_to_read = newlen - pos; + bytes_read = fread(&buf[pos], 1, bytes_to_read, stdin); + pos += bytes_read; + if (bytes_read != bytes_to_read) { + if (feof(stdin)) { + *len_ret = pos; + return buf; + } else { + imagex_error_with_errno("error reading stdin"); + break; + } + } + newlen += inc; + inc *= 3; + inc /= 2; + } + free(buf); + return NULL; +} + +enum { + PARSE_FILENAME_SUCCESS = 0, + PARSE_FILENAME_FAILURE = 1, + PARSE_FILENAME_NONE = 2, +}; + +/* + * Parses a filename in the source list file format. (See the man page for + * 'imagex capture' for details on this format and the meaning.) Accepted + * formats for filenames are an unquoted string (whitespace-delimited), or a + * double or single-quoted string. + * + * @line_p: Pointer to the pointer to the line of data. Will be updated + * to point past the filename iff the return value is + * PARSE_FILENAME_SUCCESS. If *len_p > 0, (*line_p)[*len_p - 1] must + * be '\0'. + * + * @len_p: @len_p initially stores the length of the line of data, which may + * be 0, and it will be updated to the number of bytes remaining in + * the line iff the return value is PARSE_FILENAME_SUCCESS. + * + * @fn_ret: Iff the return value is PARSE_FILENAME_SUCCESS, a pointer to the + * parsed filename will be returned here. + * + * Returns: PARSE_FILENAME_SUCCESS if a filename was successfully parsed; or + * PARSE_FILENAME_FAILURE if the data was invalid due to a missing + * closing quote; or PARSE_FILENAME_NONE if the line ended before the + * beginning of a filename was found. + */ +static int parse_filename(char **line_p, size_t *len_p, char **fn_ret) +{ + size_t len = *len_p; + char *line = *line_p; + char *fn; + char quote_char; + + /* Skip leading whitespace */ + for (;;) { + if (len == 0) + return PARSE_FILENAME_NONE; + if (!isspace(*line) && *line != '\0') + break; + line++; + len--; + } + quote_char = *line; + if (quote_char == '"' || quote_char == '\'') { + /* Quoted filename */ + line++; + len--; + fn = line; + line = memchr(line, quote_char, len); + if (!line) { + imagex_error("Missing closing quote: %s", fn - 1); + return PARSE_FILENAME_FAILURE; + } + } else { + /* Unquoted filename. Go until whitespace. Line is terminated + * by '\0', so no need to check 'len'. */ + fn = line; + do { + line++; + } while (!isspace(*line) && *line != '\0'); + } + *line = '\0'; + len -= line - fn; + *len_p = len; + *line_p = line; + *fn_ret = fn; + return PARSE_FILENAME_SUCCESS; +} + +/* Parses a line of data (not an empty line or comment) in the source list file + * format. (See the man page for 'imagex capture' for details on this format + * and the meaning.) + * + * @line: Line of data to be parsed. line[len - 1] must be '\0', unless + * len == 0. The data in @line will be modified by this function call. + * + * @len: Length of the line of data. + * + * @source: On success, the capture source and target described by the line is + * written into this destination. Note that it will contain pointers + * to data in the @line array. + * + * Returns true if the line was valid; false otherwise. */ +static bool +parse_source_list_line(char *line, size_t len, + struct wimlib_capture_source *source) +{ + /* SOURCE [DEST] */ + int ret; + ret = parse_filename(&line, &len, &source->fs_source_path); + if (ret != PARSE_FILENAME_SUCCESS) + return false; + ret = parse_filename(&line, &len, &source->wim_target_path); + if (ret == PARSE_FILENAME_NONE) + source->wim_target_path = source->fs_source_path; + return ret != PARSE_FILENAME_FAILURE; +} + +/* Returns %true if the given line of length @len > 0 is a comment or empty line + * in the source list file format. */ +static bool is_comment_line(const char *line, size_t len) +{ + for (;;) { + if (*line == '#') + return true; + if (!isspace(*line) && *line != '\0') + return false; + ++line; + --len; + if (len == 0) + return true; + } +} + +/* Parses a file in the source list format. (See the man page for 'imagex + * capture' for details on this format and the meaning.) + * + * @source_list_contents: Contents of the source list file. Note that this + * buffer will be modified to save memory allocations, + * and cannot be freed until the returned array of + * wimlib_capture_source's has also been freed. + * + * @source_list_nbytes: Number of bytes of data in the @source_list_contents + * buffer. + * + * @nsources_ret: On success, the length of the returned array is + * returned here. + * + * Returns: An array of `struct wimlib_capture_source's that can be passed to + * the wimlib_add_image_multisource() function to specify how a WIM image is to + * be created. */ +static struct wimlib_capture_source * +parse_source_list(char *source_list_contents, size_t source_list_nbytes, + size_t *nsources_ret) +{ + size_t nlines; + char *p; + struct wimlib_capture_source *sources; + size_t i, j; + + nlines = 0; + for (i = 0; i < source_list_nbytes; i++) + if (source_list_contents[i] == '\n') + nlines++; + sources = calloc(nlines, sizeof(*sources)); + if (!sources) { + imagex_error("out of memory"); + return NULL; + } + p = source_list_contents; + j = 0; + for (i = 0; i < nlines; i++) { + /* XXX: Could use rawmemchr() here instead, but it may not be + * available on all platforms. */ + char *endp = memchr(p, '\n', source_list_nbytes); + size_t len = endp - p + 1; + *endp = '\0'; + if (!is_comment_line(p, len)) { + if (!parse_source_list_line(p, len, &sources[j++])) { + free(sources); + return NULL; + } + } + p = endp + 1; + + } + *nsources_ret = j; + return sources; +} + +/* Reads the contents of a file into memory. */ static char *file_get_contents(const char *filename, size_t *len_ret) { struct stat stbuf; - char *buf; + char *buf = NULL; size_t len; FILE *fp; if (stat(filename, &stbuf) != 0) { imagex_error_with_errno("Failed to stat the file `%s'", filename); - return NULL; + goto out; } len = stbuf.st_size; fp = fopen(filename, "rb"); if (!fp) { imagex_error_with_errno("Failed to open the file `%s'", filename); - return NULL; + goto out; } buf = malloc(len); @@ -306,18 +544,22 @@ static char *file_get_contents(const char *filename, size_t *len_ret) goto out_free_buf; } *len_ret = len; - return buf; + goto out_fclose; out_free_buf: free(buf); + buf = NULL; out_fclose: fclose(fp); - return NULL; +out: + return buf; } +/* Return 0 if a path names a file to which the current user has write access; + * -1 otherwise (and print an error message). */ static int file_writable(const char *path) { int ret; - ret = access(path, F_OK | W_OK); + ret = access(path, W_OK); if (ret != 0) imagex_error_with_errno("Can't modify `%s'", path); return ret; @@ -326,6 +568,8 @@ static int file_writable(const char *path) #define TO_PERCENT(numerator, denominator) \ (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator))) +/* Given an enumerated value for WIM compression type, return a descriptive + * string. */ static const char *get_data_type(int ctype) { switch (ctype) { @@ -339,6 +583,7 @@ static const char *get_data_type(int ctype) return NULL; } +/* Progress callback function passed to various wimlib functions. */ static int imagex_progress_func(enum wimlib_progress_msg msg, const union wimlib_progress_info *info) { @@ -360,11 +605,17 @@ static int imagex_progress_func(enum wimlib_progress_msg msg, info->write_streams.completed_bytes >> 20, info->write_streams.total_bytes >> 20, percent_done); - if (info->write_streams.completed_bytes == info->write_streams.total_bytes) + if (info->write_streams.completed_bytes >= info->write_streams.total_bytes) putchar('\n'); break; case WIMLIB_PROGRESS_MSG_SCAN_BEGIN: - printf("Scanning `%s'...\n", info->scan.source); + printf("Scanning `%s'", info->scan.source); + if (*info->scan.wim_target_path) { + printf(" (loading as WIM path: `/%s')...\n", + info->scan.wim_target_path); + } else { + printf(" (loading as root of WIM image)...\n"); + } break; case WIMLIB_PROGRESS_MSG_SCAN_DENTRY: if (info->scan.excluded) @@ -406,13 +657,6 @@ static int imagex_progress_func(enum wimlib_progress_msg msg, "NTFS volume" : "directory"), info->extract.target); break; - case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_END: - printf("Done applying WIM image.\n"); - if (info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) { - printf("Unmounting NTFS volume `%s'...\n", - info->extract.target); - } - break; /*case WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_BEGIN:*/ /*printf("Applying directory structure to %s\n",*/ /*info->extract.target);*/ @@ -425,12 +669,21 @@ static int imagex_progress_func(enum wimlib_progress_msg msg, info->extract.completed_bytes >> 20, info->extract.total_bytes >> 20, percent_done); - if (info->extract.completed_bytes == info->extract.total_bytes) + if (info->extract.completed_bytes >= info->extract.total_bytes) putchar('\n'); break; case WIMLIB_PROGRESS_MSG_EXTRACT_DENTRY: puts(info->extract.cur_path); break; + case WIMLIB_PROGRESS_MSG_APPLY_TIMESTAMPS: + printf("Setting timestamps on all extracted files...\n"); + break; + case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_END: + if (info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) { + printf("Unmounting NTFS volume `%s'...\n", + info->extract.target); + } + break; case WIMLIB_PROGRESS_MSG_JOIN_STREAMS: percent_done = TO_PERCENT(info->join.completed_bytes, info->join.total_bytes); @@ -465,7 +718,10 @@ static int imagex_progress_func(enum wimlib_progress_msg msg, return 0; } - +/* Open all the split WIM parts that correspond to a file glob. + * + * @first_part specifies the first part of the split WIM and it may be either + * included or omitted from the glob. */ static int open_swms_from_glob(const char *swm_glob, const char *first_part, int open_flags, @@ -537,8 +793,9 @@ static unsigned parse_num_threads(const char *optarg) } -/* Extract one image, or all images, from a WIM file into a directory. */ -static int imagex_apply(int argc, const char **argv) +/* Apply one image, or all images, from a WIM file into a directory, OR apply + * one image from a WIM file to a NTFS volume. */ +static int imagex_apply(int argc, char **argv) { int c; int open_flags = WIMLIB_OPEN_FLAG_SPLIT_OK; @@ -572,6 +829,9 @@ static int imagex_apply(int argc, const char **argv) case 'r': swm_glob = optarg; break; + case 'U': + extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA; + break; default: usage(APPLY); return -1; @@ -636,6 +896,8 @@ static int imagex_apply(int argc, const char **argv) ret = wimlib_extract_image(w, image, target, extract_flags, additional_swms, num_additional_swms, imagex_progress_func); + if (ret == 0) + printf("Done applying WIM image.\n"); out: wimlib_free(w); if (additional_swms) { @@ -646,28 +908,41 @@ out: return ret; } -static int imagex_capture_or_append(int argc, const char **argv) +/* Create a WIM image from a directory tree, NTFS volume, or multiple files or + * directory trees. 'imagex capture': create a new WIM file containing the + * desired image. 'imagex append': add a new image to an existing WIM file. */ +static int imagex_capture_or_append(int argc, char **argv) { int c; int open_flags = 0; int add_image_flags = 0; int write_flags = 0; int compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS; - const char *source; const char *wimfile; const char *name; const char *desc; const char *flags_element = NULL; - const char *config_file = NULL; - char *config_str = NULL; - size_t config_len = 0; WIMStruct *w = NULL; int ret; int cur_image; - char *default_name; int cmd = strcmp(argv[0], "append") ? CAPTURE : APPEND; unsigned num_threads = 0; + char *source; + size_t source_name_len; + char *source_copy; + + const char *config_file = NULL; + char *config_str = NULL; + size_t config_len; + + bool source_list = false; + size_t source_list_nbytes; + char *source_list_contents = NULL; + bool capture_sources_malloced = false; + struct wimlib_capture_source *capture_sources; + size_t num_sources; + for_opt(c, capture_or_append_options) { switch (c) { case 'b': @@ -702,6 +977,12 @@ static int imagex_capture_or_append(int argc, const char **argv) case 'R': write_flags |= WIMLIB_WRITE_FLAG_REBUILD; break; + case 'U': + add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_UNIX_DATA; + break; + case 'S': + source_list = true; + break; default: usage(cmd); return -1; @@ -709,24 +990,63 @@ static int imagex_capture_or_append(int argc, const char **argv) } argc -= optind; argv += optind; + if (argc < 2 || argc > 4) { usage(cmd); return -1; } + source = argv[0]; wimfile = argv[1]; - char source_copy[strlen(source) + 1]; - memcpy(source_copy, source, strlen(source) + 1); - default_name = basename(source_copy); - - name = (argc >= 3) ? argv[2] : default_name; + if (argc >= 3) { + name = argv[2]; + } else { + /* Set default name to SOURCE argument, omitting any directory + * prefixes and trailing slashes. This requires making a copy + * of @source. */ + source_name_len = strlen(source); + source_copy = alloca(source_name_len + 1); + name = basename(strcpy(source_copy, source)); + } + /* Image description defaults to NULL if not given. */ desc = (argc >= 4) ? argv[3] : NULL; + if (source_list) { + /* Set up capture sources in source list mode */ + if (source[0] == '-' && source[1] == '\0') { + source_list_contents = stdin_get_contents(&source_list_nbytes); + } else { + source_list_contents = file_get_contents(source, + &source_list_nbytes); + } + if (!source_list_contents) + return -1; + + capture_sources = parse_source_list(source_list_contents, + source_list_nbytes, + &num_sources); + if (!capture_sources) { + ret = -1; + goto out; + } + capture_sources_malloced = true; + } else { + /* Set up capture source in non-source-list mode (could be + * either "normal" mode or "NTFS mode"--- see the man page). */ + capture_sources = alloca(sizeof(struct wimlib_capture_source)); + capture_sources[0].fs_source_path = source; + capture_sources[0].wim_target_path = NULL; + capture_sources[0].reserved = 0; + num_sources = 1; + } + if (config_file) { config_str = file_get_contents(config_file, &config_len); - if (!config_str) - return -1; + if (!config_str) { + ret = -1; + goto out; + } } if (cmd == APPEND) @@ -737,26 +1057,32 @@ static int imagex_capture_or_append(int argc, const char **argv) if (ret != 0) goto out; - struct stat stbuf; - - ret = stat(source, &stbuf); - if (ret == 0) { - if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) { - printf("Capturing WIM image from NTFS filesystem on `%s'\n", - source); - add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NTFS; - } - } else { - if (errno != ENOENT) { - imagex_error_with_errno("Failed to stat `%s'", source); - ret = -1; - goto out; + if (!source_list) { + struct stat stbuf; + ret = stat(source, &stbuf); + if (ret == 0) { + if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) { + printf("Capturing WIM image from NTFS filesystem on `%s'\n", + source); + add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NTFS; + } + } else { + if (errno != ENOENT) { + imagex_error_with_errno("Failed to stat `%s'", source); + ret = -1; + goto out; + } } } - ret = wimlib_add_image(w, source, name, config_str, config_len, - add_image_flags, imagex_progress_func); - + ret = wimlib_add_image_multisource(w, capture_sources, + num_sources, name, + (config_str ? config_str : + default_capture_config), + (config_str ? config_len : + strlen(default_capture_config)), + add_image_flags, + imagex_progress_func); if (ret != 0) goto out; cur_image = wimlib_get_num_images(w); @@ -784,11 +1110,14 @@ static int imagex_capture_or_append(int argc, const char **argv) out: wimlib_free(w); free(config_str); + free(source_list_contents); + if (capture_sources_malloced) + free(capture_sources); return ret; } /* Remove image(s) from a WIM. */ -static int imagex_delete(int argc, const char **argv) +static int imagex_delete(int argc, char **argv) { int c; int open_flags = 0; @@ -861,7 +1190,7 @@ out: } /* Print the files contained in an image(s) in a WIM file. */ -static int imagex_dir(int argc, const char **argv) +static int imagex_dir(int argc, char **argv) { const char *wimfile; WIMStruct *w; @@ -913,7 +1242,7 @@ out: /* Exports one, or all, images from a WIM file to a new WIM file or an existing * WIM file. */ -static int imagex_export(int argc, const char **argv) +static int imagex_export(int argc, char **argv) { int c; int open_flags = 0; @@ -1006,7 +1335,7 @@ static int imagex_export(int argc, const char **argv) ret = file_writable(dest_wimfile); if (ret != 0) - return ret; + goto out; dest_ctype = wimlib_get_compression_type(dest_w); if (compression_type_specified @@ -1018,7 +1347,6 @@ static int imagex_export(int argc, const char **argv) ret = -1; goto out; } - compression_type = dest_ctype; } else { wim_is_new = true; /* dest_wimfile is not an existing file, so create a new WIM. */ @@ -1078,7 +1406,7 @@ out: /* Prints information about a WIM file; also can mark an image as bootable, * change the name of an image, or change the description of an image. */ -static int imagex_info(int argc, const char **argv) +static int imagex_info(int argc, char **argv) { int c; bool boot = false; @@ -1100,6 +1428,7 @@ static int imagex_info(int argc, const char **argv) int open_flags = WIMLIB_OPEN_FLAG_SPLIT_OK; int part_number; int total_parts; + int num_images; for_opt(c, info_options) { switch (c) { @@ -1165,7 +1494,7 @@ static int imagex_info(int argc, const char **argv) image = wimlib_resolve_image(w, image_num_or_name); if (image == WIMLIB_NO_IMAGE && strcmp(image_num_or_name, "0") != 0) { imagex_error("The image `%s' does not exist", - image_num_or_name); + image_num_or_name); if (boot) imagex_error("If you would like to set the boot " "index to 0, specify image \"0\" with " @@ -1174,7 +1503,18 @@ static int imagex_info(int argc, const char **argv) goto out; } - if (image == WIMLIB_ALL_IMAGES && wimlib_get_num_images(w) > 1) { + num_images = wimlib_get_num_images(w); + + if (num_images == 0) { + if (boot) { + imagex_error("--boot is meaningless on a WIM with no " + "images"); + ret = WIMLIB_ERR_INVALID_IMAGE; + goto out; + } + } + + if (image == WIMLIB_ALL_IMAGES && num_images > 1) { if (boot) { imagex_error("Cannot specify the --boot flag " "without specifying a specific " @@ -1232,13 +1572,14 @@ static int imagex_info(int argc, const char **argv) "file `%s' for " "writing ", xml_out_file); + ret = -1; goto out; } ret = wimlib_extract_xml_data(w, fp); if (fclose(fp) != 0) { imagex_error("Failed to close the file `%s'", xml_out_file); - goto out; + ret = -1; } if (ret != 0) @@ -1258,7 +1599,8 @@ static int imagex_info(int argc, const char **argv) /* Modification operations */ if (total_parts != 1) { imagex_error("Modifying a split WIM is not supported."); - return -1; + ret = -1; + goto out; } if (image == WIMLIB_ALL_IMAGES) image = 1; @@ -1266,7 +1608,8 @@ static int imagex_info(int argc, const char **argv) if (image == WIMLIB_NO_IMAGE && new_name) { imagex_error("Cannot specify new_name (`%s') when " "using image 0", new_name); - return -1; + ret = -1; + goto out; } if (boot) { @@ -1314,18 +1657,18 @@ static int imagex_info(int argc, const char **argv) /* Only call wimlib_overwrite() if something actually needs to * be changed. */ if (boot || new_name || new_desc || - check != wimlib_has_integrity_table(w)) { + (check && !wimlib_has_integrity_table(w))) + { + int write_flags; ret = file_writable(wimfile); if (ret != 0) return ret; - int write_flags; - if (check) { + if (check) write_flags = WIMLIB_WRITE_FLAG_CHECK_INTEGRITY; - } else { + else write_flags = 0; - } ret = wimlib_overwrite(w, write_flags, 1, imagex_progress_func); @@ -1333,7 +1676,7 @@ static int imagex_info(int argc, const char **argv) ret = 0; } else { printf("The file `%s' was not modified because nothing " - "needed to be done.\n", wimfile); + "needed to be done.\n", wimfile); ret = 0; } } @@ -1343,7 +1686,7 @@ out: } /* Join split WIMs into one part WIM */ -static int imagex_join(int argc, const char **argv) +static int imagex_join(int argc, char **argv) { int c; int swm_open_flags = WIMLIB_OPEN_FLAG_SPLIT_OK; @@ -1369,15 +1712,16 @@ static int imagex_join(int argc, const char **argv) goto err; } output_path = argv[0]; - return wimlib_join(++argv, --argc, output_path, swm_open_flags, - wim_write_flags, imagex_progress_func); + return wimlib_join((const char **)++argv, --argc, output_path, + swm_open_flags, wim_write_flags, + imagex_progress_func); err: usage(JOIN); return -1; } /* Mounts an image using a FUSE mount. */ -static int imagex_mount_rw_or_ro(int argc, const char **argv) +static int imagex_mount_rw_or_ro(int argc, char **argv) { int c; int mount_flags = 0; @@ -1398,6 +1742,9 @@ static int imagex_mount_rw_or_ro(int argc, const char **argv) for_opt(c, mount_options) { switch (c) { + case 'A': + mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER; + break; case 'c': open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY; break; @@ -1422,6 +1769,9 @@ static int imagex_mount_rw_or_ro(int argc, const char **argv) case 'D': staging_dir = optarg; break; + case 'U': + mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA; + break; default: goto mount_usage; } @@ -1469,7 +1819,7 @@ static int imagex_mount_rw_or_ro(int argc, const char **argv) if (mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) { ret = file_writable(wimfile); if (ret != 0) - return ret; + goto out; } ret = wimlib_mount_image(w, image, dir, mount_flags, additional_swms, @@ -1493,7 +1843,8 @@ mount_usage: return -1; } -static int imagex_optimize(int argc, const char **argv) +/* Rebuild a WIM file */ +static int imagex_optimize(int argc, char **argv) { int c; int open_flags = 0; @@ -1564,7 +1915,7 @@ static int imagex_optimize(int argc, const char **argv) } /* Split a WIM into a spanned set */ -static int imagex_split(int argc, const char **argv) +static int imagex_split(int argc, char **argv) { int c; int open_flags = WIMLIB_OPEN_FLAG_SPLIT_OK; @@ -1606,8 +1957,8 @@ static int imagex_split(int argc, const char **argv) return ret; } -/* Unmounts an image. */ -static int imagex_unmount(int argc, const char **argv) +/* Unmounts a mounted WIM image. */ +static int imagex_unmount(int argc, char **argv) { int c; int unmount_flags = 0; @@ -1621,6 +1972,9 @@ static int imagex_unmount(int argc, const char **argv) case 'C': unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY; break; + case 'R': + unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD; + break; default: usage(UNMOUNT); return -1; @@ -1642,7 +1996,7 @@ static int imagex_unmount(int argc, const char **argv) struct imagex_command { const char *name; - int (*func)(int , const char **); + int (*func)(int , char **); int cmd; }; @@ -1670,7 +2024,7 @@ static void version() { static const char *s = "imagex (" PACKAGE ") " PACKAGE_VERSION "\n" - "Copyright (C) 2012 Eric Biggers\n" + "Copyright (C) 2012, 2013 Eric Biggers\n" "License GPLv3+; GNU GPL version 3 or later .\n" "This is free software: you are free to change and redistribute it.\n" "There is NO WARRANTY, to the extent permitted by law.\n" @@ -1680,7 +2034,7 @@ static void version() } -static void help_or_version(int argc, const char **argv) +static void help_or_version(int argc, char **argv) { int i; const char *p; @@ -1739,8 +2093,8 @@ static void usage_all() fputs(extra, stdout); } - -int main(int argc, const char **argv) +/* Entry point for the 'imagex' program. */ +int main(int argc, char **argv) { const struct imagex_command *cmd; int ret; @@ -1751,27 +2105,57 @@ int main(int argc, const char **argv) return 1; } + /* Handle --help and --version for all commands. Note that this will + * not return if either of these arguments are present. */ help_or_version(argc, argv); argc--; argv++; + /* The user may like to see more informative error messages. */ wimlib_set_print_errors(true); + /* Calling wimlib_global_init() is not strictly necessary because + * 'imagex' is single-threaded. */ + ret = wimlib_global_init(); + if (ret) + goto out; + + /* Search for the function to handle the 'imagex' subcommand. */ for_imagex_command(cmd) { if (strcmp(cmd->name, *argv) == 0) { ret = cmd->func(argc, argv); - if (ret > 0) { - imagex_error("Exiting with error code %d:\n" - " %s.", ret, - wimlib_get_error_string(ret)); - if (ret == WIMLIB_ERR_NTFS_3G) - imagex_error_with_errno("errno"); - } - return ret; + goto out_check_write_error; } } imagex_error("Unrecognized command: `%s'", argv[0]); usage_all(); return 1; +out_check_write_error: + /* For 'imagex info' and 'imagex dir', data printed to standard output + * is part of the program's actual behavior and not just for + * informational purposes, so we should set a failure exit status if + * there was a write error. */ + if (cmd == &imagex_commands[INFO] || cmd == &imagex_commands[DIR]) { + if (ferror(stdout) || fclose(stdout)) { + imagex_error_with_errno("output error"); + if (ret == 0) + ret = -1; + } + } +out: + /* Exit status (ret): -1 indicates an error found by 'imagex' outside + * of the wimlib library code. 0 indicates success. > 0 indicates a + * wimlib error code from which an error message can be printed. */ + if (ret > 0) { + imagex_error("Exiting with error code %d:\n" + " %s.", ret, + wimlib_get_error_string(ret)); + if (ret == WIMLIB_ERR_NTFS_3G && errno != 0) + imagex_error_with_errno("errno"); + } + /* Calling wimlib_global_cleanup() is not strictly necessary because the + * process is exiting anyway. */ + wimlib_global_cleanup(); + return ret; }