Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add a separate cdylib crate #168

Merged
merged 1 commit into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 30 additions & 16 deletions .github/workflows/checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -257,20 +257,34 @@ jobs:
with:
toolchain: stable
targets: ${{matrix.target}}
- name: Rust cache
uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8
with:
shared-key: "stable-${{matrix.target}}"
- name: get zpipe.c
run: wget https://www.zlib.net/zpipe.c
- name: cargo build
run: cargo build --target ${{matrix.target}} -p libz-rs-sys --release
- name: cc
run: cc -o zpipe zpipe.c target/${{matrix.target}}/release/deps/liblibz_rs_sys.so
- name: execute
run: cat Cargo.toml | ./zpipe | ./zpipe -d > out.txt
- name: compare
run: cmp -s Cargo.toml out.txt
- name: "cdylib: default settings"
working-directory: libz-rs-sys-cdylib
run: |
cargo build --release --target ${{matrix.target}}
cc -o zpipe zpipe.c target/${{matrix.target}}/release/deps/libz_rs.so
./zpipe < Cargo.toml | ./zpipe -d > out.txt
cmp -s Cargo.toml out.txt
- name: "cdylib: rust-allocator"
working-directory: libz-rs-sys-cdylib
run: |
cargo build --release --target ${{matrix.target}} --no-default-features --features="rust-allocator"
cc -o zpipe zpipe.c target/${{matrix.target}}/release/deps/libz_rs.so
./zpipe < Cargo.toml | ./zpipe -d > out.txt
cmp -s Cargo.toml out.txt
- name: "cdylib: no_std"
working-directory: libz-rs-sys-cdylib
run: |
cargo build --release --target ${{matrix.target}} --no-default-features
cc -o zpipe_no_std zpipe_no_std.c target/${{matrix.target}}/release/deps/libz_rs.so
./zpipe_no_std < Cargo.toml | ./zpipe_no_std -d > out.txt
cmp -s Cargo.toml out.txt
- name: "cdylib: custom-prefix"
working-directory: libz-rs-sys-cdylib
env:
LIBZ_RS_SYS_PREFIX: "MY_CUSTOM_PREFIX_"
run: |
cargo build --release --target ${{matrix.target}} --features=custom-prefix
objdump -tT target/${{matrix.target}}/release/deps/libz_rs.so | grep -q "MY_CUSTOM_PREFIX_uncompress" || (echo "symbol not found!" && exit 1)

miri:
name: "Miri"
Expand All @@ -285,8 +299,8 @@ jobs:
uses: taiki-e/install-action@56ab7930c591507f833cbaed864d201386d518a8
with:
tool: cargo-nextest
- name: Test public C api with NULL arguments
- name: Test public C api with NULL arguments
run: "cargo +nightly miri nextest run -j4 -p test-libz-rs-sys null::"
env:
RUSTFLAGS: "-Ctarget-feature=+avx2"
MIRIFLAGS: "-Zmiri-tree-borrows"
MIRIFLAGS: "-Zmiri-tree-borrows"
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ members = [
"test-libz-rs-sys",
"dynamic-libz-sys",
]
exclude = [ ]
exclude = [
"libz-rs-sys-cdylib",
]

# Without the `-p` flag, cargo ignores `--no-default-features` when you have a
# workspace, and without `resolver = "2"` here, you can't use `-p` like this.
Expand Down
16 changes: 16 additions & 0 deletions libz-rs-sys-cdylib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "libz-rs-sys-cdylib"
readme = "README.md"

[lib]
name = "z_rs" # turns into e.g. `libz_rs.so`
crate-type=["cdylib"]

[features]
default = ["c-allocator"] # when used as a cdylib crate, use the c allocator
c-allocator = ["libz-rs-sys/c-allocator"] # by default, use malloc/free for memory allocation
rust-allocator = ["libz-rs-sys/rust-allocator", "libz-rs-sys/std"] # by default, use the rust global alloctor for memory allocation
custom-prefix = ["libz-rs-sys/custom-prefix"] # use the LIBZ_RS_SYS_PREFIX to prefix all exported symbols

[dependencies]
libz-rs-sys = { path = "../libz-rs-sys", default-features = false }
79 changes: 79 additions & 0 deletions libz-rs-sys-cdylib/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# `libz-rs-sys-cdylib`

Build `zlib-rs` as a drop-in replacement for the zlib dynamic library

```sh
# build the cdylib
cargo build --release

# the extension of a cdylib varies per platform
cc zpipe.c target/release/libz_rs.so

# verify the implementation can compress and decompress our Cargo.toml
./zpipe < Cargo.toml | ./zpipe -d
```

By default this build uses libc `malloc`/`free` to (de)allocate memory, and only depends on the rust `core` library.

## Features

### Allocators

We provide three options for the default allocator

**`c-allocator`**

```sh
cargo build --release --no-default-features --features "c-allocator"
```

Uses the libc `malloc` and `free` functions for memory allocation.

**`rust-allocator`**

```sh
cargo build --release --no-default-features --features "std,rust-allocator"
```
Uses the rust standard library global allocator for memory allocation.

**no allocator**

```sh
cargo build --release --no-default-features
```

No allocator is configured automatically. This means that, before [`inflateInit_`] or [`deflateInit_`] are called,
the user must set the `zalloc` and `zfree` fields of the `z_stream` to valid allocation and deallocation functions,
and the `opaque` field to either `NULL` or a pointer expected by the (de)allocation functions.

If no allocator is configured, the initialization functions will return `Z_STREAM_ERROR`.

### Symbol Prefix

Symbols in C programs all live in the same namespace. A common solution to prevent names from clashing is to prefix
all of a library's symbols with a prefix. We support prefixing the name at build time with the `custom-prefix` feature
flag. When enabled, the value of the `LIBZ_RS_SYS_PREFIX` is used as a prefix for all exported symbols. For example:

```ignore
> LIBZ_RS_SYS_PREFIX="MY_CUSTOM_PREFIX_" cargo build --release --features=custom-prefix

Compiling libz-rs-sys v0.2.1 (/home/folkertdev/rust/zlib-rs/libz-rs-sys)
Compiling libz-rs-sys-cdylib v0.2.1 (/home/folkertdev/rust/zlib-rs/libz-rs-sys-cdylib)
Finished `release` profile [optimized + debuginfo] target(s) in 0.16s
> objdump -tT ../target/release/libz_rs.so | grep "uncompress"
00000000000758e0 l O .got 0000000000000000 _ZN7zlib_rs7inflate10uncompress17hda65e03b54919c40E$got
0000000000025da0 l F .text 000000000000029a _ZN7zlib_rs7inflate10uncompress17hda65e03b54919c40E
000000000001d700 g F .text 0000000000000051 MY_CUSTOM_PREFIX_uncompress
000000000001d700 g DF .text 0000000000000051 Base MY_CUSTOM_PREFIX_uncompress
```

### `![no_std]`

The dynamic library can be built without the rust `std` crate, e.g. for embedded devices that don't support it. Disabling
the standard library has the following limitations:

- CPU feature detection is currently disabled. This is true for both compile-time and run-time feature detection.
This means `zlib-rs` will not make use of SIMD or other custom instructions.
- The `rust-allocator` should not be used. It internally enables the standard library, causing issues. Using `c-allocator`
or not providing an allocator at build time is still supported.On embedded it is most common to provide a custom allocator
that "allocates" into a custom array.
3 changes: 3 additions & 0 deletions libz-rs-sys-cdylib/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
extern crate libz_rs_sys;

pub use libz_rs_sys::*;
205 changes: 205 additions & 0 deletions libz-rs-sys-cdylib/zpipe.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
/* zpipe.c: example of proper use of zlib's inflate() and deflate()
Not copyrighted -- provided to the public domain
Version 1.4 11 December 2005 Mark Adler */

/* Version history:
1.0 30 Oct 2004 First version
1.1 8 Nov 2004 Add void casting for unused return values
Use switch statement for inflate() return values
1.2 9 Nov 2004 Add assertions to document zlib guarantees
1.3 6 Apr 2005 Remove incorrect assertion in inf()
1.4 11 Dec 2005 Add hack to avoid MSDOS end-of-line conversions
Avoid some compiler warnings for input and output buffers
*/

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "zlib.h"

#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__)
# include <fcntl.h>
# include <io.h>
# define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY)
#else
# define SET_BINARY_MODE(file)
#endif

#define CHUNK 16384

/* Compress from file source to file dest until EOF on source.
def() returns Z_OK on success, Z_MEM_ERROR if memory could not be
allocated for processing, Z_STREAM_ERROR if an invalid compression
level is supplied, Z_VERSION_ERROR if the version of zlib.h and the
version of the library linked do not match, or Z_ERRNO if there is
an error reading or writing the files. */
int def(FILE *source, FILE *dest, int level)
{
int ret, flush;
unsigned have;
z_stream strm;
unsigned char in[CHUNK];
unsigned char out[CHUNK];

/* allocate deflate state */
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
ret = deflateInit(&strm, level);
if (ret != Z_OK)
return ret;

/* compress until end of file */
do {
strm.avail_in = fread(in, 1, CHUNK, source);
if (ferror(source)) {
(void)deflateEnd(&strm);
return Z_ERRNO;
}
flush = feof(source) ? Z_FINISH : Z_NO_FLUSH;
strm.next_in = in;

/* run deflate() on input until output buffer not full, finish
compression if all of source has been read in */
do {
strm.avail_out = CHUNK;
strm.next_out = out;
ret = deflate(&strm, flush); /* no bad return value */
assert(ret != Z_STREAM_ERROR); /* state not clobbered */
have = CHUNK - strm.avail_out;
if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
(void)deflateEnd(&strm);
return Z_ERRNO;
}
} while (strm.avail_out == 0);
assert(strm.avail_in == 0); /* all input will be used */

/* done when last data in file processed */
} while (flush != Z_FINISH);
assert(ret == Z_STREAM_END); /* stream will be complete */

/* clean up and return */
(void)deflateEnd(&strm);
return Z_OK;
}

/* Decompress from file source to file dest until stream ends or EOF.
inf() returns Z_OK on success, Z_MEM_ERROR if memory could not be
allocated for processing, Z_DATA_ERROR if the deflate data is
invalid or incomplete, Z_VERSION_ERROR if the version of zlib.h and
the version of the library linked do not match, or Z_ERRNO if there
is an error reading or writing the files. */
int inf(FILE *source, FILE *dest)
{
int ret;
unsigned have;
z_stream strm;
unsigned char in[CHUNK];
unsigned char out[CHUNK];

/* allocate inflate state */
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = 0;
strm.next_in = Z_NULL;
ret = inflateInit(&strm);
if (ret != Z_OK)
return ret;

/* decompress until deflate stream ends or end of file */
do {
strm.avail_in = fread(in, 1, CHUNK, source);
if (ferror(source)) {
(void)inflateEnd(&strm);
return Z_ERRNO;
}
if (strm.avail_in == 0)
break;
strm.next_in = in;

/* run inflate() on input until output buffer not full */
do {
strm.avail_out = CHUNK;
strm.next_out = out;
ret = inflate(&strm, Z_NO_FLUSH);
assert(ret != Z_STREAM_ERROR); /* state not clobbered */
switch (ret) {
case Z_NEED_DICT:
ret = Z_DATA_ERROR; /* and fall through */
case Z_DATA_ERROR:
case Z_MEM_ERROR:
(void)inflateEnd(&strm);
return ret;
}
have = CHUNK - strm.avail_out;
if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
(void)inflateEnd(&strm);
return Z_ERRNO;
}
} while (strm.avail_out == 0);

/* done when inflate() says it's done */
} while (ret != Z_STREAM_END);

/* clean up and return */
(void)inflateEnd(&strm);
return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
}

/* report a zlib or i/o error */
void zerr(int ret)
{
fputs("zpipe: ", stderr);
switch (ret) {
case Z_ERRNO:
if (ferror(stdin))
fputs("error reading stdin\n", stderr);
if (ferror(stdout))
fputs("error writing stdout\n", stderr);
break;
case Z_STREAM_ERROR:
fputs("invalid compression level\n", stderr);
break;
case Z_DATA_ERROR:
fputs("invalid or incomplete deflate data\n", stderr);
break;
case Z_MEM_ERROR:
fputs("out of memory\n", stderr);
break;
case Z_VERSION_ERROR:
fputs("zlib version mismatch!\n", stderr);
}
}

/* compress or decompress from stdin to stdout */
int main(int argc, char **argv)
{
int ret;

/* avoid end-of-line conversions */
SET_BINARY_MODE(stdin);
SET_BINARY_MODE(stdout);

/* do compression if no arguments */
if (argc == 1) {
ret = def(stdin, stdout, Z_DEFAULT_COMPRESSION);
if (ret != Z_OK)
zerr(ret);
return ret;
}

/* do decompression if -d specified */
else if (argc == 2 && strcmp(argv[1], "-d") == 0) {
ret = inf(stdin, stdout);
if (ret != Z_OK)
zerr(ret);
return ret;
}

/* otherwise, report usage */
else {
fputs("zpipe usage: zpipe [-d] < source > dest\n", stderr);
return 1;
}
}
Loading
Loading