Part 5. More processing (40 points)
In this part, you’ll add more fields to your Process
structure and
implementing a few methods. The next part will explain the meaning of the
fields in more detail.
Your task
Add the following fields to your Process
structure
- process state
char
- parent process ID
i32
, - session ID
i32
, - controlling terminal
i32
, - total execution time
i64
(noti32
), - command line
Vec<String>
, and - a user ID
u32
You can pick whatever names you like. I chose to name my structure fields after the field names given in the proc(5) man page, when applicable.
The process state, parent process ID, session ID, and controlling terminal are
parsed directly from fields named state
, ppid
, session
, and tty_nr
.
The total execution time comes from adding the results of parsing the fields
utime
and stime
as i64
. So something like this.
let utime: i64 = fields[14].parse()?;
let stime: i64 = fields[15].parse()?;
let execution_time = utime + stime;
The command line and user ID are slightly more complicated.
The command line (when available) is stored in /proc/<pid>/cmdline
. Search
the proc(5) man page for /proc/pid/cmdline
(only the first paragraph
of its description is important for us here).
Recall that a C string ends with a single 0 byte to indicate the end of the
string. The same convention is happening here. So /proc/<pid>/cmdline
consists of strings terminated by a 0.
Fortunately, Rust is awesome and comes with a standard library function for
splitting “string data that is terminated, rather than separated by a
pattern.” That function is str::split_terminator().
So read the file to a string and then use split_terminator()
with a pattern
of '\0'
, a zero character.
The user ID (often abbreviated UID) of the user who is running the process is
not stored in any of the virtual files. Instead, Linux exposes that
information as the user who owns the files in /proc/<pid>/
. You have
already opened up /proc/<pid>/stat
so we can simply ask for the UID.
#![allow(unused)] fn main() { use std::fs::File; use std::os::unix::fs::MetadataExt; pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>; fn run() -> Result<()> { let path = "/proc/1/stat"; let mut file = File::open(path) .map_err(|err| format!("{path}: {err}"))?; let metadata = file.metadata() .map_err(|err| format!("Couldn't get metadata for {path}: {err}"))?; let uid = metadata.uid(); println!("The UID that owns {path} is {uid}"); Ok(()) } let _ = run(); }
Four more methods to implement. One is involved, three are easy.
#![allow(unused)] fn main() { pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>; struct Process; impl Process { /// Look up information about a running process with the given PID. pub fn for_pid(pid: i32) -> Result<Self> { todo!("You did this already") } /// Look up information for the current process. pub fn for_self() -> Result<Self> { todo!() } /// Returns a list of all running processes. pub fn all_processes() -> Result<Vec<Self>> { todo!() } /// Returns `true` if the process is a session leader. pub fn is_session_leader(&self) -> bool { todo!() } /// Returns `true` if the process has a controlling terminal. pub fn has_tty(&self) -> bool { todo!() } } }
The eagle-eyed among you may have noticed a symbolic link /proc/self
.
This always points to /proc/<pid>
where <pid>
is the process’s own PID.
However, there’s an easier way to get a process’s identifier for the current process than reading the target of the symbolic link.
#![allow(unused)] fn main() { let pid = std::process::id(); println!("The current process ID is {pid}"); }
The Process::for_self()
function is easy if you use std::process::id()
and
Self::for_pid()
. The one minor catch is std::process::id()
returns a
u32
and we want an i32
. We can cast a u32
into an i32
.
#![allow(unused)] fn main() { let x: u32 = 1; let y: i32 = x as i32; println!("{x} {y}"); }
The last two methods you have to implement, is_session_leader()
and has_tty()
are convenience
methods you’ll use later. The meanings will be explained in the next part. For
right now, a process is a session leader if and only if its PID matches its
session id. A process has a controlling terminal if and only if its
controlling terminal field is not 0.
The final function, Process::all_processes()
is more complicated. You need
to examine the contents of the /proc
directory and find all of the
directories whose names are numbers.
Here’s some example code for reading the contents of the `/proc directory and printing the names of all directories contained within.
#![allow(unused)] fn main() { use std::fs; pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>; fn go() -> Result<()> { for entry in fs::read_dir("/proc")? { // Unwrap the result and return any errors that result from reading /proc. let entry = entry?; // If the entry is not a directory or reading the metadata failed, continue. match entry.metadata() { Ok(metadata) if metadata.is_dir() => (), _ => continue, } let file_name = entry.file_name(); if let Some(name) = file_name.to_str() { println!("{name}") } } Ok(()) } let _ = go(); }
There are lots of ways to determine if a string is a number, but the easiest
is probably to try to parse it as an i32
using the slightly unpleasant “turbofish” syntax:
#![allow(unused)] fn main() { println!("{}", "1234".parse::<i32>().is_ok()); println!("{}", "12e4".parse::<i32>().is_ok()); }
Once you find a directory in /proc
whose name is a number, you can use
Process::for_pid()
to get information about that process.
After reading the /proc
directory and learning that there’s a process with
PID 12345, for example, you might call Process::for_pid(12345)
and this
could fail! If the process has exited between you learning of its existence
and you reading its /proc
entry, the Linux kernel will have deleted its
/proc
entry and the File::open()
will fail. If this happens,
Process::all_processes()
should continue finding the other processes rather
than returning an error.
So in particular, you do not want to have code like this.
let mut result: Vec<Process> = Vec::new();
for entry in fs::read_dir("/proc")? {
// ...
let proc = Process::from_pid(pid)?; // <-- Don't do this here!
result.push(proc);
}
The ?
will cause an early return on an error and we don’t want that.
Test that your code is working by changing ps.rs
’s main
to iterate over
all processes and print out the debug representation.