libxdiff/
lib.rs

1//! # libxdiff bindings for Rust
2//!
3//! This library contains bindings to the [libxdiff][1] C library. The
4//! underlying library defines "MMFiles" using chains of non-contiguous buffers
5//! to minimize reallocations when appending and mutating.
6//! This wrapper structures the API by defining all [`MMFile`]s to be compact
7//! (backed by a single buffer). The non-compact form is [`MMBlocks`].
8//!
9//! libxdiff tracks iteration over buffers internally, so some operations that
10//! conceptually are read-only end up requiring `&mut` arguments in order to be
11//! safe.
12//!
13//! # Example
14//!
15//! ```rust
16//! use core::str::from_utf8;
17//! use libxdiff::MMFile;
18//!
19//! let mut f1 = MMFile::from_bytes(b"hello world\n");
20//! let mut f2 = MMFile::from_bytes(b"hello world!\n");
21//! let mut diff_lines = Vec::<String>::new();
22//! f1.diff_raw(&mut f2, |line: &[u8]| {
23//!     diff_lines.push(from_utf8(line).unwrap().to_owned());
24//! })
25//! .unwrap();
26//!
27//! assert_eq!(
28//!     diff_lines,
29//!     vec![
30//!         "@@ -1,1 +1,1 @@\n",
31//!         "-", "hello world\n",
32//!         "+", "hello world!\n",
33//!     ],
34//! );
35//! ```
36//!
37//! [1]: http://www.xmailserver.org/xdiff-lib.html
38
39#[cfg_attr(not(feature = "std"), no_std)]
40use core::{
41    ffi::{c_long, c_uint, c_ulong, c_void},
42    mem::MaybeUninit,
43    ptr::{addr_of, null_mut},
44    sync::atomic::{AtomicBool, Ordering},
45};
46
47use libc::{free, malloc, realloc, size_t};
48use libxdiff_sys::{memallocator_t, mmfile_t, xdl_init_mmfile, xdl_set_allocator, XDL_MMF_ATOMIC};
49
50mod mmfile;
51pub use mmfile::*;
52
53mod mmblocks;
54pub use mmblocks::*;
55
56#[cfg(test)]
57mod tests;
58
59unsafe extern "C" fn wrap_malloc(_obj: *mut c_void, size: c_uint) -> *mut c_void {
60    malloc(size as size_t)
61}
62
63unsafe extern "C" fn wrap_free(_obj: *mut c_void, ptr: *mut c_void) {
64    free(ptr)
65}
66
67unsafe extern "C" fn wrap_realloc(
68    _obj: *mut c_void,
69    ptr: *mut c_void,
70    size: c_uint,
71) -> *mut c_void {
72    realloc(ptr, size as size_t)
73}
74
75// must call before using any xdl functions and must only call once
76unsafe fn init() {
77    let alloc_struct = memallocator_t {
78        priv_: null_mut(),
79        malloc: Some(wrap_malloc),
80        free: Some(wrap_free),
81        realloc: Some(wrap_realloc),
82    };
83    unsafe { xdl_set_allocator(addr_of!(alloc_struct)) };
84}
85
86static INITIALIZED: AtomicBool = AtomicBool::new(false);
87
88/// Safely ensure libxdiff has been initialized before proceeding. U
89/// This is called automatically when [`MMFile`]s are created. From
90/// that point on, the existence of the object means the library has been
91/// initialized already.
92pub(crate) fn ensure_init() {
93    if let Ok(_) = INITIALIZED.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) {
94        unsafe { init() }
95    }
96}
97
98/// Initialize a new mmfile_t
99pub(crate) fn init_mmfile(len: usize) -> mmfile_t {
100    ensure_init();
101    let mut inner: MaybeUninit<mmfile_t> = MaybeUninit::uninit();
102    let inner_ptr = inner.as_mut_ptr();
103    let err = unsafe { xdl_init_mmfile(inner_ptr, len as c_long, XDL_MMF_ATOMIC as c_ulong) };
104    if err != 0 {
105        panic!("mmfile initialization failed");
106    }
107    unsafe { inner.assume_init() }
108}