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 *)¶m);
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: