diff --git a/Cargo.lock b/Cargo.lock index 40161e1..8857e7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -161,6 +161,8 @@ version = "1.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" dependencies = [ + "jobserver", + "libc", "shlex", ] @@ -357,7 +359,19 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -465,6 +479,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + [[package]] name = "js-sys" version = "0.3.77" @@ -477,9 +501,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.172" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libloading" @@ -552,7 +576,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -571,10 +595,14 @@ name = "nginx-sys" version = "0.5.0" dependencies = [ "bindgen", + "bitflags", "cc", "dunce", "errno", + "libc", "nginx-src", + "openssl-sys", + "pcre2-sys", "regex", ] @@ -624,6 +652,18 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "os_pipe" version = "1.2.2" @@ -657,6 +697,17 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "pcre2-sys" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "550f5d18fb1b90c20b87e161852c10cde77858c3900c5059b5ad2a1449f11d8a" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -669,6 +720,12 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "prettyplease" version = "0.2.32" @@ -697,6 +754,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "redox_syscall" version = "0.5.12" @@ -743,7 +806,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", @@ -1097,6 +1160,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -1109,6 +1178,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -1390,6 +1468,15 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + [[package]] name = "xattr" version = "1.5.0" diff --git a/Cargo.toml b/Cargo.toml index 589fc61..3254c42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,9 @@ lock_api = "0.4.13" nginx-sys = { path = "nginx-sys", default-features=false, version = "0.5.0"} pin-project-lite = { version = "0.2.16", optional = true } +[workspace.dependencies] +libc = { version = "0.2.174", default-features = false } + [features] default = ["std"] async = [ diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 9006aba..84524fe 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -22,7 +22,7 @@ chrono = "0.4.23" http = "1.1.0" # use unicode-rs idna backend for lower MSRV and faster builds idna_adapter = "=1.1.0" -libc = "0.2.140" +libc.workspace = true tokio = { version = "1.33.0", features = ["full"] } [[example]] diff --git a/examples/httporigdst.rs b/examples/httporigdst.rs index b1f7774..51d906f 100644 --- a/examples/httporigdst.rs +++ b/examples/httporigdst.rs @@ -1,16 +1,17 @@ use std::ffi::{c_int, c_void}; use std::ptr::addr_of; +use libc::sockaddr_storage; use ngx::core; use ngx::ffi::{ in_port_t, ngx_conf_t, ngx_connection_local_sockaddr, ngx_http_add_variable, ngx_http_module_t, ngx_http_variable_t, ngx_inet_get_port, ngx_int_t, ngx_module_t, ngx_sock_ntop, ngx_str_t, - ngx_variable_value_t, sockaddr, sockaddr_storage, INET_ADDRSTRLEN, NGX_HTTP_MODULE, + ngx_variable_value_t, sockaddr, NGX_HTTP_MODULE, }; use ngx::http::{self, HttpModule}; use ngx::{http_variable_get, ngx_log_debug_http, ngx_string}; -const IPV4_STRLEN: usize = INET_ADDRSTRLEN as usize; +const IPV4_STRLEN: usize = 16; // Not exported from `libc` #[derive(Debug, Default)] struct NgxHttpOrigDstCtx { diff --git a/nginx-sys/Cargo.toml b/nginx-sys/Cargo.toml index 8780749..b2da180 100644 --- a/nginx-sys/Cargo.toml +++ b/nginx-sys/Cargo.toml @@ -20,16 +20,27 @@ default-target = "x86_64-unknown-linux-gnu" targets = [] [dependencies] +libc = { optional = true, workspace = true } +openssl-sys = { version = "0.9.109", optional = true } +pcre2-sys = { version = "0.2.9", optional = true } [target.'cfg(not(windows))'.dependencies] errno = { version = "0.3", default-features = false } [build-dependencies] bindgen = "0.71" +bitflags = "2.9.1" cc = "1.2.0" dunce = "1.0.5" regex = "1.11.1" nginx-src = { version = "~1.28.0", optional = true, path = "../nginx-src" } [features] +# Reexport types from "libc" instead of generating our own bindings. +libc = ["dep:libc"] +# Reexport types from "openssl-sys" instead of generating our own bindings. +# Note that openssl-sys depends on "std". +openssl-sys = ["dep:openssl-sys"] +# Reexport types from "pcre2-sys" instead of generating our own bindings. +pcre2-sys = ["dep:pcre2-sys"] vendored = ["dep:nginx-src"] diff --git a/nginx-sys/build/bindgen_callbacks.rs b/nginx-sys/build/bindgen_callbacks.rs new file mode 100644 index 0000000..0f800f0 --- /dev/null +++ b/nginx-sys/build/bindgen_callbacks.rs @@ -0,0 +1,149 @@ +use std::collections::HashMap; + +use bindgen::callbacks::{DeriveTrait, ImplementsTrait}; +use bitflags::bitflags; + +bitflags! { + /// Efficient encoding for bindgen type information enums + #[derive(Clone, Debug)] + pub struct TypeFlags: u8 { + const COPY = 1; + const DEBUG = 1 << 1; + const DEFAULT = 1 << 2; + const HASH = 1 << 3; + const PARTIAL_ORD_OR_PARTIAL_EQ = 1 << 4; + } +} + +impl TypeFlags { + pub fn implements(&self, t: DeriveTrait) -> bool { + match t { + DeriveTrait::Copy => self.contains(Self::COPY), + DeriveTrait::Debug => self.contains(Self::DEBUG), + DeriveTrait::Default => self.contains(Self::DEFAULT), + DeriveTrait::Hash => self.contains(Self::HASH), + DeriveTrait::PartialEqOrPartialOrd => self.contains(Self::PARTIAL_ORD_OR_PARTIAL_EQ), + } + } +} + +#[derive(Debug)] +struct Crate<'a> { + name: &'a str, + types: HashMap<&'a str, TypeFlags>, +} + +impl<'a> Crate<'a> { + pub fn new(name: &'a str, types: impl IntoIterator) -> Self { + Self { + name, + types: HashMap::from_iter(types), + } + } + + pub fn type_names(&self) -> impl Iterator { + self.types.keys().cloned() + } + + pub fn uses(&self) -> Option { + if self.types.is_empty() { + return None; + } + + Some(format!( + r#" +#[allow(unused_imports)] +pub use {}::{{{}}}; +"#, + self.name, + self.type_names().collect::>().join(",") + )) + } +} + +#[derive(Debug, Default)] +pub struct NgxBindgenCallbacks<'a>(Vec>); + +impl<'a> NgxBindgenCallbacks<'a> { + pub fn new() -> Self { + Self::default() + } + + pub fn add_external_types( + &mut self, + source: &'a str, + types: impl IntoIterator, + ) { + if let Some(c) = self.0.iter_mut().find(|c| c.name == source) { + c.types.extend(types) + } else { + self.0.push(Crate::new(source, types)); + } + } + + fn find(&self, name: &str) -> Option<(&Crate, &str, &TypeFlags)> { + for c in &self.0[..] { + for (key, value) in c.types.iter() { + if *key == name { + return Some((c, *key, value)); + } + } + } + None + } + + fn blocklist(&self) -> String { + self.0 + .iter() + .flat_map(Crate::type_names) + .collect::>() + .join("|") + } + + fn uses(&self) -> String { + self.0 + .iter() + .flat_map(Crate::uses) + .collect::>() + .join("\n") + } + + pub fn add_to_builder(self, mut builder: bindgen::Builder) -> bindgen::Builder + where + 'a: 'static, + { + let blocklist = self.blocklist(); + if !blocklist.is_empty() { + builder = builder.blocklist_type(blocklist); + } + + let uses = self.uses(); + if !uses.is_empty() { + builder = builder.raw_line(uses); + } + + builder.parse_callbacks(Box::new(self)) + } +} + +impl<'a> bindgen::callbacks::ParseCallbacks for NgxBindgenCallbacks<'a> { + fn blocklisted_type_implements_trait( + &self, + name: &str, + derive_trait: DeriveTrait, + ) -> Option { + let parts = name.split_ascii_whitespace().collect::>(); + let type_name = match &parts[..] { + ["const", "struct", n] => n, + ["const", n] => n, + ["struct", n] => n, + [n] => n, + _ => panic!("unhandled blocklisted type: {name}"), + }; + + if self.find(type_name)?.2.implements(derive_trait) { + return Some(ImplementsTrait::Yes); + } + None + } +} diff --git a/nginx-sys/build/main.rs b/nginx-sys/build/main.rs index a206dee..3cd4348 100644 --- a/nginx-sys/build/main.rs +++ b/nginx-sys/build/main.rs @@ -6,6 +6,8 @@ use std::fs::{read_to_string, File}; use std::io::Write; use std::path::{Path, PathBuf}; +mod bindgen_callbacks; + const ENV_VARS_TRIGGERING_RECOMPILE: &[&str] = &["OUT_DIR", "NGINX_BUILD_DIR", "NGINX_SOURCE_DIR"]; /// The feature flags set by the nginx configuration script. @@ -215,10 +217,29 @@ fn generate_binding(nginx: &NginxSource) { .parse() .expect("rust-version is valid and supported by bindgen"); - let bindings = bindgen::Builder::default() - // Bindings will not compile on Linux without block listing this item - // It is worth investigating why this is - .blocklist_item("IPPORT_RESERVED") + // Functions that we need for macro and inline fn reimplementations in the nginx-sys itself. + #[cfg(windows)] + let macro_dependencies = [ + "GetLastError", + "SetLastError", + "SwitchToThread", + "WSAGetLastError", + "WSASetLastError", + "rand", + ]; + #[cfg(not(windows))] + let macro_dependencies = ["random", "sched_yield", "usleep"]; + + let mut bindings = bindgen::Builder::default() + // Allow all the NGINX symbols, + .allowlist_function("ngx_.*") + .allowlist_type("ngx_.*") + .allowlist_var("(NGX|NGINX|ngx|nginx)_.*") + // ...and a few symbols required for compilation, + .allowlist_type("bpf_.*") + .allowlist_type("sig_atomic_t|time_t|u_char|u_short") + // ...and a couple of symbols we need in nginx-sys. + .allowlist_function(macro_dependencies.join("|")) // will be restored later in build.rs .blocklist_item("NGX_ALIGNMENT") .generate_cstr(true) @@ -227,9 +248,78 @@ fn generate_binding(nginx: &NginxSource) { .clang_args(clang_args) .layout_tests(false) .rust_target(rust_target) - .use_core() - .generate() - .expect("Unable to generate bindings"); + .use_core(); + + if cfg!(any( + feature = "libc", + feature = "openssl-sys", + feature = "pcre2-sys" + )) { + use bindgen_callbacks::TypeFlags as TF; + + let mut callbacks = bindgen_callbacks::NgxBindgenCallbacks::new(); + if cfg!(feature = "libc") { + callbacks.add_external_types( + "libc", + [ + ("glob_t", TF::COPY), + ("in6_addr", TF::COPY), + ("iocb", TF::COPY), + ("sem_t", TF::COPY), + ("sockaddr_in", TF::COPY), + ("sockaddr_in6", TF::COPY), + ("stat", TF::COPY), + ("DIR", TF::COPY | TF::DEBUG), + ("cmsghdr", TF::COPY | TF::DEBUG), + ("cpu_set_t", TF::COPY | TF::DEBUG), + ("dirent", TF::COPY | TF::DEBUG), + ("gid_t", TF::COPY | TF::DEBUG), + ("in6_pktinfo", TF::COPY | TF::DEBUG), + ("in_addr_t", TF::COPY | TF::DEBUG), + ("in_pktinfo", TF::COPY | TF::DEBUG), + ("in_port_t", TF::COPY | TF::DEBUG), + ("ino_t", TF::COPY | TF::DEBUG), + ("iovec", TF::COPY | TF::DEBUG), + ("msghdr", TF::COPY | TF::DEBUG), + ("off_t", TF::COPY | TF::DEBUG), + ("pid_t", TF::COPY | TF::DEBUG), + ("pthread_cond_t", TF::COPY | TF::DEBUG), + ("pthread_mutex_t", TF::COPY | TF::DEBUG), + ("sockaddr", TF::COPY | TF::DEBUG), + ("sockaddr_un", TF::COPY | TF::DEBUG), + ("socklen_t", TF::COPY | TF::DEBUG), + ("time_t", TF::COPY | TF::DEBUG), + ("tm", TF::COPY | TF::DEBUG), + ("uid_t", TF::COPY | TF::DEBUG), + ], + ); + } + + if cfg!(feature = "openssl-sys") { + callbacks.add_external_types( + "openssl_sys", + [ + ("SSL", TF::empty()), + ("SSL_CTX", TF::empty()), + ("SSL_SESSION", TF::empty()), + ], + ) + } + + if cfg!(feature = "pcre2-sys") { + callbacks.add_external_types( + "pcre2_sys", + [ + ("pcre2_code_8", TF::COPY | TF::DEBUG), + ("pcre2_real_code_8", TF::COPY | TF::DEBUG), + ], + ); + } + + bindings = callbacks.add_to_builder(bindings); + } + + let bindings = bindings.generate().expect("Unable to generate bindings"); // Write the bindings to the $OUT_DIR/bindings.rs file. let out_dir_env =