libsparse: add support for including fds

Add sparse_file_add_fd to include all or part of the contents
of an fd in the output file.  Will be useful for re-sparsing files
where fd will point to the input sparse file.

Change-Id: I5d4ab07fb37231e8e9c1912f62a2968c8b0a00ef
diff --git a/libsparse/backed_block.c b/libsparse/backed_block.c
index b259190..8c3fab0 100644
--- a/libsparse/backed_block.c
+++ b/libsparse/backed_block.c
@@ -35,6 +35,10 @@
 			int64_t offset;
 		} file;
 		struct {
+			int fd;
+			int64_t offset;
+		} fd;
+		struct {
 			uint32_t val;
 		} fill;
 	};
@@ -78,10 +82,20 @@
 	return bb->file.filename;
 }
 
+int backed_block_fd(struct backed_block *bb)
+{
+	assert(bb->type == BACKED_BLOCK_FD);
+	return bb->fd.fd;
+}
+
 int64_t backed_block_file_offset(struct backed_block *bb)
 {
-	assert(bb->type == BACKED_BLOCK_FILE);
-	return bb->file.offset;
+	assert(bb->type == BACKED_BLOCK_FILE || bb->type == BACKED_BLOCK_FD);
+	if (bb->type == BACKED_BLOCK_FILE) {
+		return bb->file.offset;
+	} else { /* bb->type == BACKED_BLOCK_FD */
+		return bb->fd.offset;
+	}
 }
 
 uint32_t backed_block_fill_val(struct backed_block *bb)
@@ -211,3 +225,22 @@
 
 	return queue_bb(bbl, bb);
 }
+
+/* Queues a chunk of a fd to be written to the specified data blocks */
+int backed_block_add_fd(struct backed_block_list *bbl, int fd, int64_t offset,
+		unsigned int len, unsigned int block)
+{
+	struct backed_block *bb = calloc(1, sizeof(struct backed_block));
+	if (bb == NULL) {
+		return -ENOMEM;
+	}
+
+	bb->block = block;
+	bb->len = len;
+	bb->type = BACKED_BLOCK_FD;
+	bb->fd.fd = fd;
+	bb->fd.offset = offset;
+	bb->next = NULL;
+
+	return queue_bb(bbl, bb);
+}
diff --git a/libsparse/backed_block.h b/libsparse/backed_block.h
index 3166505..ca2ad1d 100644
--- a/libsparse/backed_block.h
+++ b/libsparse/backed_block.h
@@ -25,6 +25,7 @@
 enum backed_block_type {
 	BACKED_BLOCK_DATA,
 	BACKED_BLOCK_FILE,
+	BACKED_BLOCK_FD,
 	BACKED_BLOCK_FILL,
 };
 
@@ -34,6 +35,8 @@
 		unsigned int len, unsigned int block);
 int backed_block_add_file(struct backed_block_list *bbl, const char *filename,
 		int64_t offset, unsigned int len, unsigned int block);
+int backed_block_add_fd(struct backed_block_list *bbl, int fd,
+		int64_t offset, unsigned int len, unsigned int block);
 
 struct backed_block *backed_block_iter_new(struct backed_block_list *bbl);
 struct backed_block *backed_block_iter_next(struct backed_block *bb);
@@ -41,6 +44,7 @@
 unsigned int backed_block_block(struct backed_block *bb);
 void *backed_block_data(struct backed_block *bb);
 const char *backed_block_filename(struct backed_block *bb);
+int backed_block_fd(struct backed_block *bb);
 int64_t backed_block_file_offset(struct backed_block *bb);
 uint32_t backed_block_fill_val(struct backed_block *bb);
 enum backed_block_type backed_block_type(struct backed_block *bb);
diff --git a/libsparse/include/sparse/sparse.h b/libsparse/include/sparse/sparse.h
index db06884..6484333 100644
--- a/libsparse/include/sparse/sparse.h
+++ b/libsparse/include/sparse/sparse.h
@@ -111,6 +111,32 @@
 		unsigned int block);
 
 /**
+ * sparse_file_add_file - associate a chunk of a file with a sparse file
+ *
+ * @s - sparse file cookie
+ * @filename - filename of the file to be copied
+ * @file_offset - offset into the copied file
+ * @len - length of the copied block
+ * @block - offset in blocks into the sparse file to place the file chunk
+ *
+ * Associates a chunk of an existing fd with a sparse file cookie.
+ * The region [block * block_size : block * block_size + len) must not already
+ * be used in the sparse file. If len is not a multiple of the block size the
+ * data will be padded with zeros.
+ *
+ * Allows adding large amounts of data to a sparse file without needing to keep
+ * it all mapped.  File size is limited by available virtual address space,
+ * exceptionally large files may need to be added in multiple chunks.
+ *
+ * The fd must remain open until the sparse file is closed or the fd block is
+ * removed from the sparse file.
+ *
+ * Returns 0 on success, negative errno on error.
+ */
+int sparse_file_add_fd(struct sparse_file *s,
+		int fd, int64_t file_offset, unsigned int len, unsigned int block);
+
+/**
  * sparse_file_write - write a sparse file to a file
  *
  * @s - sparse file cookie
diff --git a/libsparse/output_file.c b/libsparse/output_file.c
index f911f8c..4193fd1 100644
--- a/libsparse/output_file.c
+++ b/libsparse/output_file.c
@@ -511,38 +511,28 @@
 	return out->sparse_ops->write_fill_chunk(out, len, fill_val);
 }
 
-/* Write a contiguous region of data blocks from a file */
-int write_file_chunk(struct output_file *out, unsigned int len,
-		const char *file, int64_t offset)
+int write_fd_chunk(struct output_file *out, unsigned int len,
+		int fd, int64_t offset)
 {
 	int ret;
 	int64_t aligned_offset;
 	int aligned_diff;
 	int buffer_size;
 
-	int file_fd = open(file, O_RDONLY | O_BINARY);
-	if (file_fd < 0) {
-		return -errno;
-	}
-
 	aligned_offset = offset & ~(4096 - 1);
 	aligned_diff = offset - aligned_offset;
 	buffer_size = len + aligned_diff;
 
 #ifndef USE_MINGW
-	char *data = mmap64(NULL, buffer_size, PROT_READ, MAP_SHARED, file_fd,
+	char *data = mmap64(NULL, buffer_size, PROT_READ, MAP_SHARED, fd,
 			aligned_offset);
 	if (data == MAP_FAILED) {
-		ret = -errno;
-		close(file_fd);
-		return ret;
+		return -errno;
 	}
 #else
 	char *data = malloc(buffer_size);
 	if (!data) {
-		ret = -errno;
-		close(file_fd);
-		return ret;
+		return -errno;
 	}
 	memset(data, 0, buffer_size);
 #endif
@@ -554,6 +544,23 @@
 #else
 	free(data);
 #endif
+
+	return ret;
+}
+
+/* Write a contiguous region of data blocks from a file */
+int write_file_chunk(struct output_file *out, unsigned int len,
+		const char *file, int64_t offset)
+{
+	int ret;
+
+	int file_fd = open(file, O_RDONLY | O_BINARY);
+	if (file_fd < 0) {
+		return -errno;
+	}
+
+	ret = write_fd_chunk(out, len, file_fd, offset);
+
 	close(file_fd);
 
 	return ret;
diff --git a/libsparse/output_file.h b/libsparse/output_file.h
index cb2feb7..d23abf3 100644
--- a/libsparse/output_file.h
+++ b/libsparse/output_file.h
@@ -31,6 +31,8 @@
 		uint32_t fill_val);
 int write_file_chunk(struct output_file *out, unsigned int len,
 		const char *file, int64_t offset);
+int write_fd_chunk(struct output_file *out, unsigned int len,
+		int fd, int64_t offset);
 int write_skip_chunk(struct output_file *out, int64_t len);
 void close_output_file(struct output_file *out);
 
diff --git a/libsparse/sparse.c b/libsparse/sparse.c
index fce9dbb..4ebcf0f 100644
--- a/libsparse/sparse.c
+++ b/libsparse/sparse.c
@@ -70,6 +70,12 @@
 			len, block);
 }
 
+int sparse_file_add_fd(struct sparse_file *s,
+		int fd, int64_t file_offset, unsigned int len, unsigned int block)
+{
+	return backed_block_add_fd(s->backed_block_list, fd, file_offset,
+			len, block);
+}
 unsigned int sparse_count_chunks(struct sparse_file *s)
 {
 	struct backed_block *bb;
@@ -122,6 +128,10 @@
 			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));