+/* Alternate chunk table format for resources with WIM_RESHDR_FLAG_CONCAT set.
+ */
+struct alt_chunk_table_header_disk {
+ /* Uncompressed size of the resource. */
+ le64 res_usize;
+
+ /* Number of bytes each compressed chunk decompresses into, except
+ * possibly the last which decompresses into the remainder. */
+ le32 chunk_size;
+
+ /* ??? */
+ le32 unknown;
+
+ /* This header is directly followed by a table of compressed sizes of
+ * the chunks. */
+} _packed_attribute;
+
+/* Read data from a compressed WIM resource. */
+static int
+read_compressed_wim_resource(const struct wim_resource_spec * const rspec,
+ const struct data_range * const ranges,
+ const size_t num_ranges,
+ const consume_data_callback_t cb,
+ void * const cb_ctx,
+ const bool raw_chunks_mode)
+{
+ int ret;
+ int errno_save;
+
+ u64 *chunk_offsets = NULL;
+ u8 *ubuf = NULL;
+ void *cbuf = NULL;
+ bool chunk_offsets_malloced = false;
+ bool ubuf_malloced = false;
+ bool cbuf_malloced = false;
+
+ /* Sanity checks */
+ wimlib_assert(rspec != NULL);
+ wimlib_assert(rspec->ctype != WIMLIB_COMPRESSION_TYPE_NONE);
+ wimlib_assert(is_power_of_2(rspec->cchunk_size));
+ wimlib_assert(cb != NULL);
+ wimlib_assert(num_ranges != 0);
+ for (size_t i = 0; i < num_ranges; i++) {
+ wimlib_assert(ranges[i].size != 0);
+ wimlib_assert(ranges[i].offset + ranges[i].size >= ranges[i].size);
+ wimlib_assert(ranges[i].offset + ranges[i].size <= rspec->uncompressed_size);
+ }
+ for (size_t i = 0; i < num_ranges - 1; i++)
+ wimlib_assert(ranges[i].offset + ranges[i].size <= ranges[i + 1].offset);
+
+ /* Get the offsets of the first and last bytes of the read. */
+ const u64 first_offset = ranges[0].offset;
+ const u64 last_offset = ranges[num_ranges - 1].offset + ranges[num_ranges - 1].size - 1;
+
+ /* Get the file descriptor for the WIM. */
+ struct filedes * const in_fd = &rspec->wim->in_fd;
+
+ /* Determine if we're reading a pipable resource from a pipe or not. */
+ const bool is_pipe_read = !filedes_is_seekable(in_fd);
+
+ /* Determine if the chunk table is in an altenate format. */
+ const bool alt_chunk_table = (rspec->flags & WIM_RESHDR_FLAG_CONCAT) && !is_pipe_read;
+
+ /* Get the maximum size of uncompressed chunks in this resource, which
+ * we require be a power of 2. */
+ u32 chunk_size;
+ u64 cur_read_offset = rspec->offset_in_wim;
+ if (alt_chunk_table) {
+ /* Alternate chunk table format. */
+ struct alt_chunk_table_header_disk hdr;
+
+ ret = full_pread(in_fd, &hdr, sizeof(hdr), cur_read_offset);
+ if (ret)
+ goto read_error;
+ cur_read_offset += sizeof(hdr);
+
+ chunk_size = le32_to_cpu(hdr.chunk_size);
+
+ if (!is_power_of_2(chunk_size)) {
+ ERROR("Invalid compressed resource: "
+ "expected power-of-2 chunk size (got %u)", chunk_size);
+ ret = WIMLIB_ERR_INVALID_CHUNK_SIZE;
+ goto out_free_memory;
+ }
+ } else {
+ chunk_size = rspec->cchunk_size;
+ }
+ const u32 chunk_order = bsr32(chunk_size);
+
+ /* Calculate the total number of chunks the resource is divided into. */
+ const u64 num_chunks = (rspec->uncompressed_size + chunk_size - 1) >> chunk_order;
+
+ /* Calculate the 0-based indices of the first and last chunks containing
+ * data that needs to be passed to the callback. */
+ const u64 first_needed_chunk = first_offset >> chunk_order;
+ const u64 last_needed_chunk = last_offset >> chunk_order;
+
+ /* Calculate the 0-based index of the first chunk that actually needs to
+ * be read. This is normally first_needed_chunk, but for pipe reads we
+ * must always start from the 0th chunk. */
+ const u64 read_start_chunk = (is_pipe_read ? 0 : first_needed_chunk);
+
+ /* Calculate the number of chunk offsets that are needed for the chunks
+ * being read. */
+ const u64 num_needed_chunk_offsets =
+ last_needed_chunk - read_start_chunk + 1 +
+ (last_needed_chunk < num_chunks - 1);
+
+ /* Calculate the number of entries in the chunk table. Normally, it's
+ * one less than the number of chunks, since the first chunk has no
+ * entry. But in the alternate chunk table format, the chunk entries
+ * contain chunk sizes, not offsets, and there is one per chunk. */
+ const u64 num_chunk_entries = (alt_chunk_table ? num_chunks : num_chunks - 1);
+
+ /* Set the size of each chunk table entry based on the resource's
+ * uncompressed size. XXX: Does the alternate chunk table really
+ * always have 4-byte entries? */
+ const u64 chunk_entry_size =
+ (rspec->uncompressed_size > (1ULL << 32) && !alt_chunk_table)
+ ? 8 : 4;
+
+ /* Calculate the size of the chunk table in bytes. */
+ const u64 chunk_table_size = num_chunk_entries * chunk_entry_size;
+
+ /* Includes header */
+ const u64 chunk_table_full_size =
+ (alt_chunk_table) ? chunk_table_size + sizeof(struct alt_chunk_table_header_disk)
+ : chunk_table_size;
+
+ if (!is_pipe_read) {
+ /* Read the needed chunk table entries into memory and use them
+ * to initialize the chunk_offsets array. */
+
+ u64 first_chunk_entry_to_read;
+ u64 last_chunk_entry_to_read;
+
+ if (alt_chunk_table) {
+ /* The alternate chunk table contains chunk sizes, not
+ * offsets, so we always must read all preceding entries
+ * in order to determine offsets. */
+ first_chunk_entry_to_read = 0;
+ last_chunk_entry_to_read = last_needed_chunk;
+ } else {
+ /* Here we must account for the fact that the first
+ * chunk has no explicit chunk table entry. */