Tidy up turtle API

This commit is contained in:
kayomn 2023-03-02 20:46:25 +00:00
parent a44aed2fe3
commit 530535b2a9
2 changed files with 29 additions and 162 deletions

View File

@ -13,7 +13,6 @@ import coral.io;
using coral::closure;
using coral::io_error;
using coral::path;
using coral::slice;
using coral::unreachable;
using coral::usize;
@ -336,126 +335,10 @@ export namespace turtle {
};
/**
* Provides unmanaged access to a native directory.
* [coral::file_system] wrapper around native file system access to provide a managed and system-agnostic
* environment for performing file I/O.
*/
struct native_directory final : public coral::file_walker {
native_directory() = default;
/**
* Attempts to close any currently open directory.
*
* A [close_result] is returned containing either [close_result::ok] to indicate success or any other value to
* indicate an error. See [close_result] for more details.
*
* *Note*: failing to close should not be treated as a reason to retry the closing operation, and should instead
* be used to inform the end-user that the operation failed or that the process should exit.
*/
close_result close() {
if (closedir(this->dir) == 0) return close_result::io_unavailable;
return close_result::ok;
}
/**
* See [coral::file_walker::has_next].
*/
bool has_next() override {
return this->entry != nullptr;
}
/**
* Returns `true` if a directory is currently open, otherwise `false`.
*/
bool is_open() const {
return this->dir != nullptr;
}
/**
* See [coral::file_walker::next].
*/
coral::expected<path, io_error> next() override {
usize name_length {0};
constexpr usize name_max {sizeof(dirent::d_name) / sizeof(char)};
while ((name_length < name_max) && (this->entry->d_name[name_length] != 0)) name_length += 1;
path current_path {path{}.joined(slice{this->entry->d_name, name_length})};
errno = 0;
this->entry = readdir(this->dir);
if (this->entry == nullptr) switch (errno) {
case EBADF: return io_error::unavailable;
default: unreachable();
}
return current_path;
}
/**
* Attempts to open a native directory at `directory_path`.
*
* An [open_result] is returned containing either [open_result::ok] to indicate success or any other value to
* indicate an error. See [open_result] for more details.
*
* *Note*: the opened directory must be closed using [close] once no longer needed or the process will leak
* directory streams.
*
* *Note*: if a directory is currently open under the native directory, it will attempt to close it before
* proceeding with opening the next. This means that [open] is safe to call without first calling [close].
*
* *Note*: it is recommended to prefer performing file I/O via [sandboxed_fs] unless direct file access is
* required.
*/
open_result open(native_path const & directory_path) {
if (this->is_open()) {
open_result const result {close_to_open_result(this->close())};
if (result != open_result::ok) return result;
}
// No room for zero terminator.
errno = 0;
this->dir = opendir(directory_path.as_slice().as_chars().pointer);
if (!this->is_open()) switch (errno) {
case EACCES: return open_result::access_denied;
case EMFILE: case ENFILE: return open_result::too_many;
case ENOENT: case ENOTDIR: return open_result::not_found;
case ENOMEM: return open_result::out_of_memory;
}
errno = 0;
this->entry = readdir(this->dir);
if (this->entry == nullptr) switch (errno) {
case EBADF: {
if (this->is_open()) {
open_result const result {close_to_open_result(this->close())};
if (result != open_result::ok) return result;
}
return open_result::io_unavailable;
}
default: unreachable();
}
return open_result::ok;
}
private:
DIR * dir {nullptr};
dirent * entry {nullptr};
};
/**
* [coral::fs] wrapper around native file system access to provide a managed and system-agnostic environment for
* performing file I/O.
*/
struct sandboxed_fs : public coral::fs {
struct file_sandbox : public coral::file_system {
/**
* Permission flags that a [sandboxed_fs] may specify for restricting access to it.
*/
@ -468,16 +351,15 @@ export namespace turtle {
};
/**
* Constructs a sandbox located at `sandbox_path` with `sandbox_permissions` as the permissions given to users
* of it.
* Constructs a sandbox located at `path` with `sandbox_permissions` as the permissions given to users of it.
*/
sandboxed_fs(native_path const & sandbox_path, permissions const & access_permissions) {
this->path = sandbox_path;
file_sandbox(native_path const & path, permissions const & access_permissions) {
this->path = path;
this->access_permissions = access_permissions;
}
/**
* Returns a reference to a [sandboxed_fs] that provides access to the base directory which, on most systems, is
* Returns a reference to a [file_sandbox] that provides access to the base directory which, on most systems, is
* the current working directory.
*
* The base directory may be used to access things loose files created outside of the application, such as user-
@ -485,17 +367,17 @@ export namespace turtle {
*
* *Note*: The base file system does not permit being written to.
*/
static sandboxed_fs & base() {
static sandboxed_fs base_fs {"./", {
static file_sandbox & base() {
static file_sandbox base_sandbox {"./", {
.can_read = true,
.can_walk = true
}};
return base_fs;
return base_sandbox;
}
/**
* Returns a reference to a [sandboxed_fs] that operates as a temporary file store.
* Returns a reference to a [file_sandbox] that operates as a temporary file store.
*
* As the name implies, the existence of files that exist here are not guaranteed beyond the duration of an
* application run lifetime. The purpose of the temporary file store is to support transactional I/O
@ -503,13 +385,13 @@ export namespace turtle {
*
* *Note*: The temp file system does not permit being walked.
*/
static sandboxed_fs & temp() {
static sandboxed_fs base_fs {"/tmp", {
static file_sandbox & temp() {
static file_sandbox temp_sandbox {"/tmp", {
.can_read = true,
.can_write = false,
}};
return base_fs;
return temp_sandbox;
}
/**
@ -517,17 +399,16 @@ export namespace turtle {
*
* *Note*: this function will only work on sandboxes with the [permissions::can_read] flag enabled.
*/
void read_file(path const & target_path, closure<void(coral::file_reader &)> const & then) override {
void read_file(slice<char const> const & path, closure<void(coral::file_reader &)> const & then) override {
if (!this->access_permissions.can_read) return;
this->path.joined(target_path.as_slice()).and_then([&](native_path const & native_file_path) -> void {
this->path.joined(path).and_then([&](native_path const & native_file_path) -> void {
native_file file;
if (file.open(native_file_path, native_file::open_mode::read_only) != open_result::ok) return;
then(file);
// TODO: Error orphaned file handle!
if (file.close() != close_result::ok) return;
});
}
@ -537,18 +418,11 @@ export namespace turtle {
*
* *Note*: this function will only work on sandboxes with the [permissions::can_walk] flag enabled.
*/
void walk_files(path const & target_path, closure<void(coral::file_walker &)> const & then) override {
void walk_files(slice<char const> const & path, closure<void(coral::file_walker &)> const & then) override {
if (!this->access_permissions.can_walk) return;
this->path.joined(target_path.as_slice()).and_then([&](native_path const & native_directory_path) -> void {
native_directory directory;
if (directory.open(native_directory_path) == open_result::ok) return;
then(directory);
// TODO: Error orphaned file handle!
if (directory.close() != close_result::ok) return;
this->path.joined(path).and_then([&](native_path const & native_directory_path) -> void {
// TODO: Implement.
});
}
@ -557,17 +431,16 @@ export namespace turtle {
*
* *Note*: this function will only work on sandboxes with the [permissions::can_write] flag enabled.
*/
void write_file(path const & target_path, closure<void(coral::file_writer &)> const & then) override {
void write_file(slice<char const> const & path, closure<void(coral::file_writer &)> const & then) override {
if (!this->access_permissions.can_write) return;
this->path.joined(target_path.as_slice()).and_then([&](native_path const & native_file_path) -> void {
this->path.joined(path).and_then([&](native_path const & native_file_path) -> void {
native_file file;
if (file.open(native_file_path, native_file::open_mode::overwrite) != open_result::ok) return;
then(file);
// TODO: Error orphaned file handle!
if (file.close() != close_result::ok) return;
});
}
@ -583,12 +456,9 @@ export namespace turtle {
};
/**
* Returns a reference to the process-wide output device used for writing data out from to the wider system.
*
* This [coral::writer] is particularly useful for command-line tools which require communicating with another
* process via pipes or an end-user via the shell.
* Returns a reference to the process-wide error logging [coral::writer] used for recording errors.
*/
coral::writer & output() {
coral::writer & error_log() {
static struct : public coral::writer {
coral::expected<usize, io_error> write(slice<coral::u8 const> const & data) override {
coral::size const data_written {::write(STDOUT_FILENO, data.pointer, sizeof(coral::u8) * data.length)};

View File

@ -13,26 +13,23 @@ export namespace turtle {
};
struct event_loop {
void log(log_level level, coral::slice<char const> const & message) {
static_cast<void>(output().write(message.as_chars().as_bytes()));
static_cast<void>(output().write(coral::slice{"\n"}.as_bytes()));
}
bool poll() {
return false;
}
static int run(coral::path const & title, coral::closure<int(event_loop &)> execute) {
static int run(coral::slice<char const> const & title, coral::closure<int(event_loop &)> execute) {
event_loop loop{title};
return execute(loop);
}
private:
coral::path title;
static constexpr coral::usize title_max = 256;
event_loop(coral::path const & title) {
this->title = title;
coral::u8 title_buffer[256];
event_loop(coral::slice<char const> const & title) {
coral::copy(this->title_buffer, title.sliced(0, coral::min(title.length, title_max)).as_bytes());
}
};