#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