part 1 done

This commit is contained in:
ridethepig 2023-02-25 07:20:45 +00:00
parent 163540889c
commit bd5895814e
4 changed files with 61 additions and 28 deletions

View File

@ -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: {} <name or pid of target>", 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)]

View File

@ -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 "<terminal>".
/// * For pipes (filenames formatted like pipe:[pipenum]), this will return "<pipe #pipenum>".
#[allow(unused)] // TODO: delete this line for Milestone 4
fn path_to_name(path: &str) -> String {
if path.starts_with("/dev/pts/") {
String::from("<terminal>")
@ -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<usize> {
// 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<AccessMode> {
// 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<OpenFile> {
// 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

View File

@ -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<Vec<usize>> {
// 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::<usize>().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<Vec<(usize, OpenFile)>> {
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)]

View File

@ -50,7 +50,6 @@ impl From<std::num::ParseIntError> 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<Process, Error> {
// 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<Process, Error> {
/// 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<Option<Process>, 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<Option<Process>, 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<Vec<Process>, 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<Vec<Process>, 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<Option<usize>, Error> {
let output = String::from_utf8(
Command::new("pgrep")
@ -129,7 +125,6 @@ fn get_pid_by_command_name(name: &str) -> Result<Option<usize>, 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<Option<Process>, Error> {
let pid_by_command = get_pid_by_command_name(query)?;
if pid_by_command.is_some() {