mirror of
https://github.com/Atmosphere-NX/Atmosphere-libs.git
synced 2025-06-21 11:02:45 +02:00
kvdb: implement MemoryKeyValueStore
This commit is contained in:
parent
a4a3ebed50
commit
99ebbd2a18
2
Makefile
2
Makefile
@ -16,7 +16,7 @@ include $(DEVKITPRO)/libnx/switch_rules
|
||||
# INCLUDES is a list of directories containing header files
|
||||
#---------------------------------------------------------------------------------
|
||||
TARGET := $(notdir $(CURDIR))
|
||||
SOURCES := source source/spl source/spl/smc source/updater source/patcher source/map source/rnd source/util source/sm source/cfg source/pm source/hid source/ldr
|
||||
SOURCES := source source/spl source/spl/smc source/updater source/patcher source/map source/rnd source/util source/sm source/cfg source/pm source/hid source/ldr source/kvdb
|
||||
DATA := data
|
||||
INCLUDES := include
|
||||
|
||||
|
68
include/stratosphere/kvdb/kvdb_archive.hpp
Normal file
68
include/stratosphere/kvdb/kvdb_archive.hpp
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2019 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 <switch.h>
|
||||
#include "../defines.hpp"
|
||||
#include "../results.hpp"
|
||||
|
||||
#include "kvdb_auto_buffer.hpp"
|
||||
|
||||
namespace sts::kvdb {
|
||||
|
||||
/* Functionality for parsing/generating a key value archive. */
|
||||
class ArchiveReader {
|
||||
private:
|
||||
AutoBuffer &buffer;
|
||||
size_t offset;
|
||||
public:
|
||||
ArchiveReader(AutoBuffer &b) : buffer(b), offset(0) { /* ... */ }
|
||||
private:
|
||||
Result Peek(void *dst, size_t size);
|
||||
Result Read(void *dst, size_t size);
|
||||
public:
|
||||
Result ReadEntryCount(size_t *out);
|
||||
Result GetEntrySize(size_t *out_key_size, size_t *out_value_size);
|
||||
Result ReadEntry(void *out_key, size_t key_size, void *out_value, size_t value_size);
|
||||
};
|
||||
|
||||
class ArchiveWriter {
|
||||
private:
|
||||
AutoBuffer &buffer;
|
||||
size_t offset;
|
||||
public:
|
||||
ArchiveWriter(AutoBuffer &b) : buffer(b), offset(0) { /* ... */ }
|
||||
private:
|
||||
Result Write(const void *src, size_t size);
|
||||
public:
|
||||
void WriteHeader(size_t entry_count);
|
||||
void WriteEntry(const void *key, size_t key_size, const void *value, size_t value_size);
|
||||
};
|
||||
|
||||
class ArchiveSizeHelper {
|
||||
private:
|
||||
size_t size;
|
||||
public:
|
||||
ArchiveSizeHelper();
|
||||
|
||||
void AddEntry(size_t key_size, size_t value_size);
|
||||
|
||||
size_t GetSize() const {
|
||||
return this->size;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
88
include/stratosphere/kvdb/kvdb_auto_buffer.hpp
Normal file
88
include/stratosphere/kvdb/kvdb_auto_buffer.hpp
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2019 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 <switch.h>
|
||||
#include "../defines.hpp"
|
||||
#include "../results.hpp"
|
||||
|
||||
namespace sts::kvdb {
|
||||
|
||||
class AutoBuffer {
|
||||
NON_COPYABLE(AutoBuffer);
|
||||
private:
|
||||
std::unique_ptr<u8[]> buffer;
|
||||
size_t size;
|
||||
public:
|
||||
AutoBuffer() : size(0) { /* ... */ }
|
||||
|
||||
AutoBuffer(AutoBuffer &&rhs) {
|
||||
this->buffer = std::move(rhs.buffer);
|
||||
this->size = rhs.size;
|
||||
rhs.size = 0;
|
||||
}
|
||||
|
||||
AutoBuffer& operator=(AutoBuffer &&rhs) {
|
||||
rhs.Swap(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Swap(AutoBuffer &rhs) {
|
||||
std::swap(this->buffer, rhs.buffer);
|
||||
std::swap(this->size, rhs.size);
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
this->buffer.reset();
|
||||
this->size = 0;
|
||||
}
|
||||
|
||||
u8 *Get() const {
|
||||
return this->buffer.get();
|
||||
}
|
||||
|
||||
size_t GetSize() const {
|
||||
return this->size;
|
||||
}
|
||||
|
||||
Result Initialize(size_t size) {
|
||||
/* Check that we're not already initialized. */
|
||||
if (this->buffer != nullptr) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Allocate a buffer. */
|
||||
auto mem = new (std::nothrow) u8[size];
|
||||
if (mem == nullptr) {
|
||||
return ResultKvdbAllocationFailed;
|
||||
}
|
||||
|
||||
this->buffer.reset(mem);
|
||||
this->size = size;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Initialize(const void *buf, size_t size) {
|
||||
/* Create a new buffer of the right size. */
|
||||
R_TRY(this->Initialize(size));
|
||||
|
||||
/* Copy the input data in. */
|
||||
std::memcpy(this->buffer.get(), buf, size);
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
};
|
||||
}
|
152
include/stratosphere/kvdb/kvdb_bounded_string.hpp
Normal file
152
include/stratosphere/kvdb/kvdb_bounded_string.hpp
Normal file
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2019 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 <cstring>
|
||||
#include <cstdarg>
|
||||
#include <switch.h>
|
||||
#include "../defines.hpp"
|
||||
#include "../results.hpp"
|
||||
|
||||
namespace sts::kvdb {
|
||||
|
||||
/* Represents a string with a backing buffer of N bytes. */
|
||||
template<size_t N>
|
||||
class BoundedString {
|
||||
static_assert(N > 0, "BoundedString requires non-zero backing buffer!");
|
||||
private:
|
||||
char buffer[N];
|
||||
private:
|
||||
/* Utility. */
|
||||
static inline void CheckLength(size_t len) {
|
||||
if (len >= N) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
public:
|
||||
/* Constructors. */
|
||||
BoundedString() {
|
||||
buffer[0] = 0;
|
||||
}
|
||||
|
||||
explicit BoundedString(const char *s) {
|
||||
this->Set(s);
|
||||
}
|
||||
|
||||
/* Static constructors. */
|
||||
static BoundedString<N> Make(const char *s) {
|
||||
return BoundedString<N>(s);
|
||||
}
|
||||
|
||||
static BoundedString<N> MakeFormat(const char *format, ...) __attribute__((format (printf, 1, 2))) {
|
||||
BoundedString<N> string;
|
||||
|
||||
std::va_list args;
|
||||
va_start(args, format);
|
||||
CheckLength(std::vsnprintf(string.buffer, N, format, args));
|
||||
va_end(args);
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
/* Getters. */
|
||||
size_t GetLength() const {
|
||||
return strnlen(this->buffer, N);
|
||||
}
|
||||
|
||||
const char *Get() const {
|
||||
return this->buffer;
|
||||
}
|
||||
|
||||
operator const char *() const {
|
||||
return this->buffer;
|
||||
}
|
||||
|
||||
/* Setters. */
|
||||
void Set(const char *s) {
|
||||
/* Ensure string can fit in our buffer. */
|
||||
CheckLength(strnlen(s, N));
|
||||
std::strncpy(this->buffer, s, N);
|
||||
}
|
||||
|
||||
void SetFormat(const char *format, ...) __attribute__((format (printf, 2, 3))) {
|
||||
/* Format into the buffer, abort if too large. */
|
||||
std::va_list args;
|
||||
va_start(args, format);
|
||||
CheckLength(std::vsnprintf(this->buffer, N, format, args));
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
/* Append to existing. */
|
||||
void Append(const char *s) {
|
||||
const size_t length = GetLength();
|
||||
CheckLength(length + strnlen(s, N));
|
||||
std::strncat(this->buffer, s, N - length - 1);
|
||||
}
|
||||
|
||||
void Append(char c) {
|
||||
const size_t length = GetLength();
|
||||
CheckLength(length + 1);
|
||||
this->buffer[length] = c;
|
||||
this->buffer[length + 1] = 0;
|
||||
}
|
||||
|
||||
void AppendFormat(const char *format, ...) __attribute__((format (printf, 2, 3))) {
|
||||
const size_t length = GetLength();
|
||||
std::va_list args;
|
||||
va_start(args, format);
|
||||
CheckLength(std::vsnprintf(this->buffer + length, N - length, format, args) + length);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
/* Substring utilities. */
|
||||
void GetSubstring(char *dst, size_t dst_size, size_t offset, size_t length) const {
|
||||
/* Make sure output buffer can hold the substring. */
|
||||
if (offset + length > GetLength() || dst_size <= length) {
|
||||
std::abort();
|
||||
}
|
||||
/* Copy substring to dst. */
|
||||
std::strncpy(dst, this->buffer + offset, length);
|
||||
dst[length] = 0;
|
||||
}
|
||||
|
||||
BoundedString<N> GetSubstring(size_t offset, size_t length) const {
|
||||
BoundedString<N> string;
|
||||
GetSubstring(string.buffer, N, offset, length);
|
||||
return string;
|
||||
}
|
||||
|
||||
/* Comparison. */
|
||||
constexpr bool operator==(const BoundedString<N> &rhs) const {
|
||||
return std::strncmp(this->buffer, rhs.buffer, N) == 0;
|
||||
}
|
||||
|
||||
constexpr bool operator!=(const BoundedString<N> &rhs) const {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
bool EndsWith(const char *s, size_t offset) const {
|
||||
return std::strncmp(this->buffer + offset, s, N - offset) == 0;
|
||||
}
|
||||
|
||||
bool EndsWith(const char *s) {
|
||||
const size_t suffix_length = strnlen(s, N);
|
||||
const size_t length = GetLength();
|
||||
return suffix_length <= length && EndsWith(s, length - suffix_length);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
566
include/stratosphere/kvdb/kvdb_memory_key_value_store.hpp
Normal file
566
include/stratosphere/kvdb/kvdb_memory_key_value_store.hpp
Normal file
@ -0,0 +1,566 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2019 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 <algorithm>
|
||||
#include <switch.h>
|
||||
#include <sys/stat.h>
|
||||
#include "kvdb_auto_buffer.hpp"
|
||||
#include "kvdb_archive.hpp"
|
||||
#include "kvdb_bounded_string.hpp"
|
||||
#include "kvdb_memory_key_value_store.hpp"
|
||||
|
||||
namespace sts::kvdb {
|
||||
|
||||
template<class Key>
|
||||
class MemoryKeyValueStore {
|
||||
static_assert(std::is_pod<Key>::value, "KeyValueStore Keys must be pod!");
|
||||
NON_COPYABLE(MemoryKeyValueStore);
|
||||
NON_MOVEABLE(MemoryKeyValueStore);
|
||||
public:
|
||||
/* Subtypes. */
|
||||
class Entry {
|
||||
private:
|
||||
Key key;
|
||||
void *value;
|
||||
size_t value_size;
|
||||
public:
|
||||
constexpr Entry(const Key &k, void *v, size_t s) : key(k), value(v), value_size(s) { /* ... */ }
|
||||
|
||||
const Key &GetKey() const {
|
||||
return this->key;
|
||||
}
|
||||
|
||||
template<typename Value = void>
|
||||
Value *GetValuePointer() {
|
||||
/* Size check. Note: Nintendo does not size check. */
|
||||
if constexpr (!std::is_same<Value, void>::value) {
|
||||
if (sizeof(Value) > this->value_size) {
|
||||
std::abort();
|
||||
}
|
||||
/* Ensure we only get pod. */
|
||||
static_assert(std::is_pod<Value>::value, "KeyValueStore Values must be pod");
|
||||
}
|
||||
return reinterpret_cast<Value *>(this->value);
|
||||
}
|
||||
|
||||
template<typename Value = void>
|
||||
const Value *GetValuePointer() const {
|
||||
/* Size check. Note: Nintendo does not size check. */
|
||||
if constexpr (!std::is_same<Value, void>::value) {
|
||||
if (sizeof(Value) > this->value_size) {
|
||||
std::abort();
|
||||
}
|
||||
/* Ensure we only get pod. */
|
||||
static_assert(std::is_pod<Value>::value, "KeyValueStore Values must be pod");
|
||||
}
|
||||
return reinterpret_cast<Value *>(this->value);
|
||||
}
|
||||
|
||||
template<typename Value>
|
||||
Value &GetValue() {
|
||||
return *(this->GetValuePointer<Value>());
|
||||
}
|
||||
|
||||
template<typename Value>
|
||||
const Value &GetValue() const {
|
||||
return *(this->GetValuePointer<Value>());
|
||||
}
|
||||
|
||||
size_t GetValueSize() const {
|
||||
return this->value_size;
|
||||
}
|
||||
|
||||
constexpr inline bool operator<(const Key &rhs) const {
|
||||
return key < rhs;
|
||||
}
|
||||
|
||||
constexpr inline bool operator==(const Key &rhs) const {
|
||||
return key == rhs;
|
||||
}
|
||||
};
|
||||
|
||||
class Index {
|
||||
private:
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
Entry *entries;
|
||||
public:
|
||||
Index() : count(0), capacity(0), entries(nullptr) { /* ... */ }
|
||||
|
||||
~Index() {
|
||||
if (this->entries != nullptr) {
|
||||
this->ResetEntries();
|
||||
std::free(this->entries);
|
||||
this->entries = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
size_t GetCount() const {
|
||||
return this->count;
|
||||
}
|
||||
|
||||
size_t GetCapacity() const {
|
||||
return this->capacity;
|
||||
}
|
||||
|
||||
void ResetEntries() {
|
||||
for (size_t i = 0; i < this->count; i++) {
|
||||
std::free(this->entries[i].GetValuePointer());
|
||||
}
|
||||
this->count = 0;
|
||||
}
|
||||
|
||||
Result Initialize(size_t capacity) {
|
||||
this->entries = reinterpret_cast<Entry *>(std::malloc(sizeof(Entry) * capacity));
|
||||
if (this->entries == nullptr) {
|
||||
return ResultKvdbAllocationFailed;
|
||||
}
|
||||
this->capacity = capacity;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Set(const Key &key, const void *value, size_t value_size) {
|
||||
/* Allocate new value. */
|
||||
void *new_value = std::malloc(value_size);
|
||||
if (new_value == nullptr) {
|
||||
return ResultKvdbAllocationFailed;
|
||||
}
|
||||
std::memcpy(new_value, value, value_size);
|
||||
|
||||
/* Find entry for key. */
|
||||
Entry *it = this->lower_bound(key);
|
||||
if (it != this->end() && it->GetKey() == key) {
|
||||
/* Entry already exists. Free old value. */
|
||||
std::free(it->GetValuePointer());
|
||||
} else {
|
||||
/* We need to add a new entry. Check we have room, move future keys forward. */
|
||||
if (this->count >= this->capacity) {
|
||||
std::free(new_value);
|
||||
return ResultKvdbTooManyKeys;
|
||||
}
|
||||
std::memmove(it + 1, it, sizeof(*it) * (this->end() - it));
|
||||
this->count++;
|
||||
}
|
||||
|
||||
/* Save the new Entry in the map. */
|
||||
*it = Entry(key, new_value, value_size);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result AddUnsafe(const Key &key, void *value, size_t value_size) {
|
||||
if (this->count >= this->capacity) {
|
||||
return ResultKvdbTooManyKeys;
|
||||
}
|
||||
|
||||
this->entries[this->count++] = Entry(key, value, value_size);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Remove(const Key &key) {
|
||||
/* Find entry for key. */
|
||||
Entry *it = this->find(key);
|
||||
|
||||
/* Check if the entry is valid. */
|
||||
if (it != this->end()) {
|
||||
/* Free the value, move entries back. */
|
||||
std::free(it->GetValuePointer());
|
||||
std::memmove(it, it + 1, sizeof(*it) * (this->end() - (it + 1)));
|
||||
this->count--;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
/* If it's not, we didn't remove it. */
|
||||
return ResultKvdbKeyNotFound;
|
||||
}
|
||||
|
||||
Entry *begin() {
|
||||
return this->GetBegin();
|
||||
}
|
||||
|
||||
const Entry *begin() const {
|
||||
return this->GetBegin();
|
||||
}
|
||||
|
||||
Entry *end() {
|
||||
return this->GetEnd();
|
||||
}
|
||||
|
||||
const Entry *end() const {
|
||||
return this->GetEnd();
|
||||
}
|
||||
|
||||
const Entry *cbegin() const {
|
||||
return this->begin();
|
||||
}
|
||||
|
||||
const Entry *cend() const {
|
||||
return this->end();
|
||||
}
|
||||
|
||||
Entry *lower_bound(const Key &key) {
|
||||
return this->GetLowerBound(key);
|
||||
}
|
||||
|
||||
const Entry *lower_bound(const Key &key) const {
|
||||
return this->GetLowerBound(key);
|
||||
}
|
||||
|
||||
Entry *find(const Key &key) {
|
||||
return this->Find(key);
|
||||
}
|
||||
|
||||
const Entry *find(const Key &key) const {
|
||||
return this->Find(key);
|
||||
}
|
||||
private:
|
||||
Entry *GetBegin() {
|
||||
return this->entries;
|
||||
}
|
||||
|
||||
const Entry *GetBegin() const {
|
||||
return this->entries;
|
||||
}
|
||||
|
||||
Entry *GetEnd() {
|
||||
return this->GetBegin() + this->count;
|
||||
}
|
||||
|
||||
const Entry *GetEnd() const {
|
||||
return this->GetBegin() + this->count;
|
||||
}
|
||||
|
||||
Entry *GetLowerBound(const Key &key) {
|
||||
return std::lower_bound(this->GetBegin(), this->GetEnd(), key);
|
||||
}
|
||||
|
||||
const Entry *GetLowerBound(const Key &key) const {
|
||||
return std::lower_bound(this->GetBegin(), this->GetEnd(), key);
|
||||
}
|
||||
|
||||
Entry *Find(const Key &key) {
|
||||
auto it = this->GetLowerBound(key);
|
||||
auto end = this->GetEnd();
|
||||
if (it != end && it->GetKey() == key) {
|
||||
return it;
|
||||
}
|
||||
return end;
|
||||
}
|
||||
|
||||
const Entry *Find(const Key &key) const {
|
||||
auto it = this->GetLowerBound(key);
|
||||
auto end = this->GetEnd();
|
||||
if (it != end && it->GetKey() == key) {
|
||||
return it;
|
||||
}
|
||||
return end;
|
||||
}
|
||||
};
|
||||
private:
|
||||
static constexpr size_t MaxPathLen = 0x300; /* TODO: FS_MAX_PATH - 1? */
|
||||
using Path = kvdb::BoundedString<MaxPathLen>;
|
||||
private:
|
||||
Index index;
|
||||
Path path;
|
||||
Path temp_path;
|
||||
public:
|
||||
MemoryKeyValueStore() { /* ... */ }
|
||||
|
||||
Result Initialize(const char *dir, size_t capacity) {
|
||||
/* Ensure that the passed path is a directory. */
|
||||
{
|
||||
struct stat st;
|
||||
if (stat(dir, &st) != 0 || !(S_ISDIR(st.st_mode))) {
|
||||
return ResultFsPathNotFound;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set paths. */
|
||||
this->path.SetFormat("%s%s", dir, "/imkvdb.arc");
|
||||
this->temp_path.SetFormat("%s%s", dir, "/imkvdb.tmp");
|
||||
|
||||
/* Initialize our index. */
|
||||
R_TRY(this->index.Initialize(capacity));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Initialize(size_t capacity) {
|
||||
/* This initializes without an archive file. */
|
||||
/* A store initialized this way cannot have its contents loaded from or flushed to disk. */
|
||||
this->path.Set("");
|
||||
this->temp_path.Set("");
|
||||
|
||||
/* Initialize our index. */
|
||||
R_TRY(this->index.Initialize(capacity));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
size_t GetCount() const {
|
||||
return this->index.GetCount();
|
||||
}
|
||||
|
||||
size_t GetCapacity() const {
|
||||
return this->index.GetCapacity();
|
||||
}
|
||||
|
||||
Result Load() {
|
||||
/* Reset any existing entries. */
|
||||
this->index.ResetEntries();
|
||||
|
||||
/* Try to read the archive -- note, path not found is a success condition. */
|
||||
/* This is because no archive file = no entries, so we're in the right state. */
|
||||
AutoBuffer buffer;
|
||||
R_TRY_CATCH(this->ReadArchiveFile(&buffer)) {
|
||||
R_CATCH(ResultFsPathNotFound) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
/* Parse entries from the buffer. */
|
||||
{
|
||||
ArchiveReader reader(buffer);
|
||||
|
||||
size_t entry_count = 0;
|
||||
R_TRY(reader.ReadEntryCount(&entry_count));
|
||||
|
||||
for (size_t i = 0; i < entry_count; i++) {
|
||||
/* Get size of key/value. */
|
||||
size_t key_size = 0, value_size = 0;
|
||||
R_TRY(reader.GetEntrySize(&key_size, &value_size));
|
||||
|
||||
/* Allocate memory for value. */
|
||||
void *new_value = std::malloc(value_size);
|
||||
if (new_value == nullptr) {
|
||||
return ResultKvdbAllocationFailed;
|
||||
}
|
||||
auto value_guard = SCOPE_GUARD { std::free(new_value); };
|
||||
|
||||
/* Read key and value. */
|
||||
Key key;
|
||||
R_TRY(reader.ReadEntry(&key, sizeof(key), new_value, value_size));
|
||||
R_TRY(this->index.AddUnsafe(key, new_value, value_size));
|
||||
|
||||
/* We succeeded, so cancel the value guard to prevent deallocation. */
|
||||
value_guard.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Save() {
|
||||
/* Create a buffer to hold the archive. */
|
||||
AutoBuffer buffer;
|
||||
R_TRY(buffer.Initialize(this->GetArchiveSize()));
|
||||
|
||||
/* Write the archive to the buffer. */
|
||||
{
|
||||
ArchiveWriter writer(buffer);
|
||||
writer.WriteHeader(this->GetCount());
|
||||
for (const auto &it : this->index) {
|
||||
const auto &key = it.GetKey();
|
||||
writer.WriteEntry(&key, sizeof(Key), it.GetValuePointer(), it.GetValueSize());
|
||||
}
|
||||
}
|
||||
|
||||
/* Save the buffer to disk. */
|
||||
return this->Commit(buffer);
|
||||
}
|
||||
|
||||
Result Set(const Key &key, const void *value, size_t value_size) {
|
||||
return this->index.Set(key, value, value_size);
|
||||
}
|
||||
|
||||
template<typename Value>
|
||||
Result Set(const Key &key, const Value &value) {
|
||||
/* Only allow setting pod. */
|
||||
static_assert(std::is_pod<Value>::value, "KeyValueStore Values must be pod");
|
||||
return this->Set(key, &value, sizeof(Value));
|
||||
}
|
||||
|
||||
template<typename Value>
|
||||
Result Set(const Key &key, const Value *value) {
|
||||
/* Only allow setting pod. */
|
||||
static_assert(std::is_pod<Value>::value, "KeyValueStore Values must be pod");
|
||||
return this->Set(key, value, sizeof(Value));
|
||||
}
|
||||
|
||||
Result Get(size_t *out_size, void *out_value, size_t max_out_size, const Key &key) {
|
||||
/* Find entry. */
|
||||
auto it = this->find(key);
|
||||
if (it == this->end()) {
|
||||
return ResultKvdbKeyNotFound;
|
||||
}
|
||||
|
||||
size_t size = std::min(max_out_size, it.GetValueSize());
|
||||
std::memcpy(out_value, it.GetValuePointer(), size);
|
||||
*out_size = size;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
template<typename Value = void>
|
||||
Result GetValuePointer(Value **out_value, const Key &key) {
|
||||
/* Find entry. */
|
||||
auto it = this->find(key);
|
||||
if (it == this->end()) {
|
||||
return ResultKvdbKeyNotFound;
|
||||
}
|
||||
|
||||
*out_value = it->template GetValuePointer<Value>();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
template<typename Value = void>
|
||||
Result GetValuePointer(const Value **out_value, const Key &key) const {
|
||||
/* Find entry. */
|
||||
auto it = this->find(key);
|
||||
if (it == this->end()) {
|
||||
return ResultKvdbKeyNotFound;
|
||||
}
|
||||
|
||||
*out_value = it->template GetValuePointer<Value>();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
template<typename Value>
|
||||
Result GetValue(Value *out_value, const Key &key) const {
|
||||
/* Find entry. */
|
||||
auto it = this->find(key);
|
||||
if (it == this->end()) {
|
||||
return ResultKvdbKeyNotFound;
|
||||
}
|
||||
|
||||
*out_value = it->template GetValue<Value>();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result GetValueSize(size_t *out_size, const Key &key) const {
|
||||
/* Find entry. */
|
||||
auto it = this->find(key);
|
||||
if (it == this->end()) {
|
||||
return ResultKvdbKeyNotFound;
|
||||
}
|
||||
|
||||
*out_size = it->GetValueSize();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Remove(const Key &key) {
|
||||
return this->index.Remove(key);
|
||||
}
|
||||
|
||||
Entry *begin() {
|
||||
return this->index.begin();
|
||||
}
|
||||
|
||||
const Entry *begin() const {
|
||||
return this->index.begin();
|
||||
}
|
||||
|
||||
Entry *end() {
|
||||
return this->index.end();
|
||||
}
|
||||
|
||||
const Entry *end() const {
|
||||
return this->index.end();
|
||||
}
|
||||
|
||||
const Entry *cbegin() const {
|
||||
return this->index.cbegin();
|
||||
}
|
||||
|
||||
const Entry *cend() const {
|
||||
return this->index.cend();
|
||||
}
|
||||
|
||||
Entry *lower_bound(const Key &key) {
|
||||
return this->index.lower_bound(key);
|
||||
}
|
||||
|
||||
const Entry *lower_bound(const Key &key) const {
|
||||
return this->index.lower_bound(key);
|
||||
}
|
||||
|
||||
Entry *find(const Key &key) {
|
||||
return this->index.find(key);
|
||||
}
|
||||
|
||||
const Entry *find(const Key &key) const {
|
||||
return this->index.find(key);
|
||||
}
|
||||
private:
|
||||
Result Commit(const AutoBuffer &buffer) {
|
||||
/* Try to delete temporary archive, but allow deletion failure (it may not exist). */
|
||||
std::remove(this->temp_path.Get());
|
||||
|
||||
/* Write data to the temporary archive. */
|
||||
{
|
||||
FILE *f = fopen(this->temp_path, "wb");
|
||||
if (f == nullptr) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
ON_SCOPE_EXIT { fclose(f); };
|
||||
|
||||
if (fwrite(buffer.Get(), buffer.GetSize(), 1, f) != 1) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to delete the saved archive, but allow deletion failure. */
|
||||
std::remove(this->path.Get());
|
||||
|
||||
/* Rename the path. */
|
||||
if (std::rename(this->temp_path.Get(), this->path.Get()) != 0) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
size_t GetArchiveSize() const {
|
||||
ArchiveSizeHelper size_helper;
|
||||
|
||||
for (const auto &it : this->index) {
|
||||
size_helper.AddEntry(sizeof(Key), it.GetValueSize());
|
||||
}
|
||||
|
||||
return size_helper.GetSize();
|
||||
}
|
||||
|
||||
Result ReadArchiveFile(AutoBuffer *dst) const {
|
||||
/* Open the file. */
|
||||
FILE *f = fopen(this->path, "rb");
|
||||
if (f == nullptr) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
ON_SCOPE_EXIT { fclose(f); };
|
||||
|
||||
/* Get the archive file size. */
|
||||
fseek(f, 0, SEEK_END);
|
||||
const size_t archive_size = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
/* Make a new buffer, read the file. */
|
||||
R_TRY(dst->Initialize(archive_size));
|
||||
if (fread(dst->Get(), archive_size, 1, f) != 1) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
@ -19,7 +19,7 @@
|
||||
|
||||
static constexpr u32 Module_Kvdb = 20;
|
||||
|
||||
static constexpr Result ResultKvdbTooLargeKey = MAKERESULT(Module_Kvdb, 1);
|
||||
static constexpr Result ResultKvdbTooManyKeys = MAKERESULT(Module_Kvdb, 1);
|
||||
static constexpr Result ResultKvdbKeyNotFound = MAKERESULT(Module_Kvdb, 2);
|
||||
static constexpr Result ResultKvdbAllocationFailed = MAKERESULT(Module_Kvdb, 4);
|
||||
static constexpr Result ResultKvdbInvalidKeyValue = MAKERESULT(Module_Kvdb, 5);
|
||||
|
186
source/kvdb/kvdb_archive.cpp
Normal file
186
source/kvdb/kvdb_archive.cpp
Normal file
@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2019 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/>.
|
||||
*/
|
||||
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/kvdb/kvdb_archive.hpp>
|
||||
|
||||
namespace sts::kvdb {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Convenience definitions. */
|
||||
constexpr u8 ArchiveHeaderMagic[4] = {'I', 'M', 'K', 'V'};
|
||||
constexpr u8 ArchiveEntryMagic[4] = {'I', 'M', 'E', 'N'};
|
||||
|
||||
/* Archive types. */
|
||||
struct ArchiveHeader {
|
||||
u8 magic[sizeof(ArchiveHeaderMagic)];
|
||||
u32 pad;
|
||||
u32 entry_count;
|
||||
|
||||
Result Validate() const {
|
||||
if (std::memcmp(this->magic, ArchiveHeaderMagic, sizeof(ArchiveHeaderMagic)) != 0) {
|
||||
return ResultKvdbInvalidKeyValue;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
static ArchiveHeader Make(size_t entry_count) {
|
||||
ArchiveHeader header = {};
|
||||
std::memcpy(header.magic, ArchiveHeaderMagic, sizeof(ArchiveHeaderMagic));
|
||||
header.entry_count = static_cast<u32>(entry_count);
|
||||
return header;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(ArchiveHeader) == 0xC && std::is_pod<ArchiveHeader>::value, "ArchiveHeader definition!");
|
||||
|
||||
struct ArchiveEntryHeader {
|
||||
u8 magic[sizeof(ArchiveEntryMagic)];
|
||||
u32 key_size;
|
||||
u32 value_size;
|
||||
|
||||
Result Validate() const {
|
||||
if (std::memcmp(this->magic, ArchiveEntryMagic, sizeof(ArchiveEntryMagic)) != 0) {
|
||||
return ResultKvdbInvalidKeyValue;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
static ArchiveEntryHeader Make(size_t ksz, size_t vsz) {
|
||||
ArchiveEntryHeader header = {};
|
||||
std::memcpy(header.magic, ArchiveEntryMagic, sizeof(ArchiveEntryMagic));
|
||||
header.key_size = ksz;
|
||||
header.value_size = vsz;
|
||||
return header;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(ArchiveEntryHeader) == 0xC && std::is_pod<ArchiveEntryHeader>::value, "ArchiveEntryHeader definition!");
|
||||
|
||||
}
|
||||
|
||||
/* Reader functionality. */
|
||||
Result ArchiveReader::Peek(void *dst, size_t size) {
|
||||
/* Bounds check. */
|
||||
if (this->offset + size > this->buffer.GetSize() || this->offset + size <= this->offset) {
|
||||
return ResultKvdbInvalidKeyValue;
|
||||
}
|
||||
|
||||
std::memcpy(dst, this->buffer.Get() + this->offset, size);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ArchiveReader::Read(void *dst, size_t size) {
|
||||
R_TRY(this->Peek(dst, size));
|
||||
this->offset += size;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ArchiveReader::ReadEntryCount(size_t *out) {
|
||||
/* This should only be called at the start of reading stream. */
|
||||
if (this->offset != 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Read and validate header. */
|
||||
ArchiveHeader header;
|
||||
R_TRY(this->Read(&header, sizeof(header)));
|
||||
R_TRY(header.Validate());
|
||||
|
||||
*out = header.entry_count;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ArchiveReader::GetEntrySize(size_t *out_key_size, size_t *out_value_size) {
|
||||
/* This should only be called after ReadEntryCount. */
|
||||
if (this->offset == 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Peek the next entry header. */
|
||||
ArchiveEntryHeader header;
|
||||
R_TRY(this->Peek(&header, sizeof(header)));
|
||||
R_TRY(header.Validate());
|
||||
|
||||
*out_key_size = header.key_size;
|
||||
*out_value_size = header.value_size;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ArchiveReader::ReadEntry(void *out_key, size_t key_size, void *out_value, size_t value_size) {
|
||||
/* This should only be called after ReadEntryCount. */
|
||||
if (this->offset == 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Read the next entry header. */
|
||||
ArchiveEntryHeader header;
|
||||
R_TRY(this->Read(&header, sizeof(header)));
|
||||
R_TRY(header.Validate());
|
||||
|
||||
/* Key size and Value size must be correct. */
|
||||
if (key_size != header.key_size || value_size != header.value_size) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
R_ASSERT(this->Read(out_key, key_size));
|
||||
R_ASSERT(this->Read(out_value, value_size));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
/* Writer functionality. */
|
||||
Result ArchiveWriter::Write(const void *src, size_t size) {
|
||||
/* Bounds check. */
|
||||
if (this->offset + size > this->buffer.GetSize() || this->offset + size <= this->offset) {
|
||||
return ResultKvdbInvalidKeyValue;
|
||||
}
|
||||
|
||||
std::memcpy(this->buffer.Get() + this->offset, src, size);
|
||||
this->offset += size;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void ArchiveWriter::WriteHeader(size_t entry_count) {
|
||||
/* This should only be called at start of write. */
|
||||
if (this->offset != 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
ArchiveHeader header = ArchiveHeader::Make(entry_count);
|
||||
R_ASSERT(this->Write(&header, sizeof(header)));
|
||||
}
|
||||
|
||||
void ArchiveWriter::WriteEntry(const void *key, size_t key_size, const void *value, size_t value_size) {
|
||||
/* This should only be called after writing header. */
|
||||
if (this->offset == 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
ArchiveEntryHeader header = ArchiveEntryHeader::Make(key_size, value_size);
|
||||
R_ASSERT(this->Write(&header, sizeof(header)));
|
||||
R_ASSERT(this->Write(key, key_size));
|
||||
R_ASSERT(this->Write(value, value_size));
|
||||
}
|
||||
|
||||
/* Size helper functionality. */
|
||||
ArchiveSizeHelper::ArchiveSizeHelper() : size(sizeof(ArchiveHeader)) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
void ArchiveSizeHelper::AddEntry(size_t key_size, size_t value_size) {
|
||||
this->size += sizeof(ArchiveEntryHeader) + key_size + value_size;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user