├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENCE ├── README.md └── src ├── lib.rs ├── main.rs ├── shell.rs └── systemd.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Install libsystemd-dev 18 | run: sudo apt install -y libsystemd-dev 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .DS_Store 3 | 4 | /target 5 | -------------------------------------------------------------------------------- /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 = "angea" 7 | version = "0.0.8" 8 | dependencies = [ 9 | "libc", 10 | "libsystemd-sys", 11 | "nix", 12 | ] 13 | 14 | [[package]] 15 | name = "bitflags" 16 | version = "1.3.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 19 | 20 | [[package]] 21 | name = "build-env" 22 | version = "0.3.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "1522ac6ee801a11bf9ef3f80403f4ede6eb41291fac3dde3de09989679305f25" 25 | 26 | [[package]] 27 | name = "cfg-if" 28 | version = "1.0.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 31 | 32 | [[package]] 33 | name = "libc" 34 | version = "0.2.138" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" 37 | 38 | [[package]] 39 | name = "libsystemd-sys" 40 | version = "0.9.3" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "ed080163caa59cc29b34bce2209b737149a4bac148cd9a8b04e4c12822798119" 43 | dependencies = [ 44 | "build-env", 45 | "libc", 46 | "pkg-config", 47 | ] 48 | 49 | [[package]] 50 | name = "nix" 51 | version = "0.26.1" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694" 54 | dependencies = [ 55 | "bitflags", 56 | "cfg-if", 57 | "libc", 58 | "static_assertions", 59 | ] 60 | 61 | [[package]] 62 | name = "pkg-config" 63 | version = "0.3.19" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" 66 | 67 | [[package]] 68 | name = "static_assertions" 69 | version = "1.1.0" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 72 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "angea" 3 | version = "0.0.8" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | libc = "0.2" 8 | nix = { version = "0.26", default-features = false, features = ["event", "dir", "mount", "sched", "signal", "term"]} 9 | libsystemd-sys = "0.9" 10 | 11 | [profile.release] 12 | opt-level = 3 13 | lto = true 14 | panic = "abort" 15 | codegen-units = 1 16 | strip = true 17 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 incisakura 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 | # Angea 2 | 3 | --- 4 | > Naming from hydrangea(アジサイ) 5 | 6 | A lite tool to make systemd work in Windows Subsystem for Linux 2 7 | 8 | **WSL1 is not supported.** 9 | 10 | ## Attention 11 | 12 | Microsoft has officially released systemd support for WLS. Thus, this repository has reached its end. It would be archived. For more about that, see [here](https://devblogs.microsoft.com/commandline/systemd-support-is-now-available-in-wsl/) or further documents from Microsoft. 13 | 14 | ## Usage 15 | 16 | See `angea help` 17 | 18 | ## Advanced Usage 19 | 20 | ### Custom Shell Program 21 | 22 | Run `angea shell` with envivonment variable `ARGS`. (default args in example below) 23 | 24 | **Note**: The first argument must be a absolute path. 25 | 26 | ``` bash 27 | ANGEA_ARGS="/usr/bin/bash -l" angea shell 28 | ``` 29 | 30 | ### Custom Envivonment Variable 31 | 32 | Notice: Wroung environment variable passed may trigger an error. 33 | 34 | ``` bash 35 | // Set Envivonment Variable 36 | ANGEA_ENVS="TERM=xterm-256color,WSL=1" angea shell 37 | 38 | // Inherit Envivonment Variable 39 | // If `ANGEA_ENV_INHERIT` is not set, angea would inherit `TERM` by default 40 | ANGEA_ENV_INHERIT="TERM,WT_SESSION" angea shell 41 | 42 | // Both 43 | ANGEA_ENVS="TERM=xterm-256color" ENV_INHERIT="WT_SESSION" angea shell 44 | ``` 45 | 46 | ## Requirement 47 | 48 | Nothing! But you should install `systemd` as least. 49 | 50 | ## Credit 51 | 52 | Incisakura <incisakura@icloud.com> 53 | 54 | ## Licence 55 | 56 | MIT 57 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod shell; 2 | 3 | mod systemd; 4 | 5 | use nix::Result; 6 | use shell::{get_pty, PTYForward}; 7 | use std::env; 8 | 9 | pub fn cmd() { 10 | let mut args = env::args(); 11 | args.next(); 12 | let ret = match args.next() { 13 | Some(s) if s == "boot" => boot(), 14 | Some(s) if s == "shutdown" => shutdown(), 15 | Some(s) if s == "shell" => shell(args.next()), 16 | _ => help(), 17 | }; 18 | if let Err(e) = ret { 19 | eprintln!("{}", e); 20 | } 21 | } 22 | 23 | fn shell(user: Option) -> Result<()> { 24 | boot()?; 25 | 26 | let user = user.unwrap_or_else(|| String::from("root")); 27 | let master = get_pty(user)?; 28 | let mut f = PTYForward::new(master)?; 29 | f.wait()?; 30 | Ok(()) 31 | } 32 | 33 | fn boot() -> Result<()> { 34 | if systemd::get_running()?.is_none() { 35 | systemd::start()?; 36 | } 37 | Ok(()) 38 | } 39 | 40 | fn shutdown() -> Result<()> { 41 | systemd::shutdown() 42 | } 43 | 44 | fn help() -> Result<()> { 45 | print!(concat!( 46 | "Angea version ", 47 | env!("CARGO_PKG_VERSION"), 48 | " 49 | Usage: angea [more] 50 | Command: 51 | boot Start systemd 52 | shell [user] Open a shell in systemd. [Default: root] 53 | shutdown Kill running systemd 54 | help This message 55 | " 56 | )); 57 | Ok(()) 58 | } 59 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use angea::cmd; 2 | 3 | fn main() { 4 | cmd(); 5 | } 6 | -------------------------------------------------------------------------------- /src/shell.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::ffi::c_void; 3 | use std::mem; 4 | use std::mem::MaybeUninit; 5 | use std::os::raw::c_char; 6 | use std::os::unix::io::{AsRawFd, RawFd}; 7 | use std::os::unix::prelude::IntoRawFd; 8 | use std::ptr; 9 | 10 | use nix::errno::Errno; 11 | use nix::fcntl::{fcntl, FcntlArg, OFlag}; 12 | use nix::pty::{posix_openpt, ptsname_r, unlockpt}; 13 | use nix::sys::epoll::{self, EpollEvent, EpollFlags, EpollOp}; 14 | use nix::sys::signal::{sigprocmask, SigmaskHow, Signal}; 15 | use nix::sys::signalfd::{SigSet, SignalFd}; 16 | use nix::sys::termios::{self, SetArg, Termios}; 17 | use nix::unistd::{read, write}; 18 | use nix::Result; 19 | 20 | use libsystemd_sys::bus::*; 21 | 22 | /// Init and get pty master fd 23 | pub fn get_pty(user: String) -> Result { 24 | // pty peer 25 | let pty = posix_openpt(OFlag::O_NONBLOCK | OFlag::O_RDWR | OFlag::O_NOCTTY)?; 26 | unlockpt(&pty)?; 27 | let pts = ptsname_r(&pty)?; 28 | 29 | // dbus method call 30 | unsafe { dbus(user, pts)? }; 31 | window_resize(pty.as_raw_fd())?; 32 | Ok(pty.into_raw_fd()) 33 | } 34 | 35 | const SD_BUS_TYPE_ARRAY: c_char = 'a' as c_char; 36 | 37 | const SD_BUS_TYPE_VARIANT: c_char = 'v' as c_char; 38 | 39 | const SD_BUS_TYPE_STRUCT: c_char = 'r' as c_char; 40 | 41 | #[rustfmt::skip] 42 | /// D-Bus call to spawn a shell service in systemd 43 | unsafe fn dbus(user: String, slave: String) -> Result<()> { 44 | // Arguments 45 | let mut args = env::var("ANGEA_ARGS") 46 | .map(|v| v.split_ascii_whitespace().map(append_null_ref).collect()) 47 | .unwrap_or_else(|_| vec!["/bin/bash\0".to_string(), "-l\0".to_string()]); 48 | let mut args: Vec<*mut c_char> = args 49 | .iter_mut() 50 | .map(|s| s.as_mut_ptr().cast()) 51 | .chain(Some(ptr::null_mut())) 52 | .collect(); 53 | 54 | // Environment Variables 55 | let mut envs: Vec = Vec::new(); 56 | if let Ok(s) = env::var("ANGEA_ENVS") { 57 | envs.extend(s.split(',').map(|s| append_null_ref(s.trim()))); 58 | } 59 | if let Ok(s) = env::var("TERM") { 60 | envs.push(format!("TERM={}\0", s)); 61 | } 62 | if let Ok(s) = env::var("ANGEA_ENV_INHERIT") { 63 | envs.extend( 64 | s.split(',') 65 | .filter_map(|k| env::var(k.trim()).map(|v| format!("{}={}\0", k, v)).ok()) 66 | ); 67 | } 68 | let mut envs: Vec<*mut c_char> = envs 69 | .iter_mut() 70 | .map(|s| s.as_mut_ptr().cast()) 71 | .chain(Some(ptr::null_mut())) 72 | .collect(); 73 | 74 | let pts_id = slave.trim_start_matches("/dev/pts/"); 75 | let service = format!("angea-shell@{}.service\0", pts_id); 76 | let slave = append_null_owned(slave); 77 | let user = append_null_owned(user); 78 | 79 | // Init bus and message 80 | let mut bus = MaybeUninit::uninit(); 81 | assert(sd_bus_default_system(bus.as_mut_ptr()))?; 82 | let bus = bus.assume_init(); 83 | 84 | let mut message = MaybeUninit::uninit(); 85 | assert(sd_bus_message_new_method_call( 86 | bus, 87 | message.as_mut_ptr(), 88 | char("org.freedesktop.systemd1\0"), 89 | char("/org/freedesktop/systemd1\0"), 90 | char("org.freedesktop.systemd1.Manager\0"), 91 | char("StartTransientUnit\0"), 92 | ))?; 93 | let message = message.assume_init(); 94 | 95 | // Append message arguments 96 | assert(sd_bus_message_append(message, char("ss\0"), void(&service), void("fail\0")))?; 97 | 98 | // Enter a(sv) 99 | assert(sd_bus_message_open_container(message, SD_BUS_TYPE_ARRAY, char("(sv)\0")))?; 100 | assert(sd_bus_message_append( 101 | message, 102 | char("(sv)(sv)(sv)(sv)(sv)(sv)(sv)\0"), 103 | void("Description\0"), void("s\0"), void("Angea Shell Serivice\0"), 104 | void("WorkingDirectory\0"), void("s\0"), void("~\0"), 105 | void("StandardOutput\0"), void("s\0"), void("tty\0"), 106 | void("StandardInput\0"), void("s\0"), void("tty\0"), 107 | void("StandardError\0"), void("s\0"), void("tty\0"), 108 | void("TTYPath\0"), void("s\0"), void(&slave), 109 | void("User\0"), void("s\0"), void(&user), 110 | ))?; 111 | 112 | // Environment 113 | assert(sd_bus_message_open_container(message, SD_BUS_TYPE_STRUCT, char("sv\0")))?; 114 | assert(sd_bus_message_append(message, char("s\0"), void("Environment\0")))?; 115 | assert(sd_bus_message_open_container(message, SD_BUS_TYPE_VARIANT, char("as\0")))?; 116 | assert(sd_bus_message_append_strv(message, envs.as_mut_ptr()))?; 117 | assert(sd_bus_message_close_container(message))?; 118 | assert(sd_bus_message_close_container(message))?; 119 | 120 | // ExecStart 121 | assert(sd_bus_message_open_container(message, SD_BUS_TYPE_STRUCT, char("sv\0")))?; 122 | assert(sd_bus_message_append(message, char("s\0"), void("ExecStart\0")))?; 123 | assert(sd_bus_message_open_container(message, SD_BUS_TYPE_VARIANT, char("a(sasb)\0")))?; 124 | assert(sd_bus_message_open_container(message, SD_BUS_TYPE_ARRAY, char("(sasb)\0")))?; 125 | assert(sd_bus_message_open_container(message, SD_BUS_TYPE_STRUCT, char("sasb\0")))?; 126 | assert(sd_bus_message_append(message, char("s\0"), void(args[0])))?; 127 | assert(sd_bus_message_append_strv(message, args.as_mut_ptr()))?; 128 | assert(sd_bus_message_append(message, char("b\0"), 1i32))?; // 1 stands for `true` 129 | assert(sd_bus_message_close_container(message))?; 130 | assert(sd_bus_message_close_container(message))?; 131 | assert(sd_bus_message_close_container(message))?; 132 | assert(sd_bus_message_close_container(message))?; 133 | // Exit a(sv) 134 | assert(sd_bus_message_close_container(message))?; 135 | 136 | // Auxiliary 137 | assert(sd_bus_message_append(message, char("a(sa(sv))\0"), 0))?; 138 | 139 | // Send message 140 | assert(sd_bus_call(bus, message, 0, ptr::null_mut(), ptr::null_mut()))?; 141 | 142 | // Free pointer resource 143 | sd_bus_close(bus); 144 | sd_bus_unref(bus); 145 | sd_bus_message_unref(message); 146 | 147 | Ok(()) 148 | } 149 | 150 | /// Convert sd_bus_* return value to `nix::Result` 151 | fn assert(v: i32) -> Result<()> { 152 | if v < 0 { 153 | return Err(Errno::from_i32(-v)); 154 | } 155 | Ok(()) 156 | } 157 | 158 | /// Convert to `*const c_char` 159 | fn char(v: T) -> *const c_char { 160 | v.as_char_ptr() 161 | } 162 | 163 | /// Convert to `*const c_void` 164 | fn void(v: T) -> *const c_void { 165 | v.as_void_ptr() 166 | } 167 | 168 | trait StrPtrCast: Sized { 169 | fn as_char_ptr(&self) -> *const c_char; 170 | 171 | fn as_void_ptr(&self) -> *const c_void { 172 | self.as_char_ptr().cast() 173 | } 174 | } 175 | 176 | impl StrPtrCast for &String { 177 | fn as_char_ptr(&self) -> *const c_char { 178 | self.as_ptr().cast() 179 | } 180 | } 181 | 182 | impl StrPtrCast for &str { 183 | fn as_char_ptr(&self) -> *const c_char { 184 | self.as_ptr().cast() 185 | } 186 | } 187 | 188 | impl StrPtrCast for *mut c_char { 189 | fn as_char_ptr(&self) -> *const c_char { 190 | *self 191 | } 192 | } 193 | 194 | pub struct PTYForward { 195 | epoll: RawFd, 196 | master: RawFd, 197 | signal_fd: SignalFd, 198 | stdin_origin: Termios, 199 | stdout_origin: Termios, 200 | } 201 | 202 | const STDIN_EVENT: u64 = 1; 203 | 204 | const MASTER_EVENT: u64 = 2; 205 | 206 | const SIGNAL_EVENT: u64 = 3; 207 | 208 | impl PTYForward { 209 | pub fn new(master: RawFd) -> Result { 210 | let epoll = epoll::epoll_create()?; 211 | 212 | let mut stdin_event = EpollEvent::new(EpollFlags::EPOLLIN, STDIN_EVENT); 213 | let mut master_event = EpollEvent::new(EpollFlags::EPOLLIN, MASTER_EVENT); 214 | epoll::epoll_ctl( 215 | epoll, 216 | EpollOp::EpollCtlAdd, 217 | libc::STDIN_FILENO, 218 | &mut stdin_event, 219 | )?; 220 | epoll::epoll_ctl(epoll, EpollOp::EpollCtlAdd, master, &mut master_event)?; 221 | 222 | let mut sig_set = SigSet::empty(); 223 | sig_set.add(Signal::SIGWINCH); 224 | sigprocmask(SigmaskHow::SIG_SETMASK, Some(&sig_set), None)?; 225 | let signal_fd = SignalFd::new(&sig_set)?; 226 | let mut sig_event = EpollEvent::new(EpollFlags::EPOLLIN, SIGNAL_EVENT); 227 | epoll::epoll_ctl( 228 | epoll, 229 | EpollOp::EpollCtlAdd, 230 | signal_fd.as_raw_fd(), 231 | &mut sig_event, 232 | )?; 233 | 234 | Self::set_nonblock(libc::STDIN_FILENO, true)?; 235 | let stdin_origin = Self::set_raw_termios(libc::STDIN_FILENO)?; 236 | let stdout_origin = Self::set_raw_termios(libc::STDOUT_FILENO)?; 237 | Ok(PTYForward { 238 | epoll, 239 | master, 240 | signal_fd, 241 | stdin_origin, 242 | stdout_origin, 243 | }) 244 | } 245 | 246 | pub fn wait(&mut self) -> Result<()> { 247 | wait_service(self.master)?; 248 | 249 | let mut events = [EpollEvent::empty(); 128]; 250 | let mut buf = [0; 1024]; 251 | unsafe { 252 | 'epoll: loop { 253 | let n = epoll::epoll_wait(self.epoll, &mut events, -1)?; 254 | let ready = events.get_unchecked(..n); 255 | 256 | for ev in ready { 257 | match ev.data() { 258 | STDIN_EVENT => { 259 | // stdin => master 260 | match read(libc::STDIN_FILENO, &mut buf) { 261 | Ok(n) => write(self.master, buf.get_unchecked(..n))?, 262 | Err(Errno::EWOULDBLOCK) => continue, 263 | Err(e) => return Err(e), 264 | }; 265 | } 266 | MASTER_EVENT => { 267 | // master => stdout 268 | match read(self.master, &mut buf) { 269 | Ok(n) => write(libc::STDOUT_FILENO, buf.get_unchecked(..n))?, 270 | Err(Errno::EWOULDBLOCK) => continue, 271 | Err(Errno::EIO) => break 'epoll, 272 | Err(e) => return Err(e), 273 | }; 274 | } 275 | SIGNAL_EVENT => { 276 | // signal 277 | self.signal_fd.read_signal()?; 278 | window_resize(self.master)?; 279 | } 280 | _ => {} 281 | } 282 | } 283 | } 284 | } 285 | Ok(()) 286 | } 287 | 288 | /// Recovery termios and non-block status 289 | /// 290 | /// # Errors 291 | /// 292 | /// Unexpected I/O error. But it should be no error because `PTYForward::new()` is ok. 293 | fn disconnect(&self) -> Result<()> { 294 | termios::tcsetattr(libc::STDOUT_FILENO, SetArg::TCSANOW, &self.stdout_origin)?; 295 | termios::tcsetattr(libc::STDIN_FILENO, SetArg::TCSANOW, &self.stdin_origin)?; 296 | Self::set_nonblock(libc::STDIN_FILENO, false)?; 297 | Ok(()) 298 | } 299 | 300 | /// Set I/O non-block 301 | /// 302 | /// # Errors 303 | /// 304 | /// Unexpected I/O error 305 | fn set_nonblock(fd: RawFd, nonblock: bool) -> Result<()> { 306 | let bits = fcntl(fd, FcntlArg::F_GETFL)?; 307 | let mut flags = unsafe { OFlag::from_bits_unchecked(bits) }; 308 | flags = if nonblock { 309 | flags | OFlag::O_NONBLOCK 310 | } else { 311 | flags & !OFlag::O_NONBLOCK 312 | }; 313 | fcntl(fd, FcntlArg::F_SETFL(flags))?; 314 | Ok(()) 315 | } 316 | 317 | /// Set raw termios config, return origin config for recovery 318 | fn set_raw_termios(fd: RawFd) -> Result { 319 | let stdin_origin = termios::tcgetattr(fd)?; 320 | let mut stdin_attr = stdin_origin.clone(); 321 | termios::cfmakeraw(&mut stdin_attr); 322 | termios::tcsetattr(fd, SetArg::TCSANOW, &stdin_attr)?; 323 | Ok(stdin_origin) 324 | } 325 | } 326 | 327 | impl Drop for PTYForward { 328 | fn drop(&mut self) { 329 | if let Err(e) = self.disconnect() { 330 | println!("error when disconnecting: {}", e); 331 | } 332 | } 333 | } 334 | 335 | fn wait_service(master: RawFd) -> Result<()> { 336 | let mut buf = [0; 8]; 337 | for _ in 0..30 { 338 | if let Ok(n) = read(master, &mut buf) { 339 | write(libc::STDOUT_FILENO, &buf[..n])?; 340 | return Ok(()); 341 | } 342 | std::thread::sleep(std::time::Duration::from_millis(100)); 343 | } 344 | Err(Errno::ETIMEDOUT) 345 | } 346 | 347 | fn window_resize(master: RawFd) -> Result<()> { 348 | unsafe { 349 | let mut size: libc::winsize = mem::zeroed(); 350 | if libc::ioctl(libc::STDOUT_FILENO, libc::TIOCGWINSZ, &mut size) == -1 351 | || libc::ioctl(master, libc::TIOCSWINSZ, &size) == -1 352 | { 353 | return Err(Errno::last()); 354 | } 355 | } 356 | Ok(()) 357 | } 358 | 359 | fn append_null_ref(str: &str) -> String { 360 | let mut str = String::from(str); 361 | str.push('\0'); 362 | str 363 | } 364 | 365 | fn append_null_owned(mut str: String) -> String { 366 | str.push('\0'); 367 | str 368 | } 369 | -------------------------------------------------------------------------------- /src/systemd.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | 3 | use nix::dir::Dir; 4 | use nix::fcntl::{open, OFlag}; 5 | use nix::mount::{mount, MsFlags}; 6 | use nix::sched::{clone, CloneFlags}; 7 | use nix::sys::signal::{kill, Signal}; 8 | use nix::sys::stat::Mode; 9 | use nix::unistd::{close, execve, read, Pid}; 10 | use nix::Result; 11 | 12 | /// Start a systemd process in a new PID namespace. 13 | pub fn start() -> Result<()> { 14 | let mut stack = [0; 4096]; 15 | clone( 16 | Box::new(|| -> isize { 17 | let args = [CString::new("/lib/systemd/systemd").unwrap()]; 18 | let environ: [CString; 0] = []; 19 | mount( 20 | Some("proc"), 21 | "/proc", 22 | Some("proc"), 23 | MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC | MsFlags::MS_NODEV, 24 | None::<&str>, 25 | ) 26 | .unwrap(); 27 | execve(args[0].as_c_str(), &args, &environ).unwrap(); 28 | unreachable!(); 29 | }), 30 | &mut stack, 31 | CloneFlags::CLONE_NEWPID | CloneFlags::CLONE_NEWNS, 32 | None, 33 | )?; 34 | Ok(()) 35 | } 36 | 37 | /// Try to get running systemd pid from procfs 38 | pub fn get_running() -> Result> { 39 | let proc = Dir::open("/proc", OFlag::O_DIRECTORY, Mode::empty())?; 40 | for entry in proc { 41 | match entry { 42 | Ok(e) => { 43 | let file_name = e.file_name().to_string_lossy(); 44 | if file_name == "." || file_name == ".." { 45 | continue; 46 | } 47 | let pid = match file_name.parse() { 48 | Ok(p) => p, 49 | Err(_) => continue, 50 | }; 51 | let mut path = String::from("/proc/"); 52 | path.push_str(&file_name); 53 | path.push_str("/comm"); 54 | 55 | let fd = open(path.as_str(), OFlag::O_RDONLY, Mode::empty())?; 56 | let mut buf = [0; 8]; 57 | let n = read(fd, &mut buf)?; 58 | if &buf[..n] == b"systemd\n" { 59 | return Ok(Some(Pid::from_raw(pid))); 60 | } 61 | close(fd)?; 62 | } 63 | Err(e) => return Err(e), 64 | } 65 | } 66 | Ok(None) 67 | } 68 | 69 | /// Kill running process 70 | pub fn shutdown() -> Result<()> { 71 | if let Some(pid) = get_running()? { 72 | kill(pid, Signal::SIGKILL) 73 | } else { 74 | Ok(()) 75 | } 76 | } 77 | --------------------------------------------------------------------------------