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