Launchd is rather simple actually. Far simpler than you might think.
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.
For the simplest of scenarios, launchd just keeps a process alive. A simple "hello world" example of that would be:
<?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.example.sleep</string> <key>ProgramArguments</key> <array> <string>sleep</string> <string>100</string> </array> <key>OnDemand</key> <false/> </dict> </plist>
In the above example, we have three keys to our top level dictionary. The first
is the Label
which is what is used to uniquely identify jobs when interacting
with launchd. The second is ProgramArguments
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 OnDemand
which overrides the default value of
true with false thereby instructing launchd to always try and keep this job
running. That's it! A Label
, some ProgramArguments
and OnDemand
set to false is all
you need to keep a daemon alive with launchd!
Now if you've ever written a daemon before, you've either called the
daemon()
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.
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:
<?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.example.sleep</string> <key>ProgramArguments</key> <array> <string>sleep</string> <string>100</string> </array> <key>OnDemand</key> <false/> <key>UserName</key> <string>daemon</string> <key>GroupName</key> <string>daemon</string> <key>EnvironmentVariables</key> <dict> <key>FOO</key> <string>bar</string> </dict> </dict> </plist>
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.
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):
<?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.example.sleep</string> <key>ProgramArguments</key> <array> <string>sleep</string> <string>100</string> </array> <key>OnDemand</key> <false/> <key>StandardOutPath</key> <string>/var/log/myjob.log</string> <key>StandardErrorPath</key> <string>/var/log/myjob.log</string> <key>Debug</key> <true/> <key>SoftResourceLimits</key> <dict> <key>Core</key> <integer>9223372036854775807</integer> </dict> <key>HardResourceLimits</key> <dict> <key>Core</key> <integer>9223372036854775807</integer> </dict> </dict> </plist>
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.
Here is an example on how to have a job start every five minutes (300 seconds):
<?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.example.touchsomefile</string> <key>ProgramArguments</key> <array> <string>touch</string> <string>/tmp/helloworld</string> </array> <key>StartInterval</key> <integer>300</integer> </dict> </plist>
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 StartCalendarInterval
dictionary is treated as a wildcard:
<?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.example.touchsomefile</string> <key>ProgramArguments</key> <array> <string>touch</string> <string>/tmp/helloworld</string> </array> <key>StartCalendarInterval</key> <dict> <key>Minute</key> <integer>11</integer> <key>Hour</key> <integer>11</integer> <key>Day</key> <integer>11</integer> </dict> </dict>
The following example will start the job whenever any of the paths being watched change for whatever reason:
<?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.example.watchetchostconfig</string> <key>ProgramArguments</key> <array> <string>syslog</string> <string>-s</string> <string>-l</string> <string>notice</string> <string>somebody touched /etc/hostconfig</string> </array> <key>WatchPaths</key> <array> <string>/etc/hostconfig</string> </array> </dict>
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:
<?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.example.mailpush</string> <key>ProgramArguments</key> <array> <string>my_custom_mail_push_tool</string> </array> <key>QueueDirectories</key> <array> <string>/var/spool/mymailqdir</string> </array> </dict>
Launchd will happily emulate inetd style daemon semantics:
<?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.example.telnetd</string> <key>ProgramArguments</key> <array> <string>/usr/libexec/telnetd</string> </array> <key>inetdCompatibility</key> <dict> <key>Wait</key> <false/> </dict> <key>Sockets</key> <dict> <key>Listeners</key> <dict> <key>SockServiceName</key> <string>telnet</string> <key>SockType</key> <string>stream</string> </dict> </dict> </dict>