9.4. Name and Bootstrap ServersConsider two programs communicating via Mach IPCsay, using the familiar client-server model. The server will have receive rights to a port, which is how it will receive request messages from a client. A client must possess send rights to such a port to send messages to the server. How does the client acquire these rights? A rather contrived and impractical way is that the server task creates the client task. As the client task's creator, the server task can manipulate the client task's port space. Specifically, the server task can insert send rights to the server port into the client's port space. A more reasonable alternativeone used in practiceis that every task is created with send rights to a system-wide server that acts as a trusted intermediary. Mach-based systems have such an intermediary: the Network Message Server (netmsgserver). 9.4.1. The Network Message ServerA Mach program desiring to receive messages on a port can publish the port through the netmsgserver. The publication process involves the server task registering the server port, along with an associated ASCII string name, with the netmsgserver. Since a client task will have send rights to a port that netmsgserver is listening on, it can send a lookup message containing the ASCII string associated with the desired service. // ipc_common.h (shared between the client and the server) #define SERVICE_NAME "com.osxbook.SomeService" // ipc_server.c #include "ipc_common.h" ... kern_return_t kr; port_t server_port; server_port = mach_port_allocate(...); ... kr = netname_check_in(name_server_port, (netname_name_t)SERVICE_NAME, mach_task_self(), server_port); ... The first argument to the netname_check_in call is the task's port to the Network Name Server. The global variable name_server_port represents send rights to the default system-wide name server. The second argument is the ASCII name of the service to be checked in. The third argument is a signaturetypically a port to which the calling task has send rights. The signature is required later, when checking out (i.e., removing the port from the name server's namespace) the server port, which is the fourth argument. ... kr = netname_check_out(name_server_port, (netname_name_t)SERVICE_NAME, mach_task_self()); ... Once a server task checks in a port successfully, a client can look it up using the ASCII name that the server used for it. // ipc_client.c #include "ipc_common.h" ... kern_return_t kr; port_t server_port; ... kr = netname_look_up(name_server_port, (netname_name_t)"*", (netname_name_t)SERVICE_NAME, &server_port); ... The second argument is the host name whose Network Name Server is to be queried. An empty string represents the local host, whereas the "*" string specifies all hosts on the local network, resulting in a broadcast lookup.
We noted earlier that the design of Mach's IPC allows for transparent extension to a distributed environment, even though the kernel does not have any explicit support for distributed IPC. Two programs residing on different machines can communicate using Mach IPC if both machines are running an intermediary user-level program that extends Mach IPC across a network. The netmsgserver transparently handles servers and clients residing on different machines. Whereas it communicates with tasks on the local machine using regular, local Mach IPC, it can communicate with other netmsgserver tasks on the network using arbitrary networking protocols, forwarding messages sent to local proxy ports to the appropriate remote netmsgserver tasks. Thus, the netmsgserver works both as a name server (allowing a network-wide name registration of ports) and a proxy server for distributed IPC (performing network-transparent message transfers). Mac OS X provides a conceptually similar facility called Distributed Objects (see Section 9.14) for use by Cocoa programs. 9.4.2. The Bootstrap ServerMac OS X does not provide a netmsgserver, or rather, it doesn't provide a network-capable netmsgserver. It does provide a local name serverthe Bootstrap Serverwhich allows tasks to publish ports that other tasks on the same machine can send messages to. The Bootstrap Server's functionality is provided by the bootstrap task, whose program encapsulation is the launchd program. Besides managing name-port bindings in its role as the Mach name server, the Bootstrap Server also initiates certain (typically on-demand) system daemonsspecifically those that have not been migrated to the higher-level server interface exported by launchd.
9.4.2.1. The Bootstrap PortEach task has a bootstrap port that it inherits from its parent task. The bootstrap port allows a task to access various system services. The Bootstrap Server provides its own service port to its descendant tasks via their bootstrap ports. Therefore, all direct descendants of the Bootstrap Server receive privileged bootstrap ports. It is possible for a parent task to change its bootstrap port while creating a tasksay, to limit the set of services available to the child task. System services that execute untrusted tasks replace the Mach bootstrap task special port with a subset port. A task can retrieve its default bootstrap port by using task_get_bootstrap_port(). 9.4.2.2. The Bootstrap ContextThe scope of the bootstrap task's lookup mechanism available to a subsequent task, as determined by the latter task's bootstrap port, is referred to as the task's bootstrap context. In other words, a task's bootstrap context determines which services (the corresponding ports, that is) the task can look up. There is a single top-level bootstrap context when Mac OS X boots: the startup context. launchd executes in this context, and so do early system services that rely on being able to look up various Mach ports. Subsequent, less privileged bootstrap contexts can be created by the system for running programs that may be untrusted. For example, when a user logs in, the bootstrap task creates a login context, which is a subset of the startup context. All of a user's processes are in the login context.
9.4.2.3. Debugging the Bootstrap ServerWhen experimenting with the Bootstrap Server or launchd in general, you may find it worthwhile to configure launchd to log debugging messages. You can arrange for log messages from launchd to be written to a file by adjusting launchd's log level and configuring the system log daemon (syslogd) not to ignore these messages. There are multiple ways to adjust launchd's log level. If you wish to debug launchd from the point where it starts, you should create a system-wide launchd configuration file (/etc/launchd.conf) with the following contents: # /etc/launchd.conf log level debug When launchd starts, the contents of launchd.conf are run as subcommands through the launchctl program.
Setting a log-level value of debug will cause launchd to generate a substantial amount of debugging output. Alternatively, you can create a per-user launchd configuration file (~/.launchd.conf), which will apply the log-level change only to the per-user local scope. Moreover, if you only wish to change launchd's log level temporarily, you can run the launchctl program yourself. launchd generates its log messages using the syslog(3) API. syslogd selects which messages to log based on rules specified in its configuration file (/etc/syslog.conf). The default rules do not include debugging or informational messages from launchd. You can temporarily log all launchd messages to a specific file by adding a rule such as the following to /etc/syslog.conf: # /etc/syslog.conf ... launchd.* /var/log/launchd_debug.log Thereafter, you must either send syslogd a hangup signal (SIGHUP) or restart it. In particular, you can examine /var/log/launchd_debug.log after system startup to see subset bootstrap contexts being created and disabled as users log in and log out. Registered service 2307 bootstrap 1103: com.apple.SecurityServer ... Service checkin attempt for service /usr/sbin/cupsd bootstrap 1103 bootstrap_check_in service /usr/sbin/cupsd unknown received message on port 1103 Handled request. Server create attempt: "/usr/sbin/cupsd -f" bootstrap 1103 adding new server "/usr/sbin/cupsd -f" with uid 0 Allocating port b503 for server /usr/sbin/cupsd -f New server b503 in bootstrap 1103: "/usr/sbin/cupsd -f" received message on port b503 Handled request. Service creation attempt for service /usr/sbin/cupsd bootstrap b503 Created new service b603 in bootstrap 1103: /usr/sbin/cupsd received message on port b503 Handled request. Service checkin attempt for service /usr/sbin/cups ... Subset create attempt: bootstrap 1103, requestor: ad07 Created bootstrap subset ac07 parent 1103 requestor ad07 ... Received dead name notification for bootstrap subset ac07 requestor port ad07 ... 9.4.3. The Bootstrap Server APILet us first look at examples of functions supported by the Mac OS X Bootstrap Server, after which we will see examples of communicating with the server using these functions. bootstrap_create_server() defines a server that can be launched and relaunched by the Bootstrap Server in the context corresponding to bootstrap_port. kern_return_t bootstrap_create_server(mach_port_t bootstrap_port, cmd_t server_command, integer_t server_uid, boolean_t on_demand, mach_port_t *server_port); The on_demand argument determines the relaunching behavior as managed by launchd. If on_demand is true, launchd will relaunch a nonrunning server when any of the registered service ports is used for the first time. If on_demand is false, launchd will relaunch the server as soon as the server exits, regardless of whether any of its service ports are in use. The server task created because of relaunching has server_port as its bootstrap port. The server abstraction created by this call is automatically deleted when all of its associated services are deleted and the server program has exited. Services associated with the server can be declared by calling bootstrap_create_service(), with server_port (obtained by calling bootstrap_create_server()) specified as the bootstrap port in that call. kern_return_t bootstrap_create_service(mach_port_t bootstrap_port, name_t service_name, mach_port_t *service_port); bootstrap_create_service() creates a port and binds service_name to it. Send rights to the newly created port are returned in service_port. Later on, a service may call bootstrap_check_in() to check in the binding: In doing so, the caller will acquire receive rights for the bound port and the service will be made active. Thus, bootstrap_create_service() allows establishment of a name port binding before the backing service is even available. Lookups performed on bindings created by this mechanism return send rights to service_port, even if no service has checked in yet. If a caller uses such rights to send requests to the port, the messages will be queued until a server checks in. kern_return_t bootstrap_check_in(mach_port_t bootstrap_port, name_t service_name, mach_port_t *service_port); bootstrap_check_in() returns the receive rights for the service named by service_name, thus making the service active. The service must already be defined in the bootstrap context by an earlier call to bootstrap_create_service(). When used in conjunction with bootstrap_subset(), bootstrap_check_in() can be used to create services that are available only to a subset of tasks. It is an error to attempt to check in an already active service. bootstrap_register() registers a send right for the service port specified by service_port, with service_name specifying the service. kern_return_t bootstrap_register(mach_port_t bootstrap_port, name_t service_name, mach_port_t service_port); After a successful service registration, if a client looks up the service, the Bootstrap Server will provide send rights for the bound port to the client. Although you cannot register a service if an active binding already exists, you can register a service if an inactive binding exists. In the latter case, the existing service port, which the Bootstrap Server would have receive rights to, will be deallocated. In particular, if service_port is MACH_PORT_NULL, this can be used to undeclare (shut down) a declared service.
Each service created by the Bootstrap Server has an associated backup port, which the Bootstrap Server uses to detect when a service is no longer being served. When this happens, the Bootstrap Server regains all rights to the named port. Clients can continue to perform successful lookups on the port while the Bootstrap Server has receive rights to the port. If a client wishes to determine whether the service is active or not, it must call bootstrap_status(). A restarting service that wishes to resume serving existing clients must first attempt to call bootstrap_check_in() to prevent the original port from being destroyed. bootstrap_look_up() returns send rights for the service port of the service specified by service_name. A successful return means that the service must have been either declared or registered under this name, although it is not guaranteed to be active. bootstrap_status() can be used to check whether the service is active. kern_return_t bootstrap_look_up(mach_port_t bootstrap_port, name_service_t service_name, mach_port_t *service_port); bootstrap_look_up_array() returns send rights for the service ports of multiple services. The service_names array specifies the service names to look up, whereas the service_ports array contains the corresponding looked-up service ports. The Boolean out parameter all_services_known is true on return if all specified service names are known; it is false otherwise. kern_return_t bootstrap_look_up_array(mach_port_t bootstrap_port, name_array_t service_names, int service_names_cnt, port_array_t *service_port, int *service_ports_cnt, boolean_t *all_services_known); bootstrap_status() returns whether a service is known to users of the specified bootstrap port and whether a server is able to receive messages on an associated service portthat is, whether the service is active. Note that if a service is known but not active, then the Bootstrap Server has receive rights for the service port. kern_return_t bootstrap_status(mach_port_t bootstrap_port, name_t service_name, bootstrap_status_t *service_active); bootstrap_info() returns information about all services that are known, except those that are defined only in subset contextsunless the subset port is an ancestor of bootstrap_port. The service_names array contains the names of all known services. The server_names array contains the namesif knownof the corresponding servers that provide the services. The service_active array contains a Boolean value for each name in the service_names array. This value is true for services that are receiving messages sent to their ports and false for the rest. kern_return_t bootstrap_info(port_t bootstrap_port, name_array_t *service_names, int *service_names_cnt, name_array_t *server_names, int *server_names_cnt, bool_array_t *service_active, int *service_active_cnt); bootstrap_subset() returns a new port to be used as a subset bootstrap port. The new port is similar to bootstrap_port, but any ports dynamically registered by calling bootstrap_register() are available only to tasks using subset_port or its descendants. kern_return_t bootstrap_subset(mach_port_t bootstrap_port, mach_port_t requestor_port, mach_port_t *subset_port); A lookup operation on subset_port will return not only ports registered with only subset_port but also ports registered with ancestors of subset_port. If the same service is registered with both subset_port and an ancestor port, a lookup for that service by a user of subset_port will fetch the subset_port version of the service. This way, services can be transparently customized for certain tasks without affecting the rest of the system, which can continue to use the default versions of the services in question. The lifespan of subset_port is determined by requestor_port; subset_port, its descendants, and any services advertised by these ports are all destroyed when requestor_port is destroyed. bootstrap_parent() returns the parent bootstrap port of bootstrap_port, which is typically a bootstrap subset port. The calling task must have superuser privileges. For example, when called from a user login context, this function will return the bootstrap port corresponding to the startup context. When called from the startup context, the parent port returned is the same as the bootstrap port. kern_return_t bootstrap_parent(mach_port_t bootstrap_port, mach_port_t *parent_port);
The /usr/libexec/StartupItemContext program can be used to run an executable in the startup contextthat is, the context in which the Mac OS X startup items run. It works by calling bootstrap_parent() repeatedly until it has reached the startup (root) context. It then sets the port as its own bootstrap port, after which it can execute the requested program. The automatic relaunching of servers by the Bootstrap Server is useful for creating crash-resistant servers. However, it is neither necessary nor advisable to create production servers by directly using the Bootstrap Server interface. Beginning with Mac OS X 10.4, the launch API,[8] as exported through <launch.h>, should be used. With this caveat, let us look at two examples of using the Bootstrap Server interface.
9.4.3.1. Displaying Information about All Known ServicesIn this example, we will use bootstrap_info() to retrieve a list of all known services that can be looked up in the bootstrap context associated with the given bootstrap port. Figure 915 shows the program. Figure 915. Displaying information about all known services in a bootstrap context
You can run the bootstrap_info program in different bootstrap contexts to see the differences in the services that are accessible from those contexts. For example, the program's output will be different when run from a shell in an SSH login compared with running it from a normal, graphical login. Similarly, using /usr/libexec/StartupItemContext to run bootstrap_info will list services in the startup context. 9.4.3.2. Creating a Crash-Resistant ServerIn this example, we will create a dummy server that will be crash resistant in that if it exits unexpectedly, it will be relaunched by the Bootstrap Server. We will also provide a way for our server to explicitly arrange for its shutdown if it really wishes to exit. The server will provide a service named com.osxbook.DummySleeper (the server does nothing but sleep). The server executable will reside as /tmp/sleeperd. The server will check for the existence of a flag file, /tmp/sleeperd.off; if it exists, the server will turn itself off by calling bootstrap_register() with a null port as the service port. Figure 916 shows the program. Figure 916. A crash-resistant server
Note that the program also shows an example of using the Apple System Logger (ASL) facility. Beginning with Mac OS X 10.4, the asl(3) interface is available as a replacement for the syslog(3) logging interface. Besides logging, the ASL facility provides functions for querying logged messages. Section 10.8.3 contains an overview of logging in Mac OS X. Our server logs a few messages at the ASL_LEVEL_ERR log level. These messages will be written to both /var/log/system.log and /var/log/asl.log. Moreover, if launchd's debugging output is enabled (as described in Section 9.4.2.3), you will see detailed log messages corresponding to the Bootstrap Server calls made by our program. $ gcc -Wall -o /tmp/sleeperd bootstrap_server.c $ /tmp/sleeperd
Since our server does not fork and performs no operation other than sleeping, it will hang on running. We can examine the launchd log at this point to see the relevant messages, which are shown annotated, with prefixes removed, in Figure 917. Figure 917. launchd debug messages corresponding to a Mach server's initialization
Let us kill the server by sending it the interrupt signalthat is, by typing ctrl-c in the shell from which we executed /tmp/sleeperd. We can then verify that the server was indeed relaunched. ^C $ ps -ax | grep sleeperd 2364 ?? Ss 0:00.01 /tmp/sleeperd Let us examine launchd's log again (Figure 918). Figure 918. launchd debug messages corresponding to a Mach server's relaunch
We can see that certain log messages are different from when we executed /tmp/sleeperd for the first time. In the first case, both a new server and a new service were created. In this case, when launchd respawns the server, the server's first attempt to call bootstrap_check_in() succeeds because the service already exists. Now, even if we kill the server process by sending it SIGKILL, launchd will relaunch it. We can cause the server to terminate permanently by creating the /tmp/sleeperd.off file. $ touch /tmp/sleeperd.off $ kill -TERM 2344 $ ps -ax | grep sleeperd $ The log messages will show that launchd relaunched our server even this time. However, instead of calling bootstrap_check_in(), the server calls bootstrap_register() with MACH_PORT_NULL specified as the service port, which makes the service unavailable. ... # server died received message on port f627 server /tmp/sleeperd dropped server port received message on port f03 Notified dead name c1ab Received task death notification for server /tmp/sleeperd waitpid: cmd = /tmp/sleeperd: No child processes # launchd is attempting to relaunch Allocating port f62b for server /tmp/sleeperd Launched server f62b in bootstrap 5103 uid 501: "/tmp/sleeperd": [pid 2380] ... # server -> bootstrap_register(..., MACH_PORT_NULL) server /tmp/sleeperd dropped server port received message on port f03 Notified dead name f84f # server -> exit() Received task death notification for server /tmp/sleeperd waitpid: cmd = /tmp/sleeperd: No child processes Deleting server /tmp/sleeperd Declared service com.osxbook.DummySleeper now unavailable ... |