diff --git a/Cargo.toml b/Cargo.toml index 53fad24..392588a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,9 +21,11 @@ libc = "0.2.186" [build-dependencies] pkg-config = "0.3.33" bindgen = { version = "0.72", optional = true } +cc = "1.2" [features] use-bindgen = ["bindgen"] +extra_traits = ["libc/extra_traits"] [[bin]] name = "regenerate_bindings" diff --git a/build.rs b/build.rs index 0adb5cf..15d43b6 100644 --- a/build.rs +++ b/build.rs @@ -4,6 +4,8 @@ extern crate pkg_config; extern crate bindgen; fn main() { + probe_time64(); + match pkg_config::Config::new().statik(false).probe("alsa") { Err(pkg_config::Error::Failure { command, output }) => panic!( "Pkg-config failed - usually this is because alsa development headers are not installed.\n\n\ @@ -19,6 +21,55 @@ fn main() { }; } +// 32-bit glibc's struct timespec is 8 or 16 bytes depending on the time64 +// transition; libc::timespec assumes 8, so ALSA can write past it and +// segfault on 32-bit targets with time64. +fn probe_time64() { + println!("cargo:rustc-check-cfg=cfg(alsa_sys_time64)"); + + let target_os = std::env::var("CARGO_CFG_TARGET_OS"); + let target_env = std::env::var("CARGO_CFG_TARGET_ENV"); + let target_pointer_width = std::env::var("CARGO_CFG_TARGET_POINTER_WIDTH"); + + // Only glibc on 32-bit Linux has this ambiguity. musl defaulted to a + // 64-bit time_t from the start, and 64-bit targets already match + // libc::timespec natively. + if target_os.is_ok_and(|v| v != "linux") + || target_env.is_ok_and(|v| v != "gnu") + || target_pointer_width.is_ok_and(|v| v != "32") + { + return; + } + + let probe_path = std::path::Path::new(&std::env::var("OUT_DIR").unwrap()).join("time64_probe.c"); + if let Err(e) = std::fs::write( + &probe_path, + "#include \n\ + #if defined(__TIMESIZE) && __TIMESIZE == 64\n\ + alsa_sys_time64=yes\n\ + #else\n\ + alsa_sys_time64=no\n\ + #endif\n", + ) { + println!("cargo:warning=alsa-sys: could not write time64 probe ({e}), assuming legacy 32-bit time_t"); + return; + } + + // On failure, default to 32-bit time_t. A false negative is a no-op, a + // false positive causes segfaults. + let expanded = match cc::Build::new().file(&probe_path).try_expand() { + Ok(bytes) => bytes, + Err(e) => { + println!("cargo:warning=alsa-sys: could not probe glibc time_t width ({e}), assuming legacy 32-bit time_t"); + return; + } + }; + + if String::from_utf8_lossy(&expanded).contains("alsa_sys_time64=yes") { + println!("cargo:rustc-cfg=alsa_sys_time64"); + } +} + #[cfg(feature = "use-bindgen")] fn generate_bindings(alsa_library: &pkg_config::Library) { use std::env; diff --git a/src/lib.rs b/src/lib.rs index 8042f56..7377a90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,38 @@ #![allow(non_upper_case_globals)] #![no_std] -use libc::{FILE, pid_t, pollfd, timespec, timeval}; +use libc::{FILE, pid_t, pollfd}; + +#[cfg(not(alsa_sys_time64))] +pub use libc::{timespec, timeval}; + +// Mirrors glibc's 32-bit time64 struct timespec; see probe_time64 in build.rs. +#[cfg(alsa_sys_time64)] +#[repr(C)] +#[derive(Copy, Clone, Debug, Default)] +#[cfg_attr(feature = "extra_traits", derive(PartialEq, Eq, Hash))] +pub struct timespec { + pub tv_sec: i64, + #[cfg(target_endian = "little")] + pub tv_nsec: i32, + #[cfg(target_endian = "little")] + __pad: i32, + #[cfg(target_endian = "big")] + __pad: i32, + #[cfg(target_endian = "big")] + pub tv_nsec: i32, +} + +// Mirrors glibc's 32-bit time64 struct timeval; see probe_time64 in build.rs. +#[cfg(alsa_sys_time64)] +#[repr(C)] +#[derive(Copy, Clone, Debug, Default)] +#[cfg_attr(feature = "extra_traits", derive(PartialEq, Eq, Hash))] +pub struct timeval { + pub tv_sec: i64, + pub tv_usec: i32, + __pad: i32, +} #[cfg(feature = "use-bindgen")] include!(concat!(env!("OUT_DIR"), "/generated.rs"));