Back to index

radiance  4R0+20100331
win_popen.c
Go to the documentation of this file.
00001 #ifndef lint
00002 static const char RCSid[] = "$Id: win_popen.c,v 1.4 2004/10/04 10:14:22 schorsch Exp $";
00003 #endif
00004 /*
00005 Replacement for the posix popen() on Windows
00006  
00007 We don't just let the shell do the work like the original.
00008 Since per default there's no decent shell around, we implement
00009 the most basic functionality right here.
00010 
00011 currently supports mode "r" and | to chain several processes.
00012 Substrings between matching quotes are considered one word,
00013 ignoring any | within. Quotes don't nest.
00014  
00015 */
00016 
00017 #include <windows.h>
00018 #include <stdlib.h>
00019 #include <stdio.h>
00020 #include <string.h>
00021 #include <ctype.h>
00022 #include <io.h>     /* _open_osfhandle()  */
00023 #include <fcntl.h>  /* _O_RDONLY          */
00024 
00025 #include "paths.h"
00026 #include "rtio.h"
00027 #include "rterror.h"
00028 
00029 
00030 #define RAD_MAX_PIPES 32 /* maximum number of pipes */
00031 
00032 #define R_MODE 1
00033 #define W_MODE 2
00034 #define A_MODE 3
00035 
00036 static int parse_pipes(char *s, char *lines[], char **infn, char **outfn,
00037 int *append, int maxl);
00038 static BOOL createPipes(HANDLE*, HANDLE*, HANDLE*, HANDLE*);
00039 static BOOL runChild(char*, char*, HANDLE, HANDLE, HANDLE);
00040 static void resetStdHandles(HANDLE stdoutOrig, HANDLE stdinOrig);
00041 HANDLE newFile(char *fn, int mode);
00042 
00043 
00044 int
00045 win_pclose(    /* posix pclose replacement */
00046 FILE* p
00047 )
00048 {
00049        fclose(p);
00050        /* not sure if it's useful to wait for anything on Windows */
00051        return 0;
00052 }
00053 
00054 
00055 FILE *
00056 win_popen(     /* posix popen replacement */
00057 char* command,
00058 char* type
00059 )
00060 {
00061        char *execfile, *args;
00062        char *cmdlines[RAD_MAX_PIPES];
00063        char *infn = NULL, *outfn = NULL;
00064        char executable[512], estr[512];
00065        int n, i, mode = 0, append = 0;
00066        int ncmds = 0;
00067        FILE *inf = NULL;
00068        HANDLE stdoutRd = NULL, stdoutWr = NULL;
00069        HANDLE stdinRd = NULL, stdinWr = NULL;
00070        HANDLE stderrWr = NULL;
00071        HANDLE stdoutOrig, stdinOrig;
00072 
00073        if (strchr(type, 'w')) {
00074               mode = W_MODE;
00075        } else if (strchr(type, 'r')) {
00076               mode = R_MODE;
00077        } else {
00078               _snprintf(estr, sizeof(estr),
00079                             "Invalid mode \"%s\" for win_popen().", type);
00080               eputs(estr);
00081               return NULL;
00082        }
00083 
00084        stdoutOrig = GetStdHandle(STD_OUTPUT_HANDLE);
00085        stdinOrig = GetStdHandle(STD_INPUT_HANDLE);
00086        /* if we have a console, use it for error output */
00087        stderrWr = GetStdHandle(STD_ERROR_HANDLE);
00088 
00089        ncmds = parse_pipes(command,cmdlines,&infn,&outfn,&append,RAD_MAX_PIPES);
00090        if(ncmds <= 0) {
00091               eputs("Too many pipes or malformed command.");
00092               goto error;
00093        }
00094 
00095        if (infn != NULL) {
00096               stdoutRd = newFile(infn, mode);
00097        }
00098 
00099        for(n = 0; n < ncmds; ++n) {
00100               if(!createPipes(&stdoutRd, &stdoutWr,
00101                      &stdinRd, &stdinWr)) {
00102                      eputs("Error creating pipe");
00103                      goto error;
00104               }
00105               if (outfn != NULL && n == ncmds - 1) {
00106                      CloseHandle(stdoutWr);
00107                      CloseHandle(stdoutRd);
00108                      stdoutWr = newFile(outfn, mode);
00109               }
00110               if (n == 0 && mode == W_MODE) {
00111                      /* create a standard C file pointer for writing to the input */
00112                      inf = _fdopen(_open_osfhandle((long)stdinWr, _O_RDONLY), "w");
00113               }
00114               /* find the executable on the PATH */
00115               args = nextword(executable, sizeof(executable), cmdlines[n]);
00116               if (args == NULL) {
00117                      eputs("Empty command.");
00118                      goto error;
00119               }
00120               execfile = getpath(executable, getenv("PATH"), X_OK);
00121               if(execfile == NULL) {
00122                      _snprintf(estr, sizeof(estr),
00123                                    "Can't find executable for \"%s\".", executable);
00124                      eputs(estr);
00125                      goto error;
00126               }
00127               if(!runChild(execfile, cmdlines[n], stdinRd, stdoutWr, stderrWr)) {
00128                      _snprintf(estr, sizeof(estr),
00129                                    "Unable to execute executable \"%s\".", executable);
00130                      eputs(estr);
00131                      goto error;
00132               }
00133               /* close the stdout end just passed to the last process,
00134                  or the final read will block */
00135               CloseHandle(stdoutWr);
00136        }
00137 
00138        /* clean up */
00139        resetStdHandles(stdinOrig, stdoutOrig);
00140        for (i = 0; i < ncmds; i++) free(cmdlines[i]);
00141        if (infn != NULL) free(infn);
00142        if (outfn != NULL) free(outfn);
00143 
00144        if (mode == R_MODE) {
00145               /* return a standard C file pointer for reading the output */
00146               return _fdopen(_open_osfhandle((long)stdoutRd, _O_RDONLY), "r");
00147        } else if (mode == W_MODE) {
00148               /* return a standard C file pointer for writing to the input */
00149               return inf;
00150        }
00151 
00152 error:
00153        resetStdHandles(stdinOrig, stdoutOrig);
00154        for (i = 0; i < ncmds; i++) free(cmdlines[i]);
00155        if (infn != NULL) free(infn);
00156        if (outfn != NULL) free(outfn);
00157        return NULL;
00158 }
00159 
00160 
00161 HANDLE
00162 newFile(char *fn, int mode)
00163 {
00164        SECURITY_ATTRIBUTES sAttr;
00165        HANDLE fh;
00166 
00167        sAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
00168        sAttr.bInheritHandle = TRUE;
00169        sAttr.lpSecurityDescriptor = NULL;
00170 
00171        if (mode == R_MODE) {
00172               fh = CreateFile(fn,
00173                             GENERIC_READ,
00174                             FILE_SHARE_READ,
00175                             &sAttr,
00176                             OPEN_EXISTING,
00177                             FILE_ATTRIBUTE_NORMAL,
00178                             NULL);
00179        } else if (mode == W_MODE || mode == A_MODE) {
00180               fh = CreateFile(fn,
00181                             GENERIC_WRITE,
00182                             FILE_SHARE_WRITE,
00183                             &sAttr,
00184                             mode==W_MODE?CREATE_ALWAYS:OPEN_ALWAYS,
00185                             FILE_ATTRIBUTE_NORMAL,
00186                             NULL);
00187        }
00188        if (fh == NULL) {
00189               int e = GetLastError();
00190        }
00191        return fh;
00192 }
00193 
00194 
00195 static BOOL
00196 createPipes(       /* establish matching pipes for a subprocess */
00197 HANDLE* stdoutRd,
00198 HANDLE* stdoutWr,
00199 HANDLE* stdinRd,
00200 HANDLE* stdinWr
00201 )
00202 {
00203        HANDLE stdoutRdInh = NULL;
00204        HANDLE stdinWrInh = NULL;
00205        HANDLE curproc;
00206        SECURITY_ATTRIBUTES sAttr;
00207        
00208        curproc = GetCurrentProcess();
00209        
00210        /* The rules of inheritance for handles are a mess.
00211           Just to be safe, make all handles we pass to the
00212           child processes inheritable */
00213        sAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
00214        sAttr.bInheritHandle = TRUE;
00215        sAttr.lpSecurityDescriptor = NULL;
00216        
00217        if(*stdoutRd != NULL){
00218        /* if we have a previous stdout pipe,
00219           assign the other end to stdin for the new process */
00220               CloseHandle(*stdinRd);
00221               if(!DuplicateHandle(curproc, *stdoutRd,
00222                      curproc, stdinRd, 0,
00223                      TRUE, DUPLICATE_SAME_ACCESS))
00224                      return FALSE;
00225               CloseHandle(*stdoutRd);
00226               if(!SetStdHandle(STD_INPUT_HANDLE, *stdinRd))
00227                      return FALSE;
00228        } else {
00229               /* there's no previous stdout, create a new stdin pipe */
00230               if(!CreatePipe(stdinRd, &stdinWrInh, &sAttr, 0))
00231                      return FALSE;
00232               if(!SetStdHandle(STD_INPUT_HANDLE, *stdinRd))
00233                      return FALSE;
00234               CloseHandle(stdinWrInh);
00235        }
00236        
00237        /* create the stdout pipe for the new process */
00238        if(!CreatePipe(&stdoutRdInh, stdoutWr, &sAttr, 0))
00239               return FALSE;
00240        if(!SetStdHandle(STD_OUTPUT_HANDLE, *stdoutWr))
00241               return FALSE;
00242        if(!DuplicateHandle(curproc, stdoutRdInh,
00243               curproc, stdoutRd, 0,
00244               FALSE, DUPLICATE_SAME_ACCESS))
00245               return FALSE;
00246        CloseHandle(stdoutRdInh);
00247        
00248        return TRUE;
00249 }
00250 
00251 
00252 static void
00253 resetStdHandles(    /* clean up our std streams */
00254 HANDLE stdoutOrig,
00255 HANDLE stdinOrig
00256 )
00257 {
00258        SetStdHandle(STD_OUTPUT_HANDLE, stdoutOrig);
00259        SetStdHandle(STD_INPUT_HANDLE, stdinOrig);
00260 }
00261 
00262 
00263 static int
00264 parse_pipes(     /* split a shell command pipe sequence */
00265 char *s,
00266 char *lines[],
00267 char **infn,
00268 char **outfn,
00269 int *append,
00270 int maxl
00271 )
00272 {
00273        int n = 0, i;
00274        char *se, *ws;
00275        char *curs;
00276        int llen = 0;
00277        int quote = 0;
00278        int last = 0;
00279 
00280        if (maxl<= 0) return 0;
00281        if (s == NULL) {
00282               return 0;
00283        }
00284        *infn = *outfn = NULL;
00285        while (isspace(*s)) s++; /* leading whitespace */
00286        se = s;
00287        while (n < maxl) {
00288               switch (*se) {
00289                      case '"':
00290                             if (quote == '"') quote = 0;
00291                             else if (quote == 0) quote = '"';
00292                             break;
00293                      case '\'':
00294                             if (quote == '\'') quote = 0;
00295                             else if (quote == 0) quote = '\'';
00296                             break;
00297                      case '<':
00298                      case '>':
00299                      case '|':
00300                      case '\0':
00301                             if (*se != '\0' && quote)
00302                                    break;
00303                             llen = se - s;
00304                             curs = malloc(llen+1);
00305                             strncpy(curs, s, llen);
00306                             /* remove unix style line-end escapes */
00307                             while((ws = strstr(curs, "\\\n")) != NULL)
00308                                    *ws = *(ws+1) = ' ';
00309                             /* remove DOS style line-end escapes */
00310                             while((ws = strstr(curs, "\\\r\n")) != NULL)
00311                                    *ws = *(ws+1) = *(ws+2) = ' ';
00312                             while (isspace(*(curs + llen - 1)))
00313                                    llen--; /* trailing whitespace */
00314                             curs[llen] = '\0';
00315 
00316                             if (last == '|' || last == 0) { /* first or pipe */
00317                                    lines[n] = curs;
00318                                    n++;
00319                                    curs = NULL;
00320                             } else if (last == '<') { /* input file */
00321                                    if (*infn != NULL) {
00322                                           eputs("win_popen(): ambiguous input redirection");
00323                                           goto error;
00324                                    }
00325                                    *infn = curs;
00326                                    curs = NULL;
00327                             } else if (last == '>') { /* output file */
00328                                    if (*outfn != NULL) {
00329                                           eputs("win_popen(): ambiguous output redirection");
00330                                           goto error;
00331                                    }
00332                                    *outfn = curs;
00333                                    curs = NULL;
00334                                    if (*se != '\0' && *se+1 == '>') { /* >> */
00335                                           *append = 1;
00336                                           se++;
00337                                    }
00338                             }
00339                             last = *se;
00340 
00341                             if (*se == '\0') return n;
00342                             s = se + 1;
00343                             while (isspace(*s)) s++; /* leading whitespace */
00344                             se = s;
00345                             break;
00346                      default:
00347                             break;
00348               }
00349               se++;
00350        }
00351        /* more jobs than slots */
00352 error:
00353        for (i = 0; i < n; i++) free(lines[i]);
00354        if (*infn != NULL) free(*infn);
00355        if (*outfn != NULL) free(*outfn);
00356        if (curs != NULL) free(curs);
00357        return -1;
00358 }
00359 
00360 
00361 static BOOL
00362 runChild(         /* start a child process with the right std streams */
00363 char* executable,
00364 char* cmdline,
00365 HANDLE stdinRd,
00366 HANDLE stdoutWr,
00367 HANDLE stderrWr
00368 )
00369 {
00370        PROCESS_INFORMATION procInfo;
00371        STARTUPINFO startupInfo;
00372 
00373        /* use the given handles and don't display the console window */
00374        ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
00375        startupInfo.cb = sizeof(STARTUPINFO);
00376        startupInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
00377        startupInfo.wShowWindow = SW_HIDE;
00378        startupInfo.hStdInput = stdinRd;
00379        startupInfo.hStdOutput = stdoutWr;
00380        startupInfo.hStdError = stderrWr;
00381 
00382        return CreateProcess(executable, cmdline, NULL, NULL,
00383               TRUE, 0,
00384               NULL, NULL, &startupInfo, &procInfo);
00385 }
00386 
00387 
00388