use std::ffi::CStr;
use std::fmt;
use std::marker::PhantomData;
use std::ptr::NonNull;
use crate::Tag;
use crate::internal::header::Header;
unsafe extern "C" {
fn free(ptr: *mut std::ffi::c_void);
}
pub struct Files {
ptr: Option<NonNull<librpm_sys::rpmfiles_s>>,
}
unsafe impl Send for Files {}
unsafe impl Sync for Files {}
impl Files {
pub(crate) fn from_header(header: &Header) -> Self {
let ptr = unsafe {
librpm_sys::rpmfilesNew(
std::ptr::null_mut(),
header.as_ptr(),
Tag::BASENAMES as librpm_sys::rpmTagVal,
0,
)
};
Files {
ptr: NonNull::new(ptr),
}
}
pub fn len(&self) -> usize {
match self.ptr {
Some(ptr) => unsafe { librpm_sys::rpmfilesFC(ptr.as_ptr()) as usize },
None => 0,
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn digest_algo(&self) -> i32 {
match self.ptr {
Some(ptr) => unsafe { librpm_sys::rpmfilesDigestAlgo(ptr.as_ptr()) },
None => 0,
}
}
pub fn iter(&self) -> FileIter<'_> {
FileIter {
ptr: self.ptr,
index: 0,
count: self.len() as i32,
_marker: PhantomData,
}
}
}
impl Drop for Files {
fn drop(&mut self) {
if let Some(ptr) = self.ptr {
unsafe {
librpm_sys::rpmfilesFree(ptr.as_ptr());
}
}
}
}
impl fmt::Debug for Files {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Files").field("len", &self.len()).finish()
}
}
impl<'a> IntoIterator for &'a Files {
type Item = FileEntry<'a>;
type IntoIter = FileIter<'a>;
fn into_iter(self) -> FileIter<'a> {
self.iter()
}
}
pub struct FileEntry<'a> {
ptr: NonNull<librpm_sys::rpmfiles_s>,
index: i32,
_marker: PhantomData<&'a Files>,
}
impl FileEntry<'_> {
pub fn path(&self) -> String {
let p = unsafe { librpm_sys::rpmfilesFN(self.ptr.as_ptr(), self.index) };
assert!(!p.is_null());
let s = unsafe { CStr::from_ptr(p) }
.to_str()
.expect("file path is not UTF-8")
.to_owned();
unsafe { free(p.cast()) };
s
}
pub fn basename(&self) -> &str {
let p = unsafe { librpm_sys::rpmfilesBN(self.ptr.as_ptr(), self.index) };
assert!(!p.is_null());
unsafe { CStr::from_ptr(p) }
.to_str()
.expect("file basename is not UTF-8")
}
pub fn dirname(&self) -> &str {
let di = unsafe { librpm_sys::rpmfilesDI(self.ptr.as_ptr(), self.index) };
let p = unsafe { librpm_sys::rpmfilesDN(self.ptr.as_ptr(), di as i32) };
assert!(!p.is_null());
unsafe { CStr::from_ptr(p) }
.to_str()
.expect("file dirname is not UTF-8")
}
pub fn size(&self) -> u64 {
unsafe { librpm_sys::rpmfilesFSize(self.ptr.as_ptr(), self.index) }
}
pub fn mode(&self) -> u16 {
unsafe { librpm_sys::rpmfilesFMode(self.ptr.as_ptr(), self.index) }
}
pub fn user(&self) -> &str {
let p = unsafe { librpm_sys::rpmfilesFUser(self.ptr.as_ptr(), self.index) };
assert!(!p.is_null());
unsafe { CStr::from_ptr(p) }
.to_str()
.expect("file user is not UTF-8")
}
pub fn group(&self) -> &str {
let p = unsafe { librpm_sys::rpmfilesFGroup(self.ptr.as_ptr(), self.index) };
assert!(!p.is_null());
unsafe { CStr::from_ptr(p) }
.to_str()
.expect("file group is not UTF-8")
}
pub fn flags(&self) -> FileAttrs {
let raw = unsafe { librpm_sys::rpmfilesFFlags(self.ptr.as_ptr(), self.index) };
FileAttrs(raw)
}
pub fn link_target(&self) -> Option<&str> {
let p = unsafe { librpm_sys::rpmfilesFLink(self.ptr.as_ptr(), self.index) };
if p.is_null() {
return None;
}
let s = unsafe { CStr::from_ptr(p) }
.to_str()
.expect("link target is not UTF-8");
if s.is_empty() { None } else { Some(s) }
}
pub fn caps(&self) -> Option<&str> {
let p = unsafe { librpm_sys::rpmfilesFCaps(self.ptr.as_ptr(), self.index) };
if p.is_null() {
return None;
}
let s = unsafe { CStr::from_ptr(p) }
.to_str()
.expect("file caps is not UTF-8");
if s.is_empty() { None } else { Some(s) }
}
pub fn digest(&self) -> Option<&[u8]> {
let mut algo: std::ffi::c_int = 0;
let mut len: usize = 0;
let p = unsafe {
librpm_sys::rpmfilesFDigest(self.ptr.as_ptr(), self.index, &mut algo, &mut len)
};
if p.is_null() || len == 0 {
None
} else {
Some(unsafe { std::slice::from_raw_parts(p, len) })
}
}
}
pub struct FileIter<'a> {
ptr: Option<NonNull<librpm_sys::rpmfiles_s>>,
index: i32,
count: i32,
_marker: PhantomData<&'a Files>,
}
impl<'a> Iterator for FileIter<'a> {
type Item = FileEntry<'a>;
fn next(&mut self) -> Option<FileEntry<'a>> {
let ptr = self.ptr?;
if self.index >= self.count {
return None;
}
let entry = FileEntry {
ptr,
index: self.index,
_marker: PhantomData,
};
self.index += 1;
Some(entry)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = (self.count - self.index).max(0) as usize;
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for FileIter<'_> {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FileAttrs(u32);
impl FileAttrs {
pub fn bits(self) -> u32 {
self.0
}
pub fn is_config(self) -> bool {
self.0 & librpm_sys::rpmfileAttrs_e_RPMFILE_CONFIG != 0
}
pub fn is_doc(self) -> bool {
self.0 & librpm_sys::rpmfileAttrs_e_RPMFILE_DOC != 0
}
pub fn is_missingok(self) -> bool {
self.0 & librpm_sys::rpmfileAttrs_e_RPMFILE_MISSINGOK != 0
}
pub fn is_noreplace(self) -> bool {
self.0 & librpm_sys::rpmfileAttrs_e_RPMFILE_NOREPLACE != 0
}
pub fn is_ghost(self) -> bool {
self.0 & librpm_sys::rpmfileAttrs_e_RPMFILE_GHOST != 0
}
pub fn is_license(self) -> bool {
self.0 & librpm_sys::rpmfileAttrs_e_RPMFILE_LICENSE != 0
}
pub fn is_artifact(self) -> bool {
self.0 & librpm_sys::rpmfileAttrs_e_RPMFILE_ARTIFACT != 0
}
}