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