xref: /netbsd-src/usr.bin/resize/resize.c (revision 66a2df677d651ddc19b09b511f39746a461f094f)
1 /* $XTermId: resize.c,v 1.144 2020/06/03 00:26:23 tom Exp $ */
2 
3 /*
4  * Copyright 2003-2018,2020 by Thomas E. Dickey
5  *
6  *                         All Rights Reserved
7  *
8  * Permission is hereby granted, free of charge, to any person obtaining a
9  * copy of this software and associated documentation files (the
10  * "Software"), to deal in the Software without restriction, including
11  * without limitation the rights to use, copy, modify, merge, publish,
12  * distribute, sublicense, and/or sell copies of the Software, and to
13  * permit persons to whom the Software is furnished to do so, subject to
14  * the following conditions:
15  *
16  * The above copyright notice and this permission notice shall be included
17  * in all copies or substantial portions of the Software.
18  *
19  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22  * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
23  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26  *
27  * Except as contained in this notice, the name(s) of the above copyright
28  * holders shall not be used in advertising or otherwise to promote the
29  * sale, use or other dealings in this Software without prior written
30  * authorization.
31  *
32  *
33  * Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts.
34  *
35  *                         All Rights Reserved
36  *
37  * Permission to use, copy, modify, and distribute this software and its
38  * documentation for any purpose and without fee is hereby granted,
39  * provided that the above copyright notice appear in all copies and that
40  * both that copyright notice and this permission notice appear in
41  * supporting documentation, and that the name of Digital Equipment
42  * Corporation not be used in advertising or publicity pertaining to
43  * distribution of the software without specific, written prior permission.
44  *
45  *
46  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
47  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
48  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
49  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
50  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
51  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
52  * SOFTWARE.
53  */
54 
55 /* resize.c */
56 
57 #include <stdio.h>
58 #include <ctype.h>
59 
60 #ifdef RESIZE_ONLY
61 #include "resize.h"
62 #else
63 #include <xterm.h>
64 #include <version.h>
65 #include <xstrings.h>
66 #include <xtermcap.h>
67 #include <xterm_io.h>
68 #endif
69 
70 #ifndef USE_TERMINFO		/* avoid conflict with configure script */
71 #if defined(__QNX__) || defined(__SCO__) || defined(linux) || defined(__OpenBSD__) || defined(__UNIXWARE__)
72 #define USE_TERMINFO
73 #endif
74 #endif
75 
76 #if defined(__QNX__)
77 #include <unix.h>
78 #endif
79 
80 /*
81  * Some OS's may want to use both, like SCO for example.  We catch here anyone
82  * who hasn't decided what they want.
83  */
84 #if !defined(USE_TERMCAP) && !defined(USE_TERMINFO)
85 #define USE_TERMINFO
86 #endif
87 
88 #include <signal.h>
89 #include <pwd.h>
90 
91 #ifdef USE_IGNORE_RC
92 int ignore_unused;
93 #endif
94 
95 #ifdef __MVS__
96 #define ESCAPE(string) "\047" string
97 #else
98 #define ESCAPE(string) "\033" string
99 #endif
100 
101 #define	EMULATIONS	2
102 #define	SUN		1
103 #define	VT100		0
104 
105 #define	TIMEOUT		10
106 
107 #define	SHELL_UNKNOWN	0
108 #define	SHELL_C		1
109 #define	SHELL_BOURNE	2
110 
111 /* *INDENT-OFF* */
112 static struct {
113     const char *name;
114     int type;
115 } shell_list[] = {
116     { "csh",	SHELL_C },	/* vanilla cshell */
117     { "jcsh",   SHELL_C },
118     { "tcsh",   SHELL_C },
119     { "sh",	SHELL_BOURNE }, /* vanilla Bourne shell */
120     { "ash",    SHELL_BOURNE },
121     { "bash",	SHELL_BOURNE }, /* GNU Bourne again shell */
122     { "dash",	SHELL_BOURNE },
123     { "jsh",    SHELL_BOURNE },
124     { "ksh",	SHELL_BOURNE }, /* Korn shell (from AT&T toolchest) */
125     { "ksh-i",	SHELL_BOURNE }, /* another name for Korn shell */
126     { "ksh93",	SHELL_BOURNE }, /* Korn shell */
127     { "mksh",   SHELL_BOURNE },
128     { "pdksh",  SHELL_BOURNE },
129     { "zsh",    SHELL_BOURNE },
130     { NULL,	SHELL_BOURNE }	/* default (same as xterm's) */
131 };
132 /* *INDENT-ON* */
133 
134 static const char *const emuname[EMULATIONS] =
135 {
136     "VT100",
137     "Sun",
138 };
139 static char *myname;
140 static int shell_type = SHELL_UNKNOWN;
141 static const char *const getsize[EMULATIONS] =
142 {
143     ESCAPE("7") ESCAPE("[r") ESCAPE("[9999;9999H") ESCAPE("[6n"),
144     ESCAPE("[18t"),
145 };
146 #if defined(USE_STRUCT_WINSIZE)
147 static const char *const getwsize[EMULATIONS] =
148 {				/* size in pixels */
149     0,
150     ESCAPE("[14t"),
151 };
152 #endif /* USE_STRUCT_WINSIZE */
153 static const char *const restore[EMULATIONS] =
154 {
155     ESCAPE("8"),
156     0,
157 };
158 static const char *const setsize[EMULATIONS] =
159 {
160     0,
161     ESCAPE("[8;%s;%st"),
162 };
163 
164 #ifdef USE_ANY_SYSV_TERMIO
165 static struct termio tioorig;
166 #elif defined(USE_TERMIOS)
167 static struct termios tioorig;
168 #else
169 static struct sgttyb sgorig;
170 #endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
171 
172 static const char *const size[EMULATIONS] =
173 {
174     ESCAPE("[%d;%dR"),
175     ESCAPE("[8;%d;%dt"),
176 };
177 static const char sunname[] = "sunsize";
178 static int tty;
179 static FILE *ttyfp;
180 
181 #if defined(USE_STRUCT_WINSIZE)
182 static const char *wsize[EMULATIONS] =
183 {
184     0,
185     ESCAPE("[4;%hd;%hdt"),
186 };
187 #endif /* USE_STRUCT_WINSIZE */
188 
189 static void failed(const char *) GCC_NORETURN;
190 static void onintr(int) GCC_NORETURN;
191 static void resize_timeout(int) GCC_NORETURN;
192 static void Usage(void) GCC_NORETURN;
193 
194 static void
failed(const char * s)195 failed(const char *s)
196 {
197     int save = errno;
198     IGNORE_RC(write(2, myname, strlen(myname)));
199     IGNORE_RC(write(2, ": ", (size_t) 2));
200     errno = save;
201     perror(s);
202     exit(EXIT_FAILURE);
203 }
204 
205 /* ARGSUSED */
206 static void
onintr(int sig GCC_UNUSED)207 onintr(int sig GCC_UNUSED)
208 {
209 #ifdef USE_ANY_SYSV_TERMIO
210     (void) ioctl(tty, TCSETAW, &tioorig);
211 #elif defined(USE_TERMIOS)
212     (void) tcsetattr(tty, TCSADRAIN, &tioorig);
213 #else /* not USE_TERMIOS */
214     (void) ioctl(tty, TIOCSETP, &sgorig);
215 #endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
216     exit(EXIT_FAILURE);
217 }
218 
219 static void
resize_timeout(int sig)220 resize_timeout(int sig)
221 {
222     fprintf(stderr, "\n%s: Time out occurred\r\n", myname);
223     onintr(sig);
224 }
225 
226 static void
Usage(void)227 Usage(void)
228 {
229     fprintf(stderr, strcmp(myname, sunname) == 0 ?
230 	    "Usage: %s [rows cols]\n" :
231 	    "Usage: %s [-v] [-u] [-c] [-s [rows cols]]\n", myname);
232     exit(EXIT_FAILURE);
233 }
234 
235 #ifdef USE_TERMCAP
236 static void
print_termcap(const char * termcap)237 print_termcap(const char *termcap)
238 {
239     int ch;
240 
241     putchar('\'');
242     while ((ch = *termcap++) != '\0') {
243 	switch (ch & 0xff) {
244 	case 127:		/* undo bug in GNU termcap */
245 	    printf("^?");
246 	    break;
247 	case '\'':		/* must escape anyway (unlikely) */
248 	    /* FALLTHRU */
249 	case '!':		/* must escape for SunOS csh */
250 	    putchar('\\');
251 	    /* FALLTHRU */
252 	default:
253 	    putchar(ch);
254 	    break;
255 	}
256     }
257     putchar('\'');
258 }
259 #endif /* USE_TERMCAP */
260 
261 static int
checkdigits(char * str)262 checkdigits(char *str)
263 {
264     while (*str) {
265 	if (!isdigit(CharOf(*str)))
266 	    return (0);
267 	str++;
268     }
269     return (1);
270 }
271 
272 static void
readstring(FILE * fp,char * buf,const char * str)273 readstring(FILE *fp, char *buf, const char *str)
274 {
275     int last, c;
276 #if !defined(USG) && !defined(__minix)
277     /* What is the advantage of setitimer() over alarm()? */
278     struct itimerval it;
279 #endif
280 
281     signal(SIGALRM, resize_timeout);
282 #if defined(USG) || defined(__minix)
283     alarm(TIMEOUT);
284 #else
285     memset((char *) &it, 0, sizeof(struct itimerval));
286     it.it_value.tv_sec = TIMEOUT;
287     setitimer(ITIMER_REAL, &it, (struct itimerval *) NULL);
288 #endif
289     if ((c = getc(fp)) == 0233) {	/* meta-escape, CSI */
290 	c = ESCAPE("")[0];
291 	*buf++ = (char) c;
292 	*buf++ = '[';
293     } else {
294 	*buf++ = (char) c;
295     }
296     if (c != *str) {
297 	fprintf(stderr, "%s: unknown character, exiting.\r\n", myname);
298 	onintr(0);
299     }
300     last = str[strlen(str) - 1];
301     while ((*buf++ = (char) getc(fp)) != last) {
302 	;
303     }
304 #if defined(USG) || defined(__minix)
305     alarm(0);
306 #else
307     memset((char *) &it, 0, sizeof(struct itimerval));
308     setitimer(ITIMER_REAL, &it, (struct itimerval *) NULL);
309 #endif
310     *buf = 0;
311 }
312 
313 /*
314    resets termcap string to reflect current screen size
315  */
316 int
main(int argc,char ** argv ENVP_ARG)317 main(int argc, char **argv ENVP_ARG)
318 {
319 #ifdef USE_TERMCAP
320     char *env;
321 #endif
322     char *ptr;
323     int emu = VT100;
324     char *shell;
325     int i;
326     int rc;
327     int rows, cols;
328 #ifdef USE_ANY_SYSV_TERMIO
329     struct termio tio;
330 #elif defined(USE_TERMIOS)
331     struct termios tio;
332 #else
333     struct sgttyb sg;
334 #endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
335 #ifdef USE_TERMCAP
336     int ok_tcap = 1;
337     char termcap[TERMCAP_SIZE];
338     char newtc[TERMCAP_SIZE];
339 #endif /* USE_TERMCAP */
340     char buf[BUFSIZ];
341 #ifdef TTYSIZE_STRUCT
342     TTYSIZE_STRUCT ts;
343 #endif
344     char *name_of_tty;
345 #ifdef CANT_OPEN_DEV_TTY
346     extern char *ttyname();
347 #endif
348     const char *setname = "";
349 
350     myname = x_basename(argv[0]);
351     if (strcmp(myname, sunname) == 0)
352 	emu = SUN;
353     for (argv++, argc--; argc > 0 && **argv == '-'; argv++, argc--) {
354 	switch ((*argv)[1]) {
355 	case 's':		/* Sun emulation */
356 	    if (emu == SUN)
357 		Usage();	/* Never returns */
358 	    emu = SUN;
359 	    break;
360 	case 'u':		/* Bourne (Unix) shell */
361 	    shell_type = SHELL_BOURNE;
362 	    break;
363 	case 'c':		/* C shell */
364 	    shell_type = SHELL_C;
365 	    break;
366 	case 'v':
367 	    printf("%s\n", xtermVersion());
368 	    exit(EXIT_SUCCESS);
369 	default:
370 	    Usage();		/* Never returns */
371 	}
372     }
373 
374     if (SHELL_UNKNOWN == shell_type) {
375 	/* Find out what kind of shell this user is running.
376 	 * This is the same algorithm that xterm uses.
377 	 */
378 	if ((ptr = x_getenv("SHELL")) == NULL) {
379 	    uid_t uid = getuid();
380 	    struct passwd pw;
381 
382 	    if (x_getpwuid(uid, &pw)) {
383 		(void) x_getlogin(uid, &pw);
384 	    }
385 	    if (!OkPasswd(&pw)
386 		|| *(ptr = pw.pw_shell) == 0) {
387 		/* this is the same default that xterm uses */
388 		ptr = x_strdup("/bin/sh");
389 	    }
390 	}
391 
392 	shell = x_basename(ptr);
393 
394 	/* now that we know, what kind is it? */
395 	for (i = 0; shell_list[i].name; i++) {
396 	    if (!strcmp(shell_list[i].name, shell)) {
397 		break;
398 	    }
399 	}
400 	shell_type = shell_list[i].type;
401     }
402 
403     if (argc == 2) {
404 	if (!setsize[emu]) {
405 	    fprintf(stderr,
406 		    "%s: Can't set window size under %s emulation\n",
407 		    myname, emuname[emu]);
408 	    exit(EXIT_FAILURE);
409 	}
410 	if (!checkdigits(argv[0]) || !checkdigits(argv[1])) {
411 	    Usage();		/* Never returns */
412 	}
413     } else if (argc != 0) {
414 	Usage();		/* Never returns */
415     }
416 #ifdef CANT_OPEN_DEV_TTY
417     if ((name_of_tty = ttyname(fileno(stderr))) == NULL)
418 #endif
419 	name_of_tty = x_strdup("/dev/tty");
420 
421     if ((ttyfp = fopen(name_of_tty, "r+")) == NULL) {
422 	fprintf(stderr, "%s:  can't open terminal %s\n",
423 		myname, name_of_tty);
424 	exit(EXIT_FAILURE);
425     }
426     tty = fileno(ttyfp);
427 #ifdef USE_TERMCAP
428     if ((env = x_getenv("TERM")) == 0) {
429 	env = x_strdup(DFT_TERMTYPE);
430 	if (SHELL_BOURNE == shell_type) {
431 	    setname = "TERM=" DFT_TERMTYPE ";\nexport TERM;\n";
432 	} else {
433 	    setname = "setenv TERM " DFT_TERMTYPE ";\n";
434 	}
435     }
436     termcap[0] = 0;		/* ...just in case we've accidentally gotten terminfo */
437     if (tgetent(termcap, env) <= 0 || termcap[0] == 0) {
438 	ok_tcap = 0;
439     }
440 #endif /* USE_TERMCAP */
441 #ifdef USE_TERMINFO
442     if (x_getenv("TERM") == 0) {
443 	if (SHELL_BOURNE == shell_type) {
444 	    setname = "TERM=" DFT_TERMTYPE ";\nexport TERM;\n";
445 	} else {
446 	    setname = "setenv TERM " DFT_TERMTYPE ";\n";
447 	}
448     }
449 #endif /* USE_TERMINFO */
450 
451 #ifdef USE_ANY_SYSV_TERMIO
452     rc = ioctl(tty, TCGETA, &tioorig);
453     tio = tioorig;
454     UIntClr(tio.c_iflag, (ICRNL | IUCLC));
455     UIntClr(tio.c_lflag, (ICANON | ECHO));
456     tio.c_cflag |= CS8;
457     tio.c_cc[VMIN] = 6;
458     tio.c_cc[VTIME] = 1;
459 #elif defined(USE_TERMIOS)
460     rc = tcgetattr(tty, &tioorig);
461     tio = tioorig;
462     UIntClr(tio.c_iflag, ICRNL);
463     UIntClr(tio.c_lflag, (ICANON | ECHO));
464     tio.c_cflag |= CS8;
465     tio.c_cc[VMIN] = 6;
466     tio.c_cc[VTIME] = 1;
467 #else /* not USE_TERMIOS */
468     rc = ioctl(tty, TIOCGETP, &sgorig);
469     sg = sgorig;
470     sg.sg_flags |= RAW;
471     UIntClr(sg.sg_flags, ECHO);
472 #endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
473     if (rc != 0)
474 	failed("get tty settings");
475 
476     signal(SIGINT, onintr);
477     signal(SIGQUIT, onintr);
478     signal(SIGTERM, onintr);
479 
480 #ifdef USE_ANY_SYSV_TERMIO
481     rc = ioctl(tty, TCSETAW, &tio);
482 #elif defined(USE_TERMIOS)
483     rc = tcsetattr(tty, TCSADRAIN, &tio);
484 #else /* not USE_TERMIOS */
485     rc = ioctl(tty, TIOCSETP, &sg);
486 #endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
487     if (rc != 0)
488 	failed("set tty settings");
489 
490     if (argc == 2) {		/* look for optional parameters of "-s" */
491 	char *tmpbuf = TypeMallocN(char,
492 				   strlen(setsize[emu]) +
493 				   strlen(argv[0]) +
494 				   strlen(argv[1]) +
495 				   1);
496 	if (tmpbuf == 0) {
497 	    fprintf(stderr, "%s: Cannot query size\n", myname);
498 	    onintr(0);
499 	} else {
500 	    sprintf(tmpbuf, setsize[emu], argv[0], argv[1]);
501 	    IGNORE_RC(write(tty, tmpbuf, strlen(tmpbuf)));
502 	    free(tmpbuf);
503 	}
504     }
505     IGNORE_RC(write(tty, getsize[emu], strlen(getsize[emu])));
506     readstring(ttyfp, buf, size[emu]);
507     if (sscanf(buf, size[emu], &rows, &cols) != 2) {
508 	fprintf(stderr, "%s: Can't get rows and columns\r\n", myname);
509 	onintr(0);
510     }
511     if (restore[emu])
512 	IGNORE_RC(write(tty, restore[emu], strlen(restore[emu])));
513 #if defined(USE_STRUCT_WINSIZE)
514     /* finally, set the tty's window size */
515     if (getwsize[emu]) {
516 	/* get the window size in pixels */
517 	IGNORE_RC(write(tty, getwsize[emu], strlen(getwsize[emu])));
518 	readstring(ttyfp, buf, wsize[emu]);
519 	if (sscanf(buf, wsize[emu], &ts.ws_xpixel, &ts.ws_ypixel) != 2) {
520 	    fprintf(stderr, "%s: Can't get window size\r\n", myname);
521 	    onintr(0);
522 	}
523 	setup_winsize(ts, rows, cols, 0, 0);
524 	SET_TTYSIZE(tty, ts);
525     } else if (ioctl(tty, TIOCGWINSZ, &ts) != -1) {
526 	/* we don't have any way of directly finding out
527 	   the current height & width of the window in pixels.  We try
528 	   our best by computing the font height and width from the "old"
529 	   window-size values, and multiplying by these ratios... */
530 #define scaled(old,new,len) (old)?((unsigned)(new)*(len)/(old)):(len)
531 	setup_winsize(ts, rows, cols,
532 		      scaled(TTYSIZE_ROWS(ts), rows, ts.ws_ypixel),
533 		      scaled(TTYSIZE_COLS(ts), cols, ts.ws_xpixel));
534 	SET_TTYSIZE(tty, ts);
535     }
536 #endif /* USE_STRUCT_WINSIZE */
537 
538 #ifdef USE_ANY_SYSV_TERMIO
539     rc = ioctl(tty, TCSETAW, &tioorig);
540 #elif defined(USE_TERMIOS)
541     rc = tcsetattr(tty, TCSADRAIN, &tioorig);
542 #else /* not USE_TERMIOS */
543     rc = ioctl(tty, TIOCSETP, &sgorig);
544 #endif /* USE_ANY_SYSV_TERMIO/USE_TERMIOS */
545     if (rc != 0)
546 	failed("set tty settings");
547 
548     signal(SIGINT, SIG_DFL);
549     signal(SIGQUIT, SIG_DFL);
550     signal(SIGTERM, SIG_DFL);
551 
552 #ifdef USE_TERMCAP
553     if (ok_tcap) {
554 	/* update termcap string */
555 	/* first do columns */
556 	if ((ptr = x_strindex(termcap, "co#")) == NULL) {
557 	    fprintf(stderr, "%s: No `co#'\n", myname);
558 	    exit(EXIT_FAILURE);
559 	}
560 
561 	i = (int) (ptr - termcap) + 3;
562 	strncpy(newtc, termcap, (size_t) i);
563 	sprintf(newtc + i, "%d", cols);
564 	if ((ptr = strchr(ptr, ':')) != 0)
565 	    strcat(newtc, ptr);
566 
567 	/* now do lines */
568 	if ((ptr = x_strindex(newtc, "li#")) == NULL) {
569 	    fprintf(stderr, "%s: No `li#'\n", myname);
570 	    exit(EXIT_FAILURE);
571 	}
572 
573 	i = (int) (ptr - newtc) + 3;
574 	strncpy(termcap, newtc, (size_t) i);
575 	sprintf(termcap + i, "%d", rows);
576 	if ((ptr = strchr(ptr, ':')) != 0)
577 	    strcat(termcap, ptr);
578     }
579 #endif /* USE_TERMCAP */
580 
581     if (SHELL_BOURNE == shell_type) {
582 
583 #ifdef USE_TERMCAP
584 	if (ok_tcap) {
585 	    printf("%sTERMCAP=", setname);
586 	    print_termcap(termcap);
587 	    printf(";\nexport TERMCAP;\n");
588 	}
589 #endif /* USE_TERMCAP */
590 #ifdef USE_TERMINFO
591 	printf("%sCOLUMNS=%d;\nLINES=%d;\nexport COLUMNS LINES;\n",
592 	       setname, cols, rows);
593 #endif /* USE_TERMINFO */
594 
595     } else {			/* not Bourne shell */
596 
597 #ifdef USE_TERMCAP
598 	if (ok_tcap) {
599 	    printf("set noglob;\n%ssetenv TERMCAP ", setname);
600 	    print_termcap(termcap);
601 	    printf(";\nunset noglob;\n");
602 	}
603 #endif /* USE_TERMCAP */
604 #ifdef USE_TERMINFO
605 	printf("set noglob;\n%ssetenv COLUMNS '%d';\nsetenv LINES '%d';\nunset noglob;\n",
606 	       setname, cols, rows);
607 #endif /* USE_TERMINFO */
608     }
609     exit(EXIT_SUCCESS);
610 }
611