Skip to main content

libbsd_sys/
lib.rs

1//! Raw FFI bindings to libbsd.
2//!
3//! This crate provides `extern "C"` declarations for the functions and types
4//! exported by [libbsd](https://libbsd.freedesktop.org/), a library that
5//! provides commonly-used BSD functions on GNU/Linux systems.
6//!
7//! # Platform support
8//!
9//! On **macOS**, **FreeBSD**, **OpenBSD**, and **NetBSD**,
10//! most of these functions are already part of the system C library, so no
11//! additional library is needed.
12//!
13//! On **Linux**, the crate uses `pkg-config` at build time to locate libbsd.
14//! On Debian/Ubuntu, install the development headers with:
15//!
16//! ```sh
17//! apt install libbsd-dev
18//! ```
19//!
20//! On **Windows** and other unsupported platforms, this crate is empty.
21//!
22//! # Conditional compilation
23//!
24//! Functions that only exist in libbsd (not on any BSD natively) are gated
25//! behind `#[cfg(target_os = "linux")]`. Functions available on the BSDs but
26//! not macOS are gated behind `#[cfg(not(target_os = "macos"))]`.
27//!
28//! The `strnvis` and `strnunvis` functions have different parameter orders
29//! depending on whether the platform follows the NetBSD convention (macOS,
30//! NetBSD, OpenBSD) or the FreeBSD convention (FreeBSD, Linux/libbsd).
31//!
32//! # Environment variables
33//!
34//! If `pkg-config` can't find libbsd, the build script emits a
35//! `cargo:warning` and skips the link step rather than aborting. This
36//! lets `cargo clippy` and `cargo check` in downstream crates succeed
37//! without `libbsd-dev` installed; only a real binary build that
38//! references libbsd symbols will fail at link time.
39//!
40//! The build script also recognizes the following environment variables:
41//!
42//! - **`LIBBSD_NO_PKG_CONFIG`** — Set to any value to skip `pkg-config`
43//!   probing, matching the [pkg-config-rs convention](https://docs.rs/pkg-config).
44//!   On its own, the build script emits no `rustc-link-lib` directive at
45//!   all. To produce a working binary in this mode, either set
46//!   `LIBBSD_LIB_DIR` or arrange linkage yourself (e.g. via
47//!   `RUSTFLAGS="-l bsd"`).
48//!
49//! - **`LIBBSD_LIB_DIR`** — Path to the directory containing the libbsd
50//!   library. Skips `pkg-config` and emits a search path plus
51//!   `rustc-link-lib` directive pointing at this directory.
52//!
53//! - **`LIBBSD_INCLUDE_DIR`** — Path(s) to libbsd headers (colon-separated
54//!   on Unix). Only used in the manual override path; the include paths are
55//!   exported as `DEP_BSD_INCLUDE` for dependent build scripts.
56//!
57//! - **`LIBBSD_STATIC`** — Set to `1`/`true`/`yes` to force static linking,
58//!   or `0`/`false`/`no` to force dynamic linking. Overrides the `static`
59//!   crate feature when set.
60//!
61//! - **`DOCS_RS`** — When set (as it is automatically on docs.rs), the build
62//!   script skips all linking. This allows documentation builds to succeed
63//!   without libbsd installed.
64//!
65//! # Metadata for dependent crates
66//!
67//! This crate sets `links = "bsd"` in `Cargo.toml`, so dependent crates'
68//! build scripts can read the following metadata via `DEP_BSD_*` environment
69//! variables:
70//!
71//! - **`DEP_BSD_INCLUDE`** — Include paths for libbsd headers (one path per
72//!   value; there may be multiple `include=` lines).
73//!
74//! - **`DEP_BSD_LIBDIR`** — Library directory (one path per value).
75
76#![no_std]
77#![allow(non_camel_case_types)]
78
79#[cfg(not(target_os = "windows"))]
80mod imp;
81#[cfg(not(target_os = "windows"))]
82pub use imp::*;
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn smoke_arc4random() {
90        unsafe {
91            let _ = arc4random();
92        }
93    }
94
95    #[test]
96    fn smoke_strlcpy() {
97        let src = b"hello\0";
98        let mut dst = [0u8; 16];
99        unsafe {
100            let n = strlcpy(
101                dst.as_mut_ptr().cast(),
102                src.as_ptr().cast(),
103                dst.len() as size_t,
104            );
105            assert_eq!(n, 5);
106            assert_eq!(&dst[..6], b"hello\0");
107        }
108    }
109
110    #[test]
111    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
112    fn smoke_humanize_number() {
113        let mut buf = [0u8; 16];
114        unsafe {
115            let ret = humanize_number(
116                buf.as_mut_ptr().cast(),
117                buf.len() as size_t,
118                1024 * 1024,
119                b"\0".as_ptr().cast(),
120                HN_AUTOSCALE,
121                HN_DECIMAL | HN_NOSPACE | HN_B,
122            );
123            assert!(ret >= 0);
124        }
125    }
126
127    #[test]
128    fn smoke_arc4random_uniform() {
129        unsafe {
130            let val = arc4random_uniform(100);
131            assert!(val < 100);
132        }
133    }
134
135    #[test]
136    fn smoke_strtonum() {
137        let s = b"42\0";
138        let mut errstr: *const core::ffi::c_char = core::ptr::null();
139        unsafe {
140            let val = strtonum(s.as_ptr().cast(), 0, 100, &mut errstr);
141            assert_eq!(val, 42);
142            assert!(errstr.is_null());
143        }
144    }
145
146    #[test]
147    fn smoke_getprogname() {
148        unsafe {
149            let name = getprogname();
150            assert!(!name.is_null());
151        }
152    }
153
154    #[test]
155    fn smoke_vis_str() {
156        let src = b"hello\tworld\0";
157        let mut dst = [0u8; 64];
158        unsafe {
159            let ret = strvis(dst.as_mut_ptr().cast(), src.as_ptr().cast(), VIS_TAB);
160            assert!(ret > 0);
161        }
162    }
163
164    // -------------------------------------------------------------------
165    // Link smoke tests: verify every extern symbol resolves at link time.
166    // Each test coerces a function item to its fn-pointer type, forcing
167    // the linker to resolve the symbol.  Variadic and divergent functions
168    // use alternative strategies noted inline.
169    // -------------------------------------------------------------------
170
171    use core::ffi::{c_char, c_int, c_long, c_uchar, c_uint, c_void};
172
173    // <bsd/string.h>
174    #[test]
175    fn link_string() {
176        let _: unsafe extern "C" fn(*mut c_char, *const c_char, size_t) -> size_t = strlcpy;
177        let _: unsafe extern "C" fn(*mut c_char, *const c_char, size_t) -> size_t = strlcat;
178        let _: unsafe extern "C" fn(*const c_char, *const c_char, size_t) -> *mut c_char = strnstr;
179        let _: unsafe extern "C" fn(mode_t, *mut c_char) = strmode;
180        let _: unsafe extern "C" fn(*mut c_void, size_t) = explicit_bzero;
181    }
182
183    // <bsd/stdlib.h>
184    #[test]
185    fn link_stdlib() {
186        let _: unsafe extern "C" fn() -> u32 = arc4random;
187        let _: unsafe extern "C" fn(*mut c_void, size_t) = arc4random_buf;
188        let _: unsafe extern "C" fn(u32) -> u32 = arc4random_uniform;
189        let _: unsafe extern "C" fn() -> *const c_char = getprogname;
190        let _: unsafe extern "C" fn(*const c_char) = setprogname;
191        let _: unsafe extern "C" fn(
192            *mut c_void,
193            size_t,
194            size_t,
195            Option<unsafe extern "C" fn(*const c_void, *const c_void) -> c_int>,
196        ) -> c_int = heapsort;
197        let _: unsafe extern "C" fn(
198            *mut c_void,
199            size_t,
200            size_t,
201            Option<unsafe extern "C" fn(*const c_void, *const c_void) -> c_int>,
202        ) -> c_int = mergesort;
203        let _: unsafe extern "C" fn(*mut *const c_uchar, c_int, *const c_uchar, c_uint) -> c_int =
204            radixsort;
205        let _: unsafe extern "C" fn(*mut *const c_uchar, c_int, *const c_uchar, c_uint) -> c_int =
206            sradixsort;
207        let _: unsafe extern "C" fn(*mut c_void, size_t) -> *mut c_void = reallocf;
208        let _: unsafe extern "C" fn(*mut c_void, size_t, size_t) -> *mut c_void = reallocarray;
209        let _: unsafe extern "C" fn(*const c_char, i64, i64, *mut *const c_char) -> i64 = strtonum;
210        let _: unsafe extern "C" fn(*mut c_int, *mut c_long) -> *mut c_char = getbsize;
211    }
212
213    #[test]
214    #[cfg(not(target_os = "macos"))]
215    fn link_stdlib_not_macos() {
216        let _: unsafe extern "C" fn(*mut c_void, size_t, size_t, size_t) -> *mut c_void =
217            recallocarray;
218        let _: unsafe extern "C" fn(*mut c_void, size_t) = freezero;
219    }
220
221    #[test]
222    #[cfg(target_os = "linux")]
223    fn link_stdlib_linux() {
224        let _: unsafe extern "C" fn() = arc4random_stir;
225        let _: unsafe extern "C" fn(*mut c_uchar, c_int) = arc4random_addrandom;
226        let _: unsafe extern "C" fn(*const c_char, *mut i64) -> c_int = dehumanize_number;
227    }
228
229    // <bsd/unistd.h>
230    #[test]
231    fn link_unistd() {
232        let _ = &raw const optreset;
233        let _: unsafe extern "C" fn(*const c_void, mode_t) -> mode_t = getmode;
234        let _: unsafe extern "C" fn(*const c_char) -> *mut c_void = setmode;
235        let _: unsafe extern "C" fn(c_int) = closefrom;
236        let _: unsafe extern "C" fn(c_int, *mut uid_t, *mut gid_t) -> c_int = getpeereid;
237    }
238
239    #[test]
240    #[cfg(target_os = "linux")]
241    fn link_unistd_linux() {
242        let _: unsafe extern "C" fn(c_int, *const *mut c_char, *const c_char) -> c_int = bsd_getopt;
243        let _: unsafe extern "C" fn(c_int, *mut *mut c_char, *mut *mut c_char) = setproctitle_init;
244    }
245
246    #[test]
247    #[cfg(not(target_os = "macos"))]
248    fn link_setproctitle() {
249        // Variadic: verify linkage by calling with an empty format string.
250        unsafe { setproctitle(b"\0".as_ptr().cast()) }
251    }
252
253    // <bsd/stdio.h>
254    #[test]
255    fn link_stdio() {
256        let _: unsafe extern "C" fn(*const c_char, *const c_char) -> *const c_char = fmtcheck;
257        let _: unsafe extern "C" fn(*mut FILE, *mut size_t) -> *mut c_char = fgetln;
258        let _: unsafe extern "C" fn(
259            *const c_void,
260            Option<unsafe extern "C" fn(*mut c_void, *mut c_char, c_int) -> c_int>,
261            Option<unsafe extern "C" fn(*mut c_void, *const c_char, c_int) -> c_int>,
262            Option<unsafe extern "C" fn(*mut c_void, off_t, c_int) -> off_t>,
263            Option<unsafe extern "C" fn(*mut c_void) -> c_int>,
264        ) -> *mut FILE = funopen;
265        let _: unsafe extern "C" fn(*mut FILE) -> c_int = fpurge;
266    }
267
268    // <bsd/readpassphrase.h>
269    #[test]
270    fn link_readpassphrase() {
271        let _: unsafe extern "C" fn(*const c_char, *mut c_char, size_t, c_int) -> *mut c_char =
272            readpassphrase;
273    }
274
275    // <bsd/vis.h>
276    #[test]
277    fn link_vis() {
278        let _: unsafe extern "C" fn(*mut c_char, c_int, c_int, c_int) -> *mut c_char = vis;
279        let _: unsafe extern "C" fn(*mut c_char, size_t, c_int, c_int, c_int) -> *mut c_char = nvis;
280        let _: unsafe extern "C" fn(
281            *mut c_char,
282            c_int,
283            c_int,
284            c_int,
285            *const c_char,
286        ) -> *mut c_char = svis;
287        let _: unsafe extern "C" fn(
288            *mut c_char,
289            size_t,
290            c_int,
291            c_int,
292            c_int,
293            *const c_char,
294        ) -> *mut c_char = snvis;
295        let _: unsafe extern "C" fn(*mut c_char, *const c_char, c_int) -> c_int = strvis;
296        let _: unsafe extern "C" fn(*mut *mut c_char, *const c_char, c_int) -> c_int = stravis;
297        let _: unsafe extern "C" fn(*mut c_char, *const c_char, c_int, *const c_char) -> c_int =
298            strsvis;
299        let _: unsafe extern "C" fn(
300            *mut c_char,
301            size_t,
302            *const c_char,
303            c_int,
304            *const c_char,
305        ) -> c_int = strsnvis;
306        let _: unsafe extern "C" fn(*mut c_char, *const c_char, size_t, c_int) -> c_int = strvisx;
307        let _: unsafe extern "C" fn(*mut c_char, size_t, *const c_char, size_t, c_int) -> c_int =
308            strnvisx;
309        let _: unsafe extern "C" fn(
310            *mut c_char,
311            size_t,
312            *const c_char,
313            size_t,
314            c_int,
315            *mut c_int,
316        ) -> c_int = strenvisx;
317        let _: unsafe extern "C" fn(
318            *mut c_char,
319            *const c_char,
320            size_t,
321            c_int,
322            *const c_char,
323        ) -> c_int = strsvisx;
324        let _: unsafe extern "C" fn(
325            *mut c_char,
326            size_t,
327            *const c_char,
328            size_t,
329            c_int,
330            *const c_char,
331        ) -> c_int = strsnvisx;
332        let _: unsafe extern "C" fn(
333            *mut c_char,
334            size_t,
335            *const c_char,
336            size_t,
337            c_int,
338            *const c_char,
339            *mut c_int,
340        ) -> c_int = strsenvisx;
341        let _: unsafe extern "C" fn(*mut c_char, *const c_char) -> c_int = strunvis;
342        let _: unsafe extern "C" fn(*mut c_char, *const c_char, c_int) -> c_int = strunvisx;
343        let _: unsafe extern "C" fn(*mut c_char, size_t, *const c_char, c_int) -> c_int =
344            strnunvisx;
345        let _: unsafe extern "C" fn(*mut c_char, c_int, *mut c_int, c_int) -> c_int = unvis;
346    }
347
348    #[test]
349    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
350    fn link_vis_strnvis_freebsd() {
351        let _: unsafe extern "C" fn(*mut c_char, *const c_char, size_t, c_int) -> c_int = strnvis;
352        let _: unsafe extern "C" fn(*mut c_char, *const c_char, size_t) -> ssize_t = strnunvis;
353    }
354
355    #[test]
356    #[cfg(any(target_os = "macos", target_os = "netbsd", target_os = "openbsd"))]
357    fn link_vis_strnvis_netbsd() {
358        let _: unsafe extern "C" fn(*mut c_char, size_t, *const c_char, c_int) -> c_int = strnvis;
359        let _: unsafe extern "C" fn(*mut c_char, size_t, *const c_char) -> c_int = strnunvis;
360    }
361
362    // <bsd/libutil.h>
363    #[test]
364    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
365    fn link_libutil() {
366        let _: unsafe extern "C" fn(
367            *mut c_char,
368            size_t,
369            i64,
370            *const c_char,
371            c_int,
372            c_int,
373        ) -> c_int = humanize_number;
374        let _: unsafe extern "C" fn(*const c_char, *mut u64) -> c_int = expand_number;
375        let _: unsafe extern "C" fn(*const c_char, mode_t, *mut pid_t) -> *mut pidfh = pidfile_open;
376        let _: unsafe extern "C" fn(*const pidfh) -> c_int = pidfile_fileno;
377        let _: unsafe extern "C" fn(*mut pidfh) -> c_int = pidfile_write;
378        let _: unsafe extern "C" fn(*mut pidfh) -> c_int = pidfile_close;
379        let _: unsafe extern "C" fn(*mut pidfh) -> c_int = pidfile_remove;
380    }
381
382    // flopen/flopenat are FreeBSD-specific; not available on NetBSD.
383    #[test]
384    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
385    fn link_flopen() {
386        // Variadic; verify linkage by calling.
387        unsafe {
388            let fd = flopen(b"/dev/null\0".as_ptr().cast(), 0);
389            if fd >= 0 {
390                libc::close(fd);
391            }
392            // Invalid dirfd — fails immediately, just verifies linkage.
393            let _ = flopenat(-1, b"\0".as_ptr().cast(), 0);
394        }
395    }
396
397    #[test]
398    fn link_fparseln() {
399        let _: unsafe extern "C" fn(
400            *mut FILE,
401            *mut size_t,
402            *mut size_t,
403            *const [c_char; 3],
404            c_int,
405        ) -> *mut c_char = fparseln;
406    }
407
408    // <bsd/nlist.h>
409    #[test]
410    #[cfg(not(target_os = "macos"))]
411    fn link_nlist() {
412        let _: unsafe extern "C" fn(*const c_char, *mut nlist) -> c_int = nlist;
413    }
414
415    // <bsd/stringlist.h>
416    #[test]
417    fn link_stringlist() {
418        let _: unsafe extern "C" fn() -> *mut StringList = sl_init;
419        let _: unsafe extern "C" fn(*mut StringList, *mut c_char) -> c_int = sl_add;
420        let _: unsafe extern "C" fn(*mut StringList, c_int) = sl_free;
421        let _: unsafe extern "C" fn(*mut StringList, *const c_char) -> *mut c_char = sl_find;
422    }
423
424    #[test]
425    #[cfg(target_os = "linux")]
426    fn link_stringlist_linux() {
427        let _: unsafe extern "C" fn(*mut StringList, *const c_char, c_int) -> c_int = sl_delete;
428    }
429
430    // <bsd/timeconv.h>
431    #[test]
432    #[cfg(target_os = "linux")]
433    fn link_timeconv() {
434        let _: unsafe extern "C" fn(i32) -> libc::time_t = time32_to_time;
435        let _: unsafe extern "C" fn(libc::time_t) -> i32 = time_to_time32;
436        let _: unsafe extern "C" fn(i64) -> libc::time_t = time64_to_time;
437        let _: unsafe extern "C" fn(libc::time_t) -> i64 = time_to_time64;
438        let _: unsafe extern "C" fn(libc::time_t) -> c_long = time_to_long;
439        let _: unsafe extern "C" fn(c_long) -> libc::time_t = long_to_time;
440        let _: unsafe extern "C" fn(libc::time_t) -> c_int = time_to_int;
441        let _: unsafe extern "C" fn(c_int) -> libc::time_t = int_to_time;
442    }
443
444    // <bsd/err.h>
445    #[test]
446    fn link_err() {
447        // warnc is variadic; verify linkage by calling with code 0.
448        unsafe { warnc(0, core::ptr::null()) }
449        // errc is variadic and divergent; verify linkage without calling.
450        if core::hint::black_box(false) {
451            unsafe { errc(1, 0, core::ptr::null()) }
452        }
453    }
454
455    // <bsd/wchar.h>
456    #[test]
457    fn link_wchar() {
458        let _: unsafe extern "C" fn(*mut FILE, *mut size_t) -> *mut libc::wchar_t = fgetwln;
459        let _: unsafe extern "C" fn(*mut libc::wchar_t, *const libc::wchar_t, size_t) -> size_t =
460            wcslcat;
461        let _: unsafe extern "C" fn(*mut libc::wchar_t, *const libc::wchar_t, size_t) -> size_t =
462            wcslcpy;
463    }
464
465    // <bsd/grp.h>
466    #[test]
467    fn link_grp() {
468        let _: unsafe extern "C" fn(*const c_char, *mut gid_t) -> c_int = gid_from_group;
469        let _: unsafe extern "C" fn(gid_t, c_int) -> *const c_char = group_from_gid;
470    }
471
472    // <bsd/pwd.h>
473    #[test]
474    fn link_pwd() {
475        let _: unsafe extern "C" fn(*const c_char, *mut uid_t) -> c_int = uid_from_user;
476        let _: unsafe extern "C" fn(uid_t, c_int) -> *const c_char = user_from_uid;
477    }
478
479    // misc
480    #[test]
481    fn link_inet() {
482        let _: unsafe extern "C" fn(c_int, *const c_char, *mut c_void, size_t) -> c_int =
483            inet_net_pton;
484    }
485}