Skip to content

Repository files navigation

bsd-rs

A Rust port of libbsd 0.12.2, published as two crates:

  • bsd — safe, idiomatic Rust implementations of the BSD libc extensions.
  • bsd-abi — a C ABI-compatible replacement for libbsd.so.0.

bsd

[dependencies]
bsd = { version = "0.1", default-features = false, features = ["vis"] }

Fifteen modules: cstr, fmt, line, mem, mode, net, num, proc, size, sort, symbols, time, tty, users and vis. Each is a Cargo feature and all are enabled by default. libc is the only dependency, and is optional.

use bsd::{mode::{Changes, Mode}, size::Format, vis::{Encoder, Flags}};

Mode(0o100_644).to_string();                     // "-rw-r--r-- "  (strmode)
Changes::parse("u+x,go-w")?.apply(0o644);        // 0o744         (setmode/getmode)
Format::new(5).suffix("B").render(1_500_000)?;   // "1 MB"        (humanize_number)
Encoder::new(Flags::WHITE).encode(b"a b");       // br"a\040b"    (strvis)

Items take Rust types and Rust names rather than being transliterated, and each one's documentation names the C function it replaces, so searching the docs for strlcpy or humanize_number finds its counterpart.

Functionality the standard library already covers is not reimplemented:

libbsd Use instead
the twelve endian conversions u16::from_be_bytes and its siblings
heapsort, mergesort slice::sort_unstable, slice::sort
the err family eprintln!, io::Error::last_os_error, process::exit
funopen Read / Write / Seek
queue.h, tree.h, bitstring.h VecDeque, BTreeMap, bitvec

docs/spec/api.md lists every omission with its replacement, and the reasoning behind each.

bsd-abi

bsd-abi produces a shared object that a program already linked against libbsd resolves to without relinking: the same 110 symbols under the same LIBBSD_* version nodes, with soname libbsd.so.0. Alongside it, the packaging step installs the same 28 headers, libbsd.a, libbsd-ctor.a and the three pkg-config files.

crates/package.sh <dir>    # writes lib/, include/ and lib/pkgconfig/ under <dir>

As in the C, the exported set is chosen by configuration rather than probed. Five profiles carry libbsd's per-platform selector tables — profile-glibc (the default), profile-musl, profile-darwin, profile-solaris and profile-aix — and individual abi-* features select symbols within a profile. Cargo features are additive, so switching profile requires --no-default-features.

The shared object is 469 KB stripped, against the C's 83 KB. The difference is the Rust standard library rather than this port's code; docs/spec/abi.md measures the same effect for static linking under [spec:libbsd:req:abi.static-archive].

Building and testing

cargo test --workspace          # unit, integration and differential tests
crates/package.sh <dir>         # build the installable tree
crates/run-c-tests.sh           # upstream's C suite against the packaged library
crates/feature-matrix.sh        # build each bsd feature on its own
crates/profile-matrix.sh        # build each ABI profile
crates/header-match.sh          # compare installed prototypes to the Rust definitions

Requirements:

  • A nightly toolchain, pinned in rust-toolchain.toml. bsd-abi needs c_variadic for its seventeen variadic entry points; bsd alone builds on stable.
  • libbsd-dev, or the equivalent for your distribution, for the differential tests, which compare against the reference library at run time.
  • cbindgen, for header-match.sh.

header-match.sh covers a gap the other checks leave open: no stage of a normal build compares a Rust extern "C" signature against the C prototype that declares it, and upstream's suite catches a mismatch only for the 33 symbols it calls. It takes the header's view from gcc -aux-info and the Rust view from cbindgen and diffs them, covering 94 of the 107 distinct exported names; the remaining 13 are exempt with a reason recorded for each.

Repository layout

Path Contents
crates/bsd the safe Rust API
crates/bsd-abi the C entry points, version script and packaging inputs
crates/bsd-abi-ctor the setproctitle constructor, shipped as libbsd-ctor.a
crates/difftest differential tests against the installed reference library
docs/spec the port specification
include/bsd upstream's headers, installed unmodified
test upstream's C test suite, run unmodified

The C implementation is not vendored. include/bsd/ and test/ are upstream libbsd, used as they stand: the headers are what bsd-abi installs, and the suite is compiled against the packaged library by crates/run-c-tests.sh.

Specification

docs/spec/port/ carries two rules for each C function — a def rule fixing its shape and a sem rule stating its runtime behaviour — in enough detail to reimplement it without consulting the C. docs/spec/abi.md governs the shared object, docs/spec/api.md governs the safe API, and docs/spec/scope.md records what is deliberately not translated. All 341 rules are covered by the implementation.

Where the C has a defect, the rule says so and states what the port does with it. Both crates fix them, bsd-abi included: a drop-in that reproduces buffer overruns is not worth having. Each divergence is recorded in the rule for the function it belongs to, and where the reference's defect is observable at run time its test pins that too — so a repair upstream fails the test rather than passing unnoticed.

What is corrected:

Defect Port
strnvis and the bounded encoders write the terminator at index dlen, one past the buffer the caller sized dlen bounds the whole destination, as it does in OpenBSD
unvis folds a numeric entity at eleven times the running value, so only single digits decode folds at ten: &#12; is 12, not 13
unvis never leaves the number state at the ;, swallowing what follows returns to the ground state: &#7;89 is \x07 then 89
stravis leaves the caller's pointer aimed at freed memory when it fails stores null
humanize_number negates i64::MIN into itself and prints nonsense carries the magnitude unsigned
inet_net_pton writes past the destination on the hexadecimal path, then wraps its unsigned counter and stops bounding the rest of the parse stops where the destination ends and reports EMSGSIZE
nlist indexes the string table with an unvalidated st_name, so a malformed object faults the calling process the offset is checked against the table, and an out-of-range symbol is skipped
pwcache returns a pointer into its own dead stack frame when both allocations fail unreachable: the path does not exist here
arc4random's fork check compares the stored pid against 1, so a process running as pid 1 — every container init — fully reseeds on every call compares against the pid the state was seeded in
arc4random leaves the caller's out-parameter holding MAP_FAILED when its first mapping fails null, which is what the caller tests for
a typo (abi_reallocf=ues) drops reallocf from a musl build exported, as every other platform table does

Two upstream tests assert the first of these — strnvis(str, 10, ...) writing eleven bytes — so they fail against this port by design. crates/run-c-tests.sh reports them in a diverged category and fails the run if either starts passing.

Status

  • 110 versioned exports over 107 distinct names, matching the reference.
  • 94 of those 107 prototypes verified against the installed headers.
  • Upstream's C test suite: 24 pass, 2 skipped on the C build's own terms, and 2 diverging by specification — see above.
  • 492 Rust tests.

Licence

BSD-3-Clause AND ISC AND MIT, matching the C it replaces; per-file provenance is recorded in the spec rule for each symbol, and COPYING carries the licence texts as upstream ships them.

About

A Rust port of libbsd: the bsd crate (safe API) and bsd-abi (C ABI drop-in for libbsd.so.0)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages