Skip to main content

libz_rs_sys/
gz.rs

1use zlib_rs::c_api::*;
2
3use crate::gz::GzMode::GZ_READ;
4use crate::{
5    deflate, deflateEnd, deflateInit2_, deflateReset, inflate, inflateEnd, inflateInit2_,
6    inflateReset, prefix, z_off64_t, z_off_t, zlibVersion,
7};
8use core::cmp::Ordering;
9use core::ffi::{c_char, c_int, c_uint, c_void, CStr};
10use core::ptr;
11use libc::size_t; // FIXME: Switch to core::ffi::c_size_t when it's stable.
12use libc::{O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_TRUNC, O_WRONLY, SEEK_CUR, SEEK_END, SEEK_SET};
13use zlib_rs::deflate::Strategy;
14use zlib_rs::MAX_WBITS;
15
16/// In the zlib C API, this structure exposes just enough of the internal state
17/// of an open [`gzFile`] to support the `gzgetc` C macro. Since Rust code won't be
18/// using that C macro, we define [`gzFile_s`] as an empty structure.
19// For ABI compatibility with zlib and zlib-ng, the first fields in [`GzState`] match
20// what would be in the C version of [`gzFile_s`]. But we don't want new users to rely
21// on this internal implementation, so the Rust [`gzFile_s`] is intentionally opaque.
22#[allow(non_camel_case_types)]
23pub enum gzFile_s {}
24
25/// File handle for an open gzip file.
26#[allow(non_camel_case_types)]
27pub type gzFile = *mut gzFile_s;
28
29// The internals of a gzip file handle (the thing gzFile actually points to, with the
30// public gzFile_s part at the front for ABI compatibility).
31#[repr(C)]
32struct GzState {
33    // Public interface:
34    // These first three fields must match the structure gzFile_s in the C version
35    // of zlib. In the C library, a macro called gzgetc() reads and writes these
36    // fields directly.
37    have: c_uint,       // number of bytes available at next
38    next: *const Bytef, // next byte of uncompressed data
39    pos: i64,           // current offset in uncompressed data stream
40
41    // End of public interface:
42    // All fields after this point are opaque to C code using this library,
43    // so they can be rearranged without breaking compatibility.
44
45    // Fields used for both reading and writing
46    mode: GzMode,
47    fd: c_int, // file descriptor
48    source: Source,
49    want: usize,     // requested buffer size, default is GZBUFSIZE
50    input: *mut u8,  // input buffer (double-sized when writing)
51    in_size: usize,  // usable size of input buffer (See [`gz_init`] for explanation.)
52    output: *mut u8, // output buffer (double-sized when reading)
53    out_size: usize, // size of *output
54    direct: bool,    // true in pass-through mode, false if processing gzip data
55
56    // Fields used just for reading
57    how: How,
58    start: i64,
59    eof: bool,  // whether we have reached the end of the input file
60    past: bool, // whether a read past the end has been requested
61
62    // Fields used just for writing
63    level: i8,
64    strategy: Strategy,
65    reset: bool, // whether a reset is pending after a Z_FINISH
66
67    // Fields used for seek requests
68    skip: i64,  // amount to skip (already rewound if backwards)
69    seek: bool, // whether a seek request is pending
70
71    // Error information
72    err: c_int,         // last error (0 if no error)
73    msg: *const c_char, // error message from last error (NULL if none)
74
75    // zlib inflate or deflate stream
76    stream: z_stream,
77}
78
79impl GzState {
80    fn configure(&mut self, mode: &[u8]) -> Result<(bool, bool), ()> {
81        let mut exclusive = false;
82        let mut cloexec = false;
83
84        for &ch in mode {
85            if ch.is_ascii_digit() {
86                self.level = (ch - b'0') as i8;
87            } else {
88                match ch {
89                    b'r' => self.mode = GzMode::GZ_READ,
90                    b'w' => self.mode = GzMode::GZ_WRITE,
91                    b'a' => self.mode = GzMode::GZ_APPEND,
92                    b'+' => {
93                        // Read+Write mode isn't supported
94                        return Err(());
95                    }
96                    b'b' => {} // binary mode is the default
97                    b'e' => cloexec = true,
98                    b'x' => exclusive = true,
99                    b'f' => self.strategy = Strategy::Filtered,
100                    b'h' => self.strategy = Strategy::HuffmanOnly,
101                    b'R' => self.strategy = Strategy::Rle,
102                    b'F' => self.strategy = Strategy::Fixed,
103                    b'T' => self.direct = true,
104                    _ => {} // for compatibility with zlib-ng, ignore unexpected characters in the mode
105                }
106            }
107        }
108
109        Ok((exclusive, cloexec))
110    }
111
112    // Get the number of bytes allocated for the `self.input` buffer.
113    fn in_capacity(&self) -> usize {
114        match self.mode {
115            GzMode::GZ_WRITE => self.want * 2,
116            _ => self.want,
117        }
118    }
119
120    // Get the number of bytes allocated for the `self.output` buffer.
121    fn out_capacity(&self) -> usize {
122        match self.mode {
123            GzMode::GZ_READ => self.want * 2,
124            _ => self.want,
125        }
126    }
127
128    /// Compute the number of bytes of input buffered in `self`.
129    ///
130    /// # Safety
131    ///
132    /// Either
133    /// - `state.input` is null.
134    /// - `state.stream.next_in .. state.stream.next_in + state.stream.avail_in`
135    ///   is contained in `state.input .. state.input + state.in_size`.
136    ///
137    /// It is almost always the case that one of those two conditions is true
138    /// inside this module. The notable exception is in a specific block within
139    /// `gz_write`, where we temporarily set `state.next_in` to point to a
140    /// caller-supplied buffer to do a zero-copy optimization when compressing
141    /// large inputs.
142    unsafe fn input_len(&self) -> usize {
143        if self.input.is_null() {
144            return 0;
145        }
146
147        // Safety: `next_in .. next_in + avail_in` is a subslice, so the preconditions hold.
148        let end = unsafe { self.stream.next_in.add(self.stream.avail_in as usize) };
149
150        // Safety: the caller guarantees that the input slice of `stream` is a subslice of `input`.
151        (unsafe { end.offset_from(self.input) }) as _
152    }
153}
154
155// Gzip operating modes
156// NOTE: These values match what zlib-ng uses.
157#[allow(non_camel_case_types)]
158#[derive(Clone, Copy, Debug, PartialEq, Eq)]
159enum GzMode {
160    GZ_NONE = 0,
161    GZ_READ = 7247,
162    GZ_WRITE = 31153,
163    GZ_APPEND = 1,
164}
165
166// gzip read strategies
167// NOTE: These values match what zlib-ng uses.
168#[derive(Debug, PartialEq, Eq)]
169enum How {
170    Look = 0, // look for a gzip header
171    Copy = 1, // copy input directly
172    Gzip = 2, // decompress a gzip stream
173}
174
175const GZBUFSIZE: usize = 128 * 1024;
176
177#[cfg(feature = "rust-allocator")]
178use zlib_rs::allocate::RUST as ALLOCATOR;
179
180#[cfg(not(feature = "rust-allocator"))]
181#[cfg(feature = "c-allocator")]
182use zlib_rs::allocate::C as ALLOCATOR;
183
184#[cfg(not(feature = "rust-allocator"))]
185#[cfg(not(feature = "c-allocator"))]
186compile_error!("Either rust-allocator or c-allocator feature is required");
187
188// The different ways to specify the source for gzopen_help
189enum Source {
190    Path(*const c_char),
191    Fd(c_int),
192}
193
194/// Open a gzip file for reading or writing.
195///
196/// # Returns
197///
198/// * If successful, an opaque handle that the caller can later free with [`gzfree`]
199/// * On error, a null pointer
200///
201/// # Safety
202///
203/// The caller must ensure that `path` and `mode` point to valid C strings. If the
204/// return value is non-NULL, caller must delete it using only [`gzclose`].
205///
206/// [`gzfree`]: crate::z_stream
207#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzopen64))]
208pub unsafe extern "C" fn gzopen64(path: *const c_char, mode: *const c_char) -> gzFile {
209    if path.is_null() {
210        return ptr::null_mut();
211    }
212    let source = Source::Path(path);
213    unsafe { gzopen_help(source, mode) }
214}
215
216/// Open a gzip file for reading or writing.
217///
218/// # Returns
219///
220/// * If successful, an opaque handle that the caller can later free with [`gzfree`]
221/// * On error, a null pointer
222///
223/// # Safety
224///
225/// The caller must ensure that `path` and `mode` point to valid C strings. If the
226/// return value is non-NULL, caller must delete it using only [`gzclose`].
227///
228/// [`gzfree`]: crate::z_stream
229#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzopen))]
230pub unsafe extern "C" fn gzopen(path: *const c_char, mode: *const c_char) -> gzFile {
231    if path.is_null() {
232        return ptr::null_mut();
233    }
234    let source = Source::Path(path);
235    unsafe { gzopen_help(source, mode) }
236}
237
238/// Given an open file descriptor, prepare to read or write a gzip file.
239/// NOTE: This is similar to [`gzopen`], but for cases where the caller already
240/// has the file open.
241///
242/// # Returns
243///
244/// * If successful, an opaque handle that the caller can later free with [`gzfree`]
245/// * On error, a null pointer
246///
247/// The file descriptor is not used until the next `gz{read, write, seek, close}` operation,
248/// so `gzdopen` will not detect if `fd` is invalid (unless `fd == -1` which returns a null
249/// pointer).
250///
251/// # Safety
252///
253/// The caller must ensure that `mode` points to a valid C string. If the
254/// return value is non-NULL, caller must delete it using only [`gzclose`].
255///
256/// [`gzfree`]: crate::z_stream
257#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzdopen))]
258pub unsafe extern "C" fn gzdopen(fd: c_int, mode: *const c_char) -> gzFile {
259    // Safety: the caller is responsible for `mode` being a non-null C string.
260    unsafe { gzopen_help(Source::Fd(fd), mode) }
261}
262
263/// Internal implementation shared by gzopen and gzdopen.
264///
265/// # Safety
266///
267/// The caller must ensure that mode points to a valid C string.
268unsafe fn gzopen_help(source: Source, mode: *const c_char) -> gzFile {
269    if mode.is_null() {
270        return ptr::null_mut();
271    }
272
273    let Some(state) = ALLOCATOR.allocate_zeroed_raw::<GzState>() else {
274        return ptr::null_mut();
275    };
276    // Safety: the allocate_zeroed_raw call above ensures that the allocated block
277    // has the right size and alignment to be used as a GzState. And because the
278    // allocator zeroes the allocated space, all the GzState fields are initialized.
279    let state = unsafe { state.cast::<GzState>().as_mut() };
280    state.in_size = 0;
281    state.out_size = 0;
282    state.want = GZBUFSIZE;
283    state.msg = ptr::null();
284
285    state.mode = GzMode::GZ_NONE;
286    state.level = crate::Z_DEFAULT_COMPRESSION as i8;
287    state.strategy = Strategy::Default;
288    state.direct = false;
289
290    state.stream = z_stream::default();
291    state.stream.zalloc = Some(ALLOCATOR.zalloc);
292    state.stream.zfree = Some(ALLOCATOR.zfree);
293    state.stream.opaque = ALLOCATOR.opaque;
294
295    let mode = unsafe { CStr::from_ptr(mode) };
296    let Ok((exclusive, cloexec)) = state.configure(mode.to_bytes()) else {
297        // Safety: state is a valid pointer allocated in this function and not used after freeing
298        unsafe { free_state(state) };
299        return ptr::null_mut();
300    };
301
302    // Must specify read, write, or append
303    if state.mode == GzMode::GZ_NONE {
304        // Safety: we know state is a valid pointer because it was allocated earlier in this
305        // function, and it is not used after the free because we return immediately afterward.
306        unsafe { free_state(state) };
307        return ptr::null_mut();
308    }
309
310    // Can't force transparent read
311    if state.mode == GzMode::GZ_READ {
312        if state.direct {
313            // Safety: we know state is a valid pointer because it was allocated earlier in this
314            // function, and it is not used after the free because we return immediately afterward.
315            unsafe { free_state(state) };
316            return ptr::null_mut();
317        }
318        state.direct = true; // Assume an empty file for now. Later, we'll check for a gzip header.
319    }
320
321    // Open the file unless the caller passed a file descriptor.
322    match source {
323        Source::Fd(fd) => {
324            state.fd = fd;
325            state.source = Source::Fd(fd);
326        }
327        Source::Path(path) => {
328            // Save the path name for error messages
329            // FIXME: support Windows wide characters for compatibility with zlib-ng
330            let cloned_path = unsafe { gz_strdup(path) };
331            if cloned_path.is_null() {
332                unsafe { free_state(state) };
333                return ptr::null_mut();
334            }
335            state.source = Source::Path(cloned_path);
336            let mut oflag = 0;
337
338            #[cfg(target_os = "linux")]
339            {
340                oflag |= libc::O_LARGEFILE;
341            }
342            #[cfg(target_os = "windows")]
343            {
344                oflag |= libc::O_BINARY;
345            }
346            if cloexec {
347                #[cfg(target_os = "linux")]
348                {
349                    oflag |= libc::O_CLOEXEC;
350                }
351            }
352
353            if state.mode == GzMode::GZ_READ {
354                oflag |= O_RDONLY;
355            } else {
356                oflag |= O_WRONLY | O_CREAT;
357                if exclusive {
358                    oflag |= O_EXCL;
359                }
360                if state.mode == GzMode::GZ_WRITE {
361                    oflag |= O_TRUNC;
362                } else {
363                    oflag |= O_APPEND;
364                }
365            }
366            // FIXME: support _wopen for WIN32
367            // Safety: We constructed state.path as a valid C string above.
368            state.fd = unsafe { libc::open(cloned_path, oflag, 0o666) };
369        }
370    }
371
372    if state.fd == -1 {
373        // Safety: we know state is a valid pointer because it was allocated earlier in this
374        // function, and it is not used after the free because we return immediately afterward.
375        unsafe { free_state(state) };
376        return ptr::null_mut();
377    }
378
379    // NOTE: the `lseek64` return value is ignored, an invalid file descriptor will only be
380    // detected by the next `gz{read, write, seek, close}`.
381    if state.mode == GzMode::GZ_APPEND {
382        let _ = lseek64(state.fd, 0, SEEK_END); // so gzoffset() is correct
383        state.mode = GzMode::GZ_WRITE; // simplify later checks
384    }
385
386    if state.mode == GzMode::GZ_READ {
387        // Save the current position for rewinding
388        state.start = lseek64(state.fd, 0, SEEK_CUR) as _;
389        if state.start == -1 {
390            state.start = 0;
391        }
392    }
393
394    // Initialize stream
395    gz_reset(state);
396
397    // FIXME change this to core::ptr::from_mut(state).cast::<gzFile_s>() once MSRV >= 1.76
398    (state as *mut GzState).cast::<gzFile_s>()
399}
400
401// Format a fake file path corresponding to an fd, for use in error messages.
402fn fd_path(buf: &mut [u8; 27], fd: c_int) -> &CStr {
403    // This is equivalent to `format!("<fd:{}>\0", fd)`, but without the dependency on std.
404
405    use core::fmt::Write;
406
407    // The array size is chosen so that any file descriptor value will fit. We need space for 6
408    // characters, plus space for the largest decimal value for the `c_int` type. On some systems
409    // the c_int type can actually be 64 bits. The `i64::MIN` value has 20 digits, and the minus
410    // sign, for a total of 6 + 20 + 1 = 27.
411    struct Writer<'a> {
412        buf: &'a mut [u8; 27],
413        len: usize,
414    }
415
416    impl Write for Writer<'_> {
417        fn write_str(&mut self, s: &str) -> core::fmt::Result {
418            let Some(dst) = self.buf.get_mut(self.len..self.len + s.len()) else {
419                return Err(core::fmt::Error);
420            };
421
422            dst.copy_from_slice(s.as_bytes());
423            self.len += s.len();
424
425            Ok(())
426        }
427    }
428
429    let mut w = Writer { buf, len: 0 };
430
431    write!(w, "<fd:{fd}>\0").unwrap();
432
433    unsafe { CStr::from_ptr(w.buf[..w.len].as_ptr().cast()) }
434}
435
436// Reset the internal state of an open gzip stream according to
437// its mode (read or write)
438fn gz_reset(state: &mut GzState) {
439    state.have = 0; // no output data available
440    if state.mode == GzMode::GZ_READ {
441        state.eof = false; // not at end of file
442        state.past = false; // have not read past end yet
443        state.how = How::Look; // look for gzip header
444    } else {
445        state.reset = false; // no deflateReset pending
446    }
447    state.seek = false; // no seek request pending
448                        // Safety: It is valid to pass a null msg pointer to `gz_error`.
449    unsafe { gz_error(state, None) }; // clear error status
450    state.pos = 0; // no uncompressed data yet
451    state.stream.avail_in = 0; // no input data yet
452}
453
454// Set the error message for a gzip stream, and deallocate any
455// previously set error message.
456//
457// # Arguments
458//
459// * `state` - An initialized stream state.
460// * `err_msg` - `None` or `Some(err, msg)`. In the latter case, this function will
461//   make a deep copy of the `msg` string, so `msg` need not remain in scope after the
462//   call to this function.
463//
464// # Safety
465//
466// - `state` must be a properly constructed `GzState`, e.g. as produced by [`gzopen`]
467unsafe fn gz_error(state: &mut GzState, err_msg: Option<(c_int, &str)>) {
468    if !state.msg.is_null() {
469        // NOTE: zlib-ng has a special case here: it skips the deallocation if
470        // state.err == Z_MEM_ERROR. However, we always set state.msg to null
471        // when state.err is set to Z_MEM_ERROR, so that case is unreachable
472        // here.
473        unsafe { deallocate_cstr(state.msg.cast_mut()) };
474        state.msg = ptr::null_mut();
475    }
476
477    match err_msg {
478        None => {
479            state.err = Z_OK;
480        }
481        Some((err, msg)) => {
482            // On error, set state.have to 0 so that the `gzgetc()` C macro fails
483            if err != Z_OK && err != Z_BUF_ERROR {
484                state.have = 0;
485            }
486
487            // Set the error code
488            state.err = err;
489
490            // For an out of memory error, don't bother trying to allocate space for an error string.
491            // ([`gzerror`] will provide literal string as a special case for OOM errors.)
492            if err == Z_MEM_ERROR {
493                return;
494            }
495
496            // Format the error string to include the file path.
497            // Safety: `gzopen` and `gzdopen` ensure that `state.path` is a non-null C string
498            let sep = ": ";
499            let buf = &mut [0u8; 27];
500            state.msg = match state.source {
501                Source::Path(path) => unsafe {
502                    gz_strcat(&[CStr::from_ptr(path).to_str().unwrap(), sep, msg])
503                },
504                Source::Fd(fd) => unsafe {
505                    gz_strcat(&[fd_path(buf, fd).to_str().unwrap(), sep, msg])
506                },
507            };
508
509            if state.msg.is_null() {
510                state.err = Z_MEM_ERROR;
511            }
512        }
513    }
514}
515
516// Deallocate a GzState structure and all heap-allocated fields inside it.
517//
518// # Safety
519//
520// - The `state` object and all heap-allocated fields within it must have been obtained
521//   using `ALLOCATOR`.
522// - The caller must not use the `state` after passing it to this function.
523unsafe fn free_state(state: *mut GzState) {
524    if state.is_null() {
525        return;
526    }
527    // Safety: `deallocate_cstr` accepts null pointers or C strings, and in this
528    // module we use only `ALLOCATOR` to allocate strings assigned to these fields.
529    unsafe {
530        match (*state).source {
531            Source::Path(path) => deallocate_cstr(path.cast_mut()),
532            Source::Fd(_) => { /* fd is owned by the caller */ }
533        }
534        deallocate_cstr((*state).msg.cast_mut());
535    }
536    // Safety: state is a valid GzState, and free_buffers checks for null
537    // input and output pointers internally.
538    unsafe { free_buffers(state.as_mut().unwrap()) };
539
540    // Safety: The caller has ensured that `state` was allocated using `ALLOCATOR`.
541    unsafe { ALLOCATOR.deallocate(state, 1) };
542}
543
544// Deallocate the input and output buffers in a GzState.
545//
546// # Safety
547//
548// * `state` must have been obtained from [`gzopen`] or [`gzdopen`].
549unsafe fn free_buffers(state: &mut GzState) {
550    if !state.input.is_null() {
551        // Safety: state.input is always allocated using ALLOCATOR, and
552        // its allocation size is stored in state.in_size.
553        unsafe { ALLOCATOR.deallocate(state.input, state.in_capacity()) };
554        state.input = ptr::null_mut();
555    }
556    state.in_size = 0;
557    if !state.output.is_null() {
558        // Safety: state.output is always allocated using ALLOCATOR, and
559        // its allocation size is stored in state.out_size.
560        unsafe { ALLOCATOR.deallocate(state.output, state.out_capacity()) };
561        state.output = ptr::null_mut();
562    }
563    state.out_size = 0;
564}
565
566// Free a string that was allocated with `ALLOCATOR`
567//
568// # Safety
569//
570// * `s` must be either null or a null-terminated C string that was allocated with `ALLOCATOR`.
571// * If `s` is not null, the length of the string (including the null terminator byte) must
572//   exactly match the allocation size.
573unsafe fn deallocate_cstr(s: *mut c_char) {
574    if s.is_null() {
575        return;
576    }
577    // Safety: We checked above that `s` is non-null, and the caller ensured it
578    // is a C string allocated with `ALLOCATOR`.
579    unsafe { ALLOCATOR.deallocate::<c_char>(s, libc::strlen(s) + 1) };
580}
581
582/// Close an open gzip file and free the internal data structures referenced by the file handle.
583///
584/// # Returns
585///
586/// * [`Z_ERRNO`] if closing the file failed
587/// * [`Z_OK`] otherwise
588///
589/// # Safety
590///
591/// `file` must be one of the following:
592/// - A file handle must have been obtained from a function in this library, such as [`gzopen`].
593/// - A null pointer.
594///
595/// This function may be called at most once for any file handle.
596///
597/// `file` must not be used after this call returns, as the memory it references may have
598/// been deallocated.
599#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzclose))]
600pub unsafe extern "C" fn gzclose(file: gzFile) -> c_int {
601    let Some(state) = (unsafe { file.cast::<GzState>().as_ref() }) else {
602        return Z_STREAM_ERROR;
603    };
604
605    match state.mode {
606        GzMode::GZ_READ => unsafe { gzclose_r(file) },
607        GzMode::GZ_WRITE | GzMode::GZ_APPEND | GzMode::GZ_NONE => unsafe { gzclose_w(file) },
608    }
609}
610
611/// Close a gzip file that was opened for reading.
612///
613/// # Returns
614///
615/// * Z_OK if `state` has no outstanding error and the file is closed successfully.
616/// * A Z_ error code if the `state` is null or the file close operation fails.
617///
618/// # Safety
619///
620/// `file` must be one of the following:
621/// - A file handle must have been obtained from a function in this library, such as [`gzopen`].
622/// - A null pointer.
623///
624/// `file` must not be used after this call returns, as the memory it references may have
625/// been deallocated.
626#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzclose_r))]
627pub unsafe extern "C" fn gzclose_r(file: gzFile) -> c_int {
628    let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
629        return Z_STREAM_ERROR;
630    };
631
632    // Check that we're reading.
633    if state.mode != GzMode::GZ_READ {
634        return Z_STREAM_ERROR;
635    }
636
637    // Process any buffered input.
638    if state.in_size != 0 {
639        // Safety: state.stream was properly initialized as a z_stream in gzopen_help.
640        unsafe { inflateEnd(&mut state.stream as *mut z_stream) };
641    }
642
643    let err = match state.err {
644        Z_BUF_ERROR => Z_BUF_ERROR,
645        _ => Z_OK,
646    };
647
648    let ret = match unsafe { libc::close(state.fd) } {
649        0 => err,
650        _ => Z_ERRNO,
651    };
652
653    // Delete the underlying allocation.
654    // Safety: The `state` reference is not used beyond this point.
655    unsafe { free_state(file.cast::<GzState>()) };
656
657    ret
658}
659
660/// Close a gzip file that was opened for writing.
661///
662/// # Returns
663///
664/// * Z_OK if `state` has no outstanding error and the file is closed successfully.
665/// * A Z_ error code if the `state` is null or the file close operation fails.
666///
667/// # Safety
668///
669/// `file` must be one of the following:
670/// - A file handle must have been obtained from a function in this library, such as [`gzopen`].
671/// - A null pointer.
672///
673/// `file` must not be used after this call returns, as the memory it references may have
674/// been deallocated.
675#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzclose_w))]
676pub unsafe extern "C" fn gzclose_w(file: gzFile) -> c_int {
677    let mut ret = Z_OK;
678
679    let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
680        return Z_STREAM_ERROR;
681    };
682
683    // Check that we're writing.
684    if state.mode != GzMode::GZ_WRITE {
685        return Z_STREAM_ERROR;
686    }
687
688    // Check for a pending seek request
689    if state.seek {
690        state.seek = false;
691        if gz_zero(state, state.skip as _).is_err() {
692            ret = state.err;
693        }
694    }
695
696    // Compress (if not in direct mode) and output any data left in the input buffer.
697    if gz_comp(state, Z_FINISH).is_err() {
698        ret = state.err;
699    }
700    if state.in_size != 0 && !state.direct {
701        // Safety: state.stream was properly initialized as a z_stream in gzopen_help.
702        unsafe { deflateEnd(&mut state.stream as *mut z_stream) };
703    }
704    if unsafe { libc::close(state.fd) } == -1 {
705        ret = Z_ERRNO;
706    }
707
708    // Delete the underlying allocation.
709    // Safety: The `state` reference is not used beyond this point.
710    unsafe { free_state(file.cast::<GzState>()) };
711
712    ret
713}
714
715/// Set the internal buffer size used by this library's functions for `file` to
716/// `size`.  The default buffer size is 128 KB.  This function must be called
717/// after [`gzopen`] or [`gzdopen`], but before any other calls that read or write
718/// the file (including [`gzdirect`]).  The buffer memory allocation is always
719/// deferred to the first read or write.  Three times `size` in buffer space is
720/// allocated.
721///
722/// # Returns
723///
724/// * `0` on success.
725/// * `-1` on failure.
726///
727/// # Arguments
728///
729/// * `file` - file handle.
730/// * `size` - requested buffer size in bytes.
731///
732/// # Safety
733///
734/// `file` must be one of the following:
735/// - A file handle must have been obtained from a function in this library, such as [`gzopen`].
736/// - A null pointer.
737#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzbuffer))]
738pub unsafe extern "C" fn gzbuffer(file: gzFile, size: c_uint) -> c_int {
739    let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
740        return -1;
741    };
742    if state.mode != GzMode::GZ_READ && state.mode != GzMode::GZ_WRITE {
743        return -1;
744    }
745
746    // Make sure we haven't already allocated memory.
747    if state.in_size != 0 {
748        return -1;
749    }
750
751    // Check and set requested size.
752    let size = size as usize;
753    if size.checked_mul(2).is_none() {
754        // We must be able to double the requested size, because one of the two
755        // buffers (state.input in write mode, state.output in read mode) will be
756        // allocated as twice the requested size. Note: Because the C API specifies `size`
757        // is an unsigned int, but we use usize to represent the buffer sizes internally,
758        // this error condition is impossible to trigger in the common case where int
759        // is 32 bits and usize is 64 bits.
760        return -1;
761    }
762
763    // Use a minimum buffer size of 8 to work with flush semantics elsewhere in the implementation.
764    state.want = Ord::max(size, 8);
765
766    0
767}
768
769/// Retrieve the zlib error code and a human-readable string description of
770/// the most recent error on a gzip file stream.
771///
772/// # Arguments
773///
774/// * `file` - A gzip file handle, or null
775/// * `errnum` - A pointer to a C integer in which the zlib error code should be
776///   written, or null if the caller does not need the numeric error code.
777///
778/// # Returns
779///
780/// * A pointer to a null-terminated C string describing the error, if `file` is non-null
781///   and has an error
782/// * A pointer to an empty (zero-length), null-terminated C string, if `file` is non-null
783///   but has no error
784/// * Null otherwise
785///
786/// # Safety
787///
788/// `file` must be one of the following:
789/// - A file handle obtained from [`gzopen`] or [`gzdopen`].
790/// - A null pointer.
791///
792/// If this function returns a non-null string, the caller must not modifiy or
793/// deallocate the string.
794///
795/// If `errnum` is non-null, it must point to an address at which a [`c_int`] may be written.
796#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzerror))]
797pub unsafe extern "C" fn gzerror(file: gzFile, errnum: *mut c_int) -> *const c_char {
798    // Get internal structure and check integrity
799    let Some(state) = (unsafe { file.cast::<GzState>().as_ref() }) else {
800        return ptr::null();
801    };
802    if state.mode != GzMode::GZ_READ && state.mode != GzMode::GZ_WRITE {
803        return ptr::null();
804    }
805
806    // Return error information
807    if !errnum.is_null() {
808        // Safety:
809        // * `errnum` is non-null
810        // * The caller is responsible for ensuring that `errnum` points to writable
811        //   memory with proper alignment.
812        unsafe { *errnum = state.err };
813    }
814    if state.err == Z_MEM_ERROR {
815        b"out of memory\0".as_ptr().cast::<c_char>()
816    } else if state.msg.is_null() {
817        b"\0".as_ptr().cast::<c_char>()
818    } else {
819        state.msg
820    }
821}
822
823/// Clear the error and end-of-file state for `file`.
824///
825/// # Arguments
826///
827/// * `file` - A gzip file handle, or null
828///
829/// # Safety
830///
831/// `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
832#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzclearerr))]
833pub unsafe extern "C" fn gzclearerr(file: gzFile) {
834    // Get internal structure and check integrity
835    let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
836        return;
837    };
838    if state.mode != GzMode::GZ_READ && state.mode != GzMode::GZ_WRITE {
839        return;
840    }
841
842    // Clear error and EOF
843    if state.mode == GzMode::GZ_READ {
844        state.eof = false;
845        state.past = false;
846    }
847
848    // Safety: we've checked state, and gz_error supports a null message argument.
849    unsafe { gz_error(state, None) };
850}
851
852/// Check whether a read operation has tried to read beyond the end of `file`.
853///
854/// # Returns
855///
856/// * 1 if the end-of-file indicator is set. Note that this indicator is set only
857///   if a read tries to go past the end of the input. If the last read request
858///   attempted to read exactly the number of bytes remaining in the file, the
859///   end-of-file indicator will not be set.
860/// * 0 the end-of-file indicator is not set or `file` is null
861///
862/// # Arguments
863///
864/// * `file` - A gzip file handle, or null
865///
866/// # Safety
867///
868/// `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
869#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzeof))]
870pub unsafe extern "C" fn gzeof(file: gzFile) -> c_int {
871    // Get internal structure and check integrity
872    let Some(state) = (unsafe { file.cast::<GzState>().as_ref() }) else {
873        return 0;
874    };
875    if state.mode != GzMode::GZ_READ {
876        return 0;
877    }
878
879    // Per the semantics described in the function comments above, the return value
880    // is based on state.past, rather than state.eof.
881    state.past as _
882}
883
884/// Check whether `file` is in direct mode (reading or writing literal bytes without compression).
885///
886/// NOTE: If `gzdirect` is called immediately after [`gzopen`] or [`gzdopen`], it may allocate
887/// buffers internally to read the file header and determine whether the content is a gzip file.
888/// If [`gzbuffer`] is used, it should be called before `gzdirect`.
889///
890/// # Returns
891///
892/// 0 if `file` is null.
893///
894/// If `file` is being read,
895/// * 1 if the contents are being read directly, without decompression.
896/// * 0 if the contents are being decompressed when read.
897///
898/// If `file` is being written,
899/// * 1 if transparent mode was requested upon open (with the `"wT"` mode flag for [`gzopen`]).
900/// * 0 otherwise.
901///
902/// # Arguments
903///
904/// * `file` - A gzip file handle, or null
905///
906/// # Safety
907///
908/// `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
909#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzdirect))]
910pub unsafe extern "C" fn gzdirect(file: gzFile) -> c_int {
911    let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
912        return 0;
913    };
914
915    // In write mode, the direct flag was set in `gzopen_help`. In read mode, the
916    // direct status is determined lazily on the first read operation. If the first
917    // read hasn't happened yet, we can look at the header now to determine if the
918    // file is in gzip format.
919    if state.mode == GzMode::GZ_READ && state.how == How::Look && state.have == 0 {
920        let _ = unsafe { gz_look(state) };
921    }
922
923    state.direct as _
924}
925
926/// Read and decompress up to `len` uncompressed bytes from `file` into `buf`.  If
927/// the input file is not in gzip format, `gzread` copies up to `len` bytes into
928/// the buffer directly from the file.
929///
930/// After reaching the end of a gzip stream in the input, `gzread` will continue
931/// to read, looking for another gzip stream.  Any number of gzip streams may be
932/// concatenated in the input file, and will all be decompressed by `gzread()`.
933/// If something other than a gzip stream is encountered after a gzip stream,
934/// `gzread` ignores that remaining trailing garbage (and no error is returned).
935///
936/// `gzread` can be used to read a gzip file that is being concurrently written.
937/// Upon reaching the end of the input, `gzread` will return with the available
938/// data.  If the error code returned by [`gzerror`] is `Z_OK` or `Z_BUF_ERROR`,
939/// then [`gzclearerr`] can be used to clear the end of file indicator in order
940/// to permit `gzread` to be tried again.  `Z_OK` indicates that a gzip stream
941/// was completed on the last `gzread`.  `Z_BUF_ERROR` indicates that the input
942/// file ended in the middle of a gzip stream.  Note that `gzread` does not return
943/// `-1` in the event of an incomplete gzip stream.  This error is deferred until
944/// [`gzclose`], which will return `Z_BUF_ERROR` if the last gzread ended in the
945/// middle of a gzip stream.  Alternatively, `gzerror` can be used before `gzclose`
946/// to detect this case.
947///
948/// If the unsigned value `len` is too large to fit in the signed return type
949/// `c_int`, then nothing is read, `-1` is returned, and the error state is set to
950/// `Z_STREAM_ERROR`.
951///
952/// # Returns
953///
954/// * The number of uncompressed bytes read from the file into `buf`, which may
955///   be smaller than `len` if there is insufficient data in the file.
956/// * `-1` on error.
957///
958/// # Arguments
959///
960/// * `file` - A gzip file handle, or null.
961/// * `buf` - Buffer where the read data should be stored. The caller retains ownership of this buffer.
962/// * `len` - Number of bytes to attempt to read into `buf`.
963///
964/// # Safety
965///
966/// - `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
967/// - The caller must ensure that `buf` points to at least `len` writable bytes.
968#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzread))]
969pub unsafe extern "C" fn gzread(file: gzFile, buf: *mut c_void, len: c_uint) -> c_int {
970    let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
971        return -1;
972    };
973
974    // Check that we're reading and that there's no (serious) error.
975    if state.mode != GzMode::GZ_READ || (state.err != Z_OK && state.err != Z_BUF_ERROR) {
976        return -1;
977    }
978
979    // Check that the requested number of bytes can be represented by the return type.
980    if c_int::try_from(len).is_err() {
981        const MSG: &str = "request does not fit in an int";
982        // Safety: we confirmed above that state is valid.
983        unsafe { gz_error(state, Some((Z_STREAM_ERROR, MSG))) };
984        return -1;
985    }
986
987    // With the initial checks passed, try the actual read.
988    // Safety: The caller is responsible for ensuring that `buf` points to >= `len` writable bytes.
989    let got = unsafe { gz_read(state, buf.cast::<u8>(), len as usize) };
990
991    // Check for an error
992    if got == 0 && state.err != Z_OK && state.err != Z_BUF_ERROR {
993        -1
994    } else {
995        got as _
996    }
997}
998
999/// Read and decompress up to `nitems` items of size `size` from `file` into `buf`,
1000/// otherwise operating as [`gzread`] does. This duplicates the interface of
1001/// C stdio's `fread()`, with `size_t` request and return types.
1002///
1003/// `gzfread` returns the number of full items read of size `size`, or zero if
1004/// the end of the file was reached and a full item could not be read, or if
1005/// there was an error.  [`gzerror`] must be consulted if zero is returned in
1006/// order to determine if there was an error.  If the multiplication of `size` and
1007/// `nitems` overflows, i.e. the product does not fit in a `size_t`, then nothing
1008/// is read, zero is returned, and the error state is set to `Z_STREAM_ERROR`.
1009///
1010/// In the event that the end of file is reached and only a partial item is
1011/// available at the end, i.e. the remaining uncompressed data length is not a
1012/// multiple of `size`, then the final partial item is nevertheless read into `buf`
1013/// and the end-of-file flag is set.  The length of the partial item read is not
1014/// provided, but could be inferred from the result of [`gztell`].  This behavior
1015/// is the same as the behavior of `fread` implementations in common libraries,
1016/// but it prevents the direct use of `gzfread` to read a concurrently written
1017/// file, resetting and retrying on end-of-file, when `size` is not 1.
1018///
1019/// # Returns
1020///
1021/// - The number of complete object of size `size` read into `buf`.
1022/// - `0` on error or end-of-file.
1023///
1024/// # Safety
1025///
1026/// - `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
1027/// - The caller must ensure that `buf` points to at least `size * nitems` writable bytes.
1028#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzfread))]
1029pub unsafe extern "C" fn gzfread(
1030    buf: *mut c_void,
1031    size: size_t,
1032    nitems: size_t,
1033    file: gzFile,
1034) -> size_t {
1035    if size == 0 || buf.is_null() {
1036        return 0;
1037    }
1038
1039    let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
1040        return 0;
1041    };
1042
1043    // Check that we're reading and that there's no (serious) error.
1044    if state.mode != GzMode::GZ_READ || (state.err != Z_OK && state.err != Z_BUF_ERROR) {
1045        return 0;
1046    }
1047
1048    // Compute the number of bytes to read, and make sure it fits in a size_t.
1049    let Some(len) = size.checked_mul(nitems) else {
1050        const MSG: &str = "request does not fit in a size_t";
1051        unsafe { gz_error(state, Some((Z_STREAM_ERROR, MSG))) };
1052        return 0;
1053    };
1054
1055    if len == 0 {
1056        len
1057    } else {
1058        // Safety: The caller is responsible for ensuring that `buf` points to at least
1059        // `len = size * nitems` writable bytes.
1060        (unsafe { gz_read(state, buf.cast::<u8>(), len) }) / size
1061    }
1062}
1063
1064// Attempt to read enough bytes from the input to fill the supplied `buf`.
1065//
1066// # Returns
1067//
1068// The number of bytes read into `buf`. Note: A return value of zero means either end-of-file
1069// or an error. `state.err` must be consulted to determine which.
1070//
1071// # Safety
1072//
1073// * `state` must have been properly initialized, e.g. by [`gzopen_help`].
1074// * `buf` must point to at least `len` bytes of writable memory.
1075unsafe fn gz_read(state: &mut GzState, mut buf: *mut u8, mut len: usize) -> usize {
1076    if len == 0 {
1077        return 0;
1078    }
1079
1080    // Process a skip request.
1081    if state.seek {
1082        state.seek = false;
1083        if gz_skip(state, state.skip).is_err() {
1084            return 0;
1085        }
1086    }
1087
1088    // Loop until we get enough bytes or reach the end of the file.
1089    let mut got = 0;
1090    loop {
1091        // Set n to the maximum amount of len that fits in an unsigned int.
1092        let mut n = Ord::min(len, c_uint::MAX as usize);
1093
1094        // First just try copying data from the output buffer. Note: The output
1095        // buffer contains bytes that have been decompressed by `state.stream` and
1096        // are waiting to be consumed - or, in direct mode, it contains bytes read
1097        // directly from the underlying file descriptor.
1098        if state.have != 0 {
1099            n = Ord::min(n, state.have as usize);
1100            // Safety:
1101            // * n <= state.have, and there are `state.have` readable bytes starting
1102            //   at `state.next`.
1103            // * n <= len, and the caller is responsible for ensuring that `buf`
1104            //   points to at least `len` writable bytes.
1105            // * `state.next` points into an internal buffer not visible outside the
1106            //   `GzState`, and `buf` is supplied by the caller, so the source and
1107            //   destination are guaranteed not to overlap.
1108            unsafe { ptr::copy_nonoverlapping(state.next, buf, n) };
1109            state.next = unsafe { state.next.add(n) };
1110            state.have -= n as c_uint;
1111        } else if state.eof && state.stream.avail_in == 0 {
1112            // The output buffer is empty, and we're at the end of the file.
1113            state.past = true; // Tried to read past end
1114            break;
1115        } else if state.how == How::Look || n < state.in_size * 2 {
1116            // For small len or a new stream, load more data from the file descriptor into
1117            // the output buffer. Note: If we haven't scanned the file header yet, gz_fetch
1118            // will read the header and determine whether to use decompression or direct read.
1119            if unsafe { gz_fetch(state) }.is_err() {
1120                return 0;
1121            }
1122
1123            // Now that we've tried reading, we can try to copy from the output buffer.
1124            // The copy above assures that we will leave with space in the output buffer,
1125            // allowing at least one gzungetc() to succeed.
1126            continue;
1127        } else if state.how == How::Copy {
1128            // For a large requested read length, in copy mode (meaning that the input
1129            // file is not gzip and we're returning its contents directly), bypass the
1130            // output buffer and read from the file descriptor directly into buf.
1131            // Safety: n <= len, and the caller is responsible for ensuring that `buf`
1132            // points to at least `len` writable bytes.
1133            let Ok(bytes_read) = (unsafe { gz_load(state, buf, n) }) else {
1134                return 0;
1135            };
1136            n = bytes_read;
1137        } else {
1138            // We are in gzip mode, and the requested read size is large. Get more data and
1139            // decompress it directly into buf, bypassing stream.output.
1140            debug_assert_eq!(state.how, How::Gzip);
1141            state.stream.avail_out = n as c_uint;
1142            state.stream.next_out = buf;
1143            if unsafe { gz_decomp(state) }.is_err() {
1144                return 0;
1145            }
1146            n = state.have as usize;
1147            state.have = 0;
1148        }
1149
1150        // Update progress
1151        len -= n;
1152        buf = unsafe { buf.add(n) };
1153        got += n;
1154        state.pos += n as i64;
1155
1156        if len == 0 {
1157            break;
1158        }
1159    }
1160
1161    got
1162}
1163
1164// Given an unsigned value `x`, determine whether `x` is larger than the maximum
1165// signed 64-bit offset value.
1166// Note: This can happen only on targets where the C unsigned int is a 64-bit value.
1167macro_rules! gt_off {
1168    ($x:expr) => {
1169        core::mem::size_of_val(&$x) == core::mem::size_of::<i64>()
1170            && $x as usize > i64::MAX as usize
1171    };
1172}
1173
1174// Skip len uncompressed bytes of output.
1175//
1176// # Returns
1177//
1178// - `Ok` on success.
1179// - `Err` on error.
1180fn gz_skip(state: &mut GzState, mut len: i64) -> Result<(), ()> {
1181    // Skip over len bytes or reach end-of-file, whichever comes first.
1182    while len != 0 {
1183        // Skip over whatever is in output buffer.
1184        if state.have != 0 {
1185            // For consistency with zlib-ng, we use `gt_off` to check whether the value
1186            // of `state.have` is too large to be represented as a signed 64-bit offset.
1187            // This case can be triggered only if the platform has 64-bit C ints and
1188            // `state.have` is >= 2^63.
1189            let n = if gt_off!(state.have) || state.have as i64 > len {
1190                len as usize
1191            } else {
1192                state.have as usize
1193            };
1194            state.have -= n as c_uint;
1195            // Safety: `n` <= `state.have` and there are at least `state.have` accessible
1196            // bytes after `state.next` in the buffer.
1197            state.next = unsafe { state.next.add(n) };
1198            state.pos += n as i64;
1199            len -= n as i64;
1200        } else if state.eof && state.stream.avail_in == 0 {
1201            // Output buffer empty -- return if we're at the end of the input.
1202            break;
1203        } else {
1204            // Need more data to skip -- load up output buffer.
1205            // Get more output, looking for header if required.
1206            // Safety: `state` is valid, and `state.have` is zero in this branch.
1207            if unsafe { gz_fetch(state) }.is_err() {
1208                return Err(());
1209            }
1210        }
1211    }
1212    Ok(())
1213}
1214
1215// Given a gzip file opened for reading, check for a gzip header, and set
1216// `state.direct` accordingly.
1217//
1218// # Returns
1219//
1220// * `Ok` on success
1221// * `Err` on failure
1222//
1223// # Safety
1224//
1225// `state` must have been properly initialized, e.g. by [`gzopen_help`].
1226unsafe fn gz_look(state: &mut GzState) -> Result<(), ()> {
1227    // Allocate buffers if needed.
1228    if state.input.is_null() {
1229        let capacity = state.in_capacity();
1230        state.in_size = capacity;
1231        let Some(input) = ALLOCATOR.allocate_slice_raw::<u8>(capacity) else {
1232            // Safety: The caller confirmed the validity of state.
1233            unsafe { gz_error(state, Some((Z_MEM_ERROR, "out of memory"))) };
1234            return Err(());
1235        };
1236        state.input = input.as_ptr();
1237
1238        if state.output.is_null() {
1239            let capacity = state.out_capacity();
1240            state.out_size = capacity;
1241            let Some(output) = ALLOCATOR.allocate_slice_raw::<u8>(capacity) else {
1242                // Safety: The caller confirmed the validity of state, and free_buffers checks
1243                // for null input and output pointers internally.
1244                unsafe { free_buffers(state) };
1245                // Safety: The caller confirmed the validity of state.
1246                unsafe { gz_error(state, Some((Z_MEM_ERROR, "out of memory"))) };
1247                return Err(());
1248            };
1249            state.output = output.as_ptr();
1250        }
1251
1252        // Allocate memory for inflate.
1253        state.stream.avail_in = 0;
1254        state.stream.next_in = ptr::null_mut();
1255        // Safety: `gzopen_help` initialized `state.stream`'s `zalloc`, `zfree`, and
1256        // `opaque` fields as needed by `inflateInit2`.
1257        if unsafe {
1258            inflateInit2_(
1259                &mut state.stream as *mut z_stream,
1260                MAX_WBITS + 16,
1261                zlibVersion(),
1262                core::mem::size_of::<z_stream>() as i32,
1263            )
1264        } != Z_OK
1265        {
1266            // Safety: The caller confirmed the validity of `state`, and `free_buffers` checks
1267            // for null input and output pointers internally.
1268            unsafe { free_buffers(state) };
1269            // Safety: The caller confirmed the validity of `state`.
1270            unsafe { gz_error(state, Some((Z_MEM_ERROR, "out of memory"))) };
1271            return Err(());
1272        }
1273    }
1274
1275    // Get at least the magic bytes in the input buffer.
1276    if state.stream.avail_in < 2 {
1277        // `gz_avail` attempts to read as much data as available from the underlying file
1278        // into the input buffer. This will hopefully give us enough bytes to check for a
1279        // gzip file header.
1280        // Safety: The caller confirmed the validity of `state`.
1281        if unsafe { gz_avail(state) }? == 0 {
1282            return Ok(());
1283        }
1284    }
1285
1286    // Look for gzip magic bytes.
1287    // Note: If we are reading a partially written gzip file, and all that is available to read is
1288    // the first byte of the gzip magic number, we cannot tell whether what follows will be the
1289    // rest of the gzip magic. For simplicity, we assume that the writer of a gzip file will
1290    // write the header (or at least the magic number at the start of the header) atomically,
1291    // so if our initial read found a single byte it is a sufficient indication that the file
1292    // is not in gzip format.
1293    // Safety: `gz_avail` ensures that `next_in` points to at least `avail_in` readable bytes.
1294    if state.stream.avail_in > 1
1295        && unsafe { *state.stream.next_in } == 31
1296        && unsafe { *state.stream.next_in.add(1) } == 139
1297    {
1298        // Safety: We initialized `state.stream` with `inflateInit2` above.
1299        unsafe { inflateReset(&mut state.stream as *mut z_stream) };
1300        state.how = How::Gzip;
1301        state.direct = false;
1302        return Ok(());
1303    }
1304
1305    // No gzip header. If we were decoding gzip before, the remaining bytes
1306    // are trailing garbage that can be ignored.
1307    if !state.direct {
1308        state.stream.avail_in = 0;
1309        state.eof = true;
1310        state.have = 0;
1311        return Ok(());
1312    }
1313
1314    // The file is not in gzip format, so enable direct mode, and copy all
1315    // buffered input to the output.
1316    // Safety:
1317    // * `state.output` was allocated above.
1318    // * `gz_avail` ensures that `next_in` points to at least `avail_in` readable bytes.
1319    unsafe {
1320        ptr::copy_nonoverlapping(
1321            state.stream.next_in,
1322            state.output,
1323            state.stream.avail_in as usize,
1324        )
1325    };
1326    state.next = state.output;
1327    state.have = state.stream.avail_in;
1328    state.stream.avail_in = 0;
1329    state.how = How::Copy;
1330    state.direct = true;
1331
1332    Ok(())
1333}
1334
1335// Load data into the input buffer and set the eof flag if the last of the data has been
1336// loaded.
1337//
1338// # Returns
1339//
1340// * `Ok(n)` on success, where `n` is the number of bytes available (`state.stream.avail_in`)
1341// * `Err` on error
1342//
1343// # Safety
1344//
1345// `state` must have been properly initialized, e.g. by [`gzopen_help`].
1346unsafe fn gz_avail(state: &mut GzState) -> Result<usize, ()> {
1347    if state.err != Z_OK && state.err != Z_BUF_ERROR {
1348        return Err(());
1349    }
1350    if !state.eof {
1351        if state.stream.avail_in != 0 {
1352            // Copy any remaining input to the start. Note: The source and destination are
1353            // within the same buffer, so this may be an overlapping copy.
1354            unsafe {
1355                ptr::copy(
1356                    state.stream.next_in,
1357                    state.input,
1358                    state.stream.avail_in as usize,
1359                )
1360            };
1361        }
1362        let got = unsafe {
1363            gz_load(
1364                state,
1365                state.input.add(state.stream.avail_in as usize),
1366                state.in_size - state.stream.avail_in as usize,
1367            )
1368        }?;
1369        state.stream.avail_in += got as uInt;
1370        state.stream.next_in = state.input;
1371    }
1372    Ok(state.stream.avail_in as usize)
1373}
1374
1375// Read data from `state`'s underlying file descriptor into a buffer.
1376//
1377// # Returns
1378//
1379// * `Ok(n)` on success, where `n` is the number of bytes read.
1380// * `Err` on error
1381//
1382// # Arguments
1383//
1384// * `state` - gzip file handle.
1385// * `buf` - address at which the data read from the file should be stored.
1386// * `size` - number of bytes to read
1387//
1388// # Safety
1389//
1390// * `state` must have been properly initialized, e.g. by [`gzopen_help`].
1391// * `buf` mut point to a writable block of at least `len` bytes.
1392unsafe fn gz_load(state: &mut GzState, buf: *mut u8, len: usize) -> Result<usize, ()> {
1393    let mut have = 0;
1394    let mut ret = 0;
1395    while have < len {
1396        ret = unsafe { libc::read(state.fd, buf.add(have).cast::<_>(), (len - have) as _) };
1397        if ret <= 0 {
1398            break;
1399        }
1400        have += ret as usize;
1401    }
1402    if ret < 0 {
1403        unsafe { gz_error(state, Some((Z_ERRNO, "read error"))) }; // FIXME implement `zstrerror`
1404        return Err(());
1405    }
1406    if ret == 0 {
1407        state.eof = true;
1408    }
1409    Ok(have)
1410}
1411
1412// Fetch data and put it in the output buffer, decompressing if needed. If the header has
1413// not been read yet, parse it to determine whether the file is in gzip format.
1414//
1415// # Returns
1416//
1417// * `Ok` on success.
1418// * `Err` on error. Check [`gzerror`] for more information on the error condition.
1419//
1420// # Safety
1421// * `state` must have been properly initialized, e.g. by [`gzopen_help`].
1422// * `state.have` must be zero.
1423//
1424unsafe fn gz_fetch(state: &mut GzState) -> Result<(), ()> {
1425    loop {
1426        // Process the input, which may cause state transitions among Look/Gzip/Copy.
1427        match &state.how {
1428            How::Look => {
1429                // -> Look, Copy (only if never Gzip), or Gzip
1430                unsafe { gz_look(state) }?;
1431                if state.how == How::Look {
1432                    return Ok(());
1433                }
1434            }
1435            How::Copy => {
1436                // -> Copy
1437                let bytes_read = unsafe { gz_load(state, state.output, state.out_size) }?;
1438                state.next = state.output;
1439                state.have += bytes_read as uInt;
1440                return Ok(());
1441            }
1442            How::Gzip => {
1443                // -> Gzip or Look (if at end of gzip stream)
1444                state.stream.avail_out = state.out_size as c_uint;
1445                state.stream.next_out = state.output;
1446                unsafe { gz_decomp(state) }?;
1447            }
1448        }
1449
1450        // Keep trying until either:
1451        // - we have some data in the output buffer (measured by state.have)
1452        // - or both the input buffer and the underling file have been fully consumed.
1453        if state.have != 0 || (state.eof && state.stream.avail_in == 0) {
1454            break;
1455        }
1456    }
1457
1458    Ok(())
1459}
1460
1461// Decompress from input and put the result in `state`'s output buffer.
1462// On return, `state.have` and `state.next` denote the just decompressed
1463// data.  If the gzip stream completes, `state.how` is reset to `Look`
1464// to look for the next gzip stream or raw data once the data in the output
1465// buffer is consumed.
1466//
1467// # Returns
1468//
1469// * `Ok` on success.
1470// * `Err` on error.
1471//
1472// # Safety
1473//
1474// * `state` must have been properly initialized, e.g. by [`gzopen_help`].
1475unsafe fn gz_decomp(state: &mut GzState) -> Result<(), ()> {
1476    // Decompress into the output buffer until we run out of either input data
1477    // or space in the output buffer.
1478    let had = state.stream.avail_out;
1479    loop {
1480        // Get more input for inflate().
1481        if state.stream.avail_in == 0 && unsafe { gz_avail(state) }.is_err() {
1482            return Err(());
1483        }
1484        if state.stream.avail_in == 0 {
1485            unsafe { gz_error(state, Some((Z_BUF_ERROR, "unexpected end of file"))) };
1486            break;
1487        }
1488
1489        // Decompress and handle errors.
1490        match unsafe { inflate(&mut state.stream, Z_NO_FLUSH) } {
1491            Z_STREAM_ERROR | Z_NEED_DICT => {
1492                const MSG: &str = "internal error: inflate stream corrupt";
1493                unsafe { gz_error(state, Some((Z_STREAM_ERROR, MSG))) };
1494                return Err(());
1495            }
1496            Z_MEM_ERROR => {
1497                unsafe { gz_error(state, Some((Z_MEM_ERROR, "out of memory"))) };
1498                return Err(());
1499            }
1500            Z_DATA_ERROR => {
1501                // FIXME gz_error(state, Z_DATA_ERROR, strm->msg == NULL ? "compressed data error" : strm->msg);
1502                unsafe { gz_error(state, Some((Z_DATA_ERROR, "compressed data error"))) };
1503                return Err(());
1504            }
1505            Z_STREAM_END => {
1506                // If the gzip stream completed successfully, look for another.
1507                state.how = How::Look;
1508                break;
1509            }
1510            _ => {}
1511        }
1512
1513        if state.stream.avail_out == 0 {
1514            break;
1515        }
1516    }
1517
1518    // Update the size and start of the data in the output buffer.
1519    state.have = had - state.stream.avail_out;
1520    state.next = unsafe { state.stream.next_out.sub(state.have as usize) };
1521
1522    Ok(())
1523}
1524
1525/// Compress and write the len uncompressed bytes at buf to file.
1526///
1527/// # Returns
1528///
1529/// - The number of uncompressed bytes written, on success.
1530/// - Or 0 in case of error.
1531///
1532/// # Safety
1533///
1534/// - `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
1535/// - `buf` must point to at least `len` bytes of readable memory.
1536#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzwrite))]
1537pub unsafe extern "C" fn gzwrite(file: gzFile, buf: *const c_void, len: c_uint) -> c_int {
1538    let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
1539        return 0;
1540    };
1541
1542    // Check that we're writing and that there's no error.
1543    if state.mode != GzMode::GZ_WRITE || state.err != Z_OK {
1544        return 0;
1545    }
1546
1547    // Check that the requested number of bytes can be represented by the return type.
1548    if c_int::try_from(len).is_err() {
1549        const MSG: &str = "requested length does not fit in int";
1550        // Safety: we confirmed above that state is valid.
1551        unsafe { gz_error(state, Some((Z_DATA_ERROR, MSG))) };
1552        return 0;
1553    }
1554
1555    // Also check that the requested number of bytes can be represented by usize,
1556    // which gz_write uses internally.
1557    let Ok(len) = usize::try_from(len) else {
1558        const MSG: &str = "requested length does not fit in usize";
1559        // Safety: we confirmed above that state is valid.
1560        unsafe { gz_error(state, Some((Z_DATA_ERROR, MSG))) };
1561        return 0;
1562    };
1563
1564    // Safety: We validated state above, and the caller is responsible for ensuring
1565    // that buf points to at least len bytes of readable memory.
1566    unsafe { gz_write(state, buf, len) }
1567}
1568
1569/// Compress and write `nitems` items of size `size` from `buf` to `file`, duplicating
1570/// the interface of C stdio's `fwrite`, with `size_t` request and return types.
1571///
1572/// # Returns
1573///
1574/// - The number of full items written of size `size` on success.
1575/// - Zero on error.
1576///
1577/// Note: If the multiplication of `size` and `nitems` overflows, i.e. the product does
1578/// not fit in a `size_t`, then nothing is written, zero is returned, and the error state
1579/// is set to `Z_STREAM_ERROR`.
1580///
1581/// # Safety
1582///
1583/// - `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
1584/// - The caller must ensure that `buf` points to at least `size * nitems` readable bytes.
1585#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzfwrite))]
1586pub unsafe extern "C" fn gzfwrite(
1587    buf: *const c_void,
1588    size: size_t,
1589    nitems: size_t,
1590    file: gzFile,
1591) -> size_t {
1592    if size == 0 || buf.is_null() {
1593        return 0;
1594    }
1595
1596    let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
1597        return 0;
1598    };
1599
1600    // Check that we're writing and that there's no error.
1601    if state.mode != GzMode::GZ_WRITE || state.err != Z_OK {
1602        return 0;
1603    }
1604
1605    // Compute the number of bytes to write, and make sure it fits in a size_t.
1606    let Some(len) = size.checked_mul(nitems) else {
1607        const MSG: &str = "request does not fit in a size_t";
1608        unsafe { gz_error(state, Some((Z_STREAM_ERROR, MSG))) };
1609        return 0;
1610    };
1611
1612    if len == 0 {
1613        len
1614    } else {
1615        // Safety: The caller is responsible for ensuring that `buf` points to at least
1616        // `len = size * nitems` readable bytes.
1617        (unsafe { gz_write(state, buf, len) }) as size_t / size
1618    }
1619}
1620
1621// Internal implementation of `gzwrite`.
1622//
1623// # Returns
1624// - The number of uncompress bytes written, on success.
1625// - Or 0 in case of error.
1626//
1627// # Safety
1628//
1629/// - `state` must have been properly initialized, e.g. by [`gzopen_help`].
1630/// - `buf` must point to at least `len` bytes of readable memory.
1631unsafe fn gz_write(state: &mut GzState, mut buf: *const c_void, mut len: usize) -> c_int {
1632    // If len is zero, avoid unnecessary operations.
1633    if len == 0 {
1634        return 0;
1635    }
1636
1637    // Allocate memory if this is the first time through.
1638    if state.input.is_null() && gz_init(state).is_err() {
1639        return 0;
1640    }
1641
1642    if state.seek {
1643        state.seek = false;
1644        if gz_zero(state, state.skip as _).is_err() {
1645            return 0;
1646        }
1647    }
1648
1649    let put = len as c_int;
1650
1651    // For small len, copy to input buffer, otherwise compress directly.
1652    if len < state.in_size {
1653        // Copy to input buffer, compress when full.
1654        loop {
1655            if state.stream.avail_in == 0 {
1656                state.stream.next_in = state.input;
1657            }
1658            // Safety: `state.stream.next_in` points into the buffer starting at `state.input`.
1659            let have = unsafe { state.input_len() };
1660            let copy = Ord::min(state.in_size.saturating_sub(have), len);
1661            // Safety: The caller is responsible for ensuring that buf points to at least len readable
1662            // bytes, and copy is <= len.
1663            unsafe { ptr::copy(buf, state.input.add(have).cast::<c_void>(), copy) };
1664            state.stream.avail_in += copy as c_uint;
1665            state.pos += copy as i64;
1666            buf = unsafe { buf.add(copy) };
1667            len -= copy;
1668            if len != 0 && gz_comp(state, Z_NO_FLUSH).is_err() {
1669                return 0;
1670            }
1671            if len == 0 {
1672                break;
1673            }
1674        }
1675    } else {
1676        // Consume any data left in the input buffer.
1677        if state.stream.avail_in != 0 && gz_comp(state, Z_NO_FLUSH).is_err() {
1678            return 0;
1679        }
1680
1681        // Directly compress user buffer to file.
1682        // Note: For this operation, we temporarily break the invariant that
1683        // `state.stream.next_in` points to somewhere in the `state.input` buffer.
1684        let save_next_in = state.stream.next_in;
1685        state.stream.next_in = buf.cast::<_>();
1686        loop {
1687            let n = Ord::min(len, c_uint::MAX as usize) as c_uint;
1688            state.stream.avail_in = n;
1689            state.pos += n as i64;
1690            if gz_comp(state, Z_NO_FLUSH).is_err() {
1691                return 0;
1692            }
1693            len -= n as usize;
1694            if len == 0 {
1695                break;
1696            }
1697        }
1698        state.stream.next_in = save_next_in;
1699    }
1700
1701    // Input was all buffered or compressed.
1702    put
1703}
1704
1705// Compress `len` null bytes to output.
1706//
1707// # Returns
1708//
1709// - `Ok` on success.
1710// - `Err` on error.
1711fn gz_zero(state: &mut GzState, mut len: usize) -> Result<(), ()> {
1712    // Consume whatever is left in the input buffer.
1713    if state.stream.avail_in != 0 && gz_comp(state, Z_NO_FLUSH).is_err() {
1714        return Err(());
1715    }
1716
1717    // Compress `len` zeros.
1718    let mut first = true;
1719    while len != 0 {
1720        let n = Ord::min(state.in_size, len);
1721        if first {
1722            // Safety: `state.input` is non-null here, either because it was initialized
1723            // before this function was called (enabling the `state.stream.avail_in != 0`
1724            // case in the check above) or because the call to `gz_comp` initialized it.
1725            // All initialization paths in this module ensure that, when `state.input` is
1726            // non-null, it points to `state.in_size` bytes of writable memory. Here we
1727            // are writing `n` bytes, where `n` is initialized above to be <= `state.in_size`.
1728            unsafe { state.input.write_bytes(0u8, n) };
1729            first = false;
1730        }
1731        state.stream.avail_in = n as _;
1732        state.stream.next_in = state.input;
1733        state.pos += n as i64;
1734        if gz_comp(state, Z_NO_FLUSH).is_err() {
1735            return Err(());
1736        }
1737        len -= n;
1738    }
1739
1740    Ok(())
1741}
1742
1743// Initialize `state` for writing a gzip file.  Mark initialization by setting
1744// `state.input` to non-null.
1745//
1746// # Returns
1747//
1748// - `Ok` on success.
1749// - `Err` on error.
1750fn gz_init(state: &mut GzState) -> Result<(), ()> {
1751    // Allocate input buffer.
1752    // The buffer is twice as big as state.want, but we set in_size to half the
1753    // buffer size (i.e. state.in_size == state.want). The reason for this is to
1754    // ensure that we always have state.want bytes available for exclusive use
1755    // by gzprintf.
1756    let capacity = state.in_capacity();
1757    state.in_size = capacity / 2;
1758    let Some(input) = ALLOCATOR.allocate_slice_raw::<u8>(capacity) else {
1759        // Safety: The caller confirmed the validity of state.
1760        unsafe { gz_error(state, Some((Z_MEM_ERROR, "out of memory"))) };
1761        return Err(());
1762    };
1763    state.input = input.as_ptr();
1764    // Note: zlib-ng fills the input buffer with zeroes here, but it's unneeded.
1765
1766    // Only need output buffer and deflate state if compressing.
1767    if !state.direct {
1768        // Allocate output buffer.
1769        let capacity = state.out_capacity();
1770        state.out_size = capacity;
1771        let Some(output) = ALLOCATOR.allocate_slice_raw::<u8>(capacity) else {
1772            unsafe { free_buffers(state) };
1773            // Safety: The caller confirmed the validity of state.
1774            unsafe { gz_error(state, Some((Z_MEM_ERROR, "out of memory"))) };
1775            return Err(());
1776        };
1777        state.output = output.as_ptr();
1778
1779        // Allocate deflate memory, set up for gzip compression.
1780        state.stream.zalloc = Some(ALLOCATOR.zalloc);
1781        state.stream.zfree = Some(ALLOCATOR.zfree);
1782        state.stream.opaque = ALLOCATOR.opaque;
1783        const DEF_MEM_LEVEL: c_int = 8;
1784        if unsafe {
1785            deflateInit2_(
1786                &mut state.stream,
1787                state.level as _,
1788                Z_DEFLATED,
1789                MAX_WBITS + 16,
1790                DEF_MEM_LEVEL,
1791                state.strategy as _,
1792                zlibVersion(),
1793                core::mem::size_of::<z_stream>() as _,
1794            )
1795        } != Z_OK
1796        {
1797            unsafe { free_buffers(state) };
1798            // Safety: The caller confirmed the validity of state.
1799            unsafe { gz_error(state, Some((Z_MEM_ERROR, "out of memory"))) };
1800            return Err(());
1801        }
1802        state.stream.next_in = ptr::null_mut();
1803    }
1804
1805    // Note: zlib-ng sets state.size = state.want here to mark the state as initialized.
1806    // We don't have state.size, so gz_write looks for a non-null state.input buffer
1807    // (which we allocated above) to tell if the state has been initialized.
1808
1809    // Initialize write buffer if compressing.
1810    if !state.direct {
1811        state.stream.avail_out = state.out_size as _;
1812        state.stream.next_out = state.output;
1813        state.next = state.stream.next_out;
1814    }
1815
1816    Ok(())
1817}
1818
1819// Compress whatever is at avail_in and next_in (unless in direct mode) and write
1820// to the output file.
1821//
1822// # Returns
1823//
1824// - `Ok` on success.
1825// - `Err` on error.
1826fn gz_comp(state: &mut GzState, flush: c_int) -> Result<(), ()> {
1827    // Allocate memory if this is the first time through
1828    if state.input.is_null() && gz_init(state).is_err() {
1829        return Err(());
1830    }
1831
1832    // Write directly if requested.
1833    if state.direct {
1834        let got = unsafe {
1835            libc::write(
1836                state.fd,
1837                state.stream.next_in.cast::<c_void>(),
1838                state.stream.avail_in as _,
1839            )
1840        };
1841        if got < 0 || got as c_uint != state.stream.avail_in {
1842            // FIXME implement zstrerror and use it instead of a hard-coded error message here.
1843            unsafe { gz_error(state, Some((Z_ERRNO, "write error"))) };
1844            return Err(());
1845        }
1846        state.stream.avail_in = 0;
1847        return Ok(());
1848    }
1849
1850    // Check for a pending reset.
1851    if state.reset {
1852        // Don't start a new gzip stream unless there is data to write.
1853        if state.stream.avail_in == 0 {
1854            return Ok(());
1855        }
1856        // Safety: `state.reset` is set only in `gz_comp`, which first initializes
1857        // `state.stream` using `deflateInit2_`.
1858        let _ = unsafe { deflateReset(&mut state.stream) };
1859        state.reset = false;
1860    }
1861
1862    // Run deflate on the provided input until it produces no more output.
1863    let mut ret = Z_OK;
1864    loop {
1865        // Write out current buffer contents if full, or if flushing, but if
1866        // doing Z_FINISH then don't write until we get to Z_STREAM_END.
1867        if state.stream.avail_out == 0
1868            || (flush != Z_NO_FLUSH && (flush != Z_FINISH || ret == Z_STREAM_END))
1869        {
1870            // Safety: Within this gz module, `state.stream.next` and `state.stream.next_out`
1871            // always point within the same allocated object, `state.stream.output`.
1872            let have = unsafe { state.stream.next_out.offset_from(state.next) };
1873            if have < 0 {
1874                const MSG: &str = "corrupt internal state in gz_comp";
1875                unsafe { gz_error(state, Some((Z_STREAM_ERROR, MSG))) };
1876                return Err(());
1877            }
1878            if have != 0 {
1879                let ret = unsafe { libc::write(state.fd, state.next.cast::<c_void>(), have as _) };
1880                if ret != have as _ {
1881                    unsafe { gz_error(state, Some((Z_ERRNO, "write error"))) };
1882                    return Err(());
1883                }
1884            }
1885            if state.stream.avail_out == 0 {
1886                state.stream.avail_out = state.out_size as _;
1887                state.stream.next_out = state.output;
1888            }
1889            state.next = state.stream.next_out;
1890        }
1891
1892        // Compress.
1893        let mut have = state.stream.avail_out;
1894        ret = unsafe { deflate(&mut state.stream, flush) };
1895        if ret == Z_STREAM_ERROR {
1896            const MSG: &str = "internal error: deflate stream corrupt";
1897            unsafe { gz_error(state, Some((Z_STREAM_ERROR, MSG))) };
1898            return Err(());
1899        }
1900        have -= state.stream.avail_out;
1901
1902        if have == 0 {
1903            break;
1904        }
1905    }
1906
1907    // If that completed a deflate stream, allow another to start.
1908    if flush == Z_FINISH {
1909        state.reset = true;
1910    }
1911
1912    Ok(())
1913}
1914
1915/// Flush all pending output buffered in `file`. The parameter `flush` is interpreted
1916/// the same way as in the [`deflate`] function. The return value is the zlib error
1917/// number (see [`gzerror`]). `gzflush` is permitted only when writing.
1918///
1919/// # Returns
1920///
1921/// - `Z_OK` on success.
1922/// - a `Z_` error code on error.
1923///
1924/// # Safety
1925///
1926/// - `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
1927#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzflush))]
1928pub unsafe extern "C" fn gzflush(file: gzFile, flush: c_int) -> c_int {
1929    let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
1930        return Z_STREAM_ERROR;
1931    };
1932
1933    // Check that we're writing and that there's no error.
1934    if state.mode != GzMode::GZ_WRITE || state.err != Z_OK {
1935        return Z_STREAM_ERROR;
1936    }
1937
1938    // Check flush parameter.
1939    if !(0..=Z_FINISH).contains(&flush) {
1940        return Z_STREAM_ERROR;
1941    }
1942
1943    // Check for seek request.
1944    if state.seek {
1945        state.seek = false;
1946        if gz_zero(state, state.skip as _).is_err() {
1947            return state.err;
1948        }
1949    }
1950
1951    // Compress remaining data with requested flush.
1952    let _ = gz_comp(state, flush);
1953    state.err
1954}
1955
1956/// Return the starting position for the next [`gzread`] or [`gzwrite`] on `file`.
1957/// This position represents a number of bytes in the uncompressed data stream,
1958/// and is zero when starting, even if appending or reading a gzip stream from
1959/// the middle of a file using [`gzdopen`].
1960///
1961/// # Returns
1962///
1963/// * The number of bytes prior to the current read or write position in the
1964///   uncompressed data stream, on success.
1965/// * -1 on error.
1966///
1967/// # Safety
1968///
1969/// - `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
1970#[cfg_attr(feature = "export-symbols", export_name = prefix!(gztell64))]
1971pub unsafe extern "C" fn gztell64(file: gzFile) -> z_off64_t {
1972    let Some(state) = (unsafe { file.cast::<GzState>().as_ref() }) else {
1973        return -1;
1974    };
1975
1976    // Check integrity.
1977    if state.mode != GzMode::GZ_READ && state.mode != GzMode::GZ_WRITE {
1978        // Unreachable if `file` was initialized with `gzopen` or `gzdopen`.
1979        return -1;
1980    }
1981
1982    // Return position.
1983    match state.seek {
1984        true => (state.pos + state.skip) as z_off64_t,
1985        false => state.pos as z_off64_t,
1986    }
1987}
1988
1989/// Return the starting position for the next [`gzread`] or [`gzwrite`] on `file`.
1990/// This position represents a number of bytes in the uncompressed data stream,
1991/// and is zero when starting, even if appending or reading a gzip stream from
1992/// the middle of a file using [`gzdopen`].
1993///
1994/// # Returns
1995///
1996/// * The number of bytes prior to the current read or write position in the
1997///   uncompressed data stream, on success.
1998/// * -1 on error.
1999///
2000/// # Safety
2001///
2002/// - `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
2003#[cfg_attr(feature = "export-symbols", export_name = prefix!(gztell))]
2004pub unsafe extern "C" fn gztell(file: gzFile) -> z_off_t {
2005    z_off_t::try_from(unsafe { gztell64(file) }).unwrap_or(-1)
2006}
2007
2008/// Return the current compressed (actual) read or write offset of `file`.  This
2009/// offset includes the count of bytes that precede the gzip stream, for example
2010/// when appending or when using [`gzdopen`] for reading. When reading, the
2011/// offset does not include as yet unused buffered input. This information can
2012//  be used for a progress indicator.
2013///
2014/// # Returns
2015///
2016/// * The number of bytes prior to the current read or write position in the
2017///   compressed data stream, on success.
2018/// * -1 on error.
2019///
2020/// # Safety
2021///
2022/// - `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
2023#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzoffset64))]
2024pub unsafe extern "C" fn gzoffset64(file: gzFile) -> z_off64_t {
2025    let Some(state) = (unsafe { file.cast::<GzState>().as_ref() }) else {
2026        return -1;
2027    };
2028
2029    // Check integrity.
2030    if state.mode != GzMode::GZ_READ && state.mode != GzMode::GZ_WRITE {
2031        // Unreachable if `file` was initialized with `gzopen` or `gzdopen`.
2032        return -1;
2033    }
2034
2035    // Compute and return effective offset in file.
2036    let offset = lseek64(state.fd, 0, SEEK_CUR) as z_off64_t;
2037    if offset == -1 {
2038        return -1;
2039    }
2040
2041    // When reading, don't count buffered input.
2042    match state.mode {
2043        GzMode::GZ_READ => offset - state.stream.avail_in as z_off64_t,
2044        GzMode::GZ_NONE | GzMode::GZ_WRITE | GzMode::GZ_APPEND => offset,
2045    }
2046}
2047
2048/// Return the current compressed (actual) read or write offset of `file`.  This
2049/// offset includes the count of bytes that precede the gzip stream, for example
2050/// when appending or when using [`gzdopen`] for reading. When reading, the
2051/// offset does not include as yet unused buffered input. This information can
2052//  be used for a progress indicator.
2053///
2054/// # Returns
2055///
2056/// * The number of bytes prior to the current read or write position in the
2057///   compressed data stream, on success.
2058/// * -1 on error.
2059///
2060/// # Safety
2061///
2062/// - `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
2063#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzoffset))]
2064pub unsafe extern "C" fn gzoffset(file: gzFile) -> z_off_t {
2065    z_off_t::try_from(unsafe { gzoffset64(file) }).unwrap_or(-1)
2066}
2067
2068/// Compress and write `c`, converted to an unsigned 8-bit char, into `file`.
2069///
2070/// # Returns
2071///
2072///  - The value that was written, on success.
2073///  - `-1` on error.
2074///
2075/// # Safety
2076///
2077/// - `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
2078#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzputc))]
2079pub unsafe extern "C" fn gzputc(file: gzFile, c: c_int) -> c_int {
2080    let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
2081        return -1;
2082    };
2083
2084    // Check that we're writing and that there's no error.
2085    if state.mode != GzMode::GZ_WRITE || state.err != Z_OK {
2086        return -1;
2087    }
2088
2089    // Check for seek request.
2090    if state.seek {
2091        state.seek = false;
2092        if gz_zero(state, state.skip as _).is_err() {
2093            return -1;
2094        }
2095    }
2096
2097    // Try writing to input buffer for speed (state.input == null if buffer not initialized).
2098    if !state.input.is_null() {
2099        if state.stream.avail_in == 0 {
2100            state.stream.next_in = state.input;
2101        }
2102        // Safety: `state.stream.next_in` points into the buffer starting at `state.input`.
2103        // (This is an invariant maintained throughout this module, except for a specific
2104        // block within `gz_write` that does not call any function that might call `gzputc`.)
2105        let have = unsafe { state.input_len() };
2106        if have < state.in_size {
2107            // Safety: `input` has `in_size` bytes, and `have` < `in_size`.
2108            unsafe { *state.input.add(have) = c as u8 };
2109            state.stream.avail_in += 1;
2110            state.pos += 1;
2111            return c & 0xff;
2112        }
2113    }
2114
2115    // No room in buffer or not initialized, use gz_write.
2116    let buf = [c as u8];
2117    // Safety: We have confirmed that `state` is valid, and `buf` contains 1 readable byte of data.
2118    match unsafe { gz_write(state, buf.as_ptr().cast::<c_void>(), 1) } {
2119        1 => c & 0xff,
2120        _ => -1,
2121    }
2122}
2123
2124/// Compress and write the given null-terminated string `s` to file, excluding
2125/// the terminating null character.
2126///
2127/// # Returns
2128///
2129/// - the number of characters written, on success.
2130/// - `-1` in case of error.
2131///
2132/// # Safety
2133///
2134/// - `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
2135/// - `s` must point to a null-terminated C string.
2136#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzputs))]
2137pub unsafe extern "C" fn gzputs(file: gzFile, s: *const c_char) -> c_int {
2138    let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
2139        return -1;
2140    };
2141
2142    if s.is_null() {
2143        return -1;
2144    }
2145
2146    // Check that we're writing and that there's no error.
2147    if state.mode != GzMode::GZ_WRITE || state.err != Z_OK {
2148        return -1;
2149    }
2150
2151    // Write string.
2152    let len = unsafe { libc::strlen(s) };
2153    if c_int::try_from(len).is_err() {
2154        const MSG: &str = "string length does not fit in int";
2155        unsafe { gz_error(state, Some((Z_STREAM_ERROR, MSG))) };
2156        return -1;
2157    }
2158    let put = unsafe { gz_write(state, s.cast::<c_void>(), len) };
2159    match put.cmp(&(len as i32)) {
2160        Ordering::Less => -1,
2161        Ordering::Equal | Ordering::Greater => len as _,
2162    }
2163}
2164
2165/// Read one decompressed byte from `file`.
2166///
2167/// Note: The C header file `zlib.h` provides a macro wrapper for `gzgetc` that implements
2168/// the fast path inline and calls this function for the slow path.
2169///
2170/// # Returns
2171///
2172/// - The byte read, on success.
2173/// - `-1` on error.
2174///
2175/// # Safety
2176///
2177/// - `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
2178#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzgetc))]
2179pub unsafe extern "C" fn gzgetc(file: gzFile) -> c_int {
2180    let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
2181        return -1;
2182    };
2183
2184    // Check that we're reading and that there's no (serious) error.
2185    if state.mode != GzMode::GZ_READ || (state.err != Z_OK && state.err != Z_BUF_ERROR) {
2186        return -1;
2187    }
2188
2189    // Try output buffer (no need to check for skip request).
2190    if state.have != 0 {
2191        state.have -= 1;
2192        state.pos += 1;
2193        // Safety: Since `state.have` is at least 1, `state.next` points to at least
2194        // one readable byte within `state.output`.
2195        let ret = unsafe { *state.next };
2196        // Safety: Since `state.have` is at least 1, the byte between `state.next` and
2197        // `state.next + 1` is within the bounds of the `state.output` buffer, as required
2198        // by the pointer `add` method.
2199        state.next = unsafe { state.next.add(1) };
2200        return c_int::from(ret);
2201    }
2202
2203    // Nothing there -- try gz_read.
2204    let mut c = 0u8;
2205    // Safety: `c` is big enough to hold `len = 1` bytes.
2206    match unsafe { gz_read(state, core::slice::from_mut(&mut c).as_mut_ptr(), 1) } {
2207        1 => c_int::from(c),
2208        _ => -1,
2209    }
2210}
2211
2212/// Backward-compatibility alias for [`gzgetc`].
2213///
2214/// # Returns
2215///
2216/// - The byte read, on success.
2217/// - `-1` on error.
2218///
2219/// # Safety
2220///
2221/// - `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
2222#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzgetc_))]
2223pub unsafe extern "C" fn gzgetc_(file: gzFile) -> c_int {
2224    // Safety: The caller has ensured that `file` is null or a valid file handle.
2225    unsafe { gzgetc(file) }
2226}
2227
2228/// Push `c` back onto the stream for file to be read as the first character on
2229/// the next read.  At least one character of push-back is always allowed.
2230///
2231/// `gzungetc` will fail if `c` is `-1`, and may fail if a character has been pushed
2232/// but not read yet. If `gzungetc` is used immediately after [`gzopen`] or [`gzdopen`],
2233/// at least the output buffer size of pushed characters is allowed.  (See [`gzbuffer`].)
2234///
2235/// The pushed character will be discarded if the stream is repositioned with
2236/// [`gzseek`] or [`gzrewind`].
2237///
2238/// # Returns
2239///
2240/// - The character pushed, on success.
2241/// - `-1` on failure.
2242///
2243/// # Safety
2244///
2245/// - `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
2246#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzungetc))]
2247pub unsafe extern "C" fn gzungetc(c: c_int, file: gzFile) -> c_int {
2248    let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
2249        return -1;
2250    };
2251
2252    // Validate the input.
2253    if c < 0 {
2254        return -1;
2255    }
2256
2257    // Check that we're reading and that there's no (serious) error.
2258    if state.mode != GzMode::GZ_READ || (state.err != Z_OK && state.err != Z_BUF_ERROR) {
2259        return -1;
2260    }
2261
2262    // In case this was just opened, set up the input buffer.
2263    if state.how == How::Look && state.have == 0 {
2264        // We have verified that `state` is valid.
2265        let _ = unsafe { gz_look(state) };
2266    }
2267
2268    // Process a skip request.
2269    if state.seek {
2270        state.seek = false;
2271        if gz_skip(state, state.skip).is_err() {
2272            return -1;
2273        }
2274    }
2275
2276    // If output buffer empty, put byte at end (allows more pushing).
2277    if state.have == 0 {
2278        state.have = 1;
2279        // Safety: because `state.have` is nonzero, the `state.output` buffer has been
2280        // allocated. And because the buffer's size is `state.out_size`, a pointer to
2281        // `output + out_size - 1` points within the buffer.
2282        state.next = unsafe { state.output.add(state.out_size - 1) };
2283        // Safety: from the addition above, `state.next` currently points within the
2284        // `state.output` buffer.
2285        unsafe { *(state.next as *mut u8) = c as u8 };
2286        state.pos -= 1;
2287        state.past = false;
2288        return c;
2289    }
2290
2291    // If no room, give up (must have already done a `gzungetc`).
2292    if state.have as usize == state.out_size {
2293        const MSG: &str = "out of room to push characters";
2294        // Safety: We have verified that `state` is valid.
2295        unsafe { gz_error(state, Some((Z_DATA_ERROR, MSG))) };
2296        return -1;
2297    }
2298
2299    // Slide output data if needed and insert byte before existing data.
2300    if state.next == state.output {
2301        // There are `state.have` bytes of usable content at the front of the buffer
2302        // `state.output`, which has capacity `state.out_size`. We want to move that
2303        // content to the end of the buffer, so we copy from `state.output` to
2304        // `state.output + (state.out_size - state.have)` and update `state.next`
2305        // to point to the content's new location within the buffer.
2306        let offset = state.out_size - state.have as usize;
2307
2308        // Safety: `state.have` < `state.out_size`, or we would have returned in the
2309        // check for the == case above. Therefore, `offset`, which is `out_size - have`,
2310        // is in the range `1..=(out_size - 1)`. When we add that to `output`, the result
2311        // is within the buffer's allocation of `out_size` bytes.
2312        let dst = unsafe { state.output.add(offset) };
2313
2314        // Safety: `state.next` points a sequence of `state.have` initialized bytes
2315        // within the `state.output` buffer. And because `dst` was computed as
2316        // `state.output + state.out_size - state.have`, we can write `state.have`
2317        // bytes starting at `dst` and they will all be within the buffer.
2318        // Note that this may be an overlapping copy.
2319        unsafe { ptr::copy(state.next, dst as _, state.have as _) };
2320        state.next = dst;
2321    }
2322    state.have += 1;
2323    // Safety: `state.next` > `state.output`, due to the `state.next = dst` above, so it
2324    // is safe to decrease `state.next` by 1.
2325    state.next = unsafe { state.next.sub(1) };
2326    // Safety: `state.next` >= `state.output` following the subtraction.
2327    unsafe { *(state.next as *mut u8) = c as u8 };
2328    state.pos -= 1;
2329    state.past = false;
2330    c
2331}
2332
2333/// Read decompressed bytes from `file` into `buf`, until `len-1` characters are
2334/// read, or until a newline character is read and transferred to `buf`, or an
2335/// end-of-file condition is encountered.  If any characters are read or if `len`
2336/// is one, the string is terminated with a null character.  If no characters
2337/// are read due to an end-of-file or `len` is less than one, then the buffer is
2338/// left untouched.
2339///
2340/// Note: This function generally only makes sense for files where the decompressed
2341/// content is text. If there are any null bytes, this function will copy them into
2342/// `buf` just like any other character, resulting in early truncation of the
2343/// returned C string. To read gzip files whose decompressed content is binary,
2344/// please see [`gzread`].
2345///
2346/// # Returns
2347///
2348/// - `buf`, which now is a null-terminated string, on success.
2349/// - `null` on error. If there was an error, the contents at `buf` are indeterminate.
2350///
2351/// # Safety
2352///
2353/// - `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
2354/// - `buf` must be null or a pointer to at least `len` writable bytes.
2355#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzgets))]
2356pub unsafe extern "C" fn gzgets(file: gzFile, buf: *mut c_char, len: c_int) -> *mut c_char {
2357    // Check parameters.
2358    if buf.is_null() || len < 1 {
2359        return ptr::null_mut();
2360    }
2361
2362    let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
2363        return ptr::null_mut();
2364    };
2365
2366    // Check that we're reading and that there's no (serious) error.
2367    if state.mode != GzMode::GZ_READ || (state.err != Z_OK && state.err != Z_BUF_ERROR) {
2368        return ptr::null_mut();
2369    }
2370
2371    // Process a skip request.
2372    if state.seek {
2373        state.seek = false;
2374        if gz_skip(state, state.skip).is_err() {
2375            return ptr::null_mut();
2376        }
2377    }
2378
2379    // Copy output bytes up to newline or `len - 1`, whichever comes first.
2380    let mut left = len as usize - 1;
2381    if left == 0 {
2382        // The caller provided a 1-byte buffer, so write the terminating null and we're done.
2383        // Safety: `len` is 1 in this block, so it's safe to write 1 byte at `*buf`.
2384        unsafe { *buf = 0 };
2385        return buf;
2386    }
2387    let mut dst = buf;
2388    loop {
2389        // Assure that something is in the output buffer.
2390        // Safety: `state` is valid based on the checked cast above.
2391        if state.have == 0 && unsafe { gz_fetch(state) }.is_err() {
2392            // Error -- couldn't read any data.
2393            return ptr::null_mut();
2394        }
2395        if state.have == 0 {
2396            // End of file; return whatever we have.
2397            state.past = true;
2398            break;
2399        }
2400
2401        // Look for newline in current output buffer.
2402        let mut n = Ord::min(left, state.have as _);
2403        // Safety: `state.next` points to a block of `state.have` readable bytes. We're scanning
2404        // the first `n` of those bytes, and `n <= state.have` based on the `min` calculation.
2405        let eol = unsafe { libc::memchr(state.next.cast::<c_void>(), '\n' as c_int, n as _) };
2406        if !eol.is_null() {
2407            // Compute the number of bytes to copy, + 1 because we need to copy the newline itself.
2408            // Safety: `eol` was found by `memchr` in the same buffer as `state.next`, so `offset_of`
2409            // is valid. And because `memchr` only scans forward, `eol` will be at or after
2410            // `state.next`, so we can cast the result of `offset_from` to an unsigned value.
2411            n = unsafe { eol.cast::<u8>().offset_from(state.next) } as usize + 1;
2412        }
2413
2414        // Copy through end of line, or remainder if newline not found.
2415        // Safety: `state.next` points to at least `n` readable bytes because `n <= state.have`,
2416        // `dst` points to at least `n` writable bytes because `n <= left`, and the source
2417        // and destination regions are nonoverlapping because we're copying from an internal
2418        // buffer to a caller-supplied buffer.
2419        unsafe { ptr::copy_nonoverlapping(state.next, dst as _, n) };
2420        state.have -= n as c_uint;
2421        // Safety: As described above, `state.next` pointed to at least `n` readable bytes, so
2422        // when we increase it by `n` it will still point into the `output` buffer.
2423        state.next = unsafe { state.next.add(n) };
2424        state.pos += n as i64;
2425        left -= n;
2426        // Safety: `dst` pointed to at least `n` writable bytes, so when we increase it by `n`
2427        // it will still point into `buf`.
2428        dst = unsafe { dst.add(n) };
2429
2430        if left == 0 || !eol.is_null() {
2431            break;
2432        }
2433    }
2434
2435    if dst == buf {
2436        // Nothing was copied.
2437        ptr::null_mut()
2438    } else {
2439        // Something was copied. Null-terminate and return the string.
2440        // Safety: we copied at most `left = len - 1` bytes, and `dst` points just past
2441        // the last copied byte, so `dst` is within the block of `len` writable bytes
2442        // starting at `buf`.
2443        unsafe { *dst = 0 };
2444        buf
2445    }
2446}
2447
2448/// Dynamically update the compression level and strategy for `file`. See the
2449/// description of [`deflateInit2_`] for the meaning of these parameters. Previously
2450/// provided data is flushed before applying the parameter changes.
2451///
2452/// Note: If `level` is not valid, this function will silently fail with a return
2453/// value of `Z_OK`, matching the semantics of the C zlib version. However, if
2454/// `strategy` is not valid, this function will return an error.
2455///
2456/// # Returns
2457///
2458/// - [`Z_OK`] on success.
2459/// - [`Z_STREAM_ERROR`] if the file was not opened for writing.
2460/// - [`Z_ERRNO`] if there is an error writing the flushed data.
2461/// - [`Z_MEM_ERROR`] if there is a memory allocation error.
2462///
2463/// # Safety
2464///
2465/// - `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
2466#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzsetparams))]
2467pub unsafe extern "C" fn gzsetparams(file: gzFile, level: c_int, strategy: c_int) -> c_int {
2468    let Ok(strategy) = Strategy::try_from(strategy) else {
2469        return Z_STREAM_ERROR;
2470    };
2471    let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
2472        return Z_STREAM_ERROR;
2473    };
2474
2475    // Check that we're writing and that there's no error.
2476    if state.mode != GzMode::GZ_WRITE || state.err != Z_OK || state.direct {
2477        return Z_STREAM_ERROR;
2478    }
2479
2480    // If no change is requested, then do nothing.
2481    if level == c_int::from(state.level) && strategy == state.strategy {
2482        return Z_OK;
2483    }
2484
2485    // Check for seek request.
2486    if state.seek {
2487        state.seek = false;
2488        if gz_zero(state, state.skip as _).is_err() {
2489            return state.err;
2490        }
2491    }
2492
2493    // Change compression parameters for subsequent input.
2494    if !state.input.is_null() {
2495        // Flush previous input with previous parameters before changing.
2496        if state.stream.avail_in != 0 && gz_comp(state, Z_BLOCK).is_err() {
2497            return state.err;
2498        }
2499        // Safety: Because `state` is in write mode and `state.input` is non-null, `state.stream`
2500        // was initialized using `deflateInit2` in `gz_init`.
2501        unsafe { super::deflateParams(&mut state.stream, level, strategy as c_int) };
2502    }
2503    state.level = level as _;
2504    state.strategy = strategy;
2505    Z_OK
2506}
2507
2508/// Set the starting position to `offset` relative to `whence` for the next [`gzread`]
2509/// or [`gzwrite`] on `file`. The `offset` represents a number of bytes in the
2510/// uncompressed data stream. The `whence` parameter is defined as in `lseek(2)`,
2511/// but only `SEEK_CUR` (relative to current position) and `SEEK_SET` (absolute from
2512/// start of the uncompressed data stream) are supported.
2513///
2514/// If `file` is open for reading, this function is emulated but can extremely
2515/// slow (because it operates on the decompressed data stream).  If `file` is open
2516/// for writing, only forward seeks are supported; `gzseek` then compresses a sequence
2517/// of zeroes up to the new starting position. If a negative `offset` is specified in
2518/// write mode, `gzseek` returns -1.
2519///
2520/// # Returns
2521///
2522/// - The resulting offset location as measured in bytes from the beginning of the uncompressed
2523///   stream, on success.
2524/// - `-1` on error.
2525///
2526/// # Safety
2527///
2528/// - `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
2529#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzseek64))]
2530pub unsafe extern "C" fn gzseek64(file: gzFile, offset: z_off64_t, whence: c_int) -> z_off64_t {
2531    let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
2532        return -1;
2533    };
2534    if state.mode != GzMode::GZ_READ && state.mode != GzMode::GZ_WRITE {
2535        // Unreachable if `file` was initialized with `gzopen` or `gzdopen`.
2536        return -1;
2537    }
2538
2539    // Check that there's no error.
2540    if state.err != Z_OK && state.err != Z_BUF_ERROR {
2541        return -1;
2542    }
2543
2544    // Can only seek from start or relative to current position.
2545    if whence != SEEK_SET && whence != SEEK_CUR {
2546        return -1;
2547    }
2548
2549    let mut offset: i64 = offset as _;
2550
2551    // Normalize offset to a SEEK_CUR specification (i.e., relative to current position).
2552    if whence == SEEK_SET {
2553        offset -= state.pos;
2554    } else if state.seek {
2555        offset += state.skip;
2556    }
2557    state.seek = false;
2558
2559    // If we are reading non-compressed content, just lseek to the right location.
2560    if state.mode == GZ_READ && state.how == How::Copy && state.pos + offset >= 0 {
2561        let ret = lseek64(
2562            state.fd,
2563            offset as z_off64_t - state.have as z_off64_t,
2564            SEEK_CUR,
2565        );
2566        if ret == -1 {
2567            return -1;
2568        }
2569        state.have = 0;
2570        state.eof = false;
2571        state.past = false;
2572        state.seek = false;
2573        // Safety: `state` was validated above.
2574        unsafe { gz_error(state, None) };
2575        state.stream.avail_in = 0;
2576        state.pos += offset;
2577        return state.pos as _;
2578    }
2579
2580    // Calculate the skip amount. If we're seeking backwards in a compressed file, we'll
2581    // need to rewind to the start and decompress content until we arrive at the right spot.
2582    if offset < 0 {
2583        if state.mode != GzMode::GZ_READ {
2584            // Can't go backwards when writing.
2585            return -1;
2586        }
2587        offset += state.pos;
2588        if offset < 0 {
2589            // Before start of file!
2590            return -1;
2591        }
2592
2593        // Rewind, then skip to offset.
2594        // Safety: `file` points to an initialized `GzState`.
2595        if unsafe { gzrewind_help(state) } == -1 {
2596            return -1;
2597        }
2598    }
2599
2600    // If reading, skip what's in output buffer. (This simplifies `gzgetc`.)
2601    if state.mode == GzMode::GZ_READ {
2602        // For consistency with zlib-ng, we use `gt_off` to check whether the value
2603        // of `state.have` is too large to be represented as a signed 64-bit offset.
2604        // This case can be triggered only if the platform has 64-bit C ints and
2605        // `state.have` is >= 2^63.
2606        let n = if gt_off!(state.have) || state.have as i64 > offset {
2607            offset as usize
2608        } else {
2609            state.have as usize
2610        };
2611        state.have -= n as c_uint;
2612        // Safety: `n` <= `state.have`, and `state.next` points to at least `state.have`
2613        // accessible bytes within the buffer.
2614        state.next = unsafe { state.next.add(n) };
2615        state.pos += n as i64;
2616        offset -= n as i64;
2617    }
2618
2619    // Request skip (if not zero). The actual seek will happen on the next read or write operation.
2620    if offset != 0 {
2621        state.seek = true;
2622        state.skip = offset;
2623    }
2624
2625    (state.pos + offset) as _
2626}
2627
2628/// Set the starting position to `offset` relative to `whence` for the next [`gzread`]
2629/// or [`gzwrite`] on `file`. The `offset` represents a number of bytes in the
2630/// uncompressed data stream. The `whence` parameter is defined as in `lseek(2)`,
2631/// but only `SEEK_CUR` (relative to current position) and `SEEK_SET` (absolute from
2632/// start of the uncompressed data stream) are supported.
2633///
2634/// If `file` is open for reading, this function is emulated but can extremely
2635/// slow (because it operates on the decompressed data stream).  If `file` is open
2636/// for writing, only forward seeks are supported; `gzseek` then compresses a sequence
2637/// of zeroes up to the new starting position. If a negative `offset` is specified in
2638/// write mode, `gzseek` returns -1.
2639///
2640/// # Returns
2641///
2642/// - The resulting offset location as measured in bytes from the beginning of the uncompressed
2643///   stream, on success.
2644/// - `-1` on error.
2645///
2646/// # Safety
2647///
2648/// - `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
2649#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzseek))]
2650pub unsafe extern "C" fn gzseek(file: gzFile, offset: z_off_t, whence: c_int) -> z_off_t {
2651    z_off_t::try_from(unsafe { gzseek64(file, offset as z_off64_t, whence) }).unwrap_or(-1)
2652}
2653
2654/// Rewind `file` to the start. This function is supported only for reading.
2655///
2656/// Note: `gzrewind(file)` is equivalent to [`gzseek`]`(file, 0, SEEK_SET)`
2657///
2658/// # Returns
2659///
2660/// - `0` on success.
2661/// - `-1` on error.
2662///
2663/// # Safety
2664///
2665/// - `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
2666#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzrewind))]
2667pub unsafe extern "C" fn gzrewind(file: gzFile) -> c_int {
2668    let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
2669        return -1;
2670    };
2671
2672    unsafe { gzrewind_help(state) }
2673}
2674
2675unsafe fn gzrewind_help(state: &mut GzState) -> c_int {
2676    // Check that we're reading and that there's no error.
2677    if state.mode != GzMode::GZ_READ || (state.err != Z_OK && state.err != Z_BUF_ERROR) {
2678        return -1;
2679    }
2680
2681    // Back up and start over.
2682    if lseek64(state.fd, state.start as _, SEEK_SET) == -1 {
2683        return -1;
2684    }
2685    gz_reset(state);
2686    0
2687}
2688
2689/// Convert, format, compress, and write the variadic arguments `...` to a file under control of the string format, as in `fprintf`.
2690///
2691/// # Returns
2692///
2693/// Returns the number of uncompressed bytes actually written, or a negative zlib error code in case of error.
2694/// The number of uncompressed bytes written is limited to 8191, or one less than the buffer size given to [`gzbuffer`].
2695/// The caller should assure that this limit is not exceeded. If it is exceeded, then [`gzprintf`] will return `0` with nothing written.
2696///
2697/// Contrary to other implementations that can use the insecure `vsprintf`, the `zlib-rs` library always uses `vsnprintf`,
2698/// so attempting to write more bytes than the limit can never run into buffer overflow issues.
2699///
2700/// # Safety
2701///
2702/// - The `format`  must be a valid C string
2703/// - The variadic arguments must correspond with the format string in number and type
2704#[cfg(feature = "gzprintf")]
2705#[cfg_attr(docsrs, doc(cfg(feature = "gzprintf")))]
2706#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzprintf))]
2707pub unsafe extern "C" fn gzprintf(file: gzFile, format: *const c_char, va: ...) -> c_int {
2708    unsafe { gzvprintf(file, format, va) }
2709}
2710
2711/// Convert, format, compress, and write the variable argument list to a file under control of the string format, as in `vfprintf`.
2712///
2713/// # Returns
2714///
2715/// Returns the number of uncompressed bytes actually written, or a negative zlib error code in case of error.
2716/// The number of uncompressed bytes written is limited to 8191, or one less than the buffer size given to [`gzbuffer`].
2717/// The caller should assure that this limit is not exceeded. If it is exceeded, then [`gzvprintf`] will return `0` with nothing written.
2718///
2719/// Contrary to other implementations that can use the insecure `vsprintf`, the `zlib-rs` library always uses `vsnprintf`,
2720/// so attempting to write more bytes than the limit can never run into buffer overflow issues.
2721///
2722/// # Safety
2723///
2724/// - The `format`  must be a valid C string
2725/// - The variadic arguments must correspond with the format string in number and type
2726#[cfg(feature = "gzprintf")]
2727#[cfg_attr(docsrs, doc(cfg(feature = "gzprintf")))]
2728#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzvprintf))]
2729pub unsafe extern "C" fn gzvprintf(
2730    file: gzFile,
2731    format: *const c_char,
2732    va: core::ffi::VaList,
2733) -> c_int {
2734    let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
2735        return Z_STREAM_ERROR;
2736    };
2737
2738    // Check that we're writing and that there's no error.
2739    if state.mode != GzMode::GZ_WRITE || state.err != Z_OK {
2740        return Z_STREAM_ERROR;
2741    }
2742
2743    // Make sure we have some buffer space.
2744    if state.input.is_null() && gz_init(state).is_err() {
2745        return state.err;
2746    }
2747
2748    // Check for seek request.
2749    if state.seek {
2750        state.seek = false;
2751        if gz_zero(state, state.skip as _).is_err() {
2752            return state.err;
2753        }
2754    }
2755
2756    // Do the printf() into the input buffer, put length in len -- the input
2757    // buffer is double-sized just for this function, so there is guaranteed to
2758    // be state.size bytes available after the current contents
2759    if state.stream.avail_in == 0 {
2760        state.stream.next_in = state.input;
2761    }
2762
2763    // A pointer to the space that can be used by `vsnprintf`. The size of the input buffer
2764    // is `2 * state.in_size`, just for this function. That means we have at least
2765    // `state.in_size` bytes available.
2766    let next = unsafe { (state.stream.next_in).add(state.stream.avail_in as usize) }.cast_mut();
2767
2768    // NOTE: zlib-ng writes a NULL byte to the last position of the input buffer. It must do so
2769    // because in some cases it falls back to the `vsprintf` function, which contrary to
2770    // `vsnprintf` does not guarantee NULL-termination.
2771    //
2772    // We do not support using `vsprintf`, and therefore don't need to write or check that byte.
2773
2774    // This function is not currently exposed by libc, because `core::ffi::VaList` is unstable.
2775    extern "C" {
2776        fn vsnprintf(
2777            s: *mut c_char,
2778            n: libc::size_t,
2779            format: *const c_char,
2780            va: core::ffi::VaList,
2781        ) -> c_int;
2782    }
2783
2784    // Safety: as described earlier, there are at least state.in_size bytes available starting at
2785    // `next`. We forward `format` and `va`, so the caller is responsible for guarenteeing that
2786    // these are valid.
2787    let len = unsafe { vsnprintf(next.cast::<c_char>(), state.in_size, format, va) };
2788
2789    // Check that printf() results fit in buffer.
2790    if len == 0 || len as usize >= state.in_size {
2791        return 0;
2792    }
2793
2794    // Update buffer and position, compress first half if past that.
2795    state.stream.avail_in += len as u32;
2796    state.pos += i64::from(len);
2797    if state.stream.avail_in as usize >= state.in_size {
2798        let left = state.stream.avail_in - state.in_size as u32;
2799        state.stream.avail_in = state.in_size as u32;
2800        if gz_comp(state, Z_NO_FLUSH).is_err() {
2801            return state.err;
2802        }
2803        unsafe { core::ptr::copy(state.input.add(state.in_size), state.input, left as usize) };
2804        state.stream.next_in = state.input;
2805        state.stream.avail_in = left;
2806    }
2807
2808    len
2809}
2810
2811// Create a deep copy of a C string using `ALLOCATOR`
2812//
2813// # Safety
2814//
2815// The caller must ensure that s is either null or a pointer to a null-terminated C string.
2816unsafe fn gz_strdup(src: *const c_char) -> *mut c_char {
2817    if src.is_null() {
2818        return ptr::null_mut();
2819    }
2820
2821    // SAFETY: the caller must ensure this is a valid C string
2822    let src = unsafe { CStr::from_ptr(src) };
2823
2824    let len = src.to_bytes_with_nul().len();
2825    let Some(dst) = ALLOCATOR.allocate_slice_raw::<c_char>(len) else {
2826        return ptr::null_mut();
2827    };
2828
2829    // SAFETY: src and dst don't overlap, because dst was just allocated. src is valid for a read
2830    // of len bytes, and dst is valid for a write of len bytes.
2831    unsafe { core::ptr::copy_nonoverlapping(src.as_ptr(), dst.as_ptr(), len) };
2832
2833    dst.as_ptr()
2834}
2835
2836// Create a new C string, allocated using `ALLOCATOR`, that contains the
2837// concatenation of zero or more C strings.
2838//
2839// # Returns
2840//
2841// * A pointer to a C string, for which the caller receives ownership,
2842// * Or a null pointer upon error.
2843//
2844// # Safety
2845//
2846// * The return value, if non-null, must be freed using `ALLOCATOR`.
2847unsafe fn gz_strcat(strings: &[&str]) -> *mut c_char {
2848    let mut len = 1; // 1 for null terminator
2849    for src in strings {
2850        len += src.len();
2851    }
2852    let Some(buf) = ALLOCATOR.allocate_slice_raw::<c_char>(len) else {
2853        return ptr::null_mut();
2854    };
2855    let start = buf.as_ptr().cast::<c_char>();
2856    let mut dst = start.cast::<u8>();
2857    for src in strings {
2858        let size = src.len();
2859        unsafe {
2860            ptr::copy_nonoverlapping(src.as_ptr(), dst, size);
2861        };
2862        dst = unsafe { dst.add(size) };
2863    }
2864    unsafe { *dst = 0 };
2865    start
2866}
2867
2868fn lseek64(fd: c_int, offset: z_off64_t, origin: c_int) -> z_off64_t {
2869    #[cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))]
2870    {
2871        return unsafe { libc::lseek64(fd, offset as _, origin) as z_off64_t };
2872    }
2873
2874    #[allow(unused)]
2875    {
2876        (unsafe { libc::lseek(fd, offset as _, origin) }) as z_off64_t
2877    }
2878}
2879
2880#[cfg(test)]
2881mod tests {
2882    use super::*;
2883    use std::ffi::CString;
2884    use std::path::Path;
2885
2886    // Generate a file path relative to the project's root
2887    fn crate_path(file: &str) -> String {
2888        path(Path::new(env!("CARGO_MANIFEST_DIR")), file)
2889    }
2890
2891    fn path(prefix: &Path, file: &str) -> String {
2892        let mut path_buf = prefix.to_path_buf();
2893        path_buf.push(file);
2894        path_buf.as_path().to_str().unwrap().to_owned()
2895    }
2896
2897    #[test]
2898    fn test_configure() {
2899        let mut state = core::mem::MaybeUninit::<GzState>::zeroed();
2900        let state = unsafe { state.assume_init_mut() };
2901
2902        state.configure(b"r").unwrap();
2903        assert_eq!(state.mode, GzMode::GZ_READ);
2904        state.configure(b"rw").unwrap();
2905        assert_eq!(state.mode, GzMode::GZ_WRITE);
2906        state.configure(b"wr").unwrap();
2907        assert_eq!(state.mode, GzMode::GZ_READ);
2908
2909        state.configure(b"4").unwrap();
2910        assert_eq!(state.level, 4);
2911        state.configure(b"64").unwrap();
2912        assert_eq!(state.level, 4);
2913
2914        state.configure(b"f").unwrap();
2915        assert_eq!(state.strategy, Strategy::Filtered);
2916        state.configure(b"h").unwrap();
2917        assert_eq!(state.strategy, Strategy::HuffmanOnly);
2918        state.configure(b"R").unwrap();
2919        assert_eq!(state.strategy, Strategy::Rle);
2920        state.configure(b"F").unwrap();
2921        assert_eq!(state.strategy, Strategy::Fixed);
2922
2923        // Unknown characters are ignored.
2924        state.configure(b"xqz").unwrap();
2925
2926        // Plus errors (read + write mode is not supported)
2927        state.configure(b"123+").unwrap_err();
2928
2929        assert_eq!(state.configure(b""), Ok((false, false)));
2930        assert_eq!(state.configure(b"x"), Ok((true, false)));
2931        assert_eq!(state.configure(b"e"), Ok((false, true)));
2932        assert_eq!(state.configure(b"xe"), Ok((true, true)));
2933    }
2934
2935    // Map a byte string literal to a C string
2936    // FIXME: switch to c"example" format once MSRV >= 1.77
2937    macro_rules! c {
2938        ($s:literal) => {{
2939            $s.as_ptr().cast::<c_char>()
2940        }};
2941    }
2942
2943    #[test]
2944    fn gzdopen_invalid_fd() {
2945        assert_eq!(unsafe { gzdopen(-1, c!(b"r\0")) }, core::ptr::null_mut())
2946    }
2947
2948    #[test]
2949    fn gzopen_path_null() {
2950        assert_eq!(
2951            unsafe { gzopen(core::ptr::null(), c!(b"r\0")) },
2952            core::ptr::null_mut()
2953        )
2954    }
2955
2956    #[test]
2957    fn gzopen_mode_null() {
2958        assert_eq!(
2959            unsafe { gzopen(c!(b"/foo/bar\0"), core::ptr::null(),) },
2960            core::ptr::null_mut()
2961        )
2962    }
2963
2964    #[test]
2965    fn test_gz_strdup() {
2966        let src = ptr::null();
2967        let dup = unsafe { gz_strdup(src) };
2968        assert!(dup.is_null());
2969
2970        let src = b"\0";
2971        let dup = unsafe { gz_strdup(src.as_ptr().cast::<c_char>()) };
2972        assert!(!dup.is_null());
2973        assert_eq!(unsafe { CStr::from_ptr(dup) }.to_bytes_with_nul(), src);
2974        unsafe { ALLOCATOR.deallocate(dup, libc::strlen(dup) + 1) };
2975
2976        let src = b"example\0";
2977        let dup = unsafe { gz_strdup(src.as_ptr().cast::<c_char>()) };
2978        assert!(!dup.is_null());
2979        assert_eq!(unsafe { CStr::from_ptr(dup) }.to_bytes_with_nul(), src);
2980        unsafe { ALLOCATOR.deallocate(dup, libc::strlen(dup) + 1) };
2981    }
2982
2983    #[test]
2984    fn test_gz_strcat() {
2985        let src = [];
2986        let dup = unsafe { gz_strcat(&src) };
2987        assert!(!dup.is_null());
2988        assert_eq!(unsafe { libc::strlen(dup) }, 0);
2989        unsafe { ALLOCATOR.deallocate(dup, libc::strlen(dup) + 1) };
2990
2991        let src = ["example"];
2992        let dup = unsafe { gz_strcat(&src) };
2993        assert!(!dup.is_null());
2994        assert_eq!(
2995            unsafe { CStr::from_ptr(dup) }.to_bytes_with_nul(),
2996            b"example\0"
2997        );
2998        unsafe { ALLOCATOR.deallocate(dup, libc::strlen(dup) + 1) };
2999
3000        let src = ["hello", "", ",", "world"];
3001        let dup = unsafe { gz_strcat(&src) };
3002        assert!(!dup.is_null());
3003        assert_eq!(
3004            unsafe { CStr::from_ptr(dup) }.to_bytes_with_nul(),
3005            b"hello,world\0"
3006        );
3007        unsafe { ALLOCATOR.deallocate(dup, libc::strlen(dup) + 1) };
3008    }
3009
3010    #[test]
3011    fn test_fd_path() {
3012        let mut buf = [0u8; 27];
3013        assert_eq!(fd_path(&mut buf, 0).to_bytes(), b"<fd:0>");
3014        assert_eq!(fd_path(&mut buf, 9).to_bytes(), b"<fd:9>");
3015        assert_eq!(fd_path(&mut buf, -1).to_bytes(), b"<fd:-1>");
3016        assert_eq!(
3017            fd_path(&mut buf, i32::MIN).to_bytes(),
3018            format!("<fd:{}>", i32::MIN).as_bytes(),
3019        );
3020    }
3021
3022    #[test]
3023    #[cfg_attr(
3024        not(any(target_os = "linux", target_os = "macos")),
3025        ignore = "lseek is not implemented"
3026    )]
3027    fn test_gz_error() {
3028        // gzerror(null) should return null.
3029        assert!(unsafe { gzerror(ptr::null_mut(), ptr::null_mut()) }.is_null());
3030
3031        // Open a gzip stream with an invalid file handle. Initially, no error
3032        // status should be set.
3033        let handle = unsafe { gzdopen(-2, c!(b"r\0")) };
3034        assert!(!handle.is_null());
3035
3036        let state = (unsafe { handle.cast::<GzState>().as_mut() }).unwrap();
3037        assert_eq!(state.err, Z_OK);
3038        assert!(state.msg.is_null());
3039        let mut err = Z_ERRNO;
3040        let msg = unsafe { gzerror(handle, &mut err as *mut c_int) };
3041        assert_eq!(unsafe { CStr::from_ptr(msg) }.to_bytes_with_nul(), b"\0");
3042        assert_eq!(err, Z_OK);
3043
3044        // When an error is set, the path should be prepended to the error message automatically.
3045        let state = (unsafe { handle.cast::<GzState>().as_mut() }).unwrap();
3046        unsafe { gz_error(state, Some((Z_ERRNO, "example error"))) };
3047        assert_eq!(state.err, Z_ERRNO);
3048        assert_eq!(
3049            unsafe { CStr::from_ptr(state.msg) }.to_bytes_with_nul(),
3050            b"<fd:-2>: example error\0"
3051        );
3052        let mut err = Z_OK;
3053        let msg = unsafe { gzerror(handle, &mut err as *mut c_int) };
3054        assert_eq!(
3055            unsafe { CStr::from_ptr(msg) }.to_bytes_with_nul(),
3056            b"<fd:-2>: example error\0"
3057        );
3058        assert_eq!(err, Z_ERRNO);
3059
3060        // Setting the error message to null should clear the old error message.
3061        let state = (unsafe { handle.cast::<GzState>().as_mut() }).unwrap();
3062        unsafe { gz_error(state, None) };
3063        assert_eq!(state.err, Z_OK);
3064        assert!(state.msg.is_null());
3065        let mut err = Z_ERRNO;
3066        let msg = unsafe { gzerror(handle, &mut err as *mut c_int) };
3067        assert_eq!(unsafe { CStr::from_ptr(msg) }.to_bytes_with_nul(), b"\0");
3068        assert_eq!(err, Z_OK);
3069
3070        // Setting the error code to Z_MEM_ERROR should clear the internal error message
3071        // (because gz_error doesn't try to allocate space for a copy of the message if
3072        // the reason for the error is that allocations are failing).
3073        let state = (unsafe { handle.cast::<GzState>().as_mut() }).unwrap();
3074        unsafe { gz_error(state, Some((Z_MEM_ERROR, "should be ignored"))) };
3075        assert_eq!(state.err, Z_MEM_ERROR);
3076        assert!(state.msg.is_null());
3077        let mut err = Z_OK;
3078        let msg = unsafe { gzerror(handle, &mut err as *mut c_int) };
3079        assert_eq!(
3080            unsafe { CStr::from_ptr(msg) }.to_bytes_with_nul(),
3081            b"out of memory\0"
3082        );
3083        assert_eq!(err, Z_MEM_ERROR);
3084
3085        // gzclose should return an error because the fd is invalid.
3086        assert_eq!(unsafe { gzclose(handle) }, Z_ERRNO);
3087    }
3088
3089    #[test]
3090    #[cfg_attr(
3091        not(any(target_os = "linux", target_os = "macos")),
3092        ignore = "lseek is not implemented"
3093    )]
3094    fn test_gzclearerr() {
3095        // gzclearerr on a null file handle should return quietly.
3096        unsafe { gzclearerr(ptr::null_mut()) };
3097
3098        // Open a gzip stream with an invalid file handle. Initially, no error
3099        // status should be set.
3100        let handle = unsafe { gzdopen(-2, c!(b"r\0")) };
3101        assert!(!handle.is_null());
3102
3103        // gzclearerr should reset the eof and past flags.
3104        unsafe { handle.cast::<GzState>().as_mut().unwrap().eof = true };
3105        unsafe { handle.cast::<GzState>().as_mut().unwrap().past = true };
3106        unsafe { gzclearerr(handle) };
3107        assert!(!unsafe { handle.cast::<GzState>().as_ref().unwrap().eof });
3108        assert!(!unsafe { handle.cast::<GzState>().as_ref().unwrap().past });
3109
3110        // Set an error flag and message.
3111        unsafe {
3112            gz_error(
3113                handle.cast::<GzState>().as_mut().unwrap(),
3114                Some((Z_STREAM_ERROR, "example error")),
3115            )
3116        };
3117        let mut err = Z_OK;
3118        let msg = unsafe { gzerror(handle, &mut err as *mut c_int) };
3119        assert_eq!(err, Z_STREAM_ERROR);
3120        assert_eq!(
3121            unsafe { CStr::from_ptr(msg) }.to_bytes_with_nul(),
3122            b"<fd:-2>: example error\0"
3123        );
3124
3125        // gzclearerr should clear the error flag and message.
3126        unsafe { gzclearerr(handle) };
3127        let msg = unsafe { gzerror(handle, &mut err as *mut c_int) };
3128        assert_eq!(err, Z_OK);
3129        assert_eq!(unsafe { CStr::from_ptr(msg) }.to_bytes_with_nul(), b"\0");
3130
3131        // gzclose should return an error because the fd is invalid.
3132        assert_eq!(unsafe { gzclose(handle) }, Z_ERRNO);
3133
3134        // Test the write and append modes, where gzclearerr should not clear eof or past.
3135        for mode in [c!(b"w\0"), c!(b"a\0")] {
3136            // Open a gzip stream for write with an invalid file handle. gzeof should return 0.
3137            let handle = unsafe { gzdopen(-2, mode) };
3138            assert!(!handle.is_null());
3139            assert_eq!(unsafe { gzeof(handle) }, 0);
3140
3141            // gzclearerr should not reset the eof and past flags in write or append mode.
3142            unsafe { handle.cast::<GzState>().as_mut().unwrap().eof = true };
3143            unsafe { handle.cast::<GzState>().as_mut().unwrap().past = true };
3144            unsafe { gzclearerr(handle) };
3145            assert!(unsafe { handle.cast::<GzState>().as_ref().unwrap().eof });
3146            assert!(unsafe { handle.cast::<GzState>().as_ref().unwrap().past });
3147
3148            // Set an error flag and message.
3149            unsafe {
3150                gz_error(
3151                    handle.cast::<GzState>().as_mut().unwrap(),
3152                    Some((Z_STREAM_ERROR, "example error")),
3153                )
3154            };
3155
3156            // gzclearerr should clear the error flag and message.
3157            unsafe { gzclearerr(handle) };
3158            let msg = unsafe { gzerror(handle, &mut err as *mut c_int) };
3159            assert_eq!(err, Z_OK);
3160            assert_eq!(unsafe { CStr::from_ptr(msg) }.to_bytes_with_nul(), b"\0");
3161
3162            // gzclose should return an error because the fd is invalid.
3163            assert_eq!(unsafe { gzclose(handle) }, Z_ERRNO);
3164        }
3165    }
3166
3167    #[test]
3168    #[cfg_attr(
3169        not(any(target_os = "linux", target_os = "macos")),
3170        ignore = "lseek is not implemented"
3171    )]
3172    fn test_gzeof() {
3173        // gzeof on a null file handle should return false.
3174        assert_eq!(unsafe { gzeof(ptr::null_mut()) }, 0);
3175
3176        // Open a gzip stream for read with an invalid file handle. gzeof should return 0.
3177        let handle = unsafe { gzdopen(-2, c!(b"r\0")) };
3178        assert!(!handle.is_null());
3179        assert_eq!(unsafe { gzeof(handle) }, 0);
3180
3181        // gzeof should return 1 only if there was a read attempt past the end of the stream.
3182        unsafe { handle.cast::<GzState>().as_mut().unwrap().eof = true };
3183        assert_eq!(unsafe { gzeof(handle) }, 0);
3184        unsafe { handle.cast::<GzState>().as_mut().unwrap().past = true };
3185        assert_eq!(unsafe { gzeof(handle) }, 1);
3186
3187        // gzclose should return an error because the fd is invalid.
3188        assert_eq!(unsafe { gzclose(handle) }, Z_ERRNO);
3189
3190        // Test the write and append modes, where gzeof should always return 0.
3191        for mode in [c!(b"w\0"), c!(b"a\0")] {
3192            // Open a gzip stream for write with an invalid file handle. gzeof should return 0.
3193            let handle = unsafe { gzdopen(-2, mode) };
3194            assert!(!handle.is_null());
3195            assert_eq!(unsafe { gzeof(handle) }, 0);
3196
3197            // Even with the past flag set, gzeof should still return 0 in write or append mode.
3198            unsafe { handle.cast::<GzState>().as_mut().unwrap().past = true };
3199            assert_eq!(unsafe { gzeof(handle) }, 0);
3200
3201            // gzclose should return an error because the fd is invalid.
3202            assert_eq!(unsafe { gzclose(handle) }, Z_ERRNO);
3203        }
3204    }
3205
3206    #[test]
3207    #[cfg_attr(
3208        not(any(target_os = "linux", target_os = "macos")),
3209        ignore = "lseek is not implemented"
3210    )]
3211    // Open a gzip file for reading. gzdirect should return 0.
3212    fn test_gzdirect_gzip_file() {
3213        let file = unsafe {
3214            gzopen(
3215                CString::new(crate_path("src/test-data/example.gz"))
3216                    .unwrap()
3217                    .as_ptr(),
3218                CString::new("r").unwrap().as_ptr(),
3219            )
3220        };
3221        assert!(!file.is_null());
3222        // Set a smaller read batch size to exercise the buffer management code paths.
3223        const FILE_SIZE: usize = 48; // size of test-data/example.gz
3224        const BLOCK_SIZE: usize = 40;
3225        unsafe { file.cast::<GzState>().as_mut().unwrap().want = BLOCK_SIZE };
3226        assert_eq!(unsafe { gzdirect(file) }, 0);
3227        // gzdirect should have pulled the first `BLOCK_SIZE` bytes of the file into `file`'s internal input buffer.
3228        assert_eq!(unsafe { file.cast::<GzState>().as_ref().unwrap().have }, 0);
3229        assert_eq!(
3230            unsafe { file.cast::<GzState>().as_ref().unwrap().stream.avail_in },
3231            BLOCK_SIZE as uInt
3232        );
3233        // Consume some of the buffered input and call gz_avail. It should move the remaining
3234        // input to the front of the input buffer.
3235        unsafe {
3236            let state = file.cast::<GzState>().as_mut().unwrap();
3237            const CONSUME: usize = 10;
3238            state.stream.next_in = state.stream.next_in.add(CONSUME);
3239            state.stream.avail_in -= CONSUME as uInt;
3240            let expected_avail = BLOCK_SIZE - CONSUME + (FILE_SIZE - BLOCK_SIZE);
3241            assert_eq!(gz_avail(state), Ok(expected_avail));
3242            assert_eq!(state.stream.avail_in as usize, expected_avail);
3243        };
3244        assert_eq!(unsafe { gzclose(file) }, Z_OK);
3245    }
3246
3247    // Open a non-gzip file for reading. gzdirect should return 1.
3248    #[test]
3249    #[cfg_attr(
3250        not(any(target_os = "linux", target_os = "macos")),
3251        ignore = "lseek is not implemented"
3252    )]
3253    fn test_gzdirect_non_gzip_file() {
3254        let file = unsafe {
3255            gzopen(
3256                CString::new(crate_path("src/test-data/example.txt"))
3257                    .unwrap()
3258                    .as_ptr(),
3259                CString::new("r").unwrap().as_ptr(),
3260            )
3261        };
3262        assert!(!file.is_null());
3263        assert_eq!(unsafe { gzdirect(file) }, 1);
3264        // gzdirect should have pulled the entire contents of the file (which is smaller than
3265        // GZBUFSIZE) into `file`'s internal output buffer.
3266        assert_eq!(unsafe { file.cast::<GzState>().as_ref().unwrap().have }, 20);
3267        assert_eq!(
3268            unsafe { file.cast::<GzState>().as_ref().unwrap().stream.avail_in },
3269            0
3270        );
3271        assert_eq!(unsafe { gzclose(file) }, Z_OK);
3272
3273        // Open a file containing only the gzip magic number. gzdirect should return 0.
3274        let file = unsafe {
3275            gzopen(
3276                CString::new(crate_path("src/test-data/magic-only.gz"))
3277                    .unwrap()
3278                    .as_ptr(),
3279                CString::new("r").unwrap().as_ptr(),
3280            )
3281        };
3282        assert!(!file.is_null());
3283        assert_eq!(unsafe { gzdirect(file) }, 0);
3284        assert_eq!(unsafe { file.cast::<GzState>().as_ref().unwrap().have }, 0);
3285        assert_eq!(
3286            unsafe { file.cast::<GzState>().as_ref().unwrap().stream.avail_in },
3287            2
3288        );
3289
3290        assert_eq!(unsafe { gzclose(file) }, Z_OK);
3291
3292        // Open a file containing only the first byte of the gzip magic number. gzdirect should return 1.
3293        let file = unsafe {
3294            gzopen(
3295                CString::new(crate_path("src/test-data/incomplete-magic.gz"))
3296                    .unwrap()
3297                    .as_ptr(),
3298                CString::new("r").unwrap().as_ptr(),
3299            )
3300        };
3301        assert!(!file.is_null());
3302        assert_eq!(unsafe { gzdirect(file) }, 1);
3303        assert_eq!(unsafe { file.cast::<GzState>().as_ref().unwrap().have }, 1);
3304        assert_eq!(
3305            unsafe { file.cast::<GzState>().as_ref().unwrap().stream.avail_in },
3306            0
3307        );
3308        assert_eq!(unsafe { gzclose(file) }, Z_OK);
3309    }
3310
3311    #[test]
3312    #[cfg_attr(
3313        not(any(target_os = "linux", target_os = "macos")),
3314        ignore = "lseek is not implemented"
3315    )]
3316    fn test_gzbuffer() {
3317        // gzbuffer on a null file handle should return -1.
3318        assert_eq!(unsafe { gzbuffer(ptr::null_mut(), 1024) }, -1);
3319
3320        // Open a valid file handle to test the remaining gzbuffer edge cases.
3321        let file = unsafe {
3322            gzopen(
3323                CString::new(crate_path("src/test-data/example.txt"))
3324                    .unwrap()
3325                    .as_ptr(),
3326                CString::new("r").unwrap().as_ptr(),
3327            )
3328        };
3329        // Temporarily put the file handle in a stat that isn't read or write. gzbuffer should fail.
3330        unsafe { file.cast::<GzState>().as_mut().unwrap().mode = GzMode::GZ_NONE };
3331        assert_eq!(unsafe { gzbuffer(file, 1024) }, -1);
3332        // Put the file handle back in read mode, and now gzbuffer should work.
3333        unsafe { file.cast::<GzState>().as_mut().unwrap().mode = GzMode::GZ_READ };
3334        assert_eq!(unsafe { gzbuffer(file, 1024) }, 0);
3335        assert_eq!(
3336            unsafe { file.cast::<GzState>().as_ref().unwrap().want },
3337            1024
3338        );
3339        // Request a very small buffer size. gzbuffer should instead use the min size, 8 bytes.
3340        assert_eq!(unsafe { gzbuffer(file, 5) }, 0);
3341        assert_eq!(unsafe { file.cast::<GzState>().as_ref().unwrap().want }, 8);
3342        // Call gzdirect to force the allocation of buffers. After that, gzbuffer should fail.
3343        assert_eq!(unsafe { gzdirect(file) }, 1);
3344        assert_eq!(unsafe { gzbuffer(file, 1024) }, -1);
3345        assert_eq!(unsafe { file.cast::<GzState>().as_ref().unwrap().want }, 8);
3346        assert_eq!(unsafe { gzclose(file) }, Z_OK);
3347    }
3348}