Part 2. Looking up users (20 points)

In this part, you’re going to implement a function username() which takes a user ID (UID) and returns a string containing the username.

The libc function you called in Part 1 was very simple: It took no arguments and it could never fail. The libc function you’ll call in this part is much more complicated.

POSIX-compatible operating systems provide functions to get information about a user. Take a look at the man page for getpwnam_r(3) (this is in section 3 of the manual which is the “Library Functions Manual”). The four functions described all behave similarly: They look up a user in the user database. getpwnam(), and getpwnam_r() look up a user given a user name whereas getpwuid() and getpwuid_r() look up a user given a UID.

The information returned by these functions is a passwd structure. In C, this structure looks like this.

struct passwd {
   char   *pw_name;       /* username */
   char   *pw_passwd;     /* user password */
   uid_t   pw_uid;        /* user ID */
   gid_t   pw_gid;        /* group ID */
   char   *pw_gecos;      /* user information */
   char   *pw_dir;        /* home directory */
   char   *pw_shell;      /* shell program */
};

Here’s the same definition in Rust, from the libc crate.

#![allow(unused)]
fn main() {
type c_char = i8;
type uid_t = u32;
type gid_t = u32;
#[repr(C)]
pub struct passwd {
    pub pw_name: *mut c_char,
    pub pw_passwd: *mut c_char,
    pub pw_uid: uid_t,
    pub pw_gid: gid_t,
    pub pw_gecos: *mut c_char,
    pub pw_dir: *mut c_char,
    pub pw_shell: *mut c_char,
}
}

The interesting types here are the string type here is *mut c_char. This is a raw mutable pointer to a C character (a character in C is just a byte). In fact, it’s a raw mutable pointer to a sequence of nonzero characters followed by a zero character (meaning a byte with value 0, '\0' rather than the digit '0' which has value 48).

Important

A raw pointer in Rust is essentially the same thing as a reference except without the safety guarantees: In particular, a pointer need not point at a valid, initialized object of its type whereas a reference always points to a valid, initialized object.

Just as there are two types of references, &T, and &mut T, there are two types of pointers, *const T and *mut T. A const pointer is used when the thing it is pointing to will not change (similar to &) whereas a mut pointer is used when the thing it is pointing to may be changed (similar to &mut).

It’s always safe to create a *const T from a &T or a *mut T from a &mut T.

Here’s an example.

#![allow(unused)]
fn main() {
let x: i32 = 10;
let x_ptr = &x as *const _;

let mut y: i32 = 20;
let y_ptr = &mut y as *mut _;

println!("Address of x: {x_ptr:?}");
println!("Address of y: {y_ptr:?}");
}

If you click the run button, you’ll see that addresses are just numbers. If you think of your computer’s memory as one giant array of bytes ranging from index 0 up to some n, then a pointer to some value in memory is just the index of the first byte of the value in that big array of bytes. (This isn’t completely true for several reasons. Nevertheless, if you think of a pointer as just the index of the first byte of your value, your mental model of how pointers work will be 95% correct.)

The operation that isn’t safe is dereferencing the pointer. Here’s an example:

#![allow(unused)]
fn main() {
let mut x: i32 = 10;
let x_ref = &mut x;

let y = *x_ref; // Dereferencing the reference, always safe. Creates y with value 10.
*x_ref = 20; // Dereferencing the reference, always safe. Assigns 20 to x.

let x_ptr = x_ref as *mut _;

// Dereferencing the pointer is safe in this case because it points to
// a valid, initialized `i32`. But dereferencing in general is unsafe because
// the pointer need not be valid.
// Creates z with value 20.
let z = unsafe { *x_ptr };
// Again, dereferencing is safe in this case, but not in general.
// Assigns 40 to x.
unsafe {
    *x_ptr = 40;
}
println!("x = {x}\ny = {y}\nz = {z}");
}

Click the Run button.

The getpwuid_r() function is quite complicated. The Rust binding to the C function is the following.

pub unsafe extern "C" fn getpwuid_r(
    uid: uid_t,
    pwd: *mut passwd,
    buf: *mut c_char,
    buflen: size_t,
    result: *mut *mut passwd
) -> c_int

The parameters match the parameters for the C function and are as follows.

  • uid — The user ID of the user to look up;
  • pwd — A (mutable) pointer to a passwd struct that will be filled out by getpwuid_r();
  • buf - A buffer to hold the contents of string data (see below);
  • buflen — The length of the buf buffer; and
  • result — On success, *result will set to pwd and on failure to std::ptr::null_mut(), a null pointer.

The passwd structure contains pointers to string data. The getpwuid_r() function needs to put that string data somewhere. It uses the provided buf as a place to store the strings.

Your task

Your task is to create a new user module and implement a fn username(uid: u32) -> String function that calls getpwuid_r() to retrieve the user name of the user corresponding to uid.

Start by creating a new file src/user.rs and add the user module to lib.rs with

pub mod user;

Then add

use std::ffi::CStr;
use std::ptr;
use libc;

pub fn username(uid: u32) -> String {
    unimplemented!()
}

to user.rs.

It is time to implement username(). Implementing it is a little tricky. You’re going to need to

  1. Create a passwd local variable with all of the fields set to either 0 or null
    let mut pwd = libc::passwd {
        pw_name: ptr::null_mut(),
        // fill out the rest of the fields
    };
  2. Create a Vec<libc::c_char> for the buf argument
    let mut buf: Vec<libc::c_char> = vec![0; 1024];
  3. Create a pointer to a libc::passwd named result that will be set by getpwuid_r() to the address of pwd on success.
    let mut result: *mut libc::passwd = ptr::null_mut();
  4. Call getpwuid_r(), passing the appropriate arguments.
    let ret = unsafe {
        libc::getpwuid_r(
            uid,
            &mut pwd as *mut _,
            buf.as_mut_ptr(),
            buf.len(),
            &mut result as *mut _,
        )
    };
  5. If ret is libc::ERANGE, then the provided buffer wasn’t large enough to hold the string data. Resize buf (something like buf.resize(buf.len() * 2, 0)) and call getpwuid_r() again. Steps 4 and 5 should be in a loop and and you should break out of the loop if ret is any other value.
  6. If ret is any nonzero value (other than libc::ERANGE which you handled in step 5) or if result.is_null(), then either the getpwuid_r() call failed or there was no user with that UID. In either case, convert the UID to a String (uid.to_string()) and return it.
  7. At this point, everything has succeeded and we just need to convert from a C string into a Rust String and return it. For this, we can use a &CStr reference which behaves like a &str for a C string.
     // UNSAFE: This is safe because ret is 0 and result is not null and
     // therefore pwd was filled out. Additionally, pwd.pw_name is a valid,
     // NUL-terminated C-string.
     let username: &CStr = unsafe {
         CStr::from_ptr(pwd.pw_name)
     };
    
     // Convert CStr into a String and return it.
     username.to_string_lossy().into_owned()

Finally, modify the main() function in whoami.rs to call user::username(uid) with the UID that results from libc::geteuid(). You’ll want to add use process::user; to whoami.rs to be able to call functions in the user module.

If all has worked correctly, running cargo run --bin whoami should print out your username.

$ cargo run --quiet --bin whoami
steve