diff --git a/rust-bindings/rust/conf/ostree.toml b/rust-bindings/rust/conf/ostree.toml index 235cfbe1..4eec250f 100644 --- a/rust-bindings/rust/conf/ostree.toml +++ b/rust-bindings/rust/conf/ostree.toml @@ -24,6 +24,7 @@ generate = [ "OSTree.MutableTree", "OSTree.ObjectType", "OSTree.Remote", + "OSTree.RepoCheckoutFilterResult", "OSTree.RepoCheckoutMode", "OSTree.RepoCheckoutOverwriteMode", "OSTree.RepoCommitModifier", diff --git a/rust-bindings/rust/src/auto/enums.rs b/rust-bindings/rust/src/auto/enums.rs index 14825326..466b8bba 100644 --- a/rust-bindings/rust/src/auto/enums.rs +++ b/rust-bindings/rust/src/auto/enums.rs @@ -242,6 +242,53 @@ impl FromGlib for ObjectType { } } +#[cfg(any(feature = "v2018_2", feature = "dox"))] +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Clone, Copy)] +pub enum RepoCheckoutFilterResult { + Allow, + Skip, + #[doc(hidden)] + __Unknown(i32), +} + +#[cfg(any(feature = "v2018_2", feature = "dox"))] +impl fmt::Display for RepoCheckoutFilterResult { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "RepoCheckoutFilterResult::{}", match *self { + RepoCheckoutFilterResult::Allow => "Allow", + RepoCheckoutFilterResult::Skip => "Skip", + _ => "Unknown", + }) + } +} + +#[cfg(any(feature = "v2018_2", feature = "dox"))] +#[doc(hidden)] +impl ToGlib for RepoCheckoutFilterResult { + type GlibType = ostree_sys::OstreeRepoCheckoutFilterResult; + + fn to_glib(&self) -> ostree_sys::OstreeRepoCheckoutFilterResult { + match *self { + RepoCheckoutFilterResult::Allow => ostree_sys::OSTREE_REPO_CHECKOUT_FILTER_ALLOW, + RepoCheckoutFilterResult::Skip => ostree_sys::OSTREE_REPO_CHECKOUT_FILTER_SKIP, + RepoCheckoutFilterResult::__Unknown(value) => value + } + } +} + +#[cfg(any(feature = "v2018_2", feature = "dox"))] +#[doc(hidden)] +impl FromGlib for RepoCheckoutFilterResult { + fn from_glib(value: ostree_sys::OstreeRepoCheckoutFilterResult) -> Self { + match value { + 0 => RepoCheckoutFilterResult::Allow, + 1 => RepoCheckoutFilterResult::Skip, + value => RepoCheckoutFilterResult::__Unknown(value), + } + } +} + #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] #[derive(Clone, Copy)] pub enum RepoCheckoutMode { diff --git a/rust-bindings/rust/src/auto/mod.rs b/rust-bindings/rust/src/auto/mod.rs index 887d3259..17a94c61 100644 --- a/rust-bindings/rust/src/auto/mod.rs +++ b/rust-bindings/rust/src/auto/mod.rs @@ -71,6 +71,8 @@ pub use self::enums::DeploymentUnlockedState; pub use self::enums::GpgSignatureAttr; pub use self::enums::GpgSignatureFormatFlags; pub use self::enums::ObjectType; +#[cfg(any(feature = "v2018_2", feature = "dox"))] +pub use self::enums::RepoCheckoutFilterResult; pub use self::enums::RepoCheckoutMode; pub use self::enums::RepoCheckoutOverwriteMode; pub use self::enums::RepoMode; diff --git a/rust-bindings/rust/src/lib.rs b/rust-bindings/rust/src/lib.rs index ea3130f6..904ac98f 100644 --- a/rust-bindings/rust/src/lib.rs +++ b/rust-bindings/rust/src/lib.rs @@ -34,11 +34,13 @@ pub use crate::auto::*; mod collection_ref; mod object_name; mod repo; +#[cfg(any(feature = "v2018_2", feature = "dox"))] mod repo_checkout_at_options; #[cfg(any(feature = "v2018_6", feature = "dox"))] pub use crate::collection_ref::*; pub use crate::object_name::*; pub use crate::repo::*; +#[cfg(any(feature = "v2018_2", feature = "dox"))] pub use crate::repo_checkout_at_options::*; // tests diff --git a/rust-bindings/rust/src/repo_checkout_at_options.rs b/rust-bindings/rust/src/repo_checkout_at_options.rs index ed65b17a..b69e2c30 100644 --- a/rust-bindings/rust/src/repo_checkout_at_options.rs +++ b/rust-bindings/rust/src/repo_checkout_at_options.rs @@ -1,11 +1,15 @@ -use glib::translate::{Stash, ToGlib, ToGlibPtr}; +use glib::translate::{FromGlibPtrNone, Stash, ToGlib, ToGlibPtr}; +use glib_sys::gpointer; use libc::c_char; -use ostree_sys::OstreeRepoCheckoutAtOptions; -use std::path::PathBuf; +use ostree_sys::{OstreeRepo, OstreeRepoCheckoutAtOptions, OstreeRepoCheckoutFilterResult}; +use std::path::{Path, PathBuf}; +use {Repo, RepoCheckoutFilterResult}; use {RepoCheckoutMode, RepoCheckoutOverwriteMode}; use {RepoDevInoCache, SePolicy}; -#[derive(PartialEq, Eq, Hash, Debug, Clone)] +pub type RepoCheckoutFilter = + Option RepoCheckoutFilterResult>>; + pub struct RepoCheckoutAtOptions { pub mode: RepoCheckoutMode, pub overwrite_mode: RepoCheckoutOverwriteMode, @@ -18,9 +22,8 @@ pub struct RepoCheckoutAtOptions { pub force_copy_zerosized: bool, pub subpath: Option, pub devino_to_csum_cache: Option, - // TODO: those thingamajigs - // pub filter: OstreeRepoCheckoutFilter, - // pub filter_user_data: gpointer, + // TODO: might be interesting to turn this into a type parameter + pub filter: RepoCheckoutFilter, pub sepolicy: Option, pub sepolicy_prefix: Option, } @@ -39,6 +42,7 @@ impl Default for RepoCheckoutAtOptions { force_copy_zerosized: false, subpath: None, devino_to_csum_cache: None, + filter: None, sepolicy: None, sepolicy_prefix: None, } @@ -47,6 +51,21 @@ impl Default for RepoCheckoutAtOptions { type StringStash<'a, T> = Stash<'a, *const c_char, Option>; +unsafe extern "C" fn filter_trampoline( + repo: *mut OstreeRepo, + path: *const c_char, + stat: *mut libc::stat, + user_data: gpointer, +) -> OstreeRepoCheckoutFilterResult { + // TODO: handle unwinding + let closure = + user_data as *const Box RepoCheckoutFilterResult>; + let repo = FromGlibPtrNone::from_glib_none(repo); + let path: PathBuf = FromGlibPtrNone::from_glib_none(path); + let result = (*closure)(&repo, &path, &*stat); + result.to_glib() +} + impl<'a> ToGlibPtr<'a, *const OstreeRepoCheckoutAtOptions> for RepoCheckoutAtOptions { type Storage = ( Box, @@ -75,6 +94,21 @@ impl<'a> ToGlibPtr<'a, *const OstreeRepoCheckoutAtOptions> for RepoCheckoutAtOpt let sepolicy = self.sepolicy.to_glib_none(); options.sepolicy = sepolicy.0; + if let Some(filter) = &self.filter { + options.filter_user_data = filter + as *const Box RepoCheckoutFilterResult> + as gpointer; + options.filter = Some( + filter_trampoline + as unsafe extern "C" fn( + *mut OstreeRepo, + *const c_char, + *mut libc::stat, + gpointer, + ) -> OstreeRepoCheckoutFilterResult, + ); + } + Stash(options.as_ref(), (options, subpath, sepolicy_prefix)) } } @@ -83,7 +117,7 @@ impl<'a> ToGlibPtr<'a, *const OstreeRepoCheckoutAtOptions> for RepoCheckoutAtOpt mod tests { use super::*; use gio::{File, NONE_CANCELLABLE}; - use glib_sys::{GFALSE, GTRUE}; + use glib_sys::{gpointer, GFALSE, GTRUE}; use ostree_sys::{ OSTREE_REPO_CHECKOUT_MODE_NONE, OSTREE_REPO_CHECKOUT_MODE_USER, OSTREE_REPO_CHECKOUT_OVERWRITE_NONE, OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_IDENTICAL, @@ -132,6 +166,9 @@ mod tests { force_copy_zerosized: true, subpath: Some("sub/path".into()), devino_to_csum_cache: Some(RepoDevInoCache::new()), + filter: Some(Box::new(|_repo, _path, _stat| { + RepoCheckoutFilterResult::Skip + })), sepolicy: Some(SePolicy::new(&File::new_for_path("a/b"), NONE_CANCELLABLE).unwrap()), sepolicy_prefix: Some("prefix".into()), }; @@ -161,8 +198,25 @@ mod tests { ); assert_eq!((*ptr).unused_ints, [0; 6]); assert_eq!((*ptr).unused_ptrs, [ptr::null_mut(); 3]); - assert_eq!((*ptr).filter, None); - assert_eq!((*ptr).filter_user_data, ptr::null_mut()); + assert_eq!( + (*ptr).filter, + Some( + filter_trampoline + as unsafe extern "C" fn( + *mut OstreeRepo, + *const c_char, + *mut libc::stat, + gpointer, + ) + -> OstreeRepoCheckoutFilterResult + ) + ); + assert_eq!( + (*ptr).filter_user_data, + options.filter.as_ref().unwrap() + as *const Box RepoCheckoutFilterResult> + as gpointer + ); assert_eq!((*ptr).sepolicy, options.sepolicy.to_glib_none().0); assert_eq!( CStr::from_ptr((*ptr).sepolicy_prefix), diff --git a/rust-bindings/rust/tests/repo.rs b/rust-bindings/rust/tests/repo.rs index 1ae4361b..12038ded 100644 --- a/rust-bindings/rust/tests/repo.rs +++ b/rust-bindings/rust/tests/repo.rs @@ -13,8 +13,8 @@ use gio::prelude::*; use gio::NONE_CANCELLABLE; use glib::prelude::*; use ostree::{ - ObjectName, ObjectType, RepoCheckoutAtOptions, RepoCheckoutMode, RepoCheckoutOverwriteMode, - RepoDevInoCache, + ObjectName, ObjectType, RepoCheckoutAtOptions, RepoCheckoutFilterResult, RepoCheckoutMode, + RepoCheckoutOverwriteMode, RepoDevInoCache, }; use std::os::unix::io::AsRawFd; @@ -161,6 +161,9 @@ fn should_checkout_at_with_options() { force_copy: true, force_copy_zerosized: true, devino_to_csum_cache: Some(RepoDevInoCache::new()), + filter: Some(Box::new(|_repo, _path, _stat| { + RepoCheckoutFilterResult::Allow + })), ..Default::default() }), dirfd.as_raw_fd(), @@ -172,3 +175,36 @@ fn should_checkout_at_with_options() { assert_test_file(checkout_dir.path()); } + +#[test] +#[cfg(feature = "v2016_8")] +fn should_checkout_at_with_filter() { + let test_repo = TestRepo::new(); + let checksum = test_repo.test_commit("test"); + let checkout_dir = tempfile::tempdir().expect("checkout dir"); + + let dirfd = openat::Dir::open(checkout_dir.path()).expect("openat"); + test_repo + .repo + .checkout_at( + Some(&RepoCheckoutAtOptions { + filter: Some(Box::new(|_repo, path, _stat| { + if let Some("testfile") = path.file_name().map(|s| s.to_str().unwrap()) { + RepoCheckoutFilterResult::Skip + } else { + RepoCheckoutFilterResult::Allow + } + })), + ..Default::default() + }), + dirfd.as_raw_fd(), + "test-checkout", + &checksum, + NONE_CANCELLABLE, + ) + .expect("checkout at"); + + let testdir = checkout_dir.path().join("test-checkout").join("testdir"); + assert!(std::fs::read_dir(&testdir).is_ok()); + assert!(std::fs::File::open(&testdir.join("testfile")).is_err()); +}