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