ofiles/
lib.rs

1use std::fs::{self, File};
2use std::io::{BufRead, BufReader};
3use std::path::{Path, PathBuf};
4
5use error_chain;
6use glob::glob;
7use log::{trace, info};
8use nix::sys::stat::{lstat, SFlag};
9
10/// Newtype pattern to avoid type errors.
11/// https://www.gnu.org/software/libc/manual/html_node/Process-Identification.html
12#[derive(Debug, Clone, Copy)]
13pub struct Pid(u32);
14
15#[derive(Debug)]
16struct Inode(u64);
17
18impl From<Pid> for u32 {
19    fn from(pid: Pid) -> u32 {
20        pid.0
21    }
22}
23
24impl Inode {
25    pub fn contained_in(&self, other: &str) -> bool {
26        let num_str: String = other.chars().filter(|x| x.is_numeric()).collect();
27        match num_str.parse::<u64>() {
28            Ok(n) => n == self.0,
29            Err(_) => false,
30        }
31    }
32}
33
34error_chain::error_chain! {
35    foreign_links {
36        Io(::std::io::Error);
37        Nix(nix::Error);
38        ParseInt(::std::num::ParseIntError);
39        Parse(::std::string::ParseError);
40    }
41
42    errors {
43        InodeNotFound(t: String) {
44            description("Inode not found")
45            display("Inode not found: '{}'", t)
46        }
47    }
48}
49
50macro_rules! unwrap_or_continue {
51    ($e:expr) => {{
52        if let Ok(x) = $e {
53            x
54        } else {
55            continue;
56        }
57    }};
58}
59
60//fn extract_pid_from_proc<P: AsRef<Path>>(proc_entry: P) -> Result<Pid> {
61//
62//    let vec: Vec<&str> = proc_entry.as_os_string()?
63//                        .split('/')
64//                        .collect();
65//
66//    eprintln!("vec: {:?}", vec);
67//    Ok(Pid(0))
68////                        .collect::<Vec<&str>>()[2]
69////                        .parse::<u32>().unwrap();
70////            pids.push(Pid(pid));
71//}
72
73/// Given a single `line` from `/proc/net/unix`, return the Inode.
74///
75/// See man 5 proc.
76fn extract_socket_inode(line: &str) -> Result<Inode> {
77    let elements: Vec<&str> = line.split(' ').collect();
78    let inode = Inode(elements[6].parse::<u64>()?);
79
80    Ok(inode)
81}
82
83/// Search `/proc/net/unix` for the line containing `path_buf` and return the inode given
84/// by the system.
85fn socket_file_to_inode(path_buf: &PathBuf) -> Result<Inode> {
86    let f = File::open("/proc/net/unix")?;
87    let f = BufReader::new(f);
88
89    for line in f.lines() {
90        if let Ok(l) = line {
91            info!("line: {:?}", l);
92            if l.contains(path_buf.to_str().unwrap()) {
93                let inode = extract_socket_inode(&l)?;
94                return Ok(inode);
95            }
96        }
97    }
98
99    Err(Error::from_kind(ErrorKind::InodeNotFound(
100        path_buf.to_str().unwrap().to_string(),
101    )))
102}
103
104/// Given a file path, return the process id of any processes that have an open file descriptor
105/// pointing to the given file.
106pub fn opath<P: AsRef<Path>>(path: P) -> Result<Vec<Pid>> {
107    let mut path_buf = PathBuf::new();
108    path_buf.push(path);
109    let mut pids: Vec<Pid> = Vec::new();
110    let stat_info = lstat(&path_buf)?;
111    info!("stat info: {:?}", stat_info);
112
113    let mut target_path = PathBuf::new();
114    target_path.push(fs::canonicalize(&path_buf)?);
115    info!("Target path: {:?}", target_path);
116
117    // FIXME: not sure what the *right* way to do this is. Revisit later.
118    if SFlag::S_IFMT.bits() & stat_info.st_mode == SFlag::S_IFREG.bits() {
119        info!("stat info reg file: {:?}", stat_info.st_mode);
120        for entry in glob("/proc/*/fd/*").expect("Failed to read glob pattern") {
121            let e = unwrap_or_continue!(entry);
122            let real = unwrap_or_continue!(fs::read_link(&e));
123
124            if real == target_path {
125                let pbuf = e.to_str().unwrap().split('/').collect::<Vec<&str>>()[2];
126                let pid = unwrap_or_continue!(pbuf.parse::<u32>());
127                pids.push(Pid(pid));
128                info!("process: {:?} -> real: {:?}", pid, real);
129            }
130        }
131    } else if SFlag::S_IFMT.bits() & stat_info.st_mode == SFlag::S_IFSOCK.bits() {
132        info!("stat info socket file: {:?}", stat_info.st_mode);
133        let inode = socket_file_to_inode(&target_path)?;
134        info!("inode: {:?}", inode);
135        for entry in glob("/proc/*/fd/*").expect("Failed to read glob pattern") {
136            let e = unwrap_or_continue!(entry);
137            let real = unwrap_or_continue!(fs::read_link(&e));
138            let real = real.as_path().display().to_string();
139            trace!("real: {:?} vs {}", real, inode.0);
140            if inode.contained_in(&real) {
141                info!("real found: {:?}", real);
142                let pbuf = e.to_str().unwrap().split('/').collect::<Vec<&str>>()[2];
143                let pid = unwrap_or_continue!(pbuf.parse::<u32>());
144                pids.push(Pid(pid));
145            }
146        }
147    } else if SFlag::S_IFMT.bits() & stat_info.st_mode == SFlag::S_IFDIR.bits() {
148        info!("Got a directory!");
149        for entry in glob("/proc/*/fd/*").expect("Failed to read glob pattern") {
150            let e = unwrap_or_continue!(entry);
151            let real = unwrap_or_continue!(fs::read_link(&e));
152            trace!("Real: {:?}", real);
153
154            if real == target_path {
155                info!("Found target: {:?}", target_path);
156                let pbuf = e.to_str().unwrap().split('/').collect::<Vec<&str>>()[2];
157                let pid = unwrap_or_continue!(pbuf.parse::<u32>());
158                pids.push(Pid(pid));
159                info!("process: {:?} -> real: {:?}", pid, real);
160            }
161        }
162    } else {
163        return Err(crate::ErrorKind::InodeNotFound(format!("Unknown file {:?}", stat_info)).into());
164    }
165
166    Ok(pids)
167}
168
169#[cfg(test)]
170mod tests {
171    use super::opath;
172    use super::Inode;
173    use std::fs::File;
174    use std::io::Write;
175    use std::process::Command;
176    use std::thread;
177    use std::time::Duration;
178
179    use env_logger;
180    use nix::unistd::{fork, ForkResult};
181    use rusty_fork::rusty_fork_id;
182    use rusty_fork::rusty_fork_test;
183    use rusty_fork::rusty_fork_test_name;
184    use tempfile::{NamedTempFile, TempDir};
185
186    // TODO: test symlink, socket file, fifo
187
188    #[test]
189    fn test_inode_contained_in() {
190        let inode = Inode(1234);
191        let buf = "socket:[1234]";
192
193        assert!(inode.contained_in(buf));
194    }
195
196    rusty_fork_test! {
197    #[test]
198    fn test_ofile_other_process_unix_socket() {
199        env_logger::init();
200        let path = "/tmp/.opath_socket";
201
202        match fork() {
203            Ok(ForkResult::Parent { child, .. }) => {
204                eprintln!("Child pid: {}", child);
205                let mut spawn = Command::new("nc")
206                                .arg("-U")
207                                .arg(&path)
208                                .arg("-l")
209                                .spawn()
210                                .unwrap();
211                thread::sleep(Duration::from_millis(500));
212                let pid = opath(&path).unwrap().pop().unwrap();
213
214                assert_eq!(pid.0, spawn.id() as u32);
215                spawn.kill().unwrap();
216                std::fs::remove_file(&path).unwrap();
217            },
218            Ok(ForkResult::Child) => {
219                thread::sleep(Duration::from_millis(5000));
220            },
221            Err(_) => panic!("Fork failed"),
222        }
223    }
224    }
225
226    #[test]
227    fn test_ofile_basic() {
228        let mut file = NamedTempFile::new().unwrap();
229        writeln!(file, "T").unwrap();
230
231        let p = file.path();
232
233        let ofile_pid = opath(p).unwrap().pop().unwrap();
234
235        assert_eq!(ofile_pid.0, std::process::id());
236    }
237
238    #[test]
239    fn test_directory_basic() {
240        let tmp_dir = TempDir::new().unwrap();
241        let p = tmp_dir.path();
242        let _dir = File::open(&p).unwrap();
243
244        let ofile_pid = opath(p).unwrap().pop().unwrap();
245
246        assert_eq!(ofile_pid.0, std::process::id());
247    }
248
249    rusty_fork_test! {
250    #[test]
251    fn test_file_other_process() {
252        let path = "/tmp/.opath_tmp";
253
254        match fork() {
255            Ok(ForkResult::Parent { child, .. }) => {
256                thread::sleep(Duration::from_millis(100));
257                eprintln!("Child pid: {}", child);
258                let pid = opath(&path).unwrap().pop().unwrap();
259
260                assert_eq!(pid.0, child.as_raw() as u32);
261            },
262            Ok(ForkResult::Child) => {
263                let mut f = File::create(&path).unwrap();
264                writeln!(f, "test").unwrap();
265
266                thread::sleep(Duration::from_millis(500));
267            },
268            Err(_) => panic!("Fork failed"),
269        }
270    }
271    }
272
273    rusty_fork_test! {
274    #[test]
275    fn test_directory_other_process() {
276        let path = ".";
277
278        match fork() {
279            Ok(ForkResult::Parent { child, .. }) => {
280                thread::sleep(Duration::from_millis(100));
281                eprintln!("Child pid: {}", child);
282                let pid = opath(&path).unwrap().pop().unwrap();
283
284                assert_eq!(pid.0, child.as_raw() as u32);
285            },
286            Ok(ForkResult::Child) => {
287                let _dir = File::open(&path).unwrap();
288
289                thread::sleep(Duration::from_millis(500));
290            },
291            Err(_) => panic!("Fork failed"),
292        }
293    }
294    }
295}