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