nx_vfsblock_lib.nx source
↩ module page · 239 lines · 12652 B
1// nx_vfsblock_lib.nx -- the SOVEREIGN block-backed VFS + write-ahead JOURNAL library (NishiOS rung 4: closes the
2// FILESYSTEM census gap "block-backed VFS mount + journaling"). ONE on-disk format (NSFS v2) designed once so the
3// mount gate (nx_vfs_blockfs) and the journal gate (nx_journal) share it -- no format churn, rule-15 DRY.
4//
5// ON-DISK LAYOUT (NSFS v2, 256B blocks, 1024 blocks = 256KB image; ADDITIVE-ONLY data, rule 13):
6// block 0 SUPERBLOCK: [0]magic'NSFS' [8]version=2 [16]nblocks [24]next_free [32]j0 [40]nj [48]ino0 [56]ni [64]data0
7// blocks 1..32 JOURNAL: block 1 = header [0]jmagic'NSJL' [8]seq [16]nrec [24]committed [32]sum [40+8j]target_j (j<27);
8// blocks 2..28 = record payloads (the FULL new content of the target block = physical redo WAL, jbd2-class)
9// blocks 33..96 INODE TABLE (64 inodes, one per block): [0]used [8]type(1=dir,0=file) [16]parent [24]size
10// [32]start(data block#) [40]nblk [48]datasum [64..127]name(63B+NUL). Root = inode 0 (dir, parent -1).
11// blocks 97.. DATA (bump-allocated contiguous, never reused = history-preserving)
12//
13// HIERARCHY = the nx_vfs model persisted: children are inodes whose parent field points at the dir inode; path
14// resolution walks components from root against the ON-DISK inode table. MOUNT = validate superblock (magic+version)
15// -> REPLAY the journal (crash recovery) -> validate root. WAL protocol: stage payloads+targets -> COMMIT (single
16// flag write after a checksum over the staged records) -> APPLY to home blocks -> CHECKPOINT (clear committed).
17// Crash after commit -> replay re-applies (idempotent physical copies). Crash before commit -> txn discarded, home
18// blocks untouched BY CONSTRUCTION (records live only in the journal region). Corrupt committed txn -> checksum
19// REFUSES the replay and discards (the jbd2 rule: an unprovable txn is never applied).
20// Txn cap: 27 record blocks -> journaled creates up to ~25 data blocks (~6.4KB); bigger files go through the direct
21// path at image-build time. NEVER-BRICK: operates on RAM + an image file, writes 0 firmware. license_tier: ORIGINAL
22import "nx_syscalls.nx"
23
24const VB_BS: i64 = 256
25const VB_NB: i64 = 1024
26const VB_MAGIC: i64 = 0x4E534653
27const VB_VER: i64 = 2
28const VB_J0: i64 = 1
29const VB_NJREC: i64 = 27
30const VB_INO0: i64 = 33
31const VB_NI: i64 = 64
32const VB_DATA0: i64 = 97
33const VB_JMAGIC: i64 = 0x4E534A4C
34
35func vb_rd(a: *u8, o: i64) -> i64 { var v: i64=0; var i: i64=0; while i<8 { v=v|((a[o+i] as i64)<<(i*8)); i=i+1 } return v }
36func vb_wr(a: *u8, o: i64, v: i64) -> i64 { var i: i64=0; while i<8 { a[o+i]=((v>>(i*8))&255) as u8; i=i+1 } return 0 }
37func vb_ino(i: i64) -> i64 { return (VB_INO0+i)*VB_BS }
38func vb_sum(a: *u8, off: i64, n: i64) -> i64 { var s: i64=0; var i: i64=0; while i<n { s=s*131+(a[off+i] as i64); i=i+1 } return s }
39func vb_nm_set(a: *u8, boff: i64, name: *u8) -> i64 { var i: i64=0; while i<63 { if name[i]==(0 as u8) { a[boff+64+i]=0 as u8; i=63 } else { a[boff+64+i]=name[i]; i=i+1 } } a[boff+64+63]=0 as u8; return 0 }
40func vb_nm_eq(a: *u8, boff: i64, name: *u8) -> i64 { var i: i64=0; while i<64 { let cn: i64=name[i] as i64; let cb: i64=a[boff+64+i] as i64; if cn!=cb { return 0 } if cn==0 { return 1 } i=i+1 } return 1 }
41
42// ---- format + mount ----
43func vb_format(a: *u8) -> i64 {
44 var z: i64=0; while z<VB_NB*VB_BS { a[z]=0 as u8; z=z+1 }
45 vb_wr(a,0,VB_MAGIC); vb_wr(a,8,VB_VER); vb_wr(a,16,VB_NB); vb_wr(a,24,VB_DATA0)
46 vb_wr(a,32,VB_J0); vb_wr(a,40,VB_NJREC); vb_wr(a,48,VB_INO0); vb_wr(a,56,VB_NI); vb_wr(a,64,VB_DATA0)
47 vb_wr(a,VB_J0*VB_BS,VB_JMAGIC) // journal present, seq=0, nrec=0, committed=0
48 let ro: i64=vb_ino(0)
49 vb_wr(a,ro,1); vb_wr(a,ro+8,1); vb_wr(a,ro+16,0-1); vb_wr(a,ro+24,0)
50 vb_nm_set(a,ro,"" as *u8)
51 return 0
52}
53// mount: 0 ok (journal replayed if needed), -1 bad magic, -2 bad version, -3 root invalid
54func vb_mount(a: *u8) -> i64 {
55 if vb_rd(a,0)!=VB_MAGIC { return 0-1 }
56 if vb_rd(a,8)!=VB_VER { return 0-2 }
57 let r: i64=vbj_replay(a)
58 if r<0-1 { return r } // (replay never returns < -1; kept for shape)
59 let ro: i64=vb_ino(0)
60 if vb_rd(a,ro)!=1 { return 0-3 }
61 if vb_rd(a,ro+8)!=1 { return 0-3 }
62 return 0
63}
64
65// ---- namespace ops (direct-write path; the journaled path wraps these block writes through the WAL) ----
66func vb_alloc_inode(a: *u8) -> i64 { var i: i64=1; while i<VB_NI { if vb_rd(a,vb_ino(i))==0 { return i } i=i+1 } return 0-1 }
67func vb_lookup(a: *u8, parent: i64, name: *u8) -> i64 {
68 var i: i64=0
69 while i<VB_NI { let bo: i64=vb_ino(i); if vb_rd(a,bo)==1 { if vb_rd(a,bo+16)==parent { if i!=parent { if vb_nm_eq(a,bo,name)==1 { return i } } } } i=i+1 }
70 return 0-1
71}
72func vb_mkdir(a: *u8, parent: i64, name: *u8) -> i64 {
73 let po: i64=vb_ino(parent); if vb_rd(a,po)!=1 { return 0-1 } if vb_rd(a,po+8)!=1 { return 0-1 }
74 if vb_lookup(a,parent,name)>=0 { return 0-2 }
75 let idx: i64=vb_alloc_inode(a); if idx<0 { return 0-1 }
76 let bo: i64=vb_ino(idx)
77 vb_wr(a,bo,1); vb_wr(a,bo+8,1); vb_wr(a,bo+16,parent); vb_wr(a,bo+24,0); vb_wr(a,bo+32,0); vb_wr(a,bo+40,0); vb_wr(a,bo+48,0)
78 vb_nm_set(a,bo,name)
79 return idx
80}
81func vb_create(a: *u8, parent: i64, name: *u8, data: *u8, len: i64) -> i64 {
82 let po: i64=vb_ino(parent); if vb_rd(a,po)!=1 { return 0-1 } if vb_rd(a,po+8)!=1 { return 0-1 }
83 if vb_lookup(a,parent,name)>=0 { return 0-2 }
84 let idx: i64=vb_alloc_inode(a); if idx<0 { return 0-1 }
85 var nb: i64=(len+VB_BS-1)/VB_BS; if nb==0 { nb=1 }
86 let start: i64=vb_rd(a,24)
87 if start+nb>VB_NB { return 0-3 }
88 var k: i64=0; while k<len { a[start*VB_BS+k]=data[k]; k=k+1 }
89 let bo: i64=vb_ino(idx)
90 vb_wr(a,bo,1); vb_wr(a,bo+8,0); vb_wr(a,bo+16,parent); vb_wr(a,bo+24,len); vb_wr(a,bo+32,start); vb_wr(a,bo+40,nb)
91 let ds: i64=vb_sum(a,start*VB_BS,len)
92 vb_wr(a,bo+48,ds)
93 vb_nm_set(a,bo,name)
94 vb_wr(a,24,start+nb)
95 return idx
96}
97func vb_resolve(a: *u8, path: *u8) -> i64 {
98 var cur: i64=0; var i: i64=0
99 if path[0]==(47 as u8) { i=1 }
100 let comp: *u8=sys_mmap(80)
101 var outer: i64=1
102 while outer==1 {
103 if path[i]==(0 as u8) { outer=0 } else {
104 var j: i64=0; var inner: i64=1
105 while inner==1 { if path[i]==(0 as u8) { inner=0 } else { if path[i]==(47 as u8) { inner=0 } else { comp[j]=path[i]; j=j+1; i=i+1 } } }
106 comp[j]=0 as u8
107 cur=vb_lookup(a,cur,comp); if cur<0 { return 0-1 }
108 if path[i]==(47 as u8) { i=i+1 }
109 }
110 }
111 return cur
112}
113func vb_read(a: *u8, idx: i64, out: *u8) -> i64 {
114 let bo: i64=vb_ino(idx); if vb_rd(a,bo)!=1 { return 0-1 } if vb_rd(a,bo+8)!=0 { return 0-1 }
115 let sz: i64=vb_rd(a,bo+24); let start: i64=vb_rd(a,bo+32)
116 var k: i64=0; while k<sz { out[k]=a[start*VB_BS+k]; k=k+1 }
117 return sz
118}
119func vb_verify(a: *u8, idx: i64) -> i64 {
120 let bo: i64=vb_ino(idx); if vb_rd(a,bo)!=1 { return 0-1 } if vb_rd(a,bo+8)!=0 { return 0-1 }
121 let sz: i64=vb_rd(a,bo+24); let start: i64=vb_rd(a,bo+32); let stored: i64=vb_rd(a,bo+48)
122 if vb_sum(a,start*VB_BS,sz)==stored { return 1 } return 0
123}
124func vb_lscount(a: *u8, dir: i64) -> i64 { var c: i64=0; var i: i64=0; while i<VB_NI { if vb_rd(a,vb_ino(i))==1 { if vb_rd(a,vb_ino(i)+16)==dir { if i!=dir { c=c+1 } } } i=i+1 } return c }
125
126// ---- image persistence ----
127func vb_save(a: *u8, path: *u8) -> i64 {
128 let fd: i64=sys_openat_wr(path,0x1a4); if fd<0 { return 0-1 }
129 var off: i64=0
130 while off<VB_NB*VB_BS { let w: i64=sys_write(fd,((a as i64)+off) as *u8,VB_NB*VB_BS-off); if w<=0 { sys_close(fd); return 0-1 } off=off+w }
131 sys_close(fd); return 0
132}
133func vb_load(a: *u8, path: *u8) -> i64 {
134 let fd: i64=sys_openat_rd(path); if fd<0 { return 0-1 }
135 var t: i64=0; var go: i64=1
136 while go==1 { let k: i64=sys_read(fd,((a as i64)+t) as *u8,VB_NB*VB_BS-t); if k<=0 { go=0 } else { t=t+k } }
137 sys_close(fd); return t
138}
139
140// ---- WRITE-AHEAD JOURNAL (physical redo, jbd2-class) ----
141func vbj_hdr() -> i64 { return VB_J0*VB_BS }
142func vbj_rec(j: i64) -> i64 { return (VB_J0+1+j)*VB_BS }
143func vbj_begin(a: *u8) -> i64 {
144 let h: i64=vbj_hdr()
145 let seq: i64=vb_rd(a,h+8)
146 vb_wr(a,h,VB_JMAGIC); vb_wr(a,h+8,seq+1); vb_wr(a,h+16,0); vb_wr(a,h+24,0); vb_wr(a,h+32,0)
147 return 0
148}
149// stage the FULL new content of target block tb: src[0..len) + zero padding. Returns record slot or -1 (txn full).
150func vbj_add(a: *u8, tb: i64, src: *u8, len: i64) -> i64 {
151 let h: i64=vbj_hdr()
152 let j: i64=vb_rd(a,h+16)
153 if j>=VB_NJREC { return 0-1 }
154 vb_wr(a,h+40+j*8,tb)
155 let ro: i64=vbj_rec(j)
156 var k: i64=0; while k<VB_BS { if k<len { a[ro+k]=src[k] } else { a[ro+k]=0 as u8 } k=k+1 }
157 vb_wr(a,h+16,j+1)
158 return j
159}
160func vbj_sumrecs(a: *u8) -> i64 {
161 let h: i64=vbj_hdr()
162 let n: i64=vb_rd(a,h+16)
163 var s: i64=0; var j: i64=0
164 while j<n { s=s*257+vb_rd(a,h+40+j*8); s=s+vb_sum(a,vbj_rec(j),VB_BS); j=j+1 }
165 return s
166}
167// COMMIT = checksum over staged records, then the single committed flag. The atomic point.
168func vbj_commit(a: *u8) -> i64 { let h: i64=vbj_hdr(); let s: i64=vbj_sumrecs(a); vb_wr(a,h+32,s); vb_wr(a,h+24,1); return 0 }
169func vbj_apply(a: *u8) -> i64 {
170 let h: i64=vbj_hdr()
171 let n: i64=vb_rd(a,h+16)
172 var j: i64=0
173 while j<n { let tb: i64=vb_rd(a,h+40+j*8); let ro: i64=vbj_rec(j); var k: i64=0; while k<VB_BS { a[tb*VB_BS+k]=a[ro+k]; k=k+1 } j=j+1 }
174 return 0
175}
176func vbj_checkpoint(a: *u8) -> i64 { let h: i64=vbj_hdr(); vb_wr(a,h+24,0); return 0 }
177// replay on mount: 1 = recovered a committed txn; 0 = nothing to do; -1 = committed txn REFUSED (checksum) + discarded
178func vbj_replay(a: *u8) -> i64 {
179 let h: i64=vbj_hdr()
180 if vb_rd(a,h)!=VB_JMAGIC { return 0 }
181 if vb_rd(a,h+24)!=1 { return 0 }
182 let want: i64=vb_rd(a,h+32)
183 let got: i64=vbj_sumrecs(a)
184 if got!=want { vbj_checkpoint(a); return 0-1 }
185 vbj_apply(a); vbj_checkpoint(a)
186 return 1
187}
188
189// ---- journaled namespace ops: stage(new blocks) -> commit -> apply -> checkpoint ----
190// journaled mkdir touches ONE block (the new inode). Returns idx or negative.
191func vbj_mkdir_tx(a: *u8, parent: i64, name: *u8) -> i64 {
192 let po: i64=vb_ino(parent); if vb_rd(a,po)!=1 { return 0-1 } if vb_rd(a,po+8)!=1 { return 0-1 }
193 if vb_lookup(a,parent,name)>=0 { return 0-2 }
194 let idx: i64=vb_alloc_inode(a); if idx<0 { return 0-1 }
195 let nb: *u8=sys_mmap(VB_BS)
196 vb_wr(nb,0,1); vb_wr(nb,8,1); vb_wr(nb,16,parent); vb_wr(nb,24,0); vb_wr(nb,32,0); vb_wr(nb,40,0); vb_wr(nb,48,0)
197 var i: i64=0; while i<63 { if name[i]==(0 as u8) { nb[64+i]=0 as u8; i=63 } else { nb[64+i]=name[i]; i=i+1 } } nb[64+63]=0 as u8
198 vbj_begin(a)
199 let r: i64=vbj_add(a,VB_INO0+idx,nb,VB_BS)
200 if r<0 { return 0-3 }
201 vbj_commit(a); vbj_apply(a); vbj_checkpoint(a)
202 return idx
203}
204// journaled create: stages data blocks + inode block + superblock(bump), then commit/apply/checkpoint.
205func vbj_create_tx(a: *u8, parent: i64, name: *u8, data: *u8, len: i64) -> i64 {
206 let po: i64=vb_ino(parent); if vb_rd(a,po)!=1 { return 0-1 } if vb_rd(a,po+8)!=1 { return 0-1 }
207 if vb_lookup(a,parent,name)>=0 { return 0-2 }
208 let idx: i64=vb_alloc_inode(a); if idx<0 { return 0-1 }
209 var nblk: i64=(len+VB_BS-1)/VB_BS; if nblk==0 { nblk=1 }
210 if nblk>VB_NJREC-2 { return 0-4 } // txn cap: data + inode + superblock must fit the journal
211 let start: i64=vb_rd(a,24)
212 if start+nblk>VB_NB { return 0-3 }
213 vbj_begin(a)
214 // stage data blocks (padded)
215 var b: i64=0
216 while b<nblk {
217 var seg: i64=len-b*VB_BS; if seg>VB_BS { seg=VB_BS } if seg<0 { seg=0 }
218 let r1: i64=vbj_add(a,start+b,((data as i64)+b*VB_BS) as *u8,seg)
219 if r1<0 { return 0-4 }
220 b=b+1
221 }
222 // stage the inode block: datasum computed over the STAGED payloads (home blocks not yet written)
223 var ds: i64=0
224 var q: i64=0
225 while q<len { let ro: i64=vbj_rec(q/VB_BS); ds=ds*131+(a[ro+(q%VB_BS)] as i64); q=q+1 }
226 let nb2: *u8=sys_mmap(VB_BS)
227 vb_wr(nb2,0,1); vb_wr(nb2,8,0); vb_wr(nb2,16,parent); vb_wr(nb2,24,len); vb_wr(nb2,32,start); vb_wr(nb2,40,nblk); vb_wr(nb2,48,ds)
228 var i2: i64=0; while i2<63 { if name[i2]==(0 as u8) { nb2[64+i2]=0 as u8; i2=63 } else { nb2[64+i2]=name[i2]; i2=i2+1 } } nb2[64+63]=0 as u8
229 let r2: i64=vbj_add(a,VB_INO0+idx,nb2,VB_BS)
230 if r2<0 { return 0-4 }
231 // stage the superblock with the bumped allocator
232 let sb: *u8=sys_mmap(VB_BS)
233 var k2: i64=0; while k2<VB_BS { sb[k2]=a[k2]; k2=k2+1 }
234 vb_wr(sb,24,start+nblk)
235 let r3: i64=vbj_add(a,0,sb,VB_BS)
236 if r3<0 { return 0-4 }
237 vbj_commit(a); vbj_apply(a); vbj_checkpoint(a)
238 return idx
239}