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. */
4 
5 /* This file implements things from unixunix.c, plus related stuff */
6 
7 #include "hack.h"
8 
9 #include <descrip.h>
10 #include <dvidef.h>
11 #include <jpidef.h>
12 #include <ssdef.h>
13 #include <errno.h>
14 #include <signal.h>
15 #undef off_t
16 #ifdef GNUC
17 #include <sys/stat.h>
18 #else
19 # define umask hide_umask_dummy /* DEC C: avoid conflict with system.h */
20 #include <stat.h>
21 # undef  umask
22 #endif
23 #include <ctype.h>
24 
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 */
29 
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));
35 #endif
36 
37 static int
veryold(fd)38 veryold(fd)
39 int fd;
40 {
41 	register int i;
42 	time_t date;
43 	struct stat buf;
44 
45 	if(fstat(fd, &buf)) return(0);			/* cannot get status */
46 #ifndef INSURANCE
47 	if(buf.st_size != sizeof(int)) return(0);	/* not an xlock file */
48 #endif
49 	(void) time(&date);
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;
53 
54 		if (read(fd, (genericptr_t)&lockedpid, sizeof(lockedpid)) !=
55 				sizeof(lockedpid))	/* strange ... */
56 			return 0;
57 		status = lib$getjpi(&code, &lockedpid, 0, &dummy);
58 		if (vms_ok(status) || status != SS$_NONEXPR)
59 			return 0;
60 	}
61 	(void) close(fd);
62 
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
66 	 */
67 	for(i = 1; i <= MAXDUNGEON*MAXLEVEL + 1; i++) {
68 		/* try to remove all */
69 		set_levelfile_name(lock, i);
70 		(void) delete(lock);
71 	}
72 	set_levelfile_name(lock, 0);
73 	if(delete(lock)) return(0);			/* cannot remove it */
74 	return(1);					/* success! */
75 }
76 
77 void
getlock()78 getlock()
79 {
80 	register int i = 0, fd;
81 
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
85 	 * works
86 	 * also incidentally prevents development of any hack-o-matic programs
87 	 */
88 	if (isatty(0) <= 0)
89 		error("You must play from a terminal.");
90 
91 	/* we ignore QUIT and INT at this point */
92 	if (!lock_file(HLOCK, LOCKPREFIX, 10)) {
93 		wait_synch();
94 		error("Quitting.");
95 	}
96 
97 	regularize(lock);
98 	set_levelfile_name(lock, 0);
99 	if(locknum > 25) locknum = 25;
100 
101 	do {
102 		if(locknum) lock[0] = 'a' + i++;
103 
104 		if((fd = open(lock, 0, 0)) == -1) {
105 			if(errno == ENOENT) goto gotlock;    /* no such file */
106 			perror(lock);
107 			unlock_file(HLOCK);
108 			error("Cannot open %s", lock);
109 		}
110 
111 		if(veryold(fd))	/* if true, this closes fd and unlinks lock */
112 			goto gotlock;
113 		(void) close(fd);
114 	} while(i < locknum);
115 
116 	unlock_file(HLOCK);
117 	error(locknum ? "Too many hacks running now."
118 		      : "There is a game in progress under your name.");
119 
120 gotlock:
121 	fd = creat(lock, FCMASK);
122 	unlock_file(HLOCK);
123 	if(fd == -1) {
124 		error("cannot creat lock file.");
125 	} else {
126 		if(write(fd, (char *) &hackpid, sizeof(hackpid))
127 		    != sizeof(hackpid)){
128 			error("cannot write lock");
129 		}
130 		if(close(fd) == -1) {
131 			error("cannot close lock");
132 		}
133 	}
134 }
135 
136 void
regularize(s)137 regularize(s)	/* normalize file name */
138 register char *s;
139 {
140 	register char *lp;
141 
142 	for (lp = s; *lp; lp++)         /* note: '-' becomes '_' */
143 	    if (!(isalpha(*lp) || isdigit(*lp) || *lp == '$'))
144 			*lp = '_';
145 }
146 
147 #undef getuid
148 int
vms_getuid()149 vms_getuid()
150 {
151     return (getgid() << 16) | getuid();
152 }
153 
154 #ifndef FAB$C_STMLF
155 #define FAB$C_STMLF 5
156 #endif
157 /* check whether the open file specified by `fd' is in stream-lf format */
158 boolean
file_is_stmlf(fd)159 file_is_stmlf(fd)
160 int fd;
161 {
162     int rfm;
163     struct stat buf;
164 
165     if (fstat(fd, &buf)) return FALSE;	/* cannot get status? */
166 
167 #ifdef stat_alignment_fix	/* gcc-vms alignment kludge */
168     rfm = stat_alignment_fix(&buf)->st_fab_rfm;
169 #else
170     rfm = buf.st_fab_rfm;
171 #endif
172     return rfm == FAB$C_STMLF;
173 }
174 
175 /*------*/
176 #ifndef LNM$_STRING
177 #include <lnmdef.h>	/* logical name definitions */
178 #endif
179 #define ENVSIZ LNM$C_NAMLENGTH  /*255*/
180 
181 #define ENV_USR 0	/* user-mode */
182 #define ENV_SUP 1	/* supervisor-mode */
183 #define ENV_JOB 2	/* job-wide entry */
184 
185 /* vms_define() - assign a value to a logical name */
186 int
vms_define(name,value,flag)187 vms_define(name, value, flag)
188 const char *name;
189 const char *value;
190 int flag;
191 {
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();
197 
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" );
203 
204     switch (flag) {
205 	case ENV_JOB:	/* job logical name */
206 		tbl_dsc.len = strlen( tbl_dsc.adr = "LNM$JOB" );
207 	    /*FALLTHRU*/
208 	case ENV_SUP:	/* supervisor-mode process logical name */
209 		result = lib$set_logical(&nam_dsc, &val_dsc, &tbl_dsc);
210 	    break;
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);
215 	    break;
216 	default:	/*[ bad input ]*/
217 		result = 0;
218 	    break;
219     }
220     result &= 1;	/* odd => success (== 1), even => failure (== 0) */
221     return !result;	/* 0 == success, 1 == failure */
222 }
223 
224 /* vms_putenv() - create or modify an environment value */
225 int
vms_putenv(string)226 vms_putenv(string)
227 const char *string;
228 {
229     char name[ENVSIZ+1], value[ENVSIZ+1], *p;   /* [255+1] */
230 
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);
236     } else
237 	return 1;	/* failure */
238 }
239 
240 /*
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.
247 
248    Called by verify_termcap() for convenience.
249  */
250 static
verify_term()251 char *verify_term()
252 {
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");
257     if (!term || !*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;
265 
266 	(void)lib$getdvi(&dvicode, (unsigned short *)0, &tt, &devtype,
267 			 (genericptr_t)0, (unsigned short *)0);
268 
269 	if (devtype &&
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(); */
275 	    term = smgdevtyp;
276 	}
277     }
278     return term;
279 }
280 
281 /*
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.
285  */
286 #define GNU_DEFAULT_TERMCAP "emacs_library:[etc]termcap.dat"
287 #define NETHACK_DEF_TERMCAP "nethackdir:termcap"
288 #define HACK_DEF_TERMCAP    "hackdir:termcap"
289 
290 char *
verify_termcap()291 verify_termcap()	/* called from startup(src/termcap.c) */
292 {
293     struct stat dummy;
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 */
301     if (tc) {
302 	/* putenv(strcat(strcpy(buffer,"TERMCAP="),tc)); */
303 	vms_define("TERMCAP", tc, ENV_USR);
304     } else {
305 	/* perhaps someday we'll construct a termcap entry string */
306     }
307     return verify_term();
308 }
309 /*------*/
310 
311 #ifdef SHELL
312 # ifndef CLI$M_NOWAIT
313 #  define CLI$M_NOWAIT 1
314 # endif
315 #endif
316 
317 #if defined(CHDIR) || defined(SHELL) || defined(SECURE)
318 static unsigned long oprv[2];
319 
320 void
privoff()321 privoff()
322 {
323 	unsigned long pid = 0, prv[2] = { ~0, ~0 };
324 	unsigned short code = JPI$_PROCPRIV;
325 
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);
329 }
330 
331 void
privon()332 privon()
333 {
334 	(void) sys$setprv(1, oprv, 0, (unsigned long *)0);
335 }
336 #endif	/* CHDIR || SHELL || SECURE */
337 
338 #if defined(SHELL) || defined(SUSPEND)
339 static void
hack_escape(screen_manip,msg_str)340 hack_escape(screen_manip, msg_str)
341 boolean screen_manip;
342 const char *msg_str;
343 {
344 	if (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) */
348 }
349 
350 static void
hack_resume(screen_manip)351 hack_resume(screen_manip)
352 boolean screen_manip;
353 {
354 	(void) signal(SIGINT, (SIG_RET_TYPE) done1);
355 # ifdef WIZARD
356 	if (wizard) (void) signal(SIGQUIT,SIG_DFL);
357 # endif
358 	if (screen_manip)
359 	    resume_nhwindows();	/* setup terminal modes, redraw screen, &c */
360 }
361 #endif	/* SHELL || SUSPEND */
362 
363 #ifdef SHELL
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)*/
367 
dosh()368 int dosh()
369 {
370 	return vms_doshell("", TRUE);	/* call for interactive child process */
371 }
372 
373 /* vms_doshell -- called by dosh() and readmail() */
374 
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.				     */
380 
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.	     */
384 
385 int
vms_doshell(execstring,screenoutput)386 vms_doshell(execstring, screenoutput)
387 const char *execstring;
388 boolean screenoutput;
389 {
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);
394 
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;
402 	} else
403 		command = 0;
404 
405 	/* use asynch subprocess and suppress output iff one-shot command */
406 	if (!screenoutput) {
407 		spawnflags = CLI$M_NOWAIT;
408 		inoutfile = &nulldevice;
409 	}
410 
411 	hack_escape(screenoutput, command ? (const char *) 0 :
412      "  \"Escaping\" into a subprocess; LOGOUT to reconnect and resume play. ");
413 
414 	if (command || !dosh_pid || !vms_ok(status = lib$attach(&dosh_pid))) {
415 # ifdef CHDIR
416 		(void) chdir(getenv("PATH"));
417 # endif
418 		privoff();
419 		new_pid = 0;
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;
423 		privon();
424 # ifdef CHDIR
425 		chdirx((char *) 0, 0);
426 # endif
427 	}
428 
429 	hack_resume(screenoutput);
430 
431 	if (!vms_ok(status)) {
432 		pline("  Spawn failed.  (%%x%08lX) ", status);
433 		mark_synch();
434 	}
435 	return 0;
436 }
437 #endif	/* SHELL */
438 
439 #ifdef SUSPEND
440 /* dosuspend() -- if we're a subprocess, attach to our parent;
441  *		if not, there's nothing we can do.
442  */
443 int
dosuspend()444 dosuspend()
445 {
446 	static long owner_pid = -1;
447 	unsigned long status;
448 
449 	if (owner_pid == -1)	/* need to check for parent */
450 		owner_pid = getppid();
451 	if (owner_pid == 0) {
452 		pline(
453      "  No parent process.  Use '!' to Spawn, 'S' to Save,  or 'Q' to Quit. ");
454 		mark_synch();
455 		return 0;
456 	}
457 
458 	/* restore normal tty environment & clear screen */
459 	hack_escape(1,
460      " Attaching to parent process; use the ATTACH command to resume play. ");
461 
462 	status = lib$attach(&owner_pid);	/* connect to parent */
463 
464 	hack_resume(1);	/* resume game tty environment & refresh screen */
465 
466 	if (!vms_ok(status)) {
467 		pline("  Unable to attach to parent.  (%%x%08lX) ", status);
468 		mark_synch();
469 	}
470 	return 0;
471 }
472 #endif	/* SUSPEND */
473 
474 /*vmsunix.c*/
475