Annotation of parser3/src/main/pa_exec.C, revision 1.108

1.1       paf         1: /** @file
                      2:        Parser: program executing for different OS-es.
                      3: 
1.105     moko        4:        Copyright (c) 2001-2026 Art. Lebedev Studio (https://www.artlebedev.com)
1.97      moko        5:        Authors: Konstantin Morshnev <moko@design.ru>, Alexandr Petrosian <paf@design.ru>
1.1       paf         6: 
1.18      paf         7:        @todo setrlimit
1.1       paf         8: */
1.38      paf         9: 
1.1       paf        10: #include "pa_config_includes.h"
                     11: 
1.19      paf        12: #include "pa_exec.h"
                     13: #include "pa_exception.h"
                     14: #include "pa_common.h"
                     15: 
1.108   ! moko       16: volatile const char * IDENT_PA_EXEC_C="$Id: pa_exec.C,v 1.107 2026/10/03 14:30:03 moko Exp $" IDENT_PA_EXEC_H;
        !            17: 
        !            18: THREAD_LOCAL int pa_exec_active_pid=0;
1.82      moko       19: 
1.84      moko       20: #ifdef _MSC_VER
1.1       paf        21: 
1.83      moko       22: #include <windows.h>
1.1       paf        23: 
                     24: /// this func from http://www.ccas.ru/~posp/popov/spawn.htm
1.36      paf        25: static DWORD CreateHiddenConsoleProcess(LPCTSTR szCmdLine,
1.88      moko       26:                                        LPCTSTR szScriptFileSpec,
                     27:                                        char *szEnv,
                     28:                                        PROCESS_INFORMATION* ppi, 
                     29:                                        LPHANDLE phInWrite,
                     30:                                        LPHANDLE phOutRead,
                     31:                                        LPHANDLE phErrRead)
1.1       paf        32: {
1.51      paf        33:        DWORD result=0;
1.36      paf        34:        BOOL fCreated;
1.51      paf        35:        STARTUPINFO si;
                     36:        SECURITY_ATTRIBUTES sa={0};
                     37:        HANDLE hInRead;
                     38:        HANDLE hOutWrite;
                     39:        HANDLE hErrWrite;
                     40:        
                     41:        // Create pipes
                     42:        // initialize security attributes for handle inheritance (for WinNT)
                     43:        sa.nLength=sizeof(sa);
                     44:        sa.bInheritHandle=TRUE;
                     45:        sa.lpSecurityDescriptor=NULL;
                     46:        
                     47:        // create STDIN pipe
                     48:        if(!CreatePipe(&hInRead, phInWrite, &sa, 0))
                     49:                goto error;
1.86      moko       50: 
                     51:        // Ensure the write handle to the pipe for STDIN is not inherited. 
                     52:        if (!SetHandleInformation(*phInWrite, HANDLE_FLAG_INHERIT, 0))
                     53:                goto error;
                     54: 
1.51      paf        55:        // create STDOUT pipe
                     56:        if(!CreatePipe(phOutRead, &hOutWrite, &sa, 0))
                     57:                goto error;
                     58:        
1.86      moko       59:        // Ensure the read handle to the pipe for STDOUT is not inherited.
                     60:        if (!SetHandleInformation(*phOutRead, HANDLE_FLAG_INHERIT, 0))
                     61:                goto error;
                     62: 
1.51      paf        63:        // create STDERR pipe
                     64:        if(!CreatePipe(phErrRead, &hErrWrite, &sa, 0))
                     65:                goto error;
                     66:        
1.86      moko       67:        // Ensure the read handle to the pipe for STDERR is not inherited.
                     68:        if (!SetHandleInformation(*phErrRead, HANDLE_FLAG_INHERIT, 0))
                     69:                goto error;
                     70: 
1.51      paf        71:        // process startup information
                     72:        memset(&si, 0, sizeof(si));
                     73:        si.cb=sizeof(si); 
                     74:        si.dwFlags=STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
                     75:        // child process' console must be hidden for Win95 compatibility
                     76:        si.wShowWindow=SW_HIDE;
                     77:        // assign "other" sides of pipes
                     78:        si.hStdInput=hInRead;
                     79:        si.hStdOutput=hOutWrite;
                     80:        si.hStdError=hErrWrite;
                     81:        
1.31      paf        82:        // calculating script's directory
                     83:        char dir[MAX_STRING];
1.96      moko       84:        pa_strncpy(dir, szScriptFileSpec, MAX_STRING);
1.33      paf        85:        lsplit(dir,' '); // trim arguments
1.106     moko       86:        rsplit(dir,"/\\"); // trim filename
1.51      paf        87:        
                     88:        // Create a child process (suspended)
                     89:        fCreated=CreateProcess(NULL,
                     90:                (LPTSTR)szCmdLine,
                     91:                NULL,
                     92:                NULL,
                     93:                TRUE,
                     94:                CREATE_NO_WINDOW,
                     95:                szEnv,
                     96:                dir,
                     97:                &si,
                     98:                ppi);
1.36      paf        99:        if(!fCreated)
                    100:                result=GetLastError();
1.51      paf       101:        
                    102:        CloseHandle(hInRead);
                    103:        CloseHandle(hOutWrite);
                    104:        CloseHandle(hErrWrite);
                    105:        
                    106:        if(!fCreated)
                    107:                goto error;
                    108:        
                    109:        return result;
                    110:        
1.1       paf       111: error:
1.36      paf       112:        if(!result/*yet*/)
                    113:                result=GetLastError(); // get it
1.51      paf       114:        
                    115:        CloseHandle(*phInWrite);
                    116:        CloseHandle(*phOutRead);
                    117:        CloseHandle(*phErrRead);
                    118:        
                    119:        return result;
                    120: }
1.36      paf       121: 
1.87      moko      122: static int get_exit_status(HANDLE hProcess) {
                    123:        DWORD dwExitCode = 0;
                    124:        if(!GetExitCodeProcess(hProcess, &dwExitCode))
                    125:                return -1;
                    126:        if(dwExitCode != STILL_ACTIVE)
                    127:                return dwExitCode;
                    128:        // wait for 1 second for process to exit
                    129:        if(WaitForSingleObject(hProcess, 1000) != WAIT_OBJECT_0)
                    130:                return -2;
                    131:        if(!GetExitCodeProcess(hProcess, &dwExitCode))
                    132:                return -1;
                    133:        return dwExitCode;
                    134: }
                    135: 
1.103     moko      136: static void read_pipe(String& result, HANDLE hOutRead, String::Language lang) {
1.51      paf       137:        while(true) {
1.77      misha     138:                char *buf=new(PointerFreeGC) char[MAX_STRING+1];
1.78      misha     139:                DWORD size=0;
1.77      misha     140:                if(!ReadFile(hOutRead, buf, MAX_STRING, &size, NULL) || !size) 
1.51      paf       141:                        break;
                    142:                buf[size]=0;
                    143:                result.append_know_length(buf, size, lang);
                    144:        }
1.1       paf       145: }
                    146: 
1.103     moko      147: static void read_pipe(File_read_result& result, HANDLE hOutRead) {
1.71      misha     148: 
1.77      misha     149:        char *buf=(char*)pa_malloc(MAX_STRING+1);
                    150:        DWORD bufsize = MAX_STRING;
1.70      misha     151: 
                    152:        result.headers = 0;
                    153:        result.length = 0;
                    154:        result.str = 0;
                    155:        result.success = false;
                    156: 
                    157:        while(true) {
1.77      misha     158:                DWORD size=0;
                    159:                if(!ReadFile(hOutRead, buf + result.length, bufsize - result.length, &size, NULL) || !size)
1.70      misha     160:                        break;
1.77      misha     161:                result.length += size;
                    162:                if(result.length >= bufsize){
                    163:                        bufsize *= 2;
                    164:                        buf=(char*)pa_realloc(buf, bufsize+1);
1.70      misha     165:                }
1.77      misha     166:                result.str=buf;
1.70      misha     167:        }
                    168: }
                    169: 
1.102     moko      170: static const char* arg_quote(const char *arg) {
1.101     moko      171:        size_t length = strlen(arg);
1.103     moko      172:        if (length >= 1 && arg[0] == '-')
                    173:                return arg; // avoid options qouting
1.101     moko      174:        if (length >= 2 && arg[0] == '"' && arg[length - 1] == '"')
                    175:                return arg; // allready qouted
                    176: 
                    177:        size_t extra_length = 2; // opening and closing quotes
                    178:        for(const char *src = arg; *src; src++){
1.103     moko      179:                if(*src == '"')
1.101     moko      180:                        extra_length++;
                    181:        }
                    182: 
                    183:        char *result = (char *)pa_malloc(length + extra_length + 1);
                    184:        char *dest = result;
                    185: 
                    186:        *dest++ = '"';
                    187: 
                    188:        for(const char *src=arg; *src;){
                    189:                char c = *src++;
1.103     moko      190:                if(c == '"')
                    191:                        *dest++ = '"';
1.99      moko      192:                *dest++ = c;
                    193:        }
                    194: 
                    195:        *dest++ = '"';
                    196:        *dest = '\0';
                    197:        return result;
                    198: }
                    199: 
1.51      paf       200: static const char* buildCommand(const char* file_spec_cstr, const ArrayString& argv) {
                    201:        const char* result=file_spec_cstr;
1.92      moko      202:        if(FILE *f=pa_fopen(file_spec_cstr, "r")) {
1.51      paf       203:                try {
1.1       paf       204:                char buf[MAX_STRING];
                    205:                size_t size=fread(buf, 1, MAX_STRING-1, f);
                    206:                if(size>2) {
                    207:                        buf[size]=0;
                    208:                        if(strncmp(buf, "#!", 2)==0) {
1.51      paf       209:                                const char* begin=buf+2;
1.65      paf       210:                                while(*begin==' ') // alx: were an old magic for some linux-es
1.4       paf       211:                                        begin++;
1.68      paf       212:                                if(const char *end=strchr(begin, '\n')) {
1.51      paf       213:                                        String string(pa_strdup(begin, end-begin));
1.1       paf       214:                                        string << " " << file_spec_cstr;
1.8       parser    215:                                        result=string.cstr();
1.1       paf       216:                                }
                    217:                        }
                    218:                }
1.51      paf       219:                } catch(...) {
                    220:                        fclose(f);
                    221:                        rethrow;
                    222:                }
1.1       paf       223:                fclose(f);
                    224:        }
1.101     moko      225: 
1.51      paf       226:        { // appending argv
                    227:                String string(result);
                    228:                for(size_t i=0; i<argv.count(); i++) {
                    229:                        string << " ";
1.102     moko      230:                        string << arg_quote(argv[i]->cstr());
1.8       parser    231:                }
1.51      paf       232:                result=string.cstr();
1.8       parser    233:        }
                    234: 
                    235:        return result;
1.1       paf       236: }
                    237: 
1.51      paf       238: #else
1.1       paf       239: 
1.51      paf       240: static pid_t execve_piped(const char* file_spec_cstr, 
1.27      paf       241:                        char * const argv[], char * const env[],
                    242:                        int *pipe_in, int *pipe_out, int *pipe_err) {
1.46      paf       243:        pid_t pid;
1.1       paf       244:        int in_fds[2];
                    245:        int out_fds[2];
                    246:        int err_fds[2];
                    247:        int save_errno;
                    248:        
                    249:        if(pipe_in && pipe(in_fds)<0) {
                    250:                save_errno=errno;
                    251:                errno=save_errno;
                    252:                return 0;
                    253:        }
                    254:        
                    255:        if(pipe_out && pipe(out_fds)<0) {
                    256:                save_errno=errno;
                    257:                if(pipe_in) {
                    258:                        close(in_fds[0]); close(in_fds[1]);
                    259:                }
                    260:                errno=save_errno;
                    261:                return 0;
                    262:        }
                    263:        
                    264:        if(pipe_err && pipe(err_fds)<0) {
                    265:                save_errno=errno;
                    266:                if(pipe_in) {
                    267:                        close(in_fds[0]); close(in_fds[1]);
                    268:                }
                    269:                if(pipe_out) {
                    270:                        close(out_fds[0]); close(out_fds[1]);
                    271:                }
                    272:                errno=save_errno;
                    273:                return 0;
                    274:        }
                    275:        
                    276:        if((pid=fork())<0) {
                    277:                save_errno=errno;
                    278:                if(pipe_in) {
                    279:                        close(in_fds[0]); close(in_fds[1]);
                    280:                }
                    281:                if(pipe_out) {
                    282:                        close(out_fds[0]); close(out_fds[1]);
                    283:                }
                    284:                if(pipe_err) {
                    285:                        close(err_fds[0]); close(err_fds[1]);
                    286:                }
                    287:                errno=save_errno;
1.46      paf       288:                return -1;
1.1       paf       289:        }
                    290:        
                    291:        if(!pid) {
                    292:                /* Child process */
                    293:                
                    294:                if(pipe_out) {
                    295:                        close(out_fds[0]);
                    296:                        dup2(out_fds[1], STDOUT_FILENO);
                    297:                        close(out_fds[1]);
                    298:                }
                    299:                
                    300:                if(pipe_in) {
                    301:                        close(in_fds[1]);
                    302:                        dup2(in_fds[0], STDIN_FILENO);
                    303:                        close(in_fds[0]);
                    304:                }
                    305:                
                    306:                if(pipe_err) {
                    307:                        close(err_fds[0]);
                    308:                        dup2(err_fds[1], STDERR_FILENO);
                    309:                        close(err_fds[1]);
                    310:                }
                    311:                
1.42      paf       312:                /* grabbed this from Apache source: */
                    313:                /*  HP-UX SIGCHLD fix goes here, if someone will remind me what it is... */
1.1       paf       314:                signal(SIGCHLD, SIG_DFL);       /* Was that it? */
                    315:        
1.31      paf       316:                // chdir to script's directory
                    317:                char dir[MAX_STRING];
1.96      moko      318:                pa_strncpy(dir, file_spec_cstr, MAX_STRING);
1.31      paf       319:                rsplit(dir,'/'); // trim filename
1.104     moko      320:                PA_UNUSED int ignore_result=chdir(dir);
1.31      paf       321: 
1.108   ! moko      322:                // put the child in its own process group so the response timeout can kill it and its children
        !           323:                setpgid(0, 0);
        !           324: 
1.31      paf       325:                //  execute
                    326:                execve(file_spec_cstr, argv, env);
1.1       paf       327:                exit(-errno);
                    328:        }
                    329:        
                    330:        /* Parent process */
1.108   ! moko      331: 
        !           332:        setpgid(pid, pid); // repeat in the parent in case the timeout fires before the child calls setpgid()
1.1       paf       333:        
                    334:        if(pipe_out) {
                    335:                close(out_fds[1]);
                    336:                *pipe_out=out_fds[0];
                    337:        }
                    338:        
                    339:        if(pipe_in) {
                    340:                close(in_fds[0]);
                    341:                *pipe_in=in_fds[1];
                    342:        }
                    343:        
                    344:        if(pipe_err) {
                    345:                close(err_fds[1]);
                    346:                *pipe_err=err_fds[0];
                    347:        }
                    348: 
                    349:        return pid;
                    350: }
                    351: 
1.108   ! moko      352: class Temp_exec_pid {
        !           353: public:
        !           354:        Temp_exec_pid(pid_t pid) { pa_exec_active_pid=pid; }
        !           355:        ~Temp_exec_pid() { pa_exec_active_pid=0; }
        !           356: };
        !           357: 
1.1       paf       358: static int get_exit_status(int pid) {
1.95      moko      359:        int status=0;
1.58      paf       360:        pid_t cid;
                    361:        while ((cid=waitpid(pid, &status, WUNTRACED)) == -1 && errno == EINTR); 
                    362:        if(!cid)
1.1       paf       363:                return -1;
                    364:        return WIFEXITED(status) ? 
                    365:                WEXITSTATUS(status) : -2;
                    366: }
                    367: 
1.51      paf       368: static void read_pipe(String& result, int file, String::Language lang){
1.1       paf       369:        while(true) {
1.77      misha     370:                char *buf=new(PointerFreeGC) char[MAX_STRING+1];
                    371:                ssize_t length=read(file, buf, MAX_STRING);
1.51      paf       372:                if(length<=0)
1.49      paf       373:                        break;
1.51      paf       374:                buf[length]=0;
                    375:                result.append_know_length(buf, length, lang);
                    376:        }
1.1       paf       377: }
                    378: 
1.70      misha     379: static void read_pipe(File_read_result& result, int file){
1.77      misha     380:        char *buf=(char*)pa_malloc(MAX_STRING+1);
1.90      moko      381:        size_t bufsize = MAX_STRING;
1.70      misha     382: 
                    383:        result.headers = 0;
                    384:        result.length = 0;
                    385:        result.str = 0;
                    386:        result.success = false;
                    387: 
                    388:        while(true) {
1.77      misha     389:                ssize_t size=read(file, buf + result.length, bufsize - result.length);
1.70      misha     390:                if(size <= 0)
                    391:                        break;
1.77      misha     392:                result.length += size;
                    393:                if(result.length >= bufsize){
                    394:                        bufsize *= 2;
                    395:                        buf=(char*)pa_realloc(buf, bufsize+1);
1.70      misha     396:                }
1.77      misha     397:                result.str=buf;
1.70      misha     398:        }
                    399: }
                    400: 
1.51      paf       401: #endif
1.1       paf       402: 
1.51      paf       403: #ifndef DOXYGEN
                    404: struct Append_env_pair_info {
1.84      moko      405: #ifdef _MSC_VER
1.62      paf       406:        String::Body& body;
                    407:        Append_env_pair_info(String::Body& abody): body(abody) {}
1.51      paf       408: #else
                    409:        char **env_ref;
                    410: #endif
                    411: };
                    412: #endif
1.83      moko      413: 
1.51      paf       414: static void append_env_pair(HashStringString::key_type key, HashStringString::value_type value,
                    415:                Append_env_pair_info *info) {
1.84      moko      416: #ifdef _MSC_VER
1.62      paf       417:        info->body << key << "=" << value;
                    418:        info->body.append_know_length("\1", 1); // placeholder for of zero byte
1.1       paf       419: #else
1.52      paf       420:        String::Body body;
1.63      paf       421:        body << key << "=" << value.cstr();
1.1       paf       422: 
1.61      paf       423:        *(info->env_ref++)=body.cstrm();
1.1       paf       424: #endif
                    425: }
1.31      paf       426: 
1.89      moko      427: PA_exec_result pa_exec(bool forced_allow, const String& file_spec, const HashStringString* env, const ArrayString& argv, String::C in) {
1.51      paf       428:        PA_exec_result result;
1.1       paf       429: 
1.28      paf       430: #ifdef NO_PA_EXECS
1.32      paf       431:        if(!forced_allow)
1.88      moko      432:                throw Exception(PARSER_RUNTIME, &file_spec, "parser execs are disabled [recompile parser without --disable-execs configure option]");
1.32      paf       433: #endif
1.28      paf       434: 
1.84      moko      435: #ifdef _MSC_VER
1.1       paf       436: 
                    437:        PROCESS_INFORMATION pi; 
                    438:        HANDLE hInWrite, hOutRead, hErrRead;
1.75      misha     439:        const char* script_spec_cstr=file_spec.taint_cstr(String::L_FILE_SPEC);
1.65      paf       440:        const char* cmd=buildCommand(script_spec_cstr, argv);
1.53      paf       441:        char* env_cstr=0;
1.1       paf       442:        if(env) {
1.62      paf       443:                String::Body body;
                    444:                Append_env_pair_info info(body);
1.68      paf       445:                env->for_each<Append_env_pair_info*>(append_env_pair, &info);
1.62      paf       446:                env_cstr=info.body.cstrm();
1.51      paf       447:                for(char* replacer=env_cstr; *replacer; replacer++)
                    448:                        if(*replacer=='\1')
                    449:                                *replacer=0;
1.1       paf       450:        }
1.65      paf       451:        if(DWORD error=CreateHiddenConsoleProcess(cmd, script_spec_cstr, env_cstr, &pi, &hInWrite, &hOutRead, &hErrRead)) {
1.36      paf       452:                char szErrorDesc[MAX_STRING];
1.51      paf       453:                const char* param="the file you tried to run";
1.36      paf       454:                size_t error_size=FormatMessage(
                    455:                        FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_ARGUMENT_ARRAY , NULL, error,
                    456:                        MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
1.51      paf       457:                        szErrorDesc, sizeof(szErrorDesc), (va_list *)&param);
1.36      paf       458:                if(error_size>3) // ".\r\n"
                    459:                        szErrorDesc[error_size-3]=0;
1.73      misha     460: 
1.88      moko      461:                throw Exception("file.execute", &file_spec, "exec failed - %s (%u). Consider adding shbang line (#!x:\\interpreter\\command line)", error_size ? szErrorDesc : "<unknown>", error);
1.36      paf       462:        } else {
1.5       paf       463:                DWORD written_size;
1.89      moko      464:                if(in.length>0)
                    465:                        WriteFile(hInWrite, in.str, in.length, &written_size, NULL);
1.9       parser    466:                CloseHandle(hInWrite);
1.70      misha     467:                read_pipe(result.out, hOutRead);
1.9       parser    468:                CloseHandle(hOutRead);
1.83      moko      469:                read_pipe(result.err, hErrRead, String::L_TAINTED);
1.9       parser    470:                CloseHandle(hErrRead);
1.87      moko      471:                result.status=get_exit_status(pi.hProcess);
1.83      moko      472:                // We must close the handles to the new process and its main thread
                    473:                // to prevent handle and memory leaks.
1.1       paf       474:                CloseHandle(pi.hProcess);
1.73      misha     475:                CloseHandle(pi.hThread);
1.1       paf       476:        }
                    477: 
                    478: #else
1.53      paf       479: 
                    480:        // execve needs non const
1.75      misha     481:        char* file_spec_cstr=file_spec.taint_cstrm(String::L_FILE_SPEC); 
1.1       paf       482: 
                    483:        int pipe_write, pipe_read, pipe_err;
1.31      paf       484: 
1.32      paf       485:        if(!forced_allow) {
                    486:                struct stat finfo;
1.91      moko      487:                if(pa_stat(file_spec_cstr, &finfo)!=0)
1.107     moko      488:                        throw Exception("file.missing", &file_spec, "stat failed: %s (%d)%s", strerror(errno), errno, actual_filename(file_spec, file_spec_cstr));
1.31      paf       489: 
1.50      paf       490:                check_safe_mode(finfo, file_spec, file_spec_cstr);
1.32      paf       491:        }
1.31      paf       492: 
1.76      misha     493:        char* argv_cstrs[1+100+1]={file_spec_cstr, 0};
1.51      paf       494:        const int argv_size=argv.count();
                    495:        const int argv_max=sizeof(argv_cstrs)/sizeof(argv_cstrs[0])-1-1;
                    496:        if(argv_size>argv_max)
1.88      moko      497:                throw Exception(PARSER_RUNTIME, &file_spec, "too many arguments (%d > max %d)", argv_size, argv_max);
1.51      paf       498:        for(int i=0; i<argv_size; i++)
                    499:                argv_cstrs[1+i]=argv[i]->cstrm();
                    500:        argv_cstrs[1+argv_size]=0;
                    501: 
                    502:        char **env_cstrs;
1.1       paf       503:        if(env) {
1.51      paf       504:                env_cstrs=new(PointerFreeGC) char *[env->count()+1/*0*/];
                    505:                Append_env_pair_info info={env_cstrs}; 
                    506:                env->for_each(append_env_pair, &info);
                    507:                *info.env_ref=0;
                    508:        } else
                    509:                env_cstrs=0;
1.31      paf       510: 
1.46      paf       511:        pid_t pid=execve_piped(
1.1       paf       512:                file_spec_cstr,
1.27      paf       513:                argv_cstrs, env_cstrs,
1.22      paf       514:                &pipe_write, &pipe_read, &pipe_err);
1.46      paf       515:        if(pid>0) {
1.108   ! moko      516:                // child running
        !           517:                Temp_exec_pid temp(pid);
1.89      moko      518:                if(in.length>0) // there is some in data
1.104     moko      519:                        PA_UNUSED int ignore_result=write(pipe_write, in.str, in.length);
1.1       paf       520:                close(pipe_write);
1.70      misha     521:                read_pipe(result.out, pipe_read);
1.5       paf       522:                close(pipe_read);
1.51      paf       523:                read_pipe(result.err, pipe_err, String::L_TAINTED);
1.1       paf       524:                close(pipe_err);
                    525: 
1.51      paf       526:                result.status=get_exit_status(pid); // negative may mean "-errno[execl()]"
1.59      paf       527:        } else { 
                    528:                const char* str=strerror(errno);
1.88      moko      529:                throw Exception("file.execute", &file_spec, "%s error: %s (%d)", pid<0?"fork":"pipe", str?str:"<unknown>", errno); 
1.59      paf       530:        }
1.1       paf       531: #endif
                    532: 
1.51      paf       533:        return result;
1.1       paf       534: }

E-mail: