The kernel can deadlock if it tries to allocate blocks for a quota data
file that is stored in the same filesystem that has the quotas enabled.
This happens because of recursion within the quota subsystem:

sleep_finish(ffff8000225d16c0,1) at sleep_finish+0xfe
tsleep(ffff8000007f0880,9,ffffffff81f8b8d8,0) at tsleep+0xb2
# waits for the struct dquot lock that was acquired earlier
ufs_quota_alloc_blocks2(fffffd82631dbbc0,4,fffffd843f7e3c60,0) at 
ufs_quota_alloc_blocks2+0x149
ffs_alloc(fffffd82631dbbc0,0,ce5,800,fffffd843f7e3c60,ffff8000225d1940) at 
ffs_alloc+0x114
ffs2_balloc(fffffd82631dbbc0,0,20,fffffd843f7e3c60,1,ffff8000225d1a28) at 
ffs2_balloc+0x50d
ffs_write(ffff8000225d1aa8) at ffs_write+0x229
VOP_WRITE(fffffd83357662f8,ffff8000225d1b18,0,fffffd843f7e3c60) at 
VOP_WRITE+0x6a
# has acquired struct dquot lock
dqsync(fffffd83357662f8,ffff8000007f0880) at dqsync+0x133
qsync_vnode(fffffd83357662f8,0) at qsync_vnode+0x68
vfs_mount_foreach_vnode(ffff8000006ae000,ffffffff81a92120,0) at 
vfs_mount_foreach_vnode+0x4a
qsync(ffff8000006ae000) at qsync+0x37
ffs_sync(ffff8000006ae000,3,0,fffffd843f7e3ea0,ffff80002259b508) at 
ffs_sync+0x114
sync_fsync(ffff8000225d1d60) at sync_fsync+0x144
VOP_FSYNC(fffffd8406af5bc8,fffffd843f7e3ea0,3,ffff80002259b508) at 
VOP_FSYNC+0x83
syncer_thread(ffff80002259b508) at syncer_thread+0x1b2

One way to fix this is to exclude quota data files from the accounting,
as shown in the following patch. The quota files are root's property
and special to the system, so the omission should not be a problem
in general.

A slight variation of the patch is to skip only the files that are used
to store quotas in the same filesystem. However, I think the categorical
skipping is clearer.

The patch makes quotacheck(8) ignore the quota data files in the
same-filesystem case. Note that there is no adjustment for externally
stored quota files. External storage probably does not use quotas.

Also, the VSYSTEM flag should be cleared when quotas are disabled.

(One might still invoke curious patterns by setting up a filesystem
with quotas on a vnd device that is backed by a sparse file on
a quota-enabled filesystem. However, I think this is a slightly
different thing that relates to the subsystem's shared data structures.)

OK?

Index: sbin/quotacheck/quotacheck.c
===================================================================
RCS file: src/sbin/quotacheck/quotacheck.c,v
retrieving revision 1.41
diff -u -p -r1.41 quotacheck.c
--- sbin/quotacheck/quotacheck.c        28 Jun 2019 13:32:45 -0000      1.41
+++ sbin/quotacheck/quotacheck.c        25 Jun 2022 10:46:22 -0000
@@ -263,10 +263,13 @@ int
 chkquota(const char *vfstype, const char *fsname, const char *mntpt,
     void *auxarg, pid_t *pidp)
 {
+       struct stat sb;
        struct quotaname *qnp = auxarg;
        struct fileusage *fup;
        union dinode *dp;
        int cg, i, mode, errs = 0, status;
+       dev_t groupdev = 0, userdev = 0, dev = 0;
+       ino_t groupino = 0, userino = 0;
        ino_t ino, inosused;
        pid_t pid;
        char *cp;
@@ -276,8 +279,20 @@ chkquota(const char *vfstype, const char
                warn("fork");
                return 1;
        case 0:         /* child */
+               if ((qnp->flags & HASGRP) != 0 &&
+                   stat(qnp->grpqfname, &sb) == 0) {
+                       groupdev = sb.st_dev;
+                       groupino = sb.st_ino;
+               }
+               if ((qnp->flags & HASUSR) != 0 &&
+                   stat(qnp->usrqfname, &sb) == 0) {
+                       userdev = sb.st_dev;
+                       userino = sb.st_ino;
+               }
                if ((fi = opendev(fsname, O_RDONLY, 0, NULL)) == -1)
                        err(1, "%s", fsname);
+               if (stat(mntpt, &sb) == 0)
+                       dev = sb.st_dev;
                sync();
                for (i = 0; sblock_try[i] != -1; i++) {
                        bread(sblock_try[i] / DEV_BSIZE, (char *)&sblock,
@@ -332,6 +347,13 @@ chkquota(const char *vfstype, const char
                                    ino < ROOTINO ||
                                    (mode = DIP(dp, di_mode) & IFMT) == 0)
                                        continue;
+                               /*
+                                * Skip the quota data files because
+                                * the kernel excludes them from accounting.
+                                */
+                               if ((ino == groupino && dev == groupdev) ||
+                                   (ino == userino && dev == userdev))
+                                       continue;
                                if (qnp->flags & HASGRP) {
                                        fup = addid(DIP(dp, di_gid),
                                            GRPQUOTA, NULL);
Index: sys/ufs/ufs/ufs_quota.c
===================================================================
RCS file: src/sys/ufs/ufs/ufs_quota.c,v
retrieving revision 1.47
diff -u -p -r1.47 ufs_quota.c
--- sys/ufs/ufs/ufs_quota.c     24 Jun 2020 22:03:45 -0000      1.47
+++ sys/ufs/ufs/ufs_quota.c     25 Jun 2022 10:46:22 -0000
@@ -156,6 +156,13 @@ getinoquota(struct inode *ip)
 
        ump = ip->i_ump;
        /*
+        * Do not apply quotas to quota files.
+        * The system will deadlock if the quota files are
+        * on the same filesystem that has quotas enabled.
+        */
+       if (vp->v_flag & VSYSTEM)
+               return (0);
+       /*
         * Set up the user quota based on file uid.
         * EINVAL means that quotas are not enabled.
         */
@@ -439,7 +446,15 @@ chkdquot(struct inode *ip)
 
        if (!VOP_ISLOCKED(vp)) 
                panic ("chkdquot: vnode is not locked");
-               
+
+       /*
+        * Do not apply quotas to quota files.
+        * The system will deadlock if the quota files are
+        * on the same filesystem that has quotas enabled.
+        */
+       if (vp->v_flag & VSYSTEM)
+               return;
+
        for (i = 0; i < MAXQUOTAS; i++) {
                if (ump->um_quotas[i] == NULLVP ||
                    (ump->um_qflags[i] & (QTF_OPENING|QTF_CLOSING)))
@@ -600,6 +615,7 @@ quotaoff(struct proc *p, struct mount *m
        qa.type = type;
        vfs_mount_foreach_vnode(mp, quotaoff_vnode, &qa);
 
+       qvp->v_flag &= ~VSYSTEM;
        error = vn_close(qvp, FREAD|FWRITE, p->p_ucred, p);
        ump->um_quotas[type] = NULLVP;
        crfree(ump->um_cred[type]);

Reply via email to