smf(5): asking versus doing

Let’s consider how applications are traditionally started: we execute (or the system executes) a command, such as fooadm(1M), which in turn calls fork(2), does some detachment work, and then calls exec(2) to run food(1M) (which is what we wanted). A schematic of this sequence would look like

For long-running, always-needed applications (which we call services), this model raises some questions:

  • Who is responsible for food(1M)? After fooadm(1M) finishes, the intent behind the running food(1M) is murky. Is it acceptable for food(1M) to exit after some period of time? Or is exit a failure condition?
  • How did food(1M) get the privileges, resources, etc. that it needed to run? Either fooadm(1M) was executed holding enough privileges for food(1M) to function normally, or food(1M) is setuid-root and must verify that its requesters are authorized to execute it for some set of its offered operations. Ditto for resource assignments.
  • Why does each fooadm(1M)–food(1M) pair on the system (baradm(1M)–bard(1M), …) handle this relationship slightly differently? I can only speculate.

(Lest anyone assume I’m pretending to novelty: most restarters (init(1M)) or super-servers (inetd(1M)) have answered the first two of these questions by offering a single, specific application model. But many daemons we run on systems today fit neither of these application models well.)

In smf(5), the service management facility, directly forking a service is discouraged. Instead, one requests that a service be enabled, and the master restarter, svc.startd(1M) or a delegate—like inetd(1M)—will do the fork(2)–exec(2) sequence. The equivalent diagram might be drawn as

Upon receiving an enable request from smf_enable_instance(3SCF) or svcadm enable <em>fmri</em>, svc.startd(1M) determines if the service’s dependencies are satisfied and, if so, requests that the responsible restarter start an instance of the service. What the responsible restarter is doing is:

  • setting the privileges, resource bindings, and application-specific environment for the service,
  • creating a fault boundary to place the service instance within,
  • calling fork(2)–exec(2) to run the service*, and
  • watching the service for error conditions, upon which the service is terminated (and restarted if appropriate).

(The combination of the master restarter and the delegates are handling these calculations and operations for every service on the system, propagating their state changes and evaluating the impact of those state changes in turn.)

Moreover, because the smf_enable_instance(3SCF) request is evaluated based on the authorizations of the caller, fooadm(1M) can be run with no significant privileges. Since we split authorizations into action authorizations (non-persistent operations, like “restart this service”) and modify authorizations (changing configuration aspects), it becomes straightforward to create an operator role that can tend a service, but not change its configuration or affect any other independent service on the system.

More flexible administrative assignments is one aspect of inserting smf(5) into Solaris, but we’ll contrast these two approaches again—and reveal exactly what those purple rectangles represent.

  • Not every restarter need offer a fork(2)–exec(2) application model, but presently all smf(5) restarters do.