Reading Linux groups via the Rust Foreign Function Interface

The world continues to embraces Rust for its safety properties. While writing utilities in Rust, we are going to have to work with existing code to perform common tasks. I recently needed to list the set of Linux groups registered on a system, and get access to the users assigned to each. Here’s my notes of what I learned.

Comparable code in C

The three C APIs I want to call are:

  • getgrenet
  • setgrent
  • endgrent

A simple C program to enumerate the groups looks like this:

#include <stdio.h>
 
#define _GNU_SOURCE         /* See feature_test_macros(7) */
#include <grp.h>
 
 
int main(){
  struct group * current_group;
  printf("Hello World\n");
  setgrent();
  while( current_group = getgrent()){
    if (current_group){
      printf("ID: %6d ", current_group->gr_gid);
      printf("Name: %20s \n", current_group->gr_name);
    }
  } 
  endgrent();
  return 0;
}

Steps in Rust

In order to make these same calls from Rust, I have to do a few things:

  1. Import the functions from native code.
  2. Create a comparable structure to the struct group defined in C.
  3. Wrap the calls to the C code in an unsafe block
  4. Convert from the raw memory types to Rust types that I can use in standard rust macros like println!

Import the functions from Native Code

The functions I want to call are in libc. For the Cargo system to acces s them, I need to following dependency:

[dependencies]
libc = "0.2.0"

Inside the rust code itself, I have to reference the foreign library. I also need a couple standard functions for string conversion:

extern crate libc;
use libc::c_char;
use std::ffi::CStr;
use std::str;

Create a comparable structure to the struct group defined in C.

For the Group structure, I need a comparable rust structure. Since this iteration I am not going through the group members, I can limit myself to the first couple elements of the structure:

#[repr(C)]
pub struct GroupEnt {
    gr_name:   *const c_char,        /* group name */
    gr_passwd:  *const c_char,      /* group password */
    gr_gid:    u32         /* group ID */
}
To import the foreign functions, I need a block that defines them:
extern {
    fn setgrent();
    fn getgrent() -> *const GroupEnt;
    fn endgrent();
}

Wrap the calls to the C code in an unsafe block and convert from the raw memory types to Rust types

Finally, to call the code, I need to wrap them in unsafe blocks.

fn enumerate_groups(){
    let groupent: * const GroupEnt;
 
    unsafe{
        setgrent();
        groupent = getgrent();
    }
    let c_str: &amp;CStr = unsafe { CStr::from_ptr((*groupent).gr_name) };
    println!("{}", c_str.to_str().unwrap());
 
    unsafe{
        endgrent();
    }
}

This will print the first element of the list. Next steps:

  1. Iterate through the whole list
  2. Iterate through the list of users.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.