Index: include/unistd.h =================================================================== --- include/unistd.h +++ include/unistd.h @@ -583,6 +583,7 @@ int undelete(const char *); int unwhiteout(const char *); void *valloc(size_t); /* obsoleted by malloc() */ +int fdunlinkat(int, const char *, int, int); #ifndef _OPTRESET_DECLARED #define _OPTRESET_DECLARED Index: lib/libc/sys/Makefile.inc =================================================================== --- lib/libc/sys/Makefile.inc +++ lib/libc/sys/Makefile.inc @@ -478,6 +478,7 @@ MLINKS+=thr_kill.2 thr_kill2.2 MLINKS+=truncate.2 ftruncate.2 MLINKS+=unlink.2 unlinkat.2 +MLINKS+=unlink.2 fdunlinkat.2 MLINKS+=utimensat.2 futimens.2 MLINKS+=utimes.2 futimes.2 \ utimes.2 futimesat.2 \ Index: lib/libc/sys/Symbol.map =================================================================== --- lib/libc/sys/Symbol.map +++ lib/libc/sys/Symbol.map @@ -400,6 +400,7 @@ statfs; cpuset_getdomain; cpuset_setdomain; + fdunlinkat; }; FBSDprivate_1.0 { Index: lib/libc/sys/unlink.2 =================================================================== --- lib/libc/sys/unlink.2 +++ lib/libc/sys/unlink.2 @@ -28,7 +28,7 @@ .\" @(#)unlink.2 8.1 (Berkeley) 6/4/93 .\" $FreeBSD$ .\" -.Dd December 1, 2017 +.Dd June 10, 2018 .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 fdunlinkat "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 @@ -105,6 +107,26 @@ respectively, depending on whether or not the .Dv AT_REMOVEDIR bit is set in flag. +.Pp +The +.Fn fdunlinkat +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 fdunlinkat +behaves exactly like +.Fn unlinkat . .Sh RETURN VALUES .Rv -std unlink .Sh ERRORS @@ -201,6 +223,15 @@ .Dv AT_FDCWD nor a file descriptor associated with a directory. .El +.Pp +In addition to the errors returned by the +.Fn unlinkat , +.Fn fdunlinkat +may fail if: +.Bl -tag -width Er +.It Bq Er EAGAIN +The file descriptor is not associated with the path. +.El .Sh SEE ALSO .Xr chflags 2 , .Xr close 2 , @@ -220,6 +251,10 @@ .Fn unlinkat system call appeared in .Fx 8.0 . +The +.Fn fdunlinkat +system call appeared in +.Fx 12.0 . .Pp The .Fn unlink Index: sys/amd64/conf/CAPSICUM =================================================================== --- sys/amd64/conf/CAPSICUM +++ sys/amd64/conf/CAPSICUM @@ -1,91 +0,0 @@ - - - - -cpu HAMMER -ident CAPSICUM - -makeoptions DEBUG=-g # Build kernel with gdb(1) debug symbols -makeoptions WITH_CTF=1 # Run ctfconvert(1) for DTrace support - -options SCHED_ULE # ULE scheduler -options PREEMPTION # Enable kernel thread preemption -options INET # InterNETworking -options INET6 # IPv6 communications protocols -options TCP_OFFLOAD # TCP offload -options SCTP # Stream Control Transmission Protocol -options NFSCL # New Network Filesystem client -options NFS_ROOT # NFS usable as /, requires NFSCL -options COMPAT_FREEBSD32 # Compatible with i386 binaries -options COMPAT_FREEBSD4 # Compatible with FreeBSD4 -options COMPAT_FREEBSD5 # Compatible with FreeBSD5 -options COMPAT_FREEBSD6 # Compatible with FreeBSD6 -options COMPAT_FREEBSD7 # Compatible with FreeBSD7 -options KTRACE # ktrace(1) support -options STACK # stack(9) support -options _KPOSIX_PRIORITY_SCHEDULING # POSIX P1003_1B real-time extensions -options PRINTF_BUFR_SIZE=128 # Prevent printf output being interspersed. -options KBD_INSTALL_CDEV # install a CDEV entry in /dev -options HWPMC_HOOKS # Necessary kernel hooks for hwpmc(4) -options AUDIT # Security event auditing -options CAPABILITY_MODE # Capsicum capability mode -options CAPABILITIES # Capsicum capabilities -options MAC # TrustedBSD MAC Framework -options KDTRACE_FRAME # Ensure frames are compiled in -options KDTRACE_HOOKS # Kernel DTrace hooks -options DDB_CTF # Kernel ELF linker loads CTF data - -# Debugging support. Always need this: -options KDB # Enable kernel debugger support. -# For minimum debugger support (stable branch) use: -#options KDB_TRACE # Print a stack trace for a panic. -# For full debugger support use this instead: -options DDB # Support DDB. -options INVARIANTS # Enable calls of extra sanity checking -options INVARIANT_SUPPORT # Extra sanity checks of internal structures, required by INVARIANTS -options WITNESS # Enable checks to detect deadlocks and cycles -options WITNESS_SKIPSPIN # Don't run witness on spinlocks for speed -options MALLOC_DEBUG_MAXZONES=8 # Separate malloc(9) zones - -# Bus support. -device acpi -device pci - -# atkbdc0 controls both the keyboard and the PS/2 mouse -device atkbdc # AT keyboard controller -device atkbd # AT keyboard - -device kbdmux # keyboard multiplexer - -device vga # VGA video card driver -options VESA # Add support for VESA BIOS Extensions (VBE) - -# syscons is the default console driver, resembling an SCO console -device sc -options SC_PIXEL_MODE # add support for the raster text mode - -# Serial (COM) ports -device uart # Generic UART driver - -# Pseudo devices. -device loop # Network loopback -device random # Entropy device -device ether # Ethernet support - -# The `bpf' device enables the Berkeley Packet Filter. -# Be aware of the administrative consequences of enabling this! -# Note that 'bpf' is required for DHCP. -device bpf # Berkeley packet filter - -device le - - -# VirtIO support -device virtio # Generic VirtIO bus (required) -device virtio_pci # VirtIO PCI device -device vtnet # VirtIO Ethernet device -device virtio_blk # VirtIO Block device -device virtio_scsi # VirtIO SCSI device -device virtio_balloon # VirtIO Memory Balloon device -device virtio_scsi # VirtIO SCSI device -device virtio_balloon # VirtIO Memory Balloon device Index: sys/cddl/compat/opensolaris/sys/vnode.h =================================================================== --- sys/cddl/compat/opensolaris/sys/vnode.h +++ sys/cddl/compat/opensolaris/sys/vnode.h @@ -278,7 +278,7 @@ ASSERT(seg == UIO_SYSSPACE); ASSERT(dirflag == RMFILE); - return (kern_unlinkat(curthread, AT_FDCWD, fnamep, seg, 0)); + return (kern_fdunlinkat(curthread, AT_FDCWD, fnamep, FD_NONE, seg, 0)); } #endif /* _KERNEL */ Index: sys/compat/cloudabi/cloudabi_file.c =================================================================== --- sys/compat/cloudabi/cloudabi_file.c +++ sys/compat/cloudabi/cloudabi_file.c @@ -750,9 +750,11 @@ return (error); if (uap->flags & CLOUDABI_UNLINK_REMOVEDIR) - error = kern_rmdirat(td, uap->fd, path, UIO_SYSSPACE); + error = kern_fdrmdirat(td, uap->fd, path, FD_NONE, + UIO_SYSSPACE); else - error = kern_unlinkat(td, uap->fd, path, UIO_SYSSPACE, 0); + error = kern_fdunlinkat(td, uap->fd, path, FD_NONE, + UIO_SYSSPACE, 0); cloudabi_freestr(path); return (error); } Index: sys/compat/freebsd32/capabilities.conf =================================================================== --- sys/compat/freebsd32/capabilities.conf +++ sys/compat/freebsd32/capabilities.conf @@ -192,6 +192,7 @@ renameat symlinkat unlinkat +fdunlinkat freebsd32_utimensat pdfork pdgetpid Index: sys/compat/freebsd32/syscalls.master =================================================================== --- sys/compat/freebsd32/syscalls.master +++ sys/compat/freebsd32/syscalls.master @@ -1123,5 +1123,7 @@ cpuwhich_t which, uint32_t id1, uint32_t id2, \ size_t domainsetsize, domainset_t *mask, \ int policy); } +563 AUE_UNLINKAT NOPROTO { int fdunlinkat(int dfd, const char *path, int fd, \ + int flag, mode_t mode); } ; vim: syntax=off Index: sys/compat/linux/linux_file.c =================================================================== --- sys/compat/linux/linux_file.c +++ sys/compat/linux/linux_file.c @@ -580,7 +580,7 @@ printf(ARGS(unlink, "%s"), path); #endif - error = kern_unlinkat(td, AT_FDCWD, path, UIO_SYSSPACE, 0); + error = kern_fdunlinkat(td, AT_FDCWD, path, FD_NONE, UIO_SYSSPACE, 0); if (error == EPERM) { /* Introduce POSIX noncompliant behaviour of Linux */ if (kern_statat(td, 0, AT_FDCWD, path, UIO_SYSSPACE, &st, @@ -612,9 +612,9 @@ #endif if (args->flag & LINUX_AT_REMOVEDIR) - error = kern_rmdirat(td, dfd, path, UIO_SYSSPACE); + error = kern_fdrmdirat(td, dfd, path, FD_NONE, UIO_SYSSPACE); else - error = kern_unlinkat(td, dfd, path, UIO_SYSSPACE, 0); + error = kern_fdunlinkat(td, dfd, path, FD_NONE, UIO_SYSSPACE, 0); if (error == EPERM && !(args->flag & LINUX_AT_REMOVEDIR)) { /* Introduce POSIX noncompliant behaviour of Linux */ if (kern_statat(td, AT_SYMLINK_NOFOLLOW, dfd, path, @@ -725,7 +725,7 @@ if (ldebug(rmdir)) printf(ARGS(rmdir, "%s"), path); #endif - error = kern_rmdirat(td, AT_FDCWD, path, UIO_SYSSPACE); + error = kern_fdrmdirat(td, AT_FDCWD, path, FD_NONE, UIO_SYSSPACE); LFREEPATH(path); return (error); } Index: sys/i386/ibcs2/ibcs2_misc.c =================================================================== --- sys/i386/ibcs2/ibcs2_misc.c +++ sys/i386/ibcs2/ibcs2_misc.c @@ -1071,7 +1071,7 @@ int error; CHECKALTEXIST(td, uap->path, &path); - error = kern_unlinkat(td, AT_FDCWD, path, UIO_SYSSPACE, 0); + error = kern_fdunlinkat(td, AT_FDCWD, path, FD_NONE, UIO_SYSSPACE, 0); free(path, M_TEMP); return (error); } @@ -1120,7 +1120,7 @@ int error; CHECKALTEXIST(td, uap->path, &path); - error = kern_rmdirat(td, AT_FDCWD, path, UIO_SYSSPACE); + error = kern_fdrmdirat(td, AT_FDCWD, path, FD_NONE, UIO_SYSSPACE); free(path, M_TEMP); return (error); } Index: sys/kern/capabilities.conf =================================================================== --- sys/kern/capabilities.conf +++ sys/kern/capabilities.conf @@ -463,6 +463,7 @@ renameat symlinkat unlinkat +fdunlinkat utimensat ## Index: sys/kern/syscalls.master =================================================================== --- sys/kern/syscalls.master +++ sys/kern/syscalls.master @@ -1027,6 +1027,8 @@ cpuwhich_t which, id_t id, \ size_t domainsetsize, domainset_t *mask, \ int policy); } +563 AUE_UNLINKAT STD { int fdunlinkat(int dfd, const char *path, int fd, \ + int flag, mode_t mode); } ; Please copy any additions and changes to the following compatability tables: ; sys/compat/freebsd32/syscalls.master Index: sys/kern/vfs_mountroot.c =================================================================== --- sys/kern/vfs_mountroot.c +++ sys/kern/vfs_mountroot.c @@ -388,7 +388,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_fdunlinkat(td, AT_FDCWD, "/dev/dev", FD_NONE, UIO_SYSSPACE, 0); if (error) printf("mountroot: unable to unlink /dev/dev " Index: sys/kern/vfs_syscalls.c =================================================================== --- sys/kern/vfs_syscalls.c +++ sys/kern/vfs_syscalls.c @@ -1737,7 +1737,22 @@ sys_unlink(struct thread *td, struct unlink_args *uap) { - return (kern_unlinkat(td, AT_FDCWD, uap->path, UIO_USERSPACE, 0)); + return (kern_fdunlinkat(td, AT_FDCWD, uap->path, FD_NONE, UIO_USERSPACE, + 0)); +} + +static int +kern_fdunlinkat_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_fdrmdirat(td, dfd, path, fd, UIO_USERSPACE)); + + return (kern_fdunlinkat(td, dfd, path, fd, UIO_USERSPACE, 0)); } #ifndef _SYS_SYSPROTO_H_ @@ -1750,43 +1765,65 @@ int sys_unlinkat(struct thread *td, struct unlinkat_args *uap) { - int flag = uap->flag; - int fd = uap->fd; - char *path = uap->path; - if (flag & ~AT_REMOVEDIR) - return (EINVAL); + return (kern_fdunlinkat_ex(td, uap->fd, uap->path, FD_NONE, uap->flag, + UIO_USERSPACE, 0)); +} - if (flag & AT_REMOVEDIR) - return (kern_rmdirat(td, fd, path, UIO_USERSPACE)); - else - return (kern_unlinkat(td, fd, path, UIO_USERSPACE, 0)); +#ifndef _SYS_SYSPROTO_H_ +struct fdunlinkat_args { + int dfd; + const char *path; + int fd; + int flag; +}; +#endif +int +sys_fdunlinkat(struct thread *td, struct fdunlinkat_args *uap) +{ + + return (kern_fdunlinkat_ex(td, uap->dfd, uap->path, uap->fd, uap->flag, + UIO_USERSPACE, 0)); } int -kern_unlinkat(struct thread *td, int fd, char *path, enum uio_seg pathseg, - ino_t oldinum) +kern_fdunlinkat(struct thread *td, int dfd, const char *path, int fd, + enum uio_seg pathseg, 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, - pathseg, path, fd, cap_rights_init(&rights, CAP_UNLINKAT), td); - if ((error = namei(&nd)) != 0) - return (error == EINVAL ? EPERM : error); + pathseg, path, dfd, cap_rights_init(&rights, CAP_UNLINKAT), 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) { + error = EAGAIN; } else { /* * The root of a mounted filesystem cannot be deleted. @@ -1805,8 +1842,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 @@ -1828,6 +1866,9 @@ vrele(vp); else vput(vp); +fdout: + if (fp != NULL) + fdrop(fp, td); return (error); } @@ -2645,7 +2686,7 @@ AUDIT_ARG_FD(uap->fd); AUDIT_ARG_FFLAGS(uap->flags); - error = getvnode(td, uap->fd, cap_rights_init(&rights, CAP_FCHFLAGS), + error = getvnode(td, uap->fd, cap_rights_init(&rights, CAP_LOOKUP), &fp); if (error != 0) return (error); @@ -3675,24 +3716,35 @@ sys_rmdir(struct thread *td, struct rmdir_args *uap) { - return (kern_rmdirat(td, AT_FDCWD, uap->path, UIO_USERSPACE)); + return (kern_fdrmdirat(td, AT_FDCWD, uap->path, FD_NONE, + UIO_USERSPACE)); } int -kern_rmdirat(struct thread *td, int fd, char *path, enum uio_seg pathseg) +kern_fdrmdirat(struct thread *td, int dfd, const char *path, int fd, + enum uio_seg pathseg) { 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, - pathseg, path, fd, cap_rights_init(&rights, CAP_UNLINKAT), td); + pathseg, path, dfd, cap_rights_init(&rights, CAP_UNLINKAT), td); if ((error = namei(&nd)) != 0) - return (error); + goto fdout; vp = nd.ni_vp; if (vp->v_type != VDIR) { error = ENOTDIR; @@ -3712,6 +3764,12 @@ error = EBUSY; goto out; } + + if (fp != NULL && fp->f_vnode != vp) { + error = EAGAIN; + goto out; + } + #ifdef MAC error = mac_vnode_check_unlink(td->td_ucred, nd.ni_dvp, vp, &nd.ni_cnd); @@ -3726,7 +3784,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); @@ -3739,6 +3797,9 @@ vrele(nd.ni_dvp); else vput(nd.ni_dvp); +fdout: + if (fp != NULL) + fdrop(fp, td); return (error); } Index: sys/sys/fcntl.h =================================================================== --- sys/sys/fcntl.h +++ sys/sys/fcntl.h @@ -313,6 +313,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 fdunlinkat() and + * similar syscalls. + */ +#define FD_NONE -100 +#endif + #ifndef _KERNEL __BEGIN_DECLS int open(const char *, int, ...); Index: sys/sys/syscallsubr.h =================================================================== --- sys/sys/syscallsubr.h +++ sys/sys/syscallsubr.h @@ -216,7 +216,7 @@ enum uio_seg fromseg, struct mbuf **controlp); int kern_renameat(struct thread *td, int oldfd, char *old, int newfd, char *new, enum uio_seg pathseg); -int kern_rmdirat(struct thread *td, int fd, char *path, +int kern_fdrmdirat(struct thread *td, int dfd, const char *path, int fd, enum uio_seg pathseg); int kern_sched_getparam(struct thread *td, struct thread *targettd, struct sched_param *param); @@ -283,7 +283,7 @@ int kern_thr_suspend(struct thread *td, struct timespec *tsp); int kern_truncate(struct thread *td, char *path, enum uio_seg pathseg, off_t length); -int kern_unlinkat(struct thread *td, int fd, char *path, +int kern_fdunlinkat(struct thread *td, int dfd, const char *path, int fd, enum uio_seg pathseg, ino_t oldinum); int kern_utimesat(struct thread *td, int fd, char *path, enum uio_seg pathseg, struct timeval *tptr, enum uio_seg tptrseg); Index: sys/ufs/ffs/ffs_alloc.c =================================================================== --- sys/ufs/ffs/ffs_alloc.c +++ sys/ufs/ffs/ffs_alloc.c @@ -3096,14 +3096,15 @@ } #endif /* DEBUG */ /* - * kern_unlinkat will do its own start/finish writes and + * kern_fdunlinkat 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, (ino_t)cmd.size); + error = kern_fdunlinkat(td, AT_FDCWD, + (char *)(intptr_t)cmd.value, FD_NONE, UIO_USERSPACE, + (ino_t)cmd.size); break; case FFS_SET_INODE: