use zlib_rs::c_api::*;
use crate::gz::GzMode::GZ_READ;
use crate::{
deflate, deflateEnd, deflateInit2_, deflateReset, inflate, inflateEnd, inflateInit2_,
inflateReset, prefix, z_off64_t, z_off_t, zlibVersion,
};
use core::cmp::Ordering;
use core::ffi::{c_char, c_int, c_uint, c_void, CStr};
use core::ptr;
use libc::size_t; use libc::{O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_TRUNC, O_WRONLY, SEEK_CUR, SEEK_END, SEEK_SET};
use zlib_rs::deflate::Strategy;
use zlib_rs::MAX_WBITS;
#[allow(non_camel_case_types)]
pub enum gzFile_s {}
#[allow(non_camel_case_types)]
pub type gzFile = *mut gzFile_s;
#[repr(C)]
struct GzState {
have: c_uint, next: *const Bytef, pos: i64,
mode: GzMode,
fd: c_int, source: Source,
want: usize, input: *mut u8, in_size: usize, output: *mut u8, out_size: usize, direct: bool,
how: How,
start: i64,
eof: bool, past: bool,
level: i8,
strategy: Strategy,
reset: bool,
skip: i64, seek: bool,
err: c_int, msg: *const c_char,
stream: z_stream,
}
impl GzState {
fn configure(&mut self, mode: &[u8]) -> Result<(bool, bool), ()> {
let mut exclusive = false;
let mut cloexec = false;
for &ch in mode {
if ch.is_ascii_digit() {
self.level = (ch - b'0') as i8;
} else {
match ch {
b'r' => self.mode = GzMode::GZ_READ,
b'w' => self.mode = GzMode::GZ_WRITE,
b'a' => self.mode = GzMode::GZ_APPEND,
b'+' => {
return Err(());
}
b'b' => {} b'e' => cloexec = true,
b'x' => exclusive = true,
b'f' => self.strategy = Strategy::Filtered,
b'h' => self.strategy = Strategy::HuffmanOnly,
b'R' => self.strategy = Strategy::Rle,
b'F' => self.strategy = Strategy::Fixed,
b'T' => self.direct = true,
_ => {} }
}
}
Ok((exclusive, cloexec))
}
fn in_capacity(&self) -> usize {
match self.mode {
GzMode::GZ_WRITE => self.want * 2,
_ => self.want,
}
}
fn out_capacity(&self) -> usize {
match self.mode {
GzMode::GZ_READ => self.want * 2,
_ => self.want,
}
}
unsafe fn input_len(&self) -> usize {
if self.input.is_null() {
return 0;
}
let end = unsafe { self.stream.next_in.add(self.stream.avail_in as usize) };
(unsafe { end.offset_from(self.input) }) as _
}
}
#[allow(non_camel_case_types)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum GzMode {
GZ_NONE = 0,
GZ_READ = 7247,
GZ_WRITE = 31153,
GZ_APPEND = 1,
}
#[derive(Debug, PartialEq, Eq)]
enum How {
Look = 0, Copy = 1, Gzip = 2, }
const GZBUFSIZE: usize = 128 * 1024;
#[cfg(feature = "rust-allocator")]
use zlib_rs::allocate::RUST as ALLOCATOR;
#[cfg(not(feature = "rust-allocator"))]
#[cfg(feature = "c-allocator")]
use zlib_rs::allocate::C as ALLOCATOR;
#[cfg(not(feature = "rust-allocator"))]
#[cfg(not(feature = "c-allocator"))]
compile_error!("Either rust-allocator or c-allocator feature is required");
enum Source {
Path(*const c_char),
Fd(c_int),
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzopen64))]
pub unsafe extern "C" fn gzopen64(path: *const c_char, mode: *const c_char) -> gzFile {
if path.is_null() {
return ptr::null_mut();
}
let source = Source::Path(path);
unsafe { gzopen_help(source, mode) }
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzopen))]
pub unsafe extern "C" fn gzopen(path: *const c_char, mode: *const c_char) -> gzFile {
if path.is_null() {
return ptr::null_mut();
}
let source = Source::Path(path);
unsafe { gzopen_help(source, mode) }
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzdopen))]
pub unsafe extern "C" fn gzdopen(fd: c_int, mode: *const c_char) -> gzFile {
unsafe { gzopen_help(Source::Fd(fd), mode) }
}
unsafe fn gzopen_help(source: Source, mode: *const c_char) -> gzFile {
if mode.is_null() {
return ptr::null_mut();
}
let Some(state) = ALLOCATOR.allocate_zeroed_raw::<GzState>() else {
return ptr::null_mut();
};
let state = unsafe { state.cast::<GzState>().as_mut() };
state.in_size = 0;
state.out_size = 0;
state.want = GZBUFSIZE;
state.msg = ptr::null();
state.mode = GzMode::GZ_NONE;
state.level = crate::Z_DEFAULT_COMPRESSION as i8;
state.strategy = Strategy::Default;
state.direct = false;
state.stream = z_stream::default();
state.stream.zalloc = Some(ALLOCATOR.zalloc);
state.stream.zfree = Some(ALLOCATOR.zfree);
state.stream.opaque = ALLOCATOR.opaque;
let mode = unsafe { CStr::from_ptr(mode) };
let Ok((exclusive, cloexec)) = state.configure(mode.to_bytes()) else {
unsafe { free_state(state) };
return ptr::null_mut();
};
if state.mode == GzMode::GZ_NONE {
unsafe { free_state(state) };
return ptr::null_mut();
}
if state.mode == GzMode::GZ_READ {
if state.direct {
unsafe { free_state(state) };
return ptr::null_mut();
}
state.direct = true; }
match source {
Source::Fd(fd) => {
state.fd = fd;
state.source = Source::Fd(fd);
}
Source::Path(path) => {
let cloned_path = unsafe { gz_strdup(path) };
if cloned_path.is_null() {
unsafe { free_state(state) };
return ptr::null_mut();
}
state.source = Source::Path(cloned_path);
let mut oflag = 0;
#[cfg(target_os = "linux")]
{
oflag |= libc::O_LARGEFILE;
}
#[cfg(target_os = "windows")]
{
oflag |= libc::O_BINARY;
}
if cloexec {
#[cfg(target_os = "linux")]
{
oflag |= libc::O_CLOEXEC;
}
}
if state.mode == GzMode::GZ_READ {
oflag |= O_RDONLY;
} else {
oflag |= O_WRONLY | O_CREAT;
if exclusive {
oflag |= O_EXCL;
}
if state.mode == GzMode::GZ_WRITE {
oflag |= O_TRUNC;
} else {
oflag |= O_APPEND;
}
}
state.fd = unsafe { libc::open(cloned_path, oflag, 0o666) };
}
}
if state.fd == -1 {
unsafe { free_state(state) };
return ptr::null_mut();
}
if state.mode == GzMode::GZ_APPEND {
let _ = lseek64(state.fd, 0, SEEK_END); state.mode = GzMode::GZ_WRITE; }
if state.mode == GzMode::GZ_READ {
state.start = lseek64(state.fd, 0, SEEK_CUR) as _;
if state.start == -1 {
state.start = 0;
}
}
gz_reset(state);
(state as *mut GzState).cast::<gzFile_s>()
}
fn fd_path(buf: &mut [u8; 27], fd: c_int) -> &CStr {
use core::fmt::Write;
struct Writer<'a> {
buf: &'a mut [u8; 27],
len: usize,
}
impl Write for Writer<'_> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
let Some(dst) = self.buf.get_mut(self.len..self.len + s.len()) else {
return Err(core::fmt::Error);
};
dst.copy_from_slice(s.as_bytes());
self.len += s.len();
Ok(())
}
}
let mut w = Writer { buf, len: 0 };
write!(w, "<fd:{fd}>\0").unwrap();
unsafe { CStr::from_ptr(w.buf[..w.len].as_ptr().cast()) }
}
fn gz_reset(state: &mut GzState) {
state.have = 0; if state.mode == GzMode::GZ_READ {
state.eof = false; state.past = false; state.how = How::Look; } else {
state.reset = false; }
state.seek = false; unsafe { gz_error(state, None) }; state.pos = 0; state.stream.avail_in = 0; }
unsafe fn gz_error(state: &mut GzState, err_msg: Option<(c_int, &str)>) {
if !state.msg.is_null() {
unsafe { deallocate_cstr(state.msg.cast_mut()) };
state.msg = ptr::null_mut();
}
match err_msg {
None => {
state.err = Z_OK;
}
Some((err, msg)) => {
if err != Z_OK && err != Z_BUF_ERROR {
state.have = 0;
}
state.err = err;
if err == Z_MEM_ERROR {
return;
}
let sep = ": ";
let buf = &mut [0u8; 27];
state.msg = match state.source {
Source::Path(path) => unsafe {
gz_strcat(&[CStr::from_ptr(path).to_str().unwrap(), sep, msg])
},
Source::Fd(fd) => unsafe {
gz_strcat(&[fd_path(buf, fd).to_str().unwrap(), sep, msg])
},
};
if state.msg.is_null() {
state.err = Z_MEM_ERROR;
}
}
}
}
unsafe fn free_state(state: *mut GzState) {
if state.is_null() {
return;
}
unsafe {
match (*state).source {
Source::Path(path) => deallocate_cstr(path.cast_mut()),
Source::Fd(_) => { }
}
deallocate_cstr((*state).msg.cast_mut());
}
unsafe { free_buffers(state.as_mut().unwrap()) };
unsafe { ALLOCATOR.deallocate(state, 1) };
}
unsafe fn free_buffers(state: &mut GzState) {
if !state.input.is_null() {
unsafe { ALLOCATOR.deallocate(state.input, state.in_capacity()) };
state.input = ptr::null_mut();
}
state.in_size = 0;
if !state.output.is_null() {
unsafe { ALLOCATOR.deallocate(state.output, state.out_capacity()) };
state.output = ptr::null_mut();
}
state.out_size = 0;
}
unsafe fn deallocate_cstr(s: *mut c_char) {
if s.is_null() {
return;
}
unsafe { ALLOCATOR.deallocate::<c_char>(s, libc::strlen(s) + 1) };
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzclose))]
pub unsafe extern "C" fn gzclose(file: gzFile) -> c_int {
let Some(state) = (unsafe { file.cast::<GzState>().as_ref() }) else {
return Z_STREAM_ERROR;
};
match state.mode {
GzMode::GZ_READ => unsafe { gzclose_r(file) },
GzMode::GZ_WRITE | GzMode::GZ_APPEND | GzMode::GZ_NONE => unsafe { gzclose_w(file) },
}
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzclose_r))]
pub unsafe extern "C" fn gzclose_r(file: gzFile) -> c_int {
let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
return Z_STREAM_ERROR;
};
if state.mode != GzMode::GZ_READ {
return Z_STREAM_ERROR;
}
if state.in_size != 0 {
unsafe { inflateEnd(&mut state.stream as *mut z_stream) };
}
let err = match state.err {
Z_BUF_ERROR => Z_BUF_ERROR,
_ => Z_OK,
};
let ret = match unsafe { libc::close(state.fd) } {
0 => err,
_ => Z_ERRNO,
};
unsafe { free_state(file.cast::<GzState>()) };
ret
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzclose_w))]
pub unsafe extern "C" fn gzclose_w(file: gzFile) -> c_int {
let mut ret = Z_OK;
let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
return Z_STREAM_ERROR;
};
if state.mode != GzMode::GZ_WRITE {
return Z_STREAM_ERROR;
}
if state.seek {
state.seek = false;
if gz_zero(state, state.skip as _).is_err() {
ret = state.err;
}
}
if gz_comp(state, Z_FINISH).is_err() {
ret = state.err;
}
if state.in_size != 0 && !state.direct {
unsafe { deflateEnd(&mut state.stream as *mut z_stream) };
}
if unsafe { libc::close(state.fd) } == -1 {
ret = Z_ERRNO;
}
unsafe { free_state(file.cast::<GzState>()) };
ret
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzbuffer))]
pub unsafe extern "C" fn gzbuffer(file: gzFile, size: c_uint) -> c_int {
let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
return -1;
};
if state.mode != GzMode::GZ_READ && state.mode != GzMode::GZ_WRITE {
return -1;
}
if state.in_size != 0 {
return -1;
}
let size = size as usize;
if size.checked_mul(2).is_none() {
return -1;
}
state.want = Ord::max(size, 8);
0
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzerror))]
pub unsafe extern "C" fn gzerror(file: gzFile, errnum: *mut c_int) -> *const c_char {
let Some(state) = (unsafe { file.cast::<GzState>().as_ref() }) else {
return ptr::null();
};
if state.mode != GzMode::GZ_READ && state.mode != GzMode::GZ_WRITE {
return ptr::null();
}
if !errnum.is_null() {
unsafe { *errnum = state.err };
}
if state.err == Z_MEM_ERROR {
b"out of memory\0".as_ptr().cast::<c_char>()
} else if state.msg.is_null() {
b"\0".as_ptr().cast::<c_char>()
} else {
state.msg
}
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzclearerr))]
pub unsafe extern "C" fn gzclearerr(file: gzFile) {
let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
return;
};
if state.mode != GzMode::GZ_READ && state.mode != GzMode::GZ_WRITE {
return;
}
if state.mode == GzMode::GZ_READ {
state.eof = false;
state.past = false;
}
unsafe { gz_error(state, None) };
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzeof))]
pub unsafe extern "C" fn gzeof(file: gzFile) -> c_int {
let Some(state) = (unsafe { file.cast::<GzState>().as_ref() }) else {
return 0;
};
if state.mode != GzMode::GZ_READ {
return 0;
}
state.past as _
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzdirect))]
pub unsafe extern "C" fn gzdirect(file: gzFile) -> c_int {
let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
return 0;
};
if state.mode == GzMode::GZ_READ && state.how == How::Look && state.have == 0 {
let _ = unsafe { gz_look(state) };
}
state.direct as _
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzread))]
pub unsafe extern "C" fn gzread(file: gzFile, buf: *mut c_void, len: c_uint) -> c_int {
let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
return -1;
};
if state.mode != GzMode::GZ_READ || (state.err != Z_OK && state.err != Z_BUF_ERROR) {
return -1;
}
if c_int::try_from(len).is_err() {
const MSG: &str = "request does not fit in an int";
unsafe { gz_error(state, Some((Z_STREAM_ERROR, MSG))) };
return -1;
}
let got = unsafe { gz_read(state, buf.cast::<u8>(), len as usize) };
if got == 0 && state.err != Z_OK && state.err != Z_BUF_ERROR {
-1
} else {
got as _
}
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzfread))]
pub unsafe extern "C" fn gzfread(
buf: *mut c_void,
size: size_t,
nitems: size_t,
file: gzFile,
) -> size_t {
if size == 0 || buf.is_null() {
return 0;
}
let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
return 0;
};
if state.mode != GzMode::GZ_READ || (state.err != Z_OK && state.err != Z_BUF_ERROR) {
return 0;
}
let Some(len) = size.checked_mul(nitems) else {
const MSG: &str = "request does not fit in a size_t";
unsafe { gz_error(state, Some((Z_STREAM_ERROR, MSG))) };
return 0;
};
if len == 0 {
len
} else {
(unsafe { gz_read(state, buf.cast::<u8>(), len) }) / size
}
}
unsafe fn gz_read(state: &mut GzState, mut buf: *mut u8, mut len: usize) -> usize {
if len == 0 {
return 0;
}
if state.seek {
state.seek = false;
if gz_skip(state, state.skip).is_err() {
return 0;
}
}
let mut got = 0;
loop {
let mut n = Ord::min(len, c_uint::MAX as usize);
if state.have != 0 {
n = Ord::min(n, state.have as usize);
unsafe { ptr::copy_nonoverlapping(state.next, buf, n) };
state.next = unsafe { state.next.add(n) };
state.have -= n as c_uint;
} else if state.eof && state.stream.avail_in == 0 {
state.past = true; break;
} else if state.how == How::Look || n < state.in_size * 2 {
if unsafe { gz_fetch(state) }.is_err() {
return 0;
}
continue;
} else if state.how == How::Copy {
let Ok(bytes_read) = (unsafe { gz_load(state, buf, n) }) else {
return 0;
};
n = bytes_read;
} else {
debug_assert_eq!(state.how, How::Gzip);
state.stream.avail_out = n as c_uint;
state.stream.next_out = buf;
if unsafe { gz_decomp(state) }.is_err() {
return 0;
}
n = state.have as usize;
state.have = 0;
}
len -= n;
buf = unsafe { buf.add(n) };
got += n;
state.pos += n as i64;
if len == 0 {
break;
}
}
got
}
macro_rules! gt_off {
($x:expr) => {
core::mem::size_of_val(&$x) == core::mem::size_of::<i64>()
&& $x as usize > i64::MAX as usize
};
}
fn gz_skip(state: &mut GzState, mut len: i64) -> Result<(), ()> {
while len != 0 {
if state.have != 0 {
let n = if gt_off!(state.have) || state.have as i64 > len {
len as usize
} else {
state.have as usize
};
state.have -= n as c_uint;
state.next = unsafe { state.next.add(n) };
state.pos += n as i64;
len -= n as i64;
} else if state.eof && state.stream.avail_in == 0 {
break;
} else {
if unsafe { gz_fetch(state) }.is_err() {
return Err(());
}
}
}
Ok(())
}
unsafe fn gz_look(state: &mut GzState) -> Result<(), ()> {
if state.input.is_null() {
let capacity = state.in_capacity();
state.in_size = capacity;
let Some(input) = ALLOCATOR.allocate_slice_raw::<u8>(capacity) else {
unsafe { gz_error(state, Some((Z_MEM_ERROR, "out of memory"))) };
return Err(());
};
state.input = input.as_ptr();
if state.output.is_null() {
let capacity = state.out_capacity();
state.out_size = capacity;
let Some(output) = ALLOCATOR.allocate_slice_raw::<u8>(capacity) else {
unsafe { free_buffers(state) };
unsafe { gz_error(state, Some((Z_MEM_ERROR, "out of memory"))) };
return Err(());
};
state.output = output.as_ptr();
}
state.stream.avail_in = 0;
state.stream.next_in = ptr::null_mut();
if unsafe {
inflateInit2_(
&mut state.stream as *mut z_stream,
MAX_WBITS + 16,
zlibVersion(),
core::mem::size_of::<z_stream>() as i32,
)
} != Z_OK
{
unsafe { free_buffers(state) };
unsafe { gz_error(state, Some((Z_MEM_ERROR, "out of memory"))) };
return Err(());
}
}
if state.stream.avail_in < 2 {
if unsafe { gz_avail(state) }? == 0 {
return Ok(());
}
}
if state.stream.avail_in > 1
&& unsafe { *state.stream.next_in } == 31
&& unsafe { *state.stream.next_in.add(1) } == 139
{
unsafe { inflateReset(&mut state.stream as *mut z_stream) };
state.how = How::Gzip;
state.direct = false;
return Ok(());
}
if !state.direct {
state.stream.avail_in = 0;
state.eof = true;
state.have = 0;
return Ok(());
}
unsafe {
ptr::copy_nonoverlapping(
state.stream.next_in,
state.output,
state.stream.avail_in as usize,
)
};
state.next = state.output;
state.have = state.stream.avail_in;
state.stream.avail_in = 0;
state.how = How::Copy;
state.direct = true;
Ok(())
}
unsafe fn gz_avail(state: &mut GzState) -> Result<usize, ()> {
if state.err != Z_OK && state.err != Z_BUF_ERROR {
return Err(());
}
if !state.eof {
if state.stream.avail_in != 0 {
unsafe {
ptr::copy(
state.stream.next_in,
state.input,
state.stream.avail_in as usize,
)
};
}
let got = unsafe {
gz_load(
state,
state.input.add(state.stream.avail_in as usize),
state.in_size - state.stream.avail_in as usize,
)
}?;
state.stream.avail_in += got as uInt;
state.stream.next_in = state.input;
}
Ok(state.stream.avail_in as usize)
}
unsafe fn gz_load(state: &mut GzState, buf: *mut u8, len: usize) -> Result<usize, ()> {
let mut have = 0;
let mut ret = 0;
while have < len {
ret = unsafe { libc::read(state.fd, buf.add(have).cast::<_>(), (len - have) as _) };
if ret <= 0 {
break;
}
have += ret as usize;
}
if ret < 0 {
unsafe { gz_error(state, Some((Z_ERRNO, "read error"))) }; return Err(());
}
if ret == 0 {
state.eof = true;
}
Ok(have)
}
unsafe fn gz_fetch(state: &mut GzState) -> Result<(), ()> {
loop {
match &state.how {
How::Look => {
unsafe { gz_look(state) }?;
if state.how == How::Look {
return Ok(());
}
}
How::Copy => {
let bytes_read = unsafe { gz_load(state, state.output, state.out_size) }?;
state.next = state.output;
state.have += bytes_read as uInt;
return Ok(());
}
How::Gzip => {
state.stream.avail_out = state.out_size as c_uint;
state.stream.next_out = state.output;
unsafe { gz_decomp(state) }?;
}
}
if state.have != 0 || (state.eof && state.stream.avail_in == 0) {
break;
}
}
Ok(())
}
unsafe fn gz_decomp(state: &mut GzState) -> Result<(), ()> {
let had = state.stream.avail_out;
loop {
if state.stream.avail_in == 0 && unsafe { gz_avail(state) }.is_err() {
return Err(());
}
if state.stream.avail_in == 0 {
unsafe { gz_error(state, Some((Z_BUF_ERROR, "unexpected end of file"))) };
break;
}
match unsafe { inflate(&mut state.stream, Z_NO_FLUSH) } {
Z_STREAM_ERROR | Z_NEED_DICT => {
const MSG: &str = "internal error: inflate stream corrupt";
unsafe { gz_error(state, Some((Z_STREAM_ERROR, MSG))) };
return Err(());
}
Z_MEM_ERROR => {
unsafe { gz_error(state, Some((Z_MEM_ERROR, "out of memory"))) };
return Err(());
}
Z_DATA_ERROR => {
unsafe { gz_error(state, Some((Z_DATA_ERROR, "compressed data error"))) };
return Err(());
}
Z_STREAM_END => {
state.how = How::Look;
break;
}
_ => {}
}
if state.stream.avail_out == 0 {
break;
}
}
state.have = had - state.stream.avail_out;
state.next = unsafe { state.stream.next_out.sub(state.have as usize) };
Ok(())
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzwrite))]
pub unsafe extern "C" fn gzwrite(file: gzFile, buf: *const c_void, len: c_uint) -> c_int {
let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
return 0;
};
if state.mode != GzMode::GZ_WRITE || state.err != Z_OK {
return 0;
}
if c_int::try_from(len).is_err() {
const MSG: &str = "requested length does not fit in int";
unsafe { gz_error(state, Some((Z_DATA_ERROR, MSG))) };
return 0;
}
let Ok(len) = usize::try_from(len) else {
const MSG: &str = "requested length does not fit in usize";
unsafe { gz_error(state, Some((Z_DATA_ERROR, MSG))) };
return 0;
};
unsafe { gz_write(state, buf, len) }
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzfwrite))]
pub unsafe extern "C" fn gzfwrite(
buf: *const c_void,
size: size_t,
nitems: size_t,
file: gzFile,
) -> size_t {
if size == 0 || buf.is_null() {
return 0;
}
let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
return 0;
};
if state.mode != GzMode::GZ_WRITE || state.err != Z_OK {
return 0;
}
let Some(len) = size.checked_mul(nitems) else {
const MSG: &str = "request does not fit in a size_t";
unsafe { gz_error(state, Some((Z_STREAM_ERROR, MSG))) };
return 0;
};
if len == 0 {
len
} else {
(unsafe { gz_write(state, buf, len) }) as size_t / size
}
}
unsafe fn gz_write(state: &mut GzState, mut buf: *const c_void, mut len: usize) -> c_int {
if len == 0 {
return 0;
}
if state.input.is_null() && gz_init(state).is_err() {
return 0;
}
if state.seek {
state.seek = false;
if gz_zero(state, state.skip as _).is_err() {
return 0;
}
}
let put = len as c_int;
if len < state.in_size {
loop {
if state.stream.avail_in == 0 {
state.stream.next_in = state.input;
}
let have = unsafe { state.input_len() };
let copy = Ord::min(state.in_size.saturating_sub(have), len);
unsafe { ptr::copy(buf, state.input.add(have).cast::<c_void>(), copy) };
state.stream.avail_in += copy as c_uint;
state.pos += copy as i64;
buf = unsafe { buf.add(copy) };
len -= copy;
if len != 0 && gz_comp(state, Z_NO_FLUSH).is_err() {
return 0;
}
if len == 0 {
break;
}
}
} else {
if state.stream.avail_in != 0 && gz_comp(state, Z_NO_FLUSH).is_err() {
return 0;
}
let save_next_in = state.stream.next_in;
state.stream.next_in = buf.cast::<_>();
loop {
let n = Ord::min(len, c_uint::MAX as usize) as c_uint;
state.stream.avail_in = n;
state.pos += n as i64;
if gz_comp(state, Z_NO_FLUSH).is_err() {
return 0;
}
len -= n as usize;
if len == 0 {
break;
}
}
state.stream.next_in = save_next_in;
}
put
}
fn gz_zero(state: &mut GzState, mut len: usize) -> Result<(), ()> {
if state.stream.avail_in != 0 && gz_comp(state, Z_NO_FLUSH).is_err() {
return Err(());
}
let mut first = true;
while len != 0 {
let n = Ord::min(state.in_size, len);
if first {
unsafe { state.input.write_bytes(0u8, n) };
first = false;
}
state.stream.avail_in = n as _;
state.stream.next_in = state.input;
state.pos += n as i64;
if gz_comp(state, Z_NO_FLUSH).is_err() {
return Err(());
}
len -= n;
}
Ok(())
}
fn gz_init(state: &mut GzState) -> Result<(), ()> {
let capacity = state.in_capacity();
state.in_size = capacity / 2;
let Some(input) = ALLOCATOR.allocate_slice_raw::<u8>(capacity) else {
unsafe { gz_error(state, Some((Z_MEM_ERROR, "out of memory"))) };
return Err(());
};
state.input = input.as_ptr();
if !state.direct {
let capacity = state.out_capacity();
state.out_size = capacity;
let Some(output) = ALLOCATOR.allocate_slice_raw::<u8>(capacity) else {
unsafe { free_buffers(state) };
unsafe { gz_error(state, Some((Z_MEM_ERROR, "out of memory"))) };
return Err(());
};
state.output = output.as_ptr();
state.stream.zalloc = Some(ALLOCATOR.zalloc);
state.stream.zfree = Some(ALLOCATOR.zfree);
state.stream.opaque = ALLOCATOR.opaque;
const DEF_MEM_LEVEL: c_int = 8;
if unsafe {
deflateInit2_(
&mut state.stream,
state.level as _,
Z_DEFLATED,
MAX_WBITS + 16,
DEF_MEM_LEVEL,
state.strategy as _,
zlibVersion(),
core::mem::size_of::<z_stream>() as _,
)
} != Z_OK
{
unsafe { free_buffers(state) };
unsafe { gz_error(state, Some((Z_MEM_ERROR, "out of memory"))) };
return Err(());
}
state.stream.next_in = ptr::null_mut();
}
if !state.direct {
state.stream.avail_out = state.out_size as _;
state.stream.next_out = state.output;
state.next = state.stream.next_out;
}
Ok(())
}
fn gz_comp(state: &mut GzState, flush: c_int) -> Result<(), ()> {
if state.input.is_null() && gz_init(state).is_err() {
return Err(());
}
if state.direct {
let got = unsafe {
libc::write(
state.fd,
state.stream.next_in.cast::<c_void>(),
state.stream.avail_in as _,
)
};
if got < 0 || got as c_uint != state.stream.avail_in {
unsafe { gz_error(state, Some((Z_ERRNO, "write error"))) };
return Err(());
}
state.stream.avail_in = 0;
return Ok(());
}
if state.reset {
if state.stream.avail_in == 0 {
return Ok(());
}
let _ = unsafe { deflateReset(&mut state.stream) };
state.reset = false;
}
let mut ret = Z_OK;
loop {
if state.stream.avail_out == 0
|| (flush != Z_NO_FLUSH && (flush != Z_FINISH || ret == Z_STREAM_END))
{
let have = unsafe { state.stream.next_out.offset_from(state.next) };
if have < 0 {
const MSG: &str = "corrupt internal state in gz_comp";
unsafe { gz_error(state, Some((Z_STREAM_ERROR, MSG))) };
return Err(());
}
if have != 0 {
let ret = unsafe { libc::write(state.fd, state.next.cast::<c_void>(), have as _) };
if ret != have as _ {
unsafe { gz_error(state, Some((Z_ERRNO, "write error"))) };
return Err(());
}
}
if state.stream.avail_out == 0 {
state.stream.avail_out = state.out_size as _;
state.stream.next_out = state.output;
}
state.next = state.stream.next_out;
}
let mut have = state.stream.avail_out;
ret = unsafe { deflate(&mut state.stream, flush) };
if ret == Z_STREAM_ERROR {
const MSG: &str = "internal error: deflate stream corrupt";
unsafe { gz_error(state, Some((Z_STREAM_ERROR, MSG))) };
return Err(());
}
have -= state.stream.avail_out;
if have == 0 {
break;
}
}
if flush == Z_FINISH {
state.reset = true;
}
Ok(())
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzflush))]
pub unsafe extern "C" fn gzflush(file: gzFile, flush: c_int) -> c_int {
let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
return Z_STREAM_ERROR;
};
if state.mode != GzMode::GZ_WRITE || state.err != Z_OK {
return Z_STREAM_ERROR;
}
if !(0..=Z_FINISH).contains(&flush) {
return Z_STREAM_ERROR;
}
if state.seek {
state.seek = false;
if gz_zero(state, state.skip as _).is_err() {
return state.err;
}
}
let _ = gz_comp(state, flush);
state.err
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gztell64))]
pub unsafe extern "C" fn gztell64(file: gzFile) -> z_off64_t {
let Some(state) = (unsafe { file.cast::<GzState>().as_ref() }) else {
return -1;
};
if state.mode != GzMode::GZ_READ && state.mode != GzMode::GZ_WRITE {
return -1;
}
match state.seek {
true => (state.pos + state.skip) as z_off64_t,
false => state.pos as z_off64_t,
}
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gztell))]
pub unsafe extern "C" fn gztell(file: gzFile) -> z_off_t {
z_off_t::try_from(unsafe { gztell64(file) }).unwrap_or(-1)
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzoffset64))]
pub unsafe extern "C" fn gzoffset64(file: gzFile) -> z_off64_t {
let Some(state) = (unsafe { file.cast::<GzState>().as_ref() }) else {
return -1;
};
if state.mode != GzMode::GZ_READ && state.mode != GzMode::GZ_WRITE {
return -1;
}
let offset = lseek64(state.fd, 0, SEEK_CUR) as z_off64_t;
if offset == -1 {
return -1;
}
match state.mode {
GzMode::GZ_READ => offset - state.stream.avail_in as z_off64_t,
GzMode::GZ_NONE | GzMode::GZ_WRITE | GzMode::GZ_APPEND => offset,
}
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzoffset))]
pub unsafe extern "C" fn gzoffset(file: gzFile) -> z_off_t {
z_off_t::try_from(unsafe { gzoffset64(file) }).unwrap_or(-1)
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzputc))]
pub unsafe extern "C" fn gzputc(file: gzFile, c: c_int) -> c_int {
let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
return -1;
};
if state.mode != GzMode::GZ_WRITE || state.err != Z_OK {
return -1;
}
if state.seek {
state.seek = false;
if gz_zero(state, state.skip as _).is_err() {
return -1;
}
}
if !state.input.is_null() {
if state.stream.avail_in == 0 {
state.stream.next_in = state.input;
}
let have = unsafe { state.input_len() };
if have < state.in_size {
unsafe { *state.input.add(have) = c as u8 };
state.stream.avail_in += 1;
state.pos += 1;
return c & 0xff;
}
}
let buf = [c as u8];
match unsafe { gz_write(state, buf.as_ptr().cast::<c_void>(), 1) } {
1 => c & 0xff,
_ => -1,
}
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzputs))]
pub unsafe extern "C" fn gzputs(file: gzFile, s: *const c_char) -> c_int {
let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
return -1;
};
if s.is_null() {
return -1;
}
if state.mode != GzMode::GZ_WRITE || state.err != Z_OK {
return -1;
}
let len = unsafe { libc::strlen(s) };
if c_int::try_from(len).is_err() {
const MSG: &str = "string length does not fit in int";
unsafe { gz_error(state, Some((Z_STREAM_ERROR, MSG))) };
return -1;
}
let put = unsafe { gz_write(state, s.cast::<c_void>(), len) };
match put.cmp(&(len as i32)) {
Ordering::Less => -1,
Ordering::Equal | Ordering::Greater => len as _,
}
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzgetc))]
pub unsafe extern "C" fn gzgetc(file: gzFile) -> c_int {
let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
return -1;
};
if state.mode != GzMode::GZ_READ || (state.err != Z_OK && state.err != Z_BUF_ERROR) {
return -1;
}
if state.have != 0 {
state.have -= 1;
state.pos += 1;
let ret = unsafe { *state.next };
state.next = unsafe { state.next.add(1) };
return c_int::from(ret);
}
let mut c = 0u8;
match unsafe { gz_read(state, core::slice::from_mut(&mut c).as_mut_ptr(), 1) } {
1 => c_int::from(c),
_ => -1,
}
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzgetc_))]
pub unsafe extern "C" fn gzgetc_(file: gzFile) -> c_int {
unsafe { gzgetc(file) }
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzungetc))]
pub unsafe extern "C" fn gzungetc(c: c_int, file: gzFile) -> c_int {
let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
return -1;
};
if c < 0 {
return -1;
}
if state.mode != GzMode::GZ_READ || (state.err != Z_OK && state.err != Z_BUF_ERROR) {
return -1;
}
if state.how == How::Look && state.have == 0 {
let _ = unsafe { gz_look(state) };
}
if state.seek {
state.seek = false;
if gz_skip(state, state.skip).is_err() {
return -1;
}
}
if state.have == 0 {
state.have = 1;
state.next = unsafe { state.output.add(state.out_size - 1) };
unsafe { *(state.next as *mut u8) = c as u8 };
state.pos -= 1;
state.past = false;
return c;
}
if state.have as usize == state.out_size {
const MSG: &str = "out of room to push characters";
unsafe { gz_error(state, Some((Z_DATA_ERROR, MSG))) };
return -1;
}
if state.next == state.output {
let offset = state.out_size - state.have as usize;
let dst = unsafe { state.output.add(offset) };
unsafe { ptr::copy(state.next, dst as _, state.have as _) };
state.next = dst;
}
state.have += 1;
state.next = unsafe { state.next.sub(1) };
unsafe { *(state.next as *mut u8) = c as u8 };
state.pos -= 1;
state.past = false;
c
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzgets))]
pub unsafe extern "C" fn gzgets(file: gzFile, buf: *mut c_char, len: c_int) -> *mut c_char {
if buf.is_null() || len < 1 {
return ptr::null_mut();
}
let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
return ptr::null_mut();
};
if state.mode != GzMode::GZ_READ || (state.err != Z_OK && state.err != Z_BUF_ERROR) {
return ptr::null_mut();
}
if state.seek {
state.seek = false;
if gz_skip(state, state.skip).is_err() {
return ptr::null_mut();
}
}
let mut left = len as usize - 1;
if left == 0 {
unsafe { *buf = 0 };
return buf;
}
let mut dst = buf;
loop {
if state.have == 0 && unsafe { gz_fetch(state) }.is_err() {
return ptr::null_mut();
}
if state.have == 0 {
state.past = true;
break;
}
let mut n = Ord::min(left, state.have as _);
let eol = unsafe { libc::memchr(state.next.cast::<c_void>(), '\n' as c_int, n as _) };
if !eol.is_null() {
n = unsafe { eol.cast::<u8>().offset_from(state.next) } as usize + 1;
}
unsafe { ptr::copy_nonoverlapping(state.next, dst as _, n) };
state.have -= n as c_uint;
state.next = unsafe { state.next.add(n) };
state.pos += n as i64;
left -= n;
dst = unsafe { dst.add(n) };
if left == 0 || !eol.is_null() {
break;
}
}
if dst == buf {
ptr::null_mut()
} else {
unsafe { *dst = 0 };
buf
}
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzsetparams))]
pub unsafe extern "C" fn gzsetparams(file: gzFile, level: c_int, strategy: c_int) -> c_int {
let Ok(strategy) = Strategy::try_from(strategy) else {
return Z_STREAM_ERROR;
};
let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
return Z_STREAM_ERROR;
};
if state.mode != GzMode::GZ_WRITE || state.err != Z_OK || state.direct {
return Z_STREAM_ERROR;
}
if level == c_int::from(state.level) && strategy == state.strategy {
return Z_OK;
}
if state.seek {
state.seek = false;
if gz_zero(state, state.skip as _).is_err() {
return state.err;
}
}
if !state.input.is_null() {
if state.stream.avail_in != 0 && gz_comp(state, Z_BLOCK).is_err() {
return state.err;
}
unsafe { super::deflateParams(&mut state.stream, level, strategy as c_int) };
}
state.level = level as _;
state.strategy = strategy;
Z_OK
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzseek64))]
pub unsafe extern "C" fn gzseek64(file: gzFile, offset: z_off64_t, whence: c_int) -> z_off64_t {
let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
return -1;
};
if state.mode != GzMode::GZ_READ && state.mode != GzMode::GZ_WRITE {
return -1;
}
if state.err != Z_OK && state.err != Z_BUF_ERROR {
return -1;
}
if whence != SEEK_SET && whence != SEEK_CUR {
return -1;
}
let mut offset: i64 = offset as _;
if whence == SEEK_SET {
offset -= state.pos;
} else if state.seek {
offset += state.skip;
}
state.seek = false;
if state.mode == GZ_READ && state.how == How::Copy && state.pos + offset >= 0 {
let ret = lseek64(
state.fd,
offset as z_off64_t - state.have as z_off64_t,
SEEK_CUR,
);
if ret == -1 {
return -1;
}
state.have = 0;
state.eof = false;
state.past = false;
state.seek = false;
unsafe { gz_error(state, None) };
state.stream.avail_in = 0;
state.pos += offset;
return state.pos as _;
}
if offset < 0 {
if state.mode != GzMode::GZ_READ {
return -1;
}
offset += state.pos;
if offset < 0 {
return -1;
}
if unsafe { gzrewind_help(state) } == -1 {
return -1;
}
}
if state.mode == GzMode::GZ_READ {
let n = if gt_off!(state.have) || state.have as i64 > offset {
offset as usize
} else {
state.have as usize
};
state.have -= n as c_uint;
state.next = unsafe { state.next.add(n) };
state.pos += n as i64;
offset -= n as i64;
}
if offset != 0 {
state.seek = true;
state.skip = offset;
}
(state.pos + offset) as _
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzseek))]
pub unsafe extern "C" fn gzseek(file: gzFile, offset: z_off_t, whence: c_int) -> z_off_t {
z_off_t::try_from(unsafe { gzseek64(file, offset as z_off64_t, whence) }).unwrap_or(-1)
}
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzrewind))]
pub unsafe extern "C" fn gzrewind(file: gzFile) -> c_int {
let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
return -1;
};
unsafe { gzrewind_help(state) }
}
unsafe fn gzrewind_help(state: &mut GzState) -> c_int {
if state.mode != GzMode::GZ_READ || (state.err != Z_OK && state.err != Z_BUF_ERROR) {
return -1;
}
if lseek64(state.fd, state.start as _, SEEK_SET) == -1 {
return -1;
}
gz_reset(state);
0
}
#[cfg(feature = "gzprintf")]
#[cfg_attr(docsrs, doc(cfg(feature = "gzprintf")))]
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzprintf))]
pub unsafe extern "C" fn gzprintf(file: gzFile, format: *const c_char, va: ...) -> c_int {
unsafe { gzvprintf(file, format, va) }
}
#[cfg(feature = "gzprintf")]
#[cfg_attr(docsrs, doc(cfg(feature = "gzprintf")))]
#[cfg_attr(feature = "export-symbols", export_name = prefix!(gzvprintf))]
pub unsafe extern "C" fn gzvprintf(
file: gzFile,
format: *const c_char,
va: core::ffi::VaList,
) -> c_int {
let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
return Z_STREAM_ERROR;
};
if state.mode != GzMode::GZ_WRITE || state.err != Z_OK {
return Z_STREAM_ERROR;
}
if state.input.is_null() && gz_init(state).is_err() {
return state.err;
}
if state.seek {
state.seek = false;
if gz_zero(state, state.skip as _).is_err() {
return state.err;
}
}
if state.stream.avail_in == 0 {
state.stream.next_in = state.input;
}
let next = unsafe { (state.stream.next_in).add(state.stream.avail_in as usize) }.cast_mut();
extern "C" {
fn vsnprintf(
s: *mut c_char,
n: libc::size_t,
format: *const c_char,
va: core::ffi::VaList,
) -> c_int;
}
let len = unsafe { vsnprintf(next.cast::<c_char>(), state.in_size, format, va) };
if len == 0 || len as usize >= state.in_size {
return 0;
}
state.stream.avail_in += len as u32;
state.pos += i64::from(len);
if state.stream.avail_in as usize >= state.in_size {
let left = state.stream.avail_in - state.in_size as u32;
state.stream.avail_in = state.in_size as u32;
if gz_comp(state, Z_NO_FLUSH).is_err() {
return state.err;
}
unsafe { core::ptr::copy(state.input.add(state.in_size), state.input, left as usize) };
state.stream.next_in = state.input;
state.stream.avail_in = left;
}
len
}
unsafe fn gz_strdup(src: *const c_char) -> *mut c_char {
if src.is_null() {
return ptr::null_mut();
}
let src = unsafe { CStr::from_ptr(src) };
let len = src.to_bytes_with_nul().len();
let Some(dst) = ALLOCATOR.allocate_slice_raw::<c_char>(len) else {
return ptr::null_mut();
};
unsafe { core::ptr::copy_nonoverlapping(src.as_ptr(), dst.as_ptr(), len) };
dst.as_ptr()
}
unsafe fn gz_strcat(strings: &[&str]) -> *mut c_char {
let mut len = 1; for src in strings {
len += src.len();
}
let Some(buf) = ALLOCATOR.allocate_slice_raw::<c_char>(len) else {
return ptr::null_mut();
};
let start = buf.as_ptr().cast::<c_char>();
let mut dst = start.cast::<u8>();
for src in strings {
let size = src.len();
unsafe {
ptr::copy_nonoverlapping(src.as_ptr(), dst, size);
};
dst = unsafe { dst.add(size) };
}
unsafe { *dst = 0 };
start
}
fn lseek64(fd: c_int, offset: z_off64_t, origin: c_int) -> z_off64_t {
#[cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))]
{
return unsafe { libc::lseek64(fd, offset as _, origin) as z_off64_t };
}
#[allow(unused)]
{
(unsafe { libc::lseek(fd, offset as _, origin) }) as z_off64_t
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CString;
use std::path::Path;
fn crate_path(file: &str) -> String {
path(Path::new(env!("CARGO_MANIFEST_DIR")), file)
}
fn path(prefix: &Path, file: &str) -> String {
let mut path_buf = prefix.to_path_buf();
path_buf.push(file);
path_buf.as_path().to_str().unwrap().to_owned()
}
#[test]
fn test_configure() {
let mut state = core::mem::MaybeUninit::<GzState>::zeroed();
let state = unsafe { state.assume_init_mut() };
state.configure(b"r").unwrap();
assert_eq!(state.mode, GzMode::GZ_READ);
state.configure(b"rw").unwrap();
assert_eq!(state.mode, GzMode::GZ_WRITE);
state.configure(b"wr").unwrap();
assert_eq!(state.mode, GzMode::GZ_READ);
state.configure(b"4").unwrap();
assert_eq!(state.level, 4);
state.configure(b"64").unwrap();
assert_eq!(state.level, 4);
state.configure(b"f").unwrap();
assert_eq!(state.strategy, Strategy::Filtered);
state.configure(b"h").unwrap();
assert_eq!(state.strategy, Strategy::HuffmanOnly);
state.configure(b"R").unwrap();
assert_eq!(state.strategy, Strategy::Rle);
state.configure(b"F").unwrap();
assert_eq!(state.strategy, Strategy::Fixed);
state.configure(b"xqz").unwrap();
state.configure(b"123+").unwrap_err();
assert_eq!(state.configure(b""), Ok((false, false)));
assert_eq!(state.configure(b"x"), Ok((true, false)));
assert_eq!(state.configure(b"e"), Ok((false, true)));
assert_eq!(state.configure(b"xe"), Ok((true, true)));
}
macro_rules! c {
($s:literal) => {{
$s.as_ptr().cast::<c_char>()
}};
}
#[test]
fn gzdopen_invalid_fd() {
assert_eq!(unsafe { gzdopen(-1, c!(b"r\0")) }, core::ptr::null_mut())
}
#[test]
fn gzopen_path_null() {
assert_eq!(
unsafe { gzopen(core::ptr::null(), c!(b"r\0")) },
core::ptr::null_mut()
)
}
#[test]
fn gzopen_mode_null() {
assert_eq!(
unsafe { gzopen(c!(b"/foo/bar\0"), core::ptr::null(),) },
core::ptr::null_mut()
)
}
#[test]
fn test_gz_strdup() {
let src = ptr::null();
let dup = unsafe { gz_strdup(src) };
assert!(dup.is_null());
let src = b"\0";
let dup = unsafe { gz_strdup(src.as_ptr().cast::<c_char>()) };
assert!(!dup.is_null());
assert_eq!(unsafe { CStr::from_ptr(dup) }.to_bytes_with_nul(), src);
unsafe { ALLOCATOR.deallocate(dup, libc::strlen(dup) + 1) };
let src = b"example\0";
let dup = unsafe { gz_strdup(src.as_ptr().cast::<c_char>()) };
assert!(!dup.is_null());
assert_eq!(unsafe { CStr::from_ptr(dup) }.to_bytes_with_nul(), src);
unsafe { ALLOCATOR.deallocate(dup, libc::strlen(dup) + 1) };
}
#[test]
fn test_gz_strcat() {
let src = [];
let dup = unsafe { gz_strcat(&src) };
assert!(!dup.is_null());
assert_eq!(unsafe { libc::strlen(dup) }, 0);
unsafe { ALLOCATOR.deallocate(dup, libc::strlen(dup) + 1) };
let src = ["example"];
let dup = unsafe { gz_strcat(&src) };
assert!(!dup.is_null());
assert_eq!(
unsafe { CStr::from_ptr(dup) }.to_bytes_with_nul(),
b"example\0"
);
unsafe { ALLOCATOR.deallocate(dup, libc::strlen(dup) + 1) };
let src = ["hello", "", ",", "world"];
let dup = unsafe { gz_strcat(&src) };
assert!(!dup.is_null());
assert_eq!(
unsafe { CStr::from_ptr(dup) }.to_bytes_with_nul(),
b"hello,world\0"
);
unsafe { ALLOCATOR.deallocate(dup, libc::strlen(dup) + 1) };
}
#[test]
fn test_fd_path() {
let mut buf = [0u8; 27];
assert_eq!(fd_path(&mut buf, 0).to_bytes(), b"<fd:0>");
assert_eq!(fd_path(&mut buf, 9).to_bytes(), b"<fd:9>");
assert_eq!(fd_path(&mut buf, -1).to_bytes(), b"<fd:-1>");
assert_eq!(
fd_path(&mut buf, i32::MIN).to_bytes(),
format!("<fd:{}>", i32::MIN).as_bytes(),
);
}
#[test]
#[cfg_attr(
not(any(target_os = "linux", target_os = "macos")),
ignore = "lseek is not implemented"
)]
fn test_gz_error() {
assert!(unsafe { gzerror(ptr::null_mut(), ptr::null_mut()) }.is_null());
let handle = unsafe { gzdopen(-2, c!(b"r\0")) };
assert!(!handle.is_null());
let state = (unsafe { handle.cast::<GzState>().as_mut() }).unwrap();
assert_eq!(state.err, Z_OK);
assert!(state.msg.is_null());
let mut err = Z_ERRNO;
let msg = unsafe { gzerror(handle, &mut err as *mut c_int) };
assert_eq!(unsafe { CStr::from_ptr(msg) }.to_bytes_with_nul(), b"\0");
assert_eq!(err, Z_OK);
let state = (unsafe { handle.cast::<GzState>().as_mut() }).unwrap();
unsafe { gz_error(state, Some((Z_ERRNO, "example error"))) };
assert_eq!(state.err, Z_ERRNO);
assert_eq!(
unsafe { CStr::from_ptr(state.msg) }.to_bytes_with_nul(),
b"<fd:-2>: example error\0"
);
let mut err = Z_OK;
let msg = unsafe { gzerror(handle, &mut err as *mut c_int) };
assert_eq!(
unsafe { CStr::from_ptr(msg) }.to_bytes_with_nul(),
b"<fd:-2>: example error\0"
);
assert_eq!(err, Z_ERRNO);
let state = (unsafe { handle.cast::<GzState>().as_mut() }).unwrap();
unsafe { gz_error(state, None) };
assert_eq!(state.err, Z_OK);
assert!(state.msg.is_null());
let mut err = Z_ERRNO;
let msg = unsafe { gzerror(handle, &mut err as *mut c_int) };
assert_eq!(unsafe { CStr::from_ptr(msg) }.to_bytes_with_nul(), b"\0");
assert_eq!(err, Z_OK);
let state = (unsafe { handle.cast::<GzState>().as_mut() }).unwrap();
unsafe { gz_error(state, Some((Z_MEM_ERROR, "should be ignored"))) };
assert_eq!(state.err, Z_MEM_ERROR);
assert!(state.msg.is_null());
let mut err = Z_OK;
let msg = unsafe { gzerror(handle, &mut err as *mut c_int) };
assert_eq!(
unsafe { CStr::from_ptr(msg) }.to_bytes_with_nul(),
b"out of memory\0"
);
assert_eq!(err, Z_MEM_ERROR);
assert_eq!(unsafe { gzclose(handle) }, Z_ERRNO);
}
#[test]
#[cfg_attr(
not(any(target_os = "linux", target_os = "macos")),
ignore = "lseek is not implemented"
)]
fn test_gzclearerr() {
unsafe { gzclearerr(ptr::null_mut()) };
let handle = unsafe { gzdopen(-2, c!(b"r\0")) };
assert!(!handle.is_null());
unsafe { handle.cast::<GzState>().as_mut().unwrap().eof = true };
unsafe { handle.cast::<GzState>().as_mut().unwrap().past = true };
unsafe { gzclearerr(handle) };
assert!(!unsafe { handle.cast::<GzState>().as_ref().unwrap().eof });
assert!(!unsafe { handle.cast::<GzState>().as_ref().unwrap().past });
unsafe {
gz_error(
handle.cast::<GzState>().as_mut().unwrap(),
Some((Z_STREAM_ERROR, "example error")),
)
};
let mut err = Z_OK;
let msg = unsafe { gzerror(handle, &mut err as *mut c_int) };
assert_eq!(err, Z_STREAM_ERROR);
assert_eq!(
unsafe { CStr::from_ptr(msg) }.to_bytes_with_nul(),
b"<fd:-2>: example error\0"
);
unsafe { gzclearerr(handle) };
let msg = unsafe { gzerror(handle, &mut err as *mut c_int) };
assert_eq!(err, Z_OK);
assert_eq!(unsafe { CStr::from_ptr(msg) }.to_bytes_with_nul(), b"\0");
assert_eq!(unsafe { gzclose(handle) }, Z_ERRNO);
for mode in [c!(b"w\0"), c!(b"a\0")] {
let handle = unsafe { gzdopen(-2, mode) };
assert!(!handle.is_null());
assert_eq!(unsafe { gzeof(handle) }, 0);
unsafe { handle.cast::<GzState>().as_mut().unwrap().eof = true };
unsafe { handle.cast::<GzState>().as_mut().unwrap().past = true };
unsafe { gzclearerr(handle) };
assert!(unsafe { handle.cast::<GzState>().as_ref().unwrap().eof });
assert!(unsafe { handle.cast::<GzState>().as_ref().unwrap().past });
unsafe {
gz_error(
handle.cast::<GzState>().as_mut().unwrap(),
Some((Z_STREAM_ERROR, "example error")),
)
};
unsafe { gzclearerr(handle) };
let msg = unsafe { gzerror(handle, &mut err as *mut c_int) };
assert_eq!(err, Z_OK);
assert_eq!(unsafe { CStr::from_ptr(msg) }.to_bytes_with_nul(), b"\0");
assert_eq!(unsafe { gzclose(handle) }, Z_ERRNO);
}
}
#[test]
#[cfg_attr(
not(any(target_os = "linux", target_os = "macos")),
ignore = "lseek is not implemented"
)]
fn test_gzeof() {
assert_eq!(unsafe { gzeof(ptr::null_mut()) }, 0);
let handle = unsafe { gzdopen(-2, c!(b"r\0")) };
assert!(!handle.is_null());
assert_eq!(unsafe { gzeof(handle) }, 0);
unsafe { handle.cast::<GzState>().as_mut().unwrap().eof = true };
assert_eq!(unsafe { gzeof(handle) }, 0);
unsafe { handle.cast::<GzState>().as_mut().unwrap().past = true };
assert_eq!(unsafe { gzeof(handle) }, 1);
assert_eq!(unsafe { gzclose(handle) }, Z_ERRNO);
for mode in [c!(b"w\0"), c!(b"a\0")] {
let handle = unsafe { gzdopen(-2, mode) };
assert!(!handle.is_null());
assert_eq!(unsafe { gzeof(handle) }, 0);
unsafe { handle.cast::<GzState>().as_mut().unwrap().past = true };
assert_eq!(unsafe { gzeof(handle) }, 0);
assert_eq!(unsafe { gzclose(handle) }, Z_ERRNO);
}
}
#[test]
#[cfg_attr(
not(any(target_os = "linux", target_os = "macos")),
ignore = "lseek is not implemented"
)]
fn test_gzdirect_gzip_file() {
let file = unsafe {
gzopen(
CString::new(crate_path("src/test-data/example.gz"))
.unwrap()
.as_ptr(),
CString::new("r").unwrap().as_ptr(),
)
};
assert!(!file.is_null());
const FILE_SIZE: usize = 48; const BLOCK_SIZE: usize = 40;
unsafe { file.cast::<GzState>().as_mut().unwrap().want = BLOCK_SIZE };
assert_eq!(unsafe { gzdirect(file) }, 0);
assert_eq!(unsafe { file.cast::<GzState>().as_ref().unwrap().have }, 0);
assert_eq!(
unsafe { file.cast::<GzState>().as_ref().unwrap().stream.avail_in },
BLOCK_SIZE as uInt
);
unsafe {
let state = file.cast::<GzState>().as_mut().unwrap();
const CONSUME: usize = 10;
state.stream.next_in = state.stream.next_in.add(CONSUME);
state.stream.avail_in -= CONSUME as uInt;
let expected_avail = BLOCK_SIZE - CONSUME + (FILE_SIZE - BLOCK_SIZE);
assert_eq!(gz_avail(state), Ok(expected_avail));
assert_eq!(state.stream.avail_in as usize, expected_avail);
};
assert_eq!(unsafe { gzclose(file) }, Z_OK);
}
#[test]
#[cfg_attr(
not(any(target_os = "linux", target_os = "macos")),
ignore = "lseek is not implemented"
)]
fn test_gzdirect_non_gzip_file() {
let file = unsafe {
gzopen(
CString::new(crate_path("src/test-data/example.txt"))
.unwrap()
.as_ptr(),
CString::new("r").unwrap().as_ptr(),
)
};
assert!(!file.is_null());
assert_eq!(unsafe { gzdirect(file) }, 1);
assert_eq!(unsafe { file.cast::<GzState>().as_ref().unwrap().have }, 20);
assert_eq!(
unsafe { file.cast::<GzState>().as_ref().unwrap().stream.avail_in },
0
);
assert_eq!(unsafe { gzclose(file) }, Z_OK);
let file = unsafe {
gzopen(
CString::new(crate_path("src/test-data/magic-only.gz"))
.unwrap()
.as_ptr(),
CString::new("r").unwrap().as_ptr(),
)
};
assert!(!file.is_null());
assert_eq!(unsafe { gzdirect(file) }, 0);
assert_eq!(unsafe { file.cast::<GzState>().as_ref().unwrap().have }, 0);
assert_eq!(
unsafe { file.cast::<GzState>().as_ref().unwrap().stream.avail_in },
2
);
assert_eq!(unsafe { gzclose(file) }, Z_OK);
let file = unsafe {
gzopen(
CString::new(crate_path("src/test-data/incomplete-magic.gz"))
.unwrap()
.as_ptr(),
CString::new("r").unwrap().as_ptr(),
)
};
assert!(!file.is_null());
assert_eq!(unsafe { gzdirect(file) }, 1);
assert_eq!(unsafe { file.cast::<GzState>().as_ref().unwrap().have }, 1);
assert_eq!(
unsafe { file.cast::<GzState>().as_ref().unwrap().stream.avail_in },
0
);
assert_eq!(unsafe { gzclose(file) }, Z_OK);
}
#[test]
#[cfg_attr(
not(any(target_os = "linux", target_os = "macos")),
ignore = "lseek is not implemented"
)]
fn test_gzbuffer() {
assert_eq!(unsafe { gzbuffer(ptr::null_mut(), 1024) }, -1);
let file = unsafe {
gzopen(
CString::new(crate_path("src/test-data/example.txt"))
.unwrap()
.as_ptr(),
CString::new("r").unwrap().as_ptr(),
)
};
unsafe { file.cast::<GzState>().as_mut().unwrap().mode = GzMode::GZ_NONE };
assert_eq!(unsafe { gzbuffer(file, 1024) }, -1);
unsafe { file.cast::<GzState>().as_mut().unwrap().mode = GzMode::GZ_READ };
assert_eq!(unsafe { gzbuffer(file, 1024) }, 0);
assert_eq!(
unsafe { file.cast::<GzState>().as_ref().unwrap().want },
1024
);
assert_eq!(unsafe { gzbuffer(file, 5) }, 0);
assert_eq!(unsafe { file.cast::<GzState>().as_ref().unwrap().want }, 8);
assert_eq!(unsafe { gzdirect(file) }, 1);
assert_eq!(unsafe { gzbuffer(file, 1024) }, -1);
assert_eq!(unsafe { file.cast::<GzState>().as_ref().unwrap().want }, 8);
assert_eq!(unsafe { gzclose(file) }, Z_OK);
}
}