nx_research_fetch.nx source
↩ module page · 350 lines · 21259 B
1// nx_research_fetch.nx -- the Nishi RESEARCHER's sovereign web-fetch for the SPEED / BEAT-CUDA arc. Reuses the
2// proven sovereign TLS-1.3 stack (nx_https_fetch_follow: url->connect->TLS1.3 handshake validated vs the
3// Mozilla CA store->GET->redirect-follow->dechunk) -- 100% sovereign (own TLS, nx_cc->nxasm, no curl/wget).
4// Fetches a CITED source on efficient LLM inference + cross-vendor (CUDA-alternative) GPU compute, saves the
5// raw body to knowledge/fetched/ for analysis. The only non-Nishi inputs are the CA-root DATA + the fetched
6// page (exactly the researcher's contract). license_tier: ORIGINAL
7import "nx_syscalls.nx"
8import "nx_x509_trust_store.nx"
9import "nx_trust_store_load_from_certdata.nx"
10import "nx_https_fetch_follow.nx"
11const K_MAGIC_5381: i64 = 5381
12const RF_CERTDATA_CAP: i64 = 4194304 // Mozilla certdata.txt measures ~1MB; this is ITS reserve alone
13// THE NETWORK-BODY RESERVE -- the one legitimate bound (buffer-cap law): a response body's size is
14// genuinely unknowable in advance, so the bound is NAMED for this single purpose and sized for the
15// library mirror lane (packs carry images to ~22MB, PDFs to ~33MB, clips to ~92MB). A body that FILLS
16// the reserve ANNOUNCES and REFUSES below instead of saving as clean -- the old shared 4MiB cap
17// saved a cut PDF with status=200 and no marker (debt 1787063835). mmap commits pages lazily, so
18// unused headroom costs address space, never resident memory.
19const RF_BODY_CAP: i64 = 536870912 // RAISED 2026-09-14 (deliberately, per the refusal's own remedy) from 128 MiB for the BRIGHT leetcode documents parquet, which filled the 128 MiB reserve and was refused as maybe-cut; address space only, pages commit lazily
20const K_MAGIC_2000: i64 = 2000
21const K_MAGIC_2048: i64 = 2048
22const RF_PIPE: i64 = 124
23const RF_NL: i64 = 10
24const RF_PROV_PATH: *u8 = "knowledge/status/fetch_provenance.jrnl"
25// A provenance row is a fixed handful of short fields plus TWO paths: a url and a mirror path. Both are
26// already bounded elsewhere in this organ (rf_build_path writes into a 512-byte buffer), so this is
27// derived from those bounds rather than picked -- four times 512 leaves room for the epoch, the 64-hex
28// digest, the byte count and the hop count with the whole row still far inside one page.
29const RF_PROV_ROWCAP: i64 = 2048
30
31func rf_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
32func rf_putn(v: i64) -> i64 {
33 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 }
34 var m: i64 = v
35 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m }
36 let d: *u8 = sys_mmap(24); var k: i64 = 0
37 while m > 0 { d[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
38 let o: *u8 = sys_mmap(24); var wi: i64 = 0
39 while wi < k { o[wi] = d[k - 1 - wi]; wi = wi + 1 }
40 sys_write(1, o, k)
41 return 0
42}
43
44// Bounded substring witness over the fetched body (n bytes, not NUL-terminated).
45func rf_has(hay: *u8, n: i64, needle: *u8) -> i64 {
46 var nl: i64 = 0
47 while needle[nl] != (0 as u8) { nl = nl + 1 }
48 if nl == 0 { return 0 }
49 var i: i64 = 0
50 while i + nl <= n {
51 var j: i64 = 0
52 while j < nl { if hay[i+j] != needle[j] { break } j = j + 1 }
53 if j == nl { return 1 }
54 i = i + 1
55 }
56 return 0
57}
58
59// Build knowledge/fetched/<name>: caller name (argv[2]) if given (sanitized), else rf_<djb2hex(url)>.raw.
60// FIXES the srch_latest.raw clobber (2026-07-03): every distinct URL now maps to a stable distinct file
61// (idempotent -- re-fetching a URL overwrites only its own file), so parallel research fetches coexist.
62func rf_hexnib(v: i64) -> i64 { if v < 10 { return 48 + v } return 87 + v } // 0-9 then a-f
63func rf_build_path(url: *u8, argc: i64, argv: *i64, out: *u8) -> i64 {
64 let pre: *u8 = "knowledge/fetched/\x00"
65 var o: i64 = 0
66 while pre[o] != (0 as u8) { out[o] = pre[o]; o = o + 1 }
67 if argc >= 3 {
68 let name: *u8 = argv[2] as *u8
69 var i: i64 = 0
70 while name[i] != (0 as u8) {
71 let c: i64 = name[i] as i64
72 var ok: i64 = 0
73 if c >= 48 { if c <= 57 { ok = 1 } } // 0-9
74 if c >= 65 { if c <= 90 { ok = 1 } } // A-Z
75 if c >= 97 { if c <= 122 { ok = 1 } } // a-z
76 if c == 46 { ok = 1 } // .
77 if c == 95 { ok = 1 } // _
78 if c == 45 { ok = 1 } // -
79 if ok == 1 { out[o] = c as u8 } else { out[o] = 95 as u8 }
80 o = o + 1; i = i + 1
81 }
82 } else {
83 out[o] = 114 as u8; out[o+1] = 102 as u8; out[o+2] = 95 as u8; o = o + 3 // "rf_"
84 var h: i64 = K_MAGIC_5381
85 var i: i64 = 0
86 while url[i] != (0 as u8) { h = (((h << 5) + h) + (url[i] as i64)) & 0xFFFFFFFF; i = i + 1 }
87 var s: i64 = 28
88 while s >= 0 { out[o] = rf_hexnib((h >> s) & 0xF) as u8; o = o + 1; s = s - 4 }
89 let suf: *u8 = ".raw\x00"
90 var j: i64 = 0
91 while suf[j] != (0 as u8) { out[o] = suf[j]; o = o + 1; j = j + 1 }
92 }
93 out[o] = 0 as u8
94 return o
95}
96
97// ---- FETCH-TIME PROVENANCE (2026-08-20, lane E) ------------------------------------------------
98// WHY THIS EXISTS. On 2026-08-18 four references were appended to charsim.refs from search-engine
99// summaries with ZERO pages opened; one of them carried a mirror that was a capture of a DIFFERENT url
100// fetched three days earlier, and nx_compare_refs_gate passed the file 9 of 9. It could not have done
101// otherwise: it proves a mirror EXISTS and that the pin EQUALS the filehash OF THAT MIRROR -- never that
102// the mirror is a capture of the url in the row.
103// ★★★★★★A PIN PROVES THE BYTES DID NOT CHANGE, NEVER THAT THEY ARE THE RIGHT DOCUMENT, AND A CITATION
104// REGISTER THAT CANNOT TELL THOSE APART WILL BLESS A FABRICATED PROVENANCE FOREVER.
105// The binding has to be made where the two facts are BOTH IN HAND, which is here and nowhere else: this
106// organ is the only place that knows the url it asked for AND the file it just wrote. A checker bolted on
107// afterwards can only ever re-inspect the file and re-assert what the row already claims.
108// ROW: prov|<epoch>|<requested-url>|<mirror-path>|<sha256 of the saved bytes>|<bytes>|<redirect-hops>
109// APPEND-ONLY by construction (sys_openat_append), so a later fetch of the same url adds a row and never
110// rewrites history -- the journal is evidence, and evidence that can be edited in place is testimony.
111// ⚠DECLARED IMPRECISION: the FINAL url after redirects is NOT recorded, only the requested one and the
112// hop count. Threading a final-url out-parameter would change nx_https_fetch_follow's signature at every
113// one of its call sites, which is a separate change with its own blast radius. The hole this closes is
114// "this mirror is a capture of some OTHER url"; the hole it leaves open is "the requested url redirected
115// somewhere I did not record". Naming it beats pretending the record is complete.
116func rf_hex32(dst: *u8, o: i64, digest: *u8) -> i64 {
117 var p: i64 = o
118 var i: i64 = 0
119 while i < 32 {
120 let b: i64 = digest[i] as i64
121 dst[p] = rf_hexnib((b >> 4) & 0xF) as u8
122 dst[p+1] = rf_hexnib(b & 0xF) as u8
123 p = p + 2
124 i = i + 1
125 }
126 return p
127}
128func rf_prov_cat(dst: *u8, o: i64, s: *u8) -> i64 {
129 var p: i64 = o
130 var i: i64 = 0
131 while s[i] != (0 as u8) { dst[p] = s[i]; p = p + 1; i = i + 1 }
132 return p
133}
134func rf_prov_num(dst: *u8, o: i64, v: i64) -> i64 {
135 if v == 0 { dst[o] = 48 as u8; return o + 1 }
136 var m: i64 = v
137 let d: *u8 = sys_mmap(24)
138 var k: i64 = 0
139 while m > 0 { d[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
140 var p: i64 = o
141 var i: i64 = 0
142 while i < k { dst[p] = d[k-1-i]; p = p + 1; i = i + 1 }
143 return p
144}
145func rf_provenance(url: *u8, mirror: *u8, body: *u8, n: i64) -> i64 {
146 let digest: *u8 = sys_mmap(32)
147 sha256_digest(body, n, digest)
148 let row: *u8 = sys_mmap(RF_PROV_ROWCAP)
149 var p: i64 = rf_prov_cat(row, 0, "prov|" as *u8)
150 p = rf_prov_num(row, p, sys_now_realtime_sec())
151 row[p] = RF_PIPE as u8; p = p + 1
152 p = rf_prov_cat(row, p, url)
153 row[p] = RF_PIPE as u8; p = p + 1
154 p = rf_prov_cat(row, p, mirror)
155 row[p] = RF_PIPE as u8; p = p + 1
156 p = rf_hex32(row, p, digest)
157 row[p] = RF_PIPE as u8; p = p + 1
158 p = rf_prov_num(row, p, n)
159 row[p] = RF_PIPE as u8; p = p + 1
160 p = rf_prov_num(row, p, nx_https_last_hops())
161 row[p] = RF_NL as u8; p = p + 1
162 let jfd: i64 = sys_openat_append(RF_PROV_PATH as *u8, MODE_0644)
163 // A JOURNAL THAT CANNOT BE OPENED MUST SAY SO. Silently skipping the row would make an unprovenanced
164 // mirror indistinguishable from a provenanced one, which is the exact defect this whole function exists
165 // to end -- so the failure is announced and the fetch still succeeds (the body is real either way).
166 if jfd < 0 { rf_puts("RF-PROVENANCE-UNWRITTEN: cannot append "); rf_puts(RF_PROV_PATH as *u8); rf_puts(" -- this mirror will read as UNPROVEN to the refs gate\n"); return 0 - 1 }
167 sys_write(jfd, row, p)
168 sys_close(jfd)
169 rf_puts("PROVENANCE "); sys_write(1, row, p)
170 return 0
171}
172
173// --- TLS-1.3 SHORT-BODY LEDGER + ITS ARMING SWITCH ----------------------------------------------
174// The TLS-1.2 leg has REFUSED a body short of its declared Content-Length since 2026-08-18
175// (RF-TRUNCATED, exit 6). The TLS-1.3 leg computed the same number and discarded it at three of its
176// four termination exits, so a cut body arrived here as a positive byte count with status 200 and was
177// saved -- and pinned -- as a clean mirror. nx_https_get_complete.nx now seals that outcome; this is
178// where it becomes visible to a human and countable by a census.
179const RF_TRUNC_NAMES: *u8 = "knowledge/status/fetch_truncation.names"
180const RF_TRUNC_ARM: *u8 = "knowledge/status/fetch_truncation_arm.conf"
181
182// ARMING IS DATA, NOT A RECOMPILE (Rules 11 and 17). The conf carries one row: `arm=0` or `arm=1`.
183// IT SHIPS AS 0 ON PURPOSE. A check that converts silent successes into estate-wide refusals on the
184// day it lands is a check everyone switches off, and the backlog it would refuse against PREDATES it.
185// WHAT ARMS IT, STATED SO IT CANNOT BECOME FOLKLORE: flip the row to arm=1 once
186// knowledge/status/refs_mirror_incomplete.txt -- the worklist nx_compare_refs_gate regenerates every
187// run, and whose ratchet knowledge/status/refs_mirror_ratchet.conf self-baselined at 23 on 2026-08-20
188// -- reaches ZERO rows. At that point the first refusal anybody meets is a NEW offender rather than
189// an inherited one, which is the difference between a ratchet and a permanently-red detector.
190// A MISSING OR UNREADABLE CONF READS AS 0, NEVER AS ARMED: for a guard that can stop a fetch, the
191// fail-safe direction is to keep fetching and keep talking.
192func rf_trunc_armed() -> i64 {
193 let lp: *i64 = sys_mmap(16) as *i64
194 lp[0] = 0
195 let b: *u8 = sys_read_file(RF_TRUNC_ARM as *u8, lp)
196 if (b as i64) <= 0 { return 0 }
197 if lp[0] <= 0 { return 0 }
198 if rf_has(b, lp[0], "arm=1" as *u8) == 1 { return 1 }
199 return 0
200}
201
202// NAME THE OFFENDER, NOT ONLY THE COUNT. A count without a worklist is not actionable, and the name is
203// already in hand at the instant the shortfall is measured -- throwing it away is what forces the next
204// reader to re-derive it. Append-only, one row per OBSERVATION (never per file), so a repeat offender
205// reads as a repeat instead of overwriting its own history.
206// Row: <epoch>|<mirror-path>|<expected>|<got>
207func rf_trunc_name(mirror: *u8, expected: i64, got: i64) -> i64 {
208 let row: *u8 = sys_mmap(RF_PROV_ROWCAP)
209 var p: i64 = 0
210 p = rf_prov_num(row, p, sys_now_realtime_sec())
211 row[p] = RF_PIPE as u8; p = p + 1
212 p = rf_prov_cat(row, p, mirror)
213 row[p] = RF_PIPE as u8; p = p + 1
214 p = rf_prov_num(row, p, expected)
215 row[p] = RF_PIPE as u8; p = p + 1
216 p = rf_prov_num(row, p, got)
217 row[p] = RF_NL as u8; p = p + 1
218 let fd: i64 = sys_openat_append(RF_TRUNC_NAMES as *u8, MODE_0644)
219 if fd < 0 { rf_puts("RF-TRUNC-LEDGER-UNWRITTEN: cannot append "); rf_puts(RF_TRUNC_NAMES as *u8); rf_puts(" -- this offender is ANNOUNCED but NOT COUNTED, so the ratchet under-reports\n"); return 0 - 1 }
220 sys_write(fd, row, p)
221 sys_close(fd)
222 return 0
223}
224
225func main(argc: i64, argv: *i64) -> i64 {
226 // ARGV-DRIVEN (operator law: NO .txt staging-input files anywhere -- pass inputs as args). The URL is
227 // argv[1] (already NUL-terminated by the kernel). The Mozilla CA bundle is reference DATA, not a staging
228 // file, and is the researcher's one legitimate non-Nishi input.
229 if argc < 2 { rf_puts("usage: nx_research_fetch <url>\n"); return 3 }
230 let url: *u8 = argv[1] as *u8
231 let cpath: *u8 = "data/mozilla_certdata.txt\x00"
232 let r: i64 = nx_trust_store_load_from_certdata(cpath, 512, RF_CERTDATA_CAP)
233 if r <= 0 { rf_puts("RF: certdata load failed\n"); return 1 }
234 let store: *TrustStore = r as *TrustStore
235 rf_puts("CA roots="); rf_putn(trust_store_count(store)); rf_puts("\n")
236 rf_puts("FETCH "); rf_puts(url); rf_puts("\n")
237 let out: *u8 = sys_mmap(RF_BODY_CAP)
238 let status: *i64 = sys_mmap(8) as *i64
239 // RETRY-WITH-BACKOFF 2026-07-08: arxiv (and most paper hosts) rate-limit
240 // rapid repeated fetches with HTTP 429 -- the reason a batch of 2025/2026
241 // papers "wasn't getting through". 429 clears in a few seconds, so back off
242 // (2s/4s/8s) and retry. got: 0=trying 1=ok 2=gave-up. Success = body AND
243 // status < 400.
244 var n: i64 = 0
245 var got: i64 = 0
246 var attempt: i64 = 0
247 var backoff_ms: i64 = K_MAGIC_2000
248 while got == 0 {
249 n = nx_https_fetch_follow_best(url, store, out, RF_BODY_CAP, 6, status)
250 rf_puts("status="); rf_putn(status[0]); rf_puts(" body_bytes="); rf_putn(n); rf_puts("\n")
251 // 🔴 A CUT BODY IS NOT A TRANSIENT FAILURE AND MUST NEVER BE RETRIED INTO A SAVE (2026-08-20).
252 // The TLS-1.2 leg used to return a body truncated at its receive window with status=200, and this
253 // organ printed SAVED over it -- a FALSE PINNED CITATION, because a content-pin computed over a
254 // truncated body matches perfectly forever. ★★★★★★A PIN PROVES THE BYTES DID NOT CHANGE, NEVER
255 // THAT THEY ARE THE WHOLE DOCUMENT. The transport now refuses and names both numbers; this exits
256 // immediately rather than burning the retry ladder on a defect that is deterministic by construction.
257 if n == 0 - NX_FF_TRUNCATED {
258 rf_puts("RF-TRUNCATED: the transport refused a body short of its declared Content-Length (see the T12-TRUNCATED line above for expected= and got=). NOTHING WAS SAVED -- a partial document must never enter the citation register\n")
259 return 6
260 }
261 if n > 0 { if status[0] < 400 { got = 1 } }
262 if got == 0 {
263 // A DEFINITIVE STATUS EXITS THE LADDER AT ONCE (2026-08-25). 429 and 5xx are transient --
264 // the resource exists and the server asks us to wait. 404/403/401/410 are FINAL: asking
265 // again cannot change the answer, and each pointless retry costs 2s+4s+8s AND three extra
266 // requests at an upstream that already answered clearly. MEASURED: a licence sweep probed
267 // four repositories and THREE returned a plain 404 on the first candidate, each burning
268 // the full ladder; a caller walking a 4-candidate ladder multiplies that by four.
269 // The precedent is already here -- the truncated-body case exits at once rather than
270 // "burning the retry ladder on a defect that is deterministic by construction".
271 // status[0] <= 0 means no HTTP answer parsed at all, which IS transient, so it falls through.
272 // 4xx ONLY, and 429 excluded. This block is reached whenever the fetch did NOT succeed,
273 // which includes a 200 that returned an EMPTY body -- and an empty body may well be
274 // transient, so a condition of "status > 0 and < 500" would wrongly retire it on the first
275 // try. Narrowing to 4xx keeps every non-4xx failure on the retry ladder exactly as before.
276 if status[0] >= 400 { if status[0] < 500 { if status[0] != 429 {
277 rf_puts("RF-DEFINITIVE: status="); rf_putn(status[0])
278 rf_puts(" is final, not transient -- the resource is absent or forbidden. NOT retried, NOTHING saved; a caller walking a candidate ladder should try its next candidate now.\n")
279 return 2
280 } } }
281 attempt = attempt + 1
282 if attempt >= 4 { got = 2 } else {
283 rf_puts("RF: retry #"); rf_putn(attempt); rf_puts(" in "); rf_putn(backoff_ms); rf_puts("ms (rate-limit/transient)\n")
284 sys_sleep_ms(backoff_ms)
285 backoff_ms = backoff_ms * 2
286 }
287 }
288 }
289 if got != 1 { rf_puts("RF: fetch failed after retries (code "); rf_putn(n); rf_puts(")\n"); return 2 }
290 // TRUNCATION WITNESS: a body that fills the reserve to the brim is indistinguishable from a cut
291 // one, so it REFUSES loudly (never a clean SAVED for a maybe-partial artifact). False-refusal odds
292 // are one body in RF_BODY_CAP; a deliberate raise re-fetches and disambiguates.
293 if n >= RF_BODY_CAP {
294 rf_puts("RF-TRUNCATED: body filled the entire "); rf_putn(RF_BODY_CAP)
295 rf_puts("-byte reserve -- artifact may be cut, REFUSING to save it as clean (raise RF_BODY_CAP deliberately and re-fetch)\n")
296 return 5
297 }
298
299 // BOT-WALL WITNESS (2026-08-18, refs-lane finding): anti-bot interstitials arrive with 2xx/3xx status
300 // and a small plausible body, so "SAVED" blessed them as clean mirrors (measured on this estate:
301 // freedesktop's go-away meta-refresh challenge saved at 3.5KB status<400; PubMed's cookie
302 // interstitial at 5.5KB). Markers are TOOL-INTERNAL tokens read from real captured specimens
303 // (go-away's __goaway_ challenge params; PubMed's cookie-required-message CSS class), never prose
304 // words. FAIL-SAFE BY SHAPE: a hit still saves the body -- to <name>.botwall -- and ANNOUNCES the
305 // rule that fired, so a false positive costs a rename and never loses evidence, and every refusal
306 // names its rule (the deny-guard law). Cloudflare challenges arrive 403 and are already refused by
307 // the status<400 check above; a corpus-wide FP sweep came back UNPROVEN (nx_absent corpus_complete=0
308 // -- the fetched corpus outgrew the searcher's budget), which is WHY the preserve-on-refuse shape
309 // was chosen over a hard refusal.
310 var bw_rule: *u8 = 0 as *u8
311 if rf_has(out, n, "__goaway_" as *u8) == 1 { bw_rule = "goaway-meta-refresh-challenge" as *u8 }
312 if (bw_rule as i64) == 0 { if rf_has(out, n, "cookie-required-message" as *u8) == 1 { bw_rule = "pubmed-cookie-interstitial" as *u8 } }
313 let opath: *u8 = sys_mmap(512)
314 let ol: i64 = rf_build_path(url, argc, argv, opath)
315 if (bw_rule as i64) != 0 {
316 let sufb: *u8 = ".botwall" as *u8
317 var bi: i64 = 0
318 while sufb[bi] != (0 as u8) { opath[ol+bi] = sufb[bi]; bi = bi + 1 }
319 opath[ol+bi] = 0 as u8
320 rf_puts("RF-BOTWALL rule="); rf_puts(bw_rule); rf_puts(" -- an anti-bot interstitial, NOT the document: preserved at the .botwall path below, never cite it as a mirror\n")
321 }
322 // THE 1.3 LEG NOW ANSWERS THE QUESTION THE 1.2 LEG HAS ANSWERED SINCE 2026-08-18. UNJUDGEABLE is
323 // a real and common answer here (chunked and read-to-close responses declare no length at all), and
324 // it is NOT a complaint -- only TRUNCATED speaks. An axis that cannot see must abstain, not acquit.
325 let bst: i64 = nx_https_last_body_state()
326 if bst == NX_HTTPS_BODY_TRUNCATED {
327 let bexp: i64 = nx_https_last_body_expected()
328 let bgot: i64 = nx_https_last_body_got()
329 rf_puts("RF-BODY-TRUNCATED expected="); rf_putn(bexp)
330 rf_puts(" got="); rf_putn(bgot)
331 rf_puts(" short="); rf_putn(bexp - bgot)
332 rf_puts(" mirror="); rf_puts(opath)
333 rf_puts(" -- the peer declared a Content-Length this fetch did not reach. DO NOT PIN THIS AS A COMPLETE MIRROR: a content-pin computed over a cut body matches perfectly forever.\n")
334 rf_trunc_name(opath, bexp, bgot)
335 if rf_trunc_armed() == 1 {
336 rf_puts("RF-TRUNCATED: the truncation ratchet is ARMED (fetch_truncation_arm.conf arm=1) and this body is short -- REFUSING to save it as clean\n")
337 return 7
338 }
339 }
340 let fd: i64 = sys_openat_wr(opath, MODE_0644)
341 if fd < 0 { rf_puts("RF: save failed (dir missing?); first 2KB to stdout:\n"); var c: i64 = 0; while c < K_MAGIC_2048 { if c >= n { break } c = c + 1 } sys_write(1, out, c); rf_puts("\n"); return 0 }
342 sys_write(fd, out, n)
343 sys_close(fd)
344 // BIND THE MIRROR TO THE URL AT THE MOMENT BOTH ARE IN HAND. Written BEFORE the SAVED line so a
345 // reader who sees SAVED has already seen the provenance row, and SAVED stays the LAST line for the
346 // positional parsers that anchor on it.
347 rf_provenance(url, opath, out, n)
348 rf_puts("SAVED "); rf_puts(opath); rf_puts(" ("); rf_putn(n); rf_puts(" bytes) -- grep it to cite\n")
349 return 0
350}