]> git.saurik.com Git - apple/launchd.git/commitdiff
launchd-106.3.tar.gz mac-os-x-1042 mac-os-x-1043 mac-os-x-1044ppc mac-os-x-1045ppc v106.3
authorApple <opensource@apple.com>
Fri, 3 Jun 2005 04:53:59 +0000 (04:53 +0000)
committerApple <opensource@apple.com>
Fri, 3 Jun 2005 04:53:59 +0000 (04:53 +0000)
launchd/doc/HOWTO.html [new file with mode: 0644]
launchd/doc/sampled.c [new file with mode: 0644]
launchd/src/StartupItems.c
launchd/src/launch.h
launchd/src/launchctl.1
launchd/src/launchctl.c
launchd/src/launchd.8
launchd/src/launchd.c
launchd/src/launchd.plist.5
launchd/testing/StartCalendarInterval.plist [new file with mode: 0644]

diff --git a/launchd/doc/HOWTO.html b/launchd/doc/HOWTO.html
new file mode 100644 (file)
index 0000000..a166903
--- /dev/null
@@ -0,0 +1,270 @@
+<html>
+<body>
+<h1>Getting Started With Launchd</h1>
+
+<p>Launchd is rather simple actually. Far simpler than you might think.</p>
+
+<h2>Before We Get Started</h2>
+
+<p>Launchd, in an effort to be consistent with other Apple software, uses the
+Property List file format for storing configuration information. Apple's
+Property List APIs provide for three different file formats for storing a
+property list to disk. A plain text option, a binary option, and an XML text
+option. For the remainder of this HOWTO, we will use the XML varient to show
+what a configuration file looks like.</p>
+
+<h3>The basics:</h3>
+
+<p>For the simplest of scenarios, launchd just keeps a process alive. A simple "hello
+world" example of that would be:</p>
+
+<blockquote><pre>
+&lt;?xml version="1.0" encoding="UTF-8"?&gt;
+&lt;!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt;
+&lt;plist version="1.0"&gt;
+&lt;dict&gt;
+        &lt;key&gt;Label&lt;/key&gt;
+        &lt;string&gt;com.example.sleep&lt;/string&gt;
+        &lt;key&gt;ProgramArguments&lt;/key&gt;
+        &lt;array&gt;
+                &lt;string&gt;sleep&lt;/string&gt;
+                &lt;string&gt;100&lt;/string&gt;
+        &lt;/array&gt;
+        &lt;key&gt;OnDemand&lt;/key&gt;
+        &lt;false/&gt;
+&lt;/dict&gt;
+&lt;/plist&gt;
+</pre></blockquote>
+
+<p>In the above example, we have three keys to our top level dictionary. The first
+is the <code>Label</code> which is what is used to uniquely identify jobs when interacting
+with launchd. The second is <code>ProgramArguments</code> which for its value, we have an
+array of strings which represent the tokenized arguments and the program to
+run. The third and final key is <code>OnDemand</code> which overrides the default value of
+true with false thereby instructing launchd to always try and keep this job
+running. That's it! A <code>Label</code>, some <code>ProgramArguments</code> and <code>OnDemand</code> set to false is all
+you need to keep a daemon alive with launchd!</p>
+
+<p>Now if you've ever written a daemon before, you've either called the
+<code>daemon()</code> function or written one yourself. With launchd, that is
+not only unnecessary, but unsupported. If you try and run a daemon you didn't
+write under launchd, you must, at the very least, find a configuration option to
+keep the daemon from daemonizing itself so that launchd can monitor it.</p>
+
+<h3>Going beyond the basics with optional keys:</h3>
+
+<p>There are many optional keys available and documented in the launchd.plist
+man page, so we'll only give a few optional, but common examples:</p>
+
+<blockquote><pre>
+&lt;?xml version="1.0" encoding="UTF-8"?&gt;
+&lt;!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt;
+&lt;plist version="1.0"&gt;
+&lt;dict&gt;
+        &lt;key&gt;Label&lt;/key&gt;
+        &lt;string&gt;com.example.sleep&lt;/string&gt;
+        &lt;key&gt;ProgramArguments&lt;/key&gt;
+        &lt;array&gt;
+                &lt;string&gt;sleep&lt;/string&gt;
+                &lt;string&gt;100&lt;/string&gt;
+        &lt;/array&gt;
+        &lt;key&gt;OnDemand&lt;/key&gt;
+        &lt;false/&gt;
+        &lt;key&gt;UserName&lt;/key&gt;
+        &lt;string&gt;daemon&lt;/string&gt;
+        &lt;key&gt;GroupName&lt;/key&gt;
+        &lt;string&gt;daemon&lt;/string&gt;
+        &lt;key&gt;EnvironmentVariables&lt;/key&gt;
+       &lt;dict&gt;
+               &lt;key&gt;FOO&lt;/key&gt;
+               &lt;string&gt;bar&lt;/string&gt;
+       &lt;/dict&gt;
+&lt;/dict&gt;
+&lt;/plist&gt;
+</pre></blockquote>
+
+<p>In the above example, we see that the job is run as a certain user and group, and additionally has an extra environment variable set.</p>
+
+<h3>Debugging tricks:</h3>
+
+<p>The following example will enable core dumps, set standard out and error to
+go to a log file and to instruct launchd to temporarily bump up the debug level
+of launchd's loggging while acting on behave of your job (remember to adjust
+your syslog.conf accordingly):</p>
+
+<blockquote><pre>
+&lt;?xml version="1.0" encoding="UTF-8"?&gt;
+&lt;!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt;
+&lt;plist version="1.0"&gt;
+&lt;dict&gt;
+        &lt;key&gt;Label&lt;/key&gt;
+        &lt;string&gt;com.example.sleep&lt;/string&gt;
+        &lt;key&gt;ProgramArguments&lt;/key&gt;
+        &lt;array&gt;
+                &lt;string&gt;sleep&lt;/string&gt;
+                &lt;string&gt;100&lt;/string&gt;
+        &lt;/array&gt;
+        &lt;key&gt;OnDemand&lt;/key&gt;
+        &lt;false/&gt;
+        &lt;key&gt;StandardOutPath&lt;/key&gt;
+        &lt;string&gt;/var/log/myjob.log&lt;/string&gt;
+        &lt;key&gt;StandardErrorPath&lt;/key&gt;
+        &lt;string&gt;/var/log/myjob.log&lt;/string&gt;
+        &lt;key&gt;Debug&lt;/key&gt;
+        &lt;true/&gt;
+        &lt;key&gt;SoftResourceLimits&lt;/key&gt;
+       &lt;dict&gt;
+               &lt;key&gt;Core&lt;/key&gt;
+               &lt;integer&gt;9223372036854775807&lt;/integer&gt;
+       &lt;/dict&gt;
+        &lt;key&gt;HardResourceLimits&lt;/key&gt;
+       &lt;dict&gt;
+               &lt;key&gt;Core&lt;/key&gt;
+               &lt;integer&gt;9223372036854775807&lt;/integer&gt;
+       &lt;/dict&gt;
+&lt;/dict&gt;
+&lt;/plist&gt;
+</pre></blockquote>
+
+<h2>But what if I don't want or expect my job to run continuously?</h2>
+
+<h3>The basics of on demand launching:</h3>
+
+<p>Launchd provides a multitude of different criteria that can be used to specify
+when a job should be started. It is important to note that launchd will only
+run one instance of your job though. Therefore, if your on demand job
+malfunctions, you are guaranteed that launchd will not spawn additional copies
+when your criteria is satisfied once more in the future.</p>
+
+<h3>Starting a job periodically:</h3>
+
+<p>Here is an example on how to have a job start every five minutes (300 seconds):</p>
+
+<blockquote><pre>
+&lt;?xml version="1.0" encoding="UTF-8"?&gt;
+&lt;!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt;
+&lt;plist version="1.0"&gt;
+&lt;dict&gt;
+        &lt;key&gt;Label&lt;/key&gt;
+        &lt;string&gt;com.example.touchsomefile&lt;/string&gt;
+        &lt;key&gt;ProgramArguments&lt;/key&gt;
+        &lt;array&gt;
+                &lt;string&gt;touch&lt;/string&gt;
+               &lt;string&gt;/tmp/helloworld&lt;/string&gt;
+        &lt;/array&gt;
+        &lt;key&gt;StartInterval&lt;/key&gt;
+       &lt;integer&gt;300&lt;/integer&gt;
+&lt;/dict&gt;
+&lt;/plist&gt;
+</pre></blockquote>
+
+<p>Sometimes you want a job started on a calendar based interval. The following example will start the job on the 11th minute of the 11th hour every day (using a 24 hour clock system) on the 11th day of the month. Like the Unix cron subsystem, any missing key of the <code>StartCalendarInterval</code> dictionary is treated as a wildcard:</p>
+
+<blockquote><pre>
+&lt;?xml version="1.0" encoding="UTF-8"?&gt;
+&lt;!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt;
+&lt;plist version="1.0"&gt;
+&lt;dict&gt;
+        &lt;key&gt;Label&lt;/key&gt;
+        &lt;string&gt;com.example.touchsomefile&lt;/string&gt;
+        &lt;key&gt;ProgramArguments&lt;/key&gt;
+        &lt;array&gt;
+                &lt;string&gt;touch&lt;/string&gt;
+               &lt;string&gt;/tmp/helloworld&lt;/string&gt;
+        &lt;/array&gt;
+        &lt;key&gt;StartCalendarInterval&lt;/key&gt;
+        &lt;dict&gt;
+               &lt;key&gt;Minute&lt;/key&gt;
+               &lt;integer&gt;11&lt;/integer&gt;
+               &lt;key&gt;Hour&lt;/key&gt;
+               &lt;integer&gt;11&lt;/integer&gt;
+               &lt;key&gt;Day&lt;/key&gt;
+               &lt;integer&gt;11&lt;/integer&gt;
+        &lt;/dict&gt;
+&lt;/dict&gt;
+</pre></blockquote>
+
+<h3>Starting a job based on file system activity:</h3>
+
+<p>The following example will start the job whenever any of the paths being watched change for whatever reason:</p>
+
+<blockquote><pre>
+&lt;?xml version="1.0" encoding="UTF-8"?&gt;
+&lt;!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt;
+&lt;plist version="1.0"&gt;
+&lt;dict&gt;
+        &lt;key&gt;Label&lt;/key&gt;
+       &lt;string&gt;com.example.watchetchostconfig&lt;/string&gt;
+        &lt;key&gt;ProgramArguments&lt;/key&gt;
+        &lt;array&gt;
+                &lt;string&gt;syslog&lt;/string&gt;
+                &lt;string&gt;-s&lt;/string&gt;
+                &lt;string&gt;-l&lt;/string&gt;
+                &lt;string&gt;notice&lt;/string&gt;
+               &lt;string&gt;somebody touched /etc/hostconfig&lt;/string&gt;
+        &lt;/array&gt;
+       &lt;key&gt;WatchPaths&lt;/key&gt;
+        &lt;array&gt;
+               &lt;string&gt;/etc/hostconfig&lt;/string&gt;
+        &lt;/array&gt;
+&lt;/dict&gt;
+</pre></blockquote>
+
+<p>An additional file system trigger is the notion of a queue directory. Launchd will star your job whenever
+the given directories are non-empty and will keep your job running as long as those directories are not empty:</p>
+
+<blockquote><pre>
+&lt;?xml version="1.0" encoding="UTF-8"?&gt;
+&lt;!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt;
+&lt;plist version="1.0"&gt;
+&lt;dict&gt;
+        &lt;key&gt;Label&lt;/key&gt;
+       &lt;string&gt;com.example.mailpush&lt;/string&gt;
+        &lt;key&gt;ProgramArguments&lt;/key&gt;
+        &lt;array&gt;
+               &lt;string&gt;my_custom_mail_push_tool&lt;/string&gt;
+        &lt;/array&gt;
+       &lt;key&gt;QueueDirectories&lt;/key&gt;
+        &lt;array&gt;
+               &lt;string&gt;/var/spool/mymailqdir&lt;/string&gt;
+        &lt;/array&gt;
+&lt;/dict&gt;
+</pre></blockquote>
+
+<h3>Inetd Emulation</h3>
+
+<p>Launchd will happily emulate inetd style daemon semantics:</p>
+
+<blockquote><pre>
+&lt;?xml version="1.0" encoding="UTF-8"?&gt;
+&lt;!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt;
+&lt;plist version="1.0"&gt;
+&lt;dict&gt;
+        &lt;key&gt;Label&lt;/key&gt;
+       &lt;string&gt;com.example.telnetd&lt;/string&gt;
+        &lt;key&gt;ProgramArguments&lt;/key&gt;
+        &lt;array&gt;
+               &lt;string&gt;/usr/libexec/telnetd&lt;/string&gt;
+        &lt;/array&gt;
+       &lt;key&gt;inetdCompatibility&lt;/key&gt;
+        &lt;dict&gt;
+               &lt;key&gt;Wait&lt;/key&gt;
+               &lt;false/&gt;
+        &lt;/dict&gt;
+        &lt;key&gt;Sockets&lt;/key&gt;
+        &lt;dict&gt;
+               &lt;key&gt;Listeners&lt;/key&gt;
+               &lt;dict&gt;
+                       &lt;key&gt;SockServiceName&lt;/key&gt;
+                       &lt;string&gt;telnet&lt;/string&gt;
+                       &lt;key&gt;SockType&lt;/key&gt;
+                       &lt;string&gt;stream&lt;/string&gt;
+               &lt;/dict&gt;
+        &lt;/dict&gt;
+&lt;/dict&gt;
+</pre></blockquote>
+
+<h1>TBD</h1>
+
+</body>
+</html>
diff --git a/launchd/doc/sampled.c b/launchd/doc/sampled.c
new file mode 100644 (file)
index 0000000..eed5526
--- /dev/null
@@ -0,0 +1,103 @@
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <libgen.h>
+
+#include "launch.h"
+
+int main(void)
+{
+       struct timespec timeout = { 60, 0 };
+       struct sockaddr_storage ss;
+       socklen_t slen = sizeof(ss);
+       struct kevent kev;
+       launch_data_t tmp, resp, msg = launch_data_new_string(LAUNCH_KEY_CHECKIN);
+       size_t i;
+       int kq;
+
+       openlog(getprogname(), LOG_PERROR|LOG_PID|LOG_CONS, LOG_DAEMON);
+
+       if (-1 == (kq = kqueue())) {
+               syslog(LOG_ERR, "kqueue(): %m");
+               exit(EXIT_FAILURE);
+       }
+
+       if ((resp = launch_msg(msg)) == NULL) {
+               syslog(LOG_ERR, "launch_msg(\"" LAUNCH_KEY_CHECKIN "\") IPC failure: %m");
+               exit(EXIT_FAILURE);
+       }
+
+       if (LAUNCH_DATA_ERRNO == launch_data_get_type(resp)) {
+               errno = launch_data_get_errno(resp);
+               syslog(LOG_ERR, "Check-in failed: %m");
+               exit(EXIT_FAILURE);
+       }
+
+       tmp = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_TIMEOUT);
+       if (tmp)
+               timeout.tv_sec = launch_data_get_integer(tmp);
+
+       tmp = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_SOCKETS);
+       if (NULL == tmp) {
+               syslog(LOG_ERR, "No sockets found to answer requests on!");
+               exit(EXIT_FAILURE);
+       }
+
+       if (launch_data_dict_get_count(tmp) > 1) {
+               syslog(LOG_WARNING, "Some sockets will be ignored!");
+       }
+
+       tmp = launch_data_dict_lookup(tmp, "SampleListeners");
+       if (NULL == tmp) {
+               syslog(LOG_ERR, "No known sockets found to answer requests on!");
+               exit(EXIT_FAILURE);
+       }
+
+       for (i = 0; i < launch_data_array_get_count(tmp); i++) {
+               launch_data_t tmpi = launch_data_array_get_index(tmp, i);
+
+               EV_SET(&kev, launch_data_get_fd(tmpi), EVFILT_READ, EV_ADD, 0, 0, NULL);
+               if (kevent(kq, &kev, 1, NULL, 0, NULL) == -1) {
+                       syslog(LOG_DEBUG, "kevent(): %m");
+                       exit(EXIT_FAILURE);
+               }
+       }
+
+       launch_data_free(msg);
+       launch_data_free(resp);
+
+       for (;;) {
+               FILE *c;
+               int r;
+
+               if ((r = kevent(kq, NULL, 0, &kev, 1, &timeout)) == -1) {
+                       syslog(LOG_ERR, "kevent(): %m");
+                       exit(EXIT_FAILURE);
+               } else if (r == 0) {
+                       exit(EXIT_SUCCESS);
+               }
+
+               if ((r = accept(kev.ident, (struct sockaddr *)&ss, &slen)) == -1) {
+                       syslog(LOG_ERR, "accept(): %m");
+                       continue; /* this isn't fatal */
+               }
+
+               c = fdopen(r, "r+");
+
+               if (c) {
+                       fprintf(c, "hello world!\n");
+                       fclose(c);
+               } else {
+                       close(r);
+               }
+       }
+}
index 46f0e1aa3adb8e6c285fb5a7d7071206e4ca416a..615cf60a1ae6041d483cf3a755e17934e8f42e60 100644 (file)
@@ -968,7 +968,7 @@ StartupItemRun(CFMutableDictionaryRef aStatusDict, CFMutableDictionaryRef anItem
                /* Compute path to excecutable */
                {
                        char           *tmp;
-                       strcpy(anExecutable, aBundlePath);      /* .../foo     */
+                       strncpy(anExecutable, aBundlePath, sizeof(anExecutable));       /* .../foo     */
                        tmp = rindex(anExecutable, '/');        /* /foo        */
                        strncat(anExecutable, tmp, strlen(tmp));        /* .../foo/foo */
                }
index b4d95578e6ffc9657c59cf83899e352e2fa45ed0..0a8a1750928ce8f62412b063866c4b9c741f8f6e 100644 (file)
@@ -98,8 +98,7 @@
 #define LAUNCH_JOBSOCKETKEY_SERVICENAME                "SockServiceName"
 #define LAUNCH_JOBSOCKETKEY_FAMILY             "SockFamily"
 #define LAUNCH_JOBSOCKETKEY_PROTOCOL           "SockProtocol"
-#define LAUNCH_JOBSOCKETKEY_FD                 "SockFD"
-#define LAUNCH_JOBSOCKETKEY_ADDRINFORESULTS    "AddrinfoResults"
+#define LAUNCH_JOBSOCKETKEY_MULTICASTGROUP     "MulticastGroup"
 
 typedef struct _launch_data *launch_data_t;
 
index 604979283f92f46448c8e09a5d808c258a5f543c..8a45e8520daaaae8f5cf61c7e2228d0a3b65c76d 100644 (file)
@@ -24,13 +24,15 @@ to be read at the time
 starts.
 .Sh SUBCOMMANDS
 .Bl -tag -width -indent
-.It Xo Ar load Op Fl w
+.It Xo Ar load Op Fl wF
 .Ar paths ...
 .Xc
 Load the specified configuration files or directories of configuration files.
 .Bl -tag -width -indent
 .It Fl w
 Remove the disabled key and write the configuration files back out to disk.
+.It Fl F
+Force the loading of the plist. Ignore the Disabled key.
 .El
 .It Xo Ar unload Op Fl w
 .Ar paths ...
@@ -107,6 +109,11 @@ of
 .It Ar help
 Print out a quick usage statement.
 .El
+.Sh ENVIRONMENTAL VARIABLES
+.Bl -tag -width -indent
+.It Pa LAUNCHD_SOCKET
+This variable informs launchctl how to find the correct launchd to talk to. If it is missing, launchctl will use a built-in default.
+.El
 .Sh FILES
 .Bl -tag -width "/System/Library/LaunchDaemons" -compact
 .It Pa ~/Library/LaunchAgents
index f6911d57cf01b59725cc02cd3007c4cf3f2b0399..5142250a8a2da0bd49083c88127590d12aa91b46 100644 (file)
@@ -58,11 +58,12 @@ static launch_data_t CF2launch_data(CFTypeRef);
 static launch_data_t read_plist_file(const char *file, bool editondisk, bool load);
 static CFPropertyListRef CreateMyPropertyListFromFile(const char *);
 static void WriteMyPropertyListToFile(CFPropertyListRef, const char *);
-static void readpath(const char *, launch_data_t, launch_data_t, bool editondisk, bool load);
+static void readpath(const char *, launch_data_t, launch_data_t, bool editondisk, bool load, bool forceload);
 static int _fd(int);
 static int demux_cmd(int argc, char *const argv[]);
 static launch_data_t do_rendezvous_magic(const struct addrinfo *res, const char *serv);
 static void submit_job_pass(launch_data_t jobs);
+static void do_mgroup_join(int fd, int family, int socktype, int protocol, const char *mgroup);
 
 static int load_and_unload_cmd(int argc, char *const argv[]);
 //static int reload_cmd(int argc, char *const argv[]);
@@ -347,7 +348,7 @@ static bool delay_to_second_pass(launch_data_t o)
        return res;
 }
 
-static void readfile(const char *what, launch_data_t pass1, launch_data_t pass2, bool editondisk, bool load)
+static void readfile(const char *what, launch_data_t pass1, launch_data_t pass2, bool editondisk, bool load, bool forceload)
 {
        launch_data_t tmpd, thejob;
        bool job_disabled = false;
@@ -357,9 +358,18 @@ static void readfile(const char *what, launch_data_t pass1, launch_data_t pass2,
                return;
        }
 
+       if (NULL == launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_LABEL)) {
+               fprintf(stderr, "%s: missing the Label key: %s\n", getprogname(), what);
+               launch_data_free(thejob);
+               return;
+       }
+
        if ((tmpd = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_DISABLED)))
                job_disabled = launch_data_get_bool(tmpd);
 
+       if (forceload)
+               job_disabled = false;
+
        if (job_disabled && load) {
                launch_data_free(thejob);
                return;
@@ -371,7 +381,7 @@ static void readfile(const char *what, launch_data_t pass1, launch_data_t pass2,
                launch_data_array_append(pass1, thejob);
 }
 
-static void readpath(const char *what, launch_data_t pass1, launch_data_t pass2, bool editondisk, bool load)
+static void readpath(const char *what, launch_data_t pass1, launch_data_t pass2, bool editondisk, bool load, bool forceload)
 {
        char buf[MAXPATHLEN];
        struct stat sb;
@@ -382,7 +392,7 @@ static void readpath(const char *what, launch_data_t pass1, launch_data_t pass2,
                return;
 
        if (S_ISREG(sb.st_mode) && !(sb.st_mode & S_IWOTH)) {
-               readfile(what, pass1, pass2, editondisk, load);
+               readfile(what, pass1, pass2, editondisk, load, forceload);
        } else {
                if ((d = opendir(what)) == NULL) {
                        fprintf(stderr, "%s: opendir() failed to open the directory\n", getprogname());
@@ -394,7 +404,7 @@ static void readpath(const char *what, launch_data_t pass1, launch_data_t pass2,
                                continue;
                        snprintf(buf, sizeof(buf), "%s/%s", what, de->d_name);
 
-                       readfile(buf, pass1, pass2, editondisk, load);
+                       readfile(buf, pass1, pass2, editondisk, load, forceload);
                }
                closedir(d);
        }
@@ -510,7 +520,7 @@ static void sock_dict_edit_entry(launch_data_t tmp, const char *key, launch_data
                launch_data_array_append(fdarray, val);
        } else {
                launch_data_t rnames = NULL;
-               const char *node = NULL, *serv = NULL;
+               const char *node = NULL, *serv = NULL, *mgroup = NULL;
                char servnbuf[50];
                struct addrinfo hints, *res0, *res;
                int gerr, sock_opt = 1;
@@ -524,6 +534,8 @@ static void sock_dict_edit_entry(launch_data_t tmp, const char *key, launch_data
 
                if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_NODENAME)))
                        node = launch_data_get_string(val);
+               if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_MULTICASTGROUP)))
+                       mgroup = launch_data_get_string(val);
                if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_SERVICENAME))) {
                        if (LAUNCH_DATA_INTEGER == launch_data_get_type(val)) {
                                sprintf(servnbuf, "%lld", launch_data_get_integer(val));
@@ -567,14 +579,25 @@ static void sock_dict_edit_entry(launch_data_t tmp, const char *key, launch_data
                                        fprintf(stderr, "setsockopt(IPV6_V6ONLY): %m");
                                        return;
                                }
-                               if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (void *)&sock_opt, sizeof(sock_opt)) == -1) {
-                                       fprintf(stderr, "socket(): %s\n", strerror(errno));
-                                       return;
+                               if (mgroup) {
+                                       if (setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, (void *)&sock_opt, sizeof(sock_opt)) == -1) {
+                                               fprintf(stderr, "setsockopt(SO_REUSEPORT): %s\n", strerror(errno));
+                                               return;
+                                       }
+                               } else {
+                                       if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (void *)&sock_opt, sizeof(sock_opt)) == -1) {
+                                               fprintf(stderr, "setsockopt(SO_REUSEADDR): %s\n", strerror(errno));
+                                               return;
+                                       }
                                }
                                if (bind(sfd, res->ai_addr, res->ai_addrlen) == -1) {
                                        fprintf(stderr, "bind(): %s\n", strerror(errno));
                                        return;
                                }
+
+                               if (mgroup) {
+                                       do_mgroup_join(sfd, res->ai_family, res->ai_socktype, res->ai_protocol, mgroup);
+                               }
                                if ((res->ai_socktype == SOCK_STREAM || res->ai_socktype == SOCK_SEQPACKET)
                                                && listen(sfd, SOMAXCONN) == -1) {
                                        fprintf(stderr, "listen(): %s\n", strerror(errno));
@@ -624,6 +647,52 @@ static void sock_dict_edit_entry(launch_data_t tmp, const char *key, launch_data
        }
 }
 
+static void do_mgroup_join(int fd, int family, int socktype, int protocol, const char *mgroup)
+{
+       struct addrinfo hints, *res0, *res;
+       struct ip_mreq mreq;
+       struct ipv6_mreq m6req;
+       int gerr;
+
+       memset(&hints, 0, sizeof(hints));
+
+       hints.ai_flags |= AI_PASSIVE;
+       hints.ai_family = family;
+       hints.ai_socktype = socktype;
+       hints.ai_protocol = protocol;
+
+       if ((gerr = getaddrinfo(mgroup, NULL, &hints, &res0)) != 0) {
+               fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(gerr));
+               return;
+       }
+
+       for (res = res0; res; res = res->ai_next) {
+               if (AF_INET == family) {
+                       memset(&mreq, 0, sizeof(mreq));
+                       mreq.imr_multiaddr = ((struct sockaddr_in *)res->ai_addr)->sin_addr;
+                       if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1) {
+                               fprintf(stderr, "setsockopt(IP_ADD_MEMBERSHIP): %s\n", strerror(errno));
+                               continue;
+                       }
+                       break;
+               } else if (AF_INET6 == family) {
+                       memset(&m6req, 0, sizeof(m6req));
+                       m6req.ipv6mr_multiaddr = ((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
+                       if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &m6req, sizeof(m6req)) == -1) {
+                               fprintf(stderr, "setsockopt(IPV6_JOIN_GROUP): %s\n", strerror(errno));
+                               continue;
+                       }
+                       break;
+               } else {
+                       fprintf(stderr, "unknown family during multicast group bind!\n");
+                       break;
+               }
+       }
+
+       freeaddrinfo(res0);
+}
+
+
 static launch_data_t do_rendezvous_magic(const struct addrinfo *res, const char *serv)
 {
        struct stat sb;
@@ -801,17 +870,17 @@ static int load_and_unload_cmd(int argc, char *const argv[])
        int i, ch;
        bool wflag = false;
        bool lflag = false;
+       bool Fflag = false;
 
        if (!strcmp(argv[0], "load"))
                lflag = true;
 
-       while ((ch = getopt(argc, argv, "w")) != -1) {
+       while ((ch = getopt(argc, argv, "wF")) != -1) {
                switch (ch) {
-               case 'w':
-                       wflag = true;
-                       break;
+               case 'w': wflag = true; break;
+               case 'F': Fflag = true; break;
                default:
-                       fprintf(stderr, "usage: %s load [-w] paths...\n", getprogname());
+                       fprintf(stderr, "usage: %s load [-wF] paths...\n", getprogname());
                        return 1;
                }
        }
@@ -835,7 +904,7 @@ static int load_and_unload_cmd(int argc, char *const argv[])
        pass2 = launch_data_alloc(LAUNCH_DATA_ARRAY);
 
        for (i = 0; i < argc; i++)
-               readpath(argv[i], pass1, pass2, wflag, lflag);
+               readpath(argv[i], pass1, pass2, wflag, lflag, Fflag);
 
        if (0 == launch_data_array_get_count(pass1) && 0 == launch_data_array_get_count(pass2)) {
                fprintf(stderr, "nothing found to %s\n", lflag ? "load" : "unload");
index e1d3e518a5b47c21527a4580fd420110f6251dec..5938eae36ed5e36facb2a5cdeb8171b57447fbc3 100644 (file)
@@ -6,6 +6,7 @@
 .Nd System wide and per-user daemon/agent manager
 .Sh SYNOPSIS
 .Nm
+.Op Fl d
 .Op Fl v
 .Op Fl s
 .Op Fl x
@@ -31,6 +32,8 @@ During boot
 is invoked by the kernel to run as the first process on the system and to further bootstrap the rest of the system.
 .Sh OPTIONS WHEN RUN AS PID 1
 .Bl -tag -width -indent
+.It Fl d
+Daemonize. Useful when passing a command to launchd on the command line.
 .It Fl v
 Verbose boot.
 .It Fl s
@@ -40,6 +43,11 @@ to give a shell prompt before booting the system.
 .It Fl x
 Safe mode boot. Instructs the system to boot conservatively.
 .El
+.Sh ENVIRONMENTAL VARIABLES
+.Bl -tag -width -indent
+.It Pa LAUNCHD_SOCKET
+This variable is exported when invoking a command via the launchd command line. It informs launchctl how to find the correct launchd to talk to.
+.El
 .Sh NOTES
 In Darwin it is preferable to have your daemon launch via launchd instead of modifying
 .Nm rc
index 7078394b94d7b8fcc65abaf036c6175488ba5dd7..0eb441be4f007973be8354426122371a406935cb 100644 (file)
@@ -177,6 +177,7 @@ static void unsetup_job_env(launch_data_t obj, const char *key, void *context);
 
 static size_t total_children = 0;
 static pid_t readcfg_pid = 0;
+static pid_t launchd_proper_pid = 0;
 static bool launchd_inited = false;
 static bool shutdown_in_progress = false;
 static pthread_t mach_server_loop_thread;
@@ -421,6 +422,9 @@ static char *sockpath = NULL;
 
 static void launchd_clean_up(void)
 {
+       if (launchd_proper_pid != getpid())
+               return;
+
        seteuid(0);
        setegid(0);
 
@@ -489,9 +493,13 @@ static void launchd_server_init(bool create_session)
                        goto out_bad;
                }
        }
+
        if (chown(ourdir, getuid(), getgid()) == -1)
                syslog(LOG_WARNING, "chown(\"%s\"): %m", ourdir);
 
+       setegid(getgid());
+       seteuid(getuid());
+
        ourdirfd = _fd(open(ourdir, O_RDONLY));
        if (ourdirfd == -1) {
                syslog(LOG_ERR, "open(\"%s\"): %m", ourdir);
@@ -524,8 +532,6 @@ static void launchd_server_init(bool create_session)
                        syslog(LOG_ERR, "bind(\"thesocket\"): %m");
                goto out_bad;
        }
-       if (chown(sun.sun_path, getuid(), getgid()) == -1)
-               syslog(LOG_WARNING, "chown(\"thesocket\"): %m");
 
        if (listen(fd, SOMAXCONN) == -1) {
                syslog(LOG_ERR, "listen(\"thesocket\"): %m");
@@ -542,6 +548,7 @@ static void launchd_server_init(bool create_session)
        sockdir = strdup(ourdir);
        sockpath = strdup(sun.sun_path);
 
+       launchd_proper_pid = getpid();
        atexit(launchd_clean_up);
 
 out_bad:
@@ -807,7 +814,7 @@ static void job_watch(struct jobcb *j)
 
                if (-1 == (dcc_r = dir_has_files(thepath))) {
                        job_log_error(j, LOG_ERR, "dir_has_files(\"%s\", ...)", thepath);
-               } else if (dcc_r > 0) {
+               } else if (dcc_r > 0 && !shutdown_in_progress) {
                        job_start(j);
                        break;
                }
@@ -1065,7 +1072,7 @@ static launch_data_t load_job(launch_data_t pload)
        launch_data_t tmp, resp;
        const char *label;
        struct jobcb *j;
-       bool startnow;
+       bool startnow, hasprog = false, hasprogargs = false;
 
        if ((label = job_get_string(pload, LAUNCH_JOBKEY_LABEL))) {
                TAILQ_FOREACH(j, &jobs, tqe) {
@@ -1078,7 +1085,13 @@ static launch_data_t load_job(launch_data_t pload)
                resp = launch_data_new_errno(EINVAL);
                goto out;
        }
-       if (launch_data_dict_lookup(pload, LAUNCH_JOBKEY_PROGRAMARGUMENTS) == NULL) {
+
+       if (launch_data_dict_lookup(pload, LAUNCH_JOBKEY_PROGRAM))
+               hasprog = true;
+       if (launch_data_dict_lookup(pload, LAUNCH_JOBKEY_PROGRAMARGUMENTS))
+               hasprogargs = true;
+
+       if (!hasprog && !hasprogargs) {
                resp = launch_data_new_errno(EINVAL);
                goto out;
        }
@@ -1133,6 +1146,11 @@ static launch_data_t load_job(launch_data_t pload)
                launch_data_t tmp_k;
 
                j->start_cal_interval = calloc(1, sizeof(struct tm));
+               j->start_cal_interval->tm_min = -1;
+               j->start_cal_interval->tm_hour = -1;
+               j->start_cal_interval->tm_mday = -1;
+               j->start_cal_interval->tm_wday = -1;
+               j->start_cal_interval->tm_mon = -1;
 
                if (LAUNCH_DATA_DICTIONARY == launch_data_get_type(tmp)) {
                        if ((tmp_k = launch_data_dict_lookup(tmp, LAUNCH_JOBKEY_CAL_MINUTE)))
@@ -1141,11 +1159,8 @@ static launch_data_t load_job(launch_data_t pload)
                                j->start_cal_interval->tm_hour = launch_data_get_integer(tmp_k);
                        if ((tmp_k = launch_data_dict_lookup(tmp, LAUNCH_JOBKEY_CAL_DAY)))
                                j->start_cal_interval->tm_mday = launch_data_get_integer(tmp_k);
-                       if ((tmp_k = launch_data_dict_lookup(tmp, LAUNCH_JOBKEY_CAL_WEEKDAY))) {
+                       if ((tmp_k = launch_data_dict_lookup(tmp, LAUNCH_JOBKEY_CAL_WEEKDAY)))
                                j->start_cal_interval->tm_wday = launch_data_get_integer(tmp_k);
-                               if (j->start_cal_interval->tm_wday == 0)
-                                       j->start_cal_interval->tm_wday = 7;
-                       }
                        if ((tmp_k = launch_data_dict_lookup(tmp, LAUNCH_JOBKEY_CAL_MONTH)))
                                j->start_cal_interval->tm_mon = launch_data_get_integer(tmp_k);
                }
@@ -1425,15 +1440,15 @@ static bool job_restart_fitness_test(struct jobcb *j)
                job_log(j, LOG_WARNING, "failed to checkin");
                job_remove(j);
                return false;
+       } else if (j->failed_exits >= LAUNCHD_FAILED_EXITS_THRESHOLD) {
+               job_log(j, LOG_WARNING, "too many failures in succession");
+               job_remove(j);
+               return false;
        } else if (od || shutdown_in_progress) {
                if (!od && shutdown_in_progress)
                        job_log(j, LOG_NOTICE, "exited while shutdown is in progress, will not restart unless demand requires it");
                job_watch(j);
                return false;
-       } else if (j->failed_exits >= LAUNCHD_FAILED_EXITS_THRESHOLD) {
-               job_log(j, LOG_WARNING, "too many failures in a row for a job that should be alive all the time");
-               job_remove(j);
-               return false;
        }
 
        return true;
@@ -1466,8 +1481,8 @@ static void job_callback(void *obj, struct kevent *kev)
                                startnow = false;
                        }
                }
-       } else if (kev->filter == EVFILT_TIMER && kev->fflags & NOTE_ABSOLUTE) {
-                       job_set_alarm(j);
+       } else if (kev->filter == EVFILT_TIMER && (void *)kev->ident == j->start_cal_interval) {
+               job_set_alarm(j);
        } else if (kev->filter == EVFILT_VNODE) {
                size_t i;
                const char *thepath = NULL;
@@ -1624,14 +1639,25 @@ static void job_start_child(struct jobcb *j, int execfd)
        bool inetcompat = job_get_bool(j->ldj, LAUNCH_JOBKEY_INETDCOMPATIBILITY);
        size_t i, argv_cnt;
        const char **argv, *file2exec = "/usr/libexec/launchproxy";
+       int r;
+       bool hasprog = false;
 
        job_setup_attributes(j);
 
-       argv_cnt = launch_data_array_get_count(ldpa);
-       argv = alloca((argv_cnt + 2) * sizeof(char *));
-       for (i = 0; i < argv_cnt; i++)
-               argv[i + 1] = launch_data_get_string(launch_data_array_get_index(ldpa, i));
-       argv[argv_cnt + 1] = NULL;
+       if (ldpa) {
+               argv_cnt = launch_data_array_get_count(ldpa);
+               argv = alloca((argv_cnt + 2) * sizeof(char *));
+               for (i = 0; i < argv_cnt; i++)
+                       argv[i + 1] = launch_data_get_string(launch_data_array_get_index(ldpa, i));
+               argv[argv_cnt + 1] = NULL;
+       } else {
+               argv = alloca(3 * sizeof(char *));
+               argv[1] = job_get_string(j->ldj, LAUNCH_JOBKEY_PROGRAM);
+               argv[2] = NULL;
+       }
+
+       if (job_get_string(j->ldj, LAUNCH_JOBKEY_PROGRAM))
+               hasprog = true;
 
        if (inetcompat) {
                argv[0] = file2exec;
@@ -1640,12 +1666,17 @@ static void job_start_child(struct jobcb *j, int execfd)
                file2exec = job_get_file2exec(j->ldj);
        }
 
-       if (-1 == execvp(file2exec, (char *const*)argv)) {
-               int e = errno; /* errno is a macro that expands, best not to take the address of it */
-               write(execfd, &e, sizeof(e));
-               job_log_error(j, LOG_ERR, "execvp(\"%s\", ...)", file2exec);
+       if (hasprog) {
+               r = execv(file2exec, (char *const*)argv);
+       } else {
+               r = execvp(file2exec, (char *const*)argv);
+       }
+
+       if (-1 == r) {
+               write(execfd, &errno, sizeof(errno));
+               job_log_error(j, LOG_ERR, "execv%s(\"%s\", ...)", hasprog ? "" : "p", file2exec);
        }
-       _exit(EXIT_FAILURE);
+       exit(EXIT_FAILURE);
 }
 
 static void job_setup_attributes(struct jobcb *j)
@@ -1714,11 +1745,11 @@ static void job_setup_attributes(struct jobcb *j)
                        gre_g = gre->gr_gid;
                        if (-1 == setgid(gre_g)) {
                                job_log_error(j, LOG_ERR, "setgid(%d)", gre_g);
-                               _exit(EXIT_FAILURE);
+                               exit(EXIT_FAILURE);
                        }
                } else {
                        job_log(j, LOG_ERR, "getgrnam(\"%s\") failed", tmpstr);
-                       _exit(EXIT_FAILURE);
+                       exit(EXIT_FAILURE);
                }
        }
        if ((tmpstr = job_get_string(j->ldj, LAUNCH_JOBKEY_USERNAME))) {
@@ -1729,27 +1760,27 @@ static void job_setup_attributes(struct jobcb *j)
 
                        if (pwe->pw_expire && time(NULL) >= pwe->pw_expire) {
                                job_log(j, LOG_ERR, "expired account: %s", tmpstr);
-                               _exit(EXIT_FAILURE);
+                               exit(EXIT_FAILURE);
                        }
                        if (job_get_bool(j->ldj, LAUNCH_JOBKEY_INITGROUPS)) {
                                if (-1 == initgroups(tmpstr, gre ? gre_g : pwe_g)) {
                                        job_log_error(j, LOG_ERR, "initgroups()");
-                                       _exit(EXIT_FAILURE);
+                                       exit(EXIT_FAILURE);
                                }
                        }
                        if (!gre) {
                                if (-1 == setgid(pwe_g)) {
                                        job_log_error(j, LOG_ERR, "setgid(%d)", pwe_g);
-                                       _exit(EXIT_FAILURE);
+                                       exit(EXIT_FAILURE);
                                }
                        }
                        if (-1 == setuid(pwe_u)) {
                                job_log_error(j, LOG_ERR, "setuid(%d)", pwe_u);
-                               _exit(EXIT_FAILURE);
+                               exit(EXIT_FAILURE);
                        }
                } else {
                        job_log(j, LOG_WARNING, "getpwnam(\"%s\") failed", tmpstr);
-                       _exit(EXIT_FAILURE);
+                       exit(EXIT_FAILURE);
                }
        }
        if ((tmpstr = job_get_string(j->ldj, LAUNCH_JOBKEY_WORKINGDIRECTORY)))
@@ -2001,13 +2032,13 @@ static void reload_launchd_config(void)
                        int fd = open(ldconf, O_RDONLY);
                        if (fd == -1) {
                                syslog(LOG_ERR, "open(\"%s\"): %m", ldconf);
-                               _exit(EXIT_FAILURE);
+                               exit(EXIT_FAILURE);
                        }
                        dup2(fd, STDIN_FILENO);
                        close(fd);
                        execl(LAUNCHCTL_PATH, LAUNCHCTL_PATH, NULL);
                        syslog(LOG_ERR, "execl(\"%s\", ...): %m", LAUNCHCTL_PATH);
-                       _exit(EXIT_FAILURE);
+                       exit(EXIT_FAILURE);
                } else if (readcfg_pid == -1) {
                        close(spair[0]);
                        close(spair[1]);
@@ -2256,20 +2287,20 @@ static void job_set_alarm(struct jobcb *j)
        latertm.tm_isdst = -1;
 
 
-       if (j->start_cal_interval->tm_min)
+       if (-1 != j->start_cal_interval->tm_min)
                latertm.tm_min = j->start_cal_interval->tm_min;
-       if (j->start_cal_interval->tm_hour)
+       if (-1 != j->start_cal_interval->tm_hour)
                latertm.tm_hour = j->start_cal_interval->tm_hour;
 
        otherlatertm = latertm;
 
-       if (j->start_cal_interval->tm_mday)
+       if (-1 != j->start_cal_interval->tm_mday)
                latertm.tm_mday = j->start_cal_interval->tm_mday;
-       if (j->start_cal_interval->tm_mon)
+       if (-1 != j->start_cal_interval->tm_mon)
                latertm.tm_mon = j->start_cal_interval->tm_mon;
 
        /* cron semantics are fun */
-       if (j->start_cal_interval->tm_wday) {
+       if (-1 != j->start_cal_interval->tm_wday) {
                int delta, realwday = j->start_cal_interval->tm_wday;
 
                if (realwday == 7)
@@ -2287,9 +2318,9 @@ static void job_set_alarm(struct jobcb *j)
                        otherlatertm.tm_mday += delta;
                else if (delta < 0)
                        otherlatertm.tm_mday += 7 + delta;
-               else if (otherlatertm.tm_hour < nowtm->tm_hour)
+               else if (-1 != j->start_cal_interval->tm_hour && otherlatertm.tm_hour <= nowtm->tm_hour)
                        otherlatertm.tm_mday += 7;
-               else if (otherlatertm.tm_min < nowtm->tm_min)
+               else if (-1 != j->start_cal_interval->tm_min && otherlatertm.tm_min <= nowtm->tm_min)
                        otherlatertm.tm_hour++;
                else
                        otherlatertm.tm_min++;
@@ -2297,13 +2328,13 @@ static void job_set_alarm(struct jobcb *j)
                otherlater = mktime(&otherlatertm);
        }
 
-       if (latertm.tm_mon < nowtm->tm_mon) {
+       if (-1 != j->start_cal_interval->tm_mon && latertm.tm_mon <= nowtm->tm_mon) {
                latertm.tm_year++;
-       } else if (latertm.tm_mday < nowtm->tm_mday) {
+       } else if (-1 != j->start_cal_interval->tm_mday && latertm.tm_mday <= nowtm->tm_mday) {
                latertm.tm_mon++;
-       } else if (latertm.tm_hour < nowtm->tm_hour) {
+       } else if (-1 != j->start_cal_interval->tm_hour && latertm.tm_hour <= nowtm->tm_hour) {
                latertm.tm_mday++;
-       } else if (latertm.tm_min < nowtm->tm_min) {
+       } else if (-1 != j->start_cal_interval->tm_min && latertm.tm_min <= nowtm->tm_min) {
                latertm.tm_hour++;
        } else {
                latertm.tm_min++;
@@ -2312,7 +2343,7 @@ static void job_set_alarm(struct jobcb *j)
        later = mktime(&latertm);
 
        if (otherlater) {
-               if (j->start_cal_interval->tm_mday)
+               if (-1 != j->start_cal_interval->tm_mday)
                        later = later < otherlater ? later : otherlater;
                else
                        later = otherlater;
index 96844de9da1529dafb932a74414e69355e7e0293..e9ab97af9422ee2617e4b34acd4adbddba615685 100644 (file)
@@ -91,6 +91,7 @@ This optional key specifies the user to run the job as. The default is the group
 .It Sy inetdCompatibility <dictionary>
 The presence of this key specifies that the daemon expects to be run as if it were launched from
 .Xr inetd 8 .
+This flag is incompatible with the ServiceIPC key.
 .Bl -ohang -offset indent
 .It Sy Wait <boolean>
 This flag corresponds to the "wait" or "nowait" option of
@@ -126,7 +127,7 @@ before running the job.
 .It Sy ServiceIPC <boolean>
 This optional key specifies whether the job participates in advanced communication with
 .Nm launchd .
-The default is false.
+The default is false. This flag is incompatible with the inetdCompatibility key.
 .It Sy TimeOut <integer>
 The recommended time out to pass to the job. If no value is specified, a default time out will be supplied by
 .Nm launchd
@@ -134,7 +135,7 @@ for use by the job at check in time.
 .It Sy InitGroups <boolean>
 This optional key specifies whether the job should have
 .Xr initgroups 3
-should be called before running the job.
+be called before running the job.
 The default is false.
 .It Sy WatchPaths <array of strings>
 This optional key causes the job to be started if any one of the listed paths are modified.
@@ -142,10 +143,16 @@ This optional key causes the job to be started if any one of the listed paths ar
 Much like the WatchPaths option, this key will watch the paths for modifications. The difference being that the job will only be started if the path is a directory and the directory is not empty.
 .It Sy StartInterval <integer>
 This optional key causes the job to be started every N seconds.
+If the system is asleep, the job will be started the next time the computer
+wakes up.  If multiple intervals transpire before the computer is woken, those
+events will be coalesced into one event upon wake from sleep.
 .It Sy StartCalendarInterval <dictionary of integers>
 This optional key causes the job to be started every calendar interval as specified. Missing arguments are considered to be wildcard. The semantics are much like
-.Xr crontab 5
-.
+.Xr crontab 5 .
+Unlike cron which skips job invocations when the computer is asleep, launchd
+will start the job the next time the computer wakes up.  If multiple intervals
+transpire before the computer is woken, those events will be coalesced into one
+event upon wake from sleep.
 .Bl -ohang -offset indent
 .It Sy Minute <integer>
 The minute on which this job will be run.
@@ -168,7 +175,6 @@ This optional key specifies what file should be used for data being sent to stde
 This optional key specifies that
 .Nm launchd
 should adjust its log mask temporarily to LOG_DEBUG while dealing with this job.
-.Xr stdio 3 .
 .It Sy SoftResourceLimits <dictionary of integers>
 .It Sy HardResourceLimits <dictionary of integers>
 Resource limits to be imposed on the job. These adjust variables set with
@@ -209,11 +215,17 @@ This optional key specifies what
 value should be applied to the daemon.
 .It Sy LowPriorityIO <boolean>
 This optional key specifies whether the kernel should consider this daemon to be low priority when doing file system I/O.
-.It Sy Sockets <dictionary of dictionaries...>
+.It Sy Sockets <dictionary of dictionaries... OR dictionary of array of dictionaries...>
 This optional key is used to specify launch on demand sockets that can be used to let
 .Nm launchd
 know when to run the job. The job can check-in and get a copy of the file descriptors using APIs outlined in
 .Xr launch 3 .
+The keys of the top level Sockets dictionary can be anything. They are meant for the application developer to use to
+differentiate different which descriptors correspond to which application level protocols (e.g. http vs. ftp vs. DNS...).
+At check in time, the value of each Sockets dictionary key will be an array of descriptors. Daemon/Agent writers should
+consider all descriptors of a given key to be to be effectively equivalent, even though each file descriptor likely represents
+a different networking protocol which conforms to the criteria specified in the job configuration file.
+.Pp
 The paramters below are used as inputs to call
 .Xr getaddrinfo 3 .
 .Bl -ohang -offset indent
@@ -256,6 +268,12 @@ to.
 This optional key can be used to request that the service be registered with the
 .Xr mDNSResponder 8 .
 If the value is boolean, the service name is inferred from the SockServiceName.
+.It Sy MulticastGroup <string>
+This optional key can be used to request that the datagram socket join a multicast group.
+If the value is a hostname, then
+.Xr getaddrinfo 3
+will be used to join the correct multicast address for a given socket family.
+If an explicit IPv4 or IPv6 address is given, it is required that the SockFamily family also be set, otherwise the results are undefined.
 .El
 .El
 .Pp
diff --git a/launchd/testing/StartCalendarInterval.plist b/launchd/testing/StartCalendarInterval.plist
new file mode 100644 (file)
index 0000000..503a4b3
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+    <key>Label</key>
+    <string>com.apple.launchd.test.StartCalendarInterval</string>
+    <key>ProgramArguments</key>
+    <array>
+           <string>echo</string>
+           <string>StartCalendarInterval</string>
+    </array>
+    <key>StartCalendarInterval</key>
+    <dict>
+    </dict>
+</dict>
+</plist>