| #include "sam.h" |
| |
| Rune genbuf[BLOCKSIZE]; |
| int io; |
| int panicking; |
| int rescuing; |
| String genstr; |
| String rhs; |
| String curwd; |
| String cmdstr; |
| Rune empty[] = { 0 }; |
| char *genc; |
| File *curfile; |
| File *flist; |
| File *cmd; |
| jmp_buf mainloop; |
| List tempfile = { 'p' }; |
| int quitok = TRUE; |
| int downloaded; |
| int dflag; |
| int Rflag; |
| char *machine; |
| char *home; |
| int bpipeok; |
| int termlocked; |
| char *samterm = SAMTERM; |
| char *rsamname = RSAM; |
| File *lastfile; |
| Disk *disk; |
| long seq; |
| |
| char *winsize; |
| |
| Rune baddir[] = { '<', 'b', 'a', 'd', 'd', 'i', 'r', '>', '\n'}; |
| |
| void usage(void); |
| |
| extern int notify(void(*)(void*,char*)); |
| |
| void |
| main(int _argc, char **_argv) |
| { |
| volatile int i, argc; |
| char **volatile argv; |
| String *t; |
| char *termargs[10], **ap; |
| |
| argc = _argc; |
| argv = _argv; |
| ap = termargs; |
| *ap++ = "samterm"; |
| ARGBEGIN{ |
| case 'd': |
| dflag++; |
| break; |
| case 'r': |
| machine = EARGF(usage()); |
| break; |
| case 'R': |
| Rflag++; |
| break; |
| case 't': |
| samterm = EARGF(usage()); |
| break; |
| case 's': |
| rsamname = EARGF(usage()); |
| break; |
| default: |
| dprint("sam: unknown flag %c\n", ARGC()); |
| usage(); |
| /* options for samterm */ |
| case 'a': |
| *ap++ = "-a"; |
| break; |
| case 'W': |
| *ap++ = "-W"; |
| *ap++ = EARGF(usage()); |
| break; |
| }ARGEND |
| *ap = nil; |
| |
| Strinit(&cmdstr); |
| Strinit0(&lastpat); |
| Strinit0(&lastregexp); |
| Strinit0(&genstr); |
| Strinit0(&rhs); |
| Strinit0(&curwd); |
| Strinit0(&plan9cmd); |
| home = getenv(HOME); |
| disk = diskinit(); |
| if(home == 0) |
| home = "/"; |
| if(!dflag) |
| startup(machine, Rflag, termargs, (char**)argv); |
| notify(notifyf); |
| getcurwd(); |
| if(argc>0){ |
| for(i=0; i<argc; i++){ |
| if(!setjmp(mainloop)){ |
| t = tmpcstr(argv[i]); |
| Straddc(t, '\0'); |
| Strduplstr(&genstr, t); |
| freetmpstr(t); |
| fixname(&genstr); |
| logsetname(newfile(), &genstr); |
| } |
| } |
| }else if(!downloaded) |
| newfile(); |
| seq++; |
| if(file.nused) |
| current(file.filepptr[0]); |
| setjmp(mainloop); |
| cmdloop(); |
| trytoquit(); /* if we already q'ed, quitok will be TRUE */ |
| exits(0); |
| } |
| |
| void |
| usage(void) |
| { |
| dprint("usage: sam [-d] [-t samterm] [-s sam name] [-r machine] [file ...]\n"); |
| exits("usage"); |
| } |
| |
| void |
| rescue(void) |
| { |
| int i, nblank = 0; |
| File *f; |
| char *c; |
| char buf[256]; |
| char *root; |
| |
| if(rescuing++) |
| return; |
| io = -1; |
| for(i=0; i<file.nused; i++){ |
| f = file.filepptr[i]; |
| if(f==cmd || f->b.nc==0 || !fileisdirty(f)) |
| continue; |
| if(io == -1){ |
| sprint(buf, "%s/sam.save", home); |
| io = create(buf, 1, 0777); |
| if(io<0) |
| return; |
| } |
| if(f->name.s[0]){ |
| c = Strtoc(&f->name); |
| strncpy(buf, c, sizeof buf-1); |
| buf[sizeof buf-1] = 0; |
| free(c); |
| }else |
| sprint(buf, "nameless.%d", nblank++); |
| root = getenv("PLAN9"); |
| if(root == nil) |
| root = "/usr/local/plan9"; |
| fprint(io, "#!/bin/sh\n%s/bin/samsave '%s' $* <<'---%s'\n", root, buf, buf); |
| addr.r.p1 = 0, addr.r.p2 = f->b.nc; |
| writeio(f); |
| fprint(io, "\n---%s\n", (char *)buf); |
| } |
| } |
| |
| void |
| panic(char *s) |
| { |
| int wasd; |
| |
| if(!panicking++ && !setjmp(mainloop)){ |
| wasd = downloaded; |
| downloaded = 0; |
| dprint("sam: panic: %s: %r\n", s); |
| if(wasd) |
| fprint(2, "sam: panic: %s: %r\n", s); |
| rescue(); |
| abort(); |
| } |
| } |
| |
| void |
| hiccough(char *s) |
| { |
| File *f; |
| int i; |
| |
| if(rescuing) |
| exits("rescue"); |
| if(s) |
| dprint("%s\n", s); |
| resetcmd(); |
| resetxec(); |
| resetsys(); |
| if(io > 0) |
| close(io); |
| |
| /* |
| * back out any logged changes & restore old sequences |
| */ |
| for(i=0; i<file.nused; i++){ |
| f = file.filepptr[i]; |
| if(f==cmd) |
| continue; |
| if(f->seq==seq){ |
| bufdelete(&f->epsilon, 0, f->epsilon.nc); |
| f->seq = f->prevseq; |
| f->dot.r = f->prevdot; |
| f->mark = f->prevmark; |
| state(f, f->prevmod ? Dirty: Clean); |
| } |
| } |
| |
| update(); |
| if (curfile) { |
| if (curfile->unread) |
| curfile->unread = FALSE; |
| else if (downloaded) |
| outTs(Hcurrent, curfile->tag); |
| } |
| longjmp(mainloop, 1); |
| } |
| |
| void |
| intr(void) |
| { |
| error(Eintr); |
| } |
| |
| void |
| trytoclose(File *f) |
| { |
| char *t; |
| char buf[256]; |
| |
| if(f == cmd) /* possible? */ |
| return; |
| if(f->deleted) |
| return; |
| if(fileisdirty(f) && !f->closeok){ |
| f->closeok = TRUE; |
| if(f->name.s[0]){ |
| t = Strtoc(&f->name); |
| strncpy(buf, t, sizeof buf-1); |
| free(t); |
| }else |
| strcpy(buf, "nameless file"); |
| error_s(Emodified, buf); |
| } |
| f->deleted = TRUE; |
| } |
| |
| void |
| trytoquit(void) |
| { |
| int c; |
| File *f; |
| |
| if(!quitok){ |
| for(c = 0; c<file.nused; c++){ |
| f = file.filepptr[c]; |
| if(f!=cmd && fileisdirty(f)){ |
| quitok = TRUE; |
| eof = FALSE; |
| error(Echanges); |
| } |
| } |
| } |
| } |
| |
| void |
| load(File *f) |
| { |
| Address saveaddr; |
| |
| Strduplstr(&genstr, &f->name); |
| filename(f); |
| if(f->name.s[0]){ |
| saveaddr = addr; |
| edit(f, 'I'); |
| addr = saveaddr; |
| }else{ |
| f->unread = 0; |
| f->cleanseq = f->seq; |
| } |
| |
| fileupdate(f, TRUE, TRUE); |
| } |
| |
| void |
| cmdupdate(void) |
| { |
| if(cmd && cmd->seq!=0){ |
| fileupdate(cmd, FALSE, downloaded); |
| cmd->dot.r.p1 = cmd->dot.r.p2 = cmd->b.nc; |
| telldot(cmd); |
| } |
| } |
| |
| void |
| delete(File *f) |
| { |
| if(downloaded && f->rasp) |
| outTs(Hclose, f->tag); |
| delfile(f); |
| if(f == curfile) |
| current(0); |
| } |
| |
| void |
| update(void) |
| { |
| int i, anymod; |
| File *f; |
| |
| settempfile(); |
| for(anymod = i=0; i<tempfile.nused; i++){ |
| f = tempfile.filepptr[i]; |
| if(f==cmd) /* cmd gets done in main() */ |
| continue; |
| if(f->deleted) { |
| delete(f); |
| continue; |
| } |
| if(f->seq==seq && fileupdate(f, FALSE, downloaded)) |
| anymod++; |
| if(f->rasp) |
| telldot(f); |
| } |
| if(anymod) |
| seq++; |
| } |
| |
| File * |
| current(File *f) |
| { |
| return curfile = f; |
| } |
| |
| void |
| edit(File *f, int cmd) |
| { |
| int empty = TRUE; |
| Posn p; |
| int nulls; |
| |
| if(cmd == 'r') |
| logdelete(f, addr.r.p1, addr.r.p2); |
| if(cmd=='e' || cmd=='I'){ |
| logdelete(f, (Posn)0, f->b.nc); |
| addr.r.p2 = f->b.nc; |
| }else if(f->b.nc!=0 || (f->name.s[0] && Strcmp(&genstr, &f->name)!=0)) |
| empty = FALSE; |
| if((io = open(genc, OREAD))<0) { |
| if (curfile && curfile->unread) |
| curfile->unread = FALSE; |
| error_r(Eopen, genc); |
| } |
| p = readio(f, &nulls, empty, TRUE); |
| closeio((cmd=='e' || cmd=='I')? -1 : p); |
| if(cmd == 'r') |
| f->ndot.r.p1 = addr.r.p2, f->ndot.r.p2 = addr.r.p2+p; |
| else |
| f->ndot.r.p1 = f->ndot.r.p2 = 0; |
| f->closeok = empty; |
| if (quitok) |
| quitok = empty; |
| else |
| quitok = FALSE; |
| state(f, empty && !nulls? Clean : Dirty); |
| if(empty && !nulls) |
| f->cleanseq = f->seq; |
| if(cmd == 'e') |
| filename(f); |
| } |
| |
| int |
| getname(File *f, String *s, int save) |
| { |
| int c, i; |
| |
| Strzero(&genstr); |
| if(genc){ |
| free(genc); |
| genc = 0; |
| } |
| if(s==0 || (c = s->s[0])==0){ /* no name provided */ |
| if(f) |
| Strduplstr(&genstr, &f->name); |
| goto Return; |
| } |
| if(c!=' ' && c!='\t') |
| error(Eblank); |
| for(i=0; (c=s->s[i])==' ' || c=='\t'; i++) |
| ; |
| while(s->s[i] > ' ') |
| Straddc(&genstr, s->s[i++]); |
| if(s->s[i]) |
| error(Enewline); |
| fixname(&genstr); |
| if(f && (save || f->name.s[0]==0)){ |
| logsetname(f, &genstr); |
| if(Strcmp(&f->name, &genstr)){ |
| quitok = f->closeok = FALSE; |
| f->qidpath = 0; |
| f->mtime = 0; |
| state(f, Dirty); /* if it's 'e', fix later */ |
| } |
| } |
| Return: |
| genc = Strtoc(&genstr); |
| i = genstr.n; |
| if(i && genstr.s[i-1]==0) |
| i--; |
| return i; /* strlen(name) */ |
| } |
| |
| void |
| filename(File *f) |
| { |
| if(genc) |
| free(genc); |
| genc = Strtoc(&genstr); |
| dprint("%c%c%c %s\n", " '"[f->mod], |
| "-+"[f->rasp!=0], " ."[f==curfile], genc); |
| } |
| |
| void |
| undostep(File *f, int isundo) |
| { |
| uint p1, p2; |
| int mod; |
| |
| mod = f->mod; |
| fileundo(f, isundo, 1, &p1, &p2, TRUE); |
| f->ndot = f->dot; |
| if(f->mod){ |
| f->closeok = 0; |
| quitok = 0; |
| }else |
| f->closeok = 1; |
| |
| if(f->mod != mod){ |
| f->mod = mod; |
| if(mod) |
| mod = Clean; |
| else |
| mod = Dirty; |
| state(f, mod); |
| } |
| } |
| |
| int |
| undo(int isundo) |
| { |
| File *f; |
| int i; |
| Mod max; |
| |
| max = undoseq(curfile, isundo); |
| if(max == 0) |
| return 0; |
| settempfile(); |
| for(i = 0; i<tempfile.nused; i++){ |
| f = tempfile.filepptr[i]; |
| if(f!=cmd && undoseq(f, isundo)==max) |
| undostep(f, isundo); |
| } |
| return 1; |
| } |
| |
| int |
| readcmd(String *s) |
| { |
| int retcode; |
| |
| if(flist != 0) |
| fileclose(flist); |
| flist = fileopen(); |
| |
| addr.r.p1 = 0, addr.r.p2 = flist->b.nc; |
| retcode = plan9(flist, '<', s, FALSE); |
| fileupdate(flist, FALSE, FALSE); |
| flist->seq = 0; |
| if (flist->b.nc > BLOCKSIZE) |
| error(Etoolong); |
| Strzero(&genstr); |
| Strinsure(&genstr, flist->b.nc); |
| bufread(&flist->b, (Posn)0, genbuf, flist->b.nc); |
| memmove(genstr.s, genbuf, flist->b.nc*RUNESIZE); |
| genstr.n = flist->b.nc; |
| Straddc(&genstr, '\0'); |
| return retcode; |
| } |
| |
| void |
| getcurwd(void) |
| { |
| String *t; |
| char buf[256]; |
| |
| buf[0] = 0; |
| getwd(buf, sizeof(buf)); |
| t = tmpcstr(buf); |
| Strduplstr(&curwd, t); |
| freetmpstr(t); |
| if(curwd.n == 0) |
| warn(Wpwd); |
| else if(curwd.s[curwd.n-1] != '/') |
| Straddc(&curwd, '/'); |
| } |
| |
| void |
| cd(String *str) |
| { |
| int i, fd; |
| char *s; |
| File *f; |
| String owd; |
| |
| getcurwd(); |
| if(getname((File *)0, str, FALSE)) |
| s = genc; |
| else |
| s = home; |
| if(chdir(s)) |
| syserror("chdir"); |
| fd = open("/dev/wdir", OWRITE); |
| if(fd > 0) |
| write(fd, s, strlen(s)); |
| dprint("!\n"); |
| Strinit(&owd); |
| Strduplstr(&owd, &curwd); |
| getcurwd(); |
| settempfile(); |
| /* |
| * Two passes so that if we have open |
| * /a/foo.c and /b/foo.c and cd from /b to /a, |
| * we don't ever have two foo.c simultaneously. |
| */ |
| for(i=0; i<tempfile.nused; i++){ |
| f = tempfile.filepptr[i]; |
| if(f!=cmd && f->name.s[0]!='/' && f->name.s[0]!=0){ |
| Strinsert(&f->name, &owd, (Posn)0); |
| fixname(&f->name); |
| sortname(f); |
| } |
| } |
| for(i=0; i<tempfile.nused; i++){ |
| f = tempfile.filepptr[i]; |
| if(f != cmd && Strispre(&curwd, &f->name)){ |
| fixname(&f->name); |
| sortname(f); |
| } |
| } |
| Strclose(&owd); |
| } |
| |
| int |
| loadflist(String *s) |
| { |
| int c, i; |
| |
| c = s->s[0]; |
| for(i = 0; s->s[i]==' ' || s->s[i]=='\t'; i++) |
| ; |
| if((c==' ' || c=='\t') && s->s[i]!='\n'){ |
| if(s->s[i]=='<'){ |
| Strdelete(s, 0L, (long)i+1); |
| readcmd(s); |
| }else{ |
| Strzero(&genstr); |
| while((c = s->s[i++]) && c!='\n') |
| Straddc(&genstr, c); |
| Straddc(&genstr, '\0'); |
| } |
| }else{ |
| if(c != '\n') |
| error(Eblank); |
| Strdupl(&genstr, empty); |
| } |
| if(genc) |
| free(genc); |
| genc = Strtoc(&genstr); |
| return genstr.s[0]; |
| } |
| |
| File * |
| readflist(int readall, int delete) |
| { |
| Posn i; |
| int c; |
| File *f; |
| String t; |
| |
| Strinit(&t); |
| for(i=0,f=0; f==0 || readall || delete; i++){ /* ++ skips blank */ |
| Strdelete(&genstr, (Posn)0, i); |
| for(i=0; (c = genstr.s[i])==' ' || c=='\t' || c=='\n'; i++) |
| ; |
| if(i >= genstr.n) |
| break; |
| Strdelete(&genstr, (Posn)0, i); |
| for(i=0; (c=genstr.s[i]) && c!=' ' && c!='\t' && c!='\n'; i++) |
| ; |
| |
| if(i == 0) |
| break; |
| genstr.s[i] = 0; |
| Strduplstr(&t, tmprstr(genstr.s, i+1)); |
| fixname(&t); |
| f = lookfile(&t); |
| if(delete){ |
| if(f == 0) |
| warn_S(Wfile, &t); |
| else |
| trytoclose(f); |
| }else if(f==0 && readall) |
| logsetname(f = newfile(), &t); |
| } |
| Strclose(&t); |
| return f; |
| } |
| |
| File * |
| tofile(String *s) |
| { |
| File *f; |
| |
| if(s->s[0] != ' ') |
| error(Eblank); |
| if(loadflist(s) == 0){ |
| f = lookfile(&genstr); /* empty string ==> nameless file */ |
| if(f == 0) |
| error_s(Emenu, genc); |
| }else if((f=readflist(FALSE, FALSE)) == 0) |
| error_s(Emenu, genc); |
| return current(f); |
| } |
| |
| File * |
| getfile(String *s) |
| { |
| File *f; |
| |
| if(loadflist(s) == 0) |
| logsetname(f = newfile(), &genstr); |
| else if((f=readflist(TRUE, FALSE)) == 0) |
| error(Eblank); |
| return current(f); |
| } |
| |
| void |
| closefiles(File *f, String *s) |
| { |
| if(s->s[0] == 0){ |
| if(f == 0) |
| error(Enofile); |
| trytoclose(f); |
| return; |
| } |
| if(s->s[0] != ' ') |
| error(Eblank); |
| if(loadflist(s) == 0) |
| error(Enewline); |
| readflist(FALSE, TRUE); |
| } |
| |
| void |
| copy(File *f, Address addr2) |
| { |
| Posn p; |
| int ni; |
| for(p=addr.r.p1; p<addr.r.p2; p+=ni){ |
| ni = addr.r.p2-p; |
| if(ni > BLOCKSIZE) |
| ni = BLOCKSIZE; |
| bufread(&f->b, p, genbuf, ni); |
| loginsert(addr2.f, addr2.r.p2, tmprstr(genbuf, ni)->s, ni); |
| } |
| addr2.f->ndot.r.p2 = addr2.r.p2+(f->dot.r.p2-f->dot.r.p1); |
| addr2.f->ndot.r.p1 = addr2.r.p2; |
| } |
| |
| void |
| move(File *f, Address addr2) |
| { |
| if(addr.r.p2 <= addr2.r.p2){ |
| logdelete(f, addr.r.p1, addr.r.p2); |
| copy(f, addr2); |
| }else if(addr.r.p1 >= addr2.r.p2){ |
| copy(f, addr2); |
| logdelete(f, addr.r.p1, addr.r.p2); |
| }else |
| error(Eoverlap); |
| } |
| |
| Posn |
| nlcount(File *f, Posn p0, Posn p1) |
| { |
| Posn nl = 0; |
| |
| while(p0 < p1) |
| if(filereadc(f, p0++)=='\n') |
| nl++; |
| return nl; |
| } |
| |
| void |
| printposn(File *f, int charsonly) |
| { |
| Posn l1, l2; |
| |
| if(!charsonly){ |
| l1 = 1+nlcount(f, (Posn)0, addr.r.p1); |
| l2 = l1+nlcount(f, addr.r.p1, addr.r.p2); |
| /* check if addr ends with '\n' */ |
| if(addr.r.p2>0 && addr.r.p2>addr.r.p1 && filereadc(f, addr.r.p2-1)=='\n') |
| --l2; |
| dprint("%lud", l1); |
| if(l2 != l1) |
| dprint(",%lud", l2); |
| dprint("; "); |
| } |
| dprint("#%lud", addr.r.p1); |
| if(addr.r.p2 != addr.r.p1) |
| dprint(",#%lud", addr.r.p2); |
| dprint("\n"); |
| } |
| |
| void |
| settempfile(void) |
| { |
| if(tempfile.nalloc < file.nused){ |
| if(tempfile.filepptr) |
| free(tempfile.filepptr); |
| tempfile.filepptr = emalloc(sizeof(File*)*file.nused); |
| tempfile.nalloc = file.nused; |
| } |
| memmove(tempfile.filepptr, file.filepptr, sizeof(File*)*file.nused); |
| tempfile.nused = file.nused; |
| } |