X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=vms%2Fvms.c;h=1150ea3859835b6cc109569e4f4f7af8c509081f;hb=7dc9aaa56b11c3e04f31eb9de23451166e23126f;hp=fac9243cc5c1d11b677ef2113a6f70a19d6c67d7;hpb=a44ceb8eed3b07435791041547cd2f83c470d7be;p=p5sagit%2Fp5-mst-13.2.git diff --git a/vms/vms.c b/vms/vms.c index fac9243..1150ea3 100644 --- a/vms/vms.c +++ b/vms/vms.c @@ -1,11 +1,15 @@ /* vms.c * * VMS-specific routines for perl5 + * Version: 5.7.0 * - * Last revised: 15-Aug-1999 by Charles Bailey bailey@newman.upenn.edu - * Version: 5.5.60 + * August 2000 tweaks to vms_image_init, my_flush, my_fwrite, cando_by_name, + * and Perl_cando by Craig Berry + * 29-Aug-2000 Charles Lane's piping improvements rolled in + * 20-Aug-1999 revisions by Charles Bailey bailey@newman.upenn.edu */ +#include #include #include #include @@ -14,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +51,9 @@ # define SS$_NOSUCHOBJECT 2696 #endif +/* We implement I/O here, so we will be mixing PerlIO and stdio calls. */ +#define PERLIO_NOT_STDIO 0 + /* Don't replace system definitions of vfork, getenv, and stat, * code below needs to get to the underlying CRTL routines. */ #define DONT_MASK_RTL_CALLS @@ -56,6 +65,11 @@ # define WARN_INTERNAL WARN_MISC #endif +#if defined(__VMS_VER) && __VMS_VER >= 70000000 && __DECC_VER >= 50200000 +# define RTL_USES_UTC 1 +#endif + + /* gcc's header files don't #define direct access macros * corresponding to VAXC's variant structs */ #ifdef __GNUC__ @@ -68,6 +82,9 @@ # define prv$v_sysprv prv$r_prvdef_bits0.prv$v_sysprv #endif +#if defined(NEED_AN_H_ERRNO) +dEXT int h_errno; +#endif struct itmlst_3 { unsigned short int buflen; @@ -76,6 +93,19 @@ struct itmlst_3 { unsigned short int *retlen; }; +#define do_fileify_dirspec(a,b,c) mp_do_fileify_dirspec(aTHX_ a,b,c) +#define do_pathify_dirspec(a,b,c) mp_do_pathify_dirspec(aTHX_ a,b,c) +#define do_tovmsspec(a,b,c) mp_do_tovmsspec(aTHX_ a,b,c) +#define do_tovmspath(a,b,c) mp_do_tovmspath(aTHX_ a,b,c) +#define do_rmsexpand(a,b,c,d,e) mp_do_rmsexpand(aTHX_ a,b,c,d,e) +#define do_tounixspec(a,b,c) mp_do_tounixspec(aTHX_ a,b,c) +#define do_tounixpath(a,b,c) mp_do_tounixpath(aTHX_ a,b,c) +#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 + static char *__mystrtolower(char *str) { if (str) for (; *str; ++str) *str= tolower(*str); @@ -95,12 +125,19 @@ 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 + /*{{{int vmstrnenv(const char *lnm, char *eqv, unsigned long int idx, struct dsc$descriptor_s **tabvec, unsigned long int flags) */ int -vmstrnenv(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], *cp1, *cp2; + 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; unsigned char acmode; @@ -110,20 +147,29 @@ vmstrnenv(const char *lnm, char *eqv, unsigned long int idx, {LNM$C_NAMLENGTH, LNM$_STRING, eqv, &eqvlen}, {0, 0, 0, 0}}; $DESCRIPTOR(crtlenv,"CRTL_ENV"); $DESCRIPTOR(clisym,"CLISYM"); -#if defined(USE_THREADS) +#if defined(PERL_IMPLICIT_CONTEXT) + pTHX = NULL; +# if defined(USE_5005THREADS) /* We jump through these hoops because we can be called at */ /* platform-specific initialization time, which is before anything is */ /* set up--we can't even do a plain dTHX since that relies on the */ /* interpreter structure to be initialized */ - struct perl_thread *thr; if (PL_curinterp) { - thr = PL_threadnum? THR : (struct perl_thread*)SvPVX(PL_thrsv); + aTHX = PL_threadnum? THR : (struct perl_thread*)SvPVX(PL_thrsv); } else { - thr = NULL; + aTHX = NULL; } +# else + if (PL_curinterp) { + aTHX = PERL_GET_INTERP; + } else { + aTHX = NULL; + } + +# endif #endif - if (!lnm || !eqv || idx > LNM$_MAX_INDEX) { + if (!lnm || !eqv || idx > PERL_LNM_MAX_ALLOWED_INDEX) { set_errno(EINVAL); set_vaxc_errno(SS$_BADPARAM); return 0; } for (cp1 = (char *)lnm, cp2 = uplnm; *cp1; cp1++, cp2++) { @@ -135,6 +181,7 @@ vmstrnenv(const char *lnm, char *eqv, unsigned long int idx, } lnmdsc.dsc$w_length = cp1 - lnm; lnmdsc.dsc$a_pointer = uplnm; + uplnm[lnmdsc.dsc$w_length] = '\0'; secure = flags & PERL__TRNENV_SECURE; acmode = secure ? PSL$C_EXEC : PSL$C_USER; if (!tabvec || !*tabvec) tabvec = env_tables; @@ -179,13 +226,13 @@ vmstrnenv(const char *lnm, char *eqv, unsigned long int idx, /* fully initialized, in which case either thr or PL_curcop */ /* might be bogus. We have to check, since ckWARN needs them */ /* both to be valid if running threaded */ -#if defined(USE_THREADS) +#if defined(USE_5005THREADS) if (thr && PL_curcop) { #endif if (ckWARN(WARN_MISC)) { Perl_warner(aTHX_ WARN_MISC,"Value of CLI symbol \"%s\" too long",lnm); } -#if defined(USE_THREADS) +#if defined(USE_5005THREADS) } else { Perl_warner(aTHX_ WARN_MISC,"Value of CLI symbol \"%s\" too long",lnm); } @@ -204,6 +251,19 @@ vmstrnenv(const char *lnm, char *eqv, unsigned long int idx, 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 INTSIZE == 4 + *((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; + } break; } } @@ -220,7 +280,7 @@ vmstrnenv(const char *lnm, char *eqv, unsigned long int idx, /*{{{ int my_trnlnm(const char *lnm, char *eqv, unsigned long int idx)*/ /* Define as a function so we can access statics. */ -int my_trnlnm(const char *lnm, char *eqv, unsigned long int idx) +int Perl_my_trnlnm(pTHX_ const char *lnm, char *eqv, unsigned long int idx) { return vmstrnenv(lnm,eqv,idx,fildev, #ifdef SECURE_INTERNAL_GETENV @@ -247,7 +307,7 @@ Perl_my_getenv(pTHX_ const char *lnm, bool sys) static char __my_getenv_eqv[LNM$C_NAMLENGTH+1]; char uplnm[LNM$C_NAMLENGTH+1], *cp1, *cp2, *eqv; unsigned long int idx = 0; - int trnsuccess; + int trnsuccess, success, secure, saverr, savvmserr; SV *tmpsv; if (PL_curinterp) { /* Perl interpreter running -- may be threaded */ @@ -270,15 +330,26 @@ Perl_my_getenv(pTHX_ const char *lnm, bool sys) idx = strtoul(cp2+1,NULL,0); lnm = uplnm; } - if (vmstrnenv(lnm,eqv,idx, - sys ? fildev : NULL, + /* 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; + success = vmstrnenv(lnm,eqv,idx, + secure ? fildev : NULL, #ifdef SECURE_INTERNAL_GETENV - sys ? PERL__TRNENV_SECURE : 0 + secure ? PERL__TRNENV_SECURE : 0 #else - 0 + 0 #endif - )) return eqv; - else return Nullch; + ); + /* Discard NOLOGNAM on internal calls since we're often looking + * for an optional name, and this "error" often shows up as the + * (bogus) exit status for a die() call later on. */ + if (sys && vaxc$errno == SS$_NOLOGNAM) SETERRNO(saverr,savvmserr); + return success ? eqv : Nullch; } } /* end of my_getenv() */ @@ -287,12 +358,12 @@ Perl_my_getenv(pTHX_ const char *lnm, bool sys) /*{{{ SV *my_getenv_len(const char *lnm, bool sys)*/ char * -my_getenv_len(const char *lnm, unsigned long *len, bool sys) +Perl_my_getenv_len(pTHX_ const char *lnm, unsigned long *len, bool sys) { - dTHX; char *buf, *cp1, *cp2; unsigned long idx = 0; static char __my_getenv_len_eqv[LNM$C_NAMLENGTH+1]; + int secure, saverr, savvmserr; SV *tmpsv; if (PL_curinterp) { /* Perl interpreter running -- may be threaded */ @@ -316,23 +387,31 @@ my_getenv_len(const char *lnm, unsigned long *len, bool sys) idx = strtoul(cp2+1,NULL,0); lnm = buf; } - if ((*len = vmstrnenv(lnm,buf,idx, - sys ? fildev : NULL, + if (sys) { + /* Impose security constraints only if tainting */ + secure = PL_curinterp ? PL_tainting : will_taint; + saverr = errno; savvmserr = vaxc$errno; + } + else secure = 0; + *len = vmstrnenv(lnm,buf,idx, + secure ? fildev : NULL, #ifdef SECURE_INTERNAL_GETENV - sys ? PERL__TRNENV_SECURE : 0 + secure ? PERL__TRNENV_SECURE : 0 #else - 0 + 0 #endif - ))) - return buf; - else - return Nullch; + ); + /* Discard NOLOGNAM on internal calls since we're often looking + * for an optional name, and this "error" often shows up as the + * (bogus) exit status for a die() call later on. */ + if (sys && vaxc$errno == SS$_NOLOGNAM) SETERRNO(saverr,savvmserr); + return *len ? buf : Nullch; } } /* end of my_getenv_len() */ /*}}}*/ -static void create_mbx(unsigned short int *, struct dsc$descriptor_s *); +static void create_mbx(pTHX_ unsigned short int *, struct dsc$descriptor_s *); static void riseandshine(unsigned long int dummy) { sys$wake(0,0); } @@ -343,9 +422,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 @@ -360,11 +439,34 @@ prime_env_iter(void) $DESCRIPTOR(clidsc,"DCL"); $DESCRIPTOR(clitabdsc,"DCLTABLES"); $DESCRIPTOR(crtlenv,"CRTL_ENV"); $DESCRIPTOR(clisym,"CLISYM"); $DESCRIPTOR(local,"_LOCAL"); $DESCRIPTOR(mbxdsc,mbxnam); -#ifdef USE_THREADS +#if defined(PERL_IMPLICIT_CONTEXT) + pTHX; +#endif +#if defined(USE_5005THREADS) || defined(USE_ITHREADS) static perl_mutex primenv_mutex; MUTEX_INIT(&primenv_mutex); #endif +#if defined(PERL_IMPLICIT_CONTEXT) + /* We jump through these hoops because we can be called at */ + /* platform-specific initialization time, which is before anything is */ + /* set up--we can't even do a plain dTHX since that relies on the */ + /* interpreter structure to be initialized */ +#if defined(USE_5005THREADS) + if (PL_curinterp) { + aTHX = PL_threadnum? THR : (struct perl_thread*)SvPVX(PL_thrsv); + } else { + aTHX = NULL; + } +#else + if (PL_curinterp) { + aTHX = PERL_GET_INTERP; + } else { + aTHX = NULL; + } +#endif +#endif + if (primed || !PL_envgv) return; MUTEX_LOCK(&primenv_mutex); if (primed) { MUTEX_UNLOCK(&primenv_mutex); return; } @@ -396,8 +498,9 @@ prime_env_iter(void) } else { start++; - (void) hv_store(envhv,environ[j],start - environ[j] - 1, - newSVpv(start,0),0); + sv = newSVpv(start,0); + SvTAINTED_on(sv); + (void) hv_store(envhv,environ[j],start - environ[j] - 1,sv,0); } } continue; @@ -486,7 +589,9 @@ prime_env_iter(void) continue; } PERL_HASH(hash,key,keylen); - hv_store(envhv,key,keylen,newSVpvn(cp2,cp1 - cp2 + 1),hash); + sv = newSVpvn(cp2,cp1 - cp2 + 1); + SvTAINTED_on(sv); + hv_store(envhv,key,keylen,sv,hash); hv_store(seenhv,key,keylen,&PL_sv_yes,hash); } if (cmddsc.dsc$w_length == 14) { /* We just read LNM$FILE_DEV */ @@ -496,7 +601,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); } } } @@ -519,7 +626,7 @@ prime_env_iter(void) * Like setenv() returns 0 for success, non-zero on error. */ int -vmssetenv(char *lnm, char *eqv, struct dsc$descriptor_s **tabvec) +Perl_vmssetenv(pTHX_ char *lnm, char *eqv, struct dsc$descriptor_s **tabvec) { char uplnm[LNM$C_NAMLENGTH], *cp1, *cp2; unsigned short int curtab, ivlnm = 0, ivsym = 0, ivenv = 0; @@ -529,7 +636,6 @@ vmssetenv(char *lnm, char *eqv, struct dsc$descriptor_s **tabvec) tmpdsc = {6,DSC$K_DTYPE_T,DSC$K_CLASS_S,0}; $DESCRIPTOR(crtlenv,"CRTL_ENV"); $DESCRIPTOR(clisym,"CLISYM"); $DESCRIPTOR(local,"_LOCAL"); - dTHX; for (cp1 = lnm, cp2 = uplnm; *cp1; cp1++, cp2++) { *cp2 = _toupper(*cp1); @@ -549,7 +655,7 @@ vmssetenv(char *lnm, char *eqv, struct dsc$descriptor_s **tabvec) if ((cp1 = strchr(environ[i],'=')) && !strncmp(environ[i],lnm,cp1 - environ[i])) { #ifdef HAS_SETENV - return setenv(lnm,eqv,1) ? vaxc$errno : 0; + return setenv(lnm,"",1) ? vaxc$errno : 0; } } ivenv = 1; retsts = SS$_NOLOGNAM; @@ -609,6 +715,12 @@ 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); + } + } retsts = lib$set_logical(&lnmdsc,&eqvdsc,tabvec[0],0,0); } } @@ -651,19 +763,56 @@ vmssetenv(char *lnm, char *eqv, struct dsc$descriptor_s **tabvec) void Perl_my_setenv(pTHX_ char *lnm,char *eqv) { - if (lnm && *lnm && strlen(lnm) == 7) { - char uplnm[8]; - int i; - for (i = 0; lnm[i]; i++) uplnm[i] = _toupper(lnm[i]); - if (!strcmp(uplnm,"DEFAULT")) { - if (eqv && *eqv) chdir(eqv); - return; + if (lnm && *lnm) { + int len = strlen(lnm); + if (len == 7) { + char uplnm[8]; + int i; + for (i = 0; lnm[i]; i++) uplnm[i] = _toupper(lnm[i]); + if (!strcmp(uplnm,"DEFAULT")) { + if (eqv && *eqv) chdir(eqv); + return; + } + } +#ifndef RTL_USES_UTC + if (len == 6 || len == 2) { + char uplnm[7]; + int i; + for (i = 0; lnm[i]; i++) uplnm[i] = _toupper(lnm[i]); + uplnm[len] = '\0'; + if (!strcmp(uplnm,"UCX$TZ")) tz_updated = 1; + if (!strcmp(uplnm,"TZ")) tz_updated = 1; } +#endif } (void) vmssetenv(lnm,eqv,NULL); } /*}}}*/ +/*{{{static void vmssetuserlnm(char *name, char *eqv); +/* vmssetuserlnm + * sets a user-mode logical in the process logical name table + * used for redirection of sys$error + */ +void +Perl_vmssetuserlnm(pTHX_ char *name, char *eqv) +{ + $DESCRIPTOR(d_tab, "LNM$PROCESS"); + struct dsc$descriptor_d d_name = {0,DSC$K_DTYPE_T,DSC$K_CLASS_D,0}; + unsigned long int iss, attr = LNM$M_CONFINE; + unsigned char acmode = PSL$C_USER; + struct itmlst_3 lnmlst[2] = {{0, LNM$_STRING, 0, 0}, + {0, 0, 0, 0}}; + d_name.dsc$a_pointer = name; + d_name.dsc$w_length = strlen(name); + + lnmlst[0].buflen = strlen(eqv); + lnmlst[0].bufadr = eqv; + + iss = sys$crelnm(&attr,&d_tab,&d_name,&acmode,lnmlst); + if (!(iss&1)) lib$signal(iss); +} +/*}}}*/ /*{{{ char *my_crypt(const char *textpasswd, const char *usrname)*/ @@ -677,7 +826,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 @@ -702,8 +851,7 @@ my_crypt(const char *textpasswd, const char *usrname) usrdsc.dsc$a_pointer = usrname; if (!((sts = sys$getuai(0, 0, &usrdsc, uailst, 0, 0, 0)) & 1)) { switch (sts) { - case SS$_NOGRPPRV: - case SS$_NOSYSPRV: + case SS$_NOGRPPRV: case SS$_NOSYSPRV: set_errno(EACCES); break; case RMS$_RNF: @@ -728,13 +876,13 @@ my_crypt(const char *textpasswd, const char *usrname) /*}}}*/ -static char *do_rmsexpand(char *, char *, int, char *, unsigned); -static char *do_fileify_dirspec(char *, char *, int); -static char *do_tovmsspec(char *, char *, int); +static char *mp_do_rmsexpand(pTHX_ char *, char *, int, char *, unsigned); +static char *mp_do_fileify_dirspec(pTHX_ char *, char *, int); +static char *mp_do_tovmsspec(pTHX_ char *, char *, int); /*{{{int do_rmdir(char *name)*/ int -do_rmdir(char *name) +Perl_do_rmdir(pTHX_ char *name) { char dirfile[NAM$C_MAXRSS+1]; int retval; @@ -758,12 +906,11 @@ do_rmdir(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; @@ -802,15 +949,13 @@ kill_file(char *name) newace.myace$l_ident = oldace.myace$l_ident; if (!((aclsts = sys$change_acl(0,&type,&fildsc,lcklst,0,0,0)) & 1)) { switch (aclsts) { - case RMS$_FNF: - case RMS$_DNF: - case RMS$_DIR: - case SS$_NOSUCHOBJECT: + case RMS$_FNF: case RMS$_DNF: case SS$_NOSUCHOBJECT: set_errno(ENOENT); break; + case RMS$_DIR: + set_errno(ENOTDIR); break; case RMS$_DEV: set_errno(ENODEV); break; - case RMS$_SYN: - case SS$_INVFILFOROP: + case RMS$_SYN: case SS$_INVFILFOROP: set_errno(EINVAL); break; case RMS$_PRV: set_errno(EACCES); break; @@ -862,10 +1007,12 @@ 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; /* CRTL mkdir() doesn't tolerate trailing /, since that implies * null file name/type. However, it's commonplace under Unix, @@ -881,23 +1028,104 @@ my_mkdir(char *dir, Mode_t mode) } /* end of my_mkdir */ /*}}}*/ +/*{{{int my_chdir(char *)*/ +int +Perl_my_chdir(pTHX_ char *dir) +{ + STRLEN dirlen = strlen(dir); + + /* zero length string sometimes gives ACCVIO */ + if (dirlen == 0) return -1; + + /* some versions of CRTL chdir() doesn't tolerate trailing /, since + * that implies + * null file name/type. However, it's commonplace under Unix, + * so we'll allow it for a gain in portability. + */ + if (dir[dirlen-1] == '/') { + char *newdir = savepvn(dir,dirlen-1); + int ret = chdir(newdir); + Safefree(newdir); + return ret; + } + else return chdir(dir); +} /* end of my_chdir */ +/*}}}*/ + + +/*{{{FILE *my_tmpfile()*/ +FILE * +my_tmpfile(void) +{ + FILE *fp; + char *cp; + + if ((fp = tmpfile())) return fp; + + New(1323,cp,L_tmpnam+24,char); + strcpy(cp,"Sys$Scratch:"); + tmpnam(cp+strlen(cp)); + strcat(cp,".Perltmp"); + fp = fopen(cp,"w+","fop=dlt"); + Safefree(cp); + return fp; +} +/*}}}*/ + + +#ifndef HOMEGROWN_POSIX_SIGNALS +/* + * The C RTL's sigaction fails to check for invalid signal numbers so we + * help it out a bit. The docs are correct, but the actual routine doesn't + * do what the docs say it will. + */ +/*{{{int Perl_my_sigaction (pTHX_ int, const struct sigaction*, struct sigaction*);*/ +int +Perl_my_sigaction (pTHX_ int sig, const struct sigaction* act, + struct sigaction* oact) +{ + if (sig == SIGKILL || sig == SIGSTOP || sig == SIGCONT) { + SETERRNO(EINVAL, SS$_INVARG); + return -1; + } + return sigaction(sig, act, oact); +} +/*}}}*/ +#endif + +/* default piping mailbox size */ +#define PERL_BUFSIZ 512 + static void -create_mbx(unsigned short int *chan, struct dsc$descriptor_s *namdsc) +create_mbx(pTHX_ unsigned short int *chan, struct dsc$descriptor_s *namdsc) { - static unsigned long int mbxbufsiz; - long int syiitm = SYI$_MAXBUF, dviitm = DVI$_DEVNAM; - dTHX; + unsigned long int mbxbufsiz; + static unsigned long int syssize = 0; + unsigned long int dviitm = DVI$_DEVNAM; + char csize[LNM$C_NAMLENGTH+1]; - if (!mbxbufsiz) { + if (!syssize) { + unsigned long syiitm = SYI$_MAXBUF; /* - * Get the SYSGEN parameter MAXBUF, and the smaller of it and the - * preprocessor consant BUFSIZ from stdio.h as the size of the - * 'pipe' mailbox. + * Get the SYSGEN parameter MAXBUF + * + * If the logical 'PERL_MBX_SIZE' is defined + * use the value of the logical instead of PERL_BUFSIZ, but + * keep the size between 128 and MAXBUF. + * */ - _ckvmssts(lib$getsyi(&syiitm, &mbxbufsiz, 0, 0, 0, 0)); - if (mbxbufsiz > BUFSIZ) mbxbufsiz = BUFSIZ; + _ckvmssts(lib$getsyi(&syiitm, &syssize, 0, 0, 0, 0)); } + + if (vmstrnenv("PERL_MBX_SIZE", csize, 0, fildev, 0)) { + mbxbufsiz = atoi(csize); + } else { + mbxbufsiz = PERL_BUFSIZ; + } + if (mbxbufsiz < 128) mbxbufsiz = 128; + if (mbxbufsiz > syssize) mbxbufsiz = syssize; + _ckvmssts(sys$crembx(0,chan,mbxbufsiz,mbxbufsiz,0,0,0)); _ckvmssts(lib$getdvi(&dviitm, chan, NULL, NULL, namdsc, &namdsc->dsc$w_length)); @@ -905,15 +1133,82 @@ 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; + pInfo next; PerlIO *fp; /* stdio file pointer to pipe mailbox */ 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 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 @@ -925,46 +1220,23 @@ struct exit_control_block unsigned long int exit_status; }; -static struct pipe_details *open_pipes = NULL; -static $DESCRIPTOR(nl_desc, "NL:"); -static int waitpid_asleep = 0; +#define RETRY_DELAY "0 ::0.20" +#define MAX_RETRY 50 -/* 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; +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:"); - 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; /* first we try sending an EOF...ignore if doesn't work, make sure we @@ -974,9 +1246,14 @@ pipe_exit_routine() info = open_pipes; while (info) { - if (info->mode != 'r' && !info->done) { - if (pipe_eof(info->fp, 1) & 1) did_stuff = 1; + int need_eof; + _ckvmssts(sys$setast(0)); + 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)); + did_stuff = 1; } + _ckvmssts(sys$setast(1)); info = info->next; } if (did_stuff) sleep(1); /* wait for EOF to have an effect */ @@ -984,22 +1261,25 @@ pipe_exit_routine() did_stuff = 0; info = open_pipes; while (info) { + _ckvmssts(sys$setast(0)); if (!info->done) { /* Tap them gently on the shoulder . . .*/ sts = sys$forcex(&info->pid,0,&abort); if (!(sts&1) && sts != SS$_NONEXPR) _ckvmssts(sts); did_stuff = 1; } + _ckvmssts(sys$setast(1)); info = info->next; } if (did_stuff) sleep(1); /* wait for them to respond */ info = open_pipes; while (info) { + _ckvmssts(sys$setast(0)); 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; } @@ -1014,97 +1294,971 @@ 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; + + while (i) { + if (i == info) break; + i = i->next; + } + if (!i) return; /* unlinked, probably freed too */ + + info->completion &= 0x0FFFFFFF; /* strip off "control" field */ + info->done = TRUE; + +/* + Writing to subprocess ... + if my_pclose'd: EOF already sent, should shutdown chan_in part of pipe + + chan_out may be waiting for "done" flag, or hung waiting + for i/o completion to child...cancel the i/o. This will + put it into "snarf mode" (done but no EOF yet) that discards + input. + + Output from subprocess (stdout, stderr) needs to be flushed and + shut down. We try sending an EOF, but if the mbx is full the pipe + routine should still catch the "shut_on_empty" flag, telling it to + use immediate-style reads so that "mbx empty" -> EOF. + + +*/ + if (info->in && !info->in_done) { /* only for mode=w */ + if (info->in->shut_on_empty && info->in->need_wake) { + info->in->need_wake = FALSE; + _ckvmssts_noperl(sys$dclast(pipe_tochild2_ast,info->in,0)); + } else { + _ckvmssts_noperl(sys$cancel(info->in->chan_out)); + } + } + + if (info->out && !info->out_done) { /* were we also piping output? */ + info->out->shut_on_empty = TRUE; + iss = sys$qio(0,info->out->chan_in,IO$_WRITEOF|IO$M_NORSWAIT, 0, 0, 0, 0, 0, 0, 0, 0, 0); + if (iss == SS$_MBFULL) iss = SS$_NORMAL; + _ckvmssts_noperl(iss); + } + + if (info->err && !info->err_done) { /* we were piping stderr */ + info->err->shut_on_empty = TRUE; + iss = sys$qio(0,info->err->chan_in,IO$_WRITEOF|IO$M_NORSWAIT, 0, 0, 0, 0, 0, 0, 0, 0, 0); + if (iss == SS$_MBFULL) iss = SS$_NORMAL; + _ckvmssts_noperl(iss); } + _ckvmssts_noperl(sys$setef(pipe_ef)); + +} + +static unsigned long int setup_cmddsc(pTHX_ char *cmd, int check_img); +static void vms_execfree(pTHX); + +/* + we actually differ from vmstrnenv since we use this to + get the RMS IFI to check if SYS$OUTPUT and SYS$ERROR *really* + are pointing to the same thing +*/ + +static unsigned short +popen_translate(pTHX_ char *logical, char *result) +{ + int iss; + $DESCRIPTOR(d_table,"LNM$PROCESS_TABLE"); + $DESCRIPTOR(d_log,""); + struct _il3 { + unsigned short length; + unsigned short code; + char * buffer_addr; + unsigned short *retlenaddr; + } itmlst[2]; + unsigned short l, ifi; + + d_log.dsc$a_pointer = logical; + d_log.dsc$w_length = strlen(logical); + + itmlst[0].code = LNM$_STRING; + itmlst[0].length = 255; + itmlst[0].buffer_addr = result; + itmlst[0].retlenaddr = &l; + + itmlst[1].code = 0; + itmlst[1].length = 0; + itmlst[1].buffer_addr = 0; + itmlst[1].retlenaddr = 0; + + iss = sys$trnlnm(0, &d_table, &d_log, 0, itmlst); + if (iss == SS$_NOLOGNAM) { + iss = SS$_NORMAL; + l = 0; + } + if (!(iss&1)) lib$signal(iss); + result[l] = '\0'; +/* + logicals for PPFs have a 4 byte prefix ESC+NUL+(RMS IFI) + strip it off and return the ifi, if any +*/ + ifi = 0; + if (result[0] == 0x1b && result[1] == 0x00) { + memcpy(&ifi,result+2,2); + strcpy(result,result+4); + } + return ifi; /* this is the RMS internal file id */ +} + +#define MAX_DCL_SYMBOL 255 +static void pipe_infromchild_ast(pPipe p); + +/* + I'm using LIB$(GET|FREE)_VM here so that we can allocate and deallocate + inside an AST routine without worrying about reentrancy and which Perl + memory allocator is being used. + + We read data and queue up the buffers, then spit them out one at a + time to the output mailbox when the output mailbox is ready for one. + +*/ +#define INITIAL_TOCHILDQUEUE 2 + +static pPipe +pipe_tochild_setup(pTHX_ char *rmbx, char *wmbx) +{ + pPipe p; + pCBuf b; + char mbx1[64], mbx2[64]; + struct dsc$descriptor_s d_mbx1 = {sizeof mbx1, DSC$K_DTYPE_T, + DSC$K_CLASS_S, mbx1}, + d_mbx2 = {sizeof mbx2, DSC$K_DTYPE_T, + DSC$K_CLASS_S, mbx2}; + unsigned int dviitm = DVI$_DEVBUFSIZ; + int j, n; + + New(1368, p, 1, Pipe); + + create_mbx(aTHX_ &p->chan_in , &d_mbx1); + create_mbx(aTHX_ &p->chan_out, &d_mbx2); + _ckvmssts(lib$getdvi(&dviitm, &p->chan_in, 0, &p->bufsize)); + + p->buf = 0; + p->shut_on_empty = FALSE; + p->need_wake = FALSE; + p->type = 0; + p->retry = 0; + p->iosb.status = SS$_NORMAL; + p->iosb2.status = SS$_NORMAL; + p->free = RQE_ZERO; + p->wait = RQE_ZERO; + p->curr = 0; + p->curr2 = 0; + p->info = 0; +#ifdef PERL_IMPLICIT_CONTEXT + p->thx = aTHX; +#endif + + n = sizeof(CBuf) + p->bufsize; + + for (j = 0; j < INITIAL_TOCHILDQUEUE; j++) { + _ckvmssts(lib$get_vm(&n, &b)); + b->buf = (char *) b + sizeof(CBuf); + _ckvmssts(lib$insqhi(b, &p->free)); + } + + pipe_tochild2_ast(p); + pipe_tochild1_ast(p); + strcpy(wmbx, mbx1); + strcpy(rmbx, mbx2); + return p; } +/* reads the MBX Perl is writing, and queues */ + +static void +pipe_tochild1_ast(pPipe p) +{ + pCBuf b = p->curr; + int iss = p->iosb.status; + int eof = (iss == SS$_ENDOFFILE); +#ifdef PERL_IMPLICIT_CONTEXT + pTHX = p->thx; +#endif + + if (p->retry) { + if (eof) { + p->shut_on_empty = TRUE; + b->eof = TRUE; + _ckvmssts(sys$dassgn(p->chan_in)); + } else { + _ckvmssts(iss); + } + + b->eof = eof; + b->size = p->iosb.count; + _ckvmssts(lib$insqhi(b, &p->wait)); + if (p->need_wake) { + p->need_wake = FALSE; + _ckvmssts(sys$dclast(pipe_tochild2_ast,p,0)); + } + } else { + p->retry = 1; /* initial call */ + } + + if (eof) { /* flush the free queue, return when done */ + int n = sizeof(CBuf) + p->bufsize; + while (1) { + iss = lib$remqti(&p->free, &b); + if (iss == LIB$_QUEWASEMP) return; + _ckvmssts(iss); + _ckvmssts(lib$free_vm(&n, &b)); + } + } + + iss = lib$remqti(&p->free, &b); + if (iss == LIB$_QUEWASEMP) { + int n = sizeof(CBuf) + p->bufsize; + _ckvmssts(lib$get_vm(&n, &b)); + b->buf = (char *) b + sizeof(CBuf); + } else { + _ckvmssts(iss); + } + + p->curr = b; + iss = sys$qio(0,p->chan_in, + IO$_READVBLK|(p->shut_on_empty ? IO$M_NOWAIT : 0), + &p->iosb, + pipe_tochild1_ast, p, b->buf, p->bufsize, 0, 0, 0, 0); + if (iss == SS$_ENDOFFILE && p->shut_on_empty) iss = SS$_NORMAL; + _ckvmssts(iss); +} + + +/* writes queued buffers to output, waits for each to complete before + doing the next */ + +static void +pipe_tochild2_ast(pPipe p) +{ + pCBuf b = p->curr2; + int iss = p->iosb2.status; + int n = sizeof(CBuf) + p->bufsize; + int done = (p->info && p->info->done) || + iss == SS$_CANCEL || iss == SS$_ABORT; +#if defined(PERL_IMPLICIT_CONTEXT) + pTHX = p->thx; +#endif + + do { + if (p->type) { /* type=1 has old buffer, dispose */ + if (p->shut_on_empty) { + _ckvmssts(lib$free_vm(&n, &b)); + } else { + _ckvmssts(lib$insqhi(b, &p->free)); + } + p->type = 0; + } + + iss = lib$remqti(&p->wait, &b); + if (iss == LIB$_QUEWASEMP) { + if (p->shut_on_empty) { + if (done) { + _ckvmssts(sys$dassgn(p->chan_out)); + *p->pipe_done = TRUE; + _ckvmssts(sys$setef(pipe_ef)); + } else { + _ckvmssts(sys$qio(0,p->chan_out,IO$_WRITEOF, + &p->iosb2, pipe_tochild2_ast, p, 0, 0, 0, 0, 0, 0)); + } + return; + } + p->need_wake = TRUE; + return; + } + _ckvmssts(iss); + p->type = 1; + } while (done); + + + p->curr2 = b; + if (b->eof) { + _ckvmssts(sys$qio(0,p->chan_out,IO$_WRITEOF, + &p->iosb2, pipe_tochild2_ast, p, 0, 0, 0, 0, 0, 0)); + } else { + _ckvmssts(sys$qio(0,p->chan_out,IO$_WRITEVBLK, + &p->iosb2, pipe_tochild2_ast, p, b->buf, b->size, 0, 0, 0, 0)); + } + + return; + +} + + +static pPipe +pipe_infromchild_setup(pTHX_ char *rmbx, char *wmbx) +{ + pPipe p; + char mbx1[64], mbx2[64]; + struct dsc$descriptor_s d_mbx1 = {sizeof mbx1, DSC$K_DTYPE_T, + DSC$K_CLASS_S, mbx1}, + d_mbx2 = {sizeof mbx2, DSC$K_DTYPE_T, + DSC$K_CLASS_S, mbx2}; + unsigned int dviitm = DVI$_DEVBUFSIZ; + + New(1367, p, 1, Pipe); + create_mbx(aTHX_ &p->chan_in , &d_mbx1); + create_mbx(aTHX_ &p->chan_out, &d_mbx2); + + _ckvmssts(lib$getdvi(&dviitm, &p->chan_in, 0, &p->bufsize)); + New(1367, p->buf, p->bufsize, char); + p->shut_on_empty = FALSE; + p->info = 0; + p->type = 0; + p->iosb.status = SS$_NORMAL; +#if defined(PERL_IMPLICIT_CONTEXT) + p->thx = aTHX; +#endif + pipe_infromchild_ast(p); + + strcpy(wmbx, mbx1); + strcpy(rmbx, mbx2); + return p; +} + +static void +pipe_infromchild_ast(pPipe p) +{ + int iss = p->iosb.status; + int eof = (iss == SS$_ENDOFFILE); + int myeof = (eof && (p->iosb.dvispec == mypid || p->iosb.dvispec == 0)); + int kideof = (eof && (p->iosb.dvispec == p->info->pid)); +#if defined(PERL_IMPLICIT_CONTEXT) + pTHX = p->thx; +#endif + + if (p->info && p->info->closing && p->chan_out) { /* output shutdown */ + _ckvmssts(sys$dassgn(p->chan_out)); + p->chan_out = 0; + } + + /* read completed: + input shutdown if EOF from self (done or shut_on_empty) + output shutdown if closing flag set (my_pclose) + send data/eof from child or eof from self + otherwise, re-read (snarf of data from child) + */ + + if (p->type == 1) { + p->type = 0; + if (myeof && p->chan_in) { /* input shutdown */ + _ckvmssts(sys$dassgn(p->chan_in)); + p->chan_in = 0; + } + + if (p->chan_out) { + if (myeof || kideof) { /* pass EOF to parent */ + _ckvmssts(sys$qio(0,p->chan_out,IO$_WRITEOF, &p->iosb, + pipe_infromchild_ast, p, + 0, 0, 0, 0, 0, 0)); + return; + } else if (eof) { /* eat EOF --- fall through to read*/ + + } else { /* transmit data */ + _ckvmssts(sys$qio(0,p->chan_out,IO$_WRITEVBLK,&p->iosb, + pipe_infromchild_ast,p, + p->buf, p->iosb.count, 0, 0, 0, 0)); + return; + } + } + } + + /* everything shut? flag as done */ + + if (!p->chan_in && !p->chan_out) { + *p->pipe_done = TRUE; + _ckvmssts(sys$setef(pipe_ef)); + return; + } + + /* write completed (or read, if snarfing from child) + if still have input active, + queue read...immediate mode if shut_on_empty so we get EOF if empty + otherwise, + check if Perl reading, generate EOFs as needed + */ + + if (p->type == 0) { + p->type = 1; + if (p->chan_in) { + iss = sys$qio(0,p->chan_in,IO$_READVBLK|(p->shut_on_empty ? IO$M_NOW : 0),&p->iosb, + pipe_infromchild_ast,p, + p->buf, p->bufsize, 0, 0, 0, 0); + if (p->shut_on_empty && iss == SS$_ENDOFFILE) iss = SS$_NORMAL; + _ckvmssts(iss); + } else { /* send EOFs for extra reads */ + p->iosb.status = SS$_ENDOFFILE; + p->iosb.dvispec = 0; + _ckvmssts(sys$qio(0,p->chan_out,IO$_SETMODE|IO$M_READATTN, + 0, 0, 0, + pipe_infromchild_ast, p, 0, 0, 0, 0)); + } + } +} + +static pPipe +pipe_mbxtofd_setup(pTHX_ int fd, char *out) +{ + pPipe p; + char mbx[64]; + unsigned long dviitm = DVI$_DEVBUFSIZ; + struct stat s; + struct dsc$descriptor_s d_mbx = {sizeof mbx, DSC$K_DTYPE_T, + DSC$K_CLASS_S, mbx}; + + /* things like terminals and mbx's don't need this filter */ + if (fd && fstat(fd,&s) == 0) { + unsigned long dviitm = DVI$_DEVCHAR, devchar; + struct dsc$descriptor_s d_dev = {strlen(s.st_dev), DSC$K_DTYPE_T, + DSC$K_CLASS_S, s.st_dev}; + + _ckvmssts(lib$getdvi(&dviitm,0,&d_dev,&devchar,0,0)); + if (!(devchar & DEV$M_DIR)) { /* non directory structured...*/ + strcpy(out, s.st_dev); + return 0; + } + } + + New(1366, p, 1, Pipe); + p->fd_out = dup(fd); + create_mbx(aTHX_ &p->chan_in, &d_mbx); + _ckvmssts(lib$getdvi(&dviitm, &p->chan_in, 0, &p->bufsize)); + New(1366, p->buf, p->bufsize+1, char); + p->shut_on_empty = FALSE; + p->retry = 0; + p->info = 0; + strcpy(out, mbx); + + _ckvmssts(sys$qio(0, p->chan_in, IO$_READVBLK, &p->iosb, + pipe_mbxtofd_ast, p, + p->buf, p->bufsize, 0, 0, 0, 0)); + + return p; +} + +static void +pipe_mbxtofd_ast(pPipe p) +{ + int iss = p->iosb.status; + int done = p->info->done; + int iss2; + int eof = (iss == SS$_ENDOFFILE); + int myeof = eof && ((p->iosb.dvispec == mypid)||(p->iosb.dvispec == 0)); + int err = !(iss&1) && !eof; +#if defined(PERL_IMPLICIT_CONTEXT) + pTHX = p->thx; +#endif + + if (done && myeof) { /* end piping */ + close(p->fd_out); + sys$dassgn(p->chan_in); + *p->pipe_done = TRUE; + _ckvmssts(sys$setef(pipe_ef)); + return; + } + + if (!err && !eof) { /* good data to send to file */ + p->buf[p->iosb.count] = '\n'; + iss2 = write(p->fd_out, p->buf, p->iosb.count+1); + if (iss2 < 0) { + p->retry++; + if (p->retry < MAX_RETRY) { + _ckvmssts(sys$setimr(0,delaytime,pipe_mbxtofd_ast,p)); + return; + } + } + p->retry = 0; + } else if (err) { + _ckvmssts(iss); + } + + + iss = sys$qio(0, p->chan_in, IO$_READVBLK|(p->shut_on_empty ? IO$M_NOW : 0), &p->iosb, + pipe_mbxtofd_ast, p, + p->buf, p->bufsize, 0, 0, 0, 0); + if (p->shut_on_empty && (iss == SS$_ENDOFFILE)) iss = SS$_NORMAL; + _ckvmssts(iss); +} + + +typedef struct _pipeloc PLOC; +typedef struct _pipeloc* pPLOC; + +struct _pipeloc { + pPLOC next; + char dir[NAM$C_MAXRSS+1]; +}; +static pPLOC head_PLOC = 0; + +void +free_pipelocs(pTHX_ void *head) +{ + pPLOC p, pnext; + + p = (pPLOC) head; + while (p) { + pnext = p->next; + Safefree(p); + p = pnext; + } +} + +static void +store_pipelocs(pTHX) +{ + int i; + pPLOC p; + AV *av = GvAVn(PL_incgv); + SV *dirsv; + GV *gv; + char *dir, *x; + char *unixdir; + char temp[NAM$C_MAXRSS+1]; + STRLEN n_a; + +/* the . directory from @INC comes last */ + + New(1370,p,1,PLOC); + p->next = head_PLOC; + head_PLOC = p; + strcpy(p->dir,"./"); + +/* get the directory from $^X */ + + if (PL_origargv && PL_origargv[0]) { /* maybe nul if embedded Perl */ + strcpy(temp, PL_origargv[0]); + x = strrchr(temp,']'); + if (x) x[1] = '\0'; + + if ((unixdir = tounixpath(temp, Nullch)) != Nullch) { + New(1370,p,1,PLOC); + p->next = head_PLOC; + head_PLOC = p; + strncpy(p->dir,unixdir,sizeof(p->dir)-1); + p->dir[NAM$C_MAXRSS] = '\0'; + } + } + +/* reverse order of @INC entries, skip "." since entered above */ + + for (i = 0; i <= AvFILL(av); i++) { + dirsv = *av_fetch(av,i,TRUE); + + if (SvROK(dirsv)) continue; + dir = SvPVx(dirsv,n_a); + if (strcmp(dir,".") == 0) continue; + if ((unixdir = tounixpath(dir, Nullch)) == Nullch) + continue; + + New(1370,p,1,PLOC); + p->next = head_PLOC; + head_PLOC = p; + strncpy(p->dir,unixdir,sizeof(p->dir)-1); + p->dir[NAM$C_MAXRSS] = '\0'; + } + +/* most likely spot (ARCHLIB) put first in the list */ + +#ifdef ARCHLIB_EXP + if ((unixdir = tounixpath(ARCHLIB_EXP, Nullch)) != Nullch) { + New(1370,p,1,PLOC); + p->next = head_PLOC; + head_PLOC = p; + strncpy(p->dir,unixdir,sizeof(p->dir)-1); + p->dir[NAM$C_MAXRSS] = '\0'; + } +#endif + Perl_call_atexit(aTHX_ &free_pipelocs, head_PLOC); +} + + +static char * +find_vmspipe(pTHX) +{ + static int vmspipe_file_status = 0; + static char vmspipe_file[NAM$C_MAXRSS+1]; + + /* already found? Check and use ... need read+execute permission */ + + if (vmspipe_file_status == 1) { + if (cando_by_name(S_IRUSR, 0, vmspipe_file) + && cando_by_name(S_IXUSR, 0, vmspipe_file)) { + return vmspipe_file; + } + vmspipe_file_status = 0; + } + + /* scan through stored @INC, $^X */ + + if (vmspipe_file_status == 0) { + char file[NAM$C_MAXRSS+1]; + pPLOC p = head_PLOC; + + while (p) { + strcpy(file, p->dir); + strncat(file, "vmspipe.com",NAM$C_MAXRSS); + file[NAM$C_MAXRSS] = '\0'; + p = p->next; + + if (!do_tovmsspec(file,vmspipe_file,0)) continue; + + if (cando_by_name(S_IRUSR, 0, vmspipe_file) + && cando_by_name(S_IXUSR, 0, vmspipe_file)) { + vmspipe_file_status = 1; + return vmspipe_file; + } + } + vmspipe_file_status = -1; /* failed, use tempfiles */ + } + + return 0; +} + +static FILE * +vmspipe_tempfile(pTHX) +{ + char file[NAM$C_MAXRSS+1]; + FILE *fp; + static int index = 0; + stat_t s0, s1; + + /* create a tempfile */ + + /* we can't go from W, shr=get to R, shr=get without + an intermediate vulnerable state, so don't bother trying... + + and lib$spawn doesn't shr=put, so have to close the write + + So... match up the creation date/time and the FID to + make sure we're dealing with the same file + + */ + + index++; + sprintf(file,"sys$scratch:perlpipe_%08.8x_%d.com",mypid,index); + fp = fopen(file,"w"); + if (!fp) { + sprintf(file,"sys$login:perlpipe_%08.8x_%d.com",mypid,index); + fp = fopen(file,"w"); + if (!fp) { + sprintf(file,"sys$disk:[]perlpipe_%08.8x_%d.com",mypid,index); + fp = fopen(file,"w"); + } + } + if (!fp) return 0; /* we're hosed */ + + fprintf(fp,"$! 'f$verify(0)\n"); + fprintf(fp,"$! --- protect against nonstandard definitions ---\n"); + fprintf(fp,"$ perl_cfile = f$environment(\"procedure\")\n"); + fprintf(fp,"$ perl_define = \"define/nolog\"\n"); + fprintf(fp,"$ perl_on = \"set noon\"\n"); + fprintf(fp,"$ perl_exit = \"exit\"\n"); + fprintf(fp,"$ perl_del = \"delete\"\n"); + fprintf(fp,"$ pif = \"if\"\n"); + fprintf(fp,"$! --- define i/o redirection (sys$output set by lib$spawn)\n"); + fprintf(fp,"$ pif perl_popen_in .nes. \"\" then perl_define/user/name_attributes=confine sys$input 'perl_popen_in'\n"); + fprintf(fp,"$ pif perl_popen_err .nes. \"\" then perl_define/user/name_attributes=confine sys$error 'perl_popen_err'\n"); + fprintf(fp,"$ pif perl_popen_out .nes. \"\" then perl_define sys$output 'perl_popen_out'\n"); + fprintf(fp,"$ cmd = perl_popen_cmd\n"); + fprintf(fp,"$! --- get rid of global symbols\n"); + fprintf(fp,"$ perl_del/symbol/global perl_popen_in\n"); + fprintf(fp,"$ perl_del/symbol/global perl_popen_err\n"); + fprintf(fp,"$ perl_del/symbol/global perl_popen_out\n"); + fprintf(fp,"$ perl_del/symbol/global perl_popen_cmd\n"); + fprintf(fp,"$ perl_on\n"); + fprintf(fp,"$ 'cmd\n"); + fprintf(fp,"$ perl_status = $STATUS\n"); + fprintf(fp,"$ perl_del 'perl_cfile'\n"); + fprintf(fp,"$ perl_exit 'perl_status'\n"); + fsync(fileno(fp)); + + fgetname(fp, file, 1); + fstat(fileno(fp), &s0); + fclose(fp); + + fp = fopen(file,"r","shr=get"); + if (!fp) return 0; + fstat(fileno(fp), &s1); + + if (s0.st_ino[0] != s1.st_ino[0] || + s0.st_ino[1] != s1.st_ino[1] || + s0.st_ino[2] != s1.st_ino[2] || + s0.st_ctime != s1.st_ctime ) { + fclose(fp); + return 0; + } + + return fp; +} + + + static PerlIO * -safe_popen(char *cmd, char *mode) +safe_popen(pTHX_ char *cmd, char *mode) { static int handler_set_up = FALSE; - char mbxname[64]; - unsigned short int chan; - unsigned long int 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, + unsigned long int sts, flags=1; /* nowait - gnu c doesn't allow &1 */ + unsigned int table = LIB$K_CLI_GLOBAL_SYM; + char *p, symbol[MAX_DCL_SYMBOL+1], *vmspipe; + char in[512], out[512], err[512], mbx[512]; + FILE *tpipe = 0; + char tfilebuf[NAM$C_MAXRSS+1]; + pInfo info; + struct dsc$descriptor_s d_symbol= {0, DSC$K_DTYPE_T, + DSC$K_CLASS_S, symbol}; + struct dsc$descriptor_s vmspipedsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0}; + + $DESCRIPTOR(d_sym_cmd,"PERL_POPEN_CMD"); + $DESCRIPTOR(d_sym_in ,"PERL_POPEN_IN"); + $DESCRIPTOR(d_sym_out,"PERL_POPEN_OUT"); + $DESCRIPTOR(d_sym_err,"PERL_POPEN_ERR"); + /* once-per-program initialization... + note that the SETAST calls and the dual test of pipe_ef + makes sure that only the FIRST thread through here does + the initialization...all other threads wait until it's + done. + + Yeah, uglier than a pthread call, it's got all the stuff inline + rather than in a separate routine. + */ - cmddsc.dsc$w_length=strlen(cmd); - cmddsc.dsc$a_pointer=cmd; - if (cmddsc.dsc$w_length > 255) { - set_errno(E2BIG); set_vaxc_errno(CLI$_BUFOVF); - return Nullfp; + 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)); } - New(1301,info,1,struct pipe_details); - - /* create mailbox */ - create_mbx(&chan,&namdsc); + /* see if we can find a VMSPIPE.COM */ - /* open a FILE* onto it */ - info->fp = PerlIO_open(mbxname, mode); - - /* give up other channel onto it */ - _ckvmssts(sys$dassgn(chan)); - - if (!info->fp) + tfilebuf[0] = '@'; + vmspipe = find_vmspipe(aTHX); + if (vmspipe) { + strcpy(tfilebuf+1,vmspipe); + } else { /* uh, oh...we're in tempfile hell */ + tpipe = vmspipe_tempfile(aTHX); + if (!tpipe) { /* a fish popular in Boston */ + if (ckWARN(WARN_PIPE)) { + Perl_warner(aTHX_ WARN_PIPE,"unable to find VMSPIPE.COM for i/o piping"); + } return Nullfp; + } + fgetname(tpipe,tfilebuf+1,1); + } + vmspipedsc.dsc$a_pointer = tfilebuf; + vmspipedsc.dsc$w_length = strlen(tfilebuf); + + sts = setup_cmddsc(aTHX_ cmd,0); + 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_PIPE)) { + Perl_warner(aTHX_ WARN_PIPE,"Can't pipe \"%*s\": %s", strlen(cmd), cmd, Strerror(errno)); + } + return Nullfp; + } + New(1301,info,1,Info); info->mode = *mode; info->done = FALSE; - info->completion=0; + info->completion = 0; + info->closing = FALSE; + info->in = 0; + info->out = 0; + info->err = 0; + info->in_done = TRUE; + info->out_done = TRUE; + info->err_done = TRUE; + in[0] = out[0] = err[0] = '\0'; + + 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; + } + info->fp = PerlIO_open(mbx, mode); + if (!info->fp && info->out) { + sys$cancel(info->out->chan_out); - if (*mode == 'r') { - _ckvmssts(lib$spawn(&cmddsc, &nl_desc, &namdsc, &flags, - 0 /* name */, &info->pid, &info->completion, - 0, popen_completion_ast,info,0,0,0)); - } - else { - _ckvmssts(lib$spawn(&cmddsc, &namdsc, 0 /* sys$output */, &flags, - 0 /* name */, &info->pid, &info->completion, - 0, popen_completion_ast,info,0,0,0)); - } + 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); + 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 { /* piping to subroutine , mode=w*/ + + info->in = pipe_tochild_setup(aTHX_ in,mbx); + info->fp = PerlIO_open(mbx, mode); + 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); + return Nullfp; + } + + + info->out = pipe_mbxtofd_setup(aTHX_ fileno(stdout), out); + if (info->out) { + info->out->pipe_done = &info->out_done; + info->out_done = FALSE; + info->out->info = info; + } - if (!handler_set_up) { - _ckvmssts(sys$dclexh(&pipe_exitblock)); - handler_set_up = TRUE; + info->err = pipe_mbxtofd_setup(aTHX_ fileno(stderr), err); + if (info->err) { + info->err->pipe_done = &info->err_done; + info->err_done = FALSE; + info->err->info = info; + } } + + symbol[MAX_DCL_SYMBOL] = '\0'; + + strncpy(symbol, in, MAX_DCL_SYMBOL); + d_symbol.dsc$w_length = strlen(symbol); + _ckvmssts(lib$set_symbol(&d_sym_in, &d_symbol, &table)); + + strncpy(symbol, err, MAX_DCL_SYMBOL); + d_symbol.dsc$w_length = strlen(symbol); + _ckvmssts(lib$set_symbol(&d_sym_err, &d_symbol, &table)); + + strncpy(symbol, out, MAX_DCL_SYMBOL); + d_symbol.dsc$w_length = strlen(symbol); + _ckvmssts(lib$set_symbol(&d_sym_out, &d_symbol, &table)); + + p = VMScmd.dsc$a_pointer; + while (*p && *p != '\n') p++; + *p = '\0'; /* truncate on \n */ + p = VMScmd.dsc$a_pointer; + while (*p == ' ' || *p == '\t') p++; /* remove leading whitespace */ + if (*p == '$') p++; /* remove leading $ */ + while (*p == ' ' || *p == '\t') p++; + strncpy(symbol, p, MAX_DCL_SYMBOL); + d_symbol.dsc$w_length = strlen(symbol); + _ckvmssts(lib$set_symbol(&d_sym_cmd, &d_symbol, &table)); + + _ckvmssts(sys$setast(0)); info->next=open_pipes; /* prepend to list */ open_pipes=info; + _ckvmssts(sys$setast(1)); + _ckvmssts(lib$spawn(&vmspipedsc, &nl_desc, &nl_desc, &flags, + 0, &info->pid, &info->completion, + 0, popen_completion_ast,info,0,0,0)); + + /* if we were using a tempfile, close it now */ + + if (tpipe) fclose(tpipe); + + /* once the subprocess is spawned, its copied the symbols and + we can get rid of ours */ + + _ckvmssts(lib$delete_symbol(&d_sym_cmd, &table)); + _ckvmssts(lib$delete_symbol(&d_sym_in, &table)); + _ckvmssts(lib$delete_symbol(&d_sym_err, &table)); + _ckvmssts(lib$delete_symbol(&d_sym_out, &table)); + vms_execfree(aTHX); PL_forkprocess = info->pid; return info->fp; } /* end of safe_popen */ -/*{{{ FILE *my_popen(char *cmd, char *mode)*/ -FILE * +/*{{{ PerlIO *my_popen(char *cmd, char *mode)*/ +PerlIO * Perl_my_popen(pTHX_ char *cmd, char *mode) { TAINT_ENV(); TAINT_PROPER("popen"); PERL_FLUSHALL_FOR_CHILD; - return safe_popen(cmd,mode); + return safe_popen(aTHX_ cmd,mode); } /*}}}*/ -/*{{{ I32 my_pclose(FILE *fp)*/ -I32 Perl_my_pclose(pTHX_ FILE *fp) +/*{{{ I32 my_pclose(PerlIO *fp)*/ +I32 Perl_my_pclose(pTHX_ PerlIO *fp) { - struct pipe_details *info, *last = NULL; + pInfo info, last = NULL; unsigned long int retsts; + int done, iss; for (info = open_pipes; info != NULL; last = info, info = info->next) if (info->fp == fp) break; @@ -1117,69 +2271,240 @@ 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. */ - if (info->mode != 'r' && !info->done) pipe_eof(info->fp,0); + * 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)... + */ + + PerlIO_flush(info->fp); /* first, flush data */ + + _ckvmssts(sys$setast(0)); + 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)); PerlIO_close(info->fp); - if (info->done) retsts = info->completion; - else waitpid(info->pid,(int *) &retsts,0); + /* + 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... + */ + + while (!done) { + _ckvmssts(sys$setast(0)); + done = info->done && info->in_done && info->out_done && info->err_done; + if (!done) _ckvmssts(sys$clref(pipe_ef)); + _ckvmssts(sys$setast(1)); + if (!done) _ckvmssts(sys$waitfr(pipe_ef)); + } + retsts = info->completion; /* remove from list of open pipes */ + _ckvmssts(sys$setast(0)); if (last) last->next = info->next; else open_pipes = info->next; + _ckvmssts(sys$setast(1)); + + /* free buffers and structures */ + + if (info->in) { + if (info->in->buf) Safefree(info->in->buf); + Safefree(info->in); + } + if (info->out) { + if (info->out->buf) Safefree(info->out->buf); + Safefree(info->out); + } + if (info->err) { + if (info->err->buf) Safefree(info->err->buf); + Safefree(info->err); + } Safefree(info); return retsts; } /* end of my_pclose() */ -/* sort-of waitpid; use only with popen() */ +#if defined(__CRTL_VER) && __CRTL_VER >= 70100322 + /* Roll our own prototype because we want this regardless of whether + * _VMS_WAIT is defined. + */ + __pid_t __vms_waitpid( __pid_t __pid, int *__stat_loc, int __options ); +#endif +/* sort-of waitpid; special handling of pipe clean-up for subprocesses + created with popen(); otherwise partially emulate waitpid() unless + we have a suitable one from the CRTL that came with VMS 7.2 and later. + Also check processes not considered by the CRTL waitpid(). + */ /*{{{Pid_t my_waitpid(Pid_t pid, int *statusp, int flags)*/ Pid_t -my_waitpid(Pid_t pid, int *statusp, int flags) +Perl_my_waitpid(pTHX_ Pid_t pid, int *statusp, int flags) { - struct pipe_details *info; - dTHX; + pInfo info; + int done; + int sts; + + if (statusp) *statusp = 0; for (info = open_pipes; info != NULL; info = info->next) if (info->pid == pid) break; if (info != NULL) { /* we know about this child */ while (!info->done) { - waitpid_asleep = 1; - sys$hiber(); + _ckvmssts(sys$setast(0)); + done = info->done; + if (!done) _ckvmssts(sys$clref(pipe_ef)); + _ckvmssts(sys$setast(1)); + if (!done) _ckvmssts(sys$waitfr(pipe_ef)); } - *statusp = info->completion; + if (statusp) *statusp = info->completion; return pid; + } - else { /* we haven't heard of this child */ + else { /* this child is not one of our own pipe children */ + +#if defined(__CRTL_VER) && __CRTL_VER >= 70100322 + + /* waitpid() became available in the CRTL as of VMS 7.0, but only + * in 7.2 did we get a version that fills in the VMS completion + * status as Perl has always tried to do. + */ + + sts = __vms_waitpid( pid, statusp, flags ); + + if ( sts == 0 || !(sts == -1 && errno == ECHILD) ) + return sts; + + /* If the real waitpid tells us the child does not exist, we + * fall through here to implement waiting for a child that + * was created by some means other than exec() (say, spawned + * from DCL) or to wait for a process that is not a subprocess + * of the current process. + */ + +#endif /* defined(__CRTL_VER) && __CRTL_VER >= 70100322 */ + $DESCRIPTOR(intdsc,"0 00:00:01"); - unsigned long int ownercode = JPI$_OWNER, ownerpid, mypid; - unsigned long int interval[2],sts; + unsigned long int ownercode = JPI$_OWNER, ownerpid; + unsigned long int pidcode = JPI$_PID, mypid; + unsigned long int interval[2]; + int termination_mbu = 0; + unsigned short qio_iosb[4]; + unsigned int jpi_iosb[2]; + struct itmlst_3 jpilist[3] = { + {sizeof(ownerpid), JPI$_OWNER, &ownerpid, 0}, + {sizeof(termination_mbu), JPI$_TMBU, &termination_mbu, 0}, + { 0, 0, 0, 0} + }; + char trmmbx[NAM$C_DVI+1]; + $DESCRIPTOR(trmmbxdsc,trmmbx); + struct accdef trmmsg; + unsigned short int mbxchan; + + if (pid <= 0) { + /* Sorry folks, we don't presently implement rooting around for + the first child we can find, and we definitely don't want to + pass a pid of -1 to $getjpi, where it is a wildcard operation. + */ + set_errno(ENOTSUP); + return -1; + } + + /* Get the owner of the child so I can warn if it's not mine, plus + * get the termination mailbox. If the process doesn't exist or I + * don't have the privs to look at it, I can go home early. + */ + sts = sys$getjpiw(0,&pid,NULL,&jpilist,&jpi_iosb,NULL,NULL); + if (sts & 1) sts = jpi_iosb[0]; + if (!(sts & 1)) { + switch (sts) { + case SS$_NONEXPR: + set_errno(ECHILD); + break; + case SS$_NOPRIV: + set_errno(EACCES); + break; + default: + _ckvmssts(sts); + } + set_vaxc_errno(sts); + return -1; + } if (ckWARN(WARN_EXEC)) { - _ckvmssts(lib$getjpi(&ownercode,&pid,0,&ownerpid,0,0)); - _ckvmssts(lib$getjpi(&ownercode,0,0,&mypid,0,0)); + /* remind folks they are asking for non-standard waitpid behavior */ + _ckvmssts(lib$getjpi(&pidcode,0,0,&mypid,0,0)); if (ownerpid != mypid) - Perl_warner(aTHX_ WARN_EXEC,"pid %x not a child",pid); + Perl_warner(aTHX_ WARN_EXEC, + "waitpid: process %x is not a child of process %x", + pid,mypid); } - _ckvmssts(sys$bintim(&intdsc,interval)); - while ((sts=lib$getjpi(&ownercode,&pid,0,&ownerpid,0,0)) & 1) { - _ckvmssts(sys$schdwk(0,0,interval,0)); - _ckvmssts(sys$hiber()); + /* It's possible to have a mailbox unit number but no actual mailbox; we + * check for this by assigning a channel to it, which we need anyway. + */ + if (termination_mbu != 0) { + sprintf(trmmbx, "MBA%d:", termination_mbu); + trmmbxdsc.dsc$w_length = strlen(trmmbx); + sts = sys$assign(&trmmbxdsc, &mbxchan, 0, 0); + if (sts == SS$_NOSUCHDEV) { + termination_mbu = 0; /* set up to take "no mailbox" case */ + sts = SS$_NORMAL; + } + _ckvmssts(sts); } - _ckvmssts(sts); - - /* There's no easy way to find the termination status a child we're - * not aware of beforehand. If we're really interested in the future, - * we can go looking for a termination mailbox, or chase after the - * accounting record for the process. + /* If the process doesn't have a termination mailbox, then simply check + * on it once a second until it's not there anymore. */ - *statusp = 0; + if (termination_mbu == 0) { + _ckvmssts(sys$bintim(&intdsc,interval)); + while ((sts=lib$getjpi(&ownercode,&pid,0,&ownerpid,0,0)) & 1) { + _ckvmssts(sys$schdwk(0,0,interval,0)); + _ckvmssts(sys$hiber()); + } + if (sts == SS$_NONEXPR) sts = SS$_NORMAL; + } + else { + /* If we do have a termination mailbox, post reads to it until we get a + * termination message, discarding messages of the wrong type or for other + * processes. If there is a place to put the final status, then do so. + */ + sts = SS$_NORMAL; + while (sts & 1) { + memset((void *) &trmmsg, 0, sizeof(trmmsg)); + sts = sys$qiow(0,mbxchan,IO$_READVBLK,&qio_iosb,0,0, + &trmmsg,ACC$K_TERMLEN,0,0,0,0); + if (sts & 1) sts = qio_iosb[0]; + + if ( sts & 1 + && trmmsg.acc$w_msgtyp == MSG$_DELPROC + && trmmsg.acc$l_pid == pid ) { + + if (statusp) *statusp = trmmsg.acc$l_finalsts; + sts = sys$dassgn(mbxchan); + break; + } + } + } /* termination_mbu ? */ + + _ckvmssts(sts); return pid; - } + + } /* else one of our own pipe children */ } /* end of waitpid() */ /*}}}*/ @@ -1227,10 +2552,10 @@ my_gconvert(double val, int ndig, int trail, char *buf) * rmesexpand() returns the address of the resultant string if * successful, and NULL on error. */ -static char *do_tounixspec(char *, char *, int); +static char *mp_do_tounixspec(pTHX_ char *, char *, int); static char * -do_rmsexpand(char *filespec, char *outbuf, int ts, char *defspec, unsigned opts) +mp_do_rmsexpand(pTHX_ char *filespec, char *outbuf, int ts, char *defspec, unsigned opts) { static char __rmsexpand_retbuf[NAM$C_MAXRSS+1]; char vmsfspec[NAM$C_MAXRSS+1], tmpfspec[NAM$C_MAXRSS+1]; @@ -1274,8 +2599,7 @@ do_rmsexpand(char *filespec, char *outbuf, int ts, char *defspec, unsigned opts) retsts = sys$parse(&myfab,0,0); if (!(retsts & 1)) { mynam.nam$b_nop |= NAM$M_SYNCHK; - if (retsts == RMS$_DNF || retsts == RMS$_DIR || - retsts == RMS$_DEV || retsts == RMS$_DEV) { + if (retsts == RMS$_DNF || retsts == RMS$_DIR || retsts == RMS$_DEV) { retsts = sys$parse(&myfab,0,0); if (retsts & 1) goto expanded; } @@ -1366,9 +2690,9 @@ do_rmsexpand(char *filespec, char *outbuf, int ts, char *defspec, unsigned opts) } /*}}}*/ /* External entry points */ -char *rmsexpand(char *spec, char *buf, char *def, unsigned opt) +char *Perl_rmsexpand(pTHX_ char *spec, char *buf, char *def, unsigned opt) { return do_rmsexpand(spec,buf,0,def,opt); } -char *rmsexpand_ts(char *spec, char *buf, char *def, unsigned opt) +char *Perl_rmsexpand_ts(pTHX_ char *spec, char *buf, char *def, unsigned opt) { return do_rmsexpand(spec,buf,1,def,opt); } @@ -1407,7 +2731,7 @@ char *rmsexpand_ts(char *spec, char *buf, char *def, unsigned opt) */ /*{{{ char *fileify_dirspec[_ts](char *path, char *buf)*/ -static char *do_fileify_dirspec(char *dir,char *buf,int ts) +static char *mp_do_fileify_dirspec(pTHX_ char *dir,char *buf,int ts) { static char __fileify_retbuf[NAM$C_MAXRSS+1]; unsigned long int dirlen, retlen, addmfd = 0, hasfilename = 0; @@ -1418,7 +2742,7 @@ static char *do_fileify_dirspec(char *dir,char *buf,int ts) set_errno(EINVAL); set_vaxc_errno(SS$_BADPARAM); return NULL; } dirlen = strlen(dir); - while (dir[dirlen-1] == '/') --dirlen; + while (dirlen && dir[dirlen-1] == '/') --dirlen; if (!dirlen) { /* We had Unixish '/' -- substitute top of current tree */ strcpy(trndir,"/sys$disk/000000"); dir = trndir; @@ -1444,10 +2768,14 @@ static char *do_fileify_dirspec(char *dir,char *buf,int ts) * ... do_fileify_dirspec("myroot",buf,1) ... * does something useful. */ - if (!strcmp(dir+dirlen-2,".]")) { + if (dirlen >= 2 && !strcmp(dir+dirlen-2,".]")) { 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. */ @@ -1474,7 +2802,7 @@ static char *do_fileify_dirspec(char *dir,char *buf,int ts) (dir[2] == '\0' || (dir[2] == '/' && dir[3] == '\0'))) return do_fileify_dirspec("[-]",buf,ts); } - if (dir[dirlen-1] == '/') { /* path ends with '/'; just add .dir;1 */ + if (dirlen && dir[dirlen-1] == '/') { /* path ends with '/'; just add .dir;1 */ dirlen -= 1; /* to last element */ lastdir = strrchr(dir,'/'); } @@ -1501,7 +2829,7 @@ static char *do_fileify_dirspec(char *dir,char *buf,int ts) } while ((cp1 = strstr(cp1,"/.")) != NULL); lastdir = strrchr(dir,'/'); } - else if (!strcmp(&dir[dirlen-7],"/000000")) { + else if (dirlen >= 7 && !strcmp(&dir[dirlen-7],"/000000")) { /* Ditto for specs that end in an MFD -- let the VMS code * figure out whether it's a real device or a rooted logical. */ dir[dirlen] = '/'; dir[dirlen+1] = '\0'; @@ -1591,13 +2919,14 @@ static char *do_fileify_dirspec(char *dir,char *buf,int ts) /* Yes; fake the fnb bits so we'll check type below */ dirnam.nam$l_fnb |= NAM$M_EXP_TYPE | NAM$M_EXP_VER; } - else { - if (dirfab.fab$l_sts != RMS$_FNF) { - set_errno(EVMSERR); - set_vaxc_errno(dirfab.fab$l_sts); + else { /* No; just work with potential name */ + if (dirfab.fab$l_sts == RMS$_FNF) dirnam = savnam; + else { + set_errno(EVMSERR); set_vaxc_errno(dirfab.fab$l_sts); + dirnam.nam$b_nop |= NAM$M_SYNCHK; dirnam.nam$l_rlf = NULL; + dirfab.fab$b_dns = 0; (void) sys$parse(&dirfab,0,0); return NULL; } - dirnam = savnam; /* No; just work with potential name */ } } if (!(dirnam.nam$l_fnb & (NAM$M_EXP_DEV | NAM$M_EXP_DIR))) { @@ -1613,6 +2942,8 @@ static char *do_fileify_dirspec(char *dir,char *buf,int ts) cmplen = (dirnam.nam$l_fnb & NAM$M_EXP_VER) ? 6 : 4; if (strncmp(dirnam.nam$l_type,".DIR;1",cmplen)) { /* Something other than .DIR[;1]. Bzzt. */ + dirnam.nam$b_nop |= NAM$M_SYNCHK; dirnam.nam$l_rlf = NULL; + dirfab.fab$b_dns = 0; (void) sys$parse(&dirfab,0,0); set_errno(ENOTDIR); set_vaxc_errno(RMS$_DIR); return NULL; @@ -1625,6 +2956,8 @@ static char *do_fileify_dirspec(char *dir,char *buf,int ts) else if (ts) New(1311,retspec,dirnam.nam$b_esl+1,char); else retspec = __fileify_retbuf; strcpy(retspec,esa); + dirnam.nam$b_nop |= NAM$M_SYNCHK; dirnam.nam$l_rlf = NULL; + dirfab.fab$b_dns = 0; (void) sys$parse(&dirfab,0,0); return retspec; } if ((cp1 = strstr(esa,".][000000]")) != NULL) { @@ -1633,7 +2966,11 @@ static char *do_fileify_dirspec(char *dir,char *buf,int ts) dirnam.nam$b_esl -= 9; } if ((cp1 = strrchr(esa,']')) == NULL) cp1 = strrchr(esa,'>'); - if (cp1 == NULL) return NULL; /* should never happen */ + if (cp1 == NULL) { /* should never happen */ + dirnam.nam$b_nop |= NAM$M_SYNCHK; dirnam.nam$l_rlf = NULL; + dirfab.fab$b_dns = 0; (void) sys$parse(&dirfab,0,0); + return NULL; + } term = *cp1; *cp1 = '\0'; retlen = strlen(esa); @@ -1650,6 +2987,8 @@ static char *do_fileify_dirspec(char *dir,char *buf,int ts) /* Go back and expand rooted logical name */ dirnam.nam$b_nop = NAM$M_SYNCHK | NAM$M_NOCONCEAL; if (!(sys$parse(&dirfab) & 1)) { + dirnam.nam$l_rlf = NULL; + dirfab.fab$b_dns = 0; (void) sys$parse(&dirfab,0,0); set_errno(EVMSERR); set_vaxc_errno(dirfab.fab$l_sts); return NULL; @@ -1659,6 +2998,7 @@ static char *do_fileify_dirspec(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)) { @@ -1694,6 +3034,8 @@ static char *do_fileify_dirspec(char *dir,char *buf,int ts) strcpy(cp2+9,cp1); } } + dirnam.nam$b_nop |= NAM$M_SYNCHK; dirnam.nam$l_rlf = NULL; + dirfab.fab$b_dns = 0; (void) sys$parse(&dirfab,0,0); /* We've set up the string up through the filename. Add the type and version, and we're done. */ strcat(retspec,".DIR;1"); @@ -1706,13 +3048,13 @@ static char *do_fileify_dirspec(char *dir,char *buf,int ts) } /* end of do_fileify_dirspec() */ /*}}}*/ /* External entry points */ -char *fileify_dirspec(char *dir, char *buf) +char *Perl_fileify_dirspec(pTHX_ char *dir, char *buf) { return do_fileify_dirspec(dir,buf,0); } -char *fileify_dirspec_ts(char *dir, char *buf) +char *Perl_fileify_dirspec_ts(pTHX_ char *dir, char *buf) { return do_fileify_dirspec(dir,buf,1); } /*{{{ char *pathify_dirspec[_ts](char *path, char *buf)*/ -static char *do_pathify_dirspec(char *dir,char *buf, int ts) +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; @@ -1846,6 +3188,8 @@ static char *do_pathify_dirspec(char *dir,char *buf, int ts) savnam = dirnam; if (!(sys$search(&dirfab)&1)) { /* Does the file really exist? */ if (dirfab.fab$l_sts != RMS$_FNF) { + dirnam.nam$b_nop |= NAM$M_SYNCHK; dirnam.nam$l_rlf = NULL; + dirfab.fab$b_dns = 0; (void) sys$parse(&dirfab,0,0); set_errno(EVMSERR); set_vaxc_errno(dirfab.fab$l_sts); return NULL; @@ -1858,6 +3202,8 @@ static char *do_pathify_dirspec(char *dir,char *buf, int ts) cmplen = (dirnam.nam$l_fnb & NAM$M_EXP_VER) ? 6 : 4; if (strncmp(dirnam.nam$l_type,".DIR;1",cmplen)) { /* Something other than .DIR[;1]. Bzzt. */ + dirnam.nam$b_nop |= NAM$M_SYNCHK; dirnam.nam$l_rlf = NULL; + dirfab.fab$b_dns = 0; (void) sys$parse(&dirfab,0,0); set_errno(ENOTDIR); set_vaxc_errno(RMS$_DIR); return NULL; @@ -1877,6 +3223,8 @@ static char *do_pathify_dirspec(char *dir,char *buf, int ts) else if (ts) New(1314,retpath,retlen,char); else retpath = __pathify_retbuf; strcpy(retpath,esa); + dirnam.nam$b_nop |= NAM$M_SYNCHK; dirnam.nam$l_rlf = NULL; + dirfab.fab$b_dns = 0; (void) sys$parse(&dirfab,0,0); /* $PARSE may have upcased filespec, so convert output to lower * case if input contained any lowercase characters. */ if (haslower) __mystrtolower(retpath); @@ -1886,13 +3234,13 @@ static char *do_pathify_dirspec(char *dir,char *buf, int ts) } /* end of do_pathify_dirspec() */ /*}}}*/ /* External entry points */ -char *pathify_dirspec(char *dir, char *buf) +char *Perl_pathify_dirspec(pTHX_ char *dir, char *buf) { return do_pathify_dirspec(dir,buf,0); } -char *pathify_dirspec_ts(char *dir, char *buf) +char *Perl_pathify_dirspec_ts(pTHX_ char *dir, char *buf) { return do_pathify_dirspec(dir,buf,1); } /*{{{ char *tounixspec[_ts](char *path, char *buf)*/ -static char *do_tounixspec(char *spec, char *buf, int ts) +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]; @@ -2016,11 +3364,11 @@ static char *do_tounixspec(char *spec, char *buf, int ts) } /* end of do_tounixspec() */ /*}}}*/ /* External entry points */ -char *tounixspec(char *spec, char *buf) { return do_tounixspec(spec,buf,0); } -char *tounixspec_ts(char *spec, char *buf) { return do_tounixspec(spec,buf,1); } +char *Perl_tounixspec(pTHX_ char *spec, char *buf) { return do_tounixspec(spec,buf,0); } +char *Perl_tounixspec_ts(pTHX_ char *spec, char *buf) { return do_tounixspec(spec,buf,1); } /*{{{ char *tovmsspec[_ts](char *path, char *buf)*/ -static char *do_tovmsspec(char *path, char *buf, int ts) { +static char *mp_do_tovmsspec(pTHX_ char *path, char *buf, int ts) { static char __tovmsspec_retbuf[NAM$C_MAXRSS+1]; char *rslt, *dirend; register char *cp1, *cp2; @@ -2160,11 +3508,11 @@ static char *do_tovmsspec(char *path, char *buf, int ts) { } /* end of do_tovmsspec() */ /*}}}*/ /* External entry points */ -char *tovmsspec(char *path, char *buf) { return do_tovmsspec(path,buf,0); } -char *tovmsspec_ts(char *path, char *buf) { return do_tovmsspec(path,buf,1); } +char *Perl_tovmsspec(pTHX_ char *path, char *buf) { return do_tovmsspec(path,buf,0); } +char *Perl_tovmsspec_ts(pTHX_ char *path, char *buf) { return do_tovmsspec(path,buf,1); } /*{{{ char *tovmspath[_ts](char *path, char *buf)*/ -static char *do_tovmspath(char *path, char *buf, int ts) { +static char *mp_do_tovmspath(pTHX_ char *path, char *buf, int ts) { static char __tovmspath_retbuf[NAM$C_MAXRSS+1]; int vmslen; char pathified[NAM$C_MAXRSS+1], vmsified[NAM$C_MAXRSS+1], *cp; @@ -2188,12 +3536,12 @@ static char *do_tovmspath(char *path, char *buf, int ts) { } /* end of do_tovmspath() */ /*}}}*/ /* External entry points */ -char *tovmspath(char *path, char *buf) { return do_tovmspath(path,buf,0); } -char *tovmspath_ts(char *path, char *buf) { return do_tovmspath(path,buf,1); } +char *Perl_tovmspath(pTHX_ char *path, char *buf) { return do_tovmspath(path,buf,0); } +char *Perl_tovmspath_ts(pTHX_ char *path, char *buf) { return do_tovmspath(path,buf,1); } /*{{{ char *tounixpath[_ts](char *path, char *buf)*/ -static char *do_tounixpath(char *path, char *buf, int ts) { +static char *mp_do_tounixpath(pTHX_ char *path, char *buf, int ts) { static char __tounixpath_retbuf[NAM$C_MAXRSS+1]; int unixlen; char pathified[NAM$C_MAXRSS+1], unixified[NAM$C_MAXRSS+1], *cp; @@ -2217,8 +3565,8 @@ static char *do_tounixpath(char *path, char *buf, int ts) { } /* end of do_tounixpath() */ /*}}}*/ /* External entry points */ -char *tounixpath(char *path, char *buf) { return do_tounixpath(path,buf,0); } -char *tounixpath_ts(char *path, char *buf) { return do_tounixpath(path,buf,1); } +char *Perl_tounixpath(pTHX_ char *path, char *buf) { return do_tounixpath(path,buf,0); } +char *Perl_tounixpath_ts(pTHX_ char *path, char *buf) { return do_tounixpath(path,buf,1); } /* * @(#)argproc.c 2.2 94/08/16 Mark Pizzolato (mark@infocomm.com) @@ -2263,18 +3611,18 @@ static void add_item(struct list_item **head, char *value, int *count); -static void expand_wild_cards(char *item, - struct list_item **head, - struct list_item **tail, - int *count); +static void mp_expand_wild_cards(pTHX_ char *item, + struct list_item **head, + struct list_item **tail, + int *count); static int background_process(int argc, char **argv); -static void pipe_and_fork(char **cmargv); +static void pipe_and_fork(pTHX_ char **cmargv); /*{{{ void getredirection(int *ac, char ***av)*/ static void -getredirection(int *ac, char ***av) +mp_getredirection(pTHX_ int *ac, char ***av) /* * Process vms redirection arg's. Exit if any error is seen. * If getredirection() processes an argument, it is erased @@ -2334,7 +3682,7 @@ getredirection(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]; @@ -2349,7 +3697,7 @@ getredirection(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]; @@ -2369,7 +3717,7 @@ getredirection(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; @@ -2391,7 +3739,7 @@ getredirection(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; @@ -2400,7 +3748,7 @@ getredirection(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); @@ -2431,10 +3779,10 @@ getredirection(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) */ @@ -2450,7 +3798,7 @@ getredirection(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); @@ -2464,33 +3812,41 @@ getredirection(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(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 PerlIO_printf(Perl_debug_log, "Arglist:\n"); for (j = 0; j < *ac; ++j) @@ -2520,7 +3876,7 @@ static void add_item(struct list_item **head, ++(*count); } -static void expand_wild_cards(char *item, +static void mp_expand_wild_cards(pTHX_ char *item, struct list_item **head, struct list_item **tail, int *count) @@ -2598,14 +3954,13 @@ unsigned long int zero = 0, sts; set_vaxc_errno(sts); switch (sts) { - case RMS$_FNF: - case RMS$_DNF: - case RMS$_DIR: + 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$_FNM: - case RMS$_SYN: + case RMS$_FNM: case RMS$_SYN: set_errno(EINVAL); break; case RMS$_PRV: set_errno(EACCES); break; @@ -2660,7 +4015,7 @@ 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, ""); @@ -2679,7 +4034,7 @@ static void pipe_and_fork(char **cmargv) cmddsc.dsc$a_pointer = subcmd; cmddsc.dsc$w_length = strlen(cmddsc.dsc$a_pointer); - create_mbx(&child_chan,&mbxdsc); + create_mbx(aTHX_ &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); @@ -2759,17 +4114,19 @@ vms_image_init(int *argcp, char ***argvp) unsigned long int iprv[(sizeof(union prvdef) + sizeof(unsigned long int) - 1) / sizeof(unsigned long int)]; unsigned short int dummy, rlen; struct dsc$descriptor_s **tabvec; - dTHX; +#if defined(PERL_IMPLICIT_CONTEXT) + pTHX = NULL; +#endif struct itmlst_3 jpilist[4] = { {sizeof iprv, JPI$_IMAGPRIV, iprv, &dummy}, {sizeof rlst, JPI$_RIGHTSLIST, rlst, &rlen}, { sizeof rsz, JPI$_RIGHTS_SIZE, &rsz, &dummy}, { 0, 0, 0, 0} }; - _ckvmssts(sys$getjpiw(0,NULL,NULL,jpilist,iosb,NULL,NULL)); - _ckvmssts(iosb[0]); + _ckvmssts_noperl(sys$getjpiw(0,NULL,NULL,jpilist,iosb,NULL,NULL)); + _ckvmssts_noperl(iosb[0]); for (i = 0; i < sizeof iprv / sizeof(unsigned long int); i++) { if (iprv[i]) { /* Running image installed with privs? */ - _ckvmssts(sys$setprv(0,iprv,0,NULL)); /* Turn 'em off. */ + _ckvmssts_noperl(sys$setprv(0,iprv,0,NULL)); /* Turn 'em off. */ will_taint = TRUE; break; } @@ -2781,12 +4138,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 @@ -2842,12 +4208,12 @@ vms_image_init(int *argcp, char ***argvp) tabvec[tabidx]->dsc$b_dtype = DSC$K_DTYPE_T; tabvec[tabidx]->dsc$b_class = DSC$K_CLASS_D; tabvec[tabidx]->dsc$a_pointer = NULL; - _ckvmssts(lib$scopy_r_dx(&len,eqv,tabvec[tabidx])); + _ckvmssts_noperl(lib$scopy_r_dx(&len,eqv,tabvec[tabidx])); } if (tabidx) { tabvec[tabidx] = NULL; env_tables = tabvec; } getredirection(argcp,argvp); -#if defined(USE_THREADS) && defined(__DECC) +#if defined(USE_5005THREADS) && ( defined(__DECC) || defined(__DECCXX) ) { # include (void) decc$set_reentrancy(C$C_MULTITHREAD); @@ -2875,7 +4241,7 @@ vms_image_init(int *argcp, char ***argvp) */ /*{{{int trim_unixpath(char *fspec, char *wildspec, int opts)*/ int -trim_unixpath(char *fspec, char *wildspec, int opts) +Perl_trim_unixpath(pTHX_ char *fspec, char *wildspec, int opts) { char unixified[NAM$C_MAXRSS+1], unixwild[NAM$C_MAXRSS+1], *template, *base, *end, *cp1, *cp2; @@ -3034,7 +4400,7 @@ trim_unixpath(char *fspec, char *wildspec, int opts) */ /*{{{ DIR *opendir(char*name) */ DIR * -opendir(char *name) +Perl_opendir(pTHX_ char *name) { DIR *dd; char dir[NAM$C_MAXRSS+1]; @@ -3098,8 +4464,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; @@ -3107,7 +4472,6 @@ collectversions(dd) char *p, *text, buff[sizeof dd->entry.d_name]; int i; unsigned long context, tmpsts; - dTHX; /* Convenient shorthand. */ e = &dd->entry; @@ -3154,7 +4518,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]; @@ -3175,7 +4539,8 @@ readdir(DIR *dd) case RMS$_DEV: set_errno(ENODEV); break; case RMS$_DIR: - case RMS$_FNF: + set_errno(ENOTDIR); break; + case RMS$_FNF: case RMS$_DNF: set_errno(ENOENT); break; default: set_errno(EVMSERR); @@ -3198,7 +4563,7 @@ readdir(DIR *dd) dd->entry.d_namlen = strlen(dd->entry.d_name); dd->entry.vms_verscount = 0; - if (dd->vms_wantversions) collectversions(dd); + if (dd->vms_wantversions) collectversions(aTHX_ dd); return &dd->entry; } /* end of readdir() */ @@ -3220,10 +4585,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) @@ -3286,12 +4650,10 @@ my_vfork() /*}}}*/ -static struct dsc$descriptor_s VMScmd = {0,DSC$K_DTYPE_T,DSC$K_CLASS_S,Nullch}; - static void -vms_execfree() { +vms_execfree(pTHX) { if (PL_Cmd) { - Safefree(PL_Cmd); + if (PL_Cmd != VMScmd.dsc$a_pointer) Safefree(PL_Cmd); PL_Cmd = Nullch; } if (VMScmd.dsc$a_pointer) { @@ -3302,9 +4664,8 @@ vms_execfree() { } 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; @@ -3345,62 +4706,116 @@ setup_argstr(SV *really, SV **mark, SV **sp) } /* end of setup_argstr() */ +#define MAX_DCL_LINE_LENGTH 255 static unsigned long int -setup_cmddsc(char *cmd, int check_img) +setup_cmddsc(pTHX_ char *cmd, int check_img) { - char resspec[NAM$C_MAXRSS+1]; + char vmsspec[NAM$C_MAXRSS+1], resspec[NAM$C_MAXRSS+1]; $DESCRIPTOR(defdsc,".EXE"); + $DESCRIPTOR(defdsc2,"."); $DESCRIPTOR(resdsc,resspec); 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; - register int isdcl = 0; - dTHX; + register char *s, *rest, *cp, *wordbreak; + register int isdcl; + if (strlen(cmd) > MAX_DCL_LINE_LENGTH) + return CLI$_BUFOVF; /* continuation lines currently unsupported */ s = cmd; while (*s && isspace(*s)) s++; - if (check_img) { - if (*s == '$') { /* Check whether this is a DCL command: leading $ and */ - isdcl = 1; /* no dev/dir separators (i.e. not a foreign command) */ - for (cp = s; *cp && *cp != '/' && !isspace(*cp); cp++) { - if (*cp == ':' || *cp == '[' || *cp == '<') { - isdcl = 0; - break; - } + + if (*s == '@' || *s == '$') { + vmsspec[0] = *s; rest = s + 1; + for (cp = &vmsspec[1]; *rest && isspace(*rest); rest++,cp++) *cp = *rest; + } + else { cp = vmsspec; rest = s; } + if (*rest == '.' || *rest == '/') { + char *cp2; + for (cp2 = resspec; + *rest && !isspace(*rest) && cp2 - resspec < sizeof resspec; + rest++, cp2++) *cp2 = *rest; + *cp2 = '\0'; + if (do_tovmsspec(resspec,cp,0)) { + s = vmsspec; + if (*rest) { + for (cp2 = vmsspec + strlen(vmsspec); + *rest && cp2 - vmsspec < sizeof vmsspec; + rest++, cp2++) *cp2 = *rest; + *cp2 = '\0'; } } } - else isdcl = 1; + /* Intuit whether verb (first word of cmd) is a DCL command: + * - if first nonspace char is '@', it's a DCL indirection + * otherwise + * - if verb contains a filespec separator, it's not a DCL command + * - 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 { + register char *filespec = strpbrk(s,":<[.;"); + rest = wordbreak = strpbrk(s," \"\t/"); + if (!wordbreak) wordbreak = s + strlen(s); + if (*s == '$') check_img = 0; + if (filespec && (filespec < wordbreak)) isdcl = 0; + else isdcl = !check_img; + } + if (!isdcl) { - cmd = s; - while (*s && !isspace(*s)) s++; - rest = *s ? s : 0; - imgdsc.dsc$a_pointer = cmd; - imgdsc.dsc$w_length = s - cmd; + imgdsc.dsc$a_pointer = s; + imgdsc.dsc$w_length = wordbreak - s; retsts = lib$find_file(&imgdsc,&resdsc,&cxt,&defdsc,0,0,&flags); - if (retsts & 1) { + if (!(retsts&1)) { + _ckvmssts(lib$find_file_end(&cxt)); + retsts = lib$find_file(&imgdsc,&resdsc,&cxt,&defdsc2,0,0,&flags); + if (!(retsts & 1) && *s == '$') { + _ckvmssts(lib$find_file_end(&cxt)); + imgdsc.dsc$a_pointer++; imgdsc.dsc$w_length--; + retsts = lib$find_file(&imgdsc,&resdsc,&cxt,&defdsc,0,0,&flags); + if (!(retsts&1)) { _ckvmssts(lib$find_file_end(&cxt)); + retsts = lib$find_file(&imgdsc,&resdsc,&cxt,&defdsc2,0,0,&flags); + } + } + } + _ckvmssts(lib$find_file_end(&cxt)); + + if (retsts & 1) { + FILE *fp; s = resspec; while (*s && !isspace(*s)) s++; *s = '\0'; + + /* check that it's really not DCL with no file extension */ + fp = fopen(resspec,"r","ctx=bin,shr=get"); + if (fp) { + char b[4] = {0,0,0,0}; + read(fileno(fp),b,4); + isdcl = isprint(b[0]) && isprint(b[1]) && isprint(b[2]) && isprint(b[3]); + fclose(fp); + } + 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); - strcpy(VMScmd.dsc$a_pointer,"$ MCR "); + if (!isdcl) { + strcpy(VMScmd.dsc$a_pointer,"$ MCR "); + } else { + strcpy(VMScmd.dsc$a_pointer,"@"); + } 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; + 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; - PL_Cmd = Nullch; /* Don't try to free twice in vms_execfree() */ - } + if (cmd == PL_Cmd) VMScmd.dsc$a_pointer = PL_Cmd; else VMScmd.dsc$a_pointer = savepvn(cmd,VMScmd.dsc$w_length); if (!(retsts & 1)) { /* just hand off status values likely to be due to user error */ @@ -3410,16 +4825,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--; @@ -3430,7 +4844,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)); } @@ -3440,10 +4854,9 @@ vms_do_aexec(SV *really,SV **mark,SV **sp) /* {{{bool vms_do_exec(char *cmd) */ bool -vms_do_exec(char *cmd) +Perl_vms_do_exec(pTHX_ char *cmd) { - dTHX; if (vfork_called) { /* this follows a vfork - act Unixish */ vfork_called--; if (vfork_called < 0) { @@ -3458,19 +4871,21 @@ vms_do_exec(char *cmd) TAINT_ENV(); TAINT_PROPER("exec"); - if ((retsts = setup_cmddsc(cmd,1)) & 1) + if ((retsts = setup_cmddsc(aTHX_ cmd,1)) & 1) retsts = lib$do_command(&VMScmd); switch (retsts) { - case RMS$_FNF: + case RMS$_FNF: case RMS$_DNF: set_errno(ENOENT); break; - case RMS$_DNF: case RMS$_DIR: case RMS$_DEV: + 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 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 */ @@ -3482,7 +4897,7 @@ vms_do_exec(char *cmd) Perl_warner(aTHX_ WARN_EXEC,"Can't exec \"%*s\": %s", VMScmd.dsc$w_length, VMScmd.dsc$a_pointer, Strerror(errno)); } - vms_execfree(); + vms_execfree(aTHX); } return FALSE; @@ -3490,14 +4905,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() */ @@ -3505,10 +4919,9 @@ do_aspawn(void *really,void **mark,void **sp) /* {{{unsigned long int do_spawn(char *cmd) */ unsigned long int -do_spawn(char *cmd) +Perl_do_spawn(pTHX_ char *cmd) { unsigned long int sts, substs, hadcmd = 1; - dTHX; TAINT_ENV(); TAINT_PROPER("spawn"); @@ -3516,21 +4929,28 @@ do_spawn(char *cmd) hadcmd = 0; sts = lib$spawn(0,0,0,0,0,0,&substs,0,0,0,0,0,0); } - else if ((sts = setup_cmddsc(cmd,0)) & 1) { - sts = lib$spawn(&VMScmd,0,0,0,0,0,&substs,0,0,0,0,0,0); + else { + sts = setup_cmddsc(aTHX_ cmd,0); + if (sts & 1) { + sts = lib$spawn(&VMScmd,0,0,0,0,0,&substs,0,0,0,0,0,0); + } else { + substs = sts; /* didn't spawn, use command setup failure for return */ + } } if (!(sts & 1)) { switch (sts) { - case RMS$_FNF: + case RMS$_FNF: case RMS$_DNF: set_errno(ENOENT); break; - case RMS$_DNF: case RMS$_DIR: case RMS$_DEV: + 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 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 */ @@ -3545,40 +4965,112 @@ do_spawn(char *cmd) Strerror(errno)); } } - vms_execfree(); + vms_execfree(aTHX); return substs; } /* end of do_spawn() */ /*}}}*/ + +static unsigned int *sockflags, sockflagsize; + +/* + * Shim fdopen to identify sockets for my_fwrite later, since the stdio + * routines found in some versions of the CRTL can't deal with sockets. + * We don't shim the other file open routines since a socket isn't + * likely to be opened by a name. + */ +/*{{{ FILE *my_fdopen(int fd, const char *mode)*/ +FILE *my_fdopen(int fd, const char *mode) +{ + FILE *fp = fdopen(fd, (char *) mode); + + if (fp) { + unsigned int fdoff = fd / sizeof(unsigned int); + struct stat sbuf; /* native stat; we don't need flex_stat */ + if (!sockflagsize || fdoff > sockflagsize) { + if (sockflags) Renew( sockflags,fdoff+2,unsigned int); + else New (1324,sockflags,fdoff+2,unsigned int); + memset(sockflags+sockflagsize,0,fdoff + 2 - sockflagsize); + sockflagsize = fdoff + 2; + } + if (fstat(fd,&sbuf) == 0 && S_ISSOCK(sbuf.st_mode)) + sockflags[fdoff] |= 1 << (fd % sizeof(unsigned int)); + } + return fp; + +} +/*}}}*/ + + +/* + * Clear the corresponding bit when the (possibly) socket stream is closed. + * There still a small hole: we miss an implicit close which might occur + * via freopen(). >> Todo + */ +/*{{{ int my_fclose(FILE *fp)*/ +int my_fclose(FILE *fp) { + if (fp) { + unsigned int fd = fileno(fp); + unsigned int fdoff = fd / sizeof(unsigned int); + + if (sockflagsize && fdoff <= sockflagsize) + sockflags[fdoff] &= ~(1 << fd % sizeof(unsigned int)); + } + return fclose(fp); +} +/*}}}*/ + + /* * A simple fwrite replacement which outputs itmsz*nitm chars without * introducing record boundaries every itmsz chars. + * We are using fputs, which depends on a terminating null. We may + * well be writing binary data, so we need to accommodate not only + * data with nulls sprinkled in the middle but also data with no null + * byte at the end. */ -/*{{{ int my_fwrite(void *src, size_t itmsz, size_t nitm, FILE *dest)*/ +/*{{{ int my_fwrite(const void *src, size_t itmsz, size_t nitm, FILE *dest)*/ int -my_fwrite(void *src, size_t itmsz, size_t nitm, FILE *dest) +my_fwrite(const void *src, size_t itmsz, size_t nitm, FILE *dest) { - register char *cp, *end; + register char *cp, *end, *cpd, *data; + register unsigned int fd = fileno(dest); + register unsigned int fdoff = fd / sizeof(unsigned int); + int retval; + int bufsize = itmsz * nitm + 1; + + if (fdoff < sockflagsize && + (sockflags[fdoff] | 1 << (fd % sizeof(unsigned int)))) { + if (write(fd, src, itmsz * nitm) == EOF) return EOF; + return nitm; + } - end = (char *)src + itmsz * nitm; + _ckvmssts_noperl(lib$get_vm(&bufsize, &data)); + memcpy( data, src, itmsz*nitm ); + data[itmsz*nitm] = '\0'; - while ((char *)src <= end) { - for (cp = src; cp <= end; cp++) if (!*cp) break; - if (fputs(src,dest) == EOF) return EOF; + end = data + itmsz * nitm; + retval = (int) nitm; /* on success return # items written */ + + cpd = data; + while (cpd <= end) { + for (cp = cpd; cp <= end; cp++) if (!*cp) break; + if (fputs(cpd,dest) == EOF) { retval = EOF; break; } if (cp < end) - if (fputc('\0',dest) == EOF) return EOF; - src = cp + 1; + if (fputc('\0',dest) == EOF) { retval = EOF; break; } + cpd = cp + 1; } - return 1; + if (data) _ckvmssts_noperl(lib$free_vm(&bufsize, &data)); + return retval; } /* end of my_fwrite() */ /*}}}*/ /*{{{ int my_flush(FILE *fp)*/ int -my_flush(FILE *fp) +Perl_my_flush(pTHX_ FILE *fp) { int res; if ((res = fflush(fp)) == 0 && fp) { @@ -3588,6 +5080,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; } /*}}}*/ @@ -3652,9 +5151,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]; @@ -3734,15 +5232,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; @@ -3773,13 +5270,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 { @@ -3819,7 +5315,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() */ @@ -3829,7 +5325,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)); } @@ -3839,9 +5335,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; @@ -3967,9 +5462,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 @@ -4018,6 +5510,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 @@ -4030,9 +5805,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; @@ -4049,6 +5823,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); } @@ -4083,9 +5858,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; @@ -4114,11 +5888,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); @@ -4134,15 +5908,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(&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() */ /*}}}*/ @@ -4171,9 +5954,8 @@ my_localtime(const time_t *timep) static const long int utime_baseadjust[2] = { 0x4beb4000, 0x7c9567 }; /*{{{int my_utime(char *path, struct utimbuf *utimes)*/ -int my_utime(char *file, struct utimbuf *utimes) +int Perl_my_utime(pTHX_ char *file, struct utimbuf *utimes) { - dTHX; register int i; long int bintime[2], len = 2, lowbit, unixtime, secscale = 10000000; /* seconds --> 100 ns intervals */ @@ -4220,7 +6002,7 @@ int my_utime(char *file, struct utimbuf *utimes) /* If input was UTC; convert to local for sys svc */ if (!VMSISH_TIME) unixtime = _toloc(unixtime); # endif - unixtime >> 1; secscale << 1; + unixtime >>= 1; secscale <<= 1; retsts = lib$emul(&secscale, &unixtime, &lowbit, bintime); if (!(retsts & 1)) { set_errno(EVMSERR); @@ -4266,6 +6048,8 @@ int my_utime(char *file, struct utimbuf *utimes) } retsts = sys$search(&myfab,0,0); if (!(retsts & 1)) { + mynam.nam$b_nop |= NAM$M_SYNCHK; mynam.nam$l_rlf = NULL; + myfab.fab$b_dns = 0; (void) sys$parse(&myfab,0,0); set_vaxc_errno(retsts); if (retsts == RMS$_PRV) set_errno(EACCES); else if (retsts == RMS$_FNF) set_errno(ENOENT); @@ -4278,6 +6062,8 @@ int my_utime(char *file, struct utimbuf *utimes) retsts = sys$assign(&devdsc,&chan,0,0); if (!(retsts & 1)) { + mynam.nam$b_nop |= NAM$M_SYNCHK; mynam.nam$l_rlf = NULL; + myfab.fab$b_dns = 0; (void) sys$parse(&myfab,0,0); set_vaxc_errno(retsts); if (retsts == SS$_IVDEVNAM) set_errno(ENOTDIR); else if (retsts == SS$_NOPRIV) set_errno(EACCES); @@ -4290,7 +6076,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 @@ -4302,6 +6088,8 @@ int my_utime(char *file, struct utimbuf *utimes) myfib.fib$r_acctl_overlay.fib$l_acctl = FIB$M_NORECORD; #endif retsts = sys$qiow(0,chan,IO$_MODIFY,iosb,0,0,&fibdsc,&fnmdsc,0,0,myatr,0); + mynam.nam$b_nop |= NAM$M_SYNCHK; mynam.nam$l_rlf = NULL; + myfab.fab$b_dns = 0; (void) sys$parse(&myfab,0,0); _ckvmssts(sys$dassgn(chan)); if (retsts & 1) retsts = iosb[0]; if (!(retsts & 1)) { @@ -4350,14 +6138,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; @@ -4403,7 +6190,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 @@ -4425,6 +6211,7 @@ is_null_device(name) bool Perl_cando(pTHX_ Mode_t bit, Uid_t effective, Stat_t *statbufp) { + char fname_phdev[NAM$C_MAXRSS+1]; if (statbufp == &PL_statcache) return cando_by_name(bit,effective,namecache); else { char fname[NAM$C_MAXRSS+1]; @@ -4443,7 +6230,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"); @@ -4458,7 +6253,7 @@ Perl_cando(pTHX_ Mode_t bit, Uid_t effective, Stat_t *statbufp) /*{{{I32 cando_by_name(I32 bit, Uid_t effective, char *fname)*/ I32 -cando_by_name(I32 bit, Uid_t effective, char *fname) +Perl_cando_by_name(pTHX_ I32 bit, Uid_t effective, char *fname) { static char usrname[L_cuserid]; static struct dsc$descriptor_s usrdsc = @@ -4466,7 +6261,6 @@ cando_by_name(I32 bit, Uid_t effective, char *fname) char vmsname[NAM$C_MAXRSS+1], fileified[NAM$C_MAXRSS+1]; unsigned long int objtyp = ACL$C_FILE, access, retsts, privused, iosb[2]; unsigned short int retlen; - dTHX; struct dsc$descriptor_s namdsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0}; union prvdef curprv; struct itmlst_3 armlst[3] = {{sizeof access, CHP$_ACCESS, &access, &retlen}, @@ -4497,26 +6291,14 @@ cando_by_name(I32 bit, Uid_t effective, char *fname) } switch (bit) { - case S_IXUSR: - case S_IXGRP: - case S_IXOTH: - access = ARM$M_EXECUTE; - break; - case S_IRUSR: - case S_IRGRP: - case S_IROTH: - access = ARM$M_READ; - break; - case S_IWUSR: - case S_IWGRP: - case S_IWOTH: - access = ARM$M_WRITE; - break; - case S_IDUSR: - case S_IDGRP: - case S_IDOTH: - access = ARM$M_DELETE; - break; + case S_IXUSR: case S_IXGRP: case S_IXOTH: + access = ARM$M_EXECUTE; break; + case S_IRUSR: case S_IRGRP: case S_IROTH: + access = ARM$M_READ; break; + case S_IWUSR: case S_IWGRP: case S_IWOTH: + access = ARM$M_WRITE; break; + case S_IDUSR: case S_IDGRP: case S_IDOTH: + access = ARM$M_DELETE; break; default: return FALSE; } @@ -4524,7 +6306,7 @@ cando_by_name(I32 bit, Uid_t effective, char *fname) retsts = sys$check_access(&objtyp,&namdsc,&usrdsc,armlst); if (retsts == SS$_NOPRIV || retsts == SS$_NOSUCHOBJECT || retsts == SS$_INVFILFOROP || retsts == RMS$_FNF || retsts == RMS$_SYN || - retsts == RMS$_DIR || retsts == RMS$_DEV) { + retsts == RMS$_DIR || retsts == RMS$_DEV || retsts == RMS$_DNF) { set_vaxc_errno(retsts); if (retsts == SS$_NOPRIV) set_errno(EACCES); else if (retsts == SS$_INVFILFOROP) set_errno(EINVAL); @@ -4557,12 +6339,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) { @@ -4591,19 +6372,19 @@ flex_fstat(int fd, Stat_t *statbufp) /*{{{ int flex_stat(const char *fspec, Stat_t *statbufp)*/ int -flex_stat(const char *fspec, Stat_t *statbufp) +Perl_flex_stat(pTHX_ const char *fspec, Stat_t *statbufp) { - dTHX; char fileified[NAM$C_MAXRSS+1]; char temp_fspec[NAM$C_MAXRSS+300]; int retval = -1; + if (!fspec) return retval; strcpy(temp_fspec, fspec); if (statbufp == (Stat_t *) &PL_statcache) do_tovmsspec(temp_fspec,namecache,0); if (is_null_device(temp_fspec)) { /* Fake a stat() for the null device */ memset(statbufp,0,sizeof *statbufp); - statbufp->st_dev = encode_dev("_NLA0:"); + statbufp->st_dev = encode_dev(aTHX_ "_NLA0:"); statbufp->st_mode = S_IFBLK | S_IREAD | S_IWRITE | S_IEXEC; statbufp->st_uid = 0x00010001; statbufp->st_gid = 0x0001; @@ -4627,7 +6408,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) { @@ -4691,7 +6472,7 @@ my_getlogin() */ /*{{{int rmscopy(char *src, char *dst, int preserve_dates)*/ int -rmscopy(char *spec_in, char *spec_out, int preserve_dates) +Perl_rmscopy(pTHX_ char *spec_in, char *spec_out, int preserve_dates) { char vmsin[NAM$C_MAXRSS+1], vmsout[NAM$C_MAXRSS+1], esa[NAM$C_MAXRSS], rsa[NAM$C_MAXRSS], ubf[32256]; @@ -4737,9 +6518,10 @@ rmscopy(char *spec_in, char *spec_out, int preserve_dates) if (!((sts = sys$open(&fab_in)) & 1)) { set_vaxc_errno(sts); switch (sts) { - case RMS$_FNF: - case RMS$_DIR: + 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$_SYN: @@ -4781,8 +6563,10 @@ rmscopy(char *spec_in, char *spec_out, int preserve_dates) if (!((sts = sys$create(&fab_out)) & 1)) { set_vaxc_errno(sts); switch (sts) { - case RMS$_DIR: + case RMS$_DNF: set_errno(ENOENT); break; + case RMS$_DIR: + set_errno(ENOTDIR); break; case RMS$_DEV: set_errno(ENODEV); break; case RMS$_SYN: @@ -4977,7 +6761,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); @@ -5014,7 +6798,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); @@ -5030,7 +6814,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); @@ -5050,11 +6834,125 @@ rmscopy_fromperl(pTHX_ CV *cv) XSRETURN(1); } + +void +mod2fname(pTHX_ CV *cv) +{ + dXSARGS; + char ultimate_name[NAM$C_MAXRSS+1], work_name[NAM$C_MAXRSS*8 + 1], + workbuff[NAM$C_MAXRSS*1 + 1]; + int total_namelen = 3, counter, num_entries; + /* ODS-5 ups this, but we want to be consistent, so... */ + int max_name_len = 39; + AV *in_array = (AV *)SvRV(ST(0)); + + num_entries = av_len(in_array); + + /* All the names start with PL_. */ + strcpy(ultimate_name, "PL_"); + + /* Clean up our working buffer */ + Zero(work_name, sizeof(work_name), char); + + /* Run through the entries and build up a working name */ + for(counter = 0; counter <= num_entries; counter++) { + /* If it's not the first name then tack on a __ */ + if (counter) { + strcat(work_name, "__"); + } + strcat(work_name, SvPV(*av_fetch(in_array, counter, FALSE), + PL_na)); + } + + /* Check to see if we actually have to bother...*/ + if (strlen(work_name) + 3 <= max_name_len) { + strcat(ultimate_name, work_name); + } else { + /* It's too darned big, so we need to go strip. We use the same */ + /* algorithm as xsubpp does. First, strip out doubled __ */ + char *source, *dest, last; + dest = workbuff; + last = 0; + for (source = work_name; *source; source++) { + if (last == *source && last == '_') { + continue; + } + *dest++ = *source; + last = *source; + } + /* Go put it back */ + strcpy(work_name, workbuff); + /* Is it still too big? */ + if (strlen(work_name) + 3 > max_name_len) { + /* Strip duplicate letters */ + last = 0; + dest = workbuff; + for (source = work_name; *source; source++) { + if (last == toupper(*source)) { + continue; + } + *dest++ = *source; + last = toupper(*source); + } + strcpy(work_name, workbuff); + } + + /* Is it *still* too big? */ + if (strlen(work_name) + 3 > max_name_len) { + /* Too bad, we truncate */ + work_name[max_name_len - 2] = 0; + } + strcat(ultimate_name, work_name); + } + + /* Okay, return it */ + ST(0) = sv_2mortal(newSVpv(ultimate_name, 0)); + XSRETURN(1); +} + +void +hushexit_fromperl(pTHX_ CV *cv) +{ + dXSARGS; + + if (items > 0) { + VMSISH_HUSHED = SvTRUE(ST(0)); + } + ST(0) = boolSV(VMSISH_HUSHED); + XSRETURN(1); +} + +void +Perl_sys_intern_dup(pTHX_ struct interp_intern *src, + struct interp_intern *dst) +{ + memcpy(dst,src,sizeof(struct interp_intern)); +} + +void +Perl_sys_intern_clear(pTHX) +{ +} + +void +Perl_sys_intern_init(pTHX) +{ + int ix = RAND_MAX; + float x; + + VMSISH_HUSHED = 0; + + x = (float)ix; + MY_INV_RAND_MAX = 1./x; +} + + + 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; @@ -5070,7 +6968,11 @@ init_os_extras() newXSproto("VMS::Filespec::vmspath",vmspath_fromperl,file,"$"); newXSproto("VMS::Filespec::unixpath",unixpath_fromperl,file,"$"); newXSproto("VMS::Filespec::candelete",candelete_fromperl,file,"$"); + newXSproto("DynaLoader::mod2fname", mod2fname, file, "$"); newXS("File::Copy::rmscopy",rmscopy_fromperl,file); + newXSproto("vmsish::hushed",hushexit_fromperl,file,";$"); + + store_pipelocs(aTHX); return; }