1// Copyright 2018 The Fuchsia Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <algorithm> 6#include <array> 7#include <cassert> 8#include <cerrno> 9#include <climits> 10#include <cstdio> 11#include <cstdlib> 12#include <cstring> 13#include <deque> 14#include <dirent.h> 15#include <fcntl.h> 16#include <fnmatch.h> 17#include <forward_list> 18#include <functional> 19#include <getopt.h> 20#include <limits> 21#include <list> 22#include <memory> 23#include <numeric> 24#include <set> 25#include <string> 26#include <sys/mman.h> 27#include <sys/stat.h> 28#include <sys/uio.h> 29#include <unistd.h> 30#include <utility> 31#include <vector> 32 33#include <fbl/macros.h> 34#include <fbl/unique_fd.h> 35#include <lib/cksum.h> 36#include <lz4/lz4frame.h> 37#include <zircon/boot/image.h> 38 39namespace { 40 41const char* const kCmdlineWS = " \t\r\n"; 42 43bool Aligned(uint32_t length) { 44 return length % ZBI_ALIGNMENT == 0; 45} 46 47// It's not clear where this magic number comes from. 48constexpr size_t kLZ4FMaxHeaderFrameSize = 128; 49 50// iovec.iov_base is void* but we only use pointers to const. 51template<typename T> 52iovec Iovec(const T* buffer, size_t size = sizeof(T)) { 53 assert(size > 0); 54 return {const_cast<void*>(static_cast<const void*>(buffer)), size}; 55} 56 57class AppendBuffer { 58public: 59 explicit AppendBuffer(size_t size) : 60 buffer_(std::make_unique<std::byte[]>(size)), ptr_(buffer_.get()) { 61 } 62 63 size_t size() const { 64 return ptr_ - buffer_.get(); 65 } 66 67 iovec get() { 68 return Iovec(buffer_.get(), size()); 69 } 70 71 std::unique_ptr<std::byte[]> release() { 72 ptr_ = nullptr; 73 return std::move(buffer_); 74 } 75 76 template<typename T> 77 void Append(const T* data, size_t bytes = sizeof(T)) { 78 ptr_ = static_cast<std::byte*>(memcpy(static_cast<void*>(ptr_), 79 static_cast<const void*>(data), 80 bytes)) + bytes; 81 } 82 83 void Pad(size_t bytes) { 84 ptr_ = static_cast<std::byte*>(memset(static_cast<void*>(ptr_), 0, 85 bytes)) + bytes; 86 } 87 88private: 89 std::unique_ptr<std::byte[]> buffer_; 90 std::byte* ptr_ = nullptr; 91}; 92 93class Item; 94using ItemPtr = std::unique_ptr<Item>; 95 96class OutputStream { 97public: 98 OutputStream() = delete; 99 100 DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(OutputStream); 101 OutputStream(OutputStream&&) = default; 102 103 explicit OutputStream(fbl::unique_fd fd) : fd_(std::move(fd)) { 104 } 105 106 ~OutputStream() { 107 Flush(); 108 } 109 110 // Queue the iovec for output. The second argument can transfer 111 // ownership of the memory that buffer.iov_base points into. This 112 // object may refer to buffer.iov_base until Flush() completes. 113 void Write(const iovec& buffer, 114 std::unique_ptr<std::byte[]> owned = nullptr) { 115 assert(buffer.iov_len > 0); 116 if (buffer.iov_len + total_ > UINT32_MAX - sizeof(zbi_header_t) + 1) { 117 fprintf(stderr, "output size exceeds format maximum\n"); 118 exit(1); 119 } 120 total_ += static_cast<uint32_t>(buffer.iov_len); 121 *write_pos_++ = buffer; 122 if (write_pos_ == iov_.end()) { 123 Flush(); 124 } else if (owned) { 125 owned_buffers_.push_front(std::move(owned)); 126 } 127 } 128 129 uint32_t WritePosition() const { 130 return total_; 131 } 132 133 void Flush() { 134 auto read_pos = iov_.begin(); 135 while (read_pos != write_pos_) { 136 read_pos = WriteBuffers(read_pos); 137 } 138 write_pos_ = iov_.begin(); 139 owned_buffers_.clear(); 140 } 141 142 // Emit a placeholder. The return value will be passed to PatchHeader. 143 uint32_t PlaceHeader() { 144 uint32_t pos = WritePosition(); 145 static const zbi_header_t dummy = {}; 146 Write(Iovec(&dummy)); 147 return pos; 148 } 149 150 // Replace a placeholder with a real header. 151 void PatchHeader(const zbi_header_t& header, uint32_t place) { 152 assert(place < total_); 153 assert(total_ - place >= sizeof(header)); 154 155 if (flushed_ <= place) { 156 // We haven't actually written it yet, so just update it in 157 // memory. A placeholder always has its own iovec, so just 158 // skip over earlier ones until we hit the right offset. 159 auto it = iov_.begin(); 160 for (place -= flushed_; place > 0; place -= it++->iov_len) { 161 assert(it != write_pos_); 162 assert(place >= it->iov_len); 163 } 164 assert(it->iov_len == sizeof(header)); 165 auto buffer = std::make_unique<std::byte[]>(sizeof(header)); 166 it->iov_base = memcpy(buffer.get(), &header, sizeof(header)); 167 owned_buffers_.push_front(std::move(buffer)); 168 } else { 169 assert(flushed_ >= place + sizeof(header)); 170 // Overwrite the earlier part of the file with pwrite. This 171 // does not affect the current lseek position for the next writev. 172 auto buf = reinterpret_cast<const std::byte*>(&header); 173 size_t len = sizeof(header); 174 while (len > 0) { 175 ssize_t wrote = pwrite(fd_.get(), buf, len, place); 176 if (wrote < 0) { 177 perror("pwrite on output file"); 178 exit(1); 179 } 180 len -= wrote; 181 buf += wrote; 182 place += wrote; 183 } 184 } 185 } 186 187private: 188 using IovecArray = std::array<iovec, IOV_MAX>; 189 IovecArray iov_; 190 IovecArray::iterator write_pos_ = iov_.begin(); 191 // iov_[n].iov_base might point into these buffers. They're just 192 // stored here to own the buffers until iov_ is flushed. 193 std::forward_list<std::unique_ptr<std::byte[]>> owned_buffers_; 194 fbl::unique_fd fd_; 195 uint32_t flushed_ = 0; 196 uint32_t total_ = 0; 197 198 bool Buffering() const { 199 return write_pos_ != iov_.begin(); 200 } 201 202 IovecArray::iterator WriteBuffers(IovecArray::iterator read_pos) { 203 assert(read_pos != write_pos_); 204 ssize_t wrote = writev(fd_.get(), &(*read_pos), write_pos_ - read_pos); 205 if (wrote < 0) { 206 perror("writev to output file"); 207 exit(1); 208 } 209 flushed_ += wrote; 210#ifndef NDEBUG 211 off_t pos = lseek(fd_.get(), 0, SEEK_CUR); 212#endif 213 assert(static_cast<off_t>(flushed_) == pos || 214 (pos == -1 && errno == ESPIPE)); 215 // Skip all the buffers that were wholly written. 216 while (wrote >= read_pos->iov_len) { 217 wrote -= read_pos->iov_len; 218 ++read_pos; 219 if (wrote == 0) { 220 break; 221 } 222 assert(read_pos != write_pos_); 223 } 224 if (wrote > 0) { 225 // writev wrote only part of this buffer. Do the rest next time. 226 read_pos->iov_len -= wrote; 227 read_pos->iov_base = static_cast<void*>( 228 static_cast<std::byte*>(read_pos->iov_base) + wrote); 229 } 230 return read_pos; 231 } 232}; 233 234class FileWriter { 235public: 236 FileWriter(const char* outfile, std::string prefix) : 237 prefix_(std::move(prefix)), outfile_(outfile) { 238 } 239 240 unsigned int NextFileNumber() const { 241 return files_ + 1; 242 } 243 244 OutputStream RawFile(const char* name) { 245 ++files_; 246 if (outfile_) { 247 if (files_ > 1) { 248 fprintf(stderr, 249 "--output (-o) cannot write second file %s\n", name); 250 exit(1); 251 } else { 252 return CreateFile(outfile_); 253 } 254 } else { 255 auto file = prefix_ + name; 256 return CreateFile(file.c_str()); 257 } 258 } 259 260private: 261 std::string prefix_; 262 const char* outfile_ = nullptr; 263 unsigned int files_ = 0; 264 265 OutputStream CreateFile(const char* outfile) { 266 // Remove the file in case it exists. This makes it safe to 267 // to do e.g. `zbi -o boot.zbi boot.zbi --entry=bin/foo=mybuild/foo` 268 // to modify a file "in-place" because the input `boot.zbi` will 269 // already have been opened before the new `boot.zbi` is created. 270 remove(outfile); 271 272 fbl::unique_fd fd(open(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0666)); 273 if (!fd && errno == ENOENT) { 274 MakeDirs(outfile); 275 fd.reset(open(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0666)); 276 } 277 if (!fd) { 278 fprintf(stderr, "cannot create %s: %s\n", 279 outfile, strerror(errno)); 280 exit(1); 281 } 282 283 return OutputStream(std::move(fd)); 284 } 285 286 static void MakeDirs(const std::string& name) { 287 auto lastslash = name.rfind('/'); 288 if (lastslash == std::string::npos) { 289 return; 290 } 291 auto dir = name.substr(0, lastslash); 292 if (mkdir(dir.c_str(), 0777) == 0) { 293 return; 294 } 295 if (errno == ENOENT) { 296 MakeDirs(dir); 297 if (mkdir(dir.c_str(), 0777) == 0) { 298 return; 299 } 300 } 301 if (errno != EEXIST) { 302 fprintf(stderr, "mkdir: %s: %s\n", 303 dir.c_str(), strerror(errno)); 304 exit(1); 305 } 306 } 307}; 308 309class NameMatcher { 310public: 311 NameMatcher(const char* const* patterns, int count) : 312 begin_(patterns), end_(&patterns[count]) { 313 assert(count >= 0); 314 assert(!patterns[count]); 315 } 316 NameMatcher(char** argv, int argi, int argc) : 317 NameMatcher(&argv[argi], argc - argi) { 318 } 319 320 unsigned int names_checked() const { return names_checked_; } 321 unsigned int names_matched() const { return names_matched_; } 322 323 bool MatchesAll(void) const { return begin_ == end_; } 324 325 // Not const because it keeps stats. 326 bool Matches(const char* name, bool casefold = false) { 327 ++names_checked_; 328 if (MatchesAll() || PatternMatch(name, casefold)) { 329 ++names_matched_; 330 return true; 331 } else { 332 return false; 333 } 334 } 335 336 void Summary(const char* verbed, const char* items, bool verbose) { 337 if (!MatchesAll()) { 338 if (names_checked() == 0) { 339 fprintf(stderr, "no %s\n", items); 340 exit(1); 341 } else if (names_matched() == 0) { 342 fprintf(stderr, "no matching %s\n", items); 343 exit(1); 344 } else if (verbose) { 345 printf("%s %u of %u %s\n", 346 verbed, names_matched(), names_checked(), items); 347 } 348 } 349 } 350 351private: 352 const char* const* const begin_ = nullptr; 353 const char* const* const end_ = nullptr; 354 unsigned int names_checked_ = 0; 355 unsigned int names_matched_ = 0; 356 357 bool PatternMatch(const char* name, bool casefold) const { 358 bool excludes = false, included = false; 359 for (auto next = begin_; next != end_; ++next) { 360 auto ptn = *next; 361 if (ptn[0] == '!' || ptn[0] == '^') { 362 excludes = true; 363 } else { 364 included = (included || fnmatch( 365 ptn, name, casefold ? FNM_CASEFOLD : 0) == 0); 366 } 367 } 368 if (included && excludes) { 369 for (auto next = begin_; next != end_; ++next) { 370 auto ptn = *next; 371 if (ptn[0] == '!' || ptn[0] == '^') { 372 ++ptn; 373 if (fnmatch(ptn, name, casefold ? FNM_CASEFOLD : 0) == 0) { 374 return false; 375 } 376 } 377 } 378 } 379 return false; 380 } 381}; 382 383class Checksummer { 384public: 385 void Write(const iovec& buffer) { 386 crc_ = crc32(crc_, static_cast<const uint8_t*>(buffer.iov_base), 387 buffer.iov_len); 388 } 389 390 void Write(const std::list<const iovec>& list) { 391 for (const auto& buffer : list) { 392 Write(buffer); 393 } 394 } 395 396 void FinalizeHeader(zbi_header_t* header) { 397 header->crc32 = 0; 398 uint32_t header_crc = crc32( 399 0, reinterpret_cast<const uint8_t*>(header), sizeof(*header)); 400 header->crc32 = crc32_combine(header_crc, crc_, header->length); 401 } 402 403private: 404 uint32_t crc_ = 0; 405}; 406 407// This tells LZ4f_compressUpdate it can keep a pointer to data. 408constexpr const LZ4F_compressOptions_t kCompressOpt = { 1, {} }; 409 410class Compressor { 411public: 412 DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(Compressor); 413 Compressor() = default; 414 415#define LZ4F_CALL(func, ...) \ 416 [&](){ \ 417 auto result = func(__VA_ARGS__); \ 418 if (LZ4F_isError(result)) { \ 419 fprintf(stderr, "%s: %s\n", #func, LZ4F_getErrorName(result)); \ 420 exit(1); \ 421 } \ 422 return result; \ 423 }() 424 425 void Init(OutputStream* out, const zbi_header_t& header) { 426 header_ = header; 427 assert(header_.flags & ZBI_FLAG_STORAGE_COMPRESSED); 428 assert(header_.flags & ZBI_FLAG_CRC32); 429 430 // Write a place-holder for the header, which we will go back 431 // and fill in once we know the payload length and CRC. 432 header_pos_ = out->PlaceHeader(); 433 434 prefs_.frameInfo.contentSize = header_.length; 435 436 prefs_.frameInfo.blockSizeID = LZ4F_max64KB; 437 prefs_.frameInfo.blockMode = LZ4F_blockIndependent; 438 439 // LZ4 compression levels 1-3 are for "fast" compression, and 4-16 440 // are for higher compression. The additional compression going from 441 // 4 to 16 is not worth the extra time needed during compression. 442 prefs_.compressionLevel = 4; 443 444 LZ4F_CALL(LZ4F_createCompressionContext, &ctx_, LZ4F_VERSION); 445 446 // Record the original uncompressed size in header_.extra. 447 // WriteBuffer will accumulate the compressed size in header_.length. 448 header_.extra = header_.length; 449 header_.length = 0; 450 451 // This might start writing compression format headers before it 452 // receives any data. 453 auto buffer = GetBuffer(kLZ4FMaxHeaderFrameSize); 454 size_t size = LZ4F_CALL(LZ4F_compressBegin, ctx_, 455 buffer.data.get(), buffer.size, &prefs_); 456 assert(size <= buffer.size); 457 WriteBuffer(out, std::move(buffer), size); 458 } 459 460 ~Compressor() { 461 LZ4F_CALL(LZ4F_freeCompressionContext, ctx_); 462 } 463 464 // NOTE: Input buffer may be referenced for the life of the Compressor! 465 void Write(OutputStream* out, const iovec& input) { 466 auto buffer = GetBuffer(LZ4F_compressBound(input.iov_len, &prefs_)); 467 size_t actual_size = LZ4F_CALL(LZ4F_compressUpdate, 468 ctx_, buffer.data.get(), buffer.size, 469 input.iov_base, input.iov_len, 470 &kCompressOpt); 471 WriteBuffer(out, std::move(buffer), actual_size); 472 } 473 474 uint32_t Finish(OutputStream* out) { 475 // Write the closing chunk from the compressor. 476 auto buffer = GetBuffer(LZ4F_compressBound(0, &prefs_)); 477 size_t actual_size = LZ4F_CALL(LZ4F_compressEnd, 478 ctx_, buffer.data.get(), buffer.size, 479 &kCompressOpt); 480 481 WriteBuffer(out, std::move(buffer), actual_size); 482 483 // Complete the checksum. 484 crc_.FinalizeHeader(&header_); 485 486 // Write the header back where its place was held. 487 out->PatchHeader(header_, header_pos_); 488 return header_.length; 489 } 490 491private: 492 struct Buffer { 493 // Move-only type: after moving, data is nullptr and size is 0. 494 Buffer() = default; 495 Buffer(std::unique_ptr<std::byte[]> buffer, size_t max_size) : 496 data(std::move(buffer)), size(max_size) { 497 } 498 Buffer(Buffer&& other) { 499 *this = std::move(other); 500 } 501 Buffer& operator=(Buffer&& other) { 502 data = std::move(other.data); 503 size = other.size; 504 other.size = 0; 505 return *this; 506 } 507 std::unique_ptr<std::byte[]> data; 508 size_t size = 0; 509 } unused_buffer_; 510 zbi_header_t header_; 511 Checksummer crc_; 512 LZ4F_compressionContext_t ctx_; 513 LZ4F_preferences_t prefs_{}; 514 uint32_t header_pos_ = 0; 515 // IOV_MAX buffers might be live at once. 516 static constexpr const size_t kMinBufferSize = (128 << 20) / IOV_MAX; 517 518 Buffer GetBuffer(size_t max_size) { 519 if (unused_buffer_.size >= max_size) { 520 // We have an old buffer that will do fine. 521 return std::move(unused_buffer_); 522 } else { 523 // Get a new buffer. 524 max_size = std::max(max_size, kMinBufferSize); 525 return {std::make_unique<std::byte[]>(max_size), max_size}; 526 } 527 } 528 529 void WriteBuffer(OutputStream* out, Buffer buffer, size_t actual_size) { 530 if (actual_size > 0) { 531 header_.length += actual_size; 532 const iovec iov{buffer.data.get(), actual_size}; 533 crc_.Write(iov); 534 out->Write(iov, std::move(buffer.data)); 535 buffer.size = 0; 536 } else { 537 // The compressor often delivers zero bytes for an input chunk. 538 // Stash the unused buffer for next time to cut down on new/delete. 539 unused_buffer_ = std::move(buffer); 540 } 541 } 542}; 543 544const size_t Compressor::kMinBufferSize; 545 546constexpr const LZ4F_decompressOptions_t kDecompressOpt{}; 547 548std::unique_ptr<std::byte[]> Decompress(const std::list<const iovec>& payload, 549 uint32_t decompressed_length) { 550 auto buffer = std::make_unique<std::byte[]>(decompressed_length); 551 552 LZ4F_decompressionContext_t ctx; 553 LZ4F_CALL(LZ4F_createDecompressionContext, &ctx, LZ4F_VERSION); 554 555 std::byte* dst = buffer.get(); 556 size_t dst_size = decompressed_length; 557 for (const auto& iov : payload) { 558 auto src = static_cast<const std::byte*>(iov.iov_base); 559 size_t src_size = iov.iov_len; 560 do { 561 if (dst_size == 0) { 562 fprintf(stderr, "decompression produced too much data\n"); 563 exit(1); 564 } 565 566 size_t nwritten = dst_size, nread = src_size; 567 LZ4F_CALL(LZ4F_decompress, ctx, dst, &nwritten, src, &nread, 568 &kDecompressOpt); 569 570 assert(nread <= src_size); 571 src += nread; 572 src_size -= nread; 573 574 assert(nwritten <= dst_size); 575 dst += nwritten; 576 dst_size -= nwritten; 577 } while (src_size > 0); 578 } 579 if (dst_size > 0) { 580 fprintf(stderr, 581 "decompression produced too little data by %zu bytes\n", 582 dst_size); 583 exit(1); 584 } 585 586 LZ4F_CALL(LZ4F_freeDecompressionContext, ctx); 587 588 return buffer; 589} 590 591#undef LZ4F_CALL 592 593class FileContents { 594public: 595 DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(FileContents); 596 FileContents() = default; 597 598 // Get unowned file contents from a BOOTFS image. 599 // The entry has been validated against the payload size. 600 FileContents(const zbi_bootfs_dirent_t& entry, 601 const std::byte* bootfs_payload) : 602 mapped_(const_cast<void*>(static_cast<const void*>(bootfs_payload + 603 entry.data_off))), 604 mapped_size_(ZBI_BOOTFS_PAGE_ALIGN(entry.data_len)), 605 exact_size_(entry.data_len), 606 owned_(false) { 607 } 608 609 // Get unowned file contents from a string. 610 // This object won't support PageRoundedView. 611 FileContents(const char* buffer, bool null_terminate) : 612 mapped_(const_cast<char*>(buffer)), mapped_size_(strlen(buffer) + 1), 613 exact_size_(mapped_size_ - (null_terminate ? 0 : 1)), owned_(false) { 614 } 615 616 FileContents(FileContents&& other) { 617 *this = std::move(other); 618 } 619 620 FileContents& operator=(FileContents&& other) { 621 std::swap(mapped_, other.mapped_); 622 std::swap(mapped_size_, other.mapped_size_); 623 std::swap(exact_size_, other.exact_size_); 624 std::swap(owned_, other.owned_); 625 return *this; 626 } 627 628 ~FileContents() { 629 if (owned_ && mapped_) { 630 munmap(mapped_, mapped_size_); 631 } 632 } 633 634 size_t exact_size() const { return exact_size_; } 635 size_t mapped_size() const { return mapped_size_; } 636 637 static FileContents Map(const fbl::unique_fd& fd, 638 const struct stat& st, 639 const char* filename) { 640 // st_size is off_t, everything else is size_t. 641 const size_t size = st.st_size; 642 static_assert(std::numeric_limits<decltype(st.st_size)>::max() <= 643 std::numeric_limits<size_t>::max(), "size_t < off_t?"); 644 645 static size_t pagesize = []() -> size_t { 646 size_t pagesize = sysconf(_SC_PAGE_SIZE); 647 assert(pagesize >= ZBI_BOOTFS_PAGE_SIZE); 648 assert(pagesize % ZBI_BOOTFS_PAGE_SIZE == 0); 649 return pagesize; 650 }(); 651 652 void* map = mmap(nullptr, size, 653 PROT_READ, MAP_FILE | MAP_PRIVATE, fd.get(), 0); 654 if (map == MAP_FAILED) { 655 fprintf(stderr, "mmap: %s: %s\n", filename, strerror(errno)); 656 exit(1); 657 } 658 assert(map); 659 660 FileContents result; 661 result.mapped_ = map; 662 result.exact_size_ = size; 663 result.mapped_size_ = (size + pagesize - 1) & -pagesize; 664 return result; 665 } 666 667 const iovec View(size_t offset, size_t length) const { 668 assert(length > 0); 669 assert(offset < exact_size_); 670 assert(exact_size_ - offset >= length); 671 return Iovec(static_cast<const std::byte*>(mapped_) + offset, length); 672 } 673 674 const iovec PageRoundedView(size_t offset, size_t length) const { 675 assert(length > 0); 676 assert(offset < mapped_size_); 677 assert(mapped_size_ - offset >= length); 678 return Iovec(static_cast<const std::byte*>(mapped_) + offset, length); 679 } 680 681private: 682 void* mapped_ = nullptr; 683 size_t mapped_size_ = 0; 684 size_t exact_size_ = 0; 685 bool owned_ = true; 686}; 687 688class FileOpener { 689public: 690 DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(FileOpener); 691 FileOpener() = default; 692 693 void Init(const char* output_file, const char* depfile) { 694 if (depfile) { 695 depfile_ = fopen(depfile, "w"); 696 if (!depfile_) { 697 perror(depfile); 698 exit(1); 699 } 700 fprintf(depfile_, "%s:", output_file); 701 } 702 } 703 704 fbl::unique_fd Open(const char* file, struct stat* st = nullptr) { 705 fbl::unique_fd fd(open(file, O_RDONLY)); 706 if (!fd) { 707 perror(file); 708 exit(1); 709 } 710 if (st && fstat(fd.get(), st) < 0) { 711 perror("fstat"); 712 exit(1); 713 } 714 if (depfile_) { 715 fprintf(depfile_, " %s", file); 716 } 717 return fd; 718 } 719 720 fbl::unique_fd Open(const std::string& file, struct stat* st = nullptr) { 721 return Open(file.c_str(), st); 722 } 723 724 ~FileOpener() { 725 if (depfile_) { 726 fputc('\n', depfile_); 727 fclose(depfile_); 728 } 729 } 730 731private: 732 FILE* depfile_ = nullptr; 733}; 734 735void RequireRegularFile(const struct stat& st, const char* file) { 736 if (!S_ISREG(st.st_mode)) { 737 fprintf(stderr, "%s: not a regular file\n", file); 738 exit(1); 739 } 740} 741 742class GroupFilter { 743public: 744 DISALLOW_COPY_ASSIGN_AND_MOVE(GroupFilter); 745 GroupFilter() = default; 746 747 void SetFilter(const char* groups) { 748 if (!strcmp(groups, "all")) { 749 groups_.reset(); 750 } else { 751 not_ = groups[0] == '!'; 752 if (not_) { 753 ++groups; 754 } 755 groups_ = std::make_unique<std::set<std::string>>(); 756 while (const char *p = strchr(groups, ',')) { 757 groups_->emplace(groups, p - groups); 758 groups = p + 1; 759 } 760 groups_->emplace(groups); 761 } 762 } 763 764 bool AllowsUnspecified() const { 765 return !groups_ || not_; 766 } 767 768 bool Allows(const std::string& group) const { 769 return !groups_ || (groups_->find(group) == groups_->end()) == not_; 770 } 771 772private: 773 std::unique_ptr<std::set<std::string>> groups_; 774 bool not_ = false; 775}; 776 777// Base class for ManifestInputFileGenerator and DirectoryInputFileGenerator. 778// These both deliver target name -> file contents mappings until they don't. 779struct InputFileGenerator { 780 struct value_type { 781 std::string target; 782 FileContents file; 783 }; 784 virtual ~InputFileGenerator() = default; 785 virtual bool Next(FileOpener*, const std::string& prefix, value_type*) = 0; 786}; 787 788using InputFileGeneratorList = 789 std::deque<std::unique_ptr<InputFileGenerator>>; 790 791class ManifestInputFileGenerator : public InputFileGenerator { 792public: 793 ManifestInputFileGenerator(FileContents file, std::string prefix, 794 const GroupFilter* filter) : 795 file_(std::move(file)), prefix_(std::move(prefix)), filter_(filter) { 796 read_ptr_ = static_cast<const char*>( 797 file_.View(0, file_.exact_size()).iov_base); 798 eof_ = read_ptr_ + file_.exact_size(); 799 } 800 801 ~ManifestInputFileGenerator() override = default; 802 803 bool Next(FileOpener* opener, const std::string& prefix, 804 value_type* value) override { 805 while (read_ptr_ != eof_) { 806 auto eol = static_cast<const char*>( 807 memchr(read_ptr_, '\n', eof_ - read_ptr_)); 808 auto line = read_ptr_; 809 if (eol) { 810 read_ptr_ = eol + 1; 811 } else { 812 read_ptr_ = eol = eof_; 813 } 814 auto eq = static_cast<const char*>(memchr(line, '=', eol - line)); 815 if (!eq) { 816 fprintf(stderr, "manifest entry has no '=' separator: %.*s\n", 817 static_cast<int>(eol - line), line); 818 exit(1); 819 } 820 821 line = AllowEntry(line, eq, eol); 822 if (line) { 823 std::string target(line, eq - line); 824 std::string source(eq + 1, eol - (eq + 1)); 825 struct stat st; 826 auto fd = opener->Open(source, &st); 827 RequireRegularFile(st, source.c_str()); 828 auto file = FileContents::Map(fd, st, source.c_str()); 829 *value = value_type{prefix + target, std::move(file)}; 830 return true; 831 } 832 } 833 return false; 834 } 835 836private: 837 FileContents file_; 838 const std::string prefix_; 839 const GroupFilter* filter_ = nullptr; 840 const char* read_ptr_ = nullptr; 841 const char* eof_ = nullptr; 842 843 // Returns the beginning of the `target=source` portion of the entry 844 // if the entry is allowed by the filter, otherwise nullptr. 845 const char* AllowEntry(const char* start, const char* eq, const char* eol) { 846 if (*start != '{') { 847 // This entry doesn't specify a group. 848 return filter_->AllowsUnspecified() ? start : nullptr; 849 } 850 auto end_group = static_cast<const char*>( 851 memchr(start + 1, '}', eq - start)); 852 if (!end_group) { 853 fprintf(stderr, 854 "manifest entry has '{' but no '}': %.*s\n", 855 static_cast<int>(eol - start), start); 856 exit(1); 857 } 858 std::string group(start + 1, end_group - 1 - start); 859 return filter_->Allows(group) ? end_group + 1 : nullptr; 860 } 861}; 862 863class DirectoryInputFileGenerator : public InputFileGenerator { 864public: 865 DirectoryInputFileGenerator(fbl::unique_fd fd, std::string prefix) : 866 source_prefix_(std::move(prefix)) { 867 walk_pos_.emplace_front(MakeUniqueDir(std::move(fd)), 0); 868 } 869 870 ~DirectoryInputFileGenerator() override = default; 871 872 bool Next(FileOpener* opener, const std::string& prefix, 873 value_type* value) override { 874 do { 875 const dirent* d = readdir(walk_pos_.front().dir.get()); 876 if (!d) { 877 Ascend(); 878 continue; 879 } 880 if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) { 881 continue; 882 } 883 std::string target = prefix + walk_prefix_ + d->d_name; 884 std::string source = source_prefix_ + walk_prefix_ + d->d_name; 885 struct stat st; 886 auto fd = opener->Open(source, &st); 887 if (S_ISDIR(st.st_mode)) { 888 Descend(std::move(fd), d->d_name); 889 } else { 890 RequireRegularFile(st, source.c_str()); 891 auto file = FileContents::Map(std::move(fd), st, 892 source.c_str()); 893 *value = value_type{std::move(target), std::move(file)}; 894 return true; 895 } 896 } while (!walk_pos_.empty()); 897 return false; 898 } 899 900private: 901 // std::unique_ptr for fdopendir/closedir. 902 static void DeleteUniqueDir(DIR* dir) { 903 closedir(dir); 904 } 905 using UniqueDir = std::unique_ptr<DIR, decltype(&DeleteUniqueDir)>; 906 UniqueDir MakeUniqueDir(fbl::unique_fd fd) { 907 DIR* dir = fdopendir(fd.release()); 908 if (!dir) { 909 perror("fdopendir"); 910 exit(1); 911 } 912 return UniqueDir(dir, &DeleteUniqueDir); 913 } 914 915 // State of our depth-first directory tree walk. 916 struct WalkState { 917 WalkState(UniqueDir d, size_t len) : 918 dir(std::move(d)), parent_prefix_len(len) { 919 } 920 UniqueDir dir; 921 size_t parent_prefix_len; 922 }; 923 924 const std::string source_prefix_; 925 std::forward_list<WalkState> walk_pos_; 926 std::string walk_prefix_; 927 928 void Descend(fbl::unique_fd fd, const char* name) { 929 size_t parent = walk_prefix_.size(); 930 walk_prefix_ += name; 931 walk_prefix_ += "/"; 932 walk_pos_.emplace_front(MakeUniqueDir(std::move(fd)), parent); 933 } 934 935 void Ascend() { 936 walk_prefix_.resize(walk_pos_.front().parent_prefix_len); 937 walk_pos_.pop_front(); 938 } 939}; 940 941class Item { 942public: 943 // Only the static methods below can create an Item. 944 Item() = delete; 945 946 DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(Item); 947 948 static const char* TypeName(uint32_t zbi_type) { 949 return ItemTypeInfo(zbi_type).name; 950 } 951 952 static bool ParseTypeName(const char* name, uint32_t* abi_type) { 953 for (const auto& t : kItemTypes_) { 954 if (!strcasecmp(t.name, name)) { 955 *abi_type = t.type; 956 return true; 957 } 958 } 959 int i = 0; 960 return sscanf(name, "%x%n", abi_type, &i) == 1 && name[i] == '\0'; 961 } 962 963 static std::string ExtractedFileName(unsigned int n, uint32_t zbi_type, 964 bool raw) { 965 std::string name; 966 char buf[32]; 967 const auto info = ItemTypeInfo(zbi_type); 968 if (info.name) { 969 snprintf(buf, sizeof(buf), "%03u.", n); 970 name = buf; 971 name += info.name; 972 for (auto& c : name) { 973 c = std::tolower(c); 974 } 975 } else { 976 snprintf(buf, sizeof(buf), "%03u.%08x", n, zbi_type); 977 name = buf; 978 } 979 name += (raw && info.extension) ? info.extension : ".zbi"; 980 return name; 981 } 982 983 static void PrintTypeUsage(FILE* out) { 984 fprintf(out, "\ 985TYPE can be hexadecimal or a name string (case-insensitive).\n\ 986Extracted items use the file names shown below:\n\ 987 --type --extract-item --extract-raw\n\ 988"); 989 for (const auto& t : kItemTypes_) { 990 const auto zbi_name = ExtractedFileName(1, t.type, false); 991 const auto raw_name = ExtractedFileName(1, t.type, true); 992 fprintf(out, " %-20s %-26s %s\n", 993 t.name, zbi_name.c_str(), raw_name.c_str()); 994 } 995 } 996 997 static bool TypeIsStorage(uint32_t zbi_type) { 998 return (zbi_type == ZBI_TYPE_STORAGE_BOOTFS || 999 zbi_type == ZBI_TYPE_STORAGE_RAMDISK); 1000 } 1001 1002 uint32_t type() const { 1003 return header_.type; 1004 } 1005 1006 uint32_t PayloadSize() const { 1007 return header_.length; 1008 } 1009 1010 uint32_t TotalSize() const { 1011 return sizeof(header_) + ZBI_ALIGN(PayloadSize()); 1012 } 1013 1014 void Describe(uint32_t pos) const { 1015 const char* type_name = TypeName(type()); 1016 if (!type_name) { 1017 printf("%08x: %08x UNKNOWN (type=%08x)\n", 1018 pos, header_.length, header_.type); 1019 } else if (TypeIsStorage(type())) { 1020 printf("%08x: %08x %s (size=%08x)\n", 1021 pos, header_.length, type_name, header_.extra); 1022 } else { 1023 printf("%08x: %08x %s\n", 1024 pos, header_.length, type_name); 1025 } 1026 if (header_.flags & ZBI_FLAG_CRC32) { 1027 auto print_crc = [](const zbi_header_t& header) { 1028 printf(" : MAGIC=%08x CRC=%08x\n", 1029 header.magic, header.crc32); 1030 }; 1031 1032 Checksummer crc; 1033 crc.Write(payload_); 1034 zbi_header_t check_header = header_; 1035 crc.FinalizeHeader(&check_header); 1036 1037 if (compress_) { 1038 // We won't compute it until StreamCompressed, so 1039 // write out the computation we just did to check. 1040 print_crc(check_header); 1041 } else { 1042 print_crc(header_); 1043 if (check_header.crc32 != header_.crc32) { 1044 fprintf(stderr, "error: CRC %08x does not match header\n", 1045 check_header.crc32); 1046 } 1047 } 1048 } else { 1049 printf(" : MAGIC=%08x NO CRC\n", header_.magic); 1050 } 1051 } 1052 1053 bool AlreadyCompressed() const { 1054 return (header_.flags & ZBI_FLAG_STORAGE_COMPRESSED) && !compress_; 1055 } 1056 1057 int Show() { 1058 if (header_.length > 0) { 1059 if (AlreadyCompressed()) { 1060 return CreateFromCompressed(*this)->Show(); 1061 } 1062 switch (header_.type) { 1063 case ZBI_TYPE_STORAGE_BOOTFS: 1064 return ShowBootFS(); 1065 case ZBI_TYPE_CMDLINE: 1066 return ShowCmdline(); 1067 } 1068 } 1069 return 0; 1070 } 1071 1072 // Streaming exhausts the item's payload. The OutputStream will now 1073 // have pointers into buffers owned by this Item, so this Item must be 1074 // kept alive until out->Flush() runs (while *this is alive, to be safe). 1075 void Stream(OutputStream* out) { 1076 assert(Aligned(out->WritePosition())); 1077 uint32_t wrote = compress_ ? StreamCompressed(out) : StreamRaw(out); 1078 assert(out->WritePosition() % ZBI_ALIGNMENT == wrote % ZBI_ALIGNMENT); 1079 uint32_t aligned = ZBI_ALIGN(wrote); 1080 if (aligned > wrote) { 1081 static const std::byte padding[ZBI_ALIGNMENT]{}; 1082 out->Write(Iovec(padding, aligned - wrote)); 1083 } 1084 assert(Aligned(out->WritePosition())); 1085 } 1086 1087 // The buffer will be released when this Item is destroyed. This item 1088 // and items earlier on the list can hold pointers into the buffer. 1089 void OwnBuffer(std::unique_ptr<std::byte[]> buffer) { 1090 buffers_.push_front(std::move(buffer)); 1091 } 1092 void OwnFile(FileContents file) { 1093 files_.push_front(std::move(file)); 1094 } 1095 1096 // Consume another Item while keeping its owned buffers and files alive. 1097 void TakeOwned(ItemPtr other) { 1098 if (other) { 1099 buffers_.splice_after(buffers_.before_begin(), other->buffers_); 1100 files_.splice_after(files_.before_begin(), other->files_); 1101 } 1102 } 1103 1104 // Create from in-core data. 1105 static ItemPtr CreateFromBuffer( 1106 uint32_t type, std::unique_ptr<std::byte[]> payload, size_t size) { 1107 auto item = MakeItem(NewHeader(type, size)); 1108 item->payload_.emplace_front(Iovec(payload.get(), size)); 1109 item->OwnBuffer(std::move(payload)); 1110 Checksummer crc; 1111 crc.Write(item->payload_); 1112 crc.FinalizeHeader(&item->header_); 1113 return item; 1114 } 1115 1116 // Create from local scratch data. 1117 template<typename T> 1118 static ItemPtr Create(uint32_t type, const T& payload) { 1119 auto buffer = std::make_unique<std::byte[]>(sizeof(payload)); 1120 memcpy(buffer.get(), &payload, sizeof(payload)); 1121 return CreateFromBuffer(type, std::move(buffer), sizeof(payload)); 1122 } 1123 1124 // Create from raw file contents. 1125 static ItemPtr CreateFromFile( 1126 FileContents file, uint32_t type, bool compress) { 1127 bool null_terminate = type == ZBI_TYPE_CMDLINE; 1128 compress = compress && TypeIsStorage(type); 1129 1130 size_t size = file.exact_size() + (null_terminate ? 1 : 0); 1131 auto item = MakeItem(NewHeader(type, size), compress); 1132 1133 // If we need some zeros, see if they're already right there 1134 // in the last mapped page past the exact end of the file. 1135 if (size <= file.mapped_size()) { 1136 // Use the padding that's already there. 1137 item->payload_.emplace_front(file.PageRoundedView(0, size)); 1138 } else { 1139 // No space, so we need a separate padding buffer. 1140 if (null_terminate) { 1141 item->payload_.emplace_front(Iovec("", 1)); 1142 } 1143 item->payload_.emplace_front(file.View(0, file.exact_size())); 1144 } 1145 1146 if (!compress) { 1147 // Compute the checksum now so the item is ready to write out. 1148 Checksummer crc; 1149 crc.Write(file.View(0, file.exact_size())); 1150 if (null_terminate) { 1151 crc.Write(Iovec("", 1)); 1152 } 1153 crc.FinalizeHeader(&item->header_); 1154 } 1155 1156 // The item now owns the file mapping that its payload points into. 1157 item->OwnFile(std::move(file)); 1158 1159 return item; 1160 } 1161 1162 // Create from an existing fully-baked item in an input file. 1163 static ItemPtr CreateFromItem(const FileContents& file, 1164 uint32_t offset) { 1165 if (offset > file.exact_size() || 1166 file.exact_size() - offset < sizeof(zbi_header_t)) { 1167 fprintf(stderr, "input file too short for next header\n"); 1168 exit(1); 1169 } 1170 const zbi_header_t* header = static_cast<const zbi_header_t*>( 1171 file.View(offset, sizeof(zbi_header_t)).iov_base); 1172 offset += sizeof(zbi_header_t); 1173 if (file.exact_size() - offset < header->length) { 1174 fprintf(stderr, "input file too short for payload of %u bytes\n", 1175 header->length); 1176 exit(1); 1177 } 1178 auto item = MakeItem(*header); 1179 item->payload_.emplace_front(file.View(offset, header->length)); 1180 return item; 1181 } 1182 1183 // Create by decompressing a fully-baked item that is compressed. 1184 static ItemPtr CreateFromCompressed(const Item& compressed) { 1185 assert(compressed.AlreadyCompressed()); 1186 auto item = MakeItem(compressed.header_); 1187 item->header_.flags &= ~ZBI_FLAG_STORAGE_COMPRESSED; 1188 item->header_.length = item->header_.extra; 1189 auto buffer = Decompress(compressed.payload_, item->header_.length); 1190 item->payload_.emplace_front( 1191 Iovec(buffer.get(), item->header_.length)); 1192 item->OwnBuffer(std::move(buffer)); 1193 return item; 1194 } 1195 1196 // Same, but consumes the compressed item while keeping its 1197 // owned buffers alive in the new uncompressed item. 1198 static ItemPtr CreateFromCompressed(ItemPtr compressed) { 1199 auto uncompressed = CreateFromCompressed(*compressed); 1200 uncompressed->TakeOwned(std::move(compressed)); 1201 return uncompressed; 1202 } 1203 1204 // Create a BOOTFS item. 1205 template<typename Filter> 1206 static ItemPtr CreateBootFS(FileOpener* opener, 1207 const InputFileGeneratorList& input, 1208 const Filter& include_file, 1209 bool sort, 1210 const std::string& prefix, 1211 bool compress) { 1212 auto item = MakeItem(NewHeader(ZBI_TYPE_STORAGE_BOOTFS, 0), compress); 1213 1214 // Collect the names and exact sizes here and the contents in payload_. 1215 struct Entry { 1216 std::string name; 1217 uint32_t data_len = 0; 1218 }; 1219 std::deque<Entry> entries; 1220 size_t dirsize = 0, bodysize = 0; 1221 for (const auto& generator : input) { 1222 InputFileGenerator::value_type next; 1223 while (generator->Next(opener, prefix, &next)) { 1224 if (!include_file(next.target.c_str())) { 1225 continue; 1226 } 1227 // Accumulate the space needed for each zbi_bootfs_dirent_t. 1228 dirsize += ZBI_BOOTFS_DIRENT_SIZE(next.target.size() + 1); 1229 Entry entry; 1230 entry.name.swap(next.target); 1231 entry.data_len = static_cast<uint32_t>(next.file.exact_size()); 1232 if (entry.data_len != next.file.exact_size()) { 1233 fprintf(stderr, 1234 "input file size exceeds format maximum\n"); 1235 exit(1); 1236 } 1237 uint32_t size = ZBI_BOOTFS_PAGE_ALIGN(entry.data_len); 1238 bodysize += size; 1239 item->payload_.emplace_back( 1240 next.file.PageRoundedView(0, size)); 1241 entries.push_back(std::move(entry)); 1242 item->OwnFile(std::move(next.file)); 1243 } 1244 } 1245 1246 if (sort) { 1247 std::sort(entries.begin(), entries.end(), 1248 [](const Entry& a, const Entry& b) { 1249 return a.name < b.name; 1250 }); 1251 } 1252 1253 // Now we can calculate the final sizes. 1254 const zbi_bootfs_header_t header = { 1255 ZBI_BOOTFS_MAGIC, // magic 1256 static_cast<uint32_t>(dirsize), // dirsize 1257 0, // reserved0 1258 0, // reserved1 1259 }; 1260 size_t header_size = ZBI_BOOTFS_PAGE_ALIGN(sizeof(header) + dirsize); 1261 item->header_.length = static_cast<uint32_t>(header_size + bodysize); 1262 if (item->header_.length != header_size + bodysize) { 1263 fprintf(stderr, "BOOTFS image size exceeds format maximum\n"); 1264 exit(1); 1265 } 1266 1267 // Now fill a buffer with the BOOTFS header and directory entries. 1268 AppendBuffer buffer(header_size); 1269 buffer.Append(&header); 1270 uint32_t data_off = static_cast<uint32_t>(header_size); 1271 for (const auto& file : item->payload_) { 1272 const auto& entry = entries.front(); 1273 const zbi_bootfs_dirent_t entry_hdr = { 1274 static_cast<uint32_t>(entry.name.size() + 1), // name_len 1275 entry.data_len, // data_len 1276 data_off, // data_off 1277 }; 1278 data_off += static_cast<uint32_t>(file.iov_len); 1279 buffer.Append(&entry_hdr); 1280 buffer.Append(entry.name.c_str(), entry_hdr.name_len); 1281 buffer.Pad( 1282 ZBI_BOOTFS_DIRENT_SIZE(entry_hdr.name_len) - 1283 offsetof(zbi_bootfs_dirent_t, name[entry_hdr.name_len])); 1284 entries.pop_front(); 1285 } 1286 assert(data_off == item->header_.length); 1287 // Zero fill to the end of the page. 1288 buffer.Pad(header_size - buffer.size()); 1289 1290 if (!compress) { 1291 // Checksum the BOOTFS image right now: header and then payload. 1292 Checksummer crc; 1293 crc.Write(buffer.get()); 1294 crc.Write(item->payload_); 1295 crc.FinalizeHeader(&item->header_); 1296 } 1297 1298 // Put the header at the front of the payload. 1299 item->payload_.emplace_front(buffer.get()); 1300 item->OwnBuffer(buffer.release()); 1301 1302 return item; 1303 } 1304 1305 // The generator consumes the Item. The FileContents it generates 1306 // point into the Item's storage, so the generator must be kept 1307 // alive as long as any of those FileContents is alive. 1308 static auto ReadBootFS(ItemPtr item) { 1309 return std::unique_ptr<InputFileGenerator>( 1310 new BootFSInputFileGenerator(std::move(item))); 1311 } 1312 1313 void ExtractItem(FileWriter* writer, NameMatcher* matcher) { 1314 std::string namestr = ExtractedFileName(writer->NextFileNumber(), 1315 type(), false); 1316 auto name = namestr.c_str(); 1317 if (matcher->Matches(name, true)) { 1318 WriteZBI(writer, name, (Item*const[]){this}); 1319 } 1320 } 1321 1322 void ExtractRaw(FileWriter* writer, NameMatcher* matcher) { 1323 std::string namestr = ExtractedFileName(writer->NextFileNumber(), 1324 type(), true); 1325 auto name = namestr.c_str(); 1326 if (matcher->Matches(name, true)) { 1327 if (type() == ZBI_TYPE_CMDLINE) { 1328 // Drop a trailing NUL. 1329 iovec iov = payload_.back(); 1330 auto str = static_cast<const char*>(iov.iov_base); 1331 if (str[iov.iov_len - 1] == '\0') { 1332 payload_.pop_back(); 1333 --iov.iov_len; 1334 payload_.push_back(iov); 1335 } 1336 } 1337 if (AlreadyCompressed()) { 1338 auto uncompressed = CreateFromCompressed(*this); 1339 // The uncompressed item must outlive the OutputStream. 1340 auto out = writer->RawFile(name); 1341 uncompressed->StreamRawPayload(&out); 1342 } else { 1343 auto out = writer->RawFile(name); 1344 StreamRawPayload(&out); 1345 } 1346 } 1347 } 1348 1349 template<typename ItemList> 1350 static void WriteZBI(FileWriter* writer, const char* name, 1351 const ItemList& items) { 1352 auto out = writer->RawFile(name); 1353 1354 uint32_t header_start = out.PlaceHeader(); 1355 uint32_t payload_start = out.WritePosition(); 1356 assert(Aligned(payload_start)); 1357 1358 for (const auto& item : items) { 1359 // The OutputStream stores pointers into Item buffers in its write 1360 // queue until it goes out of scope below. The ItemList keeps all 1361 // the items alive past then. 1362 item->Stream(&out); 1363 } 1364 1365 const zbi_header_t header = 1366 ZBI_CONTAINER_HEADER(out.WritePosition() - payload_start); 1367 assert(Aligned(header.length)); 1368 out.PatchHeader(header, header_start); 1369 } 1370 1371 void AppendPayload(std::string* buffer) const { 1372 if (AlreadyCompressed()) { 1373 CreateFromCompressed(*this)->AppendPayload(buffer); 1374 } else { 1375 for (const auto& iov : payload_) { 1376 buffer->append(static_cast<const char*>(iov.iov_base), 1377 iov.iov_len); 1378 } 1379 } 1380 } 1381 1382private: 1383 zbi_header_t header_; 1384 std::list<const iovec> payload_; 1385 // The payload_ items might point into these buffers. They're just 1386 // stored here to own the buffers until the payload is exhausted. 1387 std::forward_list<FileContents> files_; 1388 std::forward_list<std::unique_ptr<std::byte[]>> buffers_; 1389 const bool compress_; 1390 1391 struct ItemTypeInfo { 1392 uint32_t type; 1393 const char* name; 1394 const char* extension; 1395 }; 1396 static constexpr const ItemTypeInfo kItemTypes_[] = { 1397#define kITemTypes_Element(type, name, extension) {type, name, extension}, 1398 ZBI_ALL_TYPES(kITemTypes_Element) 1399#undef kitemtypes_element 1400};; 1401 1402 static constexpr ItemTypeInfo ItemTypeInfo(uint32_t zbi_type) { 1403 for (const auto& t : kItemTypes_) { 1404 if (t.type == zbi_type) { 1405 return t; 1406 } 1407 } 1408 return {}; 1409 } 1410 1411 static constexpr zbi_header_t NewHeader(uint32_t type, uint32_t size) { 1412 return { 1413 type, // type 1414 size, // length 1415 0, // extra 1416 ZBI_FLAG_VERSION | ZBI_FLAG_CRC32, // flags 1417 0, // reserved0 1418 0, // reserved1 1419 ZBI_ITEM_MAGIC, // magic 1420 0, // crc32 1421 }; 1422 } 1423 1424 Item(const zbi_header_t& header, bool compress) : 1425 header_(header), compress_(compress) { 1426 if (compress_) { 1427 // We'll compress and checksum on the way out. 1428 header_.flags |= ZBI_FLAG_STORAGE_COMPRESSED; 1429 } 1430 } 1431 1432 static ItemPtr MakeItem(const zbi_header_t& header, 1433 bool compress = false) { 1434 return ItemPtr(new Item(header, compress)); 1435 } 1436 1437 void StreamRawPayload(OutputStream* out) { 1438 do { 1439 out->Write(payload_.front()); 1440 payload_.pop_front(); 1441 } while (!payload_.empty()); 1442 } 1443 1444 uint32_t StreamRaw(OutputStream* out) { 1445 // The header is already fully baked. 1446 out->Write(Iovec(&header_, sizeof(header_))); 1447 // The payload goes out as is. 1448 StreamRawPayload(out); 1449 return sizeof(header_) + header_.length; 1450 } 1451 1452 uint32_t StreamCompressed(OutputStream* out) { 1453 // Compress and checksum the payload. 1454 Compressor compressor; 1455 compressor.Init(out, header_); 1456 do { 1457 // The compressor streams the header and compressed payload out. 1458 compressor.Write(out, payload_.front()); 1459 payload_.pop_front(); 1460 } while (!payload_.empty()); 1461 // This writes the final header as well as the last of the payload. 1462 return compressor.Finish(out); 1463 } 1464 1465 int ShowCmdline() const { 1466 std::string cmdline = std::accumulate( 1467 payload_.begin(), payload_.end(), std::string(), 1468 [](std::string cmdline, const iovec& iov) { 1469 return cmdline.append( 1470 static_cast<const char*>(iov.iov_base), 1471 iov.iov_len); 1472 }); 1473 size_t start = 0; 1474 while (start < cmdline.size()) { 1475 size_t word_end = cmdline.find_first_of(kCmdlineWS, start); 1476 if (word_end == std::string::npos) { 1477 if (cmdline[start] != '\0') { 1478 printf(" : %s\n", cmdline.c_str() + start); 1479 } 1480 break; 1481 } 1482 if (word_end > start) { 1483 printf(" : %.*s\n", 1484 static_cast<int>(word_end - start), 1485 cmdline.c_str() + start); 1486 } 1487 start = word_end + 1; 1488 } 1489 return 0; 1490 } 1491 1492 const std::byte* payload_data() { 1493 if (payload_.size() > 1) { 1494 AppendBuffer buffer(PayloadSize()); 1495 for (const auto& iov : payload_) { 1496 buffer.Append(iov.iov_base, iov.iov_len); 1497 } 1498 payload_.clear(); 1499 payload_.push_front(buffer.get()); 1500 OwnBuffer(buffer.release()); 1501 } 1502 assert(payload_.size() == 1); 1503 return static_cast<const std::byte*>(payload_.front().iov_base); 1504 } 1505 1506 class BootFSDirectoryIterator { 1507 public: 1508 operator bool() const { 1509 return left_ > 0; 1510 } 1511 1512 const zbi_bootfs_dirent_t& operator*() const { 1513 auto entry = reinterpret_cast<const zbi_bootfs_dirent_t*>(next_); 1514 assert(left_ >= sizeof(*entry)); 1515 return *entry; 1516 } 1517 1518 const zbi_bootfs_dirent_t* operator->() const { 1519 return &**this; 1520 } 1521 1522 BootFSDirectoryIterator& operator++() { 1523 assert(left_ > 0); 1524 if (left_ < sizeof(zbi_bootfs_dirent_t)) { 1525 fprintf(stderr, "BOOTFS directory truncated\n"); 1526 left_ = 0; 1527 } else { 1528 size_t size = ZBI_BOOTFS_DIRENT_SIZE((*this)->name_len); 1529 if (size > left_) { 1530 fprintf(stderr, 1531 "BOOTFS directory truncated or bad name_len\n"); 1532 left_ = 0; 1533 } else { 1534 next_ += size; 1535 left_ -= size; 1536 } 1537 } 1538 return *this; 1539 } 1540 1541 // The iterator itself is a container enough to use range-based for. 1542 const BootFSDirectoryIterator& begin() { 1543 return *this; 1544 } 1545 1546 BootFSDirectoryIterator end() { 1547 return BootFSDirectoryIterator(); 1548 } 1549 1550 static int Create(Item* item, BootFSDirectoryIterator* it) { 1551 zbi_bootfs_header_t superblock; 1552 const uint32_t length = item->header_.length; 1553 if (length < sizeof(superblock)) { 1554 fprintf(stderr, "payload too short for BOOTFS header\n"); 1555 return 1; 1556 } 1557 memcpy(&superblock, item->payload_data(), sizeof(superblock)); 1558 if (superblock.magic != ZBI_BOOTFS_MAGIC) { 1559 fprintf(stderr, "BOOTFS header magic %#x should be %#x\n", 1560 superblock.magic, ZBI_BOOTFS_MAGIC); 1561 return 1; 1562 } 1563 if (superblock.dirsize > length - sizeof(superblock)) { 1564 fprintf(stderr, 1565 "BOOTFS header dirsize %u > payload size %zu\n", 1566 superblock.dirsize, length - sizeof(superblock)); 1567 return 1; 1568 } 1569 it->next_ = item->payload_data() + sizeof(superblock); 1570 it->left_ = superblock.dirsize; 1571 return 0; 1572 } 1573 1574 private: 1575 const std::byte* next_ = nullptr; 1576 uint32_t left_ = 0; 1577 }; 1578 1579 bool CheckBootFSDirent(const zbi_bootfs_dirent_t& entry, 1580 bool always_print) const { 1581 const char* align_check = 1582 entry.data_off % ZBI_BOOTFS_PAGE_SIZE == 0 ? "" : 1583 "[ERROR: misaligned offset] "; 1584 const char* size_check = 1585 (entry.data_off < header_.length && 1586 header_.length - entry.data_off >= entry.data_len) ? "" : 1587 "[ERROR: offset+size too large] "; 1588 bool ok = align_check[0] == '\0' && size_check[0] == '\0'; 1589 if (always_print || !ok) { 1590 fprintf(always_print ? stdout : stderr, 1591 " : %08x %08x %s%s%.*s\n", 1592 entry.data_off, entry.data_len, 1593 align_check, size_check, 1594 static_cast<int>(entry.name_len), entry.name); 1595 } 1596 return ok; 1597 } 1598 1599 int ShowBootFS() { 1600 assert(!AlreadyCompressed()); 1601 BootFSDirectoryIterator dir; 1602 int status = BootFSDirectoryIterator::Create(this, &dir); 1603 for (const auto& entry : dir) { 1604 if (!CheckBootFSDirent(entry, true)) { 1605 status = 1; 1606 } 1607 } 1608 return status; 1609 } 1610 1611 class BootFSInputFileGenerator : public InputFileGenerator { 1612 public: 1613 explicit BootFSInputFileGenerator(ItemPtr item) : 1614 item_(std::move(item)) { 1615 if (item_->AlreadyCompressed()) { 1616 item_ = CreateFromCompressed(std::move(item_)); 1617 } 1618 int status = BootFSDirectoryIterator::Create(item_.get(), &dir_); 1619 if (status != 0) { 1620 exit(status); 1621 } 1622 } 1623 1624 ~BootFSInputFileGenerator() override = default; 1625 1626 // Copying from an existing BOOTFS ignores the --prefix setting. 1627 bool Next(FileOpener*, const std::string&, 1628 value_type* value) override { 1629 if (!dir_) { 1630 return false; 1631 } 1632 if (!item_->CheckBootFSDirent(*dir_, false)) { 1633 exit(1); 1634 } 1635 value->target = dir_->name; 1636 value->file = FileContents(*dir_, item_->payload_data()); 1637 ++dir_; 1638 return true; 1639 } 1640 1641 private: 1642 ItemPtr item_; 1643 BootFSDirectoryIterator dir_; 1644 }; 1645}; 1646 1647constexpr decltype(Item::kItemTypes_) Item::kItemTypes_; 1648 1649using ItemList = std::vector<ItemPtr>; 1650 1651bool ImportFile(const FileContents& file, const char* filename, 1652 ItemList* items) { 1653 if (file.exact_size() <= (sizeof(zbi_header_t) * 2)) { 1654 return false; 1655 } 1656 const zbi_header_t* header = static_cast<const zbi_header_t*>( 1657 file.View(0, sizeof(zbi_header_t)).iov_base); 1658 if (!(header->type == ZBI_TYPE_CONTAINER && 1659 header->extra == ZBI_CONTAINER_MAGIC && 1660 header->magic == ZBI_ITEM_MAGIC)) { 1661 return false; 1662 } 1663 size_t file_size = file.exact_size() - sizeof(zbi_header_t); 1664 if (file_size != header->length) { 1665 fprintf(stderr, "%s: header size doesn't match file size\n", filename); 1666 exit(1); 1667 } 1668 if (!Aligned(header->length)) { 1669 fprintf(stderr, "ZBI item misaligned\n"); 1670 exit(1); 1671 } 1672 uint32_t pos = sizeof(zbi_header_t); 1673 do { 1674 auto item = Item::CreateFromItem(file, pos); 1675 pos += item->TotalSize(); 1676 items->push_back(std::move(item)); 1677 } while (pos < file.exact_size()); 1678 return true; 1679} 1680 1681const uint32_t kImageArchUndefined = ZBI_TYPE_DISCARD; 1682 1683// Returns nullptr if complete, else an explanatory string. 1684const char* IncompleteImage(const ItemList& items, const uint32_t image_arch) { 1685 if (!ZBI_IS_KERNEL_BOOTITEM(items.front()->type())) { 1686 return "first item not KERNEL"; 1687 } 1688 1689 if (items.front()->type() != image_arch && 1690 image_arch != kImageArchUndefined) { 1691 return "kernel arch mismatch"; 1692 } 1693 1694 auto count = 1695 std::count_if(items.begin(), items.end(), 1696 [](const ItemPtr& item) { 1697 return item->type() == ZBI_TYPE_STORAGE_BOOTFS; 1698 }); 1699 if (count == 0) { 1700 return "no /boot BOOTFS item"; 1701 } 1702 if (count > 1) { 1703 return "multiple BOOTFS items"; 1704 } 1705 return nullptr; 1706} 1707 1708constexpr const char kOptString[] = "-B:cd:e:FxXRg:hto:p:sT:uv"; 1709constexpr const option kLongOpts[] = { 1710 {"complete", required_argument, nullptr, 'B'}, 1711 {"compressed", no_argument, nullptr, 'c'}, 1712 {"depfile", required_argument, nullptr, 'd'}, 1713 {"entry", required_argument, nullptr, 'e'}, 1714 {"files", no_argument, nullptr, 'F'}, 1715 {"extract", no_argument, nullptr, 'x'}, 1716 {"extract-items", no_argument, nullptr, 'X'}, 1717 {"extract-raw", no_argument, nullptr, 'R'}, 1718 {"groups", required_argument, nullptr, 'g'}, 1719 {"help", no_argument, nullptr, 'h'}, 1720 {"list", no_argument, nullptr, 't'}, 1721 {"output", required_argument, nullptr, 'o'}, 1722 {"prefix", required_argument, nullptr, 'p'}, 1723 {"sort", no_argument, nullptr, 's'}, 1724 {"type", required_argument, nullptr, 'T'}, 1725 {"uncompressed", no_argument, nullptr, 'u'}, 1726 {"verbose", no_argument, nullptr, 'v'}, 1727 {nullptr, no_argument, nullptr, 0}, 1728}; 1729 1730constexpr const char kUsageFormatString[] = "\ 1731Usage: %s [OUTPUT...] INPUT... [-- PATTERN...]\n\ 1732\n\ 1733Diagnostic switches:\n\ 1734 --help, -h print this message\n\ 1735 --list, -t list input ZBI item headers; no --output\n\ 1736 --verbose, -v show contents (e.g. BOOTFS file names)\n\ 1737 --extract, -x extract BOOTFS files\n\ 1738 --extract-items, -X extract items as pseudo-files (see below)\n\ 1739 --extract-raw, -R extract original payloads, not ZBI format\n\ 1740\n\ 1741Output file switches must come before input arguments:\n\ 1742 --output=FILE, -o FILE output file name\n\ 1743 --depfile=FILE, -d FILE makefile dependency output file name\n\ 1744\n\ 1745The `--output` FILE is always removed and created fresh after all input\n\ 1746files have been opened. So it is safe to use the same file name as an input\n\ 1747file and the `--output` FILE, to append more items.\n\ 1748\n\ 1749Input control switches apply to subsequent input arguments:\n\ 1750 --files, -F read BOOTFS manifest files (default)\n\ 1751 --groups=GROUPS, -g GROUPS comma-separated list of manifest groups\n\ 1752 --prefix=PREFIX, -p PREFIX prepend PREFIX/ to target file names\n\ 1753 --type=TYPE, -T TYPE input files are TYPE items (see below)\n\ 1754 --compressed, -c compress RAMDISK images (default)\n\ 1755 --uncompressed, -u do not compress RAMDISK images\n\ 1756\n\ 1757Input arguments:\n\ 1758 --entry=TEXT, -e TEXT like an input file containing only TEXT\n\ 1759 FILE input or manifest file\n\ 1760 DIRECTORY directory tree copied to BOOTFS PREFIX/\n\ 1761\n\ 1762With `--files` or `-F` (the default state), files with ZBI_TYPE_CONTAINER\n\ 1763headers are incomplete boot files and other files are BOOTFS manifest files.\n\ 1764Each DIRECTORY is listed recursively and handled just like a manifest file\n\ 1765using the path relative to DIRECTORY as the target name (before any PREFIX).\n\ 1766Each `--group`, `--prefix`, `-g`, or `-p` switch affects each file from a\n\ 1767manifest or directory in subsequent FILE or DIRECTORY arguments.\n\ 1768If GROUPS starts with `!` then only manifest entries that match none of\n\ 1769the listed groups are used.\n\ 1770\n\ 1771With `--type` or `-T`, input files are treated as TYPE instead of manifest\n\ 1772files, and directories are not permitted. See below for the TYPE strings.\n\ 1773\n\ 1774Format control switches (last switch affects all output):\n\ 1775 --complete=ARCH, -B ARCH verify result is a complete boot image\n\ 1776 --compressed, -c compress BOOTFS images (default)\n\ 1777 --uncompressed, -u do not compress BOOTFS images\n\ 1778 --sort, -s sort BOOTFS entries by name\n\ 1779\n\ 1780In all cases there is only a single BOOTFS item (if any) written out.\n\ 1781The BOOTFS image contains all files from BOOTFS items in ZBI input files,\n\ 1782manifest files, directories, and `--entry` switches (in input order unless\n\ 1783`--sort` was specified).\n\ 1784\n\ 1785Each argument after -- is shell filename PATTERN (* matches even /)\n\ 1786to filter the files that will be packed into BOOTFS, extracted, or listed.\n\ 1787For a PATTERN that starts with ! or ^ matching names are excluded after\n\ 1788including matches for all positive PATTERN arguments.\n\ 1789\n\ 1790When extracting a single file, `--output` or `-o` can be used.\n\ 1791Otherwise multiple files are created with their BOOTFS file names\n\ 1792relative to PREFIX (default empty, so in the current directory).\n\ 1793\n\ 1794With `--extract-items` or `-X`, instead of BOOTFS files the names are\n\ 1795synthesized as shown below, numbered in the order items appear in the input\n\ 1796starting with 001. Output files are ZBI files that can be input later.\n\ 1797\n\ 1798With `--extract-raw` or `-R`, each file is written with just the\n\ 1799uncompressed payload of the item and no ZBI headers.\n\ 1800\n\ 1801"; 1802 1803void usage(const char* progname) { 1804 fprintf(stderr, kUsageFormatString, progname); 1805 Item::PrintTypeUsage(stderr); 1806} 1807 1808} // anonymous namespace 1809 1810int main(int argc, char** argv) { 1811 FileOpener opener; 1812 GroupFilter filter; 1813 const char* outfile = nullptr; 1814 const char* depfile = nullptr; 1815 uint32_t complete_arch = kImageArchUndefined; 1816 bool input_manifest = true; 1817 uint32_t input_type = ZBI_TYPE_DISCARD; 1818 bool compressed = true; 1819 bool extract = false; 1820 bool extract_items = false; 1821 bool extract_raw = false; 1822 bool list_contents = false; 1823 bool sort = false; 1824 bool verbose = false; 1825 ItemList items; 1826 InputFileGeneratorList bootfs_input; 1827 std::string prefix; 1828 int opt; 1829 while ((opt = getopt_long(argc, argv, 1830 kOptString, kLongOpts, nullptr)) != -1) { 1831 // A non-option argument (1) is an input, handled below. 1832 // All other cases continue the loop and don't break the switch. 1833 switch (opt) { 1834 case 1: 1835 break; 1836 1837 case 'o': 1838 if (outfile) { 1839 fprintf(stderr, "only one output file\n"); 1840 exit(1); 1841 } 1842 if (!items.empty()) { 1843 fprintf(stderr, "--output or -o must precede inputs\n"); 1844 exit(1); 1845 } 1846 outfile = optarg; 1847 continue; 1848 1849 case 'd': 1850 if (depfile) { 1851 fprintf(stderr, "only one depfile\n"); 1852 exit(1); 1853 } 1854 if (!outfile) { 1855 fprintf(stderr, 1856 "--output -or -o must precede --depfile or -d\n"); 1857 exit(1); 1858 } 1859 if (!items.empty()) { 1860 fprintf(stderr, "--depfile or -d must precede inputs\n"); 1861 exit(1); 1862 } 1863 depfile = optarg; 1864 opener.Init(outfile, depfile); 1865 continue; 1866 1867 case 'F': 1868 input_manifest = true; 1869 continue; 1870 1871 case 'T': 1872 if (Item::ParseTypeName(optarg, &input_type)) { 1873 input_manifest = false; 1874 } else { 1875 fprintf(stderr, "unrecognized type: %s\n", optarg); 1876 exit(1); 1877 } 1878 continue; 1879 1880 case 'p': 1881 // A nonempty prefix should have no leading slashes and 1882 // exactly one trailing slash. 1883 prefix = optarg; 1884 while (!prefix.empty() && prefix.front() == '/') { 1885 prefix.erase(0, 1); 1886 } 1887 if (!prefix.empty() && prefix.back() == '/') { 1888 prefix.pop_back(); 1889 } 1890 if (prefix.empty() && optarg[0] != '\0') { 1891 fprintf(stderr, "\ 1892--prefix cannot be /; use --prefix= (empty) instead\n"); 1893 exit(1); 1894 } 1895 if (!prefix.empty()) { 1896 prefix.push_back('/'); 1897 } 1898 continue; 1899 1900 case 'g': 1901 filter.SetFilter(optarg); 1902 continue; 1903 1904 case 't': 1905 list_contents = true; 1906 continue; 1907 1908 case 'v': 1909 verbose = true; 1910 continue; 1911 1912 case 'B': 1913 if (!strcmp(optarg, "x64")) { 1914 complete_arch = ZBI_TYPE_KERNEL_X64; 1915 } else if (!strcmp(optarg, "arm64")) { 1916 complete_arch = ZBI_TYPE_KERNEL_ARM64; 1917 } else { 1918 fprintf(stderr, "--complete architecture argument must be one" 1919 " of: x64, arm64\n"); 1920 exit(1); 1921 } 1922 continue; 1923 case 'c': 1924 compressed = true; 1925 continue; 1926 1927 case 'u': 1928 compressed = false; 1929 continue; 1930 1931 case 's': 1932 sort = true; 1933 continue; 1934 1935 case 'x': 1936 extract = true; 1937 continue; 1938 1939 case 'X': 1940 extract = true; 1941 extract_items = true; 1942 continue; 1943 1944 case 'R': 1945 extract = true; 1946 extract_items = true; 1947 extract_raw = true; 1948 continue; 1949 1950 case 'e': 1951 if (input_manifest) { 1952 bootfs_input.emplace_back( 1953 new ManifestInputFileGenerator(FileContents(optarg, false), 1954 prefix, &filter)); 1955 } else if (input_type == ZBI_TYPE_CONTAINER) { 1956 fprintf(stderr, 1957 "cannot use --entry (-e) with --target=CONTAINER\n"); 1958 exit(1); 1959 } else { 1960 items.push_back( 1961 Item::CreateFromFile( 1962 FileContents(optarg, input_type == ZBI_TYPE_CMDLINE), 1963 input_type, compressed)); 1964 } 1965 continue; 1966 1967 case 'h': 1968 default: 1969 usage(argv[0]); 1970 exit(opt == 'h' ? 0 : 1); 1971 } 1972 assert(opt == 1); 1973 1974 struct stat st; 1975 auto fd = opener.Open(optarg, &st); 1976 1977 // A directory populates the BOOTFS. 1978 if (input_manifest && S_ISDIR(st.st_mode)) { 1979 // Calculate the prefix for opening files within the directory. 1980 // This won't be part of the BOOTFS file name. 1981 std::string dir_prefix(optarg); 1982 if (dir_prefix.back() != '/') { 1983 dir_prefix.push_back('/'); 1984 } 1985 bootfs_input.emplace_back( 1986 new DirectoryInputFileGenerator(std::move(fd), 1987 std::move(dir_prefix))); 1988 continue; 1989 } 1990 1991 // Anything else must be a regular file. 1992 RequireRegularFile(st, optarg); 1993 auto file = FileContents::Map(std::move(fd), st, optarg); 1994 1995 if (input_manifest || input_type == ZBI_TYPE_CONTAINER) { 1996 if (ImportFile(file, optarg, &items)) { 1997 // It's another file in ZBI format. The last item will own 1998 // the file buffer, so it lives until all earlier items are 1999 // exhausted. 2000 items.back()->OwnFile(std::move(file)); 2001 } else if (input_manifest) { 2002 // It must be a manifest file. 2003 bootfs_input.emplace_back( 2004 new ManifestInputFileGenerator(std::move(file), 2005 prefix, &filter)); 2006 } else { 2007 fprintf(stderr, "%s: not a Zircon Boot container\n", optarg); 2008 exit(1); 2009 } 2010 } else { 2011 items.push_back(Item::CreateFromFile(std::move(file), 2012 input_type, compressed)); 2013 } 2014 } 2015 2016 // Remaining arguments (after --) are patterns for matching file names. 2017 NameMatcher name_matcher(argv, optind, argc); 2018 2019 if (list_contents) { 2020 if (outfile || depfile) { 2021 fprintf(stderr, "\ 2022--output (-o) and --depfile (-d) are incompatible with --list (-t)\n"); 2023 exit(1); 2024 } 2025 } else { 2026 if (!outfile && !extract) { 2027 fprintf(stderr, "no output file\n"); 2028 exit(1); 2029 } 2030 } 2031 2032 // Don't merge incoming items when only listing or extracting. 2033 const bool merge = !list_contents && !extract; 2034 2035 auto is_bootfs = [](const ItemPtr& item) { 2036 return item->type() == ZBI_TYPE_STORAGE_BOOTFS; 2037 }; 2038 2039 // If there are multiple BOOTFS input items, or any BOOTFS items when 2040 // we're also creating a fresh BOOTFS, merge them all into the new one. 2041 const bool merge_bootfs = 2042 ((!extract_items && !name_matcher.MatchesAll()) || 2043 ((merge || !bootfs_input.empty()) && 2044 ((bootfs_input.empty() ? 0 : 1) + 2045 std::count_if(items.begin(), items.end(), is_bootfs)) > 1)); 2046 2047 if (merge_bootfs) { 2048 for (auto& item : items) { 2049 if (is_bootfs(item)) { 2050 // Null out the list entry. 2051 ItemPtr old; 2052 item.swap(old); 2053 // The generator consumes the old item. 2054 bootfs_input.push_back(Item::ReadBootFS(std::move(old))); 2055 } 2056 } 2057 } 2058 2059 ItemPtr keepalive; 2060 if (merge) { 2061 // Merge multiple CMDLINE input items with spaces in between. 2062 std::string cmdline; 2063 for (auto& item : items) { 2064 if (item && item->type() == ZBI_TYPE_CMDLINE) { 2065 // Null out the list entry. 2066 ItemPtr old; 2067 item.swap(old); 2068 cmdline.append({' '}); 2069 old->AppendPayload(&cmdline); 2070 // Trim leading whitespace. 2071 cmdline.erase(0, cmdline.find_first_not_of(kCmdlineWS)); 2072 // Trim trailing NULs and whitespace. 2073 while (!cmdline.empty() && cmdline.back() == '\0') { 2074 cmdline.pop_back(); 2075 } 2076 cmdline.erase(cmdline.find_last_not_of(kCmdlineWS) + 1); 2077 // Keep alive all the owned files from the old item, 2078 // since it might have owned files used by other items. 2079 old->TakeOwned(std::move(keepalive)); 2080 keepalive.swap(old); 2081 } 2082 } 2083 if (!cmdline.empty()) { 2084 size_t size = cmdline.size() + 1; 2085 auto buffer = std::make_unique<std::byte[]>(size); 2086 memcpy(buffer.get(), cmdline.c_str(), size); 2087 items.push_back(Item::CreateFromBuffer(ZBI_TYPE_CMDLINE, 2088 std::move(buffer), size)); 2089 } 2090 } 2091 2092 // Compact out the null entries. 2093 items.erase(std::remove(items.begin(), items.end(), nullptr), items.end()); 2094 2095 if (!bootfs_input.empty()) { 2096 // Pack up the BOOTFS. 2097 items.push_back( 2098 Item::CreateBootFS(&opener, bootfs_input, [&](const char* name) { 2099 return extract_items || name_matcher.Matches(name); 2100 }, sort, prefix, compressed)); 2101 } 2102 2103 if (items.empty()) { 2104 fprintf(stderr, "no inputs\n"); 2105 exit(1); 2106 } 2107 2108 items.back()->TakeOwned(std::move(keepalive)); 2109 2110 if (!list_contents && complete_arch != kImageArchUndefined) { 2111 // The only hard requirement is that the kernel be first. 2112 // But it seems most orderly to put the BOOTFS second, 2113 // other storage in the middle, and CMDLINE last. 2114 std::stable_sort( 2115 items.begin(), items.end(), 2116 [](const ItemPtr& a, const ItemPtr& b) { 2117 auto item_rank = [](uint32_t type) { 2118 return (ZBI_IS_KERNEL_BOOTITEM(type) ? 0 : 2119 type == ZBI_TYPE_STORAGE_BOOTFS ? 1 : 2120 type == ZBI_TYPE_CMDLINE ? 9 : 2121 5); 2122 }; 2123 return item_rank(a->type()) < item_rank(b->type()); 2124 }); 2125 } 2126 2127 if (complete_arch != kImageArchUndefined) { 2128 const char* incomplete = IncompleteImage(items, complete_arch); 2129 if (incomplete) { 2130 fprintf(stderr, "incomplete image: %s\n", incomplete); 2131 exit(1); 2132 } 2133 } 2134 2135 // Now we're ready to start writing output! 2136 FileWriter writer(outfile, std::move(prefix)); 2137 2138 if (list_contents || verbose || extract) { 2139 if (list_contents || verbose) { 2140 const char* incomplete = IncompleteImage(items, complete_arch); 2141 if (incomplete) { 2142 printf("INCOMPLETE: %s\n", incomplete); 2143 } else { 2144 puts("COMPLETE: bootable image"); 2145 } 2146 } 2147 2148 // Contents start after the ZBI_TYPE_CONTAINER header. 2149 uint32_t pos = sizeof(zbi_header_t); 2150 int status = 0; 2151 for (auto& item : items) { 2152 if (list_contents || verbose) { 2153 item->Describe(pos); 2154 } 2155 if (verbose) { 2156 status |= item->Show(); 2157 } 2158 pos += item->TotalSize(); 2159 if (extract_items) { 2160 if (extract_raw) { 2161 item->ExtractRaw(&writer, &name_matcher); 2162 } else { 2163 item->ExtractItem(&writer, &name_matcher); 2164 } 2165 } else if (extract && is_bootfs(item)) { 2166 auto generator = Item::ReadBootFS(std::move(item)); 2167 InputFileGenerator::value_type next; 2168 while (generator->Next(&opener, prefix, &next)) { 2169 if (name_matcher.Matches(next.target.c_str())) { 2170 writer.RawFile(next.target.c_str()) 2171 .Write(next.file.View(0, next.file.exact_size())); 2172 } 2173 } 2174 } 2175 } 2176 if (status) { 2177 exit(status); 2178 } 2179 } else { 2180 Item::WriteZBI(&writer, "boot.zbi", items); 2181 } 2182 2183 name_matcher.Summary(extract ? "extracted" : "matched", 2184 extract_items ? "boot items" : "BOOTFS files", 2185 verbose); 2186 2187 return 0; 2188} 2189