| 1 | 
schorsch | 
1.1 | 
#ifndef lint | 
| 2 | 
  | 
  | 
static const char RCSid[] = "$Id$"; | 
| 3 | 
  | 
  | 
#endif | 
| 4 | 
  | 
  | 
/* | 
| 5 | 
  | 
  | 
Replacement for the posix popen() on Windows | 
| 6 | 
  | 
  | 
  | 
| 7 | 
  | 
  | 
We don't just let the shell do the work like the original. | 
| 8 | 
  | 
  | 
Since per default there's no decent shell around, we implement | 
| 9 | 
  | 
  | 
the most basic functionality right here. | 
| 10 | 
  | 
  | 
 | 
| 11 | 
  | 
  | 
currently supports mode "r" and | to chain several processes. | 
| 12 | 
  | 
  | 
Substrings between matching quotes are considered one word, | 
| 13 | 
  | 
  | 
ignoring any | within. Quotes don't nest. | 
| 14 | 
  | 
  | 
  | 
| 15 | 
  | 
  | 
*/ | 
| 16 | 
  | 
  | 
 | 
| 17 | 
  | 
  | 
#include <windows.h> | 
| 18 | 
  | 
  | 
#include <stdlib.h> | 
| 19 | 
  | 
  | 
#include <stdio.h> | 
| 20 | 
  | 
  | 
#include <string.h> | 
| 21 | 
  | 
  | 
#include <ctype.h> | 
| 22 | 
  | 
  | 
#include <io.h>     /* _open_osfhandle()  */ | 
| 23 | 
  | 
  | 
#include <fcntl.h>  /* _O_RDONLY          */ | 
| 24 | 
  | 
  | 
 | 
| 25 | 
  | 
  | 
#include "paths.h" | 
| 26 | 
  | 
  | 
#include "rtio.h" | 
| 27 | 
  | 
  | 
#include "rterror.h" | 
| 28 | 
  | 
  | 
 | 
| 29 | 
  | 
  | 
 | 
| 30 | 
  | 
  | 
#define RAD_MAX_PIPES 32 /* maximum number of pipes */ | 
| 31 | 
  | 
  | 
 | 
| 32 | 
  | 
  | 
static int parse_pipes(char*, char**, int); | 
| 33 | 
  | 
  | 
static BOOL createPipes(HANDLE*, HANDLE*, HANDLE*, HANDLE*); | 
| 34 | 
  | 
  | 
static BOOL runChild(char*, char*, HANDLE, HANDLE, HANDLE); | 
| 35 | 
  | 
  | 
static void resetStdHandles(HANDLE stdoutOrig, HANDLE stdinOrig); | 
| 36 | 
  | 
  | 
 | 
| 37 | 
  | 
  | 
 | 
| 38 | 
  | 
  | 
int | 
| 39 | 
  | 
  | 
pclose(    /* posix pclose replacement */ | 
| 40 | 
  | 
  | 
FILE* p | 
| 41 | 
  | 
  | 
) | 
| 42 | 
  | 
  | 
{ | 
| 43 | 
  | 
  | 
        fclose(p); | 
| 44 | 
  | 
  | 
        /* not sure if it's useful to wait for anything on Windows */ | 
| 45 | 
  | 
  | 
        return 0; | 
| 46 | 
  | 
  | 
} | 
| 47 | 
  | 
  | 
 | 
| 48 | 
  | 
  | 
 | 
| 49 | 
  | 
  | 
FILE * | 
| 50 | 
  | 
  | 
popen(     /* posix popen replacement */ | 
| 51 | 
  | 
  | 
char* command, | 
| 52 | 
  | 
  | 
char* type | 
| 53 | 
  | 
  | 
) | 
| 54 | 
  | 
  | 
{ | 
| 55 | 
  | 
  | 
        char *execfile, *args; | 
| 56 | 
  | 
  | 
        char *cmdlines[RAD_MAX_PIPES]; | 
| 57 | 
  | 
  | 
        char executable[512]; | 
| 58 | 
  | 
  | 
        int n, i; | 
| 59 | 
  | 
  | 
        int ncmds = 0; | 
| 60 | 
  | 
  | 
        HANDLE stdoutRd = NULL, stdoutWr = NULL; | 
| 61 | 
  | 
  | 
        HANDLE stdinRd = NULL, stdinWr = NULL; | 
| 62 | 
  | 
  | 
        HANDLE stderrWr = NULL; | 
| 63 | 
  | 
  | 
        HANDLE stdoutOrig, stdinOrig; | 
| 64 | 
  | 
  | 
 | 
| 65 | 
  | 
  | 
        stdoutOrig = GetStdHandle(STD_OUTPUT_HANDLE); | 
| 66 | 
  | 
  | 
        stdinOrig = GetStdHandle(STD_INPUT_HANDLE); | 
| 67 | 
  | 
  | 
        /* if we have a console, use it for error output */ | 
| 68 | 
  | 
  | 
        stderrWr = GetStdHandle(STD_ERROR_HANDLE); | 
| 69 | 
  | 
  | 
 | 
| 70 | 
  | 
  | 
        if((ncmds = parse_pipes(command, cmdlines, RAD_MAX_PIPES)) <= 0) { | 
| 71 | 
  | 
  | 
                eputs("Too many pipes or malformed command."); | 
| 72 | 
  | 
  | 
                goto error; | 
| 73 | 
  | 
  | 
        } | 
| 74 | 
  | 
  | 
 | 
| 75 | 
  | 
  | 
        for(n = 0; n < ncmds; ++n) { | 
| 76 | 
  | 
  | 
                if(!createPipes(&stdoutRd, &stdoutWr, | 
| 77 | 
  | 
  | 
                        &stdinRd, &stdinWr)) { | 
| 78 | 
  | 
  | 
                        eputs("Error creating pipe"); | 
| 79 | 
  | 
  | 
                        goto error; | 
| 80 | 
  | 
  | 
                } | 
| 81 | 
  | 
  | 
                /* find the executable on the PATH */ | 
| 82 | 
  | 
  | 
                args = nextword(executable, sizeof(executable), cmdlines[n]); | 
| 83 | 
  | 
  | 
                if (args == NULL) { | 
| 84 | 
  | 
  | 
                        eputs("Empty command."); | 
| 85 | 
  | 
  | 
                        goto error; | 
| 86 | 
  | 
  | 
                } | 
| 87 | 
  | 
  | 
                execfile = getpath(executable, getenv("PATH"), X_OK); | 
| 88 | 
  | 
  | 
                if(execfile == NULL) { | 
| 89 | 
  | 
  | 
                        char estr[512]; | 
| 90 | 
  | 
  | 
                        _snprintf(estr, sizeof(estr), | 
| 91 | 
  | 
  | 
                                        "Can't find executable for \"%s\".", executable); | 
| 92 | 
  | 
  | 
                        eputs(estr); | 
| 93 | 
  | 
  | 
                        goto error; | 
| 94 | 
  | 
  | 
                } | 
| 95 | 
  | 
  | 
                if(!runChild(execfile, cmdlines[n], stdinRd, stdoutWr, stderrWr)) { | 
| 96 | 
  | 
  | 
                        char estr[512]; | 
| 97 | 
  | 
  | 
                        _snprintf(estr, sizeof(estr), | 
| 98 | 
  | 
  | 
                                        "Unable to execute executable \"%s\".", executable); | 
| 99 | 
  | 
  | 
                        eputs(estr); | 
| 100 | 
  | 
  | 
                        goto error; | 
| 101 | 
  | 
  | 
                } | 
| 102 | 
  | 
  | 
                /* close the stdout end just passed to the last process, | 
| 103 | 
  | 
  | 
                   or the final read will block */ | 
| 104 | 
  | 
  | 
                CloseHandle(stdoutWr); | 
| 105 | 
  | 
  | 
        } | 
| 106 | 
  | 
  | 
        /* clean up */ | 
| 107 | 
  | 
  | 
        resetStdHandles(stdinOrig, stdoutOrig); | 
| 108 | 
  | 
  | 
        for (i = 0; i < ncmds; i++) free(cmdlines[i]); | 
| 109 | 
  | 
  | 
 | 
| 110 | 
  | 
  | 
        /* return a standard C file pointer for reading the output */ | 
| 111 | 
  | 
  | 
        return _fdopen(_open_osfhandle((long)stdoutRd, _O_RDONLY), "r"); | 
| 112 | 
  | 
  | 
 | 
| 113 | 
  | 
  | 
error: | 
| 114 | 
  | 
  | 
        resetStdHandles(stdinOrig, stdoutOrig); | 
| 115 | 
  | 
  | 
        for (i = 0; i < ncmds; i++) free(cmdlines[i]); | 
| 116 | 
  | 
  | 
        return NULL; | 
| 117 | 
  | 
  | 
} | 
| 118 | 
  | 
  | 
 | 
| 119 | 
  | 
  | 
 | 
| 120 | 
  | 
  | 
static BOOL | 
| 121 | 
  | 
  | 
createPipes(       /* establish matching pipes for a subprocess */ | 
| 122 | 
  | 
  | 
HANDLE* stdoutRd, | 
| 123 | 
  | 
  | 
HANDLE* stdoutWr, | 
| 124 | 
  | 
  | 
HANDLE* stdinRd, | 
| 125 | 
  | 
  | 
HANDLE* stdinWr | 
| 126 | 
  | 
  | 
) | 
| 127 | 
  | 
  | 
{ | 
| 128 | 
  | 
  | 
        HANDLE stdoutRdInh = NULL; | 
| 129 | 
  | 
  | 
        HANDLE stdinWrInh = NULL; | 
| 130 | 
  | 
  | 
        HANDLE curproc; | 
| 131 | 
  | 
  | 
        SECURITY_ATTRIBUTES sAttr; | 
| 132 | 
  | 
  | 
         | 
| 133 | 
  | 
  | 
        curproc = GetCurrentProcess(); | 
| 134 | 
  | 
  | 
         | 
| 135 | 
  | 
  | 
        /* The rules of inheritance for handles are a mess. | 
| 136 | 
  | 
  | 
           Just to be safe, make all handles we pass to the | 
| 137 | 
  | 
  | 
           child processes inheritable */ | 
| 138 | 
  | 
  | 
        sAttr.nLength = sizeof(SECURITY_ATTRIBUTES); | 
| 139 | 
  | 
  | 
        sAttr.bInheritHandle = TRUE; | 
| 140 | 
  | 
  | 
        sAttr.lpSecurityDescriptor = NULL; | 
| 141 | 
  | 
  | 
         | 
| 142 | 
  | 
  | 
        if(*stdoutRd != NULL){ | 
| 143 | 
  | 
  | 
        /* if we have a previous stdout pipe, | 
| 144 | 
  | 
  | 
           assign the other end to stdin for the new process */ | 
| 145 | 
  | 
  | 
                CloseHandle(*stdinRd); | 
| 146 | 
  | 
  | 
                if(!DuplicateHandle(curproc, *stdoutRd, | 
| 147 | 
  | 
  | 
                        curproc, stdinRd, 0, | 
| 148 | 
  | 
  | 
                        TRUE, DUPLICATE_SAME_ACCESS)) | 
| 149 | 
  | 
  | 
                        return FALSE; | 
| 150 | 
  | 
  | 
                CloseHandle(*stdoutRd); | 
| 151 | 
  | 
  | 
                if(!SetStdHandle(STD_INPUT_HANDLE, *stdinRd)) | 
| 152 | 
  | 
  | 
                        return FALSE; | 
| 153 | 
  | 
  | 
        } else { | 
| 154 | 
  | 
  | 
                /* there's no previous stdout, create a new stdin pipe */ | 
| 155 | 
  | 
  | 
                if(!CreatePipe(stdinRd, &stdinWrInh, &sAttr, 0)) | 
| 156 | 
  | 
  | 
                        return FALSE; | 
| 157 | 
  | 
  | 
                if(!SetStdHandle(STD_INPUT_HANDLE, *stdinRd)) | 
| 158 | 
  | 
  | 
                        return FALSE; | 
| 159 | 
  | 
  | 
                CloseHandle(stdinWrInh); | 
| 160 | 
  | 
  | 
        } | 
| 161 | 
  | 
  | 
         | 
| 162 | 
  | 
  | 
        /* create the stdout pipe for the new process */ | 
| 163 | 
  | 
  | 
        if(!CreatePipe(&stdoutRdInh, stdoutWr, &sAttr, 0)) | 
| 164 | 
  | 
  | 
                return FALSE; | 
| 165 | 
  | 
  | 
        if(!SetStdHandle(STD_OUTPUT_HANDLE, *stdoutWr)) | 
| 166 | 
  | 
  | 
                return FALSE; | 
| 167 | 
  | 
  | 
        if(!DuplicateHandle(curproc, stdoutRdInh, | 
| 168 | 
  | 
  | 
                curproc, stdoutRd, 0, | 
| 169 | 
  | 
  | 
                FALSE, DUPLICATE_SAME_ACCESS)) | 
| 170 | 
  | 
  | 
                return FALSE; | 
| 171 | 
  | 
  | 
        CloseHandle(stdoutRdInh); | 
| 172 | 
  | 
  | 
         | 
| 173 | 
  | 
  | 
        return TRUE; | 
| 174 | 
  | 
  | 
} | 
| 175 | 
  | 
  | 
 | 
| 176 | 
  | 
  | 
 | 
| 177 | 
  | 
  | 
static void | 
| 178 | 
  | 
  | 
resetStdHandles(    /* clean up our std streams */ | 
| 179 | 
  | 
  | 
HANDLE stdoutOrig, | 
| 180 | 
  | 
  | 
HANDLE stdinOrig | 
| 181 | 
  | 
  | 
) | 
| 182 | 
  | 
  | 
{ | 
| 183 | 
  | 
  | 
        SetStdHandle(STD_OUTPUT_HANDLE, stdoutOrig); | 
| 184 | 
  | 
  | 
        SetStdHandle(STD_INPUT_HANDLE, stdinOrig); | 
| 185 | 
  | 
  | 
} | 
| 186 | 
  | 
  | 
 | 
| 187 | 
  | 
  | 
 | 
| 188 | 
  | 
  | 
static int | 
| 189 | 
  | 
  | 
parse_pipes(     /* split a shell command pipe sequence */ | 
| 190 | 
  | 
  | 
char* s, | 
| 191 | 
  | 
  | 
char* lines[], | 
| 192 | 
  | 
  | 
int maxl | 
| 193 | 
  | 
  | 
) | 
| 194 | 
  | 
  | 
{ | 
| 195 | 
  | 
  | 
        int n = 0, i; | 
| 196 | 
  | 
  | 
        char *se, *ws; | 
| 197 | 
  | 
  | 
        int llen = 0; | 
| 198 | 
  | 
  | 
        int quote = 0; | 
| 199 | 
  | 
  | 
 | 
| 200 | 
  | 
  | 
        if (maxl<= 0) return 0; | 
| 201 | 
  | 
  | 
        if (s == NULL) { | 
| 202 | 
  | 
  | 
                return 0; | 
| 203 | 
  | 
  | 
        } | 
| 204 | 
  | 
  | 
        while (isspace(*s)) s++; /* leading whitespace */ | 
| 205 | 
  | 
  | 
        se = s; | 
| 206 | 
  | 
  | 
        while (n < maxl) { | 
| 207 | 
  | 
  | 
                switch (*se) { | 
| 208 | 
  | 
  | 
                        case '"': | 
| 209 | 
  | 
  | 
                                if (quote == '"') quote = 0; | 
| 210 | 
  | 
  | 
                                else if (quote == 0) quote = '"'; | 
| 211 | 
  | 
  | 
                                break; | 
| 212 | 
  | 
  | 
                        case '\'': | 
| 213 | 
  | 
  | 
                                if (quote == '\'') quote = 0; | 
| 214 | 
  | 
  | 
                                else if (quote == 0) quote = '\''; | 
| 215 | 
  | 
  | 
                                break; | 
| 216 | 
  | 
  | 
                        case '|': | 
| 217 | 
  | 
  | 
                        case '\0': | 
| 218 | 
  | 
  | 
                                if (*se == '|' && quote) | 
| 219 | 
  | 
  | 
                                        break; | 
| 220 | 
  | 
  | 
                                llen = se - s; | 
| 221 | 
  | 
  | 
                                lines[n] = malloc(llen+1); | 
| 222 | 
  | 
  | 
                                strncpy(lines[n], s, llen); | 
| 223 | 
  | 
  | 
                                /* remove unix style line-end escapes */ | 
| 224 | 
  | 
  | 
                                while((ws = strstr(lines[n], "\\\n")) != NULL) | 
| 225 | 
  | 
  | 
                                        *ws = *(ws+1) = ' '; | 
| 226 | 
  | 
  | 
                                /* remove DOS style line-end escapes */ | 
| 227 | 
  | 
  | 
                                while((ws = strstr(lines[n], "\\\r\n")) != NULL) | 
| 228 | 
  | 
  | 
                                        *ws = *(ws+1) = *(ws+2) = ' '; | 
| 229 | 
  | 
  | 
                                while (isspace(*(lines[n] + llen - 1))) | 
| 230 | 
  | 
  | 
                                        llen--; /* trailing whitespace */ | 
| 231 | 
  | 
  | 
                                lines[n][llen] = '\0'; | 
| 232 | 
  | 
  | 
                                n++; | 
| 233 | 
  | 
  | 
                                if (*se == '\0') return n; | 
| 234 | 
  | 
  | 
                                s = se + 1; | 
| 235 | 
  | 
  | 
                                while (isspace(*s)) s++; /* leading whitespace */ | 
| 236 | 
  | 
  | 
                                se = s; | 
| 237 | 
  | 
  | 
                                break; | 
| 238 | 
  | 
  | 
                        default: | 
| 239 | 
  | 
  | 
                                break; | 
| 240 | 
  | 
  | 
                } | 
| 241 | 
  | 
  | 
                se++; | 
| 242 | 
  | 
  | 
        } | 
| 243 | 
  | 
  | 
        /* more jobs than slots */ | 
| 244 | 
  | 
  | 
        for (i = 0; i < n; i++) free(lines[i]); | 
| 245 | 
  | 
  | 
        return -1; | 
| 246 | 
  | 
  | 
} | 
| 247 | 
  | 
  | 
 | 
| 248 | 
  | 
  | 
 | 
| 249 | 
  | 
  | 
static BOOL | 
| 250 | 
  | 
  | 
runChild(         /* start a child process with the right std streams */ | 
| 251 | 
  | 
  | 
char* executable, | 
| 252 | 
  | 
  | 
char* cmdline, | 
| 253 | 
  | 
  | 
HANDLE stdinRd, | 
| 254 | 
  | 
  | 
HANDLE stdoutWr, | 
| 255 | 
  | 
  | 
HANDLE stderrWr | 
| 256 | 
  | 
  | 
) | 
| 257 | 
  | 
  | 
{ | 
| 258 | 
  | 
  | 
        PROCESS_INFORMATION procInfo; | 
| 259 | 
  | 
  | 
        STARTUPINFO startupInfo; | 
| 260 | 
  | 
  | 
 | 
| 261 | 
  | 
  | 
        /* use the given handles and don't display the console window */ | 
| 262 | 
  | 
  | 
        ZeroMemory(&startupInfo, sizeof(STARTUPINFO)); | 
| 263 | 
  | 
  | 
        startupInfo.cb = sizeof(STARTUPINFO); | 
| 264 | 
  | 
  | 
        startupInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; | 
| 265 | 
  | 
  | 
        startupInfo.wShowWindow = SW_HIDE; | 
| 266 | 
  | 
  | 
        startupInfo.hStdInput = stdinRd; | 
| 267 | 
  | 
  | 
        startupInfo.hStdOutput = stdoutWr; | 
| 268 | 
  | 
  | 
        startupInfo.hStdError = stderrWr; | 
| 269 | 
  | 
  | 
 | 
| 270 | 
  | 
  | 
        return CreateProcess(executable, cmdline, NULL, NULL, | 
| 271 | 
  | 
  | 
                TRUE, 0, | 
| 272 | 
  | 
  | 
                NULL, NULL, &startupInfo, &procInfo); | 
| 273 | 
  | 
  | 
} | 
| 274 | 
  | 
  | 
 | 
| 275 | 
  | 
  | 
 | 
| 276 | 
  | 
  | 
 |