#[cfg(any(feature = "v2016_4", feature = "dox"))] use crate::RepoListRefsExtFlags; #[cfg(feature = "cap-std-apis")] use crate::RepoMode; use crate::{Checksum, ObjectDetails, ObjectName, ObjectType, Repo, RepoTransactionStats}; use ffi::OstreeRepoListObjectsFlags; use glib::ffi as glib_sys; use glib::{self, translate::*, Error, IsA}; use std::{ collections::{HashMap, HashSet}, future::Future, mem::MaybeUninit, path::Path, pin::Pin, ptr, }; unsafe extern "C" fn read_variant_table( _key: glib_sys::gpointer, value: glib_sys::gpointer, hash_set: glib_sys::gpointer, ) { let value: glib::Variant = from_glib_none(value as *const glib_sys::GVariant); let set: &mut HashSet = &mut *(hash_set as *mut HashSet); set.insert(ObjectName::new_from_variant(value)); } unsafe extern "C" fn read_variant_object_map( key: glib_sys::gpointer, value: glib_sys::gpointer, hash_set: glib_sys::gpointer, ) { let key: glib::Variant = from_glib_none(key as *const glib_sys::GVariant); let value: glib::Variant = from_glib_none(value as *const glib_sys::GVariant); let set: &mut HashMap = &mut *(hash_set as *mut HashMap); if let Some(details) = ObjectDetails::new_from_variant(value) { set.insert(ObjectName::new_from_variant(key), details); } } unsafe fn from_glib_container_variant_set(ptr: *mut glib_sys::GHashTable) -> HashSet { let mut set = HashSet::new(); glib_sys::g_hash_table_foreach( ptr, Some(read_variant_table), &mut set as *mut HashSet as *mut _, ); glib_sys::g_hash_table_unref(ptr); set } unsafe fn from_glib_container_variant_map( ptr: *mut glib_sys::GHashTable, ) -> HashMap { let mut set = HashMap::new(); glib_sys::g_hash_table_foreach( ptr, Some(read_variant_object_map), &mut set as *mut HashMap as *mut _, ); glib_sys::g_hash_table_unref(ptr); set } /// An open transaction in the repository. /// /// This will automatically invoke [`ostree::Repo::abort_transaction`] when the value is dropped. pub struct TransactionGuard<'a> { /// Reference to the repository for this transaction. repo: Option<&'a Repo>, } impl<'a> TransactionGuard<'a> { /// Commit this transaction. pub fn commit>( mut self, cancellable: Option<&P>, ) -> Result { // Safety: This is the only function which mutates this option let repo = self.repo.take().unwrap(); repo.commit_transaction(cancellable) } } impl<'a> Drop for TransactionGuard<'a> { fn drop(&mut self) { if let Some(repo) = self.repo { // TODO: better logging in ostree? // See also https://github.com/ostreedev/ostree/issues/2413 let _ = repo.abort_transaction(gio::NONE_CANCELLABLE); } } } impl Repo { /// Create a new `Repo` object for working with an OSTree repo at the given path. pub fn new_for_path>(path: P) -> Repo { Repo::new(&gio::File::for_path(path.as_ref())) } #[cfg(feature = "cap-std-apis")] /// A version of [`open_at`] which uses cap-std. pub fn open_at_dir(dir: &cap_std::fs::Dir, path: &str) -> Result { use std::os::unix::io::AsRawFd; crate::Repo::open_at(dir.as_raw_fd(), path, gio::NONE_CANCELLABLE) } #[cfg(feature = "cap-std-apis")] /// A version of [`create_at`] which uses cap-std, and also returns the opened repo. pub fn create_at_dir( dir: &cap_std::fs::Dir, path: &str, mode: RepoMode, options: Option<&glib::Variant>, ) -> Result { use std::os::unix::io::AsRawFd; crate::Repo::create_at(dir.as_raw_fd(), path, mode, options, gio::NONE_CANCELLABLE)?; Repo::open_at_dir(dir, path) } /// A wrapper for [`prepare_transaction`] which ensures the transaction will be aborted when the guard goes out of scope. pub fn auto_transaction>( &self, cancellable: Option<&P>, ) -> Result { let _ = self.prepare_transaction(cancellable)?; Ok(TransactionGuard { repo: Some(self) }) } /// Return a copy of the directory file descriptor for this repository. #[cfg(any(feature = "v2016_4", feature = "dox"))] #[cfg_attr(feature = "dox", doc(cfg(feature = "v2016_4")))] pub fn dfd_as_file(&self) -> std::io::Result { use std::os::unix::prelude::FromRawFd; use std::os::unix::prelude::IntoRawFd; unsafe { // A temporary owned file instance let dfd = std::fs::File::from_raw_fd(self.dfd()); // So we can call dup() on it let copy = dfd.try_clone(); // Now release our temporary ownership of the original let _ = dfd.into_raw_fd(); copy } } /// Borrow the directory file descriptor for this repository. #[cfg(feature = "cap-std-apis")] pub fn dfd_borrow<'a>(&'a self) -> io_lifetimes::BorrowedFd<'a> { unsafe { io_lifetimes::BorrowedFd::borrow_raw_fd(self.dfd()) } } /// Return a new `cap-std` directory reference for this repository. #[cfg(feature = "cap-std-apis")] pub fn dfd_as_dir(&self) -> std::io::Result { cap_std::fs::Dir::reopen_dir(&self.dfd_borrow()) } /// Find all objects reachable from a commit. pub fn traverse_commit>( &self, commit_checksum: &str, maxdepth: i32, cancellable: Option<&P>, ) -> Result, Error> { unsafe { let mut error = ptr::null_mut(); let mut hashtable = ptr::null_mut(); let _ = ffi::ostree_repo_traverse_commit( self.to_glib_none().0, commit_checksum.to_glib_none().0, maxdepth, &mut hashtable, cancellable.map(AsRef::as_ref).to_glib_none().0, &mut error, ); if error.is_null() { Ok(from_glib_container_variant_set(hashtable)) } else { Err(from_glib_full(error)) } } } /// List all branch names (refs). pub fn list_refs>( &self, refspec_prefix: Option<&str>, cancellable: Option<&P>, ) -> Result, Error> { unsafe { let mut error = ptr::null_mut(); let mut hashtable = ptr::null_mut(); let _ = ffi::ostree_repo_list_refs( self.to_glib_none().0, refspec_prefix.to_glib_none().0, &mut hashtable, cancellable.map(AsRef::as_ref).to_glib_none().0, &mut error, ); if error.is_null() { Ok(FromGlibPtrContainer::from_glib_container(hashtable)) } else { Err(from_glib_full(error)) } } } /// List all repo objects pub fn list_objects>( &self, flags: OstreeRepoListObjectsFlags, cancellable: Option<&P>, ) -> Result, Error> { unsafe { let mut error = ptr::null_mut(); let mut hashtable = ptr::null_mut(); ffi::ostree_repo_list_objects( self.to_glib_none().0, flags, &mut hashtable, cancellable.map(AsRef::as_ref).to_glib_none().0, &mut error, ); if error.is_null() { Ok(from_glib_container_variant_map(hashtable)) } else { Err(from_glib_full(error)) } } } /// List refs with extended options. #[cfg(any(feature = "v2016_4", feature = "dox"))] pub fn list_refs_ext>( &self, refspec_prefix: Option<&str>, flags: RepoListRefsExtFlags, cancellable: Option<&P>, ) -> Result, Error> { unsafe { let mut error = ptr::null_mut(); let mut hashtable = ptr::null_mut(); let _ = ffi::ostree_repo_list_refs_ext( self.to_glib_none().0, refspec_prefix.to_glib_none().0, &mut hashtable, flags.into_glib(), cancellable.map(AsRef::as_ref).to_glib_none().0, &mut error, ); if error.is_null() { Ok(FromGlibPtrContainer::from_glib_container(hashtable)) } else { Err(from_glib_full(error)) } } } /// Resolve a refspec to a commit SHA256. /// Returns an error if the refspec does not exist. pub fn require_rev(&self, refspec: &str) -> Result { // SAFETY: Since we said `false` for "allow_noent", this function must return a value Ok(self.resolve_rev(refspec, false)?.unwrap()) } /// Query metadata for a content object. /// /// This is similar to [`load_file`], but is more efficient if reading the file content is not needed. pub fn query_file>( &self, checksum: &str, cancellable: Option<&P>, ) -> Result<(gio::FileInfo, glib::Variant), glib::Error> { unsafe { let mut out_file_info = ptr::null_mut(); let mut out_xattrs = ptr::null_mut(); let mut error = ptr::null_mut(); let r = ffi::ostree_repo_load_file( self.to_glib_none().0, checksum.to_glib_none().0, ptr::null_mut(), &mut out_file_info, &mut out_xattrs, cancellable.map(|p| p.as_ref()).to_glib_none().0, &mut error, ); if error.is_null() { debug_assert!(r != 0); Ok((from_glib_full(out_file_info), from_glib_full(out_xattrs))) } else { debug_assert_eq!(r, 0); Err(from_glib_full(error)) } } } /// Write a content object from provided input. pub fn write_content, Q: IsA>( &self, expected_checksum: Option<&str>, object_input: &P, length: u64, cancellable: Option<&Q>, ) -> Result { unsafe { let mut error = ptr::null_mut(); let mut out_csum = ptr::null_mut(); let _ = ffi::ostree_repo_write_content( self.to_glib_none().0, expected_checksum.to_glib_none().0, object_input.as_ref().to_glib_none().0, length, &mut out_csum, cancellable.map(|p| p.as_ref()).to_glib_none().0, &mut error, ); if error.is_null() { Ok(from_glib_full(out_csum)) } else { Err(from_glib_full(error)) } } } /// Write a metadata object. pub fn write_metadata>( &self, objtype: ObjectType, expected_checksum: Option<&str>, object: &glib::Variant, cancellable: Option<&P>, ) -> Result { unsafe { let mut error = ptr::null_mut(); let mut out_csum = ptr::null_mut(); let _ = ffi::ostree_repo_write_metadata( self.to_glib_none().0, objtype.into_glib(), expected_checksum.to_glib_none().0, object.to_glib_none().0, &mut out_csum, cancellable.map(|p| p.as_ref()).to_glib_none().0, &mut error, ); if error.is_null() { Ok(from_glib_full(out_csum)) } else { Err(from_glib_full(error)) } } } /// Asynchronously write a content object. pub fn write_content_async< P: IsA, Q: IsA, R: FnOnce(Result) + Send + 'static, >( &self, expected_checksum: Option<&str>, object: &P, length: u64, cancellable: Option<&Q>, callback: R, ) { let user_data: Box = Box::new(callback); unsafe extern "C" fn write_content_async_trampoline< R: FnOnce(Result) + Send + 'static, >( _source_object: *mut glib::gobject_ffi::GObject, res: *mut gio::ffi::GAsyncResult, user_data: glib::ffi::gpointer, ) { let mut error = ptr::null_mut(); let mut out_csum = MaybeUninit::uninit(); let _ = ffi::ostree_repo_write_content_finish( _source_object as *mut _, res, out_csum.as_mut_ptr(), &mut error, ); let out_csum = out_csum.assume_init(); let result = if error.is_null() { Ok(Checksum::from_glib_full(out_csum)) } else { Err(from_glib_full(error)) }; let callback: Box = Box::from_raw(user_data as *mut _); callback(result); } let callback = write_content_async_trampoline::; unsafe { ffi::ostree_repo_write_content_async( self.to_glib_none().0, expected_checksum.to_glib_none().0, object.as_ref().to_glib_none().0, length, cancellable.map(|p| p.as_ref()).to_glib_none().0, Some(callback), Box::into_raw(user_data) as *mut _, ); } } /// Asynchronously write a content object. pub fn write_content_async_future + Clone + 'static>( &self, expected_checksum: Option<&str>, object: &P, length: u64, ) -> Pin> + 'static>> { let expected_checksum = expected_checksum.map(ToOwned::to_owned); let object = object.clone(); Box::pin(gio::GioFuture::new(self, move |obj, cancellable, send| { obj.write_content_async( expected_checksum .as_ref() .map(::std::borrow::Borrow::borrow), &object, length, Some(cancellable), move |res| { send.resolve(res); }, ); })) } /// Asynchronously write a metadata object. pub fn write_metadata_async< P: IsA, Q: FnOnce(Result) + Send + 'static, >( &self, objtype: ObjectType, expected_checksum: Option<&str>, object: &glib::Variant, cancellable: Option<&P>, callback: Q, ) { let user_data: Box = Box::new(callback); unsafe extern "C" fn write_metadata_async_trampoline< Q: FnOnce(Result) + Send + 'static, >( _source_object: *mut glib::gobject_ffi::GObject, res: *mut gio::ffi::GAsyncResult, user_data: glib_sys::gpointer, ) { let mut error = ptr::null_mut(); let mut out_csum = MaybeUninit::uninit(); let _ = ffi::ostree_repo_write_metadata_finish( _source_object as *mut _, res, out_csum.as_mut_ptr(), &mut error, ); let out_csum = out_csum.assume_init(); let result = if error.is_null() { Ok(Checksum::from_glib_full(out_csum)) } else { Err(from_glib_full(error)) }; let callback: Box = Box::from_raw(user_data as *mut _); callback(result); } let callback = write_metadata_async_trampoline::; unsafe { ffi::ostree_repo_write_metadata_async( self.to_glib_none().0, objtype.into_glib(), expected_checksum.to_glib_none().0, object.to_glib_none().0, cancellable.map(|p| p.as_ref()).to_glib_none().0, Some(callback), Box::into_raw(user_data) as *mut _, ); } } /// Asynchronously write a metadata object. pub fn write_metadata_async_future( &self, objtype: ObjectType, expected_checksum: Option<&str>, object: &glib::Variant, ) -> Pin> + 'static>> { let expected_checksum = expected_checksum.map(ToOwned::to_owned); let object = object.clone(); Box::pin(gio::GioFuture::new(self, move |obj, cancellable, send| { obj.write_metadata_async( objtype, expected_checksum .as_ref() .map(::std::borrow::Borrow::borrow), &object, Some(cancellable), move |res| { send.resolve(res); }, ); })) } /// Load and parse directory metadata. /// In particular, uid/gid/mode are stored in big-endian format; this function /// converts them to host native endianness. pub fn read_dirmeta(&self, checksum: &str) -> Result { let v = self.load_variant(crate::ObjectType::DirMeta, checksum)?; // Safety: We know the variant type will match since we just passed it above Ok(crate::DirMetaParsed::from_variant(&v).unwrap()) } }