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