libbz2_rs_sys/
high_level.rs

1#![allow(unsafe_op_in_unsafe_fn)]
2
3use core::ffi::{c_char, c_int, c_uint, c_void, CStr};
4use core::{mem, ptr};
5
6use libc::FILE;
7use libc::{fclose, fdopen, ferror, fflush, fgetc, fopen, fread, fwrite, ungetc};
8
9use crate::allocator::Allocator;
10use crate::bzlib::prefix;
11use crate::bzlib::BZ_MAX_UNUSED_U32;
12use crate::bzlib::{bz_stream, BZ2_bzCompressEnd, BZ2_bzDecompressEnd};
13use crate::bzlib::{Action, BzStream, ReturnCode};
14use crate::bzlib::{
15    BZ2_bzCompressHelp, BZ2_bzCompressInitHelp, BZ2_bzDecompressHelp, BZ2_bzDecompressInitHelp,
16};
17use crate::BZ_MAX_UNUSED;
18
19#[cfg(doc)]
20use crate::{
21    BZ2_bzCompressInit, BZ2_bzDecompressInit, BZ_CONFIG_ERROR, BZ_DATA_ERROR, BZ_DATA_ERROR_MAGIC,
22    BZ_FINISH, BZ_FINISH_OK, BZ_FLUSH, BZ_FLUSH_OK, BZ_IO_ERROR, BZ_MEM_ERROR, BZ_OK,
23    BZ_OUTBUFF_FULL, BZ_PARAM_ERROR, BZ_RUN, BZ_RUN_OK, BZ_SEQUENCE_ERROR, BZ_STREAM_END,
24    BZ_UNEXPECTED_EOF,
25};
26
27// FIXME remove this
28#[cfg(not(target_os = "windows"))]
29extern "C" {
30    #[cfg_attr(target_os = "macos", link_name = "__stdinp")]
31    static mut stdin: *mut FILE;
32    #[cfg_attr(target_os = "macos", link_name = "__stdoutp")]
33    static mut stdout: *mut FILE;
34}
35
36#[cfg(target_os = "windows")]
37extern "C" {
38    fn __acrt_iob_func(idx: libc::c_uint) -> *mut FILE;
39}
40
41#[cfg(not(target_os = "windows"))]
42macro_rules! STDIN {
43    () => {
44        stdin
45    };
46}
47
48#[cfg(target_os = "windows")]
49macro_rules! STDIN {
50    () => {
51        __acrt_iob_func(0)
52    };
53}
54
55#[cfg(not(target_os = "windows"))]
56macro_rules! STDOUT {
57    () => {
58        stdout
59    };
60}
61
62#[cfg(target_os = "windows")]
63macro_rules! STDOUT {
64    () => {
65        __acrt_iob_func(1)
66    };
67}
68
69/// Abstract handle to a `.bz2` file.
70///
71/// This type is created by:
72///
73/// - [`BZ2_bzReadOpen`]
74/// - [`BZ2_bzWriteOpen`]
75/// - [`BZ2_bzopen`]
76///
77/// And destructed by:
78///
79/// - [`BZ2_bzReadClose`]
80/// - [`BZ2_bzWriteClose`]
81/// - [`BZ2_bzclose`]
82#[allow(non_camel_case_types)]
83pub struct BZFILE {
84    handle: *mut FILE,
85    buf: [i8; BZ_MAX_UNUSED as usize],
86    bufN: i32,
87    strm: bz_stream,
88    lastErr: ReturnCode,
89    operation: Operation,
90    initialisedOk: bool,
91}
92
93unsafe fn myfeof(f: *mut FILE) -> bool {
94    let c = fgetc(f);
95    if c == -1 {
96        return true;
97    }
98
99    ungetc(c, f);
100
101    false
102}
103
104macro_rules! BZ_SETERR_RAW {
105    ($bzerror:expr, $bzf:expr, $return_code:expr) => {
106        if let Some(bzerror) = $bzerror.as_deref_mut() {
107            *bzerror = $return_code as c_int;
108        }
109
110        if let Some(bzf) = $bzf.as_deref_mut() {
111            bzf.lastErr = $return_code;
112        }
113    };
114}
115
116macro_rules! BZ_SETERR {
117    ($bzerror:expr, $bzf:expr, $return_code:expr) => {
118        if let Some(bzerror) = $bzerror.as_deref_mut() {
119            *bzerror = $return_code as c_int;
120        }
121
122        $bzf.lastErr = $return_code;
123    };
124}
125
126/// Prepare to write compressed data to a file handle.
127///
128/// The file handle `f` should refer to a file which has been opened for writing, and for which the error indicator `libc::ferror(f)` is not set.
129///
130/// For the meaning of parameters `blockSize100k`, `verbosity` and `workFactor`, see [`BZ2_bzCompressInit`].
131///
132/// # Returns
133///
134/// - if `*bzerror` is [`BZ_OK`], a valid pointer to an abstract `BZFILE`
135/// - otherwise `NULL`
136///
137/// # Possible assignments to `bzerror`
138///
139/// - [`BZ_PARAM_ERROR`] if any of
140///     - `f.is_null`
141///     - `!(1..=9).contains(&blockSize100k)`
142///     - `!(0..=4).contains(&verbosity)`
143///     - `!(0..=250).contains(&workFactor)`
144/// - [`BZ_CONFIG_ERROR`] if no default allocator is configured
145/// - [`BZ_IO_ERROR`] if `libc::ferror(f)` is nonzero
146/// - [`BZ_MEM_ERROR`] if insufficient memory is available
147/// - [`BZ_OK`] otherwise
148///
149/// # Safety
150///
151/// The caller must guarantee that
152///
153/// * `bzerror` satisfies the requirements of [`pointer::as_mut`]
154/// * Either
155///     - `f` is `NULL`
156///     - `f` a valid pointer to a `FILE`
157///
158/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
159#[export_name = prefix!(BZ2_bzWriteOpen)]
160pub unsafe extern "C" fn BZ2_bzWriteOpen(
161    bzerror: *mut c_int,
162    f: *mut FILE,
163    blockSize100k: c_int,
164    verbosity: c_int,
165    workFactor: c_int,
166) -> *mut BZFILE {
167    BZ2_bzWriteOpenHelp(bzerror.as_mut(), f, blockSize100k, verbosity, workFactor)
168}
169
170unsafe fn BZ2_bzWriteOpenHelp(
171    mut bzerror: Option<&mut c_int>,
172    f: *mut FILE,
173    blockSize100k: c_int,
174    verbosity: c_int,
175    mut workFactor: c_int,
176) -> *mut BZFILE {
177    let mut bzf: Option<&mut BZFILE> = None;
178
179    BZ_SETERR_RAW!(bzerror, bzf, ReturnCode::BZ_OK);
180
181    if f.is_null()
182        || !(1..=9).contains(&blockSize100k)
183        || !(0..=250).contains(&workFactor)
184        || !(0..=4).contains(&verbosity)
185    {
186        BZ_SETERR_RAW!(bzerror, bzf, ReturnCode::BZ_PARAM_ERROR);
187        return ptr::null_mut();
188    }
189
190    if ferror(f) != 0 {
191        BZ_SETERR_RAW!(bzerror, bzf, ReturnCode::BZ_IO_ERROR);
192        return ptr::null_mut();
193    }
194
195    let Some(allocator) = Allocator::DEFAULT else {
196        BZ_SETERR_RAW!(bzerror, bzf, ReturnCode::BZ_CONFIG_ERROR);
197        return ptr::null_mut();
198    };
199
200    let Some(bzf) = allocator.allocate_zeroed::<BZFILE>(1) else {
201        BZ_SETERR_RAW!(bzerror, bzf, ReturnCode::BZ_MEM_ERROR);
202        return ptr::null_mut();
203    };
204
205    // SAFETY: bzf is non-null and correctly initalized
206    let bzf = unsafe { &mut *bzf };
207
208    BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_OK);
209
210    bzf.initialisedOk = false;
211    bzf.bufN = 0;
212    bzf.handle = f;
213    bzf.operation = Operation::Writing;
214    bzf.strm.bzalloc = None;
215    bzf.strm.bzfree = None;
216    bzf.strm.opaque = ptr::null_mut();
217
218    if workFactor == 0 {
219        workFactor = 30;
220    }
221
222    match BZ2_bzCompressInitHelp(
223        BzStream::from_mut(&mut bzf.strm),
224        blockSize100k,
225        verbosity,
226        workFactor,
227    ) {
228        ReturnCode::BZ_OK => {
229            bzf.strm.avail_in = 0;
230            bzf.initialisedOk = true;
231
232            bzf as *mut BZFILE
233        }
234        error => {
235            BZ_SETERR!(bzerror, bzf, error);
236            allocator.deallocate(bzf, 1);
237
238            ptr::null_mut()
239        }
240    }
241}
242
243/// Absorbs `len` bytes from the buffer `buf`, eventually to be compressed and written to the file.
244///
245/// # Returns
246///
247/// # Possible assignments to `bzerror`
248///
249/// - [`BZ_PARAM_ERROR`] if any of
250///     - `b.is_null()`
251///     - `buf.is_null()`
252///     - `len < 0`
253/// - [`BZ_SEQUENCE_ERROR`] if b was opened with [`BZ2_bzReadOpen`]
254/// - [`BZ_IO_ERROR`] if there is an error writing to the compressed file
255/// - [`BZ_OK`] otherwise
256///
257/// # Safety
258///
259/// The caller must guarantee that
260///
261/// * `bzerror` satisfies the requirements of [`pointer::as_mut`]
262/// * Either
263///     - `b` is `NULL`
264///     - `b` is initialized with [`BZ2_bzWriteOpen`] or [`BZ2_bzReadOpen`]
265/// * Either
266///     - `buf` is `NULL`
267///     - `buf` is writable for `len` bytes
268///
269/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
270#[export_name = prefix!(BZ2_bzWrite)]
271pub unsafe extern "C" fn BZ2_bzWrite(
272    bzerror: *mut c_int,
273    b: *mut BZFILE,
274    buf: *const c_void,
275    len: c_int,
276) {
277    BZ2_bzWriteHelp(bzerror.as_mut(), b.as_mut(), buf, len)
278}
279
280unsafe fn BZ2_bzWriteHelp(
281    mut bzerror: Option<&mut c_int>,
282    mut b: Option<&mut BZFILE>,
283    buf: *const c_void,
284    len: c_int,
285) {
286    BZ_SETERR_RAW!(bzerror, b, ReturnCode::BZ_OK);
287
288    let Some(bzf) = b.as_mut() else {
289        BZ_SETERR_RAW!(bzerror, b, ReturnCode::BZ_PARAM_ERROR);
290        return;
291    };
292
293    if buf.is_null() || len < 0 as c_int {
294        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_PARAM_ERROR);
295        return;
296    }
297
298    if !matches!(bzf.operation, Operation::Writing) {
299        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_SEQUENCE_ERROR);
300        return;
301    }
302
303    if ferror(bzf.handle) != 0 {
304        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_IO_ERROR);
305        return;
306    }
307
308    if len == 0 {
309        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_OK);
310        return;
311    }
312
313    bzf.strm.avail_in = len as c_uint;
314    bzf.strm.next_in = buf.cast::<c_char>();
315
316    loop {
317        bzf.strm.avail_out = BZ_MAX_UNUSED_U32;
318        bzf.strm.next_out = bzf.buf.as_mut_ptr().cast::<c_char>();
319        match BZ2_bzCompressHelp(
320            unsafe { BzStream::from_mut(&mut bzf.strm) },
321            Action::Run as c_int,
322        ) {
323            ReturnCode::BZ_RUN_OK => {
324                if bzf.strm.avail_out < BZ_MAX_UNUSED_U32 {
325                    let n1 = (BZ_MAX_UNUSED_U32 - bzf.strm.avail_out) as usize;
326                    let n2 = fwrite(
327                        bzf.buf.as_mut_ptr().cast::<c_void>(),
328                        mem::size_of::<u8>(),
329                        n1,
330                        bzf.handle,
331                    );
332                    if n1 != n2 || ferror(bzf.handle) != 0 {
333                        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_IO_ERROR);
334                        return;
335                    }
336                }
337                if bzf.strm.avail_in == 0 {
338                    BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_OK);
339                    return;
340                }
341            }
342            error => {
343                BZ_SETERR!(bzerror, bzf, error);
344                return;
345            }
346        }
347    }
348}
349
350/// Compresses and flushes to the compressed file all data so far supplied by [`BZ2_bzWrite`].
351///
352/// The logical end-of-stream markers are also written, so subsequent calls to [`BZ2_bzWrite`] are illegal.
353/// All memory associated with the compressed file `b` is released. [`libc::fflush`] is called on the compressed file,
354/// but it is not [`libc::fclose`]'d.
355///
356/// If [`BZ2_bzWriteClose`] is called to clean up after an error, the only action is to release the memory.
357/// The library records the error codes issued by previous calls, so this situation will be detected automatically.
358/// There is no attempt to complete the compression operation, nor to [`libc::fflush`] the compressed file.
359/// You can force this behaviour to happen even in the case of no error, by passing a nonzero value to `abandon`.
360///
361/// # Possible assignments to `bzerror`
362///
363/// - [`BZ_CONFIG_ERROR`] if no default allocator is configured
364/// - [`BZ_SEQUENCE_ERROR`] if b was opened with [`BZ2_bzWriteOpen`]
365/// - [`BZ_IO_ERROR`] if there is an error writing to the compressed file
366/// - [`BZ_OK`] otherwise
367///
368/// # Safety
369///
370/// The caller must guarantee that
371///
372/// * `bzerror` satisfies the requirements of [`pointer::as_mut`]
373/// * Either
374///     - `b` is `NULL`
375///     - `b` is initialized with [`BZ2_bzReadOpen`] or [`BZ2_bzWriteOpen`]
376/// * `nbytes_in` satisfies the requirements of [`pointer::as_mut`]
377/// * `nbytes_out` satisfies the requirements of [`pointer::as_mut`]
378///
379/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
380#[export_name = prefix!(BZ2_bzWriteClose)]
381pub unsafe extern "C" fn BZ2_bzWriteClose(
382    bzerror: *mut c_int,
383    b: *mut BZFILE,
384    abandon: c_int,
385    nbytes_in: *mut c_uint,
386    nbytes_out: *mut c_uint,
387) {
388    BZ2_bzWriteCloseHelp(
389        bzerror.as_mut(),
390        b.as_mut(),
391        abandon,
392        nbytes_in.as_mut(),
393        nbytes_out.as_mut(),
394    )
395}
396
397unsafe fn BZ2_bzWriteCloseHelp(
398    bzerror: Option<&mut c_int>,
399    b: Option<&mut BZFILE>,
400    abandon: c_int,
401    nbytes_in: Option<&mut c_uint>,
402    nbytes_out: Option<&mut c_uint>,
403) {
404    BZ2_bzWriteClose64Help(bzerror, b, abandon, nbytes_in, None, nbytes_out, None);
405}
406
407/// Compresses and flushes to the compressed file all data so far supplied by [`BZ2_bzWrite`].
408///
409/// The logical end-of-stream markers are also written, so subsequent calls to [`BZ2_bzWrite`] are illegal.
410/// All memory associated with the compressed file `b` is released. [`libc::fflush`] is called on the compressed file,
411/// but it is not [`libc::fclose`]'d.
412///
413/// If [`BZ2_bzWriteClose64`] is called to clean up after an error, the only action is to release the memory.
414/// The library records the error codes issued by previous calls, so this situation will be detected automatically.
415/// There is no attempt to complete the compression operation, nor to [`libc::fflush`] the compressed file.
416/// You can force this behaviour to happen even in the case of no error, by passing a nonzero value to `abandon`.
417///
418/// # Possible assignments to `bzerror`
419///
420/// - [`BZ_CONFIG_ERROR`] if no default allocator is configured
421/// - [`BZ_SEQUENCE_ERROR`] if b was opened with [`BZ2_bzWriteOpen`]
422/// - [`BZ_IO_ERROR`] if there is an error writing to the compressed file
423/// - [`BZ_OK`] otherwise
424///
425/// # Safety
426///
427/// The caller must guarantee that
428///
429/// * `bzerror` satisfies the requirements of [`pointer::as_mut`]
430/// * Either
431///     - `b` is `NULL`
432///     - `b` is initialized with [`BZ2_bzReadOpen`] or [`BZ2_bzWriteOpen`]
433/// * `nbytes_in_lo32: satisfies the requirements of [`pointer::as_mut`]
434/// * `nbytes_in_hi32: satisfies the requirements of [`pointer::as_mut`]
435/// * `nbytes_out_lo32: satisfies the requirements of [`pointer::as_mut`]
436/// * `nbytes_out_hi32: satisfies the requirements of [`pointer::as_mut`]
437///
438/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
439#[export_name = prefix!(BZ2_bzWriteClose64)]
440pub unsafe extern "C" fn BZ2_bzWriteClose64(
441    bzerror: *mut c_int,
442    b: *mut BZFILE,
443    abandon: c_int,
444    nbytes_in_lo32: *mut c_uint,
445    nbytes_in_hi32: *mut c_uint,
446    nbytes_out_lo32: *mut c_uint,
447    nbytes_out_hi32: *mut c_uint,
448) {
449    BZ2_bzWriteClose64Help(
450        bzerror.as_mut(),
451        b.as_mut(),
452        abandon,
453        nbytes_in_lo32.as_mut(),
454        nbytes_in_hi32.as_mut(),
455        nbytes_out_lo32.as_mut(),
456        nbytes_out_hi32.as_mut(),
457    )
458}
459
460unsafe fn BZ2_bzWriteClose64Help(
461    mut bzerror: Option<&mut c_int>,
462    mut b: Option<&mut BZFILE>,
463    abandon: c_int,
464    mut nbytes_in_lo32: Option<&mut c_uint>,
465    mut nbytes_in_hi32: Option<&mut c_uint>,
466    mut nbytes_out_lo32: Option<&mut c_uint>,
467    mut nbytes_out_hi32: Option<&mut c_uint>,
468) {
469    let Some(bzf) = b else {
470        BZ_SETERR_RAW!(bzerror, b, ReturnCode::BZ_PARAM_ERROR);
471        return;
472    };
473
474    if !matches!(bzf.operation, Operation::Writing) {
475        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_SEQUENCE_ERROR);
476        return;
477    }
478
479    if ferror(bzf.handle) != 0 {
480        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_IO_ERROR);
481        return;
482    }
483
484    if let Some(nbytes_in_lo32) = nbytes_in_lo32.as_deref_mut() {
485        *nbytes_in_lo32 = 0
486    }
487    if let Some(nbytes_in_hi32) = nbytes_in_hi32.as_deref_mut() {
488        *nbytes_in_hi32 = 0;
489    }
490    if let Some(nbytes_out_lo32) = nbytes_out_lo32.as_deref_mut() {
491        *nbytes_out_lo32 = 0;
492    }
493    if let Some(nbytes_out_hi32) = nbytes_out_hi32.as_deref_mut() {
494        *nbytes_out_hi32 = 0;
495    }
496
497    if abandon == 0 && bzf.lastErr == ReturnCode::BZ_OK {
498        loop {
499            bzf.strm.avail_out = BZ_MAX_UNUSED_U32;
500            bzf.strm.next_out = (bzf.buf).as_mut_ptr().cast::<c_char>();
501            match BZ2_bzCompressHelp(BzStream::from_mut(&mut bzf.strm), 2 as c_int) {
502                ret @ (ReturnCode::BZ_FINISH_OK | ReturnCode::BZ_STREAM_END) => {
503                    if bzf.strm.avail_out < BZ_MAX_UNUSED_U32 {
504                        let n1 = (BZ_MAX_UNUSED_U32 - bzf.strm.avail_out) as usize;
505                        let n2 = fwrite(
506                            bzf.buf.as_mut_ptr().cast::<c_void>(),
507                            mem::size_of::<u8>(),
508                            n1,
509                            bzf.handle,
510                        );
511                        if n1 != n2 || ferror(bzf.handle) != 0 {
512                            BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_IO_ERROR);
513                        }
514                    }
515
516                    if let ReturnCode::BZ_STREAM_END = ret {
517                        break;
518                    }
519                }
520                ret => {
521                    BZ_SETERR!(bzerror, bzf, ret);
522                    return;
523                }
524            }
525        }
526    }
527
528    if abandon == 0 && ferror(bzf.handle) == 0 {
529        fflush(bzf.handle);
530        if ferror(bzf.handle) != 0 {
531            BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_IO_ERROR);
532            return;
533        }
534    }
535
536    if let Some(nbytes_in_lo32) = nbytes_in_lo32 {
537        *nbytes_in_lo32 = bzf.strm.total_in_lo32;
538    }
539    if let Some(nbytes_in_hi32) = nbytes_in_hi32 {
540        *nbytes_in_hi32 = bzf.strm.total_in_hi32;
541    }
542    if let Some(nbytes_out_lo32) = nbytes_out_lo32 {
543        *nbytes_out_lo32 = bzf.strm.total_out_lo32;
544    }
545    if let Some(nbytes_out_hi32) = nbytes_out_hi32 {
546        *nbytes_out_hi32 = bzf.strm.total_out_hi32;
547    }
548
549    BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_OK);
550
551    BZ2_bzCompressEnd(&mut bzf.strm);
552
553    let Some(allocator) = Allocator::DEFAULT else {
554        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_CONFIG_ERROR);
555        return;
556    };
557
558    allocator.deallocate(bzf, 1);
559}
560
561/// Prepare to read compressed data from a file handle.
562///
563/// The file handle `f` should refer to a file which has been opened for reading, and for which the error indicator `libc::ferror(f)` is not set.
564///
565/// If small is 1, the library will try to decompress using less memory, at the expense of speed.
566///
567/// For reasons explained below, [`BZ2_bzRead`] will decompress the nUnused bytes starting at unused, before starting to read from the file `f`.
568/// At most [`BZ_MAX_UNUSED`] bytes may be supplied like this. If this facility is not required, you should pass NULL and 0 for unused and nUnused respectively.
569///
570/// For the meaning of parameters `small`, `verbosity`, see [`BZ2_bzDecompressInit`].
571///
572/// Because the compression ratio of the compressed data cannot be known in advance,
573/// there is no easy way to guarantee that the output buffer will be big enough.
574/// You may of course make arrangements in your code to record the size of the uncompressed data,
575/// but such a mechanism is beyond the scope of this library.
576///
577/// # Returns
578///
579/// - if `*bzerror` is [`BZ_OK`], a valid pointer to an abstract `BZFILE`
580/// - otherwise `NULL`
581///
582/// # Possible assignments to `bzerror`
583///
584/// - [`BZ_PARAM_ERROR`] if any of
585///     - `(unused.is_null() && nUnused != 0)`
586///     - `(!unused.is_null() && !(0..=BZ_MAX_UNUSED).contains(&nUnused))`
587///     - `!(0..=1).contains(&small)`
588///     - `!(0..=4).contains(&verbosity)`
589/// - [`BZ_CONFIG_ERROR`] if no default allocator is configured
590/// - [`BZ_IO_ERROR`] if `libc::ferror(f)` is nonzero
591/// - [`BZ_MEM_ERROR`] if insufficient memory is available
592/// - [`BZ_OK`] otherwise
593///
594/// # Safety
595///
596/// The caller must guarantee that
597///
598/// * `bzerror` satisfies the requirements of [`pointer::as_mut`]
599/// * Either
600///     - `unused` is `NULL`
601///     - `unused` is readable for `nUnused` bytes
602///
603/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
604#[export_name = prefix!(BZ2_bzReadOpen)]
605pub unsafe extern "C" fn BZ2_bzReadOpen(
606    bzerror: *mut c_int,
607    f: *mut FILE,
608    verbosity: c_int,
609    small: c_int,
610    unused: *mut c_void,
611    nUnused: c_int,
612) -> *mut BZFILE {
613    BZ2_bzReadOpenHelp(bzerror.as_mut(), f, verbosity, small, unused, nUnused)
614}
615
616unsafe fn BZ2_bzReadOpenHelp(
617    mut bzerror: Option<&mut c_int>,
618    f: *mut FILE,
619    verbosity: c_int,
620    small: c_int,
621    unused: *mut c_void,
622    nUnused: c_int,
623) -> *mut BZFILE {
624    let mut bzf: Option<&mut BZFILE> = None;
625
626    BZ_SETERR_RAW!(bzerror, bzf, ReturnCode::BZ_OK);
627
628    if f.is_null()
629        || !(0..=1).contains(&small)
630        || !(0..=4).contains(&verbosity)
631        || (unused.is_null() && nUnused != 0)
632        || (!unused.is_null() && !(0..=BZ_MAX_UNUSED_U32 as c_int).contains(&nUnused))
633    {
634        BZ_SETERR_RAW!(bzerror, bzf, ReturnCode::BZ_PARAM_ERROR);
635        return ptr::null_mut::<BZFILE>();
636    }
637
638    if ferror(f) != 0 {
639        BZ_SETERR_RAW!(bzerror, bzf, ReturnCode::BZ_IO_ERROR);
640        return ptr::null_mut::<BZFILE>();
641    }
642
643    let Some(allocator) = Allocator::DEFAULT else {
644        BZ_SETERR_RAW!(bzerror, bzf, ReturnCode::BZ_CONFIG_ERROR);
645        return ptr::null_mut();
646    };
647
648    let Some(bzf) = allocator.allocate_zeroed::<BZFILE>(1) else {
649        BZ_SETERR_RAW!(bzerror, bzf, ReturnCode::BZ_MEM_ERROR);
650        return ptr::null_mut();
651    };
652
653    // SAFETY: bzf is non-null and correctly initalized
654    let bzf = unsafe { &mut *bzf };
655
656    BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_OK);
657
658    bzf.initialisedOk = false;
659    bzf.handle = f;
660    bzf.bufN = 0;
661    bzf.operation = Operation::Reading;
662    bzf.strm.bzalloc = None;
663    bzf.strm.bzfree = None;
664    bzf.strm.opaque = ptr::null_mut();
665
666    if nUnused > 0 {
667        ptr::copy(
668            unused as *mut i8,
669            bzf.buf[bzf.bufN as usize..].as_mut_ptr(),
670            nUnused as usize,
671        );
672        bzf.bufN += nUnused;
673    }
674
675    match BZ2_bzDecompressInitHelp(BzStream::from_mut(&mut bzf.strm), verbosity, small) {
676        ReturnCode::BZ_OK => {
677            bzf.strm.avail_in = bzf.bufN as c_uint;
678            bzf.strm.next_in = bzf.buf.as_mut_ptr().cast::<c_char>();
679            bzf.initialisedOk = true;
680        }
681        ret => {
682            BZ_SETERR!(bzerror, bzf, ret);
683
684            allocator.deallocate(bzf, 1);
685
686            return ptr::null_mut();
687        }
688    }
689
690    bzf as *mut BZFILE
691}
692
693/// Releases all memory associated with a [`BZFILE`] opened with [`BZ2_bzReadOpen`].
694///
695/// This function does not call `fclose` on the underlying file handle, the caller should close the
696/// file if appropriate.
697///
698/// This function should be called to clean up after all error situations on `BZFILE`s opened with
699/// [`BZ2_bzReadOpen`].
700///
701/// # Possible assignments to `bzerror`
702///
703/// - [`BZ_CONFIG_ERROR`] if no default allocator is configured
704/// - [`BZ_SEQUENCE_ERROR`] if b was opened with [`BZ2_bzWriteOpen`]
705/// - [`BZ_OK`] otherwise
706///
707/// # Safety
708///
709/// The caller must guarantee that
710///
711/// * `bzerror` satisfies the requirements of [`pointer::as_mut`]
712/// * Either
713///     - `b` is `NULL`
714///     - `b` is initialized with [`BZ2_bzReadOpen`] or [`BZ2_bzWriteOpen`]
715///
716/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
717#[export_name = prefix!(BZ2_bzReadClose)]
718pub unsafe extern "C" fn BZ2_bzReadClose(bzerror: *mut c_int, b: *mut BZFILE) {
719    BZ2_bzReadCloseHelp(bzerror.as_mut(), b.as_mut())
720}
721
722unsafe fn BZ2_bzReadCloseHelp(mut bzerror: Option<&mut c_int>, mut b: Option<&mut BZFILE>) {
723    BZ_SETERR_RAW!(bzerror, b, ReturnCode::BZ_OK);
724
725    let Some(bzf) = b else {
726        BZ_SETERR_RAW!(bzerror, b, ReturnCode::BZ_OK);
727        return;
728    };
729
730    if !matches!(bzf.operation, Operation::Reading) {
731        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_SEQUENCE_ERROR);
732        return;
733    }
734
735    if bzf.initialisedOk {
736        BZ2_bzDecompressEnd(&mut bzf.strm);
737    }
738
739    let Some(allocator) = Allocator::DEFAULT else {
740        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_CONFIG_ERROR);
741        return;
742    };
743
744    allocator.deallocate(bzf, 1)
745}
746
747/// Reads up to `len` (uncompressed) bytes from the compressed file `b` into the buffer `buf`.
748///
749/// # Returns
750///
751/// The number of bytes read
752///
753/// # Possible assignments to `bzerror`
754///
755/// - [`BZ_PARAM_ERROR`] if any of
756///     - `b.is_null()`
757///     - `buf.is_null()`
758///     - `len < 0`
759/// - [`BZ_SEQUENCE_ERROR`] if b was opened with [`BZ2_bzWriteOpen`]
760/// - [`BZ_IO_ERROR`] if there is an error reading from the compressed file
761/// - [`BZ_UNEXPECTED_EOF`] if the compressed data ends before the logical end-of-stream was detected
762/// - [`BZ_DATA_ERROR`] if a data integrity error is detected in the compressed stream
763/// - [`BZ_DATA_ERROR_MAGIC`] if the compressed stream doesn't begin with the right magic bytes
764/// - [`BZ_MEM_ERROR`] if insufficient memory is available
765/// - [`BZ_STREAM_END`] if the logical end-of-stream was detected
766/// - [`BZ_OK`] otherwise
767///
768/// # Safety
769///
770/// The caller must guarantee that
771///
772/// * `bzerror` satisfies the requirements of [`pointer::as_mut`]
773/// * Either
774///     - `b` is `NULL`
775///     - `b` is initialized with [`BZ2_bzReadOpen`] or [`BZ2_bzWriteOpen`]
776/// * Either
777///     - `buf` is `NULL`
778///     - `buf` is writable for `len` bytes
779///
780/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
781#[export_name = prefix!(BZ2_bzRead)]
782pub unsafe extern "C" fn BZ2_bzRead(
783    bzerror: *mut c_int,
784    b: *mut BZFILE,
785    buf: *mut c_void,
786    len: c_int,
787) -> c_int {
788    BZ2_bzReadHelp(bzerror.as_mut(), b.as_mut(), buf, len)
789}
790
791unsafe fn BZ2_bzReadHelp(
792    mut bzerror: Option<&mut c_int>,
793    mut b: Option<&mut BZFILE>,
794    buf: *mut c_void,
795    len: c_int,
796) -> c_int {
797    BZ_SETERR_RAW!(bzerror, b, ReturnCode::BZ_OK);
798
799    let Some(bzf) = b.as_mut() else {
800        BZ_SETERR_RAW!(bzerror, b, ReturnCode::BZ_PARAM_ERROR);
801        return 0;
802    };
803
804    if buf.is_null() || len < 0 {
805        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_PARAM_ERROR);
806        return 0;
807    }
808
809    if !matches!(bzf.operation, Operation::Reading) {
810        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_SEQUENCE_ERROR);
811        return 0;
812    }
813
814    if len == 0 as c_int {
815        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_OK);
816        return 0;
817    }
818
819    bzf.strm.avail_out = len as c_uint;
820    bzf.strm.next_out = buf as *mut c_char;
821    loop {
822        if ferror(bzf.handle) != 0 {
823            BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_IO_ERROR);
824            return 0;
825        }
826
827        if bzf.strm.avail_in == 0 && !myfeof(bzf.handle) {
828            let n = fread(
829                (bzf.buf).as_mut_ptr() as *mut c_void,
830                ::core::mem::size_of::<u8>(),
831                5000,
832                bzf.handle,
833            ) as i32;
834
835            if ferror(bzf.handle) != 0 {
836                BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_IO_ERROR);
837                return 0;
838            }
839
840            bzf.bufN = n;
841            bzf.strm.avail_in = bzf.bufN as c_uint;
842            bzf.strm.next_in = (bzf.buf).as_mut_ptr().cast::<c_char>();
843        }
844
845        match BZ2_bzDecompressHelp(unsafe { BzStream::from_mut(&mut bzf.strm) }) {
846            ReturnCode::BZ_OK => {
847                if myfeof(bzf.handle) && bzf.strm.avail_in == 0 && bzf.strm.avail_out > 0 {
848                    BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_UNEXPECTED_EOF);
849                    return 0;
850                } else if bzf.strm.avail_out == 0 {
851                    BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_OK);
852                    return len;
853                } else {
854                    continue;
855                }
856            }
857            ReturnCode::BZ_STREAM_END => {
858                BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_STREAM_END);
859                return (len as c_uint - bzf.strm.avail_out) as c_int;
860            }
861            error => {
862                BZ_SETERR!(bzerror, bzf, error);
863                return 0;
864            }
865        }
866    }
867}
868
869/// Returns data which was read from the compressed file but was not needed to get to the logical end-of-stream.
870///
871/// # Returns
872///
873/// - `*unused` is set to the address of the data
874/// - `*nUnused` is set to the number of bytes.
875///
876/// `*nUnused` will be set to a value contained in `0..=BZ_MAX_UNUSED`.
877///
878/// # Possible assignments to `bzerror`
879///
880/// - [`BZ_PARAM_ERROR`] if any of
881///     - `b.is_null()`
882///     - `unused.is_null()`
883///     - `nUnused.is_null()`
884/// - [`BZ_SEQUENCE_ERROR`] if any of
885///     - [`BZ_STREAM_END`] has not been signaled
886///     - b was opened with [`BZ2_bzWriteOpen`]
887/// - [`BZ_OK`] otherwise
888///
889/// # Safety
890///
891/// The caller must guarantee that
892///
893/// * `bzerror` satisfies the requirements of [`pointer::as_mut`]
894/// * `unused` satisfies the requirements of [`pointer::as_mut`]
895/// * `nUnused` satisfies the requirements of [`pointer::as_mut`]
896/// * Either
897///     - `b` is `NULL`
898///     - `b` is initialized with [`BZ2_bzReadOpen`] or [`BZ2_bzWriteOpen`]
899///
900/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
901#[export_name = prefix!(BZ2_bzReadGetUnused)]
902pub unsafe extern "C" fn BZ2_bzReadGetUnused(
903    bzerror: *mut c_int,
904    b: *mut BZFILE,
905    unused: *mut *mut c_void,
906    nUnused: *mut c_int,
907) {
908    BZ2_bzReadGetUnusedHelp(
909        bzerror.as_mut(),
910        b.as_mut(),
911        unused.as_mut(),
912        nUnused.as_mut(),
913    )
914}
915
916unsafe fn BZ2_bzReadGetUnusedHelp(
917    mut bzerror: Option<&mut c_int>,
918    mut b: Option<&mut BZFILE>,
919    unused: Option<&mut *mut c_void>,
920    nUnused: Option<&mut c_int>,
921) {
922    let Some(bzf) = b.as_mut() else {
923        BZ_SETERR_RAW!(bzerror, b, ReturnCode::BZ_PARAM_ERROR);
924        return;
925    };
926
927    if bzf.lastErr != ReturnCode::BZ_STREAM_END {
928        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_SEQUENCE_ERROR);
929        return;
930    }
931
932    let (Some(unused), Some(nUnused)) = (unused, nUnused) else {
933        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_PARAM_ERROR);
934        return;
935    };
936
937    BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_OK);
938
939    *nUnused = bzf.strm.avail_in as c_int;
940    *unused = bzf.strm.next_in as *mut c_void;
941}
942
943#[derive(Copy, Clone)]
944pub(crate) enum Operation {
945    Reading,
946    Writing,
947}
948
949enum OpenMode {
950    Pointer,
951    FileDescriptor(i32),
952}
953
954unsafe fn bzopen_or_bzdopen(path: Option<&CStr>, open_mode: OpenMode, mode: &CStr) -> *mut BZFILE {
955    let mut bzerr = 0;
956    let mut unused: [c_char; BZ_MAX_UNUSED as usize] = [0; BZ_MAX_UNUSED as usize];
957
958    let mut blockSize100k = 9;
959    let verbosity = 0;
960    let workFactor = 30;
961    let nUnused = 0;
962
963    let mut smallMode = false;
964    let mut operation = Operation::Reading;
965
966    for c in mode.to_bytes() {
967        match c {
968            b'r' => operation = Operation::Reading,
969            b'w' => operation = Operation::Writing,
970            b's' => smallMode = true,
971            b'0'..=b'9' => blockSize100k = (*c - b'0') as i32,
972            _ => {}
973        }
974    }
975
976    let mode = match open_mode {
977        OpenMode::Pointer => match operation {
978            Operation::Reading => b"rbe\0".as_slice(),
979            Operation::Writing => b"rbe\0".as_slice(),
980        },
981        OpenMode::FileDescriptor(_) => match operation {
982            Operation::Reading => b"rb\0".as_slice(),
983            Operation::Writing => b"rb\0".as_slice(),
984        },
985    };
986
987    let mode2 = mode.as_ptr().cast_mut().cast::<c_char>();
988
989    let default_file = match operation {
990        Operation::Reading => STDIN!(),
991        Operation::Writing => STDOUT!(),
992    };
993
994    let fp = match open_mode {
995        OpenMode::Pointer => match path {
996            None => default_file,
997            Some(path) if path.is_empty() => default_file,
998            Some(path) => fopen(path.as_ptr(), mode2),
999        },
1000        OpenMode::FileDescriptor(fd) => fdopen(fd, mode2),
1001    };
1002
1003    if fp.is_null() {
1004        return ptr::null_mut();
1005    }
1006
1007    let bzfp = match operation {
1008        Operation::Reading => BZ2_bzReadOpen(
1009            &mut bzerr,
1010            fp,
1011            verbosity,
1012            smallMode as i32,
1013            unused.as_mut_ptr() as *mut c_void,
1014            nUnused,
1015        ),
1016        Operation::Writing => BZ2_bzWriteOpen(
1017            &mut bzerr,
1018            fp,
1019            blockSize100k.clamp(1, 9),
1020            verbosity,
1021            workFactor,
1022        ),
1023    };
1024
1025    if bzfp.is_null() {
1026        if fp != STDIN!() && fp != STDOUT!() {
1027            fclose(fp);
1028        }
1029        return ptr::null_mut();
1030    }
1031
1032    bzfp
1033}
1034
1035/// Opens a `.bz2` file for reading or writing using its name. Analogous to [`libc::fopen`].
1036///
1037/// # Safety
1038///
1039/// The caller must guarantee that
1040///
1041/// * Either
1042///     - `path` is `NULL`
1043///     - `path` is a null-terminated sequence of bytes
1044/// * Either
1045///     - `mode` is `NULL`
1046///     - `mode` is a null-terminated sequence of bytes
1047///
1048/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
1049#[export_name = prefix!(BZ2_bzopen)]
1050pub unsafe extern "C" fn BZ2_bzopen(path: *const c_char, mode: *const c_char) -> *mut BZFILE {
1051    let mode = if mode.is_null() {
1052        return ptr::null_mut();
1053    } else {
1054        CStr::from_ptr(mode)
1055    };
1056
1057    let path = if path.is_null() {
1058        None
1059    } else {
1060        Some(CStr::from_ptr(path))
1061    };
1062
1063    bzopen_or_bzdopen(path, OpenMode::Pointer, mode)
1064}
1065
1066/// Opens a `.bz2` file for reading or writing using a pre-existing file descriptor. Analogous to [`libc::fdopen`].
1067///
1068/// # Safety
1069///
1070/// The caller must guarantee that
1071///
1072/// * `fd` must be a valid file descriptor for the duration of [`BZ2_bzdopen`]
1073/// * Either
1074///     - `mode` is `NULL`
1075///     - `mode` is a null-terminated sequence of bytes
1076///
1077/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
1078#[export_name = prefix!(BZ2_bzdopen)]
1079pub unsafe extern "C" fn BZ2_bzdopen(fd: c_int, mode: *const c_char) -> *mut BZFILE {
1080    let mode = if mode.is_null() {
1081        return ptr::null_mut();
1082    } else {
1083        CStr::from_ptr(mode)
1084    };
1085
1086    bzopen_or_bzdopen(None, OpenMode::FileDescriptor(fd), mode)
1087}
1088
1089/// Reads up to `len` (uncompressed) bytes from the compressed file `b` into the buffer `buf`.
1090///
1091/// Analogous to [`libc::fread`].
1092///
1093/// # Returns
1094///
1095/// Number of bytes read on success, or `-1` on failure.
1096///
1097/// # Safety
1098///
1099/// The caller must guarantee that
1100///
1101/// * Either
1102///     - `b` is `NULL`
1103///     - `b` is initialized with [`BZ2_bzWriteOpen`] or [`BZ2_bzReadOpen`]
1104/// * Either
1105///     - `buf` is `NULL`
1106///     - `buf` is writable for `len` bytes
1107///
1108/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
1109#[export_name = prefix!(BZ2_bzread)]
1110pub unsafe extern "C" fn BZ2_bzread(b: *mut BZFILE, buf: *mut c_void, len: c_int) -> c_int {
1111    BZ2_bzreadHelp(b.as_mut(), buf, len)
1112}
1113
1114unsafe fn BZ2_bzreadHelp(mut b: Option<&mut BZFILE>, buf: *mut c_void, len: c_int) -> c_int {
1115    let mut bzerr = 0;
1116
1117    if let Some(b) = b.as_deref_mut() {
1118        if b.lastErr == ReturnCode::BZ_STREAM_END {
1119            return 0;
1120        }
1121    }
1122
1123    let nread = BZ2_bzReadHelp(Some(&mut bzerr), b, buf, len);
1124    if bzerr == 0 || bzerr == ReturnCode::BZ_STREAM_END as i32 {
1125        nread
1126    } else {
1127        -1
1128    }
1129}
1130
1131/// Absorbs `len` bytes from the buffer `buf`, eventually to be compressed and written to the file.
1132///
1133/// Analogous to [`libc::fwrite`].
1134///
1135/// # Returns
1136///
1137/// The value `len` on success, or `-1` on failure.
1138///
1139/// # Safety
1140///
1141/// The caller must guarantee that
1142///
1143/// * Either
1144///     - `b` is `NULL`
1145///     - `b` is initialized with [`BZ2_bzWriteOpen`] or [`BZ2_bzReadOpen`]
1146/// * Either
1147///     - `buf` is `NULL`
1148///     - `buf` is readable for `len` bytes
1149///
1150/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
1151#[export_name = prefix!(BZ2_bzwrite)]
1152pub unsafe extern "C" fn BZ2_bzwrite(b: *mut BZFILE, buf: *const c_void, len: c_int) -> c_int {
1153    BZ2_bzwriteHelp(b.as_mut(), buf, len)
1154}
1155
1156unsafe fn BZ2_bzwriteHelp(b: Option<&mut BZFILE>, buf: *const c_void, len: c_int) -> c_int {
1157    let mut bzerr = 0;
1158    BZ2_bzWriteHelp(Some(&mut bzerr), b, buf, len);
1159
1160    match bzerr {
1161        0 => len,
1162        _ => -1,
1163    }
1164}
1165
1166/// Flushes a [`BZFILE`].
1167///
1168/// Analogous to [`libc::fflush`].
1169///
1170/// # Safety
1171///
1172/// The caller must guarantee that
1173///
1174/// * Either
1175///     - `b` is `NULL`
1176///     - `b` is initialized with [`BZ2_bzReadOpen`] or [`BZ2_bzWriteOpen`]
1177#[export_name = prefix!(BZ2_bzflush)]
1178pub unsafe extern "C" fn BZ2_bzflush(mut _b: *mut BZFILE) -> c_int {
1179    /* do nothing now... */
1180    0
1181}
1182
1183/// Closes a [`BZFILE`].
1184///
1185/// Analogous to [`libc::fclose`].
1186///
1187/// # Safety
1188///
1189/// The caller must guarantee that
1190///
1191/// * Either
1192///     - `b` is `NULL`
1193///     - `b` is initialized with [`BZ2_bzReadOpen`] or [`BZ2_bzWriteOpen`]
1194#[export_name = prefix!(BZ2_bzclose)]
1195pub unsafe extern "C" fn BZ2_bzclose(b: *mut BZFILE) {
1196    BZ2_bzcloseHelp(b.as_mut())
1197}
1198
1199unsafe fn BZ2_bzcloseHelp(mut b: Option<&mut BZFILE>) {
1200    let mut bzerr: c_int = 0;
1201
1202    let operation = if let Some(bzf) = &mut b {
1203        bzf.operation
1204    } else {
1205        return;
1206    };
1207
1208    match operation {
1209        Operation::Reading => {
1210            BZ2_bzReadCloseHelp(Some(&mut bzerr), b.as_deref_mut());
1211        }
1212        Operation::Writing => {
1213            BZ2_bzWriteCloseHelp(Some(&mut bzerr), b.as_deref_mut(), false as i32, None, None);
1214            if bzerr != 0 {
1215                BZ2_bzWriteCloseHelp(None, b.as_deref_mut(), true as i32, None, None);
1216            }
1217        }
1218    }
1219
1220    if let Some(bzf) = b {
1221        if bzf.handle != STDIN!() && bzf.handle != STDOUT!() {
1222            fclose(bzf.handle);
1223        }
1224    }
1225}
1226
1227const BZERRORSTRINGS: [&str; 16] = [
1228    "OK\0",
1229    "SEQUENCE_ERROR\0",
1230    "PARAM_ERROR\0",
1231    "MEM_ERROR\0",
1232    "DATA_ERROR\0",
1233    "DATA_ERROR_MAGIC\0",
1234    "IO_ERROR\0",
1235    "UNEXPECTED_EOF\0",
1236    "OUTBUFF_FULL\0",
1237    "CONFIG_ERROR\0",
1238    "???\0",
1239    "???\0",
1240    "???\0",
1241    "???\0",
1242    "???\0",
1243    "???\0",
1244];
1245
1246/// Describes the most recent error.
1247///
1248/// # Returns
1249///
1250/// A null-terminated string describing the most recent error status of `b`, and also sets `*errnum` to its numerical value.
1251///
1252/// # Safety
1253///
1254/// The caller must guarantee that
1255///
1256/// * Either
1257///     - `b` is `NULL`
1258///     - `b` is initialized with [`BZ2_bzReadOpen`] or [`BZ2_bzWriteOpen`]
1259/// * `errnum` satisfies the requirements of [`pointer::as_mut`]
1260///
1261/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
1262#[export_name = prefix!(BZ2_bzerror)]
1263pub unsafe extern "C" fn BZ2_bzerror(b: *const BZFILE, errnum: *mut c_int) -> *const c_char {
1264    BZ2_bzerrorHelp(
1265        b.as_ref().expect("Passed null pointer to BZ2_bzerror"),
1266        errnum.as_mut(),
1267    )
1268}
1269
1270fn BZ2_bzerrorHelp(b: &BZFILE, errnum: Option<&mut c_int>) -> *const c_char {
1271    let err = Ord::min(0, b.lastErr as c_int);
1272    if let Some(errnum) = errnum {
1273        *errnum = err;
1274    };
1275    let msg = match BZERRORSTRINGS.get(-err as usize) {
1276        Some(msg) => msg,
1277        None => "???\0",
1278    };
1279    msg.as_ptr().cast::<c_char>()
1280}
1281
1282#[cfg(test)]
1283mod tests {
1284    use super::*;
1285
1286    #[test]
1287    fn error_messages() {
1288        let mut bz_file = BZFILE {
1289            handle: core::ptr::null_mut(),
1290            buf: [0; 5000],
1291            bufN: 0,
1292            strm: bz_stream::zeroed(),
1293            lastErr: ReturnCode::BZ_OK,
1294            operation: Operation::Reading,
1295            initialisedOk: false,
1296        };
1297
1298        let return_codes = [
1299            ReturnCode::BZ_OK,
1300            ReturnCode::BZ_RUN_OK,
1301            ReturnCode::BZ_FLUSH_OK,
1302            ReturnCode::BZ_FINISH_OK,
1303            ReturnCode::BZ_STREAM_END,
1304            ReturnCode::BZ_SEQUENCE_ERROR,
1305            ReturnCode::BZ_PARAM_ERROR,
1306            ReturnCode::BZ_MEM_ERROR,
1307            ReturnCode::BZ_DATA_ERROR,
1308            ReturnCode::BZ_DATA_ERROR_MAGIC,
1309            ReturnCode::BZ_IO_ERROR,
1310            ReturnCode::BZ_UNEXPECTED_EOF,
1311            ReturnCode::BZ_OUTBUFF_FULL,
1312            ReturnCode::BZ_CONFIG_ERROR,
1313        ];
1314
1315        for return_code in return_codes {
1316            bz_file.lastErr = return_code;
1317
1318            let mut errnum = 0;
1319            let ptr = unsafe { BZ2_bzerror(&bz_file as *const BZFILE, &mut errnum) };
1320            assert!(!ptr.is_null());
1321            let cstr = unsafe { CStr::from_ptr(ptr) };
1322
1323            let msg = cstr.to_str().unwrap();
1324
1325            let expected = match return_code {
1326                ReturnCode::BZ_OK => "OK",
1327                ReturnCode::BZ_RUN_OK => "OK",
1328                ReturnCode::BZ_FLUSH_OK => "OK",
1329                ReturnCode::BZ_FINISH_OK => "OK",
1330                ReturnCode::BZ_STREAM_END => "OK",
1331                ReturnCode::BZ_SEQUENCE_ERROR => "SEQUENCE_ERROR",
1332                ReturnCode::BZ_PARAM_ERROR => "PARAM_ERROR",
1333                ReturnCode::BZ_MEM_ERROR => "MEM_ERROR",
1334                ReturnCode::BZ_DATA_ERROR => "DATA_ERROR",
1335                ReturnCode::BZ_DATA_ERROR_MAGIC => "DATA_ERROR_MAGIC",
1336                ReturnCode::BZ_IO_ERROR => "IO_ERROR",
1337                ReturnCode::BZ_UNEXPECTED_EOF => "UNEXPECTED_EOF",
1338                ReturnCode::BZ_OUTBUFF_FULL => "OUTBUFF_FULL",
1339                ReturnCode::BZ_CONFIG_ERROR => "CONFIG_ERROR",
1340            };
1341
1342            assert_eq!(msg, expected);
1343
1344            if (return_code as i32) < 0 {
1345                assert_eq!(return_code as i32, errnum);
1346            } else {
1347                assert_eq!(0, errnum);
1348            }
1349        }
1350    }
1351}