code wiki / _hdl_build / nx_siteedit_history.nx

nx_siteedit_history.nx source

↩ module page · 146 lines · 5967 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] = 45 as u8; return sh_catn(d, o + 1, 0 - v) } 20 if v == 0 { d[o] = 48 as u8; return o + 1 } 21 var m: i64 = v 22 var nd: i64 = 0 23 var t: i64 = m 24 while t > 0 { nd = nd + 1; t = t / 10 } 25 var i: i64 = nd - 1 26 while i >= 0 { d[o + i] = (48 + (m % 10)) as u8; m = m / 10; i = i - 1 } 27 return o + nd 28} 29 30// dir(cfgpath) into dbuf (NUL-terminated); returns the basename offset (index just past the last '/'). 31func sh_dir_of(cfgpath: *u8, dbuf: *u8) -> i64 { 32 var last: i64 = 0 - 1 33 var i: i64 = 0 34 while cfgpath[i] != (0 as u8) { if (cfgpath[i] as i64) == 47 { last = i } i = i + 1 } 35 if last < 0 { dbuf[0] = 46 as u8; dbuf[1] = 0 as u8; return 0 } // no '/' -> "." (cwd) 36 if last == 0 { dbuf[0] = 47 as u8; dbuf[1] = 0 as u8; return 1 } // root 37 var j: i64 = 0 38 while j < last { dbuf[j] = cfgpath[j]; j = j + 1 } 39 dbuf[last] = 0 as u8 40 return last + 1 41} 42 43// if name == "<bname>.v<digits>" return the epoch, else 0 (rejects <bname>.new, <bname>, <bname>.vX9, others). 44func sh_match(name: *u8, bname: *u8, bnl: i64) -> i64 { 45 var i: i64 = 0 46 while i < bnl { if (name[i] as i64) != (bname[i] as i64) { return 0 } i = i + 1 } 47 if (name[bnl] as i64) != 46 { return 0 } // '.' 48 if (name[bnl + 1] as i64) != 118 { return 0 } // 'v' 49 var k: i64 = bnl + 2 50 var ep: i64 = 0 51 var any: i64 = 0 52 while name[k] != (0 as u8) { 53 let c: i64 = name[k] as i64 54 if c < 48 { return 0 } 55 if c > 57 { return 0 } 56 ep = ep * 10 + (c - 48) 57 any = 1 58 k = k + 1 59 } 60 if any == 0 { return 0 } 61 return ep 62} 63 64func sh_open_dir(path: *u8) -> i64 { return __syscall(SH_OPENAT, SH_AT_FDCWD, path as i64, SH_O_DIRECTORY, 0, 0, 0) } 65func sh_getdents(fd: i64, buf: *u8, cap: i64) -> i64 { return __syscall(SH_GETDENTS64, fd, buf as i64, cap, 0, 0, 0) } 66 67// enumerate <cfgpath>.v<epoch> snapshots -> eps[0..cap); returns count (bounded by cap). 0 on unreadable dir. 68func sh_list(cfgpath: *u8, eps: *i64, cap: i64) -> i64 { 69 let dbuf: *u8 = sys_mmap(SH_MAGIC_1024) 70 let boff: i64 = sh_dir_of(cfgpath, dbuf) 71 let bname: *u8 = ((cfgpath as i64) + boff) as *u8 72 let bnl: i64 = sh_slen(bname) 73 let dfd: i64 = sh_open_dir(dbuf) 74 if dfd < 0 { return 0 } 75 let buf: *u8 = sys_mmap(SH_MAGIC_65536) 76 var cnt: i64 = 0 77 var go: i64 = 1 78 while go == 1 { 79 let n: i64 = sh_getdents(dfd, buf, SH_MAGIC_65536) 80 if n <= 0 { go = 0 } else { 81 var off: i64 = 0 82 while off < n { 83 let rb: *u8 = ((buf as i64) + off + 16) as *u8 84 let reclen: i64 = (rb[0] as i64) | ((rb[1] as i64) << 8) 85 if reclen <= 0 { off = n } else { 86 let nm: *u8 = ((buf as i64) + off + 19) as *u8 87 let ep: i64 = sh_match(nm, bname, bnl) 88 if ep > 0 { if cnt < cap { eps[cnt] = ep; cnt = cnt + 1 } } 89 off = off + reclen 90 } 91 } 92 } 93 } 94 sys_close(dfd) 95 return cnt 96} 97 98// insertion sort DESCENDING (newest epoch first). Small N (bounded by the list cap) -> O(n^2) is fine. 99func sh_sort_desc(eps: *i64, n: i64) -> i64 { 100 var i: i64 = 1 101 while i < n { 102 let key: i64 = eps[i] 103 var j: i64 = i - 1 104 var run: i64 = 1 105 while run == 1 { 106 if j < 0 { run = 0 } else { 107 if eps[j] < key { eps[j + 1] = eps[j]; j = j - 1 } else { run = 0 } 108 } 109 } 110 eps[j + 1] = key 111 i = i + 1 112 } 113 return 0 114} 115 116// parse the &v=<digits> query param from a request path (restore/view carry ?s=<token>&v=<epoch>). Matches the 117// literal "&v=" so a stray 'v' inside the base64url session token can't false-match. Returns the epoch, or 0. 118func sh_qs_epoch(path: *u8, pn: i64) -> i64 { 119 var i: i64 = 0 120 while i + 3 <= pn { 121 if (path[i] as i64) == 38 { if (path[i + 1] as i64) == 118 { if (path[i + 2] as i64) == 61 { // '&' 'v' '=' 122 var k: i64 = i + 3 123 var ep: i64 = 0 124 var any: i64 = 0 125 var run: i64 = 1 126 while run == 1 { 127 if k >= pn { run = 0 } else { 128 let c: i64 = path[k] as i64 129 if c < 48 { run = 0 } else { if c > 57 { run = 0 } else { ep = ep * 10 + (c - 48); any = 1; k = k + 1 } } 130 } 131 } 132 if any == 1 { return ep } 133 } } } 134 i = i + 1 135 } 136 return 0 137} 138 139// build "<cfgpath>.v<epoch>" into out (NUL-terminated); returns length. 140func sh_version_path(cfgpath: *u8, ep: i64, out: *u8) -> i64 { 141 var o: i64 = sh_cat(out, 0, cfgpath) 142 o = sh_cat(out, o, ".v" as *u8) 143 o = sh_catn(out, o, ep) 144 out[o] = 0 as u8 145 return o 146}