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.
Table of contents
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:
- Import the functions from native code.
- Create a comparable structure to the struct group defined in C.
- Wrap the calls to the C code in an unsafe block
- 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 */ } |
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: &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: