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 (not i32),
  • 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.

Example

#![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!()
    }
}
}

Tip

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.

Example

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.

Bug

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.