The softdep freeblks workitem might hold a reference on the dquot.

Current dqflush() panics when a dquot with with non-zero refcount is
encountered.  The situation is possible, because quotas are turned off
before softdep workitem queue if flushed, due to the quota file writes
might create softdep workitems.

Make the encountering an active dquot in dqflush() not fatal, return
the error from quotaoff() instead.  Ignore the quotaoff() failures
when ffs_flushfiles() is called in the course of softdep_flushfiles()
loop, until the last iteration.  At the last loop, the quotas must be
closed, and because SU workitems should be already flushed, the
references to dquot are gone.

Sponsored by:	The FreeBSD Foundation
Reported and tested by:	pho
Reviewed by:	mckusick
MFC after:	2 weeks
This commit is contained in:
Konstantin Belousov 2013-02-27 07:32:39 +00:00
parent 94f4ac214c
commit ba05dec5a4
4 changed files with 63 additions and 17 deletions

View File

@ -385,6 +385,7 @@ extern int vttoif_tab[];
#define SKIPSYSTEM 0x0001 /* vflush: skip vnodes marked VSYSTEM */
#define FORCECLOSE 0x0002 /* vflush: force file closure */
#define WRITECLOSE 0x0004 /* vflush: only close writable files */
#define EARLYFLUSH 0x0008 /* vflush: early call for ffs_flushfiles */
#define V_SAVE 0x0001 /* vinvalbuf: sync file first */
#define V_ALT 0x0002 /* vinvalbuf: invalidate only alternate bufs */
#define V_NORMAL 0x0004 /* vinvalbuf: invalidate only regular bufs */

View File

@ -1908,7 +1908,12 @@ softdep_flushfiles(oldmnt, flags, td)
int flags;
struct thread *td;
{
int error, depcount, loopcnt, retry_flush_count, retry;
#ifdef QUOTA
struct ufsmount *ump;
int i;
#endif
int error, early, depcount, loopcnt, retry_flush_count, retry;
int morework;
loopcnt = 10;
retry_flush_count = 3;
@ -1926,7 +1931,9 @@ softdep_flushfiles(oldmnt, flags, td)
* Do another flush in case any vnodes were brought in
* as part of the cleanup operations.
*/
if ((error = ffs_flushfiles(oldmnt, flags, td)) != 0)
early = retry_flush_count == 1 || (oldmnt->mnt_kern_flag &
MNTK_UNMOUNT) == 0 ? 0 : EARLYFLUSH;
if ((error = ffs_flushfiles(oldmnt, flags | early, td)) != 0)
break;
if ((error = softdep_flushworklist(oldmnt, &depcount, td)) != 0 ||
depcount == 0)
@ -1950,7 +1957,17 @@ softdep_flushfiles(oldmnt, flags, td)
MNT_ILOCK(oldmnt);
KASSERT((oldmnt->mnt_kern_flag & MNTK_NOINSMNTQ) != 0,
("softdep_flushfiles: !MNTK_NOINSMNTQ"));
if (oldmnt->mnt_nvnodelistsize > 0) {
morework = oldmnt->mnt_nvnodelistsize > 0;
#ifdef QUOTA
ump = VFSTOUFS(oldmnt);
UFS_LOCK(ump);
for (i = 0; i < MAXQUOTAS; i++) {
if (ump->um_quotas[i] != NULLVP)
morework = 1;
}
UFS_UNLOCK(ump);
#endif
if (morework) {
if (--retry_flush_count > 0) {
retry = 1;
loopcnt = 3;

View File

@ -1351,9 +1351,10 @@ ffs_flushfiles(mp, flags, td)
struct thread *td;
{
struct ufsmount *ump;
int error;
int qerror, error;
ump = VFSTOUFS(mp);
qerror = 0;
#ifdef QUOTA
if (mp->mnt_flag & MNT_QUOTA) {
int i;
@ -1361,11 +1362,19 @@ ffs_flushfiles(mp, flags, td)
if (error)
return (error);
for (i = 0; i < MAXQUOTAS; i++) {
quotaoff(td, mp, i);
error = quotaoff(td, mp, i);
if (error != 0) {
if ((flags & EARLYFLUSH) == 0)
return (error);
else
qerror = error;
}
}
/*
* Here we fall through to vflush again to ensure
* that we have gotten rid of all the system vnodes.
* Here we fall through to vflush again to ensure that
* we have gotten rid of all the system vnodes, unless
* quotas must not be closed.
*/
}
#endif
@ -1380,11 +1389,21 @@ ffs_flushfiles(mp, flags, td)
* that we have gotten rid of all the system vnodes.
*/
}
/*
* Flush all the files.
/*
* Do not close system files if quotas were not closed, to be
* able to sync the remaining dquots. The freeblks softupdate
* workitems might hold a reference on a dquot, preventing
* quotaoff() from completing. Next round of
* softdep_flushworklist() iteration should process the
* blockers, allowing the next run of quotaoff() to finally
* flush held dquots.
*
* Otherwise, flush all the files.
*/
if ((error = vflush(mp, 0, flags, td)) != 0)
if (qerror == 0 && (error = vflush(mp, 0, flags, td)) != 0)
return (error);
/*
* Flush filesystem metadata.
*/

View File

@ -80,7 +80,7 @@ static int dqopen(struct vnode *, struct ufsmount *, int);
static int dqget(struct vnode *,
u_long, struct ufsmount *, int, struct dquot **);
static int dqsync(struct vnode *, struct dquot *);
static void dqflush(struct vnode *);
static int dqflush(struct vnode *);
static int quotaoff1(struct thread *td, struct mount *mp, int type);
static int quotaoff_inchange(struct thread *td, struct mount *mp, int type);
@ -674,8 +674,12 @@ quotaoff1(struct thread *td, struct mount *mp, int type)
vrele(vp);
}
dqflush(qvp);
/* Clear um_quotas before closing the quota vnode to prevent
error = dqflush(qvp);
if (error != 0)
return (error);
/*
* Clear um_quotas before closing the quota vnode to prevent
* access to the closed vnode from dqget/dqsync
*/
UFS_LOCK(ump);
@ -1594,17 +1598,19 @@ dqsync(struct vnode *vp, struct dquot *dq)
/*
* Flush all entries from the cache for a particular vnode.
*/
static void
static int
dqflush(struct vnode *vp)
{
struct dquot *dq, *nextdq;
struct dqhash *dqh;
int error;
/*
* Move all dquot's that used to refer to this quota
* file off their hash chains (they will eventually
* fall off the head of the free list and be re-used).
*/
error = 0;
DQH_LOCK();
for (dqh = &dqhashtbl[dqhash]; dqh >= dqhashtbl; dqh--) {
for (dq = LIST_FIRST(dqh); dq; dq = nextdq) {
@ -1612,12 +1618,15 @@ dqflush(struct vnode *vp)
if (dq->dq_ump->um_quotas[dq->dq_type] != vp)
continue;
if (dq->dq_cnt)
panic("dqflush: stray dquot");
LIST_REMOVE(dq, dq_hash);
dq->dq_ump = (struct ufsmount *)0;
error = EBUSY;
else {
LIST_REMOVE(dq, dq_hash);
dq->dq_ump = NULL;
}
}
}
DQH_UNLOCK();
return (error);
}
/*