Index: head/include/unistd.h =================================================================== --- head/include/unistd.h +++ head/include/unistd.h @@ -585,6 +585,7 @@ int undelete(const char *); int unwhiteout(const char *); void *valloc(size_t); /* obsoleted by malloc() */ +int funlinkat(int, const char *, int, int); #ifndef _OPTRESET_DECLARED #define _OPTRESET_DECLARED Index: head/lib/libc/sys/Makefile.inc =================================================================== --- head/lib/libc/sys/Makefile.inc +++ head/lib/libc/sys/Makefile.inc @@ -485,6 +485,7 @@ MLINKS+=thr_kill.2 thr_kill2.2 MLINKS+=truncate.2 ftruncate.2 MLINKS+=unlink.2 unlinkat.2 +MLINKS+=unlink.2 funlinkat.2 MLINKS+=utimensat.2 futimens.2 MLINKS+=utimes.2 futimes.2 \ utimes.2 futimesat.2 \ Index: head/lib/libc/sys/Symbol.map =================================================================== --- head/lib/libc/sys/Symbol.map +++ head/lib/libc/sys/Symbol.map @@ -406,6 +406,7 @@ fhlinkat; fhreadlink; getfhat; + funlinkat; }; FBSDprivate_1.0 { Index: head/lib/libc/sys/unlink.2 =================================================================== --- head/lib/libc/sys/unlink.2 +++ head/lib/libc/sys/unlink.2 @@ -28,7 +28,7 @@ .\" @(#)unlink.2 8.1 (Berkeley) 6/4/93 .\" $FreeBSD$ .\" -.Dd November 11, 2018 +.Dd April 6, 2019 .Dt UNLINK 2 .Os .Sh NAME @@ -42,7 +42,9 @@ .Ft int .Fn unlink "const char *path" .Ft int -.Fn unlinkat "int fd" "const char *path" "int flag" +.Fn unlinkat "int dfd" "const char *path" "int flag" +.Ft int +.Fn funlinkat "int dfd" "const char *path" "int fd" "int flag" .Sh DESCRIPTION The .Fn unlink @@ -74,7 +76,7 @@ specifies a relative path. In this case the directory entry to be removed is determined relative to the directory associated with the file descriptor -.Fa fd +.Fa dfd instead of the current working directory. .Pp The values for @@ -113,6 +115,26 @@ respectively, depending on whether or not the .Dv AT_REMOVEDIR bit is set in flag. +.Pp +The +.Fn funlinkat +system call can be used to unlink an already-opened file, unless that +file has been replaced since it was opened. +It is equivalent to +.Fn unlinkat +in the case where +.Fa path +is already open as the file descriptor +.Fa fd . +Otherwise, the path will not be removed and an error will be returned. +The +.Fa fd +can be set the +.Dv FD_NONE . +In that case +.Fn funlinkat +behaves exactly like +.Fn unlinkat . .Sh RETURN VALUES .Rv -std unlink .Sh ERRORS @@ -227,6 +249,15 @@ .Fa path escapes it. .El +.Pp +In addition to the errors returned by +.Fn unlinkat , +.Fn funlinkat +may fail if: +.Bl -tag -width Er +.It Bq Er EDEADLK +The file descriptor is not associated with the path. +.El .Sh SEE ALSO .Xr chflags 2 , .Xr close 2 , @@ -246,6 +277,10 @@ .Fn unlinkat system call appeared in .Fx 8.0 . +The +.Fn funlinkat +system call appeared in +.Fx 13.0 . .Pp The .Fn unlink Index: head/sys/cddl/compat/opensolaris/sys/vnode.h =================================================================== --- head/sys/cddl/compat/opensolaris/sys/vnode.h +++ head/sys/cddl/compat/opensolaris/sys/vnode.h @@ -278,7 +278,8 @@ ASSERT(seg == UIO_SYSSPACE); ASSERT(dirflag == RMFILE); - return (kern_unlinkat(curthread, AT_FDCWD, fnamep, seg, 0, 0)); + return (kern_funlinkat(curthread, AT_FDCWD, fnamep, FD_NONE, seg, 0, + 0)); } #endif /* _KERNEL */ Index: head/sys/compat/cloudabi/cloudabi_file.c =================================================================== --- head/sys/compat/cloudabi/cloudabi_file.c +++ head/sys/compat/cloudabi/cloudabi_file.c @@ -752,9 +752,11 @@ return (error); if (uap->flags & CLOUDABI_UNLINK_REMOVEDIR) - error = kern_rmdirat(td, uap->fd, path, UIO_SYSSPACE, 0); + error = kern_frmdirat(td, uap->fd, path, FD_NONE, + UIO_SYSSPACE, 0); else - error = kern_unlinkat(td, uap->fd, path, UIO_SYSSPACE, 0, 0); + error = kern_funlinkat(td, uap->fd, path, FD_NONE, + UIO_SYSSPACE, 0, 0); cloudabi_freestr(path); return (error); } Index: head/sys/compat/freebsd32/syscalls.master =================================================================== --- head/sys/compat/freebsd32/syscalls.master +++ head/sys/compat/freebsd32/syscalls.master @@ -1145,5 +1145,7 @@ const char *to); } 567 AUE_NULL NOPROTO { int fhreadlink( struct fhandle *fhp, char *buf, \ size_t bufsize); } +568 AUE_UNLINKAT NOPROTO { int funlinkat(int dfd, const char *path, int fd, \ + int flag); } ; vim: syntax=off Index: head/sys/compat/linux/linux_file.c =================================================================== --- head/sys/compat/linux/linux_file.c +++ head/sys/compat/linux/linux_file.c @@ -590,7 +590,7 @@ printf(ARGS(unlink, "%s"), path); #endif - error = kern_unlinkat(td, AT_FDCWD, path, UIO_SYSSPACE, 0, 0); + error = kern_funlinkat(td, AT_FDCWD, path, FD_NONE, UIO_SYSSPACE, 0, 0); if (error == EPERM) { /* Introduce POSIX noncompliant behaviour of Linux */ if (kern_statat(td, 0, AT_FDCWD, path, UIO_SYSSPACE, &st, @@ -623,9 +623,10 @@ #endif if (args->flag & LINUX_AT_REMOVEDIR) - error = kern_rmdirat(td, dfd, path, UIO_SYSSPACE, 0); + error = kern_frmdirat(td, dfd, path, FD_NONE, UIO_SYSSPACE, 0); else - error = kern_unlinkat(td, dfd, path, UIO_SYSSPACE, 0, 0); + error = kern_funlinkat(td, dfd, path, FD_NONE, UIO_SYSSPACE, 0, + 0); if (error == EPERM && !(args->flag & LINUX_AT_REMOVEDIR)) { /* Introduce POSIX noncompliant behaviour of Linux */ if (kern_statat(td, AT_SYMLINK_NOFOLLOW, dfd, path, @@ -741,7 +742,7 @@ if (ldebug(rmdir)) printf(ARGS(rmdir, "%s"), path); #endif - error = kern_rmdirat(td, AT_FDCWD, path, UIO_SYSSPACE, 0); + error = kern_frmdirat(td, AT_FDCWD, path, FD_NONE, UIO_SYSSPACE, 0); LFREEPATH(path); return (error); } Index: head/sys/kern/capabilities.conf =================================================================== --- head/sys/kern/capabilities.conf +++ head/sys/kern/capabilities.conf @@ -468,6 +468,7 @@ renameat symlinkat unlinkat +funlinkat utimensat ## Index: head/sys/kern/syscalls.master =================================================================== --- head/sys/kern/syscalls.master +++ head/sys/kern/syscalls.master @@ -3167,6 +3167,14 @@ size_t bufsize ); } +568 AUE_UNLINKAT STD { + int funlinkat( + int dfd, + _In_z_ const char *path, + int fd, + int flag + ); + } ; Please copy any additions and changes to the following compatability tables: ; sys/compat/freebsd32/syscalls.master Index: head/sys/kern/vfs_mountroot.c =================================================================== --- head/sys/kern/vfs_mountroot.c +++ head/sys/kern/vfs_mountroot.c @@ -389,7 +389,7 @@ if (mporoot == mpdevfs) { vfs_unbusy(mpdevfs); /* Unlink the no longer needed /dev/dev -> / symlink */ - error = kern_unlinkat(td, AT_FDCWD, "/dev/dev", + error = kern_funlinkat(td, AT_FDCWD, "/dev/dev", FD_NONE, UIO_SYSSPACE, 0, 0); if (error) printf("mountroot: unable to unlink /dev/dev " Index: head/sys/kern/vfs_syscalls.c =================================================================== --- head/sys/kern/vfs_syscalls.c +++ head/sys/kern/vfs_syscalls.c @@ -1751,9 +1751,24 @@ sys_unlink(struct thread *td, struct unlink_args *uap) { - return (kern_unlinkat(td, AT_FDCWD, uap->path, UIO_USERSPACE, 0, 0)); + return (kern_funlinkat(td, AT_FDCWD, uap->path, FD_NONE, UIO_USERSPACE, + 0, 0)); } +static int +kern_funlinkat_ex(struct thread *td, int dfd, const char *path, int fd, + int flag, enum uio_seg pathseg, ino_t oldinum) +{ + + if ((flag & ~AT_REMOVEDIR) != 0) + return (EINVAL); + + if ((flag & AT_REMOVEDIR) != 0) + return (kern_frmdirat(td, dfd, path, fd, UIO_USERSPACE, 0)); + + return (kern_funlinkat(td, dfd, path, fd, UIO_USERSPACE, 0, 0)); +} + #ifndef _SYS_SYSPROTO_H_ struct unlinkat_args { int fd; @@ -1764,46 +1779,69 @@ int sys_unlinkat(struct thread *td, struct unlinkat_args *uap) { - int fd, flag; - const char *path; - flag = uap->flag; - fd = uap->fd; - path = uap->path; + return (kern_funlinkat_ex(td, uap->fd, uap->path, FD_NONE, uap->flag, + UIO_USERSPACE, 0)); +} - if ((flag & ~(AT_REMOVEDIR | AT_BENEATH)) != 0) - return (EINVAL); +#ifndef _SYS_SYSPROTO_H_ +struct funlinkat_args { + int dfd; + const char *path; + int fd; + int flag; +}; +#endif +int +sys_funlinkat(struct thread *td, struct funlinkat_args *uap) +{ - if ((uap->flag & AT_REMOVEDIR) != 0) - return (kern_rmdirat(td, fd, path, UIO_USERSPACE, flag)); - else - return (kern_unlinkat(td, fd, path, UIO_USERSPACE, flag, 0)); + return (kern_funlinkat_ex(td, uap->dfd, uap->path, uap->fd, uap->flag, + UIO_USERSPACE, 0)); } int -kern_unlinkat(struct thread *td, int fd, const char *path, +kern_funlinkat(struct thread *td, int dfd, const char *path, int fd, enum uio_seg pathseg, int flag, ino_t oldinum) { struct mount *mp; + struct file *fp; struct vnode *vp; struct nameidata nd; struct stat sb; + cap_rights_t rights; int error; + fp = NULL; + if (fd != FD_NONE) { + error = getvnode(td, fd, cap_rights_init(&rights, CAP_LOOKUP), + &fp); + if (error != 0) + return (error); + } + restart: bwillwrite(); NDINIT_ATRIGHTS(&nd, DELETE, LOCKPARENT | LOCKLEAF | AUDITVNODE1 | ((flag & AT_BENEATH) != 0 ? BENEATH : 0), - pathseg, path, fd, &cap_unlinkat_rights, td); - if ((error = namei(&nd)) != 0) - return (error == EINVAL ? EPERM : error); + pathseg, path, dfd, &cap_unlinkat_rights, td); + if ((error = namei(&nd)) != 0) { + if (error == EINVAL) + error = EPERM; + goto fdout; + } vp = nd.ni_vp; if (vp->v_type == VDIR && oldinum == 0) { error = EPERM; /* POSIX */ } else if (oldinum != 0 && ((error = vn_stat(vp, &sb, td->td_ucred, NOCRED, td)) == 0) && sb.st_ino != oldinum) { - error = EIDRM; /* Identifier removed */ + error = EIDRM; /* Identifier removed */ + } else if (fp != NULL && fp->f_vnode != vp) { + if ((fp->f_vnode->v_iflag & VI_DOOMED) != 0) + error = EBADF; + else + error = EDEADLK; } else { /* * The root of a mounted filesystem cannot be deleted. @@ -1822,8 +1860,9 @@ else vput(vp); if ((error = vn_start_write(NULL, &mp, - V_XSLEEP | PCATCH)) != 0) - return (error); + V_XSLEEP | PCATCH)) != 0) { + goto fdout; + } goto restart; } #ifdef MAC @@ -1845,6 +1884,9 @@ vrele(vp); else vput(vp); +fdout: + if (fp != NULL) + fdrop(fp, td); return (error); } @@ -3704,25 +3746,36 @@ sys_rmdir(struct thread *td, struct rmdir_args *uap) { - return (kern_rmdirat(td, AT_FDCWD, uap->path, UIO_USERSPACE, 0)); + return (kern_frmdirat(td, AT_FDCWD, uap->path, FD_NONE, UIO_USERSPACE, + 0)); } int -kern_rmdirat(struct thread *td, int fd, const char *path, enum uio_seg pathseg, - int flag) +kern_frmdirat(struct thread *td, int dfd, const char *path, int fd, + enum uio_seg pathseg, int flag) { struct mount *mp; struct vnode *vp; + struct file *fp; struct nameidata nd; + cap_rights_t rights; int error; + fp = NULL; + if (fd != FD_NONE) { + error = getvnode(td, fd, cap_rights_init(&rights, CAP_LOOKUP), + &fp); + if (error != 0) + return (error); + } + restart: bwillwrite(); NDINIT_ATRIGHTS(&nd, DELETE, LOCKPARENT | LOCKLEAF | AUDITVNODE1 | ((flag & AT_BENEATH) != 0 ? BENEATH : 0), - pathseg, path, fd, &cap_unlinkat_rights, td); + pathseg, path, dfd, &cap_unlinkat_rights, td); if ((error = namei(&nd)) != 0) - return (error); + goto fdout; vp = nd.ni_vp; if (vp->v_type != VDIR) { error = ENOTDIR; @@ -3742,6 +3795,15 @@ error = EBUSY; goto out; } + + if (fp != NULL && fp->f_vnode != vp) { + if ((fp->f_vnode->v_iflag & VI_DOOMED) != 0) + error = EBADF; + else + error = EDEADLK; + goto out; + } + #ifdef MAC error = mac_vnode_check_unlink(td->td_ucred, nd.ni_dvp, vp, &nd.ni_cnd); @@ -3756,7 +3818,7 @@ else vput(nd.ni_dvp); if ((error = vn_start_write(NULL, &mp, V_XSLEEP | PCATCH)) != 0) - return (error); + goto fdout; goto restart; } vfs_notify_upper(vp, VFS_NOTIFY_UPPER_UNLINK); @@ -3769,6 +3831,9 @@ vrele(nd.ni_dvp); else vput(nd.ni_dvp); +fdout: + if (fp != NULL) + fdrop(fp, td); return (error); } Index: head/sys/sys/fcntl.h =================================================================== --- head/sys/sys/fcntl.h +++ head/sys/sys/fcntl.h @@ -316,6 +316,16 @@ #define POSIX_FADV_NOREUSE 5 /* access data only once */ #endif + +#ifdef __BSD_VISIBLE +/* + * Magic value that specify that corresponding file descriptor to filename + * is unknown and sanitary check should be omitted in the funlinkat() and + * similar syscalls. + */ +#define FD_NONE -200 +#endif + #ifndef _KERNEL __BEGIN_DECLS int open(const char *, int, ...); Index: head/sys/sys/syscallsubr.h =================================================================== --- head/sys/sys/syscallsubr.h +++ head/sys/sys/syscallsubr.h @@ -218,7 +218,7 @@ enum uio_seg fromseg, struct mbuf **controlp); int kern_renameat(struct thread *td, int oldfd, const char *old, int newfd, const char *new, enum uio_seg pathseg); -int kern_rmdirat(struct thread *td, int fd, const char *path, +int kern_frmdirat(struct thread *td, int dfd, const char *path, int fd, enum uio_seg pathseg, int flag); int kern_sched_getparam(struct thread *td, struct thread *targettd, struct sched_param *param); @@ -285,7 +285,7 @@ int kern_thr_suspend(struct thread *td, struct timespec *tsp); int kern_truncate(struct thread *td, const char *path, enum uio_seg pathseg, off_t length); -int kern_unlinkat(struct thread *td, int fd, const char *path, +int kern_funlinkat(struct thread *td, int dfd, const char *path, int fd, enum uio_seg pathseg, int flag, ino_t oldinum); int kern_utimesat(struct thread *td, int fd, const char *path, enum uio_seg pathseg, struct timeval *tptr, enum uio_seg tptrseg); Index: head/sys/ufs/ffs/ffs_alloc.c =================================================================== --- head/sys/ufs/ffs/ffs_alloc.c +++ head/sys/ufs/ffs/ffs_alloc.c @@ -3431,14 +3431,15 @@ } #endif /* DEBUG */ /* - * kern_unlinkat will do its own start/finish writes and + * kern_funlinkat will do its own start/finish writes and * they do not nest, so drop ours here. Setting mp == NULL * indicates that vn_finished_write is not needed down below. */ vn_finished_write(mp); mp = NULL; - error = kern_unlinkat(td, AT_FDCWD, (char *)(intptr_t)cmd.value, - UIO_USERSPACE, 0, (ino_t)cmd.size); + error = kern_funlinkat(td, AT_FDCWD, + (char *)(intptr_t)cmd.value, FD_NONE, UIO_USERSPACE, + 0, (ino_t)cmd.size); break; case FFS_SET_INODE: