Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
40 changes: 40 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,46 @@ jobs:
cargo +nightly check --tests -vv --target=hexagon-unknown-linux-musl -Zbuild-std -Zbuild-std-features=llvm-libunwind


zerocopy:
name: Zerocopy derives
runs-on: ubuntu-latest
env:
RUSTFLAGS: -D warnings
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: ./.github/actions/install-rust
with:
toolchain: stable
- run: >
rustup target add
x86_64-unknown-linux-musl
x86_64-unknown-linux-gnux32
i686-unknown-linux-gnu
riscv64gc-unknown-linux-gnu
aarch64-unknown-linux-gnu
powerpc64le-unknown-linux-gnu
armv5te-unknown-linux-gnueabi
loongarch64-unknown-linux-gnu
s390x-unknown-linux-gnu
- run: |
# The derives are compile-time verified per target (padding differs
# by architecture), so check a width- and endianness-diverse set.
for target in \
x86_64-unknown-linux-gnu \
x86_64-unknown-linux-musl \
x86_64-unknown-linux-gnux32 \
i686-unknown-linux-gnu \
riscv64gc-unknown-linux-gnu \
aarch64-unknown-linux-gnu \
powerpc64le-unknown-linux-gnu \
armv5te-unknown-linux-gnueabi \
loongarch64-unknown-linux-gnu \
s390x-unknown-linux-gnu; do
cargo check --features zerocopy --target=$target
done

gen:
name: Update generated files
runs-on: ubuntu-latest
Expand Down
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ rust-version = "1.63"

[dependencies]
core = { version = "1.0.0", optional = true, package = "rustc-std-workspace-core" }
# Optional: compile-time-verified byte-conversion derives on the fixed-layout
# wire-format types (see `ZEROCOPY_TYPES` in gen/src/main.rs).
zerocopy = { version = "0.8.0", optional = true, default-features = false, features = ["derive"] }

[dev-dependencies]
static_assertions = "1.1.0"
libc = "0.2.100"

[package.metadata.docs.rs]
features = ["default", "bootparam", "btrfs", "elf_uapi", "image", "ioctl", "landlock", "netlink", "io_uring", "if_arp", "if_ether", "if_packet", "net", "ptrace", "prctl", "elf", "xdp", "mempolicy", "system", "loop_device"]
features = ["default", "bootparam", "btrfs", "elf_uapi", "image", "ioctl", "landlock", "netlink", "io_uring", "if_arp", "if_ether", "if_packet", "net", "ptrace", "prctl", "elf", "xdp", "mempolicy", "system", "loop_device", "zerocopy"]
targets = ["x86_64-unknown-linux-gnu", "i686-unknown-linux-gnu"]

[lints.rust.unexpected_cfgs]
Expand Down Expand Up @@ -58,4 +61,5 @@ default = ["std", "general", "errno"]
std = []
no_std = []
elf = []
zerocopy = ["dep:zerocopy"]
rustc-dep-of-std = ["core", "no_std"]
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ The full bindings are quite large, so they've been split up into modules and
cargo features. By default, `general` and `errno` are enabled, which provide
most things needed by general-purpose code.

The optional `zerocopy` feature adds compile-time-verified [zerocopy] trait
derives (`FromBytes`, `IntoBytes`, `KnownLayout`, `Immutable`) to a curated
set of fixed-layout wire-format types (`stat`, `statx`, `rusage`,
`timespec`, …), so they can be converted to and from byte buffers without
hand-written `unsafe`. See `ZEROCOPY_TYPES` in `gen/src/main.rs` for the
list and the criteria for adding to it.

[zerocopy]: https://crates.io/crates/zerocopy

To regenerate the generated bindings, run `cargo update && cd gen && cargo run --release`.

## Similar crates
Expand Down
105 changes: 105 additions & 0 deletions gen/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! A program which generates a linux-headers installation and runs bindgen
//! over the headers, for each supported architecture.

use bindgen::callbacks::{AttributeInfo, ParseCallbacks, TypeKind};
use bindgen::{builder, EnumVariation};
use std::collections::HashSet;
use std::fs::File;
Expand All @@ -15,6 +16,106 @@ const LINUX_VERSION: &str = "v6.17";
/// Some commonly used features.
const DEFAULT_FEATURES: &str = "\"general\", \"errno\"";

/// Fixed-layout wire-format types that receive `zerocopy` trait derives
/// under the optional `zerocopy` feature, so users can safely convert them
/// to and from byte buffers without hand-written `unsafe`.
///
/// A type belongs here only if, on every supported architecture, it has no
/// pointer, enum, or union fields and no implicit padding — `zerocopy`'s
/// derives verify all of that at compile time, so an unsound addition fails
/// the build rather than compiling into UB.
const ZEROCOPY_TYPES: &[&str] = &[
"__kernel_fsid_t",
"__kernel_itimerspec",
"__kernel_old_timeval",
"__kernel_timespec",
"epoll_event",
"itimerspec",
"itimerval",
"new_utsname",
"pollfd",
"rlimit",
"rlimit64",
"rusage",
"stat",
"statfs",
"statfs64",
"statx",
"statx_timestamp",
"timespec",
"timeval",
"timezone",
"winsize",
];

/// Architectures on which a [`ZEROCOPY_TYPES`] entry does *not* get the
/// derives: the type has implicit padding there (`IntoBytes` would reject
/// it — e.g. `epoll_event` is `__EPOLL_PACKED` only on x86-family), or a
/// field type is excluded. Verified empirically by compiling the feature
/// for every architecture; entries for csky, m68k, and mips32r6 are
/// conservative because their toolchains can't currently build core (a
/// missing derive is never unsound, just unavailable).
const ZEROCOPY_ARCH_EXCEPTIONS: &[(&str, &[&str])] = &[
(
"epoll_event",
&[
"aarch64",
"arm",
"csky",
"hexagon",
"loongarch64",
"m68k",
"mips",
"mips32r6",
"mips64",
"mips64r6",
"powerpc",
"powerpc64",
"riscv32",
"riscv64",
"s390x",
"sparc",
"sparc64",
],
),
(
"statfs64",
&[
"csky", "hexagon", "m68k", "mips", "mips32r6", "powerpc", "riscv32", "sparc", "x32",
],
),
("stat", &["m68k", "powerpc", "powerpc64", "sparc", "sparc64"]),
("timespec", &["x32"]),
("itimerspec", &["x32"]), // contains timespec
("timeval", &["sparc64"]),
("__kernel_old_timeval", &["sparc64"]),
("itimerval", &["sparc64"]), // contains timeval
("rusage", &["sparc64"]), // contains __kernel_old_timeval
];

/// Adds `zerocopy` derives (feature-gated) to the types in
/// [`ZEROCOPY_TYPES`], per the architecture exceptions above.
#[derive(Debug)]
struct ZerocopyDerives {
arch: String,
}

impl ParseCallbacks for ZerocopyDerives {
fn add_attributes(&self, info: &AttributeInfo<'_>) -> Vec<String> {
let excepted = ZEROCOPY_ARCH_EXCEPTIONS
.iter()
.any(|(name, arches)| *name == info.name && arches.contains(&self.arch.as_str()));
if info.kind == TypeKind::Struct && ZEROCOPY_TYPES.contains(&info.name) && !excepted {
vec![
"#[cfg_attr(feature = \"zerocopy\", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]"
.to_owned(),
]
} else {
vec![]
}
}
}

fn main() {
let mut args = env::args();
let _exe = args.next().unwrap();
Expand Down Expand Up @@ -174,6 +275,7 @@ fn main() {
writeln!(cargo_toml, "std = []").unwrap();
writeln!(cargo_toml, "no_std = []").unwrap();
writeln!(cargo_toml, "elf = []").unwrap();
writeln!(cargo_toml, "zerocopy = [\"dep:zerocopy\"]").unwrap();
writeln!(cargo_toml, "rustc-dep-of-std = [\"core\", \"no_std\"]").unwrap();

eprintln!("All bindings generated!");
Expand Down Expand Up @@ -320,6 +422,9 @@ fn run_bindgen(
// The generated bindings are quite large, so use a few simple options
// to keep the file sizes down.
.rustfmt_configuration_file(Some(Path::new("bindgen-rustfmt.toml").to_owned()))
.parse_callbacks(Box::new(ZerocopyDerives {
arch: rust_arch.to_owned(),
}))
.layout_tests(false)
.generate_comments(false)
.default_enum_style(EnumVariation::Rust {
Expand Down
19 changes: 19 additions & 0 deletions src/aarch64/general.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ pub fds_bits: [crate::ctypes::c_ulong; 16usize],
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct __kernel_fsid_t {
pub val: [crate::ctypes::c_int; 2usize],
}
Expand Down Expand Up @@ -478,6 +479,7 @@ pub nr_recently_evicted: __u64,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct pollfd {
pub fd: crate::ctypes::c_int,
pub events: crate::ctypes::c_short,
Expand All @@ -500,18 +502,21 @@ pub reserved: [__u32; 13usize],
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct __kernel_timespec {
pub tv_sec: __kernel_time64_t,
pub tv_nsec: crate::ctypes::c_longlong,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct __kernel_itimerspec {
pub it_interval: __kernel_timespec,
pub it_value: __kernel_timespec,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct __kernel_old_timeval {
pub tv_sec: __kernel_long_t,
pub tv_usec: __kernel_long_t,
Expand All @@ -536,6 +541,7 @@ pub tv_usec: __s64,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct rusage {
pub ru_utime: __kernel_old_timeval,
pub ru_stime: __kernel_old_timeval,
Expand All @@ -556,12 +562,14 @@ pub ru_nivcsw: __kernel_long_t,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct rlimit {
pub rlim_cur: __kernel_ulong_t,
pub rlim_max: __kernel_ulong_t,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct rlimit64 {
pub rlim_cur: __u64,
pub rlim_max: __u64,
Expand Down Expand Up @@ -699,13 +707,15 @@ pub _attribute: *mut crate::ctypes::c_void,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct statx_timestamp {
pub tv_sec: __s64,
pub tv_nsec: __u32,
pub __reserved: __s32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct statx {
pub stx_mask: __u32,
pub stx_blksize: __u32,
Expand Down Expand Up @@ -775,6 +785,7 @@ pub c_ospeed: speed_t,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct winsize {
pub ws_row: crate::ctypes::c_ushort,
pub ws_col: crate::ctypes::c_ushort,
Expand All @@ -793,30 +804,35 @@ pub c_cc: [crate::ctypes::c_uchar; 8usize],
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct timespec {
pub tv_sec: __kernel_old_time_t,
pub tv_nsec: crate::ctypes::c_long,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct timeval {
pub tv_sec: __kernel_old_time_t,
pub tv_usec: __kernel_suseconds_t,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct itimerspec {
pub it_interval: timespec,
pub it_value: timespec,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct itimerval {
pub it_interval: timeval,
pub it_value: timeval,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct timezone {
pub tz_minuteswest: crate::ctypes::c_int,
pub tz_dsttime: crate::ctypes::c_int,
Expand Down Expand Up @@ -966,6 +982,7 @@ pub d_name: __IncompleteArrayField<crate::ctypes::c_char>,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct stat {
pub st_dev: crate::ctypes::c_ulong,
pub st_ino: crate::ctypes::c_ulong,
Expand All @@ -990,6 +1007,7 @@ pub __unused5: crate::ctypes::c_uint,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct statfs {
pub f_type: __kernel_long_t,
pub f_bsize: __kernel_long_t,
Expand All @@ -1006,6 +1024,7 @@ pub f_spare: [__kernel_long_t; 4usize],
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct statfs64 {
pub f_type: __kernel_long_t,
pub f_bsize: __kernel_long_t,
Expand Down
3 changes: 3 additions & 0 deletions src/aarch64/io_uring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,18 +322,21 @@ pub build_id_addr: __u64,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct __kernel_timespec {
pub tv_sec: __kernel_time64_t,
pub tv_nsec: crate::ctypes::c_longlong,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct __kernel_itimerspec {
pub it_interval: __kernel_timespec,
pub it_value: __kernel_timespec,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct __kernel_old_timeval {
pub tv_sec: __kernel_long_t,
pub tv_usec: __kernel_long_t,
Expand Down
1 change: 1 addition & 0 deletions src/aarch64/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ pub machine: [crate::ctypes::c_char; 65usize],
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "zerocopy", derive(zerocopy::FromBytes, zerocopy::IntoBytes, zerocopy::KnownLayout, zerocopy::Immutable))]
pub struct new_utsname {
pub sysname: [crate::ctypes::c_char; 65usize],
pub nodename: [crate::ctypes::c_char; 65usize],
Expand Down
Loading