1 /*
2 * svcproc.cxx
3 *
4 * Service process (daemon) implementation.
5 *
6 * Portable Windows Library
7 *
8 * Copyright (c) 1993-1998 Equivalence Pty. Ltd.
9 *
10 * The contents of this file are subject to the Mozilla Public License
11 * Version 1.0 (the "License"); you may not use this file except in
12 * compliance with the License. You may obtain a copy of the License at
13 * http://www.mozilla.org/MPL/
14 *
15 * Software distributed under the License is distributed on an "AS IS"
16 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17 * the License for the specific language governing rights and limitations
18 * under the License.
19 *
20 * The Original Code is Portable Windows Library.
21 *
22 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
23 *
24 * Portions are Copyright (C) 1993 Free Software Foundation, Inc.
25 * All Rights Reserved.
26 *
27 * Contributor(s): ______________________________________.
28 *
29 * $Revision: 28050 $
30 * $Author: rjongbloed $
31 * $Date: 2012-07-18 02:15:30 -0500 (Wed, 18 Jul 2012) $
32 */
33
34 #include <ptlib.h>
35
36 #pragma implementation "svcproc.h"
37 #include <ptlib/svcproc.h>
38
39 #ifdef P_VXWORKS
40 #include <logLib.h>
41 #define LOG_EMERG 0
42 #define LOG_ALERT 1
43 #define LOG_CRIT 2
44 #define LOG_ERR 3
45 #define LOG_WARNING 4
46 #define LOG_NOTICE 5
47 #define LOG_INFO 6
48 #define LOG_DEBUG 7
49 #else
50 #include <syslog.h>
51 #include <pwd.h>
52 #include <grp.h>
53 #endif
54
55 #include <stdarg.h>
56 #if (__GNUC__ >= 3)
57 #include <fstream>
58 #else
59 #include <fstream.h>
60 #endif
61 #include <signal.h>
62
63 #include "uerror.h"
64
65 #ifdef P_LINUX
66 #include <sys/resource.h>
67 #endif
68
69 #define new PNEW
70
71 extern void PXSignalHandler(int);
72
73 #define MAX_LOG_LINE_LEN 1024
74
75 static const char * const PLevelName[PSystemLog::NumLogLevels+1] = {
76 "Message",
77 "Fatal error",
78 "Error",
79 "Warning",
80 "Info",
81 "Debug",
82 "Debug2",
83 "Debug3",
84 "Debug4",
85 "Debug5",
86 "Debug6",
87 };
88
PServiceProcess(const char * manuf,const char * name,WORD majorVersion,WORD minorVersion,CodeStatus status,WORD buildNumber)89 PServiceProcess::PServiceProcess(const char * manuf,
90 const char * name,
91 WORD majorVersion,
92 WORD minorVersion,
93 CodeStatus status,
94 WORD buildNumber)
95 : PProcess(manuf, name, majorVersion, minorVersion, status, buildNumber)
96 {
97 isTerminating = PFalse;
98 }
99
100
~PServiceProcess()101 PServiceProcess::~PServiceProcess()
102 {
103 PSetErrorStream(NULL);
104 #if PTRACING
105 PTrace::SetStream(NULL);
106 PTrace::ClearOptions(PTrace::SystemLogStream);
107 #endif
108
109 if (!pidFileToRemove)
110 PFile::Remove(pidFileToRemove);
111 }
112
113
Current()114 PServiceProcess & PServiceProcess::Current()
115 {
116 PProcess & process = PProcess::Current();
117 PAssert(PIsDescendant(&process, PServiceProcess), "Not a service process!");
118 return (PServiceProcess &)process;
119 }
120
121
122 #ifndef P_VXWORKS
KillProcess(int pid,int sig)123 static int KillProcess(int pid, int sig)
124 {
125 if (kill(pid, sig) != 0)
126 return -1;
127
128 cout << "Sent SIG";
129 if (sig == SIGTERM)
130 cout << "TERM";
131 else
132 cout << "KILL";
133 cout << " to daemon at pid " << pid << ' ' << flush;
134
135 for (PINDEX retry = 1; retry <= 10; retry++) {
136 PThread::Sleep(1000);
137 if (kill(pid, 0) != 0) {
138 cout << "\nDaemon stopped." << endl;
139 return 0;
140 }
141 cout << '.' << flush;
142 }
143 cout << "\nDaemon has not stopped." << endl;
144
145 return 1;
146 }
147 #endif // !P_VXWORKS
148
149
_PXShowSystemWarning(PINDEX code,const PString & str)150 void PServiceProcess::_PXShowSystemWarning(PINDEX code, const PString & str)
151 {
152 PSYSTEMLOG(Warning, "PWLib\t" << GetOSClass() << " error #" << code << '-' << str);
153 }
154
155
InitialiseService()156 int PServiceProcess::InitialiseService()
157 {
158 #ifndef P_VXWORKS
159 #if PMEMORY_CHECK
160 PMemoryHeap::SetIgnoreAllocations(PTrue);
161 #endif
162 PSetErrorStream(new PSystemLog(PSystemLog::StdError));
163 #if PTRACING
164 PTrace::SetStream(new PSystemLog(PSystemLog::Debug3));
165 PTrace::ClearOptions(PTrace::FileAndLine);
166 PTrace::SetOptions(PTrace::SystemLogStream);
167 PTrace::SetLevel(4);
168 #endif
169 #if PMEMORY_CHECK
170 PMemoryHeap::SetIgnoreAllocations(PFalse);
171 #endif
172 debugMode = PFalse;
173
174 // parse arguments so we can grab what we want
175 PArgList & args = GetArguments();
176
177 args.Parse("v-version."
178 "d-daemon."
179 "c-console."
180 "h-help."
181 "x-execute."
182 "p-pid-file:"
183 "H-handlemax:"
184 "i-ini-file:"
185 "k-kill."
186 "t-terminate."
187 "s-status."
188 "l-log-file:"
189 "u-uid:"
190 "g-gid:"
191 "C-core-size:"
192 , false);
193
194 // if only displaying version information, do it and finish
195 if (args.HasOption('v')) {
196 cout << "Product Name: " << productName << endl
197 << "Manufacturer: " << manufacturer << endl
198 << "Version : " << GetVersion(PTrue) << endl
199 << "System : " << GetOSName() << '-'
200 << GetOSHardware() << ' '
201 << GetOSVersion() << endl;
202 return 0;
203 }
204
205 PString pidfilename;
206 if (args.HasOption('p'))
207 pidfilename = args.GetOptionString('p');
208 #ifdef _PATH_VARRUN
209 else
210 pidfilename = _PATH_VARRUN;
211 #endif
212
213 if (!pidfilename && PDirectory::Exists(pidfilename))
214 pidfilename = PDirectory(pidfilename) + PProcess::Current().GetFile().GetFileName() + ".pid";
215
216 if (args.HasOption('k') || args.HasOption('t') || args.HasOption('s')) {
217 pid_t pid;
218
219 {
220 ifstream pidfile((const char*)pidfilename);
221 if (!pidfile.is_open()) {
222 cout << "Could not open pid file: \"" << pidfilename << "\""
223 " - " << strerror(errno) << endl;
224 return 1;
225 }
226
227 pidfile >> pid;
228 if (pid == 0) {
229 cout << "Illegal format pid file \"" << pidfilename << '"' << endl;
230 return 1;
231 }
232 }
233
234 if (args.HasOption('s')) {
235 cout << "Process at " << pid << ' ';
236 if (kill(pid, 0) == 0)
237 cout << "is running.";
238 else if (errno == ESRCH)
239 cout << "does not exist.";
240 else
241 cout << " status could not be determined, error: " << strerror(errno);
242 cout << endl;
243 return 0;
244 }
245
246 int sig = args.HasOption('t') ? SIGTERM : SIGKILL;
247 switch (KillProcess(pid, sig)) {
248 case -1 :
249 break;
250 case 0 :
251 PFile::Remove(pidfilename);
252 return 0;
253 case 1 :
254 if (args.HasOption('t') && args.HasOption('k')) {
255 switch (KillProcess(pid, SIGKILL)) {
256 case -1 :
257 break;
258 case 0 :
259 PFile::Remove(pidfilename);
260 return 0;
261 case 1 :
262 return 2;
263 }
264 }
265 else
266 return 2;
267 }
268
269 cout << "Could not stop process " << pid <<
270 " - " << strerror(errno) << endl;
271 return 1;
272 }
273
274 // Set the gid we are running under
275 if (args.HasOption('g')) {
276 PString gidstr = args.GetOptionString('g');
277 if (!SetGroupName(gidstr)) {
278 cout << "Could not set GID to \"" << gidstr << "\" - " << strerror(errno) << endl;
279 return 1;
280 }
281 }
282
283 // Set the uid we are running under
284 if (args.HasOption('u')) {
285 PString uidstr = args.GetOptionString('u');
286 if (!SetUserName(uidstr)) {
287 cout << "Could not set UID to \"" << uidstr << "\" - " << strerror(errno) << endl;
288 return 1;
289 }
290 }
291
292 PBoolean helpAndExit = PFalse;
293
294 // if displaying help, then do it
295 if (args.HasOption('h'))
296 helpAndExit = PTrue;
297 else if (!args.HasOption('d') && !args.HasOption('x')) {
298 cout << "error: must specify one of -v, -h, -t, -k, -d or -x" << endl;
299 helpAndExit = PTrue;
300 }
301
302 // set flag for console messages
303 if (args.HasOption('c')) {
304 PSystemLog::SetTarget(new PSystemLogToStderr());
305 debugMode = PTrue;
306 }
307
308 if (args.HasOption('l')) {
309 PFilePath fileName = args.GetOptionString('l');
310 if (fileName.IsEmpty()) {
311 cout << "error: must specify file name for -l" << endl;
312 helpAndExit = PTrue;
313 }
314 else if (PDirectory::Exists(fileName))
315 fileName = PDirectory(fileName) + PProcess::Current().GetFile().GetFileName() + ".log";
316 PSystemLog::SetTarget(new PSystemLogToFile(fileName));
317 }
318
319 if (helpAndExit) {
320 cout << "usage: [-c] -v|-d|-h|-x\n"
321 " -h --help output this help message and exit\n"
322 " -v --version display version information and exit\n"
323 #if !defined(BE_THREADS) && !defined(P_RTEMS)
324 " -d --daemon run as a daemon\n"
325 #endif
326 " -u --uid uid set user id to run as\n"
327 " -g --gid gid set group id to run as\n"
328 " -p --pid-file name or directory for pid file\n"
329 " -t --terminate orderly terminate process in pid file\n"
330 " -k --kill preemptively kill process in pid file\n"
331 " -s --status check to see if daemon is running\n"
332 " -c --console output messages to stdout rather than syslog\n"
333 " -l --log-file file output messages to file or directory instead of syslog\n"
334 " -x --execute execute as a normal program\n"
335 " -i --ini-file set the ini file to use, may be explicit file or\n"
336 " a ':' separated set of directories to search.\n"
337 " -H --handlemax n set maximum number of file handles (set before uid/gid)\n"
338 #ifdef P_LINUX
339 " -C --core-size set the maximum core file size\n"
340 #endif
341 << endl;
342 return 0;
343 }
344
345 // open the system logger for this program
346 PSYSTEMLOG(StdError, "Starting service process \"" << GetName() << "\" v" << GetVersion(PTrue));
347
348 if (args.HasOption('i'))
349 SetConfigurationPath(PFilePath(args.GetOptionString('i')));
350
351 if (args.HasOption('H')) {
352 int uid = geteuid();
353 seteuid(getuid()); // Switch back to starting uid for next call
354 SetMaxHandles(args.GetOptionString('H').AsInteger());
355 seteuid(uid);
356 }
357
358 // set the core file size
359 if (args.HasOption('C')) {
360 #ifdef P_LINUX
361 struct rlimit rlim;
362 if (getrlimit(RLIMIT_CORE, &rlim) != 0)
363 cout << "Could not get current core file size : error = " << errno << endl;
364 else {
365 int uid = geteuid();
366 seteuid(getuid()); // Switch back to starting uid for next call
367 int v = args.GetOptionString('C').AsInteger();
368 rlim.rlim_cur = v;
369 if (setrlimit(RLIMIT_CORE, &rlim) != 0)
370 cout << "Could not set current core file size to " << v << " : error = " << errno << endl;
371 else {
372 getrlimit(RLIMIT_CORE, &rlim);
373 cout << "Core file size set to " << rlim.rlim_cur << "/" << rlim.rlim_max << endl;
374 }
375 seteuid(uid);
376 }
377 #endif
378 }
379
380 #if !defined(BE_THREADS) && !defined(P_RTEMS)
381 if (!args.HasOption('d'))
382 return -1;
383
384 // Run as a daemon, ie fork
385
386 if (!pidfilename) {
387 ifstream pidfile((const char*)pidfilename);
388 if (pidfile.is_open()) {
389 pid_t pid;
390 pidfile >> pid;
391 if (pid != 0 && kill(pid, 0) == 0) {
392 cout << "Already have daemon running with pid " << pid << endl;
393 return 2;
394 }
395 }
396 }
397
398 // Need to get rid of the config write thread before fork, as on
399 // pthreads platforms the forked process does not have the thread
400 CommonDestruct();
401
402 pid_t pid = fork();
403 switch (pid) {
404 case 0 : // The forked process
405 break;
406
407 case -1 : // Failed
408 cout << "Fork failed creating daemon process." << endl;
409 return 1;
410
411 default : // Parent process
412 cout << "Daemon started with pid " << pid << endl;
413 if (!pidfilename) {
414 // Write out the child pid to magic file in /var/run (at least for linux)
415 ofstream pidfile((const char*)pidfilename);
416 if (pidfile.is_open())
417 pidfile << pid;
418 else
419 cout << "Could not write pid to file \"" << pidfilename << "\""
420 " - " << strerror(errno) << endl;
421 }
422 return 0;
423 }
424
425 // Set ourselves as out own process group so we don't get signals
426 // from our parent's terminal (hopefully!)
427 PSETPGRP();
428
429 CommonConstruct();
430
431 pidFileToRemove = pidfilename;
432
433 // Only if we are running in the background as a daemon, we intercept
434 // the core dumping signals so get a message in the log file.
435 signal(SIGSEGV, PXSignalHandler);
436 signal(SIGFPE, PXSignalHandler);
437 signal(SIGBUS, PXSignalHandler);
438
439 // Also if in background, don't want to get blocked on input from stdin
440 ::close(STDIN_FILENO);
441
442 #endif // !BE_THREADS && !P_RTEMS
443 #endif // !P_VXWORKS
444 return -1;
445 }
446
InternalMain(void *)447 int PServiceProcess::InternalMain(void *)
448 {
449 if ((terminationValue = InitialiseService()) < 0) {
450 // Make sure housekeeping thread is going so signals are handled.
451 SignalTimerChange();
452
453 terminationValue = 1;
454 if (OnStart()) {
455 terminationValue = 0;
456 Main();
457 Terminate();
458 }
459 }
460
461 return terminationValue;
462 }
463
464
OnPause()465 PBoolean PServiceProcess::OnPause()
466 {
467 return PTrue;
468 }
469
OnContinue()470 void PServiceProcess::OnContinue()
471 {
472 }
473
OnStop()474 void PServiceProcess::OnStop()
475 {
476 }
477
478
Terminate()479 void PServiceProcess::Terminate()
480 {
481 if (isTerminating) {
482 // If we are the process itself and another thread is terminating us,
483 // just stop and wait forever for us to go away
484 if (PThread::Current() == this)
485 Sleep(PMaxTimeInterval);
486 PSYSTEMLOG(Error, "Nested call to process termination!");
487 return;
488 }
489
490 isTerminating = PTrue;
491
492 PSYSTEMLOG(Warning, "Stopping service process \"" << GetName() << "\" v" << GetVersion(PTrue));
493
494 // Avoid strange errors caused by threads (and the process itself!) being destoyed
495 // before they have EVER been scheduled
496 Yield();
497
498 // Do the services stop code
499 OnStop();
500
501 PSystemLog::SetTarget(NULL);
502
503 // Now end the program
504 _exit(terminationValue);
505 }
506
PXOnAsyncSignal(int sig)507 void PServiceProcess::PXOnAsyncSignal(int sig)
508 {
509 const char * sigmsg;
510
511 // Override the default behavious for these signals as that just
512 // summarily exits the program. Allow PXOnSignal() to do orderly exit.
513
514 switch (sig) {
515 case SIGINT :
516 case SIGTERM :
517 case SIGHUP :
518 return;
519
520 case SIGSEGV :
521 sigmsg = "segmentation fault (SIGSEGV)";
522 break;
523
524 case SIGFPE :
525 sigmsg = "floating point exception (SIGFPE)";
526 break;
527
528 #ifndef __BEOS__ // In BeOS, SIGBUS is the same value as SIGSEGV
529 case SIGBUS :
530 sigmsg = "bus error (SIGBUS)";
531 break;
532 #endif
533 default :
534 PProcess::PXOnAsyncSignal(sig);
535 return;
536 }
537
538 signal(SIGSEGV, SIG_DFL);
539 signal(SIGFPE, SIG_DFL);
540 signal(SIGBUS, SIG_DFL);
541
542 static PBoolean inHandler = PFalse;
543 if (inHandler) {
544 raise(SIGQUIT); // Dump core
545 _exit(-1); // Fail safe if raise() didn't dump core and exit
546 }
547
548 inHandler = PTrue;
549
550 PThreadIdentifier tid = GetCurrentThreadId();
551 ThreadMap::iterator thread = m_activeThreads.find(tid);
552
553 char msg[200];
554 sprintf(msg, "\nCaught %s, thread_id=" PTHREAD_ID_FMT, sigmsg, tid);
555
556 if (thread != m_activeThreads.end()) {
557 PString thread_name = thread->second->GetThreadName();
558 if (thread_name.IsEmpty())
559 sprintf(&msg[strlen(msg)], " obj_ptr=%p", thread->second);
560 else {
561 strcat(msg, " name=");
562 strcat(msg, thread_name);
563 }
564 }
565
566 strcat(msg, ", aborting.\n");
567
568 PSYSTEMLOG(Fatal, msg);
569
570 raise(SIGQUIT); // Dump core
571 _exit(-1); // Fail safe if raise() didn't dump core and exit
572 }
573
574
PXOnSignal(int sig)575 void PServiceProcess::PXOnSignal(int sig)
576 {
577 PProcess::PXOnSignal(sig);
578 switch (sig) {
579 case SIGINT :
580 case SIGTERM :
581 Terminate();
582 break;
583
584 case SIGUSR1 :
585 OnPause();
586 break;
587
588 case SIGUSR2 :
589 OnContinue();
590 break;
591
592 #if 0
593 case SIGHUP :
594 if (currentLogLevel < PSystemLog::NumLogLevels-1) {
595 currentLogLevel = (PSystemLog::Level)(currentLogLevel+1);
596 PSystemLog s(PSystemLog::StdError);
597 s << "Log level increased to " << PLevelName[currentLogLevel+1];
598 }
599 break;
600
601 #ifdef SIGWINCH
602 case SIGWINCH :
603 if (currentLogLevel > PSystemLog::Fatal) {
604 currentLogLevel = (PSystemLog::Level)(currentLogLevel-1);
605 PSystemLog s(PSystemLog::StdError);
606 s << "Log level decreased to " << PLevelName[currentLogLevel+1];
607 }
608 break;
609 #endif
610 #endif
611 }
612 }
613
614