summaryrefslogtreecommitdiff
path: root/appl/cmd/ar.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/ar.b')
-rw-r--r--appl/cmd/ar.b856
1 files changed, 856 insertions, 0 deletions
diff --git a/appl/cmd/ar.b b/appl/cmd/ar.b
new file mode 100644
index 00000000..8ef237b8
--- /dev/null
+++ b/appl/cmd/ar.b
@@ -0,0 +1,856 @@
+implement Ar;
+
+#
+# ar - portable (ascii) format version
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "string.m";
+ str: String;
+
+Ar: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+ARMAG: con "!<arch>\n";
+SARMAG: con len ARMAG;
+ARFMAG0: con byte '`';
+ARFMAG1: con byte '\n';
+SARNAME: con 16; # ancient limit
+
+#
+# printable archive header
+# name[SARNAME] date[12] uid[6] gid[6] mode[8] size[10] fmag[2]
+#
+Oname: con 0;
+Lname: con SARNAME;
+Odate: con Oname+Lname;
+Ldate: con 12;
+Ouid: con Odate+Ldate;
+Luid: con 6;
+Ogid: con Ouid+Luid;
+Lgid: con 6;
+Omode: con Ogid+Lgid;
+Lmode: con 8;
+Osize: con Omode+Lmode;
+Lsize: con 10;
+Ofmag: con Osize+Lsize;
+Lfmag: con 2;
+SAR_HDR: con Ofmag+Lfmag; # 60
+
+#
+# The algorithm uses up to 3 temp files. The "pivot contents" is the
+# archive contents specified by an a, b, or i option. The temp files are
+# astart - contains existing contentss up to and including the pivot contents.
+# amiddle - contains new files moved or inserted behind the pivot.
+# aend - contains the existing contentss that follow the pivot contents.
+# When all contentss have been processed, function 'install' streams the
+# temp files, in order, back into the archive.
+#
+
+Armember: adt { # one per archive contents
+ name: string; # trimmed
+ length: int;
+ date: int;
+ uid: int;
+ gid: int;
+ mode: int;
+ size: int;
+ contents: array of byte;
+ fd: ref Sys->FD; # if contents is nil and fd is not nil, fd has contents
+ next: cyclic ref Armember;
+
+ new: fn(name: string, fd: ref Sys->FD): ref Armember;
+ rdhdr: fn(b: ref Iobuf): ref Armember;
+ read: fn(m: self ref Armember, b: ref Iobuf): int;
+ wrhdr: fn(m: self ref Armember, fd: ref Sys->FD);
+ write: fn(m: self ref Armember, fd: ref Sys->FD);
+ skip: fn(m: self ref Armember, b: ref Iobuf);
+ replace: fn(m: self ref Armember, name: string, fd: ref Sys->FD);
+ copyout: fn(m: self ref Armember, b: ref Iobuf, destfd: ref Sys->FD);
+};
+
+Arfile: adt { # one per tempfile
+ fd: ref Sys->FD; # paging file descriptor, nil if none allocated
+
+ head: ref Armember;
+ tail: ref Armember;
+
+ new: fn(): ref Arfile;
+ copy: fn(ar: self ref Arfile, b: ref Iobuf, mem: ref Armember);
+ insert: fn(ar: self ref Arfile, mem: ref Armember);
+ stream: fn(ar: self ref Arfile, fd: ref Sys->FD);
+ page: fn(ar: self ref Arfile): int;
+};
+
+File: adt {
+ name: string;
+ trimmed: string;
+ found: int;
+};
+
+man := "mrxtdpq";
+opt := "uvnbailo";
+
+aflag := 0;
+bflag := 0;
+cflag := 0;
+oflag := 0;
+uflag := 0;
+vflag := 0;
+
+pivotname: string;
+bout: ref Iobuf;
+stderr: ref Sys->FD;
+parts: array of ref Arfile;
+
+comfun: ref fn(a: string, f: array of ref File);
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ daytime = load Daytime Daytime->PATH;
+ str = load String String->PATH;
+
+ stderr = sys->fildes(2);
+ bout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
+ if(len args < 3)
+ usage();
+ args = tl args;
+ s := hd args; args = tl args;
+ for(i := 0; i < len s; i++){
+ case s[i] {
+ 'a' => aflag = 1;
+ 'b' => bflag = 1;
+ 'c' => cflag = 1;
+ 'd' => setcom(dcmd);
+ 'i' => bflag = 1;
+ 'l' => ; # ignored
+ 'm' => setcom(mcmd);
+ 'o' => oflag = 1;
+ 'p' => setcom(pcmd);
+ 'q' => setcom(qcmd);
+ 'r' => setcom(rcmd);
+ 't' => setcom(tcmd);
+ 'u' => uflag = 1;
+ 'v' => vflag = 1;
+ 'x' => setcom(xcmd);
+ * =>
+ sys->fprint(stderr, "ar: bad option `%c'\n", s[i]);
+ usage();
+ }
+ }
+ if(aflag && bflag){
+ sys->fprint(stderr, "ar: only one of 'a' and 'b' can be specified\n");
+ usage();
+ }
+ if(aflag || bflag){
+ pivotname = trim(hd args); args = tl args;
+ if(len args < 2)
+ usage();
+ }
+ if(comfun == nil){
+ if(uflag == 0){
+ sys->fprint(stderr, "ar: one of [%s] must be specified\n", man);
+ usage();
+ }
+ setcom(rcmd);
+ }
+ cp := hd args; args = tl args;
+ files := array[len args] of ref File;
+ for(i = 0; args != nil; args = tl args)
+ files[i++] = ref File(hd args, trim(hd args), 0);
+ comfun(cp, files); # do the command
+ for(i = 0; i < len files; i++)
+ if(!files[i].found){
+ sys->fprint(stderr, "ar: %s not found\n", files[i].name);
+ cp = "error";
+ }
+ bout.flush();
+ if(cp != nil)
+ raise "fail:"+cp;
+}
+
+#
+# select a command
+#
+setcom(fun: ref fn(s: string, f: array of ref File))
+{
+ if(comfun != nil){
+ sys->fprint(stderr, "ar: only one of [%s] allowed\n", man);
+ usage();
+ }
+ comfun = fun;
+}
+
+#
+# perform the 'r' and 'u' commands
+#
+rcmd(arname: string, files: array of ref File)
+{
+ bar := openar(arname, Sys->ORDWR, 1);
+ parts = array[2] of {Arfile.new(), nil};
+ ap := parts[0];
+ if(bar != nil){
+ while((mem := Armember.rdhdr(bar)) != nil){
+ if(bamatch(mem.name, pivotname)) # check for pivot
+ ap = parts[1] = Arfile.new();
+ f := match(files, mem.name);
+ if(f == nil){
+ ap.copy(bar, mem);
+ continue;
+ }
+ f.found = 1;
+ dfd := sys->open(f.name, Sys->OREAD);
+ if(dfd == nil){
+ if(len files > 0)
+ sys->fprint(stderr, "ar: cannot open %s: %r\n", f.name);
+ ap.copy(bar, mem);
+ continue;
+ }
+ if(uflag){
+ (ok, d) := sys->fstat(dfd);
+ if(ok < 0 || d.mtime <= mem.date){
+ if(ok < 0)
+ sys->fprint(stderr, "ar: cannot stat %s: %r\n", f.name);
+ ap.copy(bar, mem);
+ continue;
+ }
+ }
+ mem.skip(bar);
+ mesg('r', f.name);
+ mem.replace(f.name, dfd);
+ ap.insert(mem);
+ dfd = nil;
+ }
+ }
+ # copy in remaining files named on command line
+ for(i := 0; i < len files; i++){
+ f := files[i];
+ if(f.found)
+ continue;
+ f.found = 1;
+ dfd := sys->open(f.name, Sys->OREAD);
+ if(dfd != nil){
+ mesg('a', f.name);
+ parts[0].insert(Armember.new(f.trimmed, dfd));
+ }else
+ sys->fprint(stderr, "ar: cannot open %s: %r\n", f.name);
+ }
+ if(bar == nil && !cflag)
+ install(arname, parts, 1); # issue 'creating' msg
+ else
+ install(arname, parts, 0);
+}
+
+dcmd(arname: string, files: array of ref File)
+{
+ if(len files == 0)
+ return;
+ changed := 0;
+ parts = array[] of {Arfile.new()};
+ bar := openar(arname, Sys->ORDWR, 0);
+ while((mem := Armember.rdhdr(bar)) != nil){
+ if(match(files, mem.name) != nil){
+ mesg('d', mem.name);
+ mem.skip(bar);
+ changed = 1;
+ }else
+ parts[0].copy(bar, mem);
+ mem = nil; # conserves memory
+ }
+ if(changed)
+ install(arname, parts, 0);
+}
+
+xcmd(arname: string, files: array of ref File)
+{
+ bar := openar(arname, Sys->OREAD, 0);
+ i := 0;
+ while((mem := Armember.rdhdr(bar)) != nil){
+ if((f := match(files, mem.name)) != nil){
+ f.found = 1;
+ fd := sys->create(f.name, Sys->OWRITE, mem.mode & 8r777);
+ if(fd == nil){
+ sys->fprint(stderr, "ar: cannot create %s: %r\n", f.name);
+ mem.skip(bar);
+ }else{
+ mesg('x', f.name);
+ mem.copyout(bar, fd);
+ if(oflag){
+ dx := sys->nulldir;
+ dx.atime = mem.date;
+ dx.mtime = mem.date;
+ if(sys->fwstat(fd, dx) < 0)
+ sys->fprint(stderr, "ar: can't set times on %s: %r", f.name);
+ }
+ fd = nil;
+ mem = nil;
+ }
+ if(len files > 0 && ++i >= len files)
+ break;
+ }else
+ mem.skip(bar);
+ }
+}
+
+pcmd(arname: string, files: array of ref File)
+{
+ bar := openar(arname, Sys->OREAD, 0);
+ i := 0;
+ while((mem := Armember.rdhdr(bar)) != nil){
+ if((f := match(files, mem.name)) != nil){
+ if(vflag)
+ sys->print("\n<%s>\n\n", f.name);
+ mem.copyout(bar, sys->fildes(1));
+ if(len files > 0 && ++i >= len files)
+ break;
+ }else
+ mem.skip(bar);
+ mem = nil; # we no longer need the contents
+ }
+}
+
+mcmd(arname: string, files: array of ref File)
+{
+ if(len files == 0)
+ return;
+ parts = array[3] of {Arfile.new(), Arfile.new(), nil};
+ bar := openar(arname, Sys->ORDWR, 0);
+ ap := parts[0];
+ while((mem := Armember.rdhdr(bar)) != nil){
+ if(bamatch(mem.name, pivotname))
+ ap = parts[2] = Arfile.new();
+ if((f := match(files, mem.name)) != nil){
+ mesg('m', f.name);
+ parts[1].copy(bar, mem);
+ }else
+ ap.copy(bar, mem);
+ }
+ if(pivotname != nil && parts[2] == nil)
+ sys->fprint(stderr, "ar: %s not found - files moved to end\n", pivotname);
+ install(arname, parts, 0);
+}
+
+tcmd(arname: string, files: array of ref File)
+{
+ bar := openar(arname, Sys->OREAD, 0);
+ while((mem := Armember.rdhdr(bar)) != nil){
+ if((f := match(files, mem.name)) != nil){
+ longls := "";
+ if(vflag)
+ longls = longtext(mem)+" ";
+ bout.puts(longls+f.trimmed+"\n");
+ }
+ mem.skip(bar);
+ mem = nil;
+ }
+}
+
+qcmd(arname: string, files: array of ref File)
+{
+ if(aflag || bflag){
+ sys->fprint(stderr, "ar: abi not allowed with q\n");
+ raise "fail:usage";
+ }
+ fd := openrawar(arname, Sys->ORDWR, 1);
+ if(fd == nil){
+ if(!cflag)
+ sys->fprint(stderr, "ar: creating %s\n", arname);
+ fd = arcreate(arname);
+ }
+ # leave note group behind when writing archive; i.e. sidestep interrupts
+ sys->seek(fd, big 0, 2); # append
+ for(i := 0; i < len files; i++){
+ f := files[i];
+ f.found = 1;
+ dfd := sys->open(f.name, Sys->OREAD);
+ if(dfd != nil){
+ mesg('q', f.name);
+ mem := Armember.new(f.trimmed, dfd);
+ if(mem != nil){
+ mem.write(fd);
+ mem = nil;
+ }
+ }else
+ sys->fprint(stderr, "ar: cannot open %s: %r\n", f.name);
+ }
+}
+
+#
+# open an archive and validate its header
+#
+openrawar(arname: string, mode: int, errok: int): ref Sys->FD
+{
+ fd := sys->open(arname, mode);
+ if(fd == nil){
+ if(!errok){
+ sys->fprint(stderr, "ar: cannot open %s: %r\n", arname);
+ raise "fail:error";
+ }
+ return nil;
+ }
+ mbuf := array[SARMAG] of byte;
+ if(sys->read(fd, mbuf, SARMAG) != SARMAG || string mbuf != ARMAG){
+ sys->fprint(stderr, "ar: %s not in archive format\n", arname);
+ raise "fail:error";
+ }
+ return fd;
+}
+
+openar(arname: string, mode: int, errok: int): ref Iobuf
+{
+ fd := openrawar(arname, mode, errok);
+ if(fd == nil)
+ return nil;
+ bfd := bufio->fopen(fd, mode);
+ bfd.seek(big SARMAG, 0);
+ return bfd;
+}
+
+#
+# create an archive and set its header
+#
+arcreate(arname: string): ref Sys->FD
+{
+ fd := sys->create(arname, Sys->OWRITE, 8r666);
+ if(fd == nil){
+ sys->fprint(stderr, "ar: cannot create %s: %r\n", arname);
+ raise "fail:create";
+ }
+ a := array of byte ARMAG;
+ mustwrite(fd, a, len a);
+ return fd;
+}
+
+#
+# error handling
+#
+wrerr()
+{
+ sys->fprint(stderr, "ar: write error: %r\n");
+ raise "fail:write error";
+}
+
+rderr()
+{
+ sys->fprint(stderr, "ar: read error: %r\n");
+ raise "fail:read error";
+}
+
+phaseerr(offset: big)
+{
+ sys->fprint(stderr, "ar: phase error at offset %bd\n", offset);
+ raise "fail:phase error";
+}
+
+usage()
+{
+ sys->fprint(stderr, "usage: ar [%s][%s] archive files ...\n", opt, man);
+ raise "fail:usage";
+}
+
+#
+# concatenate the several sequences of members into one archive
+#
+install(arname: string, seqs: array of ref Arfile, createflag: int)
+{
+ # leave process group behind when copying back; i.e. sidestep interrupts
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ if(createflag)
+ sys->fprint(stderr, "ar: creating %s\n", arname);
+ fd := arcreate(arname);
+ for(i := 0; i < len seqs; i++)
+ if((ap := seqs[i]) != nil)
+ ap.stream(fd);
+}
+
+#
+# return the command line File matching a given name
+#
+match(files: array of ref File, file: string): ref File
+{
+ if(len files == 0)
+ return ref File(file, file, 0); # empty list always matches
+ for(i := 0; i < len files; i++)
+ if(!files[i].found && files[i].trimmed == file){
+ files[i].found = 1;
+ return files[i];
+ }
+ return nil;
+}
+
+#
+# is `file' the pivot member's name and is the archive positioned
+# at the correct point wrt after or before options? return true if so.
+#
+state := 0;
+
+bamatch(file: string, pivot: string): int
+{
+ case state {
+ 0 => # looking for position file
+ if(aflag){
+ if(file == pivot)
+ state = 1;
+ }else if(bflag){
+ if(file == pivot){
+ state = 2; # found
+ return 1;
+ }
+ }
+ 1 => # found - after previous file
+ state = 2;
+ return 1;
+ 2 => # already found position file
+ ;
+ }
+ return 0;
+}
+
+#
+# output a message, if 'v' option was specified
+#
+mesg(c: int, file: string)
+{
+ if(vflag)
+ bout.puts(sys->sprint("%c - %s\n", c, file));
+}
+
+#
+# return just the file name
+#
+trim(s: string): string
+{
+ for(j := len s; j > 0 && s[j-1] == '/';)
+ j--;
+ k := 0;
+ for(i := 0; i < j; i++)
+ if(s[i] == '/')
+ k = i+1;
+ return s[k: j];
+}
+
+longtext(mem: ref Armember): string
+{
+ s := modes(mem.mode);
+ s += sys->sprint(" %3d/%1d", mem.uid, mem.gid);
+ s += sys->sprint(" %7ud", mem.size);
+ t := daytime->text(daytime->local(mem.date));
+ return s+sys->sprint(" %-12.12s %-4.4s ", t[4:], t[24:]);
+}
+
+mtab := array[] of {
+ "---", "--x", "-w-", "-wx",
+ "r--", "r-x", "rw-", "rwx"
+};
+
+modes(mode: int): string
+{
+ return mtab[(mode>>6)&7]+mtab[(mode>>3)&7]+mtab[mode&7];
+}
+
+#
+# read the header for the next archive contents
+#
+Armember.rdhdr(b: ref Iobuf): ref Armember
+{
+ buf := array[SAR_HDR] of byte;
+ if((n := b.read(buf, len buf)) != len buf){
+ if(n == 0)
+ return nil;
+ if(n > 0)
+ sys->werrstr("unexpected end-of-file");
+ rderr();
+ }
+ mem := ref Armember;
+ for(i := Oname+Lname; i > Oname; i--)
+ if(buf[i-1] != byte '/' && buf[i-1] != byte ' ')
+ break;
+ mem.name = string buf[Oname:i];
+ mem.date = intof(buf[Odate: Odate+Ldate], 10);
+ mem.uid = intof(buf[Ouid: Ouid+Luid], 10);
+ mem.gid = intof(buf[Ogid: Ogid+Lgid], 10);
+ mem.mode = intof(buf[Omode: Omode+Lmode], 8);
+ mem.size = intof(buf[Osize: Osize+Lsize], 10);
+ if(buf[Ofmag] != ARFMAG0 || buf[Ofmag+1] != ARFMAG1)
+ phaseerr(b.offset()-big SAR_HDR);
+ return mem;
+}
+
+intof(a: array of byte, base: int): int
+{
+ for(i := len a; i > 0; i--)
+ if(a[i-1] != byte ' '){
+ a = a[0:i];
+ break;
+ }
+ (n, s) := str->toint(string a, base);
+ if(s != nil){
+ sys->fprint(stderr, "ar: invalid integer in archive member's header: %q\n", string a);
+ raise "fail:error";
+ }
+ return n;
+}
+
+Armember.wrhdr(mem: self ref Armember, fd: ref Sys->FD)
+{
+ b := array[SAR_HDR] of {* => byte ' '};
+ nm := array of byte mem.name;
+ if(len nm > Lname)
+ nm = nm[0:Lname];
+ b[Oname:] = nm;
+ b[Odate:] = sys->aprint("%-12ud", mem.date);
+ b[Ouid:] = sys->aprint("%-6d", 0);
+ b[Ogid:] = sys->aprint("%-6d", 0);
+ b[Omode:] = sys->aprint("%-8uo", mem.mode);
+ b[Osize:] = sys->aprint("%-10ud", mem.size);
+ b[Ofmag] = ARFMAG0;
+ b[Ofmag+1] = ARFMAG1;
+ mustwrite(fd, b, len b);
+}
+
+#
+# make a new member from the given file, with the file's contents
+#
+Armember.new(name: string, fd: ref Sys->FD): ref Armember
+{
+ mem := ref Armember;
+ mem.replace(name, fd);
+ return mem;
+}
+
+#
+# replace the contents of an existing member
+#
+Armember.replace(mem: self ref Armember, name: string, fd: ref Sys->FD)
+{
+ (ok, d) := sys->fstat(fd);
+ if(ok < 0){
+ sys->fprint(stderr, "ar: cannot stat %s: %r\n", name);
+ raise "fail:no stat";
+ }
+ mem.name = trim(name);
+ mem.date = d.mtime;
+ mem.uid = 0;
+ mem.gid = 0;
+ mem.mode = d.mode & 8r777;
+ mem.size = int d.length;
+ if(big mem.size != d.length){
+ sys->fprint(stderr, "ar: file %s too big\n", name);
+ raise "fail:error";
+ }
+ mem.fd = fd;
+ mem.contents = nil; # will be copied across from fd when needed
+}
+
+#
+# read the contents of an archive member
+#
+Armember.read(mem: self ref Armember, b: ref Iobuf): int
+{
+ if(mem.contents != nil)
+ return len mem.contents;
+ mem.contents = buffer(mem.size + (mem.size&1));
+ n := b.read(mem.contents, len mem.contents);
+ if(n != len mem.contents){
+ if(n >= 0)
+ sys->werrstr("unexpected end-of-file");
+ rderr();
+ }
+ return n;
+}
+
+mustwrite(fd: ref Sys->FD, buf: array of byte, n: int)
+{
+ if(sys->write(fd, buf, n) != n)
+ wrerr();
+}
+
+#
+# write an archive member to ofd, including header
+#
+Armember.write(mem: self ref Armember, ofd: ref Sys->FD)
+{
+ mem.wrhdr(ofd);
+ if(mem.contents != nil){
+ mustwrite(ofd, mem.contents, len mem.contents);
+ return;
+ }
+ if(mem.fd == nil)
+ raise "ar: write nil fd";
+ buf := array[Sys->ATOMICIO] of byte; # could be bigger
+ for(nr := mem.size; nr > 0;){
+ n := nr;
+ if(n > len buf)
+ n = len buf;
+ n = sys->read(mem.fd, buf, n);
+ if(n <= 0){
+ if(n == 0)
+ sys->werrstr("unexpected end-of-file");
+ rderr();
+ }
+ mustwrite(ofd, buf, n);
+ nr -= n;
+ }
+ if(mem.size & 1)
+ mustwrite(ofd, array[] of {byte '\n'}, 1);
+}
+
+#
+# seek past the current member's contents in b
+#
+Armember.skip(mem: self ref Armember, b: ref Iobuf)
+{
+ b.seek(big(mem.size + (mem.size&1)), 1);
+}
+
+#
+# copy a member's contents from memory or directly from an archive to another file
+#
+Armember.copyout(mem: self ref Armember, b: ref Iobuf, ofd: ref Sys->FD)
+{
+ if(mem.contents != nil){
+ mustwrite(ofd, mem.contents, len mem.contents);
+ return;
+ }
+ buf := array[Sys->ATOMICIO] of byte; # could be bigger
+ for(nr := mem.size; nr > 0;){
+ n := nr;
+ if(n > len buf)
+ n = len buf;
+ n = b.read(buf, n);
+ if(n <= 0){
+ if(n == 0)
+ sys->werrstr("unexpected end-of-file");
+ rderr();
+ }
+ mustwrite(ofd, buf, n);
+ nr -= n;
+ }
+ if(mem.size & 1)
+ b.getc();
+}
+
+#
+# Temp file I/O subsystem. We attempt to cache all three temp files in
+# core. When we run out of memory we spill to disk.
+# The I/O model assumes that temp files:
+# 1) are only written on the end
+# 2) are only read from the beginning
+# 3) are only read after all writing is complete.
+# The architecture uses one control block per temp file. Each control
+# block anchors a chain of buffers, each containing an archive contents.
+#
+Arfile.new(): ref Arfile
+{
+ return ref Arfile;
+}
+
+#
+# copy the contents of mem at b into the temporary
+#
+Arfile.copy(ap: self ref Arfile, b: ref Iobuf, mem: ref Armember)
+{
+ mem.read(b);
+ ap.insert(mem);
+}
+
+#
+# insert a contents buffer into the contents chain
+#
+Arfile.insert(ap: self ref Arfile, mem: ref Armember)
+{
+ mem.next = nil;
+ if(ap.head == nil)
+ ap.head = mem;
+ else
+ ap.tail.next = mem;
+ ap.tail = mem;
+}
+
+#
+# stream the contents in a temp file to the file referenced by 'fd'.
+#
+Arfile.stream(ap: self ref Arfile, fd: ref Sys->FD)
+{
+ if(ap.fd != nil){ # copy prefix from disk
+ buf := array[Sys->ATOMICIO] of byte;
+ sys->seek(ap.fd, big 0, 0);
+ while((n := sys->read(ap.fd, buf, len buf)) > 0)
+ mustwrite(fd, buf, n);
+ if(n < 0)
+ rderr();
+ ap.fd = nil;
+ }
+ # dump the in-core buffers, which always follow the contents in the temp file
+ for(mem := ap.head; mem != nil; mem = mem.next)
+ mem.write(fd);
+}
+
+#
+# spill a member's contents to disk
+#
+
+totalmem := 0;
+warned := 0;
+tn := 0;
+
+Arfile.page(ap: self ref Arfile): int
+{
+ mem := ap.head;
+ if(ap.fd == nil && !warned){
+ pid := sys->pctl(0, nil);
+ for(i := 0;; i++){
+ name := sys->sprint("/tmp/art%d.%d.%d", pid, tn, i);
+ ap.fd = sys->create(name, Sys->OEXCL | Sys->ORDWR | Sys->ORCLOSE, 8r600);
+ if(ap.fd != nil)
+ break;
+ if(i >= 20){
+ warned =1;
+ sys->fprint(stderr,"ar: warning: can't create temp file %s: %r\n", name);
+ return 0; # we'll simply use the memory
+ }
+ }
+ tn++;
+ }
+ mem.write(ap.fd);
+ ap.head = mem.next;
+ if(ap.tail == mem)
+ ap.tail = mem.next;
+ totalmem -= len mem.contents;
+ return 1;
+}
+
+#
+# account for the space taken by a contents's contents,
+# pushing earlier contentss to disk to keep the space below a
+# reasonable level
+#
+
+buffer(n: int): array of byte
+{
+Flush:
+ while(totalmem + n > 1024*1024){
+ for(i := 0; i < len parts; i++)
+ if(parts[i] != nil && parts[i].page())
+ continue Flush;
+ break;
+ }
+ totalmem += n;
+ return array[n] of byte;
+}