Skip to content

Commit

Permalink
Add a function for extracting the contents of an RPM to disk
Browse files Browse the repository at this point in the history
  • Loading branch information
dralley committed Jan 21, 2025
1 parent 5f04f74 commit 31903ca
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Added support for ecdsa signatures
- Added `Package::files()` for iterating over the files of an RPM package (metadata & contents).
- Added `Package::extract()` for extracting the archive contents of an RPM package to a directory on disk

## 0.16.0

Expand Down
62 changes: 58 additions & 4 deletions src/rpm/package.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{
fs, io,
io::Read,
fs,
io::{self, Read, Write},
os::unix::fs::PermissionsExt,
path::{Path, PathBuf},
str::FromStr,
};
Expand Down Expand Up @@ -77,6 +78,59 @@ impl Package {
})
}

/// Extract all contents of the package payload to a given directory
pub fn extract(&self, dest: impl AsRef<Path>) -> Result<(), Error> {
fs::create_dir_all(&dest)?;

let dirs = self
.metadata
.header
.get_entry_data_as_string_array(IndexTag::RPMTAG_DIRNAMES)?;

// pull every base directory name in the package and create the directory in advancec
for dir in dirs {
let dir_path = dest
.as_ref()
.join(Path::new(dir).strip_prefix("/").unwrap_or(dest.as_ref()));
fs::create_dir_all(&dir_path)?;
}

// TODO: reduce memory by replacing this with an impl that writes the files immediately after reading them from the archive
// instead of reading each file entirely into memory (while the archive is also entirely in memory) before writing them
for file in self.files()? {
let file = file?;
let file_path = dest.as_ref().join(
file.metadata
.path
.strip_prefix("/")
.unwrap_or(dest.as_ref()),
);

let perms = fs::Permissions::from_mode(file.metadata.mode.permissions().into());
match file.metadata.mode {
FileMode::Dir { .. } => {
fs::create_dir_all(&file_path)?;
fs::set_permissions(&file_path, perms)?;
}
FileMode::Regular { .. } => {
let mut f = fs::File::create(&file_path)?;
f.write_all(&file.content)?;
fs::set_permissions(&file_path, perms)?;
}
FileMode::SymbolicLink { .. } => {
// broken symlinks (common for debuginfo handling) are perceived as not existing by "exists()"
if file_path.exists() || file_path.symlink_metadata().is_ok() {
fs::remove_file(&file_path)?;
}
std::os::unix::fs::symlink(&file.metadata.linkto, &file_path)?;
}
_ => unreachable!("Encountered an unknown or invalid FileMode"),
}
}

Ok(())
}

/// Create package signatures using an external key and add them to the signature header
#[cfg(feature = "signature-meta")]
pub fn sign<S>(&mut self, signer: S) -> Result<(), Error>
Expand Down Expand Up @@ -1044,7 +1098,7 @@ pub struct FileIterator<'a> {

#[derive(Debug)]
pub struct RpmFile {
pub file_entry: FileEntry,
pub metadata: FileEntry,
pub content: Vec<u8>,
}

Expand Down Expand Up @@ -1077,7 +1131,7 @@ impl Iterator for FileIterator<'_> {
}

Some(Ok(RpmFile {
file_entry,
metadata: file_entry,
content,
}))
}
Expand Down

0 comments on commit 31903ca

Please sign in to comment.