diff --git a/libsparse/include/sparse/sparse.h b/libsparse/include/sparse/sparse.h index ae5495599..b2152270b 100644 --- a/libsparse/include/sparse/sparse.h +++ b/libsparse/include/sparse/sparse.h @@ -157,6 +157,27 @@ int sparse_file_add_fd(struct sparse_file *s, int sparse_file_write(struct sparse_file *s, int fd, bool gz, bool sparse, bool crc); +/** + * sparse_file_callback - call a callback for blocks in sparse file + * + * @s - sparse file cookie + * @sparse - write in the Android sparse file format + * @crc - append a crc chunk + * @write - function to call for each block + * @priv - value that will be passed as the first argument to write + * + * Writes a sparse file by calling a callback function. If sparse is true, the + * file will be written in the Android sparse file format. If crc is true, the + * crc of the expanded data will be calculated and appended in a crc chunk. + * The callback 'write' will be called with data and length for each data, + * and with data==NULL to skip over a region (only used for non-sparse format). + * The callback should return negative on error, 0 on success. + * + * Returns 0 on success, negative errno on error. + */ +int sparse_file_callback(struct sparse_file *s, bool sparse, bool crc, + int (*write)(void *priv, const void *data, int len), void *priv); + /** * sparse_file_read - read a file into a sparse file cookie * diff --git a/libsparse/output_file.c b/libsparse/output_file.c index 14b057a9e..dc56149e7 100644 --- a/libsparse/output_file.c +++ b/libsparse/output_file.c @@ -18,6 +18,7 @@ #define _LARGEFILE64_SOURCE 1 #include +#include #include #include #include @@ -112,6 +113,15 @@ struct output_file_normal { #define to_output_file_normal(_o) \ container_of((_o), struct output_file_normal, out) +struct output_file_callback { + struct output_file out; + void *priv; + int (*write)(void *priv, const void *buf, int len); +}; + +#define to_output_file_callback(_o) \ + container_of((_o), struct output_file_callback, out) + static int file_open(struct output_file *out, int fd) { struct output_file_normal *outn = to_output_file_normal(out); @@ -262,6 +272,57 @@ static struct output_file_ops gz_file_ops = { .close = gz_file_close, }; +static int callback_file_open(struct output_file *out, int fd) +{ + return 0; +} + +static int callback_file_skip(struct output_file *out, int64_t off) +{ + struct output_file_callback *outc = to_output_file_callback(out); + int to_write; + int ret; + + while (off > 0) { + to_write = min(off, (int64_t)INT_MAX); + ret = outc->write(outc->priv, NULL, to_write); + if (ret < 0) { + return ret; + } + off -= to_write; + } + + return 0; +} + +static int callback_file_pad(struct output_file *out, int64_t len) +{ + return -1; +} + +static int callback_file_write(struct output_file *out, void *data, int len) +{ + int ret; + struct output_file_callback *outc = to_output_file_callback(out); + + return outc->write(outc->priv, data, len); +} + +static void callback_file_close(struct output_file *out) +{ + struct output_file_callback *outc = to_output_file_callback(out); + + free(outc); +} + +static struct output_file_ops callback_file_ops = { + .open = callback_file_open, + .skip = callback_file_skip, + .pad = callback_file_pad, + .write = callback_file_write, + .close = callback_file_close, +}; + int read_all(int fd, void *buf, size_t len) { size_t total = 0; @@ -577,6 +638,32 @@ static struct output_file *output_file_new_normal(void) return &outn->out; } +struct output_file *open_output_callback(int (*write)(void *, const void *, int), + void *priv, unsigned int block_size, int64_t len, int gz, int sparse, + int chunks, int crc) +{ + int ret; + struct output_file_callback *outc; + + outc = calloc(1, sizeof(struct output_file_callback)); + if (!outc) { + error_errno("malloc struct outc"); + return NULL; + } + + outc->out.ops = &callback_file_ops; + outc->priv = priv; + outc->write = write; + + ret = output_file_init(&outc->out, block_size, len, sparse, chunks, crc); + if (ret < 0) { + free(outc); + return NULL; + } + + return &outc->out; +} + struct output_file *open_output_fd(int fd, unsigned int block_size, int64_t len, int gz, int sparse, int chunks, int crc) { diff --git a/libsparse/output_file.h b/libsparse/output_file.h index 24496f7d4..7a9fa2434 100644 --- a/libsparse/output_file.h +++ b/libsparse/output_file.h @@ -23,6 +23,9 @@ struct output_file; struct output_file *open_output_fd(int fd, unsigned int block_size, int64_t len, int gz, int sparse, int chunks, int crc); +struct output_file *open_output_callback(int (*write)(void *, const void *, int), + void *priv, unsigned int block_size, int64_t len, int gz, int sparse, + int chunks, int crc); int write_data_chunk(struct output_file *out, unsigned int len, void *data); int write_fill_chunk(struct output_file *out, unsigned int len, uint32_t fill_val); diff --git a/libsparse/sparse.c b/libsparse/sparse.c index d778e1dc6..c560ec93c 100644 --- a/libsparse/sparse.c +++ b/libsparse/sparse.c @@ -99,20 +99,33 @@ unsigned int sparse_count_chunks(struct sparse_file *s) return chunks; } -int sparse_file_write(struct sparse_file *s, int fd, bool gz, bool sparse, - bool crc) +static void sparse_file_write_block(struct output_file *out, + struct backed_block *bb) +{ + switch (backed_block_type(bb)) { + case BACKED_BLOCK_DATA: + write_data_chunk(out, backed_block_len(bb), backed_block_data(bb)); + break; + case BACKED_BLOCK_FILE: + write_file_chunk(out, backed_block_len(bb), + backed_block_filename(bb), backed_block_file_offset(bb)); + break; + case BACKED_BLOCK_FD: + write_fd_chunk(out, backed_block_len(bb), + backed_block_fd(bb), backed_block_file_offset(bb)); + break; + case BACKED_BLOCK_FILL: + write_fill_chunk(out, backed_block_len(bb), + backed_block_fill_val(bb)); + break; + } +} + +static int write_all_blocks(struct sparse_file *s, struct output_file *out) { struct backed_block *bb; unsigned int last_block = 0; int64_t pad; - int chunks; - struct output_file *out; - - chunks = sparse_count_chunks(s); - out = open_output_fd(fd, s->block_size, s->len, gz, sparse, chunks, crc); - - if (!out) - return -ENOMEM; for (bb = backed_block_iter_new(s->backed_block_list); bb; bb = backed_block_iter_next(bb)) { @@ -120,23 +133,7 @@ int sparse_file_write(struct sparse_file *s, int fd, bool gz, bool sparse, unsigned int blocks = backed_block_block(bb) - last_block; write_skip_chunk(out, (int64_t)blocks * s->block_size); } - switch (backed_block_type(bb)) { - case BACKED_BLOCK_DATA: - write_data_chunk(out, backed_block_len(bb), backed_block_data(bb)); - break; - case BACKED_BLOCK_FILE: - write_file_chunk(out, backed_block_len(bb), - backed_block_filename(bb), backed_block_file_offset(bb)); - break; - case BACKED_BLOCK_FD: - write_fd_chunk(out, backed_block_len(bb), - backed_block_fd(bb), backed_block_file_offset(bb)); - break; - case BACKED_BLOCK_FILL: - write_fill_chunk(out, backed_block_len(bb), - backed_block_fill_val(bb)); - break; - } + sparse_file_write_block(out, bb); last_block = backed_block_block(bb) + DIV_ROUND_UP(backed_block_len(bb), s->block_size); } @@ -147,9 +144,48 @@ int sparse_file_write(struct sparse_file *s, int fd, bool gz, bool sparse, write_skip_chunk(out, pad); } + return 0; +} + +int sparse_file_write(struct sparse_file *s, int fd, bool gz, bool sparse, + bool crc) +{ + int ret; + int chunks; + struct output_file *out; + + chunks = sparse_count_chunks(s); + out = open_output_fd(fd, s->block_size, s->len, gz, sparse, chunks, crc); + + if (!out) + return -ENOMEM; + + ret = write_all_blocks(s, out); + close_output_file(out); - return 0; + return ret; +} + +int sparse_file_callback(struct sparse_file *s, bool sparse, bool crc, + int (*write)(void *priv, const void *data, int len), void *priv) +{ + int ret; + int chunks; + struct output_file *out; + + chunks = sparse_count_chunks(s); + out = open_output_callback(write, priv, s->block_size, s->len, false, + sparse, chunks, crc); + + if (!out) + return -ENOMEM; + + ret = write_all_blocks(s, out); + + close_output_file(out); + + return ret; } void sparse_file_verbose(struct sparse_file *s)