OSDN Git Service

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