Diff for /parser3/src/main/pa_exec.C between versions 1.43 and 1.92

version 1.43, 2002/11/20 13:41:54 version 1.92, 2017/02/06 16:17:12
Line 1 Line 1
 /** @file  /** @file
         Parser: program executing for different OS-es.          Parser: program executing for different OS-es.
   
         Copyright(c) 2000,2001, 2002 ArtLebedev Group(http://www.artlebedev.com)          Copyright (c) 2001-2015 Art. Lebedev Studio (http://www.artlebedev.com)
         Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)          Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)
   
         @todo setrlimit          @todo setrlimit
 */  */
   
 static const char* IDENT_EXEC_C="$Date$";  
   
 #include "pa_config_includes.h"  #include "pa_config_includes.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"
   
 #ifdef WIN32  volatile const char * IDENT_PA_EXEC_C="$Id$" IDENT_PA_EXEC_H;
 #       include <windows.h>  
 #else  #ifdef _MSC_VER
 #       include <signal.h>  
 #       include <sys/types.h>  
 #       include <sys/wait.h>  
 #endif  
   
 #ifdef WIN32  #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 DWORD CreateHiddenConsoleProcess(LPCTSTR szCmdLine,  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)
 {  {
     DWORD result=0;          DWORD result=0;
         BOOL fCreated;          BOOL fCreated;
     STARTUPINFO si;          STARTUPINFO si;
     SECURITY_ATTRIBUTES sa={0};          SECURITY_ATTRIBUTES sa={0};
     HANDLE hInRead;          HANDLE hInRead;
     HANDLE hOutWrite;          HANDLE hOutWrite;
     HANDLE hErrWrite;          HANDLE hErrWrite;
           
     // Create pipes          // Create pipes
     // initialize security attributes for handle inheritance (for WinNT)          // initialize security attributes for handle inheritance (for WinNT)
     sa.nLength=sizeof(sa);          sa.nLength=sizeof(sa);
     sa.bInheritHandle=TRUE;          sa.bInheritHandle=TRUE;
     sa.lpSecurityDescriptor=NULL;          sa.lpSecurityDescriptor=NULL;
           
     // create STDIN pipe          // create STDIN pipe
     if(!CreatePipe(&hInRead, phInWrite, &sa, 0))          if(!CreatePipe(&hInRead, phInWrite, &sa, 0))
         goto error;                  goto error;
   
     // create STDOUT pipe          // Ensure the write handle to the pipe for STDIN is not inherited. 
     if(!CreatePipe(phOutRead, &hOutWrite, &sa, 0))          if (!SetHandleInformation(*phInWrite, HANDLE_FLAG_INHERIT, 0))
         goto error;                  goto error;
   
     // create STDERR pipe          // create STDOUT pipe
     if(!CreatePipe(phErrRead, &hErrWrite, &sa, 0))          if(!CreatePipe(phOutRead, &hOutWrite, &sa, 0))
         goto error;                  goto error;
           
     // process startup information          // Ensure the read handle to the pipe for STDOUT is not inherited.
     memset(&si, 0, sizeof(si));          if (!SetHandleInformation(*phOutRead, HANDLE_FLAG_INHERIT, 0))
     si.cb=sizeof(si);                   goto error;
     si.dwFlags=STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;  
     // child process' console must be hidden for Win95 compatibility          // create STDERR pipe
     si.wShowWindow=SW_HIDE;          if(!CreatePipe(phErrRead, &hErrWrite, &sa, 0))
     // assign "other" sides of pipes                  goto error;
     si.hStdInput=hInRead;          
     si.hStdOutput=hOutWrite;          // Ensure the read handle to the pipe for STDERR is not inherited.
     si.hStdError=hErrWrite;          if (!SetHandleInformation(*phErrRead, HANDLE_FLAG_INHERIT, 0))
                   goto error;
   
           // process startup information
           memset(&si, 0, sizeof(si));
           si.cb=sizeof(si); 
           si.dwFlags=STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
           // child process' console must be hidden for Win95 compatibility
           si.wShowWindow=SW_HIDE;
           // assign "other" sides of pipes
           si.hStdInput=hInRead;
           si.hStdOutput=hOutWrite;
           si.hStdError=hErrWrite;
           
         // calculating script's directory          // calculating script's directory
         char dir[MAX_STRING];          char dir[MAX_STRING];
         strncpy(dir, szCmdLine, MAX_STRING-1); dir[MAX_STRING-1]=0;          strncpy(dir, szScriptFileSpec, MAX_STRING-1); dir[MAX_STRING-1]=0;
         lsplit(dir,' '); // trim arguments          lsplit(dir,' '); // trim arguments
         rsplit(dir,'/'); rsplit(dir,'\\'); // trim filename          rsplit(dir,'/'); rsplit(dir,'\\'); // trim filename
         chdir(dir);          
           // Create a child process (suspended)
     // Create a child process (suspended)          fCreated=CreateProcess(NULL,
     fCreated=CreateProcess(NULL,                  (LPTSTR)szCmdLine,
                               (LPTSTR)szCmdLine,                  NULL,
                               NULL,                  NULL,
                               NULL,                  TRUE,
                               TRUE,                  CREATE_NO_WINDOW,
                               CREATE_NO_WINDOW,                  szEnv,
                               szEnv,                  dir,
                               dir,                  &si,
                               &si,                  ppi);
                               ppi);  
         if(!fCreated)          if(!fCreated)
                 result=GetLastError();                  result=GetLastError();
           
     CloseHandle(hInRead);          CloseHandle(hInRead);
     CloseHandle(hOutWrite);          CloseHandle(hOutWrite);
     CloseHandle(hErrWrite);          CloseHandle(hErrWrite);
           
     if(!fCreated)          if(!fCreated)
         goto error;                  goto error;
           
     return result;          return result;
           
 error:  error:
         if(!result/*yet*/)          if(!result/*yet*/)
                 result=GetLastError(); // get it                  result=GetLastError(); // get it
           
           CloseHandle(*phInWrite);
           CloseHandle(*phOutRead);
           CloseHandle(*phErrRead);
           
           return result;
   }
   
     CloseHandle(*phInWrite);  static int get_exit_status(HANDLE hProcess) {
     CloseHandle(*phOutRead);          DWORD dwExitCode = 0;
     CloseHandle(*phErrRead);          if(!GetExitCodeProcess(hProcess, &dwExitCode))
                   return -1;
     return result;          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;
 }  }
   
 static void read_pipe(String& result, HANDLE hOutRead, const char *file_spec,   static void read_pipe(String& result, HANDLE hOutRead, String::Language lang){
                                           String::Untaint_lang lang){  
         while(true) {          while(true) {
                 char *buf=(char *)result.pool().malloc(MAX_STRING);                  char *buf=new(PointerFreeGC) char[MAX_STRING+1];
                 unsigned long size;                  DWORD size=0;
                 if(!ReadFile(hOutRead, buf, MAX_STRING, &size, NULL) || !size)                   if(!ReadFile(hOutRead, buf, MAX_STRING, &size, NULL) || !size) 
                         break;                          break;
                 result.APPEND(buf, size, lang, file_spec, 0);                  buf[size]=0;
     }                  result.append_know_length(buf, size, lang);
           }
 }  }
   
   static void read_pipe(File_read_result& result, HANDLE hOutRead){
   
           char *buf=(char*)pa_malloc(MAX_STRING+1);
           DWORD bufsize = MAX_STRING;
   
           result.headers = 0;
           result.length = 0;
           result.str = 0;
           result.success = false;
   
           while(true) {
                   DWORD size=0;
                   if(!ReadFile(hOutRead, buf + result.length, bufsize - result.length, &size, NULL) || !size)
                           break;
                   result.length += size;
                   if(result.length >= bufsize){
                           bufsize *= 2;
                           buf=(char*)pa_realloc(buf, bufsize+1);
                   }
                   result.str=buf;
           }
   }
   
 static const char *buildCommand(Pool& pool,   static const char* buildCommand(const char* file_spec_cstr, const ArrayString& argv) {
                                                                 const String& origin_string,          const char* result=file_spec_cstr;
                                                                 const char *file_spec_cstr, const Array *argv) {          if(FILE *f=pa_fopen(file_spec_cstr, "r")) {
         const char *result=file_spec_cstr;                  try {
         if(FILE *f=fopen(file_spec_cstr, "r")) {  
                 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) {
                                 const char *begin=buf+2;                                  const char* begin=buf+2;
                                 if(*begin==' ') // alx: were an old magic for some linux-es                                  while(*begin==' ') // alx: were an old magic for some linux-es
                                         begin++;                                          begin++;
                                 if(char *end=strchr(begin, '\n')) {                                  if(const char *end=strchr(begin, '\n')) {
                                         String string(pool);                                          String string(pa_strdup(begin, end-begin));
                                         string.APPEND_AS_IS(begin, end-begin,   
                                                 origin_string.origin().file, 0);  
                                         string << " " << file_spec_cstr;                                          string << " " << file_spec_cstr;
                                         result=string.cstr();                                          result=string.cstr();
                                 }                                  }
                         }                          }
                 }                  }
                   } catch(...) {
                           fclose(f);
                           rethrow;
                   }
                 fclose(f);                  fclose(f);
         }          }
         if(argv) {          { // appending argv
                 String buf(pool);                  String string(result);
                 buf << result;                  for(size_t i=0; i<argv.count(); i++) {
                 for(int i=0; i<argv->size(); i++) {                          string << " ";
                         buf << " ";                          string << *argv[i];
                         buf << *argv->get_string(i);  
                 }                  }
   
                 result=buf.cstr();                  result=string.cstr();
         }          }
   
         return result;          return result;
Line 163  static const char *buildCommand(Pool& po Line 205  static const char *buildCommand(Pool& po
   
 #else  #else
   
 static int execve_piped(const char *file_spec_cstr,   static pid_t execve_piped(const char* file_spec_cstr, 
                         char * const argv[], char * const env[],                          char * const argv[], char * const env[],
                         int *pipe_in, int *pipe_out, int *pipe_err) {                          int *pipe_in, int *pipe_out, int *pipe_err) {
         int pid;          pid_t 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 211  static int execve_piped(const char *file Line 253  static int execve_piped(const char *file
                         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 272  static int execve_piped(const char *file Line 314  static int execve_piped(const char *file
   
 static int get_exit_status(int pid) {  static int get_exit_status(int pid) {
         int status;          int status;
         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, String::Untaint_lang lang){  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];
                 ssize_t size=read(file, buf, MAX_STRING);                  ssize_t length=read(file, buf, MAX_STRING);
                 if(size<=0)                  if(length<=0)
                         break;                          break;
                 result.APPEND(buf, size, lang, 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
   
 ///@test maybe here and at argv construction --- cstr(String::UL_UNSPECIFIED  #ifndef DOXYGEN
 static void append_env_pair(const Hash::Key& key, Hash::Val *value, void *info) {  struct Append_env_pair_info {
 #ifdef WIN32  #ifdef _MSC_VER
         String& string=*static_cast<String *>(info);          String::Body& body;
                   Append_env_pair_info(String::Body& abody): body(abody) {}
         string << key << "=" << *static_cast<String *>(value);  #else
         string.APPEND_AS_IS("", 1, 0, 0); // zero byte          char **env_ref;
   #endif
   };
   #endif
   
   ///@test maybe here and at argv construction --- untaint_cstr(String::L_AS_IS
   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_ref=static_cast<char ***>(info);          *(info->env_ref++)=body.cstrm();
         **env_ref=string.cstr(String::UL_PASS_APPENDED);  (*env_ref)++;  
 #endif  #endif
 }  }
   
 int pa_exec(  PA_exec_result pa_exec(bool forced_allow, const String& file_spec, const HashStringString* env, const ArrayString& argv, String::C in) {
                         bool forced_allow,          PA_exec_result result;
                         const String& file_spec,   
                         const Hash *env,  
                         const Array *argv,  
                         const String& in, String& out, String& err) {  
         Pool& pool=file_spec.pool();  
   
 #ifdef NO_PA_EXECS  #ifdef NO_PA_EXECS
         if(!forced_allow)          if(!forced_allow)
                 throw Exception("parser.runtime",                  throw Exception(PARSER_RUNTIME, &file_spec, "parser execs are disabled [recompile parser without --disable-execs configure option]");
                         &file_spec,  
                         "parser execs are disabled [recompile parser without --disable-execs configure option]");  
 #endif  #endif
   
 #ifdef WIN32  #ifdef _MSC_VER
   
         PROCESS_INFORMATION pi;           PROCESS_INFORMATION pi; 
         HANDLE hInWrite, hOutRead, hErrRead;          HANDLE hInWrite, hOutRead, hErrRead;
         char *file_spec_cstr=file_spec.cstr(String::UL_FILE_SPEC);           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();                  env->for_each<Append_env_pair_info*>(append_env_pair, &info);
                   env_cstr=info.body.cstrm();
                   for(char* replacer=env_cstr; *replacer; replacer++)
                           if(*replacer=='\1')
                                   *replacer=0;
         }          }
         if(DWORD error=CreateHiddenConsoleProcess(cmd, env_cstr, &pi, &hInWrite, &hOutRead, &hErrRead)) {          if(DWORD error=CreateHiddenConsoleProcess(cmd, script_spec_cstr, env_cstr, &pi, &hInWrite, &hOutRead, &hErrRead)) {
                 char szErrorDesc[MAX_STRING];                  char szErrorDesc[MAX_STRING];
                 char *param="the file you tried to run";                  const char* param="the file you tried to run";
                 size_t error_size=FormatMessage(                  size_t error_size=FormatMessage(
                         FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_ARGUMENT_ARRAY , NULL, error,                          FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_ARGUMENT_ARRAY , NULL, error,
                         MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),                          MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
                         szErrorDesc, sizeof(szErrorDesc), &param);                          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;
               
                 throw Exception(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,  
                         "exec failed - %s (%ld). Consider adding shbang line (#!x:\\interpreter\\command line)",   
                                 error_size?szErrorDesc:"<unknown>", (long)error);  
         } else {          } else {
                 const char *in_cstr=in.cstr();  
                 DWORD written_size;                  DWORD written_size;
                 WriteFile(hInWrite, in_cstr, in.size(), &written_size, NULL);                  if(in.length>0)
                 // EOF for stupid text reads                          WriteFile(hInWrite, in.str, in.length, &written_size, NULL);
                 // normally they should read CONTENT_LENGTH bytes,  
                 // without this char  
                 WriteFile(hInWrite, "\x1A", 1, &written_size, NULL);  
                 CloseHandle(hInWrite);                  CloseHandle(hInWrite);
                 read_pipe(out, hOutRead, file_spec_cstr, String::UL_AS_IS);                  read_pipe(result.out, hOutRead);
                 CloseHandle(hOutRead);                  CloseHandle(hOutRead);
                 read_pipe(err, hErrRead, file_spec_cstr, String::UL_TAINTED);                             read_pipe(result.err, hErrRead, String::L_TAINTED);
                 CloseHandle(hErrRead);                  CloseHandle(hErrRead);
 /*                        result.status=get_exit_status(pi.hProcess);
 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.
         * We must close the handles to the new process and its main thread  
     * to prevent handle and memory leaks.  
 */        
                 CloseHandle(pi.hProcess);                  CloseHandle(pi.hProcess);
         CloseHandle(pi.hThread);                  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;
         char *file_spec_cstr=file_spec.cstr(String::UL_FILE_SPEC);  
   
 #ifdef NO_FOREIGN_GROUP_FILES  
         if(!forced_allow) {          if(!forced_allow) {
                 struct stat finfo;                  struct stat finfo;
                 if(stat(file_spec_cstr, &finfo)!=0)                  if(pa_stat(file_spec_cstr, &finfo)!=0)
                         throw Exception("file.missing",                           throw Exception("file.missing", &file_spec, "stat failed: %s (%d), actual filename '%s'", strerror(errno), errno, file_spec_cstr);
                                         &file_spec,   
                                         "stat failed: %s (%d), actual filename '%s'",   
                                                 strerror(errno), errno, file_spec_cstr);  
   
                 if(finfo.st_gid/*foreign?*/!=getegid())  
                         throw Exception("parser.runtime",  
                                 &file_spec,  
                                 "parser executing files of foreign group is     disabled [recompile parser without --disable-foreign-group-files configure option], actual filename '%s'",   
                                         file_spec_cstr);  
         }  
 #endif  
   
         char *argv_cstrs[1+10+1]={file_spec_cstr, 0};                  check_safe_mode(finfo, file_spec, file_spec_cstr);
         if(argv) {  
                 const int argv_size=argv->size();  
                 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->get_string(i)->cstr();  
                 argv_cstrs[1+argv_size]=0;  
         }          }
         char **env_cstrs=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_cstrs=                  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_ref=env_cstrs;                  env->for_each(append_env_pair, &info);
                 env->for_each(append_env_pair, &env_ref);                  *info.env_ref=0;
                 *env_ref=0;          } else
         }                  env_cstrs=0;
   
         int pid=execve_piped(          pid_t pid=execve_piped(
                 file_spec_cstr,                  file_spec_cstr,
                 argv_cstrs, env_cstrs,                  argv_cstrs, env_cstrs,
                 &pipe_write, &pipe_read, &pipe_err);                  &pipe_write, &pipe_read, &pipe_err);
         if(pid) {          if(pid>0) {
                 // in child                  // in child
                 const char *in_cstr=in.cstr();                  if(in.length>0) // there is some in data
                 if(*in_cstr) // there is some in data                          write(pipe_write, in.str, in.length);
                         write(pipe_write, in_cstr, in.size());  
                 close(pipe_write);                  close(pipe_write);
                 read_pipe(out, pipe_read, file_spec_cstr, String::UL_AS_IS);                  read_pipe(result.out, pipe_read);
                 close(pipe_read);                  close(pipe_read);
                 read_pipe(err, pipe_err, file_spec_cstr, String::UL_TAINTED);                  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 { 
                 throw Exception(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.43  
changed lines
  Added in v.1.92


E-mail: