/* [.vms]vms_popen.c -- substitute routines for missing pipe calls. Copyright (C) 1991-1993, 1996, 2010, 2011, 2014 the Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef NO_VMS_PIPES #include "awk.h" /* really "../awk.h" */ #include #ifndef PIPES_SIMULATED FILE * popen( const char *command, const char *mode ) { fatal(" Cannot open pipe `%s' (not implemented)", command); /* NOT REACHED */ return 0; } int pclose( FILE *current ) { fatal(" Internal error ('pclose' not implemented)"); /* NOT REACHED */ return -1; } int fork( void ) { fatal(" Internal error ('fork' not implemented)"); /* NOT REACHED */ return -1; } #else /*PIPES_SIMULATED*/ /* * Simulate pipes using temporary files; hope that the user * doesn't expect pipe i/o to be interleaved with other i/o ;-}. * * This was initially based on the MSDOS version, but cannot * use a static array to hold pipe info, because there's no * fixed limit on the range of valid 'fileno's. Another * difference is that redirection is handled using LIB$SPAWN * rather than constructing a command for system() which uses * '<' or '>'. */ #include "vms.h" #include #include /* logical name definitions */ #ifndef STDC_HEADERS extern int strcmp(const char*, const char *); #endif extern char *mktemp(char *); static void push_logicals(void); static void pop_logicals(void); static Itm *save_translation(const struct dsc$descriptor_s *); static void restore_translation(const struct dsc$descriptor_s *, const Itm *); typedef enum { unopened = 0, reading, writing } pipemode; typedef struct pipe_info { char *command; char *name; pipemode pmode; } PIPE; static PIPE *pipes; static int pipes_lim = 0; #define psize(n) ((n) * sizeof(PIPE)) #define expand_pipes(k) do { PIPE *new_p; \ int new_p_lim = ((k) / _NFILE + 1) * _NFILE; \ emalloc(new_p, PIPE *, psize(new_p_lim), "expand_pipes"); \ if (pipes_lim > 0) \ memcpy(new_p, pipes, psize(pipes_lim)), free(pipes); \ memset(new_p + psize(pipes_lim), 0, psize(new_p_lim - pipes_lim)); \ pipes = new_p, pipes_lim = new_p_lim; } while(0) FILE * popen( const char *command, const char *mode ) { FILE *current; char *name; int cur; pipemode curmode; if (strcmp(mode, "r") == 0) curmode = reading; else if (strcmp(mode, "w") == 0) curmode = writing; else return NULL; /* make a name for the temporary file */ if ((name = mktemp(strdup("sys$scratch:gawk-pipe_XXXXXX.tmp"))) == 0) return NULL; if (curmode == reading) { /* an input pipe reads a temporary file created by the command */ vms_execute(command, (char *)0, name); /* 'command >tempfile' */ } if ((current = fopen(name, mode, "mbc=24", "mbf=2")) == NULL) { free(name); return NULL; } cur = fileno(current); if (cur >= pipes_lim) expand_pipes(cur); /* assert( cur >= 0 && cur < pipes_lim ); */ pipes[cur].name = name; pipes[cur].pmode = curmode; pipes[cur].command = strdup(command); return current; } int pclose( FILE *current ) { int rval, cur = fileno(current); /* assert( cur >= 0 && cur < pipes_lim ); */ if ((cur < 0) || (pipes[cur].pmode == unopened)) return -1; /* should never happen, but does with two-way */ rval = fclose(current); /* close temp file; if reading, we're done */ if (pipes[cur].pmode == writing) { /* an output pipe feeds the temporary file to the other program */ rval = vms_execute(pipes[cur].command, pipes[cur].name, (char *)0); } /* clean up */ unlink(pipes[cur].name); /* get rid of the temporary file */ pipes[cur].pmode = unopened; free(pipes[cur].name), pipes[cur].name = 0; free(pipes[cur].command), pipes[cur].command = 0; return rval; } /* * Create a process and execute a command in it. This is essentially * the same as system() but allows us to specify SYS$INPUT (stdin) * and/or SYS$OUTPUT (stdout) for the process. * [With more work it could truly simulate a pipe using mailboxes.] */ int vms_execute( const char *command, const char *input, const char *output ) { struct dsc$descriptor_s cmd, in, out, *in_p, *out_p; U_Long sts, cmpltn_sts; cmd.dsc$w_length = strlen(cmd.dsc$a_pointer = (char *)command); cmd.dsc$b_dtype = DSC$K_DTYPE_T; cmd.dsc$b_class = DSC$K_CLASS_S; if (input) { in.dsc$w_length = strlen(in.dsc$a_pointer = (char *)input); in_p = ∈ in.dsc$b_dtype = DSC$K_DTYPE_T; in.dsc$b_class = DSC$K_CLASS_S; } else in_p = 0; if (output) { out.dsc$w_length = strlen(out.dsc$a_pointer = (char *)output); out_p = &out; out.dsc$b_dtype = DSC$K_DTYPE_T; out.dsc$b_class = DSC$K_CLASS_S; } else out_p = 0; push_logicals(); /* guard against user-mode definitions of sys$Xput */ sts = LIB$SPAWN(&cmd, in_p, out_p, (U_Long *)0, (struct dsc$descriptor_s *)0, (U_Long *)0, &cmpltn_sts); pop_logicals(); /* restore environment */ if (vmswork(sts) && vmsfail(cmpltn_sts)) sts = cmpltn_sts; if (vmsfail(sts)) { errno = EVMSERR, vaxc$errno = sts; return -1; } else return 0; } /*----* This rigmarole is to guard against interference from the current environment. User-mode definitions of SYS$INPUT and/or SYS$OUTPUT will interact with spawned subprocesses--including LIB$SPAWN with explicit input and/or output arguments specified--if they were defined without the 'CONFINED' attribute. The definitions created in vms_args.c as part of command line I/O redirection happened to fall into this category :-(, but even though that's been fixed, there's still the possibility of the user doing something like |$ define/user sys$output foo.out prior to starting the program. Without ``/name_attr=confine'', that will really screw up pipe simulation, so we've got to work- around it here. This is true whether pipes are implemented via mailboxes or temporary files, as long as lib$spawn() is being used. push_logicals() calls save_translation() the first time it's invoked; the latter allocates some memory to hold a full logical name translation and uses $trnlnm to fill that in. Then if either sys$input or sys$output has a user-mode, non-confined translation, push_logicals() will delete the definition(s) using $dellnm. After the spawned command has returned, pop_logicals() is called; it calls restore_translation() for any deleted values; the latter uses $crllnm or $crelog to recreate the original definition. SYS$ERROR is currently ignored; perhaps it should receive the same treatment... *----*/ /* logical name table, and names of interest; these are all constant */ static const Descrip(lnmtable,"LNM$PROCESS_TABLE"); static const Descrip(sys_input,"SYS$INPUT"); static const Descrip(sys_output,"SYS$OUTPUT"); static const unsigned char acmode = PSL$C_USER; /* only care about user-mode */ /* macros for simplfying the code a bunch */ #define DelTrans(l) SYS$DELLNM(&lnmtable, (l), &acmode) #define GetTrans(l,i) SYS$TRNLNM((U_Long *)0, &lnmtable, (l), &acmode, (i)) #define SetTrans(l,i) SYS$CRELNM((U_Long *)0, &lnmtable, (l), &acmode, (i)) /* itemlist manipulation macros; separate versions for aggregate and scalar */ #define SetItmA(i,c,p,r) ((i).code = (c), (i).len = sizeof (p),\ (i).buffer = (p), (i).retlen = (U_Short *)(r)) #define SetItmS(i,c,p) ((i).code = (c), (i).len = sizeof *(p),\ (i).buffer = (p), (i).retlen = (U_Short *)0) #define EndItm0(i) ((i).code = (i).len = 0) /* translate things once, then hold the results here for multiple re-use */ static Itm *input_definition, *output_definition; static void push_logicals( void ) /* deassign sys$input and/or sys$output */ { static int init_done = 0; if (!init_done) { /* do logical name lookups one-time only */ input_definition = save_translation(&sys_input); output_definition = save_translation(&sys_output); init_done = 1; } if (input_definition) DelTrans(&sys_input); /* kill sys$input */ if (output_definition) DelTrans(&sys_output); /* and sys$output */ } static void pop_logicals( void ) /* redefine sys$input and/or sys$output */ { if (input_definition) restore_translation(&sys_input, input_definition); if (output_definition) restore_translation(&sys_output, output_definition); } static Itm * save_translation( const struct dsc$descriptor_s *logname ) { Itm trans[4], *itmlst; long trans_attr, max_trans_indx; /* 0-based translation index count */ unsigned char trans_acmode; /* translation's access mode */ unsigned itmlst_size; register int i, j; itmlst = 0; /* Want translation index count for non-confined, user-mode definition; unfortunately, $trnlnm does not provide that much control. Try to fetch several values of interest, then decide based on the result. */ SetItmS(trans[0], LNM$_MAX_INDEX, &max_trans_indx), max_trans_indx = -1; SetItmS(trans[1], LNM$_ACMODE, &trans_acmode), trans_acmode = 0; SetItmS(trans[2], LNM$_ATTRIBUTES, &trans_attr), trans_attr = 0; EndItm0(trans[3]); if (vmswork(GetTrans(logname, trans)) && max_trans_indx >= 0 && trans_acmode == PSL$C_USER && !(trans_attr & LNM$M_CONFINE)) { /* Now know that definition of interest exists; allocate and initialize an item list and associated buffers; use three entries for each translation. */ itmlst_size = (3 * (max_trans_indx + 1) + 1) * sizeof(Itm); emalloc(itmlst, Itm *, itmlst_size, "save_translation"); for (i = 0; i <= max_trans_indx; i++) { struct def { U_Long indx, attr; U_Short len; char str[LNM$C_NAMLENGTH], eos; } *wrk; emalloc(wrk, struct def *, sizeof (struct def), "save_translation"); wrk->indx = (U_Long)i; /* this one's an input value for $trnlnm */ SetItmS(itmlst[3*i+0], LNM$_INDEX, &wrk->indx); SetItmS(itmlst[3*i+1], LNM$_ATTRIBUTES, &wrk->attr), wrk->attr = 0; SetItmA(itmlst[3*i+2], LNM$_STRING, &wrk->str, &wrk->len), wrk->len = 0; } EndItm0(itmlst[3*i]); /* assert( i == max_trans_indx+1 ); */ /* Time to perform full logical name translation, then update item list for subsequent restoration. If there are any holes [don't know whether that's possible] collapse them out of the list; don't want them at restore time. */ if (vmswork(GetTrans(logname, itmlst))) { for (i = 0, j = -1; i <= max_trans_indx; i++) { U_Long *attr_p; attr_p = itmlst[3*i+1].buffer; /* copy (void *) to true type */ if (*attr_p & LNM$M_EXISTS) { *attr_p &= ~LNM$M_EXISTS; /* must clear this bit */ if (++j < i) itmlst[3*j+0] = itmlst[3*i+0], itmlst[3*j+1] = itmlst[3*i+1], itmlst[3*j+2] = itmlst[3*i+2]; if (itmlst[3*j+2].retlen) { /* fixup buffer length */ itmlst[3*j+2].len = *itmlst[3*j+2].retlen; itmlst[3*j+2].retlen = (U_Short *)0; } } } if (++j < i) EndItm0(itmlst[3*j]); } else /* should never happen; tolerate potential memory leak */ free(itmlst), itmlst = 0; /*('wrk' buffer(s) will become lost)*/ } return itmlst; } static void restore_translation( const struct dsc$descriptor_s *logname, const Itm *itemlist ) { struct dsc$descriptor_s trans_val; U_Long *attr_p; # define LOG_PROCESS_TABLE 2 /* */ # define LOG_USERMODE PSL$C_USER /* assert( itemlist[1].code == LNM$_ATTRIBUTES ); */ attr_p = itemlist[1].buffer; /* copy (void *) to (U_Long *) */ if (*attr_p & LNM$M_CRELOG) { /* check original creation method */ /* $crelog values can have only one translation; so it'll be the first string entry in the itemlist. */ /* assert( itemlist[2].code == LNM$_STRING ); */ trans_val.dsc$a_pointer = itemlist[2].buffer; trans_val.dsc$w_length = itemlist[2].len; trans_val.dsc$b_dtype = DSC$K_DTYPE_T; trans_val.dsc$b_class = DSC$K_CLASS_S; (void) SYS$CRELOG(LOG_PROCESS_TABLE, logname, &trans_val, LOG_USERMODE); } else { /* $crelnm definition; itemlist could specify multiple translations, but has already been setup properly for use as-is. */ (void) SetTrans(logname, itemlist); } } #endif /*PIPES_SIMULATED*/ #endif /*!NO_VMS_PIPES*/