Skip to content

Commit 525490f

Browse files
brianpanefolkertdev
authored andcommitted
Implement gzsetparams
1 parent 8b15596 commit 525490f

File tree

2 files changed

+191
-1
lines changed

2 files changed

+191
-1
lines changed

libz-rs-sys/src/gz.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2269,6 +2269,68 @@ pub unsafe extern "C-unwind" fn gzgets(file: gzFile, buf: *mut c_char, len: c_in
22692269
}
22702270
}
22712271

2272+
/// Dynamically update the compression level and strategy for `file`. See the
2273+
/// description of [`deflateInit2`] for the meaning of these parameters. Previously
2274+
/// provided data is flushed before applying the parameter changes.
2275+
///
2276+
/// Note: If `level` is not valid, this function will silently fail with a return
2277+
/// value of `Z_OK`, matching the semantics of the C zlib version. However, if
2278+
/// `strategy` is not valid, this function will return an error.
2279+
///
2280+
/// # Returns
2281+
///
2282+
/// - [`Z_OK`] on success.
2283+
/// - [`Z_STREAM_ERROR`] if the file was not opened for writing.
2284+
/// - [`Z_ERRNO`] if there is an error writing the flushed data.
2285+
/// - [`Z_MEM_ERROR`] if there is a memory allocation error.
2286+
///
2287+
/// # Safety
2288+
///
2289+
/// - `file`, if non-null, must be an open file handle obtained from [`gzopen`] or [`gzdopen`].
2290+
#[cfg_attr(feature = "export-symbols", export_name = crate::prefix!(gzsetparams))]
2291+
pub unsafe extern "C-unwind" fn gzsetparams(file: gzFile, level: c_int, strategy: c_int) -> c_int {
2292+
let Ok(strategy) = Strategy::try_from(strategy) else {
2293+
return Z_STREAM_ERROR;
2294+
};
2295+
let Some(state) = (unsafe { file.cast::<GzState>().as_mut() }) else {
2296+
return Z_STREAM_ERROR;
2297+
};
2298+
2299+
// Check that we're writing and that there's no error.
2300+
if state.mode != GzMode::GZ_WRITE || state.err != Z_OK || state.direct {
2301+
return Z_STREAM_ERROR;
2302+
}
2303+
2304+
// If no change is requested, then do nothing.
2305+
if level == c_int::from(state.level) && strategy == state.strategy {
2306+
return Z_OK;
2307+
}
2308+
2309+
/* FIXME: uncomment when seek support is implemented
2310+
// Check for seek request.
2311+
if state.seek {
2312+
state.seek = false`;
2313+
if gz_zero(state, state.skip) == -1 {
2314+
return state.err;
2315+
}
2316+
}
2317+
*/
2318+
2319+
// Change compression parameters for subsequent input.
2320+
if !state.input.is_null() {
2321+
// Flush previous input with previous parameters before changing.
2322+
if state.stream.avail_in != 0 && gz_comp(state, Z_BLOCK).is_err() {
2323+
return state.err;
2324+
}
2325+
// Safety: Because `state` is in write mode and `state.input` is non-null, `state.stream`
2326+
// was initialized using `deflateInit2` in `gz_init`.
2327+
unsafe { super::deflateParams(&mut state.stream, level, strategy as c_int) };
2328+
}
2329+
state.level = level as _;
2330+
state.strategy = strategy;
2331+
Z_OK
2332+
}
2333+
22722334
// Create a deep copy of a C string using `ALLOCATOR`
22732335
//
22742336
// # Safety

test-libz-rs-sys/src/gz.rs

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use zlib_rs::c_api::*;
33
use libz_rs_sys::{
44
gzFile_s, gzbuffer, gzclearerr, gzclose, gzclose_r, gzclose_w, gzdirect, gzdopen, gzerror,
55
gzflush, gzfread, gzfwrite, gzgetc, gzgetc_, gzgets, gzoffset, gzopen, gzputc, gzputs, gzread,
6-
gztell, gzungetc, gzwrite,
6+
gzsetparams, gztell, gzungetc, gzwrite,
77
};
88

99
use libc::size_t;
@@ -1472,6 +1472,134 @@ fn gzfwrite_error() {
14721472
assert_eq!(unsafe { gzclose(file) }, Z_ERRNO);
14731473
}
14741474

1475+
#[test]
1476+
fn gzsetparams_basic() {
1477+
// Create a temporary directory that will be automatically removed when
1478+
// temp_dir goes out of scope.
1479+
let temp_dir_path = temp_base();
1480+
let temp_dir = tempfile::TempDir::new_in(temp_dir_path).unwrap();
1481+
let temp_path = temp_dir.path();
1482+
let file_name = path(temp_path, "output.gz");
1483+
1484+
// Open a file for writing.
1485+
let file = unsafe {
1486+
gzopen(
1487+
CString::new(file_name.as_str()).unwrap().as_ptr(),
1488+
CString::new("w").unwrap().as_ptr(),
1489+
)
1490+
};
1491+
assert!(!file.is_null());
1492+
1493+
// Call gzsetparams before doing any writes. It should return Z_OK and should
1494+
// not cause any file I/O yet.
1495+
assert_eq!(unsafe { gzsetparams(file, 2, 0) }, Z_OK);
1496+
assert_eq!(file_size(&file_name).unwrap(), 0);
1497+
1498+
// Provide some data to the file descriptor for compression. The deflate output
1499+
// should be buffered, so the file size should not increase yet.
1500+
const STRING1: &[u8] = b"first write ";
1501+
assert_eq!(
1502+
unsafe { gzwrite(file, STRING1.as_ptr().cast::<c_void>(), STRING1.len() as _) },
1503+
STRING1.len() as _
1504+
);
1505+
assert_eq!(file_size(&file_name).unwrap(), 0);
1506+
1507+
// Call gzsetparams to change the deflate parameters. It should return Z_OK and should
1508+
// flush some of the previously buffered data to the file.
1509+
assert_eq!(unsafe { gzsetparams(file, 9, 0) }, Z_OK);
1510+
let size = file_size(&file_name).unwrap();
1511+
assert!(size > 0);
1512+
1513+
// Write more data to the file descriptor.
1514+
const STRING2: &[u8] = b"second write ";
1515+
assert_eq!(
1516+
unsafe { gzwrite(file, STRING2.as_ptr().cast::<c_void>(), STRING2.len() as _) },
1517+
STRING2.len() as _
1518+
);
1519+
1520+
// Call gzsetparams with the same parameters as last time. This should be a no-op, returning
1521+
// Z_OK and not forcing a flush of the buffered write.
1522+
assert_eq!(unsafe { gzsetparams(file, 9, 0) }, Z_OK);
1523+
assert_eq!(file_size(&file_name).unwrap(), size);
1524+
1525+
// Call gzsetparams with different parameters. This should return Z_OK and should flush
1526+
// the previously buffered output to the file.
1527+
assert_eq!(unsafe { gzsetparams(file, 9, 1) }, Z_OK);
1528+
let new_size = file_size(&file_name).unwrap();
1529+
assert!(new_size > size);
1530+
let size = new_size;
1531+
1532+
// Do another write, and close the file.
1533+
const STRING3: &[u8] = b"third write";
1534+
assert_eq!(
1535+
unsafe { gzwrite(file, STRING3.as_ptr().cast::<c_void>(), STRING3.len() as _) },
1536+
STRING3.len() as _
1537+
);
1538+
assert_eq!(unsafe { gzclose(file) }, Z_OK);
1539+
let new_size = file_size(&file_name).unwrap();
1540+
assert!(new_size > size);
1541+
1542+
// Read and uncompress the file. We should get back the concatenation of all the
1543+
// content written to it.
1544+
let file = unsafe {
1545+
gzopen(
1546+
CString::new(file_name.as_str()).unwrap().as_ptr(),
1547+
CString::new("r").unwrap().as_ptr(),
1548+
)
1549+
};
1550+
assert!(!file.is_null());
1551+
const EXPECTED: &[u8] = b"first write second write third write";
1552+
let mut buf = [0u8; EXPECTED.len() + 1];
1553+
assert_eq!(
1554+
unsafe { gzread(file, buf.as_mut_ptr().cast::<c_void>(), buf.len() as _) },
1555+
EXPECTED.len() as _
1556+
);
1557+
assert_eq!(&buf[..EXPECTED.len()], EXPECTED);
1558+
assert_eq!(unsafe { gzclose(file) }, Z_OK);
1559+
}
1560+
1561+
#[test]
1562+
fn gzsetparams_error() {
1563+
// gzsetparams on a null file handle should return Z_STREAM_ERROR.
1564+
assert_eq!(
1565+
unsafe { gzsetparams(ptr::null_mut(), 1, 0) },
1566+
Z_STREAM_ERROR
1567+
);
1568+
1569+
// gzsetparams on a read-only file should return Z_STREAM_ERROR.
1570+
let file = unsafe { gzdopen(-2, CString::new("r").unwrap().as_ptr()) };
1571+
assert!(!file.is_null());
1572+
assert_eq!(unsafe { gzsetparams(file, 1, 0) }, Z_STREAM_ERROR);
1573+
assert_eq!(unsafe { gzclose(file) }, Z_ERRNO);
1574+
1575+
// gzsetparams on a file in direct-write mode should return Z_STREAM_ERROR.
1576+
let file = unsafe { gzdopen(-2, CString::new("wT").unwrap().as_ptr()) };
1577+
assert!(!file.is_null());
1578+
assert_eq!(unsafe { gzsetparams(file, 1, 0) }, Z_STREAM_ERROR);
1579+
assert_eq!(unsafe { gzclose(file) }, Z_ERRNO);
1580+
1581+
// gzsetparams with an invalid strategy should return Z_STREAM_ERROR.
1582+
let file = unsafe { gzdopen(-2, CString::new("w").unwrap().as_ptr()) };
1583+
assert!(!file.is_null());
1584+
assert_eq!(unsafe { gzsetparams(file, 1, 0) }, Z_OK);
1585+
assert_eq!(unsafe { gzsetparams(file, 1, -1) }, Z_STREAM_ERROR);
1586+
assert_eq!(unsafe { gzclose(file) }, Z_ERRNO);
1587+
1588+
// Open a gzip file handle around an invalid file descriptor, do a buffered write,
1589+
// and then call gzsetparams with valid parameters. This should trigger a file
1590+
// write that fails, resulting in an error code bubbling back up through
1591+
// through gzsetparams.
1592+
let file = unsafe { gzdopen(-2, CString::new("w").unwrap().as_ptr()) };
1593+
assert!(!file.is_null());
1594+
const CONTENT: &[u8] = b"0123456789";
1595+
assert_eq!(
1596+
unsafe { gzwrite(file, CONTENT.as_ptr().cast::<c_void>(), CONTENT.len() as _) },
1597+
CONTENT.len() as _
1598+
);
1599+
assert_eq!(unsafe { gzsetparams(file, 1, 2) }, Z_ERRNO);
1600+
assert_eq!(unsafe { gzclose(file) }, Z_ERRNO);
1601+
}
1602+
14751603
// Get the size in bytes of a file.
14761604
//
14771605
// # Returns

0 commit comments

Comments
 (0)