home *** CD-ROM | disk | FTP | other *** search
/ kermit.columbia.edu / kermit.columbia.edu.tar / kermit.columbia.edu / archives / ckc072.zip / ckvfio.c < prev    next >
C/C++ Source or Header  |  1988-08-16  |  17KB  |  586 lines

  1. char *ckzv = "VMS file support, 1.0(010), 24 Jan 88";
  2. char *ckzsys = " Vax/VMS";
  3.  
  4. /* C K V F I O  --  Kermit file system support for VAX/VMS */
  5.  
  6. /* Stew Rubenstein, Harvard University Chemical Labs */
  7. /*  (c) 1985 President and Fellows of Harvard College  */
  8. /*  Based on CKZUNX.C, 4.1(015) 28 Feb 85 */
  9. /* Also, Martin Minow (MM), Digital Equipment Corporation, Maynard MA */
  10. /* Also, Dan Schullman (DS), Digital Equipment Corporation, Maynard MA */
  11. /* Adapted from ckufio.c, by... */
  12. /* F. da Cruz (FdC), Columbia University Center for Computing Activities */
  13.  
  14. /* Edit history
  15.  * 003 20-Mar-85 MM  fixed fprintf bug in zsout.c
  16.  * 004 21-Mar-84 MM  create text files in variable-stream.
  17.  * 005  8-May-85 MM  filled in zkself (not tested), fixed other minor bugs
  18.  * 006  5-Jul-85 DS  handle version number in zltor, zrtol
  19.  * 007 11-Jul-85 FdC fix zclose() to give return codes
  20.  * 008 19-Mar-86 FdC Fix system() for "!", zopeni() for REMOTE commands.
  21.  * 008 17-Sep-87 FdC Define PWDCMD.
  22.  * 090 (???)
  23.  * 010 24-Jan-88 FdC Add zgtdir() function, even tho it doesn't work...
  24.  */
  25.  
  26. /* Definitions of some VMS system commands */
  27.  
  28. char *DIRCMD = "DIRECTORY ";        /* For directory listing */
  29. char *DELCMD = "DELETE ";        /* For file deletion */
  30. char *TYPCMD = "TYPE ";            /* For typing a file */
  31. char *SPACMD = "DIRECTORY/TOTAL";    /* Space/quota of current directory */
  32. char *SPACM2 = "DIRECTORY/TOTAL ";    /* Space/quota of current directory */
  33. char *WHOCMD = "SHOW USERS";        /* For seeing who's logged in */
  34. char *PWDCMD = "SHOW DEFAULT";        /* For seeing current directory */
  35.  
  36. /*
  37.   Functions (n is one of the predefined file numbers from ckermi.h):
  38.  
  39.    zopeni(n,name)   -- Opens an existing file for input.
  40.    zopeno(n,name)   -- Opens a new file for output.
  41.    zclose(n)        -- Closes a file.
  42.    zchin(n)         -- Gets the next character from an input file.
  43.    zsout(n,s)       -- Write a null-terminated string to output file, buffered.
  44.    zsoutl(n,s)      -- Like zsout, but appends a line terminator.
  45.    zsoutx(n,s,x)    -- Write x characters to output file, unbuffered.
  46.    zchout(n,c)      -- Add a character to an output file, unbuffered.
  47.    zchki(name)      -- Check if named file exists and is readable, return size.
  48.    zchko(name)      -- Check if named file can be created.
  49.    znewn(name,s)    -- Make a new unique file name based on the given name.
  50.    zdelet(name)     -- Delete the named file.
  51.    zxpand(string)   -- Expands the given wildcard string into a list of files.
  52.    znext(string)    -- Returns the next file from the list in "string".
  53.    zxcmd(cmd)       -- Execute the command in a lower fork.
  54.    zclosf()         -- Close input file associated with zxcmd()'s lower fork.
  55.    zrtol(n1,n2)     -- Convert remote filename into local form.
  56.    zltor(n1,n2)     -- Convert local filename into remote form.
  57.    zchdir(dirnam)   -- Change working directory.
  58.    zhome()          -- Return pointer to home directory name string.
  59.    zkself()         -- Log self out
  60.  */
  61.  
  62.  
  63. /* Includes */
  64.  
  65. #include "ckcker.h"
  66. #include "ckcdeb.h"
  67. #include <stdio.h>
  68. #include <ctype.h>
  69. #include <rms.h>
  70. #include <descrip.h>
  71. #include <dvidef.h>
  72. #include <iodef.h>
  73. #include <errno.h>
  74.  
  75. #define MAXWLD 500            /* Maximum wildcard filenames */
  76.  
  77.  
  78. /* Declarations */
  79.  
  80. FILE *fp[ZNFILS] = {             /* File pointers */
  81.     NULL, NULL, NULL, NULL, NULL, NULL, NULL };
  82.  
  83. static int fcount;            /* Number of files in wild group */
  84. char *getenv(), *strcpy();        /* For finding home directory */
  85.  
  86. static char *mtchs[MAXWLD],        /* Matches found for filename */
  87.      **mtchptr;                /* Pointer to current match */
  88.  
  89.  
  90. /***  Z K S E L F --  Log self out  ***/
  91.  
  92. /*** (someone please check if this works in VMS) ***/
  93.  
  94. zkself() {
  95.     return (kill(0,9));
  96. }
  97.  
  98.  
  99. /*  Z O P E N I  --  Open an existing file for input. */
  100.  
  101. zopeni(n,name) int n; char *name; {
  102.     debug(F111," zopeni",name,n);
  103.     debug(F101,"  fp","",(int) fp[n]);
  104.     if (n == ZSYSFN) {            /* Input from a system function? */
  105.     return(zxcmd(name));        /* Try to fork the command */
  106.     }
  107.     if (n == ZSTDIO) {            /* Standard input? */
  108.     if (isatty(0)) {
  109.         fprintf(stderr,"?Terminal input not allowed\n");
  110.         debug(F110,"zopeni: attempts input from unredirected stdin","",0);
  111.         return(0);
  112.     }
  113.     fp[ZIFILE] = stdin;
  114.     return(1);
  115.     }
  116.     if (chkfn(n) != 0) return(0);
  117.     fp[n] = fopen(name,"r");        /* Real file. */
  118.     debug(F111," zopeni", name, (int) fp[n]);
  119.     if (fp[n] == NULL) perror(name);    /* +1, want a useful message    */
  120.     return((fp[n] != NULL) ? 1 : 0);
  121. }
  122.  
  123. /*  Z O P E N O  --  Open a new file for output.  */
  124.  
  125. zopeno(n,name) int n; char *name; {
  126.  
  127.     int fildes;
  128.     extern int binary;
  129.  
  130.     debug(F111," zopeno",name,n);
  131.     if (chkfn(n) != 0) return(0);
  132.     if ((n == ZCTERM) || (n == ZSTDIO)) {   /* Terminal or standard output */
  133.     fp[ZOFILE] = stdout;
  134.     debug(F101," fp[]=stdout", "", (int) fp[n]);
  135.     return(1);
  136.     }
  137.     /*
  138.      * Create "binary" output files as fixed-block 512 byte records.
  139.      * This should permit copying task images.  It is rumored that
  140.      * Vax C will null-fill an incomplete final block.
  141.      *
  142.      * Create all debugging files (and normal output files) in
  143.      * "vanilla" RMS -- variable length, implicit carriage control.
  144.      * This way, old brain-damaged programs aren't suprised by
  145.      * bizarre Unix-styled files.
  146.      */
  147.     if (n == ZOFILE && binary != 0)
  148.     fildes = creat(name, 0, "mrs=512", "rfm=fix");
  149.     else
  150.     fildes = creat(name, 0, "rat=cr", "rfm=var");
  151.     fp[n] = (fildes == -1) ? NULL : fdopen(fildes, "w");
  152.     if (fp[n] == NULL) perror(name);        /* +1, print useful msg    */
  153.     if (n == ZDFILE && isatty(fileno(fp[n])))
  154.     setbuf(fp[n],NULL); /* Make debugging file unbuffered */
  155.     debug(F101, " fp[n]", "", (int) fp[n]);
  156.     return((fp[n] != NULL) ? 1 : 0);
  157. }
  158.  
  159. /*  Z C L O S E  --  Close the given file.  */
  160.  
  161. /*  Returns 0 if arg out of range, 1 if successful, -1 if close failed.  */
  162.  
  163. zclose(n) int n; {
  164.     int x;
  165.     if (chkfn(n) < 1) return(0);
  166.     if ((fp[n] != stdout) && (fp[n] != stdin)) x = fclose(fp[n]);
  167.     fp[n] = NULL;
  168.     return((x == EOF) ? -1 : 1);
  169. }
  170.  
  171. /*  Z C H I N  --  Get a character from the input file.  */
  172.  
  173. static int subprocess_input = 0, sub_count;
  174. static int input_mbxchn, output_mbxchn, child_pid = 0;
  175. static char *sub_ptr, sub_buf[200];
  176.  
  177. get_subprc_line() {
  178.     struct { short status, size, trm, trmsize; } subiosb;
  179.     if ((SYS$QIOW(0, output_mbxchn, IO$_READVBLK, &subiosb, 0, 0,
  180.         sub_buf, sizeof sub_buf, 0, 0, 0, 0) & 7) != 1
  181.     || (subiosb.status & 7) != 1) return(-1);
  182.     if (subiosb.size == 29
  183.      && strncmp(sub_buf, ">>> END OF KERMIT COMMAND <<<",
  184.         subiosb.size) == 0) {
  185.     subprocess_input = 0;
  186.     return(-1);
  187.     }
  188.     sub_buf[subiosb.size] = '\n';
  189.     sub_buf[subiosb.size + 1] = '\0';
  190.     sub_count = subiosb.size;
  191.     sub_ptr = sub_buf;
  192.     return(0);
  193. }
  194.  
  195. zchin(n,c) int n; char *c; {
  196.     int a;
  197.     if (n == ZIFILE && subprocess_input) {
  198.     if (--sub_count < 0)
  199.         if (get_subprc_line()) return(-1);
  200.     a = *sub_ptr++;
  201.     } else {
  202.     if (chkfn(n) < 1) return(-1);
  203.     a = getc(fp[n]);
  204.     }
  205.     if (a == EOF) return(-1);
  206.     *c = (a & 0377);
  207.     return(0);
  208. }
  209.  
  210.  
  211. /*  Z S O U T  --  Write a string to the given file, buffered.  */
  212.  
  213. zsout(n,s) int n; char *s; {
  214.     if (chkfn(n) < 1) return(-1);
  215.     fputs(s, fp[n]);            /* Don't use fprintf here MM */
  216.     return(0);
  217. }
  218.  
  219. /*  Z S O U T L  --  Write string to file, with line terminator, buffered  */
  220.  
  221. zsoutl(n,s) int n; char *s; {
  222.     if (chkfn(n) < 1) return(-1);
  223.     fputs(s, fp[n]);            /* Don't use fprintf MM */
  224.     putc('\n', fp[n]);
  225.     return(0);
  226. }
  227.  
  228. /*  Z S O U T X  --  Write x characters to file, unbuffered.  */
  229.  
  230. zsoutx(n,s,x) int n, x; char *s; {
  231.     if (chkfn(n) < 1) return(-1);
  232.     return(write(fp[n]->_file,s,x));
  233. }
  234.  
  235.  
  236. /*  Z C H O U T  --  Add a character to the given file.  */
  237.  
  238. zchout(n,c) int n; char c; {
  239.     if (chkfn(n) < 1) return(-1);
  240.     if (n == ZSFILE)
  241.         return(write(fp[n]->_file,&c,1)); /* Use unbuffered for session log */
  242.     else {
  243.     if (putc(c,fp[n]) == EOF)    /* If true, maybe there was an error */
  244.         return(ferror(fp[n]));    /* Check to make sure */
  245.     else                /* Otherwise... */
  246.         return(0);            /* There was no error. */
  247.     }
  248. }
  249.  
  250.  
  251. /*  C H K F N  --  Internal function to verify file number is ok  */
  252.  
  253. /*
  254.  Returns:
  255.   -1: File number n is out of range
  256.    0: n is in range, but file is not open
  257.    1: n in range and file is open
  258. */
  259. chkfn(n) int n; {
  260.     switch (n) {
  261.     case ZCTERM:
  262.     case ZSTDIO:
  263.     case ZIFILE:
  264.     case ZOFILE:
  265.     case ZDFILE:
  266.     case ZTFILE:
  267.     case ZPFILE:
  268.     case ZSFILE: break;
  269.     default:
  270.         debug(F101,"chkfn: file number out of range","",n);
  271.         fprintf(stderr,"?File number out of range - %d\n",n);
  272.         return(-1);
  273.     }
  274.     return( (fp[n] == NULL) ? 0 : 1 );
  275. }
  276.  
  277.  
  278. /*  Z C H K I  --  Check if input file exists and is readable  */
  279.  
  280. /*
  281.   Returns:
  282.    >= 0 if the file can be read (returns the size).
  283.      -1 if file doesn't exist or can't be accessed,
  284.      -2 if file exists but is not readable (e.g. a directory file).
  285.      -3 if file exists but protected against read access.
  286. */
  287. /*
  288.  For Berkeley Unix, a file must be of type "regular" to be readable.
  289.  Directory files, special files, and symbolic links are not readable.
  290. */
  291. long
  292. zchki(name) char *name; {
  293.     int x; long pos;
  294.  
  295.     x = open(name, 0);
  296.     if (x < 0) {
  297.     debug(F111,"zchki stat fails",name,errno);
  298.     return(-1);
  299.     }
  300.     pos = lseek(x, 0, 2);
  301.     close(x);
  302.     return(pos);
  303. }
  304.  
  305.  
  306. /*  Z C H K O  --  Check if output file can be created  */
  307.  
  308. /*
  309.  Returns -1 if write permission for the file would be denied, 0 otherwise.
  310. */
  311. zchko(name) char *name; {
  312.     return(0);                /* Always creates new version */
  313. }
  314.  
  315.  
  316. /*  Z D E L E T  --  Delete the named file.  */
  317.  
  318. zdelet(name) char *name; {
  319.     delete(name);
  320. }
  321.  
  322.  
  323. /*  Z R T O L  --  Convert remote filename into local form  */
  324.  
  325. /*  For VMS, we eliminate all special characters and truncate.  */
  326. /*  Doesn't allow the longer filespecs that VMS V4 supports.    */
  327. /*  Assumes version number delimited by semicolon, not period.  */
  328. /*  Should really use RMS to parse filespec components.  -- DS  */
  329.  
  330. zrtol(name,name2) char *name, *name2; {
  331.     int count;
  332.     char *cp;
  333.  
  334.     count = 9;
  335.     for ( cp = name2; *name != '\0'; name++ ) {
  336.     switch (*name) {
  337.         case '.':            /* File type */
  338.             count = 3;        /* Max length for this field */
  339.         *cp++ = '.';
  340.         break;
  341.         case ';':            /* Version */
  342.             count = 5;
  343.         *cp++ = ';';
  344.         break;
  345.         default:
  346.             if (count > 0 && isalnum(*name)) {
  347.             --count;
  348.             *cp++ = islower(*name) ? toupper(*name) : *name;
  349.         }
  350.         break;
  351.     }
  352.     }
  353.     *cp = '\0';                /* End of name */
  354.     debug(F110,"zrtol: ",name2,0);
  355. }
  356.  
  357.  
  358. /*  Z L T O R  --  Convert filename from local format to common form.   */
  359.  
  360. zltor(name,name2) char *name, *name2; {
  361.     char *cp, *pp;
  362.  
  363.     for (cp = pp = name; *cp != '\0'; cp++) {    /* strip path name */
  364.         if (*cp == ']' || *cp == ':') {
  365.         pp = cp;
  366.         pp++;
  367.     }
  368.     }
  369.     for ( ; --cp >= pp; ) {        /* From end to beginning */
  370.     if (!isdigit(*cp)) {        /* if not numeric, then */
  371.         if (*cp == '-') --cp;    /* if minus sign, skip over, or */
  372.         if (*cp == ';') *cp = '\0'; /* if version delim, make end */
  373.         break;
  374.     }
  375.     }
  376.     cp = name2;                /* If nothing before dot, */
  377.     if (*pp == '.') *cp++ = 'X';    /* insert 'X' */
  378.     strcpy(cp,pp);
  379.  
  380.     debug(F110,"zltor: ",name2,0);
  381. }    
  382.  
  383.  
  384. /*  Z C H D I R  --  Change directory  */
  385.  
  386. zchdir(dirnam) char *dirnam; {
  387.     char *hd;
  388.     if (*dirnam == '\0') hd = getenv("HOME");
  389.     else hd = dirnam;
  390.     return((chdir(hd) == 0) ? 1 : 0);
  391. }
  392.  
  393.  
  394. /*  Z H O M E  --  Return pointer to user's home directory  */
  395.  
  396. char *
  397. zhome() {
  398.     return(getenv("HOME"));
  399. }
  400.  
  401. /*  Z G T D I R  --  Return pointer to user's current directory  */
  402.  
  403. char *
  404. zgtdir() {
  405. /*    char *getcwd();
  406. /*    char cwdbuf[100];
  407. /*    char *buf;
  408. /*    buf = cwdbuf;
  409. /*    return(getcwd(buf,100));
  410. */
  411.     return("");  /* Can't seem to make LINK find getcwd()... */
  412. }
  413.  
  414. /*  Z X C M D -- Run a system command so its output can be read like a file */
  415.  
  416. zxcmd(comand) char *comand; {
  417.     char input_mbxnam[10], output_mbxnam[10];
  418.     char cmdbuf[200];
  419.  
  420.     if (child_pid == 0) {
  421.         struct dsc$descriptor_s inpdsc, outdsc;
  422.         struct { short buflen, code; char *bufadr; short *retlen; } itmlst[2];
  423.  
  424.         SYS$CREMBX(0, &input_mbxchn, 0, 0, 0, 0, 0);
  425.         itmlst[0].buflen = sizeof input_mbxnam;
  426.         itmlst[0].code   = DVI$_DEVNAM;
  427.         itmlst[0].bufadr = input_mbxnam;
  428.         itmlst[0].retlen = 0;
  429.         itmlst[1].buflen = 0;
  430.         itmlst[1].code   = 0;
  431.         SYS$GETDVI(0, input_mbxchn, 0, itmlst, 0, 0, 0, 0);
  432.         SYS$WAITFR(0);
  433.  
  434.         SYS$CREMBX(0, &output_mbxchn, 0, 0, 0, 0, 0);
  435.         itmlst[0].buflen = sizeof output_mbxnam;
  436.         itmlst[0].bufadr = output_mbxnam;
  437.         SYS$GETDVI(0, output_mbxchn, 0, itmlst, 0, 0, 0, 0);
  438.         SYS$WAITFR(0);
  439.  
  440.         inpdsc.dsc$w_length  = strlen(input_mbxnam);
  441.         inpdsc.dsc$b_dtype   = DSC$K_DTYPE_T;
  442.         inpdsc.dsc$b_class   = DSC$K_CLASS_S;
  443.         inpdsc.dsc$a_pointer = input_mbxnam;
  444.  
  445.         outdsc.dsc$w_length  = strlen(output_mbxnam);
  446.         outdsc.dsc$b_dtype   = DSC$K_DTYPE_T;
  447.         outdsc.dsc$b_class   = DSC$K_CLASS_S;
  448.         outdsc.dsc$a_pointer = output_mbxnam;
  449.  
  450.         LIB$SPAWN(0, &inpdsc, &outdsc, &1, 0, &child_pid);
  451.     SYS$QIOW(0, input_mbxchn, IO$_WRITEVBLK | IO$M_NOW, 0, 0, 0,
  452.         "$ SET NOON", 10, 0, 0, 0, 0);
  453.     }
  454.  
  455.     strcpy(cmdbuf, "$ ");
  456.     strcat(cmdbuf, comand);
  457.     SYS$QIOW(0, input_mbxchn, IO$_WRITEVBLK | IO$M_NOW, 0, 0, 0,
  458.         cmdbuf, strlen(cmdbuf), 0, 0, 0, 0);
  459.     SYS$QIOW(0, input_mbxchn, IO$_WRITEVBLK | IO$M_NOW, 0, 0, 0,
  460.         "$ WRITE SYS$OUTPUT \">>> END OF KERMIT COMMAND <<<\"",
  461.         50, 0, 0, 0, 0);
  462.     subprocess_input = 1;
  463.     sub_count = 0;
  464.     return(1);
  465. }
  466.  
  467. /*  Z C L O S F  - close the suprocess output file.  */
  468.  
  469. zclosf() {
  470. }
  471.  
  472. /*  Z K I L L F  - kill the subprocess used for host commands  */
  473. /*  The return value is 1 if the subprocess was killed successfully. */
  474. /*            -1 if there was no subprocess to kill. */
  475.  
  476. zkillf() {
  477.     if (child_pid == 0) return(-1);
  478.     return((SYS$DELPRC(&child_pid) & 7) == 1);
  479. }
  480.  
  481.  
  482. /*  Z X P A N D  --  Expand a wildcard string into an array of strings  */
  483. /*
  484.   Returns the number of files that match fn1, with data structures set up
  485.   so that first file (if any) will be returned by the next znext() call.
  486. */
  487. zxpand(fn) char *fn; {
  488.     fcount = fgen(fn,mtchs,MAXWLD);    /* Look up the file. */
  489.     if (fcount > 0) {
  490.     mtchptr = mtchs;        /* Save pointer for next. */
  491.     }
  492.     debug(F111,"zxpand",mtchs[0],fcount);
  493.     return(fcount);
  494. }
  495.  
  496.  
  497. /*  Z N E X T  --  Get name of next file from list created by zxpand(). */
  498. /*
  499.  Returns >0 if there's another file, with its name copied into the arg string,
  500.  or 0 if no more files in list.
  501. */
  502. znext(fn) char *fn; {
  503.     if (fcount-- > 0) strcpy(fn,*mtchptr++);
  504.     else *fn = '\0';
  505.     debug(F111,"znext",fn,fcount+1);
  506.     return(fcount+1);
  507. }
  508.  
  509.  
  510. /*  Z N E W N  --  Make a new name for the given file  */
  511.  
  512. znewn(fn,s) char *fn, **s; {
  513.     static char buf[100];
  514.     char *bp, *xp;
  515.     int len = 0, n = 0, d = 0, t;
  516.  
  517.     strcpy(buf, fn);            /* Version numbers are handled by OS */
  518.     *s = buf;
  519. }
  520.  
  521.  
  522. /*  Wildcard expansion for VMS is easy;  we just use a run-time library call.
  523. */
  524. fgen(pat,resarry,len)
  525. char *pat,*resarry[];
  526. int len;
  527. {
  528.     struct dsc$descriptor_s file_spec, result, deflt;
  529.     long context;
  530.     int count, slen, status, plen;
  531.     char *pp, *rp, result_string[256], *strchr();
  532.  
  533.     file_spec.dsc$w_length  = strlen(pat);
  534.     file_spec.dsc$b_dtype   = DSC$K_DTYPE_T;
  535.     file_spec.dsc$b_class   = DSC$K_CLASS_S;
  536.     file_spec.dsc$a_pointer = pat;
  537.  
  538.     result.dsc$w_length  = sizeof result_string;
  539.     result.dsc$b_dtype   = DSC$K_DTYPE_T;
  540.     result.dsc$b_class   = DSC$K_CLASS_S;
  541.     result.dsc$a_pointer = result_string;
  542.  
  543.     deflt.dsc$w_length  = 3;
  544.     deflt.dsc$b_dtype   = DSC$K_DTYPE_T;
  545.     deflt.dsc$b_class   = DSC$K_CLASS_S;
  546.     deflt.dsc$a_pointer = "*.*";
  547.  
  548.     count = 0;
  549.     context = 0;
  550.     pp = strchr(pat, ']');
  551.     if (pp == 0) pp = strchr(pat, ':');
  552.     if (pp == 0) plen = 0;
  553.     else plen = pp - pat + 1;
  554.     while (count < len
  555.        && (status = LIB$FIND_FILE(&file_spec, &result, &context, &deflt))
  556.         == RMS$_NORMAL) {
  557.     rp = strchr(result_string, ']') + 1;
  558.     slen = strchr(rp, ' ') - rp;
  559.         resarry[count] = malloc(slen + plen + 1);
  560.     if (plen != 0)
  561.         strncpy(resarry[count], pat, plen);
  562.     strncpy(resarry[count] + plen, rp, slen);
  563.     resarry[count][slen + plen] = '\0';
  564.     ++count;
  565.     }
  566. #ifdef DVI$_ALT_HOST_TYPE
  567.     lib$find_file_end(&context);    /* Only on V4 and later */
  568. #endif
  569.     if (status == RMS$_FNF) return(0);
  570.     if (status == RMS$_NMF) return(count);
  571.     return(-1);
  572. }
  573.  
  574. system(s)  char *s;  {
  575.     struct dsc$descriptor_s cmd;
  576.  
  577.     if ( *s ) {
  578.     zxcmd(s);
  579.     while (!get_subprc_line())
  580.         fputs(sub_buf, stdout);
  581.     putchar('\n');
  582.     } else {
  583.     LIB$SPAWN();
  584.     }
  585. }
  586.