code wiki / _hdl_build / nx_journal.nx
nx_journal.nx source
↩ module page · 132 lines · 9040 B
1// nx_journal.nx -- GATE: WRITE-AHEAD JOURNALING + CRASH RECOVERY on the block-backed VFS (NSFS v2, nx_vfsblock_lib).
2// Closes the FILESYSTEM census gap half "journaling". Grounded on the banked field research (osb_journalfs/osb_ext4:
3// jbd2-class physical redo WAL -- stage whole new blocks, COMMIT with a checksum, apply, checkpoint; an uncommitted
4// or unprovable txn is NEVER applied). The crash points are SIMULATED FOR REAL: the gate stages a txn, saves the
5// image at the exact crash instant, wipes RAM, reloads, and MOUNTS -- recovery is what mount actually does.
6// T1 journaled ops end-to-end (mkdir+create through the WAL; checkpointed after).
7// T2 CRASH AFTER COMMIT, BEFORE APPLY -> mount REPLAYS -> the file EXISTS byte-exact (durability of committed).
8// T3 CRASH BEFORE COMMIT -> mount discards -> the file is ABSENT, allocator + prior files untouched (atomicity).
9// T4 teeth: a COMMITTED txn with a corrupted journal byte -> checksum REFUSES the replay, txn discarded, FS intact.
10// T5 replay is IDEMPOTENT (second replay = no-op) + the FS stays fully consistent for new journaled writes.
11// expect_exit: 0 Sovereign: nx_cc->nxasm via nx_syscalls. NEVER-BRICK: RAM + an image file, 0 firmware writes.
12import "nx_vfsblock_lib.nx"
13import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
14import "nx_g_puts_lib.nx"
15
16// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
17// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
18// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
19// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
20func g_pn(v: i64) -> i64 { nxi_out(v); return 0 }
21func ck(name: *u8, c: i64) -> i64 { if c==1 { g_puts(" PASS " as *u8) } else { g_puts(" FAIL " as *u8) } g_puts(name); g_puts("\n" as *u8); return c }
22func streq_n(a: *u8, b: *u8, n: i64) -> i64 { var i: i64=0; while i<n { if a[i]!=b[i] { return 0 } i=i+1 } return 1 }
23func wlog(fd: i64, s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(fd,s,n); return 0 }
24func jcommitted(a: *u8) -> i64 { return vb_rd(a,vbj_hdr()+24) }
25
26// stage a 1-block file creation through the WAL but DO NOT commit/apply -- the gate controls the crash point.
27func stage_small(a: *u8, scr: *u8, parent: i64, name: *u8, data: *u8, len: i64) -> i64 {
28 let idx: i64=vb_alloc_inode(a)
29 let start: i64=vb_rd(a,24)
30 vbj_begin(a)
31 vbj_add(a,start,data,len)
32 let ds: i64=vb_sum(a,vbj_rec(0),len)
33 var k: i64=0; while k<VB_BS { scr[k]=0 as u8; k=k+1 }
34 vb_wr(scr,0,1); vb_wr(scr,8,0); vb_wr(scr,16,parent); vb_wr(scr,24,len); vb_wr(scr,32,start); vb_wr(scr,40,1); vb_wr(scr,48,ds)
35 var i: i64=0; while i<63 { if name[i]==(0 as u8) { scr[64+i]=0 as u8; i=63 } else { scr[64+i]=name[i]; i=i+1 } } scr[64+63]=0 as u8
36 vbj_add(a,VB_INO0+idx,scr,VB_BS)
37 var k2: i64=0; while k2<VB_BS { scr[k2]=a[k2]; k2=k2+1 }
38 vb_wr(scr,24,start+1)
39 vbj_add(a,0,scr,VB_BS)
40 return idx
41}
42
43func main() -> i64 {
44 g_puts("nx_journal (WRITE-AHEAD JOURNAL + crash recovery on the block-backed VFS: stage -> commit -> apply -> checkpoint)\n" as *u8)
45 var pass: i64=0; var total: i64=0
46 let a: *u8 = sys_mmap(VB_NB*VB_BS)
47 let scr: *u8 = sys_mmap(VB_BS)
48 let out: *u8 = sys_mmap(VB_BS*32)
49 let img: *u8 = "knowledge/journal_disk.img" as *u8
50
51 // T1: journaled mkdir + create, end-to-end
52 vb_format(a)
53 let logs: i64=vbj_mkdir_tx(a,0,"logs" as *u8)
54 let e1: i64=vbj_create_tx(a,logs,"entry" as *u8,"journaled-write-1" as *u8,17)
55 let r1: i64=vb_resolve(a,"/logs/entry" as *u8)
56 let n1: i64=vb_read(a,r1,out)
57 var t1: i64=0
58 if logs>=0 { if e1>=0 { if r1==e1 { if n1==17 { if streq_n(out,"journaled-write-1" as *u8,17)==1 { if jcommitted(a)==0 { t1=1 } } } } } }
59 g_puts(" T1 vbj_mkdir_tx /logs="); g_pn(logs); g_puts(" vbj_create_tx /logs/entry="); g_pn(e1); g_puts(" read="); g_pn(n1); g_puts("B committed-flag(after)="); g_pn(jcommitted(a)); g_puts("\n" as *u8)
60 pass=pass+ck("T1: journaled ops land through the WAL and checkpoint (committed flag retired)" as *u8, t1); total=total+1
61
62 // T2: CRASH AFTER COMMIT, BEFORE APPLY -> replay on mount recovers the write
63 let ca: i64=stage_small(a,scr,logs,"crashA" as *u8,"recovered-after-crash" as *u8,21)
64 vbj_commit(a)
65 vb_save(a,img) // <-- the crash instant: WAL durable, home blocks NOT written
66 var z: i64=0; while z<VB_NB*VB_BS { a[z]=0 as u8; z=z+1 }
67 vb_load(a,img)
68 let pre2: i64=jcommitted(a)
69 let m2: i64=vb_mount(a) // mount = validate + REPLAY
70 let q2: i64=vb_resolve(a,"/logs/crashA" as *u8)
71 let qn2: i64=vb_read(a,q2,out)
72 var t2: i64=0
73 if pre2==1 { if m2==0 { if q2==ca { if qn2==21 { if streq_n(out,"recovered-after-crash" as *u8,21)==1 { if jcommitted(a)==0 { t2=1 } } } } } }
74 g_puts(" T2 crash-after-commit: pre-mount committed="); g_pn(pre2); g_puts(" mount rc="); g_pn(m2); g_puts(" -> /logs/crashA read="); g_pn(qn2); g_puts("B (recovered)\n" as *u8)
75 pass=pass+ck("T2: crash AFTER commit -> mount replays the WAL -> the committed write EXISTS byte-exact" as *u8, t2); total=total+1
76
77 // T3: CRASH BEFORE COMMIT -> txn discarded, nothing half-applied
78 let nf3: i64=vb_rd(a,24)
79 let cb: i64=stage_small(a,scr,logs,"crashB" as *u8,"never-committed" as *u8,15)
80 vb_save(a,img) // crash BEFORE the commit record
81 var z3: i64=0; while z3<VB_NB*VB_BS { a[z3]=0 as u8; z3=z3+1 }
82 vb_load(a,img)
83 let m3: i64=vb_mount(a)
84 let q3: i64=vb_resolve(a,"/logs/crashB" as *u8)
85 let qa3: i64=vb_resolve(a,"/logs/crashA" as *u8)
86 let qn3: i64=vb_read(a,qa3,out)
87 var t3: i64=0
88 if m3==0 { if q3==(0-1) { if vb_rd(a,24)==nf3 { if qn3==21 { if streq_n(out,"recovered-after-crash" as *u8,21)==1 { t3=1 } } } } }
89 g_puts(" T3 crash-before-commit: mount rc="); g_pn(m3); g_puts(" /logs/crashB="); g_pn(q3); g_puts(" (absent) next_free unchanged="); g_pn(vb_rd(a,24)); g_puts("\n" as *u8)
90 pass=pass+ck("T3: crash BEFORE commit -> txn cleanly ABSENT, allocator + prior files untouched (atomicity)" as *u8, t3); total=total+1
91
92 // T4 teeth: committed txn with a corrupted journal record -> checksum refuses the replay
93 let cc: i64=stage_small(a,scr,logs,"crashC" as *u8,"corrupt-me" as *u8,10)
94 vbj_commit(a)
95 let rc0: i64=vbj_rec(0)
96 a[rc0+1] = (a[rc0+1] ^ (0xFF as u8))
97 vb_save(a,img)
98 var z4: i64=0; while z4<VB_NB*VB_BS { a[z4]=0 as u8; z4=z4+1 }
99 vb_load(a,img)
100 let pre4: i64=jcommitted(a)
101 let m4: i64=vb_mount(a)
102 let q4: i64=vb_resolve(a,"/logs/crashC" as *u8)
103 let qa4: i64=vb_resolve(a,"/logs/crashA" as *u8)
104 let v4: i64=vb_verify(a,qa4)
105 var t4: i64=0
106 if pre4==1 { if m4==0 { if q4==(0-1) { if jcommitted(a)==0 { if v4==1 { t4=1 } } } } }
107 g_puts(" T4 corrupt committed journal: pre-mount committed="); g_pn(pre4); g_puts(" mount rc="); g_pn(m4); g_puts(" /logs/crashC="); g_pn(q4); g_puts(" (refused+discarded) prior verify="); g_pn(v4); g_puts("\n" as *u8)
108 pass=pass+ck("T4 (teeth): a corrupted COMMITTED txn is REFUSED by the checksum and discarded -- never half-applied" as *u8, t4); total=total+1
109
110 // T5: replay idempotence + the FS keeps accepting journaled writes
111 let cd: i64=stage_small(a,scr,logs,"crashD" as *u8,"idempotent-replay" as *u8,17)
112 vbj_commit(a)
113 let rp1: i64=vbj_replay(a)
114 let rp2: i64=vbj_replay(a)
115 let q5: i64=vb_resolve(a,"/logs/crashD" as *u8)
116 let qn5: i64=vb_read(a,q5,out)
117 let fin: i64=vbj_create_tx(a,logs,"final" as *u8,"fs-still-consistent" as *u8,19)
118 let q6: i64=vb_resolve(a,"/logs/final" as *u8)
119 var t5: i64=0
120 if rp1==1 { if rp2==0 { if q5==cd { if qn5==17 { if streq_n(out,"idempotent-replay" as *u8,17)==1 { if fin>=0 { if q6==fin { if vb_lscount(a,logs)==4 { t5=1 } } } } } } } }
121 g_puts(" T5 replay1="); g_pn(rp1); g_puts(" replay2="); g_pn(rp2); g_puts(" (idempotent) /logs/crashD="); g_pn(qn5); g_puts("B; new journaled create /logs/final="); g_pn(fin); g_puts(" ls /logs="); g_pn(vb_lscount(a,logs)); g_puts("\n" as *u8)
122 pass=pass+ck("T5: replay is IDEMPOTENT (second = no-op) and the FS stays consistent for new journaled writes" as *u8, t5); total=total+1
123
124 var okall: i64=0; if pass==total { okall=1 }
125 g_puts("---- nx_journal: passed "); g_pn(pass); g_puts(" / "); g_pn(total); g_puts(" ----\n" as *u8)
126 if okall==1 {
127 let logf: i64=sys_openat_append("knowledge/status/journal.log" as *u8, 420)
128 if logf>=0 { wlog(logf,"NXJOURNAL GREEN: write-ahead journal on the block-backed VFS -- crash-after-commit recovered, crash-before-commit atomic, corrupt txn refused, replay idempotent\n" as *u8); sys_close(logf) }
129 g_puts("verdict=GREEN (journaling: jbd2-class WAL with real simulated-crash recovery -- the FILESYSTEM journaling gap half closed)\n" as *u8); sys_exit(0); return 0
130 }
131 g_puts("verdict=RED\n" as *u8); sys_exit(1); return 1
132}