Repository: sorah/subsystemctl
Branch: master
Commit: cf112a287313
Files: 10
Total size: 31.9 KB
Directory structure:
gitextract_nwmrkkw_/
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
├── rustfmt.toml
└── src/
├── bottle.rs
├── environment.rs
├── error.rs
├── lib.rs
└── main.rs
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
/target
================================================
FILE: Cargo.toml
================================================
[package]
name = "subsystemctl"
version = "0.2.0"
description = "Utility to run systemd in WSL2 with a Linux namespace"
homepage = "https://github.com/sorah/subsystemctl"
repository = "https://github.com/sorah/subsystemctl"
authors = ["Sorah Fukumori <her@sorah.jp>"]
license = "MIT"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.31"
thiserror = "1.0.19"
clap = "2.33.1"
nix = "0.17.0"
libc = "0.2.71"
env_logger = "0.7.1"
log = "0.4.8"
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 Sorah Fukumori
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# subsystemd: Run systemd in WSL2
__Maintanence Note:__ You may want to use [nullpo-head/wsl-distrod](https://github.com/nullpo-head/wsl-distrod) which provides better compatibility with systemd and easier installation, and well maintained.
Run systemd under Linux namespace in WSL2. Heavily inspired by [arkane-systems/genie][genie], but written in Rust.
## Difference with arkane-systems/genie
Slightly following [genie]'s behavior, but noted below...
- Interface
- Command line interface is not compatible.
- Behavior
- Hostname altertion is optional with `--hostname`, `--hostname-suffix`
- `/etc/hosts` are not updated. Users are encouraged to use `nss-myhostname`.
- Uses `machinectl shell` to launch a user shell; this allows running systemd user session
- Internal
- Removed dependency to `unshare`, `daemonize`, `nsenter` command line tools
- systemd-wide environment variables are set via `systemd.conf` drop-in, using `DefaultEnvironment=`
- systemd PID from root namespace is stored at `/run/subsystemctl/systemd.pid`
## Install
### Arch Linux
PKGBUILD: https://github.com/sorah/arch.sorah.jp/tree/master/aur-sorah/PKGBUILDs/subsystemctl
_(PKGBUILD originally submitted to AUR (https://aur.archlinux.org/packages/subsystemctl) was deleted as [they unwelcomes WSL-exclusive packages](https://lists.archlinux.org/pipermail/aur-requests/2020-June/041193.html).)_
### Debian/Ubuntu
Refer to https://github.com/nkmideb/subsystemctl for debian source.
Pre-built package binaries available at https://github.com/nkmideb/subsystemctl/releases for your convenient.
### Self build
```
cargo install subsystemctl
```
or from git source:
```bash
cargo build --release
install -m6755 -oroot -groot ./target/release/subsystemctl /usr/local/bin/subsystemctl
```
## Usage
### `subsystemctl start`: Start `systemd` environment
```ps1
PS> wsl -u root -- subsystemctl start
```
### `subsystemctl shell`: shell login to systemd-enabled environment
```ps1
PS> wsl subsystemctl shell
Connected to the local host. Press ^] three times within 1s to exit session.
someone@hostname$ ...
```
#### Specifying uid to login
```ps1
PS> wsl -u root -- subsystemctl shell --uid=1000
Connected to the local host. Press ^] three times within 1s to exit session.
someone@hostname$ ...
```
#### Automatically starting and entering a user shell
```ps1
PS> wsl -u root -d Arch -- subsystemctl shell --uid=1000 --start
[2021-06-27T16:32:20Z INFO subsystemctl] Starting systemd
Connected to the local host. Press ^] three times within 1s to exit session.
someone@hostname$ ...
```
### `subsystemctl exec`: Raw `nsenter` like interface
```ps1
PS> wsl subsystemctl exec id
uid=1000(sorah) gid=1000(sorah) groups=1000(sorah),116(admin)
```
#### Specifying uid (and gid)
```ps1
PS> wsl -u root -- subsystemctl exec id
uid=0(root) gid=0(root) groups=0(root)
PS> wsl -u root -- subsystemctl exec --uid=1000 id
uid=1000(sorah) gid=1000(sorah) groups=1000(sorah),116(admin)
PS> wsl -u root -- subsystemctl exec --uid=1000 --gid=116 id
uid=1000(sorah) gid=116(admin) groups=116(admin)
```
### `subsystemctl is-running`
```bash
#!/bin/bash
if subsystemctl is-running; then
echo "running"
else
echo "not-running"
fi
```
### `subsystemctl is-inside`
```bash
#!/bin/bash
if subsystemctl is-inside; then
echo "inside"
else
echo "outside"
fi
```
## Tips
### systemd-resolved, networkd are recommended to be disabled
otherwise `/etc/resolv.conf` might get overwritten to resolved stub-resolver.
## Author
Sorah Fukumori https://sorah.jp/
## License
MIT
[genie]: https://github.com/arkane-systems/genie
================================================
FILE: rustfmt.toml
================================================
max_width = 110
use_small_heuristics = "Max"
newline_style = "Unix"
================================================
FILE: src/bottle.rs
================================================
use crate::environment;
use crate::error;
use std::ffi::CStr;
use std::ffi::CString;
use std::ffi::OsStr;
static RUNTIME_DIR: &str = "/run/subsystemctl";
static PID_FILE: &str = "/run/subsystemctl/systemd.pid";
static HOSTNAME_FILE: &str = "/run/subsystemctl/hostname";
static ORIG_HOSTNAME_FILE: &str = "/run/subsystemctl/hostname.orig";
const OS_NONE: Option<&'static [u8]> = None;
pub fn get_systemd_pid() -> Result<Option<i32>, Box<dyn std::error::Error>> {
let result = std::fs::read(PID_FILE);
match result {
Ok(buf) => {
let pid: i32 = String::from_utf8(buf)?.trim().parse()?;
Ok(Some(pid))
}
Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound {
Ok(None)
} else {
Err(Box::new(e))
}
}
}
}
fn put_systemd_pid(pid: i32) -> std::io::Result<()> {
std::fs::create_dir_all(RUNTIME_DIR)?;
std::fs::write(PID_FILE, format!("{}\n", pid))
}
fn zap_systemd_pid() -> std::io::Result<()> {
if std::fs::metadata(PID_FILE).is_ok() {
std::fs::remove_file(PID_FILE)?;
}
Ok(())
}
pub fn get_original_hostname() -> std::io::Result<String> {
let buf = if std::fs::metadata(ORIG_HOSTNAME_FILE).is_ok() {
std::fs::read(ORIG_HOSTNAME_FILE)
} else {
std::fs::read("/etc/hostname")
}?;
Ok(String::from_utf8(buf).unwrap().trim().to_owned())
}
pub fn put_hostname(name: String) -> std::io::Result<()> {
std::fs::create_dir_all(RUNTIME_DIR)?;
if std::fs::metadata(ORIG_HOSTNAME_FILE).is_err() {
let orig_hostname = std::fs::read("/etc/hostname")?;
std::fs::write(ORIG_HOSTNAME_FILE, orig_hostname)?
}
std::fs::write(HOSTNAME_FILE, format!("{}\n", name))
}
pub fn is_running() -> bool {
if environment::is_pid1_systemd() {
return true;
}
if let Ok(pid_o) = get_systemd_pid() {
if let Some(pid) = pid_o {
if let Ok(meta) = std::fs::metadata(std::format!("/proc/{}", pid)) {
return meta.is_dir();
}
}
}
false
}
pub fn is_inside() -> bool {
environment::is_pid1_systemd()
}
pub fn start(name: Option<String>) -> Result<(), Box<dyn std::error::Error>> {
if let Some(hostname) = name {
ensure_hostname(hostname)?;
}
exec_systemd_ensure_dropin()?;
Ok(exec_systemd()?)
}
fn exec_systemd_ensure_dropin() -> std::io::Result<()> {
std::fs::create_dir_all("/run/systemd/system.conf.d")?;
let envs = vec!["WSL_INTEROP", "WSL_DISTRO_NAME", "WSL_NAME", "WT_SESSION", "WT_PROFILE_ID"];
let envvar_strs: Vec<String> = envs
.into_iter()
.filter_map(|name| {
let e = std::env::var(name);
if let Ok(env) = e {
if env.contains("\"") {
None // XXX:
} else {
Some(format!("\"{}={}\"", name, env))
}
} else {
None
}
})
.collect();
let dropin = format!(
"[Manager]\nDefaultEnvironment=INSIDE_GENIE=1 INSIDE_SUBSYSTEMCTL=1 {}\n",
envvar_strs.join(" ")
);
std::fs::write("/run/systemd/system.conf.d/10-subsystemctl-env.conf", dropin)
}
fn ensure_hostname(name: String) -> Result<(), Box<dyn std::error::Error>> {
let needs_bind = std::fs::metadata(HOSTNAME_FILE).is_err();
put_hostname(name)?;
if !needs_bind {
return Ok(());
}
nix::mount::mount(
Some(OsStr::new(HOSTNAME_FILE)),
OsStr::new("/etc/hostname"),
OS_NONE,
nix::mount::MsFlags::MS_BIND,
OS_NONE,
)?;
Ok(())
}
fn exec_systemd() -> Result<(), error::Error> {
use nix::unistd::ForkResult;
let systemd_bin = CString::new(environment::systemd_bin().unwrap()).unwrap();
match nix::unistd::fork() {
Ok(ForkResult::Child) => exec_systemd0_handle_child_failure(exec_systemd1_child(systemd_bin)),
Ok(ForkResult::Parent { child, .. }) => exec_systemd1_parent(child),
Err(e) => panic!("{}",e),
}
}
fn exec_systemd0_handle_child_failure(r: Result<(), error::Error>) -> Result<(), error::Error> {
match r {
Ok(_) => {} // do nothing
Err(error::Error::StartFailed(exitstatus)) => {
log::error!("Something went wrong while starting");
std::process::exit(exitstatus);
}
Err(e) => panic!("{}",e),
}
std::process::exit(0);
}
fn exec_systemd1_parent(child: nix::unistd::Pid) -> Result<(), error::Error> {
use nix::sys::wait::WaitStatus;
// TODO: monitor systemd status instead of pid file
loop {
match get_systemd_pid() {
Ok(Some(pid)) => {
log::debug!("Watching pid: child_pid={}, pid={}", child, pid);
let pidns_path = format!("/proc/{}/ns/pid", pid);
let mntns_path = format!("/proc/{}/ns/mnt", pid);
if std::fs::metadata(pidns_path).is_ok() && std::fs::metadata(mntns_path).is_ok() {
break;
}
}
Ok(None) => {
log::debug!("Watching pid: none");
}
Err(e) => {
log::debug!("Watching pid: e={:?}", e);
}
}
match nix::sys::wait::waitpid(child, Some(nix::sys::wait::WaitPidFlag::WNOWAIT)) {
Ok(WaitStatus::Exited(_pid, status)) => {
return Err(error::Error::StartFailed(status));
}
Ok(WaitStatus::Signaled(_pid, signal, _)) => {
return Err(error::Error::StartFailed(128 + (signal as i32)));
}
Err(nix::Error::Sys(nix::errno::Errno::ECHILD)) => {
return Err(error::Error::StartFailed(128));
}
_ => {} // ignore,
}
std::thread::sleep(std::time::Duration::from_millis(500));
}
// TODO:
Ok(())
}
fn exec_systemd1_child(systemd_bin: CString) -> Result<(), error::Error> {
use nix::sched::CloneFlags;
use nix::unistd::ForkResult;
nix::sched::unshare(CloneFlags::CLONE_NEWNS | CloneFlags::CLONE_NEWPID).unwrap();
nix::unistd::setsid().unwrap();
match nix::unistd::fork() {
Ok(ForkResult::Child) => exec_systemd0_handle_child_failure(exec_systemd2_child(systemd_bin)),
Ok(ForkResult::Parent { child, .. }) => exec_systemd2_parent(child),
Err(e) => panic!("{}",e),
}
}
fn exec_systemd2_parent(child: nix::unistd::Pid) -> Result<(), error::Error> {
put_systemd_pid(child.as_raw()).unwrap();
std::process::exit(0);
}
fn exec_systemd2_child(systemd_bin: CString) -> Result<(), error::Error> {
use nix::fcntl::OFlag;
use nix::mount::MsFlags;
use std::os::unix::io::RawFd;
nix::mount::mount(
Some(OsStr::new("none")),
OsStr::new("/"),
OS_NONE,
MsFlags::MS_REC | MsFlags::MS_SHARED,
OS_NONE,
)
.expect("set_propagation mount failure");
nix::mount::mount(
Some(OsStr::new("none")),
OsStr::new("/proc"),
OS_NONE,
MsFlags::MS_REC | MsFlags::MS_PRIVATE,
OS_NONE,
)
.expect("proc propagation mount failure");
nix::mount::mount(
Some(OsStr::new("proc")),
OsStr::new("/proc"),
Some(OsStr::new("proc")),
MsFlags::MS_NOSUID | MsFlags::MS_NODEV | MsFlags::MS_NOEXEC,
OS_NONE,
)
.expect("proc mount failure");
nix::unistd::chdir("/").unwrap();
nix::unistd::setgid(nix::unistd::Gid::from_raw(0)).unwrap();
nix::unistd::setuid(nix::unistd::Uid::from_raw(0)).unwrap();
match nix::unistd::close(0 as RawFd) {
Ok(_) => {}
Err(nix::Error::Sys(nix::errno::Errno::EBADF)) => {}
Err(e) => panic!("{}",e),
}
match nix::unistd::close(1 as RawFd) {
Ok(_) => {}
Err(nix::Error::Sys(nix::errno::Errno::EBADF)) => {}
Err(e) => panic!("{}",e),
}
match nix::unistd::close(2 as RawFd) {
Ok(_) => {}
Err(nix::Error::Sys(nix::errno::Errno::EBADF)) => {}
Err(e) => panic!("{}",e),
}
nix::fcntl::open(OsStr::new("/dev/null"), OFlag::O_RDONLY, nix::sys::stat::Mode::empty()).unwrap();
nix::fcntl::open(OsStr::new("/dev/null"), OFlag::O_WRONLY, nix::sys::stat::Mode::empty()).unwrap();
nix::fcntl::open(OsStr::new("/dev/null"), OFlag::O_WRONLY, nix::sys::stat::Mode::empty()).unwrap();
nix::unistd::execve(systemd_bin.as_c_str(), &[systemd_bin.as_c_str()], &[]).unwrap();
panic!("should unreach");
}
pub fn stop() -> Result<(), Box<dyn std::error::Error>> {
let systemd_pid = get_systemd_pid().unwrap().unwrap();
nix::sys::signal::kill(nix::unistd::Pid::from_raw(systemd_pid), Some(SIGRTMIN_plus_4()))?;
zap_systemd_pid().unwrap();
Ok(())
}
extern "C" {
fn __libc_current_sigrtmin() -> libc::c_int;
}
#[allow(non_snake_case)]
fn SIGRTMIN_plus_4() -> nix::sys::signal::Signal {
unsafe { std::mem::transmute(__libc_current_sigrtmin() + 4) }
}
pub fn wait() -> Result<(), Box<dyn std::error::Error>> {
use nix::sys::wait::WaitStatus;
use nix::unistd::ForkResult;
log::debug!("Waiting systemd-machined to start");
let machinectl = environment::machinectl_bin()?;
match nix::unistd::fork() {
Ok(ForkResult::Child) => {
wait_internal(machinectl);
std::process::exit(0);
}
Ok(ForkResult::Parent { child, .. }) => {
loop {
match nix::sys::wait::waitpid(child, None) {
Ok(WaitStatus::Exited(_pid, status)) => {
if status == 0 {
log::debug!("machined is now up");
return Ok(());
} else {
return Err(Box::new(error::Error::WaitFailed));
}
}
Ok(WaitStatus::Signaled(_pid, _signal, _)) => {
return Err(Box::new(error::Error::WaitFailed));
}
Ok(_) => {} // ignore
Err(nix::Error::Sys(nix::errno::Errno::ECHILD)) => return Ok(()), // ???
Err(nix::Error::Sys(nix::errno::Errno::EINTR)) => {} // ignore
Err(e) => panic!("{}",e),
}
}
}
Err(e) => panic!("{}",e),
}
}
fn wait_internal(machinectl: String) {
setns_systemd();
loop {
let cmd = std::process::Command::new(&machinectl)
.arg("list")
.output()
.expect("failed to execute machinectl list");
if cmd.status.success() {
break;
}
std::thread::sleep(std::time::Duration::from_millis(600));
log::debug!("Still waiting systemd-machined to start");
}
log::debug!("systemd-machined is up (internal)");
}
pub fn exec(cmdline: Vec<String>, uid: nix::unistd::Uid, gid: nix::unistd::Gid) -> Result<i32, error::Error> {
let args_string: Vec<CString> = cmdline.into_iter().map(|a| CString::new(a).unwrap()).collect();
let args: Vec<&CStr> = args_string.iter().map(|s| s.as_c_str()).collect();
enter(args[0], args.as_slice(), uid, gid)
}
pub fn shell(uid: Option<nix::unistd::Uid>, quiet: bool) -> Result<i32, error::Error> {
let machinectl = CString::new(environment::machinectl_bin()?).unwrap();
let mut args = vec![CString::new("machinectl").unwrap(), CString::new("shell").unwrap()];
if let Some(u) = uid {
args.push(CString::new("--uid").unwrap());
args.push(CString::new(format!("{}", u)).unwrap());
}
if quiet {
args.push(CString::new("--quiet").unwrap());
}
args.push(CString::new("--setenv").unwrap());
args.push(CString::new(format!("SUBSYSTEMCTL_PATH={}", std::env::current_dir().unwrap().display())).unwrap());
args.push(CString::new(".host").unwrap());
args.push(CString::new("/bin/sh").unwrap());
args.push(CString::new("-c").unwrap());
args.push(CString::new("cd \"${SUBSYSTEMCTL_PATH}\"; exec ${SHELL:-sh}").unwrap());
let args_c: Vec<&CStr> = args.iter().map(|s| s.as_c_str()).collect();
enter(
machinectl.as_c_str(),
&args_c.as_slice(),
nix::unistd::Uid::from_raw(0),
nix::unistd::Gid::from_raw(0),
)
}
fn setns_systemd() {
use nix::fcntl::OFlag;
use nix::sched::CloneFlags;
let sd_pid = get_systemd_pid().unwrap().unwrap();
let pidns_path = format!("/proc/{}/ns/pid", sd_pid);
let mntns_path = format!("/proc/{}/ns/mnt", sd_pid);
{
let pidns_fd =
nix::fcntl::open(OsStr::new(&pidns_path), OFlag::O_RDONLY, nix::sys::stat::Mode::empty())
.unwrap();
nix::sched::setns(pidns_fd, CloneFlags::CLONE_NEWPID).unwrap();
nix::unistd::close(pidns_fd).unwrap();
}
{
let mntns_fd =
nix::fcntl::open(OsStr::new(&mntns_path), OFlag::O_RDONLY, nix::sys::stat::Mode::empty())
.unwrap();
nix::sched::setns(mntns_fd, CloneFlags::CLONE_NEWNS).unwrap();
nix::unistd::close(mntns_fd).unwrap();
}
}
fn enter(
path: &CStr,
args: &[&CStr],
uid: nix::unistd::Uid,
gid: nix::unistd::Gid,
) -> Result<i32, error::Error> {
use nix::sys::wait::WaitStatus;
use nix::unistd::ForkResult;
setns_systemd();
match nix::unistd::fork() {
Ok(ForkResult::Child) => {
log::debug!("enter(child): uid={}, gid={}", uid, gid);
nix::unistd::setgroups(&[]).unwrap();
unsafe {
let ent = libc::getpwuid(uid.as_raw() as libc::uid_t);
if !ent.is_null() {
let username = CString::from_raw((*ent).pw_name);
nix::unistd::initgroups(&username, gid).unwrap();
}
}
nix::unistd::setgid(gid).unwrap();
nix::unistd::setuid(uid).unwrap();
log::debug!("execvp {:?}, {:?}", path, args);
match nix::unistd::execvp(path, args) {
Err(nix::Error::Sys(errno)) => {
log::error!("exec failed: {}", errno.desc());
return Ok(128);
}
Err(e) => panic!("{}",e),
Ok(_) => {}
}
panic!("should unreach");
}
Ok(ForkResult::Parent { child, .. }) => {
loop {
match nix::sys::wait::waitpid(child, None) {
Ok(WaitStatus::Exited(_pid, status)) => {
return Ok(status);
}
Ok(WaitStatus::Signaled(_pid, signal, _)) => {
return Ok(128 + (signal as i32));
}
Ok(_) => {} // ignore
Err(nix::Error::Sys(nix::errno::Errno::ECHILD)) => return Ok(128), // ???
Err(nix::Error::Sys(nix::errno::Errno::EINTR)) => {} // ignore
Err(e) => panic!("{}",e),
}
}
}
Err(e) => panic!("{}",e),
}
}
================================================
FILE: src/environment.rs
================================================
use crate::error::Error;
static PROC_MOUNTS: &str = "/proc/self/mounts";
static OSRELEASE_FILE: &str = "/proc/sys/kernel/osrelease";
static WSL_OSRELEASE: &str = "microsoft";
pub fn is_wsl() -> Result<bool, Box<dyn std::error::Error>> {
let osrelease = String::from_utf8(std::fs::read(OSRELEASE_FILE)?)?;
Ok(osrelease.contains(WSL_OSRELEASE))
}
pub fn is_wsl1() -> Result<bool, Box<dyn std::error::Error>> {
log::debug!("Checking is_wsl1");
// Assume we're in WSL 1 or 2.
// Find rootfs is lxfs or not. If it's lxfs, then it's WSL1, otherwise considered 2.
let mounts_buf = String::from_utf8(std::fs::read(PROC_MOUNTS)?)?;
let mut mounts_lines = mounts_buf.lines();
while let Some(mount) = mounts_lines.next() {
let mut iter = mount.split_ascii_whitespace();
iter.next();
let mountpoint_o = iter.next();
let fstype_o = iter.next();
if let (Some(mountpoint), Some(fstype)) = (mountpoint_o, fstype_o) {
log::debug!("Checking is_wsl1: mountpoint={}, fstype={}", mountpoint, fstype);
if mountpoint != "/" {
continue;
}
return Ok(fstype == "lxfs" || fstype == "wslfs");
} else {
return Err(Box::new(Error::InvalidProcMounts));
}
}
Ok(false)
}
pub fn systemd_bin() -> Result<String, Error> {
//if let Ok(bin) = std::env::var("SUBSYSTEMCTL_SYSTEMD_BIN") {
// return Ok(bin);
//}
if std::fs::metadata("/lib/systemd/systemd").is_ok() {
return Ok("/lib/systemd/systemd".to_string());
}
if std::fs::metadata("/usr/lib/systemd/systemd").is_ok() {
return Ok("/usr/lib/systemd/systemd".to_string());
}
Err(Error::NoSystemdFound)
}
pub fn machinectl_bin() -> Result<String, Error> {
// if let Ok(bin) = std::env::var("SUBSYSTEMCTL_MACHINECTL_BIN") {
// return Ok(bin)
// }
if std::fs::metadata("/usr/bin/machinectl").is_ok() {
return Ok("/usr/bin/machinectl".to_string());
}
if std::fs::metadata("/bin/machinectl").is_ok() {
return Ok("/bin/machinectl".to_string());
}
Err(Error::NoSystemdFound)
}
pub fn is_pid1_systemd() -> bool {
if let Ok(cmdline_vec) = std::fs::read("/proc/1/cmdline") {
let cmdline = String::from_utf8(cmdline_vec).unwrap();
let binpath = cmdline.split('\0').next().unwrap();
binpath == systemd_bin().unwrap()
} else {
false
}
}
================================================
FILE: src/error.rs
================================================
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Unexpected format in /proc/self/mounts")]
InvalidProcMounts,
#[error("Systemd not found in standard locations (machinectl and systemd are necessary)")]
NoSystemdFound,
#[error("Systemd is not running")]
NotRunning,
#[error("Something went wrong while waiting")]
WaitFailed,
#[error("Something went wrong while starting, exitstatus={0}")]
StartFailed(i32),
}
================================================
FILE: src/lib.rs
================================================
pub mod environment;
pub mod error;
pub mod bottle;
================================================
FILE: src/main.rs
================================================
use subsystemctl::bottle;
use subsystemctl::environment;
fn main() -> anyhow::Result<()> {
env_logger::from_env(env_logger::Env::default().default_filter_or("info")).init();
let app = clap::App::new(clap::crate_name!())
.version(clap::crate_version!())
.about(clap::crate_description!())
.setting(clap::AppSettings::SubcommandRequired)
.subcommand(
clap::SubCommand::with_name("start")
.about("Start systemd in a Linux namespace (mount, pid)")
.arg(
clap::Arg::with_name("hostname")
.help("Change hostname during start up")
.takes_value(true)
.short("n")
.long("hostname"),
)
.arg(
clap::Arg::with_name("hostname-suffix")
.help("Append a suffix to hostname during startup")
.takes_value(true)
.short("N")
.long("hostname-suffix"),
)
.arg(
clap::Arg::with_name("wait")
.help("Wait systemd-machined to start up")
.short("w")
.long("wait"),
),
)
.subcommand(clap::SubCommand::with_name("stop").about("stop"))
.subcommand(
clap::SubCommand::with_name("shell")
.about("Start a shell in a systemd namespace using machinectl-shell")
.arg(
clap::Arg::with_name("start")
.help("Start systemd when necessary")
.short("s")
.long("start"),
)
.arg(
clap::Arg::with_name("quiet")
.help("Suppress machinectl-shell output")
.short("q")
.long("quiet"),
)
.arg(clap::Arg::with_name("uid").takes_value(true).short("u").long("uid")),
)
.subcommand(
clap::SubCommand::with_name("exec")
.about("Execute a command in a systemd namespace")
.arg(
clap::Arg::with_name("start")
.help("Start systemd when necessary")
.short("s")
.long("start"),
)
.arg(
clap::Arg::with_name("uid")
.help("setuid(2) on exec. Only available for root, default to current uid (getuid(2)")
.takes_value(true)
.short("u")
.long("uid"),
)
.arg(
clap::Arg::with_name("gid")
.help("setgid(2) on exec. Only available for root, default to current gid (getgid(2)")
.takes_value(true)
.short("g")
.long("gid"),
)
.arg(clap::Arg::with_name("command").takes_value(true).multiple(true)
.allow_hyphen_values(true)
.last(true)),
)
.subcommand(
clap::SubCommand::with_name("is-running")
.about("Return 0 if a systemd namespace is running, otherwise 1"),
)
.subcommand(
clap::SubCommand::with_name("is-inside")
.about("Return 0 if invoked from inside of a systemd namespace, otherwise 1"),
);
let matches = app.get_matches();
run_subcommand(matches.subcommand())
}
fn run_subcommand(subcommand: (&str, Option<&clap::ArgMatches>)) -> anyhow::Result<()> {
match subcommand {
("start", Some(m)) => cmd_start(m),
("stop", Some(m)) => cmd_stop(m),
("exec", Some(m)) => cmd_exec(m),
("shell", Some(m)) => cmd_shell(m),
("is-running", Some(m)) => cmd_is_running(m),
("is-inside", Some(m)) => cmd_is_inside(m),
_ => panic!("?"),
}
}
fn cmd_start(m: &clap::ArgMatches) -> anyhow::Result<()> {
check_root()?;
check_prereq()?;
if bottle::is_running() {
log::warn!("systemd is running, not starting again");
return Ok(());
}
let hostname = if m.is_present("hostname") {
Some(m.value_of_lossy("hostname").unwrap().to_string())
} else if m.is_present("hostname-suffix") {
let suffix = m.value_of_lossy("hostname-suffix").unwrap();
Some(std::format!("{}{}", bottle::get_original_hostname()?, suffix))
} else {
None
};
// TODO: resolv.conf
autostart(m.is_present("wait"), hostname)?;
Ok(())
}
fn cmd_stop(_m: &clap::ArgMatches) -> anyhow::Result<()> {
check_root()?;
check_prereq()?;
if !bottle::is_running() {
log::warn!("systemd is already stopped or not running");
return Ok(());
}
if bottle::is_inside() {
return Err(anyhow::anyhow!("Cannot stop from inside of systemd environment"));
}
let r = bottle::stop();
if let Err(e) = r {
return Err(anyhow::anyhow!("Failed to stop: {}", e));
}
Ok(())
}
fn cmd_exec(m: &clap::ArgMatches) -> anyhow::Result<()> {
check_prereq()?;
if !bottle::is_running() {
if m.is_present("start") {
log::info!("Starting systemd");
autostart(true, None)?;
} else {
return Err(anyhow::anyhow!("systemd is not running. Try start it first: subsystemctl start"));
}
}
let cmd = m.values_of_lossy("command");
if cmd.is_none() {
return Err(anyhow::anyhow!("command not given"));
}
let (uid, gid) = extract_uid_gid(m)?;
let r = bottle::exec(cmd.unwrap(), uid, gid);
match r {
Ok(retval) => std::process::exit(retval),
Err(e) => return Err(anyhow::anyhow!("Failed to start: {}", e)),
}
}
fn cmd_shell(m: &clap::ArgMatches) -> anyhow::Result<()> {
check_prereq()?;
if !bottle::is_running() {
if m.is_present("start") {
log::info!("Starting systemd");
autostart(true, None)?;
} else {
return Err(anyhow::anyhow!("systemd is not running. Try start it first: subsystemctl start"));
}
}
let (uid, _gid) = extract_uid_gid(m)?;
let quiet = m.is_present("quiet");
let r = bottle::shell(Some(uid), quiet);
match r {
Ok(retval) => std::process::exit(retval),
Err(e) => return Err(anyhow::anyhow!("Failed to start: {}", e)),
}
}
fn cmd_is_running(_m: &clap::ArgMatches) -> anyhow::Result<()> {
check_prereq()?;
if !bottle::is_running() {
std::process::exit(1);
}
Ok(())
}
fn cmd_is_inside(_m: &clap::ArgMatches) -> anyhow::Result<()> {
check_prereq()?;
if !bottle::is_inside() {
std::process::exit(1);
}
Ok(())
}
fn check_prereq() -> anyhow::Result<()> {
if std::env::var("SUBSYSTEMCTL_IGNORE_WSL_CHECK").is_err() {
if !environment::is_wsl().expect("Cannot check WSL1/2 status") {
return Err(anyhow::anyhow!("not running in WSL1/2; This tool only runs in WSL2"));
}
if environment::is_wsl1().expect("Cannot check WSL1") {
return Err(anyhow::anyhow!("not running in WSL2; This tool only runs in WSL2"));
}
}
Ok(())
}
fn check_root() -> anyhow::Result<()> {
if !nix::unistd::getuid().is_root() {
return Err(anyhow::anyhow!("This subcommand is only available for root"));
}
Ok(())
}
fn extract_uid_gid(m: &clap::ArgMatches) -> anyhow::Result<(nix::unistd::Uid, nix::unistd::Gid)> {
if !nix::unistd::getuid().is_root() {
if m.is_present("uid") || m.is_present("gid") {
return Err(anyhow::anyhow!("uid,gid flags are only available for root"));
}
log::debug!(
"extract_uid_gid: non-root, uid={}, gid={}",
nix::unistd::getuid(),
nix::unistd::getgid()
);
return Ok((nix::unistd::getuid(), nix::unistd::getgid()));
}
let uid = if let Some(id) = m.value_of("uid") {
log::debug!("uid flag: {}", &id);
nix::unistd::Uid::from_raw(id.parse()?)
} else {
nix::unistd::Uid::from_raw(0)
};
let gid = if let Some(id) = m.value_of("gid") {
log::debug!("gid flag: {}", &id);
nix::unistd::Gid::from_raw(id.parse()?)
} else {
if !uid.is_root() {
nix::unistd::Gid::from_raw(uid.as_raw())
} else {
nix::unistd::getgid()
}
};
Ok((uid, gid))
}
fn autostart(wait: bool, hostname: Option<String>) -> anyhow::Result<()> {
environment::machinectl_bin()?;
let r = bottle::start(hostname);
if let Err(e) = r {
return Err(anyhow::anyhow!("Failed to start: {}", e));
}
if wait {
if let Err(e) = bottle::wait() {
return Err(anyhow::anyhow!("Failed to wait machined: {}", e));
}
}
Ok(())
}
gitextract_nwmrkkw_/
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
├── rustfmt.toml
└── src/
├── bottle.rs
├── environment.rs
├── error.rs
├── lib.rs
└── main.rs
SYMBOL INDEX (44 symbols across 4 files)
FILE: src/bottle.rs
constant OS_NONE (line 12) | const OS_NONE: Option<&'static [u8]> = None;
function get_systemd_pid (line 14) | pub fn get_systemd_pid() -> Result<Option<i32>, Box<dyn std::error::Erro...
function put_systemd_pid (line 31) | fn put_systemd_pid(pid: i32) -> std::io::Result<()> {
function zap_systemd_pid (line 36) | fn zap_systemd_pid() -> std::io::Result<()> {
function get_original_hostname (line 43) | pub fn get_original_hostname() -> std::io::Result<String> {
function put_hostname (line 52) | pub fn put_hostname(name: String) -> std::io::Result<()> {
function is_running (line 61) | pub fn is_running() -> bool {
function is_inside (line 75) | pub fn is_inside() -> bool {
function start (line 79) | pub fn start(name: Option<String>) -> Result<(), Box<dyn std::error::Err...
function exec_systemd_ensure_dropin (line 87) | fn exec_systemd_ensure_dropin() -> std::io::Result<()> {
function ensure_hostname (line 115) | fn ensure_hostname(name: String) -> Result<(), Box<dyn std::error::Error...
function exec_systemd (line 131) | fn exec_systemd() -> Result<(), error::Error> {
function exec_systemd0_handle_child_failure (line 143) | fn exec_systemd0_handle_child_failure(r: Result<(), error::Error>) -> Re...
function exec_systemd1_parent (line 155) | fn exec_systemd1_parent(child: nix::unistd::Pid) -> Result<(), error::Er...
function exec_systemd1_child (line 195) | fn exec_systemd1_child(systemd_bin: CString) -> Result<(), error::Error> {
function exec_systemd2_parent (line 209) | fn exec_systemd2_parent(child: nix::unistd::Pid) -> Result<(), error::Er...
function exec_systemd2_child (line 214) | fn exec_systemd2_child(systemd_bin: CString) -> Result<(), error::Error> {
function stop (line 274) | pub fn stop() -> Result<(), Box<dyn std::error::Error>> {
function __libc_current_sigrtmin (line 282) | fn __libc_current_sigrtmin() -> libc::c_int;
function SIGRTMIN_plus_4 (line 286) | fn SIGRTMIN_plus_4() -> nix::sys::signal::Signal {
function wait (line 290) | pub fn wait() -> Result<(), Box<dyn std::error::Error>> {
function wait_internal (line 328) | fn wait_internal(machinectl: String) {
function exec (line 344) | pub fn exec(cmdline: Vec<String>, uid: nix::unistd::Uid, gid: nix::unist...
function shell (line 351) | pub fn shell(uid: Option<nix::unistd::Uid>, quiet: bool) -> Result<i32, ...
function setns_systemd (line 381) | fn setns_systemd() {
function enter (line 406) | fn enter(
FILE: src/environment.rs
function is_wsl (line 7) | pub fn is_wsl() -> Result<bool, Box<dyn std::error::Error>> {
function is_wsl1 (line 12) | pub fn is_wsl1() -> Result<bool, Box<dyn std::error::Error>> {
function systemd_bin (line 39) | pub fn systemd_bin() -> Result<String, Error> {
function machinectl_bin (line 52) | pub fn machinectl_bin() -> Result<String, Error> {
function is_pid1_systemd (line 65) | pub fn is_pid1_systemd() -> bool {
FILE: src/error.rs
type Error (line 2) | pub enum Error {
FILE: src/main.rs
function main (line 4) | fn main() -> anyhow::Result<()> {
function run_subcommand (line 92) | fn run_subcommand(subcommand: (&str, Option<&clap::ArgMatches>)) -> anyh...
function cmd_start (line 104) | fn cmd_start(m: &clap::ArgMatches) -> anyhow::Result<()> {
function cmd_stop (line 126) | fn cmd_stop(_m: &clap::ArgMatches) -> anyhow::Result<()> {
function cmd_exec (line 144) | fn cmd_exec(m: &clap::ArgMatches) -> anyhow::Result<()> {
function cmd_shell (line 167) | fn cmd_shell(m: &clap::ArgMatches) -> anyhow::Result<()> {
function cmd_is_running (line 187) | fn cmd_is_running(_m: &clap::ArgMatches) -> anyhow::Result<()> {
function cmd_is_inside (line 195) | fn cmd_is_inside(_m: &clap::ArgMatches) -> anyhow::Result<()> {
function check_prereq (line 203) | fn check_prereq() -> anyhow::Result<()> {
function check_root (line 215) | fn check_root() -> anyhow::Result<()> {
function extract_uid_gid (line 222) | fn extract_uid_gid(m: &clap::ArgMatches) -> anyhow::Result<(nix::unistd:...
function autostart (line 254) | fn autostart(wait: bool, hostname: Option<String>) -> anyhow::Result<()> {
Condensed preview — 10 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (35K chars).
[
{
"path": ".gitignore",
"chars": 8,
"preview": "/target\n"
},
{
"path": "Cargo.toml",
"chars": 536,
"preview": "[package]\nname = \"subsystemctl\"\nversion = \"0.2.0\"\ndescription = \"Utility to run systemd in WSL2 with a Linux namespace\"\n"
},
{
"path": "LICENSE",
"chars": 1071,
"preview": "MIT License\n\nCopyright (c) 2020 Sorah Fukumori\n\nPermission is hereby granted, free of charge, to any person obtaining a "
},
{
"path": "README.md",
"chars": 3627,
"preview": "# subsystemd: Run systemd in WSL2\n\n__Maintanence Note:__ You may want to use [nullpo-head/wsl-distrod](https://github.co"
},
{
"path": "rustfmt.toml",
"chars": 68,
"preview": "max_width = 110\nuse_small_heuristics = \"Max\"\nnewline_style = \"Unix\"\n"
},
{
"path": "src/bottle.rs",
"chars": 15309,
"preview": "use crate::environment;\nuse crate::error;\nuse std::ffi::CStr;\nuse std::ffi::CString;\nuse std::ffi::OsStr;\n\nstatic RUNTIM"
},
{
"path": "src/environment.rs",
"chars": 2464,
"preview": "use crate::error::Error;\nstatic PROC_MOUNTS: &str = \"/proc/self/mounts\";\n\nstatic OSRELEASE_FILE: &str = \"/proc/sys/kerne"
},
{
"path": "src/error.rs",
"chars": 463,
"preview": "#[derive(thiserror::Error, Debug)]\npub enum Error {\n #[error(\"Unexpected format in /proc/self/mounts\")]\n InvalidPr"
},
{
"path": "src/lib.rs",
"chars": 52,
"preview": "pub mod environment;\npub mod error;\npub mod bottle;\n"
},
{
"path": "src/main.rs",
"chars": 9095,
"preview": "use subsystemctl::bottle;\nuse subsystemctl::environment;\n\nfn main() -> anyhow::Result<()> {\n env_logger::from_env(env"
}
]
About this extraction
This page contains the full source code of the sorah/subsystemctl GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 10 files (31.9 KB), approximately 8.7k tokens, and a symbol index with 44 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.