Getting Started With Launchd

Launchd is rather simple actually. Far simpler than you might think.

Before We Get Started

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.

The basics:

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.

Going beyond the basics with optional keys:

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.

Debugging tricks:

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>

But what if I don't want or expect my job to run continuously?

The basics of on demand launching:

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.

Starting a job periodically:

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>

Starting a job based on file system activity:

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>

Inetd Emulation

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>

TBD