ostree/cargo: bump to ostree-sys 0.9.2

This commit is contained in:
Luca BRUNO authored and Colin Walters committed 2022-05-06 12:53:57 -04:00
1 parent 61b4629b37
commit 3fc55a524b
91 files changed
+2 -1

No files matched your search

@@ -0,0 +1,280 @@
use crate::{RepoCheckoutMode, RepoCheckoutOverwriteMode, RepoDevInoCache, SePolicy};
use glib::translate::*;
use libc::c_char;
use std::path::PathBuf;
#[cfg(any(feature = "v2018_2", feature = "dox"))]
mod repo_checkout_filter;
#[cfg(any(feature = "v2018_2", feature = "dox"))]
pub use self::repo_checkout_filter::RepoCheckoutFilter;
/// Options for checking out an OSTree commit.
pub struct RepoCheckoutAtOptions {
/// Checkout mode.
pub mode: RepoCheckoutMode,
/// Overwrite mode.
pub overwrite_mode: RepoCheckoutOverwriteMode,
/// Deprecated, do not use.
pub enable_uncompressed_cache: bool,
/// Perform `fsync()` on checked out files and directories.
pub enable_fsync: bool,
/// Handle OCI/Docker style whiteout files.
pub process_whiteouts: bool,
/// Require hardlinking.
pub no_copy_fallback: bool,
/// Never hardlink; reflink if possible, otherwise full physical copy.
#[cfg(any(feature = "v2017_6", feature = "dox"))]
pub force_copy: bool,
/// Suppress mode bits outside of 0775 for directories.
#[cfg(any(feature = "v2017_7", feature = "dox"))]
pub bareuseronly_dirs: bool,
/// Copy zero-sized files rather than hardlinking.
#[cfg(any(feature = "v2018_9", feature = "dox"))]
pub force_copy_zerosized: bool,
/// Only check out this subpath.
pub subpath: Option<PathBuf>,
/// A cache from device, inode pairs to checksums.
pub devino_to_csum_cache: Option<RepoDevInoCache>,
/// A callback function to decide which files and directories will be checked out from the
/// repo. See the documentation on [RepoCheckoutFilter](struct.RepoCheckoutFilter.html) for more
/// information on the signature.
///
/// # Panics
/// This callback may not panic. If it does, `abort()` will be called to avoid unwinding across
/// an FFI boundary and into the libostree C code (which is Undefined Behavior). If you prefer to
/// swallow the panic rather than aborting, you can use `std::panic::catch_unwind` inside your
/// callback to catch and silence any panics that occur.
#[cfg(any(feature = "v2018_2", feature = "dox"))]
pub filter: Option<RepoCheckoutFilter>,
/// SELinux policy.
#[cfg(any(feature = "v2017_6", feature = "dox"))]
pub sepolicy: Option<SePolicy>,
/// When computing security contexts, prefix the path with this value.
pub sepolicy_prefix: Option<String>,
}
impl Default for RepoCheckoutAtOptions {
fn default() -> Self {
RepoCheckoutAtOptions {
mode: RepoCheckoutMode::None,
overwrite_mode: RepoCheckoutOverwriteMode::None,
enable_uncompressed_cache: false,
enable_fsync: false,
process_whiteouts: false,
no_copy_fallback: false,
#[cfg(feature = "v2017_6")]
force_copy: false,
#[cfg(feature = "v2017_7")]
bareuseronly_dirs: false,
#[cfg(feature = "v2018_9")]
force_copy_zerosized: false,
subpath: None,
devino_to_csum_cache: None,
#[cfg(feature = "v2018_2")]
filter: None,
#[cfg(feature = "v2017_6")]
sepolicy: None,
sepolicy_prefix: None,
}
}
}
type StringStash<'a, T> = Stash<'a, *const c_char, Option<T>>;
type WrapperStash<'a, GlibT, WrappedT> = Stash<'a, *mut GlibT, Option<WrappedT>>;
impl<'a> ToGlibPtr<'a, *const ffi::OstreeRepoCheckoutAtOptions> for RepoCheckoutAtOptions {
#[allow(clippy::type_complexity)]
type Storage = (
Box<ffi::OstreeRepoCheckoutAtOptions>,
StringStash<'a, PathBuf>,
StringStash<'a, String>,
WrapperStash<'a, ffi::OstreeRepoDevInoCache, RepoDevInoCache>,
WrapperStash<'a, ffi::OstreeSePolicy, SePolicy>,
);
// We need to make sure that all memory pointed to by the returned pointer is kept alive by
// either the `self` reference or the returned Stash.
fn to_glib_none(&'a self) -> Stash<*const ffi::OstreeRepoCheckoutAtOptions, Self> {
// Creating this struct from zeroed memory is fine since it's `repr(C)` and only contains
// primitive types. In fact, the libostree docs say to zero the struct. This means we handle
// the unused bytes correctly.
// The struct needs to be boxed so the pointer we return remains valid even as the Stash is
// moved around.
let mut options =
Box::new(unsafe { std::mem::zeroed::<ffi::OstreeRepoCheckoutAtOptions>() });
options.mode = self.mode.into_glib();
options.overwrite_mode = self.overwrite_mode.into_glib();
options.enable_uncompressed_cache = self.enable_uncompressed_cache.into_glib();
options.enable_fsync = self.enable_fsync.into_glib();
options.process_whiteouts = self.process_whiteouts.into_glib();
options.no_copy_fallback = self.no_copy_fallback.into_glib();
#[cfg(feature = "v2017_6")]
{
options.force_copy = self.force_copy.into_glib();
}
#[cfg(feature = "v2017_7")]
{
options.bareuseronly_dirs = self.bareuseronly_dirs.into_glib();
}
#[cfg(feature = "v2018_9")]
{
options.force_copy_zerosized = self.force_copy_zerosized.into_glib();
}
// We keep these complex values alive by returning them in our Stash. Technically, some of
// these are being kept alive by `self` already, but it's better to be consistent here.
let subpath = self.subpath.to_glib_none();
options.subpath = subpath.0;
let sepolicy_prefix = self.sepolicy_prefix.to_glib_none();
options.sepolicy_prefix = sepolicy_prefix.0;
let devino_to_csum_cache = self.devino_to_csum_cache.to_glib_none();
options.devino_to_csum_cache = devino_to_csum_cache.0;
#[cfg(feature = "v2017_6")]
let sepolicy = {
let sepolicy = self.sepolicy.to_glib_none();
options.sepolicy = sepolicy.0;
sepolicy
};
#[cfg(not(feature = "v2017_6"))]
let sepolicy = None.to_glib_none();
#[cfg(feature = "v2018_2")]
{
if let Some(filter) = &self.filter {
options.filter_user_data = filter.to_glib_none().0;
options.filter = Some(repo_checkout_filter::filter_trampoline_unwindsafe);
}
}
Stash(
options.as_ref(),
(
options,
subpath,
sepolicy_prefix,
devino_to_csum_cache,
sepolicy,
),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use glib::ffi::{GFALSE, GTRUE};
use std::ffi::{CStr, CString};
use std::ptr;
#[test]
fn should_convert_default_options() {
let options = RepoCheckoutAtOptions::default();
let stash = options.to_glib_none();
let ptr = stash.0;
unsafe {
assert_eq!((*ptr).mode, ffi::OSTREE_REPO_CHECKOUT_MODE_NONE);
assert_eq!(
(*ptr).overwrite_mode,
ffi::OSTREE_REPO_CHECKOUT_OVERWRITE_NONE
);
assert_eq!((*ptr).enable_uncompressed_cache, GFALSE);
assert_eq!((*ptr).enable_fsync, GFALSE);
assert_eq!((*ptr).process_whiteouts, GFALSE);
assert_eq!((*ptr).no_copy_fallback, GFALSE);
#[cfg(feature = "v2017_6")]
assert_eq!((*ptr).force_copy, GFALSE);
#[cfg(feature = "v2017_7")]
assert_eq!((*ptr).bareuseronly_dirs, GFALSE);
#[cfg(feature = "v2018_9")]
assert_eq!((*ptr).force_copy_zerosized, GFALSE);
assert_eq!((*ptr).unused_bools, [GFALSE; 4]);
assert_eq!((*ptr).subpath, ptr::null());
assert_eq!((*ptr).devino_to_csum_cache, ptr::null_mut());
assert_eq!((*ptr).unused_ints, [0; 6]);
assert_eq!((*ptr).unused_ptrs, [ptr::null_mut(); 3]);
#[cfg(feature = "v2018_2")]
assert_eq!((*ptr).filter, None);
#[cfg(feature = "v2018_2")]
assert_eq!((*ptr).filter_user_data, ptr::null_mut());
#[cfg(feature = "v2017_6")]
assert_eq!((*ptr).sepolicy, ptr::null_mut());
assert_eq!((*ptr).sepolicy_prefix, ptr::null());
}
}
#[test]
fn should_convert_non_default_options() {
let options = RepoCheckoutAtOptions {
mode: RepoCheckoutMode::User,
overwrite_mode: RepoCheckoutOverwriteMode::UnionIdentical,
enable_uncompressed_cache: true,
enable_fsync: true,
process_whiteouts: true,
no_copy_fallback: true,
#[cfg(feature = "v2017_6")]
force_copy: true,
#[cfg(feature = "v2017_7")]
bareuseronly_dirs: true,
#[cfg(feature = "v2018_9")]
force_copy_zerosized: true,
subpath: Some("sub/path".into()),
devino_to_csum_cache: Some(RepoDevInoCache::new()),
#[cfg(feature = "v2018_2")]
filter: RepoCheckoutFilter::new(|_repo, _path, _stat| {
crate::RepoCheckoutFilterResult::Skip
}),
#[cfg(feature = "v2017_6")]
sepolicy: Some(
SePolicy::new(&gio::File::for_path("a/b"), gio::NONE_CANCELLABLE).unwrap(),
),
sepolicy_prefix: Some("prefix".into()),
};
let stash = options.to_glib_none();
let ptr = stash.0;
unsafe {
assert_eq!((*ptr).mode, ffi::OSTREE_REPO_CHECKOUT_MODE_USER);
assert_eq!(
(*ptr).overwrite_mode,
ffi::OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_IDENTICAL
);
assert_eq!((*ptr).enable_uncompressed_cache, GTRUE);
assert_eq!((*ptr).enable_fsync, GTRUE);
assert_eq!((*ptr).process_whiteouts, GTRUE);
assert_eq!((*ptr).no_copy_fallback, GTRUE);
#[cfg(feature = "v2017_6")]
assert_eq!((*ptr).force_copy, GTRUE);
#[cfg(feature = "v2017_7")]
assert_eq!((*ptr).bareuseronly_dirs, GTRUE);
#[cfg(feature = "v2018_9")]
assert_eq!((*ptr).force_copy_zerosized, GTRUE);
assert_eq!((*ptr).unused_bools, [GFALSE; 4]);
assert_eq!(
CStr::from_ptr((*ptr).subpath),
CString::new("sub/path").unwrap().as_c_str()
);
assert_eq!(
(*ptr).devino_to_csum_cache,
options.devino_to_csum_cache.to_glib_none().0
);
assert_eq!((*ptr).unused_ints, [0; 6]);
assert_eq!((*ptr).unused_ptrs, [ptr::null_mut(); 3]);
#[cfg(feature = "v2018_2")]
assert!((*ptr).filter == Some(repo_checkout_filter::filter_trampoline_unwindsafe));
#[cfg(feature = "v2018_2")]
assert_eq!(
(*ptr).filter_user_data,
options.filter.as_ref().unwrap().to_glib_none().0,
);
#[cfg(feature = "v2017_6")]
assert_eq!((*ptr).sepolicy, options.sepolicy.to_glib_none().0);
assert_eq!(
CStr::from_ptr((*ptr).sepolicy_prefix),
CString::new("prefix").unwrap().as_c_str()
);
}
}
}
@@ -0,0 +1,217 @@
use crate::{Repo, RepoCheckoutFilterResult};
use glib::ffi::gpointer;
use glib::translate::*;
use libc::c_char;
use std::any::Any;
use std::panic::catch_unwind;
use std::path::{Path, PathBuf};
use std::process::abort;
/// A filter callback to decide which files to checkout from a [Repo](struct.Repo.html). The
/// function is called for every directory and file in the dirtree.
///
/// # Arguments
/// * `repo` - the `Repo` that is being checked out
/// * `path` - the path of the current file, as an absolute path rooted at the commit's root. The
/// root directory is '/', a subdir would be '/subdir' etc.
/// * `stat` - the metadata of the current file
///
/// # Return Value
/// The return value determines whether the current file is checked out or skipped.
pub struct RepoCheckoutFilter(Box<dyn Fn(&Repo, &Path, &libc::stat) -> RepoCheckoutFilterResult>);
impl RepoCheckoutFilter {
/// Wrap a closure for use as a filter function.
///
/// # Return Value
/// The return value is always `Some` containing the value. It simply comes pre-wrapped for your
/// convenience.
pub fn new<F>(closure: F) -> Option<RepoCheckoutFilter>
where
F: (Fn(&Repo, &Path, &libc::stat) -> RepoCheckoutFilterResult) + 'static,
{
Some(RepoCheckoutFilter(Box::new(closure)))
}
/// Call the contained closure.
fn call(&self, repo: &Repo, path: &Path, stat: &libc::stat) -> RepoCheckoutFilterResult {
self.0(repo, path, stat)
}
}
impl<'a> ToGlibPtr<'a, gpointer> for RepoCheckoutFilter {
type Storage = ();
fn to_glib_none(&'a self) -> Stash<gpointer, Self> {
Stash(self as *const RepoCheckoutFilter as gpointer, ())
}
}
impl FromGlibPtrNone<gpointer> for &RepoCheckoutFilter {
// `ptr` must be valid for the lifetime of the returned reference.
unsafe fn from_glib_none(ptr: gpointer) -> Self {
assert!(!ptr.is_null());
&*(ptr as *const RepoCheckoutFilter)
}
}
/// Trampoline to be called by libostree that calls the Rust closure in the `user_data` parameter.
///
/// # Safety
/// All parameters must be valid pointers for the runtime of the function. In particular,
/// `user_data` must point to a [RepoCheckoutFilter](struct.RepoCheckoutFilter.html) value.
///
/// # Panics
/// If any parameter is a null pointer, the function panics.
unsafe fn filter_trampoline(
repo: *mut ffi::OstreeRepo,
path: *const c_char,
stat: *mut libc::stat,
user_data: gpointer,
) -> ffi::OstreeRepoCheckoutFilterResult {
// We can't guarantee it's a valid pointer, but we can make sure it's not null.
assert!(!stat.is_null());
let stat = &*stat;
// This reference is valid until the end of this function, which is shorter than the lifetime
// of `user_data` so we're fine.
let closure: &RepoCheckoutFilter = from_glib_none(user_data);
// `repo` lives at least until the end of this function. This means we can just borrow the
// reference so long as our `repo` is not moved out of this function.
let repo = from_glib_borrow(repo);
// This is a copy so no problems here.
let path: PathBuf = from_glib_none(path);
let result = closure.call(&repo, &path, stat);
result.into_glib()
}
/// Unwind-safe trampoline to call the Rust filter callback. See [filter_trampoline](fn.filter_trampoline.html).
/// This function additionally catches panics and aborts to avoid unwinding into C code.
pub(super) unsafe extern "C" fn filter_trampoline_unwindsafe(
repo: *mut ffi::OstreeRepo,
path: *const c_char,
stat: *mut libc::stat,
user_data: gpointer,
) -> ffi::OstreeRepoCheckoutFilterResult {
// Unwinding across an FFI boundary is Undefined Behavior and we have no other way to communicate
// the error. We abort() safely to avoid further problems.
let result = catch_unwind(move || filter_trampoline(repo, path, stat, user_data));
result.unwrap_or_else(|panic| {
print_panic(panic);
abort()
})
}
/// Print a panic message and the value to stderr, if we can.
///
/// If the panic value is either `&str` or `String`, we print it. Otherwise, we don't.
fn print_panic(panic: Box<dyn Any>) {
eprintln!("A Rust callback invoked by C code panicked.");
eprintln!("Unwinding across FFI boundaries is Undefined Behavior so abort() will be called.");
let msg = {
if let Some(s) = panic.as_ref().downcast_ref::<&str>() {
s
} else if let Some(s) = panic.as_ref().downcast_ref::<String>() {
s
} else {
"UNABLE TO SHOW VALUE OF PANIC"
}
};
eprintln!("Panic value: {}", msg);
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CString;
use std::ptr;
#[test]
#[should_panic]
fn trampoline_should_panic_if_repo_is_nullptr() {
let path = CString::new("/a/b/c").unwrap();
let mut stat: libc::stat = unsafe { std::mem::zeroed() };
let filter = RepoCheckoutFilter(Box::new(|_, _, _| RepoCheckoutFilterResult::Allow));
unsafe {
filter_trampoline(
ptr::null_mut(),
path.as_ptr(),
&mut stat,
filter.to_glib_none().0,
);
}
}
#[test]
#[should_panic]
fn trampoline_should_panic_if_path_is_nullptr() {
let repo = Repo::new_default();
let mut stat: libc::stat = unsafe { std::mem::zeroed() };
let filter = RepoCheckoutFilter(Box::new(|_, _, _| RepoCheckoutFilterResult::Allow));
unsafe {
filter_trampoline(
repo.to_glib_none().0,
ptr::null(),
&mut stat,
filter.to_glib_none().0,
);
}
}
#[test]
#[should_panic]
fn trampoline_should_panic_if_stat_is_nullptr() {
let repo = Repo::new_default();
let path = CString::new("/a/b/c").unwrap();
let filter = RepoCheckoutFilter(Box::new(|_, _, _| RepoCheckoutFilterResult::Allow));
unsafe {
filter_trampoline(
repo.to_glib_none().0,
path.as_ptr(),
ptr::null_mut(),
filter.to_glib_none().0,
);
}
}
#[test]
#[should_panic]
fn trampoline_should_panic_if_user_data_is_nullptr() {
let repo = Repo::new_default();
let path = CString::new("/a/b/c").unwrap();
let mut stat: libc::stat = unsafe { std::mem::zeroed() };
unsafe {
filter_trampoline(
repo.to_glib_none().0,
path.as_ptr(),
&mut stat,
ptr::null_mut(),
);
}
}
#[test]
fn trampoline_should_call_the_closure() {
let repo = Repo::new_default();
let path = CString::new("/a/b/c").unwrap();
let mut stat: libc::stat = unsafe { std::mem::zeroed() };
let filter = {
let repo = repo.clone();
let path = path.clone();
RepoCheckoutFilter(Box::new(move |arg_repo, arg_path, _| {
assert_eq!(arg_repo, &repo);
assert_eq!(&CString::new(arg_path.to_str().unwrap()).unwrap(), &path);
RepoCheckoutFilterResult::Skip
}))
};
let result = unsafe {
filter_trampoline(
repo.to_glib_none().0,
path.as_ptr(),
&mut stat,
filter.to_glib_none().0,
)
};
assert_eq!(result, ffi::OSTREE_REPO_CHECKOUT_FILTER_SKIP);
}
}