xref: /openbsd/games/hack/hack.pager.c (revision df69c215)
1 /*	$OpenBSD: hack.pager.c,v 1.25 2019/06/28 13:32:52 deraadt Exp $	*/
2 
3 /*
4  * Copyright (c) 1985, Stichting Centrum voor Wiskunde en Informatica,
5  * Amsterdam
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are
10  * met:
11  *
12  * - Redistributions of source code must retain the above copyright notice,
13  * this list of conditions and the following disclaimer.
14  *
15  * - Redistributions in binary form must reproduce the above copyright
16  * notice, this list of conditions and the following disclaimer in the
17  * documentation and/or other materials provided with the distribution.
18  *
19  * - Neither the name of the Stichting Centrum voor Wiskunde en
20  * Informatica, nor the names of its contributors may be used to endorse or
21  * promote products derived from this software without specific prior
22  * written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
25  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
26  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
27  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
28  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
30  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
31  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
32  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
33  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
34  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35  */
36 
37 /*
38  * Copyright (c) 1982 Jay Fenlason <hack@gnu.org>
39  * All rights reserved.
40  *
41  * Redistribution and use in source and binary forms, with or without
42  * modification, are permitted provided that the following conditions
43  * are met:
44  * 1. Redistributions of source code must retain the above copyright
45  *    notice, this list of conditions and the following disclaimer.
46  * 2. Redistributions in binary form must reproduce the above copyright
47  *    notice, this list of conditions and the following disclaimer in the
48  *    documentation and/or other materials provided with the distribution.
49  * 3. The name of the author may not be used to endorse or promote products
50  *    derived from this software without specific prior written permission.
51  *
52  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
53  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
54  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
55  * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
56  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
57  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
58  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
59  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
60  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
61  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
62  */
63 
64 /* This file contains the command routine dowhatis() and a pager. */
65 /* Also readmail() and doshell(), and generally the things that
66    contact the outside world. */
67 
68 #include <libgen.h>
69 #include <signal.h>
70 #include <stdio.h>
71 #include <stdlib.h>
72 #include <unistd.h>
73 
74 #include "hack.h"
75 
76 extern int CO, LI;	/* usually COLNO and ROWNO+2 */
77 extern char *CD;
78 extern char quitchars[];
79 
80 static void page_more(FILE *, int);
81 
82 int
dowhatis(void)83 dowhatis(void)
84 {
85 	FILE *fp;
86 	char bufr[BUFSZ+6];
87 	char *buf = &bufr[6], q;
88 	size_t len;
89 	extern char readchar();
90 
91 	if (!(fp = fopen(DATAFILE, "r")))
92 		pline("Cannot open data file!");
93 	else {
94 		pline("Specify what? ");
95 		q = readchar();
96 		if (q != '\t')
97 			while (fgets(buf,BUFSZ,fp))
98 				if (*buf == q) {
99 					len = strcspn(buf, "\n");
100 					/* bad data file */
101 					if (len == 0)
102 						continue;
103 					buf[len] = '\0';
104 					/* Expand tab 'by hand' */
105 					if (buf[1] == '\t'){
106 						buf = bufr;
107 						buf[0] = q;
108 						(void) strncpy(buf+1, "       ", 7);
109 						len = strlen(buf);
110 					}
111 					pline("%s", buf);
112 					if (buf[len - 1] == ';') {
113 						pline("More info? ");
114 						if (readchar() == 'y') {
115 							page_more(fp,1); /* does fclose() */
116 							return(0);
117 						}
118 					}
119 					(void) fclose(fp); 	/* kopper@psuvax1 */
120 					return(0);
121 				}
122 		pline("I've never heard of such things.");
123 		(void) fclose(fp);
124 	}
125 	return(0);
126 }
127 
128 /* make the paging of a file interruptible */
129 static int got_intrup;
130 
131 void
intruph(int notused)132 intruph(int notused)
133 {
134 	got_intrup++;
135 }
136 
137 /* simple pager, also used from dohelp() */
138 /* strip: nr of chars to be stripped from each line (0 or 1) */
139 static void
page_more(FILE * fp,int strip)140 page_more(FILE *fp, int strip)
141 {
142 	char *bufr;
143 	sig_t prevsig = signal(SIGINT, intruph);
144 
145 	set_pager(0);
146 	bufr = (char *) alloc((unsigned) CO);
147 	while (fgets(bufr, CO, fp) && (!strip || *bufr == '\t') &&
148 	    !got_intrup) {
149 		bufr[strcspn(bufr, "\n")] = '\0';
150 		if (page_line(bufr+strip)) {
151 			set_pager(2);
152 			goto ret;
153 		}
154 	}
155 	set_pager(1);
156 ret:
157 	free(bufr);
158 	(void) fclose(fp);
159 	(void) signal(SIGINT, prevsig);
160 	got_intrup = 0;
161 }
162 
163 static boolean whole_screen = TRUE;
164 #define	PAGMIN	12	/* minimum # of lines for page below level map */
165 
166 void
set_whole_screen(void)167 set_whole_screen(void)
168 {	/* called in termcap as soon as LI is known */
169 	whole_screen = (LI-ROWNO-2 <= PAGMIN || !CD);
170 }
171 
172 #ifdef NEWS
173 int
readnews(void)174 readnews(void)
175 {
176 	int ret;
177 
178 	whole_screen = TRUE;	/* force a docrt(), our first */
179 	ret = page_file(NEWS, TRUE);
180 	set_whole_screen();
181 	return(ret);		/* report whether we did docrt() */
182 }
183 #endif /* NEWS */
184 
185 /* 0: open  1: wait+close  2: close */
186 void
set_pager(int mode)187 set_pager(int mode)
188 {
189 	static boolean so;
190 	if(mode == 0) {
191 		if(!whole_screen) {
192 			/* clear topline */
193 			clrlin();
194 			/* use part of screen below level map */
195 			curs(1, ROWNO+4);
196 		} else {
197 			cls();
198 		}
199 		so = flags.standout;
200 		flags.standout = 1;
201 	} else {
202 		if(mode == 1) {
203 			curs(1, LI);
204 			more();
205 		}
206 		flags.standout = so;
207 		if(whole_screen)
208 			docrt();
209 		else {
210 			curs(1, ROWNO+4);
211 			cl_eos();
212 		}
213 	}
214 }
215 
216 int
page_line(char * s)217 page_line(char *s)		/* returns 1 if we should quit */
218 {
219 	extern char morc;
220 
221 	if(cury == LI-1) {
222 		if(!*s)
223 			return(0);	/* suppress blank lines at top */
224 		putchar('\n');
225 		cury++;
226 		cmore("q\033");
227 		if(morc) {
228 			morc = 0;
229 			return(1);
230 		}
231 		if(whole_screen)
232 			cls();
233 		else {
234 			curs(1, ROWNO+4);
235 			cl_eos();
236 		}
237 	}
238 	puts(s);
239 	cury++;
240 	return(0);
241 }
242 
243 /*
244  * Flexible pager: feed it with a number of lines and it will decide
245  * whether these should be fed to the pager above, or displayed in a
246  * corner.
247  * Call:
248  *	cornline(0, title or 0)	: initialize
249  *	cornline(1, text)	: add text to the chain of texts
250  *	cornline(2, morcs)	: output everything and cleanup
251  *	cornline(3, 0)		: cleanup
252  */
253 void
cornline(int mode,char * text)254 cornline(int mode, char *text)
255 {
256 	static struct line {
257 		struct line *next_line;
258 		char *line_text;
259 	} *texthead, *texttail;
260 	static int maxlen;
261 	static int linect;
262 	struct line *tl;
263 
264 	if(mode == 0) {
265 		texthead = 0;
266 		maxlen = 0;
267 		linect = 0;
268 		if(text) {
269 			cornline(1, text);	/* title */
270 			cornline(1, "");	/* blank line */
271 		}
272 		return;
273 	}
274 
275 	if(mode == 1) {
276 	    int len;
277 
278 	    if(!text) return;	/* superfluous, just to be sure */
279 	    linect++;
280 	    len = strlen(text);
281 	    if(len > maxlen)
282 		maxlen = len;
283 	    tl = (struct line *)
284 		alloc((unsigned)(len + sizeof(struct line) + 1));
285 	    tl->next_line = 0;
286 	    tl->line_text = (char *)(tl + 1);
287 	    (void) strlcpy(tl->line_text, text, len + 1);
288 	    if(!texthead)
289 		texthead = tl;
290 	    else
291 		texttail->next_line = tl;
292 	    texttail = tl;
293 	    return;
294 	}
295 
296 	/* --- now we really do it --- */
297 	if(mode == 2 && linect == 1)			    /* topline only */
298 		pline("%s", texthead->line_text);
299 	else
300 	if(mode == 2) {
301 	    int curline, lth;
302 
303 	    if(flags.toplin == 1) more();	/* ab@unido */
304 	    remember_topl();
305 
306 	    lth = CO - maxlen - 2;		   /* Use full screen width */
307 	    if (linect < LI && lth >= 10) {		     /* in a corner */
308 		home();
309 		cl_end();
310 		flags.toplin = 0;
311 		curline = 1;
312 		for (tl = texthead; tl; tl = tl->next_line) {
313 		    curs(lth, curline);
314 		    if(curline > 1)
315 			cl_end();
316 		    putsym(' ');
317 		    putstr (tl->line_text);
318 		    curline++;
319 		}
320 		curs(lth, curline);
321 		cl_end();
322 		cmore(text);
323 		home();
324 		cl_end();
325 		docorner(lth, curline-1);
326 	    } else {					/* feed to pager */
327 		set_pager(0);
328 		for (tl = texthead; tl; tl = tl->next_line) {
329 		    if (page_line (tl->line_text)) {
330 			set_pager(2);
331 			goto cleanup;
332 		    }
333 		}
334 		if(text) {
335 			cgetret(text);
336 			set_pager(2);
337 		} else
338 			set_pager(1);
339 	    }
340 	}
341 
342 cleanup:
343 	while ((tl = texthead)) {
344 		texthead = tl->next_line;
345 		free(tl);
346 	}
347 }
348 
349 int
dohelp(void)350 dohelp(void)
351 {
352 	char c;
353 
354 	pline ("Long or short help? ");
355 	while (((c = readchar ()) != 'l') && (c != 's') && !strchr(quitchars,c))
356 		hackbell ();
357 	if (!strchr(quitchars, c))
358 		(void) page_file((c == 'l') ? HELP : SHELP, FALSE);
359 	return(0);
360 }
361 
362 /* return: 0 - cannot open fnam; 1 - otherwise */
363 int
page_file(char * fnam,boolean silent)364 page_file(char *fnam, boolean silent)
365 {
366 #ifdef DEF_PAGER			/* this implies that UNIX is defined */
367       {
368 	/* use external pager; this may give security problems */
369 
370 	int fd = open(fnam, O_RDONLY);
371 
372 	if(fd == -1) {
373 		if(!silent) pline("Cannot open %s.", fnam);
374 		return(0);
375 	}
376 	if(child(1)){
377 		extern char *catmore;
378 
379 		/* Now that child() does a setuid(getuid()) and a chdir(),
380 		   we may not be able to open file fnam anymore, so make
381 		   it stdin. */
382 		(void) close(0);
383 		if(dup(fd)) {
384 			if(!silent) printf("Cannot open %s as stdin.\n", fnam);
385 		} else {
386 			execlp(catmore, basename(catmore), (char *)NULL);
387 			if(!silent) printf("Cannot exec %s.\n", catmore);
388 		}
389 		exit(1);
390 	}
391 	(void) close(fd);
392       }
393 #else /* DEF_PAGER */
394       {
395 	FILE *f;			/* free after Robert Viduya */
396 
397 	if ((f = fopen (fnam, "r")) == (FILE *) 0) {
398 		if(!silent) {
399 			home(); perror (fnam); flags.toplin = 1;
400 			pline ("Cannot open %s.", fnam);
401 		}
402 		return(0);
403 	}
404 	page_more(f, 0);
405       }
406 #endif /* DEF_PAGER */
407 
408 	return(1);
409 }
410 
411 #ifdef UNIX
412 #ifdef SHELL
413 int
dosh(void)414 dosh(void)
415 {
416 	char *str;
417 
418 	if(child(0)) {
419 		if ((str = getenv("SHELL")))
420 			execlp(str, str, (char *)NULL);
421 		else
422 			execl("/bin/sh", "sh", (char *)NULL);
423 		pline("sh: cannot execute.");
424 		exit(1);
425 	}
426 	return(0);
427 }
428 #endif /* SHELL */
429 
430 #include <sys/wait.h>
431 
432 int
child(int wt)433 child(int wt)
434 {
435 	int status;
436 	int f;
437 	char *home;
438 	gid_t gid;
439 
440 	f = fork();
441 	if(f == 0){		/* child */
442 		settty(NULL);		/* also calls end_screen() */
443 		/* revoke privs */
444 		gid = getgid();
445 		setresgid(gid, gid, gid);
446 #ifdef CHDIR
447 		home = getenv("HOME");
448 		if (home == NULL || *home == '\0')
449 			home = "/";
450 		(void) chdir(home);
451 #endif /* CHDIR */
452 		return(1);
453 	}
454 	if(f == -1) {	/* cannot fork */
455 		pline("Fork failed. Try again.");
456 		return(0);
457 	}
458 	/* fork succeeded; wait for child to exit */
459 	(void) signal(SIGINT,SIG_IGN);
460 	(void) signal(SIGQUIT,SIG_IGN);
461 	(void) wait(&status);
462 	gettty();
463 	setftty();
464 	(void) signal(SIGINT,done1);
465 #ifdef WIZARD
466 	if(wizard) (void) signal(SIGQUIT,SIG_DFL);
467 #endif /* WIZARD */
468 	if(wt) getret();
469 	docrt();
470 	return(0);
471 }
472 #endif /* UNIX */
473