\fISOURCE\fR specifies the location of the files to create the new WIM image
from. If \fISOURCE\fR is a directory, the WIM image is captured from that
-directory. Alternatively, \fISOURCE\fR is a regular file or block device, it is
-interpreted as a NTFS volume from which a WIM image is to be captured. Still
+directory. Alternatively, if \fISOURCE\fR is a regular file or block device, it
+is interpreted as a NTFS volume from which a WIM image is to be captured. Still
alternatively, if the \fB--source-list\fR option is given, \fISOURCE\fR is
interpreted as a file that itself provides a list of files and directories to
include in the new WIM image.
Yet another capture mode is entered when the \fB--source-list\fR option is
given. It is essentially an extension of the \fBNORMAL MODE\fR that allows
-multiple files or directories to be incorporated into a WIM image in a single
-command. See the documentation for \fB--source-list\fR below.
+multiple files or directories to be incorporated into a WIM image using a single
+\fBimagex capture\fR command. See the documentation for \fB--source-list\fR
+below.
.SH OPTIONS
.TP 6
Filenames containing whitespace may be quoted with either single quotes or
double quotes. Quotes may not be escaped.
-Empty lines, and lines beginning with '#' followed by optional whitespace, are
-ignored.
+Lines consisting only of whitespace and lines beginning with '#' preceded by
+optional whitespace are ignored.
-As a special case, if \fISOURCE\fR is "-" the source list is read from standard
+As a special case, if \fISOURCE\fR is "-", the source list is read from standard
input rather than an external file.
-The NTFS capture mode cannot be used with \fB--source-list\fR.
+The NTFS capture mode cannot be used with \fB--source-list\fR, as only capturing
+a full NTFS volume is supported.
.SH NOTES
\fISOURCE\fR may be a symbolic link to a directory rather than a directory
itself. However, additional symbolic links in subdirectories, or in additional
-source directories not destined for the WIM image root with
-(\fB--source-list\fR), are not dereferenced unless \fB--dereference\fR is
+source directories not destined for the WIM image root (with
+\fB--source-list\fR), are not dereferenced unless \fB--dereference\fR is
specified.
.SH EXAMPLES
.SH SUPPORTED FEATURES
-The following general features are currently supported:
+The following general features are currently supported (note: this is not a
+complete list):
.IP \[bu] 3
Create a stand-alone WIM from a directory or NTFS volume (\fBimagex capture\fR)
.IP \[bu] 4
Because Microsoft designed the WIM file format to accomodate Windows-specific
-and NTFS-specific features, \fBimagex\fR must have two separate image capture
-and application modes (although the \fBimagex\fR subcommands for the modes are
-the same): one for general image capture and application, and one for the
-capture or application of an image specifically from/to an NTFS volume.
+and NTFS-specific features, wimlib must have two separate image capture and
+application modes (although the \fBimagex\fR subcommands for the modes are the
+same): one for general image capture and application, and one for the capture or
+application of an image specifically from/to an NTFS volume.
.IP \[bu]
Microsoft's version has some weird limitations, like it won't let you extract a
.IP \[bu]
There are bugs in Microsoft's WIM library and I obviously have not included the
-same bugs in wimlib, although in some cases I have had to work around some
-bugs for compatibility purposes.
+same bugs in wimlib, although in some cases I have had to work around bugs for
+compatibility purposes.
.IP \[bu]
-wimlib's \fBimagex\fR offers the extra commands \fBimagex optimize\fR and
-\fBimagex join\fR to easily remove holes in a WIM or join the parts of a split
-WIM, respectively.
+wimlib's \fBimagex\fR offers the extra command \fBimagex optimize\fR,
+which lets you easily remove wasted space in a WIM (which can arise after
+a WIM image is appended or mounted read-write).
+
+.IP \[bu]
+wimlib's \fBimagex\fR also offers the command \fBimagex join\fR, which lets you
+easily join the parts of a split WIM.
.IP \[bu]
wimlib's \fBimagex apply\fR supports keeping files hard-linked or symlinked
Microsoft's versions for some reason. I don't know what they have their program
do that takes so long to simply set up a mountpoint.
+.IP \[bu]
+wimlib's \fBimagex mount\fR supports mounting an image from a split WIM, but
+Microsoft's software does not.
+
.SH WARNING
Note: \fBwimlib\fR and \fBimagex\fR are experimental. Use Microsoft's
opts, NULL)) != -1)
enum imagex_op_type {
- APPEND,
+ APPEND = 0,
APPLY,
CAPTURE,
DELETE,
"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'},
return ret;
}
+/* Parse the argument to --compress */
static int get_compression_type(const char *optarg)
{
if (strcasecmp(optarg, "maximum") == 0 || strcasecmp(optarg, "lzx") == 0)
}
}
+/* 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;
"*.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;
PARSE_FILENAME_NONE = 2,
};
-static int parse_filename(char **fn_ret, char **line_p, size_t *len_p)
+/*
+ * 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;
- int ret;
char quote_char;
/* Skip leading whitespace */
len--;
fn = line;
line = memchr(line, quote_char, len);
- if (line) {
- *line = '\0';
- len -= line - fn;
- ret = PARSE_FILENAME_SUCCESS;
- } else {
+ if (!line) {
imagex_error("Missing closing quote: %s", fn - 1);
return PARSE_FILENAME_FAILURE;
}
fn = line;
do {
line++;
- len--;
} while (!isspace(*line) && *line != '\0');
- *line = '\0';
- ret = PARSE_FILENAME_SUCCESS;
}
+ *line = '\0';
+ len -= line - fn;
*len_p = len;
*line_p = line;
*fn_ret = fn;
- return ret;
+ 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(&source->fs_source_path, &line, &len);
+ ret = parse_filename(&line, &len, &source->fs_source_path);
if (ret != PARSE_FILENAME_SUCCESS)
return false;
- ret = parse_filename(&source->wim_target_path, &line, &len);
+ 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 (;;) {
}
}
+/* 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)
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;
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;
#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) {
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)
{
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,
}
-/* Extract one image, or all images, from a WIM file into a directory. */
+/* 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;
return ret;
}
+/* 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;
const char *config_file = NULL;
char *config_str = NULL;
- size_t config_len = 0;
+ size_t config_len;
bool source_list = false;
size_t source_list_nbytes;
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 {
}
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;
return -1;
}
+/* Rebuild a WIM file */
static int imagex_optimize(int argc, char **argv)
{
int c;
return ret;
}
-/* Unmounts an image. */
+/* Unmounts a mounted WIM image. */
static int imagex_unmount(int argc, char **argv)
{
int c;
fputs(extra, stdout);
}
-
+/* Entry point for the 'imagex' program. */
int main(int argc, char **argv)
{
const struct imagex_command *cmd;
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);
usage_all();
return 1;
out:
+ /* 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;
+ }
+ }
+
+ /* 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,
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;
}
/* Parses the contents of the image capture configuration file and fills in a
* `struct capture_config'. */
-static int init_capture_config(const char *_config_str, size_t config_len,
- const char *_prefix, struct capture_config *config)
+static int init_capture_config(struct capture_config *config,
+ const char *_config_str, size_t config_len)
{
char *config_str;
- char *prefix;
char *p;
char *eol;
char *next_p;
ERROR("Could not duplicate capture config string");
return WIMLIB_ERR_NOMEM;
}
- prefix = STRDUP(_prefix);
- if (!prefix) {
- FREE(config_str);
- return WIMLIB_ERR_NOMEM;
- }
memcpy(config_str, _config_str, config_len);
next_p = config_str;
config->config_str = config_str;
- config->prefix = prefix;
- config->prefix_len = strlen(prefix);
while (bytes_remaining) {
line_no++;
p = next_p;
return ret;
}
+static int capture_config_set_prefix(struct capture_config *config,
+ const char *_prefix)
+{
+ char *prefix = STRDUP(_prefix);
+
+ if (!prefix)
+ return WIMLIB_ERR_NOMEM;
+ FREE(config->prefix);
+ config->prefix = prefix;
+ config->prefix_len = strlen(prefix);
+ return 0;
+}
+
static bool match_pattern(const char *path, const char *path_basename,
const struct pattern_list *list)
{
sources->wim_target_path);
sources->wim_target_path =
(char*)canonicalize_target_path(sources->wim_target_path);
- DEBUG("\"%s\"", sources->wim_target_path);
+ DEBUG("Canonical target: \"%s\"", sources->wim_target_path);
sources++;
}
}
static int capture_source_cmp(const void *p1, const void *p2)
{
- const struct wimlib_capture_source *s1, *s2;
-
- s1 = p1;
- s2 = p2;
+ const struct wimlib_capture_source *s1 = p1, *s2 = p2;
return strcmp(s1->wim_target_path, s2->wim_target_path);
}
DEBUG("Creating filler directory \"%s\"", name);
dentry = new_dentry_with_inode(name);
if (dentry) {
+ /* Set the inode number to 0 for now. The final inode number
+ * will be assigned later by assign_inode_numbers(). */
dentry->d_inode->i_ino = 0;
dentry->d_inode->i_resolved = 1;
dentry->d_inode->i_attributes = FILE_ATTRIBUTE_DIRECTORY;
/* Transfers the children of @branch to @target. It is an error if @target is
* not a directory or if both @branch and @target contain a child dentry with
* the same name. */
-static int do_overlay(struct wim_dentry *target,
- struct wim_dentry *branch)
+static int do_overlay(struct wim_dentry *target, struct wim_dentry *branch)
{
struct rb_root *rb_root;
if (!dentry_is_directory(target)) {
ERROR("Cannot overlay directory `%s' over non-directory",
branch->file_name_utf8);
- /* XXX Use a different error code */
- return WIMLIB_ERR_INVALID_DENTRY;
+ return WIMLIB_ERR_INVALID_OVERLAY;
}
rb_root = &branch->d_inode->i_children;
while (rb_root->rb_node) { /* While @branch has children... */
- struct wim_dentry *child;
-
- child = container_of(rb_root->rb_node, struct wim_dentry, rb_node);
+ struct wim_dentry *child = rbnode_dentry(rb_root->rb_node);
+ /* Move @child to the directory @target */
unlink_dentry(child);
if (!dentry_add_child(target, child)) {
+ /* Revert the change to avoid leaking the directory tree
+ * rooted at @child */
dentry_add_child(branch, child);
- ERROR("Overlay error: file `%s' already exists as child of `%s'",
+ ERROR("Overlay error: file `%s' already exists "
+ "as a child of `%s'",
child->file_name_utf8, target->file_name_utf8);
- return WIMLIB_ERR_INVALID_DENTRY;
+ return WIMLIB_ERR_INVALID_OVERLAY;
}
}
return 0;
* @branch
* Branch to add.
* @target_path:
- * Path in the WIM image to add the branch.
+ * Path in the WIM image to add the branch, with leading and trailing
+ * slashes stripped.
*/
static int attach_branch(struct wim_dentry **root_p,
struct wim_dentry *branch,
}
parent = dentry;
target_path = slash;
+ /* Skip over slashes. Note: this cannot overrun the length of
+ * the string because the last character cannot be a slash, as
+ * trailing slashes were tripped. */
do {
++target_path;
- wimlib_assert(*target_path != '\0');
} while (*target_path == '/');
}
const struct capture_config *,
int, wimlib_progress_func_t, void *);
void *extra_arg;
- struct wim_dentry *root_dentry = NULL;
+ struct wim_dentry *root_dentry;
+ struct wim_dentry *branch;
struct wim_security_data *sd;
struct capture_config config;
struct wim_image_metadata *imd;
config_str = default_config;
config_len = strlen(default_config);
}
- memset(&config, 0, sizeof(struct capture_config));
+ ret = init_capture_config(&config, config_str, config_len);
+ if (ret)
+ goto out;
DEBUG("Allocating security data");
-
sd = CALLOC(1, sizeof(struct wim_security_data));
if (!sd) {
ret = WIMLIB_ERR_NOMEM;
- goto out_destroy_config;
+ goto out_destroy_capture_config;
}
sd->total_length = 8;
sd->refcnt = 1;
if (!root_dentry)
goto out_free_security_data;
} else {
- size_t i = 0;
- struct wim_dentry *branch;
- int flags;
+ size_t i;
+
+ root_dentry = NULL;
+ i = 0;
do {
+ int flags;
+ union wimlib_progress_info progress;
+
DEBUG("Building dentry tree for source %zu of %zu "
"(\"%s\" => \"%s\")", i + 1, num_sources,
sources[i].fs_source_path,
sources[i].wim_target_path);
- union wimlib_progress_info progress;
if (progress_func) {
memset(&progress, 0, sizeof(progress));
progress.scan.source = sources[i].fs_source_path;
progress.scan.wim_target_path = sources[i].wim_target_path;
progress_func(WIMLIB_PROGRESS_MSG_SCAN_BEGIN, &progress);
}
- ret = init_capture_config(config_str, config_len,
- sources[i].fs_source_path,
- &config);
+ ret = capture_config_set_prefix(&config,
+ sources[i].fs_source_path);
if (ret)
goto out_free_dentry_tree;
flags = add_image_flags | WIMLIB_ADD_IMAGE_FLAG_SOURCE;
goto out_free_dentry_tree;
}
if (branch) {
+ /* Use the target name, not the source name, for
+ * the root of each branch from a capture
+ * source. (This will also set the root dentry
+ * of the entire image to be unnamed.) */
ret = set_dentry_name(branch,
path_basename(sources[i].wim_target_path));
- if (ret) {
- free_dentry_tree(branch, w->lookup_table);
- goto out_free_dentry_tree;
- }
+ if (ret)
+ goto out_free_branch;
ret = attach_branch(&root_dentry, branch,
sources[i].wim_target_path);
- if (ret) {
- free_dentry_tree(branch, w->lookup_table);
- goto out_free_dentry_tree;
- }
+ if (ret)
+ goto out_free_branch;
}
- destroy_capture_config(&config);
if (progress_func)
progress_func(WIMLIB_PROGRESS_MSG_SCAN_END, &progress);
} while (++i != num_sources);
destroy_image_metadata(&w->image_metadata[w->hdr.image_count - 1],
w->lookup_table);
w->hdr.image_count--;
- return ret;
+ goto out;
+out_free_branch:
+ free_dentry_tree(branch, w->lookup_table);
out_free_dentry_tree:
free_dentry_tree(root_dentry, w->lookup_table);
out_free_security_data:
free_security_data(sd);
-out_destroy_config:
+out_destroy_capture_config:
destroy_capture_config(&config);
out:
return ret;
= "The WIM's integrity table is invalid",
[WIMLIB_ERR_INVALID_LOOKUP_TABLE_ENTRY]
= "An entry in the WIM's lookup table is invalid",
+ [WIMLIB_ERR_INVALID_OVERLAY]
+ = "Conflicting files in overlay when creating a WIM image",
[WIMLIB_ERR_INVALID_PARAM]
= "An invalid parameter was given",
[WIMLIB_ERR_INVALID_PART_NUMBER]
WIMLIB_ERR_UNSUPPORTED,
WIMLIB_ERR_WRITE,
WIMLIB_ERR_XML,
+ WIMLIB_ERR_INVALID_OVERLAY,
};
* specifying the @a sources and @a num_sources parameters instead of the @a
* source parameter. The rest of the parameters are the same as
* wimlib_add_image(). See the documentation for <b>imagex capture</b> for full
- * details on how this mode works. */
+ * details on how this mode works.
+ *
+ * Additional notes: @a sources is not a @c const parameter and you cannot
+ * assume that its contents are valid after this function returns. You must
+ * save pointers to the strings in these structures if you need to free them
+ * later, and/or save copies if needed.
+ *
+ * It is also possible for this function to return ::WIMLIB_ERR_INVALID_OVERLAY
+ * when trying to overlay a non-directory on a directory or when otherwise
+ * trying to overlay multiple conflicting files to the same location in the WIM
+ * image. */
extern int wimlib_add_image_multisource(WIMStruct *w,
struct wimlib_capture_source *sources,
size_t num_sources,
for (size_t j = 0; j < MAX_CHUNKS_PER_MSG; j++) {
msgs[i].compressed_chunks[j] = MALLOC(WIM_CHUNK_SIZE);
- // The extra 8 bytes is because longest_match() in lz.c
- // may read a little bit off the end of the uncompressed
- // data. It doesn't need to be initialized--- we really
- // just need to avoid accessing an unmapped page.
+ // The extra 8 bytes is because longest_match() in
+ // lz77.c may read a little bit off the end of the
+ // uncompressed data. It doesn't need to be
+ // initialized--- we really just need to avoid accessing
+ // an unmapped page.
msgs[i].uncompressed_chunks[j] = MALLOC(WIM_CHUNK_SIZE + 8);
if (msgs[i].compressed_chunks[j] == NULL ||
msgs[i].uncompressed_chunks[j] == NULL)