blob: 90c68f40b5e75a9a4f3c1c0818058a174664f44a [file] [log] [blame] [edit]
// Copyright 2025 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "init/libpreservation/filesystem_manager.h"
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <base/files/file_path.h>
#include <base/logging.h>
#include <base/strings/string_util.h>
#include <init/libpreservation/ext2fs.h>
#include "base/functional/callback_helpers.h"
namespace libpreservation {
namespace {
bool ValidatePath(const base::FilePath& path) {
if (!base::IsStringUTF8(path.value())) {
return false;
}
auto path_components = path.GetComponents();
if (path_components.empty()) {
LOG(ERROR) << "Invalid path";
return false;
}
// Validate that the path components don't contain special paths.
for (auto& component : path_components) {
if (component == base::FilePath::kCurrentDirectory ||
component == base::FilePath::kParentDirectory) {
LOG(ERROR) << "Invalid path component " << component;
return false;
}
}
return true;
}
} // namespace
FilesystemManager::FilesystemManager(std::unique_ptr<Ext2fs> fs)
: fs_(std::move(fs)) {}
bool FilesystemManager::CreateDirectory(const base::FilePath& path) {
if (!ValidatePath(path)) {
return false;
}
ext2_ino_t parent_inode = EXT2_ROOT_INO;
base::FilePath parent_dir = path.DirName();
// Check if parent directory exists.
if (!parent_dir.value().empty()) {
if (!fs_->LookupInode(parent_dir, &parent_inode)) {
LOG(ERROR) << "Failed to look up parent directory: " << parent_dir;
return false;
}
}
if (!fs_->Mkdir(parent_inode, path.BaseName().value())) {
LOG(ERROR) << "Failed to create directory: " << path;
return false;
}
return true;
}
bool FilesystemManager::CreateFileAndFixedGoalFallocate(
const base::FilePath& path, uint64_t size, const ExtentArray& extents) {
if (!ValidatePath(path)) {
return false;
}
ext2_ino_t parent_inode = EXT2_ROOT_INO;
base::FilePath parent_dir = path.DirName();
if (!fs_->LookupInode(parent_dir, &parent_inode)) {
LOG(ERROR) << "Failed to look up parent directory: " << parent_dir;
return false;
}
// Create new inode and link to the last component.
ext2_ino_t new_inode;
if (!fs_->NewInode(parent_inode, &new_inode)) {
LOG(ERROR) << "Failed to create new inode for " << path;
return false;
}
if (!fs_->LinkFile(parent_inode, path.BaseName().value(), new_inode)) {
LOG(ERROR) << "Failed to link file: " << path;
return false;
}
// Cleanup the file if we fail any operations from here on.
base::ScopedClosureRunner cleanup(base::BindOnce(
base::IgnoreResult(&Ext2fs::Unlink), base::Unretained(fs_.get()),
parent_inode, path.BaseName().value()));
// Set up inode attributes.
struct ext2_inode inode;
memset(&inode, 0, sizeof(struct ext2_inode));
inode.i_mode = LINUX_S_IFREG | (0600 & ~fs_->GetUmask());
inode.i_size = size & 0xFFFFFFFF;
inode.i_size_high = size >> 32;
inode.i_atime = inode.i_ctime = inode.i_mtime = time(0);
inode.i_links_count = 1;
// Set up extent header for inode.
inode.i_flags &= ~EXT4_EXTENTS_FL;
if (!fs_->InitInodeExtentHeader(new_inode, &inode)) {
LOG(ERROR) << "Failed to setup extent header for " << path;
return false;
}
// Write inode to disk.
if (!fs_->PersistInode(new_inode, inode)) {
LOG(ERROR) << "Failed to write inode for " << path;
return false;
}
fs_->MarkInodeInUseAsFile(new_inode);
// Prepare extents to fallocate() in order of physical goal to ensure that
// the block allocator can always allocate the extent on an empty filesystem.
std::map<blk64_t, Extent> extents_by_goal;
for (auto& extent : extents.extent()) {
extents_by_goal.insert({extent.goal(), extent});
}
// For each extent, fallocate() with a fixed goal.
for (auto& [_, extent] : extents_by_goal) {
if (!fs_->FixedGoalFallocate(new_inode, extent.goal(), extent.start(),
extent.length())) {
LOG(ERROR) << "Extent (start, length, goal): " << extent.start() << " "
<< extent.length() << " " << extent.goal();
LOG(ERROR) << "Failed to allocate extent for file: " << path;
return false;
}
}
cleanup.ReplaceClosure(base::DoNothing());
return true;
}
bool FilesystemManager::UnlinkFile(const base::FilePath& path) {
if (!ValidatePath(path)) {
return false;
}
ext2_ino_t parent_inode = EXT2_ROOT_INO;
base::FilePath parent_dir = path.DirName();
if (!parent_dir.value().empty()) {
if (!fs_->LookupInode(parent_dir, &parent_inode)) {
LOG(ERROR) << "Failed to look up path: " << parent_dir;
return false;
}
}
if (!fs_->Unlink(parent_inode, path.BaseName().value())) {
LOG(ERROR) << "Failed to unlink file: " << path;
return false;
}
return true;
}
bool FilesystemManager::FileExists(const base::FilePath& path) {
if (!ValidatePath(path)) {
return false;
}
ext2_ino_t inode = EXT2_ROOT_INO;
if (!fs_->LookupInode(path, &inode)) {
return false;
}
return true;
}
} // namespace libpreservation