blob: 4d8e8804d61a2db0531306c572c5256f177081e3 [file]
// 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