X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=vms%2Fvms.c;h=e7687ac7aec387b593dc6c69732b02afcf5e5cfe;hb=9137345a080bfc646c2f9440cdb7bd90b8b37428;hp=40348e00564b9e851457d4b7c1eea388b4398e1c;hpb=4b19af017623bfa3bb72bb164598a517f586e0d3;p=p5sagit%2Fp5-mst-13.2.git diff --git a/vms/vms.c b/vms/vms.c index 40348e0..e7687ac 100644 --- a/vms/vms.c +++ b/vms/vms.c @@ -1,9 +1,12 @@ /* 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 @@ -14,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +50,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 +64,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 +102,18 @@ 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 + +/* OpenVMS User's Guide says at least 9 iterative translations will be performed, + * depending on the facility. SHOW LOGICAL does 10, so we'll imitate that for + * the Perl facility. + */ +#define PERL_LNM_MAX_ITER 10 + +#define MAX_DCL_SYMBOL 255 /* well, what *we* can set, at least*/ +#define MAX_DCL_LINE_LENGTH (4*MAX_DCL_SYMBOL-4) + static char *__mystrtolower(char *str) { if (str) for (; *str; ++str) *str= tolower(*str); @@ -108,17 +133,49 @@ static bool will_taint = FALSE; /* tainting active, but no PL_curinterp yet */ /* munching */ 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}; +#ifndef RTL_USES_UTC +static int tz_updated = 1; +#endif + +/* my_maxidx + * Routine to retrieve the maximum equivalence index for an input + * logical name. Some calls to this routine have no knowledge if + * the variable is a logical or not. So on error we return a max + * index of zero. + */ +/*{{{int my_maxidx(char *lnm) */ +static int +my_maxidx(char *lnm) +{ + int status; + int midx; + int attr = LNM$M_CASE_BLIND; + struct dsc$descriptor lnmdsc; + struct itmlst_3 itlst[2] = {{sizeof(midx), LNM$_MAX_INDEX, &midx, 0}, + {0, 0, 0, 0}}; + + lnmdsc.dsc$w_length = strlen(lnm); + lnmdsc.dsc$b_dtype = DSC$K_DTYPE_T; + lnmdsc.dsc$b_class = DSC$K_CLASS_S; + lnmdsc.dsc$a_pointer = lnm; + + status = sys$trnlnm(&attr, &fildevdsc, &lnmdsc, 0, itlst); + if ((status & 1) == 0) + midx = 0; + + return (midx); +} +/*}}}*/ /*{{{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; unsigned short int eqvlen, curtab, ivlnm = 0, ivsym = 0, ivenv = 0, secure; unsigned long int retsts, attr = LNM$M_CASE_BLIND; + int midx; unsigned char acmode; struct dsc$descriptor_s lnmdsc = {0,DSC$K_DTYPE_T,DSC$K_CLASS_S,0}, tmpdsc = {6,DSC$K_DTYPE_T,DSC$K_CLASS_S,0}; @@ -126,20 +183,16 @@ 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) - /* 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 defined(PERL_IMPLICIT_CONTEXT) + pTHX = NULL; if (PL_curinterp) { - thr = PL_threadnum? THR : (struct perl_thread*)SvPVX(PL_thrsv); + aTHX = PERL_GET_INTERP; } else { - thr = NULL; + aTHX = NULL; } #endif - if (!lnm || !eqv || idx > LNM$_MAX_INDEX) { + if (!lnm || !eqv || ((idx != 0) && ((idx-1) > PERL_LNM_MAX_ALLOWED_INDEX))) { set_errno(EINVAL); set_vaxc_errno(SS$_BADPARAM); return 0; } for (cp1 = (char *)lnm, cp2 = uplnm; *cp1; cp1++, cp2++) { @@ -169,6 +222,7 @@ Perl_vmstrnenv(pTHX_ const char *lnm, char *eqv, unsigned long int idx, retsts = SS$_NOLOGNAM; for (i = 0; environ[i]; i++) { if ((eq = strchr(environ[i],'=')) && + lnmdsc.dsc$w_length == (eq - environ[i]) && !strncmp(environ[i],uplnm,eq - environ[i])) { eq++; for (eqvlen = 0; eq[eqvlen]; eqvlen++) eqv[eqvlen] = eq[eqvlen]; @@ -196,18 +250,9 @@ 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 (thr && PL_curcop) { -#endif if (ckWARN(WARN_MISC)) { - Perl_warner(aTHX_ WARN_MISC,"Value of CLI symbol \"%s\" too long",lnm); + Perl_warner(aTHX_ packWARN(WARN_MISC),"Value of CLI symbol \"%s\" too long",lnm); } -#if defined(USE_THREADS) - } else { - Perl_warner(aTHX_ WARN_MISC,"Value of CLI symbol \"%s\" too long",lnm); - } -#endif - } strncpy(eqv,eqvdsc.dsc$a_pointer,eqvlen); } @@ -218,22 +263,40 @@ Perl_vmstrnenv(pTHX_ const char *lnm, char *eqv, unsigned long int idx, } } else if (!ivlnm) { - retsts = sys$trnlnm(&attr,tabvec[curtab],&lnmdsc,&acmode,lnmlst); - if (retsts == SS$_IVLOGNAM) { ivlnm = 1; continue; } - if (retsts == SS$_NOLOGNAM) continue; - /* PPFs have a prefix */ - if ( + if ( (idx == 0) && (flags & PERL__TRNENV_JOIN_SEARCHLIST) ) { + midx = my_maxidx((char *) lnm); + for (idx = 0, cp1 = eqv; idx <= midx; idx++) { + lnmlst[1].bufadr = cp1; + eqvlen = 0; + retsts = sys$trnlnm(&attr,tabvec[curtab],&lnmdsc,&acmode,lnmlst); + if (retsts == SS$_IVLOGNAM) { ivlnm = 1; break; } + if (retsts == SS$_NOLOGNAM) break; + /* PPFs have a prefix */ + if ( #if INTSIZE == 4 - *((int *)uplnm) == *((int *)"SYS$") && + *((int *)uplnm) == *((int *)"SYS$") && #endif - eqvlen >= 4 && eqv[0] == 0x1b && eqv[1] == 0x00 && - ( (uplnm[4] == 'O' && !strcmp(uplnm,"SYS$OUTPUT")) || - (uplnm[4] == 'I' && !strcmp(uplnm,"SYS$INPUT")) || - (uplnm[4] == 'E' && !strcmp(uplnm,"SYS$ERROR")) || - (uplnm[4] == 'C' && !strcmp(uplnm,"SYS$COMMAND")) ) ) { - memcpy(eqv,eqv+4,eqvlen-4); - eqvlen -= 4; + eqvlen >= 4 && eqv[0] == 0x1b && eqv[1] == 0x00 && + ( (uplnm[4] == 'O' && !strcmp(uplnm,"SYS$OUTPUT")) || + (uplnm[4] == 'I' && !strcmp(uplnm,"SYS$INPUT")) || + (uplnm[4] == 'E' && !strcmp(uplnm,"SYS$ERROR")) || + (uplnm[4] == 'C' && !strcmp(uplnm,"SYS$COMMAND")) ) ) { + memcpy(eqv,eqv+4,eqvlen-4); + eqvlen -= 4; + } + cp1 += eqvlen; + *cp1 = '\0'; + } + if ((retsts == SS$_IVLOGNAM) || + (retsts == SS$_NOLOGNAM)) { continue; } + } + else { + retsts = sys$trnlnm(&attr,tabvec[curtab],&lnmdsc,&acmode,lnmlst); + if (retsts == SS$_IVLOGNAM) { ivlnm = 1; continue; } + if (retsts == SS$_NOLOGNAM) continue; + eqv[eqvlen] = '\0'; } + eqvlen = strlen(eqv); break; } } @@ -274,43 +337,81 @@ int Perl_my_trnlnm(pTHX_ const char *lnm, char *eqv, unsigned long int idx) char * Perl_my_getenv(pTHX_ const char *lnm, bool sys) { - static char __my_getenv_eqv[LNM$C_NAMLENGTH+1]; + static char *__my_getenv_eqv = NULL; char uplnm[LNM$C_NAMLENGTH+1], *cp1, *cp2, *eqv; unsigned long int idx = 0; - int trnsuccess; + int trnsuccess, success, secure, saverr, savvmserr; + int midx, flags; SV *tmpsv; + midx = my_maxidx((char *) lnm) + 1; + if (PL_curinterp) { /* Perl interpreter running -- may be threaded */ /* Set up a temporary buffer for the return value; Perl will * clean it up at the next statement transition */ - tmpsv = sv_2mortal(newSVpv("",LNM$C_NAMLENGTH+1)); + tmpsv = sv_2mortal(newSVpv("",(LNM$C_NAMLENGTH*midx)+1)); if (!tmpsv) return NULL; eqv = SvPVX(tmpsv); } - else eqv = __my_getenv_eqv; /* Assume no interpreter ==> single thread */ + else { + /* Assume no interpreter ==> single thread */ + if (__my_getenv_eqv != NULL) { + Renew(__my_getenv_eqv,LNM$C_NAMLENGTH*midx+1,char); + } + else { + New(1380,__my_getenv_eqv,LNM$C_NAMLENGTH*midx+1,char); + } + eqv = __my_getenv_eqv; + } + for (cp1 = (char *) lnm, cp2 = eqv; *cp1; cp1++,cp2++) *cp2 = _toupper(*cp1); if (cp1 - lnm == 7 && !strncmp(eqv,"DEFAULT",7)) { getcwd(eqv,LNM$C_NAMLENGTH); return eqv; } else { + /* Impose security constraints only if tainting */ + if (sys) { + /* Impose security constraints only if tainting */ + secure = PL_curinterp ? PL_tainting : will_taint; + saverr = errno; savvmserr = vaxc$errno; + } + else { + secure = 0; + } + + flags = +#ifdef SECURE_INTERNAL_GETENV + secure ? PERL__TRNENV_SECURE : 0 +#else + 0 +#endif + ; + + /* For the getenv interface we combine all the equivalence names + * of a search list logical into one value to acquire a maximum + * value length of 255*128 (assuming %ENV is using logicals). + */ + flags |= PERL__TRNENV_JOIN_SEARCHLIST; + + /* If the name contains a semicolon-delimited index, parse it + * off and make sure we only retrieve the equivalence name for + * that index. */ if ((cp2 = strchr(lnm,';')) != NULL) { strcpy(uplnm,lnm); uplnm[cp2-lnm] = '\0'; idx = strtoul(cp2+1,NULL,0); lnm = uplnm; + flags &= ~PERL__TRNENV_JOIN_SEARCHLIST; } - /* Impose security constraints only if tainting */ - if (sys) sys = PL_curinterp ? PL_tainting : will_taint; - if (vmstrnenv(lnm,eqv,idx, - sys ? fildev : NULL, -#ifdef SECURE_INTERNAL_GETENV - sys ? PERL__TRNENV_SECURE : 0 -#else - 0 -#endif - )) return eqv; - else return Nullch; + + success = vmstrnenv(lnm,eqv,idx,secure ? fildev : NULL,flags); + + /* 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,22 +420,35 @@ 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 midx, flags; + static char *__my_getenv_len_eqv = NULL; + int secure, saverr, savvmserr; SV *tmpsv; + midx = my_maxidx((char *) lnm) + 1; + if (PL_curinterp) { /* Perl interpreter running -- may be threaded */ /* Set up a temporary buffer for the return value; Perl will * clean it up at the next statement transition */ - tmpsv = sv_2mortal(newSVpv("",LNM$C_NAMLENGTH+1)); + tmpsv = sv_2mortal(newSVpv("",(LNM$C_NAMLENGTH*midx)+1)); if (!tmpsv) return NULL; buf = SvPVX(tmpsv); } - else buf = __my_getenv_len_eqv; /* Assume no interpreter ==> single thread */ + else { + /* Assume no interpreter ==> single thread */ + if (__my_getenv_len_eqv != NULL) { + Renew(__my_getenv_len_eqv,LNM$C_NAMLENGTH*midx+1,char); + } + else { + New(1381,__my_getenv_len_eqv,LNM$C_NAMLENGTH*midx+1,char); + } + buf = __my_getenv_len_eqv; + } + for (cp1 = (char *)lnm, cp2 = buf; *cp1; cp1++,cp2++) *cp2 = _toupper(*cp1); if (cp1 - lnm == 7 && !strncmp(buf,"DEFAULT",7)) { getcwd(buf,LNM$C_NAMLENGTH); @@ -342,31 +456,46 @@ my_getenv_len(const char *lnm, unsigned long *len, bool sys) return buf; } else { + if (sys) { + /* Impose security constraints only if tainting */ + secure = PL_curinterp ? PL_tainting : will_taint; + saverr = errno; savvmserr = vaxc$errno; + } + else { + secure = 0; + } + + flags = +#ifdef SECURE_INTERNAL_GETENV + secure ? PERL__TRNENV_SECURE : 0 +#else + 0 +#endif + ; + + flags |= PERL__TRNENV_JOIN_SEARCHLIST; + if ((cp2 = strchr(lnm,';')) != NULL) { strcpy(buf,lnm); buf[cp2-lnm] = '\0'; idx = strtoul(cp2+1,NULL,0); lnm = buf; + flags &= ~PERL__TRNENV_JOIN_SEARCHLIST; } - /* Impose security constraints only if tainting */ - if (sys) sys = PL_curinterp ? PL_tainting : will_taint; - if ((*len = vmstrnenv(lnm,buf,idx, - sys ? fildev : NULL, -#ifdef SECURE_INTERNAL_GETENV - sys ? PERL__TRNENV_SECURE : 0 -#else - 0 -#endif - ))) - return buf; - else - return Nullch; + + *len = vmstrnenv(lnm,buf,idx,secure ? fildev : NULL,flags); + + /* 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 +506,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 +523,26 @@ prime_env_iter(void) $DESCRIPTOR(clidsc,"DCL"); $DESCRIPTOR(clitabdsc,"DCLTABLES"); $DESCRIPTOR(crtlenv,"CRTL_ENV"); $DESCRIPTOR(clisym,"CLISYM"); $DESCRIPTOR(local,"_LOCAL"); $DESCRIPTOR(mbxdsc,mbxnam); -#if defined(USE_THREADS) || defined(USE_ITHREADS) +#if defined(PERL_IMPLICIT_CONTEXT) + pTHX; +#endif +#if 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 (PL_curinterp) { + aTHX = PERL_GET_INTERP; + } else { + aTHX = NULL; + } +#endif + if (primed || !PL_envgv) return; MUTEX_LOCK(&primenv_mutex); if (primed) { MUTEX_UNLOCK(&primenv_mutex); return; } @@ -426,12 +570,13 @@ prime_env_iter(void) for (j = 0; environ[j]; j++) { if (!(start = strchr(environ[j],'='))) { if (ckWARN(WARN_INTERNAL)) - Perl_warner(aTHX_ WARN_INTERNAL,"Ill-formed CRTL environ value \"%s\"\n",environ[j]); + Perl_warner(aTHX_ packWARN(WARN_INTERNAL),"Ill-formed CRTL environ value \"%s\"\n",environ[j]); } 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; @@ -495,7 +640,7 @@ prime_env_iter(void) continue; } if (sts == SS$_BUFFEROVF && ckWARN(WARN_INTERNAL)) - Perl_warner(aTHX_ WARN_INTERNAL,"Buffer overflow in prime_env_iter: %s",buf); + Perl_warner(aTHX_ packWARN(WARN_INTERNAL),"Buffer overflow in prime_env_iter: %s",buf); for (cp1 = buf; *cp1 && isspace(*cp1); cp1++) ; if (*cp1 == '(' || /* Logical name table name */ @@ -516,11 +661,27 @@ prime_env_iter(void) cp1--; /* stop on last non-space char */ } if ((!keylen || (cp1 - cp2 < -1)) && ckWARN(WARN_INTERNAL)) { - Perl_warner(aTHX_ WARN_INTERNAL,"Ill-formed message in prime_env_iter: |%s|",buf); + Perl_warner(aTHX_ packWARN(WARN_INTERNAL),"Ill-formed message in prime_env_iter: |%s|",buf); continue; } PERL_HASH(hash,key,keylen); - hv_store(envhv,key,keylen,newSVpvn(cp2,cp1 - cp2 + 1),hash); + + if (cp1 == cp2 && *cp2 == '.') { + /* A single dot usually means an unprintable character, such as a null + * to indicate a zero-length value. Get the actual value to make sure. + */ + char lnm[LNM$C_NAMLENGTH+1]; + char eqv[LNM$C_NAMLENGTH+1]; + strncpy(lnm, key, keylen); + int trnlen = vmstrnenv(lnm, eqv, 0, fildev, 0); + sv = newSVpvn(eqv, strlen(eqv)); + } + else { + 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 +691,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); } } } @@ -545,7 +708,7 @@ prime_env_iter(void) /*}}}*/ -/*{{{ int vmssetenv(char *lnm, char *eqv)*/ +/*{{{ int vmssetenv(const char *lnm, const char *eqv)*/ /* Define or delete an element in the same "environment" as * vmstrnenv(). If an element is to be deleted, it's removed from * the first place it's found. If it's to be set, it's set in the @@ -553,19 +716,25 @@ 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_ const char *lnm, const char *eqv, struct dsc$descriptor_s **tabvec) { - char uplnm[LNM$C_NAMLENGTH], *cp1, *cp2; + char uplnm[LNM$C_NAMLENGTH], *cp1, *cp2, *c; unsigned short int curtab, ivlnm = 0, ivsym = 0, ivenv = 0; + int nseg = 0, j; unsigned long int retsts, usermode = PSL$C_USER; + struct itmlst_3 *ile, *ilist; struct dsc$descriptor_s lnmdsc = {0,DSC$K_DTYPE_T,DSC$K_CLASS_S,uplnm}, eqvdsc = {0,DSC$K_DTYPE_T,DSC$K_CLASS_S,0}, 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++) { + if (!lnm) { + set_errno(EINVAL); set_vaxc_errno(SS$_IVLOGNAM); + return SS$_IVLOGNAM; + } + + for (cp1 = (char *)lnm, cp2 = uplnm; *cp1; cp1++, cp2++) { *cp2 = _toupper(*cp1); if (cp1 - lnm > LNM$C_NAMLENGTH) { set_errno(EINVAL); set_vaxc_errno(SS$_IVLOGNAM); @@ -579,17 +748,18 @@ vmssetenv(char *lnm, char *eqv, struct dsc$descriptor_s **tabvec) for (curtab = 0; tabvec[curtab]; curtab++) { if (!ivenv && !str$case_blind_compare(tabvec[curtab],&crtlenv)) { int i; - for (i = 0; environ[i]; i++) { /* Iff it's an environ elt, reset */ + for (i = 0; environ[i]; i++) { /* If it's an environ elt, reset */ if ((cp1 = strchr(environ[i],'=')) && + lnmdsc.dsc$w_length == (cp1 - 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; #else if (ckWARN(WARN_INTERNAL)) - Perl_warner(aTHX_ WARN_INTERNAL,"This Perl can't reset CRTL environ elements (%s)",lnm); + Perl_warner(aTHX_ packWARN(WARN_INTERNAL),"This Perl can't reset CRTL environ elements (%s)",lnm); ivenv = 1; retsts = SS$_NOSUCHPGM; break; } @@ -624,12 +794,12 @@ vmssetenv(char *lnm, char *eqv, struct dsc$descriptor_s **tabvec) return setenv(lnm,eqv,1) ? vaxc$errno : 0; #else if (ckWARN(WARN_INTERNAL)) - Perl_warner(aTHX_ WARN_INTERNAL,"This Perl can't set CRTL environ elements (%s=%s)",lnm,eqv); + Perl_warner(aTHX_ packWARN(WARN_INTERNAL),"This Perl can't set CRTL environ elements (%s=%s)",lnm,eqv); retsts = SS$_NOSUCHPGM; #endif } else { - eqvdsc.dsc$a_pointer = eqv; + eqvdsc.dsc$a_pointer = (char *)eqv; eqvdsc.dsc$w_length = strlen(eqv); if ((tmpdsc.dsc$a_pointer = tabvec[0]->dsc$a_pointer) && !str$case_blind_compare(&tmpdsc,&clisym)) { @@ -644,12 +814,42 @@ vmssetenv(char *lnm, char *eqv, struct dsc$descriptor_s **tabvec) else { if (!*eqv) eqvdsc.dsc$w_length = 1; if (eqvdsc.dsc$w_length > LNM$C_NAMLENGTH) { - eqvdsc.dsc$w_length = LNM$C_NAMLENGTH; - if (ckWARN(WARN_MISC)) { - Perl_warner(aTHX_ WARN_MISC,"Value of logical \"%s\" too long. Truncating to %i bytes",lnm, LNM$C_NAMLENGTH); + + nseg = (eqvdsc.dsc$w_length + LNM$C_NAMLENGTH - 1) / LNM$C_NAMLENGTH; + if (nseg > PERL_LNM_MAX_ALLOWED_INDEX + 1) { + Perl_warner(aTHX_ packWARN(WARN_MISC),"Value of logical \"%s\" too long. Truncating to %i bytes", + lnm, LNM$C_NAMLENGTH * (PERL_LNM_MAX_ALLOWED_INDEX+1)); + eqvdsc.dsc$w_length = LNM$C_NAMLENGTH * (PERL_LNM_MAX_ALLOWED_INDEX+1); + nseg = PERL_LNM_MAX_ALLOWED_INDEX + 1; + } + + New(1382,ilist,nseg+1,struct itmlst_3); + ile = ilist; + if (!ile) { + set_errno(ENOMEM); set_vaxc_errno(SS$_INSFMEM); + return SS$_INSFMEM; } + memset(ilist, 0, (sizeof(struct itmlst_3) * (nseg+1))); + + for (j = 0, c = eqvdsc.dsc$a_pointer; j < nseg; j++, ile++, c += LNM$C_NAMLENGTH) { + ile->itmcode = LNM$_STRING; + ile->bufadr = c; + if ((j+1) == nseg) { + ile->buflen = strlen(c); + /* in case we are truncating one that's too long */ + if (ile->buflen > LNM$C_NAMLENGTH) ile->buflen = LNM$C_NAMLENGTH; + } + else { + ile->buflen = LNM$C_NAMLENGTH; + } + } + + retsts = lib$set_logical(&lnmdsc,0,tabvec[0],0,ilist); + Safefree (ilist); + } + else { + retsts = lib$set_logical(&lnmdsc,&eqvdsc,tabvec[0],0,0); } - retsts = lib$set_logical(&lnmdsc,&eqvdsc,tabvec[0],0,0); } } } @@ -686,24 +886,61 @@ vmssetenv(char *lnm, char *eqv, struct dsc$descriptor_s **tabvec) } /* end of vmssetenv() */ /*}}}*/ -/*{{{ void my_setenv(char *lnm, char *eqv)*/ +/*{{{ void my_setenv(const char *lnm, const char *eqv)*/ /* This has to be a function since there's a prototype for it in proto.h */ void -Perl_my_setenv(pTHX_ char *lnm,char *eqv) +Perl_my_setenv(pTHX_ const char *lnm, const 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 +954,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 +1034,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 +1135,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 +1158,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; @@ -953,7 +1187,6 @@ my_tmpfile(void) { FILE *fp; char *cp; - dTHX; if ((fp = tmpfile())) return fp; @@ -968,22 +1201,178 @@ my_tmpfile(void) /*}}}*/ +#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 + +#ifdef KILL_BY_SIGPRC +#include + +/* We implement our own kill() using the undocumented system service + sys$sigprc for one of two reasons: + + 1.) If the kill() in an older CRTL uses sys$forcex, causing the + target process to do a sys$exit, which usually can't be handled + gracefully...certainly not by Perl and the %SIG{} mechanism. + + 2.) If the kill() in the CRTL can't be called from a signal + handler without disappearing into the ether, i.e., the signal + it purportedly sends is never trapped. Still true as of VMS 7.3. + + sys$sigprc has the same parameters as sys$forcex, but throws an exception + in the target process rather than calling sys$exit. + + Note that distinguishing SIGSEGV from SIGBUS requires an extra arg + on the ACCVIO condition, which sys$sigprc (and sys$forcex) don't + provide. On VMS 7.0+ this is taken care of by doing sys$sigprc + with condition codes C$_SIG0+nsig*8, catching the exception on the + target process and resignaling with appropriate arguments. + + But we don't have that VMS 7.0+ exception handler, so if you + Perl_my_kill(.., SIGSEGV) it will show up as a SIGBUS. Oh well. + + Also note that SIGTERM is listed in the docs as being "unimplemented", + yet always seems to be signaled with a VMS condition code of 4 (and + correctly handled for that code). So we hardwire it in. + + Unlike the VMS 7.0+ CRTL kill() function, we actually check the signal + number to see if it's valid. So Perl_my_kill(pid,0) returns -1 rather + than signalling with an unrecognized (and unhandled by CRTL) code. +*/ + +#define _MY_SIG_MAX 17 + +unsigned int +Perl_sig_to_vmscondition(int sig) +{ + static unsigned int sig_code[_MY_SIG_MAX+1] = + { + 0, /* 0 ZERO */ + SS$_HANGUP, /* 1 SIGHUP */ + SS$_CONTROLC, /* 2 SIGINT */ + SS$_CONTROLY, /* 3 SIGQUIT */ + SS$_RADRMOD, /* 4 SIGILL */ + SS$_BREAK, /* 5 SIGTRAP */ + SS$_OPCCUS, /* 6 SIGABRT */ + SS$_COMPAT, /* 7 SIGEMT */ +#ifdef __VAX + SS$_FLTOVF, /* 8 SIGFPE VAX */ +#else + SS$_HPARITH, /* 8 SIGFPE AXP */ +#endif + SS$_ABORT, /* 9 SIGKILL */ + SS$_ACCVIO, /* 10 SIGBUS */ + SS$_ACCVIO, /* 11 SIGSEGV */ + SS$_BADPARAM, /* 12 SIGSYS */ + SS$_NOMBX, /* 13 SIGPIPE */ + SS$_ASTFLT, /* 14 SIGALRM */ + 4, /* 15 SIGTERM */ + 0, /* 16 SIGUSR1 */ + 0 /* 17 SIGUSR2 */ + }; + +#if __VMS_VER >= 60200000 + static int initted = 0; + if (!initted) { + initted = 1; + sig_code[16] = C$_SIGUSR1; + sig_code[17] = C$_SIGUSR2; + } +#endif + + if (sig < _SIG_MIN) return 0; + if (sig > _MY_SIG_MAX) return 0; + return sig_code[sig]; +} + + +int +Perl_my_kill(int pid, int sig) +{ + dTHX; + int iss; + unsigned int code; + int sys$sigprc(unsigned int *pidadr, + struct dsc$descriptor_s *prcname, + unsigned int code); + + code = Perl_sig_to_vmscondition(sig); + + if (!pid || !code) { + return -1; + } + + iss = sys$sigprc((unsigned int *)&pid,0,code); + if (iss&1) return 0; + + switch (iss) { + case SS$_NOPRIV: + set_errno(EPERM); break; + case SS$_NONEXPR: + case SS$_NOSUCHNODE: + case SS$_UNREACHABLE: + set_errno(ESRCH); break; + case SS$_INSFMEM: + set_errno(ENOMEM); break; + default: + _ckvmssts(iss); + set_errno(EVMSERR); + } + set_vaxc_errno(iss); + + return -1; +} +#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)); @@ -991,15 +1380,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 @@ -1011,49 +1469,55 @@ struct exit_control_block unsigned long int exit_status; }; -static struct pipe_details *open_pipes = NULL; +typedef struct _closed_pipes Xpipe; +typedef struct _closed_pipes* pXpipe; + +struct _closed_pipes { + int pid; /* PID of subprocess */ + unsigned long completion; /* termination status of subprocess */ +}; +#define NKEEPCLOSED 50 +static Xpipe closed_list[NKEEPCLOSED]; +static int closed_index = 0; +static int closed_num = 0; + +#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; @@ -1062,14 +1526,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; @@ -1083,7 +1566,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) { @@ -1091,7 +1591,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; @@ -1108,96 +1607,1081 @@ 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(struct pipe_details *thispipe) +popen_completion_ast(pInfo info) { - thispipe->done = TRUE; - if (waitpid_asleep) { - waitpid_asleep = 0; - sys$wake(0,0); + pInfo i = open_pipes; + int iss; + pXpipe x; + + info->completion &= 0x0FFFFFFF; /* strip off "control" field */ + closed_list[closed_index].pid = info->pid; + closed_list[closed_index].completion = info->completion; + closed_index++; + if (closed_index == NKEEPCLOSED) + closed_index = 0; + closed_num++; + + while (i) { + if (i == info) break; + i = i->next; } -} + if (!i) return; /* unlinked, probably freed too */ -static unsigned long int setup_cmddsc(char *cmd, int check_img); -static void vms_execfree(pTHX); - -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->done = TRUE; - if (!(setup_cmddsc(cmd,0) & 1)) { set_errno(EINVAL); return Nullfp; } - New(1301,info,1,struct pipe_details); +/* + Writing to subprocess ... + if my_pclose'd: EOF already sent, should shutdown chan_in part of pipe - /* create mailbox */ - create_mbx(&chan,&namdsc); + 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. - /* open a FILE* onto it */ - info->fp = PerlIO_open(mbxname, mode); + 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. - /* give up other channel onto it */ - _ckvmssts(sys$dassgn(chan)); - if (!info->fp) - return Nullfp; - - info->mode = *mode; - info->done = FALSE; - info->completion=0; - - 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)); - } +*/ + 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)); + } + } - vms_execfree(aTHX); - if (!handler_set_up) { - _ckvmssts(sys$dclexh(&pipe_exitblock)); - handler_set_up = TRUE; - } - info->next=open_pipes; /* prepend to list */ - open_pipes=info; - - PL_forkprocess = info->pid; - return info->fp; -} /* end of safe_popen */ + 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)); -/*{{{ FILE *my_popen(char *cmd, char *mode)*/ -FILE * -Perl_my_popen(pTHX_ char *cmd, char *mode) -{ - TAINT_ENV(); - TAINT_PROPER("popen"); - PERL_FLUSHALL_FOR_CHILD; - return safe_popen(cmd,mode); } -/*}}}*/ +static unsigned long int setup_cmddsc(pTHX_ char *cmd, int check_img, int *suggest_quote, struct dsc$descriptor_s **pvmscmd); +static void vms_execfree(struct dsc$descriptor_s *vmscmd); -/*{{{ I32 my_pclose(FILE *fp)*/ -I32 Perl_my_pclose(pTHX_ FILE *fp) +/* + 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 */ +} + +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(aTHX_ &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 */ + +#ifdef PERL_IMPLICIT_CONTEXT + if (aTHX && PL_origargv && PL_origargv[0]) { /* maybe nul if embedded Perl */ +#else + if (PL_origargv && PL_origargv[0]) { /* maybe nul if embedded Perl */ +#endif + 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 */ + +#ifdef PERL_IMPLICIT_CONTEXT + if (aTHX) +#endif + 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 +} + + +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,"$! --- build command line to get max possible length\n"); + fprintf(fp,"$c=perl_popen_cmd0\n"); + fprintf(fp,"$c=c+perl_popen_cmd1\n"); + fprintf(fp,"$c=c+perl_popen_cmd2\n"); + fprintf(fp,"$x=perl_popen_cmd3\n"); + fprintf(fp,"$c=c+x\n"); + fprintf(fp,"$ perl_on\n"); + fprintf(fp,"$ 'c'\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 = CLI$M_NOWAIT; + /* The use of a GLOBAL table (as was done previously) rendered + * Perl's qx() or `` unusable from a C<$ SET SYMBOL/SCOPE=NOGLOBAL> DCL + * environment. Hence we've switched to LOCAL symbol table. + */ + unsigned int table = LIB$K_CLI_LOCAL_SYM; + int j, 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; + char cmd_sym_name[20]; + 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}; + struct dsc$descriptor_s d_sym_cmd = {0, DSC$K_DTYPE_T, + DSC$K_CLASS_S, cmd_sym_name}; + struct dsc$descriptor_s *vmscmd; + $DESCRIPTOR(d_sym_in ,"PERL_POPEN_IN"); + $DESCRIPTOR(d_sym_out,"PERL_POPEN_OUT"); + $DESCRIPTOR(d_sym_err,"PERL_POPEN_ERR"); + + if (!head_PLOC) store_pipelocs(aTHX); /* at least TRY to use a static vmspipe file */ + + /* 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_ packWARN(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,&vmscmd); + 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_ packWARN(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; + } + + 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; + } + + } else if (*mode == 'w') { /* piping to subroutine */ + + 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; + } + + 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); + } + + if (info->in) { + info->in->pipe_done = &info->in_done; + info->in_done = FALSE; + info->in->info = info; + } + + /* 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->in->buf) Safefree(info->in->buf); + Safefree(info->in); + Safefree(info); + *psts = RMS$_FNF; + return Nullfp; + } + + + } 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++; + + for (j = 0; j < 4; j++) { + sprintf(cmd_sym_name,"PERL_POPEN_CMD%d",j); + d_sym_cmd.dsc$w_length = strlen(cmd_sym_name); + + strncpy(symbol, p, MAX_DCL_SYMBOL); + d_symbol.dsc$w_length = strlen(symbol); + _ckvmssts(lib$set_symbol(&d_sym_cmd, &d_symbol, &table)); + + if (strlen(p) > MAX_DCL_SYMBOL) { + p += MAX_DCL_SYMBOL; + } else { + p += strlen(p); + } + } + _ckvmssts(sys$setast(0)); + info->next=open_pipes; /* prepend to list */ + open_pipes=info; + _ckvmssts(sys$setast(1)); + /* Omit arg 2 (input file) so the child will get the parent's SYS$INPUT + * and SYS$COMMAND. vmspipe.com will redefine SYS$INPUT, but we'll still + * have SYS$COMMAND if we need it. + */ + _ckvmssts(lib$spawn(&vmspipedsc, 0, &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 */ + + for (j = 0; j < 4; j++) { + sprintf(cmd_sym_name,"PERL_POPEN_CMD%d",j); + d_sym_cmd.dsc$w_length = strlen(cmd_sym_name); + _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(vmscmd); + +#ifdef PERL_IMPLICIT_CONTEXT + if (aTHX) +#endif + 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 */ + + +/*{{{ 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(aTHX_ cmd,mode,&sts); +} + +/*}}}*/ + +/*{{{ 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; @@ -1210,75 +2694,213 @@ 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 >= 70200000 + /* 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; + int j; + + 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)); + } + + if (statusp) *statusp = info->completion; + return pid; + } + + /* child that already terminated? */ + + for (j = 0; j < NKEEPCLOSED && j < closed_num; j++) { + if (closed_list[j].pid == pid) { + if (statusp) *statusp = closed_list[j].completion; + return pid; + } + } + + /* fall through if this child is not one of our own pipe children */ + +#if defined(__CRTL_VER) && __CRTL_VER >= 70200000 + + /* 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 >= 70200000 */ + + { + $DESCRIPTOR(intdsc,"0 00:00:01"); + unsigned long int ownercode = JPI$_OWNER, ownerpid; + unsigned long int pidcode = JPI$_PID, mypid; + unsigned long int interval[2]; + unsigned int jpi_iosb[2]; + struct itmlst_3 jpilist[2] = { + {sizeof(ownerpid), JPI$_OWNER, &ownerpid, 0}, + { 0, 0, 0, 0} + }; + + 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. 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; } - *statusp = info->completion; - return pid; - } - else { /* we haven't heard of this child */ - $DESCRIPTOR(intdsc,"0 00:00:01"); - unsigned long int ownercode = JPI$_OWNER, ownerpid, mypid; - unsigned long int interval[2],sts; - 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_ packWARN(WARN_EXEC), + "waitpid: process %x is not a child of process %x", + pid,mypid); } + /* simply check on it once a second until it's not there anymore. */ + _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()); + _ckvmssts(sys$schdwk(0,0,interval,0)); + _ckvmssts(sys$hiber()); } - _ckvmssts(sts); + if (sts == SS$_NONEXPR) sts = SS$_NORMAL; - /* 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. - */ - *statusp = 0; + _ckvmssts(sts); return pid; } - } /* end of waitpid() */ /*}}}*/ /*}}}*/ @@ -1510,6 +3132,7 @@ static char *mp_do_fileify_dirspec(pTHX_ char *dir,char *buf,int ts) unsigned long int dirlen, retlen, addmfd = 0, hasfilename = 0; char *retspec, *cp1, *cp2, *lastdir; char trndir[NAM$C_MAXRSS+2], vmsdir[NAM$C_MAXRSS+1]; + unsigned short int trnlnm_iter_count; if (!dir || !*dir) { set_errno(EINVAL); set_vaxc_errno(SS$_BADPARAM); return NULL; @@ -1526,7 +3149,11 @@ static char *mp_do_fileify_dirspec(pTHX_ char *dir,char *buf,int ts) } if (!strpbrk(dir+1,"/]>:")) { strcpy(trndir,*dir == '/' ? dir + 1: dir); - while (!strpbrk(trndir,"/]>:>") && my_trnlnm(trndir,trndir,0)) ; + trnlnm_iter_count = 0; + while (!strpbrk(trndir,"/]>:>") && my_trnlnm(trndir,trndir,0)) { + trnlnm_iter_count++; + if (trnlnm_iter_count >= PERL_LNM_MAX_ITER) break; + } dir = trndir; dirlen = strlen(dir); } @@ -1545,6 +3172,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. */ @@ -1767,6 +3398,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)) { @@ -1827,6 +3459,8 @@ static char *mp_do_pathify_dirspec(pTHX_ char *dir,char *buf, int ts) static char __pathify_retbuf[NAM$C_MAXRSS+1]; unsigned long int retlen; char *retpath, *cp1, *cp2, trndir[NAM$C_MAXRSS+1]; + unsigned short int trnlnm_iter_count; + STRLEN trnlen; if (!dir || !*dir) { set_errno(EINVAL); set_vaxc_errno(SS$_BADPARAM); return NULL; @@ -1835,9 +3469,12 @@ static char *mp_do_pathify_dirspec(pTHX_ char *dir,char *buf, int ts) if (*dir) strcpy(trndir,dir); else getcwd(trndir,sizeof trndir - 1); + trnlnm_iter_count = 0; while (!strpbrk(trndir,"/]:>") && !no_translate_barewords && my_trnlnm(trndir,trndir,0)) { - STRLEN trnlen = strlen(trndir); + trnlnm_iter_count++; + if (trnlnm_iter_count >= PERL_LNM_MAX_ITER) break; + trnlen = strlen(trndir); /* Trap simple rooted lnms, and return lnm:[000000] */ if (!strcmp(trndir+trnlen-2,".]")) { @@ -2012,7 +3649,9 @@ static char *mp_do_tounixspec(pTHX_ char *spec, char *buf, int ts) { static char __tounixspec_retbuf[NAM$C_MAXRSS+1]; char *dirend, *rslt, *cp1, *cp2, *cp3, tmp[NAM$C_MAXRSS+1]; - int devlen, dirlen, retlen = NAM$C_MAXRSS+1, expand = 0; + int devlen, dirlen, retlen = NAM$C_MAXRSS+1; + int expand = 1; /* guarantee room for leading and trailing slashes */ + unsigned short int trnlnm_iter_count; if (spec == NULL) return NULL; if (strlen(spec) > NAM$C_MAXRSS) return NULL; @@ -2059,11 +3698,14 @@ static char *mp_do_tounixspec(pTHX_ char *spec, char *buf, int ts) if (ts) Safefree(rslt); return NULL; } + trnlnm_iter_count = 0; do { cp3 = tmp; while (*cp3 != ':' && *cp3) cp3++; *(cp3++) = '\0'; if (strchr(cp3,']') != NULL) break; + trnlnm_iter_count++; + if (trnlnm_iter_count >= PERL_LNM_MAX_ITER+1) break; } while (vmstrnenv(tmp,tmp,0,fildev,0)); if (ts && !buf && ((devlen = strlen(tmp)) + (dirlen = strlen(cp2)) + 1 > retlen)) { @@ -2384,9 +4026,9 @@ static void mp_expand_wild_cards(pTHX_ char *item, struct list_item **tail, int *count); -static int background_process(int argc, char **argv); +static int background_process(pTHX_ 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 @@ -2434,11 +4076,11 @@ mp_getredirection(pTHX_ int *ac, char ***av) */ ap = argv[argc-1]; if (0 == strcmp("&", ap)) - exit(background_process(--argc, argv)); + exit(background_process(aTHX_ --argc, argv)); if (*ap && '&' == ap[strlen(ap)-1]) { ap[strlen(ap)-1] = '\0'; - exit(background_process(argc, argv)); + exit(background_process(aTHX_ argc, argv)); } /* * Now we handle the general redirection cases that involve '>', '>>', @@ -2450,7 +4092,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]; @@ -2465,7 +4107,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]; @@ -2485,7 +4127,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; @@ -2507,7 +4149,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; @@ -2516,7 +4158,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); @@ -2547,10 +4189,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) */ @@ -2566,7 +4208,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); @@ -2580,35 +4222,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 @@ -2648,6 +4294,7 @@ static void mp_expand_wild_cards(pTHX_ char *item, int expcount = 0; unsigned long int context = 0; int isunix = 0; +int item_len = 0; char *had_version; char *had_device; int had_directory; @@ -2667,6 +4314,20 @@ unsigned long int zero = 0, sts; add_item(head, tail, item, count); return; } + else + { + /* "double quoted" wild card expressions pass as is */ + /* From DCL that means using e.g.: */ + /* perl program """perl.*""" */ + item_len = strlen(item); + if ( '"' == *item && '"' == item[item_len-1] ) + { + item++; + item[item_len-2] = '\0'; + add_item(head, tail, item, count); + return; + } + } resultspec.dsc$b_dtype = DSC$K_DTYPE_T; resultspec.dsc$b_class = DSC$K_CLASS_D; resultspec.dsc$a_pointer = NULL; @@ -2779,44 +4440,58 @@ 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; + struct dsc$descriptor_s *vmscmd; + char subcmd[2*MAX_DCL_LINE_LENGTH], *p, *q; + int sts, j, l, ismcr, quote, tquote = 0; + + sts = setup_cmddsc(aTHX_ cmargv[0],0,"e,&vmscmd); + vms_execfree(vmscmd); + + 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++; + } } - cmddsc.dsc$a_pointer = subcmd; - cmddsc.dsc$w_length = strlen(cmddsc.dsc$a_pointer); + } else { + if ((quote||tquote) && *q == '"') { + *p++ = '"'; + l++; + } + *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); + fp = safe_popen(aTHX_ subcmd,"wbF",&sts); + if (fp == Nullfp) { + PerlIO_printf(Perl_debug_log,"Can't open output pipe (status %d)",sts); } } -static int background_process(int argc, char **argv) +static int background_process(pTHX_ int argc, char **argv) { char command[2048] = "$"; $DESCRIPTOR(value, ""); @@ -2878,17 +4553,23 @@ 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]); +#ifdef KILL_BY_SIGPRC + (void) Perl_csighandler_init(); +#endif + + _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; } @@ -2900,12 +4581,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 @@ -2924,15 +4614,19 @@ vms_image_init(int *argcp, char ***argvp) * hasn't been allocated when vms_image_init() is called. */ if (will_taint) { - char ***newap; - New(1320,newap,*argcp+2,char **); - newap[0] = argvp[0]; - *newap[1] = "-T"; - Copy(argvp[1],newap[2],*argcp-1,char **); + char **newargv, **oldargv; + oldargv = *argvp; + New(1320,newargv,(*argcp)+2,char *); + newargv[0] = oldargv[0]; + New(1320,newargv[1],3,char); + strcpy(newargv[1], "-T"); + Copy(&oldargv[1],&newargv[2],(*argcp)-1,char **); + (*argcp)++; + newargv[*argcp] = NULL; /* We orphan the old argv, since we don't know where it's come from, * so we don't know how to free it. */ - *argcp++; argvp = newap; + *argvp = newargv; } else { /* Did user explicitly request tainting? */ int i; @@ -2961,12 +4655,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_ITHREADS) && ( defined(__DECC) || defined(__DECCXX) ) { # include (void) decc$set_reentrancy(C$C_MULTITHREAD); @@ -3145,6 +4839,18 @@ Perl_trim_unixpath(pTHX_ char *fspec, char *wildspec, int opts) * Minor modifications to original routines. */ +/* readdir may have been redefined by reentr.h, so make sure we get + * the local version for what we do here. + */ +#ifdef readdir +# undef readdir +#endif +#if !defined(PERL_IMPLICIT_CONTEXT) +# define readdir Perl_readdir +#else +# define readdir(a) Perl_readdir(aTHX_ a) +#endif + /* Number of elements in vms_versions array */ #define VERSIZE(e) (sizeof e->vms_versions / sizeof e->vms_versions[0]) @@ -3162,15 +4868,18 @@ Perl_opendir(pTHX_ char *name) if (do_tovmspath(name,dir,0) == NULL) { return NULL; } + /* Check access before stat; otherwise stat does not + * accurately report whether it's a directory. + */ + if (!cando_by_name(S_IRUSR,0,dir)) { + /* cando_by_name has already set errno */ + return NULL; + } if (flex_stat(dir,&sb) == -1) return NULL; if (!S_ISDIR(sb.st_mode)) { set_errno(ENOTDIR); set_vaxc_errno(RMS$_DIR); return NULL; } - if (!cando_by_name(S_IRUSR,0,dir)) { - set_errno(EACCES); set_vaxc_errno(RMS$_PRV); - return NULL; - } /* Get memory for the handle, and the pattern. */ New(1306,dd,1,DIR); New(1307,dd->pattern,strlen(dir)+sizeof "*.*" + 1,char); @@ -3184,6 +4893,12 @@ Perl_opendir(pTHX_ char *name) dd->pat.dsc$w_length = strlen(dd->pattern); dd->pat.dsc$b_dtype = DSC$K_DTYPE_T; dd->pat.dsc$b_class = DSC$K_CLASS_S; +#if defined(USE_ITHREADS) + New(1308,dd->mutex,1,perl_mutex); + MUTEX_INIT( (perl_mutex *) dd->mutex ); +#else + dd->mutex = NULL; +#endif return dd; } /* end of opendir() */ @@ -3209,6 +4924,10 @@ closedir(DIR *dd) { (void)lib$find_file_end(&dd->context); Safefree(dd->pattern); +#if defined(USE_ITHREADS) + MUTEX_DESTROY( (perl_mutex *) dd->mutex ); + Safefree(dd->mutex); +#endif Safefree((char *)dd); } /*}}}*/ @@ -3217,8 +4936,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; @@ -3226,7 +4944,6 @@ collectversions(dd) char *p, *text, buff[sizeof dd->entry.d_name]; int i; unsigned long context, tmpsts; - dTHX; /* Convenient shorthand. */ e = &dd->entry; @@ -3273,7 +4990,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]; @@ -3318,13 +5035,35 @@ 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() */ /*}}}*/ /* + * Read the next entry from the directory -- thread-safe version. + */ +/*{{{ int readdir_r(DIR *dd, struct dirent *entry, struct dirent **result)*/ +int +Perl_readdir_r(pTHX_ DIR *dd, struct dirent *entry, struct dirent **result) +{ + int retval; + + MUTEX_LOCK( (perl_mutex *) dd->mutex ); + + entry = readdir(dd); + *result = entry; + retval = ( *result == NULL ? errno : 0 ); + + MUTEX_UNLOCK( (perl_mutex *) dd->mutex ); + + return retval; + +} /* end of readdir_r() */ +/*}}}*/ + +/* * Return something that can be used in a seekdir later. */ /*{{{ long telldir(DIR *dd)*/ @@ -3340,10 +5079,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) @@ -3371,7 +5109,7 @@ seekdir(DIR *dd, long count) * * vms_do_aexec() and vms_do_exec() are called in response to the * perl 'exec' function. If this follows a vfork call, then they - * call out the the regular perl routines in doio.c which do an + * call out the regular perl routines in doio.c which do an * execvp (for those who really want to try this under VMS). * Otherwise, they do exactly what the perl docs say exec should * do - terminate the current script and invoke a new command @@ -3384,7 +5122,7 @@ seekdir(DIR *dd, long count) * in 'VMSish fashion' (i.e. not after a call to vfork) The args * are concatenated to form a DCL command string. If the first arg * begins with '$' (i.e. the perl script had "\$ Type" or some such), - * the the command string is handed off to DCL directly. Otherwise, + * the command string is handed off to DCL directly. Otherwise, * the first token of the command is taken as the filespec of an image * to run. The filespec is expanded using a default type of '.EXE' and * the process defaults for device, directory, etc., and if found, the resultant @@ -3407,22 +5145,19 @@ my_vfork() static void -vms_execfree(pTHX) { - if (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; +vms_execfree(struct dsc$descriptor_s *vmscmd) +{ + if (vmscmd) { + if (vmscmd->dsc$a_pointer) { + Safefree(vmscmd->dsc$a_pointer); + } + Safefree(vmscmd); } } 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; @@ -3465,21 +5200,30 @@ 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, + struct dsc$descriptor_s **pvmscmd) { char vmsspec[NAM$C_MAXRSS+1], resspec[NAM$C_MAXRSS+1]; $DESCRIPTOR(defdsc,".EXE"); $DESCRIPTOR(defdsc2,"."); $DESCRIPTOR(resdsc,resspec); + struct dsc$descriptor_s *vmscmd; struct dsc$descriptor_s imgdsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0}; 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; + New(402,vmscmd,sizeof(struct dsc$descriptor_s),struct dsc$descriptor_s); + vmscmd->dsc$a_pointer = NULL; + vmscmd->dsc$b_dtype = DSC$K_DTYPE_T; + vmscmd->dsc$b_class = DSC$K_CLASS_S; + vmscmd->dsc$w_length = 0; + if (pvmscmd) *pvmscmd = vmscmd; + + 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++; @@ -3511,8 +5255,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); @@ -3557,24 +5303,41 @@ 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 || @@ -3583,16 +5346,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--; @@ -3603,7 +5365,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)); } @@ -3613,10 +5375,10 @@ 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) { + struct dsc$descriptor_s *vmscmd; - dTHX; if (vfork_called) { /* this follows a vfork - act Unixish */ vfork_called--; if (vfork_called < 0) { @@ -3631,8 +5393,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,&vmscmd)) & 1) + retsts = lib$do_command(vmscmd); switch (retsts) { case RMS$_FNF: case RMS$_DNF: @@ -3645,7 +5407,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 */ @@ -3654,10 +5416,10 @@ 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)); + Perl_warner(aTHX_ packWARN(WARN_EXEC),"Can't exec \"%*s\": %s", + vmscmd->dsc$w_length, vmscmd->dsc$a_pointer, Strerror(errno)); } - vms_execfree(aTHX); + vms_execfree(vmscmd); } return FALSE; @@ -3665,14 +5427,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() */ @@ -3680,82 +5441,148 @@ 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; + unsigned long int sts, substs; TAINT_ENV(); TAINT_PROPER("spawn"); if (!cmd || !*cmd) { - hadcmd = 0; sts = lib$spawn(0,0,0,0,0,0,&substs,0,0,0,0,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 (ckWARN(WARN_EXEC)) { + Perl_warner(aTHX_ packWARN(WARN_EXEC),"Can't spawn: %s", + Strerror(errno)); + } + } + sts = substs; } - 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(aTHX_ cmd, "nW", (int *)&sts); } - - 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: - 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 (ckWARN(WARN_EXEC)) { - Perl_warner(aTHX_ WARN_EXEC,"Can't spawn \"%*s\": %s", - hadcmd ? VMScmd.dsc$w_length : 0, - hadcmd ? VMScmd.dsc$a_pointer : "", - Strerror(errno)); - } + return sts; +} /* 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)); } - vms_execfree(aTHX); - return substs; + return fp; -} /* end of do_spawn() */ +} +/*}}}*/ + + +/* + * 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; + } + + _ckvmssts_noperl(lib$get_vm(&bufsize, &data)); + memcpy( data, src, itmsz*nitm ); + data[itmsz*nitm] = '\0'; - end = (char *)src + itmsz * nitm; + end = data + itmsz * nitm; + retval = (int) nitm; /* on success return # items written */ - while ((char *)src <= end) { - for (cp = src; cp <= end; cp++) if (!*cp) break; - if (fputs(src,dest) == EOF) return EOF; + 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) { @@ -3765,6 +5592,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; } /*}}}*/ @@ -3829,9 +5663,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]; @@ -3911,15 +5744,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; @@ -3950,13 +5782,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 { @@ -3996,7 +5827,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() */ @@ -4006,7 +5837,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)); } @@ -4016,9 +5847,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; @@ -4078,7 +5908,7 @@ int my_sigdelset(sigset_t *set, int sig) { int my_sigismember(sigset_t *set, int sig) { if (!set) { SETERRNO(EFAULT,SS$_ACCVIO); return -1; } if (sig > NSIG) { SETERRNO(EINVAL,LIB$_INVARG); return -1; } - *set & (1 << (sig - 1)); + return *set & (1 << (sig - 1)); } /*}}}*/ @@ -4144,9 +5974,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 @@ -4195,6 +6022,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 @@ -4207,9 +6317,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; @@ -4226,6 +6335,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); } @@ -4260,9 +6370,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; @@ -4291,11 +6400,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); @@ -4311,15 +6420,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() */ /*}}}*/ @@ -4347,10 +6465,9 @@ 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 my_utime(const char *path, const struct utimbuf *utimes)*/ +int Perl_my_utime(pTHX_ const char *file, const struct utimbuf *utimes) { - dTHX; register int i; long int bintime[2], len = 2, lowbit, unixtime, secscale = 10000000; /* seconds --> 100 ns intervals */ @@ -4382,7 +6499,7 @@ int my_utime(char *file, struct utimbuf *utimes) set_vaxc_errno(LIB$_INVARG); return -1; } - if (do_tovmsspec(file,vmsspec,0) == NULL) return -1; + if (do_tovmsspec((char *)file,vmsspec,0) == NULL) return -1; if (utimes != NULL) { /* Convert Unix time (seconds since 01-JAN-1970 00:00:00.00) @@ -4471,7 +6588,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 @@ -4533,14 +6650,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; @@ -4586,7 +6702,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 @@ -4606,8 +6721,9 @@ is_null_device(name) * subset of the applicable information. */ bool -Perl_cando(pTHX_ Mode_t bit, Uid_t effective, Stat_t *statbufp) +Perl_cando(pTHX_ Mode_t bit, Uid_t effective, const 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]; @@ -4626,7 +6742,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"); @@ -4641,27 +6765,34 @@ 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 = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, usrname}; 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; + unsigned short int retlen, trnlnm_iter_count; 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}, {sizeof privused, CHP$_PRIVUSED, &privused, &retlen},{0,0,0,0}}; - struct itmlst_3 jpilst[2] = {{sizeof curprv, JPI$_CURPRIV, &curprv, &retlen}, + struct itmlst_3 jpilst[3] = {{sizeof curprv, JPI$_CURPRIV, &curprv, &retlen}, + {sizeof usrname, JPI$_USERNAME, &usrname, &usrdsc.dsc$w_length}, {0,0,0,0}}; + struct itmlst_3 usrprolst[2] = {{sizeof curprv, CHP$_PRIV, &curprv, &retlen}, + {0,0,0,0}}; + struct dsc$descriptor_s usrprodsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0}; if (!fname || !*fname) return FALSE; /* Make sure we expand logical names, since sys$check_access doesn't */ if (!strpbrk(fname,"/]>:")) { strcpy(fileified,fname); - while (!strpbrk(fileified,"/]>:>") && my_trnlnm(fileified,fileified,0)) ; + trnlnm_iter_count = 0; + while (!strpbrk(fileified,"/]>:>") && my_trnlnm(fileified,fileified,0)) { + trnlnm_iter_count++; + if (trnlnm_iter_count >= PERL_LNM_MAX_ITER) break; + } fname = fileified; } if (!do_tovmsspec(fname,vmsname,1)) return FALSE; @@ -4674,11 +6805,6 @@ cando_by_name(I32 bit, Uid_t effective, char *fname) namdsc.dsc$a_pointer = fileified; } - if (!usrdsc.dsc$w_length) { - cuserid(usrname); - usrdsc.dsc$w_length = strlen(usrname); - } - switch (bit) { case S_IXUSR: case S_IXGRP: case S_IXOTH: access = ARM$M_EXECUTE; break; @@ -4692,38 +6818,51 @@ cando_by_name(I32 bit, Uid_t effective, char *fname) return FALSE; } + /* Before we call $check_access, create a user profile with the current + * process privs since otherwise it just uses the default privs from the + * UAF and might give false positives or negatives. This only works on + * VMS versions v6.0 and later since that's when sys$create_user_profile + * became available. + */ + + /* get current process privs and username */ + _ckvmssts(sys$getjpiw(0,0,0,jpilst,iosb,0,0)); + _ckvmssts(iosb[0]); + +#if defined(__VMS_VER) && __VMS_VER >= 60000000 + + /* find out the space required for the profile */ + _ckvmssts(sys$create_user_profile(&usrdsc,&usrprolst,0,0, + &usrprodsc.dsc$w_length,0)); + + /* allocate space for the profile and get it filled in */ + New(1330,usrprodsc.dsc$a_pointer,usrprodsc.dsc$w_length,char); + _ckvmssts(sys$create_user_profile(&usrdsc,&usrprolst,0,usrprodsc.dsc$a_pointer, + &usrprodsc.dsc$w_length,0)); + + /* use the profile to check access to the file; free profile & analyze results */ + retsts = sys$check_access(&objtyp,&namdsc,0,armlst,0,0,0,&usrprodsc); + Safefree(usrprodsc.dsc$a_pointer); + if (retsts == SS$_NOCALLPRIV) retsts = SS$_NOPRIV; /* not really 3rd party */ + +#else + retsts = sys$check_access(&objtyp,&namdsc,&usrdsc,armlst); + +#endif + 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); else set_errno(ENOENT); return FALSE; } - if (retsts == SS$_NORMAL) { - if (!privused) return TRUE; - /* We can get access, but only by using privs. Do we have the - necessary privs currently enabled? */ - _ckvmssts(sys$getjpiw(0,0,0,jpilst,iosb,0,0)); - if ((privused & CHP$M_BYPASS) && !curprv.prv$v_bypass) return FALSE; - if ((privused & CHP$M_SYSPRV) && !curprv.prv$v_sysprv && - !curprv.prv$v_bypass) return FALSE; - if ((privused & CHP$M_GRPPRV) && !curprv.prv$v_grpprv && - !curprv.prv$v_sysprv && !curprv.prv$v_bypass) return FALSE; - if ((privused & CHP$M_READALL) && !curprv.prv$v_readall) return FALSE; - return TRUE; - } - if (retsts == SS$_ACCONFLICT) { + if (retsts == SS$_NORMAL || 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 */ @@ -4734,12 +6873,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) { @@ -4768,19 +6906,21 @@ 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; + int saved_errno, saved_vaxc_errno; + if (!fspec) return retval; + saved_errno = errno; saved_vaxc_errno = vaxc$errno; 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; @@ -4804,7 +6944,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) { @@ -4825,6 +6965,8 @@ flex_stat(const char *fspec, Stat_t *statbufp) } # endif } + /* If we were successful, leave errno where we found it */ + if (retval == 0) { errno = saved_errno; vaxc$errno = saved_vaxc_errno; } return retval; } /* end of flex_stat() */ @@ -5157,7 +7299,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); @@ -5194,7 +7336,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); @@ -5210,7 +7352,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); @@ -5232,7 +7374,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], @@ -5307,10 +7449,46 @@ 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) +{ + unsigned int ix = RAND_MAX; + double x; + + VMSISH_HUSHED = 0; + + x = (float)ix; + MY_INV_RAND_MAX = 1./x; +} + +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; @@ -5328,6 +7506,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); /* will redo any earlier attempts */ return; }