#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; } 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; } 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_); } 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_; }; } // 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, 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)); } } // namespace io