Index: lib/libc/gen/opendir.c =================================================================== --- lib/libc/gen/opendir.c +++ lib/libc/gen/opendir.c @@ -273,6 +273,24 @@ return (true); } +static int +is_unionstack(int fd) +{ + struct statfs sfb; + int unionstack; + + unionstack = _fcntl(fd, F_ISUNIONSTACK); + if (unionstack != -1) + return (unionstack); + + /* + * Temporary compat for kernels which don't provide F_ISUNIONSTACK. + */ + if (_fstatfs(fd, &sfb) < 0) + return (-1); + return (strcmp(sfb.f_fstypename, "unionfs") == 0 || + (sfb.f_flags & MNT_UNION)); +} /* * Common routine for opendir(3), __opendir2(3) and fdopendir(3). @@ -310,15 +328,11 @@ /* * Determine whether this directory is the top of a union stack. */ + unionstack = 0; if (flags & DTF_NODUP) { - struct statfs sfb; - - if (_fstatfs(fd, &sfb) < 0) + unionstack = is_unionstack(fd); + if (unionstack == -1) goto fail; - unionstack = !strcmp(sfb.f_fstypename, "unionfs") - || (sfb.f_flags & MNT_UNION); - } else { - unionstack = 0; } if (unionstack) { Index: lib/libc/sys/fcntl.2 =================================================================== --- lib/libc/sys/fcntl.2 +++ lib/libc/sys/fcntl.2 @@ -185,6 +185,9 @@ seals. .It Dv F_GET_SEALS Get seals associated with the file, if the underlying filesystem supports seals. +.It Dv F_ISUNIONSTACK +See if the vnode is part of a union stack (either the "union" lag from mount(2) +or unionfs). This is a hack not intended to be used outside of libc. .El .Pp The flags for the Index: sys/fs/unionfs/union_vfsops.c =================================================================== --- sys/fs/unionfs/union_vfsops.c +++ sys/fs/unionfs/union_vfsops.c @@ -296,7 +296,7 @@ if ((ump->um_lowervp->v_mount->mnt_flag & MNT_LOCAL) && (ump->um_uppervp->v_mount->mnt_flag & MNT_LOCAL)) mp->mnt_flag |= MNT_LOCAL; - mp->mnt_kern_flag |= MNTK_NOMSYNC; + mp->mnt_kern_flag |= MNTK_NOMSYNC | MNTK_UNIONFS; MNT_IUNLOCK(mp); /* Index: sys/kern/kern_descrip.c =================================================================== --- sys/kern/kern_descrip.c +++ sys/kern/kern_descrip.c @@ -489,6 +489,7 @@ struct filedescent *fde; struct proc *p; struct vnode *vp; + struct mount *mp; int error, flg, seals, tmp; uint64_t bsize; off_t foffset; @@ -816,6 +817,49 @@ fdrop(fp, td); break; + case F_ISUNIONSTACK: + /* + * See if the vnode is part of a union stack (either the "union" + * flag from mount(2) or unionfs). + * + * Prior to introduction of this op libc's readdir would call + * fstatfs(2), in effect unnecessarily copying kilobytes of + * data just to check fs name and a mount flag. + * + * Fixing the code to handle everything in the kernel instead + * is a non-trivial endeavor and has low priority, thus this + * horrible kludge facilitates the current behavior in a much + * cheaper manner until someone(tm) sorts this out. + */ + error = fget_unlocked(fdp, fd, &cap_no_rights, &fp, NULL); + if (error != 0) + break; + if (fp->f_type != DTYPE_VNODE) { + fdrop(fp, td); + error = EBADF; + break; + } + vp = fp->f_vnode; + /* + * Since we don't prevent dooming the vnode even non-null mp + * found can become immediately stale. This is tolerable since + * mount points are type-stable (providing safe memory access) + * and any vfs op on this vnode going forward will return an + * error (meaning return value in this case is meaningless). + */ + mp = (struct mount *)atomic_load_ptr(&vp->v_mount); + if (__predict_false(mp == NULL)) { + fdrop(fp, td); + error = EBADF; + break; + } + td->td_retval[0] = 0; + if (mp->mnt_kern_flag & MNTK_UNIONFS || + mp->mnt_flag & MNT_UNION) + td->td_retval[0] = 1; + fdrop(fp, td); + break; + default: error = EINVAL; break; Index: sys/sys/fcntl.h =================================================================== --- sys/sys/fcntl.h +++ sys/sys/fcntl.h @@ -250,6 +250,7 @@ #define F_DUP2FD_CLOEXEC 18 /* Like F_DUP2FD, but FD_CLOEXEC is set */ #define F_ADD_SEALS 19 #define F_GET_SEALS 20 +#define F_ISUNIONSTACK 21 /* Kludge for libc, don't use it. */ /* Seals (F_ADD_SEALS, F_GET_SEALS). */ #define F_SEAL_SEAL 0x0001 /* Prevent adding sealings */ Index: sys/sys/mount.h =================================================================== --- sys/sys/mount.h +++ sys/sys/mount.h @@ -414,6 +414,7 @@ #define MNTK_USES_BCACHE 0x00004000 /* FS uses the buffer cache. */ #define MNTK_TEXT_REFS 0x00008000 /* Keep use ref for text */ #define MNTK_VMSETSIZE_BUG 0x00010000 +#define MNTK_UNIONFS 0x00020000 /* A hack for F_ISUNIONSTACK */ #define MNTK_NOASYNC 0x00800000 /* disable async */ #define MNTK_UNMOUNT 0x01000000 /* unmount in progress */ #define MNTK_MWAIT 0x02000000 /* waiting for unmount to finish */