13#include <cosmos/GroupInfo.hxx> 
   14#include <cosmos/PasswdInfo.hxx> 
   15#include <cosmos/error/ApiError.hxx> 
   16#include <cosmos/error/FileError.hxx> 
   17#include <cosmos/error/InternalError.hxx> 
   18#include <cosmos/error/RuntimeError.hxx> 
   19#include <cosmos/error/UsageError.hxx> 
   20#include <cosmos/formatting.hxx> 
   21#include <cosmos/fs/DirIterator.hxx> 
   22#include <cosmos/fs/DirStream.hxx> 
   23#include <cosmos/fs/Directory.hxx> 
   24#include <cosmos/fs/File.hxx> 
   25#include <cosmos/fs/FileStatus.hxx> 
   26#include <cosmos/fs/filesystem.hxx> 
   27#include <cosmos/fs/path.hxx> 
   28#include <cosmos/proc/process.hxx> 
   29#include <cosmos/string.hxx> 
   30#include <cosmos/utils.hxx> 
   35                const SysString path, 
const OpenMode mode,
 
   36                const OpenFlags flags, 
const std::optional<FileMode> fmode) {
 
   38        if (flags.anyOf({OpenFlag::CREATE, OpenFlag::TMPFILE}) && !fmode) {
 
   39                cosmos_throw (UsageError(
"the given open flags require an fmode argument"));
 
   42        int raw_flags = flags.
raw() | to_integral(mode);
 
   45        auto fd = ::open(path.raw(), raw_flags, fmode ? to_integral(fmode.value().raw()) : 0);
 
   48                cosmos_throw (FileError(path, 
"open"));
 
   51        return FileDescriptor{
FileNum{fd}};
 
   54FileDescriptor open_at(
 
   55                const DirFD dir_fd, 
const SysString path,
 
   56                const OpenMode mode, 
const OpenFlags flags,
 
   57                const std::optional<FileMode> fmode) {
 
   58        int raw_flags = flags.
raw() | to_integral(mode);
 
   60        if (flags.anyOf({OpenFlag::CREATE, OpenFlag::TMPFILE}) && !fmode) {
 
   61                cosmos_throw (UsageError(
"the given open flags require an fmode argument"));
 
   64        auto fd = ::openat(to_integral(dir_fd.raw()), path.raw(),
 
   65                                raw_flags, fmode ? to_integral(fmode.value().raw()) : 0);
 
   68                cosmos_throw (FileError(path, 
"openat"));
 
   71        return FileDescriptor{
FileNum{fd}};
 
   74void close_range(
const FileNum first, 
const FileNum last, 
const CloseRangeFlags flags) {
 
   78        if (::close_range(to_integral(first), to_integral(last), flags.raw()) != 0) {
 
   79                cosmos_throw (ApiError(
"close_range()"));
 
   85        std::pair<std::string, int> expand_temp_path(
const SysString str) {
 
   88                auto _template = str.view();
 
   89                auto lastsep = _template.rfind(
'/');
 
   91                if (lastsep != _template.npos) {
 
   92                        path = _template.substr(0, lastsep + 1);
 
   93                        base = _template.substr(lastsep + 1);
 
   99                        cosmos_throw (UsageError(
"empty basename not allowed"));
 
  102                constexpr auto XS = 
"XXXXXX";
 
  103                constexpr auto PLACEHOLDER = 
"{}";
 
  106                if (
auto placeholder_pos = base.rfind(PLACEHOLDER); placeholder_pos != base.npos) {
 
  107                        suffixlen = base.size() - placeholder_pos - 2;
 
  108                        base.replace(placeholder_pos, 2, XS);
 
  115                return {path, suffixlen};
 
  120std::pair<FileDescriptor, std::string> make_tempfile(
 
  121                const SysString _template, 
const OpenFlags flags) {
 
  123        auto [path, suffixlen] = expand_temp_path(_template);
 
  125        const auto fd = ::mkostemps(path.data(), suffixlen, flags.raw());
 
  128                cosmos_throw (ApiError(
"mkostemps()"));
 
  131        return {FileDescriptor{
FileNum{fd}}, path};
 
  134std::string make_tempdir(
const SysString _template) {
 
  137        std::string expanded{_template};
 
  138        expanded += 
"XXXXXX";
 
  140        if (::mkdtemp(expanded.data()) == 
nullptr) {
 
  141                cosmos_throw (ApiError(
"mkdtemp()"));
 
  147void make_fifo(
const SysString path, 
const FileMode mode) {
 
  148        auto res = ::mkfifo(path.raw(), to_integral(mode.raw()));
 
  151                cosmos_throw (FileError(path, 
"mkfifo()"));
 
  155void make_fifo_at(
const DirFD dir_fd, 
const SysString path,
 
  156                const FileMode mode) {
 
  157        auto res = ::mkfifoat(to_integral(dir_fd.raw()), path.raw(), to_integral(mode.raw()));
 
  160                cosmos_throw (FileError(path, 
"mkfifoat()"));
 
  164FileMode set_umask(
const FileMode mode) {
 
  165        auto raw_mode = to_integral(mode.raw());
 
  167        if ((raw_mode & ~0777) != 0) {
 
  168                cosmos_throw (UsageError(
"invalid bits set in umask"));
 
  171        auto old_mode = ::umask(raw_mode);
 
  173        return FileMode{
ModeT{old_mode}};
 
  176bool exists_file(
const SysString path) {
 
  178        if (::lstat(path.raw(), &s) == 0)
 
  181                cosmos_throw (FileError(path, 
"lstat()"));
 
  186void unlink_file(
const SysString path) {
 
  187        if (::unlink(path.raw()) != 0) {
 
  188                cosmos_throw (FileError(path, 
"unlink()"));
 
  192void unlink_file_at(
const DirFD dir_fd, 
const SysString path) {
 
  193        if (::unlinkat(to_integral(dir_fd.raw()), path.raw(), 0) != 0) {
 
  194                cosmos_throw (FileError(path, 
"unlinkat()"));
 
  198void change_dir(
const SysString path) {
 
  199        if (::chdir(path.raw()) != 0) {
 
  200                cosmos_throw (FileError(path, 
"chdir()"));
 
  204std::string get_working_dir() {
 
  209                if (
auto res = ::getcwd(ret.data(), ret.size()); res == 
nullptr) {
 
  213                                        ret.resize(ret.size() * 2);
 
  216                                default: cosmos_throw (ApiError(
"getcwd()"));
 
  221                if (
auto termpos = ret.find(
'\0'); termpos != ret.npos) {
 
  229std::optional<std::string> which(
const std::string_view exec_base) 
noexcept {
 
  231        auto checkExecutable = [](
const std::string &path) -> 
bool {
 
  233                        File f{path, OpenMode::READ_ONLY};
 
  237                                status.updateFrom(f.fd());
 
  238                        } 
catch (
const CosmosError &) {
 
  242                        if (!status.type().isRegular())
 
  244                        else if (!status.mode().canAnyExec())
 
  254        if (exec_base.empty())
 
  257        if (exec_base.front() == 
'/') {
 
  259                if (checkExecutable(std::string{exec_base})) {
 
  260                       return {std::string{exec_base}};
 
  265        const auto pathvar = proc::get_env_var(
"PATH");
 
  269        const auto paths = split(*pathvar, 
":");
 
  271        for (
const auto &dir: paths) {
 
  272                auto path = dir + 
"/" + std::string{exec_base};
 
  274                if (checkExecutable(path)) {
 
  282void make_dir(
const SysString path, 
const FileMode mode) {
 
  283        if (::mkdir(path.raw(), to_integral(mode.raw())) != 0) {
 
  284                cosmos_throw (FileError(path, 
"mkdir()"));
 
  288void make_dir_at(
const DirFD dir_fd, 
const SysString path, 
const FileMode mode) {
 
  289        if (::mkdirat(to_integral(dir_fd.raw()), path.raw(), to_integral(mode.raw())) != 0) {
 
  290                cosmos_throw (FileError(path, 
"mkdirat()"));
 
  294void remove_dir(
const SysString path) {
 
  295        if (::rmdir(path.raw()) != 0) {
 
  296                cosmos_throw (FileError(path, 
"rmdir()"));
 
  300void remove_dir_at(
const DirFD dir_fd, 
const SysString path) {
 
  301        if (::unlinkat(to_integral(dir_fd.raw()), path.raw(), AT_REMOVEDIR) != 0) {
 
  302                cosmos_throw (FileError(path, 
"unlinkat(AT_REMOVEDIR)"));
 
  306Errno make_all_dirs(
const SysString path, 
const FileMode mode) {
 
  307        const auto normpath = normalize_path(path);
 
  310        Errno ret{Errno::EXISTS};
 
  313                cosmos_throw (UsageError(
"empty string passed in"));
 
  316        while (sep_pos != normpath.npos) {
 
  317                sep_pos = normpath.find(
'/', sep_pos + 1);
 
  318                prefix = normpath.substr(0, sep_pos);
 
  320                if (prefix.back() == 
'/') {
 
  323                } 
else if (prefix == 
".") {
 
  328                if (::mkdir(prefix.data(), to_integral(mode.raw())) != 0) {
 
  333                        cosmos_throw (FileError(prefix, 
"mkdir()"));
 
  337                ret = Errno::NO_ERROR;
 
  345        void remove_tree(DirStream &stream) {
 
  347                const auto dir_fd = stream.fd();
 
  349                using Type = DirEntry::Type;
 
  351                for (
const auto entry: stream) {
 
  353                        if (entry.isDotEntry())
 
  356                        const auto name = entry.name();
 
  358                        switch(entry.type()) {
 
  359                                case Type::UNKNOWN: {
 
  360                                        const FileStatus fs{dir_fd, name};
 
  361                                        if (fs.type().isDirectory())
 
  366                                case Type::DIRECTORY:
 
  369                                        DirStream subdir{dir_fd, name};
 
  371                                        dir.removeDirAt(name);
 
  376                                        dir.unlinkFileAt(name);
 
  385void remove_tree(
const SysString path) {
 
  391void change_mode(
const SysString path, 
const FileMode mode) {
 
  392        if (::chmod(path.raw(), to_integral(mode.raw())) != 0) {
 
  393                cosmos_throw (FileError(path, 
"chmod()"));
 
  397void change_mode(
const FileDescriptor fd, 
const FileMode mode) {
 
  398        if (::fchmod(to_integral(fd.raw()), to_integral(mode.raw())) != 0) {
 
  399                cosmos_throw (FileError(
"(fd)", 
"fchmod()"));
 
  403void change_owner(
const SysString path, 
const UserID uid, 
const GroupID gid) {
 
  404        if (::chown(path.raw(), to_integral(uid), to_integral(gid)) != 0) {
 
  405                cosmos_throw (FileError(path, 
"chown()"));
 
  409void change_owner(
const FileDescriptor fd, 
const UserID uid, 
const GroupID gid) {
 
  410        if (::fchown(to_integral(fd.raw()), to_integral(uid), to_integral(gid)) != 0) {
 
  411                cosmos_throw (FileError(
"(fd)", 
"fchown()"));
 
  417UserID resolve_user(
const SysString user) {
 
  419                return UserID::INVALID;
 
  422        PasswdInfo info{user};
 
  424                cosmos_throw (RuntimeError{user.str() + 
" does not exist"});
 
  430GroupID resolve_group(
const SysString group) {
 
  432                return GroupID::INVALID;
 
  435        GroupInfo info{group};
 
  437                cosmos_throw (RuntimeError{group.str() + 
"does not exist"});
 
  445void change_owner(
const SysString path, 
const SysString user, 
const SysString group) {
 
  447        const UserID uid = resolve_user(user);
 
  448        const GroupID gid = resolve_group(group);
 
  449        change_owner(path, uid, gid);
 
  452void change_owner(
const FileDescriptor fd, 
const SysString user, 
const SysString group) {
 
  453        const UserID uid = resolve_user(user);
 
  454        const GroupID gid = resolve_group(group);
 
  455        change_owner(fd, uid, gid);
 
  458void change_owner_nofollow(
const SysString path, 
const UserID uid, 
const GroupID gid) {
 
  459        if (::lchown(path.raw(), to_integral(uid), to_integral(gid)) != 0) {
 
  460                cosmos_throw (FileError(path, 
"lchown()"));
 
  464void change_owner_nofollow(
const SysString path, 
const SysString user, 
const SysString group) {
 
  465        const UserID uid = resolve_user(user);
 
  466        const GroupID gid = resolve_group(group);
 
  467        change_owner_nofollow(path, uid, gid);
 
  470void make_symlink(
const SysString target, 
const SysString path) {
 
  471        if (::symlink(target.raw(), path.raw()) != 0) {
 
  472                cosmos_throw (FileError(path, 
"symlink()"));
 
  476void make_symlink_at(
const SysString target, 
const DirFD dir_fd,
 
  477                const SysString path) {
 
  478        if (::symlinkat(target.raw(), to_integral(dir_fd.raw()), path.raw()) != 0) {
 
  479                cosmos_throw (FileError(path, 
"symlinkat()"));
 
  485        std::string read_symlink(
const SysString path,
 
  486                        const std::string_view call, std::function<
int(
const char*, 
char*, 
size_t)> readlink_func) {
 
  491                        auto res = readlink_func(path.raw(), &ret.front(), ret.size());
 
  494                                cosmos_throw (FileError(path, call));
 
  499                        auto len = 
static_cast<size_t>(res);
 
  501                        if (len < ret.size()) {
 
  515std::string read_symlink(
const SysString path) {
 
  516        return read_symlink(path, 
"readlink()", ::readlink);
 
  519std::string read_symlink_at(
const DirFD dir_fd, 
const SysString path) {
 
  521        auto readlink_func = [&](
const char *p, 
char *buf, 
size_t size) {
 
  522                return ::readlinkat(to_integral(dir_fd.raw()), p, buf, size);
 
  525        return read_symlink(path, 
"readlinkat()", readlink_func);
 
  528void link(
const SysString old_path, 
const SysString new_path) {
 
  529        if (::link(old_path.raw(), new_path.raw()) != 0) {
 
  530                cosmos_throw (FileError(new_path, std::string{
"link() for "} + std::string{old_path}));
 
  534void linkat(
const DirFD old_dir, 
const SysString old_path,
 
  535                const DirFD new_dir, 
const SysString new_path,
 
  536                const FollowSymlinks follow_old) {
 
  538                                to_integral(old_dir.raw()), old_path.raw(),
 
  539                                to_integral(new_dir.raw()), new_path.raw(),
 
  540                                follow_old ? AT_SYMLINK_FOLLOW : 0) != 0) {
 
  541                cosmos_throw (FileError(new_path, std::string{
"linkat() for "} + std::string{old_path}));
 
  545void linkat_fd(
const FileDescriptor fd, 
const DirFD new_dir, 
const SysString new_path) {
 
  547                                to_integral(fd.raw()), 
"",
 
  548                                to_integral(new_dir.raw()), new_path.raw(),
 
  549                                AT_EMPTY_PATH) != 0) {
 
  551                cosmos_throw (FileError(new_path, std::string{
"linkat(AT_EMPTY_PATH)"}));
 
  555void linkat_proc_fd(
const FileDescriptor fd, 
const DirFD new_dir, 
const SysString new_path) {
 
  568                        AT_CWD, cosmos::sprintf(
"/proc/self/fd/%d", to_integral(fd.raw())),
 
  569                        new_dir, new_path, FollowSymlinks{true});
 
  572void truncate(
const FileDescriptor fd, off_t length) {
 
  573        if (::ftruncate(to_integral(fd.raw()), length) != 0) {
 
  574                cosmos_throw (ApiError(
"ftruncate()"));
 
  578void truncate(
const SysString path, off_t length) {
 
  579        if (::truncate(path.raw(), length) != 0) {
 
  580                cosmos_throw (ApiError(
"truncate()"));
 
  586        size_t copy_file_range(
 
  587                        const FileDescriptor fd_in, off_t *off_in,
 
  588                        const FileDescriptor fd_out, off_t *off_out,
 
  592                const auto res = ::copy_file_range(
 
  593                                to_integral(fd_in.raw()),  off_in,
 
  594                                to_integral(fd_out.raw()), off_out,
 
  598                        cosmos_throw (ApiError(
"copy_file_range()"));
 
  601                return static_cast<size_t>(res);
 
  606size_t copy_file_range(
 
  607                const FileDescriptor fd_in, 
const FileDescriptor fd_out,
 
  609        return copy_file_range(fd_in, 
nullptr, fd_out, 
nullptr, len);
 
  612size_t copy_file_range(CopyFileRangeParameters &pars) {
 
  613        auto copied = copy_file_range(
 
  614                pars.in,  pars.off_in  ? &pars.off_in.value()  : 
nullptr,
 
  615                pars.out, pars.off_out ? &pars.off_out.value() : 
nullptr,
 
  625static_assert(F_OK == 0, 
"F_OK is non-zero, breaking check_access()");
 
  627void check_access(
const SysString path, 
const AccessChecks checks) {
 
  628        if (::access(path.raw(), checks.raw()) == 0) {
 
  632        cosmos_throw (ApiError(
"access()"));
 
  635void check_access_at(
const DirFD dir_fd, 
const SysString path,
 
  636                const AccessChecks checks, 
const AccessFlags flags) {
 
  637        if (::faccessat(to_integral(dir_fd.raw()), path.raw(), checks.raw(), flags.raw()) == 0) {
 
  641        cosmos_throw (ApiError(
"faccessat()"));
 
  644COSMOS_API 
void check_access_fd(
const FileDescriptor fd, 
const AccessChecks checks,
 
  645                const AccessFlags flags) {
 
  647        if (::faccessat(to_integral(fd.raw()), 
"", checks.raw(), flags.raw() | AT_EMPTY_PATH) == 0) {
 
  651        cosmos_throw (ApiError(
"faccessat()"));
 
  654COSMOS_API 
void flock(
const FileDescriptor fd, 
const LockOperation operation, 
const LockFlags flags) {
 
  655        if (::flock(to_integral(fd.raw()), cosmos::to_integral(operation) | flags.raw()) != 0) {
 
  656                cosmos_throw (ApiError(
"flock()"));
 
FileNum raw() const
Returns the primitive file descriptor contained in the object.
Errno
Strong enum type representing errno error constants.
Errno get_errno()
Wrapper that returns the Errno strongly typed representation of the current errno
NamedBool< struct close_file_t, true > AutoCloseFD
Strong boolean type for expressing the responsibility to close file descriptors.
FileNum
Primitive file descriptor.
ModeT
Combined file type and mode bits of a file (as found in st_mode struct stat).