Blob


1 use std::{
2 ffi::OsStr,
3 fs::OpenOptions,
4 io::{self, ErrorKind},
5 os::{
6 fd::{AsFd, AsRawFd, BorrowedFd},
7 unix::{fs::OpenOptionsExt, process::ExitStatusExt},
8 },
9 path::{Path, PathBuf},
10 ptr::addr_of_mut,
11 time::SystemTime,
12 };
14 mod status;
15 mod tai;
17 use status::SvStatus;
19 fn locked(fd: BorrowedFd) -> io::Result<bool> {
20 let mut flock = libc::flock {
21 l_type: libc::F_RDLCK as libc::c_short,
22 l_whence: libc::SEEK_SET as libc::c_short,
23 l_start: 0,
24 l_len: 0,
25 l_pid: 0,
26 };
27 match unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_GETLK, addr_of_mut!(flock)) } {
28 -1 => Err(io::Error::last_os_error()),
29 0 => Ok(flock.l_type != libc::F_UNLCK as libc::c_short),
30 _ => todo!("fcntl() != -1 && fcntl != 0"),
31 }
32 }
34 pub struct SupervisedStatus {
35 pub up: bool,
36 pub wanted_up: bool,
37 pub normally_up: bool,
38 pub ready: bool,
39 pub paused: bool,
40 pub pid: u64,
41 pub exit_code: Option<i32>,
42 // signal
43 pub signal_number: Option<i32>,
44 pub up_down_since: SystemTime,
45 pub ready_since: SystemTime,
46 }
48 pub enum Status {
49 Unsupervised,
50 Supervised(SupervisedStatus),
51 }
53 pub struct ServiceDir(PathBuf);
55 impl ServiceDir {
56 pub fn name(&self) -> &OsStr {
57 self.0.file_name().unwrap()
58 }
59 pub fn supervised(&self) -> io::Result<bool> {
60 let mut path = self.0.clone();
61 path.extend(["supervise", "lock"]);
62 match OpenOptions::new()
63 .read(true)
64 .custom_flags(libc::O_NONBLOCK)
65 .open(&path)
66 {
67 Err(err) => match err.kind() {
68 ErrorKind::NotFound => Ok(false),
69 _ => Err(err),
70 },
71 Ok(lock) => locked(lock.as_fd()),
72 }
73 }
74 pub fn status(&self) -> io::Result<Status> {
75 if self.supervised()? {
76 let mut path = self.0.clone();
77 path.extend(["supervise", "status"]);
78 let status = OpenOptions::new()
79 .read(true)
80 .custom_flags(libc::O_CLOEXEC)
81 .open(&path)
82 .and_then(SvStatus::try_from)?;
83 assert!(path.pop());
84 assert!(path.pop());
85 path.push("down");
86 let normally_up = !path.try_exists()?;
87 Ok(Status::Supervised(SupervisedStatus {
88 up: status.pid != 0 && !status.finishing,
89 wanted_up: status.want_up,
90 normally_up,
91 ready: status.pid != 0 && status.ready,
92 paused: status.paused,
93 pid: status.pid,
94 exit_code: if status.pid != 0 && !status.finishing {
95 None
96 } else {
97 status.wait_status.code()
98 },
99 signal_number: if status.pid != 0 && !status.finishing {
100 None
101 } else {
102 status.wait_status.signal()
103 },
104 up_down_since: status.stamp,
105 ready_since: status.ready_stamp,
106 }))
107 } else {
108 Ok(Status::Unsupervised)
113 pub struct ScanDir(PathBuf);
115 impl ScanDir {
116 pub fn new<P: AsRef<Path>>(path: P) -> Self {
117 Self(PathBuf::from(path.as_ref()))
119 pub fn service_dirs(&self) -> io::Result<impl Iterator<Item = ServiceDir>> {
120 Ok(self.0.read_dir()?.flatten().filter_map(|entry| {
121 let path = entry.path();
122 if path.file_name().unwrap() == ".s6-svscan" {
123 None
124 } else {
125 Some(ServiceDir(path))
127 }))