part 1 done
This commit is contained in:
parent
163540889c
commit
bd5895814e
@ -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)]
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user