diff --git a/scubainit-rs/src/groups.rs b/scubainit-rs/src/groups.rs index 3aefe38c..3a997ac6 100644 --- a/scubainit-rs/src/groups.rs +++ b/scubainit-rs/src/groups.rs @@ -1,8 +1,7 @@ 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, @@ -10,16 +9,15 @@ pub struct GroupEntry { pub members: Vec, } -pub struct GroupFileReader { - reader: BufReader, +pub struct GroupFileReader<'a> { + reader: BufReader<&'a File>, } -impl GroupFileReader { - pub fn open>(path: P) -> std::io::Result { - let f = File::open(path)?; - Ok(GroupFileReader { - reader: BufReader::new(f), - }) +impl GroupFileReader<'_> { + pub fn new(file: &File) -> GroupFileReader { + GroupFileReader { + reader: BufReader::new(file), + } } } @@ -58,7 +56,7 @@ impl GroupEntry { } } -impl Iterator for GroupFileReader { +impl Iterator for GroupFileReader<'_> { type Item = GroupEntry; fn next(&mut self) -> Option { diff --git a/scubainit-rs/src/main.rs b/scubainit-rs/src/main.rs index b91ca373..c01851a8 100644 --- a/scubainit-rs/src/main.rs +++ b/scubainit-rs/src/main.rs @@ -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>(path: P) -> std::io::Result { + fs::File::options().append(true).read(true).create(true).open(path) +} + struct UserInfo { uid: u32, gid: u32, @@ -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; @@ -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)?) } diff --git a/scubainit-rs/tests/groups.rs b/scubainit-rs/tests/groups.rs index 2e5c1832..6b5c8032 100644 --- a/scubainit-rs/tests/groups.rs +++ b/scubainit-rs/tests/groups.rs @@ -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}; @@ -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"); @@ -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>(path: P) -> std::io::Result { + 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(()) }