blob: d9ab416a59c817c5d9941f1b2650f2852d24ac55 [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 <fbl/algorithm.h>
#include <fs/connection.h>
#include <safemath/checked_math.h>
#include <zircon/assert.h>
#include <zircon/boot/image.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/status.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:
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};
}
} // namespace
namespace bootsvc {
const char* const kLastPanicFilePath = "log/last-panic.txt";
zx_status_t RetrieveBootImage(zx::vmo* out_vmo, ItemMap* out_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;
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)) {
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();
}
DiscardItem(&vmo, discard_begin, discard_end);
*out_vmo = std::move(vmo);
*out_map = std::move(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(); it++) {
// Skip any leading whitespace.
if (isspace(*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.
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, zx::channel* out) {
zx::channel local, remote;
zx_status_t status = zx::channel::create(0, &local, &remote);
if (status != ZX_OK) {
return status;
}
auto conn = std::make_unique<fs::Connection>(vfs, vnode, std::move(local),
ZX_FS_FLAG_DIRECTORY |
ZX_FS_RIGHT_READABLE |
ZX_FS_RIGHT_WRITABLE);
status = vfs->ServeConnection(std::move(conn));
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