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