xref: /openbsd-src/games/hack/hack.pager.c (revision 50b7afb2c2c0993b0894d4e34bf857cb13ed9c80)
1 /*	$OpenBSD: hack.pager.c,v 1.20 2014/03/11 08:05:15 guenther 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	<sys/types.h>
69 #include	<libgen.h>
70 #include	<signal.h>
71 #include	<stdio.h>
72 #include	<stdlib.h>
73 #include	<unistd.h>
74 #include "hack.h"
75 extern int CO, LI;	/* usually COLNO and ROWNO+2 */
76 extern char *CD;
77 extern char quitchars[];
78 
79 static void page_more(FILE *, int);
80 
81 int
82 dowhatis()
83 {
84 	FILE *fp;
85 	char bufr[BUFSZ+6];
86 	char *buf = &bufr[6], q;
87 	size_t len;
88 	extern char readchar();
89 
90 	if (!(fp = fopen(DATAFILE, "r")))
91 		pline("Cannot open data file!");
92 	else {
93 		pline("Specify what? ");
94 		q = readchar();
95 		if (q != '\t')
96 			while (fgets(buf,BUFSZ,fp))
97 				if (*buf == q) {
98 					len = strcspn(buf, "\n");
99 					/* bad data file */
100 					if (len == 0)
101 						continue;
102 					buf[len] = '\0';
103 					/* Expand tab 'by hand' */
104 					if (buf[1] == '\t'){
105 						buf = bufr;
106 						buf[0] = q;
107 						(void) strncpy(buf+1, "       ", 7);
108 						len = strlen(buf);
109 					}
110 					pline(buf);
111 					if (buf[len - 1] == ';') {
112 						pline("More info? ");
113 						if (readchar() == 'y') {
114 							page_more(fp,1); /* does fclose() */
115 							return(0);
116 						}
117 					}
118 					(void) fclose(fp); 	/* kopper@psuvax1 */
119 					return(0);
120 				}
121 		pline("I've never heard of such things.");
122 		(void) fclose(fp);
123 	}
124 	return(0);
125 }
126 
127 /* make the paging of a file interruptible */
128 static int got_intrup;
129 
130 void
131 intruph(int notused)
132 {
133 	got_intrup++;
134 }
135 
136 /* simple pager, also used from dohelp() */
137 /* strip: nr of chars to be stripped from each line (0 or 1) */
138 static void
139 page_more(FILE *fp, int strip)
140 {
141 	char *bufr;
142 	sig_t prevsig = signal(SIGINT, intruph);
143 
144 	set_pager(0);
145 	bufr = (char *) alloc((unsigned) CO);
146 	while (fgets(bufr, CO, fp) && (!strip || *bufr == '\t') &&
147 	    !got_intrup) {
148 		bufr[strcspn(bufr, "\n")] = '\0';
149 		if (page_line(bufr+strip)) {
150 			set_pager(2);
151 			goto ret;
152 		}
153 	}
154 	set_pager(1);
155 ret:
156 	free(bufr);
157 	(void) fclose(fp);
158 	(void) signal(SIGINT, prevsig);
159 	got_intrup = 0;
160 }
161 
162 static boolean whole_screen = TRUE;
163 #define	PAGMIN	12	/* minimum # of lines for page below level map */
164 
165 void
166 set_whole_screen()
167 {	/* called in termcap as soon as LI is known */
168 	whole_screen = (LI-ROWNO-2 <= PAGMIN || !CD);
169 }
170 
171 #ifdef NEWS
172 int
173 readnews()
174 {
175 	int ret;
176 
177 	whole_screen = TRUE;	/* force a docrt(), our first */
178 	ret = page_file(NEWS, TRUE);
179 	set_whole_screen();
180 	return(ret);		/* report whether we did docrt() */
181 }
182 #endif /* NEWS */
183 
184 /* 0: open  1: wait+close  2: close */
185 void
186 set_pager(int mode)
187 {
188 	static boolean so;
189 	if(mode == 0) {
190 		if(!whole_screen) {
191 			/* clear topline */
192 			clrlin();
193 			/* use part of screen below level map */
194 			curs(1, ROWNO+4);
195 		} else {
196 			cls();
197 		}
198 		so = flags.standout;
199 		flags.standout = 1;
200 	} else {
201 		if(mode == 1) {
202 			curs(1, LI);
203 			more();
204 		}
205 		flags.standout = so;
206 		if(whole_screen)
207 			docrt();
208 		else {
209 			curs(1, ROWNO+4);
210 			cl_eos();
211 		}
212 	}
213 }
214 
215 int
216 page_line(char *s)		/* returns 1 if we should quit */
217 {
218 	extern char morc;
219 
220 	if(cury == LI-1) {
221 		if(!*s)
222 			return(0);	/* suppress blank lines at top */
223 		putchar('\n');
224 		cury++;
225 		cmore("q\033");
226 		if(morc) {
227 			morc = 0;
228 			return(1);
229 		}
230 		if(whole_screen)
231 			cls();
232 		else {
233 			curs(1, ROWNO+4);
234 			cl_eos();
235 		}
236 	}
237 	puts(s);
238 	cury++;
239 	return(0);
240 }
241 
242 /*
243  * Flexible pager: feed it with a number of lines and it will decide
244  * whether these should be fed to the pager above, or displayed in a
245  * corner.
246  * Call:
247  *	cornline(0, title or 0)	: initialize
248  *	cornline(1, text)	: add text to the chain of texts
249  *	cornline(2, morcs)	: output everything and cleanup
250  *	cornline(3, 0)		: cleanup
251  */
252 void
253 cornline(int mode, char *text)
254 {
255 	static struct line {
256 		struct line *next_line;
257 		char *line_text;
258 	} *texthead, *texttail;
259 	static int maxlen;
260 	static int linect;
261 	struct line *tl;
262 
263 	if(mode == 0) {
264 		texthead = 0;
265 		maxlen = 0;
266 		linect = 0;
267 		if(text) {
268 			cornline(1, text);	/* title */
269 			cornline(1, "");	/* blank line */
270 		}
271 		return;
272 	}
273 
274 	if(mode == 1) {
275 	    int len;
276 
277 	    if(!text) return;	/* superfluous, just to be sure */
278 	    linect++;
279 	    len = strlen(text);
280 	    if(len > maxlen)
281 		maxlen = len;
282 	    tl = (struct line *)
283 		alloc((unsigned)(len + sizeof(struct line) + 1));
284 	    tl->next_line = 0;
285 	    tl->line_text = (char *)(tl + 1);
286 	    (void) strlcpy(tl->line_text, text, len + 1);
287 	    if(!texthead)
288 		texthead = tl;
289 	    else
290 		texttail->next_line = tl;
291 	    texttail = tl;
292 	    return;
293 	}
294 
295 	/* --- now we really do it --- */
296 	if(mode == 2 && linect == 1)			    /* topline only */
297 		pline(texthead->line_text);
298 	else
299 	if(mode == 2) {
300 	    int curline, lth;
301 
302 	    if(flags.toplin == 1) more();	/* ab@unido */
303 	    remember_topl();
304 
305 	    lth = CO - maxlen - 2;		   /* Use full screen width */
306 	    if (linect < LI && lth >= 10) {		     /* in a corner */
307 		home();
308 		cl_end();
309 		flags.toplin = 0;
310 		curline = 1;
311 		for (tl = texthead; tl; tl = tl->next_line) {
312 		    curs(lth, curline);
313 		    if(curline > 1)
314 			cl_end();
315 		    putsym(' ');
316 		    putstr (tl->line_text);
317 		    curline++;
318 		}
319 		curs(lth, curline);
320 		cl_end();
321 		cmore(text);
322 		home();
323 		cl_end();
324 		docorner(lth, curline-1);
325 	    } else {					/* feed to pager */
326 		set_pager(0);
327 		for (tl = texthead; tl; tl = tl->next_line) {
328 		    if (page_line (tl->line_text)) {
329 			set_pager(2);
330 			goto cleanup;
331 		    }
332 		}
333 		if(text) {
334 			cgetret(text);
335 			set_pager(2);
336 		} else
337 			set_pager(1);
338 	    }
339 	}
340 
341 cleanup:
342 	while ((tl = texthead)) {
343 		texthead = tl->next_line;
344 		free(tl);
345 	}
346 }
347 
348 int
349 dohelp()
350 {
351 	char c;
352 
353 	pline ("Long or short help? ");
354 	while (((c = readchar ()) != 'l') && (c != 's') && !strchr(quitchars,c))
355 		hackbell ();
356 	if (!strchr(quitchars, c))
357 		(void) page_file((c == 'l') ? HELP : SHELP, FALSE);
358 	return(0);
359 }
360 
361 /* return: 0 - cannot open fnam; 1 - otherwise */
362 int
363 page_file(char *fnam, boolean silent)
364 {
365 #ifdef DEF_PAGER			/* this implies that UNIX is defined */
366       {
367 	/* use external pager; this may give security problems */
368 
369 	int fd = open(fnam, O_RDONLY);
370 
371 	if(fd < 0) {
372 		if(!silent) pline("Cannot open %s.", fnam);
373 		return(0);
374 	}
375 	if(child(1)){
376 		extern char *catmore;
377 
378 		/* Now that child() does a setuid(getuid()) and a chdir(),
379 		   we may not be able to open file fnam anymore, so make
380 		   it stdin. */
381 		(void) close(0);
382 		if(dup(fd)) {
383 			if(!silent) printf("Cannot open %s as stdin.\n", fnam);
384 		} else {
385 			execlp(catmore, basename(catmore), (char *) 0);
386 			if(!silent) printf("Cannot exec %s.\n", catmore);
387 		}
388 		exit(1);
389 	}
390 	(void) close(fd);
391       }
392 #else /* DEF_PAGER */
393       {
394 	FILE *f;			/* free after Robert Viduya */
395 
396 	if ((f = fopen (fnam, "r")) == (FILE *) 0) {
397 		if(!silent) {
398 			home(); perror (fnam); flags.toplin = 1;
399 			pline ("Cannot open %s.", fnam);
400 		}
401 		return(0);
402 	}
403 	page_more(f, 0);
404       }
405 #endif /* DEF_PAGER */
406 
407 	return(1);
408 }
409 
410 #ifdef UNIX
411 #ifdef SHELL
412 int
413 dosh()
414 {
415 	char *str;
416 
417 	if(child(0)) {
418 		if ((str = getenv("SHELL")))
419 			execlp(str, str, (char *) 0);
420 		else
421 			execl("/bin/sh", "sh", (char *) 0);
422 		pline("sh: cannot execute.");
423 		exit(1);
424 	}
425 	return(0);
426 }
427 #endif /* SHELL */
428 
429 #include	<sys/wait.h>
430 
431 int
432 child(int wt)
433 {
434 	int status;
435 	int f;
436 	char *home;
437 	gid_t gid;
438 
439 	f = fork();
440 	if(f == 0){		/* child */
441 		settty((char *) 0);		/* also calls end_screen() */
442 		/* revoke privs */
443 		gid = getgid();
444 		setresgid(gid, gid, gid);
445 #ifdef CHDIR
446 		home = getenv("HOME");
447 		if (home == NULL || *home == '\0')
448 			home = "/";
449 		(void) chdir(home);
450 #endif /* CHDIR */
451 		return(1);
452 	}
453 	if(f == -1) {	/* cannot fork */
454 		pline("Fork failed. Try again.");
455 		return(0);
456 	}
457 	/* fork succeeded; wait for child to exit */
458 	(void) signal(SIGINT,SIG_IGN);
459 	(void) signal(SIGQUIT,SIG_IGN);
460 	(void) wait(&status);
461 	gettty();
462 	setftty();
463 	(void) signal(SIGINT,done1);
464 #ifdef WIZARD
465 	if(wizard) (void) signal(SIGQUIT,SIG_DFL);
466 #endif /* WIZARD */
467 	if(wt) getret();
468 	docrt();
469 	return(0);
470 }
471 #endif /* UNIX */
472