]> wimlib.net Git - wimlib/blobdiff - programs/imagex.c
Refactor headers
[wimlib] / programs / imagex.c
index 4735f1aa2dd6614e0a0998dfdca27c19ab83c886..b45d2d4b99232e0c2d1ba4daf3445318d58967e5 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "config.h"
+#ifdef HAVE_CONFIG_H
+#  include "config.h" /* Need for PACKAGE_VERSION, etc. */
+#endif
+
 #include "wimlib.h"
 #include "wimlib_tchar.h"
 
@@ -40,7 +43,7 @@
 #include <locale.h>
 
 #ifdef HAVE_ALLOCA_H
-#include <alloca.h>
+#  include <alloca.h>
 #endif
 
 #ifdef __WIN32__
@@ -80,7 +83,7 @@ enum imagex_op_type {
 };
 
 static void usage(int cmd_type);
-static void usage_all();
+static void usage_all(void);
 
 static bool imagex_be_quiet = false;
 
@@ -169,17 +172,27 @@ IMAGEX_PROGNAME" unmount DIRECTORY [--commit] [--check] [--rebuild]\n"
 ),
 [UPDATE] =
 T(
-IMAGEX_PROGNAME" update WIMFILE IMAGE [--check] [--rebuild]\n"
-"                      [--threads=NUM_THREADS] [DEFAULT_ADD_OPTIONS]\n"
-"                      [DEFAULT_DELETE_OPTIONS] [< CMDFILE]\n"
-"               ... where each CMD is:\n"
-"               add [--unix-data] [--no-acls] [--strict-acls] [--dereference]\n"
-"                   [--verbose] FILE_OR_DIRECTORY DEST_WIM_PATH\n"
-"               delete [--force] [--recursive] WIM_PATH\n"
-"               rename SRC_PATH_IN_WIM DEST_PATH_IN_WIM\n"
+IMAGEX_PROGNAME" update WIMFILE [IMAGE_NUM | IMAGE_NAME] [--check] [--rebuild]\n"
+"                       [--threads=NUM_THREADS] [DEFAULT_ADD_OPTIONS]\n"
+"                       [DEFAULT_DELETE_OPTIONS] < CMDFILE\n"
 ),
 };
 
+
+static void
+recommend_man_page(const tchar *cmd_name)
+{
+       const tchar *format_str;
+#ifdef __WIN32__
+       format_str = T("See "IMAGEX_PROGNAME"%"TS"%"TS".pdf in the "
+                      "doc directory for more details.\n");
+#else
+       format_str = T("Try `man "IMAGEX_PROGNAME"%"TS"%"TS"' "
+                      "for more details.\n");
+#endif
+       tprintf(format_str, *cmd_name ? T("-") : T(""), cmd_name);
+}
+
 enum {
        IMAGEX_ALLOW_OTHER_OPTION,
        IMAGEX_BOOT_OPTION,
@@ -326,12 +339,23 @@ static const struct option unmount_options[] = {
 };
 
 static const struct option update_options[] = {
+       /* Careful: some of the options here set the defaults for update
+        * commands, but the flags given to an actual update command (and not to
+        * `imagex update' itself are also handled in
+        * update_command_add_option().  */
        {T("threads"),     required_argument, NULL, IMAGEX_THREADS_OPTION},
        {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
        {T("rebuild"),     no_argument,       NULL, IMAGEX_REBUILD_OPTION},
+
+       /* Default delete options */
        {T("force"),       no_argument,       NULL, IMAGEX_FORCE_OPTION},
        {T("recursive"),   no_argument,       NULL, IMAGEX_RECURSIVE_OPTION},
+
+       /* Global add option */
        {T("config"),      required_argument, NULL, IMAGEX_CONFIG_OPTION},
+
+       /* Default add options */
+       {T("verbose"),     no_argument,       NULL, IMAGEX_VERBOSE_OPTION},
        {T("dereference"), no_argument,       NULL, IMAGEX_DEREFERENCE_OPTION},
        {T("unix-data"),   no_argument,       NULL, IMAGEX_UNIX_DATA_OPTION},
        {T("noacls"),      no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
@@ -454,36 +478,35 @@ static struct wimlib_capture_config default_capture_config = {
 };
 
 enum {
-       PARSE_FILENAME_SUCCESS = 0,
-       PARSE_FILENAME_FAILURE = 1,
-       PARSE_FILENAME_NONE = 2,
+       PARSE_STRING_SUCCESS = 0,
+       PARSE_STRING_FAILURE = 1,
+       PARSE_STRING_NONE = 2,
 };
 
 /*
- * Parses a filename in the source list file format.  (See the man page for
- * 'wimlib-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.
+ * Parses a string token from an array of characters.
+ *
+ * Tokens are either whitespace-delimited, or double or single-quoted.
  *
  * @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
+ *           to point past the string token iff the return value is
+ *           PARSE_STRING_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.
+ *           the line iff the return value is PARSE_STRING_SUCCESS.
  *
- * @fn_ret:  Iff the return value is PARSE_FILENAME_SUCCESS, a pointer to the
- *           parsed filename will be returned here.
+ * @fn_ret:  Iff the return value is PARSE_STRING_SUCCESS, a pointer to the
+ *           parsed string token 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.
+ * Returns: PARSE_STRING_SUCCESS if a string token was successfully parsed; or
+ *          PARSE_STRING_FAILURE if the data was invalid due to a missing
+ *          closing quote; or PARSE_STRING_NONE if the line ended before the
+ *          beginning of a string token was found.
  */
 static int
-parse_filename(tchar **line_p, size_t *len_p, tchar **fn_ret)
+parse_string(tchar **line_p, size_t *len_p, tchar **fn_ret)
 {
        size_t len = *len_p;
        tchar *line = *line_p;
@@ -493,7 +516,7 @@ parse_filename(tchar **line_p, size_t *len_p, tchar **fn_ret)
        /* Skip leading whitespace */
        for (;;) {
                if (len == 0)
-                       return PARSE_FILENAME_NONE;
+                       return PARSE_STRING_NONE;
                if (!istspace(*line) && *line != T('\0'))
                        break;
                line++;
@@ -501,17 +524,17 @@ parse_filename(tchar **line_p, size_t *len_p, tchar **fn_ret)
        }
        quote_char = *line;
        if (quote_char == T('"') || quote_char == T('\'')) {
-               /* Quoted filename */
+               /* Quoted string */
                line++;
                len--;
                fn = line;
                line = tmemchr(line, quote_char, len);
                if (!line) {
                        imagex_error(T("Missing closing quote: %"TS), fn - 1);
-                       return PARSE_FILENAME_FAILURE;
+                       return PARSE_STRING_FAILURE;
                }
        } else {
-               /* Unquoted filename.  Go until whitespace.  Line is terminated
+               /* Unquoted string.  Go until whitespace.  Line is terminated
                 * by '\0', so no need to check 'len'. */
                fn = line;
                do {
@@ -523,7 +546,7 @@ parse_filename(tchar **line_p, size_t *len_p, tchar **fn_ret)
        *len_p = len;
        *line_p = line;
        *fn_ret = fn;
-       return PARSE_FILENAME_SUCCESS;
+       return PARSE_STRING_SUCCESS;
 }
 
 /* Parses a line of data (not an empty line or comment) in the source list file
@@ -546,13 +569,13 @@ parse_source_list_line(tchar *line, size_t len,
 {
        /* SOURCE [DEST] */
        int ret;
-       ret = parse_filename(&line, &len, &source->fs_source_path);
-       if (ret != PARSE_FILENAME_SUCCESS)
+       ret = parse_string(&line, &len, &source->fs_source_path);
+       if (ret != PARSE_STRING_SUCCESS)
                return false;
-       ret = parse_filename(&line, &len, &source->wim_target_path);
-       if (ret == PARSE_FILENAME_NONE)
+       ret = parse_string(&line, &len, &source->wim_target_path);
+       if (ret == PARSE_STRING_NONE)
                source->wim_target_path = source->fs_source_path;
-       return ret != PARSE_FILENAME_FAILURE;
+       return ret != PARSE_STRING_FAILURE;
 }
 
 /* Returns %true if the given line of length @len > 0 is a comment or empty line
@@ -752,12 +775,12 @@ parse_capture_config_line(tchar *line, size_t len,
                               "(such as [ExclusionList]"), line);
                return false;
        case CAPTURE_CONFIG_EXCLUSION_SECTION:
-               if (parse_filename(&line, &len, &filename) != PARSE_FILENAME_SUCCESS)
+               if (parse_string(&line, &len, &filename) != PARSE_STRING_SUCCESS)
                        return false;
                return pattern_list_add_pattern(&config->exclusion_pats,
                                                filename);
        case CAPTURE_CONFIG_EXCLUSION_EXCEPTION_SECTION:
-               if (parse_filename(&line, &len, &filename) != PARSE_FILENAME_SUCCESS)
+               if (parse_string(&line, &len, &filename) != PARSE_STRING_SUCCESS)
                        return false;
                return pattern_list_add_pattern(&config->exclusion_exception_pats,
                                                filename);
@@ -882,8 +905,7 @@ stdin_get_contents(size_t *len_ret)
 
 
 static tchar *
-translate_text_to_tstr(char *text, size_t num_bytes,
-                      size_t *num_tchars_ret)
+translate_text_to_tstr(char *text, size_t num_bytes, size_t *num_tchars_ret)
 {
 #ifndef __WIN32__
        /* On non-Windows, assume an ASCII-compatible encoding, such as UTF-8.
@@ -1202,6 +1224,19 @@ parse_num_threads(const tchar *optarg)
        }
 }
 
+/*
+ * Parse an option passed to an update command.
+ *
+ * @op:                One of WIMLIB_UPDATE_OP_* that indicates the command being
+ *             parsed.
+ *
+ * @option:    Text string for the option (beginning with --)
+ *
+ * @cmd:       `struct wimlib_update_command' that is being constructed for
+ *             this command.
+ *
+ * Returns true if the option was recognized; false if not.
+ */
 static bool
 update_command_add_option(int op, const tchar *option,
                          struct wimlib_update_command *cmd)
@@ -1209,7 +1244,9 @@ update_command_add_option(int op, const tchar *option,
        bool recognized = true;
        switch (op) {
        case WIMLIB_UPDATE_OP_ADD:
-               if (!tstrcmp(option, T("--unix-data")))
+               if (!tstrcmp(option, T("--verbose")))
+                       cmd->add.add_flags |= WIMLIB_ADD_IMAGE_FLAG_VERBOSE;
+               else if (!tstrcmp(option, T("--unix-data")))
                        cmd->add.add_flags |= WIMLIB_ADD_IMAGE_FLAG_UNIX_DATA;
                else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
                        cmd->add.add_flags |= WIMLIB_ADD_IMAGE_FLAG_NO_ACLS;
@@ -1228,20 +1265,21 @@ update_command_add_option(int op, const tchar *option,
                else
                        recognized = false;
                break;
-       case WIMLIB_UPDATE_OP_RENAME:
+       default:
                recognized = false;
                break;
        }
        return recognized;
 }
 
+/* How many nonoption arguments each `imagex update' command expects */
 static const unsigned update_command_num_nonoptions[] = {
        [WIMLIB_UPDATE_OP_ADD] = 2,
        [WIMLIB_UPDATE_OP_DELETE] = 1,
        [WIMLIB_UPDATE_OP_RENAME] = 2,
 };
 
-static bool
+static void
 update_command_add_nonoption(int op, const tchar *nonoption,
                             struct wimlib_update_command *cmd,
                             unsigned num_nonoptions)
@@ -1263,9 +1301,22 @@ update_command_add_nonoption(int op, const tchar *nonoption,
                        cmd->rename.wim_target_path = (tchar*)nonoption;
                break;
        }
-       return true;
 }
 
+/*
+ * Parse a command passed on stdin to `imagex update'.
+ *
+ * @line:      Text of the command.
+ * @len:       Length of the line, including a null terminator
+ *             at line[len - 1].
+ *
+ * @command:   A `struct wimlib_update_command' to fill in from the parsed
+ *             line.
+ *
+ * @line_number: Line number of the command, for diagnostics.
+ *
+ * Returns true on success; returns false on parse error.
+ */
 static bool
 parse_update_command(tchar *line, size_t len,
                     struct wimlib_update_command *command,
@@ -1276,8 +1327,9 @@ parse_update_command(tchar *line, size_t len,
        int op;
        size_t num_nonoptions;
 
-       ret = parse_filename(&line, &len, &command_name);
-       if (ret != PARSE_FILENAME_SUCCESS)
+       /* Get the command name ("add", "delete", "rename") */
+       ret = parse_string(&line, &len, &command_name);
+       if (ret != PARSE_STRING_SUCCESS)
                return false;
 
        if (!tstrcasecmp(command_name, T("add"))) {
@@ -1292,25 +1344,29 @@ parse_update_command(tchar *line, size_t len,
                return false;
        }
        command->op = op;
+
+       /* Parse additional options and non-options as needed */
        num_nonoptions = 0;
        for (;;) {
                tchar *next_string;
 
-               ret = parse_filename(&line, &len, &next_string);
-               if (ret == PARSE_FILENAME_NONE)
+               ret = parse_string(&line, &len, &next_string);
+               if (ret == PARSE_STRING_NONE) /* End of line */
                        break;
-               else if (ret != PARSE_FILENAME_SUCCESS)
+               else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
                        return false;
                if (next_string[0] == T('-') && next_string[1] == T('-')) {
+                       /* Option */
                        if (!update_command_add_option(op, next_string, command))
                        {
                                imagex_error(T("Unrecognized option \"%"TS"\" to "
-                                              "update command on line %zu"),
-                                            next_string, line_number);
+                                              "update command \"%"TS"\" on line %zu"),
+                                            next_string, command_name, line_number);
 
                                return false;
                        }
                } else {
+                       /* Nonoption */
                        if (num_nonoptions == update_command_num_nonoptions[op])
                        {
                                imagex_error(T("Unexpected argument \"%"TS"\" in "
@@ -1321,9 +1377,8 @@ parse_update_command(tchar *line, size_t len,
                                             command_name, num_nonoptions);
                                return false;
                        }
-                       if (!update_command_add_nonoption(op, next_string,
-                                                         command, num_nonoptions))
-                               return false;
+                       update_command_add_nonoption(op, next_string,
+                                                    command, num_nonoptions);
                        num_nonoptions++;
                }
        }
@@ -2073,18 +2128,19 @@ free_extract_commands(struct wimlib_extract_command *cmds, size_t num_cmds,
 }
 
 static struct wimlib_extract_command *
-prepare_extract_commands(tchar **argv, int argc, int extract_flags,
-                        tchar *dest_dir, size_t *num_cmds_ret)
+prepare_extract_commands(tchar **paths, unsigned num_paths,
+                        int extract_flags, tchar *dest_dir,
+                        size_t *num_cmds_ret)
 {
        struct wimlib_extract_command *cmds;
        size_t num_cmds;
        tchar *emptystr = T("");
 
-       if (argc == 0) {
-               argc = 1;
-               argv = &emptystr;
+       if (num_paths == 0) {
+               num_paths = 1;
+               paths = &emptystr;
        }
-       num_cmds = argc;
+       num_cmds = num_paths;
        cmds = calloc(num_cmds, sizeof(cmds[0]));
        if (!cmds) {
                imagex_error(T("Out of memory!"));
@@ -2093,18 +2149,18 @@ prepare_extract_commands(tchar **argv, int argc, int extract_flags,
 
        for (size_t i = 0; i < num_cmds; i++) {
                cmds[i].extract_flags = extract_flags;
-               cmds[i].wim_source_path = argv[i];
-               if (is_root_wim_path(argv[i])) {
+               cmds[i].wim_source_path = paths[i];
+               if (is_root_wim_path(paths[i])) {
                        cmds[i].fs_dest_path = dest_dir;
                } else {
-                       size_t len = tstrlen(dest_dir) + 1 + tstrlen(argv[i]);
+                       size_t len = tstrlen(dest_dir) + 1 + tstrlen(paths[i]);
                        cmds[i].fs_dest_path = malloc((len + 1) * sizeof(tchar));
                        if (!cmds[i].fs_dest_path) {
                                free_extract_commands(cmds, num_cmds, dest_dir);
                                return NULL;
                        }
                        tsprintf(cmds[i].fs_dest_path, T("%"TS"/%"TS), dest_dir,
-                                tbasename(argv[i]));
+                                tbasename(paths[i]));
                }
        }
        *num_cmds_ret = num_cmds;
@@ -2160,19 +2216,15 @@ imagex_extract(int argc, tchar **argv)
                        imagex_be_quiet = true;
                        break;
                default:
-                       usage(EXTRACT);
-                       ret = -1;
-                       goto out;
+                       goto out_usage;
                }
        }
        argc -= optind;
        argv += optind;
 
-       if (argc < 2) {
-               usage(EXTRACT);
-               ret = -1;
-               goto out;
-       }
+       if (argc < 2)
+               goto out_usage;
+
        wimfile = argv[0];
        image_num_or_name = argv[1];
 
@@ -2209,7 +2261,7 @@ imagex_extract(int argc, tchar **argv)
        win32_acquire_restore_privileges();
 #endif
 
-       ret = wimlib_extract_files(wim, image, 0, cmds, num_cmds,
+       ret = wimlib_extract_files(wim, image, cmds, num_cmds, 0,
                                   additional_swms, num_additional_swms,
                                   imagex_progress_func);
        if (ret == 0) {
@@ -2234,6 +2286,10 @@ out_free_cmds:
        free_extract_commands(cmds, num_cmds, dest_dir);
 out:
        return ret;
+out_usage:
+       usage(EXTRACT);
+       ret = -1;
+       goto out;
 }
 
 /* Prints information about a WIM file; also can mark an image as bootable,
@@ -2847,6 +2903,9 @@ imagex_unmount(int argc, tchar **argv)
        return ret;
 }
 
+/*
+ * Add, delete, or rename files in a WIM image.
+ */
 static int
 imagex_update(int argc, tchar **argv)
 {
@@ -2856,9 +2915,9 @@ imagex_update(int argc, tchar **argv)
        WIMStruct *wim;
        int ret;
        int open_flags = 0;
-       int write_flags = WIMLIB_WRITE_FLAG_SOFT_DELETE;
+       int write_flags = 0;
        int update_flags = 0;
-       int default_add_flags = 0;
+       int default_add_flags = WIMLIB_ADD_IMAGE_FLAG_EXCLUDE_VERBOSE;
        int default_delete_flags = 0;
        unsigned num_threads = 0;
        int c;
@@ -2873,6 +2932,7 @@ imagex_update(int argc, tchar **argv)
 
        for_opt(c, update_options) {
                switch (c) {
+               /* Generic or write options */
                case IMAGEX_THREADS_OPTION:
                        num_threads = parse_num_threads(optarg);
                        if (num_threads == UINT_MAX) {
@@ -2887,15 +2947,24 @@ imagex_update(int argc, tchar **argv)
                case IMAGEX_REBUILD_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
                        break;
+
+               /* Default delete options */
                case IMAGEX_FORCE_OPTION:
                        default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
                        break;
                case IMAGEX_RECURSIVE_OPTION:
                        default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
                        break;
+
+               /* Global add option */
                case IMAGEX_CONFIG_OPTION:
                        config_file = optarg;
                        break;
+
+               /* Default add options */
+               case IMAGEX_VERBOSE_OPTION:
+                       default_add_flags |= WIMLIB_ADD_IMAGE_FLAG_VERBOSE;
+                       break;
                case IMAGEX_DEREFERENCE_OPTION:
                        default_add_flags |= WIMLIB_ADD_IMAGE_FLAG_DEREFERENCE;
                        break;
@@ -2934,6 +3003,7 @@ imagex_update(int argc, tchar **argv)
        if (ret)
                goto out_wimlib_free;
 
+       /* Parse capture configuration file if specified */
        if (config_file) {
                size_t config_len;
 
@@ -2951,12 +3021,18 @@ imagex_update(int argc, tchar **argv)
                config = &default_capture_config;
        }
 
+       /* Read update commands from standard input */
+       if (isatty(STDIN_FILENO)) {
+               tputs(T("Reading update commands from standard input..."));
+               recommend_man_page(T("update"));
+       }
        cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
        if (!cmd_file_contents) {
                ret = -1;
                goto out_free_config;
        }
 
+       /* Parse the update commands */
        cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
                                         &num_cmds);
        if (!cmds) {
@@ -2964,6 +3040,7 @@ imagex_update(int argc, tchar **argv)
                goto out_free_cmd_file_contents;
        }
 
+       /* Set default flags and capture config on the update commands */
        for (size_t i = 0; i < num_cmds; i++) {
                switch (cmds[i].op) {
                case WIMLIB_UPDATE_OP_ADD:
@@ -2978,11 +3055,13 @@ imagex_update(int argc, tchar **argv)
                }
        }
 
+       /* Execute the update commands */
        ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags,
                                  imagex_progress_func);
        if (ret)
                goto out_free_cmds;
 
+       /* Overwrite the updated WIM */
        ret = wimlib_overwrite(wim, write_flags, num_threads,
                               imagex_progress_func);
 out_free_cmds:
@@ -3034,7 +3113,7 @@ static const struct imagex_command imagex_commands[] = {
 };
 
 static void
-version()
+version(void)
 {
        static const tchar *s =
        T(
@@ -3090,14 +3169,14 @@ usage(int cmd_type)
        tprintf(T("Usage:\n%"TS), usage_strings[cmd_type]);
        for_imagex_command(cmd) {
                if (cmd->cmd == cmd_type) {
-                       tprintf(T("\nTry `man "IMAGEX_PROGNAME"-%"TS"' "
-                                 "for more details.\n"), cmd->name);
+                       tputc(T('\n'), stdout);
+                       recommend_man_page(cmd->name);
                }
        }
 }
 
 static void
-usage_all()
+usage_all(void)
 {
        tfputs(T("Usage:\n"), stdout);
        for (int i = 0; i < ARRAY_LEN(usage_strings); i++)
@@ -3109,9 +3188,9 @@ usage_all()
 "\n"
 "    The compression TYPE may be \"maximum\", \"fast\", or \"none\".\n"
 "\n"
-"    Try `man "IMAGEX_PROGNAME"' for more information.\n"
        );
        tfputs(extra, stdout);
+       recommend_man_page(T(""));
 }
 
 /* Entry point for wimlib's ImageX implementation.  On UNIX the command