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