fs/system: deduplicate RomFs code

This commit is contained in:
Michael Scire
2020-12-04 22:08:33 -08:00
parent c45088d1cd
commit 73167448cc
16 changed files with 226 additions and 1723 deletions

View File

@@ -0,0 +1,204 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere/fs/common/fs_dbm_rom_types.hpp>
#include <stratosphere/fs/common/fs_dbm_rom_path_tool.hpp>
#include <stratosphere/fs/common/fs_dbm_rom_key_value_storage.hpp>
namespace ams::fs {
class HierarchicalRomFileTable {
public:
using Position = u32;
struct FindPosition {
Position next_dir;
Position next_file;
};
static_assert(util::is_pod<FindPosition>::value);
using DirectoryInfo = RomDirectoryInfo;
using FileInfo = RomFileInfo;
static constexpr RomFileId PositionToFileId(Position pos) {
return static_cast<RomFileId>(pos);
}
static constexpr Position FileIdToPosition(RomFileId id) {
return static_cast<Position>(id);
}
private:
static constexpr inline Position InvalidPosition = ~Position();
static constexpr inline Position RootPosition = 0;
static constexpr inline size_t ReservedDirectoryCount = 1;
static constexpr RomDirectoryId PositionToDirectoryId(Position pos) {
return static_cast<RomDirectoryId>(pos);
}
static constexpr Position DirectoryIdToPosition(RomDirectoryId id) {
return static_cast<Position>(id);
}
static_assert(std::is_same<RomDirectoryId, RomFileId>::value);
struct RomDirectoryEntry {
Position next;
Position dir;
Position file;
};
static_assert(util::is_pod<RomDirectoryEntry>::value);
struct RomFileEntry {
Position next;
FileInfo info;
};
static_assert(util::is_pod<RomFileEntry>::value);
static constexpr inline u32 MaxKeyLength = RomPathTool::MaxPathLength;
template<typename ImplKeyType, typename ClientKeyType, typename ValueType>
class EntryMapTable : public KeyValueRomStorageTemplate<ImplKeyType, ValueType, MaxKeyLength> {
public:
using ImplKey = ImplKeyType;
using ClientKey = ClientKeyType;
using Value = ValueType;
using Position = HierarchicalRomFileTable::Position;
using Base = KeyValueRomStorageTemplate<ImplKeyType, ValueType, MaxKeyLength>;
public:
Result Add(Position *out, const ClientKeyType &key, const Value &value) {
return Base::AddInternal(out, key.key, key.Hash(), key.name.path, key.name.length * sizeof(RomPathChar), value);
}
Result Get(Position *out_pos, Value *out_val, const ClientKeyType &key) {
return Base::GetInternal(out_pos, out_val, key.key, key.Hash(), key.name.path, key.name.length * sizeof(RomPathChar));
}
Result GetByPosition(ImplKey *out_key, Value *out_val, Position pos) {
return Base::GetByPosition(out_key, out_val, pos);
}
Result GetByPosition(ImplKey *out_key, Value *out_val, void *out_aux, size_t *out_aux_size, Position pos) {
return Base::GetByPosition(out_key, out_val, out_aux, out_aux_size, pos);
}
Result SetByPosition(Position pos, const Value &value) {
return Base::SetByPosition(pos, value);
}
};
struct RomEntryKey {
Position parent;
bool IsEqual(const RomEntryKey &rhs, const void *aux_lhs, size_t aux_lhs_size, const void *aux_rhs, size_t aux_rhs_size) const {
if (this->parent != rhs.parent) {
return false;
}
if (aux_lhs_size != aux_rhs_size) {
return false;
}
return RomPathTool::IsEqualPath(reinterpret_cast<const RomPathChar *>(aux_lhs), reinterpret_cast<const RomPathChar *>(aux_rhs), aux_lhs_size / sizeof(RomPathChar));
}
};
static_assert(util::is_pod<RomEntryKey>::value);
struct EntryKey {
RomEntryKey key;
RomPathTool::RomEntryName name;
constexpr u32 Hash() const {
u32 hash = this->key.parent ^ 123456789;
const RomPathChar * name = this->name.path;
const RomPathChar * const end = name + this->name.length;
while (name < end) {
const u32 cur = static_cast<u32>(static_cast<std::make_unsigned<RomPathChar>::type>(*(name++)));
hash = ((hash >> 5) | (hash << 27)) ^ cur;
}
return hash;
}
};
static_assert(util::is_pod<EntryKey>::value);
using DirectoryEntryMapTable = EntryMapTable<RomEntryKey, EntryKey, RomDirectoryEntry>;
using FileEntryMapTable = EntryMapTable<RomEntryKey, EntryKey, RomFileEntry>;
private:
DirectoryEntryMapTable dir_table;
FileEntryMapTable file_table;
public:
static s64 QueryDirectoryEntryBucketStorageSize(s64 count);
static size_t QueryDirectoryEntrySize(size_t aux_size);
static s64 QueryFileEntryBucketStorageSize(s64 count);
static size_t QueryFileEntrySize(size_t aux_size);
static Result Format(SubStorage dir_bucket, SubStorage file_bucket);
public:
HierarchicalRomFileTable();
constexpr u32 GetDirectoryEntryCount() const {
return this->dir_table.GetEntryCount();
}
constexpr u32 GetFileEntryCount() const {
return this->file_table.GetEntryCount();
}
Result Initialize(SubStorage dir_bucket, SubStorage dir_entry, SubStorage file_bucket, SubStorage file_entry);
void Finalize();
Result CreateRootDirectory();
Result CreateDirectory(RomDirectoryId *out, const RomPathChar *path, const DirectoryInfo &info);
Result CreateFile(RomFileId *out, const RomPathChar *path, const FileInfo &info);
Result ConvertPathToDirectoryId(RomDirectoryId *out, const RomPathChar *path);
Result ConvertPathToFileId(RomFileId *out, const RomPathChar *path);
Result GetDirectoryInformation(DirectoryInfo *out, const RomPathChar *path);
Result GetDirectoryInformation(DirectoryInfo *out, RomDirectoryId id);
Result OpenFile(FileInfo *out, const RomPathChar *path);
Result OpenFile(FileInfo *out, RomFileId id);
Result FindOpen(FindPosition *out, const RomPathChar *path);
Result FindOpen(FindPosition *out, RomDirectoryId id);
Result FindNextDirectory(RomPathChar *out, FindPosition *find, size_t length);
Result FindNextFile(RomPathChar *out, FindPosition *find, size_t length);
Result QueryRomFileSystemSize(s64 *out_dir_entry_size, s64 *out_file_entry_size);
private:
Result GetGrandParent(Position *out_pos, EntryKey *out_dir_key, RomDirectoryEntry *out_dir_entry, Position pos, RomPathTool::RomEntryName name, const RomPathChar *path);
Result FindParentDirectoryRecursive(Position *out_pos, EntryKey *out_dir_key, RomDirectoryEntry *out_dir_entry, RomPathTool::PathParser *parser, const RomPathChar *path);
Result FindPathRecursive(EntryKey *out_key, RomDirectoryEntry *out_dir_entry, bool is_dir, const RomPathChar *path);
Result FindDirectoryRecursive(EntryKey *out_key, RomDirectoryEntry *out_dir_entry, const RomPathChar *path);
Result FindFileRecursive(EntryKey *out_key, RomDirectoryEntry *out_dir_entry, const RomPathChar *path);
Result CheckSameEntryExists(const EntryKey &key, Result if_exists);
Result GetDirectoryEntry(Position *out_pos, RomDirectoryEntry *out_entry, const EntryKey &key);
Result GetDirectoryEntry(RomDirectoryEntry *out_entry, RomDirectoryId id);
Result GetFileEntry(Position *out_pos, RomFileEntry *out_entry, const EntryKey &key);
Result GetFileEntry(RomFileEntry *out_entry, RomFileId id);
Result GetDirectoryInformation(DirectoryInfo *out, const EntryKey &key);
Result OpenFile(FileInfo *out, const EntryKey &key);
Result FindOpen(FindPosition *out, const EntryKey &key);
};
}

View File

@@ -0,0 +1,314 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere/fs/common/fs_dbm_rom_types.hpp>
#include <stratosphere/fs/fs_substorage.hpp>
namespace ams::fs {
template<typename KeyType, typename ValueType, size_t MaxAuxiliarySize>
class KeyValueRomStorageTemplate {
public:
using Key = KeyType;
using Value = ValueType;
using Position = u32;
using BucketIndex = s64;
struct FindIndex {
BucketIndex ind;
Position pos;
};
static_assert(util::is_pod<FindIndex>::value);
private:
static constexpr inline Position InvalidPosition = ~Position();
struct Element {
Key key;
Value value;
Position next;
u32 size;
};
static_assert(util::is_pod<Element>::value);
private:
s64 bucket_count;
SubStorage bucket_storage;
SubStorage kv_storage;
s64 total_entry_size;
u32 entry_count;
public:
static constexpr s64 QueryBucketStorageSize(s64 num) {
return num * sizeof(Position);
}
static constexpr s64 QueryBucketCount(s64 size) {
return size / sizeof(Position);
}
static constexpr size_t QueryEntrySize(size_t aux_size) {
return util::AlignUp(sizeof(Element) + aux_size, alignof(Element));
}
static Result Format(SubStorage bucket, s64 count) {
const Position pos = InvalidPosition;
for (s64 i = 0; i < count; i++) {
R_TRY(bucket.Write(i * sizeof(pos), std::addressof(pos), sizeof(pos)));
}
return ResultSuccess();
}
public:
KeyValueRomStorageTemplate() : bucket_count(), bucket_storage(), kv_storage(), total_entry_size(), entry_count() { /* ... */ }
Result Initialize(const SubStorage &bucket, s64 count, const SubStorage &kv) {
AMS_ASSERT(count > 0);
this->bucket_storage = bucket;
this->bucket_count = count;
this->kv_storage = kv;
return ResultSuccess();
}
void Finalize() {
this->bucket_storage = SubStorage();
this->kv_storage = SubStorage();
this->bucket_count = 0;
}
s64 GetTotalEntrySize() const {
return this->total_entry_size;
}
Result GetFreeSize(s64 *out) {
AMS_ASSERT(out != nullptr);
s64 kv_size = 0;
R_TRY(this->kv_storage.GetSize(std::addressof(kv_size)));
*out = kv_size - this->total_entry_size;
return ResultSuccess();
}
constexpr u32 GetEntryCount() const {
return this->entry_count;
}
protected:
Result AddInternal(Position *out, const Key &key, u32 hash_key, const void *aux, size_t aux_size, const Value &value) {
AMS_ASSERT(out != nullptr);
AMS_ASSERT(aux != nullptr || aux_size == 0);
AMS_ASSERT(this->bucket_count > 0);
{
Position pos, prev_pos;
Element elem;
const Result find_res = this->FindInternal(std::addressof(pos), std::addressof(prev_pos), std::addressof(elem), key, hash_key, aux, aux_size);
R_UNLESS(R_FAILED(find_res), fs::ResultDbmAlreadyExists());
R_UNLESS(fs::ResultDbmKeyNotFound::Includes(find_res), find_res);
}
Position pos;
R_TRY(this->AllocateEntry(std::addressof(pos), aux_size));
Position next_pos;
R_TRY(this->LinkEntry(std::addressof(next_pos), pos, hash_key));
const Element elem = { key, value, next_pos, static_cast<u32>(aux_size) };
R_TRY(this->WriteKeyValue(std::addressof(elem), pos, aux, aux_size));
*out = pos;
this->entry_count++;
return ResultSuccess();
}
Result GetInternal(Position *out_pos, Value *out_val, const Key &key, u32 hash_key, const void *aux, size_t aux_size) {
AMS_ASSERT(out_pos != nullptr);
AMS_ASSERT(out_val != nullptr);
AMS_ASSERT(aux != nullptr);
Position pos, prev_pos;
Element elem;
R_TRY(this->FindInternal(std::addressof(pos), std::addressof(prev_pos), std::addressof(elem), key, hash_key, aux, aux_size));
*out_pos = pos;
*out_val = elem.value;
return ResultSuccess();
}
Result GetByPosition(Key *out_key, Value *out_val, Position pos) {
AMS_ASSERT(out_key != nullptr);
AMS_ASSERT(out_val != nullptr);
Element elem;
R_TRY(this->ReadKeyValue(std::addressof(elem), pos));
*out_key = elem.key;
*out_val = elem.value;
return ResultSuccess();
}
Result GetByPosition(Key *out_key, Value *out_val, void *out_aux, size_t *out_aux_size, Position pos) {
AMS_ASSERT(out_key != nullptr);
AMS_ASSERT(out_val != nullptr);
AMS_ASSERT(out_aux != nullptr);
AMS_ASSERT(out_aux_size != nullptr);
Element elem;
R_TRY(this->ReadKeyValue(std::addressof(elem), out_aux, out_aux_size, pos));
*out_key = elem.key;
*out_val = elem.value;
return ResultSuccess();
}
Result SetByPosition(Position pos, const Value &value) {
Element elem;
R_TRY(this->ReadKeyValue(std::addressof(elem), pos));
elem.value = value;
return this->WriteKeyValue(std::addressof(elem), pos, nullptr, 0);
}
private:
BucketIndex HashToBucket(u32 hash_key) const {
return hash_key % this->bucket_count;
}
Result FindInternal(Position *out_pos, Position *out_prev, Element *out_elem, const Key &key, u32 hash_key, const void *aux, size_t aux_size) {
AMS_ASSERT(out_pos != nullptr);
AMS_ASSERT(out_prev != nullptr);
AMS_ASSERT(out_elem != nullptr);
AMS_ASSERT(aux != nullptr || aux_size == 0);
AMS_ASSERT(this->bucket_count > 0);
*out_pos = 0;
*out_prev = 0;
const BucketIndex ind = HashToBucket(hash_key);
Position cur;
R_TRY(this->ReadBucket(std::addressof(cur), ind));
s64 kv_size;
R_TRY(this->kv_storage.GetSize(std::addressof(kv_size)));
AMS_ASSERT(cur == InvalidPosition || cur < kv_size);
R_UNLESS(cur != InvalidPosition, fs::ResultDbmKeyNotFound());
u8 *buf = static_cast<u8 *>(::ams::fs::impl::Allocate(MaxAuxiliarySize));
R_UNLESS(buf != nullptr, fs::ResultAllocationFailureInDbmRomKeyValueStorage());
ON_SCOPE_EXIT { ::ams::fs::impl::Deallocate(buf, MaxAuxiliarySize); };
while (true) {
size_t cur_aux_size;
R_TRY(this->ReadKeyValue(out_elem, buf, std::addressof(cur_aux_size), cur));
if (key.IsEqual(out_elem->key, aux, aux_size, buf, cur_aux_size)) {
*out_pos = cur;
return ResultSuccess();
}
*out_prev = cur;
cur = out_elem->next;
R_UNLESS(cur != InvalidPosition, fs::ResultDbmKeyNotFound());
}
}
Result AllocateEntry(Position *out, size_t aux_size) {
AMS_ASSERT(out != nullptr);
s64 kv_size;
R_TRY(this->kv_storage.GetSize(std::addressof(kv_size)));
const size_t end_pos = this->total_entry_size + sizeof(Element) + aux_size;
R_UNLESS(end_pos <= static_cast<size_t>(kv_size), fs::ResultDbmKeyFull());
*out = static_cast<Position>(this->total_entry_size);
this->total_entry_size = util::AlignUp(static_cast<s64>(end_pos), alignof(Position));
return ResultSuccess();
}
Result LinkEntry(Position *out, Position pos, u32 hash_key) {
AMS_ASSERT(out != nullptr);
const BucketIndex ind = HashToBucket(hash_key);
Position next;
R_TRY(this->ReadBucket(std::addressof(next), ind));
s64 kv_size;
R_TRY(this->kv_storage.GetSize(std::addressof(kv_size)));
AMS_ASSERT(next == InvalidPosition || next < kv_size);
R_TRY(this->WriteBucket(pos, ind));
*out = next;
return ResultSuccess();
}
Result ReadBucket(Position *out, BucketIndex ind) {
AMS_ASSERT(out != nullptr);
AMS_ASSERT(ind < this->bucket_count);
const s64 offset = ind * sizeof(Position);
return this->bucket_storage.Read(offset, out, sizeof(*out));
}
Result WriteBucket(Position pos, BucketIndex ind) {
AMS_ASSERT(ind < this->bucket_count);
const s64 offset = ind * sizeof(Position);
return this->bucket_storage.Write(offset, std::addressof(pos), sizeof(pos));
}
Result ReadKeyValue(Element *out, Position pos) {
AMS_ASSERT(out != nullptr);
s64 kv_size;
R_TRY(this->kv_storage.GetSize(std::addressof(kv_size)));
AMS_ASSERT(pos < kv_size);
return this->kv_storage.Read(pos, out, sizeof(*out));
}
Result ReadKeyValue(Element *out, void *out_aux, size_t *out_aux_size, Position pos) {
AMS_ASSERT(out != nullptr);
AMS_ASSERT(out_aux != nullptr);
AMS_ASSERT(out_aux_size != nullptr);
R_TRY(this->ReadKeyValue(out, pos));
*out_aux_size = out->size;
if (out->size > 0) {
R_TRY(this->kv_storage.Read(pos + sizeof(*out), out_aux, out->size));
}
return ResultSuccess();
}
Result WriteKeyValue(const Element *elem, Position pos, const void *aux, size_t aux_size) {
AMS_ASSERT(elem != nullptr);
AMS_ASSERT(aux != nullptr);
s64 kv_size;
R_TRY(this->kv_storage.GetSize(std::addressof(kv_size)));
AMS_ASSERT(pos < kv_size);
R_TRY(this->kv_storage.Write(pos, elem, sizeof(*elem)));
if (aux != nullptr && aux_size > 0) {
R_TRY(this->kv_storage.Write(pos + sizeof(*elem), aux, aux_size));
}
return ResultSuccess();
}
};
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere/fs/common/fs_dbm_rom_types.hpp>
namespace ams::fs::RomPathTool {
constexpr inline u32 MaxPathLength = 0x300;
struct RomEntryName {
size_t length;
const RomPathChar *path;
};
static_assert(util::is_pod<RomEntryName>::value);
constexpr void InitEntryName(RomEntryName *entry) {
AMS_ASSERT(entry != nullptr);
entry->length = 0;
}
constexpr inline bool IsSeparator(RomPathChar c) {
return c == RomStringTraits::DirectorySeparator;
}
constexpr inline bool IsNullTerminator(RomPathChar c) {
return c == RomStringTraits::NullTerminator;
}
constexpr inline bool IsDot(RomPathChar c) {
return c == RomStringTraits::Dot;
}
constexpr inline bool IsCurrentDirectory(const RomEntryName &name) {
return name.length == 1 && IsDot(name.path[0]);
}
constexpr inline bool IsCurrentDirectory(const RomPathChar *p, size_t length) {
AMS_ASSERT(p != nullptr);
return length == 1 && IsDot(p[0]);
}
constexpr inline bool IsCurrentDirectory(const RomPathChar *p) {
AMS_ASSERT(p != nullptr);
return IsDot(p[0]) && IsNullTerminator(p[1]);
}
constexpr inline bool IsParentDirectory(const RomEntryName &name) {
return name.length == 2 && IsDot(name.path[0]) && IsDot(name.path[1]);
}
constexpr inline bool IsParentDirectory(const RomPathChar *p) {
AMS_ASSERT(p != nullptr);
return IsDot(p[0]) && IsDot(p[1]) && IsNullTerminator(p[2]);
}
constexpr inline bool IsParentDirectory(const RomPathChar *p, size_t length) {
AMS_ASSERT(p != nullptr);
return length == 2 && IsDot(p[0]) && IsDot(p[1]);
}
constexpr inline bool IsEqualPath(const RomPathChar *lhs, const RomPathChar *rhs, size_t length) {
AMS_ASSERT(lhs != nullptr);
AMS_ASSERT(rhs != nullptr);
return std::strncmp(lhs, rhs, length) == 0;
}
Result GetParentDirectoryName(RomEntryName *out, const RomEntryName &cur, const RomPathChar *p);
class PathParser {
private:
const RomPathChar *prev_path_start;
const RomPathChar *prev_path_end;
const RomPathChar *next_path;
bool finished;
public:
constexpr PathParser() : prev_path_start(), prev_path_end(), next_path(), finished() { /* ... */ }
Result Initialize(const RomPathChar *path);
void Finalize();
bool IsParseFinished() const;
bool IsDirectoryPath() const;
Result GetAsDirectoryName(RomEntryName *out) const;
Result GetAsFileName(RomEntryName *out) const;
Result GetNextDirectoryName(RomEntryName *out);
};
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere/fs/fs_common.hpp>
namespace ams::fs {
using RomPathChar = char;
using RomFileId = s32;
using RomDirectoryId = s32;
struct RomFileSystemInformation {
s64 size;
s64 directory_bucket_offset;
s64 directory_bucket_size;
s64 directory_entry_offset;
s64 directory_entry_size;
s64 file_bucket_offset;
s64 file_bucket_size;
s64 file_entry_offset;
s64 file_entry_size;
s64 body_offset;
};
static_assert(util::is_pod<RomFileSystemInformation>::value);
static_assert(sizeof(RomFileSystemInformation) == 0x50);
struct RomDirectoryInfo {
/* ... */
};
static_assert(util::is_pod<RomDirectoryInfo>::value);
struct RomFileInfo {
Int64 offset;
Int64 size;
};
static_assert(util::is_pod<RomFileInfo>::value);
namespace RomStringTraits {
constexpr inline char DirectorySeparator = '/';
constexpr inline char NullTerminator = '\x00';
constexpr inline char Dot = '.';
}
}