xref: /inferno-os/appl/cmd/ar.b (revision 55b0bc0011ddae9df99d50fa0498110585d09a81)
1implement Ar;
2
3#
4# ar - portable (ascii) format version
5#
6
7include "sys.m";
8	sys: Sys;
9
10include "draw.m";
11
12include "bufio.m";
13	bufio: Bufio;
14	Iobuf: import bufio;
15
16include "daytime.m";
17	daytime: Daytime;
18
19include "string.m";
20	str: String;
21
22Ar: module
23{
24	init:	fn(nil: ref Draw->Context, nil: list of string);
25};
26
27ARMAG: con "!<arch>\n";
28SARMAG: con len ARMAG;
29ARFMAG0: con byte '`';
30ARFMAG1: con byte '\n';
31SARNAME: con 16;	# ancient limit
32
33#
34# printable archive header
35#	name[SARNAME] date[12] uid[6] gid[6] mode[8] size[10] fmag[2]
36#
37Oname:	con 0;
38Lname:	con SARNAME;
39Odate:	con Oname+Lname;
40Ldate:	con 12;
41Ouid:	con Odate+Ldate;
42Luid:		con 6;
43Ogid:	con Ouid+Luid;
44Lgid:		con 6;
45Omode:	con Ogid+Lgid;
46Lmode:	con 8;
47Osize:	con Omode+Lmode;
48Lsize:	con 10;
49Ofmag:	con Osize+Lsize;
50Lfmag:	con 2;
51SAR_HDR:	con Ofmag+Lfmag;	# 60
52
53#
54# 	The algorithm uses up to 3 temp files.  The "pivot contents" is the
55# 	archive contents specified by an a, b, or i option.  The temp files are
56# 	astart - contains existing contentss up to and including the pivot contents.
57# 	amiddle - contains new files moved or inserted behind the pivot.
58# 	aend - contains the existing contentss that follow the pivot contents.
59# 	When all contentss have been processed, function 'install' streams the
60#  	temp files, in order, back into the archive.
61#
62
63Armember: adt {	# one per archive contents
64	name:	string;	# trimmed
65	length:	int;
66	date:	int;
67	uid:	int;
68	gid:	int;
69	mode:	int;
70	size:	int;
71	contents:	array of byte;
72	fd:	ref Sys->FD;	# if contents is nil and fd is not nil, fd has contents
73	next:	cyclic ref Armember;
74
75	new:		fn(name: string, fd: ref Sys->FD): ref Armember;
76	rdhdr:	fn(b: ref Iobuf): ref Armember;
77	read:		fn(m: self ref Armember, b:  ref Iobuf): int;
78	wrhdr:	fn(m: self ref Armember, fd: ref Sys->FD);
79	write:	fn(m: self ref Armember, fd: ref Sys->FD);
80	skip:		fn(m: self ref Armember, b: ref Iobuf);
81	replace:	fn(m: self ref Armember, name: string, fd: ref Sys->FD);
82	copyout:	fn(m: self ref Armember, b: ref Iobuf, destfd: ref Sys->FD);
83};
84
85Arfile: adt {	# one per tempfile
86	fd:	ref Sys->FD;	# paging file descriptor, nil if none allocated
87
88	head:	ref Armember;
89	tail:	ref Armember;
90
91	new:		fn(): ref Arfile;
92	copy:	fn(ar: self ref Arfile, b: ref Iobuf, mem: ref Armember);
93	insert:	fn(ar: self ref Arfile, mem: ref Armember);
94	stream:	fn(ar: self ref Arfile, fd: ref Sys->FD);
95	page:	fn(ar: self ref Arfile): int;
96};
97
98File: adt {
99	name:	string;
100	trimmed:	string;
101	found:	int;
102};
103
104man :=	"mrxtdpq";
105opt :=	"uvnbailo";
106
107aflag := 0;
108bflag := 0;
109cflag := 0;
110oflag := 0;
111uflag := 0;
112vflag := 0;
113
114pivotname: string;
115bout: ref Iobuf;
116stderr: ref Sys->FD;
117parts: array of ref Arfile;
118
119comfun: ref fn(a: string, f: array of ref File);
120
121init(nil: ref Draw->Context, args: list of string)
122{
123	sys = load Sys Sys->PATH;
124	bufio = load Bufio Bufio->PATH;
125	daytime = load Daytime Daytime->PATH;
126	str = load String String->PATH;
127
128	stderr = sys->fildes(2);
129	bout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
130	if(len args < 3)
131		usage();
132	args = tl args;
133	s := hd args; args = tl args;
134	for(i := 0; i < len s; i++){
135		case s[i] {
136		'a' =>	aflag = 1;
137		'b' =>	bflag = 1;
138		'c' =>	cflag = 1;
139		'd' =>	setcom(dcmd);
140		'i' =>		bflag = 1;
141		'l' =>		;	# ignored
142		'm' =>	setcom(mcmd);
143		'o' =>	oflag = 1;
144		'p' =>	setcom(pcmd);
145		'q' =>	setcom(qcmd);
146		'r' =>		setcom(rcmd);
147		't' =>		setcom(tcmd);
148		'u' =>	uflag = 1;
149		'v' =>	vflag = 1;
150		'x' =>	setcom(xcmd);
151		* =>
152			sys->fprint(stderr, "ar: bad option `%c'\n", s[i]);
153			usage();
154		}
155	}
156	if(aflag && bflag){
157		sys->fprint(stderr, "ar: only one of 'a' and 'b' can be specified\n");
158		usage();
159	}
160	if(aflag || bflag){
161		pivotname = trim(hd args); args = tl args;
162		if(len args < 2)
163			usage();
164	}
165	if(comfun == nil){
166		if(uflag == 0){
167			sys->fprint(stderr, "ar: one of [%s] must be specified\n", man);
168			usage();
169		}
170		setcom(rcmd);
171	}
172	cp := hd args; args = tl args;
173	files := array[len args] of ref File;
174	for(i = 0; args != nil; args = tl args)
175		files[i++] = ref File(hd args, trim(hd args), 0);
176	comfun(cp, files);	# do the command
177	allfound := 1;
178	for(i = 0; i < len files; i++)
179		if(!files[i].found){
180			sys->fprint(stderr, "ar: %s not found\n", files[i].name);
181			allfound = 0;
182		}
183	bout.flush();
184	if(!allfound)
185		raise "fail: file not found";
186}
187
188#
189# 	select a command
190#
191setcom(fun: ref fn(s: string, f: array of ref File))
192{
193	if(comfun != nil){
194		sys->fprint(stderr, "ar: only one of [%s] allowed\n", man);
195		usage();
196	}
197	comfun = fun;
198}
199
200#
201# 	perform the 'r' and 'u' commands
202#
203rcmd(arname: string, files: array of ref File)
204{
205	bar := openar(arname, Sys->ORDWR, 1);
206	parts = array[2] of {Arfile.new(), nil};
207	ap := parts[0];
208	if(bar != nil){
209		while((mem := Armember.rdhdr(bar)) != nil){
210			if(bamatch(mem.name, pivotname))	# check for pivot
211				ap = parts[1] = Arfile.new();
212			f := match(files, mem.name);
213			if(f == nil){
214				ap.copy(bar, mem);
215				continue;
216			}
217			f.found = 1;
218			dfd := sys->open(f.name, Sys->OREAD);
219			if(dfd == nil){
220				if(len files > 0)
221					sys->fprint(stderr, "ar: cannot open %s: %r\n", f.name);
222				ap.copy(bar, mem);
223				continue;
224			}
225			if(uflag){
226				(ok, d) := sys->fstat(dfd);
227				if(ok < 0 || d.mtime <= mem.date){
228					if(ok < 0)
229						sys->fprint(stderr, "ar: cannot stat %s: %r\n", f.name);
230					ap.copy(bar, mem);
231					continue;
232				}
233			}
234			mem.skip(bar);
235			mesg('r', f.name);
236			mem.replace(f.name, dfd);
237			ap.insert(mem);
238			dfd = nil;
239		}
240	}
241	# copy in remaining files named on command line
242	for(i := 0; i < len files; i++){
243		f := files[i];
244		if(f.found)
245			continue;
246		f.found = 1;
247		dfd := sys->open(f.name, Sys->OREAD);
248		if(dfd != nil){
249			mesg('a', f.name);
250			parts[0].insert(Armember.new(f.trimmed, dfd));
251		}else
252			sys->fprint(stderr, "ar: cannot open %s: %r\n", f.name);
253	}
254	if(bar == nil && !cflag)
255		install(arname, parts, 1);	# issue 'creating' msg
256	else
257		install(arname, parts, 0);
258}
259
260dcmd(arname: string, files: array of ref File)
261{
262	if(len files == 0)
263		return;
264	changed := 0;
265	parts = array[] of {Arfile.new()};
266	bar := openar(arname, Sys->ORDWR, 0);
267	while((mem := Armember.rdhdr(bar)) != nil){
268		if(match(files, mem.name) != nil){
269			mesg('d', mem.name);
270			mem.skip(bar);
271			changed = 1;
272		}else
273			parts[0].copy(bar, mem);
274		mem =  nil;	# conserves memory
275	}
276	if(changed)
277		install(arname, parts, 0);
278}
279
280xcmd(arname: string, files: array of ref File)
281{
282	bar := openar(arname, Sys->OREAD, 0);
283	i := 0;
284	while((mem := Armember.rdhdr(bar)) != nil){
285		if((f := match(files, mem.name)) != nil){
286			f.found = 1;
287			fd := sys->create(f.name, Sys->OWRITE, mem.mode & 8r777);
288			if(fd == nil){
289				sys->fprint(stderr, "ar: cannot create %s: %r\n", f.name);
290				mem.skip(bar);
291			}else{
292				mesg('x', f.name);
293				mem.copyout(bar, fd);
294				if(oflag){
295					dx := sys->nulldir;
296					dx.atime = mem.date;
297					dx.mtime = mem.date;
298					if(sys->fwstat(fd, dx) < 0)
299						sys->fprint(stderr, "ar: can't set times on %s: %r", f.name);
300				}
301				fd = nil;
302				mem = nil;
303			}
304			if(len files > 0 && ++i >= len files)
305				break;
306		}else
307			mem.skip(bar);
308	}
309}
310
311pcmd(arname: string, files: array of ref File)
312{
313	bar := openar(arname, Sys->OREAD, 0);
314	i := 0;
315	while((mem := Armember.rdhdr(bar)) != nil){
316		if((f := match(files, mem.name)) != nil){
317			if(vflag)
318				sys->print("\n<%s>\n\n", f.name);
319			mem.copyout(bar, sys->fildes(1));
320			if(len files > 0 && ++i >= len files)
321				break;
322		}else
323			mem.skip(bar);
324		mem = nil;	# we no longer need the contents
325	}
326}
327
328mcmd(arname: string, files: array of ref File)
329{
330	if(len files == 0)
331		return;
332	parts = array[3] of {Arfile.new(), Arfile.new(), nil};
333	bar := openar(arname, Sys->ORDWR, 0);
334	ap := parts[0];
335	while((mem := Armember.rdhdr(bar)) != nil){
336		if(bamatch(mem.name, pivotname))
337			ap = parts[2] = Arfile.new();
338		if((f := match(files, mem.name)) != nil){
339			mesg('m', f.name);
340			parts[1].copy(bar, mem);
341		}else
342			ap.copy(bar, mem);
343	}
344	if(pivotname != nil && parts[2] == nil)
345		sys->fprint(stderr, "ar: %s not found - files moved to end\n", pivotname);
346	install(arname, parts, 0);
347}
348
349tcmd(arname: string, files: array of ref File)
350{
351	bar := openar(arname, Sys->OREAD, 0);
352	while((mem := Armember.rdhdr(bar)) != nil){
353		if((f := match(files, mem.name)) != nil){
354			longls := "";
355			if(vflag)
356				longls = longtext(mem)+" ";
357			bout.puts(longls+f.trimmed+"\n");
358		}
359		mem.skip(bar);
360		mem = nil;
361	}
362}
363
364qcmd(arname: string, files: array of ref File)
365{
366	if(aflag || bflag){
367		sys->fprint(stderr, "ar: abi not allowed with q\n");
368		raise "fail:usage";
369	}
370	fd := openrawar(arname, Sys->ORDWR, 1);
371	if(fd == nil){
372		if(!cflag)
373			sys->fprint(stderr, "ar: creating %s\n", arname);
374		fd = arcreate(arname);
375	}
376	# leave note group behind when writing archive; i.e. sidestep interrupts
377	sys->seek(fd, big 0, 2);	# append
378	for(i := 0; i < len files; i++){
379		f := files[i];
380		f.found = 1;
381		dfd := sys->open(f.name, Sys->OREAD);
382		if(dfd != nil){
383			mesg('q', f.name);
384			mem := Armember.new(f.trimmed, dfd);
385			if(mem != nil){
386				mem.write(fd);
387				mem = nil;
388			}
389		}else
390			sys->fprint(stderr, "ar: cannot open %s: %r\n", f.name);
391	}
392}
393
394#
395# 	open an archive and validate its header
396#
397openrawar(arname: string, mode: int, errok: int): ref Sys->FD
398{
399	fd := sys->open(arname, mode);
400	if(fd == nil){
401		if(!errok){
402			sys->fprint(stderr, "ar: cannot open %s: %r\n", arname);
403			raise "fail:error";
404		}
405		return nil;
406	}
407	mbuf := array[SARMAG] of byte;
408	if(sys->read(fd, mbuf, SARMAG) != SARMAG || string mbuf != ARMAG){
409		sys->fprint(stderr, "ar: %s not in archive format\n", arname);
410		raise "fail:error";
411	}
412	return fd;
413}
414
415openar(arname: string, mode: int, errok: int): ref Iobuf
416{
417	fd := openrawar(arname, mode, errok);
418	if(fd == nil)
419		return nil;
420	bfd := bufio->fopen(fd, mode);
421	bfd.seek(big SARMAG, 0);
422	return bfd;
423}
424
425#
426# 	create an archive and set its header
427#
428arcreate(arname: string): ref Sys->FD
429{
430	fd := sys->create(arname, Sys->OWRITE, 8r666);
431	if(fd == nil){
432		sys->fprint(stderr, "ar: cannot create %s: %r\n", arname);
433		raise "fail:create";
434	}
435	a := array of byte ARMAG;
436	mustwrite(fd, a, len a);
437	return fd;
438}
439
440#
441# 		error handling
442#
443wrerr()
444{
445	sys->fprint(stderr, "ar: write error: %r\n");
446	raise "fail:write error";
447}
448
449rderr()
450{
451	sys->fprint(stderr, "ar: read error: %r\n");
452	raise "fail:read error";
453}
454
455phaseerr(offset: big)
456{
457	sys->fprint(stderr, "ar: phase error at offset %bd\n", offset);
458	raise "fail:phase error";
459}
460
461usage()
462{
463	sys->fprint(stderr, "usage: ar [%s][%s] archive files ...\n", opt, man);
464	raise "fail:usage";
465}
466
467#
468# concatenate the several sequences of members into one archive
469#
470install(arname: string, seqs: array of ref Arfile, createflag: int)
471{
472	# leave process group behind when copying back; i.e. sidestep interrupts
473	sys->pctl(Sys->NEWPGRP, nil);
474
475	if(createflag)
476		sys->fprint(stderr, "ar: creating %s\n", arname);
477	fd := arcreate(arname);
478	for(i := 0; i < len seqs; i++)
479		if((ap := seqs[i]) != nil)
480			ap.stream(fd);
481}
482
483#
484# return the command line File matching a given name
485#
486match(files: array of ref File, file: string): ref File
487{
488	if(len files == 0)
489		return ref File(file, file, 0);	# empty list always matches
490	for(i := 0; i < len files; i++)
491		if(!files[i].found && files[i].trimmed == file){
492			files[i].found = 1;
493			return files[i];
494		}
495	return nil;
496}
497
498#
499# is `file' the pivot member's name and is the archive positioned
500# at the correct point wrt after or before options?  return true if so.
501#
502state := 0;
503
504bamatch(file: string, pivot: string): int
505{
506	case state {
507	0 =>			# looking for position file
508		if(aflag){
509			if(file == pivot)
510				state = 1;
511		}else if(bflag){
512			if(file == pivot){
513				state = 2;	# found
514				return 1;
515			}
516		}
517	1 =>			# found - after previous file
518		state = 2;
519		return 1;
520	2 =>			# already found position file
521		;
522	}
523	return 0;
524}
525
526#
527# output a message, if 'v' option was specified
528#
529mesg(c: int, file: string)
530{
531	if(vflag)
532		bout.puts(sys->sprint("%c - %s\n", c, file));
533}
534
535#
536# return just the file name
537#
538trim(s: string): string
539{
540	for(j := len s; j > 0 && s[j-1] == '/';)
541		j--;
542	k := 0;
543	for(i := 0; i < j; i++)
544		if(s[i] == '/')
545			k = i+1;
546	return s[k: j];
547}
548
549longtext(mem: ref Armember): string
550{
551	s := modes(mem.mode);
552	s += sys->sprint(" %3d/%1d", mem.uid, mem.gid);
553	s += sys->sprint(" %7ud", mem.size);
554	t := daytime->text(daytime->local(mem.date));
555	return s+sys->sprint(" %-12.12s %-4.4s ", t[4:], t[24:]);
556}
557
558mtab := array[] of {
559	"---",	"--x",	"-w-",	"-wx",
560	"r--",	"r-x",	"rw-",	"rwx"
561};
562
563modes(mode: int): string
564{
565	return mtab[(mode>>6)&7]+mtab[(mode>>3)&7]+mtab[mode&7];
566}
567
568#
569# read the header for the next archive contents
570#
571Armember.rdhdr(b: ref Iobuf): ref Armember
572{
573	buf := array[SAR_HDR] of byte;
574	if((n := b.read(buf, len buf)) != len buf){
575		if(n == 0)
576			return nil;
577		if(n > 0)
578			sys->werrstr("unexpected end-of-file");
579		rderr();
580	}
581	mem := ref Armember;
582	for(i := Oname+Lname; i > Oname; i--)
583		if(buf[i-1] != byte '/' && buf[i-1] != byte ' ')
584			break;
585	mem.name = string buf[Oname:i];
586	mem.date = intof(buf[Odate: Odate+Ldate], 10);
587	mem.uid = intof(buf[Ouid: Ouid+Luid], 10);
588	mem.gid = intof(buf[Ogid: Ogid+Lgid], 10);
589	mem.mode = intof(buf[Omode: Omode+Lmode], 8);
590	mem.size = intof(buf[Osize: Osize+Lsize], 10);
591	if(buf[Ofmag] != ARFMAG0 || buf[Ofmag+1] != ARFMAG1)
592		phaseerr(b.offset()-big SAR_HDR);
593	return mem;
594}
595
596intof(a: array of byte, base: int): int
597{
598	for(i := len a; i > 0; i--)
599		if(a[i-1] != byte ' '){
600			a = a[0:i];
601			break;
602		}
603	(n, s) := str->toint(string a, base);
604	if(s != nil){
605		sys->fprint(stderr, "ar: invalid integer in archive member's header: %q\n", string a);
606		raise "fail:error";
607	}
608	return n;
609}
610
611Armember.wrhdr(mem: self ref Armember, fd: ref Sys->FD)
612{
613	b := array[SAR_HDR] of {* => byte ' '};
614	nm := array of byte mem.name;
615	if(len nm > Lname)
616		nm = nm[0:Lname];
617	b[Oname:] = nm;
618	b[Odate:] = sys->aprint("%-12ud", mem.date);
619	b[Ouid:] = sys->aprint("%-6d", 0);
620	b[Ogid:] = sys->aprint("%-6d", 0);
621	b[Omode:] = sys->aprint("%-8uo", mem.mode);
622	b[Osize:] = sys->aprint("%-10ud", mem.size);
623	b[Ofmag] = ARFMAG0;
624	b[Ofmag+1] = ARFMAG1;
625	mustwrite(fd, b, len b);
626}
627
628#
629# make a new member from the given file, with the file's contents
630#
631Armember.new(name: string, fd: ref Sys->FD): ref Armember
632{
633	mem := ref Armember;
634	mem.replace(name, fd);
635	return mem;
636}
637
638#
639# replace the contents  of an existing member
640#
641Armember.replace(mem: self ref Armember, name: string, fd: ref Sys->FD)
642{
643	(ok, d) := sys->fstat(fd);
644	if(ok < 0){
645		sys->fprint(stderr, "ar: cannot stat %s: %r\n", name);
646		raise "fail:no stat";
647	}
648	mem.name = trim(name);
649	mem.date = d.mtime;
650	mem.uid = 0;
651	mem.gid = 0;
652	mem.mode = d.mode & 8r777;
653	mem.size = int d.length;
654	if(big mem.size != d.length){
655		sys->fprint(stderr, "ar: file %s too big\n", name);
656		raise "fail:error";
657	}
658	mem.fd = fd;
659	mem.contents = nil;	# will be copied across from fd when needed
660}
661
662#
663# read the contents of an archive member
664#
665Armember.read(mem: self ref Armember, b: ref Iobuf): int
666{
667	if(mem.contents != nil)
668		return len mem.contents;
669	mem.contents = buffer(mem.size + (mem.size&1));
670	n := b.read(mem.contents, len mem.contents);
671	if(n != len mem.contents){
672		if(n >= 0)
673			sys->werrstr("unexpected end-of-file");
674		rderr();
675	}
676	return n;
677}
678
679mustwrite(fd: ref Sys->FD, buf: array of byte, n: int)
680{
681	if(sys->write(fd, buf, n) != n)
682		wrerr();
683}
684
685#
686# write an archive member to ofd, including header
687#
688Armember.write(mem: self ref Armember, ofd: ref Sys->FD)
689{
690	mem.wrhdr(ofd);
691	if(mem.contents != nil){
692		mustwrite(ofd, mem.contents, len mem.contents);
693		return;
694	}
695	if(mem.fd == nil)
696		raise "ar: write nil fd";
697	buf := array[Sys->ATOMICIO] of byte;	# could be bigger
698	for(nr := mem.size; nr > 0;){
699		n := nr;
700		if(n > len buf)
701			n = len buf;
702		n = sys->read(mem.fd, buf, n);
703		if(n <= 0){
704			if(n == 0)
705				sys->werrstr("unexpected end-of-file");
706			rderr();
707		}
708		mustwrite(ofd, buf, n);
709		nr -= n;
710	}
711	if(mem.size & 1)
712		mustwrite(ofd, array[] of {byte '\n'}, 1);
713}
714
715#
716# seek past the current member's contents in b
717#
718Armember.skip(mem: self ref Armember, b: ref Iobuf)
719{
720	b.seek(big(mem.size + (mem.size&1)), 1);
721}
722
723#
724# copy a member's contents from memory or directly from an archive to another file
725#
726Armember.copyout(mem: self ref Armember, b: ref Iobuf, ofd: ref Sys->FD)
727{
728	if(mem.contents != nil){
729		mustwrite(ofd, mem.contents, len mem.contents);
730		return;
731	}
732	buf := array[Sys->ATOMICIO] of byte;	# could be bigger
733	for(nr := mem.size; nr > 0;){
734		n := nr;
735		if(n > len buf)
736			n = len buf;
737		n = b.read(buf, n);
738		if(n <= 0){
739			if(n == 0)
740				sys->werrstr("unexpected end-of-file");
741			rderr();
742		}
743		mustwrite(ofd, buf, n);
744		nr -= n;
745	}
746	if(mem.size & 1)
747		b.getc();
748}
749
750#
751# 	Temp file I/O subsystem.  We attempt to cache all three temp files in
752# 	core.  When we run out of memory we spill to disk.
753# 	The I/O model assumes that temp files:
754# 		1) are only written on the end
755# 		2) are only read from the beginning
756# 		3) are only read after all writing is complete.
757# 	The architecture uses one control block per temp file.  Each control
758# 	block anchors a chain of buffers, each containing an archive contents.
759#
760Arfile.new(): ref Arfile
761{
762	return ref Arfile;
763}
764
765#
766# copy the contents of mem at b into the temporary
767#
768Arfile.copy(ap: self ref Arfile, b: ref Iobuf, mem: ref Armember)
769{
770	mem.read(b);
771	ap.insert(mem);
772}
773
774#
775#  insert a contents buffer into the contents chain
776#
777Arfile.insert(ap: self ref Arfile, mem: ref Armember)
778{
779	mem.next = nil;
780	if(ap.head == nil)
781		ap.head = mem;
782	else
783		ap.tail.next = mem;
784	ap.tail = mem;
785}
786
787#
788# stream the contents in a temp file to the file referenced by 'fd'.
789#
790Arfile.stream(ap: self ref Arfile, fd: ref Sys->FD)
791{
792	if(ap.fd != nil){		# copy prefix from disk
793		buf := array[Sys->ATOMICIO] of byte;
794		sys->seek(ap.fd, big 0, 0);
795		while((n := sys->read(ap.fd, buf, len buf)) > 0)
796			mustwrite(fd, buf, n);
797		if(n < 0)
798			rderr();
799		ap.fd = nil;
800	}
801	# dump the in-core buffers, which always follow the contents in the temp file
802	for(mem := ap.head; mem != nil; mem = mem.next)
803		mem.write(fd);
804}
805
806#
807# spill a member's contents to disk
808#
809
810totalmem := 0;
811warned := 0;
812tn := 0;
813
814Arfile.page(ap: self ref Arfile): int
815{
816	mem := ap.head;
817	if(ap.fd == nil && !warned){
818		pid := sys->pctl(0, nil);
819		for(i := 0;; i++){
820			name := sys->sprint("/tmp/art%d.%d.%d", pid, tn, i);
821			ap.fd = sys->create(name, Sys->OEXCL | Sys->ORDWR | Sys->ORCLOSE, 8r600);
822			if(ap.fd != nil)
823				break;
824			if(i >= 20){
825				warned =1;
826				sys->fprint(stderr,"ar: warning: can't create temp file %s: %r\n", name);
827				return 0;	# we'll simply use the memory
828			}
829		}
830		tn++;
831	}
832	mem.write(ap.fd);
833	ap.head = mem.next;
834	if(ap.tail == mem)
835		ap.tail = mem.next;
836	totalmem -= len mem.contents;
837	return 1;
838}
839
840#
841# account for the space taken by a contents's contents,
842# pushing earlier contentss to disk to keep the space below a
843# reasonable level
844#
845
846buffer(n: int): array of byte
847{
848Flush:
849	while(totalmem + n > 1024*1024){
850		for(i := 0; i < len parts; i++)
851			if(parts[i] != nil && parts[i].page())
852				continue Flush;
853		break;
854	}
855	totalmem += n;
856	return array[n] of byte;
857}
858