1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 * Copyright (c) 2012 Gary Mills
26 */
27
28/*	Copyright (c) 1988 AT&T	*/
29/*	  All Rights Reserved  	*/
30
31/*
32 *	tput - print terminal attribute
33 *
34 *  return-codes - command line arguments:
35 *	0: ok if boolean capname -> TRUE
36 *	1: for boolean capname -> FALSE
37 *
38 *  return-codes - standard input arguments:
39 *	0: ok; tput for all lines was successful
40 *
41 *  return-codes - both cases:
42 *	2	usage error
43 *	3	bad terminal type given or no terminfo database
44 *	4	unknown capname
45 *	-1	capname is a numeric variable that is not specified in the
46 *		terminfo database(E.g. tpu -T450 lines).
47 *
48 *  tput printfs a value if an INT capname was given; e.g. cols.
49 *	putp's a string if a STRING capname was given; e.g. clear. and
50 *  for BOOLEAN capnames, e.g. hard-copy, just returns the boolean value.
51 */
52
53#include <curses.h>
54#include <term.h>
55#include <fcntl.h>
56#include <ctype.h>
57#include <stdlib.h>
58#include <string.h>
59#include <sys/types.h>
60#include <unistd.h>
61#include <locale.h>
62
63/* externs from libcurses */
64extern int tigetnum();
65
66static int outputcap(char *cap, int argc, char **argv);
67static int allnumeric(char *string);
68static int getpad(char *cap);
69static void setdelay();
70static void settabs();
71static void cat(char *file);
72static void initterm();
73static void reset_term();
74
75static char *progname;		/* argv[0] */
76static int CurrentBaudRate;	/* current baud rate */
77static int reset = 0;		/* called as reset_term */
78static int fildes = 1;
79
80int
81main(int argc, char **argv)
82{
83	int i, std_argc;
84	char *term = getenv("TERM");
85	char *cap, std_input = FALSE;
86	int setuperr;
87
88	(void) setlocale(LC_ALL, "");
89#if !defined(TEXT_DOMAIN)
90#define	TEXT_DOMAIN "SYS_TEST"
91#endif
92	(void) textdomain(TEXT_DOMAIN);
93
94	progname = argv[0];
95
96	while ((i = getopt(argc, argv, "ST:")) != EOF) {
97		switch (i) {
98		case 'T':
99			fildes = -1;
100			(void) putenv("LINES=");
101			(void) putenv("COLUMNS=");
102			term = optarg;
103			break;
104
105		case 'S':
106			std_input = TRUE;
107			break;
108
109		case '?':			/* FALLTHROUGH		*/
110		usage:				/* FALLTHROUGH		*/
111		default:
112			(void) fprintf(stderr, gettext(
113			    "usage:\t%s [-T [term]] capname "
114			    "[parm argument...]\n"), progname);
115			(void) fprintf(stderr, gettext("OR:\t%s -S <<\n"),
116			    progname);
117			exit(2);
118		}
119	}
120
121	if (!term || !*term) {
122		(void) fprintf(stderr,
123		    gettext("%s: No value for $TERM and no -T specified\n"),
124		    progname);
125		exit(2);
126	}
127
128	(void) setupterm(term, fildes, &setuperr);
129
130	switch (setuperr) {
131	case -2:
132		(void) fprintf(stderr,
133		    gettext("%s: unreadable terminal descriptor \"%s\"\n"),
134		    progname, term);
135		exit(3);
136		break;
137
138	case -1:
139		(void) fprintf(stderr,
140		    gettext("%s: no terminfo database\n"), progname);
141		exit(3);
142		break;
143
144	case 0:
145		(void) fprintf(stderr,
146		    gettext("%s: unknown terminal \"%s\"\n"),
147		    progname, term);
148		exit(3);
149	}
150
151	reset_shell_mode();
152
153	/* command line arguments */
154	if (!std_input) {
155		if (argc == optind)
156			goto usage;
157
158		cap = argv[optind++];
159
160		if (strcmp(cap, "init") == 0)
161			initterm();
162		else if (strcmp(cap, "reset") == 0)
163			reset_term();
164		else if (strcmp(cap, "longname") == 0)
165			(void) printf("%s\n", longname());
166		else
167			exit(outputcap(cap, argc, argv));
168		return (0);
169	} else {			/* standard input argumets	*/
170		char buff[128];
171		char **v;
172
173		/* allocate storage for the 'faked' argv[] array	*/
174		v = (char **)malloc(10 * sizeof (char *));
175		for (i = 0; i < 10; i++)
176			v[i] = (char *)malloc(32 * sizeof (char));
177
178		while (fgets(buff, sizeof (buff), stdin) != NULL) {
179			/* read standard input line; skip over empty lines */
180			if ((std_argc =
181			    sscanf(buff,
182			    "%31s %31s %31s %31s %31s %31s %31s %31s "
183			    "%31s %31s",
184			    v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7],
185			    v[8], v[9])) < 1) {
186				continue;
187			}
188
189			cap = v[0];
190			optind = 1;
191
192			if (strcmp(cap, "init") == 0) {
193				initterm();
194			} else if (strcmp(cap, "reset") == 0) {
195				reset_term();
196			} else if (strcmp(cap, "longname") == 0) {
197				(void) printf("%s\n", longname());
198			} else {
199				(void) outputcap(cap, std_argc, v);
200			}
201			(void) fflush(stdout);
202		}
203
204		return (0);
205	}
206}
207
208static long parm[9] = {
209    0, 0, 0, 0, 0, 0, 0, 0, 0
210};
211
212static int
213outputcap(char *cap, int argc, char **argv)
214{
215	int parmset = 0;
216	char *thisstr;
217	int i;
218
219	if ((i = tigetflag(cap)) >= 0)
220		return (1 - i);
221
222	if ((i = tigetnum(cap)) >= -1) {
223		(void) printf("%d\n", i);
224		return (0);
225	}
226
227	if ((thisstr = tigetstr(cap)) != (char *)-1) {
228		if (!thisstr) {
229			return (1);
230		}
231		for (parmset = 0; optind < argc; optind++, parmset++)
232			if (allnumeric(argv[optind]))
233				parm[parmset] = atoi(argv[optind]);
234			else
235				parm[parmset] = (int)argv[optind];
236
237		if (parmset)
238			putp(tparm(thisstr,
239			    parm[0], parm[1], parm[2], parm[3],
240			    parm[4], parm[5], parm[6], parm[7], parm[8]));
241		else
242			putp(thisstr);
243		return (0);
244	}
245
246	(void) fprintf(stderr,
247	    gettext("%s: unknown terminfo capability '%s'\n"), progname, cap);
248
249	exit(4);
250	/* NOTREACHED */
251}
252
253/*
254 *  The decision as to whether an argument is a number or not is to simply
255 *  look at whether there are any non-digits in the string.
256 */
257static int
258allnumeric(char *string)
259{
260	if (*string) {
261		while (*string) {
262			if (!isdigit(*string++)) {
263				return (0);
264			}
265		}
266		return (1);
267	} else {
268		return (0);
269	}
270}
271
272/*
273 *  SYSTEM DEPENDENT TERMINAL DELAY TABLES
274 *
275 *	These tables maintain the correspondence between the delays
276 *	defined in terminfo and the delay algorithms in the tty driver
277 *	on the particular systems. For each type of delay, the bits used
278 *	for that delay must be specified, in XXbits, and a table
279 *	must be defined giving correspondences between delays and
280 *	algorithms. Algorithms which are not fixed delays, such
281 *	as dependent on current column or line number, must be
282 *	kludged in some way at this time.
283 *
284 *	Some of this was taken from tset(1).
285 */
286
287struct delay
288{
289    int d_delay;
290    int d_bits;
291};
292
293/* The appropriate speeds for various termio settings. */
294static int speeds[] = {
295		0,	/*  B0,		*/
296		50,	/*  B50,	*/
297		75,	/*  B75,	*/
298		110,	/*  B110,	*/
299		134,	/*  B134,	*/
300		150,	/*  B150,	*/
301		200,	/*  B200,	*/
302		300,	/*  B300,	*/
303		600,	/*  B600,	*/
304		1200,	/*  B1200,	*/
305		1800,	/*  B1800,	*/
306		2400,	/*  B2400,	*/
307		4800,	/*  B4800,	*/
308		9600,	/*  B9600,	*/
309		19200,	/*  EXTA,	*/
310		38400,	/*  EXTB,	*/
311		57600,	/*  B57600,	*/
312		76800,	/*  B76800,	*/
313		115200,	/*  B115200,	*/
314		153600,	/*  B153600,	*/
315		230400,	/*  B230400,	*/
316		307200,	/*  B307200,	*/
317		460800,	/*  B460800,	*/
318		921600, /*  B921600,	*/
319		0,
320};
321
322#if defined(SYSV) || defined(USG)
323/*	Unix 3.0 on up */
324
325/*    Carriage Return delays	*/
326
327static int	CRbits = CRDLY;
328static struct delay	CRdelay[] =
329{
330	0,	CR0,
331	80,	CR1,
332	100,	CR2,
333	150,	CR3,
334	-1
335};
336
337/*	New Line delays	*/
338
339static int	NLbits = NLDLY;
340static struct delay	NLdelay[] =
341{
342	0,	NL0,
343	100,	NL1,
344	-1
345};
346
347/*	Back Space delays	*/
348
349static int	BSbits = BSDLY;
350static struct delay	BSdelay[] =
351{
352	0,	BS0,
353	50,	BS1,
354	-1
355};
356
357/*	TaB delays	*/
358
359static int	TBbits = TABDLY;
360static struct delay	TBdelay[] =
361{
362	0,	TAB0,
363	11,	TAB1,		/* special M37 delay */
364	100,	TAB2,
365				/* TAB3 is XTABS and not a delay */
366	-1
367};
368
369/*	Form Feed delays	*/
370
371static int	FFbits = FFDLY;
372static struct delay	FFdelay[] =
373{
374	0,	FF0,
375	2000,	FF1,
376	-1
377};
378
379#else	/* BSD */
380
381/*	Carriage Return delays	*/
382
383int	CRbits = CRDELAY;
384struct delay	CRdelay[] =
385{
386	0,	CR0,
387	9,	CR3,
388	80,	CR1,
389	160,	CR2,
390	-1
391};
392
393/*	New Line delays	*/
394
395int	NLbits = NLDELAY;
396struct delay	NLdelay[] =
397{
398	0,	NL0,
399	66,	NL1,		/* special M37 delay */
400	100,	NL2,
401	-1
402};
403
404/*	Tab delays	*/
405
406int	TBbits = TBDELAY;
407struct delay	TBdelay[] =
408{
409	0,	TAB0,
410	11,	TAB1,		/* special M37 delay */
411	-1
412};
413
414/*	Form Feed delays	*/
415
416int	FFbits = VTDELAY;
417struct delay	FFdelay[] =
418{
419	0,	FF0,
420	2000,	FF1,
421	-1
422};
423#endif	/* BSD */
424
425/*
426 *  Initterm, a.k.a. reset_term, does terminal specific initialization. In
427 *  particular, the init_strings from terminfo are output and tabs are
428 *  set, if they aren't hardwired in. Much of this stuff was done by
429 *  the tset(1) program.
430 */
431
432/*
433 *  Figure out how many milliseconds of padding the capability cap
434 *  needs and return that number. Padding is stored in the string as "$<n>",
435 *  where n is the number of milliseconds of padding. More than one
436 *  padding string is allowed within the string, although this is unlikely.
437 */
438
439static int
440getpad(char *cap)
441{
442	int padding = 0;
443
444	/* No padding needed at speeds below padding_baud_rate */
445	if (padding_baud_rate > CurrentBaudRate || cap == NULL)
446		return (0);
447
448	while (*cap) {
449		if ((cap[0] == '$') && (cap[1] == '<')) {
450			cap++;
451			cap++;
452			padding += atoi(cap);
453			while (isdigit (*cap))
454				cap++;
455			while (*cap == '.' || *cap == '/' || *cap == '*' ||
456			    isdigit(*cap))
457				cap++;
458			while (*cap == '>')
459				cap++;
460		} else {
461			cap++;
462		}
463	}
464
465	return (padding);
466}
467
468/*
469 *  Set the appropriate delay bits in the termio structure for
470 *  the given delay.
471 */
472static void
473setdelay(delay, delaytable, bits, flags)
474register int delay;
475struct delay delaytable[];
476int bits;
477#ifdef SYSV
478tcflag_t *flags;
479#else	/* SYSV */
480unsigned short *flags;
481#endif	/* SYSV */
482{
483	register struct delay  *p;
484	register struct delay  *lastdelay;
485
486	/* Clear out the bits, replace with new ones */
487	*flags &= ~bits;
488
489	/* Scan the delay table for first entry with adequate delay */
490	for (lastdelay = p = delaytable;
491	    (p -> d_delay >= 0) && (p -> d_delay < delay);
492	    p++) {
493		lastdelay = p;
494	}
495
496	/* use last entry if none will do */
497	*flags |= lastdelay -> d_bits;
498}
499
500/*
501 * Set the hardware tabs on the terminal, using clear_all_tabs,
502 * set_tab, and column_address capabilities. Cursor_address and cursor_right
503 * may also be used, if necessary.
504 * This is done before the init_file and init_3string, so they can patch in
505 * case we blow this.
506 */
507
508static void
509settabs()
510{
511	register int c;
512
513	/* Do not set tabs if they power up properly. */
514	if (init_tabs == 8)
515		return;
516
517	if (set_tab) {
518		/* Force the cursor to be at the left margin. */
519		if (carriage_return)
520			putp(carriage_return);
521		else
522			(void) putchar('\r');
523
524		/* Clear any current tab settings. */
525		if (clear_all_tabs)
526			putp(clear_all_tabs);
527
528		/* Set the tabs. */
529		for (c = 8; c < columns; c += 8) {
530			/* Get to that column. */
531			(void) fputs("        ", stdout);
532
533			/* Set the tab. */
534			putp(set_tab);
535		}
536
537		/* Get back to the left column. */
538		if (carriage_return)
539			putp(carriage_return);
540		else
541			(void) putchar('\r');
542
543	}
544}
545
546/*
547 *  Copy "file" onto standard output.
548 */
549
550static void
551cat(file)
552char *file;				/* File to copy. */
553{
554	register int fd;			/* File descriptor. */
555	register ssize_t i;			/* Number characters read. */
556	char buf[BUFSIZ];			/* Buffer to read into. */
557
558	fd = open(file, O_RDONLY);
559
560	if (fd < 0) {
561		perror("Cannot open initialization file");
562	} else {
563		while ((i = read(fd, buf, BUFSIZ)) > (ssize_t)0)
564			(void) write(fileno(stdout), buf, (unsigned)i);
565		(int)close(fd);
566	}
567}
568
569/*
570 *  Initialize the terminal.
571 *  Send the initialization strings to the terminal.
572 */
573
574static void
575initterm()
576{
577	register int filedes;		/* File descriptor for ioctl's. */
578#if defined(SYSV) || defined(USG)
579	struct termio termmode;		/* To hold terminal settings. */
580	struct termios termmodes;	/* To hold terminal settings. */
581	int i;
582	int istermios = -1;
583#define	GTTY(fd, mode)	ioctl(fd, TCGETA, mode)
584#define	GTTYS(fd, mode) \
585	(istermios = ioctl(fd, TCGETS, mode))
586#define	STTY(fd, mode)	ioctl(fd, TCSETAW, mode)
587#define	STTYS(fd, mode)	ioctl(fd, TCSETSW, mode)
588#define	SPEED(mode)	(mode.c_cflag & CBAUD)
589#define	SPEEDS(mode)	(cfgetospeed(&mode))
590#define	OFLAG(mode)	mode.c_oflag
591#else	/* BSD */
592	struct sgttyb termmode;		/* To hold terminal settings. */
593#define	GTTY(fd, mode)	gtty(fd, mode)
594#define	STTY(fd, mode)	stty(fd, mode)
595#define	SPEED(mode)	(mode.sg_ospeed & 017)
596#define	OFLAG(mode)	mode.sg_flags
597#define	TAB3		XTABS
598#endif
599
600	/* Get the terminal settings. */
601	/* First try standard output, then standard error, */
602	/* then standard input, then /dev/tty. */
603#ifdef SYSV
604	if ((filedes = 1, GTTYS(filedes, &termmodes) < 0) ||
605	    (filedes = 2, GTTYS(filedes, &termmodes) < 0) ||
606	    (filedes = 0, GTTYS(filedes, &termmodes) < 0) ||
607	    (filedes = open("/dev/tty", O_RDWR),
608	    GTTYS(filedes, &termmodes) < 0)) {
609#endif	/* SYSV */
610		if ((filedes = 1, GTTY(filedes, &termmode) == -1) ||
611		    (filedes = 2, GTTY(filedes, &termmode) == -1) ||
612		    (filedes = 0, GTTY(filedes, &termmode) == -1) ||
613		    (filedes = open("/dev/tty", O_RDWR),
614		    GTTY(filedes, &termmode) == -1)) {
615			filedes = -1;
616			CurrentBaudRate = speeds[B1200];
617		} else
618			CurrentBaudRate = speeds[SPEED(termmode)];
619#ifdef SYSV
620		termmodes.c_lflag = termmode.c_lflag;
621		termmodes.c_oflag = termmode.c_oflag;
622		termmodes.c_iflag = termmode.c_iflag;
623		termmodes.c_cflag = termmode.c_cflag;
624		for (i = 0; i < NCC; i++)
625			termmodes.c_cc[i] = termmode.c_cc[i];
626	} else
627		CurrentBaudRate = speeds[SPEEDS(termmodes)];
628#endif	/* SYSV */
629
630	if (xon_xoff) {
631#ifdef SYSV
632		OFLAG(termmodes) &=
633		    ~(NLbits | CRbits | BSbits | FFbits | TBbits);
634#else	/* SYSV */
635		OFLAG(termmode) &=
636		    ~(NLbits | CRbits | BSbits | FFbits | TBbits);
637#endif	/* SYSV */
638	} else {
639#ifdef SYSV
640		setdelay(getpad(carriage_return),
641		    CRdelay, CRbits, &OFLAG(termmodes));
642		setdelay(getpad(scroll_forward),
643		    NLdelay, NLbits, &OFLAG(termmodes));
644		setdelay(getpad(cursor_left),
645		    BSdelay, BSbits, &OFLAG(termmodes));
646		setdelay(getpad(form_feed),
647		    FFdelay, FFbits, &OFLAG(termmodes));
648		setdelay(getpad(tab),
649		    TBdelay, TBbits, &OFLAG(termmodes));
650#else	/* SYSV */
651		setdelay(getpad(carriage_return),
652		    CRdelay, CRbits, &OFLAG(termmode));
653		setdelay(getpad(scroll_forward),
654		    NLdelay, NLbits, &OFLAG(termmode));
655		setdelay(getpad(cursor_left),
656		    BSdelay, BSbits, &OFLAG(termmode));
657		setdelay(getpad(form_feed),
658		    FFdelay, FFbits, &OFLAG(termmode));
659		setdelay(getpad(tab),
660		    TBdelay, TBbits, &OFLAG(termmode));
661#endif	/* SYSV */
662	}
663
664	/* If tabs can be sent to the tty, turn off their expansion. */
665	if (tab && set_tab || init_tabs == 8) {
666#ifdef SYSV
667		OFLAG(termmodes) &= ~(TAB3);
668#else	/* SYSV */
669		OFLAG(termmode) &= ~(TAB3);
670#endif	/* SYSV */
671	} else {
672#ifdef SYSV
673		OFLAG(termmodes) |= TAB3;
674#else	/* SYSV */
675		OFLAG(termmode) |= TAB3;
676#endif	/* SYSV */
677	}
678
679	/* Do the changes to the terminal settings */
680#ifdef SYSV
681	if (istermios < 0) {
682		int i;
683
684		termmode.c_lflag = termmodes.c_lflag;
685		termmode.c_oflag = termmodes.c_oflag;
686		termmode.c_iflag = termmodes.c_iflag;
687		termmode.c_cflag = termmodes.c_cflag;
688		for (i = 0; i < NCC; i++)
689			termmode.c_cc[i] = termmodes.c_cc[i];
690		(void) STTY(filedes, &termmode);
691	} else
692		(void) STTYS(filedes, &termmodes);
693
694#else	/* SYSV */
695	(void) STTY(filedes, &termmode);
696#endif	/* SYSV */
697
698	/* Send first initialization strings. */
699	if (init_prog)
700	(void) system(init_prog);
701
702	if (reset && reset_1string) {
703		putp(reset_1string);
704	} else if (init_1string) {
705		putp(init_1string);
706	}
707
708	if (reset && reset_2string) {
709		putp(reset_2string);
710	} else if (init_2string) {
711		putp(init_2string);
712	}
713
714	/* Set up the tabs stops. */
715	settabs();
716
717	/* Send out initializing file. */
718	if (reset && reset_file) {
719		cat(reset_file);
720	} else if (init_file) {
721		cat(init_file);
722	}
723
724	/* Send final initialization strings. */
725	if (reset && reset_3string) {
726		putp(reset_3string);
727	} else if (init_3string) {
728		putp(init_3string);
729	}
730
731	if (carriage_return) {
732		putp(carriage_return);
733	} else {
734		(void) putchar('\r');
735	}
736
737	/* Send color initialization strings */
738
739	if (orig_colors)
740		putp(orig_colors);
741
742	if (orig_pair)
743	putp(orig_pair);
744
745	/* Let the terminal settle down. */
746	(void) fflush(stdout);
747	(void) sleep(1);
748}
749
750static void
751reset_term()
752{
753	reset++;
754	initterm();
755}
756