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}