1
0
mirror of https://github.com/Zygo/bees.git synced 2025-05-17 13:25:45 +02:00
bees/lib/fd.cc
2016-11-17 12:12:13 -05:00

576 lines
14 KiB
C++

#include "crucible/chatter.h"
#include "crucible/error.h"
#include "crucible/fd.h"
#include "crucible/ntoa.h"
#include "crucible/string.h"
#include <cstdio>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <vector>
#include <sys/mman.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
namespace crucible {
using namespace std;
static const struct bits_ntoa_table o_flags_table[] = {
NTOA_TABLE_ENTRY_BITS(O_APPEND),
NTOA_TABLE_ENTRY_BITS(O_ASYNC),
NTOA_TABLE_ENTRY_BITS(O_CLOEXEC),
NTOA_TABLE_ENTRY_BITS(O_CREAT),
NTOA_TABLE_ENTRY_BITS(O_DIRECT),
NTOA_TABLE_ENTRY_BITS(O_DIRECTORY),
NTOA_TABLE_ENTRY_BITS(O_EXCL),
NTOA_TABLE_ENTRY_BITS(O_LARGEFILE),
NTOA_TABLE_ENTRY_BITS(O_NOATIME),
NTOA_TABLE_ENTRY_BITS(O_NOCTTY),
NTOA_TABLE_ENTRY_BITS(O_NOFOLLOW),
NTOA_TABLE_ENTRY_BITS(O_NONBLOCK),
NTOA_TABLE_ENTRY_BITS(O_NDELAY), // NONBLOCK will prevent this
NTOA_TABLE_ENTRY_BITS(O_SYNC),
NTOA_TABLE_ENTRY_BITS(O_TRUNC),
// These aren't really bit values
NTOA_TABLE_ENTRY_BITS(O_RDWR),
NTOA_TABLE_ENTRY_BITS(O_WRONLY),
NTOA_TABLE_ENTRY_BITS(O_RDONLY),
NTOA_TABLE_ENTRY_END(),
};
static const struct bits_ntoa_table o_mode_table[] = {
NTOA_TABLE_ENTRY_BITS(S_IFMT),
NTOA_TABLE_ENTRY_BITS(S_IFSOCK),
NTOA_TABLE_ENTRY_BITS(S_IFLNK),
NTOA_TABLE_ENTRY_BITS(S_IFREG),
NTOA_TABLE_ENTRY_BITS(S_IFBLK),
NTOA_TABLE_ENTRY_BITS(S_IFDIR),
NTOA_TABLE_ENTRY_BITS(S_IFCHR),
NTOA_TABLE_ENTRY_BITS(S_IFIFO),
NTOA_TABLE_ENTRY_BITS(S_ISUID),
NTOA_TABLE_ENTRY_BITS(S_ISGID),
NTOA_TABLE_ENTRY_BITS(S_ISVTX),
NTOA_TABLE_ENTRY_BITS(S_IRWXU),
NTOA_TABLE_ENTRY_BITS(S_IRUSR),
NTOA_TABLE_ENTRY_BITS(S_IWUSR),
NTOA_TABLE_ENTRY_BITS(S_IXUSR),
NTOA_TABLE_ENTRY_BITS(S_IRWXG),
NTOA_TABLE_ENTRY_BITS(S_IRGRP),
NTOA_TABLE_ENTRY_BITS(S_IWGRP),
NTOA_TABLE_ENTRY_BITS(S_IXGRP),
NTOA_TABLE_ENTRY_BITS(S_IRWXO),
NTOA_TABLE_ENTRY_BITS(S_IROTH),
NTOA_TABLE_ENTRY_BITS(S_IWOTH),
NTOA_TABLE_ENTRY_BITS(S_IXOTH),
NTOA_TABLE_ENTRY_END(),
};
string o_flags_ntoa(int flags)
{
return bits_ntoa(flags, o_flags_table);
}
string o_mode_ntoa(mode_t mode)
{
return bits_ntoa(mode, o_mode_table);
}
void
IOHandle::close()
{
CHATTER_TRACE("close fd " << m_fd << " in " << this);
if (m_fd >= 0) {
// Assume that ::close always destroys the FD, even if errors are encountered;
int closing_fd = m_fd;
m_fd = -1;
CHATTER_UNWIND("closing fd " << closing_fd << " in " << this);
DIE_IF_MINUS_ONE(::close(closing_fd));
}
}
IOHandle::~IOHandle()
{
CHATTER_TRACE("destroy fd " << m_fd << " in " << this);
if (m_fd >= 0) {
catch_all([&](){
close();
});
}
}
IOHandle::IOHandle() :
m_fd(-1)
{
CHATTER_TRACE("open fd " << m_fd << " in " << this);
}
IOHandle::IOHandle(int fd) :
m_fd(fd)
{
CHATTER_TRACE("open fd " << m_fd << " in " << this);
}
int
IOHandle::release_fd()
{
CHATTER_TRACE("release fd " << m_fd << " in " << this);
int rv = m_fd;
m_fd = -1;
return rv;
}
// XXX: necessary? useful?
template <>
struct ChatterTraits<Fd> {
Chatter &operator()(Chatter &c, const Fd &fd) const
{
c << "Fd {this=" << &fd << " fd=" << static_cast<int>(fd) << "}";
return c;
}
};
int
open_or_die(const string &file, int flags, mode_t mode)
{
int fd(::open(file.c_str(), flags, mode));
if (fd < 0) {
THROW_ERRNO("open: name '" << file << "' mode " << oct << setfill('0') << setw(3) << mode << " flags " << o_flags_ntoa(flags));
}
return fd;
}
int
openat_or_die(int dir_fd, const string &file, int flags, mode_t mode)
{
int fd(::openat(dir_fd, file.c_str(), flags, mode));
if (fd < 0) {
THROW_ERRNO("openat: dir_fd " << dir_fd << " " << name_fd(dir_fd) << " name '" << file << "' mode " << oct << setfill('0') << setw(3) << mode << " flags " << o_flags_ntoa(flags));
}
return fd;
}
static const struct bits_ntoa_table mmap_prot_table[] = {
NTOA_TABLE_ENTRY_BITS(PROT_EXEC),
NTOA_TABLE_ENTRY_BITS(PROT_READ),
NTOA_TABLE_ENTRY_BITS(PROT_WRITE),
NTOA_TABLE_ENTRY_BITS(PROT_NONE),
NTOA_TABLE_ENTRY_END(),
};
string mmap_prot_ntoa(int prot)
{
return bits_ntoa(prot, mmap_prot_table);
}
static const struct bits_ntoa_table mmap_flags_table[] = {
NTOA_TABLE_ENTRY_BITS(MAP_SHARED),
NTOA_TABLE_ENTRY_BITS(MAP_PRIVATE),
NTOA_TABLE_ENTRY_BITS(MAP_32BIT),
NTOA_TABLE_ENTRY_BITS(MAP_ANONYMOUS),
NTOA_TABLE_ENTRY_BITS(MAP_DENYWRITE),
NTOA_TABLE_ENTRY_BITS(MAP_EXECUTABLE),
#if MAP_FILE
NTOA_TABLE_ENTRY_BITS(MAP_FILE),
#endif
NTOA_TABLE_ENTRY_BITS(MAP_FIXED),
NTOA_TABLE_ENTRY_BITS(MAP_GROWSDOWN),
NTOA_TABLE_ENTRY_BITS(MAP_HUGETLB),
NTOA_TABLE_ENTRY_BITS(MAP_LOCKED),
NTOA_TABLE_ENTRY_BITS(MAP_NONBLOCK),
NTOA_TABLE_ENTRY_BITS(MAP_NORESERVE),
NTOA_TABLE_ENTRY_BITS(MAP_POPULATE),
NTOA_TABLE_ENTRY_BITS(MAP_STACK),
#ifdef MAP_UNINITIALIZED
NTOA_TABLE_ENTRY_BITS(MAP_UNINITIALIZED),
#endif
NTOA_TABLE_ENTRY_END(),
};
string mmap_flags_ntoa(int flags)
{
return bits_ntoa(flags, mmap_flags_table);
}
void *
mmap_or_die(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
{
void *rv = mmap(addr, length, prot, flags, fd, offset);
if (rv == MAP_FAILED) {
THROW_ERRNO("mmap: addr " << addr << " length " << length
<< " prot " << mmap_prot_ntoa(prot)
<< " flags " << mmap_flags_ntoa(flags)
<< " fd " << fd << " offset " << offset);
}
return rv;
}
void
rename_or_die(const string &from, const string &to)
{
if (::rename(from.c_str(), to.c_str())) {
THROW_ERRNO("rename: " << from << " -> " << to);
}
}
void
renameat_or_die(int fromfd, const string &frompath, int tofd, const string &topath)
{
if (::renameat(fromfd, frompath.c_str(), tofd, topath.c_str())) {
THROW_ERRNO("renameat: " << name_fd(fromfd) << "/" << frompath
<< " -> " << name_fd(tofd) << "/" << topath);
}
}
string
socket_domain_ntoa(int domain)
{
static const bits_ntoa_table table[] = {
NTOA_TABLE_ENTRY_ENUM(AF_UNIX),
NTOA_TABLE_ENTRY_ENUM(AF_LOCAL), // probably the same as AF_UNIX
NTOA_TABLE_ENTRY_ENUM(AF_INET),
NTOA_TABLE_ENTRY_ENUM(AF_INET6),
NTOA_TABLE_ENTRY_ENUM(AF_PACKET),
NTOA_TABLE_ENTRY_END()
};
return bits_ntoa(domain, table);
}
string
socket_type_ntoa(int type)
{
static const bits_ntoa_table table[] = {
NTOA_TABLE_ENTRY_BITS(SOCK_CLOEXEC),
NTOA_TABLE_ENTRY_BITS(SOCK_NONBLOCK),
NTOA_TABLE_ENTRY_ENUM(SOCK_STREAM),
NTOA_TABLE_ENTRY_ENUM(SOCK_DGRAM),
NTOA_TABLE_ENTRY_ENUM(SOCK_RAW),
NTOA_TABLE_ENTRY_ENUM(SOCK_PACKET),
NTOA_TABLE_ENTRY_END()
};
return bits_ntoa(type, table);
}
string
socket_protocol_ntoa(int protocol)
{
static const bits_ntoa_table table[] = {
// an empty table just prints the number
NTOA_TABLE_ENTRY_END()
};
return bits_ntoa(protocol, table);
}
Fd
socket_or_die(int domain, int type, int protocol)
{
Fd fd(::socket(domain, type, protocol));
if (fd < 0) {
THROW_ERRNO("socket: domain " << socket_domain_ntoa(domain)
<< " type " << socket_type_ntoa(type)
<< " protocol " << socket_protocol_ntoa(protocol));
}
return fd;
}
void
write_or_die_partial(int fd, const void *buf, size_t size_wanted, size_t &size_written)
{
if (size_wanted > (static_cast<size_t>(~0) >> 1)) {
THROW_ERROR(invalid_argument, "cannot read " << size_wanted << ", more than signed size allows");
}
if (fd < 0) {
THROW_ERROR(invalid_argument, "write: trying to write on a closed file descriptor");
}
int rv = write(fd, buf, size_wanted);
if (rv < 0) {
THROW_ERRNO("write: " << size_wanted << " bytes returned " << rv);
}
size_written = rv;
}
void
write_or_die(int fd, const void *buf, size_t size)
{
size_t size_written = 0;
write_or_die_partial(fd, buf, size, size_written);
if (size_written != size) {
THROW_ERROR(runtime_error, "write: only " << size_written << " of " << size << " bytes written");
}
}
void
pwrite_or_die(int fd, const void *buf, size_t size, off_t offset)
{
if (size > (static_cast<size_t>(~0) >> 1)) {
THROW_ERROR(invalid_argument, "pwrite: cannot write " << size << ", more than signed size allows");
}
if (fd < 0) {
THROW_ERROR(invalid_argument, "pwrite: trying to write on a closed file descriptor");
}
int rv = ::pwrite(fd, buf, size, offset);
if (rv != static_cast<int>(size)) {
THROW_ERROR(runtime_error, "pwrite: only " << rv << " of " << size << " bytes written at offset " << offset);
}
}
template<>
void
write_or_die<string>(int fd, const string &text)
{
return write_or_die(fd, text.data(), text.size());
}
void
read_partial_or_die(int fd, void *buf, size_t size, size_t &size_read)
{
if (size > (static_cast<size_t>(~0) >> 1)) {
THROW_ERROR(invalid_argument, "cannot read " << size << ", more than signed size allows");
}
if (fd < 0) {
THROW_ERROR(runtime_error, "read: trying to read on a closed file descriptor");
}
size_read = 0;
while (size) {
int rv = read(fd, buf, size);
if (rv < 0) {
if (errno == EINTR) {
CHATTER_TRACE("resuming after EINTR");
continue;
}
THROW_ERRNO("read: " << size << " bytes");
}
if (rv > static_cast<int>(size)) {
THROW_ERROR(runtime_error, "read: somehow read more bytes (" << rv << ") than requested (" << size << ")");
}
if (rv == 0) break;
size_read += rv;
size -= rv;
// CHATTER("read " << rv << " bytes from fd " << fd);
}
}
string
read_string(int fd, size_t size)
{
string rv(size, '\0');
size_t size_read = 0;
void *rvp = const_cast<char *>(rv.data());
read_partial_or_die(fd, rvp, size, size_read);
rv.resize(size_read);
return rv;
}
void
read_or_die(int fd, void *buf, size_t size)
{
size_t size_read = 0;
read_partial_or_die(fd, buf, size, size_read);
if (size_read != size) {
THROW_ERROR(runtime_error, "read: " << size_read << " of " << size << " bytes");
}
}
void
pread_or_die(int fd, void *buf, size_t size, off_t offset)
{
if (size > (static_cast<size_t>(~0) >> 1)) {
THROW_ERROR(invalid_argument, "cannot read " << size << ", more than signed size allows");
}
if (fd < 0) {
throw runtime_error("read: trying to read on a closed file descriptor");
} else {
while (size) {
int rv = pread(fd, buf, size, offset);
if (rv < 0) {
if (errno == EINTR) {
CHATTER(__func__ << "resuming after EINTR");
continue;
}
THROW_ERRNO("pread: " << size << " bytes");
}
if (rv != static_cast<int>(size)) {
THROW_ERROR(runtime_error, "pread: " << size << " bytes at offset " << offset << " returned " << rv);
}
break;
}
}
}
template<>
void
pread_or_die<string>(int fd, string &text, off_t offset)
{
return pread_or_die(fd, const_cast<char *>(text.data()), text.size(), offset);
}
template<>
void
pread_or_die<vector<char>>(int fd, vector<char> &text, off_t offset)
{
return pread_or_die(fd, text.data(), text.size(), offset);
}
template<>
void
pread_or_die<vector<uint8_t>>(int fd, vector<uint8_t> &text, off_t offset)
{
return pread_or_die(fd, text.data(), text.size(), offset);
}
Stat::Stat()
{
memset_zero<stat>(this);
}
Stat &
Stat::lstat(const string &filename)
{
CHATTER_UNWIND("lstat " << filename);
DIE_IF_MINUS_ONE(::lstat(filename.c_str(), this));
return *this;
}
Stat &
Stat::fstat(int fd)
{
CHATTER_UNWIND("fstat " << fd);
DIE_IF_MINUS_ONE(::fstat(fd, this));
return *this;
}
Stat::Stat(int fd)
{
memset_zero<stat>(this);
fstat(fd);
}
Stat::Stat(const string &filename)
{
memset_zero<stat>(this);
lstat(filename);
}
string
readlink_or_die(const string &path)
{
// Start with a reasonable guess since it will usually work
off_t size = 4096;
while (size < 1048576) {
char buf[size + 1];
int rv;
DIE_IF_MINUS_ONE(rv = readlink(path.c_str(), buf, size + 1));
// No negative values allowed except -1
THROW_CHECK1(runtime_error, rv, rv >= 0);
if (rv <= size) {
buf[rv] = 0;
return buf;
}
// cerr << "Retrying readlink(" << path << ", buf, " << size + 1 << ")" << endl;
// This is from the Linux readlink(2) man page (release 3.44).
// It only works when the filesystem reports st_size accurately for symlinks,
// and at least one doesn't, so we can't rely on it at all.
// size = lstat_or_die(path).st_size;
size *= 2;
}
THROW_ERROR(runtime_error, "readlink: maximum buffer size exceeded");
}
// Turn a FD into a human-recognizable filename OR an error message.
string
name_fd(int fd)
{
try {
ostringstream oss;
oss << "/proc/self/fd/" << fd;
return readlink_or_die(oss.str());
} catch (exception &e) {
return string(e.what());
}
}
bool
assert_no_leaked_fds()
{
struct rlimit rlim;
int rv = getrlimit(RLIMIT_NOFILE, &rlim);
if (rv) {
perror("getrlimit(RLIMIT_NOFILE)");
// Well, that sucked. Guess.
rlim.rlim_cur = 1024;
}
CHATTER("Checking for leaked FDs in range 3.." << rlim.rlim_cur);
int leaked_fds = 0;
for (unsigned i = 3; i < rlim.rlim_cur; ++i) {
struct stat buf;
if (! fstat(i, &buf)) {
CHATTER("WARNING: fd " << i << " open at exit");
++leaked_fds;
}
}
CHATTER(leaked_fds << " leaked FD(s) found");
return leaked_fds == 0;
}
pair<Fd, Fd>
socketpair_or_die(int domain, int type, int protocol)
{
pair<Fd, Fd> rv;
int sv[2];
DIE_IF_MINUS_ONE(socketpair(domain, type, protocol, sv));
rv.first = sv[0];
rv.second = sv[1];
return rv;
}
void
dup2_or_die(int fd_in, int fd_out)
{
DIE_IF_MINUS_ONE(dup2(fd_in, fd_out));
}
string
st_mode_ntoa(mode_t mode)
{
static const bits_ntoa_table table[] = {
NTOA_TABLE_ENTRY_BITS(S_IFMT),
NTOA_TABLE_ENTRY_BITS(S_IFSOCK),
NTOA_TABLE_ENTRY_BITS(S_IFLNK),
NTOA_TABLE_ENTRY_BITS(S_IFMT),
NTOA_TABLE_ENTRY_BITS(S_IFSOCK),
NTOA_TABLE_ENTRY_BITS(S_IFLNK),
NTOA_TABLE_ENTRY_BITS(S_IFREG),
NTOA_TABLE_ENTRY_BITS(S_IFBLK),
NTOA_TABLE_ENTRY_BITS(S_IFDIR),
NTOA_TABLE_ENTRY_BITS(S_IFCHR),
NTOA_TABLE_ENTRY_BITS(S_IFIFO),
NTOA_TABLE_ENTRY_BITS(S_ISUID),
NTOA_TABLE_ENTRY_BITS(S_ISGID),
NTOA_TABLE_ENTRY_BITS(S_ISVTX),
NTOA_TABLE_ENTRY_BITS(S_IRWXU),
NTOA_TABLE_ENTRY_BITS(S_IRUSR),
NTOA_TABLE_ENTRY_BITS(S_IWUSR),
NTOA_TABLE_ENTRY_BITS(S_IXUSR),
NTOA_TABLE_ENTRY_BITS(S_IRWXG),
NTOA_TABLE_ENTRY_BITS(S_IRGRP),
NTOA_TABLE_ENTRY_BITS(S_IWGRP),
NTOA_TABLE_ENTRY_BITS(S_IXGRP),
NTOA_TABLE_ENTRY_BITS(S_IRWXO),
NTOA_TABLE_ENTRY_BITS(S_IROTH),
NTOA_TABLE_ENTRY_BITS(S_IWOTH),
NTOA_TABLE_ENTRY_BITS(S_IXOTH),
NTOA_TABLE_ENTRY_END()
};
return bits_ntoa(mode, table);
}
};