mirror of
https://github.com/Zygo/bees.git
synced 2025-08-03 14:23:29 +02:00
bees: remove local cruft, throw at github
This commit is contained in:
37
lib/Makefile
Normal file
37
lib/Makefile
Normal file
@@ -0,0 +1,37 @@
|
||||
default: libcrucible.so
|
||||
|
||||
OBJS = \
|
||||
crc64.o \
|
||||
chatter.o \
|
||||
error.o \
|
||||
execpipe.o \
|
||||
extentwalker.o \
|
||||
fd.o \
|
||||
fs.o \
|
||||
interp.o \
|
||||
ntoa.o \
|
||||
path.o \
|
||||
process.o \
|
||||
string.o \
|
||||
time.o \
|
||||
uuid.o \
|
||||
|
||||
include ../makeflags
|
||||
|
||||
LDFLAGS = -shared -luuid
|
||||
|
||||
depends.mk: *.c *.cc
|
||||
for x in *.c; do $(CC) $(CFLAGS) -M "$$x"; done > depends.mk.new
|
||||
for x in *.cc; do $(CXX) $(CXXFLAGS) -M "$$x"; done >> depends.mk.new
|
||||
mv -fv depends.mk.new depends.mk
|
||||
|
||||
-include depends.mk
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) -o $@ -c $<
|
||||
|
||||
%.o: %.cc ../include/crucible/%.h
|
||||
$(CXX) $(CXXFLAGS) -o $@ -c $<
|
||||
|
||||
libcrucible.so: $(OBJS) Makefile
|
||||
$(CXX) $(LDFLAGS) -o $@ $(OBJS)
|
140
lib/chatter.cc
Normal file
140
lib/chatter.cc
Normal file
@@ -0,0 +1,140 @@
|
||||
#include "crucible/chatter.h"
|
||||
#include "crucible/error.h"
|
||||
#include "crucible/path.h"
|
||||
#include "crucible/process.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <ctime>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
namespace crucible {
|
||||
using namespace std;
|
||||
|
||||
static auto_ptr<set<string>> chatter_names;
|
||||
static const char *SPACETAB = " \t";
|
||||
|
||||
static
|
||||
void
|
||||
init_chatter_names()
|
||||
{
|
||||
if (!chatter_names.get()) {
|
||||
chatter_names.reset(new set<string>);
|
||||
const char *sp = ::getenv("CRUCIBLE_CHATTER");
|
||||
if (sp) {
|
||||
cerr << "CRUCIBLE_CHATTER = '" << sp << "'" << endl;
|
||||
string s(sp);
|
||||
while (!s.empty()) {
|
||||
s.erase(0, s.find_first_not_of(SPACETAB));
|
||||
if (s.empty()) {
|
||||
break;
|
||||
}
|
||||
size_t last = s.find_first_of(SPACETAB);
|
||||
string first_word = s.substr(0, last);
|
||||
cerr << "\t'" << first_word << "'" << endl;
|
||||
chatter_names->insert(first_word);
|
||||
s.erase(0, last);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Chatter::Chatter(string name, ostream &os)
|
||||
: m_name(name), m_os(os)
|
||||
{
|
||||
}
|
||||
|
||||
Chatter::~Chatter()
|
||||
{
|
||||
ostringstream header_stream;
|
||||
|
||||
time_t ltime;
|
||||
DIE_IF_MINUS_ONE(time(<ime));
|
||||
struct tm ltm;
|
||||
DIE_IF_ZERO(localtime_r(<ime, <m));
|
||||
|
||||
char buf[1024];
|
||||
DIE_IF_ZERO(strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", <m));
|
||||
|
||||
header_stream << buf;
|
||||
header_stream << " " << getpid() << "." << gettid();
|
||||
if (!m_name.empty()) {
|
||||
header_stream << " " << m_name;
|
||||
}
|
||||
header_stream << ": ";
|
||||
|
||||
string out = m_oss.str();
|
||||
string header = header_stream.str();
|
||||
|
||||
string::size_type start = 0;
|
||||
while (start < out.size()) {
|
||||
size_t end_line = out.find_first_of("\n", start);
|
||||
if (end_line != string::npos) {
|
||||
assert(out[end_line] == '\n');
|
||||
size_t end = end_line;
|
||||
m_os << (header + out.substr(start, end - start) + "\n") << flush;
|
||||
start = end_line + 1;
|
||||
} else {
|
||||
m_os << (header + out.substr(start) + "\n") << flush;
|
||||
start = out.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Chatter::Chatter(Chatter &&c)
|
||||
: m_name(c.m_name), m_os(c.m_os), m_oss(c.m_oss.str())
|
||||
{
|
||||
c.m_oss.str("");
|
||||
}
|
||||
|
||||
set<ChatterBox*> ChatterBox::s_boxes;
|
||||
|
||||
set<ChatterBox*>& ChatterBox::all_boxes()
|
||||
{
|
||||
return s_boxes;
|
||||
}
|
||||
|
||||
ChatterBox::ChatterBox(string file, int line, string pretty_function, ostream &os)
|
||||
: m_file(basename(file)), m_line(line), m_pretty_function(pretty_function), m_enabled(false), m_os(os)
|
||||
{
|
||||
s_boxes.insert(this);
|
||||
init_chatter_names();
|
||||
if (chatter_names->find(m_file) != chatter_names->end()) {
|
||||
m_enabled = true;
|
||||
} else if (chatter_names->find(m_pretty_function) != chatter_names->end()) {
|
||||
m_enabled = true;
|
||||
} else if (!chatter_names->empty()) {
|
||||
cerr << "CRUCIBLE_CHATTER does not list '" << m_file << "' or '" << m_pretty_function << "'" << endl;
|
||||
}
|
||||
// cerr << "ChatterBox " << reinterpret_cast<void*>(this) << " constructed" << endl;
|
||||
}
|
||||
|
||||
ChatterBox::~ChatterBox()
|
||||
{
|
||||
s_boxes.erase(this);
|
||||
// cerr << "ChatterBox " << reinterpret_cast<void*>(this) << " destructed" << endl;
|
||||
}
|
||||
|
||||
void
|
||||
ChatterBox::set_enable(bool en)
|
||||
{
|
||||
m_enabled = en;
|
||||
}
|
||||
|
||||
ChatterUnwinder::ChatterUnwinder(function<void()> f) :
|
||||
m_func(f)
|
||||
{
|
||||
}
|
||||
|
||||
ChatterUnwinder::~ChatterUnwinder()
|
||||
{
|
||||
if (uncaught_exception()) {
|
||||
m_func();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
59
lib/crc64.cc
Normal file
59
lib/crc64.cc
Normal file
@@ -0,0 +1,59 @@
|
||||
#include "crucible/crc64.h"
|
||||
|
||||
#define POLY64REV 0xd800000000000000ULL
|
||||
|
||||
namespace crucible {
|
||||
|
||||
static bool init = false;
|
||||
static uint64_t CRCTable[256];
|
||||
|
||||
static void init_crc64_table()
|
||||
{
|
||||
if (!init) {
|
||||
for (int i = 0; i <= 255; i++) {
|
||||
uint64_t part = i;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (part & 1) {
|
||||
part = (part >> 1) ^ POLY64REV;
|
||||
} else {
|
||||
part >>= 1;
|
||||
}
|
||||
}
|
||||
CRCTable[i] = part;
|
||||
}
|
||||
init = true;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t
|
||||
Digest::CRC::crc64(const char *s)
|
||||
{
|
||||
init_crc64_table();
|
||||
|
||||
uint64_t crc = 0;
|
||||
for (; *s; s++) {
|
||||
uint64_t temp1 = crc >> 8;
|
||||
uint64_t temp2 = CRCTable[(crc ^ static_cast<uint64_t>(*s)) & 0xff];
|
||||
crc = temp1 ^ temp2;
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
uint64_t
|
||||
Digest::CRC::crc64(const void *p, size_t len)
|
||||
{
|
||||
init_crc64_table();
|
||||
|
||||
uint64_t crc = 0;
|
||||
for (const unsigned char *s = static_cast<const unsigned char *>(p); len; --len) {
|
||||
uint64_t temp1 = crc >> 8;
|
||||
uint64_t temp2 = CRCTable[(crc ^ *s++) & 0xff];
|
||||
crc = temp1 ^ temp2;
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
|
||||
};
|
74
lib/error.cc
Normal file
74
lib/error.cc
Normal file
@@ -0,0 +1,74 @@
|
||||
#include "crucible/error.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <iostream>
|
||||
|
||||
#include <cxxabi.h>
|
||||
|
||||
namespace crucible {
|
||||
using namespace std;
|
||||
|
||||
static
|
||||
string
|
||||
analyze_exception(const exception &e)
|
||||
{
|
||||
// Let's ignore all the potential memory allocation exceptions for now, K?
|
||||
ostringstream oss;
|
||||
|
||||
int status;
|
||||
char *realname = abi::__cxa_demangle(typeid(e).name(), 0, 0, &status);
|
||||
oss << "exception type ";
|
||||
// This is questionable since anything that would cause
|
||||
// cxa_demangle to fail will probably cause an exception anyway.
|
||||
if (realname) {
|
||||
oss << realname;
|
||||
free(realname);
|
||||
} else {
|
||||
oss << typeid(e).name();
|
||||
}
|
||||
oss << ": " << e.what();
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// FIXME: could probably avoid some of these levels of indirection
|
||||
static
|
||||
function<void(string s)> current_catch_explainer = [&](string s) {
|
||||
cerr << s << endl;
|
||||
};
|
||||
|
||||
void
|
||||
set_catch_explainer(function<void(string s)> f)
|
||||
{
|
||||
current_catch_explainer = f;
|
||||
}
|
||||
|
||||
void
|
||||
default_catch_explainer(string s)
|
||||
{
|
||||
current_catch_explainer(s);
|
||||
}
|
||||
|
||||
int
|
||||
catch_all(const function<void()> &f, const function<void(string)> &explainer)
|
||||
{
|
||||
try {
|
||||
f();
|
||||
return 0;
|
||||
} catch (const exception &e) {
|
||||
explainer(analyze_exception(e));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
catch_and_explain(const function<void()> &f, const function<void(string)> &explainer)
|
||||
{
|
||||
try {
|
||||
f();
|
||||
} catch (const exception &e) {
|
||||
explainer(analyze_exception(e));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
104
lib/execpipe.cc
Normal file
104
lib/execpipe.cc
Normal file
@@ -0,0 +1,104 @@
|
||||
#include "crucible/execpipe.h"
|
||||
|
||||
#include "crucible/chatter.h"
|
||||
#include "crucible/error.h"
|
||||
#include "crucible/process.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace crucible {
|
||||
using namespace std;
|
||||
|
||||
void
|
||||
redirect_stdin(const Fd &child_fd)
|
||||
{
|
||||
dup2_or_die(child_fd, STDIN_FILENO);
|
||||
}
|
||||
|
||||
void
|
||||
redirect_stdin_stdout(const Fd &child_fd)
|
||||
{
|
||||
dup2_or_die(child_fd, STDOUT_FILENO);
|
||||
dup2_or_die(child_fd, STDIN_FILENO);
|
||||
}
|
||||
|
||||
void
|
||||
redirect_stdin_stdout_stderr(const Fd &child_fd)
|
||||
{
|
||||
dup2_or_die(child_fd, STDERR_FILENO);
|
||||
dup2_or_die(child_fd, STDOUT_FILENO);
|
||||
dup2_or_die(child_fd, STDIN_FILENO);
|
||||
}
|
||||
|
||||
void
|
||||
redirect_stdout_stderr(const Fd &child_fd)
|
||||
{
|
||||
dup2_or_die(child_fd, STDERR_FILENO);
|
||||
dup2_or_die(child_fd, STDOUT_FILENO);
|
||||
}
|
||||
|
||||
void
|
||||
redirect_stdout(const Fd &child_fd)
|
||||
{
|
||||
dup2_or_die(child_fd, STDOUT_FILENO);
|
||||
}
|
||||
|
||||
void
|
||||
redirect_stderr(const Fd &child_fd)
|
||||
{
|
||||
dup2_or_die(child_fd, STDERR_FILENO);
|
||||
}
|
||||
|
||||
Fd popen(function<int()> f, function<void(const Fd &child_fd)> import_fd_fn)
|
||||
{
|
||||
Fd parent_fd, child_fd;
|
||||
{
|
||||
pair<Fd, Fd> fd_pair = socketpair_or_die();
|
||||
parent_fd = fd_pair.first;
|
||||
child_fd = fd_pair.second;
|
||||
}
|
||||
|
||||
pid_t fv;
|
||||
DIE_IF_MINUS_ONE(fv = fork());
|
||||
|
||||
if (fv) {
|
||||
child_fd->close();
|
||||
return parent_fd;
|
||||
} else {
|
||||
int rv = EXIT_FAILURE;
|
||||
catch_all([&]() {
|
||||
parent_fd->close();
|
||||
import_fd_fn(child_fd);
|
||||
// system("ls -l /proc/$$/fd/ >&2");
|
||||
|
||||
rv = f();
|
||||
});
|
||||
_exit(rv);
|
||||
cerr << "PID " << getpid() << " TID " << gettid() << "STILL ALIVE" << endl;
|
||||
system("ls -l /proc/$$/task/ >&2");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
string
|
||||
read_all(Fd fd, size_t max_bytes, size_t chunk_bytes)
|
||||
{
|
||||
char buf[chunk_bytes];
|
||||
string str;
|
||||
size_t rv;
|
||||
while (1) {
|
||||
read_partial_or_die(fd, static_cast<void *>(buf), chunk_bytes, rv);
|
||||
if (rv == 0) {
|
||||
break;
|
||||
}
|
||||
if (max_bytes - str.size() < rv) {
|
||||
THROW_ERROR(out_of_range, "Output size limit " << max_bytes << " exceeded by appending " << rv << " bytes read to " << str.size() << " already in string");
|
||||
}
|
||||
str.append(buf, rv);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
}
|
630
lib/extentwalker.cc
Normal file
630
lib/extentwalker.cc
Normal file
@@ -0,0 +1,630 @@
|
||||
#include "crucible/extentwalker.h"
|
||||
|
||||
#include "crucible/chatter.h"
|
||||
#include "crucible/error.h"
|
||||
#include "crucible/fs.h"
|
||||
#include "crucible/limits.h"
|
||||
#include "crucible/string.h"
|
||||
|
||||
|
||||
namespace crucible {
|
||||
using namespace std;
|
||||
|
||||
const off_t ExtentWalker::sc_step_size;
|
||||
|
||||
// fm_start, fm_length, fm_flags, m_extents
|
||||
// fe_logical, fe_physical, fe_length, fe_flags
|
||||
|
||||
static const off_t MAX_OFFSET = numeric_limits<off_t>::max();
|
||||
static const off_t FIEMAP_BLOCK_SIZE = 4096;
|
||||
|
||||
static bool __ew_do_log = getenv("EXTENTWALKER_DEBUG");
|
||||
|
||||
#define EWLOG(x) do { \
|
||||
if (__ew_do_log) { \
|
||||
CHATTER(x); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
ostream &
|
||||
operator<<(ostream &os, const Extent &e)
|
||||
{
|
||||
os << "Extent {"
|
||||
<< " begin = " << to_hex(e.m_begin)
|
||||
<< ", end = " << to_hex(e.m_end)
|
||||
<< ", physical = " << to_hex(e.m_physical)
|
||||
<< ", flags = ";
|
||||
if (e.m_flags & Extent::HOLE) {
|
||||
os << "Extent::HOLE|";
|
||||
}
|
||||
if (e.m_flags & Extent::PREALLOC) {
|
||||
os << "Extent::PREALLOC|";
|
||||
}
|
||||
if (e.m_flags & Extent::OBSCURED) {
|
||||
os << "Extent::OBSCURED|";
|
||||
}
|
||||
if (e.m_flags & ~(Extent::HOLE | Extent::PREALLOC | Extent::OBSCURED)) {
|
||||
os << fiemap_extent_flags_ntoa(e.m_flags & ~(Extent::HOLE | Extent::PREALLOC | Extent::OBSCURED));
|
||||
}
|
||||
if (e.m_physical_len) {
|
||||
os << ", physical_len = " << to_hex(e.m_physical_len);
|
||||
}
|
||||
if (e.m_logical_len) {
|
||||
os << ", logical_len = " << to_hex(e.m_logical_len);
|
||||
}
|
||||
if (e.m_offset) {
|
||||
os << ", offset = " << to_hex(e.m_offset);
|
||||
}
|
||||
return os << " }";
|
||||
}
|
||||
|
||||
ostream &
|
||||
operator<<(ostream &os, const ExtentWalker::Vec &v)
|
||||
{
|
||||
os << "ExtentWalker::Vec {";
|
||||
for (auto e : v) {
|
||||
os << "\n\t" << e;
|
||||
}
|
||||
return os << "}";
|
||||
}
|
||||
|
||||
ostream &
|
||||
operator<<(ostream &os, const ExtentWalker &ew)
|
||||
{
|
||||
return os << "ExtentWalker {"
|
||||
<< " fd = " << name_fd(ew.m_fd)
|
||||
<< ", stat.st_size = " << to_hex(ew.m_stat.st_size)
|
||||
<< ", extents = " << ew.m_extents
|
||||
<< ", current = [" << ew.m_current - ew.m_extents.begin()
|
||||
<< "] }";
|
||||
}
|
||||
|
||||
Extent::Extent() :
|
||||
m_begin(0),
|
||||
m_end(0),
|
||||
m_physical(0),
|
||||
m_flags(0),
|
||||
m_physical_len(0),
|
||||
m_logical_len(0),
|
||||
m_offset(0)
|
||||
{
|
||||
}
|
||||
|
||||
Extent::operator bool() const
|
||||
{
|
||||
THROW_CHECK2(invalid_argument, m_begin, m_end, m_end >= m_begin);
|
||||
return m_end > m_begin;
|
||||
}
|
||||
|
||||
off_t
|
||||
Extent::size() const
|
||||
{
|
||||
THROW_CHECK2(invalid_argument, m_begin, m_end, m_end >= m_begin);
|
||||
return m_end - m_begin;
|
||||
}
|
||||
|
||||
bool
|
||||
Extent::operator==(const Extent &that) const
|
||||
{
|
||||
return m_begin == that.m_begin && m_end == that.m_end && m_physical == that.m_physical && m_flags == that.m_flags;
|
||||
}
|
||||
|
||||
ExtentWalker::ExtentWalker(Fd fd) :
|
||||
m_fd(fd),
|
||||
m_current(m_extents.begin())
|
||||
{
|
||||
}
|
||||
|
||||
ExtentWalker::ExtentWalker(Fd fd, off_t initial_pos) :
|
||||
m_fd(fd),
|
||||
m_current(m_extents.begin())
|
||||
{
|
||||
seek(initial_pos);
|
||||
}
|
||||
|
||||
ExtentWalker::Itr
|
||||
ExtentWalker::find_in_cache(off_t pos)
|
||||
{
|
||||
EWLOG("find_in_cache " << to_hex(pos));
|
||||
// EOF is an annoying special case
|
||||
if (pos >= m_stat.st_size) {
|
||||
if (!m_extents.empty() && m_extents.rbegin()->m_end == m_stat.st_size) {
|
||||
auto i = m_extents.end();
|
||||
return --i;
|
||||
}
|
||||
}
|
||||
for (auto vi = m_extents.begin(); vi != m_extents.end(); ++vi) {
|
||||
if (pos >= vi->m_begin && pos < vi->m_end) {
|
||||
EWLOG("pos " << to_hex(pos) << " in " << *vi);
|
||||
if (vi == m_extents.begin() && !(m_extents.begin()->m_begin == 0)) {
|
||||
// Must have an extent before pos, unless
|
||||
// there can be no extent before pos because pos == 0
|
||||
EWLOG("can't match first unless begin is BOF");
|
||||
break;
|
||||
}
|
||||
auto ni = vi;
|
||||
++ni;
|
||||
if (ni == m_extents.end() && !(vi->m_end >= m_stat.st_size)) {
|
||||
// Must have an extent after pos, unless
|
||||
// there can be no extent after pos because pos >= EOF
|
||||
EWLOG("can't match last unless end past EOF " << to_hex(m_stat.st_size));
|
||||
break;
|
||||
}
|
||||
// Extent surrounded on either side by other known extents
|
||||
return vi;
|
||||
}
|
||||
}
|
||||
EWLOG("find_in_cache failed: " << *this);
|
||||
return m_extents.end();
|
||||
}
|
||||
|
||||
void
|
||||
ExtentWalker::run_fiemap(off_t pos)
|
||||
{
|
||||
ostringstream log;
|
||||
CHATTER_UNWIND("Log of run_fiemap: " << log.str());
|
||||
|
||||
EWLOG("pos = " << to_hex(pos));
|
||||
|
||||
THROW_CHECK1(invalid_argument, pos, (pos & (FIEMAP_BLOCK_SIZE - 1)) == 0);
|
||||
|
||||
Vec fm;
|
||||
|
||||
off_t step_size = pos;
|
||||
off_t begin = pos - min(pos, sc_step_size);
|
||||
|
||||
// This loop should not run forever
|
||||
int loop_count = 0;
|
||||
int loop_limit = 99;
|
||||
while (true) {
|
||||
if (loop_count == 90) {
|
||||
EWLOG(log.str());
|
||||
}
|
||||
|
||||
THROW_CHECK1(runtime_error, loop_count, loop_count < loop_limit);
|
||||
++loop_count;
|
||||
|
||||
// Get file size every time in case it changes under us
|
||||
m_stat.fstat(m_fd);
|
||||
|
||||
// Get fiemap begin..EOF
|
||||
fm = get_extent_map(begin);
|
||||
EWLOG("fiemap result loop count #" << loop_count << ":" << fm);
|
||||
|
||||
// This algorithm seeks at least three extents: one before,
|
||||
// one after, and one containing pos. Files which contain
|
||||
// two or fewer extents will cause an obvious problem with that,
|
||||
// so handle those cases separately.
|
||||
|
||||
// FIEMAP lies, and we catch it in a lie about the size of the
|
||||
// second extent. To work around this, try getting more than 3.
|
||||
|
||||
// 0..2(ish) extents
|
||||
if (fm.size() < sc_extent_fetch_min) {
|
||||
// If we are not at beginning of file, move backward
|
||||
if (begin > 0) {
|
||||
step_size /= 2;
|
||||
auto next_begin = (begin - min(step_size, begin)) & ~(FIEMAP_BLOCK_SIZE - 1);
|
||||
EWLOG("step backward " << to_hex(begin) << " -> " << to_hex(next_begin) << " extents size " << fm.size());
|
||||
if (begin == next_begin) {
|
||||
EWLOG("step backward stopped");
|
||||
break;
|
||||
}
|
||||
begin = next_begin;
|
||||
continue;
|
||||
}
|
||||
|
||||
// We are at beginning of file and have too few extents.
|
||||
|
||||
// Zero extents? Entire file is a hole.
|
||||
if (fm.empty()) {
|
||||
EWLOG("zero extents");
|
||||
break;
|
||||
}
|
||||
|
||||
// We know we have the beginning of the file and at least
|
||||
// one extent. If the last extent is EOF then we have the
|
||||
// whole file in the buffer. If the last extent is NOT
|
||||
// EOF then fiemap did something we didn't expect.
|
||||
THROW_CHECK1(runtime_error, fm.rbegin()->flags(), fm.rbegin()->flags() & FIEMAP_EXTENT_LAST);
|
||||
break;
|
||||
}
|
||||
|
||||
// We have at least three extents, so there is now a first and last.
|
||||
// We want pos to be between first and last. There doesn't have
|
||||
// to be an extent between these (it could be a hole).
|
||||
auto &first_extent = fm.at(sc_extent_fetch_min - 2);
|
||||
auto &last_extent = *fm.rbegin();
|
||||
EWLOG("first_extent = " << first_extent);
|
||||
EWLOG("last_extent = " << last_extent);
|
||||
|
||||
// First extent must end on or before pos
|
||||
if (first_extent.end() > pos) {
|
||||
// Can we move backward?
|
||||
if (begin > 0) {
|
||||
step_size /= 2;
|
||||
auto next_begin = (begin - min(step_size, begin)) & ~(FIEMAP_BLOCK_SIZE - 1);
|
||||
EWLOG("step backward " << to_hex(begin) << " -> " << to_hex(next_begin) << " extents size " << fm.size());
|
||||
if (begin == next_begin) {
|
||||
EWLOG("step backward stopped");
|
||||
break;
|
||||
}
|
||||
begin = next_begin;
|
||||
continue;
|
||||
}
|
||||
|
||||
// We are as far back as we can go, so there must be no
|
||||
// extent before pos (i.e. file starts with a hole).
|
||||
EWLOG("no extent before pos");
|
||||
break;
|
||||
}
|
||||
|
||||
// First extent ends on or before pos.
|
||||
|
||||
// If last extent is EOF then we have the entire file in the buffer.
|
||||
// pos could be in last extent, so skip the later checks that
|
||||
// insist pos be located prior to the last extent.
|
||||
if (last_extent.flags() & FIEMAP_EXTENT_LAST) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Don't have EOF, must have an extent after pos.
|
||||
if (last_extent.begin() <= pos) {
|
||||
step_size /= 2;
|
||||
auto new_begin = (begin + step_size) & ~(FIEMAP_BLOCK_SIZE - 1);
|
||||
EWLOG("step forward " << to_hex(begin) << " -> " << to_hex(new_begin));
|
||||
if (begin == new_begin) {
|
||||
EWLOG("step forward stopped");
|
||||
break;
|
||||
}
|
||||
begin = new_begin;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Last extent begins after pos, first extent ends on or before pos.
|
||||
// All other cases should have been handled before here.
|
||||
THROW_CHECK2(runtime_error, pos, first_extent, first_extent.end() <= pos);
|
||||
THROW_CHECK2(runtime_error, pos, last_extent, last_extent.begin() > pos);
|
||||
|
||||
// We should probably stop now
|
||||
break;
|
||||
}
|
||||
|
||||
// Fill in holes so there are Extent records over entire range
|
||||
auto fmi = fm.begin();
|
||||
off_t ipos = begin;
|
||||
Vec new_vec;
|
||||
// If we mapped the entire file and there are no extents,
|
||||
// the entire file is a hole.
|
||||
bool last_extent_is_last = (begin == 0 && fm.empty());
|
||||
while (fmi != fm.end()) {
|
||||
Extent new_extent(*fmi);
|
||||
THROW_CHECK2(runtime_error, ipos, new_extent.m_begin, ipos <= new_extent.m_begin);
|
||||
if (new_extent.m_begin > ipos) {
|
||||
Extent hole_extent;
|
||||
hole_extent.m_begin = ipos;
|
||||
hole_extent.m_end = fmi->begin();
|
||||
hole_extent.m_physical = 0;
|
||||
hole_extent.m_flags = Extent::HOLE;
|
||||
new_vec.push_back(hole_extent);
|
||||
ipos += hole_extent.size();
|
||||
}
|
||||
THROW_CHECK2(runtime_error, ipos, new_extent.m_begin, ipos == new_extent.m_begin);
|
||||
new_vec.push_back(new_extent);
|
||||
ipos += new_extent.size();
|
||||
last_extent_is_last = fmi->flags() & FIEMAP_EXTENT_LAST;
|
||||
++fmi;
|
||||
}
|
||||
// If we have run out of extents before EOF, insert a hole at the end
|
||||
if (last_extent_is_last && ipos < m_stat.st_size) {
|
||||
Extent hole_extent;
|
||||
hole_extent.m_begin = ipos;
|
||||
hole_extent.m_end = m_stat.st_size;
|
||||
hole_extent.m_physical = 0;
|
||||
hole_extent.m_flags = Extent::HOLE;
|
||||
if (!new_vec.empty() && new_vec.rbegin()->m_flags & FIEMAP_EXTENT_LAST) {
|
||||
new_vec.rbegin()->m_flags &= ~(FIEMAP_EXTENT_LAST);
|
||||
hole_extent.m_flags |= FIEMAP_EXTENT_LAST;
|
||||
}
|
||||
new_vec.push_back(hole_extent);
|
||||
ipos += new_vec.size();
|
||||
}
|
||||
THROW_CHECK1(runtime_error, new_vec.size(), !new_vec.empty());
|
||||
|
||||
// Allow last extent to extend beyond desired range (e.g. at EOF)
|
||||
THROW_CHECK2(runtime_error, ipos, new_vec.rbegin()->m_end, ipos <= new_vec.rbegin()->m_end);
|
||||
// If we have the last extent in the file, truncate it to the file size.
|
||||
if (ipos >= m_stat.st_size) {
|
||||
THROW_CHECK2(runtime_error, new_vec.rbegin()->m_begin, m_stat.st_size, m_stat.st_size > new_vec.rbegin()->m_begin);
|
||||
THROW_CHECK2(runtime_error, new_vec.rbegin()->m_end, m_stat.st_size, m_stat.st_size <= new_vec.rbegin()->m_end);
|
||||
new_vec.rbegin()->m_end = m_stat.st_size;
|
||||
}
|
||||
|
||||
// Verify contiguous, ascending order, at least one Extent
|
||||
THROW_CHECK1(runtime_error, new_vec, !new_vec.empty());
|
||||
|
||||
ipos = new_vec.begin()->m_begin;
|
||||
bool last_flag_last = false;
|
||||
for (auto e : new_vec) {
|
||||
THROW_CHECK1(runtime_error, new_vec, e.m_begin == ipos);
|
||||
THROW_CHECK1(runtime_error, e, e.size() > 0);
|
||||
THROW_CHECK1(runtime_error, new_vec, !last_flag_last);
|
||||
ipos += e.size();
|
||||
last_flag_last = e.m_flags & FIEMAP_EXTENT_LAST;
|
||||
}
|
||||
THROW_CHECK1(runtime_error, new_vec, !last_extent_is_last || new_vec.rbegin()->m_end == ipos);
|
||||
|
||||
m_extents = new_vec;
|
||||
m_current = m_extents.begin();
|
||||
}
|
||||
|
||||
void
|
||||
ExtentWalker::reset()
|
||||
{
|
||||
m_extents.clear();
|
||||
m_current = m_extents.begin();
|
||||
}
|
||||
|
||||
void
|
||||
ExtentWalker::seek(off_t pos)
|
||||
{
|
||||
CHATTER_UNWIND("seek " << to_hex(pos));
|
||||
THROW_CHECK1(out_of_range, pos, pos >= 0);
|
||||
Itr rv = find_in_cache(pos);
|
||||
if (rv != m_extents.end()) {
|
||||
m_current = rv;
|
||||
return;
|
||||
}
|
||||
run_fiemap(pos);
|
||||
m_current = find_in_cache(pos);
|
||||
}
|
||||
|
||||
Extent
|
||||
ExtentWalker::current()
|
||||
{
|
||||
THROW_CHECK2(invalid_argument, *this, m_extents.size(), m_current != m_extents.end());
|
||||
CHATTER_UNWIND("current " << *m_current);
|
||||
return *m_current;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
ExtentWalker::next()
|
||||
{
|
||||
CHATTER_UNWIND("next");
|
||||
THROW_CHECK1(invalid_argument, (m_current != m_extents.end()), m_current != m_extents.end());
|
||||
if (current().m_end >= m_stat.st_size) {
|
||||
CHATTER_UNWIND("next EOF");
|
||||
return false;
|
||||
}
|
||||
auto next_pos = current().m_end;
|
||||
if (next_pos >= m_stat.st_size) {
|
||||
CHATTER_UNWIND("next next_pos = " << next_pos << " m_stat.st_size = " << m_stat.st_size);
|
||||
return false;
|
||||
}
|
||||
seek(next_pos);
|
||||
THROW_CHECK1(runtime_error, (m_current != m_extents.end()), m_current != m_extents.end());
|
||||
|
||||
// FIEMAP is full of lies, so this check keeps failing
|
||||
// THROW_CHECK2(runtime_error, current().m_begin, next_pos, current().m_begin == next_pos);
|
||||
// Just ensure that pos is in the next extent somewhere.
|
||||
THROW_CHECK2(runtime_error, current(), next_pos, current().m_begin <= next_pos);
|
||||
THROW_CHECK2(runtime_error, current(), next_pos, current().m_end > next_pos);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ExtentWalker::prev()
|
||||
{
|
||||
CHATTER_UNWIND("prev");
|
||||
THROW_CHECK1(invalid_argument, (m_current != m_extents.end()), m_current != m_extents.end());
|
||||
auto prev_iter = m_current;
|
||||
if (prev_iter->m_begin == 0) {
|
||||
CHATTER_UNWIND("prev BOF");
|
||||
return false;
|
||||
}
|
||||
THROW_CHECK1(invalid_argument, (prev_iter != m_extents.begin()), prev_iter != m_extents.begin());
|
||||
--prev_iter;
|
||||
CHATTER_UNWIND("prev seeking to " << *prev_iter << "->m_begin");
|
||||
auto prev_end = current().m_begin;
|
||||
seek(prev_iter->m_begin);
|
||||
THROW_CHECK1(runtime_error, (m_current != m_extents.end()), m_current != m_extents.end());
|
||||
THROW_CHECK2(runtime_error, current().m_end, prev_end, current().m_end == prev_end);
|
||||
return true;
|
||||
}
|
||||
|
||||
ExtentWalker::~ExtentWalker()
|
||||
{
|
||||
}
|
||||
|
||||
BtrfsExtentWalker::BtrfsExtentWalker(Fd fd) :
|
||||
ExtentWalker(fd),
|
||||
m_tree_id(0)
|
||||
{
|
||||
}
|
||||
|
||||
BtrfsExtentWalker::BtrfsExtentWalker(Fd fd, off_t initial_pos) :
|
||||
ExtentWalker(fd),
|
||||
m_tree_id(0)
|
||||
{
|
||||
seek(initial_pos);
|
||||
}
|
||||
|
||||
void
|
||||
BtrfsExtentWalker::set_root_fd(Fd root_fd)
|
||||
{
|
||||
m_root_fd = root_fd;
|
||||
}
|
||||
|
||||
BtrfsExtentWalker::BtrfsExtentWalker(Fd fd, off_t initial_pos, Fd root_fd) :
|
||||
ExtentWalker(fd),
|
||||
m_tree_id(0)
|
||||
{
|
||||
set_root_fd(root_fd);
|
||||
seek(initial_pos);
|
||||
}
|
||||
|
||||
BtrfsExtentWalker::Vec
|
||||
BtrfsExtentWalker::get_extent_map(off_t pos)
|
||||
{
|
||||
BtrfsIoctlSearchKey sk;
|
||||
if (!m_root_fd) {
|
||||
m_root_fd = m_fd;
|
||||
}
|
||||
if (!m_tree_id) {
|
||||
m_tree_id = btrfs_get_root_id(m_fd);
|
||||
}
|
||||
sk.tree_id = m_tree_id;
|
||||
sk.min_objectid = m_stat.st_ino;
|
||||
sk.max_objectid = numeric_limits<uint64_t>::max();
|
||||
sk.min_offset = ranged_cast<uint64_t>(pos);
|
||||
sk.max_offset = numeric_limits<uint64_t>::max();
|
||||
sk.min_transid = 0;
|
||||
sk.max_transid = numeric_limits<uint64_t>::max();
|
||||
sk.min_type = sk.max_type = BTRFS_EXTENT_DATA_KEY;
|
||||
sk.nr_items = sc_extent_fetch_max;
|
||||
|
||||
CHATTER_UNWIND("sk " << sk << " root_fd " << name_fd(m_root_fd));
|
||||
sk.do_ioctl(m_root_fd);
|
||||
|
||||
Vec rv;
|
||||
|
||||
bool past_eof = false;
|
||||
for (auto i : sk.m_result) {
|
||||
// If we're seeing extents from the next file then we're past EOF on this file
|
||||
if (i.objectid > m_stat.st_ino) {
|
||||
past_eof = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Ignore things that aren't EXTENT_DATA_KEY
|
||||
if (i.type != BTRFS_EXTENT_DATA_KEY) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Hmmmkay we shouldn't be seeing these
|
||||
if (i.objectid < m_stat.st_ino) {
|
||||
THROW_ERROR(out_of_range, "objectid " << i.objectid << " < m_stat.st_ino " << m_stat.st_ino);
|
||||
continue;
|
||||
}
|
||||
|
||||
Extent e;
|
||||
e.m_begin = i.offset;
|
||||
auto compressed = call_btrfs_get(btrfs_stack_file_extent_compression, i.m_data);
|
||||
// FIEMAP told us about compressed extents and we can too
|
||||
if (compressed) {
|
||||
e.m_flags |= FIEMAP_EXTENT_ENCODED;
|
||||
}
|
||||
|
||||
auto type = call_btrfs_get(btrfs_stack_file_extent_type, i.m_data);
|
||||
off_t len = -1;
|
||||
switch (type) {
|
||||
default:
|
||||
cerr << "Unhandled file extent type " << type << " in root " << m_tree_id << " ino " << m_stat.st_ino << endl;
|
||||
break;
|
||||
case BTRFS_FILE_EXTENT_INLINE:
|
||||
len = ranged_cast<off_t>(call_btrfs_get(btrfs_stack_file_extent_ram_bytes, i.m_data));
|
||||
e.m_flags |= FIEMAP_EXTENT_DATA_INLINE | FIEMAP_EXTENT_NOT_ALIGNED;
|
||||
// Inline extents are never obscured, so don't bother filling in m_physical_len, etc.
|
||||
break;
|
||||
case BTRFS_FILE_EXTENT_PREALLOC:
|
||||
e.m_flags |= Extent::PREALLOC;
|
||||
case BTRFS_FILE_EXTENT_REG: {
|
||||
e.m_physical = call_btrfs_get(btrfs_stack_file_extent_disk_bytenr, i.m_data);
|
||||
|
||||
// This is the length of the full extent (decompressed)
|
||||
off_t ram = ranged_cast<off_t>(call_btrfs_get(btrfs_stack_file_extent_ram_bytes, i.m_data));
|
||||
|
||||
// This is the length of the part of the extent appearing in the file (decompressed)
|
||||
len = ranged_cast<off_t>(call_btrfs_get(btrfs_stack_file_extent_num_bytes, i.m_data));
|
||||
|
||||
// This is the offset from start of on-disk extent to the part we see in the file (decompressed)
|
||||
// May be negative due to the kind of bug we're stuck with forever, so no cast range check
|
||||
off_t offset = call_btrfs_get(btrfs_stack_file_extent_offset, i.m_data);
|
||||
|
||||
// If there is a physical address there must be size too
|
||||
if (e.m_physical) {
|
||||
THROW_CHECK1(runtime_error, ram, ram > 0);
|
||||
THROW_CHECK1(runtime_error, len, len > 0);
|
||||
THROW_CHECK2(runtime_error, offset, ram, offset < ram);
|
||||
} else {
|
||||
// There are two kinds of hole in btrfs. This is the other one.
|
||||
e.m_flags |= Extent::HOLE;
|
||||
}
|
||||
|
||||
// Partially obscured extent
|
||||
// FIXME: sometimes this happens:
|
||||
// i.type == BTRFS_EXTENT_DATA_KEY
|
||||
// type = 0x1
|
||||
// compressed = 0x0
|
||||
// REG start 0x0 offset 0x0 num 0x20000 ram 0x21000 gen 1101121
|
||||
// btrfs_file_extent_item {
|
||||
// generation = 1101121
|
||||
// ram_bytes = 135168
|
||||
// compression = 0x0
|
||||
// encryption = 0x0
|
||||
// other_encoding = 0x0
|
||||
// type = 0x1
|
||||
// disk_bytenr = 0x0
|
||||
// disk_num_bytes = 0x0
|
||||
// offset = 0x0
|
||||
// num_bytes = 0x20000
|
||||
// }
|
||||
if (ram != len || offset != 0) {
|
||||
e.m_flags |= Extent::OBSCURED;
|
||||
// cerr << e << "\nram = " << ram << ", len = " << len << ", offset = " << offset << endl;
|
||||
}
|
||||
e.m_physical_len = ram;
|
||||
e.m_logical_len = len;
|
||||
e.m_offset = offset;
|
||||
|
||||
// To maintain compatibility with FIEMAP we ignore the offset for compressed extents.
|
||||
// At some point we'll grow out of this.
|
||||
if (!compressed) {
|
||||
e.m_physical += offset;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (len > 0) {
|
||||
e.m_end = e.m_begin + len;
|
||||
if (e.m_end >= m_stat.st_size) {
|
||||
e.m_flags |= FIEMAP_EXTENT_LAST;
|
||||
}
|
||||
// FIXME: no FIEMAP_EXTENT_SHARED
|
||||
// WONTFIX: non-trivial to replicate LOGIAL_INO
|
||||
rv.push_back(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Plug a hole at EOF
|
||||
if (past_eof && !rv.empty()) {
|
||||
rv.rbegin()->m_flags |= FIEMAP_EXTENT_LAST;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
ExtentWalker::Vec
|
||||
ExtentWalker::get_extent_map(off_t pos)
|
||||
{
|
||||
Fiemap fm;
|
||||
fm.fm_start = ranged_cast<uint64_t>(pos);
|
||||
fm.fm_length = ranged_cast<uint64_t>(numeric_limits<off_t>::max() - pos);
|
||||
fm.m_max_count = fm.m_min_count = sc_extent_fetch_max;
|
||||
fm.do_ioctl(m_fd);
|
||||
Vec rv;
|
||||
for (auto i : fm.m_extents) {
|
||||
Extent e;
|
||||
e.m_begin = ranged_cast<off_t>(i.fe_logical);
|
||||
e.m_end = ranged_cast<off_t>(i.fe_logical + i.fe_length);
|
||||
e.m_physical = i.fe_physical;
|
||||
e.m_flags = i.fe_flags;
|
||||
rv.push_back(e);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
};
|
575
lib/fd.cc
Normal file
575
lib/fd.cc
Normal file
@@ -0,0 +1,575 @@
|
||||
#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);
|
||||
}
|
||||
|
||||
};
|
96
lib/interp.cc
Normal file
96
lib/interp.cc
Normal file
@@ -0,0 +1,96 @@
|
||||
#include "crucible/interp.h"
|
||||
|
||||
#include "crucible/chatter.h"
|
||||
|
||||
namespace crucible {
|
||||
using namespace std;
|
||||
|
||||
int
|
||||
Proc::exec(const ArgList &args)
|
||||
{
|
||||
return m_cmd(args);
|
||||
}
|
||||
|
||||
Proc::Proc(const function<int(const ArgList &)> &f) :
|
||||
m_cmd(f)
|
||||
{
|
||||
}
|
||||
|
||||
Command::~Command()
|
||||
{
|
||||
}
|
||||
|
||||
ArgList::ArgList(const char **argv)
|
||||
{
|
||||
while (argv && *argv) {
|
||||
push_back(*argv++);
|
||||
}
|
||||
}
|
||||
|
||||
ArgList::ArgList(const vector<string> &&that) :
|
||||
vector<string>(that)
|
||||
{
|
||||
}
|
||||
|
||||
Interp::~Interp()
|
||||
{
|
||||
}
|
||||
|
||||
Interp::Interp(const map<string, shared_ptr<Command> > &cmdlist) :
|
||||
m_commands(cmdlist)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
Interp::add_command(const string &name, const shared_ptr<Command> &command)
|
||||
{
|
||||
m_commands[name] = command;
|
||||
}
|
||||
|
||||
int
|
||||
Interp::exec(const ArgList &args)
|
||||
{
|
||||
auto next_arg = args.begin();
|
||||
++next_arg;
|
||||
return m_commands.at(args[0])->exec(vector<string>(next_arg, args.end()));
|
||||
}
|
||||
|
||||
ArgParser::~ArgParser()
|
||||
{
|
||||
}
|
||||
|
||||
ArgParser::ArgParser()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ArgParser::add_opt(string opt, ArgActor actor)
|
||||
{
|
||||
m_string_opts[opt] = actor;
|
||||
}
|
||||
|
||||
void
|
||||
ArgParser::parse_backend(void *t, const ArgList &args)
|
||||
{
|
||||
bool quote_args = false;
|
||||
for (string arg : args) {
|
||||
if (quote_args) {
|
||||
cerr << "arg: '" << arg << "'" << endl;
|
||||
continue;
|
||||
}
|
||||
if (arg == "--") {
|
||||
quote_args = true;
|
||||
continue;
|
||||
}
|
||||
if (arg.compare(0, 2, "--") == 0) {
|
||||
auto found = m_string_opts.find(arg.substr(2, string::npos));
|
||||
if (found != m_string_opts.end()) {
|
||||
found->second.predicate(t, "foo");
|
||||
}
|
||||
(void)t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
};
|
40
lib/ntoa.cc
Normal file
40
lib/ntoa.cc
Normal file
@@ -0,0 +1,40 @@
|
||||
#include "crucible/ntoa.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace crucible {
|
||||
using namespace std;
|
||||
|
||||
string bits_ntoa(unsigned long n, const bits_ntoa_table *table)
|
||||
{
|
||||
string out;
|
||||
while (n && table->a) {
|
||||
// No bits in n outside of mask
|
||||
assert( ((~table->mask) & table->n) == 0);
|
||||
if ( (n & table->mask) == table->n) {
|
||||
if (!out.empty()) {
|
||||
out += "|";
|
||||
}
|
||||
out += table->a;
|
||||
n &= ~(table->mask);
|
||||
}
|
||||
++table;
|
||||
}
|
||||
if (n) {
|
||||
ostringstream oss;
|
||||
oss << "0x" << hex << n;
|
||||
if (!out.empty()) {
|
||||
out += "|";
|
||||
}
|
||||
out += oss.str();
|
||||
}
|
||||
if (out.empty()) {
|
||||
out = "0";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
};
|
26
lib/path.cc
Normal file
26
lib/path.cc
Normal file
@@ -0,0 +1,26 @@
|
||||
#include "crucible/path.h"
|
||||
|
||||
#include "crucible/error.h"
|
||||
|
||||
namespace crucible {
|
||||
using namespace std;
|
||||
|
||||
string
|
||||
basename(string s)
|
||||
{
|
||||
size_t left = s.find_last_of("/");
|
||||
size_t right = s.find_last_not_of("/");
|
||||
if (left == string::npos) {
|
||||
return s;
|
||||
}
|
||||
return s.substr(left + 1, right);
|
||||
}
|
||||
|
||||
string
|
||||
join(string dir, string base)
|
||||
{
|
||||
// TODO: a lot of sanity checking, maybe canonicalization
|
||||
return dir + "/" + base;
|
||||
}
|
||||
|
||||
};
|
121
lib/process.cc
Normal file
121
lib/process.cc
Normal file
@@ -0,0 +1,121 @@
|
||||
#include "crucible/process.h"
|
||||
|
||||
#include "crucible/chatter.h"
|
||||
#include "crucible/error.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
// for gettid()
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
namespace crucible {
|
||||
using namespace std;
|
||||
|
||||
bool
|
||||
Process::joinable()
|
||||
{
|
||||
return !!m_pid;
|
||||
}
|
||||
|
||||
Process::~Process()
|
||||
{
|
||||
if (joinable()) {
|
||||
// because it's just not the same without the word "zombie"...
|
||||
CHATTER("ZOMBIE WARNING: joinable Process pid " << m_pid << " abandoned");
|
||||
}
|
||||
}
|
||||
|
||||
Process::Process() :
|
||||
m_pid(0)
|
||||
{
|
||||
}
|
||||
|
||||
Process::Process(Process &&move_from) :
|
||||
m_pid(0)
|
||||
{
|
||||
swap(m_pid, move_from.m_pid);
|
||||
}
|
||||
|
||||
void
|
||||
Process::do_fork(function<int()> child_func)
|
||||
{
|
||||
int rv = fork();
|
||||
if (rv < 0) {
|
||||
THROW_ERRNO("fork failed");
|
||||
}
|
||||
m_pid = rv;
|
||||
|
||||
if (rv == 0) {
|
||||
// child
|
||||
catch_all([&]() {
|
||||
int rv = child_func();
|
||||
exit(rv);
|
||||
});
|
||||
terminate();
|
||||
}
|
||||
}
|
||||
|
||||
Process::status_type
|
||||
Process::join()
|
||||
{
|
||||
if (m_pid == 0) {
|
||||
THROW_ERROR(invalid_argument, "Process not created");
|
||||
}
|
||||
|
||||
int status = 0;
|
||||
pid_t rv = waitpid(m_pid, &status, 0);
|
||||
if (rv == -1) {
|
||||
THROW_ERRNO("waitpid failed, pid = " << m_pid);
|
||||
}
|
||||
if (rv != m_pid) {
|
||||
THROW_ERROR(runtime_error, "waitpid failed, wanted pid = " << m_pid << ", got rv = " << rv << ", status = " << status);
|
||||
}
|
||||
m_pid = 0;
|
||||
return status;
|
||||
}
|
||||
|
||||
void
|
||||
Process::detach()
|
||||
{
|
||||
m_pid = 0;
|
||||
}
|
||||
|
||||
Process::native_handle_type
|
||||
Process::native_handle()
|
||||
{
|
||||
return m_pid;
|
||||
}
|
||||
|
||||
Process::id
|
||||
Process::get_id()
|
||||
{
|
||||
return m_pid;
|
||||
}
|
||||
|
||||
void
|
||||
Process::kill(int sig)
|
||||
{
|
||||
if (!m_pid) {
|
||||
THROW_ERROR(invalid_argument, "Process not created");
|
||||
}
|
||||
|
||||
int rv = ::kill(m_pid, sig);
|
||||
if (rv) {
|
||||
THROW_ERRNO("killing process " << m_pid << " with signal " << sig);
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
struct ResourceHandle<Process::id, Process>;
|
||||
|
||||
pid_t
|
||||
gettid()
|
||||
{
|
||||
return syscall(SYS_gettid);
|
||||
}
|
||||
|
||||
}
|
43
lib/string.cc
Normal file
43
lib/string.cc
Normal file
@@ -0,0 +1,43 @@
|
||||
#include "crucible/string.h"
|
||||
|
||||
#include "crucible/error.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
namespace crucible {
|
||||
using namespace std;
|
||||
|
||||
string
|
||||
to_hex(uint64_t i)
|
||||
{
|
||||
return astringprintf("0x%" PRIx64, i);
|
||||
}
|
||||
|
||||
uint64_t
|
||||
from_hex(const string &s)
|
||||
{
|
||||
return stoull(s, 0, 0);
|
||||
}
|
||||
|
||||
vector<string>
|
||||
split(string delim, string s)
|
||||
{
|
||||
if (delim.empty()) {
|
||||
THROW_ERROR(invalid_argument, "delimiter empty when splitting '" << s << "'");
|
||||
}
|
||||
vector<string> rv;
|
||||
size_t n = 0;
|
||||
while (n < s.length()) {
|
||||
size_t f = s.find(delim, n);
|
||||
if (f == string::npos) {
|
||||
rv.push_back(s.substr(n));
|
||||
break;
|
||||
}
|
||||
if (f > n) {
|
||||
rv.push_back(s.substr(n, f - n));
|
||||
}
|
||||
n = f + delim.length();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
};
|
158
lib/time.cc
Normal file
158
lib/time.cc
Normal file
@@ -0,0 +1,158 @@
|
||||
#include "crucible/time.h"
|
||||
|
||||
#include "crucible/error.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <ctime>
|
||||
#include <thread>
|
||||
|
||||
namespace crucible {
|
||||
|
||||
double
|
||||
nanosleep(double secs)
|
||||
{
|
||||
if (secs <= 0) return secs;
|
||||
|
||||
struct timespec req;
|
||||
req.tv_sec = time_t(floor(secs));
|
||||
req.tv_nsec = long((secs - floor(secs)) * 1000000000);
|
||||
|
||||
// Just silently ignore weirdo values for now
|
||||
if (req.tv_sec < 0) return secs;
|
||||
if (req.tv_sec > 1000000000) return secs;
|
||||
if (req.tv_nsec < 0) return secs;
|
||||
if (req.tv_nsec > 1000000000) return secs;
|
||||
|
||||
struct timespec rem;
|
||||
rem.tv_sec = 0;
|
||||
rem.tv_nsec = 0;
|
||||
|
||||
int nanosleep_rv = ::nanosleep(&req, &rem);
|
||||
if (nanosleep_rv) {
|
||||
THROW_ERRNO("nanosleep (" << secs << ") { tv_sec = " << req.tv_sec << ", tv_nsec = " << req.tv_nsec << " }");
|
||||
}
|
||||
return rem.tv_sec + (double(rem.tv_nsec) / 1000000000.0);
|
||||
}
|
||||
|
||||
Timer::Timer() :
|
||||
m_start(chrono::high_resolution_clock::now())
|
||||
{
|
||||
}
|
||||
|
||||
double
|
||||
Timer::age() const
|
||||
{
|
||||
chrono::high_resolution_clock::time_point end = chrono::high_resolution_clock::now();
|
||||
return chrono::duration<double>(end - m_start).count();
|
||||
}
|
||||
|
||||
double
|
||||
Timer::report(int precision) const
|
||||
{
|
||||
return ceil(age() * precision) / precision;
|
||||
}
|
||||
|
||||
void
|
||||
Timer::reset()
|
||||
{
|
||||
m_start = chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
void
|
||||
Timer::set(const chrono::high_resolution_clock::time_point &start)
|
||||
{
|
||||
m_start = start;
|
||||
}
|
||||
|
||||
void
|
||||
Timer::set(double delta)
|
||||
{
|
||||
m_start += chrono::duration_cast<chrono::high_resolution_clock::duration>(chrono::duration<double>(delta));
|
||||
}
|
||||
|
||||
double
|
||||
Timer::lap()
|
||||
{
|
||||
auto end = chrono::high_resolution_clock::now();
|
||||
double rv = chrono::duration<double>(end - m_start).count();
|
||||
m_start = end;
|
||||
return rv;
|
||||
}
|
||||
|
||||
ostream &
|
||||
operator<<(ostream &os, const Timer &t)
|
||||
{
|
||||
return os << t.report();
|
||||
}
|
||||
|
||||
bool
|
||||
Timer::operator<(double d) const
|
||||
{
|
||||
return age() < d;
|
||||
}
|
||||
|
||||
bool
|
||||
Timer::operator>(double d) const
|
||||
{
|
||||
return age() > d;
|
||||
}
|
||||
|
||||
RateLimiter::RateLimiter(double rate, double burst) :
|
||||
m_rate(rate),
|
||||
m_burst(burst)
|
||||
{
|
||||
}
|
||||
|
||||
RateLimiter::RateLimiter(double rate) :
|
||||
m_rate(rate),
|
||||
m_burst(rate)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
RateLimiter::update_tokens()
|
||||
{
|
||||
double delta = m_timer.lap();
|
||||
m_tokens += delta * m_rate;
|
||||
if (m_tokens > m_burst) {
|
||||
m_tokens = m_burst;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RateLimiter::sleep_for(double cost)
|
||||
{
|
||||
borrow(cost);
|
||||
while (1) {
|
||||
unique_lock<mutex> lock(m_mutex);
|
||||
update_tokens();
|
||||
if (m_tokens >= 0) {
|
||||
return;
|
||||
}
|
||||
double sleep_time(-m_tokens / m_rate);
|
||||
lock.unlock();
|
||||
if (sleep_time > 0.0) {
|
||||
nanosleep(sleep_time);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
RateLimiter::is_ready()
|
||||
{
|
||||
unique_lock<mutex> lock(m_mutex);
|
||||
update_tokens();
|
||||
return m_tokens >= 0;
|
||||
}
|
||||
|
||||
void
|
||||
RateLimiter::borrow(double cost)
|
||||
{
|
||||
unique_lock<mutex> lock(m_mutex);
|
||||
m_tokens -= cost;
|
||||
}
|
||||
|
||||
}
|
16
lib/uuid.cc
Normal file
16
lib/uuid.cc
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "crucible/uuid.h"
|
||||
|
||||
namespace crucible {
|
||||
using namespace std;
|
||||
|
||||
const size_t uuid_unparsed_size = 37; // "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\0"
|
||||
|
||||
string
|
||||
uuid_unparse(const unsigned char in[16])
|
||||
{
|
||||
char out[uuid_unparsed_size];
|
||||
::uuid_unparse(in, out);
|
||||
return string(out);
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user