From cdb6e4e646693cfea7ddde77447f9cf665cda9c0 Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Fri, 18 Jul 2008 00:19:53 +0000 Subject: [PATCH] HAMMER 63/Many: IO Error handling features This commit removes all the remaining Debugger() calls and KKASSERTs in the I/O error path. Errors are now propagated up the call tree and properly reported. * Report I/O errors instead of asserting. * Read or Write errors in the flush path disable flushing and force the mount into read-only mode. Modified buffers are left locked in memory until umount to provide a consistent snapshot of the state of the filesystem. You must umount and remount to recover the filesystem. The filesystem will automatically rollback to the last valid flush upon remounting. * umount and umount -f are now able to unmount a HAMMER filesystem that has catastrophic write errors (e.g. pulling the USB cable on an external drive). --- sys/vfs/hammer/hammer.h | 16 ++-- sys/vfs/hammer/hammer_blockmap.c | 60 +++++++++---- sys/vfs/hammer/hammer_flusher.c | 61 ++++++++++--- sys/vfs/hammer/hammer_inode.c | 149 +++++++++++++++++++++---------- sys/vfs/hammer/hammer_io.c | 116 +++++++++++++++++------- sys/vfs/hammer/hammer_object.c | 11 +-- sys/vfs/hammer/hammer_ondisk.c | 55 ++++++++++-- sys/vfs/hammer/hammer_undo.c | 7 +- sys/vfs/hammer/hammer_vfsops.c | 71 +++++++++++---- 9 files changed, 405 insertions(+), 141 deletions(-) diff --git a/sys/vfs/hammer/hammer.h b/sys/vfs/hammer/hammer.h index 2145db58d0..03e964246c 100644 --- a/sys/vfs/hammer/hammer.h +++ b/sys/vfs/hammer/hammer.h @@ -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.119 2008/07/16 18:30:59 dillon Exp $ + * $DragonFly: src/sys/vfs/hammer/hammer.h,v 1.120 2008/07/18 00:19:53 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); diff --git a/sys/vfs/hammer/hammer_blockmap.c b/sys/vfs/hammer/hammer_blockmap.c index 54fe2a3904..f64f73b515 100644 --- a/sys/vfs/hammer/hammer_blockmap.c +++ b/sys/vfs/hammer/hammer_blockmap.c @@ -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.25 2008/07/16 18:30:59 dillon Exp $ + * $DragonFly: src/sys/vfs/hammer/hammer_blockmap.c,v 1.26 2008/07/18 00:19:53 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); diff --git a/sys/vfs/hammer/hammer_flusher.c b/sys/vfs/hammer/hammer_flusher.c index d78ba7212d..89616ce895 100644 --- a/sys/vfs/hammer/hammer_flusher.c +++ b/sys/vfs/hammer/hammer_flusher.c @@ -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.42 2008/07/16 18:30:59 dillon Exp $ + * $DragonFly: src/sys/vfs/hammer/hammer_flusher.c,v 1.43 2008/07/18 00:19:53 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) || diff --git a/sys/vfs/hammer/hammer_inode.c b/sys/vfs/hammer/hammer_inode.c index 081fde7f92..6d40e7f998 100644 --- a/sys/vfs/hammer/hammer_inode.c +++ b/sys/vfs/hammer/hammer_inode.c @@ -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.104 2008/07/16 18:30:59 dillon Exp $ + * $DragonFly: src/sys/vfs/hammer/hammer_inode.c,v 1.105 2008/07/18 00:19:53 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) @@ -1248,6 +1232,78 @@ hammer_unload_inode(struct hammer_inode *ip) return(0); } +/* + * 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); } diff --git a/sys/vfs/hammer/hammer_io.c b/sys/vfs/hammer/hammer_io.c index 5477af9960..b397052601 100644 --- a/sys/vfs/hammer/hammer_io.c +++ b/sys/vfs/hammer/hammer_io.c @@ -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.50 2008/07/14 20:27:54 dillon Exp $ + * $DragonFly: src/sys/vfs/hammer/hammer_io.c,v 1.51 2008/07/18 00:19:53 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; diff --git a/sys/vfs/hammer/hammer_object.c b/sys/vfs/hammer/hammer_object.c index a8baab1325..17b3fedca0 100644 --- a/sys/vfs/hammer/hammer_object.c +++ b/sys/vfs/hammer/hammer_object.c @@ -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.91 2008/07/18 00:19:53 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. diff --git a/sys/vfs/hammer/hammer_ondisk.c b/sys/vfs/hammer/hammer_ondisk.c index c259e70e16..1dc0abf539 100644 --- a/sys/vfs/hammer/hammer_ondisk.c +++ b/sys/vfs/hammer/hammer_ondisk.c @@ -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.70 2008/07/16 18:30:59 dillon Exp $ + * $DragonFly: src/sys/vfs/hammer/hammer_ondisk.c,v 1.71 2008/07/18 00:19:53 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); } diff --git a/sys/vfs/hammer/hammer_undo.c b/sys/vfs/hammer/hammer_undo.c index 0aa60bf20f..297cf79aa8 100644 --- a/sys/vfs/hammer/hammer_undo.c +++ b/sys/vfs/hammer/hammer_undo.c @@ -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.19 2008/07/16 18:30:59 dillon Exp $ + * $DragonFly: src/sys/vfs/hammer/hammer_undo.c,v 1.20 2008/07/18 00:19:53 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) diff --git a/sys/vfs/hammer/hammer_vfsops.c b/sys/vfs/hammer/hammer_vfsops.c index 85cd42d691..bb77bbf7d7 100644 --- a/sys/vfs/hammer/hammer_vfsops.c +++ b/sys/vfs/hammer/hammer_vfsops.c @@ -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.64 2008/07/14 20:27:54 dillon Exp $ + * $DragonFly: src/sys/vfs/hammer/hammer_vfsops.c,v 1.65 2008/07/18 00:19:53 dillon Exp $ */ #include @@ -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 */ @@ -657,6 +668,28 @@ hammer_free_hmp(struct mount *mp) kfree(hmp, M_HAMMER); } +/* + * 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. -- 2.41.0