Skip to content

Commit

Permalink
Basic file-writer implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
asoffer committed Feb 23, 2024
1 parent 6a77d6a commit 443ccd5
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 3 deletions.
29 changes: 26 additions & 3 deletions nth/io/writer/BUILD
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
package(default_visibility = ["//visibility:public"])

cc_library(
name = "writer",
hdrs = ["writer.h"],
name = "file",
hdrs = ["file.h"],
srcs = ["file.cc"],
deps = [
"//nth/utility:bytes",
":writer",
"//nth/io:file_path",
"//nth/process/syscall:lseek",
"//nth/process/syscall:open",
"//nth/process/syscall:write",
],
)

cc_test(
name = "file_test",
srcs = ["file_test.cc"],
deps = [
":file",
":test",
"//nth/test:main",
]
)

cc_library(
name = "string",
hdrs = ["string.h"],
Expand All @@ -34,3 +49,11 @@ cc_library(
"//nth/test",
],
)

cc_library(
name = "writer",
hdrs = ["writer.h"],
deps = [
"//nth/utility:bytes",
],
)
40 changes: 40 additions & 0 deletions nth/io/writer/file.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#include "nth/io/writer/file.h"

#include "nth/debug/debug.h"
#include "nth/process/syscall/lseek.h"
#include "nth/process/syscall/open.h"
#include "nth/process/syscall/write.h"

namespace nth::io {

std::optional<file_writer> file_writer::try_open(file_path const &f,
int flags) {
int file_descriptor = nth::sys::open(f.path().c_str(), flags);
if (file_descriptor > 0) {
return file_writer(file_descriptor);
} else {
return std::nullopt;
}
}

std::optional<file_writer::cursor_type> file_writer::allocate(size_t) {
NTH_UNIMPLEMENTED("This action is not supported.");
}

file_writer::cursor_type file_writer::cursor() const {
off_t result = nth::sys::lseek(file_descriptor_, 0, SEEK_CUR);
NTH_REQUIRE((v.debug), result != off_t{-1});
return result;
}

bool file_writer::write(std::span<std::byte const> data) {
if (data.empty()) { return true; }
ssize_t result = nth::sys::write(file_descriptor_, data.data(), data.size());
return result != -1;
}

bool file_writer::write_at(cursor_type, std::span<std::byte const>) {
NTH_UNIMPLEMENTED("This action is not supported.");
}

} // namespace nth::io
49 changes: 49 additions & 0 deletions nth/io/writer/file.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#ifndef NTH_IO_WRITER_FILE_H
#define NTH_IO_WRITER_FILE_H

#include <fcntl.h>

#include <optional>
#include <span>

#include "nth/io/file_path.h"
#include "nth/io/writer/writer.h"

namespace nth::io {

// Writes data to a file referenced by the writer.
struct file_writer {
enum class flags : int {
// If specified, the file will be created if it does not exist. If not
// specified, `try_open` will return `std::nullopt` if the file does not
// exist.
create = O_CREAT,
};
using enum flags;

using cursor_type = ptrdiff_t;

static std::optional<file_writer> try_open(
file_path const &f, std::same_as<flags> auto... flags) {
return try_open(f, (O_WRONLY | ... | static_cast<int>(flags)));
}

std::optional<cursor_type> allocate(size_t n);
cursor_type cursor() const;

bool write(std::span<std::byte const> data);

bool write_at(cursor_type c, std::span<std::byte const> data);

private:
static std::optional<file_writer> try_open(file_path const &f, int flags);

explicit file_writer(int file_descriptor)
: file_descriptor_(file_descriptor) {}

int file_descriptor_;
};

} // namespace nth::io

#endif // NTH_IO_WRITER_FILE_H
124 changes: 124 additions & 0 deletions nth/io/writer/file_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#include "nth/io/writer/file.h"

#include <unistd.h>

#include "nth/io/writer/test.h"
#include "nth/process/syscall/lseek.h"
#include "nth/process/syscall/open.h"
#include "nth/process/syscall/write.h"
#include "nth/test/test.h"

NTH_FAKE_IMPLEMENTATION(int, nth::sys::open, (char const *, ptr)(int, n));
NTH_FAKE_IMPLEMENTATION(ssize_t, nth::sys::write,
(int, fd)(void const *, ptr)(size_t, count));
NTH_FAKE_IMPLEMENTATION(off_t, nth::sys::lseek,
(int, fd)(off_t, offset)(int, whence));

namespace nth::io {
namespace {

NTH_TEST("file_writer/open/succeed",
std::same_as<file_writer::flags> auto... flags) {
std::optional path = file_path::try_construct("file");
NTH_ASSERT(path.has_value());
auto handle = nth::test::fake<nth::sys::open>.with(
+[](char const *, int) { return 17; });
std::optional r = file_writer::try_open(*path, flags...);
NTH_ASSERT(r.has_value());
}

NTH_TEST("file_writer/open/fails",
std::same_as<file_writer::flags> auto... flags) {
std::optional path = file_path::try_construct("file.txt");
NTH_ASSERT(path.has_value());
auto handle = nth::test::fake<nth::sys::open>.with(
+[](char const *, int) { return -1; });
std::optional r = file_writer::try_open(*path, flags...);
NTH_ASSERT(not r.has_value());
}

NTH_INVOKE_TEST("file_writer/open/*") {
// TODO: Test with no arguments.
co_yield file_writer::create;
}

std::optional<file_writer> try_open(std::string_view name) {
std::optional path = file_path::try_construct(name);
if (not path.has_value()) { return std::nullopt; }
return file_writer::try_open(*path);
}

NTH_TEST("file_writer/write/succeeds") {
auto open_handle = nth::test::fake<nth::sys::open>.with(
+[](char const *, int) { return 17; });
std::array<std::byte, 10> data = {
std::byte{'a'}, std::byte{'b'}, std::byte{'c'}, std::byte{'d'},
std::byte{'e'}, std::byte{'f'}, std::byte{'g'}, std::byte{'h'},
std::byte{'i'}, std::byte{'j'}};
std::string content;
auto write_handle = nth::test::fake<nth::sys::write>.with(
[&](int, void const *ptr, size_t count) -> ssize_t {
content.append(std::string_view(static_cast<char const *>(ptr), count));
return count;
});

std::optional r = try_open("file.txt");
NTH_ASSERT(r.has_value());
NTH_ASSERT(r->write(data));
NTH_EXPECT(content == "abcdefghij");
}

NTH_TEST("file_writer/write/empty") {
auto open_handle = nth::test::fake<nth::sys::open>.with(
+[](char const *, int) { return 17; });
auto write_handle = nth::test::fake<nth::sys::write>.with(
+[](int, void const *, size_t) -> ssize_t { std::abort(); });

std::optional r = try_open("file.txt");
NTH_ASSERT(r.has_value());

std::array<std::byte, 0> data;
NTH_EXPECT(r->write(data));
}

NTH_TEST("file_writer/write/partial-write") {
auto open_handle = nth::test::fake<nth::sys::open>.with(
+[](char const *, int) { return 17; });
std::array<std::byte, 10> data{
std::byte{'a'},
std::byte{'b'},
std::byte{'c'},
std::byte{'d'},
};
std::string content;
auto write_handle = nth::test::fake<nth::sys::write>.with(
[&](int, void const *ptr, size_t) -> ssize_t {
content.append(std::string_view(static_cast<char const *>(ptr), 4));
return 4;
});
std::optional r = try_open("file.txt");
NTH_ASSERT(r.has_value());
NTH_ASSERT(r->write(data));
NTH_EXPECT(content == "abcd");
}

NTH_TEST("file_writer/cursor/allocate") {
// TODO: Implement
// auto open_handle = nth::test::fake<nth::sys::open>.with(
// +[](char const *, int) { return 17; });
// auto lseek_handle = nth::test::fake<nth::sys::lseek>.with(
// [n = off_t{}](int, off_t offset, int) mutable -> off_t {
// return std::exchange(n, n + offset);
// });

// std::optional r = try_open("file.txt");
// NTH_ASSERT(r.has_value());
// NTH_EXPECT(r->cursor() == 0);
// NTH_EXPECT(r->skip(4));
// NTH_EXPECT(r->cursor() == 4);
// NTH_EXPECT(r->skip(2));
// NTH_EXPECT(r->cursor() == 6);
}

} // namespace
} // namespace nth::io
10 changes: 10 additions & 0 deletions nth/process/syscall/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,13 @@ cc_library(
"//nth/debug:fakeable_function",
],
)

cc_library(
name = "write",
hdrs = ["write.h"],
srcs = ["write.cc"],
deps = [
"//nth/base:attributes",
"//nth/debug:fakeable_function",
],
)
16 changes: 16 additions & 0 deletions nth/process/syscall/write.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include "nth/process/syscall/write.h"

#include <unistd.h>

#include "nth/base/attributes.h"

namespace nth::sys {

NTH_REAL_IMPLEMENTATION(ssize_t, write,
(int, fd)(void const*, ptr)(size_t, count)) {
return ::write(fd, ptr, count);
}

} // namespace nth::sys


14 changes: 14 additions & 0 deletions nth/process/syscall/write.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#ifndef NTH_PROCESS_SYSCALL_WRITE_H
#define NTH_PROCESS_SYSCALL_WRITE_H

#include <sys/types.h>

#include "nth/debug/fakeable_function.h"

namespace nth::sys {

NTH_FAKEABLE(ssize_t, write, (int, fd)(void const*, ptr)(size_t, count));

} // namespace nth::sys

#endif // NTH_PROCESS_SYSCALL_WRITE_H

0 comments on commit 443ccd5

Please sign in to comment.