From a7eeb22efb88d8b8073d71138bcb31cb3f2c4f4d Mon Sep 17 00:00:00 2001 From: Chris Fries <cfries@google.com> Date: Mon, 17 Apr 2017 21:53:16 -0500 Subject: 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 --- libsparse/Android.bp | 15 +- libsparse/output_file.h | 8 + libsparse/sparse_file.h | 7 + libsparse/sparse_format.h | 8 + libsparse/sparse_read.c | 508 ---------------------------------------------- libsparse/sparse_read.cpp | 483 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 518 insertions(+), 511 deletions(-) delete mode 100644 libsparse/sparse_read.c create mode 100644 libsparse/sparse_read.cpp (limited to 'libsparse') diff --git a/libsparse/Android.bp b/libsparse/Android.bp index dd8b5fd1d..6ec0991c7 100644 --- a/libsparse/Android.bp +++ b/libsparse/Android.bp @@ -10,17 +10,23 @@ cc_library { "sparse.c", "sparse_crc32.c", "sparse_err.c", - "sparse_read.c", + "sparse_read.cpp", ], cflags: ["-Werror"], local_include_dirs: ["include"], export_include_dirs: ["include"], target: { host: { - shared_libs: ["libz-host"], + shared_libs: [ + "libz-host", + "libbase", + ], }, android: { - shared_libs: ["libz"], + shared_libs: [ + "libz", + "libbase", + ], }, windows: { enabled: true, @@ -38,6 +44,7 @@ cc_binary { static_libs: [ "libsparse", "libz", + "libbase", ], cflags: ["-Werror"], @@ -50,6 +57,7 @@ cc_binary { static_libs: [ "libsparse", "libz", + "libbase", ], cflags: ["-Werror"], @@ -61,6 +69,7 @@ cc_binary_host { static_libs: [ "libsparse", "libz", + "libbase", ], cflags: ["-Werror"], diff --git a/libsparse/output_file.h b/libsparse/output_file.h index 474c1fc21..b67e94ecb 100644 --- a/libsparse/output_file.h +++ b/libsparse/output_file.h @@ -17,6 +17,10 @@ #ifndef _OUTPUT_FILE_H_ #define _OUTPUT_FILE_H_ +#ifdef __cplusplus +extern "C" { +#endif + #include <sparse/sparse.h> struct output_file; @@ -38,4 +42,8 @@ void output_file_close(struct output_file *out); int read_all(int fd, void *buf, size_t len); +#ifdef __cplusplus +} +#endif + #endif diff --git a/libsparse/sparse_file.h b/libsparse/sparse_file.h index 91a12e678..763f43f67 100644 --- a/libsparse/sparse_file.h +++ b/libsparse/sparse_file.h @@ -17,6 +17,10 @@ #ifndef _LIBSPARSE_SPARSE_FILE_H_ #define _LIBSPARSE_SPARSE_FILE_H_ +#ifdef __cplusplus +extern "C" { +#endif + #include <sparse/sparse.h> struct sparse_file { @@ -28,5 +32,8 @@ struct sparse_file { struct output_file *out; }; +#ifdef __cplusplus +} +#endif #endif /* _LIBSPARSE_SPARSE_FILE_H_ */ diff --git a/libsparse/sparse_format.h b/libsparse/sparse_format.h index c41f12a54..779e0385d 100644 --- a/libsparse/sparse_format.h +++ b/libsparse/sparse_format.h @@ -18,6 +18,10 @@ #define _LIBSPARSE_SPARSE_FORMAT_H_ #include "sparse_defs.h" +#ifdef __cplusplus +extern "C" { +#endif + typedef struct sparse_header { __le32 magic; /* 0xed26ff3a */ __le16 major_version; /* (0x1) - reject images with higher major versions */ @@ -52,4 +56,8 @@ typedef struct chunk_header { * For a CRC32 chunk, it's 4 bytes of CRC32 */ +#ifdef __cplusplus +} +#endif + #endif diff --git a/libsparse/sparse_read.c b/libsparse/sparse_read.c deleted file mode 100644 index a18820277..000000000 --- a/libsparse/sparse_read.c +++ /dev/null @@ -1,508 +0,0 @@ -/* - * 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 _GNU_SOURCE -#define _FILE_OFFSET_BITS 64 -#define _LARGEFILE64_SOURCE 1 - -#include <inttypes.h> -#include <fcntl.h> -#include <stdarg.h> -#include <stdbool.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include <sparse/sparse.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)) - -#define COPY_BUF_SIZE (1024U*1024U) -static char *copybuf; - -#define min(a, b) \ - ({ typeof(a) _a = (a); typeof(b) _b = (b); (_a < _b) ? _a : _b; }) - -static void verbose_error(bool verbose, int err, const char *fmt, ...) -{ - char *s = ""; - char *at = ""; - if (fmt) { - va_list argp; - int size; - - va_start(argp, fmt); - size = vsnprintf(NULL, 0, fmt, argp); - va_end(argp); - - if (size < 0) { - return; - } - - at = malloc(size + 1); - if (at == NULL) { - return; - } - - va_start(argp, fmt); - vsnprintf(at, size, fmt, argp); - va_end(argp); - at[size] = 0; - s = " at "; - } - if (verbose) { -#ifndef _WIN32 - if (err == -EOVERFLOW) { - sparse_print_verbose("EOF while reading file%s%s\n", s, at); - } else -#endif - if (err == -EINVAL) { - sparse_print_verbose("Invalid sparse file format%s%s\n", s, at); - } else if (err == -ENOMEM) { - sparse_print_verbose("Failed allocation while reading file%s%s\n", - s, at); - } else { - sparse_print_verbose("Unknown error %d%s%s\n", err, s, at); - } - } - if (fmt) { - free(at); - } -} - -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; - unsigned int 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 = 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 = 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 = 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 = 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 = 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 = min(remain, 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; -} diff --git a/libsparse/sparse_read.cpp b/libsparse/sparse_read.cpp new file mode 100644 index 000000000..bd668735a --- /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; +} -- cgit v1.2.3