| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #define _POSIX_C_SOURCE 200809L |
| |
| #include <ctype.h> |
| #include <dirent.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| |
| #include <lz4frame.h> |
| #include <lib/cksum.h> |
| |
| #include <zircon/boot/bootdata.h> |
| |
| #define MAXBUFFER (1024*1024) |
| |
| int verbose = 0; |
| |
| typedef struct fsentry fsentry_t; |
| |
| struct fsentry { |
| fsentry_t* next; |
| |
| char* name; |
| size_t namelen; |
| uint32_t offset; |
| uint32_t length; |
| |
| char* srcpath; |
| }; |
| |
| #define ITEM_BOOTDATA 0 |
| #define ITEM_BOOTFS_BOOT 1 |
| #define ITEM_BOOTFS_SYSTEM 2 |
| #define ITEM_KERNEL 3 |
| #define ITEM_CMDLINE 4 |
| |
| typedef struct item item_t; |
| |
| struct item { |
| uint32_t type; |
| item_t* next; |
| |
| fsentry_t* first; |
| fsentry_t* last; |
| |
| // size of header and total output size |
| // used by bootfs items |
| size_t hdrsize; |
| size_t outsize; |
| }; |
| |
| typedef struct filter filter_t; |
| struct filter { |
| filter_t* next; |
| char text[]; |
| }; |
| |
| filter_t* group_filter = NULL; |
| |
| int add_filter(filter_t** flist, const char* text) { |
| size_t len = strlen(text) + 1; |
| if (len == 1) { |
| fprintf(stderr, "error: empty filter string\n"); |
| return -1; |
| } |
| filter_t* filter = malloc(sizeof(filter_t) + len); |
| if (filter == NULL) { |
| fprintf(stderr, "error: out of memory (filter string)\n"); |
| return -1; |
| } |
| memcpy(filter->text, text, len); |
| filter->next = *flist; |
| *flist = filter; |
| return 0; |
| } |
| |
| char* trim(char* str) { |
| char* end; |
| while (isspace(*str)) { |
| str++; |
| } |
| end = str + strlen(str); |
| while (end > str) { |
| end--; |
| if (isspace(*end)) { |
| *end = 0; |
| } else { |
| break; |
| } |
| } |
| return str; |
| } |
| |
| static item_t* first_item; |
| static item_t* last_item; |
| |
| item_t* new_item(uint32_t type) { |
| item_t* item = calloc(1, sizeof(item_t)); |
| if (item == NULL) { |
| fprintf(stderr, "OUT OF MEMORY\n"); |
| exit(-1); |
| } |
| item->type = type; |
| if (first_item) { |
| last_item->next = item; |
| } else { |
| first_item = item; |
| } |
| last_item = item; |
| |
| return item; |
| } |
| |
| fsentry_t* import_manifest_entry(const char* fn, int lineno, const char* dst, const char* src) { |
| fsentry_t* e; |
| struct stat s; |
| |
| if (dst[0] == 0) { |
| fprintf(stderr, "%s:%d: illegal filename\n", fn, lineno); |
| return NULL; |
| } |
| if (stat(src, &s) < 0) { |
| fprintf(stderr, "%s:%d: cannot stat '%s'\n", fn, lineno, src); |
| return NULL; |
| } |
| if (s.st_size > INT32_MAX) { |
| fprintf(stderr, "%s:%d: file too large '%s'\n", fn, lineno, src); |
| return NULL; |
| } |
| |
| if ((e = calloc(1, sizeof(*e))) == NULL) return NULL; |
| if ((e->name = strdup(dst)) == NULL) goto fail; |
| if ((e->srcpath = strdup(src)) == NULL) goto fail; |
| e->namelen = strlen(e->name) + 1; |
| e->length = s.st_size; |
| return e; |
| fail: |
| free(e->name); |
| free(e); |
| return NULL; |
| } |
| |
| fsentry_t* import_directory_entry(const char* dst, const char* src, struct stat* s) { |
| fsentry_t* e; |
| |
| if (s->st_size > INT32_MAX) { |
| fprintf(stderr, "error: file too large '%s'\n", src); |
| return NULL; |
| } |
| |
| if ((e = calloc(1, sizeof(*e))) == NULL) return NULL; |
| if ((e->name = strdup(dst)) == NULL) goto fail; |
| if ((e->srcpath = strdup(src)) == NULL) goto fail; |
| e->namelen = strlen(e->name) + 1; |
| e->length = s->st_size; |
| return e; |
| fail: |
| free(e->name); |
| free(e); |
| return NULL; |
| } |
| |
| void add_entry(item_t* fs, fsentry_t* e) { |
| e->next = NULL; |
| if (fs->last) { |
| fs->last->next = e; |
| } else { |
| fs->first = e; |
| } |
| fs->last = e; |
| fs->hdrsize += sizeof(bootfs_entry_t) + BOOTFS_ALIGN(e->namelen); |
| } |
| |
| int import_manifest(FILE* fp, const char* fn, item_t* fs) { |
| int lineno = 0; |
| fsentry_t* e; |
| char* eq; |
| char line[4096]; |
| |
| while (fgets(line, sizeof(line), fp) != NULL) { |
| lineno++; |
| if ((eq = strchr(line, '=')) == NULL) { |
| continue; |
| } |
| *eq++ = 0; |
| char* dstfn = trim(line); |
| char* srcfn = trim(eq); |
| char* group = "default"; |
| |
| if (dstfn[0] == '{') { |
| char* end = strchr(dstfn + 1, '}'); |
| if (end) { |
| *end = 0; |
| group = dstfn + 1; |
| dstfn = end + 1; |
| } else { |
| fprintf(stderr, "%s:%d: unterminated group designator\n", fn, lineno); |
| return -1; |
| } |
| } |
| if (group_filter) { |
| filter_t* filter; |
| for (filter = group_filter; filter != NULL; filter = filter->next) { |
| if (!strcmp(filter->text, group)) { |
| goto okay; |
| } |
| } |
| if (verbose) { |
| fprintf(stderr, "excluding: %s (group '%s')\n", dstfn, group); |
| } |
| continue; |
| } |
| okay: |
| if ((e = import_manifest_entry(fn, lineno, dstfn, srcfn)) == NULL) { |
| return -1; |
| } |
| add_entry(fs, e); |
| } |
| fclose(fp); |
| return 0; |
| } |
| |
| int import_file_as(const char* fn, uint32_t type, uint32_t hdrlen, bootdata_t* hdr) { |
| // bootdata file |
| struct stat s; |
| if (stat(fn, &s) != 0) { |
| fprintf(stderr, "error: cannot stat '%s'\n", fn); |
| return -1; |
| } |
| |
| if (type == ITEM_BOOTDATA) { |
| size_t hsz = sizeof(bootdata_t); |
| if (hdr->flags & BOOTDATA_FLAG_EXTRA) { |
| hsz += sizeof(bootextra_t); |
| } |
| if (s.st_size < hsz) { |
| fprintf(stderr, "error: bootdata file too small '%s'\n", fn); |
| return -1; |
| } |
| if (s.st_size & 7) { |
| fprintf(stderr, "error: bootdata file misaligned '%s'\n", fn); |
| return -1; |
| } |
| if (s.st_size != (hdrlen + hsz)) { |
| fprintf(stderr, "error: bootdata header size mismatch '%s'\n", fn); |
| return -1; |
| } |
| } |
| |
| fsentry_t* e; |
| if ((e = import_directory_entry("bootdata", fn, &s)) < 0) { |
| return -1; |
| } |
| item_t* item = new_item(type); |
| add_entry(item, e); |
| return 0; |
| } |
| |
| int import_file(const char* fn, bool system) { |
| FILE* fp; |
| if ((fp = fopen(fn, "r")) == NULL) { |
| return -1; |
| } |
| |
| bootdata_t hdr; |
| if ((fread(&hdr, sizeof(hdr), 1, fp) != 1) || |
| (hdr.type != BOOTDATA_CONTAINER) || |
| (hdr.extra != BOOTDATA_MAGIC)) { |
| // not a bootdata file, must be a manifest... |
| rewind(fp); |
| |
| item_t* item = new_item(system ? ITEM_BOOTFS_SYSTEM : ITEM_BOOTFS_BOOT); |
| return import_manifest(fp, fn, item); |
| } else { |
| fclose(fp); |
| return import_file_as(fn, ITEM_BOOTDATA, hdr.length, &hdr); |
| } |
| } |
| |
| |
| int import_directory(const char* dpath, const char* spath, item_t* item, bool system) { |
| #define MAX_BOOTFS_PATH_LEN 4096 |
| char dst[MAX_BOOTFS_PATH_LEN]; |
| char src[MAX_BOOTFS_PATH_LEN]; |
| #undef MAX_BOOTFS_PATH_LEN |
| struct stat s; |
| struct dirent* de; |
| DIR* dir; |
| |
| if ((dir = opendir(spath)) == NULL) { |
| fprintf(stderr, "error: cannot open directory '%s'\n", spath); |
| return -1; |
| } |
| |
| if (item == NULL) { |
| item = new_item(system ? ITEM_BOOTFS_SYSTEM : ITEM_BOOTFS_BOOT); |
| } |
| |
| while ((de = readdir(dir)) != NULL) { |
| char* name = de->d_name; |
| if (name[0] == '.') { |
| if (name[1] == 0) { |
| continue; |
| } |
| if ((name[1] == '.') && (name[2] == 0)) { |
| continue; |
| } |
| } |
| if (snprintf(src, sizeof(src), "%s/%s", spath, name) > sizeof(src)) { |
| fprintf(stderr, "error: name '%s/%s' is too long\n", spath, name); |
| goto fail; |
| } |
| if (stat(src, &s) < 0) { |
| fprintf(stderr, "error: cannot stat '%s'\n", src); |
| goto fail; |
| } |
| if (S_ISREG(s.st_mode)) { |
| fsentry_t* e; |
| if (snprintf(dst, sizeof(dst), "%s%s", dpath, name) > sizeof(dst)) { |
| fprintf(stderr, "error: name '%s%s' is too long\n", dpath, name); |
| goto fail; |
| } |
| if ((e = import_directory_entry(dst, src, &s)) < 0) { |
| goto fail; |
| } |
| add_entry(item, e); |
| } else if (S_ISDIR(s.st_mode)) { |
| if (snprintf(dst, sizeof(dst), "%s%s/", dpath, name) > sizeof(dst)) { |
| fprintf(stderr, "error: name '%s%s/' is too long\n", dpath, name); |
| goto fail; |
| } |
| import_directory(dst, src, item, system); |
| } else { |
| fprintf(stderr, "error: unsupported filetype '%s'\n", src); |
| goto fail; |
| } |
| } |
| closedir(dir); |
| return 0; |
| fail: |
| closedir(dir); |
| return -1; |
| } |
| |
| static int readx(int fd, void* ptr, size_t len) { |
| size_t total = len; |
| while (len > 0) { |
| ssize_t r = read(fd, ptr, len); |
| if (r <= 0) { |
| return -1; |
| } |
| ptr += r; |
| len -= r; |
| } |
| return total; |
| } |
| |
| static int writex(int fd, const void* ptr, size_t len) { |
| size_t total = len; |
| while (len > 0) { |
| ssize_t r = write(fd, ptr, len); |
| if (r <= 0) { |
| return -1; |
| } |
| ptr += r; |
| len -= r; |
| } |
| return total; |
| } |
| |
| int readcrc32(int fd, size_t len, uint32_t* crc) { |
| uint8_t buf[MAXBUFFER]; |
| while (len > 0) { |
| size_t xfer = (len > sizeof(buf)) ? sizeof(buf) : len; |
| if (readx(fd, buf, xfer) < 0) { |
| return -1; |
| } |
| *crc = crc32(*crc, buf, xfer); |
| len -= xfer; |
| } |
| return 0; |
| } |
| |
| typedef struct { |
| ssize_t (*setup)(int fd, void** cookie, uint32_t* crc); |
| ssize_t (*write)(int fd, const void* src, size_t len, void* cookie, uint32_t* crc); |
| ssize_t (*write_file)(int fd, const char* fn, size_t len, void* cookie, uint32_t* crc); |
| ssize_t (*finish)(int fd, void* cookie, uint32_t* crc); |
| } io_ops; |
| |
| ssize_t copydata(int fd, const void* src, size_t len, void* cookie, uint32_t* crc) { |
| if (crc) { |
| *crc = crc32(*crc, src, len); |
| } |
| if (writex(fd, src, len) < 0) { |
| return -1; |
| } else { |
| return len; |
| } |
| } |
| |
| ssize_t copyfile(int fd, const char* fn, size_t len, void* cookie, uint32_t* crc) { |
| char buf[MAXBUFFER]; |
| int r, fdi; |
| if ((fdi = open(fn, O_RDONLY)) < 0) { |
| fprintf(stderr, "error: cannot open '%s'\n", fn); |
| return -1; |
| } |
| |
| r = 0; |
| size_t total = len; |
| while (len > 0) { |
| size_t xfer = (len > sizeof(buf)) ? sizeof(buf) : len; |
| if ((r = readx(fdi, buf, xfer)) < 0) { |
| break; |
| } |
| if (crc) { |
| *crc = crc32(*crc, (void*)buf, xfer); |
| } |
| if ((r = writex(fd, buf, xfer)) < 0) { |
| break; |
| } |
| len -= xfer; |
| } |
| close(fdi); |
| return (r < 0) ? r : total; |
| } |
| |
| static const io_ops io_plain = { |
| .write = copydata, |
| .write_file = copyfile, |
| }; |
| |
| static LZ4F_preferences_t lz4_prefs = { |
| .frameInfo = { |
| .blockSizeID = LZ4F_max64KB, |
| .blockMode = LZ4F_blockIndependent, |
| }, |
| // LZ4 compression levels 1-3 are for "fast" compression, and 4-16 are for |
| // higher compression. The additional compression going from 4 to 16 is not |
| // worth the extra time needed during compression. |
| .compressionLevel = 4, |
| }; |
| |
| static bool check_and_log_lz4_error(LZ4F_errorCode_t code, const char* msg) { |
| if (LZ4F_isError(code)) { |
| fprintf(stderr, "%s: %s\n", msg, LZ4F_getErrorName(code)); |
| return true; |
| } |
| return false; |
| } |
| |
| ssize_t compress_setup(int fd, void** cookie, uint32_t* crc) { |
| LZ4F_compressionContext_t cctx; |
| LZ4F_errorCode_t errc = LZ4F_createCompressionContext(&cctx, LZ4F_VERSION); |
| if (check_and_log_lz4_error(errc, "could not initialize compression context")) { |
| return -1; |
| } |
| uint8_t buf[128]; |
| size_t r = LZ4F_compressBegin(cctx, buf, sizeof(buf), &lz4_prefs); |
| if (check_and_log_lz4_error(r, "could not begin compression")) { |
| return r; |
| } |
| |
| // Note: LZ4F_compressionContext_t is a typedef to a pointer, so this is |
| // "safe". |
| *cookie = (void*)cctx; |
| |
| if (crc && (r > 0)) { |
| *crc = crc32(*crc, buf, r); |
| } |
| return writex(fd, buf, r); |
| } |
| |
| ssize_t compress_data(int fd, const void* src, size_t len, void* cookie, uint32_t* crc) { |
| // max will be, worst case, a bit larger than MAXBUFFER |
| size_t max = LZ4F_compressBound(len, &lz4_prefs); |
| uint8_t buf[max]; |
| size_t r = LZ4F_compressUpdate((LZ4F_compressionContext_t)cookie, buf, max, src, len, NULL); |
| if (check_and_log_lz4_error(r, "could not compress data")) { |
| return -1; |
| } |
| if (crc) { |
| *crc = crc32(*crc, buf, r); |
| } |
| return writex(fd, buf, r); |
| } |
| |
| ssize_t compress_file(int fd, const char* fn, size_t len, void* cookie, uint32_t* crc) { |
| if (len == 0) { |
| // Don't bother trying to compress empty files |
| return 0; |
| } |
| |
| char buf[MAXBUFFER]; |
| int r, fdi; |
| if ((fdi = open(fn, O_RDONLY)) < 0) { |
| fprintf(stderr, "error: cannot open '%s'\n", fn); |
| return -1; |
| } |
| |
| r = 0; |
| size_t total = len; |
| while (len > 0) { |
| size_t xfer = (len > sizeof(buf)) ? sizeof(buf) : len; |
| if ((r = readx(fdi, buf, xfer)) < 0) { |
| break; |
| } |
| if ((r = compress_data(fd, buf, xfer, cookie, crc)) < 0) { |
| break; |
| } |
| len -= xfer; |
| } |
| close(fdi); |
| return (r < 0) ? -1 : total; |
| } |
| |
| ssize_t compress_finish(int fd, void* cookie, uint32_t* crc) { |
| // Max write is one block (64kB uncompressed) plus 8 bytes of footer. |
| size_t max = LZ4F_compressBound(65536, &lz4_prefs) + 8; |
| uint8_t buf[max]; |
| size_t r = LZ4F_compressEnd((LZ4F_compressionContext_t)cookie, buf, max, NULL); |
| if (check_and_log_lz4_error(r, "could not finish compression")) { |
| r = -1; |
| } else { |
| if (crc) { |
| *crc = crc32(*crc, buf, r); |
| } |
| r = writex(fd, buf, r); |
| } |
| |
| LZ4F_errorCode_t errc = LZ4F_freeCompressionContext((LZ4F_compressionContext_t)cookie); |
| if (check_and_log_lz4_error(errc, "could not free compression context")) { |
| r = -1; |
| } |
| |
| return r; |
| } |
| |
| static const io_ops io_compressed = { |
| .setup = compress_setup, |
| .write = compress_data, |
| .write_file = compress_file, |
| .finish = compress_finish, |
| }; |
| |
| ssize_t copybootdatafile(int fd, const char* fn, size_t len) { |
| char buf[MAXBUFFER]; |
| int r, fdi; |
| if ((fdi = open(fn, O_RDONLY)) < 0) { |
| fprintf(stderr, "error: cannot open '%s'\n", fn); |
| return -1; |
| } |
| |
| bootdata_t hdr; |
| if ((r = readx(fdi, &hdr, sizeof(hdr))) < 0) { |
| fprintf(stderr, "error: '%s' cannot read file header\n", fn); |
| goto fail; |
| } |
| if ((hdr.type != BOOTDATA_CONTAINER) || |
| (hdr.extra != BOOTDATA_MAGIC)) { |
| fprintf(stderr, "error: '%s' is not a bootdata file\n", fn); |
| goto fail; |
| } |
| len -= sizeof(hdr); |
| if (hdr.flags & BOOTDATA_FLAG_EXTRA) { |
| bootextra_t extra; |
| if ((r = readx(fdi, &extra, sizeof(extra))) < 0) { |
| fprintf(stderr, "error: '%s' cannot read extra header\n", fn); |
| goto fail; |
| } |
| len -= sizeof(extra); |
| } |
| if ((hdr.length != len)) { |
| fprintf(stderr, "error: '%s' header length (%u) != %zd\n", fn, hdr.length, len); |
| goto fail; |
| } |
| |
| r = 0; |
| size_t total = len; |
| while (len > 0) { |
| size_t xfer = (len > sizeof(buf)) ? sizeof(buf) : len; |
| if ((r = readx(fdi, buf, xfer)) < 0) { |
| break; |
| } |
| if ((r = writex(fd, buf, xfer)) < 0) { |
| break; |
| } |
| len -= xfer; |
| } |
| close(fdi); |
| return (r < 0) ? r : total; |
| |
| fail: |
| close(fdi); |
| return -1; |
| } |
| |
| #define PAGEALIGN(n) (((n) + 4095) & (~4095)) |
| #define PAGEFILL(n) (PAGEALIGN(n) - (n)) |
| |
| char fill[4096]; |
| |
| #define CHECK(w) do { if ((w) < 0) goto fail; } while (0) |
| |
| int write_bootfs(int fd, const io_ops* op, item_t* item, bool compressed, bool extra) { |
| uint32_t n; |
| fsentry_t* e; |
| |
| uint32_t crcval = 0; |
| uint32_t* crc = NULL; |
| |
| size_t hdrsize = sizeof(bootdata_t); |
| |
| if (extra) { |
| hdrsize += sizeof(bootextra_t); |
| crc = &crcval; |
| } |
| |
| // Make note of where we started |
| off_t start = lseek(fd, 0, SEEK_CUR); |
| |
| if (start < 0) { |
| fprintf(stderr, "error: couldn't seek\n"); |
| fail: |
| return -1; |
| } |
| |
| if (compressed) { |
| // Set the LZ4 content size to be original size |
| lz4_prefs.frameInfo.contentSize = item->outsize; |
| } |
| |
| // Increment past the bootdata header which will be filled out later. |
| if (lseek(fd, (start + hdrsize), SEEK_SET) != (start + hdrsize)) { |
| fprintf(stderr, "error: cannot seek\n"); |
| return -1; |
| } |
| |
| void* cookie = NULL; |
| if (op->setup) { |
| CHECK(op->setup(fd, &cookie, crc)); |
| } |
| |
| // write directory size entry |
| { |
| bootfs_header_t hdr = { |
| .magic = BOOTFS_MAGIC, |
| .dirsize = item->hdrsize - sizeof(bootfs_header_t), |
| }; |
| CHECK(op->write(fd, &hdr, sizeof(hdr), cookie, crc)); |
| } |
| fsentry_t* last_entry = NULL; |
| for (e = item->first; e != NULL; e = e->next) { |
| bootfs_entry_t entry = { |
| .name_len = e->namelen, |
| .data_len = e->length, |
| .data_off = e->offset, |
| }; |
| CHECK(op->write(fd, &entry, sizeof(entry), cookie, crc)); |
| CHECK(op->write(fd, e->name, e->namelen, cookie, crc)); |
| if ((n = BOOTFS_ALIGN(e->namelen) - e->namelen) > 0) { |
| CHECK(op->write(fd, fill, n, cookie, crc)); |
| } |
| last_entry = e; |
| } |
| // Record length of last file |
| uint32_t last_length = last_entry ? last_entry->length : 0; |
| |
| if ((n = PAGEFILL(item->hdrsize))) { |
| CHECK(op->write(fd, fill, n, cookie, crc)); |
| } |
| |
| for (e = item->first; e != NULL; e = e->next) { |
| if (verbose) { |
| fprintf(stderr, "%08x %08x %s\n", e->offset, e->length, e->name); |
| } |
| CHECK(op->write_file(fd, e->srcpath, e->length, cookie, crc)); |
| if ((n = PAGEFILL(e->length))) { |
| CHECK(op->write(fd, fill, n, cookie, crc)); |
| } |
| } |
| // If the last entry has length zero, add an extra zero page at the end. |
| // This prevents the possibility of trying to read/map past the end of the |
| // bootfs at runtime. |
| if (last_length == 0) { |
| CHECK(op->write(fd, fill, sizeof(fill), cookie, crc)); |
| } |
| |
| if (op->finish) { |
| CHECK(op->finish(fd, cookie, crc)); |
| } |
| |
| off_t end = lseek(fd, 0, SEEK_CUR); |
| if (end < 0) { |
| fprintf(stderr, "error: couldn't seek\n"); |
| return -1; |
| } |
| |
| // pad bootdata_t records to 8 byte boundary |
| size_t pad = BOOTDATA_ALIGN(end) - end; |
| if (pad) { |
| if (writex(fd, fill, pad) < 0) { |
| return -1; |
| } |
| } |
| |
| // Write the bootheader |
| if (lseek(fd, start, SEEK_SET) != start) { |
| fprintf(stderr, "error: couldn't seek to bootdata header\n"); |
| return -1; |
| } |
| |
| size_t wrote = (end - start) - hdrsize; |
| |
| bootdata_t boothdr = { |
| .type = (item->type == ITEM_BOOTFS_SYSTEM) ? |
| BOOTDATA_BOOTFS_SYSTEM : BOOTDATA_BOOTFS_BOOT, |
| .length = wrote, |
| .extra = compressed ? item->outsize : wrote, |
| .flags = compressed ? BOOTDATA_BOOTFS_FLAG_COMPRESSED : 0 |
| }; |
| if (extra) { |
| boothdr.flags |= BOOTDATA_FLAG_EXTRA | BOOTDATA_FLAG_CRC32; |
| } |
| if (writex(fd, &boothdr, sizeof(boothdr)) < 0) { |
| return -1; |
| } |
| if (extra) { |
| bootextra_t extra = { |
| .reserved0 = 0, |
| .reserved1 = 0, |
| .magic = BOOTITEM_MAGIC, |
| .crc32 = 0, |
| }; |
| uint32_t hdrcrc = crc32(0, (void*) &boothdr, sizeof(boothdr)); |
| hdrcrc = crc32(hdrcrc, (void*) &extra, sizeof(extra)); |
| extra.crc32 = crc32_combine(hdrcrc, *crc, boothdr.length); |
| if (writex(fd, &extra, sizeof(extra)) < 0) { |
| return -1; |
| } |
| } |
| |
| if (lseek(fd, end + pad, SEEK_SET) != (end + pad)) { |
| fprintf(stderr, "error: couldn't seek to end of item\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| int write_bootitem(int fd, item_t* item, uint32_t type, size_t nulls, bool extra) { |
| uint32_t* crc = NULL; |
| |
| bootdata_t hdr = { |
| .type = type, |
| .length = item->first->length + nulls, |
| .extra = 0, |
| .flags = 0, |
| }; |
| bootextra_t ehdr = { |
| .reserved0 = 0, |
| .reserved1 = 0, |
| .magic = BOOTITEM_MAGIC, |
| .crc32 = 0, |
| }; |
| if (extra) { |
| hdr.flags |= BOOTDATA_FLAG_EXTRA | BOOTDATA_FLAG_CRC32; |
| } |
| if (writex(fd, &hdr, sizeof(hdr)) < 0) { |
| return -1; |
| } |
| off_t eoff = 0; |
| if (extra) { |
| if ((eoff = lseek(fd, 0, SEEK_CUR)) < 0) { |
| return -1; |
| } |
| // placeholder header |
| if (writex(fd, &ehdr, sizeof(ehdr)) < 0) { |
| return -1; |
| } |
| crc = &ehdr.crc32; |
| uint32_t tmp = crc32(0, (void*) &hdr, sizeof(hdr)); |
| *crc = crc32(tmp, (void*) &ehdr, sizeof(ehdr)); |
| } |
| if (copyfile(fd, item->first->srcpath, item->first->length, NULL, crc) < 0) { |
| return -1; |
| } |
| if (nulls && (copydata(fd, fill, nulls, NULL, crc) < 0)) { |
| return -1; |
| } |
| size_t pad = BOOTDATA_ALIGN(hdr.length) - hdr.length; |
| if (pad) { |
| if (writex(fd, fill, pad) < 0) { |
| return -1; |
| } |
| } |
| if (extra) { |
| // patch computed crc into extra header |
| off_t save; |
| if ((save = lseek(fd, 0, SEEK_CUR)) < 0) { |
| return -1; |
| } |
| if (lseek(fd, eoff, SEEK_SET) != eoff) { |
| return -1; |
| } |
| if (writex(fd, &ehdr, sizeof(ehdr)) < 0) { |
| return -1; |
| } |
| if (lseek(fd, save, SEEK_SET) != save) { |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| int write_bootdata(const char* fn, item_t* item, bool extra) { |
| //TODO: re-enable for debugging someday |
| bool compressed = true; |
| |
| int fd; |
| const io_ops* op = compressed ? &io_compressed : &io_plain; |
| |
| fd = open(fn, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| if (fd < 0) { |
| fprintf(stderr, "error: cannot create '%s'\n", fn); |
| return -1; |
| } |
| |
| size_t hdrsize = sizeof(bootdata_t); |
| if (extra) { |
| hdrsize += sizeof(bootextra_t); |
| } |
| |
| // Leave room for file header |
| if (lseek(fd, hdrsize, SEEK_SET) != hdrsize) { |
| fprintf(stderr, "error: cannot seek\n"); |
| goto fail; |
| } |
| |
| while (item != NULL) { |
| switch (item->type) { |
| case ITEM_BOOTDATA: |
| CHECK(copybootdatafile(fd, item->first->srcpath, item->first->length)); |
| break; |
| case ITEM_KERNEL: |
| CHECK(write_bootitem(fd, item, BOOTDATA_KERNEL, 0, extra)); |
| break; |
| case ITEM_CMDLINE: |
| CHECK(write_bootitem(fd, item, BOOTDATA_CMDLINE, 1, extra)); |
| break; |
| case ITEM_BOOTFS_BOOT: |
| case ITEM_BOOTFS_SYSTEM: |
| CHECK(write_bootfs(fd, op, item, compressed, extra)); |
| break; |
| default: |
| fprintf(stderr, "error: internal: type %08x unknown\n", item->type); |
| goto fail; |
| } |
| |
| item = item->next; |
| } |
| |
| off_t file_end = lseek(fd, 0, SEEK_CUR); |
| if (file_end < 0) { |
| fprintf(stderr, "error: couldn't seek\n"); |
| goto fail; |
| } |
| |
| // Write the file header |
| if (lseek(fd, 0, SEEK_SET) != 0) { |
| fprintf(stderr, "error: couldn't seek to bootdata file header\n"); |
| goto fail; |
| } |
| |
| bootdata_t filehdr = { |
| .type = BOOTDATA_CONTAINER, |
| .length = file_end - hdrsize, |
| .extra = BOOTDATA_MAGIC, |
| .flags = extra ? BOOTDATA_FLAG_EXTRA : 0, |
| }; |
| if (writex(fd, &filehdr, sizeof(filehdr)) < 0) { |
| goto fail; |
| } |
| if (extra) { |
| bootextra_t fileextra = { |
| .reserved0 = 0, |
| .reserved1 = 1, |
| .magic = BOOTITEM_MAGIC, |
| .crc32 = BOOTITEM_NO_CRC32, |
| }; |
| if (writex(fd, &fileextra, sizeof(fileextra)) < 0) { |
| goto fail; |
| } |
| } |
| close(fd); |
| return 0; |
| |
| fail: |
| fprintf(stderr, "error: failed writing '%s'\n", fn); |
| close(fd); |
| return -1; |
| } |
| |
| int dump_bootdata(const char* fn) { |
| int fd; |
| if ((fd = open(fn, O_RDONLY)) < 0) { |
| fprintf(stderr, "error: cannot open '%s'\n", fn); |
| return -1; |
| } |
| |
| bootdata_t hdr; |
| if (readx(fd, &hdr, sizeof(hdr)) < 0) { |
| fprintf(stderr, "error: cannot read header\n"); |
| goto fail; |
| } |
| |
| if ((hdr.type != BOOTDATA_CONTAINER) || |
| (hdr.extra != BOOTDATA_MAGIC) || |
| (hdr.length < sizeof(hdr))) { |
| fprintf(stderr, "error: invalid bootdata header\n"); |
| goto fail; |
| } |
| size_t off = sizeof(hdr); |
| if (hdr.flags & BOOTDATA_FLAG_EXTRA) { |
| bootextra_t extra; |
| if (readx(fd, &extra, sizeof(extra)) < 0) { |
| fprintf(stderr, "error: cannot read extra header\n"); |
| goto fail; |
| } |
| if (extra.magic != BOOTITEM_MAGIC) { |
| fprintf(stderr, "error: invalid extra header\n"); |
| goto fail; |
| } |
| off += sizeof(bootextra_t); |
| } |
| size_t end = off + hdr.length; |
| |
| while (off < end) { |
| if (readx(fd, &hdr, sizeof(hdr)) < 0) { |
| fprintf(stderr, "error: cannot read section header\n"); |
| goto fail; |
| } |
| switch (hdr.type) { |
| case BOOTDATA_BOOTFS_BOOT: |
| printf("%08zx: %08x BOOTFS @/boot (size=%08x)\n", |
| off, hdr.length, hdr.extra); |
| break; |
| case BOOTDATA_BOOTFS_SYSTEM: |
| printf("%08zx: %08x BOOTFS @/system (size=%08x)\n", |
| off, hdr.length, hdr.extra); |
| break; |
| case BOOTDATA_KERNEL: |
| printf("%08zx: %08x KERNEL\n", off, hdr.length); |
| break; |
| case BOOTDATA_MDI: |
| printf("%08zx: %08x MDI\n", off, hdr.length); |
| break; |
| case BOOTDATA_CMDLINE: |
| printf("%08zx: %08x CMDLINE\n", off, hdr.length); |
| break; |
| default: |
| printf("%08zx: %08x UNKNOWN (type=%08x)\n", off, hdr.length, hdr.type); |
| break; |
| } |
| off += sizeof(hdr); |
| |
| bootextra_t ehdr; |
| uint32_t crc = 0; |
| if (hdr.flags & BOOTDATA_FLAG_EXTRA) { |
| if (readx(fd, &ehdr, sizeof(ehdr)) < 0) { |
| fprintf(stderr, "error: cannot read extra header data\n"); |
| goto fail; |
| } |
| printf("%08zx: MAGIC=%08x CRC=%08x\n", off, ehdr.magic, ehdr.crc32); |
| if (ehdr.magic != BOOTITEM_MAGIC) { |
| fprintf(stderr, "error: bad bootitem magic\n"); |
| } |
| uint32_t tmp = ehdr.crc32; |
| ehdr.crc32 = 0; |
| crc = crc32(crc, (void*) &hdr, sizeof(hdr)); |
| crc = crc32(crc, (void*) &ehdr, sizeof(ehdr)); |
| ehdr.crc32 = tmp; |
| off += sizeof(ehdr); |
| } |
| size_t pad = BOOTDATA_ALIGN(hdr.length) - hdr.length; |
| if (hdr.flags & BOOTDATA_FLAG_CRC32) { |
| if (!(hdr.flags & BOOTDATA_FLAG_EXTRA)) { |
| fprintf(stderr, "error: crc32 indicated w/out extra data!\n"); |
| goto fail; |
| } |
| if (readcrc32(fd, hdr.length, &crc) < 0) { |
| fprintf(stderr, "error: failed to read data for crc\n"); |
| goto fail; |
| } |
| if (crc != ehdr.crc32) { |
| fprintf(stderr, "error: CRC %08x does not match header\n", crc); |
| } |
| if (pad && (lseek(fd, pad, SEEK_CUR) < 0)) { |
| fprintf(stderr, "error: seeking\n"); |
| goto fail; |
| } |
| } else { |
| if (lseek(fd, hdr.length + pad, SEEK_CUR) < 0) { |
| fprintf(stderr, "error: seeking\n"); |
| goto fail; |
| } |
| } |
| off += hdr.length + pad; |
| } |
| close(fd); |
| return 0; |
| fail: |
| close(fd); |
| return -1; |
| } |
| |
| |
| void usage(void) { |
| fprintf(stderr, |
| "usage: mkbootfs <option-or-input>*\n" |
| "\n" |
| " mkbootfs creates a bootdata image consisting of the inputs\n" |
| " provided in the specified order.\n" |
| "\n" |
| "options: -o <filename> output bootdata file name\n" |
| " -k <filename> include kernel (must be first)\n" |
| " -C <filename> include kernel command line\n" |
| " -c compress bootfs image (default)\n" |
| " -v verbose output\n" |
| " -x enable bootextra data (crc32)\n" |
| " -t <filename> dump bootdata contents\n" |
| " -g <group> select allowed groups for manifest items\n" |
| " (multiple groups may be comma separated)\n" |
| " (the value 'all' resets to include all groups)\n" |
| " --uncompressed don't compress bootfs image (debug only)\n" |
| " --target=system bootfs to be unpacked at /system\n" |
| " --target=boot bootfs to be unpacked at /boot\n" |
| "\n" |
| "inputs: <filename> file containing bootdata (binary)\n" |
| " or a manifest (target=srcpath lines)\n" |
| " @<directory> directory to recursively import\n" |
| "\n" |
| "notes: Each manifest or directory is imported as a distinct bootfs\n" |
| " section, tagged for unpacking at /boot or /system based on\n" |
| " the most recent --target= directive.\n" |
| ); |
| } |
| |
| int main(int argc, char **argv) { |
| const char* output_file = "user.bootfs"; |
| |
| bool compressed = true; |
| bool have_kernel = false; |
| bool have_cmdline = false; |
| bool extra = false; |
| unsigned incount = 0; |
| |
| if (argc == 1) { |
| usage(); |
| return -1; |
| } |
| bool system = true; |
| |
| if ((argc == 3) && (!strcmp(argv[1],"-t"))) { |
| return dump_bootdata(argv[2]); |
| } |
| |
| argc--; |
| argv++; |
| while (argc > 0) { |
| const char* cmd = argv[0]; |
| if (!strcmp(cmd,"-v")) { |
| verbose = 1; |
| } else if (!strcmp(cmd,"-o")) { |
| if (argc < 2) { |
| fprintf(stderr, "error: no output filename given\n"); |
| return -1; |
| } |
| output_file = argv[1]; |
| argc--; |
| argv++; |
| } else if (!strcmp(cmd,"-k")) { |
| if (have_kernel) { |
| fprintf(stderr, "error: only one kernel may be included\n"); |
| return -1; |
| } |
| if (argc < 2) { |
| fprintf(stderr, "error: no kernel filename given\n"); |
| return -1; |
| } |
| if (first_item != NULL) { |
| fprintf(stderr, "error: kernel must be the first input\n"); |
| return -1; |
| } |
| have_kernel = 1; |
| if (import_file_as(argv[1], ITEM_KERNEL, 0, NULL) < 0) { |
| return -1; |
| } |
| argc--; |
| argv++; |
| } else if (!strcmp(cmd, "-C")) { |
| if (have_cmdline) { |
| fprintf(stderr, "error: only one command line may be included\n"); |
| return -1; |
| } |
| if (argc < 2) { |
| fprintf(stderr, "error: no kernel command line file given\n"); |
| return -1; |
| } |
| have_cmdline = true; |
| |
| if (import_file_as(argv[1], ITEM_CMDLINE, 0, NULL) < 0) { |
| return -1; |
| } |
| argc--; |
| argv++; |
| } else if (!strcmp(cmd,"-g")) { |
| if (argc < 2) { |
| fprintf(stderr, "error: no group specified\n"); |
| return -1; |
| } |
| group_filter = NULL; |
| if (strcmp(argv[1], "all")) { |
| char* group = argv[1]; |
| while (group) { |
| char* next = strchr(group, ','); |
| if (next) { |
| *next++ = 0; |
| } |
| if (add_filter(&group_filter, group) < 0) { |
| return -1; |
| } |
| group = next; |
| } |
| } |
| argc--; |
| argv++; |
| } else if (!strcmp(cmd,"-h") || !strcmp(cmd, "--help")) { |
| usage(); |
| fprintf(stderr, "usage: mkbootfs [-v] [-o <fsimage>] <manifests>...\n"); |
| return 0; |
| } else if (!strcmp(cmd,"-t")) { |
| fprintf(stderr, "error: -t option must be used alone, with one filename.\n"); |
| return 0; |
| } else if (!strcmp(cmd,"-x")) { |
| extra = true; |
| } else if (!strcmp(cmd,"-c")) { |
| compressed = true; |
| } else if (!strcmp(cmd,"--uncompressed")) { |
| compressed = false; |
| } else if (!strcmp(cmd,"--target=system")) { |
| system = true; |
| } else if (!strcmp(cmd,"--target=boot")) { |
| system = false; |
| } else if (cmd[0] == '-') { |
| fprintf(stderr, "unknown option: %s\n", cmd); |
| return -1; |
| } else { |
| // input file |
| incount++; |
| char* path = argv[0]; |
| if (path[0] == '@') { |
| path++; |
| int len = strlen(path); |
| if (path[len - 1] == '/') { |
| // remove trailing slash |
| path[len - 1] = 0; |
| } |
| if (import_directory("", path, NULL, system) < 0) { |
| fprintf(stderr, "error: failed to import directory %s\n", path); |
| return -1; |
| } |
| } else if (import_file(path, system) < 0) { |
| fprintf(stderr, "error: failed to import file %s\n", path); |
| return -1; |
| } |
| } |
| argc--; |
| argv++; |
| } |
| if (first_item == NULL) { |
| fprintf(stderr, "error: no inputs given\n"); |
| return -1; |
| } |
| |
| // preflight calculations for bootfs items |
| for (item_t* item = first_item; item != NULL; item = item->next) { |
| switch (item->type) { |
| case ITEM_BOOTFS_BOOT: |
| case ITEM_BOOTFS_SYSTEM: |
| // account for the bootfs header record |
| item->hdrsize += sizeof(bootfs_header_t); |
| size_t off = PAGEALIGN(item->hdrsize); |
| fsentry_t* last_entry = NULL; |
| for (fsentry_t* e = item->first; e != NULL; e = e->next) { |
| e->offset = off; |
| off += PAGEALIGN(e->length); |
| if (off > INT32_MAX) { |
| fprintf(stderr, "error: userfs too large\n"); |
| return -1; |
| } |
| last_entry = e; |
| } |
| if (last_entry && last_entry->length == 0) { |
| off += sizeof(fill); |
| } |
| item->outsize = off; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| return write_bootdata(output_file, first_item, extra); |
| } |