Annotation of parser3/src/main/pa_exec.C, revision 1.102
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.102 ! moko 16: volatile const char * IDENT_PA_EXEC_C="$Id: pa_exec.C,v 1.101 2024/11/24 23:21:39 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.102 ! moko 168: static const char* arg_quote(const char *arg) {
1.101 moko 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++;
1.102 ! moko 186: if(c == '"'){
! 187: *dest++ = '"'; // required for .cmd, .exe also supports \"
! 188: } else if(c == '\\'){
! 189: *dest++ = '\\';
1.99 moko 190: }
191: *dest++ = c;
192: }
193:
194: *dest++ = '"';
195: *dest = '\0';
196: return result;
197: }
198:
1.51 paf 199: static const char* buildCommand(const char* file_spec_cstr, const ArrayString& argv) {
200: const char* result=file_spec_cstr;
1.92 moko 201: if(FILE *f=pa_fopen(file_spec_cstr, "r")) {
1.51 paf 202: try {
1.1 paf 203: char buf[MAX_STRING];
204: size_t size=fread(buf, 1, MAX_STRING-1, f);
205: if(size>2) {
206: buf[size]=0;
207: if(strncmp(buf, "#!", 2)==0) {
1.51 paf 208: const char* begin=buf+2;
1.65 paf 209: while(*begin==' ') // alx: were an old magic for some linux-es
1.4 paf 210: begin++;
1.68 paf 211: if(const char *end=strchr(begin, '\n')) {
1.51 paf 212: String string(pa_strdup(begin, end-begin));
1.1 paf 213: string << " " << file_spec_cstr;
1.8 parser 214: result=string.cstr();
1.1 paf 215: }
216: }
217: }
1.51 paf 218: } catch(...) {
219: fclose(f);
220: rethrow;
221: }
1.1 paf 222: fclose(f);
223: }
1.101 moko 224:
1.51 paf 225: { // appending argv
226: String string(result);
227: for(size_t i=0; i<argv.count(); i++) {
228: string << " ";
1.102 ! moko 229: string << arg_quote(argv[i]->cstr());
1.8 parser 230: }
1.51 paf 231: result=string.cstr();
1.8 parser 232: }
233:
234: return result;
1.1 paf 235: }
236:
1.51 paf 237: #else
1.1 paf 238:
1.51 paf 239: static pid_t execve_piped(const char* file_spec_cstr,
1.27 paf 240: char * const argv[], char * const env[],
241: int *pipe_in, int *pipe_out, int *pipe_err) {
1.46 paf 242: pid_t pid;
1.1 paf 243: int in_fds[2];
244: int out_fds[2];
245: int err_fds[2];
246: int save_errno;
247:
248: if(pipe_in && pipe(in_fds)<0) {
249: save_errno=errno;
250: errno=save_errno;
251: return 0;
252: }
253:
254: if(pipe_out && pipe(out_fds)<0) {
255: save_errno=errno;
256: if(pipe_in) {
257: close(in_fds[0]); close(in_fds[1]);
258: }
259: errno=save_errno;
260: return 0;
261: }
262:
263: if(pipe_err && pipe(err_fds)<0) {
264: save_errno=errno;
265: if(pipe_in) {
266: close(in_fds[0]); close(in_fds[1]);
267: }
268: if(pipe_out) {
269: close(out_fds[0]); close(out_fds[1]);
270: }
271: errno=save_errno;
272: return 0;
273: }
274:
275: if((pid=fork())<0) {
276: save_errno=errno;
277: if(pipe_in) {
278: close(in_fds[0]); close(in_fds[1]);
279: }
280: if(pipe_out) {
281: close(out_fds[0]); close(out_fds[1]);
282: }
283: if(pipe_err) {
284: close(err_fds[0]); close(err_fds[1]);
285: }
286: errno=save_errno;
1.46 paf 287: return -1;
1.1 paf 288: }
289:
290: if(!pid) {
291: /* Child process */
292:
293: if(pipe_out) {
294: close(out_fds[0]);
295: dup2(out_fds[1], STDOUT_FILENO);
296: close(out_fds[1]);
297: }
298:
299: if(pipe_in) {
300: close(in_fds[1]);
301: dup2(in_fds[0], STDIN_FILENO);
302: close(in_fds[0]);
303: }
304:
305: if(pipe_err) {
306: close(err_fds[0]);
307: dup2(err_fds[1], STDERR_FILENO);
308: close(err_fds[1]);
309: }
310:
1.42 paf 311: /* grabbed this from Apache source: */
312: /* HP-UX SIGCHLD fix goes here, if someone will remind me what it is... */
1.1 paf 313: signal(SIGCHLD, SIG_DFL); /* Was that it? */
314:
1.31 paf 315: // chdir to script's directory
316: char dir[MAX_STRING];
1.96 moko 317: pa_strncpy(dir, file_spec_cstr, MAX_STRING);
1.31 paf 318: rsplit(dir,'/'); // trim filename
319: chdir(dir);
320:
321: // execute
322: execve(file_spec_cstr, argv, env);
1.1 paf 323: exit(-errno);
324: }
325:
326: /* Parent process */
327:
328: if(pipe_out) {
329: close(out_fds[1]);
330: *pipe_out=out_fds[0];
331: }
332:
333: if(pipe_in) {
334: close(in_fds[0]);
335: *pipe_in=in_fds[1];
336: }
337:
338: if(pipe_err) {
339: close(err_fds[1]);
340: *pipe_err=err_fds[0];
341: }
342:
343: return pid;
344: }
345:
346: static int get_exit_status(int pid) {
1.95 moko 347: int status=0;
1.58 paf 348: pid_t cid;
349: while ((cid=waitpid(pid, &status, WUNTRACED)) == -1 && errno == EINTR);
350: if(!cid)
1.1 paf 351: return -1;
352: return WIFEXITED(status) ?
353: WEXITSTATUS(status) : -2;
354: }
355:
1.51 paf 356: static void read_pipe(String& result, int file, String::Language lang){
1.1 paf 357: while(true) {
1.77 misha 358: char *buf=new(PointerFreeGC) char[MAX_STRING+1];
359: ssize_t length=read(file, buf, MAX_STRING);
1.51 paf 360: if(length<=0)
1.49 paf 361: break;
1.51 paf 362: buf[length]=0;
363: result.append_know_length(buf, length, lang);
364: }
1.1 paf 365: }
366:
1.70 misha 367: static void read_pipe(File_read_result& result, int file){
1.77 misha 368: char *buf=(char*)pa_malloc(MAX_STRING+1);
1.90 moko 369: size_t bufsize = MAX_STRING;
1.70 misha 370:
371: result.headers = 0;
372: result.length = 0;
373: result.str = 0;
374: result.success = false;
375:
376: while(true) {
1.77 misha 377: ssize_t size=read(file, buf + result.length, bufsize - result.length);
1.70 misha 378: if(size <= 0)
379: break;
1.77 misha 380: result.length += size;
381: if(result.length >= bufsize){
382: bufsize *= 2;
383: buf=(char*)pa_realloc(buf, bufsize+1);
1.70 misha 384: }
1.77 misha 385: result.str=buf;
1.70 misha 386: }
387: }
388:
1.51 paf 389: #endif
1.1 paf 390:
1.51 paf 391: #ifndef DOXYGEN
392: struct Append_env_pair_info {
1.84 moko 393: #ifdef _MSC_VER
1.62 paf 394: String::Body& body;
395: Append_env_pair_info(String::Body& abody): body(abody) {}
1.51 paf 396: #else
397: char **env_ref;
398: #endif
399: };
400: #endif
1.83 moko 401:
1.51 paf 402: static void append_env_pair(HashStringString::key_type key, HashStringString::value_type value,
403: Append_env_pair_info *info) {
1.84 moko 404: #ifdef _MSC_VER
1.62 paf 405: info->body << key << "=" << value;
406: info->body.append_know_length("\1", 1); // placeholder for of zero byte
1.1 paf 407: #else
1.52 paf 408: String::Body body;
1.63 paf 409: body << key << "=" << value.cstr();
1.1 paf 410:
1.61 paf 411: *(info->env_ref++)=body.cstrm();
1.1 paf 412: #endif
413: }
1.31 paf 414:
1.89 moko 415: PA_exec_result pa_exec(bool forced_allow, const String& file_spec, const HashStringString* env, const ArrayString& argv, String::C in) {
1.51 paf 416: PA_exec_result result;
1.1 paf 417:
1.28 paf 418: #ifdef NO_PA_EXECS
1.32 paf 419: if(!forced_allow)
1.88 moko 420: throw Exception(PARSER_RUNTIME, &file_spec, "parser execs are disabled [recompile parser without --disable-execs configure option]");
1.32 paf 421: #endif
1.28 paf 422:
1.84 moko 423: #ifdef _MSC_VER
1.1 paf 424:
425: PROCESS_INFORMATION pi;
426: HANDLE hInWrite, hOutRead, hErrRead;
1.75 misha 427: const char* script_spec_cstr=file_spec.taint_cstr(String::L_FILE_SPEC);
1.65 paf 428: const char* cmd=buildCommand(script_spec_cstr, argv);
1.53 paf 429: char* env_cstr=0;
1.1 paf 430: if(env) {
1.62 paf 431: String::Body body;
432: Append_env_pair_info info(body);
1.68 paf 433: env->for_each<Append_env_pair_info*>(append_env_pair, &info);
1.62 paf 434: env_cstr=info.body.cstrm();
1.51 paf 435: for(char* replacer=env_cstr; *replacer; replacer++)
436: if(*replacer=='\1')
437: *replacer=0;
1.1 paf 438: }
1.65 paf 439: if(DWORD error=CreateHiddenConsoleProcess(cmd, script_spec_cstr, env_cstr, &pi, &hInWrite, &hOutRead, &hErrRead)) {
1.36 paf 440: char szErrorDesc[MAX_STRING];
1.51 paf 441: const char* param="the file you tried to run";
1.36 paf 442: size_t error_size=FormatMessage(
443: FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_ARGUMENT_ARRAY , NULL, error,
444: MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
1.51 paf 445: szErrorDesc, sizeof(szErrorDesc), (va_list *)¶m);
1.36 paf 446: if(error_size>3) // ".\r\n"
447: szErrorDesc[error_size-3]=0;
1.73 misha 448:
1.88 moko 449: 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 450: } else {
1.5 paf 451: DWORD written_size;
1.89 moko 452: if(in.length>0)
453: WriteFile(hInWrite, in.str, in.length, &written_size, NULL);
1.9 parser 454: CloseHandle(hInWrite);
1.70 misha 455: read_pipe(result.out, hOutRead);
1.9 parser 456: CloseHandle(hOutRead);
1.83 moko 457: read_pipe(result.err, hErrRead, String::L_TAINTED);
1.9 parser 458: CloseHandle(hErrRead);
1.87 moko 459: result.status=get_exit_status(pi.hProcess);
1.83 moko 460: // We must close the handles to the new process and its main thread
461: // to prevent handle and memory leaks.
1.1 paf 462: CloseHandle(pi.hProcess);
1.73 misha 463: CloseHandle(pi.hThread);
1.1 paf 464: }
465:
466: #else
1.53 paf 467:
468: // execve needs non const
1.75 misha 469: char* file_spec_cstr=file_spec.taint_cstrm(String::L_FILE_SPEC);
1.1 paf 470:
471: int pipe_write, pipe_read, pipe_err;
1.31 paf 472:
1.32 paf 473: if(!forced_allow) {
474: struct stat finfo;
1.91 moko 475: if(pa_stat(file_spec_cstr, &finfo)!=0)
1.88 moko 476: throw Exception("file.missing", &file_spec, "stat failed: %s (%d), actual filename '%s'", strerror(errno), errno, file_spec_cstr);
1.31 paf 477:
1.50 paf 478: check_safe_mode(finfo, file_spec, file_spec_cstr);
1.32 paf 479: }
1.31 paf 480:
1.76 misha 481: char* argv_cstrs[1+100+1]={file_spec_cstr, 0};
1.51 paf 482: const int argv_size=argv.count();
483: const int argv_max=sizeof(argv_cstrs)/sizeof(argv_cstrs[0])-1-1;
484: if(argv_size>argv_max)
1.88 moko 485: throw Exception(PARSER_RUNTIME, &file_spec, "too many arguments (%d > max %d)", argv_size, argv_max);
1.51 paf 486: for(int i=0; i<argv_size; i++)
487: argv_cstrs[1+i]=argv[i]->cstrm();
488: argv_cstrs[1+argv_size]=0;
489:
490: char **env_cstrs;
1.1 paf 491: if(env) {
1.51 paf 492: env_cstrs=new(PointerFreeGC) char *[env->count()+1/*0*/];
493: Append_env_pair_info info={env_cstrs};
494: env->for_each(append_env_pair, &info);
495: *info.env_ref=0;
496: } else
497: env_cstrs=0;
1.31 paf 498:
1.46 paf 499: pid_t pid=execve_piped(
1.1 paf 500: file_spec_cstr,
1.27 paf 501: argv_cstrs, env_cstrs,
1.22 paf 502: &pipe_write, &pipe_read, &pipe_err);
1.46 paf 503: if(pid>0) {
1.21 paf 504: // in child
1.89 moko 505: if(in.length>0) // there is some in data
506: write(pipe_write, in.str, in.length);
1.1 paf 507: close(pipe_write);
1.70 misha 508: read_pipe(result.out, pipe_read);
1.5 paf 509: close(pipe_read);
1.51 paf 510: read_pipe(result.err, pipe_err, String::L_TAINTED);
1.1 paf 511: close(pipe_err);
512:
1.51 paf 513: result.status=get_exit_status(pid); // negative may mean "-errno[execl()]"
1.59 paf 514: } else {
515: const char* str=strerror(errno);
1.88 moko 516: throw Exception("file.execute", &file_spec, "%s error: %s (%d)", pid<0?"fork":"pipe", str?str:"<unknown>", errno);
1.59 paf 517: }
1.1 paf 518: #endif
519:
1.51 paf 520: return result;
1.1 paf 521: }
E-mail: