Files
nethack/src/cfgfiles.c
nhmall 8c8121eb3c move a pair of functions from save.c to restore.c
A utility that links only with restore.o needs to find them.
2026-04-13 09:08:53 -04:00

2077 lines
53 KiB
C

/* NetHack 3.7 cfgfiles.c $NHDT-Date: 1740532826 2025/02/25 17:20:26 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.417 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Derek S. Ray, 2015. */
/* NetHack may be freely redistributed. See license for details. */
#define NEED_VARARGS
#include "hack.h"
#include "dlb.h"
#include <errno.h>
#if (!defined(MAC) && !defined(O_WRONLY) && !defined(AZTEC_C)) \
|| defined(USE_FCNTL)
#include <fcntl.h>
#endif
#define BIGBUFSZ (5 * BUFSZ) /* big enough to format a 4*BUFSZ string (from
* config file parsing) with modest decoration;
* result will then be truncated to BUFSZ-1 */
#ifdef USER_SOUNDS
extern char *sounddir; /* defined in sounds.c */
#endif
staticfn void vconfig_error_add(const char *, va_list);
staticfn FILE *fopen_config_file(const char *, int);
staticfn int get_uchars(char *, uchar *, boolean, int, const char *);
#ifdef NOCWD_ASSUMPTIONS
staticfn void adjust_prefix(char *, int);
#endif
staticfn char *choose_random_part(char *, char);
staticfn boolean config_error_nextline(const char *);
staticfn void free_config_sections(void);
staticfn char *is_config_section(char *);
staticfn boolean handle_config_section(char *);
boolean parse_config_line(char *);
staticfn char *find_optparam(char *);
#ifndef SFCTOOL
staticfn boolean cnf_line_OPTIONS(char *);
staticfn boolean cnf_line_AUTOPICKUP_EXCEPTION(char *);
staticfn boolean cnf_line_BINDINGS(char *);
staticfn boolean cnf_line_AUTOCOMPLETE(char *);
staticfn boolean cnf_line_MSGTYPE(char *);
staticfn boolean cnf_line_HACKDIR(char *);
staticfn boolean cnf_line_LEVELDIR(char *);
staticfn boolean cnf_line_SAVEDIR(char *);
staticfn boolean cnf_line_BONESDIR(char *);
staticfn boolean cnf_line_DATADIR(char *);
staticfn boolean cnf_line_SCOREDIR(char *);
staticfn boolean cnf_line_LOCKDIR(char *);
staticfn boolean cnf_line_CONFIGDIR(char *);
staticfn boolean cnf_line_TROUBLEDIR(char *);
staticfn boolean cnf_line_NAME(char *);
staticfn boolean cnf_line_ROLE(char *);
staticfn boolean cnf_line_dogname(char *);
staticfn boolean cnf_line_catname(char *);
#endif /* SFCTOOL */
#ifdef SYSCF
staticfn boolean cnf_line_WIZARDS(char *);
staticfn boolean cnf_line_SHELLERS(char *);
staticfn boolean cnf_line_MSGHANDLER(char *);
staticfn boolean cnf_line_EXPLORERS(char *);
staticfn boolean cnf_line_DEBUGFILES(char *);
staticfn boolean cnf_line_DUMPLOGFILE(char *);
staticfn boolean cnf_line_GENERICUSERS(char *);
staticfn boolean cnf_line_BONES_POOLS(char *);
staticfn boolean cnf_line_SUPPORT(char *);
staticfn boolean cnf_line_RECOVER(char *);
staticfn boolean cnf_line_CHECK_SAVE_UID(char *);
staticfn boolean cnf_line_CHECK_PLNAME(char *);
staticfn boolean cnf_line_SEDUCE(char *);
staticfn boolean cnf_line_HIDEUSAGE(char *);
staticfn boolean cnf_line_MAXPLAYERS(char *);
staticfn boolean cnf_line_PERSMAX(char *);
staticfn boolean cnf_line_PERS_IS_UID(char *);
staticfn boolean cnf_line_ENTRYMAX(char *);
staticfn boolean cnf_line_POINTSMIN(char *);
staticfn boolean cnf_line_MAX_STATUENAME_RANK(char *);
staticfn boolean cnf_line_LIVELOG(char *);
staticfn boolean cnf_line_PANICTRACE_LIBC(char *);
staticfn boolean cnf_line_PANICTRACE_GDB(char *);
staticfn boolean cnf_line_GDBPATH(char *);
staticfn boolean cnf_line_GREPPATH(char *);
staticfn boolean cnf_line_CRASHREPORTURL(char *);
staticfn boolean cnf_line_ACCESSIBILITY(char *);
staticfn boolean cnf_line_PORTABLE_DEVICE_PATHS(char *);
#endif /* SYSCF */
#ifndef SFCTOOL
staticfn boolean cnf_line_BOULDER(char *);
staticfn boolean cnf_line_MENUCOLOR(char *);
staticfn boolean cnf_line_HILITE_STATUS(char *);
staticfn boolean cnf_line_WARNINGS(char *);
staticfn boolean cnf_line_ROGUESYMBOLS(char *);
staticfn boolean cnf_line_SYMBOLS(char *);
staticfn boolean cnf_line_WIZKIT(char *);
#ifdef USER_SOUNDS
staticfn boolean cnf_line_SOUNDDIR(char *);
staticfn boolean cnf_line_SOUND(char *);
#endif
staticfn boolean cnf_line_QT_TILEWIDTH(char *);
staticfn boolean cnf_line_QT_TILEHEIGHT(char *);
staticfn boolean cnf_line_QT_FONTSIZE(char *);
staticfn boolean cnf_line_QT_COMPACT(char *);
#endif /* SFCTOOL */
struct _cnf_parser_state; /* defined below (far below...) */
staticfn void cnf_parser_init(struct _cnf_parser_state *parser);
staticfn void cnf_parser_done(struct _cnf_parser_state *parser);
staticfn void parse_conf_buf(struct _cnf_parser_state *parser,
boolean (*proc)(char *arg));
/* next one is in extern.h; why here too? */
boolean parse_conf_str(const char *str, boolean (*proc)(char *arg));
static boolean ignore_errors_on_unmatched = FALSE,
ignore_statement_errors = FALSE;
#ifdef SFCTOOL
#ifdef wait_synch
#undef wait_synch
#endif
#define wait_synch()
#endif /* SFCTOOL */
/* ---------- BEGIN CONFIG FILE HANDLING ----------- */
/* used for messaging. Also used in options.c */
static const char *default_configfile =
#ifdef UNIX
".nethackrc";
#else
#if defined(MAC) || defined(__BEOS__)
"NetHack Defaults";
#else
#if defined(MSDOS) || defined(WIN32)
CONFIG_FILE;
#else
"NetHack.cnf";
#endif
#endif
#endif
static char configfile[BUFSZ];
char *
get_configfile(void)
{
return configfile;
}
const char *
get_default_configfile(void)
{
return default_configfile;
}
#ifdef MSDOS
/* conflict with speed-dial under windows
* for XXX.cnf file so support of NetHack.cnf
* is for backward compatibility only.
* Preferred name (and first tried) is now defaults.nh but
* the game will try the old name if there
* is no defaults.nh.
*/
const char *backward_compat_configfile = "nethack.cnf";
#endif
#ifndef SFCTOOL
/* #saveoptions - save config options into file */
int
do_write_config_file(void)
{
FILE *fp;
char tmp[BUFSZ];
if (!configfile[0]) {
pline("Strange, could not figure out config file name.");
return ECMD_OK;
}
if (flags.suppress_alert < FEATURE_NOTICE_VER(3,7,0)) {
pline("Warning: saveoptions is highly experimental!");
wait_synch();
pline("Some settings are not saved!");
wait_synch();
pline("All manual customization and comments are removed"
" from the file!");
wait_synch();
}
#define overwrite_prompt "Overwrite config file %.*s?"
Sprintf(tmp, overwrite_prompt,
(int) (BUFSZ - sizeof overwrite_prompt - 2), configfile);
#undef overwrite_prompt
if (!paranoid_query(TRUE, tmp))
return ECMD_OK;
fp = fopen(configfile, "w");
if (fp) {
size_t len, wrote;
strbuf_t buf;
strbuf_init(&buf);
all_options_strbuf(&buf);
len = strlen(buf.str);
wrote = fwrite(buf.str, 1, len, fp);
fclose(fp);
strbuf_empty(&buf);
if (wrote != len)
pline("An error occurred, wrote only partial data (%zu/%zu).",
wrote, len);
}
return ECMD_OK;
}
#endif /* SFCTOOL */
/* remember the name of the file we're accessing;
if may be used in option reject messages */
void
set_configfile_name(const char *fname)
{
(void) strncpy(configfile, fname, sizeof configfile - 1);
configfile[sizeof configfile - 1] = '\0';
}
staticfn FILE *
fopen_config_file(const char *filename, int src)
{
FILE *fp;
#if defined(UNIX) || defined(VMS)
char tmp_config[BUFSZ];
char *envp;
#endif
if (src == set_in_sysconf) {
/* SYSCF_FILE; if we can't open it, caller will bail */
if (filename && *filename) {
set_configfile_name(fqname(filename, SYSCONFPREFIX, 0));
fp = fopen(configfile, "r");
} else
fp = (FILE *) 0;
return fp;
}
/* If src != set_in_sysconf, "filename" is an environment variable, so it
* should hang around. If set, it is expected to be a full path name
* (if relevant)
*/
if (filename && *filename) {
set_configfile_name(filename);
#ifdef UNIX
if (!strncmp(configfile, "~/", 2) && (envp = nh_getenv("HOME")) != 0) {
/* support for command line '--nethackrc=~/path' (or for
NETHACKOPTIONS='@~/path'; we don't support ~user/path) */
Snprintf(tmp_config, sizeof tmp_config, "%s/%s",
envp, configfile + 2); /* insert $HOME/ and remove ~/ */
set_configfile_name(tmp_config);
}
if (access(configfile, 4) == -1) { /* 4 is R_OK on newer systems */
/* nasty sneaky attempt to read file through
* NetHack's setuid permissions -- this is the only
* place a file name may be wholly under the player's
* control (but SYSCF_FILE is not under the player's
* control so it's OK).
*/
raw_printf("Access to %s denied (%d).", configfile, errno);
wait_synch();
/* fall through to standard names */
} else
#endif
if ((fp = fopen(configfile, "r")) != (FILE *) 0) {
return fp;
#if defined(UNIX) || defined(VMS)
} else {
/* access() above probably caught most problems for UNIX */
raw_printf("Couldn't open requested config file %s (%d).",
configfile, errno);
wait_synch();
#endif
}
}
/* fall through to standard names */
#if defined(MICRO) || defined(MAC) || defined(__BEOS__) || defined(WIN32)
set_configfile_name(fqname(default_configfile, CONFIGPREFIX, 0));
if ((fp = fopen(configfile, "r")) != (FILE *) 0) {
return fp;
} else if (strcmp(default_configfile, configfile)) {
set_configfile_name(default_configfile);
if ((fp = fopen(configfile, "r")) != (FILE *) 0)
return fp;
}
#ifdef MSDOS
set_configfile_name(fqname(backward_compat_configfile, CONFIGPREFIX, 0));
if ((fp = fopen(configfile, "r")) != (FILE *) 0) {
return fp;
} else if (strcmp(backward_compat_configfile, configfile)) {
set_configfile_name(backward_compat_configfile);
if ((fp = fopen(configfile, "r")) != (FILE *) 0)
return fp;
}
#endif
#else
/* constructed full path names don't need fqname() */
#ifdef VMS
/* no punctuation, so might be a logical name */
set_configfile_name("nethackini");
if ((fp = fopen(configfile, "r")) != (FILE *) 0)
return fp;
set_configfile_name("sys$login:nethack.ini");
if ((fp = fopen(configfile, "r")) != (FILE *) 0)
return fp;
envp = nh_getenv("HOME");
if (!envp || !*envp)
Strcpy(tmp_config, "NetHack.cnf");
else
Sprintf(tmp_config, "%s%s%s", envp,
!strchr(":]>/", envp[strlen(envp) - 1]) ? "/" : "",
"NetHack.cnf");
set_configfile_name(tmp_config);
if ((fp = fopen(configfile, "r")) != (FILE *) 0)
return fp;
#else /* should be only UNIX left */
envp = nh_getenv("HOME");
if (!envp)
Strcpy(tmp_config, ".nethackrc");
else
Sprintf(tmp_config, "%s/%s", envp, ".nethackrc");
set_configfile_name(tmp_config);
if ((fp = fopen(configfile, "r")) != (FILE *) 0)
return fp;
#if defined(__APPLE__) /* UNIX+__APPLE__ => OSX || MacOS */
/* try an alternative */
if (envp) {
/* keep 'tmp_config' intact here; if alternates fail, use it to
restore configfile[] to its preferred setting (".nethackrc") */
char alt_config[sizeof tmp_config];
/* OSX-style configuration settings */
Snprintf(alt_config, sizeof alt_config, "%s/%s", envp,
"Library/Preferences/NetHack Defaults");
set_configfile_name(alt_config);
if ((fp = fopen(configfile, "r")) != (FILE *) 0)
return fp;
/* may be easier for user to edit if filename has '.txt' suffix */
Snprintf(alt_config, sizeof alt_config, "%s/%s", envp,
"Library/Preferences/NetHack Defaults.txt");
set_configfile_name(alt_config);
if ((fp = fopen(configfile, "r")) != (FILE *) 0)
return fp;
/* couldn't open either of the alternate names; for use in
messages, put 'configfile' back to the normal value rather than
leaving it set to last alternate; retry open() to reset 'errno' */
set_configfile_name(tmp_config);
if ((fp = fopen(configfile, "r")) != (FILE *) 0)
return fp;
}
#endif /*__APPLE__*/
if (errno != ENOENT) {
const char *details;
/* e.g., problems when setuid NetHack can't search home
directory restricted to user */
#if defined(NHSTDC) && !defined(NOTSTDC)
if ((details = strerror(errno)) == 0)
#endif
details = "";
raw_printf("Couldn't open default config file %s %s(%d).",
configfile, details, errno);
wait_synch();
}
#endif /* !VMS => Unix */
#endif /* !(MICRO || MAC || __BEOS__ || WIN32) */
return (FILE *) 0;
}
/*
* Retrieve a list of integers from buf into a uchar array.
*
* NOTE: zeros are inserted unless modlist is TRUE, in which case the list
* location is unchanged. Callers must handle zeros if modlist is FALSE.
*/
staticfn int
get_uchars(char *bufp, /* current pointer */
uchar *list, /* return list */
boolean modlist, /* TRUE: list is being modified in place */
int size, /* return list size */
const char *name) /* name of option for error message */
{
unsigned int num = 0;
int count = 0;
boolean havenum = FALSE;
while (1) {
switch (*bufp) {
case ' ':
case '\0':
case '\t':
case '\n':
if (havenum) {
/* if modifying in place, don't insert zeros */
if (num || !modlist)
list[count] = num;
count++;
num = 0;
havenum = FALSE;
}
if (count == size || !*bufp)
return count;
bufp++;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
havenum = TRUE;
num = num * 10 + (*bufp - '0');
bufp++;
break;
case '\\':
goto gi_error;
break;
default:
gi_error:
raw_printf("Syntax error in %s", name);
wait_synch();
return count;
}
}
/*NOTREACHED*/
}
#ifdef NOCWD_ASSUMPTIONS
staticfn void
adjust_prefix(char *bufp, int prefixid)
{
char *ptr;
if (!bufp)
return;
#ifdef WIN32
if (fqn_prefix_locked[prefixid])
return;
#endif
/* Backward compatibility, ignore trailing ;n */
if ((ptr = strchr(bufp, ';')) != 0)
*ptr = '\0';
if (strlen(bufp) > 0) {
gf.fqn_prefix[prefixid] = (char *) alloc(strlen(bufp) + 2);
Strcpy(gf.fqn_prefix[prefixid], bufp);
append_slash(gf.fqn_prefix[prefixid]);
}
}
#endif
/* Choose at random one of the sep separated parts from str. Mangles str. */
staticfn char *
choose_random_part(char *str, char sep)
{
int nsep = 1;
int csep;
int len = 0;
char *begin = str;
if (!str)
return (char *) 0;
while (*str) {
if (*str == sep)
nsep++;
str++;
}
#ifndef SFCTOOL
csep = rn2(nsep);
#else
nhUse(nsep);
csep = 1;
#endif
str = begin;
while ((csep > 0) && *str) {
str++;
if (*str == sep)
csep--;
}
if (*str) {
if (*str == sep)
str++;
begin = str;
while (*str && *str != sep) {
str++;
len++;
}
*str = '\0';
if (len)
return begin;
}
return (char *) 0;
}
staticfn void
free_config_sections(void)
{
if (gc.config_section_chosen) {
free(gc.config_section_chosen);
gc.config_section_chosen = NULL;
}
if (gc.config_section_current) {
free(gc.config_section_current);
gc.config_section_current = NULL;
}
}
/* check for " [ anything-except-bracket-or-empty ] # arbitrary-comment"
with spaces optional; returns pointer to "anything-except..." (with
trailing " ] #..." stripped) if ok, otherwise Null */
staticfn char *
is_config_section(
char *str) /* trailing spaces are stripped, ']' too iff result is good */
{
char *a, *c, *z;
/* remove any spaces at start and end; won't significantly interfere
with echoing the string in a config error message, if warranted */
a = trimspaces(str);
/* first character should be open square bracket; set pointer past it */
if (*a++ != '[')
return (char *) 0;
/* last character should be close bracket, ignoring any comment */
z = strchr(a, ']');
if (!z)
return (char *) 0;
/* comment, if present, can be preceded by spaces */
for (c = z + 1; *c == ' '; ++c)
continue;
if (*c && *c != '#')
return (char *) 0;
/* we now know that result is good; there won't be a config error
message so we can modify the input string */
*z = '\0';
/* 'a' points past '[' and the string ends where ']' was; remove any
spaces between '[' and choice-start and between choice-end and ']' */
return trimspaces(a);
}
staticfn boolean
handle_config_section(char *buf)
{
char *sect = is_config_section(buf);
if (sect) {
if (gc.config_section_current)
free(gc.config_section_current), gc.config_section_current = 0;
/* is_config_section() removed brackets from 'sect' */
if (!gc.config_section_chosen) {
config_error_add("Section \"[%s]\" without CHOOSE", sect);
return TRUE;
}
if (*sect) { /* got a section name */
gc.config_section_current = dupstr(sect);
debugpline1("set config section: '%s'",
gc.config_section_current);
} else { /* empty section name => end of sections */
free_config_sections();
debugpline0("unset config section");
}
return TRUE;
}
if (gc.config_section_current) {
if (!gc.config_section_chosen)
return TRUE;
if (strcmp(gc.config_section_current, gc.config_section_chosen))
return TRUE;
}
return FALSE;
}
#define match_varname(INP, NAM, LEN) match_optname(INP, NAM, LEN, TRUE)
/* find the '=' or ':' */
staticfn char *
find_optparam(char *buf)
{
char *bufp, *altp;
bufp = strchr(buf, '=');
altp = strchr(buf, ':');
if (!bufp || (altp && altp < bufp))
bufp = altp;
return bufp;
}
#ifndef SFCTOOL
staticfn boolean
cnf_line_OPTIONS(char *origbuf)
{
char *bufp = find_optparam(origbuf);
++bufp; /* skip '='; parseoptions() handles spaces */
return parseoptions(bufp, TRUE, TRUE);
}
staticfn boolean
cnf_line_AUTOPICKUP_EXCEPTION(char *bufp)
{
add_autopickup_exception(bufp);
return TRUE;
}
staticfn boolean
cnf_line_BINDINGS(char *bufp)
{
return parsebindings(bufp);
}
staticfn boolean
cnf_line_AUTOCOMPLETE(char *bufp)
{
parseautocomplete(bufp, TRUE);
return TRUE;
}
staticfn boolean
cnf_line_MSGTYPE(char *bufp)
{
return msgtype_parse_add(bufp);
}
staticfn boolean
cnf_line_HACKDIR(char *bufp)
{
#ifdef NOCWD_ASSUMPTIONS
adjust_prefix(bufp, HACKPREFIX);
#else /*NOCWD_ASSUMPTIONS*/
#ifdef MICRO
(void) strncpy(gh.hackdir, bufp, PATHLEN - 1);
#else /* MICRO */
nhUse(bufp);
#endif /* MICRO */
#endif /*NOCWD_ASSUMPTIONS*/
return TRUE;
}
staticfn boolean
cnf_line_LEVELDIR(char *bufp)
{
#ifdef NOCWD_ASSUMPTIONS
adjust_prefix(bufp, LEVELPREFIX);
#else /*NOCWD_ASSUMPTIONS*/
#ifdef MICRO
if (strlen(bufp) >= PATHLEN)
bufp[PATHLEN - 1] = '\0';
Strcpy(g.permbones, bufp);
if (!ramdisk_specified || !*levels)
Strcpy(levels, bufp);
gr.ramdisk = (strcmp(g.permbones, levels) != 0);
#else /* MICRO */
nhUse(bufp);
#endif /* MICRO */
#endif /*NOCWD_ASSUMPTIONS*/
return TRUE;
}
staticfn boolean
cnf_line_SAVEDIR(char *bufp)
{
#ifdef NOCWD_ASSUMPTIONS
adjust_prefix(bufp, SAVEPREFIX);
#else /*NOCWD_ASSUMPTIONS*/
#ifdef MICRO
char *ptr;
if ((ptr = strchr(bufp, ';')) != 0) {
*ptr = '\0';
}
(void) strncpy(gs.SAVEP, bufp, SAVESIZE - 1);
append_slash(gs.SAVEP);
#else /* MICRO */
nhUse(bufp);
#endif /* MICRO */
#endif /*NOCWD_ASSUMPTIONS*/
return TRUE;
}
staticfn boolean
cnf_line_BONESDIR(char *bufp)
{
#ifdef NOCWD_ASSUMPTIONS
adjust_prefix(bufp, BONESPREFIX);
#else
nhUse(bufp);
#endif
return TRUE;
}
staticfn boolean
cnf_line_DATADIR(char *bufp)
{
#ifdef NOCWD_ASSUMPTIONS
adjust_prefix(bufp, DATAPREFIX);
#else
nhUse(bufp);
#endif
return TRUE;
}
staticfn boolean
cnf_line_SCOREDIR(char *bufp)
{
#ifdef NOCWD_ASSUMPTIONS
adjust_prefix(bufp, SCOREPREFIX);
#else
nhUse(bufp);
#endif
return TRUE;
}
staticfn boolean
cnf_line_LOCKDIR(char *bufp)
{
#ifdef NOCWD_ASSUMPTIONS
adjust_prefix(bufp, LOCKPREFIX);
#else
nhUse(bufp);
#endif
return TRUE;
}
staticfn boolean
cnf_line_CONFIGDIR(char *bufp)
{
#ifdef NOCWD_ASSUMPTIONS
adjust_prefix(bufp, CONFIGPREFIX);
#else
nhUse(bufp);
#endif
return TRUE;
}
staticfn boolean
cnf_line_TROUBLEDIR(char *bufp)
{
#ifdef NOCWD_ASSUMPTIONS
adjust_prefix(bufp, TROUBLEPREFIX);
#else
nhUse(bufp);
#endif
return TRUE;
}
staticfn boolean
cnf_line_NAME(char *bufp)
{
(void) strncpy(svp.plname, bufp, PL_NSIZ - 1);
return TRUE;
}
staticfn boolean
cnf_line_ROLE(char *bufp)
{
int len;
if ((len = str2role(bufp)) >= 0)
flags.initrole = len;
return TRUE;
}
staticfn boolean
cnf_line_dogname(char *bufp)
{
(void) strncpy(gd.dogname, bufp, PL_PSIZ - 1);
return TRUE;
}
staticfn boolean
cnf_line_catname(char *bufp)
{
(void) strncpy(gc.catname, bufp, PL_PSIZ - 1);
return TRUE;
}
#endif /* SFCTOOL */
#ifdef SYSCF
staticfn boolean
cnf_line_WIZARDS(char *bufp)
{
if (sysopt.wizards)
free((genericptr_t) sysopt.wizards);
sysopt.wizards = dupstr(bufp);
if (strlen(sysopt.wizards) && strcmp(sysopt.wizards, "*")) {
/* pre-format WIZARDS list now; it's displayed during a panic
and since that panic might be due to running out of memory,
we don't want to risk attempting to allocate any memory then */
if (sysopt.fmtd_wizard_list)
free((genericptr_t) sysopt.fmtd_wizard_list);
sysopt.fmtd_wizard_list = build_english_list(sysopt.wizards);
}
return TRUE;
}
staticfn boolean
cnf_line_SHELLERS(char *bufp)
{
if (sysopt.shellers)
free((genericptr_t) sysopt.shellers);
sysopt.shellers = dupstr(bufp);
return TRUE;
}
staticfn boolean
cnf_line_MSGHANDLER(char *bufp)
{
if (sysopt.msghandler)
free((genericptr_t) sysopt.msghandler);
sysopt.msghandler = dupstr(bufp);
return TRUE;
}
staticfn boolean
cnf_line_EXPLORERS(char *bufp)
{
if (sysopt.explorers)
free((genericptr_t) sysopt.explorers);
sysopt.explorers = dupstr(bufp);
return TRUE;
}
staticfn boolean
cnf_line_DEBUGFILES(char *bufp)
{
/* might already have a vaule from getenv("DEBUGFILES");
if so, ignore this value from SYSCF */
if (!sysopt.env_dbgfl) {
if (sysopt.debugfiles)
free((genericptr_t) sysopt.debugfiles);
sysopt.debugfiles = dupstr(bufp);
}
return TRUE;
}
staticfn boolean
cnf_line_DUMPLOGFILE(char *bufp)
{
#ifdef DUMPLOG
if (sysopt.dumplogfile)
free((genericptr_t) sysopt.dumplogfile);
sysopt.dumplogfile = dupstr(bufp);
#else
nhUse(bufp);
#endif /*DUMPLOG*/
return TRUE;
}
staticfn boolean
cnf_line_GENERICUSERS(char *bufp)
{
if (sysopt.genericusers)
free((genericptr_t) sysopt.genericusers);
sysopt.genericusers = dupstr(bufp);
return TRUE;
}
staticfn boolean
cnf_line_BONES_POOLS(char *bufp)
{
/* max value of 10 guarantees (N % bones.pools) will be one digit
so we don't lose control of the length of bones file names */
int n = atoi(bufp);
sysopt.bones_pools = (n <= 0) ? 0 : min(n, 10);
/* note: right now bones_pools==0 is the same as bones_pools==1,
but we could change that and make bones_pools==0 become an
indicator to suppress bones usage altogether */
return TRUE;
}
staticfn boolean
cnf_line_SUPPORT(char *bufp)
{
if (sysopt.support)
free((genericptr_t) sysopt.support);
sysopt.support = dupstr(bufp);
return TRUE;
}
staticfn boolean
cnf_line_RECOVER(char *bufp)
{
if (sysopt.recover)
free((genericptr_t) sysopt.recover);
sysopt.recover = dupstr(bufp);
return TRUE;
}
staticfn boolean
cnf_line_CHECK_SAVE_UID(char *bufp)
{
int n = atoi(bufp);
sysopt.check_save_uid = n;
return TRUE;
}
staticfn boolean
cnf_line_CHECK_PLNAME(char *bufp)
{
int n = atoi(bufp);
sysopt.check_plname = n;
return TRUE;
}
staticfn boolean
cnf_line_SEDUCE(char *bufp)
{
int n = !!atoi(bufp); /* XXX this could be tighter */
#ifdef SYSCF
int src = iflags.parse_config_file_src;
boolean in_sysconf = (src == set_in_sysconf);
#else
boolean in_sysconf = FALSE;
#endif
/* allow anyone to disable it but can only enable it in sysconf
or as a no-op for the user when sysconf hasn't disabled it */
if (!in_sysconf && !sysopt.seduce && n != 0) {
config_error_add("Illegal value in SEDUCE");
n = 0;
}
sysopt.seduce = n;
sysopt_seduce_set(sysopt.seduce);
return TRUE;
}
staticfn boolean
cnf_line_HIDEUSAGE(char *bufp)
{
int n = !!atoi(bufp);
sysopt.hideusage = n;
return TRUE;
}
staticfn boolean
cnf_line_MAXPLAYERS(char *bufp)
{
int n = atoi(bufp);
/* XXX to get more than 25, need to rewrite all lock code */
if (n < 0 || n > 25) {
config_error_add("Illegal value in MAXPLAYERS (maximum is 25)");
n = 5;
}
sysopt.maxplayers = n;
return TRUE;
}
staticfn boolean
cnf_line_PERSMAX(char *bufp)
{
int n = atoi(bufp);
if (n < 1) {
config_error_add("Illegal value in PERSMAX (minimum is 1)");
n = 0;
}
sysopt.persmax = n;
return TRUE;
}
staticfn boolean
cnf_line_PERS_IS_UID(char *bufp)
{
int n = atoi(bufp);
if (n != 0 && n != 1) {
config_error_add("Illegal value in PERS_IS_UID (must be 0 or 1)");
n = 0;
}
sysopt.pers_is_uid = n;
return TRUE;
}
staticfn boolean
cnf_line_ENTRYMAX(char *bufp)
{
int n = atoi(bufp);
if (n < 10) {
config_error_add("Illegal value in ENTRYMAX (minimum is 10)");
n = 10;
}
sysopt.entrymax = n;
return TRUE;
}
staticfn boolean
cnf_line_POINTSMIN(char *bufp)
{
int n = atoi(bufp);
if (n < 1) {
config_error_add("Illegal value in POINTSMIN (minimum is 1)");
n = 100;
}
sysopt.pointsmin = n;
return TRUE;
}
staticfn boolean
cnf_line_MAX_STATUENAME_RANK(char *bufp)
{
int n = atoi(bufp);
if (n < 1) {
config_error_add("Illegal value in MAX_STATUENAME_RANK"
" (minimum is 1)");
n = 10;
}
sysopt.tt_oname_maxrank = n;
return TRUE;
}
staticfn boolean
cnf_line_LIVELOG(char *bufp)
{
/* using 0 for base accepts "dddd" as decimal provided that first 'd'
isn't '0', "0xhhhh" as hexadecimal, and "0oooo" as octal; ignores
any trailing junk, including '8' or '9' for leading '0' octal */
long L = strtol(bufp, NULL, 0);
if (L < 0L || L > 0xffffL) {
config_error_add("Illegal value for LIVELOG"
" (must be between 0 and 0xFFFF).");
return 0;
}
sysopt.livelog = L;
return TRUE;
}
staticfn boolean
cnf_line_PANICTRACE_LIBC(char *bufp)
{
int n = atoi(bufp);
#if defined(PANICTRACE) && defined(PANICTRACE_LIBC)
if (n < 0 || n > 2) {
config_error_add("Illegal value in PANICTRACE_LIBC (not 0,1,2)");
n = 0;
}
#endif
sysopt.panictrace_libc = n;
return TRUE;
}
staticfn boolean
cnf_line_PANICTRACE_GDB(char *bufp)
{
int n = atoi(bufp);
#if defined(PANICTRACE)
if (n < 0 || n > 2) {
config_error_add("Illegal value in PANICTRACE_GDB (not 0,1,2)");
n = 0;
}
#endif
sysopt.panictrace_gdb = n;
return TRUE;
}
staticfn boolean
cnf_line_GDBPATH(char *bufp)
{
#if defined(PANICTRACE) && !defined(VMS)
if (!file_exists(bufp)) {
config_error_add("File specified in GDBPATH does not exist");
return FALSE;
}
#endif
if (sysopt.gdbpath)
free((genericptr_t) sysopt.gdbpath);
sysopt.gdbpath = dupstr(bufp);
return TRUE;
}
staticfn boolean
cnf_line_GREPPATH(char *bufp)
{
#if defined(PANICTRACE) && !defined(VMS)
if (!file_exists(bufp)) {
config_error_add("File specified in GREPPATH does not exist");
return FALSE;
}
#endif
if (sysopt.greppath)
free((genericptr_t) sysopt.greppath);
sysopt.greppath = dupstr(bufp);
return TRUE;
}
staticfn boolean
cnf_line_CRASHREPORTURL(char *bufp)
{
if (sysopt.crashreporturl)
free((genericptr_t) sysopt.crashreporturl);
sysopt.crashreporturl = dupstr(bufp);
return TRUE;
}
staticfn boolean
cnf_line_ACCESSIBILITY(char *bufp)
{
int n = atoi(bufp);
if (n < 0 || n > 1) {
config_error_add("Illegal value in ACCESSIBILITY (not 0,1)");
n = 0;
}
sysopt.accessibility = n;
return TRUE;
}
staticfn boolean
cnf_line_PORTABLE_DEVICE_PATHS(char *bufp)
{
#ifdef WIN32
int n = atoi(bufp);
if (n < 0 || n > 1) {
config_error_add("Illegal value in PORTABLE_DEVICE_PATHS"
" (not 0 or 1)");
n = 0;
}
sysopt.portable_device_paths = n;
#else /* Windows-only directive encountered by non-Windows config */
nhUse(bufp);
config_error_add("PORTABLE_DEVICE_PATHS is not supported");
#endif
return TRUE;
}
#endif /* SYSCF */
#ifndef SFCTOOL
staticfn boolean
cnf_line_BOULDER(char *bufp)
{
(void) get_uchars(bufp, &go.ov_primary_syms[SYM_BOULDER + SYM_OFF_X],
TRUE, 1, "BOULDER");
return TRUE;
}
staticfn boolean
cnf_line_MENUCOLOR(char *bufp)
{
return add_menu_coloring(bufp);
}
staticfn boolean
cnf_line_HILITE_STATUS(char *bufp)
{
#ifdef STATUS_HILITES
return parse_status_hl1(bufp, TRUE);
#else
nhUse(bufp);
return TRUE;
#endif
}
staticfn boolean
cnf_line_WARNINGS(char *bufp)
{
uchar translate[MAXPCHARS];
(void) get_uchars(bufp, translate, FALSE, WARNCOUNT, "WARNINGS");
assign_warnings(translate);
return TRUE;
}
staticfn boolean
cnf_line_ROGUESYMBOLS(char *bufp)
{
if (parsesymbols(bufp, ROGUESET)) {
switch_symbols(TRUE);
return TRUE;
}
config_error_add("Error in ROGUESYMBOLS definition '%s'", bufp);
return FALSE;
}
staticfn boolean
cnf_line_SYMBOLS(char *bufp)
{
if (parsesymbols(bufp, PRIMARYSET)) {
switch_symbols(TRUE);
return TRUE;
}
if (!config_unmatched_ignored())
config_error_add("Error in SYMBOLS definition '%s'", bufp);
return FALSE;
}
staticfn boolean
cnf_line_WIZKIT(char *bufp)
{
(void) strncpy(gw.wizkit, bufp, WIZKIT_MAX - 1);
return TRUE;
}
#ifdef USER_SOUNDS
staticfn boolean
cnf_line_SOUNDDIR(char *bufp)
{
if (sounddir)
free((genericptr_t) sounddir);
sounddir = dupstr(bufp);
return TRUE;
}
staticfn boolean
cnf_line_SOUND(char *bufp)
{
add_sound_mapping(bufp);
return TRUE;
}
#endif /*USER_SOUNDS*/
staticfn boolean
cnf_line_QT_TILEWIDTH(char *bufp)
{
#ifdef QT_GRAPHICS
extern char *qt_tilewidth;
if (qt_tilewidth == NULL)
qt_tilewidth = dupstr(bufp);
#else
nhUse(bufp);
#endif
return TRUE;
}
staticfn boolean
cnf_line_QT_TILEHEIGHT(char *bufp)
{
#ifdef QT_GRAPHICS
extern char *qt_tileheight;
if (qt_tileheight == NULL)
qt_tileheight = dupstr(bufp);
#else
nhUse(bufp);
#endif
return TRUE;
}
staticfn boolean
cnf_line_QT_FONTSIZE(char *bufp)
{
#ifdef QT_GRAPHICS
extern char *qt_fontsize;
if (qt_fontsize == NULL)
qt_fontsize = dupstr(bufp);
#else
nhUse(bufp);
#endif
return TRUE;
}
staticfn boolean
cnf_line_QT_COMPACT(char *bufp)
{
#ifdef QT_GRAPHICS
extern int qt_compact_mode;
qt_compact_mode = atoi(bufp);
#else
nhUse(bufp);
#endif
return TRUE;
}
#endif /* SFCTOOL */
typedef boolean (*config_line_stmt_func)(char *);
/* normal */
#define CNFL_N(n, l) { #n, l, FALSE, FALSE, cnf_line_##n }
/* normal, alias */
#define CNFL_NA(n, l, f) { #n, l, FALSE, FALSE, cnf_line_##f }
/* sysconf only */
#define CNFL_S(n, l) { #n, l, TRUE, FALSE, cnf_line_##n }
static const struct match_config_line_stmt {
const char *name;
int len;
boolean syscnf_only;
boolean origbuf;
config_line_stmt_func fn;
} config_line_stmt[] = {
#ifndef SFCTOOL
/* OPTIONS handled separately */
{ "OPTIONS", 4, FALSE, TRUE, cnf_line_OPTIONS },
CNFL_N(AUTOPICKUP_EXCEPTION, 5),
CNFL_N(BINDINGS, 4),
CNFL_N(AUTOCOMPLETE, 5),
CNFL_N(MSGTYPE, 7),
CNFL_N(HACKDIR, 4),
CNFL_N(LEVELDIR, 4),
CNFL_NA(LEVELS, 4, LEVELDIR),
CNFL_N(SAVEDIR, 4),
CNFL_N(BONESDIR, 5),
CNFL_N(DATADIR, 4),
CNFL_N(SCOREDIR, 4),
CNFL_N(LOCKDIR, 4),
CNFL_N(CONFIGDIR, 4),
CNFL_N(TROUBLEDIR, 4),
CNFL_N(NAME, 4),
CNFL_N(ROLE, 4),
CNFL_NA(CHARACTER, 4, ROLE),
CNFL_N(dogname, 3),
CNFL_N(catname, 3),
#endif /* SFCTOOL */
#ifdef SYSCF
CNFL_S(WIZARDS, 7),
CNFL_S(SHELLERS, 8),
CNFL_S(MSGHANDLER, 9),
CNFL_S(EXPLORERS, 7),
CNFL_S(DEBUGFILES, 5),
CNFL_S(DUMPLOGFILE, 7),
CNFL_S(GENERICUSERS, 12),
CNFL_S(BONES_POOLS, 10),
CNFL_S(SUPPORT, 7),
CNFL_S(RECOVER, 7),
CNFL_S(CHECK_SAVE_UID, 14),
CNFL_S(CHECK_PLNAME, 12),
CNFL_S(SEDUCE, 6),
CNFL_S(HIDEUSAGE, 9),
CNFL_S(MAXPLAYERS, 10),
CNFL_S(PERSMAX, 7),
CNFL_S(PERS_IS_UID, 11),
CNFL_S(ENTRYMAX, 8),
CNFL_S(POINTSMIN, 9),
CNFL_S(MAX_STATUENAME_RANK, 10),
CNFL_S(LIVELOG, 7),
CNFL_S(PANICTRACE_LIBC, 15),
CNFL_S(PANICTRACE_GDB, 14),
CNFL_S(CRASHREPORTURL, 13),
CNFL_S(GDBPATH, 7),
CNFL_S(GREPPATH, 7),
CNFL_S(ACCESSIBILITY, 13),
CNFL_S(PORTABLE_DEVICE_PATHS, 8),
#endif /*SYSCF*/
#ifndef SFCTOOL
CNFL_N(BOULDER, 3),
CNFL_N(MENUCOLOR, 9),
CNFL_N(HILITE_STATUS, 6),
CNFL_N(WARNINGS, 5),
CNFL_N(ROGUESYMBOLS, 4),
CNFL_N(SYMBOLS, 4),
CNFL_N(WIZKIT, 6),
#ifdef USER_SOUNDS
CNFL_N(SOUNDDIR, 8),
CNFL_N(SOUND, 5),
#endif /*USER_SOUNDS*/
CNFL_N(QT_TILEWIDTH, 12),
CNFL_N(QT_TILEHEIGHT, 13),
CNFL_N(QT_FONTSIZE, 11),
CNFL_N(QT_COMPACT, 10)
#endif /* SFCTOOL */
};
#undef CNFL_N
#undef CNFL_NA
#undef CNFL_S
static boolean disregarded_config_lines[SIZE(config_line_stmt)];
boolean
parse_config_line(char *origbuf)
{
#if defined(MICRO) && !defined(NOCWD_ASSUMPTIONS)
static boolean ramdisk_specified = FALSE;
#endif
#ifdef SYSCF
int src = iflags.parse_config_file_src;
boolean in_sysconf = (src == set_in_sysconf);
#endif
char *bufp, buf[4 * BUFSZ];
int i;
while (*origbuf == ' ' || *origbuf == '\t') /* skip leading whitespace */
++origbuf; /* (caller probably already did this) */
(void) strncpy(buf, origbuf, sizeof buf - 1);
buf[sizeof buf - 1] = '\0'; /* strncpy not guaranteed to NUL terminate */
/* convert any tab to space, condense consecutive spaces into one,
remove leading and trailing spaces (exception: if there is nothing
but spaces, one of them will be kept even though it leads/trails) */
mungspaces(buf);
/* find the '=' or ':' */
bufp = find_optparam(buf);
if (!bufp) {
if (!ignore_statement_errors)
config_error_add("Not a config statement, missing '='");
return FALSE;
}
/* skip past '=', then space between it and value, if any */
++bufp;
if (*bufp == ' ')
++bufp;
for (i = 0; i < SIZE(config_line_stmt); i++) {
#ifdef SYSCF
if (config_line_stmt[i].syscnf_only && !in_sysconf)
continue;
#endif
if (match_varname(buf, config_line_stmt[i].name,
config_line_stmt[i].len)) {
char *parm = config_line_stmt[i].origbuf ? origbuf : bufp;
if (!disregarded_config_lines[i])
return config_line_stmt[i].fn(parm);
}
}
if (!ignore_errors_on_unmatched)
config_error_add("Unknown config statement");
return FALSE;
}
#ifdef USER_SOUNDS
boolean
can_read_file(const char *filename)
{
return (boolean) (access(filename, 4) == 0);
}
#endif /* USER_SOUNDS */
struct _config_error_errmsg {
int line_num;
char *errormsg;
struct _config_error_errmsg *next;
};
struct _config_error_frame {
int line_num;
int num_errors;
boolean origline_shown;
boolean fromfile;
boolean secure;
char origline[4 * BUFSZ];
char source[BUFSZ];
struct _config_error_frame *next;
};
static struct _config_error_frame *config_error_data = 0;
static struct _config_error_errmsg *config_error_msg = 0;
void
config_error_init(boolean from_file, const char *sourcename, boolean secure)
{
struct _config_error_frame *tmp = (struct _config_error_frame *)
alloc(sizeof *tmp);
tmp->line_num = 0;
tmp->num_errors = 0;
tmp->origline_shown = FALSE;
tmp->fromfile = from_file;
tmp->secure = secure;
tmp->origline[0] = '\0';
if (sourcename && sourcename[0]) {
(void) strncpy(tmp->source, sourcename, sizeof (tmp->source) - 1);
tmp->source[sizeof (tmp->source) - 1] = '\0';
} else
tmp->source[0] = '\0';
tmp->next = config_error_data;
config_error_data = tmp;
program_state.config_error_ready = TRUE;
}
staticfn boolean
config_error_nextline(const char *line)
{
struct _config_error_frame *ced = config_error_data;
if (!ced)
return FALSE;
if (ced->num_errors && ced->secure)
return FALSE;
ced->line_num++;
ced->origline_shown = FALSE;
if (line && line[0]) {
(void) strncpy(ced->origline, line, sizeof (ced->origline) - 1);
ced->origline[sizeof (ced->origline) - 1] = '\0';
} else
ced->origline[0] = '\0';
return TRUE;
}
#ifndef SFCTOOL
int
l_get_config_errors(lua_State *L)
{
struct _config_error_errmsg *dat = config_error_msg;
struct _config_error_errmsg *tmp;
int idx = 1;
lua_newtable(L);
while (dat) {
lua_pushinteger(L, idx++);
lua_newtable(L);
nhl_add_table_entry_int(L, "line", dat->line_num);
nhl_add_table_entry_str(L, "error", dat->errormsg);
lua_settable(L, -3);
tmp = dat->next;
free(dat->errormsg);
dat->errormsg = (char *) 0;
free(dat);
dat = tmp;
}
config_error_msg = (struct _config_error_errmsg *) 0;
return 1;
}
#endif /* SFCTOOL */
/* varargs 'config_error_add()' moved to pline.c */
void
config_erradd(const char *buf)
{
char lineno[QBUFSZ];
const char *punct;
if (!buf || !*buf)
buf = "Unknown error";
/* if buf[] doesn't end in a period, exclamation point, or question mark,
we'll include a period (in the message, not appended to buf[]) */
punct = c_eos((char *) buf) - 1; /* eos(buf)-1 is valid */
punct = strchr(".!?", *punct) ? "" : ".";
if (!program_state.config_error_ready) {
/* either very early, where pline() will use raw_print(), or
player gave bad value when prompted by interactive 'O' command */
pline("%s%s%s", !iflags.window_inited ? "config_error_add: " : "",
buf, punct);
wait_synch();
return;
}
if (iflags.in_lua) {
struct _config_error_errmsg *dat
= (struct _config_error_errmsg *) alloc(sizeof *dat);
dat->next = config_error_msg;
dat->line_num = config_error_data->line_num;
dat->errormsg = dupstr(buf);
config_error_msg = dat;
return;
}
config_error_data->num_errors++;
if (!config_error_data->origline_shown && !config_error_data->secure) {
pline("\n%s", config_error_data->origline);
config_error_data->origline_shown = TRUE;
}
if (config_error_data->line_num > 0 && !config_error_data->secure) {
Sprintf(lineno, "Line %d: ", config_error_data->line_num);
} else
lineno[0] = '\0';
pline("%s %s%s%s", config_error_data->secure ? "Error:" : " *",
lineno, buf, punct);
}
int
config_error_done(void)
{
int n;
struct _config_error_frame *tmp = config_error_data;
if (!config_error_data)
return 0;
n = config_error_data->num_errors;
#ifndef USER_SOUNDS
if (gn.no_sound_notified > 0) {
/* no USER_SOUNDS; config_error_add() was called once for first
SOUND or SOUNDDIR entry seen, then skipped for any others;
include those skipped ones in the total error count */
n += (gn.no_sound_notified - 1);
gn.no_sound_notified = 0;
}
#endif
if (n) {
boolean cmdline = !strcmp(config_error_data->source, "command line");
pline("\n%d error%s %s %s.\n", n, plur(n), cmdline ? "on" : "in",
*config_error_data->source ? config_error_data->source
: configfile);
wait_synch();
}
config_error_data = tmp->next;
free(tmp);
program_state.config_error_ready = (config_error_data != 0);
return n;
}
boolean
read_config_file(const char *filename, int src)
{
FILE *fp;
boolean rv = TRUE;
if (!(fp = fopen_config_file(filename, src)))
return FALSE;
#ifndef SFCTOOL
/* begin detection of duplicate configfile options */
reset_duplicate_opt_detection();
#endif /* SFCTOOL */
free_config_sections();
iflags.parse_config_file_src = src;
rv = parse_conf_file(fp, parse_config_line);
(void) fclose(fp);
free_config_sections();
#ifndef SFCTOOL
/* turn off detection of duplicate configfile options */
reset_duplicate_opt_detection();
#endif /* SFCTOOL */
return rv;
}
struct _cnf_parser_state {
char *inbuf;
unsigned inbufsz;
int rv;
char *ep;
char *buf;
boolean skip, morelines;
boolean cont;
boolean pbreak;
};
/* Initialize config parser data */
staticfn void
cnf_parser_init(struct _cnf_parser_state *parser)
{
parser->rv = TRUE; /* assume successful parse */
parser->ep = parser->buf = (char *) 0;
parser->skip = FALSE;
parser->morelines = FALSE;
parser->inbufsz = 4 * BUFSZ;
parser->inbuf = (char *) alloc(parser->inbufsz);
parser->cont = FALSE;
parser->pbreak = FALSE;
memset(parser->inbuf, 0, parser->inbufsz);
}
/* caller has finished with 'parser' (except for 'rv' so leave that intact) */
staticfn void
cnf_parser_done(struct _cnf_parser_state *parser)
{
parser->ep = 0; /* points into parser->inbuf, so becoming stale */
if (parser->inbuf)
free(parser->inbuf), parser->inbuf = 0;
if (parser->buf)
free(parser->buf), parser->buf = 0;
}
/*
* Parse config buffer, handling comments, empty lines, config sections,
* CHOOSE, and line continuation, calling proc for every valid line.
*
* Continued lines are merged together with one space in between.
*/
staticfn void
parse_conf_buf(struct _cnf_parser_state *p, boolean (*proc)(char *arg))
{
p->cont = FALSE;
p->pbreak = FALSE;
p->ep = strchr(p->inbuf, '\n');
if (p->skip) { /* in case previous line was too long */
if (p->ep)
p->skip = FALSE; /* found newline; next line is normal */
} else {
if (!p->ep) { /* newline missing */
if (strlen(p->inbuf) < (p->inbufsz - 2)) {
/* likely the last line of file is just
missing a newline; process it anyway */
p->ep = eos(p->inbuf);
} else {
config_error_add("Line too long, skipping");
p->skip = TRUE; /* discard next fgets */
}
} else {
*p->ep = '\0'; /* remove newline */
}
if (p->ep) {
char *tmpbuf = (char *) 0;
int len;
boolean ignoreline = FALSE;
boolean oldline = FALSE;
/* line continuation (trailing '\') */
p->morelines = (--p->ep >= p->inbuf && *p->ep == '\\');
if (p->morelines)
*p->ep = '\0';
/* trim off spaces at end of line */
while (p->ep >= p->inbuf
&& (*p->ep == ' ' || *p->ep == '\t' || *p->ep == '\r'))
*p->ep-- = '\0';
if (!config_error_nextline(p->inbuf)) {
p->rv = FALSE;
if (p->buf)
free(p->buf), p->buf = (char *) 0;
p->pbreak = TRUE;
return;
}
p->ep = p->inbuf;
while (*p->ep == ' ' || *p->ep == '\t')
++p->ep;
/* ignore empty lines and full-line comment lines */
if (!*p->ep || *p->ep == '#')
ignoreline = TRUE;
if (p->buf)
oldline = TRUE;
/* merge now read line with previous ones, if necessary */
if (!ignoreline) {
len = (int) strlen(p->ep) + 1; /* +1: final '\0' */
if (p->buf)
len += (int) strlen(p->buf) + 1; /* +1: space */
tmpbuf = (char *) alloc(len);
*tmpbuf = '\0';
if (p->buf) {
Strcat(strcpy(tmpbuf, p->buf), " ");
free(p->buf), p->buf = 0;
}
p->buf = strcat(tmpbuf, p->ep);
if (strlen(p->buf) >= p->inbufsz)
p->buf[p->inbufsz - 1] = '\0';
}
if (p->morelines || (ignoreline && !oldline))
return;
if (handle_config_section(p->buf)) {
free(p->buf), p->buf = (char *) 0;
return;
}
/* from here onwards, we'll handle buf only */
if (match_varname(p->buf, "CHOOSE", 6)) {
char *section;
char *bufp = find_optparam(p->buf);
if (!bufp) {
config_error_add("Format is CHOOSE=section1"
",section2,...");
p->rv = FALSE;
free(p->buf), p->buf = (char *) 0;
return;
}
bufp++;
if (gc.config_section_chosen)
free(gc.config_section_chosen),
gc.config_section_chosen = 0;
section = choose_random_part(bufp, ',');
if (section) {
gc.config_section_chosen = dupstr(section);
} else {
config_error_add("No config section to choose");
p->rv = FALSE;
}
free(p->buf), p->buf = (char *) 0;
return;
}
if (!(*proc)(p->buf))
p->rv = FALSE;
free(p->buf), p->buf = (char *) 0;
}
}
}
boolean
parse_conf_str(const char *str, boolean (*proc)(char *arg))
{
size_t len;
struct _cnf_parser_state parser;
cnf_parser_init(&parser);
free_config_sections();
config_error_init(FALSE, "parse_conf_str", FALSE);
while (str && *str) {
len = 0;
while (*str && len < (parser.inbufsz-1)) {
parser.inbuf[len] = *str;
len++;
str++;
if (parser.inbuf[len-1] == '\n')
break;
}
parser.inbuf[len] = '\0';
parse_conf_buf(&parser, proc);
if (parser.pbreak)
break;
}
cnf_parser_done(&parser);
free_config_sections();
config_error_done();
return parser.rv;
}
/* parse_conf_file
*
* Read from file fp, calling parse_conf_buf for each line.
*/
boolean
parse_conf_file(FILE *fp, boolean (*proc)(char *arg))
{
struct _cnf_parser_state parser;
cnf_parser_init(&parser);
free_config_sections();
while (fgets(parser.inbuf, parser.inbufsz, fp)) {
parse_conf_buf(&parser, proc);
if (parser.pbreak)
break;
}
cnf_parser_done(&parser);
free_config_sections();
return parser.rv;
}
DISABLE_WARNING_FORMAT_NONLITERAL
void
config_error_add(const char *str, ...)
{
va_list the_args;
va_start(the_args, str);
vconfig_error_add(str, the_args);
va_end(the_args);
}
staticfn void
vconfig_error_add(const char *str, va_list the_args)
{ /* start of vconf...() or of nested block in USE_OLDARG's conf...() */
int vlen = 0;
char buf[BIGBUFSZ]; /* will be chopped down to BUFSZ-1 if longer */
vlen = vsnprintf(buf, sizeof buf, str, the_args);
#if (NH_DEVEL_STATUS != NH_STATUS_RELEASED) && defined(DEBUG)
if (vlen >= (int) sizeof buf)
panic("%s: truncation of buffer at %zu of %d bytes",
"config_error_add", sizeof buf, vlen);
#else
nhUse(vlen);
#endif
buf[BUFSZ - 1] = '\0';
config_erradd(buf);
}
#ifndef SFCTOOL
void
rcfile(void)
{
char *opts = 0, *xtraopts = 0;
const char *envname, *namesrc, *nameval;
go.opt_phase = environ_opt;
/* getenv() instead of nhgetenv(): let total length of options be long;
parseoptions() will check each individually */
envname = "NETHACKOPTIONS";
opts = getenv(envname);
if (!opts) {
/* fall back to original name; discouraged */
envname = "HACKOPTIONS";
opts = getenv(envname);
}
if (gc.cmdline_rcfile) {
namesrc = "command line";
nameval = gc.cmdline_rcfile;
xtraopts = opts;
if (opts && (*opts == '/' || *opts == '\\' || *opts == '@'))
xtraopts = 0; /* NETHACKOPTIONS is a file name; ignore it */
} else if (opts && (*opts == '/' || *opts == '\\' || *opts == '@')) {
/* NETHACKOPTIONS is a file name; use that instead of the default */
if (*opts == '@')
++opts; /* @filename */
namesrc = envname;
nameval = opts;
xtraopts = 0;
} else {
/* either no NETHACKOPTIONS or it wasn't a file name;
read the default configuration file */
nameval = namesrc = 0;
xtraopts = opts;
}
go.opt_phase = rc_file_opt;
/* seemingly arbitrary name length restriction is to prevent error
messages, if any were to be delivered while accessing the file,
from potentially overflowing buffers */
if (nameval && (int) strlen(nameval) >= BUFSZ / 2) {
config_error_init(TRUE, namesrc, FALSE);
config_error_add(
"nethackrc file name \"%.40s\"... too long; using default",
nameval);
config_error_done();
nameval = namesrc = 0; /* revert to default nethackrc */
}
config_error_init(TRUE, nameval, nameval ? CONFIG_ERROR_SECURE : FALSE);
(void) read_config_file(nameval, set_in_config);
config_error_done();
if (xtraopts) {
/* NETHACKOPTIONS is present and not a file name */
go.opt_phase = environ_opt;
config_error_init(FALSE, envname, FALSE);
(void) parseoptions(xtraopts, TRUE, FALSE);
config_error_done();
}
if (gc.cmdline_rcfile)
free((genericptr_t) gc.cmdline_rcfile), gc.cmdline_rcfile = 0;
/*[end of nethackrc handling]*/
}
void
rcfile_interface_options(void)
{
allopt_array_init();
disregard_all_options();
disregard_all_config_statements();
heed_this_option(opt_windowtype);
heed_this_option(opt_soundlib);
set_ignore_errors_on_unmatched();
ignore_statement_errors = TRUE;
rcfile();
heed_all_config_statements();
heed_all_options();
disregard_this_option(opt_windowtype);
disregard_this_option(opt_soundlib);
clear_ignore_errors_on_unmatched();
ignore_statement_errors = FALSE;
}
void
heed_all_config_statements(void)
{
int i;
for (i = 0; i < SIZE(disregarded_config_lines); i++) {
disregarded_config_lines[i] = FALSE;
}
}
void
disregard_all_config_statements(void)
{
int i;
for (i = 0; i < SIZE(disregarded_config_lines); i++) {
disregarded_config_lines[i] = TRUE;
}
}
void
heed_this_config_statement(int statement_idx)
{
if (statement_idx >= 0 && statement_idx < SIZE(disregarded_config_lines))
disregarded_config_lines[statement_idx] = FALSE;
}
void
disregard_this_config_statement(int statement_idx)
{
if (statement_idx >= 0 && statement_idx < SIZE(disregarded_config_lines))
disregarded_config_lines[statement_idx] = TRUE;
}
void
clear_ignore_errors_on_unmatched(void)
{
ignore_errors_on_unmatched = FALSE;
}
void
set_ignore_errors_on_unmatched(void)
{
ignore_errors_on_unmatched = TRUE;
}
boolean
config_unmatched_ignored(void)
{
if (ignore_errors_on_unmatched)
return TRUE;
return FALSE;
}
#endif /* SFCTOOL */
#ifdef SYSCF
#ifdef SYSCF_FILE
void
assure_syscf_file(void)
{
int fd;
#ifdef WIN32
/* We are checking that the sysconf exists ... lock the path */
fqn_prefix_locked[SYSCONFPREFIX] = TRUE;
#endif
/*
* All we really care about is the end result - can we read the file?
* So just check that directly.
*
* Not tested on most of the old platforms (which don't attempt
* to implement SYSCF).
* Some ports don't like open()'s optional third argument;
* VMS overrides open() usage with a macro which requires it.
*/
#ifndef VMS
#if defined(NOCWD_ASSUMPTIONS) && defined(WIN32)
fd = open(fqname(SYSCF_FILE, SYSCONFPREFIX, 0), O_RDONLY);
#else
fd = open(SYSCF_FILE, O_RDONLY);
#endif
#else /* VMS */
fd = open(SYSCF_FILE, O_RDONLY, 0);
#endif /* VMS */
if (fd >= 0) {
/* readable */
close(fd);
return;
}
#ifndef SFCTOOL
if (gd.deferred_showpaths)
do_deferred_showpaths(1); /* does not return */
#endif
raw_printf("Unable to open SYSCF_FILE.\n");
exit(EXIT_FAILURE);
}
#endif /* SYSCF_FILE */
#endif /* SYSCF */
/* ---------- END CONFIG FILE HANDLING ----------- */
/*cfgfiles.c*/