[
  {
    "path": ".gitignore",
    "content": "/target\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"subsystemctl\"\nversion = \"0.2.0\"\ndescription = \"Utility to run systemd in WSL2 with a Linux namespace\"\nhomepage = \"https://github.com/sorah/subsystemctl\"\nrepository = \"https://github.com/sorah/subsystemctl\"\nauthors = [\"Sorah Fukumori <her@sorah.jp>\"]\nlicense = \"MIT\"\nedition = \"2018\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\nanyhow = \"1.0.31\"\nthiserror = \"1.0.19\"\nclap = \"2.33.1\"\nnix = \"0.17.0\"\nlibc = \"0.2.71\"\nenv_logger = \"0.7.1\"\nlog = \"0.4.8\"\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Sorah Fukumori\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# subsystemd: Run systemd in WSL2\n\n__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.\n\nRun systemd under Linux namespace in WSL2. Heavily inspired by [arkane-systems/genie][genie], but written in Rust.\n\n## Difference with arkane-systems/genie\n\nSlightly following [genie]'s behavior, but noted below...\n\n- Interface\n  - Command line interface is not compatible.\n- Behavior\n  - Hostname altertion is optional with `--hostname`, `--hostname-suffix`\n    - `/etc/hosts` are not updated. Users are encouraged to use `nss-myhostname`.\n  - Uses `machinectl shell` to launch a user shell; this allows running systemd user session\n- Internal\n  - Removed dependency to `unshare`, `daemonize`, `nsenter` command line tools\n  - systemd-wide environment variables are set via `systemd.conf` drop-in, using `DefaultEnvironment=`\n  - systemd PID from root namespace is stored at `/run/subsystemctl/systemd.pid`\n\n## Install\n\n### Arch Linux\n\nPKGBUILD: https://github.com/sorah/arch.sorah.jp/tree/master/aur-sorah/PKGBUILDs/subsystemctl\n\n_(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).)_\n\n### Debian/Ubuntu\n\nRefer to https://github.com/nkmideb/subsystemctl for debian source.\n\nPre-built package binaries available at https://github.com/nkmideb/subsystemctl/releases for your convenient.\n\n### Self build\n\n```\ncargo install subsystemctl\n```\n\nor from git source:\n\n```bash\ncargo build --release\ninstall -m6755 -oroot -groot ./target/release/subsystemctl /usr/local/bin/subsystemctl\n```\n\n## Usage\n\n### `subsystemctl start`: Start `systemd` environment\n\n```ps1\nPS> wsl -u root -- subsystemctl start\n```\n\n### `subsystemctl shell`: shell login to systemd-enabled environment\n\n```ps1\nPS> wsl subsystemctl shell\nConnected to the local host. Press ^] three times within 1s to exit session.\nsomeone@hostname$ ...\n```\n\n#### Specifying uid to login\n\n```ps1\nPS> wsl -u root -- subsystemctl shell --uid=1000\nConnected to the local host. Press ^] three times within 1s to exit session.\nsomeone@hostname$ ...\n```\n\n#### Automatically starting and entering a user shell\n\n```ps1\nPS> wsl -u root -d Arch -- subsystemctl shell --uid=1000 --start\n[2021-06-27T16:32:20Z INFO  subsystemctl] Starting systemd\nConnected to the local host. Press ^] three times within 1s to exit session.\n\nsomeone@hostname$ ...\n```\n\n### `subsystemctl exec`: Raw `nsenter` like interface\n\n```ps1\nPS> wsl subsystemctl exec id\nuid=1000(sorah) gid=1000(sorah) groups=1000(sorah),116(admin)\n```\n\n#### Specifying uid (and gid)\n\n```ps1\nPS> wsl -u root -- subsystemctl exec id\nuid=0(root) gid=0(root) groups=0(root)\n\nPS> wsl -u root -- subsystemctl exec --uid=1000 id\nuid=1000(sorah) gid=1000(sorah) groups=1000(sorah),116(admin)\n\nPS> wsl -u root -- subsystemctl exec --uid=1000 --gid=116 id\nuid=1000(sorah) gid=116(admin) groups=116(admin)\n```\n\n### `subsystemctl is-running`\n\n```bash\n#!/bin/bash\nif subsystemctl is-running; then\n  echo \"running\"\nelse\n  echo \"not-running\"\nfi\n```\n\n### `subsystemctl is-inside`\n\n```bash\n#!/bin/bash\nif subsystemctl is-inside; then\n  echo \"inside\"\nelse\n  echo \"outside\"\nfi\n```\n\n## Tips\n\n### systemd-resolved, networkd are recommended to be disabled\n\notherwise `/etc/resolv.conf` might get overwritten to resolved stub-resolver.\n\n## Author\n\nSorah Fukumori https://sorah.jp/\n\n## License\n\nMIT\n\n\n[genie]: https://github.com/arkane-systems/genie\n"
  },
  {
    "path": "rustfmt.toml",
    "content": "max_width = 110\nuse_small_heuristics = \"Max\"\nnewline_style = \"Unix\"\n"
  },
  {
    "path": "src/bottle.rs",
    "content": "use crate::environment;\nuse crate::error;\nuse std::ffi::CStr;\nuse std::ffi::CString;\nuse std::ffi::OsStr;\n\nstatic RUNTIME_DIR: &str = \"/run/subsystemctl\";\nstatic PID_FILE: &str = \"/run/subsystemctl/systemd.pid\";\nstatic HOSTNAME_FILE: &str = \"/run/subsystemctl/hostname\";\nstatic ORIG_HOSTNAME_FILE: &str = \"/run/subsystemctl/hostname.orig\";\n\nconst OS_NONE: Option<&'static [u8]> = None;\n\npub fn get_systemd_pid() -> Result<Option<i32>, Box<dyn std::error::Error>> {\n    let result = std::fs::read(PID_FILE);\n    match result {\n        Ok(buf) => {\n            let pid: i32 = String::from_utf8(buf)?.trim().parse()?;\n            Ok(Some(pid))\n        }\n        Err(e) => {\n            if e.kind() == std::io::ErrorKind::NotFound {\n                Ok(None)\n            } else {\n                Err(Box::new(e))\n            }\n        }\n    }\n}\n\nfn put_systemd_pid(pid: i32) -> std::io::Result<()> {\n    std::fs::create_dir_all(RUNTIME_DIR)?;\n    std::fs::write(PID_FILE, format!(\"{}\\n\", pid))\n}\n\nfn zap_systemd_pid() -> std::io::Result<()> {\n    if std::fs::metadata(PID_FILE).is_ok() {\n        std::fs::remove_file(PID_FILE)?;\n    }\n    Ok(())\n}\n\npub fn get_original_hostname() -> std::io::Result<String> {\n    let buf = if std::fs::metadata(ORIG_HOSTNAME_FILE).is_ok() {\n        std::fs::read(ORIG_HOSTNAME_FILE)\n    } else {\n        std::fs::read(\"/etc/hostname\")\n    }?;\n    Ok(String::from_utf8(buf).unwrap().trim().to_owned())\n}\n\npub fn put_hostname(name: String) -> std::io::Result<()> {\n    std::fs::create_dir_all(RUNTIME_DIR)?;\n    if std::fs::metadata(ORIG_HOSTNAME_FILE).is_err() {\n        let orig_hostname = std::fs::read(\"/etc/hostname\")?;\n        std::fs::write(ORIG_HOSTNAME_FILE, orig_hostname)?\n    }\n    std::fs::write(HOSTNAME_FILE, format!(\"{}\\n\", name))\n}\n\npub fn is_running() -> bool {\n    if environment::is_pid1_systemd() {\n        return true;\n    }\n    if let Ok(pid_o) = get_systemd_pid() {\n        if let Some(pid) = pid_o {\n            if let Ok(meta) = std::fs::metadata(std::format!(\"/proc/{}\", pid)) {\n                return meta.is_dir();\n            }\n        }\n    }\n    false\n}\n\npub fn is_inside() -> bool {\n    environment::is_pid1_systemd()\n}\n\npub fn start(name: Option<String>) -> Result<(), Box<dyn std::error::Error>> {\n    if let Some(hostname) = name {\n        ensure_hostname(hostname)?;\n    }\n    exec_systemd_ensure_dropin()?;\n    Ok(exec_systemd()?)\n}\n\nfn exec_systemd_ensure_dropin() -> std::io::Result<()> {\n    std::fs::create_dir_all(\"/run/systemd/system.conf.d\")?;\n\n    let envs = vec![\"WSL_INTEROP\", \"WSL_DISTRO_NAME\", \"WSL_NAME\", \"WT_SESSION\", \"WT_PROFILE_ID\"];\n\n    let envvar_strs: Vec<String> = envs\n        .into_iter()\n        .filter_map(|name| {\n            let e = std::env::var(name);\n            if let Ok(env) = e {\n                if env.contains(\"\\\"\") {\n                    None // XXX:\n                } else {\n                    Some(format!(\"\\\"{}={}\\\"\", name, env))\n                }\n            } else {\n                None\n            }\n        })\n        .collect();\n\n    let dropin = format!(\n        \"[Manager]\\nDefaultEnvironment=INSIDE_GENIE=1 INSIDE_SUBSYSTEMCTL=1 {}\\n\",\n        envvar_strs.join(\" \")\n    );\n    std::fs::write(\"/run/systemd/system.conf.d/10-subsystemctl-env.conf\", dropin)\n}\n\nfn ensure_hostname(name: String) -> Result<(), Box<dyn std::error::Error>> {\n    let needs_bind = std::fs::metadata(HOSTNAME_FILE).is_err();\n    put_hostname(name)?;\n    if !needs_bind {\n        return Ok(());\n    }\n    nix::mount::mount(\n        Some(OsStr::new(HOSTNAME_FILE)),\n        OsStr::new(\"/etc/hostname\"),\n        OS_NONE,\n        nix::mount::MsFlags::MS_BIND,\n        OS_NONE,\n    )?;\n    Ok(())\n}\n\nfn exec_systemd() -> Result<(), error::Error> {\n    use nix::unistd::ForkResult;\n\n    let systemd_bin = CString::new(environment::systemd_bin().unwrap()).unwrap();\n\n    match nix::unistd::fork() {\n        Ok(ForkResult::Child) => exec_systemd0_handle_child_failure(exec_systemd1_child(systemd_bin)),\n        Ok(ForkResult::Parent { child, .. }) => exec_systemd1_parent(child),\n        Err(e) => panic!(\"{}\",e),\n    }\n}\n\nfn exec_systemd0_handle_child_failure(r: Result<(), error::Error>) -> Result<(), error::Error> {\n    match r {\n        Ok(_) => {} // do nothing\n        Err(error::Error::StartFailed(exitstatus)) => {\n            log::error!(\"Something went wrong while starting\");\n            std::process::exit(exitstatus);\n        }\n        Err(e) => panic!(\"{}\",e),\n    }\n    std::process::exit(0);\n}\n\nfn exec_systemd1_parent(child: nix::unistd::Pid) -> Result<(), error::Error> {\n    use nix::sys::wait::WaitStatus;\n\n    // TODO: monitor systemd status instead of pid file\n    loop {\n        match get_systemd_pid() {\n            Ok(Some(pid)) => {\n                log::debug!(\"Watching pid: child_pid={}, pid={}\", child, pid);\n                let pidns_path = format!(\"/proc/{}/ns/pid\", pid);\n                let mntns_path = format!(\"/proc/{}/ns/mnt\", pid);\n                if std::fs::metadata(pidns_path).is_ok() && std::fs::metadata(mntns_path).is_ok() {\n                    break;\n                }\n            }\n            Ok(None) => {\n                log::debug!(\"Watching pid: none\");\n            }\n            Err(e) => {\n                log::debug!(\"Watching pid: e={:?}\", e);\n            }\n        }\n        match nix::sys::wait::waitpid(child, Some(nix::sys::wait::WaitPidFlag::WNOWAIT)) {\n            Ok(WaitStatus::Exited(_pid, status)) => {\n                return Err(error::Error::StartFailed(status));\n            }\n            Ok(WaitStatus::Signaled(_pid, signal, _)) => {\n                return Err(error::Error::StartFailed(128 + (signal as i32)));\n            }\n            Err(nix::Error::Sys(nix::errno::Errno::ECHILD)) => {\n                return Err(error::Error::StartFailed(128));\n            }\n            _ => {} // ignore,\n        }\n        std::thread::sleep(std::time::Duration::from_millis(500));\n    }\n\n    // TODO:\n    Ok(())\n}\n\nfn exec_systemd1_child(systemd_bin: CString) -> Result<(), error::Error> {\n    use nix::sched::CloneFlags;\n    use nix::unistd::ForkResult;\n\n    nix::sched::unshare(CloneFlags::CLONE_NEWNS | CloneFlags::CLONE_NEWPID).unwrap();\n    nix::unistd::setsid().unwrap();\n\n    match nix::unistd::fork() {\n        Ok(ForkResult::Child) => exec_systemd0_handle_child_failure(exec_systemd2_child(systemd_bin)),\n        Ok(ForkResult::Parent { child, .. }) => exec_systemd2_parent(child),\n        Err(e) => panic!(\"{}\",e),\n    }\n}\n\nfn exec_systemd2_parent(child: nix::unistd::Pid) -> Result<(), error::Error> {\n    put_systemd_pid(child.as_raw()).unwrap();\n    std::process::exit(0);\n}\n\nfn exec_systemd2_child(systemd_bin: CString) -> Result<(), error::Error> {\n    use nix::fcntl::OFlag;\n    use nix::mount::MsFlags;\n    use std::os::unix::io::RawFd;\n\n    nix::mount::mount(\n        Some(OsStr::new(\"none\")),\n        OsStr::new(\"/\"),\n        OS_NONE,\n        MsFlags::MS_REC | MsFlags::MS_SHARED,\n        OS_NONE,\n    )\n    .expect(\"set_propagation mount failure\");\n\n    nix::mount::mount(\n        Some(OsStr::new(\"none\")),\n        OsStr::new(\"/proc\"),\n        OS_NONE,\n        MsFlags::MS_REC | MsFlags::MS_PRIVATE,\n        OS_NONE,\n    )\n    .expect(\"proc propagation mount failure\");\n\n    nix::mount::mount(\n        Some(OsStr::new(\"proc\")),\n        OsStr::new(\"/proc\"),\n        Some(OsStr::new(\"proc\")),\n        MsFlags::MS_NOSUID | MsFlags::MS_NODEV | MsFlags::MS_NOEXEC,\n        OS_NONE,\n    )\n    .expect(\"proc mount failure\");\n\n    nix::unistd::chdir(\"/\").unwrap();\n    nix::unistd::setgid(nix::unistd::Gid::from_raw(0)).unwrap();\n    nix::unistd::setuid(nix::unistd::Uid::from_raw(0)).unwrap();\n\n    match nix::unistd::close(0 as RawFd) {\n        Ok(_) => {}\n        Err(nix::Error::Sys(nix::errno::Errno::EBADF)) => {}\n        Err(e) => panic!(\"{}\",e),\n    }\n    match nix::unistd::close(1 as RawFd) {\n        Ok(_) => {}\n        Err(nix::Error::Sys(nix::errno::Errno::EBADF)) => {}\n        Err(e) => panic!(\"{}\",e),\n    }\n    match nix::unistd::close(2 as RawFd) {\n        Ok(_) => {}\n        Err(nix::Error::Sys(nix::errno::Errno::EBADF)) => {}\n        Err(e) => panic!(\"{}\",e),\n    }\n\n    nix::fcntl::open(OsStr::new(\"/dev/null\"), OFlag::O_RDONLY, nix::sys::stat::Mode::empty()).unwrap();\n    nix::fcntl::open(OsStr::new(\"/dev/null\"), OFlag::O_WRONLY, nix::sys::stat::Mode::empty()).unwrap();\n    nix::fcntl::open(OsStr::new(\"/dev/null\"), OFlag::O_WRONLY, nix::sys::stat::Mode::empty()).unwrap();\n\n    nix::unistd::execve(systemd_bin.as_c_str(), &[systemd_bin.as_c_str()], &[]).unwrap();\n    panic!(\"should unreach\");\n}\n\npub fn stop() -> Result<(), Box<dyn std::error::Error>> {\n    let systemd_pid = get_systemd_pid().unwrap().unwrap();\n    nix::sys::signal::kill(nix::unistd::Pid::from_raw(systemd_pid), Some(SIGRTMIN_plus_4()))?;\n    zap_systemd_pid().unwrap();\n    Ok(())\n}\n\nextern \"C\" {\n    fn __libc_current_sigrtmin() -> libc::c_int;\n}\n\n#[allow(non_snake_case)]\nfn SIGRTMIN_plus_4() -> nix::sys::signal::Signal {\n    unsafe { std::mem::transmute(__libc_current_sigrtmin() + 4) }\n}\n\npub fn wait() -> Result<(), Box<dyn std::error::Error>> {\n    use nix::sys::wait::WaitStatus;\n    use nix::unistd::ForkResult;\n\n    log::debug!(\"Waiting systemd-machined to start\");\n    let machinectl = environment::machinectl_bin()?;\n\n    match nix::unistd::fork() {\n        Ok(ForkResult::Child) => {\n            wait_internal(machinectl);\n            std::process::exit(0);\n        }\n        Ok(ForkResult::Parent { child, .. }) => {\n            loop {\n                match nix::sys::wait::waitpid(child, None) {\n                    Ok(WaitStatus::Exited(_pid, status)) => {\n                        if status == 0 {\n                            log::debug!(\"machined is now up\");\n                            return Ok(());\n                        } else {\n                            return Err(Box::new(error::Error::WaitFailed));\n                        }\n                    }\n                    Ok(WaitStatus::Signaled(_pid, _signal, _)) => {\n                        return Err(Box::new(error::Error::WaitFailed));\n                    }\n                    Ok(_) => {}                                                       // ignore\n                    Err(nix::Error::Sys(nix::errno::Errno::ECHILD)) => return Ok(()), // ???\n                    Err(nix::Error::Sys(nix::errno::Errno::EINTR)) => {}              // ignore\n                    Err(e) => panic!(\"{}\",e),\n                }\n            }\n        }\n\n        Err(e) => panic!(\"{}\",e),\n    }\n}\n\nfn wait_internal(machinectl: String) {\n    setns_systemd();\n    loop {\n        let cmd = std::process::Command::new(&machinectl)\n            .arg(\"list\")\n            .output()\n            .expect(\"failed to execute machinectl list\");\n        if cmd.status.success() {\n            break;\n        }\n        std::thread::sleep(std::time::Duration::from_millis(600));\n        log::debug!(\"Still waiting systemd-machined to start\");\n    }\n    log::debug!(\"systemd-machined is up (internal)\");\n}\n\npub fn exec(cmdline: Vec<String>, uid: nix::unistd::Uid, gid: nix::unistd::Gid) -> Result<i32, error::Error> {\n    let args_string: Vec<CString> = cmdline.into_iter().map(|a| CString::new(a).unwrap()).collect();\n    let args: Vec<&CStr> = args_string.iter().map(|s| s.as_c_str()).collect();\n\n    enter(args[0], args.as_slice(), uid, gid)\n}\n\npub fn shell(uid: Option<nix::unistd::Uid>, quiet: bool) -> Result<i32, error::Error> {\n    let machinectl = CString::new(environment::machinectl_bin()?).unwrap();\n    let mut args = vec![CString::new(\"machinectl\").unwrap(), CString::new(\"shell\").unwrap()];\n\n    if let Some(u) = uid {\n        args.push(CString::new(\"--uid\").unwrap());\n        args.push(CString::new(format!(\"{}\", u)).unwrap());\n    }\n    if quiet {\n        args.push(CString::new(\"--quiet\").unwrap());\n    }\n\n    args.push(CString::new(\"--setenv\").unwrap());\n    args.push(CString::new(format!(\"SUBSYSTEMCTL_PATH={}\", std::env::current_dir().unwrap().display())).unwrap());\n\n    args.push(CString::new(\".host\").unwrap());\n\n    args.push(CString::new(\"/bin/sh\").unwrap());\n    args.push(CString::new(\"-c\").unwrap());\n    args.push(CString::new(\"cd \\\"${SUBSYSTEMCTL_PATH}\\\"; exec ${SHELL:-sh}\").unwrap());\n\n    let args_c: Vec<&CStr> = args.iter().map(|s| s.as_c_str()).collect();\n    enter(\n        machinectl.as_c_str(),\n        &args_c.as_slice(),\n        nix::unistd::Uid::from_raw(0),\n        nix::unistd::Gid::from_raw(0),\n    )\n}\n\nfn setns_systemd() {\n    use nix::fcntl::OFlag;\n    use nix::sched::CloneFlags;\n\n    let sd_pid = get_systemd_pid().unwrap().unwrap();\n    let pidns_path = format!(\"/proc/{}/ns/pid\", sd_pid);\n    let mntns_path = format!(\"/proc/{}/ns/mnt\", sd_pid);\n\n    {\n        let pidns_fd =\n            nix::fcntl::open(OsStr::new(&pidns_path), OFlag::O_RDONLY, nix::sys::stat::Mode::empty())\n                .unwrap();\n        nix::sched::setns(pidns_fd, CloneFlags::CLONE_NEWPID).unwrap();\n        nix::unistd::close(pidns_fd).unwrap();\n    }\n\n    {\n        let mntns_fd =\n            nix::fcntl::open(OsStr::new(&mntns_path), OFlag::O_RDONLY, nix::sys::stat::Mode::empty())\n                .unwrap();\n        nix::sched::setns(mntns_fd, CloneFlags::CLONE_NEWNS).unwrap();\n        nix::unistd::close(mntns_fd).unwrap();\n    }\n}\n\nfn enter(\n    path: &CStr,\n    args: &[&CStr],\n    uid: nix::unistd::Uid,\n    gid: nix::unistd::Gid,\n) -> Result<i32, error::Error> {\n    use nix::sys::wait::WaitStatus;\n    use nix::unistd::ForkResult;\n\n    setns_systemd();\n\n    match nix::unistd::fork() {\n        Ok(ForkResult::Child) => {\n            log::debug!(\"enter(child): uid={}, gid={}\", uid, gid);\n\n            nix::unistd::setgroups(&[]).unwrap();\n\n            unsafe {\n                let ent = libc::getpwuid(uid.as_raw() as libc::uid_t);\n                if !ent.is_null() {\n                    let username = CString::from_raw((*ent).pw_name);\n                    nix::unistd::initgroups(&username, gid).unwrap();\n                }\n            }\n\n            nix::unistd::setgid(gid).unwrap();\n            nix::unistd::setuid(uid).unwrap();\n\n            log::debug!(\"execvp {:?}, {:?}\", path, args);\n            match nix::unistd::execvp(path, args) {\n                Err(nix::Error::Sys(errno)) => {\n                    log::error!(\"exec failed: {}\", errno.desc());\n                    return Ok(128);\n                }\n                Err(e) => panic!(\"{}\",e),\n                Ok(_) => {}\n            }\n            panic!(\"should unreach\");\n        }\n        Ok(ForkResult::Parent { child, .. }) => {\n            loop {\n                match nix::sys::wait::waitpid(child, None) {\n                    Ok(WaitStatus::Exited(_pid, status)) => {\n                        return Ok(status);\n                    }\n                    Ok(WaitStatus::Signaled(_pid, signal, _)) => {\n                        return Ok(128 + (signal as i32));\n                    }\n                    Ok(_) => {}                                                        // ignore\n                    Err(nix::Error::Sys(nix::errno::Errno::ECHILD)) => return Ok(128), // ???\n                    Err(nix::Error::Sys(nix::errno::Errno::EINTR)) => {}               // ignore\n                    Err(e) => panic!(\"{}\",e),\n                }\n            }\n        }\n\n        Err(e) => panic!(\"{}\",e),\n    }\n}\n"
  },
  {
    "path": "src/environment.rs",
    "content": "use crate::error::Error;\nstatic PROC_MOUNTS: &str = \"/proc/self/mounts\";\n\nstatic OSRELEASE_FILE: &str = \"/proc/sys/kernel/osrelease\";\nstatic WSL_OSRELEASE: &str = \"microsoft\";\n\npub fn is_wsl() -> Result<bool, Box<dyn std::error::Error>> {\n    let osrelease = String::from_utf8(std::fs::read(OSRELEASE_FILE)?)?;\n    Ok(osrelease.contains(WSL_OSRELEASE))\n}\n\npub fn is_wsl1() -> Result<bool, Box<dyn std::error::Error>> {\n    log::debug!(\"Checking is_wsl1\");\n    // Assume we're in WSL 1 or 2.\n    // Find rootfs is lxfs or not. If it's lxfs, then it's WSL1, otherwise considered 2.\n\n    let mounts_buf = String::from_utf8(std::fs::read(PROC_MOUNTS)?)?;\n    let mut mounts_lines = mounts_buf.lines();\n\n    while let Some(mount) = mounts_lines.next() {\n        let mut iter = mount.split_ascii_whitespace();\n        iter.next();\n        let mountpoint_o = iter.next();\n        let fstype_o = iter.next();\n        if let (Some(mountpoint), Some(fstype)) = (mountpoint_o, fstype_o) {\n            log::debug!(\"Checking is_wsl1: mountpoint={}, fstype={}\", mountpoint, fstype);\n            if mountpoint != \"/\" {\n                continue;\n            }\n            return Ok(fstype == \"lxfs\" || fstype == \"wslfs\");\n        } else {\n            return Err(Box::new(Error::InvalidProcMounts));\n        }\n    }\n\n    Ok(false)\n}\n\npub fn systemd_bin() -> Result<String, Error> {\n    //if let Ok(bin) = std::env::var(\"SUBSYSTEMCTL_SYSTEMD_BIN\") {\n    //    return Ok(bin);\n    //}\n    if std::fs::metadata(\"/lib/systemd/systemd\").is_ok() {\n        return Ok(\"/lib/systemd/systemd\".to_string());\n    }\n    if std::fs::metadata(\"/usr/lib/systemd/systemd\").is_ok() {\n        return Ok(\"/usr/lib/systemd/systemd\".to_string());\n    }\n    Err(Error::NoSystemdFound)\n}\n\npub fn machinectl_bin() -> Result<String, Error> {\n    // if let Ok(bin) = std::env::var(\"SUBSYSTEMCTL_MACHINECTL_BIN\") {\n    //   return Ok(bin)\n    // }\n    if std::fs::metadata(\"/usr/bin/machinectl\").is_ok() {\n        return Ok(\"/usr/bin/machinectl\".to_string());\n    }\n    if std::fs::metadata(\"/bin/machinectl\").is_ok() {\n        return Ok(\"/bin/machinectl\".to_string());\n    }\n    Err(Error::NoSystemdFound)\n}\n\npub fn is_pid1_systemd() -> bool {\n    if let Ok(cmdline_vec) = std::fs::read(\"/proc/1/cmdline\") {\n        let cmdline = String::from_utf8(cmdline_vec).unwrap();\n        let binpath = cmdline.split('\\0').next().unwrap();\n\n        binpath == systemd_bin().unwrap()\n    } else {\n        false\n    }\n}\n"
  },
  {
    "path": "src/error.rs",
    "content": "#[derive(thiserror::Error, Debug)]\npub enum Error {\n    #[error(\"Unexpected format in /proc/self/mounts\")]\n    InvalidProcMounts,\n\n    #[error(\"Systemd not found in standard locations (machinectl and systemd are necessary)\")]\n    NoSystemdFound,\n\n    #[error(\"Systemd is not running\")]\n    NotRunning,\n\n    #[error(\"Something went wrong while waiting\")]\n    WaitFailed,\n\n    #[error(\"Something went wrong while starting, exitstatus={0}\")]\n    StartFailed(i32),\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "pub mod environment;\npub mod error;\npub mod bottle;\n"
  },
  {
    "path": "src/main.rs",
    "content": "use subsystemctl::bottle;\nuse subsystemctl::environment;\n\nfn main() -> anyhow::Result<()> {\n    env_logger::from_env(env_logger::Env::default().default_filter_or(\"info\")).init();\n\n    let app = clap::App::new(clap::crate_name!())\n        .version(clap::crate_version!())\n        .about(clap::crate_description!())\n        .setting(clap::AppSettings::SubcommandRequired)\n        .subcommand(\n            clap::SubCommand::with_name(\"start\")\n                .about(\"Start systemd in a Linux namespace (mount, pid)\")\n                .arg(\n                    clap::Arg::with_name(\"hostname\")\n                        .help(\"Change hostname during start up\")\n                        .takes_value(true)\n                        .short(\"n\")\n                        .long(\"hostname\"),\n                )\n                .arg(\n                    clap::Arg::with_name(\"hostname-suffix\")\n                        .help(\"Append a suffix to hostname during startup\")\n                        .takes_value(true)\n                        .short(\"N\")\n                        .long(\"hostname-suffix\"),\n                )\n                .arg(\n                    clap::Arg::with_name(\"wait\")\n                        .help(\"Wait systemd-machined to start up\")\n                        .short(\"w\")\n                        .long(\"wait\"),\n                ),\n        )\n        .subcommand(clap::SubCommand::with_name(\"stop\").about(\"stop\"))\n        .subcommand(\n            clap::SubCommand::with_name(\"shell\")\n                .about(\"Start a shell in a systemd namespace using machinectl-shell\")\n                .arg(\n                    clap::Arg::with_name(\"start\")\n                        .help(\"Start systemd when necessary\")\n                        .short(\"s\")\n                        .long(\"start\"),\n                )\n                .arg(\n                    clap::Arg::with_name(\"quiet\")\n                        .help(\"Suppress machinectl-shell output\")\n                        .short(\"q\")\n                        .long(\"quiet\"),\n                )\n                .arg(clap::Arg::with_name(\"uid\").takes_value(true).short(\"u\").long(\"uid\")),\n        )\n        .subcommand(\n            clap::SubCommand::with_name(\"exec\")\n                .about(\"Execute a command in a systemd namespace\")\n                .arg(\n                    clap::Arg::with_name(\"start\")\n                        .help(\"Start systemd when necessary\")\n                        .short(\"s\")\n                        .long(\"start\"),\n                )\n                .arg(\n                    clap::Arg::with_name(\"uid\")\n                        .help(\"setuid(2) on exec. Only available for root, default to current uid (getuid(2)\")\n                        .takes_value(true)\n                        .short(\"u\")\n                        .long(\"uid\"),\n                )\n                .arg(\n                    clap::Arg::with_name(\"gid\")\n                        .help(\"setgid(2) on exec. Only available for root, default to current gid (getgid(2)\")\n                        .takes_value(true)\n                        .short(\"g\")\n                        .long(\"gid\"),\n                )\n                .arg(clap::Arg::with_name(\"command\").takes_value(true).multiple(true)\n                .allow_hyphen_values(true)\n                .last(true)),\n        )\n        .subcommand(\n            clap::SubCommand::with_name(\"is-running\")\n                .about(\"Return 0 if a systemd namespace is running, otherwise 1\"),\n        )\n        .subcommand(\n            clap::SubCommand::with_name(\"is-inside\")\n                .about(\"Return 0 if invoked from inside of a systemd namespace, otherwise 1\"),\n        );\n    let matches = app.get_matches();\n    run_subcommand(matches.subcommand())\n}\n\nfn run_subcommand(subcommand: (&str, Option<&clap::ArgMatches>)) -> anyhow::Result<()> {\n    match subcommand {\n        (\"start\", Some(m)) => cmd_start(m),\n        (\"stop\", Some(m)) => cmd_stop(m),\n        (\"exec\", Some(m)) => cmd_exec(m),\n        (\"shell\", Some(m)) => cmd_shell(m),\n        (\"is-running\", Some(m)) => cmd_is_running(m),\n        (\"is-inside\", Some(m)) => cmd_is_inside(m),\n        _ => panic!(\"?\"),\n    }\n}\n\nfn cmd_start(m: &clap::ArgMatches) -> anyhow::Result<()> {\n    check_root()?;\n    check_prereq()?;\n    if bottle::is_running() {\n        log::warn!(\"systemd is running, not starting again\");\n        return Ok(());\n    }\n    let hostname = if m.is_present(\"hostname\") {\n        Some(m.value_of_lossy(\"hostname\").unwrap().to_string())\n    } else if m.is_present(\"hostname-suffix\") {\n        let suffix = m.value_of_lossy(\"hostname-suffix\").unwrap();\n        Some(std::format!(\"{}{}\", bottle::get_original_hostname()?, suffix))\n    } else {\n        None\n    };\n\n    // TODO: resolv.conf\n    autostart(m.is_present(\"wait\"), hostname)?;\n\n    Ok(())\n}\n\nfn cmd_stop(_m: &clap::ArgMatches) -> anyhow::Result<()> {\n    check_root()?;\n    check_prereq()?;\n    if !bottle::is_running() {\n        log::warn!(\"systemd is already stopped or not running\");\n        return Ok(());\n    }\n    if bottle::is_inside() {\n        return Err(anyhow::anyhow!(\"Cannot stop from inside of systemd environment\"));\n    }\n\n    let r = bottle::stop();\n    if let Err(e) = r {\n        return Err(anyhow::anyhow!(\"Failed to stop: {}\", e));\n    }\n    Ok(())\n}\n\nfn cmd_exec(m: &clap::ArgMatches) -> anyhow::Result<()> {\n    check_prereq()?;\n    if !bottle::is_running() {\n        if m.is_present(\"start\") {\n            log::info!(\"Starting systemd\");\n            autostart(true, None)?;\n        } else {\n            return Err(anyhow::anyhow!(\"systemd is not running. Try start it first: subsystemctl start\"));\n        }\n    }\n\n    let cmd = m.values_of_lossy(\"command\");\n    if cmd.is_none() {\n        return Err(anyhow::anyhow!(\"command not given\"));\n    }\n    let (uid, gid) = extract_uid_gid(m)?;\n    let r = bottle::exec(cmd.unwrap(), uid, gid);\n    match r {\n        Ok(retval) => std::process::exit(retval),\n        Err(e) => return Err(anyhow::anyhow!(\"Failed to start: {}\", e)),\n    }\n}\n\nfn cmd_shell(m: &clap::ArgMatches) -> anyhow::Result<()> {\n    check_prereq()?;\n    if !bottle::is_running() {\n        if m.is_present(\"start\") {\n            log::info!(\"Starting systemd\");\n            autostart(true, None)?;\n        } else {\n            return Err(anyhow::anyhow!(\"systemd is not running. Try start it first: subsystemctl start\"));\n        }\n    }\n\n    let (uid, _gid) = extract_uid_gid(m)?;\n    let quiet = m.is_present(\"quiet\");\n    let r = bottle::shell(Some(uid), quiet);\n    match r {\n        Ok(retval) => std::process::exit(retval),\n        Err(e) => return Err(anyhow::anyhow!(\"Failed to start: {}\", e)),\n    }\n}\n\nfn cmd_is_running(_m: &clap::ArgMatches) -> anyhow::Result<()> {\n    check_prereq()?;\n    if !bottle::is_running() {\n        std::process::exit(1);\n    }\n    Ok(())\n}\n\nfn cmd_is_inside(_m: &clap::ArgMatches) -> anyhow::Result<()> {\n    check_prereq()?;\n    if !bottle::is_inside() {\n        std::process::exit(1);\n    }\n    Ok(())\n}\n\nfn check_prereq() -> anyhow::Result<()> {\n    if std::env::var(\"SUBSYSTEMCTL_IGNORE_WSL_CHECK\").is_err() {\n        if !environment::is_wsl().expect(\"Cannot check WSL1/2 status\") {\n            return Err(anyhow::anyhow!(\"not running in WSL1/2; This tool only runs in WSL2\"));\n        }\n        if environment::is_wsl1().expect(\"Cannot check WSL1\") {\n            return Err(anyhow::anyhow!(\"not running in WSL2; This tool only runs in WSL2\"));\n        }\n    }\n    Ok(())\n}\n\nfn check_root() -> anyhow::Result<()> {\n    if !nix::unistd::getuid().is_root() {\n        return Err(anyhow::anyhow!(\"This subcommand is only available for root\"));\n    }\n    Ok(())\n}\n\nfn extract_uid_gid(m: &clap::ArgMatches) -> anyhow::Result<(nix::unistd::Uid, nix::unistd::Gid)> {\n    if !nix::unistd::getuid().is_root() {\n        if m.is_present(\"uid\") || m.is_present(\"gid\") {\n            return Err(anyhow::anyhow!(\"uid,gid flags are only available for root\"));\n        }\n        log::debug!(\n            \"extract_uid_gid: non-root, uid={}, gid={}\",\n            nix::unistd::getuid(),\n            nix::unistd::getgid()\n        );\n        return Ok((nix::unistd::getuid(), nix::unistd::getgid()));\n    }\n\n    let uid = if let Some(id) = m.value_of(\"uid\") {\n        log::debug!(\"uid flag: {}\", &id);\n        nix::unistd::Uid::from_raw(id.parse()?)\n    } else {\n        nix::unistd::Uid::from_raw(0)\n    };\n    let gid = if let Some(id) = m.value_of(\"gid\") {\n        log::debug!(\"gid flag: {}\", &id);\n        nix::unistd::Gid::from_raw(id.parse()?)\n    } else {\n        if !uid.is_root() {\n            nix::unistd::Gid::from_raw(uid.as_raw())\n        } else {\n            nix::unistd::getgid()\n        }\n    };\n    Ok((uid, gid))\n}\n\nfn autostart(wait: bool, hostname: Option<String>) -> anyhow::Result<()> {\n    environment::machinectl_bin()?;\n    let r = bottle::start(hostname);\n    if let Err(e) = r {\n        return Err(anyhow::anyhow!(\"Failed to start: {}\", e));\n    }\n    if wait {\n        if let Err(e) = bottle::wait() {\n            return Err(anyhow::anyhow!(\"Failed to wait machined: {}\", e));\n        }\n    }\n\n    Ok(())\n}\n"
  }
]