|
| 1 | +//! Custom backends for [`Odb`]s. |
| 2 | +use crate::util::Binding; |
| 3 | +use crate::{raw, Error, ErrorClass, ErrorCode, ObjectType, Oid}; |
| 4 | +use libc::{c_int, size_t}; |
| 5 | +use std::ffi::c_void; |
| 6 | +use std::marker::PhantomData; |
| 7 | +use std::mem::ManuallyDrop; |
| 8 | +use std::{ptr, slice}; |
| 9 | + |
| 10 | +pub trait OdbBackend { |
| 11 | + fn supported_operations(&self) -> SupportedOperations; |
| 12 | + |
| 13 | + fn read( |
| 14 | + &mut self, |
| 15 | + ctx: &OdbBackendContext, |
| 16 | + oid: Oid, |
| 17 | + out: &mut OdbBackendAllocation, |
| 18 | + ) -> Result<ObjectType, Error> { |
| 19 | + (ctx, oid, out); |
| 20 | + unimplemented!("OdbBackend::read") |
| 21 | + } |
| 22 | + // TODO: fn read_prefix(&mut self, ctx: &OdbBackendContext); |
| 23 | + fn read_header(&mut self, ctx: &OdbBackendContext, oid: Oid) -> Result<OdbHeader, Error> { |
| 24 | + (ctx, oid); |
| 25 | + unimplemented!("OdbBackend::read") |
| 26 | + } |
| 27 | + // TODO: fn write() |
| 28 | + // TODO: fn writestream() |
| 29 | + // TODO: fn readstream() |
| 30 | + fn exists(&mut self, ctx: &OdbBackendContext, oid: Oid) -> Result<bool, Error>; |
| 31 | + // TODO: fn exists_prefix() |
| 32 | + // TODO: fn refresh() |
| 33 | + // TODO: fn foreach() |
| 34 | + // TODO: fn writepack() |
| 35 | + // TODO: fn writemidx() |
| 36 | + // TODO: fn freshen() |
| 37 | +} |
| 38 | + |
| 39 | +bitflags::bitflags! { |
| 40 | + pub struct SupportedOperations: u32 { |
| 41 | + /// The backend supports the [`OdbBackend::read`] method. |
| 42 | + const READ = 1; |
| 43 | + /// The backend supports the [`OdbBackend::read_prefix`] method. |
| 44 | + const READ_PREFIX = 1 << 1; |
| 45 | + /// The backend supports the [`OdbBackend::read_header`] method. |
| 46 | + const READ_HEADER = 1 << 2; |
| 47 | + /// The backend supports the [`OdbBackend::write`] method. |
| 48 | + const WRITE = 1 << 3; |
| 49 | + /// The backend supports the [`OdbBackend::writestream`] method. |
| 50 | + const WRITESTREAM = 1 << 4; |
| 51 | + /// The backend supports the [`OdbBackend::readstream`] method. |
| 52 | + const READSTREAM = 1 << 5; |
| 53 | + /// The backend supports the [`OdbBackend::exists`] method. |
| 54 | + const EXISTS = 1 << 6; |
| 55 | + /// The backend supports the [`OdbBackend::exists_prefix`] method. |
| 56 | + const EXISTS_PREFIX = 1 << 7; |
| 57 | + /// The backend supports the [`OdbBackend::refresh`] method. |
| 58 | + const REFRESH = 1 << 7; |
| 59 | + /// The backend supports the [`OdbBackend::foreach`] method. |
| 60 | + const FOREACH = 1 << 8; |
| 61 | + /// The backend supports the [`OdbBackend::writepack`] method. |
| 62 | + const WRITEPACK = 1 << 9; |
| 63 | + /// The backend supports the [`OdbBackend::writemidx`] method. |
| 64 | + const WRITEMIDX = 1 << 10; |
| 65 | + /// The backend supports the [`OdbBackend::freshen`] method. |
| 66 | + const FRESHEN = 1 << 11; |
| 67 | + } |
| 68 | +} |
| 69 | + |
| 70 | +pub struct OdbHeader { |
| 71 | + pub size: usize, |
| 72 | + pub object_type: ObjectType, |
| 73 | +} |
| 74 | + |
| 75 | +pub struct OdbBackendAllocation { |
| 76 | + backend_ptr: *mut raw::git_odb_backend, |
| 77 | + raw: *mut c_void, |
| 78 | + size: usize, |
| 79 | +} |
| 80 | +impl OdbBackendAllocation { |
| 81 | + pub fn as_mut(&mut self) -> &mut [u8] { |
| 82 | + unsafe { slice::from_raw_parts_mut(self.raw.cast(), self.size) } |
| 83 | + } |
| 84 | +} |
| 85 | +impl Drop for OdbBackendAllocation { |
| 86 | + fn drop(&mut self) { |
| 87 | + unsafe { |
| 88 | + raw::git_odb_backend_data_free(self.backend_ptr, self.raw); |
| 89 | + } |
| 90 | + } |
| 91 | +} |
| 92 | + |
| 93 | +pub struct OdbBackendRead {} |
| 94 | + |
| 95 | +pub struct OdbBackendContext { |
| 96 | + backend_ptr: *mut raw::git_odb_backend, |
| 97 | +} |
| 98 | +impl OdbBackendContext { |
| 99 | + /// Creates an instance of `OdbBackendAllocation` that points to `null`. |
| 100 | + /// Its size will be 0. |
| 101 | + pub fn null_alloc(&self) -> OdbBackendAllocation { |
| 102 | + OdbBackendAllocation { |
| 103 | + backend_ptr: self.backend_ptr, |
| 104 | + raw: ptr::null_mut(), |
| 105 | + size: 0, |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + /// Attempts to allocate a buffer of size `size`. |
| 110 | + /// |
| 111 | + /// # Return value |
| 112 | + /// `Some(allocation)` if the allocation succeeded. |
| 113 | + /// `None` otherwise. This usually indicates that there is not enough memory. |
| 114 | + pub fn alloc(&self, size: usize) -> Option<OdbBackendAllocation> { |
| 115 | + let data = unsafe { raw::git_odb_backend_data_alloc(self.backend_ptr, size as size_t) }; |
| 116 | + if data.is_null() { |
| 117 | + return None; |
| 118 | + } |
| 119 | + Some(OdbBackendAllocation { |
| 120 | + backend_ptr: self.backend_ptr, |
| 121 | + raw: data, |
| 122 | + size, |
| 123 | + }) |
| 124 | + } |
| 125 | + |
| 126 | + /// Attempts to allocate a buffer of size `size`, returning an error when that fails. |
| 127 | + /// Essentially the same as [`alloc`], but returns a [`Result`]. |
| 128 | + /// |
| 129 | + /// # Return value |
| 130 | + /// `Ok(allocation)` if the allocation succeeded. |
| 131 | + /// `Err(error)` otherwise. The error is always a `GenericError` of class `NoMemory`. |
| 132 | + pub fn try_alloc(&self, size: usize) -> Result<OdbBackendAllocation, Error> { |
| 133 | + self.alloc(size).ok_or_else(|| { |
| 134 | + Error::new( |
| 135 | + ErrorCode::GenericError, |
| 136 | + ErrorClass::NoMemory, |
| 137 | + "out of memory", |
| 138 | + ) |
| 139 | + }) |
| 140 | + } |
| 141 | +} |
| 142 | + |
| 143 | +/// A handle to an [`OdbBackend`] that has been added to an [`Odb`](crate::Odb). |
| 144 | +pub struct CustomOdbBackend<'a, B: OdbBackend> { |
| 145 | + // NOTE: Any pointer in this field must be both non-null and properly aligned. |
| 146 | + raw: NonNull<Backend<B>>, |
| 147 | + phantom: PhantomData<fn() -> &'a ()>, |
| 148 | +} |
| 149 | + |
| 150 | +impl<'a, B: OdbBackend> CustomOdbBackend<'a, B> { |
| 151 | + pub(crate) fn new_inner(backend: B) -> Box<Backend<B>> { |
| 152 | + let mut parent = raw::git_odb_backend { |
| 153 | + version: raw::GIT_ODB_BACKEND_VERSION, |
| 154 | + odb: ptr::null_mut(), |
| 155 | + read: None, |
| 156 | + read_prefix: None, |
| 157 | + read_header: None, |
| 158 | + write: None, |
| 159 | + writestream: None, |
| 160 | + readstream: None, |
| 161 | + exists: None, |
| 162 | + exists_prefix: None, |
| 163 | + refresh: None, |
| 164 | + foreach: None, |
| 165 | + writepack: None, |
| 166 | + writemidx: None, |
| 167 | + freshen: None, |
| 168 | + free: None, |
| 169 | + }; |
| 170 | + Self::set_operations(backend.supported_operations(), &mut parent); |
| 171 | + |
| 172 | + Box::new(Backend { |
| 173 | + parent, |
| 174 | + inner: backend, |
| 175 | + }) |
| 176 | + } |
| 177 | + pub(crate) fn new(backend: Box<Backend<B>>) -> Self { |
| 178 | + // SAFETY: Box::into_raw guarantees that the pointer is properly aligned and non-null |
| 179 | + let backend = Box::into_raw(backend); |
| 180 | + let backend = unsafe { NonNull::new_unchecked(backend) }; |
| 181 | + Self { |
| 182 | + raw: backend, |
| 183 | + phantom: PhantomData, |
| 184 | + } |
| 185 | + } |
| 186 | + fn set_operations( |
| 187 | + supported_operations: SupportedOperations, |
| 188 | + backend: &mut raw::git_odb_backend, |
| 189 | + ) { |
| 190 | + macro_rules! op_if { |
| 191 | + ($name:ident if $flag:ident) => { |
| 192 | + backend.$name = supported_operations |
| 193 | + .contains(SupportedOperations::$flag) |
| 194 | + .then_some(Backend::<B>::$name) |
| 195 | + }; |
| 196 | + } |
| 197 | + op_if!(read if READ); |
| 198 | + op_if!(read_header if READ_HEADER); |
| 199 | + op_if!(exists if EXISTS); |
| 200 | + |
| 201 | + backend.free = Some(Backend::<B>::free); |
| 202 | + } |
| 203 | + |
| 204 | + pub(crate) fn as_git_odb_backend(&self) -> *mut raw::git_odb_backend { |
| 205 | + self.raw.cast() |
| 206 | + } |
| 207 | +} |
| 208 | + |
| 209 | +#[repr(C)] |
| 210 | +pub(crate) struct Backend<B> { |
| 211 | + parent: raw::git_odb_backend, |
| 212 | + inner: B, |
| 213 | +} |
| 214 | +impl<B: OdbBackend> Backend<B> { |
| 215 | + extern "C" fn read( |
| 216 | + data_ptr: *mut *mut c_void, |
| 217 | + size_ptr: *mut size_t, |
| 218 | + otype_ptr: *mut raw::git_object_t, |
| 219 | + backend_ptr: *mut raw::git_odb_backend, |
| 220 | + oid_ptr: *const raw::git_oid, |
| 221 | + ) -> raw::git_error_code { |
| 222 | + let backend = unsafe { backend_ptr.cast::<Self>().as_mut().unwrap() }; |
| 223 | + let data = unsafe { data_ptr.as_mut().unwrap() }; |
| 224 | + let size = unsafe { size_ptr.as_mut().unwrap() }; |
| 225 | + let object_type = unsafe { otype_ptr.as_mut().unwrap() }; |
| 226 | + let oid = unsafe { Oid::from_raw(oid_ptr) }; |
| 227 | + |
| 228 | + let context = OdbBackendContext { backend_ptr }; |
| 229 | + |
| 230 | + let mut allocation = ManuallyDrop::new(context.null_alloc()); |
| 231 | + |
| 232 | + let output = match backend.inner.read(&context, oid, &mut allocation) { |
| 233 | + Err(e) => { |
| 234 | + ManuallyDrop::into_inner(allocation); |
| 235 | + return e.raw_code(); |
| 236 | + } |
| 237 | + Ok(o) => o, |
| 238 | + }; |
| 239 | + |
| 240 | + *size = allocation.size; |
| 241 | + *data = allocation.raw; |
| 242 | + *object_type = output.raw(); |
| 243 | + |
| 244 | + raw::GIT_OK |
| 245 | + } |
| 246 | + extern "C" fn read_header( |
| 247 | + size_ptr: *mut size_t, |
| 248 | + otype_ptr: *mut raw::git_object_t, |
| 249 | + backend_ptr: *mut raw::git_odb_backend, |
| 250 | + oid_ptr: *const raw::git_oid, |
| 251 | + ) -> raw::git_error_code { |
| 252 | + let size = unsafe { size_ptr.as_mut().unwrap() }; |
| 253 | + let otype = unsafe { otype_ptr.as_mut().unwrap() }; |
| 254 | + let backend = unsafe { backend_ptr.cast::<Backend<B>>().as_mut().unwrap() }; |
| 255 | + let oid = unsafe { Oid::from_raw(oid_ptr) }; |
| 256 | + |
| 257 | + let context = OdbBackendContext { backend_ptr }; |
| 258 | + |
| 259 | + let header = match backend.inner.read_header(&context, oid) { |
| 260 | + Err(e) => unsafe { return e.raw_set_git_error() }, |
| 261 | + Ok(header) => header, |
| 262 | + }; |
| 263 | + *size = header.size; |
| 264 | + *otype = header.object_type.raw(); |
| 265 | + raw::GIT_OK |
| 266 | + } |
| 267 | + |
| 268 | + extern "C" fn free(backend: *mut raw::git_odb_backend) { |
| 269 | + let inner = unsafe { Box::from_raw(backend.cast::<Self>()) }; |
| 270 | + drop(inner); |
| 271 | + } |
| 272 | + |
| 273 | + extern "C" fn exists( |
| 274 | + backend_ptr: *mut raw::git_odb_backend, |
| 275 | + oid_ptr: *const raw::git_oid, |
| 276 | + ) -> c_int { |
| 277 | + let backend = unsafe { backend_ptr.cast::<Backend<B>>().as_mut().unwrap() }; |
| 278 | + let oid = unsafe { Oid::from_raw(oid_ptr) }; |
| 279 | + let context = OdbBackendContext { backend_ptr }; |
| 280 | + let exists = match backend.inner.exists(&context, oid) { |
| 281 | + Err(e) => return unsafe { e.raw_set_git_error() }, |
| 282 | + Ok(x) => x, |
| 283 | + }; |
| 284 | + if exists { |
| 285 | + 1 |
| 286 | + } else { |
| 287 | + 0 |
| 288 | + } |
| 289 | + } |
| 290 | +} |
0 commit comments