aboutsummaryrefslogtreecommitdiff
path: root/src/libutil
diff options
context:
space:
mode:
authorTom Hubrecht <github@mail.hubrecht.ovh>2024-05-28 11:52:13 +0200
committerTom Hubrecht <github@mail.hubrecht.ovh>2024-05-29 09:54:47 +0200
commit6b5078c81554ddb36547f8c41805cc94b7738396 (patch)
tree0e0c3552d9d397920326b6e21cff278c4b272935 /src/libutil
parent81bdf8d2d672e135e68745e6975ad5edafadf13a (diff)
util.{hh,cc}: Split out file-system.{hh,cc}
Change-Id: Ifa89a529e7e34e7291eca87d802d2f569cf2493e
Diffstat (limited to 'src/libutil')
-rw-r--r--src/libutil/archive.cc1
-rw-r--r--src/libutil/archive.hh1
-rw-r--r--src/libutil/canon-path.cc2
-rw-r--r--src/libutil/cgroup.cc1
-rw-r--r--src/libutil/config.cc1
-rw-r--r--src/libutil/file-system.cc678
-rw-r--r--src/libutil/file-system.hh270
-rw-r--r--src/libutil/filesystem.cc176
-rw-r--r--src/libutil/hash.hh1
-rw-r--r--src/libutil/input-accessor.hh6
-rw-r--r--src/libutil/meson.build3
-rw-r--r--src/libutil/namespaces.cc1
-rw-r--r--src/libutil/source-path.hh2
-rw-r--r--src/libutil/tarfile.cc1
-rw-r--r--src/libutil/util.cc501
-rw-r--r--src/libutil/util.hh239
16 files changed, 965 insertions, 919 deletions
diff --git a/src/libutil/archive.cc b/src/libutil/archive.cc
index a18c54ebf..c3f39d4b3 100644
--- a/src/libutil/archive.cc
+++ b/src/libutil/archive.cc
@@ -12,6 +12,7 @@
#include <fcntl.h>
#include "archive.hh"
+#include "file-system.hh"
#include "util.hh"
#include "config.hh"
#include "signals.hh"
diff --git a/src/libutil/archive.hh b/src/libutil/archive.hh
index 017b6633c..f163a1947 100644
--- a/src/libutil/archive.hh
+++ b/src/libutil/archive.hh
@@ -3,6 +3,7 @@
#include "types.hh"
#include "serialise.hh"
+#include "file-system.hh"
namespace nix {
diff --git a/src/libutil/canon-path.cc b/src/libutil/canon-path.cc
index 040464532..f678fae94 100644
--- a/src/libutil/canon-path.cc
+++ b/src/libutil/canon-path.cc
@@ -1,5 +1,5 @@
#include "canon-path.hh"
-#include "util.hh"
+#include "file-system.hh"
namespace nix {
diff --git a/src/libutil/cgroup.cc b/src/libutil/cgroup.cc
index 9320d2371..aa7802f79 100644
--- a/src/libutil/cgroup.cc
+++ b/src/libutil/cgroup.cc
@@ -2,6 +2,7 @@
#include "cgroup.hh"
#include "util.hh"
+#include "file-system.hh"
#include "finally.hh"
#include <chrono>
diff --git a/src/libutil/config.cc b/src/libutil/config.cc
index 81efcd507..f6f14878a 100644
--- a/src/libutil/config.cc
+++ b/src/libutil/config.cc
@@ -2,6 +2,7 @@
#include "args.hh"
#include "abstract-setting-to-json.hh"
#include "experimental-features.hh"
+#include "file-system.hh"
#include "config-impl.hh"
diff --git a/src/libutil/file-system.cc b/src/libutil/file-system.cc
new file mode 100644
index 000000000..e5ba42eb6
--- /dev/null
+++ b/src/libutil/file-system.cc
@@ -0,0 +1,678 @@
+#include <sys/time.h>
+#include <filesystem>
+#include <atomic>
+
+#include "environment-variables.hh"
+#include "file-system.hh"
+#include "finally.hh"
+#include "serialise.hh"
+#include "util.hh"
+#include "signals.hh"
+#include "types.hh"
+
+namespace fs = std::filesystem;
+
+namespace nix {
+
+Path absPath(Path path, std::optional<PathView> dir, bool resolveSymlinks)
+{
+ if (path.empty() || path[0] != '/') {
+ if (!dir) {
+#ifdef __GNU__
+ /* GNU (aka. GNU/Hurd) doesn't have any limitation on path
+ lengths and doesn't define `PATH_MAX'. */
+ char *buf = getcwd(NULL, 0);
+ if (buf == NULL)
+#else
+ char buf[PATH_MAX];
+ if (!getcwd(buf, sizeof(buf)))
+#endif
+ throw SysError("cannot get cwd");
+ path = concatStrings(buf, "/", path);
+#ifdef __GNU__
+ free(buf);
+#endif
+ } else
+ path = concatStrings(*dir, "/", path);
+ }
+ return canonPath(path, resolveSymlinks);
+}
+
+
+Path canonPath(PathView path, bool resolveSymlinks)
+{
+ assert(path != "");
+
+ std::string s;
+ s.reserve(256);
+
+ if (path[0] != '/')
+ throw Error("not an absolute path: '%1%'", path);
+
+ std::string temp;
+
+ /* Count the number of times we follow a symlink and stop at some
+ arbitrary (but high) limit to prevent infinite loops. */
+ unsigned int followCount = 0, maxFollow = 1024;
+
+ while (1) {
+
+ /* Skip slashes. */
+ while (!path.empty() && path[0] == '/') path.remove_prefix(1);
+ if (path.empty()) break;
+
+ /* Ignore `.'. */
+ if (path == "." || path.substr(0, 2) == "./")
+ path.remove_prefix(1);
+
+ /* If `..', delete the last component. */
+ else if (path == ".." || path.substr(0, 3) == "../")
+ {
+ if (!s.empty()) s.erase(s.rfind('/'));
+ path.remove_prefix(2);
+ }
+
+ /* Normal component; copy it. */
+ else {
+ s += '/';
+ if (const auto slash = path.find('/'); slash == std::string::npos) {
+ s += path;
+ path = {};
+ } else {
+ s += path.substr(0, slash);
+ path = path.substr(slash);
+ }
+
+ /* If s points to a symlink, resolve it and continue from there */
+ if (resolveSymlinks && isLink(s)) {
+ if (++followCount >= maxFollow)
+ throw Error("infinite symlink recursion in path '%1%'", path);
+ temp = concatStrings(readLink(s), path);
+ path = temp;
+ if (!temp.empty() && temp[0] == '/') {
+ s.clear(); /* restart for symlinks pointing to absolute path */
+ } else {
+ s = dirOf(s);
+ if (s == "/") { // we don’t want trailing slashes here, which dirOf only produces if s = /
+ s.clear();
+ }
+ }
+ }
+ }
+ }
+
+ return s.empty() ? "/" : std::move(s);
+}
+
+void chmodPath(const Path & path, mode_t mode)
+{
+ if (chmod(path.c_str(), mode) == -1)
+ throw SysError("setting permissions on '%s'", path);
+}
+
+Path dirOf(const PathView path)
+{
+ Path::size_type pos = path.rfind('/');
+ if (pos == std::string::npos)
+ return ".";
+ return pos == 0 ? "/" : Path(path, 0, pos);
+}
+
+
+std::string_view baseNameOf(std::string_view path)
+{
+ if (path.empty())
+ return "";
+
+ auto last = path.size() - 1;
+ if (path[last] == '/' && last > 0)
+ last -= 1;
+
+ auto pos = path.rfind('/', last);
+ if (pos == std::string::npos)
+ pos = 0;
+ else
+ pos += 1;
+
+ return path.substr(pos, last - pos + 1);
+}
+
+
+std::string expandTilde(std::string_view path)
+{
+ // TODO: expand ~user ?
+ auto tilde = path.substr(0, 2);
+ if (tilde == "~/" || tilde == "~")
+ return getHome() + std::string(path.substr(1));
+ else
+ return std::string(path);
+}
+
+
+bool isInDir(std::string_view path, std::string_view dir)
+{
+ return path.substr(0, 1) == "/"
+ && path.substr(0, dir.size()) == dir
+ && path.size() >= dir.size() + 2
+ && path[dir.size()] == '/';
+}
+
+
+bool isDirOrInDir(std::string_view path, std::string_view dir)
+{
+ return path == dir || isInDir(path, dir);
+}
+
+
+struct stat stat(const Path & path)
+{
+ struct stat st;
+ if (stat(path.c_str(), &st))
+ throw SysError("getting status of '%1%'", path);
+ return st;
+}
+
+
+struct stat lstat(const Path & path)
+{
+ struct stat st;
+ if (lstat(path.c_str(), &st))
+ throw SysError("getting status of '%1%'", path);
+ return st;
+}
+
+std::optional<struct stat> maybeLstat(const Path & path)
+{
+ std::optional<struct stat> st{std::in_place};
+ if (lstat(path.c_str(), &*st))
+ {
+ if (errno == ENOENT || errno == ENOTDIR)
+ st.reset();
+ else
+ throw SysError("getting status of '%s'", path);
+ }
+ return st;
+}
+
+bool pathExists(const Path & path)
+{
+ return maybeLstat(path).has_value();
+}
+
+bool pathAccessible(const Path & path)
+{
+ try {
+ return pathExists(path);
+ } catch (SysError & e) {
+ // swallow EPERM
+ if (e.errNo == EPERM) return false;
+ throw;
+ }
+}
+
+
+Path readLink(const Path & path)
+{
+ checkInterrupt();
+ std::vector<char> buf;
+ for (ssize_t bufSize = PATH_MAX/4; true; bufSize += bufSize/2) {
+ buf.resize(bufSize);
+ ssize_t rlSize = readlink(path.c_str(), buf.data(), bufSize);
+ if (rlSize == -1)
+ if (errno == EINVAL)
+ throw Error("'%1%' is not a symlink", path);
+ else
+ throw SysError("reading symbolic link '%1%'", path);
+ else if (rlSize < bufSize)
+ return std::string(buf.data(), rlSize);
+ }
+}
+
+
+bool isLink(const Path & path)
+{
+ struct stat st = lstat(path);
+ return S_ISLNK(st.st_mode);
+}
+
+
+DirEntries readDirectory(DIR *dir, const Path & path)
+{
+ DirEntries entries;
+ entries.reserve(64);
+
+ struct dirent * dirent;
+ while (errno = 0, dirent = readdir(dir)) { /* sic */
+ checkInterrupt();
+ std::string name = dirent->d_name;
+ if (name == "." || name == "..") continue;
+ entries.emplace_back(name, dirent->d_ino,
+#ifdef HAVE_STRUCT_DIRENT_D_TYPE
+ dirent->d_type
+#else
+ DT_UNKNOWN
+#endif
+ );
+ }
+ if (errno) throw SysError("reading directory '%1%'", path);
+
+ return entries;
+}
+
+DirEntries readDirectory(const Path & path)
+{
+ AutoCloseDir dir(opendir(path.c_str()));
+ if (!dir) throw SysError("opening directory '%1%'", path);
+
+ return readDirectory(dir.get(), path);
+}
+
+
+unsigned char getFileType(const Path & path)
+{
+ struct stat st = lstat(path);
+ if (S_ISDIR(st.st_mode)) return DT_DIR;
+ if (S_ISLNK(st.st_mode)) return DT_LNK;
+ if (S_ISREG(st.st_mode)) return DT_REG;
+ return DT_UNKNOWN;
+}
+
+
+std::string readFile(int fd)
+{
+ struct stat st;
+ if (fstat(fd, &st) == -1)
+ throw SysError("statting file");
+
+ return drainFD(fd, true, st.st_size);
+}
+
+
+std::string readFile(const Path & path)
+{
+ AutoCloseFD fd{open(path.c_str(), O_RDONLY | O_CLOEXEC)};
+ if (!fd)
+ throw SysError("opening file '%1%'", path);
+ return readFile(fd.get());
+}
+
+
+void readFile(const Path & path, Sink & sink)
+{
+ AutoCloseFD fd{open(path.c_str(), O_RDONLY | O_CLOEXEC)};
+ if (!fd)
+ throw SysError("opening file '%s'", path);
+ drainFD(fd.get(), sink);
+}
+
+
+void writeFile(const Path & path, std::string_view s, mode_t mode, bool sync)
+{
+ AutoCloseFD fd{open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode)};
+ if (!fd)
+ throw SysError("opening file '%1%'", path);
+ try {
+ writeFull(fd.get(), s);
+ } catch (Error & e) {
+ e.addTrace({}, "writing file '%1%'", path);
+ throw;
+ }
+ if (sync)
+ fd.fsync();
+ // Explicitly close to make sure exceptions are propagated.
+ fd.close();
+ if (sync)
+ syncParent(path);
+}
+
+
+void writeFile(const Path & path, Source & source, mode_t mode, bool sync)
+{
+ AutoCloseFD fd{open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode)};
+ if (!fd)
+ throw SysError("opening file '%1%'", path);
+
+ std::vector<char> buf(64 * 1024);
+
+ try {
+ while (true) {
+ try {
+ auto n = source.read(buf.data(), buf.size());
+ writeFull(fd.get(), {buf.data(), n});
+ } catch (EndOfFile &) { break; }
+ }
+ } catch (Error & e) {
+ e.addTrace({}, "writing file '%1%'", path);
+ throw;
+ }
+ if (sync)
+ fd.fsync();
+ // Explicitly close to make sure exceptions are propagated.
+ fd.close();
+ if (sync)
+ syncParent(path);
+}
+
+void syncParent(const Path & path)
+{
+ AutoCloseFD fd{open(dirOf(path).c_str(), O_RDONLY, 0)};
+ if (!fd)
+ throw SysError("opening file '%1%'", path);
+ fd.fsync();
+}
+
+static void _deletePath(int parentfd, const Path & path, uint64_t & bytesFreed)
+{
+ checkInterrupt();
+
+ std::string name(baseNameOf(path));
+
+ struct stat st;
+ if (fstatat(parentfd, name.c_str(), &st, AT_SYMLINK_NOFOLLOW) == -1) {
+ if (errno == ENOENT) return;
+ throw SysError("getting status of '%1%'", path);
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ /* We are about to delete a file. Will it likely free space? */
+
+ switch (st.st_nlink) {
+ /* Yes: last link. */
+ case 1:
+ bytesFreed += st.st_size;
+ break;
+ /* Maybe: yes, if 'auto-optimise-store' or manual optimisation
+ was performed. Instead of checking for real let's assume
+ it's an optimised file and space will be freed.
+
+ In worst case we will double count on freed space for files
+ with exactly two hardlinks for unoptimised packages.
+ */
+ case 2:
+ bytesFreed += st.st_size;
+ break;
+ /* No: 3+ links. */
+ default:
+ break;
+ }
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ /* Make the directory accessible. */
+ const auto PERM_MASK = S_IRUSR | S_IWUSR | S_IXUSR;
+ if ((st.st_mode & PERM_MASK) != PERM_MASK) {
+ if (fchmodat(parentfd, name.c_str(), st.st_mode | PERM_MASK, 0) == -1)
+ throw SysError("chmod '%1%'", path);
+ }
+
+ int fd = openat(parentfd, path.c_str(), O_RDONLY);
+ if (fd == -1)
+ throw SysError("opening directory '%1%'", path);
+ AutoCloseDir dir(fdopendir(fd));
+ if (!dir)
+ throw SysError("opening directory '%1%'", path);
+ for (auto & i : readDirectory(dir.get(), path))
+ _deletePath(dirfd(dir.get()), path + "/" + i.name, bytesFreed);
+ }
+
+ int flags = S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0;
+ if (unlinkat(parentfd, name.c_str(), flags) == -1) {
+ if (errno == ENOENT) return;
+ throw SysError("cannot unlink '%1%'", path);
+ }
+}
+
+static void _deletePath(const Path & path, uint64_t & bytesFreed)
+{
+ Path dir = dirOf(path);
+ if (dir == "")
+ dir = "/";
+
+ AutoCloseFD dirfd{open(dir.c_str(), O_RDONLY)};
+ if (!dirfd) {
+ if (errno == ENOENT) return;
+ throw SysError("opening directory '%1%'", path);
+ }
+
+ _deletePath(dirfd.get(), path, bytesFreed);
+}
+
+
+void deletePath(const Path & path)
+{
+ uint64_t dummy;
+ deletePath(path, dummy);
+}
+
+
+void deletePath(const Path & path, uint64_t & bytesFreed)
+{
+ //Activity act(*logger, lvlDebug, "recursively deleting path '%1%'", path);
+ bytesFreed = 0;
+ _deletePath(path, bytesFreed);
+}
+
+Paths createDirs(const Path & path)
+{
+ Paths created;
+ if (path == "/") return created;
+
+ struct stat st;
+ if (lstat(path.c_str(), &st) == -1) {
+ created = createDirs(dirOf(path));
+ if (mkdir(path.c_str(), 0777) == -1 && errno != EEXIST)
+ throw SysError("creating directory '%1%'", path);
+ st = lstat(path);
+ created.push_back(path);
+ }
+
+ if (S_ISLNK(st.st_mode) && stat(path.c_str(), &st) == -1)
+ throw SysError("statting symlink '%1%'", path);
+
+ if (!S_ISDIR(st.st_mode)) throw Error("'%1%' is not a directory", path);
+
+ return created;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+
+AutoDelete::AutoDelete() : del{false} {}
+
+AutoDelete::AutoDelete(const std::string & p, bool recursive) : path(p)
+{
+ del = true;
+ this->recursive = recursive;
+}
+
+AutoDelete::~AutoDelete()
+{
+ try {
+ if (del) {
+ if (recursive)
+ deletePath(path);
+ else {
+ if (remove(path.c_str()) == -1)
+ throw SysError("cannot unlink '%1%'", path);
+ }
+ }
+ } catch (...) {
+ ignoreException();
+ }
+}
+
+void AutoDelete::cancel()
+{
+ del = false;
+}
+
+void AutoDelete::reset(const Path & p, bool recursive) {
+ path = p;
+ this->recursive = recursive;
+ del = true;
+}
+
+//////////////////////////////////////////////////////////////////////
+
+static Path tempName(Path tmpRoot, const Path & prefix, bool includePid,
+ std::atomic<unsigned int> & counter)
+{
+ tmpRoot = canonPath(tmpRoot.empty() ? getEnv("TMPDIR").value_or("/tmp") : tmpRoot, true);
+ if (includePid)
+ return fmt("%1%/%2%-%3%-%4%", tmpRoot, prefix, getpid(), counter++);
+ else
+ return fmt("%1%/%2%-%3%", tmpRoot, prefix, counter++);
+}
+
+Path createTempDir(const Path & tmpRoot, const Path & prefix,
+ bool includePid, bool useGlobalCounter, mode_t mode)
+{
+ static std::atomic<unsigned int> globalCounter = 0;
+ std::atomic<unsigned int> localCounter = 0;
+ auto & counter(useGlobalCounter ? globalCounter : localCounter);
+
+ while (1) {
+ checkInterrupt();
+ Path tmpDir = tempName(tmpRoot, prefix, includePid, counter);
+ if (mkdir(tmpDir.c_str(), mode) == 0) {
+#if __FreeBSD__
+ /* Explicitly set the group of the directory. This is to
+ work around around problems caused by BSD's group
+ ownership semantics (directories inherit the group of
+ the parent). For instance, the group of /tmp on
+ FreeBSD is "wheel", so all directories created in /tmp
+ will be owned by "wheel"; but if the user is not in
+ "wheel", then "tar" will fail to unpack archives that
+ have the setgid bit set on directories. */
+ if (chown(tmpDir.c_str(), (uid_t) -1, getegid()) != 0)
+ throw SysError("setting group of directory '%1%'", tmpDir);
+#endif
+ return tmpDir;
+ }
+ if (errno != EEXIST)
+ throw SysError("creating directory '%1%'", tmpDir);
+ }
+}
+
+
+std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix)
+{
+ Path tmpl(getEnv("TMPDIR").value_or("/tmp") + "/" + prefix + ".XXXXXX");
+ // Strictly speaking, this is UB, but who cares...
+ // FIXME: use O_TMPFILE.
+ AutoCloseFD fd(mkstemp((char *) tmpl.c_str()));
+ if (!fd)
+ throw SysError("creating temporary file '%s'", tmpl);
+ closeOnExec(fd.get());
+ return {std::move(fd), tmpl};
+}
+
+void createSymlink(const Path & target, const Path & link)
+{
+ if (symlink(target.c_str(), link.c_str()))
+ throw SysError("creating symlink from '%1%' to '%2%'", link, target);
+}
+
+void replaceSymlink(const Path & target, const Path & link)
+{
+ for (unsigned int n = 0; true; n++) {
+ Path tmp = canonPath(fmt("%s/.%d_%s", dirOf(link), n, baseNameOf(link)));
+
+ try {
+ createSymlink(target, tmp);
+ } catch (SysError & e) {
+ if (e.errNo == EEXIST) continue;
+ throw;
+ }
+
+ renameFile(tmp, link);
+
+ break;
+ }
+}
+
+void setWriteTime(const fs::path & p, const struct stat & st)
+{
+ struct timeval times[2];
+ times[0] = {
+ .tv_sec = st.st_atime,
+ .tv_usec = 0,
+ };
+ times[1] = {
+ .tv_sec = st.st_mtime,
+ .tv_usec = 0,
+ };
+ if (lutimes(p.c_str(), times) != 0)
+ throw SysError("changing modification time of '%s'", p);
+}
+
+void copy(const fs::directory_entry & from, const fs::path & to, CopyFileFlags flags)
+{
+ // TODO: Rewrite the `is_*` to use `symlink_status()`
+ auto statOfFrom = lstat(from.path().c_str());
+ auto fromStatus = from.symlink_status();
+
+ // Mark the directory as writable so that we can delete its children
+ if (flags.deleteAfter && fs::is_directory(fromStatus)) {
+ fs::permissions(from.path(), fs::perms::owner_write, fs::perm_options::add | fs::perm_options::nofollow);
+ }
+
+
+ if (fs::is_symlink(fromStatus) || fs::is_regular_file(fromStatus)) {
+ auto opts = fs::copy_options::overwrite_existing;
+
+ if (!flags.followSymlinks) {
+ opts |= fs::copy_options::copy_symlinks;
+ }
+
+ fs::copy(from.path(), to, opts);
+ } else if (fs::is_directory(fromStatus)) {
+ fs::create_directory(to);
+ for (auto & entry : fs::directory_iterator(from.path())) {
+ copy(entry, to / entry.path().filename(), flags);
+ }
+ } else {
+ throw Error("file '%s' has an unsupported type", from.path());
+ }
+
+ setWriteTime(to, statOfFrom);
+ if (flags.deleteAfter) {
+ if (!fs::is_symlink(fromStatus))
+ fs::permissions(from.path(), fs::perms::owner_write, fs::perm_options::add | fs::perm_options::nofollow);
+ fs::remove(from.path());
+ }
+}
+
+
+void copyFile(const Path & oldPath, const Path & newPath, CopyFileFlags flags)
+{
+ return copy(fs::directory_entry(fs::path(oldPath)), fs::path(newPath), flags);
+}
+
+void renameFile(const Path & oldName, const Path & newName)
+{
+ fs::rename(oldName, newName);
+}
+
+void moveFile(const Path & oldName, const Path & newName)
+{
+ try {
+ renameFile(oldName, newName);
+ } catch (fs::filesystem_error & e) {
+ auto oldPath = fs::path(oldName);
+ auto newPath = fs::path(newName);
+ // For the move to be as atomic as possible, copy to a temporary
+ // directory
+ fs::path temp = createTempDir(newPath.parent_path(), "rename-tmp");
+ Finally removeTemp = [&]() { fs::remove(temp); };
+ auto tempCopyTarget = temp / "copy-target";
+ if (e.code().value() == EXDEV) {
+ fs::remove(newPath);
+ warn("Can’t rename %s as %s, copying instead", oldName, newName);
+ copy(fs::directory_entry(oldPath), tempCopyTarget, { .deleteAfter = true });
+ renameFile(tempCopyTarget, newPath);
+ }
+ }
+}
+
+}
diff --git a/src/libutil/file-system.hh b/src/libutil/file-system.hh
new file mode 100644
index 000000000..1d91ef334
--- /dev/null
+++ b/src/libutil/file-system.hh
@@ -0,0 +1,270 @@
+#pragma once
+/**
+ * @file
+ *
+ * Utiltities for working with the file sytem and file paths.
+ */
+
+#include "types.hh"
+#include "util.hh"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <unistd.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <functional>
+#include <optional>
+
+#ifndef HAVE_STRUCT_DIRENT_D_TYPE
+#define DT_UNKNOWN 0
+#define DT_REG 1
+#define DT_LNK 2
+#define DT_DIR 3
+#endif
+
+namespace nix {
+
+struct Sink;
+struct Source;
+
+/**
+ * @return An absolutized path, resolving paths relative to the
+ * specified directory, or the current directory otherwise. The path
+ * is also canonicalised.
+ */
+Path absPath(Path path,
+ std::optional<PathView> dir = {},
+ bool resolveSymlinks = false);
+
+/**
+ * Canonicalise a path by removing all `.` or `..` components and
+ * double or trailing slashes. Optionally resolves all symlink
+ * components such that each component of the resulting path is *not*
+ * a symbolic link.
+ */
+Path canonPath(PathView path, bool resolveSymlinks = false);
+
+/**
+ * Change the permissions of a path
+ * Not called `chmod` as it shadows and could be confused with
+ * `int chmod(char *, mode_t)`, which does not handle errors
+ */
+void chmodPath(const Path & path, mode_t mode);
+
+/**
+ * @return The directory part of the given canonical path, i.e.,
+ * everything before the final `/`. If the path is the root or an
+ * immediate child thereof (e.g., `/foo`), this means `/`
+ * is returned.
+ */
+Path dirOf(const PathView path);
+
+/**
+ * @return the base name of the given canonical path, i.e., everything
+ * following the final `/` (trailing slashes are removed).
+ */
+std::string_view baseNameOf(std::string_view path);
+
+/**
+ * Perform tilde expansion on a path.
+ */
+std::string expandTilde(std::string_view path);
+
+/**
+ * Check whether 'path' is a descendant of 'dir'. Both paths must be
+ * canonicalized.
+ */
+bool isInDir(std::string_view path, std::string_view dir);
+
+/**
+ * Check whether 'path' is equal to 'dir' or a descendant of
+ * 'dir'. Both paths must be canonicalized.
+ */
+bool isDirOrInDir(std::string_view path, std::string_view dir);
+
+/**
+ * Get status of `path`.
+ */
+struct stat stat(const Path & path);
+struct stat lstat(const Path & path);
+
+/**
+ * `lstat` the given path if it exists.
+ * @return std::nullopt if the path doesn't exist, or an optional containing the result of `lstat` otherwise
+ */
+std::optional<struct stat> maybeLstat(const Path & path);
+
+/**
+ * @return true iff the given path exists.
+ */
+bool pathExists(const Path & path);
+
+/**
+ * A version of pathExists that returns false on a permission error.
+ * Useful for inferring default paths across directories that might not
+ * be readable.
+ * @return true iff the given path can be accessed and exists
+ */
+bool pathAccessible(const Path & path);
+
+/**
+ * Read the contents (target) of a symbolic link. The result is not
+ * in any way canonicalised.
+ */
+Path readLink(const Path & path);
+
+bool isLink(const Path & path);
+
+/**
+ * Read the contents of a directory. The entries `.` and `..` are
+ * removed.
+ */
+struct DirEntry
+{
+ std::string name;
+ ino_t ino;
+ /**
+ * one of DT_*
+ */
+ unsigned char type;
+ DirEntry(std::string name, ino_t ino, unsigned char type)
+ : name(std::move(name)), ino(ino), type(type) { }
+};
+
+typedef std::vector<DirEntry> DirEntries;
+
+DirEntries readDirectory(const Path & path);
+
+unsigned char getFileType(const Path & path);
+
+/**
+ * Read the contents of a file into a string.
+ */
+std::string readFile(int fd);
+std::string readFile(const Path & path);
+void readFile(const Path & path, Sink & sink);
+
+/**
+ * Write a string to a file.
+ */
+void writeFile(const Path & path, std::string_view s, mode_t mode = 0666, bool sync = false);
+
+void writeFile(const Path & path, Source & source, mode_t mode = 0666, bool sync = false);
+
+/**
+ * Flush a file's parent directory to disk
+ */
+void syncParent(const Path & path);
+
+/**
+ * Delete a path; i.e., in the case of a directory, it is deleted
+ * recursively. It's not an error if the path does not exist. The
+ * second variant returns the number of bytes and blocks freed.
+ */
+void deletePath(const Path & path);
+
+void deletePath(const Path & path, uint64_t & bytesFreed);
+
+/**
+ * Create a directory and all its parents, if necessary. Returns the
+ * list of created directories, in order of creation.
+ */
+Paths createDirs(const Path & path);
+inline Paths createDirs(PathView path)
+{
+ return createDirs(Path(path));
+}
+
+/**
+ * Create a symlink.
+ */
+void createSymlink(const Path & target, const Path & link);
+
+/**
+ * Atomically create or replace a symlink.
+ */
+void replaceSymlink(const Path & target, const Path & link);
+
+void renameFile(const Path & src, const Path & dst);
+
+/**
+ * Similar to 'renameFile', but fallback to a copy+remove if `src` and `dst`
+ * are on a different filesystem.
+ *
+ * Beware that this might not be atomic because of the copy that happens behind
+ * the scenes
+ */
+void moveFile(const Path & src, const Path & dst);
+
+struct CopyFileFlags
+{
+ /**
+ * Delete the file after copying.
+ */
+ bool deleteAfter = false;
+
+ /**
+ * Follow symlinks and copy the eventual target.
+ */
+ bool followSymlinks = false;
+};
+
+/**
+ * Recursively copy the content of `oldPath` to `newPath`. If `andDelete` is
+ * `true`, then also remove `oldPath` (making this equivalent to `moveFile`, but
+ * with the guaranty that the destination will be “fresh”, with no stale inode
+ * or file descriptor pointing to it).
+ */
+void copyFile(const Path & oldPath, const Path & newPath, CopyFileFlags flags);
+
+/**
+ * Automatic cleanup of resources.
+ */
+
+
+class AutoDelete
+{
+ Path path;
+ bool del;
+ bool recursive;
+public:
+ AutoDelete();
+ AutoDelete(const Path & p, bool recursive = true);
+ ~AutoDelete();
+ void cancel();
+ void reset(const Path & p, bool recursive = true);
+ operator Path() const { return path; }
+ operator PathView() const { return path; }
+};
+
+struct DIRDeleter
+{
+ void operator()(DIR * dir) const {
+ closedir(dir);
+ }
+};
+
+typedef std::unique_ptr<DIR, DIRDeleter> AutoCloseDir;
+
+/**
+ * Create a temporary directory.
+ */
+Path createTempDir(const Path & tmpRoot = "", const Path & prefix = "nix",
+ bool includePid = true, bool useGlobalCounter = true, mode_t mode = 0755);
+
+/**
+ * Create a temporary file, returning a file handle and its path.
+ */
+std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix = "nix");
+
+/**
+ * Used in various places.
+ */
+typedef std::function<bool(const Path & path)> PathFilter;
+
+extern PathFilter defaultPathFilter;
+
+}
diff --git a/src/libutil/filesystem.cc b/src/libutil/filesystem.cc
deleted file mode 100644
index 85871870c..000000000
--- a/src/libutil/filesystem.cc
+++ /dev/null
@@ -1,176 +0,0 @@
-#include <sys/time.h>
-#include <filesystem>
-#include <atomic>
-
-#include "environment-variables.hh"
-#include "finally.hh"
-#include "util.hh"
-#include "signals.hh"
-#include "types.hh"
-
-namespace fs = std::filesystem;
-
-namespace nix {
-
-static Path tempName(Path tmpRoot, const Path & prefix, bool includePid,
- std::atomic<unsigned int> & counter)
-{
- tmpRoot = canonPath(tmpRoot.empty() ? getEnv("TMPDIR").value_or("/tmp") : tmpRoot, true);
- if (includePid)
- return fmt("%1%/%2%-%3%-%4%", tmpRoot, prefix, getpid(), counter++);
- else
- return fmt("%1%/%2%-%3%", tmpRoot, prefix, counter++);
-}
-
-Path createTempDir(const Path & tmpRoot, const Path & prefix,
- bool includePid, bool useGlobalCounter, mode_t mode)
-{
- static std::atomic<unsigned int> globalCounter = 0;
- std::atomic<unsigned int> localCounter = 0;
- auto & counter(useGlobalCounter ? globalCounter : localCounter);
-
- while (1) {
- checkInterrupt();
- Path tmpDir = tempName(tmpRoot, prefix, includePid, counter);
- if (mkdir(tmpDir.c_str(), mode) == 0) {
-#if __FreeBSD__
- /* Explicitly set the group of the directory. This is to
- work around around problems caused by BSD's group
- ownership semantics (directories inherit the group of
- the parent). For instance, the group of /tmp on
- FreeBSD is "wheel", so all directories created in /tmp
- will be owned by "wheel"; but if the user is not in
- "wheel", then "tar" will fail to unpack archives that
- have the setgid bit set on directories. */
- if (chown(tmpDir.c_str(), (uid_t) -1, getegid()) != 0)
- throw SysError("setting group of directory '%1%'", tmpDir);
-#endif
- return tmpDir;
- }
- if (errno != EEXIST)
- throw SysError("creating directory '%1%'", tmpDir);
- }
-}
-
-
-std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix)
-{
- Path tmpl(getEnv("TMPDIR").value_or("/tmp") + "/" + prefix + ".XXXXXX");
- // Strictly speaking, this is UB, but who cares...
- // FIXME: use O_TMPFILE.
- AutoCloseFD fd(mkstemp((char *) tmpl.c_str()));
- if (!fd)
- throw SysError("creating temporary file '%s'", tmpl);
- closeOnExec(fd.get());
- return {std::move(fd), tmpl};
-}
-
-void createSymlink(const Path & target, const Path & link)
-{
- if (symlink(target.c_str(), link.c_str()))
- throw SysError("creating symlink from '%1%' to '%2%'", link, target);
-}
-
-void replaceSymlink(const Path & target, const Path & link)
-{
- for (unsigned int n = 0; true; n++) {
- Path tmp = canonPath(fmt("%s/.%d_%s", dirOf(link), n, baseNameOf(link)));
-
- try {
- createSymlink(target, tmp);
- } catch (SysError & e) {
- if (e.errNo == EEXIST) continue;
- throw;
- }
-
- renameFile(tmp, link);
-
- break;
- }
-}
-
-void setWriteTime(const fs::path & p, const struct stat & st)
-{
- struct timeval times[2];
- times[0] = {
- .tv_sec = st.st_atime,
- .tv_usec = 0,
- };
- times[1] = {
- .tv_sec = st.st_mtime,
- .tv_usec = 0,
- };
- if (lutimes(p.c_str(), times) != 0)
- throw SysError("changing modification time of '%s'", p);
-}
-
-void copy(const fs::directory_entry & from, const fs::path & to, CopyFileFlags flags)
-{
- // TODO: Rewrite the `is_*` to use `symlink_status()`
- auto statOfFrom = lstat(from.path().c_str());
- auto fromStatus = from.symlink_status();
-
- // Mark the directory as writable so that we can delete its children
- if (flags.deleteAfter && fs::is_directory(fromStatus)) {
- fs::permissions(from.path(), fs::perms::owner_write, fs::perm_options::add | fs::perm_options::nofollow);
- }
-
-
- if (fs::is_symlink(fromStatus) || fs::is_regular_file(fromStatus)) {
- auto opts = fs::copy_options::overwrite_existing;
-
- if (!flags.followSymlinks) {
- opts |= fs::copy_options::copy_symlinks;
- }
-
- fs::copy(from.path(), to, opts);
- } else if (fs::is_directory(fromStatus)) {
- fs::create_directory(to);
- for (auto & entry : fs::directory_iterator(from.path())) {
- copy(entry, to / entry.path().filename(), flags);
- }
- } else {
- throw Error("file '%s' has an unsupported type", from.path());
- }
-
- setWriteTime(to, statOfFrom);
- if (flags.deleteAfter) {
- if (!fs::is_symlink(fromStatus))
- fs::permissions(from.path(), fs::perms::owner_write, fs::perm_options::add | fs::perm_options::nofollow);
- fs::remove(from.path());
- }
-}
-
-
-void copyFile(const Path & oldPath, const Path & newPath, CopyFileFlags flags)
-{
- return copy(fs::directory_entry(fs::path(oldPath)), fs::path(newPath), flags);
-}
-
-void renameFile(const Path & oldName, const Path & newName)
-{
- fs::rename(oldName, newName);
-}
-
-void moveFile(const Path & oldName, const Path & newName)
-{
- try {
- renameFile(oldName, newName);
- } catch (fs::filesystem_error & e) {
- auto oldPath = fs::path(oldName);
- auto newPath = fs::path(newName);
- // For the move to be as atomic as possible, copy to a temporary
- // directory
- fs::path temp = createTempDir(newPath.parent_path(), "rename-tmp");
- Finally removeTemp = [&]() { fs::remove(temp); };
- auto tempCopyTarget = temp / "copy-target";
- if (e.code().value() == EXDEV) {
- fs::remove(newPath);
- warn("Can’t rename %s as %s, copying instead", oldName, newName);
- copy(fs::directory_entry(oldPath), tempCopyTarget, { .deleteAfter = true });
- renameFile(tempCopyTarget, newPath);
- }
- }
-}
-
-}
diff --git a/src/libutil/hash.hh b/src/libutil/hash.hh
index ae3ee40f4..bb2a5e2a7 100644
--- a/src/libutil/hash.hh
+++ b/src/libutil/hash.hh
@@ -3,6 +3,7 @@
#include "types.hh"
#include "serialise.hh"
+#include "file-system.hh"
namespace nix {
diff --git a/src/libutil/input-accessor.hh b/src/libutil/input-accessor.hh
index 740175af0..d8e17c9b1 100644
--- a/src/libutil/input-accessor.hh
+++ b/src/libutil/input-accessor.hh
@@ -1,8 +1,10 @@
#pragma once
///@file
-#include "types.hh"
-#include "archive.hh"
+
+#include <map>
+#include <optional>
+#include <string>
namespace nix {
diff --git a/src/libutil/meson.build b/src/libutil/meson.build
index c890911e9..8af251d1b 100644
--- a/src/libutil/meson.build
+++ b/src/libutil/meson.build
@@ -13,7 +13,7 @@ libutil_sources = files(
'escape-string.cc',
'exit.cc',
'experimental-features.cc',
- 'filesystem.cc',
+ 'file-system.cc',
'git.cc',
'hash.cc',
'hilite.cc',
@@ -62,6 +62,7 @@ libutil_headers = files(
'exit.hh',
'experimental-features.hh',
'experimental-features-json.hh',
+ 'file-system.hh',
'finally.hh',
'fmt.hh',
'git.hh',
diff --git a/src/libutil/namespaces.cc b/src/libutil/namespaces.cc
index dec3a7189..4b539f342 100644
--- a/src/libutil/namespaces.cc
+++ b/src/libutil/namespaces.cc
@@ -1,5 +1,6 @@
#if __linux__
+#include "file-system.hh"
#include "namespaces.hh"
#include "util.hh"
#include "finally.hh"
diff --git a/src/libutil/source-path.hh b/src/libutil/source-path.hh
index 4d4c51062..311e9f7a6 100644
--- a/src/libutil/source-path.hh
+++ b/src/libutil/source-path.hh
@@ -6,7 +6,9 @@
*/
#include "ref.hh"
+#include "archive.hh"
#include "canon-path.hh"
+#include "file-system.hh"
#include "repair-flag.hh"
#include "input-accessor.hh"
diff --git a/src/libutil/tarfile.cc b/src/libutil/tarfile.cc
index f3f04a936..cabb7af9b 100644
--- a/src/libutil/tarfile.cc
+++ b/src/libutil/tarfile.cc
@@ -1,6 +1,7 @@
#include <archive.h>
#include <archive_entry.h>
+#include "file-system.hh"
#include "serialise.hh"
#include "tarfile.hh"
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index 63d9e5248..ac3071ba8 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -5,6 +5,7 @@
#include "cgroup.hh"
#include "signals.hh"
#include "environment-variables.hh"
+#include "file-system.hh"
#include <array>
#include <cctype>
@@ -50,352 +51,6 @@
namespace nix {
-Path absPath(Path path, std::optional<PathView> dir, bool resolveSymlinks)
-{
- if (path.empty() || path[0] != '/') {
- if (!dir) {
-#ifdef __GNU__
- /* GNU (aka. GNU/Hurd) doesn't have any limitation on path
- lengths and doesn't define `PATH_MAX'. */
- char *buf = getcwd(NULL, 0);
- if (buf == NULL)
-#else
- char buf[PATH_MAX];
- if (!getcwd(buf, sizeof(buf)))
-#endif
- throw SysError("cannot get cwd");
- path = concatStrings(buf, "/", path);
-#ifdef __GNU__
- free(buf);
-#endif
- } else
- path = concatStrings(*dir, "/", path);
- }
- return canonPath(path, resolveSymlinks);
-}
-
-
-Path canonPath(PathView path, bool resolveSymlinks)
-{
- assert(path != "");
-
- std::string s;
- s.reserve(256);
-
- if (path[0] != '/')
- throw Error("not an absolute path: '%1%'", path);
-
- std::string temp;
-
- /* Count the number of times we follow a symlink and stop at some
- arbitrary (but high) limit to prevent infinite loops. */
- unsigned int followCount = 0, maxFollow = 1024;
-
- while (1) {
-
- /* Skip slashes. */
- while (!path.empty() && path[0] == '/') path.remove_prefix(1);
- if (path.empty()) break;
-
- /* Ignore `.'. */
- if (path == "." || path.substr(0, 2) == "./")
- path.remove_prefix(1);
-
- /* If `..', delete the last component. */
- else if (path == ".." || path.substr(0, 3) == "../")
- {
- if (!s.empty()) s.erase(s.rfind('/'));
- path.remove_prefix(2);
- }
-
- /* Normal component; copy it. */
- else {
- s += '/';
- if (const auto slash = path.find('/'); slash == std::string::npos) {
- s += path;
- path = {};
- } else {
- s += path.substr(0, slash);
- path = path.substr(slash);
- }
-
- /* If s points to a symlink, resolve it and continue from there */
- if (resolveSymlinks && isLink(s)) {
- if (++followCount >= maxFollow)
- throw Error("infinite symlink recursion in path '%1%'", path);
- temp = concatStrings(readLink(s), path);
- path = temp;
- if (!temp.empty() && temp[0] == '/') {
- s.clear(); /* restart for symlinks pointing to absolute path */
- } else {
- s = dirOf(s);
- if (s == "/") { // we don’t want trailing slashes here, which dirOf only produces if s = /
- s.clear();
- }
- }
- }
- }
- }
-
- return s.empty() ? "/" : std::move(s);
-}
-
-void chmodPath(const Path & path, mode_t mode)
-{
- if (chmod(path.c_str(), mode) == -1)
- throw SysError("setting permissions on '%s'", path);
-}
-
-Path dirOf(const PathView path)
-{
- Path::size_type pos = path.rfind('/');
- if (pos == std::string::npos)
- return ".";
- return pos == 0 ? "/" : Path(path, 0, pos);
-}
-
-
-std::string_view baseNameOf(std::string_view path)
-{
- if (path.empty())
- return "";
-
- auto last = path.size() - 1;
- if (path[last] == '/' && last > 0)
- last -= 1;
-
- auto pos = path.rfind('/', last);
- if (pos == std::string::npos)
- pos = 0;
- else
- pos += 1;
-
- return path.substr(pos, last - pos + 1);
-}
-
-
-std::string expandTilde(std::string_view path)
-{
- // TODO: expand ~user ?
- auto tilde = path.substr(0, 2);
- if (tilde == "~/" || tilde == "~")
- return getHome() + std::string(path.substr(1));
- else
- return std::string(path);
-}
-
-
-bool isInDir(std::string_view path, std::string_view dir)
-{
- return path.substr(0, 1) == "/"
- && path.substr(0, dir.size()) == dir
- && path.size() >= dir.size() + 2
- && path[dir.size()] == '/';
-}
-
-
-bool isDirOrInDir(std::string_view path, std::string_view dir)
-{
- return path == dir || isInDir(path, dir);
-}
-
-
-struct stat stat(const Path & path)
-{
- struct stat st;
- if (stat(path.c_str(), &st))
- throw SysError("getting status of '%1%'", path);
- return st;
-}
-
-
-struct stat lstat(const Path & path)
-{
- struct stat st;
- if (lstat(path.c_str(), &st))
- throw SysError("getting status of '%1%'", path);
- return st;
-}
-
-std::optional<struct stat> maybeLstat(const Path & path)
-{
- std::optional<struct stat> st{std::in_place};
- if (lstat(path.c_str(), &*st))
- {
- if (errno == ENOENT || errno == ENOTDIR)
- st.reset();
- else
- throw SysError("getting status of '%s'", path);
- }
- return st;
-}
-
-bool pathExists(const Path & path)
-{
- return maybeLstat(path).has_value();
-}
-
-bool pathAccessible(const Path & path)
-{
- try {
- return pathExists(path);
- } catch (SysError & e) {
- // swallow EPERM
- if (e.errNo == EPERM) return false;
- throw;
- }
-}
-
-
-Path readLink(const Path & path)
-{
- checkInterrupt();
- std::vector<char> buf;
- for (ssize_t bufSize = PATH_MAX/4; true; bufSize += bufSize/2) {
- buf.resize(bufSize);
- ssize_t rlSize = readlink(path.c_str(), buf.data(), bufSize);
- if (rlSize == -1)
- if (errno == EINVAL)
- throw Error("'%1%' is not a symlink", path);
- else
- throw SysError("reading symbolic link '%1%'", path);
- else if (rlSize < bufSize)
- return std::string(buf.data(), rlSize);
- }
-}
-
-
-bool isLink(const Path & path)
-{
- struct stat st = lstat(path);
- return S_ISLNK(st.st_mode);
-}
-
-
-DirEntries readDirectory(DIR *dir, const Path & path)
-{
- DirEntries entries;
- entries.reserve(64);
-
- struct dirent * dirent;
- while (errno = 0, dirent = readdir(dir)) { /* sic */
- checkInterrupt();
- std::string name = dirent->d_name;
- if (name == "." || name == "..") continue;
- entries.emplace_back(name, dirent->d_ino,
-#ifdef HAVE_STRUCT_DIRENT_D_TYPE
- dirent->d_type
-#else
- DT_UNKNOWN
-#endif
- );
- }
- if (errno) throw SysError("reading directory '%1%'", path);
-
- return entries;
-}
-
-DirEntries readDirectory(const Path & path)
-{
- AutoCloseDir dir(opendir(path.c_str()));
- if (!dir) throw SysError("opening directory '%1%'", path);
-
- return readDirectory(dir.get(), path);
-}
-
-
-unsigned char getFileType(const Path & path)
-{
- struct stat st = lstat(path);
- if (S_ISDIR(st.st_mode)) return DT_DIR;
- if (S_ISLNK(st.st_mode)) return DT_LNK;
- if (S_ISREG(st.st_mode)) return DT_REG;
- return DT_UNKNOWN;
-}
-
-
-std::string readFile(int fd)
-{
- struct stat st;
- if (fstat(fd, &st) == -1)
- throw SysError("statting file");
-
- return drainFD(fd, true, st.st_size);
-}
-
-
-std::string readFile(const Path & path)
-{
- AutoCloseFD fd{open(path.c_str(), O_RDONLY | O_CLOEXEC)};
- if (!fd)
- throw SysError("opening file '%1%'", path);
- return readFile(fd.get());
-}
-
-
-void readFile(const Path & path, Sink & sink)
-{
- AutoCloseFD fd{open(path.c_str(), O_RDONLY | O_CLOEXEC)};
- if (!fd)
- throw SysError("opening file '%s'", path);
- drainFD(fd.get(), sink);
-}
-
-
-void writeFile(const Path & path, std::string_view s, mode_t mode, bool sync)
-{
- AutoCloseFD fd{open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode)};
- if (!fd)
- throw SysError("opening file '%1%'", path);
- try {
- writeFull(fd.get(), s);
- } catch (Error & e) {
- e.addTrace({}, "writing file '%1%'", path);
- throw;
- }
- if (sync)
- fd.fsync();
- // Explicitly close to make sure exceptions are propagated.
- fd.close();
- if (sync)
- syncParent(path);
-}
-
-
-void writeFile(const Path & path, Source & source, mode_t mode, bool sync)
-{
- AutoCloseFD fd{open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode)};
- if (!fd)
- throw SysError("opening file '%1%'", path);
-
- std::vector<char> buf(64 * 1024);
-
- try {
- while (true) {
- try {
- auto n = source.read(buf.data(), buf.size());
- writeFull(fd.get(), {buf.data(), n});
- } catch (EndOfFile &) { break; }
- }
- } catch (Error & e) {
- e.addTrace({}, "writing file '%1%'", path);
- throw;
- }
- if (sync)
- fd.fsync();
- // Explicitly close to make sure exceptions are propagated.
- fd.close();
- if (sync)
- syncParent(path);
-}
-
-void syncParent(const Path & path)
-{
- AutoCloseFD fd{open(dirOf(path).c_str(), O_RDONLY, 0)};
- if (!fd)
- throw SysError("opening file '%1%'", path);
- fd.fsync();
-}
std::string readLine(int fd)
{
@@ -425,98 +80,6 @@ void writeLine(int fd, std::string s)
}
-static void _deletePath(int parentfd, const Path & path, uint64_t & bytesFreed)
-{
- checkInterrupt();
-
- std::string name(baseNameOf(path));
-
- struct stat st;
- if (fstatat(parentfd, name.c_str(), &st, AT_SYMLINK_NOFOLLOW) == -1) {
- if (errno == ENOENT) return;
- throw SysError("getting status of '%1%'", path);
- }
-
- if (!S_ISDIR(st.st_mode)) {
- /* We are about to delete a file. Will it likely free space? */
-
- switch (st.st_nlink) {
- /* Yes: last link. */
- case 1:
- bytesFreed += st.st_size;
- break;
- /* Maybe: yes, if 'auto-optimise-store' or manual optimisation
- was performed. Instead of checking for real let's assume
- it's an optimised file and space will be freed.
-
- In worst case we will double count on freed space for files
- with exactly two hardlinks for unoptimised packages.
- */
- case 2:
- bytesFreed += st.st_size;
- break;
- /* No: 3+ links. */
- default:
- break;
- }
- }
-
- if (S_ISDIR(st.st_mode)) {
- /* Make the directory accessible. */
- const auto PERM_MASK = S_IRUSR | S_IWUSR | S_IXUSR;
- if ((st.st_mode & PERM_MASK) != PERM_MASK) {
- if (fchmodat(parentfd, name.c_str(), st.st_mode | PERM_MASK, 0) == -1)
- throw SysError("chmod '%1%'", path);
- }
-
- int fd = openat(parentfd, path.c_str(), O_RDONLY);
- if (fd == -1)
- throw SysError("opening directory '%1%'", path);
- AutoCloseDir dir(fdopendir(fd));
- if (!dir)
- throw SysError("opening directory '%1%'", path);
- for (auto & i : readDirectory(dir.get(), path))
- _deletePath(dirfd(dir.get()), path + "/" + i.name, bytesFreed);
- }
-
- int flags = S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0;
- if (unlinkat(parentfd, name.c_str(), flags) == -1) {
- if (errno == ENOENT) return;
- throw SysError("cannot unlink '%1%'", path);
- }
-}
-
-static void _deletePath(const Path & path, uint64_t & bytesFreed)
-{
- Path dir = dirOf(path);
- if (dir == "")
- dir = "/";
-
- AutoCloseFD dirfd{open(dir.c_str(), O_RDONLY)};
- if (!dirfd) {
- if (errno == ENOENT) return;
- throw SysError("opening directory '%1%'", path);
- }
-
- _deletePath(dirfd.get(), path, bytesFreed);
-}
-
-
-void deletePath(const Path & path)
-{
- uint64_t dummy;
- deletePath(path, dummy);
-}
-
-
-void deletePath(const Path & path, uint64_t & bytesFreed)
-{
- //Activity act(*logger, lvlDebug, "recursively deleting path '%1%'", path);
- bytesFreed = 0;
- _deletePath(path, bytesFreed);
-}
-
-
std::string getUserName()
{
auto pw = getpwuid(geteuid());
@@ -632,28 +195,6 @@ std::optional<Path> getSelfExe()
}
-Paths createDirs(const Path & path)
-{
- Paths created;
- if (path == "/") return created;
-
- struct stat st;
- if (lstat(path.c_str(), &st) == -1) {
- created = createDirs(dirOf(path));
- if (mkdir(path.c_str(), 0777) == -1 && errno != EEXIST)
- throw SysError("creating directory '%1%'", path);
- st = lstat(path);
- created.push_back(path);
- }
-
- if (S_ISLNK(st.st_mode) && stat(path.c_str(), &st) == -1)
- throw SysError("statting symlink '%1%'", path);
-
- if (!S_ISDIR(st.st_mode)) throw Error("'%1%' is not a directory", path);
-
- return created;
-}
-
void readFull(int fd, char * buf, size_t count)
{
@@ -762,46 +303,6 @@ unsigned int getMaxCPU()
//////////////////////////////////////////////////////////////////////
-AutoDelete::AutoDelete() : del{false} {}
-
-AutoDelete::AutoDelete(const std::string & p, bool recursive) : path(p)
-{
- del = true;
- this->recursive = recursive;
-}
-
-AutoDelete::~AutoDelete()
-{
- try {
- if (del) {
- if (recursive)
- deletePath(path);
- else {
- if (remove(path.c_str()) == -1)
- throw SysError("cannot unlink '%1%'", path);
- }
- }
- } catch (...) {
- ignoreException();
- }
-}
-
-void AutoDelete::cancel()
-{
- del = false;
-}
-
-void AutoDelete::reset(const Path & p, bool recursive) {
- path = p;
- this->recursive = recursive;
- del = true;
-}
-
-
-
-//////////////////////////////////////////////////////////////////////
-
-
AutoCloseFD::AutoCloseFD() : fd{-1} {}
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index 1ce7e8312..63a4fcb6a 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -40,135 +40,6 @@ extern const std::string nativeSystem;
/**
- * @return An absolutized path, resolving paths relative to the
- * specified directory, or the current directory otherwise. The path
- * is also canonicalised.
- */
-Path absPath(Path path,
- std::optional<PathView> dir = {},
- bool resolveSymlinks = false);
-
-/**
- * Canonicalise a path by removing all `.` or `..` components and
- * double or trailing slashes. Optionally resolves all symlink
- * components such that each component of the resulting path is *not*
- * a symbolic link.
- */
-Path canonPath(PathView path, bool resolveSymlinks = false);
-
-/**
- * Change the permissions of a path
- * Not called `chmod` as it shadows and could be confused with
- * `int chmod(char *, mode_t)`, which does not handle errors
- */
-void chmodPath(const Path & path, mode_t mode);
-
-/**
- * @return The directory part of the given canonical path, i.e.,
- * everything before the final `/`. If the path is the root or an
- * immediate child thereof (e.g., `/foo`), this means `/`
- * is returned.
- */
-Path dirOf(const PathView path);
-
-/**
- * @return the base name of the given canonical path, i.e., everything
- * following the final `/` (trailing slashes are removed).
- */
-std::string_view baseNameOf(std::string_view path);
-
-/**
- * Perform tilde expansion on a path.
- */
-std::string expandTilde(std::string_view path);
-
-/**
- * Check whether 'path' is a descendant of 'dir'. Both paths must be
- * canonicalized.
- */
-bool isInDir(std::string_view path, std::string_view dir);
-
-/**
- * Check whether 'path' is equal to 'dir' or a descendant of
- * 'dir'. Both paths must be canonicalized.
- */
-bool isDirOrInDir(std::string_view path, std::string_view dir);
-
-/**
- * Get status of `path`.
- */
-struct stat stat(const Path & path);
-struct stat lstat(const Path & path);
-
-/**
- * `lstat` the given path if it exists.
- * @return std::nullopt if the path doesn't exist, or an optional containing the result of `lstat` otherwise
- */
-std::optional<struct stat> maybeLstat(const Path & path);
-
-/**
- * @return true iff the given path exists.
- */
-bool pathExists(const Path & path);
-
-/**
- * A version of pathExists that returns false on a permission error.
- * Useful for inferring default paths across directories that might not
- * be readable.
- * @return true iff the given path can be accessed and exists
- */
-bool pathAccessible(const Path & path);
-
-/**
- * Read the contents (target) of a symbolic link. The result is not
- * in any way canonicalised.
- */
-Path readLink(const Path & path);
-
-bool isLink(const Path & path);
-
-/**
- * Read the contents of a directory. The entries `.` and `..` are
- * removed.
- */
-struct DirEntry
-{
- std::string name;
- ino_t ino;
- /**
- * one of DT_*
- */
- unsigned char type;
- DirEntry(std::string name, ino_t ino, unsigned char type)
- : name(std::move(name)), ino(ino), type(type) { }
-};
-
-typedef std::vector<DirEntry> DirEntries;
-
-DirEntries readDirectory(const Path & path);
-
-unsigned char getFileType(const Path & path);
-
-/**
- * Read the contents of a file into a string.
- */
-std::string readFile(int fd);
-std::string readFile(const Path & path);
-void readFile(const Path & path, Sink & sink);
-
-/**
- * Write a string to a file.
- */
-void writeFile(const Path & path, std::string_view s, mode_t mode = 0666, bool sync = false);
-
-void writeFile(const Path & path, Source & source, mode_t mode = 0666, bool sync = false);
-
-/**
- * Flush a file's parent directory to disk
- */
-void syncParent(const Path & path);
-
-/**
* Read a line from a file descriptor.
*/
std::string readLine(int fd);
@@ -178,14 +49,6 @@ std::string readLine(int fd);
*/
void writeLine(int fd, std::string s);
-/**
- * Delete a path; i.e., in the case of a directory, it is deleted
- * recursively. It's not an error if the path does not exist. The
- * second variant returns the number of bytes and blocks freed.
- */
-void deletePath(const Path & path);
-
-void deletePath(const Path & path, uint64_t & bytesFreed);
std::string getUserName();
@@ -238,57 +101,6 @@ Path getStateDir();
*/
Path createNixStateDir();
-/**
- * Create a directory and all its parents, if necessary. Returns the
- * list of created directories, in order of creation.
- */
-Paths createDirs(const Path & path);
-inline Paths createDirs(PathView path)
-{
- return createDirs(Path(path));
-}
-
-/**
- * Create a symlink.
- */
-void createSymlink(const Path & target, const Path & link);
-
-/**
- * Atomically create or replace a symlink.
- */
-void replaceSymlink(const Path & target, const Path & link);
-
-void renameFile(const Path & src, const Path & dst);
-
-/**
- * Similar to 'renameFile', but fallback to a copy+remove if `src` and `dst`
- * are on a different filesystem.
- *
- * Beware that this might not be atomic because of the copy that happens behind
- * the scenes
- */
-void moveFile(const Path & src, const Path & dst);
-
-struct CopyFileFlags
-{
- /**
- * Delete the file after copying.
- */
- bool deleteAfter = false;
-
- /**
- * Follow symlinks and copy the eventual target.
- */
- bool followSymlinks = false;
-};
-
-/**
- * Recursively copy the content of `oldPath` to `newPath`. If `andDelete` is
- * `true`, then also remove `oldPath` (making this equivalent to `moveFile`, but
- * with the guaranty that the destination will be “fresh”, with no stale inode
- * or file descriptor pointing to it).
- */
-void copyFile(const Path & oldPath, const Path & newPath, CopyFileFlags flags);
/**
* Wrappers arount read()/write() that read/write exactly the
@@ -313,27 +125,6 @@ void drainFD(int fd, Sink & sink, bool block = true);
*/
unsigned int getMaxCPU();
-/**
- * Automatic cleanup of resources.
- */
-
-
-class AutoDelete
-{
- Path path;
- bool del;
- bool recursive;
-public:
- AutoDelete();
- AutoDelete(const Path & p, bool recursive = true);
- ~AutoDelete();
- void cancel();
- void reset(const Path & p, bool recursive = true);
- operator Path() const { return path; }
- operator PathView() const { return path; }
-};
-
-
class AutoCloseFD
{
int fd;
@@ -353,19 +144,6 @@ public:
void reset() { *this = {}; }
};
-
-/**
- * Create a temporary directory.
- */
-Path createTempDir(const Path & tmpRoot = "", const Path & prefix = "nix",
- bool includePid = true, bool useGlobalCounter = true, mode_t mode = 0755);
-
-/**
- * Create a temporary file, returning a file handle and its path.
- */
-std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix = "nix");
-
-
class Pipe
{
public:
@@ -375,16 +153,6 @@ public:
};
-struct DIRDeleter
-{
- void operator()(DIR * dir) const {
- closedir(dir);
- }
-};
-
-typedef std::unique_ptr<DIR, DIRDeleter> AutoCloseDir;
-
-
class Pid
{
pid_t pid = -1;
@@ -824,13 +592,6 @@ struct MaintainCount
/**
- * Used in various places.
- */
-typedef std::function<bool(const Path & path)> PathFilter;
-
-extern PathFilter defaultPathFilter;
-
-/**
* Common initialisation performed in child processes.
*/
void commonChildInit();