diff --git a/Makefile b/Makefile index 9fd2568..5fa4db9 100644 --- a/Makefile +++ b/Makefile @@ -4,9 +4,12 @@ VERSION := $(or $(VERSION), $(shell git describe --dirty --always --tags 2>/dev/ CFLAGS += -O2 -Wall -g `pkg-config --cflags libxml-2.0 libusb-1.0` LDFLAGS += `pkg-config --libs libxml-2.0 libusb-1.0` +ifeq ($(OS),Windows_NT) +LDFLAGS += -lws2_32 +endif prefix := /usr/local -QDL_SRCS := firehose.c io.c qdl.c sahara.c util.c patch.c program.c read.c sha2.c sim.c ufs.c usb.c ux.c oscompat.c vip.c +QDL_SRCS := firehose.c io.c qdl.c sahara.c util.c patch.c program.c read.c sha2.c sim.c ufs.c usb.c ux.c oscompat.c vip.c sparse.c QDL_OBJS := $(QDL_SRCS:.c=.o) RAMDUMP_SRCS := ramdump.c sahara.c io.c sim.c usb.c util.c ux.c oscompat.c diff --git a/firehose.c b/firehose.c index 9978dc6..6745abe 100644 --- a/firehose.c +++ b/firehose.c @@ -26,6 +26,7 @@ #include "ufs.h" #include "oscompat.h" #include "vip.h" +#include "sparse.h" enum { FIREHOSE_ACK = 0, @@ -366,6 +367,7 @@ static int firehose_program(struct qdl_device *qdl, struct program *program, int int left; int ret; int n; + uint32_t fill_value; num_sectors = program->num_sectors; @@ -373,14 +375,16 @@ static int firehose_program(struct qdl_device *qdl, struct program *program, int if (ret < 0) err(1, "failed to stat \"%s\"\n", program->filename); - num_sectors = (sb.st_size + program->sector_size - 1) / program->sector_size; + if (!program->sparse) { + num_sectors = (sb.st_size + program->sector_size - 1) / program->sector_size; - if (program->num_sectors && num_sectors > program->num_sectors) { - ux_err("%s to big for %s truncated to %d\n", - program->filename, - program->label, - program->num_sectors * program->sector_size); - num_sectors = program->num_sectors; + if (program->num_sectors && num_sectors > program->num_sectors) { + ux_err("%s to big for %s truncated to %d\n", + program->filename, + program->label, + program->num_sectors * program->sector_size); + num_sectors = program->num_sectors; + } } buf = malloc(qdl->max_payload_size); @@ -418,7 +422,24 @@ static int firehose_program(struct qdl_device *qdl, struct program *program, int t0 = time(NULL); - lseek(fd, (off_t)program->file_offset * program->sector_size, SEEK_SET); + if (!program->sparse) { + lseek(fd, (off_t)program->file_offset * program->sector_size, SEEK_SET); + } else { + switch (program->sparse_chunk_type) { + case CHUNK_TYPE_RAW: + lseek(fd, (off_t)program->sparse_chunk_data, SEEK_SET); + break; + case CHUNK_TYPE_FILL: + fill_value = (uint32_t)program->sparse_chunk_data; + for (n = 0; n < qdl->max_payload_size; n += sizeof(fill_value)) + memcpy(buf + n, &fill_value, sizeof(fill_value)); + break; + default: + ux_err("[SPARSE] invalid chunk type\n"); + goto out; + } + } + left = num_sectors; ux_debug("FIREHOSE RAW BINARY WRITE: %s, %d bytes\n", @@ -432,14 +453,16 @@ static int firehose_program(struct qdl_device *qdl, struct program *program, int vip_gen_chunk_init(qdl); chunk_size = MIN(qdl->max_payload_size / program->sector_size, left); - n = read(fd, buf, chunk_size * program->sector_size); - if (n < 0) { - ux_err("failed to read %s\n", program->filename); - goto out; - } + if (!program->sparse || program->sparse_chunk_type != CHUNK_TYPE_FILL) { + n = read(fd, buf, chunk_size * program->sector_size); + if (n < 0) { + ux_err("failed to read %s\n", program->filename); + goto out; + } - if (n < qdl->max_payload_size) - memset(buf + n, 0, qdl->max_payload_size - n); + if (n < qdl->max_payload_size) + memset(buf + n, 0, qdl->max_payload_size - n); + } vip_gen_chunk_update(qdl, buf, chunk_size * program->sector_size); diff --git a/program.c b/program.c index e494869..a8163a5 100644 --- a/program.c +++ b/program.c @@ -15,6 +15,7 @@ #include "program.h" #include "qdl.h" #include "oscompat.h" +#include "sparse.h" static struct program *programes; static struct program *programes_last; @@ -54,6 +55,97 @@ static int load_erase_tag(xmlNode *node, bool is_nand) return 0; } +static struct program *program_load_sparse(struct program *program, int fd) +{ + struct program *program_sparse = NULL; + struct program *programes_sparse = NULL; + struct program *programes_sparse_last = NULL; + char tmp[PATH_MAX]; + + sparse_header_t sparse_header; + unsigned int start_sector, chunk_size, chunk_type, chunk_data; + + if (sparse_header_parse(fd, &sparse_header)) { + /* + * If the XML tag "program" contains the attribute 'sparse="true"' + * for a partition node but lacks a sparse header, + * it will be validated against the defined partition size. + * If the sizes match, it is likely that the 'sparse="true"' attribute + * was set by mistake. + */ + if ((off_t)program->sector_size * program->num_sectors == + lseek(fd, 0, SEEK_END)) { + program_sparse = calloc(1, sizeof(struct program)); + memcpy(program_sparse, program, sizeof(struct program)); + program_sparse->sparse = false; + program_sparse->next = NULL; + return program_sparse; + } + + ux_err("[PROGRAM] Unable to parse sparse header at %s...failed\n", + program->filename); + return NULL; + } + + for (uint32_t i = 0; i < sparse_header.total_chunks; ++i) { + chunk_type = sparse_chunk_header_parse(fd, &sparse_header, + &chunk_size, &chunk_data); + + switch (chunk_type) { + case CHUNK_TYPE_RAW: + case CHUNK_TYPE_FILL: + case CHUNK_TYPE_DONT_CARE: + break; + default: + ux_err("[PROGRAM] Unable to parse sparse chunk %i at %s...failed\n", + i, program->filename); + return NULL; + } + + if (chunk_size == 0) + continue; + + if (chunk_size % program->sector_size != 0) { + ux_err("[SPARSE] File chunk #%u size %u is not a sector-multiple\n", + i, chunk_size); + return NULL; + } + + switch (chunk_type) { + case CHUNK_TYPE_RAW: + case CHUNK_TYPE_FILL: + + program_sparse = calloc(1, sizeof(struct program)); + memcpy(program_sparse, program, sizeof(struct program)); + + program_sparse->next = NULL; + program_sparse->num_sectors = chunk_size / program->sector_size; + + program_sparse->sparse_chunk_type = chunk_type; + program_sparse->sparse_chunk_data = chunk_data; + + if (programes_sparse) { + programes_sparse_last->next = program_sparse; + programes_sparse_last = program_sparse; + } else { + programes_sparse = program_sparse; + programes_sparse_last = program_sparse; + } + + break; + default: + break; + } + + start_sector = (unsigned int)strtoul(program->start_sector, NULL, 0); + start_sector += chunk_size / program->sector_size; + sprintf(tmp, "%u", start_sector); + program->start_sector = strdup(tmp); + } + + return programes_sparse; +} + static int load_program_tag(xmlNode *node, bool is_nand) { struct program *program; @@ -138,6 +230,7 @@ int program_execute(struct qdl_device *qdl, int (*apply)(struct qdl_device *qdl, const char *incdir, bool allow_missing) { struct program *program; + struct program *program_sparse; const char *filename; char tmp[PATH_MAX]; int ret; @@ -166,7 +259,21 @@ int program_execute(struct qdl_device *qdl, int (*apply)(struct qdl_device *qdl, continue; } - ret = apply(qdl, program, fd); + if (!program->sparse) { + ret = apply(qdl, program, fd); + } else { + program_sparse = program_load_sparse(program, fd); + if (!program_sparse) { + ux_err("[PROGRAM] load sparse failed\n"); + return -EINVAL; + } + + for (; program_sparse; program_sparse = program_sparse->next) { + ret = apply(qdl, program_sparse, fd); + if (ret) + break; + } + } close(fd); if (ret) diff --git a/program.h b/program.h index 754c46c..4998211 100644 --- a/program.h +++ b/program.h @@ -20,6 +20,9 @@ struct program { bool is_nand; bool is_erase; + unsigned int sparse_chunk_type; + unsigned int sparse_chunk_data; + struct program *next; }; diff --git a/sparse.c b/sparse.c new file mode 100644 index 0000000..ea67c91 --- /dev/null +++ b/sparse.c @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 2025, Maksim Paimushkin + * All rights reserved. + */ +#define _FILE_OFFSET_BITS 64 +#ifdef _WIN32 +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sparse.h" +#include "qdl.h" + +int sparse_header_parse(int fd, sparse_header_t *sparse_header) +{ + lseek(fd, 0, SEEK_SET); + + if (read(fd, sparse_header, sizeof(sparse_header_t)) != sizeof(sparse_header_t)) { + ux_err("[SPARSE] Unable to read sparse header\n"); + return -EINVAL; + } + + if (ntohl(sparse_header->magic) != ntohl(SPARSE_HEADER_MAGIC)) { + ux_err("[SPARSE] Invalid magic in sparse header\n"); + return -EINVAL; + } + + if (ntohs(sparse_header->major_version) != ntohs(SPARSE_HEADER_MAJOR_VER)) { + ux_err("[SPARSE] Invalid major version in sparse header\n"); + return -EINVAL; + } + + if (ntohs(sparse_header->minor_version) != ntohs(SPARSE_HEADER_MINOR_VER)) { + ux_err("[SPARSE] Invalid minor version in sparse header\n"); + return -EINVAL; + } + + if (sparse_header->file_hdr_sz > sizeof(sparse_header_t)) + lseek(fd, sparse_header->file_hdr_sz - sizeof(sparse_header_t), SEEK_CUR); + + return 0; +} + +int sparse_chunk_header_parse(int fd, sparse_header_t *sparse_header, + unsigned int *chunk_size, unsigned int *value) +{ + chunk_header_t chunk_header; + uint32_t fill_value = 0; + + *chunk_size = 0; + *value = 0; + + if (read(fd, &chunk_header, sizeof(chunk_header_t)) != sizeof(chunk_header_t)) { + ux_err("[SPARSE] Unable to read sparse chunk header\n"); + return -EINVAL; + } + + if (sparse_header->chunk_hdr_sz > sizeof(chunk_header_t)) + lseek(fd, sparse_header->chunk_hdr_sz - sizeof(chunk_header_t), SEEK_CUR); + + if (ntohs(chunk_header.chunk_type) == ntohs(CHUNK_TYPE_RAW)) { + *chunk_size = chunk_header.chunk_sz * sparse_header->blk_sz; + + if (chunk_header.total_sz != (sparse_header->chunk_hdr_sz + *chunk_size)) { + ux_err("[SPARSE] Bogus chunk size, type Raw\n"); + return -EINVAL; + } + + /* Save the current file offset in the 'value' variable */ + *value = lseek(fd, 0, SEEK_CUR); + + /* Move the file cursor forward by the size of the chunk */ + lseek(fd, *chunk_size, SEEK_CUR); + + return CHUNK_TYPE_RAW; + + } else if (ntohs(chunk_header.chunk_type) == ntohs(CHUNK_TYPE_DONT_CARE)) { + *chunk_size = chunk_header.chunk_sz * sparse_header->blk_sz; + + if (chunk_header.total_sz != sparse_header->chunk_hdr_sz) { + ux_err("[SPARSE] Bogus chunk size, type Don't Care\n"); + return -EINVAL; + } + + return CHUNK_TYPE_DONT_CARE; + + } else if (ntohs(chunk_header.chunk_type) == ntohs(CHUNK_TYPE_FILL)) { + *chunk_size = chunk_header.chunk_sz * sparse_header->blk_sz; + + if (chunk_header.total_sz != (sparse_header->chunk_hdr_sz + sizeof(fill_value))) { + ux_err("[SPARSE] Bogus chunk size, type Fill\n"); + return -EINVAL; + } + + if (read(fd, &fill_value, sizeof(fill_value)) != sizeof(fill_value)) { + ux_err("[SPARSE] Unable to read fill value\n"); + return -EINVAL; + } + + /* Save the current fill value in the 'value' variable */ + *value = fill_value; + + return CHUNK_TYPE_FILL; + } + + ux_err("[SPARSE] Unknown chunk\n"); + return -EINVAL; +} diff --git a/sparse.h b/sparse.h new file mode 100644 index 0000000..69f576e --- /dev/null +++ b/sparse.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* + * Copyright (C) 2010 The Android Open Source Project + */ +#ifndef __SPARSE_H__ +#define __SPARSE_H__ + +#include + +typedef struct __attribute__((__packed__)) sparse_header { + /* 0xed26ff3a */ + uint32_t magic; + /* (0x1) - reject images with higher major versions */ + uint16_t major_version; + /* (0x0) - allow images with higer minor versions */ + uint16_t minor_version; + /* 28 bytes for first revision of the file format */ + uint16_t file_hdr_sz; + /* 12 bytes for first revision of the file format */ + uint16_t chunk_hdr_sz; + /* block size in bytes, must be a multiple of 4 (4096) */ + uint32_t blk_sz; + /* total blocks in the non-sparse output image */ + uint32_t total_blks; + /* total chunks in the sparse input image */ + uint32_t total_chunks; + /* + * CRC32 checksum of the original data, counting "don't care" + * as 0. Standard 802.3 polynomial, use a Public Domain + * table implementation + */ + uint32_t image_checksum; +} sparse_header_t; + +#define SPARSE_HEADER_MAGIC 0xed26ff3a +#define SPARSE_HEADER_MAJOR_VER 0x0001 +#define SPARSE_HEADER_MINOR_VER 0x0000 + +typedef struct __attribute__((__packed__)) chunk_header { + uint16_t chunk_type; /* 0xCAC1 -> raw; 0xCAC2 -> fill; 0xCAC3 -> don't care */ + uint16_t reserved1; + uint32_t chunk_sz; /* in blocks in output image */ + uint32_t total_sz; /* in bytes of chunk input file including chunk header and data */ +} chunk_header_t; + +#define CHUNK_TYPE_RAW 0xCAC1 +#define CHUNK_TYPE_FILL 0xCAC2 +#define CHUNK_TYPE_DONT_CARE 0xCAC3 + +/* + * Parses the sparse image header from the file descriptor. + * Returns 0 on success, or an error code otherwise. + */ +int sparse_header_parse(int fd, sparse_header_t *sparse_header); + +/* + * Parses the sparse image chunk header from the file descriptor. + * Sets the chunk size and value based on the parsed data. + * Returns the chunk type on success, or an error code otherwise. + */ +int sparse_chunk_header_parse(int fd, sparse_header_t *sparse_header, + unsigned int *chunk_size, unsigned int *value); + +#endif