Index: lib/libc/gen/opendir.c =================================================================== --- lib/libc/gen/opendir.c +++ lib/libc/gen/opendir.c @@ -273,6 +273,28 @@ 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) { + unionstack = -1; + goto fail; + } + unionstack = !strcmp(sfb.f_fstypename, "unionfs") + || (sfb.f_flags & MNT_UNION); +fail: + return (unionstack); +} /* * Common routine for opendir(3), __opendir2(3) and fdopendir(3). @@ -310,15 +332,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: 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,48 @@ 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; + } + /* + * 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(&fp->f_vnode->v_mount); + if (__predict_false(mp == NULL)) { + fdrop(fp, td); + error = EBADF; + break; + } + td->td_retval[0] = 0; + if (strcmp(mp->mnt_vfc->vfc_name, "unionfs") == 0 || + 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 */