code wiki / (root) / nx_vfsblock_lib.nx

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}