Skip to content

Commit

Permalink
Change GroupFileReader to accept &File
Browse files Browse the repository at this point in the history
This allows a single file to be used for reading and writing.
  • Loading branch information
JonathonReinhart committed Dec 10, 2023
1 parent 16bd0e4 commit 5d57caa
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 18 deletions.
20 changes: 9 additions & 11 deletions scubainit-rs/src/groups.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
use std::path::Path;

#[derive(Debug)]
#[derive(Debug, Eq, PartialEq)]
pub struct GroupEntry {
pub name: String,
pub passwd: String,
pub gid: u32,
pub members: Vec<String>,
}

pub struct GroupFileReader {
reader: BufReader<File>,
pub struct GroupFileReader<'a> {
reader: BufReader<&'a File>,
}

impl GroupFileReader {
pub fn open<P: AsRef<Path>>(path: P) -> std::io::Result<GroupFileReader> {
let f = File::open(path)?;
Ok(GroupFileReader {
reader: BufReader::new(f),
})
impl GroupFileReader<'_> {
pub fn new(file: &File) -> GroupFileReader {
GroupFileReader {
reader: BufReader::new(file),
}
}
}

Expand Down Expand Up @@ -58,7 +56,7 @@ impl GroupEntry {
}
}

impl Iterator for GroupFileReader {
impl Iterator for GroupFileReader<'_> {
type Item = GroupEntry;

fn next(&mut self) -> Option<Self::Item> {
Expand Down
15 changes: 11 additions & 4 deletions scubainit-rs/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ fn main() -> Result<()> {
Ok(())
}


/// Opens a file for reading and appeneding.
///
/// The file is created if it does not exist.
fn open_read_append<P: AsRef<Path>>(path: P) -> std::io::Result<fs::File> {
fs::File::options().append(true).read(true).create(true).open(path)
}

struct UserInfo {
uid: u32,
gid: u32,
Expand All @@ -82,10 +90,10 @@ impl UserInfo {
let group_name = &self.group;
let gid = self.gid;

let file = open_read_append(ETC_GROUP)?;

// Try to find a conflicting group (one matching name or gid).
// TODO: Skip if file is missing
// TODO: Can we open the file once (like fopen(path, "a+"))
let mut reader = groups::GroupFileReader::open(ETC_GROUP)?;
let mut reader = groups::GroupFileReader::new(&file);
for grp in reader {
let name_matches = grp.name.as_str() == group_name;
let gid_matches = grp.gid == gid;
Expand All @@ -110,7 +118,6 @@ impl UserInfo {
gid: gid,
members: Vec::new(),
};
let file = fs::File::options().append(true).create(true).open(ETC_GROUP)?;
let mut writer = groups::GroupFileWriter::new(&file);
Ok(writer.write(&grp)?)
}
Expand Down
93 changes: 90 additions & 3 deletions scubainit-rs/tests/groups.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use anyhow::Result;
use std::io::{Read, Seek};
use std::fs;
use std::io::{BufRead, BufReader};
use std::io::{Read, Seek, Write};
use std::path::Path;
use tempfile;

use scubainit_rs::groups::{GroupEntry, GroupFileReader, GroupFileWriter};
Expand All @@ -16,14 +19,16 @@ macro_rules! vec_of_strings {

#[test]
fn test_group_empty() -> Result<()> {
let mut reader = GroupFileReader::open("testdata/group_empty")?;
let file = fs::File::open("testdata/group_empty")?;
let mut reader = GroupFileReader::new(&file);
assert!(reader.next().is_none());
Ok(())
}

#[test]
fn test_group1() -> Result<()> {
let mut reader = GroupFileReader::open("testdata/group1")?;
let file = fs::File::open("testdata/group1")?;
let mut reader = GroupFileReader::new(&file);

let g = reader.next().unwrap();
assert_eq!(g.name, "foo");
Expand Down Expand Up @@ -67,5 +72,87 @@ fn test_write() -> Result<()> {
assert_eq!(buffer, "foo:x:1234:moe,larry,shemp\n");


Ok(())
}

#[test]
fn test_write_read() -> Result<()> {
let mut file = tempfile::tempfile()?;

let mut writer = GroupFileWriter::new(&file);
let grp_w = GroupEntry {
name: "foo".to_string(),
passwd: "x".to_string(),
gid: 1234,
members: vec_of_strings!["moe", "larry", "shemp"],
};
writer.write(&grp_w)?;

file.rewind()?;

let mut reader = GroupFileReader::new(&file);
let grp_r = reader.next().unwrap();

assert_eq!(grp_w, grp_r);
Ok(())
}

// TODO: DRY
fn open_read_append<P: AsRef<Path>>(path: P) -> std::io::Result<fs::File> {
fs::File::options().append(true).read(true).create(true).open(path)
}

#[test]
fn test_read_write() -> Result<()> {
const LINE1: &str = "foo:x:1234:moe,larry,shemp";
const LINE2: &str = "bar:x:2345:moe";

// First populate a file with some content
let mut content = tempfile::NamedTempFile::new()?;
writeln!(content, "{LINE1}").unwrap();
writeln!(content, "{LINE2}").unwrap();

// Process the tempfile
{
let file = open_read_append(content.path())?;

// Now read
let mut reader = GroupFileReader::new(&file);

let r = reader.next().unwrap();
assert_eq!(r.name, "foo");

let r = reader.next().unwrap();
assert_eq!(r.name, "bar");

// Now write
let mut writer = GroupFileWriter::new(&file);
let new_grp = GroupEntry {
name: "snap".to_string(),
passwd: "x".to_string(),
gid: 3456,
members: vec_of_strings!["crackle", "pop"],
};
writer.write(&new_grp)?;
}

content.rewind()?;

// Read back the modified file and verify
let mut content_reader = BufReader::new(content);
let mut line = String::with_capacity(128);

line.clear();
content_reader.read_line(&mut line)?;
assert_eq!(line.trim_end(), LINE1);

line.clear();
content_reader.read_line(&mut line)?;
assert_eq!(line.trim_end(), LINE2);

line.clear();
content_reader.read_line(&mut line)?;
assert_eq!(line.trim_end(), "snap:x:3456:crackle,pop");

Ok(())
}

0 comments on commit 5d57caa

Please sign in to comment.