1 /*
2 ** Copyright (C) 2002-2008 Christophe Kalt
3 **
4 ** This file is part of shmux,
5 ** see the LICENSE file for details on your rights.
6 */
7 
8 #include "os.h"
9 
10 #include <fcntl.h>
11 #include <sys/ioctl.h>
12 #if defined(HAVE_TERMCAP_H)
13 # include <termcap.h>
14 #elif defined(HAVE_CURSES_H)
15 # include <curses.h>
16 # if defined(HAVE_TERM_H) && defined(sun)
17 #  include <term.h>
18 # endif
19 #endif
20 #include <termios.h>
21 #include <signal.h>
22 #include <stdarg.h>
23 
24 #include "term.h"
25 
26 static char const rcsid[] = "@(#)$Id$";
27 
28 extern char *myname;
29 
30 static int targets, internalmsgs, debugmsgs, padding, mypid;
31 static struct termios origt, shmuxt;
32 static int otty, etty, CO, got_sigwin, ttyin;
33 static char *MD,			/* bold */
34 	    *ME,			/* turn off bold (and more) */
35 	    *LE,			/* move cursor left one position */
36 	    *CE,			/* clear to end of line */
37 	    *CR,			/* carriage return */
38 	    *NL;			/* newline character if not \n */
39 static char status[512];
40 static FILE *ttyout;
41 
42 static void shmux_signal(int);
43 static void tty_init(int);
44 static int putchar2(int);
45 static int putchar3(int);
46 static void gprint(char *, char, char *, va_list);
47 
48 /*
49 ** shmux_signal
50 **	signal handler
51 */
52 static void
53 shmux_signal(sig)
54 int sig;
55 {
56     if (sig == SIGWINCH || sig == SIGCONT)
57 	got_sigwin += 1;
58     if (sig == SIGCONT)
59       {
60 	if (ttyin >= 0 && tcsetattr(ttyin, TCSANOW, &shmuxt) < 0)
61 	    /* Calling eprint from here isn't so smart.. */
62 	    eprint("tcsetattr() failed: %s", strerror(errno));
63       }
64     if (sig == SIGINT || sig == SIGQUIT || sig == SIGABRT || sig == SIGTERM)
65       {
66 	/* restore original tty settings */
67 	if (ttyin >= 0 && tcsetattr(ttyin, TCSANOW, &origt) < 0)
68 	    /* Calling eprint from here isn't so smart.. */
69 	    eprint("tcsetattr() failed: %s", strerror(errno));
70         /* Resend the signal to ourselves */
71         kill(getpid(), sig);
72       }
73     /* Nothing done for SIGTSTP, except ignoring it. */
74 }
75 
76 /*
77 ** term_init:
78 **	Initialize terminal handling system.
79 */
80 void
81 term_init(maxlen, prefix, progress, internal, debug, interactive)
82 int maxlen, prefix, progress, internal, debug, interactive;
83 {
84     static char termcap[2048], area[1024];
85     char *term, *ptr;
86     struct sigaction sa;
87 
88     assert( maxlen != 0 );
89 
90     padding = prefix*maxlen;
91     targets = prefix;
92     internalmsgs = internal;
93     debugmsgs = debug;
94 
95     mypid = getpid();
96 
97     /* Input initialization */
98     ttyin = -1;
99 
100     if (interactive != 0)
101       {
102 	if (isatty(fileno(stdin)) == 1)
103 	    ttyin = fileno(stdin);
104 	else
105 	    ttyin = open("/dev/tty", O_RDONLY, 0);
106       }
107 
108     /* Output initializations */
109 
110     LE = NULL;
111     CE = NULL;
112     CR = "\r";
113     NL = "\n";
114     status[0] = '\0';
115 
116     otty = isatty(fileno(stdout));
117     etty = isatty(fileno(stderr));
118 
119     if (otty == 1)
120 	ttyout = stdout;
121     else if (etty == 1)
122 	ttyout = stderr;
123     else
124 	ttyout = fopen("/dev/tty", "a");
125 
126     term = getenv("TERM");
127 
128     if (debugmsgs != 0)
129 	fprintf(stdout,
130 	     "%*s$ itty[%d] ttyin[%d] otty[%d] etty[%d] ttyout[%d] TERM[%s]\n",
131 		padding, myname, isatty(fileno(stdin)), ttyin,
132 		otty, etty, fileno(ttyout), (term != NULL) ? term : "");
133 
134     if (term == NULL)
135       {
136 	if (progress != 0 && ttyout != NULL)
137 	    fprintf(stderr, "%*s: TERM variable is not set!\n",
138 		    padding, myname);
139 	tty_init(interactive);
140 	return;
141       }
142     if (tgetent(termcap, term) < 1)
143       {
144 	if (progress != 0 && ttyout != NULL)
145 	    fprintf(stderr, "%*s: No TERMCAP entry for ``%s''.\n",
146 		    padding, myname, term);
147 	tty_init(interactive);
148 	return;
149       }
150 
151     ptr = area;
152 
153     /* Get terminal width and setup signal handler to keep track of it. */
154     if ((CO = tgetnum("co")) == -1) CO = 80;
155     sigemptyset(&sa.sa_mask);
156     sa.sa_flags = 0;
157     sa.sa_handler = shmux_signal;
158     sigaction(SIGWINCH, &sa, NULL);
159     sigaction(SIGCONT, &sa, NULL);
160     got_sigwin = 0;
161     term_size();
162 
163     MD = tgetstr("md", &ptr);
164     if (MD != NULL)
165       {
166 	ME = tgetstr("me", &ptr);
167 	if (ME == NULL)
168 	    MD = NULL;
169       }
170     else
171 	ME = NULL;
172     CR = tgetstr("cr", &ptr);
173     if (CR == NULL)
174 	CR = "\r"; /* Let's just assume.. */
175     NL = tgetstr("nl", &ptr);
176     if (NL == NULL)
177 	NL = "\n"; /* As per specification. */
178     LE = tgetstr("le", &ptr);
179     CE = tgetstr("ce", &ptr);
180     if (progress == 0)
181 	CE = NULL;
182 
183     if (CE == NULL && progress != 0 && ttyout != NULL)
184 	fprintf(stderr, "%*s: Terminal ``%s'' is too dumb for the progress status bar! (no ce)\n", padding, myname, term);
185 
186     tty_init(interactive);
187 }
188 
189 /*
190 ** term_size:
191 **	Query /dev/tty to find its size
192 */
193 void
194 term_size(void)
195 {
196 #if defined(TIOCGWINSZ)
197   struct winsize ws;
198 
199   if (ttyout == NULL)
200       return;
201   if (ioctl(fileno(ttyout), TIOCGWINSZ, &ws) < 0)
202     {
203       eprint("ioctl(tty, TIOCGWINSZ) failed: %s", strerror(errno));
204       return;
205     }
206   if (ws.ws_col > 0)
207     {
208       CO = ws.ws_col;
209       dprint("window seems to have %d columns.", CO);
210     }
211 #endif
212 }
213 
214 /*
215 ** tty_init
216 **	/dev/tty initialization
217 */
218 static void
219 tty_init(interactive)
220 int interactive;
221 {
222     if (ttyin < 0)
223       {
224 	dprint("No input tty available.");
225 	return;
226       }
227     if (ttyout == NULL)
228       {
229 	dprint("No output tty available.");
230         ttyin = -1;
231 	return;
232       }
233 
234     if (tcgetattr(ttyin, &origt) < 0) /* save original tty settings */
235       {
236 	eprint("tcgetattr() failed: %s", strerror(errno));
237 	ttyin = -1;
238       }
239     else if (tcgetpgrp(ttyin) != getpgrp())
240         ttyin = -1; /* Running in the background */
241     else
242       {
243         shmuxt = origt;
244         shmuxt.c_lflag &= ~(ICANON|ECHO); /* no echo or canonical processing */
245 #if !defined(BROKEN_POLL)
246         shmuxt.c_cc[VMIN] = 1; /* no buffering */
247 #else
248         shmuxt.c_cc[VMIN] = 0; /* no blocking */
249 #endif
250         shmuxt.c_cc[VTIME] = 0; /* no delaying */
251         if (tcsetattr(ttyin, TCSANOW, &shmuxt) == 0)
252           {
253             struct sigaction sa;
254 
255             atexit(tty_restore);
256 
257             /*
258             ** Catch signals which require reinitializing or restoring
259             ** tty settings
260             */
261             sigemptyset(&sa.sa_mask);
262             sa.sa_flags = 0;
263             sa.sa_handler = shmux_signal;
264             sigaction(SIGTSTP, &sa, NULL);
265             /* Only need to catch the following signals once, then we die. */
266             sa.sa_flags = SA_RESETHAND;
267             sigaction(SIGINT, &sa, NULL);
268             sigaction(SIGQUIT, &sa, NULL);
269             sigaction(SIGABRT, &sa, NULL);
270             sigaction(SIGTERM, &sa, NULL);
271 
272             dprint("Input tty initialized (0x%lX -> 0x%lX)",
273                    origt.c_lflag, shmuxt.c_lflag);
274           }
275         else
276           {
277             eprint("tcsetattr() failed: %s", strerror(errno));
278             ttyin = -1;
279           }
280       }
281 
282     if (interactive != 0 && ttyin == -1)
283       {
284         fprintf(ttyout,
285                 "%*s: Input unavailable, interactive mode is disabled.\n",
286                 padding, myname);
287         fflush(ttyout);
288       }
289 }
290 
291 /*
292 ** tty_fd
293 **	Returns the file descriptor associated with the tty input
294 */
295 int
296 tty_fd(void)
297 {
298     return ttyin;
299 }
300 
301 /*
302 ** tty_restore
303 **	Restores /dev/tty original settings
304 */
305 void
306 tty_restore(void)
307 {
308     if (getpid() != mypid)
309 	return;
310     /* restore original tty settings */
311     if (ttyin >= 0 && tcsetattr(ttyin, TCSANOW, &origt) < 0)
312 	eprint("tcsetattr() failed: %s", strerror(errno));
313     ttyin = -1;
314 }
315 
316 /*
317 ** term_togglemsg
318 **	Toggles verbose mode
319 */
320 int
321 term_togglemsg(void)
322 {
323     internalmsgs = 1 - internalmsgs;
324     return internalmsgs;
325 }
326 
327 /*
328 ** term_debugmsg
329 **	Toggles debug messages
330 */
331 int
332 term_toggledbg(void)
333 {
334     debugmsgs = 1 - debugmsgs;
335     return debugmsgs;
336 }
337 
338 /*
339 ** sprint:
340 **	progress status printf wrapper responsible for displaying a string
341 **	on a pseudo status line, leaving the cursor at the beginning of the
342 **	line so that it can easily be overwritten afterwards.
343 */
344 void
345 sprint(char *format, ...)
346 {
347     char *ch;
348     int bold;
349 
350     if (CE == NULL || ttyout == NULL)
351 	return;
352 
353     if (got_sigwin > 0)
354       {
355 	got_sigwin = 0;
356 	term_size();
357       }
358 
359     if (format != NULL)
360       {
361         va_list va;
362 	char tmp[512];
363 
364         va_start(va, format);
365         vsnprintf(tmp, 512, format, va);
366         va_end(va);
367 	tmp[CO-1] = '\0';
368 	if (strcmp(tmp, status) == 0)
369 	    return;
370 	strlcpy(status, tmp, sizeof(status));
371       }
372 
373     bold = 0;
374     ch = status;
375     while (*ch != '\0')
376       {
377 	if (*ch == '\a' && bold == 0)
378 	  {
379 	    if (otty == 1 && MD != NULL)
380 	      {
381 		tputs(MD, 0, putchar3);
382 		bold = 1;
383 	      }
384 	  }
385 	else
386 	  {
387 	    if ((*ch == ' ' || *ch == '\a') && bold == 1)
388 	      {
389 		tputs(ME, 0, putchar3);
390 		bold = 0;
391 	      }
392 	    fprintf(ttyout, "%c", *ch);
393 	  }
394 	ch += 1;
395       }
396 
397     tputs(CE, 0, putchar3);
398     tputs(CR, 0, putchar3);
399     fflush(ttyout);
400 }
401 
402 /*
403 ** putchar2:
404 **	Same as putchar, for stderr.
405 */
406 static int
407 putchar2(c)
408 int c;
409 {
410     return fputc(c, stderr);
411 }
412 
413 /*
414 ** putchar3:
415 **	Same as putchar, for tty (either stdout or stderr).
416 */
417 static int
418 putchar3(c)
419 int c;
420 {
421     return fputc(c, ttyout);
422 }
423 
424 /*
425 ** uprint:
426 **	printf wrapper for messages destined to the user (e.g. /dev/tty)
427 */
428 void
429 uprint(char *format, ...)
430 {
431     va_list va;
432 
433     assert( ttyout != NULL );
434 
435     if (CE != NULL)
436 	tputs(CE, 0, putchar3);
437 
438     fprintf(ttyout, ">> ");
439     va_start(va, format);
440     vfprintf(ttyout, format, va);
441     va_end(va);
442     tputs(NL, 0, putchar3);
443     fflush(ttyout);
444 
445     sprint(NULL);
446 }
447 
448 /*
449 ** uprompt:
450 **	prompt the user (e.g. /dev/tty)
451 */
452 char *
453 uprompt(char *format, ...)
454 {
455     va_list va;
456     static char input[1024];
457     int pos;
458 
459     if (CE != NULL) tputs(CE, 0, putchar3);
460 
461     /* Display the prompt. */
462     fprintf(ttyout, ">> ");
463     va_start(va, format);
464     vfprintf(ttyout, format, va);
465     va_end(va);
466     fprintf(ttyout, " ");
467     fflush(ttyout);
468 
469     /* Read/process user input */
470     pos = -1;
471     while (1)
472       {
473 	switch (read(ttyin, input+pos+1, 1))
474 	  {
475 	  case -1:
476 	      eprint("Unexpected read(/dev/tty) error: %s", strerror(errno));
477 	      pos = -2;
478 	      break;
479 	  case 0:
480 	      eprint("Unexpected empty read(/dev/tty) result");
481 	      pos = -2;
482 	      break;
483 	  default:
484 	      pos += 1;
485 	      break;
486 	  }
487 	if (pos < 0)
488 	    break;
489 
490 	if (input[pos] == origt.c_cc[VERASE])
491 	  {
492 	    pos -= 1;
493 	    if (pos >= 0)
494 	      {
495 		pos -= 1;
496 		if (LE != NULL) tputs(LE, 0, putchar3);
497 		if (CE != NULL) tputs(CE, 0, putchar3);
498 	      }
499 	  }
500 	else if (input[pos] == origt.c_cc[VKILL])
501 	  {
502 	    pos -= 1;
503 	    while (pos >= 0)
504 	      {
505 		pos -= 1;
506 		if (LE != NULL) tputs(LE, 0, putchar3);
507 	      }
508 	    if (CE != NULL) tputs(CE, 0, putchar3);
509 	  }
510 	else if (input[pos] == '\n')
511 	  {
512 	    input[pos] = '\0';
513 	    break;
514 	  }
515 	else
516 	  {
517 	    if (pos < 1023)
518 	      {
519 		fprintf(ttyout, "%c", input[pos]);
520 		if (CE != NULL) tputs(CE, 0, putchar3); /* Useful after a multiline VKILL */
521 	      }
522 	    else
523 		pos -= 1;
524 	  }
525 	fflush(ttyout);
526       }
527 
528     tputs(NL, 0, putchar3);
529     fflush(ttyout);
530 
531     sprint(NULL);
532 
533     if (pos == -2)
534 	return NULL;
535     input[pos+1] = '\0';
536     return input;
537 }
538 
539 /*
540 ** gprint:
541 **	generic (private) printf wrapper used by (most of) the public wrappers
542 **	to display a line with the proper indentation.
543 */
544 static void
545 gprint(char *prefix, char separator, char *format, va_list va)
546 {
547     FILE *std;
548     int (*pc)(int);
549 
550     if (CE != NULL)
551       {
552 	tputs(CE, 0, putchar3);
553 	fflush(ttyout);
554       }
555 
556     std = stdout; pc = putchar;
557     if (separator == MSG_STDERR || separator == MSG_STDERRTRUNC)
558       {
559 	std = stderr; pc = putchar2;
560 	if (etty == 1 && MD != NULL)
561 	    tputs(MD, 0, pc);
562       }
563 
564     if ((prefix != NULL && targets != 0) || (prefix == myname))
565 	fprintf(std, "%*s%c ", padding, prefix, separator);
566     vfprintf(std, format, va);
567     if (std == stderr && etty == 1 && ME != NULL)
568 	tputs(ME, 0, pc);
569     tputs(NL, 0, pc);
570     fflush(std);
571 
572     sprint(NULL);
573 }
574 
575 /*
576 ** dprint:
577 **	debug printf wrapper
578 */
579 void
580 dprint(char *format, ...)
581 {
582     va_list va;
583 
584     if (debugmsgs == 0)
585 	return;
586 
587     va_start(va, format);
588     gprint(myname, MSG_DEBUG, format, va);
589     va_end(va);
590 }
591 
592 /*
593 ** iprint:
594 **	internal printf wrapper
595 */
596 void
597 iprint(char *format, ...)
598 {
599     va_list va;
600 
601     if (internalmsgs == 0)
602 	return;
603 
604     va_start(va, format);
605     gprint(myname, MSG_STDOUT, format, va);
606     va_end(va);
607 }
608 
609 /*
610 ** eprint:
611 **	internal printf wrapper for errors
612 */
613 void
614 eprint(char *format, ...)
615 {
616     va_list va;
617 
618     va_start(va, format);
619     gprint(myname, MSG_STDERR, format, va);
620     va_end(va);
621 }
622 
623 /*
624 ** tprint:
625 **	general printf wrapper
626 */
627 void
628 tprint(char *target, char separator, char *format, ...)
629 {
630     va_list va;
631 
632     va_start(va, format);
633     gprint(target, separator, format, va);
634     va_end(va);
635 }
636 
637 /*
638 ** nprint:
639 **	normal printf wrapper.. total overkill
640 */
641 void
642 nprint(char *format, ...)
643 {
644     va_list va;
645 
646     va_start(va, format);
647     vprintf(format, va);
648     va_end(va);
649     tputs(NL, 0, putchar);
650 }
651