blob: dccec5b45b68d4debf0cb3a567f039c9ff901663 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "util.h"
#include <ctype.h>
#include <string.h>
#include <zircon/assert.h>
#include <zircon/boot/image.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <fbl/algorithm.h>
#include <fs/vfs_types.h>
#include <safemath/checked_math.h>
#include "zircon/device/vfs.h"
namespace {
// Returns true for boot item types that should be stored.
bool StoreItem(uint32_t type) {
switch (type) {
case ZBI_TYPE_CRASHLOG:
case ZBI_TYPE_KERNEL_DRIVER:
case ZBI_TYPE_PLATFORM_ID:
case ZBI_TYPE_STORAGE_BOOTFS_FACTORY:
case ZBI_TYPE_STORAGE_RAMDISK:
case ZBI_TYPE_IMAGE_ARGS:
case ZBI_TYPE_SERIAL_NUMBER:
case ZBI_TYPE_BOOTLOADER_FILE:
case ZBI_TYPE_DEVICETREE:
return true;
default:
return ZBI_TYPE_DRV_METADATA(type);
}
}
// Discards the boot item from the boot image VMO.
void DiscardItem(zx::vmo* vmo, uint32_t begin_in, uint32_t end_in) {
uint64_t begin = fbl::round_up(begin_in, static_cast<uint32_t>(PAGE_SIZE));
uint64_t end = fbl::round_down(end_in, static_cast<uint32_t>(PAGE_SIZE));
if (begin < end) {
zx_status_t status = vmo->op_range(ZX_VMO_OP_DECOMMIT, begin, end - begin, nullptr, 0);
ZX_ASSERT_MSG(status == ZX_OK, "Discarding boot item failed: %s\n",
zx_status_get_string(status));
printf("bootsvc: Decommitted BOOTDATA VMO from %#lx to %#lx\n", begin, end);
}
}
bootsvc::ItemKey CreateItemKey(uint32_t type, uint32_t extra) {
switch (type) {
case ZBI_TYPE_STORAGE_RAMDISK:
// If this is for a ramdisk, set the extra value to zero.
return bootsvc::ItemKey{.type = type, .extra = 0};
default:
// Otherwise, store the extra value.
return bootsvc::ItemKey{.type = type, .extra = extra};
}
}
bootsvc::ItemValue CreateItemValue(uint32_t type, uint32_t off, uint32_t len) {
switch (type) {
case ZBI_TYPE_STORAGE_RAMDISK:
// If this is for a ramdisk, capture the ZBI header.
len = safemath::CheckAdd(len, sizeof(zbi_header_t)).ValueOrDie<uint32_t>();
break;
default:
// Otherwise, adjust the offset to skip the ZBI header.
off = safemath::CheckAdd(off, sizeof(zbi_header_t)).ValueOrDie<uint32_t>();
}
return bootsvc::ItemValue{.offset = off, .length = len};
}
zx_status_t ProcessFactoryItem(const zx::vmo& vmo, uint32_t offset, uint32_t length,
bootsvc::FactoryItemValue* out_factory_item) {
offset = safemath::CheckAdd(offset, sizeof(zbi_header_t)).ValueOrDie<uint32_t>();
zx::vmo payload;
zx_status_t status = zx::vmo::create(length, 0, &payload);
if (status != ZX_OK) {
printf("bootsvc: Failed to create payload vmo: %s\n", zx_status_get_string(status));
return status;
}
auto buffer = std::make_unique<uint8_t[]>(length);
status = vmo.read(buffer.get(), offset, length);
if (status != ZX_OK) {
printf("bootsvc: Failed to read input vmo: %s\n", zx_status_get_string(status));
return status;
}
status = payload.write(buffer.get(), 0, length);
if (status != ZX_OK) {
printf("bootsvc: Failed to write payload vmo: %s\n", zx_status_get_string(status));
return status;
}
// Wipe the factory item from the original VMO.
memset(buffer.get(), 0, length);
status = vmo.write(buffer.get(), offset, length);
if (status != ZX_OK) {
printf("bootsvc: Failed to wipe input vmo: %s\n", zx_status_get_string(status));
return status;
}
*out_factory_item = bootsvc::FactoryItemValue{.vmo = std::move(payload), .length = length};
return ZX_OK;
}
zx_status_t ProcessBootloaderFile(const zx::vmo& vmo, uint32_t offset, uint32_t length,
std::string* out_filename,
bootsvc::ItemValue* out_bootloader_file) {
offset = safemath::CheckAdd(offset, sizeof(zbi_header_t)).ValueOrDie<uint32_t>();
if (length < sizeof(uint8_t)) {
printf("bootsvc: Bootloader File ZBI item too small\n");
return ZX_ERR_OUT_OF_RANGE;
}
uint8_t name_len;
zx_status_t status = vmo.read(&name_len, offset, sizeof(uint8_t));
if (status != ZX_OK) {
printf("bootsvc: Failed to read input VMO: %s\n", zx_status_get_string(status));
return status;
}
if (length <= sizeof(uint8_t) + name_len) {
printf("bootsvc: Bootloader File ZBI item too small.\n");
return ZX_ERR_OUT_OF_RANGE;
}
std::string name(name_len, '\0');
offset = safemath::CheckAdd(offset, sizeof(uint8_t)).ValueOrDie<uint32_t>();
status = vmo.read(name.data(), offset, name_len);
if (status != ZX_OK) {
printf("bootsvc: Failed to read input VMO: %s\n", zx_status_get_string(status));
return status;
}
offset = safemath::CheckAdd(offset, name_len).ValueOrDie<uint32_t>();
uint32_t payload_length = length - name_len - sizeof(uint8_t);
*out_bootloader_file = bootsvc::ItemValue{.offset = offset, .length = payload_length};
*out_filename = std::move(name);
return ZX_OK;
}
} // namespace
namespace bootsvc {
const char* const kLastPanicFilePath = "log/last-panic.txt";
zx_status_t RetrieveBootImage(zx::vmo* out_vmo, ItemMap* out_map, FactoryItemMap* out_factory_map,
BootloaderFileMap* out_bootloader_file_map) {
// Validate boot image VMO provided by startup handle.
zx::vmo vmo(zx_take_startup_handle(PA_HND(PA_VMO_BOOTDATA, 0)));
zbi_header_t header;
zx_status_t status = vmo.read(&header, 0, sizeof(header));
if (status != ZX_OK) {
printf("bootsvc: Failed to read ZBI image header: %s\n", zx_status_get_string(status));
return status;
} else if (header.type != ZBI_TYPE_CONTAINER || header.extra != ZBI_CONTAINER_MAGIC ||
header.magic != ZBI_ITEM_MAGIC || !(header.flags & ZBI_FLAG_VERSION)) {
printf("bootsvc: Invalid ZBI image header\n");
return ZX_ERR_IO_DATA_INTEGRITY;
}
// Used to discard pages from the boot image VMO.
uint32_t discard_begin = 0;
uint32_t discard_end = 0;
// Read boot items from the boot image VMO.
ItemMap map;
FactoryItemMap factory_map;
BootloaderFileMap bootloader_file_map;
uint32_t off = sizeof(header);
uint32_t len = header.length;
while (len > sizeof(header)) {
status = vmo.read(&header, off, sizeof(header));
if (status != ZX_OK) {
printf("bootsvc: Failed to read ZBI item header: %s\n", zx_status_get_string(status));
return status;
} else if (header.type == ZBI_CONTAINER_MAGIC || header.magic != ZBI_ITEM_MAGIC) {
printf("bootsvc: Invalid ZBI item header\n");
return ZX_ERR_IO_DATA_INTEGRITY;
}
uint32_t item_len = ZBI_ALIGN(header.length + static_cast<uint32_t>(sizeof(zbi_header_t)));
uint32_t next_off = safemath::CheckAdd(off, item_len).ValueOrDie();
if (item_len > len) {
printf("bootsvc: ZBI item too large (%u > %u)\n", item_len, len);
return ZX_ERR_IO_DATA_INTEGRITY;
} else if (StoreItem(header.type)) {
if (header.type == ZBI_TYPE_STORAGE_BOOTFS_FACTORY) {
FactoryItemValue factory_item;
status = ProcessFactoryItem(vmo, off, header.length, &factory_item);
if (status != ZX_OK) {
printf("bootsvc: Failed to process factory item: %s\n", zx_status_get_string(status));
return status;
}
factory_map.emplace(header.extra, std::move(factory_item));
} else if (header.type == ZBI_TYPE_BOOTLOADER_FILE) {
std::string filename;
ItemValue bootloader_file;
status = ProcessBootloaderFile(vmo, off, header.length, &filename, &bootloader_file);
if (status == ZX_OK) {
bootloader_file_map.emplace(std::move(filename), std::move(bootloader_file));
} else {
printf("bootsvc: Failed to process bootloader file: %s\n", zx_status_get_string(status));
}
} else {
map.emplace(CreateItemKey(header.type, header.extra),
CreateItemValue(header.type, off, header.length));
}
DiscardItem(&vmo, discard_begin, discard_end);
discard_begin = next_off;
} else {
discard_end = next_off;
}
off = next_off;
len = safemath::CheckSub(len, item_len).ValueOrDie();
// Kernel cmdline cannot be present in the zbi vmo.
ZX_DEBUG_ASSERT(header.type != ZBI_TYPE_CMDLINE);
}
if (discard_end > discard_begin) {
// We are at the end of the last element and it should be discarded.
// We should discard until the end of the page.
discard_end = fbl::round_up(discard_end, static_cast<uint32_t>(PAGE_SIZE));
}
DiscardItem(&vmo, discard_begin, discard_end);
*out_vmo = std::move(vmo);
*out_map = std::move(map);
*out_factory_map = std::move(factory_map);
*out_bootloader_file_map = std::move(bootloader_file_map);
return ZX_OK;
}
zx_status_t ParseBootArgs(std::string_view str, fbl::Vector<char>* buf) {
buf->reserve(buf->size() + str.size());
for (auto it = str.begin(); it != str.end();) {
// Skip any leading whitespace.
if (isspace(*it)) {
it++;
continue;
}
// Is the line a comment or a zero-length name?
bool is_comment = *it == '#' || *it == '=';
// Append line, if it is not a comment.
for (; it != str.end(); it++) {
if (*it == '\n') {
// We've reached the end of the line.
it++;
break;
} else if (is_comment) {
// Skip this character, as it is part of a comment.
continue;
} else if (isspace(*it)) {
// It is invalid to have a space within an argument.
return ZX_ERR_INVALID_ARGS;
} else {
buf->push_back(*it);
}
}
if (!is_comment) {
buf->push_back(0);
}
}
return ZX_OK;
}
zx_status_t CreateVnodeConnection(fs::Vfs* vfs, fbl::RefPtr<fs::Vnode> vnode, fs::Rights rights,
zx::channel* out) {
zx::channel local, remote;
zx_status_t status = zx::channel::create(0, &local, &remote);
if (status != ZX_OK) {
return status;
}
status = vfs->ServeDirectory(std::move(vnode), std::move(local), rights);
if (status != ZX_OK) {
return status;
}
*out = std::move(remote);
return ZX_OK;
}
fbl::Vector<fbl::String> SplitString(fbl::String input, char delimiter) {
fbl::Vector<fbl::String> result;
// No fbl::String::find, do it ourselves.
const char* start = input.begin();
for (auto end = start; end != input.end(); start = end + 1) {
end = start;
while (end != input.end() && *end != delimiter) {
++end;
}
result.push_back(fbl::String(start, end - start));
}
return result;
}
} // namespace bootsvc