1 #include "sam.h"
2 
3 Rune	genbuf[BLOCKSIZE];
4 int	io;
5 int	panicking;
6 int	rescuing;
7 String	genstr;
8 String	rhs;
9 String	curwd;
10 String	cmdstr;
11 Rune	empty[] = { 0 };
12 char	*genc;
13 File	*curfile;
14 File	*flist;
15 File	*cmd;
16 jmp_buf	mainloop;
17 List	tempfile = { 'p' };
18 int	quitok = TRUE;
19 int	downloaded;
20 int	dflag;
21 int	Rflag;
22 char	*machine;
23 char	*home;
24 int	bpipeok;
25 int	termlocked;
26 char	*samterm = SAMTERM;
27 char	*rsamname = RSAM;
28 File	*lastfile;
29 Disk	*disk;
30 long	seq;
31 
32 char *winsize;
33 
34 Rune	baddir[] = { '<', 'b', 'a', 'd', 'd', 'i', 'r', '>', '\n'};
35 
36 void	usage(void);
37 
38 extern int notify(void(*)(void*,char*));
39 
40 void
main(int _argc,char ** _argv)41 main(int _argc, char **_argv)
42 {
43 	volatile int i, argc;
44 	char **volatile argv;
45 	String *t;
46 	char *termargs[10], **ap;
47 
48 	argc = _argc;
49 	argv = _argv;
50 	ap = termargs;
51 	*ap++ = "samterm";
52 	ARGBEGIN{
53 	case 'd':
54 		dflag++;
55 		break;
56 	case 'r':
57 		machine = EARGF(usage());
58 		break;
59 	case 'R':
60 		Rflag++;
61 		break;
62 	case 't':
63 		samterm = EARGF(usage());
64 		break;
65 	case 's':
66 		rsamname = EARGF(usage());
67 		break;
68 	default:
69 		dprint("sam: unknown flag %c\n", ARGC());
70 		usage();
71 	/* options for samterm */
72 	case 'a':
73 		*ap++ = "-a";
74 		break;
75 	case 'W':
76 		*ap++ = "-W";
77 		*ap++ = EARGF(usage());
78 		break;
79 	}ARGEND
80 	*ap = nil;
81 
82 	Strinit(&cmdstr);
83 	Strinit0(&lastpat);
84 	Strinit0(&lastregexp);
85 	Strinit0(&genstr);
86 	Strinit0(&rhs);
87 	Strinit0(&curwd);
88 	Strinit0(&plan9cmd);
89 	home = getenv(HOME);
90 	disk = diskinit();
91 	if(home == 0)
92 		home = "/";
93 	if(!dflag)
94 		startup(machine, Rflag, termargs, (char**)argv);
95 	notify(notifyf);
96 	getcurwd();
97 	if(argc>0){
98 		for(i=0; i<argc; i++){
99 			if(!setjmp(mainloop)){
100 				t = tmpcstr(argv[i]);
101 				Straddc(t, '\0');
102 				Strduplstr(&genstr, t);
103 				freetmpstr(t);
104 				fixname(&genstr);
105 				logsetname(newfile(), &genstr);
106 			}
107 		}
108 	}else if(!downloaded)
109 		newfile();
110 	seq++;
111 	if(file.nused)
112 		current(file.filepptr[0]);
113 	setjmp(mainloop);
114 	cmdloop();
115 	trytoquit();	/* if we already q'ed, quitok will be TRUE */
116 	exits(0);
117 }
118 
119 void
usage(void)120 usage(void)
121 {
122 	dprint("usage: sam [-d] [-t samterm] [-s sam name] [-r machine] [file ...]\n");
123 	exits("usage");
124 }
125 
126 void
rescue(void)127 rescue(void)
128 {
129 	int i, nblank = 0;
130 	File *f;
131 	char *c;
132 	char buf[256];
133 	char *root;
134 
135 	if(rescuing++)
136 		return;
137 	io = -1;
138 	for(i=0; i<file.nused; i++){
139 		f = file.filepptr[i];
140 		if(f==cmd || f->b.nc==0 || !fileisdirty(f))
141 			continue;
142 		if(io == -1){
143 			sprint(buf, "%s/sam.save", home);
144 			io = create(buf, 1, 0777);
145 			if(io<0)
146 				return;
147 		}
148 		if(f->name.s[0]){
149 			c = Strtoc(&f->name);
150 			strncpy(buf, c, sizeof buf-1);
151 			buf[sizeof buf-1] = 0;
152 			free(c);
153 		}else
154 			sprint(buf, "nameless.%d", nblank++);
155 		root = getenv("PLAN9");
156 		if(root == nil)
157 			root = "/usr/local/plan9";
158 		fprint(io, "#!/bin/sh\n%s/bin/samsave '%s' $* <<'---%s'\n", root, buf, buf);
159 		addr.r.p1 = 0, addr.r.p2 = f->b.nc;
160 		writeio(f);
161 		fprint(io, "\n---%s\n", (char *)buf);
162 	}
163 }
164 
165 void
panic(char * s)166 panic(char *s)
167 {
168 	int wasd;
169 
170 	if(!panicking++ && !setjmp(mainloop)){
171 		wasd = downloaded;
172 		downloaded = 0;
173 		dprint("sam: panic: %s: %r\n", s);
174 		if(wasd)
175 			fprint(2, "sam: panic: %s: %r\n", s);
176 		rescue();
177 		abort();
178 	}
179 }
180 
181 void
hiccough(char * s)182 hiccough(char *s)
183 {
184 	File *f;
185 	int i;
186 
187 	if(rescuing)
188 		exits("rescue");
189 	if(s)
190 		dprint("%s\n", s);
191 	resetcmd();
192 	resetxec();
193 	resetsys();
194 	if(io > 0)
195 		close(io);
196 
197 	/*
198 	 * back out any logged changes & restore old sequences
199 	 */
200 	for(i=0; i<file.nused; i++){
201 		f = file.filepptr[i];
202 		if(f==cmd)
203 			continue;
204 		if(f->seq==seq){
205 			bufdelete(&f->epsilon, 0, f->epsilon.nc);
206 			f->seq = f->prevseq;
207 			f->dot.r = f->prevdot;
208 			f->mark = f->prevmark;
209 			state(f, f->prevmod ? Dirty: Clean);
210 		}
211 	}
212 
213 	update();
214 	if (curfile) {
215 		if (curfile->unread)
216 			curfile->unread = FALSE;
217 		else if (downloaded)
218 			outTs(Hcurrent, curfile->tag);
219 	}
220 	longjmp(mainloop, 1);
221 }
222 
223 void
intr(void)224 intr(void)
225 {
226 	error(Eintr);
227 }
228 
229 void
trytoclose(File * f)230 trytoclose(File *f)
231 {
232 	char *t;
233 	char buf[256];
234 
235 	if(f == cmd)	/* possible? */
236 		return;
237 	if(f->deleted)
238 		return;
239 	if(fileisdirty(f) && !f->closeok){
240 		f->closeok = TRUE;
241 		if(f->name.s[0]){
242 			t = Strtoc(&f->name);
243 			strncpy(buf, t, sizeof buf-1);
244 			free(t);
245 		}else
246 			strcpy(buf, "nameless file");
247 		error_s(Emodified, buf);
248 	}
249 	f->deleted = TRUE;
250 }
251 
252 void
trytoquit(void)253 trytoquit(void)
254 {
255 	int c;
256 	File *f;
257 
258 	if(!quitok){
259 		for(c = 0; c<file.nused; c++){
260 			f = file.filepptr[c];
261 			if(f!=cmd && fileisdirty(f)){
262 				quitok = TRUE;
263 				eof = FALSE;
264 				error(Echanges);
265 			}
266 		}
267 	}
268 }
269 
270 void
load(File * f)271 load(File *f)
272 {
273 	Address saveaddr;
274 
275 	Strduplstr(&genstr, &f->name);
276 	filename(f);
277 	if(f->name.s[0]){
278 		saveaddr = addr;
279 		edit(f, 'I');
280 		addr = saveaddr;
281 	}else{
282 		f->unread = 0;
283 		f->cleanseq = f->seq;
284 	}
285 
286 	fileupdate(f, TRUE, TRUE);
287 }
288 
289 void
cmdupdate(void)290 cmdupdate(void)
291 {
292 	if(cmd && cmd->seq!=0){
293 		fileupdate(cmd, FALSE, downloaded);
294 		cmd->dot.r.p1 = cmd->dot.r.p2 = cmd->b.nc;
295 		telldot(cmd);
296 	}
297 }
298 
299 void
delete(File * f)300 delete(File *f)
301 {
302 	if(downloaded && f->rasp)
303 		outTs(Hclose, f->tag);
304 	delfile(f);
305 	if(f == curfile)
306 		current(0);
307 }
308 
309 void
update(void)310 update(void)
311 {
312 	int i, anymod;
313 	File *f;
314 
315 	settempfile();
316 	for(anymod = i=0; i<tempfile.nused; i++){
317 		f = tempfile.filepptr[i];
318 		if(f==cmd)	/* cmd gets done in main() */
319 			continue;
320 		if(f->deleted) {
321 			delete(f);
322 			continue;
323 		}
324 		if(f->seq==seq && fileupdate(f, FALSE, downloaded))
325 			anymod++;
326 		if(f->rasp)
327 			telldot(f);
328 	}
329 	if(anymod)
330 		seq++;
331 }
332 
333 File *
current(File * f)334 current(File *f)
335 {
336 	return curfile = f;
337 }
338 
339 void
edit(File * f,int cmd)340 edit(File *f, int cmd)
341 {
342 	int empty = TRUE;
343 	Posn p;
344 	int nulls;
345 
346 	if(cmd == 'r')
347 		logdelete(f, addr.r.p1, addr.r.p2);
348 	if(cmd=='e' || cmd=='I'){
349 		logdelete(f, (Posn)0, f->b.nc);
350 		addr.r.p2 = f->b.nc;
351 	}else if(f->b.nc!=0 || (f->name.s[0] && Strcmp(&genstr, &f->name)!=0))
352 		empty = FALSE;
353 	if((io = open(genc, OREAD))<0) {
354 		if (curfile && curfile->unread)
355 			curfile->unread = FALSE;
356 		error_r(Eopen, genc);
357 	}
358 	p = readio(f, &nulls, empty, TRUE);
359 	closeio((cmd=='e' || cmd=='I')? -1 : p);
360 	if(cmd == 'r')
361 		f->ndot.r.p1 = addr.r.p2, f->ndot.r.p2 = addr.r.p2+p;
362 	else
363 		f->ndot.r.p1 = f->ndot.r.p2 = 0;
364 	f->closeok = empty;
365 	if (quitok)
366 		quitok = empty;
367 	else
368 		quitok = FALSE;
369 	state(f, empty && !nulls? Clean : Dirty);
370 	if(empty && !nulls)
371 		f->cleanseq = f->seq;
372 	if(cmd == 'e')
373 		filename(f);
374 }
375 
376 int
getname(File * f,String * s,int save)377 getname(File *f, String *s, int save)
378 {
379 	int c, i;
380 
381 	Strzero(&genstr);
382 	if(genc){
383 		free(genc);
384 		genc = 0;
385 	}
386 	if(s==0 || (c = s->s[0])==0){		/* no name provided */
387 		if(f)
388 			Strduplstr(&genstr, &f->name);
389 		goto Return;
390 	}
391 	if(c!=' ' && c!='\t')
392 		error(Eblank);
393 	for(i=0; (c=s->s[i])==' ' || c=='\t'; i++)
394 		;
395 	while(s->s[i] > ' ')
396 		Straddc(&genstr, s->s[i++]);
397 	if(s->s[i])
398 		error(Enewline);
399 	fixname(&genstr);
400 	if(f && (save || f->name.s[0]==0)){
401 		logsetname(f, &genstr);
402 		if(Strcmp(&f->name, &genstr)){
403 			quitok = f->closeok = FALSE;
404 			f->qidpath = 0;
405 			f->mtime = 0;
406 			state(f, Dirty); /* if it's 'e', fix later */
407 		}
408 	}
409     Return:
410 	genc = Strtoc(&genstr);
411 	i = genstr.n;
412 	if(i && genstr.s[i-1]==0)
413 		i--;
414 	return i;	/* strlen(name) */
415 }
416 
417 void
filename(File * f)418 filename(File *f)
419 {
420 	if(genc)
421 		free(genc);
422 	genc = Strtoc(&genstr);
423 	dprint("%c%c%c %s\n", " '"[f->mod],
424 		"-+"[f->rasp!=0], " ."[f==curfile], genc);
425 }
426 
427 void
undostep(File * f,int isundo)428 undostep(File *f, int isundo)
429 {
430 	uint p1, p2;
431 	int mod;
432 
433 	mod = f->mod;
434 	fileundo(f, isundo, 1, &p1, &p2, TRUE);
435 	f->ndot = f->dot;
436 	if(f->mod){
437 		f->closeok = 0;
438 		quitok = 0;
439 	}else
440 		f->closeok = 1;
441 
442 	if(f->mod != mod){
443 		f->mod = mod;
444 		if(mod)
445 			mod = Clean;
446 		else
447 			mod = Dirty;
448 		state(f, mod);
449 	}
450 }
451 
452 int
undo(int isundo)453 undo(int isundo)
454 {
455 	File *f;
456 	int i;
457 	Mod max;
458 
459 	max = undoseq(curfile, isundo);
460 	if(max == 0)
461 		return 0;
462 	settempfile();
463 	for(i = 0; i<tempfile.nused; i++){
464 		f = tempfile.filepptr[i];
465 		if(f!=cmd && undoseq(f, isundo)==max)
466 			undostep(f, isundo);
467 	}
468 	return 1;
469 }
470 
471 int
readcmd(String * s)472 readcmd(String *s)
473 {
474 	int retcode;
475 
476 	if(flist != 0)
477 		fileclose(flist);
478 	flist = fileopen();
479 
480 	addr.r.p1 = 0, addr.r.p2 = flist->b.nc;
481 	retcode = plan9(flist, '<', s, FALSE);
482 	fileupdate(flist, FALSE, FALSE);
483 	flist->seq = 0;
484 	if (flist->b.nc > BLOCKSIZE)
485 		error(Etoolong);
486 	Strzero(&genstr);
487 	Strinsure(&genstr, flist->b.nc);
488 	bufread(&flist->b, (Posn)0, genbuf, flist->b.nc);
489 	memmove(genstr.s, genbuf, flist->b.nc*RUNESIZE);
490 	genstr.n = flist->b.nc;
491 	Straddc(&genstr, '\0');
492 	return retcode;
493 }
494 
495 void
getcurwd(void)496 getcurwd(void)
497 {
498 	String *t;
499 	char buf[256];
500 
501 	buf[0] = 0;
502 	getwd(buf, sizeof(buf));
503 	t = tmpcstr(buf);
504 	Strduplstr(&curwd, t);
505 	freetmpstr(t);
506 	if(curwd.n == 0)
507 		warn(Wpwd);
508 	else if(curwd.s[curwd.n-1] != '/')
509 		Straddc(&curwd, '/');
510 }
511 
512 void
cd(String * str)513 cd(String *str)
514 {
515 	int i, fd;
516 	char *s;
517 	File *f;
518 	String owd;
519 
520 	getcurwd();
521 	if(getname((File *)0, str, FALSE))
522 		s = genc;
523 	else
524 		s = home;
525 	if(chdir(s))
526 		syserror("chdir");
527 	fd = open("/dev/wdir", OWRITE);
528 	if(fd > 0)
529 		write(fd, s, strlen(s));
530 	dprint("!\n");
531 	Strinit(&owd);
532 	Strduplstr(&owd, &curwd);
533 	getcurwd();
534 	settempfile();
535 	/*
536 	 * Two passes so that if we have open
537 	 * /a/foo.c and /b/foo.c and cd from /b to /a,
538 	 * we don't ever have two foo.c simultaneously.
539 	 */
540 	for(i=0; i<tempfile.nused; i++){
541 		f = tempfile.filepptr[i];
542 		if(f!=cmd && f->name.s[0]!='/' && f->name.s[0]!=0){
543 			Strinsert(&f->name, &owd, (Posn)0);
544 			fixname(&f->name);
545 			sortname(f);
546 		}
547 	}
548 	for(i=0; i<tempfile.nused; i++){
549 		f = tempfile.filepptr[i];
550 		if(f != cmd && Strispre(&curwd, &f->name)){
551 			fixname(&f->name);
552 			sortname(f);
553 		}
554 	}
555 	Strclose(&owd);
556 }
557 
558 int
loadflist(String * s)559 loadflist(String *s)
560 {
561 	int c, i;
562 
563 	c = s->s[0];
564 	for(i = 0; s->s[i]==' ' || s->s[i]=='\t'; i++)
565 		;
566 	if((c==' ' || c=='\t') && s->s[i]!='\n'){
567 		if(s->s[i]=='<'){
568 			Strdelete(s, 0L, (long)i+1);
569 			readcmd(s);
570 		}else{
571 			Strzero(&genstr);
572 			while((c = s->s[i++]) && c!='\n')
573 				Straddc(&genstr, c);
574 			Straddc(&genstr, '\0');
575 		}
576 	}else{
577 		if(c != '\n')
578 			error(Eblank);
579 		Strdupl(&genstr, empty);
580 	}
581 	if(genc)
582 		free(genc);
583 	genc = Strtoc(&genstr);
584 	return genstr.s[0];
585 }
586 
587 File *
readflist(int readall,int delete)588 readflist(int readall, int delete)
589 {
590 	Posn i;
591 	int c;
592 	File *f;
593 	String t;
594 
595 	Strinit(&t);
596 	for(i=0,f=0; f==0 || readall || delete; i++){	/* ++ skips blank */
597 		Strdelete(&genstr, (Posn)0, i);
598 		for(i=0; (c = genstr.s[i])==' ' || c=='\t' || c=='\n'; i++)
599 			;
600 		if(i >= genstr.n)
601 			break;
602 		Strdelete(&genstr, (Posn)0, i);
603 		for(i=0; (c=genstr.s[i]) && c!=' ' && c!='\t' && c!='\n'; i++)
604 			;
605 
606 		if(i == 0)
607 			break;
608 		genstr.s[i] = 0;
609 		Strduplstr(&t, tmprstr(genstr.s, i+1));
610 		fixname(&t);
611 		f = lookfile(&t);
612 		if(delete){
613 			if(f == 0)
614 				warn_S(Wfile, &t);
615 			else
616 				trytoclose(f);
617 		}else if(f==0 && readall)
618 			logsetname(f = newfile(), &t);
619 	}
620 	Strclose(&t);
621 	return f;
622 }
623 
624 File *
tofile(String * s)625 tofile(String *s)
626 {
627 	File *f;
628 
629 	if(s->s[0] != ' ')
630 		error(Eblank);
631 	if(loadflist(s) == 0){
632 		f = lookfile(&genstr);	/* empty string ==> nameless file */
633 		if(f == 0)
634 			error_s(Emenu, genc);
635 	}else if((f=readflist(FALSE, FALSE)) == 0)
636 		error_s(Emenu, genc);
637 	return current(f);
638 }
639 
640 File *
getfile(String * s)641 getfile(String *s)
642 {
643 	File *f;
644 
645 	if(loadflist(s) == 0)
646 		logsetname(f = newfile(), &genstr);
647 	else if((f=readflist(TRUE, FALSE)) == 0)
648 		error(Eblank);
649 	return current(f);
650 }
651 
652 void
closefiles(File * f,String * s)653 closefiles(File *f, String *s)
654 {
655 	if(s->s[0] == 0){
656 		if(f == 0)
657 			error(Enofile);
658 		trytoclose(f);
659 		return;
660 	}
661 	if(s->s[0] != ' ')
662 		error(Eblank);
663 	if(loadflist(s) == 0)
664 		error(Enewline);
665 	readflist(FALSE, TRUE);
666 }
667 
668 void
copy(File * f,Address addr2)669 copy(File *f, Address addr2)
670 {
671 	Posn p;
672 	int ni;
673 	for(p=addr.r.p1; p<addr.r.p2; p+=ni){
674 		ni = addr.r.p2-p;
675 		if(ni > BLOCKSIZE)
676 			ni = BLOCKSIZE;
677 		bufread(&f->b, p, genbuf, ni);
678 		loginsert(addr2.f, addr2.r.p2, tmprstr(genbuf, ni)->s, ni);
679 	}
680 	addr2.f->ndot.r.p2 = addr2.r.p2+(f->dot.r.p2-f->dot.r.p1);
681 	addr2.f->ndot.r.p1 = addr2.r.p2;
682 }
683 
684 void
move(File * f,Address addr2)685 move(File *f, Address addr2)
686 {
687 	if(addr.r.p2 <= addr2.r.p2){
688 		logdelete(f, addr.r.p1, addr.r.p2);
689 		copy(f, addr2);
690 	}else if(addr.r.p1 >= addr2.r.p2){
691 		copy(f, addr2);
692 		logdelete(f, addr.r.p1, addr.r.p2);
693 	}else
694 		error(Eoverlap);
695 }
696 
697 Posn
nlcount(File * f,Posn p0,Posn p1)698 nlcount(File *f, Posn p0, Posn p1)
699 {
700 	Posn nl = 0;
701 
702 	while(p0 < p1)
703 		if(filereadc(f, p0++)=='\n')
704 			nl++;
705 	return nl;
706 }
707 
708 void
printposn(File * f,int charsonly)709 printposn(File *f, int charsonly)
710 {
711 	Posn l1, l2;
712 
713 	if(!charsonly){
714 		l1 = 1+nlcount(f, (Posn)0, addr.r.p1);
715 		l2 = l1+nlcount(f, addr.r.p1, addr.r.p2);
716 		/* check if addr ends with '\n' */
717 		if(addr.r.p2>0 && addr.r.p2>addr.r.p1 && filereadc(f, addr.r.p2-1)=='\n')
718 			--l2;
719 		dprint("%lud", l1);
720 		if(l2 != l1)
721 			dprint(",%lud", l2);
722 		dprint("; ");
723 	}
724 	dprint("#%lud", addr.r.p1);
725 	if(addr.r.p2 != addr.r.p1)
726 		dprint(",#%lud", addr.r.p2);
727 	dprint("\n");
728 }
729 
730 void
settempfile(void)731 settempfile(void)
732 {
733 	if(tempfile.nalloc < file.nused){
734 		if(tempfile.filepptr)
735 			free(tempfile.filepptr);
736 		tempfile.filepptr = emalloc(sizeof(File*)*file.nused);
737 		tempfile.nalloc = file.nused;
738 	}
739 	memmove(tempfile.filepptr, file.filepptr, sizeof(File*)*file.nused);
740 	tempfile.nused = file.nused;
741 }
742