From 6ed8f5151719fbc14ec0ac6d28a346d1f74cf2ca Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Fri, 2 Jan 2026 22:42:31 +0100 Subject: Initial commit --- src/io.cc | 358 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 358 insertions(+) create mode 100644 src/io.cc (limited to 'src/io.cc') diff --git a/src/io.cc b/src/io.cc new file mode 100644 index 0000000..660c5f7 --- /dev/null +++ b/src/io.cc @@ -0,0 +1,358 @@ +#include "io.hh" + +#include "unique_fd.hh" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace io { + +namespace { + +class BasicReader : public Reader { + public: + explicit BasicReader(unique_fd fd) : fd_(std::move(fd)) {} + + [[nodiscard]] + std::expected read(void* dst, size_t max) override { + ssize_t ret = ::read( + fd_.get(), dst, + std::min(static_cast(std::numeric_limits::max()), + max)); + if (ret < 0) { + switch (errno) { + case EINTR: + return read(dst, max); + default: + return std::unexpected(ReadError::Error); + } + } else if (ret == 0 && max > 0) { + return std::unexpected(ReadError::Eof); + } + offset_ += ret; + return ret; + } + + [[nodiscard]] + std::expected skip(size_t max) override { + off_t ret; + if (sizeof(size_t) > sizeof(off_t)) { + ret = lseek( + fd_.get(), + // NOLINTNEXTLINE(bugprone-narrowing-conversions) + std::min(static_cast(std::numeric_limits::max()), max), + SEEK_CUR); + } else { + ret = lseek(fd_.get(), static_cast(max), SEEK_CUR); + } + if (ret < 0) { + return std::unexpected(ReadError::Error); + } + // Don't want skip to go past (cached) file end. + if (!size_.has_value() || ret >= size_.value()) { + // When going past end, double check that it still is the end. + off_t ret2 = lseek(fd_.get(), 0, SEEK_END); + if (ret2 < 0) { + // We're screwed, but try to go back to original position and then + // return error. + size_.reset(); + lseek(fd_.get(), offset_, SEEK_SET); + return std::unexpected(ReadError::Error); + } + size_ = ret2; + if (ret >= ret2) { + auto distance = ret2 - offset_; + offset_ = ret2; + if (distance == 0 && max > 0) + return std::unexpected(ReadError::Eof); + return distance; + } + // Seek back to where we should be + if (lseek(fd_.get(), ret, SEEK_SET) < 0) { + return std::unexpected(ReadError::Error); + } + } + auto distance = ret - offset_; + offset_ = ret; + return distance; + } + + [[nodiscard]] + int raw_fd() const override { + return fd_.get(); + } + + private: + unique_fd fd_; + off_t offset_{0}; + std::optional size_; +}; + +class MemoryReader : public Reader { + public: + MemoryReader(void* ptr, size_t size) : ptr_(ptr), size_(size) {} + + [[nodiscard]] + std::expected read(void* dst, size_t max) override { + size_t avail = size_ - offset_; + if (avail == 0 && max > 0) + return std::unexpected(io::ReadError::Eof); + size_t ret = std::min(max, avail); + memcpy(dst, reinterpret_cast(ptr_) + offset_, ret); + offset_ += ret; + return ret; + } + + [[nodiscard]] + std::expected skip(size_t max) override { + size_t avail = size_ - offset_; + size_t ret = std::min(max, avail); + offset_ += ret; + return ret; + } + + [[nodiscard]] + int raw_fd() const override { + return -1; + } + + protected: + void* ptr_; + size_t const size_; + + private: + size_t offset_{0}; +}; + +class MmapReader : public MemoryReader { + public: + MmapReader(unique_fd fd, void* ptr, size_t size) + : MemoryReader(ptr, size), fd_(std::move(fd)) {} + + ~MmapReader() override { munmap(ptr_, size_); } + + [[nodiscard]] + int raw_fd() const override { + return fd_.get(); + } + + private: + unique_fd fd_; +}; + +class StringReader : public MemoryReader { + public: + explicit StringReader(std::string data) + : MemoryReader(nullptr, data.size()), data_(std::move(data)) { + ptr_ = data_.data(); + } + + private: + std::string data_; +}; + +class BasicWriter : public Writer { + public: + explicit BasicWriter(unique_fd fd) : fd_(std::move(fd)) {} + + [[nodiscard]] + std::expected write(void const* dst, + size_t size) override { + ssize_t ret = ::write( + fd_.get(), dst, + std::min(static_cast(std::numeric_limits::max()), + size)); + if (ret < 0) { + switch (errno) { + case EINTR: + return write(dst, size); + default: + return std::unexpected(WriteError::Error); + } + } else if (ret == 0 && size > 0) { + return std::unexpected(WriteError::Error); + } + return ret; + } + + [[nodiscard]] + std::expected close() override { + if (::close(fd_.release()) == 0) + return {}; + return std::unexpected(WriteError::Error); + } + + [[nodiscard]] + int raw_fd() const override { + return fd_.get(); + } + + private: + unique_fd fd_; +}; + +} // namespace + +std::expected Reader::repeat_read(void* dst, size_t max) { + auto ret = read(dst, max); + if (!ret.has_value() || ret.value() == max) + return ret; + + char* d = reinterpret_cast(dst); + size_t offset = ret.value(); + while (true) { + ret = read(d + offset, max - offset); + if (!ret.has_value()) + break; + offset += ret.value(); + if (offset == max) + break; + } + return offset; +} + +std::expected Reader::repeat_skip(size_t max) { + auto ret = skip(max); + if (!ret.has_value() || ret.value() == max) + return ret; + + size_t offset = ret.value(); + while (true) { + ret = skip(max - offset); + if (!ret.has_value()) + break; + offset += ret.value(); + if (offset == max) + break; + } + return offset; +} + +std::expected Writer::repeat_write(void const* dst, + size_t size) { + auto ret = write(dst, size); + if (!ret.has_value() || ret.value() == size) + return ret; + + char const* d = reinterpret_cast(dst); + size_t offset = ret.value(); + while (true) { + ret = write(d + offset, size - offset); + if (!ret.has_value()) + break; + offset += ret.value(); + if (offset == size) + break; + } + return offset; +} + +std::expected, OpenError> open( + const std::string& file_path) { + return openat(AT_FDCWD, file_path); +} + +std::expected, OpenError> openat( + int dirfd, const std::string& file_path) { + unique_fd fd(::openat(dirfd, file_path.c_str(), O_RDONLY)); + if (fd) { + struct stat buf; + if (fstat(fd.get(), &buf) == 0) { + if (std::cmp_less_equal(buf.st_size, + std::numeric_limits::max())) { + auto size = static_cast(buf.st_size); + void* ptr = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd.get(), 0); + if (ptr != MAP_FAILED) { + return std::make_unique(std::move(fd), ptr, size); + } + } + } + return std::make_unique(std::move(fd)); + } + OpenError err; + switch (errno) { + case EINTR: + return openat(dirfd, file_path); + case EACCES: + err = OpenError::NoAccess; + break; + case ENOENT: + err = OpenError::NoSuchFile; + break; + default: + err = OpenError::Error; + break; + } + return std::unexpected(err); +} + +std::unique_ptr memory(std::string data) { + return std::make_unique(std::move(data)); +} + +std::expected, CreateError> create( + const std::string& file_path, bool replace_existing) { + return createat(AT_FDCWD, file_path, replace_existing); +} + +std::expected, CreateError> createat( + int dirfd, const std::string& file_path, bool replace_existing) { + int flags = O_WRONLY | O_CREAT; + if (replace_existing) { + flags |= O_TRUNC; + } else { + flags |= O_EXCL; + } + unique_fd fd(::openat(dirfd, file_path.c_str(), flags, 0666)); + if (fd) { + return std::make_unique(std::move(fd)); + } + CreateError err; + switch (errno) { + case EINTR: + return createat(dirfd, file_path, replace_existing); + case EACCES: + err = CreateError::NoAccess; + break; + case EEXIST: + err = CreateError::Exists; + break; + default: + err = CreateError::Error; + break; + } + return std::unexpected(err); +} + +std::expected, std::unique_ptr>, + PipeError> +pipe() { + int fds[2]; + if (::pipe(fds) == 0) { + return std::make_pair(reader_from_raw(fds[0]), writer_from_raw(fds[1])); + } + return std::unexpected(PipeError::Error); +} + +std::unique_ptr reader_from_raw(int fd) { + return std::make_unique(unique_fd{fd}); +} + +std::unique_ptr writer_from_raw(int fd) { + return std::make_unique(unique_fd{fd}); +} + +} // namespace io -- cgit v1.2.3-70-g09d2