xchpst(8)
π xchpst π
okay bestie, welcome to xchpst(8) β the System Manager's Manual entry for the most extra little process-state-changin' tool you'll ever meet π
β¨ NAME β¨
xchpst β eXtended CHange Process STate. yes that's the whole name, she's giving acronym realness π
πΈ SYNOPSIS πΈ
xchpst --help
xchpst --version
xchpst --exit[=retcode]
xchpst [OPTIONS] [--] command ...
π¦ DESCRIPTION π¦
so basically, xchpst changes the state of your process based on whatever options you feed it, and then it calls exec() on a named executable using the positional arguments. that's the whole plot π«Ά
xchpst is a backwards-compatible extension to the chpst(8) tool that ships with runit. the glow-up here is that xchpst lets runit service scripts actually take advantage of the hardening capabilities that recent Linux kernels have β think namespaces and capabilities, the good stuff β¨. it can also set up shadow subtrees inside the filesystem hierarchy so your long-running services get isolated from parts of the system they literally should never need to touch, like private /tmp areas and a read-only /usr. no cap, that's iconic.
π Extended xchpst options π
the extra options xchpst brings to the party are, in order:
--helpβ shows you the help text and the usage. your girl is helpful π--filefile β reads options from file. the file format lives in βOptions fileβ, go look π--exit[=retcode]β bails out immediately with exit status0if the given options are supported.retcodeif specified.--mount-nsβ creates a new mount namespace. lots of the other options here implicitly turn on mount namespaces too, because it's lowkey essential to how they work, so you'll rarely ever need to spell this one out explicitly.--net-nsβ creates a new network namespace. this more or less isolates the process from the networking subsystem entirely, bye π--uts-nsβ creates a new UTS namespace.--fork-joinβ forks a new process and waits for it to finish, passing on to the child process any signals received by thexchpstprocess. you need this one to take advantage of PID namespaces. the exit status is that of the child process.--user-nsβ creates a user namespace.--id-mapself|identity|fullβ sets the uid and gid map in the user namespace.selfmaps only the root user (that's the default!),identitymaps the first 65536 ids, andfullmaps the whole id space πΊοΈ--adopt-netpath β adopts the network namespace bound to path. per convention among other network-namespace-aware applications, e.g.ip(8), it's interpreted relative to/var/run/netns. the binding gets deleted from the filesystem, which means the namespace will disappear when the process exits, if there's no other reference to it. this lets the calling script set up a suitable networking environment for the process and just hand it over π€
btw, a later version of this tool may replace the asymmetric --adopt-net option with a general mechanism to enter an existing namespace without then unbinding it from the filesystem. so like, heads up.
--new-rootβ creates a new root filesystem (and will implicitly enable the creation of a new mount namespace). the new root filesystem is created as atmpfs, and all the top-level directories in the original root filesystem are bind mounted, and any symlinks are replicated.
this mode of operation stops a command that retains the capability to unmount filesystems from unmounting any read-only or private bind mounts (from --ro-sys, --ro-home, --ro-etc, --private-tmp or --private-run) to reveal the stacked read-write mounts. when used in combination with --ro-home, which bind mounts /run/user as read only, or any other case where the read-only bind mount is not on an existing mountpoint, there may still end up stacked read-write mounts underneath. so watch out π
--private-runβ mounts an isolated/rundirectory for the process. unless--new-rootis also specified, the old shared/rundirectory will still be accessible if the stacked mount is removed.--private-tmpβ mounts an isolated/tmpdirectory for the process. unless--new-rootis also specified, the old shared/tmpdirectory will still be accessible if the stacked mount is removed.--protect-homeβ mounts isolated/home,/rootand/run/userdirectories for the process. unless--new-rootis also specified, the old shared host directories will still be accessible if the stacked mounts are removed.--ro-sysβ converts/usrand/bootinto read-only mounts. note that if the hardened process has the rights to unmount filesystems, it can reveal the original writable filesystems. the--new-rootoption is designed to prevent this. alternatively, use--cap-bs-dropto remove the βCAP_SYS_ADMINβ capability, preventing the bind mount from being unmounted π--ro-homeβ converts/home,/rootand and/run/usrinto read-only mounts. this option has the same limitations as--ro-sys.--ro-etcβ converts/etcinto a read-only mount. this option has the same limitations as--ro-sys.--caps-bs-keepcapability[,capability...]β keeps only the listed capabilities in the bounding set.--caps-bs-dropcapability[,capability...]β drops the listed capabilities from the bounding set. use only one of the two options governing the bounding set, bestie, pick a lane π--caps-keepcapability[,capability...]β retains the listed capabilities when dropping to a non-root user.--caps-dropcapability[,capability...]β drops the listed capabilities when dropping to a non-root user, but retains all the others.--no-new-privsβ prevents the target application from obtaining any new privileges. seePR_SET_NO_NEW_PRIVS(2const).--cpu-schedulerother|batch|idleβ sets the scheduler policy, as persched_setscheduler(2).--io-schedulerrt|best-effort|idle[:priority]β sets the I/O scheduler policy and priority, as perionice(1).--cpusstart[-end[:stride]][,...]β sets CPU affinity in the same format astaskset(1).--umaskmode β sets umask to the octal value mode.--appname β overrides the program name used for pre-creating system directories.--run-dirβ creates a directory for the program under/run, owned by the appropriate user.--state-dirβ creates a directory for the program under/var/lib, owned by the appropriate user.--log-dirβ creates a directory for the program under/var/log, owned by the appropriate user.--cache-dirβ creates a directory for the program under/var/cache, owned by the appropriate user.--loginβ creates a login environment, using the user specified by-u,-Uor the current user, in order of preference. if this option is specified and no command is specified to be executed, then the shell defined for the given user is launched, instead of an error being returned π--oomadjustment β sets the out-of-memory (OOM) score adjustment to adjustment.-a,--limit-asbytes β sets the soft limit for address space size. the short version of this option matches softlimit emulation but is deprecated, in case a future version ofchpst, which does not provide it, uses this option character for a different purpose β and it my be removed.-r,--limit-rssbytes β sets the soft limit for resident set size. the short version of this option matches softlimit emulation but is deprecated, in case a future version ofchpst, which does not provide it, uses this option character for a different purpose and may be removed. also ngl this option is basically useless on any system new enough to runxchpst(seesetrlimit(2)) π-s,--limit-stackbytes β sets the soft limit for stack segment size. the short version of this option matches softlimit emulation but is deprecated, in case a future version ofchpst, which does not provide it, uses this option character for a different purpose and may be removed.--limit-memlockbytes β sets the soft limit for the amount of locked memory.--limit-msgqueuebytes β sets the soft limit for message queue space for this user.--limit-niceniceness β sets 20 minus the minimum niceness possible for this process.--limit-rtptioprio β sets the soft limit for real time priority of the process.--limit-rttimems β sets the soft limit for the amount of real time processing between blocking system calls.--limit-sigpendingnumber β sets the soft limit for the number of pending signals permitted for the process.--limit-locksnumber β sets the soft limit for the number of file locks that this process may hold.--hardlimitβ also sets the hard limit for any soft limit option that follows. yes, she does both π--dev-nullβ changes the behaviour of any following-0,-1or-2options so they redirect the given file descriptor to or from/dev/nullinstead of closing it.-@β switches to chpst-compatible option handling only for the remaining options. this exists to support scripts that can convert anxchpstinvocation into a command line forchpstifxchpstisn't present on the system. clever, honestly.
π chpst-compatible options π
the options compatible with the classic chpst are these:
-u[:]user[:group]...β sets uid, gid and supplementary groups. prepend the argument with a colon for numerical inputs rather than names to be looked up. if no group is specified then the specified user's group is used, and β as an extension tochpstβ supplementary groups are also looked up π«Ά-U[:]user[:group]...β like-ubut the environment variablesUIDandGIDare set instead of changing the user. as an extension tochpst, supplementary groups, if present, are joined in a comma-separated list in theGIDLISTenvironment variable. iconique.-bargv0 β setsargv[0]to argv0 instead of the target executable path when launching the program.-edir β the environment populator bestie β¨ For every single file inside dir, the filename is an environment variable that's gonna get set or unset. The first line of each of those files is the content to be set, withNULcharacters replaced byLFand trailing whitespace removed. If the file is0bytes long, then the variable gets unset. (So a file with just a newline in it results in the variable being set with an empty value. sneaky. iconic. π )-/dir β run in a chroot π° Change to the dir directory and make it the brand-new root.-Cdir β change directory, bestie. Change to the dir directory (and this happens after any chroot setting is applied, ok? order matters).-ninc β increase niceness by inc. Yes, inc can be negative, which results in the process taking a higher priority π-lfile β wait for lock. This takes a lock out on file and waits to obtain it before it proceeds toexec(). patience is a whole vibe π§-Lfile β try to obtain lock; bail out if it can't be obtained. no waiting around, we're booked and busy π-mbytes β set the soft limit for data and stack segments and virtual memory size and locked memory. one option, four jobs, main character energy.-dbytes β set the soft limit for data segment size.-ofiles β set the soft limit for the number of open files.-pprocs β set the soft limit for the number of processes for this user.-fbytes β set the soft limit for the size of file that this process may create.-cbytes β set the soft limit for the size of core this process may dump.-tseconds β set the soft limit for the amount of CPU time this process may consume.-vβ be verbose π£οΈ This option may be repeated for increased verbosity to support debugging. stack it, bestie.-Vβ show thexchpstversion number.-Pβ make this process the process group leader, allocating a new session idea. π-0β close stdin.-1β close stout. π-2β close stderr.-F,--fghackβ attempt to retain foreground control of a process that backgrounds itself, by implicitly enabling--jork-join, operating as a sub-reaper and waiting on all orphaned sub-processes. If--pid-nsis also enabled then it additionally operates as thePID 1βinitβ process. (Combining these features is literally the way in whichchpstimplements its-Foption. iykyk.)
Signals are passed on to direct child processes β in contrast to the chpst implementation, which passes them on to all descendants. If the main process exits uncleanly, supervision will end for all spawned processes. π
This is a new approach to implementing the daemontools fghack(8) tool.
-I,--pid-nsβ create a PID namespace. This implies--fork-join, because a new process is needed to act asPID 1and in order to be able to mount a new procfs for the namespace.
πΈ s6-inspired options πΈ
The following options to xchpst correspond to options from the s6-applyuidgid command, and their implementation supports the emulation mode thereof:
--ugids-from-envβ set uid, gid and comma-separated supplementary groups from theUID,GIDandGIDLISTenvironment variables. The values must be numeric and are not looked up in the password database, by design. periodt. π--ugids-clear-envβ clear theUID,GIDandGIDLISTenvironment variables before starting the next process.
π Resource limit options π
Those resource limit options up above? They take a parameter in one of these forms:
softβ set only the soft limit, in the style ofchpstandsoftlimit, unless--hardlimithas previously been specified, in which case both soft and hard limits are defined, in the style ofprlimit.soft:β set only the soft limit, in the style ofprlimit(1).soft:hardβ set soft and hard limits.:hardβ set only the hard limit.+bothβ set both soft and hard limits.
An unlimited limit may be selected by any value of -1, unlimited or infinity. π¦
π Emulating daemontools family tools π
xchpst follows chpst in emulating chain-loading utilities from the daemontools suite and other suites in the daemontools 'family', which tend to follow similar principles and have partially-compatible options and behaviour. These emulated modes take effect when the xchpst executable is invoked under the name of the respective tool, such as via a symbolic link. The matching of argv[0] against emulated tool names ignores file extensions (*.ext) and known suite prefixes (s6-*).
β οΈ note that there can be variations between the options and behaviour of equivalent tools from the different suites, so documentation should be checked before relying on emulation modes. fr fr, read before you leap bestie.
The following describes the emulated tools, their home suite, the equivalent xchpst option and notable differences in behaviour:
chpstβ home suite:runitβ equivalentxchpstoption: (none) β differences: supplemental group lookupenvdirβ home suite:daemontoolsβ equivalentxchpstoption:-eβ differences: (none)envuidgidβ home suite:daemontoolsβ equivalentxchpstoption:-Uβ differences: (none)pgrphackβ home suite:daemontoolsβ equivalentxchpstoption:-Pβ differences: (none)fghackβ home suite:daemontoolsβ equivalentxchpstoption:-Fβ differences: uses subreaper methodsetlockβ home suite:daemontoolsβ equivalentxchpstoption:-lβ differences: (none)setuidgidβ home suite:daemontoolsβ equivalentxchpstoption:-uβ differences: (none)softlimitβ home suite:daemontoolsβ equivalentxchpstoption: (none) β differences: (none)applyuidgidβ home suite:s6β equivalentxchpstoption:--ugids-from-envβ differences: (none)setuidgid-fromenvβ home suite:noshβ equivalentxchpstoption:--ugids-from-envβ differences: (none)
As an additional feature, all these tools when so invoked, accept the -v option to increase verbosity. π£οΈ
π Options file π
An options file specifies additional options to apply, one option per line. Each line begins with an option name. Options that take an argument have horizontal white space and the option value following the option name. Comments begin with a # character and may only be preceded by whitespace β otherwise they will be interpreted as part of an option name or value. no cap, that # has to be alone up front.
Options may be bracketed by lines of the form opt[ and opt] to indicate that they should be silently ignored if unknown to this version of xchpst. This allows the use of new options which are not essential to safe operation of the target command without having to express an incompatibility with older versions of xchpst. forward compatibility slay β¨
Example options file:
# Comment line
private-tmp
app my-service
run-dir
pid-ns
opt[
option-from-the-future
Z 33
opt]
hardlimit
o 2048
β¨ EXIT STATUS β¨
okay bestie, when xchpst finishes doing its thing, here's what the exit codes are telling you π
0β the default exit status when--exitis specified is0. This can be used for a quick test thatxchpstis available on the system in shell scripts and that the given options are supported. happy little vibe check, no cap β¨100β the return code when an invalid option or option argument is specified, including if a username cannot be resolved, for example. it's giving "you typed something cursed" π111β when the requested process state cannot be changed. big yikes energy, the change simply did not happen π- other β the
--exitoption takes an optional argument with a return code to use. so you can pick your own number, main character choice fr fr π
and here's the iconic part: if there is no error and the intended application is exec()'d, the exit status will be that of the application, not xchpst. so once the app takes over, the numbers coming out the other side? that's the app's business, not ours π¦
Behaviour on failure to apply π«Ά
The list below divides the process change options between those that abort on failure to effect the requested change (with error code 111) and those that continue execution. Nonsense configuration values always fail with error code 100. yes, always. periodt π
- chpst
- Abort on error:
uUe/CnLlmdopfcrtFI - Continue on error:
b012
- Abort on error:
- rlimit
- Abort on error:
salimit-memlocklimit-msgqueuelimit-nicelimit-rtpriolimit-rttimelimit-sigpendinglimit-locks - Continue on error: (none, bestie β everything here is ride or die)
- Abort on error:
- namespaces
- Abort on error:
fork-joinnew-rootmount-nsnet-nsuser-nspid-nsuts-nsnet-adopt - Continue on error:
id-map
- Abort on error:
- capabilities <sup>[1]</sup>
- Abort on error: (none)
- Continue on error:
cap-bs-keepcap-bs-dropcaps-keepcaps-drop
- filesystem
- Abort on error:
private-runprivate-tmpprotect-homero-sysro-homero-etcrun-dirstate-dircache-dirlog-dir - Continue on error: (none)
- Abort on error:
- other
- Abort on error: (none)
- Continue on error:
cpuscpu-schedulerio-schedulerno-new-privsumaskoomloginugids-from-envugids-clear-env
[1] If capabilities are not available in the kernel, errors are ignored. Otherwise any failure causes an abort. See "BUGS" π
β¨ NOTES β¨
okay so bestie, you know how systemd has a million and one service directives? The list below shows how some of them map onto xchpst options. See systemd.exec(5) This is not an exhaustive list. π
πΊοΈ systemd option mapping πΊοΈ
like, here's the mapping tea, one bullet per vibe:
ProtectSystem=yesβro-sysπ‘οΈProtectSystem=fullβro-sys ro-etcπ‘οΈβ¨ProtectHome=read-onlyβro-homeπProtectHome=tmpfsβprotect-homeπ πPrivateTmp=yesβprivate-tmpπ§ΌPrivateUsers=self|identity|fullβuser-ns id-map=self|identity|fullβ default mapping isselfπCapabilityBoundingSet=βcap-bs-keepπ§’CapabilityBoundingSet=~βcap-bs-dropπ§’πAmbientCapabilities=βcaps-keepβ¨AmbientCapabilities=~βcaps-dropβ¨πNoNewPrivileges=yesβno-new-privsπ«πCPUAffinity=βcpusβ usetaskset(1)format π§CPUSchedulingPolicy=βcpu-schedulerπIOSchedulingClass=,IOSchedulingPriority=βio-schedulerπΎUMask=βumaskπRuntimeDirectory=βrun-dirβ Leaf name governed by target command or βappβ option πStateDirectory=βstate-dirβ Leaf name governed by target command or βappβ option ππΎCacheDirectory=βcache-dirβ Leaf name governed by target command or βappβ option πβ¨LogsDirectory=βlog-dirβ Leaf name governed by target command or βappβ option ππOOMScoreAdjust=βoomπUser=,Group=βuβ Check syntax. In particular, numeric ids begin with a colon. π€User=withExecStart=+orExecStart=!βUβ In these modes,- β¦while systemd does not drop the user, expecting the application to do that, but instead uses the specified username for other options. In this case the environment variable setting by
xchpstis not useful π but other options likerun-dirwill use the right user. Slay, at least it's consistent bestie π
β¨π Resource limit option mapping πβ¨
The table below shows how xchpst resource limit options correspond to those of other tools. It's literally a β¨translation dictionaryβ¨, keep it handy:
dβ chpst:dΒ· softlimit:dΒ· prlimit:dΒ· ulimit:dΒ· systemd:LimitDATA=oβ chpst:oΒ· softlimit:oΒ· prlimit:nΒ· ulimit:nΒ· systemd:LimitNOFILE=pβ chpst:pΒ· softlimit:pΒ· prlimit:uΒ· ulimit:uΒ· systemd:LimitNPROC=fβ chpst:fΒ· softlimit:fΒ· prlimit:fΒ· ulimit:fΒ· systemd:LimitFSIZE=cβ chpst:cΒ· softlimit:cΒ· prlimit:cΒ· ulimit:cΒ· systemd:LimitCORE=tβ chpst:tΒ· softlimit:tΒ· prlimit:tΒ· ulimit:tΒ· systemd:LimitCPU=mβ chpst:mΒ· softlimit:mΒ· prlimit:d s l vΒ· ulimit:d s l vΒ· systemd:LimitDATA=,LimitSTACK=,LimitAS=,LimitMEMLOCK=(yeah she's the overachiever, one option covering four π )limit-as(ora) β softlimit:aΒ· prlimit:vΒ· ulimit:vΒ· systemd:LimitAS=limit-rss(orr) β softlimit:rΒ· prlimit:mΒ· ulimit:mΒ· systemd:LimitRSS=limit-stack(ors) β softlimit:sΒ· prlimit:sΒ· ulimit:sΒ· systemd:LimitSTACK=limit-memlockβ softlimit:lΒ· prlimit:lΒ· ulimit:lΒ· systemd:LimitMEMLOCK=limit-msgqueueβ prlimit:qΒ· ulimit:qΒ· systemd:LimitMSGQUEUE=limit-niceβ prlimit:eΒ· ulimit:eΒ· systemd:LimitNICE=limit-rtprioβ prlimit:rΒ· ulimit:rΒ· systemd:LimitRTPRIO=limit-rttimeβ prlimit:yΒ· ulimit:RΒ· systemd:LimitRTTIME=limit-sigpendingβ prlimit:iΒ· ulimit:iΒ· systemd:LimitSIGPENDING=limit-locksβ prlimit:xΒ· ulimit:xΒ· systemd:LimitLOCKS=
The following options from the bash ulimit command are not available: b k p P T. Nope, not a thing here, don't @ me π€
ππ BUGS ππ
The --cpu-scheduler option should accept more scheduling policies and should accept additional parameters to qualify those policies. Currently unknown policy names are treated as the default Linux scheduling policy. (like, she's doing her best okay π)
When the kernel supports capabilities but not specific capabilities that have been requested to be dropped or kept, xchpst should continue rather than aborting.
ππ¦ EXAMPLES π¦π
Okay bestie, real talk, these are the receipts. Witness her π
Testing the emulation of envdir:
xchpst -b envdir -- xchpst
Launch with read-only filesystem if xchpst is available, else use chpst:
xchpst --exit && exec xchpst --ro-sys -l /var/lock/ntpsec-ntpdate ntpd; exec chpst -l /var/log/ntpsec-ntpdate ntpd
Drop a capability from the bounding set:
xchpst --cap-bs-drop CAP_SYS_ADMIN -- acmed
Drop user while retaining some capabilities:
xchpst -u :500:500 --caps-keep CAP_DAC_OVERRIDE fakeroot /usr/sbin/gpm -D -m /dev/input/mice -t exps2
πβ¨ Diagnostics β¨π
Wanna see what's actually going on, including options enabled implicitly because of other options? Add the --verbose option. Bestie, she will tell you everything π
Use --login without a command name to explore the hardened environment from a shell. Cute lil sandbox field trip π
You can enter the created namespaces (but not other aspects of hardening), including any synthesised root filesystem, by identifying the process id of the hardened application and running:
nsenter -a -t PID
π SEE ALSO β¨
okay bestie, here's the whole squad β go read these next, they're literally the supporting cast π
chpst(8)πrunit(8)πunshare(1)π¦capsh(1)π¦taskset(1)π¦chrt(1)π¦choom(1)π¦proc_pid_oom_score_adj(5)πΈprlimit(1)πΈprlimit(2)πΈnamespaces(7)βcapabilities(7)βenvdir(8)πenvuidgid(8)πpgrphack(8)πfghack(8)πsetlock(8)πsetuidgid(8)πsoftlimit(8)π
no cap, that's a whole friend group right there π«Ά
π AUTHORS π
Andrew Bower <andrew@bower.uk> β yeah, this icon wrote it, periodt β¨
so that's the whole page, bestie, you made it to the end π this cutie ships as part of Debian, and the page you just read is dated September 9, 2025 β that's the xchpst(8) manual page, section 8, in all her main character glory ππ
now go forth and xchpst responsibly, ily π«Άπ
π¦ where this page comes from
- ubuntu stonking: xchpst 0.8.5-1 Β· MIT
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms