How To Use ‘`launchctl print`’ as a Replacement for ‘`launchctl bslist`?’

command linelaunchd

man launchctl' remarks that:

LEGACY SUBCOMMANDS
     Subcommands from the previous implementation of launchd are generally available,
     though some may be unimplemented. Unimplemented subcommands are documented
     as such.

⋮

     bslist [PID | ..] [-j]
         This subcommand is not implemented and has been superseded by the print
         subcommand, which provides much richer information.

at least as of OS X v10.11.x 'El Capitan.' (Presumably, this was also the case in v10.10.x 'Yosemite,' as that was the OS release that first included 'launchd 2.0,' if I'm reading/recalling my history correctly.) What arguments, then, would one pass to 'launchctl print' to get output like what the 'bslist' sub-command used to provide? Would you need to filter it any (using grep, sed, awk, etc.) to get the desired result(s)?

('launchctl print system' spews a bunch of data out, but I'm not clear on whether that includes the same information the 'bslist' sub-command would have output in the past or, if so, where in the former's output said information might be. Its 'endpoints' key contains a list similar to the example provided in Listing 1 in the 'Mach Bootstrap Basics' section of this old, outdated Apple documentation on 'Daemons and Agents,' but I'm uncertain if that's what I'm looking for, particularly since it contains a couple extra columns.)

Best Answer

Short answer

Use:

bash -c "if [ \$(id -u) -eq 0 ]; then domain=system; else domain=\"user/\$(id -u)\"; fi; launchctl print \$domain | sed -e '1,/endpoints = {/d' -e '/}/,\$d' -e 's/.* \([A|D]\)\(  *\)\(.*\)/\1  \3/'"

If you prefer a script, create a file, for example /usr/local/bin/bslist, with these contents:

#!/bin/bash

if [ $(id -u) -eq 0 ]; then
    domain=system
else
    domain="user/$(id -u)"
fi
launchctl print $domain | sed -e '1,/endpoints = {/d' -e '/}/,$d' -e 's/.* \([A|D]\)\(  *\)\(.*\)/\1  \3/';

and make it executable: chmod a+x /usr/local/bin/bslist. (See the end of this post for an explanation of how the script works.)

Note that both the command and script above fully support sudo:

  • To the get the output equivalent to running sudo launchctl bslist, simply prepend sudo:

    sudo bash -c "if [ \$(id -u) -eq 0 ]; then domain=system; else domain=\"user/\$(id -u)\"; fi; launchctl print \$domain | sed -e '1,/endpoints = {/d' -e '/}/,\$d' -e 's/.* \([A|D]\)\( *\)\(.*\)/\1 \3/'"

    sudo /user/local/bin/bslist.

  • To get the output for a different user <user>, that is, the output sudo -u <user> launchctl bslist would produce, prepend sudo -u <user> instead.

(Tested in macOS 10.15 "Catalina" and OS X 10.10 "Yosemite".)

Long answer

The long gone bslist

bslist was removed with OS X 10.10 "Yosemite". According to OS X 10.9 Mavericks' man page of launchctl, bslist

(...) prints out Mach bootstrap services and their respective states. While the namespace appears flat, it is in fact hierarchical, thus allowing for certain services to be only available to a subset of processes. The three states a service can be in are active ("A"), inactive ("I") and on-demand ("D").

Typical output is:

A  com.apple.finder.ServiceProvider
D  com.apple.udb.system-push
D  com.apple.systemprofiler
A  com.apple.systemuiserver.ServiceProvider
A  com.apple.dock.server
[...]

where:

  • the first column is the bootstrap service state (A for "Active" and D "On-demand")
  • the second column is the name of the bootstrap service

print, the new kid in town

Apple replaced bslist with an enhanced subcommand: print.

Why enhanced? As nicely explained here, bootstrap services are arraged in a hierarchical namespace. While bslist hides this complexity from the user by making the following assumptions:

  • When run as root (whether it is via a root shell or sudo), bslist outputs the system-wide domain.
  • When run from as a non-privileged user, the target is assumed to be the per-user domain for that current user.

print takes another approach: it gives the user a finer control on the output by accepting the desired domain as an argument (see the man page of launchctl for details).

Making print behave like bslist

Luckily, after running bslist in OS X 10.9 "Mavericks" and print in OS X 10.10 "Yosemite" multiple times and comparing the output, I can confirm that all information provided by bslist is contained in print:

  • The system-wide domain printed by bslist when run as root can is provided by the system option in the endpoints array.
  • The per-user domain is provided by print with the user/<UID> option, also in the endpoints array.

The exact commands are provided above in the short answer section of this post, here I revisit the script (with comments) for a better understanding of what it does:

#!/bin/bash

# Compare the user UID (from command "id -u") with 0
if [ $(id -u) -eq 0 ]; then
    # If the user is root (that is, the user UID is 0), request 
    # the "system" domain
    domain=system
else
    # Otherwise request the user domain
    domain="user/$(id -u)"
fi
# Run launchctl
launchctl print $domain |\
# Remove the output before "endpoints = {"
 sed -e '1,/endpoints = {/d' \
# Remove the output after "}"
-e '/}/,$d' \
# Remove the port information and format the output as bslist
-e 's/.* \([A|D]\)\(  *\)\(.*\)/\1  \3/';

A few words on bootstrap services

This answer deals with bootstrap services, but, what are they?

macOS uses a hybrid kernel, called XNU, that combines the Mach kernel developed at Carnegie Mellon University with components from FreeBSD and a C++ API for writing drivers called IOKit.

Interprocess communication (IPC) plays a large role in the Mach component of the kernel. The Mach implementation of IPC relies on the notion of "ports".

In Mach IPC, ports are somewhat similar to TCP and UDP ports: in the same way that a process requires the TCP/UDP port of a resource on the network to be able to communicate with it, processes communicating over Mach IPC need to know the port of the desired service. This information is provided by the bootstrap server, which is one of the functions of the launchd process.

So, in this oversimplified analogy, the bootstrap server plays a role roughly equivalent to /etc/services.

As with the /etc/services file, the bootstrap server maintains a list of ports and names. You can get a list of them with launchctl print, just look for the endpoint array section, for example:

port: 0x3e607

name: com.apple.dock.server

Stretching the analogy, the difference between the services file and Mach IPC is that, while /etc/services is static, the list of ports and names the bootstrap server maintains is dynamic, as services can request to be added to it.

And that brings us back to the original question: Bootstrap services are simply services registered with the bootstrap server.

References

If you are interested in the macOS launch process, Mach IPC, launchd and its internals, you may find these references useful:

See Mach Bootstrap Basics and Mach Messaging and Mach Interprocess Communication (IPC) for more information on bootstrap basics and IPC.

See Kernel Architecture Overview for more information on the architecture of the macOS kernel.

See Mach Overview for an overview of the Mach component of the macOS kernel.

See Mac OS X For Unix Geeks and The Alpha and the Omega - launchd for an overview of the startup process in macOS.

See LAUNCHCTL 2.0 SYNTAX for a discussion about changes in the launchctl syntax.

See Mach Message and Bootstrap Server on OS X for an overview of Mach messaging and the bootstrap server.

See the source code of the bslist subcommand (look for bslist_cmd) for an insight on launchctl. You can download launchd tarballs from here.

See macOS IPC Man in the Middle for a presentation on implementation flaws in Mach IPC.