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:
parent
44b0f624ae
commit
a1304030b8
@ -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
|
||||
|
@ -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 \
|
||||
|
@ -406,6 +406,7 @@ FBSD_1.6 {
|
||||
fhlinkat;
|
||||
fhreadlink;
|
||||
getfhat;
|
||||
funlinkat;
|
||||
};
|
||||
|
||||
FBSDprivate_1.0 {
|
||||
|
@ -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
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -468,6 +468,7 @@ readlinkat
|
||||
renameat
|
||||
symlinkat
|
||||
unlinkat
|
||||
funlinkat
|
||||
utimensat
|
||||
|
||||
##
|
||||
|
@ -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
|
||||
|
@ -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 "
|
||||
|
@ -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 @@ restart:
|
||||
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 @@ out:
|
||||
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 @@ restart:
|
||||
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 @@ restart:
|
||||
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 @@ out:
|
||||
vrele(nd.ni_dvp);
|
||||
else
|
||||
vput(nd.ni_dvp);
|
||||
fdout:
|
||||
if (fp != NULL)
|
||||
fdrop(fp, td);
|
||||
return (error);
|
||||
}
|
||||
|
||||
|
@ -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, ...);
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user