VFS_QUOTACTL(9): allow implementation to indicate busy state changes

Instead of requiring all implementations of vfs_quotactl to unbusy
the mount for Q_QUOTAON and Q_QUOTAOFF, add an "mp_busy" in/out param
to VFS_QUOTACTL(9).  The implementation may then indicate to the caller
whether it needed to unbusy the mount.

Reviewed By:	kib, markj
Differential Revision: https://reviews.freebsd.org/D30218
This commit is contained in:
Jason A. Harmening 2021-05-11 08:54:58 -07:00
parent 811e645d28
commit 6d3e78ad6c
13 changed files with 135 additions and 53 deletions

View File

@ -28,7 +28,7 @@
.\" .\"
.\" $FreeBSD$ .\" $FreeBSD$
.\" .\"
.Dd December 17, 2020 .Dd May 29, 2021
.Dt VFS_QUOTACTL 9 .Dt VFS_QUOTACTL 9
.Os .Os
.Sh NAME .Sh NAME
@ -39,12 +39,38 @@
.In sys/mount.h .In sys/mount.h
.In sys/vnode.h .In sys/vnode.h
.Ft int .Ft int
.Fn VFS_QUOTACTL "struct mount *mp" "int cmds" "uid_t uid" "void *arg" .Fn VFS_QUOTACTL "struct mount *mp" "int cmds" "uid_t uid" "void *arg" "bool *mp_busy"
.Sh DESCRIPTION .Sh DESCRIPTION
Implement file system quotas. Implement file system quotas.
.Pp
The
.Fa mp_busy
argument is an input/output parameter.
.Fn VFS_QUOTACTL
must be called with
.Fa mp
marked busy through
.Xr vfs_busy 9
and
.Fa *mp_busy
set to true.
The filesystem implementation of
.Fn VFS_QUOTACTL
may then unbusy
.Fa mp
using
.Xr vfs_unbusy 9
prior to performing quota file I/O.
In this case the implementation must set
.Fa *mp_busy
to false to indicate that the caller must not unbusy
.Fa mp
upon completion of
.Fn VFS_QUOTACTL .
.Pp
See See
.Xr quotactl 2 .Xr quotactl 2
for a description of the arguments. for a description of the remaining arguments.
.Sh SEE ALSO .Sh SEE ALSO
.Xr quotactl 2 , .Xr quotactl 2 ,
.Xr vnode 9 .Xr vnode 9

View File

@ -102,7 +102,12 @@ SYSCTL_INT(_vfs_zfs_version, OID_AUTO, zpl, CTLFLAG_RD, &zfs_version_zpl, 0,
"ZPL_VERSION"); "ZPL_VERSION");
/* END CSTYLED */ /* END CSTYLED */
#if __FreeBSD_version >= 1400018
static int zfs_quotactl(vfs_t *vfsp, int cmds, uid_t id, void *arg,
bool *mp_busy);
#else
static int zfs_quotactl(vfs_t *vfsp, int cmds, uid_t id, void *arg); static int zfs_quotactl(vfs_t *vfsp, int cmds, uid_t id, void *arg);
#endif
static int zfs_mount(vfs_t *vfsp); static int zfs_mount(vfs_t *vfsp);
static int zfs_umount(vfs_t *vfsp, int fflag); static int zfs_umount(vfs_t *vfsp, int fflag);
static int zfs_root(vfs_t *vfsp, int flags, vnode_t **vpp); static int zfs_root(vfs_t *vfsp, int flags, vnode_t **vpp);
@ -267,7 +272,11 @@ done:
} }
static int static int
#if __FreeBSD_version >= 1400018
zfs_quotactl(vfs_t *vfsp, int cmds, uid_t id, void *arg, bool *mp_busy)
#else
zfs_quotactl(vfs_t *vfsp, int cmds, uid_t id, void *arg) zfs_quotactl(vfs_t *vfsp, int cmds, uid_t id, void *arg)
#endif
{ {
zfsvfs_t *zfsvfs = vfsp->vfs_data; zfsvfs_t *zfsvfs = vfsp->vfs_data;
struct thread *td; struct thread *td;
@ -291,8 +300,10 @@ zfs_quotactl(vfs_t *vfsp, int cmds, uid_t id, void *arg)
break; break;
default: default:
error = EINVAL; error = EINVAL;
#if __FreeBSD_version < 1400018
if (cmd == Q_QUOTAON || cmd == Q_QUOTAOFF) if (cmd == Q_QUOTAON || cmd == Q_QUOTAOFF)
vfs_unbusy(vfsp); vfs_unbusy(vfsp);
#endif
goto done; goto done;
} }
} }
@ -351,11 +362,15 @@ zfs_quotactl(vfs_t *vfsp, int cmds, uid_t id, void *arg)
case Q_QUOTAON: case Q_QUOTAON:
// As far as I can tell, you can't turn quotas on or off on zfs // As far as I can tell, you can't turn quotas on or off on zfs
error = 0; error = 0;
#if __FreeBSD_version < 1400018
vfs_unbusy(vfsp); vfs_unbusy(vfsp);
#endif
break; break;
case Q_QUOTAOFF: case Q_QUOTAOFF:
error = ENOTSUP; error = ENOTSUP;
#if __FreeBSD_version < 1400018
vfs_unbusy(vfsp); vfs_unbusy(vfsp);
#endif
break; break;
case Q_SETQUOTA: case Q_SETQUOTA:
error = copyin(arg, &dqblk, sizeof (dqblk)); error = copyin(arg, &dqblk, sizeof (dqblk));

View File

@ -294,13 +294,39 @@ nullfs_root(mp, flags, vpp)
} }
static int static int
nullfs_quotactl(mp, cmd, uid, arg) nullfs_quotactl(mp, cmd, uid, arg, mp_busy)
struct mount *mp; struct mount *mp;
int cmd; int cmd;
uid_t uid; uid_t uid;
void *arg; void *arg;
bool *mp_busy;
{ {
return VFS_QUOTACTL(MOUNTTONULLMOUNT(mp)->nullm_vfs, cmd, uid, arg); struct mount *lowermp;
struct null_mount *mntdata;
int error;
bool unbusy;
mntdata = MOUNTTONULLMOUNT(mp);
lowermp = atomic_load_ptr(&mntdata->nullm_vfs);
KASSERT(*mp_busy == true, ("upper mount not busy"));
/*
* See comment in sys_quotactl() for an explanation of why the
* lower mount needs to be busied by the caller of VFS_QUOTACTL()
* but may be unbusied by the implementation. We must unbusy
* the upper mount for the same reason; otherwise a namei lookup
* issued by the VFS_QUOTACTL() implementation could traverse the
* upper mount and deadlock.
*/
vfs_unbusy(mp);
*mp_busy = false;
unbusy = true;
error = vfs_busy(lowermp, 0);
if (error == 0)
error = VFS_QUOTACTL(lowermp, cmd, uid, arg, &unbusy);
if (unbusy)
vfs_unbusy(lowermp);
return (error);
} }
static int static int

View File

@ -352,11 +352,12 @@ out:
*/ */
/* ARGSUSED */ /* ARGSUSED */
static int static int
smbfs_quotactl(mp, cmd, uid, arg) smbfs_quotactl(mp, cmd, uid, arg, mp_busy)
struct mount *mp; struct mount *mp;
int cmd; int cmd;
uid_t uid; uid_t uid;
void *arg; void *arg;
bool *mp_busy;
{ {
SMBVDEBUG("return EOPNOTSUPP\n"); SMBVDEBUG("return EOPNOTSUPP\n");
return EOPNOTSUPP; return EOPNOTSUPP;

View File

@ -371,16 +371,38 @@ unionfs_root(struct mount *mp, int flags, struct vnode **vpp)
} }
static int static int
unionfs_quotactl(struct mount *mp, int cmd, uid_t uid, void *arg) unionfs_quotactl(struct mount *mp, int cmd, uid_t uid, void *arg,
bool *mp_busy)
{ {
struct mount *uppermp;
struct unionfs_mount *ump; struct unionfs_mount *ump;
int error;
bool unbusy;
ump = MOUNTTOUNIONFSMOUNT(mp); ump = MOUNTTOUNIONFSMOUNT(mp);
uppermp = atomic_load_ptr(&ump->um_uppervp->v_mount);
KASSERT(*mp_busy == true, ("upper mount not busy"));
/*
* See comment in sys_quotactl() for an explanation of why the
* lower mount needs to be busied by the caller of VFS_QUOTACTL()
* but may be unbusied by the implementation. We must unbusy
* the upper mount for the same reason; otherwise a namei lookup
* issued by the VFS_QUOTACTL() implementation could traverse the
* upper mount and deadlock.
*/
vfs_unbusy(mp);
*mp_busy = false;
unbusy = true;
error = vfs_busy(uppermp, 0);
/* /*
* Writing is always performed to upper vnode. * Writing is always performed to upper vnode.
*/ */
return (VFS_QUOTACTL(ump->um_uppervp->v_mount, cmd, uid, arg)); if (error == 0)
error = VFS_QUOTACTL(uppermp, cmd, uid, arg, &unbusy);
if (unbusy)
vfs_unbusy(uppermp);
return (error);
} }
static int static int

View File

@ -1350,13 +1350,13 @@ vfs_stdstatfs (mp, sbp)
} }
int int
vfs_stdquotactl (mp, cmds, uid, arg) vfs_stdquotactl (mp, cmds, uid, arg, mp_busy)
struct mount *mp; struct mount *mp;
int cmds; int cmds;
uid_t uid; uid_t uid;
void *arg; void *arg;
bool *mp_busy;
{ {
return (EOPNOTSUPP); return (EOPNOTSUPP);
} }

View File

@ -212,12 +212,14 @@ vfs_cachedroot_sigdefer(struct mount *mp, int flags, struct vnode **vpp)
} }
static int static int
vfs_quotactl_sigdefer(struct mount *mp, int cmd, uid_t uid, void *arg) vfs_quotactl_sigdefer(struct mount *mp, int cmd, uid_t uid, void *arg,
bool *mp_busy)
{ {
int prev_stops, rc; int prev_stops, rc;
prev_stops = sigdeferstop(SIGDEFERSTOP_SILENT); prev_stops = sigdeferstop(SIGDEFERSTOP_SILENT);
rc = (*mp->mnt_vfc->vfc_vfsops_sd->vfs_quotactl)(mp, cmd, uid, arg); rc = (*mp->mnt_vfc->vfc_vfsops_sd->vfs_quotactl)(mp, cmd, uid, arg,
mp_busy);
sigallowstop(prev_stops); sigallowstop(prev_stops);
return (rc); return (rc);
} }

View File

@ -89,8 +89,6 @@ __FBSDID("$FreeBSD$");
#include <fs/devfs/devfs.h> #include <fs/devfs/devfs.h>
#include <ufs/ufs/quota.h>
MALLOC_DEFINE(M_FADVISE, "fadvise", "posix_fadvise(2) information"); MALLOC_DEFINE(M_FADVISE, "fadvise", "posix_fadvise(2) information");
static int kern_chflagsat(struct thread *td, int fd, const char *path, static int kern_chflagsat(struct thread *td, int fd, const char *path,
@ -195,6 +193,7 @@ sys_quotactl(struct thread *td, struct quotactl_args *uap)
struct mount *mp; struct mount *mp;
struct nameidata nd; struct nameidata nd;
int error; int error;
bool mp_busy;
AUDIT_ARG_CMD(uap->cmd); AUDIT_ARG_CMD(uap->cmd);
AUDIT_ARG_UID(uap->uid); AUDIT_ARG_UID(uap->uid);
@ -213,21 +212,21 @@ sys_quotactl(struct thread *td, struct quotactl_args *uap)
vfs_rel(mp); vfs_rel(mp);
return (error); return (error);
} }
error = VFS_QUOTACTL(mp, uap->cmd, uap->uid, uap->arg); mp_busy = true;
error = VFS_QUOTACTL(mp, uap->cmd, uap->uid, uap->arg, &mp_busy);
/* /*
* Since quota on operation typically needs to open quota * Since quota on/off operations typically need to open quota
* file, the Q_QUOTAON handler needs to unbusy the mount point * files, the implementation may need to unbusy the mount point
* before calling into namei. Otherwise, unmount might be * before calling into namei. Otherwise, unmount might be
* started between two vfs_busy() invocations (first is our, * started between two vfs_busy() invocations (first is ours,
* second is from mount point cross-walk code in lookup()), * second is from mount point cross-walk code in lookup()),
* causing deadlock. * causing deadlock.
* *
* Require that Q_QUOTAON handles the vfs_busy() reference on * Avoid unbusying mp if the implementation indicates it has
* its own, always returning with ubusied mount point. * already done so.
*/ */
if ((uap->cmd >> SUBCMDSHIFT) != Q_QUOTAON && if (mp_busy)
(uap->cmd >> SUBCMDSHIFT) != Q_QUOTAOFF)
vfs_unbusy(mp); vfs_unbusy(mp);
vfs_rel(mp); vfs_rel(mp);
return (error); return (error);

View File

@ -759,7 +759,8 @@ struct mntarg;
typedef int vfs_cmount_t(struct mntarg *ma, void *data, uint64_t flags); typedef int vfs_cmount_t(struct mntarg *ma, void *data, uint64_t flags);
typedef int vfs_unmount_t(struct mount *mp, int mntflags); typedef int vfs_unmount_t(struct mount *mp, int mntflags);
typedef int vfs_root_t(struct mount *mp, int flags, struct vnode **vpp); typedef int vfs_root_t(struct mount *mp, int flags, struct vnode **vpp);
typedef int vfs_quotactl_t(struct mount *mp, int cmds, uid_t uid, void *arg); typedef int vfs_quotactl_t(struct mount *mp, int cmds, uid_t uid, void *arg,
bool *mp_busy);
typedef int vfs_statfs_t(struct mount *mp, struct statfs *sbp); typedef int vfs_statfs_t(struct mount *mp, struct statfs *sbp);
typedef int vfs_sync_t(struct mount *mp, int waitfor); typedef int vfs_sync_t(struct mount *mp, int waitfor);
typedef int vfs_vget_t(struct mount *mp, ino_t ino, int flags, typedef int vfs_vget_t(struct mount *mp, ino_t ino, int flags,
@ -832,10 +833,10 @@ vfs_statfs_t __vfs_statfs;
_rc = (*(MP)->mnt_op->vfs_cachedroot)(MP, FLAGS, VPP); \ _rc = (*(MP)->mnt_op->vfs_cachedroot)(MP, FLAGS, VPP); \
_rc; }) _rc; })
#define VFS_QUOTACTL(MP, C, U, A) ({ \ #define VFS_QUOTACTL(MP, C, U, A, MP_BUSY) ({ \
int _rc; \ int _rc; \
\ \
_rc = (*(MP)->mnt_op->vfs_quotactl)(MP, C, U, A); \ _rc = (*(MP)->mnt_op->vfs_quotactl)(MP, C, U, A, MP_BUSY); \
_rc; }) _rc; })
#define VFS_STATFS(MP, SBP) ({ \ #define VFS_STATFS(MP, SBP) ({ \

View File

@ -76,7 +76,7 @@
* cannot include sys/param.h and should only be updated here. * cannot include sys/param.h and should only be updated here.
*/ */
#undef __FreeBSD_version #undef __FreeBSD_version
#define __FreeBSD_version 1400017 #define __FreeBSD_version 1400018
/* /*
* __FreeBSD_kernel__ indicates that this system uses the kernel of FreeBSD, * __FreeBSD_kernel__ indicates that this system uses the kernel of FreeBSD,

View File

@ -232,7 +232,7 @@ int getinoquota(struct inode *);
int qsync(struct mount *); int qsync(struct mount *);
int qsyncvp(struct vnode *); int qsyncvp(struct vnode *);
int quotaoff(struct thread *, struct mount *, int); int quotaoff(struct thread *, struct mount *, int);
int quotaon(struct thread *, struct mount *, int, void *); int quotaon(struct thread *, struct mount *, int, void *, bool *);
int getquota32(struct thread *, struct mount *, u_long, int, void *); int getquota32(struct thread *, struct mount *, u_long, int, void *);
int setquota32(struct thread *, struct mount *, u_long, int, void *); int setquota32(struct thread *, struct mount *, u_long, int, void *);
int setuse32(struct thread *, struct mount *, u_long, int, void *); int setuse32(struct thread *, struct mount *, u_long, int, void *);

View File

@ -492,7 +492,8 @@ chkdquot(struct inode *ip)
* Q_QUOTAON - set up a quota file for a particular filesystem. * Q_QUOTAON - set up a quota file for a particular filesystem.
*/ */
int int
quotaon(struct thread *td, struct mount *mp, int type, void *fname) quotaon(struct thread *td, struct mount *mp, int type, void *fname,
bool *mp_busy)
{ {
struct ufsmount *ump; struct ufsmount *ump;
struct vnode *vp, **vpp; struct vnode *vp, **vpp;
@ -502,15 +503,11 @@ quotaon(struct thread *td, struct mount *mp, int type, void *fname)
struct nameidata nd; struct nameidata nd;
error = priv_check(td, PRIV_UFS_QUOTAON); error = priv_check(td, PRIV_UFS_QUOTAON);
if (error != 0) { if (error != 0)
vfs_unbusy(mp);
return (error); return (error);
}
if ((mp->mnt_flag & MNT_RDONLY) != 0) { if ((mp->mnt_flag & MNT_RDONLY) != 0)
vfs_unbusy(mp);
return (EROFS); return (EROFS);
}
ump = VFSTOUFS(mp); ump = VFSTOUFS(mp);
dq = NODQUOT; dq = NODQUOT;
@ -518,7 +515,9 @@ quotaon(struct thread *td, struct mount *mp, int type, void *fname)
NDINIT(&nd, LOOKUP, FOLLOW, UIO_USERSPACE, fname, td); NDINIT(&nd, LOOKUP, FOLLOW, UIO_USERSPACE, fname, td);
flags = FREAD | FWRITE; flags = FREAD | FWRITE;
vfs_ref(mp); vfs_ref(mp);
KASSERT(*mp_busy, ("%s called without busied mount", __func__));
vfs_unbusy(mp); vfs_unbusy(mp);
*mp_busy = false;
error = vn_open(&nd, &flags, 0, NULL); error = vn_open(&nd, &flags, 0, NULL);
if (error != 0) { if (error != 0) {
vfs_rel(mp); vfs_rel(mp);
@ -529,10 +528,9 @@ quotaon(struct thread *td, struct mount *mp, int type, void *fname)
error = vfs_busy(mp, MBF_NOWAIT); error = vfs_busy(mp, MBF_NOWAIT);
vfs_rel(mp); vfs_rel(mp);
if (error == 0) { if (error == 0) {
if (vp->v_type != VREG) { *mp_busy = true;
if (vp->v_type != VREG)
error = EACCES; error = EACCES;
vfs_unbusy(mp);
}
} }
if (error != 0) { if (error != 0) {
VOP_UNLOCK(vp); VOP_UNLOCK(vp);
@ -545,7 +543,6 @@ quotaon(struct thread *td, struct mount *mp, int type, void *fname)
UFS_UNLOCK(ump); UFS_UNLOCK(ump);
VOP_UNLOCK(vp); VOP_UNLOCK(vp);
(void) vn_close(vp, FREAD|FWRITE, td->td_ucred, td); (void) vn_close(vp, FREAD|FWRITE, td->td_ucred, td);
vfs_unbusy(mp);
return (EALREADY); return (EALREADY);
} }
ump->um_qflags[type] |= QTF_OPENING|QTF_CLOSING; ump->um_qflags[type] |= QTF_OPENING|QTF_CLOSING;
@ -556,7 +553,6 @@ quotaon(struct thread *td, struct mount *mp, int type, void *fname)
ump->um_qflags[type] &= ~(QTF_OPENING|QTF_CLOSING); ump->um_qflags[type] &= ~(QTF_OPENING|QTF_CLOSING);
UFS_UNLOCK(ump); UFS_UNLOCK(ump);
(void) vn_close(vp, FREAD|FWRITE, td->td_ucred, td); (void) vn_close(vp, FREAD|FWRITE, td->td_ucred, td);
vfs_unbusy(mp);
return (error); return (error);
} }
VOP_UNLOCK(vp); VOP_UNLOCK(vp);
@ -640,7 +636,6 @@ again:
("quotaon: leaking flags")); ("quotaon: leaking flags"));
UFS_UNLOCK(ump); UFS_UNLOCK(ump);
vfs_unbusy(mp);
return (error); return (error);
} }

View File

@ -87,17 +87,14 @@ ufs_root(mp, flags, vpp)
* Do operations associated with quotas * Do operations associated with quotas
*/ */
int int
ufs_quotactl(mp, cmds, id, arg) ufs_quotactl(mp, cmds, id, arg, mp_busy)
struct mount *mp; struct mount *mp;
int cmds; int cmds;
uid_t id; uid_t id;
void *arg; void *arg;
bool *mp_busy;
{ {
#ifndef QUOTA #ifndef QUOTA
if ((cmds >> SUBCMDSHIFT) == Q_QUOTAON ||
(cmds >> SUBCMDSHIFT) == Q_QUOTAOFF)
vfs_unbusy(mp);
return (EOPNOTSUPP); return (EOPNOTSUPP);
#else #else
struct thread *td; struct thread *td;
@ -117,25 +114,23 @@ ufs_quotactl(mp, cmds, id, arg)
break; break;
default: default:
if (cmd == Q_QUOTAON || cmd == Q_QUOTAOFF)
vfs_unbusy(mp);
return (EINVAL); return (EINVAL);
} }
} }
if ((u_int)type >= MAXQUOTAS) { if ((u_int)type >= MAXQUOTAS)
if (cmd == Q_QUOTAON || cmd == Q_QUOTAOFF)
vfs_unbusy(mp);
return (EINVAL); return (EINVAL);
}
switch (cmd) { switch (cmd) {
case Q_QUOTAON: case Q_QUOTAON:
error = quotaon(td, mp, type, arg); error = quotaon(td, mp, type, arg, mp_busy);
break; break;
case Q_QUOTAOFF: case Q_QUOTAOFF:
vfs_ref(mp); vfs_ref(mp);
KASSERT(*mp_busy,
("%s called without busied mount", __func__));
vfs_unbusy(mp); vfs_unbusy(mp);
*mp_busy = false;
vn_start_write(NULL, &mp, V_WAIT | V_MNTREF); vn_start_write(NULL, &mp, V_WAIT | V_MNTREF);
error = quotaoff(td, mp, type); error = quotaoff(td, mp, type);
vn_finished_write(mp); vn_finished_write(mp);