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 configuration to build a dynamic library #57

Merged
merged 6 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ members = [
"libbzip2-rs-sys",
"test-libbzip2-rs-sys",
]
exclude = [
"libbzip2-rs-sys-cdylib",
]
package.edition = "2021"


Expand Down
23 changes: 23 additions & 0 deletions libbzip2-rs-sys-cdylib/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions libbzip2-rs-sys-cdylib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[package]
name = "libz-rs-sys-cdylib"
folkertdev marked this conversation as resolved.
Show resolved Hide resolved
folkertdev marked this conversation as resolved.
Show resolved Hide resolved
version = "0.0.0"
edition = "2021"
readme = "README.md"
license = "Zlib"
folkertdev marked this conversation as resolved.
Show resolved Hide resolved
repository = "https://github.com/trifectatechfoundation/libbzip2-rs"
homepage = "https://github.com/trifectatechfoundation/libbzip2-rs"
publish = true
description = "A memory-safe bzip2 implementation written in rust"
rust-version = "1.82" # MSRV

[lib]
name = "bz2_rs" # turns into e.g. `libz_rs.so`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/libz_rs.so/libbz2_rs.so/

folkertdev marked this conversation as resolved.
Show resolved Hide resolved
crate-type=["cdylib"]

[features]
default = ["c-allocator", "libbzip2-rs-sys/stdio", "libbzip2-rs-sys/std"] # when used as a cdylib crate, use the c allocator
c-allocator = ["libbzip2-rs-sys/c-allocator"] # by default, use malloc/free for memory allocation
rust-allocator = ["libbzip2-rs-sys/rust-allocator", "libbzip2-rs-sys/std"] # by default, use the rust global alloctor for memory allocation
custom-prefix = ["libbzip2-rs-sys/custom-prefix"] # use the LIBZ_RS_SYS_PREFIX to prefix all exported symbols
folkertdev marked this conversation as resolved.
Show resolved Hide resolved
folkertdev marked this conversation as resolved.
Show resolved Hide resolved
capi = []

[dependencies]
libbzip2-rs-sys = { version = "0.0.0", path = "../libbzip2-rs-sys", default-features = false }

[package.metadata.capi.library]
version = "1.0.9" # the zlib api version we match
folkertdev marked this conversation as resolved.
Show resolved Hide resolved
name = "bz2_rs"

[package.metadata.capi.header]
enabled = false

[package.metadata.capi.pkg_config]
name = "libbz2_rs"
filename = "libbz2_rs"
114 changes: 114 additions & 0 deletions libbzip2-rs-sys-cdylib/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# `libbzip2-rs-sys-cdylib`

A drop-in replacement for the `libbz2` dynamic library

```sh
# build the cdylib
# using `cargo build` will work but has limitations, see below
cargo build --release

# the extension of a cdylib varies per platform
cc bzpipe.c -o bzpipe target/release/libbz2_rs.so -I ../

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

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

## Feature Flags

### 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 [`BZ2_bzCompressInit`] or [`BZ2_bzDecompressInit`] are called,
the user must set the `bzalloc` and `bzfree` fields of the `bz_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 `BZ_PARAM_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 `LIBBZIP2_RS_SYS_PREFIX` is used as a prefix for all exported symbols. For example:

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

Compiling libbzip2-rs-sys v0.0.0 (libbzip2-rs/libbzip2-rs-sys)
Compiling libz-rs-sys-cdylib v0.0.0 (libbzip2-rs/libbzip2-rs-sys-cdylib)
Finished `release` profile [optimized] target(s) in 0.97s
> objdump -tT target/release/libbz2_rs.so | grep "BZ2_bzCompressInit"
000000000002f300 l F .text 0000000000000441 .hidden _ZN15libbzip2_rs_sys5bzlib22BZ2_bzCompressInitHelp17hac60bda3d983fe05E
000000000002f2e0 g F .text 000000000000001a MY_CUSTOM_PREFIX_BZ2_bzCompressInit
000000000002f2e0 g DF .text 000000000000001a Base MY_CUSTOM_PREFIX_BZ2_bzCompressInit
```

### `![no_std]`
folkertdev marked this conversation as resolved.
Show resolved Hide resolved

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:

- 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.

## Build for Distribution

A `cargo build` currently does not set some fields that are required or useful when using a dynamic library from C.
For instance, the soname and version are not set by a standard `cargo build`.

To build a proper, installable dynamic library, we recommend [`cargo-c`](https://github.com/lu-zero/cargo-c):

```
cargo install cargo-c
```

This tool deals with setting fields (soname, version) that a normal `cargo build` does not set (today).
It's configuration is in the `Cargo.toml`, where e.g. the library name or version can be changed.

```
> cargo cbuild --release
Compiling libc v0.2.167
Compiling libbzip2-rs-sys v0.0.0 (libbzip2-rs/libbzip2-rs-sys)
Compiling libz-rs-sys-cdylib v0.0.0 (libbzip2-rs/libbzip2-rs-sys-cdylib)
Finished `release` profile [optimized] target(s) in 1.63s
Building pkg-config files
> tree target
target
├── CACHEDIR.TAG
└── x86_64-unknown-linux-gnu
├── CACHEDIR.TAG
└── release
├── examples
├── incremental
├── libbz2_rs.a
├── libbz2_rs.d
├── libbz2_rs.pc
├── libbz2_rs.so
└── libbz2_rs-uninstalled.pc
```
200 changes: 200 additions & 0 deletions libbzip2-rs-sys-cdylib/bzpipe.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "bzlib.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 256

/* Compress from file source to file dest until EOF on source.
def() returns BZ_OK on success, BZ_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
folkertdev marked this conversation as resolved.
Show resolved Hide resolved
version of the library linked do not match, or BZ_IO_ERROR if there is
an error reading or writing the files. */
int def(FILE *source, FILE *dest)
{
int ret;
unsigned have;
bz_stream strm;
unsigned char in[CHUNK];
unsigned char out[CHUNK];

/* allocate deflate state */
strm.bzalloc = NULL;
strm.bzfree = NULL;
strm.opaque = NULL;
ret = BZ2_bzCompressInit(&strm, 9, 0, 0);
if (ret != BZ_OK)
return ret;

/* compress until end of file */
int done = 0;
while (!done) {
strm.avail_in = fread(in, 1, CHUNK, source);
done = feof(source);

if (ferror(source)) {
(void)BZ2_bzCompressEnd(&strm);
return BZ_IO_ERROR;
}
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 = BZ2_bzCompress(&strm, BZ_FLUSH); /* no bad return value */
assert(ret != BZ_PARAM_ERROR); /* state not clobbered */
have = CHUNK - strm.avail_out;
if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
(void)BZ2_bzCompressEnd(&strm);
return BZ_IO_ERROR;
}
} while (strm.avail_out == 0);
assert(strm.avail_in == 0); /* all input will be used */

/* done when last data in file processed */
}

strm.avail_out = CHUNK;
strm.next_out = out;
ret = BZ2_bzCompress(&strm, BZ_FINISH); /* no bad return value */
have = CHUNK - strm.avail_out;
if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
(void)BZ2_bzCompressEnd(&strm);
return BZ_IO_ERROR;
}

assert(ret == BZ_STREAM_END); /* stream will be complete */

/* clean up and return */
(void)BZ2_bzCompressEnd(&strm);
return BZ_OK;
}

/* Decompress from file source to file dest until stream ends or EOF.
inf() returns BZ_OK on success, BZ_MEM_ERROR if memory could not be
allocated for processing, BZ_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 BZ_IO_ERROR if there
is an error reading or writing the files. */
int inf(FILE *source, FILE *dest)
{
int ret;
unsigned have;
bz_stream strm;
unsigned char in[CHUNK];
unsigned char out[CHUNK];

/* allocate inflate state */
strm.bzalloc = NULL;
strm.bzfree = NULL;
strm.opaque = NULL;
strm.avail_in = 0;
strm.next_in = NULL;
ret = BZ2_bzDecompressInit(&strm, 0, 0);
if (ret != BZ_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)BZ2_bzDecompressEnd(&strm);
return BZ_IO_ERROR;
}
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 = BZ2_bzDecompress(&strm);
assert(ret != BZ_PARAM_ERROR); /* state not clobbered */
switch (ret) {
case BZ_DATA_ERROR:
case BZ_MEM_ERROR:
(void)BZ2_bzDecompressEnd(&strm);
return ret;
}
have = CHUNK - strm.avail_out;
if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
(void)BZ2_bzDecompressEnd(&strm);
return BZ_IO_ERROR;
}
} while (strm.avail_out == 0);

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

/* clean up and return */
(void)BZ2_bzDecompressEnd(&strm);
return ret == BZ_STREAM_END ? BZ_OK : BZ_DATA_ERROR;
}

/* report a zlib or i/o error */
folkertdev marked this conversation as resolved.
Show resolved Hide resolved
void zerr(int ret)
{
fputs("zpipe: ", stderr);
folkertdev marked this conversation as resolved.
Show resolved Hide resolved
switch (ret) {
case BZ_IO_ERROR:
if (ferror(stdin))
fputs("error reading stdin\n", stderr);
if (ferror(stdout))
fputs("error writing stdout\n", stderr);
break;
case BZ_PARAM_ERROR:
fputs("invalid block size\n", stderr);
break;
case BZ_DATA_ERROR:
case BZ_DATA_ERROR_MAGIC:
fputs("invalid or incomplete data\n", stderr);
break;
case BZ_MEM_ERROR:
fputs("out of memory\n", stderr);
break;
}
}

/* 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);
if (ret != BZ_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 != BZ_OK)
zerr(ret);
return ret;
}

/* otherwise, report usage */
else {
fputs("zpipe usage: zpipe [-d] < source > dest\n", stderr);
folkertdev marked this conversation as resolved.
Show resolved Hide resolved
return 1;
}
}
3 changes: 3 additions & 0 deletions libbzip2-rs-sys-cdylib/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
extern crate libbzip2_rs_sys;

pub use libbzip2_rs_sys::*;