Introduce funlinkat syscall that always us to check if we are removing

the file associated with the given file descriptor.

Reviewed by:	kib, asomers
Reviewed by:	cem, jilles, brooks (they reviewed previous version)
Discussed with:	pjd, and many others
Differential Revision:	https://reviews.freebsd.org/D14567
This commit is contained in:
Mariusz Zaborski 2019-04-06 09:34:26 +00:00
parent 44b0f624ae
commit a1304030b8
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=345982
15 changed files with 170 additions and 41 deletions

View file

@ -585,6 +585,7 @@ off_t __syscall(quad_t, ...);
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

View file

@ -485,6 +485,7 @@ MLINKS+=timer_settime.2 timer_getoverrun.2 \
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 \

View file

@ -406,6 +406,7 @@ FBSD_1.6 {
fhlinkat;
fhreadlink;
getfhat;
funlinkat;
};
FBSDprivate_1.0 {

View file

@ -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 @@ except in the case where
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 @@ or
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 @@ or the relative
.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 @@ The
.Fn unlinkat
system call appeared in
.Fx 8.0 .
The
.Fn funlinkat
system call appeared in
.Fx 13.0 .
.Pp
The
.Fn unlink

View file

@ -278,7 +278,8 @@ vn_remove(char *fnamep, enum uio_seg seg, enum rm dirflag)
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 */

View file

@ -752,9 +752,11 @@ cloudabi_sys_file_unlink(struct thread *td,
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);
}

View file

@ -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

View file

@ -590,7 +590,7 @@ linux_unlink(struct thread *td, struct linux_unlink_args *args)
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 @@ linux_unlinkat(struct thread *td, struct linux_unlinkat_args *args)
#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 @@ linux_rmdir(struct thread *td, struct linux_rmdir_args *args)
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);
}

View file

@ -468,6 +468,7 @@ readlinkat
renameat
symlinkat
unlinkat
funlinkat
utimensat
##

View file

@ -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

View file

@ -389,7 +389,7 @@ vfs_mountroot_shuffle(struct thread *td, struct mount *mpdevfs)
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 "

View file

@ -1751,7 +1751,22 @@ int
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_
@ -1764,46 +1779,69 @@ struct unlinkat_args {
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 @@ kern_unlinkat(struct thread *td, int fd, const char *path,
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 @@ kern_unlinkat(struct thread *td, int fd, const char *path,
vrele(vp);
else
vput(vp);
fdout:
if (fp != NULL)
fdrop(fp, td);
return (error);
}
@ -3704,25 +3746,36 @@ int
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 @@ kern_rmdirat(struct thread *td, int fd, const char *path, enum uio_seg pathseg,
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 @@ kern_rmdirat(struct thread *td, int fd, const char *path, enum uio_seg pathseg,
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 @@ kern_rmdirat(struct thread *td, int fd, const char *path, enum uio_seg pathseg,
vrele(nd.ni_dvp);
else
vput(nd.ni_dvp);
fdout:
if (fp != NULL)
fdrop(fp, td);
return (error);
}

View file

@ -316,6 +316,16 @@ struct __oflock {
#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, ...);

View file

@ -218,7 +218,7 @@ int kern_recvit(struct thread *td, int s, struct msghdr *mp,
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_new(struct thread *td, struct thr_param *param);
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);

View file

@ -3431,14 +3431,15 @@ sysctl_ffs_fsck(SYSCTL_HANDLER_ARGS)
}
#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: