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