diff options
author | Jan Kara <jack@suse.cz> | 2008-10-20 17:50:38 -0400 |
---|---|---|
committer | Mark Fasheh <mfasheh@suse.com> | 2009-01-05 11:40:24 -0500 |
commit | 2205363dce7447b8e85f1ead14387664c1a98753 (patch) | |
tree | 729b2716f2e31bda2e035a11cc39aa5472dff2c4 /fs/ocfs2/quota_local.c | |
parent | 171bf93ce11f4c9929fdce6ce63df8da2f3c4475 (diff) |
ocfs2: Implement quota recovery
Implement functions for recovery after a crash. Functions just
read local quota file and sync info to global quota file.
Signed-off-by: Jan Kara <jack@suse.cz>
Signed-off-by: Mark Fasheh <mfasheh@suse.com>
Diffstat (limited to 'fs/ocfs2/quota_local.c')
-rw-r--r-- | fs/ocfs2/quota_local.c | 425 |
1 files changed, 417 insertions, 8 deletions
diff --git a/fs/ocfs2/quota_local.c b/fs/ocfs2/quota_local.c index 40e82b483136..b98562174cd0 100644 --- a/fs/ocfs2/quota_local.c +++ b/fs/ocfs2/quota_local.c | |||
@@ -49,14 +49,25 @@ static unsigned int ol_quota_chunk_block(struct super_block *sb, int c) | |||
49 | return 1 + (ol_chunk_blocks(sb) + 1) * c; | 49 | return 1 + (ol_chunk_blocks(sb) + 1) * c; |
50 | } | 50 | } |
51 | 51 | ||
52 | /* Offset of the dquot structure in the quota file */ | 52 | static unsigned int ol_dqblk_block(struct super_block *sb, int c, int off) |
53 | static loff_t ol_dqblk_off(struct super_block *sb, int c, int off) | 53 | { |
54 | int epb = ol_quota_entries_per_block(sb); | ||
55 | |||
56 | return ol_quota_chunk_block(sb, c) + 1 + off / epb; | ||
57 | } | ||
58 | |||
59 | static unsigned int ol_dqblk_block_off(struct super_block *sb, int c, int off) | ||
54 | { | 60 | { |
55 | int epb = ol_quota_entries_per_block(sb); | 61 | int epb = ol_quota_entries_per_block(sb); |
56 | 62 | ||
57 | return ((ol_quota_chunk_block(sb, c) + 1 + off / epb) | 63 | return (off % epb) * sizeof(struct ocfs2_local_disk_dqblk); |
58 | << sb->s_blocksize_bits) + | 64 | } |
59 | (off % epb) * sizeof(struct ocfs2_local_disk_dqblk); | 65 | |
66 | /* Offset of the dquot structure in the quota file */ | ||
67 | static loff_t ol_dqblk_off(struct super_block *sb, int c, int off) | ||
68 | { | ||
69 | return (ol_dqblk_block(sb, c, off) << sb->s_blocksize_bits) + | ||
70 | ol_dqblk_block_off(sb, c, off); | ||
60 | } | 71 | } |
61 | 72 | ||
62 | /* Compute block number from given offset */ | 73 | /* Compute block number from given offset */ |
@@ -253,6 +264,379 @@ static void olq_update_info(struct buffer_head *bh, void *private) | |||
253 | spin_unlock(&dq_data_lock); | 264 | spin_unlock(&dq_data_lock); |
254 | } | 265 | } |
255 | 266 | ||
267 | static int ocfs2_add_recovery_chunk(struct super_block *sb, | ||
268 | struct ocfs2_local_disk_chunk *dchunk, | ||
269 | int chunk, | ||
270 | struct list_head *head) | ||
271 | { | ||
272 | struct ocfs2_recovery_chunk *rc; | ||
273 | |||
274 | rc = kmalloc(sizeof(struct ocfs2_recovery_chunk), GFP_NOFS); | ||
275 | if (!rc) | ||
276 | return -ENOMEM; | ||
277 | rc->rc_chunk = chunk; | ||
278 | rc->rc_bitmap = kmalloc(sb->s_blocksize, GFP_NOFS); | ||
279 | if (!rc->rc_bitmap) { | ||
280 | kfree(rc); | ||
281 | return -ENOMEM; | ||
282 | } | ||
283 | memcpy(rc->rc_bitmap, dchunk->dqc_bitmap, | ||
284 | (ol_chunk_entries(sb) + 7) >> 3); | ||
285 | list_add_tail(&rc->rc_list, head); | ||
286 | return 0; | ||
287 | } | ||
288 | |||
289 | static void free_recovery_list(struct list_head *head) | ||
290 | { | ||
291 | struct ocfs2_recovery_chunk *next; | ||
292 | struct ocfs2_recovery_chunk *rchunk; | ||
293 | |||
294 | list_for_each_entry_safe(rchunk, next, head, rc_list) { | ||
295 | list_del(&rchunk->rc_list); | ||
296 | kfree(rchunk->rc_bitmap); | ||
297 | kfree(rchunk); | ||
298 | } | ||
299 | } | ||
300 | |||
301 | void ocfs2_free_quota_recovery(struct ocfs2_quota_recovery *rec) | ||
302 | { | ||
303 | int type; | ||
304 | |||
305 | for (type = 0; type < MAXQUOTAS; type++) | ||
306 | free_recovery_list(&(rec->r_list[type])); | ||
307 | kfree(rec); | ||
308 | } | ||
309 | |||
310 | /* Load entries in our quota file we have to recover*/ | ||
311 | static int ocfs2_recovery_load_quota(struct inode *lqinode, | ||
312 | struct ocfs2_local_disk_dqinfo *ldinfo, | ||
313 | int type, | ||
314 | struct list_head *head) | ||
315 | { | ||
316 | struct super_block *sb = lqinode->i_sb; | ||
317 | struct buffer_head *hbh; | ||
318 | struct ocfs2_local_disk_chunk *dchunk; | ||
319 | int i, chunks = le32_to_cpu(ldinfo->dqi_chunks); | ||
320 | int status = 0; | ||
321 | |||
322 | for (i = 0; i < chunks; i++) { | ||
323 | hbh = ocfs2_read_quota_block(lqinode, | ||
324 | ol_quota_chunk_block(sb, i), | ||
325 | &status); | ||
326 | if (!hbh) { | ||
327 | mlog_errno(status); | ||
328 | break; | ||
329 | } | ||
330 | dchunk = (struct ocfs2_local_disk_chunk *)hbh->b_data; | ||
331 | if (le32_to_cpu(dchunk->dqc_free) < ol_chunk_entries(sb)) | ||
332 | status = ocfs2_add_recovery_chunk(sb, dchunk, i, head); | ||
333 | brelse(hbh); | ||
334 | if (status < 0) | ||
335 | break; | ||
336 | } | ||
337 | if (status < 0) | ||
338 | free_recovery_list(head); | ||
339 | return status; | ||
340 | } | ||
341 | |||
342 | static struct ocfs2_quota_recovery *ocfs2_alloc_quota_recovery(void) | ||
343 | { | ||
344 | int type; | ||
345 | struct ocfs2_quota_recovery *rec; | ||
346 | |||
347 | rec = kmalloc(sizeof(struct ocfs2_quota_recovery), GFP_NOFS); | ||
348 | if (!rec) | ||
349 | return NULL; | ||
350 | for (type = 0; type < MAXQUOTAS; type++) | ||
351 | INIT_LIST_HEAD(&(rec->r_list[type])); | ||
352 | return rec; | ||
353 | } | ||
354 | |||
355 | /* Load information we need for quota recovery into memory */ | ||
356 | struct ocfs2_quota_recovery *ocfs2_begin_quota_recovery( | ||
357 | struct ocfs2_super *osb, | ||
358 | int slot_num) | ||
359 | { | ||
360 | unsigned int feature[MAXQUOTAS] = { OCFS2_FEATURE_RO_COMPAT_USRQUOTA, | ||
361 | OCFS2_FEATURE_RO_COMPAT_GRPQUOTA}; | ||
362 | unsigned int ino[MAXQUOTAS] = { LOCAL_USER_QUOTA_SYSTEM_INODE, | ||
363 | LOCAL_GROUP_QUOTA_SYSTEM_INODE }; | ||
364 | struct super_block *sb = osb->sb; | ||
365 | struct ocfs2_local_disk_dqinfo *ldinfo; | ||
366 | struct inode *lqinode; | ||
367 | struct buffer_head *bh; | ||
368 | int type; | ||
369 | int status = 0; | ||
370 | struct ocfs2_quota_recovery *rec; | ||
371 | |||
372 | mlog(ML_NOTICE, "Beginning quota recovery in slot %u\n", slot_num); | ||
373 | rec = ocfs2_alloc_quota_recovery(); | ||
374 | if (!rec) | ||
375 | return ERR_PTR(-ENOMEM); | ||
376 | /* First init... */ | ||
377 | |||
378 | for (type = 0; type < MAXQUOTAS; type++) { | ||
379 | if (!OCFS2_HAS_RO_COMPAT_FEATURE(sb, feature[type])) | ||
380 | continue; | ||
381 | /* At this point, journal of the slot is already replayed so | ||
382 | * we can trust metadata and data of the quota file */ | ||
383 | lqinode = ocfs2_get_system_file_inode(osb, ino[type], slot_num); | ||
384 | if (!lqinode) { | ||
385 | status = -ENOENT; | ||
386 | goto out; | ||
387 | } | ||
388 | status = ocfs2_inode_lock_full(lqinode, NULL, 1, | ||
389 | OCFS2_META_LOCK_RECOVERY); | ||
390 | if (status < 0) { | ||
391 | mlog_errno(status); | ||
392 | goto out_put; | ||
393 | } | ||
394 | /* Now read local header */ | ||
395 | bh = ocfs2_read_quota_block(lqinode, 0, &status); | ||
396 | if (!bh) { | ||
397 | mlog_errno(status); | ||
398 | mlog(ML_ERROR, "failed to read quota file info header " | ||
399 | "(slot=%d type=%d)\n", slot_num, type); | ||
400 | goto out_lock; | ||
401 | } | ||
402 | ldinfo = (struct ocfs2_local_disk_dqinfo *)(bh->b_data + | ||
403 | OCFS2_LOCAL_INFO_OFF); | ||
404 | status = ocfs2_recovery_load_quota(lqinode, ldinfo, type, | ||
405 | &rec->r_list[type]); | ||
406 | brelse(bh); | ||
407 | out_lock: | ||
408 | ocfs2_inode_unlock(lqinode, 1); | ||
409 | out_put: | ||
410 | iput(lqinode); | ||
411 | if (status < 0) | ||
412 | break; | ||
413 | } | ||
414 | out: | ||
415 | if (status < 0) { | ||
416 | ocfs2_free_quota_recovery(rec); | ||
417 | rec = ERR_PTR(status); | ||
418 | } | ||
419 | return rec; | ||
420 | } | ||
421 | |||
422 | /* Sync changes in local quota file into global quota file and | ||
423 | * reinitialize local quota file. | ||
424 | * The function expects local quota file to be already locked and | ||
425 | * dqonoff_mutex locked. */ | ||
426 | static int ocfs2_recover_local_quota_file(struct inode *lqinode, | ||
427 | int type, | ||
428 | struct ocfs2_quota_recovery *rec) | ||
429 | { | ||
430 | struct super_block *sb = lqinode->i_sb; | ||
431 | struct ocfs2_mem_dqinfo *oinfo = sb_dqinfo(sb, type)->dqi_priv; | ||
432 | struct ocfs2_local_disk_chunk *dchunk; | ||
433 | struct ocfs2_local_disk_dqblk *dqblk; | ||
434 | struct dquot *dquot; | ||
435 | handle_t *handle; | ||
436 | struct buffer_head *hbh = NULL, *qbh = NULL; | ||
437 | int status = 0; | ||
438 | int bit, chunk; | ||
439 | struct ocfs2_recovery_chunk *rchunk, *next; | ||
440 | qsize_t spacechange, inodechange; | ||
441 | |||
442 | mlog_entry("ino=%lu type=%u", (unsigned long)lqinode->i_ino, type); | ||
443 | |||
444 | status = ocfs2_lock_global_qf(oinfo, 1); | ||
445 | if (status < 0) | ||
446 | goto out; | ||
447 | |||
448 | list_for_each_entry_safe(rchunk, next, &(rec->r_list[type]), rc_list) { | ||
449 | chunk = rchunk->rc_chunk; | ||
450 | hbh = ocfs2_read_quota_block(lqinode, | ||
451 | ol_quota_chunk_block(sb, chunk), | ||
452 | &status); | ||
453 | if (!hbh) { | ||
454 | mlog_errno(status); | ||
455 | break; | ||
456 | } | ||
457 | dchunk = (struct ocfs2_local_disk_chunk *)hbh->b_data; | ||
458 | for_each_bit(bit, rchunk->rc_bitmap, ol_chunk_entries(sb)) { | ||
459 | qbh = ocfs2_read_quota_block(lqinode, | ||
460 | ol_dqblk_block(sb, chunk, bit), | ||
461 | &status); | ||
462 | if (!qbh) { | ||
463 | mlog_errno(status); | ||
464 | break; | ||
465 | } | ||
466 | dqblk = (struct ocfs2_local_disk_dqblk *)(qbh->b_data + | ||
467 | ol_dqblk_block_off(sb, chunk, bit)); | ||
468 | dquot = dqget(sb, le64_to_cpu(dqblk->dqb_id), type); | ||
469 | if (!dquot) { | ||
470 | status = -EIO; | ||
471 | mlog(ML_ERROR, "Failed to get quota structure " | ||
472 | "for id %u, type %d. Cannot finish quota " | ||
473 | "file recovery.\n", | ||
474 | (unsigned)le64_to_cpu(dqblk->dqb_id), | ||
475 | type); | ||
476 | goto out_put_bh; | ||
477 | } | ||
478 | handle = ocfs2_start_trans(OCFS2_SB(sb), | ||
479 | OCFS2_QSYNC_CREDITS); | ||
480 | if (IS_ERR(handle)) { | ||
481 | status = PTR_ERR(handle); | ||
482 | mlog_errno(status); | ||
483 | goto out_put_dquot; | ||
484 | } | ||
485 | mutex_lock(&sb_dqopt(sb)->dqio_mutex); | ||
486 | spin_lock(&dq_data_lock); | ||
487 | /* Add usage from quota entry into quota changes | ||
488 | * of our node. Auxiliary variables are important | ||
489 | * due to signedness */ | ||
490 | spacechange = le64_to_cpu(dqblk->dqb_spacemod); | ||
491 | inodechange = le64_to_cpu(dqblk->dqb_inodemod); | ||
492 | dquot->dq_dqb.dqb_curspace += spacechange; | ||
493 | dquot->dq_dqb.dqb_curinodes += inodechange; | ||
494 | spin_unlock(&dq_data_lock); | ||
495 | /* We want to drop reference held by the crashed | ||
496 | * node. Since we have our own reference we know | ||
497 | * global structure actually won't be freed. */ | ||
498 | status = ocfs2_global_release_dquot(dquot); | ||
499 | if (status < 0) { | ||
500 | mlog_errno(status); | ||
501 | goto out_commit; | ||
502 | } | ||
503 | /* Release local quota file entry */ | ||
504 | status = ocfs2_journal_access(handle, lqinode, | ||
505 | qbh, OCFS2_JOURNAL_ACCESS_WRITE); | ||
506 | if (status < 0) { | ||
507 | mlog_errno(status); | ||
508 | goto out_commit; | ||
509 | } | ||
510 | lock_buffer(qbh); | ||
511 | WARN_ON(!ocfs2_test_bit(bit, dchunk->dqc_bitmap)); | ||
512 | ocfs2_clear_bit(bit, dchunk->dqc_bitmap); | ||
513 | le32_add_cpu(&dchunk->dqc_free, 1); | ||
514 | unlock_buffer(qbh); | ||
515 | status = ocfs2_journal_dirty(handle, qbh); | ||
516 | if (status < 0) | ||
517 | mlog_errno(status); | ||
518 | out_commit: | ||
519 | mutex_unlock(&sb_dqopt(sb)->dqio_mutex); | ||
520 | ocfs2_commit_trans(OCFS2_SB(sb), handle); | ||
521 | out_put_dquot: | ||
522 | dqput(dquot); | ||
523 | out_put_bh: | ||
524 | brelse(qbh); | ||
525 | if (status < 0) | ||
526 | break; | ||
527 | } | ||
528 | brelse(hbh); | ||
529 | list_del(&rchunk->rc_list); | ||
530 | kfree(rchunk->rc_bitmap); | ||
531 | kfree(rchunk); | ||
532 | if (status < 0) | ||
533 | break; | ||
534 | } | ||
535 | ocfs2_unlock_global_qf(oinfo, 1); | ||
536 | out: | ||
537 | if (status < 0) | ||
538 | free_recovery_list(&(rec->r_list[type])); | ||
539 | mlog_exit(status); | ||
540 | return status; | ||
541 | } | ||
542 | |||
543 | /* Recover local quota files for given node different from us */ | ||
544 | int ocfs2_finish_quota_recovery(struct ocfs2_super *osb, | ||
545 | struct ocfs2_quota_recovery *rec, | ||
546 | int slot_num) | ||
547 | { | ||
548 | unsigned int ino[MAXQUOTAS] = { LOCAL_USER_QUOTA_SYSTEM_INODE, | ||
549 | LOCAL_GROUP_QUOTA_SYSTEM_INODE }; | ||
550 | struct super_block *sb = osb->sb; | ||
551 | struct ocfs2_local_disk_dqinfo *ldinfo; | ||
552 | struct buffer_head *bh; | ||
553 | handle_t *handle; | ||
554 | int type; | ||
555 | int status = 0; | ||
556 | struct inode *lqinode; | ||
557 | unsigned int flags; | ||
558 | |||
559 | mlog(ML_NOTICE, "Finishing quota recovery in slot %u\n", slot_num); | ||
560 | mutex_lock(&sb_dqopt(sb)->dqonoff_mutex); | ||
561 | for (type = 0; type < MAXQUOTAS; type++) { | ||
562 | if (list_empty(&(rec->r_list[type]))) | ||
563 | continue; | ||
564 | mlog(0, "Recovering quota in slot %d\n", slot_num); | ||
565 | lqinode = ocfs2_get_system_file_inode(osb, ino[type], slot_num); | ||
566 | if (!lqinode) { | ||
567 | status = -ENOENT; | ||
568 | goto out; | ||
569 | } | ||
570 | status = ocfs2_inode_lock_full(lqinode, NULL, 1, | ||
571 | OCFS2_META_LOCK_NOQUEUE); | ||
572 | /* Someone else is holding the lock? Then he must be | ||
573 | * doing the recovery. Just skip the file... */ | ||
574 | if (status == -EAGAIN) { | ||
575 | mlog(ML_NOTICE, "skipping quota recovery for slot %d " | ||
576 | "because quota file is locked.\n", slot_num); | ||
577 | status = 0; | ||
578 | goto out_put; | ||
579 | } else if (status < 0) { | ||
580 | mlog_errno(status); | ||
581 | goto out_put; | ||
582 | } | ||
583 | /* Now read local header */ | ||
584 | bh = ocfs2_read_quota_block(lqinode, 0, &status); | ||
585 | if (!bh) { | ||
586 | mlog_errno(status); | ||
587 | mlog(ML_ERROR, "failed to read quota file info header " | ||
588 | "(slot=%d type=%d)\n", slot_num, type); | ||
589 | goto out_lock; | ||
590 | } | ||
591 | ldinfo = (struct ocfs2_local_disk_dqinfo *)(bh->b_data + | ||
592 | OCFS2_LOCAL_INFO_OFF); | ||
593 | /* Is recovery still needed? */ | ||
594 | flags = le32_to_cpu(ldinfo->dqi_flags); | ||
595 | if (!(flags & OLQF_CLEAN)) | ||
596 | status = ocfs2_recover_local_quota_file(lqinode, | ||
597 | type, | ||
598 | rec); | ||
599 | /* We don't want to mark file as clean when it is actually | ||
600 | * active */ | ||
601 | if (slot_num == osb->slot_num) | ||
602 | goto out_bh; | ||
603 | /* Mark quota file as clean if we are recovering quota file of | ||
604 | * some other node. */ | ||
605 | handle = ocfs2_start_trans(osb, 1); | ||
606 | if (IS_ERR(handle)) { | ||
607 | status = PTR_ERR(handle); | ||
608 | mlog_errno(status); | ||
609 | goto out_bh; | ||
610 | } | ||
611 | status = ocfs2_journal_access(handle, lqinode, bh, | ||
612 | OCFS2_JOURNAL_ACCESS_WRITE); | ||
613 | if (status < 0) { | ||
614 | mlog_errno(status); | ||
615 | goto out_trans; | ||
616 | } | ||
617 | lock_buffer(bh); | ||
618 | ldinfo->dqi_flags = cpu_to_le32(flags | OLQF_CLEAN); | ||
619 | unlock_buffer(bh); | ||
620 | status = ocfs2_journal_dirty(handle, bh); | ||
621 | if (status < 0) | ||
622 | mlog_errno(status); | ||
623 | out_trans: | ||
624 | ocfs2_commit_trans(osb, handle); | ||
625 | out_bh: | ||
626 | brelse(bh); | ||
627 | out_lock: | ||
628 | ocfs2_inode_unlock(lqinode, 1); | ||
629 | out_put: | ||
630 | iput(lqinode); | ||
631 | if (status < 0) | ||
632 | break; | ||
633 | } | ||
634 | out: | ||
635 | mutex_unlock(&sb_dqopt(sb)->dqonoff_mutex); | ||
636 | kfree(rec); | ||
637 | return status; | ||
638 | } | ||
639 | |||
256 | /* Read information header from quota file */ | 640 | /* Read information header from quota file */ |
257 | static int ocfs2_local_read_info(struct super_block *sb, int type) | 641 | static int ocfs2_local_read_info(struct super_block *sb, int type) |
258 | { | 642 | { |
@@ -262,6 +646,7 @@ static int ocfs2_local_read_info(struct super_block *sb, int type) | |||
262 | struct inode *lqinode = sb_dqopt(sb)->files[type]; | 646 | struct inode *lqinode = sb_dqopt(sb)->files[type]; |
263 | int status; | 647 | int status; |
264 | struct buffer_head *bh = NULL; | 648 | struct buffer_head *bh = NULL; |
649 | struct ocfs2_quota_recovery *rec; | ||
265 | int locked = 0; | 650 | int locked = 0; |
266 | 651 | ||
267 | info->dqi_maxblimit = 0x7fffffffffffffffLL; | 652 | info->dqi_maxblimit = 0x7fffffffffffffffLL; |
@@ -275,6 +660,7 @@ static int ocfs2_local_read_info(struct super_block *sb, int type) | |||
275 | info->dqi_priv = oinfo; | 660 | info->dqi_priv = oinfo; |
276 | oinfo->dqi_type = type; | 661 | oinfo->dqi_type = type; |
277 | INIT_LIST_HEAD(&oinfo->dqi_chunk); | 662 | INIT_LIST_HEAD(&oinfo->dqi_chunk); |
663 | oinfo->dqi_rec = NULL; | ||
278 | oinfo->dqi_lqi_bh = NULL; | 664 | oinfo->dqi_lqi_bh = NULL; |
279 | oinfo->dqi_ibh = NULL; | 665 | oinfo->dqi_ibh = NULL; |
280 | 666 | ||
@@ -305,10 +691,27 @@ static int ocfs2_local_read_info(struct super_block *sb, int type) | |||
305 | oinfo->dqi_ibh = bh; | 691 | oinfo->dqi_ibh = bh; |
306 | 692 | ||
307 | /* We crashed when using local quota file? */ | 693 | /* We crashed when using local quota file? */ |
308 | if (!(info->dqi_flags & OLQF_CLEAN)) | 694 | if (!(info->dqi_flags & OLQF_CLEAN)) { |
309 | goto out_err; /* So far we just bail out. Later we should resync here */ | 695 | rec = OCFS2_SB(sb)->quota_rec; |
696 | if (!rec) { | ||
697 | rec = ocfs2_alloc_quota_recovery(); | ||
698 | if (!rec) { | ||
699 | status = -ENOMEM; | ||
700 | mlog_errno(status); | ||
701 | goto out_err; | ||
702 | } | ||
703 | OCFS2_SB(sb)->quota_rec = rec; | ||
704 | } | ||
310 | 705 | ||
311 | status = ocfs2_load_local_quota_bitmaps(sb_dqopt(sb)->files[type], | 706 | status = ocfs2_recovery_load_quota(lqinode, ldinfo, type, |
707 | &rec->r_list[type]); | ||
708 | if (status < 0) { | ||
709 | mlog_errno(status); | ||
710 | goto out_err; | ||
711 | } | ||
712 | } | ||
713 | |||
714 | status = ocfs2_load_local_quota_bitmaps(lqinode, | ||
312 | ldinfo, | 715 | ldinfo, |
313 | &oinfo->dqi_chunk); | 716 | &oinfo->dqi_chunk); |
314 | if (status < 0) { | 717 | if (status < 0) { |
@@ -394,6 +797,12 @@ static int ocfs2_local_free_info(struct super_block *sb, int type) | |||
394 | } | 797 | } |
395 | ocfs2_release_local_quota_bitmaps(&oinfo->dqi_chunk); | 798 | ocfs2_release_local_quota_bitmaps(&oinfo->dqi_chunk); |
396 | 799 | ||
800 | /* dqonoff_mutex protects us against racing with recovery thread... */ | ||
801 | if (oinfo->dqi_rec) { | ||
802 | ocfs2_free_quota_recovery(oinfo->dqi_rec); | ||
803 | mark_clean = 0; | ||
804 | } | ||
805 | |||
397 | if (!mark_clean) | 806 | if (!mark_clean) |
398 | goto out; | 807 | goto out; |
399 | 808 | ||