diff --git a/lib/libc/sys/stat.2 b/lib/libc/sys/stat.2 --- a/lib/libc/sys/stat.2 +++ b/lib/libc/sys/stat.2 @@ -111,6 +111,17 @@ flag in the .Xr open 2 manual page. +.It Dv AT_EMPTY_PATH +If the +.Fa path +argument is an empty string, operate on the file or directory +referenced by the descriptor +.Fa fd . +If +.Fa fd +is equal to +.Dv AT_FDCWD , +operate on the current working directory. .El .Pp If diff --git a/sys/kern/vfs_lookup.c b/sys/kern/vfs_lookup.c --- a/sys/kern/vfs_lookup.c +++ b/sys/kern/vfs_lookup.c @@ -319,6 +319,7 @@ ndp->ni_lcf |= NI_LCF_STRICTRELATIVE; ndp->ni_resflags |= NIRES_STRICTREL; if (ndp->ni_dirfd == AT_FDCWD) { + /* XXXKIB allow for EMPTYPATH ? */ #ifdef KTRACE if (KTRPOINT(td, KTR_CAPFAIL)) ktrcapfail(CAPFAIL_LOOKUP, NULL, NULL); @@ -400,7 +401,9 @@ } #endif } - if (error == 0 && (*dpp)->v_type != VDIR) + if (error == 0 && (*dpp)->v_type != VDIR && + (cnp->cn_pnbuf[0] != '\0' || + (cnp->cn_flags & EMPTYPATH) == 0)) error = ENOTDIR; } if (error == 0 && (cnp->cn_flags & RBENEATH) != 0) { @@ -466,7 +469,6 @@ * Don't allow empty pathnames. */ if (__predict_false(*cnp->cn_pnbuf == '\0')) { - namei_cleanup_cnp(cnp); return (ENOENT); } @@ -554,6 +556,36 @@ error = namei_getpath(ndp); if (__predict_false(error != 0)) { + if (error == ENOENT) { + MPASS(*cnp->cn_pnbuf == '\0'); + if ((cnp->cn_flags & EMPTYPATH) != 0) { + MPASS((cnp->cn_flags & (LOCKPARENT | + WANTPARENT)) == 0); + error = namei_setup(ndp, &dp, &pwd); + if (error == 0) { + ndp->ni_vp = dp; + vref(dp); + SDT_PROBE4(vfs, namei, lookup, return, + error, ndp->ni_vp, false, ndp); + namei_cleanup_cnp(cnp); + pwd_drop(pwd); + ndp->ni_resflags |= NIRES_EMPTYPATH; + NDVALIDATE(ndp); + if ((cnp->cn_flags & LOCKLEAF) != 0) { + VOP_LOCK(dp, (cnp->cn_flags & + LOCKSHARED) != 0 ? + LK_SHARED : LK_EXCLUSIVE); + if (VN_IS_DOOMED(dp)) { + vput(dp); + return (ENOENT); + } + } + return (0); + } + } + namei_cleanup_cnp(cnp); + } + SDT_PROBE4(vfs, namei, lookup, return, error, NULL, false, ndp); return (error); } diff --git a/sys/kern/vfs_syscalls.c b/sys/kern/vfs_syscalls.c --- a/sys/kern/vfs_syscalls.c +++ b/sys/kern/vfs_syscalls.c @@ -129,6 +129,8 @@ res |= (at_flags & AT_SYMLINK_NOFOLLOW) != 0 ? NOFOLLOW : FOLLOW; } + if ((mask & AT_EMPTY_PATH) != 0 && (at_flags & AT_EMPTY_PATH) != 0) + res |= EMPTYPATH; return (res); } @@ -1496,12 +1498,13 @@ int flag; flag = uap->flag; - if ((flag & ~(AT_SYMLINK_FOLLOW | AT_RESOLVE_BENEATH)) != 0) + if ((flag & ~(AT_SYMLINK_FOLLOW | AT_RESOLVE_BENEATH | + AT_EMPTY_PATH)) != 0) return (EINVAL); return (kern_linkat(td, uap->fd1, uap->fd2, uap->path1, uap->path2, UIO_USERSPACE, at2cnpflags(flag, AT_SYMLINK_FOLLOW | - AT_RESOLVE_BENEATH))); + AT_RESOLVE_BENEATH | AT_EMPTY_PATH))); } int hardlink_check_uid = 0; @@ -1578,6 +1581,23 @@ LOCKPARENT | SAVENAME | AUDITVNODE2 | NOCACHE, segflag, path, fd, &cap_linkat_target_rights, td); if ((error = namei(&nd)) == 0) { + if ((nd.ni_resflags & NIRES_EMPTYPATH) != 0) { + error = priv_check(td, PRIV_VFS_FHOPEN); + if (error != 0) { + NDFREE(&nd, NDF_ONLY_PNBUF); + if (nd.ni_vp != NULL) { + if (nd.ni_dvp == nd.ni_vp) + vrele(nd.ni_dvp); + else + vput(nd.ni_dvp); + vrele(nd.ni_vp); + } else { + vput(nd.ni_dvp); + } + vrele(vp); + return (error); + } + } if (nd.ni_vp != NULL) { NDFREE(&nd, NDF_ONLY_PNBUF); if (nd.ni_dvp == nd.ni_vp) @@ -2387,12 +2407,13 @@ struct nameidata nd; int error; - if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_RESOLVE_BENEATH)) != 0) + if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_RESOLVE_BENEATH | + AT_EMPTY_PATH)) != 0) return (EINVAL); NDINIT_ATRIGHTS(&nd, LOOKUP, at2cnpflags(flag, AT_RESOLVE_BENEATH | - AT_SYMLINK_NOFOLLOW) | LOCKSHARED | LOCKLEAF | AUDITVNODE1, - pathseg, path, fd, &cap_fstat_rights, td); + AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH) | LOCKSHARED | LOCKLEAF | + AUDITVNODE1, pathseg, path, fd, &cap_fstat_rights, td); if ((error = namei(&nd)) != 0) return (error); @@ -2710,7 +2731,8 @@ sys_chflagsat(struct thread *td, struct chflagsat_args *uap) { - if ((uap->atflag & ~(AT_SYMLINK_NOFOLLOW | AT_RESOLVE_BENEATH)) != 0) + if ((uap->atflag & ~(AT_SYMLINK_NOFOLLOW | AT_RESOLVE_BENEATH | + AT_EMPTY_PATH)) != 0) return (EINVAL); return (kern_chflagsat(td, uap->fd, uap->path, UIO_USERSPACE, @@ -2743,8 +2765,8 @@ AUDIT_ARG_FFLAGS(flags); NDINIT_ATRIGHTS(&nd, LOOKUP, at2cnpflags(atflag, AT_SYMLINK_NOFOLLOW | - AT_RESOLVE_BENEATH) | AUDITVNODE1, pathseg, path, fd, - &cap_fchflags_rights, td); + AT_RESOLVE_BENEATH | AT_EMPTY_PATH) | AUDITVNODE1, pathseg, path, + fd, &cap_fchflags_rights, td); if ((error = namei(&nd)) != 0) return (error); NDFREE_NOTHING(&nd); @@ -2838,7 +2860,8 @@ sys_fchmodat(struct thread *td, struct fchmodat_args *uap) { - if ((uap->flag & ~(AT_SYMLINK_NOFOLLOW | AT_RESOLVE_BENEATH)) != 0) + if ((uap->flag & ~(AT_SYMLINK_NOFOLLOW | AT_RESOLVE_BENEATH | + AT_EMPTY_PATH)) != 0) return (EINVAL); return (kern_fchmodat(td, uap->fd, uap->path, UIO_USERSPACE, @@ -2871,8 +2894,8 @@ AUDIT_ARG_MODE(mode); NDINIT_ATRIGHTS(&nd, LOOKUP, at2cnpflags(flag, AT_SYMLINK_NOFOLLOW | - AT_RESOLVE_BENEATH) | AUDITVNODE1, pathseg, path, fd, - &cap_fchmod_rights, td); + AT_RESOLVE_BENEATH | AT_EMPTY_PATH) | AUDITVNODE1, pathseg, path, + fd, &cap_fchmod_rights, td); if ((error = namei(&nd)) != 0) return (error); NDFREE_NOTHING(&nd); @@ -2966,7 +2989,8 @@ sys_fchownat(struct thread *td, struct fchownat_args *uap) { - if ((uap->flag & ~(AT_SYMLINK_NOFOLLOW | AT_RESOLVE_BENEATH)) != 0) + if ((uap->flag & ~(AT_SYMLINK_NOFOLLOW | AT_RESOLVE_BENEATH | + AT_EMPTY_PATH)) != 0) return (EINVAL); return (kern_fchownat(td, uap->fd, uap->path, UIO_USERSPACE, uap->uid, @@ -2982,8 +3006,8 @@ AUDIT_ARG_OWNER(uid, gid); NDINIT_ATRIGHTS(&nd, LOOKUP, at2cnpflags(flag, AT_SYMLINK_NOFOLLOW | - AT_RESOLVE_BENEATH) | AUDITVNODE1, pathseg, path, fd, - &cap_fchown_rights, td); + AT_RESOLVE_BENEATH | AT_EMPTY_PATH) | AUDITVNODE1, pathseg, path, + fd, &cap_fchown_rights, td); if ((error = namei(&nd)) != 0) return (error); @@ -3334,13 +3358,14 @@ struct timespec ts[2]; int error, flags; - if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_RESOLVE_BENEATH)) != 0) + if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_RESOLVE_BENEATH | + AT_EMPTY_PATH)) != 0) return (EINVAL); if ((error = getutimens(tptr, tptrseg, ts, &flags)) != 0) return (error); NDINIT_ATRIGHTS(&nd, LOOKUP, at2cnpflags(flag, AT_SYMLINK_NOFOLLOW | - AT_RESOLVE_BENEATH) | AUDITVNODE1, + AT_RESOLVE_BENEATH | AT_EMPTY_PATH) | AUDITVNODE1, pathseg, path, fd, &cap_futimes_rights, td); if ((error = namei(&nd)) != 0) return (error); diff --git a/sys/sys/fcntl.h b/sys/sys/fcntl.h --- a/sys/sys/fcntl.h +++ b/sys/sys/fcntl.h @@ -219,10 +219,13 @@ #define AT_SYMLINK_NOFOLLOW 0x0200 /* Do not follow symbolic links */ #define AT_SYMLINK_FOLLOW 0x0400 /* Follow symbolic link */ #define AT_REMOVEDIR 0x0800 /* Remove directory instead of file */ +#endif /* __POSIX_VISIBLE >= 200809 */ +#if __BSD_VISIBLE /* #define AT_UNUSED1 0x1000 *//* Was AT_BENEATH */ #define AT_RESOLVE_BENEATH 0x2000 /* Do not allow name resolution to walk out of dirfd */ -#endif +#define AT_EMPTY_PATH 0x4000 /* Operate on dirfd if path is empty */ +#endif /* __BSD_VISIBLE */ /* * Constants used for fcntl(2) diff --git a/sys/sys/namei.h b/sys/sys/namei.h --- a/sys/sys/namei.h +++ b/sys/sys/namei.h @@ -144,10 +144,12 @@ #define WANTPARENT 0x0010 /* want parent vnode returned unlocked */ #define FAILIFEXISTS 0x0020 /* return EEXIST if found */ #define FOLLOW 0x0040 /* follow symbolic links */ +#define EMPTYPATH 0x0080 /* Allow empty path for *at */ #define LOCKSHARED 0x0100 /* Shared lock leaf */ #define NOFOLLOW 0x0000 /* do not follow symbolic links (pseudo) */ #define RBENEATH 0x100000000ULL /* No escape, even tmp, from start dir */ #define MODMASK 0xf000001ffULL /* mask of operational modifiers */ + /* * Namei parameter descriptors. * @@ -198,6 +200,7 @@ */ #define NIRES_ABS 0x00000001 /* Path was absolute */ #define NIRES_STRICTREL 0x00000002 /* Restricted lookup result */ +#define NIRES_EMPTYPATH 0x00000004 /* EMPTYPATH used */ /* * Flags in ni_lcf, valid for the duration of the namei call.