X-Git-Url: https://wimlib.net/git/?p=wimlib;a=blobdiff_plain;f=programs%2Fimagex.c;h=0f65ae51cbdd1a18ef988fae3ef4f529f5a92717;hp=f07b11833cf0484951de4776c2d690137e2db52a;hb=de12c346dc64404821d52d545e2e1b3d44230f2a;hpb=88f30aa4a33ff4d175cd4749e84d8d373eed795f diff --git a/programs/imagex.c b/programs/imagex.c index f07b1183..0f65ae51 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,9 +26,10 @@ #include "wimlib.h" +#include #include #include -#include + #include #include #include @@ -37,6 +38,17 @@ #include #include #include +#include + +#ifdef HAVE_ALLOCA_H +#include +#endif + +#ifdef __WIN32__ +# include "imagex-win32.h" +#else +# include +#endif #define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0])) @@ -44,7 +56,7 @@ opts, NULL)) != -1) enum imagex_op_type { - APPEND, + APPEND = 0, APPLY, CAPTURE, DELETE, @@ -67,7 +79,8 @@ 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] [--unix-data]\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" @@ -76,7 +89,8 @@ static const char *usage_strings[] = { "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] [--unix-data]\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] = @@ -108,12 +122,6 @@ static const char *usage_strings[] = { "imagex unmount DIRECTORY [--commit] [--check] [--rebuild]\n", }; -static const struct option common_options[] = { - {"help", 0, NULL, 'h'}, - {"version", 0, NULL, 'v'}, - {NULL, 0, NULL, 0}, -}; - static const struct option apply_options[] = { {"check", no_argument, NULL, 'c'}, {"hardlink", no_argument, NULL, 'h'}, @@ -134,6 +142,7 @@ static const struct option capture_or_append_options[] = { {"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[] = { @@ -256,6 +265,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) @@ -271,6 +281,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; @@ -295,23 +307,236 @@ static const char *default_capture_config = "*.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); @@ -326,14 +551,18 @@ 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; @@ -346,6 +575,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) { @@ -359,6 +590,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) { @@ -384,7 +616,13 @@ static int imagex_progress_func(enum wimlib_progress_msg msg, 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) @@ -487,7 +725,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, @@ -499,6 +740,7 @@ static int open_swms_from_glob(const char *swm_glob, glob_t globbuf; int ret; + /* Warning: glob() is replaced in Windows native builds */ ret = glob(swm_glob, GLOB_ERR | GLOB_NOSORT, NULL, &globbuf); if (ret != 0) { if (ret == GLOB_NOMATCH) { @@ -559,8 +801,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; @@ -658,11 +901,17 @@ static int imagex_apply(int argc, const char **argv) } } +#ifdef __WIN32__ + win32_acquire_restore_privileges(); +#endif 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"); +#ifdef __WIN32__ + win32_release_restore_privileges(); +#endif out: wimlib_free(w); if (additional_swms) { @@ -673,28 +922,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': @@ -732,6 +994,9 @@ static int imagex_capture_or_append(int argc, const char **argv) case 'U': add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_UNIX_DATA; break; + case 'S': + source_list = true; + break; default: usage(cmd); return -1; @@ -739,24 +1004,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) @@ -767,40 +1071,47 @@ 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_str : default_capture_config), - (config_str ? config_len : strlen(default_capture_config)), - add_image_flags, imagex_progress_func); - +#ifdef __WIN32__ + win32_acquire_capture_privileges(); +#endif + + 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; + goto out_release_privs; cur_image = wimlib_get_num_images(w); if (desc) { ret = wimlib_set_image_descripton(w, cur_image, desc); if (ret != 0) - goto out; + goto out_release_privs; } if (flags_element) { ret = wimlib_set_image_flags(w, cur_image, flags_element); if (ret != 0) - goto out; + goto out_release_privs; } if (cmd == APPEND) { ret = wimlib_overwrite(w, write_flags, num_threads, @@ -813,14 +1124,21 @@ static int imagex_capture_or_append(int argc, const char **argv) ret = 0; if (ret != 0) imagex_error("Failed to write the WIM file `%s'", wimfile); +out_release_privs: +#ifdef __WIN32__ + win32_release_capture_privileges(); +#endif 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; @@ -893,7 +1211,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; @@ -945,7 +1263,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; @@ -1025,9 +1343,10 @@ static int imagex_export(int argc, const char **argv) wim_is_new = false; /* Destination file exists. */ - if (!S_ISREG(stbuf.st_mode) && !S_ISLNK(stbuf.st_mode)) { + + if (!S_ISREG(stbuf.st_mode)) { imagex_error("`%s' is not a regular file", - dest_wimfile); + dest_wimfile); ret = -1; goto out; } @@ -1109,7 +1428,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; @@ -1389,7 +1708,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; @@ -1415,15 +1734,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; @@ -1545,7 +1865,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; @@ -1616,7 +1937,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; @@ -1658,8 +1979,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; @@ -1697,7 +2018,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; }; @@ -1725,7 +2046,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" @@ -1735,7 +2056,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; @@ -1794,38 +2115,61 @@ 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; + setlocale(LC_ALL, ""); + if (argc < 2) { imagex_error("No command specified"); usage_all(); 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); + + /* Do any initializations that the library needs */ 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); - goto out; + 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, @@ -1833,6 +2177,9 @@ out: if (ret == WIMLIB_ERR_NTFS_3G && errno != 0) imagex_error_with_errno("errno"); } + + /* Make the library free any resources it's holding (not strictly + * necessary because the process is ending anyway). */ wimlib_global_cleanup(); return ret; }