Fix some issues with getting tar header fields; Add some doc tests in

tar module; Add some tests for version checks;
This commit is contained in:
Nathan Fisher 2023-04-08 19:10:30 -04:00
parent acbdf2d992
commit 32b4f80715
3 changed files with 117 additions and 51 deletions

View File

@ -117,25 +117,45 @@ impl Default for Header {
} }
impl Header { impl Header {
/// Gets the filename of this archive member
///
/// # Example
/// ```
/// use hpk_package::tar::Header;
///
/// let header = Header::new("test/1.txt").unwrap();
/// let filename = header.filename().unwrap();
/// assert_eq!(filename.as_str(), "1.txt");
/// ```
pub fn filename(&self) -> Result<String, fmt::Error> { pub fn filename(&self) -> Result<String, fmt::Error> {
let mut s = String::new(); let mut s = String::new();
for c in self.fname { for c in self.fname {
if c != b'\0' { if c == 0 {
write!(s, "{c}")?;
} else {
break; break;
} else {
write!(s, "{}", char::from(c))?;
} }
} }
Ok(s) Ok(s)
} }
/// Gets the Unix mode of this archive member
///
/// # Example
/// ```
/// use hpk_package::tar::Header;
///
/// let header = Header::new("test/1.txt").unwrap();
/// let mode = header.mode().unwrap();
/// assert_eq!(mode, 420);
/// ```
pub fn mode(&self) -> Result<u32, Error> { pub fn mode(&self) -> Result<u32, Error> {
let mut s = String::new(); let mut s = String::new();
for c in self.mode { for c in self.mode {
if c != b'\0' { if c == 0 {
write!(s, "{c}")?;
} else {
break; break;
} else {
write!(s, "{}", char::from(c))?;
} }
} }
let mode = u32::from_str_radix(&s, 8)?; let mode = u32::from_str_radix(&s, 8)?;
@ -145,10 +165,10 @@ impl Header {
fn uid(&self) -> Result<u32, Error> { fn uid(&self) -> Result<u32, Error> {
let mut s = String::new(); let mut s = String::new();
for c in self.mode { for c in self.mode {
if c != b'\0' { if c == 0 {
write!(s, "{c}")?;
} else {
break; break;
} else {
write!(s, "{}", char::from(c))?;
} }
} }
let uid = u32::from_str_radix(&s, 8)?; let uid = u32::from_str_radix(&s, 8)?;
@ -158,10 +178,10 @@ impl Header {
fn gid(&self) -> Result<u32, Error> { fn gid(&self) -> Result<u32, Error> {
let mut s = String::new(); let mut s = String::new();
for c in self.mode { for c in self.mode {
if c != b'\0' { if c == 0 {
write!(s, "{c}")?;
} else {
break; break;
} else {
write!(s, "{}", char::from(c))?;
} }
} }
let gid = u32::from_str_radix(&s, 8)?; let gid = u32::from_str_radix(&s, 8)?;
@ -171,10 +191,10 @@ impl Header {
fn username(&self) -> Result<String, fmt::Error> { fn username(&self) -> Result<String, fmt::Error> {
let mut s = String::new(); let mut s = String::new();
for c in self.username { for c in self.username {
if c != b'\0' { if c == 0 {
write!(s, "{c}")?;
} else {
break; break;
} else {
write!(s, "{}", char::from(c))?;
} }
} }
Ok(s) Ok(s)
@ -183,10 +203,10 @@ impl Header {
fn groupname(&self) -> Result<String, fmt::Error> { fn groupname(&self) -> Result<String, fmt::Error> {
let mut s = String::new(); let mut s = String::new();
for c in self.groupname { for c in self.groupname {
if c != b'\0' { if c == 0 {
write!(s, "{c}")?;
} else {
break; break;
} else {
write!(s, "{}", char::from(c))?;
} }
} }
Ok(s) Ok(s)
@ -205,25 +225,61 @@ impl Header {
}) })
} }
pub fn prefix(&self) -> Result<String, fmt::Error> { /// Gets the path to the file minus it's final component
///
/// # Example
/// ```
/// use hpk_package::tar::Header;
///
/// let header = Header::new("test/1.txt").unwrap();
/// let prefix = header.prefix().unwrap();
/// assert_eq!(prefix.as_str(), "test");
/// ```
pub fn prefix(&self) -> Option<String> {
let mut s = String::new(); let mut s = String::new();
for c in self.file_prefix { for c in self.file_prefix {
if c != b'\0' { if c != 0 {
write!(s, "{c}")?; write!(s, "{}", char::from(c)).ok()?;
} else { } else {
break; break;
} }
} }
Ok(s) if s.is_empty() {
None
} else {
Some(s)
}
}
/// Gets the full file path to this archive member.
///
/// # Example
///
/// ```
/// use hpk_package::tar::Header;
/// use std::path::PathBuf;
///
/// let header = Header::new("test/1.txt").unwrap();
/// let path = header.file_path().unwrap();
/// assert_eq!(PathBuf::from("test/1.txt"), path);
/// ```
pub fn file_path(&self) -> Result<PathBuf, fmt::Error> {
let mut path = match self.prefix() {
Some(p) => PathBuf::from(&p),
None => PathBuf::new(),
};
let name = self.filename()?;
path.push(&name);
Ok(path)
} }
pub fn new(filename: &str) -> Result<Self, Error> { pub fn new(filename: &str) -> Result<Self, Error> {
let mut header = Header::default(); let mut header = Header::default();
let meta = fs::symlink_metadata(filename)?; let meta = fs::symlink_metadata(filename)?;
let (filename, prefix) = if filename.len() < 100 { let (filename, prefix) = {
(filename.to_string(), None) // Original tar has a maximum file name length of 100 bytes. The ustar
} else { // revision allows storing the path prefix separately, with 100 bytes
// Deal with file names longer than 100 bytes // reserved for the file name and 150 bytes for the rest of the path.
let path = PathBuf::from(&filename); let path = PathBuf::from(&filename);
let name = match path.file_name().and_then(|n| n.to_str()) { let name = match path.file_name().and_then(|n| n.to_str()) {
Some(n) => n.to_string(), Some(n) => n.to_string(),
@ -234,16 +290,8 @@ impl Header {
))) )))
} }
}; };
let dir = match path.parent() { let dir = path.parent().map(|x| format!("{}", x.display()));
Some(d) => d, (name, dir)
None => {
return Err(Error::Io(io::Error::new(
io::ErrorKind::Other,
"Cannot get path prefix",
)))
}
};
(name, Some(format!("{}", dir.display())))
}; };
/* Fill in metadata */ /* Fill in metadata */
@ -293,10 +341,10 @@ impl Header {
owner: Option<Owner>, owner: Option<Owner>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let mut header = Header::default(); let mut header = Header::default();
let (filename, prefix) = if filename.len() < 100 { let (filename, prefix) = {
(filename.to_string(), None) // Original tar has a maximum file name length of 100 bytes. The ustar
} else { // revision allows storing the path prefix separately, with 100 bytes
// Deal with file names longer than 100 bytes // reserved for the file name and 150 bytes for the rest of the path.
let path = PathBuf::from(&filename); let path = PathBuf::from(&filename);
let name = match path.file_name().and_then(|n| n.to_str()) { let name = match path.file_name().and_then(|n| n.to_str()) {
Some(n) => n.to_string(), Some(n) => n.to_string(),
@ -307,16 +355,8 @@ impl Header {
))) )))
} }
}; };
let dir = match path.parent() { let dir = path.parent().map(|x| format!("{}", x.display()));
Some(d) => d, (name, dir)
None => {
return Err(Error::Io(io::Error::new(
io::ErrorKind::Other,
"Cannot get path prefix",
)))
}
};
(name, Some(format!("{}", dir.display())))
}; };
header.fname[..filename.len()].copy_from_slice(filename.as_bytes()); header.fname[..filename.len()].copy_from_slice(filename.as_bytes());
let mode = format!("{:07o}", meta.st_mode()); let mode = format!("{:07o}", meta.st_mode());
@ -431,7 +471,7 @@ fn get_username_for_uid<'a>(uid: u32) -> Result<&'a str, std::str::Utf8Error> {
user.to_str() user.to_str()
} }
pub fn get_groupname_for_gid<'a>(gid: u32) -> Result<&'a str, std::str::Utf8Error> { fn get_groupname_for_gid<'a>(gid: u32) -> Result<&'a str, std::str::Utf8Error> {
let group = unsafe { let group = unsafe {
let gr = libc::getgrgid(gid); let gr = libc::getgrgid(gid);
let name = (*gr).gr_name; let name = (*gr).gr_name;

View File

@ -165,4 +165,30 @@ mod test {
}) })
); );
} }
#[test]
fn rapid_num_eq() {
let rapid = Rapid { major: 42, minor: 0 };
assert_eq!(rapid, 42);
}
#[test]
fn rapid_num_gt() {
let rapid = Rapid { major: 1, minor: 42 };
assert!(rapid > 1);
}
#[test]
fn rapid_semver_eq() {
let rapid = Rapid { major: 42, minor: 69 };
let semver = SemVer { major: 42, minor: 69, patch: 0 };
assert_eq!(rapid, semver);
}
#[test]
fn rapid_semver_lt() {
let rapid = Rapid { major: 42, minor: 69 };
let semver = SemVer { major: 42, minor: 69, patch: 1 };
assert!(rapid < semver);
}
} }

Binary file not shown.