diff options
-rw-r--r-- | include/data_reader.h | 10 | ||||
-rw-r--r-- | lib/sqfs/data_reader.c | 81 |
2 files changed, 91 insertions, 0 deletions
diff --git a/include/data_reader.h b/include/data_reader.h index c6d85f1..51bfff3 100644 --- a/include/data_reader.h +++ b/include/data_reader.h @@ -41,4 +41,14 @@ void data_reader_destroy(data_reader_t *data); int data_reader_dump_file(data_reader_t *data, file_info_t *fi, int outfd, bool allow_sparse); +/* + Read a chunk of data from a file. Starting from 'offset' into the + uncompressed file, read 'size' bytes into 'buffer'. + + Returns the number of bytes read, 0 if EOF, -1 on failure. Prints an + error message to stderr on failure. + */ +ssize_t data_reader_read(data_reader_t *data, file_info_t *fi, + uint64_t offset, void *buffer, size_t size); + #endif /* DATA_READER_H */ diff --git a/lib/sqfs/data_reader.c b/lib/sqfs/data_reader.c index e33ffbb..27a8302 100644 --- a/lib/sqfs/data_reader.c +++ b/lib/sqfs/data_reader.c @@ -225,3 +225,84 @@ fail_sparse: perror("creating sparse output file"); return -1; } + +ssize_t data_reader_read(data_reader_t *data, file_info_t *fi, + uint64_t offset, void *buffer, size_t size) +{ + size_t i, diff, fragsz, count, total = 0; + off_t off; + char *ptr; + + /* work out block count and fragment size */ + fragsz = fi->size % data->block_size; + count = fi->size / data->block_size; + + if (fragsz != 0 && !(fi->flags & FILE_FLAG_HAS_FRAGMENT)) { + fragsz = 0; + ++count; + } + + /* work out block index and on-disk location */ + off = fi->startblock; + i = 0; + + while (offset > data->block_size && i < count) { + off += SQFS_ON_DISK_BLOCK_SIZE(fi->blocks[i++].size); + offset -= data->block_size; + } + + /* copy data from blocks */ + while (i < count && size > 0) { + diff = data->block_size - offset; + if (size < diff) + size = diff; + + if (SQFS_IS_SPARSE_BLOCK(fi->blocks[i].size)) { + memset(buffer, 0, diff); + } else { + if (precache_data_block(data, off, fi->blocks[i].size)) + return -1; + + memcpy(buffer, (char *)data->block + offset, diff); + off += SQFS_ON_DISK_BLOCK_SIZE(fi->blocks[i].size); + } + + ++i; + offset = 0; + size -= diff; + total += diff; + buffer = (char *)buffer + diff; + } + + /* copy from fragment */ + if (i == count && size > 0 && fragsz > 0) { + if (precache_fragment_block(data, fi->fragment)) + return -1; + + if (fi->fragment_offset >= data->frag_used) + goto fail_range; + + if ((fi->fragment_offset + fragsz - 1) >= data->frag_used) + goto fail_range; + + ptr = (char *)data->frag_block + fi->fragment_offset; + ptr += offset; + + if (offset >= fragsz) { + offset = 0; + size = 0; + } + + if (offset + size > fragsz) + size = fragsz - offset; + + if (size > 0) { + memcpy(buffer, ptr + offset, size); + total += size; + } + } + return total; +fail_range: + fputs("attempted to read past fragment block limits\n", stderr); + return -1; +} |