+ 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 blob->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(blob->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(blob->file_on_disk, L"\\??\\", 4)) {
+ p_question_mark = &blob->file_on_disk[1];
+ *p_question_mark = L'\\';
+ }
+#endif
+
+ ret = done_with_file(blob->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_blob(struct blob_descriptor *blob, struct write_blobs_ctx *ctx)
+{
+ if (likely(!(ctx->write_resource_flags &
+ WRITE_RESOURCE_FLAG_SEND_DONE_WITH_FILE)))
+ return 0;
+ return do_done_with_blob(blob, ctx->progress_data.progfunc,
+ ctx->progress_data.progctx);
+}
+
+/* Begin processing a blob for writing. */
+static int
+write_blob_begin_read(struct blob_descriptor *blob, void *_ctx)
+{
+ struct write_blobs_ctx *ctx = _ctx;
+ int ret;
+
+ wimlib_assert(blob->size > 0);
+
+ ctx->cur_read_blob_offset = 0;
+ ctx->cur_read_blob_size = blob->size;
+
+ /* As an optimization, we allow some blobs to be "unhashed", meaning
+ * their SHA-1 message digests are unknown. This is the case with blobs
+ * that are added by scanning a directory tree with wimlib_add_image(),
+ * for example. Since WIM uses single-instance blobs, we don't know
+ * whether such each such blob really need to written until it is
+ * actually checksummed, unless it has a unique size. In such cases we
+ * read and checksum the blob in this function, thereby advancing ahead
+ * of read_blob_list(), which will still provide the data again to
+ * write_blob_process_chunk(). This is okay because an unhashed blob
+ * cannot be in a WIM resource, which might be costly to decompress. */
+ if (ctx->blob_table != NULL && blob->unhashed && !blob->unique_size) {
+
+ struct blob_descriptor *new_blob;
+
+ ret = hash_unhashed_blob(blob, ctx->blob_table, &new_blob);
+ if (ret)
+ return ret;
+ if (new_blob != blob) {
+ /* Duplicate blob detected. */
+
+ if (new_blob->will_be_in_output_wim ||
+ blob_filtered(new_blob, ctx->filter_ctx))
+ {
+ /* The duplicate blob is already being included
+ * in the output WIM, or it would be filtered
+ * out if it had been. Skip writing this blob
+ * (and reading it again) entirely, passing its
+ * output reference count to the duplicate blob
+ * in the former case. */
+ DEBUG("Discarding duplicate blob of "
+ "length %"PRIu64, blob->size);
+ ret = do_write_blobs_progress(&ctx->progress_data,
+ blob->size, 1, true);
+ list_del(&blob->write_blobs_list);
+ list_del(&blob->blob_table_list);
+ if (new_blob->will_be_in_output_wim)
+ new_blob->out_refcnt += blob->out_refcnt;
+ if (ctx->write_resource_flags & WRITE_RESOURCE_FLAG_SOLID)
+ ctx->cur_write_res_size -= blob->size;
+ if (!ret)
+ ret = done_with_blob(blob, ctx);
+ free_blob_descriptor(blob);
+ if (ret)
+ return ret;
+ return BEGIN_BLOB_STATUS_SKIP_BLOB;
+ } else {
+ /* The duplicate blob can validly be written,
+ * but was not marked as such. Discard the
+ * current blob descriptor and use the
+ * duplicate, but actually freeing the current
+ * blob descriptor must wait until
+ * read_blob_list() has finished reading its
+ * data. */
+ DEBUG("Blob duplicate, but not already "
+ "selected for writing.");
+ list_replace(&blob->write_blobs_list,
+ &new_blob->write_blobs_list);
+ list_replace(&blob->blob_table_list,
+ &new_blob->blob_table_list);
+ blob->will_be_in_output_wim = 0;
+ new_blob->out_refcnt = blob->out_refcnt;
+ new_blob->will_be_in_output_wim = 1;
+ new_blob->may_send_done_with_file = 0;
+ blob = new_blob;
+ }
+ }
+ }
+ list_move_tail(&blob->write_blobs_list, &ctx->blobs_being_compressed);
+ return 0;
+}
+
+/* Rewrite a blob that was just written compressed as uncompressed instead.
+ */
+static int
+write_blob_uncompressed(struct blob_descriptor *blob, struct filedes *out_fd)
+{
+ int ret;
+ u64 begin_offset = blob->out_reshdr.offset_in_wim;
+ u64 end_offset = out_fd->offset;
+
+ if (filedes_seek(out_fd, begin_offset) == -1)
+ return 0;
+
+ ret = extract_full_blob_to_fd(blob, 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 blob of "
+ "size %"PRIu64", continuing on.", blob->size);
+ return 0;
+ }
+ return ret;
+ }
+
+ wimlib_assert(out_fd->offset - begin_offset == blob->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;
+ }
+
+ blob->out_reshdr.size_in_wim = blob->size;
+ blob->out_reshdr.flags &= ~(WIM_RESHDR_FLAG_COMPRESSED |
+ WIM_RESHDR_FLAG_SOLID);
+ return 0;
+}