OSDN Git Service

shrink mine
[nethackexpress/trunk.git] / sys / vms / vmsunix.c
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
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
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
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
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
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
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
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
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 *
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
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
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
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
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
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
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
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*/