
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
|