libsparse: add callback output file type

Add a new output file subclass that will call a callback for
each block as it is written.  Will be used to measure the space
used by each sparse block to allow resparsing files.

Also add sparse_file_callback, which will write out a sparse
file by calling the provided write function.

Change-Id: I18707bd9c357b68da319cc07982e93d1c2b2bee2
diff --git a/libsparse/include/sparse/sparse.h b/libsparse/include/sparse/sparse.h
index ae54955..b215227 100644
--- a/libsparse/include/sparse/sparse.h
+++ b/libsparse/include/sparse/sparse.h
@@ -158,6 +158,27 @@
 		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
  *
  * @s - sparse file cookie
diff --git a/libsparse/output_file.c b/libsparse/output_file.c
index 14b057a..dc56149 100644
--- a/libsparse/output_file.c
+++ b/libsparse/output_file.c
@@ -18,6 +18,7 @@
 #define _LARGEFILE64_SOURCE 1
 
 #include <fcntl.h>
+#include <limits.h>
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdlib.h>
@@ -112,6 +113,15 @@
 #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 @@
 	.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 @@
 	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 24496f7..7a9fa24 100644
--- a/libsparse/output_file.h
+++ b/libsparse/output_file.h
@@ -23,6 +23,9 @@
 
 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 d778e1d..c560ec9 100644
--- a/libsparse/sparse.c
+++ b/libsparse/sparse.c
@@ -99,20 +99,33 @@
 	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 @@
 			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 @@
 		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)