]> wimlib.net Git - wimlib/blobdiff - src/resource.c
write_stream_list(): Cleanup
[wimlib] / src / resource.c
index 466de1a5ef8e02c76ba7a509a7e551730ab3253e..892aff18dc0852909fda70a96103b0a6dd4f5c31 100644 (file)
@@ -27,7 +27,6 @@
 #endif
 
 #include "wimlib.h"
-#include "wimlib/dentry.h"
 #include "wimlib/endianness.h"
 #include "wimlib/error.h"
 #include "wimlib/file_io.h"
 #endif
 #include <errno.h>
 #include <fcntl.h>
-#include <stdarg.h>
 #include <stdlib.h>
 #include <unistd.h>
 
 /*
- *                            Compressed resources
+ *                         Compressed WIM resources
  *
- * A compressed resource in a WIM consists of a number of consecutive LZX or
- * XPRESS-compressed chunks, each of which decompresses to 32768 bytes of data,
- * except possibly the last, which always decompresses to any remaining bytes.
- * In addition, immediately before the chunks, a table (the "chunk table")
- * provides the offset, in bytes relative to the end of the chunk table, of the
- * start of each compressed chunk, except for the first chunk which is omitted
- * as it always has an offset of 0.  Therefore, a compressed resource with N
- * chunks will have a chunk table with N - 1 entries.
+ * A compressed resource in a WIM consists of a number of compressed chunks,
+ * each of which decompresses to a fixed chunk size (given in the WIM header;
+ * usually 32768) except possibly the last, which always decompresses to any
+ * remaining bytes.  In addition, immediately before the chunks, a table (the
+ * "chunk table") provides the offset, in bytes relative to the end of the chunk
+ * table, of the start of each compressed chunk, except for the first chunk
+ * which is omitted as it always has an offset of 0.  Therefore, a compressed
+ * resource with N chunks will have a chunk table with N - 1 entries.
  *
  * Additional information:
  *
  * writing.  To make sequential writing possible, the chunk table is placed
  * after the chunks rather than before the chunks, and to make sequential
  * reading possible, each chunk is prefixed with a 4-byte header giving its
- * compressed size as a 32-bit, unsigned, little-endian integer (less than or
- * equal to 32768).  Otherwise the details are the same.
+ * compressed size as a 32-bit, unsigned, little-endian integer.  Otherwise the
+ * details are the same.
  */
 
-static int decompress(const void *cchunk, unsigned clen,
-                     void *uchunk, unsigned ulen,
-                     int ctype, u32 wim_chunk_size)
+
+/* Decompress the specified chunk that uses the specified compression type
+ * @ctype, part of a WIM with default chunk size @wim_chunk_size.  For LZX the
+ * separate @wim_chunk_size is needed because it determines the window size used
+ * for LZX compression.  */
+static int
+decompress(const void *cchunk, unsigned clen,
+          void *uchunk, unsigned ulen,
+          int ctype, u32 wim_chunk_size)
 {
        switch (ctype) {
        case WIMLIB_COMPRESSION_TYPE_XPRESS:
@@ -113,52 +117,21 @@ static int decompress(const void *cchunk, unsigned clen,
        }
 }
 
-/*
- * read_compressed_resource()-
- *
- * Read data from a compressed resource being read from a seekable WIM file.
- * The resource may be either pipable or non-pipable.
- *
- * @flags may be:
- *
- * 0:
- *     Just do a normal read, decompressing the data if necessary.
- *
- * WIMLIB_READ_RESOURCE_FLAG_RAW_CHUNKS:
- *     Read the raw contents of the compressed chunks of the compressed
- *     resource.  For pipable resources, this does *not* include the chunk
- *     headers.  If a callback function is being used, it will be called once
- *     for each compressed chunk.  For non-pipable resources, this mode
- *     excludes the chunk table.  For pipable resources, this mode excludes the
- *     stream and chunk headers.
- */
+/* Read data from a compressed WIM resource.  Assumes parameters were already
+ * verified by read_partial_wim_resource().  */
 static int
-read_compressed_resource(const struct wim_lookup_table_entry * const lte,
-                        u64 size, const consume_data_callback_t cb,
-                        const u32 in_chunk_size, void * const ctx_or_buf,
-                        const int flags, const u64 offset)
+read_compressed_wim_resource(const struct wim_lookup_table_entry * const lte,
+                            const u64 size, const consume_data_callback_t cb,
+                            const u32 cb_chunk_size, void * const ctx_or_buf,
+                            const int flags, const u64 offset)
 {
        int ret;
+       int errno_save;
 
        const u32 orig_chunk_size = wim_resource_chunk_size(lte);
        const u32 orig_chunk_order = bsr32(orig_chunk_size);
 
        wimlib_assert(is_power_of_2(orig_chunk_size));
-       wimlib_assert(cb == NULL || is_power_of_2(in_chunk_size));
-
-       /* Currently, reading raw compressed chunks is only guaranteed to work
-        * correctly when the full resource is requested.  Furthermore, in such
-        * cases the requested size is specified as the compressed size, but
-        * here we change it to an uncompressed size to avoid confusing the rest
-        * of this function.  */
-       if (flags & WIMLIB_READ_RESOURCE_FLAG_RAW_CHUNKS) {
-               wimlib_assert(offset == 0);
-               wimlib_assert(size == lte->resource_entry.size);
-               wimlib_assert(wim_resource_chunk_size(lte) == in_chunk_size);
-               size = wim_resource_size(lte);
-       }
-
-       wimlib_assert(offset + size <= wim_resource_size(lte));
 
        /* Handle the trivial case.  */
        if (size == 0)
@@ -172,22 +145,23 @@ read_compressed_resource(const struct wim_lookup_table_entry * const lte,
        bool out_buf_malloced = false;
        bool tmp_buf_malloced = false;
        bool compressed_buf_malloced = false;
-       const size_t stack_max = 32768;
 
        /* Get the file descriptor for the WIM.  */
        struct filedes * const in_fd = &lte->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);
+
        /* Calculate the number of chunks the resource is divided into.  */
        const u64 num_chunks = wim_resource_chunks(lte);
 
-       /* Calculate the number of entries in the chunk table; it's one less
-        * than the number of chunks, since the first chunk has no entry.  */
-       const u64 num_chunk_entries = num_chunks - 1;
-
        /* Calculate the 0-based index of the chunk at which the read starts.
         */
        const u64 start_chunk = offset >> orig_chunk_order;
 
+       /* For pipe reads, we always must start from the 0th chunk.  */
+       const u64 actual_start_chunk = (is_pipe_read ? 0 : start_chunk);
+
        /* Calculate the offset, within the start chunk, of the first byte of
         * the read.  */
        const u32 start_offset_in_chunk = offset & (orig_chunk_size - 1);
@@ -200,130 +174,133 @@ read_compressed_resource(const struct wim_lookup_table_entry * const lte,
         * read.  */
        const u32 end_offset_in_chunk = (offset + size - 1) & (orig_chunk_size - 1);
 
-       /* Calculate the number of chunk entries are actually needed to read the
-        * requested part of the resource.  Include an entry for the first chunk
-        * even though that doesn't exist in the on-disk table, but take into
-        * account that if the last chunk required for the read is not the last
-        * chunk of the resource, an extra chunk entry is needed so that the
-        * compressed size of the last chunk of the read can be determined.  */
-       const u64 num_alloc_chunk_entries = end_chunk - start_chunk +
-                                           1 + (end_chunk != num_chunks - 1);
+       /* Calculate the number of entries in the chunk table; it's one less
+        * than the number of chunks, since the first chunk has no entry.  */
+       const u64 num_chunk_entries = num_chunks - 1;
 
        /* Set the size of each chunk table entry based on the resource's
         * uncompressed size.  */
-       const u64 chunk_entry_size = (wim_resource_size(lte) > ((u64)1 << 32)) ? 8 : 4;
+       const u64 chunk_entry_size = (wim_resource_size(lte) > (1ULL << 32)) ? 8 : 4;
 
        /* Calculate the size, in bytes, of the full chunk table.  */
        const u64 chunk_table_size = num_chunk_entries * chunk_entry_size;
 
-       /* Allocate a buffer to hold a subset of the chunk table.  It will only
-        * contain offsets for the chunks that are actually needed for this
-        * read.  For speed, allocate the buffer on the stack unless it's too
-        * large.  */
-       if (num_alloc_chunk_entries <= stack_max) {
-               chunk_offsets = alloca(num_alloc_chunk_entries * sizeof(u64));
-               chunk_offsets_malloced = false;
-       } else {
-               chunk_offsets = MALLOC(num_alloc_chunk_entries * sizeof(u64));
-               if (!chunk_offsets) {
-                       ERROR("Failed to allocate chunk table "
-                             "with %"PRIu64" entries", num_alloc_chunk_entries);
-                       return WIMLIB_ERR_NOMEM;
+       /* Current offset to read from.  */
+       u64 cur_read_offset = lte->resource_entry.offset;
+       if (!is_pipe_read) {
+               /* Read the chunk table into memory.  */
+
+               /* Calculate the number of chunk entries are actually needed to
+                * read the requested part of the resource.  Include an entry
+                * for the first chunk even though that doesn't exist in the
+                * on-disk table, but take into account that if the last chunk
+                * required for the read is not the last chunk of the resource,
+                * an extra chunk entry is needed so that the compressed size of
+                * the last chunk of the read can be determined.  */
+               const u64 num_alloc_chunk_entries = end_chunk - start_chunk +
+                                                   1 + (end_chunk != num_chunks - 1);
+
+               /* Allocate a buffer to hold a subset of the chunk table.  It
+                * will only contain offsets for the chunks that are actually
+                * needed for this read.  For speed, allocate the buffer on the
+                * stack unless it's too large.  */
+               if ((size_t)(num_alloc_chunk_entries * sizeof(u64)) !=
+                           (num_alloc_chunk_entries * sizeof(u64)))
+                       goto oom;
+
+               if (num_alloc_chunk_entries <= STACK_MAX / sizeof(u64)) {
+                       chunk_offsets = alloca(num_alloc_chunk_entries * sizeof(u64));
+               } else {
+                       chunk_offsets = MALLOC(num_alloc_chunk_entries * sizeof(u64));
+                       if (chunk_offsets == NULL)
+                               goto oom;
+                       chunk_offsets_malloced = true;
                }
-               chunk_offsets_malloced = true;
-       }
 
-       /* Set the implicit offset of the first chunk if it's included in the
-        * needed chunks.  */
-       if (start_chunk == 0)
-               chunk_offsets[0] = 0;
+               /* Set the implicit offset of the first chunk if it's included
+                * in the needed chunks.  */
+               if (start_chunk == 0)
+                       chunk_offsets[0] = 0;
 
-       /* Calculate the index of the first needed entry in the chunk table.  */
-       const u64 start_table_idx = (start_chunk == 0) ? 0 : start_chunk - 1;
+               /* Calculate the index of the first needed entry in the chunk
+                * table.  */
+               const u64 start_table_idx = (start_chunk == 0) ?
+                               0 : start_chunk - 1;
 
-       /* Calculate the number of entries that need to be read from the chunk
-        * table.  */
-       const u64 num_needed_chunk_entries = (start_chunk == 0) ?
+               /* Calculate the number of entries that need to be read from the
+                * chunk table.  */
+               const u64 num_needed_chunk_entries = (start_chunk == 0) ?
                                num_alloc_chunk_entries - 1 : num_alloc_chunk_entries;
 
-       /* Calculate the number of bytes of data that need to be read from the
-        * chunk table.  */
-       const size_t chunk_table_needed_size =
+               /* Calculate the number of bytes of data that need to be read
+                * from the chunk table.  */
+               const size_t chunk_table_needed_size =
                                num_needed_chunk_entries * chunk_entry_size;
-       if ((u64)chunk_table_needed_size !=
-           num_needed_chunk_entries * chunk_entry_size)
-       {
-               ERROR("Compressed read request too large to fit into memory!");
-               ret = WIMLIB_ERR_NOMEM;
-               goto out_free_memory;
-       }
 
-       /* Calculate the byte offset, in the WIM file, of the first chunk table
-        * entry to read.  Take into account that if the WIM file is in the
-        * special "pipable" format, then the chunk table is at the end of the
-        * resource, not the beginning.  */
-       const u64 file_offset_of_needed_chunk_entries =
-               lte->resource_entry.offset
-               + (start_table_idx * chunk_entry_size)
-               + (lte->is_pipable ? (lte->resource_entry.size - chunk_table_size) : 0);
-
-       /* Read the needed chunk table entries into the end of the chunk_offsets
-        * buffer.  */
-       void * const chunk_tab_data = (u8*)&chunk_offsets[num_alloc_chunk_entries] -
-                                     chunk_table_needed_size;
-       ret = full_pread(in_fd, chunk_tab_data, chunk_table_needed_size,
-                        file_offset_of_needed_chunk_entries);
-       if (ret)
-               goto read_error;
-
-       /* Now fill in chunk_offsets from the entries we have read in
-        * chunk_tab_data.  Careful: chunk_offsets aliases chunk_tab_data, which
-        * breaks C's aliasing rules when we read 32-bit integers and store
-        * 64-bit integers.  But since the operations are safe as long as the
-        * compiler doesn't mess with their order, we use the gcc may_alias
-        * extension to tell the compiler that loads from the 32-bit integers
-        * may alias stores to the 64-bit integers.  */
-       {
-               typedef le64 __attribute__((may_alias)) aliased_le64_t;
-               typedef le32 __attribute__((may_alias)) aliased_le32_t;
-               u64 * const chunk_offsets_p = chunk_offsets + (start_chunk == 0);
-               u64 i;
-
-               if (chunk_entry_size == 4) {
-                       aliased_le32_t *raw_entries = (aliased_le32_t*)chunk_tab_data;
-                       for (i = 0; i < num_needed_chunk_entries; i++)
-                               chunk_offsets_p[i] = le32_to_cpu(raw_entries[i]);
-               } else {
-                       aliased_le64_t *raw_entries = (aliased_le64_t*)chunk_tab_data;
-                       for (i = 0; i < num_needed_chunk_entries; i++)
-                               chunk_offsets_p[i] = le64_to_cpu(raw_entries[i]);
+               /* Calculate the byte offset, in the WIM file, of the first
+                * chunk table entry to read.  Take into account that if the WIM
+                * file is in the special "pipable" format, then the chunk table
+                * is at the end of the resource, not the beginning.  */
+               const u64 file_offset_of_needed_chunk_entries =
+                       lte->resource_entry.offset
+                       + (start_table_idx * chunk_entry_size)
+                       + (lte->is_pipable ? (lte->resource_entry.size - chunk_table_size) : 0);
+
+               /* Read the needed chunk table entries into the end of the
+                * chunk_offsets buffer.  */
+               void * const chunk_tab_data = (u8*)&chunk_offsets[num_alloc_chunk_entries] -
+                                             chunk_table_needed_size;
+               ret = full_pread(in_fd, chunk_tab_data, chunk_table_needed_size,
+                                file_offset_of_needed_chunk_entries);
+               if (ret)
+                       goto read_error;
+
+               /* Now fill in chunk_offsets from the entries we have read in
+                * chunk_tab_data.  Careful: chunk_offsets aliases
+                * chunk_tab_data, which breaks C's aliasing rules when we read
+                * 32-bit integers and store 64-bit integers.  But since the
+                * operations are safe as long as the compiler doesn't mess with
+                * their order, we use the gcc may_alias extension to tell the
+                * compiler that loads from the 32-bit integers may alias stores
+                * to the 64-bit integers.  */
+               {
+                       typedef le64 __attribute__((may_alias)) aliased_le64_t;
+                       typedef le32 __attribute__((may_alias)) aliased_le32_t;
+                       u64 * const chunk_offsets_p = chunk_offsets + (start_chunk == 0);
+                       u64 i;
+
+                       if (chunk_entry_size == 4) {
+                               aliased_le32_t *raw_entries = (aliased_le32_t*)chunk_tab_data;
+                               for (i = 0; i < num_needed_chunk_entries; i++)
+                                       chunk_offsets_p[i] = le32_to_cpu(raw_entries[i]);
+                       } else {
+                               aliased_le64_t *raw_entries = (aliased_le64_t*)chunk_tab_data;
+                               for (i = 0; i < num_needed_chunk_entries; i++)
+                                       chunk_offsets_p[i] = le64_to_cpu(raw_entries[i]);
+                       }
                }
-       }
 
-       /* Calculate file offset of the first chunk that needs to be read.
-        * Note: if the resource is pipable, the entries in the chunk table do
-        * *not* include the chunk headers.  */
-       u64 cur_read_offset = lte->resource_entry.offset + chunk_offsets[0];
-       if (!lte->is_pipable)
-               cur_read_offset += chunk_table_size;
-       else
-               cur_read_offset += start_chunk * sizeof(struct pwm_chunk_hdr);
+               /* Set offset to beginning of first chunk to read.  */
+               cur_read_offset += chunk_offsets[0];
+               if (lte->is_pipable)
+                       cur_read_offset += start_chunk * sizeof(struct pwm_chunk_hdr);
+               else
+                       cur_read_offset += chunk_table_size;
+       }
 
        /* If using a callback function, allocate a temporary buffer that will
-        * be used to pass data to it.  If writing directly to a buffer instead,
-        * arrange to write data directly into it.  */
+        * hold data being passed to it.  If writing directly to a buffer
+        * instead, arrange to write data directly into it.  */
        size_t out_buf_size;
        u8 *out_buf_end, *out_p;
        if (cb) {
-               out_buf_size = max(in_chunk_size, orig_chunk_size);
-               if (out_buf_size <= stack_max) {
+               out_buf_size = max(cb_chunk_size, orig_chunk_size);
+               if (out_buf_size <= STACK_MAX) {
                        out_buf = alloca(out_buf_size);
                } else {
                        out_buf = MALLOC(out_buf_size);
-                       if (out_buf == NULL) {
-                               ret = WIMLIB_ERR_NOMEM;
-                               goto out_free_memory;
-                       }
+                       if (out_buf == NULL)
+                               goto oom;
                        out_buf_malloced = true;
                }
        } else {
@@ -335,36 +312,32 @@ read_compressed_resource(const struct wim_lookup_table_entry * const lte,
 
        /* Unless the raw compressed data was requested, allocate a temporary
         * buffer for reading compressed chunks, each of which can be at most
-        * orig_chunk_size - 1 bytes.  This excludes compressed chunks that are
-        * a full orig_chunk_size bytes, which are actually stored uncompressed.
-        */
+        * @orig_chunk_size - 1 bytes.  This excludes compressed chunks that are
+        * a full @orig_chunk_size bytes, which are actually stored
+        * uncompressed.  */
        if (!(flags & WIMLIB_READ_RESOURCE_FLAG_RAW_CHUNKS)) {
-               if (orig_chunk_size - 1 <= stack_max) {
+               if (orig_chunk_size - 1 <= STACK_MAX) {
                        compressed_buf = alloca(orig_chunk_size - 1);
                } else {
                        compressed_buf = MALLOC(orig_chunk_size - 1);
-                       if (compressed_buf == NULL) {
-                               ret = WIMLIB_ERR_NOMEM;
-                               goto out_free_memory;
-                       }
+                       if (compressed_buf == NULL)
+                               goto oom;
                        compressed_buf_malloced = true;
                }
        }
 
-       /* Allocate yet another temporary buffer, this one for reading partial
-        * chunks.  */
+       /* Allocate yet another temporary buffer, this one for decompressing
+        * chunks for which only part of the data is needed.  */
        if (start_offset_in_chunk != 0 ||
            (end_offset_in_chunk != orig_chunk_size - 1 &&
             offset + size != wim_resource_size(lte)))
        {
-               if (orig_chunk_size <= stack_max) {
+               if (orig_chunk_size <= STACK_MAX) {
                        tmp_buf = alloca(orig_chunk_size);
                } else {
                        tmp_buf = MALLOC(orig_chunk_size);
-                       if (tmp_buf == NULL) {
-                               ret = WIMLIB_ERR_NOMEM;
-                               goto out_free_memory;
-                       }
+                       if (tmp_buf == NULL)
+                               goto oom;
                        tmp_buf_malloced = true;
                }
        }
@@ -372,136 +345,159 @@ read_compressed_resource(const struct wim_lookup_table_entry * const lte,
        /* Read, and possibly decompress, each needed chunk, either writing the
         * data directly into the @ctx_or_buf buffer or passing it to the @cb
         * callback function.  */
-       for (u64 i = start_chunk; i <= end_chunk; i++) {
+       for (u64 i = actual_start_chunk; i <= end_chunk; i++) {
 
-               /* If the resource is pipable, skip the chunk header.  */
-               if (lte->is_pipable)
-                       cur_read_offset += sizeof(struct pwm_chunk_hdr);
-
-               /* Calculate the sizes of the compressed chunk and of the
-                * uncompressed chunk.  */
-               u32 compressed_chunk_size;
-               u32 uncompressed_chunk_size;
-               if (i != num_chunks - 1) {
-                       /* Not the last chunk.  Compressed size is given by
-                        * difference of chunk table entries; uncompressed size
-                        * is always the WIM chunk size.  */
-                       compressed_chunk_size = chunk_offsets[i + 1 - start_chunk] -
-                                               chunk_offsets[i - start_chunk];
-                       uncompressed_chunk_size = orig_chunk_size;
-               } else {
-                       /* Last chunk.  Compressed size is the remaining size in
-                        * the compressed resource; uncompressed size is the
-                        * remaining size in the uncompressed resource.  */
-                       compressed_chunk_size = lte->resource_entry.size -
-                                               chunk_table_size -
-                                               chunk_offsets[i - start_chunk];
-                       if (lte->is_pipable)
-                               compressed_chunk_size -= num_chunks *
-                                                        sizeof(struct pwm_chunk_hdr);
-
-                       if ((wim_resource_size(lte) & (orig_chunk_size - 1)) == 0)
-                               uncompressed_chunk_size = orig_chunk_size;
-                       else
-                               uncompressed_chunk_size = wim_resource_size(lte) &
-                                                         (orig_chunk_size - 1);
-               }
-
-               /* Calculate how much of this chunk needs to be read.  */
+               /* Calculate uncompressed size of next chunk.  */
+               u32 chunk_usize;
+               if ((i == num_chunks - 1) && (wim_resource_size(lte) & (orig_chunk_size - 1)))
+                       chunk_usize = (wim_resource_size(lte) & (orig_chunk_size - 1));
+               else
+                       chunk_usize = orig_chunk_size;
 
-               u32 partial_chunk_size;
-               u32 start_offset = 0;
-               u32 end_offset = orig_chunk_size - 1;
+               /* Calculate compressed size of next chunk.  */
+               u32 chunk_csize;
+               if (is_pipe_read) {
+                       struct pwm_chunk_hdr chunk_hdr;
 
-               if (flags & WIMLIB_READ_RESOURCE_FLAG_RAW_CHUNKS) {
-                       partial_chunk_size = compressed_chunk_size;
+                       ret = full_pread(in_fd, &chunk_hdr,
+                                        sizeof(chunk_hdr), cur_read_offset);
+                       if (ret)
+                               goto read_error;
+                       chunk_csize = le32_to_cpu(chunk_hdr.compressed_size);
                } else {
-                       if (i == start_chunk)
-                               start_offset = start_offset_in_chunk;
+                       if (i == num_chunks - 1) {
+                               chunk_csize = lte->resource_entry.size -
+                                             chunk_table_size -
+                                             chunk_offsets[i - start_chunk];
+                               if (lte->is_pipable)
+                                       chunk_csize -= num_chunks * sizeof(struct pwm_chunk_hdr);
+                       } else {
+                               chunk_csize = chunk_offsets[i + 1 - start_chunk] -
+                                             chunk_offsets[i - start_chunk];
+                       }
+               }
+               if (chunk_csize == 0 || chunk_csize > chunk_usize) {
+                       ERROR("Invalid chunk size in compressed resource!");
+                       errno = EINVAL;
+                       ret = WIMLIB_ERR_DECOMPRESSION;
+                       goto out_free_memory;
+               }
+               if (lte->is_pipable)
+                       cur_read_offset += sizeof(struct pwm_chunk_hdr);
 
-                       if (i == end_chunk)
-                               end_offset = end_offset_in_chunk;
+               if (i >= start_chunk) {
+                       /* Calculate how much of this chunk needs to be read.  */
+                       u32 chunk_needed_size;
+                       u32 start_offset = 0;
+                       u32 end_offset = orig_chunk_size - 1;
 
-                       partial_chunk_size = end_offset + 1 - start_offset;
-               }
+                       if (flags & WIMLIB_READ_RESOURCE_FLAG_RAW_CHUNKS) {
+                               chunk_needed_size = chunk_csize;
+                       } else {
+                               if (i == start_chunk)
+                                       start_offset = start_offset_in_chunk;
 
-               if (compressed_chunk_size == uncompressed_chunk_size ||
-                   (flags & WIMLIB_READ_RESOURCE_FLAG_RAW_CHUNKS))
-               {
-                       /* Chunk stored uncompressed, or reading raw chunk data.  */
-                       ret = full_pread(in_fd,
-                                        out_p,
-                                        partial_chunk_size,
-                                        cur_read_offset + start_offset);
-                       if (ret)
-                               goto read_error;
-               } else {
-                       /* Compressed chunk and not doing raw read.  */
-                       u8 *target;
-
-                       /* Read the compressed data into compressed_buf.  */
-                       ret = full_pread(in_fd,
-                                        compressed_buf,
-                                        compressed_chunk_size,
-                                        cur_read_offset);
-                       if (ret)
-                               goto read_error;
+                               if (i == end_chunk)
+                                       end_offset = end_offset_in_chunk;
 
-                       /* For partial chunks we must buffer the uncompressed
-                        * data because we don't need all of it.  */
-                       if (partial_chunk_size == uncompressed_chunk_size)
-                               target = out_p;
-                       else
-                               target = tmp_buf;
-
-                       /* Decompress the chunk.  */
-                       ret = decompress(compressed_buf,
-                                        compressed_chunk_size,
-                                        target,
-                                        uncompressed_chunk_size,
-                                        wim_resource_compression_type(lte),
-                                        orig_chunk_size);
-                       if (ret) {
-                               ERROR("Failed to decompress data.");
-                               ret = WIMLIB_ERR_DECOMPRESSION;
-                               errno = EINVAL;
-                               goto out_free_memory;
+                               chunk_needed_size = end_offset + 1 - start_offset;
                        }
-                       if (partial_chunk_size != uncompressed_chunk_size)
-                               memcpy(out_p, tmp_buf + start_offset,
-                                      partial_chunk_size);
-               }
 
-               out_p += partial_chunk_size;
+                       if (chunk_csize == chunk_usize ||
+                           (flags & WIMLIB_READ_RESOURCE_FLAG_RAW_CHUNKS))
+                       {
+                               /* Read the raw chunk data.  */
 
-               if (cb) {
-                       /* Feed the data to the callback function.  */
-                       wimlib_assert(offset == 0);
+                               ret = full_pread(in_fd,
+                                                out_p,
+                                                chunk_needed_size,
+                                                cur_read_offset + start_offset);
+                               if (ret)
+                                       goto read_error;
+                       } else {
+                               /* Read and decompress the chunk.  */
 
-                       if (flags & WIMLIB_READ_RESOURCE_FLAG_RAW_CHUNKS) {
-                               ret = cb(out_buf, out_p - out_buf, ctx_or_buf);
+                               u8 *target;
+
+                               ret = full_pread(in_fd,
+                                                compressed_buf,
+                                                chunk_csize,
+                                                cur_read_offset);
                                if (ret)
+                                       goto read_error;
+
+                               if (chunk_needed_size == chunk_usize)
+                                       target = out_p;
+                               else
+                                       target = tmp_buf;
+
+                               ret = decompress(compressed_buf,
+                                                chunk_csize,
+                                                target,
+                                                chunk_usize,
+                                                wim_resource_compression_type(lte),
+                                                orig_chunk_size);
+                               if (ret) {
+                                       ERROR("Failed to decompress data!");
+                                       ret = WIMLIB_ERR_DECOMPRESSION;
+                                       errno = EINVAL;
                                        goto out_free_memory;
-                               out_p = out_buf;
+                               }
+                               if (chunk_needed_size != chunk_usize)
+                                       memcpy(out_p, tmp_buf + start_offset,
+                                              chunk_needed_size);
+                       }
+
+                       out_p += chunk_needed_size;
 
-                       } else if (i == end_chunk || out_p == out_buf_end) {
-                               size_t bytes_sent;
-                               const u8 *p;
+                       if (cb) {
+                               /* Feed the data to the callback function.  */
 
-                               for (p = out_buf; p != out_p; p += bytes_sent) {
-                                       bytes_sent = min(in_chunk_size, out_p - p);
-                                       ret = cb(p, bytes_sent, ctx_or_buf);
+                               if (flags & WIMLIB_READ_RESOURCE_FLAG_RAW_CHUNKS) {
+                                       ret = cb(out_buf, out_p - out_buf, ctx_or_buf);
                                        if (ret)
                                                goto out_free_memory;
+                                       out_p = out_buf;
+                               } else if (i == end_chunk || out_p == out_buf_end) {
+                                       size_t bytes_sent;
+                                       const u8 *p;
+
+                                       for (p = out_buf; p != out_p; p += bytes_sent) {
+                                               bytes_sent = min(cb_chunk_size, out_p - p);
+                                               ret = cb(p, bytes_sent, ctx_or_buf);
+                                               if (ret)
+                                                       goto out_free_memory;
+                                       }
+                                       out_p = out_buf;
                                }
-                               out_p = out_buf;
                        }
+                       cur_read_offset += chunk_csize;
+               } else {
+                       u8 dummy;
+
+                       /* Skip data only.  */
+                       cur_read_offset += chunk_csize;
+                       ret = full_pread(in_fd, &dummy, 1, cur_read_offset - 1);
+                       if (ret)
+                               goto read_error;
                }
-               cur_read_offset += compressed_chunk_size;
        }
 
+       if (is_pipe_read
+           && size == lte->resource_entry.original_size
+           && chunk_table_size)
+       {
+               u8 dummy;
+               /* Skip chunk table at end of pipable resource.  */
+
+               cur_read_offset += chunk_table_size;
+               ret = full_pread(in_fd, &dummy, 1, cur_read_offset - 1);
+               if (ret)
+                       goto read_error;
+       }
        ret = 0;
 out_free_memory:
+       errno_save = errno;
        if (chunk_offsets_malloced)
                FREE(chunk_offsets);
        if (out_buf_malloced)
@@ -510,199 +506,97 @@ out_free_memory:
                FREE(compressed_buf);
        if (tmp_buf_malloced)
                FREE(tmp_buf);
+       errno = errno_save;
        return ret;
 
-read_error:
-       ERROR_WITH_ERRNO("Error reading compressed file resource");
+oom:
+       ERROR("Not enough memory available to read size=%"PRIu64" bytes "
+             "from compressed resource!", size);
+       errno = ENOMEM;
+       ret = WIMLIB_ERR_NOMEM;
        goto out_free_memory;
-}
 
-/* Skip over the chunk table at the end of pipable, compressed resource being
- * read from a pipe.  */
-static int
-skip_chunk_table(const struct wim_lookup_table_entry *lte,
-                struct filedes *in_fd)
-{
-       u64 num_chunk_entries = wim_resource_chunks(lte) - 1;
-       u64 chunk_entry_size = (wim_resource_size(lte) > ((u64)1 << 32)) ? 8 : 4;
-       u64 chunk_table_size = num_chunk_entries * chunk_entry_size;
-       int ret;
-
-       if (num_chunk_entries != 0) {
-               u8 dummy;
-               ret = full_pread(in_fd, &dummy, 1,
-                                in_fd->offset + chunk_table_size - 1);
-               if (ret)
-                       return ret;
-       }
-       return 0;
+read_error:
+       ERROR_WITH_ERRNO("Error reading compressed file resource!");
+       goto out_free_memory;
 }
 
-/* Read and decompress data from a compressed, pipable resource being read from
- * a pipe.  */
+/* Read raw data from a file descriptor at the specified offset.  */
 static int
-read_pipable_resource(const struct wim_lookup_table_entry *lte,
-                     u64 size, consume_data_callback_t cb,
-                     u32 in_chunk_size, void *ctx_or_buf,
-                     int flags, u64 offset)
+read_raw_file_data(struct filedes *in_fd,
+                  u64 size,
+                  consume_data_callback_t cb,
+                  u32 cb_chunk_size,
+                  void *ctx_or_buf,
+                  u64 offset)
 {
-       struct filedes *in_fd;
        int ret;
-       const u32 orig_chunk_size = wim_resource_chunk_size(lte);
-       u8 cchunk[orig_chunk_size - 1];
+       u8 *tmp_buf;
+       bool tmp_buf_malloced = false;
 
-       size_t out_buf_size;
-       u8 *out_buf, *out_buf_end, *out_p;
        if (cb) {
-               out_buf_size = max(in_chunk_size, orig_chunk_size);
-               out_buf = alloca(out_buf_size);
-       } else {
-               out_buf_size = size;
-               out_buf = ctx_or_buf;
-       }
-       out_buf_end = out_buf + out_buf_size;
-       out_p = out_buf;
-
-       /* Get pointers to appropriate decompression function and the input file
-        * descriptor.  */
-       in_fd = &lte->wim->in_fd;
-
-       /* This function currently assumes the entire resource is being read at
-        * once and that the raw compressed data isn't being requested.  This is
-        * based on the fact that this function currently only gets called
-        * during the operation of wimlib_extract_image_from_pipe().  */
-       wimlib_assert(!(flags & WIMLIB_READ_RESOURCE_FLAG_RAW));
-       wimlib_assert(offset == 0);
-       wimlib_assert(size == wim_resource_size(lte));
-       wimlib_assert(in_fd->offset == lte->resource_entry.offset);
-
-       u32 chunk_usize;
-       for (offset = 0; offset < size; offset += chunk_usize) {
-               struct pwm_chunk_hdr chunk_hdr;
-               u32 chunk_csize;
-
-               /* Calculate uncompressed size of next chunk.  */
-               chunk_usize = min(orig_chunk_size, size - offset);
-
-               /* Read the compressed size of the next chunk from the chunk
-                * header.  */
-               ret = full_read(in_fd, &chunk_hdr, sizeof(chunk_hdr));
-               if (ret)
-                       goto read_error;
-
-               chunk_csize = le32_to_cpu(chunk_hdr.compressed_size);
-
-               if (chunk_csize > orig_chunk_size) {
-                       errno = EINVAL;
-                       ret = WIMLIB_ERR_INVALID_PIPABLE_WIM;
-                       goto invalid;
-               }
-
-               /* Read chunk data.  */
-               ret = full_read(in_fd, cchunk, chunk_csize);
-               if (ret)
-                       goto read_error;
-
-               if (flags & WIMLIB_READ_RESOURCE_FLAG_SEEK_ONLY)
-                       continue;
-
-               /* Decompress chunk if needed.  Uncompressed size same
-                * as compressed size means the chunk is uncompressed.
-                */
-               if (chunk_csize == chunk_usize) {
-                       memcpy(out_p, cchunk, chunk_usize);
+               /* Send data to callback function in chunks.  */
+               if (cb_chunk_size <= STACK_MAX) {
+                       tmp_buf = alloca(cb_chunk_size);
                } else {
-                       ret = (*decompress)(cchunk, chunk_csize,
-                                           out_p, chunk_usize,
-                                           wim_resource_compression_type(lte),
-                                           orig_chunk_size);
-                       if (ret) {
-                               errno = EINVAL;
-                               ret = WIMLIB_ERR_DECOMPRESSION;
-                               goto invalid;
+                       tmp_buf = MALLOC(cb_chunk_size);
+                       if (tmp_buf == NULL) {
+                               ret = WIMLIB_ERR_NOMEM;
+                               goto out;
                        }
+                       tmp_buf_malloced = true;
                }
-               out_p += chunk_usize;
-
-               /* Feed the uncompressed data into the callback function or copy
-                * it into the provided buffer.  */
-               if (cb && (out_p == out_buf_end ||
-                          offset + chunk_usize == size))
-               {
-                       size_t bytes_sent;
-                       const u8 *p;
 
-                       for (p = out_buf; p != out_p; p += bytes_sent) {
-                               bytes_sent = min(in_chunk_size, out_p - p);
-                               ret = cb(p, bytes_sent, ctx_or_buf);
-                               if (ret)
-                                       return ret;
-                       }
-                       out_p = out_buf;
+               while (size) {
+                       size_t bytes_to_read = min(cb_chunk_size, size);
+                       ret = full_pread(in_fd, tmp_buf, bytes_to_read,
+                                        offset);
+                       if (ret)
+                               goto read_error;
+                       ret = cb(tmp_buf, bytes_to_read, ctx_or_buf);
+                       if (ret)
+                               goto out;
+                       size -= bytes_to_read;
+                       offset += bytes_to_read;
                }
+       } else {
+               /* Read data directly into buffer.  */
+               ret = full_pread(in_fd, ctx_or_buf, size, offset);
+               if (ret)
+                       goto read_error;
        }
-
-       ret = skip_chunk_table(lte, in_fd);
-       if (ret)
-               goto read_error;
-       return 0;
+       ret = 0;
+       goto out;
 
 read_error:
-       ERROR_WITH_ERRNO("Error reading compressed file resource");
-       return ret;
-
-invalid:
-       ERROR("Compressed file resource is invalid");
+       ERROR_WITH_ERRNO("Read error");
+out:
+       if (tmp_buf_malloced)
+               FREE(tmp_buf);
        return ret;
 }
 
 /*
  * read_partial_wim_resource()-
  *
- * Read a range of data from a uncompressed or compressed resource in a WIM
+ * Read a range of data from an uncompressed or compressed resource in a WIM
  * file.  Data is written into a buffer or fed into a callback function, as
  * documented in read_resource_prefix().
  *
- * @flags can be:
- *
- * 0:
- *     Just do a normal read, decompressing the data if necessary.  @size and
- *     @offset are interpreted relative to the uncompressed contents of the
- *     stream.
+ * By default, this function provides the uncompressed data of the resource, and
+ * @size and @offset and interpreted relative to the uncompressed contents of
+ * the resource.  This behavior can be modified by either of the following
+ * flags:
  *
  * WIMLIB_READ_RESOURCE_FLAG_RAW_FULL:
- *     Only valid when the resource is compressed:  Read the raw contents of
- *     the compressed resource.  If the resource is non-pipable, this includes
- *     the chunk table as well as the compressed chunks.  If the resource is
- *     pipable, this includes the compressed chunks--- including the chunk
- *     headers--- and the chunk table.  The stream header is still *not*
- *     included.
- *
- *     In this mode, @offset is relative to the beginning of the raw contents
- *     of the compressed resource--- that is, the chunk table if the resource
- *     is non-pipable, or the header for the first compressed chunk if the
- *     resource is pipable.  @size is the number of raw bytes to read, which
- *     must not overrun the end of the resource.  For example, if @offset is 0,
- *     then @size can be at most the raw size of the compressed resource
- *     (@lte->resource_entry.size).
+ *     Read @size bytes at @offset of the raw contents of the compressed
+ *     resource.  In the case of pipable resources, this excludes the stream
+ *     header.  Exclusive with WIMLIB_READ_RESOURCE_FLAG_RAW_CHUNKS.
  *
  * WIMLIB_READ_RESOURCE_FLAG_RAW_CHUNKS:
- *     Only valid when the resource is compressed and is not being read from a
- *     pipe:  Read the raw contents of the compressed chunks of the compressed
- *     resource.  For pipable resources, this does *not* include the chunk
- *     headers.  If a callback function is being used, it will be called once
- *     for each compressed chunk.  The chunk table is excluded.  Also, for
- *     pipable resources, the stream and chunk headers are excluded.  In this
- *     mode, @size must be exactly the raw size of the compressed resource
- *     (@lte->resource_entry.size) and @offset must be 0.
- *
- * WIMLIB_READ_RESOURCE_FLAG_SEEK_ONLY:
- *     Only valid when the resource is being read from a pipe:  Skip over the
- *     requested data rather than feed it to the callback function or write it
- *     into the buffer.  No decompression is done.
- *     WIMLIB_READ_RESOURCE_FLAG_RAW_* may not be combined with this flag.
- *     @offset must be 0 and @size must be the uncompressed size of the
- *     resource.
+ *     Read the raw compressed chunks of the compressed resource.  @size must
+ *     be the full uncompressed size, @offset must be 0, and @cb_chunk_size
+ *     must be the resource chunk size.
  *
  * Return values:
  *     WIMLIB_ERR_SUCCESS (0)
@@ -710,109 +604,63 @@ invalid:
  *     WIMLIB_ERR_UNEXPECTED_END_OF_FILE (errno set to 0)
  *     WIMLIB_ERR_NOMEM                  (errno set to ENOMEM)
  *     WIMLIB_ERR_DECOMPRESSION          (errno set to EINVAL)
- *     WIMLIB_ERR_INVALID_PIPABLE_WIM    (errno set to EINVAL)
  *
  *     or other error code returned by the @cb function.
  */
 int
 read_partial_wim_resource(const struct wim_lookup_table_entry *lte,
                          u64 size, consume_data_callback_t cb,
-                         u32 in_chunk_size,
+                         u32 cb_chunk_size,
                          void *ctx_or_buf, int flags, u64 offset)
 {
        struct filedes *in_fd;
-       int ret;
 
-       /* Make sure the resource is actually located in a WIM file and is not
-        * somewhere else.  */
+       /* Verify parameters.  */
        wimlib_assert(lte->resource_location == RESOURCE_IN_WIM);
-
-       /* If a callback was specified, in_chunk_size must be a power of 2 (and
-        * not 0).  */
-       wimlib_assert(cb == NULL || is_power_of_2(in_chunk_size));
-
-       /* If a callback was specified, offset must be zero.  */
-       wimlib_assert(cb == NULL || offset == 0);
-
-       /* Retrieve input file descriptor for the WIM file.  */
        in_fd = &lte->wim->in_fd;
-
-       /* Don't allow raw reads (either full or chunks) of uncompressed
-        * resources.  */
-       wimlib_assert(!(flags & WIMLIB_READ_RESOURCE_FLAG_RAW) ||
-                     resource_is_compressed(&lte->resource_entry));
-
-       /* Don't allow seek-only reads unless reading from a pipe; also don't
-        * allow combining SEEK_ONLY with either RAW flag.  */
-       wimlib_assert(!(flags & WIMLIB_READ_RESOURCE_FLAG_SEEK_ONLY) ||
-                     (!filedes_is_seekable(in_fd) &&
-                      !(flags & WIMLIB_READ_RESOURCE_FLAG_RAW)));
+       if (cb)
+               wimlib_assert(is_power_of_2(cb_chunk_size));
+       if (flags & WIMLIB_READ_RESOURCE_FLAG_RAW_CHUNKS) {
+               /* Raw chunks mode is subject to the restrictions noted.  */
+               wimlib_assert(!(flags & WIMLIB_READ_RESOURCE_FLAG_RAW_FULL));
+               wimlib_assert(cb_chunk_size == wim_resource_chunk_size(lte));
+               wimlib_assert(size == lte->resource_entry.original_size);
+               wimlib_assert(offset == 0);
+       } else if (flags & WIMLIB_READ_RESOURCE_FLAG_RAW_FULL) {
+               /* Raw full mode:  read must not overrun end of store size.  */
+               wimlib_assert(offset + size >= size &&
+                             offset + size <= lte->resource_entry.size);
+       } else {
+               /* Normal mode:  read must not overrun end of original size.  */
+               wimlib_assert(offset + size >= size &&
+                             offset + size <= lte->resource_entry.original_size);
+       }
 
        DEBUG("Reading WIM resource: %"PRIu64" @ +%"PRIu64" "
-             "from %"PRIu64" @ +%"PRIu64" (readflags 0x%08x, resflags 0x%02x%s)",
+             "from %"PRIu64"(%"PRIu64") @ +%"PRIu64" "
+             "(readflags 0x%08x, resflags 0x%02x%s)",
              size, offset,
-             lte->resource_entry.original_size, lte->resource_entry.offset,
+             lte->resource_entry.size,
+             lte->resource_entry.original_size,
+             lte->resource_entry.offset,
              flags, lte->resource_entry.flags,
              (lte->is_pipable ? ", pipable" : ""));
 
        if ((flags & WIMLIB_READ_RESOURCE_FLAG_RAW_FULL) ||
-           !resource_is_compressed(&lte->resource_entry))
-       {
-               /* Reading raw resource contents or reading uncompressed
-                * resource.  */
-               wimlib_assert(offset + size <= lte->resource_entry.size);
-               offset += lte->resource_entry.offset;
-               if (flags & WIMLIB_READ_RESOURCE_FLAG_SEEK_ONLY) {
-                       if (lte->resource_entry.size != 0) {
-                               u8 dummy;
-                               ret = full_pread(in_fd, &dummy, 1,
-                                                offset + lte->resource_entry.size - 1);
-                               if (ret)
-                                       goto read_error;
-                       }
-               } else if (cb) {
-                       /* Send data to callback function */
-                       u8 buf[min(in_chunk_size, size)];
-                       while (size) {
-                               size_t bytes_to_read = min(in_chunk_size, size);
-                               ret = full_pread(in_fd, buf, bytes_to_read,
-                                                offset);
-                               if (ret)
-                                       goto read_error;
-                               ret = cb(buf, bytes_to_read, ctx_or_buf);
-                               if (ret)
-                                       goto out;
-                               size -= bytes_to_read;
-                               offset += bytes_to_read;
-                       }
-               } else {
-                       /* Send data directly to a buffer */
-                       ret = full_pread(in_fd, ctx_or_buf, size, offset);
-                       if (ret)
-                               goto read_error;
-               }
-               ret = 0;
-       } else if (lte->is_pipable && !filedes_is_seekable(in_fd)) {
-               /* Reading compressed, pipable resource from pipe.  */
-               ret = read_pipable_resource(lte, size, cb,
-                                           in_chunk_size,
-                                           ctx_or_buf, flags, offset);
+           !resource_is_compressed(&lte->resource_entry)) {
+               return read_raw_file_data(in_fd,
+                                         size,
+                                         cb,
+                                         cb_chunk_size,
+                                         ctx_or_buf,
+                                         offset + lte->resource_entry.offset);
        } else {
-               /* Reading compressed, possibly pipable resource from seekable
-                * file.  */
-               ret = read_compressed_resource(lte, size, cb,
-                                              in_chunk_size,
-                                              ctx_or_buf, flags, offset);
+               return read_compressed_wim_resource(lte, size, cb,
+                                                   cb_chunk_size,
+                                                   ctx_or_buf, flags, offset);
        }
-       goto out;
-
-read_error:
-       ERROR_WITH_ERRNO("Error reading data from WIM");
-out:
-       return ret;
 }
 
-
 int
 read_partial_wim_resource_into_buf(const struct wim_lookup_table_entry *lte,
                                   size_t size, u64 offset, void *buf)
@@ -824,104 +672,76 @@ static int
 read_wim_resource_prefix(const struct wim_lookup_table_entry *lte,
                         u64 size,
                         consume_data_callback_t cb,
-                        u32 in_chunk_size,
+                        u32 cb_chunk_size,
                         void *ctx_or_buf,
                         int flags)
 {
-       return read_partial_wim_resource(lte, size, cb, in_chunk_size,
+       return read_partial_wim_resource(lte, size, cb, cb_chunk_size,
                                         ctx_or_buf, flags, 0);
 }
 
-
 #ifndef __WIN32__
+/* This function handles reading resource data that is located in an external
+ * file,  such as a file that has been added to the WIM image through execution
+ * of a wimlib_add_command.
+ *
+ * This assumes the file can be accessed using the standard POSIX open(),
+ * read(), and close().  On Windows this will not necessarily be the case (since
+ * the file may need FILE_FLAG_BACKUP_SEMANTICS to be opened, or the file may be
+ * encrypted), so Windows uses its own code for its equivalent case.
+ */
 static int
 read_file_on_disk_prefix(const struct wim_lookup_table_entry *lte,
                         u64 size,
                         consume_data_callback_t cb,
-                        u32 in_chunk_size,
+                        u32 cb_chunk_size,
                         void *ctx_or_buf,
                         int _ignored_flags)
 {
-       const tchar *filename = lte->file_on_disk;
        int ret;
-       struct filedes fd;
        int raw_fd;
-       u8 *out_buf;
-       bool out_buf_malloced;
-       const size_t stack_max = 32768;
+       struct filedes fd;
 
-       DEBUG("Reading %"PRIu64" bytes from \"%"TS"\"",
-             size, lte->file_on_disk);
+       wimlib_assert(size <= wim_resource_size(lte));
+       DEBUG("Reading %"PRIu64" bytes from \"%"TS"\"", size, lte->file_on_disk);
 
-       raw_fd = open(filename, O_RDONLY);
+       raw_fd = open(lte->file_on_disk, O_BINARY | O_RDONLY);
        if (raw_fd < 0) {
-               ERROR_WITH_ERRNO("Can't open \"%"TS"\"", filename);
+               ERROR_WITH_ERRNO("Can't open \"%"TS"\"", lte->file_on_disk);
                return WIMLIB_ERR_OPEN;
        }
        filedes_init(&fd, raw_fd);
-       out_buf_malloced = false;
-       if (cb) {
-               /* Send data to callback function */
-               if (in_chunk_size <= stack_max) {
-                       out_buf = alloca(in_chunk_size);
-               } else {
-                       out_buf = MALLOC(in_chunk_size);
-                       if (out_buf == NULL) {
-                               ret = WIMLIB_ERR_NOMEM;
-                               goto out_close;
-                       }
-                       out_buf_malloced = true;
-               }
-
-               size_t bytes_to_read;
-               while (size) {
-                       bytes_to_read = min(in_chunk_size, size);
-                       ret = full_read(&fd, out_buf, bytes_to_read);
-                       if (ret)
-                               goto read_error;
-                       ret = cb(out_buf, bytes_to_read, ctx_or_buf);
-                       if (ret)
-                               goto out_close;
-                       size -= bytes_to_read;
-               }
-       } else {
-               /* Send data directly to a buffer */
-               ret = full_read(&fd, ctx_or_buf, size);
-               if (ret)
-                       goto read_error;
-       }
-       ret = 0;
-       goto out_close;
-
-read_error:
-       ERROR_WITH_ERRNO("Error reading \"%"TS"\"", filename);
-out_close:
+       ret = read_raw_file_data(&fd, size, cb, cb_chunk_size, ctx_or_buf, 0);
        filedes_close(&fd);
-       if (out_buf_malloced)
-               FREE(out_buf);
        return ret;
 }
 #endif /* !__WIN32__ */
 
+/* This function handles the trivial case of reading resource data that is, in
+ * fact, already located in an in-memory buffer.  */
 static int
 read_buffer_prefix(const struct wim_lookup_table_entry *lte,
                   u64 size, consume_data_callback_t cb,
-                  u32 in_chunk_size,
+                  u32 cb_chunk_size,
                   void *ctx_or_buf, int _ignored_flags)
 {
+       wimlib_assert(size <= wim_resource_size(lte));
 
        if (cb) {
+               /* Feed the data into the callback function in
+                * appropriately-sized chunks.  */
                int ret;
                u32 chunk_size;
 
                for (u64 offset = 0; offset < size; offset += chunk_size) {
-                       chunk_size = min(in_chunk_size, size - offset);
+                       chunk_size = min(cb_chunk_size, size - offset);
                        ret = cb((const u8*)lte->attached_buffer + offset,
                                 chunk_size, ctx_or_buf);
                        if (ret)
                                return ret;
                }
        } else {
+               /* Copy the data directly into the specified buffer.  */
                memcpy(ctx_or_buf, lte->attached_buffer, size);
        }
        return 0;
@@ -930,32 +750,44 @@ read_buffer_prefix(const struct wim_lookup_table_entry *lte,
 typedef int (*read_resource_prefix_handler_t)(const struct wim_lookup_table_entry *lte,
                                              u64 size,
                                              consume_data_callback_t cb,
-                                             u32 in_chunk_size,
+                                             u32 cb_chunk_size,
                                              void *ctx_or_buf,
                                              int flags);
 
 /*
  * read_resource_prefix()-
  *
- * Read the first @size bytes from a generic "resource", which may be located in
- * the WIM (compressed or uncompressed), in an external file, or directly in an
- * in-memory buffer.
+ * Reads the first @size bytes from a generic "resource", which may be located
+ * in any one of several locations, such as in a WIM file (compressed or
+ * uncompressed), in an external file, or directly in an in-memory buffer.
  *
- * Feed the data either to a callback function (cb != NULL, passing it
- * ctx_or_buf), or write it directly into a buffer (cb == NULL, ctx_or_buf
- * specifies the buffer, which must have room for @size bytes).
+ * This function feeds the data either to a callback function (@cb != NULL,
+ * passing it @ctx_or_buf), or write it directly into a buffer (@cb == NULL,
+ * @ctx_or_buf specifies the buffer, which must have room for at least @size
+ * bytes).
  *
- * When using a callback function, it is called with chunks up to 32768 bytes in
- * size until the resource is exhausted.
+ * When (@cb != NULL), @cb_chunk_size specifies the maximum size of data chunks
+ * to feed the callback function.  @cb_chunk_size must be positive, and if the
+ * resource is in a WIM file, must be a power of 2.  All chunks, except possibly
+ * the last one, will be this size.  If (@cb == NULL), @cb_chunk_size is
+ * ignored.
  *
  * If the resource is located in a WIM file, @flags can be set as documented in
  * read_partial_wim_resource().  Otherwise @flags are ignored.
+ *
+ * Returns 0 on success; nonzero on error.  A nonzero value will be returned if
+ * the resource data cannot be successfully read (for a number of different
+ * reasons, depending on the resource location), or if a callback function was
+ * specified and it returned nonzero.
  */
 int
 read_resource_prefix(const struct wim_lookup_table_entry *lte,
-                    u64 size, consume_data_callback_t cb, u32 in_chunk_size,
+                    u64 size, consume_data_callback_t cb, u32 cb_chunk_size,
                     void *ctx_or_buf, int flags)
 {
+       /* This function merely verifies several preconditions, then passes
+        * control to an appropriate function for understanding each possible
+        * resource location.  */
        static const read_resource_prefix_handler_t handlers[] = {
                [RESOURCE_IN_WIM]             = read_wim_resource_prefix,
        #ifdef __WIN32__
@@ -976,17 +808,24 @@ read_resource_prefix(const struct wim_lookup_table_entry *lte,
        };
        wimlib_assert(lte->resource_location < ARRAY_LEN(handlers)
                      && handlers[lte->resource_location] != NULL);
-       wimlib_assert(cb == NULL || in_chunk_size > 0);
-       return handlers[lte->resource_location](lte, size, cb, in_chunk_size, ctx_or_buf, flags);
+       wimlib_assert(cb == NULL || cb_chunk_size > 0);
+       return handlers[lte->resource_location](lte, size, cb, cb_chunk_size,
+                                               ctx_or_buf, flags);
 }
 
+/* Read the full uncompressed data of the specified resource into the specified
+ * buffer, which must have space for at least lte->resource_entry.original_size
+ * bytes.  */
 int
 read_full_resource_into_buf(const struct wim_lookup_table_entry *lte,
                            void *buf)
 {
-       return read_resource_prefix(lte, wim_resource_size(lte), NULL, 0, buf, 0);
+       return read_resource_prefix(lte, wim_resource_size(lte),
+                                   NULL, 0, buf, 0);
 }
 
+/* Read the full uncompressed data of the specified resource.  A buffer
+ * sufficient to hold the data is allocated and returned in @buf_ret.  */
 int
 read_full_resource_into_alloc_buf(const struct wim_lookup_table_entry *lte,
                                  void **buf_ret)
@@ -1003,7 +842,7 @@ read_full_resource_into_alloc_buf(const struct wim_lookup_table_entry *lte,
        }
 
        buf = MALLOC(lte->resource_entry.original_size);
-       if (!buf)
+       if (buf == NULL)
                return WIMLIB_ERR_NOMEM;
 
        ret = read_full_resource_into_buf(lte, buf);
@@ -1016,6 +855,8 @@ read_full_resource_into_alloc_buf(const struct wim_lookup_table_entry *lte,
        return 0;
 }
 
+/* Retrieve the full uncompressed data of the specified WIM resource, provided
+ * as a raw `struct resource_entry'.  */
 int
 res_entry_to_data(const struct resource_entry *res_entry,
                  WIMStruct *wim, void **buf_ret)
@@ -1024,7 +865,7 @@ res_entry_to_data(const struct resource_entry *res_entry,
        struct wim_lookup_table_entry *lte;
 
        lte = new_lookup_table_entry();
-       if (!lte)
+       if (lte == NULL)
                return WIMLIB_ERR_NOMEM;
 
        copy_resource_entry(&lte->resource_entry, res_entry);
@@ -1053,12 +894,12 @@ extract_chunk_sha1_wrapper(const void *chunk, size_t chunk_size,
        return ctx->extract_chunk(chunk, chunk_size, ctx->extract_chunk_arg);
 }
 
-/* Extracts the first @size bytes of a WIM resource to somewhere.  In the
- * process, the SHA1 message digest of the resource is checked if the full
+/* Extracts the first @size bytes of a resource to somewhere.  In the process,
+ * the SHA1 message digest of the uncompressed resource is checked if the full
  * resource is being extracted.
  *
- * @extract_chunk is a function that is called to extract each chunk of the
- * resource. */
+ * @extract_chunk is a function that will be called to extract each chunk of the
+ * resource.  */
 int
 extract_wim_resource(const struct wim_lookup_table_entry *lte,
                     u64 size,
@@ -1109,6 +950,9 @@ extract_wim_chunk_to_fd(const void *buf, size_t len, void *_fd_p)
        return ret;
 }
 
+/* Extract the first @size bytes of the specified resource to the specified file
+ * descriptor.  If @size is the full size of the resource, its SHA1 message
+ * digest is also checked.  */
 int
 extract_wim_resource_to_fd(const struct wim_lookup_table_entry *lte,
                           struct filedes *fd, u64 size)
@@ -1124,7 +968,7 @@ sha1_chunk(const void *buf, size_t len, void *ctx)
        return 0;
 }
 
-/* Calculate the SHA1 message digest of a stream. */
+/* Calculate the SHA1 message digest of a resource, storing it in @lte->hash.  */
 int
 sha1_resource(struct wim_lookup_table_entry *lte)
 {
@@ -1137,18 +981,19 @@ sha1_resource(struct wim_lookup_table_entry *lte)
                                   &sha_ctx, 0);
        if (ret == 0)
                sha1_final(lte->hash, &sha_ctx);
+
        return ret;
 }
 
-/* Translates a WIM resource entry from the on-disk format to an in-memory
- * format. */
+/* Translates a WIM resource entry from the on-disk format into an in-memory
+ * format.  */
 void
 get_resource_entry(const struct resource_entry_disk *disk_entry,
                   struct resource_entry *entry)
 {
        /* Note: disk_entry may not be 8 byte aligned--- in that case, the
-        * offset and original_size members will be unaligned.  (This should be
-        * okay since `struct resource_entry_disk' is declared as packed.) */
+        * offset and original_size members will be unaligned.  (This is okay
+        * since `struct resource_entry_disk' is declared as packed.)  */
 
        /* Read the size and flags into a bitfield portably... */
        entry->size = (((u64)disk_entry->size[0] <<  0) |
@@ -1183,8 +1028,8 @@ put_resource_entry(const struct resource_entry *entry,
                   struct resource_entry_disk *disk_entry)
 {
        /* Note: disk_entry may not be 8 byte aligned--- in that case, the
-        * offset and original_size members will be unaligned.  (This should be
-        * okay since `struct resource_entry_disk' is declared as packed.) */
+        * offset and original_size members will be unaligned.  (This is okay
+        * since `struct resource_entry_disk' is declared as packed.)  */
        u64 size = entry->size;
 
        disk_entry->size[0] = size >>  0;