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#[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
60fn 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
83fn 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
104pub 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 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 #[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}