summaryrefslogtreecommitdiff
path: root/ept/utils/sys.cc
diff options
context:
space:
mode:
Diffstat (limited to 'ept/utils/sys.cc')
-rw-r--r--ept/utils/sys.cc786
1 files changed, 786 insertions, 0 deletions
diff --git a/ept/utils/sys.cc b/ept/utils/sys.cc
new file mode 100644
index 0000000..8f6f2ff
--- /dev/null
+++ b/ept/utils/sys.cc
@@ -0,0 +1,786 @@
+#include "sys.h"
+#include "string.h"
+#include <cstddef>
+#include <cstring>
+#include <exception>
+#include <sstream>
+#include <system_error>
+#include <cerrno>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <alloca.h>
+
+namespace {
+
+inline const char* to_cstring(const std::string& s)
+{
+ return s.c_str();
+}
+
+inline const char* to_cstring(const char* s)
+{
+ return s;
+}
+
+}
+
+namespace ept {
+namespace sys {
+
+std::unique_ptr<struct stat> stat(const std::string& pathname)
+{
+ std::unique_ptr<struct stat> res(new struct stat);
+ if (::stat(pathname.c_str(), res.get()) == -1)
+ {
+ if (errno == ENOENT)
+ return std::unique_ptr<struct stat>();
+ else
+ throw std::system_error(errno, std::system_category(), "cannot stat " + pathname);
+ }
+ return res;
+}
+
+void stat(const std::string& pathname, struct stat& st)
+{
+ if (::stat(pathname.c_str(), &st) == -1)
+ throw std::system_error(errno, std::system_category(), "cannot stat " + pathname);
+}
+
+#define common_stat_body(testfunc) \
+ struct stat st; \
+ if (::stat(pathname.c_str(), &st) == -1) { \
+ if (errno == ENOENT) \
+ return false; \
+ else \
+ throw std::system_error(errno, std::system_category(), "cannot stat " + pathname); \
+ } \
+ return testfunc(st.st_mode)
+
+bool isdir(const std::string& pathname)
+{
+ common_stat_body(S_ISDIR);
+}
+
+bool isblk(const std::string& pathname)
+{
+ common_stat_body(S_ISBLK);
+}
+
+bool ischr(const std::string& pathname)
+{
+ common_stat_body(S_ISCHR);
+}
+
+bool isfifo(const std::string& pathname)
+{
+ common_stat_body(S_ISFIFO);
+}
+
+bool islnk(const std::string& pathname)
+{
+ common_stat_body(S_ISLNK);
+}
+
+bool isreg(const std::string& pathname)
+{
+ common_stat_body(S_ISREG);
+}
+
+bool issock(const std::string& pathname)
+{
+ common_stat_body(S_ISSOCK);
+}
+
+#undef common_stat_body
+
+time_t timestamp(const std::string& file)
+{
+ struct stat st;
+ stat(file, st);
+ return st.st_mtime;
+}
+
+time_t timestamp(const std::string& file, time_t def)
+{
+ auto st = sys::stat(file);
+ return st.get() ? st->st_mtime : def;
+}
+
+size_t size(const std::string& file)
+{
+ struct stat st;
+ stat(file, st);
+ return (size_t)st.st_size;
+}
+
+size_t size(const std::string& file, size_t def)
+{
+ auto st = sys::stat(file);
+ return st.get() ? (size_t)st->st_size : def;
+}
+
+ino_t inode(const std::string& file)
+{
+ struct stat st;
+ stat(file, st);
+ return st.st_ino;
+}
+
+ino_t inode(const std::string& file, ino_t def)
+{
+ auto st = sys::stat(file);
+ return st.get() ? st->st_ino : def;
+}
+
+
+bool access(const std::string &s, int m)
+{
+ return ::access(s.c_str(), m) == 0;
+}
+
+bool exists(const std::string& file)
+{
+ return sys::access(file, F_OK);
+}
+
+std::string getcwd()
+{
+#if defined(__GLIBC__)
+ char* cwd = ::get_current_dir_name();
+ if (cwd == NULL)
+ throw std::system_error(errno, std::system_category(), "cannot get the current working directory");
+ const std::string str(cwd);
+ ::free(cwd);
+ return str;
+#else
+ size_t size = pathconf(".", _PC_PATH_MAX);
+ char *buf = (char *)alloca( size );
+ if (::getcwd(buf, size) == NULL)
+ throw std::system_error(errno, std::system_category(), "cannot get the current working directory");
+ return buf;
+#endif
+}
+
+std::string abspath(const std::string& pathname)
+{
+ if (pathname[0] == '/')
+ return str::normpath(pathname);
+ else
+ return str::normpath(str::joinpath(sys::getcwd(), pathname));
+}
+
+
+/*
+ * MMap
+ */
+
+MMap::MMap(void* addr, size_t length)
+ : addr(addr), length(length)
+{
+}
+
+MMap::MMap(MMap&& o)
+ : addr(o.addr), length(o.length)
+{
+ o.addr = MAP_FAILED;
+ o.length = 0;
+}
+
+MMap& MMap::operator=(MMap&& o)
+{
+ if (this == &o) return *this;
+
+ munmap();
+ addr = o.addr;
+ length = o.length;
+ o.addr = MAP_FAILED;
+ o.length = 0;
+ return *this;
+}
+
+MMap::~MMap()
+{
+ if (addr != MAP_FAILED) ::munmap(addr, length);
+}
+
+void MMap::munmap()
+{
+ if (::munmap(addr, length) == -1)
+ throw std::system_error(errno, std::system_category(), "cannot unmap memory");
+ addr = MAP_FAILED;
+}
+
+
+/*
+ * FileDescriptor
+ */
+
+FileDescriptor::FileDescriptor() {}
+FileDescriptor::FileDescriptor(FileDescriptor&& o)
+ : fd(o.fd)
+{
+ o.fd = -1;
+}
+FileDescriptor::FileDescriptor(int fd) : fd(fd) {}
+FileDescriptor::~FileDescriptor() {}
+
+void FileDescriptor::throw_error(const char* desc)
+{
+ throw std::system_error(errno, std::system_category(), desc);
+}
+
+void FileDescriptor::close()
+{
+ if (fd == -1) return;
+ if (::close(fd) == -1)
+ throw_error("cannot close");
+ fd = -1;
+}
+
+void FileDescriptor::fstat(struct stat& st)
+{
+ if (::fstat(fd, &st) == -1)
+ throw_error("cannot stat");
+}
+
+void FileDescriptor::fchmod(mode_t mode)
+{
+ if (::fchmod(fd, mode) == -1)
+ throw_error("cannot fchmod");
+}
+
+size_t FileDescriptor::write(const void* buf, size_t count)
+{
+ ssize_t res = ::write(fd, buf, count);
+ if (res == -1)
+ throw_error("cannot write");
+ return res;
+}
+
+void FileDescriptor::write_all(const void* buf, size_t count)
+{
+ size_t written = 0;
+ while (written < count)
+ written += write((unsigned char*)buf + written, count - written);
+}
+
+MMap FileDescriptor::mmap(size_t length, int prot, int flags, off_t offset)
+{
+ void* res =::mmap(0, length, prot, flags, fd, offset);
+ if (res == MAP_FAILED)
+ throw_error("cannot mmap");
+ return MMap(res, length);
+}
+
+
+/*
+ * NamedFileDescriptor
+ */
+
+NamedFileDescriptor::NamedFileDescriptor(int fd, const std::string& pathname)
+ : FileDescriptor(fd), pathname(pathname)
+{
+}
+
+NamedFileDescriptor::NamedFileDescriptor(NamedFileDescriptor&& o)
+ : FileDescriptor(std::move(o)), pathname(std::move(o.pathname))
+{
+}
+
+NamedFileDescriptor& NamedFileDescriptor::operator=(NamedFileDescriptor&& o)
+{
+ if (this == &o) return *this;
+ fd = o.fd;
+ pathname = std::move(o.pathname);
+ o.fd = -1;
+ return *this;
+}
+
+void NamedFileDescriptor::throw_error(const char* desc)
+{
+ throw std::system_error(errno, std::system_category(), pathname + ": " + desc);
+}
+
+
+/*
+ * Path
+ */
+
+Path::Path(const char* pathname, int flags)
+ : NamedFileDescriptor(-1, pathname)
+{
+ fd = open(pathname, flags | O_PATH);
+ if (fd == -1)
+ throw_error("cannot open path");
+}
+
+Path::Path(const std::string& pathname, int flags)
+ : NamedFileDescriptor(-1, pathname)
+{
+ fd = open(pathname.c_str(), flags | O_PATH);
+ if (fd == -1)
+ throw_error("cannot open path");
+}
+
+Path::Path(Path& parent, const char* pathname, int flags)
+ : NamedFileDescriptor(parent.openat(pathname, flags | O_PATH),
+ str::joinpath(parent.name(), pathname))
+{
+}
+
+Path::~Path()
+{
+ if (fd != -1)
+ ::close(fd);
+}
+
+DIR* Path::fdopendir()
+{
+ int fd1 = ::openat(fd, ".", O_DIRECTORY);
+ if (fd1 == -1)
+ throw_error("cannot open directory");
+
+ DIR* res = ::fdopendir(fd1);
+ if (!res)
+ throw_error("cannot fdopendir");
+
+ return res;
+}
+
+Path::iterator Path::begin()
+{
+ if (fd == -1)
+ return iterator();
+ else
+ return iterator(*this);
+}
+
+Path::iterator Path::end()
+{
+ return iterator();
+}
+
+int Path::openat(const char* pathname, int flags, mode_t mode)
+{
+ int res = ::openat(fd, pathname, flags, mode);
+ if (res == -1)
+ throw_error("cannot openat");
+ return res;
+}
+
+void Path::fstatat(const char* pathname, struct stat& st)
+{
+ if (::fstatat(fd, pathname, &st, 0) == -1)
+ throw_error("cannot fstatat");
+}
+
+void Path::lstatat(const char* pathname, struct stat& st)
+{
+ if (::fstatat(fd, pathname, &st, AT_SYMLINK_NOFOLLOW) == -1)
+ throw_error("cannot fstatat");
+}
+
+void Path::unlinkat(const char* pathname)
+{
+ if (::unlinkat(fd, pathname, 0) == -1)
+ throw_error("cannot unlinkat");
+}
+
+void Path::rmdirat(const char* pathname)
+{
+ if (::unlinkat(fd, pathname, AT_REMOVEDIR) == -1)
+ throw_error("cannot unlinkat");
+}
+
+Path::iterator::iterator()
+{
+}
+
+Path::iterator::iterator(Path& dir)
+ : path(&dir)
+{
+ this->dir = dir.fdopendir();
+
+ long name_max = fpathconf(dir.fd, _PC_NAME_MAX);
+ if (name_max == -1) // Limit not defined, or error: take a guess
+ name_max = 255;
+ size_t len = offsetof(dirent, d_name) + name_max + 1;
+ cur_entry = (struct dirent*)malloc(len);
+ if (cur_entry == NULL)
+ throw std::bad_alloc();
+
+ operator++();
+}
+
+Path::iterator::~iterator()
+{
+ if (cur_entry) free(cur_entry);
+ if (dir) closedir(dir);
+}
+
+bool Path::iterator::operator==(const iterator& i) const
+{
+ if (!dir && !i.dir) return true;
+ if (!dir || !i.dir) return false;
+ return cur_entry->d_ino == i.cur_entry->d_ino;
+}
+bool Path::iterator::operator!=(const iterator& i) const
+{
+ if (!dir && !i.dir) return false;
+ if (!dir || !i.dir) return true;
+ return cur_entry->d_ino != i.cur_entry->d_ino;
+}
+
+void Path::iterator::operator++()
+{
+ struct dirent* result;
+ if (readdir_r(dir, cur_entry, &result) != 0)
+ path->throw_error("cannot readdir_r");
+
+ if (result == nullptr)
+ {
+ // Turn into an end iterator
+ free(cur_entry);
+ cur_entry = nullptr;
+ closedir(dir);
+ dir = nullptr;
+ }
+}
+
+bool Path::iterator::isdir() const
+{
+#if defined(_DIRENT_HAVE_D_TYPE) || defined(HAVE_STRUCT_DIRENT_D_TYPE)
+ if (cur_entry->d_type == DT_DIR)
+ return true;
+ if (cur_entry->d_type != DT_UNKNOWN)
+ return false;
+#endif
+ // No d_type, we'll need to stat
+ struct stat st;
+ path->fstatat(cur_entry->d_name, st);
+ return S_ISDIR(st.st_mode);
+}
+
+bool Path::iterator::isblk() const
+{
+#if defined(_DIRENT_HAVE_D_TYPE) || defined(HAVE_STRUCT_DIRENT_D_TYPE)
+ if (cur_entry->d_type == DT_BLK)
+ return true;
+ if (cur_entry->d_type != DT_UNKNOWN)
+ return false;
+#endif
+ // No d_type, we'll need to stat
+ struct stat st;
+ path->fstatat(cur_entry->d_name, st);
+ return S_ISBLK(st.st_mode);
+}
+
+bool Path::iterator::ischr() const
+{
+#if defined(_DIRENT_HAVE_D_TYPE) || defined(HAVE_STRUCT_DIRENT_D_TYPE)
+ if (cur_entry->d_type == DT_CHR)
+ return true;
+ if (cur_entry->d_type != DT_UNKNOWN)
+ return false;
+#endif
+ // No d_type, we'll need to stat
+ struct stat st;
+ path->fstatat(cur_entry->d_name, st);
+ return S_ISCHR(st.st_mode);
+}
+
+bool Path::iterator::isfifo() const
+{
+#if defined(_DIRENT_HAVE_D_TYPE) || defined(HAVE_STRUCT_DIRENT_D_TYPE)
+ if (cur_entry->d_type == DT_FIFO)
+ return true;
+ if (cur_entry->d_type != DT_UNKNOWN)
+ return false;
+#endif
+ // No d_type, we'll need to stat
+ struct stat st;
+ path->fstatat(cur_entry->d_name, st);
+ return S_ISFIFO(st.st_mode);
+}
+
+bool Path::iterator::islnk() const
+{
+#if defined(_DIRENT_HAVE_D_TYPE) || defined(HAVE_STRUCT_DIRENT_D_TYPE)
+ if (cur_entry->d_type == DT_LNK)
+ return true;
+ if (cur_entry->d_type != DT_UNKNOWN)
+ return false;
+#endif
+ struct stat st;
+ path->fstatat(cur_entry->d_name, st);
+ return S_ISLNK(st.st_mode);
+}
+
+bool Path::iterator::isreg() const
+{
+#if defined(_DIRENT_HAVE_D_TYPE) || defined(HAVE_STRUCT_DIRENT_D_TYPE)
+ if (cur_entry->d_type == DT_REG)
+ return true;
+ if (cur_entry->d_type != DT_UNKNOWN)
+ return false;
+#endif
+ struct stat st;
+ path->fstatat(cur_entry->d_name, st);
+ return S_ISREG(st.st_mode);
+}
+
+bool Path::iterator::issock() const
+{
+#if defined(_DIRENT_HAVE_D_TYPE) || defined(HAVE_STRUCT_DIRENT_D_TYPE)
+ if (cur_entry->d_type == DT_SOCK)
+ return true;
+ if (cur_entry->d_type != DT_UNKNOWN)
+ return false;
+#endif
+ struct stat st;
+ path->fstatat(cur_entry->d_name, st);
+ return S_ISSOCK(st.st_mode);
+}
+
+
+void Path::rmtree()
+{
+ for (auto i = begin(); i != end(); ++i)
+ {
+ if (strcmp(i->d_name, ".") == 0 || strcmp(i->d_name, "..") == 0) continue;
+ if (i.isdir())
+ {
+ Path sub(*this, i->d_name);
+ sub.rmtree();
+ }
+ else
+ unlinkat(i->d_name);
+ }
+ // TODO: is there a way to do this using fd instead?
+ rmdir(name());
+}
+
+/*
+ * File
+ */
+
+File::File(const std::string& pathname, int flags, mode_t mode)
+ : NamedFileDescriptor(-1, pathname)
+{
+ fd = open(pathname.c_str(), flags, mode);
+ if (fd == -1)
+ throw std::system_error(errno, std::system_category(), "cannot open file " + pathname);
+}
+
+File::~File()
+{
+ if (fd != -1) ::close(fd);
+}
+
+File File::mkstemp(const std::string& prefix)
+{
+ char* fbuf = (char*)alloca(prefix.size() + 7);
+ memcpy(fbuf, prefix.data(), prefix.size());
+ memcpy(fbuf + prefix.size(), "XXXXXX", 7);
+ int fd = ::mkstemp(fbuf);
+ if (fd < 0)
+ throw std::system_error(errno, std::system_category(), std::string("cannot create temporary file ") + fbuf);
+ return File(fd, fbuf);
+}
+
+std::string read_file(const std::string& file)
+{
+ File in(file, O_RDONLY);
+
+ // Get the file size
+ struct stat st;
+ in.fstat(st);
+
+ // mmap the input file
+ MMap src = in.mmap(st.st_size, PROT_READ, MAP_SHARED);
+
+ return std::string((const char*)src, st.st_size);
+}
+
+void write_file(const std::string& file, const std::string& data, mode_t mode)
+{
+ File out(file, O_WRONLY | O_CREAT, mode);
+ out.write_all(data.data(), data.size());
+ out.close();
+}
+
+void write_file_atomically(const std::string& file, const std::string& data, mode_t mode)
+{
+ File out = File::mkstemp(file);
+
+ // Read the umask
+ mode_t mask = umask(0777);
+ umask(mask);
+
+ // Set the file permissions, honoring umask
+ out.fchmod(mode & ~mask);
+
+ out.write_all(data.data(), data.size());
+ out.close();
+
+ if (rename(out.name().c_str(), file.c_str()) < 0)
+ throw std::system_error(errno, std::system_category(), "cannot rename " + out.name() + " to " + file);
+}
+
+#if 0
+void mkFilePath(const std::string& file)
+{
+ size_t pos = file.rfind('/');
+ if (pos != std::string::npos)
+ mkpath(file.substr(0, pos));
+}
+#endif
+
+bool unlink_ifexists(const std::string& file)
+{
+ if (::unlink(file.c_str()) != 0)
+ {
+ if (errno != ENOENT)
+ throw std::system_error(errno, std::system_category(), "cannot unlink " + file);
+ else
+ return false;
+ }
+ else
+ return true;
+}
+
+bool rename_ifexists(const std::string& src, const std::string& dst)
+{
+ if (::rename(src.c_str(), dst.c_str()) != 0)
+ {
+ if (errno != ENOENT)
+ throw std::system_error(errno, std::system_category(), "cannot rename " + src + " to " + dst);
+ else
+ return false;
+ }
+ else
+ return true;
+}
+
+template<typename String>
+static void impl_mkdir_ifmissing(String pathname, mode_t mode)
+{
+ for (unsigned i = 0; i < 5; ++i)
+ {
+ // If it does not exist, make it
+ if (::mkdir(to_cstring(pathname), mode) != -1)
+ return;
+
+ // throw on all errors except EEXIST. Note that EEXIST "includes the case
+ // where pathname is a symbolic link, dangling or not."
+ if (errno != EEXIST && errno != EISDIR)
+ {
+ std::stringstream msg;
+ msg << "cannot create directory " << pathname;
+ throw std::system_error(errno, std::system_category(), msg.str());
+ }
+
+ // Ensure that, if dir exists, it is a directory
+ std::unique_ptr<struct stat> st = sys::stat(pathname);
+ if (st.get() == NULL)
+ {
+ // Either dir has just been deleted, or we hit a dangling
+ // symlink.
+ //
+ // Retry creating a directory: the more we keep failing, the more
+ // the likelyhood of a dangling symlink increases.
+ //
+ // We could lstat here, but it would add yet another case for a
+ // race condition if the broken symlink gets deleted between the
+ // stat and the lstat.
+ continue;
+ }
+ else if (!S_ISDIR(st->st_mode))
+ {
+ // If it exists but it is not a directory, complain
+ std::stringstream msg;
+ msg << pathname << " exists but is not a directory";
+ throw std::runtime_error(msg.str());
+ }
+ else
+ // If it exists and it is a directory, we're fine
+ return;
+ }
+ std::stringstream msg;
+ msg << pathname << " exists and looks like a dangling symlink";
+ throw std::runtime_error(msg.str());
+}
+
+void mkdir_ifmissing(const char* pathname, mode_t mode)
+{
+ return impl_mkdir_ifmissing(pathname, mode);
+}
+
+void mkdir_ifmissing(const std::string& pathname, mode_t mode)
+{
+ return impl_mkdir_ifmissing(pathname, mode);
+}
+
+void makedirs(const std::string& pathname, mode_t mode)
+{
+ if (pathname == "/" || pathname == ".") return;
+ std::string parent = str::dirname(pathname);
+
+ // First ensure that the upper path exists
+ makedirs(parent, mode);
+
+ // Then create this dir
+ mkdir_ifmissing(pathname, mode);
+}
+
+std::string which(const std::string& name)
+{
+ // argv[0] has an explicit path: ensure it becomes absolute
+ if (name.find('/') != std::string::npos)
+ return sys::abspath(name);
+
+ // argv[0] has no explicit path, look for it in $PATH
+ const char* path = getenv("PATH");
+ if (!path) return name;
+
+ str::Split splitter(path, ":", true);
+ for (const auto& i: splitter)
+ {
+ std::string candidate = str::joinpath(i, name);
+ if (sys::access(candidate, X_OK))
+ return sys::abspath(candidate);
+ }
+
+ return name;
+}
+
+void unlink(const std::string& pathname)
+{
+ if (::unlink(pathname.c_str()) < 0)
+ throw std::system_error(errno, std::system_category(), "cannot unlink " + pathname);
+}
+
+void rmdir(const std::string& pathname)
+{
+ if (::rmdir(pathname.c_str()) < 0)
+ throw std::system_error(errno, std::system_category(), "cannot rmdir " + pathname);
+}
+
+void rmtree(const std::string& pathname)
+{
+ Path path(pathname);
+ path.rmtree();
+}
+
+#if 0
+std::string mkdtemp( std::string tmpl )
+{
+ char *_tmpl = reinterpret_cast< char * >( alloca( tmpl.size() + 1 ) );
+ strcpy( _tmpl, tmpl.c_str() );
+ return ::mkdtemp( _tmpl );
+}
+#endif
+}
+}