+ if (ctx->write_resource_flags & WRITE_RESOURCE_FLAG_PACK_STREAMS) {
+ struct alt_chunk_table_header_disk hdr;
+
+ hdr.res_usize = cpu_to_le64(res_actual_size);
+ hdr.chunk_size = cpu_to_le32(ctx->out_chunk_size);
+ hdr.compression_format = cpu_to_le32(ctx->out_ctype);
+
+ BUILD_BUG_ON(WIMLIB_COMPRESSION_TYPE_XPRESS != 1);
+ BUILD_BUG_ON(WIMLIB_COMPRESSION_TYPE_LZX != 2);
+ BUILD_BUG_ON(WIMLIB_COMPRESSION_TYPE_LZMS != 3);
+
+ ret = full_pwrite(ctx->out_fd, &hdr, sizeof(hdr),
+ chunk_table_offset - sizeof(hdr));
+ if (ret)
+ goto write_error;
+ res_start_offset = chunk_table_offset - sizeof(hdr);
+ } else {
+ res_start_offset = chunk_table_offset;
+ }
+
+ ret = full_pwrite(ctx->out_fd, ctx->chunk_csizes,
+ chunk_table_size, chunk_table_offset);
+ if (ret)
+ goto write_error;
+ }
+
+ *res_start_offset_ret = res_start_offset;
+ *res_store_size_ret = res_end_offset - res_start_offset;
+
+ return 0;
+
+write_error:
+ ERROR_WITH_ERRNO("Write error");
+ return ret;
+}
+
+/* Finish writing a WIM resource by writing or updating the chunk table (if not
+ * writing the data uncompressed) and loading its metadata into @out_reshdr. */
+static int
+end_write_resource(struct write_streams_ctx *ctx, struct wim_reshdr *out_reshdr)
+{
+ int ret;
+ u64 res_size_in_wim;
+ u64 res_uncompressed_size;
+ u64 res_offset_in_wim;
+
+ 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) {
+ ret = end_chunk_table(ctx, res_uncompressed_size,
+ &res_offset_in_wim, &res_size_in_wim);
+ if (ret)
+ return ret;
+ } else {
+ res_offset_in_wim = ctx->chunks_start_offset;
+ res_size_in_wim = ctx->out_fd->offset - res_offset_in_wim;
+ }
+ out_reshdr->uncompressed_size = res_uncompressed_size;
+ out_reshdr->size_in_wim = res_size_in_wim;
+ out_reshdr->offset_in_wim = res_offset_in_wim;
+ DEBUG("Finished writing resource: %"PRIu64" => %"PRIu64" @ %"PRIu64"",
+ res_uncompressed_size, res_size_in_wim, res_offset_in_wim);
+ return 0;
+}
+
+/* No more data streams of the file at @path are needed. */
+static int
+done_with_file(const tchar *path, wimlib_progress_func_t progfunc, void *progctx)
+{
+ union wimlib_progress_info info;
+
+ info.done_with_file.path_to_file = path;
+
+ return call_progress(progfunc, WIMLIB_PROGRESS_MSG_DONE_WITH_FILE,
+ &info, progctx);
+}
+
+static inline bool
+is_file_stream(const struct wim_lookup_table_entry *lte)
+{
+ return lte->resource_location == RESOURCE_IN_FILE_ON_DISK
+#ifdef __WIN32__
+ || lte->resource_location == RESOURCE_IN_WINNT_FILE_ON_DISK
+ || lte->resource_location == RESOURCE_WIN32_ENCRYPTED
+#endif
+ ;
+}
+
+static int
+do_done_with_stream(struct wim_lookup_table_entry *lte,
+ wimlib_progress_func_t progfunc, void *progctx)
+{
+ int ret;
+ struct wim_inode *inode;
+
+ if (!lte->may_send_done_with_file)
+ return 0;
+
+ inode = lte->file_inode;
+
+ wimlib_assert(inode != NULL);
+ wimlib_assert(inode->num_remaining_streams > 0);
+ if (--inode->num_remaining_streams > 0)
+ return 0;
+
+#ifdef __WIN32__
+ /* XXX: This logic really should be somewhere else. */
+
+ /* We want the path to the file, but lte->file_on_disk might actually
+ * refer to a named data stream. Temporarily strip the named data
+ * stream from the path. */
+ wchar_t *p_colon = NULL;
+ wchar_t *p_question_mark = NULL;
+ const wchar_t *p_stream_name;
+
+ p_stream_name = path_stream_name(lte->file_on_disk);
+ if (unlikely(p_stream_name)) {
+ p_colon = (wchar_t *)(p_stream_name - 1);
+ wimlib_assert(*p_colon == L':');
+ *p_colon = L'\0';
+ }
+
+ /* We also should use a fake Win32 path instead of a NT path */
+ if (!wcsncmp(lte->file_on_disk, L"\\??\\", 4)) {
+ p_question_mark = <e->file_on_disk[1];
+ *p_question_mark = L'\\';
+ }
+#endif
+
+ ret = done_with_file(lte->file_on_disk, progfunc, progctx);
+
+#ifdef __WIN32__
+ if (p_colon)
+ *p_colon = L':';
+ if (p_question_mark)
+ *p_question_mark = L'?';
+#endif
+ return ret;
+}
+
+/* Handle WIMLIB_WRITE_FLAG_SEND_DONE_WITH_FILE_MESSAGES mode. */
+static inline int
+done_with_stream(struct wim_lookup_table_entry *lte,
+ struct write_streams_ctx *ctx)
+{
+ if (likely(!(ctx->write_resource_flags &
+ WRITE_RESOURCE_FLAG_SEND_DONE_WITH_FILE)))
+ return 0;
+ return do_done_with_stream(lte, ctx->progress_data.progfunc,
+ ctx->progress_data.progctx);
+}
+
+/* Begin processing a stream for writing. */
+static int
+write_stream_begin_read(struct wim_lookup_table_entry *lte, void *_ctx)
+{
+ struct write_streams_ctx *ctx = _ctx;
+ int ret;
+
+ wimlib_assert(lte->size > 0);
+
+ 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
+ * streams that are added by scanning a directry tree with
+ * wimlib_add_image(), for example. Since WIM uses single-instance
+ * streams, we don't know whether such each such stream really need to
+ * written until it is actually checksummed, unless it has a unique
+ * size. In such cases we read and checksum the stream in this
+ * function, thereby advancing ahead of read_stream_list(), which will
+ * still provide the data again to write_stream_process_chunk(). This
+ * is okay because an unhashed stream cannot be in a WIM resource, which
+ * might be costly to decompress. */
+ if (ctx->lookup_table != NULL && lte->unhashed && !lte->unique_size) {
+
+ struct wim_lookup_table_entry *lte_new;
+
+ ret = hash_unhashed_stream(lte, ctx->lookup_table, <e_new);
+ if (ret)
+ return ret;
+ if (lte_new != lte) {
+ /* Duplicate stream detected. */
+
+ if (lte_new->will_be_in_output_wim ||
+ stream_filtered(lte_new, ctx->filter_ctx))
+ {
+ /* The duplicate stream is already being
+ * included in the output WIM, or it would be
+ * filtered out if it had been. Skip writing
+ * this stream (and reading it again) entirely,
+ * passing its output reference count to the
+ * duplicate stream in the former case. */
+ DEBUG("Discarding duplicate stream of "
+ "length %"PRIu64, lte->size);
+ ret = do_write_streams_progress(&ctx->progress_data,
+ lte->size,
+ 1, true);
+ list_del(<e->write_streams_list);
+ list_del(<e->lookup_table_list);
+ if (lte_new->will_be_in_output_wim)
+ lte_new->out_refcnt += lte->out_refcnt;
+ if (ctx->write_resource_flags & WRITE_RESOURCE_FLAG_PACK_STREAMS)
+ ctx->cur_write_res_size -= lte->size;
+ if (!ret)
+ ret = done_with_stream(lte, ctx);
+ free_lookup_table_entry(lte);
+ if (ret)
+ return ret;
+ return BEGIN_STREAM_STATUS_SKIP_STREAM;
+ } else {
+ /* The duplicate stream can validly be written,
+ * but was not marked as such. Discard the
+ * current stream entry and use the duplicate,
+ * but actually freeing the current entry must
+ * wait until read_stream_list() has finished
+ * reading its data. */
+ DEBUG("Stream duplicate, but not already "
+ "selected for writing.");
+ list_replace(<e->write_streams_list,
+ <e_new->write_streams_list);
+ list_replace(<e->lookup_table_list,
+ <e_new->lookup_table_list);
+ lte->will_be_in_output_wim = 0;
+ lte_new->out_refcnt = lte->out_refcnt;
+ lte_new->will_be_in_output_wim = 1;
+ lte_new->may_send_done_with_file = 0;
+ lte = lte_new;
+ }
+ }
+ }
+ list_move_tail(<e->write_streams_list, &ctx->pending_streams);
+ 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;
+
+ 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, out_fd->offset);
+ return WIMLIB_ERR_WRITE;
+ }
+
+ lte->out_reshdr.size_in_wim = lte->size;
+ lte->out_reshdr.flags &= ~(WIM_RESHDR_FLAG_COMPRESSED |
+ WIM_RESHDR_FLAG_PACKED_STREAMS);
+ return 0;
+}
+
+/* Returns true if the specified stream should be truncated from the WIM file
+ * and re-written as uncompressed. lte->out_reshdr must be filled in from the
+ * initial write of the stream. */
+static bool
+should_rewrite_stream_uncompressed(const struct write_streams_ctx *ctx,
+ const struct wim_lookup_table_entry *lte)
+{
+ /* If the compressed data is smaller than the uncompressed data, prefer
+ * the compressed data. */
+ if (lte->out_reshdr.size_in_wim < lte->out_reshdr.uncompressed_size)
+ return false;
+
+ /* If we're not actually writing compressed data, then there's no need
+ * for re-writing. */
+ if (!ctx->compressor)
+ return false;
+
+ /* If writing a pipable WIM, everything we write to the output is final
+ * (it might actually be a pipe!). */
+ if (ctx->write_resource_flags & WRITE_RESOURCE_FLAG_PIPABLE)
+ return false;
+
+ /* If the stream that would need to be re-read is located in a solid
+ * block in another WIM file, then re-reading it would be costly. So
+ * don't do it.
+ *
+ * Exception: if the compressed size happens to be *exactly* the same as
+ * the uncompressed size, then the stream *must* be written uncompressed
+ * in order to remain compatible with the Windows Overlay Filesystem
+ * Filter Driver (WOF).
+ *
+ * TODO: we are currently assuming that the optimization for
+ * single-chunk resources in maybe_rewrite_stream_uncompressed()
+ * prevents this case from being triggered too often. To fully prevent
+ * excessive decompressions in degenerate cases, we really should
+ * obtain the uncompressed data by decompressing the compressed data we
+ * wrote to the output file.
+ */
+ if ((lte->flags & WIM_RESHDR_FLAG_PACKED_STREAMS) &&
+ (lte->out_reshdr.size_in_wim != lte->out_reshdr.uncompressed_size))
+ return false;
+
+ return true;
+}
+
+static int
+maybe_rewrite_stream_uncompressed(struct write_streams_ctx *ctx,
+ struct wim_lookup_table_entry *lte)
+{
+ if (!should_rewrite_stream_uncompressed(ctx, lte))
+ return 0;
+
+ /* Regular (non-solid) WIM resources with exactly one chunk and
+ * compressed size equal to uncompressed size are exactly the same as
+ * the corresponding compressed data --- since there must be 0 entries
+ * in the chunk table and the only chunk must be stored uncompressed.
+ * In this case, there's no need to rewrite anything. */
+ if (ctx->chunk_index == 1 &&
+ lte->out_reshdr.size_in_wim == lte->out_reshdr.uncompressed_size)