+/* Retrieve the full uncompressed data of the specified WIM resource. */
+int
+wim_reshdr_to_data(const struct wim_reshdr *reshdr, WIMStruct *wim, void **buf_ret)
+{
+ DEBUG("offset_in_wim=%"PRIu64", size_in_wim=%"PRIu64", "
+ "uncompressed_size=%"PRIu64,
+ reshdr->offset_in_wim, reshdr->size_in_wim, reshdr->uncompressed_size);
+
+ struct wim_resource_spec rspec;
+ wim_res_hdr_to_spec(reshdr, wim, &rspec);
+ return wim_resource_spec_to_data(&rspec, buf_ret);
+}
+
+struct read_stream_list_ctx {
+ read_stream_list_begin_stream_t begin_stream;
+ consume_data_callback_t consume_chunk;
+ read_stream_list_end_stream_t end_stream;
+ void *begin_stream_ctx;
+ void *consume_chunk_ctx;
+ void *end_stream_ctx;
+ struct wim_lookup_table_entry *cur_stream;
+ u64 cur_stream_offset;
+ struct wim_lookup_table_entry *final_stream;
+ size_t list_head_offset;
+};
+
+static int
+read_stream_list_wrapper_cb(const void *chunk, size_t size, void *_ctx)
+{
+ struct read_stream_list_ctx *ctx = _ctx;
+ int ret;
+
+ if (ctx->cur_stream_offset == 0) {
+ /* Starting a new stream. */
+ ret = (*ctx->begin_stream)(ctx->cur_stream, ctx->begin_stream_ctx);
+ if (ret)
+ return ret;
+ }
+
+ ret = (*ctx->consume_chunk)(chunk, size, ctx->consume_chunk_ctx);
+ if (ret)
+ return ret;
+
+ ctx->cur_stream_offset += size;
+
+ if (ctx->cur_stream_offset == ctx->cur_stream->size) {
+ /* Finished reading all the data for a stream; advance
+ * to the next one. */
+ ret = (*ctx->end_stream)(ctx->cur_stream, ctx->end_stream_ctx);
+ if (ret)
+ return ret;
+
+ if (ctx->cur_stream == ctx->final_stream)
+ return 0;
+
+ struct list_head *cur = (struct list_head *)
+ ((u8*)ctx->cur_stream + ctx->list_head_offset);
+ struct list_head *next = cur->next;
+
+ ctx->cur_stream = (struct wim_lookup_table_entry *)
+ ((u8*)next - ctx->list_head_offset);
+
+ ctx->cur_stream_offset = 0;
+ }
+ return 0;
+}
+
+/*
+ * Read a list of streams, each of which may be in any supported location (e.g.
+ * in a WIM or in an external file). Unlike read_stream_prefix() or the
+ * functions which call it, this function optimizes the case where multiple
+ * streams are packed into a single compressed WIM resource and reads them all
+ * consecutively, only decompressing the data one time.
+ *
+ * @stream_list
+ * List of streams (represented as `struct wim_lookup_table_entry's) to
+ * read.
+ * @list_head_offset
+ * Offset of the `struct list_head' within each `struct
+ * wim_lookup_table_entry' that makes up the @stream_list.
+ * @begin_stream
+ * Callback for starting to process a stream.
+ * @consume_chunk
+ * Callback for receiving a chunk of stream data.
+ * @end_stream
+ * Callback for finishing the processing of a stream.
+ * @cb_chunk_size
+ * Size of chunks to provide to @consume_chunk. For a given stream, all
+ * the chunks will be this size, except possibly the last which will be the
+ * remainder.
+ * @cb_ctx
+ * Parameter to pass to the callback functions.
+ *
+ * Returns 0 on success; a nonzero error code on failure. Failure can occur due
+ * to an error reading the data or due to an error status being returned by any
+ * of the callback functions.
+ */
+int
+read_stream_list(struct list_head *stream_list,
+ size_t list_head_offset,
+ read_stream_list_begin_stream_t begin_stream,
+ consume_data_callback_t consume_chunk,
+ read_stream_list_end_stream_t end_stream,
+ u32 cb_chunk_size,
+ void *cb_ctx)
+{
+ int ret;
+ struct list_head *cur, *next;
+ struct wim_lookup_table_entry *lte;
+
+ ret = sort_stream_list_by_sequential_order(stream_list, list_head_offset);
+ if (ret)
+ return ret;
+
+ for (cur = stream_list->next, next = cur->next;
+ cur != stream_list;
+ cur = next, next = cur->next)
+ {
+ lte = (struct wim_lookup_table_entry*)((u8*)cur - list_head_offset);
+
+ if (lte_is_partial(lte)) {
+
+ struct wim_lookup_table_entry *lte_next, *lte_last;
+ struct list_head *next2;
+ size_t stream_count;
+
+ /* The next stream is a proper sub-sequence of a WIM
+ * resource. See if there are other streams in the same
+ * resource that need to be read. Since
+ * sort_stream_list_by_sequential_order() sorted the
+ * streams by offset in the WIM, this can be determined
+ * by simply scanning forward in the list. */
+
+ lte_last = lte;
+ stream_count = 1;
+ for (next2 = next;
+ next2 != stream_list
+ && (lte_next = (struct wim_lookup_table_entry*)
+ ((u8*)next2 - list_head_offset),
+ lte_next->resource_location == RESOURCE_IN_WIM
+ && lte_next->rspec == lte->rspec);
+ next2 = next2->next)
+ {
+ lte_last = lte_next;
+ stream_count++;
+ }
+ if (stream_count > 1) {
+ /* Reading multiple streams combined into a
+ * single WIM resource. They are in the stream
+ * list, sorted by offset; @lte specifies the
+ * first stream in the resource that needs to be
+ * read and @lte_last specifies the last stream
+ * in the resource that needs to be read. */
+
+ next = next2;
+
+ struct data_range ranges[stream_count];
+
+ {
+ struct list_head *next3;
+ size_t i;
+ struct wim_lookup_table_entry *lte_cur;
+
+ next3 = cur;
+ for (i = 0; i < stream_count; i++) {
+ lte_cur = (struct wim_lookup_table_entry*)
+ ((u8*)next3 - list_head_offset);
+ ranges[i].offset = lte_cur->offset_in_res;
+ ranges[i].size = lte_cur->size;
+ next3 = next3->next;
+ }
+ }
+
+ struct rechunker_context rechunker_ctx = {
+ .buffer = MALLOC(cb_chunk_size),
+ .buffer_filled = 0,
+ .cb_chunk_size = cb_chunk_size,
+ .ranges = ranges,
+ .num_ranges = stream_count,
+ .cur_range = 0,
+ .range_bytes_remaining = ranges[0].size,
+ .cb = consume_chunk,
+ .cb_ctx = cb_ctx,
+ };
+
+ if (rechunker_ctx.buffer == NULL)
+ return WIMLIB_ERR_NOMEM;
+
+ struct read_stream_list_ctx ctx = {
+ .begin_stream = begin_stream,
+ .begin_stream_ctx = cb_ctx,
+ .consume_chunk = rechunker_cb,
+ .consume_chunk_ctx = &rechunker_ctx,
+ .end_stream = end_stream,
+ .end_stream_ctx = cb_ctx,
+ .cur_stream = lte,
+ .cur_stream_offset = 0,
+ .final_stream = lte_last,
+ .list_head_offset = list_head_offset,
+ };
+
+ ret = read_compressed_wim_resource(lte->rspec,
+ ranges,
+ stream_count,
+ read_stream_list_wrapper_cb,
+ &ctx,
+ false);
+ FREE(rechunker_ctx.buffer);
+ if (ret)
+ return ret;
+ continue;
+ }
+ }
+ ret = (*begin_stream)(lte, cb_ctx);
+ if (ret)
+ return ret;
+
+ ret = read_stream_prefix(lte, lte->size, consume_chunk,
+ cb_chunk_size, cb_ctx, 0);
+ if (ret)
+ return ret;
+
+ ret = (*end_stream)(lte, cb_ctx);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+