1//
2// ServerApplication.cpp
3//
4// Library: Util
5// Package: Application
6// Module: ServerApplication
7//
8// Copyright (c) 2004-2006, Applied Informatics Software Engineering GmbH.
9// and Contributors.
10//
11// SPDX-License-Identifier: BSL-1.0
12//
13
14
15#include "Poco/Util/ServerApplication.h"
16#include "Poco/Util/Option.h"
17#include "Poco/Util/OptionSet.h"
18#include "Poco/Util/OptionException.h"
19#include "Poco/FileStream.h"
20#include "Poco/Exception.h"
21#if !defined(POCO_VXWORKS)
22#include "Poco/Process.h"
23#include "Poco/NamedEvent.h"
24#endif
25#include "Poco/NumberFormatter.h"
26#include "Poco/Logger.h"
27#include "Poco/String.h"
28#if defined(POCO_OS_FAMILY_UNIX) && !defined(POCO_VXWORKS)
29#include "Poco/TemporaryFile.h"
30#include <stdlib.h>
31#include <unistd.h>
32#include <stdio.h>
33#include <signal.h>
34#include <sys/stat.h>
35#include <fstream>
36#elif defined(POCO_OS_FAMILY_WINDOWS)
37#if !defined(_WIN32_WCE)
38#include "Poco/Util/WinService.h"
39#include "Poco/Util/WinRegistryKey.h"
40#endif
41#include "Poco/UnWindows.h"
42#include <cstring>
43#endif
44#include "Poco/UnicodeConverter.h"
45
46
47using Poco::NumberFormatter;
48using Poco::Exception;
49using Poco::SystemException;
50
51
52namespace Poco {
53namespace Util {
54
55
56#if defined(POCO_OS_FAMILY_WINDOWS)
57Poco::NamedEvent ServerApplication::_terminate(Poco::ProcessImpl::terminationEventName(Poco::Process::id()));
58#if !defined(_WIN32_WCE)
59Poco::Event ServerApplication::_terminated;
60SERVICE_STATUS ServerApplication::_serviceStatus;
61SERVICE_STATUS_HANDLE ServerApplication::_serviceStatusHandle = 0;
62#endif
63#endif
64#if defined(POCO_VXWORKS) || POCO_OS == POCO_OS_ANDROID || defined(__NACL__) || defined(__EMSCRIPTEN__)
65Poco::Event ServerApplication::_terminate;
66#endif
67
68
69ServerApplication::ServerApplication()
70{
71#if defined(POCO_OS_FAMILY_WINDOWS)
72#if !defined(_WIN32_WCE)
73 _action = SRV_RUN;
74 std::memset(&_serviceStatus, 0, sizeof(_serviceStatus));
75#endif
76#endif
77}
78
79
80ServerApplication::~ServerApplication()
81{
82}
83
84
85bool ServerApplication::isInteractive() const
86{
87 bool runsInBackground = config().getBool("application.runAsDaemon", false) || config().getBool("application.runAsService", false);
88 return !runsInBackground;
89}
90
91
92int ServerApplication::run()
93{
94 return Application::run();
95}
96
97
98void ServerApplication::terminate()
99{
100#if defined(POCO_OS_FAMILY_WINDOWS)
101 _terminate.set();
102#elif defined(POCO_VXWORKS) || POCO_OS == POCO_OS_ANDROID || defined(__NACL__) || defined(__EMSCRIPTEN__)
103 _terminate.set();
104#else
105 Poco::Process::requestTermination(Process::id());
106#endif
107}
108
109
110#if defined(POCO_OS_FAMILY_WINDOWS)
111#if !defined(_WIN32_WCE)
112
113
114//
115// Windows specific code
116//
117BOOL ServerApplication::ConsoleCtrlHandler(DWORD ctrlType)
118{
119 switch (ctrlType)
120 {
121 case CTRL_C_EVENT:
122 case CTRL_CLOSE_EVENT:
123 case CTRL_BREAK_EVENT:
124 terminate();
125 return _terminated.tryWait(10000) ? TRUE : FALSE;
126 default:
127 return FALSE;
128 }
129}
130
131
132HDEVNOTIFY ServerApplication::registerServiceDeviceNotification(LPVOID filter, DWORD flags)
133{
134 return RegisterDeviceNotification(_serviceStatusHandle, filter, flags);
135}
136
137
138DWORD ServerApplication::handleDeviceEvent(DWORD /*event_type*/, LPVOID /*event_data*/)
139{
140 return ERROR_CALL_NOT_IMPLEMENTED;
141}
142
143
144DWORD ServerApplication::ServiceControlHandler(DWORD control, DWORD event_type, LPVOID event_data, LPVOID context)
145{
146 DWORD result = NO_ERROR;
147 ServerApplication* pThis = reinterpret_cast<ServerApplication*>(context);
148
149 switch (control)
150 {
151 case SERVICE_CONTROL_STOP:
152 case SERVICE_CONTROL_SHUTDOWN:
153 terminate();
154 _serviceStatus.dwCurrentState = SERVICE_STOP_PENDING;
155 break;
156 case SERVICE_CONTROL_INTERROGATE:
157 break;
158 case SERVICE_CONTROL_DEVICEEVENT:
159 if (pThis)
160 {
161 result = pThis->handleDeviceEvent(event_type, event_data);
162 }
163 break;
164 }
165 SetServiceStatus(_serviceStatusHandle, &_serviceStatus);
166 return result;
167}
168
169
170#if !defined(POCO_NO_WSTRING)
171void ServerApplication::ServiceMain(DWORD argc, LPWSTR* argv)
172#endif
173{
174 ServerApplication& app = static_cast<ServerApplication&>(Application::instance());
175
176 app.config().setBool("application.runAsService", true);
177
178#if !defined(POCO_NO_WSTRING)
179 _serviceStatusHandle = RegisterServiceCtrlHandlerExW(L"", ServiceControlHandler, &app);
180#endif
181 if (!_serviceStatusHandle)
182 throw SystemException("cannot register service control handler");
183
184 _serviceStatus.dwServiceType = SERVICE_WIN32;
185 _serviceStatus.dwCurrentState = SERVICE_START_PENDING;
186 _serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
187 _serviceStatus.dwWin32ExitCode = 0;
188 _serviceStatus.dwServiceSpecificExitCode = 0;
189 _serviceStatus.dwCheckPoint = 0;
190 _serviceStatus.dwWaitHint = 0;
191 SetServiceStatus(_serviceStatusHandle, &_serviceStatus);
192
193 try
194 {
195#if !defined(POCO_NO_WSTRING)
196 std::vector<std::string> args;
197 for (DWORD i = 0; i < argc; ++i)
198 {
199 std::string arg;
200 Poco::UnicodeConverter::toUTF8(argv[i], arg);
201 args.push_back(arg);
202 }
203 app.init(args);
204#endif
205 _serviceStatus.dwCurrentState = SERVICE_RUNNING;
206 SetServiceStatus(_serviceStatusHandle, &_serviceStatus);
207 int rc = app.run();
208 _serviceStatus.dwWin32ExitCode = rc ? ERROR_SERVICE_SPECIFIC_ERROR : 0;
209 _serviceStatus.dwServiceSpecificExitCode = rc;
210 }
211 catch (Exception& exc)
212 {
213 app.logger().log(exc);
214 _serviceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
215 _serviceStatus.dwServiceSpecificExitCode = EXIT_CONFIG;
216 }
217 catch (...)
218 {
219 app.logger().error("fatal error - aborting");
220 _serviceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
221 _serviceStatus.dwServiceSpecificExitCode = EXIT_SOFTWARE;
222 }
223 _serviceStatus.dwCurrentState = SERVICE_STOPPED;
224 SetServiceStatus(_serviceStatusHandle, &_serviceStatus);
225}
226
227
228void ServerApplication::waitForTerminationRequest()
229{
230 SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
231 _terminate.wait();
232 _terminated.set();
233}
234
235
236int ServerApplication::run(int argc, char** argv)
237{
238 if (!hasConsole() && isService())
239 {
240 return 0;
241 }
242 else
243 {
244 int rc = EXIT_OK;
245 try
246 {
247 init(argc, argv);
248 switch (_action)
249 {
250 case SRV_REGISTER:
251 registerService();
252 rc = EXIT_OK;
253 break;
254 case SRV_UNREGISTER:
255 unregisterService();
256 rc = EXIT_OK;
257 break;
258 default:
259 rc = run();
260 }
261 }
262 catch (Exception& exc)
263 {
264 logger().log(exc);
265 rc = EXIT_SOFTWARE;
266 }
267 return rc;
268 }
269}
270
271
272int ServerApplication::run(const std::vector<std::string>& args)
273{
274 if (!hasConsole() && isService())
275 {
276 return 0;
277 }
278 else
279 {
280 int rc = EXIT_OK;
281 try
282 {
283 init(args);
284 switch (_action)
285 {
286 case SRV_REGISTER:
287 registerService();
288 rc = EXIT_OK;
289 break;
290 case SRV_UNREGISTER:
291 unregisterService();
292 rc = EXIT_OK;
293 break;
294 default:
295 rc = run();
296 }
297 }
298 catch (Exception& exc)
299 {
300 logger().log(exc);
301 rc = EXIT_SOFTWARE;
302 }
303 return rc;
304 }
305}
306
307
308#if !defined(POCO_NO_WSTRING)
309int ServerApplication::run(int argc, wchar_t** argv)
310{
311 if (!hasConsole() && isService())
312 {
313 return 0;
314 }
315 else
316 {
317 int rc = EXIT_OK;
318 try
319 {
320 init(argc, argv);
321 switch (_action)
322 {
323 case SRV_REGISTER:
324 registerService();
325 rc = EXIT_OK;
326 break;
327 case SRV_UNREGISTER:
328 unregisterService();
329 rc = EXIT_OK;
330 break;
331 default:
332 rc = run();
333 }
334 }
335 catch (Exception& exc)
336 {
337 logger().log(exc);
338 rc = EXIT_SOFTWARE;
339 }
340 return rc;
341 }
342}
343#endif
344
345
346bool ServerApplication::isService()
347{
348#if !defined(POCO_NO_WSTRING)
349 SERVICE_TABLE_ENTRYW svcDispatchTable[2];
350 svcDispatchTable[0].lpServiceName = L"";
351 svcDispatchTable[0].lpServiceProc = ServiceMain;
352 svcDispatchTable[1].lpServiceName = NULL;
353 svcDispatchTable[1].lpServiceProc = NULL;
354 return StartServiceCtrlDispatcherW(svcDispatchTable) != 0;
355#endif
356}
357
358
359bool ServerApplication::hasConsole()
360{
361 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
362 return hStdOut != INVALID_HANDLE_VALUE && hStdOut != NULL;
363}
364
365
366void ServerApplication::registerService()
367{
368 std::string name = config().getString("application.baseName");
369 std::string path = config().getString("application.path");
370
371 WinService service(name);
372 if (_displayName.empty())
373 service.registerService(path);
374 else
375 service.registerService(path, _displayName);
376 if (_startup == "auto")
377 service.setStartup(WinService::SVC_AUTO_START);
378 else if (_startup == "manual")
379 service.setStartup(WinService::SVC_MANUAL_START);
380 if (!_description.empty())
381 service.setDescription(_description);
382 logger().information("The application has been successfully registered as a service.");
383}
384
385
386void ServerApplication::unregisterService()
387{
388 std::string name = config().getString("application.baseName");
389
390 WinService service(name);
391 service.unregisterService();
392 logger().information("The service has been successfully unregistered.");
393}
394
395
396void ServerApplication::defineOptions(OptionSet& options)
397{
398 Application::defineOptions(options);
399
400 options.addOption(
401 Option("registerService", "", "Register the application as a service.")
402 .required(false)
403 .repeatable(false)
404 .callback(OptionCallback<ServerApplication>(this, &ServerApplication::handleRegisterService)));
405
406 options.addOption(
407 Option("unregisterService", "", "Unregister the application as a service.")
408 .required(false)
409 .repeatable(false)
410 .callback(OptionCallback<ServerApplication>(this, &ServerApplication::handleUnregisterService)));
411
412 options.addOption(
413 Option("displayName", "", "Specify a display name for the service (only with /registerService).")
414 .required(false)
415 .repeatable(false)
416 .argument("name")
417 .callback(OptionCallback<ServerApplication>(this, &ServerApplication::handleDisplayName)));
418
419 options.addOption(
420 Option("description", "", "Specify a description for the service (only with /registerService).")
421 .required(false)
422 .repeatable(false)
423 .argument("text")
424 .callback(OptionCallback<ServerApplication>(this, &ServerApplication::handleDescription)));
425
426 options.addOption(
427 Option("startup", "", "Specify the startup mode for the service (only with /registerService).")
428 .required(false)
429 .repeatable(false)
430 .argument("automatic|manual")
431 .callback(OptionCallback<ServerApplication>(this, &ServerApplication::handleStartup)));
432}
433
434
435void ServerApplication::handleRegisterService(const std::string& /*name*/, const std::string& /*value*/)
436{
437 _action = SRV_REGISTER;
438}
439
440
441void ServerApplication::handleUnregisterService(const std::string& /*name*/, const std::string& /*value*/)
442{
443 _action = SRV_UNREGISTER;
444}
445
446
447void ServerApplication::handleDisplayName(const std::string& /*name*/, const std::string& value)
448{
449 _displayName = value;
450}
451
452
453void ServerApplication::handleDescription(const std::string& /*name*/, const std::string& value)
454{
455 _description = value;
456}
457
458
459void ServerApplication::handleStartup(const std::string& /*name*/, const std::string& value)
460{
461 if (Poco::icompare(value, 4, std::string("auto")) == 0)
462 _startup = "auto";
463 else if (Poco::icompare(value, std::string("manual")) == 0)
464 _startup = "manual";
465 else
466 throw InvalidArgumentException("argument to startup option must be 'auto[matic]' or 'manual'");
467}
468
469
470#else // _WIN32_WCE
471void ServerApplication::waitForTerminationRequest()
472{
473 _terminate.wait();
474}
475
476
477int ServerApplication::run(int argc, char** argv)
478{
479 try
480 {
481 init(argc, argv);
482 }
483 catch (Exception& exc)
484 {
485 logger().log(exc);
486 return EXIT_CONFIG;
487 }
488 return run();
489}
490
491
492int ServerApplication::run(const std::vector<std::string>& args)
493{
494 try
495 {
496 init(args);
497 }
498 catch (Exception& exc)
499 {
500 logger().log(exc);
501 return EXIT_CONFIG;
502 }
503 return run();
504}
505
506
507#if !defined(POCO_NO_WSTRING)
508int ServerApplication::run(int argc, wchar_t** argv)
509{
510 try
511 {
512 init(argc, argv);
513 }
514 catch (Exception& exc)
515 {
516 logger().log(exc);
517 return EXIT_CONFIG;
518 }
519 return run();
520}
521#endif
522
523
524#endif // _WIN32_WCE
525#elif defined(POCO_VXWORKS)
526//
527// VxWorks specific code
528//
529void ServerApplication::waitForTerminationRequest()
530{
531 _terminate.wait();
532}
533
534
535int ServerApplication::run(int argc, char** argv)
536{
537 try
538 {
539 init(argc, argv);
540 }
541 catch (Exception& exc)
542 {
543 logger().log(exc);
544 return EXIT_CONFIG;
545 }
546 return run();
547}
548
549
550int ServerApplication::run(const std::vector<std::string>& args)
551{
552 try
553 {
554 init(args);
555 }
556 catch (Exception& exc)
557 {
558 logger().log(exc);
559 return EXIT_CONFIG;
560 }
561 return run();
562}
563
564
565void ServerApplication::defineOptions(OptionSet& options)
566{
567 Application::defineOptions(options);
568}
569
570
571#elif defined(POCO_OS_FAMILY_UNIX)
572
573
574//
575// Unix specific code
576//
577void ServerApplication::waitForTerminationRequest()
578{
579#if POCO_OS != POCO_OS_ANDROID && !defined(__NACL__) && !defined(__EMSCRIPTEN__)
580 sigset_t sset;
581 sigemptyset(&sset);
582 if (!std::getenv("POCO_ENABLE_DEBUGGER"))
583 {
584 sigaddset(&sset, SIGINT);
585 }
586 sigaddset(&sset, SIGQUIT);
587 sigaddset(&sset, SIGTERM);
588 sigprocmask(SIG_BLOCK, &sset, NULL);
589 int sig;
590 sigwait(&sset, &sig);
591#else // POCO_OS != POCO_OS_ANDROID || __NACL__ || __EMSCRIPTEN__
592 _terminate.wait();
593#endif
594}
595
596
597int ServerApplication::run(int argc, char** pArgv)
598{
599 bool runAsDaemon = isDaemon(argc, pArgv);
600 if (runAsDaemon)
601 {
602 beDaemon();
603 }
604 try
605 {
606 init(argc, pArgv);
607 if (runAsDaemon)
608 {
609 int rc = chdir("/");
610 if (rc != 0) return EXIT_OSERR;
611 }
612 }
613 catch (Exception& exc)
614 {
615 logger().log(exc);
616 return EXIT_CONFIG;
617 }
618 return run();
619}
620
621
622int ServerApplication::run(const std::vector<std::string>& args)
623{
624 bool runAsDaemon = false;
625 for (std::vector<std::string>::const_iterator it = args.begin(); it != args.end(); ++it)
626 {
627 if (*it == "--daemon")
628 {
629 runAsDaemon = true;
630 break;
631 }
632 }
633 if (runAsDaemon)
634 {
635 beDaemon();
636 }
637 try
638 {
639 init(args);
640 if (runAsDaemon)
641 {
642 int rc = chdir("/");
643 if (rc != 0) return EXIT_OSERR;
644 }
645 }
646 catch (Exception& exc)
647 {
648 logger().log(exc);
649 return EXIT_CONFIG;
650 }
651 return run();
652}
653
654
655bool ServerApplication::isDaemon(int argc, char** pArgv)
656{
657 std::string option("--daemon");
658 for (int i = 1; i < argc; ++i)
659 {
660 if (option == pArgv[i])
661 return true;
662 }
663 return false;
664}
665
666
667void ServerApplication::beDaemon()
668{
669#if !defined(POCO_NO_FORK_EXEC)
670 pid_t pid;
671 if ((pid = fork()) < 0)
672 throw SystemException("cannot fork daemon process");
673 else if (pid != 0)
674 exit(0);
675
676 setsid();
677 umask(027);
678
679 // attach stdin, stdout, stderr to /dev/null
680 // instead of just closing them. This avoids
681 // issues with third party/legacy code writing
682 // stuff to stdout/stderr.
683 FILE* fin = freopen("/dev/null", "r+", stdin);
684 if (!fin) throw Poco::OpenFileException("Cannot attach stdin to /dev/null");
685 FILE* fout = freopen("/dev/null", "r+", stdout);
686 if (!fout) throw Poco::OpenFileException("Cannot attach stdout to /dev/null");
687 FILE* ferr = freopen("/dev/null", "r+", stderr);
688 if (!ferr) throw Poco::OpenFileException("Cannot attach stderr to /dev/null");
689#else
690 throw Poco::NotImplementedException("platform does not allow fork/exec");
691#endif
692}
693
694
695void ServerApplication::defineOptions(OptionSet& rOptions)
696{
697 Application::defineOptions(rOptions);
698
699 rOptions.addOption(
700 Option("daemon", "", "Run application as a daemon.")
701 .required(false)
702 .repeatable(false)
703 .callback(OptionCallback<ServerApplication>(this, &ServerApplication::handleDaemon)));
704
705 rOptions.addOption(
706 Option("umask", "", "Set the daemon's umask (octal, e.g. 027).")
707 .required(false)
708 .repeatable(false)
709 .argument("mask")
710 .callback(OptionCallback<ServerApplication>(this, &ServerApplication::handleUMask)));
711
712 rOptions.addOption(
713 Option("pidfile", "", "Write the process ID of the application to given file.")
714 .required(false)
715 .repeatable(false)
716 .argument("path")
717 .callback(OptionCallback<ServerApplication>(this, &ServerApplication::handlePidFile)));
718}
719
720
721void ServerApplication::handleDaemon(const std::string& rName, const std::string&)
722{
723 config().setBool("application.runAsDaemon", true);
724}
725
726
727void ServerApplication::handleUMask(const std::string& rName, const std::string& rValue)
728{
729 int mask = 0;
730 for (std::string::const_iterator it = rValue.begin(); it != rValue.end(); ++it)
731 {
732 mask *= 8;
733 if (*it >= '0' && *it <= '7')
734 mask += *it - '0';
735 else
736 throw Poco::InvalidArgumentException("umask contains non-octal characters", rValue);
737 }
738 umask(mask);
739}
740
741
742void ServerApplication::handlePidFile(const std::string& rName, const std::string& rValue)
743{
744 Poco::FileOutputStream ostr(rValue);
745 if (ostr.good())
746 ostr << Poco::Process::id() << std::endl;
747 else
748 throw Poco::CreateFileException("Cannot write PID to file", rValue);
749 Poco::TemporaryFile::registerForDeletion(rValue);
750}
751
752
753#endif
754
755
756} } // namespace Poco::Util
757