Skip to main content

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_bzReadOpen`]
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,
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: *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_bzReadOpen`]
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,
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    b: *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 mut b = b.as_mut();
470    let Some(bzf) = b else {
471        BZ_SETERR_RAW!(bzerror, b, ReturnCode::BZ_PARAM_ERROR);
472        return;
473    };
474
475    if !matches!(bzf.operation, Operation::Writing) {
476        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_SEQUENCE_ERROR);
477        return;
478    }
479
480    #[cfg(not(miri))]
481    if !bzf.handle.is_null() && ferror(bzf.handle) != 0 {
482        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_IO_ERROR);
483        return;
484    }
485
486    if let Some(nbytes_in_lo32) = nbytes_in_lo32.as_deref_mut() {
487        *nbytes_in_lo32 = 0
488    }
489    if let Some(nbytes_in_hi32) = nbytes_in_hi32.as_deref_mut() {
490        *nbytes_in_hi32 = 0;
491    }
492    if let Some(nbytes_out_lo32) = nbytes_out_lo32.as_deref_mut() {
493        *nbytes_out_lo32 = 0;
494    }
495    if let Some(nbytes_out_hi32) = nbytes_out_hi32.as_deref_mut() {
496        *nbytes_out_hi32 = 0;
497    }
498
499    if abandon == 0 && bzf.lastErr == ReturnCode::BZ_OK {
500        loop {
501            bzf.strm.avail_out = BZ_MAX_UNUSED_U32;
502            bzf.strm.next_out = (bzf.buf).as_mut_ptr().cast::<c_char>();
503            match BZ2_bzCompressHelp(BzStream::from_mut(&mut bzf.strm), 2 as c_int) {
504                ret @ (ReturnCode::BZ_FINISH_OK | ReturnCode::BZ_STREAM_END) => {
505                    if bzf.strm.avail_out < BZ_MAX_UNUSED_U32 {
506                        let n1 = (BZ_MAX_UNUSED_U32 - bzf.strm.avail_out) as usize;
507                        let n2 = fwrite(
508                            bzf.buf.as_mut_ptr().cast::<c_void>(),
509                            mem::size_of::<u8>(),
510                            n1,
511                            bzf.handle,
512                        );
513                        if n1 != n2 || ferror(bzf.handle) != 0 {
514                            BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_IO_ERROR);
515                            return;
516                        }
517                    }
518
519                    if let ReturnCode::BZ_STREAM_END = ret {
520                        break;
521                    }
522                }
523                ret => {
524                    BZ_SETERR!(bzerror, bzf, ret);
525                    return;
526                }
527            }
528        }
529    }
530
531    if abandon == 0 && ferror(bzf.handle) == 0 {
532        fflush(bzf.handle);
533        if ferror(bzf.handle) != 0 {
534            BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_IO_ERROR);
535            return;
536        }
537    }
538
539    if let Some(nbytes_in_lo32) = nbytes_in_lo32 {
540        *nbytes_in_lo32 = bzf.strm.total_in_lo32;
541    }
542    if let Some(nbytes_in_hi32) = nbytes_in_hi32 {
543        *nbytes_in_hi32 = bzf.strm.total_in_hi32;
544    }
545    if let Some(nbytes_out_lo32) = nbytes_out_lo32 {
546        *nbytes_out_lo32 = bzf.strm.total_out_lo32;
547    }
548    if let Some(nbytes_out_hi32) = nbytes_out_hi32 {
549        *nbytes_out_hi32 = bzf.strm.total_out_hi32;
550    }
551
552    BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_OK);
553
554    BZ2_bzCompressEnd(&mut bzf.strm);
555
556    let Some(allocator) = Allocator::DEFAULT else {
557        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_CONFIG_ERROR);
558        return;
559    };
560
561    allocator.deallocate(bzf, 1);
562}
563
564/// Prepare to read compressed data from a file handle.
565///
566/// 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.
567///
568/// If small is 1, the library will try to decompress using less memory, at the expense of speed.
569///
570/// For reasons explained below, [`BZ2_bzRead`] will decompress the nUnused bytes starting at unused, before starting to read from the file `f`.
571/// 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.
572///
573/// For the meaning of parameters `small`, `verbosity`, see [`BZ2_bzDecompressInit`].
574///
575/// Because the compression ratio of the compressed data cannot be known in advance,
576/// there is no easy way to guarantee that the output buffer will be big enough.
577/// You may of course make arrangements in your code to record the size of the uncompressed data,
578/// but such a mechanism is beyond the scope of this library.
579///
580/// # Returns
581///
582/// - if `*bzerror` is [`BZ_OK`], a valid pointer to an abstract `BZFILE`
583/// - otherwise `NULL`
584///
585/// # Possible assignments to `bzerror`
586///
587/// - [`BZ_PARAM_ERROR`] if any of
588///     - `(unused.is_null() && nUnused != 0)`
589///     - `(!unused.is_null() && !(0..=BZ_MAX_UNUSED).contains(&nUnused))`
590///     - `!(0..=1).contains(&small)`
591///     - `!(0..=4).contains(&verbosity)`
592/// - [`BZ_CONFIG_ERROR`] if no default allocator is configured
593/// - [`BZ_IO_ERROR`] if `libc::ferror(f)` is nonzero
594/// - [`BZ_MEM_ERROR`] if insufficient memory is available
595/// - [`BZ_OK`] otherwise
596///
597/// # Safety
598///
599/// The caller must guarantee that
600///
601/// * `bzerror` satisfies the requirements of [`pointer::as_mut`]
602/// * Either
603///     - `unused` is `NULL`
604///     - `unused` is readable for `nUnused` bytes
605///
606/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
607#[export_name = prefix!(BZ2_bzReadOpen)]
608pub unsafe extern "C" fn BZ2_bzReadOpen(
609    bzerror: *mut c_int,
610    f: *mut FILE,
611    verbosity: c_int,
612    small: c_int,
613    unused: *mut c_void,
614    nUnused: c_int,
615) -> *mut BZFILE {
616    BZ2_bzReadOpenHelp(bzerror.as_mut(), f, verbosity, small, unused, nUnused)
617}
618
619unsafe fn BZ2_bzReadOpenHelp(
620    mut bzerror: Option<&mut c_int>,
621    f: *mut FILE,
622    verbosity: c_int,
623    small: c_int,
624    unused: *mut c_void,
625    nUnused: c_int,
626) -> *mut BZFILE {
627    let mut bzf: Option<&mut BZFILE> = None;
628
629    BZ_SETERR_RAW!(bzerror, bzf, ReturnCode::BZ_OK);
630
631    if f.is_null()
632        || !(0..=1).contains(&small)
633        || !(0..=4).contains(&verbosity)
634        || (unused.is_null() && nUnused != 0)
635        || (!unused.is_null() && !(0..=BZ_MAX_UNUSED_U32 as c_int).contains(&nUnused))
636    {
637        BZ_SETERR_RAW!(bzerror, bzf, ReturnCode::BZ_PARAM_ERROR);
638        return ptr::null_mut::<BZFILE>();
639    }
640
641    if ferror(f) != 0 {
642        BZ_SETERR_RAW!(bzerror, bzf, ReturnCode::BZ_IO_ERROR);
643        return ptr::null_mut::<BZFILE>();
644    }
645
646    let Some(allocator) = Allocator::DEFAULT else {
647        BZ_SETERR_RAW!(bzerror, bzf, ReturnCode::BZ_CONFIG_ERROR);
648        return ptr::null_mut();
649    };
650
651    let Some(bzf) = allocator.allocate_zeroed::<BZFILE>(1) else {
652        BZ_SETERR_RAW!(bzerror, bzf, ReturnCode::BZ_MEM_ERROR);
653        return ptr::null_mut();
654    };
655
656    // SAFETY: bzf is non-null and correctly initalized
657    let bzf = unsafe { &mut *bzf };
658
659    BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_OK);
660
661    bzf.initialisedOk = false;
662    bzf.handle = f;
663    bzf.bufN = 0;
664    bzf.operation = Operation::Reading;
665    bzf.strm.bzalloc = None;
666    bzf.strm.bzfree = None;
667    bzf.strm.opaque = ptr::null_mut();
668
669    if nUnused > 0 {
670        ptr::copy(
671            unused as *mut i8,
672            bzf.buf[bzf.bufN as usize..].as_mut_ptr(),
673            nUnused as usize,
674        );
675        bzf.bufN += nUnused;
676    }
677
678    match BZ2_bzDecompressInitHelp(BzStream::from_mut(&mut bzf.strm), verbosity, small) {
679        ReturnCode::BZ_OK => {
680            bzf.strm.avail_in = bzf.bufN as c_uint;
681            bzf.strm.next_in = bzf.buf.as_mut_ptr().cast::<c_char>();
682            bzf.initialisedOk = true;
683        }
684        ret => {
685            BZ_SETERR!(bzerror, bzf, ret);
686
687            allocator.deallocate(bzf, 1);
688
689            return ptr::null_mut();
690        }
691    }
692
693    bzf as *mut BZFILE
694}
695
696/// Releases all memory associated with a [`BZFILE`] opened with [`BZ2_bzReadOpen`].
697///
698/// This function does not call `fclose` on the underlying file handle, the caller should close the
699/// file if appropriate.
700///
701/// This function should be called to clean up after all error situations on `BZFILE`s opened with
702/// [`BZ2_bzReadOpen`].
703///
704/// # Possible assignments to `bzerror`
705///
706/// - [`BZ_CONFIG_ERROR`] if no default allocator is configured
707/// - [`BZ_SEQUENCE_ERROR`] if b was opened with [`BZ2_bzWriteOpen`]
708/// - [`BZ_OK`] otherwise
709///
710/// # Safety
711///
712/// The caller must guarantee that
713///
714/// * `bzerror` satisfies the requirements of [`pointer::as_mut`]
715/// * Either
716///     - `b` is `NULL`
717///     - `b` is initialized with [`BZ2_bzReadOpen`] or [`BZ2_bzWriteOpen`]
718///
719/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
720#[export_name = prefix!(BZ2_bzReadClose)]
721pub unsafe extern "C" fn BZ2_bzReadClose(bzerror: *mut c_int, b: *mut BZFILE) {
722    let mut bzerror = bzerror.as_mut();
723    let mut b = b.as_mut();
724
725    BZ_SETERR_RAW!(bzerror, b, ReturnCode::BZ_OK);
726
727    let Some(bzf) = b else {
728        BZ_SETERR_RAW!(bzerror, b, ReturnCode::BZ_OK);
729        return;
730    };
731
732    if !matches!(bzf.operation, Operation::Reading) {
733        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_SEQUENCE_ERROR);
734        return;
735    }
736
737    if bzf.initialisedOk {
738        BZ2_bzDecompressEnd(&mut bzf.strm);
739    }
740
741    let Some(allocator) = Allocator::DEFAULT else {
742        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_CONFIG_ERROR);
743        return;
744    };
745
746    allocator.deallocate(bzf, 1)
747}
748
749/// Reads up to `len` (uncompressed) bytes from the compressed file `b` into the buffer `buf`.
750///
751/// # Returns
752///
753/// The number of bytes read
754///
755/// # Possible assignments to `bzerror`
756///
757/// - [`BZ_PARAM_ERROR`] if any of
758///     - `b.is_null()`
759///     - `buf.is_null()`
760///     - `len < 0`
761/// - [`BZ_SEQUENCE_ERROR`] if b was opened with [`BZ2_bzWriteOpen`]
762/// - [`BZ_IO_ERROR`] if there is an error reading from the compressed file
763/// - [`BZ_UNEXPECTED_EOF`] if the compressed data ends before the logical end-of-stream was detected
764/// - [`BZ_DATA_ERROR`] if a data integrity error is detected in the compressed stream
765/// - [`BZ_DATA_ERROR_MAGIC`] if the compressed stream doesn't begin with the right magic bytes
766/// - [`BZ_MEM_ERROR`] if insufficient memory is available
767/// - [`BZ_STREAM_END`] if the logical end-of-stream was detected
768/// - [`BZ_OK`] otherwise
769///
770/// # Safety
771///
772/// The caller must guarantee that
773///
774/// * `bzerror` satisfies the requirements of [`pointer::as_mut`]
775/// * Either
776///     - `b` is `NULL`
777///     - `b` is initialized with [`BZ2_bzReadOpen`] or [`BZ2_bzWriteOpen`]
778/// * Either
779///     - `buf` is `NULL`
780///     - `buf` is writable for `len` bytes
781///
782/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
783#[export_name = prefix!(BZ2_bzRead)]
784pub unsafe extern "C" fn BZ2_bzRead(
785    bzerror: *mut c_int,
786    b: *mut BZFILE,
787    buf: *mut c_void,
788    len: c_int,
789) -> c_int {
790    BZ2_bzReadHelp(bzerror.as_mut(), b.as_mut(), buf, len)
791}
792
793unsafe fn BZ2_bzReadHelp(
794    mut bzerror: Option<&mut c_int>,
795    mut b: Option<&mut BZFILE>,
796    buf: *mut c_void,
797    len: c_int,
798) -> c_int {
799    BZ_SETERR_RAW!(bzerror, b, ReturnCode::BZ_OK);
800
801    let Some(bzf) = b.as_mut() else {
802        BZ_SETERR_RAW!(bzerror, b, ReturnCode::BZ_PARAM_ERROR);
803        return 0;
804    };
805
806    if buf.is_null() || len < 0 {
807        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_PARAM_ERROR);
808        return 0;
809    }
810
811    if !matches!(bzf.operation, Operation::Reading) {
812        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_SEQUENCE_ERROR);
813        return 0;
814    }
815
816    if len == 0 as c_int {
817        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_OK);
818        return 0;
819    }
820
821    bzf.strm.avail_out = len as c_uint;
822    bzf.strm.next_out = buf as *mut c_char;
823    loop {
824        if ferror(bzf.handle) != 0 {
825            BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_IO_ERROR);
826            return 0;
827        }
828
829        if bzf.strm.avail_in == 0 && !myfeof(bzf.handle) {
830            let n = fread(
831                (bzf.buf).as_mut_ptr() as *mut c_void,
832                ::core::mem::size_of::<u8>(),
833                5000,
834                bzf.handle,
835            ) as i32;
836
837            if ferror(bzf.handle) != 0 {
838                BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_IO_ERROR);
839                return 0;
840            }
841
842            bzf.bufN = n;
843            bzf.strm.avail_in = bzf.bufN as c_uint;
844            bzf.strm.next_in = (bzf.buf).as_mut_ptr().cast::<c_char>();
845        }
846
847        match BZ2_bzDecompressHelp(unsafe { BzStream::from_mut(&mut bzf.strm) }) {
848            ReturnCode::BZ_OK => {
849                if myfeof(bzf.handle) && bzf.strm.avail_in == 0 && bzf.strm.avail_out > 0 {
850                    BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_UNEXPECTED_EOF);
851                    return 0;
852                } else if bzf.strm.avail_out == 0 {
853                    BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_OK);
854                    return len;
855                } else {
856                    continue;
857                }
858            }
859            ReturnCode::BZ_STREAM_END => {
860                BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_STREAM_END);
861                return (len as c_uint - bzf.strm.avail_out) as c_int;
862            }
863            error => {
864                BZ_SETERR!(bzerror, bzf, error);
865                return 0;
866            }
867        }
868    }
869}
870
871/// Returns data which was read from the compressed file but was not needed to get to the logical end-of-stream.
872///
873/// # Returns
874///
875/// - `*unused` is set to the address of the data
876/// - `*nUnused` is set to the number of bytes.
877///
878/// `*nUnused` will be set to a value contained in `0..=BZ_MAX_UNUSED`.
879///
880/// # Possible assignments to `bzerror`
881///
882/// - [`BZ_PARAM_ERROR`] if any of
883///     - `b.is_null()`
884///     - `unused.is_null()`
885///     - `nUnused.is_null()`
886/// - [`BZ_SEQUENCE_ERROR`] if any of
887///     - [`BZ_STREAM_END`] has not been signaled
888///     - b was opened with [`BZ2_bzWriteOpen`]
889/// - [`BZ_OK`] otherwise
890///
891/// # Safety
892///
893/// The caller must guarantee that
894///
895/// * `bzerror` satisfies the requirements of [`pointer::as_mut`]
896/// * `unused` satisfies the requirements of [`pointer::as_mut`]
897/// * `nUnused` satisfies the requirements of [`pointer::as_mut`]
898/// * Either
899///     - `b` is `NULL`
900///     - `b` is initialized with [`BZ2_bzReadOpen`] or [`BZ2_bzWriteOpen`]
901///
902/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
903#[export_name = prefix!(BZ2_bzReadGetUnused)]
904pub unsafe extern "C" fn BZ2_bzReadGetUnused(
905    bzerror: *mut c_int,
906    b: *mut BZFILE,
907    unused: *mut *mut c_void,
908    nUnused: *mut c_int,
909) {
910    BZ2_bzReadGetUnusedHelp(
911        bzerror.as_mut(),
912        b.as_mut(),
913        unused.as_mut(),
914        nUnused.as_mut(),
915    )
916}
917
918unsafe fn BZ2_bzReadGetUnusedHelp(
919    mut bzerror: Option<&mut c_int>,
920    mut b: Option<&mut BZFILE>,
921    unused: Option<&mut *mut c_void>,
922    nUnused: Option<&mut c_int>,
923) {
924    let Some(bzf) = b.as_mut() else {
925        BZ_SETERR_RAW!(bzerror, b, ReturnCode::BZ_PARAM_ERROR);
926        return;
927    };
928
929    if bzf.lastErr != ReturnCode::BZ_STREAM_END {
930        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_SEQUENCE_ERROR);
931        return;
932    }
933
934    let (Some(unused), Some(nUnused)) = (unused, nUnused) else {
935        BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_PARAM_ERROR);
936        return;
937    };
938
939    BZ_SETERR!(bzerror, bzf, ReturnCode::BZ_OK);
940
941    *nUnused = bzf.strm.avail_in as c_int;
942    *unused = bzf.strm.next_in as *mut c_void;
943}
944
945#[derive(Copy, Clone)]
946pub(crate) enum Operation {
947    Reading,
948    Writing,
949}
950
951enum OpenMode {
952    Pointer,
953    FileDescriptor(i32),
954}
955
956unsafe fn bzopen_or_bzdopen(path: Option<&CStr>, open_mode: OpenMode, mode: &CStr) -> *mut BZFILE {
957    let mut bzerr = 0;
958    let mut unused: [c_char; BZ_MAX_UNUSED as usize] = [0; BZ_MAX_UNUSED as usize];
959
960    let mut blockSize100k = 9;
961    let verbosity = 0;
962    let workFactor = 30;
963    let nUnused = 0;
964
965    let mut smallMode = false;
966    let mut operation = Operation::Reading;
967
968    for c in mode.to_bytes() {
969        match c {
970            b'r' => operation = Operation::Reading,
971            b'w' => operation = Operation::Writing,
972            b's' => smallMode = true,
973            b'0'..=b'9' => blockSize100k = (*c - b'0') as i32,
974            _ => {}
975        }
976    }
977
978    let mode = match open_mode {
979        OpenMode::Pointer => match operation {
980            Operation::Reading => b"rbe\0".as_slice(),
981            Operation::Writing => b"wbe\0".as_slice(),
982        },
983        OpenMode::FileDescriptor(_) => match operation {
984            Operation::Reading => b"rb\0".as_slice(),
985            Operation::Writing => b"wb\0".as_slice(),
986        },
987    };
988
989    let mode2 = mode.as_ptr().cast_mut().cast::<c_char>();
990
991    let default_file = match operation {
992        Operation::Reading => STDIN!(),
993        Operation::Writing => STDOUT!(),
994    };
995
996    let (fp, close_handle) = match open_mode {
997        OpenMode::Pointer => match path {
998            None => (default_file, false),
999            Some(path) if path.is_empty() => (default_file, false),
1000            Some(path) => (fopen(path.as_ptr(), mode2), true),
1001        },
1002        OpenMode::FileDescriptor(fd) => (fdopen(fd, mode2), true),
1003    };
1004
1005    if fp.is_null() {
1006        return ptr::null_mut();
1007    }
1008
1009    let bzfp = match operation {
1010        Operation::Reading => BZ2_bzReadOpen(
1011            &mut bzerr,
1012            fp,
1013            verbosity,
1014            smallMode as i32,
1015            unused.as_mut_ptr() as *mut c_void,
1016            nUnused,
1017        ),
1018        Operation::Writing => BZ2_bzWriteOpen(
1019            &mut bzerr,
1020            fp,
1021            blockSize100k.clamp(1, 9),
1022            verbosity,
1023            workFactor,
1024        ),
1025    };
1026
1027    if bzfp.is_null() {
1028        if close_handle {
1029            fclose(fp);
1030        }
1031        return ptr::null_mut();
1032    }
1033
1034    bzfp
1035}
1036
1037/// Opens a `.bz2` file for reading or writing using its name. Analogous to [`libc::fopen`].
1038///
1039/// # Safety
1040///
1041/// The caller must guarantee that
1042///
1043/// * Either
1044///     - `path` is `NULL`
1045///     - `path` is a null-terminated sequence of bytes
1046/// * Either
1047///     - `mode` is `NULL`
1048///     - `mode` is a null-terminated sequence of bytes
1049///
1050/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
1051#[export_name = prefix!(BZ2_bzopen)]
1052pub unsafe extern "C" fn BZ2_bzopen(path: *const c_char, mode: *const c_char) -> *mut BZFILE {
1053    let mode = if mode.is_null() {
1054        return ptr::null_mut();
1055    } else {
1056        CStr::from_ptr(mode)
1057    };
1058
1059    let path = if path.is_null() {
1060        None
1061    } else {
1062        Some(CStr::from_ptr(path))
1063    };
1064
1065    bzopen_or_bzdopen(path, OpenMode::Pointer, mode)
1066}
1067
1068/// Opens a `.bz2` file for reading or writing using a pre-existing file descriptor. Analogous to [`libc::fdopen`].
1069///
1070/// # Safety
1071///
1072/// The caller must guarantee that
1073///
1074/// * `fd` must be a valid file descriptor for the duration of [`BZ2_bzdopen`]
1075/// * Either
1076///     - `mode` is `NULL`
1077///     - `mode` is a null-terminated sequence of bytes
1078///
1079/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
1080#[export_name = prefix!(BZ2_bzdopen)]
1081pub unsafe extern "C" fn BZ2_bzdopen(fd: c_int, mode: *const c_char) -> *mut BZFILE {
1082    let mode = if mode.is_null() {
1083        return ptr::null_mut();
1084    } else {
1085        CStr::from_ptr(mode)
1086    };
1087
1088    bzopen_or_bzdopen(None, OpenMode::FileDescriptor(fd), mode)
1089}
1090
1091/// Reads up to `len` (uncompressed) bytes from the compressed file `b` into the buffer `buf`.
1092///
1093/// Analogous to [`libc::fread`].
1094///
1095/// # Returns
1096///
1097/// Number of bytes read on success, or `-1` on failure.
1098///
1099/// # Safety
1100///
1101/// The caller must guarantee that
1102///
1103/// * Either
1104///     - `b` is `NULL`
1105///     - `b` is initialized with [`BZ2_bzWriteOpen`] or [`BZ2_bzReadOpen`]
1106/// * Either
1107///     - `buf` is `NULL`
1108///     - `buf` is writable for `len` bytes
1109///
1110/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
1111#[export_name = prefix!(BZ2_bzread)]
1112pub unsafe extern "C" fn BZ2_bzread(b: *mut BZFILE, buf: *mut c_void, len: c_int) -> c_int {
1113    BZ2_bzreadHelp(b.as_mut(), buf, len)
1114}
1115
1116unsafe fn BZ2_bzreadHelp(mut b: Option<&mut BZFILE>, buf: *mut c_void, len: c_int) -> c_int {
1117    let mut bzerr = 0;
1118
1119    if let Some(b) = b.as_deref_mut() {
1120        if b.lastErr == ReturnCode::BZ_STREAM_END {
1121            return 0;
1122        }
1123    }
1124
1125    let nread = BZ2_bzReadHelp(Some(&mut bzerr), b, buf, len);
1126    if bzerr == 0 || bzerr == ReturnCode::BZ_STREAM_END as i32 {
1127        nread
1128    } else {
1129        -1
1130    }
1131}
1132
1133/// Absorbs `len` bytes from the buffer `buf`, eventually to be compressed and written to the file.
1134///
1135/// Analogous to [`libc::fwrite`].
1136///
1137/// # Returns
1138///
1139/// The value `len` on success, or `-1` on failure.
1140///
1141/// # Safety
1142///
1143/// The caller must guarantee that
1144///
1145/// * Either
1146///     - `b` is `NULL`
1147///     - `b` is initialized with [`BZ2_bzWriteOpen`] or [`BZ2_bzReadOpen`]
1148/// * Either
1149///     - `buf` is `NULL`
1150///     - `buf` is readable for `len` bytes
1151///
1152/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
1153#[export_name = prefix!(BZ2_bzwrite)]
1154pub unsafe extern "C" fn BZ2_bzwrite(b: *mut BZFILE, buf: *const c_void, len: c_int) -> c_int {
1155    BZ2_bzwriteHelp(b.as_mut(), buf, len)
1156}
1157
1158unsafe fn BZ2_bzwriteHelp(b: Option<&mut BZFILE>, buf: *const c_void, len: c_int) -> c_int {
1159    let mut bzerr = 0;
1160    BZ2_bzWriteHelp(Some(&mut bzerr), b, buf, len);
1161
1162    match bzerr {
1163        0 => len,
1164        _ => -1,
1165    }
1166}
1167
1168/// Flushes a [`BZFILE`].
1169///
1170/// Analogous to [`libc::fflush`].
1171///
1172/// # Safety
1173///
1174/// The caller must guarantee that
1175///
1176/// * Either
1177///     - `b` is `NULL`
1178///     - `b` is initialized with [`BZ2_bzReadOpen`] or [`BZ2_bzWriteOpen`]
1179#[export_name = prefix!(BZ2_bzflush)]
1180pub unsafe extern "C" fn BZ2_bzflush(mut _b: *mut BZFILE) -> c_int {
1181    /* do nothing now... */
1182    0
1183}
1184
1185/// Closes a [`BZFILE`].
1186///
1187/// Analogous to [`libc::fclose`].
1188///
1189/// # Safety
1190///
1191/// The caller must guarantee that
1192///
1193/// * Either
1194///     - `b` is `NULL`
1195///     - `b` is initialized with [`BZ2_bzReadOpen`] or [`BZ2_bzWriteOpen`]
1196#[export_name = prefix!(BZ2_bzclose)]
1197pub unsafe extern "C" fn BZ2_bzclose(b: *mut BZFILE) {
1198    let mut bzerr: c_int = 0;
1199
1200    if b.is_null() {
1201        return;
1202    }
1203
1204    let operation = (*b).operation;
1205    let handle = (*b).handle;
1206
1207    match operation {
1208        Operation::Reading => {
1209            BZ2_bzReadClose(&raw mut bzerr, b);
1210        }
1211        Operation::Writing => {
1212            BZ2_bzWriteCloseHelp(Some(&mut bzerr), b, false as i32, None, None);
1213            if bzerr != 0 {
1214                BZ2_bzWriteCloseHelp(None, b, true as i32, None, None);
1215            }
1216        }
1217    }
1218
1219    if !handle.is_null() && handle != STDIN!() && handle != STDOUT!() {
1220        fclose(handle);
1221    }
1222}
1223
1224const BZERRORSTRINGS: [&str; 16] = [
1225    "OK\0",
1226    "SEQUENCE_ERROR\0",
1227    "PARAM_ERROR\0",
1228    "MEM_ERROR\0",
1229    "DATA_ERROR\0",
1230    "DATA_ERROR_MAGIC\0",
1231    "IO_ERROR\0",
1232    "UNEXPECTED_EOF\0",
1233    "OUTBUFF_FULL\0",
1234    "CONFIG_ERROR\0",
1235    "???\0",
1236    "???\0",
1237    "???\0",
1238    "???\0",
1239    "???\0",
1240    "???\0",
1241];
1242
1243/// Describes the most recent error.
1244///
1245/// # Returns
1246///
1247/// A null-terminated string describing the most recent error status of `b`, and also sets `*errnum` to its numerical value.
1248///
1249/// # Safety
1250///
1251/// The caller must guarantee that
1252///
1253/// * Either
1254///     - `b` is `NULL`
1255///     - `b` is initialized with [`BZ2_bzReadOpen`] or [`BZ2_bzWriteOpen`]
1256/// * `errnum` satisfies the requirements of [`pointer::as_mut`]
1257///
1258/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
1259#[export_name = prefix!(BZ2_bzerror)]
1260pub unsafe extern "C" fn BZ2_bzerror(b: *const BZFILE, errnum: *mut c_int) -> *const c_char {
1261    // The C implementation dereferences `b` unconditionally, we just return "???".
1262    let errnum = errnum.as_mut();
1263    match b.as_ref() {
1264        Some(b) => BZ2_bzerrorHelp(b, errnum),
1265        None => {
1266            if let Some(errnum) = errnum {
1267                *errnum = crate::BZ_PARAM_ERROR;
1268            }
1269            let msg = "BZ2_bzerror was passed a NULL pointer\0";
1270            msg.as_ptr().cast::<c_char>()
1271        }
1272    }
1273}
1274
1275fn BZ2_bzerrorHelp(b: &BZFILE, errnum: Option<&mut c_int>) -> *const c_char {
1276    let err = Ord::min(0, b.lastErr as c_int);
1277    if let Some(errnum) = errnum {
1278        *errnum = err;
1279    };
1280    let msg = match BZERRORSTRINGS.get(-err as usize) {
1281        Some(msg) => msg,
1282        None => "???\0",
1283    };
1284    msg.as_ptr().cast::<c_char>()
1285}
1286
1287#[cfg(test)]
1288mod tests {
1289    use super::*;
1290
1291    #[test]
1292    fn bz_error_file_is_null_ptr() {
1293        let mut errnum = 0;
1294        let ptr = unsafe { BZ2_bzerror(core::ptr::null(), &mut errnum) };
1295        let cstr = unsafe { CStr::from_ptr(ptr) };
1296
1297        assert_eq!(
1298            cstr.to_str().unwrap(),
1299            "BZ2_bzerror was passed a NULL pointer"
1300        )
1301    }
1302
1303    #[test]
1304    fn bz_error_errnum_is_null_ptr() {
1305        let bz_file = BZFILE {
1306            handle: core::ptr::null_mut(),
1307            buf: [0; 5000],
1308            bufN: 0,
1309            strm: bz_stream::zeroed(),
1310            lastErr: ReturnCode::BZ_OK,
1311            operation: Operation::Reading,
1312            initialisedOk: false,
1313        };
1314
1315        unsafe { BZ2_bzerror(&bz_file, core::ptr::null_mut()) };
1316    }
1317
1318    #[test]
1319    fn error_messages() {
1320        let mut bz_file = BZFILE {
1321            handle: core::ptr::null_mut(),
1322            buf: [0; 5000],
1323            bufN: 0,
1324            strm: bz_stream::zeroed(),
1325            lastErr: ReturnCode::BZ_OK,
1326            operation: Operation::Reading,
1327            initialisedOk: false,
1328        };
1329
1330        let return_codes = [
1331            ReturnCode::BZ_OK,
1332            ReturnCode::BZ_RUN_OK,
1333            ReturnCode::BZ_FLUSH_OK,
1334            ReturnCode::BZ_FINISH_OK,
1335            ReturnCode::BZ_STREAM_END,
1336            ReturnCode::BZ_SEQUENCE_ERROR,
1337            ReturnCode::BZ_PARAM_ERROR,
1338            ReturnCode::BZ_MEM_ERROR,
1339            ReturnCode::BZ_DATA_ERROR,
1340            ReturnCode::BZ_DATA_ERROR_MAGIC,
1341            ReturnCode::BZ_IO_ERROR,
1342            ReturnCode::BZ_UNEXPECTED_EOF,
1343            ReturnCode::BZ_OUTBUFF_FULL,
1344            ReturnCode::BZ_CONFIG_ERROR,
1345        ];
1346
1347        for return_code in return_codes {
1348            bz_file.lastErr = return_code;
1349
1350            let mut errnum = 0;
1351            let ptr = unsafe { BZ2_bzerror(&bz_file, &mut errnum) };
1352            assert!(!ptr.is_null());
1353            let cstr = unsafe { CStr::from_ptr(ptr) };
1354
1355            let msg = cstr.to_str().unwrap();
1356
1357            let expected = match return_code {
1358                ReturnCode::BZ_OK => "OK",
1359                ReturnCode::BZ_RUN_OK => "OK",
1360                ReturnCode::BZ_FLUSH_OK => "OK",
1361                ReturnCode::BZ_FINISH_OK => "OK",
1362                ReturnCode::BZ_STREAM_END => "OK",
1363                ReturnCode::BZ_SEQUENCE_ERROR => "SEQUENCE_ERROR",
1364                ReturnCode::BZ_PARAM_ERROR => "PARAM_ERROR",
1365                ReturnCode::BZ_MEM_ERROR => "MEM_ERROR",
1366                ReturnCode::BZ_DATA_ERROR => "DATA_ERROR",
1367                ReturnCode::BZ_DATA_ERROR_MAGIC => "DATA_ERROR_MAGIC",
1368                ReturnCode::BZ_IO_ERROR => "IO_ERROR",
1369                ReturnCode::BZ_UNEXPECTED_EOF => "UNEXPECTED_EOF",
1370                ReturnCode::BZ_OUTBUFF_FULL => "OUTBUFF_FULL",
1371                ReturnCode::BZ_CONFIG_ERROR => "CONFIG_ERROR",
1372            };
1373
1374            assert_eq!(msg, expected);
1375
1376            if (return_code as i32) < 0 {
1377                assert_eq!(return_code as i32, errnum);
1378            } else {
1379                assert_eq!(0, errnum);
1380            }
1381        }
1382    }
1383
1384    #[test]
1385    fn bzclose_write() {
1386        let Some(allocator) = Allocator::DEFAULT else {
1387            return;
1388        };
1389
1390        {
1391            let bzf_ptr: *mut BZFILE = allocator.allocate_zeroed(1).unwrap();
1392            let mut bzerr: c_int = 0;
1393
1394            unsafe {
1395                (*bzf_ptr).operation = Operation::Writing;
1396                (*bzf_ptr).initialisedOk = false;
1397                (*bzf_ptr).handle = core::ptr::null_mut();
1398                BZ2_bzWriteClose64(
1399                    &raw mut bzerr,
1400                    bzf_ptr,
1401                    1, // abandon
1402                    ptr::null_mut(),
1403                    ptr::null_mut(),
1404                    ptr::null_mut(),
1405                    ptr::null_mut(),
1406                );
1407            }
1408        }
1409
1410        {
1411            let bzf_ptr: *mut BZFILE = allocator.allocate_zeroed(1).unwrap();
1412
1413            unsafe {
1414                (*bzf_ptr).operation = Operation::Writing;
1415                (*bzf_ptr).initialisedOk = false;
1416                (*bzf_ptr).handle = core::ptr::null_mut();
1417                BZ2_bzclose(bzf_ptr);
1418            }
1419        }
1420    }
1421
1422    #[test]
1423    fn bzclose_read() {
1424        let Some(allocator) = Allocator::DEFAULT else {
1425            return;
1426        };
1427
1428        {
1429            let bzf_ptr: *mut BZFILE = allocator.allocate_zeroed(1).unwrap();
1430            let mut bzerr: c_int = 0;
1431
1432            unsafe {
1433                (*bzf_ptr).operation = Operation::Reading;
1434                (*bzf_ptr).initialisedOk = false;
1435                BZ2_bzReadClose(&raw mut bzerr, bzf_ptr);
1436            }
1437        }
1438
1439        {
1440            let bzf_ptr: *mut BZFILE = allocator.allocate_zeroed(1).unwrap();
1441
1442            unsafe {
1443                (*bzf_ptr).operation = Operation::Reading;
1444                (*bzf_ptr).initialisedOk = false;
1445                BZ2_bzclose(bzf_ptr);
1446            }
1447        }
1448    }
1449}