code wiki / (root) / nx_nxwreap.nx

nx_nxwreap.nx source

↩ module page · 135 lines · 6246 B

1// nx_nxwreap.nx -- CLI for the leaked-write-guard-scratch reaper. All judgement lives in 2// nx_nxwreap_lib; this file parses argv, prints the census and appends one audit line. It holds no 3// second copy of the decision, the threshold or the name contract. 4// 5// usage: nx_nxwreap <dir> [max_age_s|-] [apply] 6// <dir> ONE directory, NOT a tree. Deliberate: buildroot/runtime holds 12,548 entries and 7// subtrees (knowledge/, _attic/, _hdl_build/) that a naive recursive walk would take 8// minutes to cross, and AN UNBOUNDED SCAN THAT NEVER RETURNS READS EXACTLY LIKE A 9// CRASH. Enumerate the directories that hold scratch with a glob whose envelope you 10// can read, then point this at each of them and sum the partitions. 11// max_age_s `-` or omitted -> conf, then the derived default. The chosen value AND ITS SOURCE 12// are printed, because a default silently standing in for a missing conf is a number 13// whose provenance nobody can recover. 14// apply THE ONLY WAY ANYTHING IS UNLINKED. Absent -> DRY RUN, which reports and writes 15// nothing. Any third argument that is not exactly `apply` is REFUSED BY NAME rather 16// than being treated as truthy, so a typo can never delete a file. 17// 18// EXIT CODES ARE NOT ALL ZERO, AND THAT IS DELIBERATE. Its sibling nx_lock_reap always exits 0 so a 19// cron row can never go red on it; that is right for a one-path reaper and wrong here, because this 20// one can be handed a directory it cannot open, and \"I could not look\" must never be spelled the 21// same way as \"there was nothing to do\". 22// 0 scanned | 2 usage/refusal | 3 UNSCANNABLE (the directory could not be opened) 23// license_tier: ORIGINAL expect_exit: 0 24import "nx_syscalls.nx" 25import "nx_nxwreap_lib.nx" 26 27const NR_LINE: i64 = 1024 28const NR_LOG: *u8 = "knowledge/status/nxwreap.log" as *u8 29const NR_MODE: i64 = 420 30const NR_RC_OK: i64 = 0 31const NR_RC_USAGE: i64 = 2 32const NR_RC_NOSCAN: i64 = 3 33 34func nr_streq(a: *u8, b: *u8) -> i64 { 35 var i: i64 = 0 36 while a[i] != (0 as u8) { 37 if a[i] != b[i] { return 0 } 38 i = i + 1 39 } 40 if b[i] != (0 as u8) { return 0 } 41 return 1 42} 43 44func main(argc: i64, argv: *i64) -> i64 { 45 if argc < 2 { 46 nw_puts("usage: nx_nxwreap <dir> [max_age_s|-] [apply]\n" as *u8) 47 nw_puts(" DRY BY DEFAULT. Only the literal third argument `apply` unlinks anything.\n" as *u8) 48 return NR_RC_USAGE 49 } 50 let dir: *u8 = argv[1] as *u8 51 if dir[0] == (0 as u8) { 52 nw_puts("REFUSED-EMPTY-DIR: an empty path would resolve to the process CWD, which is not a directory anybody asked for\n" as *u8) 53 return NR_RC_USAGE 54 } 55 56 let srcp: *i64 = sys_mmap(NW_I64_BYTES) as *i64 57 var maxage: i64 = nw_maxage(srcp) 58 var src: *u8 = nw_maxage_src_name(srcp[0]) 59 if argc >= 3 { 60 let a2: *u8 = argv[2] as *u8 61 if nr_streq(a2, "-" as *u8) == 0 { 62 let ep: *i64 = sys_mmap(NW_I64_BYTES) as *i64 63 let v: i64 = ccz_num_at(a2, ccz_slen(a2), 0, ep) 64 // A NON-NUMERIC max_age IS REFUSED, NEVER LENIENTLY PARSED TO 0. A threshold of 0 would 65 // make every scratch file older than zero seconds reapable -- the single most destructive 66 // reading of a typo available. 67 if v <= 0 { 68 nw_puts("REFUSED-BAD-MAXAGE: argument 2 must be a positive integer of seconds, or `-` to take the conf/default\n" as *u8) 69 return NR_RC_USAGE 70 } 71 maxage = v 72 src = "argv[2] (top of the configuration hierarchy)" as *u8 73 } 74 } 75 76 var apply: i64 = 0 77 if argc >= 4 { 78 let a3: *u8 = argv[3] as *u8 79 if nr_streq(a3, "apply" as *u8) == 1 { apply = 1 } else { 80 nw_puts("REFUSED-UNKNOWN-MODE: argument 3 must be exactly `apply` or be omitted. A mode token that is not recognised must REFUSE, never fall through to the permissive value.\n" as *u8) 81 return NR_RC_USAGE 82 } 83 } 84 85 let now: i64 = sys_now_realtime_sec() 86 nw_puts("NXWREAP dir=" as *u8) 87 nw_puts(dir) 88 nw_puts(" max_age_s=" as *u8) 89 nw_num(maxage) 90 nw_puts(" from " as *u8) 91 nw_puts(src) 92 nw_puts("\n ship-loop bound (nx_organ_ship OS_TIMEOUT_MS) derives to " as *u8) 93 nw_num(nw_ship_bound_s()) 94 nw_puts("s; the threshold must be at least that\n mode=" as *u8) 95 if apply == 1 { nw_puts("APPLY (files WILL be unlinked)\n" as *u8) } else { nw_puts("DRY (nothing will be written)\n" as *u8) } 96 97 let out: *i64 = nw_ctr() 98 let rc: i64 = nw_scan(dir, now, maxage, apply, out) 99 if rc < 0 { 100 nw_puts("UNSCANNABLE dir=" as *u8) 101 nw_puts(dir) 102 nw_puts(" -- could not open it. This is NOT the same as finding nothing, and it is not reported in the same word.\n" as *u8) 103 return NR_RC_NOSCAN 104 } 105 nw_print_partition(out) 106 107 let line: *u8 = sys_mmap(NR_LINE) 108 var o: i64 = 0 109 o = ccz_cat_str(line, o, "NXWREAP ts=" as *u8) 110 o = ccz_cat_num(line, o, now) 111 o = ccz_cat_str(line, o, " dir=" as *u8) 112 o = ccz_cat_str(line, o, dir) 113 o = ccz_cat_str(line, o, " max_age_s=" as *u8) 114 o = ccz_cat_num(line, o, maxage) 115 o = ccz_cat_str(line, o, " apply=" as *u8) 116 o = ccz_cat_num(line, o, apply) 117 o = ccz_cat_str(line, o, " examined=" as *u8) 118 o = ccz_cat_num(line, o, out[NW_C_TOTAL]) 119 o = ccz_cat_str(line, o, " reap=" as *u8) 120 o = ccz_cat_num(line, o, out[NW_C_REAP]) 121 o = ccz_cat_str(line, o, " live_owner=" as *u8) 122 o = ccz_cat_num(line, o, out[NW_C_LIVE_OWNER]) 123 o = ccz_cat_str(line, o, " too_young=" as *u8) 124 o = ccz_cat_num(line, o, out[NW_C_TOO_YOUNG]) 125 o = ccz_cat_str(line, o, " malformed=" as *u8) 126 o = ccz_cat_num(line, o, out[NW_C_MALFORMED]) 127 o = ccz_cat_str(line, o, " unlinked=" as *u8) 128 o = ccz_cat_num(line, o, out[NW_C_UNLINKED]) 129 o = ccz_cat_str(line, o, "\n" as *u8) 130 let lfd: i64 = sys_openat_append(NR_LOG, NR_MODE) 131 // FAIL-SOFT: no permission, no journal, no problem. A write failure never changes the verdict. 132 if lfd >= 0 { sys_write(lfd, line, o); sys_close(lfd) } 133 sys_munmap(line, NR_LINE) 134 return NR_RC_OK 135}