| /* |
| * Copyright (C) 2010-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 DEFAULT_BLOCK_SIZE "4K" |
| #define DEFAULT_CHUNK_SIZE "64M" |
| #define DEFAULT_SUFFIX "%03d" |
| |
| #include "ext4_utils.h" |
| #include "sparse_format.h" |
| #if 0 /* endian.h is not on all platforms */ |
| # include <endian.h> |
| #else |
| /* For now, just assume we're going to run on little-endian. */ |
| # define my_htole32(h) (h) |
| # define my_htole16(h) (h) |
| #endif |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <stdarg.h> |
| #include <stddef.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #define COPY_BUF_SIZE (1024*1024) |
| static char *copy_buf; |
| |
| static const char *progname(const char *argv0) |
| { |
| const char *prog_name; |
| if ((prog_name = strrchr(argv0, '/'))) |
| return(prog_name + 1); /* Advance beyond '/'. */ |
| return(argv0); /* No '/' in argv0, use it as is. */ |
| } |
| |
| static void error_exit(const char *fmt, ...) |
| { |
| va_list ap; |
| va_start(ap, fmt); |
| vfprintf(stderr, fmt, ap); |
| fputc('\n', stderr); |
| va_end(ap); |
| |
| exit(EXIT_FAILURE); |
| } |
| |
| static void usage(const char *argv0, const char *error_fmt, ...) |
| { |
| fprintf(stderr, |
| "Usage: %s [OPTIONS] <raw_image_file>\n", |
| progname(argv0)); |
| fprintf(stderr, "The <raw_image_file> will be split into as many sparse\n"); |
| fprintf(stderr, "files as needed. Each sparse file will contain a single\n"); |
| fprintf(stderr, "DONT CARE chunk to offset to the correct block and then\n"); |
| fprintf(stderr, "a single RAW chunk containing a portion of the data from\n"); |
| fprintf(stderr, "the raw image file. The sparse files will be named by\n"); |
| fprintf(stderr, "appending a number to the name of the raw image file.\n"); |
| fprintf(stderr, "\n"); |
| fprintf(stderr, "OPTIONS (Defaults are enclosed by square brackets):\n"); |
| fprintf(stderr, " -s SUFFIX Format appended number with SUFFIX [%s]\n", |
| DEFAULT_SUFFIX); |
| fprintf(stderr, " -B SIZE Use a block size of SIZE [%s]\n", |
| DEFAULT_BLOCK_SIZE); |
| fprintf(stderr, " -C SIZE Use a chunk size of SIZE [%s]\n", |
| DEFAULT_CHUNK_SIZE); |
| fprintf(stderr, "SIZE is a decimal integer that may optionally be\n"); |
| fprintf(stderr, "followed by a suffix that specifies a multiplier for\n"); |
| fprintf(stderr, "the integer:\n"); |
| fprintf(stderr, " c 1 byte (the default when omitted)\n"); |
| fprintf(stderr, " w 2 bytes\n"); |
| fprintf(stderr, " b 512 bytes\n"); |
| fprintf(stderr, " kB 1000 bytes\n"); |
| fprintf(stderr, " K 1024 bytes\n"); |
| fprintf(stderr, " MB 1000*1000 bytes\n"); |
| fprintf(stderr, " M 1024*1024 bytes\n"); |
| fprintf(stderr, " GB 1000*1000*1000 bytes\n"); |
| fprintf(stderr, " G 1024*1024*1024 bytes\n"); |
| |
| if (error_fmt && *error_fmt) |
| { |
| fprintf(stderr, "\n"); |
| va_list ap; |
| va_start(ap, error_fmt); |
| vfprintf(stderr, error_fmt, ap); |
| va_end(ap); |
| fprintf(stderr, "\n"); |
| } |
| |
| exit(EXIT_FAILURE); |
| } |
| |
| static void cpy_file(int out_fd, char *out_path, int in_fd, char *in_path, |
| size_t len) |
| { |
| ssize_t s, cpy_len = COPY_BUF_SIZE; |
| |
| while (len) { |
| if (len < COPY_BUF_SIZE) |
| cpy_len = len; |
| |
| s = read(in_fd, copy_buf, cpy_len); |
| if (s < 0) |
| error_exit("\"%s\": %s", in_path, strerror(errno)); |
| if (!s) |
| error_exit("\"%s\": Unexpected EOF", in_path); |
| |
| cpy_len = s; |
| |
| s = write(out_fd, copy_buf, cpy_len); |
| if (s < 0) |
| error_exit("\"%s\": %s", out_path, strerror(errno)); |
| if (s != cpy_len) |
| error_exit("\"%s\": Short data write (%lu)", out_path, |
| (unsigned long)s); |
| |
| len -= cpy_len; |
| } |
| } |
| |
| static int parse_size(const char *size_str, size_t *size) |
| { |
| static const size_t MAX_SIZE_T = ~(size_t)0; |
| size_t mult; |
| unsigned long long int value; |
| const char *end; |
| errno = 0; |
| value = strtoull(size_str, (char **)&end, 10); |
| if (errno != 0 || end == size_str || value > MAX_SIZE_T) |
| return -1; |
| if (*end == '\0') { |
| *size = value; |
| return 0; |
| } |
| if (!strcmp(end, "c")) |
| mult = 1; |
| else if (!strcmp(end, "w")) |
| mult = 2; |
| else if (!strcmp(end, "b")) |
| mult = 512; |
| else if (!strcmp(end, "kB")) |
| mult = 1000; |
| else if (!strcmp(end, "K")) |
| mult = 1024; |
| else if (!strcmp(end, "MB")) |
| mult = (size_t)1000*1000; |
| else if (!strcmp(end, "M")) |
| mult = (size_t)1024*1024; |
| else if (!strcmp(end, "GB")) |
| mult = (size_t)1000*1000*1000; |
| else if (!strcmp(end, "G")) |
| mult = (size_t)1024*1024*1024; |
| else |
| return -1; |
| |
| if (value > MAX_SIZE_T / mult) |
| return -1; |
| *size = value * mult; |
| return 0; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| char *suffix = DEFAULT_SUFFIX; |
| char *block_size_str = DEFAULT_BLOCK_SIZE; |
| char *chunk_size_str = DEFAULT_CHUNK_SIZE; |
| size_t block_size, chunk_size, blocks_per_chunk, to_write; |
| char *in_path, *out_path, *out_fmt; |
| int in_fd, out_fd; |
| struct stat in_st; |
| off_t left_to_write; |
| struct { |
| sparse_header_t sparse_hdr; |
| chunk_header_t dont_care_hdr; |
| chunk_header_t raw_hdr; |
| } file_hdr; |
| unsigned int file_count; |
| ssize_t s; |
| int i; |
| |
| /* Parse the command line. */ |
| while ((i = getopt(argc, argv, "s:B:C:")) != -1) |
| { |
| switch (i) { |
| case 's': |
| suffix = optarg; |
| break; |
| case 'B': |
| block_size_str = optarg; |
| break; |
| case 'C': |
| chunk_size_str = optarg; |
| break; |
| default: |
| usage(argv[0], NULL); |
| break; |
| } |
| } |
| |
| if (parse_size(block_size_str, &block_size)) |
| usage(argv[0], "Can not parse \"%s\" as a block size.", |
| block_size_str); |
| if (block_size % 4096) |
| usage(argv[0], "Block size is not a multiple of 4096."); |
| |
| if (parse_size(chunk_size_str, &chunk_size)) |
| usage(argv[0], "Can not parse \"%s\" as a chunk size.", |
| chunk_size_str); |
| if (chunk_size % block_size) |
| usage(argv[0], "Chunk size is not a multiple of the block size."); |
| blocks_per_chunk = chunk_size / block_size; |
| |
| if ((argc - optind) != 1) |
| usage(argv[0], "Missing or extra arguments."); |
| in_path = argv[optind]; |
| |
| /* Open the input file and validate it. */ |
| if ((in_fd = open(in_path, O_RDONLY)) < 0) |
| error_exit("open \"%s\": %s", in_path, strerror(errno)); |
| if (fstat(in_fd, &in_st)) |
| error_exit("fstat \"%s\": %s", in_path, strerror(errno)); |
| left_to_write = in_st.st_size; |
| if (left_to_write % block_size) |
| error_exit( |
| "\"%s\" size (%llu) is not a multiple of the block size (%llu).\n", |
| in_path, |
| (unsigned long long)left_to_write, (unsigned long long)block_size); |
| |
| /* Get a buffer for copying the chunks. */ |
| if ((copy_buf = malloc(COPY_BUF_SIZE)) == 0) |
| error_exit("malloc copy buffer: %s", strerror(errno)); |
| |
| /* Get a buffer for a sprintf format to form output paths. */ |
| if ((out_fmt = malloc(sizeof("%s") + strlen(suffix))) == 0) |
| error_exit("malloc format buffer: %s", strerror(errno)); |
| out_fmt[0] = '%'; |
| out_fmt[1] = 's'; |
| strcpy(out_fmt + 2, suffix); |
| |
| /* Get a buffer for an output path. */ |
| i = snprintf(copy_buf, COPY_BUF_SIZE, out_fmt, in_path, UINT_MAX); |
| if (i >= COPY_BUF_SIZE) |
| error_exit("Ridulously long suffix: %s", suffix); |
| if ((out_path = malloc(i + 1)) == 0) |
| error_exit("malloc output path buffer: %s", strerror(errno)); |
| |
| /* |
| * Each file gets a sparse_header, a Don't Care chunk to offset to |
| * where the data belongs and then a Raw chunk with the actual data. |
| */ |
| memset((void *)&file_hdr.sparse_hdr, 0, sizeof(file_hdr.sparse_hdr)); |
| file_hdr.sparse_hdr.magic = my_htole32(SPARSE_HEADER_MAGIC); |
| file_hdr.sparse_hdr.major_version = my_htole16(1); |
| file_hdr.sparse_hdr.minor_version = my_htole16(0); |
| file_hdr.sparse_hdr.file_hdr_sz = my_htole16(sizeof(sparse_header_t)); |
| file_hdr.sparse_hdr.chunk_hdr_sz = my_htole16(sizeof(chunk_header_t)); |
| file_hdr.sparse_hdr.blk_sz = my_htole32(block_size); |
| /* The total_blks will be set in the file loop below. */ |
| file_hdr.sparse_hdr.total_chunks = my_htole32(2); |
| file_hdr.sparse_hdr.image_checksum = my_htole32(0); /* Typically unused. */ |
| |
| memset((void *)&file_hdr.dont_care_hdr, 0, sizeof(file_hdr.dont_care_hdr)); |
| file_hdr.dont_care_hdr.chunk_type = my_htole16(CHUNK_TYPE_DONT_CARE); |
| /* The Don't Care's chunk_sz will be set in the file loop below. */ |
| file_hdr.dont_care_hdr.total_sz = my_htole32(sizeof(chunk_header_t)); |
| |
| memset((void *)&file_hdr.raw_hdr, 0, sizeof(file_hdr.raw_hdr)); |
| file_hdr.raw_hdr.chunk_type = my_htole16(CHUNK_TYPE_RAW); |
| file_hdr.raw_hdr.chunk_sz = my_htole32(blocks_per_chunk); |
| file_hdr.raw_hdr.total_sz = my_htole32(chunk_size + sizeof(chunk_header_t)); |
| |
| /* Loop through writing chunk_size to each of the output files. */ |
| to_write = chunk_size; |
| for (file_count = 1; left_to_write ; file_count++) { |
| /* Fix up the headers on the last block. */ |
| if (left_to_write < (off_t)chunk_size) { |
| to_write = left_to_write; |
| file_hdr.raw_hdr.chunk_sz = my_htole32(left_to_write / block_size); |
| file_hdr.raw_hdr.total_sz = my_htole32(left_to_write |
| + sizeof(chunk_header_t)); |
| } |
| |
| /* Form the pathname for this output file and open it. */ |
| sprintf(out_path, out_fmt, in_path, file_count); |
| if ((out_fd = creat(out_path, 0666)) < 0) |
| error_exit("\"%s\": %s", out_path, strerror(errno)); |
| |
| /* Update and write the headers to this output file. */ |
| s = (file_count-1) * blocks_per_chunk; |
| file_hdr.dont_care_hdr.chunk_sz = my_htole32(s); |
| file_hdr.sparse_hdr.total_blks = my_htole32(s |
| + (to_write / block_size)); |
| s = write(out_fd, (void *)&file_hdr, sizeof(file_hdr)); |
| if (s < 0) |
| error_exit("\"%s\": %s", out_path, strerror(errno)); |
| if (s != sizeof(file_hdr)) |
| error_exit("\"%s\": Short write (%lu)", out_path, (unsigned long)s); |
| |
| /* Copy this chunk from the input file to the output file. */ |
| cpy_file(out_fd, out_path, in_fd, in_path, to_write); |
| |
| /* Close this output file and update the amount left to write. */ |
| if (close(out_fd)) |
| error_exit("close \"%s\": %s", out_path, strerror(errno)); |
| left_to_write -= to_write; |
| } |
| |
| if (close(in_fd)) |
| error_exit("close \"%s\": %s", in_path, strerror(errno)); |
| |
| exit(EXIT_SUCCESS); |
| } |