diff --git a/libsparse/img2simg.cpp b/libsparse/img2simg.cpp index 4c2c6ca6f..3e24cc014 100644 --- a/libsparse/img2simg.cpp +++ b/libsparse/img2simg.cpp @@ -93,7 +93,7 @@ int main(int argc, char* argv[]) { } sparse_file_verbose(s); - ret = sparse_file_read(s, in, false, false); + ret = sparse_file_read(s, in, SPARSE_READ_MODE_NORMAL, false); if (ret) { fprintf(stderr, "Failed to read file\n"); exit(-1); diff --git a/libsparse/include/sparse/sparse.h b/libsparse/include/sparse/sparse.h index 9f912697a..7c52c3f90 100644 --- a/libsparse/include/sparse/sparse.h +++ b/libsparse/include/sparse/sparse.h @@ -225,23 +225,42 @@ int sparse_file_foreach_chunk(struct sparse_file *s, bool sparse, bool crc, int (*write)(void *priv, const void *data, size_t len, unsigned int block, unsigned int nr_blocks), void *priv); + +/** + * enum sparse_read_mode - The method to use when reading in files + * @SPARSE_READ_MODE_NORMAL: The input is a regular file. Constant chunks of + * data (including holes) will be be converted to + * fill chunks. + * @SPARSE_READ_MODE_SPARSE: The input is an Android sparse file. + * @SPARSE_READ_MODE_HOLE: The input is a regular file. Holes will be converted + * to "don't care" chunks. Other constant chunks will + * be converted to fill chunks. + */ +enum sparse_read_mode { + SPARSE_READ_MODE_NORMAL = false, + SPARSE_READ_MODE_SPARSE = true, + SPARSE_READ_MODE_HOLE, +}; + /** * sparse_file_read - read a file into a sparse file cookie * * @s - sparse file cookie * @fd - file descriptor to read from - * @sparse - read a file in the Android sparse file format + * @mode - mode to use when reading the input file * @crc - verify the crc of a file in the Android sparse file format * - * Reads a file into a sparse file cookie. If sparse is true, the file is - * assumed to be in the Android sparse file format. If sparse is false, the - * file will be sparsed by looking for block aligned chunks of all zeros or - * another 32 bit value. If crc is true, the crc of the sparse file will be - * verified. + * Reads a file into a sparse file cookie. If @mode is + * %SPARSE_READ_MODE_SPARSE, the file is assumed to be in the Android sparse + * file format. If @mode is %SPARSE_READ_MODE_NORMAL, the file will be sparsed + * by looking for block aligned chunks of all zeros or another 32 bit value. If + * @mode is %SPARSE_READ_MODE_HOLE, the file will be sparsed like + * %SPARSE_READ_MODE_NORMAL, but holes in the file will be converted to "don't + * care" chunks. If crc is true, the crc of the sparse file will be verified. * * Returns 0 on success, negative errno on error. */ -int sparse_file_read(struct sparse_file *s, int fd, bool sparse, bool crc); +int sparse_file_read(struct sparse_file *s, int fd, enum sparse_read_mode mode, bool crc); /** * sparse_file_import - import an existing sparse file diff --git a/libsparse/sparse_read.cpp b/libsparse/sparse_read.cpp index 0f39172e2..028b6bed1 100644 --- a/libsparse/sparse_read.cpp +++ b/libsparse/sparse_read.cpp @@ -457,12 +457,10 @@ static int sparse_file_read_sparse(struct sparse_file* s, SparseFileSource* sour return 0; } -static int sparse_file_read_normal(struct sparse_file* s, int fd) { +static int do_sparse_file_read_normal(struct sparse_file* s, int fd, uint32_t* buf, int64_t offset, + int64_t remain) { int ret; - uint32_t* buf = (uint32_t*)malloc(s->block_size); - unsigned int block = 0; - int64_t remain = s->len; - int64_t offset = 0; + unsigned int block = offset / s->block_size; unsigned int to_read; unsigned int i; bool sparse_block; @@ -476,7 +474,6 @@ static int sparse_file_read_normal(struct sparse_file* s, int fd) { ret = read_all(fd, buf, to_read); if (ret < 0) { error("failed to read sparse file"); - free(buf); return ret; } @@ -504,20 +501,93 @@ static int sparse_file_read_normal(struct sparse_file* s, int fd) { block++; } - free(buf); return 0; } -int sparse_file_read(struct sparse_file* s, int fd, bool sparse, bool crc) { - if (crc && !sparse) { +static int sparse_file_read_normal(struct sparse_file* s, int fd) { + int ret; + uint32_t* buf = (uint32_t*)malloc(s->block_size); + + if (!buf) + return -ENOMEM; + + ret = do_sparse_file_read_normal(s, fd, buf, 0, s->len); + free(buf); + return ret; +} + +#ifdef __linux__ +static int sparse_file_read_hole(struct sparse_file* s, int fd) { + int ret; + uint32_t* buf = (uint32_t*)malloc(s->block_size); + int64_t end = 0; + int64_t start = 0; + + if (!buf) { + return -ENOMEM; + } + + do { + start = lseek(fd, end, SEEK_DATA); + if (start < 0) { + if (errno == ENXIO) + /* The rest of the file is a hole */ + break; + + error("could not seek to data"); + free(buf); + return -errno; + } else if (start > s->len) { + break; + } + + end = lseek(fd, start, SEEK_HOLE); + if (end < 0) { + error("could not seek to end"); + free(buf); + return -errno; + } + end = std::min(end, s->len); + + start = ALIGN_DOWN(start, s->block_size); + end = ALIGN(end, s->block_size); + if (lseek(fd, start, SEEK_SET) < 0) { + free(buf); + return -errno; + } + + ret = do_sparse_file_read_normal(s, fd, buf, start, end - start); + if (ret) { + free(buf); + return ret; + } + } while (end < s->len); + + free(buf); + return 0; +} +#else +static int sparse_file_read_hole(struct sparse_file* s __unused, int fd __unused) { + return -ENOTSUP; +} +#endif + +int sparse_file_read(struct sparse_file* s, int fd, enum sparse_read_mode mode, bool crc) { + if (crc && mode != SPARSE_READ_MODE_SPARSE) { return -EINVAL; } - if (sparse) { - SparseFileFdSource source(fd); - return sparse_file_read_sparse(s, &source, crc); - } else { - return sparse_file_read_normal(s, fd); + switch (mode) { + case SPARSE_READ_MODE_SPARSE: { + SparseFileFdSource source(fd); + return sparse_file_read_sparse(s, &source, crc); + } + case SPARSE_READ_MODE_NORMAL: + return sparse_file_read_normal(s, fd); + case SPARSE_READ_MODE_HOLE: + return sparse_file_read_hole(s, fd); + default: + return -EINVAL; } }