├── LICENSE ├── README.md ├── pidfd.rs └── shutdown.rs /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 Orbital Labs, LLC 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 | # pidfd-rs 2 | 3 | Fast, polling-free approach to kill and wait for all processes to exit in an init implementation. 4 | 5 | - Rust + Tokio 6 | - pidfd + epoll + timer 7 | -------------------------------------------------------------------------------- /pidfd.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2023 Orbital Labs, LLC 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | use std::os::fd::{OwnedFd, FromRawFd, AsRawFd, RawFd}; 24 | 25 | use nix::{libc::{syscall, SYS_pidfd_open, PIDFD_NONBLOCK, SYS_pidfd_send_signal, siginfo_t}, sys::signal::Signal}; 26 | use tokio::io::unix::{AsyncFd, AsyncFdReadyGuard}; 27 | 28 | pub struct PidFd(AsyncFd); 29 | 30 | impl PidFd { 31 | pub fn open(pid: i32) -> std::io::Result { 32 | let fd = unsafe { syscall(SYS_pidfd_open, pid, PIDFD_NONBLOCK) }; 33 | if fd < 0 { 34 | return Err(std::io::Error::last_os_error()); 35 | } 36 | let fd = unsafe { OwnedFd::from_raw_fd(fd as _) }; 37 | let fd = AsyncFd::new(fd)?; 38 | Ok(Self(fd)) 39 | } 40 | 41 | pub fn kill(&self, signal: Signal) -> nix::Result<()> { 42 | let res = unsafe { syscall(SYS_pidfd_send_signal, self.as_raw_fd(), signal, std::ptr::null::<*const siginfo_t>(), 0) }; 43 | if res < 0 { 44 | return Err(nix::Error::last()); 45 | } 46 | 47 | Ok(()) 48 | } 49 | 50 | pub async fn wait(&self) -> tokio::io::Result> { 51 | self.0.readable().await 52 | } 53 | } 54 | 55 | impl AsRawFd for PidFd { 56 | fn as_raw_fd(&self) -> RawFd { 57 | self.0.as_raw_fd() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /shutdown.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2023 Orbital Labs, LLC 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | fn kill_one_entry(entry: Result, signal: Signal) -> Result, Box> { 24 | let filename = entry?.file_name(); 25 | if let Ok(pid) = filename.to_str().unwrap().parse::() { 26 | // skip pid 1 27 | if pid == 1 { 28 | return Ok(None); 29 | } 30 | 31 | // skip kthreads (they won't exit) 32 | if is_process_kthread(pid)? { 33 | return Ok(None); 34 | } 35 | 36 | // open a pidfd before killing, then kill via pidfd for safety 37 | let pidfd = PidFd::open(pid)?; 38 | pidfd.kill(signal)?; 39 | Ok(Some(pidfd)) 40 | } else { 41 | Ok(None) 42 | } 43 | } 44 | 45 | fn broadcast_signal(signal: Signal) -> nix::Result> { 46 | // freeze to get consistent snapshot and avoid thrashing 47 | kill(Pid::from_raw(-1), Signal::SIGSTOP)?; 48 | 49 | // can't use kill(-1) because we need to know which PIDs to wait for exit 50 | // otherwise unmount returns EBUSY 51 | let mut pids = Vec::new(); 52 | match fs::read_dir("/proc") { 53 | Ok(entries) => { 54 | for entry in entries { 55 | match kill_one_entry(entry, signal) { 56 | Ok(Some(pid)) => { 57 | pids.push(pid); 58 | }, 59 | Err(e) => { 60 | println!(" !!! Failed to read /proc entry: {}", e); 61 | }, 62 | _ => {}, 63 | } 64 | } 65 | }, 66 | Err(e) => { 67 | println!(" !!! Failed to read /proc: {}", e); 68 | }, 69 | } 70 | 71 | // always make sure to unfreeze 72 | kill(Pid::from_raw(-1), Signal::SIGCONT)?; 73 | Ok(pids) 74 | } 75 | 76 | async fn wait_for_pidfds_exit(pidfds: Vec, timeout: Duration) -> Result<(), Box> { 77 | let futures = pidfds.into_iter() 78 | .map(|pidfd| { 79 | async move { 80 | let _guard = pidfd.wait().await?; 81 | Ok::<(), tokio::io::Error>(()) 82 | } 83 | }) 84 | .collect::>(); 85 | 86 | let results = tokio::time::timeout(timeout, futures::future::join_all(futures)).await?; 87 | for result in results { 88 | if let Err(err) = result { 89 | return Err(InitError::PollPidFd(err).into()); 90 | } 91 | } 92 | 93 | Ok(()) 94 | } 95 | --------------------------------------------------------------------------------