You may also specify the actual names of the compression algorithms, "XPRESS"
and "LZX", instead of "fast" and "maximum", respectively.
.TP
+\fB--threads\fR=\fINUM_THREADS\fR
+Number of threads to use for compressing data. Default: autodetect (number of
+processors). Note: if creating or appending to an uncompressed WIM, additional
+threads will not be used, regardless of this parameter, since no compression
+needs to be done in this case.
+.TP
\fB--flags\fR=\fIEDITIONID\fR
Specify a string to use in the <FLAGS> element of the XML data for the new
image.
You may also specify the actual names of the compression algorithms, "XPRESS"
and "LZX", instead of "fast" and "maximum", respectively.
-
+.TP
+\fB--threads\fR=\fINUM_THREADS\fR
+Number of threads to use for compressing data. Default: autodetect (number of
+processors). Note: if exporting to an uncompressed WIM, or exporting to a WIM
+with the same compression type as the source WIM, additional threads will not
+be used, regardless of this parameter, since no data compression needs to be
+done in these cases.
.TP
\fB--ref\fR="\fIGLOB\fR"
File glob of additional split WIM parts that are part of the split WIM being
#include <string.h>
#include <errno.h>
#include <libgen.h>
+#include <limits.h>
#include <sys/stat.h>
#include <unistd.h>
[APPEND] =
"imagex append (DIRECTORY | NTFS_VOLUME) WIMFILE [IMAGE_NAME]\n"
" [DESCRIPTION] [--boot] [--check] [--flags EDITION_ID]\n"
-" [--verbose] [--dereference] [--config=FILE]\n",
+" [--verbose] [--dereference] [--config=FILE]\n"
+" [--threads=NUM_THREADS]\n",
[APPLY] =
"imagex apply WIMFILE [IMAGE_NUM | IMAGE_NAME | all]\n"
" (DIRECTORY | NTFS_VOLUME) [--check] [--hardlink]\n"
"imagex capture (DIRECTORY | NTFS_VOLUME) WIMFILE [IMAGE_NAME]\n"
" [DESCRIPTION] [--boot] [--check] [--compress=TYPE]\n"
" [--flags EDITION_ID] [--verbose] [--dereference]\n"
-" [--config=FILE]\n",
+" [--config=FILE] [--threads=NUM_THREADS]\n",
[DELETE] =
"imagex delete WIMFILE (IMAGE_NUM | IMAGE_NAME | all) [--check]\n",
[DIR] =
"imagex dir WIMFILE (IMAGE_NUM | IMAGE_NAME | all)\n",
[EXPORT] =
"imagex export SRC_WIMFILE (SRC_IMAGE_NUM | SRC_IMAGE_NAME | all ) \n"
-" DEST_WIMFILE [DEST_IMAGE_NAME]\n"
-" [DEST_IMAGE_DESCRIPTION] [--boot] [--check]\n"
-" [--compress=TYPE] [--ref=\"GLOB\"]\n",
+" DEST_WIMFILE [DEST_IMAGE_NAME] [DEST_IMAGE_DESCRIPTION]\n"
+" [--boot] [--check] [--compress=TYPE] [--ref=\"GLOB\"]\n"
+" [--threads=NUM_THREADS]\n",
[INFO] =
"imagex info WIMFILE [IMAGE_NUM | IMAGE_NAME] [NEW_NAME]\n"
" [NEW_DESC] [--boot] [--check] [--header] [--lookup-table]\n"
{"dereference", no_argument, NULL, 'L'},
{"flags", required_argument, NULL, 'f'},
{"verbose", no_argument, NULL, 'v'},
+ {"threads", required_argument, NULL, 't'},
{NULL, 0, NULL, 0},
};
static const struct option delete_options[] = {
{"check", no_argument, NULL, 'c'},
{"compress", required_argument, NULL, 'x'},
{"ref", required_argument, NULL, 'r'},
+ {"threads", required_argument, NULL, 't'},
{NULL, 0, NULL, 0},
};
}
+static unsigned parse_num_threads(const char *optarg)
+{
+ char *tmp;
+ unsigned nthreads = strtoul(optarg, &tmp, 10);
+ if (nthreads == UINT_MAX || *tmp || tmp == optarg) {
+ imagex_error("Number of threads must be a non-negative integer!");
+ return UINT_MAX;
+ } else {
+ return nthreads;
+ }
+}
+
+
/* Extract one image, or all images, from a WIM file into a directory. */
static int imagex_apply(int argc, const char **argv)
{
{
int c;
int open_flags = WIMLIB_OPEN_FLAG_SHOW_PROGRESS;
- int add_image_flags = 0;
+ int add_image_flags = WIMLIB_ADD_IMAGE_FLAG_SHOW_PROGRESS;
int write_flags = WIMLIB_WRITE_FLAG_SHOW_PROGRESS;
int compression_type = WIM_COMPRESSION_TYPE_XPRESS;
const char *dir;
int cur_image;
char *default_name;
int cmd = strcmp(argv[0], "append") ? CAPTURE : APPEND;
+ unsigned num_threads = 0;
for_opt(c, capture_or_append_options) {
switch (c) {
add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_VERBOSE;
write_flags |= WIMLIB_WRITE_FLAG_VERBOSE;
break;
+ case 't':
+ num_threads = parse_num_threads(optarg);
+ if (num_threads == UINT_MAX)
+ return -1;
+ break;
default:
usage(cmd);
return -1;
if (ret != 0)
goto out;
}
- if (cmd == APPEND)
- ret = wimlib_overwrite(w, write_flags);
- else
- ret = wimlib_write(w, wimfile, WIM_ALL_IMAGES, write_flags);
+ if (cmd == APPEND) {
+ ret = wimlib_overwrite(w, write_flags, num_threads);
+ } else {
+ ret = wimlib_write(w, wimfile, WIM_ALL_IMAGES, write_flags,
+ num_threads);
+ }
if (ret != 0)
imagex_error("Failed to write the WIM file `%s'", wimfile);
out:
goto out;
}
- ret = wimlib_overwrite(w, write_flags);
+ ret = wimlib_overwrite(w, write_flags, 0);
if (ret != 0) {
imagex_error("Failed to write the file `%s' with image "
"deleted", wimfile);
const char *swm_glob = NULL;
WIMStruct **additional_swms = NULL;
unsigned num_additional_swms = 0;
+ unsigned num_threads = 0;
for_opt(c, export_options) {
switch (c) {
case 'r':
swm_glob = optarg;
break;
+ case 't':
+ num_threads = parse_num_threads(optarg);
+ if (num_threads == UINT_MAX)
+ return -1;
+ break;
default:
usage(EXPORT);
return -1;
if (wim_is_new)
ret = wimlib_write(dest_w, dest_wimfile, WIM_ALL_IMAGES,
- write_flags);
+ write_flags, num_threads);
else
- ret = wimlib_overwrite(dest_w, write_flags);
+ ret = wimlib_overwrite(dest_w, write_flags, num_threads);
out:
wimlib_free(src_w);
wimlib_free(dest_w);
return (dentry_correct_length_unaligned(dentry) + 7) & ~7;
}
-/* Return %true iff @dentry has the UTF-8 file name @name that has length
- * @name_len bytes. */
-static bool dentry_has_name(const struct dentry *dentry, const char *name,
- size_t name_len)
-{
- if (dentry->file_name_utf8_len != name_len)
- return false;
- return memcmp(dentry->file_name_utf8, name, name_len) == 0;
-}
-
/* Return %true iff the alternate data stream entry @entry has the UTF-8 stream
* name @name that has length @name_len bytes. */
static inline bool ads_entry_has_name(const struct ads_entry *entry,
sd->refcnt = 1;
DEBUG("Building dentry tree.");
+ if (flags & WIMLIB_ADD_IMAGE_FLAG_SHOW_PROGRESS) {
+ printf("Scanning `%s'...\n", dir);
+ }
ret = (*capture_tree)(&root_dentry, dir, w->lookup_table, sd,
&config, flags | WIMLIB_ADD_IMAGE_FLAG_ROOT,
extra_arg);
xml_update_image_info(w, w->current_image);
- ret = wimlib_overwrite(w, check_integrity);
+ ret = wimlib_overwrite(w, check_integrity, 0);
if (ret != 0) {
ERROR("Failed to commit changes");
return ret;
/** Include an integrity table in the new WIM file. */
#define WIMLIB_WRITE_FLAG_CHECK_INTEGRITY 0x00000001
-/** Print progress information when writing the integrity table. */
+/** Print progress information when writing streams and when writing the
+ * integrity table. */
#define WIMLIB_WRITE_FLAG_SHOW_PROGRESS 0x00000002
/** Print file paths as we write then */
/** Follow symlinks; archive and dump the files they point to. */
#define WIMLIB_ADD_IMAGE_FLAG_DEREFERENCE 0x00000004
+/** Show progress information when scanning a directory tree */
+#define WIMLIB_ADD_IMAGE_FLAG_SHOW_PROGRESS 0x00000004
+
/** See documentation for wimlib_export_image(). */
#define WIMLIB_EXPORT_FLAG_BOOT 0x00000001
* printed as it is scanned or captured. If
* ::WIMLIB_ADD_IMAGE_FLAG_DEREFERENCE is specified, the files or
* directories pointed to by symbolic links are archived rather than the
- * symbolic links themselves.
+ * symbolic links themselves. If ::WIMLIB_ADD_IMAGE_FLAG_SHOW_PROGRESS is
+ * specified, progress information will be printed (distinct from the
+ * verbose information).
*
* @return 0 on success; nonzero on error. On error, changes to @a wim are
* discarded so that it appears to be in the same state as when this function
* Pointer to the ::WIMStruct for the WIM file to write. There may have
* been in-memory changes made to it, which are then reflected in the
* output file.
- * @param flags
+ * @param write_flags
* Bitwise OR of ::WIMLIB_WRITE_FLAG_CHECK_INTEGRITY and/or
* ::WIMLIB_WRITE_FLAG_SHOW_PROGRESS.
+ * @param num_threads
+ * Number of threads to use for compression (see wimlib_write()).
*
* @return 0 on success; nonzero on error. This function may return any value
* returned by wimlib_write() as well as the following error codes:
* The temporary file that the WIM was written to could not be renamed to
* the original filename of @a wim.
*/
-extern int wimlib_overwrite(WIMStruct *wim, int flags);
+extern int wimlib_overwrite(WIMStruct *wim, int write_flags,
+ unsigned num_threads);
/**
* Updates the header and XML data of the WIM file, without the need to write
*
* @param wim
* Pointer to the ::WIMStruct for the WIM file to overwrite.
- * @param flags
+ * @param write_flags
* Bitwise OR of ::WIMLIB_WRITE_FLAG_CHECK_INTEGRITY and/or
* ::WIMLIB_WRITE_FLAG_SHOW_PROGRESS.
*
* Failed to write the WIM header, the XML data, or the integrity table to
* the WIM file associated with @a wim.
*/
-extern int wimlib_overwrite_xml_and_header(WIMStruct *wim, int flags);
+extern int wimlib_overwrite_xml_and_header(WIMStruct *wim, int write_flags);
/**
* Prints information about one image, or all images, contained in a WIM.
* included in the WIM being written. If ::WIMLIB_WRITE_FLAG_SHOW_PROGRESS
* is given, the progress of the calculation of the integrity table is
* shown.
+ * @param num_threads
+ * Number of threads to use for compressing data. Autodetected if set to
+ * 0. Note: if no data compression needs to be done, no threads will be
+ * created regardless of this parameter (e.g. if writing an uncompressed
+ * WIM, or exporting an image from a compressed WIM to another WIM of the
+ * same compression type).
*
* @return 0 on success; nonzero on error.
* @retval ::WIMLIB_ERR_DECOMPRESSION
* An error occurred when trying to write data to the new WIM file at @a
* path.
*/
-extern int wimlib_write(WIMStruct *wim, const char *path, int image, int flags);
+extern int wimlib_write(WIMStruct *wim, const char *path, int image,
+ int write_flags, unsigned num_threads);
/*
* Writes a WIM file to the original file that it was read from, overwriting it.
*/
-WIMLIBAPI int wimlib_overwrite(WIMStruct *w, int write_flags)
+WIMLIBAPI int wimlib_overwrite(WIMStruct *w, int write_flags,
+ unsigned num_threads)
{
const char *wimfile_name;
size_t wim_name_len;
randomize_char_array_with_alnum(tmpfile + wim_name_len, 9);
tmpfile[wim_name_len + 9] = '\0';
- ret = wimlib_write(w, tmpfile, WIM_ALL_IMAGES, write_flags);
+ ret = wimlib_write(w, tmpfile, WIM_ALL_IMAGES, write_flags,
+ num_threads);
if (ret != 0) {
ERROR("Failed to write the WIM file `%s'", tmpfile);
if (unlink(tmpfile) != 0)
DEBUG("Compressor thread terminating");
}
+static void show_stream_write_progress(u64 *cur_size, u64 *next_size,
+ u64 total_size, u64 one_percent,
+ unsigned *cur_percent,
+ const struct lookup_table_entry *cur_lte)
+{
+ if (*cur_size >= *next_size) {
+ printf("\r%"PRIu64" MiB of %"PRIu64" MiB "
+ "(uncompressed) written (%u%% done)",
+ *cur_size >> 20,
+ total_size >> 20, *cur_percent);
+ fflush(stdout);
+ *next_size += one_percent;
+ (*cur_percent)++;
+ }
+ *cur_size += wim_resource_size(cur_lte);
+}
+
+static void finish_stream_write_progress(u64 total_size)
+{
+ printf("\r%"PRIu64" MiB of %"PRIu64" MiB "
+ "(uncompressed) written (100%% done)\n",
+ total_size >> 20, total_size >> 20);
+ fflush(stdout);
+}
+
static int write_stream_list_serial(struct list_head *stream_list,
FILE *out_fp, int out_ctype,
- int write_flags)
+ int write_flags, u64 total_size)
{
struct lookup_table_entry *lte;
int ret;
+ u64 one_percent = total_size / 100;
+ u64 cur_size = 0;
+ u64 next_size = 0;
+ unsigned cur_percent = 0;
+
list_for_each_entry(lte, stream_list, staging_list) {
+ if (write_flags & WIMLIB_WRITE_FLAG_SHOW_PROGRESS) {
+ show_stream_write_progress(&cur_size, &next_size,
+ total_size, one_percent,
+ &cur_percent, lte);
+ }
ret = write_wim_resource(lte, out_fp, out_ctype,
<e->output_resource_entry, 0);
if (ret != 0)
return ret;
}
+ if (write_flags & WIMLIB_WRITE_FLAG_SHOW_PROGRESS)
+ finish_stream_write_progress(total_size);
return 0;
}
return 0;
}
+
/*
* This function is executed by the main thread when the resources are being
* compressed in parallel. The main thread is in change of all reading of the
int out_ctype,
struct shared_queue *res_to_compress_queue,
struct shared_queue *compressed_res_queue,
- size_t queue_size)
+ size_t queue_size,
+ int write_flags,
+ u64 total_size)
{
int ret;
struct lookup_table_entry *lte;
struct message *msg;
+ u64 one_percent = total_size / 100;
+ u64 cur_size = 0;
+ u64 next_size = 0;
+ unsigned cur_percent = 0;
+
#ifdef WITH_NTFS_3G
ntfs_inode *ni = NULL;
#endif
DEBUG2("Complete msg (begin_chunk=%"PRIu64")", msg->begin_chunk);
if (msg->begin_chunk == 0) {
DEBUG2("Begin chunk tab");
+
+
+
+ if (write_flags & WIMLIB_WRITE_FLAG_SHOW_PROGRESS) {
+ show_stream_write_progress(&cur_size,
+ &next_size,
+ total_size,
+ one_percent,
+ &cur_percent,
+ cur_lte);
+ }
+
// This is the first set of chunks. Leave space
// for the chunk table in the output file.
off_t cur_offset = ftello(out_fp);
&my_resources,
staging_list)
{
+ if (write_flags & WIMLIB_WRITE_FLAG_SHOW_PROGRESS) {
+ show_stream_write_progress(&cur_size,
+ &next_size,
+ total_size,
+ one_percent,
+ &cur_percent,
+ lte);
+ }
+
ret = write_wim_resource(lte,
out_fp,
out_ctype,
0);
if (ret != 0)
break;
+ if (write_flags & WIMLIB_WRITE_FLAG_SHOW_PROGRESS) {
+ show_stream_write_progress(&cur_size,
+ &next_size,
+ total_size,
+ one_percent,
+ &cur_percent,
+ lte);
+ }
}
+ if (write_flags & WIMLIB_WRITE_FLAG_SHOW_PROGRESS)
+ finish_stream_write_progress(total_size);
} else {
size_t num_available_msgs = 0;
struct list_head *cur;
static int write_stream_list_parallel(struct list_head *stream_list,
FILE *out_fp, int out_ctype,
- int write_flags)
+ int write_flags, u64 total_size,
+ unsigned num_threads)
{
int ret;
- long nthreads;
struct shared_queue res_to_compress_queue;
struct shared_queue compressed_res_queue;
- nthreads = sysconf(_SC_NPROCESSORS_ONLN);
- if (nthreads < 1) {
- WARNING("Could not determine number of processors! Assuming 1");
- goto out_serial;
+ if (num_threads == 0) {
+ long nthreads = sysconf(_SC_NPROCESSORS_ONLN);
+ if (nthreads < 1) {
+ WARNING("Could not determine number of processors! Assuming 1");
+ goto out_serial;
+ } else {
+ num_threads = nthreads;
+ }
}
wimlib_assert(stream_list->next != stream_list);
{
- pthread_t compressor_threads[nthreads];
+ pthread_t compressor_threads[num_threads];
static const double MESSAGES_PER_THREAD = 2.0;
- size_t queue_size = (size_t)(nthreads * MESSAGES_PER_THREAD);
+ size_t queue_size = (size_t)(num_threads * MESSAGES_PER_THREAD);
DEBUG("Initializing shared queues (queue_size=%zu)", queue_size);
params.compressed_res_queue = &compressed_res_queue;
params.compress = get_compress_func(out_ctype);
- for (long i = 0; i < nthreads; i++) {
- DEBUG("pthread_create thread %ld", i);
+ for (unsigned i = 0; i < num_threads; i++) {
+ DEBUG("pthread_create thread %u", i);
ret = pthread_create(&compressor_threads[i], NULL,
compressor_thread_proc, ¶ms);
if (ret != 0) {
ERROR_WITH_ERRNO("Failed to create compressor "
- "thread %ld", i);
- nthreads = i;
+ "thread %u", i);
+ num_threads = i;
goto out_join;
}
}
- if (write_flags & WIMLIB_WRITE_FLAG_VERBOSE) {
- printf("Writing compressed data using %ld threads...\n",
- nthreads);
+ if (write_flags & WIMLIB_WRITE_FLAG_SHOW_PROGRESS) {
+ printf("Writing compressed data using %u threads...\n",
+ num_threads);
}
ret = main_writer_thread_proc(stream_list,
out_ctype,
&res_to_compress_queue,
&compressed_res_queue,
- queue_size);
+ queue_size,
+ write_flags,
+ total_size);
out_join:
- for (long i = 0; i < nthreads; i++)
+ for (unsigned i = 0; i < num_threads; i++)
shared_queue_put(&res_to_compress_queue, NULL);
- for (long i = 0; i < nthreads; i++) {
+ for (unsigned i = 0; i < num_threads; i++) {
if (pthread_join(compressor_threads[i], NULL)) {
- WARNING("Failed to join compressor thread %ld: %s",
+ WARNING("Failed to join compressor thread %u: %s",
i, strerror(errno));
}
}
out_serial:
WARNING("Falling back to single-threaded compression");
return write_stream_list_serial(stream_list, out_fp,
- out_ctype, write_flags);
+ out_ctype, write_flags, total_size);
}
static int write_stream_list(struct list_head *stream_list, FILE *out_fp,
- int out_ctype, int write_flags)
+ int out_ctype, int write_flags,
+ unsigned num_threads)
{
struct lookup_table_entry *lte;
size_t num_streams = 0;
wimlib_get_compression_type_string(out_ctype));
}
- if (compression_needed && total_size >= 1000000) {
+ if (compression_needed && total_size >= 1000000 && num_threads != 1) {
return write_stream_list_parallel(stream_list, out_fp,
- out_ctype, write_flags);
+ out_ctype, write_flags,
+ total_size, num_threads);
} else {
- if (write_flags & WIMLIB_WRITE_FLAG_VERBOSE) {
- puts("Using 1 thread (no compression needed)");
+ if (write_flags & WIMLIB_WRITE_FLAG_SHOW_PROGRESS) {
+ const char *reason = "";
+ if (num_threads != 1)
+ reason = " (no compression needed)";
+ printf("Writing data using 1 thread%s\n", reason);
}
return write_stream_list_serial(stream_list, out_fp,
- out_ctype, write_flags);
+ out_ctype, write_flags,
+ total_size);
}
}
dentry_find_streams_to_write, w);
}
-static int write_wim_streams(WIMStruct *w, int image, int write_flags)
+static int write_wim_streams(WIMStruct *w, int image, int write_flags,
+ unsigned num_threads)
{
LIST_HEAD(stream_list);
w->private = &stream_list;
for_image(w, image, find_streams_to_write);
return write_stream_list(&stream_list, w->out_fp,
- wimlib_get_compression_type(w), write_flags);
+ wimlib_get_compression_type(w), write_flags,
+ num_threads);
}
/*
/* Writes a stand-alone WIM to a file. */
WIMLIBAPI int wimlib_write(WIMStruct *w, const char *path,
- int image, int write_flags)
+ int image, int write_flags, unsigned num_threads)
{
int ret;
for_lookup_table_entry(w->lookup_table, lte_zero_out_refcnt, NULL);
- ret = write_wim_streams(w, image, write_flags);
+ ret = write_wim_streams(w, image, write_flags, num_threads);
if (ret != 0) {
/*ERROR("Failed to write WIM file resources to `%s'", path);*/