The most common cause of test failures on VMS is the insertion of extra newlines in pipes. For more details, see the archives, but the gist of it is that you never know whether
print 'a', 'b', 'c', "\n"; will give you the same thing as print "abc\n"; when the output device is a pipe and autoflushing is enabled (which it typically needs to be for inter-process communication). The first example might well give you an extra newline somewhere in the middle. So I took a swing at replacing our mailbox-based pipe implementation with a socketpair-based implementation. Since sockets are stream-oriented devices (unlike mailboxes, which are record-oriented), it seemed like this at least had the potential to solve our problems. There is a diff attached to this message that shows what I did. The experiment failed, for reasons that I'll attempt to summarize. I used the socketpair that came with the CRTL in v8.2, but if other obstacles can be overcome, a homegrown socketpair might well work on older versions of VMS. I borrowed some infrastructure from the existing pipe implementation but left it intact, protected by #ifdefs. I did get communication happening through the socket-based pipes, but all carriage control was stripped. Instead of getting the occasional spurious newline, I got none at all. Somehow the sockets need to be convinced to leave the carriage control information alone, but I was not able to come up with a way to do that. Aside from the obvious fact that I didn't get it working, the main drawbacks to using sockets come down to: -- The TCP/IP Services documentation says that standard I/O is not supported on sockets, and specifically fdopen() is not supported. Since popen() returns a FILE pointer, you absolutely have to have that. The calls to fdopen() succeed, but if the other stdio functions don't properly handle the resulting FILE pointers, that may explain the loss of carriage control. -- Modifying the buffer size on a socket cannot be done without privileges; you're stuck with 256 bytes, which is likely to be a serious performance drag. I'm not sure I've gained anything except a better understanding of the challenge. I think the next thing to try is turning off all the home-grown piping code and seeing if the new DECC$STREAM_PIPE feature does us any good, though that unfortunately requires 8.2 and later. BTW, as I understand it, most modern unices use shared memory for pipes. Is there any particular reason we couldn't use global sections on VMS?
--- vms/vms.c;-0 Wed Dec 14 16:04:57 2005 +++ vms/vms.c Mon Jan 2 18:51:41 2006 @@ -2105,10 +2105,22 @@ int test_unix_status; return SS$_ABORT; /* Should not get here */ } +static unsigned long int setup_cmddsc(pTHX_ const char *cmd, int check_img, int *suggest_quote, struct dsc$descriptor_s **pvmscmd); +static void vms_execfree(struct dsc$descriptor_s *vmscmd); + +struct exit_control_block +{ + struct exit_control_block *flink; + unsigned long int (*exit_routine)(); + unsigned long int arg_count; + unsigned long int *status_address; + unsigned long int exit_status; +}; /* default piping mailbox size */ #define PERL_BUFSIZ 512 +#ifdef USE_PERL_MAILBOX_PIPES static void create_mbx(pTHX_ unsigned short int *chan, struct dsc$descriptor_s *namdsc) @@ -2227,15 +2239,6 @@ struct pipe_details int err_done; }; -struct exit_control_block -{ - struct exit_control_block *flink; - unsigned long int (*exit_routine)(); - unsigned long int arg_count; - unsigned long int *status_address; - unsigned long int exit_status; -}; - typedef struct _closed_pipes Xpipe; typedef struct _closed_pipes* pXpipe; @@ -2444,9 +2447,6 @@ popen_completion_ast(pInfo info) } -static unsigned long int setup_cmddsc(pTHX_ const char *cmd, int check_img, int *suggest_quote, struct dsc$descriptor_s **pvmscmd); -static void vms_execfree(struct dsc$descriptor_s *vmscmd); - /* we actually differ from vmstrnenv since we use this to get the RMS IFI to check if SYS$OUTPUT and SYS$ERROR *really* @@ -3478,6 +3478,219 @@ safe_popen(pTHX_ const char *cmd, const return info->fp; } /* end of safe_popen */ +#elif defined (USE_PERL_SOCKPAIR_PIPES) + + +typedef struct pipe_details Info; +typedef struct pipe_details* pInfo; +static pInfo open_pipes = NULL; + +struct pipe_details +{ + pInfo next; + FILE *fp; /* file pointer to pipe */ + int pid; /* PID of subprocess */ + int done; /* subprocess has completed */ + unsigned int completion; /* termination status of subprocess */ +}; + +static int pipe_ef = 0; + +static void +popen_completion_ast(pInfo info) +{ + pInfo i = open_pipes; + + info->completion &= 0x0FFFFFFF; /* strip off "control" field */ + + while (i) { + if (i == info) break; + i = i->next; + } + if (!i) return; /* unlinked, probably freed too */ + + info->done = TRUE; + _ckvmssts_noperl(sys$setef(pipe_ef)); +} + +FILE * +vms_sockpair_popen(const char *command, const char *mode) +{ + unsigned short int pd0chan, pd1chan; + int pd[2]; + FILE *parent_return_fd; + unsigned int dviitm = DVI$_DEVNAM; + char pd0_devnam[LNM$C_NAMLENGTH+1], pd1_devnam[LNM$C_NAMLENGTH+1]; + struct dsc$descriptor_s pd0_devnam_dsc + = { LNM$C_NAMLENGTH, DSC$K_DTYPE_T, DSC$K_CLASS_S, pd0_devnam }; + struct dsc$descriptor_s pd1_devnam_dsc + = { LNM$C_NAMLENGTH, DSC$K_DTYPE_T, DSC$K_CLASS_S, pd1_devnam }; + struct dsc$descriptor_s *command_dsc; + char bidirectional = 0; + unsigned int flags = CLI$M_NOWAIT; + struct dsc$descriptor_s *child_in = NULL, *child_out = NULL; + int info_size, status, i; + pInfo info; + char mymode[6] = "\0"; + const char *c; + + if (command == NULL || mode == NULL) { + SETERRNO(EINVAL, LIB$_INVARG); + return NULL; + } + + status = setup_cmddsc(aTHX_ command, 0, 0, &command_dsc); + if (!(status & 1)) { + switch (status) { + 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(status); /* fall through */ + default: /* SS$_DUPLNAM, SS$_CLI, resource exhaustion, etc. */ + set_errno(EVMSERR); + } + set_vaxc_errno(status); + if (ckWARN(WARN_PIPE)) { + Perl_warner(aTHX_ packWARN(WARN_PIPE), + "Can't pipe \"%*s\": %s", + strlen(command), + command, + Strerror(errno)); + } + return NULL; + } + + if (!pipe_ef) { + status = lib$get_ef(&pipe_ef); + if (!(status & 1)) { + SETERRNO(EVMSERR, status); + return NULL; + } + } + + if (socketpair(AF_INET, SOCK_STREAM, 0, pd) < 0) { + return NULL; + } + + pd0chan = decc$get_sdc( pd[0] ); + pd1chan = decc$get_sdc( pd[1] ); + + status = lib$getdvi(&dviitm, &pd0chan, NULL, NULL, + &pd0_devnam_dsc, &pd0_devnam_dsc.dsc$w_length); + + if (status & 1) { + status = lib$getdvi(&dviitm, &pd1chan, NULL, NULL, + &pd1_devnam_dsc, &pd1_devnam_dsc.dsc$w_length); + } + + if (!(status & 1)) { + SETERRNO(EVMSERR, status); + (void) close(pd[0]); + (void) close(pd[1]); + return NULL; + } + + pd0_devnam_dsc.dsc$a_pointer[pd0_devnam_dsc.dsc$w_length] = '\0'; + pd1_devnam_dsc.dsc$a_pointer[pd1_devnam_dsc.dsc$w_length] = '\0'; + + info_size = sizeof(Info); + status = lib$get_vm(&info_size, &info); + if (!(status & 1)) { + SETERRNO(ENOMEM, status); + (void) close(pd[0]); + (void) close(pd[1]); + return NULL; + } + + info->done = 0; + info->pid = 0; + info->completion = 0; + + /* throw away invalid mode specifiers */ + c = mode; + while (*c) { + if (strchr("rwb+\0", *c)) { + strncat(mymode, c, 1); + } + c++; + } + + if (strchr(mymode, '+')) { + bidirectional = 1; + strcpy(mymode, "r+"); + } +/** + if (!strchr(mymode, 'b')) + strncat(mymode, "b", 1); +**/ + if (mymode[0] == 'r') { + child_out = &pd1_devnam_dsc; + if (bidirectional) + child_in = &pd1_devnam_dsc; + } + else { + child_in = &pd0_devnam_dsc; + } + + (void) sys$setast(0); + + status = lib$spawn(command_dsc, + child_in, + child_out, + &flags, + 0, + &info->pid, + &info->completion, + 0, + popen_completion_ast, + info, + 0,0,0); + + vms_execfree(command_dsc); + if (!(status & 1)) { + SETERRNO(EVMSERR, status); + (void) close(pd[0]); + (void) close(pd[1]); + (void) lib$free_vm(&info_size, &info); + (void) sys$setast(1); + return NULL; + } + + if (mymode[0] == 'r') { + parent_return_fd = fdopen(pd[0], mymode); + (void) close(pd[1]); + } + else { + parent_return_fd = fdopen(pd[1], mymode); + (void) close(pd[0]); + } + + info->next=open_pipes; /* prepend to list */ + open_pipes=info; + info->fp = parent_return_fd; + +#ifdef PERL_IMPLICIT_CONTEXT + if (aTHX) +#endif + PL_forkprocess = info->pid; + + (void) sys$setast(1); + + return parent_return_fd; + +} /* end of vms_sockpair_popen */ + +#endif /* USE_PERL_MAILBOX_PIPES elif USE_PERL_SOCKPAIR_PIPES */ /*{{{ PerlIO *my_popen(char *cmd, char *mode)*/ PerlIO * @@ -3487,7 +3700,13 @@ Perl_my_popen(pTHX_ const char *cmd, con TAINT_ENV(); TAINT_PROPER("popen"); PERL_FLUSHALL_FOR_CHILD; +#if USE_PERL_MAILBOX_PIPES return safe_popen(aTHX_ cmd,mode,&sts); +#elif defined(USE_PERL_SOCKPAIR_PIPES) + return PerlIO_importFILE(vms_sockpair_popen(cmd, mode), mode); +#else + return popen(cmd, mode); +#endif /* USE_PERL_MAILBOX_PIPES */ } /*}}}*/ @@ -3496,14 +3715,25 @@ Perl_my_popen(pTHX_ const char *cmd, con I32 Perl_my_pclose(pTHX_ PerlIO *fp) { pInfo info, last = NULL; - unsigned long int retsts; - int done, iss, n; + int retsts; + int done, iss, n, pid; + +#if !defined(USE_PERL_SOCKPAIR_PIPES) && !defined(USE_PERL_MAILBOX_PIPES) + + FILE *stdio = PerlIO_exportFILE(fp, 0); + retsts = pclose(stdio); + PerlIO_releaseFILE(fp, stdio); + return retsts; + +#endif + +#ifdef USE_PERL_MAILBOX_PIPES for (info = open_pipes; info != NULL; last = info, info = info->next) if (info->fp == fp) break; if (info == NULL) { /* no such pipe open */ - set_errno(ECHILD); /* quoth POSIX */ + set_errno(ECHILD); set_vaxc_errno(SS$_NONEXPR); return -1; } @@ -3559,6 +3789,30 @@ I32 Perl_my_pclose(pTHX_ PerlIO *fp) } retsts = info->completion; +#elif defined(USE_PERL_SOCKPAIR_PIPES) + + FILE *stdio = PerlIO_exportFILE(fp, 0); + + for (info = open_pipes; info != NULL; last = info, info = info->next) + if (info->fp == stdio) break; + + if (info == NULL) { /* no such pipe open */ + SETERRNO(ECHILD, SS$_NONEXPR); + return -1; + } + + (void) fclose((FILE *)info->fp); + + PerlIO_releaseFILE(fp, stdio); + + do { + pid = my_waitpid(info->pid, &retsts, 0); + } while (pid == -1 && errno == EINTR); + + if (pid == -1) retsts == -1; + +#endif + /* remove from list of open pipes */ _ckvmssts(sys$setast(0)); if (last) last->next = info->next; @@ -3567,6 +3821,8 @@ I32 Perl_my_pclose(pTHX_ PerlIO *fp) /* free buffers and structures */ +#ifdef USE_PERL_MAILBOX_PIPES + if (info->in) { if (info->in->buf) { n = info->in->bufsize * sizeof(char); @@ -3591,6 +3847,9 @@ I32 Perl_my_pclose(pTHX_ PerlIO *fp) n = sizeof(Pipe); _ckvmssts(lib$free_vm(&n, &info->err)); } + +#endif + n = sizeof(Info); _ckvmssts(lib$free_vm(&n, &info)); @@ -3620,6 +3879,8 @@ Perl_my_waitpid(pTHX_ Pid_t pid, int *st if (statusp) *statusp = 0; +#if defined(USE_PERL_SOCKPAIR_PIPES) || defined(USE_PERL_MAILBOX_PIPES) + for (info = open_pipes; info != NULL; info = info->next) if (info->pid == pid) break; @@ -3635,6 +3896,9 @@ Perl_my_waitpid(pTHX_ Pid_t pid, int *st if (statusp) *statusp = info->completion; return pid; } +#endif + +#ifdef USE_PERL_MAILBOX_PIPES /* child that already terminated? */ @@ -3645,6 +3909,8 @@ Perl_my_waitpid(pTHX_ Pid_t pid, int *st } } +#endif + /* fall through if this child is not one of our own pipe children */ #if defined(__CRTL_VER) && __CRTL_VER >= 70200000 @@ -5640,7 +5906,7 @@ int quoted; else { if (dotdir_seen) { /* Perl wants an empty directory here to tell the difference - * between a DCL commmand and a filename + * between a DCL command and a filename */ *vmsptr++ = '['; *vmsptr++ = ']'; @@ -7003,9 +7269,9 @@ pipe_and_fork(pTHX_ char **cmargv) } *p = '\0'; - fp = safe_popen(aTHX_ subcmd,"wbF",&sts); + fp = my_popen(aTHX_ subcmd,"wbF"); if (fp == Nullfp) { - PerlIO_printf(Perl_debug_log,"Can't open output pipe (status %d)",sts); + PerlIO_printf(Perl_debug_log,"Can't open output pipe (status %d)",vaxc$errno); } } @@ -8268,7 +8534,11 @@ Perl_do_spawn(pTHX_ const char *cmd) } else { PerlIO * fp; +#ifdef USE_PERL_MAILBOX_PIPES fp = safe_popen(aTHX_ cmd, "nW", (int *)&sts); +#else + fp = my_popen(aTHX_ cmd, "r+"); +#endif if (fp != NULL) my_pclose(fp); } @@ -10733,7 +11003,9 @@ init_os_extras(void) newXSproto("VMS::Filespec::case_tolerant",vms_case_tolerant_fromperl,file,"$;$"); #endif +#ifdef USE_PERL_MAILBOX_PIPES store_pipelocs(aTHX); /* will redo any earlier attempts */ +#endif return; }