code wiki / _hdl_build / nx_siteedit_history.nx

nx_siteedit_history.nx source

↩ module page · 145 lines · 5901 B

1// nx_siteedit_history.nx -- N-deep revision history for the site editor. PURE enumeration lib (nx_syscalls only, 2// no HTML): sed_version_keep writes a `<cfgpath>.v<epoch>` snapshot on EVERY save, so history already accumulates 3// on disk -- this reads it back. sh_list getdents64's the config's directory, matches `<basename>.v<digits>`, 4// collects epochs; sh_sort_desc orders newest-first; sh_version_path rebuilds a snapshot path for restore/view. 5// The daemon renders the browse+restore page from these + does the (additive) restore. Gated by 6// nx_siteedit_history_gate (fixture dir, no live FS). license_tier: ORIGINAL 7import "nx_syscalls.nx" 8const SH_MAGIC_1024: i64 = 1024 9const SH_MAGIC_65536: i64 = 65536 10 11const SH_OPENAT: i64 = 257 // x86_64 openat(2) 12const SH_GETDENTS64: i64 = 217 // x86_64 getdents64(2) 13const SH_AT_FDCWD: i64 = 0 - 100 14const SH_O_DIRECTORY: i64 = 0x10000 // O_RDONLY(0) | O_DIRECTORY 15 16func sh_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 17func sh_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { d[o] = s[i]; o = o + 1; i = i + 1 } return o } 18func sh_catn(d: *u8, o: i64, v: i64) -> i64 { 19 if v == 0 { d[o] = 48 as u8; return o + 1 } 20 var m: i64 = v 21 var nd: i64 = 0 22 var t: i64 = m 23 while t > 0 { nd = nd + 1; t = t / 10 } 24 var i: i64 = nd - 1 25 while i >= 0 { d[o + i] = (48 + (m % 10)) as u8; m = m / 10; i = i - 1 } 26 return o + nd 27} 28 29// dir(cfgpath) into dbuf (NUL-terminated); returns the basename offset (index just past the last '/'). 30func sh_dir_of(cfgpath: *u8, dbuf: *u8) -> i64 { 31 var last: i64 = 0 - 1 32 var i: i64 = 0 33 while cfgpath[i] != (0 as u8) { if (cfgpath[i] as i64) == 47 { last = i } i = i + 1 } 34 if last < 0 { dbuf[0] = 46 as u8; dbuf[1] = 0 as u8; return 0 } // no '/' -> "." (cwd) 35 if last == 0 { dbuf[0] = 47 as u8; dbuf[1] = 0 as u8; return 1 } // root 36 var j: i64 = 0 37 while j < last { dbuf[j] = cfgpath[j]; j = j + 1 } 38 dbuf[last] = 0 as u8 39 return last + 1 40} 41 42// if name == "<bname>.v<digits>" return the epoch, else 0 (rejects <bname>.new, <bname>, <bname>.vX9, others). 43func sh_match(name: *u8, bname: *u8, bnl: i64) -> i64 { 44 var i: i64 = 0 45 while i < bnl { if (name[i] as i64) != (bname[i] as i64) { return 0 } i = i + 1 } 46 if (name[bnl] as i64) != 46 { return 0 } // '.' 47 if (name[bnl + 1] as i64) != 118 { return 0 } // 'v' 48 var k: i64 = bnl + 2 49 var ep: i64 = 0 50 var any: i64 = 0 51 while name[k] != (0 as u8) { 52 let c: i64 = name[k] as i64 53 if c < 48 { return 0 } 54 if c > 57 { return 0 } 55 ep = ep * 10 + (c - 48) 56 any = 1 57 k = k + 1 58 } 59 if any == 0 { return 0 } 60 return ep 61} 62 63func sh_open_dir(path: *u8) -> i64 { return __syscall(SH_OPENAT, SH_AT_FDCWD, path as i64, SH_O_DIRECTORY, 0, 0, 0) } 64func sh_getdents(fd: i64, buf: *u8, cap: i64) -> i64 { return __syscall(SH_GETDENTS64, fd, buf as i64, cap, 0, 0, 0) } 65 66// enumerate <cfgpath>.v<epoch> snapshots -> eps[0..cap); returns count (bounded by cap). 0 on unreadable dir. 67func sh_list(cfgpath: *u8, eps: *i64, cap: i64) -> i64 { 68 let dbuf: *u8 = sys_mmap(SH_MAGIC_1024) 69 let boff: i64 = sh_dir_of(cfgpath, dbuf) 70 let bname: *u8 = ((cfgpath as i64) + boff) as *u8 71 let bnl: i64 = sh_slen(bname) 72 let dfd: i64 = sh_open_dir(dbuf) 73 if dfd < 0 { return 0 } 74 let buf: *u8 = sys_mmap(SH_MAGIC_65536) 75 var cnt: i64 = 0 76 var go: i64 = 1 77 while go == 1 { 78 let n: i64 = sh_getdents(dfd, buf, SH_MAGIC_65536) 79 if n <= 0 { go = 0 } else { 80 var off: i64 = 0 81 while off < n { 82 let rb: *u8 = ((buf as i64) + off + 16) as *u8 83 let reclen: i64 = (rb[0] as i64) | ((rb[1] as i64) << 8) 84 if reclen <= 0 { off = n } else { 85 let nm: *u8 = ((buf as i64) + off + 19) as *u8 86 let ep: i64 = sh_match(nm, bname, bnl) 87 if ep > 0 { if cnt < cap { eps[cnt] = ep; cnt = cnt + 1 } } 88 off = off + reclen 89 } 90 } 91 } 92 } 93 sys_close(dfd) 94 return cnt 95} 96 97// insertion sort DESCENDING (newest epoch first). Small N (bounded by the list cap) -> O(n^2) is fine. 98func sh_sort_desc(eps: *i64, n: i64) -> i64 { 99 var i: i64 = 1 100 while i < n { 101 let key: i64 = eps[i] 102 var j: i64 = i - 1 103 var run: i64 = 1 104 while run == 1 { 105 if j < 0 { run = 0 } else { 106 if eps[j] < key { eps[j + 1] = eps[j]; j = j - 1 } else { run = 0 } 107 } 108 } 109 eps[j + 1] = key 110 i = i + 1 111 } 112 return 0 113} 114 115// parse the &v=<digits> query param from a request path (restore/view carry ?s=<token>&v=<epoch>). Matches the 116// literal "&v=" so a stray 'v' inside the base64url session token can't false-match. Returns the epoch, or 0. 117func sh_qs_epoch(path: *u8, pn: i64) -> i64 { 118 var i: i64 = 0 119 while i + 3 <= pn { 120 if (path[i] as i64) == 38 { if (path[i + 1] as i64) == 118 { if (path[i + 2] as i64) == 61 { // '&' 'v' '=' 121 var k: i64 = i + 3 122 var ep: i64 = 0 123 var any: i64 = 0 124 var run: i64 = 1 125 while run == 1 { 126 if k >= pn { run = 0 } else { 127 let c: i64 = path[k] as i64 128 if c < 48 { run = 0 } else { if c > 57 { run = 0 } else { ep = ep * 10 + (c - 48); any = 1; k = k + 1 } } 129 } 130 } 131 if any == 1 { return ep } 132 } } } 133 i = i + 1 134 } 135 return 0 136} 137 138// build "<cfgpath>.v<epoch>" into out (NUL-terminated); returns length. 139func sh_version_path(cfgpath: *u8, ep: i64, out: *u8) -> i64 { 140 var o: i64 = sh_cat(out, 0, cfgpath) 141 o = sh_cat(out, o, ".v" as *u8) 142 o = sh_catn(out, o, ep) 143 out[o] = 0 as u8 144 return o 145}