From bd5895814e859da8ac10a673d016fbba038f3a9b Mon Sep 17 00:00:00 2001 From: ridethepig Date: Sat, 25 Feb 2023 07:20:45 +0000 Subject: [PATCH] part 1 done --- week3/inspect-fds/src/main.rs | 19 ++++++++++--- week3/inspect-fds/src/open_file.rs | 21 ++++++-------- week3/inspect-fds/src/process.rs | 44 ++++++++++++++++++++++++++---- week3/inspect-fds/src/ps_utils.rs | 5 ---- 4 files changed, 61 insertions(+), 28 deletions(-) diff --git a/week3/inspect-fds/src/main.rs b/week3/inspect-fds/src/main.rs index 5d523a0..9f388c8 100644 --- a/week3/inspect-fds/src/main.rs +++ b/week3/inspect-fds/src/main.rs @@ -1,5 +1,7 @@ use std::env; +use ps_utils::get_target; + mod open_file; mod process; mod ps_utils; @@ -10,11 +12,20 @@ fn main() { println!("Usage: {} ", args[0]); std::process::exit(1); } - #[allow(unused)] // TODO: delete this line for Milestone 1 let target = &args[1]; - - // TODO: Milestone 1: Get the target Process using psutils::get_target() - unimplemented!(); + let target_proc = get_target(&target).expect("Error calling ps util"); + let proc = match target_proc{ + Some(_proc) => _proc, + None => { + println!("Target {target} did not match any running PIDs or executables"); + std::process::exit(1) + }, + }; + proc.print(); + let children = ps_utils::get_child_processes(proc.pid).expect("Error inspecting children of this process"); + for child in children { + child.print(); + } } #[cfg(test)] diff --git a/week3/inspect-fds/src/open_file.rs b/week3/inspect-fds/src/open_file.rs index a8d095d..20e1165 100644 --- a/week3/inspect-fds/src/open_file.rs +++ b/week3/inspect-fds/src/open_file.rs @@ -1,14 +1,10 @@ use regex::Regex; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; -#[allow(unused_imports)] // TODO: delete this line for Milestone 4 use std::{fmt, fs}; -#[allow(unused)] // TODO: delete this line for Milestone 4 const O_WRONLY: usize = 00000001; -#[allow(unused)] // TODO: delete this line for Milestone 4 const O_RDWR: usize = 00000002; -#[allow(unused)] // TODO: delete this line for Milestone 4 const COLORS: [&str; 6] = [ "\x1B[38;5;9m", "\x1B[38;5;10m", @@ -17,12 +13,10 @@ const COLORS: [&str; 6] = [ "\x1B[38;5;13m", "\x1B[38;5;14m", ]; -#[allow(unused)] // TODO: delete this line for Milestone 4 const CLEAR_COLOR: &str = "\x1B[0m"; /// This enum can be used to represent whether a file is read-only, write-only, or read/write. An /// enum is basically a value that can be one of some number of "things." -#[allow(unused)] // TODO: delete this line for Milestone 4 #[derive(Debug, Clone, PartialEq)] pub enum AccessMode { Read, @@ -53,7 +47,6 @@ pub struct OpenFile { } impl OpenFile { - #[allow(unused)] // TODO: delete this line for Milestone 4 pub fn new(name: String, cursor: usize, access_mode: AccessMode) -> OpenFile { OpenFile { name, @@ -68,7 +61,6 @@ impl OpenFile { /// * For regular files, this will simply return the supplied path. /// * For terminals (files starting with /dev/pts), this will return "". /// * For pipes (filenames formatted like pipe:[pipenum]), this will return "". - #[allow(unused)] // TODO: delete this line for Milestone 4 fn path_to_name(path: &str) -> String { if path.starts_with("/dev/pts/") { String::from("") @@ -84,7 +76,6 @@ impl OpenFile { /// extracts the cursor position of that file descriptor (technically, the position of the /// open file table entry that the fd points to) using a regex. It returns None if the cursor /// couldn't be found in the fdinfo text. - #[allow(unused)] // TODO: delete this line for Milestone 4 fn parse_cursor(fdinfo: &str) -> Option { // Regex::new will return an Error if there is a syntactical error in our regular // expression. We call unwrap() here because that indicates there's an obvious problem with @@ -103,7 +94,6 @@ impl OpenFile { /// This file takes the contents of /proc/{pid}/fdinfo/{fdnum} for some file descriptor and /// extracts the access mode for that open file using the "flags:" field contained in the /// fdinfo text. It returns None if the "flags" field couldn't be found. - #[allow(unused)] // TODO: delete this line for Milestone 4 fn parse_access_mode(fdinfo: &str) -> Option { // Regex::new will return an Error if there is a syntactical error in our regular // expression. We call unwrap() here because that indicates there's an obvious problem with @@ -134,10 +124,15 @@ impl OpenFile { /// program and we don't need to do fine-grained error handling, so returning Option is a /// simple way to indicate that "hey, we weren't able to get the necessary information" /// without making a big deal of it.) - #[allow(unused)] // TODO: delete this line for Milestone 4 pub fn from_fd(pid: usize, fd: usize) -> Option { - // TODO: implement for Milestone 4 - unimplemented!(); + let filepath = format!("/proc/{pid}/fd/{fd}"); + let link = fs::read_link(filepath).ok()?; + let name = OpenFile::path_to_name(link.to_str()?); + let fdinfo_path = format!("/proc/{pid}/fdinfo/{fd}"); + let fdinfo = fs::read_to_string(fdinfo_path).ok()?; + let cursor = OpenFile::parse_cursor(&fdinfo)?; + let access_mode = OpenFile::parse_access_mode(&fdinfo)?; + Some(OpenFile::new(name, cursor, access_mode)) } /// This function returns the OpenFile's name with ANSI escape codes included to colorize diff --git a/week3/inspect-fds/src/process.rs b/week3/inspect-fds/src/process.rs index 60a70a9..1250d9f 100644 --- a/week3/inspect-fds/src/process.rs +++ b/week3/inspect-fds/src/process.rs @@ -1,5 +1,4 @@ use crate::open_file::OpenFile; -#[allow(unused)] // TODO: delete this line for Milestone 3 use std::fs; #[derive(Debug, Clone, PartialEq)] @@ -10,7 +9,6 @@ pub struct Process { } impl Process { - #[allow(unused)] // TODO: delete this line for Milestone 1 pub fn new(pid: usize, ppid: usize, command: String) -> Process { Process { pid, ppid, command } } @@ -20,16 +18,24 @@ impl Process { /// information will commonly be unavailable if the process has exited. (Zombie processes /// still have a pid, but their resources have already been freed, including the file /// descriptor table.) - #[allow(unused)] // TODO: delete this line for Milestone 3 pub fn list_fds(&self) -> Option> { - // TODO: implement for Milestone 3 - unimplemented!(); + let path = format!("/proc/{}/fd/", self.pid); + let mut fds = Vec::new(); + let dir = fs::read_dir(path).ok()?; + for entry in dir { + let entry = entry.ok()?; + let _path = entry.path(); + // you cannot just use is_file, 'cause we want symlinks or somethins alike to be added + let filename = _path.file_name()?.to_str()?; + let fd_int = filename.parse::().ok()?; + fds.push(fd_int); + } + Some(fds) } /// This function returns a list of (fdnumber, OpenFile) tuples, if file descriptor /// information is available (it returns None otherwise). The information is commonly /// unavailable if the process has already exited. - #[allow(unused)] // TODO: delete this line for Milestone 4 pub fn list_open_files(&self) -> Option> { let mut open_files = vec![]; for fd in self.list_fds()? { @@ -37,6 +43,32 @@ impl Process { } Some(open_files) } + + pub fn print(&self) { + println!( + "========== \"{}\" (pid {}, ppid {}) ==========", + self.command, self.pid, self.ppid + ); + match self.list_open_files() { + None => println!( + "Warning: could not inspect file descriptors for this process! \ + It might have exited just as we were about to look at its fd table, \ + or it might have exited a while ago and is waiting for the parent \ + to reap it." + ), + Some(open_files) => { + for (fd, file) in open_files { + println!( + "{:<4} {:<15} cursor: {:<4} {}", + fd, + format!("({})", file.access_mode), + file.cursor, + file.colorized_name(), + ); + } + } + } + } } #[cfg(test)] diff --git a/week3/inspect-fds/src/ps_utils.rs b/week3/inspect-fds/src/ps_utils.rs index 55bafc9..b216aae 100644 --- a/week3/inspect-fds/src/ps_utils.rs +++ b/week3/inspect-fds/src/ps_utils.rs @@ -50,7 +50,6 @@ impl From for Error { /// /// Example line: /// " 578 577 emacs inode.c" -#[allow(unused)] // TODO: delete this line for Milestone 1 fn parse_ps_line(line: &str) -> Result { // ps doesn't output a very nice machine-readable output, so we do some wonky things here to // deal with variable amounts of whitespace. @@ -71,7 +70,6 @@ fn parse_ps_line(line: &str) -> Result { /// This function takes a pid and returns a Process struct for the specified process, or None if /// the specified pid doesn't exist. An Error is only returned if ps cannot be executed or /// produces unexpected output format. -#[allow(unused)] // TODO: delete this line for Milestone 1 fn get_process(pid: usize) -> Result, Error> { // Run ps to find the specified pid. We use the ? operator to return an Error if executing ps // fails, or if it returns non-utf-8 output. (The extra Error traits above are used to @@ -96,7 +94,6 @@ fn get_process(pid: usize) -> Result, Error> { /// This function takes a pid and returns a list of Process structs for processes that have the /// specified pid as their parent process. An Error is returned if ps cannot be executed or /// produces unexpected output format. -#[allow(unused)] // TODO: delete this line for Milestone 5 pub fn get_child_processes(pid: usize) -> Result, Error> { let ps_output = Command::new("ps") .args(&["--ppid", &pid.to_string(), "-o", "pid= ppid= command="]) @@ -111,7 +108,6 @@ pub fn get_child_processes(pid: usize) -> Result, Error> { /// This function takes a command name (e.g. "sort" or "./multi_pipe_test") and returns the first /// matching process's pid, or None if no matching process is found. It returns an Error if there /// is an error running pgrep or parsing pgrep's output. -#[allow(unused)] // TODO: delete this line for Milestone 1 fn get_pid_by_command_name(name: &str) -> Result, Error> { let output = String::from_utf8( Command::new("pgrep") @@ -129,7 +125,6 @@ fn get_pid_by_command_name(name: &str) -> Result, Error> { /// command name (e.g. "./subprocess_test") or a PID (e.g. "5612"). This function returns a /// Process struct if the specified process was found, None if no matching processes were found, or /// Error if an error was encountered in running ps or pgrep. -#[allow(unused)] // TODO: delete this line for Milestone 1 pub fn get_target(query: &str) -> Result, Error> { let pid_by_command = get_pid_by_command_name(query)?; if pid_by_command.is_some() {