Part 5. Controlling terminals (40 points)
The final thing to add to ps
is printing a human-readable description of the
controlling terminal (the TTY column).
In this part, you’re going to create a new DeviceMap
struct which you will
use to convert the “controlling terminal” integer to a string like pts/3
.
The controlling terminal integer has two parts, a minor number that’s in the
range [0, 255] and a major number. The integer is computed as major * 256 + minor
. The major number identifies the type of “character device.”
Linux exposes the mapping between major number and device name in the file
/proc/devices
. Go ahead and take a look at that file to see how it is
structured.
Your task
Create a new file src/device_map.rs
(and add a module declaration in
lib.rs
) and define a new DeviceMap
struct. This struct should contain a
std::collections::HashMap<i32, String>
field. (You’ll probably want to use std::collections::HashMap
.) Don’t forget to make DeviceMap
public.
You’re going to implement a public constructor named new()
and a public
lookup()
method.
#![allow(unused)] fn main() { type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>; struct DeviceMap; impl DeviceMap { pub fn new() -> Result<Self> { todo!() } pub fn lookup(&self, tty_nr: i32) -> String { let major = tty_nr >> 8; let minor = tty_nr & 0xFF; todo!() } } }
(You will need to use super::Result;
to have access to your Result
type).
The new()
function should open /proc/devices
and read lines corresponding
to the Character devices
section of the file and insert the names of the
devices into the hash map using the integer value as the key. See the
HashMap
documentation for the methods you can use.
To simplify your implementation, you may want to use this code
let devices = std::fs::read_to_string("/proc/devices")
.map_err(|err| format!("/proc/devices: {err}"))?;
for line in devices
.lines()
.skip_while(|&line| line != "Character devices:")
.skip(1)
.take_while(|&line| !line.is_empty())
{
todo!("Split the line on whitespace and insert into the map")
}
This reads /proc/devices
into a String
named devices
. Then it iterates
over the lines by (1) skipping lines until Character devices:
is found; (2)
skipping that line; and (3) only returning the lines that are nonempty.
There are duplicate numbers in /proc/devices
. That’s okay, just insert them
into the hash map one at a time. The earlier values will be replaced by the
later values.
What remains is to finish the implementation of lookup()
. The provided code
above extracts the major and minor number. Look up the major number in the
hash map. If it’s not found, return the string "?"
. If it is found, then
return format!("{device}/{minor}")
(where device
is the returned value
from the hash map).
One common source of errors is parsing incorrect fields. If your program prints out "?"
for every controlling terminal, you should make sure you’re parsing the correct controlling terminal field from /proc/pid/stat
.
The final step is to update ps
to create a DeviceMap
in run()
and when
printing the TTY
column, call the lookup()
method to get the name of the
TTY
.
Here’s some sample output as compared to the real ps
.
$ cargo run --bin ps --quiet
PID TTY TIME CMD
1521440 pts/3 00:00:00 bash
1523501 pts/3 00:00:00 ps
$ ps
PID TTY TIME CMD
1521440 pts/3 00:00:00 bash
1523768 pts/3 00:00:00 ps