| // Copyright 2026 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include "lib/linux-initramfs/initramfs.h" |
| |
| #include <algorithm> |
| #include <cassert> |
| #include <cctype> |
| |
| namespace linux_initramfs { |
| namespace { |
| |
| using namespace std::literals; |
| |
| // cf https://docs.kernel.org/driver-api/early-userspace/buffer-format.html |
| // |
| // There is a 6-byte magic string. Then there are 8-byte fields of ASCII hex. |
| |
| constexpr std::string_view kMagic = "070701"sv; |
| constexpr std::string_view kMagicChksum = "070702"sv; |
| static_assert(kMagic.size() == kMagicChksum.size()); |
| |
| constexpr std::string_view kTrailer = "TRAILER!!!"sv; |
| |
| enum FieldIndex : size_t { |
| kIno, |
| kMode, |
| kUid, |
| kGid, |
| kNlink, |
| kMtime, |
| kFilesize, |
| kMaj, |
| kMin, |
| kRmaj, |
| kRmin, |
| kNamesize, |
| kChksum, |
| kFieldCount, |
| }; |
| |
| constexpr size_t kHeaderSize = kMagic.size() + (kFieldCount * 8); |
| static_assert(kHeaderSize == Member::kHeaderSize); |
| |
| bool ValidHeader(std::string_view contents) { |
| return contents.size() >= kHeaderSize && |
| (contents.starts_with(kMagic) || contents.starts_with(kMagicChksum)) && |
| std::ranges::all_of(contents |
| .substr(0, kHeaderSize) // |
| .substr(kMagic.size()), |
| isxdigit); |
| } |
| |
| // This goes at the end of archives, but is ignored so they can be concatenated. |
| constexpr bool IsTrailer(const Member& member) { |
| return member.contents.empty() && member.name == kTrailer; |
| } |
| |
| // Extracts the n-th 8-byte hex field from the header. |
| // The input data must be valid ASCII hex digits (e.g. ValidHeader() returns |
| // true on this data). |
| constexpr uint32_t GetField(std::string_view contents, FieldIndex n) { |
| std::string_view field = contents.substr(kMagic.size() + (n * 8), 8); |
| assert(field.size() == 8); |
| // Avoid std::from_chars(..., 16) here because libc++'s non-base-10 |
| // implementation uses float arithmetic, which emits soft-float runtime library |
| // calls in kernel.phys (-mgeneral-regs-only) builds when not constant-folded. |
| uint32_t value = 0; |
| for (char c : field) { |
| value <<= 4; |
| if (c >= '0' && c <= '9') { |
| value |= static_cast<uint32_t>(c - '0'); |
| } else if (c >= 'a' && c <= 'f') { |
| value |= static_cast<uint32_t>(c - 'a' + 10); |
| } else if (c >= 'A' && c <= 'F') { |
| value |= static_cast<uint32_t>(c - 'A' + 10); |
| } else { |
| assert(false); |
| } |
| } |
| return value; |
| } |
| |
| constexpr Member GetMember(std::string_view contents) { |
| if (!ValidHeader(contents)) { |
| return {}; |
| } |
| |
| const uint32_t filesize = GetField(contents, kFilesize); |
| const uint32_t namesize = GetField(contents, kNamesize); |
| contents.remove_prefix(kHeaderSize); |
| |
| if (contents.size() < namesize || namesize == 0) [[unlikely]] { |
| return {}; |
| } |
| // The namesize includes the NUL terminator. |
| std::string_view name = contents.substr(0, namesize - 1); |
| contents.remove_prefix(namesize); |
| |
| // Skip alignment padding after the name. |
| const size_t consumed = kHeaderSize + namesize; |
| const size_t padding = ((consumed + 3) & -size_t{4}) - consumed; |
| if (contents.size() < padding) [[unlikely]] { |
| return {}; |
| } |
| contents.remove_prefix(padding); |
| |
| if (contents.size() < filesize) [[unlikely]] { |
| return {}; |
| } |
| std::string_view file = contents.substr(0, filesize); |
| |
| return {.name = name, .contents = std::as_bytes(std::span{file})}; |
| } |
| |
| } // namespace |
| |
| Initramfs::iterator Initramfs::begin() const { |
| iterator it; |
| it.value_ = GetMember(contents_); |
| |
| if (it.value_) { |
| it.contents_ = contents_; |
| assert(it != end()); |
| if (IsTrailer(*it)) { |
| ++it; |
| } |
| } else { |
| assert(it == end()); |
| } |
| return it; |
| } |
| |
| Initramfs::iterator& Initramfs::iterator::operator++() { // prefix |
| do { |
| assert(!contents_.empty()); |
| contents_.remove_prefix(value_.size_bytes()); |
| // Padding bytes of zero are allowed between members in multiples of 4. |
| if (size_t pos = contents_.find_first_not_of('\0'); |
| pos != std::string_view::npos && pos % 4 == 0) { |
| contents_.remove_prefix(pos); |
| } else { |
| value_ = {}; |
| break; |
| } |
| value_ = GetMember(contents_); |
| // A special member optionally terminates a single cpio archive. But |
| // archives can be concatenated, so just skip it and keep looking. It's |
| // only relevant to the hard-link tracking, which is not supported here. |
| } while (IsTrailer(value_)); |
| if (!value_) { // end() state. |
| contents_ = {}; |
| } |
| return *this; |
| } |
| |
| } // namespace linux_initramfs |