X-Git-Url: https://perl5.git.perl.org/perl5.git/blobdiff_plain/f20b0ac8de25af0aff0845e603605cd59e360f30..794501039f82344685f618163b157d3365dd963f:/vms/vms.c diff --git a/vms/vms.c b/vms/vms.c index 9eb4224..a82625d 100644 --- a/vms/vms.c +++ b/vms/vms.c @@ -1,11 +1,15 @@ /* vms.c * * VMS-specific routines for perl5 + * Version: 5.7.0 * - * Last revised: 20-Aug-1999 by Charles Bailey bailey@newman.upenn.edu - * Version: 5.5.60 + * August 2000 tweaks to vms_image_init, my_flush, my_fwrite, cando_by_name, + * and Perl_cando by Craig Berry + * 29-Aug-2000 Charles Lane's piping improvements rolled in + * 20-Aug-1999 revisions by Charles Bailey bailey@newman.upenn.edu */ +#include #include #include #include @@ -14,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +51,9 @@ # define SS$_NOSUCHOBJECT 2696 #endif +/* We implement I/O here, so we will be mixing PerlIO and stdio calls. */ +#define PERLIO_NOT_STDIO 0 + /* Don't replace system definitions of vfork, getenv, and stat, * code below needs to get to the underlying CRTL routines. */ #define DONT_MASK_RTL_CALLS @@ -56,6 +65,11 @@ # define WARN_INTERNAL WARN_MISC #endif +#if defined(__VMS_VER) && __VMS_VER >= 70000000 && __DECC_VER >= 50200000 +# define RTL_USES_UTC 1 +#endif + + /* gcc's header files don't #define direct access macros * corresponding to VAXC's variant structs */ #ifdef __GNUC__ @@ -89,6 +103,11 @@ struct itmlst_3 { #define expand_wild_cards(a,b,c,d) mp_expand_wild_cards(aTHX_ a,b,c,d) #define getredirection(a,b) mp_getredirection(aTHX_ a,b) +/* see system service docs for $TRNLNM -- NOT the same as LNM$_MAX_INDEX */ +#define PERL_LNM_MAX_ALLOWED_INDEX 127 + +#define MAX_DCL_LINE_LENGTH 255 + static char *__mystrtolower(char *str) { if (str) for (; *str; ++str) *str= tolower(*str); @@ -109,11 +128,15 @@ static bool will_taint = FALSE; /* tainting active, but no PL_curinterp yet */ static int no_translate_barewords; /* Temp for subprocess commands */ -static struct dsc$descriptor_s VMScmd = {0,DSC$K_DTYPE_T,DSC$K_CLASS_S,Nullch}; +static struct dsc$descriptor_s VMSCMD = {0,DSC$K_DTYPE_T,DSC$K_CLASS_S,Nullch}; + +#ifndef RTL_USES_UTC +static int tz_updated = 1; +#endif /*{{{int vmstrnenv(const char *lnm, char *eqv, unsigned long int idx, struct dsc$descriptor_s **tabvec, unsigned long int flags) */ int -Perl_vmstrnenv(pTHX_ const char *lnm, char *eqv, unsigned long int idx, +Perl_vmstrnenv(const char *lnm, char *eqv, unsigned long int idx, struct dsc$descriptor_s **tabvec, unsigned long int flags) { char uplnm[LNM$C_NAMLENGTH+1], *cp1, *cp2; @@ -126,20 +149,29 @@ Perl_vmstrnenv(pTHX_ const char *lnm, char *eqv, unsigned long int idx, {LNM$C_NAMLENGTH, LNM$_STRING, eqv, &eqvlen}, {0, 0, 0, 0}}; $DESCRIPTOR(crtlenv,"CRTL_ENV"); $DESCRIPTOR(clisym,"CLISYM"); -#if defined(USE_THREADS) +#if defined(PERL_IMPLICIT_CONTEXT) + pTHX = NULL; +# if defined(USE_5005THREADS) /* We jump through these hoops because we can be called at */ /* platform-specific initialization time, which is before anything is */ /* set up--we can't even do a plain dTHX since that relies on the */ /* interpreter structure to be initialized */ - struct perl_thread *thr; if (PL_curinterp) { - thr = PL_threadnum? THR : (struct perl_thread*)SvPVX(PL_thrsv); + aTHX = PL_threadnum? THR : (struct perl_thread*)SvPVX(PL_thrsv); + } else { + aTHX = NULL; + } +# else + if (PL_curinterp) { + aTHX = PERL_GET_INTERP; } else { - thr = NULL; + aTHX = NULL; } + +# endif #endif - if (!lnm || !eqv || idx > LNM$_MAX_INDEX) { + if (!lnm || !eqv || idx > PERL_LNM_MAX_ALLOWED_INDEX) { set_errno(EINVAL); set_vaxc_errno(SS$_BADPARAM); return 0; } for (cp1 = (char *)lnm, cp2 = uplnm; *cp1; cp1++, cp2++) { @@ -196,13 +228,13 @@ Perl_vmstrnenv(pTHX_ const char *lnm, char *eqv, unsigned long int idx, /* fully initialized, in which case either thr or PL_curcop */ /* might be bogus. We have to check, since ckWARN needs them */ /* both to be valid if running threaded */ -#if defined(USE_THREADS) +#if defined(USE_5005THREADS) if (thr && PL_curcop) { #endif if (ckWARN(WARN_MISC)) { Perl_warner(aTHX_ WARN_MISC,"Value of CLI symbol \"%s\" too long",lnm); } -#if defined(USE_THREADS) +#if defined(USE_5005THREADS) } else { Perl_warner(aTHX_ WARN_MISC,"Value of CLI symbol \"%s\" too long",lnm); } @@ -277,7 +309,7 @@ Perl_my_getenv(pTHX_ const char *lnm, bool sys) static char __my_getenv_eqv[LNM$C_NAMLENGTH+1]; char uplnm[LNM$C_NAMLENGTH+1], *cp1, *cp2, *eqv; unsigned long int idx = 0; - int trnsuccess; + int trnsuccess, success, secure, saverr, savvmserr; SV *tmpsv; if (PL_curinterp) { /* Perl interpreter running -- may be threaded */ @@ -301,16 +333,25 @@ Perl_my_getenv(pTHX_ const char *lnm, bool sys) lnm = uplnm; } /* Impose security constraints only if tainting */ - if (sys) sys = PL_curinterp ? PL_tainting : will_taint; - if (vmstrnenv(lnm,eqv,idx, - sys ? fildev : NULL, + if (sys) { + /* Impose security constraints only if tainting */ + secure = PL_curinterp ? PL_tainting : will_taint; + saverr = errno; savvmserr = vaxc$errno; + } + else secure = 0; + success = vmstrnenv(lnm,eqv,idx, + secure ? fildev : NULL, #ifdef SECURE_INTERNAL_GETENV - sys ? PERL__TRNENV_SECURE : 0 + secure ? PERL__TRNENV_SECURE : 0 #else - 0 + 0 #endif - )) return eqv; - else return Nullch; + ); + /* Discard NOLOGNAM on internal calls since we're often looking + * for an optional name, and this "error" often shows up as the + * (bogus) exit status for a die() call later on. */ + if (sys && vaxc$errno == SS$_NOLOGNAM) SETERRNO(saverr,savvmserr); + return success ? eqv : Nullch; } } /* end of my_getenv() */ @@ -319,12 +360,12 @@ Perl_my_getenv(pTHX_ const char *lnm, bool sys) /*{{{ SV *my_getenv_len(const char *lnm, bool sys)*/ char * -my_getenv_len(const char *lnm, unsigned long *len, bool sys) +Perl_my_getenv_len(pTHX_ const char *lnm, unsigned long *len, bool sys) { - dTHX; char *buf, *cp1, *cp2; unsigned long idx = 0; static char __my_getenv_len_eqv[LNM$C_NAMLENGTH+1]; + int secure, saverr, savvmserr; SV *tmpsv; if (PL_curinterp) { /* Perl interpreter running -- may be threaded */ @@ -348,25 +389,31 @@ my_getenv_len(const char *lnm, unsigned long *len, bool sys) idx = strtoul(cp2+1,NULL,0); lnm = buf; } - /* Impose security constraints only if tainting */ - if (sys) sys = PL_curinterp ? PL_tainting : will_taint; - if ((*len = vmstrnenv(lnm,buf,idx, - sys ? fildev : NULL, + if (sys) { + /* Impose security constraints only if tainting */ + secure = PL_curinterp ? PL_tainting : will_taint; + saverr = errno; savvmserr = vaxc$errno; + } + else secure = 0; + *len = vmstrnenv(lnm,buf,idx, + secure ? fildev : NULL, #ifdef SECURE_INTERNAL_GETENV - sys ? PERL__TRNENV_SECURE : 0 + secure ? PERL__TRNENV_SECURE : 0 #else - 0 + 0 #endif - ))) - return buf; - else - return Nullch; + ); + /* Discard NOLOGNAM on internal calls since we're often looking + * for an optional name, and this "error" often shows up as the + * (bogus) exit status for a die() call later on. */ + if (sys && vaxc$errno == SS$_NOLOGNAM) SETERRNO(saverr,savvmserr); + return *len ? buf : Nullch; } } /* end of my_getenv_len() */ /*}}}*/ -static void create_mbx(unsigned short int *, struct dsc$descriptor_s *); +static void create_mbx(pTHX_ unsigned short int *, struct dsc$descriptor_s *); static void riseandshine(unsigned long int dummy) { sys$wake(0,0); } @@ -377,9 +424,9 @@ prime_env_iter(void) * find, in preparation for iterating over it. */ { - dTHX; static int primed = 0; HV *seenhv = NULL, *envhv; + SV *sv = NULL; char cmd[LNM$C_NAMLENGTH+24], mbxnam[LNM$C_NAMLENGTH], *buf = Nullch; unsigned short int chan; #ifndef CLI$M_TRUSTED @@ -394,11 +441,34 @@ prime_env_iter(void) $DESCRIPTOR(clidsc,"DCL"); $DESCRIPTOR(clitabdsc,"DCLTABLES"); $DESCRIPTOR(crtlenv,"CRTL_ENV"); $DESCRIPTOR(clisym,"CLISYM"); $DESCRIPTOR(local,"_LOCAL"); $DESCRIPTOR(mbxdsc,mbxnam); -#ifdef USE_THREADS +#if defined(PERL_IMPLICIT_CONTEXT) + pTHX; +#endif +#if defined(USE_5005THREADS) || defined(USE_ITHREADS) static perl_mutex primenv_mutex; MUTEX_INIT(&primenv_mutex); #endif +#if defined(PERL_IMPLICIT_CONTEXT) + /* We jump through these hoops because we can be called at */ + /* platform-specific initialization time, which is before anything is */ + /* set up--we can't even do a plain dTHX since that relies on the */ + /* interpreter structure to be initialized */ +#if defined(USE_5005THREADS) + if (PL_curinterp) { + aTHX = PL_threadnum? THR : (struct perl_thread*)SvPVX(PL_thrsv); + } else { + aTHX = NULL; + } +#else + if (PL_curinterp) { + aTHX = PERL_GET_INTERP; + } else { + aTHX = NULL; + } +#endif +#endif + if (primed || !PL_envgv) return; MUTEX_LOCK(&primenv_mutex); if (primed) { MUTEX_UNLOCK(&primenv_mutex); return; } @@ -430,8 +500,9 @@ prime_env_iter(void) } else { start++; - (void) hv_store(envhv,environ[j],start - environ[j] - 1, - newSVpv(start,0),0); + sv = newSVpv(start,0); + SvTAINTED_on(sv); + (void) hv_store(envhv,environ[j],start - environ[j] - 1,sv,0); } } continue; @@ -520,7 +591,9 @@ prime_env_iter(void) continue; } PERL_HASH(hash,key,keylen); - hv_store(envhv,key,keylen,newSVpvn(cp2,cp1 - cp2 + 1),hash); + sv = newSVpvn(cp2,cp1 - cp2 + 1); + SvTAINTED_on(sv); + hv_store(envhv,key,keylen,sv,hash); hv_store(seenhv,key,keylen,&PL_sv_yes,hash); } if (cmddsc.dsc$w_length == 14) { /* We just read LNM$FILE_DEV */ @@ -530,7 +603,9 @@ prime_env_iter(void) int trnlen, i; for (i = 0; ppfs[i]; i++) { trnlen = vmstrnenv(ppfs[i],eqv,0,fildev,0); - hv_store(envhv,ppfs[i],strlen(ppfs[i]),newSVpv(eqv,trnlen),0); + sv = newSVpv(eqv,trnlen); + SvTAINTED_on(sv); + hv_store(envhv,ppfs[i],strlen(ppfs[i]),sv,0); } } } @@ -553,7 +628,7 @@ prime_env_iter(void) * Like setenv() returns 0 for success, non-zero on error. */ int -vmssetenv(char *lnm, char *eqv, struct dsc$descriptor_s **tabvec) +Perl_vmssetenv(pTHX_ char *lnm, char *eqv, struct dsc$descriptor_s **tabvec) { char uplnm[LNM$C_NAMLENGTH], *cp1, *cp2; unsigned short int curtab, ivlnm = 0, ivsym = 0, ivenv = 0; @@ -563,7 +638,6 @@ vmssetenv(char *lnm, char *eqv, struct dsc$descriptor_s **tabvec) tmpdsc = {6,DSC$K_DTYPE_T,DSC$K_CLASS_S,0}; $DESCRIPTOR(crtlenv,"CRTL_ENV"); $DESCRIPTOR(clisym,"CLISYM"); $DESCRIPTOR(local,"_LOCAL"); - dTHX; for (cp1 = lnm, cp2 = uplnm; *cp1; cp1++, cp2++) { *cp2 = _toupper(*cp1); @@ -583,7 +657,7 @@ vmssetenv(char *lnm, char *eqv, struct dsc$descriptor_s **tabvec) if ((cp1 = strchr(environ[i],'=')) && !strncmp(environ[i],lnm,cp1 - environ[i])) { #ifdef HAS_SETENV - return setenv(lnm,eqv,1) ? vaxc$errno : 0; + return setenv(lnm,"",1) ? vaxc$errno : 0; } } ivenv = 1; retsts = SS$_NOLOGNAM; @@ -691,19 +765,56 @@ vmssetenv(char *lnm, char *eqv, struct dsc$descriptor_s **tabvec) void Perl_my_setenv(pTHX_ char *lnm,char *eqv) { - if (lnm && *lnm && strlen(lnm) == 7) { - char uplnm[8]; - int i; - for (i = 0; lnm[i]; i++) uplnm[i] = _toupper(lnm[i]); - if (!strcmp(uplnm,"DEFAULT")) { - if (eqv && *eqv) chdir(eqv); - return; + if (lnm && *lnm) { + int len = strlen(lnm); + if (len == 7) { + char uplnm[8]; + int i; + for (i = 0; lnm[i]; i++) uplnm[i] = _toupper(lnm[i]); + if (!strcmp(uplnm,"DEFAULT")) { + if (eqv && *eqv) chdir(eqv); + return; + } + } +#ifndef RTL_USES_UTC + if (len == 6 || len == 2) { + char uplnm[7]; + int i; + for (i = 0; lnm[i]; i++) uplnm[i] = _toupper(lnm[i]); + uplnm[len] = '\0'; + if (!strcmp(uplnm,"UCX$TZ")) tz_updated = 1; + if (!strcmp(uplnm,"TZ")) tz_updated = 1; } +#endif } (void) vmssetenv(lnm,eqv,NULL); } /*}}}*/ +/*{{{static void vmssetuserlnm(char *name, char *eqv); +/* vmssetuserlnm + * sets a user-mode logical in the process logical name table + * used for redirection of sys$error + */ +void +Perl_vmssetuserlnm(pTHX_ char *name, char *eqv) +{ + $DESCRIPTOR(d_tab, "LNM$PROCESS"); + struct dsc$descriptor_d d_name = {0,DSC$K_DTYPE_T,DSC$K_CLASS_D,0}; + unsigned long int iss, attr = LNM$M_CONFINE; + unsigned char acmode = PSL$C_USER; + struct itmlst_3 lnmlst[2] = {{0, LNM$_STRING, 0, 0}, + {0, 0, 0, 0}}; + d_name.dsc$a_pointer = name; + d_name.dsc$w_length = strlen(name); + + lnmlst[0].buflen = strlen(eqv); + lnmlst[0].bufadr = eqv; + + iss = sys$crelnm(&attr,&d_tab,&d_name,&acmode,lnmlst); + if (!(iss&1)) lib$signal(iss); +} +/*}}}*/ /*{{{ char *my_crypt(const char *textpasswd, const char *usrname)*/ @@ -717,7 +828,7 @@ Perl_my_setenv(pTHX_ char *lnm,char *eqv) * be upcased by the caller. */ char * -my_crypt(const char *textpasswd, const char *usrname) +Perl_my_crypt(pTHX_ const char *textpasswd, const char *usrname) { # ifndef UAI$C_PREFERRED_ALGORITHM # define UAI$C_PREFERRED_ALGORITHM 127 @@ -797,12 +908,11 @@ Perl_do_rmdir(pTHX_ char *name) */ /*{{{int kill_file(char *name)*/ int -kill_file(char *name) +Perl_kill_file(pTHX_ char *name) { char vmsname[NAM$C_MAXRSS+1], rspec[NAM$C_MAXRSS+1]; unsigned long int jpicode = JPI$_UIC, type = ACL$C_FILE; unsigned long int cxt = 0, aclsts, fndsts, rmsts = -1; - dTHX; struct dsc$descriptor_s fildsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0}; struct myacedef { unsigned char myace$b_length; @@ -899,10 +1009,9 @@ kill_file(char *name) /*{{{int my_mkdir(char *,Mode_t)*/ int -my_mkdir(char *dir, Mode_t mode) +Perl_my_mkdir(pTHX_ char *dir, Mode_t mode) { STRLEN dirlen = strlen(dir); - dTHX; /* zero length string sometimes gives ACCVIO */ if (dirlen == 0) return -1; @@ -923,10 +1032,9 @@ my_mkdir(char *dir, Mode_t mode) /*{{{int my_chdir(char *)*/ int -my_chdir(char *dir) +Perl_my_chdir(pTHX_ char *dir) { STRLEN dirlen = strlen(dir); - dTHX; /* zero length string sometimes gives ACCVIO */ if (dirlen == 0) return -1; @@ -946,22 +1054,80 @@ my_chdir(char *dir) } /* end of my_chdir */ /*}}}*/ + +/*{{{FILE *my_tmpfile()*/ +FILE * +my_tmpfile(void) +{ + FILE *fp; + char *cp; + + if ((fp = tmpfile())) return fp; + + New(1323,cp,L_tmpnam+24,char); + strcpy(cp,"Sys$Scratch:"); + tmpnam(cp+strlen(cp)); + strcat(cp,".Perltmp"); + fp = fopen(cp,"w+","fop=dlt"); + Safefree(cp); + return fp; +} +/*}}}*/ + + +#ifndef HOMEGROWN_POSIX_SIGNALS +/* + * The C RTL's sigaction fails to check for invalid signal numbers so we + * help it out a bit. The docs are correct, but the actual routine doesn't + * do what the docs say it will. + */ +/*{{{int Perl_my_sigaction (pTHX_ int, const struct sigaction*, struct sigaction*);*/ +int +Perl_my_sigaction (pTHX_ int sig, const struct sigaction* act, + struct sigaction* oact) +{ + if (sig == SIGKILL || sig == SIGSTOP || sig == SIGCONT) { + SETERRNO(EINVAL, SS$_INVARG); + return -1; + } + return sigaction(sig, act, oact); +} +/*}}}*/ +#endif + +/* default piping mailbox size */ +#define PERL_BUFSIZ 512 + + static void -create_mbx(unsigned short int *chan, struct dsc$descriptor_s *namdsc) +create_mbx(pTHX_ unsigned short int *chan, struct dsc$descriptor_s *namdsc) { - static unsigned long int mbxbufsiz; - long int syiitm = SYI$_MAXBUF, dviitm = DVI$_DEVNAM; - dTHX; + unsigned long int mbxbufsiz; + static unsigned long int syssize = 0; + unsigned long int dviitm = DVI$_DEVNAM; + char csize[LNM$C_NAMLENGTH+1]; - if (!mbxbufsiz) { + if (!syssize) { + unsigned long syiitm = SYI$_MAXBUF; /* - * Get the SYSGEN parameter MAXBUF, and the smaller of it and the - * preprocessor consant BUFSIZ from stdio.h as the size of the - * 'pipe' mailbox. + * Get the SYSGEN parameter MAXBUF + * + * If the logical 'PERL_MBX_SIZE' is defined + * use the value of the logical instead of PERL_BUFSIZ, but + * keep the size between 128 and MAXBUF. + * */ - _ckvmssts(lib$getsyi(&syiitm, &mbxbufsiz, 0, 0, 0, 0)); - if (mbxbufsiz > BUFSIZ) mbxbufsiz = BUFSIZ; + _ckvmssts(lib$getsyi(&syiitm, &syssize, 0, 0, 0, 0)); + } + + if (vmstrnenv("PERL_MBX_SIZE", csize, 0, fildev, 0)) { + mbxbufsiz = atoi(csize); + } else { + mbxbufsiz = PERL_BUFSIZ; } + if (mbxbufsiz < 128) mbxbufsiz = 128; + if (mbxbufsiz > syssize) mbxbufsiz = syssize; + _ckvmssts(sys$crembx(0,chan,mbxbufsiz,mbxbufsiz,0,0,0)); _ckvmssts(lib$getdvi(&dviitm, chan, NULL, NULL, namdsc, &namdsc->dsc$w_length)); @@ -969,15 +1135,84 @@ create_mbx(unsigned short int *chan, struct dsc$descriptor_s *namdsc) } /* end of create_mbx() */ + /*{{{ my_popen and my_pclose*/ + +typedef struct _iosb IOSB; +typedef struct _iosb* pIOSB; +typedef struct _pipe Pipe; +typedef struct _pipe* pPipe; +typedef struct pipe_details Info; +typedef struct pipe_details* pInfo; +typedef struct _srqp RQE; +typedef struct _srqp* pRQE; +typedef struct _tochildbuf CBuf; +typedef struct _tochildbuf* pCBuf; + +struct _iosb { + unsigned short status; + unsigned short count; + unsigned long dvispec; +}; + +#pragma member_alignment save +#pragma nomember_alignment quadword +struct _srqp { /* VMS self-relative queue entry */ + unsigned long qptr[2]; +}; +#pragma member_alignment restore +static RQE RQE_ZERO = {0,0}; + +struct _tochildbuf { + RQE q; + int eof; + unsigned short size; + char *buf; +}; + +struct _pipe { + RQE free; + RQE wait; + int fd_out; + unsigned short chan_in; + unsigned short chan_out; + char *buf; + unsigned int bufsize; + IOSB iosb; + IOSB iosb2; + int *pipe_done; + int retry; + int type; + int shut_on_empty; + int need_wake; + pPipe *home; + pInfo info; + pCBuf curr; + pCBuf curr2; +#if defined(PERL_IMPLICIT_CONTEXT) + void *thx; /* Either a thread or an interpreter */ + /* pointer, depending on how we're built */ +#endif +}; + + struct pipe_details { - struct pipe_details *next; - PerlIO *fp; /* stdio file pointer to pipe mailbox */ + pInfo next; + PerlIO *fp; /* file pointer to pipe mailbox */ + int useFILE; /* using stdio, not perlio */ int pid; /* PID of subprocess */ int mode; /* == 'r' if pipe open for reading */ int done; /* subprocess has completed */ - unsigned long int completion; /* termination status of subprocess */ + int waiting; /* waiting for completion/closure */ + int closing; /* my_pclose is closing this pipe */ + unsigned long completion; /* termination status of subprocess */ + pPipe in; /* pipe in to sub */ + pPipe out; /* pipe out of sub */ + pPipe err; /* pipe of sub's sys$error */ + int in_done; /* true when in pipe finished */ + int out_done; + int err_done; }; struct exit_control_block @@ -989,49 +1224,43 @@ struct exit_control_block unsigned long int exit_status; }; -static struct pipe_details *open_pipes = NULL; +#define RETRY_DELAY "0 ::0.20" +#define MAX_RETRY 50 + +static int pipe_ef = 0; /* first call to safe_popen inits these*/ +static unsigned long mypid; +static unsigned long delaytime[2]; + +static pInfo open_pipes = NULL; static $DESCRIPTOR(nl_desc, "NL:"); -static int waitpid_asleep = 0; -/* Send an EOF to a mbx. N.B. We don't check that fp actually points - * to a mbx; that's the caller's responsibility. - */ -static unsigned long int -pipe_eof(FILE *fp, int immediate) -{ - char devnam[NAM$C_MAXRSS+1], *cp; - unsigned long int chan, iosb[2], retsts, retsts2; - struct dsc$descriptor devdsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, devnam}; - dTHX; +#define PIPE_COMPLETION_WAIT 30 /* seconds, for EOF/FORCEX wait */ + - if (fgetname(fp,devnam,1)) { - /* It oughta be a mailbox, so fgetname should give just the device - * name, but just in case . . . */ - if ((cp = strrchr(devnam,':')) != NULL) *(cp+1) = '\0'; - devdsc.dsc$w_length = strlen(devnam); - _ckvmssts(sys$assign(&devdsc,&chan,0,0)); - retsts = sys$qiow(0,chan,IO$_WRITEOF|(immediate?IO$M_NOW|IO$M_NORSWAIT:0), - iosb,0,0,0,0,0,0,0,0); - if (retsts & 1) retsts = iosb[0]; - retsts2 = sys$dassgn(chan); /* Be sure to deassign the channel */ - if (retsts & 1) retsts = retsts2; - _ckvmssts(retsts); - return retsts; - } - else _ckvmssts(vaxc$errno); /* Should never happen */ - return (unsigned long int) vaxc$errno; -} static unsigned long int -pipe_exit_routine() +pipe_exit_routine(pTHX) { - struct pipe_details *info; + pInfo info; unsigned long int retsts = SS$_NORMAL, abort = SS$_TIMEOUT; - int sts, did_stuff; - dTHX; + int sts, did_stuff, need_eof, j; + + /* + flush any pending i/o + */ + info = open_pipes; + while (info) { + if (info->fp) { + if (!info->useFILE) + PerlIO_flush(info->fp); /* first, flush data */ + else + fflush((FILE *)info->fp); + } + info = info->next; + } /* - first we try sending an EOF...ignore if doesn't work, make sure we + next we try sending an EOF...ignore if doesn't work, make sure we don't hang */ did_stuff = 0; @@ -1040,14 +1269,33 @@ pipe_exit_routine() while (info) { int need_eof; _ckvmssts(sys$setast(0)); - need_eof = info->mode != 'r' && !info->done; - _ckvmssts(sys$setast(1)); - if (need_eof) { - if (pipe_eof(info->fp, 1) & 1) did_stuff = 1; + if (info->in && !info->in->shut_on_empty) { + _ckvmssts(sys$qio(0,info->in->chan_in,IO$_WRITEOF,0,0,0, + 0, 0, 0, 0, 0, 0)); + info->waiting = 1; + did_stuff = 1; } + _ckvmssts(sys$setast(1)); info = info->next; } - if (did_stuff) sleep(1); /* wait for EOF to have an effect */ + + /* wait for EOF to have effect, up to ~ 30 sec [default] */ + + for (j = 0; did_stuff && j < PIPE_COMPLETION_WAIT; j++) { + int nwait = 0; + + info = open_pipes; + while (info) { + _ckvmssts(sys$setast(0)); + if (info->waiting && info->done) + info->waiting = 0; + nwait += info->waiting; + _ckvmssts(sys$setast(1)); + info = info->next; + } + if (!nwait) break; + sleep(1); + } did_stuff = 0; info = open_pipes; @@ -1061,7 +1309,24 @@ pipe_exit_routine() _ckvmssts(sys$setast(1)); info = info->next; } - if (did_stuff) sleep(1); /* wait for them to respond */ + + /* again, wait for effect */ + + for (j = 0; did_stuff && j < PIPE_COMPLETION_WAIT; j++) { + int nwait = 0; + + info = open_pipes; + while (info) { + _ckvmssts(sys$setast(0)); + if (info->waiting && info->done) + info->waiting = 0; + nwait += info->waiting; + _ckvmssts(sys$setast(1)); + info = info->next; + } + if (!nwait) break; + sleep(1); + } info = open_pipes; while (info) { @@ -1069,7 +1334,6 @@ pipe_exit_routine() if (!info->done) { /* We tried to be nice . . . */ sts = sys$delprc(&info->pid,0); if (!(sts&1) && sts != SS$_NONEXPR) _ckvmssts(sts); - info->done = 1; /* so my_pclose doesn't try to write EOF */ } _ckvmssts(sys$setast(1)); info = info->next; @@ -1082,100 +1346,1041 @@ pipe_exit_routine() return retsts; } -static struct exit_control_block pipe_exitblock = - {(struct exit_control_block *) 0, - pipe_exit_routine, 0, &pipe_exitblock.exit_status, 0}; +static struct exit_control_block pipe_exitblock = + {(struct exit_control_block *) 0, + pipe_exit_routine, 0, &pipe_exitblock.exit_status, 0}; + +static void pipe_mbxtofd_ast(pPipe p); +static void pipe_tochild1_ast(pPipe p); +static void pipe_tochild2_ast(pPipe p); + +static void +popen_completion_ast(pInfo info) +{ + pInfo i = open_pipes; + int iss; + + while (i) { + if (i == info) break; + i = i->next; + } + if (!i) return; /* unlinked, probably freed too */ + + info->completion &= 0x0FFFFFFF; /* strip off "control" field */ + info->done = TRUE; + +/* + Writing to subprocess ... + if my_pclose'd: EOF already sent, should shutdown chan_in part of pipe + + chan_out may be waiting for "done" flag, or hung waiting + for i/o completion to child...cancel the i/o. This will + put it into "snarf mode" (done but no EOF yet) that discards + input. + + Output from subprocess (stdout, stderr) needs to be flushed and + shut down. We try sending an EOF, but if the mbx is full the pipe + routine should still catch the "shut_on_empty" flag, telling it to + use immediate-style reads so that "mbx empty" -> EOF. + + +*/ + if (info->in && !info->in_done) { /* only for mode=w */ + if (info->in->shut_on_empty && info->in->need_wake) { + info->in->need_wake = FALSE; + _ckvmssts_noperl(sys$dclast(pipe_tochild2_ast,info->in,0)); + } else { + _ckvmssts_noperl(sys$cancel(info->in->chan_out)); + } + } + + if (info->out && !info->out_done) { /* were we also piping output? */ + info->out->shut_on_empty = TRUE; + iss = sys$qio(0,info->out->chan_in,IO$_WRITEOF|IO$M_NORSWAIT, 0, 0, 0, 0, 0, 0, 0, 0, 0); + if (iss == SS$_MBFULL) iss = SS$_NORMAL; + _ckvmssts_noperl(iss); + } + + if (info->err && !info->err_done) { /* we were piping stderr */ + info->err->shut_on_empty = TRUE; + iss = sys$qio(0,info->err->chan_in,IO$_WRITEOF|IO$M_NORSWAIT, 0, 0, 0, 0, 0, 0, 0, 0, 0); + if (iss == SS$_MBFULL) iss = SS$_NORMAL; + _ckvmssts_noperl(iss); + } + _ckvmssts_noperl(sys$setef(pipe_ef)); + +} + +static unsigned long int setup_cmddsc(pTHX_ char *cmd, int check_img, int *suggest_quote); +static void vms_execfree(pTHX); + +/* + we actually differ from vmstrnenv since we use this to + get the RMS IFI to check if SYS$OUTPUT and SYS$ERROR *really* + are pointing to the same thing +*/ + +static unsigned short +popen_translate(pTHX_ char *logical, char *result) +{ + int iss; + $DESCRIPTOR(d_table,"LNM$PROCESS_TABLE"); + $DESCRIPTOR(d_log,""); + struct _il3 { + unsigned short length; + unsigned short code; + char * buffer_addr; + unsigned short *retlenaddr; + } itmlst[2]; + unsigned short l, ifi; + + d_log.dsc$a_pointer = logical; + d_log.dsc$w_length = strlen(logical); + + itmlst[0].code = LNM$_STRING; + itmlst[0].length = 255; + itmlst[0].buffer_addr = result; + itmlst[0].retlenaddr = &l; + + itmlst[1].code = 0; + itmlst[1].length = 0; + itmlst[1].buffer_addr = 0; + itmlst[1].retlenaddr = 0; + + iss = sys$trnlnm(0, &d_table, &d_log, 0, itmlst); + if (iss == SS$_NOLOGNAM) { + iss = SS$_NORMAL; + l = 0; + } + if (!(iss&1)) lib$signal(iss); + result[l] = '\0'; +/* + logicals for PPFs have a 4 byte prefix ESC+NUL+(RMS IFI) + strip it off and return the ifi, if any +*/ + ifi = 0; + if (result[0] == 0x1b && result[1] == 0x00) { + memcpy(&ifi,result+2,2); + strcpy(result,result+4); + } + return ifi; /* this is the RMS internal file id */ +} + +#define MAX_DCL_SYMBOL 255 +static void pipe_infromchild_ast(pPipe p); + +/* + I'm using LIB$(GET|FREE)_VM here so that we can allocate and deallocate + inside an AST routine without worrying about reentrancy and which Perl + memory allocator is being used. + + We read data and queue up the buffers, then spit them out one at a + time to the output mailbox when the output mailbox is ready for one. + +*/ +#define INITIAL_TOCHILDQUEUE 2 + +static pPipe +pipe_tochild_setup(pTHX_ char *rmbx, char *wmbx) +{ + pPipe p; + pCBuf b; + char mbx1[64], mbx2[64]; + struct dsc$descriptor_s d_mbx1 = {sizeof mbx1, DSC$K_DTYPE_T, + DSC$K_CLASS_S, mbx1}, + d_mbx2 = {sizeof mbx2, DSC$K_DTYPE_T, + DSC$K_CLASS_S, mbx2}; + unsigned int dviitm = DVI$_DEVBUFSIZ; + int j, n; + + New(1368, p, 1, Pipe); + + create_mbx(aTHX_ &p->chan_in , &d_mbx1); + create_mbx(aTHX_ &p->chan_out, &d_mbx2); + _ckvmssts(lib$getdvi(&dviitm, &p->chan_in, 0, &p->bufsize)); + + p->buf = 0; + p->shut_on_empty = FALSE; + p->need_wake = FALSE; + p->type = 0; + p->retry = 0; + p->iosb.status = SS$_NORMAL; + p->iosb2.status = SS$_NORMAL; + p->free = RQE_ZERO; + p->wait = RQE_ZERO; + p->curr = 0; + p->curr2 = 0; + p->info = 0; +#ifdef PERL_IMPLICIT_CONTEXT + p->thx = aTHX; +#endif + + n = sizeof(CBuf) + p->bufsize; + + for (j = 0; j < INITIAL_TOCHILDQUEUE; j++) { + _ckvmssts(lib$get_vm(&n, &b)); + b->buf = (char *) b + sizeof(CBuf); + _ckvmssts(lib$insqhi(b, &p->free)); + } + + pipe_tochild2_ast(p); + pipe_tochild1_ast(p); + strcpy(wmbx, mbx1); + strcpy(rmbx, mbx2); + return p; +} + +/* reads the MBX Perl is writing, and queues */ + +static void +pipe_tochild1_ast(pPipe p) +{ + pCBuf b = p->curr; + int iss = p->iosb.status; + int eof = (iss == SS$_ENDOFFILE); +#ifdef PERL_IMPLICIT_CONTEXT + pTHX = p->thx; +#endif + + if (p->retry) { + if (eof) { + p->shut_on_empty = TRUE; + b->eof = TRUE; + _ckvmssts(sys$dassgn(p->chan_in)); + } else { + _ckvmssts(iss); + } + + b->eof = eof; + b->size = p->iosb.count; + _ckvmssts(lib$insqhi(b, &p->wait)); + if (p->need_wake) { + p->need_wake = FALSE; + _ckvmssts(sys$dclast(pipe_tochild2_ast,p,0)); + } + } else { + p->retry = 1; /* initial call */ + } + + if (eof) { /* flush the free queue, return when done */ + int n = sizeof(CBuf) + p->bufsize; + while (1) { + iss = lib$remqti(&p->free, &b); + if (iss == LIB$_QUEWASEMP) return; + _ckvmssts(iss); + _ckvmssts(lib$free_vm(&n, &b)); + } + } + + iss = lib$remqti(&p->free, &b); + if (iss == LIB$_QUEWASEMP) { + int n = sizeof(CBuf) + p->bufsize; + _ckvmssts(lib$get_vm(&n, &b)); + b->buf = (char *) b + sizeof(CBuf); + } else { + _ckvmssts(iss); + } + + p->curr = b; + iss = sys$qio(0,p->chan_in, + IO$_READVBLK|(p->shut_on_empty ? IO$M_NOWAIT : 0), + &p->iosb, + pipe_tochild1_ast, p, b->buf, p->bufsize, 0, 0, 0, 0); + if (iss == SS$_ENDOFFILE && p->shut_on_empty) iss = SS$_NORMAL; + _ckvmssts(iss); +} + + +/* writes queued buffers to output, waits for each to complete before + doing the next */ + +static void +pipe_tochild2_ast(pPipe p) +{ + pCBuf b = p->curr2; + int iss = p->iosb2.status; + int n = sizeof(CBuf) + p->bufsize; + int done = (p->info && p->info->done) || + iss == SS$_CANCEL || iss == SS$_ABORT; +#if defined(PERL_IMPLICIT_CONTEXT) + pTHX = p->thx; +#endif + + do { + if (p->type) { /* type=1 has old buffer, dispose */ + if (p->shut_on_empty) { + _ckvmssts(lib$free_vm(&n, &b)); + } else { + _ckvmssts(lib$insqhi(b, &p->free)); + } + p->type = 0; + } + + iss = lib$remqti(&p->wait, &b); + if (iss == LIB$_QUEWASEMP) { + if (p->shut_on_empty) { + if (done) { + _ckvmssts(sys$dassgn(p->chan_out)); + *p->pipe_done = TRUE; + _ckvmssts(sys$setef(pipe_ef)); + } else { + _ckvmssts(sys$qio(0,p->chan_out,IO$_WRITEOF, + &p->iosb2, pipe_tochild2_ast, p, 0, 0, 0, 0, 0, 0)); + } + return; + } + p->need_wake = TRUE; + return; + } + _ckvmssts(iss); + p->type = 1; + } while (done); + + + p->curr2 = b; + if (b->eof) { + _ckvmssts(sys$qio(0,p->chan_out,IO$_WRITEOF, + &p->iosb2, pipe_tochild2_ast, p, 0, 0, 0, 0, 0, 0)); + } else { + _ckvmssts(sys$qio(0,p->chan_out,IO$_WRITEVBLK, + &p->iosb2, pipe_tochild2_ast, p, b->buf, b->size, 0, 0, 0, 0)); + } + + return; + +} + + +static pPipe +pipe_infromchild_setup(pTHX_ char *rmbx, char *wmbx) +{ + pPipe p; + char mbx1[64], mbx2[64]; + struct dsc$descriptor_s d_mbx1 = {sizeof mbx1, DSC$K_DTYPE_T, + DSC$K_CLASS_S, mbx1}, + d_mbx2 = {sizeof mbx2, DSC$K_DTYPE_T, + DSC$K_CLASS_S, mbx2}; + unsigned int dviitm = DVI$_DEVBUFSIZ; + + New(1367, p, 1, Pipe); + create_mbx(aTHX_ &p->chan_in , &d_mbx1); + create_mbx(aTHX_ &p->chan_out, &d_mbx2); + + _ckvmssts(lib$getdvi(&dviitm, &p->chan_in, 0, &p->bufsize)); + New(1367, p->buf, p->bufsize, char); + p->shut_on_empty = FALSE; + p->info = 0; + p->type = 0; + p->iosb.status = SS$_NORMAL; +#if defined(PERL_IMPLICIT_CONTEXT) + p->thx = aTHX; +#endif + pipe_infromchild_ast(p); + + strcpy(wmbx, mbx1); + strcpy(rmbx, mbx2); + return p; +} + +static void +pipe_infromchild_ast(pPipe p) +{ + int iss = p->iosb.status; + int eof = (iss == SS$_ENDOFFILE); + int myeof = (eof && (p->iosb.dvispec == mypid || p->iosb.dvispec == 0)); + int kideof = (eof && (p->iosb.dvispec == p->info->pid)); +#if defined(PERL_IMPLICIT_CONTEXT) + pTHX = p->thx; +#endif + + if (p->info && p->info->closing && p->chan_out) { /* output shutdown */ + _ckvmssts(sys$dassgn(p->chan_out)); + p->chan_out = 0; + } + + /* read completed: + input shutdown if EOF from self (done or shut_on_empty) + output shutdown if closing flag set (my_pclose) + send data/eof from child or eof from self + otherwise, re-read (snarf of data from child) + */ + + if (p->type == 1) { + p->type = 0; + if (myeof && p->chan_in) { /* input shutdown */ + _ckvmssts(sys$dassgn(p->chan_in)); + p->chan_in = 0; + } + + if (p->chan_out) { + if (myeof || kideof) { /* pass EOF to parent */ + _ckvmssts(sys$qio(0,p->chan_out,IO$_WRITEOF, &p->iosb, + pipe_infromchild_ast, p, + 0, 0, 0, 0, 0, 0)); + return; + } else if (eof) { /* eat EOF --- fall through to read*/ + + } else { /* transmit data */ + _ckvmssts(sys$qio(0,p->chan_out,IO$_WRITEVBLK,&p->iosb, + pipe_infromchild_ast,p, + p->buf, p->iosb.count, 0, 0, 0, 0)); + return; + } + } + } + + /* everything shut? flag as done */ + + if (!p->chan_in && !p->chan_out) { + *p->pipe_done = TRUE; + _ckvmssts(sys$setef(pipe_ef)); + return; + } + + /* write completed (or read, if snarfing from child) + if still have input active, + queue read...immediate mode if shut_on_empty so we get EOF if empty + otherwise, + check if Perl reading, generate EOFs as needed + */ + + if (p->type == 0) { + p->type = 1; + if (p->chan_in) { + iss = sys$qio(0,p->chan_in,IO$_READVBLK|(p->shut_on_empty ? IO$M_NOW : 0),&p->iosb, + pipe_infromchild_ast,p, + p->buf, p->bufsize, 0, 0, 0, 0); + if (p->shut_on_empty && iss == SS$_ENDOFFILE) iss = SS$_NORMAL; + _ckvmssts(iss); + } else { /* send EOFs for extra reads */ + p->iosb.status = SS$_ENDOFFILE; + p->iosb.dvispec = 0; + _ckvmssts(sys$qio(0,p->chan_out,IO$_SETMODE|IO$M_READATTN, + 0, 0, 0, + pipe_infromchild_ast, p, 0, 0, 0, 0)); + } + } +} + +static pPipe +pipe_mbxtofd_setup(pTHX_ int fd, char *out) +{ + pPipe p; + char mbx[64]; + unsigned long dviitm = DVI$_DEVBUFSIZ; + struct stat s; + struct dsc$descriptor_s d_mbx = {sizeof mbx, DSC$K_DTYPE_T, + DSC$K_CLASS_S, mbx}; + + /* things like terminals and mbx's don't need this filter */ + if (fd && fstat(fd,&s) == 0) { + unsigned long dviitm = DVI$_DEVCHAR, devchar; + struct dsc$descriptor_s d_dev = {strlen(s.st_dev), DSC$K_DTYPE_T, + DSC$K_CLASS_S, s.st_dev}; + + _ckvmssts(lib$getdvi(&dviitm,0,&d_dev,&devchar,0,0)); + if (!(devchar & DEV$M_DIR)) { /* non directory structured...*/ + strcpy(out, s.st_dev); + return 0; + } + } + + New(1366, p, 1, Pipe); + p->fd_out = dup(fd); + create_mbx(aTHX_ &p->chan_in, &d_mbx); + _ckvmssts(lib$getdvi(&dviitm, &p->chan_in, 0, &p->bufsize)); + New(1366, p->buf, p->bufsize+1, char); + p->shut_on_empty = FALSE; + p->retry = 0; + p->info = 0; + strcpy(out, mbx); + + _ckvmssts(sys$qio(0, p->chan_in, IO$_READVBLK, &p->iosb, + pipe_mbxtofd_ast, p, + p->buf, p->bufsize, 0, 0, 0, 0)); + + return p; +} + +static void +pipe_mbxtofd_ast(pPipe p) +{ + int iss = p->iosb.status; + int done = p->info->done; + int iss2; + int eof = (iss == SS$_ENDOFFILE); + int myeof = eof && ((p->iosb.dvispec == mypid)||(p->iosb.dvispec == 0)); + int err = !(iss&1) && !eof; +#if defined(PERL_IMPLICIT_CONTEXT) + pTHX = p->thx; +#endif + + if (done && myeof) { /* end piping */ + close(p->fd_out); + sys$dassgn(p->chan_in); + *p->pipe_done = TRUE; + _ckvmssts(sys$setef(pipe_ef)); + return; + } + + if (!err && !eof) { /* good data to send to file */ + p->buf[p->iosb.count] = '\n'; + iss2 = write(p->fd_out, p->buf, p->iosb.count+1); + if (iss2 < 0) { + p->retry++; + if (p->retry < MAX_RETRY) { + _ckvmssts(sys$setimr(0,delaytime,pipe_mbxtofd_ast,p)); + return; + } + } + p->retry = 0; + } else if (err) { + _ckvmssts(iss); + } + + + iss = sys$qio(0, p->chan_in, IO$_READVBLK|(p->shut_on_empty ? IO$M_NOW : 0), &p->iosb, + pipe_mbxtofd_ast, p, + p->buf, p->bufsize, 0, 0, 0, 0); + if (p->shut_on_empty && (iss == SS$_ENDOFFILE)) iss = SS$_NORMAL; + _ckvmssts(iss); +} + + +typedef struct _pipeloc PLOC; +typedef struct _pipeloc* pPLOC; + +struct _pipeloc { + pPLOC next; + char dir[NAM$C_MAXRSS+1]; +}; +static pPLOC head_PLOC = 0; + +void +free_pipelocs(pTHX_ void *head) +{ + pPLOC p, pnext; + pPLOC *pHead = (pPLOC *)head; + + p = *pHead; + while (p) { + pnext = p->next; + Safefree(p); + p = pnext; + } + *pHead = 0; +} + +static void +store_pipelocs(pTHX) +{ + int i; + pPLOC p; + AV *av = 0; + SV *dirsv; + GV *gv; + char *dir, *x; + char *unixdir; + char temp[NAM$C_MAXRSS+1]; + STRLEN n_a; + + if (head_PLOC) + free_pipelocs(&head_PLOC); + +/* the . directory from @INC comes last */ + + New(1370,p,1,PLOC); + p->next = head_PLOC; + head_PLOC = p; + strcpy(p->dir,"./"); + +/* get the directory from $^X */ + + if (PL_origargv && PL_origargv[0]) { /* maybe nul if embedded Perl */ + strcpy(temp, PL_origargv[0]); + x = strrchr(temp,']'); + if (x) x[1] = '\0'; + + if ((unixdir = tounixpath(temp, Nullch)) != Nullch) { + New(1370,p,1,PLOC); + p->next = head_PLOC; + head_PLOC = p; + strncpy(p->dir,unixdir,sizeof(p->dir)-1); + p->dir[NAM$C_MAXRSS] = '\0'; + } + } + +/* reverse order of @INC entries, skip "." since entered above */ + + if (PL_incgv) av = GvAVn(PL_incgv); + + for (i = 0; av && i <= AvFILL(av); i++) { + dirsv = *av_fetch(av,i,TRUE); + + if (SvROK(dirsv)) continue; + dir = SvPVx(dirsv,n_a); + if (strcmp(dir,".") == 0) continue; + if ((unixdir = tounixpath(dir, Nullch)) == Nullch) + continue; + + New(1370,p,1,PLOC); + p->next = head_PLOC; + head_PLOC = p; + strncpy(p->dir,unixdir,sizeof(p->dir)-1); + p->dir[NAM$C_MAXRSS] = '\0'; + } + +/* most likely spot (ARCHLIB) put first in the list */ + +#ifdef ARCHLIB_EXP + if ((unixdir = tounixpath(ARCHLIB_EXP, Nullch)) != Nullch) { + New(1370,p,1,PLOC); + p->next = head_PLOC; + head_PLOC = p; + strncpy(p->dir,unixdir,sizeof(p->dir)-1); + p->dir[NAM$C_MAXRSS] = '\0'; + } +#endif + Perl_call_atexit(aTHX_ &free_pipelocs, &head_PLOC); +} + + +static char * +find_vmspipe(pTHX) +{ + static int vmspipe_file_status = 0; + static char vmspipe_file[NAM$C_MAXRSS+1]; + + /* already found? Check and use ... need read+execute permission */ + + if (vmspipe_file_status == 1) { + if (cando_by_name(S_IRUSR, 0, vmspipe_file) + && cando_by_name(S_IXUSR, 0, vmspipe_file)) { + return vmspipe_file; + } + vmspipe_file_status = 0; + } + + /* scan through stored @INC, $^X */ + + if (vmspipe_file_status == 0) { + char file[NAM$C_MAXRSS+1]; + pPLOC p = head_PLOC; + + while (p) { + strcpy(file, p->dir); + strncat(file, "vmspipe.com",NAM$C_MAXRSS); + file[NAM$C_MAXRSS] = '\0'; + p = p->next; + + if (!do_tovmsspec(file,vmspipe_file,0)) continue; + + if (cando_by_name(S_IRUSR, 0, vmspipe_file) + && cando_by_name(S_IXUSR, 0, vmspipe_file)) { + vmspipe_file_status = 1; + return vmspipe_file; + } + } + vmspipe_file_status = -1; /* failed, use tempfiles */ + } + + return 0; +} + +static FILE * +vmspipe_tempfile(pTHX) +{ + char file[NAM$C_MAXRSS+1]; + FILE *fp; + static int index = 0; + stat_t s0, s1; + + /* create a tempfile */ + + /* we can't go from W, shr=get to R, shr=get without + an intermediate vulnerable state, so don't bother trying... + + and lib$spawn doesn't shr=put, so have to close the write + + So... match up the creation date/time and the FID to + make sure we're dealing with the same file + + */ + + index++; + sprintf(file,"sys$scratch:perlpipe_%08.8x_%d.com",mypid,index); + fp = fopen(file,"w"); + if (!fp) { + sprintf(file,"sys$login:perlpipe_%08.8x_%d.com",mypid,index); + fp = fopen(file,"w"); + if (!fp) { + sprintf(file,"sys$disk:[]perlpipe_%08.8x_%d.com",mypid,index); + fp = fopen(file,"w"); + } + } + if (!fp) return 0; /* we're hosed */ + + fprintf(fp,"$! 'f$verify(0)\n"); + fprintf(fp,"$! --- protect against nonstandard definitions ---\n"); + fprintf(fp,"$ perl_cfile = f$environment(\"procedure\")\n"); + fprintf(fp,"$ perl_define = \"define/nolog\"\n"); + fprintf(fp,"$ perl_on = \"set noon\"\n"); + fprintf(fp,"$ perl_exit = \"exit\"\n"); + fprintf(fp,"$ perl_del = \"delete\"\n"); + fprintf(fp,"$ pif = \"if\"\n"); + fprintf(fp,"$! --- define i/o redirection (sys$output set by lib$spawn)\n"); + fprintf(fp,"$ pif perl_popen_in .nes. \"\" then perl_define/user/name_attributes=confine sys$input 'perl_popen_in'\n"); + fprintf(fp,"$ pif perl_popen_err .nes. \"\" then perl_define/user/name_attributes=confine sys$error 'perl_popen_err'\n"); + fprintf(fp,"$ pif perl_popen_out .nes. \"\" then perl_define sys$output 'perl_popen_out'\n"); + fprintf(fp,"$ cmd = perl_popen_cmd\n"); + fprintf(fp,"$! --- get rid of global symbols\n"); + fprintf(fp,"$ perl_del/symbol/global perl_popen_in\n"); + fprintf(fp,"$ perl_del/symbol/global perl_popen_err\n"); + fprintf(fp,"$ perl_del/symbol/global perl_popen_out\n"); + fprintf(fp,"$ perl_del/symbol/global perl_popen_cmd\n"); + fprintf(fp,"$ perl_on\n"); + fprintf(fp,"$ 'cmd\n"); + fprintf(fp,"$ perl_status = $STATUS\n"); + fprintf(fp,"$ perl_del 'perl_cfile'\n"); + fprintf(fp,"$ perl_exit 'perl_status'\n"); + fsync(fileno(fp)); + + fgetname(fp, file, 1); + fstat(fileno(fp), &s0); + fclose(fp); + + fp = fopen(file,"r","shr=get"); + if (!fp) return 0; + fstat(fileno(fp), &s1); + + if (s0.st_ino[0] != s1.st_ino[0] || + s0.st_ino[1] != s1.st_ino[1] || + s0.st_ino[2] != s1.st_ino[2] || + s0.st_ctime != s1.st_ctime ) { + fclose(fp); + return 0; + } + + return fp; +} + + + +static PerlIO * +safe_popen(pTHX_ char *cmd, char *in_mode, int *psts) +{ + static int handler_set_up = FALSE; + unsigned long int sts, flags=1; /* nowait - gnu c doesn't allow &1 */ + unsigned int table = LIB$K_CLI_GLOBAL_SYM; + int wait = 0; + char *p, mode[10], symbol[MAX_DCL_SYMBOL+1], *vmspipe; + char in[512], out[512], err[512], mbx[512]; + FILE *tpipe = 0; + char tfilebuf[NAM$C_MAXRSS+1]; + pInfo info; + struct dsc$descriptor_s d_symbol= {0, DSC$K_DTYPE_T, + DSC$K_CLASS_S, symbol}; + struct dsc$descriptor_s vmspipedsc = {0, DSC$K_DTYPE_T, + DSC$K_CLASS_S, 0}; + + $DESCRIPTOR(d_sym_cmd,"PERL_POPEN_CMD"); + $DESCRIPTOR(d_sym_in ,"PERL_POPEN_IN"); + $DESCRIPTOR(d_sym_out,"PERL_POPEN_OUT"); + $DESCRIPTOR(d_sym_err,"PERL_POPEN_ERR"); + + /* once-per-program initialization... + note that the SETAST calls and the dual test of pipe_ef + makes sure that only the FIRST thread through here does + the initialization...all other threads wait until it's + done. + + Yeah, uglier than a pthread call, it's got all the stuff inline + rather than in a separate routine. + */ + + if (!pipe_ef) { + _ckvmssts(sys$setast(0)); + if (!pipe_ef) { + unsigned long int pidcode = JPI$_PID; + $DESCRIPTOR(d_delay, RETRY_DELAY); + _ckvmssts(lib$get_ef(&pipe_ef)); + _ckvmssts(lib$getjpi(&pidcode,0,0,&mypid,0,0)); + _ckvmssts(sys$bintim(&d_delay, delaytime)); + } + if (!handler_set_up) { + _ckvmssts(sys$dclexh(&pipe_exitblock)); + handler_set_up = TRUE; + } + _ckvmssts(sys$setast(1)); + } + + /* see if we can find a VMSPIPE.COM */ + + tfilebuf[0] = '@'; + vmspipe = find_vmspipe(aTHX); + if (vmspipe) { + strcpy(tfilebuf+1,vmspipe); + } else { /* uh, oh...we're in tempfile hell */ + tpipe = vmspipe_tempfile(aTHX); + if (!tpipe) { /* a fish popular in Boston */ + if (ckWARN(WARN_PIPE)) { + Perl_warner(aTHX_ WARN_PIPE,"unable to find VMSPIPE.COM for i/o piping"); + } + return Nullfp; + } + fgetname(tpipe,tfilebuf+1,1); + } + vmspipedsc.dsc$a_pointer = tfilebuf; + vmspipedsc.dsc$w_length = strlen(tfilebuf); + + sts = setup_cmddsc(aTHX_ cmd,0,0); + if (!(sts & 1)) { + switch (sts) { + case RMS$_FNF: case RMS$_DNF: + set_errno(ENOENT); break; + case RMS$_DIR: + set_errno(ENOTDIR); break; + case RMS$_DEV: + set_errno(ENODEV); break; + case RMS$_PRV: + set_errno(EACCES); break; + case RMS$_SYN: + set_errno(EINVAL); break; + case CLI$_BUFOVF: case RMS$_RTB: case CLI$_TKNOVF: case CLI$_RSLOVF: + set_errno(E2BIG); break; + case LIB$_INVARG: case LIB$_INVSTRDES: case SS$_ACCVIO: /* shouldn't happen */ + _ckvmssts(sts); /* fall through */ + default: /* SS$_DUPLNAM, SS$_CLI, resource exhaustion, etc. */ + set_errno(EVMSERR); + } + set_vaxc_errno(sts); + if (*mode != 'n' && ckWARN(WARN_PIPE)) { + Perl_warner(aTHX_ WARN_PIPE,"Can't pipe \"%*s\": %s", strlen(cmd), cmd, Strerror(errno)); + } + *psts = sts; + return Nullfp; + } + New(1301,info,1,Info); + + strcpy(mode,in_mode); + info->mode = *mode; + info->done = FALSE; + info->completion = 0; + info->closing = FALSE; + info->in = 0; + info->out = 0; + info->err = 0; + info->fp = Nullfp; + info->useFILE = 0; + info->waiting = 0; + info->in_done = TRUE; + info->out_done = TRUE; + info->err_done = TRUE; + in[0] = out[0] = err[0] = '\0'; + + if ((p = strchr(mode,'F')) != NULL) { /* F -> use FILE* */ + info->useFILE = 1; + strcpy(p,p+1); + } + if ((p = strchr(mode,'W')) != NULL) { /* W -> wait for completion */ + wait = 1; + strcpy(p,p+1); + } + + if (*mode == 'r') { /* piping from subroutine */ + + info->out = pipe_infromchild_setup(aTHX_ mbx,out); + if (info->out) { + info->out->pipe_done = &info->out_done; + info->out_done = FALSE; + info->out->info = info; + } + if (!info->useFILE) { + info->fp = PerlIO_open(mbx, mode); + } else { + info->fp = (PerlIO *) freopen(mbx, mode, stdin); + Perl_vmssetuserlnm(aTHX_ "SYS$INPUT",mbx); + } + + if (!info->fp && info->out) { + sys$cancel(info->out->chan_out); + + while (!info->out_done) { + int done; + _ckvmssts(sys$setast(0)); + done = info->out_done; + if (!done) _ckvmssts(sys$clref(pipe_ef)); + _ckvmssts(sys$setast(1)); + if (!done) _ckvmssts(sys$waitfr(pipe_ef)); + } + if (info->out->buf) Safefree(info->out->buf); + Safefree(info->out); + Safefree(info); + *psts = RMS$_FNF; + return Nullfp; + } -static void -popen_completion_ast(struct pipe_details *thispipe) -{ - thispipe->done = TRUE; - if (waitpid_asleep) { - waitpid_asleep = 0; - sys$wake(0,0); - } -} + info->err = pipe_mbxtofd_setup(aTHX_ fileno(stderr), err); + if (info->err) { + info->err->pipe_done = &info->err_done; + info->err_done = FALSE; + info->err->info = info; + } -static unsigned long int setup_cmddsc(char *cmd, int check_img); -static void vms_execfree(pTHX); + } else if (*mode == 'w') { /* piping to subroutine */ -static PerlIO * -safe_popen(char *cmd, char *mode) -{ - static int handler_set_up = FALSE; - char mbxname[64]; - unsigned short int chan; - unsigned long int sts, flags=1; /* nowait - gnu c doesn't allow &1 */ - dTHX; - struct pipe_details *info; - struct dsc$descriptor_s namdsc = {sizeof mbxname, DSC$K_DTYPE_T, - DSC$K_CLASS_S, mbxname}, - cmddsc = {0, DSC$K_DTYPE_T, - DSC$K_CLASS_S, 0}; - + info->out = pipe_mbxtofd_setup(aTHX_ fileno(stdout), out); + if (info->out) { + info->out->pipe_done = &info->out_done; + info->out_done = FALSE; + info->out->info = info; + } - if (!(setup_cmddsc(cmd,0) & 1)) { set_errno(EINVAL); return Nullfp; } - New(1301,info,1,struct pipe_details); + info->err = pipe_mbxtofd_setup(aTHX_ fileno(stderr), err); + if (info->err) { + info->err->pipe_done = &info->err_done; + info->err_done = FALSE; + info->err->info = info; + } - /* create mailbox */ - create_mbx(&chan,&namdsc); + info->in = pipe_tochild_setup(aTHX_ in,mbx); + if (!info->useFILE) { + info->fp = PerlIO_open(mbx, mode); + } else { + info->fp = (PerlIO *) freopen(mbx, mode, stdout); + Perl_vmssetuserlnm(aTHX_ "SYS$OUTPUT",mbx); + } - /* open a FILE* onto it */ - info->fp = PerlIO_open(mbxname, mode); + if (info->in) { + info->in->pipe_done = &info->in_done; + info->in_done = FALSE; + info->in->info = info; + } - /* give up other channel onto it */ - _ckvmssts(sys$dassgn(chan)); + /* error cleanup */ + if (!info->fp && info->in) { + info->done = TRUE; + _ckvmssts(sys$qiow(0,info->in->chan_in, IO$_WRITEOF, 0, + 0, 0, 0, 0, 0, 0, 0, 0)); + + while (!info->in_done) { + int done; + _ckvmssts(sys$setast(0)); + done = info->in_done; + if (!done) _ckvmssts(sys$clref(pipe_ef)); + _ckvmssts(sys$setast(1)); + if (!done) _ckvmssts(sys$waitfr(pipe_ef)); + } - if (!info->fp) - return Nullfp; - - info->mode = *mode; - info->done = FALSE; - info->completion=0; + if (info->in->buf) Safefree(info->in->buf); + Safefree(info->in); + Safefree(info); + *psts = RMS$_FNF; + return Nullfp; + } - if (*mode == 'r') { - _ckvmssts(lib$spawn(&VMScmd, &nl_desc, &namdsc, &flags, - 0 /* name */, &info->pid, &info->completion, - 0, popen_completion_ast,info,0,0,0)); - } - else { - _ckvmssts(lib$spawn(&VMScmd, &namdsc, 0 /* sys$output */, &flags, - 0 /* name */, &info->pid, &info->completion, - 0, popen_completion_ast,info,0,0,0)); - } - vms_execfree(aTHX); - if (!handler_set_up) { - _ckvmssts(sys$dclexh(&pipe_exitblock)); - handler_set_up = TRUE; + } else if (*mode == 'n') { /* separate subprocess, no Perl i/o */ + info->out = pipe_mbxtofd_setup(aTHX_ fileno(stdout), out); + if (info->out) { + info->out->pipe_done = &info->out_done; + info->out_done = FALSE; + info->out->info = info; + } + + info->err = pipe_mbxtofd_setup(aTHX_ fileno(stderr), err); + if (info->err) { + info->err->pipe_done = &info->err_done; + info->err_done = FALSE; + info->err->info = info; + } } + + symbol[MAX_DCL_SYMBOL] = '\0'; + + strncpy(symbol, in, MAX_DCL_SYMBOL); + d_symbol.dsc$w_length = strlen(symbol); + _ckvmssts(lib$set_symbol(&d_sym_in, &d_symbol, &table)); + + strncpy(symbol, err, MAX_DCL_SYMBOL); + d_symbol.dsc$w_length = strlen(symbol); + _ckvmssts(lib$set_symbol(&d_sym_err, &d_symbol, &table)); + + strncpy(symbol, out, MAX_DCL_SYMBOL); + d_symbol.dsc$w_length = strlen(symbol); + _ckvmssts(lib$set_symbol(&d_sym_out, &d_symbol, &table)); + + p = VMSCMD.dsc$a_pointer; + while (*p && *p != '\n') p++; + *p = '\0'; /* truncate on \n */ + p = VMSCMD.dsc$a_pointer; + while (*p == ' ' || *p == '\t') p++; /* remove leading whitespace */ + if (*p == '$') p++; /* remove leading $ */ + while (*p == ' ' || *p == '\t') p++; + strncpy(symbol, p, MAX_DCL_SYMBOL); + d_symbol.dsc$w_length = strlen(symbol); + _ckvmssts(lib$set_symbol(&d_sym_cmd, &d_symbol, &table)); + + _ckvmssts(sys$setast(0)); info->next=open_pipes; /* prepend to list */ open_pipes=info; + _ckvmssts(sys$setast(1)); + _ckvmssts(lib$spawn(&vmspipedsc, &nl_desc, &nl_desc, &flags, + 0, &info->pid, &info->completion, + 0, popen_completion_ast,info,0,0,0)); + + /* if we were using a tempfile, close it now */ + + if (tpipe) fclose(tpipe); + + /* once the subprocess is spawned, it has copied the symbols and + we can get rid of ours */ + + _ckvmssts(lib$delete_symbol(&d_sym_cmd, &table)); + _ckvmssts(lib$delete_symbol(&d_sym_in, &table)); + _ckvmssts(lib$delete_symbol(&d_sym_err, &table)); + _ckvmssts(lib$delete_symbol(&d_sym_out, &table)); + vms_execfree(aTHX); PL_forkprocess = info->pid; + if (wait) { + int done = 0; + while (!done) { + _ckvmssts(sys$setast(0)); + done = info->done; + if (!done) _ckvmssts(sys$clref(pipe_ef)); + _ckvmssts(sys$setast(1)); + if (!done) _ckvmssts(sys$waitfr(pipe_ef)); + } + *psts = info->completion; + my_pclose(info->fp); + } else { + *psts = SS$_NORMAL; + } return info->fp; } /* end of safe_popen */ -/*{{{ FILE *my_popen(char *cmd, char *mode)*/ -FILE * +/*{{{ PerlIO *my_popen(char *cmd, char *mode)*/ +PerlIO * Perl_my_popen(pTHX_ char *cmd, char *mode) { + int sts; TAINT_ENV(); TAINT_PROPER("popen"); PERL_FLUSHALL_FOR_CHILD; - return safe_popen(cmd,mode); + return safe_popen(aTHX_ cmd,mode,&sts); } /*}}}*/ -/*{{{ I32 my_pclose(FILE *fp)*/ -I32 Perl_my_pclose(pTHX_ FILE *fp) +/*{{{ I32 my_pclose(PerlIO *fp)*/ +I32 Perl_my_pclose(pTHX_ PerlIO *fp) { - struct pipe_details *info, *last = NULL; + pInfo info, last = NULL; unsigned long int retsts; - int need_eof; + int done, iss; for (info = open_pipes; info != NULL; last = info, info = info->next) if (info->fp == fp) break; @@ -1188,74 +2393,248 @@ I32 Perl_my_pclose(pTHX_ FILE *fp) /* If we were writing to a subprocess, insure that someone reading from * the mailbox gets an EOF. It looks like a simple fclose() doesn't - * produce an EOF record in the mailbox. */ + * produce an EOF record in the mailbox. + * + * well, at least sometimes it *does*, so we have to watch out for + * the first EOF closing the pipe (and DASSGN'ing the channel)... + */ + if (info->fp) { + if (!info->useFILE) + PerlIO_flush(info->fp); /* first, flush data */ + else + fflush((FILE *)info->fp); + } + _ckvmssts(sys$setast(0)); - need_eof = info->mode != 'r' && !info->done; + info->closing = TRUE; + done = info->done && info->in_done && info->out_done && info->err_done; + /* hanging on write to Perl's input? cancel it */ + if (info->mode == 'r' && info->out && !info->out_done) { + if (info->out->chan_out) { + _ckvmssts(sys$cancel(info->out->chan_out)); + if (!info->out->chan_in) { /* EOF generation, need AST */ + _ckvmssts(sys$dclast(pipe_infromchild_ast,info->out,0)); + } + } + } + if (info->in && !info->in_done && !info->in->shut_on_empty) /* EOF if hasn't had one yet */ + _ckvmssts(sys$qio(0,info->in->chan_in,IO$_WRITEOF,0,0,0, + 0, 0, 0, 0, 0, 0)); _ckvmssts(sys$setast(1)); - if (need_eof) pipe_eof(info->fp,0); + if (info->fp) { + if (!info->useFILE) PerlIO_close(info->fp); + else + fclose((FILE *)info->fp); + } + /* + we have to wait until subprocess completes, but ALSO wait until all + the i/o completes...otherwise we'll be freeing the "info" structure + that the i/o ASTs could still be using... + */ - if (info->done) retsts = info->completion; - else waitpid(info->pid,(int *) &retsts,0); + while (!done) { + _ckvmssts(sys$setast(0)); + done = info->done && info->in_done && info->out_done && info->err_done; + if (!done) _ckvmssts(sys$clref(pipe_ef)); + _ckvmssts(sys$setast(1)); + if (!done) _ckvmssts(sys$waitfr(pipe_ef)); + } + retsts = info->completion; /* remove from list of open pipes */ _ckvmssts(sys$setast(0)); if (last) last->next = info->next; else open_pipes = info->next; _ckvmssts(sys$setast(1)); + + /* free buffers and structures */ + + if (info->in) { + if (info->in->buf) Safefree(info->in->buf); + Safefree(info->in); + } + if (info->out) { + if (info->out->buf) Safefree(info->out->buf); + Safefree(info->out); + } + if (info->err) { + if (info->err->buf) Safefree(info->err->buf); + Safefree(info->err); + } Safefree(info); return retsts; } /* end of my_pclose() */ -/* sort-of waitpid; use only with popen() */ +#if defined(__CRTL_VER) && __CRTL_VER >= 70100322 + /* Roll our own prototype because we want this regardless of whether + * _VMS_WAIT is defined. + */ + __pid_t __vms_waitpid( __pid_t __pid, int *__stat_loc, int __options ); +#endif +/* sort-of waitpid; special handling of pipe clean-up for subprocesses + created with popen(); otherwise partially emulate waitpid() unless + we have a suitable one from the CRTL that came with VMS 7.2 and later. + Also check processes not considered by the CRTL waitpid(). + */ /*{{{Pid_t my_waitpid(Pid_t pid, int *statusp, int flags)*/ Pid_t -my_waitpid(Pid_t pid, int *statusp, int flags) +Perl_my_waitpid(pTHX_ Pid_t pid, int *statusp, int flags) { - struct pipe_details *info; - dTHX; + pInfo info; + int done; + int sts; + + if (statusp) *statusp = 0; for (info = open_pipes; info != NULL; info = info->next) if (info->pid == pid) break; if (info != NULL) { /* we know about this child */ while (!info->done) { - waitpid_asleep = 1; - sys$hiber(); + _ckvmssts(sys$setast(0)); + done = info->done; + if (!done) _ckvmssts(sys$clref(pipe_ef)); + _ckvmssts(sys$setast(1)); + if (!done) _ckvmssts(sys$waitfr(pipe_ef)); } - *statusp = info->completion; + if (statusp) *statusp = info->completion; return pid; + } - else { /* we haven't heard of this child */ + else { /* this child is not one of our own pipe children */ + +#if defined(__CRTL_VER) && __CRTL_VER >= 70100322 + + /* waitpid() became available in the CRTL as of VMS 7.0, but only + * in 7.2 did we get a version that fills in the VMS completion + * status as Perl has always tried to do. + */ + + sts = __vms_waitpid( pid, statusp, flags ); + + if ( sts == 0 || !(sts == -1 && errno == ECHILD) ) + return sts; + + /* If the real waitpid tells us the child does not exist, we + * fall through here to implement waiting for a child that + * was created by some means other than exec() (say, spawned + * from DCL) or to wait for a process that is not a subprocess + * of the current process. + */ + +#endif /* defined(__CRTL_VER) && __CRTL_VER >= 70100322 */ + $DESCRIPTOR(intdsc,"0 00:00:01"); - unsigned long int ownercode = JPI$_OWNER, ownerpid, mypid; - unsigned long int interval[2],sts; + unsigned long int ownercode = JPI$_OWNER, ownerpid; + unsigned long int pidcode = JPI$_PID, mypid; + unsigned long int interval[2]; + int termination_mbu = 0; + unsigned short qio_iosb[4]; + unsigned int jpi_iosb[2]; + struct itmlst_3 jpilist[3] = { + {sizeof(ownerpid), JPI$_OWNER, &ownerpid, 0}, + {sizeof(termination_mbu), JPI$_TMBU, &termination_mbu, 0}, + { 0, 0, 0, 0} + }; + char trmmbx[NAM$C_DVI+1]; + $DESCRIPTOR(trmmbxdsc,trmmbx); + struct accdef trmmsg; + unsigned short int mbxchan; + + if (pid <= 0) { + /* Sorry folks, we don't presently implement rooting around for + the first child we can find, and we definitely don't want to + pass a pid of -1 to $getjpi, where it is a wildcard operation. + */ + set_errno(ENOTSUP); + return -1; + } + + /* Get the owner of the child so I can warn if it's not mine, plus + * get the termination mailbox. If the process doesn't exist or I + * don't have the privs to look at it, I can go home early. + */ + sts = sys$getjpiw(0,&pid,NULL,&jpilist,&jpi_iosb,NULL,NULL); + if (sts & 1) sts = jpi_iosb[0]; + if (!(sts & 1)) { + switch (sts) { + case SS$_NONEXPR: + set_errno(ECHILD); + break; + case SS$_NOPRIV: + set_errno(EACCES); + break; + default: + _ckvmssts(sts); + } + set_vaxc_errno(sts); + return -1; + } if (ckWARN(WARN_EXEC)) { - _ckvmssts(lib$getjpi(&ownercode,&pid,0,&ownerpid,0,0)); - _ckvmssts(lib$getjpi(&ownercode,0,0,&mypid,0,0)); + /* remind folks they are asking for non-standard waitpid behavior */ + _ckvmssts(lib$getjpi(&pidcode,0,0,&mypid,0,0)); if (ownerpid != mypid) - Perl_warner(aTHX_ WARN_EXEC,"pid %x not a child",pid); + Perl_warner(aTHX_ WARN_EXEC, + "waitpid: process %x is not a child of process %x", + pid,mypid); } - _ckvmssts(sys$bintim(&intdsc,interval)); - while ((sts=lib$getjpi(&ownercode,&pid,0,&ownerpid,0,0)) & 1) { - _ckvmssts(sys$schdwk(0,0,interval,0)); - _ckvmssts(sys$hiber()); + /* It's possible to have a mailbox unit number but no actual mailbox; we + * check for this by assigning a channel to it, which we need anyway. + */ + if (termination_mbu != 0) { + sprintf(trmmbx, "MBA%d:", termination_mbu); + trmmbxdsc.dsc$w_length = strlen(trmmbx); + sts = sys$assign(&trmmbxdsc, &mbxchan, 0, 0); + if (sts == SS$_NOSUCHDEV) { + termination_mbu = 0; /* set up to take "no mailbox" case */ + sts = SS$_NORMAL; + } + _ckvmssts(sts); } - _ckvmssts(sts); - - /* There's no easy way to find the termination status a child we're - * not aware of beforehand. If we're really interested in the future, - * we can go looking for a termination mailbox, or chase after the - * accounting record for the process. + /* If the process doesn't have a termination mailbox, then simply check + * on it once a second until it's not there anymore. */ - *statusp = 0; + if (termination_mbu == 0) { + _ckvmssts(sys$bintim(&intdsc,interval)); + while ((sts=lib$getjpi(&ownercode,&pid,0,&ownerpid,0,0)) & 1) { + _ckvmssts(sys$schdwk(0,0,interval,0)); + _ckvmssts(sys$hiber()); + } + if (sts == SS$_NONEXPR) sts = SS$_NORMAL; + } + else { + /* If we do have a termination mailbox, post reads to it until we get a + * termination message, discarding messages of the wrong type or for other + * processes. If there is a place to put the final status, then do so. + */ + sts = SS$_NORMAL; + while (sts & 1) { + memset((void *) &trmmsg, 0, sizeof(trmmsg)); + sts = sys$qiow(0,mbxchan,IO$_READVBLK,&qio_iosb,0,0, + &trmmsg,ACC$K_TERMLEN,0,0,0,0); + if (sts & 1) sts = qio_iosb[0]; + + if ( sts & 1 + && trmmsg.acc$w_msgtyp == MSG$_DELPROC + && trmmsg.acc$l_pid == pid ) { + + if (statusp) *statusp = trmmsg.acc$l_finalsts; + sts = sys$dassgn(mbxchan); + break; + } + } + } /* termination_mbu ? */ + + _ckvmssts(sts); return pid; - } + + } /* else one of our own pipe children */ } /* end of waitpid() */ /*}}}*/ @@ -1523,6 +2902,10 @@ static char *mp_do_fileify_dirspec(pTHX_ char *dir,char *buf,int ts) dir[--dirlen] = '\0'; dir[dirlen-1] = ']'; } + if (dirlen >= 2 && !strcmp(dir+dirlen-2,".>")) { + dir[--dirlen] = '\0'; + dir[dirlen-1] = '>'; + } if ((cp1 = strrchr(dir,']')) != NULL || (cp1 = strrchr(dir,'>')) != NULL) { /* If we've got an explicit filename, we can just shuffle the string. */ @@ -1745,6 +3128,7 @@ static char *mp_do_fileify_dirspec(pTHX_ char *dir,char *buf,int ts) else if (ts) New(1312,retspec,retlen+16,char); else retspec = __fileify_retbuf; cp1 = strstr(esa,"]["); + if (!cp1) cp1 = strstr(esa,"]<"); dirlen = cp1 - esa; memcpy(retspec,esa,dirlen); if (!strncmp(cp1+2,"000000]",7)) { @@ -2364,7 +3748,7 @@ static void mp_expand_wild_cards(pTHX_ char *item, static int background_process(int argc, char **argv); -static void pipe_and_fork(char **cmargv); +static void pipe_and_fork(pTHX_ char **cmargv); /*{{{ void getredirection(int *ac, char ***av)*/ static void @@ -2428,7 +3812,7 @@ mp_getredirection(pTHX_ int *ac, char ***av) { if (j+1 >= argc) { - PerlIO_printf(Perl_debug_log,"No input file after < on command line"); + fprintf(stderr,"No input file after < on command line"); exit(LIB$_WRONUMARG); } in = argv[++j]; @@ -2443,7 +3827,7 @@ mp_getredirection(pTHX_ int *ac, char ***av) { if (j+1 >= argc) { - PerlIO_printf(Perl_debug_log,"No output file after > on command line"); + fprintf(stderr,"No output file after > on command line"); exit(LIB$_WRONUMARG); } out = argv[++j]; @@ -2463,7 +3847,7 @@ mp_getredirection(pTHX_ int *ac, char ***av) out = 1 + ap; if (j >= argc) { - PerlIO_printf(Perl_debug_log,"No output file after > or >> on command line"); + fprintf(stderr,"No output file after > or >> on command line"); exit(LIB$_WRONUMARG); } continue; @@ -2485,7 +3869,7 @@ mp_getredirection(pTHX_ int *ac, char ***av) err = 2 + ap; if (j >= argc) { - PerlIO_printf(Perl_debug_log,"No output file after 2> or 2>> on command line"); + fprintf(stderr,"No output file after 2> or 2>> on command line"); exit(LIB$_WRONUMARG); } continue; @@ -2494,7 +3878,7 @@ mp_getredirection(pTHX_ int *ac, char ***av) { if (j+1 >= argc) { - PerlIO_printf(Perl_debug_log,"No command into which to pipe on command line"); + fprintf(stderr,"No command into which to pipe on command line"); exit(LIB$_WRONUMARG); } cmargc = argc-(j+1); @@ -2525,10 +3909,10 @@ mp_getredirection(pTHX_ int *ac, char ***av) { if (out != NULL) { - PerlIO_printf(Perl_debug_log,"'|' and '>' may not both be specified on command line"); + fprintf(stderr,"'|' and '>' may not both be specified on command line"); exit(LIB$_INVARGORD); } - pipe_and_fork(cmargv); + pipe_and_fork(aTHX_ cmargv); } /* Check for input from a pipe (mailbox) */ @@ -2544,7 +3928,7 @@ mp_getredirection(pTHX_ int *ac, char ***av) /* Input from a pipe, reopen it in binary mode to disable */ /* carriage control processing. */ - PerlIO_getname(stdin, mbxname); + fgetname(stdin, mbxname); mbxnam.dsc$a_pointer = mbxname; mbxnam.dsc$w_length = strlen(mbxnam.dsc$a_pointer); lib$getdvi(&dvi_item, 0, &mbxnam, &bufsize, 0, 0); @@ -2558,35 +3942,39 @@ mp_getredirection(pTHX_ int *ac, char ***av) freopen(mbxname, "rb", stdin); if (errno != 0) { - PerlIO_printf(Perl_debug_log,"Can't reopen input pipe (name: %s) in binary mode",mbxname); + fprintf(stderr,"Can't reopen input pipe (name: %s) in binary mode",mbxname); exit(vaxc$errno); } } if ((in != NULL) && (NULL == freopen(in, "r", stdin, "mbc=32", "mbf=2"))) { - PerlIO_printf(Perl_debug_log,"Can't open input file %s as stdin",in); + fprintf(stderr,"Can't open input file %s as stdin",in); exit(vaxc$errno); } if ((out != NULL) && (NULL == freopen(out, outmode, stdout, "mbc=32", "mbf=2"))) { - PerlIO_printf(Perl_debug_log,"Can't open output file %s as stdout",out); + fprintf(stderr,"Can't open output file %s as stdout",out); exit(vaxc$errno); } + if (out != NULL) Perl_vmssetuserlnm(aTHX_ "SYS$OUTPUT",out); + if (err != NULL) { if (strcmp(err,"&1") == 0) { - dup2(fileno(stdout), fileno(Perl_debug_log)); + dup2(fileno(stdout), fileno(stderr)); + Perl_vmssetuserlnm(aTHX_ "SYS$ERROR","SYS$OUTPUT"); } else { FILE *tmperr; if (NULL == (tmperr = fopen(err, errmode, "mbc=32", "mbf=2"))) { - PerlIO_printf(Perl_debug_log,"Can't open error file %s as stderr",err); + fprintf(stderr,"Can't open error file %s as stderr",err); exit(vaxc$errno); } fclose(tmperr); - if (NULL == freopen(err, "a", Perl_debug_log, "mbc=32", "mbf=2")) + if (NULL == freopen(err, "a", stderr, "mbc=32", "mbf=2")) { exit(vaxc$errno); } + Perl_vmssetuserlnm(aTHX_ "SYS$ERROR",err); } } #ifdef ARGPROC_DEBUG @@ -2757,40 +4145,53 @@ static struct exit_control_block exit_block = 0 }; -static void pipe_and_fork(char **cmargv) +static void +pipe_and_fork(pTHX_ char **cmargv) { - char subcmd[2048]; - $DESCRIPTOR(cmddsc, ""); - static char mbxname[64]; - $DESCRIPTOR(mbxdsc, mbxname); - int pid, j; - unsigned long int zero = 0, one = 1; - - strcpy(subcmd, cmargv[0]); - for (j = 1; NULL != cmargv[j]; ++j) - { - strcat(subcmd, " \""); - strcat(subcmd, cmargv[j]); - strcat(subcmd, "\""); + PerlIO *fp; + char subcmd[2*MAX_DCL_LINE_LENGTH], *p, *q; + int sts, j, l, ismcr, quote, tquote = 0; + + sts = setup_cmddsc(cmargv[0],0,"e); + + j = l = 0; + p = subcmd; + q = cmargv[0]; + ismcr = q && toupper(*q) == 'M' && toupper(*(q+1)) == 'C' + && toupper(*(q+2)) == 'R' && !*(q+3); + + while (q && l < MAX_DCL_LINE_LENGTH) { + if (!*q) { + if (j > 0 && quote) { + *p++ = '"'; + l++; + } + q = cmargv[++j]; + if (q) { + if (ismcr && j > 1) quote = 1; + tquote = (strchr(q,' ')) != NULL || *q == '\0'; + *p++ = ' '; + l++; + if (quote || tquote) { + *p++ = '"'; + l++; + } + } + } else { + if ((quote||tquote) && *q == '"') { + *p++ = '"'; + l++; } - cmddsc.dsc$a_pointer = subcmd; - cmddsc.dsc$w_length = strlen(cmddsc.dsc$a_pointer); + *p++ = *q++; + l++; + } + } + *p = '\0'; - create_mbx(&child_chan,&mbxdsc); -#ifdef ARGPROC_DEBUG - PerlIO_printf(Perl_debug_log, "Pipe Mailbox Name = '%s'\n", mbxdsc.dsc$a_pointer); - PerlIO_printf(Perl_debug_log, "Sub Process Command = '%s'\n", cmddsc.dsc$a_pointer); -#endif - _ckvmssts_noperl(lib$spawn(&cmddsc, &mbxdsc, 0, &one, - 0, &pid, child_st, &zero, sig_child, - &child_chan)); -#ifdef ARGPROC_DEBUG - PerlIO_printf(Perl_debug_log, "Subprocess's Pid = %08X\n", pid); -#endif - sys$dclexh(&exit_block); - if (NULL == freopen(mbxname, "wb", stdout)) - { - PerlIO_printf(Perl_debug_log,"Can't open output pipe (name %s)",mbxname); + store_pipelocs(); /* gets redone later */ + fp = safe_popen(subcmd,"wbF",&sts); + if (fp == Nullfp) { + PerlIO_printf(Perl_debug_log,"Can't open output pipe (status %d)",sts); } } @@ -2856,17 +4257,19 @@ vms_image_init(int *argcp, char ***argvp) unsigned long int iprv[(sizeof(union prvdef) + sizeof(unsigned long int) - 1) / sizeof(unsigned long int)]; unsigned short int dummy, rlen; struct dsc$descriptor_s **tabvec; - dTHX; +#if defined(PERL_IMPLICIT_CONTEXT) + pTHX = NULL; +#endif struct itmlst_3 jpilist[4] = { {sizeof iprv, JPI$_IMAGPRIV, iprv, &dummy}, {sizeof rlst, JPI$_RIGHTSLIST, rlst, &rlen}, { sizeof rsz, JPI$_RIGHTS_SIZE, &rsz, &dummy}, { 0, 0, 0, 0} }; - _ckvmssts(sys$getjpiw(0,NULL,NULL,jpilist,iosb,NULL,NULL)); - _ckvmssts(iosb[0]); + _ckvmssts_noperl(sys$getjpiw(0,NULL,NULL,jpilist,iosb,NULL,NULL)); + _ckvmssts_noperl(iosb[0]); for (i = 0; i < sizeof iprv / sizeof(unsigned long int); i++) { if (iprv[i]) { /* Running image installed with privs? */ - _ckvmssts(sys$setprv(0,iprv,0,NULL)); /* Turn 'em off. */ + _ckvmssts_noperl(sys$setprv(0,iprv,0,NULL)); /* Turn 'em off. */ will_taint = TRUE; break; } @@ -2878,12 +4281,21 @@ vms_image_init(int *argcp, char ***argvp) * buffer much larger than $GETJPI wants (rsz is size in bytes that * were needed to hold all identifiers at time of last call; we'll * allocate that many unsigned long ints), and go back and get 'em. + * If it gave us less than it wanted to despite ample buffer space, + * something's broken. Is your system missing a system identifier? */ + if (rsz <= jpilist[1].buflen) { + /* Perl_croak accvios when used this early in startup. */ + fprintf(stderr, "vms_image_init: $getjpiw refuses to store RIGHTSLIST of %u bytes in buffer of %u bytes.\n%s", + rsz, (unsigned long) jpilist[1].buflen, + "Check your rights database for corruption.\n"); + exit(SS$_ABORT); + } if (jpilist[1].bufadr != rlst) Safefree(jpilist[1].bufadr); jpilist[1].bufadr = New(1320,mask,rsz,unsigned long int); jpilist[1].buflen = rsz * sizeof(unsigned long int); - _ckvmssts(sys$getjpiw(0,NULL,NULL,&jpilist[1],iosb,NULL,NULL)); - _ckvmssts(iosb[0]); + _ckvmssts_noperl(sys$getjpiw(0,NULL,NULL,&jpilist[1],iosb,NULL,NULL)); + _ckvmssts_noperl(iosb[0]); } mask = jpilist[1].bufadr; /* Check attribute flags for each identifier (2nd longword); protected @@ -2939,12 +4351,12 @@ vms_image_init(int *argcp, char ***argvp) tabvec[tabidx]->dsc$b_dtype = DSC$K_DTYPE_T; tabvec[tabidx]->dsc$b_class = DSC$K_CLASS_D; tabvec[tabidx]->dsc$a_pointer = NULL; - _ckvmssts(lib$scopy_r_dx(&len,eqv,tabvec[tabidx])); + _ckvmssts_noperl(lib$scopy_r_dx(&len,eqv,tabvec[tabidx])); } if (tabidx) { tabvec[tabidx] = NULL; env_tables = tabvec; } getredirection(argcp,argvp); -#if defined(USE_THREADS) && defined(__DECC) +#if defined(USE_5005THREADS) && ( defined(__DECC) || defined(__DECCXX) ) { # include (void) decc$set_reentrancy(C$C_MULTITHREAD); @@ -3195,8 +4607,7 @@ closedir(DIR *dd) * Collect all the version numbers for the current file. */ static void -collectversions(dd) - DIR *dd; +collectversions(pTHX_ DIR *dd) { struct dsc$descriptor_s pat; struct dsc$descriptor_s res; @@ -3204,7 +4615,6 @@ collectversions(dd) char *p, *text, buff[sizeof dd->entry.d_name]; int i; unsigned long context, tmpsts; - dTHX; /* Convenient shorthand. */ e = &dd->entry; @@ -3251,7 +4661,7 @@ collectversions(dd) */ /*{{{ struct dirent *readdir(DIR *dd)*/ struct dirent * -readdir(DIR *dd) +Perl_readdir(pTHX_ DIR *dd) { struct dsc$descriptor_s res; char *p, buff[sizeof dd->entry.d_name]; @@ -3296,7 +4706,7 @@ readdir(DIR *dd) dd->entry.d_namlen = strlen(dd->entry.d_name); dd->entry.vms_verscount = 0; - if (dd->vms_wantversions) collectversions(dd); + if (dd->vms_wantversions) collectversions(aTHX_ dd); return &dd->entry; } /* end of readdir() */ @@ -3318,10 +4728,9 @@ telldir(DIR *dd) */ /*{{{ void seekdir(DIR *dd,long count)*/ void -seekdir(DIR *dd, long count) +Perl_seekdir(pTHX_ DIR *dd, long count) { int vms_wantversions; - dTHX; /* If we haven't done anything yet... */ if (dd->count == 0) @@ -3387,20 +4796,19 @@ my_vfork() static void vms_execfree(pTHX) { if (PL_Cmd) { - if (PL_Cmd != VMScmd.dsc$a_pointer) Safefree(PL_Cmd); + if (PL_Cmd != VMSCMD.dsc$a_pointer) Safefree(PL_Cmd); PL_Cmd = Nullch; } - if (VMScmd.dsc$a_pointer) { - Safefree(VMScmd.dsc$a_pointer); - VMScmd.dsc$w_length = 0; - VMScmd.dsc$a_pointer = Nullch; + if (VMSCMD.dsc$a_pointer) { + Safefree(VMSCMD.dsc$a_pointer); + VMSCMD.dsc$w_length = 0; + VMSCMD.dsc$a_pointer = Nullch; } } static char * -setup_argstr(SV *really, SV **mark, SV **sp) +setup_argstr(pTHX_ SV *really, SV **mark, SV **sp) { - dTHX; char *junk, *tmps = Nullch; register size_t cmdlen = 0; size_t rlen; @@ -3443,7 +4851,7 @@ setup_argstr(SV *really, SV **mark, SV **sp) static unsigned long int -setup_cmddsc(char *cmd, int check_img) +setup_cmddsc(pTHX_ char *cmd, int check_img, int *suggest_quote) { char vmsspec[NAM$C_MAXRSS+1], resspec[NAM$C_MAXRSS+1]; $DESCRIPTOR(defdsc,".EXE"); @@ -3453,11 +4861,11 @@ setup_cmddsc(char *cmd, int check_img) unsigned long int cxt = 0, flags = 1, retsts = SS$_NORMAL; register char *s, *rest, *cp, *wordbreak; register int isdcl; - dTHX; - if (strlen(cmd) > - (sizeof(vmsspec) > sizeof(resspec) ? sizeof(resspec) : sizeof(vmsspec))) - return LIB$_INVARG; + if (suggest_quote) *suggest_quote = 0; + + if (strlen(cmd) > MAX_DCL_LINE_LENGTH) + return CLI$_BUFOVF; /* continuation lines currently unsupported */ s = cmd; while (*s && isspace(*s)) s++; @@ -3489,8 +4897,10 @@ setup_cmddsc(char *cmd, int check_img) * - if it doesn't, caller tells us whether to default to a DCL * command, or to a local image unless told it's DCL (by leading '$') */ - if (*s == '@') isdcl = 1; - else { + if (*s == '@') { + isdcl = 1; + if (suggest_quote) *suggest_quote = 1; + } else { register char *filespec = strpbrk(s,":<[.;"); rest = wordbreak = strpbrk(s," \"\t/"); if (!wordbreak) wordbreak = s + strlen(s); @@ -3535,24 +4945,40 @@ setup_cmddsc(char *cmd, int check_img) if (check_img && isdcl) return RMS$_FNF; if (cando_by_name(S_IXUSR,0,resspec)) { - New(402,VMScmd.dsc$a_pointer,7 + s - resspec + (rest ? strlen(rest) : 0),char); + New(402,VMSCMD.dsc$a_pointer,7 + s - resspec + (rest ? strlen(rest) : 0),char); if (!isdcl) { - strcpy(VMScmd.dsc$a_pointer,"$ MCR "); + strcpy(VMSCMD.dsc$a_pointer,"$ MCR "); + if (suggest_quote) *suggest_quote = 1; } else { - strcpy(VMScmd.dsc$a_pointer,"@"); + strcpy(VMSCMD.dsc$a_pointer,"@"); + if (suggest_quote) *suggest_quote = 1; } - strcat(VMScmd.dsc$a_pointer,resspec); - if (rest) strcat(VMScmd.dsc$a_pointer,rest); - VMScmd.dsc$w_length = strlen(VMScmd.dsc$a_pointer); - return retsts; + strcat(VMSCMD.dsc$a_pointer,resspec); + if (rest) strcat(VMSCMD.dsc$a_pointer,rest); + VMSCMD.dsc$w_length = strlen(VMSCMD.dsc$a_pointer); + return (VMSCMD.dsc$w_length > MAX_DCL_LINE_LENGTH ? CLI$_BUFOVF : retsts); } else retsts = RMS$_PRV; } } /* It's either a DCL command or we couldn't find a suitable image */ - VMScmd.dsc$w_length = strlen(cmd); - if (cmd == PL_Cmd) VMScmd.dsc$a_pointer = PL_Cmd; - else VMScmd.dsc$a_pointer = savepvn(cmd,VMScmd.dsc$w_length); + VMSCMD.dsc$w_length = strlen(cmd); + if (cmd == PL_Cmd) { + VMSCMD.dsc$a_pointer = PL_Cmd; + if (suggest_quote) *suggest_quote = 1; + } + else VMSCMD.dsc$a_pointer = savepvn(cmd,VMSCMD.dsc$w_length); + + /* check if it's a symbol (for quoting purposes) */ + if (suggest_quote && !*suggest_quote) { + int iss; + char equiv[LNM$C_NAMLENGTH]; + struct dsc$descriptor_s eqvdsc = {sizeof(equiv), DSC$K_DTYPE_T, DSC$K_CLASS_S, 0}; + eqvdsc.dsc$a_pointer = equiv; + + iss = lib$get_symbol(&VMSCMD,&eqvdsc); + if (iss&1 && (*equiv == '$' || *equiv == '@')) *suggest_quote = 1; + } if (!(retsts & 1)) { /* just hand off status values likely to be due to user error */ if (retsts == RMS$_FNF || retsts == RMS$_DNF || retsts == RMS$_PRV || @@ -3561,16 +4987,15 @@ setup_cmddsc(char *cmd, int check_img) else { _ckvmssts(retsts); } } - return (VMScmd.dsc$w_length > 255 ? CLI$_BUFOVF : retsts); + return (VMSCMD.dsc$w_length > MAX_DCL_LINE_LENGTH ? CLI$_BUFOVF : retsts); } /* end of setup_cmddsc() */ /* {{{ bool vms_do_aexec(SV *really,SV **mark,SV **sp) */ bool -vms_do_aexec(SV *really,SV **mark,SV **sp) +Perl_vms_do_aexec(pTHX_ SV *really,SV **mark,SV **sp) { - dTHX; if (sp > mark) { if (vfork_called) { /* this follows a vfork - act Unixish */ vfork_called--; @@ -3581,7 +5006,7 @@ vms_do_aexec(SV *really,SV **mark,SV **sp) else return do_aexec(really,mark,sp); } /* no vfork - act VMSish */ - return vms_do_exec(setup_argstr(really,mark,sp)); + return vms_do_exec(setup_argstr(aTHX_ really,mark,sp)); } @@ -3591,10 +5016,9 @@ vms_do_aexec(SV *really,SV **mark,SV **sp) /* {{{bool vms_do_exec(char *cmd) */ bool -vms_do_exec(char *cmd) +Perl_vms_do_exec(pTHX_ char *cmd) { - dTHX; if (vfork_called) { /* this follows a vfork - act Unixish */ vfork_called--; if (vfork_called < 0) { @@ -3609,8 +5033,8 @@ vms_do_exec(char *cmd) TAINT_ENV(); TAINT_PROPER("exec"); - if ((retsts = setup_cmddsc(cmd,1)) & 1) - retsts = lib$do_command(&VMScmd); + if ((retsts = setup_cmddsc(aTHX_ cmd,1,0)) & 1) + retsts = lib$do_command(&VMSCMD); switch (retsts) { case RMS$_FNF: case RMS$_DNF: @@ -3623,7 +5047,7 @@ vms_do_exec(char *cmd) set_errno(EACCES); break; case RMS$_SYN: set_errno(EINVAL); break; - case CLI$_BUFOVF: + case CLI$_BUFOVF: case RMS$_RTB: case CLI$_TKNOVF: case CLI$_RSLOVF: set_errno(E2BIG); break; case LIB$_INVARG: case LIB$_INVSTRDES: case SS$_ACCVIO: /* shouldn't happen */ _ckvmssts(retsts); /* fall through */ @@ -3633,7 +5057,7 @@ vms_do_exec(char *cmd) set_vaxc_errno(retsts); if (ckWARN(WARN_EXEC)) { Perl_warner(aTHX_ WARN_EXEC,"Can't exec \"%*s\": %s", - VMScmd.dsc$w_length, VMScmd.dsc$a_pointer, Strerror(errno)); + VMSCMD.dsc$w_length, VMSCMD.dsc$a_pointer, Strerror(errno)); } vms_execfree(aTHX); } @@ -3643,14 +5067,13 @@ vms_do_exec(char *cmd) } /* end of vms_do_exec() */ /*}}}*/ -unsigned long int do_spawn(char *); +unsigned long int Perl_do_spawn(pTHX_ char *); /* {{{ unsigned long int do_aspawn(void *really,void **mark,void **sp) */ unsigned long int -do_aspawn(void *really,void **mark,void **sp) +Perl_do_aspawn(pTHX_ void *really,void **mark,void **sp) { - dTHX; - if (sp > mark) return do_spawn(setup_argstr((SV *)really,(SV **)mark,(SV **)sp)); + if (sp > mark) return do_spawn(setup_argstr(aTHX_ (SV *)really,(SV **)mark,(SV **)sp)); return SS$_ABORT; } /* end of do_aspawn() */ @@ -3658,10 +5081,9 @@ do_aspawn(void *really,void **mark,void **sp) /* {{{unsigned long int do_spawn(char *cmd) */ unsigned long int -do_spawn(char *cmd) +Perl_do_spawn(pTHX_ char *cmd) { unsigned long int sts, substs, hadcmd = 1; - dTHX; TAINT_ENV(); TAINT_PROPER("spawn"); @@ -3669,8 +5091,9 @@ do_spawn(char *cmd) hadcmd = 0; sts = lib$spawn(0,0,0,0,0,0,&substs,0,0,0,0,0,0); } - else if ((sts = setup_cmddsc(cmd,0)) & 1) { - sts = lib$spawn(&VMScmd,0,0,0,0,0,&substs,0,0,0,0,0,0); + else { + (void) safe_popen(cmd, "nW", (int *)&sts); + substs = sts; } if (!(sts & 1)) { @@ -3685,7 +5108,7 @@ do_spawn(char *cmd) set_errno(EACCES); break; case RMS$_SYN: set_errno(EINVAL); break; - case CLI$_BUFOVF: + case CLI$_BUFOVF: case RMS$_RTB: case CLI$_TKNOVF: case CLI$_RSLOVF: set_errno(E2BIG); break; case LIB$_INVARG: case LIB$_INVSTRDES: case SS$_ACCVIO: /* shouldn't happen */ _ckvmssts(sts); /* fall through */ @@ -3694,9 +5117,8 @@ do_spawn(char *cmd) } set_vaxc_errno(sts); if (ckWARN(WARN_EXEC)) { - Perl_warner(aTHX_ WARN_EXEC,"Can't spawn \"%*s\": %s", - hadcmd ? VMScmd.dsc$w_length : 0, - hadcmd ? VMScmd.dsc$a_pointer : "", + Perl_warner(aTHX_ WARN_EXEC,"Can't spawn \"%s\": %s", + hadcmd ? cmd : "", Strerror(errno)); } } @@ -3706,34 +5128,106 @@ do_spawn(char *cmd) } /* end of do_spawn() */ /*}}}*/ + +static unsigned int *sockflags, sockflagsize; + +/* + * Shim fdopen to identify sockets for my_fwrite later, since the stdio + * routines found in some versions of the CRTL can't deal with sockets. + * We don't shim the other file open routines since a socket isn't + * likely to be opened by a name. + */ +/*{{{ FILE *my_fdopen(int fd, const char *mode)*/ +FILE *my_fdopen(int fd, const char *mode) +{ + FILE *fp = fdopen(fd, (char *) mode); + + if (fp) { + unsigned int fdoff = fd / sizeof(unsigned int); + struct stat sbuf; /* native stat; we don't need flex_stat */ + if (!sockflagsize || fdoff > sockflagsize) { + if (sockflags) Renew( sockflags,fdoff+2,unsigned int); + else New (1324,sockflags,fdoff+2,unsigned int); + memset(sockflags+sockflagsize,0,fdoff + 2 - sockflagsize); + sockflagsize = fdoff + 2; + } + if (fstat(fd,&sbuf) == 0 && S_ISSOCK(sbuf.st_mode)) + sockflags[fdoff] |= 1 << (fd % sizeof(unsigned int)); + } + return fp; + +} +/*}}}*/ + + +/* + * Clear the corresponding bit when the (possibly) socket stream is closed. + * There still a small hole: we miss an implicit close which might occur + * via freopen(). >> Todo + */ +/*{{{ int my_fclose(FILE *fp)*/ +int my_fclose(FILE *fp) { + if (fp) { + unsigned int fd = fileno(fp); + unsigned int fdoff = fd / sizeof(unsigned int); + + if (sockflagsize && fdoff <= sockflagsize) + sockflags[fdoff] &= ~(1 << fd % sizeof(unsigned int)); + } + return fclose(fp); +} +/*}}}*/ + + /* * A simple fwrite replacement which outputs itmsz*nitm chars without * introducing record boundaries every itmsz chars. + * We are using fputs, which depends on a terminating null. We may + * well be writing binary data, so we need to accommodate not only + * data with nulls sprinkled in the middle but also data with no null + * byte at the end. */ -/*{{{ int my_fwrite(void *src, size_t itmsz, size_t nitm, FILE *dest)*/ +/*{{{ int my_fwrite(const void *src, size_t itmsz, size_t nitm, FILE *dest)*/ int -my_fwrite(void *src, size_t itmsz, size_t nitm, FILE *dest) +my_fwrite(const void *src, size_t itmsz, size_t nitm, FILE *dest) { - register char *cp, *end; + register char *cp, *end, *cpd, *data; + register unsigned int fd = fileno(dest); + register unsigned int fdoff = fd / sizeof(unsigned int); + int retval; + int bufsize = itmsz * nitm + 1; + + if (fdoff < sockflagsize && + (sockflags[fdoff] | 1 << (fd % sizeof(unsigned int)))) { + if (write(fd, src, itmsz * nitm) == EOF) return EOF; + return nitm; + } - end = (char *)src + itmsz * nitm; + _ckvmssts_noperl(lib$get_vm(&bufsize, &data)); + memcpy( data, src, itmsz*nitm ); + data[itmsz*nitm] = '\0'; - while ((char *)src <= end) { - for (cp = src; cp <= end; cp++) if (!*cp) break; - if (fputs(src,dest) == EOF) return EOF; + end = data + itmsz * nitm; + retval = (int) nitm; /* on success return # items written */ + + cpd = data; + while (cpd <= end) { + for (cp = cpd; cp <= end; cp++) if (!*cp) break; + if (fputs(cpd,dest) == EOF) { retval = EOF; break; } if (cp < end) - if (fputc('\0',dest) == EOF) return EOF; - src = cp + 1; + if (fputc('\0',dest) == EOF) { retval = EOF; break; } + cpd = cp + 1; } - return 1; + if (data) _ckvmssts_noperl(lib$free_vm(&bufsize, &data)); + return retval; } /* end of my_fwrite() */ /*}}}*/ /*{{{ int my_flush(FILE *fp)*/ int -my_flush(FILE *fp) +Perl_my_flush(pTHX_ FILE *fp) { int res; if ((res = fflush(fp)) == 0 && fp) { @@ -3743,6 +5237,13 @@ my_flush(FILE *fp) #endif res = fsync(fileno(fp)); } +/* + * If the flush succeeded but set end-of-file, we need to clear + * the error because our caller may check ferror(). BTW, this + * probably means we just flushed an empty file. + */ + if (res == 0 && vaxc$errno == RMS$_EOF) clearerr(fp); + return res; } /*}}}*/ @@ -3807,9 +5308,8 @@ static char __pw_namecache[UAI$S_IDENT+1]; /* * This routine does most of the work extracting the user information. */ -static int fillpasswd (const char *name, struct passwd *pwd) +static int fillpasswd (pTHX_ const char *name, struct passwd *pwd) { - dTHX; static struct { unsigned char length; char pw_gecos[UAI$S_OWNER+1]; @@ -3889,15 +5389,14 @@ static int fillpasswd (const char *name, struct passwd *pwd) * Get information for a named user. */ /*{{{struct passwd *getpwnam(char *name)*/ -struct passwd *my_getpwnam(char *name) +struct passwd *Perl_my_getpwnam(pTHX_ char *name) { struct dsc$descriptor_s name_desc; union uicdef uic; unsigned long int status, sts; - dTHX; __pwdcache = __passwd_empty; - if (!fillpasswd(name, &__pwdcache)) { + if (!fillpasswd(aTHX_ name, &__pwdcache)) { /* We still may be able to determine pw_uid and pw_gid */ name_desc.dsc$w_length= strlen(name); name_desc.dsc$b_dtype= DSC$K_DTYPE_T; @@ -3928,13 +5427,12 @@ struct passwd *my_getpwnam(char *name) * Called by my_getpwent with uid=-1 to list all users. */ /*{{{struct passwd *my_getpwuid(Uid_t uid)*/ -struct passwd *my_getpwuid(Uid_t uid) +struct passwd *Perl_my_getpwuid(pTHX_ Uid_t uid) { const $DESCRIPTOR(name_desc,__pw_namecache); unsigned short lname; union uicdef uic; unsigned long int status; - dTHX; if (uid == (unsigned int) -1) { do { @@ -3974,7 +5472,7 @@ struct passwd *my_getpwuid(Uid_t uid) __pwdcache.pw_uid = uic.uic$l_uic; __pwdcache.pw_gid = uic.uic$v_group; - fillpasswd(__pw_namecache, &__pwdcache); + fillpasswd(aTHX_ __pw_namecache, &__pwdcache); return &__pwdcache; } /* end of my_getpwuid() */ @@ -3984,7 +5482,7 @@ struct passwd *my_getpwuid(Uid_t uid) * Get information for next user. */ /*{{{struct passwd *my_getpwent()*/ -struct passwd *my_getpwent() +struct passwd *Perl_my_getpwent(pTHX) { return (my_getpwuid((unsigned int) -1)); } @@ -3994,9 +5492,8 @@ struct passwd *my_getpwent() * Finish searching rights database for users. */ /*{{{void my_endpwent()*/ -void my_endpwent() +void Perl_my_endpwent(pTHX) { - dTHX; if (contxt) { _ckvmssts(sys$finish_rdb(&contxt)); contxt= 0; @@ -4122,9 +5619,6 @@ static long int utc_offset_secs; #undef localtime #undef time -#if defined(__VMS_VER) && __VMS_VER >= 70000000 && __DECC_VER >= 50200000 -# define RTL_USES_UTC 1 -#endif /* * DEC C previous to 6.0 corrupts the behavior of the /prefix @@ -4173,6 +5667,289 @@ static time_t toloc_dst(time_t utc) { (gmtime_emulation_type == 1 ? toloc_dst(secs) : \ ((secs) + utc_offset_secs)))) +#ifndef RTL_USES_UTC +/* + + ucx$tz = "EST5EDT4,M4.1.0,M10.5.0" typical + DST starts on 1st sun of april at 02:00 std time + ends on last sun of october at 02:00 dst time + see the UCX management command reference, SET CONFIG TIMEZONE + for formatting info. + + No, it's not as general as it should be, but then again, NOTHING + will handle UK times in a sensible way. +*/ + + +/* + parse the DST start/end info: + (Jddd|ddd|Mmon.nth.dow)[/hh:mm:ss] +*/ + +static char * +tz_parse_startend(char *s, struct tm *w, int *past) +{ + int dinm[] = {31,28,31,30,31,30,31,31,30,31,30,31}; + int ly, dozjd, d, m, n, hour, min, sec, j, k; + time_t g; + + if (!s) return 0; + if (!w) return 0; + if (!past) return 0; + + ly = 0; + if (w->tm_year % 4 == 0) ly = 1; + if (w->tm_year % 100 == 0) ly = 0; + if (w->tm_year+1900 % 400 == 0) ly = 1; + if (ly) dinm[1]++; + + dozjd = isdigit(*s); + if (*s == 'J' || *s == 'j' || dozjd) { + if (!dozjd && !isdigit(*++s)) return 0; + d = *s++ - '0'; + if (isdigit(*s)) { + d = d*10 + *s++ - '0'; + if (isdigit(*s)) { + d = d*10 + *s++ - '0'; + } + } + if (d == 0) return 0; + if (d > 366) return 0; + d--; + if (!dozjd && d > 58 && ly) d++; /* after 28 feb */ + g = d * 86400; + dozjd = 1; + } else if (*s == 'M' || *s == 'm') { + if (!isdigit(*++s)) return 0; + m = *s++ - '0'; + if (isdigit(*s)) m = 10*m + *s++ - '0'; + if (*s != '.') return 0; + if (!isdigit(*++s)) return 0; + n = *s++ - '0'; + if (n < 1 || n > 5) return 0; + if (*s != '.') return 0; + if (!isdigit(*++s)) return 0; + d = *s++ - '0'; + if (d > 6) return 0; + } + + if (*s == '/') { + if (!isdigit(*++s)) return 0; + hour = *s++ - '0'; + if (isdigit(*s)) hour = 10*hour + *s++ - '0'; + if (*s == ':') { + if (!isdigit(*++s)) return 0; + min = *s++ - '0'; + if (isdigit(*s)) min = 10*min + *s++ - '0'; + if (*s == ':') { + if (!isdigit(*++s)) return 0; + sec = *s++ - '0'; + if (isdigit(*s)) sec = 10*sec + *s++ - '0'; + } + } + } else { + hour = 2; + min = 0; + sec = 0; + } + + if (dozjd) { + if (w->tm_yday < d) goto before; + if (w->tm_yday > d) goto after; + } else { + if (w->tm_mon+1 < m) goto before; + if (w->tm_mon+1 > m) goto after; + + j = (42 + w->tm_wday - w->tm_mday)%7; /*dow of mday 0 */ + k = d - j; /* mday of first d */ + if (k <= 0) k += 7; + k += 7 * ((n>4?4:n)-1); /* mday of n'th d */ + if (n == 5 && k+7 <= dinm[w->tm_mon]) k += 7; + if (w->tm_mday < k) goto before; + if (w->tm_mday > k) goto after; + } + + if (w->tm_hour < hour) goto before; + if (w->tm_hour > hour) goto after; + if (w->tm_min < min) goto before; + if (w->tm_min > min) goto after; + if (w->tm_sec < sec) goto before; + goto after; + +before: + *past = 0; + return s; +after: + *past = 1; + return s; +} + + + + +/* parse the offset: (+|-)hh[:mm[:ss]] */ + +static char * +tz_parse_offset(char *s, int *offset) +{ + int hour = 0, min = 0, sec = 0; + int neg = 0; + if (!s) return 0; + if (!offset) return 0; + + if (*s == '-') {neg++; s++;} + if (*s == '+') s++; + if (!isdigit(*s)) return 0; + hour = *s++ - '0'; + if (isdigit(*s)) hour = hour*10+(*s++ - '0'); + if (hour > 24) return 0; + if (*s == ':') { + if (!isdigit(*++s)) return 0; + min = *s++ - '0'; + if (isdigit(*s)) min = min*10 + (*s++ - '0'); + if (min > 59) return 0; + if (*s == ':') { + if (!isdigit(*++s)) return 0; + sec = *s++ - '0'; + if (isdigit(*s)) sec = sec*10 + (*s++ - '0'); + if (sec > 59) return 0; + } + } + + *offset = (hour*60+min)*60 + sec; + if (neg) *offset = -*offset; + return s; +} + +/* + input time is w, whatever type of time the CRTL localtime() uses. + sets dst, the zone, and the gmtoff (seconds) + + caches the value of TZ and UCX$TZ env variables; note that + my_setenv looks for these and sets a flag if they're changed + for efficiency. + + We have to watch out for the "australian" case (dst starts in + october, ends in april)...flagged by "reverse" and checked by + scanning through the months of the previous year. + +*/ + +static int +tz_parse(pTHX_ time_t *w, int *dst, char *zone, int *gmtoff) +{ + time_t when; + struct tm *w2; + char *s,*s2; + char *dstzone, *tz, *s_start, *s_end; + int std_off, dst_off, isdst; + int y, dststart, dstend; + static char envtz[1025]; /* longer than any logical, symbol, ... */ + static char ucxtz[1025]; + static char reversed = 0; + + if (!w) return 0; + + if (tz_updated) { + tz_updated = 0; + reversed = -1; /* flag need to check */ + envtz[0] = ucxtz[0] = '\0'; + tz = my_getenv("TZ",0); + if (tz) strcpy(envtz, tz); + tz = my_getenv("UCX$TZ",0); + if (tz) strcpy(ucxtz, tz); + if (!envtz[0] && !ucxtz[0]) return 0; /* we give up */ + } + tz = envtz; + if (!*tz) tz = ucxtz; + + s = tz; + while (isalpha(*s)) s++; + s = tz_parse_offset(s, &std_off); + if (!s) return 0; + if (!*s) { /* no DST, hurray we're done! */ + isdst = 0; + goto done; + } + + dstzone = s; + while (isalpha(*s)) s++; + s2 = tz_parse_offset(s, &dst_off); + if (s2) { + s = s2; + } else { + dst_off = std_off - 3600; + } + + if (!*s) { /* default dst start/end?? */ + if (tz != ucxtz) { /* if TZ tells zone only, UCX$TZ tells rule */ + s = strchr(ucxtz,','); + } + if (!s || !*s) s = ",M4.1.0,M10.5.0"; /* we know we do dst, default rule */ + } + if (*s != ',') return 0; + + when = *w; + when = _toutc(when); /* convert to utc */ + when = when - std_off; /* convert to pseudolocal time*/ + + w2 = localtime(&when); + y = w2->tm_year; + s_start = s+1; + s = tz_parse_startend(s_start,w2,&dststart); + if (!s) return 0; + if (*s != ',') return 0; + + when = *w; + when = _toutc(when); /* convert to utc */ + when = when - dst_off; /* convert to pseudolocal time*/ + w2 = localtime(&when); + if (w2->tm_year != y) { /* spans a year, just check one time */ + when += dst_off - std_off; + w2 = localtime(&when); + } + s_end = s+1; + s = tz_parse_startend(s_end,w2,&dstend); + if (!s) return 0; + + if (reversed == -1) { /* need to check if start later than end */ + int j, ds, de; + + when = *w; + if (when < 2*365*86400) { + when += 2*365*86400; + } else { + when -= 365*86400; + } + w2 =localtime(&when); + when = when + (15 - w2->tm_yday) * 86400; /* jan 15 */ + + for (j = 0; j < 12; j++) { + w2 =localtime(&when); + (void) tz_parse_startend(s_start,w2,&ds); + (void) tz_parse_startend(s_end,w2,&de); + if (ds != de) break; + when += 30*86400; + } + reversed = 0; + if (de && !ds) reversed = 1; + } + + isdst = dststart && !dstend; + if (reversed) isdst = dststart || !dstend; + +done: + if (dst) *dst = isdst; + if (gmtoff) *gmtoff = isdst ? dst_off : std_off; + if (isdst) tz = dstzone; + if (zone) { + while(isalpha(*tz)) *zone++ = *tz++; + *zone = '\0'; + } + return 1; +} + +#endif /* !RTL_USES_UTC */ /* my_time(), my_localtime(), my_gmtime() * By default traffic in UTC time values, using CRTL gmtime() or @@ -4185,9 +5962,8 @@ static time_t toloc_dst(time_t utc) { */ /*{{{time_t my_time(time_t *timep)*/ -time_t my_time(time_t *timep) +time_t Perl_my_time(pTHX_ time_t *timep) { - dTHX; time_t when; struct tm *tm_p; @@ -4204,6 +5980,7 @@ time_t my_time(time_t *timep) gmtime_emulation_type++; if (!vmstrnenv("SYS$TIMEZONE_DIFFERENTIAL",off,0,fildev,0)) { gmtime_emulation_type++; + utc_offset_secs = 0; Perl_warn(aTHX_ "no UTC offset information; assuming local time is UTC"); } else { utc_offset_secs = atol(off); } @@ -4238,9 +6015,8 @@ time_t my_time(time_t *timep) /*{{{struct tm *my_gmtime(const time_t *timep)*/ struct tm * -my_gmtime(const time_t *timep) +Perl_my_gmtime(pTHX_ const time_t *timep) { - dTHX; char *p; time_t when; struct tm *rsltmp; @@ -4269,11 +6045,11 @@ my_gmtime(const time_t *timep) /*{{{struct tm *my_localtime(const time_t *timep)*/ struct tm * -my_localtime(const time_t *timep) +Perl_my_localtime(pTHX_ const time_t *timep) { - dTHX; - time_t when; + time_t when, whenutc; struct tm *rsltmp; + int dst, offset; if (timep == NULL) { set_errno(EINVAL); set_vaxc_errno(LIB$_INVARG); @@ -4289,15 +6065,24 @@ my_localtime(const time_t *timep) # endif /* CRTL localtime() wants UTC as input, does tz correction itself */ return localtime(&when); -# else + +# else /* !RTL_USES_UTC */ + whenutc = when; # ifdef VMSISH_TIME - if (!VMSISH_TIME) when = _toloc(when); /* Input was UTC */ + if (!VMSISH_TIME) when = _toloc(whenutc); /* input was UTC */ + if (VMSISH_TIME) whenutc = _toutc(when); /* input was truelocal */ # endif + dst = -1; +#ifndef RTL_USES_UTC + if (tz_parse(aTHX_ &when, &dst, 0, &offset)) { /* truelocal determines DST*/ + when = whenutc - offset; /* pseudolocal time*/ + } # endif /* CRTL localtime() wants local time as input, so does no tz correction */ rsltmp = localtime(&when); - if (rsltmp && gmtime_emulation_type != 1) rsltmp->tm_isdst = -1; + if (rsltmp && gmtime_emulation_type != 1) rsltmp->tm_isdst = dst; return rsltmp; +# endif } /* end of my_localtime() */ /*}}}*/ @@ -4326,9 +6111,8 @@ my_localtime(const time_t *timep) static const long int utime_baseadjust[2] = { 0x4beb4000, 0x7c9567 }; /*{{{int my_utime(char *path, struct utimbuf *utimes)*/ -int my_utime(char *file, struct utimbuf *utimes) +int Perl_my_utime(pTHX_ char *file, struct utimbuf *utimes) { - dTHX; register int i; long int bintime[2], len = 2, lowbit, unixtime, secscale = 10000000; /* seconds --> 100 ns intervals */ @@ -4449,7 +6233,7 @@ int my_utime(char *file, struct utimbuf *utimes) fnmdsc.dsc$w_length = mynam.nam$b_name + mynam.nam$b_type + mynam.nam$b_ver; memset((void *) &myfib, 0, sizeof myfib); -#ifdef __DECC +#if defined(__DECC) || defined(__DECCXX) for (i=0;i<3;i++) myfib.fib$w_fid[i] = mynam.nam$w_fid[i]; for (i=0;i<3;i++) myfib.fib$w_did[i] = mynam.nam$w_did[i]; /* This prevents the revision time of the file being reset to the current @@ -4511,14 +6295,13 @@ int my_utime(char *file, struct utimbuf *utimes) * on the first call. */ #define LOCKID_MASK 0x80000000 /* Use 0 to force device name use only */ -static mydev_t encode_dev (const char *dev) +static mydev_t encode_dev (pTHX_ const char *dev) { int i; unsigned long int f; mydev_t enc; char c; const char *q; - dTHX; if (!dev || !dev[0]) return 0; @@ -4564,7 +6347,6 @@ static int is_null_device(name) const char *name; { - dTHX; /* The VMS null device is named "_NLA0:", usually abbreviated as "NL:". The underscore prefix, controller letter, and unit number are independently optional; for our purposes, the colon punctuation @@ -4586,6 +6368,7 @@ is_null_device(name) bool Perl_cando(pTHX_ Mode_t bit, Uid_t effective, Stat_t *statbufp) { + char fname_phdev[NAM$C_MAXRSS+1]; if (statbufp == &PL_statcache) return cando_by_name(bit,effective,namecache); else { char fname[NAM$C_MAXRSS+1]; @@ -4604,7 +6387,15 @@ Perl_cando(pTHX_ Mode_t bit, Uid_t effective, Stat_t *statbufp) &namdsc,&namdsc.dsc$w_length,0,0); if (retsts & 1) { fname[namdsc.dsc$w_length] = '\0'; - return cando_by_name(bit,effective,fname); +/* + * lib$fid_to_name returns DVI$_LOGVOLNAM as the device part of the name, + * but if someone has redefined that logical, Perl gets very lost. Since + * we have the physical device name from the stat buffer, just paste it on. + */ + strcpy( fname_phdev, statbufp->st_devnam ); + strcat( fname_phdev, strrchr(fname, ':') ); + + return cando_by_name(bit,effective,fname_phdev); } else if (retsts == SS$_NOSUCHDEV || retsts == SS$_NOSUCHFILE) { Perl_warn(aTHX_ "Can't get filespec - stale stat buffer?\n"); @@ -4619,7 +6410,7 @@ Perl_cando(pTHX_ Mode_t bit, Uid_t effective, Stat_t *statbufp) /*{{{I32 cando_by_name(I32 bit, Uid_t effective, char *fname)*/ I32 -cando_by_name(I32 bit, Uid_t effective, char *fname) +Perl_cando_by_name(pTHX_ I32 bit, Uid_t effective, char *fname) { static char usrname[L_cuserid]; static struct dsc$descriptor_s usrdsc = @@ -4627,7 +6418,6 @@ cando_by_name(I32 bit, Uid_t effective, char *fname) char vmsname[NAM$C_MAXRSS+1], fileified[NAM$C_MAXRSS+1]; unsigned long int objtyp = ACL$C_FILE, access, retsts, privused, iosb[2]; unsigned short int retlen; - dTHX; struct dsc$descriptor_s namdsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0}; union prvdef curprv; struct itmlst_3 armlst[3] = {{sizeof access, CHP$_ACCESS, &access, &retlen}, @@ -4673,7 +6463,7 @@ cando_by_name(I32 bit, Uid_t effective, char *fname) retsts = sys$check_access(&objtyp,&namdsc,&usrdsc,armlst); if (retsts == SS$_NOPRIV || retsts == SS$_NOSUCHOBJECT || retsts == SS$_INVFILFOROP || retsts == RMS$_FNF || retsts == RMS$_SYN || - retsts == RMS$_DIR || retsts == RMS$_DEV) { + retsts == RMS$_DIR || retsts == RMS$_DEV || retsts == RMS$_DNF) { set_vaxc_errno(retsts); if (retsts == SS$_NOPRIV) set_errno(EACCES); else if (retsts == SS$_INVFILFOROP) set_errno(EINVAL); @@ -4696,12 +6486,6 @@ cando_by_name(I32 bit, Uid_t effective, char *fname) if (retsts == SS$_ACCONFLICT) { return TRUE; } - -#if defined(__ALPHA) && defined(__VMS_VER) && __VMS_VER == 70100022 && defined(__DECC_VER) && __DECC_VER == 6009001 - /* XXX Hideous kluge to accomodate error in specific version of RTL; - we hope it'll be buried soon */ - if (retsts == 114762) return TRUE; -#endif _ckvmssts(retsts); return FALSE; /* Should never get here */ @@ -4712,12 +6496,11 @@ cando_by_name(I32 bit, Uid_t effective, char *fname) /*{{{ int flex_fstat(int fd, Stat_t *statbuf)*/ int -flex_fstat(int fd, Stat_t *statbufp) +Perl_flex_fstat(pTHX_ int fd, Stat_t *statbufp) { - dTHX; if (!fstat(fd,(stat_t *) statbufp)) { if (statbufp == (Stat_t *) &PL_statcache) *namecache == '\0'; - statbufp->st_dev = encode_dev(statbufp->st_devnam); + statbufp->st_dev = encode_dev(aTHX_ statbufp->st_devnam); # ifdef RTL_USES_UTC # ifdef VMSISH_TIME if (VMSISH_TIME) { @@ -4746,19 +6529,19 @@ flex_fstat(int fd, Stat_t *statbufp) /*{{{ int flex_stat(const char *fspec, Stat_t *statbufp)*/ int -flex_stat(const char *fspec, Stat_t *statbufp) +Perl_flex_stat(pTHX_ const char *fspec, Stat_t *statbufp) { - dTHX; char fileified[NAM$C_MAXRSS+1]; char temp_fspec[NAM$C_MAXRSS+300]; int retval = -1; + if (!fspec) return retval; strcpy(temp_fspec, fspec); if (statbufp == (Stat_t *) &PL_statcache) do_tovmsspec(temp_fspec,namecache,0); if (is_null_device(temp_fspec)) { /* Fake a stat() for the null device */ memset(statbufp,0,sizeof *statbufp); - statbufp->st_dev = encode_dev("_NLA0:"); + statbufp->st_dev = encode_dev(aTHX_ "_NLA0:"); statbufp->st_mode = S_IFBLK | S_IREAD | S_IWRITE | S_IEXEC; statbufp->st_uid = 0x00010001; statbufp->st_gid = 0x0001; @@ -4782,7 +6565,7 @@ flex_stat(const char *fspec, Stat_t *statbufp) } if (retval) retval = stat(temp_fspec,(stat_t *) statbufp); if (!retval) { - statbufp->st_dev = encode_dev(statbufp->st_devnam); + statbufp->st_dev = encode_dev(aTHX_ statbufp->st_devnam); # ifdef RTL_USES_UTC # ifdef VMSISH_TIME if (VMSISH_TIME) { @@ -5135,7 +6918,7 @@ candelete_fromperl(pTHX_ CV *cv) mysv = SvROK(ST(0)) ? SvRV(ST(0)) : ST(0); if (SvTYPE(mysv) == SVt_PVGV) { - if (!(io = GvIOp(mysv)) || !fgetname(IoIFP(io),fspec,1)) { + if (!(io = GvIOp(mysv)) || !PerlIO_getname(IoIFP(io),fspec)) { set_errno(EINVAL); set_vaxc_errno(LIB$_INVARG); ST(0) = &PL_sv_no; XSRETURN(1); @@ -5172,7 +6955,7 @@ rmscopy_fromperl(pTHX_ CV *cv) mysv = SvROK(ST(0)) ? SvRV(ST(0)) : ST(0); if (SvTYPE(mysv) == SVt_PVGV) { - if (!(io = GvIOp(mysv)) || !fgetname(IoIFP(io),inspec,1)) { + if (!(io = GvIOp(mysv)) || !PerlIO_getname(IoIFP(io),inspec)) { set_errno(EINVAL); set_vaxc_errno(LIB$_INVARG); ST(0) = &PL_sv_no; XSRETURN(1); @@ -5188,7 +6971,7 @@ rmscopy_fromperl(pTHX_ CV *cv) } mysv = SvROK(ST(1)) ? SvRV(ST(1)) : ST(1); if (SvTYPE(mysv) == SVt_PVGV) { - if (!(io = GvIOp(mysv)) || !fgetname(IoIFP(io),outspec,1)) { + if (!(io = GvIOp(mysv)) || !PerlIO_getname(IoIFP(io),outspec)) { set_errno(EINVAL); set_vaxc_errno(LIB$_INVARG); ST(0) = &PL_sv_no; XSRETURN(1); @@ -5210,7 +6993,7 @@ rmscopy_fromperl(pTHX_ CV *cv) void -mod2fname(CV *cv) +mod2fname(pTHX_ CV *cv) { dXSARGS; char ultimate_name[NAM$C_MAXRSS+1], work_name[NAM$C_MAXRSS*8 + 1], @@ -5285,10 +7068,51 @@ mod2fname(CV *cv) } void +hushexit_fromperl(pTHX_ CV *cv) +{ + dXSARGS; + + if (items > 0) { + VMSISH_HUSHED = SvTRUE(ST(0)); + } + ST(0) = boolSV(VMSISH_HUSHED); + XSRETURN(1); +} + +void +Perl_sys_intern_dup(pTHX_ struct interp_intern *src, + struct interp_intern *dst) +{ + memcpy(dst,src,sizeof(struct interp_intern)); +} + +void +Perl_sys_intern_clear(pTHX) +{ +} + +void +Perl_sys_intern_init(pTHX) +{ + int ix = RAND_MAX; + float x; + + VMSISH_HUSHED = 0; + + x = (float)ix; + MY_INV_RAND_MAX = 1./x; + + VMSCMD.dsc$a_pointer = NULL; + VMSCMD.dsc$w_length = 0; + VMSCMD.dsc$b_dtype = DSC$K_DTYPE_T; + VMSCMD.dsc$b_class = DSC$K_CLASS_S; +} + +void init_os_extras() { - char* file = __FILE__; dTHX; + char* file = __FILE__; char temp_buff[512]; if (my_trnlnm("DECC$DISABLE_TO_VMS_LOGNAME_TRANSLATION", temp_buff, 0)) { no_translate_barewords = TRUE; @@ -5306,6 +7130,9 @@ init_os_extras() newXSproto("VMS::Filespec::candelete",candelete_fromperl,file,"$"); newXSproto("DynaLoader::mod2fname", mod2fname, file, "$"); newXS("File::Copy::rmscopy",rmscopy_fromperl,file); + newXSproto("vmsish::hushed",hushexit_fromperl,file,";$"); + + store_pipelocs(aTHX); return; }