1 /* $OpenBSD: tput.c,v 1.30 2023/10/17 09:52:11 nicm Exp $ */
2
3 /****************************************************************************
4 * Copyright 2018-2022,2023 Thomas E. Dickey *
5 * Copyright 1998-2016,2017 Free Software Foundation, Inc. *
6 * *
7 * Permission is hereby granted, free of charge, to any person obtaining a *
8 * copy of this software and associated documentation files (the *
9 * "Software"), to deal in the Software without restriction, including *
10 * without limitation the rights to use, copy, modify, merge, publish, *
11 * distribute, distribute with modifications, sublicense, and/or sell *
12 * copies of the Software, and to permit persons to whom the Software is *
13 * furnished to do so, subject to the following conditions: *
14 * *
15 * The above copyright notice and this permission notice shall be included *
16 * in all copies or substantial portions of the Software. *
17 * *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS *
19 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
21 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, *
22 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR *
23 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR *
24 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
25 * *
26 * Except as contained in this notice, the name(s) of the above copyright *
27 * holders shall not be used in advertising or otherwise to promote the *
28 * sale, use or other dealings in this Software without prior written *
29 * authorization. *
30 ****************************************************************************/
31
32 /****************************************************************************
33 * Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995 *
34 * and: Eric S. Raymond <esr@snark.thyrsus.com> *
35 * and: Thomas E. Dickey 1996-on *
36 ****************************************************************************/
37
38 /*
39 * tput.c -- shellscript access to terminal capabilities
40 *
41 * by Eric S. Raymond <esr@snark.thyrsus.com>, portions based on code from
42 * Ross Ridge's mytinfo package.
43 */
44
45 #include <tparm_type.h>
46 #include <clear_cmd.h>
47 #include <reset_cmd.h>
48
49 #include <transform.h>
50 #include <tty_settings.h>
51
52
53
54 #define PUTS(s) fputs(s, stdout)
55
56 const char *_nc_progname = "tput";
57
58 static bool opt_v = FALSE; /* quiet, do not show warnings */
59 static bool opt_x = FALSE; /* clear scrollback if possible */
60
61 static bool is_init = FALSE;
62 static bool is_reset = FALSE;
63 static bool is_clear = FALSE;
64
65 static GCC_NORETURN void
quit(int status,const char * fmt,...)66 quit(int status, const char *fmt, ...)
67 {
68 va_list argp;
69
70 va_start(argp, fmt);
71 fprintf(stderr, "%s: ", _nc_progname);
72 vfprintf(stderr, fmt, argp);
73 fprintf(stderr, "\n");
74 va_end(argp);
75 ExitProgram(status);
76 }
77
78 static GCC_NORETURN void
usage(const char * optstring)79 usage(const char *optstring)
80 {
81 #define KEEP(s) s "\n"
82 static const char msg[] =
83 {
84 KEEP("")
85 KEEP("Options:")
86 KEEP(" -S << read commands from standard input")
87 KEEP(" -T TERM use this instead of $TERM")
88 KEEP(" -V print curses-version")
89 KEEP(" -v verbose, show warnings")
90 KEEP(" -x do not try to clear scrollback")
91 KEEP("")
92 KEEP("Commands:")
93 KEEP(" clear clear the screen")
94 KEEP(" init initialize the terminal")
95 KEEP(" reset reinitialize the terminal")
96 KEEP(" capname unlike clear/init/reset, print value for capability \"capname\"")
97 };
98 #undef KEEP
99 (void) fprintf(stderr, "Usage: %s [options] [command]\n", _nc_progname);
100 if (optstring != NULL) {
101 const char *s = msg;
102 while (*s != '\0') {
103 fputc(UChar(*s), stderr);
104 if (!strncmp(s, " -", 3)) {
105 if (strchr(optstring, s[3]) == NULL)
106 s = strchr(s, '\n') + 1;
107 } else if (!strncmp(s, "\n\nC", 3))
108 break;
109 ++s;
110 }
111 } else {
112 fputs(msg, stderr);
113 }
114 ExitProgram(ErrUsage);
115 }
116
117 static char *
check_aliases(char * name,bool program)118 check_aliases(char *name, bool program)
119 {
120 static char my_init[] = "init";
121 static char my_reset[] = "reset";
122 static char my_clear[] = "clear";
123
124 char *result = name;
125 if ((is_init = same_program(name, program ? PROG_INIT : my_init)))
126 result = my_init;
127 if ((is_reset = same_program(name, program ? PROG_RESET : my_reset)))
128 result = my_reset;
129 if ((is_clear = same_program(name, program ? PROG_CLEAR : my_clear)))
130 result = my_clear;
131 return result;
132 }
133
134 static int
exit_code(int token,int value)135 exit_code(int token, int value)
136 {
137 int result = 99;
138
139 switch (token) {
140 case BOOLEAN:
141 result = !value; /* TRUE=0, FALSE=1 */
142 break;
143 case NUMBER:
144 result = 0; /* always zero */
145 break;
146 case STRING:
147 result = value; /* 0=normal, 1=missing */
148 break;
149 }
150 return result;
151 }
152
153 /*
154 * Returns nonzero on error.
155 */
156 static int
tput_cmd(int fd,TTY * settings,int argc,char ** argv,int * used)157 tput_cmd(int fd, TTY * settings, int argc, char **argv, int *used)
158 {
159 NCURSES_CONST char *name;
160 char *s;
161 int status;
162 #if !PURE_TERMINFO
163 bool termcap = FALSE;
164 #endif
165
166 name = check_aliases(argv[0], FALSE);
167 *used = 1;
168 if (is_reset || is_init) {
169 TTY oldmode = *settings;
170
171 int terasechar = -1; /* new erase character */
172 int intrchar = -1; /* new interrupt character */
173 int tkillchar = -1; /* new kill character */
174
175 if (is_reset) {
176 reset_start(stdout, TRUE, FALSE);
177 reset_tty_settings(fd, settings, FALSE);
178 } else {
179 reset_start(stdout, FALSE, TRUE);
180 }
181
182 #if HAVE_SIZECHANGE
183 set_window_size(fd, &lines, &columns);
184 #else
185 (void) fd;
186 #endif
187 set_control_chars(settings, terasechar, intrchar, tkillchar);
188 set_conversions(settings);
189
190 if (send_init_strings(fd, &oldmode)) {
191 reset_flush();
192 }
193
194 update_tty_settings(&oldmode, settings);
195 return 0;
196 }
197
198 if (strcmp(name, "longname") == 0) {
199 PUTS(longname());
200 return 0;
201 }
202 #if !PURE_TERMINFO
203 retry:
204 #endif
205 if (strcmp(name, "clear") == 0) {
206 return (clear_cmd(opt_x) == ERR) ? ErrUsage : 0;
207 } else if ((status = tigetflag(name)) != -1) {
208 return exit_code(BOOLEAN, status);
209 } else if ((status = tigetnum(name)) != CANCELLED_NUMERIC) {
210 (void) printf("%d\n", status);
211 return exit_code(NUMBER, 0);
212 } else if ((s = tigetstr(name)) == CANCELLED_STRING) {
213 #if !PURE_TERMINFO
214 if (!termcap) {
215 const struct name_table_entry *np;
216
217 termcap = TRUE;
218 if ((np = _nc_find_entry(name, _nc_get_hash_table(termcap))) != 0) {
219 switch (np->nte_type) {
220 case BOOLEAN:
221 name = boolnames[np->nte_index];
222 break;
223
224 case NUMBER:
225 name = numnames[np->nte_index];
226 break;
227
228 case STRING:
229 name = strnames[np->nte_index];
230 break;
231 }
232 goto retry;
233 }
234 }
235 #endif
236 quit(ErrCapName, "unknown terminfo capability '%s'", name);
237 } else if (VALID_STRING(s)) {
238 if (argc > 1) {
239 int k;
240 int narg;
241 int analyzed;
242 int provided;
243 int popcount;
244 long numbers[1 + NUM_PARM];
245 char *strings[1 + NUM_PARM];
246 char *p_is_s[NUM_PARM];
247 TParams paramType;
248
249 /* Nasty hack time. The tparm function needs to see numeric
250 * parameters as numbers, not as pointers to their string
251 * representations
252 */
253
254 for (k = 1; (k < argc) && (k <= NUM_PARM); k++) {
255 char *tmp = 0;
256 strings[k] = argv[k];
257 numbers[k] = strtol(argv[k], &tmp, 0);
258 if (tmp == 0 || *tmp != 0)
259 numbers[k] = 0;
260 }
261 for (k = argc; k <= NUM_PARM; k++) {
262 numbers[k] = 0;
263 strings[k] = 0;
264 }
265
266 paramType = tparm_type(name);
267 #if NCURSES_XNAMES
268 /*
269 * If the capability is an extended one, analyze the string.
270 */
271 if (paramType == Numbers) {
272 struct name_table_entry const *entry_ptr;
273 entry_ptr = _nc_find_type_entry(name, STRING, FALSE);
274 if (entry_ptr == NULL) {
275 paramType = Other;
276 }
277 }
278 #endif
279
280 popcount = 0;
281 _nc_reset_tparm(NULL);
282 /*
283 * Count the number of numeric parameters which are provided.
284 */
285 provided = 0;
286 for (narg = 1; narg < argc; ++narg) {
287 char *ending = NULL;
288 long check = strtol(argv[narg], &ending, 10);
289 if (check < 0 || ending == argv[narg] || *ending != '\0')
290 break;
291 provided = narg;
292 }
293 switch (paramType) {
294 case Str:
295 s = TPARM_1(s, strings[1]);
296 analyzed = 1;
297 if (provided == 0 && argc >= 1)
298 provided++;
299 break;
300 case Str_Str:
301 s = TPARM_2(s, strings[1], strings[2]);
302 analyzed = 2;
303 if (provided == 0 && argc >= 1)
304 provided++;
305 if (provided == 1 && argc >= 2)
306 provided++;
307 break;
308 case Num_Str:
309 s = TPARM_2(s, numbers[1], strings[2]);
310 analyzed = 2;
311 if (provided == 1 && argc >= 2)
312 provided++;
313 break;
314 case Num_Str_Str:
315 s = TPARM_3(s, numbers[1], strings[2], strings[3]);
316 analyzed = 3;
317 if (provided == 1 && argc >= 2)
318 provided++;
319 if (provided == 2 && argc >= 3)
320 provided++;
321 break;
322 case Numbers:
323 analyzed = _nc_tparm_analyze(NULL, s, p_is_s, &popcount);
324 #define myParam(n) numbers[n]
325 s = TIPARM_9(s,
326 myParam(1),
327 myParam(2),
328 myParam(3),
329 myParam(4),
330 myParam(5),
331 myParam(6),
332 myParam(7),
333 myParam(8),
334 myParam(9));
335 #undef myParam
336 break;
337 case Other:
338 /* FALLTHRU */
339 default:
340 analyzed = _nc_tparm_analyze(NULL, s, p_is_s, &popcount);
341 #define myParam(n) (p_is_s[n - 1] != 0 ? ((TPARM_ARG) strings[n]) : numbers[n])
342 s = TPARM_9(s,
343 myParam(1),
344 myParam(2),
345 myParam(3),
346 myParam(4),
347 myParam(5),
348 myParam(6),
349 myParam(7),
350 myParam(8),
351 myParam(9));
352 #undef myParam
353 break;
354 }
355 if (analyzed < popcount) {
356 analyzed = popcount;
357 }
358 if (opt_v && (analyzed != provided)) {
359 fprintf(stderr, "%s: %s parameters for \"%s\"\n",
360 _nc_progname,
361 (analyzed < provided ? "extra" : "missing"),
362 argv[0]);
363 }
364 *used += provided;
365 }
366
367 /* use putp() in order to perform padding */
368 putp(s);
369 return exit_code(STRING, 0);
370 }
371 return exit_code(STRING, 1);
372 }
373
374 int
main(int argc,char ** argv)375 main(int argc, char **argv)
376 {
377 char *term;
378 int errret;
379 bool cmdline = TRUE;
380 int c;
381 char buf[BUFSIZ];
382 int result = 0;
383 int fd;
384 int used;
385 TTY old_settings;
386 TTY tty_settings;
387 bool is_alias;
388 bool need_tty;
389
390 if (pledge("stdio rpath wpath tty", NULL) == -1) {
391 perror("pledge");
392 exit(1);
393 }
394
395 _nc_progname = check_aliases(_nc_rootname(argv[0]), TRUE);
396 is_alias = (is_clear || is_reset || is_init);
397
398 term = getenv("TERM");
399
400 while ((c = getopt(argc, argv, is_alias ? "T:Vvx" : "ST:Vvx")) != -1) {
401 switch (c) {
402 case 'S':
403 cmdline = FALSE;
404 break;
405 case 'T':
406 use_env(FALSE);
407 use_tioctl(TRUE);
408 term = optarg;
409 break;
410 case 'V':
411 puts(curses_version());
412 ExitProgram(EXIT_SUCCESS);
413 case 'v': /* verbose */
414 opt_v = TRUE;
415 break;
416 case 'x': /* do not try to clear scrollback */
417 opt_x = TRUE;
418 break;
419 default:
420 usage(is_alias ? "TVx" : NULL);
421 /* NOTREACHED */
422 }
423 }
424
425 need_tty = ((is_reset || is_init) ||
426 (optind < argc &&
427 (!strcmp(argv[optind], "reset") ||
428 !strcmp(argv[optind], "init"))));
429
430 /*
431 * Modify the argument list to omit the options we processed.
432 */
433 if (is_alias) {
434 if (optind-- < argc) {
435 argc -= optind;
436 argv += optind;
437 }
438 argv[0] = strdup(_nc_progname);
439 } else {
440 argc -= optind;
441 argv += optind;
442 }
443
444 if (term == 0 || *term == '\0')
445 quit(ErrUsage, "No value for $TERM and no -T specified");
446
447 fd = save_tty_settings(&tty_settings, need_tty);
448 old_settings = tty_settings;
449
450 if (setupterm(term, fd, &errret) != OK && errret <= 0)
451 quit(ErrTermType, "unknown terminal \"%s\"", term);
452
453 if (cmdline) {
454 int code = 0;
455 if ((argc <= 0) && !is_alias)
456 usage(NULL);
457 while (argc > 0) {
458 tty_settings = old_settings;
459 code = tput_cmd(fd, &tty_settings, argc, argv, &used);
460 if (code != 0)
461 break;
462 argc -= used;
463 argv += used;
464 }
465 ExitProgram(code);
466 }
467
468 while (fgets(buf, sizeof(buf), stdin) != 0) {
469 size_t need = strlen(buf);
470 char **argvec = typeCalloc(char *, need + 1);
471 char **argnow;
472 int argnum = 0;
473 char *cp;
474
475 if (argvec == NULL) {
476 quit(ErrSystem(1), strerror(errno));
477 }
478
479 /* split the buffer into tokens */
480 for (cp = buf; *cp; cp++) {
481 if (isspace(UChar(*cp))) {
482 *cp = '\0';
483 } else if (cp == buf || cp[-1] == '\0') {
484 argvec[argnum++] = cp;
485 if (argnum >= (int) need)
486 break;
487 }
488 }
489
490 argnow = argvec;
491 while (argnum > 0) {
492 int code;
493 tty_settings = old_settings;
494 code = tput_cmd(fd, &tty_settings, argnum, argnow, &used);
495 if (code != 0) {
496 if (result == 0)
497 result = ErrSystem(0); /* will return value >4 */
498 ++result;
499 }
500 argnum -= used;
501 argnow += used;
502 }
503 free(argvec);
504 }
505
506 ExitProgram(result);
507 }
508