ofiles-0.2.0/.cargo_vcs_info.json0000644000000001121374417200500123470ustar { "git": { "sha1": "63988462f176617c4a40754282f517e6c33cde0e" } } ofiles-0.2.0/.github/workflows/rust.yml010066400017500001750000000005351365064726400163120ustar 00000000000000name: Rust on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install netcat run: sudo apt-get install netcat - name: Build run: cargo build --verbose - name: Run tests run: cargo test --verbose ofiles-0.2.0/.gitignore010066400017500001750000000000161365037212100131420ustar 00000000000000/target *.swp ofiles-0.2.0/Cargo.toml0000644000000024661374417200500103630ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "ofiles" version = "0.2.0" authors = ["Joshua Abraham "] description = "Identify processes that have opened files or sockets" homepage = "https://github.com/jabedude/ofiles/" documentation = "https://docs.rs/ofiles" readme = "README.md" keywords = ["filesystem", "sockets", "processes", "lsof", "fuser"] categories = ["filesystem", "os"] license = "BSD-3-Clause" repository = "https://github.com/jabedude/ofiles/" [dependencies.error-chain] version = "^0.12.1" default-features = false [dependencies.glob] version = "0.3.0" [dependencies.log] version = "0.4.8" [dependencies.nix] version = "0.17.0" [dev-dependencies.env_logger] version = "0.7.1" [dev-dependencies.rusty-fork] version = "0.2.2" [dev-dependencies.tempfile] version = "3.1.0" ofiles-0.2.0/Cargo.toml.orig010066400017500001750000000014141374417132000140460ustar 00000000000000[package] name = "ofiles" version = "0.2.0" authors = ["Joshua Abraham "] edition = "2018" description = "Identify processes that have opened files or sockets" keywords = ["filesystem", "sockets", "processes", "lsof", "fuser"] homepage = "https://github.com/jabedude/ofiles/" documentation = "https://docs.rs/ofiles" repository = "https://github.com/jabedude/ofiles/" readme = "README.md" license = "BSD-3-Clause" categories = ["filesystem", "os"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] error-chain = { version = "^0.12.1", default-features = false } glob = "0.3.0" log = "0.4.8" nix = "0.17.0" [dev-dependencies] env_logger = "0.7.1" rusty-fork = "0.2.2" tempfile = "3.1.0" ofiles-0.2.0/LICENSE010066400017500001750000000027621365066721200122010ustar 00000000000000BSD 3-Clause License Copyright (c) 2020, Joshua Abraham All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ofiles-0.2.0/README.md010066400017500001750000000014171365066647700124630ustar 00000000000000# ofiles [![crates.io](https://img.shields.io/crates/v/ofiles.svg)](https://crates.io/crates/ofiles) ![Rust](https://github.com/jabedude/ofiles/workflows/Rust/badge.svg) [![Documentation](https://docs.rs/ofiles/badge.svg)](https://docs.rs/ofiles/) [![license](https://img.shields.io/badge/license-BSD3.0-blue.svg)](https://github.com/jabedude/ofiles/LICENSE) A tiny library for determining what process has a file opened for reading/writing/etc. I wrote this for another project but I hope will be useful in other applications. Example: ```rust use ofiles::opath; let mut pids = opath("/path/to/a/file").unwrap(); // Now we have a Vec of process ID's that have the `/path/to/a/file` open for pid in pids { println!("Process {} has {} open!", pid, "/path/to/a/file"); } ``` ofiles-0.2.0/src/lib.rs010066400017500001750000000216461374417030500130750ustar 00000000000000use std::fs::{self, File}; use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; use error_chain; use glob::glob; use log::{trace, info}; use nix::sys::stat::{lstat, SFlag}; /// Newtype pattern to avoid type errors. /// https://www.gnu.org/software/libc/manual/html_node/Process-Identification.html #[derive(Debug, Clone, Copy)] pub struct Pid(u32); #[derive(Debug)] struct Inode(u64); impl From for u32 { fn from(pid: Pid) -> u32 { pid.0 } } impl Inode { pub fn contained_in(&self, other: &str) -> bool { let num_str: String = other.chars().filter(|x| x.is_numeric()).collect(); match num_str.parse::() { Ok(n) => n == self.0, Err(_) => false, } } } error_chain::error_chain! { foreign_links { Io(::std::io::Error); Nix(nix::Error); ParseInt(::std::num::ParseIntError); Parse(::std::string::ParseError); } errors { InodeNotFound(t: String) { description("Inode not found") display("Inode not found: '{}'", t) } } } macro_rules! unwrap_or_continue { ($e:expr) => {{ if let Ok(x) = $e { x } else { continue; } }}; } //fn extract_pid_from_proc>(proc_entry: P) -> Result { // // let vec: Vec<&str> = proc_entry.as_os_string()? // .split('/') // .collect(); // // eprintln!("vec: {:?}", vec); // Ok(Pid(0)) //// .collect::>()[2] //// .parse::().unwrap(); //// pids.push(Pid(pid)); //} /// Given a single `line` from `/proc/net/unix`, return the Inode. /// /// See man 5 proc. fn extract_socket_inode(line: &str) -> Result { let elements: Vec<&str> = line.split(' ').collect(); let inode = Inode(elements[6].parse::()?); Ok(inode) } /// Search `/proc/net/unix` for the line containing `path_buf` and return the inode given /// by the system. fn socket_file_to_inode(path_buf: &PathBuf) -> Result { let f = File::open("/proc/net/unix")?; let f = BufReader::new(f); for line in f.lines() { if let Ok(l) = line { info!("line: {:?}", l); if l.contains(path_buf.to_str().unwrap()) { let inode = extract_socket_inode(&l)?; return Ok(inode); } } } Err(Error::from_kind(ErrorKind::InodeNotFound( path_buf.to_str().unwrap().to_string(), ))) } /// Given a file path, return the process id of any processes that have an open file descriptor /// pointing to the given file. pub fn opath>(path: P) -> Result> { let mut path_buf = PathBuf::new(); path_buf.push(path); let mut pids: Vec = Vec::new(); let stat_info = lstat(&path_buf)?; info!("stat info: {:?}", stat_info); let mut target_path = PathBuf::new(); target_path.push(fs::canonicalize(&path_buf)?); info!("Target path: {:?}", target_path); // FIXME: not sure what the *right* way to do this is. Revisit later. if SFlag::S_IFMT.bits() & stat_info.st_mode == SFlag::S_IFREG.bits() { info!("stat info reg file: {:?}", stat_info.st_mode); for entry in glob("/proc/*/fd/*").expect("Failed to read glob pattern") { let e = unwrap_or_continue!(entry); let real = unwrap_or_continue!(fs::read_link(&e)); if real == target_path { let pbuf = e.to_str().unwrap().split('/').collect::>()[2]; let pid = unwrap_or_continue!(pbuf.parse::()); pids.push(Pid(pid)); info!("process: {:?} -> real: {:?}", pid, real); } } } else if SFlag::S_IFMT.bits() & stat_info.st_mode == SFlag::S_IFSOCK.bits() { info!("stat info socket file: {:?}", stat_info.st_mode); let inode = socket_file_to_inode(&target_path)?; info!("inode: {:?}", inode); for entry in glob("/proc/*/fd/*").expect("Failed to read glob pattern") { let e = unwrap_or_continue!(entry); let real = unwrap_or_continue!(fs::read_link(&e)); let real = real.as_path().display().to_string(); trace!("real: {:?} vs {}", real, inode.0); if inode.contained_in(&real) { info!("real found: {:?}", real); let pbuf = e.to_str().unwrap().split('/').collect::>()[2]; let pid = unwrap_or_continue!(pbuf.parse::()); pids.push(Pid(pid)); } } } else if SFlag::S_IFMT.bits() & stat_info.st_mode == SFlag::S_IFDIR.bits() { info!("Got a directory!"); for entry in glob("/proc/*/fd/*").expect("Failed to read glob pattern") { let e = unwrap_or_continue!(entry); let real = unwrap_or_continue!(fs::read_link(&e)); trace!("Real: {:?}", real); if real == target_path { info!("Found target: {:?}", target_path); let pbuf = e.to_str().unwrap().split('/').collect::>()[2]; let pid = unwrap_or_continue!(pbuf.parse::()); pids.push(Pid(pid)); info!("process: {:?} -> real: {:?}", pid, real); } } } else { return Err(crate::ErrorKind::InodeNotFound(format!("Unknown file {:?}", stat_info)).into()); } Ok(pids) } #[cfg(test)] mod tests { use super::opath; use super::Inode; use std::fs::File; use std::io::Write; use std::process::Command; use std::thread; use std::time::Duration; use env_logger; use nix::unistd::{fork, ForkResult}; use rusty_fork::rusty_fork_id; use rusty_fork::rusty_fork_test; use rusty_fork::rusty_fork_test_name; use tempfile::{NamedTempFile, TempDir}; // TODO: test symlink, socket file, fifo #[test] fn test_inode_contained_in() { let inode = Inode(1234); let buf = "socket:[1234]"; assert!(inode.contained_in(buf)); } rusty_fork_test! { #[test] fn test_ofile_other_process_unix_socket() { env_logger::init(); let path = "/tmp/.opath_socket"; match fork() { Ok(ForkResult::Parent { child, .. }) => { eprintln!("Child pid: {}", child); let mut spawn = Command::new("nc") .arg("-U") .arg(&path) .arg("-l") .spawn() .unwrap(); thread::sleep(Duration::from_millis(500)); let pid = opath(&path).unwrap().pop().unwrap(); assert_eq!(pid.0, spawn.id() as u32); spawn.kill().unwrap(); std::fs::remove_file(&path).unwrap(); }, Ok(ForkResult::Child) => { thread::sleep(Duration::from_millis(5000)); }, Err(_) => panic!("Fork failed"), } } } #[test] fn test_ofile_basic() { let mut file = NamedTempFile::new().unwrap(); writeln!(file, "T").unwrap(); let p = file.path(); let ofile_pid = opath(p).unwrap().pop().unwrap(); assert_eq!(ofile_pid.0, std::process::id()); } #[test] fn test_directory_basic() { let tmp_dir = TempDir::new().unwrap(); let p = tmp_dir.path(); let _dir = File::open(&p).unwrap(); let ofile_pid = opath(p).unwrap().pop().unwrap(); assert_eq!(ofile_pid.0, std::process::id()); } rusty_fork_test! { #[test] fn test_file_other_process() { let path = "/tmp/.opath_tmp"; match fork() { Ok(ForkResult::Parent { child, .. }) => { thread::sleep(Duration::from_millis(100)); eprintln!("Child pid: {}", child); let pid = opath(&path).unwrap().pop().unwrap(); assert_eq!(pid.0, child.as_raw() as u32); }, Ok(ForkResult::Child) => { let mut f = File::create(&path).unwrap(); writeln!(f, "test").unwrap(); thread::sleep(Duration::from_millis(500)); }, Err(_) => panic!("Fork failed"), } } } rusty_fork_test! { #[test] fn test_directory_other_process() { let path = "."; match fork() { Ok(ForkResult::Parent { child, .. }) => { thread::sleep(Duration::from_millis(100)); eprintln!("Child pid: {}", child); let pid = opath(&path).unwrap().pop().unwrap(); assert_eq!(pid.0, child.as_raw() as u32); }, Ok(ForkResult::Child) => { let _dir = File::open(&path).unwrap(); thread::sleep(Duration::from_millis(500)); }, Err(_) => panic!("Fork failed"), } } } }