OSDN Git Service

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