Dotfiles
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

692 lines
18 KiB

// ============================================================================
// kofd.hpp
// ko::fd
// (c) 2019 Taeyeon Mori <taeyeon at oro.sodimm.me>
// ============================================================================
// File descriptor functions
#pragma once
#include "kofs.hpp"
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/sendfile.h>
#include <sys/ioctl.h>
#include <sys/mount.h> // linux/fs.h includes linux/mount.h which overrides some of the things from sys/mount.h
#include <linux/fs.h>
#include <unistd.h>
#include <cstring>
#include <string>
#include <utility>
#include <optional>
// ==================================================================
namespace ko::fd {
// ------------------------------------------------------------------
// Working with file descriptors
/**
* Auto-close move-only filedescriptor wrapper
*/
class fd {
int _fd;
public:
fd() :
_fd(-1)
{}
fd(int fd) :
_fd(fd)
{}
fd(fd const &) = delete;
fd(fd &&o) :
_fd(o.move())
{}
fd &operator=(int fd) {
if (_fd >= 0)
::close(_fd);
_fd = fd;
return *this;
}
fd &operator=(fd &&o) {
if (_fd >= 0)
::close(_fd);
_fd = o.move();
return *this;
}
~fd() {
if (_fd >= 0)
::close(_fd);
}
/**
* Boolean operator
* @note This differs from a raw int fd
*/
operator bool() const {
return _fd >= 0;
}
/**
* Negation operator
* @note This differs from a raw int fd
*/
bool operator !() const {
return _fd < 0;
}
// Comparison
bool operator ==(int i) const {
return _fd == i;
}
bool operator !=(int i) const{
return _fd != i;
}
bool operator <(int i) const {
return _fd < i;
}
bool operator >(int i) const {
return _fd > i;
}
bool operator <=(int i) const {
return _fd <= i;
}
bool operator >=(int i) const {
return _fd >= i;
}
/**
* Get the raw int fd
* @note This is not allowed on temporaries
* @note Use move() instead to transfer ownership.
* @see move()
*/
operator int() & {
return _fd;
}
/**
* Disown this object
* @note
*/
int move() {
auto tmp = _fd;
_fd = -1;
return tmp;
}
/**
* Close the file descriptor early
*/
bool close() {
if (_fd < 0) return false;
if (::close(_fd) && errno != EBADF) return false;
_fd = -1;
return true;
}
/**
* Copy the file descriptor
*/
fd dup() {
return ::dup(_fd);
}
};
//-------------------------------------------------------------------
// Opening file descriptors
// @{
/**
* Open a file descriptor
* @param path The path
* @param flags The open(2) flags
* @param dirfd The directory fd \p path may be relative to
* @param cloexec Add O_CLOEXEC to \p flags
* @return A \c fd file descriptor
*/
fd open(const fs::cpath &path, long flags, int dirfd=AT_FDCWD, bool cloexec=true) {
return ::openat(dirfd, path, flags | (cloexec ? O_CLOEXEC : 0));
}
/**
* Open a file descriptor, creating the file if it doesn't exist
* @param path The path
* @param flags The open(2) flags
* @param mode The file mode to create with
* @param dirfd The directory fd \p path may be relative to
* @param cloexec Add O_CLOEXEC to \p flags
* @return A \c fd file descriptor
*/
fd open_creat(const fs::cpath &path, long flags, mode_t mode, int dirfd=AT_FDCWD, bool cloexec=true) {
return ::openat(dirfd, path, O_CREAT | flags | (cloexec ? O_CLOEXEC : 0), mode);
}
/**
* Open a directory file descriptor
* @param path The directory path
* @param dirfd The directory fd \p path may be relative to
* @return A \c fd directory file descriptor
*/
fd opendir(const fs::cpath &path, int dirfd=AT_FDCWD) {
return ::openat(dirfd, path, O_DIRECTORY|O_RDONLY|O_CLOEXEC);
}
/**
* Open a directory file descriptor with custom flags
* @param path The directory path
* @param flags The flags to pass to open(2)
* @param dirfd The directory fd \p path may be relative to
* @return A directory \c fd
*/
fd opendir2(const fs::cpath &path, long flags, int dirfd=AT_FDCWD) {
return ::openat(dirfd, path, flags|O_DIRECTORY);
}
// @}
//-------------------------------------------------------------------
// Checking properties
// @{
/**
* Check if a path exists
* @param path The path
* @param dirfd The directory fd \p path may be relative to
* @return true if path exists
*/
bool exists(const fs::cpath &path, int dirfd=AT_FDCWD) {
return !::faccessat(dirfd, path, F_OK, 0);
}
/**
* Check if a path is a directory
* @param path The path
* @param dirfd The directory fd \p path may be relative to
* @return true if path is a directory
*/
bool is_dir(const fs::cpath &path, int dirfd=AT_FDCWD) {
struct stat st;
if (::fstatat(dirfd, path, &st, 0))
return false;
return S_ISDIR(st.st_mode);
}
/**
* Read the target of a symbolic link
* @param path The symlink path
* @param dirfd The directory fd \p path may be relative to
* @return A fs::path. It is empty on error
*/
fs::path readlink(const fs::cpath &path, int dirfd=AT_FDCWD) {
constexpr auto static_bufsize = 4096;
char buf[static_bufsize];
auto sz = ::readlinkat(dirfd, path, buf, static_bufsize);
if (sz < 0)
return {};
if (sz < static_bufsize)
return {buf, buf + sz};
struct stat st;
if (::fstatat(dirfd, path, &st, AT_SYMLINK_NOFOLLOW))
return {};
auto extbuf = std::make_unique<char[]>(st.st_size);
sz = ::readlinkat(dirfd, path, extbuf.get(), sz);
if (sz < 0)
return {};
return {&extbuf[0], &extbuf[sz]};
}
/**
* Get the target if a file is a symbolic link or return the path as-is if it is something else
* @param path The path
* @param dirfd The directory fd \p path may be relative to
* @param notexist_ok Whether or not to return the path as-is if it doesn't exist (default=true)
* @return A fs::path, possibly relative to dirfd. It may be empty on error
*/
fs::path readlink_or_path(const fs::path &path, int dirfd=AT_FDCWD, bool notexist_ok=true) {
auto target = readlink(path, dirfd);
if (target.empty()) {
if (errno == EINVAL || (errno == ENOENT && notexist_ok))
return path;
else
return {};
}
// Make (relative) returned value relative to dirfd
if (target.is_relative())
return path.parent_path() / target;
return target;
}
/**
* Check if a directory is empty
*/
bool is_dir_empty(const fs::cpath &path, int dirfd=AT_FDCWD) {
auto fd = opendir(path, dirfd);
if (!fd)
return false;
auto dir = fs::dir_ptr(fd);
if (!dir)
return false;
errno = 0;
while (true) {
auto res = dir.readdir();
if (res == nullptr)
return errno == 0;
if (strcmp(".", res->d_name) && strcmp("..", res->d_name))
return false;
}
}
// @}
//-------------------------------------------------------------------
// Creating files and directories
// @{
/**
* Create a symbolic link
* @param target The link target.
* @param path The path of the new symlink
* @param dirfd The directory fd \p path may be relative to
* @return 0 on success
* @note target is relative to the directory containing the link, NOT dirfd
*/
int symlink(const fs::cpath &target, const fs::cpath &path, int dirfd=AT_FDCWD) {
return ::symlinkat(target, dirfd, path);
}
/**
* Create a directory
* @param path The new directory path
* @param mode The permissions to assign
* @param dirfd The directory fd \p path may be relative to
* @return 0 on success
*/
int mkdir(const fs::cpath &path, mode_t mode=0755, int dirfd=AT_FDCWD) {
return ::mkdirat(dirfd, path, mode);
}
/**
* Create all parent directories
* @param path The path of the innermost directory to create
* @param mode The permissions to assign
* @param dirfd The directory fd \p path may be relative to
* @return The number of directories created, or -1 on error
*/
int makedirs(const fs::path &path, mode_t mode=0755, int dirfd=AT_FDCWD) {
struct stat st;
// Treat empty path as .
// Check if exists
if (!fstatat(dirfd, path.empty() ? "." : path.c_str(), &st, 0)) {
// If directory, we're fine.
if (S_ISDIR(st.st_mode))
return 0;
// Else, this is an error
errno = ENOTDIR;
return -1;
}
// Propagate any error other than ENOENT
if (errno != ENOENT || path.empty())
return -1;
// Ensure parents
auto parents = makedirs(path.parent_path(), mode, dirfd);
// Actually create directory
if (mkdir(path, mode, dirfd))
return -1;
return parents + 1;
}
/**
* Create a file if it doesn't exist
* @param path The path of the file
* @param mode The permissions to assign if it has to be created
* @param dirfd The directory fd \p path may be relative to
* @return 0 on success
*/
int touch(const fs::cpath &path, mode_t mode=0755, int dirfd=AT_FDCWD) {
auto fd = open_creat(path, O_WRONLY, mode, dirfd);
return fd ? 0 : -1;
}
/**
* Remove a file
* @param path The path of the file to remove
* @param dirfd The directory fd \p may be relative to
* @return 0 on success
*/
int unlink(const fs::cpath &path, int dirfd=AT_FDCWD) {
return ::unlinkat(dirfd, path, 0);
}
/**
* Remove a directory
* @param path The path of the directory to remove
* @param dirfd The directory fd \p may be relative to
* @return 0 on success
*/
int rmdir(const fs::cpath &path, int dirfd=AT_FDCWD) {
return ::unlinkat(dirfd, path, AT_REMOVEDIR);
}
/**
* Copy a symbolic link
* @param from The source symbolic link path
* @param to The target symbolic link path (must not exist)
* @param from_dirfd The directory fd \p from may be relative to
* @param dirfd The directory fd \p to may be relative to
* @return 0 on success
*/
int copy_symlink(const fs::cpath &from, fs::cpath to,
int from_dirfd=AT_FDCWD, int dirfd=AT_FDCWD) {
auto target = readlink(from, from_dirfd);
return ::symlinkat(target.c_str(), dirfd, to);
}
// @}
//-------------------------------------------------------------------
// File descriptor I/O
// @{
// Read
/**
* Read until \p size bytes have been read or an error has been encoutnered
* @param fd A file descriptor
* @param dest The destination buffer
* @param size The desired number of bytes read
* @return The actual number of bytes read
* @note If returned value != \p size, errno will be set. errno == 0 indicates EOF
*/
size_t read(int fd, char *dest, size_t size) {
size_t have = 0;
while (have < size) {
auto got = ::read(fd, dest + have, size - have);
if (got == 0) {
errno = 0;
break;
} else if (got < 0)
break;
have += got;
}
return have;
}
/**
* Read until \p size bytes have been read or an error has been encoutnered
* @param fd A file descriptor
* @param size The desired number of bytes read
* @return The resulting string
* @note If returned string.size() != \p size, errno will be set. errno == 0 indicates EOF
*/
std::string read(int fd, size_t size) {
auto buf = std::string(size, 0);
buf.resize(read(fd, buf.data(), size));
return buf;
}
/**
* Read until \p size bytes have been read, an error has been encoutnered, or the timeout is hit
* @param fd A file descriptor
* @param dest The destination buffer
* @param size The desired number of bytes read
* @param timeout The timeout that must not be exceeded between chunk reads
* @return The actual number of bytes read
* @note If returned value != \p size, errno will be set. errno == 0 indicates EOF.
* Timeout is indicated by ETIMEDOUT.
*/
size_t read(int fd, char *dest, size_t size, timeval timeout) {
size_t have = 0;
auto fds = fd_set();
FD_ZERO(&fds);
FD_SET(fd, &fds);
while (have < size) {
auto rv = select(fd + 1, &fds, nullptr, nullptr, &timeout);
if (rv == 0) {
errno = ETIMEDOUT;
break;
} else if (rv < 0) {
break;
}
auto got = ::read(fd, dest + have, size - have);
if (got == 0) {
errno = 0;
break;
} else if (got < 0)
break;
have += got;
}
return have;
}
/**
* Read until \p size bytes have been read, an error has been encoutnered, or the timeout is hit
* @param fd A file descriptor
* @param size The desired number of bytes read
* @param timeout The timeout that must not be exceeded between chunk reads
* @return The resulting string
* @note If returned value != \p size, errno will be set. errno == 0 indicates EOF
* Timeout is indicated by ETIMEDOUT.
*/
std::string read(int fd, size_t size, timeval timeout) {
auto buf = std::string(size, 0);
buf.resize(read(fd, buf.data(), size, timeout));
return buf;
}
/**
* Read a POD type from a file descriptor
* @tparam T The type
* @param fd The file descriptor
* @return The object on success, std::nullopt on failure
* @note If std::nullopt is returned, errno will be set.
*/
template <typename T>
std::optional<T> read_bin(int fd) {
char buf[sizeof(T)];
if (read(fd, buf, sizeof(T)) == sizeof(T))
return *reinterpret_cast<T*>(buf);
else
return std::nullopt;
}
// Write
/**
* Write all bytes to a file descriptor unless an error occurs (blocking)
* @param fd The file descriptor
* @param buf The source buffer
* @param size The number of bytes to write
* @return The number of bytes written
* @note If returned value != \p size, errno will be set.
*/
size_t write(int fd, const char *buf, size_t size) {
size_t have = 0;
while (have < size) {
auto got = ::write(fd, buf + have, size - have);
if (got == 0) {
errno = 0;
break;
} else if (got < 0)
break;
have += got;
}
return have;
}
/**
* Write all bytes to a file descriptor unless an error occurs (blocking)
* @param fd The file descriptor
* @param s A string to write
* @return The number of bytes written
* @note If returned value != \p s.size(), errno will be set.
*/
size_t write(int fd, const std::string &s) {
return write(fd, s.data(), s.size());
}
/**
* Write a POD object to a file descriptor
* @tparam T The POD type
* @param fd The file descriptor
* @param v The object
* @return The number of bytes written
* @note If returned value != sizeof(T), errno will be set.
*/
template <typename T>
size_t write_bin(int fd, const T &v) {
return write(fd, reinterpret_cast<const char*>(&v), sizeof(v));
}
// Shortcuts
/**
* Read a file from disk
* @param path The file path
* @param dirfd The directory fd \p path may be relative to
* @param max The maximum number of bytes to read
* @return A pair of (data read, errno)
* @note If data.size() == max, more data may be available.
*/
std::pair<std::string, int> cat(const fs::cpath &path, int dirfd=AT_FDCWD, size_t max=1024) {
auto fd = open(path, O_RDONLY, dirfd);
if (!fd)
return {};
auto r = read(fd, max);
if (r.size() < max)
return {r, errno};
return {r, 0};
}
/**
* Write a file to disk
* @param s The data to write
* @param path The path to write to
* @param mode The mode to create the file with, if neccessary
* @param dirfd The directory fd \p path may be relative to
*/
bool dump(const std::string &s, const fs::cpath &path, mode_t mode, int dirfd=AT_FDCWD) {
auto fd = open_creat(path, O_WRONLY, mode, dirfd);
if (!fd)
return -1;
return write(fd, s) == s.size();
}
// @}
//-------------------------------------------------------------------
// Copying Files
// @{
/**
* Naively copy data between file descriptors
* @param fs The source file descriptor
* @param fd The destination file descriptor
* @param len The number of bytes to copy
*/
bool fcopy_raw(int fs, int fd, size_t len) {
constexpr size_t bufsz = 8192;
char buf[bufsz];
do {
auto target = std::min(len, bufsz);
auto nread = read(fs, buf, target);
if (nread < target && errno != 0)
return false;
auto written = write(fd, buf, nread);
if (written < nread)
return false;
if (nread < target)
return true;
len -= nread;
} while (len > 0);
return true;
}
/**
* Copy data between file descriptors
* @param fs The source file descriptor
* @param fd The destination file descriptor
* @param len The number of bytes to copy
* @return false on failure with errno set
* @note This attempts to use copy_file_range(2) and sendfile(2)
* before falling back to fcopy_raw
*/
bool fcopy(int fs, int fd, size_t len) {
while (len > 0) {
auto r = ::copy_file_range(fs, NULL, fd, NULL, len, 0);
if (r < 0) {
if (errno == ENOSYS || errno == EXDEV || errno == EINVAL)
break;
return fcopy_raw(fs, fd, len);
}
len -= r;
}
while (len > 0) {
auto r = ::sendfile(fd, fs, NULL, len);
if (r < 0)
return fcopy_raw(fs, fd, len);
len -= r;
}
return true;
}
/**
* Copy a file
* @param src The path to copy from
* @param dst The path to copy to
* @param src_dir The directory fd \p src may be relative to
* @param dst_dir The directory fd \p dst may be relative to
* @return false on failure with errno set
* @note This variant will only try to preserve the file mode, no other attributes
* @note Note that this function takes two separate directory fds
* @note This will use reflink/FICLONE if supported.
*/
bool copy0(const fs::cpath &src, const fs::cpath &dst, int src_dir=AT_FDCWD, int dst_dir=AT_FDCWD) {
struct stat st;
if (::fstatat(src_dir, src, &st, 0))
return false;
auto fs = open(src, O_RDONLY, src_dir);
if (!fs)
return false;
auto fd = open_creat(dst, O_WRONLY, st.st_mode, dst_dir);
if (!fd)
return false;
// Try reflink
#ifdef FICLONE
int ret = ::ioctl(fd, FICLONE, (int)fs);
if (ret != -1)
return ret == st.st_size;
#endif
return fcopy(fs, fd, st.st_size);
}
// @}
} // namespace ko::fd