ostree/rust-bindings/src/repo.rs

519 lines
18 KiB
Rust

#[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<ObjectName> = &mut *(hash_set as *mut HashSet<ObjectName>);
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<ObjectName, ObjectDetails> =
&mut *(hash_set as *mut HashMap<ObjectName, ObjectDetails>);
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<ObjectName> {
let mut set = HashSet::new();
glib_sys::g_hash_table_foreach(
ptr,
Some(read_variant_table),
&mut set as *mut HashSet<ObjectName> as *mut _,
);
glib_sys::g_hash_table_unref(ptr);
set
}
unsafe fn from_glib_container_variant_map(
ptr: *mut glib_sys::GHashTable,
) -> HashMap<ObjectName, ObjectDetails> {
let mut set = HashMap::new();
glib_sys::g_hash_table_foreach(
ptr,
Some(read_variant_object_map),
&mut set as *mut HashMap<ObjectName, ObjectDetails> 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<P: IsA<gio::Cancellable>>(
mut self,
cancellable: Option<&P>,
) -> Result<RepoTransactionStats, glib::Error> {
// 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<P: AsRef<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<Repo, glib::Error> {
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<Repo, glib::Error> {
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<P: IsA<gio::Cancellable>>(
&self,
cancellable: Option<&P>,
) -> Result<TransactionGuard, glib::Error> {
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<std::fs::File> {
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> {
cap_std::fs::Dir::reopen_dir(&self.dfd_borrow())
}
/// Find all objects reachable from a commit.
pub fn traverse_commit<P: IsA<gio::Cancellable>>(
&self,
commit_checksum: &str,
maxdepth: i32,
cancellable: Option<&P>,
) -> Result<HashSet<ObjectName>, 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<P: IsA<gio::Cancellable>>(
&self,
refspec_prefix: Option<&str>,
cancellable: Option<&P>,
) -> Result<HashMap<String, String>, 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<P: IsA<gio::Cancellable>>(
&self,
flags: OstreeRepoListObjectsFlags,
cancellable: Option<&P>,
) -> Result<HashMap<ObjectName, ObjectDetails>, 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<P: IsA<gio::Cancellable>>(
&self,
refspec_prefix: Option<&str>,
flags: RepoListRefsExtFlags,
cancellable: Option<&P>,
) -> Result<HashMap<String, String>, 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<glib::GString, Error> {
// 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<P: IsA<gio::Cancellable>>(
&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<P: IsA<gio::InputStream>, Q: IsA<gio::Cancellable>>(
&self,
expected_checksum: Option<&str>,
object_input: &P,
length: u64,
cancellable: Option<&Q>,
) -> Result<Checksum, Error> {
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<P: IsA<gio::Cancellable>>(
&self,
objtype: ObjectType,
expected_checksum: Option<&str>,
object: &glib::Variant,
cancellable: Option<&P>,
) -> Result<Checksum, Error> {
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<gio::InputStream>,
Q: IsA<gio::Cancellable>,
R: FnOnce(Result<Checksum, Error>) + Send + 'static,
>(
&self,
expected_checksum: Option<&str>,
object: &P,
length: u64,
cancellable: Option<&Q>,
callback: R,
) {
let user_data: Box<R> = Box::new(callback);
unsafe extern "C" fn write_content_async_trampoline<
R: FnOnce(Result<Checksum, Error>) + 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<R> = Box::from_raw(user_data as *mut _);
callback(result);
}
let callback = write_content_async_trampoline::<R>;
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<P: IsA<gio::InputStream> + Clone + 'static>(
&self,
expected_checksum: Option<&str>,
object: &P,
length: u64,
) -> Pin<Box<dyn Future<Output = Result<Checksum, Error>> + '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<gio::Cancellable>,
Q: FnOnce(Result<Checksum, Error>) + Send + 'static,
>(
&self,
objtype: ObjectType,
expected_checksum: Option<&str>,
object: &glib::Variant,
cancellable: Option<&P>,
callback: Q,
) {
let user_data: Box<Q> = Box::new(callback);
unsafe extern "C" fn write_metadata_async_trampoline<
Q: FnOnce(Result<Checksum, Error>) + 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<Q> = Box::from_raw(user_data as *mut _);
callback(result);
}
let callback = write_metadata_async_trampoline::<Q>;
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<Box<dyn Future<Output = Result<Checksum, Error>> + '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<crate::DirMetaParsed, glib::Error> {
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())
}
}