Socket: Add a 'facet<>()' member to access protocol-facets from generic socket handles
[senf.git] / Utils / Daemon / Daemon.cc
1 // $Id$
2 //
3 // Copyright (C) 2007
4 // Fraunhofer Institute for Open Communication Systems (FOKUS)
5 // Competence Center NETwork research (NET), St. Augustin, GERMANY
6 //     Stefan Bund <g0dil@berlios.de>
7 //
8 // This program is free software; you can redistribute it and/or modify
9 // it under the terms of the GNU General Public License as published by
10 // the Free Software Foundation; either version 2 of the License, or
11 // (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 // GNU General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the
20 // Free Software Foundation, Inc.,
21 // 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22
23 /** \file
24     \brief Daemon non-inline non-template implementation */
25
26 #include "Daemon.hh"
27 #include "Daemon.ih"
28
29 // Custom includes
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <sys/wait.h>
33 #include <unistd.h>
34 #include <fcntl.h>
35 #include <errno.h>
36 #include <signal.h>
37 #include <execinfo.h>
38 #include <sstream>
39 #include <algorithm>
40 #include <boost/algorithm/string/predicate.hpp>
41 #include <boost/algorithm/string/trim.hpp>
42 #include <boost/format.hpp>
43 #include "../Exception.hh"
44 #include "../membind.hh"
45 #include "../Backtrace.hh"
46
47 // #define __USE_GNU
48 #include <ucontext.h>
49
50 //#include "Daemon.mpp"
51 #define prefix_
52 ///////////////////////////////cc.p////////////////////////////////////////
53
54 #define LIBC_CALL(fn, args) if (fn args < 0) \
55     SENF_THROW_SYSTEM_EXCEPTION(#fn "()")
56
57 #define LIBC_CALL_RV(var, fn, args) \
58     int var (fn args); if (var < 0) SENF_THROW_SYSTEM_EXCEPTION(#fn "()")
59
60 ///////////////////////////////////////////////////////////////////////////
61 // senf::Daemon
62
63 prefix_ senf::Daemon::~Daemon()
64 {
65     if (pidfileCreated_) {
66         try {
67             LIBC_CALL( ::unlink, (pidfile_.c_str()) );
68         } catch (Exception e) {
69             // e << "; could not unlink " << pidfile_.c_str();
70             // throw;
71         }
72     }
73 }
74
75 prefix_ void senf::Daemon::daemonize(bool v)
76 {
77     daemonize_ = v;
78 }
79
80 prefix_ bool senf::Daemon::daemon()
81 {
82     return daemonize_;
83 }
84
85 prefix_ int senf::Daemon::argc() 
86 {
87     return argc_;
88 }
89
90 prefix_ char ** senf::Daemon::argv() 
91 {
92     return argv_;
93 }
94
95 prefix_ void senf::Daemon::consoleLog(std::string const & path, StdStream which)
96 {
97     switch (which) {
98     case StdOut : stdoutLog_ = path; break;
99     case StdErr : stderrLog_ = path; break;
100     case Both : stdoutLog_ = path; stderrLog_ = path; break;
101     }
102 }
103
104
105 prefix_ void senf::Daemon::openLog()
106 {
107     int fd (-1);
108     if (! stdoutLog_.empty()) {
109         fd = ::open(stdoutLog_.c_str(), O_WRONLY | O_APPEND | O_CREAT, 0666);
110         if (fd < 0)
111             SENF_THROW_SYSTEM_EXCEPTION("")
112                   << " Could not open \"" << stdoutLog_ << "\" for redirecting stdout.";
113         stdout_ = fd;
114     }
115     if (stderrLog_ == stdoutLog_)
116         stderr_ = fd;
117     else if (! stderrLog_.empty()) {
118         fd = ::open(stdoutLog_.c_str(), O_WRONLY | O_APPEND | O_CREAT, 0666);
119         if (fd < 0)
120             SENF_THROW_SYSTEM_EXCEPTION("")
121                   << " Could not open \"" << stderrLog_ << "\" for redirecting stderr.";
122         stderr_ = fd;
123     }
124 }
125
126 prefix_ void senf::Daemon::pidFile(std::string const & f)
127 {
128     pidfile_ = f;
129 }
130
131 namespace {
132     bool signaled (false);
133     void waitusr(int) {
134         signaled = true;
135     }
136 }
137
138 prefix_ void senf::Daemon::detach()
139 {
140     if (daemonize_ && ! detached_) {
141         // Wow .. ouch .. 
142         // To ensure all data is written to the console log file in the correct order, we suspend
143         // execution here until the parent process tells us to continue via SIGUSR1: We block
144         // SIGUSR1 and install our own signal handler saving the old handler and signal mask. Then
145         // we close stdin/stderr which will send a HUP condition to the parent process. We wait for
146         // SIGUSR1 and reinstall the old signal mask and action.
147         ::sigset_t oldsig;
148         ::sigset_t usrsig;
149         ::sigemptyset(&usrsig);
150         LIBC_CALL( ::sigaddset, (&usrsig, SIGUSR1) );
151         LIBC_CALL( ::sigprocmask, (SIG_BLOCK, &usrsig, &oldsig) );
152         struct ::sigaction oldact;
153         struct ::sigaction usract;
154         ::memset(&usract, 0, sizeof(usract));
155         usract.sa_handler = &waitusr;
156         LIBC_CALL( ::sigaction, (SIGUSR1, &usract, &oldact) );
157         ::sigset_t waitsig (oldsig);
158         LIBC_CALL( ::sigdelset, (&waitsig, SIGUSR1) );
159
160         LIBC_CALL_RV( nul, ::open, ("/dev/null", O_WRONLY) );
161         LIBC_CALL( ::dup2, (stdout_ == -1 ? nul : stdout_, 1) );
162         LIBC_CALL( ::dup2, (stderr_ == -1 ? nul : stderr_, 2) );
163         LIBC_CALL( ::close, (nul) );
164
165         signaled = false;
166         while (! signaled) {
167             ::sigsuspend(&waitsig);
168             if (errno != EINTR)
169                 SENF_THROW_SYSTEM_EXCEPTION("::sigsuspend()");
170         }
171
172         LIBC_CALL( ::sigaction, (SIGUSR1, &oldact, 0) );
173         LIBC_CALL( ::sigprocmask, (SIG_SETMASK, &oldsig, 0) );
174
175         detached_ = true;
176     }
177 }
178
179 namespace {
180     /* Purposely *not* derived from std::exception */
181     struct DaemonExitException {
182         DaemonExitException(unsigned c) : code(c) {}
183         unsigned code;
184     };
185 }
186
187 prefix_ void senf::Daemon::exit(unsigned code)
188 {
189     throw DaemonExitException(code);
190 }
191
192 prefix_ int senf::Daemon::start(int argc, char ** argv)
193 {
194     argc_ = argc;
195     argv_ = argv;
196
197     try {
198         configure();
199
200         if (daemonize_) {
201             openLog();
202             fork();
203         }
204         installSighandlers();
205         if (! pidfile_.empty()) {
206             if (pidfileCreate())
207                 pidfileCreated_ = true;
208             else {
209                 std::cerr << "PID file '" << pidfile_ 
210                           << "' creation failed. Daemon running ?" << std::endl;
211                 return 1;
212             }
213         }
214
215         main();
216     }
217     catch (DaemonExitException & e) {
218         return e.code;
219     }
220
221 #ifndef SENF_DEBUG
222
223     catch (std::exception & e) {
224         std::cerr << "\n*** Fatal exception: " << e.what() << "\n" << std::endl;
225         return 1;
226     }
227     catch (...) {
228         std::cerr << "\n*** Fatal exception: (unknown)" << "\n" << std::endl;
229         return 1;
230     }
231
232 #   endif
233
234     return 0;
235 }
236
237 ////////////////////////////////////////
238 // protected members
239
240 prefix_ senf::Daemon::Daemon()
241     : argc_(0), argv_(0), daemonize_(true), stdout_(-1), stderr_(-1), pidfile_(""),
242       pidfileCreated_(false), detached_(false)
243 {}
244
245 ////////////////////////////////////////
246 // private members
247
248 prefix_ void senf::Daemon::configure()
249 {
250     for (int i (1); i<argc_; ++i) {
251         if (argv_[i] == std::string("--no-daemon"))
252             daemonize(false);
253         else if (boost::starts_with(argv_[i], std::string("--console-log="))) {
254             std::string arg (std::string(argv_[i]), 14u);
255             std::string::size_type komma (arg.find(','));
256             if (komma == std::string::npos) {
257                 boost::trim(arg);
258                 if (arg == std::string("none")) consoleLog("");
259                 else if (!arg.empty())          consoleLog(arg);
260             } else {
261                 std::string arg1 (arg,0,komma);
262                 std::string arg2 (arg,komma+1);
263                 boost::trim(arg1);
264                 boost::trim(arg2);
265                 if (arg1 == std::string("none")) consoleLog("",StdOut);
266                 else if (! arg1.empty() )        consoleLog(arg1, StdOut);
267                 if (arg2 == std::string("none")) consoleLog("",StdErr);
268                 else if (! arg2.empty() )        consoleLog(arg2, StdErr);
269             }
270         }
271         else if (boost::starts_with(argv_[i], std::string("--pid-file="))) 
272             pidFile(std::string(std::string(argv_[i]), 11u));
273     }
274 }
275
276 prefix_ void senf::Daemon::main()
277 {
278     init();
279     detach();
280     run();
281 }
282
283 prefix_ void senf::Daemon::init()
284 {}
285
286 prefix_ void senf::Daemon::run()
287 {}
288
289 prefix_ void senf::Daemon::fork()
290 {
291     int coutpipe[2];
292     int cerrpipe[2];
293
294     LIBC_CALL_RV( nul, ::open, ("/dev/null", O_RDONLY) );
295     LIBC_CALL( ::dup2, (nul, 0) );
296     LIBC_CALL( ::close, (nul) );
297     LIBC_CALL( ::pipe, (coutpipe) );
298     LIBC_CALL( ::pipe, (cerrpipe) );
299
300     // We need to block the SIGCHLD signal here so we don't miss it, if the child
301     // dies immediately
302     ::sigset_t oldsig;
303     ::sigset_t cldsig;
304     ::sigemptyset(&cldsig);
305     LIBC_CALL( ::sigaddset, (&cldsig, SIGCHLD) );
306     LIBC_CALL( ::sigprocmask, (SIG_BLOCK, &cldsig, &oldsig) );
307     
308     LIBC_CALL_RV( pid, ::fork, () );
309
310     if (pid == 0) {
311         // Daemon process
312
313         LIBC_CALL( ::dup2, (coutpipe[1],1) );
314         LIBC_CALL( ::dup2, (cerrpipe[1],2) );
315         LIBC_CALL( ::close, (coutpipe[0]) );
316         LIBC_CALL( ::close, (coutpipe[1]) );
317         LIBC_CALL( ::close, (cerrpipe[0]) );
318         LIBC_CALL( ::close, (cerrpipe[1]) );
319         LIBC_CALL( ::setsid, () );
320         LIBC_CALL( ::sigprocmask, (SIG_SETMASK, &oldsig, 0) );
321         return;
322     }
323
324     // Ouch ... ensure, the daemon watcher does not remove the pidfile ...
325     pidfile_ = "";
326     
327     LIBC_CALL( ::close, (coutpipe[1]) );
328     LIBC_CALL( ::close, (cerrpipe[1]) );
329
330     detail::DaemonWatcher watcher (pid, coutpipe[0], cerrpipe[0], stdout_, stderr_);
331     watcher.run();
332
333     ::_exit(0);
334 }
335
336 prefix_ bool senf::Daemon::pidfileCreate()
337 {
338     // Create temporary file pidfile_.hostname.pid and hard-link it to pidfile_ If the hardlink
339     // fails, the pidfile exists. If the link count of the temporary file is not 2 after this, there
340     // was some race condition, probably over NFS.
341
342     std::string tempname;
343     boost::format linkErrorFormat(" Could not link \"%1%\" to \"%2%\".");
344
345     {
346         char hostname[HOST_NAME_MAX+1];
347         LIBC_CALL( ::gethostname, (hostname, HOST_NAME_MAX+1) );
348         hostname[HOST_NAME_MAX] = 0;
349         std::stringstream tempname_s;
350         tempname_s << pidfile_ << "." << hostname << "." << ::getpid();
351         tempname = tempname_s.str();
352     }
353
354     while (1) {
355         {
356             std::ofstream pidf (tempname.c_str());
357             if (! pidf)
358                 SENF_THROW_SYSTEM_EXCEPTION("")
359                       << " Could not open pidfile \"" << tempname << "\" for output.";
360             pidf << ::getpid() << std::endl;
361             if (! pidf)
362                 SENF_THROW_SYSTEM_EXCEPTION("")
363                       << " Could not write to pidfile \"" << tempname << "\".";
364         }
365
366         if (::link(tempname.c_str(), pidfile_.c_str()) < 0) {
367             if (errno != EEXIST) 
368                 SENF_THROW_SYSTEM_EXCEPTION("") << linkErrorFormat % pidfile_ % tempname;
369         }
370         else {
371             struct ::stat s;
372             LIBC_CALL( ::stat, (tempname.c_str(), &s) );
373             LIBC_CALL( ::unlink, (tempname.c_str()) );
374             return s.st_nlink == 2;
375         }
376
377         // pidfile exists. Check, whether the pid in the pidfile still exists.
378         {
379             int old_pid (-1);
380             std::ifstream pidf (pidfile_.c_str());
381             if ( ! (pidf >> old_pid)
382                  || old_pid < 0 
383                  || ::kill(old_pid, 0) >= 0 
384                  || errno == EPERM ) {
385                 LIBC_CALL( ::unlink, (tempname.c_str()) );
386                 return false;
387             }
388         }
389
390         // If we reach this point, the pid file exists but the process mentioned within the
391         // pid file does *not* exists. We assume, the pid file to be stale.
392
393         // I hope, the following procedure is without race condition: We remove our generated
394         // temporary pid file and recreate it as hard-link to the old pid file. Now we check, that
395         // the hard-link count of this file is 2. If it is not, we terminate, since someone else
396         // must have already created his hardlink. We then truncate the file and write our pid.
397
398         LIBC_CALL( ::unlink, (tempname.c_str() ));
399         if (::link(pidfile_.c_str(), tempname.c_str()) < 0) {
400             if (errno != ENOENT)
401                 SENF_THROW_SYSTEM_EXCEPTION("") << linkErrorFormat % tempname % pidfile_;
402             // Hmm ... the pidfile mysteriously disappeared ... try again.
403             continue;
404         }
405
406         {
407             struct ::stat s;
408             LIBC_CALL( ::stat, (tempname.c_str(), &s) );
409             if (s.st_nlink != 2) {
410                 LIBC_CALL( ::unlink, (tempname.c_str()) );
411                 return false;
412             }
413         }
414         
415         {
416             std::ofstream pidf (tempname.c_str());
417             pidf << ::getpid() << std::endl;
418         }
419
420         LIBC_CALL( ::unlink, (tempname.c_str()) );
421         break;
422     }
423     return true;
424 }
425
426
427 #ifdef SENF_DEBUG
428
429 namespace {
430     void fatalSignalsHandler(int sig, ::siginfo_t * info, void * arg)
431     {
432         static char const * const signames[] = {
433             "", 
434             "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE", 
435             "SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM", "SIGTERM", 
436             "SIGSTKFLT", "SIGCHLD", "SIGCONT", "SIGSTOP", "SIGTSTP", "SIGTTIN", "SIGTTOU", 
437             "SIGURG", "SIGXCPU", "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGIO", 
438             "SIGPWR", "SIGSYS" };
439
440         // ::ucontext_t * ucontext = static_cast<ucontext_t*>(arg);
441         std::cerr << "\n" << "Signal " << sig;
442         if (unsigned(sig) < sizeof(signames) / sizeof(signames[0]))
443             std::cerr << " (" << signames[unsigned(sig)] << ")";
444         std::cerr << " received\n";
445
446         if (sig == SIGSEGV)
447             std::cerr << "Invalid memory access at " << info->si_addr << "\n";
448
449         static void * entries[SENF_DEBUG_BACKTRACE_NUMCALLERS];
450         unsigned nEntries( ::backtrace(entries, SENF_DEBUG_BACKTRACE_NUMCALLERS) );
451
452         // Hack the callers address into the backtrace
453         // entries[1] = reinterpret_cast<void *>(ucontext->uc_mcontext.gregs[REG_EIP]);
454
455         std::cerr << "Backtrace:\n";
456         senf::formatBacktrace(std::cerr, entries, nEntries);
457         std::cerr << "-- \n";
458
459         if (sig != SIGUSR2) {
460             ::signal(sig, SIG_DFL);
461             ::kill(::getpid(), sig);
462         }
463     }
464
465 }
466
467 #endif
468
469 prefix_ void senf::Daemon::installSighandlers()
470 {
471 #ifdef SENF_DEBUG
472     struct ::sigaction sa;
473     sa.sa_sigaction = &fatalSignalsHandler;
474     ::sigemptyset(&sa.sa_mask);
475     sa.sa_flags = SA_RESTART | SA_SIGINFO;
476
477     ::sigaction(SIGILL,    &sa, NULL);
478     ::sigaction(SIGTRAP,   &sa, NULL);
479     ::sigaction(SIGABRT,   &sa, NULL);
480     ::sigaction(SIGFPE,    &sa, NULL);
481     ::sigaction(SIGBUS,    &sa, NULL);
482     ::sigaction(SIGSEGV,   &sa, NULL);
483     ::sigaction(SIGSTKFLT, &sa, NULL);
484     ::sigaction(SIGSYS,    &sa, NULL);
485     ::sigaction(SIGUSR2,   &sa, NULL);
486 #endif
487 }
488
489 ///////////////////////////////////////////////////////////////////////////
490 // senf::detail::DaemonWatcher
491
492 prefix_ senf::detail::DaemonWatcher::DaemonWatcher(int pid, int coutpipe, int cerrpipe,
493                                                    int stdout, int stderr)
494     : childPid_(pid), coutpipe_(coutpipe), cerrpipe_(cerrpipe), stdout_(stdout),
495       stderr_(stderr), sigChld_(false),
496       coutForwarder_(coutpipe_, boost::bind(&DaemonWatcher::pipeClosed, this, 1)), 
497       cerrForwarder_(cerrpipe_, boost::bind(&DaemonWatcher::pipeClosed, this, 2)) 
498 {
499     coutForwarder_.addTarget(1);
500     if (stdout_ >= 0)
501         coutForwarder_.addTarget(stdout_);
502     cerrForwarder_.addTarget(2);
503     if (stderr_ >= 0)
504         cerrForwarder_.addTarget(stderr_);
505 }
506
507 prefix_ void senf::detail::DaemonWatcher::run()
508 {
509     Scheduler::instance().registerSignal(SIGCHLD, senf::membind(&DaemonWatcher::sigChld, this));
510     Scheduler::instance().process();
511 }
512
513 ////////////////////////////////////////
514 // private members
515
516 prefix_ void senf::detail::DaemonWatcher::pipeClosed(int id)
517 {
518     switch (id) {
519     case 1 : coutpipe_ = -1; break;
520     case 2 : cerrpipe_ = -1; break;
521     }
522
523     if (coutpipe_ == -1 && cerrpipe_ == -1) {
524         if (sigChld_)
525             childDied(); // does not return
526         if (::kill(childPid_, SIGUSR1) < 0)
527             if (errno != ESRCH) SENF_THROW_SYSTEM_EXCEPTION("::kill()");
528         Scheduler::instance().timeout(
529             Scheduler::instance().eventTime() + ClockService::seconds(1),
530             senf::membind(&DaemonWatcher::childOk, this));
531     }
532 }
533
534 prefix_ void senf::detail::DaemonWatcher::sigChld()
535 {
536     sigChld_ = true;
537     if (coutpipe_ == -1 && cerrpipe_ == -1)
538         childDied(); // does not return
539 }
540
541 prefix_ void senf::detail::DaemonWatcher::childDied()
542 {
543     int status (0);
544     if (::waitpid(childPid_,&status,0) < 0) SENF_THROW_SYSTEM_EXCEPTION("::waitpid()");
545     if (WIFSIGNALED(status)) {
546         ::signal(WTERMSIG(status),SIG_DFL);
547         ::kill(::getpid(), WTERMSIG(status));
548         // should not be reached
549         ::_exit(1);
550     }
551     if (WEXITSTATUS(status) == 0)
552         ::_exit(1);
553     ::_exit(WEXITSTATUS(status));
554 }
555
556 prefix_ void senf::detail::DaemonWatcher::childOk()
557 {
558     Scheduler::instance().terminate();
559 }
560
561 ///////////////////////////////////////////////////////////////////////////
562 // senf::detail::DaemonWatcher::Forwarder
563
564 prefix_ senf::detail::DaemonWatcher::Forwarder::Forwarder(int src, Callback cb)
565     : src_(src), cb_(cb)
566 {
567     Scheduler::instance().add(src_, senf::membind(&Forwarder::readData, this),
568                               Scheduler::EV_READ);
569 }
570
571 prefix_ senf::detail::DaemonWatcher::Forwarder::~Forwarder()
572 {
573     if (src_ != -1)
574         Scheduler::instance().remove(src_);
575     
576     for (Targets::iterator i (targets_.begin()); i != targets_.end(); ++i)
577         if (i->offset >= buffer_.size())
578             Scheduler::instance().remove(i->fd);
579 }
580
581 prefix_ void senf::detail::DaemonWatcher::Forwarder::addTarget(int fd)
582 {
583     Target target = { fd, 0 };
584     targets_.push_back(target);
585 }
586
587 prefix_ void senf::detail::DaemonWatcher::Forwarder::readData(Scheduler::EventId event)
588 {
589     char buf[1024];
590     int n (0);
591
592     while (1) {
593         n = ::read(src_,buf,1024);
594         if (n<0) {
595             if (errno != EINTR) SENF_THROW_SYSTEM_EXCEPTION("::read()");
596         } else 
597             break;
598     }
599
600     if (n == 0) {
601         // Hangup
602         Scheduler::instance().remove(src_);
603         if (buffer_.empty())
604             cb_(); 
605         src_ = -1;
606         return;
607     }
608
609     if (targets_.empty())
610         return;
611
612     for (Targets::iterator i (targets_.begin()); i != targets_.end(); ++i)
613         if (i->offset >= buffer_.size())
614             Scheduler::instance().add( i->fd, 
615                                        boost::bind(&Forwarder::writeData, this, _1, i),
616                                        Scheduler::EV_WRITE );
617
618     buffer_.insert(buffer_.end(), buf, buf+n);
619 }
620
621 prefix_ void senf::detail::DaemonWatcher::Forwarder::writeData(Scheduler::EventId event,
622                                                                Targets::iterator target)
623 {    
624     if (event != Scheduler::EV_WRITE) {
625         // Broken pipe while writing data ? Not much, we can do here, we just drop the data
626         Scheduler::instance().remove(target->fd);
627         targets_.erase(target);
628         if (targets_.empty() && src_ == -1)
629             cb_();
630         return;
631     }
632
633     char buf[1024];
634     int n (buffer_.size() - target->offset > 1024 ? 1024 : buffer_.size() - target->offset);
635     std::copy(buffer_.begin() + target->offset, buffer_.begin() + target->offset + n, buf);
636
637     int w (::write(target->fd, buf, n));
638     if (w < 0) {
639         if (errno != EINTR) SENF_THROW_SYSTEM_EXCEPTION("::write()");
640         return;
641     }
642     target->offset += w;
643
644     n = std::min_element(
645         targets_.begin(), targets_.end(),
646         boost::bind(&Target::offset, _1) < boost::bind(&Target::offset, _2))->offset;
647
648     buffer_.erase(buffer_.begin(), buffer_.begin()+n);
649
650     for (Targets::iterator i (targets_.begin()); i != targets_.end(); ++i)
651         i->offset -= n;
652
653     if (target->offset >= buffer_.size())
654         Scheduler::instance().remove(target->fd);
655     if (src_ == -1 && (buffer_.empty() || targets_.empty()))
656         cb_();
657 }
658
659 #undef LIBC_CALL
660 #undef LIBC_CALL_RV
661
662 ///////////////////////////////cc.e////////////////////////////////////////
663 #undef prefix_
664 //#include "Daemon.mpp"
665
666 \f
667 // Local Variables:
668 // mode: c++
669 // fill-column: 100
670 // comment-column: 40
671 // c-file-style: "senf"
672 // indent-tabs-mode: nil
673 // ispell-local-dictionary: "american"
674 // compile-command: "scons -u test"
675 // End: