Merge pull request #3541 from alyssais/gcdos

Fix long paths permanently breaking GC
This commit is contained in:
Eelco Dolstra 2020-04-28 16:33:45 +02:00 committed by GitHub
commit ee754f0f41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -268,16 +268,13 @@ bool isLink(const Path & path)
} }
DirEntries readDirectory(const Path & path) DirEntries readDirectory(DIR *dir, const Path & path)
{ {
DirEntries entries; DirEntries entries;
entries.reserve(64); entries.reserve(64);
AutoCloseDir dir(opendir(path.c_str()));
if (!dir) throw SysError(format("opening directory '%1%'") % path);
struct dirent * dirent; struct dirent * dirent;
while (errno = 0, dirent = readdir(dir.get())) { /* sic */ while (errno = 0, dirent = readdir(dir)) { /* sic */
checkInterrupt(); checkInterrupt();
string name = dirent->d_name; string name = dirent->d_name;
if (name == "." || name == "..") continue; if (name == "." || name == "..") continue;
@ -294,6 +291,14 @@ DirEntries readDirectory(const Path & path)
return entries; 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) unsigned char getFileType(const Path & path)
{ {
@ -389,12 +394,14 @@ void writeLine(int fd, string s)
} }
static void _deletePath(const Path & path, unsigned long long & bytesFreed) static void _deletePath(int parentfd, const Path & path, unsigned long long & bytesFreed)
{ {
checkInterrupt(); checkInterrupt();
string name(baseNameOf(path));
struct stat st; 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; if (errno == ENOENT) return;
throw SysError(format("getting status of '%1%'") % path); throw SysError(format("getting status of '%1%'") % path);
} }
@ -406,20 +413,45 @@ static void _deletePath(const Path & path, unsigned long long & bytesFreed)
/* Make the directory accessible. */ /* Make the directory accessible. */
const auto PERM_MASK = S_IRUSR | S_IWUSR | S_IXUSR; const auto PERM_MASK = S_IRUSR | S_IWUSR | S_IXUSR;
if ((st.st_mode & PERM_MASK) != PERM_MASK) { 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); throw SysError(format("chmod '%1%'") % path);
} }
for (auto & i : readDirectory(path)) int fd = openat(parentfd, path.c_str(), O_RDONLY);
_deletePath(path + "/" + i.name, bytesFreed); 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; if (errno == ENOENT) return;
throw SysError(format("cannot unlink '%1%'") % path); throw SysError(format("cannot unlink '%1%'") % path);
} }
} }
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) void deletePath(const Path & path)
{ {