Re: [PATCH v1 01/12] VFS: don't count references through ->d_parent
Miklos Szeredi <[email protected]>
| Newsgroups | gmane.linux.nfs,gmane.linux.kernel.autofs,gmane.linux.kernel,gmane.comp.file-systems.ceph.devel,gmane.comp.file-systems.coda.general,gmane.linux.file-systems |
|---|---|
| Message-ID | <CAJfpegt1wHngLs9uR943gT6zxV67D53k3n4GiFKowsAb3C6LJw@mail.gmail.com> |
On Mon, 3 Aug 2026 at 03:37, NeilBrown <[email protected]> wrote: > When a DCACHE_CURSOR dentry is added to d_children, it is only ever > added "before" or "behind" an existing child, so it will never be the > first and so never needs to adjust the refcount on the parent. If it > remains on the list until dput() it could be the last child to be > removed, in which case normal handling applies in dentry_unlist() (where > it has been moved from dentry_kill(). Not handling the parent refcount together with cursor removal can result in inconsistency, e.g: - add cursor (seek between two positive dentries) - remove all real children (parent refcount not touched, since cursor is still on d_children) - remove cursor (seek to zero offset) - close directory (dentry_unlist() will skip parent refcount update since dentry->d_sib is no longer linked) Repro attached, produces "BUG: Dentry ffff888107ef75d0{i=143e,n=dir} still in use (1) [unm ount of hugetlbfs hugetlbfs]". Thanks, Milklos
rmwhilereaddir.c
(text/x-csrc, 905 B)
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <err.h>
#include <sys/stat.h>
#define CHECK_ERR(_expr) \
({ typeof(_expr) _ret = (_expr); if (_ret == -1) { err(1, #_expr); } _ret; })
#define CHECK_NULL(_expr) \
({ typeof(_expr) _ret = (_expr); if (_ret == NULL) { errx(1, #_expr " returned NULL"); } _ret; })
int main(void)
{
int dfd;
DIR *dp;
struct dirent *de;
CHECK_ERR(mkdir("dir", 0777));
CHECK_ERR(mknod("dir/foo", 0666, 0));
CHECK_ERR(mknod("dir/bar", 0666, 0));
dfd = CHECK_ERR(open("dir", O_RDONLY));
dp = CHECK_NULL(fdopendir(dfd));
do {
de = CHECK_NULL(readdir(dp));
} while (de->d_name[0] == '.');
CHECK_ERR(lseek(dfd, de->d_off, SEEK_SET)); /* links cursor between foo and bar */
CHECK_ERR(unlink("dir/foo"));
CHECK_ERR(unlink("dir/bar"));
CHECK_ERR(lseek(dfd, 0, SEEK_SET)); /* removes cursor */
CHECK_ERR(closedir(dp));
CHECK_ERR(rmdir("dir"));
}