diff --git a/src/libutil/file-system.cc b/src/libutil/file-system.cc index 000259777..e06b022fc 100644 --- a/src/libutil/file-system.cc +++ b/src/libutil/file-system.cc @@ -375,14 +375,6 @@ void syncParent(const Path & path) fd.fsync(); } -#ifdef __FreeBSD__ -# define MOUNTEDPATHS_PARAM , std::set & mountedPaths -# define MOUNTEDPATHS_ARG , mountedPaths -#else -# define MOUNTEDPATHS_PARAM -# define MOUNTEDPATHS_ARG -#endif - void recursiveSync(const Path & path) { /* If it's a file or symlink, just fsync and return. */ @@ -427,129 +419,6 @@ void recursiveSync(const Path & path) } } -static void _deletePath( - Descriptor parentfd, - const std::filesystem::path & path, - uint64_t & bytesFreed, - std::exception_ptr & ex MOUNTEDPATHS_PARAM) -{ -#ifndef _WIN32 - checkInterrupt(); - -# ifdef __FreeBSD__ - // In case of emergency (unmount fails for some reason) not recurse into mountpoints. - // This prevents us from tearing up the nullfs-mounted nix store. - if (mountedPaths.find(path) != mountedPaths.end()) { - return; - } -# endif - - std::string name(path.filename()); - assert(name != "." && name != ".." && !name.empty()); - - 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, name.c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW); - if (fd == -1) - throw SysError("opening directory %1%", path); - AutoCloseDir dir(fdopendir(fd)); - if (!dir) - throw SysError("opening directory %1%", path); - - struct dirent * dirent; - while (errno = 0, dirent = readdir(dir.get())) { /* sic */ - checkInterrupt(); - std::string childName = dirent->d_name; - if (childName == "." || childName == "..") - continue; - _deletePath(dirfd(dir.get()), path / childName, bytesFreed, ex MOUNTEDPATHS_ARG); - } - if (errno) - throw SysError("reading directory %1%", path); - } - - int flags = S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0; - if (unlinkat(parentfd, name.c_str(), flags) == -1) { - if (errno == ENOENT) - return; - try { - throw SysError("cannot unlink %1%", path); - } catch (...) { - if (!ex) - ex = std::current_exception(); - else - ignoreExceptionExceptInterrupt(); - } - } -#else - // TODO implement - throw UnimplementedError("_deletePath"); -#endif -} - -static void _deletePath(const std::filesystem::path & path, uint64_t & bytesFreed MOUNTEDPATHS_PARAM) -{ - assert(path.is_absolute()); - assert(path.parent_path() != path); - - AutoCloseFD dirfd = toDescriptor(open(path.parent_path().string().c_str(), O_RDONLY)); - if (!dirfd) { - if (errno == ENOENT) - return; - throw SysError("opening directory %s", path.parent_path()); - } - - std::exception_ptr ex; - - _deletePath(dirfd.get(), path, bytesFreed, ex MOUNTEDPATHS_ARG); - - if (ex) - std::rethrow_exception(ex); -} - -void deletePath(const std::filesystem::path & path) -{ - uint64_t dummy; - deletePath(path, dummy); -} - void createDir(const Path & path, mode_t mode) { if (mkdir( @@ -572,25 +441,6 @@ void createDirs(const std::filesystem::path & path) } } -void deletePath(const std::filesystem::path & path, uint64_t & bytesFreed) -{ - // Activity act(*logger, lvlDebug, "recursively deleting path '%1%'", path); -#ifdef __FreeBSD__ - std::set mountedPaths; - struct statfs * mntbuf; - int count; - if ((count = getmntinfo(&mntbuf, MNT_WAIT)) < 0) { - throw SysError("getmntinfo"); - } - - for (int i = 0; i < count; i++) { - mountedPaths.emplace(mntbuf[i].f_mntonname); - } -#endif - bytesFreed = 0; - _deletePath(path, bytesFreed MOUNTEDPATHS_ARG); -} - ////////////////////////////////////////////////////////////////////// AutoDelete::AutoDelete() diff --git a/src/libutil/unix/file-system.cc b/src/libutil/unix/file-system.cc index 6069c2d36..7086cb258 100644 --- a/src/libutil/unix/file-system.cc +++ b/src/libutil/unix/file-system.cc @@ -10,6 +10,8 @@ #include "nix/util/file-system.hh" #include "nix/util/environment-variables.hh" +#include "nix/util/signals.hh" +#include "nix/util/util.hh" #include "util-unix-config-private.hh" @@ -72,4 +74,154 @@ void setWriteTime( #endif } +#ifdef __FreeBSD__ +# define MOUNTEDPATHS_PARAM , std::set & mountedPaths +# define MOUNTEDPATHS_ARG , mountedPaths +#else +# define MOUNTEDPATHS_PARAM +# define MOUNTEDPATHS_ARG +#endif + +static void _deletePath( + Descriptor parentfd, + const std::filesystem::path & path, + uint64_t & bytesFreed, + std::exception_ptr & ex MOUNTEDPATHS_PARAM) +{ +#ifndef _WIN32 + checkInterrupt(); + +# ifdef __FreeBSD__ + // In case of emergency (unmount fails for some reason) not recurse into mountpoints. + // This prevents us from tearing up the nullfs-mounted nix store. + if (mountedPaths.find(path) != mountedPaths.end()) { + return; + } +# endif + + std::string name(path.filename()); + assert(name != "." && name != ".." && !name.empty()); + + 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, name.c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW); + if (fd == -1) + throw SysError("opening directory %1%", path); + AutoCloseDir dir(fdopendir(fd)); + if (!dir) + throw SysError("opening directory %1%", path); + + struct dirent * dirent; + while (errno = 0, dirent = readdir(dir.get())) { /* sic */ + checkInterrupt(); + std::string childName = dirent->d_name; + if (childName == "." || childName == "..") + continue; + _deletePath(dirfd(dir.get()), path / childName, bytesFreed, ex MOUNTEDPATHS_ARG); + } + if (errno) + throw SysError("reading directory %1%", path); + } + + int flags = S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0; + if (unlinkat(parentfd, name.c_str(), flags) == -1) { + if (errno == ENOENT) + return; + try { + throw SysError("cannot unlink %1%", path); + } catch (...) { + if (!ex) + ex = std::current_exception(); + else + ignoreExceptionExceptInterrupt(); + } + } +#else + // TODO implement + throw UnimplementedError("_deletePath"); +#endif +} + +static void _deletePath(const std::filesystem::path & path, uint64_t & bytesFreed MOUNTEDPATHS_PARAM) +{ + assert(path.is_absolute()); + assert(path.parent_path() != path); + + AutoCloseFD dirfd = toDescriptor(open(path.parent_path().string().c_str(), O_RDONLY)); + if (!dirfd) { + if (errno == ENOENT) + return; + throw SysError("opening directory %s", path.parent_path()); + } + + std::exception_ptr ex; + + _deletePath(dirfd.get(), path, bytesFreed, ex MOUNTEDPATHS_ARG); + + if (ex) + std::rethrow_exception(ex); +} + +void deletePath(const std::filesystem::path & path) +{ + uint64_t dummy; + deletePath(path, dummy); +} + +void deletePath(const std::filesystem::path & path, uint64_t & bytesFreed) +{ + // Activity act(*logger, lvlDebug, "recursively deleting path '%1%'", path); +#ifdef __FreeBSD__ + std::set mountedPaths; + struct statfs * mntbuf; + int count; + if ((count = getmntinfo(&mntbuf, MNT_WAIT)) < 0) { + throw SysError("getmntinfo"); + } + + for (int i = 0; i < count; i++) { + mountedPaths.emplace(mntbuf[i].f_mntonname); + } +#endif + bytesFreed = 0; + _deletePath(path, bytesFreed MOUNTEDPATHS_ARG); +} + } // namespace nix diff --git a/src/libutil/windows/file-system.cc b/src/libutil/windows/file-system.cc index 0c021777b..79931d591 100644 --- a/src/libutil/windows/file-system.cc +++ b/src/libutil/windows/file-system.cc @@ -39,4 +39,18 @@ std::filesystem::path defaultTempDir() return std::filesystem::path(buf); } +void deletePath(const std::filesystem::path & path) +{ + std::error_code ec; + std::filesystem::remove_all(path, ec); + if (ec && ec != std::errc::no_such_file_or_directory) + throw SysError(ec.default_error_condition().value(), "recursively deleting %1%", path); +} + +void deletePath(const std::filesystem::path & path, uint64_t & bytesFreed) +{ + bytesFreed = 0; + deletePath(path); +} + } // namespace nix