1 /* SCCS Id: @(#)vmsunix.c 3.4 2001/07/27 */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /* NetHack may be freely redistributed. See license for details. */
5 /* This file implements things from unixunix.c, plus related stuff */
19 # define umask hide_umask_dummy /* DEC C: avoid conflict with system.h */
25 extern unsigned long sys$setprv();
26 extern unsigned long lib$getdvi(), lib$getjpi(), lib$spawn(), lib$attach();
27 extern unsigned long smg$init_term_table_by_type(), smg$del_term_table();
28 #define vms_ok(sts) ((sts) & 1) /* odd => success */
30 static int FDECL(veryold, (int));
31 static char *NDECL(verify_term);
32 #if defined(SHELL) || defined(SUSPEND)
33 static void FDECL(hack_escape, (BOOLEAN_P,const char *));
34 static void FDECL(hack_resume, (BOOLEAN_P));
45 if(fstat(fd, &buf)) return(0); /* cannot get status */
47 if(buf.st_size != sizeof(int)) return(0); /* not an xlock file */
50 if(date - buf.st_mtime < 3L*24L*60L*60L) { /* recent */
51 int lockedpid; /* should be the same size as hackpid */
52 unsigned long status, dummy, code = JPI$_PID;
54 if (read(fd, (genericptr_t)&lockedpid, sizeof(lockedpid)) !=
55 sizeof(lockedpid)) /* strange ... */
57 status = lib$getjpi(&code, &lockedpid, 0, &dummy);
58 if (vms_ok(status) || status != SS$_NONEXPR)
63 /* cannot use maxledgerno() here, because we need to find a lock name
64 * before starting everything (including the dungeon initialization
65 * that sets astral_level, needed for maxledgerno()) up
67 for(i = 1; i <= MAXDUNGEON*MAXLEVEL + 1; i++) {
68 /* try to remove all */
69 set_levelfile_name(lock, i);
72 set_levelfile_name(lock, 0);
73 if(delete(lock)) return(0); /* cannot remove it */
74 return(1); /* success! */
80 register int i = 0, fd;
82 /* idea from rpick%ucqais@uccba.uc.edu
83 * prevent automated rerolling of characters
84 * test input (fd0) so that tee'ing output to get a screen dump still
86 * also incidentally prevents development of any hack-o-matic programs
89 error("You must play from a terminal.");
91 /* we ignore QUIT and INT at this point */
92 if (!lock_file(HLOCK, LOCKPREFIX, 10)) {
98 set_levelfile_name(lock, 0);
99 if(locknum > 25) locknum = 25;
102 if(locknum) lock[0] = 'a' + i++;
104 if((fd = open(lock, 0, 0)) == -1) {
105 if(errno == ENOENT) goto gotlock; /* no such file */
108 error("Cannot open %s", lock);
111 if(veryold(fd)) /* if true, this closes fd and unlinks lock */
114 } while(i < locknum);
117 error(locknum ? "Too many hacks running now."
118 : "There is a game in progress under your name.");
121 fd = creat(lock, FCMASK);
124 error("cannot creat lock file.");
126 if(write(fd, (char *) &hackpid, sizeof(hackpid))
128 error("cannot write lock");
130 if(close(fd) == -1) {
131 error("cannot close lock");
137 regularize(s) /* normalize file name */
142 for (lp = s; *lp; lp++) /* note: '-' becomes '_' */
143 if (!(isalpha(*lp) || isdigit(*lp) || *lp == '$'))
151 return (getgid() << 16) | getuid();
155 #define FAB$C_STMLF 5
157 /* check whether the open file specified by `fd' is in stream-lf format */
165 if (fstat(fd, &buf)) return FALSE; /* cannot get status? */
167 #ifdef stat_alignment_fix /* gcc-vms alignment kludge */
168 rfm = stat_alignment_fix(&buf)->st_fab_rfm;
170 rfm = buf.st_fab_rfm;
172 return rfm == FAB$C_STMLF;
177 #include <lnmdef.h> /* logical name definitions */
179 #define ENVSIZ LNM$C_NAMLENGTH /*255*/
181 #define ENV_USR 0 /* user-mode */
182 #define ENV_SUP 1 /* supervisor-mode */
183 #define ENV_JOB 2 /* job-wide entry */
185 /* vms_define() - assign a value to a logical name */
187 vms_define(name, value, flag)
192 struct dsc { unsigned short len, mbz; const char *adr; }; /* descriptor */
193 struct itm3 { short buflen, itmcode; const char *bufadr; short *retlen; };
194 static struct itm3 itm_lst[] = { {0,LNM$_STRING,0,0}, {0,0} };
195 struct dsc nam_dsc, val_dsc, tbl_dsc;
196 unsigned long result, sys$crelnm(), lib$set_logical();
198 /* set up string descriptors */
199 nam_dsc.mbz = val_dsc.mbz = tbl_dsc.mbz = 0;
200 nam_dsc.len = strlen( nam_dsc.adr = name );
201 val_dsc.len = strlen( val_dsc.adr = value );
202 tbl_dsc.len = strlen( tbl_dsc.adr = "LNM$PROCESS" );
205 case ENV_JOB: /* job logical name */
206 tbl_dsc.len = strlen( tbl_dsc.adr = "LNM$JOB" );
208 case ENV_SUP: /* supervisor-mode process logical name */
209 result = lib$set_logical(&nam_dsc, &val_dsc, &tbl_dsc);
211 case ENV_USR: /* user-mode process logical name */
212 itm_lst[0].buflen = val_dsc.len;
213 itm_lst[0].bufadr = val_dsc.adr;
214 result = sys$crelnm(0, &tbl_dsc, &nam_dsc, 0, itm_lst);
216 default: /*[ bad input ]*/
220 result &= 1; /* odd => success (== 1), even => failure (== 0) */
221 return !result; /* 0 == success, 1 == failure */
224 /* vms_putenv() - create or modify an environment value */
229 char name[ENVSIZ+1], value[ENVSIZ+1], *p; /* [255+1] */
231 p = strchr(string, '=');
232 if (p > string && p < string + sizeof name && strlen(p+1) < sizeof value) {
233 (void)strncpy(name, string, p - string), name[p - string] = '\0';
234 (void)strcpy(value, p+1);
235 return vms_define(name, value, ENV_USR);
237 return 1; /* failure */
241 Support for VT420 was added to VMS in version V5.4, but as of V5.5-2
242 VAXCRTL still doesn't handle it and puts TERM=undefined into the
243 environ[] array. getenv("TERM") will return "undefined" instead of
244 something sensible. Even though that's finally fixed in V6.0, site
245 defined terminals also return "undefined" so query SMG's TERMTABLE
246 instead of just checking VMS's device-type value for VT400_Series.
248 Called by verify_termcap() for convenience.
253 char *term = getenv("NETHACK_TERM");
254 if (!term) term = getenv("HACK_TERM");
255 if (!term) term = getenv("EMACS_TERM");
256 if (!term) term = getenv("TERM");
258 || !strcmpi(term, "undefined") || !strcmpi(term, "unknown")) {
259 static char smgdevtyp[31+1]; /* size is somewhat arbitrary */
260 static char dev_tty[] = "TT:";
261 static $DESCRIPTOR(smgdsc, smgdevtyp);
262 static $DESCRIPTOR(tt, dev_tty);
263 unsigned short dvicode = DVI$_DEVTYPE;
264 unsigned long devtype = 0L, termtab = 0L;
266 (void)lib$getdvi(&dvicode, (unsigned short *)0, &tt, &devtype,
267 (genericptr_t)0, (unsigned short *)0);
270 vms_ok(smg$init_term_table_by_type(&devtype, &termtab, &smgdsc))) {
271 register char *p = &smgdevtyp[smgdsc.dsc$w_length];
272 /* strip trailing blanks */
273 while (p > smgdevtyp && *--p == ' ') *p = '\0';
274 /* (void)smg$del_term_table(); */
282 Figure out whether the termcap code will find a termcap file; if not,
283 try to help it out. This avoids modifying the GNU termcap sources and
284 can simplify configuration for sites which don't already use termcap.
286 #define GNU_DEFAULT_TERMCAP "emacs_library:[etc]termcap.dat"
287 #define NETHACK_DEF_TERMCAP "nethackdir:termcap"
288 #define HACK_DEF_TERMCAP "hackdir:termcap"
291 verify_termcap() /* called from startup(src/termcap.c) */
294 const char *tc = getenv("TERMCAP");
295 if (tc) return verify_term(); /* no termcap fixups needed */
296 if (!tc && !stat(NETHACK_DEF_TERMCAP, &dummy)) tc = NETHACK_DEF_TERMCAP;
297 if (!tc && !stat(HACK_DEF_TERMCAP, &dummy)) tc = HACK_DEF_TERMCAP;
298 if (!tc && !stat(GNU_DEFAULT_TERMCAP, &dummy)) tc = GNU_DEFAULT_TERMCAP;
299 if (!tc && !stat("[]termcap", &dummy)) tc = "[]termcap"; /* current dir */
300 if (!tc && !stat("$TERMCAP", &dummy)) tc = "$TERMCAP"; /* alt environ */
302 /* putenv(strcat(strcpy(buffer,"TERMCAP="),tc)); */
303 vms_define("TERMCAP", tc, ENV_USR);
305 /* perhaps someday we'll construct a termcap entry string */
307 return verify_term();
312 # ifndef CLI$M_NOWAIT
313 # define CLI$M_NOWAIT 1
317 #if defined(CHDIR) || defined(SHELL) || defined(SECURE)
318 static unsigned long oprv[2];
323 unsigned long pid = 0, prv[2] = { ~0, ~0 };
324 unsigned short code = JPI$_PROCPRIV;
326 (void) sys$setprv(0, prv, 0, oprv);
327 (void) lib$getjpi(&code, &pid, (genericptr_t)0, prv);
328 (void) sys$setprv(1, prv, 0, (unsigned long *)0);
334 (void) sys$setprv(1, oprv, 0, (unsigned long *)0);
336 #endif /* CHDIR || SHELL || SECURE */
338 #if defined(SHELL) || defined(SUSPEND)
340 hack_escape(screen_manip, msg_str)
341 boolean screen_manip;
345 suspend_nhwindows(msg_str); /* clear screen, reset terminal, &c */
346 (void) signal(SIGQUIT,SIG_IGN); /* ignore ^Y */
347 (void) signal(SIGINT,SIG_DFL); /* don't trap ^C (implct cnvrs to ^Y) */
351 hack_resume(screen_manip)
352 boolean screen_manip;
354 (void) signal(SIGINT, (SIG_RET_TYPE) done1);
356 if (wizard) (void) signal(SIGQUIT,SIG_DFL);
359 resume_nhwindows(); /* setup terminal modes, redraw screen, &c */
361 #endif /* SHELL || SUSPEND */
364 unsigned long dosh_pid = 0, /* this should cover any interactive escape */
365 mail_pid = 0; /* this only covers the last mail or phone; */
366 /*(mail & phone commands aren't expected to leave any process hanging around)*/
370 return vms_doshell("", TRUE); /* call for interactive child process */
373 /* vms_doshell -- called by dosh() and readmail() */
375 /* If execstring is not a null string, then it will be executed in a spawned */
376 /* subprocess, which will then return. It is for handling mail or phone */
377 /* interactive commands, which are only available if both MAIL and SHELL are */
378 /* #defined, but we don't bother making the support code conditionalized on */
379 /* MAIL here, just on SHELL being enabled. */
381 /* Normally, all output from this interaction will be 'piped' to the user's */
382 /* screen (SYS$OUTPUT). However, if 'screenoutput' is set to FALSE, output */
383 /* will be piped into oblivion. Used for silent phone call rejection. */
386 vms_doshell(execstring, screenoutput)
387 const char *execstring;
388 boolean screenoutput;
390 unsigned long status, new_pid, spawnflags = 0;
391 struct dsc$descriptor_s comstring, *command, *inoutfile = 0;
392 static char dev_null[] = "_NLA0:";
393 static $DESCRIPTOR(nulldevice, dev_null);
395 /* Is this an interactive shell spawn, or do we have a command to do? */
396 if (execstring && *execstring) {
397 comstring.dsc$w_length = strlen(execstring);
398 comstring.dsc$b_dtype = DSC$K_DTYPE_T;
399 comstring.dsc$b_class = DSC$K_CLASS_S;
400 comstring.dsc$a_pointer = (char *)execstring;
401 command = &comstring;
405 /* use asynch subprocess and suppress output iff one-shot command */
407 spawnflags = CLI$M_NOWAIT;
408 inoutfile = &nulldevice;
411 hack_escape(screenoutput, command ? (const char *) 0 :
412 " \"Escaping\" into a subprocess; LOGOUT to reconnect and resume play. ");
414 if (command || !dosh_pid || !vms_ok(status = lib$attach(&dosh_pid))) {
416 (void) chdir(getenv("PATH"));
420 status = lib$spawn(command, inoutfile, inoutfile, &spawnflags,
421 (struct dsc$descriptor_s *) 0, &new_pid);
422 if (!command) dosh_pid = new_pid; else mail_pid = new_pid;
425 chdirx((char *) 0, 0);
429 hack_resume(screenoutput);
431 if (!vms_ok(status)) {
432 pline(" Spawn failed. (%%x%08lX) ", status);
440 /* dosuspend() -- if we're a subprocess, attach to our parent;
441 * if not, there's nothing we can do.
446 static long owner_pid = -1;
447 unsigned long status;
449 if (owner_pid == -1) /* need to check for parent */
450 owner_pid = getppid();
451 if (owner_pid == 0) {
453 " No parent process. Use '!' to Spawn, 'S' to Save, or 'Q' to Quit. ");
458 /* restore normal tty environment & clear screen */
460 " Attaching to parent process; use the ATTACH command to resume play. ");
462 status = lib$attach(&owner_pid); /* connect to parent */
464 hack_resume(1); /* resume game tty environment & refresh screen */
466 if (!vms_ok(status)) {
467 pline(" Unable to attach to parent. (%%x%08lX) ", status);