#include "wimlib/error.h"
#include "wimlib/file_io.h"
#include "wimlib/header.h"
+#include "wimlib/inode.h"
#include "wimlib/integrity.h"
#include "wimlib/lookup_table.h"
#include "wimlib/metadata.h"
if (rspec->flags & WIM_RESHDR_FLAG_COMPRESSED) {
/* Normal compressed resource: Must use same compression type
* and chunk size. */
- return (rspec->wim->compression_type == out_ctype &&
- rspec->wim->chunk_size == out_chunk_size);
+ return (rspec->compression_type == out_ctype &&
+ rspec->chunk_size == out_chunk_size);
}
/* XXX: For compatibility, we can't allow multiple packed resources per
static void
do_write_streams_progress(struct write_streams_progress_data *progress_data,
- u64 size,
- bool discarded,
- struct wim_lookup_table_entry *cur_stream)
+ struct wim_lookup_table_entry *cur_stream,
+ u64 complete_size,
+ u32 complete_count,
+ bool discarded)
{
union wimlib_progress_info *progress = &progress_data->progress;
bool new_wim_part;
if (discarded) {
- progress->write_streams.total_bytes -= size;
+ progress->write_streams.total_bytes -= complete_size;
+ progress->write_streams.total_streams -= complete_count;
if (progress_data->next_progress != ~(uint64_t)0 &&
progress_data->next_progress > progress->write_streams.total_bytes)
{
progress_data->next_progress = progress->write_streams.total_bytes;
}
} else {
- progress->write_streams.completed_bytes += size;
+ progress->write_streams.completed_bytes += complete_size;
+ progress->write_streams.completed_streams += complete_count;
}
+
new_wim_part = false;
if (cur_stream->resource_location == RESOURCE_IN_WIM &&
cur_stream->rspec->wim != progress_data->prev_wim_part)
}
progress_data->prev_wim_part = cur_stream->rspec->wim;
}
- progress->write_streams.completed_streams++;
+
if (progress_data->progress_func
&& (progress->write_streams.completed_bytes >= progress_data->next_progress
|| new_wim_part))
/* List of streams that currently have chunks being compressed. */
struct list_head pending_streams;
+ /* List of streams in the resource pack. Streams are moved here after
+ * @pending_streams only when writing a packed resource. */
+ struct list_head pack_streams;
+
/* Set to true if the stream currently being read was a duplicate, and
* therefore the corresponding stream entry needs to be freed once the
* read finishes. (In this case we add the duplicate entry to
* pending_streams rather than the entry being read.) */
bool stream_was_duplicate;
- /* Current uncompressed offset in the resource being read. */
- u64 cur_read_res_offset;
+ /* Current uncompressed offset in the stream being read. */
+ u64 cur_read_stream_offset;
- /* Uncompressed size of the resource currently being read. */
- u64 cur_read_res_size;
+ /* Uncompressed size of the stream currently being read. */
+ u64 cur_read_stream_size;
- /* Current uncompressed offset in the resource being written. */
- u64 cur_write_res_offset;
+ /* Current uncompressed offset in the stream being written. */
+ u64 cur_write_stream_offset;
/* Uncompressed size of resource currently being written. */
u64 cur_write_res_size;
u64 chunks_start_offset;
};
-static u64
-get_chunk_entry_size(u64 res_size, int write_resource_flags)
-{
- if (res_size <= UINT32_MAX ||
- (write_resource_flags & WIM_RESHDR_FLAG_PACKED_STREAMS))
- return 4;
- else
- return 8;
-}
-
/* Reserve space for the chunk table and prepare to accumulate the chunk table
* in memory. */
static int
* are unknown. */
reserve_size = expected_num_chunk_entries *
get_chunk_entry_size(res_expected_size,
- ctx->write_resource_flags);
+ 0 != (ctx->write_resource_flags &
+ WIM_RESHDR_FLAG_PACKED_STREAMS));
if (ctx->write_resource_flags & WRITE_RESOURCE_FLAG_PACK_STREAMS)
reserve_size += sizeof(struct alt_chunk_table_header_disk);
memset(ctx->chunk_csizes, 0, reserve_size);
/* Output file descriptor is now positioned at the offset at which to
* write the first chunk of the resource. */
ctx->chunks_start_offset = ctx->out_fd->offset;
- ctx->cur_write_res_offset = 0;
+ ctx->cur_write_stream_offset = 0;
ctx->cur_write_res_size = res_expected_size;
return 0;
}
actual_num_chunk_entries--;
chunk_entry_size = get_chunk_entry_size(res_actual_size,
- ctx->write_resource_flags);
+ 0 != (ctx->write_resource_flags &
+ WIM_RESHDR_FLAG_PACKED_STREAMS));
typedef le64 __attribute__((may_alias)) aliased_le64_t;
typedef le32 __attribute__((may_alias)) aliased_le32_t;
u64 res_uncompressed_size;
u64 res_offset_in_wim;
- wimlib_assert(ctx->cur_write_res_size == ctx->cur_write_res_offset);
+ wimlib_assert(ctx->cur_write_stream_offset == ctx->cur_write_res_size ||
+ (ctx->write_resource_flags & WRITE_RESOURCE_FLAG_PACK_STREAMS));
res_uncompressed_size = ctx->cur_write_res_size;
if (ctx->compressor) {
wimlib_assert(lte->size > 0);
- ctx->cur_read_res_offset = 0;
- ctx->cur_read_res_size = lte->size;
+ ctx->cur_read_stream_offset = 0;
+ ctx->cur_read_stream_size = lte->size;
/* As an optimization, we allow some streams to be "unhashed", meaning
* their SHA1 message digests are unknown. This is the case with
DEBUG("Discarding duplicate stream of "
"length %"PRIu64, lte->size);
do_write_streams_progress(&ctx->progress_data,
- lte->size, true, lte);
+ lte, lte->size,
+ 1, true);
list_del(<e->write_streams_list);
list_del(<e->lookup_table_list);
if (lte_new->will_be_in_output_wim)
return 0;
}
+/* Rewrite a stream that was just written compressed as uncompressed instead.
+ * This function is optional, but if a stream did not compress to less than its
+ * original size, it might as well be written uncompressed. */
static int
write_stream_uncompressed(struct wim_lookup_table_entry *lte,
struct filedes *out_fd)
{
int ret;
+ u64 begin_offset = lte->out_reshdr.offset_in_wim;
+ u64 end_offset = out_fd->offset;
+
+ if (filedes_seek(out_fd, begin_offset) == -1)
+ return 0;
- if (-1 == lseek(out_fd->fd, lte->out_reshdr.offset_in_wim, SEEK_SET) ||
- 0 != ftruncate(out_fd->fd, lte->out_reshdr.offset_in_wim))
+ ret = extract_full_stream_to_fd(lte, out_fd);
+ if (ret) {
+ /* Error reading the uncompressed data. */
+ if (out_fd->offset == begin_offset &&
+ filedes_seek(out_fd, end_offset) != -1)
+ {
+ /* Nothing was actually written yet, and we successfully
+ * seeked to the end of the compressed resource, so
+ * don't issue a hard error; just keep the compressed
+ * resource instead. */
+ WARNING("Recovered compressed stream of "
+ "size %"PRIu64", continuing on.",
+ lte->size);
+ return 0;
+ }
+ return ret;
+ }
+
+ wimlib_assert(out_fd->offset - begin_offset == lte->size);
+
+ if (out_fd->offset < end_offset &&
+ 0 != ftruncate(out_fd->fd, out_fd->offset))
{
ERROR_WITH_ERRNO("Can't truncate output file to "
- "offset %"PRIu64, lte->out_reshdr.offset_in_wim);
+ "offset %"PRIu64, out_fd->offset);
return WIMLIB_ERR_WRITE;
}
- out_fd->offset = lte->out_reshdr.offset_in_wim;
-
- ret = extract_stream_to_fd(lte, out_fd, lte->size);
- if (ret)
- return ret;
-
- wimlib_assert(out_fd->offset - lte->out_reshdr.offset_in_wim == lte->size);
lte->out_reshdr.size_in_wim = lte->size;
lte->out_reshdr.flags &= ~(WIM_RESHDR_FLAG_COMPRESSED |
WIM_RESHDR_FLAG_PACKED_STREAMS);
int ret;
struct wim_lookup_table_entry *lte;
+ u32 completed_stream_count;
+ u32 completed_size;
lte = list_entry(ctx->pending_streams.next,
struct wim_lookup_table_entry, write_streams_list);
- if (ctx->cur_write_res_offset == 0 &&
+ if (ctx->cur_write_stream_offset == 0 &&
!(ctx->write_resource_flags & WRITE_RESOURCE_FLAG_PACK_STREAMS))
{
/* Starting to write a new stream in non-packed mode. */
if (ret)
goto error;
- ctx->cur_write_res_offset += usize;
+ ctx->cur_write_stream_offset += usize;
- do_write_streams_progress(&ctx->progress_data,
- usize, false, lte);
+ completed_size = usize;
+ completed_stream_count = 0;
+ if (ctx->write_resource_flags & WRITE_RESOURCE_FLAG_PACK_STREAMS) {
+ /* Wrote chunk in packed mode. It may have finished multiple
+ * streams. */
+ while (ctx->cur_write_stream_offset > lte->size) {
+ struct wim_lookup_table_entry *next;
- if (ctx->cur_write_res_offset == ctx->cur_write_res_size &&
- !(ctx->write_resource_flags & WRITE_RESOURCE_FLAG_PACK_STREAMS))
- {
- wimlib_assert(ctx->cur_write_res_offset == lte->size);
+ ctx->cur_write_stream_offset -= lte->size;
- /* Finished writing a stream in non-packed mode. */
+ wimlib_assert(!list_is_singular(&ctx->pending_streams) &&
+ !list_empty(&ctx->pending_streams));
+ next = list_entry(lte->write_streams_list.next,
+ struct wim_lookup_table_entry,
+ write_streams_list);
+ list_move_tail(<e->write_streams_list,
+ &ctx->pack_streams);
+ lte = next;
+ completed_stream_count++;
+ }
+ if (ctx->cur_write_stream_offset == lte->size) {
+ ctx->cur_write_stream_offset = 0;
+ list_move_tail(<e->write_streams_list,
+ &ctx->pack_streams);
+ completed_stream_count++;
+ }
+ } else {
+ /* Wrote chunk in non-packed mode. It may have finished a
+ * stream. */
+ if (ctx->cur_write_stream_offset == lte->size) {
- ret = end_write_resource(ctx, <e->out_reshdr);
- if (ret)
- return ret;
+ completed_stream_count++;
+
+ list_del(<e->write_streams_list);
- lte->out_reshdr.flags = filter_resource_flags(lte->flags);
- if (ctx->compressor != NULL)
- lte->out_reshdr.flags |= WIM_RESHDR_FLAG_COMPRESSED;
+ wimlib_assert(ctx->cur_write_stream_offset ==
+ ctx->cur_write_res_size);
- if (ctx->compressor != NULL &&
- lte->out_reshdr.size_in_wim >= lte->out_reshdr.uncompressed_size &&
- !(ctx->write_resource_flags & WRITE_RESOURCE_FLAG_PIPABLE) &&
- !(lte->flags & WIM_RESHDR_FLAG_PACKED_STREAMS))
- {
- /* Stream did not compress to less than its original
- * size. If we're not writing a pipable WIM (which
- * could mean the output file descriptor is
- * non-seekable), and the stream isn't located in a
- * resource pack (which would make reading it again
- * costly), truncate the file to the start of the stream
- * and write it uncompressed instead. */
- DEBUG("Stream of size %"PRIu64" did not compress to "
- "less than original size; writing uncompressed.",
- lte->size);
- ret = write_stream_uncompressed(lte, ctx->out_fd);
+ ret = end_write_resource(ctx, <e->out_reshdr);
if (ret)
return ret;
- }
- wimlib_assert(lte->out_reshdr.uncompressed_size == lte->size);
+ lte->out_reshdr.flags = filter_resource_flags(lte->flags);
+ if (ctx->compressor != NULL)
+ lte->out_reshdr.flags |= WIM_RESHDR_FLAG_COMPRESSED;
+
+ if (ctx->compressor != NULL &&
+ lte->out_reshdr.size_in_wim >= lte->out_reshdr.uncompressed_size &&
+ !(ctx->write_resource_flags & WRITE_RESOURCE_FLAG_PIPABLE) &&
+ !(lte->flags & WIM_RESHDR_FLAG_PACKED_STREAMS))
+ {
+ /* Stream did not compress to less than its original
+ * size. If we're not writing a pipable WIM (which
+ * could mean the output file descriptor is
+ * non-seekable), and the stream isn't located in a
+ * resource pack (which would make reading it again
+ * costly), truncate the file to the start of the stream
+ * and write it uncompressed instead. */
+ DEBUG("Stream of size %"PRIu64" did not compress to "
+ "less than original size; writing uncompressed.",
+ lte->size);
+ ret = write_stream_uncompressed(lte, ctx->out_fd);
+ if (ret)
+ return ret;
+ }
+ wimlib_assert(lte->out_reshdr.uncompressed_size == lte->size);
- list_del(<e->write_streams_list);
- ctx->cur_write_res_offset = 0;
+ ctx->cur_write_stream_offset = 0;
+ }
}
+ do_write_streams_progress(&ctx->progress_data, lte,
+ completed_size, completed_stream_count,
+ false);
+
return 0;
error:
ret = write_chunk(ctx, chunk, size, size);
if (ret)
return ret;
- ctx->cur_read_res_offset += size;
+ ctx->cur_read_stream_offset += size;
return 0;
}
} else {
u64 res_bytes_remaining;
- res_bytes_remaining = ctx->cur_read_res_size -
- ctx->cur_read_res_offset;
+ res_bytes_remaining = ctx->cur_read_stream_size -
+ ctx->cur_read_stream_offset;
needed_chunk_size = min(ctx->out_chunk_size,
ctx->chunk_buf_filled +
res_bytes_remaining);
/* No intermediate buffering needed. */
resized_chunk = chunkptr;
chunkptr += needed_chunk_size;
- ctx->cur_read_res_offset += needed_chunk_size;
+ ctx->cur_read_stream_offset += needed_chunk_size;
} else {
/* Intermediate buffering needed. */
size_t bytes_consumed;
memcpy(&ctx->chunk_buf[ctx->chunk_buf_filled],
chunkptr, bytes_consumed);
- resized_chunk = ctx->chunk_buf;
-
chunkptr += bytes_consumed;
- ctx->cur_read_res_offset += bytes_consumed;
+ ctx->cur_read_stream_offset += bytes_consumed;
ctx->chunk_buf_filled += bytes_consumed;
if (ctx->chunk_buf_filled == needed_chunk_size) {
resized_chunk = ctx->chunk_buf;
{
struct write_streams_ctx *ctx = _ctx;
if (status == 0)
- wimlib_assert(ctx->cur_read_res_offset == ctx->cur_read_res_size);
+ wimlib_assert(ctx->cur_read_stream_offset == ctx->cur_read_stream_size);
if (ctx->stream_was_duplicate) {
free_lookup_table_entry(lte);
} else if (lte->unhashed && ctx->lookup_table != NULL) {
ret = write_raw_copy_resource(lte->rspec, out_fd);
if (ret)
return ret;
- do_write_streams_progress(progress_data, lte->size, false, lte);
+ do_write_streams_progress(progress_data, lte, lte->size,
+ 1, false);
}
return 0;
}
* Write a list of streams to the output WIM file.
*
* @stream_list
- * The list of streams to write, specifies a list of `struct
+ * The list of streams to write, specified by a list of `struct
* wim_lookup_table_entry's linked by the 'write_streams_list' member.
*
* @out_fd
*
* WRITE_RESOURCE_FLAG_RECOMPRESS:
* Force compression of all resources, even if they could otherwise
- * be re-used by caping the raw data, due to being located in a WIM
+ * be re-used by copying the raw data, due to being located in a WIM
* file with compatible compression parameters.
*
* WRITE_RESOURCE_FLAG_PIPABLE:
* Write the resources in the wimlib-specific pipable format, and
* furthermore do so in such a way that no seeking backwards in
- * @out_fd will be performed (so it may be a pipe, contrary to the
- * default behavior).
+ * @out_fd will be performed (so it may be a pipe).
*
* WRITE_RESOURCE_FLAG_PACK_STREAMS:
* Pack all the streams into a single resource rather than writing
- * them in separate resources. This format is only valid if the
- * WIM version number is WIM_VERSION_PACKED_STREAMS. This flag
- * currently may not be combined with WRITE_RESOURCE_FLAG_PIPABLE.
+ * them in separate resources. This flag is only valid if the WIM
+ * version number has been, or will be, set to
+ * WIM_VERSION_PACKED_STREAMS. This flag may not be combined with
+ * WRITE_RESOURCE_FLAG_PIPABLE.
*
* @out_ctype
* Compression format to use to write the output streams, specified as one
* In both cases, the @out_reshdr of the `struct wim_lookup_table_entry' for
* each stream written will be updated to specify its location, size, and flags
* in the output WIM. In the packed resource case,
- * WIM_RESHDR_FLAG_PACKED_STREAMS shall be set in the @flags field of the
- * @out_reshdr, and @out_res_offset_in_wim and @out_res_size_in_wim will also
- * be set to the offset and size, respectively, in the output WIM of the full
- * packed resource containing the corresponding stream.
+ * WIM_RESHDR_FLAG_PACKED_STREAMS will be set in the @flags field of each
+ * @out_reshdr, and furthermore @out_res_offset_in_wim and @out_res_size_in_wim
+ * of each @out_reshdr will be set to the offset and size, respectively, in the
+ * output WIM of the packed resource containing the corresponding stream.
*
* Each of the streams to write may be in any location supported by the
* resource-handling code (specifically, read_stream_list()), such as the
* contents of external file that has been logically added to the output WIM, or
- * a stream in another WIM file that has been imported, or even stream in the
+ * a stream in another WIM file that has been imported, or even a stream in the
* "same" WIM file of which a modified copy is being written. In the case that
* a stream is already in a WIM file and uses compatible compression parameters,
* by default this function will re-use the raw data instead of decompressing
* specified in @write_resource_flags, this is not done.
*
* As a further requirement, this function requires that the
- * @will_be_in_output_wim member be set on all streams in @stream_list as well
- * as any other streams not in @stream_list that will be in the output WIM file,
- * but not on any other streams in the output WIM's lookup table or sharing a
- * packed resource with a stream in @stream_list. Still furthermore, if
- * on-the-fly deduplication of streams is possible, then all streams in
+ * @will_be_in_output_wim member be set to 1 on all streams in @stream_list as
+ * well as any other streams not in @stream_list that will be in the output WIM
+ * file, but set to 0 on any other streams in the output WIM's lookup table or
+ * sharing a packed resource with a stream in @stream_list. Still furthermore,
+ * if on-the-fly deduplication of streams is possible, then all streams in
* @stream_list must also be linked by @lookup_table_list along with any other
* streams that have @will_be_in_output_wim set.
*
* This function handles on-the-fly deduplication of streams for which SHA1
- * message digests have not yet been calculated and it is therefore known
- * whether such streams are already in @stream_list or in the WIM's lookup table
- * at all. If @lookup_table is non-NULL, then each stream in @stream_list that
- * has @unhashed set but not @unique_size set is checksummed immediately before
- * it would otherwise be read for writing in order to determine if it is
- * identical to another stream already being written or one that would be
- * filtered out of the output WIM using stream_filtered() with the context
- * @filter_ctx. Each such duplicate stream will be removed from @stream_list, its
- * reference count transfered to the pre-existing duplicate stream, its memory
- * freed, and will not be written. Alternatively, if a stream in @stream_list
- * is a duplicate with any stream in @lookup_table that has not been marked for
- * writing or would not be hard-filtered, it is freed and the pre-existing
- * duplicate is written instead, taking ownership of the reference count and
- * slot in the @lookup_table_list.
- *
- * Returns 0 if all streams were written successfully (or did not need to be
- * written); otherwise a non-zero error code.
+ * message digests have not yet been calculated. Such streams may or may not
+ * need to be written. If @lookup_table is non-NULL, then each stream in
+ * @stream_list that has @unhashed set but not @unique_size set is checksummed
+ * immediately before it would otherwise be read for writing in order to
+ * determine if it is identical to another stream already being written or one
+ * that would be filtered out of the output WIM using stream_filtered() with the
+ * context @filter_ctx. Each such duplicate stream will be removed from
+ * @stream_list, its reference count transfered to the pre-existing duplicate
+ * stream, its memory freed, and will not be written. Alternatively, if a
+ * stream in @stream_list is a duplicate with any stream in @lookup_table that
+ * has not been marked for writing or would not be hard-filtered, it is freed
+ * and the pre-existing duplicate is written instead, taking ownership of the
+ * reference count and slot in the @lookup_table_list.
+ *
+ * Returns 0 if every stream was either written successfully or did not need to
+ * be written; otherwise returns a non-zero error code.
*/
static int
write_stream_list(struct list_head *stream_list,
ctx.write_resource_flags = write_resource_flags;
ctx.filter_ctx = filter_ctx;
- if (out_chunk_size != 0) {
+ if (out_ctype != WIMLIB_COMPRESSION_TYPE_NONE) {
+ wimlib_assert(out_chunk_size != 0);
if (out_chunk_size <= STACK_MAX) {
ctx.chunk_buf = alloca(out_chunk_size);
} else {
* to do compression. There are serial and parallel implementations of
* the chunk_compressor interface. We default to parallel using the
* specified number of threads, unless the upper bound on the number
- * bytes needing to be compressed is less 2000000 (heuristic value). */
+ * bytes needing to be compressed is less than a heuristic value. */
if (out_ctype != WIMLIB_COMPRESSION_TYPE_NONE) {
#ifdef ENABLE_MULTITHREADED_COMPRESSION
- if (ctx.num_bytes_to_compress >= 2000000) {
+ if (ctx.num_bytes_to_compress > max(2000000, out_chunk_size)) {
ret = new_parallel_chunk_compressor(out_ctype,
out_chunk_size,
num_threads, 0,
ctx.progress_data.progress.write_streams.num_threads);
INIT_LIST_HEAD(&ctx.pending_streams);
+ INIT_LIST_HEAD(&ctx.pack_streams);
if (ctx.progress_data.progress_func) {
(*ctx.progress_data.progress_func)(WIMLIB_PROGRESS_MSG_WRITE_STREAMS,
reshdr.uncompressed_size);
offset_in_res = 0;
- list_for_each_entry(lte, &ctx.pending_streams, write_streams_list) {
+ list_for_each_entry(lte, &ctx.pack_streams, write_streams_list) {
lte->out_reshdr.size_in_wim = lte->size;
lte->out_reshdr.flags = filter_resource_flags(lte->flags);
lte->out_reshdr.flags |= WIM_RESHDR_FLAG_PACKED_STREAMS;
&ctx.progress_data);
out_destroy_context:
- if (out_chunk_size > STACK_MAX)
+ if (out_ctype != WIMLIB_COMPRESSION_TYPE_NONE && out_chunk_size > STACK_MAX)
FREE(ctx.chunk_buf);
FREE(ctx.chunk_csizes);
if (ctx.compressor)
struct stream_size_table tab;
struct wim_lookup_table_entry *lte;
- ret = init_stream_size_table(&tab, lt->capacity);
+ ret = init_stream_size_table(&tab, 9001);
if (ret)
return ret;
int image, int write_flags, unsigned num_threads,
wimlib_progress_func_t progress_func)
{
- if (!path)
+ if (write_flags & ~WIMLIB_WRITE_MASK_PUBLIC)
return WIMLIB_ERR_INVALID_PARAM;
- write_flags &= WIMLIB_WRITE_MASK_PUBLIC;
+ if (path == NULL || path[0] == T('\0'))
+ return WIMLIB_ERR_INVALID_PARAM;
return write_standalone_wim(wim, path, image, write_flags,
num_threads, progress_func);
int image, int write_flags, unsigned num_threads,
wimlib_progress_func_t progress_func)
{
+ if (write_flags & ~WIMLIB_WRITE_MASK_PUBLIC)
+ return WIMLIB_ERR_INVALID_PARAM;
+
if (fd < 0)
return WIMLIB_ERR_INVALID_PARAM;
- write_flags &= WIMLIB_WRITE_MASK_PUBLIC;
write_flags |= WIMLIB_WRITE_FLAG_FILE_DESCRIPTOR;
return write_standalone_wim(wim, &fd, image, write_flags,
return ret;
}
- close_wim(wim);
+ if (filedes_valid(&wim->in_fd)) {
+ filedes_close(&wim->in_fd);
+ filedes_invalidate(&wim->in_fd);
+ }
/* Rename the new WIM file to the original WIM file. Note: on Windows
* this actually calls win32_rename_replacement(), not _wrename(), so
int ret;
u32 orig_hdr_flags;
- write_flags &= WIMLIB_WRITE_MASK_PUBLIC;
-
- if (write_flags & WIMLIB_WRITE_FLAG_FILE_DESCRIPTOR)
+ if (write_flags & ~WIMLIB_WRITE_MASK_PUBLIC)
return WIMLIB_ERR_INVALID_PARAM;
if (!wim->filename)