blob: caf86459221016b5c1c4d67a7361f7d55773b9b0 [file] [log] [blame]
// Copyright 2020 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 "src/storage/factory/factoryfs/factoryfs.h"
#include <getopt.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/status.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <iostream>
#include <block-client/cpp/remote-block-device.h>
#include <fs/managed_vfs.h>
#include <fs/pseudo_dir.h>
#include <fs/trace.h>
#include <fs/vnode.h>
#include <storage/buffer/owned_vmoid.h>
#include "src/storage/factory/factoryfs/directory.h"
#include "src/storage/factory/factoryfs/file.h"
#include "src/storage/factory/factoryfs/format.h"
#include "src/storage/factory/factoryfs/superblock.h"
namespace factoryfs {
static zx_status_t IsValidDirectoryEntry(const DirectoryEntry& entry, const Superblock& info) {
if (entry.name_len == 0 || entry.name_len > kFactoryfsMaxNameSize) {
return ZX_ERR_IO_DATA_INTEGRITY;
}
uint32_t max_data_off = info.data_blocks + info.directory_ent_blocks + kFactoryfsSuperblockBlocks;
if (entry.data_off >= max_data_off) {
return ZX_ERR_IO_DATA_INTEGRITY;
}
return ZX_OK;
}
static void DumpDirectoryEntry(const DirectoryEntry* entry) {
if (fs::trace_debug_enabled()) {
std::string_view entry_name(entry->name, entry->name_len);
std::cerr << "Directory entry: data_len=" << entry->data_len << ", data_off=" << entry->data_off
<< ", name=" << entry_name << std::endl;
}
}
uint32_t FsToDeviceBlocks(uint32_t fs_block, uint32_t disk_block_size) {
return fs_block * (kFactoryfsBlockSize / disk_block_size);
};
zx_status_t Factoryfs::OpenRootNode(fbl::RefPtr<fs::Vnode>* out) {
auto root = fbl::MakeRefCounted<Directory>(*this, std::string_view());
auto validated_options = root->ValidateOptions(fs::VnodeConnectionOptions());
if (validated_options.is_error()) {
return validated_options.error();
}
zx_status_t status = root->Open(validated_options.value(), nullptr);
if (status != ZX_OK) {
return status;
}
*out = std::move(root);
return ZX_OK;
}
Factoryfs::Factoryfs(std::unique_ptr<BlockDevice> device, const Superblock* superblock)
: block_device_(std::move(device)), superblock_(*superblock) {}
std::unique_ptr<BlockDevice> Factoryfs::Reset() {
if (!block_device_) {
return nullptr;
}
// TODO(manalib) Shutdown all internal connections to factoryfs,
// by iterating over open_vnodes
return std::move(block_device_);
}
zx_status_t Factoryfs::Create(async_dispatcher_t* dispatcher, std::unique_ptr<BlockDevice> device,
MountOptions* options, std::unique_ptr<Factoryfs>* out) {
TRACE_DURATION("factoryfs", "Factoryfs::Create");
Superblock superblock;
zx_status_t status = device->ReadBlock(0, kFactoryfsBlockSize, &superblock);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "could not read info block: " << zx_status_get_string(status);
return status;
}
fuchsia_hardware_block_BlockInfo block_info;
status = device->BlockGetInfo(&block_info);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "cannot acquire block info: " << zx_status_get_string(status);
return status;
}
// TODO(manalib).
// Both generic fsck as well as generic mount open the device in read-write mode.
// Hence we cannot return an error here. Simply flagging this inconsistency for now.
if ((block_info.flags & BLOCK_FLAG_READONLY) == 0) {
FX_LOGS(ERROR) << "Factory partition should only be mounting as read-only.";
// return ZX_ERR_IO;
}
if (kFactoryfsBlockSize % block_info.block_size != 0) {
FX_LOGS(ERROR) << "Factoryfs block size (" << kFactoryfsBlockSize
<< ") not divisible by device block size (" << block_info.block_size << ")";
return ZX_ERR_IO;
}
// Perform superblock validations.
status = CheckSuperblock(&superblock);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Check Superblock failure";
return status;
}
auto fs = std::unique_ptr<Factoryfs>(new Factoryfs(std::move(device), &superblock));
fs->block_info_ = std::move(block_info);
*out = std::move(fs);
return ZX_OK;
}
Factoryfs::~Factoryfs() { Reset(); }
zx_status_t Factoryfs::GetFsId(zx::event* out_fs_id) const {
ZX_DEBUG_ASSERT(fs_id_.is_valid());
return fs_id_.duplicate(ZX_RIGHTS_BASIC, out_fs_id);
}
zx_status_t Factoryfs::InitDirectoryVmo() {
if (directory_vmo_.is_valid()) {
return ZX_OK;
}
zx_status_t status;
const size_t vmo_size = fbl::round_up(GetDirectorySize(), kFactoryfsBlockSize);
if ((status = zx::vmo::create(vmo_size, 0, &directory_vmo_)) != ZX_OK) {
FX_LOGS(ERROR) << "Failed to initialize directory vmo; error: " << zx_status_get_string(status);
return status;
}
zx_object_set_property(directory_vmo_.get(), ZX_PROP_NAME, "factoryfs-directory",
strlen("factoryfs-directory"));
storage::OwnedVmoid vmoid;
if ((status = Device().BlockAttachVmo(directory_vmo_,
&vmoid.GetReference(block_device_.get()))) != ZX_OK) {
directory_vmo_.reset();
return status;
}
Superblock info = Info();
uint32_t dev_block_size = GetDeviceBlockInfo().block_size;
uint32_t dev_blocks = info.directory_ent_blocks * (kFactoryfsBlockSize / dev_block_size);
block_fifo_request_t request = {
.opcode = BLOCKIO_READ,
.vmoid = vmoid.get(),
.length = dev_blocks,
.vmo_offset = 0,
.dev_offset = info.directory_ent_start_block * (kFactoryfsBlockSize / dev_block_size),
};
return Device().FifoTransaction(&request, 1);
}
// Parses all entries in the container directory from offset 0.
// |parse_data| is guarenteed to be 4 byte aligned.
zx_status_t Factoryfs::ParseEntries(Callback callback, void* parse_data) {
size_t avail = GetDirectorySize();
zx_status_t status;
// To enforce 4 byte alignment for all the directory entries,
// we need to enforce the starting address of parse_data is
// also 4 byte aligned.
uintptr_t buffer = reinterpret_cast<uintptr_t>(parse_data);
if (buffer & 3) {
return ZX_ERR_IO_DATA_INTEGRITY;
}
// Note about alignment: ptr is enforced to be always 4-byte aligned,
// and DirectoryEntry itself is always 4 byte aligned i.e DirentSize will be a multiple of 4.
// Hence it is safe to use reinterpret cast.
while (avail > sizeof(DirectoryEntry)) {
DirectoryEntry* entry = reinterpret_cast<DirectoryEntry*>(buffer);
if (entry->name_len == 0) {
break;
}
size_t size = DirentSize(entry->name_len);
if (size > avail) {
FX_LOGS(ERROR) << "invalid directory entry: size > avail!";
DumpDirectoryEntry(entry);
return ZX_ERR_IO;
}
if ((status = IsValidDirectoryEntry(*entry, Info())) != ZX_OK) {
FX_LOGS(ERROR) << "invalid directory entry!";
DumpDirectoryEntry(entry);
return status;
}
if ((status = callback(entry)) == ZX_OK) {
return status;
}
buffer += size;
avail -= size;
}
return ZX_ERR_NOT_FOUND;
}
zx::status<std::unique_ptr<DirectoryEntryManager>> Factoryfs::LookupInternal(
const std::string_view path) {
if (path.empty()) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
uint32_t dirent_blocks = Info().directory_ent_blocks;
// We need to make sure "block" is 4 byte aligned to access directory entries.
// and initialized to zero.
std::vector<uint32_t> block(dirent_blocks * kFactoryfsBlockSize / 4, 0);
uint32_t len = dirent_blocks * kFactoryfsBlockSize;
zx_status_t status = ZX_OK;
if ((status = InitDirectoryVmo()) != ZX_OK) {
FX_LOGS(ERROR) << "Failed to initialize VMO error: " << zx_status_get_string(status);
return zx::error(status);
}
if ((status = directory_vmo_.read(block.data(), 0, len)) != ZX_OK) {
FX_LOGS(ERROR) << "Failed to read VMO error: " << zx_status_get_string(status);
return zx::error(status);
}
std::unique_ptr<DirectoryEntryManager> out_entry;
status = ParseEntries(
[&](const DirectoryEntry* entry) {
std::string_view entry_name(entry->name, entry->name_len);
// Perform a partial match.
if (entry_name.compare(0, path.size(), path) == 0 &&
(entry_name.size() == path.size() || entry_name[path.size()] == '/')) {
return DirectoryEntryManager::Create(entry, &out_entry);
}
return ZX_ERR_NOT_FOUND;
},
block.data());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Directory::LookupInternal failed with error: "
<< zx_status_get_string(status);
return zx::error(status);
}
ZX_ASSERT(out_entry);
return zx::ok(std::move(out_entry));
}
zx::status<fbl::RefPtr<fs::Vnode>> Factoryfs::Lookup(const std::string_view path) {
auto iter = open_vnodes_cache_.find(path);
if (iter != open_vnodes_cache_.end()) {
return zx::ok(fbl::RefPtr(iter->second));
}
std::unique_ptr<DirectoryEntryManager> dir_entry;
if (auto dir_entry_or = LookupInternal(path); dir_entry_or.is_error()) {
return dir_entry_or.take_error();
} else {
dir_entry = std::move(dir_entry_or).value();
}
// If we got a partial match, then we need to create a directory node rather than
// a file node.
if (path.size() < dir_entry->GetName().size()) {
return zx::ok(fbl::MakeRefCounted<Directory>(*this, path));
} else {
return zx::ok(fbl::MakeRefCounted<File>(*this, std::move(dir_entry)));
}
}
void Factoryfs::DidOpen(std::string_view path, fs::Vnode& vnode) {
ZX_ASSERT(open_vnodes_cache_.emplace(path, &vnode).second);
}
void Factoryfs::DidClose(const std::string_view path) {
auto iter = open_vnodes_cache_.find(path);
ZX_ASSERT(iter != open_vnodes_cache_.end());
open_vnodes_cache_.erase(iter);
}
} // namespace factoryfs