8992b54eae4341272af57f09e8b4a10de3b6e71f
[public/netxms.git] / src / server / netxmsd / netxmsd.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Server startup module
4 ** Copyright (C) 2003-2017 Raden Solutions
5 **
6 ** This program is free software; you can redistribute it and/or modify
7 ** it under the terms of the GNU General Public License as published by
8 ** the Free Software Foundation; either version 2 of the License, or
9 ** (at your option) any later version.
10 **
11 ** This program is distributed in the hope that it will be useful,
12 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ** GNU General Public License for more details.
15 **
16 ** You should have received a copy of the GNU General Public License
17 ** along with this program; if not, write to the Free Software
18 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 **
20 ** File: netxmsd.cpp
21 **
22 **/
23
24 #include "netxmsd.h"
25
26 #if HAVE_GETOPT_H
27 #include <getopt.h>
28 #endif
29
30 #ifdef _WIN32
31 #include <dbghelp.h>
32 #endif
33
34 #if defined(_WITH_ENCRYPTION) && defined(_WIN32)
35 #include <openssl/applink.c>
36 #endif
37
38 /**
39 * Global data
40 */
41 BOOL g_bCheckDB = FALSE;
42
43 /**
44 * Debug level
45 */
46 static int s_debugLevel = NXCONFIG_UNINITIALIZED_VALUE;
47 static TCHAR *s_debugTags = NULL;
48
49 /**
50 * Help text
51 */
52 static TCHAR help_text[] = _T("NetXMS Server Version ") NETXMS_VERSION_STRING _T(" Build ") NETXMS_VERSION_BUILD_STRING _T(" (") NETXMS_BUILD_TAG _T(")") IS_UNICODE_BUILD_STRING _T("\n")
53 _T("Copyright (c) 2003-2015 Raden Solutions\n\n")
54 _T("Usage: netxmsd [<options>]\n\n")
55 _T("Valid options are:\n")
56 _T(" -e : Run database check on startup\n")
57 _T(" -c <file> : Set non-default configuration file\n")
58 _T(" -d : Run as daemon/service\n")
59 _T(" -D <level> : Set debug level (valid levels are 0..9)\n")
60 _T(" -h : Display help and exit\n")
61 #ifdef _WIN32
62 _T(" -I : Install Windows service\n")
63 _T(" -L <user> : Login name for service account.\n")
64 _T(" -P <passwd> : Password for service account.\n")
65 #else
66 _T(" -p <file> : Specify pid file.\n")
67 #endif
68 _T(" -q : Disable interactive console\n")
69 #ifdef _WIN32
70 _T(" -R : Remove Windows service\n")
71 _T(" -s : Start Windows service\n")
72 _T(" -S : Stop Windows service\n")
73 #endif
74 _T(" -v : Display version and exit\n")
75 _T("\n");
76
77 /**
78 * Execute command and wait
79 */
80 static BOOL ExecAndWait(char *pszCommand)
81 {
82 BOOL bSuccess = TRUE;
83
84 #ifdef _WIN32
85 STARTUPINFOA si;
86 PROCESS_INFORMATION pi;
87
88 // Fill in process startup info structure
89 memset(&si, 0, sizeof(STARTUPINFOA));
90 si.cb = sizeof(STARTUPINFOA);
91 si.dwFlags = 0;
92
93 // Create new process
94 if (!CreateProcessA(NULL, pszCommand, NULL, NULL, FALSE,
95 IsStandalone() ? 0 : CREATE_NO_WINDOW | DETACHED_PROCESS,
96 NULL, NULL, &si, &pi))
97 {
98 bSuccess = FALSE;
99 }
100 else
101 {
102 WaitForSingleObject(pi.hProcess, INFINITE);
103
104 // Close all handles
105 CloseHandle(pi.hThread);
106 CloseHandle(pi.hProcess);
107 }
108 #else
109 bSuccess = (system(pszCommand) != -1);
110 #endif
111
112 return bSuccess;
113 }
114
115 /**
116 * Create minidump of given process
117 */
118
119 #ifdef _WIN32
120
121 static void CreateMiniDump(DWORD pid)
122 {
123 HANDLE hFile, hProcess;
124 TCHAR error[256];
125
126 _tprintf(_T("INFO: Starting minidump for process %d\n"), pid);
127 hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
128 if (hProcess != NULL)
129 {
130 TCHAR fname[MAX_PATH];
131 _sntprintf(fname, MAX_PATH, _T("%s\\netxmsd-%u-%u.mdmp"), g_szDumpDir, pid, time(NULL));
132 hFile = CreateFile(fname, GENERIC_WRITE, 0, NULL,
133 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
134 if (hFile != INVALID_HANDLE_VALUE)
135 {
136 static const TCHAR *comments = _T("Version: ") NETXMS_VERSION_STRING _T("\nBuild tag: ") NETXMS_BUILD_TAG;
137 MINIDUMP_USER_STREAM us;
138 us.Type = CommentStreamW;
139 us.Buffer = (void*)comments;
140 us.BufferSize = static_cast<ULONG>(_tcslen(comments) * sizeof(TCHAR));
141
142 MINIDUMP_USER_STREAM_INFORMATION usi;
143 usi.UserStreamCount = 1;
144 usi.UserStreamArray = &us;
145
146 MiniDumpWriteDump(hProcess, pid, hFile,
147 static_cast<MINIDUMP_TYPE>(MiniDumpWithFullMemory | MiniDumpWithHandleData | MiniDumpWithProcessThreadData), NULL, &usi, NULL);
148 CloseHandle(hFile);
149 _tprintf(_T("INFO: Minidump created successfully\n"));
150 }
151 else
152 {
153 _tprintf(_T("ERROR: cannot create file for minidump (%s)\n"), GetSystemErrorText(GetLastError(), error, 256));
154 }
155 }
156 else
157 {
158 _tprintf(_T("ERROR: cannot open process %d (%s)\n"), pid, GetSystemErrorText(GetLastError(), error, 256));
159 }
160 }
161
162 #endif
163
164 #ifdef _WIN32
165 #define VALID_OPTIONS "c:CdD:ehIL:P:qRsSv"
166 #else
167 #define VALID_OPTIONS "c:CdD:ehp:qv"
168 #endif
169
170 /**
171 * Parse command line
172 * Returns TRUE on success and FALSE on failure
173 */
174 static BOOL ParseCommandLine(int argc, char *argv[])
175 {
176 int ch;
177 char *eptr;
178 #ifdef _WIN32
179 TCHAR login[256] = _T(""), password[256] = _T("");
180 char exePath[MAX_PATH], dllPath[MAX_PATH], *ptr;
181 BOOL useLogin = FALSE;
182 #endif
183 #if defined(_WIN32) || HAVE_DECL_GETOPT_LONG
184 static struct option longOptions[] =
185 {
186 { (char *)"check-db", 0, NULL, 'e' },
187 { (char *)"config", 1, NULL, 'c' },
188 { (char *)"daemon", 0, NULL, 'd' },
189 { (char *)"debug", 1, NULL, 'D' },
190 { (char *)"help", 0, NULL, 'h' },
191 { (char *)"quiet", 1, NULL, 'q' },
192 #ifdef _WIN32
193 { (char *)"check-service", 0, NULL, '!' },
194 { (char *)"dump", 1, NULL, '~' },
195 { (char *)"dump-dir", 1, NULL, '@' },
196 { (char *)"install", 0, NULL, 'I' },
197 { (char *)"login", 1, NULL, 'L' },
198 { (char *)"password", 1, NULL, 'P' },
199 { (char *)"remove", 0, NULL, 'R' },
200 { (char *)"start", 0, NULL, 's' },
201 { (char *)"stop", 0, NULL, 'S' },
202 #else
203 { (char *)"pid-file", 1, NULL, 'p' },
204 #endif
205 { NULL, 0, 0, 0 }
206 };
207 #endif
208
209 #if defined(_WIN32) || HAVE_DECL_GETOPT_LONG
210 while((ch = getopt_long(argc, argv, VALID_OPTIONS, longOptions, NULL)) != -1)
211 #else
212 while((ch = getopt(argc, argv, VALID_OPTIONS)) != -1)
213 #endif
214 {
215 switch(ch)
216 {
217 case 'h':
218 _tprintf(help_text);
219 return FALSE;
220 case 'v':
221 {
222 _tprintf(_T("NetXMS Server Version ") NETXMS_VERSION_STRING _T(" Build ") NETXMS_VERSION_BUILD_STRING _T(" (") NETXMS_BUILD_TAG _T(")") IS_UNICODE_BUILD_STRING _T("\n"));
223 String ciphers = NXCPGetSupportedCiphersAsText();
224 _tprintf(_T("NXCP: %d.%d.%d.%d (%s)\n"),
225 NXCP_VERSION, CLIENT_PROTOCOL_VERSION_BASE, CLIENT_PROTOCOL_VERSION_MOBILE, CLIENT_PROTOCOL_VERSION_FULL,
226 ciphers.isEmpty() ? _T("NO ENCRYPTION") : ciphers.getBuffer());
227 _tprintf(_T("Built with: %hs\n"), CPP_COMPILER_VERSION);
228 }
229 return FALSE;
230 case 'c':
231 #ifdef UNICODE
232 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, optarg, -1, g_szConfigFile, MAX_PATH);
233 g_szConfigFile[MAX_PATH - 1] = 0;
234 #else
235 nx_strncpy(g_szConfigFile, optarg, MAX_PATH);
236 #endif
237 break;
238 case 'C': // Check config
239 g_flags &= ~AF_DAEMON;
240 _tprintf(_T("Checking configuration file (%s):\n\n"), g_szConfigFile);
241 LoadConfig(&s_debugLevel);
242 return FALSE;
243 case 'd':
244 g_flags |= AF_DAEMON;
245 break;
246 case 'D': // Debug level
247 s_debugLevel = strtoul(optarg, &eptr, 0);
248 if ((*eptr != 0) || (s_debugLevel > 9))
249 {
250 _tprintf(_T("Invalid debug level \"%hs\" - should be in range 0..9\n"), optarg);
251 s_debugLevel = 0;
252 }
253 break;
254 case 'q': // disable interactive console
255 g_flags |= AF_DEBUG_CONSOLE_DISABLED;
256 break;
257 case 'e':
258 g_bCheckDB = TRUE;
259 break;
260 #ifdef _WIN32
261 case 'L':
262 #ifdef UNICODE
263 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, optarg, -1, login, 256);
264 login[255] = 0;
265 #else
266 nx_strncpy(login, optarg, 256);
267 #endif
268 useLogin = TRUE;
269 break;
270 #ifndef _WIN32
271 case 'p': // PID file
272 #ifdef UNICODE
273 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, optarg, -1, g_szPIDFile, MAX_PATH);
274 g_szPIDFile[MAX_PATH - 1] = 0;
275 #else
276 nx_strncpy(g_szPIDFile, optarg, MAX_PATH);
277 #endif
278 break;
279 #endif
280 case 'P':
281 #ifdef UNICODE
282 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, optarg, -1, password, 256);
283 password[255] = 0;
284 #else
285 nx_strncpy(password, optarg, 256);
286 #endif
287 break;
288 case 'I': // Install service
289 ptr = strrchr(argv[0], '\\');
290 if (ptr != NULL)
291 ptr++;
292 else
293 ptr = argv[0];
294
295 _fullpath(exePath, ptr, 255);
296
297 if (stricmp(&exePath[strlen(exePath)-4], ".exe"))
298 strcat(exePath, ".exe");
299 strcpy(dllPath, exePath);
300 ptr = strrchr(dllPath, '\\');
301 if (ptr != NULL) // Shouldn't be NULL
302 {
303 ptr++;
304 strcpy(ptr, "libnxsrv.dll");
305 }
306 #ifdef UNICODE
307 WCHAR wexePath[MAX_PATH], wdllPath[MAX_PATH];
308 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, exePath, -1, wexePath, MAX_PATH);
309 wexePath[MAX_PATH - 1] = 0;
310 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, dllPath, -1, wdllPath, MAX_PATH);
311 wdllPath[MAX_PATH - 1] = 0;
312 InstallService(wexePath, wdllPath, useLogin ? login : NULL, useLogin ? password : NULL);
313 #else
314 InstallService(exePath, dllPath, useLogin ? login : NULL, useLogin ? password : NULL);
315 #endif
316 return FALSE;
317 case 'R': // Remove service
318 RemoveService();
319 return FALSE;
320 case 's': // Start service
321 StartCoreService();
322 return FALSE;
323 case 'S': // Stop service
324 StopCoreService();
325 return FALSE;
326 case '!': // Check service configuration (for migration from pre-0.2.20)
327 CheckServiceConfig();
328 return FALSE;
329 case '@':
330 #ifdef UNICODE
331 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, optarg, -1, g_szDumpDir, MAX_PATH);
332 #else
333 strlcpy(g_szDumpDir, optarg, MAX_PATH);
334 #endif
335 if (g_szDumpDir[_tcslen(g_szDumpDir) - 1] == _T('\\'))
336 {
337 g_szDumpDir[_tcslen(g_szDumpDir) - 1] = 0;
338 }
339 break;
340 case '~':
341 CreateMiniDump(strtoul(optarg, NULL, 0));
342 return FALSE;
343 #endif
344 default:
345 break;
346 }
347 }
348 return TRUE;
349 }
350
351 /**
352 * Startup code
353 */
354 int main(int argc, char* argv[])
355 {
356 InitNetXMSProcess(false);
357
358 // Check for alternate config file location
359 #ifdef _WIN32
360 HKEY hKey;
361 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("Software\\NetXMS\\Server"), 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
362 {
363 DWORD dwSize = MAX_PATH * sizeof(TCHAR);
364 RegQueryValueEx(hKey, _T("ConfigFile"), NULL, NULL, (BYTE *)g_szConfigFile, &dwSize);
365 RegCloseKey(hKey);
366 }
367 #else
368 const TCHAR *configEnv = _tgetenv(_T("NETXMSD_CONFIG"));
369 if ((configEnv != NULL) && (*configEnv != 0))
370 {
371 nx_strncpy(g_szConfigFile, configEnv, MAX_PATH);
372 }
373 #endif
374
375 if (!ParseCommandLine(argc, argv))
376 return 1;
377
378 if (!LoadConfig(&s_debugLevel))
379 {
380 if (IsStandalone())
381 _tprintf(_T("Error loading configuration file\n"));
382 return 1;
383 }
384
385 if (s_debugLevel == NXCONFIG_UNINITIALIZED_VALUE)
386 s_debugLevel = 0;
387 nxlog_set_debug_level(s_debugLevel);
388 free(s_debugTags);
389
390 // Set exception handler
391 #ifdef _WIN32
392 if (g_flags & AF_CATCH_EXCEPTIONS)
393 SetExceptionHandler(SEHServiceExceptionHandler, SEHServiceExceptionDataWriter,
394 g_szDumpDir, _T("netxmsd"), MSG_EXCEPTION, (g_flags & AF_WRITE_FULL_DUMP) ? true : false, IsStandalone());
395 __try {
396 #endif
397
398 // Check database before start if requested
399 if (g_bCheckDB)
400 {
401 char szCmd[MAX_PATH + 128], *pszSep;
402
403 strncpy(szCmd, argv[0], MAX_PATH);
404 pszSep = strrchr(szCmd, FS_PATH_SEPARATOR[0]);
405 if (pszSep != NULL)
406 pszSep++;
407 else
408 pszSep = szCmd;
409 #ifdef UNICODE
410 snprintf(pszSep, 128, "nxdbmgr -c \"%S\" -f check", g_szConfigFile);
411 #else
412 snprintf(pszSep, 128, "nxdbmgr -c \"%s\" -f check", g_szConfigFile);
413 #endif
414 if (!ExecAndWait(szCmd))
415 if (IsStandalone())
416 _tprintf(_T("ERROR: Failed to execute command \"%hs\"\n"), szCmd);
417 }
418
419 #ifdef _WIN32
420 if (!IsStandalone())
421 {
422 InitService();
423 }
424 else
425 {
426 if (!Initialize())
427 {
428 _tprintf(_T("NetXMS Core initialization failed\n"));
429
430 // Remove database lock
431 if (g_flags & AF_DB_LOCKED)
432 {
433 UnlockDB();
434 ShutdownDB();
435 }
436 nxlog_close();
437 return 3;
438 }
439 Main(NULL);
440 }
441 #else /* not _WIN32 */
442 if (!IsStandalone())
443 {
444 if (daemon(0, 0) == -1)
445 {
446 perror("Call to daemon() failed");
447 return 2;
448 }
449 }
450
451 // Write PID file
452 FILE *fp = _tfopen(g_szPIDFile, _T("w"));
453 if (fp != NULL)
454 {
455 _ftprintf(fp, _T("%d"), getpid());
456 fclose(fp);
457 }
458
459 // Initialize server
460 if (!Initialize())
461 {
462 // Remove database lock
463 if (g_flags & AF_DB_LOCKED)
464 {
465 UnlockDB();
466 ShutdownDB();
467 }
468 nxlog_close();
469 return 3;
470 }
471
472 // Everything is OK, start common main loop
473 StartMainLoop(SignalHandler, Main);
474 #endif /* _WIN32 */
475
476 #ifdef _WIN32
477 LIBNETXMS_EXCEPTION_HANDLER
478 #endif
479 return 0;
480 }