source: trunk/base/src/programs/daemondo/main.c @ 112510

Last change on this file since 112510 was 112510, checked in by toby@…, 6 years ago

daemondo: format fixes

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 43.5 KB
Line 
1/*  -*- mode: cc-mode; coding: utf-8; tab-width: 4; c-basic-offset: 4 -*- vim:fenc=utf-8:filetype=c:et:sw=4:ts=4:sts=4
2
3    daemondo - main.c
4   
5    Copyright (c) 2005-2007 James Berry <jberry@macports.org>
6    All rights reserved.
7
8    Redistribution and use in source and binary forms, with or without
9    modification, are permitted provided that the following conditions
10    are met:
11    1. Redistributions of source code must retain the above copyright
12       notice, this list of conditions and the following disclaimer.
13    2. Redistributions in binary form must reproduce the above copyright
14       notice, this list of conditions and the following disclaimer in the
15       documentation and/or other materials provided with the distribution.
16    3. Neither the name of The MacPorts Project nor the names of its contributors
17       may be used to endorse or promote products derived from this software
18       without specific prior written permission.
19   
20    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30    POSSIBILITY OF SUCH DAMAGE.
31
32    $Id: main.c 112510 2013-10-24 21:00:31Z toby@macports.org $
33*/
34
35/*
36    Potentially useful System Configuration regex patterns:
37
38        (backslash quoting below is only to protect the C comment)
39        State:/Network/Interface/.*\/Link
40        State:/Network/Interface/.*\/IPv4
41        State:/Network/Interface/.*\/IPv6
42       
43        State:/Network/Global/DNS
44        State:/Network/Global/IPv4
45       
46    Potentially useful notifications from Darwin Notify Center:
47   
48        com.apple.system.config.network_change
49*/
50
51#if HAVE_CONFIG_H
52#include <config.h>
53#endif
54
55#include <stdio.h>
56#include <unistd.h>
57#include <signal.h>
58#include <getopt.h>
59#include <fcntl.h>
60#include <stdarg.h>
61#include <time.h>
62#include <sys/types.h>
63#include <sys/event.h>
64#include <sys/time.h>
65#include <sys/wait.h>
66#include <mach/mach.h>
67
68#include <CoreFoundation/CoreFoundation.h>
69#include <SystemConfiguration/SystemConfiguration.h>
70
71#include <IOKit/pwr_mgt/IOPMLib.h>
72#include <IOKit/IOMessage.h>
73
74// Constants
75const CFTimeInterval kChildDeathTimeout = 20.0;
76const CFTimeInterval kChildStartPidTimeout = 30.0;
77
78typedef enum {
79    kPidStyleUnknown = 0,
80    kPidStyleNone,
81    kPidStyleExec,
82    kPidStyleFileAuto,
83    kPidStyleFileClean
84} PidStyle;
85
86// Globals
87CFStringRef         kProgramName        = NULL;
88CFStringRef         kChildWatchMode     = NULL;
89
90int                 verbosity           = 0;        // Verbosity level
91const char*         label               = NULL;
92
93const char* const*  startArgs           = NULL;     // Argvs for start-cmd, stop-cmd, and restart-cmd
94const char* const*  stopArgs            = NULL;
95const char* const*  restartArgs         = NULL;
96
97PidStyle            pidStyle            = kPidStyleUnknown;
98const char*         pidFile             = NULL;
99
100int                 terminating         = 0;        // TRUE if we're terminating
101pid_t               runningPid          = 0;        // Current running pid (0 while stopped, -1 if we don't know pid)
102
103int                 kqfd                = 0;        // Kqueue file descriptor
104
105mach_port_t         sigChild_m_port     = 0;        // Mach port to send signals through
106mach_port_t         sigGeneric_m_port   = 0;        // Mach port to send signals through
107
108CFMutableArrayRef   scRestartPatterns   = NULL;     // Array of sc patterns to restart daemon on
109CFMutableArrayRef   distNotifyNames     = NULL;     // Array of distributed notification names to restart daemon on
110CFMutableArrayRef   darwinNotifyNames   = NULL;     // Array of darwin notification names to restart daemon on
111
112io_connect_t        pwrRootPort         = 0;
113int                 restartOnWakeup     = 0;        // TRUE to restart daemon on wake from sleep
114CFRunLoopTimerRef   restartTimer        = NULL;     // Timer for scheduled restart
115CFTimeInterval      restartHysteresis   = 5.0;      // Default hysteresis is 5 seconds
116int                                 restartWait                 = 3;            // Default wait during restart is 3 seconds
117
118
119__printflike(1, 2)
120void
121LogMessage(const char* fmt, ...)
122{
123    struct tm tm;
124    time_t timestamp;
125    char datestring[32];
126   
127    // Format the date-time stamp
128    time(&timestamp);
129    strftime(datestring, sizeof(datestring), "%F %T", localtime_r(&timestamp, &tm));
130   
131    // Output the log header
132    if (label != NULL)
133        printf("%s %s: ", datestring, label);
134    else
135        printf("%s ", datestring);
136   
137    // Output the message
138    va_list ap;
139    va_start(ap, fmt);
140    vprintf(fmt, ap);
141    va_end(ap);
142}
143
144
145const char*
146CatArray(const char* const* strarray, char* buf, size_t size)
147{
148    const char* sep = " ";
149    int cnt = 0;
150    if (size == 0)
151        return NULL;
152    *buf = '\0';
153    for (cnt = 0; *strarray; ++strarray) {
154        if (cnt++ > 0)
155            strlcat(buf, sep, size);
156        strlcat(buf, *strarray, size);
157    }
158    return buf;
159}
160
161
162void
163DoVersion(void)
164{
165    printf("daemondo, version 1.1\n\n");
166}
167
168
169void
170DoHelp(void)
171{
172    DoVersion();
173   
174    const char* helpText =
175        "usage: daemondo [-hv] [--version]\n"
176        "                     --start-cmd prog args... ;\n"
177        "                     [--stop-cmd prog arg... ;]\n"
178        "                     [--restart-cmd prog arg... ;]\n"
179        "                     [--restart-wakeup]\n"
180        "                     [--restart-netchange]\n"
181        "\n"
182        "daemondo is a wrapper program that runs daemons. It starts the specified\n"
183        "daemon on launch, stops it when given SIGTERM, and restarts it on SIGHUP.\n"
184        "It can also watch for transitions in system state, such as a change in\n"
185        "network availability or system power state, and restart the daemon on such\n"
186        "an event.\n"
187        "\n"
188        "daemondo works well as an adapter between darwin 8's launchd, and daemons\n"
189        "that are normally started via traditional rc.d style scripts or parameters.\n"
190        "\n"
191        "Parameters:\n"
192        "\n"
193        "  -h, --help                      Provide this help.\n"
194        "  -v                              Increase verbosity.\n"
195        "      --verbosity=n               Set verbosity to n.\n"
196        "  -V, --version                   Display program version information.\n"
197        "  -l, --label=desc                Label used to describe the daemon.\n"
198        "\n"
199        "  -s, --start-cmd args... ;       Required: command that will start the daemon.\n"
200        "  -k, --stop-cmd args... ;        Command that will stop the daemon.\n"
201        "  -r, --restart-cmd args... ;     Command that will restart the daemon.\n"
202        "\n"
203        "      --pid=none|exec|fileauto|fileclean\n"
204        "                                  Whether to use/how to treat pid file.\n"
205        "      --pidfile=<pidfile>         A pidfile from which to scavenge the target pid.\n"
206        "\n"
207        "      --restart-wakeup            Restart daemon on wake from sleep.\n"
208        "      --restart-netchange         Restart daemon on a network change.\n"
209        "      --restart-config regex... ; SC patterns on which to restart the daemon.\n"
210        "      --restart-dist-notify names... ;\n"
211        "                                  Distributed Notification Center notifications\n"
212        "                                  on which to restart the daemon.\n"
213        "      --restart-darwin-notify names... ;\n"
214        "                                  Darwin Notification Center notifications\n"
215        "                                  on which to restart the daemon.\n"
216        "      --restart-config regex... ; SC patterns on which to restart the daemon.\n"
217        "\n"
218        "daemondo responds to SIGHUP by restarting the daemon, and to SIGTERM by\n"
219        "stopping it. daemondo exits on receipt of SIGTERM, or when it detects\n"
220        "that the daemon process has died.\n"
221        "\n"
222        "The arguments start-cmd, stop-cmd, restart-cmd, restart-config,\n"
223        "restart-dist-notify, and restart-darwin-notify, if present,\n"
224        "must each be followed by arguments terminated by a ';'. You may need to\n"
225        "escape or quote the ';' to protect it from special handling by your shell.\n"
226        "\n"
227        "daemondo runs in one of two modes: (1) If no stop-cmd is given, daemondo\n"
228        "executes start-cmd asyncronously, and tracks the process id; that process id\n"
229        "is used to signal the daemon for later stop and/or restart. (2) If stop-cmd\n"
230        "is given, then both start-cmd and stop-cmd are issued syncronously, and are\n"
231        "assumed to do all the work of controlling the daemon. In such cases there is\n"
232        "no process id to track. In either mode, restart-cmd, if present, is used to\n"
233        "restart the daemon. If in mode 1, restart-cmd must not disrupt the process id.\n"
234        "If restart-cmd is not provided, the daemon is restarted via a stop/start\n"
235        "sequence.\n"
236        "\n"
237        "The argument restart-config specifies a set of regex patterns corresponding\n"
238        "to system configuration keys, on notification of change for which the daemon\n"
239        "will be restarted\n"
240        "\n"
241        "The arguments restart-dist-notify and restart-darwin-notify specify a set of\n"
242        "notification names from the distributed and darwin notification centers,\n"
243        "respectively, on receipt of which the daemon will be restarted.\n"
244        "\n"
245        "The argument restart-wakeup will cause the daemon to be restarted when the\n"
246        "computer wakes from sleep.\n"
247        "\n"
248        "The argument restart-netchange will cause the daemon to be restarted when\n"
249        "the network configuration changes. This is a shortcut for the more\n"
250        "verbose --restart-darwin-notify com.apple.system.config.network_change.\n"
251        "\n"
252        "In mode 1 only, daemondo will exit when it detects that the daemon being\n"
253        "monitored has exited.\n"
254        "\n"
255        ;
256       
257    printf("%s", helpText);
258}
259
260
261void
262CreatePidFile(void)
263{
264    // Write a pid file if we're expected to
265    if (pidFile != NULL)
266    {
267        FILE* f = NULL;
268        switch (pidStyle)
269        {
270        default:
271        case kPidStyleNone:         // No pid is available
272        case kPidStyleFileAuto:     // The process should create its own pid file
273        case kPidStyleFileClean:    // The process should create its own pid file
274            break;
275        case kPidStyleExec:         // We know the pid, and will write it to the pid file
276            f = fopen(pidFile, "w");
277            if (f != NULL)
278            {
279                fprintf(f, "%d", runningPid);
280                fclose(f);
281            }
282            break;
283        }
284    }
285}
286
287void
288DestroyPidFile(void)
289{
290    // Cleanup the pid file
291    if (pidFile != NULL)
292    {
293        switch (pidStyle)
294        {
295        default:
296        case kPidStyleNone:         // No pid is available
297        case kPidStyleFileAuto:     // The process should remove its own pid file
298            break;
299        case kPidStyleExec:         // We wrote the file, and we'll remove it
300        case kPidStyleFileClean:    // The process wrote the file, but we'll remove it
301                        if (verbosity >= 5)
302                                LogMessage("Attempting to delete pidfile %s\n", pidFile);
303            if (unlink(pidFile) && verbosity >= 3)
304                                LogMessage("Failed attempt to delete pidfile %s (%d)\n", pidFile, errno);           
305            break;
306        }
307    } else {
308                if (verbosity >= 5)
309                        LogMessage("No pidfile to delete: none specified\n");
310    }
311}
312
313
314pid_t
315CheckForValidPidFile(void)
316{
317    // Try to read the pid from the pid file
318    pid_t pid = -1;
319    FILE* f = fopen(pidFile, "r");
320    if (f != NULL)
321    {
322        if (1 != fscanf(f, "%d", &pid))
323            pid = -1;
324        if (pid == 0)
325            pid = -1;
326        fclose(f);
327    }
328   
329    // Check whether the pid represents a valid process
330    if (pid != -1 && 0 != kill(pid, 0))
331        pid = -1;
332   
333    return pid;
334}
335
336
337pid_t
338DeletePreexistingPidFile(void)
339{
340    // Try to read the pid from the pid file
341    pid_t pid = -1;
342    FILE* f = fopen(pidFile, "r");
343    if (f != NULL)
344    {
345        if (1 != fscanf(f, "%d", &pid))
346            pid = -1;
347        if (pid == 0)
348            pid = -1;
349        fclose(f);
350    }
351   
352    // Check whether the pid represents a valid process
353    int valid = (pid != -1 && 0 != kill(pid, 0));
354   
355    // Log information about the discovered pid file
356    if (verbosity >= 3 && pid != -1) {
357        LogMessage("Discovered preexisting pidfile %s containing pid %d which is a %s process\n", pidFile, pid, 
358                (valid) ? "valid" : "invalid");
359    }
360   
361    // Try to delete the pidfile if it's present
362    if (pid != -1) {
363            if (unlink(pidFile)) {
364                if (verbosity >= 3)
365                                LogMessage("Error %d while trying to cleanup prexisting pidfile %s\n", errno, pidFile);
366                } else {
367                        if (verbosity >= 3)
368                                LogMessage("Deleted preexisting pidfile %s\n", pidFile);
369                }
370        }
371   
372    return pid;
373}
374
375
376pid_t
377WaitForValidPidFile(void)
378{
379    CFAbsoluteTime patience = CFAbsoluteTimeGetCurrent() + kChildStartPidTimeout;
380   
381    // Poll for a child process and pidfile to be generated, until we lose patience.
382    pid_t pid = -1;
383    while ((pid = CheckForValidPidFile()) == -1 && (patience - CFAbsoluteTimeGetCurrent() > 0))
384        sleep(1);
385       
386    if (verbosity >= 3)
387        LogMessage("Discovered pid %d from pidfile %s\n", pid, pidFile);
388
389    return pid;
390}
391
392
393
394void
395MonitorChild(pid_t childPid)
396{
397    runningPid = childPid;
398   
399    if (runningPid != 0 && runningPid != -1) {
400        if (verbosity >=3 )
401            LogMessage("Start monitoring of pid %d via kevent\n", runningPid);
402       
403        // Monitor the process deaths for that pid
404        struct kevent ke;
405        EV_SET(&ke, childPid, EVFILT_PROC, EV_ADD | EV_ONESHOT, NOTE_EXIT, 0, NULL);
406        if (-1 == kevent(kqfd, &ke, 1, NULL, 0, NULL))
407            LogMessage("Could not monitor kevent for pid %d (%d)\n", runningPid, errno);
408    }
409}
410
411
412void
413UnmonitorChild()
414{
415    runningPid = 0;
416}
417
418
419int
420MonitoringChild()
421{
422    return runningPid != 0;
423}
424
425
426void
427ProcessChildDeath(pid_t childPid)
428{
429    // Take special note if process runningPid dies
430    if (runningPid != 0 && runningPid != -1 && childPid == runningPid)
431    {
432        if (verbosity >= 1)
433            LogMessage("Target process %d has died\n", childPid);
434           
435        UnmonitorChild();
436        DestroyPidFile();
437       
438        CFRunLoopStop(CFRunLoopGetCurrent());
439    }
440}
441
442
443void
444WaitChildDeath(pid_t childPid)
445{
446    // Wait for the death of a particular child
447    int wait_result = 0;
448    int wait_stat = 0;
449   
450    // Set up a timer for how long we'll wait for child death before we
451    // kill the child outright with SIGKILL (infanticide)
452    CFAbsoluteTime patience = CFAbsoluteTimeGetCurrent() + kChildDeathTimeout;
453   
454    // Wait for the death of child, calling into our run loop if it's not dead yet.
455    // Note that the wait may actually be processed by our runloop callback, in which
456    // case the wait here will simply return -1.
457    while ((wait_result = wait4(childPid, &wait_stat, WNOHANG, NULL)) == 0)
458    {
459        CFTimeInterval patienceRemaining = patience - CFAbsoluteTimeGetCurrent();
460        if (patienceRemaining > 0)
461            CFRunLoopRunInMode(kChildWatchMode, patienceRemaining, true);
462        else
463        {
464            // We've run out of patience; kill the child with SIGKILL
465            if (verbosity >= 3)
466                LogMessage("Child %d didn't die; Killing with SIGKILL.\n", childPid);
467           
468            if (0 != kill(childPid, SIGKILL))
469            {
470                if (verbosity >= 3)
471                    LogMessage("Attempt to kill process %d failed.\n", childPid);
472            }
473        }
474    }
475   
476    // The child should be dead and gone by now.
477    ProcessChildDeath(childPid);
478}
479
480
481void
482CheckChildren(void)
483{
484    // Process any pending child deaths
485    int wait_stat = 0;
486    pid_t pid = 0;
487    while ((pid = wait4(0, &wait_stat, WNOHANG, NULL)) != 0 && pid != -1)
488        ProcessChildDeath(pid);
489}
490
491
492pid_t
493Exec(const char* const argv[], int sync)
494{
495    if (!argv || !argv[0] || !*argv[0])
496        return -1;
497       
498    pid_t pid = fork();
499    switch (pid)
500    {
501    case 0:
502        // In the child process
503        {           
504            // Child process has no stdin, but shares stdout and stderr with us
505            // Is that the right behavior?
506            int nullfd = 0;
507            if ((nullfd = open("/dev/null", O_RDONLY)) == -1)
508                _exit(1);
509            dup2(nullfd, STDIN_FILENO);
510
511            // Launch the child
512            execvp(argv[0], (char* const*)argv);
513           
514            // We get here only if the exec fails.
515            LogMessage("Unable to launch process %s.\n", argv[0]);
516            _exit(1);
517        }
518        break;
519   
520    case -1:
521        // error starting child process
522        LogMessage("Unable to fork child process %s.\n", argv[0]);
523        break;
524   
525    default:
526        // In the original process
527        if (sync)
528        {
529            // If synchronous, wait for the child process to complete
530            WaitChildDeath(pid);
531            pid = 0;
532        }
533        break;
534    }
535   
536    return pid;
537}
538
539
540int
541Start(void)
542{   
543    char buf[1024];
544   
545    if (!startArgs || !startArgs[0])
546    {
547        LogMessage("There is nothing to start. No start-cmd was specified\n");
548        return 2;
549    }
550   
551    if (verbosity >= 1)
552        LogMessage("Starting process\n");
553        if (pidFile != NULL)
554                DeletePreexistingPidFile();
555    if (verbosity >= 2)
556        LogMessage("Running start-cmd %s\n", CatArray(startArgs, buf, sizeof(buf)));
557       
558    // Exec the start-cmd
559    pid_t pid = Exec(startArgs, pidStyle == kPidStyleNone);
560   
561    // Process error during Exec
562    if (pid == -1)
563    {
564        if (verbosity >= 2)
565            LogMessage("Error running start-cmd %s\n", CatArray(startArgs, buf, sizeof(buf)));
566        if (verbosity >= 1)
567            LogMessage("error while starting\n");
568        return 2;
569    }
570   
571    // Try to discover the pid of the running process
572    switch (pidStyle)
573    {
574    case kPidStyleNone:         // The command should have completed: we have no pid (should be zero)
575        pid = -1;
576        break;
577       
578    case kPidStyleExec:         // The pid comes from the Exec
579        break;
580       
581    case kPidStyleFileAuto:     // Poll pid from the pidfile
582    case kPidStyleFileClean:
583        pid = WaitForValidPidFile();
584        if (pid == -1)
585        {
586            if (verbosity >= 2)
587                LogMessage("Error; expected pidfile not found following Exec of start-cmd %s\n", CatArray(startArgs, buf, sizeof(buf)));
588            if (verbosity >= 1)
589                LogMessage("error while starting\n");
590            return 2;
591        }
592        break;
593       
594    default:
595        break;
596    }
597   
598    // If we have a pid, then begin tracking it
599    MonitorChild(pid);
600    if (pid != 0 && pid != -1)
601    {
602        if (verbosity >= 1)
603            LogMessage("Target process id is %d\n", pid);
604
605        // Create a pid file if we need to     
606        CreatePidFile();
607    }
608   
609    return 0;
610}
611
612
613int
614Stop(void)
615{
616    char buf[1024];
617
618    pid_t pid;
619    if (!stopArgs || !stopArgs[0])
620    {
621        // We don't have a stop command, so we try to kill the process
622        // we're tracking with runningPid
623        if ((pid = runningPid) != 0 && pid != -1)
624        {
625            if (verbosity >= 1)
626                LogMessage("Stopping process %d\n", pid);
627           
628            // Send the process a SIGTERM to ask it to quit
629            kill(pid, SIGTERM);
630           
631            // Wait for process to quit, killing it after a timeout
632            WaitChildDeath(pid);
633        }
634        else
635        {
636            if (verbosity >= 1)
637                LogMessage("process was already stopped\n");
638        }
639    }
640    else
641    {
642        // We have a stop-cmd to use. We execute it synchronously,
643        // and trust it to do the job.
644        if (verbosity >= 1)
645            LogMessage("Stopping process\n");
646        if (verbosity >= 2)
647            LogMessage("Running stop-cmd %s\n", CatArray(stopArgs, buf, sizeof(buf)));
648        pid = Exec(stopArgs, TRUE);
649        if (pid == -1)
650        {
651            if (verbosity >= 2)
652                LogMessage("Error while running stop-cmd %s\n", CatArray(stopArgs, buf, sizeof(buf)));
653            if (verbosity >= 1)
654                LogMessage("error stopping process\n");
655            return 2;
656        }
657
658        // We've executed stop-cmd, so we assume any runningPid process is gone
659        UnmonitorChild();
660        DestroyPidFile();
661    }
662   
663    return 0;
664}
665
666
667int
668Restart(void)
669{
670    char buf[1024];
671
672    if (!restartArgs || !restartArgs[0])
673    {
674        // We weren't given a restart command, so just use stop/start
675        if (verbosity >= 1)
676            LogMessage("Restarting process\n");
677           
678        // Stop the process
679        Stop();
680       
681        // Delay for a restartWait seconds to allow other process support to stabilize
682        // (This gives a chance for other processes that might be monitoring the process,
683        // for instance, to detect its death and cleanup).
684        sleep(restartWait);
685       
686        // Start it again
687        Start();
688    }
689    else
690    {
691        // Bug: we should recapture the target process id from the pidfile in this case
692       
693        // Execute the restart-cmd and trust it to do the job
694        if (verbosity >= 1)
695            LogMessage("Restarting process\n");
696        if (verbosity >= 2)
697            LogMessage("Running restart-cmd %s\n", CatArray(restartArgs, buf, sizeof(buf)));
698           
699        pid_t pid = Exec(restartArgs, TRUE);
700        if (pid == -1)
701        {
702            if (verbosity >= 2)
703                LogMessage("Error running restart-cmd %s\n", CatArray(restartArgs, buf, sizeof(buf)));
704            if (verbosity >= 1)
705                LogMessage("error restarting process\n");
706            return 2;
707        }
708    }
709   
710    return 0;
711}
712
713
714void
715ScheduledRestartCallback(CFRunLoopTimerRef timer UNUSED, void *info UNUSED)
716{
717    if (verbosity >= 3)
718        LogMessage("Scheduled restart time has arrived.\n");
719       
720    // Our scheduled restart fired, so restart now
721    Restart();
722}
723
724
725void
726CancelScheduledRestart(void)
727{
728    // Kill off any existing timer
729    if (restartTimer)
730    {
731        if (CFRunLoopTimerIsValid(restartTimer))
732            CFRunLoopTimerInvalidate(restartTimer);
733        CFRelease(restartTimer);
734        restartTimer = NULL;
735    }
736}
737
738
739void
740ScheduleRestartForTime(CFAbsoluteTime absoluteTime)
741{
742    // Cancel any currently scheduled restart
743    CancelScheduledRestart();
744   
745    // Schedule a new restart
746    restartTimer = CFRunLoopTimerCreate(NULL, absoluteTime, 0, 0, 0, ScheduledRestartCallback, NULL);
747    if (restartTimer)
748        CFRunLoopAddTimer(CFRunLoopGetCurrent(), restartTimer, kCFRunLoopDefaultMode);
749}
750
751
752void
753ScheduleDelayedRestart(void)
754{
755    // The hysteresis here allows us to take multiple restart requests within a small
756    // period of time, and coalesce them together into only one. It also allows for
757    // a certain amount of "slop time" for things to stabilize following whatever
758    // event is triggering the restart.
759    if (verbosity >= 3)
760        LogMessage("Scheduling restart %f seconds in future.\n", restartHysteresis);
761    ScheduleRestartForTime(CFAbsoluteTimeGetCurrent() + restartHysteresis);
762}
763
764
765void
766DynamicStoreChanged(
767                    SCDynamicStoreRef   store UNUSED,
768                    CFArrayRef          changedKeys,
769                    void                *info UNUSED
770                    )
771{
772    if (verbosity >= 3)
773    {
774        char bigBuf[1024];
775        *bigBuf = '\0';
776       
777        CFIndex cnt = CFArrayGetCount(changedKeys);
778        CFIndex i;
779        for (i = 0; i < cnt; ++i)
780        {
781            char buf[256];
782            CFStringRef value = CFArrayGetValueAtIndex(changedKeys, i);
783            CFStringGetCString(value, buf, sizeof(buf), kCFStringEncodingUTF8);
784            if (i > 0)
785                strlcat(bigBuf, ", ", sizeof(bigBuf));
786            strlcat(bigBuf, buf, sizeof(bigBuf));
787        }
788
789        LogMessage("Restarting daemon because of the following changes in the dynamic store: %s\n", bigBuf);
790    }
791   
792    ScheduleDelayedRestart();
793}
794
795
796void
797PowerCallBack(void *x UNUSED, io_service_t y UNUSED, natural_t messageType, void *messageArgument)
798{
799    switch (messageType)
800    {
801    case kIOMessageSystemWillSleep:
802    case kIOMessageCanSystemSleep:
803        /*  Power Manager waits for your reply via one of these functions for up
804        to 30 seconds. If you don't acknowledge the power change by calling
805        IOAllowPowerChange(), you'll delay sleep by 30 seconds. */
806        IOAllowPowerChange(pwrRootPort, (long)messageArgument);
807        break;
808    case kIOMessageSystemHasPoweredOn:
809        if (restartOnWakeup)
810        {
811            if (verbosity >= 3)
812                LogMessage("Restarting daemon because of system wake from sleep\n");
813            ScheduleDelayedRestart();
814        }
815        break;
816    }
817}
818
819
820void
821NotificationCenterCallback(
822                                CFNotificationCenterRef center UNUSED,
823                                void *observer UNUSED,
824                                CFStringRef name,
825                                const void *object UNUSED,
826                                CFDictionaryRef userInfo UNUSED)
827{
828    if (verbosity >= 3)
829    {
830        char buf[256];
831        CFStringGetCString(name, buf, sizeof(buf), kCFStringEncodingUTF8);
832        LogMessage("Restarting daemon due to receipt of the notification %s\n", buf);
833    }
834       
835    ScheduleDelayedRestart();
836}
837
838
839void
840SignalCallback(CFMachPortRef port UNUSED, void *msg, CFIndex size UNUSED, void *info UNUSED)
841{
842    mach_msg_header_t* hdr = (mach_msg_header_t*)msg;
843    switch (hdr->msgh_id)
844    {
845    case SIGTERM:       
846        // On receipt of SIGTERM we set our terminate flag and stop the process
847        if (!terminating)
848        {
849            terminating = true;
850            if (verbosity >= 1)
851                LogMessage("SIGTERM received\n");
852            Stop();
853        }
854        break;
855   
856    case SIGHUP:
857        if (verbosity >= 1)
858            LogMessage("SIGHUP received\n");
859        if (!terminating)
860            Restart();
861        break;
862       
863    case SIGCHLD:
864        CheckChildren();
865        break;
866       
867    default:
868        break;
869    }
870}
871
872
873void KQueueCallBack (CFSocketRef socketRef, CFSocketCallBackType type UNUSED,
874             CFDataRef address UNUSED, const void *data UNUSED, void *context UNUSED)
875{
876    int fd = CFSocketGetNative(socketRef);
877   
878    struct kevent event;
879    memset(&event, 0x00, sizeof(struct kevent));
880   
881    if (kevent(fd, NULL, 0, &event, 1, NULL) == -1) {
882        LogMessage("Couldn't get kevent.  Error %d/%s\n", errno, strerror(errno));
883    } else {
884        if (event.fflags & NOTE_EXIT) {
885       
886            pid_t pid = event.ident;
887           
888            if (verbosity >= 3)
889                LogMessage("Received kevent: pid %d has exited\n", pid);
890               
891            ProcessChildDeath(pid);
892        } else
893            LogMessage("Unexpected kevent received: %d\n", event.fflags);
894    }
895
896}
897
898
899void
900AddNotificationToCenter(const void* value, void* context)
901{
902    CFNotificationCenterAddObserver((CFNotificationCenterRef)context,
903        kProgramName,
904        NotificationCenterCallback,
905        value,      // name of notification
906        NULL,       // object to observe
907        CFNotificationSuspensionBehaviorDeliverImmediately);
908}
909
910
911void handle_child_signal(int sig)
912{
913    // Because there's a limited environment in which we can operate while
914    // handling a signal, we send a mach message to our run loop, and handle
915    // things from there.
916    mach_msg_header_t header;
917    header.msgh_bits        = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
918    header.msgh_size        = sizeof(header);
919    header.msgh_remote_port = sigChild_m_port;
920    header.msgh_local_port  = MACH_PORT_NULL;
921    header.msgh_reserved    = 0;
922    header.msgh_id          = sig;
923   
924    mach_msg_return_t status = mach_msg_send(&header);
925    if (status != 0) {
926        LogMessage("mach_msg_send failed in handle_child_signal!\n");
927    }
928}
929
930
931void handle_generic_signal(int sig)
932{
933    // Because there's a limited environment in which we can operate while
934    // handling a signal, we send a mach message to our run loop, and handle
935    // things from there.
936    mach_msg_header_t header;
937    header.msgh_bits        = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
938    header.msgh_size        = sizeof(header);
939    header.msgh_remote_port = sigGeneric_m_port;
940    header.msgh_local_port  = MACH_PORT_NULL;
941    header.msgh_reserved    = 0;
942    header.msgh_id          = sig;
943   
944    mach_msg_return_t status = mach_msg_send(&header);
945    if (status != 0) {
946        LogMessage("mach_msg_send failed in handle_generic_signal!\n");
947    }
948}
949
950
951int
952MainLoop(void)
953{
954    // *** TODO: This routine needs more error checking
955   
956    int status = 0;
957   
958    if (verbosity >= 3)
959        LogMessage("Initializing; daemondo pid is %d\n", getpid());
960   
961    // === Setup Notifications of Changes to System Configuration ===
962    // Create a new SCDynamicStore session and an associated runloop source, adding it default mode
963    SCDynamicStoreRef   dsRef           = SCDynamicStoreCreate(NULL, kProgramName, DynamicStoreChanged, NULL);
964    CFRunLoopSourceRef  dsSrc           = SCDynamicStoreCreateRunLoopSource(NULL, dsRef, 0);
965    CFRunLoopAddSource(CFRunLoopGetCurrent(), dsSrc, kCFRunLoopDefaultMode);
966   
967    // Tell the DynamicStore which keys to notify us on: this is the set of keys on which the
968    // daemon will be restarted, at least for now--we may want to give more flexibility at some point.
969    (void) SCDynamicStoreSetNotificationKeys(dsRef, NULL, scRestartPatterns);
970   
971   
972    // === Setup Notifications from Notification Centers  ===
973    CFArrayApplyFunction(distNotifyNames, CFRangeMake(0, CFArrayGetCount(distNotifyNames)),
974        AddNotificationToCenter, CFNotificationCenterGetDistributedCenter());
975    CFArrayApplyFunction(darwinNotifyNames, CFRangeMake(0, CFArrayGetCount(darwinNotifyNames)),
976        AddNotificationToCenter, CFNotificationCenterGetDarwinNotifyCenter());
977
978
979    // === Setup Notifications of Changes to System Power State ===
980    // Register for system power notifications, adding a runloop source to handle then
981    IONotificationPortRef   powerRef = NULL;
982    io_object_t             pwrNotifier = 0;
983    pwrRootPort = IORegisterForSystemPower(0, &powerRef, PowerCallBack, &pwrNotifier);
984    if (pwrRootPort != 0)
985        CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(powerRef), kCFRunLoopDefaultMode);
986       
987   
988    // === Setup Notifications of Signals ===
989    // Add a mach port source to our runloop for handling of the signals
990    CFMachPortRef       sigChildPort    = CFMachPortCreate(NULL, SignalCallback, NULL, NULL);
991    CFMachPortRef       sigGenericPort  = CFMachPortCreate(NULL, SignalCallback, NULL, NULL);
992   
993    CFRunLoopSourceRef  sigChildSrc     = CFMachPortCreateRunLoopSource(NULL, sigChildPort, 0);
994    CFRunLoopSourceRef  sigGenericSrc   = CFMachPortCreateRunLoopSource(NULL, sigGenericPort, 0);
995   
996   
997    // === Setup kevent notifications of process death
998    kqfd = kqueue();
999    CFSocketRef kqSocket                = CFSocketCreateWithNative(NULL,  kqfd,
1000                                            kCFSocketReadCallBack, KQueueCallBack, NULL);
1001    CFRunLoopSourceRef  kqueueSrc       = CFSocketCreateRunLoopSource(NULL, kqSocket, 0);   
1002   
1003   
1004    // Add only the child signal sources to the childwatch mode
1005    CFRunLoopAddSource(CFRunLoopGetCurrent(), sigChildSrc, kChildWatchMode);
1006    CFRunLoopAddSource(CFRunLoopGetCurrent(), kqueueSrc, kChildWatchMode);
1007   
1008    // Add both child and generic signal sources to the default mode
1009    CFRunLoopAddSource(CFRunLoopGetCurrent(), sigChildSrc, kCFRunLoopDefaultMode);
1010    CFRunLoopAddSource(CFRunLoopGetCurrent(), kqueueSrc, kCFRunLoopDefaultMode);
1011    CFRunLoopAddSource(CFRunLoopGetCurrent(), sigGenericSrc, kCFRunLoopDefaultMode);
1012   
1013    // Install signal handlers
1014    sigChild_m_port     = CFMachPortGetPort(sigChildPort);
1015    sigGeneric_m_port   = CFMachPortGetPort(sigGenericPort);
1016
1017    signal(SIGCHLD, handle_child_signal);
1018    signal(SIGTERM, handle_generic_signal);
1019    signal(SIGHUP, handle_generic_signal);
1020   
1021   
1022    // === Core Loop ===
1023    // Start the daemon
1024    status = Start();
1025   
1026    if (verbosity >= 3)
1027        LogMessage("Start event loop\n");
1028   
1029    // Run the run loop until we stop it, or until the process we're tracking stops
1030    while (status == 0 && !terminating && MonitoringChild())
1031        CFRunLoopRunInMode(kCFRunLoopDefaultMode, 99999999.0, true);
1032       
1033    if (verbosity >= 3)
1034        LogMessage("End event loop\n");
1035   
1036       
1037    // === Tear Down (we don't really need to do all of this) ===
1038    // The daemon should by now have either been stopped, or stopped of its own accord
1039       
1040    // Remove signal handlers
1041    signal(SIGTERM, SIG_DFL);
1042    signal(SIGHUP, SIG_DFL);
1043    signal(SIGCHLD, SIG_DFL);
1044   
1045    sigChild_m_port = 0;
1046    sigGeneric_m_port = 0;
1047   
1048    // Remove run loop sources
1049    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), sigChildSrc, kChildWatchMode);
1050    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), kqueueSrc, kChildWatchMode);
1051   
1052    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), sigChildSrc, kCFRunLoopDefaultMode);
1053    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), kqueueSrc, kCFRunLoopDefaultMode);
1054    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), sigGenericSrc, kCFRunLoopDefaultMode);
1055   
1056    // Tear down signal handling infrastructure
1057    CFRelease(sigChildSrc);
1058    CFRelease(sigGenericSrc);
1059   
1060    CFRelease(sigChildPort);
1061    CFRelease(sigGenericPort);
1062   
1063    // Tear down kqueue infrastructure
1064    CFRelease(kqueueSrc);
1065    CFRelease(kqSocket);
1066    close(kqfd);
1067
1068    // Tear down DynamicStore stuff
1069    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), dsSrc, kCFRunLoopDefaultMode);
1070   
1071    CFRelease(dsSrc);
1072    CFRelease(dsRef);
1073   
1074    // Tear down notifications from Notification Center
1075    CFNotificationCenterRemoveEveryObserver(CFNotificationCenterGetDistributedCenter(), kProgramName);
1076    CFNotificationCenterRemoveEveryObserver(CFNotificationCenterGetDarwinNotifyCenter(), kProgramName);
1077
1078    // Tear down power management stuff
1079    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(powerRef), kCFRunLoopDefaultMode);
1080    IODeregisterForSystemPower(&pwrNotifier);
1081   
1082    if (verbosity >= 3)
1083        LogMessage("Terminating\n");
1084   
1085    return status; 
1086}
1087
1088
1089int
1090CollectCmdArgs(char* arg1, int argc, char* const argv[], const char * const ** args)
1091{
1092    // Count the number of additional arguments up until end of args or the marker argument ";"
1093    int moreArgs = 0;
1094    for (; moreArgs < argc && 0 != strcmp(";", argv[moreArgs]); ++moreArgs)
1095        ;
1096       
1097    // We were given one argument for free
1098    int nargs = moreArgs + 1;
1099       
1100    // Allocate an array for the arguments
1101    *args = calloc(sizeof(char**), nargs+1);
1102    if (!*args)
1103        return 0;
1104       
1105    // Copy the arguments into our new array
1106    (*(char***)args)[0] = arg1;
1107   
1108    int i;
1109    for (i = 0; i < moreArgs; ++i)
1110        (*(char***)args)[i+1] = argv[i];
1111       
1112    // NULL-terminate the argument array
1113    (*(char***)args)[nargs] = NULL;
1114   
1115    // Return number of args we consumed, accounting for potential trailing ";"
1116    return (moreArgs == argc) ? moreArgs : moreArgs + 1;
1117}
1118
1119
1120void
1121AddSingleArrayArg(const char* arg, CFMutableArrayRef array)
1122{
1123    CFStringRef s = CFStringCreateWithCString(NULL, arg, kCFStringEncodingUTF8);
1124    CFArrayAppendValue(array, s);
1125    CFRelease(s);
1126}
1127
1128
1129int
1130CollectArrayArgs(char* arg1, int argc, char* const argv[], CFMutableArrayRef array)
1131{
1132    // Let CollectCmdArgs do the grunt work
1133    const char* const* args = NULL;
1134    int argsUsed = CollectCmdArgs(arg1, argc, argv, &args);
1135   
1136    // Add arguments to the mutable array
1137    if (args != NULL)
1138    {
1139        const char* const* argp = args;
1140        for (; *argp != NULL; ++argp)
1141            AddSingleArrayArg(*argp, array);
1142        free((void*)args);
1143    }
1144   
1145    return argsUsed;
1146}
1147
1148
1149enum {
1150    kVerbosityOpt           = 256,
1151    kRestartConfigOpt,
1152    kRestartDistNotifyOpt,
1153    kRestartDarwinNotifyOpt,
1154    kRestartWakeupOpt,
1155    kRestartNetChangeOpt,
1156    kPidOpt,
1157    kPidFileOpt,
1158    kRestartHysteresisOpt,
1159    kRestartWaitOpt
1160};
1161
1162
1163int
1164main(int argc, char* argv[])
1165{
1166    int status = 0;
1167   
1168    // Initialization
1169    kProgramName        = CFSTR("daemondo");
1170    kChildWatchMode     = CFSTR("ChildWatch");      // A runloop mode
1171   
1172    scRestartPatterns   = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
1173    distNotifyNames     = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
1174    darwinNotifyNames   = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
1175   
1176    // Make stdout flush after every line
1177    setvbuf(stdout, (char *)NULL, _IOLBF, 0);
1178   
1179    // Process arguments
1180    static struct option longopts[] = {
1181            // Start/Stop/Restart the process
1182        { "start-cmd",      required_argument,      0,              's' },
1183        { "stop-cmd",       required_argument,      0,              'k' },
1184        { "restart-cmd",    required_argument,      0,              'r' },
1185       
1186            // Dynamic Store Keys to monitor
1187        { "restart-config", required_argument,      0,              kRestartConfigOpt },
1188       
1189            // Notifications to monitor
1190        { "restart-dist-notify",
1191                            required_argument,      0,              kRestartDistNotifyOpt },
1192        { "restart-darwin-notify",
1193                            required_argument,      0,              kRestartDarwinNotifyOpt },
1194       
1195            // Control over behavior on power state
1196        { "restart-wakeup", no_argument,            0,              kRestartWakeupOpt },
1197
1198            // Short-cuts
1199        { "restart-netchange",
1200                            no_argument,            0,              kRestartNetChangeOpt },
1201                           
1202            // Pid-files
1203        { "pid",            required_argument,      0,              kPidOpt },
1204        { "pidfile",        required_argument,      0,              kPidFileOpt },
1205       
1206            // other
1207        { "help",           no_argument,            0,              'h' },
1208        { "v",              no_argument,            0,              'v' },
1209        { "verbosity",      optional_argument,      0,              kVerbosityOpt },
1210        { "version",        no_argument,            0,              'V' },
1211        { "label",          required_argument,      0,              'l' },
1212        { "restart-hysteresis",
1213                            required_argument,      0,              kRestartHysteresisOpt },
1214        { "restart-wait",
1215                            required_argument,      0,              kRestartWaitOpt },
1216       
1217        { 0,                0,                      0,              0 }
1218    };
1219
1220    while (status == 0 && optind < argc)
1221    {
1222        int optindex = 0;
1223        int ret = getopt_long(argc, argv, ":s:k:r:l:hvV", longopts, &optindex);
1224        int opt = ret;
1225        switch (opt)
1226        {
1227        case ':':
1228            printf("Option error: missing argument for option %s\n", longopts[optindex].name);
1229            exit(1);
1230            break;
1231           
1232        case 's':
1233            if (startArgs)
1234            {
1235                printf("Option error: start-cmd option may be given only once.\n");
1236                exit(1);
1237            }
1238            else
1239            {
1240                optind += CollectCmdArgs(optarg, argc - optind, argv + optind, &startArgs);
1241                optreset = 1;
1242            }
1243            break;
1244           
1245        case 'k':
1246            if (stopArgs)
1247            {
1248                printf("Option error: stop-cmd option may be given only once.\n");
1249                exit(1);
1250            }
1251            else
1252            {
1253                optind += CollectCmdArgs(optarg, argc - optind, argv + optind, &stopArgs);
1254                optreset = 1;
1255            }
1256            break;
1257
1258        case 'r':
1259            if (restartArgs)
1260            {
1261                printf("Option error: restart-cmd option may be given only once.\n");
1262                exit(1);
1263            }
1264            else
1265            {
1266                optind += CollectCmdArgs(optarg, argc - optind, argv + optind, &restartArgs);
1267                optreset = 1;
1268            }
1269            break;
1270           
1271        case kRestartConfigOpt:
1272            optind += CollectArrayArgs(optarg, argc - optind, argv + optind, scRestartPatterns);
1273            optreset = 1;
1274            break;
1275           
1276        case kRestartDistNotifyOpt:
1277            optind += CollectArrayArgs(optarg, argc - optind, argv + optind, distNotifyNames);
1278            optreset = 1;
1279            break;
1280           
1281        case kRestartDarwinNotifyOpt:
1282            optind += CollectArrayArgs(optarg, argc - optind, argv + optind, darwinNotifyNames);
1283            optreset = 1;
1284            break;
1285           
1286        case kRestartWakeupOpt:
1287            restartOnWakeup = TRUE;
1288            break;
1289           
1290        case kRestartNetChangeOpt:
1291            AddSingleArrayArg("com.apple.system.config.network_change", darwinNotifyNames);
1292            break;
1293           
1294        case kRestartHysteresisOpt:
1295            restartHysteresis = strtof(optarg, NULL);
1296            if (restartHysteresis < 0)
1297                restartHysteresis = 0;
1298            break;
1299           
1300        case kRestartWaitOpt:
1301            restartWait = strtol(optarg, NULL, 10);
1302            if (restartWait < 0)
1303                restartWait = 0;
1304            break;
1305           
1306        case kPidOpt:
1307            if      (0 == strcasecmp(optarg, "none"))
1308                pidStyle = kPidStyleNone;
1309            else if (0 == strcasecmp(optarg, "exec"))
1310                pidStyle = kPidStyleExec;
1311            else if (0 == strcasecmp(optarg, "fileauto"))
1312                pidStyle = kPidStyleFileAuto;
1313            else if (0 == strcasecmp(optarg, "fileclean"))
1314                pidStyle = kPidStyleFileClean;
1315            else {
1316                status = 1;
1317                LogMessage("Unexpected pid style %s\n", optarg);
1318            }
1319            break;
1320       
1321        case kPidFileOpt:
1322            if (pidFile != NULL)
1323                free((char*)pidFile);
1324            pidFile = strdup(optarg);
1325            break;
1326       
1327        case 'h':
1328            DoHelp();
1329            exit(0);
1330            break;
1331           
1332        case 'l':
1333            if (label != NULL)
1334                free((char*)label);
1335            label = strdup(optarg);
1336            break;
1337           
1338        case 'v':
1339            ++verbosity;
1340            break;
1341       
1342        case kVerbosityOpt:
1343            if (optarg)
1344                verbosity = strtol(optarg, NULL,  10);
1345            else
1346                ++verbosity;
1347            break;
1348   
1349        case 'V':
1350            DoVersion();
1351            break;
1352           
1353        default:
1354            LogMessage("unexpected parameter: %s\n", argv[optind]);
1355            status = 1;
1356            break;
1357        }
1358    }
1359   
1360    // Default the pid style if it wasn't given
1361    if (pidStyle == kPidStyleUnknown)
1362    {
1363        if (startArgs && !stopArgs && !restartArgs)
1364            pidStyle = kPidStyleExec;
1365        else
1366            pidStyle = kPidStyleNone;
1367    }
1368   
1369    // Go into our main loop
1370    if (status == 0 && startArgs)
1371        status = MainLoop();
1372    else
1373        printf("use option --help for help\n");
1374       
1375    return status;
1376}
Note: See TracBrowser for help on using the repository browser.