Fixed Windows build errors
[public/netxms.git] / src / agent / core / exec.cpp
1 /*
2 ** NetXMS multiplatform core agent
3 ** Copyright (C) 2003-2013 Victor Kirhenshtein
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 **
19 ** File: exec.cpp
20 **
21 **/
22
23 #include "nxagentd.h"
24
25 #ifdef _WIN32
26 #include <winternl.h>
27 #define popen _popen
28 #else
29 #include <sys/wait.h>
30 #endif
31
32 /**
33 * Information for process starter
34 */
35 struct PROCESS_START_INFO
36 {
37 char *cmdLine;
38 pid_t *pid;
39 CONDITION condProcessStarted;
40 };
41
42 /**
43 * Execute external command
44 */
45 #if !defined(_WIN32) && !defined(_NETWARE) /* unix-only hack */
46 static THREAD_RESULT THREAD_CALL Waiter(void *arg)
47 {
48 pid_t pid = *((pid_t *)arg);
49 waitpid(pid, NULL, 0);
50 free(arg);
51 return THREAD_OK;
52 }
53
54 static THREAD_RESULT THREAD_CALL Worker(void *arg)
55 {
56 char *cmd = ((PROCESS_START_INFO *)arg)->cmdLine;
57 if (cmd == NULL)
58 {
59 return THREAD_OK;
60 }
61
62 char *pCmd[128];
63 char *pTmp = cmd;
64 int state = 0;
65 int nCount = 0;
66
67 pCmd[nCount++] = pTmp;
68 int nLen = strlen(pTmp);
69 for (int i = 0; i < nLen; i++)
70 {
71 switch(pTmp[i])
72 {
73 case ' ':
74 if (state == 0)
75 {
76 pTmp[i] = 0;
77 if (pTmp[i + 1] != 0)
78 {
79 pCmd[nCount++] = pTmp + i + 1;
80 }
81 }
82 break;
83 case '"':
84 state == 0 ? state++ : state--;
85
86 memmove(pTmp + i, pTmp + i + 1, nLen - i);
87 i--;
88 break;
89 case '\\':
90 if (pTmp[i+1] == '"')
91 {
92 memmove(pTmp + i, pTmp + i + 1, nLen - i);
93 }
94 break;
95 default:
96 break;
97 }
98 }
99 pCmd[nCount] = NULL;
100
101 // DO EXEC
102 pid_t p;
103 p = fork();
104 switch(p)
105 {
106 case -1: // error
107 *((PROCESS_START_INFO *)arg)->pid = -1;
108 ConditionSet(((PROCESS_START_INFO *)arg)->condProcessStarted);
109 break;
110 case 0: // child
111 {
112 int sd = open("/dev/null", O_RDWR);
113 if (sd == -1)
114 {
115 sd = open("/dev/null", O_RDONLY);
116 }
117 close(0); close(1); close(2);
118 dup2(sd, 0); dup2(sd, 1); dup2(sd, 2);
119 close(sd);
120 execv(pCmd[0], pCmd);
121 _exit(127);
122 }
123 break;
124 default: // parent
125 {
126 *((PROCESS_START_INFO *)arg)->pid = p;
127 ConditionSet(((PROCESS_START_INFO *)arg)->condProcessStarted);
128 pid_t *pp = (pid_t *)malloc(sizeof(pid_t));
129 *pp = p;
130 ThreadCreate(Waiter, 0, (void *)pp);
131 }
132 break;
133 }
134
135 free(cmd);
136
137 return THREAD_OK;
138 }
139 #endif
140
141 UINT32 ExecuteCommand(TCHAR *pszCommand, StringList *args, pid_t *pid)
142 {
143 TCHAR *pszCmdLine, *sptr;
144 UINT32 i, dwSize, dwRetCode = ERR_SUCCESS;
145
146 DebugPrintf(INVALID_INDEX, 4, _T("EXEC: Expanding command \"%s\""), pszCommand);
147
148 // Substitute $1 .. $9 with actual arguments
149 if (args != NULL)
150 {
151 dwSize = ((UINT32)_tcslen(pszCommand) + 1) * sizeof(TCHAR);
152 pszCmdLine = (TCHAR *)malloc(dwSize);
153 for(sptr = pszCommand, i = 0; *sptr != 0; sptr++)
154 if (*sptr == _T('$'))
155 {
156 sptr++;
157 if (*sptr == 0)
158 break; // Single $ character at the end of line
159 if ((*sptr >= _T('1')) && (*sptr <= _T('9')))
160 {
161 int argNum = *sptr - _T('1');
162
163 if (argNum < args->size())
164 {
165 int iArgLength;
166
167 // Extend resulting line
168 iArgLength = (int)_tcslen(args->get(argNum));
169 dwSize += iArgLength * sizeof(TCHAR);
170 pszCmdLine = (TCHAR *)realloc(pszCmdLine, dwSize);
171 _tcscpy(&pszCmdLine[i], args->get(argNum));
172 i += iArgLength;
173 }
174 }
175 else
176 {
177 pszCmdLine[i++] = *sptr;
178 }
179 }
180 else
181 {
182 pszCmdLine[i++] = *sptr;
183 }
184 pszCmdLine[i] = 0;
185 }
186 else
187 {
188 pszCmdLine = pszCommand;
189 }
190
191 DebugPrintf(INVALID_INDEX, 4, _T("EXEC: Executing \"%s\""), pszCmdLine);
192 #if defined(_WIN32)
193 STARTUPINFO si;
194 PROCESS_INFORMATION pi;
195
196 // Fill in process startup info structure
197 memset(&si, 0, sizeof(STARTUPINFO));
198 si.cb = sizeof(STARTUPINFO);
199 si.dwFlags = 0;
200
201 // Create new process
202 if (!CreateProcess(NULL, pszCmdLine, NULL, NULL, FALSE,
203 CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
204 {
205 nxlog_write(MSG_CREATE_PROCESS_FAILED, EVENTLOG_ERROR_TYPE, "se", pszCmdLine, GetLastError());
206 dwRetCode = ERR_EXEC_FAILED;
207 }
208 else
209 {
210 if (pid != NULL)
211 {
212 HMODULE dllKernel32;
213
214 dllKernel32 = LoadLibrary(_T("KERNEL32.DLL"));
215 if (dllKernel32 != NULL)
216 {
217 DWORD (WINAPI *fpGetProcessId)(HANDLE);
218
219 fpGetProcessId = (DWORD (WINAPI *)(HANDLE))GetProcAddress(dllKernel32, "GetProcessId");
220 if (fpGetProcessId != NULL)
221 {
222 *pid = fpGetProcessId(pi.hProcess);
223 }
224 else
225 {
226 HMODULE dllNtdll;
227
228 dllNtdll = LoadLibrary(_T("NTDLL.DLL"));
229 if (dllNtdll != NULL)
230 {
231 NTSTATUS (WINAPI *pfZwQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
232
233 pfZwQueryInformationProcess = (NTSTATUS (WINAPI *)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG))GetProcAddress(dllNtdll, "ZwQueryInformationProcess");
234 if (pfZwQueryInformationProcess != NULL)
235 {
236 PROCESS_BASIC_INFORMATION pbi;
237 ULONG len;
238
239 if (pfZwQueryInformationProcess(pi.hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), &len) == 0)
240 {
241 *pid = (pid_t)pbi.UniqueProcessId;
242 }
243 }
244 FreeLibrary(dllNtdll);
245 }
246 }
247 FreeLibrary(dllKernel32);
248 }
249 }
250
251 // Close all handles
252 CloseHandle(pi.hThread);
253 CloseHandle(pi.hProcess);
254 }
255 #elif defined(_NETWARE)
256 if (system(pszCmdLine) == 0)
257 dwRetCode = ERR_SUCCESS;
258 else
259 dwRetCode = ERR_EXEC_FAILED;
260 #else
261 PROCESS_START_INFO pi;
262 pid_t tempPid;
263
264 #ifdef UNICODE
265 pi.cmdLine = MBStringFromWideString(pszCmdLine);
266 #else
267 pi.cmdLine = strdup(pszCmdLine);
268 #endif
269 pi.pid = (pid != NULL) ? pid : &tempPid;
270 pi.condProcessStarted = ConditionCreate(TRUE);
271 if (ThreadCreate(Worker, 0, &pi))
272 {
273 if (ConditionWait(pi.condProcessStarted, 5000))
274 {
275 if (*pi.pid == -1)
276 dwRetCode = ERR_EXEC_FAILED;
277 }
278 else
279 {
280 dwRetCode = ERR_EXEC_FAILED;
281 }
282 ConditionDestroy(pi.condProcessStarted);
283 }
284 else
285 {
286 dwRetCode = ERR_EXEC_FAILED;
287 }
288 #endif
289
290 // Cleanup
291 if (args != NULL)
292 free(pszCmdLine);
293
294 return dwRetCode;
295 }
296
297 /**
298 * Structure for passing data to popen() worker
299 */
300 struct POPEN_WORKER_DATA
301 {
302 int status;
303 TCHAR *cmdLine;
304 StringList values;
305 CONDITION finished;
306 CONDITION released;
307 };
308
309 /**
310 * Worker thread for executing external parameter handler
311 */
312 static THREAD_RESULT THREAD_CALL POpenWorker(void *arg)
313 {
314 FILE *hPipe;
315 POPEN_WORKER_DATA *data = (POPEN_WORKER_DATA *)arg;
316
317 if ((hPipe = _tpopen(data->cmdLine, _T("r"))) != NULL)
318 {
319 data->status = SYSINFO_RC_SUCCESS;
320
321 while(true)
322 {
323 TCHAR value[MAX_RESULT_LENGTH];
324
325 TCHAR *ret = safe_fgetts(value, MAX_RESULT_LENGTH, hPipe);
326 if (ret == NULL)
327 {
328 if (!feof(hPipe))
329 {
330 DebugPrintf(INVALID_INDEX, 4, _T("H_ExternalParameter/POpenWorker: worker thread pipe read error: %s"), _tcserror(errno));
331 data->status = SYSINFO_RC_ERROR;
332 }
333 break;
334 }
335
336 DebugPrintf(INVALID_INDEX, 4, _T("H_ExternalParameter/POpenWorker: worker thread pipe read result: %p"), ret);
337 TCHAR *pTmp;
338 if ((pTmp = _tcschr(value, _T('\n'))) != NULL)
339 {
340 *pTmp = 0;
341 }
342 if (value[0] != 0)
343 {
344 data->values.add(value);
345 }
346 }
347 pclose(hPipe);
348 }
349 else
350 {
351 nxlog_write(MSG_CREATE_PROCESS_FAILED, EVENTLOG_ERROR_TYPE, "se", data->cmdLine, errno);
352 data->status = SYSINFO_RC_ERROR;
353 }
354
355 // Notify caller that data is available and wait while caller
356 // retrieves the data, then destroy object
357 ConditionSet(data->finished);
358 ConditionWait(data->released, INFINITE);
359 ConditionDestroy(data->finished);
360 ConditionDestroy(data->released);
361 delete data;
362
363 return THREAD_OK;
364 }
365
366 /**
367 * Exec function for external (user-defined) parameters and lists
368 */
369 LONG RunExternal(const TCHAR *pszCmd, const TCHAR *pszArg, StringList *value)
370 {
371 TCHAR *pszCmdLine, szBuffer[1024];
372 const TCHAR *sptr;
373 int i, iSize, iStatus;
374
375 DebugPrintf(INVALID_INDEX, 4, _T("RunExternal called for \"%s\" \"%s\""), pszCmd, pszArg);
376
377 // Substitute $1 .. $9 with actual arguments
378 iSize = (int)_tcslen(pszArg) * sizeof(TCHAR); // we don't need _tcslen + 1 because loop starts from &pszArg[1]
379 pszCmdLine = (TCHAR *)malloc(iSize);
380 for(sptr = &pszArg[1], i = 0; *sptr != 0; sptr++)
381 if (*sptr == _T('$'))
382 {
383 sptr++;
384 if (*sptr == 0)
385 break; // Single $ character at the end of line
386 if ((*sptr >= _T('1')) && (*sptr <= _T('9')))
387 {
388 if (AgentGetParameterArg(pszCmd, *sptr - '0', szBuffer, 1024))
389 {
390 int iArgLength;
391
392 // Extend resulting line
393 iArgLength = (int)_tcslen(szBuffer);
394 iSize += iArgLength * sizeof(TCHAR);
395 pszCmdLine = (TCHAR *)realloc(pszCmdLine, iSize);
396 _tcscpy(&pszCmdLine[i], szBuffer);
397 i += iArgLength;
398 }
399 }
400 else
401 {
402 pszCmdLine[i++] = *sptr;
403 }
404 }
405 else
406 {
407 pszCmdLine[i++] = *sptr;
408 }
409 pszCmdLine[i] = 0;
410 DebugPrintf(INVALID_INDEX, 4, _T("RunExternal: command line is \"%s\""), pszCmdLine);
411
412 #if defined(_WIN32)
413 if (*pszArg == _T('E'))
414 {
415 STARTUPINFO si;
416 PROCESS_INFORMATION pi;
417 SECURITY_ATTRIBUTES sa;
418 HANDLE hOutput;
419 TCHAR szTempFile[MAX_PATH];
420
421 // Create temporary file to hold process output
422 GetTempPath(MAX_PATH - 1, szBuffer);
423 GetTempFileName(szBuffer, _T("nx"), 0, szTempFile);
424 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
425 sa.lpSecurityDescriptor = NULL;
426 sa.bInheritHandle = TRUE;
427 hOutput = CreateFile(szTempFile, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
428 &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL);
429 if (hOutput != INVALID_HANDLE_VALUE)
430 {
431 // Fill in process startup info structure
432 memset(&si, 0, sizeof(STARTUPINFO));
433 si.cb = sizeof(STARTUPINFO);
434 si.dwFlags = STARTF_USESTDHANDLES;
435 si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
436 si.hStdOutput = hOutput;
437 si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
438
439 // Create new process
440 if (CreateProcess(NULL, pszCmdLine, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
441 {
442 // Wait for process termination and close all handles
443 if (WaitForSingleObject(pi.hProcess, g_dwExecTimeout) == WAIT_OBJECT_0)
444 {
445 // Rewind temporary file for reading
446 SetFilePointer(hOutput, 0, NULL, FILE_BEGIN);
447
448 // Read process output
449 DWORD size = GetFileSize(hOutput, NULL);
450 char *buffer = (char *)malloc(size + 1);
451 ReadFile(hOutput, buffer, size, &size, NULL);
452 buffer[size] = 0;
453
454 char *line = strtok(buffer, "\n");
455 while(line != NULL)
456 {
457 char *eptr = strchr(line, '\r');
458 if (eptr != NULL)
459 *eptr = 0;
460 StrStripA(line);
461
462 if (line[0] != 0)
463 {
464 #ifdef UNICODE
465 value->addPreallocated(WideStringFromMBString(line));
466 #else
467 value->add(line);
468 #endif
469 }
470 line = strtok(NULL, "\n");
471 }
472 iStatus = SYSINFO_RC_SUCCESS;
473 }
474 else
475 {
476 // Timeout waiting for external process to complete, kill it
477 TerminateProcess(pi.hProcess, 127);
478 nxlog_write(MSG_PROCESS_KILLED, EVENTLOG_WARNING_TYPE, "s", pszCmdLine);
479 iStatus = SYSINFO_RC_ERROR;
480 }
481
482 CloseHandle(pi.hThread);
483 CloseHandle(pi.hProcess);
484 }
485 else
486 {
487 nxlog_write(MSG_CREATE_PROCESS_FAILED, EVENTLOG_ERROR_TYPE, "se", pszCmdLine, GetLastError());
488 iStatus = SYSINFO_RC_ERROR;
489 }
490
491 // Remove temporary file
492 CloseHandle(hOutput);
493 DeleteFile(szTempFile);
494 }
495 else
496 {
497 nxlog_write(MSG_CREATE_TMP_FILE_FAILED, EVENTLOG_ERROR_TYPE, "e", GetLastError());
498 iStatus = SYSINFO_RC_ERROR;
499 }
500 }
501 else
502 {
503 #endif
504 {
505 POPEN_WORKER_DATA *data;
506
507 data = new POPEN_WORKER_DATA;
508 data->cmdLine = pszCmdLine;
509 data->finished = ConditionCreate(TRUE);
510 data->released = ConditionCreate(TRUE);
511 ThreadCreate(POpenWorker, 0, data);
512 DebugPrintf(INVALID_INDEX, 4, _T("RunExternal (shell exec): worker thread created"));
513 if (ConditionWait(data->finished, g_dwExecTimeout))
514 {
515 iStatus = data->status;
516 if (iStatus == SYSINFO_RC_SUCCESS)
517 {
518 value->addAll(&data->values);
519 }
520 }
521 else
522 {
523 // Timeout
524 DebugPrintf(INVALID_INDEX, 4, _T("RunExternal (shell exec): execution timeout"));
525 iStatus = SYSINFO_RC_ERROR;
526 }
527 ConditionSet(data->released); // Allow worker to destroy data
528 DebugPrintf(INVALID_INDEX, 4, _T("RunExternal (shell exec): execution status %d"), iStatus);
529 }
530
531 #ifdef _WIN32
532 }
533 #endif
534
535 free(pszCmdLine);
536 return iStatus;
537 }
538
539 /**
540 * Handler function for external (user-defined) parameters
541 */
542 LONG H_ExternalParameter(const TCHAR *cmd, const TCHAR *arg, TCHAR *value, AbstractCommSession *session)
543 {
544 DebugPrintf(INVALID_INDEX, 4, _T("H_ExternalParameter called for \"%s\" \"%s\""), cmd, arg);
545 StringList values;
546 LONG status = RunExternal(cmd, arg, &values);
547 if (status == SYSINFO_RC_SUCCESS)
548 {
549 ret_string(value, values.size() > 0 ? values.get(0) : _T(""));
550 }
551 return status;
552 }
553
554 /**
555 * Handler function for external (user-defined) lists
556 */
557 LONG H_ExternalList(const TCHAR *cmd, const TCHAR *arg, StringList *value, AbstractCommSession *session)
558 {
559 DebugPrintf(INVALID_INDEX, 4, _T("H_ExternalList called for \"%s\" \"%s\""), cmd, arg);
560 StringList values;
561 LONG status = RunExternal(cmd, arg, &values);
562 if (status == SYSINFO_RC_SUCCESS)
563 {
564 value->addAll(&values);
565 }
566 return status;
567 }
568
569 /**
570 * Execute external command via shell
571 */
572 UINT32 ExecuteShellCommand(TCHAR *pszCommand, StringList *args)
573 {
574 TCHAR *pszCmdLine, *sptr;
575 UINT32 i, dwSize, dwRetCode = ERR_SUCCESS;
576
577 DebugPrintf(INVALID_INDEX, 4, _T("SH_EXEC: Expanding command \"%s\""), pszCommand);
578
579 // Substitute $1 .. $9 with actual arguments
580 if (args != NULL)
581 {
582 dwSize = ((UINT32)_tcslen(pszCommand) + 1) * sizeof(TCHAR);
583 pszCmdLine = (TCHAR *)malloc(dwSize);
584 for(sptr = pszCommand, i = 0; *sptr != 0; sptr++)
585 if (*sptr == _T('$'))
586 {
587 sptr++;
588 if (*sptr == 0)
589 break; // Single $ character at the end of line
590 if ((*sptr >= _T('1')) && (*sptr <= _T('9')))
591 {
592 int argNum = *sptr - _T('1');
593
594 if (argNum < args->size())
595 {
596 int iArgLength;
597
598 // Extend resulting line
599 iArgLength = (int)_tcslen(args->get(argNum));
600 dwSize += iArgLength * sizeof(TCHAR);
601 pszCmdLine = (TCHAR *)realloc(pszCmdLine, dwSize);
602 _tcscpy(&pszCmdLine[i], args->get(argNum));
603 i += iArgLength;
604 }
605 }
606 else
607 {
608 pszCmdLine[i++] = *sptr;
609 }
610 }
611 else
612 {
613 pszCmdLine[i++] = *sptr;
614 }
615 pszCmdLine[i] = 0;
616 }
617 else
618 {
619 pszCmdLine = pszCommand;
620 }
621
622 DebugPrintf(INVALID_INDEX, 4, _T("SH_EXEC: Executing \"%s\""), pszCmdLine);
623
624 if (_tsystem(pszCmdLine) == 0)
625 dwRetCode = ERR_SUCCESS;
626 else
627 dwRetCode = ERR_EXEC_FAILED;
628
629 // Cleanup
630 if (args != NULL)
631 free(pszCmdLine);
632
633 return dwRetCode;
634 }