Merge changes from topic "sparse-file-read-enum"

* changes:
  libsparse: Add "hole" mode to sparse_file_read
  libsparse: Split off most of sparse_file_read_normal into a helper function
diff --git a/libsparse/img2simg.cpp b/libsparse/img2simg.cpp
index 4c2c6ca..3e24cc0 100644
--- a/libsparse/img2simg.cpp
+++ b/libsparse/img2simg.cpp
@@ -93,7 +93,7 @@
   }
 
   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 9f91269..7c52c3f 100644
--- a/libsparse/include/sparse/sparse.h
+++ b/libsparse/include/sparse/sparse.h
@@ -225,23 +225,42 @@
 	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 0f39172..028b6be 100644
--- a/libsparse/sparse_read.cpp
+++ b/libsparse/sparse_read.cpp
@@ -457,12 +457,10 @@
   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 @@
     ret = read_all(fd, buf, to_read);
     if (ret < 0) {
       error("failed to read sparse file");
-      free(buf);
       return ret;
     }
 
@@ -504,20 +501,93 @@
     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;
   }
 }