3 * VMS-specific routines for perl5
5 * Last revised: 15-Feb-1997 by Charles Bailey bailey@genetics.upenn.edu
14 #include <climsgdef.h>
23 #include <lib$routines.h>
32 #include <str$routines.h>
37 /* Older versions of ssdef.h don't have these */
38 #ifndef SS$_INVFILFOROP
39 # define SS$_INVFILFOROP 3930
41 #ifndef SS$_NOSUCHOBJECT
42 # define SS$_NOSUCHOBJECT 2696
45 /* Don't replace system definitions of vfork, getenv, and stat,
46 * code below needs to get to the underlying CRTL routines. */
47 #define DONT_MASK_RTL_CALLS
52 /* gcc's header files don't #define direct access macros
53 * corresponding to VAXC's variant structs */
55 # define uic$v_format uic$r_uic_form.uic$v_format
56 # define uic$v_group uic$r_uic_form.uic$v_group
57 # define uic$v_member uic$r_uic_form.uic$v_member
58 # define prv$v_bypass prv$r_prvdef_bits0.prv$v_bypass
59 # define prv$v_grpprv prv$r_prvdef_bits0.prv$v_grpprv
60 # define prv$v_readall prv$r_prvdef_bits0.prv$v_readall
61 # define prv$v_sysprv prv$r_prvdef_bits0.prv$v_sysprv
66 unsigned short int buflen;
67 unsigned short int itmcode;
69 unsigned short int *retlen;
72 static char *__mystrtolower(char *str)
74 if (str) for (; *str; ++str) *str= tolower(*str);
79 my_trnlnm(char *lnm, char *eqv, unsigned long int idx)
81 static char __my_trnlnm_eqv[LNM$C_NAMLENGTH+1];
82 unsigned short int eqvlen;
83 unsigned long int retsts, attr = LNM$M_CASE_BLIND;
84 $DESCRIPTOR(tabdsc,"LNM$FILE_DEV");
85 struct dsc$descriptor_s lnmdsc = {0,DSC$K_DTYPE_T,DSC$K_CLASS_S,0};
86 struct itmlst_3 lnmlst[3] = {{sizeof idx, LNM$_INDEX, &idx, 0},
87 {LNM$C_NAMLENGTH, LNM$_STRING, 0, &eqvlen},
90 if (!lnm || idx > LNM$_MAX_INDEX) {
91 set_errno(EINVAL); set_vaxc_errno(SS$_BADPARAM); return 0;
93 if (!eqv) eqv = __my_trnlnm_eqv;
94 lnmlst[1].bufadr = (void *)eqv;
95 lnmdsc.dsc$a_pointer = lnm;
96 lnmdsc.dsc$w_length = strlen(lnm);
97 retsts = sys$trnlnm(&attr,&tabdsc,&lnmdsc,0,lnmlst);
98 if (retsts == SS$_NOLOGNAM || retsts == SS$_IVLOGNAM) {
99 set_vaxc_errno(retsts); set_errno(EINVAL); return 0;
101 else if (retsts & 1) {
105 _ckvmssts(retsts); /* Must be an error */
106 return 0; /* Not reached, assuming _ckvmssts() bails out */
108 } /* end of my_trnlnm */
111 * Translate a logical name. Substitute for CRTL getenv() to avoid
112 * memory leak, and to keep my_getenv() and my_setenv() in the same
113 * domain (mostly - my_getenv() need not return a translation from
114 * the process logical name table)
116 * Note: Uses static buffer -- not thread-safe!
118 /*{{{ char *my_getenv(char *lnm)*/
122 static char __my_getenv_eqv[LNM$C_NAMLENGTH+1];
123 char uplnm[LNM$C_NAMLENGTH+1], *cp1, *cp2;
124 unsigned long int idx = 0;
127 for (cp1 = lnm, cp2= uplnm; *cp1; cp1++, cp2++) *cp2 = _toupper(*cp1);
129 if (cp1 - lnm == 7 && !strncmp(uplnm,"DEFAULT",7)) {
130 getcwd(__my_getenv_eqv,sizeof __my_getenv_eqv);
131 return __my_getenv_eqv;
134 if ((cp2 = strchr(uplnm,';')) != NULL) {
136 idx = strtoul(cp2+1,NULL,0);
138 trnsuccess = my_trnlnm(uplnm,__my_getenv_eqv,idx);
139 /* If we had a translation index, we're only interested in lnms */
140 if (!trnsuccess && cp2 != NULL) return Nullch;
141 if (trnsuccess) return __my_getenv_eqv;
143 unsigned long int retsts;
144 struct dsc$descriptor_s symdsc = {0,DSC$K_DTYPE_T,DSC$K_CLASS_S,0},
145 valdsc = {sizeof __my_getenv_eqv,DSC$K_DTYPE_T,
146 DSC$K_CLASS_S, __my_getenv_eqv};
147 symdsc.dsc$w_length = cp1 - lnm;
148 symdsc.dsc$a_pointer = uplnm;
149 retsts = lib$get_symbol(&symdsc,&valdsc,&(valdsc.dsc$w_length),0);
150 if (retsts == LIB$_INVSYMNAM) return Nullch;
151 if (retsts != LIB$_NOSUCHSYM) {
152 /* We want to return only logical names or CRTL Unix emulations */
153 if (retsts & 1) return Nullch;
156 /* Try for CRTL emulation of a Unix/POSIX name */
157 else return getenv(uplnm);
162 } /* end of my_getenv() */
165 static FILE *safe_popen(char *, char *);
167 /*{{{ void prime_env_iter() */
170 /* Fill the %ENV associative array with all logical names we can
171 * find, in preparation for iterating over it.
174 static int primed = 0; /* XXX Not thread-safe!!! */
175 HV *envhv = GvHVn(envgv);
177 char eqv[LNM$C_NAMLENGTH+1],*start,*end;
179 SV *oldrs, *linesv, *eqvsv;
182 /* Perform a dummy fetch as an lval to insure that the hash table is
183 * set up. Otherwise, the hv_store() will turn into a nullop */
184 (void) hv_fetch(envhv,"DEFAULT",7,TRUE);
185 /* Also, set up the four "special" keys that the CRTL defines,
186 * whether or not underlying logical names exist. */
187 (void) hv_fetch(envhv,"HOME",4,TRUE);
188 (void) hv_fetch(envhv,"TERM",4,TRUE);
189 (void) hv_fetch(envhv,"PATH",4,TRUE);
190 (void) hv_fetch(envhv,"USER",4,TRUE);
192 /* Now, go get the logical names */
193 if ((sholog = safe_popen("$ Show Logical *","r")) == Nullfp)
194 _ckvmssts(vaxc$errno);
195 /* We use Perl's sv_gets to read from the pipe, since safe_popen is
196 * tied to Perl's I/O layer, so it may not return a simple FILE * */
198 rs = newSVpv("\n",1);
199 linesv = newSVpv("",0);
201 if ((start = sv_gets(linesv,sholog,0)) == Nullch) {
203 SvREFCNT_dec(linesv); SvREFCNT_dec(rs); rs = oldrs;
207 while (*start != '"' && *start != '=' && *start) start++;
208 if (*start != '"') continue;
209 for (end = ++start; *end && *end != '"'; end++) ;
210 if (*end) *end = '\0';
212 if ((eqvlen = my_trnlnm(start,eqv,0)) == 0) {
213 if (vaxc$errno == SS$_NOLOGNAM || vaxc$errno == SS$_IVLOGNAM) {
215 warn("Ill-formed logical name |%s| in prime_env_iter",start);
218 else _ckvmssts(vaxc$errno);
221 eqvsv = newSVpv(eqv,eqvlen);
222 hv_store(envhv,start,(end ? end - start : strlen(start)),eqvsv,0);
225 } /* end of prime_env_iter */
229 /*{{{ void my_setenv(char *lnm, char *eqv)*/
231 my_setenv(char *lnm,char *eqv)
232 /* Define a supervisor-mode logical name in the process table.
233 * In the future we'll add tables, attribs, and acmodes,
234 * probably through a different call.
237 char uplnm[LNM$C_NAMLENGTH], *cp1, *cp2;
238 unsigned long int retsts, usermode = PSL$C_USER;
239 $DESCRIPTOR(tabdsc,"LNM$PROCESS");
240 struct dsc$descriptor_s lnmdsc = {0,DSC$K_DTYPE_T,DSC$K_CLASS_S,uplnm},
241 eqvdsc = {0,DSC$K_DTYPE_T,DSC$K_CLASS_S,0};
243 for(cp1 = lnm, cp2 = uplnm; *cp1; cp1++, cp2++) *cp2 = _toupper(*cp1);
244 lnmdsc.dsc$w_length = cp1 - lnm;
246 if (!eqv || !*eqv) { /* we're deleting a logical name */
247 retsts = sys$dellnm(&tabdsc,&lnmdsc,&usermode); /* try user mode first */
248 if (retsts == SS$_IVLOGNAM) return;
249 if (retsts != SS$_NOLOGNAM) _ckvmssts(retsts);
251 retsts = lib$delete_logical(&lnmdsc,&tabdsc); /* then supervisor mode */
252 if (retsts != SS$_NOLOGNAM) _ckvmssts(retsts);
256 eqvdsc.dsc$w_length = strlen(eqv);
257 eqvdsc.dsc$a_pointer = eqv;
259 _ckvmssts(lib$set_logical(&lnmdsc,&eqvdsc,&tabdsc,0,0));
262 } /* end of my_setenv() */
266 /*{{{ char *my_crypt(const char *textpasswd, const char *usrname)*/
267 /* my_crypt - VMS password hashing
268 * my_crypt() provides an interface compatible with the Unix crypt()
269 * C library function, and uses sys$hash_password() to perform VMS
270 * password hashing. The quadword hashed password value is returned
271 * as a NUL-terminated 8 character string. my_crypt() does not change
272 * the case of its string arguments; in order to match the behavior
273 * of LOGINOUT et al., alphabetic characters in both arguments must
274 * be upcased by the caller.
277 my_crypt(const char *textpasswd, const char *usrname)
279 # ifndef UAI$C_PREFERRED_ALGORITHM
280 # define UAI$C_PREFERRED_ALGORITHM 127
282 unsigned char alg = UAI$C_PREFERRED_ALGORITHM;
283 unsigned short int salt = 0;
284 unsigned long int sts;
286 unsigned short int dsc$w_length;
287 unsigned char dsc$b_type;
288 unsigned char dsc$b_class;
289 const char * dsc$a_pointer;
290 } usrdsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0},
291 txtdsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};
292 struct itmlst_3 uailst[3] = {
293 { sizeof alg, UAI$_ENCRYPT, &alg, 0},
294 { sizeof salt, UAI$_SALT, &salt, 0},
295 { 0, 0, NULL, NULL}};
298 usrdsc.dsc$w_length = strlen(usrname);
299 usrdsc.dsc$a_pointer = usrname;
300 if (!((sts = sys$getuai(0, 0, &usrdsc, uailst, 0, 0, 0)) & 1)) {
307 set_errno(ESRCH); /* There isn't a Unix no-such-user error */
313 if (sts != RMS$_RNF) return NULL;
316 txtdsc.dsc$w_length = strlen(textpasswd);
317 txtdsc.dsc$a_pointer = textpasswd;
318 if (!((sts = sys$hash_password(&txtdsc, alg, salt, &usrdsc, &hash)) & 1)) {
319 set_errno(EVMSERR); set_vaxc_errno(sts); return NULL;
322 return (char *) hash;
324 } /* end of my_crypt() */
328 static char *do_rmsexpand(char *, char *, int, char *, unsigned);
329 static char *do_fileify_dirspec(char *, char *, int);
330 static char *do_tovmsspec(char *, char *, int);
332 /*{{{int do_rmdir(char *name)*/
336 char dirfile[NAM$C_MAXRSS+1];
340 if (do_fileify_dirspec(name,dirfile,0) == NULL) return -1;
341 if (flex_stat(dirfile,&st) || !S_ISDIR(st.st_mode)) retval = -1;
342 else retval = kill_file(dirfile);
345 } /* end of do_rmdir */
349 * Delete any file to which user has control access, regardless of whether
350 * delete access is explicitly allowed.
351 * Limitations: User must have write access to parent directory.
352 * Does not block signals or ASTs; if interrupted in midstream
353 * may leave file with an altered ACL.
356 /*{{{int kill_file(char *name)*/
358 kill_file(char *name)
360 char vmsname[NAM$C_MAXRSS+1], rspec[NAM$C_MAXRSS+1];
361 unsigned long int jpicode = JPI$_UIC, type = ACL$C_FILE;
362 unsigned long int cxt = 0, aclsts, fndsts, rmsts = -1;
363 struct dsc$descriptor_s fildsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};
365 unsigned char myace$b_length;
366 unsigned char myace$b_type;
367 unsigned short int myace$w_flags;
368 unsigned long int myace$l_access;
369 unsigned long int myace$l_ident;
370 } newace = { sizeof(struct myacedef), ACE$C_KEYID, 0,
371 ACE$M_READ | ACE$M_WRITE | ACE$M_DELETE | ACE$M_CONTROL, 0},
372 oldace = { sizeof(struct myacedef), ACE$C_KEYID, 0, 0, 0};
374 findlst[3] = {{sizeof oldace, ACL$C_FNDACLENT, &oldace, 0},
375 {sizeof oldace, ACL$C_READACE, &oldace, 0},{0,0,0,0}},
376 addlst[2] = {{sizeof newace, ACL$C_ADDACLENT, &newace, 0},{0,0,0,0}},
377 dellst[2] = {{sizeof newace, ACL$C_DELACLENT, &newace, 0},{0,0,0,0}},
378 lcklst[2] = {{sizeof newace, ACL$C_WLOCK_ACL, &newace, 0},{0,0,0,0}},
379 ulklst[2] = {{sizeof newace, ACL$C_UNLOCK_ACL, &newace, 0},{0,0,0,0}};
381 /* Expand the input spec using RMS, since the CRTL remove() and
382 * system services won't do this by themselves, so we may miss
383 * a file "hiding" behind a logical name or search list. */
384 if (do_tovmsspec(name,vmsname,0) == NULL) return -1;
385 if (do_rmsexpand(vmsname,rspec,1,NULL,0) == NULL) return -1;
386 if (!remove(rspec)) return 0; /* Can we just get rid of it? */
387 /* If not, can changing protections help? */
388 if (vaxc$errno != RMS$_PRV) return -1;
390 /* No, so we get our own UIC to use as a rights identifier,
391 * and the insert an ACE at the head of the ACL which allows us
392 * to delete the file.
394 _ckvmssts(lib$getjpi(&jpicode,0,0,&(oldace.myace$l_ident),0,0));
395 fildsc.dsc$w_length = strlen(rspec);
396 fildsc.dsc$a_pointer = rspec;
398 newace.myace$l_ident = oldace.myace$l_ident;
399 if (!((aclsts = sys$change_acl(0,&type,&fildsc,lcklst,0,0,0)) & 1)) {
404 case SS$_NOSUCHOBJECT:
405 set_errno(ENOENT); break;
407 set_errno(ENODEV); break;
409 case SS$_INVFILFOROP:
410 set_errno(EINVAL); break;
412 set_errno(EACCES); break;
416 set_vaxc_errno(aclsts);
419 /* Grab any existing ACEs with this identifier in case we fail */
420 aclsts = fndsts = sys$change_acl(0,&type,&fildsc,findlst,0,0,&cxt);
421 if ( fndsts & 1 || fndsts == SS$_ACLEMPTY || fndsts == SS$_NOENTRY
422 || fndsts == SS$_NOMOREACE ) {
423 /* Add the new ACE . . . */
424 if (!((aclsts = sys$change_acl(0,&type,&fildsc,addlst,0,0,0)) & 1))
426 if ((rmsts = remove(name))) {
427 /* We blew it - dir with files in it, no write priv for
428 * parent directory, etc. Put things back the way they were. */
429 if (!((aclsts = sys$change_acl(0,&type,&fildsc,dellst,0,0,0)) & 1))
432 addlst[0].bufadr = &oldace;
433 if (!((aclsts = sys$change_acl(0,&type,&fildsc,addlst,0,0,&cxt)) & 1))
440 fndsts = sys$change_acl(0,&type,&fildsc,ulklst,0,0,0);
441 /* We just deleted it, so of course it's not there. Some versions of
442 * VMS seem to return success on the unlock operation anyhow (after all
443 * the unlock is successful), but others don't.
445 if (fndsts == RMS$_FNF || fndsts == SS$_NOSUCHOBJECT) fndsts = SS$_NORMAL;
446 if (aclsts & 1) aclsts = fndsts;
449 set_vaxc_errno(aclsts);
455 } /* end of kill_file() */
459 create_mbx(unsigned short int *chan, struct dsc$descriptor_s *namdsc)
461 static unsigned long int mbxbufsiz;
462 long int syiitm = SYI$_MAXBUF, dviitm = DVI$_DEVNAM;
466 * Get the SYSGEN parameter MAXBUF, and the smaller of it and the
467 * preprocessor consant BUFSIZ from stdio.h as the size of the
470 _ckvmssts(lib$getsyi(&syiitm, &mbxbufsiz, 0, 0, 0, 0));
471 if (mbxbufsiz > BUFSIZ) mbxbufsiz = BUFSIZ;
473 _ckvmssts(sys$crembx(0,chan,mbxbufsiz,mbxbufsiz,0,0,0));
475 _ckvmssts(lib$getdvi(&dviitm, chan, NULL, NULL, namdsc, &namdsc->dsc$w_length));
476 namdsc->dsc$a_pointer[namdsc->dsc$w_length] = '\0';
478 } /* end of create_mbx() */
480 /*{{{ my_popen and my_pclose*/
483 struct pipe_details *next;
484 PerlIO *fp; /* stdio file pointer to pipe mailbox */
485 int pid; /* PID of subprocess */
486 int mode; /* == 'r' if pipe open for reading */
487 int done; /* subprocess has completed */
488 unsigned long int completion; /* termination status of subprocess */
491 struct exit_control_block
493 struct exit_control_block *flink;
494 unsigned long int (*exit_routine)();
495 unsigned long int arg_count;
496 unsigned long int *status_address;
497 unsigned long int exit_status;
500 static struct pipe_details *open_pipes = NULL;
501 static $DESCRIPTOR(nl_desc, "NL:");
502 static int waitpid_asleep = 0;
504 static unsigned long int
507 unsigned long int retsts = SS$_NORMAL, abort = SS$_TIMEOUT;
510 while (open_pipes != NULL) {
511 if (!open_pipes->done) { /* Tap them gently on the shoulder . . .*/
512 _ckvmssts(sys$forcex(&open_pipes->pid,0,&abort));
515 if (!open_pipes->done) /* We tried to be nice . . . */
516 _ckvmssts(sys$delprc(&open_pipes->pid,0));
517 if ((sts = my_pclose(open_pipes->fp)) == -1) retsts = vaxc$errno;
518 else if (!(sts & 1)) retsts = sts;
523 static struct exit_control_block pipe_exitblock =
524 {(struct exit_control_block *) 0,
525 pipe_exit_routine, 0, &pipe_exitblock.exit_status, 0};
529 popen_completion_ast(struct pipe_details *thispipe)
531 thispipe->done = TRUE;
532 if (waitpid_asleep) {
539 safe_popen(char *cmd, char *mode)
541 static int handler_set_up = FALSE;
543 unsigned short int chan;
544 unsigned long int flags=1; /* nowait - gnu c doesn't allow &1 */
545 struct pipe_details *info;
546 struct dsc$descriptor_s namdsc = {sizeof mbxname, DSC$K_DTYPE_T,
547 DSC$K_CLASS_S, mbxname},
548 cmddsc = {0, DSC$K_DTYPE_T,
552 cmddsc.dsc$w_length=strlen(cmd);
553 cmddsc.dsc$a_pointer=cmd;
554 if (cmddsc.dsc$w_length > 255) {
555 set_errno(E2BIG); set_vaxc_errno(CLI$_BUFOVF);
559 New(7001,info,1,struct pipe_details);
562 create_mbx(&chan,&namdsc);
564 /* open a FILE* onto it */
565 info->fp = PerlIO_open(mbxname, mode);
567 /* give up other channel onto it */
568 _ckvmssts(sys$dassgn(chan));
578 _ckvmssts(lib$spawn(&cmddsc, &nl_desc, &namdsc, &flags,
579 0 /* name */, &info->pid, &info->completion,
580 0, popen_completion_ast,info,0,0,0));
583 _ckvmssts(lib$spawn(&cmddsc, &namdsc, 0 /* sys$output */, &flags,
584 0 /* name */, &info->pid, &info->completion,
585 0, popen_completion_ast,info,0,0,0));
588 if (!handler_set_up) {
589 _ckvmssts(sys$dclexh(&pipe_exitblock));
590 handler_set_up = TRUE;
592 info->next=open_pipes; /* prepend to list */
595 forkprocess = info->pid;
597 } /* end of safe_popen */
600 /*{{{ FILE *my_popen(char *cmd, char *mode)*/
602 my_popen(char *cmd, char *mode)
605 TAINT_PROPER("popen");
606 return safe_popen(cmd,mode);
611 /*{{{ I32 my_pclose(FILE *fp)*/
612 I32 my_pclose(FILE *fp)
614 struct pipe_details *info, *last = NULL;
615 unsigned long int retsts;
617 for (info = open_pipes; info != NULL; last = info, info = info->next)
618 if (info->fp == fp) break;
620 if (info == NULL) { /* no such pipe open */
621 set_errno(ECHILD); /* quoth POSIX */
622 set_vaxc_errno(SS$_NONEXPR);
626 /* If we were writing to a subprocess, insure that someone reading from
627 * the mailbox gets an EOF. It looks like a simple fclose() doesn't
628 * produce an EOF record in the mailbox. */
629 if (info->mode != 'r') {
630 char devnam[NAM$C_MAXRSS+1], *cp;
631 unsigned long int chan, iosb[2], retsts, retsts2;
632 struct dsc$descriptor devdsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, devnam};
634 if (fgetname(info->fp,devnam)) {
635 /* It oughta be a mailbox, so fgetname should give just the device
636 * name, but just in case . . . */
637 if ((cp = strrchr(devnam,':')) != NULL) *(cp+1) = '\0';
638 devdsc.dsc$w_length = strlen(devnam);
639 _ckvmssts(sys$assign(&devdsc,&chan,0,0));
640 retsts = sys$qiow(0,chan,IO$_WRITEOF,iosb,0,0,0,0,0,0,0,0);
641 if (retsts & 1) retsts = iosb[0];
642 retsts2 = sys$dassgn(chan); /* Be sure to deassign the channel */
643 if (retsts & 1) retsts = retsts2;
646 else _ckvmssts(vaxc$errno); /* Should never happen */
648 PerlIO_close(info->fp);
650 if (info->done) retsts = info->completion;
651 else waitpid(info->pid,(int *) &retsts,0);
653 /* remove from list of open pipes */
654 if (last) last->next = info->next;
655 else open_pipes = info->next;
660 } /* end of my_pclose() */
662 /* sort-of waitpid; use only with popen() */
663 /*{{{Pid_t my_waitpid(Pid_t pid, int *statusp, int flags)*/
665 my_waitpid(Pid_t pid, int *statusp, int flags)
667 struct pipe_details *info;
669 for (info = open_pipes; info != NULL; info = info->next)
670 if (info->pid == pid) break;
672 if (info != NULL) { /* we know about this child */
673 while (!info->done) {
678 *statusp = info->completion;
681 else { /* we haven't heard of this child */
682 $DESCRIPTOR(intdsc,"0 00:00:01");
683 unsigned long int ownercode = JPI$_OWNER, ownerpid, mypid;
684 unsigned long int interval[2],sts;
687 _ckvmssts(lib$getjpi(&ownercode,&pid,0,&ownerpid,0,0));
688 _ckvmssts(lib$getjpi(&ownercode,0,0,&mypid,0,0));
689 if (ownerpid != mypid)
690 warn("pid %d not a child",pid);
693 _ckvmssts(sys$bintim(&intdsc,interval));
694 while ((sts=lib$getjpi(&ownercode,&pid,0,&ownerpid,0,0)) & 1) {
695 _ckvmssts(sys$schdwk(0,0,interval,0));
696 _ckvmssts(sys$hiber());
700 /* There's no easy way to find the termination status a child we're
701 * not aware of beforehand. If we're really interested in the future,
702 * we can go looking for a termination mailbox, or chase after the
703 * accounting record for the process.
709 } /* end of waitpid() */
714 /*{{{ char *my_gconvert(double val, int ndig, int trail, char *buf) */
716 my_gconvert(double val, int ndig, int trail, char *buf)
718 static char __gcvtbuf[DBL_DIG+1];
721 loc = buf ? buf : __gcvtbuf;
723 #ifndef __DECC /* VAXCRTL gcvt uses E format for numbers < 1 */
725 sprintf(loc,"%.*g",ndig,val);
731 if (!buf && ndig > DBL_DIG) ndig = DBL_DIG;
732 return gcvt(val,ndig,loc);
735 loc[0] = '0'; loc[1] = '\0';
743 /*{{{char *do_rmsexpand(char *fspec, char *out, int ts, char *def, unsigned opts)*/
744 /* Shortcut for common case of simple calls to $PARSE and $SEARCH
745 * to expand file specification. Allows for a single default file
746 * specification and a simple mask of options. If outbuf is non-NULL,
747 * it must point to a buffer at least NAM$C_MAXRSS bytes long, into which
748 * the resultant file specification is placed. If outbuf is NULL, the
749 * resultant file specification is placed into a static buffer.
750 * The third argument, if non-NULL, is taken to be a default file
751 * specification string. The fourth argument is unused at present.
752 * rmesexpand() returns the address of the resultant string if
753 * successful, and NULL on error.
756 do_rmsexpand(char *filespec, char *outbuf, int ts, char *defspec, unsigned opts)
758 static char __rmsexpand_retbuf[NAM$C_MAXRSS+1];
759 char esa[NAM$C_MAXRSS], *cp, *out = NULL;
760 struct FAB myfab = cc$rms_fab;
761 struct NAM mynam = cc$rms_nam;
763 unsigned long int retsts, haslower = 0;
765 if (!filespec || !*filespec) {
766 set_vaxc_errno(LIB$_INVARG); set_errno(EINVAL);
770 if (ts) out = New(7019,outbuf,NAM$C_MAXRSS+1,char);
771 else outbuf = __rmsexpand_retbuf;
774 myfab.fab$l_fna = filespec;
775 myfab.fab$b_fns = strlen(filespec);
776 myfab.fab$l_nam = &mynam;
778 if (defspec && *defspec) {
779 myfab.fab$l_dna = defspec;
780 myfab.fab$b_dns = strlen(defspec);
783 mynam.nam$l_esa = esa;
784 mynam.nam$b_ess = sizeof esa;
785 mynam.nam$l_rsa = outbuf;
786 mynam.nam$b_rss = NAM$C_MAXRSS;
788 retsts = sys$parse(&myfab,0,0);
790 if (retsts == RMS$_DNF || retsts == RMS$_DIR ||
791 retsts == RMS$_DEV || retsts == RMS$_DEV) {
792 mynam.nam$b_nop |= NAM$M_SYNCHK;
793 retsts = sys$parse(&myfab,0,0);
794 if (retsts & 1) goto expanded;
796 if (out) Safefree(out);
797 set_vaxc_errno(retsts);
798 if (retsts == RMS$_PRV) set_errno(EACCES);
799 else if (retsts == RMS$_DEV) set_errno(ENODEV);
800 else if (retsts == RMS$_DIR) set_errno(ENOTDIR);
801 else set_errno(EVMSERR);
804 retsts = sys$search(&myfab,0,0);
805 if (!(retsts & 1) && retsts != RMS$_FNF) {
806 if (out) Safefree(out);
807 set_vaxc_errno(retsts);
808 if (retsts == RMS$_PRV) set_errno(EACCES);
809 else set_errno(EVMSERR);
813 /* If the input filespec contained any lowercase characters,
814 * downcase the result for compatibility with Unix-minded code. */
816 for (out = myfab.fab$l_fna; *out; out++)
817 if (islower(*out)) { haslower = 1; break; }
818 if (mynam.nam$b_rsl) { out = outbuf; speclen = mynam.nam$b_rsl; }
819 else { out = esa; speclen = mynam.nam$b_esl; }
820 if (!(mynam.nam$l_fnb & NAM$M_EXP_VER) &&
821 (!defspec || !*defspec || !strchr(myfab.fab$l_dna,';')))
822 speclen = mynam.nam$l_ver - out;
823 /* If we just had a directory spec on input, $PARSE "helpfully"
824 * adds an empty name and type for us */
825 if (mynam.nam$l_name == mynam.nam$l_type &&
826 mynam.nam$l_ver == mynam.nam$l_type + 1 &&
827 !(mynam.nam$l_fnb & NAM$M_EXP_NAME))
828 speclen = mynam.nam$l_name - out;
830 if (haslower) __mystrtolower(out);
832 /* Have we been working with an expanded, but not resultant, spec? */
833 if (!mynam.nam$b_rsl) strcpy(outbuf,esa);
837 /* External entry points */
838 char *rmsexpand(char *spec, char *buf, char *def, unsigned opt)
839 { return do_rmsexpand(spec,buf,0,def,opt); }
840 char *rmsexpand_ts(char *spec, char *buf, char *def, unsigned opt)
841 { return do_rmsexpand(spec,buf,1,def,opt); }
845 ** The following routines are provided to make life easier when
846 ** converting among VMS-style and Unix-style directory specifications.
847 ** All will take input specifications in either VMS or Unix syntax. On
848 ** failure, all return NULL. If successful, the routines listed below
849 ** return a pointer to a buffer containing the appropriately
850 ** reformatted spec (and, therefore, subsequent calls to that routine
851 ** will clobber the result), while the routines of the same names with
852 ** a _ts suffix appended will return a pointer to a mallocd string
853 ** containing the appropriately reformatted spec.
854 ** In all cases, only explicit syntax is altered; no check is made that
855 ** the resulting string is valid or that the directory in question
858 ** fileify_dirspec() - convert a directory spec into the name of the
859 ** directory file (i.e. what you can stat() to see if it's a dir).
860 ** The style (VMS or Unix) of the result is the same as the style
861 ** of the parameter passed in.
862 ** pathify_dirspec() - convert a directory spec into a path (i.e.
863 ** what you prepend to a filename to indicate what directory it's in).
864 ** The style (VMS or Unix) of the result is the same as the style
865 ** of the parameter passed in.
866 ** tounixpath() - convert a directory spec into a Unix-style path.
867 ** tovmspath() - convert a directory spec into a VMS-style path.
868 ** tounixspec() - convert any file spec into a Unix-style file spec.
869 ** tovmsspec() - convert any file spec into a VMS-style spec.
871 ** Copyright 1996 by Charles Bailey <bailey@genetics.upenn.edu>
872 ** Permission is given to distribute this code as part of the Perl
873 ** standard distribution under the terms of the GNU General Public
874 ** License or the Perl Artistic License. Copies of each may be
875 ** found in the Perl standard distribution.
878 static char *do_tounixspec(char *, char *, int);
880 /*{{{ char *fileify_dirspec[_ts](char *path, char *buf)*/
881 static char *do_fileify_dirspec(char *dir,char *buf,int ts)
883 static char __fileify_retbuf[NAM$C_MAXRSS+1];
884 unsigned long int dirlen, retlen, addmfd = 0, hasfilename = 0;
885 char *retspec, *cp1, *cp2, *lastdir;
886 char trndir[NAM$C_MAXRSS+1], vmsdir[NAM$C_MAXRSS+1];
889 set_errno(EINVAL); set_vaxc_errno(SS$_BADPARAM); return NULL;
891 dirlen = strlen(dir);
892 if (dir[dirlen-1] == '/') --dirlen;
895 set_vaxc_errno(RMS$_DIR);
898 if (!strpbrk(dir+1,"/]>:")) {
899 strcpy(trndir,*dir == '/' ? dir + 1: dir);
900 while (!strpbrk(trndir,"/]>:>") && my_trnlnm(trndir,trndir,0)) ;
902 dirlen = strlen(dir);
905 strncpy(trndir,dir,dirlen);
906 trndir[dirlen] = '\0';
909 /* If we were handed a rooted logical name or spec, treat it like a
910 * simple directory, so that
911 * $ Define myroot dev:[dir.]
912 * ... do_fileify_dirspec("myroot",buf,1) ...
913 * does something useful.
915 if (!strcmp(dir+dirlen-2,".]")) {
916 dir[--dirlen] = '\0';
920 if ((cp1 = strrchr(dir,']')) != NULL || (cp1 = strrchr(dir,'>')) != NULL) {
921 /* If we've got an explicit filename, we can just shuffle the string. */
922 if (*(cp1+1)) hasfilename = 1;
923 /* Similarly, we can just back up a level if we've got multiple levels
924 of explicit directories in a VMS spec which ends with directories. */
926 for (cp2 = cp1; cp2 > dir; cp2--) {
928 *cp2 = *cp1; *cp1 = '\0';
932 if (*cp2 == '[' || *cp2 == '<') break;
937 if (hasfilename || !strpbrk(dir,"]:>")) { /* Unix-style path or filename */
939 if (dir[1] == '\0' || (dir[1] == '/' && dir[2] == '\0'))
940 return do_fileify_dirspec("[]",buf,ts);
941 else if (dir[1] == '.' &&
942 (dir[2] == '\0' || (dir[2] == '/' && dir[3] == '\0')))
943 return do_fileify_dirspec("[-]",buf,ts);
945 if (dir[dirlen-1] == '/') { /* path ends with '/'; just add .dir;1 */
946 dirlen -= 1; /* to last element */
947 lastdir = strrchr(dir,'/');
949 else if ((cp1 = strstr(dir,"/.")) != NULL) {
950 /* If we have "/." or "/..", VMSify it and let the VMS code
951 * below expand it, rather than repeating the code to handle
952 * relative components of a filespec here */
954 if (*(cp1+2) == '.') cp1++;
955 if (*(cp1+2) == '/' || *(cp1+2) == '\0') {
956 if (do_tovmsspec(dir,vmsdir,0) == NULL) return NULL;
957 if (do_fileify_dirspec(vmsdir,trndir,0) == NULL) return NULL;
958 return do_tounixspec(trndir,buf,ts);
961 } while ((cp1 = strstr(cp1,"/.")) != NULL);
964 if ( !(lastdir = cp1 = strrchr(dir,'/')) &&
965 !(lastdir = cp1 = strrchr(dir,']')) &&
966 !(lastdir = cp1 = strrchr(dir,'>'))) cp1 = dir;
967 if ((cp2 = strchr(cp1,'.'))) { /* look for explicit type */
969 if (!*(cp2+1) || toupper(*(cp2+1)) != 'D' || /* Wrong type. */
970 !*(cp2+2) || toupper(*(cp2+2)) != 'I' || /* Bzzt. */
971 !*(cp2+3) || toupper(*(cp2+3)) != 'R' ||
972 (*(cp2+4) && ((*(cp2+4) != ';' && *(cp2+4) != '.') ||
973 (*(cp2+5) && ((ver = strtol(cp2+5,&cp3,10)) != 1 &&
976 set_vaxc_errno(RMS$_DIR);
982 /* If we lead off with a device or rooted logical, add the MFD
983 if we're specifying a top-level directory. */
984 if (lastdir && *dir == '/') {
986 for (cp1 = lastdir - 1; cp1 > dir; cp1--) {
993 retlen = dirlen + (addmfd ? 13 : 6);
994 if (buf) retspec = buf;
995 else if (ts) New(7009,retspec,retlen+1,char);
996 else retspec = __fileify_retbuf;
998 dirlen = lastdir - dir;
999 memcpy(retspec,dir,dirlen);
1000 strcpy(&retspec[dirlen],"/000000");
1001 strcpy(&retspec[dirlen+7],lastdir);
1004 memcpy(retspec,dir,dirlen);
1005 retspec[dirlen] = '\0';
1007 /* We've picked up everything up to the directory file name.
1008 Now just add the type and version, and we're set. */
1009 strcat(retspec,".dir;1");
1012 else { /* VMS-style directory spec */
1013 char esa[NAM$C_MAXRSS+1], term, *cp;
1014 unsigned long int sts, cmplen, haslower = 0;
1015 struct FAB dirfab = cc$rms_fab;
1016 struct NAM savnam, dirnam = cc$rms_nam;
1018 dirfab.fab$b_fns = strlen(dir);
1019 dirfab.fab$l_fna = dir;
1020 dirfab.fab$l_nam = &dirnam;
1021 dirfab.fab$l_dna = ".DIR;1";
1022 dirfab.fab$b_dns = 6;
1023 dirnam.nam$b_ess = NAM$C_MAXRSS;
1024 dirnam.nam$l_esa = esa;
1026 for (cp = dir; *cp; cp++)
1027 if (islower(*cp)) { haslower = 1; break; }
1028 if (!((sts = sys$parse(&dirfab))&1)) {
1029 if (dirfab.fab$l_sts == RMS$_DIR) {
1030 dirnam.nam$b_nop |= NAM$M_SYNCHK;
1031 sts = sys$parse(&dirfab) & 1;
1035 set_vaxc_errno(dirfab.fab$l_sts);
1041 if (sys$search(&dirfab)&1) { /* Does the file really exist? */
1042 /* Yes; fake the fnb bits so we'll check type below */
1043 dirnam.nam$l_fnb |= NAM$M_EXP_TYPE | NAM$M_EXP_VER;
1046 if (dirfab.fab$l_sts != RMS$_FNF) {
1048 set_vaxc_errno(dirfab.fab$l_sts);
1051 dirnam = savnam; /* No; just work with potential name */
1054 if (!(dirnam.nam$l_fnb & (NAM$M_EXP_DEV | NAM$M_EXP_DIR))) {
1055 cp1 = strchr(esa,']');
1056 if (!cp1) cp1 = strchr(esa,'>');
1057 if (cp1) { /* Should always be true */
1058 dirnam.nam$b_esl -= cp1 - esa - 1;
1059 memcpy(esa,cp1 + 1,dirnam.nam$b_esl);
1062 if (dirnam.nam$l_fnb & NAM$M_EXP_TYPE) { /* Was type specified? */
1063 /* Yep; check version while we're at it, if it's there. */
1064 cmplen = (dirnam.nam$l_fnb & NAM$M_EXP_VER) ? 6 : 4;
1065 if (strncmp(dirnam.nam$l_type,".DIR;1",cmplen)) {
1066 /* Something other than .DIR[;1]. Bzzt. */
1068 set_vaxc_errno(RMS$_DIR);
1072 esa[dirnam.nam$b_esl] = '\0';
1073 if (dirnam.nam$l_fnb & NAM$M_EXP_NAME) {
1074 /* They provided at least the name; we added the type, if necessary, */
1075 if (buf) retspec = buf; /* in sys$parse() */
1076 else if (ts) New(7011,retspec,dirnam.nam$b_esl+1,char);
1077 else retspec = __fileify_retbuf;
1078 strcpy(retspec,esa);
1081 if ((cp1 = strstr(esa,".][000000]")) != NULL) {
1082 for (cp2 = cp1 + 9; *cp2; cp1++,cp2++) *cp1 = *cp2;
1084 dirnam.nam$b_esl -= 9;
1086 if ((cp1 = strrchr(esa,']')) == NULL) cp1 = strrchr(esa,'>');
1087 if (cp1 == NULL) return NULL; /* should never happen */
1090 retlen = strlen(esa);
1091 if ((cp1 = strrchr(esa,'.')) != NULL) {
1092 /* There's more than one directory in the path. Just roll back. */
1094 if (buf) retspec = buf;
1095 else if (ts) New(7011,retspec,retlen+7,char);
1096 else retspec = __fileify_retbuf;
1097 strcpy(retspec,esa);
1100 if (dirnam.nam$l_fnb & NAM$M_ROOT_DIR) {
1101 /* Go back and expand rooted logical name */
1102 dirnam.nam$b_nop = NAM$M_SYNCHK | NAM$M_NOCONCEAL;
1103 if (!(sys$parse(&dirfab) & 1)) {
1105 set_vaxc_errno(dirfab.fab$l_sts);
1108 retlen = dirnam.nam$b_esl - 9; /* esa - '][' - '].DIR;1' */
1109 if (buf) retspec = buf;
1110 else if (ts) New(7012,retspec,retlen+16,char);
1111 else retspec = __fileify_retbuf;
1112 cp1 = strstr(esa,"][");
1114 memcpy(retspec,esa,dirlen);
1115 if (!strncmp(cp1+2,"000000]",7)) {
1116 retspec[dirlen-1] = '\0';
1117 for (cp1 = retspec+dirlen-1; *cp1 != '.' && *cp1 != '['; cp1--) ;
1118 if (*cp1 == '.') *cp1 = ']';
1120 memmove(cp1+8,cp1+1,retspec+dirlen-cp1);
1121 memcpy(cp1+1,"000000]",7);
1125 memcpy(retspec+dirlen,cp1+2,retlen-dirlen);
1126 retspec[retlen] = '\0';
1127 /* Convert last '.' to ']' */
1128 for (cp1 = retspec+retlen-1; *cp1 != '.' && *cp1 != '['; cp1--) ;
1129 if (*cp1 == '.') *cp1 = ']';
1131 memmove(cp1+8,cp1+1,retspec+dirlen-cp1);
1132 memcpy(cp1+1,"000000]",7);
1136 else { /* This is a top-level dir. Add the MFD to the path. */
1137 if (buf) retspec = buf;
1138 else if (ts) New(7012,retspec,retlen+16,char);
1139 else retspec = __fileify_retbuf;
1142 while (*cp1 != ':') *(cp2++) = *(cp1++);
1143 strcpy(cp2,":[000000]");
1148 /* We've set up the string up through the filename. Add the
1149 type and version, and we're done. */
1150 strcat(retspec,".DIR;1");
1152 /* $PARSE may have upcased filespec, so convert output to lower
1153 * case if input contained any lowercase characters. */
1154 if (haslower) __mystrtolower(retspec);
1157 } /* end of do_fileify_dirspec() */
1159 /* External entry points */
1160 char *fileify_dirspec(char *dir, char *buf)
1161 { return do_fileify_dirspec(dir,buf,0); }
1162 char *fileify_dirspec_ts(char *dir, char *buf)
1163 { return do_fileify_dirspec(dir,buf,1); }
1165 /*{{{ char *pathify_dirspec[_ts](char *path, char *buf)*/
1166 static char *do_pathify_dirspec(char *dir,char *buf, int ts)
1168 static char __pathify_retbuf[NAM$C_MAXRSS+1];
1169 unsigned long int retlen;
1170 char *retpath, *cp1, *cp2, trndir[NAM$C_MAXRSS+1];
1172 if (!dir || !*dir) {
1173 set_errno(EINVAL); set_vaxc_errno(SS$_BADPARAM); return NULL;
1176 if (*dir) strcpy(trndir,dir);
1177 else getcwd(trndir,sizeof trndir - 1);
1179 while (!strpbrk(trndir,"/]:>") && my_trnlnm(trndir,trndir,0)) {
1180 STRLEN trnlen = strlen(trndir);
1182 /* Trap simple rooted lnms, and return lnm:[000000] */
1183 if (!strcmp(trndir+trnlen-2,".]")) {
1184 if (buf) retpath = buf;
1185 else if (ts) New(7018,retpath,strlen(dir)+10,char);
1186 else retpath = __pathify_retbuf;
1187 strcpy(retpath,dir);
1188 strcat(retpath,":[000000]");
1194 if (!strpbrk(dir,"]:>")) { /* Unix-style path or plain name */
1195 if (*dir == '.' && (*(dir+1) == '\0' ||
1196 (*(dir+1) == '.' && *(dir+2) == '\0')))
1197 retlen = 2 + (*(dir+1) != '\0');
1199 if ( !(cp1 = strrchr(dir,'/')) &&
1200 !(cp1 = strrchr(dir,']')) &&
1201 !(cp1 = strrchr(dir,'>')) ) cp1 = dir;
1202 if ((cp2 = strchr(cp1,'.')) != NULL &&
1203 (*(cp2-1) != '/' || /* Trailing '.', '..', */
1204 !(*(cp2+1) == '\0' || /* or '...' are dirs. */
1205 (*(cp2+1) == '.' && *(cp2+2) == '\0') ||
1206 (*(cp2+1) == '.' && *(cp2+2) == '.' && *(cp2+3) == '\0')))) {
1208 if (!*(cp2+1) || toupper(*(cp2+1)) != 'D' || /* Wrong type. */
1209 !*(cp2+2) || toupper(*(cp2+2)) != 'I' || /* Bzzt. */
1210 !*(cp2+3) || toupper(*(cp2+3)) != 'R' ||
1211 (*(cp2+4) && ((*(cp2+4) != ';' && *(cp2+4) != '.') ||
1212 (*(cp2+5) && ((ver = strtol(cp2+5,&cp3,10)) != 1 &&
1213 (ver || *cp3)))))) {
1215 set_vaxc_errno(RMS$_DIR);
1218 retlen = cp2 - dir + 1;
1220 else { /* No file type present. Treat the filename as a directory. */
1221 retlen = strlen(dir) + 1;
1224 if (buf) retpath = buf;
1225 else if (ts) New(7013,retpath,retlen+1,char);
1226 else retpath = __pathify_retbuf;
1227 strncpy(retpath,dir,retlen-1);
1228 if (retpath[retlen-2] != '/') { /* If the path doesn't already end */
1229 retpath[retlen-1] = '/'; /* with '/', add it. */
1230 retpath[retlen] = '\0';
1232 else retpath[retlen-1] = '\0';
1234 else { /* VMS-style directory spec */
1235 char esa[NAM$C_MAXRSS+1], *cp;
1236 unsigned long int sts, cmplen, haslower;
1237 struct FAB dirfab = cc$rms_fab;
1238 struct NAM savnam, dirnam = cc$rms_nam;
1240 /* If we've got an explicit filename, we can just shuffle the string. */
1241 if ( ( (cp1 = strrchr(dir,']')) != NULL ||
1242 (cp1 = strrchr(dir,'>')) != NULL ) && *(cp1+1)) {
1243 if ((cp2 = strchr(cp1,'.')) != NULL) {
1245 if (!*(cp2+1) || toupper(*(cp2+1)) != 'D' || /* Wrong type. */
1246 !*(cp2+2) || toupper(*(cp2+2)) != 'I' || /* Bzzt. */
1247 !*(cp2+3) || toupper(*(cp2+3)) != 'R' ||
1248 (*(cp2+4) && ((*(cp2+4) != ';' && *(cp2+4) != '.') ||
1249 (*(cp2+5) && ((ver = strtol(cp2+5,&cp3,10)) != 1 &&
1250 (ver || *cp3)))))) {
1252 set_vaxc_errno(RMS$_DIR);
1256 else { /* No file type, so just draw name into directory part */
1257 for (cp2 = cp1; *cp2; cp2++) ;
1260 *(cp2+1) = '\0'; /* OK; trndir is guaranteed to be long enough */
1262 /* We've now got a VMS 'path'; fall through */
1264 dirfab.fab$b_fns = strlen(dir);
1265 dirfab.fab$l_fna = dir;
1266 if (dir[dirfab.fab$b_fns-1] == ']' ||
1267 dir[dirfab.fab$b_fns-1] == '>' ||
1268 dir[dirfab.fab$b_fns-1] == ':') { /* It's already a VMS 'path' */
1269 if (buf) retpath = buf;
1270 else if (ts) New(7014,retpath,strlen(dir)+1,char);
1271 else retpath = __pathify_retbuf;
1272 strcpy(retpath,dir);
1275 dirfab.fab$l_dna = ".DIR;1";
1276 dirfab.fab$b_dns = 6;
1277 dirfab.fab$l_nam = &dirnam;
1278 dirnam.nam$b_ess = (unsigned char) sizeof esa - 1;
1279 dirnam.nam$l_esa = esa;
1281 for (cp = dir; *cp; cp++)
1282 if (islower(*cp)) { haslower = 1; break; }
1284 if (!(sts = (sys$parse(&dirfab)&1))) {
1285 if (dirfab.fab$l_sts == RMS$_DIR) {
1286 dirnam.nam$b_nop |= NAM$M_SYNCHK;
1287 sts = sys$parse(&dirfab) & 1;
1291 set_vaxc_errno(dirfab.fab$l_sts);
1297 if (!(sys$search(&dirfab)&1)) { /* Does the file really exist? */
1298 if (dirfab.fab$l_sts != RMS$_FNF) {
1300 set_vaxc_errno(dirfab.fab$l_sts);
1303 dirnam = savnam; /* No; just work with potential name */
1306 if (dirnam.nam$l_fnb & NAM$M_EXP_TYPE) { /* Was type specified? */
1307 /* Yep; check version while we're at it, if it's there. */
1308 cmplen = (dirnam.nam$l_fnb & NAM$M_EXP_VER) ? 6 : 4;
1309 if (strncmp(dirnam.nam$l_type,".DIR;1",cmplen)) {
1310 /* Something other than .DIR[;1]. Bzzt. */
1312 set_vaxc_errno(RMS$_DIR);
1316 /* OK, the type was fine. Now pull any file name into the
1318 if ((cp1 = strrchr(esa,']'))) *dirnam.nam$l_type = ']';
1320 cp1 = strrchr(esa,'>');
1321 *dirnam.nam$l_type = '>';
1324 *(dirnam.nam$l_type + 1) = '\0';
1325 retlen = dirnam.nam$l_type - esa + 2;
1326 if (buf) retpath = buf;
1327 else if (ts) New(7014,retpath,retlen,char);
1328 else retpath = __pathify_retbuf;
1329 strcpy(retpath,esa);
1330 /* $PARSE may have upcased filespec, so convert output to lower
1331 * case if input contained any lowercase characters. */
1332 if (haslower) __mystrtolower(retpath);
1336 } /* end of do_pathify_dirspec() */
1338 /* External entry points */
1339 char *pathify_dirspec(char *dir, char *buf)
1340 { return do_pathify_dirspec(dir,buf,0); }
1341 char *pathify_dirspec_ts(char *dir, char *buf)
1342 { return do_pathify_dirspec(dir,buf,1); }
1344 /*{{{ char *tounixspec[_ts](char *path, char *buf)*/
1345 static char *do_tounixspec(char *spec, char *buf, int ts)
1347 static char __tounixspec_retbuf[NAM$C_MAXRSS+1];
1348 char *dirend, *rslt, *cp1, *cp2, *cp3, tmp[NAM$C_MAXRSS+1];
1349 int devlen, dirlen, retlen = NAM$C_MAXRSS+1, expand = 0;
1351 if (spec == NULL) return NULL;
1352 if (strlen(spec) > NAM$C_MAXRSS) return NULL;
1353 if (buf) rslt = buf;
1355 retlen = strlen(spec);
1356 cp1 = strchr(spec,'[');
1357 if (!cp1) cp1 = strchr(spec,'<');
1359 for (cp1++; *cp1; cp1++) {
1360 if (*cp1 == '-') expand++; /* VMS '-' ==> Unix '../' */
1361 if (*cp1 == '.' && *(cp1+1) == '.' && *(cp1+2) == '.')
1362 { expand++; cp1 +=2; } /* VMS '...' ==> Unix '/.../' */
1365 New(7015,rslt,retlen+2+2*expand,char);
1367 else rslt = __tounixspec_retbuf;
1368 if (strchr(spec,'/') != NULL) {
1375 dirend = strrchr(spec,']');
1376 if (dirend == NULL) dirend = strrchr(spec,'>');
1377 if (dirend == NULL) dirend = strchr(spec,':');
1378 if (dirend == NULL) {
1382 if (*cp2 != '[' && *cp2 != '<') {
1385 else { /* the VMS spec begins with directories */
1387 if (*cp2 == ']' || *cp2 == '>') {
1388 *(cp1++) = '.'; *(cp1++) = '/'; *(cp1++) = '\0';
1391 else if ( *cp2 != '.' && *cp2 != '-') { /* add the implied device */
1392 if (getcwd(tmp,sizeof tmp,1) == NULL) {
1393 if (ts) Safefree(rslt);
1398 while (*cp3 != ':' && *cp3) cp3++;
1400 if (strchr(cp3,']') != NULL) break;
1401 } while (((cp3 = my_getenv(tmp)) != NULL) && strcpy(tmp,cp3));
1403 ((devlen = strlen(tmp)) + (dirlen = strlen(cp2)) + 1 > retlen)) {
1404 retlen = devlen + dirlen;
1405 Renew(rslt,retlen+1+2*expand,char);
1411 *(cp1++) = *(cp3++);
1412 if (cp1 - rslt > NAM$C_MAXRSS && !ts && !buf) return NULL; /* No room */
1416 else if ( *cp2 == '.') {
1417 if (*(cp2+1) == '.' && *(cp2+2) == '.') {
1418 *(cp1++) = '.'; *(cp1++) = '.'; *(cp1++) = '.'; *(cp1++) = '/';
1424 for (; cp2 <= dirend; cp2++) {
1427 if (*(cp2+1) == '[') cp2++;
1429 else if (*cp2 == ']' || *cp2 == '>') {
1430 if (*(cp1-1) != '/') *(cp1++) = '/'; /* Don't double after ellipsis */
1432 else if (*cp2 == '.') {
1434 if (*(cp2+1) == ']' || *(cp2+1) == '>') {
1435 while (*(cp2+1) == ']' || *(cp2+1) == '>' ||
1436 *(cp2+1) == '[' || *(cp2+1) == '<') cp2++;
1437 if (!strncmp(cp2,"[000000",7) && (*(cp2+7) == ']' ||
1438 *(cp2+7) == '>' || *(cp2+7) == '.')) cp2 += 7;
1440 else if ( *(cp2+1) == '.' && *(cp2+2) == '.') {
1441 *(cp1++) = '.'; *(cp1++) = '.'; *(cp1++) = '.'; *(cp1++) ='/';
1445 else if (*cp2 == '-') {
1446 if (*(cp2-1) == '[' || *(cp2-1) == '<' || *(cp2-1) == '.') {
1447 while (*cp2 == '-') {
1449 *(cp1++) = '.'; *(cp1++) = '.'; *(cp1++) = '/';
1451 if (*cp2 != '.' && *cp2 != ']' && *cp2 != '>') { /* we don't allow */
1452 if (ts) Safefree(rslt); /* filespecs like */
1453 set_errno(EINVAL); set_vaxc_errno(RMS$_SYN); /* [fred.--foo.bar] */
1457 else *(cp1++) = *cp2;
1459 else *(cp1++) = *cp2;
1461 while (*cp2) *(cp1++) = *(cp2++);
1466 } /* end of do_tounixspec() */
1468 /* External entry points */
1469 char *tounixspec(char *spec, char *buf) { return do_tounixspec(spec,buf,0); }
1470 char *tounixspec_ts(char *spec, char *buf) { return do_tounixspec(spec,buf,1); }
1472 /*{{{ char *tovmsspec[_ts](char *path, char *buf)*/
1473 static char *do_tovmsspec(char *path, char *buf, int ts) {
1474 static char __tovmsspec_retbuf[NAM$C_MAXRSS+1];
1475 char *rslt, *dirend;
1476 register char *cp1, *cp2;
1477 unsigned long int infront = 0, hasdir = 1;
1479 if (path == NULL) return NULL;
1480 if (buf) rslt = buf;
1481 else if (ts) New(7016,rslt,strlen(path)+9,char);
1482 else rslt = __tovmsspec_retbuf;
1483 if (strpbrk(path,"]:>") ||
1484 (dirend = strrchr(path,'/')) == NULL) {
1485 if (path[0] == '.') {
1486 if (path[1] == '\0') strcpy(rslt,"[]");
1487 else if (path[1] == '.' && path[2] == '\0') strcpy(rslt,"[-]");
1488 else strcpy(rslt,path); /* probably garbage */
1490 else strcpy(rslt,path);
1493 if (*(dirend+1) == '.') { /* do we have trailing "/." or "/.." or "/..."? */
1494 if (!*(dirend+2)) dirend +=2;
1495 if (*(dirend+2) == '.' && !*(dirend+3)) dirend += 3;
1496 if (*(dirend+2) == '.' && *(dirend+3) == '.' && !*(dirend+4)) dirend += 4;
1501 char trndev[NAM$C_MAXRSS+1];
1505 while (*(cp2+1) == '/') cp2++; /* Skip multiple /s */
1506 while (*(++cp2) != '/' && *cp2) *(cp1++) = *cp2;
1508 islnm = my_trnlnm(rslt,trndev,0);
1509 trnend = islnm ? strlen(trndev) - 1 : 0;
1510 islnm = trnend ? (trndev[trnend] == ']' || trndev[trnend] == '>') : 0;
1511 rooted = islnm ? (trndev[trnend-1] == '.') : 0;
1512 /* If the first element of the path is a logical name, determine
1513 * whether it has to be translated so we can add more directories. */
1514 if (!islnm || rooted) {
1517 if (cp2 == dirend) while (infront++ < 6) *(cp1++) = '0';
1521 if (cp2 != dirend) {
1522 if (!buf && ts) Renew(rslt,strlen(path)-strlen(rslt)+trnend+4,char);
1523 strcpy(rslt,trndev);
1524 cp1 = rslt + trnend;
1537 if (*(cp2+1) == '/' || *(cp2+1) == '\0') {
1538 cp2 += 2; /* skip over "./" - it's redundant */
1539 *(cp1++) = '.'; /* but it does indicate a relative dirspec */
1541 else if (*(cp2+1) == '.' && (*(cp2+2) == '/' || *(cp2+2) == '\0')) {
1542 *(cp1++) = '-'; /* "../" --> "-" */
1545 else if (*(cp2+1) == '.' && *(cp2+2) == '.' &&
1546 (*(cp2+3) == '/' || *(cp2+3) == '\0')) {
1547 *(cp1++) = '.'; *(cp1++) = '.'; *(cp1++) = '.'; /* ".../" --> "..." */
1548 if (!*(cp2+4)) *(cp1++) = '.'; /* Simulate trailing '/' for later */
1551 if (cp2 > dirend) cp2 = dirend;
1553 else *(cp1++) = '.';
1555 for (; cp2 < dirend; cp2++) {
1557 if (*(cp2-1) == '/') continue;
1558 if (*(cp1-1) != '.') *(cp1++) = '.';
1561 else if (!infront && *cp2 == '.') {
1562 if (cp2+1 == dirend || *(cp2+1) == '\0') { cp2++; break; }
1563 else if (*(cp2+1) == '/') cp2++; /* skip over "./" - it's redundant */
1564 else if (*(cp2+1) == '.' && (*(cp2+2) == '/' || *(cp2+2) == '\0')) {
1565 if (*(cp1-1) == '-' || *(cp1-1) == '[') *(cp1++) = '-'; /* handle "../" */
1566 else if (*(cp1-2) == '[') *(cp1-1) = '-';
1567 else { /* back up over previous directory name */
1569 while (*(cp1-1) != '.' && *(cp1-1) != '[') cp1--;
1570 if (*(cp1-1) == '[') {
1571 memcpy(cp1,"000000.",7);
1576 if (cp2 == dirend) break;
1578 else if ( *(cp2+1) == '.' && *(cp2+2) == '.' &&
1579 (*(cp2+3) == '/' || *(cp2+3) == '\0') ) {
1580 if (*(cp1-1) != '.') *(cp1++) = '.'; /* May already have 1 from '/' */
1581 *(cp1++) = '.'; *(cp1++) = '.'; /* ".../" --> "..." */
1583 *(cp1++) = '.'; /* Simulate trailing '/' */
1584 cp2 += 2; /* for loop will incr this to == dirend */
1586 else cp2 += 3; /* Trailing '/' was there, so skip it, too */
1588 else *(cp1++) = '_'; /* fix up syntax - '.' in name not allowed */
1591 if (!infront && *(cp1-1) == '-') *(cp1++) = '.';
1592 if (*cp2 == '.') *(cp1++) = '_';
1593 else *(cp1++) = *cp2;
1597 if (*(cp1-1) == '.') cp1--; /* Unix spec ending in '/' ==> trailing '.' */
1598 if (hasdir) *(cp1++) = ']';
1599 if (*cp2) cp2++; /* check in case we ended with trailing '..' */
1600 while (*cp2) *(cp1++) = *(cp2++);
1605 } /* end of do_tovmsspec() */
1607 /* External entry points */
1608 char *tovmsspec(char *path, char *buf) { return do_tovmsspec(path,buf,0); }
1609 char *tovmsspec_ts(char *path, char *buf) { return do_tovmsspec(path,buf,1); }
1611 /*{{{ char *tovmspath[_ts](char *path, char *buf)*/
1612 static char *do_tovmspath(char *path, char *buf, int ts) {
1613 static char __tovmspath_retbuf[NAM$C_MAXRSS+1];
1615 char pathified[NAM$C_MAXRSS+1], vmsified[NAM$C_MAXRSS+1], *cp;
1617 if (path == NULL) return NULL;
1618 if (do_pathify_dirspec(path,pathified,0) == NULL) return NULL;
1619 if (do_tovmsspec(pathified,buf ? buf : vmsified,0) == NULL) return NULL;
1620 if (buf) return buf;
1622 vmslen = strlen(vmsified);
1623 New(7017,cp,vmslen+1,char);
1624 memcpy(cp,vmsified,vmslen);
1629 strcpy(__tovmspath_retbuf,vmsified);
1630 return __tovmspath_retbuf;
1633 } /* end of do_tovmspath() */
1635 /* External entry points */
1636 char *tovmspath(char *path, char *buf) { return do_tovmspath(path,buf,0); }
1637 char *tovmspath_ts(char *path, char *buf) { return do_tovmspath(path,buf,1); }
1640 /*{{{ char *tounixpath[_ts](char *path, char *buf)*/
1641 static char *do_tounixpath(char *path, char *buf, int ts) {
1642 static char __tounixpath_retbuf[NAM$C_MAXRSS+1];
1644 char pathified[NAM$C_MAXRSS+1], unixified[NAM$C_MAXRSS+1], *cp;
1646 if (path == NULL) return NULL;
1647 if (do_pathify_dirspec(path,pathified,0) == NULL) return NULL;
1648 if (do_tounixspec(pathified,buf ? buf : unixified,0) == NULL) return NULL;
1649 if (buf) return buf;
1651 unixlen = strlen(unixified);
1652 New(7017,cp,unixlen+1,char);
1653 memcpy(cp,unixified,unixlen);
1658 strcpy(__tounixpath_retbuf,unixified);
1659 return __tounixpath_retbuf;
1662 } /* end of do_tounixpath() */
1664 /* External entry points */
1665 char *tounixpath(char *path, char *buf) { return do_tounixpath(path,buf,0); }
1666 char *tounixpath_ts(char *path, char *buf) { return do_tounixpath(path,buf,1); }
1669 * @(#)argproc.c 2.2 94/08/16 Mark Pizzolato (mark@infocomm.com)
1671 *****************************************************************************
1673 * Copyright (C) 1989-1994 by *
1674 * Mark Pizzolato - INFO COMM, Danville, California (510) 837-5600 *
1676 * Permission is hereby granted for the reproduction of this software, *
1677 * on condition that this copyright notice is included in the reproduction, *
1678 * and that such reproduction is not for purposes of profit or material *
1681 * 27-Aug-1994 Modified for inclusion in perl5 *
1682 * by Charles Bailey bailey@genetics.upenn.edu *
1683 *****************************************************************************
1687 * getredirection() is intended to aid in porting C programs
1688 * to VMS (Vax-11 C). The native VMS environment does not support
1689 * '>' and '<' I/O redirection, or command line wild card expansion,
1690 * or a command line pipe mechanism using the '|' AND background
1691 * command execution '&'. All of these capabilities are provided to any
1692 * C program which calls this procedure as the first thing in the
1694 * The piping mechanism will probably work with almost any 'filter' type
1695 * of program. With suitable modification, it may useful for other
1696 * portability problems as well.
1698 * Author: Mark Pizzolato mark@infocomm.com
1702 struct list_item *next;
1706 static void add_item(struct list_item **head,
1707 struct list_item **tail,
1711 static void expand_wild_cards(char *item,
1712 struct list_item **head,
1713 struct list_item **tail,
1716 static int background_process(int argc, char **argv);
1718 static void pipe_and_fork(char **cmargv);
1720 /*{{{ void getredirection(int *ac, char ***av)*/
1722 getredirection(int *ac, char ***av)
1724 * Process vms redirection arg's. Exit if any error is seen.
1725 * If getredirection() processes an argument, it is erased
1726 * from the vector. getredirection() returns a new argc and argv value.
1727 * In the event that a background command is requested (by a trailing "&"),
1728 * this routine creates a background subprocess, and simply exits the program.
1730 * Warning: do not try to simplify the code for vms. The code
1731 * presupposes that getredirection() is called before any data is
1732 * read from stdin or written to stdout.
1734 * Normal usage is as follows:
1740 * getredirection(&argc, &argv);
1744 int argc = *ac; /* Argument Count */
1745 char **argv = *av; /* Argument Vector */
1746 char *ap; /* Argument pointer */
1747 int j; /* argv[] index */
1748 int item_count = 0; /* Count of Items in List */
1749 struct list_item *list_head = 0; /* First Item in List */
1750 struct list_item *list_tail; /* Last Item in List */
1751 char *in = NULL; /* Input File Name */
1752 char *out = NULL; /* Output File Name */
1753 char *outmode = "w"; /* Mode to Open Output File */
1754 char *err = NULL; /* Error File Name */
1755 char *errmode = "w"; /* Mode to Open Error File */
1756 int cmargc = 0; /* Piped Command Arg Count */
1757 char **cmargv = NULL;/* Piped Command Arg Vector */
1760 * First handle the case where the last thing on the line ends with
1761 * a '&'. This indicates the desire for the command to be run in a
1762 * subprocess, so we satisfy that desire.
1765 if (0 == strcmp("&", ap))
1766 exit(background_process(--argc, argv));
1767 if (*ap && '&' == ap[strlen(ap)-1])
1769 ap[strlen(ap)-1] = '\0';
1770 exit(background_process(argc, argv));
1773 * Now we handle the general redirection cases that involve '>', '>>',
1774 * '<', and pipes '|'.
1776 for (j = 0; j < argc; ++j)
1778 if (0 == strcmp("<", argv[j]))
1782 PerlIO_printf(Perl_debug_log,"No input file after < on command line");
1783 exit(LIB$_WRONUMARG);
1788 if ('<' == *(ap = argv[j]))
1793 if (0 == strcmp(">", ap))
1797 PerlIO_printf(Perl_debug_log,"No output file after > on command line");
1798 exit(LIB$_WRONUMARG);
1817 PerlIO_printf(Perl_debug_log,"No output file after > or >> on command line");
1818 exit(LIB$_WRONUMARG);
1822 if (('2' == *ap) && ('>' == ap[1]))
1839 PerlIO_printf(Perl_debug_log,"No output file after 2> or 2>> on command line");
1840 exit(LIB$_WRONUMARG);
1844 if (0 == strcmp("|", argv[j]))
1848 PerlIO_printf(Perl_debug_log,"No command into which to pipe on command line");
1849 exit(LIB$_WRONUMARG);
1851 cmargc = argc-(j+1);
1852 cmargv = &argv[j+1];
1856 if ('|' == *(ap = argv[j]))
1864 expand_wild_cards(ap, &list_head, &list_tail, &item_count);
1867 * Allocate and fill in the new argument vector, Some Unix's terminate
1868 * the list with an extra null pointer.
1870 New(7002, argv, item_count+1, char *);
1872 for (j = 0; j < item_count; ++j, list_head = list_head->next)
1873 argv[j] = list_head->value;
1879 PerlIO_printf(Perl_debug_log,"'|' and '>' may not both be specified on command line");
1880 exit(LIB$_INVARGORD);
1882 pipe_and_fork(cmargv);
1885 /* Check for input from a pipe (mailbox) */
1887 if (in == NULL && 1 == isapipe(0))
1889 char mbxname[L_tmpnam];
1891 long int dvi_item = DVI$_DEVBUFSIZ;
1892 $DESCRIPTOR(mbxnam, "");
1893 $DESCRIPTOR(mbxdevnam, "");
1895 /* Input from a pipe, reopen it in binary mode to disable */
1896 /* carriage control processing. */
1898 PerlIO_getname(stdin, mbxname);
1899 mbxnam.dsc$a_pointer = mbxname;
1900 mbxnam.dsc$w_length = strlen(mbxnam.dsc$a_pointer);
1901 lib$getdvi(&dvi_item, 0, &mbxnam, &bufsize, 0, 0);
1902 mbxdevnam.dsc$a_pointer = mbxname;
1903 mbxdevnam.dsc$w_length = sizeof(mbxname);
1904 dvi_item = DVI$_DEVNAM;
1905 lib$getdvi(&dvi_item, 0, &mbxnam, 0, &mbxdevnam, &mbxdevnam.dsc$w_length);
1906 mbxdevnam.dsc$a_pointer[mbxdevnam.dsc$w_length] = '\0';
1909 freopen(mbxname, "rb", stdin);
1912 PerlIO_printf(Perl_debug_log,"Can't reopen input pipe (name: %s) in binary mode",mbxname);
1916 if ((in != NULL) && (NULL == freopen(in, "r", stdin, "mbc=32", "mbf=2")))
1918 PerlIO_printf(Perl_debug_log,"Can't open input file %s as stdin",in);
1921 if ((out != NULL) && (NULL == freopen(out, outmode, stdout, "mbc=32", "mbf=2")))
1923 PerlIO_printf(Perl_debug_log,"Can't open output file %s as stdout",out);
1928 if (NULL == (tmperr = fopen(err, errmode, "mbc=32", "mbf=2")))
1930 PerlIO_printf(Perl_debug_log,"Can't open error file %s as stderr",err);
1934 if (NULL == freopen(err, "a", Perl_debug_log, "mbc=32", "mbf=2"))
1939 #ifdef ARGPROC_DEBUG
1940 PerlIO_printf(Perl_debug_log, "Arglist:\n");
1941 for (j = 0; j < *ac; ++j)
1942 PerlIO_printf(Perl_debug_log, "argv[%d] = '%s'\n", j, argv[j]);
1944 /* Clear errors we may have hit expanding wildcards, so they don't
1945 show up in Perl's $! later */
1946 set_errno(0); set_vaxc_errno(1);
1947 } /* end of getredirection() */
1950 static void add_item(struct list_item **head,
1951 struct list_item **tail,
1957 New(7003,*head,1,struct list_item);
1961 New(7004,(*tail)->next,1,struct list_item);
1962 *tail = (*tail)->next;
1964 (*tail)->value = value;
1968 static void expand_wild_cards(char *item,
1969 struct list_item **head,
1970 struct list_item **tail,
1974 unsigned long int context = 0;
1980 char vmsspec[NAM$C_MAXRSS+1];
1981 $DESCRIPTOR(filespec, "");
1982 $DESCRIPTOR(defaultspec, "SYS$DISK:[]");
1983 $DESCRIPTOR(resultspec, "");
1984 unsigned long int zero = 0, sts;
1986 if (strcspn(item, "*%") == strlen(item) || strchr(item,' ') != NULL)
1988 add_item(head, tail, item, count);
1991 resultspec.dsc$b_dtype = DSC$K_DTYPE_T;
1992 resultspec.dsc$b_class = DSC$K_CLASS_D;
1993 resultspec.dsc$a_pointer = NULL;
1994 if ((isunix = (int) strchr(item,'/')) != (int) NULL)
1995 filespec.dsc$a_pointer = do_tovmsspec(item,vmsspec,0);
1996 if (!isunix || !filespec.dsc$a_pointer)
1997 filespec.dsc$a_pointer = item;
1998 filespec.dsc$w_length = strlen(filespec.dsc$a_pointer);
2000 * Only return version specs, if the caller specified a version
2002 had_version = strchr(item, ';');
2004 * Only return device and directory specs, if the caller specifed either.
2006 had_device = strchr(item, ':');
2007 had_directory = (isunix || NULL != strchr(item, '[')) || (NULL != strchr(item, '<'));
2009 while (1 == (1 & (sts = lib$find_file(&filespec, &resultspec, &context,
2010 &defaultspec, 0, 0, &zero))))
2015 New(7005,string,resultspec.dsc$w_length+1,char);
2016 strncpy(string, resultspec.dsc$a_pointer, resultspec.dsc$w_length);
2017 string[resultspec.dsc$w_length] = '\0';
2018 if (NULL == had_version)
2019 *((char *)strrchr(string, ';')) = '\0';
2020 if ((!had_directory) && (had_device == NULL))
2022 if (NULL == (devdir = strrchr(string, ']')))
2023 devdir = strrchr(string, '>');
2024 strcpy(string, devdir + 1);
2027 * Be consistent with what the C RTL has already done to the rest of
2028 * the argv items and lowercase all of these names.
2030 for (c = string; *c; ++c)
2033 if (isunix) trim_unixpath(string,item,1);
2034 add_item(head, tail, string, count);
2037 if (sts != RMS$_NMF)
2039 set_vaxc_errno(sts);
2045 set_errno(ENOENT); break;
2047 set_errno(ENODEV); break;
2050 set_errno(EINVAL); break;
2052 set_errno(EACCES); break;
2054 _ckvmssts_noperl(sts);
2058 add_item(head, tail, item, count);
2059 _ckvmssts_noperl(lib$sfree1_dd(&resultspec));
2060 _ckvmssts_noperl(lib$find_file_end(&context));
2063 static int child_st[2];/* Event Flag set when child process completes */
2065 static unsigned short child_chan;/* I/O Channel for Pipe Mailbox */
2067 static unsigned long int exit_handler(int *status)
2071 if (0 == child_st[0])
2073 #ifdef ARGPROC_DEBUG
2074 PerlIO_printf(Perl_debug_log, "Waiting for Child Process to Finish . . .\n");
2076 fflush(stdout); /* Have to flush pipe for binary data to */
2077 /* terminate properly -- <tp@mccall.com> */
2078 sys$qiow(0, child_chan, IO$_WRITEOF, iosb, 0, 0, 0, 0, 0, 0, 0, 0);
2079 sys$dassgn(child_chan);
2081 sys$synch(0, child_st);
2086 static void sig_child(int chan)
2088 #ifdef ARGPROC_DEBUG
2089 PerlIO_printf(Perl_debug_log, "Child Completion AST\n");
2091 if (child_st[0] == 0)
2095 static struct exit_control_block exit_block =
2100 &exit_block.exit_status,
2104 static void pipe_and_fork(char **cmargv)
2107 $DESCRIPTOR(cmddsc, "");
2108 static char mbxname[64];
2109 $DESCRIPTOR(mbxdsc, mbxname);
2111 unsigned long int zero = 0, one = 1;
2113 strcpy(subcmd, cmargv[0]);
2114 for (j = 1; NULL != cmargv[j]; ++j)
2116 strcat(subcmd, " \"");
2117 strcat(subcmd, cmargv[j]);
2118 strcat(subcmd, "\"");
2120 cmddsc.dsc$a_pointer = subcmd;
2121 cmddsc.dsc$w_length = strlen(cmddsc.dsc$a_pointer);
2123 create_mbx(&child_chan,&mbxdsc);
2124 #ifdef ARGPROC_DEBUG
2125 PerlIO_printf(Perl_debug_log, "Pipe Mailbox Name = '%s'\n", mbxdsc.dsc$a_pointer);
2126 PerlIO_printf(Perl_debug_log, "Sub Process Command = '%s'\n", cmddsc.dsc$a_pointer);
2128 _ckvmssts_noperl(lib$spawn(&cmddsc, &mbxdsc, 0, &one,
2129 0, &pid, child_st, &zero, sig_child,
2131 #ifdef ARGPROC_DEBUG
2132 PerlIO_printf(Perl_debug_log, "Subprocess's Pid = %08X\n", pid);
2134 sys$dclexh(&exit_block);
2135 if (NULL == freopen(mbxname, "wb", stdout))
2137 PerlIO_printf(Perl_debug_log,"Can't open output pipe (name %s)",mbxname);
2141 static int background_process(int argc, char **argv)
2143 char command[2048] = "$";
2144 $DESCRIPTOR(value, "");
2145 static $DESCRIPTOR(cmd, "BACKGROUND$COMMAND");
2146 static $DESCRIPTOR(null, "NLA0:");
2147 static $DESCRIPTOR(pidsymbol, "SHELL_BACKGROUND_PID");
2149 $DESCRIPTOR(pidstr, "");
2151 unsigned long int flags = 17, one = 1, retsts;
2153 strcat(command, argv[0]);
2156 strcat(command, " \"");
2157 strcat(command, *(++argv));
2158 strcat(command, "\"");
2160 value.dsc$a_pointer = command;
2161 value.dsc$w_length = strlen(value.dsc$a_pointer);
2162 _ckvmssts_noperl(lib$set_symbol(&cmd, &value));
2163 retsts = lib$spawn(&cmd, &null, 0, &flags, 0, &pid);
2164 if (retsts == 0x38250) { /* DCL-W-NOTIFY - We must be BATCH, so retry */
2165 _ckvmssts_noperl(lib$spawn(&cmd, &null, 0, &one, 0, &pid));
2168 _ckvmssts_noperl(retsts);
2170 #ifdef ARGPROC_DEBUG
2171 PerlIO_printf(Perl_debug_log, "%s\n", command);
2173 sprintf(pidstring, "%08X", pid);
2174 PerlIO_printf(Perl_debug_log, "%s\n", pidstring);
2175 pidstr.dsc$a_pointer = pidstring;
2176 pidstr.dsc$w_length = strlen(pidstr.dsc$a_pointer);
2177 lib$set_symbol(&pidsymbol, &pidstr);
2181 /***** End of code taken from Mark Pizzolato's argproc.c package *****/
2184 * Trim Unix-style prefix off filespec, so it looks like what a shell
2185 * glob expansion would return (i.e. from specified prefix on, not
2186 * full path). Note that returned filespec is Unix-style, regardless
2187 * of whether input filespec was VMS-style or Unix-style.
2189 * fspec is filespec to be trimmed, and wildspec is wildcard spec used to
2190 * determine prefix (both may be in VMS or Unix syntax). opts is a bit
2191 * vector of options; at present, only bit 0 is used, and if set tells
2192 * trim unixpath to try the current default directory as a prefix when
2193 * presented with a possibly ambiguous ... wildcard.
2195 * Returns !=0 on success, with trimmed filespec replacing contents of
2196 * fspec, and 0 on failure, with contents of fpsec unchanged.
2198 /*{{{int trim_unixpath(char *fspec, char *wildspec, int opts)*/
2200 trim_unixpath(char *fspec, char *wildspec, int opts)
2202 char unixified[NAM$C_MAXRSS+1], unixwild[NAM$C_MAXRSS+1],
2203 *template, *base, *end, *cp1, *cp2;
2204 register int tmplen, reslen = 0, dirs = 0;
2206 if (!wildspec || !fspec) return 0;
2207 if (strpbrk(wildspec,"]>:") != NULL) {
2208 if (do_tounixspec(wildspec,unixwild,0) == NULL) return 0;
2209 else template = unixwild;
2211 else template = wildspec;
2212 if (strpbrk(fspec,"]>:") != NULL) {
2213 if (do_tounixspec(fspec,unixified,0) == NULL) return 0;
2214 else base = unixified;
2215 /* reslen != 0 ==> we had to unixify resultant filespec, so we must
2216 * check to see that final result fits into (isn't longer than) fspec */
2217 reslen = strlen(fspec);
2221 /* No prefix or absolute path on wildcard, so nothing to remove */
2222 if (!*template || *template == '/') {
2223 if (base == fspec) return 1;
2224 tmplen = strlen(unixified);
2225 if (tmplen > reslen) return 0; /* not enough space */
2226 /* Copy unixified resultant, including trailing NUL */
2227 memmove(fspec,unixified,tmplen+1);
2231 for (end = base; *end; end++) ; /* Find end of resultant filespec */
2232 if ((cp1 = strstr(template,".../")) == NULL) { /* No ...; just count elts */
2233 for (cp1 = template; *cp1; cp1++) if (*cp1 == '/') dirs++;
2234 for (cp1 = end ;cp1 >= base; cp1--)
2235 if ((*cp1 == '/') && !dirs--) /* postdec so we get front of rel path */
2237 if (cp1 != fspec) memmove(fspec,cp1, end - cp1 + 1);
2241 char tpl[NAM$C_MAXRSS+1], lcres[NAM$C_MAXRSS+1];
2242 char *front, *nextell, *lcend, *lcfront, *ellipsis = cp1;
2243 int ells = 1, totells, segdirs, match;
2244 struct dsc$descriptor_s wilddsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, tpl},
2245 resdsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};
2247 while ((cp1 = strstr(ellipsis+4,".../")) != NULL) {ellipsis = cp1; ells++;}
2249 for (cp1 = ellipsis+4; *cp1; cp1++) if (*cp1 == '/') dirs++;
2250 if (ellipsis == template && opts & 1) {
2251 /* Template begins with an ellipsis. Since we can't tell how many
2252 * directory names at the front of the resultant to keep for an
2253 * arbitrary starting point, we arbitrarily choose the current
2254 * default directory as a starting point. If it's there as a prefix,
2255 * clip it off. If not, fall through and act as if the leading
2256 * ellipsis weren't there (i.e. return shortest possible path that
2257 * could match template).
2259 if (getcwd(tpl, sizeof tpl,0) == NULL) return 0;
2260 for (cp1 = tpl, cp2 = base; *cp1 && *cp2; cp1++,cp2++)
2261 if (_tolower(*cp1) != _tolower(*cp2)) break;
2262 segdirs = dirs - totells; /* Min # of dirs we must have left */
2263 for (front = cp2+1; *front; front++) if (*front == '/') segdirs--;
2264 if (*cp1 == '\0' && *cp2 == '/' && segdirs < 1) {
2265 memcpy(fspec,cp2+1,end - cp2);
2269 /* First off, back up over constant elements at end of path */
2271 for (front = end ; front >= base; front--)
2272 if (*front == '/' && !dirs--) { front++; break; }
2274 for (cp1=template,cp2=lcres; *cp1 && cp2 <= lcend + sizeof lcend;
2275 cp1++,cp2++) *cp2 = _tolower(*cp1); /* Make lc copy for match */
2276 if (cp1 != '\0') return 0; /* Path too long. */
2278 *cp2 = '\0'; /* Pick up with memcpy later */
2279 lcfront = lcres + (front - base);
2280 /* Now skip over each ellipsis and try to match the path in front of it. */
2282 for (cp1 = ellipsis - 2; cp1 >= template; cp1--)
2283 if (*(cp1) == '.' && *(cp1+1) == '.' &&
2284 *(cp1+2) == '.' && *(cp1+3) == '/' ) break;
2285 if (cp1 < template) break; /* template started with an ellipsis */
2286 if (cp1 + 4 == ellipsis) { /* Consecutive ellipses */
2287 ellipsis = cp1; continue;
2289 wilddsc.dsc$w_length = resdsc.dsc$w_length = ellipsis - 1 - cp1;
2291 for (segdirs = 0, cp2 = tpl;
2292 cp1 <= ellipsis - 1 && cp2 <= tpl + sizeof tpl;
2294 if (*cp1 == '?') *cp2 = '%'; /* Substitute VMS' wildcard for Unix' */
2295 else *cp2 = _tolower(*cp1); /* else lowercase for match */
2296 if (*cp2 == '/') segdirs++;
2298 if (cp1 != ellipsis - 1) return 0; /* Path too long */
2299 /* Back up at least as many dirs as in template before matching */
2300 for (cp1 = lcfront - 1; segdirs && cp1 >= lcres; cp1--)
2301 if (*cp1 == '/' && !segdirs--) { cp1++; break; }
2302 for (match = 0; cp1 > lcres;) {
2303 resdsc.dsc$a_pointer = cp1;
2304 if (str$match_wild(&wilddsc,&resdsc) == STR$_MATCH) {
2306 if (match == 1) lcfront = cp1;
2308 for ( ; cp1 >= lcres; cp1--) if (*cp1 == '/') { cp1++; break; }
2310 if (!match) return 0; /* Can't find prefix ??? */
2311 if (match > 1 && opts & 1) {
2312 /* This ... wildcard could cover more than one set of dirs (i.e.
2313 * a set of similar dir names is repeated). If the template
2314 * contains more than 1 ..., upstream elements could resolve the
2315 * ambiguity, but it's not worth a full backtracking setup here.
2316 * As a quick heuristic, clip off the current default directory
2317 * if it's present to find the trimmed spec, else use the
2318 * shortest string that this ... could cover.
2320 char def[NAM$C_MAXRSS+1], *st;
2322 if (getcwd(def, sizeof def,0) == NULL) return 0;
2323 for (cp1 = def, cp2 = base; *cp1 && *cp2; cp1++,cp2++)
2324 if (_tolower(*cp1) != _tolower(*cp2)) break;
2325 segdirs = dirs - totells; /* Min # of dirs we must have left */
2326 for (st = cp2+1; *st; st++) if (*st == '/') segdirs--;
2327 if (*cp1 == '\0' && *cp2 == '/') {
2328 memcpy(fspec,cp2+1,end - cp2);
2331 /* Nope -- stick with lcfront from above and keep going. */
2334 memcpy(fspec,base + (lcfront - lcres), lcend - lcfront + 1);
2339 } /* end of trim_unixpath() */
2344 * VMS readdir() routines.
2345 * Written by Rich $alz, <rsalz@bbn.com> in August, 1990.
2346 * This code has no copyright.
2348 * 21-Jul-1994 Charles Bailey bailey@genetics.upenn.edu
2349 * Minor modifications to original routines.
2352 /* Number of elements in vms_versions array */
2353 #define VERSIZE(e) (sizeof e->vms_versions / sizeof e->vms_versions[0])
2356 * Open a directory, return a handle for later use.
2358 /*{{{ DIR *opendir(char*name) */
2363 char dir[NAM$C_MAXRSS+1];
2365 /* Get memory for the handle, and the pattern. */
2367 if (do_tovmspath(name,dir,0) == NULL) {
2368 Safefree((char *)dd);
2371 New(7007,dd->pattern,strlen(dir)+sizeof "*.*" + 1,char);
2373 /* Fill in the fields; mainly playing with the descriptor. */
2374 (void)sprintf(dd->pattern, "%s*.*",dir);
2377 dd->vms_wantversions = 0;
2378 dd->pat.dsc$a_pointer = dd->pattern;
2379 dd->pat.dsc$w_length = strlen(dd->pattern);
2380 dd->pat.dsc$b_dtype = DSC$K_DTYPE_T;
2381 dd->pat.dsc$b_class = DSC$K_CLASS_S;
2384 } /* end of opendir() */
2388 * Set the flag to indicate we want versions or not.
2390 /*{{{ void vmsreaddirversions(DIR *dd, int flag)*/
2392 vmsreaddirversions(DIR *dd, int flag)
2394 dd->vms_wantversions = flag;
2399 * Free up an opened directory.
2401 /*{{{ void closedir(DIR *dd)*/
2405 (void)lib$find_file_end(&dd->context);
2406 Safefree(dd->pattern);
2407 Safefree((char *)dd);
2412 * Collect all the version numbers for the current file.
2418 struct dsc$descriptor_s pat;
2419 struct dsc$descriptor_s res;
2421 char *p, *text, buff[sizeof dd->entry.d_name];
2423 unsigned long context, tmpsts;
2425 /* Convenient shorthand. */
2428 /* Add the version wildcard, ignoring the "*.*" put on before */
2429 i = strlen(dd->pattern);
2430 New(7008,text,i + e->d_namlen + 3,char);
2431 (void)strcpy(text, dd->pattern);
2432 (void)sprintf(&text[i - 3], "%s;*", e->d_name);
2434 /* Set up the pattern descriptor. */
2435 pat.dsc$a_pointer = text;
2436 pat.dsc$w_length = i + e->d_namlen - 1;
2437 pat.dsc$b_dtype = DSC$K_DTYPE_T;
2438 pat.dsc$b_class = DSC$K_CLASS_S;
2440 /* Set up result descriptor. */
2441 res.dsc$a_pointer = buff;
2442 res.dsc$w_length = sizeof buff - 2;
2443 res.dsc$b_dtype = DSC$K_DTYPE_T;
2444 res.dsc$b_class = DSC$K_CLASS_S;
2446 /* Read files, collecting versions. */
2447 for (context = 0, e->vms_verscount = 0;
2448 e->vms_verscount < VERSIZE(e);
2449 e->vms_verscount++) {
2450 tmpsts = lib$find_file(&pat, &res, &context);
2451 if (tmpsts == RMS$_NMF || context == 0) break;
2453 buff[sizeof buff - 1] = '\0';
2454 if ((p = strchr(buff, ';')))
2455 e->vms_versions[e->vms_verscount] = atoi(p + 1);
2457 e->vms_versions[e->vms_verscount] = -1;
2460 _ckvmssts(lib$find_file_end(&context));
2463 } /* end of collectversions() */
2466 * Read the next entry from the directory.
2468 /*{{{ struct dirent *readdir(DIR *dd)*/
2472 struct dsc$descriptor_s res;
2473 char *p, buff[sizeof dd->entry.d_name];
2474 unsigned long int tmpsts;
2476 /* Set up result descriptor, and get next file. */
2477 res.dsc$a_pointer = buff;
2478 res.dsc$w_length = sizeof buff - 2;
2479 res.dsc$b_dtype = DSC$K_DTYPE_T;
2480 res.dsc$b_class = DSC$K_CLASS_S;
2481 tmpsts = lib$find_file(&dd->pat, &res, &dd->context);
2482 if ( tmpsts == RMS$_NMF || dd->context == 0) return NULL; /* None left. */
2483 if (!(tmpsts & 1)) {
2484 set_vaxc_errno(tmpsts);
2487 set_errno(EACCES); break;
2489 set_errno(ENODEV); break;
2492 set_errno(ENOENT); break;
2499 /* Force the buffer to end with a NUL, and downcase name to match C convention. */
2500 buff[sizeof buff - 1] = '\0';
2501 for (p = buff; !isspace(*p); p++) *p = _tolower(*p);
2504 /* Skip any directory component and just copy the name. */
2505 if ((p = strchr(buff, ']'))) (void)strcpy(dd->entry.d_name, p + 1);
2506 else (void)strcpy(dd->entry.d_name, buff);
2508 /* Clobber the version. */
2509 if ((p = strchr(dd->entry.d_name, ';'))) *p = '\0';
2511 dd->entry.d_namlen = strlen(dd->entry.d_name);
2512 dd->entry.vms_verscount = 0;
2513 if (dd->vms_wantversions) collectversions(dd);
2516 } /* end of readdir() */
2520 * Return something that can be used in a seekdir later.
2522 /*{{{ long telldir(DIR *dd)*/
2531 * Return to a spot where we used to be. Brute force.
2533 /*{{{ void seekdir(DIR *dd,long count)*/
2535 seekdir(DIR *dd, long count)
2537 int vms_wantversions;
2539 /* If we haven't done anything yet... */
2543 /* Remember some state, and clear it. */
2544 vms_wantversions = dd->vms_wantversions;
2545 dd->vms_wantversions = 0;
2546 _ckvmssts(lib$find_file_end(&dd->context));
2549 /* The increment is in readdir(). */
2550 for (dd->count = 0; dd->count < count; )
2553 dd->vms_wantversions = vms_wantversions;
2555 } /* end of seekdir() */
2558 /* VMS subprocess management
2560 * my_vfork() - just a vfork(), after setting a flag to record that
2561 * the current script is trying a Unix-style fork/exec.
2563 * vms_do_aexec() and vms_do_exec() are called in response to the
2564 * perl 'exec' function. If this follows a vfork call, then they
2565 * call out the the regular perl routines in doio.c which do an
2566 * execvp (for those who really want to try this under VMS).
2567 * Otherwise, they do exactly what the perl docs say exec should
2568 * do - terminate the current script and invoke a new command
2569 * (See below for notes on command syntax.)
2571 * do_aspawn() and do_spawn() implement the VMS side of the perl
2572 * 'system' function.
2574 * Note on command arguments to perl 'exec' and 'system': When handled
2575 * in 'VMSish fashion' (i.e. not after a call to vfork) The args
2576 * are concatenated to form a DCL command string. If the first arg
2577 * begins with '$' (i.e. the perl script had "\$ Type" or some such),
2578 * the the command string is hrnded off to DCL directly. Otherwise,
2579 * the first token of the command is taken as the filespec of an image
2580 * to run. The filespec is expanded using a default type of '.EXE' and
2581 * the process defaults for device, directory, etc., and the resultant
2582 * filespec is invoked using the DCL verb 'MCR', and passed the rest of
2583 * the command string as parameters. This is perhaps a bit compicated,
2584 * but I hope it will form a happy medium between what VMS folks expect
2585 * from lib$spawn and what Unix folks expect from exec.
2588 static int vfork_called;
2590 /*{{{int my_vfork()*/
2600 static struct dsc$descriptor_s VMScmd = {0,DSC$K_DTYPE_T,DSC$K_CLASS_S,Nullch};
2608 if (VMScmd.dsc$a_pointer) {
2609 Safefree(VMScmd.dsc$a_pointer);
2610 VMScmd.dsc$w_length = 0;
2611 VMScmd.dsc$a_pointer = Nullch;
2616 setup_argstr(SV *really, SV **mark, SV **sp)
2618 char *junk, *tmps = Nullch;
2619 register size_t cmdlen = 0;
2625 tmps = SvPV(really,rlen);
2632 for (idx++; idx <= sp; idx++) {
2634 junk = SvPVx(*idx,rlen);
2635 cmdlen += rlen ? rlen + 1 : 0;
2638 New(401,Cmd,cmdlen+1,char);
2640 if (tmps && *tmps) {
2645 while (++mark <= sp) {
2648 strcat(Cmd,SvPVx(*mark,na));
2653 } /* end of setup_argstr() */
2656 static unsigned long int
2657 setup_cmddsc(char *cmd, int check_img)
2659 char resspec[NAM$C_MAXRSS+1];
2660 $DESCRIPTOR(defdsc,".EXE");
2661 $DESCRIPTOR(resdsc,resspec);
2662 struct dsc$descriptor_s imgdsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};
2663 unsigned long int cxt = 0, flags = 1, retsts;
2664 register char *s, *rest, *cp;
2665 register int isdcl = 0;
2668 while (*s && isspace(*s)) s++;
2670 if (*s == '$') { /* Check whether this is a DCL command: leading $ and */
2671 isdcl = 1; /* no dev/dir separators (i.e. not a foreign command) */
2672 for (cp = s; *cp && *cp != '/' && !isspace(*cp); cp++) {
2673 if (*cp == ':' || *cp == '[' || *cp == '<') {
2681 if (isdcl) { /* It's a DCL command, just do it. */
2682 VMScmd.dsc$w_length = strlen(cmd);
2684 VMScmd.dsc$a_pointer = Cmd;
2685 Cmd = Nullch; /* Don't try to free twice in vms_execfree() */
2687 else VMScmd.dsc$a_pointer = savepvn(cmd,VMScmd.dsc$w_length);
2689 else { /* assume first token is an image spec */
2691 while (*s && !isspace(*s)) s++;
2693 imgdsc.dsc$a_pointer = cmd;
2694 imgdsc.dsc$w_length = s - cmd;
2695 retsts = lib$find_file(&imgdsc,&resdsc,&cxt,&defdsc,0,0,&flags);
2696 if (!(retsts & 1)) {
2697 /* just hand off status values likely to be due to user error */
2698 if (retsts == RMS$_FNF || retsts == RMS$_DNF ||
2699 retsts == RMS$_DEV || retsts == RMS$_DIR || retsts == RMS$_SYN ||
2700 (retsts & STS$M_CODE) == (SHR$_NOWILD & STS$M_CODE)) return retsts;
2701 else { _ckvmssts(retsts); }
2704 _ckvmssts(lib$find_file_end(&cxt));
2706 while (*s && !isspace(*s)) s++;
2708 New(402,VMScmd.dsc$a_pointer,7 + s - resspec + (rest ? strlen(rest) : 0),char);
2709 strcpy(VMScmd.dsc$a_pointer,"$ MCR ");
2710 strcat(VMScmd.dsc$a_pointer,resspec);
2711 if (rest) strcat(VMScmd.dsc$a_pointer,rest);
2712 VMScmd.dsc$w_length = strlen(VMScmd.dsc$a_pointer);
2716 return (VMScmd.dsc$w_length > 255 ? CLI$_BUFOVF : SS$_NORMAL);
2718 } /* end of setup_cmddsc() */
2721 /* {{{ bool vms_do_aexec(SV *really,SV **mark,SV **sp) */
2723 vms_do_aexec(SV *really,SV **mark,SV **sp)
2726 if (vfork_called) { /* this follows a vfork - act Unixish */
2728 if (vfork_called < 0) {
2729 warn("Internal inconsistency in tracking vforks");
2732 else return do_aexec(really,mark,sp);
2734 /* no vfork - act VMSish */
2735 return vms_do_exec(setup_argstr(really,mark,sp));
2740 } /* end of vms_do_aexec() */
2743 /* {{{bool vms_do_exec(char *cmd) */
2745 vms_do_exec(char *cmd)
2748 if (vfork_called) { /* this follows a vfork - act Unixish */
2750 if (vfork_called < 0) {
2751 warn("Internal inconsistency in tracking vforks");
2754 else return do_exec(cmd);
2757 { /* no vfork - act VMSish */
2758 unsigned long int retsts;
2761 TAINT_PROPER("exec");
2762 if ((retsts = setup_cmddsc(cmd,1)) & 1)
2763 retsts = lib$do_command(&VMScmd);
2766 set_vaxc_errno(retsts);
2768 warn("Can't exec \"%s\": %s", VMScmd.dsc$a_pointer, Strerror(errno));
2774 } /* end of vms_do_exec() */
2777 unsigned long int do_spawn(char *);
2779 /* {{{ unsigned long int do_aspawn(SV *really,SV **mark,SV **sp) */
2781 do_aspawn(SV *really,SV **mark,SV **sp)
2783 if (sp > mark) return do_spawn(setup_argstr(really,mark,sp));
2786 } /* end of do_aspawn() */
2789 /* {{{unsigned long int do_spawn(char *cmd) */
2793 unsigned long int substs, hadcmd = 1;
2796 TAINT_PROPER("spawn");
2797 if (!cmd || !*cmd) {
2799 _ckvmssts(lib$spawn(0,0,0,0,0,0,&substs,0,0,0,0,0,0));
2801 else if ((substs = setup_cmddsc(cmd,0)) & 1) {
2802 _ckvmssts(lib$spawn(&VMScmd,0,0,0,0,0,&substs,0,0,0,0,0,0));
2807 set_vaxc_errno(substs);
2809 warn("Can't spawn \"%s\": %s",
2810 hadcmd ? VMScmd.dsc$a_pointer : "", Strerror(errno));
2815 } /* end of do_spawn() */
2819 * A simple fwrite replacement which outputs itmsz*nitm chars without
2820 * introducing record boundaries every itmsz chars.
2822 /*{{{ int my_fwrite(void *src, size_t itmsz, size_t nitm, FILE *dest)*/
2824 my_fwrite(void *src, size_t itmsz, size_t nitm, FILE *dest)
2826 register char *cp, *end;
2828 end = (char *)src + itmsz * nitm;
2830 while ((char *)src <= end) {
2831 for (cp = src; cp <= end; cp++) if (!*cp) break;
2832 if (fputs(src,dest) == EOF) return EOF;
2834 if (fputc('\0',dest) == EOF) return EOF;
2840 } /* end of my_fwrite() */
2844 * Here are replacements for the following Unix routines in the VMS environment:
2845 * getpwuid Get information for a particular UIC or UID
2846 * getpwnam Get information for a named user
2847 * getpwent Get information for each user in the rights database
2848 * setpwent Reset search to the start of the rights database
2849 * endpwent Finish searching for users in the rights database
2851 * getpwuid, getpwnam, and getpwent return a pointer to the passwd structure
2852 * (defined in pwd.h), which contains the following fields:-
2854 * char *pw_name; Username (in lower case)
2855 * char *pw_passwd; Hashed password
2856 * unsigned int pw_uid; UIC
2857 * unsigned int pw_gid; UIC group number
2858 * char *pw_unixdir; Default device/directory (VMS-style)
2859 * char *pw_gecos; Owner name
2860 * char *pw_dir; Default device/directory (Unix-style)
2861 * char *pw_shell; Default CLI name (eg. DCL)
2863 * If the specified user does not exist, getpwuid and getpwnam return NULL.
2865 * pw_uid is the full UIC (eg. what's returned by stat() in st_uid).
2866 * not the UIC member number (eg. what's returned by getuid()),
2867 * getpwuid() can accept either as input (if uid is specified, the caller's
2868 * UIC group is used), though it won't recognise gid=0.
2870 * Note that in VMS it is necessary to have GRPPRV or SYSPRV to return
2871 * information about other users in your group or in other groups, respectively.
2872 * If the required privilege is not available, then these routines fill only
2873 * the pw_name, pw_uid, and pw_gid fields (the others point to an empty
2876 * By Tim Adye (T.J.Adye@rl.ac.uk), 10th February 1995.
2879 /* sizes of various UAF record fields */
2880 #define UAI$S_USERNAME 12
2881 #define UAI$S_IDENT 31
2882 #define UAI$S_OWNER 31
2883 #define UAI$S_DEFDEV 31
2884 #define UAI$S_DEFDIR 63
2885 #define UAI$S_DEFCLI 31
2888 #define valid_uic(uic) ((uic).uic$v_format == UIC$K_UIC_FORMAT && \
2889 (uic).uic$v_member != UIC$K_WILD_MEMBER && \
2890 (uic).uic$v_group != UIC$K_WILD_GROUP)
2892 static char __empty[]= "";
2893 static struct passwd __passwd_empty=
2894 {(char *) __empty, (char *) __empty, 0, 0,
2895 (char *) __empty, (char *) __empty, (char *) __empty, (char *) __empty};
2896 static int contxt= 0;
2897 static struct passwd __pwdcache;
2898 static char __pw_namecache[UAI$S_IDENT+1];
2901 * This routine does most of the work extracting the user information.
2903 static int fillpasswd (const char *name, struct passwd *pwd)
2906 unsigned char length;
2907 char pw_gecos[UAI$S_OWNER+1];
2909 static union uicdef uic;
2911 unsigned char length;
2912 char pw_dir[UAI$S_DEFDEV+UAI$S_DEFDIR+1];
2915 unsigned char length;
2916 char unixdir[UAI$_DEFDEV+UAI$S_DEFDIR+1];
2919 unsigned char length;
2920 char pw_shell[UAI$S_DEFCLI+1];
2922 static char pw_passwd[UAI$S_PWD+1];
2924 static unsigned short lowner, luic, ldefdev, ldefdir, ldefcli, lpwd;
2925 struct dsc$descriptor_s name_desc;
2926 unsigned long int sts;
2928 static struct itmlst_3 itmlst[]= {
2929 {UAI$S_OWNER+1, UAI$_OWNER, &owner, &lowner},
2930 {sizeof(uic), UAI$_UIC, &uic, &luic},
2931 {UAI$S_DEFDEV+1, UAI$_DEFDEV, &defdev, &ldefdev},
2932 {UAI$S_DEFDIR+1, UAI$_DEFDIR, &defdir, &ldefdir},
2933 {UAI$S_DEFCLI+1, UAI$_DEFCLI, &defcli, &ldefcli},
2934 {UAI$S_PWD, UAI$_PWD, pw_passwd, &lpwd},
2935 {0, 0, NULL, NULL}};
2937 name_desc.dsc$w_length= strlen(name);
2938 name_desc.dsc$b_dtype= DSC$K_DTYPE_T;
2939 name_desc.dsc$b_class= DSC$K_CLASS_S;
2940 name_desc.dsc$a_pointer= (char *) name;
2942 /* Note that sys$getuai returns many fields as counted strings. */
2943 sts= sys$getuai(0, 0, &name_desc, &itmlst, 0, 0, 0);
2944 if (sts == SS$_NOSYSPRV || sts == SS$_NOGRPPRV || sts == RMS$_RNF) {
2945 set_vaxc_errno(sts); set_errno(sts == RMS$_RNF ? EINVAL : EACCES);
2947 else { _ckvmssts(sts); }
2948 if (!(sts & 1)) return 0; /* out here in case _ckvmssts() doesn't abort */
2950 if ((int) owner.length < lowner) lowner= (int) owner.length;
2951 if ((int) defdev.length < ldefdev) ldefdev= (int) defdev.length;
2952 if ((int) defdir.length < ldefdir) ldefdir= (int) defdir.length;
2953 if ((int) defcli.length < ldefcli) ldefcli= (int) defcli.length;
2954 memcpy(&defdev.pw_dir[ldefdev], &defdir.unixdir[0], ldefdir);
2955 owner.pw_gecos[lowner]= '\0';
2956 defdev.pw_dir[ldefdev+ldefdir]= '\0';
2957 defcli.pw_shell[ldefcli]= '\0';
2958 if (valid_uic(uic)) {
2959 pwd->pw_uid= uic.uic$l_uic;
2960 pwd->pw_gid= uic.uic$v_group;
2963 warn("getpwnam returned invalid UIC %#o for user \"%s\"");
2964 pwd->pw_passwd= pw_passwd;
2965 pwd->pw_gecos= owner.pw_gecos;
2966 pwd->pw_dir= defdev.pw_dir;
2967 pwd->pw_unixdir= do_tounixpath(defdev.pw_dir, defdir.unixdir,1);
2968 pwd->pw_shell= defcli.pw_shell;
2969 if (pwd->pw_unixdir && pwd->pw_unixdir[0]) {
2971 ldir= strlen(pwd->pw_unixdir) - 1;
2972 if (pwd->pw_unixdir[ldir]=='/') pwd->pw_unixdir[ldir]= '\0';
2975 strcpy(pwd->pw_unixdir, pwd->pw_dir);
2976 __mystrtolower(pwd->pw_unixdir);
2981 * Get information for a named user.
2983 /*{{{struct passwd *getpwnam(char *name)*/
2984 struct passwd *my_getpwnam(char *name)
2986 struct dsc$descriptor_s name_desc;
2988 unsigned long int status, sts;
2990 __pwdcache = __passwd_empty;
2991 if (!fillpasswd(name, &__pwdcache)) {
2992 /* We still may be able to determine pw_uid and pw_gid */
2993 name_desc.dsc$w_length= strlen(name);
2994 name_desc.dsc$b_dtype= DSC$K_DTYPE_T;
2995 name_desc.dsc$b_class= DSC$K_CLASS_S;
2996 name_desc.dsc$a_pointer= (char *) name;
2997 if ((sts = sys$asctoid(&name_desc, &uic, 0)) == SS$_NORMAL) {
2998 __pwdcache.pw_uid= uic.uic$l_uic;
2999 __pwdcache.pw_gid= uic.uic$v_group;
3002 if (sts == SS$_NOSUCHID || sts == SS$_IVIDENT || sts == RMS$_PRV) {
3003 set_vaxc_errno(sts);
3004 set_errno(sts == RMS$_PRV ? EACCES : EINVAL);
3007 else { _ckvmssts(sts); }
3010 strncpy(__pw_namecache, name, sizeof(__pw_namecache));
3011 __pw_namecache[sizeof __pw_namecache - 1] = '\0';
3012 __pwdcache.pw_name= __pw_namecache;
3014 } /* end of my_getpwnam() */
3018 * Get information for a particular UIC or UID.
3019 * Called by my_getpwent with uid=-1 to list all users.
3021 /*{{{struct passwd *my_getpwuid(Uid_t uid)*/
3022 struct passwd *my_getpwuid(Uid_t uid)
3024 const $DESCRIPTOR(name_desc,__pw_namecache);
3025 unsigned short lname;
3027 unsigned long int status;
3029 if (uid == (unsigned int) -1) {
3031 status = sys$idtoasc(-1, &lname, &name_desc, &uic, 0, &contxt);
3032 if (status == SS$_NOSUCHID || status == RMS$_PRV) {
3033 set_vaxc_errno(status);
3034 set_errno(status == RMS$_PRV ? EACCES : EINVAL);
3038 else { _ckvmssts(status); }
3039 } while (!valid_uic (uic));
3043 if (!uic.uic$v_group)
3044 uic.uic$v_group= getgid();
3046 status = sys$idtoasc(uic.uic$l_uic, &lname, &name_desc, 0, 0, 0);
3047 else status = SS$_IVIDENT;
3048 if (status == SS$_IVIDENT || status == SS$_NOSUCHID ||
3049 status == RMS$_PRV) {
3050 set_vaxc_errno(status); set_errno(status == RMS$_PRV ? EACCES : EINVAL);
3053 else { _ckvmssts(status); }
3055 __pw_namecache[lname]= '\0';
3056 __mystrtolower(__pw_namecache);
3058 __pwdcache = __passwd_empty;
3059 __pwdcache.pw_name = __pw_namecache;
3061 /* Fill in the uid and gid in case fillpasswd can't (eg. no privilege).
3062 The identifier's value is usually the UIC, but it doesn't have to be,
3063 so if we can, we let fillpasswd update this. */
3064 __pwdcache.pw_uid = uic.uic$l_uic;
3065 __pwdcache.pw_gid = uic.uic$v_group;
3067 fillpasswd(__pw_namecache, &__pwdcache);
3070 } /* end of my_getpwuid() */
3074 * Get information for next user.
3076 /*{{{struct passwd *my_getpwent()*/
3077 struct passwd *my_getpwent()
3079 return (my_getpwuid((unsigned int) -1));
3084 * Finish searching rights database for users.
3086 /*{{{void my_endpwent()*/
3090 _ckvmssts(sys$finish_rdb(&contxt));
3097 /* Used for UTC calculation in my_gmtime(), my_localtime(), my_time(),
3098 * my_utime(), and flex_stat(), all of which operate on UTC unless
3099 * VMSISH_TIMES is true.
3101 /* method used to handle UTC conversions:
3102 * 1 == CRTL gmtime(); 2 == SYS$TIMEZONE_DIFFERENTIAL; 3 == no correction
3104 static int gmtime_emulation_type;
3105 /* number of secs to add to UTC POSIX-style time to get local time */
3106 static long int utc_offset_secs;
3108 /* We #defined 'gmtime', 'localtime', and 'time' as 'my_gmtime' etc.
3109 * in vmsish.h. #undef them here so we can call the CRTL routines
3116 /* my_time(), my_localtime(), my_gmtime()
3117 * By default traffic in UTC time values, suing CRTL gmtime() or
3118 * SYS$TIMEZONE_DIFFERENTIAL to determine offset from local time zone.
3119 * Contributed by Chuck Lane <lane@duphy4.physics.drexel.edu>
3120 * Modified by Charles Bailey <bailey@genetics.upenn.edu>
3123 /*{{{time_t my_time(time_t *timep)*/
3124 time_t my_time(time_t *timep)
3128 if (gmtime_emulation_type == 0) {
3130 time_t base = 15 * 86400; /* 15jan71; to avoid month ends */
3132 gmtime_emulation_type++;
3133 if ((tm_p = gmtime(&base)) == NULL) { /* CRTL gmtime() is a fake */
3136 gmtime_emulation_type++;
3137 if ((off = my_getenv("SYS$TIMEZONE_DIFFERENTIAL")) == NULL) {
3138 gmtime_emulation_type++;
3139 warn("no UTC offset information; assuming local time is UTC");
3141 else { utc_offset_secs = atol(off); }
3143 else { /* We've got a working gmtime() */
3144 struct tm gmt, local;
3147 tm_p = localtime(&base);
3149 utc_offset_secs = (local.tm_mday - gmt.tm_mday) * 86400;
3150 utc_offset_secs += (local.tm_hour - gmt.tm_hour) * 3600;
3151 utc_offset_secs += (local.tm_min - gmt.tm_min) * 60;
3152 utc_offset_secs += (local.tm_sec - gmt.tm_sec);
3161 when != -1) when -= utc_offset_secs;
3162 if (timep != NULL) *timep = when;
3165 } /* end of my_time() */
3169 /*{{{struct tm *my_gmtime(const time_t *timep)*/
3171 my_gmtime(const time_t *timep)
3176 if (gmtime_emulation_type == 0) (void) my_time(NULL); /* Init UTC */
3180 if (VMSISH_TIME) when -= utc_offset_secs; /* Input was local time */
3182 /* CRTL localtime() wants local time as input, so does no tz correction */
3183 return localtime(&when);
3185 } /* end of my_gmtime() */
3189 /*{{{struct tm *my_localtime(const time_t *timep)*/
3191 my_localtime(const time_t *timep)
3195 if (gmtime_emulation_type == 0) (void) my_time(NULL); /* Init UTC */
3199 if (!VMSISH_TIME) when += utc_offset_secs; /* Input was UTC */
3201 /* CRTL localtime() wants local time as input, so does no tz correction */
3202 return localtime(&when);
3204 } /* end of my_localtime() */
3207 /* Reset definitions for later calls */
3208 #define gmtime(t) my_gmtime(t)
3209 #define localtime(t) my_localtime(t)
3210 #define time(t) my_time(t)
3213 /* my_utime - update modification time of a file
3214 * calling sequence is identical to POSIX utime(), but under
3215 * VMS only the modification time is changed; ODS-2 does not
3216 * maintain access times. Restrictions differ from the POSIX
3217 * definition in that the time can be changed as long as the
3218 * caller has permission to execute the necessary IO$_MODIFY $QIO;
3219 * no separate checks are made to insure that the caller is the
3220 * owner of the file or has special privs enabled.
3221 * Code here is based on Joe Meadows' FILE utility.
3224 /* Adjustment from Unix epoch (01-JAN-1970 00:00:00.00)
3225 * to VMS epoch (01-JAN-1858 00:00:00.00)
3226 * in 100 ns intervals.
3228 static const long int utime_baseadjust[2] = { 0x4beb4000, 0x7c9567 };
3230 /*{{{int my_utime(char *path, struct utimbuf *utimes)*/
3231 int my_utime(char *file, struct utimbuf *utimes)
3234 long int bintime[2], len = 2, lowbit, unixtime,
3235 secscale = 10000000; /* seconds --> 100 ns intervals */
3236 unsigned long int chan, iosb[2], retsts;
3237 char vmsspec[NAM$C_MAXRSS+1], rsa[NAM$C_MAXRSS], esa[NAM$C_MAXRSS];
3238 struct FAB myfab = cc$rms_fab;
3239 struct NAM mynam = cc$rms_nam;
3240 #if defined (__DECC) && defined (__VAX)
3241 /* VAX DEC C atrdef.h has unsigned type for pointer member atr$l_addr,
3242 * at least through VMS V6.1, which causes a type-conversion warning.
3244 # pragma message save
3245 # pragma message disable cvtdiftypes
3247 struct atrdef myatr[2] = {{sizeof bintime, ATR$C_REVDATE, bintime}, {0,0,0}};
3248 struct fibdef myfib;
3249 #if defined (__DECC) && defined (__VAX)
3250 /* This should be right after the declaration of myatr, but due
3251 * to a bug in VAX DEC C, this takes effect a statement early.
3253 # pragma message restore
3255 struct dsc$descriptor fibdsc = {sizeof(myfib), DSC$K_DTYPE_Z, DSC$K_CLASS_S,(char *) &myfib},
3256 devdsc = {0,DSC$K_DTYPE_T, DSC$K_CLASS_S,0},
3257 fnmdsc = {0,DSC$K_DTYPE_T, DSC$K_CLASS_S,0};
3259 if (file == NULL || *file == '\0') {
3261 set_vaxc_errno(LIB$_INVARG);
3264 if (do_tovmsspec(file,vmsspec,0) == NULL) return -1;
3266 if (utimes != NULL) {
3267 /* Convert Unix time (seconds since 01-JAN-1970 00:00:00.00)
3268 * to VMS quadword time (100 nsec intervals since 01-JAN-1858 00:00:00.00).
3269 * Since time_t is unsigned long int, and lib$emul takes a signed long int
3270 * as input, we force the sign bit to be clear by shifting unixtime right
3271 * one bit, then multiplying by an extra factor of 2 in lib$emul().
3273 lowbit = (utimes->modtime & 1) ? secscale : 0;
3274 unixtime = (long int) utimes->modtime;
3276 if (!VMSISH_TIME) { /* Input was UTC; convert to local for sys svc */
3277 if (!gmtime_emulation_type) (void) time(NULL); /* Initialize UTC */
3278 unixtime += utc_offset_secs;
3281 unixtime >> 1; secscale << 1;
3282 retsts = lib$emul(&secscale, &unixtime, &lowbit, bintime);
3283 if (!(retsts & 1)) {
3285 set_vaxc_errno(retsts);
3288 retsts = lib$addx(bintime,utime_baseadjust,bintime,&len);
3289 if (!(retsts & 1)) {
3291 set_vaxc_errno(retsts);
3296 /* Just get the current time in VMS format directly */
3297 retsts = sys$gettim(bintime);
3298 if (!(retsts & 1)) {
3300 set_vaxc_errno(retsts);
3305 myfab.fab$l_fna = vmsspec;
3306 myfab.fab$b_fns = (unsigned char) strlen(vmsspec);
3307 myfab.fab$l_nam = &mynam;
3308 mynam.nam$l_esa = esa;
3309 mynam.nam$b_ess = (unsigned char) sizeof esa;
3310 mynam.nam$l_rsa = rsa;
3311 mynam.nam$b_rss = (unsigned char) sizeof rsa;
3313 /* Look for the file to be affected, letting RMS parse the file
3314 * specification for us as well. I have set errno using only
3315 * values documented in the utime() man page for VMS POSIX.
3317 retsts = sys$parse(&myfab,0,0);
3318 if (!(retsts & 1)) {
3319 set_vaxc_errno(retsts);
3320 if (retsts == RMS$_PRV) set_errno(EACCES);
3321 else if (retsts == RMS$_DIR) set_errno(ENOTDIR);
3322 else set_errno(EVMSERR);
3325 retsts = sys$search(&myfab,0,0);
3326 if (!(retsts & 1)) {
3327 set_vaxc_errno(retsts);
3328 if (retsts == RMS$_PRV) set_errno(EACCES);
3329 else if (retsts == RMS$_FNF) set_errno(ENOENT);
3330 else set_errno(EVMSERR);
3334 devdsc.dsc$w_length = mynam.nam$b_dev;
3335 devdsc.dsc$a_pointer = (char *) mynam.nam$l_dev;
3337 retsts = sys$assign(&devdsc,&chan,0,0);
3338 if (!(retsts & 1)) {
3339 set_vaxc_errno(retsts);
3340 if (retsts == SS$_IVDEVNAM) set_errno(ENOTDIR);
3341 else if (retsts == SS$_NOPRIV) set_errno(EACCES);
3342 else if (retsts == SS$_NOSUCHDEV) set_errno(ENOTDIR);
3343 else set_errno(EVMSERR);
3347 fnmdsc.dsc$a_pointer = mynam.nam$l_name;
3348 fnmdsc.dsc$w_length = mynam.nam$b_name + mynam.nam$b_type + mynam.nam$b_ver;
3350 memset((void *) &myfib, 0, sizeof myfib);
3352 for (i=0;i<3;i++) myfib.fib$w_fid[i] = mynam.nam$w_fid[i];
3353 for (i=0;i<3;i++) myfib.fib$w_did[i] = mynam.nam$w_did[i];
3354 /* This prevents the revision time of the file being reset to the current
3355 * time as a result of our IO$_MODIFY $QIO. */
3356 myfib.fib$l_acctl = FIB$M_NORECORD;
3358 for (i=0;i<3;i++) myfib.fib$r_fid_overlay.fib$w_fid[i] = mynam.nam$w_fid[i];
3359 for (i=0;i<3;i++) myfib.fib$r_did_overlay.fib$w_did[i] = mynam.nam$w_did[i];
3360 myfib.fib$r_acctl_overlay.fib$l_acctl = FIB$M_NORECORD;
3362 retsts = sys$qiow(0,chan,IO$_MODIFY,iosb,0,0,&fibdsc,&fnmdsc,0,0,myatr,0);
3363 _ckvmssts(sys$dassgn(chan));
3364 if (retsts & 1) retsts = iosb[0];
3365 if (!(retsts & 1)) {
3366 set_vaxc_errno(retsts);
3367 if (retsts == SS$_NOPRIV) set_errno(EACCES);
3368 else set_errno(EVMSERR);
3373 } /* end of my_utime() */
3377 * flex_stat, flex_fstat
3378 * basic stat, but gets it right when asked to stat
3379 * a Unix-style path ending in a directory name (e.g. dir1/dir2/dir3)
3382 /* encode_dev packs a VMS device name string into an integer to allow
3383 * simple comparisons. This can be used, for example, to check whether two
3384 * files are located on the same device, by comparing their encoded device
3385 * names. Even a string comparison would not do, because stat() reuses the
3386 * device name buffer for each call; so without encode_dev, it would be
3387 * necessary to save the buffer and use strcmp (this would mean a number of
3388 * changes to the standard Perl code, to say nothing of what a Perl script
3391 * The device lock id, if it exists, should be unique (unless perhaps compared
3392 * with lock ids transferred from other nodes). We have a lock id if the disk is
3393 * mounted cluster-wide, which is when we tend to get long (host-qualified)
3394 * device names. Thus we use the lock id in preference, and only if that isn't
3395 * available, do we try to pack the device name into an integer (flagged by
3396 * the sign bit (LOCKID_MASK) being set).
3398 * Note that encode_dev cannot guarantee an 1-to-1 correspondence twixt device
3399 * name and its encoded form, but it seems very unlikely that we will find
3400 * two files on different disks that share the same encoded device names,
3401 * and even more remote that they will share the same file id (if the test
3402 * is to check for the same file).
3404 * A better method might be to use sys$device_scan on the first call, and to
3405 * search for the device, returning an index into the cached array.
3406 * The number returned would be more intelligable.
3407 * This is probably not worth it, and anyway would take quite a bit longer
3408 * on the first call.
3410 #define LOCKID_MASK 0x80000000 /* Use 0 to force device name use only */
3411 static mydev_t encode_dev (const char *dev)
3414 unsigned long int f;
3419 if (!dev || !dev[0]) return 0;
3423 struct dsc$descriptor_s dev_desc;
3424 unsigned long int status, lockid, item = DVI$_LOCKID;
3426 /* For cluster-mounted disks, the disk lock identifier is unique, so we
3427 can try that first. */
3428 dev_desc.dsc$w_length = strlen (dev);
3429 dev_desc.dsc$b_dtype = DSC$K_DTYPE_T;
3430 dev_desc.dsc$b_class = DSC$K_CLASS_S;
3431 dev_desc.dsc$a_pointer = (char *) dev;
3432 _ckvmssts(lib$getdvi(&item, 0, &dev_desc, &lockid, 0, 0));
3433 if (lockid) return (lockid & ~LOCKID_MASK);
3437 /* Otherwise we try to encode the device name */
3441 for (q = dev + strlen(dev); q--; q >= dev) {
3444 else if (isalpha (toupper (*q)))
3445 c= toupper (*q) - 'A' + (char)10;
3447 continue; /* Skip '$'s */
3449 if (i>6) break; /* 36^7 is too large to fit in an unsigned long int */
3451 enc += f * (unsigned long int) c;
3453 return (enc | LOCKID_MASK); /* May have already overflowed into bit 31 */
3455 } /* end of encode_dev() */
3457 static char namecache[NAM$C_MAXRSS+1];
3460 is_null_device(name)
3463 /* The VMS null device is named "_NLA0:", usually abbreviated as "NL:".
3464 The underscore prefix, controller letter, and unit number are
3465 independently optional; for our purposes, the colon punctuation
3466 is not. The colon can be trailed by optional directory and/or
3467 filename, but two consecutive colons indicates a nodename rather
3468 than a device. [pr] */
3469 if (*name == '_') ++name;
3470 if (tolower(*name++) != 'n') return 0;
3471 if (tolower(*name++) != 'l') return 0;
3472 if (tolower(*name) == 'a') ++name;
3473 if (*name == '0') ++name;
3474 return (*name++ == ':') && (*name != ':');
3477 /* Do the permissions allow some operation? Assumes statcache already set. */
3478 /* Do this via $Check_Access on VMS, since the CRTL stat() returns only a
3479 * subset of the applicable information. (We have to stick with struct
3480 * stat instead of struct mystat in the prototype since we have to match
3481 * the one in proto.h.)
3483 /*{{{I32 cando(I32 bit, I32 effective, struct stat *statbufp)*/
3485 cando(I32 bit, I32 effective, struct stat *statbufp)
3487 if (statbufp == &statcache) return cando_by_name(bit,effective,namecache);
3489 char fname[NAM$C_MAXRSS+1];
3490 unsigned long int retsts;
3491 struct dsc$descriptor_s devdsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0},
3492 namdsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};
3494 /* If the struct mystat is stale, we're OOL; stat() overwrites the
3495 device name on successive calls */
3496 devdsc.dsc$a_pointer = ((struct mystat *)statbufp)->st_devnam;
3497 devdsc.dsc$w_length = strlen(((struct mystat *)statbufp)->st_devnam);
3498 namdsc.dsc$a_pointer = fname;
3499 namdsc.dsc$w_length = sizeof fname - 1;
3501 retsts = lib$fid_to_name(&devdsc,&(((struct mystat *)statbufp)->st_ino),
3502 &namdsc,&namdsc.dsc$w_length,0,0);
3504 fname[namdsc.dsc$w_length] = '\0';
3505 return cando_by_name(bit,effective,fname);
3507 else if (retsts == SS$_NOSUCHDEV || retsts == SS$_NOSUCHFILE) {
3508 warn("Can't get filespec - stale stat buffer?\n");
3512 return FALSE; /* Should never get to here */
3514 } /* end of cando() */
3518 /*{{{I32 cando_by_name(I32 bit, I32 effective, char *fname)*/
3520 cando_by_name(I32 bit, I32 effective, char *fname)
3522 static char usrname[L_cuserid];
3523 static struct dsc$descriptor_s usrdsc =
3524 {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, usrname};
3525 char vmsname[NAM$C_MAXRSS+1], fileified[NAM$C_MAXRSS+1];
3526 unsigned long int objtyp = ACL$C_FILE, access, retsts, privused, iosb[2];
3527 unsigned short int retlen;
3528 struct dsc$descriptor_s namdsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};
3529 union prvdef curprv;
3530 struct itmlst_3 armlst[3] = {{sizeof access, CHP$_ACCESS, &access, &retlen},
3531 {sizeof privused, CHP$_PRIVUSED, &privused, &retlen},{0,0,0,0}};
3532 struct itmlst_3 jpilst[2] = {{sizeof curprv, JPI$_CURPRIV, &curprv, &retlen},
3535 if (!fname || !*fname) return FALSE;
3536 /* Make sure we expand logical names, since sys$check_access doesn't */
3537 if (!strpbrk(fname,"/]>:")) {
3538 strcpy(fileified,fname);
3539 while (!strpbrk(fileified,"/]>:>") && my_trnlnm(fileified,fileified,0)) ;
3542 if (!do_tovmsspec(fname,vmsname,1)) return FALSE;
3543 retlen = namdsc.dsc$w_length = strlen(vmsname);
3544 namdsc.dsc$a_pointer = vmsname;
3545 if (vmsname[retlen-1] == ']' || vmsname[retlen-1] == '>' ||
3546 vmsname[retlen-1] == ':') {
3547 if (!do_fileify_dirspec(vmsname,fileified,1)) return FALSE;
3548 namdsc.dsc$w_length = strlen(fileified);
3549 namdsc.dsc$a_pointer = fileified;
3552 if (!usrdsc.dsc$w_length) {
3554 usrdsc.dsc$w_length = strlen(usrname);
3561 access = ARM$M_EXECUTE;
3566 access = ARM$M_READ;
3571 access = ARM$M_WRITE;
3576 access = ARM$M_DELETE;
3582 retsts = sys$check_access(&objtyp,&namdsc,&usrdsc,armlst);
3583 if (retsts == SS$_NOPRIV || retsts == SS$_NOSUCHOBJECT ||
3584 retsts == SS$_INVFILFOROP || retsts == RMS$_FNF ||
3585 retsts == RMS$_DIR || retsts == RMS$_DEV) {
3586 set_vaxc_errno(retsts);
3587 if (retsts == SS$_NOPRIV) set_errno(EACCES);
3588 else if (retsts == SS$_INVFILFOROP) set_errno(EINVAL);
3589 else set_errno(ENOENT);
3592 if (retsts == SS$_NORMAL) {
3593 if (!privused) return TRUE;
3594 /* We can get access, but only by using privs. Do we have the
3595 necessary privs currently enabled? */
3596 _ckvmssts(sys$getjpiw(0,0,0,jpilst,iosb,0,0));
3597 if ((privused & CHP$M_BYPASS) && !curprv.prv$v_bypass) return FALSE;
3598 if ((privused & CHP$M_SYSPRV) && !curprv.prv$v_sysprv &&
3599 !curprv.prv$v_bypass) return FALSE;
3600 if ((privused & CHP$M_GRPPRV) && !curprv.prv$v_grpprv &&
3601 !curprv.prv$v_sysprv && !curprv.prv$v_bypass) return FALSE;
3602 if ((privused & CHP$M_READALL) && !curprv.prv$v_readall) return FALSE;
3607 return FALSE; /* Should never get here */
3609 } /* end of cando_by_name() */
3613 /*{{{ int flex_fstat(int fd, struct mystat *statbuf)*/
3615 flex_fstat(int fd, struct mystat *statbufp)
3617 if (!fstat(fd,(stat_t *) statbufp)) {
3618 if (statbufp == (struct mystat *) &statcache) *namecache == '\0';
3619 statbufp->st_dev = encode_dev(statbufp->st_devnam);
3621 if (!VMSISH_TIME) { /* Return UTC instead of local time */
3625 if (!gmtime_emulation_type) (void)time(NULL);
3626 statbufp->st_mtime -= utc_offset_secs;
3627 statbufp->st_atime -= utc_offset_secs;
3628 statbufp->st_ctime -= utc_offset_secs;
3634 } /* end of flex_fstat() */
3637 /*{{{ int flex_stat(char *fspec, struct mystat *statbufp)*/
3639 flex_stat(char *fspec, struct mystat *statbufp)
3641 char fileified[NAM$C_MAXRSS+1];
3644 if (statbufp == (struct mystat *) &statcache)
3645 do_tovmsspec(fspec,namecache,0);
3646 if (is_null_device(fspec)) { /* Fake a stat() for the null device */
3647 memset(statbufp,0,sizeof *statbufp);
3648 statbufp->st_dev = encode_dev("_NLA0:");
3649 statbufp->st_mode = S_IFBLK | S_IREAD | S_IWRITE | S_IEXEC;
3650 statbufp->st_uid = 0x00010001;
3651 statbufp->st_gid = 0x0001;
3652 time((time_t *)&statbufp->st_mtime);
3653 statbufp->st_atime = statbufp->st_ctime = statbufp->st_mtime;
3657 /* Try for a directory name first. If fspec contains a filename without
3658 * a type (e.g. sea:[dark.dark]water), and both sea:[wine.dark]water.dir
3659 * and sea:[wine.dark]water. exist, we prefer the directory here.
3660 * Similarly, sea:[wine.dark] returns the result for sea:[wine]dark.dir,
3661 * not sea:[wine.dark]., if the latter exists. If the intended target is
3662 * the file with null type, specify this by calling flex_stat() with
3663 * a '.' at the end of fspec.
3665 if (do_fileify_dirspec(fspec,fileified,0) != NULL) {
3666 retval = stat(fileified,(stat_t *) statbufp);
3667 if (!retval && statbufp == (struct mystat *) &statcache)
3668 strcpy(namecache,fileified);
3670 if (retval) retval = stat(fspec,(stat_t *) statbufp);
3672 statbufp->st_dev = encode_dev(statbufp->st_devnam);
3674 if (!VMSISH_TIME) { /* Return UTC instead of local time */
3678 if (!gmtime_emulation_type) (void)time(NULL);
3679 statbufp->st_mtime -= utc_offset_secs;
3680 statbufp->st_atime -= utc_offset_secs;
3681 statbufp->st_ctime -= utc_offset_secs;
3686 } /* end of flex_stat() */
3689 /* Insures that no carriage-control translation will be done on a file. */
3690 /*{{{FILE *my_binmode(FILE *fp, char iotype)*/
3692 my_binmode(FILE *fp, char iotype)
3694 char filespec[NAM$C_MAXRSS], *acmode;
3697 if (!fgetname(fp,filespec)) return NULL;
3698 if (iotype != '-' && fgetpos(fp,&pos) == -1) return NULL;
3700 case '<': case 'r': acmode = "rb"; break;
3702 /* use 'a' instead of 'w' to avoid creating new file;
3703 fsetpos below will take care of restoring file position */
3704 case 'a': acmode = "ab"; break;
3705 case '+': case '|': case 's': acmode = "rb+"; break;
3706 case '-': acmode = fileno(fp) ? "ab" : "rb"; break;
3708 warn("Unrecognized iotype %c in my_binmode",iotype);
3711 if (freopen(filespec,acmode,fp) == NULL) return NULL;
3712 if (iotype != '-' && fsetpos(fp,&pos) == -1) return NULL;
3714 } /* end of my_binmode() */
3718 /*{{{char *my_getlogin()*/
3719 /* VMS cuserid == Unix getlogin, except calling sequence */
3723 static char user[L_cuserid];
3724 return cuserid(user);
3729 /* rmscopy - copy a file using VMS RMS routines
3731 * Copies contents and attributes of spec_in to spec_out, except owner
3732 * and protection information. Name and type of spec_in are used as
3733 * defaults for spec_out. The third parameter specifies whether rmscopy()
3734 * should try to propagate timestamps from the input file to the output file.
3735 * If it is less than 0, no timestamps are preserved. If it is 0, then
3736 * rmscopy() will behave similarly to the DCL COPY command: timestamps are
3737 * propagated to the output file at creation iff the output file specification
3738 * did not contain an explicit name or type, and the revision date is always
3739 * updated at the end of the copy operation. If it is greater than 0, then
3740 * it is interpreted as a bitmask, in which bit 0 indicates that timestamps
3741 * other than the revision date should be propagated, and bit 1 indicates
3742 * that the revision date should be propagated.
3744 * Returns 1 on success; returns 0 and sets errno and vaxc$errno on failure.
3746 * Copyright 1996 by Charles Bailey <bailey@genetics.upenn.edu>.
3747 * Incorporates, with permission, some code from EZCOPY by Tim Adye
3748 * <T.J.Adye@rl.ac.uk>. Permission is given to distribute this code
3749 * as part of the Perl standard distribution under the terms of the
3750 * GNU General Public License or the Perl Artistic License. Copies
3751 * of each may be found in the Perl standard distribution.
3753 /*{{{int rmscopy(char *src, char *dst, int preserve_dates)*/
3755 rmscopy(char *spec_in, char *spec_out, int preserve_dates)
3757 char vmsin[NAM$C_MAXRSS+1], vmsout[NAM$C_MAXRSS+1], esa[NAM$C_MAXRSS],
3758 rsa[NAM$C_MAXRSS], ubf[32256];
3759 unsigned long int i, sts, sts2;
3760 struct FAB fab_in, fab_out;
3761 struct RAB rab_in, rab_out;
3763 struct XABDAT xabdat;
3764 struct XABFHC xabfhc;
3765 struct XABRDT xabrdt;
3766 struct XABSUM xabsum;
3768 if (!spec_in || !*spec_in || !do_tovmsspec(spec_in,vmsin,1) ||
3769 !spec_out || !*spec_out || !do_tovmsspec(spec_out,vmsout,1)) {
3770 set_errno(EINVAL); set_vaxc_errno(LIB$_INVARG);
3774 fab_in = cc$rms_fab;
3775 fab_in.fab$l_fna = vmsin;
3776 fab_in.fab$b_fns = strlen(vmsin);
3777 fab_in.fab$b_shr = FAB$M_SHRPUT | FAB$M_UPI;
3778 fab_in.fab$b_fac = FAB$M_BIO | FAB$M_GET;
3779 fab_in.fab$l_fop = FAB$M_SQO;
3780 fab_in.fab$l_nam = &nam;
3781 fab_in.fab$l_xab = (void *) &xabdat;
3784 nam.nam$l_rsa = rsa;
3785 nam.nam$b_rss = sizeof(rsa);
3786 nam.nam$l_esa = esa;
3787 nam.nam$b_ess = sizeof (esa);
3788 nam.nam$b_esl = nam.nam$b_rsl = 0;
3790 xabdat = cc$rms_xabdat; /* To get creation date */
3791 xabdat.xab$l_nxt = (void *) &xabfhc;
3793 xabfhc = cc$rms_xabfhc; /* To get record length */
3794 xabfhc.xab$l_nxt = (void *) &xabsum;
3796 xabsum = cc$rms_xabsum; /* To get key and area information */
3798 if (!((sts = sys$open(&fab_in)) & 1)) {
3799 set_vaxc_errno(sts);
3803 set_errno(ENOENT); break;
3805 set_errno(ENODEV); break;
3807 set_errno(EINVAL); break;
3809 set_errno(EACCES); break;
3817 fab_out.fab$w_ifi = 0;
3818 fab_out.fab$b_fac = FAB$M_BIO | FAB$M_PUT;
3819 fab_out.fab$b_shr = FAB$M_SHRGET | FAB$M_UPI;
3820 fab_out.fab$l_fop = FAB$M_SQO;
3821 fab_out.fab$l_fna = vmsout;
3822 fab_out.fab$b_fns = strlen(vmsout);
3823 fab_out.fab$l_dna = nam.nam$l_name;
3824 fab_out.fab$b_dns = nam.nam$l_name ? nam.nam$b_name + nam.nam$b_type : 0;
3826 if (preserve_dates == 0) { /* Act like DCL COPY */
3827 nam.nam$b_nop = NAM$M_SYNCHK;
3828 fab_out.fab$l_xab = NULL; /* Don't disturb data from input file */
3829 if (!((sts = sys$parse(&fab_out)) & 1)) {
3830 set_errno(sts == RMS$_SYN ? EINVAL : EVMSERR);
3831 set_vaxc_errno(sts);
3834 fab_out.fab$l_xab = (void *) &xabdat;
3835 if (nam.nam$l_fnb & (NAM$M_EXP_NAME | NAM$M_EXP_TYPE)) preserve_dates = 1;
3837 fab_out.fab$l_nam = (void *) 0; /* Done with NAM block */
3838 if (preserve_dates < 0) /* Clear all bits; we'll use it as a */
3839 preserve_dates =0; /* bitmask from this point forward */
3841 if (!(preserve_dates & 1)) fab_out.fab$l_xab = (void *) &xabfhc;
3842 if (!((sts = sys$create(&fab_out)) & 1)) {
3843 set_vaxc_errno(sts);
3846 set_errno(ENOENT); break;
3848 set_errno(ENODEV); break;
3850 set_errno(EINVAL); break;
3852 set_errno(EACCES); break;
3858 fab_out.fab$l_fop |= FAB$M_DLT; /* in case we have to bail out */
3859 if (preserve_dates & 2) {
3860 /* sys$close() will process xabrdt, not xabdat */
3861 xabrdt = cc$rms_xabrdt;
3863 xabrdt.xab$q_rdt = xabdat.xab$q_rdt;
3865 /* gcc doesn't like the assignment, since its prototype for xab$q_rdt
3866 * is unsigned long[2], while DECC & VAXC use a struct */
3867 memcpy(xabrdt.xab$q_rdt,xabdat.xab$q_rdt,sizeof xabrdt.xab$q_rdt);
3869 fab_out.fab$l_xab = (void *) &xabrdt;
3872 rab_in = cc$rms_rab;
3873 rab_in.rab$l_fab = &fab_in;
3874 rab_in.rab$l_rop = RAB$M_BIO;
3875 rab_in.rab$l_ubf = ubf;
3876 rab_in.rab$w_usz = sizeof ubf;
3877 if (!((sts = sys$connect(&rab_in)) & 1)) {
3878 sys$close(&fab_in); sys$close(&fab_out);
3879 set_errno(EVMSERR); set_vaxc_errno(sts);
3883 rab_out = cc$rms_rab;
3884 rab_out.rab$l_fab = &fab_out;
3885 rab_out.rab$l_rbf = ubf;
3886 if (!((sts = sys$connect(&rab_out)) & 1)) {
3887 sys$close(&fab_in); sys$close(&fab_out);
3888 set_errno(EVMSERR); set_vaxc_errno(sts);
3892 while ((sts = sys$read(&rab_in))) { /* always true */
3893 if (sts == RMS$_EOF) break;
3894 rab_out.rab$w_rsz = rab_in.rab$w_rsz;
3895 if (!(sts & 1) || !((sts = sys$write(&rab_out)) & 1)) {
3896 sys$close(&fab_in); sys$close(&fab_out);
3897 set_errno(EVMSERR); set_vaxc_errno(sts);
3902 fab_out.fab$l_fop &= ~FAB$M_DLT; /* We got this far; keep the output */
3903 sys$close(&fab_in); sys$close(&fab_out);
3904 sts = (fab_in.fab$l_sts & 1) ? fab_out.fab$l_sts : fab_in.fab$l_sts;
3906 set_errno(EVMSERR); set_vaxc_errno(sts);
3912 } /* end of rmscopy() */
3916 /*** The following glue provides 'hooks' to make some of the routines
3917 * from this file available from Perl. These routines are sufficiently
3918 * basic, and are required sufficiently early in the build process,
3919 * that's it's nice to have them available to miniperl as well as the
3920 * full Perl, so they're set up here instead of in an extension. The
3921 * Perl code which handles importation of these names into a given
3922 * package lives in [.VMS]Filespec.pm in @INC.
3926 rmsexpand_fromperl(CV *cv)
3929 char *fspec, *defspec = NULL, *rslt;
3931 if (!items || items > 2)
3932 croak("Usage: VMS::Filespec::rmsexpand(spec[,defspec])");
3933 fspec = SvPV(ST(0),na);
3934 if (!fspec || !*fspec) XSRETURN_UNDEF;
3935 if (items == 2) defspec = SvPV(ST(1),na);
3937 rslt = do_rmsexpand(fspec,NULL,1,defspec,0);
3938 ST(0) = sv_newmortal();
3939 if (rslt != NULL) sv_usepvn(ST(0),rslt,strlen(rslt));
3944 vmsify_fromperl(CV *cv)
3949 if (items != 1) croak("Usage: VMS::Filespec::vmsify(spec)");
3950 vmsified = do_tovmsspec(SvPV(ST(0),na),NULL,1);
3951 ST(0) = sv_newmortal();
3952 if (vmsified != NULL) sv_usepvn(ST(0),vmsified,strlen(vmsified));
3957 unixify_fromperl(CV *cv)
3962 if (items != 1) croak("Usage: VMS::Filespec::unixify(spec)");
3963 unixified = do_tounixspec(SvPV(ST(0),na),NULL,1);
3964 ST(0) = sv_newmortal();
3965 if (unixified != NULL) sv_usepvn(ST(0),unixified,strlen(unixified));
3970 fileify_fromperl(CV *cv)
3975 if (items != 1) croak("Usage: VMS::Filespec::fileify(spec)");
3976 fileified = do_fileify_dirspec(SvPV(ST(0),na),NULL,1);
3977 ST(0) = sv_newmortal();
3978 if (fileified != NULL) sv_usepvn(ST(0),fileified,strlen(fileified));
3983 pathify_fromperl(CV *cv)
3988 if (items != 1) croak("Usage: VMS::Filespec::pathify(spec)");
3989 pathified = do_pathify_dirspec(SvPV(ST(0),na),NULL,1);
3990 ST(0) = sv_newmortal();
3991 if (pathified != NULL) sv_usepvn(ST(0),pathified,strlen(pathified));
3996 vmspath_fromperl(CV *cv)
4001 if (items != 1) croak("Usage: VMS::Filespec::vmspath(spec)");
4002 vmspath = do_tovmspath(SvPV(ST(0),na),NULL,1);
4003 ST(0) = sv_newmortal();
4004 if (vmspath != NULL) sv_usepvn(ST(0),vmspath,strlen(vmspath));
4009 unixpath_fromperl(CV *cv)
4014 if (items != 1) croak("Usage: VMS::Filespec::unixpath(spec)");
4015 unixpath = do_tounixpath(SvPV(ST(0),na),NULL,1);
4016 ST(0) = sv_newmortal();
4017 if (unixpath != NULL) sv_usepvn(ST(0),unixpath,strlen(unixpath));
4022 candelete_fromperl(CV *cv)
4025 char fspec[NAM$C_MAXRSS+1], *fsp;
4029 if (items != 1) croak("Usage: VMS::Filespec::candelete(spec)");
4031 mysv = SvROK(ST(0)) ? SvRV(ST(0)) : ST(0);
4032 if (SvTYPE(mysv) == SVt_PVGV) {
4033 if (!(io = GvIOp(mysv)) || !fgetname(IoIFP(io),fspec)) {
4034 set_errno(EINVAL); set_vaxc_errno(LIB$_INVARG);
4041 if (mysv != ST(0) || !(fsp = SvPV(mysv,na)) || !*fsp) {
4042 set_errno(EINVAL); set_vaxc_errno(LIB$_INVARG);
4048 ST(0) = cando_by_name(S_IDUSR,0,fsp) ? &sv_yes : &sv_no;
4053 rmscopy_fromperl(CV *cv)
4056 char inspec[NAM$C_MAXRSS+1], outspec[NAM$C_MAXRSS+1], *inp, *outp;
4058 struct dsc$descriptor indsc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0},
4059 outdsc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};
4060 unsigned long int sts;
4064 if (items < 2 || items > 3)
4065 croak("Usage: File::Copy::rmscopy(from,to[,date_flag])");
4067 mysv = SvROK(ST(0)) ? SvRV(ST(0)) : ST(0);
4068 if (SvTYPE(mysv) == SVt_PVGV) {
4069 if (!(io = GvIOp(mysv)) || !fgetname(IoIFP(io),inspec)) {
4070 set_errno(EINVAL); set_vaxc_errno(LIB$_INVARG);
4077 if (mysv != ST(0) || !(inp = SvPV(mysv,na)) || !*inp) {
4078 set_errno(EINVAL); set_vaxc_errno(LIB$_INVARG);
4083 mysv = SvROK(ST(1)) ? SvRV(ST(1)) : ST(1);
4084 if (SvTYPE(mysv) == SVt_PVGV) {
4085 if (!(io = GvIOp(mysv)) || !fgetname(IoIFP(io),outspec)) {
4086 set_errno(EINVAL); set_vaxc_errno(LIB$_INVARG);
4093 if (mysv != ST(1) || !(outp = SvPV(mysv,na)) || !*outp) {
4094 set_errno(EINVAL); set_vaxc_errno(LIB$_INVARG);
4099 date_flag = (items == 3) ? SvIV(ST(2)) : 0;
4101 ST(0) = rmscopy(inp,outp,date_flag) ? &sv_yes : &sv_no;
4108 char* file = __FILE__;
4110 newXSproto("VMS::Filespec::rmsexpand",rmsexpand_fromperl,file,"$;$");
4111 newXSproto("VMS::Filespec::vmsify",vmsify_fromperl,file,"$");
4112 newXSproto("VMS::Filespec::unixify",unixify_fromperl,file,"$");
4113 newXSproto("VMS::Filespec::pathify",pathify_fromperl,file,"$");
4114 newXSproto("VMS::Filespec::fileify",fileify_fromperl,file,"$");
4115 newXSproto("VMS::Filespec::vmspath",vmspath_fromperl,file,"$");
4116 newXSproto("VMS::Filespec::unixpath",unixpath_fromperl,file,"$");
4117 newXSproto("VMS::Filespec::candelete",candelete_fromperl,file,"$");
4118 newXS("File::Copy::rmscopy",rmscopy_fromperl,file);