Launch Daemons/Agents in OS X

From AFP548 Wiki
Jump to navigation Jump to search

The following article goes over what Launch Daemons and Agents are. Examples are provided along with actual launchd jobs.

LaunchDaemon and LaunchAgents[edit]

Daemon[edit]

A daemon is a program that runs in the background as part of the overall system (that is, it is not tied to a particular user). A daemon cannot display any GUI; more specifically, it is not allowed to connect to the window server. A web server is the perfect example of a daemon.

Historically, Mac OS X had a number of different ways to start daemons (for details, see Deprecated Daemonomicon). On current systems there is only one recommend way: launchd.

A launchd daemon is configured by a sophisticated property list file. This file allows the daemon to be launched based on a variety of criteria (connections to listening sockets, items being modified in the file system, periodically, and so on). For details, see the launchd.plist man page.

A third party launchd daemon should be installed by adding a property list file to the /Library/LaunchDaemons directory.

Agent[edit]

An agent is a process that runs in the background on behalf of a particular user. Agents are useful because they can do things that daemons can't, like reliably access the user's home directory or connect to the window server. A calendar monitoring program is a good example of an agent because:

  • there should be an instance of the program per GUI login session
  • each agent has access to that user's calendar
  • the agent can launch the calendar application when certain events occur

The difference between an agent and a daemon is that an agent can display GUI if it wants to, while a daemon can't. The difference between an agent and a regular application is that an agent typically displays no GUI (or a very limited GUI).

A launchd agent is like a launchd daemon, except that it runs on behalf of a particular user. It is launched by launchd, typically as part of the process of logging in the user.

A third party launchd agent should be installed by adding a property list file to the ~/Library/LaunchAgents directory (to be invoked just for this user) or /Library/LaunchAgents directory (to be invoked for all users).

Creating Launch Daemons and Agents[edit]

If you are developing daemons to run on OS X, it is highly recommended that you design your daemons to be launchd compliant. Using launchd provides better performance and flexibility for daemons. It also improves the ability of administrators to manage the daemons running on a given system.

If you are running per-user background processes for OS X, launchd is also the preferred way to start these processes. These per-user processes are referred to as user agents. A user agent is essentially identical to a daemon, but is specific to a given logged-in user and executes only while that user is logged in.

The launchd Startup Process[edit]

After the system is booted and the kernel is running, launchd is run to finish the system initialization. As part of that initialization, it goes through the following steps:

  1. It loads the parameters for each launch-on-demand system-level daemon from the property list files found in /System/Library/LaunchDaemons/ and /Library/LaunchDaemons/.
  2. It registers the sockets and file descriptors requested by those daemons.
  3. It launches any daemons that requested to be running all the time.
  4. As requests for a particular service arrive, it launches the corresponding daemon and passes the request to it.
  5. When the system shuts down, it sends a SIGTERM signal to all of the daemons that it started.

The process for per-user agents is similar. When a user logs in, a per-user launchd is started. It does the following:

  1. It loads the parameters for each launch-on-demand user agent from the property list files found in /System/Library/LaunchAgents, /Library/LaunchAgents, and the user’s individual Library/LaunchAgents directory.
  2. It registers the sockets and file descriptors requested by those user agents.
  3. It launches any user agents that requested to be running all the time.
  4. As requests for a particular service arrive, it launches the corresponding user agent and passes the request to it.
  5. When the user logs out, it sends a SIGTERM signal to all of the user agents that it started.

Because launchd registers the sockets and file descriptors used by all daemons before it launches any of them, daemons can be launched in any order. If a request comes in for a daemon that is not yet running, the requesting process is suspended until the target daemon finishes launching and responds.

If a daemon does not receive any requests over a specific period of time, it can choose to shut itself down and release the resources it holds. When this happens, launchd monitors the shutdown and makes a note to launch the daemon again when future requests arrive.

Important: If your daemon shuts down too quickly after being launched, launchd may think it has crashed. Daemons that continue this behavior may be suspended and not launched again when future requests arrive. To avoid this behavior, do not shut down for at least 10 seconds after launch.

Creating a launchd property list file[edit]

To run under launchd, you must provide a configuration property list file for your daemon. This file contains information about your daemon, including the list of sockets or file descriptors it uses to process requests. Specifying this information in a property list file lets launchd register the corresponding file descriptors and launch your daemon only after a request arrives for your daemon’s services.

The property list file is structured the same for both daemons and agents. You indicate whether it describes a daemon or agent by the directory you place it in. Property list files describing daemons are installed in /Library/LaunchDaemons, and those describing agents are installed in /Library/LaunchAgents or in the LaunchAgents subdirectory of an individual user’s Library directory ~/Library/LaunchAgents. (The appropriate location for executables that you launch from your job is /usr/local/libexec.)

Key Description
Label Contains a unique string that identifies your daemon to launchd. (required)
ProgramArguments Contains the arguments used to launch your daemon. (required)
inetdCompatibility Indicates that your daemon requires a separate instance per incoming connection. This causes launchd to behave like inetd, passing each daemon a single socket that is already connected to the incoming client. (required if your daemon was designed to be launched by inetd; otherwise, must not be included)
KeepAlive This key specifies whether your daemon launches on-demand or must always be running. It is recommended that you design your daemon to be launched on-demand.

Please read the man page on launchd.plist (type man launchd.plist into Terminal) to find out what parameters can be used in a Property List file.

Sample launchd job[edit]

Writing a “Hello World!” launchd Job

The following simple example launches a daemon named hello, passing world as a single argument, and instructs launchd to keep the job running:

   <?xml version="1.0" encoding="UTF-8"?>
   <!DOCTYPE plist PUBLIC "-//Apple//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.hello</string>
       <key>ProgramArguments</key>
       <array>
           <string>hello</string>
           <string>world</string>
       </array>
       <key>KeepAlive</key>
       <true/>
   </dict>
   </plist>

In this example, there are three keys in the top level dictionary. The first is Label, which uniquely identifies the job. when. The second is ProgramArguments which has a value of an array of strings which represent the tokenized arguments and the program to run. The third and final key is KeepAlive which indicates that this job needs to be running at all times, rather than the default launch-on-demand behavior, so launchd should always try to keep this job running.

Running a Job Periodically[edit]

The following example creates a job that is run every five minutes (300 seconds):

   <?xml version="1.0" encoding="UTF-8"?>
   <!DOCTYPE plist PUBLIC "-//Apple//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>

Alternately, you can specify a calendar-based interval. The following example starts the job on the 7th day of every month at 13:45 (1:45 pm). Like the Unix cron subsystem, any missing key of the StartCalendarInterval dictionary is treated as a wildcard—in this case, the month is omitted, so the job is run every month.

   <?xml version="1.0" encoding="UTF-8"?>
   <!DOCTYPE plist PUBLIC "-//Apple//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>45</integer>
           <key>Hour</key>
           <integer>13</integer>
           <key>Day</key>
           <integer>7</integer>
       </dict>
   </dict>
   </plist>

Monitoring a Directory[edit]

The following example starts the job whenever any of the paths being watched have changed:

   <?xml version="1.0" encoding="UTF-8"?>
   <!DOCTYPE plist PUBLIC "-//Apple//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.watchhostconfig</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>
   </plist>

An additional file system trigger is the notion of a queue directory. The launchd daemon starts your job whenever the given directories are non-empty, and it keeps your job running as long as those directories are not empty:

   <?xml version="1.0" encoding="UTF-8"?>
   <!DOCTYPE plist PUBLIC "-//Apple//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>
   </plist>

Examples[edit]

Mounting Network Share in a Lab[edit]

This particular launchdaemon is added under /Library/LaunchAgents/com.companyname.mountCADlabshare and runs a script that's located in the directory /Library/Scripts/Company/mountLabShare.sh. The daemon was created for a Lab because a professors wanted their students to save their documents to a network share called //server/shares/Lab. The Macs on this environment are using Active Directory for authentication to log in.

com.company.mountlabshare

   <?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.company.mountlabshare</string>
       <key>OnDemand</key>
           <false/>
       <key>ProgramArguments</key>
       <array>
           <string>/Library/Scripts/Company/mountLabShare.sh</string>
       </array>
       <key>RunAtLoad</key>
           <true/>
   </dict>
   </plist>

mountLabShare.sh
This script checks to make sure there is no Lab folder in the /tmp folder. If there is, the script exits. If there isn't, the script creates the folder /tmp/Lab. It then mounts the share //server/shares/Lab to the mount point of /tmp/Lab. The reason that /tmp was used to create a mount point is that /tmp gets emptied out every reboot by the operating system (it's literally just a location for temporary storage of folders/files) which in turn automatically unmounts the network share. Keep in mind that in the context of a launch daemon, this job is constantly running in the background so as soon as the directory /tmp/Lab no longer exists, it will always re-create the folder and mount the share. However, if you manually unmount the share, the folder /tmp/Lab will still exist and the script will continue to exit because it does not know any other way to distinguish whether the network share is mounted or not. This is a problem that can be remedied by restarting the machine as it will automatically empty out the /tmp folder. Additionally you can create a Logout Hook that would unmount the share as well using a command like this: /sbin/umount -t smbfs /tmp/Lab

   #!/bin/sh
   if [ -d "/tmp/CADLab" ]
       then
       exit 0
   fi
   /bin/mkdir /tmp/CADLab
   /sbin/mount -t smbfs //server/shares/Lab /tmp/Lab
   exit 0

References[edit]