NHDT->ANH, in most cases
[aNetHack.git] / sys / unix / unixmain.c
blob53b20981da58e9a4698a2246203c923e7334f64a
1 /* NetHack 3.6 unixmain.c $ANH-Date: 1432512788 2015/05/25 00:13:08 $ $ANH-Branch: master $:$ANH-Revision: 1.52 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /* NetHack may be freely redistributed. See license for details. */
5 /* main.c - Unix NetHack */
7 #include "hack.h"
8 #include "dlb.h"
10 #include <ctype.h>
11 #include <sys/stat.h>
12 #include <signal.h>
13 #include <pwd.h>
14 #ifndef O_RDONLY
15 #include <fcntl.h>
16 #endif
18 #if !defined(_BULL_SOURCE) && !defined(__sgi) && !defined(_M_UNIX)
19 #if !defined(SUNOS4) && !(defined(ULTRIX) && defined(__GNUC__))
20 #if defined(POSIX_TYPES) || defined(SVR4) || defined(HPUX)
21 extern struct passwd *FDECL(getpwuid, (uid_t));
22 #else
23 extern struct passwd *FDECL(getpwuid, (int));
24 #endif
25 #endif
26 #endif
27 extern struct passwd *FDECL(getpwnam, (const char *));
28 #ifdef CHDIR
29 static void FDECL(chdirx, (const char *, BOOLEAN_P));
30 #endif /* CHDIR */
31 static boolean NDECL(whoami);
32 static void FDECL(process_options, (int, char **));
34 #ifdef _M_UNIX
35 extern void NDECL(check_sco_console);
36 extern void NDECL(init_sco_cons);
37 #endif
38 #ifdef __linux__
39 extern void NDECL(check_linux_console);
40 extern void NDECL(init_linux_cons);
41 #endif
43 static void NDECL(wd_message);
44 static boolean wiz_error_flag = FALSE;
45 static struct passwd *NDECL(get_unix_pw);
47 int
48 main(argc, argv)
49 int argc;
50 char *argv[];
52 register int fd;
53 #ifdef CHDIR
54 register char *dir;
55 #endif
56 boolean exact_username;
57 boolean resuming = FALSE; /* assume new game */
59 sys_early_init();
61 #if defined(__APPLE__)
63 /* special hack to change working directory to a resource fork when
64 running from finder --sam */
65 #define MAC_PATH_VALUE ".app/Contents/MacOS/"
66 char mac_cwd[1024], *mac_exe = argv[0], *mac_tmp;
67 int arg0_len = strlen(mac_exe), mac_tmp_len, mac_lhs_len = 0;
68 getcwd(mac_cwd, 1024);
69 if (mac_exe[0] == '/' && !strcmp(mac_cwd, "/")) {
70 if ((mac_exe = strrchr(mac_exe, '/')))
71 mac_exe++;
72 else
73 mac_exe = argv[0];
74 mac_tmp_len = (strlen(mac_exe) * 2) + strlen(MAC_PATH_VALUE);
75 if (mac_tmp_len <= arg0_len) {
76 mac_tmp = malloc(mac_tmp_len + 1);
77 sprintf(mac_tmp, "%s%s%s", mac_exe, MAC_PATH_VALUE, mac_exe);
78 if (!strcmp(argv[0] + (arg0_len - mac_tmp_len), mac_tmp)) {
79 mac_lhs_len =
80 (arg0_len - mac_tmp_len) + strlen(mac_exe) + 5;
81 if (mac_lhs_len > mac_tmp_len - 1)
82 mac_tmp = realloc(mac_tmp, mac_lhs_len);
83 strncpy(mac_tmp, argv[0], mac_lhs_len);
84 mac_tmp[mac_lhs_len] = '\0';
85 chdir(mac_tmp);
87 free(mac_tmp);
91 #endif
93 hname = argv[0];
94 hackpid = getpid();
95 (void) umask(0777 & ~FCMASK);
97 choose_windows(DEFAULT_WINDOW_SYS);
99 #ifdef CHDIR /* otherwise no chdir() */
101 * See if we must change directory to the playground.
102 * (Perhaps hack runs suid and playground is inaccessible
103 * for the player.)
104 * The environment variable HACKDIR is overridden by a
105 * -d command line option (must be the first option given)
107 dir = nh_getenv("NETHACKDIR");
108 if (!dir)
109 dir = nh_getenv("HACKDIR");
110 #endif
111 if (argc > 1) {
112 #ifdef CHDIR
113 if (!strncmp(argv[1], "-d", 2) && argv[1][2] != 'e') {
114 /* avoid matching "-dec" for DECgraphics; since the man page
115 * says -d directory, hope nobody's using -desomething_else
117 argc--;
118 argv++;
119 dir = argv[0] + 2;
120 if (*dir == '=' || *dir == ':')
121 dir++;
122 if (!*dir && argc > 1) {
123 argc--;
124 argv++;
125 dir = argv[0];
127 if (!*dir)
128 error("Flag -d must be followed by a directory name.");
130 if (argc > 1)
131 #endif /* CHDIR */
134 * Now we know the directory containing 'record' and
135 * may do a prscore(). Exclude `-style' - it's a Qt option.
137 if (!strncmp(argv[1], "-s", 2) && strncmp(argv[1], "-style", 6)) {
138 #ifdef CHDIR
139 chdirx(dir, 0);
140 #endif
141 #ifdef SYSCF
142 initoptions();
143 #endif
144 #ifdef PANICTRACE
145 ARGV0 = argv[0]; /* save for possible stack trace */
146 #ifndef NO_SIGNAL
147 panictrace_setsignals(TRUE);
148 #endif
149 #endif
150 prscore(argc, argv);
151 exit(EXIT_SUCCESS);
156 * Change directories before we initialize the window system so
157 * we can find the tile file.
159 #ifdef CHDIR
160 chdirx(dir, 1);
161 #endif
163 #ifdef _M_UNIX
164 check_sco_console();
165 #endif
166 #ifdef __linux__
167 check_linux_console();
168 #endif
169 initoptions();
170 #ifdef PANICTRACE
171 ARGV0 = argv[0]; /* save for possible stack trace */
172 #ifndef NO_SIGNAL
173 panictrace_setsignals(TRUE);
174 #endif
175 #endif
176 exact_username = whoami();
179 * It seems you really want to play.
181 u.uhp = 1; /* prevent RIP on early quits */
182 program_state.preserve_locks = 1;
183 #ifndef NO_SIGNAL
184 sethanguphandler((SIG_RET_TYPE) hangup);
185 #endif
187 process_options(argc, argv); /* command line options */
188 #ifdef WINCHAIN
189 commit_windowchain();
190 #endif
191 init_nhwindows(&argc, argv); /* now we can set up window system */
192 #ifdef _M_UNIX
193 init_sco_cons();
194 #endif
195 #ifdef __linux__
196 init_linux_cons();
197 #endif
199 #ifdef DEF_PAGER
200 if (!(catmore = nh_getenv("HACKPAGER"))
201 && !(catmore = nh_getenv("PAGER")))
202 catmore = DEF_PAGER;
203 #endif
204 #ifdef MAIL
205 getmailstatus();
206 #endif
208 /* wizard mode access is deferred until here */
209 set_playmode(); /* sets plname to "wizard" for wizard mode */
210 if (exact_username) {
212 * FIXME: this no longer works, ever since 3.3.0
213 * when plnamesuffix() was changed to find
214 * Name-Role-Race-Gender-Alignment. It removes
215 * all dashes rather than just the last one,
216 * regardless of whether whatever follows each
217 * dash matches role, race, gender, or alignment.
219 /* guard against user names with hyphens in them */
220 int len = strlen(plname);
221 /* append the current role, if any, so that last dash is ours */
222 if (++len < (int) sizeof plname)
223 (void) strncat(strcat(plname, "-"), pl_character,
224 sizeof plname - len - 1);
226 /* strip role,race,&c suffix; calls askname() if plname[] is empty
227 or holds a generic user name like "player" or "games" */
228 plnamesuffix();
230 if (wizard) {
231 /* use character name rather than lock letter for file names */
232 locknum = 0;
233 } else {
234 /* suppress interrupts while processing lock file */
235 (void) signal(SIGQUIT, SIG_IGN);
236 (void) signal(SIGINT, SIG_IGN);
239 * getlock() complains and quits if there is already a game
240 * in progress for current character name (when locknum == 0)
241 * or if there are too many active games (when locknum > 0).
242 * When proceeding, it creates an empty <lockname>.0 file to
243 * designate the current game.
244 * getlock() constructs <lockname> based on the character
245 * name (for !locknum) or on first available of alock, block,
246 * clock, &c not currently in use in the playground directory
247 * (for locknum > 0).
249 getlock();
250 program_state.preserve_locks = 0; /* after getlock() */
252 dlb_init(); /* must be before newgame() */
255 * Initialize the vision system. This must be before mklev() on a
256 * new game or before a level restore on a saved game.
258 vision_init();
260 display_gamewindows();
263 * First, try to find and restore a save file for specified character.
264 * We'll return here if new game player_selection() renames the hero.
266 attempt_restore:
267 if ((fd = restore_saved_game()) >= 0) {
268 const char *fq_save = fqname(SAVEF, SAVEPREFIX, 1);
270 (void) chmod(fq_save, 0); /* disallow parallel restores */
271 #ifndef NO_SIGNAL
272 (void) signal(SIGINT, (SIG_RET_TYPE) done1);
273 #endif
274 #ifdef NEWS
275 if (iflags.news) {
276 display_file(NEWS, FALSE);
277 iflags.news = FALSE; /* in case dorecover() fails */
279 #endif
280 pline("Restoring save file...");
281 mark_synch(); /* flush output */
282 if (dorecover(fd)) {
283 resuming = TRUE; /* not starting new game */
284 wd_message();
285 if (discover || wizard) {
286 if (yn("Do you want to keep the save file?") == 'n')
287 (void) delete_savefile();
288 else {
289 (void) chmod(fq_save, FCMASK); /* back to readable */
290 nh_compress(fq_save);
296 if (!resuming) {
297 /* new game: start by choosing role, race, etc;
298 player might change the hero's name while doing that,
299 in which case we try to restore under the new name
300 and skip selection this time if that didn't succeed */
301 if (!iflags.renameinprogress) {
302 player_selection();
303 if (iflags.renameinprogress) {
304 /* player has renamed the hero while selecting role;
305 if locking alphabetically, the existing lock file
306 can still be used; otherwise, discard current one
307 and create another for the new character name */
308 if (!locknum) {
309 delete_levelfile(0); /* remove empty lock file */
310 getlock();
312 goto attempt_restore;
315 newgame();
316 wd_message();
319 moveloop(resuming);
320 exit(EXIT_SUCCESS);
321 /*NOTREACHED*/
322 return (0);
325 static void
326 process_options(argc, argv)
327 int argc;
328 char *argv[];
330 int i, l;
333 * Process options.
335 while (argc > 1 && argv[1][0] == '-') {
336 argv++;
337 argc--;
338 l = (int) strlen(*argv);
339 /* must supply at least 4 chars to match "-XXXgraphics" */
340 if (l < 4)
341 l = 4;
343 switch (argv[0][1]) {
344 case 'D':
345 case 'd':
346 if ((argv[0][1] == 'D' && !argv[0][2])
347 || !strcmpi(*argv, "-debug")) {
348 wizard = TRUE, discover = FALSE;
349 } else if (!strncmpi(*argv, "-DECgraphics", l)) {
350 load_symset("DECGraphics", PRIMARY);
351 switch_symbols(TRUE);
352 } else {
353 raw_printf("Unknown option: %s", *argv);
355 break;
356 case 'X':
358 discover = TRUE, wizard = FALSE;
359 break;
360 #ifdef NEWS
361 case 'n':
362 iflags.news = FALSE;
363 break;
364 #endif
365 case 'u':
366 if (argv[0][2])
367 (void) strncpy(plname, argv[0] + 2, sizeof(plname) - 1);
368 else if (argc > 1) {
369 argc--;
370 argv++;
371 (void) strncpy(plname, argv[0], sizeof(plname) - 1);
372 } else
373 raw_print("Player name expected after -u");
374 break;
375 case 'I':
376 case 'i':
377 if (!strncmpi(*argv, "-IBMgraphics", l)) {
378 load_symset("IBMGraphics", PRIMARY);
379 load_symset("RogueIBM", ROGUESET);
380 switch_symbols(TRUE);
381 } else {
382 raw_printf("Unknown option: %s", *argv);
384 break;
385 case 'p': /* profession (role) */
386 if (argv[0][2]) {
387 if ((i = str2role(&argv[0][2])) >= 0)
388 flags.initrole = i;
389 } else if (argc > 1) {
390 argc--;
391 argv++;
392 if ((i = str2role(argv[0])) >= 0)
393 flags.initrole = i;
395 break;
396 case 'r': /* race */
397 if (argv[0][2]) {
398 if ((i = str2race(&argv[0][2])) >= 0)
399 flags.initrace = i;
400 } else if (argc > 1) {
401 argc--;
402 argv++;
403 if ((i = str2race(argv[0])) >= 0)
404 flags.initrace = i;
406 break;
407 case 'w': /* windowtype */
408 choose_windows(&argv[0][2]);
409 break;
410 case '@':
411 flags.randomall = 1;
412 break;
413 default:
414 if ((i = str2role(&argv[0][1])) >= 0) {
415 flags.initrole = i;
416 break;
418 /* else raw_printf("Unknown option: %s", *argv); */
422 #ifdef SYSCF
423 if (argc > 1)
424 raw_printf("MAXPLAYERS are set in sysconf file.\n");
425 #else
426 /* XXX This is deprecated in favor of SYSCF with MAXPLAYERS */
427 if (argc > 1)
428 locknum = atoi(argv[1]);
429 #endif
430 #ifdef MAX_NR_OF_PLAYERS
431 /* limit to compile-time limit */
432 if (!locknum || locknum > MAX_NR_OF_PLAYERS)
433 locknum = MAX_NR_OF_PLAYERS;
434 #endif
435 #ifdef SYSCF
436 /* let syscf override compile-time limit */
437 if (!locknum || (sysopt.maxplayers && locknum > sysopt.maxplayers))
438 locknum = sysopt.maxplayers;
439 #endif
442 #ifdef CHDIR
443 static void
444 chdirx(dir, wr)
445 const char *dir;
446 boolean wr;
448 if (dir /* User specified directory? */
449 #ifdef HACKDIR
450 && strcmp(dir, HACKDIR) /* and not the default? */
451 #endif
453 #ifdef SECURE
454 (void) setgid(getgid());
455 (void) setuid(getuid()); /* Ron Wessels */
456 #endif
457 } else {
458 /* non-default data files is a sign that scores may not be
459 * compatible, or perhaps that a binary not fitting this
460 * system's layout is being used.
462 #ifdef VAR_PLAYGROUND
463 int len = strlen(VAR_PLAYGROUND);
465 fqn_prefix[SCOREPREFIX] = (char *) alloc(len + 2);
466 Strcpy(fqn_prefix[SCOREPREFIX], VAR_PLAYGROUND);
467 if (fqn_prefix[SCOREPREFIX][len - 1] != '/') {
468 fqn_prefix[SCOREPREFIX][len] = '/';
469 fqn_prefix[SCOREPREFIX][len + 1] = '\0';
471 #endif
474 #ifdef HACKDIR
475 if (dir == (const char *) 0)
476 dir = HACKDIR;
477 #endif
479 if (dir && chdir(dir) < 0) {
480 perror(dir);
481 error("Cannot chdir to %s.", dir);
484 /* warn the player if we can't write the record file */
485 /* perhaps we should also test whether . is writable */
486 /* unfortunately the access system-call is worthless */
487 if (wr) {
488 #ifdef VAR_PLAYGROUND
489 fqn_prefix[LEVELPREFIX] = fqn_prefix[SCOREPREFIX];
490 fqn_prefix[SAVEPREFIX] = fqn_prefix[SCOREPREFIX];
491 fqn_prefix[BONESPREFIX] = fqn_prefix[SCOREPREFIX];
492 fqn_prefix[LOCKPREFIX] = fqn_prefix[SCOREPREFIX];
493 fqn_prefix[TROUBLEPREFIX] = fqn_prefix[SCOREPREFIX];
494 #endif
495 check_recordfile(dir);
498 #endif /* CHDIR */
500 /* returns True iff we set plname[] to username which contains a hyphen */
501 static boolean
502 whoami()
505 * Who am i? Algorithm: 1. Use name as specified in NETHACKOPTIONS
506 * 2. Use $USER or $LOGNAME (if 1. fails)
507 * 3. Use getlogin() (if 2. fails)
508 * The resulting name is overridden by command line options.
509 * If everything fails, or if the resulting name is some generic
510 * account like "games", "play", "player", "hack" then eventually
511 * we'll ask him.
512 * Note that we trust the user here; it is possible to play under
513 * somebody else's name.
515 if (!*plname) {
516 register const char *s;
518 s = nh_getenv("USER");
519 if (!s || !*s)
520 s = nh_getenv("LOGNAME");
521 if (!s || !*s)
522 s = getlogin();
524 if (s && *s) {
525 (void) strncpy(plname, s, sizeof plname - 1);
526 if (index(plname, '-'))
527 return TRUE;
530 return FALSE;
533 void
534 sethanguphandler(handler)
535 void FDECL((*handler), (int));
537 #ifdef SA_RESTART
538 /* don't want reads to restart. If SA_RESTART is defined, we know
539 * sigaction exists and can be used to ensure reads won't restart.
540 * If it's not defined, assume reads do not restart. If reads restart
541 * and a signal occurs, the game won't do anything until the read
542 * succeeds (or the stream returns EOF, which might not happen if
543 * reading from, say, a window manager). */
544 struct sigaction sact;
546 (void) memset((genericptr_t) &sact, 0, sizeof sact);
547 sact.sa_handler = (SIG_RET_TYPE) handler;
548 (void) sigaction(SIGHUP, &sact, (struct sigaction *) 0);
549 #ifdef SIGXCPU
550 (void) sigaction(SIGXCPU, &sact, (struct sigaction *) 0);
551 #endif
552 #else /* !SA_RESTART */
553 (void) signal(SIGHUP, (SIG_RET_TYPE) handler);
554 #ifdef SIGXCPU
555 (void) signal(SIGXCPU, (SIG_RET_TYPE) handler);
556 #endif
557 #endif /* ?SA_RESTART */
560 #ifdef PORT_HELP
561 void
562 port_help()
565 * Display unix-specific help. Just show contents of the helpfile
566 * named by PORT_HELP.
568 display_file(PORT_HELP, TRUE);
570 #endif
572 /* validate wizard mode if player has requested access to it */
573 boolean
574 authorize_wizard_mode()
576 struct passwd *pw = get_unix_pw();
577 if (pw && sysopt.wizards && sysopt.wizards[0]) {
578 if (check_user_string(sysopt.wizards))
579 return TRUE;
581 wiz_error_flag = TRUE; /* not being allowed into wizard mode */
582 return FALSE;
585 static void
586 wd_message()
588 if (wiz_error_flag) {
589 if (sysopt.wizards && sysopt.wizards[0]) {
590 char *tmp = build_english_list(sysopt.wizards);
591 pline("Only user%s %s may access debug (wizard) mode.",
592 index(sysopt.wizards, ' ') ? "s" : "", tmp);
593 free(tmp);
594 } else
595 pline("Entering explore/discovery mode instead.");
596 wizard = 0, discover = 1; /* (paranoia) */
597 } else if (discover)
598 You("are in non-scoring explore/discovery mode.");
602 * Add a slash to any name not ending in /. There must
603 * be room for the /
605 void
606 append_slash(name)
607 char *name;
609 char *ptr;
611 if (!*name)
612 return;
613 ptr = name + (strlen(name) - 1);
614 if (*ptr != '/') {
615 *++ptr = '/';
616 *++ptr = '\0';
618 return;
621 boolean
622 check_user_string(optstr)
623 char *optstr;
625 struct passwd *pw = get_unix_pw();
626 int pwlen;
627 char *eop, *w;
628 char *pwname;
629 if (optstr[0] == '*')
630 return TRUE; /* allow any user */
631 if (!pw)
632 return FALSE;
633 if (sysopt.check_plname)
634 pwname = plname;
635 else
636 pwname = pw->pw_name;
637 pwlen = strlen(pwname);
638 eop = eos(optstr);
639 w = optstr;
640 while (w + pwlen <= eop) {
641 if (!*w)
642 break;
643 if (isspace(*w)) {
644 w++;
645 continue;
647 if (!strncmp(w, pwname, pwlen)) {
648 if (!w[pwlen] || isspace(w[pwlen]))
649 return TRUE;
651 while (*w && !isspace(*w))
652 w++;
654 return FALSE;
657 static struct passwd *
658 get_unix_pw()
660 char *user;
661 unsigned uid;
662 static struct passwd *pw = (struct passwd *) 0;
664 if (pw)
665 return pw; /* cache answer */
667 uid = (unsigned) getuid();
668 user = getlogin();
669 if (user) {
670 pw = getpwnam(user);
671 if (pw && ((unsigned) pw->pw_uid != uid))
672 pw = 0;
674 if (pw == 0) {
675 user = nh_getenv("USER");
676 if (user) {
677 pw = getpwnam(user);
678 if (pw && ((unsigned) pw->pw_uid != uid))
679 pw = 0;
681 if (pw == 0) {
682 pw = getpwuid(uid);
685 return pw;
688 /*unixmain.c*/