Re: Fwd: Re: A new look at old NFS readdir() problems? - Mailing list pgsql-hackers
From | Tom Lane |
---|---|
Subject | Re: Fwd: Re: A new look at old NFS readdir() problems? |
Date | |
Msg-id | 338175.1735869481@sss.pgh.pa.us Whole thread Raw |
In response to | Re: Fwd: Re: A new look at old NFS readdir() problems? (Tom Lane <tgl@sss.pgh.pa.us>) |
Responses |
Re: Fwd: Re: A new look at old NFS readdir() problems?
|
List | pgsql-hackers |
I wrote: > Thomas Munro <thomas.munro@gmail.com> writes: >> For what little it's worth, I'm not quite convinced yet that FreeBSD's >> client isn't more broken than it needs to be. > I'm suspicious of that too. I poked at this a little further. I made the attached stand-alone test case (you don't need any more than "cc -o rmtree rmtree.c" to build it, then point the script at some NFS-mounted directory). This fails with my NAS at least as far back as FreeBSD 11.0. I also tried it on NetBSD 9.2 which seems fine. regards, tom lane #! /bin/sh set -e TESTDIR="$1" mkdir "$TESTDIR" i=0 while [ $i -lt 1000 ] do touch "$TESTDIR/$i" i=`expr $i + 1` done ./rmtree "$TESTDIR" /*------------------------------------------------------------------------- * * rmtree.c * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION * src/common/rmtree.c * *------------------------------------------------------------------------- */ #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys/stat.h> #include <sys/types.h> #include <dirent.h> typedef enum PGFileType { PGFILETYPE_ERROR, PGFILETYPE_UNKNOWN, PGFILETYPE_REG, PGFILETYPE_DIR, PGFILETYPE_LNK, } PGFileType; static void * palloc(size_t size) { void *tmp; /* Avoid unportable behavior of malloc(0) */ if (size == 0) size = 1; tmp = malloc(size); if (tmp == NULL) { fprintf(stderr, "out of memory\n"); exit(1); } return tmp; } static void * repalloc(void *ptr, size_t size) { void *tmp; /* Avoid unportable behavior of realloc(NULL, 0) */ if (ptr == NULL && size == 0) size = 1; tmp = realloc(ptr, size); if (!tmp) { fprintf(stderr, "out of memory\n"); exit(1); } return tmp; } static char * pstrdup(const char *in) { char *tmp; if (!in) { fprintf(stderr, "cannot duplicate null pointer (internal error)\n"); exit(1); } tmp = strdup(in); if (!tmp) { fprintf(stderr, "out of memory\n"); exit(1); } return tmp; } /* * Return the type of a directory entry. */ static PGFileType get_dirent_type(const char *path, const struct dirent *de, bool look_through_symlinks) { PGFileType result; /* * Some systems tell us the type directly in the dirent struct, but that's * a BSD and Linux extension not required by POSIX. Even when the * interface is present, sometimes the type is unknown, depending on the * filesystem. */ #if defined(DT_REG) && defined(DT_DIR) && defined(DT_LNK) if (de->d_type == DT_REG) result = PGFILETYPE_REG; else if (de->d_type == DT_DIR) result = PGFILETYPE_DIR; else if (de->d_type == DT_LNK && !look_through_symlinks) result = PGFILETYPE_LNK; else result = PGFILETYPE_UNKNOWN; #else result = PGFILETYPE_UNKNOWN; #endif if (result == PGFILETYPE_UNKNOWN) { struct stat fst; int sret; if (look_through_symlinks) sret = stat(path, &fst); else sret = lstat(path, &fst); if (sret < 0) { result = PGFILETYPE_ERROR; fprintf(stderr, "could not stat file \"%s\": %m\n", path); } else if (S_ISREG(fst.st_mode)) result = PGFILETYPE_REG; else if (S_ISDIR(fst.st_mode)) result = PGFILETYPE_DIR; else if (S_ISLNK(fst.st_mode)) result = PGFILETYPE_LNK; } return result; } /* * rmtree * * Delete a directory tree recursively. * Assumes path points to a valid directory. * Deletes everything under path. * If rmtopdir is true deletes the directory too. * Returns true if successful, false if there was any problem. * (The details of the problem are reported already, so caller * doesn't really have to say anything more, but most do.) */ static bool rmtree(const char *path, bool rmtopdir) { char pathbuf[8192]; DIR *dir; struct dirent *de; bool result = true; size_t dirnames_size = 0; size_t dirnames_capacity = 8; char **dirnames; dir = opendir(path); if (dir == NULL) { fprintf(stderr, "could not open directory \"%s\": %m\n", path); return false; } dirnames = (char **) palloc(sizeof(char *) * dirnames_capacity); while (errno = 0, (de = readdir(dir))) { if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) continue; snprintf(pathbuf, sizeof(pathbuf), "%s/%s", path, de->d_name); switch (get_dirent_type(pathbuf, de, false)) { case PGFILETYPE_ERROR: /* already logged, press on */ break; case PGFILETYPE_DIR: /* * Defer recursion until after we've closed this directory, to * avoid using more than one file descriptor at a time. */ if (dirnames_size == dirnames_capacity) { dirnames = repalloc(dirnames, sizeof(char *) * dirnames_capacity * 2); dirnames_capacity *= 2; } dirnames[dirnames_size++] = pstrdup(pathbuf); break; default: if (unlink(pathbuf) != 0 && errno != ENOENT) { fprintf(stderr, "could not remove file \"%s\": %m\n", pathbuf); result = false; } break; } } if (errno != 0) { fprintf(stderr, "could not read directory \"%s\": %m\n", path); result = false; } closedir(dir); /* Now recurse into the subdirectories we found. */ for (size_t i = 0; i < dirnames_size; ++i) { if (!rmtree(dirnames[i], true)) result = false; free(dirnames[i]); } if (rmtopdir) { if (rmdir(path) != 0) { fprintf(stderr, "could not remove directory \"%s\": %m\n", path); result = false; } } free(dirnames); return result; } int main(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "usage: %s target-directory\n", argv[0]); exit(1); } if (!rmtree(argv[1], true)) { fprintf(stderr, "rmtree failed\n"); exit(1); } return 0; }
pgsql-hackers by date: