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