3 * VMS-specific routines for perl5
5 * Last revised: 20-Mar-1996 by Charles Bailey bailey@genetics.upenn.edu
14 #include <climsgdef.h>
23 #include <lib$routines.h>
40 /* gcc's header files don't #define direct access macros
41 * corresponding to VAXC's variant structs */
43 # define uic$v_format uic$r_uic_form.uic$v_format
44 # define uic$v_group uic$r_uic_form.uic$v_group
45 # define uic$v_member uic$r_uic_form.uic$v_member
46 # define prv$v_bypass prv$r_prvdef_bits0.prv$v_bypass
47 # define prv$v_grpprv prv$r_prvdef_bits0.prv$v_grpprv
48 # define prv$v_readall prv$r_prvdef_bits0.prv$v_readall
49 # define prv$v_sysprv prv$r_prvdef_bits0.prv$v_sysprv
54 unsigned short int buflen;
55 unsigned short int itmcode;
57 unsigned short int *retlen;
61 my_trnlnm(char *lnm, char *eqv, unsigned long int idx)
63 static char __my_trnlnm_eqv[LNM$C_NAMLENGTH+1];
64 unsigned short int eqvlen;
65 unsigned long int retsts, attr = LNM$M_CASE_BLIND;
66 $DESCRIPTOR(tabdsc,"LNM$FILE_DEV");
67 struct dsc$descriptor_s lnmdsc = {0,DSC$K_DTYPE_T,DSC$K_CLASS_S,0};
68 struct itmlst_3 lnmlst[3] = {{sizeof idx, LNM$_INDEX, &idx, 0},
69 {LNM$C_NAMLENGTH, LNM$_STRING, 0, &eqvlen},
72 if (!eqv) eqv = __my_trnlnm_eqv;
73 lnmlst[1].bufadr = (void *)eqv;
74 lnmdsc.dsc$a_pointer = lnm;
75 lnmdsc.dsc$w_length = strlen(lnm);
76 retsts = sys$trnlnm(&attr,&tabdsc,&lnmdsc,0,lnmlst);
77 if (retsts == SS$_NOLOGNAM || retsts == SS$_IVLOGNAM) {
78 set_vaxc_errno(retsts); set_errno(EINVAL); return 0;
80 else if (retsts & 1) {
84 _ckvmssts(retsts); /* Must be an error */
85 return 0; /* Not reached, assuming _ckvmssts() bails out */
87 } /* end of my_trnlnm */
90 * Translate a logical name. Substitute for CRTL getenv() to avoid
91 * memory leak, and to keep my_getenv() and my_setenv() in the same
92 * domain (mostly - my_getenv() need not return a translation from
93 * the process logical name table)
95 * Note: Uses static buffer -- not thread-safe!
97 /*{{{ char *my_getenv(char *lnm)*/
101 static char __my_getenv_eqv[LNM$C_NAMLENGTH+1];
102 char uplnm[LNM$C_NAMLENGTH], *cp1, *cp2;
103 unsigned long int idx = 0;
105 for (cp1 = lnm, cp2= uplnm; *cp1; cp1++, cp2++) *cp2 = _toupper(*cp1);
107 if (cp1 - lnm == 7 && !strncmp(uplnm,"DEFAULT",7)) {
108 getcwd(__my_getenv_eqv,sizeof __my_getenv_eqv);
109 return __my_getenv_eqv;
112 if ((cp2 = strchr(uplnm,';')) != NULL) {
114 idx = strtoul(cp2+1,NULL,0);
116 if (my_trnlnm(uplnm,__my_getenv_eqv,idx)) {
117 return __my_getenv_eqv;
120 unsigned long int retsts;
121 struct dsc$descriptor_s symdsc = {0,DSC$K_DTYPE_T,DSC$K_CLASS_S,0},
122 valdsc = {sizeof __my_getenv_eqv,DSC$K_DTYPE_T,
123 DSC$K_CLASS_S, __my_getenv_eqv};
124 symdsc.dsc$w_length = cp1 - lnm;
125 symdsc.dsc$a_pointer = uplnm;
126 retsts = lib$get_symbol(&symdsc,&valdsc,&(valdsc.dsc$w_length),0);
127 if (retsts == LIB$_INVSYMNAM) return Nullch;
128 if (retsts != LIB$_NOSUCHSYM) {
129 /* We want to return only logical names or CRTL Unix emulations */
130 if (retsts & 1) return Nullch;
133 /* Try for CRTL emulation of a Unix/POSIX name */
134 else return getenv(lnm);
139 } /* end of my_getenv() */
142 /*{{{ void my_setenv(char *lnm, char *eqv)*/
144 my_setenv(char *lnm,char *eqv)
145 /* Define a supervisor-mode logical name in the process table.
146 * In the future we'll add tables, attribs, and acmodes,
147 * probably through a different call.
150 char uplnm[LNM$C_NAMLENGTH], *cp1, *cp2;
151 unsigned long int retsts, usermode = PSL$C_USER;
152 $DESCRIPTOR(tabdsc,"LNM$PROCESS");
153 struct dsc$descriptor_s lnmdsc = {0,DSC$K_DTYPE_T,DSC$K_CLASS_S,uplnm},
154 eqvdsc = {0,DSC$K_DTYPE_T,DSC$K_CLASS_S,0};
156 for(cp1 = lnm, cp2 = uplnm; *cp1; cp1++, cp2++) *cp2 = _toupper(*cp1);
157 lnmdsc.dsc$w_length = cp1 - lnm;
159 if (!eqv || !*eqv) { /* we're deleting a logical name */
160 retsts = sys$dellnm(&tabdsc,&lnmdsc,&usermode); /* try user mode first */
161 if (retsts == SS$_IVLOGNAM) return;
162 if (retsts != SS$_NOLOGNAM) _ckvmssts(retsts);
164 retsts = lib$delete_logical(&lnmdsc,&tabdsc); /* then supervisor mode */
165 if (retsts != SS$_NOLOGNAM) _ckvmssts(retsts);
169 eqvdsc.dsc$w_length = strlen(eqv);
170 eqvdsc.dsc$a_pointer = eqv;
172 _ckvmssts(lib$set_logical(&lnmdsc,&eqvdsc,&tabdsc,0,0));
175 } /* end of my_setenv() */
179 /*{{{ char *my_crypt(const char *textpasswd, const char *usrname)*/
180 /* my_crypt - VMS password hashing
181 * my_crypt() provides an interface compatible with the Unix crypt()
182 * C library function, and uses sys$hash_password() to perform VMS
183 * password hashing. The quadword hashed password value is returned
184 * as a NUL-terminated 8 character string. my_crypt() does not change
185 * the case of its string arguments; in order to match the behavior
186 * of LOGINOUT et al., alphabetic characters in both arguments must
187 * be upcased by the caller.
190 my_crypt(const char *textpasswd, const char *usrname)
192 # ifndef UAI$C_PREFERRED_ALGORITHM
193 # define UAI$C_PREFERRED_ALGORITHM 127
195 unsigned char alg = UAI$C_PREFERRED_ALGORITHM;
196 unsigned short int salt = 0;
197 unsigned long int sts;
199 unsigned short int dsc$w_length;
200 unsigned char dsc$b_type;
201 unsigned char dsc$b_class;
202 const char * dsc$a_pointer;
203 } usrdsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0},
204 txtdsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};
205 struct itmlst_3 uailst[3] = {
206 { sizeof alg, UAI$_ENCRYPT, &alg, 0},
207 { sizeof salt, UAI$_SALT, &salt, 0},
208 { 0, 0, NULL, NULL}};
211 usrdsc.dsc$w_length = strlen(usrname);
212 usrdsc.dsc$a_pointer = usrname;
213 if (!((sts = sys$getuai(0, 0, &usrdsc, uailst, 0, 0, 0)) & 1)) {
220 set_errno(ESRCH); /* There isn't a Unix no-such-user error */
226 if (sts != RMS$_RNF) return NULL;
229 txtdsc.dsc$w_length = strlen(textpasswd);
230 txtdsc.dsc$a_pointer = textpasswd;
231 if (!((sts = sys$hash_password(&txtdsc, alg, salt, &usrdsc, &hash)) & 1)) {
232 set_errno(EVMSERR); set_vaxc_errno(sts); return NULL;
235 return (char *) hash;
237 } /* end of my_crypt() */
241 static char *do_fileify_dirspec(char *, char *, int);
242 static char *do_tovmsspec(char *, char *, int);
244 /*{{{int do_rmdir(char *name)*/
248 char dirfile[NAM$C_MAXRSS+1];
252 if (do_fileify_dirspec(name,dirfile,0) == NULL) return -1;
253 if (flex_stat(dirfile,&st) || !S_ISDIR(st.st_mode)) retval = -1;
254 else retval = kill_file(dirfile);
257 } /* end of do_rmdir */
261 * Delete any file to which user has control access, regardless of whether
262 * delete access is explicitly allowed.
263 * Limitations: User must have write access to parent directory.
264 * Does not block signals or ASTs; if interrupted in midstream
265 * may leave file with an altered ACL.
268 /*{{{int kill_file(char *name)*/
270 kill_file(char *name)
272 char vmsname[NAM$C_MAXRSS+1];
273 unsigned long int jpicode = JPI$_UIC, type = ACL$C_FILE;
274 unsigned long int cxt = 0, aclsts, fndsts, rmsts = -1;
275 struct dsc$descriptor_s fildsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};
277 unsigned char myace$b_length;
278 unsigned char myace$b_type;
279 unsigned short int myace$w_flags;
280 unsigned long int myace$l_access;
281 unsigned long int myace$l_ident;
282 } newace = { sizeof(struct myacedef), ACE$C_KEYID, 0,
283 ACE$M_READ | ACE$M_WRITE | ACE$M_DELETE | ACE$M_CONTROL, 0},
284 oldace = { sizeof(struct myacedef), ACE$C_KEYID, 0, 0, 0};
286 findlst[3] = {{sizeof oldace, ACL$C_FNDACLENT, &oldace, 0},
287 {sizeof oldace, ACL$C_READACE, &oldace, 0},{0,0,0,0}},
288 addlst[2] = {{sizeof newace, ACL$C_ADDACLENT, &newace, 0},{0,0,0,0}},
289 dellst[2] = {{sizeof newace, ACL$C_DELACLENT, &newace, 0},{0,0,0,0}},
290 lcklst[2] = {{sizeof newace, ACL$C_WLOCK_ACL, &newace, 0},{0,0,0,0}},
291 ulklst[2] = {{sizeof newace, ACL$C_UNLOCK_ACL, &newace, 0},{0,0,0,0}};
293 if (!remove(name)) return 0; /* Can we just get rid of it? */
295 /* No, so we get our own UIC to use as a rights identifier,
296 * and the insert an ACE at the head of the ACL which allows us
297 * to delete the file.
299 _ckvmssts(lib$getjpi(&jpicode,0,0,&(oldace.myace$l_ident),0,0));
300 if (do_tovmsspec(name,vmsname,0) == NULL) return -1;
301 fildsc.dsc$w_length = strlen(vmsname);
302 fildsc.dsc$a_pointer = vmsname;
304 newace.myace$l_ident = oldace.myace$l_ident;
305 if (!((aclsts = sys$change_acl(0,&type,&fildsc,lcklst,0,0,0)) & 1)) {
307 set_vaxc_errno(aclsts);
310 /* Grab any existing ACEs with this identifier in case we fail */
311 aclsts = fndsts = sys$change_acl(0,&type,&fildsc,findlst,0,0,&cxt);
312 if ( fndsts & 1 || fndsts == SS$_ACLEMPTY || fndsts == SS$_NOENTRY
313 || fndsts == SS$_NOMOREACE ) {
314 /* Add the new ACE . . . */
315 if (!((aclsts = sys$change_acl(0,&type,&fildsc,addlst,0,0,0)) & 1))
317 if ((rmsts = remove(name))) {
318 /* We blew it - dir with files in it, no write priv for
319 * parent directory, etc. Put things back the way they were. */
320 if (!((aclsts = sys$change_acl(0,&type,&fildsc,dellst,0,0,0)) & 1))
323 addlst[0].bufadr = &oldace;
324 if (!((aclsts = sys$change_acl(0,&type,&fildsc,addlst,0,0,&cxt)) & 1))
332 fndsts = sys$change_acl(0,&type,&fildsc,ulklst,0,0,0);
333 if (aclsts & 1) aclsts = fndsts;
337 set_vaxc_errno(aclsts);
343 } /* end of kill_file() */
346 /* my_utime - update modification time of a file
347 * calling sequence is identical to POSIX utime(), but under
348 * VMS only the modification time is changed; ODS-2 does not
349 * maintain access times. Restrictions differ from the POSIX
350 * definition in that the time can be changed as long as the
351 * caller has permission to execute the necessary IO$_MODIFY $QIO;
352 * no separate checks are made to insure that the caller is the
353 * owner of the file or has special privs enabled.
354 * Code here is based on Joe Meadows' FILE utility.
357 /* Adjustment from Unix epoch (01-JAN-1970 00:00:00.00)
358 * to VMS epoch (01-JAN-1858 00:00:00.00)
359 * in 100 ns intervals.
361 static const long int utime_baseadjust[2] = { 0x4beb4000, 0x7c9567 };
363 /*{{{int my_utime(char *path, struct utimbuf *utimes)*/
364 int my_utime(char *file, struct utimbuf *utimes)
367 long int bintime[2], len = 2, lowbit, unixtime,
368 secscale = 10000000; /* seconds --> 100 ns intervals */
369 unsigned long int chan, iosb[2], retsts;
370 char vmsspec[NAM$C_MAXRSS+1], rsa[NAM$C_MAXRSS], esa[NAM$C_MAXRSS];
371 struct FAB myfab = cc$rms_fab;
372 struct NAM mynam = cc$rms_nam;
373 #if defined (__DECC) && defined (__VAX)
374 /* VAX DEC C atrdef.h has unsigned type for pointer member atr$l_addr,
375 * at least through VMS V6.1, which causes a type-conversion warning.
377 # pragma message save
378 # pragma message disable cvtdiftypes
380 struct atrdef myatr[2] = {{sizeof bintime, ATR$C_REVDATE, bintime}, {0,0,0}};
382 #if defined (__DECC) && defined (__VAX)
383 /* This should be right after the declaration of myatr, but due
384 * to a bug in VAX DEC C, this takes effect a statement early.
386 # pragma message restore
388 struct dsc$descriptor fibdsc = {sizeof(myfib), DSC$K_DTYPE_Z, DSC$K_CLASS_S,(char *) &myfib},
389 devdsc = {0,DSC$K_DTYPE_T, DSC$K_CLASS_S,0},
390 fnmdsc = {0,DSC$K_DTYPE_T, DSC$K_CLASS_S,0};
392 if (file == NULL || *file == '\0') {
394 set_vaxc_errno(LIB$_INVARG);
397 if (do_tovmsspec(file,vmsspec,0) == NULL) return -1;
399 if (utimes != NULL) {
400 /* Convert Unix time (seconds since 01-JAN-1970 00:00:00.00)
401 * to VMS quadword time (100 nsec intervals since 01-JAN-1858 00:00:00.00).
402 * Since time_t is unsigned long int, and lib$emul takes a signed long int
403 * as input, we force the sign bit to be clear by shifting unixtime right
404 * one bit, then multiplying by an extra factor of 2 in lib$emul().
406 lowbit = (utimes->modtime & 1) ? secscale : 0;
407 unixtime = (long int) utimes->modtime;
408 unixtime >> 1; secscale << 1;
409 retsts = lib$emul(&secscale, &unixtime, &lowbit, bintime);
412 set_vaxc_errno(retsts);
415 retsts = lib$addx(bintime,utime_baseadjust,bintime,&len);
418 set_vaxc_errno(retsts);
423 /* Just get the current time in VMS format directly */
424 retsts = sys$gettim(bintime);
427 set_vaxc_errno(retsts);
432 myfab.fab$l_fna = vmsspec;
433 myfab.fab$b_fns = (unsigned char) strlen(vmsspec);
434 myfab.fab$l_nam = &mynam;
435 mynam.nam$l_esa = esa;
436 mynam.nam$b_ess = (unsigned char) sizeof esa;
437 mynam.nam$l_rsa = rsa;
438 mynam.nam$b_rss = (unsigned char) sizeof rsa;
440 /* Look for the file to be affected, letting RMS parse the file
441 * specification for us as well. I have set errno using only
442 * values documented in the utime() man page for VMS POSIX.
444 retsts = sys$parse(&myfab,0,0);
446 set_vaxc_errno(retsts);
447 if (retsts == RMS$_PRV) set_errno(EACCES);
448 else if (retsts == RMS$_DIR) set_errno(ENOTDIR);
449 else set_errno(EVMSERR);
452 retsts = sys$search(&myfab,0,0);
454 set_vaxc_errno(retsts);
455 if (retsts == RMS$_PRV) set_errno(EACCES);
456 else if (retsts == RMS$_FNF) set_errno(ENOENT);
457 else set_errno(EVMSERR);
461 devdsc.dsc$w_length = mynam.nam$b_dev;
462 devdsc.dsc$a_pointer = (char *) mynam.nam$l_dev;
464 retsts = sys$assign(&devdsc,&chan,0,0);
466 set_vaxc_errno(retsts);
467 if (retsts == SS$_IVDEVNAM) set_errno(ENOTDIR);
468 else if (retsts == SS$_NOPRIV) set_errno(EACCES);
469 else if (retsts == SS$_NOSUCHDEV) set_errno(ENOTDIR);
470 else set_errno(EVMSERR);
474 fnmdsc.dsc$a_pointer = mynam.nam$l_name;
475 fnmdsc.dsc$w_length = mynam.nam$b_name + mynam.nam$b_type + mynam.nam$b_ver;
477 memset((void *) &myfib, 0, sizeof myfib);
479 for (i=0;i<3;i++) myfib.fib$w_fid[i] = mynam.nam$w_fid[i];
480 for (i=0;i<3;i++) myfib.fib$w_did[i] = mynam.nam$w_did[i];
481 /* This prevents the revision time of the file being reset to the current
482 * time as a result of our IO$_MODIFY $QIO. */
483 myfib.fib$l_acctl = FIB$M_NORECORD;
485 for (i=0;i<3;i++) myfib.fib$r_fid_overlay.fib$w_fid[i] = mynam.nam$w_fid[i];
486 for (i=0;i<3;i++) myfib.fib$r_did_overlay.fib$w_did[i] = mynam.nam$w_did[i];
487 myfib.fib$r_acctl_overlay.fib$l_acctl = FIB$M_NORECORD;
489 retsts = sys$qiow(0,chan,IO$_MODIFY,iosb,0,0,&fibdsc,&fnmdsc,0,0,myatr,0);
490 _ckvmssts(sys$dassgn(chan));
491 if (retsts & 1) retsts = iosb[0];
493 set_vaxc_errno(retsts);
494 if (retsts == SS$_NOPRIV) set_errno(EACCES);
495 else set_errno(EVMSERR);
500 } /* end of my_utime() */
504 create_mbx(unsigned short int *chan, struct dsc$descriptor_s *namdsc)
506 static unsigned long int mbxbufsiz;
507 long int syiitm = SYI$_MAXBUF, dviitm = DVI$_DEVNAM;
511 * Get the SYSGEN parameter MAXBUF, and the smaller of it and the
512 * preprocessor consant BUFSIZ from stdio.h as the size of the
515 _ckvmssts(lib$getsyi(&syiitm, &mbxbufsiz, 0, 0, 0, 0));
516 if (mbxbufsiz > BUFSIZ) mbxbufsiz = BUFSIZ;
518 _ckvmssts(sys$crembx(0,chan,mbxbufsiz,mbxbufsiz,0,0,0));
520 _ckvmssts(lib$getdvi(&dviitm, chan, NULL, NULL, namdsc, &namdsc->dsc$w_length));
521 namdsc->dsc$a_pointer[namdsc->dsc$w_length] = '\0';
523 } /* end of create_mbx() */
525 /*{{{ my_popen and my_pclose*/
528 struct pipe_details *next;
529 FILE *fp; /* stdio file pointer to pipe mailbox */
530 int pid; /* PID of subprocess */
531 int mode; /* == 'r' if pipe open for reading */
532 int done; /* subprocess has completed */
533 unsigned long int completion; /* termination status of subprocess */
536 struct exit_control_block
538 struct exit_control_block *flink;
539 unsigned long int (*exit_routine)();
540 unsigned long int arg_count;
541 unsigned long int *status_address;
542 unsigned long int exit_status;
545 static struct pipe_details *open_pipes = NULL;
546 static $DESCRIPTOR(nl_desc, "NL:");
547 static int waitpid_asleep = 0;
549 static unsigned long int
552 unsigned long int retsts = SS$_NORMAL, abort = SS$_TIMEOUT, sts;
554 while (open_pipes != NULL) {
555 if (!open_pipes->done) { /* Tap them gently on the shoulder . . .*/
556 _ckvmssts(sys$forcex(&open_pipes->pid,0,&abort));
559 if (!open_pipes->done) /* We tried to be nice . . . */
560 _ckvmssts(sys$delprc(&open_pipes->pid,0));
561 if (!((sts = my_pclose(open_pipes->fp))&1)) retsts = sts;
566 static struct exit_control_block pipe_exitblock =
567 {(struct exit_control_block *) 0,
568 pipe_exit_routine, 0, &pipe_exitblock.exit_status, 0};
572 popen_completion_ast(struct pipe_details *thispipe)
574 thispipe->done = TRUE;
575 if (waitpid_asleep) {
581 /*{{{ FILE *my_popen(char *cmd, char *mode)*/
583 my_popen(char *cmd, char *mode)
585 static int handler_set_up = FALSE;
587 unsigned short int chan;
588 unsigned long int flags=1; /* nowait - gnu c doesn't allow &1 */
589 struct pipe_details *info;
590 struct dsc$descriptor_s namdsc = {sizeof mbxname, DSC$K_DTYPE_T,
591 DSC$K_CLASS_S, mbxname},
592 cmddsc = {0, DSC$K_DTYPE_T,
596 cmddsc.dsc$w_length=strlen(cmd);
597 cmddsc.dsc$a_pointer=cmd;
598 if (cmddsc.dsc$w_length > 255) {
599 set_errno(E2BIG); set_vaxc_errno(CLI$_BUFOVF);
603 New(7001,info,1,struct pipe_details);
606 create_mbx(&chan,&namdsc);
608 /* open a FILE* onto it */
609 info->fp=fopen(mbxname, mode);
611 /* give up other channel onto it */
612 _ckvmssts(sys$dassgn(chan));
622 _ckvmssts(lib$spawn(&cmddsc, &nl_desc, &namdsc, &flags,
623 0 /* name */, &info->pid, &info->completion,
624 0, popen_completion_ast,info,0,0,0));
627 _ckvmssts(lib$spawn(&cmddsc, &namdsc, 0 /* sys$output */, &flags,
628 0 /* name */, &info->pid, &info->completion,
629 0, popen_completion_ast,info,0,0,0));
632 if (!handler_set_up) {
633 _ckvmssts(sys$dclexh(&pipe_exitblock));
634 handler_set_up = TRUE;
636 info->next=open_pipes; /* prepend to list */
639 forkprocess = info->pid;
644 /*{{{ I32 my_pclose(FILE *fp)*/
645 I32 my_pclose(FILE *fp)
647 struct pipe_details *info, *last = NULL;
648 unsigned long int retsts;
650 for (info = open_pipes; info != NULL; last = info, info = info->next)
651 if (info->fp == fp) break;
654 /* get here => no such pipe open */
655 croak("No such pipe open");
659 if (info->done) retsts = info->completion;
660 else waitpid(info->pid,(int *) &retsts,0);
662 /* remove from list of open pipes */
663 if (last) last->next = info->next;
664 else open_pipes = info->next;
669 } /* end of my_pclose() */
671 /* sort-of waitpid; use only with popen() */
672 /*{{{unsigned long int waitpid(unsigned long int pid, int *statusp, int flags)*/
674 waitpid(unsigned long int pid, int *statusp, int flags)
676 struct pipe_details *info;
678 for (info = open_pipes; info != NULL; info = info->next)
679 if (info->pid == pid) break;
681 if (info != NULL) { /* we know about this child */
682 while (!info->done) {
687 *statusp = info->completion;
690 else { /* we haven't heard of this child */
691 $DESCRIPTOR(intdsc,"0 00:00:01");
692 unsigned long int ownercode = JPI$_OWNER, ownerpid, mypid;
693 unsigned long int interval[2],sts;
696 _ckvmssts(lib$getjpi(&ownercode,&pid,0,&ownerpid,0,0));
697 _ckvmssts(lib$getjpi(&ownercode,0,0,&mypid,0,0));
698 if (ownerpid != mypid)
699 warn("pid %d not a child",pid);
702 _ckvmssts(sys$bintim(&intdsc,interval));
703 while ((sts=lib$getjpi(&ownercode,&pid,0,&ownerpid,0,0)) & 1) {
704 _ckvmssts(sys$schdwk(0,0,interval,0));
705 _ckvmssts(sys$hiber());
709 /* There's no easy way to find the termination status a child we're
710 * not aware of beforehand. If we're really interested in the future,
711 * we can go looking for a termination mailbox, or chase after the
712 * accounting record for the process.
718 } /* end of waitpid() */
723 /*{{{ char *my_gconvert(double val, int ndig, int trail, char *buf) */
725 my_gconvert(double val, int ndig, int trail, char *buf)
727 static char __gcvtbuf[DBL_DIG+1];
730 loc = buf ? buf : __gcvtbuf;
732 if (!buf && ndig > DBL_DIG) ndig = DBL_DIG;
733 return gcvt(val,ndig,loc);
736 loc[0] = '0'; loc[1] = '\0';
744 ** The following routines are provided to make life easier when
745 ** converting among VMS-style and Unix-style directory specifications.
746 ** All will take input specifications in either VMS or Unix syntax. On
747 ** failure, all return NULL. If successful, the routines listed below
748 ** return a pointer to a buffer containing the appropriately
749 ** reformatted spec (and, therefore, subsequent calls to that routine
750 ** will clobber the result), while the routines of the same names with
751 ** a _ts suffix appended will return a pointer to a mallocd string
752 ** containing the appropriately reformatted spec.
753 ** In all cases, only explicit syntax is altered; no check is made that
754 ** the resulting string is valid or that the directory in question
757 ** fileify_dirspec() - convert a directory spec into the name of the
758 ** directory file (i.e. what you can stat() to see if it's a dir).
759 ** The style (VMS or Unix) of the result is the same as the style
760 ** of the parameter passed in.
761 ** pathify_dirspec() - convert a directory spec into a path (i.e.
762 ** what you prepend to a filename to indicate what directory it's in).
763 ** The style (VMS or Unix) of the result is the same as the style
764 ** of the parameter passed in.
765 ** tounixpath() - convert a directory spec into a Unix-style path.
766 ** tovmspath() - convert a directory spec into a VMS-style path.
767 ** tounixspec() - convert any file spec into a Unix-style file spec.
768 ** tovmsspec() - convert any file spec into a VMS-style spec.
770 ** Copyright 1996 by Charles Bailey <bailey@genetics.upenn.edu>
771 ** Permission is given for non-commercial use of this code according
772 ** to the terms of the GNU General Public License or the Perl
773 ** Artistic License. Copies of each may be found in the Perl
774 ** standard distribution. This software is supplied without any
775 ** warranty whatsoever.
778 static char *do_tounixspec(char *, char *, int);
780 /*{{{ char *fileify_dirspec[_ts](char *path, char *buf)*/
781 static char *do_fileify_dirspec(char *dir,char *buf,int ts)
783 static char __fileify_retbuf[NAM$C_MAXRSS+1];
784 unsigned long int dirlen, retlen, addmfd = 0;
785 char *retspec, *cp1, *cp2, *lastdir;
786 char trndir[NAM$C_MAXRSS+1], vmsdir[NAM$C_MAXRSS+1];
789 set_errno(EINVAL); set_vaxc_errno(SS$_BADPARAM); return NULL;
791 dirlen = strlen(dir);
792 if (dir[dirlen-1] == '/') dir[--dirlen] = '\0';
795 set_vaxc_errno(RMS$_DIR);
798 if (!strpbrk(dir+1,"/]>:")) {
799 strcpy(trndir,*dir == '/' ? dir + 1: dir);
800 while (!strpbrk(trndir,"/]>:>") && my_trnlnm(trndir,trndir,0)) ;
802 dirlen = strlen(dir);
804 /* If we were handed a rooted logical name or spec, treat it like a
805 * simple directory, so that
806 * $ Define myroot dev:[dir.]
807 * ... do_fileify_dirspec("myroot",buf,1) ...
808 * does something useful.
810 if (!strcmp(dir+dirlen-2,".]")) {
811 dir[--dirlen] = '\0';
815 if (!strpbrk(dir,"]:>")) { /* Unix-style path or plain dir name */
817 if (dir[1] == '\0' || (dir[1] == '/' && dir[2] == '\0'))
818 return do_fileify_dirspec("[]",buf,ts);
819 else if (dir[1] == '.' &&
820 (dir[2] == '\0' || (dir[2] == '/' && dir[3] == '\0')))
821 return do_fileify_dirspec("[-]",buf,ts);
823 if (dir[dirlen-1] == '/') { /* path ends with '/'; just add .dir;1 */
824 dirlen -= 1; /* to last element */
825 lastdir = strrchr(dir,'/');
827 else if ((cp1 = strstr(trndir,"/.")) != NULL) {
829 if (*(cp1+2) == '.') cp1++;
830 if (*(cp1+2) == '/' || *(cp1+2) == '\0') {
835 } while ((cp1 = strstr(cp1,"/.")) != NULL);
836 /* If we have a relative path, VMSify it and let the VMS code
837 * below expand it, rather than repeating the code here */
839 if (do_tovmsspec(trndir,vmsdir,0) == NULL) return NULL;
840 if (do_fileify_dirspec(vmsdir,trndir,0) == NULL) return NULL;
841 return do_tounixspec(trndir,buf,ts);
845 if (!(lastdir = cp1 = strrchr(dir,'/'))) cp1 = dir;
846 if ((cp2 = strchr(cp1,'.'))) { /* look for explicit type */
847 if (toupper(*(cp2+1)) == 'D' && /* Yep. Is it .dir? */
848 toupper(*(cp2+2)) == 'I' &&
849 toupper(*(cp2+3)) == 'R') {
850 if ((cp1 = strchr(cp2,';')) || (cp1 = strchr(cp2+1,'.'))) {
851 if (*(cp1+1) != '1' || *(cp1+2) != '\0') { /* Version is not ;1 */
852 set_errno(ENOTDIR); /* Bzzt. */
853 set_vaxc_errno(RMS$_DIR);
859 else { /* There's a type, and it's not .dir. Bzzt. */
861 set_vaxc_errno(RMS$_DIR);
866 /* If we lead off with a device or rooted logical, add the MFD
867 if we're specifying a top-level directory. */
868 if (lastdir && *dir == '/') {
870 for (cp1 = lastdir - 1; cp1 > dir; cp1--) {
877 retlen = dirlen + (addmfd ? 13 : 6);
878 if (buf) retspec = buf;
879 else if (ts) New(7009,retspec,retlen+1,char);
880 else retspec = __fileify_retbuf;
882 dirlen = lastdir - dir;
883 memcpy(retspec,dir,dirlen);
884 strcpy(&retspec[dirlen],"/000000");
885 strcpy(&retspec[dirlen+7],lastdir);
888 memcpy(retspec,dir,dirlen);
889 retspec[dirlen] = '\0';
891 /* We've picked up everything up to the directory file name.
892 Now just add the type and version, and we're set. */
893 strcat(retspec,".dir;1");
896 else { /* VMS-style directory spec */
897 char esa[NAM$C_MAXRSS+1], term;
898 unsigned long int sts, cmplen, hasdev, hasdir, hastype, hasver;
899 struct FAB dirfab = cc$rms_fab;
900 struct NAM savnam, dirnam = cc$rms_nam;
902 dirfab.fab$b_fns = strlen(dir);
903 dirfab.fab$l_fna = dir;
904 dirfab.fab$l_nam = &dirnam;
905 dirfab.fab$l_dna = ".DIR;1";
906 dirfab.fab$b_dns = 6;
907 dirnam.nam$b_ess = NAM$C_MAXRSS;
908 dirnam.nam$l_esa = esa;
909 if (!((sts = sys$parse(&dirfab))&1)) {
910 if (dirfab.fab$l_sts == RMS$_DIR) {
911 dirnam.nam$b_nop |= NAM$M_SYNCHK;
912 sts = sys$parse(&dirfab) & 1;
916 set_vaxc_errno(dirfab.fab$l_sts);
922 if (sys$search(&dirfab)&1) { /* Does the file really exist? */
923 /* Yes; fake the fnb bits so we'll check type below */
924 dirnam.nam$l_fnb |= NAM$M_EXP_TYPE | NAM$M_EXP_VER;
927 if (dirfab.fab$l_sts != RMS$_FNF) {
929 set_vaxc_errno(dirfab.fab$l_sts);
932 dirnam = savnam; /* No; just work with potential name */
935 if (!(dirnam.nam$l_fnb & (NAM$M_EXP_DEV | NAM$M_EXP_DIR))) {
936 cp1 = strchr(esa,']');
937 if (!cp1) cp1 = strchr(esa,'>');
938 if (cp1) { /* Should always be true */
939 dirnam.nam$b_esl -= cp1 - esa - 1;
940 memcpy(esa,cp1 + 1,dirnam.nam$b_esl);
943 if (dirnam.nam$l_fnb & NAM$M_EXP_TYPE) { /* Was type specified? */
944 /* Yep; check version while we're at it, if it's there. */
945 cmplen = (dirnam.nam$l_fnb & NAM$M_EXP_VER) ? 6 : 4;
946 if (strncmp(dirnam.nam$l_type,".DIR;1",cmplen)) {
947 /* Something other than .DIR[;1]. Bzzt. */
949 set_vaxc_errno(RMS$_DIR);
953 esa[dirnam.nam$b_esl] = '\0';
954 if (dirnam.nam$l_fnb & NAM$M_EXP_NAME) {
955 /* They provided at least the name; we added the type, if necessary, */
956 if (buf) retspec = buf; /* in sys$parse() */
957 else if (ts) New(7011,retspec,dirnam.nam$b_esl+1,char);
958 else retspec = __fileify_retbuf;
962 if ((cp1 = strstr(esa,".][000000]")) != NULL) {
963 for (cp2 = cp1 + 9; *cp2; cp1++,cp2++) *cp1 = *cp2;
965 dirnam.nam$b_esl -= 9;
967 if ((cp1 = strrchr(esa,']')) == NULL) cp1 = strrchr(esa,'>');
968 if (cp1 == NULL) return NULL; /* should never happen */
971 retlen = strlen(esa);
972 if ((cp1 = strrchr(esa,'.')) != NULL) {
973 /* There's more than one directory in the path. Just roll back. */
975 if (buf) retspec = buf;
976 else if (ts) New(7011,retspec,retlen+7,char);
977 else retspec = __fileify_retbuf;
981 if (dirnam.nam$l_fnb & NAM$M_ROOT_DIR) {
982 /* Go back and expand rooted logical name */
983 dirnam.nam$b_nop = NAM$M_SYNCHK | NAM$M_NOCONCEAL;
984 if (!(sys$parse(&dirfab) & 1)) {
986 set_vaxc_errno(dirfab.fab$l_sts);
989 retlen = dirnam.nam$b_esl - 9; /* esa - '][' - '].DIR;1' */
990 if (buf) retspec = buf;
991 else if (ts) New(7012,retspec,retlen+16,char);
992 else retspec = __fileify_retbuf;
993 cp1 = strstr(esa,"][");
995 memcpy(retspec,esa,dirlen);
996 if (!strncmp(cp1+2,"000000]",7)) {
997 retspec[dirlen-1] = '\0';
998 for (cp1 = retspec+dirlen-1; *cp1 != '.' && *cp1 != '['; cp1--) ;
999 if (*cp1 == '.') *cp1 = ']';
1001 memmove(cp1+8,cp1+1,retspec+dirlen-cp1);
1002 memcpy(cp1+1,"000000]",7);
1006 memcpy(retspec+dirlen,cp1+2,retlen-dirlen);
1007 retspec[retlen] = '\0';
1008 /* Convert last '.' to ']' */
1009 for (cp1 = retspec+retlen-1; *cp1 != '.' && *cp1 != '['; cp1--) ;
1010 if (*cp1 == '.') *cp1 = ']';
1012 memmove(cp1+8,cp1+1,retspec+dirlen-cp1);
1013 memcpy(cp1+1,"000000]",7);
1017 else { /* This is a top-level dir. Add the MFD to the path. */
1018 if (buf) retspec = buf;
1019 else if (ts) New(7012,retspec,retlen+16,char);
1020 else retspec = __fileify_retbuf;
1023 while (*cp1 != ':') *(cp2++) = *(cp1++);
1024 strcpy(cp2,":[000000]");
1029 /* We've set up the string up through the filename. Add the
1030 type and version, and we're done. */
1031 strcat(retspec,".DIR;1");
1034 } /* end of do_fileify_dirspec() */
1036 /* External entry points */
1037 char *fileify_dirspec(char *dir, char *buf)
1038 { return do_fileify_dirspec(dir,buf,0); }
1039 char *fileify_dirspec_ts(char *dir, char *buf)
1040 { return do_fileify_dirspec(dir,buf,1); }
1042 /*{{{ char *pathify_dirspec[_ts](char *path, char *buf)*/
1043 static char *do_pathify_dirspec(char *dir,char *buf, int ts)
1045 static char __pathify_retbuf[NAM$C_MAXRSS+1];
1046 unsigned long int retlen;
1047 char *retpath, *cp1, *cp2, trndir[NAM$C_MAXRSS+1];
1049 if (!dir || !*dir) {
1050 set_errno(EINVAL); set_vaxc_errno(SS$_BADPARAM); return NULL;
1053 if (*dir) strcpy(trndir,dir);
1054 else getcwd(trndir,sizeof trndir - 1);
1056 while (!strpbrk(trndir,"/]:>") && my_trnlnm(trndir,trndir,0)) {
1057 STRLEN trnlen = strlen(trndir);
1059 /* Trap simple rooted lnms, and return lnm:[000000] */
1060 if (!strcmp(trndir+trnlen-2,".]")) {
1061 if (buf) retpath = buf;
1062 else if (ts) New(7018,retpath,strlen(dir)+10,char);
1063 else retpath = __pathify_retbuf;
1064 strcpy(retpath,dir);
1065 strcat(retpath,":[000000]");
1071 if (!strpbrk(dir,"]:>")) { /* Unix-style path or plain dir name */
1072 if (*dir == '.' && (*(dir+1) == '\0' ||
1073 (*(dir+1) == '.' && *(dir+2) == '\0')))
1074 retlen = 2 + (*(dir+1) != '\0');
1076 if (!(cp1 = strrchr(dir,'/'))) cp1 = dir;
1077 if ((cp2 = strchr(cp1,'.')) && *(cp2+1) != '.') {
1078 if (toupper(*(cp2+1)) == 'D' && /* They specified .dir. */
1079 toupper(*(cp2+2)) == 'I' && /* Trim it off. */
1080 toupper(*(cp2+3)) == 'R') {
1081 retlen = cp2 - dir + 1;
1083 else { /* Some other file type. Bzzt. */
1085 set_vaxc_errno(RMS$_DIR);
1089 else { /* No file type present. Treat the filename as a directory. */
1090 retlen = strlen(dir) + 1;
1093 if (buf) retpath = buf;
1094 else if (ts) New(7013,retpath,retlen+1,char);
1095 else retpath = __pathify_retbuf;
1096 strncpy(retpath,dir,retlen-1);
1097 if (retpath[retlen-2] != '/') { /* If the path doesn't already end */
1098 retpath[retlen-1] = '/'; /* with '/', add it. */
1099 retpath[retlen] = '\0';
1101 else retpath[retlen-1] = '\0';
1103 else { /* VMS-style directory spec */
1104 char esa[NAM$C_MAXRSS+1];
1105 unsigned long int sts, cmplen;
1106 struct FAB dirfab = cc$rms_fab;
1107 struct NAM savnam, dirnam = cc$rms_nam;
1109 dirfab.fab$b_fns = strlen(dir);
1110 dirfab.fab$l_fna = dir;
1111 if (dir[dirfab.fab$b_fns-1] == ']' ||
1112 dir[dirfab.fab$b_fns-1] == '>' ||
1113 dir[dirfab.fab$b_fns-1] == ':') { /* It's already a VMS 'path' */
1114 if (buf) retpath = buf;
1115 else if (ts) New(7014,retpath,strlen(dir)+1,char);
1116 else retpath = __pathify_retbuf;
1117 strcpy(retpath,dir);
1120 dirfab.fab$l_dna = ".DIR;1";
1121 dirfab.fab$b_dns = 6;
1122 dirfab.fab$l_nam = &dirnam;
1123 dirnam.nam$b_ess = (unsigned char) sizeof esa - 1;
1124 dirnam.nam$l_esa = esa;
1125 if (!((sts = sys$parse(&dirfab))&1)) {
1126 if (dirfab.fab$l_sts == RMS$_DIR) {
1127 dirnam.nam$b_nop |= NAM$M_SYNCHK;
1128 sts = sys$parse(&dirfab) & 1;
1132 set_vaxc_errno(dirfab.fab$l_sts);
1138 if (!(sys$search(&dirfab)&1)) { /* Does the file really exist? */
1139 if (dirfab.fab$l_sts != RMS$_FNF) {
1141 set_vaxc_errno(dirfab.fab$l_sts);
1144 dirnam = savnam; /* No; just work with potential name */
1147 if (dirnam.nam$l_fnb & NAM$M_EXP_TYPE) { /* Was type specified? */
1148 /* Yep; check version while we're at it, if it's there. */
1149 cmplen = (dirnam.nam$l_fnb & NAM$M_EXP_VER) ? 6 : 4;
1150 if (strncmp(dirnam.nam$l_type,".DIR;1",cmplen)) {
1151 /* Something other than .DIR[;1]. Bzzt. */
1153 set_vaxc_errno(RMS$_DIR);
1157 /* OK, the type was fine. Now pull any file name into the
1159 if ((cp1 = strrchr(esa,']'))) *dirnam.nam$l_type = ']';
1161 cp1 = strrchr(esa,'>');
1162 *dirnam.nam$l_type = '>';
1165 *(dirnam.nam$l_type + 1) = '\0';
1166 retlen = dirnam.nam$l_type - esa + 2;
1167 if (buf) retpath = buf;
1168 else if (ts) New(7014,retpath,retlen,char);
1169 else retpath = __pathify_retbuf;
1170 strcpy(retpath,esa);
1174 } /* end of do_pathify_dirspec() */
1176 /* External entry points */
1177 char *pathify_dirspec(char *dir, char *buf)
1178 { return do_pathify_dirspec(dir,buf,0); }
1179 char *pathify_dirspec_ts(char *dir, char *buf)
1180 { return do_pathify_dirspec(dir,buf,1); }
1182 /*{{{ char *tounixspec[_ts](char *path, char *buf)*/
1183 static char *do_tounixspec(char *spec, char *buf, int ts)
1185 static char __tounixspec_retbuf[NAM$C_MAXRSS+1];
1186 char *dirend, *rslt, *cp1, *cp2, *cp3, tmp[NAM$C_MAXRSS+1];
1187 int devlen, dirlen, retlen = NAM$C_MAXRSS+1, dashes = 0;
1189 if (spec == NULL) return NULL;
1190 if (strlen(spec) > NAM$C_MAXRSS) return NULL;
1191 if (buf) rslt = buf;
1193 retlen = strlen(spec);
1194 cp1 = strchr(spec,'[');
1195 if (!cp1) cp1 = strchr(spec,'<');
1197 for (cp1++; *cp1 == '-'; cp1++) dashes++; /* VMS '-' ==> Unix '../' */
1199 New(7015,rslt,retlen+2+2*dashes,char);
1201 else rslt = __tounixspec_retbuf;
1202 if (strchr(spec,'/') != NULL) {
1209 dirend = strrchr(spec,']');
1210 if (dirend == NULL) dirend = strrchr(spec,'>');
1211 if (dirend == NULL) dirend = strchr(spec,':');
1212 if (dirend == NULL) {
1216 if (*cp2 != '[' && *cp2 != '<') {
1219 else { /* the VMS spec begins with directories */
1221 if (*cp2 == ']' || *cp2 == '>') {
1225 else if (*cp2 == '-') {
1226 while (*cp2 == '-') {
1227 *(cp1++) = '.'; *(cp1++) = '.'; *(cp1++) = '/';
1230 if (*cp2 != '.' && *cp2 != ']' && *cp2 != '>') { /* we don't allow */
1231 if (ts) Safefree(rslt); /* filespecs like */
1232 set_errno(EINVAL); set_vaxc_errno(RMS$_SYN); /* [--foo.bar] */
1237 else if ( *(cp2) != '.') { /* add the implied device into the Unix spec */
1239 if (getcwd(tmp,sizeof tmp,1) == NULL) {
1240 if (ts) Safefree(rslt);
1245 while (*cp3 != ':' && *cp3) cp3++;
1247 if (strchr(cp3,']') != NULL) break;
1248 } while (((cp3 = my_getenv(tmp)) != NULL) && strcpy(tmp,cp3));
1250 while (*cp3) *(cp1++) = *(cp3++);
1253 ((devlen = strlen(tmp)) + (dirlen = strlen(cp2)) + 1 > retlen)) {
1254 int offset = cp1 - rslt;
1256 retlen = devlen + dirlen;
1257 Renew(rslt,retlen+1+2*dashes,char);
1258 cp1 = rslt + offset;
1263 for (; cp2 <= dirend; cp2++) {
1266 if (*(cp2+1) == '[') cp2++;
1268 else if (*cp2 == ']' || *cp2 == '>') *(cp1++) = '/';
1269 else if (*cp2 == '.') {
1271 if (*(cp2+1) == ']' || *(cp2+1) == '>') {
1272 while (*(cp2+1) == ']' || *(cp2+1) == '>' ||
1273 *(cp2+1) == '[' || *(cp2+1) == '<') cp2++;
1274 if (!strncmp(cp2,"[000000",7) && (*(cp2+7) == ']' ||
1275 *(cp2+7) == '>' || *(cp2+7) == '.')) cp2 += 7;
1278 else if (*cp2 == '-') {
1279 if (*(cp2-1) == '[' || *(cp2-1) == '<' || *(cp2-1) == '.') {
1280 while (*cp2 == '-') {
1282 *(cp1++) = '.'; *(cp1++) = '.'; *(cp1++) = '/';
1284 if (*cp2 != '.' && *cp2 != ']' && *cp2 != '>') { /* we don't allow */
1285 if (ts) Safefree(rslt); /* filespecs like */
1286 set_errno(EINVAL); set_vaxc_errno(RMS$_SYN); /* [--foo.bar] */
1291 else *(cp1++) = *cp2;
1293 else *(cp1++) = *cp2;
1295 while (*cp2) *(cp1++) = *(cp2++);
1300 } /* end of do_tounixspec() */
1302 /* External entry points */
1303 char *tounixspec(char *spec, char *buf) { return do_tounixspec(spec,buf,0); }
1304 char *tounixspec_ts(char *spec, char *buf) { return do_tounixspec(spec,buf,1); }
1306 /*{{{ char *tovmsspec[_ts](char *path, char *buf)*/
1307 static char *do_tovmsspec(char *path, char *buf, int ts) {
1308 static char __tovmsspec_retbuf[NAM$C_MAXRSS+1];
1309 char *rslt, *dirend;
1310 register char *cp1, *cp2;
1311 unsigned long int infront = 0, hasdir = 1;
1313 if (path == NULL) return NULL;
1314 if (buf) rslt = buf;
1315 else if (ts) New(7016,rslt,strlen(path)+9,char);
1316 else rslt = __tovmsspec_retbuf;
1317 if (strpbrk(path,"]:>") ||
1318 (dirend = strrchr(path,'/')) == NULL) {
1319 if (path[0] == '.') {
1320 if (path[1] == '\0') strcpy(rslt,"[]");
1321 else if (path[1] == '.' && path[2] == '\0') strcpy(rslt,"[-]");
1322 else strcpy(rslt,path); /* probably garbage */
1324 else strcpy(rslt,path);
1327 if (*(dirend+1) == '.') { /* do we have trailing "/." or "/.."? */
1328 if (!*(dirend+2)) dirend +=2;
1329 if (*(dirend+2) == '.' && !*(dirend+3)) dirend += 3;
1334 char trndev[NAM$C_MAXRSS+1];
1338 while (*(++cp2) != '/' && *cp2) *(cp1++) = *cp2;
1340 islnm = my_trnlnm(rslt,trndev,0);
1341 trnend = islnm ? strlen(trndev) - 1 : 0;
1342 islnm = trnend ? (trndev[trnend] == ']' || trndev[trnend] == '>') : 0;
1343 rooted = islnm ? (trndev[trnend-1] == '.') : 0;
1344 /* If the first element of the path is a logical name, determine
1345 * whether it has to be translated so we can add more directories. */
1346 if (!islnm || rooted) {
1349 if (cp2 == dirend) while (infront++ < 6) *(cp1++) = '0';
1353 if (cp2 != dirend) {
1354 if (!buf && ts) Renew(rslt,strlen(path)-strlen(rslt)+trnend+4,char);
1355 strcpy(rslt,trndev);
1356 cp1 = rslt + trnend;
1369 if (*(cp2+1) == '/' || *(cp2+1) == '\0') {
1370 cp2 += 2; /* skip over "./" - it's redundant */
1371 *(cp1++) = '.'; /* but it does indicate a relative dirspec */
1373 else if (*(cp2+1) == '.' && (*(cp2+2) == '/' || *(cp2+2) == '\0')) {
1374 *(cp1++) = '-'; /* "../" --> "-" */
1377 if (cp2 > dirend) cp2 = dirend;
1379 else *(cp1++) = '.';
1381 for (; cp2 < dirend; cp2++) {
1383 if (*(cp1-1) != '.') *(cp1++) = '.';
1386 else if (!infront && *cp2 == '.') {
1387 if (*(cp2+1) == '/') cp2++; /* skip over "./" - it's redundant */
1388 else if (*(cp2+1) == '\0') { cp2++; break; }
1389 else if (*(cp2+1) == '.' && (*(cp2+2) == '/' || *(cp2+2) == '\0')) {
1390 if (*(cp1-1) == '-' || *(cp1-1) == '[') *(cp1++) = '-'; /* handle "../" */
1391 else if (*(cp1-2) == '[') *(cp1-1) = '-';
1392 else { /* back up over previous directory name */
1394 while (*(cp1-1) != '.' && *(cp1-1) != '[') cp1--;
1395 if (*(cp1-1) == '[') {
1396 memcpy(cp1,"000000.",7);
1401 if (cp2 == dirend) {
1402 if (*(cp1-1) == '.') cp1--;
1406 else *(cp1++) = '_'; /* fix up syntax - '.' in name not allowed */
1409 if (!infront && *(cp1-1) == '-') *(cp1++) = '.';
1410 if (*cp2 == '/') *(cp1++) = '.';
1411 else if (*cp2 == '.') *(cp1++) = '_';
1412 else *(cp1++) = *cp2;
1416 if (*(cp1-1) == '.') cp1--; /* Unix spec ending in '/' ==> trailing '.' */
1417 if (hasdir) *(cp1++) = ']';
1418 if (*cp2) cp2++; /* check in case we ended with trailing '..' */
1419 while (*cp2) *(cp1++) = *(cp2++);
1424 } /* end of do_tovmsspec() */
1426 /* External entry points */
1427 char *tovmsspec(char *path, char *buf) { return do_tovmsspec(path,buf,0); }
1428 char *tovmsspec_ts(char *path, char *buf) { return do_tovmsspec(path,buf,1); }
1430 /*{{{ char *tovmspath[_ts](char *path, char *buf)*/
1431 static char *do_tovmspath(char *path, char *buf, int ts) {
1432 static char __tovmspath_retbuf[NAM$C_MAXRSS+1];
1434 char pathified[NAM$C_MAXRSS+1], vmsified[NAM$C_MAXRSS+1], *cp;
1436 if (path == NULL) return NULL;
1437 if (do_pathify_dirspec(path,pathified,0) == NULL) return NULL;
1438 if (do_tovmsspec(pathified,buf ? buf : vmsified,0) == NULL) return NULL;
1439 if (buf) return buf;
1441 vmslen = strlen(vmsified);
1442 New(7017,cp,vmslen+1,char);
1443 memcpy(cp,vmsified,vmslen);
1448 strcpy(__tovmspath_retbuf,vmsified);
1449 return __tovmspath_retbuf;
1452 } /* end of do_tovmspath() */
1454 /* External entry points */
1455 char *tovmspath(char *path, char *buf) { return do_tovmspath(path,buf,0); }
1456 char *tovmspath_ts(char *path, char *buf) { return do_tovmspath(path,buf,1); }
1459 /*{{{ char *tounixpath[_ts](char *path, char *buf)*/
1460 static char *do_tounixpath(char *path, char *buf, int ts) {
1461 static char __tounixpath_retbuf[NAM$C_MAXRSS+1];
1463 char pathified[NAM$C_MAXRSS+1], unixified[NAM$C_MAXRSS+1], *cp;
1465 if (path == NULL) return NULL;
1466 if (do_pathify_dirspec(path,pathified,0) == NULL) return NULL;
1467 if (do_tounixspec(pathified,buf ? buf : unixified,0) == NULL) return NULL;
1468 if (buf) return buf;
1470 unixlen = strlen(unixified);
1471 New(7017,cp,unixlen+1,char);
1472 memcpy(cp,unixified,unixlen);
1477 strcpy(__tounixpath_retbuf,unixified);
1478 return __tounixpath_retbuf;
1481 } /* end of do_tounixpath() */
1483 /* External entry points */
1484 char *tounixpath(char *path, char *buf) { return do_tounixpath(path,buf,0); }
1485 char *tounixpath_ts(char *path, char *buf) { return do_tounixpath(path,buf,1); }
1488 * @(#)argproc.c 2.2 94/08/16 Mark Pizzolato (mark@infocomm.com)
1490 *****************************************************************************
1492 * Copyright (C) 1989-1994 by *
1493 * Mark Pizzolato - INFO COMM, Danville, California (510) 837-5600 *
1495 * Permission is hereby granted for the reproduction of this software, *
1496 * on condition that this copyright notice is included in the reproduction, *
1497 * and that such reproduction is not for purposes of profit or material *
1500 * 27-Aug-1994 Modified for inclusion in perl5 *
1501 * by Charles Bailey bailey@genetics.upenn.edu *
1502 *****************************************************************************
1506 * getredirection() is intended to aid in porting C programs
1507 * to VMS (Vax-11 C). The native VMS environment does not support
1508 * '>' and '<' I/O redirection, or command line wild card expansion,
1509 * or a command line pipe mechanism using the '|' AND background
1510 * command execution '&'. All of these capabilities are provided to any
1511 * C program which calls this procedure as the first thing in the
1513 * The piping mechanism will probably work with almost any 'filter' type
1514 * of program. With suitable modification, it may useful for other
1515 * portability problems as well.
1517 * Author: Mark Pizzolato mark@infocomm.com
1521 struct list_item *next;
1525 static void add_item(struct list_item **head,
1526 struct list_item **tail,
1530 static void expand_wild_cards(char *item,
1531 struct list_item **head,
1532 struct list_item **tail,
1535 static int background_process(int argc, char **argv);
1537 static void pipe_and_fork(char **cmargv);
1539 /*{{{ void getredirection(int *ac, char ***av)*/
1541 getredirection(int *ac, char ***av)
1543 * Process vms redirection arg's. Exit if any error is seen.
1544 * If getredirection() processes an argument, it is erased
1545 * from the vector. getredirection() returns a new argc and argv value.
1546 * In the event that a background command is requested (by a trailing "&"),
1547 * this routine creates a background subprocess, and simply exits the program.
1549 * Warning: do not try to simplify the code for vms. The code
1550 * presupposes that getredirection() is called before any data is
1551 * read from stdin or written to stdout.
1553 * Normal usage is as follows:
1559 * getredirection(&argc, &argv);
1563 int argc = *ac; /* Argument Count */
1564 char **argv = *av; /* Argument Vector */
1565 char *ap; /* Argument pointer */
1566 int j; /* argv[] index */
1567 int item_count = 0; /* Count of Items in List */
1568 struct list_item *list_head = 0; /* First Item in List */
1569 struct list_item *list_tail; /* Last Item in List */
1570 char *in = NULL; /* Input File Name */
1571 char *out = NULL; /* Output File Name */
1572 char *outmode = "w"; /* Mode to Open Output File */
1573 char *err = NULL; /* Error File Name */
1574 char *errmode = "w"; /* Mode to Open Error File */
1575 int cmargc = 0; /* Piped Command Arg Count */
1576 char **cmargv = NULL;/* Piped Command Arg Vector */
1579 * First handle the case where the last thing on the line ends with
1580 * a '&'. This indicates the desire for the command to be run in a
1581 * subprocess, so we satisfy that desire.
1584 if (0 == strcmp("&", ap))
1585 exit(background_process(--argc, argv));
1586 if (*ap && '&' == ap[strlen(ap)-1])
1588 ap[strlen(ap)-1] = '\0';
1589 exit(background_process(argc, argv));
1592 * Now we handle the general redirection cases that involve '>', '>>',
1593 * '<', and pipes '|'.
1595 for (j = 0; j < argc; ++j)
1597 if (0 == strcmp("<", argv[j]))
1601 fprintf(stderr,"No input file after < on command line");
1602 exit(LIB$_WRONUMARG);
1607 if ('<' == *(ap = argv[j]))
1612 if (0 == strcmp(">", ap))
1616 fprintf(stderr,"No output file after > on command line");
1617 exit(LIB$_WRONUMARG);
1636 fprintf(stderr,"No output file after > or >> on command line");
1637 exit(LIB$_WRONUMARG);
1641 if (('2' == *ap) && ('>' == ap[1]))
1658 fprintf(stderr,"No output file after 2> or 2>> on command line");
1659 exit(LIB$_WRONUMARG);
1663 if (0 == strcmp("|", argv[j]))
1667 fprintf(stderr,"No command into which to pipe on command line");
1668 exit(LIB$_WRONUMARG);
1670 cmargc = argc-(j+1);
1671 cmargv = &argv[j+1];
1675 if ('|' == *(ap = argv[j]))
1683 expand_wild_cards(ap, &list_head, &list_tail, &item_count);
1686 * Allocate and fill in the new argument vector, Some Unix's terminate
1687 * the list with an extra null pointer.
1689 New(7002, argv, item_count+1, char *);
1691 for (j = 0; j < item_count; ++j, list_head = list_head->next)
1692 argv[j] = list_head->value;
1698 fprintf(stderr,"'|' and '>' may not both be specified on command line");
1699 exit(LIB$_INVARGORD);
1701 pipe_and_fork(cmargv);
1704 /* Check for input from a pipe (mailbox) */
1706 if (in == NULL && 1 == isapipe(0))
1708 char mbxname[L_tmpnam];
1710 long int dvi_item = DVI$_DEVBUFSIZ;
1711 $DESCRIPTOR(mbxnam, "");
1712 $DESCRIPTOR(mbxdevnam, "");
1714 /* Input from a pipe, reopen it in binary mode to disable */
1715 /* carriage control processing. */
1717 fgetname(stdin, mbxname,1);
1718 mbxnam.dsc$a_pointer = mbxname;
1719 mbxnam.dsc$w_length = strlen(mbxnam.dsc$a_pointer);
1720 lib$getdvi(&dvi_item, 0, &mbxnam, &bufsize, 0, 0);
1721 mbxdevnam.dsc$a_pointer = mbxname;
1722 mbxdevnam.dsc$w_length = sizeof(mbxname);
1723 dvi_item = DVI$_DEVNAM;
1724 lib$getdvi(&dvi_item, 0, &mbxnam, 0, &mbxdevnam, &mbxdevnam.dsc$w_length);
1725 mbxdevnam.dsc$a_pointer[mbxdevnam.dsc$w_length] = '\0';
1728 freopen(mbxname, "rb", stdin);
1731 fprintf(stderr,"Can't reopen input pipe (name: %s) in binary mode",mbxname);
1735 if ((in != NULL) && (NULL == freopen(in, "r", stdin, "mbc=32", "mbf=2")))
1737 fprintf(stderr,"Can't open input file %s as stdin",in);
1740 if ((out != NULL) && (NULL == freopen(out, outmode, stdout, "mbc=32", "mbf=2")))
1742 fprintf(stderr,"Can't open output file %s as stdout",out);
1747 if (NULL == (tmperr = fopen(err, errmode, "mbc=32", "mbf=2")))
1749 fprintf(stderr,"Can't open error file %s as stderr",err);
1753 if (NULL == freopen(err, "a", stderr, "mbc=32", "mbf=2"))
1758 #ifdef ARGPROC_DEBUG
1759 fprintf(stderr, "Arglist:\n");
1760 for (j = 0; j < *ac; ++j)
1761 fprintf(stderr, "argv[%d] = '%s'\n", j, argv[j]);
1763 } /* end of getredirection() */
1766 static void add_item(struct list_item **head,
1767 struct list_item **tail,
1773 New(7003,*head,1,struct list_item);
1777 New(7004,(*tail)->next,1,struct list_item);
1778 *tail = (*tail)->next;
1780 (*tail)->value = value;
1784 static void expand_wild_cards(char *item,
1785 struct list_item **head,
1786 struct list_item **tail,
1790 unsigned long int context = 0;
1796 char vmsspec[NAM$C_MAXRSS+1];
1797 $DESCRIPTOR(filespec, "");
1798 $DESCRIPTOR(defaultspec, "SYS$DISK:[]");
1799 $DESCRIPTOR(resultspec, "");
1800 unsigned long int zero = 0, sts;
1802 if (strcspn(item, "*%") == strlen(item))
1804 add_item(head, tail, item, count);
1807 resultspec.dsc$b_dtype = DSC$K_DTYPE_T;
1808 resultspec.dsc$b_class = DSC$K_CLASS_D;
1809 resultspec.dsc$a_pointer = NULL;
1810 if ((isunix = (int) strchr(item,'/')) != (int) NULL)
1811 filespec.dsc$a_pointer = do_tovmsspec(item,vmsspec,0);
1812 if (!isunix || !filespec.dsc$a_pointer)
1813 filespec.dsc$a_pointer = item;
1814 filespec.dsc$w_length = strlen(filespec.dsc$a_pointer);
1816 * Only return version specs, if the caller specified a version
1818 had_version = strchr(item, ';');
1820 * Only return device and directory specs, if the caller specifed either.
1822 had_device = strchr(item, ':');
1823 had_directory = (isunix || NULL != strchr(item, '[')) || (NULL != strchr(item, '<'));
1825 while (1 == (1 & (sts = lib$find_file(&filespec, &resultspec, &context,
1826 &defaultspec, 0, 0, &zero))))
1831 New(7005,string,resultspec.dsc$w_length+1,char);
1832 strncpy(string, resultspec.dsc$a_pointer, resultspec.dsc$w_length);
1833 string[resultspec.dsc$w_length] = '\0';
1834 if (NULL == had_version)
1835 *((char *)strrchr(string, ';')) = '\0';
1836 if ((!had_directory) && (had_device == NULL))
1838 if (NULL == (devdir = strrchr(string, ']')))
1839 devdir = strrchr(string, '>');
1840 strcpy(string, devdir + 1);
1843 * Be consistent with what the C RTL has already done to the rest of
1844 * the argv items and lowercase all of these names.
1846 for (c = string; *c; ++c)
1849 if (isunix) trim_unixpath(string,item);
1850 add_item(head, tail, string, count);
1853 if (sts != RMS$_NMF)
1855 set_vaxc_errno(sts);
1860 set_errno(ENOENT); break;
1862 set_errno(ENODEV); break;
1864 set_errno(EINVAL); break;
1866 set_errno(EACCES); break;
1872 add_item(head, tail, item, count);
1873 _ckvmssts(lib$sfree1_dd(&resultspec));
1874 _ckvmssts(lib$find_file_end(&context));
1877 static int child_st[2];/* Event Flag set when child process completes */
1879 static unsigned short child_chan;/* I/O Channel for Pipe Mailbox */
1881 static unsigned long int exit_handler(int *status)
1885 if (0 == child_st[0])
1887 #ifdef ARGPROC_DEBUG
1888 fprintf(stderr, "Waiting for Child Process to Finish . . .\n");
1890 fflush(stdout); /* Have to flush pipe for binary data to */
1891 /* terminate properly -- <tp@mccall.com> */
1892 sys$qiow(0, child_chan, IO$_WRITEOF, iosb, 0, 0, 0, 0, 0, 0, 0, 0);
1893 sys$dassgn(child_chan);
1895 sys$synch(0, child_st);
1900 static void sig_child(int chan)
1902 #ifdef ARGPROC_DEBUG
1903 fprintf(stderr, "Child Completion AST\n");
1905 if (child_st[0] == 0)
1909 static struct exit_control_block exit_block =
1914 &exit_block.exit_status,
1918 static void pipe_and_fork(char **cmargv)
1921 $DESCRIPTOR(cmddsc, "");
1922 static char mbxname[64];
1923 $DESCRIPTOR(mbxdsc, mbxname);
1925 unsigned long int zero = 0, one = 1;
1927 strcpy(subcmd, cmargv[0]);
1928 for (j = 1; NULL != cmargv[j]; ++j)
1930 strcat(subcmd, " \"");
1931 strcat(subcmd, cmargv[j]);
1932 strcat(subcmd, "\"");
1934 cmddsc.dsc$a_pointer = subcmd;
1935 cmddsc.dsc$w_length = strlen(cmddsc.dsc$a_pointer);
1937 create_mbx(&child_chan,&mbxdsc);
1938 #ifdef ARGPROC_DEBUG
1939 fprintf(stderr, "Pipe Mailbox Name = '%s'\n", mbxdsc.dsc$a_pointer);
1940 fprintf(stderr, "Sub Process Command = '%s'\n", cmddsc.dsc$a_pointer);
1942 _ckvmssts(lib$spawn(&cmddsc, &mbxdsc, 0, &one,
1943 0, &pid, child_st, &zero, sig_child,
1945 #ifdef ARGPROC_DEBUG
1946 fprintf(stderr, "Subprocess's Pid = %08X\n", pid);
1948 sys$dclexh(&exit_block);
1949 if (NULL == freopen(mbxname, "wb", stdout))
1951 fprintf(stderr,"Can't open output pipe (name %s)",mbxname);
1955 static int background_process(int argc, char **argv)
1957 char command[2048] = "$";
1958 $DESCRIPTOR(value, "");
1959 static $DESCRIPTOR(cmd, "BACKGROUND$COMMAND");
1960 static $DESCRIPTOR(null, "NLA0:");
1961 static $DESCRIPTOR(pidsymbol, "SHELL_BACKGROUND_PID");
1963 $DESCRIPTOR(pidstr, "");
1965 unsigned long int flags = 17, one = 1, retsts;
1967 strcat(command, argv[0]);
1970 strcat(command, " \"");
1971 strcat(command, *(++argv));
1972 strcat(command, "\"");
1974 value.dsc$a_pointer = command;
1975 value.dsc$w_length = strlen(value.dsc$a_pointer);
1976 _ckvmssts(lib$set_symbol(&cmd, &value));
1977 retsts = lib$spawn(&cmd, &null, 0, &flags, 0, &pid);
1978 if (retsts == 0x38250) { /* DCL-W-NOTIFY - We must be BATCH, so retry */
1979 _ckvmssts(lib$spawn(&cmd, &null, 0, &one, 0, &pid));
1984 #ifdef ARGPROC_DEBUG
1985 fprintf(stderr, "%s\n", command);
1987 sprintf(pidstring, "%08X", pid);
1988 fprintf(stderr, "%s\n", pidstring);
1989 pidstr.dsc$a_pointer = pidstring;
1990 pidstr.dsc$w_length = strlen(pidstr.dsc$a_pointer);
1991 lib$set_symbol(&pidsymbol, &pidstr);
1995 /***** End of code taken from Mark Pizzolato's argproc.c package *****/
1998 * Trim Unix-style prefix off filespec, so it looks like what a shell
1999 * glob expansion would return (i.e. from specified prefix on, not
2000 * full path). Note that returned filespec is Unix-style, regardless
2001 * of whether input filespec was VMS-style or Unix-style.
2003 * fspec is filespec to be trimmed, and wildspec is wildcard spec used to
2004 * determine prefix (both may be in VMS or Unix syntax).
2006 * Returns !=0 on success, with trimmed filespec replacing contents of
2007 * fspec, and 0 on failure, with contents of fpsec unchanged.
2009 /*{{{int trim_unixpath(char *fspec, char *wildspec)*/
2011 trim_unixpath(char *fspec, char *wildspec)
2013 char unixified[NAM$C_MAXRSS+1], unixwild[NAM$C_MAXRSS+1],
2014 *template, *base, *cp1, *cp2;
2015 register int tmplen, reslen = 0;
2017 if (!wildspec || !fspec) return 0;
2018 if (strpbrk(wildspec,"]>:") != NULL) {
2019 if (do_tounixspec(wildspec,unixwild,0) == NULL) return 0;
2020 else template = unixified;
2022 else template = wildspec;
2023 if (strpbrk(fspec,"]>:") != NULL) {
2024 if (do_tounixspec(fspec,unixified,0) == NULL) return 0;
2025 else base = unixified;
2026 /* reslen != 0 ==> we had to unixify resultant filespec, so we must
2027 * check to see that final result fits into (isn't longer than) fspec */
2028 reslen = strlen(fspec);
2032 /* No prefix or absolute path on wildcard, so nothing to remove */
2033 if (!*template || *template == '/') {
2034 if (base == fspec) return 1;
2035 tmplen = strlen(unixified);
2036 if (tmplen > reslen) return 0; /* not enough space */
2037 /* Copy unixified resultant, including trailing NUL */
2038 memmove(fspec,unixified,tmplen+1);
2042 /* Find prefix to template consisting of path elements without wildcards */
2043 if ((cp1 = strpbrk(template,"*%?")) == NULL)
2044 for (cp1 = template; *cp1; cp1++) ;
2045 else while (cp1 > template && *cp1 != '/') cp1--;
2046 for (cp2 = base; *cp2; cp2++) ; /* Find end of resultant filespec */
2048 /* Wildcard was in first element, so we don't have a reliable string to
2049 * match against. Guess where to trim resultant filespec by counting
2050 * directory levels in the Unix template. (We could do this instead of
2051 * string matching in all cases, since Unix doesn't have a ... wildcard
2052 * that can expand into multiple levels of subdirectory, but we try for
2053 * the string match so our caller can interpret foo/.../bar.* as
2054 * [.foo...]bar.* if it wants, and only get burned if there was a
2055 * wildcard in the first word (in which case, caveat caller). */
2056 if (cp1 == template) {
2058 for ( ; *cp1; cp1++) if (*cp1 == '/') subdirs++;
2059 /* need to back one more '/' than in template, to pick up leading dirname */
2061 while (cp2 > base) {
2062 if (*cp2 == '/') subdirs--;
2063 if (!subdirs) break; /* quit without decrement when we hit last '/' */
2066 /* ran out of directories on resultant; allow for already trimmed
2067 * resultant, which hits start of string looking for leading '/' */
2068 if (subdirs && (cp2 != base || subdirs != 1)) return 0;
2069 /* Move past leading '/', if there is one */
2070 base = cp2 + (*cp2 == '/' ? 1 : 0);
2071 tmplen = strlen(base);
2072 if (reslen && tmplen > reslen) return 0; /* not enough space */
2073 memmove(fspec,base,tmplen+1); /* copy result to fspec, with trailing NUL */
2076 /* We have a prefix string of complete directory names, so we
2077 * try to find it on the resultant filespec */
2079 tmplen = cp1 - template;
2080 if (!memcmp(base,template,tmplen)) { /* Nothing before prefix; we're done */
2081 if (reslen) { /* we converted to Unix syntax; copy result over */
2082 tmplen = cp2 - base;
2083 if (tmplen > reslen) return 0; /* not enough space */
2084 memmove(fspec,base,tmplen+1); /* Copy trimmed spec + trailing NUL */
2088 for ( ; cp2 - base > tmplen; base++) {
2089 if (*base != '/') continue;
2090 if (!memcmp(base + 1,template,tmplen)) break;
2093 if (cp2 - base == tmplen) return 0; /* Not there - not good */
2094 base++; /* Move past leading '/' */
2095 if (reslen && cp2 - base > reslen) return 0; /* not enough space */
2096 /* Copy down remaining portion of filespec, including trailing NUL */
2097 memmove(fspec,base,cp2 - base + 1);
2101 } /* end of trim_unixpath() */
2106 * VMS readdir() routines.
2107 * Written by Rich $alz, <rsalz@bbn.com> in August, 1990.
2108 * This code has no copyright.
2110 * 21-Jul-1994 Charles Bailey bailey@genetics.upenn.edu
2111 * Minor modifications to original routines.
2114 /* Number of elements in vms_versions array */
2115 #define VERSIZE(e) (sizeof e->vms_versions / sizeof e->vms_versions[0])
2118 * Open a directory, return a handle for later use.
2120 /*{{{ DIR *opendir(char*name) */
2125 char dir[NAM$C_MAXRSS+1];
2127 /* Get memory for the handle, and the pattern. */
2129 if (do_tovmspath(name,dir,0) == NULL) {
2130 Safefree((char *)dd);
2133 New(7007,dd->pattern,strlen(dir)+sizeof "*.*" + 1,char);
2135 /* Fill in the fields; mainly playing with the descriptor. */
2136 (void)sprintf(dd->pattern, "%s*.*",dir);
2139 dd->vms_wantversions = 0;
2140 dd->pat.dsc$a_pointer = dd->pattern;
2141 dd->pat.dsc$w_length = strlen(dd->pattern);
2142 dd->pat.dsc$b_dtype = DSC$K_DTYPE_T;
2143 dd->pat.dsc$b_class = DSC$K_CLASS_S;
2146 } /* end of opendir() */
2150 * Set the flag to indicate we want versions or not.
2152 /*{{{ void vmsreaddirversions(DIR *dd, int flag)*/
2154 vmsreaddirversions(DIR *dd, int flag)
2156 dd->vms_wantversions = flag;
2161 * Free up an opened directory.
2163 /*{{{ void closedir(DIR *dd)*/
2167 (void)lib$find_file_end(&dd->context);
2168 Safefree(dd->pattern);
2169 Safefree((char *)dd);
2174 * Collect all the version numbers for the current file.
2180 struct dsc$descriptor_s pat;
2181 struct dsc$descriptor_s res;
2183 char *p, *text, buff[sizeof dd->entry.d_name];
2185 unsigned long context, tmpsts;
2187 /* Convenient shorthand. */
2190 /* Add the version wildcard, ignoring the "*.*" put on before */
2191 i = strlen(dd->pattern);
2192 New(7008,text,i + e->d_namlen + 3,char);
2193 (void)strcpy(text, dd->pattern);
2194 (void)sprintf(&text[i - 3], "%s;*", e->d_name);
2196 /* Set up the pattern descriptor. */
2197 pat.dsc$a_pointer = text;
2198 pat.dsc$w_length = i + e->d_namlen - 1;
2199 pat.dsc$b_dtype = DSC$K_DTYPE_T;
2200 pat.dsc$b_class = DSC$K_CLASS_S;
2202 /* Set up result descriptor. */
2203 res.dsc$a_pointer = buff;
2204 res.dsc$w_length = sizeof buff - 2;
2205 res.dsc$b_dtype = DSC$K_DTYPE_T;
2206 res.dsc$b_class = DSC$K_CLASS_S;
2208 /* Read files, collecting versions. */
2209 for (context = 0, e->vms_verscount = 0;
2210 e->vms_verscount < VERSIZE(e);
2211 e->vms_verscount++) {
2212 tmpsts = lib$find_file(&pat, &res, &context);
2213 if (tmpsts == RMS$_NMF || context == 0) break;
2215 buff[sizeof buff - 1] = '\0';
2216 if ((p = strchr(buff, ';')))
2217 e->vms_versions[e->vms_verscount] = atoi(p + 1);
2219 e->vms_versions[e->vms_verscount] = -1;
2222 _ckvmssts(lib$find_file_end(&context));
2225 } /* end of collectversions() */
2228 * Read the next entry from the directory.
2230 /*{{{ struct dirent *readdir(DIR *dd)*/
2234 struct dsc$descriptor_s res;
2235 char *p, buff[sizeof dd->entry.d_name];
2236 unsigned long int tmpsts;
2238 /* Set up result descriptor, and get next file. */
2239 res.dsc$a_pointer = buff;
2240 res.dsc$w_length = sizeof buff - 2;
2241 res.dsc$b_dtype = DSC$K_DTYPE_T;
2242 res.dsc$b_class = DSC$K_CLASS_S;
2243 tmpsts = lib$find_file(&dd->pat, &res, &dd->context);
2244 if ( tmpsts == RMS$_NMF || dd->context == 0) return NULL; /* None left. */
2245 if (!(tmpsts & 1)) {
2246 set_vaxc_errno(tmpsts);
2249 set_errno(EACCES); break;
2251 set_errno(ENODEV); break;
2254 set_errno(ENOENT); break;
2261 /* Force the buffer to end with a NUL, and downcase name to match C convention. */
2262 buff[sizeof buff - 1] = '\0';
2263 for (p = buff; !isspace(*p); p++) *p = _tolower(*p);
2266 /* Skip any directory component and just copy the name. */
2267 if ((p = strchr(buff, ']'))) (void)strcpy(dd->entry.d_name, p + 1);
2268 else (void)strcpy(dd->entry.d_name, buff);
2270 /* Clobber the version. */
2271 if ((p = strchr(dd->entry.d_name, ';'))) *p = '\0';
2273 dd->entry.d_namlen = strlen(dd->entry.d_name);
2274 dd->entry.vms_verscount = 0;
2275 if (dd->vms_wantversions) collectversions(dd);
2278 } /* end of readdir() */
2282 * Return something that can be used in a seekdir later.
2284 /*{{{ long telldir(DIR *dd)*/
2293 * Return to a spot where we used to be. Brute force.
2295 /*{{{ void seekdir(DIR *dd,long count)*/
2297 seekdir(DIR *dd, long count)
2299 int vms_wantversions;
2301 /* If we haven't done anything yet... */
2305 /* Remember some state, and clear it. */
2306 vms_wantversions = dd->vms_wantversions;
2307 dd->vms_wantversions = 0;
2308 _ckvmssts(lib$find_file_end(&dd->context));
2311 /* The increment is in readdir(). */
2312 for (dd->count = 0; dd->count < count; )
2315 dd->vms_wantversions = vms_wantversions;
2317 } /* end of seekdir() */
2320 /* VMS subprocess management
2322 * my_vfork() - just a vfork(), after setting a flag to record that
2323 * the current script is trying a Unix-style fork/exec.
2325 * vms_do_aexec() and vms_do_exec() are called in response to the
2326 * perl 'exec' function. If this follows a vfork call, then they
2327 * call out the the regular perl routines in doio.c which do an
2328 * execvp (for those who really want to try this under VMS).
2329 * Otherwise, they do exactly what the perl docs say exec should
2330 * do - terminate the current script and invoke a new command
2331 * (See below for notes on command syntax.)
2333 * do_aspawn() and do_spawn() implement the VMS side of the perl
2334 * 'system' function.
2336 * Note on command arguments to perl 'exec' and 'system': When handled
2337 * in 'VMSish fashion' (i.e. not after a call to vfork) The args
2338 * are concatenated to form a DCL command string. If the first arg
2339 * begins with '$' (i.e. the perl script had "\$ Type" or some such),
2340 * the the command string is hrnded off to DCL directly. Otherwise,
2341 * the first token of the command is taken as the filespec of an image
2342 * to run. The filespec is expanded using a default type of '.EXE' and
2343 * the process defaults for device, directory, etc., and the resultant
2344 * filespec is invoked using the DCL verb 'MCR', and passed the rest of
2345 * the command string as parameters. This is perhaps a bit compicated,
2346 * but I hope it will form a happy medium between what VMS folks expect
2347 * from lib$spawn and what Unix folks expect from exec.
2350 static int vfork_called;
2352 /*{{{int my_vfork()*/
2362 static struct dsc$descriptor_s VMScmd = {0,DSC$K_DTYPE_T,DSC$K_CLASS_S,Nullch};
2370 if (VMScmd.dsc$a_pointer) {
2371 Safefree(VMScmd.dsc$a_pointer);
2372 VMScmd.dsc$w_length = 0;
2373 VMScmd.dsc$a_pointer = Nullch;
2378 setup_argstr(SV *really, SV **mark, SV **sp)
2380 char *junk, *tmps = Nullch;
2381 register size_t cmdlen = 0;
2387 tmps = SvPV(really,rlen);
2394 for (idx++; idx <= sp; idx++) {
2396 junk = SvPVx(*idx,rlen);
2397 cmdlen += rlen ? rlen + 1 : 0;
2400 New(401,Cmd,cmdlen+1,char);
2402 if (tmps && *tmps) {
2407 while (++mark <= sp) {
2410 strcat(Cmd,SvPVx(*mark,na));
2415 } /* end of setup_argstr() */
2418 static unsigned long int
2419 setup_cmddsc(char *cmd, int check_img)
2421 char resspec[NAM$C_MAXRSS+1];
2422 $DESCRIPTOR(defdsc,".EXE");
2423 $DESCRIPTOR(resdsc,resspec);
2424 struct dsc$descriptor_s imgdsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};
2425 unsigned long int cxt = 0, flags = 1, retsts;
2426 register char *s, *rest, *cp;
2427 register int isdcl = 0;
2430 while (*s && isspace(*s)) s++;
2432 if (*s == '$') { /* Check whether this is a DCL command: leading $ and */
2433 isdcl = 1; /* no dev/dir separators (i.e. not a foreign command) */
2434 for (cp = s; *cp && *cp != '/' && !isspace(*cp); cp++) {
2435 if (*cp == ':' || *cp == '[' || *cp == '<') {
2443 if (isdcl) { /* It's a DCL command, just do it. */
2444 VMScmd.dsc$w_length = strlen(cmd);
2446 VMScmd.dsc$a_pointer = Cmd;
2447 Cmd = Nullch; /* Don't try to free twice in vms_execfree() */
2449 else VMScmd.dsc$a_pointer = savepvn(cmd,VMScmd.dsc$w_length);
2451 else { /* assume first token is an image spec */
2453 while (*s && !isspace(*s)) s++;
2455 imgdsc.dsc$a_pointer = cmd;
2456 imgdsc.dsc$w_length = s - cmd;
2457 retsts = lib$find_file(&imgdsc,&resdsc,&cxt,&defdsc,0,0,&flags);
2458 if (!(retsts & 1)) {
2459 /* just hand off status values likely to be due to user error */
2460 if (retsts == RMS$_FNF || retsts == RMS$_DNF ||
2461 retsts == RMS$_DEV || retsts == RMS$_DIR || retsts == RMS$_SYN ||
2462 (retsts & STS$M_CODE) == (SHR$_NOWILD & STS$M_CODE)) return retsts;
2463 else { _ckvmssts(retsts); }
2466 _ckvmssts(lib$find_file_end(&cxt));
2468 while (*s && !isspace(*s)) s++;
2470 New(402,VMScmd.dsc$a_pointer,7 + s - resspec + (rest ? strlen(rest) : 0),char);
2471 strcpy(VMScmd.dsc$a_pointer,"$ MCR ");
2472 strcat(VMScmd.dsc$a_pointer,resspec);
2473 if (rest) strcat(VMScmd.dsc$a_pointer,rest);
2474 VMScmd.dsc$w_length = strlen(VMScmd.dsc$a_pointer);
2478 return (VMScmd.dsc$w_length > 255 ? CLI$_BUFOVF : SS$_NORMAL);
2480 } /* end of setup_cmddsc() */
2483 /* {{{ bool vms_do_aexec(SV *really,SV **mark,SV **sp) */
2485 vms_do_aexec(SV *really,SV **mark,SV **sp)
2488 if (vfork_called) { /* this follows a vfork - act Unixish */
2490 if (vfork_called < 0) {
2491 warn("Internal inconsistency in tracking vforks");
2494 else return do_aexec(really,mark,sp);
2496 /* no vfork - act VMSish */
2497 return vms_do_exec(setup_argstr(really,mark,sp));
2502 } /* end of vms_do_aexec() */
2505 /* {{{bool vms_do_exec(char *cmd) */
2507 vms_do_exec(char *cmd)
2510 if (vfork_called) { /* this follows a vfork - act Unixish */
2512 if (vfork_called < 0) {
2513 warn("Internal inconsistency in tracking vforks");
2516 else return do_exec(cmd);
2519 { /* no vfork - act VMSish */
2520 unsigned long int retsts;
2522 if ((retsts = setup_cmddsc(cmd,1)) & 1)
2523 retsts = lib$do_command(&VMScmd);
2526 set_vaxc_errno(retsts);
2528 warn("Can't exec \"%s\": %s", VMScmd.dsc$a_pointer, Strerror(errno));
2534 } /* end of vms_do_exec() */
2537 unsigned long int do_spawn(char *);
2539 /* {{{ unsigned long int do_aspawn(SV *really,SV **mark,SV **sp) */
2541 do_aspawn(SV *really,SV **mark,SV **sp)
2543 if (sp > mark) return do_spawn(setup_argstr(really,mark,sp));
2546 } /* end of do_aspawn() */
2549 /* {{{unsigned long int do_spawn(char *cmd) */
2553 unsigned long int substs, hadcmd = 1;
2555 if (!cmd || !*cmd) {
2557 _ckvmssts(lib$spawn(0,0,0,0,0,0,&substs,0,0,0,0,0,0));
2559 else if ((substs = setup_cmddsc(cmd,0)) & 1) {
2560 _ckvmssts(lib$spawn(&VMScmd,0,0,0,0,0,&substs,0,0,0,0,0,0));
2565 set_vaxc_errno(substs);
2567 warn("Can't spawn \"%s\": %s",
2568 hadcmd ? VMScmd.dsc$a_pointer : "", Strerror(errno));
2573 } /* end of do_spawn() */
2577 * A simple fwrite replacement which outputs itmsz*nitm chars without
2578 * introducing record boundaries every itmsz chars.
2580 /*{{{ int my_fwrite(void *src, size_t itmsz, size_t nitm, FILE *dest)*/
2582 my_fwrite(void *src, size_t itmsz, size_t nitm, FILE *dest)
2584 register char *cp, *end;
2586 end = (char *)src + itmsz * nitm;
2588 while ((char *)src <= end) {
2589 for (cp = src; cp <= end; cp++) if (!*cp) break;
2590 if (fputs(src,dest) == EOF) return EOF;
2592 if (fputc('\0',dest) == EOF) return EOF;
2598 } /* end of my_fwrite() */
2602 * Here are replacements for the following Unix routines in the VMS environment:
2603 * getpwuid Get information for a particular UIC or UID
2604 * getpwnam Get information for a named user
2605 * getpwent Get information for each user in the rights database
2606 * setpwent Reset search to the start of the rights database
2607 * endpwent Finish searching for users in the rights database
2609 * getpwuid, getpwnam, and getpwent return a pointer to the passwd structure
2610 * (defined in pwd.h), which contains the following fields:-
2612 * char *pw_name; Username (in lower case)
2613 * char *pw_passwd; Hashed password
2614 * unsigned int pw_uid; UIC
2615 * unsigned int pw_gid; UIC group number
2616 * char *pw_unixdir; Default device/directory (VMS-style)
2617 * char *pw_gecos; Owner name
2618 * char *pw_dir; Default device/directory (Unix-style)
2619 * char *pw_shell; Default CLI name (eg. DCL)
2621 * If the specified user does not exist, getpwuid and getpwnam return NULL.
2623 * pw_uid is the full UIC (eg. what's returned by stat() in st_uid).
2624 * not the UIC member number (eg. what's returned by getuid()),
2625 * getpwuid() can accept either as input (if uid is specified, the caller's
2626 * UIC group is used), though it won't recognise gid=0.
2628 * Note that in VMS it is necessary to have GRPPRV or SYSPRV to return
2629 * information about other users in your group or in other groups, respectively.
2630 * If the required privilege is not available, then these routines fill only
2631 * the pw_name, pw_uid, and pw_gid fields (the others point to an empty
2634 * By Tim Adye (T.J.Adye@rl.ac.uk), 10th February 1995.
2637 /* sizes of various UAF record fields */
2638 #define UAI$S_USERNAME 12
2639 #define UAI$S_IDENT 31
2640 #define UAI$S_OWNER 31
2641 #define UAI$S_DEFDEV 31
2642 #define UAI$S_DEFDIR 63
2643 #define UAI$S_DEFCLI 31
2646 #define valid_uic(uic) ((uic).uic$v_format == UIC$K_UIC_FORMAT && \
2647 (uic).uic$v_member != UIC$K_WILD_MEMBER && \
2648 (uic).uic$v_group != UIC$K_WILD_GROUP)
2650 static char __empty[]= "";
2651 static struct passwd __passwd_empty=
2652 {(char *) __empty, (char *) __empty, 0, 0,
2653 (char *) __empty, (char *) __empty, (char *) __empty, (char *) __empty};
2654 static int contxt= 0;
2655 static struct passwd __pwdcache;
2656 static char __pw_namecache[UAI$S_IDENT+1];
2658 static char *_mystrtolower(char *str)
2660 if (str) for (; *str; ++str) *str= tolower(*str);
2665 * This routine does most of the work extracting the user information.
2667 static int fillpasswd (const char *name, struct passwd *pwd)
2670 unsigned char length;
2671 char pw_gecos[UAI$S_OWNER+1];
2673 static union uicdef uic;
2675 unsigned char length;
2676 char pw_dir[UAI$S_DEFDEV+UAI$S_DEFDIR+1];
2679 unsigned char length;
2680 char unixdir[UAI$_DEFDEV+UAI$S_DEFDIR+1];
2683 unsigned char length;
2684 char pw_shell[UAI$S_DEFCLI+1];
2686 static char pw_passwd[UAI$S_PWD+1];
2688 static unsigned short lowner, luic, ldefdev, ldefdir, ldefcli, lpwd;
2689 struct dsc$descriptor_s name_desc;
2690 unsigned long int sts;
2692 static struct itmlst_3 itmlst[]= {
2693 {UAI$S_OWNER+1, UAI$_OWNER, &owner, &lowner},
2694 {sizeof(uic), UAI$_UIC, &uic, &luic},
2695 {UAI$S_DEFDEV+1, UAI$_DEFDEV, &defdev, &ldefdev},
2696 {UAI$S_DEFDIR+1, UAI$_DEFDIR, &defdir, &ldefdir},
2697 {UAI$S_DEFCLI+1, UAI$_DEFCLI, &defcli, &ldefcli},
2698 {UAI$S_PWD, UAI$_PWD, pw_passwd, &lpwd},
2699 {0, 0, NULL, NULL}};
2701 name_desc.dsc$w_length= strlen(name);
2702 name_desc.dsc$b_dtype= DSC$K_DTYPE_T;
2703 name_desc.dsc$b_class= DSC$K_CLASS_S;
2704 name_desc.dsc$a_pointer= (char *) name;
2706 /* Note that sys$getuai returns many fields as counted strings. */
2707 sts= sys$getuai(0, 0, &name_desc, &itmlst, 0, 0, 0);
2708 if (sts == SS$_NOSYSPRV || sts == SS$_NOGRPPRV || sts == RMS$_RNF) {
2709 set_vaxc_errno(sts); set_errno(sts == RMS$_RNF ? EINVAL : EACCES);
2711 else { _ckvmssts(sts); }
2712 if (!(sts & 1)) return 0; /* out here in case _ckvmssts() doesn't abort */
2714 if ((int) owner.length < lowner) lowner= (int) owner.length;
2715 if ((int) defdev.length < ldefdev) ldefdev= (int) defdev.length;
2716 if ((int) defdir.length < ldefdir) ldefdir= (int) defdir.length;
2717 if ((int) defcli.length < ldefcli) ldefcli= (int) defcli.length;
2718 memcpy(&defdev.pw_dir[ldefdev], &defdir.unixdir[0], ldefdir);
2719 owner.pw_gecos[lowner]= '\0';
2720 defdev.pw_dir[ldefdev+ldefdir]= '\0';
2721 defcli.pw_shell[ldefcli]= '\0';
2722 if (valid_uic(uic)) {
2723 pwd->pw_uid= uic.uic$l_uic;
2724 pwd->pw_gid= uic.uic$v_group;
2727 warn("getpwnam returned invalid UIC %#o for user \"%s\"");
2728 pwd->pw_passwd= pw_passwd;
2729 pwd->pw_gecos= owner.pw_gecos;
2730 pwd->pw_dir= defdev.pw_dir;
2731 pwd->pw_unixdir= do_tounixpath(defdev.pw_dir, defdir.unixdir,1);
2732 pwd->pw_shell= defcli.pw_shell;
2733 if (pwd->pw_unixdir && pwd->pw_unixdir[0]) {
2735 ldir= strlen(pwd->pw_unixdir) - 1;
2736 if (pwd->pw_unixdir[ldir]=='/') pwd->pw_unixdir[ldir]= '\0';
2739 strcpy(pwd->pw_unixdir, pwd->pw_dir);
2740 _mystrtolower(pwd->pw_unixdir);
2745 * Get information for a named user.
2747 /*{{{struct passwd *getpwnam(char *name)*/
2748 struct passwd *my_getpwnam(char *name)
2750 struct dsc$descriptor_s name_desc;
2752 unsigned long int status, stat;
2754 __pwdcache = __passwd_empty;
2755 if (!fillpasswd(name, &__pwdcache)) {
2756 /* We still may be able to determine pw_uid and pw_gid */
2757 name_desc.dsc$w_length= strlen(name);
2758 name_desc.dsc$b_dtype= DSC$K_DTYPE_T;
2759 name_desc.dsc$b_class= DSC$K_CLASS_S;
2760 name_desc.dsc$a_pointer= (char *) name;
2761 if ((stat = sys$asctoid(&name_desc, &uic, 0)) == SS$_NORMAL) {
2762 __pwdcache.pw_uid= uic.uic$l_uic;
2763 __pwdcache.pw_gid= uic.uic$v_group;
2766 if (stat == SS$_NOSUCHID || stat == SS$_IVIDENT || stat == RMS$_PRV) {
2767 set_vaxc_errno(stat);
2768 set_errno(stat == RMS$_PRV ? EACCES : EINVAL);
2771 else { _ckvmssts(stat); }
2774 strncpy(__pw_namecache, name, sizeof(__pw_namecache));
2775 __pw_namecache[sizeof __pw_namecache - 1] = '\0';
2776 __pwdcache.pw_name= __pw_namecache;
2778 } /* end of my_getpwnam() */
2782 * Get information for a particular UIC or UID.
2783 * Called by my_getpwent with uid=-1 to list all users.
2785 /*{{{struct passwd *my_getpwuid(Uid_t uid)*/
2786 struct passwd *my_getpwuid(Uid_t uid)
2788 const $DESCRIPTOR(name_desc,__pw_namecache);
2789 unsigned short lname;
2791 unsigned long int status;
2793 if (uid == (unsigned int) -1) {
2795 status = sys$idtoasc(-1, &lname, &name_desc, &uic, 0, &contxt);
2796 if (status == SS$_NOSUCHID || status == RMS$_PRV) {
2797 set_vaxc_errno(status);
2798 set_errno(status == RMS$_PRV ? EACCES : EINVAL);
2802 else { _ckvmssts(status); }
2803 } while (!valid_uic (uic));
2807 if (!uic.uic$v_group)
2808 uic.uic$v_group= getgid();
2810 status = sys$idtoasc(uic.uic$l_uic, &lname, &name_desc, 0, 0, 0);
2811 else status = SS$_IVIDENT;
2812 if (status == SS$_IVIDENT || status == SS$_NOSUCHID ||
2813 status == RMS$_PRV) {
2814 set_vaxc_errno(status); set_errno(status == RMS$_PRV ? EACCES : EINVAL);
2817 else { _ckvmssts(status); }
2819 __pw_namecache[lname]= '\0';
2820 _mystrtolower(__pw_namecache);
2822 __pwdcache = __passwd_empty;
2823 __pwdcache.pw_name = __pw_namecache;
2825 /* Fill in the uid and gid in case fillpasswd can't (eg. no privilege).
2826 The identifier's value is usually the UIC, but it doesn't have to be,
2827 so if we can, we let fillpasswd update this. */
2828 __pwdcache.pw_uid = uic.uic$l_uic;
2829 __pwdcache.pw_gid = uic.uic$v_group;
2831 fillpasswd(__pw_namecache, &__pwdcache);
2834 } /* end of my_getpwuid() */
2838 * Get information for next user.
2840 /*{{{struct passwd *my_getpwent()*/
2841 struct passwd *my_getpwent()
2843 return (my_getpwuid((unsigned int) -1));
2848 * Finish searching rights database for users.
2850 /*{{{void my_endpwent()*/
2854 _ckvmssts(sys$finish_rdb(&contxt));
2862 * If the CRTL has a real gmtime(), use it, else look for the logical
2863 * name SYS$TIMEZONE_DIFFERENTIAL used by the native UTC routines on
2864 * VMS >= 6.0. Can be manually defined under earlier versions of VMS
2865 * to translate to the number of seconds which must be added to UTC
2866 * to get to the local time of the system.
2867 * Contributed by Chuck Lane <lane@duphy4.physics.drexel.edu>
2870 /*{{{struct tm *my_gmtime(const time_t *time)*/
2871 /* We #defined 'gmtime' as 'my_gmtime' in vmsish.h. #undef it here
2872 * so we can call the CRTL's routine to see if it works.
2876 my_gmtime(const time_t *time)
2878 static int gmtime_emulation_type;
2879 static time_t utc_offset_secs;
2883 if (gmtime_emulation_type == 0) {
2884 gmtime_emulation_type++;
2886 if (gmtime(&when) == NULL) { /* CRTL gmtime() is just a stub */
2887 gmtime_emulation_type++;
2888 if ((p = my_getenv("SYS$TIMEZONE_DIFFERENTIAL")) == NULL)
2889 gmtime_emulation_type++;
2891 utc_offset_secs = (time_t) atol(p);
2895 switch (gmtime_emulation_type) {
2897 return gmtime(time);
2899 when = *time - utc_offset_secs;
2900 return localtime(&when);
2902 warn("gmtime not supported on this system");
2905 } /* end of my_gmtime() */
2906 /* Reset definition for later calls */
2907 #define gmtime(t) my_gmtime(t)
2912 * flex_stat, flex_fstat
2913 * basic stat, but gets it right when asked to stat
2914 * a Unix-style path ending in a directory name (e.g. dir1/dir2/dir3)
2917 /* encode_dev packs a VMS device name string into an integer to allow
2918 * simple comparisons. This can be used, for example, to check whether two
2919 * files are located on the same device, by comparing their encoded device
2920 * names. Even a string comparison would not do, because stat() reuses the
2921 * device name buffer for each call; so without encode_dev, it would be
2922 * necessary to save the buffer and use strcmp (this would mean a number of
2923 * changes to the standard Perl code, to say nothing of what a Perl script
2926 * The device lock id, if it exists, should be unique (unless perhaps compared
2927 * with lock ids transferred from other nodes). We have a lock id if the disk is
2928 * mounted cluster-wide, which is when we tend to get long (host-qualified)
2929 * device names. Thus we use the lock id in preference, and only if that isn't
2930 * available, do we try to pack the device name into an integer (flagged by
2931 * the sign bit (LOCKID_MASK) being set).
2933 * Note that encode_dev cannot guarantee an 1-to-1 correspondence twixt device
2934 * name and its encoded form, but it seems very unlikely that we will find
2935 * two files on different disks that share the same encoded device names,
2936 * and even more remote that they will share the same file id (if the test
2937 * is to check for the same file).
2939 * A better method might be to use sys$device_scan on the first call, and to
2940 * search for the device, returning an index into the cached array.
2941 * The number returned would be more intelligable.
2942 * This is probably not worth it, and anyway would take quite a bit longer
2943 * on the first call.
2945 #define LOCKID_MASK 0x80000000 /* Use 0 to force device name use only */
2946 static dev_t encode_dev (const char *dev)
2949 unsigned long int f;
2954 if (!dev || !dev[0]) return 0;
2958 struct dsc$descriptor_s dev_desc;
2959 unsigned long int status, lockid, item = DVI$_LOCKID;
2961 /* For cluster-mounted disks, the disk lock identifier is unique, so we
2962 can try that first. */
2963 dev_desc.dsc$w_length = strlen (dev);
2964 dev_desc.dsc$b_dtype = DSC$K_DTYPE_T;
2965 dev_desc.dsc$b_class = DSC$K_CLASS_S;
2966 dev_desc.dsc$a_pointer = (char *) dev;
2967 _ckvmssts(lib$getdvi(&item, 0, &dev_desc, &lockid, 0, 0));
2968 if (lockid) return (lockid & ~LOCKID_MASK);
2972 /* Otherwise we try to encode the device name */
2976 for (q = dev + strlen(dev); q--; q >= dev) {
2979 else if (isalpha (toupper (*q)))
2980 c= toupper (*q) - 'A' + (char)10;
2982 continue; /* Skip '$'s */
2984 if (i>6) break; /* 36^7 is too large to fit in an unsigned long int */
2986 enc += f * (unsigned long int) c;
2988 return (enc | LOCKID_MASK); /* May have already overflowed into bit 31 */
2990 } /* end of encode_dev() */
2992 static char namecache[NAM$C_MAXRSS+1];
2995 is_null_device(name)
2998 /* The VMS null device is named "_NLA0:", usually abbreviated as "NL:".
2999 The underscore prefix, controller letter, and unit number are
3000 independently optional; for our purposes, the colon punctuation
3001 is not. The colon can be trailed by optional directory and/or
3002 filename, but two consecutive colons indicates a nodename rather
3003 than a device. [pr] */
3004 if (*name == '_') ++name;
3005 if (tolower(*name++) != 'n') return 0;
3006 if (tolower(*name++) != 'l') return 0;
3007 if (tolower(*name) == 'a') ++name;
3008 if (*name == '0') ++name;
3009 return (*name++ == ':') && (*name != ':');
3012 /* Do the permissions allow some operation? Assumes statcache already set. */
3013 /* Do this via $Check_Access on VMS, since the CRTL stat() returns only a
3014 * subset of the applicable information.
3016 /*{{{I32 cando(I32 bit, I32 effective, struct stat *statbufp)*/
3018 cando(I32 bit, I32 effective, struct stat *statbufp)
3020 if (statbufp == &statcache)
3021 return cando_by_name(bit,effective,namecache);
3023 char fname[NAM$C_MAXRSS+1];
3024 unsigned long int retsts;
3025 struct dsc$descriptor_s devdsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0},
3026 namdsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};
3028 /* If the struct mystat is stale, we're OOL; stat() overwrites the
3029 device name on successive calls */
3030 devdsc.dsc$a_pointer = statbufp->st_devnam;
3031 devdsc.dsc$w_length = strlen(statbufp->st_devnam);
3032 namdsc.dsc$a_pointer = fname;
3033 namdsc.dsc$w_length = sizeof fname - 1;
3035 retsts = lib$fid_to_name(&devdsc,&(statbufp->st_ino),&namdsc,
3036 &namdsc.dsc$w_length,0,0);
3038 fname[namdsc.dsc$w_length] = '\0';
3039 return cando_by_name(bit,effective,fname);
3041 else if (retsts == SS$_NOSUCHDEV || retsts == SS$_NOSUCHFILE) {
3042 warn("Can't get filespec - stale stat buffer?\n");
3046 return FALSE; /* Should never get to here */
3048 } /* end of cando() */
3052 /*{{{I32 cando_by_name(I32 bit, I32 effective, char *fname)*/
3054 cando_by_name(I32 bit, I32 effective, char *fname)
3056 static char usrname[L_cuserid];
3057 static struct dsc$descriptor_s usrdsc =
3058 {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, usrname};
3059 char vmsname[NAM$C_MAXRSS+1], fileified[NAM$C_MAXRSS+1];
3060 unsigned long int objtyp = ACL$C_FILE, access, retsts, privused, iosb[2];
3061 unsigned short int retlen;
3062 struct dsc$descriptor_s namdsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};
3063 union prvdef curprv;
3064 struct itmlst_3 armlst[3] = {{sizeof access, CHP$_ACCESS, &access, &retlen},
3065 {sizeof privused, CHP$_PRIVUSED, &privused, &retlen},{0,0,0,0}};
3066 struct itmlst_3 jpilst[2] = {{sizeof curprv, JPI$_CURPRIV, &curprv, &retlen},
3069 if (!fname || !*fname) return FALSE;
3070 if (!do_tovmsspec(fname,vmsname,1)) return FALSE;
3071 retlen = namdsc.dsc$w_length = strlen(vmsname);
3072 namdsc.dsc$a_pointer = vmsname;
3073 if (vmsname[retlen-1] == ']' || vmsname[retlen-1] == '>' ||
3074 vmsname[retlen-1] == ':') {
3075 if (!do_fileify_dirspec(vmsname,fileified,1)) return FALSE;
3076 namdsc.dsc$w_length = strlen(fileified);
3077 namdsc.dsc$a_pointer = fileified;
3080 if (!usrdsc.dsc$w_length) {
3082 usrdsc.dsc$w_length = strlen(usrname);
3089 access = ARM$M_EXECUTE;
3094 access = ARM$M_READ;
3099 access = ARM$M_WRITE;
3104 access = ARM$M_DELETE;
3110 retsts = sys$check_access(&objtyp,&namdsc,&usrdsc,armlst);
3111 #ifndef SS$_NOSUCHOBJECT /* Older versions of ssdef.h don't have this */
3112 # define SS$_NOSUCHOBJECT 2696
3114 if (retsts == SS$_NOPRIV || retsts == SS$_NOSUCHOBJECT ||
3115 retsts == RMS$_FNF || retsts == RMS$_DIR ||
3116 retsts == RMS$_DEV) {
3117 set_errno(retsts == SS$_NOPRIV ? EACCES : ENOENT); set_vaxc_errno(retsts);
3120 if (retsts == SS$_NORMAL) {
3121 if (!privused) return TRUE;
3122 /* We can get access, but only by using privs. Do we have the
3123 necessary privs currently enabled? */
3124 _ckvmssts(sys$getjpiw(0,0,0,jpilst,iosb,0,0));
3125 if ((privused & CHP$M_BYPASS) && !curprv.prv$v_bypass) return FALSE;
3126 if ((privused & CHP$M_SYSPRV) && !curprv.prv$v_sysprv &&
3127 !curprv.prv$v_bypass) return FALSE;
3128 if ((privused & CHP$M_GRPPRV) && !curprv.prv$v_grpprv &&
3129 !curprv.prv$v_sysprv && !curprv.prv$v_bypass) return FALSE;
3130 if ((privused & CHP$M_READALL) && !curprv.prv$v_readall) return FALSE;
3135 return FALSE; /* Should never get here */
3137 } /* end of cando_by_name() */
3141 /*{{{ int flex_fstat(int fd, struct stat *statbuf)*/
3143 flex_fstat(int fd, struct stat *statbuf)
3145 char fspec[NAM$C_MAXRSS+1];
3147 if (!getname(fd,fspec,1)) return -1;
3148 return flex_stat(fspec,statbuf);
3150 } /* end of flex_fstat() */
3153 /*{{{ int flex_stat(char *fspec, struct stat *statbufp)*/
3154 /* We defined 'stat' as 'mystat' in vmsish.h so that declarations of
3155 * 'struct stat' elsewhere in Perl would use our struct. We go back
3156 * to the system version here, since we're actually calling their
3161 flex_stat(char *fspec, struct mystat *statbufp)
3163 char fileified[NAM$C_MAXRSS+1];
3164 int retval,myretval;
3165 struct mystat tmpbuf;
3168 if (statbufp == &statcache) do_tovmsspec(fspec,namecache,0);
3169 if (is_null_device(fspec)) { /* Fake a stat() for the null device */
3170 memset(statbufp,0,sizeof *statbufp);
3171 statbufp->st_dev = encode_dev("_NLA0:");
3172 statbufp->st_mode = S_IFBLK | S_IREAD | S_IWRITE | S_IEXEC;
3173 statbufp->st_uid = 0x00010001;
3174 statbufp->st_gid = 0x0001;
3175 time((time_t *)&statbufp->st_mtime);
3176 statbufp->st_atime = statbufp->st_ctime = statbufp->st_mtime;
3180 if (do_fileify_dirspec(fspec,fileified,0) == NULL) myretval = -1;
3182 myretval = stat(fileified,(stat_t *) &tmpbuf);
3184 retval = stat(fspec,(stat_t *) statbufp);
3190 else if (!retval) { /* Dir with same name. Substitute it. */
3191 statbufp->st_mode &= ~S_IFDIR;
3192 statbufp->st_mode |= tmpbuf.st_mode & S_IFDIR;
3193 strcpy(namecache,fileified);
3196 if (!retval) statbufp->st_dev = encode_dev(statbufp->st_devnam);
3199 } /* end of flex_stat() */
3200 /* Reset definition for later calls */
3204 /*{{{char *my_getlogin()*/
3205 /* VMS cuserid == Unix getlogin, except calling sequence */
3209 static char user[L_cuserid];
3210 return cuserid(user);
3215 /* rmscopy - copy a file using VMS RMS routines
3217 * Copies contents and attributes of spec_in to spec_out, except owner
3218 * and protection information. Name and type of spec_in are used as
3219 * defaults for spec_out. The third parameter specifies whether rmscopy()
3220 * should try to propagate timestamps from the input file to the output file.
3221 * If it is less than 0, no timestamps are preserved. If it is 0, then
3222 * rmscopy() will behave similarly to the DCL COPY command: timestamps are
3223 * propagated to the output file at creation iff the output file specification
3224 * did not contain an explicit name or type, and the revision date is always
3225 * updated at the end of the copy operation. If it is greater than 0, then
3226 * it is interpreted as a bitmask, in which bit 0 indicates that timestamps
3227 * other than the revision date should be propagated, and bit 1 indicates
3228 * that the revision date should be propagated.
3230 * Returns 1 on success; returns 0 and sets errno and vaxc$errno on failure.
3232 * Copyright 1996 by Charles Bailey <bailey@genetics.upenn.edu>.
3233 * Incorporates, with permission, some code from EZCOPY by Tim Adye
3234 * <T.J.Adye@rl.ac.uk>. Permission is given to use and distribute this
3235 * code under the same terms as Perl itself. (See the GNU General Public
3236 * License or the Perl Artistic License supplied as part of the Perl
3239 /*{{{int rmscopy(char *src, char *dst, int preserve_dates)*/
3241 rmscopy(char *spec_in, char *spec_out, int preserve_dates)
3243 char vmsin[NAM$C_MAXRSS+1], vmsout[NAM$C_MAXRSS+1], esa[NAM$C_MAXRSS],
3244 rsa[NAM$C_MAXRSS], ubf[32256];
3245 unsigned long int i, sts, sts2;
3246 struct FAB fab_in, fab_out;
3247 struct RAB rab_in, rab_out;
3249 struct XABDAT xabdat;
3250 struct XABFHC xabfhc;
3251 struct XABRDT xabrdt;
3252 struct XABSUM xabsum;
3254 if (!spec_in || !*spec_in || !do_tovmsspec(spec_in,vmsin,1) ||
3255 !spec_out || !*spec_out || !do_tovmsspec(spec_out,vmsout,1)) {
3256 set_errno(EINVAL); set_vaxc_errno(LIB$_INVARG);
3260 fab_in = cc$rms_fab;
3261 fab_in.fab$l_fna = vmsin;
3262 fab_in.fab$b_fns = strlen(vmsin);
3263 fab_in.fab$b_shr = FAB$M_SHRPUT | FAB$M_UPI;
3264 fab_in.fab$b_fac = FAB$M_BIO | FAB$M_GET;
3265 fab_in.fab$l_fop = FAB$M_SQO;
3266 fab_in.fab$l_nam = &nam;
3267 fab_in.fab$l_xab = (void *) &xabdat;
3270 nam.nam$l_rsa = rsa;
3271 nam.nam$b_rss = sizeof(rsa);
3272 nam.nam$l_esa = esa;
3273 nam.nam$b_ess = sizeof (esa);
3274 nam.nam$b_esl = nam.nam$b_rsl = 0;
3276 xabdat = cc$rms_xabdat; /* To get creation date */
3277 xabdat.xab$l_nxt = (void *) &xabfhc;
3279 xabfhc = cc$rms_xabfhc; /* To get record length */
3280 xabfhc.xab$l_nxt = (void *) &xabsum;
3282 xabsum = cc$rms_xabsum; /* To get key and area information */
3284 if (!((sts = sys$open(&fab_in)) & 1)) {
3285 set_vaxc_errno(sts);
3289 set_errno(ENOENT); break;
3291 set_errno(ENODEV); break;
3293 set_errno(EINVAL); break;
3295 set_errno(EACCES); break;
3303 fab_out.fab$w_ifi = 0;
3304 fab_out.fab$b_fac = FAB$M_BIO | FAB$M_PUT;
3305 fab_out.fab$b_shr = FAB$M_SHRGET | FAB$M_UPI;
3306 fab_out.fab$l_fop = FAB$M_SQO;
3307 fab_out.fab$l_fna = vmsout;
3308 fab_out.fab$b_fns = strlen(vmsout);
3309 fab_out.fab$l_dna = nam.nam$l_name;
3310 fab_out.fab$b_dns = nam.nam$l_name ? nam.nam$b_name + nam.nam$b_type : 0;
3312 if (preserve_dates == 0) { /* Act like DCL COPY */
3313 nam.nam$b_nop = NAM$M_SYNCHK;
3314 fab_out.fab$l_xab = NULL; /* Don't disturb data from input file */
3315 if (!((sts = sys$parse(&fab_out)) & 1)) {
3316 set_errno(sts == RMS$_SYN ? EINVAL : EVMSERR);
3317 set_vaxc_errno(sts);
3320 fab_out.fab$l_xab = (void *) &xabdat;
3321 if (nam.nam$l_fnb & (NAM$M_EXP_NAME | NAM$M_EXP_TYPE)) preserve_dates = 1;
3323 fab_out.fab$l_nam = (void *) 0; /* Done with NAM block */
3324 if (preserve_dates < 0) /* Clear all bits; we'll use it as a */
3325 preserve_dates =0; /* bitmask from this point forward */
3327 if (!(preserve_dates & 1)) fab_out.fab$l_xab = (void *) &xabfhc;
3328 if (!((sts = sys$create(&fab_out)) & 1)) {
3329 set_vaxc_errno(sts);
3332 set_errno(ENOENT); break;
3334 set_errno(ENODEV); break;
3336 set_errno(EINVAL); break;
3338 set_errno(EACCES); break;
3344 fab_out.fab$l_fop |= FAB$M_DLT; /* in case we have to bail out */
3345 if (preserve_dates & 2) {
3346 /* sys$close() will process xabrdt, not xabdat */
3347 xabrdt = cc$rms_xabrdt;
3348 xabrdt.xab$q_rdt = xabdat.xab$q_rdt;
3349 fab_out.fab$l_xab = (void *) &xabrdt;
3352 rab_in = cc$rms_rab;
3353 rab_in.rab$l_fab = &fab_in;
3354 rab_in.rab$l_rop = RAB$M_BIO;
3355 rab_in.rab$l_ubf = ubf;
3356 rab_in.rab$w_usz = sizeof ubf;
3357 if (!((sts = sys$connect(&rab_in)) & 1)) {
3358 sys$close(&fab_in); sys$close(&fab_out);
3359 set_errno(EVMSERR); set_vaxc_errno(sts);
3363 rab_out = cc$rms_rab;
3364 rab_out.rab$l_fab = &fab_out;
3365 rab_out.rab$l_rbf = ubf;
3366 if (!((sts = sys$connect(&rab_out)) & 1)) {
3367 sys$close(&fab_in); sys$close(&fab_out);
3368 set_errno(EVMSERR); set_vaxc_errno(sts);
3372 while ((sts = sys$read(&rab_in))) { /* always true */
3373 if (sts == RMS$_EOF) break;
3374 rab_out.rab$w_rsz = rab_in.rab$w_rsz;
3375 if (!(sts & 1) || !((sts = sys$write(&rab_out)) & 1)) {
3376 sys$close(&fab_in); sys$close(&fab_out);
3377 set_errno(EVMSERR); set_vaxc_errno(sts);
3382 fab_out.fab$l_fop &= ~FAB$M_DLT; /* We got this far; keep the output */
3383 sys$close(&fab_in); sys$close(&fab_out);
3384 sts = (fab_in.fab$l_sts & 1) ? fab_out.fab$l_sts : fab_in.fab$l_sts;
3386 set_errno(EVMSERR); set_vaxc_errno(sts);
3392 } /* end of rmscopy() */
3396 /*** The following glue provides 'hooks' to make some of the routines
3397 * from this file available from Perl. These routines are sufficiently
3398 * basic, and are required sufficiently early in the build process,
3399 * that's it's nice to have them available to miniperl as well as the
3400 * full Perl, so they're set up here instead of in an extension. The
3401 * Perl code which handles importation of these names into a given
3402 * package lives in [.VMS]Filespec.pm in @INC.
3406 vmsify_fromperl(CV *cv)
3411 if (items != 1) croak("Usage: VMS::Filespec::vmsify(spec)");
3412 vmsified = do_tovmsspec(SvPV(ST(0),na),NULL,1);
3413 ST(0) = sv_newmortal();
3414 if (vmsified != NULL) sv_usepvn(ST(0),vmsified,strlen(vmsified));
3419 unixify_fromperl(CV *cv)
3424 if (items != 1) croak("Usage: VMS::Filespec::unixify(spec)");
3425 unixified = do_tounixspec(SvPV(ST(0),na),NULL,1);
3426 ST(0) = sv_newmortal();
3427 if (unixified != NULL) sv_usepvn(ST(0),unixified,strlen(unixified));
3432 fileify_fromperl(CV *cv)
3437 if (items != 1) croak("Usage: VMS::Filespec::fileify(spec)");
3438 fileified = do_fileify_dirspec(SvPV(ST(0),na),NULL,1);
3439 ST(0) = sv_newmortal();
3440 if (fileified != NULL) sv_usepvn(ST(0),fileified,strlen(fileified));
3445 pathify_fromperl(CV *cv)
3450 if (items != 1) croak("Usage: VMS::Filespec::pathify(spec)");
3451 pathified = do_pathify_dirspec(SvPV(ST(0),na),NULL,1);
3452 ST(0) = sv_newmortal();
3453 if (pathified != NULL) sv_usepvn(ST(0),pathified,strlen(pathified));
3458 vmspath_fromperl(CV *cv)
3463 if (items != 1) croak("Usage: VMS::Filespec::vmspath(spec)");
3464 vmspath = do_tovmspath(SvPV(ST(0),na),NULL,1);
3465 ST(0) = sv_newmortal();
3466 if (vmspath != NULL) sv_usepvn(ST(0),vmspath,strlen(vmspath));
3471 unixpath_fromperl(CV *cv)
3476 if (items != 1) croak("Usage: VMS::Filespec::unixpath(spec)");
3477 unixpath = do_tounixpath(SvPV(ST(0),na),NULL,1);
3478 ST(0) = sv_newmortal();
3479 if (unixpath != NULL) sv_usepvn(ST(0),unixpath,strlen(unixpath));
3484 candelete_fromperl(CV *cv)
3487 char fspec[NAM$C_MAXRSS+1], *fsp;
3491 if (items != 1) croak("Usage: VMS::Filespec::candelete(spec)");
3493 mysv = SvROK(ST(0)) ? SvRV(ST(0)) : ST(0);
3494 if (SvTYPE(mysv) == SVt_PVGV) {
3495 if (!(io = GvIOp(mysv)) || !fgetname(IoIFP(io),fspec)) {
3496 set_errno(EINVAL); set_vaxc_errno(LIB$_INVARG);
3503 if (mysv != ST(0) || !(fsp = SvPV(mysv,na)) || !*fsp) {
3504 set_errno(EINVAL); set_vaxc_errno(LIB$_INVARG);
3510 ST(0) = cando_by_name(S_IDUSR,0,fsp) ? &sv_yes : &sv_no;
3515 rmscopy_fromperl(CV *cv)
3518 char inspec[NAM$C_MAXRSS+1], outspec[NAM$C_MAXRSS+1], *inp, *outp;
3520 struct dsc$descriptor indsc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0},
3521 outdsc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};
3522 unsigned long int sts;
3526 if (items < 2 || items > 3)
3527 croak("Usage: File::Copy::rmscopy(from,to[,date_flag])");
3529 mysv = SvROK(ST(0)) ? SvRV(ST(0)) : ST(0);
3530 if (SvTYPE(mysv) == SVt_PVGV) {
3531 if (!(io = GvIOp(mysv)) || !fgetname(IoIFP(io),inspec)) {
3532 set_errno(EINVAL); set_vaxc_errno(LIB$_INVARG);
3539 if (mysv != ST(0) || !(inp = SvPV(mysv,na)) || !*inp) {
3540 set_errno(EINVAL); set_vaxc_errno(LIB$_INVARG);
3545 mysv = SvROK(ST(1)) ? SvRV(ST(1)) : ST(1);
3546 if (SvTYPE(mysv) == SVt_PVGV) {
3547 if (!(io = GvIOp(mysv)) || !fgetname(IoIFP(io),outspec)) {
3548 set_errno(EINVAL); set_vaxc_errno(LIB$_INVARG);
3555 if (mysv != ST(1) || !(outp = SvPV(mysv,na)) || !*outp) {
3556 set_errno(EINVAL); set_vaxc_errno(LIB$_INVARG);
3561 date_flag = (items == 3) ? SvIV(ST(2)) : 0;
3563 ST(0) = rmscopy(inp,outp,date_flag) ? &sv_yes : &sv_no;
3570 char* file = __FILE__;
3572 newXSproto("VMS::Filespec::vmsify",vmsify_fromperl,file,"$");
3573 newXSproto("VMS::Filespec::unixify",unixify_fromperl,file,"$");
3574 newXSproto("VMS::Filespec::pathify",pathify_fromperl,file,"$");
3575 newXSproto("VMS::Filespec::fileify",fileify_fromperl,file,"$");
3576 newXSproto("VMS::Filespec::vmspath",vmspath_fromperl,file,"$");
3577 newXSproto("VMS::Filespec::unixpath",unixpath_fromperl,file,"$");
3578 newXSproto("VMS::Filespec::candelete",candelete_fromperl,file,"$");
3579 newXS("File::Copy::rmscopy",rmscopy_fromperl,file);