Diff for /parser3/src/main/pa_exec.C between versions 1.1.2.2 and 1.102

version 1.1.2.2, 2001/04/16 14:46:55 version 1.102, 2024/11/25 23:40:40
Line 1 Line 1
 /** @file  /** @file
         Parser: program executing for different OS-es.          Parser: program executing for different OS-es.
   
         Copyright(c) 2000,2001 ArtLebedev Group(http://www.artlebedev.com)          Copyright (c) 2001-2024 Art. Lebedev Studio (http://www.artlebedev.com)
           Authors: Konstantin Morshnev <moko@design.ru>, Alexandr Petrosian <paf@design.ru>
   
         Author: Alexander Petrosyan <paf@design.ru>(http://design.ru/paf)          @todo setrlimit
   
         $Id$  
 */  */
   
 #include "pa_config_includes.h"  #include "pa_config_includes.h"
   
 #ifdef WIN32  
 #       include <windows.h>  
 #else  
 #       include <signal.h>  
 #       include <sys/types.h>  
 #       include <sys/wait.h>  
 #endif  
   
 #include <stdio.h>  
 #include <errno.h>  
   
 #include "pa_exec.h"  #include "pa_exec.h"
 #include "pa_exception.h"  #include "pa_exception.h"
 #include "pa_common.h"  #include "pa_common.h"
   
   volatile const char * IDENT_PA_EXEC_C="$Id$" IDENT_PA_EXEC_H;
   
 #ifdef WIN32  #ifdef _MSC_VER
   
   #include <windows.h>
   
 /// this func from http://www.ccas.ru/~posp/popov/spawn.htm  /// this func from http://www.ccas.ru/~posp/popov/spawn.htm
 static BOOL WINAPI CreateHiddenConsoleProcess( LPCTSTR szChildName,  static DWORD CreateHiddenConsoleProcess(LPCTSTR szCmdLine,
                                                                                 char *szEnv,                                          LPCTSTR szScriptFileSpec,
                                         PROCESS_INFORMATION* ppi,                                           char *szEnv,
                                         LPHANDLE phInWrite,                                          PROCESS_INFORMATION* ppi, 
                                         LPHANDLE phOutRead,                                          LPHANDLE phInWrite,
                                         LPHANDLE phErrRead )                                          LPHANDLE phOutRead,
                                           LPHANDLE phErrRead)
 {  {
     BOOL fCreated;          DWORD result=0;
     STARTUPINFO si;          BOOL fCreated;
     SECURITY_ATTRIBUTES sa={0};          STARTUPINFO si;
     HANDLE hInRead;          SECURITY_ATTRIBUTES sa={0};
     HANDLE hOutWrite;          HANDLE hInRead;
     HANDLE hErrWrite;          HANDLE hOutWrite;
           HANDLE hErrWrite;
     // Create pipes          
     // initialize security attributes for handle inheritance (for WinNT)          // Create pipes
     sa.nLength = sizeof( sa );          // initialize security attributes for handle inheritance (for WinNT)
     sa.bInheritHandle = TRUE;          sa.nLength=sizeof(sa);
     sa.lpSecurityDescriptor  = NULL;          sa.bInheritHandle=TRUE;
           sa.lpSecurityDescriptor=NULL;
     // create STDIN pipe          
     if( !CreatePipe( &hInRead, phInWrite, &sa, 0 ))          // create STDIN pipe
         goto error;          if(!CreatePipe(&hInRead, phInWrite, &sa, 0))
                   goto error;
     // create STDOUT pipe  
     if( !CreatePipe( phOutRead, &hOutWrite, &sa, 0 ))          // Ensure the write handle to the pipe for STDIN is not inherited. 
         goto error;          if (!SetHandleInformation(*phInWrite, HANDLE_FLAG_INHERIT, 0))
                   goto error;
     // create STDERR pipe  
     if( !CreatePipe( phErrRead, &hErrWrite, &sa, 0 ))          // create STDOUT pipe
         goto error;          if(!CreatePipe(phOutRead, &hOutWrite, &sa, 0))
                   goto error;
     // process startup information          
     memset( &si, 0, sizeof( si ));          // Ensure the read handle to the pipe for STDOUT is not inherited.
     si.cb = sizeof( si );           if (!SetHandleInformation(*phOutRead, HANDLE_FLAG_INHERIT, 0))
     si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;                  goto error;
     // child process' console must be hidden for Win95 compatibility  
     si.wShowWindow = SW_HIDE;          // create STDERR pipe
     // assign "other" sides of pipes          if(!CreatePipe(phErrRead, &hErrWrite, &sa, 0))
 //    si.hStdInput = hInRead;                  goto error;
     si.hStdOutput = hOutWrite;          
     si.hStdError = hErrWrite;          // Ensure the read handle to the pipe for STDERR is not inherited.
           if (!SetHandleInformation(*phErrRead, HANDLE_FLAG_INHERIT, 0))
     // Create a child process (suspended)                  goto error;
     fCreated = CreateProcess( NULL,  
                               (LPTSTR)szChildName,          // process startup information
                               NULL,          memset(&si, 0, sizeof(si));
                               NULL,          si.cb=sizeof(si); 
                               TRUE,          si.dwFlags=STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
                               DETACHED_PROCESS,          // child process' console must be hidden for Win95 compatibility
                               szEnv,          si.wShowWindow=SW_HIDE;
                               NULL,          // assign "other" sides of pipes
                               &si,          si.hStdInput=hInRead;
                               ppi );          si.hStdOutput=hOutWrite;
           si.hStdError=hErrWrite;
     CloseHandle( hInRead );          
     CloseHandle( hOutWrite );          // calculating script's directory
     CloseHandle( hErrWrite );          char dir[MAX_STRING];
           pa_strncpy(dir, szScriptFileSpec, MAX_STRING);
           lsplit(dir,' '); // trim arguments
           rsplit(dir,'/'); rsplit(dir,'\\'); // trim filename
           
           // Create a child process (suspended)
           fCreated=CreateProcess(NULL,
                   (LPTSTR)szCmdLine,
                   NULL,
                   NULL,
                   TRUE,
                   CREATE_NO_WINDOW,
                   szEnv,
                   dir,
                   &si,
                   ppi);
           if(!fCreated)
                   result=GetLastError();
           
           CloseHandle(hInRead);
           CloseHandle(hOutWrite);
           CloseHandle(hErrWrite);
           
           if(!fCreated)
                   goto error;
           
           return result;
           
   error:
           if(!result/*yet*/)
                   result=GetLastError(); // get it
           
           CloseHandle(*phInWrite);
           CloseHandle(*phOutRead);
           CloseHandle(*phErrRead);
           
           return result;
   }
   
     if( !fCreated )  static int get_exit_status(HANDLE hProcess) {
         goto error;          DWORD dwExitCode = 0;
           if(!GetExitCodeProcess(hProcess, &dwExitCode))
                   return -1;
           if(dwExitCode != STILL_ACTIVE)
                   return dwExitCode;
           // wait for 1 second for process to exit
           if(WaitForSingleObject(hProcess, 1000) != WAIT_OBJECT_0)
                   return -2;
           if(!GetExitCodeProcess(hProcess, &dwExitCode))
                   return -1;
           return dwExitCode;
   }
   
     return TRUE;  static void read_pipe(String& result, HANDLE hOutRead, String::Language lang){
           while(true) {
                   char *buf=new(PointerFreeGC) char[MAX_STRING+1];
                   DWORD size=0;
                   if(!ReadFile(hOutRead, buf, MAX_STRING, &size, NULL) || !size) 
                           break;
                   buf[size]=0;
                   result.append_know_length(buf, size, lang);
           }
   }
   
 error:  static void read_pipe(File_read_result& result, HANDLE hOutRead){
     CloseHandle( *phInWrite );  
     CloseHandle( *phOutRead );  
     CloseHandle( *phErrRead );  
   
     return FALSE;          char *buf=(char*)pa_malloc(MAX_STRING+1);
 }          DWORD bufsize = MAX_STRING;
   
           result.headers = 0;
           result.length = 0;
           result.str = 0;
           result.success = false;
   
 static void read_pipe(String& result, HANDLE hOutRead, const char *file_spec){  
         while(true) {          while(true) {
                 char *buf=(char *)result.pool().malloc(MAX_STRING);                  DWORD size=0;
                 unsigned long size;                  if(!ReadFile(hOutRead, buf + result.length, bufsize - result.length, &size, NULL) || !size)
                 ReadFile( hOutRead, buf, MAX_STRING, &size, NULL );  
                 if(!size)   
                         break;                          break;
                 result.APPEND_CLEAN(buf, size, file_spec, 0);                  result.length += size;
     }                  if(result.length >= bufsize){
                           bufsize *= 2;
                           buf=(char*)pa_realloc(buf, bufsize+1);
                   }
                   result.str=buf;
           }
 }  }
   
   static const char* arg_quote(const char *arg) {
           size_t length = strlen(arg);
           if (length >= 2 && arg[0] == '"' && arg[length - 1] == '"')
                   return arg; // allready qouted
   
           size_t extra_length = 2; // opening and closing quotes
           for(const char *src = arg; *src; src++){
                   if(*src == '"' || *src == '\\')
                           extra_length++;
           }
   
           char *result = (char *)pa_malloc(length + extra_length + 1);
           char *dest = result;
   
           *dest++ = '"';
   
           for(const char *src=arg; *src;){
                   char c = *src++;
                   if(c == '"'){
                           *dest++ = '"'; // required for .cmd, .exe also supports \"
                   } else if(c == '\\'){
                           *dest++ = '\\';
                   }
                   *dest++ = c;
           }
   
 static const char *buildCommand(Pool& pool,           *dest++ = '"';
                                                                 const String& origin_string,          *dest = '\0';
                                                                 const char *file_spec_cstr, const Array *argv) {          return result;
         FILE *f=fopen(file_spec_cstr, "r");  }
         if(f) {  
   static const char* buildCommand(const char* file_spec_cstr, const ArrayString& argv) {
           const char* result=file_spec_cstr;
           if(FILE *f=pa_fopen(file_spec_cstr, "r")) {
                   try {
                 char buf[MAX_STRING];                  char buf[MAX_STRING];
                 size_t size=fread(buf, 1, MAX_STRING-1, f);                  size_t size=fread(buf, 1, MAX_STRING-1, f);
                 if(size>2) {                  if(size>2) {
                         buf[size]=0;                          buf[size]=0;
                         if(strncmp(buf, "#!", 2)==0) {                          if(strncmp(buf, "#!", 2)==0) {
                                 char *atEOL=strchr(buf, '\n');                                  const char* begin=buf+2;
                                 if(atEOL) {                                  while(*begin==' ') // alx: were an old magic for some linux-es
                                         String string(pool);                                          begin++;
                                         string.APPEND_CLEAN(buf+2, atEOL-(buf+2),                                   if(const char *end=strchr(begin, '\n')) {
                                                 origin_string.origin().file, 0);                                          String string(pa_strdup(begin, end-begin));
                                         string << " " << file_spec_cstr;                                          string << " " << file_spec_cstr;
                                         if(argv)                                          result=string.cstr();
                                                 for(int i=0; i<argv->size(); i++)  
                                                         string << argv->get_string(i)->cstr(String::UL_AS_IS);  
                                         file_spec_cstr=string.cstr();  
                                 }                                  }
                         }                          }
                 }                  }
                   } catch(...) {
                           fclose(f);
                           rethrow;
                   }
                 fclose(f);                  fclose(f);
         }          }
         return file_spec_cstr;  
           { // appending argv
                   String string(result);
                   for(size_t i=0; i<argv.count(); i++) {
                           string << " ";
                           string << arg_quote(argv[i]->cstr());
                   }
                   result=string.cstr();
           }
   
           return result;
 }  }
   
 #else  #else
   
 static int execle_piped(const char *path,   static pid_t execve_piped(const char* file_spec_cstr, 
                                                 const char *arg1,                          char * const argv[], char * const env[],
                                                 const char *arg2,                          int *pipe_in, int *pipe_out, int *pipe_err) {
                                                 const char *arg3,          pid_t pid;
                                                 const char *arg4,  
                                                 const char *arg5,  
                                                 char * const env[],  
                                                 int *pipe_in, int *pipe_out, int *pipe_err) {  
         int pid;  
         int in_fds[2];          int in_fds[2];
         int out_fds[2];          int out_fds[2];
         int err_fds[2];          int err_fds[2];
Line 196  static int execle_piped(const char *path Line 284  static int execle_piped(const char *path
                         close(err_fds[0]); close(err_fds[1]);                          close(err_fds[0]); close(err_fds[1]);
                 }                  }
                 errno=save_errno;                  errno=save_errno;
                 return 0;                  return -1;
         }          }
                   
         if(!pid) {          if(!pid) {
Line 220  static int execle_piped(const char *path Line 308  static int execle_piped(const char *path
                         close(err_fds[1]);                          close(err_fds[1]);
                 }                  }
                                   
                 /* HP-UX SIGCHLD fix goes here, if someone will remind me what it is... */                  /* grabbed this from Apache source: */
                   /*  HP-UX SIGCHLD fix goes here, if someone will remind me what it is... */
                 signal(SIGCHLD, SIG_DFL);       /* Was that it? */                  signal(SIGCHLD, SIG_DFL);       /* Was that it? */
                   
                 execle(path, arg1, arg2, arg3, arg4, arg5, NULL, env);                  // chdir to script's directory
                   char dir[MAX_STRING];
                   pa_strncpy(dir, file_spec_cstr, MAX_STRING);
                   rsplit(dir,'/'); // trim filename
                   chdir(dir);
   
                   //  execute
                   execve(file_spec_cstr, argv, env);
                 exit(-errno);                  exit(-errno);
         }          }
                   
Line 248  static int execle_piped(const char *path Line 344  static int execle_piped(const char *path
 }  }
   
 static int get_exit_status(int pid) {  static int get_exit_status(int pid) {
         int status;          int status=0;
         if(!waitpid(pid, &status, 0))          pid_t cid;
           while ((cid=waitpid(pid, &status, WUNTRACED)) == -1 && errno == EINTR); 
           if(!cid)
                 return -1;                  return -1;
         return WIFEXITED(status) ?           return WIFEXITED(status) ? 
                 WEXITSTATUS(status) : -2;                  WEXITSTATUS(status) : -2;
 }  }
   
 static void read_pipe(String& result, int file, const char *file_spec){  static void read_pipe(String& result, int file, String::Language lang){
         while(true) {          while(true) {
                 char *buf=(char *)result.pool().malloc(MAX_STRING);                  char *buf=new(PointerFreeGC) char[MAX_STRING+1];
                 size_t size=read(file, buf, MAX_STRING);                  ssize_t length=read(file, buf, MAX_STRING);
                 if(!size)                   if(length<=0)
                         break;                          break;
                 result.APPEND_CLEAN(buf, size, file_spec, 0);                  buf[length]=0;
     }                  result.append_know_length(buf, length, lang);
           }
   }
   
   static void read_pipe(File_read_result& result, int file){
           char *buf=(char*)pa_malloc(MAX_STRING+1);
           size_t bufsize = MAX_STRING;
   
           result.headers = 0;
           result.length = 0;
           result.str = 0;
           result.success = false;
   
           while(true) {
                   ssize_t size=read(file, buf + result.length, bufsize - result.length);
                   if(size <= 0)
                           break;
                   result.length += size;
                   if(result.length >= bufsize){
                           bufsize *= 2;
                           buf=(char*)pa_realloc(buf, bufsize+1);
                   }
                   result.str=buf;
           }
 }  }
   
 #endif  #endif
   
 static void append_env_pair(const Hash::Key& key, Hash::Val *value, void *info) {  #ifndef DOXYGEN
 #ifdef WIN32  struct Append_env_pair_info {
         String& string=*static_cast<String *>(info);  #ifdef _MSC_VER
                   String::Body& body;
         string << key << "=" << *static_cast<String *>(value);          Append_env_pair_info(String::Body& abody): body(abody) {}
         string.APPEND_CLEAN("", 1, 0, 0); // zero byte  #else
           char **env_ref;
   #endif
   };
   #endif
   
   static void append_env_pair(HashStringString::key_type key, HashStringString::value_type value,
                   Append_env_pair_info *info) {
   #ifdef _MSC_VER
           info->body << key << "=" << value;
           info->body.append_know_length("\1", 1); // placeholder for of zero byte
 #else  #else
         String string(key.pool());          String::Body body;
         string << key << "=" << *static_cast<String *>(value);          body << key << "=" << value.cstr();
   
         char **env_ptr=static_cast<char **>(info);          *(info->env_ref++)=body.cstrm();
         *env_ptr++=string.cstr();  
 #endif  #endif
 }  }
 int pa_exec(const String& file_spec,   
                         const Hash *env,  PA_exec_result pa_exec(bool forced_allow, const String& file_spec, const HashStringString* env, const ArrayString& argv, String::C in) {
                         const Array *argv,          PA_exec_result result;
                         const String& in, String& out, String& err) {  
         Pool& pool=file_spec.pool();  #ifdef NO_PA_EXECS
           if(!forced_allow)
 #ifdef WIN32                  throw Exception(PARSER_RUNTIME, &file_spec, "parser execs are disabled [recompile parser without --disable-execs configure option]");
   #endif
         char pwd[MAX_STRING];  
         GetCurrentDirectory(sizeof(pwd), pwd);  #ifdef _MSC_VER
         char *dir=file_spec.cstr(String::UL_FILE_NAME);  
         rsplit(dir, '/'); SetCurrentDirectory(dir);  
   
         PROCESS_INFORMATION pi;           PROCESS_INFORMATION pi; 
         HANDLE hInWrite, hOutRead, hErrRead;          HANDLE hInWrite, hOutRead, hErrRead;
         char *file_spec_cstr=file_spec.cstr(String::UL_FILE_NAME);           const char* script_spec_cstr=file_spec.taint_cstr(String::L_FILE_SPEC);
         const char *cmd=buildCommand(file_spec.pool(), file_spec, file_spec_cstr, argv);          const char* cmd=buildCommand(script_spec_cstr, argv);
         char *env_cstr=0;          char* env_cstr=0;
         if(env) {          if(env) {
                 String string(env->pool());                  String::Body body;
                 env->for_each(append_env_pair, &string);                  Append_env_pair_info info(body);
                 env_cstr=string.cstr(String::UL_AS_IS);                  env->for_each<Append_env_pair_info*>(append_env_pair, &info);
         }                  env_cstr=info.body.cstrm();
         if( CreateHiddenConsoleProcess(cmd, env_cstr, &pi, &hInWrite, &hOutRead, &hErrRead )) {                  for(char* replacer=env_cstr; *replacer; replacer++)
                 SetCurrentDirectory(pwd);                          if(*replacer=='\1')
                                   *replacer=0;
                 read_pipe(out, hOutRead, file_spec_cstr);          }
                 read_pipe(err, hErrRead, file_spec_cstr);          if(DWORD error=CreateHiddenConsoleProcess(cmd, script_spec_cstr, env_cstr, &pi, &hInWrite, &hOutRead, &hErrRead)) {
                 CloseHandle( hInWrite );  
                 CloseHandle( hOutRead );  
                 CloseHandle( hErrRead );  
 /*        
 from http://www.apache.org/websrc/cvsweb.cgi/apache-1.3/src/main/util_script.c?rev=1.151&content-type=text/vnd.viewcvs-markup  
   
         * We must close the handles to the new process and its main thread  
     * to prevent handle and memory leaks.  
 */        
                 CloseHandle(pi.hProcess);  
         CloseHandle(pi.hThread);  
         } else {  
                 SetCurrentDirectory(pwd);  
   
                 DWORD error=GetLastError();  
                 char szErrorDesc[MAX_STRING];                  char szErrorDesc[MAX_STRING];
                 FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, error,                  const char* param="the file you tried to run";
                 MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),                  size_t error_size=FormatMessage(
                 szErrorDesc, sizeof(szErrorDesc), NULL);                          FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_ARGUMENT_ARRAY , NULL, error,
                 size_t error_size=strlen(szErrorDesc);                          MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
                           szErrorDesc, sizeof(szErrorDesc), (va_list *)&param);
                 if(error_size>3) // ".\r\n"                  if(error_size>3) // ".\r\n"
                         szErrorDesc[error_size-3]=0;                          szErrorDesc[error_size-3]=0;
               
                 PTHROW(0, 0,                  throw Exception("file.execute", &file_spec, "exec failed - %s (%u). Consider adding shbang line (#!x:\\interpreter\\command line)", error_size ? szErrorDesc : "<unknown>", error);
                         &file_spec,          } else {
                         "exec failed - %s (%d)",                  DWORD written_size;
                                 szErrorDesc,                  if(in.length>0)
                                 (long)error);                          WriteFile(hInWrite, in.str, in.length, &written_size, NULL);
                   CloseHandle(hInWrite);
                   read_pipe(result.out, hOutRead);
                   CloseHandle(hOutRead);
                   read_pipe(result.err, hErrRead, String::L_TAINTED);
                   CloseHandle(hErrRead);
                   result.status=get_exit_status(pi.hProcess);
                   // We must close the handles to the new process and its main thread
                   // to prevent handle and memory leaks.
                   CloseHandle(pi.hProcess);
                   CloseHandle(pi.hThread);
         }          }
   
 #else  #else
   
           // execve needs non const
           char* file_spec_cstr=file_spec.taint_cstrm(String::L_FILE_SPEC); 
   
         int pipe_write, pipe_read, pipe_err;          int pipe_write, pipe_read, pipe_err;
         const char *argv_cstr[5]={"", "", "", "", ""};  
         if(argv) {          if(!forced_allow) {
                 int size=min(5, argv->size());                  struct stat finfo;
                 for(int i=0; i<size; i++)                  if(pa_stat(file_spec_cstr, &finfo)!=0)
                         argv_cstr[i]=argv->get_string(i)->cstr(String::UL_AS_IS);                          throw Exception("file.missing", &file_spec, "stat failed: %s (%d), actual filename '%s'", strerror(errno), errno, file_spec_cstr);
         }  
         const char *file_spec_cstr=file_spec.cstr(String::UL_FILE_NAME);                  check_safe_mode(finfo, file_spec, file_spec_cstr);
         char **env_cstr=0;          }
   
           char* argv_cstrs[1+100+1]={file_spec_cstr, 0};
           const int argv_size=argv.count();
           const int argv_max=sizeof(argv_cstrs)/sizeof(argv_cstrs[0])-1-1;
           if(argv_size>argv_max)
                   throw Exception(PARSER_RUNTIME, &file_spec, "too many arguments (%d > max %d)", argv_size, argv_max);
           for(int i=0; i<argv_size; i++)
                   argv_cstrs[1+i]=argv[i]->cstrm();
           argv_cstrs[1+argv_size]=0;
   
           char **env_cstrs;
         if(env) {          if(env) {
                 env_cstr=                  env_cstrs=new(PointerFreeGC) char *[env->count()+1/*0*/];
                         (char **)env->pool().malloc(sizeof(char *)*(env->size()+1/*0*/));                  Append_env_pair_info info={env_cstrs}; 
                 char **env_ptr=env_cstr;                  env->for_each(append_env_pair, &info);
                 env->for_each(append_env_pair, &env_ptr);                  *info.env_ref=0;
                 *env_ptr=0;          } else
         }                  env_cstrs=0;
         if(int pid=execle_piped(  
                 file_spec_cstr,  
                 argv_cstr[0], argv_cstr[1], argv_cstr[2], argv_cstr[3], argv_cstr[4],  
                 env_cstr,  
                 &pipe_write, &pipe_read, &pipe_err)) {  
   
                 const char *in_cstr=in.cstr(String::UL_AS_IS);          pid_t pid=execve_piped(
                 write(pipe_write, in_cstr, in.size());                  file_spec_cstr,
                   argv_cstrs, env_cstrs,
                   &pipe_write, &pipe_read, &pipe_err);
           if(pid>0) {
                   // in child
                   if(in.length>0) // there is some in data
                           write(pipe_write, in.str, in.length);
                 close(pipe_write);                  close(pipe_write);
                 read_pipe(out, pipe_read, file_spec_cstr);                  read_pipe(result.out, pipe_read);
                 read_pipe(err, pipe_err, file_spec_cstr);  
                 close(pipe_read);                  close(pipe_read);
                   read_pipe(result.err, pipe_err, String::L_TAINTED);
                 close(pipe_err);                  close(pipe_err);
   
                 return get_exit_status(pid); // negative may mean "-errno[execl()]"                  result.status=get_exit_status(pid); // negative may mean "-errno[execl()]"
         } else           } else { 
                 PTHROW(0, 0,                  const char* str=strerror(errno);
                         &file_spec,                  throw Exception("file.execute", &file_spec, "%s error: %s (%d)", pid<0?"fork":"pipe", str?str:"<unknown>", errno); 
                         "pipe error");          }
 #endif  #endif
   
         return 0;          return result;
 }  }

Removed from v.1.1.2.2  
changed lines
  Added in v.1.102


E-mail: