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}