libsparse: Fix verbose_error string usage

Switch vnsprintf to StringPrintf to avoid off-by-one, and switch
sparse_read.c to cpp.

Test: fastboot flash normal image with smaller sparse limit

Change-Id: Ia399b167625deb271bfd0ee3273071306d71c4d4
diff --git a/libsparse/sparse_read.cpp b/libsparse/sparse_read.cpp
new file mode 100644
index 0000000..bd66873
--- /dev/null
+++ b/libsparse/sparse_read.cpp
@@ -0,0 +1,483 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _FILE_OFFSET_BITS 64
+#define _LARGEFILE64_SOURCE 1
+
+#include <algorithm>
+#include <inttypes.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
+#include <unistd.h>
+
+#include <sparse/sparse.h>
+
+#include "android-base/stringprintf.h"
+#include "defs.h"
+#include "output_file.h"
+#include "sparse_crc32.h"
+#include "sparse_file.h"
+#include "sparse_format.h"
+
+
+#if defined(__APPLE__) && defined(__MACH__)
+#define lseek64 lseek
+#define off64_t off_t
+#endif
+
+#define SPARSE_HEADER_MAJOR_VER 1
+#define SPARSE_HEADER_LEN       (sizeof(sparse_header_t))
+#define CHUNK_HEADER_LEN (sizeof(chunk_header_t))
+
+static constexpr int64_t COPY_BUF_SIZE = 1024 * 1024;
+static char *copybuf;
+
+static std::string ErrorString(int err)
+{
+	if (err == -EOVERFLOW) return "EOF while reading file";
+	if (err == -EINVAL) return "Invalid sparse file format";
+	if (err == -ENOMEM) return "Failed allocation while reading file";
+	return android::base::StringPrintf("Unknown error %d", err);
+}
+
+static void verbose_error(bool verbose, int err, const char *fmt, ...)
+{
+	if (!verbose) return;
+
+	std::string msg = ErrorString(err);
+	if (fmt) {
+		msg += " at ";
+		va_list argp;
+		va_start(argp, fmt);
+		android::base::StringAppendV(&msg, fmt, argp);
+		va_end(argp);
+	}
+	sparse_print_verbose("%s\n", msg.c_str());
+}
+
+static int process_raw_chunk(struct sparse_file *s, unsigned int chunk_size,
+		int fd, int64_t offset, unsigned int blocks, unsigned int block,
+		uint32_t *crc32)
+{
+	int ret;
+	int chunk;
+	int64_t len = blocks * s->block_size;
+
+	if (chunk_size % s->block_size != 0) {
+		return -EINVAL;
+	}
+
+	if (chunk_size / s->block_size != blocks) {
+		return -EINVAL;
+	}
+
+	ret = sparse_file_add_fd(s, fd, offset, len, block);
+	if (ret < 0) {
+		return ret;
+	}
+
+	if (crc32) {
+		while (len) {
+			chunk = std::min(len, COPY_BUF_SIZE);
+			ret = read_all(fd, copybuf, chunk);
+			if (ret < 0) {
+				return ret;
+			}
+			*crc32 = sparse_crc32(*crc32, copybuf, chunk);
+			len -= chunk;
+		}
+	} else {
+		lseek64(fd, len, SEEK_CUR);
+	}
+
+	return 0;
+}
+
+static int process_fill_chunk(struct sparse_file *s, unsigned int chunk_size,
+		int fd, unsigned int blocks, unsigned int block, uint32_t *crc32)
+{
+	int ret;
+	int chunk;
+	int64_t len = (int64_t)blocks * s->block_size;
+	uint32_t fill_val;
+	uint32_t *fillbuf;
+	unsigned int i;
+
+	if (chunk_size != sizeof(fill_val)) {
+		return -EINVAL;
+	}
+
+	ret = read_all(fd, &fill_val, sizeof(fill_val));
+	if (ret < 0) {
+		return ret;
+	}
+
+	ret = sparse_file_add_fill(s, fill_val, len, block);
+	if (ret < 0) {
+		return ret;
+	}
+
+	if (crc32) {
+		/* Fill copy_buf with the fill value */
+		fillbuf = (uint32_t *)copybuf;
+		for (i = 0; i < (COPY_BUF_SIZE / sizeof(fill_val)); i++) {
+			fillbuf[i] = fill_val;
+		}
+
+		while (len) {
+			chunk = std::min(len, COPY_BUF_SIZE);
+			*crc32 = sparse_crc32(*crc32, copybuf, chunk);
+			len -= chunk;
+		}
+	}
+
+	return 0;
+}
+
+static int process_skip_chunk(struct sparse_file *s, unsigned int chunk_size,
+		int fd __unused, unsigned int blocks,
+		unsigned int block __unused, uint32_t *crc32)
+{
+	if (chunk_size != 0) {
+		return -EINVAL;
+	}
+
+	if (crc32) {
+	        int64_t len = (int64_t)blocks * s->block_size;
+		memset(copybuf, 0, COPY_BUF_SIZE);
+
+		while (len) {
+			int chunk = std::min(len, COPY_BUF_SIZE);
+			*crc32 = sparse_crc32(*crc32, copybuf, chunk);
+			len -= chunk;
+		}
+	}
+
+	return 0;
+}
+
+static int process_crc32_chunk(int fd, unsigned int chunk_size, uint32_t *crc32)
+{
+	uint32_t file_crc32;
+	int ret;
+
+	if (chunk_size != sizeof(file_crc32)) {
+		return -EINVAL;
+	}
+
+	ret = read_all(fd, &file_crc32, sizeof(file_crc32));
+	if (ret < 0) {
+		return ret;
+	}
+
+	if (crc32 != NULL && file_crc32 != *crc32) {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int process_chunk(struct sparse_file *s, int fd, off64_t offset,
+		unsigned int chunk_hdr_sz, chunk_header_t *chunk_header,
+		unsigned int cur_block, uint32_t *crc_ptr)
+{
+	int ret;
+	unsigned int chunk_data_size;
+
+	chunk_data_size = chunk_header->total_sz - chunk_hdr_sz;
+
+	switch (chunk_header->chunk_type) {
+		case CHUNK_TYPE_RAW:
+			ret = process_raw_chunk(s, chunk_data_size, fd, offset,
+					chunk_header->chunk_sz, cur_block, crc_ptr);
+			if (ret < 0) {
+				verbose_error(s->verbose, ret, "data block at %" PRId64, offset);
+				return ret;
+			}
+			return chunk_header->chunk_sz;
+		case CHUNK_TYPE_FILL:
+			ret = process_fill_chunk(s, chunk_data_size, fd,
+					chunk_header->chunk_sz, cur_block, crc_ptr);
+			if (ret < 0) {
+				verbose_error(s->verbose, ret, "fill block at %" PRId64, offset);
+				return ret;
+			}
+			return chunk_header->chunk_sz;
+		case CHUNK_TYPE_DONT_CARE:
+			ret = process_skip_chunk(s, chunk_data_size, fd,
+					chunk_header->chunk_sz, cur_block, crc_ptr);
+			if (chunk_data_size != 0) {
+				if (ret < 0) {
+					verbose_error(s->verbose, ret, "skip block at %" PRId64, offset);
+					return ret;
+				}
+			}
+			return chunk_header->chunk_sz;
+		case CHUNK_TYPE_CRC32:
+			ret = process_crc32_chunk(fd, chunk_data_size, crc_ptr);
+			if (ret < 0) {
+				verbose_error(s->verbose, -EINVAL, "crc block at %" PRId64,
+						offset);
+				return ret;
+			}
+			return 0;
+		default:
+			verbose_error(s->verbose, -EINVAL, "unknown block %04X at %" PRId64,
+					chunk_header->chunk_type, offset);
+	}
+
+	return 0;
+}
+
+static int sparse_file_read_sparse(struct sparse_file *s, int fd, bool crc)
+{
+	int ret;
+	unsigned int i;
+	sparse_header_t sparse_header;
+	chunk_header_t chunk_header;
+	uint32_t crc32 = 0;
+	uint32_t *crc_ptr = 0;
+	unsigned int cur_block = 0;
+	off64_t offset;
+
+	if (!copybuf) {
+		copybuf = (char *)malloc(COPY_BUF_SIZE);
+	}
+
+	if (!copybuf) {
+		return -ENOMEM;
+	}
+
+	if (crc) {
+		crc_ptr = &crc32;
+	}
+
+	ret = read_all(fd, &sparse_header, sizeof(sparse_header));
+	if (ret < 0) {
+		return ret;
+	}
+
+	if (sparse_header.magic != SPARSE_HEADER_MAGIC) {
+		return -EINVAL;
+	}
+
+	if (sparse_header.major_version != SPARSE_HEADER_MAJOR_VER) {
+		return -EINVAL;
+	}
+
+	if (sparse_header.file_hdr_sz < SPARSE_HEADER_LEN) {
+		return -EINVAL;
+	}
+
+	if (sparse_header.chunk_hdr_sz < sizeof(chunk_header)) {
+		return -EINVAL;
+	}
+
+	if (sparse_header.file_hdr_sz > SPARSE_HEADER_LEN) {
+		/* Skip the remaining bytes in a header that is longer than
+		 * we expected.
+		 */
+		lseek64(fd, sparse_header.file_hdr_sz - SPARSE_HEADER_LEN, SEEK_CUR);
+	}
+
+	for (i = 0; i < sparse_header.total_chunks; i++) {
+		ret = read_all(fd, &chunk_header, sizeof(chunk_header));
+		if (ret < 0) {
+			return ret;
+		}
+
+		if (sparse_header.chunk_hdr_sz > CHUNK_HEADER_LEN) {
+			/* Skip the remaining bytes in a header that is longer than
+			 * we expected.
+			 */
+			lseek64(fd, sparse_header.chunk_hdr_sz - CHUNK_HEADER_LEN, SEEK_CUR);
+		}
+
+		offset = lseek64(fd, 0, SEEK_CUR);
+
+		ret = process_chunk(s, fd, offset, sparse_header.chunk_hdr_sz, &chunk_header,
+				cur_block, crc_ptr);
+		if (ret < 0) {
+			return ret;
+		}
+
+		cur_block += ret;
+	}
+
+	if (sparse_header.total_blks != cur_block) {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int sparse_file_read_normal(struct sparse_file *s, int fd)
+{
+	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 to_read;
+	unsigned int i;
+	bool sparse_block;
+
+	if (!buf) {
+		return -ENOMEM;
+	}
+
+	while (remain > 0) {
+		to_read = std::min(remain, (int64_t)(s->block_size));
+		ret = read_all(fd, buf, to_read);
+		if (ret < 0) {
+			error("failed to read sparse file");
+			free(buf);
+			return ret;
+		}
+
+		if (to_read == s->block_size) {
+			sparse_block = true;
+			for (i = 1; i < s->block_size / sizeof(uint32_t); i++) {
+				if (buf[0] != buf[i]) {
+					sparse_block = false;
+					break;
+				}
+			}
+		} else {
+			sparse_block = false;
+		}
+
+		if (sparse_block) {
+			/* TODO: add flag to use skip instead of fill for buf[0] == 0 */
+			sparse_file_add_fill(s, buf[0], to_read, block);
+		} else {
+			sparse_file_add_fd(s, fd, offset, to_read, block);
+		}
+
+		remain -= to_read;
+		offset += to_read;
+		block++;
+	}
+
+	free(buf);
+	return 0;
+}
+
+int sparse_file_read(struct sparse_file *s, int fd, bool sparse, bool crc)
+{
+	if (crc && !sparse) {
+		return -EINVAL;
+	}
+
+	if (sparse) {
+		return sparse_file_read_sparse(s, fd, crc);
+	} else {
+		return sparse_file_read_normal(s, fd);
+	}
+}
+
+struct sparse_file *sparse_file_import(int fd, bool verbose, bool crc)
+{
+	int ret;
+	sparse_header_t sparse_header;
+	int64_t len;
+	struct sparse_file *s;
+
+	ret = read_all(fd, &sparse_header, sizeof(sparse_header));
+	if (ret < 0) {
+		verbose_error(verbose, ret, "header");
+		return NULL;
+	}
+
+	if (sparse_header.magic != SPARSE_HEADER_MAGIC) {
+		verbose_error(verbose, -EINVAL, "header magic");
+		return NULL;
+	}
+
+	if (sparse_header.major_version != SPARSE_HEADER_MAJOR_VER) {
+		verbose_error(verbose, -EINVAL, "header major version");
+		return NULL;
+	}
+
+	if (sparse_header.file_hdr_sz < SPARSE_HEADER_LEN) {
+		return NULL;
+	}
+
+	if (sparse_header.chunk_hdr_sz < sizeof(chunk_header_t)) {
+		return NULL;
+	}
+
+	len = (int64_t)sparse_header.total_blks * sparse_header.blk_sz;
+	s = sparse_file_new(sparse_header.blk_sz, len);
+	if (!s) {
+		verbose_error(verbose, -EINVAL, NULL);
+		return NULL;
+	}
+
+	ret = lseek64(fd, 0, SEEK_SET);
+	if (ret < 0) {
+		verbose_error(verbose, ret, "seeking");
+		sparse_file_destroy(s);
+		return NULL;
+	}
+
+	s->verbose = verbose;
+
+	ret = sparse_file_read(s, fd, true, crc);
+	if (ret < 0) {
+		sparse_file_destroy(s);
+		return NULL;
+	}
+
+	return s;
+}
+
+struct sparse_file *sparse_file_import_auto(int fd, bool crc, bool verbose)
+{
+	struct sparse_file *s;
+	int64_t len;
+	int ret;
+
+	s = sparse_file_import(fd, verbose, crc);
+	if (s) {
+		return s;
+	}
+
+	len = lseek64(fd, 0, SEEK_END);
+	if (len < 0) {
+		return NULL;
+	}
+
+	lseek64(fd, 0, SEEK_SET);
+
+	s = sparse_file_new(4096, len);
+	if (!s) {
+		return NULL;
+	}
+
+	ret = sparse_file_read_normal(s, fd);
+	if (ret < 0) {
+		sparse_file_destroy(s);
+		return NULL;
+	}
+
+	return s;
+}