Skip to main content

libxml_rs/abi/
data_globals.rs

1//! Exported C data globals — the classic libxml2/libxslt public variables
2//! (11.1-G data-ABI closure, residual R-000135).
3//!
4//! Downstream C code reads and writes these symbols directly
5//! (e.g. `xmlDoValidityCheckingDefaultValue = 1;`), so they must exist with
6//! upstream names, types and defaults. The parser-default accessors in
7//! `src/xml/globals/mod.rs` read and write the SAME statics (single source
8//! of truth): a C write is immediately observable by the candidate parser.
9//!
10//! Defaults match upstream `globals.c` (libxml2 2.15.3) and `xslt.c`
11//! (libxslt 1.1.45).
12//!
13//! # SAFETY
14//!
15//! `static mut` globals are unsafe to touch from Rust; every access goes
16//! through the accessor functions in `crate::xml::globals` (or directly
17//! here with an explicit safety note). C accesses are inherently racy in
18//! upstream too — upstream documents these globals as deprecated and
19//! not thread-safe.
20
21use core::ffi::c_void;
22use std::os::raw::{c_char, c_int, c_uint};
23
24use crate::abi::callbacks::{xmlGenericErrorFunc, xmlStructuredErrorFunc};
25use crate::abi::types::xmlChar;
26
27/// Serializes writes to the exported `xmlLastError` mirror.
28///
29/// Upstream's `xmlLastError` is a bare racy global (deprecated, documented
30/// not thread-safe), and the candidate preserves the C-visible semantics:
31/// downstream C consumers read the symbol directly without a lock. The
32/// candidate's internal deep-copy/free writers are serialized here so that
33/// concurrent error raises on different threads can never free the same
34/// mirror string twice or write while another thread is freeing — observed
35/// as heap corruption (`double free or corruption (!prev)`) in the parallel
36/// lib test suite (xml::errors tests racing with any other raising thread).
37static LAST_ERROR_MIRROR_LOCK: parking_lot::Mutex<()> = parking_lot::Mutex::new(());
38
39// ═══════════════════════════════════════════════════════════════════════════════
40// Parser defaults (upstream globals.c)
41// ═══════════════════════════════════════════════════════════════════════════════
42
43/// `int xmlDoValidityCheckingDefaultValue` (default 0)
44#[no_mangle]
45pub static mut xmlDoValidityCheckingDefaultValue: c_int = 0;
46
47/// `int xmlGetWarningsDefaultValue` (default 1)
48#[no_mangle]
49pub static mut xmlGetWarningsDefaultValue: c_int = 1;
50
51/// `int xmlLoadExtDtdDefaultValue` (default 0)
52#[no_mangle]
53pub static mut xmlLoadExtDtdDefaultValue: c_int = 0;
54
55/// `int xmlPedanticParserDefaultValue` (default 0)
56#[no_mangle]
57pub static mut xmlPedanticParserDefaultValue: c_int = 0;
58
59/// `int xmlLineNumbersDefaultValue` (default 1 — upstream globals.c
60/// `xmlLineNumbersDefaultValueThrDef = 1`)
61#[no_mangle]
62pub static mut xmlLineNumbersDefaultValue: c_int = 1;
63
64/// `int xmlKeepBlanksDefaultValue` (default 1)
65#[no_mangle]
66pub static mut xmlKeepBlanksDefaultValue: c_int = 1;
67
68/// `int xmlSubstituteEntitiesDefaultValue` (default 0)
69#[no_mangle]
70pub static mut xmlSubstituteEntitiesDefaultValue: c_int = 0;
71
72/// `int xmlParserDebugEntities` (default 0)
73#[no_mangle]
74pub static mut xmlParserDebugEntities: c_int = 0;
75
76/// `int xmlIndentTreeOutput` (default 1 — upstream globals.c
77/// `xmlIndentTreeOutputThrDef = 1`)
78#[no_mangle]
79pub static mut xmlIndentTreeOutput: c_int = 1;
80
81/// `const xmlChar *xmlTreeIndentString` (default "  " — upstream globals.c
82/// `xmlTreeIndentStringThrDef = "  "`)
83#[no_mangle]
84pub static mut xmlTreeIndentString: *const xmlChar = {
85    static S: [u8; 3] = *b"  \0";
86    S.as_ptr()
87};
88
89/// `int xmlSaveNoEmptyTags` (default 0)
90#[no_mangle]
91pub static mut xmlSaveNoEmptyTags: c_int = 0;
92
93/// Upstream `xmlRegisterNodeFunc xmlRegisterNodeDefaultValue` (default NULL)
94#[no_mangle]
95pub static mut xmlRegisterNodeDefaultValue: Option<
96    unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode),
97> = None;
98
99/// Upstream static `xmlRegisterCallbacks` (tree.c): the gate that arms the
100/// node register/deregister hooks once a callback has been registered.
101pub static XML_REGISTER_CALLBACKS: core::sync::atomic::AtomicBool =
102    core::sync::atomic::AtomicBool::new(false);
103
104/// Upstream `xmlRegisterNodeCallback(node)` — invoke the registered node
105/// hook, gated by `xmlRegisterCallbacks` (tree.c).
106///
107/// # SAFETY
108///
109/// - `node` must be a valid, fully-initialised node or NULL.
110pub fn register_node_hook(node: *mut crate::abi::structs::_xmlNode) {
111    if !XML_REGISTER_CALLBACKS.load(core::sync::atomic::Ordering::Relaxed) {
112        return;
113    }
114    let hook = unsafe { xmlRegisterNodeDefaultValue };
115    if let Some(h) = hook {
116        if !node.is_null() {
117            // SAFETY: the hook is a valid C callback registered by the user.
118            unsafe { h(node) };
119        }
120    }
121}
122
123/// Upstream `xmlDeregisterNodeCallback(node)` — invoke the registered node
124/// deregister hook, gated by `xmlRegisterCallbacks` (tree.c).
125///
126/// # SAFETY
127///
128/// - `node` must be a valid node about to be freed, or NULL.
129pub fn deregister_node_hook(node: *mut crate::abi::structs::_xmlNode) {
130    if !XML_REGISTER_CALLBACKS.load(core::sync::atomic::Ordering::Relaxed) {
131        return;
132    }
133    let hook = unsafe { xmlDeregisterNodeDefaultValue };
134    if let Some(h) = hook {
135        if !node.is_null() {
136            // SAFETY: the hook is a valid C callback registered by the user.
137            unsafe { h(node) };
138        }
139    }
140}
141
142/// `xmlDeregisterNodeFunc xmlDeregisterNodeDefaultValue` (default NULL)
143#[no_mangle]
144pub static mut xmlDeregisterNodeDefaultValue: Option<
145    unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode),
146> = None;
147
148/// `const char *xmlParserVersion` — matched to the system oracle build
149/// (libxml2 2.15.3 GIT build: LIBXML_VERSION_STRING "21503" plus the
150/// upstream version extra "-GITv2.15.3").
151///
152/// SAFETY: the pointed-to string is a static, immutable, null-terminated
153/// literal; `static mut` is used because C raw pointers are not `Sync`.
154/// Reads/writes of the pointer itself are racy only if C code mutates it
155/// (upstream treats it as a constant).
156#[no_mangle]
157pub static mut xmlParserVersion: *const c_char = {
158    static V: [u8; 17] = *b"21503-GITv2.15.3\0";
159    V.as_ptr() as *const c_char
160};
161
162/// `int xmlParserMaxDepth` (default 256)
163#[no_mangle]
164pub static mut xmlParserMaxDepth: c_int = 256;
165
166// ═══════════════════════════════════════════════════════════════════════════════
167// Buffer globals (upstream tree.h / xmlIO.c)
168// ═══════════════════════════════════════════════════════════════════════════════
169
170/// `int xmlDefaultBufferSize` (default 4096)
171#[no_mangle]
172pub static mut xmlDefaultBufferSize: c_int = 4096;
173
174/// `xmlBufferAllocationScheme xmlBufferAllocScheme` (default XML_BUFFER_ALLOC_EXACT = 1;
175/// upstream globals.c `xmlBufferAllocSchemeThrDef = XML_BUFFER_ALLOC_EXACT`)
176#[no_mangle]
177pub static mut xmlBufferAllocScheme: c_int = 1;
178
179// ═══════════════════════════════════════════════════════════════════════════════
180// Error callback globals (upstream xmlerror.h)
181// ═══════════════════════════════════════════════════════════════════════════════
182
183/// `xmlGenericErrorFunc xmlGenericError` — the generic error callback.
184///
185/// Upstream defaults to `xmlGenericErrorDefaultFunc` (a variadic stderr
186/// printer, error.c); the candidate's shim below reproduces it, so a
187/// freshly-initialized library routes errors to stderr exactly like upstream.
188#[cfg(target_arch = "x86_64")]
189#[no_mangle]
190pub static mut xmlGenericError: Option<xmlGenericErrorFunc> = Some(XML_GENERIC_ERROR_DEFAULT);
191
192#[cfg(not(target_arch = "x86_64"))]
193#[no_mangle]
194pub static mut xmlGenericError: Option<xmlGenericErrorFunc> = None;
195
196/// `void *xmlGenericErrorContext` — context for the generic error callback.
197#[no_mangle]
198pub static mut xmlGenericErrorContext: *mut c_void = core::ptr::null_mut();
199
200/// `xmlStructuredErrorFunc xmlStructuredError` — the structured error callback.
201#[no_mangle]
202pub static mut xmlStructuredError: Option<xmlStructuredErrorFunc> = None;
203
204/// `void *xmlStructuredErrorContext` — context for the structured callback.
205#[no_mangle]
206pub static mut xmlStructuredErrorContext: *mut c_void = core::ptr::null_mut();
207
208// ═══════════════════════════════════════════════════════════════════════════════
209// Variadic default error handlers (upstream error.c / xsltutils.c)
210// ═══════════════════════════════════════════════════════════════════════════════
211//
212// Upstream `xmlGenericErrorDefaultFunc(void *ctx, const char *msg, ...)`
213// prints the formatted message to `xmlGenericErrorContext` (stderr when
214// NULL). Stable Rust cannot define a variadic extern fn body, so the ABI
215// entry is an x86_64 SysV inline-asm shim that materialises the caller's
216// register/stack arguments into a `va_list` and forwards to a non-variadic
217// receiver — the same pattern as `xsltTransformError`
218// (exports_xslt_util.rs) and the writer's `vfmt_shim!`. Neither default
219// function is a dynamic export upstream (both are internal); the exported
220// data globals merely point at them.
221
222/// System V AMD64 `__va_list_tag` (24 bytes) — same layout as the writer's
223/// shims and `exports_xslt_util.rs`.
224#[cfg(target_arch = "x86_64")]
225#[repr(C)]
226#[derive(Clone, Copy)]
227struct VaListTag {
228    gp_offset: c_uint,
229    fp_offset: c_uint,
230    overflow_arg_area: *mut c_void,
231    reg_save_area: *mut c_void,
232}
233
234#[cfg(target_arch = "x86_64")]
235unsafe extern "C" {
236    fn vfprintf(stream: *mut c_void, format: *const c_char, ap: *mut VaListTag) -> c_int;
237}
238
239/// The `stderr` FILE* — glibc exports the `stderr` data object, an
240/// 8-byte pointer variable whose value is `&_IO_2_1_stderr_`. Upstream
241/// `xmlGenericErrorDefaultFunc` defaults the error context to `stderr`;
242/// using the real stdio object (unbuffered, fd-2 relative) keeps writes
243/// byte-exact and honors fd-2 redirection, unlike a private `fdopen(2)`
244/// FILE* which is fully buffered and lands at exit on whatever fd 2 then
245/// points to.
246#[cfg(target_arch = "x86_64")]
247unsafe fn stderr_file() -> *mut c_void {
248    extern "C" {
249        static stderr: *mut c_void;
250    }
251    unsafe { stderr }
252}
253
254/// Variadic receiver for the `xmlGenericErrorDefaultFunc` shim (upstream
255/// error.c semantics: default the context to stderr, then `vfprintf`).
256#[cfg(target_arch = "x86_64")]
257#[no_mangle]
258pub unsafe extern "C" fn xmlGenericErrorDefaultFuncV(
259    _ctx: *mut c_void,
260    msg: *const c_char,
261    ap: *mut VaListTag,
262) -> c_int {
263    unsafe {
264        if crate::abi::data_globals::xmlGenericErrorContext.is_null() {
265            crate::abi::data_globals::xmlGenericErrorContext = stderr_file();
266        }
267        let stream = crate::abi::data_globals::xmlGenericErrorContext;
268        if msg.is_null() || stream.is_null() {
269            return 0;
270        }
271        vfprintf(stream, msg, ap)
272    }
273}
274
275/// `xmlGenericErrorDefaultFunc(void *ctx, const char *msg, ...)` — the
276/// upstream default generic error handler. Not exported dynamically (matches
277/// upstream, where the symbol is internal).
278///
279/// 2 fixed args (ctx=rdi, msg=rsi) → `gp_offset` 16; the va_list pointer is
280/// passed as the 3rd arg (rdx) of the receiver.
281#[cfg(target_arch = "x86_64")]
282pub unsafe extern "C" fn xmlGenericErrorDefaultFunc() -> c_int {
283    unsafe {
284        core::arch::asm!(
285            "sub rsp, 240",
286            "mov [rsp+0], rdi",
287            "mov [rsp+8], rsi",
288            "mov [rsp+16], rdx",
289            "mov [rsp+24], rcx",
290            "mov [rsp+32], r8",
291            "mov [rsp+40], r9",
292            "movaps [rsp+48], xmm0",
293            "movaps [rsp+64], xmm1",
294            "movaps [rsp+80], xmm2",
295            "movaps [rsp+96], xmm3",
296            "movaps [rsp+112], xmm4",
297            "movaps [rsp+128], xmm5",
298            "movaps [rsp+144], xmm6",
299            "movaps [rsp+160], xmm7",
300            "mov dword ptr [rsp+176], 16",
301            "mov dword ptr [rsp+180], 48",
302            "lea rax, [rsp+256]",
303            "mov [rsp+184], rax",
304            "lea rax, [rsp]",
305            "mov [rsp+192], rax",
306            "lea rdx, [rsp+176]",
307            "call xmlGenericErrorDefaultFuncV",
308            "add rsp, 240",
309            "add rsp, 8",
310            "ret",
311            options(noreturn),
312        );
313    }
314}
315
316/// Default value of the exported `xmlGenericError` data global.
317#[cfg(target_arch = "x86_64")]
318const XML_GENERIC_ERROR_DEFAULT: xmlGenericErrorFunc = unsafe {
319    // SAFETY: the shim and the function-pointer type have identical ABI
320    // (a code pointer); the declared arity is a Rust-side fiction required
321    // to store a variadic entry in the non-variadic pointer type.
322    core::mem::transmute::<
323        unsafe extern "C" fn() -> c_int,
324        unsafe extern "C" fn(*mut c_void, *const c_char),
325    >(xmlGenericErrorDefaultFunc)
326};
327
328/// Variadic receiver for the `xsltGenericErrorDefaultFunc` shim (upstream
329/// xsltutils.c semantics: default the context to stderr, then `vfprintf`).
330#[cfg(target_arch = "x86_64")]
331#[no_mangle]
332pub unsafe extern "C" fn xsltGenericErrorDefaultFuncV(
333    _ctx: *mut c_void,
334    msg: *const c_char,
335    ap: *mut VaListTag,
336) -> c_int {
337    unsafe {
338        if crate::abi::data_globals::xsltGenericErrorContext.is_null() {
339            crate::abi::data_globals::xsltGenericErrorContext = stderr_file();
340        }
341        let stream = crate::abi::data_globals::xsltGenericErrorContext;
342        if msg.is_null() || stream.is_null() {
343            return 0;
344        }
345        vfprintf(stream, msg, ap)
346    }
347}
348
349/// `xsltGenericErrorDefaultFunc(void *ctx, const char *msg, ...)` — the
350/// upstream default XSLT error handler (xsltutils.c).
351#[cfg(target_arch = "x86_64")]
352pub unsafe extern "C" fn xsltGenericErrorDefaultFunc() -> c_int {
353    unsafe {
354        core::arch::asm!(
355            "sub rsp, 240",
356            "mov [rsp+0], rdi",
357            "mov [rsp+8], rsi",
358            "mov [rsp+16], rdx",
359            "mov [rsp+24], rcx",
360            "mov [rsp+32], r8",
361            "mov [rsp+40], r9",
362            "movaps [rsp+48], xmm0",
363            "movaps [rsp+64], xmm1",
364            "movaps [rsp+80], xmm2",
365            "movaps [rsp+96], xmm3",
366            "movaps [rsp+112], xmm4",
367            "movaps [rsp+128], xmm5",
368            "movaps [rsp+144], xmm6",
369            "movaps [rsp+160], xmm7",
370            "mov dword ptr [rsp+176], 16",
371            "mov dword ptr [rsp+180], 48",
372            "lea rax, [rsp+256]",
373            "mov [rsp+184], rax",
374            "lea rax, [rsp]",
375            "mov [rsp+192], rax",
376            "lea rdx, [rsp+176]",
377            "call xsltGenericErrorDefaultFuncV",
378            "add rsp, 240",
379            "add rsp, 8",
380            "ret",
381            options(noreturn),
382        );
383    }
384}
385
386/// Default value of the exported `xsltGenericError` data global.
387#[cfg(target_arch = "x86_64")]
388const XSLT_GENERIC_ERROR_DEFAULT: xmlGenericErrorFunc = unsafe {
389    // SAFETY: as above — ABI-identical code pointer.
390    core::mem::transmute::<
391        unsafe extern "C" fn() -> c_int,
392        unsafe extern "C" fn(*mut c_void, *const c_char),
393    >(xsltGenericErrorDefaultFunc)
394};
395
396/// The built-in default generic error handler (upstream
397/// `xmlGenericErrorDefaultFunc`), for use when a caller resets the handler
398/// with NULL. Only available on x86_64 (the variadic shim is SysV-specific);
399/// on other targets there is no default (resets leave the handler unset).
400pub fn default_generic_error_func() -> Option<xmlGenericErrorFunc> {
401    #[cfg(target_arch = "x86_64")]
402    {
403        Some(XML_GENERIC_ERROR_DEFAULT)
404    }
405    #[cfg(not(target_arch = "x86_64"))]
406    {
407        None
408    }
409}
410
411// ═══════════════════════════════════════════════════════════════════════════════
412// Static strings (upstream xmlstring.h / tree.c)
413// ═══════════════════════════════════════════════════════════════════════════════
414
415/// `const xmlChar xmlStringText[]` — "text"
416#[no_mangle]
417pub static xmlStringText: [xmlChar; 5] = [b't', b'e', b'x', b't', 0];
418
419/// `const xmlChar xmlStringTextNoenc[]` — "textnoenc"
420#[no_mangle]
421pub static xmlStringTextNoenc: [xmlChar; 9] =
422    [b't', b'e', b'x', b't', b'n', b'o', b'e', b'n', b'c'];
423
424/// `const xmlChar xmlStringComment[]` — "comment"
425#[no_mangle]
426pub static xmlStringComment: [xmlChar; 8] = [b'c', b'o', b'm', b'm', b'e', b'n', b't', 0];
427
428// ═══════════════════════════════════════════════════════════════════════════════
429// XPath numeric constants (upstream xpath.c)
430// ═══════════════════════════════════════════════════════════════════════════════
431
432/// `double xmlXPathNAN` — NaN
433#[no_mangle]
434pub static xmlXPathNAN: f64 = f64::NAN;
435
436/// `double xmlXPathPINF` — +infinity
437#[no_mangle]
438pub static xmlXPathPINF: f64 = f64::INFINITY;
439
440/// `double xmlXPathNINF` — -infinity
441#[no_mangle]
442pub static xmlXPathNINF: f64 = f64::NEG_INFINITY;
443
444// ═══════════════════════════════════════════════════════════════════════════════
445// libxslt globals (upstream xslt.c / xsltutils.c / documents.c / xslt.h)
446// ═══════════════════════════════════════════════════════════════════════════════
447// xsltMaxDepth / xsltMaxVars are exported from src/xslt/transform/mod.rs
448// (they are read by the transform engine).
449
450/// `const int xsltLibxmlVersion` = 21501 — the libxml2 version the system
451/// libxslt 1.1.45 was compiled against (upstream xslt.c `LIBXML_VERSION`;
452/// the system libxslt was built against libxml2 2.15.1). Byte-parity with
453/// the oracle DSO, read directly by `xsltproc -V`.
454#[no_mangle]
455pub static xsltLibxmlVersion: c_int = 21501;
456
457/// `const int xsltLibxsltVersion` = 10145 — the libxslt version
458/// (upstream xslt.c `XSLTPUBVAR const int xsltLibxsltVersion = LIBXSLT_VERSION`;
459/// oracle DSO symbol type R). Was previously exported as a function (T)
460/// — R-000167.
461#[no_mangle]
462pub static xsltLibxsltVersion: c_int = 10145;
463
464/// `const char *xsltEngineVersion` = "10145-GITv1.1.45" — the libxslt engine
465/// version string (upstream xslt.c `XSLTPUBVAR const char *xsltEngineVersion`;
466/// oracle DSO symbol type D). Was previously exported as a function (T)
467/// — R-000167. `static mut` follows the xmlParserVersion pattern (raw
468/// pointers are not Sync).
469#[no_mangle]
470pub static mut xsltEngineVersion: *const c_char = {
471    static S: [u8; 17] = *b"10145-GITv1.1.45\0";
472    S.as_ptr() as *const c_char
473};
474
475/// `const char *exsltLibraryVersion` = "825-GITv1.1.45" — the libexslt
476/// library version string (upstream exslt.c `EXSLTPUBVAR const char *`;
477/// oracle DSO symbol type D). Read by `xsltproc -V`.
478#[no_mangle]
479pub static mut exsltLibraryVersion: *const c_char = {
480    static S: [u8; 15] = *b"825-GITv1.1.45\0";
481    S.as_ptr() as *const c_char
482};
483
484/// `const int exsltLibexsltVersion` = 825 — the libexslt version
485/// (upstream exslt.h `EXSLTPUBVAR const int`; oracle DSO symbol type R).
486#[no_mangle]
487pub static exsltLibexsltVersion: c_int = 825;
488
489/// `const int exsltLibxmlVersion` = 21501 — the libxml2 version the system
490/// libexslt 0.8.25 was compiled against (oracle DSO symbol type R).
491#[no_mangle]
492pub static exsltLibxmlVersion: c_int = 21501;
493
494/// `const int exsltLibxsltVersion` = 10145 — the libxslt version the system
495/// libexslt 0.8.25 was compiled against (oracle DSO symbol type R).
496#[no_mangle]
497pub static exsltLibxsltVersion: c_int = 10145;
498
499/// `int xslDebugStatus` — the libxslt debugger status (upstream xsltutils.c
500/// `XSLTPUBVAR int xslDebugStatus;`, default XSLT_DEBUG_NONE = 0; oracle DSO
501/// symbol type B). Written by `xsltSetDebuggerStatus`; read by the transform
502/// engine's profiling gates (R-000165 DATA_MISSING closure).
503#[no_mangle]
504pub static mut xslDebugStatus: c_int = 0;
505
506/// `xmlGenericErrorFunc xsltGenericError` — the libxslt error callback.
507/// Upstream defaults to `xsltGenericErrorDefaultFunc` (xsltutils.c, a variadic
508/// stderr printer); the candidate's shim below reproduces it (R-000135
509/// divergence now closed).
510#[cfg(target_arch = "x86_64")]
511#[no_mangle]
512pub static mut xsltGenericError: Option<xmlGenericErrorFunc> = Some(XSLT_GENERIC_ERROR_DEFAULT);
513
514#[cfg(not(target_arch = "x86_64"))]
515#[no_mangle]
516pub static mut xsltGenericError: Option<xmlGenericErrorFunc> = None;
517
518/// `void *xsltGenericErrorContext` (default NULL)
519#[no_mangle]
520pub static mut xsltGenericErrorContext: *mut c_void = core::ptr::null_mut();
521
522/// `void *xsltGenericDebugContext` (default NULL)
523#[no_mangle]
524pub static mut xsltGenericDebugContext: *mut c_void = core::ptr::null_mut();
525
526/// `const xmlChar xsltExtMarker[]` — empty string used to mark extension
527/// nodes (upstream transform.c).
528#[no_mangle]
529pub static xsltExtMarker: [xmlChar; 1] = [0];
530
531/// `xsltDocLoaderFunc xsltDocDefaultLoader` — the document loader callback.
532/// Upstream defaults to `xsltDocDefaultLoaderFunc`; the candidate defaults
533/// to NULL and its internal loader path is used — documented safe
534/// divergence (residual R-000135).
535#[no_mangle]
536pub static mut xsltDocDefaultLoader: Option<
537    unsafe extern "C" fn(
538        *const xmlChar,
539        *mut c_void,
540        c_int,
541        *mut crate::abi::structs::_xsltStylesheet,
542        *mut crate::abi::structs::_xsltTransformContext,
543    ) -> *mut crate::abi::structs::_xmlDoc,
544> = None;
545
546// ═══════════════════════════════════════════════════════════════════════════════
547// I/O filename callback globals (upstream xmlIO.h)
548// ═══════════════════════════════════════════════════════════════════════════════
549
550/// `xmlParserInputBufferCreateFilenameFunc xmlParserInputBufferCreateFilenameValue`
551#[no_mangle]
552pub static mut xmlParserInputBufferCreateFilenameValue: Option<
553    unsafe extern "C" fn(*const c_char, c_int) -> *mut crate::abi::structs::_xmlParserInputBuffer,
554> = None;
555
556/// `xmlOutputBufferCreateFilenameFunc xmlOutputBufferCreateFilenameValue`
557#[no_mangle]
558pub static mut xmlOutputBufferCreateFilenameValue: Option<
559    unsafe extern "C" fn(
560        *const c_char,
561        crate::abi::structs::xmlCharEncodingHandlerPtr,
562        c_int,
563    ) -> *mut crate::abi::structs::_xmlOutputBuffer,
564> = None;
565
566// ═══════════════════════════════════════════════════════════════════════════════
567// Default SAX v1 handler structs + locator (upstream globals.c 2.15.3)
568// ═══════════════════════════════════════════════════════════════════════════════
569//
570// `const xmlSAXHandlerV1 xmlDefaultSAXHandler` and `htmlDefaultSAXHandler`
571// (parser.h / HTMLparser.h), plus `const xmlSAXLocator xmlDefaultSAXLocator`.
572// The handler instances reproduce the upstream initializer lists exactly
573// (globals.c); every referenced xmlSAX2* entry point is a real candidate
574// export.
575
576/// `const xmlSAXHandlerV1 xmlDefaultSAXHandler` (globals.c 2.15.3).
577#[no_mangle]
578pub static xmlDefaultSAXHandler: crate::abi::structs::_xmlSAXHandlerV1 =
579    crate::abi::structs::_xmlSAXHandlerV1 {
580        internalSubset: Some(crate::abi::exports_xml2::xmlSAX2InternalSubset),
581        isStandalone: Some(crate::abi::exports_xml2::xmlSAX2IsStandalone),
582        hasInternalSubset: Some(crate::abi::exports_xml2::xmlSAX2HasInternalSubset),
583        hasExternalSubset: Some(crate::abi::exports_xml2::xmlSAX2HasExternalSubset),
584        resolveEntity: Some(crate::abi::exports_xml2::xmlSAX2ResolveEntity),
585        getEntity: Some(crate::abi::exports_xml2::xmlSAX2GetEntity),
586        entityDecl: Some(crate::abi::exports_xml2::xmlSAX2EntityDecl),
587        notationDecl: Some(crate::abi::exports_xml2::xmlSAX2NotationDecl),
588        attributeDecl: Some(crate::abi::exports_xml2::xmlSAX2AttributeDecl),
589        elementDecl: Some(crate::abi::exports_xml2::xmlSAX2ElementDecl),
590        unparsedEntityDecl: Some(crate::abi::exports_xml2::xmlSAX2UnparsedEntityDecl),
591        setDocumentLocator: Some(crate::abi::exports_xml2::xmlSAX2SetDocumentLocator),
592        startDocument: Some(crate::abi::exports_xml2::xmlSAX2StartDocument),
593        endDocument: Some(crate::abi::exports_xml2::xmlSAX2EndDocument),
594        startElement: Some(crate::abi::exports_xml2::xmlSAX2StartElement),
595        endElement: Some(crate::abi::exports_xml2::xmlSAX2EndElement),
596        reference: Some(crate::abi::exports_xml2::xmlSAX2Reference),
597        characters: Some(crate::abi::exports_xml2::xmlSAX2Characters),
598        ignorableWhitespace: Some(crate::abi::exports_xml2::xmlSAX2IgnorableWhitespace),
599        processingInstruction: Some(crate::abi::exports_xml2::xmlSAX2ProcessingInstruction),
600        comment: Some(crate::abi::exports_xml2::xmlSAX2Comment),
601        warning: Some(crate::xml::errors::xmlParserWarning),
602        error: Some(crate::xml::errors::xmlParserError),
603        fatalError: Some(crate::xml::errors::xmlParserError),
604        getParameterEntity: Some(crate::abi::exports_xml2::xmlSAX2GetParameterEntity),
605        cdataBlock: Some(crate::abi::exports_xml2::xmlSAX2CDataBlock),
606        externalSubset: Some(crate::abi::exports_xml2::xmlSAX2ExternalSubset),
607        initialized: 1,
608    };
609
610/// `const xmlSAXHandlerV1 htmlDefaultSAXHandler` (globals.c 2.15.3).
611#[no_mangle]
612pub static htmlDefaultSAXHandler: crate::abi::structs::_xmlSAXHandlerV1 =
613    crate::abi::structs::_xmlSAXHandlerV1 {
614        internalSubset: Some(crate::abi::exports_xml2::xmlSAX2InternalSubset),
615        isStandalone: None,
616        hasInternalSubset: None,
617        hasExternalSubset: None,
618        resolveEntity: None,
619        getEntity: Some(crate::abi::exports_xml2::xmlSAX2GetEntity),
620        entityDecl: None,
621        notationDecl: None,
622        attributeDecl: None,
623        elementDecl: None,
624        unparsedEntityDecl: None,
625        setDocumentLocator: Some(crate::abi::exports_xml2::xmlSAX2SetDocumentLocator),
626        startDocument: Some(crate::abi::exports_xml2::xmlSAX2StartDocument),
627        endDocument: Some(crate::abi::exports_xml2::xmlSAX2EndDocument),
628        startElement: Some(crate::abi::exports_xml2::xmlSAX2StartElement),
629        endElement: Some(crate::abi::exports_xml2::xmlSAX2EndElement),
630        reference: None,
631        characters: Some(crate::abi::exports_xml2::xmlSAX2Characters),
632        ignorableWhitespace: Some(crate::abi::exports_xml2::xmlSAX2IgnorableWhitespace),
633        processingInstruction: Some(crate::abi::exports_xml2::xmlSAX2ProcessingInstruction),
634        comment: Some(crate::abi::exports_xml2::xmlSAX2Comment),
635        warning: Some(crate::xml::errors::xmlParserWarning),
636        error: Some(crate::xml::errors::xmlParserError),
637        fatalError: Some(crate::xml::errors::xmlParserError),
638        getParameterEntity: None,
639        cdataBlock: Some(crate::abi::exports_xml2::xmlSAX2CDataBlock),
640        externalSubset: None,
641        initialized: 1,
642    };
643
644/// `const xmlSAXLocator xmlDefaultSAXLocator` (globals.c 2.15.3).
645#[no_mangle]
646pub static xmlDefaultSAXLocator: crate::abi::callbacks::_xmlSAXLocator =
647    crate::abi::callbacks::_xmlSAXLocator {
648        getPublicId: Some(crate::abi::exports_xml2::xmlSAX2GetPublicId),
649        getSystemId: Some(crate::abi::exports_xml2::xmlSAX2GetSystemId),
650        getLineNumber: Some(crate::abi::exports_xml2::xmlSAX2GetLineNumber),
651        getColumnNumber: Some(crate::abi::exports_xml2::xmlSAX2GetColumnNumber),
652    };
653
654// ═══════════════════════════════════════════════════════════════════════════════
655// xmlLastError — the exported C global mirror of the thread-local error state
656// ═══════════════════════════════════════════════════════════════════════════════
657//
658// Upstream `XMLPUBVAR xmlError xmlLastError` (xmlerror.h). The candidate's
659// internal error state is thread-local (safe divergence, more correct than
660// upstream's racy global); this mirror is deep-copied on every error raise
661// and freed on reset, so C consumers observe upstream-equivalent lifetime
662// semantics. Residual R-000135.
663
664/// `xmlError xmlLastError` — most recent error (mirror).
665#[no_mangle]
666pub static mut xmlLastError: crate::abi::structs::_xmlError = crate::abi::structs::_xmlError {
667    domain: 0,
668    code: 0,
669    message: core::ptr::null_mut(),
670    level: 0,
671    file: core::ptr::null_mut(),
672    line: 0,
673    str1: core::ptr::null_mut(),
674    str2: core::ptr::null_mut(),
675    str3: core::ptr::null_mut(),
676    int1: 0,
677    int2: 0,
678    ctxt: core::ptr::null_mut(),
679    node: core::ptr::null_mut(),
680};
681
682/// Deep-copy `err` into the exported `xmlLastError` global.
683///
684/// The string fields are copied with `libc::malloc`/`memcpy` so the mirror
685/// owns them (previous mirror strings are freed first — upstream
686/// xmlResetError semantics).
687///
688/// # SAFETY
689///
690/// - `err` must point to a valid `_xmlError` whose string fields are
691///   NUL-terminated or NULL.
692pub unsafe fn sync_xml_last_error(err: *const crate::abi::structs::_xmlError) {
693    if err.is_null() {
694        return;
695    }
696    let _guard = LAST_ERROR_MIRROR_LOCK.lock();
697    unsafe { sync_xml_last_error_locked(err) };
698}
699
700/// Mirror write helper; caller must hold `LAST_ERROR_MIRROR_LOCK`.
701unsafe fn sync_xml_last_error_locked(err: *const crate::abi::structs::_xmlError) {
702    unsafe {
703        reset_xml_last_error_locked();
704        let src = &*err;
705        let dst = core::ptr::addr_of_mut!(xmlLastError);
706        (*dst).domain = src.domain;
707        (*dst).code = src.code;
708        (*dst).level = src.level;
709        (*dst).line = src.line;
710        (*dst).int1 = src.int1;
711        (*dst).int2 = src.int2;
712        (*dst).ctxt = src.ctxt;
713        (*dst).node = src.node;
714        (*dst).message = dup_cstr(src.message as *const u8);
715        (*dst).file = dup_cstr(src.file as *const u8);
716        (*dst).str1 = dup_cstr(src.str1 as *const u8);
717        (*dst).str2 = dup_cstr(src.str2 as *const u8);
718        (*dst).str3 = dup_cstr(src.str3 as *const u8);
719    }
720}
721
722/// Reset the exported `xmlLastError` global, freeing owned strings
723/// (upstream xmlResetError).
724///
725/// # SAFETY
726///
727/// Only call while no other thread is reading the global (upstream has the
728/// same race; documented).
729pub unsafe fn reset_xml_last_error() {
730    let _guard = LAST_ERROR_MIRROR_LOCK.lock();
731    unsafe { reset_xml_last_error_locked() };
732}
733
734/// Mirror reset helper; caller must hold `LAST_ERROR_MIRROR_LOCK`.
735unsafe fn reset_xml_last_error_locked() {
736    unsafe {
737        let dst = core::ptr::addr_of_mut!(xmlLastError);
738        if !(*dst).message.is_null() {
739            libc::free((*dst).message as *mut libc::c_void);
740        }
741        if !(*dst).file.is_null() {
742            libc::free((*dst).file as *mut libc::c_void);
743        }
744        if !(*dst).str1.is_null() {
745            libc::free((*dst).str1 as *mut libc::c_void);
746        }
747        if !(*dst).str2.is_null() {
748            libc::free((*dst).str2 as *mut libc::c_void);
749        }
750        if !(*dst).str3.is_null() {
751            libc::free((*dst).str3 as *mut libc::c_void);
752        }
753        *dst = crate::abi::structs::_xmlError {
754            domain: 0,
755            code: 0,
756            message: core::ptr::null_mut(),
757            level: 0,
758            file: core::ptr::null_mut(),
759            line: 0,
760            str1: core::ptr::null_mut(),
761            str2: core::ptr::null_mut(),
762            str3: core::ptr::null_mut(),
763            int1: 0,
764            int2: 0,
765            ctxt: core::ptr::null_mut(),
766            node: core::ptr::null_mut(),
767        };
768    }
769}
770
771/// Heap-copy a NUL-terminated string (NULL-safe).
772unsafe fn dup_cstr(s: *const u8) -> *mut c_char {
773    if s.is_null() {
774        return core::ptr::null_mut();
775    }
776    unsafe {
777        let len = libc::strlen(s as *const libc::c_char) as usize;
778        let p = libc::malloc(len + 1) as *mut u8;
779        if p.is_null() {
780            return core::ptr::null_mut();
781        }
782        libc::memcpy(p as *mut libc::c_void, s as *const libc::c_void, len + 1);
783        p as *mut c_char
784    }
785}
786
787// ═══════════════════════════════════════════════════════════════════════════════
788// Default accessor functions (upstream parser.h / tree.h / xmlsave.h)
789// ═══════════════════════════════════════════════════════════════════════════════
790//
791// The deprecated `xmlXxxDefault(v)` accessors set the corresponding global
792// when `v != 0` and return the (new) value — upstream semantics (they
793// predate the plain globals; the modern behavior is conditional-set-and-
794// return, see upstream globals.c / parser.c).
795
796/// Upstream `xmlKeepBlanksDefault(int v)`.
797#[no_mangle]
798pub unsafe extern "C" fn xmlKeepBlanksDefault(v: c_int) -> c_int {
799    unsafe {
800        if v != 0 {
801            xmlKeepBlanksDefaultValue = v;
802        }
803        xmlKeepBlanksDefaultValue
804    }
805}
806
807/// Upstream `xmlLineNumbersDefault(int v)`.
808#[no_mangle]
809pub unsafe extern "C" fn xmlLineNumbersDefault(v: c_int) -> c_int {
810    unsafe {
811        if v != 0 {
812            xmlLineNumbersDefaultValue = v;
813        }
814        xmlLineNumbersDefaultValue
815    }
816}
817
818/// Upstream `xmlSubstituteEntitiesDefault(int v)`.
819#[no_mangle]
820pub unsafe extern "C" fn xmlSubstituteEntitiesDefault(v: c_int) -> c_int {
821    unsafe {
822        if v != 0 {
823            xmlSubstituteEntitiesDefaultValue = v;
824        }
825        xmlSubstituteEntitiesDefaultValue
826    }
827}
828
829/// Upstream `xmlPedanticParserDefault(int v)`.
830#[no_mangle]
831pub unsafe extern "C" fn xmlPedanticParserDefault(v: c_int) -> c_int {
832    unsafe {
833        if v != 0 {
834            xmlPedanticParserDefaultValue = v;
835        }
836        xmlPedanticParserDefaultValue
837    }
838}
839
840/// Upstream `xmlDoValidityCheckingDefaultValue` accessor is the global
841/// itself; `xmlGetWarningsDefaultValue` likewise (no accessor functions
842/// exist for those in upstream 2.15).
843
844/// Upstream `xmlRegisterNodeDefault(xmlRegisterNodeFunc func)`.
845#[no_mangle]
846pub unsafe extern "C" fn xmlRegisterNodeDefault(
847    func: Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)>,
848) -> Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
849    unsafe {
850        // UPSTREAM-PARITY (tree.c): registering any callback arms the
851        // xmlRegisterCallbacks gate.
852        XML_REGISTER_CALLBACKS.store(true, core::sync::atomic::Ordering::Relaxed);
853        if func.is_some() {
854            xmlRegisterNodeDefaultValue = func;
855        }
856        xmlRegisterNodeDefaultValue
857    }
858}
859
860/// Upstream `xmlDeregisterNodeDefault(xmlDeregisterNodeFunc func)`.
861#[no_mangle]
862pub unsafe extern "C" fn xmlDeregisterNodeDefault(
863    func: Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)>,
864) -> Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
865    unsafe {
866        // UPSTREAM-PARITY (tree.c): registering any callback arms the
867        // xmlRegisterCallbacks gate.
868        XML_REGISTER_CALLBACKS.store(true, core::sync::atomic::Ordering::Relaxed);
869        if func.is_some() {
870            xmlDeregisterNodeDefaultValue = func;
871        }
872        xmlDeregisterNodeDefaultValue
873    }
874}
875
876/// Upstream `__xmlIndentTreeOutput(void)` (parser.h) — returns a pointer to
877/// the `xmlIndentTreeOutput` global.
878#[no_mangle]
879pub unsafe extern "C" fn __xmlIndentTreeOutput() -> *mut c_int {
880    unsafe { core::ptr::addr_of_mut!(xmlIndentTreeOutput) }
881}
882
883/// Upstream `__xmlSaveNoEmptyTags(void)` (parser.h) — returns a pointer to
884/// the `xmlSaveNoEmptyTags` global.
885#[no_mangle]
886pub unsafe extern "C" fn __xmlSaveNoEmptyTags() -> *mut c_int {
887    unsafe { core::ptr::addr_of_mut!(xmlSaveNoEmptyTags) }
888}
889
890/// Upstream `__xmlTreeIndentString(void)` (parser.h) — returns a pointer to
891/// the `xmlTreeIndentString` global.
892#[no_mangle]
893pub unsafe extern "C" fn __xmlTreeIndentString() -> *mut *const xmlChar {
894    unsafe { core::ptr::addr_of_mut!(xmlTreeIndentString) }
895}
896
897// ═══════════════════════════════════════════════════════════════════════════════
898// xmlThrDef* accessors (upstream threads.c / globals.c)
899// ═══════════════════════════════════════════════════════════════════════════════
900//
901// The deprecated `xmlThrDef*` family reads/writes the public globals with
902// the upstream semantics: when `v != 0` the global is set, and the (new)
903// value is returned. In upstream these were thread-local definitions before
904// the globals became plain variables; the modern behavior is exactly this
905// conditional-set-and-return on the global (see upstream globals.c).
906
907/// Upstream `xmlThrDefDoValidityCheckingDefaultValue(int v)`.
908#[no_mangle]
909pub unsafe extern "C" fn xmlThrDefDoValidityCheckingDefaultValue(v: c_int) -> c_int {
910    unsafe {
911        if v != 0 {
912            xmlDoValidityCheckingDefaultValue = v;
913        }
914        xmlDoValidityCheckingDefaultValue
915    }
916}
917
918/// Upstream `xmlThrDefGetWarningsDefaultValue(int v)`.
919#[no_mangle]
920pub unsafe extern "C" fn xmlThrDefGetWarningsDefaultValue(v: c_int) -> c_int {
921    unsafe {
922        if v != 0 {
923            xmlGetWarningsDefaultValue = v;
924        }
925        xmlGetWarningsDefaultValue
926    }
927}
928
929/// Upstream `xmlThrDefLoadExtDtdDefaultValue(int v)`.
930#[no_mangle]
931pub unsafe extern "C" fn xmlThrDefLoadExtDtdDefaultValue(v: c_int) -> c_int {
932    unsafe {
933        if v != 0 {
934            xmlLoadExtDtdDefaultValue = v;
935        }
936        xmlLoadExtDtdDefaultValue
937    }
938}
939
940/// Upstream `xmlThrDefPedanticParserDefaultValue(int v)`.
941#[no_mangle]
942pub unsafe extern "C" fn xmlThrDefPedanticParserDefaultValue(v: c_int) -> c_int {
943    unsafe {
944        if v != 0 {
945            xmlPedanticParserDefaultValue = v;
946        }
947        xmlPedanticParserDefaultValue
948    }
949}
950
951/// Upstream `xmlThrDefLineNumbersDefaultValue(int v)`.
952#[no_mangle]
953pub unsafe extern "C" fn xmlThrDefLineNumbersDefaultValue(v: c_int) -> c_int {
954    unsafe {
955        if v != 0 {
956            xmlLineNumbersDefaultValue = v;
957        }
958        xmlLineNumbersDefaultValue
959    }
960}
961
962/// Upstream `xmlThrDefKeepBlanksDefaultValue(int v)`.
963#[no_mangle]
964pub unsafe extern "C" fn xmlThrDefKeepBlanksDefaultValue(v: c_int) -> c_int {
965    unsafe {
966        if v != 0 {
967            xmlKeepBlanksDefaultValue = v;
968        }
969        xmlKeepBlanksDefaultValue
970    }
971}
972
973/// Upstream `xmlThrDefSubstituteEntitiesDefaultValue(int v)`.
974#[no_mangle]
975pub unsafe extern "C" fn xmlThrDefSubstituteEntitiesDefaultValue(v: c_int) -> c_int {
976    unsafe {
977        if v != 0 {
978            xmlSubstituteEntitiesDefaultValue = v;
979        }
980        xmlSubstituteEntitiesDefaultValue
981    }
982}
983
984/// Upstream `xmlThrDefParserDebugEntities(int v)`.
985#[no_mangle]
986pub unsafe extern "C" fn xmlThrDefParserDebugEntities(v: c_int) -> c_int {
987    unsafe {
988        if v != 0 {
989            xmlParserDebugEntities = v;
990        }
991        xmlParserDebugEntities
992    }
993}
994
995/// Upstream `xmlThrDefIndentTreeOutput(int v)`.
996#[no_mangle]
997pub unsafe extern "C" fn xmlThrDefIndentTreeOutput(v: c_int) -> c_int {
998    unsafe {
999        if v != 0 {
1000            xmlIndentTreeOutput = v;
1001        }
1002        xmlIndentTreeOutput
1003    }
1004}
1005
1006/// Upstream `xmlThrDefTreeIndentString(const char *v)` — sets the indent
1007/// string when non-NULL and returns the current pointer.
1008#[no_mangle]
1009pub unsafe extern "C" fn xmlThrDefTreeIndentString(v: *const c_char) -> *const c_char {
1010    unsafe {
1011        if !v.is_null() {
1012            xmlTreeIndentString = v as *const xmlChar;
1013        }
1014        xmlTreeIndentString as *const c_char
1015    }
1016}
1017
1018/// Upstream `xmlThrDefSaveNoEmptyTags(int v)`.
1019#[no_mangle]
1020pub unsafe extern "C" fn xmlThrDefSaveNoEmptyTags(v: c_int) -> c_int {
1021    unsafe {
1022        if v != 0 {
1023            xmlSaveNoEmptyTags = v;
1024        }
1025        xmlSaveNoEmptyTags
1026    }
1027}
1028
1029/// Upstream `xmlThrDefRegisterNodeDefault(xmlRegisterNodeFunc func)`.
1030#[no_mangle]
1031pub unsafe extern "C" fn xmlThrDefRegisterNodeDefault(
1032    func: Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)>,
1033) -> Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
1034    unsafe {
1035        if func.is_some() {
1036            xmlRegisterNodeDefaultValue = func;
1037        }
1038        xmlRegisterNodeDefaultValue
1039    }
1040}
1041
1042/// Upstream `xmlThrDefDeregisterNodeDefault(xmlDeregisterNodeFunc func)`.
1043#[no_mangle]
1044pub unsafe extern "C" fn xmlThrDefDeregisterNodeDefault(
1045    func: Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)>,
1046) -> Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
1047    unsafe {
1048        if func.is_some() {
1049            xmlDeregisterNodeDefaultValue = func;
1050        }
1051        xmlDeregisterNodeDefaultValue
1052    }
1053}
1054
1055/// Upstream `xmlThrDefSetGenericErrorFunc(void *ctx, xmlGenericErrorFunc func)`.
1056#[no_mangle]
1057pub unsafe extern "C" fn xmlThrDefSetGenericErrorFunc(
1058    ctx: *mut c_void,
1059    func: Option<xmlGenericErrorFunc>,
1060) {
1061    unsafe {
1062        xmlGenericErrorContext = ctx;
1063        xmlGenericError = func;
1064    }
1065}
1066
1067/// Upstream `xmlThrDefSetStructuredErrorFunc(void *ctx, xmlStructuredErrorFunc func)`.
1068#[no_mangle]
1069pub unsafe extern "C" fn xmlThrDefSetStructuredErrorFunc(
1070    ctx: *mut c_void,
1071    func: Option<xmlStructuredErrorFunc>,
1072) {
1073    unsafe {
1074        xmlStructuredErrorContext = ctx;
1075        xmlStructuredError = func;
1076    }
1077}
1078
1079/// Upstream `xmlThrDefDefaultBufferSize(int v)`.
1080#[no_mangle]
1081pub unsafe extern "C" fn xmlThrDefDefaultBufferSize(v: c_int) -> c_int {
1082    unsafe {
1083        if v != 0 {
1084            xmlDefaultBufferSize = v;
1085        }
1086        xmlDefaultBufferSize
1087    }
1088}
1089
1090/// Upstream `xmlThrDefBufferAllocScheme(xmlBufferAllocationScheme v)`.
1091#[no_mangle]
1092pub unsafe extern "C" fn xmlThrDefBufferAllocScheme(v: c_int) -> c_int {
1093    unsafe {
1094        if v != 0 {
1095            xmlBufferAllocScheme = v;
1096        }
1097        xmlBufferAllocScheme
1098    }
1099}
1100
1101/// Upstream `xmlThrDefParserInputBufferCreateFilenameDefault(...)`.
1102#[no_mangle]
1103pub unsafe extern "C" fn xmlThrDefParserInputBufferCreateFilenameDefault(
1104    func: Option<
1105        unsafe extern "C" fn(
1106            *const c_char,
1107            c_int,
1108        ) -> *mut crate::abi::structs::_xmlParserInputBuffer,
1109    >,
1110) -> Option<
1111    unsafe extern "C" fn(*const c_char, c_int) -> *mut crate::abi::structs::_xmlParserInputBuffer,
1112> {
1113    unsafe {
1114        if func.is_some() {
1115            xmlParserInputBufferCreateFilenameValue = func;
1116        }
1117        xmlParserInputBufferCreateFilenameValue
1118    }
1119}
1120
1121/// Upstream `xmlThrDefOutputBufferCreateFilenameDefault(...)`.
1122#[no_mangle]
1123pub unsafe extern "C" fn xmlThrDefOutputBufferCreateFilenameDefault(
1124    func: Option<
1125        unsafe extern "C" fn(
1126            *const c_char,
1127            crate::abi::structs::xmlCharEncodingHandlerPtr,
1128            c_int,
1129        ) -> *mut crate::abi::structs::_xmlOutputBuffer,
1130    >,
1131) -> Option<
1132    unsafe extern "C" fn(
1133        *const c_char,
1134        crate::abi::structs::xmlCharEncodingHandlerPtr,
1135        c_int,
1136    ) -> *mut crate::abi::structs::_xmlOutputBuffer,
1137> {
1138    unsafe {
1139        if func.is_some() {
1140            xmlOutputBufferCreateFilenameValue = func;
1141        }
1142        xmlOutputBufferCreateFilenameValue
1143    }
1144}
1145
1146// ═══════════════════════════════════════════════════════════════════════════════
1147// __xmlXxx() pointer accessors (upstream threads.c / globals.c)
1148// ═══════════════════════════════════════════════════════════════════════════════
1149// The deprecated thread-local API exports one `__xmlXxx(void)` accessor per
1150// global; each returns a pointer to the global so callers can read/write it.
1151
1152/// Upstream `__xmlBufferAllocScheme(void)` — returns a pointer to `xmlBufferAllocScheme`.
1153#[no_mangle]
1154pub unsafe extern "C" fn __xmlBufferAllocScheme() -> *mut c_int {
1155    // SAFETY: returning a pointer to an exported static; the caller may
1156    // read/write it exactly as with upstream's deprecated accessor.
1157    unsafe { core::ptr::addr_of_mut!(xmlBufferAllocScheme) }
1158}
1159
1160/// Upstream `__xmlDefaultBufferSize(void)` — returns a pointer to `xmlDefaultBufferSize`.
1161#[no_mangle]
1162pub unsafe extern "C" fn __xmlDefaultBufferSize() -> *mut c_int {
1163    // SAFETY: returning a pointer to an exported static; the caller may
1164    // read/write it exactly as with upstream's deprecated accessor.
1165    unsafe { core::ptr::addr_of_mut!(xmlDefaultBufferSize) }
1166}
1167
1168/// Upstream `__xmlDeregisterNodeDefaultValue(void)` — returns a pointer to `xmlDeregisterNodeDefaultValue`.
1169#[no_mangle]
1170pub unsafe extern "C" fn __xmlDeregisterNodeDefaultValue(
1171) -> *mut Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
1172    // SAFETY: returning a pointer to an exported static; the caller may
1173    // read/write it exactly as with upstream's deprecated accessor.
1174    unsafe { core::ptr::addr_of_mut!(xmlDeregisterNodeDefaultValue) }
1175}
1176
1177/// Upstream `__xmlDoValidityCheckingDefaultValue(void)` — returns a pointer to `xmlDoValidityCheckingDefaultValue`.
1178#[no_mangle]
1179pub unsafe extern "C" fn __xmlDoValidityCheckingDefaultValue() -> *mut c_int {
1180    // SAFETY: returning a pointer to an exported static; the caller may
1181    // read/write it exactly as with upstream's deprecated accessor.
1182    unsafe { core::ptr::addr_of_mut!(xmlDoValidityCheckingDefaultValue) }
1183}
1184
1185/// Upstream `__xmlGenericError(void)` — returns a pointer to `xmlGenericError`.
1186#[no_mangle]
1187pub unsafe extern "C" fn __xmlGenericError() -> *mut Option<xmlGenericErrorFunc> {
1188    // SAFETY: returning a pointer to an exported static; the caller may
1189    // read/write it exactly as with upstream's deprecated accessor.
1190    unsafe { core::ptr::addr_of_mut!(xmlGenericError) }
1191}
1192
1193/// Upstream `__xmlGenericErrorContext(void)` — returns a pointer to `xmlGenericErrorContext`.
1194#[no_mangle]
1195pub unsafe extern "C" fn __xmlGenericErrorContext() -> *mut *mut c_void {
1196    // SAFETY: returning a pointer to an exported static; the caller may
1197    // read/write it exactly as with upstream's deprecated accessor.
1198    unsafe { core::ptr::addr_of_mut!(xmlGenericErrorContext) }
1199}
1200
1201/// Upstream `__xmlGetWarningsDefaultValue(void)` — returns a pointer to `xmlGetWarningsDefaultValue`.
1202#[no_mangle]
1203pub unsafe extern "C" fn __xmlGetWarningsDefaultValue() -> *mut c_int {
1204    // SAFETY: returning a pointer to an exported static; the caller may
1205    // read/write it exactly as with upstream's deprecated accessor.
1206    unsafe { core::ptr::addr_of_mut!(xmlGetWarningsDefaultValue) }
1207}
1208
1209/// Upstream `__xmlKeepBlanksDefaultValue(void)` — returns a pointer to `xmlKeepBlanksDefaultValue`.
1210#[no_mangle]
1211pub unsafe extern "C" fn __xmlKeepBlanksDefaultValue() -> *mut c_int {
1212    // SAFETY: returning a pointer to an exported static; the caller may
1213    // read/write it exactly as with upstream's deprecated accessor.
1214    unsafe { core::ptr::addr_of_mut!(xmlKeepBlanksDefaultValue) }
1215}
1216
1217/// Upstream `__xmlLineNumbersDefaultValue(void)` — returns a pointer to `xmlLineNumbersDefaultValue`.
1218#[no_mangle]
1219pub unsafe extern "C" fn __xmlLineNumbersDefaultValue() -> *mut c_int {
1220    // SAFETY: returning a pointer to an exported static; the caller may
1221    // read/write it exactly as with upstream's deprecated accessor.
1222    unsafe { core::ptr::addr_of_mut!(xmlLineNumbersDefaultValue) }
1223}
1224
1225/// Upstream `__xmlLoadExtDtdDefaultValue(void)` — returns a pointer to `xmlLoadExtDtdDefaultValue`.
1226#[no_mangle]
1227pub unsafe extern "C" fn __xmlLoadExtDtdDefaultValue() -> *mut c_int {
1228    // SAFETY: returning a pointer to an exported static; the caller may
1229    // read/write it exactly as with upstream's deprecated accessor.
1230    unsafe { core::ptr::addr_of_mut!(xmlLoadExtDtdDefaultValue) }
1231}
1232
1233/// Upstream `__xmlOutputBufferCreateFilenameValue(void)` — returns a pointer to `xmlOutputBufferCreateFilenameValue`.
1234#[no_mangle]
1235pub unsafe extern "C" fn __xmlOutputBufferCreateFilenameValue() -> *mut Option<
1236    unsafe extern "C" fn(
1237        *const c_char,
1238        crate::abi::structs::xmlCharEncodingHandlerPtr,
1239        c_int,
1240    ) -> *mut crate::abi::structs::_xmlOutputBuffer,
1241> {
1242    // SAFETY: returning a pointer to an exported static; the caller may
1243    // read/write it exactly as with upstream's deprecated accessor.
1244    unsafe { core::ptr::addr_of_mut!(xmlOutputBufferCreateFilenameValue) }
1245}
1246
1247/// Upstream `__xmlParserDebugEntities(void)` — returns a pointer to `xmlParserDebugEntities`.
1248#[no_mangle]
1249pub unsafe extern "C" fn __xmlParserDebugEntities() -> *mut c_int {
1250    // SAFETY: returning a pointer to an exported static; the caller may
1251    // read/write it exactly as with upstream's deprecated accessor.
1252    unsafe { core::ptr::addr_of_mut!(xmlParserDebugEntities) }
1253}
1254
1255/// Upstream `__xmlParserInputBufferCreateFilenameValue(void)` — returns a pointer to `xmlParserInputBufferCreateFilenameValue`.
1256#[no_mangle]
1257pub unsafe extern "C" fn __xmlParserInputBufferCreateFilenameValue() -> *mut Option<
1258    unsafe extern "C" fn(*const c_char, c_int) -> *mut crate::abi::structs::_xmlParserInputBuffer,
1259> {
1260    // SAFETY: returning a pointer to an exported static; the caller may
1261    // read/write it exactly as with upstream's deprecated accessor.
1262    unsafe { core::ptr::addr_of_mut!(xmlParserInputBufferCreateFilenameValue) }
1263}
1264
1265/// Upstream `__xmlParserVersion(void)` — returns a pointer to `xmlParserVersion`.
1266#[no_mangle]
1267pub unsafe extern "C" fn __xmlParserVersion() -> *mut *const c_char {
1268    // SAFETY: returning a pointer to an exported static; the caller may
1269    // read/write it exactly as with upstream's deprecated accessor.
1270    unsafe { core::ptr::addr_of_mut!(xmlParserVersion) }
1271}
1272
1273/// Upstream `__xmlPedanticParserDefaultValue(void)` — returns a pointer to `xmlPedanticParserDefaultValue`.
1274#[no_mangle]
1275pub unsafe extern "C" fn __xmlPedanticParserDefaultValue() -> *mut c_int {
1276    // SAFETY: returning a pointer to an exported static; the caller may
1277    // read/write it exactly as with upstream's deprecated accessor.
1278    unsafe { core::ptr::addr_of_mut!(xmlPedanticParserDefaultValue) }
1279}
1280
1281/// Upstream `__xmlRegisterNodeDefaultValue(void)` — returns a pointer to `xmlRegisterNodeDefaultValue`.
1282#[no_mangle]
1283pub unsafe extern "C" fn __xmlRegisterNodeDefaultValue(
1284) -> *mut Option<unsafe extern "C" fn(*mut crate::abi::structs::_xmlNode)> {
1285    // SAFETY: returning a pointer to an exported static; the caller may
1286    // read/write it exactly as with upstream's deprecated accessor.
1287    unsafe { core::ptr::addr_of_mut!(xmlRegisterNodeDefaultValue) }
1288}
1289
1290/// Upstream `__xmlStructuredError(void)` — returns a pointer to `xmlStructuredError`.
1291#[no_mangle]
1292pub unsafe extern "C" fn __xmlStructuredError() -> *mut Option<xmlStructuredErrorFunc> {
1293    // SAFETY: returning a pointer to an exported static; the caller may
1294    // read/write it exactly as with upstream's deprecated accessor.
1295    unsafe { core::ptr::addr_of_mut!(xmlStructuredError) }
1296}
1297
1298/// Upstream `__xmlStructuredErrorContext(void)` — returns a pointer to `xmlStructuredErrorContext`.
1299#[no_mangle]
1300pub unsafe extern "C" fn __xmlStructuredErrorContext() -> *mut *mut c_void {
1301    // SAFETY: returning a pointer to an exported static; the caller may
1302    // read/write it exactly as with upstream's deprecated accessor.
1303    unsafe { core::ptr::addr_of_mut!(xmlStructuredErrorContext) }
1304}
1305
1306/// Upstream `__xmlSubstituteEntitiesDefaultValue(void)` — returns a pointer to `xmlSubstituteEntitiesDefaultValue`.
1307#[no_mangle]
1308pub unsafe extern "C" fn __xmlSubstituteEntitiesDefaultValue() -> *mut c_int {
1309    // SAFETY: returning a pointer to an exported static; the caller may
1310    // read/write it exactly as with upstream's deprecated accessor.
1311    unsafe { core::ptr::addr_of_mut!(xmlSubstituteEntitiesDefaultValue) }
1312}
1313
1314// ═══════════════════════════════════════════════════════════════════════════════
1315// Regression court — xmlLastError mirror concurrency (11.1-X)
1316// ═══════════════════════════════════════════════════════════════════════════════
1317//
1318// R-000135 discovery during 11.1-X: the exported `xmlLastError` mirror was
1319// deep-copied and freed without synchronization, so concurrent error raises
1320// on different threads double-freed the mirror strings. The parallel lib
1321// test suite observed this as `double free or corruption (!prev)` aborts
1322// (xml::errors tests racing with any other raising thread). The writers are
1323// serialized via LAST_ERROR_MIRROR_LOCK; these courts hammer the exact
1324// interleavings and must complete without crashing.
1325
1326#[cfg(test)]
1327mod tests {
1328    use super::*;
1329    use crate::abi::allocator::xmlMallocImpl;
1330    use crate::abi::structs::_xmlError;
1331    use crate::xml::globals;
1332    use core::ptr;
1333
1334    /// Allocate a NUL-terminated C string owned by xmlMallocImpl (the same
1335    /// allocator the thread-local error slot uses).
1336    unsafe fn alloc_cstr(s: &str) -> *mut c_char {
1337        let bytes = s.as_bytes();
1338        let p = unsafe { xmlMallocImpl(bytes.len() + 1) as *mut c_char };
1339        assert!(!p.is_null(), "alloc_cstr: xmlMallocImpl failed");
1340        unsafe {
1341            ptr::copy_nonoverlapping(bytes.as_ptr(), p as *mut u8, bytes.len());
1342            *((p as *mut u8).add(bytes.len())) = 0;
1343        }
1344        p
1345    }
1346
1347    /// Build an owned `_xmlError` with distinct string fields.
1348    unsafe fn build_error(tag: &str) -> _xmlError {
1349        _xmlError {
1350            domain: 1,
1351            code: 2,
1352            message: unsafe { alloc_cstr(&format!("msg {tag}")) },
1353            level: 3,
1354            file: unsafe { alloc_cstr(&format!("file {tag}")) },
1355            line: 4,
1356            str1: unsafe { alloc_cstr(&format!("str1 {tag}")) },
1357            str2: ptr::null_mut(),
1358            str3: ptr::null_mut(),
1359            int1: 0,
1360            int2: 0,
1361            ctxt: ptr::null_mut(),
1362            node: ptr::null_mut(),
1363        }
1364    }
1365
1366    /// Concurrent sync/reset hammer: one thread raises errors while another
1367    /// resets. Before the mirror lock this double-freed the shared strings;
1368    /// the test crashes (SIGABRT) under the old code and passes now.
1369    #[test]
1370    fn test_last_error_mirror_concurrent_sync_reset() {
1371        let sync = std::thread::spawn(|| {
1372            for i in 0..400 {
1373                unsafe { globals::set_last_error(build_error(&format!("sync {i}"))) };
1374            }
1375        });
1376        let reset = std::thread::spawn(|| {
1377            for _ in 0..400 {
1378                globals::reset_last_error();
1379            }
1380        });
1381        sync.join().unwrap();
1382        reset.join().unwrap();
1383        // Leave the mirror in a clean state for later tests. (No thread-local
1384        // assertion: the harness reuses OS threads across tests, so a prior
1385        // test's error may legitimately live in this thread's slot.)
1386        globals::reset_last_error();
1387    }
1388
1389    /// Many threads raising concurrently (the full parallel-suite shape that
1390    /// originally aborted in `test_encode_entities_reentrant_*` victims).
1391    #[test]
1392    fn test_last_error_mirror_many_threads() {
1393        let mut handles = Vec::new();
1394        for t in 0..8 {
1395            handles.push(std::thread::spawn(move || {
1396                for i in 0..150 {
1397                    unsafe { globals::set_last_error(build_error(&format!("t{t} i{i}"))) };
1398                    if i % 7 == 0 {
1399                        globals::reset_last_error();
1400                    }
1401                }
1402            }));
1403        }
1404        for h in handles {
1405            h.join().unwrap();
1406        }
1407        globals::reset_last_error();
1408    }
1409}