launchd: Confusion on semantics of bootstrap and bootout etc. after reading manual pages

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