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//! # Conditional compilation
21//!
22//! Functions that only exist in libbsd (not on any BSD natively) are gated
23//! behind `#[cfg(target_os = "linux")]`. Functions available on the BSDs but
24//! not macOS are gated behind `#[cfg(not(target_os = "macos"))]`.
25//!
26//! The `strnvis` and `strnunvis` functions have different parameter orders
27//! depending on whether the platform follows the NetBSD convention (macOS,
28//! NetBSD, OpenBSD) or the FreeBSD convention (FreeBSD, Linux/libbsd).
29
30#![no_std]
31#![allow(non_camel_case_types)]
32
33#[cfg(target_os = "windows")]
34compile_error!("libbsd-sys does not support Windows");
35
36use core::ffi::{c_char, c_int, c_long, c_uchar, c_uint, c_void};
37
38// Re-export libc types used in signatures.
39pub use libc::{FILE, gid_t, mode_t, off_t, pid_t, size_t, ssize_t, uid_t};
40
41// ---------------------------------------------------------------------------
42// <bsd/string.h>
43// ---------------------------------------------------------------------------
44
45unsafe extern "C" {
46    pub fn strlcpy(dst: *mut c_char, src: *const c_char, siz: size_t) -> size_t;
47    pub fn strlcat(dst: *mut c_char, src: *const c_char, siz: size_t) -> size_t;
48    pub fn strnstr(s: *const c_char, find: *const c_char, slen: size_t) -> *mut c_char;
49    pub fn strmode(mode: mode_t, str_: *mut c_char);
50    pub fn explicit_bzero(buf: *mut c_void, len: size_t);
51}
52
53// ---------------------------------------------------------------------------
54// <bsd/stdlib.h>
55// ---------------------------------------------------------------------------
56
57unsafe extern "C" {
58    pub fn arc4random() -> u32;
59    pub fn arc4random_buf(buf: *mut c_void, n: size_t);
60    pub fn arc4random_uniform(upper_bound: u32) -> u32;
61    #[cfg(target_os = "linux")]
62    pub fn arc4random_stir();
63    #[cfg(target_os = "linux")]
64    pub fn arc4random_addrandom(dat: *mut c_uchar, datlen: c_int);
65
66    #[cfg(target_os = "linux")]
67    pub fn dehumanize_number(str_: *const c_char, size: *mut i64) -> c_int;
68
69    pub fn getprogname() -> *const c_char;
70    pub fn setprogname(name: *const c_char);
71
72    pub fn heapsort(
73        base: *mut c_void,
74        nmemb: size_t,
75        size: size_t,
76        cmp: Option<unsafe extern "C" fn(*const c_void, *const c_void) -> c_int>,
77    ) -> c_int;
78    pub fn mergesort(
79        base: *mut c_void,
80        nmemb: size_t,
81        size: size_t,
82        cmp: Option<unsafe extern "C" fn(*const c_void, *const c_void) -> c_int>,
83    ) -> c_int;
84    pub fn radixsort(
85        base: *mut *const c_uchar,
86        nmemb: c_int,
87        table: *const c_uchar,
88        endbyte: c_uint,
89    ) -> c_int;
90    pub fn sradixsort(
91        base: *mut *const c_uchar,
92        nmemb: c_int,
93        table: *const c_uchar,
94        endbyte: c_uint,
95    ) -> c_int;
96
97    pub fn reallocf(ptr: *mut c_void, size: size_t) -> *mut c_void;
98    pub fn reallocarray(ptr: *mut c_void, nmemb: size_t, size: size_t) -> *mut c_void;
99    #[cfg(not(target_os = "macos"))]
100    pub fn recallocarray(
101        ptr: *mut c_void,
102        oldnmemb: size_t,
103        nmemb: size_t,
104        size: size_t,
105    ) -> *mut c_void;
106    #[cfg(not(target_os = "macos"))]
107    pub fn freezero(ptr: *mut c_void, size: size_t);
108
109    pub fn strtonum(
110        nptr: *const c_char,
111        minval: i64,
112        maxval: i64,
113        errstr: *mut *const c_char,
114    ) -> i64;
115
116    pub fn getbsize(headerlenp: *mut c_int, blocksizep: *mut c_long) -> *mut c_char;
117}
118
119// ---------------------------------------------------------------------------
120// <bsd/unistd.h>
121// ---------------------------------------------------------------------------
122
123unsafe extern "C" {
124    pub static mut optreset: c_int;
125
126    #[cfg(target_os = "linux")]
127    pub fn bsd_getopt(argc: c_int, argv: *const *mut c_char, shortopts: *const c_char) -> c_int;
128
129    pub fn getmode(set: *const c_void, mode: mode_t) -> mode_t;
130    pub fn setmode(mode_str: *const c_char) -> *mut c_void;
131
132    pub fn closefrom(lowfd: c_int);
133
134    #[cfg(target_os = "linux")]
135    pub fn setproctitle_init(argc: c_int, argv: *mut *mut c_char, envp: *mut *mut c_char);
136    #[cfg(not(target_os = "macos"))]
137    pub fn setproctitle(fmt: *const c_char, ...);
138
139    pub fn getpeereid(s: c_int, euid: *mut uid_t, egid: *mut gid_t) -> c_int;
140}
141
142// ---------------------------------------------------------------------------
143// <bsd/stdio.h>
144// ---------------------------------------------------------------------------
145
146unsafe extern "C" {
147    pub fn fmtcheck(f1: *const c_char, f2: *const c_char) -> *const c_char;
148
149    pub fn fgetln(fp: *mut FILE, lenp: *mut size_t) -> *mut c_char;
150
151    pub fn funopen(
152        cookie: *const c_void,
153        readfn: Option<unsafe extern "C" fn(*mut c_void, *mut c_char, c_int) -> c_int>,
154        writefn: Option<unsafe extern "C" fn(*mut c_void, *const c_char, c_int) -> c_int>,
155        seekfn: Option<unsafe extern "C" fn(*mut c_void, off_t, c_int) -> off_t>,
156        closefn: Option<unsafe extern "C" fn(*mut c_void) -> c_int>,
157    ) -> *mut FILE;
158
159    pub fn fpurge(fp: *mut FILE) -> c_int;
160}
161
162// ---------------------------------------------------------------------------
163// <bsd/readpassphrase.h>
164// ---------------------------------------------------------------------------
165
166pub const RPP_ECHO_OFF: c_int = 0x00;
167pub const RPP_ECHO_ON: c_int = 0x01;
168pub const RPP_REQUIRE_TTY: c_int = 0x02;
169pub const RPP_FORCELOWER: c_int = 0x04;
170pub const RPP_FORCEUPPER: c_int = 0x08;
171pub const RPP_SEVENBIT: c_int = 0x10;
172pub const RPP_STDIN: c_int = 0x20;
173
174unsafe extern "C" {
175    pub fn readpassphrase(
176        prompt: *const c_char,
177        buf: *mut c_char,
178        bufsiz: size_t,
179        flags: c_int,
180    ) -> *mut c_char;
181}
182
183// ---------------------------------------------------------------------------
184// <bsd/vis.h>
185// ---------------------------------------------------------------------------
186
187pub const VIS_OCTAL: c_int = 0x0001;
188pub const VIS_CSTYLE: c_int = 0x0002;
189pub const VIS_SP: c_int = 0x0004;
190pub const VIS_TAB: c_int = 0x0008;
191pub const VIS_NL: c_int = 0x0010;
192pub const VIS_WHITE: c_int = VIS_SP | VIS_TAB | VIS_NL;
193pub const VIS_SAFE: c_int = 0x0020;
194pub const VIS_DQ: c_int = 0x8000;
195pub const VIS_NOSLASH: c_int = 0x0040;
196pub const VIS_HTTP1808: c_int = 0x0080;
197pub const VIS_HTTPSTYLE: c_int = 0x0080;
198pub const VIS_MIMESTYLE: c_int = 0x0100;
199pub const VIS_HTTP1866: c_int = 0x0200;
200pub const VIS_NOESCAPE: c_int = 0x0400;
201pub const VIS_GLOB: c_int = 0x1000;
202pub const VIS_SHELL: c_int = 0x2000;
203pub const VIS_META: c_int = VIS_WHITE | VIS_GLOB | VIS_SHELL;
204pub const VIS_NOLOCALE: c_int = 0x4000;
205
206pub const UNVIS_VALID: c_int = 1;
207pub const UNVIS_VALIDPUSH: c_int = 2;
208pub const UNVIS_NOCHAR: c_int = 3;
209pub const UNVIS_SYNBAD: c_int = -1;
210pub const UNVIS_ERROR: c_int = -2;
211pub const UNVIS_END: c_int = 0x0800;
212
213unsafe extern "C" {
214    pub fn vis(dst: *mut c_char, c: c_int, flag: c_int, nextc: c_int) -> *mut c_char;
215    pub fn nvis(dst: *mut c_char, dlen: size_t, c: c_int, flag: c_int, nextc: c_int)
216    -> *mut c_char;
217
218    pub fn svis(
219        dst: *mut c_char,
220        c: c_int,
221        flag: c_int,
222        nextc: c_int,
223        extra: *const c_char,
224    ) -> *mut c_char;
225    pub fn snvis(
226        dst: *mut c_char,
227        dlen: size_t,
228        c: c_int,
229        flag: c_int,
230        nextc: c_int,
231        extra: *const c_char,
232    ) -> *mut c_char;
233
234    pub fn strvis(dst: *mut c_char, src: *const c_char, flag: c_int) -> c_int;
235    pub fn stravis(dst: *mut *mut c_char, src: *const c_char, flag: c_int) -> c_int;
236    // NB: strnvis has different parameter order depending on the platform's
237    // convention: FreeBSD (and libbsd) put src before dlen, while NetBSD
238    // (and macOS/OpenBSD) put dlen before src.
239    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
240    pub fn strnvis(dst: *mut c_char, src: *const c_char, dlen: size_t, flag: c_int) -> c_int;
241    #[cfg(any(target_os = "macos", target_os = "netbsd", target_os = "openbsd"))]
242    pub fn strnvis(dst: *mut c_char, dlen: size_t, src: *const c_char, flag: c_int) -> c_int;
243
244    pub fn strsvis(
245        dst: *mut c_char,
246        src: *const c_char,
247        flag: c_int,
248        extra: *const c_char,
249    ) -> c_int;
250    pub fn strsnvis(
251        dst: *mut c_char,
252        dlen: size_t,
253        src: *const c_char,
254        flag: c_int,
255        extra: *const c_char,
256    ) -> c_int;
257
258    pub fn strvisx(dst: *mut c_char, src: *const c_char, len: size_t, flag: c_int) -> c_int;
259    pub fn strnvisx(
260        dst: *mut c_char,
261        dlen: size_t,
262        src: *const c_char,
263        len: size_t,
264        flag: c_int,
265    ) -> c_int;
266    pub fn strenvisx(
267        dst: *mut c_char,
268        dlen: size_t,
269        src: *const c_char,
270        len: size_t,
271        flag: c_int,
272        cerr_ptr: *mut c_int,
273    ) -> c_int;
274
275    pub fn strsvisx(
276        dst: *mut c_char,
277        src: *const c_char,
278        len: size_t,
279        flag: c_int,
280        extra: *const c_char,
281    ) -> c_int;
282    pub fn strsnvisx(
283        dst: *mut c_char,
284        dlen: size_t,
285        src: *const c_char,
286        len: size_t,
287        flag: c_int,
288        extra: *const c_char,
289    ) -> c_int;
290    pub fn strsenvisx(
291        dst: *mut c_char,
292        dlen: size_t,
293        src: *const c_char,
294        len: size_t,
295        flag: c_int,
296        extra: *const c_char,
297        cerr_ptr: *mut c_int,
298    ) -> c_int;
299
300    pub fn strunvis(dst: *mut c_char, src: *const c_char) -> c_int;
301    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
302    pub fn strnunvis(dst: *mut c_char, src: *const c_char, dlen: size_t) -> ssize_t;
303    #[cfg(any(target_os = "macos", target_os = "netbsd", target_os = "openbsd"))]
304    pub fn strnunvis(dst: *mut c_char, dlen: size_t, src: *const c_char) -> c_int;
305
306    pub fn strunvisx(dst: *mut c_char, src: *const c_char, flag: c_int) -> c_int;
307    pub fn strnunvisx(dst: *mut c_char, dlen: size_t, src: *const c_char, flag: c_int) -> c_int;
308
309    pub fn unvis(cp: *mut c_char, c: c_int, apts: *mut c_int, flag: c_int) -> c_int;
310}
311
312// ---------------------------------------------------------------------------
313// <bsd/libutil.h>
314// ---------------------------------------------------------------------------
315
316#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
317pub const HN_DECIMAL: c_int = 0x01;
318#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
319pub const HN_NOSPACE: c_int = 0x02;
320#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
321pub const HN_B: c_int = 0x04;
322#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
323pub const HN_DIVISOR_1000: c_int = 0x08;
324#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
325pub const HN_IEC_PREFIXES: c_int = 0x10;
326#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
327pub const HN_GETSCALE: c_int = 0x10;
328#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
329pub const HN_AUTOSCALE: c_int = 0x20;
330
331pub const FPARSELN_UNESCESC: c_int = 0x01;
332pub const FPARSELN_UNESCCONT: c_int = 0x02;
333pub const FPARSELN_UNESCCOMM: c_int = 0x04;
334pub const FPARSELN_UNESCREST: c_int = 0x08;
335pub const FPARSELN_UNESCALL: c_int = 0x0f;
336
337#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
338#[repr(C)]
339pub struct pidfh {
340    _opaque: [u8; 0],
341}
342
343#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
344unsafe extern "C" {
345    pub fn humanize_number(
346        buf: *mut c_char,
347        len: size_t,
348        bytes: i64,
349        suffix: *const c_char,
350        scale: c_int,
351        flags: c_int,
352    ) -> c_int;
353    pub fn expand_number(buf: *const c_char, num: *mut u64) -> c_int;
354
355    pub fn flopen(path: *const c_char, flags: c_int, ...) -> c_int;
356    pub fn flopenat(dirfd: c_int, path: *const c_char, flags: c_int, ...) -> c_int;
357
358    pub fn pidfile_open(path: *const c_char, mode: mode_t, pidptr: *mut pid_t) -> *mut pidfh;
359    pub fn pidfile_fileno(pfh: *const pidfh) -> c_int;
360    pub fn pidfile_write(pfh: *mut pidfh) -> c_int;
361    pub fn pidfile_close(pfh: *mut pidfh) -> c_int;
362    pub fn pidfile_remove(pfh: *mut pidfh) -> c_int;
363}
364
365unsafe extern "C" {
366    pub fn fparseln(
367        fp: *mut FILE,
368        size: *mut size_t,
369        lineno: *mut size_t,
370        delim: *const [c_char; 3],
371        flags: c_int,
372    ) -> *mut c_char;
373}
374
375// ---------------------------------------------------------------------------
376// <bsd/nlist.h>
377// ---------------------------------------------------------------------------
378
379// On macOS, struct nlist has a different layout (Mach-O format).
380#[cfg(not(target_os = "macos"))]
381#[repr(C)]
382pub struct nlist {
383    pub n_name: *mut c_char,
384    pub n_type: u8,
385    pub n_other: c_char,
386    pub n_desc: i16,
387    pub n_value: c_ulong,
388}
389
390pub use core::ffi::c_ulong;
391
392pub const N_UNDF: u8 = 0x00;
393pub const N_ABS: u8 = 0x02;
394pub const N_TEXT: u8 = 0x04;
395pub const N_DATA: u8 = 0x06;
396pub const N_BSS: u8 = 0x08;
397pub const N_INDR: u8 = 0x0a;
398pub const N_SIZE: u8 = 0x0c;
399pub const N_COMM: u8 = 0x12;
400pub const N_SETA: u8 = 0x14;
401pub const N_SETT: u8 = 0x16;
402pub const N_SETD: u8 = 0x18;
403pub const N_SETB: u8 = 0x1a;
404pub const N_SETV: u8 = 0x1c;
405pub const N_FN: u8 = 0x1e;
406pub const N_WARN: u8 = 0x1e;
407pub const N_EXT: u8 = 0x01;
408pub const N_TYPE: u8 = 0x1e;
409pub const N_STAB: u8 = 0xe0;
410
411#[cfg(not(target_os = "macos"))]
412unsafe extern "C" {
413    pub fn nlist(filename: *const c_char, list: *mut nlist) -> c_int;
414}
415
416// ---------------------------------------------------------------------------
417// <bsd/stringlist.h>
418// ---------------------------------------------------------------------------
419
420#[repr(C)]
421pub struct StringList {
422    pub sl_str: *mut *mut c_char,
423    pub sl_max: size_t,
424    pub sl_cur: size_t,
425}
426
427unsafe extern "C" {
428    pub fn sl_init() -> *mut StringList;
429    pub fn sl_add(sl: *mut StringList, item: *mut c_char) -> c_int;
430    pub fn sl_free(sl: *mut StringList, freel: c_int);
431    pub fn sl_find(sl: *mut StringList, name: *const c_char) -> *mut c_char;
432    #[cfg(target_os = "linux")]
433    pub fn sl_delete(sl: *mut StringList, name: *const c_char, freel: c_int) -> c_int;
434}
435
436// ---------------------------------------------------------------------------
437// <bsd/timeconv.h>
438// ---------------------------------------------------------------------------
439
440#[cfg(target_os = "linux")]
441unsafe extern "C" {
442    #[link_name = "_time32_to_time"]
443    pub fn time32_to_time(t32: i32) -> libc::time_t;
444    #[link_name = "_time_to_time32"]
445    pub fn time_to_time32(t: libc::time_t) -> i32;
446    #[link_name = "_time64_to_time"]
447    pub fn time64_to_time(t64: i64) -> libc::time_t;
448    #[link_name = "_time_to_time64"]
449    pub fn time_to_time64(t: libc::time_t) -> i64;
450    #[link_name = "_time_to_long"]
451    pub fn time_to_long(t: libc::time_t) -> c_long;
452    #[link_name = "_long_to_time"]
453    pub fn long_to_time(tlong: c_long) -> libc::time_t;
454    #[link_name = "_time_to_int"]
455    pub fn time_to_int(t: libc::time_t) -> c_int;
456    #[link_name = "_int_to_time"]
457    pub fn int_to_time(tint: c_int) -> libc::time_t;
458}
459
460// ---------------------------------------------------------------------------
461// <bsd/err.h>  (non-variadic subset)
462// ---------------------------------------------------------------------------
463
464unsafe extern "C" {
465    pub fn warnc(code: c_int, format: *const c_char, ...);
466    pub fn errc(status: c_int, code: c_int, format: *const c_char, ...) -> !;
467}
468
469// ---------------------------------------------------------------------------
470// <bsd/wchar.h>
471// ---------------------------------------------------------------------------
472
473unsafe extern "C" {
474    pub fn fgetwln(stream: *mut FILE, len: *mut size_t) -> *mut libc::wchar_t;
475    pub fn wcslcat(dst: *mut libc::wchar_t, src: *const libc::wchar_t, size: size_t) -> size_t;
476    pub fn wcslcpy(dst: *mut libc::wchar_t, src: *const libc::wchar_t, size: size_t) -> size_t;
477}
478
479// ---------------------------------------------------------------------------
480// <bsd/grp.h>
481// ---------------------------------------------------------------------------
482
483unsafe extern "C" {
484    pub fn gid_from_group(name: *const c_char, gid: *mut gid_t) -> c_int;
485    pub fn group_from_gid(gid: gid_t, nosuchgroup: c_int) -> *const c_char;
486}
487
488// ---------------------------------------------------------------------------
489// <bsd/pwd.h>
490// ---------------------------------------------------------------------------
491
492unsafe extern "C" {
493    pub fn uid_from_user(name: *const c_char, uid: *mut uid_t) -> c_int;
494    pub fn user_from_uid(uid: uid_t, nosuchuser: c_int) -> *const c_char;
495}
496
497// ---------------------------------------------------------------------------
498// Misc functions found in libbsd without a dedicated header
499// ---------------------------------------------------------------------------
500
501unsafe extern "C" {
502    pub fn inet_net_pton(af: c_int, src: *const c_char, dst: *mut c_void, size: size_t) -> c_int;
503}
504
505#[cfg(test)]
506mod tests {
507    use super::*;
508
509    #[test]
510    fn smoke_arc4random() {
511        unsafe {
512            let _ = arc4random();
513        }
514    }
515
516    #[test]
517    fn smoke_strlcpy() {
518        let src = b"hello\0";
519        let mut dst = [0u8; 16];
520        unsafe {
521            let n = strlcpy(
522                dst.as_mut_ptr().cast(),
523                src.as_ptr().cast(),
524                dst.len() as size_t,
525            );
526            assert_eq!(n, 5);
527            assert_eq!(&dst[..6], b"hello\0");
528        }
529    }
530
531    #[test]
532    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
533    fn smoke_humanize_number() {
534        let mut buf = [0u8; 16];
535        unsafe {
536            let ret = humanize_number(
537                buf.as_mut_ptr().cast(),
538                buf.len() as size_t,
539                1024 * 1024,
540                b"\0".as_ptr().cast(),
541                HN_AUTOSCALE,
542                HN_DECIMAL | HN_NOSPACE | HN_B,
543            );
544            assert!(ret >= 0);
545        }
546    }
547
548    #[test]
549    fn smoke_arc4random_uniform() {
550        unsafe {
551            let val = arc4random_uniform(100);
552            assert!(val < 100);
553        }
554    }
555
556    #[test]
557    fn smoke_strtonum() {
558        let s = b"42\0";
559        let mut errstr: *const c_char = core::ptr::null();
560        unsafe {
561            let val = strtonum(s.as_ptr().cast(), 0, 100, &mut errstr);
562            assert_eq!(val, 42);
563            assert!(errstr.is_null());
564        }
565    }
566
567    #[test]
568    fn smoke_getprogname() {
569        unsafe {
570            let name = getprogname();
571            assert!(!name.is_null());
572        }
573    }
574
575    #[test]
576    fn smoke_vis_str() {
577        let src = b"hello\tworld\0";
578        let mut dst = [0u8; 64];
579        unsafe {
580            let ret = strvis(dst.as_mut_ptr().cast(), src.as_ptr().cast(), VIS_TAB);
581            assert!(ret > 0);
582        }
583    }
584}