about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--third_party/nix/src/libutil/util.cc58
1 files changed, 45 insertions, 13 deletions
diff --git a/third_party/nix/src/libutil/util.cc b/third_party/nix/src/libutil/util.cc
index 0ad2c51148c9..b77fb8ac0ab4 100644
--- a/third_party/nix/src/libutil/util.cc
+++ b/third_party/nix/src/libutil/util.cc
@@ -252,17 +252,12 @@ bool isLink(const Path& path) {
   return S_ISLNK(st.st_mode);
 }
 
-DirEntries readDirectory(const Path& path) {
+DirEntries readDirectory(DIR* dir, const Path& path) {
   DirEntries entries;
   entries.reserve(64);
 
-  AutoCloseDir dir(opendir(path.c_str()));
-  if (!dir) {
-    throw SysError(format("opening directory '%1%'") % path);
-  }
-
   struct dirent* dirent;
-  while (errno = 0, dirent = readdir(dir.get())) { /* sic */
+  while (errno = 0, dirent = readdir(dir)) { /* sic */
     checkInterrupt();
     string name = dirent->d_name;
     if (name == "." || name == "..") {
@@ -283,6 +278,15 @@ DirEntries readDirectory(const Path& path) {
   return entries;
 }
 
+DirEntries readDirectory(const Path& path) {
+  AutoCloseDir dir(opendir(path.c_str()));
+  if (!dir) {
+    throw SysError(format("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)) {
@@ -380,11 +384,14 @@ void writeLine(int fd, string s) {
   writeFull(fd, s);
 }
 
-static void _deletePath(const Path& path, unsigned long long& bytesFreed) {
+static void _deletePath(int parentfd, const Path& path,
+                        unsigned long long& bytesFreed) {
   checkInterrupt();
 
+  string name(baseNameOf(path));
+
   struct stat st;
-  if (lstat(path.c_str(), &st) == -1) {
+  if (fstatat(parentfd, name.c_str(), &st, AT_SYMLINK_NOFOLLOW) == -1) {
     if (errno == ENOENT) {
       return;
     }
@@ -399,17 +406,26 @@ static void _deletePath(const Path& path, unsigned long long& bytesFreed) {
     /* Make the directory accessible. */
     const auto PERM_MASK = S_IRUSR | S_IWUSR | S_IXUSR;
     if ((st.st_mode & PERM_MASK) != PERM_MASK) {
-      if (chmod(path.c_str(), st.st_mode | PERM_MASK) == -1) {
+      if (fchmodat(parentfd, name.c_str(), st.st_mode | PERM_MASK, 0) == -1) {
         throw SysError(format("chmod '%1%'") % path);
       }
     }
 
-    for (auto& i : readDirectory(path)) {
-      _deletePath(path + "/" + i.name, bytesFreed);
+    int fd = openat(parentfd, path.c_str(), O_RDONLY);
+    if (!fd) {
+      throw SysError(format("opening directory '%1%'") % path);
+    }
+    AutoCloseDir dir(fdopendir(fd));
+    if (!dir) {
+      throw SysError(format("opening directory '%1%'") % path);
+    }
+    for (auto& i : readDirectory(dir.get(), path)) {
+      _deletePath(dirfd(dir.get()), path + "/" + i.name, bytesFreed);
     }
   }
 
-  if (remove(path.c_str()) == -1) {
+  int flags = S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0;
+  if (unlinkat(parentfd, name.c_str(), flags) == -1) {
     if (errno == ENOENT) {
       return;
     }
@@ -417,6 +433,22 @@ static void _deletePath(const Path& path, unsigned long long& bytesFreed) {
   }
 }
 
+static void _deletePath(const Path& path, unsigned long long& bytesFreed) {
+  Path dir = dirOf(path);
+  if (dir == "") dir = "/";
+
+  AutoCloseFD dirfd(open(dir.c_str(), O_RDONLY));
+  if (!dirfd) {
+    // This really shouldn't fail silently, but it's left this way
+    // for backwards compatibility.
+    if (errno == ENOENT) return;
+
+    throw SysError(format("opening directory '%1%'") % path);
+  }
+
+  _deletePath(dirfd.get(), path, bytesFreed);
+}
+
 void deletePath(const Path& path) {
   unsigned long long dummy;
   deletePath(path, dummy);