s6-rc
Software
skarnet.org

# The s6-rc program

s6-rc is a service manager, a.k.a machine state manager: a program to manage the live state of a machine - what services are currently up and what services are currently down. It can list active services, or change the live state.

s6-rc is meant to be the one-stop shop of service management: once service definitions have been made into a compiled service database via s6-rc-compile, and the machine state has been initialized via s6-rc-init, any state change - be it initial startup, shutdown, or anything else - should be achieved by a single `s6-rc change` invocation.

s6-rc should only be run by the user owning the underlying s6 supervision tree. It can also be run by root even if the supervision tree is not owned by root, but in this case the services will always be started and stopped by the user owning the supervision tree - they will not have root privileges.

## Interface

         s6-rc help
         s6-rc [ -l live ] [ -b ] [ -a ] [ -u | -d ] list servicenames...
         s6-rc [ -l live ] [ -b ] [ -a ] [ -u | -d ] listall servicenames...
         s6-rc [ -l live ] [ -b ] diff
         s6-rc [ -l live ] [ -b ] [ -a ] [ -u | -d ] [ -p ] [ -v verbosity ] [ -n dryrunthrottle ] [ -t timeout ] change [ servicenames... ]

- s6-rc expects to find a functional live state in live. The live state is normally created at boot time by s6-rc-init. If the live state is invalid, s6-rc complains and exits 4.
- Depending on the arguments given, it prints the requested information to stdout, then exits 0; or it performs a machine state change.

## Exit codes

- 0: success
- 1: some of the state transitions could not be performed by `s6-rc change`; or a mismatch was found by `s6-rc diff`
- 2: timed out during state change
- 3: unknown service name in the arguments
- 4: invalid service database
- 100: wrong usage
- 111: system call failed

## Service selection

The arguments servicenames... may be atomic services or bundles; they are resolved into a set of atomic services, which is called the selection. If the `-a` option is present, the current set of active services (i.e. services that are up) is added to the selection. The s6-rc command always operates on the selection.

## Options

### General options

- `-l live` : look for the live state in live. Default is `/run/s6-rc`. The default can be changed at compile-time by giving the `--livedir=live` option to `./configure`.
- `-a` : add the current set of active services to the selection. This is useful for instance at shutdown time: `s6-rc -da change` will stop all services.
- `-b` : blocking lock. If the service database is currently being used by another program, s6-rc will wait until that other program has released its lock on the database, then proceed. By default, s6-rc fails with an error message if the service database is currently in use. The default is the safe behaviour: for instance, it will correctly detect and fail nested s6-rc invocations (which are an admin error), whereas `s6-rc -b` would deadlock in such a case.

### Up or down

These options control what is to be done: bring selected services up or down (for `s6-rc change`) or whether to use the forward or reverse dependency graph (for `s6-rc listall`). Default is up.

- `-u` : up.
- `-d` : down.

### `s6-rc change` control

- `-v verbosity` : be more or less verbose. Default is 1: warning and error messages will be printed to stderr. 0 silences warnings. 2 writes information messages whenever s6-rc performs a transition. 3 or more is debug info.
- `-n dryruntimeout` : dry run. s6-rc will pretend to perform transitions, but will replace all its program invocations by a call to s6-rc-dryrun, which will do nothing but print the command line s6-rc would have executed, then sleep for dryruntimeout milliseconds before reporting success.
- `-t timeout` : timeout. If s6-rc isn't done after timeout milliseconds, it will exit, leaving the live state as it is at exit time. It does not kill its children, so a child may successfully complete afterwards and the live state will not be updated; in that case, subsequent `s6-rc change` invocations will notice and correctly update it.
- `-p` : prune. The state will be brought to exactly the set of selected services, plus their dependencies, and the other services will be brought down. With the `-d` option, the meaning is reversed: the state will be brought to the maximum possible set that does not include selected services. The change, however, is always performed in the same manner: first the unwanted services are all brought down, then the wanted services are all brought up.

## Subcommands

### s6-rc help

Prints a short help message.

### s6-rc list servicenames...

Prints the selection. If the `-d` option has been given, the selection is inverted before it is printed, i.e. all the services but servicenames... will be printed.

This is mostly useful as `s6-rc -a list`, which simply prints the list of currently active services. `s6-rc -da list` will print the list of currently down services.

### s6-rc listall servicenames...

- s6-rc computes the complete dependency tree of the selection: the selection, plus the dependencies of every service in the selection, and recursively down. With the `-d` option, it computes the reverse dependency tree: the selection, plus what depends on it, and recursively up. The result is called the closed selection.
- It prints the closed selection to its standard output. Note that the closed selection may be a very long list of services, including the most basic ones.

### s6-rc diff

Checks the consistency between s6-rc's view of the state of the longruns it's managing, and s6's view. s6-rc will print one line per service whose s6 state fails to match what s6-rc believes: a `+` character (if s6 is maintaining the service as up when s6-rc thinks it's down) or a `-` character (if s6 is not trying to maintain the service when s6-rc thinks it's up), followed by the service name. If s6-rc finds at least one mismatch, it exits 1; else it exits 0.

`s6-rc diff` normally reports nothing. Currently, the only case where s6-rc's view can diverge from s6's view is when a service has been brought up via a `s6-rc change` command, then has failed at some point with a permanent failure - which means its supervisor has stopped trying to bring it back up.

### s6-rc change

`s6-rc change` is the service state engine. It will bring the machine to a state where:

- `-u` (default): all the services in the selection are up
- `-d`: all the services in the selection are down
- `-pu`: all the services in the selection, and only them, are up
- `-pd`: all the services in the selection, and only them, are down

To do so:

- From the selection, s6-rc computes the closed selection
- It then computes the necessary transitions so that all the services in the closed selection can match the desired state
- It then performs the transitions - or simulates them if the `-n` option has been given.
- s6-rc processes services as soon as they can be processed: as soon as a service that needs to change state has all its dependencies fulfilled, it undergoes its transition. Independent services are processed in parallel: s6-rc parallelizes the transitions as much as it can, and there is no useless waiting time.
- s6-rc updates the live state in live after every successful transition.
- If every transition completes successfully, the live state eventually matches the desired state, and s6-rc exits 0.
- If a transition fails, s6-rc will not perform the transitions that depend on it. It will keep processing all the other independent transitions, though, until there is no more work it can complete; then it will exit 1.

#### Longrun transitions

Transitions for longrun services are simple: s6-rc removes or creates a `./down` file in the live service directory for the longrun service, then sends a command to the supervision tree to start or stop the service. (A service that is considered down by s6-rc will have a `./down` file in its live service directory; a service that is considered up by s6-rc will not.) The transition is considered successful as soon as the daemon dies (for down transitions), or becomes up and ready (for up transitions). If a longrun service does not support readiness notification, the s6-svc command that is invoked by s6-rc will print a warning message, and the transition will be considered successful as soon as the daemon is up, i.e. as soon as the `./run` script is executed by s6-supervise.

When a longrun service supports readiness notification, unless a nonzero timeout has been declared in the `timeout-up` file in the service definition directory, s6-rc will wait forever on an "up" transition for the notification to arrive. The transition will fail if a timeout occurs.

If a down transition fails, s6-rc does nothing with it. The service has already received a SIGTERM, and may be stuck in the process of exiting; or it may already have died but is stuck in a bad `finish` script that is not timing out. In any case, it is not a situation that s6-rc can recover from; the service is most likely down, but the administrator should manually check their process list. And fix their scripts, or timeout values, because a down transition failure is always a programmer or sysadmin error.

If an up transition fails, s6-rc sends an explicit s6-svc -d command to the longrun. This ensures the service is in a known down state when failing to go up, instead of (for instance) being stuck in a not-ready limbo state.

Note that proper usage of the `timeout-kill` and `timeout-finish` values in the longrun's definition directory can considerably reduce the number of cases where the service is left in an unknown state.

Transitions are supposed to be idempotent, but it is a general rule of supervision that `run` and `finish` scripts must be idempotent, so a properly designed service directory should work with s6-rc with no additional effort.

#### Oneshot transitions

Transitions for oneshot services amount to running the up or down script for the service; those scripts are stored in the compiled service database that is linked from the live state. The transition is considered successful if the script exits zero, and unsuccessful otherwise.

s6-rc performs some black magic so that up and down scripts are always run in a reproducible way, no matter when or how `s6-rc change` is invoked. That black magic involves a special longrun service, `s6rc-oneshot-runner`, that every oneshot service automatically depends on, and that is actually used to fork the up or down scripts as scions of the s6 supervision tree, instead of children of the s6-rc process.

Transitions should be ideally transactional, or at the very least idempotent. If a transition fails, it should leave the machine in the same state as before the transition was attempted; at the very least, it should not prevent a subsequent run of the same transition from completing successfully. If an `s6-rc change` invocation fails because some transition experienced a temporary failure, it should be possible to run the exact same `s6-rc change` invocation later, and be met with success.

This is important: it means that oneshot scripts should be treated as atoms, and that some care should be taken when writing them.

#### Dry runs

For any manual change, is it recommended to perform a dry run before the state change itself: add the `-n dryrunthrottle` option to the s6-rc command line. s6-rc will then simulate all the transitions, but not actually perform them or change the real live state. The command lines that s6-rc would have run will be printed to stdout instead, and each simulated transition will take dryrunthrottle milliseconds to complete successfully.

## Signals

`s6-rc change` reacts to the following signals:

- SIGTERM: s6-rc immediately aborts all its longrun transitions with a failure, and the impacted longruns will most likely be in a down state. Oneshot transitions are untouched, because killing the oneshot subprocess would make it impossible to determine what state the oneshot service is in.
- SIGINT: same as SIGTERM.

## Usage examples

     s6-rc change myservicebundle

Brings up all the services represented by myservicebundle, bringing up all its dependencies first (recursively).

     s6-rc -bad change

Waits for any pending program of the s6-rc family to stop using the live database and current compiled service database, then brings down all the currently running services in an orderly manner. This is typically run at shutdown time. (And it's not necessarily a bad change!)

     s6-rc -l /zork -ua listall myservicebundle

Prints the names of all atomic services represented by myservicebundle plus the current live services, as well as everything they depend on, recursively. Assumes the live state is stored in the `/zork` directory instead of `/run/s6-rc`.

     s6-rc -d listall myservicebundle

Prints the names of all atomic services represented by myservicebundle, as well as everything that depends on them.

     s6-rc -pun0 change myservicebundle

Prints what s6-rc would do to bring the state to exactly the contents of myservicebundle as well as its recursive dependencies, and pruning all the rest. Does not wait any extra time between simulated transitions.