9

I've read the following manual pages on launchd: launchctl, launchd, and launchd.plist.

However, nothing is really said in these manual pages regarding what the launchctl boostrap and launchctl bootout (sub)commands actually do (see below). Is it to be inferred from their names?

Futhermore, I cannot find information in the man pages, regarding what user/group the service (agent/daemon) runs as. However, launchd.plist mentions UserName/GroupName for controlling the user/group for the system domain. So, what user are per-user services in /Library/LaunchAgents or ~/Library/LaunchAgents run as?

Finally, I would expect I need to bootstrap/load my daemon's .plist, but after putting the .plist in /Library/LaunchAgents or /Library/LaunchDaemons, it was automatically loaded (run) at system boot. Indeed, RunAtLoad=true, but what's the purpose of bootstrap, if services run anyway?

man launchctl:

 bootstrap | bootout domain-target [service-path service-path2 ...] | service-target
          Bootstraps or removes domains and services. When service arguments are present, bootstraps and correspondingly removes their definitions into the domain.  Services may be specified as a series of
          paths or a service identifier.  Paths may point to XPC service bundles, launchd.plist(5) s, or a directories containing a collection of either. If there were one or more errors while bootstrapping or
          removing a collection of services, the problematic paths will be printed with the errors that occurred.

          If no paths or service target are specified, these commands can either bootstrap or remove a domain specified as a domain target. Some domains will implicitly bootstrap pre-defined paths as part of
          their creation.

1 Answer 1

13

LaunchAgents are basically the same as LaunchDaemons, except that:

  • LaunchAgents runs only after the user Logs in, process runs on the Logged in UID (User ID) with the logged user privileges. Process can interact with the logged user via GUI.

  • LaunchDaemons runs on boot time, before the GUI is up, during the progress bar on the boot screen. It run as root, there is no need to have any logged user, run in pure background (like windows system services, or Linux rc.d daemons), it cannot interact with any user on the GUI. [This is basically for system services, but you can have your own service] (personally I have a launchDaemon that downloads and update my /etc/hosts file blocking some malicious URLs, it is a bash script I created as a service)

/Library/LaunchAgents/ - (For all users) [load after any user logs in]

~/Library/LaunchAgents/ - (for a SPECIFIC user) [load after he/she logs in]

-- To load means 'run the service', you load it on memory. But it may not run exactly on load time, if your internal plist setting sets for example a timer to run, after X hours.

Eg: I create my custom daemon /Library/LaunchDaemons/local.updateHosts.plist

I will load it:

sudo launchctl load /Library/LaunchDaemons/local.updateHosts.plist

'Load' must point to the path/to/file.plist

**you may have to kickstart it after loading, this way it will be executed, finish, and wait for the next execution time (if it is a timed service like mine)*

Since it is on LaunchDaemon, it is a system service.

[a brief pause here about launchctl]

Because to continue we need to understand the MacOS process execution architecture:


MacOS Bootstraps domains, sessions and namespaces

Additionally to BSD process contexts [ UID ], MacOS has the Mach bootstrap process contexts, called namespaces.

A namespace is like a 'place' or grouping, where various process runs.

Bootstrap namespaces are arranged hierarchically. There is a System global namespace, below it we have a per-user namespace (non GUI), and below it we have a per-session GUI namespace [created by the WindowServer when the user logs in via GUI] .

Hierarchically, each lower level can access all their upper-levels namespace services (their parents services processes)

----
    System_Namespace
         Per-User_Namespace
             Per-Session_Namespace(GUI WindowServer)
----

Technically the GUI per-session namespace is called 'Aqua' Session by Apple's API docs.

The hierarchy above shows the System Domain, the User Domain and the Session Domain (which belongs to the user, each user has its own)

An expanded view with 2 logged users is below:

``

//   System_Namespace [System]
//      |
//      ------ PerUSER_Namespace [Background] [user 501]
//      |      |
//      |      ----- PerSESSION_Namespace [Aqua] (MacOS GUI WindowServer) [user 501]
//      |
//      |
//      ------ PerUSER_Namespace [Background] [user 502]
//             |
//             ----- PerSESSION_Namespace [Aqua] (MacOS GUI WindowServer) [user 502]
// ----
//

``

This is exactly the root of the security architecture of the MacOS, it is called the Mach Layer, that works in conjunction with the BSD Layer (which takes care about user file permissions and other linux/bsd/unix permissions).

MacOS has 2 distinct security mechanisms integrated and working together: the Unix + the Mach security mechanisms.


Continuing about launchctl, when you are about to create a daemon/service you have to choose where it will run, which domain, and which context.

First, lets print the system domain services, this will list all launchdaemons, loaded or not, enabled and disabled one.

sudo launchctl print system/

Now, lets print the user domain services: (considering userid 501, you can find other users id numbers with the command: id username

sudo launchctl print user/501

Note: Catalina also accepts the syntax: sudo launchctl print user/admin <- username

You can also query a PID, and check under which domain and namespace it is running:

sudo launchctl print pid/784 (considering 784 is the PID of Finder for example)

> $ sudo launchctl print pid/758
com.apple.xpc.launchd.domain.pid.Finder.758 = {
    type = process
    handle = 758
    active count = 91
    on-demand count = 1
    service count = 90
    active service count = 2
    activity ratio = 0.02
    originator = /System/Library/CoreServices/Finder.app
    creator = Finder.758
    creator euid = 503
    uniqueid = 758
    external activation count = 0
    security context = {
        uid = 503
        asid = 100008
    }

    bringup time = 20 ms
    death port = 0x52a63

    in-progress bootstraps = 0
    pended requests = 0
    pending requests = {
    }
    subdomains = {
    }
    pending attachments = {
    }

    task-special ports = {
             0x3fc73 4       bootstrap  com.apple.xpc.launchd.user.domain.503.100008.Aqua
             0x15f03 9          access  com.apple.taskgated
    }

Under Security Context:

  • uid = 503 -> process is running for user id 503

  • asid = 100008 -> process is running on GUI session 100008


com.apple.xpc.launchd.domain.pid.Finder.758 com.apple.xpc.launchd.user.domain.503.100008.Aqua

Means:

  • Finder, which has PID 758
  • was created by launchd,
  • under the user domain,
  • for user 503,
  • which is running a graphical interface with session ID 100008.

Now you can choose and control domain, namespaces and user for your daemon.

bootout means stop a service that is running, for ex:

sudo launchctl bootout system/com.apple.netbiosd

This stops the netbios daemon.

__ Lets go back to that service we created with this command:

sudo launchctl load /Library/LaunchDaemons/local.updateHosts.plist

load is the unique parameter that you pass the full path of the .plist file, all other launchctl commands works via reference from domain hierarchy!

So to print our service is: sudo launchctl print system/local.updateHosts you dont't use the extension .plist, and the reference is system/process.name

The process name is what you define inside the .plist file under key Label:

        <key>Label</key>
        <string>local.updateHosts</string>
        <key>ProgramArguments</key>
    <array>

The bootstrap parameter is to force load your service while choosing which domain or namespace you want to make it run, ex:

sudo launchctl bootstrap user/503 /Library/LaunchDaemons/local.updateHosts.plist`

/Library/LaunchDaemons/local.updateHosts.plist: Service cannot load in requested session

The command above returned error because my service .plist only allow my service to run as a system service, otherwise it would have been started for user 503.

bootstrap let you start any service or XPC service bundle under other domains/namespaces. Basically you choose a service AND a target for it to run.

Additional sintaxes:

sudo launchctl start system/local.updateHosts

sudo launchctl stop system/local.updateHosts

sudo launchctl unload system/local.updateHosts

sudo launchctl kickstart system/local.updateHosts


If you want to go ultra-deep on this topic, I suggest this excelent documentation from Apple, it is very technical and very detailed:

https://developer.apple.com/library/archive/documentation/Darwin/Conceptual/KernelProgramming/contexts/contexts.html#//apple_ref/doc/uid/TP30000905-CH212-BEHJDFCA

3
  • Thank you @Prado for an excellent answer. However, I've the following questions. Is bootstraps "force load" different from loading using load? Is "namespace" the as "domain"? Why is bootstrap (or load) necessary, if all services in e.g. /Library/LaunchAgents and /Library/LaunchDaemons are loaded automatically at boot or when the user logs in? Will all services, except timed, run after load? And finally, do you also find the man pages poor, and where have you learned this, besides your listed reference?
    – Shuzheng
    Aug 8, 2019 at 19:57
  • Not all services under those directory are loaded. If the service is Disabled, it will be there but will not be loaded. The comamand load enables it to be loaded. Try using the command disable you will see the files are still there, but it will not be loaded on boot
    – Prado
    Aug 28, 2019 at 18:27
  • Any plist in the appropriate directory will be loaded on its own at boot or login. load is a legacy command. It appears that bootstrap is preferred if you don't want to reboot (or simply log out and log back in).
    – De Novo
    Dec 14, 2021 at 0:19

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .