Launchctl 2.0 Syntax

Loading and Unloading Daemon/Agents

In 10.9 and older, if you wanted to load launchagent the command would look like:

launchctl load /Library/LaunchAgents/com.company.launchagent.plist

And to unload it:

launchctl unload /Library/LaunchAgents/com.company.launchagent.plist

An assumption is made about how the launchagent is being loaded. For starters, it assumes the user loading the agent is the user in the shell environment. It might also make assumptions about whether the user is logged in a GUI.

To better illustrate this, get on a computer which has 2+ users on it. Log into one account normally via the GUI. Open up Terminal, and switch the account (su -l username). If you have a computer which has multiple users and you switch the user in Terminal for one that is NOT logged in, you will get an error when running the above commands.

So what if you want to load the agent under a different user account? Previously you could rely on the following sub-commands:

bsexec PID command [args]
This executes the given command in as similar an execution context as possible to the target PID. Adopted attributes include the Mach bootstrap namespace, exception server and security audit session. It does not modify the process’ credentials (UID, GID, etc.) or adopt any environment variables from the target process. It affects only the Mach bootstrap context and directly-related attributes.

asuser UID command [args]
This executes the given command in as similar an execution context as possible to that of the target user’s bootstrap. Adopted attributes include the Mach bootstrap namespace, exception server and security audit session. It does not modify the process’ credentials (UID, GID, etc.) or adopt any user-specific environment variables. It affects only the Mach bootstrap context and directly- related attributes.

But these sub-commands have been deprecated so they shouldn’t be relied on.

As you can see, it can get a little involved because you would need to gather certain information about the user or process. Rich Trouton documents this in an excellent blog post. Unfortunately, from what I can tell, there is no replacement for asuser or bsexec.

In 10.10 or greater, launchctl is much more explicit. In fact, Apple explains it as follows (can be found by typing launchctl in Terminal in 10.11):

When using a legacy subcommand which manipulates a domain, the target domain is inferred from the current execution context. When run as root (whether it is via a root shell or sudo(1)), the target domain is assumed to be the system-wide domain. When run from a normal user’s shell, the target is assumed to be the per-user domain for that current user.

For 10.10 and 10.11, if you want to load a launchagent, the command would look like:

launchctl bootstrap gui/<user's UID> /Library/LaunchAgents/com.company.launchagent.plist

At least for 10.11, to unload it:

launchctl bootout gui/<user's UID> /Library/LaunchAgents/com.company.launchagent.plist

Unfortunately, for 10.10 the sub-command unbootstrap that is documented in the manual to unload the agent does not work. You will get the output Command is not yet implemented. Since it’s an older operating system, you may want to rely on the deprecated command at least for that OS as they’ve clearly changed the command name in 10.11. And don’t worry, there are still other commands in 10.11 which also do not work as of yet and give you the same output.

Pay attention to the spacing after gui/<user’s UID>. To gather the user’s UID, you can run the command: id -u <username> where <username> would be the user account name you want to target.

The biggest change to point out is that you need to target a domain. The 3 most applicable will be:

system/[service-name]
Targets the system-wide domain or service within. Root privileges are required to make modifications.

user/<uid>/[service-name]
Targets the user domain or service within. A process running as the target user may make modifications. Root may modify any user’s domain. User domains do not exist on iOS.

gui/<uid>/[service-name]
Targets the GUI domain or service within. Each GUI domain is associated with a user domain, and a process running as the owner of that user domain may make modifications. Root may modify any GUI domain. GUI domains do not exist on iOS.

Note: This information is available by typing man launchctl in Terminal. Other domains do exist.

Some benefits to this change become apparent if you accidentally run something under root or with sudo. For example, this deprecated command would load the daemon as root.

sudo launchctl load /Library/LaunchDaemon/com.company.launchdaemon.plist

But the following deprecated command would also load the agent as root because its implicit due to sudo:

sudo launchctl load /Library/LaunchAgent/com.company.launchdaemon.plist

Perhaps that’s not what you meant to do. With the new syntax you can use sudo all you want (and in cases where you want to access another user environment other than the one you’re in its required).

Another thing to note is that you can unload everything in the user domain by not specifying a path:

launchctl bootout gui/<user's UID>

This leads to weird results so be careful using this.

Listing running daemons/agents

Under 10.9 or older, you would be able to list running agents with the simple command:

launchctl list

And if you wanted to get all the launch daemons/launch agents running, you would simply do:

sudo launchctl list

This would give you a Process ID (PID) of the daemon/agent along with the exit code and the job name. If you writing scripts, it can be rather handy to grep against some of the results to compare against in conditional statements.

With 10.10 and newer, the list sub-command is deprecated. The closest I can find is printwhich gives you a great deal more of detail. I’ll give you a few examples below.

launchctl print gui/<user's UID>

will print all the running services under that particular user domain.

launchctl print system

will print all the running services under root

launchctl print gui/<user's UID>/com.comapny.launchagent.label

will print detailed information about the specific launch agent. And if it’s not running or you’ve mistyped, you will get some output with a non-zero exit code: Could not find service “com.comapny.launchagent.label” in domain for login

Unfortunately, Apple states:

IMPORTANT: This output is NOT API in any sense at all. Do NOT rely on the structure or information emitted for ANY reason. It may change from release to release without warning.

So be sure to revisit every new OS to see if things change here.

Other sub-commands

enable/disable

The sub-commands enable and disable will enable/disable a service.

The syntax looks as follows:

launchctl disable gui/<user's UID>/com.comapny.launchagent.label

From what I gather, this may be more useful for a developer working with apps, but there is some interesting behavior to note here.

Let’s say you’ve loaded a launch agent and then disable it. You won’t be able to load the launch agent thereafter until it has been re-enabled. For example, the following commands in succession:

launchctl bootstrap gui/<user's UID> /Library/LaunchAgents/com.company.launchagent.plist
launchctl disable gui/<user's UID>/com.comapny.launchagent.label
launchctl bootstrap gui/<user's UID> /Library/LaunchAgents/com.company.launchagent.plist

That should give you an output of:
/Library/LaunchAgents/com.company.launchagent.plist: Service is disabled

What’s interesting here is that the launch agent is actually still loaded. Check it out yourself using the new launchctl print command (launchctl print gui/<user’s UID>/com.company.launchagent.label ). The state will show as Running. If you do disable the agent using launchctl bootout then you cannot load it again until you’ve re-enabled the service. Like I said, this may not be very useful to an admin, but something to be aware of nonetheless.

uncache

The sub-command uncache is not yet implemented.

kickstart

The sub-command kickstart does have one interesting use. It does have a few options you can use with it, but the interesting one to know is how to restart a service:

launchctl kickstart -k gui/<user's UID>/com.comapny.launchagent.label

The -k indicates you want to terminate the current service before it is restarted. If your launch agent uses  KeepAlive then it will restart the agent and should be pretty apparent right away. However, I want to caution that this may not be the proper way to restart a launch agent and you should properly stick to unloading and loading using the commands shown earlier. My reasoning is that I’m not a developer and launchd has been expanded to work with XPC services so its unclear to me if this is really meant for XPC services.

kill

The sub-command kill can be used as follows:

launchctl kill <signal name or number> gui/<user's UID>/com.comapny.launchagent.label

This one is a little interesting because it utilizes signals that you can send to the launch agent. Might be handy for troubleshooting. Signal names and numbers can be found in this Apple document.

For example, to terminate a process simple do:

launchctl kill 9 gui/<user's UID>/com.comapny.launchagent.label

or

launchctl kill SIGKILL gui/<user's UID>/com.comapny.launchagent.label

config

The sub-command config can allow you to set the umask system wide or just across user domain. Apple documents it here. It also lets you set PATH environment variables but I couldn’t get it working (didn’t try too hard on this one so I’ll edit this blog post if the right syntax for it).

sudo launchctl config system umask 000
sudo launchctl config user umask 000

reboot

The last sub-command that I’ll over is reboot. This one is very interesting. You can essentially tell what you want to reboot. Be careful though or you will find yourself restarting the entire computer.

sudo launchctl reboot system

will reboot the entire system after it deconstructs the user domain.

sudo launchctl reboot userspace

will deconstruct the user domain. It will appear as if the computer is restarting, but really its just the user domain that’s being restarted.

sudo launchctl reboot halt

will deconstruct the user domain, but stop short of actually restarting the computer.

launchctl reboot logout

I’ll let Apple explain the last two because I think these can be pretty handy.

With the logout argument given, launchd will tear down the caller’s GUI login session in a manner similar to a logout initiated from the Apple menu. The key difference is that a logout initiated through this subcommand will be much faster since it will not give apps a chance to display modal dialogs to block logout indefinitely; therefore there is data corruption risk to using this option. Only use it when you know you have no unsaved data in your running apps.

launchctl reboot apps

With the apps argument given, launchd will terminate all apps running in the caller’s GUI login session that did not come from a launchd.plist(5) on-disk. Apps like Finder, Dock and SystemUIServer will be unaffected. Apps are terminated in the same manner as the logout argument, and all the same caveats apply.

Unfortunately, the last option does not completely work as of 10.11 and its noted by Apple in their manual. For example, if you have an app like Safari or System Preferences opened, it will quit it. But if you have an app like TextWrangler, it remains opened.

And if you use the -s flag in either of these two scenarios it will reboot into single-user mode (because who needs a GUI, right?).

sudo launchctl reboot -s system
sudo launchctl reboot -s userspace

The last option that I haven’t covered, and frankly I’m not sure in what scenario this would be handy, is the reroot option.

With the reroot argument given, launchd will perform a userspace shutdown as with the userspace argument, but it will exec a copy of launchd from the specified mount-point.  This mechanism is a light-weight way of changing boot partitions. As part of this process, launchd will make mount-point the new root partition and bring userspace up as if the kernel had designated mount-point as the root partition.

There are other sub-commands in launchctl, but they are beyond what I’m interested in testing. Hopefully this post will help you with some of the syntax in launchctl.