1 /* NetHack 3.6	files.c	$NHDT-Date: 1576626110 2019/12/17 23:41:50 $  $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.276 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /*-Copyright (c) Derek S. Ray, 2015. */
4 /* NetHack may be freely redistributed.  See license for details. */
5 
6 #define NEED_VARARGS
7 
8 #include "hack.h"
9 #include "dlb.h"
10 
11 #ifdef TTY_GRAPHICS
12 #include "wintty.h" /* more() */
13 #endif
14 
15 #if (!defined(MAC) && !defined(O_WRONLY) && !defined(AZTEC_C)) \
16     || defined(USE_FCNTL)
17 #include <fcntl.h>
18 #endif
19 
20 #include <errno.h>
21 #ifdef _MSC_VER /* MSC 6.0 defines errno quite differently */
22 #if (_MSC_VER >= 600)
23 #define SKIP_ERRNO
24 #endif
25 #else
26 #ifdef NHSTDC
27 #define SKIP_ERRNO
28 #endif
29 #endif
30 #ifndef SKIP_ERRNO
31 #ifdef _DCC
32 const
33 #endif
34     extern int errno;
35 #endif
36 
37 #ifdef ZLIB_COMP /* RLC 09 Mar 1999: Support internal ZLIB */
38 #include "zlib.h"
39 #ifndef COMPRESS_EXTENSION
40 #define COMPRESS_EXTENSION ".gz"
41 #endif
42 #endif
43 
44 #if defined(UNIX) && defined(QT_GRAPHICS)
45 #include <sys/types.h>
46 #include <limits.h>
47 #include <dirent.h>
48 #include <stdlib.h>
49 #endif
50 
51 #if defined(UNIX) || defined(VMS) || !defined(NO_SIGNAL)
52 #include <signal.h>
53 #endif
54 
55 #if defined(MSDOS) || defined(OS2) || defined(TOS) || defined(WIN32)
56 #ifndef __DJGPP__
57 #include <sys\stat.h>
58 #else
59 #include <sys/stat.h>
60 #endif
61 #endif
62 #ifndef O_BINARY /* used for micros, no-op for others */
63 #define O_BINARY 0
64 #endif
65 
66 #ifdef PREFIXES_IN_USE
67 #define FQN_NUMBUF 4
68 static char fqn_filename_buffer[FQN_NUMBUF][FQN_MAX_FILENAME];
69 #endif
70 
71 #if !defined(MFLOPPY) && !defined(VMS) && !defined(WIN32)
72 char bones[] = "bonesnn.xxx";
73 char lock[PL_NSIZ + 14] = "1lock"; /* long enough for uid+name+.99 */
74 #else
75 #if defined(MFLOPPY)
76 char bones[FILENAME]; /* pathname of bones files */
77 char lock[FILENAME];  /* pathname of level files */
78 #endif
79 #if defined(VMS)
80 char bones[] = "bonesnn.xxx;1";
81 char lock[PL_NSIZ + 17] = "1lock"; /* long enough for _uid+name+.99;1 */
82 #endif
83 #if defined(WIN32)
84 char bones[] = "bonesnn.xxx";
85 char lock[PL_NSIZ + 25]; /* long enough for username+-+name+.99 */
86 #endif
87 #endif
88 
89 #if defined(UNIX) || defined(__BEOS__)
90 #define SAVESIZE (PL_NSIZ + 13) /* save/99999player.e */
91 #else
92 #ifdef VMS
93 #define SAVESIZE (PL_NSIZ + 22) /* [.save]<uid>player.e;1 */
94 #else
95 #if defined(WIN32)
96 #define SAVESIZE (PL_NSIZ + 40) /* username-player.NetHack-saved-game */
97 #else
98 #define SAVESIZE FILENAME /* from macconf.h or pcconf.h */
99 #endif
100 #endif
101 #endif
102 
103 #if !defined(SAVE_EXTENSION)
104 #ifdef MICRO
105 #define SAVE_EXTENSION ".sav"
106 #endif
107 #ifdef WIN32
108 #define SAVE_EXTENSION ".NetHack-saved-game"
109 #endif
110 #endif
111 
112 char SAVEF[SAVESIZE]; /* holds relative path of save file from playground */
113 #ifdef MICRO
114 char SAVEP[SAVESIZE]; /* holds path of directory for save file */
115 #endif
116 
117 #ifdef HOLD_LOCKFILE_OPEN
118 struct level_ftrack {
119     int init;
120     int fd;    /* file descriptor for level file     */
121     int oflag; /* open flags                         */
122     boolean nethack_thinks_it_is_open; /* Does NetHack think it's open? */
123 } lftrack;
124 #if defined(WIN32)
125 #include <share.h>
126 #endif
127 #endif /*HOLD_LOCKFILE_OPEN*/
128 
129 #define WIZKIT_MAX 128
130 static char wizkit[WIZKIT_MAX];
131 STATIC_DCL FILE *NDECL(fopen_wizkit_file);
132 STATIC_DCL void FDECL(wizkit_addinv, (struct obj *));
133 
134 #ifdef AMIGA
135 extern char PATH[]; /* see sys/amiga/amidos.c */
136 extern char bbs_id[];
137 static int lockptr;
138 #ifdef __SASC_60
139 #include <proto/dos.h>
140 #endif
141 
142 #include <libraries/dos.h>
143 extern void FDECL(amii_set_text_font, (char *, int));
144 #endif
145 
146 #if defined(WIN32) || defined(MSDOS)
147 static int lockptr;
148 #ifdef MSDOS
149 #define Delay(a) msleep(a)
150 #endif
151 #define Close close
152 #ifndef WIN_CE
153 #define DeleteFile unlink
154 #endif
155 #ifdef WIN32
156 /*from windmain.c */
157 extern char *FDECL(translate_path_variables, (const char *, char *));
158 #endif
159 #endif
160 
161 #ifdef MAC
162 #undef unlink
163 #define unlink macunlink
164 #endif
165 
166 #if (defined(macintosh) && (defined(__SC__) || defined(__MRC__))) \
167     || defined(__MWERKS__)
168 #define PRAGMA_UNUSED
169 #endif
170 
171 #ifdef USER_SOUNDS
172 extern char *sounddir;
173 #endif
174 
175 extern int n_dgns; /* from dungeon.c */
176 
177 #if defined(UNIX) && defined(QT_GRAPHICS)
178 #define SELECTSAVED
179 #endif
180 
181 #ifdef SELECTSAVED
182 STATIC_PTR int FDECL(CFDECLSPEC strcmp_wrap, (const void *, const void *));
183 #endif
184 STATIC_DCL char *FDECL(set_bonesfile_name, (char *, d_level *));
185 STATIC_DCL char *NDECL(set_bonestemp_name);
186 #ifdef COMPRESS
187 STATIC_DCL void FDECL(redirect, (const char *, const char *, FILE *,
188                                  BOOLEAN_P));
189 #endif
190 #if defined(COMPRESS) || defined(ZLIB_COMP)
191 STATIC_DCL void FDECL(docompress_file, (const char *, BOOLEAN_P));
192 #endif
193 #if defined(ZLIB_COMP)
194 STATIC_DCL boolean FDECL(make_compressed_name, (const char *, char *));
195 #endif
196 #ifndef USE_FCNTL
197 STATIC_DCL char *FDECL(make_lockname, (const char *, char *));
198 #endif
199 STATIC_DCL void FDECL(set_configfile_name, (const char *));
200 STATIC_DCL FILE *FDECL(fopen_config_file, (const char *, int));
201 STATIC_DCL int FDECL(get_uchars, (char *, uchar *, BOOLEAN_P,
202                                   int, const char *));
203 boolean FDECL(proc_wizkit_line, (char *));
204 boolean FDECL(parse_config_line, (char *));
205 STATIC_DCL boolean FDECL(parse_conf_file, (FILE *, boolean (*proc)(char *)));
206 STATIC_DCL FILE *NDECL(fopen_sym_file);
207 boolean FDECL(proc_symset_line, (char *));
208 STATIC_DCL void FDECL(set_symhandling, (char *, int));
209 #ifdef NOCWD_ASSUMPTIONS
210 STATIC_DCL void FDECL(adjust_prefix, (char *, int));
211 #endif
212 STATIC_DCL boolean FDECL(config_error_nextline, (const char *));
213 STATIC_DCL void NDECL(free_config_sections);
214 STATIC_DCL char *FDECL(choose_random_part, (char *, CHAR_P));
215 STATIC_DCL boolean FDECL(is_config_section, (const char *));
216 STATIC_DCL boolean FDECL(handle_config_section, (char *));
217 #ifdef SELF_RECOVER
218 STATIC_DCL boolean FDECL(copy_bytes, (int, int));
219 #endif
220 #ifdef HOLD_LOCKFILE_OPEN
221 STATIC_DCL int FDECL(open_levelfile_exclusively, (const char *, int, int));
222 #endif
223 
224 
225 static char *config_section_chosen = (char *) 0;
226 static char *config_section_current = (char *) 0;
227 
228 /*
229  * fname_encode()
230  *
231  *   Args:
232  *      legal       zero-terminated list of acceptable file name characters
233  *      quotechar   lead-in character used to quote illegal characters as
234  *                  hex digits
235  *      s           string to encode
236  *      callerbuf   buffer to house result
237  *      bufsz       size of callerbuf
238  *
239  *   Notes:
240  *      The hex digits 0-9 and A-F are always part of the legal set due to
241  *      their use in the encoding scheme, even if not explicitly included in
242  *      'legal'.
243  *
244  *   Sample:
245  *      The following call:
246  *  (void)fname_encode("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
247  *                     '%', "This is a % test!", buf, 512);
248  *      results in this encoding:
249  *          "This%20is%20a%20%25%20test%21"
250  */
251 char *
fname_encode(legal,quotechar,s,callerbuf,bufsz)252 fname_encode(legal, quotechar, s, callerbuf, bufsz)
253 const char *legal;
254 char quotechar;
255 char *s, *callerbuf;
256 int bufsz;
257 {
258     char *sp, *op;
259     int cnt = 0;
260     static char hexdigits[] = "0123456789ABCDEF";
261 
262     sp = s;
263     op = callerbuf;
264     *op = '\0';
265 
266     while (*sp) {
267         /* Do we have room for one more character or encoding? */
268         if ((bufsz - cnt) <= 4)
269             return callerbuf;
270 
271         if (*sp == quotechar) {
272             (void) sprintf(op, "%c%02X", quotechar, *sp);
273             op += 3;
274             cnt += 3;
275         } else if ((index(legal, *sp) != 0) || (index(hexdigits, *sp) != 0)) {
276             *op++ = *sp;
277             *op = '\0';
278             cnt++;
279         } else {
280             (void) sprintf(op, "%c%02X", quotechar, *sp);
281             op += 3;
282             cnt += 3;
283         }
284         sp++;
285     }
286     return callerbuf;
287 }
288 
289 /*
290  * fname_decode()
291  *
292  *   Args:
293  *      quotechar   lead-in character used to quote illegal characters as
294  *                  hex digits
295  *      s           string to decode
296  *      callerbuf   buffer to house result
297  *      bufsz       size of callerbuf
298  */
299 char *
fname_decode(quotechar,s,callerbuf,bufsz)300 fname_decode(quotechar, s, callerbuf, bufsz)
301 char quotechar;
302 char *s, *callerbuf;
303 int bufsz;
304 {
305     char *sp, *op;
306     int k, calc, cnt = 0;
307     static char hexdigits[] = "0123456789ABCDEF";
308 
309     sp = s;
310     op = callerbuf;
311     *op = '\0';
312     calc = 0;
313 
314     while (*sp) {
315         /* Do we have room for one more character? */
316         if ((bufsz - cnt) <= 2)
317             return callerbuf;
318         if (*sp == quotechar) {
319             sp++;
320             for (k = 0; k < 16; ++k)
321                 if (*sp == hexdigits[k])
322                     break;
323             if (k >= 16)
324                 return callerbuf; /* impossible, so bail */
325             calc = k << 4;
326             sp++;
327             for (k = 0; k < 16; ++k)
328                 if (*sp == hexdigits[k])
329                     break;
330             if (k >= 16)
331                 return callerbuf; /* impossible, so bail */
332             calc += k;
333             sp++;
334             *op++ = calc;
335             *op = '\0';
336         } else {
337             *op++ = *sp++;
338             *op = '\0';
339         }
340         cnt++;
341     }
342     return callerbuf;
343 }
344 
345 #ifdef PREFIXES_IN_USE
346 #define UNUSED_if_not_PREFIXES_IN_USE /*empty*/
347 #else
348 #define UNUSED_if_not_PREFIXES_IN_USE UNUSED
349 #endif
350 
351 /*ARGSUSED*/
352 const char *
fqname(basenam,whichprefix,buffnum)353 fqname(basenam, whichprefix, buffnum)
354 const char *basenam;
355 int whichprefix UNUSED_if_not_PREFIXES_IN_USE;
356 int buffnum UNUSED_if_not_PREFIXES_IN_USE;
357 {
358 #ifdef PREFIXES_IN_USE
359     char *bufptr;
360 #endif
361 #ifdef WIN32
362     char tmpbuf[BUFSZ];
363 #endif
364 
365 #ifndef PREFIXES_IN_USE
366     return basenam;
367 #else
368     if (!basenam || whichprefix < 0 || whichprefix >= PREFIX_COUNT)
369         return basenam;
370     if (!fqn_prefix[whichprefix])
371         return basenam;
372     if (buffnum < 0 || buffnum >= FQN_NUMBUF) {
373         impossible("Invalid fqn_filename_buffer specified: %d", buffnum);
374         buffnum = 0;
375     }
376     bufptr = fqn_prefix[whichprefix];
377 #ifdef WIN32
378     if (strchr(fqn_prefix[whichprefix], '%')
379         || strchr(fqn_prefix[whichprefix], '~'))
380         bufptr = translate_path_variables(fqn_prefix[whichprefix], tmpbuf);
381 #endif
382     if (strlen(bufptr) + strlen(basenam) >= FQN_MAX_FILENAME) {
383         impossible("fqname too long: %s + %s", bufptr, basenam);
384         return basenam; /* XXX */
385     }
386     Strcpy(fqn_filename_buffer[buffnum], bufptr);
387     return strcat(fqn_filename_buffer[buffnum], basenam);
388 #endif /* !PREFIXES_IN_USE */
389 }
390 
391 int
validate_prefix_locations(reasonbuf)392 validate_prefix_locations(reasonbuf)
393 char *reasonbuf; /* reasonbuf must be at least BUFSZ, supplied by caller */
394 {
395 #if defined(NOCWD_ASSUMPTIONS)
396     FILE *fp;
397     const char *filename;
398     int prefcnt, failcount = 0;
399     char panicbuf1[BUFSZ], panicbuf2[BUFSZ];
400     const char *details;
401 #endif
402 
403     if (reasonbuf)
404         reasonbuf[0] = '\0';
405 #if defined(NOCWD_ASSUMPTIONS)
406     for (prefcnt = 1; prefcnt < PREFIX_COUNT; prefcnt++) {
407         /* don't test writing to configdir or datadir; they're readonly */
408         if (prefcnt == SYSCONFPREFIX || prefcnt == CONFIGPREFIX
409             || prefcnt == DATAPREFIX)
410             continue;
411         filename = fqname("validate", prefcnt, 3);
412         if ((fp = fopen(filename, "w"))) {
413             fclose(fp);
414             (void) unlink(filename);
415         } else {
416             if (reasonbuf) {
417                 if (failcount)
418                     Strcat(reasonbuf, ", ");
419                 Strcat(reasonbuf, fqn_prefix_names[prefcnt]);
420             }
421             /* the paniclog entry gets the value of errno as well */
422             Sprintf(panicbuf1, "Invalid %s", fqn_prefix_names[prefcnt]);
423 #if defined(NHSTDC) && !defined(NOTSTDC)
424             if (!(details = strerror(errno)))
425 #endif
426                 details = "";
427             Sprintf(panicbuf2, "\"%s\", (%d) %s", fqn_prefix[prefcnt], errno,
428                     details);
429             paniclog(panicbuf1, panicbuf2);
430             failcount++;
431         }
432     }
433     if (failcount)
434         return 0;
435     else
436 #endif
437         return 1;
438 }
439 
440 /* fopen a file, with OS-dependent bells and whistles */
441 /* NOTE: a simpler version of this routine also exists in util/dlb_main.c */
442 FILE *
fopen_datafile(filename,mode,prefix)443 fopen_datafile(filename, mode, prefix)
444 const char *filename, *mode;
445 int prefix;
446 {
447     FILE *fp;
448 
449     filename = fqname(filename, prefix, prefix == TROUBLEPREFIX ? 3 : 0);
450     fp = fopen(filename, mode);
451     return fp;
452 }
453 
454 /* ----------  BEGIN LEVEL FILE HANDLING ----------- */
455 
456 #ifdef MFLOPPY
457 /* Set names for bones[] and lock[] */
458 void
set_lock_and_bones()459 set_lock_and_bones()
460 {
461     if (!ramdisk) {
462         Strcpy(levels, permbones);
463         Strcpy(bones, permbones);
464     }
465     append_slash(permbones);
466     append_slash(levels);
467 #ifdef AMIGA
468     strncat(levels, bbs_id, PATHLEN);
469 #endif
470     append_slash(bones);
471     Strcat(bones, "bonesnn.*");
472     Strcpy(lock, levels);
473 #ifndef AMIGA
474     Strcat(lock, alllevels);
475 #endif
476     return;
477 }
478 #endif /* MFLOPPY */
479 
480 /* Construct a file name for a level-type file, which is of the form
481  * something.level (with any old level stripped off).
482  * This assumes there is space on the end of 'file' to append
483  * a two digit number.  This is true for 'level'
484  * but be careful if you use it for other things -dgk
485  */
486 void
set_levelfile_name(file,lev)487 set_levelfile_name(file, lev)
488 char *file;
489 int lev;
490 {
491     char *tf;
492 
493     tf = rindex(file, '.');
494     if (!tf)
495         tf = eos(file);
496     Sprintf(tf, ".%d", lev);
497 #ifdef VMS
498     Strcat(tf, ";1");
499 #endif
500     return;
501 }
502 
503 int
create_levelfile(lev,errbuf)504 create_levelfile(lev, errbuf)
505 int lev;
506 char errbuf[];
507 {
508     int fd;
509     const char *fq_lock;
510 
511     if (errbuf)
512         *errbuf = '\0';
513     set_levelfile_name(lock, lev);
514     fq_lock = fqname(lock, LEVELPREFIX, 0);
515 
516 #if defined(MICRO) || defined(WIN32)
517 /* Use O_TRUNC to force the file to be shortened if it already
518  * exists and is currently longer.
519  */
520 #ifdef HOLD_LOCKFILE_OPEN
521     if (lev == 0)
522         fd = open_levelfile_exclusively(
523             fq_lock, lev, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY);
524     else
525 #endif
526         fd = open(fq_lock, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, FCMASK);
527 #else
528 #ifdef MAC
529     fd = maccreat(fq_lock, LEVL_TYPE);
530 #else
531     fd = creat(fq_lock, FCMASK);
532 #endif
533 #endif /* MICRO || WIN32 */
534 
535     if (fd >= 0)
536         level_info[lev].flags |= LFILE_EXISTS;
537     else if (errbuf) /* failure explanation */
538         Sprintf(errbuf, "Cannot create file \"%s\" for level %d (errno %d).",
539                 lock, lev, errno);
540 
541     return fd;
542 }
543 
544 int
open_levelfile(lev,errbuf)545 open_levelfile(lev, errbuf)
546 int lev;
547 char errbuf[];
548 {
549     int fd;
550     const char *fq_lock;
551 
552     if (errbuf)
553         *errbuf = '\0';
554     set_levelfile_name(lock, lev);
555     fq_lock = fqname(lock, LEVELPREFIX, 0);
556 #ifdef MFLOPPY
557     /* If not currently accessible, swap it in. */
558     if (level_info[lev].where != ACTIVE)
559         swapin_file(lev);
560 #endif
561 #ifdef MAC
562     fd = macopen(fq_lock, O_RDONLY | O_BINARY, LEVL_TYPE);
563 #else
564 #ifdef HOLD_LOCKFILE_OPEN
565     if (lev == 0)
566         fd = open_levelfile_exclusively(fq_lock, lev, O_RDONLY | O_BINARY);
567     else
568 #endif
569         fd = open(fq_lock, O_RDONLY | O_BINARY, 0);
570 #endif
571 
572     /* for failure, return an explanation that our caller can use;
573        settle for `lock' instead of `fq_lock' because the latter
574        might end up being too big for nethack's BUFSZ */
575     if (fd < 0 && errbuf)
576         Sprintf(errbuf, "Cannot open file \"%s\" for level %d (errno %d).",
577                 lock, lev, errno);
578 
579     return fd;
580 }
581 
582 void
delete_levelfile(lev)583 delete_levelfile(lev)
584 int lev;
585 {
586     /*
587      * Level 0 might be created by port specific code that doesn't
588      * call create_levfile(), so always assume that it exists.
589      */
590     if (lev == 0 || (level_info[lev].flags & LFILE_EXISTS)) {
591         set_levelfile_name(lock, lev);
592 #ifdef HOLD_LOCKFILE_OPEN
593         if (lev == 0)
594             really_close();
595 #endif
596         (void) unlink(fqname(lock, LEVELPREFIX, 0));
597         level_info[lev].flags &= ~LFILE_EXISTS;
598     }
599 }
600 
601 void
clearlocks()602 clearlocks()
603 {
604 #ifdef HANGUPHANDLING
605     if (program_state.preserve_locks)
606         return;
607 #endif
608 #if !defined(PC_LOCKING) && defined(MFLOPPY) && !defined(AMIGA)
609     eraseall(levels, alllevels);
610     if (ramdisk)
611         eraseall(permbones, alllevels);
612 #else
613     {
614         register int x;
615 
616 #ifndef NO_SIGNAL
617         (void) signal(SIGINT, SIG_IGN);
618 #endif
619 #if defined(UNIX) || defined(VMS)
620         sethanguphandler((void FDECL((*), (int) )) SIG_IGN);
621 #endif
622         /* can't access maxledgerno() before dungeons are created -dlc */
623         for (x = (n_dgns ? maxledgerno() : 0); x >= 0; x--)
624             delete_levelfile(x); /* not all levels need be present */
625     }
626 #endif /* ?PC_LOCKING,&c */
627 }
628 
629 #if defined(SELECTSAVED)
630 /* qsort comparison routine */
631 STATIC_OVL int CFDECLSPEC
strcmp_wrap(p,q)632 strcmp_wrap(p, q)
633 const void *p;
634 const void *q;
635 {
636 #if defined(UNIX) && defined(QT_GRAPHICS)
637     return strncasecmp(*(char **) p, *(char **) q, 16);
638 #else
639     return strncmpi(*(char **) p, *(char **) q, 16);
640 #endif
641 }
642 #endif
643 
644 #ifdef HOLD_LOCKFILE_OPEN
645 STATIC_OVL int
open_levelfile_exclusively(name,lev,oflag)646 open_levelfile_exclusively(name, lev, oflag)
647 const char *name;
648 int lev, oflag;
649 {
650     int reslt, fd;
651 
652     if (!lftrack.init) {
653         lftrack.init = 1;
654         lftrack.fd = -1;
655     }
656     if (lftrack.fd >= 0) {
657         /* check for compatible access */
658         if (lftrack.oflag == oflag) {
659             fd = lftrack.fd;
660             reslt = lseek(fd, 0L, SEEK_SET);
661             if (reslt == -1L)
662                 panic("open_levelfile_exclusively: lseek failed %d", errno);
663             lftrack.nethack_thinks_it_is_open = TRUE;
664         } else {
665             really_close();
666             fd = sopen(name, oflag, SH_DENYRW, FCMASK);
667             lftrack.fd = fd;
668             lftrack.oflag = oflag;
669             lftrack.nethack_thinks_it_is_open = TRUE;
670         }
671     } else {
672         fd = sopen(name, oflag, SH_DENYRW, FCMASK);
673         lftrack.fd = fd;
674         lftrack.oflag = oflag;
675         if (fd >= 0)
676             lftrack.nethack_thinks_it_is_open = TRUE;
677     }
678     return fd;
679 }
680 
681 void
really_close()682 really_close()
683 {
684     int fd;
685 
686     if (lftrack.init) {
687         fd = lftrack.fd;
688 
689         lftrack.nethack_thinks_it_is_open = FALSE;
690         lftrack.fd = -1;
691         lftrack.oflag = 0;
692         if (fd != -1)
693             (void) close(fd);
694     }
695     return;
696 }
697 
698 int
nhclose(fd)699 nhclose(fd)
700 int fd;
701 {
702     if (lftrack.fd == fd) {
703         really_close(); /* close it, but reopen it to hold it */
704         fd = open_levelfile(0, (char *) 0);
705         lftrack.nethack_thinks_it_is_open = FALSE;
706         return 0;
707     }
708     return close(fd);
709 }
710 #else /* !HOLD_LOCKFILE_OPEN */
711 
712 int
nhclose(fd)713 nhclose(fd)
714 int fd;
715 {
716     return close(fd);
717 }
718 #endif /* ?HOLD_LOCKFILE_OPEN */
719 
720 /* ----------  END LEVEL FILE HANDLING ----------- */
721 
722 /* ----------  BEGIN BONES FILE HANDLING ----------- */
723 
724 /* set up "file" to be file name for retrieving bones, and return a
725  * bonesid to be read/written in the bones file.
726  */
727 STATIC_OVL char *
set_bonesfile_name(file,lev)728 set_bonesfile_name(file, lev)
729 char *file;
730 d_level *lev;
731 {
732     s_level *sptr;
733     char *dptr;
734 
735     /*
736      * "bonD0.nn"   = bones for level nn in the main dungeon;
737      * "bonM0.T"    = bones for Minetown;
738      * "bonQBar.n"  = bones for level n in the Barbarian quest;
739      * "bon3D0.nn"  = \
740      * "bon3M0.T"   =  > same as above, but for bones pool #3.
741      * "bon3QBar.n" = /
742      *
743      * Return value for content validation skips "bon" and the
744      * pool number (if present), making it feasible for the admin
745      * to manually move a bones file from one pool to another by
746      * renaming it.
747      */
748     Strcpy(file, "bon");
749 #ifdef SYSCF
750     if (sysopt.bones_pools > 1) {
751         unsigned poolnum = min((unsigned) sysopt.bones_pools, 10);
752 
753         poolnum = (unsigned) ubirthday % poolnum; /* 0..9 */
754         Sprintf(eos(file), "%u", poolnum);
755     }
756 #endif
757     dptr = eos(file); /* this used to be after the following Sprintf()
758                          and the return value was (dptr - 2) */
759     /* when this naming scheme was adopted, 'filecode' was one letter;
760        3.3.0 turned it into a three letter string (via roles[] in role.c);
761        from that version through 3.6.0, 'dptr' pointed past the filecode
762        and the return value of (dptr - 2)  was wrong for bones produced
763        in the quest branch, skipping the boneid character 'Q' and the
764        first letter of the role's filecode; bones loading still worked
765        because the bonesid used for validation had the same error */
766     Sprintf(dptr, "%c%s", dungeons[lev->dnum].boneid,
767             In_quest(lev) ? urole.filecode : "0");
768     if ((sptr = Is_special(lev)) != 0)
769         Sprintf(eos(dptr), ".%c", sptr->boneid);
770     else
771         Sprintf(eos(dptr), ".%d", lev->dlevel);
772 #ifdef VMS
773     Strcat(dptr, ";1");
774 #endif
775     return dptr;
776 }
777 
778 /* set up temporary file name for writing bones, to avoid another game's
779  * trying to read from an uncompleted bones file.  we want an uncontentious
780  * name, so use one in the namespace reserved for this game's level files.
781  * (we are not reading or writing level files while writing bones files, so
782  * the same array may be used instead of copying.)
783  */
784 STATIC_OVL char *
set_bonestemp_name()785 set_bonestemp_name()
786 {
787     char *tf;
788 
789     tf = rindex(lock, '.');
790     if (!tf)
791         tf = eos(lock);
792     Sprintf(tf, ".bn");
793 #ifdef VMS
794     Strcat(tf, ";1");
795 #endif
796     return lock;
797 }
798 
799 int
create_bonesfile(lev,bonesid,errbuf)800 create_bonesfile(lev, bonesid, errbuf)
801 d_level *lev;
802 char **bonesid;
803 char errbuf[];
804 {
805     const char *file;
806     int fd;
807 
808     if (errbuf)
809         *errbuf = '\0';
810     *bonesid = set_bonesfile_name(bones, lev);
811     file = set_bonestemp_name();
812     file = fqname(file, BONESPREFIX, 0);
813 
814 #if defined(MICRO) || defined(WIN32)
815     /* Use O_TRUNC to force the file to be shortened if it already
816      * exists and is currently longer.
817      */
818     fd = open(file, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, FCMASK);
819 #else
820 #ifdef MAC
821     fd = maccreat(file, BONE_TYPE);
822 #else
823     fd = creat(file, FCMASK);
824 #endif
825 #endif
826     if (fd < 0 && errbuf) /* failure explanation */
827         Sprintf(errbuf, "Cannot create bones \"%s\", id %s (errno %d).", lock,
828                 *bonesid, errno);
829 
830 #if defined(VMS) && !defined(SECURE)
831     /*
832        Re-protect bones file with world:read+write+execute+delete access.
833        umask() doesn't seem very reliable; also, vaxcrtl won't let us set
834        delete access without write access, which is what's really wanted.
835        Can't simply create it with the desired protection because creat
836        ANDs the mask with the user's default protection, which usually
837        denies some or all access to world.
838      */
839     (void) chmod(file, FCMASK | 007); /* allow other users full access */
840 #endif /* VMS && !SECURE */
841 
842     return fd;
843 }
844 
845 #ifdef MFLOPPY
846 /* remove partial bonesfile in process of creation */
847 void
cancel_bonesfile()848 cancel_bonesfile()
849 {
850     const char *tempname;
851 
852     tempname = set_bonestemp_name();
853     tempname = fqname(tempname, BONESPREFIX, 0);
854     (void) unlink(tempname);
855 }
856 #endif /* MFLOPPY */
857 
858 /* move completed bones file to proper name */
859 void
commit_bonesfile(lev)860 commit_bonesfile(lev)
861 d_level *lev;
862 {
863     const char *fq_bones, *tempname;
864     int ret;
865 
866     (void) set_bonesfile_name(bones, lev);
867     fq_bones = fqname(bones, BONESPREFIX, 0);
868     tempname = set_bonestemp_name();
869     tempname = fqname(tempname, BONESPREFIX, 1);
870 
871 #if (defined(SYSV) && !defined(SVR4)) || defined(GENIX)
872     /* old SYSVs don't have rename.  Some SVR3's may, but since they
873      * also have link/unlink, it doesn't matter. :-)
874      */
875     (void) unlink(fq_bones);
876     ret = link(tempname, fq_bones);
877     ret += unlink(tempname);
878 #else
879     ret = rename(tempname, fq_bones);
880 #endif
881     if (wizard && ret != 0)
882         pline("couldn't rename %s to %s.", tempname, fq_bones);
883 }
884 
885 int
open_bonesfile(lev,bonesid)886 open_bonesfile(lev, bonesid)
887 d_level *lev;
888 char **bonesid;
889 {
890     const char *fq_bones;
891     int fd;
892 
893     *bonesid = set_bonesfile_name(bones, lev);
894     fq_bones = fqname(bones, BONESPREFIX, 0);
895     nh_uncompress(fq_bones); /* no effect if nonexistent */
896 #ifdef MAC
897     fd = macopen(fq_bones, O_RDONLY | O_BINARY, BONE_TYPE);
898 #else
899     fd = open(fq_bones, O_RDONLY | O_BINARY, 0);
900 #endif
901     return fd;
902 }
903 
904 int
delete_bonesfile(lev)905 delete_bonesfile(lev)
906 d_level *lev;
907 {
908     (void) set_bonesfile_name(bones, lev);
909     return !(unlink(fqname(bones, BONESPREFIX, 0)) < 0);
910 }
911 
912 /* assume we're compressing the recently read or created bonesfile, so the
913  * file name is already set properly */
914 void
compress_bonesfile()915 compress_bonesfile()
916 {
917     nh_compress(fqname(bones, BONESPREFIX, 0));
918 }
919 
920 /* ----------  END BONES FILE HANDLING ----------- */
921 
922 /* ----------  BEGIN SAVE FILE HANDLING ----------- */
923 
924 /* set savefile name in OS-dependent manner from pre-existing plname,
925  * avoiding troublesome characters */
926 void
set_savefile_name(regularize_it)927 set_savefile_name(regularize_it)
928 boolean regularize_it;
929 {
930 #ifdef VMS
931     Sprintf(SAVEF, "[.save]%d%s", getuid(), plname);
932     if (regularize_it)
933         regularize(SAVEF + 7);
934     Strcat(SAVEF, ";1");
935 #else
936 #if defined(MICRO)
937     Strcpy(SAVEF, SAVEP);
938 #ifdef AMIGA
939     strncat(SAVEF, bbs_id, PATHLEN);
940 #endif
941     {
942         int i = strlen(SAVEP);
943 #ifdef AMIGA
944         /* plname has to share space with SAVEP and ".sav" */
945         (void) strncat(SAVEF, plname, FILENAME - i - 4);
946 #else
947         (void) strncat(SAVEF, plname, 8);
948 #endif
949         if (regularize_it)
950             regularize(SAVEF + i);
951     }
952     Strcat(SAVEF, SAVE_EXTENSION);
953 #else
954 #if defined(WIN32)
955     {
956         static const char okchars[] =
957             "*ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-.";
958         const char *legal = okchars;
959         char fnamebuf[BUFSZ], encodedfnamebuf[BUFSZ];
960 
961         /* Obtain the name of the logged on user and incorporate
962          * it into the name. */
963         Sprintf(fnamebuf, "%s", plname);
964         if (regularize_it)
965             ++legal; /* skip '*' wildcard character */
966         (void) fname_encode(legal, '%', fnamebuf, encodedfnamebuf, BUFSZ);
967         Sprintf(SAVEF, "%s%s", encodedfnamebuf, SAVE_EXTENSION);
968     }
969 #else  /* not VMS or MICRO or WIN32 */
970     Sprintf(SAVEF, "save/%d%s", (int) getuid(), plname);
971     if (regularize_it)
972         regularize(SAVEF + 5); /* avoid . or / in name */
973 #endif /* WIN32 */
974 #endif /* MICRO */
975 #endif /* VMS   */
976 }
977 
978 #ifdef INSURANCE
979 void
save_savefile_name(fd)980 save_savefile_name(fd)
981 int fd;
982 {
983     (void) write(fd, (genericptr_t) SAVEF, sizeof(SAVEF));
984 }
985 #endif
986 
987 #ifndef MICRO
988 /* change pre-existing savefile name to indicate an error savefile */
989 void
set_error_savefile()990 set_error_savefile()
991 {
992 #ifdef VMS
993     {
994         char *semi_colon = rindex(SAVEF, ';');
995 
996         if (semi_colon)
997             *semi_colon = '\0';
998     }
999     Strcat(SAVEF, ".e;1");
1000 #else
1001 #ifdef MAC
1002     Strcat(SAVEF, "-e");
1003 #else
1004     Strcat(SAVEF, ".e");
1005 #endif
1006 #endif
1007 }
1008 #endif
1009 
1010 /* create save file, overwriting one if it already exists */
1011 int
create_savefile()1012 create_savefile()
1013 {
1014     const char *fq_save;
1015     int fd;
1016 
1017     fq_save = fqname(SAVEF, SAVEPREFIX, 0);
1018 #if defined(MICRO) || defined(WIN32)
1019     fd = open(fq_save, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, FCMASK);
1020 #else
1021 #ifdef MAC
1022     fd = maccreat(fq_save, SAVE_TYPE);
1023 #else
1024     fd = creat(fq_save, FCMASK);
1025 #endif
1026 #if defined(VMS) && !defined(SECURE)
1027     /*
1028        Make sure the save file is owned by the current process.  That's
1029        the default for non-privileged users, but for priv'd users the
1030        file will be owned by the directory's owner instead of the user.
1031     */
1032 #undef getuid
1033     (void) chown(fq_save, getuid(), getgid());
1034 #define getuid() vms_getuid()
1035 #endif /* VMS && !SECURE */
1036 #endif /* MICRO */
1037 
1038     return fd;
1039 }
1040 
1041 /* open savefile for reading */
1042 int
open_savefile()1043 open_savefile()
1044 {
1045     const char *fq_save;
1046     int fd;
1047 
1048     fq_save = fqname(SAVEF, SAVEPREFIX, 0);
1049 #ifdef MAC
1050     fd = macopen(fq_save, O_RDONLY | O_BINARY, SAVE_TYPE);
1051 #else
1052     fd = open(fq_save, O_RDONLY | O_BINARY, 0);
1053 #endif
1054     return fd;
1055 }
1056 
1057 /* delete savefile */
1058 int
delete_savefile()1059 delete_savefile()
1060 {
1061     (void) unlink(fqname(SAVEF, SAVEPREFIX, 0));
1062     return 0; /* for restore_saved_game() (ex-xxxmain.c) test */
1063 }
1064 
1065 /* try to open up a save file and prepare to restore it */
1066 int
restore_saved_game()1067 restore_saved_game()
1068 {
1069     const char *fq_save;
1070     int fd;
1071 
1072     reset_restpref();
1073     set_savefile_name(TRUE);
1074 #ifdef MFLOPPY
1075     if (!saveDiskPrompt(1))
1076         return -1;
1077 #endif /* MFLOPPY */
1078     fq_save = fqname(SAVEF, SAVEPREFIX, 0);
1079 
1080     nh_uncompress(fq_save);
1081     if ((fd = open_savefile()) < 0)
1082         return fd;
1083 
1084     if (validate(fd, fq_save) != 0) {
1085         (void) nhclose(fd), fd = -1;
1086         (void) delete_savefile();
1087     }
1088     return fd;
1089 }
1090 
1091 #if defined(SELECTSAVED)
1092 char *
plname_from_file(filename)1093 plname_from_file(filename)
1094 const char *filename;
1095 {
1096     int fd;
1097     char *result = 0;
1098 
1099     Strcpy(SAVEF, filename);
1100 #ifdef COMPRESS_EXTENSION
1101     SAVEF[strlen(SAVEF) - strlen(COMPRESS_EXTENSION)] = '\0';
1102 #endif
1103     nh_uncompress(SAVEF);
1104     if ((fd = open_savefile()) >= 0) {
1105         if (validate(fd, filename) == 0) {
1106             char tplname[PL_NSIZ];
1107             get_plname_from_file(fd, tplname);
1108             result = dupstr(tplname);
1109         }
1110         (void) nhclose(fd);
1111     }
1112     nh_compress(SAVEF);
1113 
1114     return result;
1115 #if 0
1116 /* --------- obsolete - used to be ifndef STORE_PLNAME_IN_FILE ----*/
1117 #if defined(UNIX) && defined(QT_GRAPHICS)
1118     /* Name not stored in save file, so we have to extract it from
1119        the filename, which loses information
1120        (eg. "/", "_", and "." characters are lost. */
1121     int k;
1122     int uid;
1123     char name[64]; /* more than PL_NSIZ */
1124 #ifdef COMPRESS_EXTENSION
1125 #define EXTSTR COMPRESS_EXTENSION
1126 #else
1127 #define EXTSTR ""
1128 #endif
1129 
1130     if ( sscanf( filename, "%*[^/]/%d%63[^.]" EXTSTR, &uid, name ) == 2 ) {
1131 #undef EXTSTR
1132         /* "_" most likely means " ", which certainly looks nicer */
1133         for (k=0; name[k]; k++)
1134             if ( name[k] == '_' )
1135                 name[k] = ' ';
1136         return dupstr(name);
1137     } else
1138 #endif /* UNIX && QT_GRAPHICS */
1139     {
1140         return 0;
1141     }
1142 /* --------- end of obsolete code ----*/
1143 #endif /* 0 - WAS STORE_PLNAME_IN_FILE*/
1144 }
1145 #endif /* defined(SELECTSAVED) */
1146 
1147 char **
get_saved_games()1148 get_saved_games()
1149 {
1150 #if defined(SELECTSAVED)
1151     int n, j = 0;
1152     char **result = 0;
1153 #ifdef WIN32
1154     {
1155         char *foundfile;
1156         const char *fq_save;
1157         const char *fq_new_save;
1158         const char *fq_old_save;
1159         char **files = 0;
1160         int i;
1161 
1162         Strcpy(plname, "*");
1163         set_savefile_name(FALSE);
1164 #if defined(ZLIB_COMP)
1165         Strcat(SAVEF, COMPRESS_EXTENSION);
1166 #endif
1167         fq_save = fqname(SAVEF, SAVEPREFIX, 0);
1168 
1169         n = 0;
1170         foundfile = foundfile_buffer();
1171         if (findfirst((char *) fq_save)) {
1172             do {
1173                 ++n;
1174             } while (findnext());
1175         }
1176 
1177         if (n > 0) {
1178             files = (char **) alloc((n + 1) * sizeof(char *)); /* at most */
1179             (void) memset((genericptr_t) files, 0, (n + 1) * sizeof(char *));
1180             if (findfirst((char *) fq_save)) {
1181                 i = 0;
1182                 do {
1183                     files[i++] = strdup(foundfile);
1184                 } while (findnext());
1185             }
1186         }
1187 
1188         if (n > 0) {
1189             result = (char **) alloc((n + 1) * sizeof(char *)); /* at most */
1190             (void) memset((genericptr_t) result, 0, (n + 1) * sizeof(char *));
1191             for(i = 0; i < n; i++) {
1192                 char *r;
1193                 r = plname_from_file(files[i]);
1194 
1195                 if (r) {
1196 
1197                     /* rename file if it is not named as expected */
1198                     Strcpy(plname, r);
1199                     set_savefile_name(FALSE);
1200                     fq_new_save = fqname(SAVEF, SAVEPREFIX, 0);
1201                     fq_old_save = fqname(files[i], SAVEPREFIX, 1);
1202 
1203                     if(strcmp(fq_old_save, fq_new_save) != 0 &&
1204                         !file_exists(fq_new_save))
1205                         rename(fq_old_save, fq_new_save);
1206 
1207                     result[j++] = r;
1208                 }
1209             }
1210         }
1211 
1212         free_saved_games(files);
1213 
1214     }
1215 #endif
1216 #if defined(UNIX) && defined(QT_GRAPHICS)
1217     /* posixly correct version */
1218     int myuid = getuid();
1219     DIR *dir;
1220 
1221     if ((dir = opendir(fqname("save", SAVEPREFIX, 0)))) {
1222         for (n = 0; readdir(dir); n++)
1223             ;
1224         closedir(dir);
1225         if (n > 0) {
1226             int i;
1227 
1228             if (!(dir = opendir(fqname("save", SAVEPREFIX, 0))))
1229                 return 0;
1230             result = (char **) alloc((n + 1) * sizeof(char *)); /* at most */
1231             (void) memset((genericptr_t) result, 0, (n + 1) * sizeof(char *));
1232             for (i = 0, j = 0; i < n; i++) {
1233                 int uid;
1234                 char name[64]; /* more than PL_NSIZ */
1235                 struct dirent *entry = readdir(dir);
1236 
1237                 if (!entry)
1238                     break;
1239                 if (sscanf(entry->d_name, "%d%63s", &uid, name) == 2) {
1240                     if (uid == myuid) {
1241                         char filename[BUFSZ];
1242                         char *r;
1243 
1244                         Sprintf(filename, "save/%d%s", uid, name);
1245                         r = plname_from_file(filename);
1246                         if (r)
1247                             result[j++] = r;
1248                     }
1249                 }
1250             }
1251             closedir(dir);
1252         }
1253     }
1254 #endif
1255 #ifdef VMS
1256     Strcpy(plname, "*");
1257     set_savefile_name(FALSE);
1258     j = vms_get_saved_games(SAVEF, &result);
1259 #endif /* VMS */
1260 
1261     if (j > 0) {
1262         if (j > 1)
1263             qsort(result, j, sizeof (char *), strcmp_wrap);
1264         result[j] = 0;
1265         return result;
1266     } else if (result) { /* could happen if save files are obsolete */
1267         free_saved_games(result);
1268     }
1269 #endif /* SELECTSAVED */
1270     return 0;
1271 }
1272 
1273 void
free_saved_games(saved)1274 free_saved_games(saved)
1275 char **saved;
1276 {
1277     if (saved) {
1278         int i = 0;
1279 
1280         while (saved[i])
1281             free((genericptr_t) saved[i++]);
1282         free((genericptr_t) saved);
1283     }
1284 }
1285 
1286 /* ----------  END SAVE FILE HANDLING ----------- */
1287 
1288 /* ----------  BEGIN FILE COMPRESSION HANDLING ----------- */
1289 
1290 #ifdef COMPRESS
1291 
1292 STATIC_OVL void
redirect(filename,mode,stream,uncomp)1293 redirect(filename, mode, stream, uncomp)
1294 const char *filename, *mode;
1295 FILE *stream;
1296 boolean uncomp;
1297 {
1298     if (freopen(filename, mode, stream) == (FILE *) 0) {
1299         (void) fprintf(stderr, "freopen of %s for %scompress failed\n",
1300                        filename, uncomp ? "un" : "");
1301         nh_terminate(EXIT_FAILURE);
1302     }
1303 }
1304 
1305 /*
1306  * using system() is simpler, but opens up security holes and causes
1307  * problems on at least Interactive UNIX 3.0.1 (SVR3.2), where any
1308  * setuid is renounced by /bin/sh, so the files cannot be accessed.
1309  *
1310  * cf. child() in unixunix.c.
1311  */
1312 STATIC_OVL void
docompress_file(filename,uncomp)1313 docompress_file(filename, uncomp)
1314 const char *filename;
1315 boolean uncomp;
1316 {
1317     char cfn[80];
1318     FILE *cf;
1319     const char *args[10];
1320 #ifdef COMPRESS_OPTIONS
1321     char opts[80];
1322 #endif
1323     int i = 0;
1324     int f;
1325 #ifdef TTY_GRAPHICS
1326     boolean istty = WINDOWPORT("tty");
1327 #endif
1328 
1329     Strcpy(cfn, filename);
1330 #ifdef COMPRESS_EXTENSION
1331     Strcat(cfn, COMPRESS_EXTENSION);
1332 #endif
1333     /* when compressing, we know the file exists */
1334     if (uncomp) {
1335         if ((cf = fopen(cfn, RDBMODE)) == (FILE *) 0)
1336             return;
1337         (void) fclose(cf);
1338     }
1339 
1340     args[0] = COMPRESS;
1341     if (uncomp)
1342         args[++i] = "-d"; /* uncompress */
1343 #ifdef COMPRESS_OPTIONS
1344     {
1345         /* we can't guarantee there's only one additional option, sigh */
1346         char *opt;
1347         boolean inword = FALSE;
1348 
1349         Strcpy(opts, COMPRESS_OPTIONS);
1350         opt = opts;
1351         while (*opt) {
1352             if ((*opt == ' ') || (*opt == '\t')) {
1353                 if (inword) {
1354                     *opt = '\0';
1355                     inword = FALSE;
1356                 }
1357             } else if (!inword) {
1358                 args[++i] = opt;
1359                 inword = TRUE;
1360             }
1361             opt++;
1362         }
1363     }
1364 #endif
1365     args[++i] = (char *) 0;
1366 
1367 #ifdef TTY_GRAPHICS
1368     /* If we don't do this and we are right after a y/n question *and*
1369      * there is an error message from the compression, the 'y' or 'n' can
1370      * end up being displayed after the error message.
1371      */
1372     if (istty)
1373         mark_synch();
1374 #endif
1375     f = fork();
1376     if (f == 0) { /* child */
1377 #ifdef TTY_GRAPHICS
1378         /* any error messages from the compression must come out after
1379          * the first line, because the more() to let the user read
1380          * them will have to clear the first line.  This should be
1381          * invisible if there are no error messages.
1382          */
1383         if (istty)
1384             raw_print("");
1385 #endif
1386         /* run compressor without privileges, in case other programs
1387          * have surprises along the line of gzip once taking filenames
1388          * in GZIP.
1389          */
1390         /* assume all compressors will compress stdin to stdout
1391          * without explicit filenames.  this is true of at least
1392          * compress and gzip, those mentioned in config.h.
1393          */
1394         if (uncomp) {
1395             redirect(cfn, RDBMODE, stdin, uncomp);
1396             redirect(filename, WRBMODE, stdout, uncomp);
1397         } else {
1398             redirect(filename, RDBMODE, stdin, uncomp);
1399             redirect(cfn, WRBMODE, stdout, uncomp);
1400         }
1401         (void) setgid(getgid());
1402         (void) setuid(getuid());
1403         (void) execv(args[0], (char *const *) args);
1404         perror((char *) 0);
1405         (void) fprintf(stderr, "Exec to %scompress %s failed.\n",
1406                        uncomp ? "un" : "", filename);
1407         nh_terminate(EXIT_FAILURE);
1408     } else if (f == -1) {
1409         perror((char *) 0);
1410         pline("Fork to %scompress %s failed.", uncomp ? "un" : "", filename);
1411         return;
1412     }
1413 #ifndef NO_SIGNAL
1414     (void) signal(SIGINT, SIG_IGN);
1415     (void) signal(SIGQUIT, SIG_IGN);
1416     (void) wait((int *) &i);
1417     (void) signal(SIGINT, (SIG_RET_TYPE) done1);
1418     if (wizard)
1419         (void) signal(SIGQUIT, SIG_DFL);
1420 #else
1421     /* I don't think we can really cope with external compression
1422      * without signals, so we'll declare that compress failed and
1423      * go on.  (We could do a better job by forcing off external
1424      * compression if there are no signals, but we want this for
1425      * testing with FailSafeC
1426      */
1427     i = 1;
1428 #endif
1429     if (i == 0) {
1430         /* (un)compress succeeded: remove file left behind */
1431         if (uncomp)
1432             (void) unlink(cfn);
1433         else
1434             (void) unlink(filename);
1435     } else {
1436         /* (un)compress failed; remove the new, bad file */
1437         if (uncomp) {
1438             raw_printf("Unable to uncompress %s", filename);
1439             (void) unlink(filename);
1440         } else {
1441             /* no message needed for compress case; life will go on */
1442             (void) unlink(cfn);
1443         }
1444 #ifdef TTY_GRAPHICS
1445         /* Give them a chance to read any error messages from the
1446          * compression--these would go to stdout or stderr and would get
1447          * overwritten only in tty mode.  It's still ugly, since the
1448          * messages are being written on top of the screen, but at least
1449          * the user can read them.
1450          */
1451         if (istty && iflags.window_inited) {
1452             clear_nhwindow(WIN_MESSAGE);
1453             more();
1454             /* No way to know if this is feasible */
1455             /* doredraw(); */
1456         }
1457 #endif
1458     }
1459 }
1460 #endif /* COMPRESS */
1461 
1462 #if defined(COMPRESS) || defined(ZLIB_COMP)
1463 #define UNUSED_if_not_COMPRESS /*empty*/
1464 #else
1465 #define UNUSED_if_not_COMPRESS UNUSED
1466 #endif
1467 
1468 /* compress file */
1469 void
nh_compress(filename)1470 nh_compress(filename)
1471 const char *filename UNUSED_if_not_COMPRESS;
1472 {
1473 #if !defined(COMPRESS) && !defined(ZLIB_COMP)
1474 #ifdef PRAGMA_UNUSED
1475 #pragma unused(filename)
1476 #endif
1477 #else
1478     docompress_file(filename, FALSE);
1479 #endif
1480 }
1481 
1482 /* uncompress file if it exists */
1483 void
nh_uncompress(filename)1484 nh_uncompress(filename)
1485 const char *filename UNUSED_if_not_COMPRESS;
1486 {
1487 #if !defined(COMPRESS) && !defined(ZLIB_COMP)
1488 #ifdef PRAGMA_UNUSED
1489 #pragma unused(filename)
1490 #endif
1491 #else
1492     docompress_file(filename, TRUE);
1493 #endif
1494 }
1495 
1496 #ifdef ZLIB_COMP /* RLC 09 Mar 1999: Support internal ZLIB */
1497 STATIC_OVL boolean
make_compressed_name(filename,cfn)1498 make_compressed_name(filename, cfn)
1499 const char *filename;
1500 char *cfn;
1501 {
1502 #ifndef SHORT_FILENAMES
1503     /* Assume free-form filename with no 8.3 restrictions */
1504     strcpy(cfn, filename);
1505     strcat(cfn, COMPRESS_EXTENSION);
1506     return TRUE;
1507 #else
1508 #ifdef SAVE_EXTENSION
1509     char *bp = (char *) 0;
1510 
1511     strcpy(cfn, filename);
1512     if ((bp = strstri(cfn, SAVE_EXTENSION))) {
1513         strsubst(bp, SAVE_EXTENSION, ".saz");
1514         return TRUE;
1515     } else {
1516         /* find last occurrence of bon */
1517         bp = eos(cfn);
1518         while (bp-- > cfn) {
1519             if (strstri(bp, "bon")) {
1520                 strsubst(bp, "bon", "boz");
1521                 return TRUE;
1522             }
1523         }
1524     }
1525 #endif /* SAVE_EXTENSION */
1526     return FALSE;
1527 #endif /* SHORT_FILENAMES */
1528 }
1529 
1530 STATIC_OVL void
docompress_file(filename,uncomp)1531 docompress_file(filename, uncomp)
1532 const char *filename;
1533 boolean uncomp;
1534 {
1535     gzFile compressedfile;
1536     FILE *uncompressedfile;
1537     char cfn[256];
1538     char buf[1024];
1539     unsigned len, len2;
1540 
1541     if (!make_compressed_name(filename, cfn))
1542         return;
1543 
1544     if (!uncomp) {
1545         /* Open the input and output files */
1546         /* Note that gzopen takes "wb" as its mode, even on systems where
1547            fopen takes "r" and "w" */
1548 
1549         uncompressedfile = fopen(filename, RDBMODE);
1550         if (!uncompressedfile) {
1551             pline("Error in zlib docompress_file %s", filename);
1552             return;
1553         }
1554         compressedfile = gzopen(cfn, "wb");
1555         if (compressedfile == NULL) {
1556             if (errno == 0) {
1557                 pline("zlib failed to allocate memory");
1558             } else {
1559                 panic("Error in docompress_file %d", errno);
1560             }
1561             fclose(uncompressedfile);
1562             return;
1563         }
1564 
1565         /* Copy from the uncompressed to the compressed file */
1566 
1567         while (1) {
1568             len = fread(buf, 1, sizeof(buf), uncompressedfile);
1569             if (ferror(uncompressedfile)) {
1570                 pline("Failure reading uncompressed file");
1571                 pline("Can't compress %s.", filename);
1572                 fclose(uncompressedfile);
1573                 gzclose(compressedfile);
1574                 (void) unlink(cfn);
1575                 return;
1576             }
1577             if (len == 0)
1578                 break; /* End of file */
1579 
1580             len2 = gzwrite(compressedfile, buf, len);
1581             if (len2 == 0) {
1582                 pline("Failure writing compressed file");
1583                 pline("Can't compress %s.", filename);
1584                 fclose(uncompressedfile);
1585                 gzclose(compressedfile);
1586                 (void) unlink(cfn);
1587                 return;
1588             }
1589         }
1590 
1591         fclose(uncompressedfile);
1592         gzclose(compressedfile);
1593 
1594         /* Delete the file left behind */
1595 
1596         (void) unlink(filename);
1597 
1598     } else { /* uncomp */
1599 
1600         /* Open the input and output files */
1601         /* Note that gzopen takes "rb" as its mode, even on systems where
1602            fopen takes "r" and "w" */
1603 
1604         compressedfile = gzopen(cfn, "rb");
1605         if (compressedfile == NULL) {
1606             if (errno == 0) {
1607                 pline("zlib failed to allocate memory");
1608             } else if (errno != ENOENT) {
1609                 panic("Error in zlib docompress_file %s, %d", filename,
1610                       errno);
1611             }
1612             return;
1613         }
1614         uncompressedfile = fopen(filename, WRBMODE);
1615         if (!uncompressedfile) {
1616             pline("Error in zlib docompress file uncompress %s", filename);
1617             gzclose(compressedfile);
1618             return;
1619         }
1620 
1621         /* Copy from the compressed to the uncompressed file */
1622 
1623         while (1) {
1624             len = gzread(compressedfile, buf, sizeof(buf));
1625             if (len == (unsigned) -1) {
1626                 pline("Failure reading compressed file");
1627                 pline("Can't uncompress %s.", filename);
1628                 fclose(uncompressedfile);
1629                 gzclose(compressedfile);
1630                 (void) unlink(filename);
1631                 return;
1632             }
1633             if (len == 0)
1634                 break; /* End of file */
1635 
1636             fwrite(buf, 1, len, uncompressedfile);
1637             if (ferror(uncompressedfile)) {
1638                 pline("Failure writing uncompressed file");
1639                 pline("Can't uncompress %s.", filename);
1640                 fclose(uncompressedfile);
1641                 gzclose(compressedfile);
1642                 (void) unlink(filename);
1643                 return;
1644             }
1645         }
1646 
1647         fclose(uncompressedfile);
1648         gzclose(compressedfile);
1649 
1650         /* Delete the file left behind */
1651         (void) unlink(cfn);
1652     }
1653 }
1654 #endif /* RLC 09 Mar 1999: End ZLIB patch */
1655 
1656 /* ----------  END FILE COMPRESSION HANDLING ----------- */
1657 
1658 /* ----------  BEGIN FILE LOCKING HANDLING ----------- */
1659 
1660 static int nesting = 0;
1661 
1662 #if defined(NO_FILE_LINKS) || defined(USE_FCNTL) /* implies UNIX */
1663 static int lockfd = -1; /* for lock_file() to pass to unlock_file() */
1664 #endif
1665 #ifdef USE_FCNTL
1666 struct flock sflock; /* for unlocking, same as above */
1667 #endif
1668 
1669 #define HUP if (!program_state.done_hup)
1670 
1671 #ifndef USE_FCNTL
1672 STATIC_OVL char *
make_lockname(filename,lockname)1673 make_lockname(filename, lockname)
1674 const char *filename;
1675 char *lockname;
1676 {
1677 #if defined(UNIX) || defined(VMS) || defined(AMIGA) || defined(WIN32) \
1678     || defined(MSDOS)
1679 #ifdef NO_FILE_LINKS
1680     Strcpy(lockname, LOCKDIR);
1681     Strcat(lockname, "/");
1682     Strcat(lockname, filename);
1683 #else
1684     Strcpy(lockname, filename);
1685 #endif
1686 #ifdef VMS
1687     {
1688         char *semi_colon = rindex(lockname, ';');
1689         if (semi_colon)
1690             *semi_colon = '\0';
1691     }
1692     Strcat(lockname, ".lock;1");
1693 #else
1694     Strcat(lockname, "_lock");
1695 #endif
1696     return lockname;
1697 #else /* !(UNIX || VMS || AMIGA || WIN32 || MSDOS) */
1698 #ifdef PRAGMA_UNUSED
1699 #pragma unused(filename)
1700 #endif
1701     lockname[0] = '\0';
1702     return (char *) 0;
1703 #endif
1704 }
1705 #endif /* !USE_FCNTL */
1706 
1707 /* lock a file */
1708 boolean
lock_file(filename,whichprefix,retryct)1709 lock_file(filename, whichprefix, retryct)
1710 const char *filename;
1711 int whichprefix;
1712 int retryct;
1713 {
1714 #if defined(PRAGMA_UNUSED) && !(defined(UNIX) || defined(VMS)) \
1715     && !(defined(AMIGA) || defined(WIN32) || defined(MSDOS))
1716 #pragma unused(retryct)
1717 #endif
1718 #ifndef USE_FCNTL
1719     char locknambuf[BUFSZ];
1720     const char *lockname;
1721 #endif
1722 
1723     nesting++;
1724     if (nesting > 1) {
1725         impossible("TRIED TO NEST LOCKS");
1726         return TRUE;
1727     }
1728 
1729 #ifndef USE_FCNTL
1730     lockname = make_lockname(filename, locknambuf);
1731 #ifndef NO_FILE_LINKS /* LOCKDIR should be subsumed by LOCKPREFIX */
1732     lockname = fqname(lockname, LOCKPREFIX, 2);
1733 #endif
1734 #endif
1735     filename = fqname(filename, whichprefix, 0);
1736 #ifdef USE_FCNTL
1737     lockfd = open(filename, O_RDWR);
1738     if (lockfd == -1) {
1739         HUP raw_printf("Cannot open file %s.  Is NetHack installed correctly?",
1740                        filename);
1741         nesting--;
1742         return FALSE;
1743     }
1744     sflock.l_type = F_WRLCK;
1745     sflock.l_whence = SEEK_SET;
1746     sflock.l_start = 0;
1747     sflock.l_len = 0;
1748 #endif
1749 
1750 #if defined(UNIX) || defined(VMS)
1751 #ifdef USE_FCNTL
1752     while (fcntl(lockfd, F_SETLK, &sflock) == -1) {
1753 #else
1754 #ifdef NO_FILE_LINKS
1755     while ((lockfd = open(lockname, O_RDWR | O_CREAT | O_EXCL, 0666)) == -1) {
1756 #else
1757     while (link(filename, lockname) == -1) {
1758 #endif
1759 #endif
1760 
1761 #ifdef USE_FCNTL
1762         if (retryct--) {
1763             HUP raw_printf(
1764                "Waiting for release of fcntl lock on %s.  (%d retries left.)",
1765                            filename, retryct);
1766             sleep(1);
1767         } else {
1768             HUP(void) raw_print("I give up.  Sorry.");
1769             HUP raw_printf("Some other process has an unnatural grip on %s.",
1770                            filename);
1771             nesting--;
1772             return FALSE;
1773         }
1774 #else
1775         int errnosv = errno;
1776 
1777         switch (errnosv) { /* George Barbanis */
1778         case EEXIST:
1779             if (retryct--) {
1780                 HUP raw_printf(
1781                     "Waiting for access to %s.  (%d retries left).", filename,
1782                     retryct);
1783 #if defined(SYSV) || defined(ULTRIX) || defined(VMS)
1784                 (void)
1785 #endif
1786                     sleep(1);
1787             } else {
1788                 HUP(void) raw_print("I give up.  Sorry.");
1789                 HUP raw_printf("Perhaps there is an old %s around?",
1790                                lockname);
1791                 nesting--;
1792                 return FALSE;
1793             }
1794 
1795             break;
1796         case ENOENT:
1797             HUP raw_printf("Can't find file %s to lock!", filename);
1798             nesting--;
1799             return FALSE;
1800         case EACCES:
1801             HUP raw_printf("No write permission to lock %s!", filename);
1802             nesting--;
1803             return FALSE;
1804 #ifdef VMS /* c__translate(vmsfiles.c) */
1805         case EPERM:
1806             /* could be misleading, but usually right */
1807             HUP raw_printf("Can't lock %s due to directory protection.",
1808                            filename);
1809             nesting--;
1810             return FALSE;
1811 #endif
1812         case EROFS:
1813             /* take a wild guess at the underlying cause */
1814             HUP perror(lockname);
1815             HUP raw_printf("Cannot lock %s.", filename);
1816             HUP raw_printf(
1817   "(Perhaps you are running NetHack from inside the distribution package?).");
1818             nesting--;
1819             return FALSE;
1820         default:
1821             HUP perror(lockname);
1822             HUP raw_printf("Cannot lock %s for unknown reason (%d).",
1823                            filename, errnosv);
1824             nesting--;
1825             return FALSE;
1826         }
1827 #endif /* USE_FCNTL */
1828     }
1829 #endif /* UNIX || VMS */
1830 
1831 #if (defined(AMIGA) || defined(WIN32) || defined(MSDOS)) \
1832     && !defined(USE_FCNTL)
1833 #ifdef AMIGA
1834 #define OPENFAILURE(fd) (!fd)
1835     lockptr = 0;
1836 #else
1837 #define OPENFAILURE(fd) (fd < 0)
1838     lockptr = -1;
1839 #endif
1840     while (--retryct && OPENFAILURE(lockptr)) {
1841 #if defined(WIN32) && !defined(WIN_CE)
1842         lockptr = sopen(lockname, O_RDWR | O_CREAT, SH_DENYRW, S_IWRITE);
1843 #else
1844         (void) DeleteFile(lockname); /* in case dead process was here first */
1845 #ifdef AMIGA
1846         lockptr = Open(lockname, MODE_NEWFILE);
1847 #else
1848         lockptr = open(lockname, O_RDWR | O_CREAT | O_EXCL, S_IWRITE);
1849 #endif
1850 #endif
1851         if (OPENFAILURE(lockptr)) {
1852             raw_printf("Waiting for access to %s.  (%d retries left).",
1853                        filename, retryct);
1854             Delay(50);
1855         }
1856     }
1857     if (!retryct) {
1858         raw_printf("I give up.  Sorry.");
1859         nesting--;
1860         return FALSE;
1861     }
1862 #endif /* AMIGA || WIN32 || MSDOS */
1863     return TRUE;
1864 }
1865 
1866 #ifdef VMS /* for unlock_file, use the unlink() routine in vmsunix.c */
1867 #ifdef unlink
1868 #undef unlink
1869 #endif
1870 #define unlink(foo) vms_unlink(foo)
1871 #endif
1872 
1873 /* unlock file, which must be currently locked by lock_file */
1874 void
unlock_file(filename)1875 unlock_file(filename)
1876 const char *filename;
1877 {
1878 #ifndef USE_FCNTL
1879     char locknambuf[BUFSZ];
1880     const char *lockname;
1881 #endif
1882 
1883     if (nesting == 1) {
1884 #ifdef USE_FCNTL
1885         sflock.l_type = F_UNLCK;
1886         if (lockfd >= 0) {
1887             if (fcntl(lockfd, F_SETLK, &sflock) == -1)
1888                 HUP raw_printf("Can't remove fcntl lock on %s.", filename);
1889             (void) close(lockfd), lockfd = -1;
1890         }
1891 #else
1892         lockname = make_lockname(filename, locknambuf);
1893 #ifndef NO_FILE_LINKS /* LOCKDIR should be subsumed by LOCKPREFIX */
1894         lockname = fqname(lockname, LOCKPREFIX, 2);
1895 #endif
1896 
1897 #if defined(UNIX) || defined(VMS)
1898         if (unlink(lockname) < 0)
1899             HUP raw_printf("Can't unlink %s.", lockname);
1900 #ifdef NO_FILE_LINKS
1901         (void) nhclose(lockfd), lockfd = -1;
1902 #endif
1903 
1904 #endif /* UNIX || VMS */
1905 
1906 #if defined(AMIGA) || defined(WIN32) || defined(MSDOS)
1907         if (lockptr)
1908             Close(lockptr);
1909         DeleteFile(lockname);
1910         lockptr = 0;
1911 #endif /* AMIGA || WIN32 || MSDOS */
1912 #endif /* USE_FCNTL */
1913     }
1914 
1915     nesting--;
1916 }
1917 
1918 /* ----------  END FILE LOCKING HANDLING ----------- */
1919 
1920 /* ----------  BEGIN CONFIG FILE HANDLING ----------- */
1921 
1922 const char *default_configfile =
1923 #ifdef UNIX
1924     ".nethackrc";
1925 #else
1926 #if defined(MAC) || defined(__BEOS__)
1927     "NetHack Defaults";
1928 #else
1929 #if defined(MSDOS) || defined(WIN32)
1930     CONFIG_FILE;
1931 #else
1932     "NetHack.cnf";
1933 #endif
1934 #endif
1935 #endif
1936 
1937 /* used for messaging */
1938 char configfile[BUFSZ];
1939 
1940 #ifdef MSDOS
1941 /* conflict with speed-dial under windows
1942  * for XXX.cnf file so support of NetHack.cnf
1943  * is for backward compatibility only.
1944  * Preferred name (and first tried) is now defaults.nh but
1945  * the game will try the old name if there
1946  * is no defaults.nh.
1947  */
1948 const char *backward_compat_configfile = "nethack.cnf";
1949 #endif
1950 
1951 /* remember the name of the file we're accessing;
1952    if may be used in option reject messages */
1953 STATIC_OVL void
set_configfile_name(fname)1954 set_configfile_name(fname)
1955 const char *fname;
1956 {
1957     (void) strncpy(configfile, fname, sizeof configfile - 1);
1958     configfile[sizeof configfile - 1] = '\0';
1959 }
1960 
1961 #ifndef MFLOPPY
1962 #define fopenp fopen
1963 #endif
1964 
1965 STATIC_OVL FILE *
fopen_config_file(filename,src)1966 fopen_config_file(filename, src)
1967 const char *filename;
1968 int src;
1969 {
1970     FILE *fp;
1971 #if defined(UNIX) || defined(VMS)
1972     char tmp_config[BUFSZ];
1973     char *envp;
1974 #endif
1975 
1976     if (src == SET_IN_SYS) {
1977         /* SYSCF_FILE; if we can't open it, caller will bail */
1978         if (filename && *filename) {
1979             set_configfile_name(fqname(filename, SYSCONFPREFIX, 0));
1980             fp = fopenp(configfile, "r");
1981         } else
1982             fp = (FILE *) 0;
1983         return  fp;
1984     }
1985     /* If src != SET_IN_SYS, "filename" is an environment variable, so it
1986      * should hang around. If set, it is expected to be a full path name
1987      * (if relevant)
1988      */
1989     if (filename && *filename) {
1990         set_configfile_name(filename);
1991 #ifdef UNIX
1992         if (access(configfile, 4) == -1) { /* 4 is R_OK on newer systems */
1993             /* nasty sneaky attempt to read file through
1994              * NetHack's setuid permissions -- this is the only
1995              * place a file name may be wholly under the player's
1996              * control (but SYSCF_FILE is not under the player's
1997              * control so it's OK).
1998              */
1999             raw_printf("Access to %s denied (%d).", configfile, errno);
2000             wait_synch();
2001             /* fall through to standard names */
2002         } else
2003 #endif
2004         if ((fp = fopenp(configfile, "r")) != (FILE *) 0) {
2005             return  fp;
2006 #if defined(UNIX) || defined(VMS)
2007         } else {
2008             /* access() above probably caught most problems for UNIX */
2009             raw_printf("Couldn't open requested config file %s (%d).",
2010                        configfile, errno);
2011             wait_synch();
2012 #endif
2013         }
2014     }
2015     /* fall through to standard names */
2016 
2017 #if defined(MICRO) || defined(MAC) || defined(__BEOS__) || defined(WIN32)
2018     set_configfile_name(fqname(default_configfile, CONFIGPREFIX, 0));
2019     if ((fp = fopenp(configfile, "r")) != (FILE *) 0) {
2020         return fp;
2021     } else if (strcmp(default_configfile, configfile)) {
2022         set_configfile_name(default_configfile);
2023         if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2024             return fp;
2025     }
2026 #ifdef MSDOS
2027     set_configfile_name(fqname(backward_compat_configfile, CONFIGPREFIX, 0));
2028     if ((fp = fopenp(configfile, "r")) != (FILE *) 0) {
2029         return fp;
2030     } else if (strcmp(backward_compat_configfile, configfile)) {
2031         set_configfile_name(backward_compat_configfile);
2032         if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2033             return fp;
2034     }
2035 #endif
2036 #else
2037 /* constructed full path names don't need fqname() */
2038 #ifdef VMS
2039     /* no punctuation, so might be a logical name */
2040     set_configfile_name("nethackini");
2041     if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2042         return fp;
2043     set_configfile_name("sys$login:nethack.ini");
2044     if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2045         return fp;
2046 
2047     envp = nh_getenv("HOME");
2048     if (!envp || !*envp)
2049         Strcpy(tmp_config, "NetHack.cnf");
2050     else
2051         Sprintf(tmp_config, "%s%s%s", envp,
2052                 !index(":]>/", envp[strlen(envp) - 1]) ? "/" : "",
2053                 "NetHack.cnf");
2054     set_configfile_name(tmp_config);
2055     if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2056         return fp;
2057 #else /* should be only UNIX left */
2058     envp = nh_getenv("HOME");
2059     if (!envp)
2060         Strcpy(tmp_config, ".nethackrc");
2061     else
2062         Sprintf(tmp_config, "%s/%s", envp, ".nethackrc");
2063 
2064     set_configfile_name(tmp_config);
2065     if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2066         return fp;
2067 #if defined(__APPLE__) /* UNIX+__APPLE__ => MacOSX */
2068     /* try an alternative */
2069     if (envp) {
2070         /* OSX-style configuration settings */
2071         Sprintf(tmp_config, "%s/%s", envp,
2072                 "Library/Preferences/NetHack Defaults");
2073         set_configfile_name(tmp_config);
2074         if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2075             return fp;
2076         /* may be easier for user to edit if filename has '.txt' suffix */
2077         Sprintf(tmp_config, "%s/%s", envp,
2078                 "Library/Preferences/NetHack Defaults.txt");
2079         set_configfile_name(tmp_config);
2080         if ((fp = fopenp(configfile, "r")) != (FILE *) 0)
2081             return fp;
2082     }
2083 #endif /*__APPLE__*/
2084     if (errno != ENOENT) {
2085         const char *details;
2086 
2087         /* e.g., problems when setuid NetHack can't search home
2088            directory restricted to user */
2089 #if defined(NHSTDC) && !defined(NOTSTDC)
2090         if ((details = strerror(errno)) == 0)
2091 #endif
2092             details = "";
2093         raw_printf("Couldn't open default config file %s %s(%d).",
2094                    configfile, details, errno);
2095         wait_synch();
2096     }
2097 #endif /* !VMS => Unix */
2098 #endif /* !(MICRO || MAC || __BEOS__ || WIN32) */
2099     return (FILE *) 0;
2100 }
2101 
2102 /*
2103  * Retrieve a list of integers from buf into a uchar array.
2104  *
2105  * NOTE: zeros are inserted unless modlist is TRUE, in which case the list
2106  *  location is unchanged.  Callers must handle zeros if modlist is FALSE.
2107  */
2108 STATIC_OVL int
get_uchars(bufp,list,modlist,size,name)2109 get_uchars(bufp, list, modlist, size, name)
2110 char *bufp;       /* current pointer */
2111 uchar *list;      /* return list */
2112 boolean modlist;  /* TRUE: list is being modified in place */
2113 int size;         /* return list size */
2114 const char *name; /* name of option for error message */
2115 {
2116     unsigned int num = 0;
2117     int count = 0;
2118     boolean havenum = FALSE;
2119 
2120     while (1) {
2121         switch (*bufp) {
2122         case ' ':
2123         case '\0':
2124         case '\t':
2125         case '\n':
2126             if (havenum) {
2127                 /* if modifying in place, don't insert zeros */
2128                 if (num || !modlist)
2129                     list[count] = num;
2130                 count++;
2131                 num = 0;
2132                 havenum = FALSE;
2133             }
2134             if (count == size || !*bufp)
2135                 return count;
2136             bufp++;
2137             break;
2138 
2139         case '0':
2140         case '1':
2141         case '2':
2142         case '3':
2143         case '4':
2144         case '5':
2145         case '6':
2146         case '7':
2147         case '8':
2148         case '9':
2149             havenum = TRUE;
2150             num = num * 10 + (*bufp - '0');
2151             bufp++;
2152             break;
2153 
2154         case '\\':
2155             goto gi_error;
2156             break;
2157 
2158         default:
2159  gi_error:
2160             raw_printf("Syntax error in %s", name);
2161             wait_synch();
2162             return count;
2163         }
2164     }
2165     /*NOTREACHED*/
2166 }
2167 
2168 #ifdef NOCWD_ASSUMPTIONS
2169 STATIC_OVL void
adjust_prefix(bufp,prefixid)2170 adjust_prefix(bufp, prefixid)
2171 char *bufp;
2172 int prefixid;
2173 {
2174     char *ptr;
2175 
2176     if (!bufp)
2177         return;
2178 #ifdef WIN32
2179     if (fqn_prefix_locked[prefixid])
2180         return;
2181 #endif
2182     /* Backward compatibility, ignore trailing ;n */
2183     if ((ptr = index(bufp, ';')) != 0)
2184         *ptr = '\0';
2185     if (strlen(bufp) > 0) {
2186         fqn_prefix[prefixid] = (char *) alloc(strlen(bufp) + 2);
2187         Strcpy(fqn_prefix[prefixid], bufp);
2188         append_slash(fqn_prefix[prefixid]);
2189     }
2190 }
2191 #endif
2192 
2193 /* Choose at random one of the sep separated parts from str. Mangles str. */
2194 STATIC_OVL char *
choose_random_part(str,sep)2195 choose_random_part(str,sep)
2196 char *str;
2197 char sep;
2198 {
2199     int nsep = 1;
2200     int csep;
2201     int len = 0;
2202     char *begin = str;
2203 
2204     if (!str)
2205         return (char *) 0;
2206 
2207     while (*str) {
2208         if (*str == sep)
2209             nsep++;
2210         str++;
2211     }
2212     csep = rn2(nsep);
2213     str = begin;
2214     while ((csep > 0) && *str) {
2215         str++;
2216         if (*str == sep)
2217             csep--;
2218     }
2219     if (*str) {
2220         if (*str == sep)
2221             str++;
2222         begin = str;
2223         while (*str && *str != sep) {
2224             str++;
2225             len++;
2226         }
2227         *str = '\0';
2228         if (len)
2229             return begin;
2230     }
2231     return (char *) 0;
2232 }
2233 
2234 STATIC_OVL void
free_config_sections()2235 free_config_sections()
2236 {
2237     if (config_section_chosen) {
2238         free(config_section_chosen);
2239         config_section_chosen = NULL;
2240     }
2241     if (config_section_current) {
2242         free(config_section_current);
2243         config_section_current = NULL;
2244     }
2245 }
2246 
2247 STATIC_OVL boolean
is_config_section(str)2248 is_config_section(str)
2249 const char *str;
2250 {
2251     const char *a = rindex(str, ']');
2252 
2253     return (a && *str == '[' && *(a+1) == '\0' && (int)(a - str) > 0);
2254 }
2255 
2256 STATIC_OVL boolean
handle_config_section(buf)2257 handle_config_section(buf)
2258 char *buf;
2259 {
2260     if (is_config_section(buf)) {
2261         char *send;
2262         if (config_section_current) {
2263             free(config_section_current);
2264         }
2265         config_section_current = dupstr(&buf[1]);
2266         send = rindex(config_section_current, ']');
2267         *send = '\0';
2268         debugpline1("set config section: '%s'", config_section_current);
2269         return TRUE;
2270     }
2271 
2272     if (config_section_current) {
2273         if (!config_section_chosen)
2274             return TRUE;
2275         if (strcmp(config_section_current, config_section_chosen))
2276             return TRUE;
2277     }
2278     return FALSE;
2279 }
2280 
2281 #define match_varname(INP, NAM, LEN) match_optname(INP, NAM, LEN, TRUE)
2282 
2283 /* find the '=' or ':' */
2284 char *
find_optparam(buf)2285 find_optparam(buf)
2286 const char *buf;
2287 {
2288     char *bufp, *altp;
2289 
2290     bufp = index(buf, '=');
2291     altp = index(buf, ':');
2292     if (!bufp || (altp && altp < bufp))
2293         bufp = altp;
2294 
2295     return bufp;
2296 }
2297 
2298 boolean
parse_config_line(origbuf)2299 parse_config_line(origbuf)
2300 char *origbuf;
2301 {
2302 #if defined(MICRO) && !defined(NOCWD_ASSUMPTIONS)
2303     static boolean ramdisk_specified = FALSE;
2304 #endif
2305 #ifdef SYSCF
2306     int n, src = iflags.parse_config_file_src;
2307 #endif
2308     char *bufp, buf[4 * BUFSZ];
2309     uchar translate[MAXPCHARS];
2310     int len;
2311     boolean retval = TRUE;
2312 
2313     while (*origbuf == ' ' || *origbuf == '\t') /* skip leading whitespace */
2314         ++origbuf;                   /* (caller probably already did this) */
2315     (void) strncpy(buf, origbuf, sizeof buf - 1);
2316     buf[sizeof buf - 1] = '\0'; /* strncpy not guaranteed to NUL terminate */
2317     /* convert any tab to space, condense consecutive spaces into one,
2318        remove leading and trailing spaces (exception: if there is nothing
2319        but spaces, one of them will be kept even though it leads/trails) */
2320     mungspaces(buf);
2321 
2322     /* find the '=' or ':' */
2323     bufp = find_optparam(buf);
2324     if (!bufp) {
2325         config_error_add("Not a config statement, missing '='");
2326         return FALSE;
2327     }
2328     /* skip past '=', then space between it and value, if any */
2329     ++bufp;
2330     if (*bufp == ' ')
2331         ++bufp;
2332 
2333     /* Go through possible variables */
2334     /* some of these (at least LEVELS and SAVE) should now set the
2335      * appropriate fqn_prefix[] rather than specialized variables
2336      */
2337     if (match_varname(buf, "OPTIONS", 4)) {
2338         /* hack: un-mungspaces to allow consecutive spaces in
2339            general options until we verify that this is unnecessary;
2340            '=' or ':' is guaranteed to be present */
2341         bufp = find_optparam(origbuf);
2342         ++bufp; /* skip '='; parseoptions() handles spaces */
2343 
2344         if (!parseoptions(bufp, TRUE, TRUE))
2345             retval = FALSE;
2346     } else if (match_varname(buf, "AUTOPICKUP_EXCEPTION", 5)) {
2347         add_autopickup_exception(bufp);
2348     } else if (match_varname(buf, "BINDINGS", 4)) {
2349         if (!parsebindings(bufp))
2350             retval = FALSE;
2351     } else if (match_varname(buf, "AUTOCOMPLETE", 5)) {
2352         parseautocomplete(bufp, TRUE);
2353     } else if (match_varname(buf, "MSGTYPE", 7)) {
2354         if (!msgtype_parse_add(bufp))
2355             retval = FALSE;
2356 #ifdef NOCWD_ASSUMPTIONS
2357     } else if (match_varname(buf, "HACKDIR", 4)) {
2358         adjust_prefix(bufp, HACKPREFIX);
2359     } else if (match_varname(buf, "LEVELDIR", 4)
2360                || match_varname(buf, "LEVELS", 4)) {
2361         adjust_prefix(bufp, LEVELPREFIX);
2362     } else if (match_varname(buf, "SAVEDIR", 4)) {
2363         adjust_prefix(bufp, SAVEPREFIX);
2364     } else if (match_varname(buf, "BONESDIR", 5)) {
2365         adjust_prefix(bufp, BONESPREFIX);
2366     } else if (match_varname(buf, "DATADIR", 4)) {
2367         adjust_prefix(bufp, DATAPREFIX);
2368     } else if (match_varname(buf, "SCOREDIR", 4)) {
2369         adjust_prefix(bufp, SCOREPREFIX);
2370     } else if (match_varname(buf, "LOCKDIR", 4)) {
2371         adjust_prefix(bufp, LOCKPREFIX);
2372     } else if (match_varname(buf, "CONFIGDIR", 4)) {
2373         adjust_prefix(bufp, CONFIGPREFIX);
2374     } else if (match_varname(buf, "TROUBLEDIR", 4)) {
2375         adjust_prefix(bufp, TROUBLEPREFIX);
2376 #else /*NOCWD_ASSUMPTIONS*/
2377 #ifdef MICRO
2378     } else if (match_varname(buf, "HACKDIR", 4)) {
2379         (void) strncpy(hackdir, bufp, PATHLEN - 1);
2380 #ifdef MFLOPPY
2381     } else if (match_varname(buf, "RAMDISK", 3)) {
2382 /* The following ifdef is NOT in the wrong
2383  * place.  For now, we accept and silently
2384  * ignore RAMDISK */
2385 #ifndef AMIGA
2386         if (strlen(bufp) >= PATHLEN)
2387             bufp[PATHLEN - 1] = '\0';
2388         Strcpy(levels, bufp);
2389         ramdisk = (strcmp(permbones, levels) != 0);
2390         ramdisk_specified = TRUE;
2391 #endif
2392 #endif
2393     } else if (match_varname(buf, "LEVELS", 4)) {
2394         if (strlen(bufp) >= PATHLEN)
2395             bufp[PATHLEN - 1] = '\0';
2396         Strcpy(permbones, bufp);
2397         if (!ramdisk_specified || !*levels)
2398             Strcpy(levels, bufp);
2399         ramdisk = (strcmp(permbones, levels) != 0);
2400     } else if (match_varname(buf, "SAVE", 4)) {
2401 #ifdef MFLOPPY
2402         extern int saveprompt;
2403 #endif
2404         char *ptr;
2405 
2406         if ((ptr = index(bufp, ';')) != 0) {
2407             *ptr = '\0';
2408 #ifdef MFLOPPY
2409             if (*(ptr + 1) == 'n' || *(ptr + 1) == 'N') {
2410                 saveprompt = FALSE;
2411             }
2412 #endif
2413         }
2414 #if defined(SYSFLAGS) && defined(MFLOPPY)
2415         else
2416             saveprompt = sysflags.asksavedisk;
2417 #endif
2418 
2419         (void) strncpy(SAVEP, bufp, SAVESIZE - 1);
2420         append_slash(SAVEP);
2421 #endif /* MICRO */
2422 #endif /*NOCWD_ASSUMPTIONS*/
2423 
2424     } else if (match_varname(buf, "NAME", 4)) {
2425         (void) strncpy(plname, bufp, PL_NSIZ - 1);
2426     } else if (match_varname(buf, "ROLE", 4)
2427                || match_varname(buf, "CHARACTER", 4)) {
2428         if ((len = str2role(bufp)) >= 0)
2429             flags.initrole = len;
2430     } else if (match_varname(buf, "DOGNAME", 3)) {
2431         (void) strncpy(dogname, bufp, PL_PSIZ - 1);
2432     } else if (match_varname(buf, "CATNAME", 3)) {
2433         (void) strncpy(catname, bufp, PL_PSIZ - 1);
2434 
2435 #ifdef SYSCF
2436     } else if (src == SET_IN_SYS && match_varname(buf, "WIZARDS", 7)) {
2437         if (sysopt.wizards)
2438             free((genericptr_t) sysopt.wizards);
2439         sysopt.wizards = dupstr(bufp);
2440         if (strlen(sysopt.wizards) && strcmp(sysopt.wizards, "*")) {
2441             /* pre-format WIZARDS list now; it's displayed during a panic
2442                and since that panic might be due to running out of memory,
2443                we don't want to risk attempting to allocate any memory then */
2444             if (sysopt.fmtd_wizard_list)
2445                 free((genericptr_t) sysopt.fmtd_wizard_list);
2446             sysopt.fmtd_wizard_list = build_english_list(sysopt.wizards);
2447         }
2448     } else if (src == SET_IN_SYS && match_varname(buf, "SHELLERS", 8)) {
2449         if (sysopt.shellers)
2450             free((genericptr_t) sysopt.shellers);
2451         sysopt.shellers = dupstr(bufp);
2452     } else if (src == SET_IN_SYS && match_varname(buf, "EXPLORERS", 7)) {
2453         if (sysopt.explorers)
2454             free((genericptr_t) sysopt.explorers);
2455         sysopt.explorers = dupstr(bufp);
2456     } else if (src == SET_IN_SYS && match_varname(buf, "DEBUGFILES", 5)) {
2457         /* if showdebug() has already been called (perhaps we've added
2458            some debugpline() calls to option processing) and has found
2459            a value for getenv("DEBUGFILES"), don't override that */
2460         if (sysopt.env_dbgfl <= 0) {
2461             if (sysopt.debugfiles)
2462                 free((genericptr_t) sysopt.debugfiles);
2463             sysopt.debugfiles = dupstr(bufp);
2464         }
2465     } else if (src == SET_IN_SYS && match_varname(buf, "DUMPLOGFILE", 7)) {
2466 #ifdef DUMPLOG
2467         if (sysopt.dumplogfile)
2468             free((genericptr_t) sysopt.dumplogfile);
2469         sysopt.dumplogfile = dupstr(bufp);
2470 #endif
2471     } else if (src == SET_IN_SYS && match_varname(buf, "GENERICUSERS", 12)) {
2472         if (sysopt.genericusers)
2473             free((genericptr_t) sysopt.genericusers);
2474         sysopt.genericusers = dupstr(bufp);
2475     } else if (src == SET_IN_SYS && match_varname(buf, "BONES_POOLS", 10)) {
2476         /* max value of 10 guarantees (N % bones.pools) will be one digit
2477            so we don't lose control of the length of bones file names */
2478         n = atoi(bufp);
2479         sysopt.bones_pools = (n <= 0) ? 0 : min(n, 10);
2480         /* note: right now bones_pools==0 is the same as bones_pools==1,
2481            but we could change that and make bones_pools==0 become an
2482            indicator to suppress bones usage altogether */
2483     } else if (src == SET_IN_SYS && match_varname(buf, "SUPPORT", 7)) {
2484         if (sysopt.support)
2485             free((genericptr_t) sysopt.support);
2486         sysopt.support = dupstr(bufp);
2487     } else if (src == SET_IN_SYS && match_varname(buf, "RECOVER", 7)) {
2488         if (sysopt.recover)
2489             free((genericptr_t) sysopt.recover);
2490         sysopt.recover = dupstr(bufp);
2491     } else if (src == SET_IN_SYS
2492                && match_varname(buf, "CHECK_SAVE_UID", 14)) {
2493         n = atoi(bufp);
2494         sysopt.check_save_uid = n;
2495     } else if (src == SET_IN_SYS
2496                && match_varname(buf, "CHECK_PLNAME", 12)) {
2497         n = atoi(bufp);
2498         sysopt.check_plname = n;
2499     } else if (match_varname(buf, "SEDUCE", 6)) {
2500         n = !!atoi(bufp); /* XXX this could be tighter */
2501         /* allow anyone to turn it off, but only sysconf to turn it on*/
2502         if (src != SET_IN_SYS && n != 0) {
2503             config_error_add("Illegal value in SEDUCE");
2504             return FALSE;
2505         }
2506         sysopt.seduce = n;
2507         sysopt_seduce_set(sysopt.seduce);
2508     } else if (src == SET_IN_SYS && match_varname(buf, "MAXPLAYERS", 10)) {
2509         n = atoi(bufp);
2510         /* XXX to get more than 25, need to rewrite all lock code */
2511         if (n < 0 || n > 25) {
2512             config_error_add("Illegal value in MAXPLAYERS (maximum is 25).");
2513             return FALSE;
2514         }
2515         sysopt.maxplayers = n;
2516     } else if (src == SET_IN_SYS && match_varname(buf, "PERSMAX", 7)) {
2517         n = atoi(bufp);
2518         if (n < 1) {
2519             config_error_add("Illegal value in PERSMAX (minimum is 1).");
2520             return FALSE;
2521         }
2522         sysopt.persmax = n;
2523     } else if (src == SET_IN_SYS && match_varname(buf, "PERS_IS_UID", 11)) {
2524         n = atoi(bufp);
2525         if (n != 0 && n != 1) {
2526             config_error_add("Illegal value in PERS_IS_UID (must be 0 or 1).");
2527             return FALSE;
2528         }
2529         sysopt.pers_is_uid = n;
2530     } else if (src == SET_IN_SYS && match_varname(buf, "ENTRYMAX", 8)) {
2531         n = atoi(bufp);
2532         if (n < 10) {
2533             config_error_add("Illegal value in ENTRYMAX (minimum is 10).");
2534             return FALSE;
2535         }
2536         sysopt.entrymax = n;
2537     } else if ((src == SET_IN_SYS) && match_varname(buf, "POINTSMIN", 9)) {
2538         n = atoi(bufp);
2539         if (n < 1) {
2540             config_error_add("Illegal value in POINTSMIN (minimum is 1).");
2541             return FALSE;
2542         }
2543         sysopt.pointsmin = n;
2544     } else if (src == SET_IN_SYS
2545                && match_varname(buf, "MAX_STATUENAME_RANK", 10)) {
2546         n = atoi(bufp);
2547         if (n < 1) {
2548             config_error_add(
2549                       "Illegal value in MAX_STATUENAME_RANK (minimum is 1).");
2550             return FALSE;
2551         }
2552         sysopt.tt_oname_maxrank = n;
2553 
2554     /* SYSCF PANICTRACE options */
2555     } else if (src == SET_IN_SYS
2556                && match_varname(buf, "PANICTRACE_LIBC", 15)) {
2557         n = atoi(bufp);
2558 #if defined(PANICTRACE) && defined(PANICTRACE_LIBC)
2559         if (n < 0 || n > 2) {
2560             config_error_add("Illegal value in PANICTRACE_LIBC (not 0,1,2).");
2561             return FALSE;
2562         }
2563 #endif
2564         sysopt.panictrace_libc = n;
2565     } else if (src == SET_IN_SYS
2566                && match_varname(buf, "PANICTRACE_GDB", 14)) {
2567         n = atoi(bufp);
2568 #if defined(PANICTRACE)
2569         if (n < 0 || n > 2) {
2570             config_error_add("Illegal value in PANICTRACE_GDB (not 0,1,2).");
2571             return FALSE;
2572         }
2573 #endif
2574         sysopt.panictrace_gdb = n;
2575     } else if (src == SET_IN_SYS && match_varname(buf, "GDBPATH", 7)) {
2576 #if defined(PANICTRACE) && !defined(VMS)
2577         if (!file_exists(bufp)) {
2578             config_error_add("File specified in GDBPATH does not exist.");
2579             return FALSE;
2580         }
2581 #endif
2582         if (sysopt.gdbpath)
2583             free((genericptr_t) sysopt.gdbpath);
2584         sysopt.gdbpath = dupstr(bufp);
2585     } else if (src == SET_IN_SYS && match_varname(buf, "GREPPATH", 7)) {
2586 #if defined(PANICTRACE) && !defined(VMS)
2587         if (!file_exists(bufp)) {
2588             config_error_add("File specified in GREPPATH does not exist.");
2589             return FALSE;
2590         }
2591 #endif
2592         if (sysopt.greppath)
2593             free((genericptr_t) sysopt.greppath);
2594         sysopt.greppath = dupstr(bufp);
2595     } else if (src == SET_IN_SYS
2596                && match_varname(buf, "ACCESSIBILITY", 13)) {
2597         n = atoi(bufp);
2598         if (n < 0 || n > 1) {
2599             config_error_add("Illegal value in ACCESSIBILITY (not 0,1).");
2600             return FALSE;
2601         }
2602         sysopt.accessibility = n;
2603 #ifdef WIN32
2604     } else if (src == SET_IN_SYS
2605                 && match_varname(buf, "portable_device_paths", 8)) {
2606         n = atoi(bufp);
2607         if (n < 0 || n > 1) {
2608             config_error_add("Illegal value in portable_device_paths (not 0,1).");
2609             return FALSE;
2610         }
2611         sysopt.portable_device_paths = n;
2612 #endif
2613 #endif /* SYSCF */
2614 
2615     } else if (match_varname(buf, "BOULDER", 3)) {
2616         (void) get_uchars(bufp, &ov_primary_syms[SYM_BOULDER + SYM_OFF_X],
2617                           TRUE, 1, "BOULDER");
2618     } else if (match_varname(buf, "MENUCOLOR", 9)) {
2619         if (!add_menu_coloring(bufp))
2620             retval = FALSE;
2621     } else if (match_varname(buf, "HILITE_STATUS", 6)) {
2622 #ifdef STATUS_HILITES
2623         if (!parse_status_hl1(bufp, TRUE))
2624             retval = FALSE;
2625 #endif
2626     } else if (match_varname(buf, "WARNINGS", 5)) {
2627         (void) get_uchars(bufp, translate, FALSE, WARNCOUNT,
2628                           "WARNINGS");
2629         assign_warnings(translate);
2630     } else if (match_varname(buf, "ROGUESYMBOLS", 4)) {
2631         if (!parsesymbols(bufp, ROGUESET)) {
2632             config_error_add("Error in ROGUESYMBOLS definition '%s'", bufp);
2633             retval = FALSE;
2634         }
2635         switch_symbols(TRUE);
2636     } else if (match_varname(buf, "SYMBOLS", 4)) {
2637         if (!parsesymbols(bufp, PRIMARY)) {
2638             config_error_add("Error in SYMBOLS definition '%s'", bufp);
2639             retval = FALSE;
2640         }
2641         switch_symbols(TRUE);
2642     } else if (match_varname(buf, "WIZKIT", 6)) {
2643         (void) strncpy(wizkit, bufp, WIZKIT_MAX - 1);
2644 #ifdef AMIGA
2645     } else if (match_varname(buf, "FONT", 4)) {
2646         char *t;
2647 
2648         if (t = strchr(buf + 5, ':')) {
2649             *t = 0;
2650             amii_set_text_font(buf + 5, atoi(t + 1));
2651             *t = ':';
2652         }
2653     } else if (match_varname(buf, "PATH", 4)) {
2654         (void) strncpy(PATH, bufp, PATHLEN - 1);
2655     } else if (match_varname(buf, "DEPTH", 5)) {
2656         extern int amii_numcolors;
2657         int val = atoi(bufp);
2658 
2659         amii_numcolors = 1L << min(DEPTH, val);
2660 #ifdef SYSFLAGS
2661     } else if (match_varname(buf, "DRIPENS", 7)) {
2662         int i, val;
2663         char *t;
2664 
2665         for (i = 0, t = strtok(bufp, ",/"); t != (char *) 0;
2666              i < 20 && (t = strtok((char *) 0, ",/")), ++i) {
2667             sscanf(t, "%d", &val);
2668             sysflags.amii_dripens[i] = val;
2669         }
2670 #endif
2671     } else if (match_varname(buf, "SCREENMODE", 10)) {
2672         extern long amii_scrnmode;
2673 
2674         if (!stricmp(bufp, "req"))
2675             amii_scrnmode = 0xffffffff; /* Requester */
2676         else if (sscanf(bufp, "%x", &amii_scrnmode) != 1)
2677             amii_scrnmode = 0;
2678     } else if (match_varname(buf, "MSGPENS", 7)) {
2679         extern int amii_msgAPen, amii_msgBPen;
2680         char *t = strtok(bufp, ",/");
2681 
2682         if (t) {
2683             sscanf(t, "%d", &amii_msgAPen);
2684             if (t = strtok((char *) 0, ",/"))
2685                 sscanf(t, "%d", &amii_msgBPen);
2686         }
2687     } else if (match_varname(buf, "TEXTPENS", 8)) {
2688         extern int amii_textAPen, amii_textBPen;
2689         char *t = strtok(bufp, ",/");
2690 
2691         if (t) {
2692             sscanf(t, "%d", &amii_textAPen);
2693             if (t = strtok((char *) 0, ",/"))
2694                 sscanf(t, "%d", &amii_textBPen);
2695         }
2696     } else if (match_varname(buf, "MENUPENS", 8)) {
2697         extern int amii_menuAPen, amii_menuBPen;
2698         char *t = strtok(bufp, ",/");
2699 
2700         if (t) {
2701             sscanf(t, "%d", &amii_menuAPen);
2702             if (t = strtok((char *) 0, ",/"))
2703                 sscanf(t, "%d", &amii_menuBPen);
2704         }
2705     } else if (match_varname(buf, "STATUSPENS", 10)) {
2706         extern int amii_statAPen, amii_statBPen;
2707         char *t = strtok(bufp, ",/");
2708 
2709         if (t) {
2710             sscanf(t, "%d", &amii_statAPen);
2711             if (t = strtok((char *) 0, ",/"))
2712                 sscanf(t, "%d", &amii_statBPen);
2713         }
2714     } else if (match_varname(buf, "OTHERPENS", 9)) {
2715         extern int amii_otherAPen, amii_otherBPen;
2716         char *t = strtok(bufp, ",/");
2717 
2718         if (t) {
2719             sscanf(t, "%d", &amii_otherAPen);
2720             if (t = strtok((char *) 0, ",/"))
2721                 sscanf(t, "%d", &amii_otherBPen);
2722         }
2723     } else if (match_varname(buf, "PENS", 4)) {
2724         extern unsigned short amii_init_map[AMII_MAXCOLORS];
2725         int i;
2726         char *t;
2727 
2728         for (i = 0, t = strtok(bufp, ",/");
2729              i < AMII_MAXCOLORS && t != (char *) 0;
2730              t = strtok((char *) 0, ",/"), ++i) {
2731             sscanf(t, "%hx", &amii_init_map[i]);
2732         }
2733         amii_setpens(amii_numcolors = i);
2734     } else if (match_varname(buf, "FGPENS", 6)) {
2735         extern int foreg[AMII_MAXCOLORS];
2736         int i;
2737         char *t;
2738 
2739         for (i = 0, t = strtok(bufp, ",/");
2740              i < AMII_MAXCOLORS && t != (char *) 0;
2741              t = strtok((char *) 0, ",/"), ++i) {
2742             sscanf(t, "%d", &foreg[i]);
2743         }
2744     } else if (match_varname(buf, "BGPENS", 6)) {
2745         extern int backg[AMII_MAXCOLORS];
2746         int i;
2747         char *t;
2748 
2749         for (i = 0, t = strtok(bufp, ",/");
2750              i < AMII_MAXCOLORS && t != (char *) 0;
2751              t = strtok((char *) 0, ",/"), ++i) {
2752             sscanf(t, "%d", &backg[i]);
2753         }
2754 #endif /*AMIGA*/
2755 #ifdef USER_SOUNDS
2756     } else if (match_varname(buf, "SOUNDDIR", 8)) {
2757         sounddir = dupstr(bufp);
2758     } else if (match_varname(buf, "SOUND", 5)) {
2759         add_sound_mapping(bufp);
2760 #endif
2761     } else if (match_varname(buf, "QT_TILEWIDTH", 12)) {
2762 #ifdef QT_GRAPHICS
2763         extern char *qt_tilewidth;
2764 
2765         if (qt_tilewidth == NULL)
2766             qt_tilewidth = dupstr(bufp);
2767 #endif
2768     } else if (match_varname(buf, "QT_TILEHEIGHT", 13)) {
2769 #ifdef QT_GRAPHICS
2770         extern char *qt_tileheight;
2771 
2772         if (qt_tileheight == NULL)
2773             qt_tileheight = dupstr(bufp);
2774 #endif
2775     } else if (match_varname(buf, "QT_FONTSIZE", 11)) {
2776 #ifdef QT_GRAPHICS
2777         extern char *qt_fontsize;
2778 
2779         if (qt_fontsize == NULL)
2780             qt_fontsize = dupstr(bufp);
2781 #endif
2782     } else if (match_varname(buf, "QT_COMPACT", 10)) {
2783 #ifdef QT_GRAPHICS
2784         extern int qt_compact_mode;
2785 
2786         qt_compact_mode = atoi(bufp);
2787 #endif
2788     } else {
2789         config_error_add("Unknown config statement");
2790         return FALSE;
2791     }
2792     return retval;
2793 }
2794 
2795 #ifdef USER_SOUNDS
2796 boolean
can_read_file(filename)2797 can_read_file(filename)
2798 const char *filename;
2799 {
2800     return (boolean) (access(filename, 4) == 0);
2801 }
2802 #endif /* USER_SOUNDS */
2803 
2804 struct _config_error_frame {
2805     int line_num;
2806     int num_errors;
2807     boolean origline_shown;
2808     boolean fromfile;
2809     boolean secure;
2810     char origline[4 * BUFSZ];
2811     char source[BUFSZ];
2812     struct _config_error_frame *next;
2813 };
2814 
2815 static struct _config_error_frame *config_error_data = 0;
2816 
2817 void
config_error_init(from_file,sourcename,secure)2818 config_error_init(from_file, sourcename, secure)
2819 boolean from_file;
2820 const char *sourcename;
2821 boolean secure;
2822 {
2823     struct _config_error_frame *tmp = (struct _config_error_frame *)
2824         alloc(sizeof (struct _config_error_frame));
2825 
2826     tmp->line_num = 0;
2827     tmp->num_errors = 0;
2828     tmp->origline_shown = FALSE;
2829     tmp->fromfile = from_file;
2830     tmp->secure = secure;
2831     tmp->origline[0] = '\0';
2832     if (sourcename && sourcename[0]) {
2833         (void) strncpy(tmp->source, sourcename, sizeof (tmp->source) - 1);
2834         tmp->source[sizeof (tmp->source) - 1] = '\0';
2835     } else
2836         tmp->source[0] = '\0';
2837 
2838     tmp->next = config_error_data;
2839     config_error_data = tmp;
2840 }
2841 
2842 STATIC_OVL boolean
config_error_nextline(line)2843 config_error_nextline(line)
2844 const char *line;
2845 {
2846     struct _config_error_frame *ced = config_error_data;
2847 
2848     if (!ced)
2849         return FALSE;
2850 
2851     if (ced->num_errors && ced->secure)
2852         return FALSE;
2853 
2854     ced->line_num++;
2855     ced->origline_shown = FALSE;
2856     if (line && line[0]) {
2857         (void) strncpy(ced->origline, line, sizeof (ced->origline) - 1);
2858         ced->origline[sizeof (ced->origline) - 1] = '\0';
2859     } else
2860         ced->origline[0] = '\0';
2861 
2862     return TRUE;
2863 }
2864 
2865 /* varargs 'config_error_add()' moved to pline.c */
2866 void
config_erradd(buf)2867 config_erradd(buf)
2868 const char *buf;
2869 {
2870     char lineno[QBUFSZ];
2871 
2872     if (!buf || !*buf)
2873         buf = "Unknown error";
2874 
2875     if (!config_error_data) {
2876         /* either very early, where pline() will use raw_print(), or
2877            player gave bad value when prompted by interactive 'O' command */
2878         pline("%s%s.", !iflags.window_inited ? "config_error_add: " : "", buf);
2879         wait_synch();
2880         return;
2881     }
2882 
2883     config_error_data->num_errors++;
2884     if (!config_error_data->origline_shown && !config_error_data->secure) {
2885         pline("\n%s", config_error_data->origline);
2886         config_error_data->origline_shown = TRUE;
2887     }
2888     if (config_error_data->line_num > 0 && !config_error_data->secure) {
2889         Sprintf(lineno, "Line %d: ", config_error_data->line_num);
2890     } else
2891         lineno[0] = '\0';
2892 
2893     pline("%s %s%s.", config_error_data->secure ? "Error:" : " *",
2894           lineno, buf);
2895 }
2896 
2897 int
config_error_done()2898 config_error_done()
2899 {
2900     int n;
2901     struct _config_error_frame *tmp = config_error_data;
2902 
2903     if (!config_error_data)
2904         return 0;
2905     n = config_error_data->num_errors;
2906     if (n) {
2907         pline("\n%d error%s in %s.\n", n,
2908                    (n > 1) ? "s" : "",
2909                    *config_error_data->source
2910               ? config_error_data->source : configfile);
2911         wait_synch();
2912     }
2913     config_error_data = tmp->next;
2914     free(tmp);
2915     return n;
2916 }
2917 
2918 boolean
read_config_file(filename,src)2919 read_config_file(filename, src)
2920 const char *filename;
2921 int src;
2922 {
2923     FILE *fp;
2924     boolean rv = TRUE;
2925 
2926     if (!(fp = fopen_config_file(filename, src)))
2927         return FALSE;
2928 
2929     /* begin detection of duplicate configfile options */
2930     set_duplicate_opt_detection(1);
2931     free_config_sections();
2932     iflags.parse_config_file_src = src;
2933 
2934     rv = parse_conf_file(fp, parse_config_line);
2935     (void) fclose(fp);
2936 
2937     free_config_sections();
2938     /* turn off detection of duplicate configfile options */
2939     set_duplicate_opt_detection(0);
2940     return rv;
2941 }
2942 
2943 STATIC_OVL FILE *
fopen_wizkit_file()2944 fopen_wizkit_file()
2945 {
2946     FILE *fp;
2947 #if defined(VMS) || defined(UNIX)
2948     char tmp_wizkit[BUFSZ];
2949 #endif
2950     char *envp;
2951 
2952     envp = nh_getenv("WIZKIT");
2953     if (envp && *envp)
2954         (void) strncpy(wizkit, envp, WIZKIT_MAX - 1);
2955     if (!wizkit[0])
2956         return (FILE *) 0;
2957 
2958 #ifdef UNIX
2959     if (access(wizkit, 4) == -1) {
2960         /* 4 is R_OK on newer systems */
2961         /* nasty sneaky attempt to read file through
2962          * NetHack's setuid permissions -- this is a
2963          * place a file name may be wholly under the player's
2964          * control
2965          */
2966         raw_printf("Access to %s denied (%d).", wizkit, errno);
2967         wait_synch();
2968         /* fall through to standard names */
2969     } else
2970 #endif
2971         if ((fp = fopenp(wizkit, "r")) != (FILE *) 0) {
2972         return fp;
2973 #if defined(UNIX) || defined(VMS)
2974     } else {
2975         /* access() above probably caught most problems for UNIX */
2976         raw_printf("Couldn't open requested config file %s (%d).", wizkit,
2977                    errno);
2978         wait_synch();
2979 #endif
2980     }
2981 
2982 #if defined(MICRO) || defined(MAC) || defined(__BEOS__) || defined(WIN32)
2983     if ((fp = fopenp(fqname(wizkit, CONFIGPREFIX, 0), "r")) != (FILE *) 0)
2984         return fp;
2985 #else
2986 #ifdef VMS
2987     envp = nh_getenv("HOME");
2988     if (envp)
2989         Sprintf(tmp_wizkit, "%s%s", envp, wizkit);
2990     else
2991         Sprintf(tmp_wizkit, "%s%s", "sys$login:", wizkit);
2992     if ((fp = fopenp(tmp_wizkit, "r")) != (FILE *) 0)
2993         return fp;
2994 #else /* should be only UNIX left */
2995     envp = nh_getenv("HOME");
2996     if (envp)
2997         Sprintf(tmp_wizkit, "%s/%s", envp, wizkit);
2998     else
2999         Strcpy(tmp_wizkit, wizkit);
3000     if ((fp = fopenp(tmp_wizkit, "r")) != (FILE *) 0)
3001         return fp;
3002     else if (errno != ENOENT) {
3003         /* e.g., problems when setuid NetHack can't search home
3004          * directory restricted to user */
3005         raw_printf("Couldn't open default wizkit file %s (%d).", tmp_wizkit,
3006                    errno);
3007         wait_synch();
3008     }
3009 #endif
3010 #endif
3011     return (FILE *) 0;
3012 }
3013 
3014 /* add to hero's inventory if there's room, otherwise put item on floor */
3015 STATIC_DCL void
wizkit_addinv(obj)3016 wizkit_addinv(obj)
3017 struct obj *obj;
3018 {
3019     if (!obj || obj == &zeroobj)
3020         return;
3021 
3022     /* subset of starting inventory pre-ID */
3023     obj->dknown = 1;
3024     if (Role_if(PM_PRIEST))
3025         obj->bknown = 1; /* ok to bypass set_bknown() */
3026     /* same criteria as lift_object()'s check for available inventory slot */
3027     if (obj->oclass != COIN_CLASS && inv_cnt(FALSE) >= 52
3028         && !merge_choice(invent, obj)) {
3029         /* inventory overflow; can't just place & stack object since
3030            hero isn't in position yet, so schedule for arrival later */
3031         add_to_migration(obj);
3032         obj->ox = 0; /* index of main dungeon */
3033         obj->oy = 1; /* starting level number */
3034         obj->owornmask =
3035             (long) (MIGR_WITH_HERO | MIGR_NOBREAK | MIGR_NOSCATTER);
3036     } else {
3037         (void) addinv(obj);
3038     }
3039 }
3040 
3041 
3042 boolean
proc_wizkit_line(buf)3043 proc_wizkit_line(buf)
3044 char *buf;
3045 {
3046     struct obj *otmp;
3047 
3048     if (strlen(buf) >= BUFSZ)
3049         buf[BUFSZ - 1] = '\0';
3050     otmp = readobjnam(buf, (struct obj *) 0);
3051 
3052     if (otmp) {
3053         if (otmp != &zeroobj)
3054             wizkit_addinv(otmp);
3055     } else {
3056         /* .60 limits output line width to 79 chars */
3057         config_error_add("Bad wizkit item: \"%.60s\"", buf);
3058         return FALSE;
3059     }
3060     return TRUE;
3061 }
3062 
3063 void
read_wizkit()3064 read_wizkit()
3065 {
3066     FILE *fp;
3067 
3068     if (!wizard || !(fp = fopen_wizkit_file()))
3069         return;
3070 
3071     program_state.wizkit_wishing = 1;
3072     config_error_init(TRUE, "WIZKIT", FALSE);
3073 
3074     parse_conf_file(fp, proc_wizkit_line);
3075     (void) fclose(fp);
3076 
3077     config_error_done();
3078     program_state.wizkit_wishing = 0;
3079 
3080     return;
3081 }
3082 
3083 /* parse_conf_file
3084  *
3085  * Read from file fp, handling comments, empty lines, config sections,
3086  * CHOOSE, and line continuation, calling proc for every valid line.
3087  *
3088  * Continued lines are merged together with one space in between.
3089  */
3090 STATIC_OVL boolean
parse_conf_file(fp,proc)3091 parse_conf_file(fp, proc)
3092 FILE *fp;
3093 boolean FDECL((*proc), (char *));
3094 {
3095     char inbuf[4 * BUFSZ];
3096     boolean rv = TRUE; /* assume successful parse */
3097     char *ep;
3098     boolean skip = FALSE, morelines = FALSE;
3099     char *buf = (char *) 0;
3100     size_t inbufsz = sizeof inbuf;
3101 
3102     free_config_sections();
3103 
3104     while (fgets(inbuf, (int) inbufsz, fp)) {
3105         ep = index(inbuf, '\n');
3106         if (skip) { /* in case previous line was too long */
3107             if (ep)
3108                 skip = FALSE; /* found newline; next line is normal */
3109         } else {
3110             if (!ep) {  /* newline missing */
3111                 if (strlen(inbuf) < (inbufsz - 2)) {
3112                     /* likely the last line of file is just
3113                        missing a newline; process it anyway  */
3114                     ep = eos(inbuf);
3115                 } else {
3116                     config_error_add("Line too long, skipping");
3117                     skip = TRUE; /* discard next fgets */
3118                 }
3119             } else {
3120                 *ep = '\0'; /* remove newline */
3121             }
3122             if (ep) {
3123                 char *tmpbuf = (char *) 0;
3124                 int len;
3125                 boolean ignoreline = FALSE;
3126                 boolean oldline = FALSE;
3127 
3128                 /* line continuation (trailing '\') */
3129                 morelines = (--ep >= inbuf && *ep == '\\');
3130                 if (morelines)
3131                     *ep = '\0';
3132 
3133                 /* trim off spaces at end of line */
3134                 while (ep >= inbuf
3135                        && (*ep == ' ' || *ep == '\t' || *ep == '\r'))
3136                     *ep-- = '\0';
3137 
3138                 if (!config_error_nextline(inbuf)) {
3139                     rv = FALSE;
3140                     if (buf)
3141                         free(buf), buf = (char *) 0;
3142                     break;
3143                 }
3144 
3145                 ep = inbuf;
3146                 while (*ep == ' ' || *ep == '\t')
3147                     ++ep;
3148 
3149                 /* ignore empty lines and full-line comment lines */
3150                 if (!*ep || *ep == '#')
3151                     ignoreline = TRUE;
3152 
3153                 if (buf)
3154                     oldline = TRUE;
3155 
3156                 /* merge now read line with previous ones, if necessary */
3157                 if (!ignoreline) {
3158                     len = (int) strlen(ep) + 1; /* +1: final '\0' */
3159                     if (buf)
3160                         len += (int) strlen(buf) + 1; /* +1: space */
3161                     tmpbuf = (char *) alloc(len);
3162                     *tmpbuf = '\0';
3163                     if (buf) {
3164                         Strcat(strcpy(tmpbuf, buf), " ");
3165                         free(buf);
3166                     }
3167                     buf = strcat(tmpbuf, ep);
3168                     if (strlen(buf) >= sizeof inbuf)
3169                         buf[sizeof inbuf - 1] = '\0';
3170                 }
3171 
3172                 if (morelines || (ignoreline && !oldline))
3173                     continue;
3174 
3175                 if (handle_config_section(buf)) {
3176                     free(buf);
3177                     buf = (char *) 0;
3178                     continue;
3179                 }
3180 
3181                 /* from here onwards, we'll handle buf only */
3182 
3183                 if (match_varname(buf, "CHOOSE", 6)) {
3184                     char *section;
3185                     char *bufp = find_optparam(buf);
3186 
3187                     if (!bufp) {
3188                         config_error_add(
3189                                     "Format is CHOOSE=section1,section2,...");
3190                         rv = FALSE;
3191                         free(buf);
3192                         buf = (char *) 0;
3193                         continue;
3194                     }
3195                     bufp++;
3196                     if (config_section_chosen)
3197                         free(config_section_chosen), config_section_chosen = 0;
3198                     section = choose_random_part(bufp, ',');
3199                     if (section) {
3200                         config_section_chosen = dupstr(section);
3201                     } else {
3202                         config_error_add("No config section to choose");
3203                         rv = FALSE;
3204                     }
3205                     free(buf);
3206                     buf = (char *) 0;
3207                     continue;
3208                 }
3209 
3210                 if (!proc(buf))
3211                     rv = FALSE;
3212 
3213                 free(buf);
3214                 buf = (char *) 0;
3215             }
3216         }
3217     }
3218 
3219     if (buf)
3220         free(buf);
3221 
3222     free_config_sections();
3223     return rv;
3224 }
3225 
3226 extern struct symsetentry *symset_list;  /* options.c */
3227 extern const char *known_handling[];     /* drawing.c */
3228 extern const char *known_restrictions[]; /* drawing.c */
3229 static int symset_count = 0;             /* for pick-list building only */
3230 static boolean chosen_symset_start = FALSE, chosen_symset_end = FALSE;
3231 static int symset_which_set = 0;
3232 
3233 STATIC_OVL
3234 FILE *
fopen_sym_file()3235 fopen_sym_file()
3236 {
3237     FILE *fp;
3238 
3239     fp = fopen_datafile(SYMBOLS, "r",
3240 #ifdef WIN32
3241                             SYSCONFPREFIX
3242 #else
3243                             HACKPREFIX
3244 #endif
3245                        );
3246 
3247     return fp;
3248 }
3249 
3250 /*
3251  * Returns 1 if the chose symset was found and loaded.
3252  *         0 if it wasn't found in the sym file or other problem.
3253  */
3254 int
read_sym_file(which_set)3255 read_sym_file(which_set)
3256 int which_set;
3257 {
3258     FILE *fp;
3259 
3260     symset[which_set].explicitly = FALSE;
3261     if (!(fp = fopen_sym_file()))
3262         return 0;
3263 
3264     symset[which_set].explicitly = TRUE;
3265     symset_count = 0;
3266     chosen_symset_start = chosen_symset_end = FALSE;
3267     symset_which_set = which_set;
3268 
3269     config_error_init(TRUE, "symbols", FALSE);
3270 
3271     parse_conf_file(fp, proc_symset_line);
3272     (void) fclose(fp);
3273 
3274     if (!chosen_symset_start && !chosen_symset_end) {
3275         /* name caller put in symset[which_set].name was not found;
3276            if it looks like "Default symbols", null it out and return
3277            success to use the default; otherwise, return failure */
3278         if (symset[which_set].name
3279             && (fuzzymatch(symset[which_set].name, "Default symbols",
3280                            " -_", TRUE)
3281                 || !strcmpi(symset[which_set].name, "default")))
3282             clear_symsetentry(which_set, TRUE);
3283         config_error_done();
3284 
3285         /* If name was defined, it was invalid... Then we're loading fallback */
3286         if (symset[which_set].name) {
3287             symset[which_set].explicitly = FALSE;
3288             return 0;
3289         }
3290 
3291         return 1;
3292     }
3293     if (!chosen_symset_end)
3294         config_error_add("Missing finish for symset \"%s\"",
3295                          symset[which_set].name ? symset[which_set].name
3296                                                 : "unknown");
3297     config_error_done();
3298     return 1;
3299 }
3300 
3301 boolean
proc_symset_line(buf)3302 proc_symset_line(buf)
3303 char *buf;
3304 {
3305     return !((boolean) parse_sym_line(buf, symset_which_set));
3306 }
3307 
3308 /* returns 0 on error */
3309 int
parse_sym_line(buf,which_set)3310 parse_sym_line(buf, which_set)
3311 char *buf;
3312 int which_set;
3313 {
3314     int val, i;
3315     struct symparse *symp;
3316     char *bufp, *commentp, *altp;
3317 
3318     if (strlen(buf) >= BUFSZ)
3319         buf[BUFSZ - 1] = '\0';
3320     /* convert each instance of whitespace (tabs, consecutive spaces)
3321        into a single space; leading and trailing spaces are stripped */
3322     mungspaces(buf);
3323 
3324     /* remove trailing comment, if any (this isn't strictly needed for
3325        individual symbols, and it won't matter if "X#comment" without
3326        separating space slips through; for handling or set description,
3327        symbol set creator is responsible for preceding '#' with a space
3328        and that comment itself doesn't contain " #") */
3329     if ((commentp = rindex(buf, '#')) != 0 && commentp[-1] == ' ')
3330         commentp[-1] = '\0';
3331 
3332     /* find the '=' or ':' */
3333     bufp = index(buf, '=');
3334     altp = index(buf, ':');
3335     if (!bufp || (altp && altp < bufp))
3336         bufp = altp;
3337     if (!bufp) {
3338         if (strncmpi(buf, "finish", 6) == 0) {
3339             /* end current graphics set */
3340             if (chosen_symset_start)
3341                 chosen_symset_end = TRUE;
3342             chosen_symset_start = FALSE;
3343             return 1;
3344         }
3345         config_error_add("No \"finish\"");
3346         return 0;
3347     }
3348     /* skip '=' and space which follows, if any */
3349     ++bufp;
3350     if (*bufp == ' ')
3351         ++bufp;
3352 
3353     symp = match_sym(buf);
3354     if (!symp) {
3355         config_error_add("Unknown sym keyword");
3356         return 0;
3357     }
3358 
3359     if (!symset[which_set].name) {
3360         /* A null symset name indicates that we're just
3361            building a pick-list of possible symset
3362            values from the file, so only do that */
3363         if (symp->range == SYM_CONTROL) {
3364             struct symsetentry *tmpsp, *lastsp;
3365 
3366             for (lastsp = symset_list; lastsp; lastsp = lastsp->next)
3367                 if (!lastsp->next)
3368                     break;
3369             switch (symp->idx) {
3370             case 0:
3371                 tmpsp = (struct symsetentry *) alloc(sizeof *tmpsp);
3372                 tmpsp->next = (struct symsetentry *) 0;
3373                 if (!lastsp)
3374                     symset_list = tmpsp;
3375                 else
3376                     lastsp->next = tmpsp;
3377                 tmpsp->idx = symset_count++;
3378                 tmpsp->name = dupstr(bufp);
3379                 tmpsp->desc = (char *) 0;
3380                 tmpsp->handling = H_UNK;
3381                 /* initialize restriction bits */
3382                 tmpsp->nocolor = 0;
3383                 tmpsp->primary = 0;
3384                 tmpsp->rogue = 0;
3385                 break;
3386             case 2:
3387                 /* handler type identified */
3388                 tmpsp = lastsp; /* most recent symset */
3389                 for (i = 0; known_handling[i]; ++i)
3390                     if (!strcmpi(known_handling[i], bufp)) {
3391                         tmpsp->handling = i;
3392                         break; /* for loop */
3393                     }
3394                 break;
3395             case 3:
3396                 /* description:something */
3397                 tmpsp = lastsp; /* most recent symset */
3398                 if (tmpsp && !tmpsp->desc)
3399                     tmpsp->desc = dupstr(bufp);
3400                 break;
3401             case 5:
3402                 /* restrictions: xxxx*/
3403                 tmpsp = lastsp; /* most recent symset */
3404                 for (i = 0; known_restrictions[i]; ++i) {
3405                     if (!strcmpi(known_restrictions[i], bufp)) {
3406                         switch (i) {
3407                         case 0:
3408                             tmpsp->primary = 1;
3409                             break;
3410                         case 1:
3411                             tmpsp->rogue = 1;
3412                             break;
3413                         }
3414                         break; /* while loop */
3415                     }
3416                 }
3417                 break;
3418             }
3419         }
3420         return 1;
3421     }
3422     if (symp->range) {
3423         if (symp->range == SYM_CONTROL) {
3424             switch (symp->idx) {
3425             case 0:
3426                 /* start of symset */
3427                 if (!strcmpi(bufp, symset[which_set].name)) {
3428                     /* matches desired one */
3429                     chosen_symset_start = TRUE;
3430                     /* these init_*() functions clear symset fields too */
3431                     if (which_set == ROGUESET)
3432                         init_rogue_symbols();
3433                     else if (which_set == PRIMARY)
3434                         init_primary_symbols();
3435                 }
3436                 break;
3437             case 1:
3438                 /* finish symset */
3439                 if (chosen_symset_start)
3440                     chosen_symset_end = TRUE;
3441                 chosen_symset_start = FALSE;
3442                 break;
3443             case 2:
3444                 /* handler type identified */
3445                 if (chosen_symset_start)
3446                     set_symhandling(bufp, which_set);
3447                 break;
3448             /* case 3: (description) is ignored here */
3449             case 4: /* color:off */
3450                 if (chosen_symset_start) {
3451                     if (bufp) {
3452                         if (!strcmpi(bufp, "true") || !strcmpi(bufp, "yes")
3453                             || !strcmpi(bufp, "on"))
3454                             symset[which_set].nocolor = 0;
3455                         else if (!strcmpi(bufp, "false")
3456                                  || !strcmpi(bufp, "no")
3457                                  || !strcmpi(bufp, "off"))
3458                             symset[which_set].nocolor = 1;
3459                     }
3460                 }
3461                 break;
3462             case 5: /* restrictions: xxxx*/
3463                 if (chosen_symset_start) {
3464                     int n = 0;
3465 
3466                     while (known_restrictions[n]) {
3467                         if (!strcmpi(known_restrictions[n], bufp)) {
3468                             switch (n) {
3469                             case 0:
3470                                 symset[which_set].primary = 1;
3471                                 break;
3472                             case 1:
3473                                 symset[which_set].rogue = 1;
3474                                 break;
3475                             }
3476                             break; /* while loop */
3477                         }
3478                         n++;
3479                     }
3480                 }
3481                 break;
3482             }
3483         } else { /* !SYM_CONTROL */
3484             val = sym_val(bufp);
3485             if (chosen_symset_start) {
3486                 if (which_set == PRIMARY) {
3487                     update_primary_symset(symp, val);
3488                 } else if (which_set == ROGUESET) {
3489                     update_rogue_symset(symp, val);
3490                 }
3491             }
3492         }
3493     }
3494     return 1;
3495 }
3496 
3497 STATIC_OVL void
set_symhandling(handling,which_set)3498 set_symhandling(handling, which_set)
3499 char *handling;
3500 int which_set;
3501 {
3502     int i = 0;
3503 
3504     symset[which_set].handling = H_UNK;
3505     while (known_handling[i]) {
3506         if (!strcmpi(known_handling[i], handling)) {
3507             symset[which_set].handling = i;
3508             return;
3509         }
3510         i++;
3511     }
3512 }
3513 
3514 /* ----------  END CONFIG FILE HANDLING ----------- */
3515 
3516 /* ----------  BEGIN SCOREBOARD CREATION ----------- */
3517 
3518 #ifdef OS2_CODEVIEW
3519 #define UNUSED_if_not_OS2_CODEVIEW /*empty*/
3520 #else
3521 #define UNUSED_if_not_OS2_CODEVIEW UNUSED
3522 #endif
3523 
3524 /* verify that we can write to scoreboard file; if not, try to create one */
3525 /*ARGUSED*/
3526 void
check_recordfile(dir)3527 check_recordfile(dir)
3528 const char *dir UNUSED_if_not_OS2_CODEVIEW;
3529 {
3530 #if defined(PRAGMA_UNUSED) && !defined(OS2_CODEVIEW)
3531 #pragma unused(dir)
3532 #endif
3533     const char *fq_record;
3534     int fd;
3535 
3536 #if defined(UNIX) || defined(VMS)
3537     fq_record = fqname(RECORD, SCOREPREFIX, 0);
3538     fd = open(fq_record, O_RDWR, 0);
3539     if (fd >= 0) {
3540 #ifdef VMS /* must be stream-lf to use UPDATE_RECORD_IN_PLACE */
3541         if (!file_is_stmlf(fd)) {
3542             raw_printf(
3543                    "Warning: scoreboard file '%s' is not in stream_lf format",
3544                        fq_record);
3545             wait_synch();
3546         }
3547 #endif
3548         (void) nhclose(fd); /* RECORD is accessible */
3549     } else if ((fd = open(fq_record, O_CREAT | O_RDWR, FCMASK)) >= 0) {
3550         (void) nhclose(fd); /* RECORD newly created */
3551 #if defined(VMS) && !defined(SECURE)
3552         /* Re-protect RECORD with world:read+write+execute+delete access. */
3553         (void) chmod(fq_record, FCMASK | 007);
3554 #endif /* VMS && !SECURE */
3555     } else {
3556         raw_printf("Warning: cannot write scoreboard file '%s'", fq_record);
3557         wait_synch();
3558     }
3559 #endif /* !UNIX && !VMS */
3560 #if defined(MICRO) || defined(WIN32)
3561     char tmp[PATHLEN];
3562 
3563 #ifdef OS2_CODEVIEW /* explicit path on opening for OS/2 */
3564     /* how does this work when there isn't an explicit path or fopenp
3565      * for later access to the file via fopen_datafile? ? */
3566     (void) strncpy(tmp, dir, PATHLEN - 1);
3567     tmp[PATHLEN - 1] = '\0';
3568     if ((strlen(tmp) + 1 + strlen(RECORD)) < (PATHLEN - 1)) {
3569         append_slash(tmp);
3570         Strcat(tmp, RECORD);
3571     }
3572     fq_record = tmp;
3573 #else
3574     Strcpy(tmp, RECORD);
3575     fq_record = fqname(RECORD, SCOREPREFIX, 0);
3576 #endif
3577 #ifdef WIN32
3578     /* If dir is NULL it indicates create but
3579        only if it doesn't already exist */
3580     if (!dir) {
3581         char buf[BUFSZ];
3582 
3583         buf[0] = '\0';
3584         fd = open(fq_record, O_RDWR);
3585         if (!(fd == -1 && errno == ENOENT)) {
3586             if (fd >= 0) {
3587                 (void) nhclose(fd);
3588             } else {
3589                 /* explanation for failure other than missing file */
3590                 Sprintf(buf, "error   \"%s\", (errno %d).",
3591                         fq_record, errno);
3592                 paniclog("scorefile", buf);
3593             }
3594             return;
3595         }
3596         Sprintf(buf, "missing \"%s\", creating new scorefile.",
3597                 fq_record);
3598         paniclog("scorefile", buf);
3599     }
3600 #endif
3601 
3602     if ((fd = open(fq_record, O_RDWR)) < 0) {
3603         /* try to create empty 'record' */
3604 #if defined(AZTEC_C) || defined(_DCC) \
3605     || (defined(__GNUC__) && defined(__AMIGA__))
3606         /* Aztec doesn't use the third argument */
3607         /* DICE doesn't like it */
3608         fd = open(fq_record, O_CREAT | O_RDWR);
3609 #else
3610         fd = open(fq_record, O_CREAT | O_RDWR, S_IREAD | S_IWRITE);
3611 #endif
3612         if (fd <= 0) {
3613             raw_printf("Warning: cannot write record '%s'", tmp);
3614             wait_synch();
3615         } else {
3616             (void) nhclose(fd);
3617         }
3618     } else {
3619         /* open succeeded => 'record' exists */
3620         (void) nhclose(fd);
3621     }
3622 #else /* MICRO || WIN32*/
3623 
3624 #ifdef MAC
3625     /* Create the "record" file, if necessary */
3626     fq_record = fqname(RECORD, SCOREPREFIX, 0);
3627     fd = macopen(fq_record, O_RDWR | O_CREAT, TEXT_TYPE);
3628     if (fd != -1)
3629         macclose(fd);
3630 #endif /* MAC */
3631 
3632 #endif /* MICRO || WIN32*/
3633 }
3634 
3635 /* ----------  END SCOREBOARD CREATION ----------- */
3636 
3637 /* ----------  BEGIN PANIC/IMPOSSIBLE/TESTING LOG ----------- */
3638 
3639 /*ARGSUSED*/
3640 void
paniclog(type,reason)3641 paniclog(type, reason)
3642 const char *type;   /* panic, impossible, trickery */
3643 const char *reason; /* explanation */
3644 {
3645 #ifdef PANICLOG
3646     FILE *lfile;
3647     char buf[BUFSZ];
3648 
3649     if (!program_state.in_paniclog) {
3650         program_state.in_paniclog = 1;
3651         lfile = fopen_datafile(PANICLOG, "a", TROUBLEPREFIX);
3652         if (lfile) {
3653 #ifdef PANICLOG_FMT2
3654             (void) fprintf(lfile, "%ld %s: %s %s\n",
3655                            ubirthday, (plname ? plname : "(none)"),
3656                            type, reason);
3657 #else
3658             time_t now = getnow();
3659             int uid = getuid();
3660             char playmode = wizard ? 'D' : discover ? 'X' : '-';
3661 
3662             (void) fprintf(lfile, "%s %08ld %06ld %d %c: %s %s\n",
3663                            version_string(buf), yyyymmdd(now), hhmmss(now),
3664                            uid, playmode, type, reason);
3665 #endif /* !PANICLOG_FMT2 */
3666             (void) fclose(lfile);
3667         }
3668         program_state.in_paniclog = 0;
3669     }
3670 #endif /* PANICLOG */
3671     return;
3672 }
3673 
3674 void
testinglog(filenm,type,reason)3675 testinglog(filenm, type, reason)
3676 const char *filenm;   /* ad hoc file name */
3677 const char *type;
3678 const char *reason;   /* explanation */
3679 {
3680     FILE *lfile;
3681     char fnbuf[BUFSZ];
3682 
3683     if (!filenm)
3684         return;
3685     Strcpy(fnbuf, filenm);
3686     if (index(fnbuf, '.') == 0)
3687         Strcat(fnbuf, ".log");
3688     lfile = fopen_datafile(fnbuf, "a", TROUBLEPREFIX);
3689     if (lfile) {
3690         (void) fprintf(lfile, "%s\n%s\n", type, reason);
3691         (void) fclose(lfile);
3692     }
3693     return;
3694 }
3695 
3696 /* ----------  END PANIC/IMPOSSIBLE/TESTING LOG ----------- */
3697 
3698 #ifdef SELF_RECOVER
3699 
3700 /* ----------  BEGIN INTERNAL RECOVER ----------- */
3701 boolean
recover_savefile()3702 recover_savefile()
3703 {
3704     int gfd, lfd, sfd;
3705     int lev, savelev, hpid, pltmpsiz;
3706     xchar levc;
3707     struct version_info version_data;
3708     int processed[256];
3709     char savename[SAVESIZE], errbuf[BUFSZ];
3710     struct savefile_info sfi;
3711     char tmpplbuf[PL_NSIZ];
3712 
3713     for (lev = 0; lev < 256; lev++)
3714         processed[lev] = 0;
3715 
3716     /* level 0 file contains:
3717      *  pid of creating process (ignored here)
3718      *  level number for current level of save file
3719      *  name of save file nethack would have created
3720      *  savefile info
3721      *  player name
3722      *  and game state
3723      */
3724     gfd = open_levelfile(0, errbuf);
3725     if (gfd < 0) {
3726         raw_printf("%s\n", errbuf);
3727         return FALSE;
3728     }
3729     if (read(gfd, (genericptr_t) &hpid, sizeof hpid) != sizeof hpid) {
3730         raw_printf("\n%s\n%s\n",
3731             "Checkpoint data incompletely written or subsequently clobbered.",
3732                    "Recovery impossible.");
3733         (void) nhclose(gfd);
3734         return FALSE;
3735     }
3736     if (read(gfd, (genericptr_t) &savelev, sizeof(savelev))
3737         != sizeof(savelev)) {
3738         raw_printf(
3739          "\nCheckpointing was not in effect for %s -- recovery impossible.\n",
3740                    lock);
3741         (void) nhclose(gfd);
3742         return FALSE;
3743     }
3744     if ((read(gfd, (genericptr_t) savename, sizeof savename)
3745          != sizeof savename)
3746         || (read(gfd, (genericptr_t) &version_data, sizeof version_data)
3747             != sizeof version_data)
3748         || (read(gfd, (genericptr_t) &sfi, sizeof sfi) != sizeof sfi)
3749         || (read(gfd, (genericptr_t) &pltmpsiz, sizeof pltmpsiz)
3750             != sizeof pltmpsiz) || (pltmpsiz > PL_NSIZ)
3751         || (read(gfd, (genericptr_t) &tmpplbuf, pltmpsiz) != pltmpsiz)) {
3752         raw_printf("\nError reading %s -- can't recover.\n", lock);
3753         (void) nhclose(gfd);
3754         return FALSE;
3755     }
3756 
3757     /* save file should contain:
3758      *  version info
3759      *  savefile info
3760      *  player name
3761      *  current level (including pets)
3762      *  (non-level-based) game state
3763      *  other levels
3764      */
3765     set_savefile_name(TRUE);
3766     sfd = create_savefile();
3767     if (sfd < 0) {
3768         raw_printf("\nCannot recover savefile %s.\n", SAVEF);
3769         (void) nhclose(gfd);
3770         return FALSE;
3771     }
3772 
3773     lfd = open_levelfile(savelev, errbuf);
3774     if (lfd < 0) {
3775         raw_printf("\n%s\n", errbuf);
3776         (void) nhclose(gfd);
3777         (void) nhclose(sfd);
3778         delete_savefile();
3779         return FALSE;
3780     }
3781 
3782     if (write(sfd, (genericptr_t) &version_data, sizeof version_data)
3783         != sizeof version_data) {
3784         raw_printf("\nError writing %s; recovery failed.", SAVEF);
3785         (void) nhclose(gfd);
3786         (void) nhclose(sfd);
3787         (void) nhclose(lfd);
3788         delete_savefile();
3789         return FALSE;
3790     }
3791 
3792     if (write(sfd, (genericptr_t) &sfi, sizeof sfi) != sizeof sfi) {
3793         raw_printf("\nError writing %s; recovery failed (savefile_info).\n",
3794                    SAVEF);
3795         (void) nhclose(gfd);
3796         (void) nhclose(sfd);
3797         (void) nhclose(lfd);
3798         delete_savefile();
3799         return FALSE;
3800     }
3801 
3802     if (write(sfd, (genericptr_t) &pltmpsiz, sizeof pltmpsiz)
3803         != sizeof pltmpsiz) {
3804         raw_printf("Error writing %s; recovery failed (player name size).\n",
3805                    SAVEF);
3806         (void) nhclose(gfd);
3807         (void) nhclose(sfd);
3808         (void) nhclose(lfd);
3809         delete_savefile();
3810         return FALSE;
3811     }
3812 
3813     if (write(sfd, (genericptr_t) &tmpplbuf, pltmpsiz) != pltmpsiz) {
3814         raw_printf("Error writing %s; recovery failed (player name).\n",
3815                    SAVEF);
3816         (void) nhclose(gfd);
3817         (void) nhclose(sfd);
3818         (void) nhclose(lfd);
3819         delete_savefile();
3820         return FALSE;
3821     }
3822 
3823     if (!copy_bytes(lfd, sfd)) {
3824         (void) nhclose(gfd);
3825         (void) nhclose(sfd);
3826         (void) nhclose(lfd);
3827         delete_savefile();
3828         return FALSE;
3829     }
3830     (void) nhclose(lfd);
3831     processed[savelev] = 1;
3832 
3833     if (!copy_bytes(gfd, sfd)) {
3834         (void) nhclose(gfd);
3835         (void) nhclose(sfd);
3836         delete_savefile();
3837         return FALSE;
3838     }
3839     (void) nhclose(gfd);
3840     processed[0] = 1;
3841 
3842     for (lev = 1; lev < 256; lev++) {
3843         /* level numbers are kept in xchars in save.c, so the
3844          * maximum level number (for the endlevel) must be < 256
3845          */
3846         if (lev != savelev) {
3847             lfd = open_levelfile(lev, (char *) 0);
3848             if (lfd >= 0) {
3849                 /* any or all of these may not exist */
3850                 levc = (xchar) lev;
3851                 write(sfd, (genericptr_t) &levc, sizeof(levc));
3852                 if (!copy_bytes(lfd, sfd)) {
3853                     (void) nhclose(lfd);
3854                     (void) nhclose(sfd);
3855                     delete_savefile();
3856                     return FALSE;
3857                 }
3858                 (void) nhclose(lfd);
3859                 processed[lev] = 1;
3860             }
3861         }
3862     }
3863     (void) nhclose(sfd);
3864 
3865 #ifdef HOLD_LOCKFILE_OPEN
3866     really_close();
3867 #endif
3868     /*
3869      * We have a successful savefile!
3870      * Only now do we erase the level files.
3871      */
3872     for (lev = 0; lev < 256; lev++) {
3873         if (processed[lev]) {
3874             const char *fq_lock;
3875 
3876             set_levelfile_name(lock, lev);
3877             fq_lock = fqname(lock, LEVELPREFIX, 3);
3878             (void) unlink(fq_lock);
3879         }
3880     }
3881     return TRUE;
3882 }
3883 
3884 boolean
copy_bytes(ifd,ofd)3885 copy_bytes(ifd, ofd)
3886 int ifd, ofd;
3887 {
3888     char buf[BUFSIZ];
3889     int nfrom, nto;
3890 
3891     do {
3892         nfrom = read(ifd, buf, BUFSIZ);
3893         nto = write(ofd, buf, nfrom);
3894         if (nto != nfrom)
3895             return FALSE;
3896     } while (nfrom == BUFSIZ);
3897     return TRUE;
3898 }
3899 
3900 /* ----------  END INTERNAL RECOVER ----------- */
3901 #endif /*SELF_RECOVER*/
3902 
3903 /* ----------  OTHER ----------- */
3904 
3905 #ifdef SYSCF
3906 #ifdef SYSCF_FILE
3907 void
assure_syscf_file()3908 assure_syscf_file()
3909 {
3910     int fd;
3911 
3912 #ifdef WIN32
3913     /* We are checking that the sysconf exists ... lock the path */
3914     fqn_prefix_locked[SYSCONFPREFIX] = TRUE;
3915 #endif
3916     /*
3917      * All we really care about is the end result - can we read the file?
3918      * So just check that directly.
3919      *
3920      * Not tested on most of the old platforms (which don't attempt
3921      * to implement SYSCF).
3922      * Some ports don't like open()'s optional third argument;
3923      * VMS overrides open() usage with a macro which requires it.
3924      */
3925 #ifndef VMS
3926 # if defined(NOCWD_ASSUMPTIONS) && defined(WIN32)
3927     fd = open(fqname(SYSCF_FILE, SYSCONFPREFIX, 0), O_RDONLY);
3928 # else
3929     fd = open(SYSCF_FILE, O_RDONLY);
3930 # endif
3931 #else
3932     fd = open(SYSCF_FILE, O_RDONLY, 0);
3933 #endif
3934     if (fd >= 0) {
3935         /* readable */
3936         close(fd);
3937         return;
3938     }
3939     raw_printf("Unable to open SYSCF_FILE.\n");
3940     exit(EXIT_FAILURE);
3941 }
3942 
3943 #endif /* SYSCF_FILE */
3944 #endif /* SYSCF */
3945 
3946 #ifdef DEBUG
3947 /* used by debugpline() to decide whether to issue a message
3948  * from a particular source file; caller passes __FILE__ and we check
3949  * whether it is in the source file list supplied by SYSCF's DEBUGFILES
3950  *
3951  * pass FALSE to override wildcard matching; useful for files
3952  * like dungeon.c and questpgr.c, which generate a ridiculous amount of
3953  * output if DEBUG is defined and effectively block the use of a wildcard */
3954 boolean
debugcore(filename,wildcards)3955 debugcore(filename, wildcards)
3956 const char *filename;
3957 boolean wildcards;
3958 {
3959     const char *debugfiles, *p;
3960 
3961     if (!filename || !*filename)
3962         return FALSE; /* sanity precaution */
3963 
3964     if (sysopt.env_dbgfl == 0) {
3965         /* check once for DEBUGFILES in the environment;
3966            if found, it supersedes the sysconf value
3967            [note: getenv() rather than nh_getenv() since a long value
3968            is valid and doesn't pose any sort of overflow risk here] */
3969         if ((p = getenv("DEBUGFILES")) != 0) {
3970             if (sysopt.debugfiles)
3971                 free((genericptr_t) sysopt.debugfiles);
3972             sysopt.debugfiles = dupstr(p);
3973             sysopt.env_dbgfl = 1;
3974         } else
3975             sysopt.env_dbgfl = -1;
3976     }
3977 
3978     debugfiles = sysopt.debugfiles;
3979     /* usual case: sysopt.debugfiles will be empty */
3980     if (!debugfiles || !*debugfiles)
3981         return FALSE;
3982 
3983 /* strip filename's path if present */
3984 #ifdef UNIX
3985     if ((p = rindex(filename, '/')) != 0)
3986         filename = p + 1;
3987 #endif
3988 #ifdef VMS
3989     filename = vms_basename(filename);
3990     /* vms_basename strips off 'type' suffix as well as path and version;
3991        we want to put suffix back (".c" assumed); since it always returns
3992        a pointer to a static buffer, we can safely modify its result */
3993     Strcat((char *) filename, ".c");
3994 #endif
3995 
3996     /*
3997      * Wildcard match will only work if there's a single pattern (which
3998      * might be a single file name without any wildcarding) rather than
3999      * a space-separated list.
4000      * [to NOT do: We could step through the space-separated list and
4001      * attempt a wildcard match against each element, but that would be
4002      * overkill for the intended usage.]
4003      */
4004     if (wildcards && pmatch(debugfiles, filename))
4005         return TRUE;
4006 
4007     /* check whether filename is an element of the list */
4008     if ((p = strstr(debugfiles, filename)) != 0) {
4009         int l = (int) strlen(filename);
4010 
4011         if ((p == debugfiles || p[-1] == ' ' || p[-1] == '/')
4012             && (p[l] == ' ' || p[l] == '\0'))
4013             return TRUE;
4014     }
4015     return FALSE;
4016 }
4017 
4018 #endif /*DEBUG*/
4019 
4020 #ifdef UNIX
4021 #ifndef PATH_MAX
4022 #include <limits.h>
4023 #endif
4024 #endif
4025 
4026 void
reveal_paths(VOID_ARGS)4027 reveal_paths(VOID_ARGS)
4028 {
4029     const char *fqn, *nodumpreason;
4030     char buf[BUFSZ];
4031 #if defined(SYSCF) || !defined(UNIX) || defined(DLB)
4032     const char *filep;
4033 #ifdef SYSCF
4034     const char *gamename = (hname && *hname) ? hname : "NetHack";
4035 #endif
4036 #endif
4037 #ifdef UNIX
4038     char *endp, *envp, cwdbuf[PATH_MAX];
4039 #endif
4040 #ifdef PREFIXES_IN_USE
4041     const char *strp;
4042     int i, maxlen = 0;
4043 
4044     raw_print("Variable playground locations:");
4045     for (i = 0; i < PREFIX_COUNT; i++)
4046         raw_printf("    [%-10s]=\"%s\"", fqn_prefix_names[i],
4047                    fqn_prefix[i] ? fqn_prefix[i] : "not set");
4048 #endif
4049 
4050     /* sysconf file */
4051 
4052 #ifdef SYSCF
4053 #ifdef PREFIXES_IN_USE
4054     strp = fqn_prefix_names[SYSCONFPREFIX];
4055     maxlen = BUFSZ - sizeof " (in )";
4056     if (strp && (int) strlen(strp) < maxlen)
4057         Sprintf(buf, " (in %s)", strp);
4058 #else
4059     buf[0] = '\0';
4060 #endif
4061     raw_printf("%s system configuration file%s:", s_suffix(gamename), buf);
4062 #ifdef SYSCF_FILE
4063     filep = SYSCF_FILE;
4064 #else
4065     filep = "sysconf";
4066 #endif
4067     fqn = fqname(filep, SYSCONFPREFIX, 0);
4068     if (fqn) {
4069         set_configfile_name(fqn);
4070         filep = configfile;
4071     }
4072     raw_printf("    \"%s\"", filep);
4073 #else /* !SYSCF */
4074     raw_printf("No system configuration file.");
4075 #endif /* ?SYSCF */
4076 
4077     /* symbols file */
4078 
4079     buf[0] = '\0';
4080 #ifndef UNIX
4081 #ifdef PREFIXES_IN_USE
4082 #ifdef WIN32
4083     strp = fqn_prefix_names[SYSCONFPREFIX];
4084 #else
4085     strp = fqn_prefix_names[HACKPREFIX];
4086 #endif /* WIN32 */
4087     maxlen = BUFSZ - sizeof " (in )";
4088     if (strp && (int) strlen(strp) < maxlen)
4089         Sprintf(buf, " (in %s)", strp);
4090 #endif /* PREFIXES_IN_USE */
4091     raw_printf("The loadable symbols file%s:", buf);
4092 #endif /* UNIX */
4093 
4094 #ifdef UNIX
4095     envp = getcwd(cwdbuf, PATH_MAX);
4096     if (envp) {
4097         raw_print("The loadable symbols file:");
4098         raw_printf("    \"%s/%s\"", envp, SYMBOLS);
4099     }
4100 #else /* UNIX */
4101     filep = SYMBOLS;
4102 #ifdef PREFIXES_IN_USE
4103 #ifdef WIN32
4104     fqn = fqname(filep, SYSCONFPREFIX, 1);
4105 #else
4106     fqn = fqname(filep, HACKPREFIX, 1);
4107 #endif /* WIN32 */
4108     if (fqn)
4109         filep = fqn;
4110 #endif /* PREFIXES_IN_USE */
4111     raw_printf("    \"%s\"", filep);
4112 #endif /* UNIX */
4113 
4114     /* dlb vs non-dlb */
4115 
4116     buf[0] = '\0';
4117 #ifdef PREFIXES_IN_USE
4118     strp = fqn_prefix_names[DATAPREFIX];
4119     maxlen = BUFSZ - sizeof " (in )";
4120     if (strp && (int) strlen(strp) < maxlen)
4121         Sprintf(buf, " (in %s)", strp);
4122 #endif
4123 #ifdef DLB
4124     raw_printf("Basic data files%s are collected inside:", buf);
4125     filep = DLBFILE;
4126 #ifdef VERSION_IN_DLB_FILENAME
4127     Strcpy(buf, build_dlb_filename((const char *) 0));
4128 #ifdef PREFIXES_IN_USE
4129     fqn = fqname(buf, DATAPREFIX, 1);
4130     if (fqn)
4131         filep = fqn;
4132 #endif /* PREFIXES_IN_USE */
4133 #endif
4134     raw_printf("    \"%s\"", filep);
4135 #ifdef DLBFILE2
4136     filep = DLBFILE2;
4137     raw_printf("    \"%s\"", filep);
4138 #endif
4139 #else /* !DLB */
4140     raw_printf("Basic data files%s are in many separate files.", buf);
4141 #endif /* ?DLB */
4142 
4143     /* dumplog */
4144 
4145 #ifndef DUMPLOG
4146     nodumpreason = "not supported";
4147 #else
4148     nodumpreason = "disabled";
4149 #ifdef SYSCF
4150     fqn = sysopt.dumplogfile;
4151 #else  /* !SYSCF */
4152 #ifdef DUMPLOG_FILE
4153     fqn = DUMPLOG_FILE;
4154 #else
4155     fqn = (char *) 0;
4156 #endif
4157 #endif /* ?SYSCF */
4158     if (fqn && *fqn) {
4159         raw_print("Your end-of-game disclosure file:");
4160         (void) dump_fmtstr(fqn, buf, FALSE);
4161         buf[sizeof buf - sizeof "    \"\""] = '\0';
4162         raw_printf("    \"%s\"", buf);
4163     } else
4164 #endif /* ?DUMPLOG */
4165         raw_printf("No end-of-game disclosure file (%s).", nodumpreason);
4166 
4167 #ifdef WIN32
4168     if (sysopt.portable_device_paths) {
4169         const char *pd = get_portable_device();
4170 
4171         raw_printf("portable_device_paths (set in sysconf):");
4172         raw_printf("    \"%s\"", pd);
4173     }
4174 #endif
4175 
4176     /* personal configuration file */
4177 
4178     buf[0] = '\0';
4179 #ifdef PREFIXES_IN_USE
4180     strp = fqn_prefix_names[CONFIGPREFIX];
4181     maxlen = BUFSZ - sizeof " (in )";
4182     if (strp && (int) strlen(strp) < maxlen)
4183         Sprintf(buf, " (in %s)", strp);
4184 #endif /* PREFIXES_IN_USE */
4185     raw_printf("Your personal configuration file%s:", buf);
4186 
4187 #ifdef UNIX
4188     buf[0] = '\0';
4189     if ((envp = nh_getenv("HOME")) != 0) {
4190         copynchars(buf, envp, (int) sizeof buf - 1 - 1);
4191         Strcat(buf, "/");
4192     }
4193     endp = eos(buf);
4194     copynchars(endp, default_configfile,
4195                (int) (sizeof buf - 1 - strlen(buf)));
4196 #if defined(__APPLE__) /* UNIX+__APPLE__ => MacOSX aka OSX aka macOS */
4197     if (envp) {
4198         if (access(buf, 4) == -1) { /* 4: R_OK, -1: failure */
4199             /* read access to default failed; might be protected excessively
4200                but more likely it doesn't exist; try first alternate:
4201                "$HOME/Library/Pref..."; 'endp' points past '/' */
4202             copynchars(endp, "Library/Preferences/NetHack Defaults",
4203                        (int) (sizeof buf - 1 - strlen(buf)));
4204             if (access(buf, 4) == -1) {
4205                 /* first alternate failed, try second:
4206                    ".../NetHack Defaults.txt"; no 'endp', just append */
4207                 copynchars(eos(buf), ".txt",
4208                            (int) (sizeof buf - 1 - strlen(buf)));
4209                 if (access(buf, 4) == -1) {
4210                     /* second alternate failed too, so revert to the
4211                        original default ("$HOME/.nethackrc") for message */
4212                     copynchars(endp, default_configfile,
4213                                (int) (sizeof buf - 1 - strlen(buf)));
4214                 }
4215             }
4216         }
4217     }
4218 #endif /* __APPLE__ */
4219     raw_printf("    \"%s\"", buf);
4220 #else /* !UNIX */
4221     fqn = (const char *) 0;
4222 #ifdef PREFIXES_IN_USE
4223     fqn = fqname(default_configfile, CONFIGPREFIX, 2);
4224 #endif
4225     raw_printf("    \"%s\"", fqn ? fqn : default_configfile);
4226 #endif  /* ?UNIX */
4227 
4228     raw_print("");
4229 }
4230 
4231 /* ----------  BEGIN TRIBUTE ----------- */
4232 
4233 /* 3.6 tribute code
4234  */
4235 
4236 #define SECTIONSCOPE 1
4237 #define TITLESCOPE 2
4238 #define PASSAGESCOPE 3
4239 
4240 #define MAXPASSAGES SIZE(context.novel.pasg) /* 20 */
4241 
4242 static int FDECL(choose_passage, (int, unsigned));
4243 
4244 /* choose a random passage that hasn't been chosen yet; once all have
4245    been chosen, reset the tracking to make all passages available again */
4246 static int
choose_passage(passagecnt,oid)4247 choose_passage(passagecnt, oid)
4248 int passagecnt; /* total of available passages */
4249 unsigned oid; /* book.o_id, used to determine whether re-reading same book */
4250 {
4251     int idx, res;
4252 
4253     if (passagecnt < 1)
4254         return 0;
4255 
4256     /* if a different book or we've used up all the passages already,
4257        reset in order to have all 'passagecnt' passages available */
4258     if (oid != context.novel.id || context.novel.count == 0) {
4259         int i, range = passagecnt, limit = MAXPASSAGES;
4260 
4261         context.novel.id = oid;
4262         if (range <= limit) {
4263             /* collect all of the N indices */
4264             context.novel.count = passagecnt;
4265             for (idx = 0; idx < MAXPASSAGES; idx++)
4266                 context.novel.pasg[idx] = (xchar) ((idx < passagecnt)
4267                                                    ? idx + 1 : 0);
4268         } else {
4269             /* collect MAXPASSAGES of the N indices */
4270             context.novel.count = MAXPASSAGES;
4271             for (idx = i = 0; i < passagecnt; ++i, --range)
4272                 if (range > 0 && rn2(range) < limit) {
4273                     context.novel.pasg[idx++] = (xchar) (i + 1);
4274                     --limit;
4275                 }
4276         }
4277     }
4278 
4279     idx = rn2(context.novel.count);
4280     res = (int) context.novel.pasg[idx];
4281     /* move the last slot's passage index into the slot just used
4282        and reduce the number of passages available */
4283     context.novel.pasg[idx] = context.novel.pasg[--context.novel.count];
4284     return res;
4285 }
4286 
4287 /* Returns True if you were able to read something. */
4288 boolean
read_tribute(tribsection,tribtitle,tribpassage,nowin_buf,bufsz,oid)4289 read_tribute(tribsection, tribtitle, tribpassage, nowin_buf, bufsz, oid)
4290 const char *tribsection, *tribtitle;
4291 int tribpassage, bufsz;
4292 char *nowin_buf;
4293 unsigned oid; /* book identifier */
4294 {
4295     dlb *fp;
4296     char line[BUFSZ], lastline[BUFSZ];
4297 
4298     int scope = 0;
4299     int linect = 0, passagecnt = 0, targetpassage = 0;
4300     const char *badtranslation = "an incomprehensible foreign translation";
4301     boolean matchedsection = FALSE, matchedtitle = FALSE;
4302     winid tribwin = WIN_ERR;
4303     boolean grasped = FALSE;
4304     boolean foundpassage = FALSE;
4305 
4306     if (nowin_buf)
4307         *nowin_buf = '\0';
4308 
4309     /* check for mandatories */
4310     if (!tribsection || !tribtitle) {
4311         if (!nowin_buf)
4312             pline("It's %s of \"%s\"!", badtranslation, tribtitle);
4313         return grasped;
4314     }
4315 
4316     debugpline3("read_tribute %s, %s, %d.", tribsection, tribtitle,
4317                 tribpassage);
4318 
4319     fp = dlb_fopen(TRIBUTEFILE, "r");
4320     if (!fp) {
4321         /* this is actually an error - cannot open tribute file! */
4322         if (!nowin_buf)
4323             pline("You feel too overwhelmed to continue!");
4324         return grasped;
4325     }
4326 
4327     /*
4328      * Syntax (not case-sensitive):
4329      *  %section books
4330      *
4331      * In the books section:
4332      *    %title booktitle (n)
4333      *          where booktitle=book title without quotes
4334      *          (n)= total number of passages present for this title
4335      *    %passage k
4336      *          where k=sequential passage number
4337      *
4338      * %e ends the passage/book/section
4339      *    If in a passage, it marks the end of that passage.
4340      *    If in a book, it marks the end of that book.
4341      *    If in a section, it marks the end of that section.
4342      *
4343      *  %section death
4344      */
4345 
4346     *line = *lastline = '\0';
4347     while (dlb_fgets(line, sizeof line, fp) != 0) {
4348         linect++;
4349         (void) strip_newline(line);
4350         switch (line[0]) {
4351         case '%':
4352             if (!strncmpi(&line[1], "section ", sizeof "section " - 1)) {
4353                 char *st = &line[9]; /* 9 from "%section " */
4354 
4355                 scope = SECTIONSCOPE;
4356                 matchedsection = !strcmpi(st, tribsection) ? TRUE : FALSE;
4357             } else if (!strncmpi(&line[1], "title ", sizeof "title " - 1)) {
4358                 char *st = &line[7]; /* 7 from "%title " */
4359                 char *p1, *p2;
4360 
4361                 if ((p1 = index(st, '(')) != 0) {
4362                     *p1++ = '\0';
4363                     (void) mungspaces(st);
4364                     if ((p2 = index(p1, ')')) != 0) {
4365                         *p2 = '\0';
4366                         passagecnt = atoi(p1);
4367                         scope = TITLESCOPE;
4368                         if (matchedsection && !strcmpi(st, tribtitle)) {
4369                             matchedtitle = TRUE;
4370                             targetpassage = !tribpassage
4371                                              ? choose_passage(passagecnt, oid)
4372                                              : (tribpassage <= passagecnt)
4373                                                 ? tribpassage : 0;
4374                         } else {
4375                             matchedtitle = FALSE;
4376                         }
4377                     }
4378                 }
4379             } else if (!strncmpi(&line[1], "passage ",
4380                                  sizeof "passage " - 1)) {
4381                 int passagenum = 0;
4382                 char *st = &line[9]; /* 9 from "%passage " */
4383 
4384                 mungspaces(st);
4385                 passagenum = atoi(st);
4386                 if (passagenum > 0 && passagenum <= passagecnt) {
4387                     scope = PASSAGESCOPE;
4388                     if (matchedtitle && passagenum == targetpassage) {
4389                         foundpassage = TRUE;
4390                         if (!nowin_buf) {
4391                             tribwin = create_nhwindow(NHW_MENU);
4392                             if (tribwin == WIN_ERR)
4393                                 goto cleanup;
4394                         }
4395                     }
4396                 }
4397             } else if (!strncmpi(&line[1], "e ", sizeof "e " - 1)) {
4398                 if (foundpassage)
4399                     goto cleanup;
4400                 if (scope == TITLESCOPE)
4401                     matchedtitle = FALSE;
4402                 if (scope == SECTIONSCOPE)
4403                     matchedsection = FALSE;
4404                 if (scope)
4405                     --scope;
4406             } else {
4407                 debugpline1("tribute file error: bad %% command, line %d.",
4408                             linect);
4409             }
4410             break;
4411         case '#':
4412             /* comment only, next! */
4413             break;
4414         default:
4415             if (foundpassage) {
4416                 if (!nowin_buf) {
4417                     /* outputting multi-line passage to text window */
4418                     putstr(tribwin, 0, line);
4419                     if (*line)
4420                         Strcpy(lastline, line);
4421                 } else {
4422                     /* fetching one-line passage into buffer */
4423                     copynchars(nowin_buf, line, bufsz - 1);
4424                     goto cleanup; /* don't wait for "%e passage" */
4425                 }
4426             }
4427         }
4428     }
4429 
4430  cleanup:
4431     (void) dlb_fclose(fp);
4432     if (nowin_buf) {
4433         /* one-line buffer */
4434         grasped = *nowin_buf ? TRUE : FALSE;
4435     } else {
4436         if (tribwin != WIN_ERR) { /* implies 'foundpassage' */
4437             /* multi-line window, normal case;
4438                if lastline is empty, there were no non-empty lines between
4439                "%passage n" and "%e passage" so we leave 'grasped' False */
4440             if (*lastline) {
4441                 display_nhwindow(tribwin, FALSE);
4442                 /* put the final attribution line into message history,
4443                    analogous to the summary line from long quest messages */
4444                 if (index(lastline, '['))
4445                     mungspaces(lastline); /* to remove leading spaces */
4446                 else /* construct one if necessary */
4447                     Sprintf(lastline, "[%s, by Terry Pratchett]", tribtitle);
4448                 putmsghistory(lastline, FALSE);
4449                 grasped = TRUE;
4450             }
4451             destroy_nhwindow(tribwin);
4452         }
4453         if (!grasped)
4454             /* multi-line window, problem */
4455             pline("It seems to be %s of \"%s\"!", badtranslation, tribtitle);
4456     }
4457     return grasped;
4458 }
4459 
4460 boolean
Death_quote(buf,bufsz)4461 Death_quote(buf, bufsz)
4462 char *buf;
4463 int bufsz;
4464 {
4465     unsigned death_oid = 1; /* chance of oid #1 being a novel is negligible */
4466 
4467     return read_tribute("Death", "Death Quotes", 0, buf, bufsz, death_oid);
4468 }
4469 
4470 /* ----------  END TRIBUTE ----------- */
4471 
4472 /*files.c*/
4473