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