Apple Developer Connection
Advanced Search
Member Login Log In | Not a Member? Contact ADC

Getting Started with launchd

In Mac OS X v10.4 Tiger, Apple introduced a new system startup program called launchd. The launchd daemon takes over many tasks from cron, xinetd, mach_init, and init, which are UNIX programs that traditionally have handled system initialization, called systems scripts, run startup items, and generally prepared the system for the user. And they still exist on Mac OS X Tiger, but launchd has superseded them in many instances. These venerable programs are widely used by system administrators, open source developers, managers of web services, even consumers who want to use cron to manage iCal scheduling, and they can still be called with launchd.

The launchd daemon also provides a big performance boost to your system. At any given time, only those daemons that are actually used are launched; combined with the fact that daemons can shut themselves down and be relaunched as needed means that you can reduce the average memory footprint of the system.

This article gives a quick overview of why launchd was needed, what it does, and then focuses on how to migrate your configuration files from cron, xinetd, mach_init, or init to a system using launchd.

Some background—why launchd?

The first process on a UNIX system has been init for a very long time. The init program has evolved in different directions on different systems. On BSD systems, init generally runs the startup scripts, then does nothing but launch getty as needed and reap orphaned processes when they terminate. On System V and Linux systems, init maintains system run levels, launching and killing other services as needed, and may run other services, such as getty or xdm. Because init predates widespread networking support, though, network services are not normally run through init; likewise, cron was developed to run periodic jobs long after init's basic form had been established.

As a result of this, processes which are not directly initiated by users might be launched from at least three different places, and configured in at least three different ways. The init program's configuration file (/etc/ttys on BSD, /etc/inittab on System V or Linux) has a unique configuration format. System V init had a convention for shell scripts to start and stop services, added on top of any other configuration file formats. cron jobs may be run either out of the system crontab file (/etc/crontab) or per-user crontabs (generally found in /var/cron/tabs or /var/spool/cron/tabs). The inetd daemon has its own configuration file format, used to specify network services. The mach_init process also had its own configuration file format.

Worse yet, the limitations of these programs can cause users to write their own. There are thousands of shell scripts designed to launch a daemon or keep it running because a non-root user can't set up jobs that listen on ports without access to inetd's configuration files.

What launchd offers

The launchd daemon offers a single, standardized, interface to any and all programs started automatically by the system. Furthermore, the configuration files that determine when to run a given program can also specify resource limits and environment variables, which simplifies setup and security for many programs. The same configuration file format is used whether a job is launched once at system startup or user login, on demand over the network, or at intervals.

It is also possible to run additional copies of launchd, most often run by a non-root user. When non-root users load jobs, the launchd daemon handling their jobs runs with their non-root privileges, giving an extra layer of security.

Benefits of launchctl

One of the key weaknesses of cron, inetd, and init has been the limited primary control interface. Your options in controlling them are to start them, stop them, or send them a signal indicating that it's time to reload all the configuration files.

The launchctl program allows individual jobs to be started or stopped, but also allows other interactions with launchd. A specific job can be stopped or started without affecting others. Jobs can always be restarted, and resource limits can be set for future jobs. A single job can be added to the system without any interruption of existing services.

Arguments given to the load and unload subcommands can be either individual job files, or directories containing job files.

If no command line arguments are given to launchctl, it reads commands from standard input. If standard input is a terminal, it even offers a prompt. This offers a way to poke around and see what launchd is doing.

Migration

There are five major ways that services have been started in the past: network services, from xinetd; regularly scheduled services, from cron; boot-time services, from rc/rc.local or init.d, or as StartupItems. Login-time items might also be started as StartupItems, or found in a user's .profile or .xinitrc. (These last two are essentially equivalent to rc.local items.) Some core system services were run directly from mach_init, which was process ID 2 on pre-launchd systems. (The configuration files in /etc/mach_init.d are not appreciably changed, and all of them have already been migrated in Mac OS X 10.4, so there's no guidance here on migrating them.)

All of these should, ideally, be migrated to launchd. They are still supported in Mac OS X Tiger, so you can leave them alone, but you should not use any of these methods for new services. The rc script is still run, and SystemStarter still makes a pass through looking for StartupItems. Both cron and xinetd will be run by launchd, if there are any configuration files for them. However, the launchd mission of putting all your configuration files in one place is thwarted if you do this; it is better to migrate configuration files.

Migrating from /etc/rc

There are two common cases for commands run from /etc/rc (or rc.local). In one, a command is simply run once at system startup. This corresponds to the RunAtLoad key in a launchd job file. The other case is a daemon which waits for connections. In most cases, it is better to switch the daemon to run on-demand, using the standard mechanisms for daemons handling incoming connections. If the daemon just needs to stay running, though, you can just spawn it using RunAtLoad. As an example, the sshd daemon, which is run from /etc/rc on many systems, is run on demand from launchd; by contrast, the Apache web server would most likely be spawned using RunAtLoad, and do its own process management.

The property list for a job run at system boot is very simple:

Listing 1: System Boot Info.plist

	<?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>localhost.etc.rc.command</string>
		<key>ProgramArguments</key>
		<array>
			<string>/path/to/daemon</string>
			<string>argument</string>
		</array>
		<key>RunAtLoad</key>
		<true/>
	</dict>
	</plist>

Migrating from StartupItems

Many services are configured using StartupItems for historical reasons. Some, however, are run this way because they have interdependencies. These jobs cannot be directly translated to run under launchd, which does not provide for explicit dependencies. Instead, services are supposed to wait for needed resources to become available, which handles the majority of cases cleanly. Secondly, some services need an explicit shutdown procedure, which is handled by the StopService() function in a StartupItem script.

Services which don't have these requirements can generally be converted directly using the RunAtLoad key to indicate a command to be run a single time at startup. As with modifications to /etc/rc, in some cases a change in daemon design or configuration is more appropriate.

In the long run, Apple recommends designing daemons to not depend on the order in which they are started. Programs should be robust in the case where a service is unavailable, and in some cases, programs should be automatically spawned when needed instead of requiring programs to wait for them.

Migrating from xinetd

The main thing to keep in mind when migrating from xinetd is the InetdCompatibility key. Most of the service restrictions traditionally imposed from xinetd can be implemented through launchd. The most important exception is the only_from field in xinetd.conf files, which launchd doesn't implement. If you need to restrict the source addresses of internet service connections, use the standard Mac OS X Firewall service. Services run by xinetd are always run on demand.

The following xinetd and launchd configuration files are equivalent. The InitGroups key in the launchd configuration file corresponds to the groups key in the xinetd file.

Listing 2: xinetd Configuration File

	service shell
	{
		disable         = yes
		socket_type     = stream
		wait            = no
		user            = root
		server          = /usr/libexec/rshd
		groups          = yes
		flags           = REUSE
	}

Listing 3: launchd Configuration File

	<?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>Disabled</key>
		<true/>
		<key>Label</key>
		<string>com.apple.rshd</string>
		<key>ProgramArguments</key>
		<array>
			<string>/usr/libexec/rshd</string>
		</array>
		<key>inetdCompatibility</key>
		<dict>
			<key>Wait</key>
			<false/>
		</dict>
		<key>InitGroups</key>
		<true/>
		<key>Sockets</key>
		<dict>
			<key>Listeners</key>
			<dict>
				<key>SockServiceName</key>
				<string>shell</string>
			</dict>
		</dict>
	</dict>
	</plist>

Migrating from cron

cron jobs are generally best handled with the StartInterval or StartCalendarInterval keys. StartInterval is used for fixed time intervals; for instance, a job running every five minutes. The StartCalendarInterval key can be used to specify regularly occurring times on a weekly or monthly basis, or that run at specific hours every day.

In some cases, a job run periodically from cron is really monitoring a directory or file; the QueueDirectories or WatchPaths keys are better ways to approach this.

The following configuration file runs a program every day at 3:15 AM:

Listing 4: Setting a Daily File


	<?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.periodic-daily</string>
		<key>ProgramArguments</key>
		<array> 
			<string>/usr/sbin/periodic</string>
			<string>daily</string>
		</array>
		<key>LowPriorityIO</key>
		<true/>
		<key>Nice</key>
		<integer>1</integer>
		<key>StartCalendarInterval</key>
		<dict>  
			<key>Hour</key>
			<integer>3</integer>
			<key>Minute</key>
			<integer>15</integer>
		</dict>
	</dict>
	</plist>

Migrating from init.d

While Mac OS X has never used System V runlevels, some programs developed on other systems may have init.dscripts. Like StartupItems, these scripts have hooks for multiple functions, such as stopping or restarting a service. Much of this functionality is unnecessary with launchd; since launchd maintains direct control over the jobs it launches, you don't need to write special code for the most common case, where the shutdown procedure is to look up a PID in a PID file and send it a SIGTERM. If this is not sufficient for your job, you need to modify your program or file an enhancement request against launchd with Apple. Whenever possible, catch SIGTERM and clean up quickly.

Past all the additional features and potential complexity of init.d items, they are essentially equivalent to lines in /etc/rc; they start a program at boot. Use the RunAtLoad key to start the job. As with /etc/rc, in some cases it's better to change to an on-demand model.

Creating and running a launchd job

Whether you're migrating a job to launchd from some other system job launcher, or you're setting up a job for a new service, there are a few basic requirements for the job.

Jobs run from launchd should not duplicate launchd functionality; for instance, they should not use chroot(2). Furthermore, they should not do the things normally required of daemon processes, such as detaching from the terminal they are initially attached to. The only things that are strictly prohibited, however, are fork()/exit() combinations (including indirect methods, such as the daemon(3) library call). A server which attempts to run itself as a daemon in this way will seem to have finished running, potentially leading to launchd respawning it, or disabling the service. As launchd does not get stalled waiting for a child that hasn't yet exited, it's not necessary to try to prevent it.

You cannot specify dependencies and ordering for launchd jobs; instead, design daemons to wait for needed resources, or trigger them automatically. For instance, on a Linux system, you must specify that the portmap service is launched before the NFS server. The Mac OS X approach is to have portmap spawned automatically when a server tries to register with it.

If your job needs to run even when no users are logged in, put it in /Library/LaunchDaemons. If it is only useful when users are logged in, put it in /Library/LaunchAgents, or in the personal LaunchAgents directories of specific users. Do not put your job in /System/Library, which is reserved for system-provided daemons.

If your job has the Disabled key, loading it will not actually run it. The launchctl load command takes an optional -w option to remove the Disabled key before starting the job. (This modifies the .plist file.)

How to use launchd effectively

While launchd offers a broad variety of new features to allow for efficient system administration, it's easy to use it poorly. You can, as they say, write bad code in any language.

If you are designing a program to run as a service, you have many options. Sincelaunchd can run services on demand, your program does not need to run all the time and starts only when there's work to do. If you are about to write a program which will scan a directory periodically for files, stop; launchd already does that, and probably does it better. Services can be launched on access to a network port, on changes to files, on creation of files in a particular directory, on user login, or on a periodic basis. The majority of programs which wait for work to do are waiting for one of these events. Let launchd do the work.

If you are installing a new job, do not make the user reboot or log out and back in to start the job. You can use launchctl to load your new job right away, without affecting other services or inconveniencing the user. There is no reason to interrupt the user's workflow to start a job.

Not every job can be run easily from launchd; for instance, the Apache web server is designed to do its own process management, and making it work cleanly with launchd would be difficult. Most jobs can be made to run well from launchd, but some will need special care to fit the launchd model.

Namespace Clashes

While launchd offers some protection from namespace clashes, it's still a bad idea to give all your jobs names like "server" or "daemon". Apple suggests the use of the reverse-DNS naming convention; for instance, com.apple.syslogd.plist. This is an excellent convention, and you should follow it when defining your own services. Use the same names for your job labels that you do for the files; it will be easier to match them up. The job stored in com.apple.syslogd.plist has the tag com.apple.syslogd.

Environment and Limits

Don't go overboard setting limits, but keep in mind limits that might be beneficial to the user. Limits can be there for security reasons or for performance reasons. If you are absolutely sure that your job can never need more than a small amount of memory, a memory limit will protect the user against bugs.

Run your job with the least privilege possible, and avoid using root privileges unless you really need them. If you need some privileges, such as access to a specific file, see whether a better choice of group can give access to that file. Since launchd is only available on Mac OS X v10.4 and later, you can also consider using ACLs to give narrowly restricted access to a file. Similarly, if your job can run in a chroot(2) environment, consider specifying a RootDirectory key in your job.

Of particular interest is that launchd can run a job as a non-root user, but still bind it to a privileged port. This removes one common reason to run daemons as root.

Testing

You can test a launchd job before you put it in the LaunchDaemons or LaunchAgents directory. Testing allows you to verify that the job is going to do what you think it does.

If you are at all concerned about a job going wild, you can create a new instance of launchd. Run the command launchd bash. You are now in a shell run by a new instance of launchd, and any launchctl commands you give will go to that new instance of launchd. Each instance of launchd has a directory in /var/launchd, identified by the uid or by the uid followed by a pid number, containing a socket for communicating with it. The environment variable LAUNCHD_SOCKET, when set to the full path of one of these socket files, specifies a specific instance of launchd to communicate with.

For More Information

Posted: 2005-08-22