libxml_rs/abi/versioning.rs
1//! C ABI versioning — LIBXML2_VERSION, LIBXSLT_VERSION, runtime version APIs (§83, §84).
2//!
3//! This module implements the public C ABI version functions:
4//! - `xmlLibxmlVersion()` — returns LIBXML2_VERSION as integer
5//! - `xmlLibxmlVersionString()` — returns LIBXML2_VERSION string pointer
6//! - `xmlParserVersion()` — alias for xmlLibxmlVersionString
7//! - `xmlCheckVersion()` — runtime version compatibility check
8//! - `xsltLibxsltVersion()` — returns LIBXSLT_VERSION as integer
9//! - `xsltLibxsltVersionString()` — returns LIBXSLT_VERSION string pointer
10//! - `xsltCheckVersion()` — runtime XSLT version compatibility check
11//!
12//! # Phase 1 status
13//!
14//! Complete — all version APIs are implemented.
15//!
16//! # Compatibility profile
17//!
18//! Currently targeting libxml2 2.15.3 / libxslt 1.1.45 compatibility
19//! (the oracle toolchain on the reference system).
20//!
21//! # UPSTREAM-PARITY
22//!
23//! Upstream version format: major * 10000 + minor * 100 + micro
24//! Example: 2.15.3 → 21503
25//!
26//! # Upstream contract
27//!
28//! Version reporting per upstream `globals.c` (`xmlLibxmlVersion`,
29//! `xmlParserVersion`, `xmlCheckVersion`) and `parser.c`; the libxslt version
30//! surface per `xslt.c`/`xslt.h`. The parity target is libxml2 2.15.3 /
31//! libxslt 1.1.45 — the system oracle DSOs.
32//!
33//! # Conceptual behavior
34//!
35//! This module implements the runtime version-reporting functions: numeric
36//! (major*10000 + minor*100 + micro) and string forms, plus
37//! `xmlCheckVersion`/`xsltCheckVersion` gatekeeping. The versioned symbols
38//! exported as DATA live in `data_globals.rs`; this module supplies the
39//! pure-Rust computation and the internal (non-exported) string helpers.
40//!
41//! # Ownership & safety invariants
42//!
43//! Returned version strings are static NUL-terminated byte slices — the caller
44//! must never free them (borrowed/static contract). The numeric constants are
45//! compile-time; nothing here allocates or transfers ownership.
46//!
47//! # Historical quirks & epochs
48//!
49//! R-000167 (11.1-S): `xsltLibxsltVersion` was exported as a function (symbol
50//! type T) while upstream 1.1.45 declares it `XSLTPUBVAR const int` (symbol
51//! type R) — a consumer reading the value per the header contract got code
52//! bytes; all four version symbols now match the oracle nm -D types. R-000133:
53//! `xmlCheckVersion` was declared-but-unexported and had to be implemented.
54//! QUIRK-0003/LORE-0004 record that the NEWS file lagged releases in the
55//! 2.7-2.9 era, so version identity must come from git tags, not NEWS.
56//!
57//! # Deliberate oddities
58//!
59//! `xmlParserVersion` is the git-version string `21503-GITv2.15.3` (the
60//! oracles own `--version` output, per SEMANTIC_EPOCHS section 1) rather than
61//! a plain `2.15.3` — deliberate parity with the oracle DSO. The
62//! candidate-only `xsltLibxsltVersionString` helper exists internally but is
63//! deliberately not exported (upstream has no such symbol).
64//!
65//! # Proving courts
66//!
67//! The DSO-LOADER court verifies symbol-type parity (R vs T vs D) against the
68//! oracle for the version data symbols (R-000167); ABI-DATA, GLOBAL-STATE and
69//! PARSER families cover the version entry points; the ORACLE-IDENTITY court
70//! family fingerprints the candidate binary against the oracle version output.
71//!
72//! # Tempting simplifications that would break parity
73//!
74//! A tempting simplification is to export the version values as plain
75//! functions again — R-000167 showed the header declares them as data, so any
76//! C consumer reading `xsltLibxsltVersion` per the header contract would read
77//! code bytes instead of the int. The symbol type must match the oracle DSO
78//! exactly; the version string must stay the git-version form or the
79//! version-dependent courts would fail.
80
81#![allow(non_upper_case_globals)]
82
83use core::ffi::c_char;
84use core::sync::atomic::AtomicBool;
85use core::sync::atomic::Ordering;
86use std::os::raw::c_int;
87
88// ═══════════════════════════════════════════════════════════════════════════════
89// Target Version Constants
90// ═══════════════════════════════════════════════════════════════════════════════
91
92/// The target libxml2 version we aim to be compatible with.
93const TARGET_LIBXML2_MAJOR: c_int = 2;
94const TARGET_LIBXML2_MINOR: c_int = 15;
95const TARGET_LIBXML2_MICRO: c_int = 3;
96
97/// The target libxslt version we aim to be compatible with.
98const TARGET_LIBXSLT_MAJOR: c_int = 1;
99const TARGET_LIBXSLT_MINOR: c_int = 1;
100const TARGET_LIBXSLT_MICRO: c_int = 45;
101
102/// The version string for libxml2 compatibility.
103const LIBXML2_VERSION_STRING: &[u8; 7] = b"2.15.3\0";
104
105/// The version string for libxslt compatibility.
106const LIBXSLT_VERSION_STRING: &[u8; 7] = b"1.1.45\0";
107
108// ═══════════════════════════════════════════════════════════════════════════════
109// Version Macros (also defined in types.rs for compile-time use)
110// ═══════════════════════════════════════════════════════════════════════════════
111
112/// Compute the numeric version from major/minor/micro components.
113#[inline]
114pub const fn version_number(major: c_int, minor: c_int, micro: c_int) -> c_int {
115 major * 10000 + minor * 100 + micro
116}
117
118/// libxml2 version as a number: 2 * 10000 + 15 * 100 + 3 = 21503
119pub const LIBXML2_VERSION_NUM: c_int = version_number(
120 TARGET_LIBXML2_MAJOR,
121 TARGET_LIBXML2_MINOR,
122 TARGET_LIBXML2_MICRO,
123);
124
125/// libxslt version as a number: 1 * 10000 + 1 * 100 + 45 = 10145
126pub const LIBXSLT_VERSION_NUM: c_int = version_number(
127 TARGET_LIBXSLT_MAJOR,
128 TARGET_LIBXSLT_MINOR,
129 TARGET_LIBXSLT_MICRO,
130);
131
132// ═══════════════════════════════════════════════════════════════════════════════
133// Initialization Tracking
134// ═══════════════════════════════════════════════════════════════════════════════
135
136/// Whether the library has been initialized.
137static INITIALIZED: AtomicBool = AtomicBool::new(false);
138
139/// Mark the library as initialized.
140pub fn set_initialized() {
141 INITIALIZED.store(true, Ordering::Release);
142}
143
144/// Check whether the library has been initialized.
145pub fn is_initialized() -> bool {
146 INITIALIZED.load(Ordering::Acquire)
147}
148
149// ═══════════════════════════════════════════════════════════════════════════════
150// libxml2 Version Functions
151// ═══════════════════════════════════════════════════════════════════════════════
152
153/// Return the libxml2 version as an integer.
154///
155/// Returns `major * 10000 + minor * 100 + micro`.
156///
157/// # UPSTREAM-PARITY
158///
159/// ```c
160/// int xmlLibxmlVersion(void);
161/// ```
162///
163/// Oracle behavior (2.15.3): returns 21503.
164pub const fn xmlLibxmlVersion() -> c_int {
165 LIBXML2_VERSION_NUM
166}
167
168/// Return the libxml2 version as a static C string.
169///
170/// # UPSTREAM-PARITY
171///
172/// ```c
173/// const char *xmlLibxmlVersionString(void);
174/// ```
175///
176/// Oracle behavior (2.15.3): returns pointer to "2.15.3".
177pub const fn xmlLibxmlVersionString() -> *const c_char {
178 LIBXML2_VERSION_STRING.as_ptr() as *const c_char
179}
180
181/// Return the parser version string (alias for `xmlLibxmlVersionString`).
182///
183/// # UPSTREAM-PARITY
184///
185/// ```c
186/// const char *xmlParserVersion(void);
187/// ```
188pub const fn xmlParserVersion() -> *const c_char {
189 xmlLibxmlVersionString()
190}
191
192/// Check that the library version is at least `version`.
193///
194/// # Returns
195///
196/// - 0 if the library version is >= `version`
197/// - -1 if the library version is < `version`
198///
199/// # UPSTREAM-PARITY
200///
201/// ```c
202/// int xmlCheckVersion(int version);
203/// ```
204///
205/// Oracle behavior: compares LIBXML2_VERSION (compiled-in) against `version`.
206/// Returns 0 if compatible, -1 if not.
207///
208/// # SAFETY
209///
210/// The function touches crate-global state only; it is safe
211/// as long as the caller respects the library's global
212/// initialization/cleanup ordering (xmlInitParser before use,
213/// xmlCleanupParser only after all users are done).
214///
215/// Violating the global lifecycle ordering, or calling this after
216/// teardown or from a signal handler, is undefined behavior.
217#[no_mangle]
218pub const unsafe extern "C" fn xmlCheckVersion(version: c_int) -> c_int {
219 if LIBXML2_VERSION_NUM >= version {
220 0
221 } else {
222 -1
223 }
224}
225
226// ═══════════════════════════════════════════════════════════════════════════════
227// libxslt Version Functions
228// ═══════════════════════════════════════════════════════════════════════════════
229
230/// Return the libxslt version as an integer.
231///
232/// Returns `major * 10000 + minor * 100 + micro`.
233///
234/// # UPSTREAM-PARITY
235///
236/// ```c
237/// int xsltLibxsltVersion(void);
238/// ```
239pub const fn xsltLibxsltVersion() -> c_int {
240 LIBXSLT_VERSION_NUM
241}
242
243/// Return the libxslt version as a static C string.
244///
245/// # UPSTREAM-PARITY
246///
247/// ```c
248/// const char *xsltLibxsltVersionString(void);
249/// ```
250pub const fn xsltLibxsltVersionString() -> *const c_char {
251 LIBXSLT_VERSION_STRING.as_ptr() as *const c_char
252}
253
254/// Convert a C string pointer to a byte slice (NULL-safe).
255///
256/// # SAFETY
257///
258/// - `ptr` must be a valid null-terminated C string or NULL.
259pub unsafe fn c_str_to_bytes<'a>(ptr: *const c_char) -> Option<&'a [u8]> {
260 if ptr.is_null() {
261 return None;
262 }
263 let len = unsafe { libc::strlen(ptr) };
264 Some(unsafe { core::slice::from_raw_parts(ptr as *const u8, len) })
265}
266
267/// Check that the XSLT library version is at least `version`.
268///
269/// # Returns
270///
271/// - 0 if the library version is >= `version`
272/// - -1 if the library version is < `version`
273///
274/// # UPSTREAM-PARITY
275///
276/// ```c
277/// int xsltCheckVersion(int version);
278/// ```
279pub const fn xsltCheckVersion(version: c_int) -> c_int {
280 if LIBXSLT_VERSION_NUM >= version {
281 0
282 } else {
283 -1
284 }
285}
286
287// ═══════════════════════════════════════════════════════════════════════════════
288// Feature Detection
289// ═══════════════════════════════════════════════════════════════════════════════
290
291// ═══════════════════════════════════════════════════════════════════════════════
292// Compile-time Version Macros (for Rust consumers)
293// ═══════════════════════════════════════════════════════════════════════════════
294
295/// The libxml2 version as a number (compile-time constant).
296pub const LIBXML2_VERSION: c_int = LIBXML2_VERSION_NUM;
297
298/// The libxml2 version major number.
299pub const LIBXML2_VERSION_MAJOR: c_int = TARGET_LIBXML2_MAJOR;
300
301/// The libxml2 version minor number.
302pub const LIBXML2_VERSION_MINOR: c_int = TARGET_LIBXML2_MINOR;
303
304/// The libxml2 version micro number.
305pub const LIBXML2_VERSION_MICRO: c_int = TARGET_LIBXML2_MICRO;
306
307/// The libxml2 version as a number (alternate name).
308pub const LIBXML2_VERSION_NUMBER: c_int = LIBXML2_VERSION_NUM;
309
310/// Extra version suffix (empty string for release versions).
311pub const LIBXML2_VERSION_EXTRA: &[u8; 1] = b"\0";
312
313/// The libxslt version as a number (compile-time constant).
314pub const LIBXSLT_VERSION: c_int = LIBXSLT_VERSION_NUM;
315
316/// The libxslt version major number.
317pub const LIBXSLT_VERSION_MAJOR: c_int = TARGET_LIBXSLT_MAJOR;
318
319/// The libxslt version minor number.
320pub const LIBXSLT_VERSION_MINOR: c_int = TARGET_LIBXSLT_MINOR;
321
322/// The libxslt version micro number.
323pub const LIBXSLT_VERSION_MICRO: c_int = TARGET_LIBXSLT_MICRO;
324
325/// The libxslt version as a number (alternate name).
326pub const LIBXSLT_VERSION_NUMBER: c_int = LIBXSLT_VERSION_NUM;
327
328/// Extra version suffix for libxslt (empty string for release versions).
329pub const LIBXSLT_VERSION_EXTRA: &[u8; 1] = b"\0";
330
331// ═══════════════════════════════════════════════════════════════════════════════
332// Tests