stat-util: fix dir_is_empty() with hidden/backup files
This is a follow-up forf470cb6d13which in turn is a follow-up fora068aceafb. The latter started to honour hidden files when deciding whether a directory is empty. The former reverted to the old behaviour to fix issue #23220. It introduced a bug though: when a directory contains a larger number of hidden entries the getdents64() buffer will not suffice to read them, since we just allocate three entries for it (which is definitely enough if we just ignore the . + .. entries, but not ig we ignore more). I think it's a bit confusing that dir_is_empty() can return true even if rmdir() on the dir would return ENOTEMPTY. Hence, let's rework the function to make it optional whether hidden files are ignored or not. After all, I looking at the users of this function I am pretty sure in more cases we want to honour hidden files.
This commit is contained in:
+25
-13
@@ -71,13 +71,10 @@ int is_device_node(const char *path) {
|
||||
return !!(S_ISBLK(info.st_mode) || S_ISCHR(info.st_mode));
|
||||
}
|
||||
|
||||
int dir_is_empty_at(int dir_fd, const char *path) {
|
||||
int dir_is_empty_at(int dir_fd, const char *path, bool ignore_hidden_or_backup) {
|
||||
_cleanup_close_ int fd = -1;
|
||||
/* Allocate space for at least 3 full dirents, since every dir has at least two entries ("." +
|
||||
* ".."), and only once we have seen if there's a third we know whether the dir is empty or not. */
|
||||
DEFINE_DIRENT_BUFFER(buffer, 3);
|
||||
struct dirent *de;
|
||||
ssize_t n;
|
||||
struct dirent *buf;
|
||||
size_t m;
|
||||
|
||||
if (path) {
|
||||
assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
|
||||
@@ -99,15 +96,30 @@ int dir_is_empty_at(int dir_fd, const char *path) {
|
||||
return fd;
|
||||
}
|
||||
|
||||
n = getdents64(fd, &buffer, sizeof(buffer));
|
||||
if (n < 0)
|
||||
return -errno;
|
||||
/* Allocate space for at least 3 full dirents, since every dir has at least two entries ("." +
|
||||
* ".."), and only once we have seen if there's a third we know whether the dir is empty or not. If
|
||||
* 'ignore_hidden_or_backup' is true we'll allocate a bit more, since we might skip over a bunch of
|
||||
* entries that we end up ignoring. */
|
||||
m = (ignore_hidden_or_backup ? 16 : 3) * DIRENT_SIZE_MAX;
|
||||
buf = alloca(m);
|
||||
|
||||
msan_unpoison(&buffer, n);
|
||||
for (;;) {
|
||||
struct dirent *de;
|
||||
ssize_t n;
|
||||
|
||||
FOREACH_DIRENT_IN_BUFFER(de, &buffer.de, n)
|
||||
if (!hidden_or_backup_file(de->d_name))
|
||||
return 0;
|
||||
n = getdents64(fd, buf, m);
|
||||
if (n < 0)
|
||||
return -errno;
|
||||
if (n == 0)
|
||||
break;
|
||||
|
||||
assert((size_t) n <= m);
|
||||
msan_unpoison(buf, n);
|
||||
|
||||
FOREACH_DIRENT_IN_BUFFER(de, buf, n)
|
||||
if (!(ignore_hidden_or_backup ? hidden_or_backup_file(de->d_name) : dot_or_dot_dot(de->d_name)))
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -17,14 +17,14 @@ int is_dir(const char *path, bool follow);
|
||||
int is_dir_fd(int fd);
|
||||
int is_device_node(const char *path);
|
||||
|
||||
int dir_is_empty_at(int dir_fd, const char *path);
|
||||
static inline int dir_is_empty(const char *path) {
|
||||
return dir_is_empty_at(AT_FDCWD, path);
|
||||
int dir_is_empty_at(int dir_fd, const char *path, bool ignore_hidden_or_backup);
|
||||
static inline int dir_is_empty(const char *path, bool ignore_hidden_or_backup) {
|
||||
return dir_is_empty_at(AT_FDCWD, path, ignore_hidden_or_backup);
|
||||
}
|
||||
|
||||
static inline int dir_is_populated(const char *path) {
|
||||
static inline int dir_is_populated(const char *path, bool ignore_hidden_or_backup) {
|
||||
int r;
|
||||
r = dir_is_empty(path);
|
||||
r = dir_is_empty(path, ignore_hidden_or_backup);
|
||||
if (r < 0)
|
||||
return r;
|
||||
return !r;
|
||||
|
||||
Reference in New Issue
Block a user