Pre-2.0 release, MFC 63 - HAMMER I/O error handling and catastrophic
authorMatthew Dillon <dillon@dragonflybsd.org>
Fri, 18 Jul 2008 00:21:09 +0000 (18 00:21 +0000)
committerMatthew Dillon <dillon@dragonflybsd.org>
Fri, 18 Jul 2008 00:21:09 +0000 (18 00:21 +0000)
umount support.

sys/vfs/hammer/hammer.h
sys/vfs/hammer/hammer_blockmap.c
sys/vfs/hammer/hammer_flusher.c
sys/vfs/hammer/hammer_inode.c
sys/vfs/hammer/hammer_io.c
sys/vfs/hammer/hammer_object.c
sys/vfs/hammer/hammer_ondisk.c
sys/vfs/hammer/hammer_undo.c
sys/vfs/hammer/hammer_vfsops.c

index b5560a5..45e1d4c 100644 (file)
@@ -31,7 +31,7 @@
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  * 
- * $DragonFly: src/sys/vfs/hammer/hammer.h,v 1.117.2.2 2008/07/16 18:39:31 dillon Exp $
+ * $DragonFly: src/sys/vfs/hammer/hammer.h,v 1.117.2.3 2008/07/18 00:21:09 dillon Exp $
  */
 /*
  * This header file contains structures used internally by the HAMMERFS
@@ -499,6 +499,7 @@ struct hammer_io {
        u_int           waitmod : 1;    /* waiting for modify_refs */
        u_int           reclaim : 1;    /* reclaim requested */
        u_int           gencrc : 1;     /* crc needs to be generated */
+       u_int           ioerror : 1;    /* abort on io-error */
 };
 
 typedef struct hammer_io *hammer_io_t;
@@ -684,7 +685,7 @@ struct hammer_mount {
        struct hammer_volume *rootvol;
        struct hammer_base_elm root_btree_beg;
        struct hammer_base_elm root_btree_end;
-       int     flags;
+       int     flags;          /* HAMMER_MOUNT_xxx flags */
        int     hflags;
        int     ronly;
        int     nvolumes;
@@ -715,6 +716,8 @@ struct hammer_mount {
        int     locked_dirty_space;             /* meta/volu count    */
        int     io_running_space;
        int     objid_cache_count;
+       int     error;                          /* critical I/O error */
+       struct krate    krate;                  /* rate limited kprintf */
        hammer_tid_t    asof;                   /* snapshot mount */
        hammer_off_t    next_tid;
        int64_t copy_stat_freebigblocks;        /* number of free bigblocks */
@@ -738,7 +741,7 @@ struct hammer_mount {
 
 typedef struct hammer_mount    *hammer_mount_t;
 
-#define HAMMER_MOUNT_UNUSED0001        0x0001
+#define HAMMER_MOUNT_CRITICAL_ERROR    0x0001
 
 struct hammer_sync_info {
        int error;
@@ -815,6 +818,8 @@ extern int hammer_verify_data;
 extern int hammer_write_mode;
 extern int64_t hammer_contention_count;
 
+void   hammer_critical_error(hammer_mount_t hmp, hammer_inode_t ip,
+                       int error, const char *msg);
 int    hammer_vop_inactive(struct vop_inactive_args *);
 int    hammer_vop_reclaim(struct vop_reclaim_args *);
 int    hammer_get_vnode(struct hammer_inode *ip, struct vnode **vpp);
@@ -1019,7 +1024,7 @@ void hammer_blockmap_reserve_complete(hammer_mount_t hmp,
 void hammer_reserve_clrdelay(hammer_mount_t hmp, hammer_reserve_t resv);
 void hammer_blockmap_free(hammer_transaction_t trans,
                        hammer_off_t bmap_off, int bytes);
-void hammer_blockmap_finalize(hammer_transaction_t trans,
+int hammer_blockmap_finalize(hammer_transaction_t trans,
                        hammer_off_t bmap_off, int bytes);
 int hammer_blockmap_getfree(hammer_mount_t hmp, hammer_off_t bmap_off,
                        int *curp, int *errorp);
@@ -1041,7 +1046,7 @@ void hammer_done_transaction(struct hammer_transaction *trans);
 
 void hammer_modify_inode(hammer_inode_t ip, int flags);
 void hammer_flush_inode(hammer_inode_t ip, int flags);
-void hammer_flush_inode_done(hammer_inode_t ip);
+void hammer_flush_inode_done(hammer_inode_t ip, int error);
 void hammer_wait_inode(hammer_inode_t ip);
 
 int  hammer_create_inode(struct hammer_transaction *trans, struct vattr *vap,
@@ -1051,6 +1056,7 @@ int  hammer_create_inode(struct hammer_transaction *trans, struct vattr *vap,
 void hammer_rel_inode(hammer_inode_t ip, int flush);
 int hammer_reload_inode(hammer_inode_t ip, void *arg __unused);
 int hammer_ino_rb_compare(hammer_inode_t ip1, hammer_inode_t ip2);
+int hammer_destroy_inode_callback(hammer_inode_t ip, void *data __unused);
 
 int hammer_sync_inode(hammer_transaction_t trans, hammer_inode_t ip);
 void hammer_test_inode(hammer_inode_t dip);
index 49225b1..06ffd5d 100644 (file)
@@ -31,7 +31,7 @@
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  * 
- * $DragonFly: src/sys/vfs/hammer/hammer_blockmap.c,v 1.24.2.1 2008/07/16 18:39:31 dillon Exp $
+ * $DragonFly: src/sys/vfs/hammer/hammer_blockmap.c,v 1.24.2.2 2008/07/18 00:21:09 dillon Exp $
  */
 
 /*
@@ -145,7 +145,10 @@ again:
        layer1_offset = freemap->phys_offset +
                        HAMMER_BLOCKMAP_LAYER1_OFFSET(next_offset);
        layer1 = hammer_bread(hmp, layer1_offset, errorp, &buffer1);
-       KKASSERT(*errorp == 0);
+       if (*errorp) {
+               result_offset = 0;
+               goto failed;
+       }
 
        /*
         * Check CRC.
@@ -172,7 +175,10 @@ again:
        layer2_offset = layer1->phys_offset +
                        HAMMER_BLOCKMAP_LAYER2_OFFSET(next_offset);
        layer2 = hammer_bread(hmp, layer2_offset, errorp, &buffer2);
-       KKASSERT(*errorp == 0);
+       if (*errorp) {
+               result_offset = 0;
+               goto failed;
+       }
 
        /*
         * Check CRC.
@@ -398,7 +404,8 @@ again:
        layer1_offset = freemap->phys_offset +
                        HAMMER_BLOCKMAP_LAYER1_OFFSET(next_offset);
        layer1 = hammer_bread(hmp, layer1_offset, errorp, &buffer1);
-       KKASSERT(*errorp == 0);
+       if (*errorp)
+               goto failed;
 
        /*
         * Check CRC.
@@ -426,7 +433,8 @@ again:
        layer2_offset = layer1->phys_offset +
                        HAMMER_BLOCKMAP_LAYER2_OFFSET(next_offset);
        layer2 = hammer_bread(hmp, layer2_offset, errorp, &buffer2);
-       KKASSERT(*errorp == 0);
+       if (*errorp)
+               goto failed;
 
        /*
         * Check CRC if not allocating into uninitialized space (which we
@@ -579,7 +587,6 @@ hammer_blockmap_reserve_complete(hammer_mount_t hmp, hammer_reserve_t resv)
                 * sizes.
                 */
                if (resv->bytes_freed == resv->append_off) {
-                       kprintf("U");
                        hammer_del_buffers(hmp, resv->zone_offset,
                                           zone2_offset,
                                           HAMMER_LARGEBLOCK_SIZE);
@@ -646,6 +653,8 @@ hammer_reserve_clrdelay(hammer_mount_t hmp, hammer_reserve_t resv)
 
 /*
  * Backend function - free (offset, bytes) in a zone.
+ *
+ * XXX error return
  */
 void
 hammer_blockmap_free(hammer_transaction_t trans,
@@ -695,7 +704,8 @@ hammer_blockmap_free(hammer_transaction_t trans,
        layer1_offset = freemap->phys_offset +
                        HAMMER_BLOCKMAP_LAYER1_OFFSET(zone_offset);
        layer1 = hammer_bread(hmp, layer1_offset, &error, &buffer1);
-       KKASSERT(error == 0);
+       if (error)
+               goto failed;
        KKASSERT(layer1->phys_offset &&
                 layer1->phys_offset != HAMMER_BLOCKMAP_UNAVAIL);
        if (layer1->layer1_crc != crc32(layer1, HAMMER_LAYER1_CRCSIZE)) {
@@ -708,7 +718,8 @@ hammer_blockmap_free(hammer_transaction_t trans,
        layer2_offset = layer1->phys_offset +
                        HAMMER_BLOCKMAP_LAYER2_OFFSET(zone_offset);
        layer2 = hammer_bread(hmp, layer2_offset, &error, &buffer2);
-       KKASSERT(error == 0);
+       if (error)
+               goto failed;
        if (layer2->entry_crc != crc32(layer2, HAMMER_LAYER2_CRCSIZE)) {
                Debugger("CRC FAILED: LAYER2");
        }
@@ -784,6 +795,7 @@ again:
        hammer_modify_buffer_done(buffer2);
        hammer_unlock(&hmp->blkmap_lock);
 
+failed:
        if (buffer1)
                hammer_rel_buffer(buffer1, 0);
        if (buffer2)
@@ -795,7 +807,7 @@ again:
  *
  * Allocate space that was previously reserved by the frontend.
  */
-void
+int
 hammer_blockmap_finalize(hammer_transaction_t trans,
                         hammer_off_t zone_offset, int bytes)
 {
@@ -814,7 +826,7 @@ hammer_blockmap_finalize(hammer_transaction_t trans,
        int offset;
 
        if (bytes == 0)
-               return;
+               return(0);
        hmp = trans->hmp;
 
        /*
@@ -840,7 +852,8 @@ hammer_blockmap_finalize(hammer_transaction_t trans,
        layer1_offset = freemap->phys_offset +
                        HAMMER_BLOCKMAP_LAYER1_OFFSET(zone_offset);
        layer1 = hammer_bread(hmp, layer1_offset, &error, &buffer1);
-       KKASSERT(error == 0);
+       if (error)
+               goto failed;
        KKASSERT(layer1->phys_offset &&
                 layer1->phys_offset != HAMMER_BLOCKMAP_UNAVAIL);
        if (layer1->layer1_crc != crc32(layer1, HAMMER_LAYER1_CRCSIZE)) {
@@ -853,7 +866,8 @@ hammer_blockmap_finalize(hammer_transaction_t trans,
        layer2_offset = layer1->phys_offset +
                        HAMMER_BLOCKMAP_LAYER2_OFFSET(zone_offset);
        layer2 = hammer_bread(hmp, layer2_offset, &error, &buffer2);
-       KKASSERT(error == 0);
+       if (error)
+               goto failed;
        if (layer2->entry_crc != crc32(layer2, HAMMER_LAYER2_CRCSIZE)) {
                Debugger("CRC FAILED: LAYER2");
        }
@@ -902,10 +916,12 @@ hammer_blockmap_finalize(hammer_transaction_t trans,
        hammer_modify_buffer_done(buffer2);
        hammer_unlock(&hmp->blkmap_lock);
 
+failed:
        if (buffer1)
                hammer_rel_buffer(buffer1, 0);
        if (buffer2)
                hammer_rel_buffer(buffer2, 0);
+       return(error);
 }
 
 /*
@@ -943,7 +959,10 @@ hammer_blockmap_getfree(hammer_mount_t hmp, hammer_off_t zone_offset,
        layer1_offset = freemap->phys_offset +
                        HAMMER_BLOCKMAP_LAYER1_OFFSET(zone_offset);
        layer1 = hammer_bread(hmp, layer1_offset, errorp, &buffer);
-       KKASSERT(*errorp == 0);
+       if (*errorp) {
+               bytes = 0;
+               goto failed;
+       }
        KKASSERT(layer1->phys_offset);
        if (layer1->layer1_crc != crc32(layer1, HAMMER_LAYER1_CRCSIZE)) {
                Debugger("CRC FAILED: LAYER1");
@@ -951,11 +970,16 @@ hammer_blockmap_getfree(hammer_mount_t hmp, hammer_off_t zone_offset,
 
        /*
         * Dive layer 2, each entry represents a large-block.
+        *
+        * (reuse buffer, layer1 pointer becomes invalid)
         */
        layer2_offset = layer1->phys_offset +
                        HAMMER_BLOCKMAP_LAYER2_OFFSET(zone_offset);
        layer2 = hammer_bread(hmp, layer2_offset, errorp, &buffer);
-       KKASSERT(*errorp == 0);
+       if (*errorp) {
+               bytes = 0;
+               goto failed;
+       }
        if (layer2->entry_crc != crc32(layer2, HAMMER_LAYER2_CRCSIZE)) {
                Debugger("CRC FAILED: LAYER2");
        }
@@ -967,6 +991,7 @@ hammer_blockmap_getfree(hammer_mount_t hmp, hammer_off_t zone_offset,
                *curp = 0;
        else
                *curp = 1;
+failed:
        if (buffer)
                hammer_rel_buffer(buffer, 0);
        hammer_rel_volume(root_volume, 0);
@@ -1030,7 +1055,8 @@ hammer_blockmap_lookup(hammer_mount_t hmp, hammer_off_t zone_offset,
        layer1_offset = freemap->phys_offset +
                        HAMMER_BLOCKMAP_LAYER1_OFFSET(zone_offset);
        layer1 = hammer_bread(hmp, layer1_offset, errorp, &buffer);
-       KKASSERT(*errorp == 0);
+       if (*errorp)
+               goto failed;
        KKASSERT(layer1->phys_offset != HAMMER_BLOCKMAP_UNAVAIL);
        if (layer1->layer1_crc != crc32(layer1, HAMMER_LAYER1_CRCSIZE)) {
                Debugger("CRC FAILED: LAYER1");
@@ -1043,7 +1069,8 @@ hammer_blockmap_lookup(hammer_mount_t hmp, hammer_off_t zone_offset,
                        HAMMER_BLOCKMAP_LAYER2_OFFSET(zone_offset);
        layer2 = hammer_bread(hmp, layer2_offset, errorp, &buffer);
 
-       KKASSERT(*errorp == 0);
+       if (*errorp)
+               goto failed;
        if (layer2->zone == 0) {
                base_off = (zone_offset & (~HAMMER_LARGEBLOCK_MASK64 & ~HAMMER_OFF_ZONE_MASK)) | HAMMER_ZONE_RAW_BUFFER;
                resv = RB_LOOKUP(hammer_res_rb_tree, &hmp->rb_resv_root,
@@ -1058,6 +1085,7 @@ hammer_blockmap_lookup(hammer_mount_t hmp, hammer_off_t zone_offset,
                Debugger("CRC FAILED: LAYER2");
        }
 
+failed:
        if (buffer)
                hammer_rel_buffer(buffer, 0);
        hammer_rel_volume(root_volume, 0);
index b0958e1..94a1994 100644 (file)
@@ -31,7 +31,7 @@
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  * 
- * $DragonFly: src/sys/vfs/hammer/hammer_flusher.c,v 1.40.2.2 2008/07/16 18:39:31 dillon Exp $
+ * $DragonFly: src/sys/vfs/hammer/hammer_flusher.c,v 1.40.2.3 2008/07/18 00:21:09 dillon Exp $
  */
 /*
  * HAMMER dependancy flusher thread
@@ -124,8 +124,9 @@ hammer_flusher_async_one(hammer_mount_t hmp)
 void
 hammer_flusher_wait(hammer_mount_t hmp, int seq)
 {
-       while ((int)(seq - hmp->flusher.done) > 0)
+       while ((int)(seq - hmp->flusher.done) > 0) {
                tsleep(&hmp->flusher.done, 0, "hmrfls", 0);
+       }
 }
 
 void
@@ -212,6 +213,8 @@ hammer_flusher_master_thread(void *arg)
                        flg = TAILQ_FIRST(&hmp->flush_group_list);
                        if (flg == NULL || flg->closed == 0)
                                break;
+                       if (hmp->flags & HAMMER_MOUNT_CRITICAL_ERROR)
+                               break;
                }
 
                /*
@@ -263,6 +266,8 @@ hammer_flusher_flush(hammer_mount_t hmp)
                                hmp->flusher.act,
                                flg->total_count, flg->refs);
                }
+               if (hmp->flags & HAMMER_MOUNT_CRITICAL_ERROR)
+                       break;
                hammer_start_transaction_fls(&hmp->flusher.trans, hmp);
 
                /*
@@ -450,10 +455,9 @@ hammer_flusher_clean_loose_ios(hammer_mount_t hmp)
 /*
  * Flush a single inode that is part of a flush group.
  *
- * NOTE!  The sync code can return EWOULDBLOCK if the flush operation
- * would otherwise blow out the buffer cache.  hammer_flush_inode_done()
- * will re-queue the inode for the next flush sequence and force the
- * flusher to run again if this occurs.
+ * Flusher errors are extremely serious, even ENOSPC shouldn't occur because
+ * the front-end should have reserved sufficient space on the media.  Any
+ * error other then EWOULDBLOCK will force the mount to be read-only.
  */
 static
 void
@@ -464,9 +468,19 @@ hammer_flusher_flush_inode(hammer_inode_t ip, hammer_transaction_t trans)
 
        hammer_flusher_clean_loose_ios(hmp);
        error = hammer_sync_inode(trans, ip);
-       if (error != EWOULDBLOCK)
-               ip->error = error;
-       hammer_flush_inode_done(ip);
+
+       /*
+        * EWOULDBLOCK can happen under normal operation, all other errors
+        * are considered extremely serious.  We must set WOULDBLOCK
+        * mechanics to deal with the mess left over from the abort of the
+        * previous flush.
+        */
+       if (error) {
+               ip->flags |= HAMMER_INODE_WOULDBLOCK;
+               if (error == EWOULDBLOCK)
+                       error = 0;
+       }
+       hammer_flush_inode_done(ip, error);
        while (hmp->flusher.finalize_want)
                tsleep(&hmp->flusher.finalize_want, 0, "hmrsxx", 0);
        if (hammer_flusher_undo_exhausted(trans, 1)) {
@@ -543,6 +557,9 @@ hammer_flusher_finalize(hammer_transaction_t trans, int final)
        if (final == 0 && !hammer_flusher_meta_limit(hmp))
                goto done;
 
+       if (hmp->flags & HAMMER_MOUNT_CRITICAL_ERROR)
+               goto done;
+
        /*
         * Flush data buffers.  This can occur asynchronously and at any
         * time.  We must interlock against the frontend direct-data write
@@ -550,6 +567,8 @@ hammer_flusher_finalize(hammer_transaction_t trans, int final)
         */
        count = 0;
        while ((io = TAILQ_FIRST(&hmp->data_list)) != NULL) {
+               if (io->ioerror)
+                       break;
                if (io->lock.refs == 0)
                        ++hammer_count_refedbufs;
                hammer_ref(&io->lock);
@@ -590,6 +609,8 @@ hammer_flusher_finalize(hammer_transaction_t trans, int final)
         */
        count = 0;
        while ((io = TAILQ_FIRST(&hmp->undo_list)) != NULL) {
+               if (io->ioerror)
+                       break;
                KKASSERT(io->modify_refs == 0);
                if (io->lock.refs == 0)
                        ++hammer_count_refedbufs;
@@ -606,6 +627,9 @@ hammer_flusher_finalize(hammer_transaction_t trans, int final)
        hammer_flusher_clean_loose_ios(hmp);
        hammer_io_wait_all(hmp, "hmrfl1");
 
+       if (hmp->flags & HAMMER_MOUNT_CRITICAL_ERROR)
+               goto failed;
+
        /*
         * Update the on-disk volume header with new UNDO FIFO end position
         * (do not generate new UNDO records for this change).  We have to
@@ -626,7 +650,7 @@ hammer_flusher_finalize(hammer_transaction_t trans, int final)
        cundomap = &hmp->blockmap[HAMMER_ZONE_UNDO_INDEX];
 
        if (dundomap->first_offset != cundomap->first_offset ||
-           dundomap->next_offset != cundomap->next_offset) {
+                  dundomap->next_offset != cundomap->next_offset) {
                hammer_modify_volume(NULL, root_volume, NULL, 0);
                dundomap->first_offset = cundomap->first_offset;
                dundomap->next_offset = cundomap->next_offset;
@@ -649,6 +673,9 @@ hammer_flusher_finalize(hammer_transaction_t trans, int final)
        hammer_flusher_clean_loose_ios(hmp);
        hammer_io_wait_all(hmp, "hmrfl2");
 
+       if (hmp->flags & HAMMER_MOUNT_CRITICAL_ERROR)
+               goto failed;
+
        /*
         * Flush meta-data.  The meta-data will be undone if we crash
         * so we can safely flush it asynchronously.
@@ -659,6 +686,8 @@ hammer_flusher_finalize(hammer_transaction_t trans, int final)
         */
        count = 0;
        while ((io = TAILQ_FIRST(&hmp->meta_list)) != NULL) {
+               if (io->ioerror)
+                       break;
                KKASSERT(io->modify_refs == 0);
                if (io->lock.refs == 0)
                        ++hammer_count_refedbufs;
@@ -688,8 +717,18 @@ hammer_flusher_finalize(hammer_transaction_t trans, int final)
                hammer_clear_undo_history(hmp);
        }
 
+       /*
+        * Cleanup.  Report any critical errors.
+        */
+failed:
        hammer_sync_unlock(trans);
 
+       if (hmp->flags & HAMMER_MOUNT_CRITICAL_ERROR) {
+               kprintf("HAMMER(%s): Critical write error during flush, "
+                       "refusing to sync UNDO FIFO\n",
+                       root_volume->ondisk->vol_name);
+       }
+
 done:
        hammer_unlock(&hmp->flusher.finalize_lock);
        if (--hmp->flusher.finalize_want == 0)
@@ -735,6 +774,8 @@ hammer_flusher_meta_halflimit(hammer_mount_t hmp)
 int
 hammer_flusher_haswork(hammer_mount_t hmp)
 {
+       if (hmp->flags & HAMMER_MOUNT_CRITICAL_ERROR)
+               return(0);
        if (TAILQ_FIRST(&hmp->flush_group_list) ||      /* dirty inodes */
            TAILQ_FIRST(&hmp->volu_list) ||             /* dirty bufffers */
            TAILQ_FIRST(&hmp->undo_list) ||
index c66a49a..8fa958b 100644 (file)
@@ -31,7 +31,7 @@
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  * 
- * $DragonFly: src/sys/vfs/hammer/hammer_inode.c,v 1.103.2.1 2008/07/16 18:39:31 dillon Exp $
+ * $DragonFly: src/sys/vfs/hammer/hammer_inode.c,v 1.103.2.2 2008/07/18 00:21:09 dillon Exp $
  */
 
 #include "hammer.h"
@@ -948,19 +948,11 @@ retry:
                error = hammer_btree_lookup(cursor);
                if (hammer_debug_inode)
                        kprintf("IPDEL %p %08x %d", ip, ip->flags, error);
-               if (error) {
-                       kprintf("error %d\n", error);
-                       Debugger("hammer_update_inode");
-               }
 
                if (error == 0) {
                        error = hammer_ip_delete_record(cursor, ip, trans->tid);
                        if (hammer_debug_inode)
                                kprintf(" error %d\n", error);
-                       if (error && error != EDEADLK) {
-                               kprintf("error %d\n", error);
-                               Debugger("hammer_update_inode2");
-                       }
                        if (error == 0) {
                                ip->flags |= HAMMER_INODE_DELONDISK;
                        }
@@ -1031,10 +1023,6 @@ retry:
                        if (error)
                                break;
                }
-               if (error) {
-                       kprintf("error %d\n", error);
-                       Debugger("hammer_update_inode3");
-               }
 
                /*
                 * The record isn't managed by the inode's record tree,
@@ -1127,10 +1115,6 @@ retry:
        cursor->flags |= HAMMER_CURSOR_BACKEND;
 
        error = hammer_btree_lookup(cursor);
-       if (error) {
-               kprintf("error %d\n", error);
-               Debugger("hammer_update_itimes1");
-       }
        if (error == 0) {
                hammer_cache_node(&ip->cache[0], cursor->node);
                if (ip->sync_flags & HAMMER_INODE_MTIME) {
@@ -1224,7 +1208,7 @@ hammer_rel_inode(struct hammer_inode *ip, int flush)
  * Unload and destroy the specified inode.  Must be called with one remaining
  * reference.  The reference is disposed of.
  *
- * This can only be called in the context of the flusher.
+ * The inode must be completely clean.
  */
 static int
 hammer_unload_inode(struct hammer_inode *ip)
@@ -1249,6 +1233,78 @@ hammer_unload_inode(struct hammer_inode *ip)
 }
 
 /*
+ * Called during unmounting if a critical error occured.  The in-memory
+ * inode and all related structures are destroyed.
+ *
+ * If a critical error did not occur the unmount code calls the standard
+ * release and asserts that the inode is gone.
+ */
+int
+hammer_destroy_inode_callback(struct hammer_inode *ip, void *data __unused)
+{
+       hammer_record_t rec;
+
+       /*
+        * Get rid of the inodes in-memory records, regardless of their
+        * state, and clear the mod-mask.
+        */
+       while ((rec = TAILQ_FIRST(&ip->target_list)) != NULL) {
+               TAILQ_REMOVE(&ip->target_list, rec, target_entry);
+               rec->target_ip = NULL;
+               if (rec->flush_state == HAMMER_FST_SETUP)
+                       rec->flush_state = HAMMER_FST_IDLE;
+       }
+       while ((rec = RB_ROOT(&ip->rec_tree)) != NULL) {
+               if (rec->flush_state == HAMMER_FST_FLUSH)
+                       --rec->flush_group->refs;
+               else
+                       hammer_ref(&rec->lock);
+               KKASSERT(rec->lock.refs == 1);
+               rec->flush_state = HAMMER_FST_IDLE;
+               rec->flush_group = NULL;
+               rec->flags |= HAMMER_RECF_DELETED_FE;
+               rec->flags |= HAMMER_RECF_DELETED_BE;
+               hammer_rel_mem_record(rec);
+       }
+       ip->flags &= ~HAMMER_INODE_MODMASK;
+       ip->sync_flags &= ~HAMMER_INODE_MODMASK;
+       KKASSERT(ip->vp == NULL);
+
+       /*
+        * Remove the inode from any flush group, force it idle.  FLUSH
+        * and SETUP states have an inode ref.
+        */
+       switch(ip->flush_state) {
+       case HAMMER_FST_FLUSH:
+               TAILQ_REMOVE(&ip->flush_group->flush_list, ip, flush_entry);
+               --ip->flush_group->refs;
+               ip->flush_group = NULL;
+               /* fall through */
+       case HAMMER_FST_SETUP:
+               hammer_unref(&ip->lock);
+               ip->flush_state = HAMMER_FST_IDLE;
+               /* fall through */
+       case HAMMER_FST_IDLE:
+               break;
+       }
+
+       /*
+        * There shouldn't be any associated vnode.  The unload needs at
+        * least one ref, if we do have a vp steal its ip ref.
+        */
+       if (ip->vp) {
+               kprintf("hammer_destroy_inode_callback: Unexpected "
+                       "vnode association ip %p vp %p\n", ip, ip->vp);
+               ip->vp->v_data = NULL;
+               ip->vp = NULL;
+       } else {
+               hammer_ref(&ip->lock);
+       }
+       hammer_unload_inode(ip);
+       return(0);
+}
+
+/*
  * Called on mount -u when switching from RW to RO or vise-versa.  Adjust
  * the read-only flag for cached inodes.
  *
@@ -1279,7 +1335,11 @@ hammer_reload_inode(hammer_inode_t ip, void *arg __unused)
 void
 hammer_modify_inode(hammer_inode_t ip, int flags)
 {
-       KKASSERT(ip->hmp->ronly == 0 ||
+       /* 
+        * ronly of 0 or 2 does not trigger assertion.
+        * 2 is a special error state 
+        */
+       KKASSERT(ip->hmp->ronly != 1 ||
                  (flags & (HAMMER_INODE_DDIRTY | HAMMER_INODE_XDIRTY | 
                            HAMMER_INODE_BUFS | HAMMER_INODE_DELETED |
                            HAMMER_INODE_ATIME | HAMMER_INODE_MTIME)) == 0);
@@ -1891,6 +1951,8 @@ hammer_setup_child_callback(hammer_record_t rec, void *data)
                 * over from a previous flush attempt.  The flush group will
                 * have been left intact - we are probably reflushing it
                 * now.
+                *
+                * If a flush error occured ip->error will be non-zero.
                 */
                KKASSERT(rec->flush_group == flg);
                r = 1;
@@ -1922,6 +1984,8 @@ hammer_syncgrp_child_callback(hammer_record_t rec, void *data)
 
 /*
  * Wait for a previously queued flush to complete.
+ *
+ * If a critical error occured we don't try to wait.
  */
 void
 hammer_wait_inode(hammer_inode_t ip)
@@ -1929,12 +1993,15 @@ hammer_wait_inode(hammer_inode_t ip)
        hammer_flush_group_t flg;
 
        flg = NULL;
-       if (ip->flush_state == HAMMER_FST_SETUP) {
-               hammer_flush_inode(ip, HAMMER_FLUSH_SIGNAL);
-       }
-       while (ip->flush_state != HAMMER_FST_IDLE) {
-               ip->flags |= HAMMER_INODE_FLUSHW;
-               tsleep(&ip->flags, 0, "hmrwin", 0);
+       if ((ip->hmp->flags & HAMMER_MOUNT_CRITICAL_ERROR) == 0) {
+               if (ip->flush_state == HAMMER_FST_SETUP) {
+                       hammer_flush_inode(ip, HAMMER_FLUSH_SIGNAL);
+               }
+               while (ip->flush_state != HAMMER_FST_IDLE &&
+                      (ip->hmp->flags & HAMMER_MOUNT_CRITICAL_ERROR) == 0) {
+                       ip->flags |= HAMMER_INODE_FLUSHW;
+                       tsleep(&ip->flags, 0, "hmrwin", 0);
+               }
        }
 }
 
@@ -1946,7 +2013,7 @@ hammer_wait_inode(hammer_inode_t ip)
  * inode on the list and re-copy its fields.
  */
 void
-hammer_flush_inode_done(hammer_inode_t ip)
+hammer_flush_inode_done(hammer_inode_t ip, int error)
 {
        hammer_mount_t hmp;
        int dorel;
@@ -1959,6 +2026,7 @@ hammer_flush_inode_done(hammer_inode_t ip)
         * Merge left-over flags back into the frontend and fix the state.
         * Incomplete truncations are retained by the backend.
         */
+       ip->error = error;
        ip->flags |= ip->sync_flags & ~HAMMER_INODE_TRUNCATED;
        ip->sync_flags &= HAMMER_INODE_TRUNCATED;
 
@@ -2047,6 +2115,8 @@ hammer_flush_inode_done(hammer_inode_t ip)
        /*
         * If the frontend made more changes and requested another flush,
         * then try to get it running.
+        *
+        * Reflushes are aborted when the inode is errored out.
         */
        if (ip->flags & HAMMER_INODE_REFLUSH) {
                ip->flags &= ~HAMMER_INODE_REFLUSH;
@@ -2210,14 +2280,8 @@ hammer_sync_record_callback(hammer_record_t record, void *data)
        }
        record->flags &= ~HAMMER_RECF_CONVERT_DELETE;
 
-       if (error) {
+       if (error)
                error = -error;
-               if (error != -ENOSPC) {
-                       kprintf("hammer_sync_record_callback: sync failed rec "
-                               "%p, error %d\n", record, error);
-                       Debugger("sync failed rec");
-               }
-       }
 done:
        hammer_flush_record_done(record, error);
 
@@ -2361,7 +2425,7 @@ hammer_sync_inode(hammer_transaction_t trans, hammer_inode_t ip)
                }
 
                if (error)
-                       Debugger("hammer_ip_delete_range errored");
+                       goto done;
 
                /*
                 * Clear the truncation flag on the backend after we have
@@ -2457,15 +2521,12 @@ hammer_sync_inode(hammer_transaction_t trans, hammer_inode_t ip)
                                hammer_modify_volume_done(trans->rootvol);
                        }
                        hammer_sync_unlock(trans);
-               } else {
-                       Debugger("hammer_ip_delete_clean errored");
                }
        }
 
-       ip->sync_flags &= ~HAMMER_INODE_BUFS;
-
        if (error)
-               Debugger("RB_SCAN errored");
+               goto done;
+       ip->sync_flags &= ~HAMMER_INODE_BUFS;
 
 defer_buffer_flush:
        /*
@@ -2547,13 +2608,11 @@ defer_buffer_flush:
        if (ip->sync_flags & (HAMMER_INODE_DDIRTY | HAMMER_INODE_ATIME | HAMMER_INODE_MTIME)) {
                error = hammer_update_inode(&cursor, ip);
        }
-       if (error)
-               Debugger("hammer_update_itimes/inode errored");
 done:
-       /*
-        * Save the TID we used to sync the inode with to make sure we
-        * do not improperly reuse it.
-        */
+       if (error) {
+               hammer_critical_error(ip->hmp, ip, error,
+                                     "while syncing inode");
+       }
        hammer_done_cursor(&cursor);
        return(error);
 }
index 1ac1ef9..04ac624 100644 (file)
@@ -31,7 +31,7 @@
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  * 
- * $DragonFly: src/sys/vfs/hammer/hammer_io.c,v 1.49.2.1 2008/07/15 18:04:54 dillon Exp $
+ * $DragonFly: src/sys/vfs/hammer/hammer_io.c,v 1.49.2.2 2008/07/18 00:21:09 dillon Exp $
  */
 /*
  * IO Primitives and buffer cache management
@@ -59,6 +59,7 @@ static void hammer_io_direct_read_complete(struct bio *nbio);
 #endif
 static void hammer_io_direct_write_complete(struct bio *nbio);
 static int hammer_io_direct_uncache_callback(hammer_inode_t ip, void *data);
+static void hammer_io_set_modlist(struct hammer_io *io);
 
 /*
  * Initialize a new, already-zero'd hammer_io structure, or reinitialize
@@ -187,13 +188,16 @@ hammer_io_read(struct vnode *devvp, struct hammer_io *io, hammer_off_t limit)
                }
                hammer_stats_disk_read += io->bytes;
                hammer_count_io_running_read -= io->bytes;
-               if (error == 0) {
-                       bp = io->bp;
-                       bp->b_ops = &hammer_bioops;
-                       KKASSERT(LIST_FIRST(&bp->b_dep) == NULL);
-                       LIST_INSERT_HEAD(&bp->b_dep, &io->worklist, node);
-                       BUF_KERNPROC(bp);
-               }
+
+               /*
+                * The code generally assumes b_ops/b_dep has been set-up,
+                * even if we error out here.
+                */
+               bp = io->bp;
+               bp->b_ops = &hammer_bioops;
+               KKASSERT(LIST_FIRST(&bp->b_dep) == NULL);
+               LIST_INSERT_HEAD(&bp->b_dep, &io->worklist, node);
+               BUF_KERNPROC(bp);
                KKASSERT(io->modified == 0);
                KKASSERT(io->running == 0);
                KKASSERT(io->waiting == 0);
@@ -513,8 +517,6 @@ static
 void
 hammer_io_modify(hammer_io_t io, int count)
 {
-       struct hammer_mount *hmp = io->hmp;
-
        /*
         * io->modify_refs must be >= 0
         */
@@ -533,26 +535,7 @@ hammer_io_modify(hammer_io_t io, int count)
 
        hammer_lock_ex(&io->lock);
        if (io->modified == 0) {
-               KKASSERT(io->mod_list == NULL);
-               switch(io->type) {
-               case HAMMER_STRUCTURE_VOLUME:
-                       io->mod_list = &hmp->volu_list;
-                       hmp->locked_dirty_space += io->bytes;
-                       hammer_count_dirtybufspace += io->bytes;
-                       break;
-               case HAMMER_STRUCTURE_META_BUFFER:
-                       io->mod_list = &hmp->meta_list;
-                       hmp->locked_dirty_space += io->bytes;
-                       hammer_count_dirtybufspace += io->bytes;
-                       break;
-               case HAMMER_STRUCTURE_UNDO_BUFFER:
-                       io->mod_list = &hmp->undo_list;
-                       break;
-               case HAMMER_STRUCTURE_DATA_BUFFER:
-                       io->mod_list = &hmp->data_list;
-                       break;
-               }
-               TAILQ_INSERT_TAIL(io->mod_list, io, mod_entry);
+               hammer_io_set_modlist(io);
                io->modified = 1;
        }
        if (io->released) {
@@ -731,6 +714,34 @@ hammer_io_clear_modlist(struct hammer_io *io)
        }
 }
 
+static void
+hammer_io_set_modlist(struct hammer_io *io)
+{
+       struct hammer_mount *hmp = io->hmp;
+
+       KKASSERT(io->mod_list == NULL);
+
+       switch(io->type) {
+       case HAMMER_STRUCTURE_VOLUME:
+               io->mod_list = &hmp->volu_list;
+               hmp->locked_dirty_space += io->bytes;
+               hammer_count_dirtybufspace += io->bytes;
+               break;
+       case HAMMER_STRUCTURE_META_BUFFER:
+               io->mod_list = &hmp->meta_list;
+               hmp->locked_dirty_space += io->bytes;
+               hammer_count_dirtybufspace += io->bytes;
+               break;
+       case HAMMER_STRUCTURE_UNDO_BUFFER:
+               io->mod_list = &hmp->undo_list;
+               break;
+       case HAMMER_STRUCTURE_DATA_BUFFER:
+               io->mod_list = &hmp->data_list;
+               break;
+       }
+       TAILQ_INSERT_TAIL(io->mod_list, io, mod_entry);
+}
+
 /************************************************************************
  *                             HAMMER_BIOOPS                           *
  ************************************************************************
@@ -763,6 +774,41 @@ hammer_io_complete(struct buf *bp)
         * Deal with people waiting for I/O to drain
         */
        if (iou->io.running) {
+               /*
+                * Deal with critical write errors.  Once a critical error
+                * has been flagged in hmp the UNDO FIFO will not be updated.
+                * That way crash recover will give us a consistent
+                * filesystem.
+                *
+                * Because of this we can throw away failed UNDO buffers.  If
+                * we throw away META or DATA buffers we risk corrupting
+                * the now read-only version of the filesystem visible to
+                * the user.  Clear B_ERROR so the buffer is not re-dirtied
+                * by the kernel and ref the io so it doesn't get thrown
+                * away.
+                */
+               if (bp->b_flags & B_ERROR) {
+                       hammer_critical_error(iou->io.hmp, NULL, bp->b_error,
+                                             "while flushing meta-data");
+                       switch(iou->io.type) {
+                       case HAMMER_STRUCTURE_UNDO_BUFFER:
+                               break;
+                       default:
+                               if (iou->io.ioerror == 0) {
+                                       iou->io.ioerror = 1;
+                                       if (iou->io.lock.refs == 0)
+                                               ++hammer_count_refedbufs;
+                                       hammer_ref(&iou->io.lock);
+                               }
+                               break;
+                       }
+                       bp->b_flags &= ~B_ERROR;
+                       bundirty(bp);
+#if 0
+                       hammer_io_set_modlist(&iou->io);
+                       iou->io.modified = 1;
+#endif
+               }
                hammer_stats_disk_write += iou->io.bytes;
                hammer_count_io_running_write -= iou->io.bytes;
                iou->io.hmp->io_running_space -= iou->io.bytes;
@@ -1164,6 +1210,10 @@ hammer_io_direct_write(hammer_mount_t hmp, hammer_record_t record,
 /*
  * On completion of the BIO this callback must disconnect
  * it from the hammer_record and chain to the previous bio.
+ *
+ * An I/O error forces the mount to read-only.  Data buffers
+ * are not B_LOCKED like meta-data buffers are, so we have to
+ * throw the buffer away to prevent the kernel from retrying.
  */
 static
 void
@@ -1173,6 +1223,12 @@ hammer_io_direct_write_complete(struct bio *nbio)
        hammer_record_t record = nbio->bio_caller_info1.ptr;
 
        obio = pop_bio(nbio);
+       if (obio->bio_buf->b_flags & B_ERROR) {
+               hammer_critical_error(record->ip->hmp, record->ip,
+                                     obio->bio_buf->b_error,
+                                     "while writing bulk data");
+               obio->bio_buf->b_flags |= B_INVAL;
+       }
        biodone(obio);
        KKASSERT(record != NULL && (record->flags & HAMMER_RECF_DIRECT_IO));
        record->flags &= ~HAMMER_RECF_DIRECT_IO;
index a8baab1..9fad52f 100644 (file)
@@ -31,7 +31,7 @@
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  * 
- * $DragonFly: src/sys/vfs/hammer/hammer_object.c,v 1.90 2008/07/14 03:20:49 dillon Exp $
+ * $DragonFly: src/sys/vfs/hammer/hammer_object.c,v 1.90.2.1 2008/07/18 00:21:09 dillon Exp $
  */
 
 #include "hammer.h"
@@ -301,7 +301,8 @@ hammer_flush_record_done(hammer_record_t record, int error)
                 * An error occured, the backend was unable to sync the
                 * record to its media.  Leave the record intact.
                 */
-               Debugger("flush_record_done error");
+               hammer_critical_error(record->ip->hmp, record->ip, error,
+                                     "while flushing record");
        }
 
        --record->flush_group->refs;
@@ -1132,9 +1133,9 @@ hammer_ip_sync_record_cursor(hammer_cursor_t cursor, hammer_record_t record)
                 * statistics in the same transaction as our B-Tree insert.
                 */
                KKASSERT(record->leaf.data_offset != 0);
-               hammer_blockmap_finalize(trans, record->leaf.data_offset,
-                                        record->leaf.data_len);
-               error = 0;
+               error = hammer_blockmap_finalize(trans,
+                                                record->leaf.data_offset,
+                                                record->leaf.data_len);
        } else if (record->data && record->leaf.data_len) {
                /*
                 * Wholely cached record, with data.  Allocate the data.
index 930daad..8d42d7c 100644 (file)
@@ -31,7 +31,7 @@
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  * 
- * $DragonFly: src/sys/vfs/hammer/hammer_ondisk.c,v 1.69.2.1 2008/07/16 18:39:31 dillon Exp $
+ * $DragonFly: src/sys/vfs/hammer/hammer_ondisk.c,v 1.69.2.2 2008/07/18 00:21:09 dillon Exp $
  */
 /*
  * Manage HAMMER's on-disk structures.  These routines are primarily
@@ -268,11 +268,21 @@ hammer_unload_volume(hammer_volume_t volume, void *data __unused)
                hmp->rootvol = NULL;
 
        /*
-        * Release our buffer and flush anything left in the buffer cache.
+        * We must not flush a dirty buffer to disk on umount.  It should
+        * have already been dealt with by the flusher, or we may be in
+        * catastrophic failure.
         */
+       hammer_io_clear_modify(&volume->io, 1);
        volume->io.waitdep = 1;
        bp = hammer_io_release(&volume->io, 1);
-       hammer_io_clear_modlist(&volume->io);
+
+       /*
+        * Clean up the persistent ref ioerror might have on the volume
+        */
+       if (volume->io.ioerror) {
+               volume->io.ioerror = 0;
+               hammer_unref(&volume->io.lock);
+       }
 
        /*
         * There should be no references on the volume, no clusters, and
@@ -290,9 +300,19 @@ hammer_unload_volume(hammer_volume_t volume, void *data __unused)
                        volume->devvp->v_rdev->si_mountpoint = NULL;
                }
                if (ronly) {
+                       /*
+                        * Make sure we don't sync anything to disk if we
+                        * are in read-only mode (1) or critically-errored
+                        * (2).  Note that there may be dirty buffers in
+                        * normal read-only mode from crash recovery.
+                        */
                        vinvalbuf(volume->devvp, 0, 0, 0);
                        VOP_CLOSE(volume->devvp, FREAD);
                } else {
+                       /*
+                        * Normal termination, save any dirty buffers
+                        * (XXX there really shouldn't be any).
+                        */
                        vinvalbuf(volume->devvp, V_SAVE, 0, 0);
                        VOP_CLOSE(volume->devvp, FREAD|FWRITE);
                }
@@ -744,12 +764,29 @@ hammer_load_buffer(hammer_buffer_t buffer, int isnew)
 
 /*
  * NOTE: Called from RB_SCAN, must return >= 0 for scan to continue.
+ * This routine is only called during unmount.
  */
 int
 hammer_unload_buffer(hammer_buffer_t buffer, void *data __unused)
 {
-       ++hammer_count_refedbufs;
-       hammer_ref(&buffer->io.lock);
+       /*
+        * Clean up the persistent ref ioerror might have on the buffer
+        * and acquire a ref (steal ioerror's if we can).
+        */
+       if (buffer->io.ioerror) {
+               buffer->io.ioerror = 0;
+       } else {
+               if (buffer->io.lock.refs == 0)
+                       ++hammer_count_refedbufs;
+               hammer_ref(&buffer->io.lock);
+       }
+
+       /*
+        * We must not flush a dirty buffer to disk on umount.  It should
+        * have already been dealt with by the flusher, or we may be in
+        * catastrophic failure.
+        */
+       hammer_io_clear_modify(&buffer->io, 1);
        hammer_flush_buffer_nodes(buffer);
        KKASSERT(buffer->io.lock.refs == 1);
        hammer_rel_buffer(buffer, 2);
@@ -1203,9 +1240,11 @@ void
 hammer_cache_node(hammer_node_cache_t cache, hammer_node_t node)
 {
        /*
-        * If the node is being deleted, don't cache it!
+        * If the node doesn't exist, or is being deleted, don't cache it!
+        *
+        * The node can only ever be NULL in the I/O failure path.
         */
-       if (node->flags & HAMMER_NODE_DELETED)
+       if (node == NULL || (node->flags & HAMMER_NODE_DELETED))
                return;
        if (cache->node == node)
                return;
@@ -1372,14 +1411,12 @@ hammer_alloc_data(hammer_transaction_t trans, int32_t data_len,
                if (data_len) {
                        data = hammer_bread_ext(trans->hmp, *data_offsetp,
                                                data_len, errorp, data_bufferp);
-                       KKASSERT(*errorp == 0);
                } else {
                        data = NULL;
                }
        } else {
                data = NULL;
        }
-       KKASSERT(*errorp == 0);
        return(data);
 }
 
index 66a27bd..6dcc704 100644 (file)
@@ -31,7 +31,7 @@
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  * 
- * $DragonFly: src/sys/vfs/hammer/hammer_undo.c,v 1.18.2.1 2008/07/16 18:39:32 dillon Exp $
+ * $DragonFly: src/sys/vfs/hammer/hammer_undo.c,v 1.18.2.2 2008/07/18 00:21:09 dillon Exp $
  */
 
 /*
@@ -138,6 +138,9 @@ again:
                undo = hammer_bnew(hmp, next_offset, &error, &buffer);
        else
                undo = hammer_bread(hmp, next_offset, &error, &buffer);
+       if (error)
+               goto done;
+
        hammer_modify_buffer(NULL, buffer, NULL, 0);
 
        KKASSERT(undomap->next_offset == next_offset);
@@ -191,8 +194,8 @@ again:
        undomap->next_offset += bytes;
 
        hammer_modify_buffer_done(buffer);
+done:
        hammer_modify_volume_done(root_volume);
-
        hammer_unlock(&hmp->undo_lock);
 
        if (buffer)
index 63ecea5..cb5bbc5 100644 (file)
@@ -31,7 +31,7 @@
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  * 
- * $DragonFly: src/sys/vfs/hammer/hammer_vfsops.c,v 1.63.2.1 2008/07/15 18:04:54 dillon Exp $
+ * $DragonFly: src/sys/vfs/hammer/hammer_vfsops.c,v 1.63.2.2 2008/07/18 00:21:09 dillon Exp $
  */
 
 #include <sys/param.h>
@@ -327,6 +327,9 @@ hammer_vfs_mount(struct mount *mp, char *mntpt, caddr_t data,
                hmp->root_btree_end.rec_type = 0xFFFFU;
                hmp->root_btree_end.obj_type = 0;
 
+               hmp->krate.freq = 1;    /* maximum reporting rate (hz) */
+               hmp->krate.count = -16; /* initial burst */
+
                hmp->sync_lock.refs = 1;
                hmp->free_lock.refs = 1;
                hmp->undo_lock.refs = 1;
@@ -595,17 +598,13 @@ static void
 hammer_free_hmp(struct mount *mp)
 {
        struct hammer_mount *hmp = (void *)mp->mnt_data;
+       hammer_flush_group_t flg;
        int count;
 
-#if 0
        /*
-        * Clean up the root vnode
+        * Flush anything dirty.  This won't even run if the
+        * filesystem errored-out.
         */
-       if (hmp->rootvp) {
-               vrele(hmp->rootvp);
-               hmp->rootvp = NULL;
-       }
-#endif
        count = 0;
        while (hammer_flusher_haswork(hmp)) {
                hammer_flusher_sync(hmp);
@@ -624,24 +623,36 @@ hammer_free_hmp(struct mount *mp)
        }
        if (count >= 5 && count < 30)
                kprintf("\n");
-       hammer_flusher_destroy(hmp);
 
-       KKASSERT(RB_EMPTY(&hmp->rb_inos_root));
+       /*
+        * If the mount had a critical error we have to destroy any
+        * remaining inodes before we can finish cleaning up the flusher.
+        */
+       if (hmp->flags & HAMMER_MOUNT_CRITICAL_ERROR) {
+               RB_SCAN(hammer_ino_rb_tree, &hmp->rb_inos_root, NULL,
+                       hammer_destroy_inode_callback, NULL);
+       }
 
-#if 0
        /*
-        * Unload & flush inodes
-        *
-        * XXX illegal to call this from here, it can only be done from
-        * the flusher.
+        * There shouldn't be any inodes left now and any left over
+        * flush groups should now be empty.
         */
-       RB_SCAN(hammer_ino_rb_tree, &hmp->rb_inos_root, NULL,
-               hammer_unload_inode, (void *)MNT_WAIT);
+       KKASSERT(RB_EMPTY(&hmp->rb_inos_root));
+       while ((flg = TAILQ_FIRST(&hmp->flush_group_list)) != NULL) {
+               TAILQ_REMOVE(&hmp->flush_group_list, flg, flush_entry);
+               KKASSERT(TAILQ_EMPTY(&flg->flush_list));
+               if (flg->refs) {
+                       kprintf("HAMMER: Warning, flush_group %p was "
+                               "not empty on umount!\n", flg);
+               }
+               kfree(flg, M_HAMMER);
+       }
 
        /*
-        * Unload & flush volumes
+        * We can finally destroy the flusher
         */
-#endif
+       hammer_flusher_destroy(hmp);
+
        /*
         * Unload buffers and then volumes
         */
@@ -658,6 +669,28 @@ hammer_free_hmp(struct mount *mp)
 }
 
 /*
+ * Report critical errors.  ip may be NULL.
+ */
+void
+hammer_critical_error(hammer_mount_t hmp, hammer_inode_t ip,
+                     int error, const char *msg)
+{
+       hmp->flags |= HAMMER_MOUNT_CRITICAL_ERROR;
+       krateprintf(&hmp->krate,
+               "HAMMER(%s): Critical error inode=%lld %s\n",
+               hmp->mp->mnt_stat.f_mntfromname,
+               (ip ? ip->obj_id : -1), msg);
+       if (hmp->ronly == 0) {
+               hmp->ronly = 2;         /* special errored read-only mode */
+               hmp->mp->mnt_flag |= MNT_RDONLY;
+               kprintf("HAMMER(%s): Forcing read-only mode\n",
+                       hmp->mp->mnt_stat.f_mntfromname);
+       }
+       hmp->error = error;
+}
+
+
+/*
  * Obtain a vnode for the specified inode number.  An exclusively locked
  * vnode is returned.
  */