DF-2584 / fix.diff
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | --- a/sys/vfs/hammer2/hammer2_flush.c +++ b/sys/vfs/hammer2/hammer2_flush.c @@ -53,8 +53,28 @@ #include "hammer2.h" -#define HAMMER2_FLUSH_DEPTH_LIMIT 60 /* stack recursion limit */ +/* + * Recursion depth limit. This MUST be comfortably below the depth at + * which hammer2_flush_core()/hammer2_flush_recurse() recursion exhausts + * a kernel thread stack. Empirically (x86_64, INVARIANTS build), the + * stack dies between 35 and 40 recursion levels, so 60 was never safe. + * Chains at the limit are deferred (see hammer2_flush_defer) and + * re-driven with the depth reset to 0. + */ +#define HAMMER2_FLUSH_DEPTH_LIMIT 16 /* stack recursion limit */ +/* + * Deferred flush element. When the recursion depth limit is reached the + * current chain is placed on this list (with a reference) and re-driven + * with the depth reset to 0 by hammer2_flush() after the stack has been + * popped. This is the re-execution deferral the header comment describes. + */ +struct hammer2_flush_defer { + TAILQ_ENTRY(hammer2_flush_defer) entry; + hammer2_chain_t *chain; +}; + +TAILQ_HEAD(hammer2_flush_defer_list, hammer2_flush_defer); /* * Recursively flush the specified chain. The chain is locked and @@ -66,6 +86,7 @@ struct hammer2_flush_info { hammer2_chain_t *parent; int depth; + struct hammer2_flush_defer_list defer_list; int error; /* cumulative error */ int flags; #ifdef HAMMER2_SCAN_DEBUG @@ -361,6 +382,8 @@ hammer2_flush(hammer2_chain_t *chain, int flags) { hammer2_flush_info_t info; + struct hammer2_flush_defer *delm; + hammer2_chain_t *dchain; int loops; /* @@ -372,6 +395,7 @@ * for re-execution after the stack has been popped. */ bzero(&info, sizeof(info)); + TAILQ_INIT(&info.defer_list); info.flags = flags & ~HAMMER2_FLUSH_TOP; /* @@ -414,6 +438,31 @@ Debugger("hell4"); } } + + /* + * Process deferred chains. Each is re-driven with the recursion + * depth reset to 0, so arbitrarily deep topologies flush with a + * bounded kernel stack. Processing a deferred chain can queue + * additional deferrals; keep going until the list is empty. + */ + while ((delm = TAILQ_FIRST(&info.defer_list)) != NULL) { + TAILQ_REMOVE(&info.defer_list, delm, entry); + dchain = delm->chain; + kfree(delm, M_HAMMER2); + + if (info.parent != dchain->parent) { + if (info.parent) + hammer2_chain_drop(info.parent); + info.parent = dchain->parent; + if (info.parent) + hammer2_chain_ref(info.parent); + } + hammer2_chain_lock(dchain, HAMMER2_RESOLVE_MAYBE); + hammer2_flush_core(&info, dchain, info.flags); + hammer2_chain_unlock(dchain); + hammer2_chain_drop(dchain); /* delm reference */ + } + #ifdef HAMMER2_SCAN_DEBUG if (info.scan_count >= 10) kprintf("hammer2_flush: scan_count %ld (%ld,%ld,%ld,%ld) " @@ -484,6 +533,7 @@ hammer2_flush_core(hammer2_flush_info_t *info, hammer2_chain_t *chain, int flags) { + struct hammer2_flush_defer *delm; hammer2_chain_t *parent; hammer2_dev_t *hmp; int save_error; @@ -579,9 +629,27 @@ #endif } else if (info->depth == HAMMER2_FLUSH_DEPTH_LIMIT) { /* - * Recursion depth reached. + * Recursion depth reached. Defer the chain; it keeps its + * flush flags and is re-driven by hammer2_flush() with the + * depth reset to 0 once this recursion has unwound. Do + * NOT panic here: a crafted filesystem image can drive the + * topology arbitrarily deep. Also do not recurse further; + * the kernel stack is nearly exhausted by the time we get + * here. */ - panic("hammer2: flush depth limit"); + delm = kmalloc(sizeof(*delm), M_HAMMER2, M_WAITOK | M_ZERO); + delm->chain = chain; + hammer2_chain_ref(chain); + TAILQ_INSERT_TAIL(&info->defer_list, delm, entry); + /* + * Do NOT setflush() the parent here: re-flagging the + * ancestor path makes flush_core() treat the subtree as + * new work and rescan it, re-deferring over and over (the + * deferred chain keeps its own ONFLUSH/MODIFIED/UPDATE + * flags, and hammer2_flush() re-drives it from its + * deferral list). + */ + goto done; } else if (chain->flags & (HAMMER2_CHAIN_ONFLUSH | HAMMER2_CHAIN_DESTROY)) { /* |