├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── rustfmt.toml └── src ├── bottle.rs ├── environment.rs ├── error.rs ├── lib.rs └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.10" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.11.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 19 | dependencies = [ 20 | "winapi", 21 | ] 22 | 23 | [[package]] 24 | name = "anyhow" 25 | version = "1.0.31" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" 28 | 29 | [[package]] 30 | name = "atty" 31 | version = "0.2.14" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 34 | dependencies = [ 35 | "hermit-abi", 36 | "libc", 37 | "winapi", 38 | ] 39 | 40 | [[package]] 41 | name = "bitflags" 42 | version = "1.2.1" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 45 | 46 | [[package]] 47 | name = "cc" 48 | version = "1.0.54" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" 51 | 52 | [[package]] 53 | name = "cfg-if" 54 | version = "0.1.10" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 57 | 58 | [[package]] 59 | name = "clap" 60 | version = "2.33.1" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" 63 | dependencies = [ 64 | "ansi_term", 65 | "atty", 66 | "bitflags", 67 | "strsim", 68 | "textwrap", 69 | "unicode-width", 70 | "vec_map", 71 | ] 72 | 73 | [[package]] 74 | name = "env_logger" 75 | version = "0.7.1" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 78 | dependencies = [ 79 | "atty", 80 | "humantime", 81 | "log", 82 | "regex", 83 | "termcolor", 84 | ] 85 | 86 | [[package]] 87 | name = "hermit-abi" 88 | version = "0.1.13" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" 91 | dependencies = [ 92 | "libc", 93 | ] 94 | 95 | [[package]] 96 | name = "humantime" 97 | version = "1.3.0" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 100 | dependencies = [ 101 | "quick-error", 102 | ] 103 | 104 | [[package]] 105 | name = "lazy_static" 106 | version = "1.4.0" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 109 | 110 | [[package]] 111 | name = "libc" 112 | version = "0.2.71" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" 115 | 116 | [[package]] 117 | name = "log" 118 | version = "0.4.8" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 121 | dependencies = [ 122 | "cfg-if", 123 | ] 124 | 125 | [[package]] 126 | name = "memchr" 127 | version = "2.3.3" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 130 | 131 | [[package]] 132 | name = "nix" 133 | version = "0.17.0" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" 136 | dependencies = [ 137 | "bitflags", 138 | "cc", 139 | "cfg-if", 140 | "libc", 141 | "void", 142 | ] 143 | 144 | [[package]] 145 | name = "proc-macro2" 146 | version = "1.0.18" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" 149 | dependencies = [ 150 | "unicode-xid", 151 | ] 152 | 153 | [[package]] 154 | name = "quick-error" 155 | version = "1.2.3" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 158 | 159 | [[package]] 160 | name = "quote" 161 | version = "1.0.6" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" 164 | dependencies = [ 165 | "proc-macro2", 166 | ] 167 | 168 | [[package]] 169 | name = "regex" 170 | version = "1.3.9" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" 173 | dependencies = [ 174 | "aho-corasick", 175 | "memchr", 176 | "regex-syntax", 177 | "thread_local", 178 | ] 179 | 180 | [[package]] 181 | name = "regex-syntax" 182 | version = "0.6.18" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" 185 | 186 | [[package]] 187 | name = "strsim" 188 | version = "0.8.0" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 191 | 192 | [[package]] 193 | name = "subsystemctl" 194 | version = "0.2.0" 195 | dependencies = [ 196 | "anyhow", 197 | "clap", 198 | "env_logger", 199 | "libc", 200 | "log", 201 | "nix", 202 | "thiserror", 203 | ] 204 | 205 | [[package]] 206 | name = "syn" 207 | version = "1.0.30" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "93a56fabc59dce20fe48b6c832cc249c713e7ed88fa28b0ee0a3bfcaae5fe4e2" 210 | dependencies = [ 211 | "proc-macro2", 212 | "quote", 213 | "unicode-xid", 214 | ] 215 | 216 | [[package]] 217 | name = "termcolor" 218 | version = "1.1.0" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" 221 | dependencies = [ 222 | "winapi-util", 223 | ] 224 | 225 | [[package]] 226 | name = "textwrap" 227 | version = "0.11.0" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 230 | dependencies = [ 231 | "unicode-width", 232 | ] 233 | 234 | [[package]] 235 | name = "thiserror" 236 | version = "1.0.19" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "b13f926965ad00595dd129fa12823b04bbf866e9085ab0a5f2b05b850fbfc344" 239 | dependencies = [ 240 | "thiserror-impl", 241 | ] 242 | 243 | [[package]] 244 | name = "thiserror-impl" 245 | version = "1.0.19" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479" 248 | dependencies = [ 249 | "proc-macro2", 250 | "quote", 251 | "syn", 252 | ] 253 | 254 | [[package]] 255 | name = "thread_local" 256 | version = "1.0.1" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 259 | dependencies = [ 260 | "lazy_static", 261 | ] 262 | 263 | [[package]] 264 | name = "unicode-width" 265 | version = "0.1.7" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" 268 | 269 | [[package]] 270 | name = "unicode-xid" 271 | version = "0.2.0" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 274 | 275 | [[package]] 276 | name = "vec_map" 277 | version = "0.8.2" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 280 | 281 | [[package]] 282 | name = "void" 283 | version = "1.0.2" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 286 | 287 | [[package]] 288 | name = "winapi" 289 | version = "0.3.8" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 292 | dependencies = [ 293 | "winapi-i686-pc-windows-gnu", 294 | "winapi-x86_64-pc-windows-gnu", 295 | ] 296 | 297 | [[package]] 298 | name = "winapi-i686-pc-windows-gnu" 299 | version = "0.4.0" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 302 | 303 | [[package]] 304 | name = "winapi-util" 305 | version = "0.1.5" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 308 | dependencies = [ 309 | "winapi", 310 | ] 311 | 312 | [[package]] 313 | name = "winapi-x86_64-pc-windows-gnu" 314 | version = "0.4.0" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 317 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "subsystemctl" 3 | version = "0.2.0" 4 | description = "Utility to run systemd in WSL2 with a Linux namespace" 5 | homepage = "https://github.com/sorah/subsystemctl" 6 | repository = "https://github.com/sorah/subsystemctl" 7 | authors = ["Sorah Fukumori "] 8 | license = "MIT" 9 | edition = "2018" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [dependencies] 14 | anyhow = "1.0.31" 15 | thiserror = "1.0.19" 16 | clap = "2.33.1" 17 | nix = "0.17.0" 18 | libc = "0.2.71" 19 | env_logger = "0.7.1" 20 | log = "0.4.8" 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sorah Fukumori 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # subsystemd: Run systemd in WSL2 2 | 3 | __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. 4 | 5 | Run systemd under Linux namespace in WSL2. Heavily inspired by [arkane-systems/genie][genie], but written in Rust. 6 | 7 | ## Difference with arkane-systems/genie 8 | 9 | Slightly following [genie]'s behavior, but noted below... 10 | 11 | - Interface 12 | - Command line interface is not compatible. 13 | - Behavior 14 | - Hostname altertion is optional with `--hostname`, `--hostname-suffix` 15 | - `/etc/hosts` are not updated. Users are encouraged to use `nss-myhostname`. 16 | - Uses `machinectl shell` to launch a user shell; this allows running systemd user session 17 | - Internal 18 | - Removed dependency to `unshare`, `daemonize`, `nsenter` command line tools 19 | - systemd-wide environment variables are set via `systemd.conf` drop-in, using `DefaultEnvironment=` 20 | - systemd PID from root namespace is stored at `/run/subsystemctl/systemd.pid` 21 | 22 | ## Install 23 | 24 | ### Arch Linux 25 | 26 | PKGBUILD: https://github.com/sorah/arch.sorah.jp/tree/master/aur-sorah/PKGBUILDs/subsystemctl 27 | 28 | _(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).)_ 29 | 30 | ### Debian/Ubuntu 31 | 32 | Refer to https://github.com/nkmideb/subsystemctl for debian source. 33 | 34 | Pre-built package binaries available at https://github.com/nkmideb/subsystemctl/releases for your convenient. 35 | 36 | ### Self build 37 | 38 | ``` 39 | cargo install subsystemctl 40 | ``` 41 | 42 | or from git source: 43 | 44 | ```bash 45 | cargo build --release 46 | install -m6755 -oroot -groot ./target/release/subsystemctl /usr/local/bin/subsystemctl 47 | ``` 48 | 49 | ## Usage 50 | 51 | ### `subsystemctl start`: Start `systemd` environment 52 | 53 | ```ps1 54 | PS> wsl -u root -- subsystemctl start 55 | ``` 56 | 57 | ### `subsystemctl shell`: shell login to systemd-enabled environment 58 | 59 | ```ps1 60 | PS> wsl subsystemctl shell 61 | Connected to the local host. Press ^] three times within 1s to exit session. 62 | someone@hostname$ ... 63 | ``` 64 | 65 | #### Specifying uid to login 66 | 67 | ```ps1 68 | PS> wsl -u root -- subsystemctl shell --uid=1000 69 | Connected to the local host. Press ^] three times within 1s to exit session. 70 | someone@hostname$ ... 71 | ``` 72 | 73 | #### Automatically starting and entering a user shell 74 | 75 | ```ps1 76 | PS> wsl -u root -d Arch -- subsystemctl shell --uid=1000 --start 77 | [2021-06-27T16:32:20Z INFO subsystemctl] Starting systemd 78 | Connected to the local host. Press ^] three times within 1s to exit session. 79 | 80 | someone@hostname$ ... 81 | ``` 82 | 83 | ### `subsystemctl exec`: Raw `nsenter` like interface 84 | 85 | ```ps1 86 | PS> wsl subsystemctl exec id 87 | uid=1000(sorah) gid=1000(sorah) groups=1000(sorah),116(admin) 88 | ``` 89 | 90 | #### Specifying uid (and gid) 91 | 92 | ```ps1 93 | PS> wsl -u root -- subsystemctl exec id 94 | uid=0(root) gid=0(root) groups=0(root) 95 | 96 | PS> wsl -u root -- subsystemctl exec --uid=1000 id 97 | uid=1000(sorah) gid=1000(sorah) groups=1000(sorah),116(admin) 98 | 99 | PS> wsl -u root -- subsystemctl exec --uid=1000 --gid=116 id 100 | uid=1000(sorah) gid=116(admin) groups=116(admin) 101 | ``` 102 | 103 | ### `subsystemctl is-running` 104 | 105 | ```bash 106 | #!/bin/bash 107 | if subsystemctl is-running; then 108 | echo "running" 109 | else 110 | echo "not-running" 111 | fi 112 | ``` 113 | 114 | ### `subsystemctl is-inside` 115 | 116 | ```bash 117 | #!/bin/bash 118 | if subsystemctl is-inside; then 119 | echo "inside" 120 | else 121 | echo "outside" 122 | fi 123 | ``` 124 | 125 | ## Tips 126 | 127 | ### systemd-resolved, networkd are recommended to be disabled 128 | 129 | otherwise `/etc/resolv.conf` might get overwritten to resolved stub-resolver. 130 | 131 | ## Author 132 | 133 | Sorah Fukumori https://sorah.jp/ 134 | 135 | ## License 136 | 137 | MIT 138 | 139 | 140 | [genie]: https://github.com/arkane-systems/genie 141 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 110 2 | use_small_heuristics = "Max" 3 | newline_style = "Unix" 4 | -------------------------------------------------------------------------------- /src/bottle.rs: -------------------------------------------------------------------------------- 1 | use crate::environment; 2 | use crate::error; 3 | use std::ffi::CStr; 4 | use std::ffi::CString; 5 | use std::ffi::OsStr; 6 | 7 | static RUNTIME_DIR: &str = "/run/subsystemctl"; 8 | static PID_FILE: &str = "/run/subsystemctl/systemd.pid"; 9 | static HOSTNAME_FILE: &str = "/run/subsystemctl/hostname"; 10 | static ORIG_HOSTNAME_FILE: &str = "/run/subsystemctl/hostname.orig"; 11 | 12 | const OS_NONE: Option<&'static [u8]> = None; 13 | 14 | pub fn get_systemd_pid() -> Result, Box> { 15 | let result = std::fs::read(PID_FILE); 16 | match result { 17 | Ok(buf) => { 18 | let pid: i32 = String::from_utf8(buf)?.trim().parse()?; 19 | Ok(Some(pid)) 20 | } 21 | Err(e) => { 22 | if e.kind() == std::io::ErrorKind::NotFound { 23 | Ok(None) 24 | } else { 25 | Err(Box::new(e)) 26 | } 27 | } 28 | } 29 | } 30 | 31 | fn put_systemd_pid(pid: i32) -> std::io::Result<()> { 32 | std::fs::create_dir_all(RUNTIME_DIR)?; 33 | std::fs::write(PID_FILE, format!("{}\n", pid)) 34 | } 35 | 36 | fn zap_systemd_pid() -> std::io::Result<()> { 37 | if std::fs::metadata(PID_FILE).is_ok() { 38 | std::fs::remove_file(PID_FILE)?; 39 | } 40 | Ok(()) 41 | } 42 | 43 | pub fn get_original_hostname() -> std::io::Result { 44 | let buf = if std::fs::metadata(ORIG_HOSTNAME_FILE).is_ok() { 45 | std::fs::read(ORIG_HOSTNAME_FILE) 46 | } else { 47 | std::fs::read("/etc/hostname") 48 | }?; 49 | Ok(String::from_utf8(buf).unwrap().trim().to_owned()) 50 | } 51 | 52 | pub fn put_hostname(name: String) -> std::io::Result<()> { 53 | std::fs::create_dir_all(RUNTIME_DIR)?; 54 | if std::fs::metadata(ORIG_HOSTNAME_FILE).is_err() { 55 | let orig_hostname = std::fs::read("/etc/hostname")?; 56 | std::fs::write(ORIG_HOSTNAME_FILE, orig_hostname)? 57 | } 58 | std::fs::write(HOSTNAME_FILE, format!("{}\n", name)) 59 | } 60 | 61 | pub fn is_running() -> bool { 62 | if environment::is_pid1_systemd() { 63 | return true; 64 | } 65 | if let Ok(pid_o) = get_systemd_pid() { 66 | if let Some(pid) = pid_o { 67 | if let Ok(meta) = std::fs::metadata(std::format!("/proc/{}", pid)) { 68 | return meta.is_dir(); 69 | } 70 | } 71 | } 72 | false 73 | } 74 | 75 | pub fn is_inside() -> bool { 76 | environment::is_pid1_systemd() 77 | } 78 | 79 | pub fn start(name: Option) -> Result<(), Box> { 80 | if let Some(hostname) = name { 81 | ensure_hostname(hostname)?; 82 | } 83 | exec_systemd_ensure_dropin()?; 84 | Ok(exec_systemd()?) 85 | } 86 | 87 | fn exec_systemd_ensure_dropin() -> std::io::Result<()> { 88 | std::fs::create_dir_all("/run/systemd/system.conf.d")?; 89 | 90 | let envs = vec!["WSL_INTEROP", "WSL_DISTRO_NAME", "WSL_NAME", "WT_SESSION", "WT_PROFILE_ID"]; 91 | 92 | let envvar_strs: Vec = envs 93 | .into_iter() 94 | .filter_map(|name| { 95 | let e = std::env::var(name); 96 | if let Ok(env) = e { 97 | if env.contains("\"") { 98 | None // XXX: 99 | } else { 100 | Some(format!("\"{}={}\"", name, env)) 101 | } 102 | } else { 103 | None 104 | } 105 | }) 106 | .collect(); 107 | 108 | let dropin = format!( 109 | "[Manager]\nDefaultEnvironment=INSIDE_GENIE=1 INSIDE_SUBSYSTEMCTL=1 {}\n", 110 | envvar_strs.join(" ") 111 | ); 112 | std::fs::write("/run/systemd/system.conf.d/10-subsystemctl-env.conf", dropin) 113 | } 114 | 115 | fn ensure_hostname(name: String) -> Result<(), Box> { 116 | let needs_bind = std::fs::metadata(HOSTNAME_FILE).is_err(); 117 | put_hostname(name)?; 118 | if !needs_bind { 119 | return Ok(()); 120 | } 121 | nix::mount::mount( 122 | Some(OsStr::new(HOSTNAME_FILE)), 123 | OsStr::new("/etc/hostname"), 124 | OS_NONE, 125 | nix::mount::MsFlags::MS_BIND, 126 | OS_NONE, 127 | )?; 128 | Ok(()) 129 | } 130 | 131 | fn exec_systemd() -> Result<(), error::Error> { 132 | use nix::unistd::ForkResult; 133 | 134 | let systemd_bin = CString::new(environment::systemd_bin().unwrap()).unwrap(); 135 | 136 | match nix::unistd::fork() { 137 | Ok(ForkResult::Child) => exec_systemd0_handle_child_failure(exec_systemd1_child(systemd_bin)), 138 | Ok(ForkResult::Parent { child, .. }) => exec_systemd1_parent(child), 139 | Err(e) => panic!("{}",e), 140 | } 141 | } 142 | 143 | fn exec_systemd0_handle_child_failure(r: Result<(), error::Error>) -> Result<(), error::Error> { 144 | match r { 145 | Ok(_) => {} // do nothing 146 | Err(error::Error::StartFailed(exitstatus)) => { 147 | log::error!("Something went wrong while starting"); 148 | std::process::exit(exitstatus); 149 | } 150 | Err(e) => panic!("{}",e), 151 | } 152 | std::process::exit(0); 153 | } 154 | 155 | fn exec_systemd1_parent(child: nix::unistd::Pid) -> Result<(), error::Error> { 156 | use nix::sys::wait::WaitStatus; 157 | 158 | // TODO: monitor systemd status instead of pid file 159 | loop { 160 | match get_systemd_pid() { 161 | Ok(Some(pid)) => { 162 | log::debug!("Watching pid: child_pid={}, pid={}", child, pid); 163 | let pidns_path = format!("/proc/{}/ns/pid", pid); 164 | let mntns_path = format!("/proc/{}/ns/mnt", pid); 165 | if std::fs::metadata(pidns_path).is_ok() && std::fs::metadata(mntns_path).is_ok() { 166 | break; 167 | } 168 | } 169 | Ok(None) => { 170 | log::debug!("Watching pid: none"); 171 | } 172 | Err(e) => { 173 | log::debug!("Watching pid: e={:?}", e); 174 | } 175 | } 176 | match nix::sys::wait::waitpid(child, Some(nix::sys::wait::WaitPidFlag::WNOWAIT)) { 177 | Ok(WaitStatus::Exited(_pid, status)) => { 178 | return Err(error::Error::StartFailed(status)); 179 | } 180 | Ok(WaitStatus::Signaled(_pid, signal, _)) => { 181 | return Err(error::Error::StartFailed(128 + (signal as i32))); 182 | } 183 | Err(nix::Error::Sys(nix::errno::Errno::ECHILD)) => { 184 | return Err(error::Error::StartFailed(128)); 185 | } 186 | _ => {} // ignore, 187 | } 188 | std::thread::sleep(std::time::Duration::from_millis(500)); 189 | } 190 | 191 | // TODO: 192 | Ok(()) 193 | } 194 | 195 | fn exec_systemd1_child(systemd_bin: CString) -> Result<(), error::Error> { 196 | use nix::sched::CloneFlags; 197 | use nix::unistd::ForkResult; 198 | 199 | nix::sched::unshare(CloneFlags::CLONE_NEWNS | CloneFlags::CLONE_NEWPID).unwrap(); 200 | nix::unistd::setsid().unwrap(); 201 | 202 | match nix::unistd::fork() { 203 | Ok(ForkResult::Child) => exec_systemd0_handle_child_failure(exec_systemd2_child(systemd_bin)), 204 | Ok(ForkResult::Parent { child, .. }) => exec_systemd2_parent(child), 205 | Err(e) => panic!("{}",e), 206 | } 207 | } 208 | 209 | fn exec_systemd2_parent(child: nix::unistd::Pid) -> Result<(), error::Error> { 210 | put_systemd_pid(child.as_raw()).unwrap(); 211 | std::process::exit(0); 212 | } 213 | 214 | fn exec_systemd2_child(systemd_bin: CString) -> Result<(), error::Error> { 215 | use nix::fcntl::OFlag; 216 | use nix::mount::MsFlags; 217 | use std::os::unix::io::RawFd; 218 | 219 | nix::mount::mount( 220 | Some(OsStr::new("none")), 221 | OsStr::new("/"), 222 | OS_NONE, 223 | MsFlags::MS_REC | MsFlags::MS_SHARED, 224 | OS_NONE, 225 | ) 226 | .expect("set_propagation mount failure"); 227 | 228 | nix::mount::mount( 229 | Some(OsStr::new("none")), 230 | OsStr::new("/proc"), 231 | OS_NONE, 232 | MsFlags::MS_REC | MsFlags::MS_PRIVATE, 233 | OS_NONE, 234 | ) 235 | .expect("proc propagation mount failure"); 236 | 237 | nix::mount::mount( 238 | Some(OsStr::new("proc")), 239 | OsStr::new("/proc"), 240 | Some(OsStr::new("proc")), 241 | MsFlags::MS_NOSUID | MsFlags::MS_NODEV | MsFlags::MS_NOEXEC, 242 | OS_NONE, 243 | ) 244 | .expect("proc mount failure"); 245 | 246 | nix::unistd::chdir("/").unwrap(); 247 | nix::unistd::setgid(nix::unistd::Gid::from_raw(0)).unwrap(); 248 | nix::unistd::setuid(nix::unistd::Uid::from_raw(0)).unwrap(); 249 | 250 | match nix::unistd::close(0 as RawFd) { 251 | Ok(_) => {} 252 | Err(nix::Error::Sys(nix::errno::Errno::EBADF)) => {} 253 | Err(e) => panic!("{}",e), 254 | } 255 | match nix::unistd::close(1 as RawFd) { 256 | Ok(_) => {} 257 | Err(nix::Error::Sys(nix::errno::Errno::EBADF)) => {} 258 | Err(e) => panic!("{}",e), 259 | } 260 | match nix::unistd::close(2 as RawFd) { 261 | Ok(_) => {} 262 | Err(nix::Error::Sys(nix::errno::Errno::EBADF)) => {} 263 | Err(e) => panic!("{}",e), 264 | } 265 | 266 | nix::fcntl::open(OsStr::new("/dev/null"), OFlag::O_RDONLY, nix::sys::stat::Mode::empty()).unwrap(); 267 | nix::fcntl::open(OsStr::new("/dev/null"), OFlag::O_WRONLY, nix::sys::stat::Mode::empty()).unwrap(); 268 | nix::fcntl::open(OsStr::new("/dev/null"), OFlag::O_WRONLY, nix::sys::stat::Mode::empty()).unwrap(); 269 | 270 | nix::unistd::execve(systemd_bin.as_c_str(), &[systemd_bin.as_c_str()], &[]).unwrap(); 271 | panic!("should unreach"); 272 | } 273 | 274 | pub fn stop() -> Result<(), Box> { 275 | let systemd_pid = get_systemd_pid().unwrap().unwrap(); 276 | nix::sys::signal::kill(nix::unistd::Pid::from_raw(systemd_pid), Some(SIGRTMIN_plus_4()))?; 277 | zap_systemd_pid().unwrap(); 278 | Ok(()) 279 | } 280 | 281 | extern "C" { 282 | fn __libc_current_sigrtmin() -> libc::c_int; 283 | } 284 | 285 | #[allow(non_snake_case)] 286 | fn SIGRTMIN_plus_4() -> nix::sys::signal::Signal { 287 | unsafe { std::mem::transmute(__libc_current_sigrtmin() + 4) } 288 | } 289 | 290 | pub fn wait() -> Result<(), Box> { 291 | use nix::sys::wait::WaitStatus; 292 | use nix::unistd::ForkResult; 293 | 294 | log::debug!("Waiting systemd-machined to start"); 295 | let machinectl = environment::machinectl_bin()?; 296 | 297 | match nix::unistd::fork() { 298 | Ok(ForkResult::Child) => { 299 | wait_internal(machinectl); 300 | std::process::exit(0); 301 | } 302 | Ok(ForkResult::Parent { child, .. }) => { 303 | loop { 304 | match nix::sys::wait::waitpid(child, None) { 305 | Ok(WaitStatus::Exited(_pid, status)) => { 306 | if status == 0 { 307 | log::debug!("machined is now up"); 308 | return Ok(()); 309 | } else { 310 | return Err(Box::new(error::Error::WaitFailed)); 311 | } 312 | } 313 | Ok(WaitStatus::Signaled(_pid, _signal, _)) => { 314 | return Err(Box::new(error::Error::WaitFailed)); 315 | } 316 | Ok(_) => {} // ignore 317 | Err(nix::Error::Sys(nix::errno::Errno::ECHILD)) => return Ok(()), // ??? 318 | Err(nix::Error::Sys(nix::errno::Errno::EINTR)) => {} // ignore 319 | Err(e) => panic!("{}",e), 320 | } 321 | } 322 | } 323 | 324 | Err(e) => panic!("{}",e), 325 | } 326 | } 327 | 328 | fn wait_internal(machinectl: String) { 329 | setns_systemd(); 330 | loop { 331 | let cmd = std::process::Command::new(&machinectl) 332 | .arg("list") 333 | .output() 334 | .expect("failed to execute machinectl list"); 335 | if cmd.status.success() { 336 | break; 337 | } 338 | std::thread::sleep(std::time::Duration::from_millis(600)); 339 | log::debug!("Still waiting systemd-machined to start"); 340 | } 341 | log::debug!("systemd-machined is up (internal)"); 342 | } 343 | 344 | pub fn exec(cmdline: Vec, uid: nix::unistd::Uid, gid: nix::unistd::Gid) -> Result { 345 | let args_string: Vec = cmdline.into_iter().map(|a| CString::new(a).unwrap()).collect(); 346 | let args: Vec<&CStr> = args_string.iter().map(|s| s.as_c_str()).collect(); 347 | 348 | enter(args[0], args.as_slice(), uid, gid) 349 | } 350 | 351 | pub fn shell(uid: Option, quiet: bool) -> Result { 352 | let machinectl = CString::new(environment::machinectl_bin()?).unwrap(); 353 | let mut args = vec![CString::new("machinectl").unwrap(), CString::new("shell").unwrap()]; 354 | 355 | if let Some(u) = uid { 356 | args.push(CString::new("--uid").unwrap()); 357 | args.push(CString::new(format!("{}", u)).unwrap()); 358 | } 359 | if quiet { 360 | args.push(CString::new("--quiet").unwrap()); 361 | } 362 | 363 | args.push(CString::new("--setenv").unwrap()); 364 | args.push(CString::new(format!("SUBSYSTEMCTL_PATH={}", std::env::current_dir().unwrap().display())).unwrap()); 365 | 366 | args.push(CString::new(".host").unwrap()); 367 | 368 | args.push(CString::new("/bin/sh").unwrap()); 369 | args.push(CString::new("-c").unwrap()); 370 | args.push(CString::new("cd \"${SUBSYSTEMCTL_PATH}\"; exec ${SHELL:-sh}").unwrap()); 371 | 372 | let args_c: Vec<&CStr> = args.iter().map(|s| s.as_c_str()).collect(); 373 | enter( 374 | machinectl.as_c_str(), 375 | &args_c.as_slice(), 376 | nix::unistd::Uid::from_raw(0), 377 | nix::unistd::Gid::from_raw(0), 378 | ) 379 | } 380 | 381 | fn setns_systemd() { 382 | use nix::fcntl::OFlag; 383 | use nix::sched::CloneFlags; 384 | 385 | let sd_pid = get_systemd_pid().unwrap().unwrap(); 386 | let pidns_path = format!("/proc/{}/ns/pid", sd_pid); 387 | let mntns_path = format!("/proc/{}/ns/mnt", sd_pid); 388 | 389 | { 390 | let pidns_fd = 391 | nix::fcntl::open(OsStr::new(&pidns_path), OFlag::O_RDONLY, nix::sys::stat::Mode::empty()) 392 | .unwrap(); 393 | nix::sched::setns(pidns_fd, CloneFlags::CLONE_NEWPID).unwrap(); 394 | nix::unistd::close(pidns_fd).unwrap(); 395 | } 396 | 397 | { 398 | let mntns_fd = 399 | nix::fcntl::open(OsStr::new(&mntns_path), OFlag::O_RDONLY, nix::sys::stat::Mode::empty()) 400 | .unwrap(); 401 | nix::sched::setns(mntns_fd, CloneFlags::CLONE_NEWNS).unwrap(); 402 | nix::unistd::close(mntns_fd).unwrap(); 403 | } 404 | } 405 | 406 | fn enter( 407 | path: &CStr, 408 | args: &[&CStr], 409 | uid: nix::unistd::Uid, 410 | gid: nix::unistd::Gid, 411 | ) -> Result { 412 | use nix::sys::wait::WaitStatus; 413 | use nix::unistd::ForkResult; 414 | 415 | setns_systemd(); 416 | 417 | match nix::unistd::fork() { 418 | Ok(ForkResult::Child) => { 419 | log::debug!("enter(child): uid={}, gid={}", uid, gid); 420 | 421 | nix::unistd::setgroups(&[]).unwrap(); 422 | 423 | unsafe { 424 | let ent = libc::getpwuid(uid.as_raw() as libc::uid_t); 425 | if !ent.is_null() { 426 | let username = CString::from_raw((*ent).pw_name); 427 | nix::unistd::initgroups(&username, gid).unwrap(); 428 | } 429 | } 430 | 431 | nix::unistd::setgid(gid).unwrap(); 432 | nix::unistd::setuid(uid).unwrap(); 433 | 434 | log::debug!("execvp {:?}, {:?}", path, args); 435 | match nix::unistd::execvp(path, args) { 436 | Err(nix::Error::Sys(errno)) => { 437 | log::error!("exec failed: {}", errno.desc()); 438 | return Ok(128); 439 | } 440 | Err(e) => panic!("{}",e), 441 | Ok(_) => {} 442 | } 443 | panic!("should unreach"); 444 | } 445 | Ok(ForkResult::Parent { child, .. }) => { 446 | loop { 447 | match nix::sys::wait::waitpid(child, None) { 448 | Ok(WaitStatus::Exited(_pid, status)) => { 449 | return Ok(status); 450 | } 451 | Ok(WaitStatus::Signaled(_pid, signal, _)) => { 452 | return Ok(128 + (signal as i32)); 453 | } 454 | Ok(_) => {} // ignore 455 | Err(nix::Error::Sys(nix::errno::Errno::ECHILD)) => return Ok(128), // ??? 456 | Err(nix::Error::Sys(nix::errno::Errno::EINTR)) => {} // ignore 457 | Err(e) => panic!("{}",e), 458 | } 459 | } 460 | } 461 | 462 | Err(e) => panic!("{}",e), 463 | } 464 | } 465 | -------------------------------------------------------------------------------- /src/environment.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | static PROC_MOUNTS: &str = "/proc/self/mounts"; 3 | 4 | static OSRELEASE_FILE: &str = "/proc/sys/kernel/osrelease"; 5 | static WSL_OSRELEASE: &str = "microsoft"; 6 | 7 | pub fn is_wsl() -> Result> { 8 | let osrelease = String::from_utf8(std::fs::read(OSRELEASE_FILE)?)?; 9 | Ok(osrelease.contains(WSL_OSRELEASE)) 10 | } 11 | 12 | pub fn is_wsl1() -> Result> { 13 | log::debug!("Checking is_wsl1"); 14 | // Assume we're in WSL 1 or 2. 15 | // Find rootfs is lxfs or not. If it's lxfs, then it's WSL1, otherwise considered 2. 16 | 17 | let mounts_buf = String::from_utf8(std::fs::read(PROC_MOUNTS)?)?; 18 | let mut mounts_lines = mounts_buf.lines(); 19 | 20 | while let Some(mount) = mounts_lines.next() { 21 | let mut iter = mount.split_ascii_whitespace(); 22 | iter.next(); 23 | let mountpoint_o = iter.next(); 24 | let fstype_o = iter.next(); 25 | if let (Some(mountpoint), Some(fstype)) = (mountpoint_o, fstype_o) { 26 | log::debug!("Checking is_wsl1: mountpoint={}, fstype={}", mountpoint, fstype); 27 | if mountpoint != "/" { 28 | continue; 29 | } 30 | return Ok(fstype == "lxfs" || fstype == "wslfs"); 31 | } else { 32 | return Err(Box::new(Error::InvalidProcMounts)); 33 | } 34 | } 35 | 36 | Ok(false) 37 | } 38 | 39 | pub fn systemd_bin() -> Result { 40 | //if let Ok(bin) = std::env::var("SUBSYSTEMCTL_SYSTEMD_BIN") { 41 | // return Ok(bin); 42 | //} 43 | if std::fs::metadata("/lib/systemd/systemd").is_ok() { 44 | return Ok("/lib/systemd/systemd".to_string()); 45 | } 46 | if std::fs::metadata("/usr/lib/systemd/systemd").is_ok() { 47 | return Ok("/usr/lib/systemd/systemd".to_string()); 48 | } 49 | Err(Error::NoSystemdFound) 50 | } 51 | 52 | pub fn machinectl_bin() -> Result { 53 | // if let Ok(bin) = std::env::var("SUBSYSTEMCTL_MACHINECTL_BIN") { 54 | // return Ok(bin) 55 | // } 56 | if std::fs::metadata("/usr/bin/machinectl").is_ok() { 57 | return Ok("/usr/bin/machinectl".to_string()); 58 | } 59 | if std::fs::metadata("/bin/machinectl").is_ok() { 60 | return Ok("/bin/machinectl".to_string()); 61 | } 62 | Err(Error::NoSystemdFound) 63 | } 64 | 65 | pub fn is_pid1_systemd() -> bool { 66 | if let Ok(cmdline_vec) = std::fs::read("/proc/1/cmdline") { 67 | let cmdline = String::from_utf8(cmdline_vec).unwrap(); 68 | let binpath = cmdline.split('\0').next().unwrap(); 69 | 70 | binpath == systemd_bin().unwrap() 71 | } else { 72 | false 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | #[derive(thiserror::Error, Debug)] 2 | pub enum Error { 3 | #[error("Unexpected format in /proc/self/mounts")] 4 | InvalidProcMounts, 5 | 6 | #[error("Systemd not found in standard locations (machinectl and systemd are necessary)")] 7 | NoSystemdFound, 8 | 9 | #[error("Systemd is not running")] 10 | NotRunning, 11 | 12 | #[error("Something went wrong while waiting")] 13 | WaitFailed, 14 | 15 | #[error("Something went wrong while starting, exitstatus={0}")] 16 | StartFailed(i32), 17 | } 18 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod environment; 2 | pub mod error; 3 | pub mod bottle; 4 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use subsystemctl::bottle; 2 | use subsystemctl::environment; 3 | 4 | fn main() -> anyhow::Result<()> { 5 | env_logger::from_env(env_logger::Env::default().default_filter_or("info")).init(); 6 | 7 | let app = clap::App::new(clap::crate_name!()) 8 | .version(clap::crate_version!()) 9 | .about(clap::crate_description!()) 10 | .setting(clap::AppSettings::SubcommandRequired) 11 | .subcommand( 12 | clap::SubCommand::with_name("start") 13 | .about("Start systemd in a Linux namespace (mount, pid)") 14 | .arg( 15 | clap::Arg::with_name("hostname") 16 | .help("Change hostname during start up") 17 | .takes_value(true) 18 | .short("n") 19 | .long("hostname"), 20 | ) 21 | .arg( 22 | clap::Arg::with_name("hostname-suffix") 23 | .help("Append a suffix to hostname during startup") 24 | .takes_value(true) 25 | .short("N") 26 | .long("hostname-suffix"), 27 | ) 28 | .arg( 29 | clap::Arg::with_name("wait") 30 | .help("Wait systemd-machined to start up") 31 | .short("w") 32 | .long("wait"), 33 | ), 34 | ) 35 | .subcommand(clap::SubCommand::with_name("stop").about("stop")) 36 | .subcommand( 37 | clap::SubCommand::with_name("shell") 38 | .about("Start a shell in a systemd namespace using machinectl-shell") 39 | .arg( 40 | clap::Arg::with_name("start") 41 | .help("Start systemd when necessary") 42 | .short("s") 43 | .long("start"), 44 | ) 45 | .arg( 46 | clap::Arg::with_name("quiet") 47 | .help("Suppress machinectl-shell output") 48 | .short("q") 49 | .long("quiet"), 50 | ) 51 | .arg(clap::Arg::with_name("uid").takes_value(true).short("u").long("uid")), 52 | ) 53 | .subcommand( 54 | clap::SubCommand::with_name("exec") 55 | .about("Execute a command in a systemd namespace") 56 | .arg( 57 | clap::Arg::with_name("start") 58 | .help("Start systemd when necessary") 59 | .short("s") 60 | .long("start"), 61 | ) 62 | .arg( 63 | clap::Arg::with_name("uid") 64 | .help("setuid(2) on exec. Only available for root, default to current uid (getuid(2)") 65 | .takes_value(true) 66 | .short("u") 67 | .long("uid"), 68 | ) 69 | .arg( 70 | clap::Arg::with_name("gid") 71 | .help("setgid(2) on exec. Only available for root, default to current gid (getgid(2)") 72 | .takes_value(true) 73 | .short("g") 74 | .long("gid"), 75 | ) 76 | .arg(clap::Arg::with_name("command").takes_value(true).multiple(true) 77 | .allow_hyphen_values(true) 78 | .last(true)), 79 | ) 80 | .subcommand( 81 | clap::SubCommand::with_name("is-running") 82 | .about("Return 0 if a systemd namespace is running, otherwise 1"), 83 | ) 84 | .subcommand( 85 | clap::SubCommand::with_name("is-inside") 86 | .about("Return 0 if invoked from inside of a systemd namespace, otherwise 1"), 87 | ); 88 | let matches = app.get_matches(); 89 | run_subcommand(matches.subcommand()) 90 | } 91 | 92 | fn run_subcommand(subcommand: (&str, Option<&clap::ArgMatches>)) -> anyhow::Result<()> { 93 | match subcommand { 94 | ("start", Some(m)) => cmd_start(m), 95 | ("stop", Some(m)) => cmd_stop(m), 96 | ("exec", Some(m)) => cmd_exec(m), 97 | ("shell", Some(m)) => cmd_shell(m), 98 | ("is-running", Some(m)) => cmd_is_running(m), 99 | ("is-inside", Some(m)) => cmd_is_inside(m), 100 | _ => panic!("?"), 101 | } 102 | } 103 | 104 | fn cmd_start(m: &clap::ArgMatches) -> anyhow::Result<()> { 105 | check_root()?; 106 | check_prereq()?; 107 | if bottle::is_running() { 108 | log::warn!("systemd is running, not starting again"); 109 | return Ok(()); 110 | } 111 | let hostname = if m.is_present("hostname") { 112 | Some(m.value_of_lossy("hostname").unwrap().to_string()) 113 | } else if m.is_present("hostname-suffix") { 114 | let suffix = m.value_of_lossy("hostname-suffix").unwrap(); 115 | Some(std::format!("{}{}", bottle::get_original_hostname()?, suffix)) 116 | } else { 117 | None 118 | }; 119 | 120 | // TODO: resolv.conf 121 | autostart(m.is_present("wait"), hostname)?; 122 | 123 | Ok(()) 124 | } 125 | 126 | fn cmd_stop(_m: &clap::ArgMatches) -> anyhow::Result<()> { 127 | check_root()?; 128 | check_prereq()?; 129 | if !bottle::is_running() { 130 | log::warn!("systemd is already stopped or not running"); 131 | return Ok(()); 132 | } 133 | if bottle::is_inside() { 134 | return Err(anyhow::anyhow!("Cannot stop from inside of systemd environment")); 135 | } 136 | 137 | let r = bottle::stop(); 138 | if let Err(e) = r { 139 | return Err(anyhow::anyhow!("Failed to stop: {}", e)); 140 | } 141 | Ok(()) 142 | } 143 | 144 | fn cmd_exec(m: &clap::ArgMatches) -> anyhow::Result<()> { 145 | check_prereq()?; 146 | if !bottle::is_running() { 147 | if m.is_present("start") { 148 | log::info!("Starting systemd"); 149 | autostart(true, None)?; 150 | } else { 151 | return Err(anyhow::anyhow!("systemd is not running. Try start it first: subsystemctl start")); 152 | } 153 | } 154 | 155 | let cmd = m.values_of_lossy("command"); 156 | if cmd.is_none() { 157 | return Err(anyhow::anyhow!("command not given")); 158 | } 159 | let (uid, gid) = extract_uid_gid(m)?; 160 | let r = bottle::exec(cmd.unwrap(), uid, gid); 161 | match r { 162 | Ok(retval) => std::process::exit(retval), 163 | Err(e) => return Err(anyhow::anyhow!("Failed to start: {}", e)), 164 | } 165 | } 166 | 167 | fn cmd_shell(m: &clap::ArgMatches) -> anyhow::Result<()> { 168 | check_prereq()?; 169 | if !bottle::is_running() { 170 | if m.is_present("start") { 171 | log::info!("Starting systemd"); 172 | autostart(true, None)?; 173 | } else { 174 | return Err(anyhow::anyhow!("systemd is not running. Try start it first: subsystemctl start")); 175 | } 176 | } 177 | 178 | let (uid, _gid) = extract_uid_gid(m)?; 179 | let quiet = m.is_present("quiet"); 180 | let r = bottle::shell(Some(uid), quiet); 181 | match r { 182 | Ok(retval) => std::process::exit(retval), 183 | Err(e) => return Err(anyhow::anyhow!("Failed to start: {}", e)), 184 | } 185 | } 186 | 187 | fn cmd_is_running(_m: &clap::ArgMatches) -> anyhow::Result<()> { 188 | check_prereq()?; 189 | if !bottle::is_running() { 190 | std::process::exit(1); 191 | } 192 | Ok(()) 193 | } 194 | 195 | fn cmd_is_inside(_m: &clap::ArgMatches) -> anyhow::Result<()> { 196 | check_prereq()?; 197 | if !bottle::is_inside() { 198 | std::process::exit(1); 199 | } 200 | Ok(()) 201 | } 202 | 203 | fn check_prereq() -> anyhow::Result<()> { 204 | if std::env::var("SUBSYSTEMCTL_IGNORE_WSL_CHECK").is_err() { 205 | if !environment::is_wsl().expect("Cannot check WSL1/2 status") { 206 | return Err(anyhow::anyhow!("not running in WSL1/2; This tool only runs in WSL2")); 207 | } 208 | if environment::is_wsl1().expect("Cannot check WSL1") { 209 | return Err(anyhow::anyhow!("not running in WSL2; This tool only runs in WSL2")); 210 | } 211 | } 212 | Ok(()) 213 | } 214 | 215 | fn check_root() -> anyhow::Result<()> { 216 | if !nix::unistd::getuid().is_root() { 217 | return Err(anyhow::anyhow!("This subcommand is only available for root")); 218 | } 219 | Ok(()) 220 | } 221 | 222 | fn extract_uid_gid(m: &clap::ArgMatches) -> anyhow::Result<(nix::unistd::Uid, nix::unistd::Gid)> { 223 | if !nix::unistd::getuid().is_root() { 224 | if m.is_present("uid") || m.is_present("gid") { 225 | return Err(anyhow::anyhow!("uid,gid flags are only available for root")); 226 | } 227 | log::debug!( 228 | "extract_uid_gid: non-root, uid={}, gid={}", 229 | nix::unistd::getuid(), 230 | nix::unistd::getgid() 231 | ); 232 | return Ok((nix::unistd::getuid(), nix::unistd::getgid())); 233 | } 234 | 235 | let uid = if let Some(id) = m.value_of("uid") { 236 | log::debug!("uid flag: {}", &id); 237 | nix::unistd::Uid::from_raw(id.parse()?) 238 | } else { 239 | nix::unistd::Uid::from_raw(0) 240 | }; 241 | let gid = if let Some(id) = m.value_of("gid") { 242 | log::debug!("gid flag: {}", &id); 243 | nix::unistd::Gid::from_raw(id.parse()?) 244 | } else { 245 | if !uid.is_root() { 246 | nix::unistd::Gid::from_raw(uid.as_raw()) 247 | } else { 248 | nix::unistd::getgid() 249 | } 250 | }; 251 | Ok((uid, gid)) 252 | } 253 | 254 | fn autostart(wait: bool, hostname: Option) -> anyhow::Result<()> { 255 | environment::machinectl_bin()?; 256 | let r = bottle::start(hostname); 257 | if let Err(e) = r { 258 | return Err(anyhow::anyhow!("Failed to start: {}", e)); 259 | } 260 | if wait { 261 | if let Err(e) = bottle::wait() { 262 | return Err(anyhow::anyhow!("Failed to wait machined: {}", e)); 263 | } 264 | } 265 | 266 | Ok(()) 267 | } 268 | --------------------------------------------------------------------------------