Mercurial > crates > nonstick
changeset 110:2346fd501b7a
Add tests for constants and do other macro niceties.
- Adds tests for all the constants. Pretty sweet.
- Moves documentation for cfg-pam-impl macro to `libpam-sys`.
- Renames `Illumos` to `Sun`.
- other stuff
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Sun, 29 Jun 2025 02:15:46 -0400 |
parents | bb465393621f |
children | 04105e9a7de8 |
files | Cargo.lock Cargo.toml build.rs libpam-sys/README.md libpam-sys/libpam-sys-impls/Cargo.toml libpam-sys/libpam-sys-impls/build.rs libpam-sys/libpam-sys-impls/src/lib.rs libpam-sys/libpam-sys-test/Cargo.toml libpam-sys/libpam-sys-test/build.rs libpam-sys/libpam-sys-test/tests/test_constants.rs libpam-sys/src/constants.rs libpam-sys/src/helpers.rs libpam-sys/src/lib.rs |
diffstat | 13 files changed, 292 insertions(+), 103 deletions(-) [+] |
line wrap: on
line diff
--- a/Cargo.lock Sat Jun 28 02:49:35 2025 -0400 +++ b/Cargo.lock Sun Jun 29 02:15:46 2025 -0400 @@ -177,6 +177,17 @@ ] [[package]] +name = "libpam-sys-test" +version = "0.0.0" +dependencies = [ + "bindgen", + "libpam-sys", + "libpam-sys-impls", + "quote", + "syn", +] + +[[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index"
--- a/Cargo.toml Sat Jun 28 02:49:35 2025 -0400 +++ b/Cargo.toml Sun Jun 29 02:15:46 2025 -0400 @@ -1,5 +1,5 @@ [workspace] -members = ["libpam-sys", "libpam-sys/libpam-sys-impls", "testharness"] +members = ["libpam-sys", "libpam-sys/libpam-sys-impls", "libpam-sys/libpam-sys-test", "testharness"] resolver = "2" [workspace.package]
--- a/build.rs Sat Jun 28 02:49:35 2025 -0400 +++ b/build.rs Sun Jun 29 02:15:46 2025 -0400 @@ -32,7 +32,6 @@ .header_contents( "linux-pam.h", r#" - #include <syslog.h> // for log levels #include <security/_pam_types.h> #include <security/pam_appl.h> #include <security/pam_ext.h>
--- a/libpam-sys/README.md Sat Jun 28 02:49:35 2025 -0400 +++ b/libpam-sys/README.md Sun Jun 29 02:15:46 2025 -0400 @@ -8,11 +8,11 @@ - Linux: `LinuxPam` - BSDs, including Mac OS: `OpenPam` -- Illumos/Solaris: `Illumos` +- Illumos/Solaris: `Sun` - Unknown: `OpenPamMinimal` Each implementation exports all the functionality available in its respective PAM library. -`OpenPamMinimal` is a subset that includes only the functions available in the spec, and the constants shared in common between OpenPAM and Illumos' implementation. +`OpenPamMinimal` is a subset that includes only the functions available in the spec, and the constants shared in common between OpenPAM and Sun's implementation. ## References @@ -22,7 +22,7 @@ - [Linux-PAM guides][linux-guides]: Documentation for developers using PAM and sysadmins. - [OpenPAM repository][openpam]: The OpenPAM implementation, used by many BSD varieties. This hews very close to the spec. - [OpenPAM man page][manbsd]: NetBSD's root man page for OpenPAM. -- [Illumos PAM repository][illumos-pam]: Illumos's implementation of PAM. Even more basic than OpenPAM. +- [Illumos PAM repository][illumos-pam]: Illumos's implementation of PAM, based on Sun's Solaris. Even more basic than OpenPAM. - [Illumos PAM man page][manillumos]: Illumos's root man page for its PAM implementation. [xsso]: https://pubs.opengroup.org/onlinepubs/8329799/toc.htm
--- a/libpam-sys/libpam-sys-impls/Cargo.toml Sat Jun 28 02:49:35 2025 -0400 +++ b/libpam-sys/libpam-sys-impls/Cargo.toml Sun Jun 29 02:15:46 2025 -0400 @@ -2,15 +2,17 @@ name = "libpam-sys-impls" description = "Macros for use in libpam-sys." version = "0.0.1" -edition = "2021" +rust-version.workspace = true +edition.workspace = true [lib] proc-macro = true -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [build-dependencies] bindgen = "0.72.0" +proc-macro2 = "1.0.95" +quote = "1.0.40" strum = { version = "0.27.1", features = ["derive"] }
--- a/libpam-sys/libpam-sys-impls/build.rs Sat Jun 28 02:49:35 2025 -0400 +++ b/libpam-sys/libpam-sys-impls/build.rs Sun Jun 29 02:15:46 2025 -0400 @@ -1,10 +1,20 @@ +//! This absurd build script basically sets up everything for libpam-sys-impl. +//! +//! 1. It's the definition site for the [`PamImpl`] enum, which then gets +//! output to the `OUT_DIR/pam_impl_enum.rs` file for parsing/inclusion +//! into the `__pam_impl_enum__` macro. +//! 2. It detects the current PAM implementation and sets an env var for +//! the macros in `libpam-sys-impl`. + +use std::{env, fs}; use strum::EnumString; +use proc_macro2::TokenStream; +use quote::quote; fn main() { let pam_impl = match option_env!("LIBPAMSYS_IMPL") { // The default option: Guess what PAM impl we're using based on OS. None => { - // Otherwise, guess what PAM impl we're using based on the OS. if cfg!(target_os = "linux") { PamImpl::LinuxPam } else if cfg!(any( @@ -15,8 +25,11 @@ target_os = "openbsd" )) { PamImpl::OpenPam - } else if cfg!(any()) { - PamImpl::Illumos + } else if cfg!(any( + target_os = "illumos", + target_os = "solaris", + )) { + PamImpl::Sun } else { PamImpl::MinimalOpenPam } @@ -28,8 +41,8 @@ } else if header_exists("security/openpam.h") { PamImpl::OpenPam } else if header_exists("security/pam_appl.h") { - // We figure we're *probably* on Illumos or something like that. - PamImpl::Illumos + // We figure we're *probably* on a Sun derivative. + PamImpl::Sun } else { // If all else fails, assume the bare minimum. PamImpl::MinimalOpenPam @@ -40,17 +53,63 @@ Err(_) => panic!("unknown PAM implementation {other:?}"), }, }; + fs::write(format!("{}/pam_impl_enum.rs", env::var("OUT_DIR").unwrap()), PamImpl::enum_tokens().to_string()).unwrap(); println!("cargo:rustc-env=LIBPAMSYS_IMPL={pam_impl:?}"); } -#[derive(Debug, EnumString)] -enum PamImpl { - Illumos, - LinuxPam, - OpenPam, - MinimalOpenPam, +/// This defines a local enum with an `enum_tokens()` method that can spit out +/// its own contents. +macro_rules! self_aware_enum { + ( + $(#here[$here:meta])* + $(#[$attr:meta])* + $name:ident { + $($tt:tt)* + } + ) => { + $(#[$here])* + $(#[$attr])* + pub enum $name { + $($tt)* + } + + impl $name { + fn enum_tokens() -> TokenStream { + quote!( + $(#[$attr])* + pub enum $name { + $($tt)* + } + ) + } + } + } } +self_aware_enum!( + #here[derive(EnumString, strum::Display)] + /// The PAM implementations supported by `libpam-sys`. + #[derive(Clone, Copy, Debug, PartialEq)] + PamImpl { + /// [Linux-PAM] is provided by most Linux distributions. + /// + /// [Linux-PAM]: https://github.com/linux-pam/linux-pam + LinuxPam, + /// [OpenPAM] is used by most BSDs, including Mac OS X. + /// + /// [OpenPAM]: https://git.des.dev/OpenPAM/OpenPAM + OpenPam, + /// Illumos and Solaris use a derivative of [Sun's implementation][sun]. + /// + /// [sun]: https://code.illumos.org/plugins/gitiles/illumos-gate/+/refs/heads/master/usr/src/lib/libpam + Sun, + /// Only the functionality in [the PAM spec], with OpenPAM/Sun consts. + /// + /// [the PAM spec]: https://pubs.opengroup.org/onlinepubs/8329799/toc.htm + MinimalOpenPam, + } +); + fn header_exists(header: &str) -> bool { bindgen::Builder::default() .blocklist_item(".*")
--- a/libpam-sys/libpam-sys-impls/src/lib.rs Sat Jun 28 02:49:35 2025 -0400 +++ b/libpam-sys/libpam-sys-impls/src/lib.rs Sun Jun 29 02:15:46 2025 -0400 @@ -2,36 +2,10 @@ use proc_macro2::{Delimiter, Group, Literal, Span, TokenStream, TokenTree}; use quote::{format_ident, quote}; use std::fmt::Display; +use std::str::FromStr; use syn::Lit; -/// A `cfg`-like attribute macro for code specific to one PAM implementation. -/// -/// Different versions of PAM export different functions and have some -/// meaningful internal implementation differences, like the way `pam_conv` -/// is handled (see [the Linux-PAM man page for details][man7]). -/// -/// ``` -/// # use libpam_sys_impls::cfg_pam_impl; -/// #[cfg_pam_impl("Illumos")] -/// fn do_something() { /* illumos-only code */ } -/// -/// #[cfg_pam_impl(not("Illumos"))] -/// fn do_something() { /* non-illumos code */ } -/// -/// #[cfg_pam_impl(any("LinuxPam", "MinimalOpenPam"))] -/// fn do_something_else() { /* Linux-PAM or minimal OpenPAM */ } -/// -/// #[cfg_pam_impl(not(any("Illumos", "OpenPam")))] -/// fn do_a_third_thing() { /* Neither Illumos nor OpenPAM */ } -/// -/// #[cfg_pam_impl(any())] -/// fn this_will_never_build() { /* why would you do this? */ } -/// -/// #[cfg_pam_impl(not(any()))] -/// fn this_will_always_build() { /* this is technically legal */ } -/// ``` -/// -/// [man7]: https://man7.org/linux/man-pages/man3/pam_conv.3.html +// For documentation on this, see the `libpam-sys` crate. #[proc_macro_attribute] pub fn cfg_pam_impl(attr: pm::TokenStream, item: pm::TokenStream) -> pm::TokenStream { Predicate::parse(attr.into(), None) @@ -45,48 +19,28 @@ .unwrap_or_else(|e| syn::Error::from(e).into_compile_error().into()) } -/// Outputs the `PamImpl` enum and `LIBPAMSYS_IMPL` constant. Private. +/// Outputs the `PamImpl` enum and `LIBPAMSYS_IMPL` constant. +/// For use only in `libpam-sys`. +/// +/// The tokens passed into the macro are pasted immediately before the enum. #[proc_macro] -pub fn pam_impl_enum(data: pm::TokenStream) -> pm::TokenStream { - if !data.is_empty() { - panic!("unexpected stuff in pam_impl_enum!()") - } - +pub fn __pam_impl_enum__(data: pm::TokenStream) -> pm::TokenStream { let variant = format_ident!("{}", pam_impl_str()); - - quote!( - /// The PAM implementations supported by `libpam-sys`. - #[non_exhaustive] - #[derive(Clone, Copy, Debug, PartialEq)] - pub enum PamImpl { - /// [Linux-PAM] is provided by most Linux implementations. - /// - /// [Linux-PAM]: https://github.com/linux-pam/linux-pam - LinuxPam, - /// [OpenPAM] is used by most BSD distributions, including Mac OS X. - /// - /// [OpenPAM]: https://git.des.dev/OpenPAM/OpenPAM - OpenPam, - /// [Illumos PAM] is used on Illumos and Solaris systems. - /// - /// [Illumos PAM]: https://code.illumos.org/plugins/gitiles/illumos-gate/+/refs/heads/master/usr/src/lib/libpam - Illumos, - /// Only the functionality in [the PAM spec], - /// with OpenPAM/Illumos constants. - /// - /// [the PAM spec]: https://pubs.opengroup.org/onlinepubs/8329799/toc.htm - MinimalOpenPam, - } - - #[doc = concat!("This version of libpam-sys was built for **", stringify!(#variant), "**.")] - pub const LIBPAMSYS_IMPL: PamImpl = PamImpl::#variant; - ) - .into() + TokenStream::from_iter([ + data.into(), + TokenStream::from_str(include_str!(concat!(env!("OUT_DIR"), "/pam_impl_enum.rs"))).unwrap(), + quote!( + impl PamImpl { + #[doc = concat!("The PAM implementation this was built for (currently `", stringify!(#variant), ")`.")] + pub const CURRENT: Self = Self::#variant; + } + ), + ]).into() } -/// The name of the PAM implementation. Used only in `libpam-sys`. Private. +/// The name of the PAM implementation. For use only in `libpam-sys`. #[proc_macro] -pub fn pam_impl_name(data: pm::TokenStream) -> pm::TokenStream { +pub fn __pam_impl_name__(data: pm::TokenStream) -> pm::TokenStream { if !data.is_empty() { panic!("pam_impl_name! does not take any input") } @@ -296,8 +250,8 @@ } } let matching = cases![ - ("Illumos", (any("Illumos", "OpenPam"))), - ("OpenPam", (any("Illumos", "OpenPam"))), + ("Sun", (any("Sun", "OpenPam"))), + ("OpenPam", (any("Sun", "OpenPam"))), ("LinuxPam", (not("OpenPam"))), ("MinimalOpenPam", (not("OpenPam"))), ("Other", (not(any("This", "That")))), @@ -311,10 +265,12 @@ let nonmatching = cases![ ("LinuxPam", (not("LinuxPam"))), - ("Illumos", ("LinuxPam")), - ("OpenPam", (any("LinuxPam", "Illumos"))), + ("Sun", ("LinuxPam")), + ("OpenPam", (any("LinuxPam", "Sun"))), ("One", (not(any("One", "Another")))), ("Negatory", (not(not("Affirmative")))), + ("MinimalOpenPam", ("OpenPam")), + ("OpenPam", (("MinimalOpenPam"))), ]; for (bad, tree) in nonmatching { let pred = parse(tree);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/libpam-sys-test/Cargo.toml Sun Jun 29 02:15:46 2025 -0400 @@ -0,0 +1,15 @@ +[package] +name = "libpam-sys-test" +version = "0.0.0" +edition.workspace = true +rust-version.workspace = true +publish = false + +[dependencies] +libpam-sys = { path = ".." } + +[build-dependencies] +bindgen = "0.72.0" +libpam-sys-impls = { path = "../libpam-sys-impls" } +quote = "1.0.40" +syn = { version = "2.0.104", default-features = false }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/libpam-sys-test/build.rs Sun Jun 29 02:15:46 2025 -0400 @@ -0,0 +1,103 @@ +use bindgen::MacroTypeVariation; +use libpam_sys_impls::cfg_pam_impl; +use quote::ToTokens; +use std::path::PathBuf; +use std::{env, fs}; +use syn::{Item, ItemConst}; + +fn main() { + generate_const_test(); +} + +#[cfg_pam_impl("LinuxPam")] +fn test_config() -> TestConfig { + TestConfig { + headers: vec![ + "security/_pam_types.h".into(), + "security/pam_appl.h".into(), + "security/pam_ext.h".into(), + "security/pam_modules.h".into(), + ], + ignore_consts: vec!["__LINUX_PAM__".into(), "__LINUX_PAM_MINOR__".into()], + } +} + +#[cfg_pam_impl("OpenPam")] +fn test_config() -> TestConfig { + TestConfig { + headers: vec![ + "security/pam_types.h", + "security/openpam.h", + "security/pam_appl.h", + "security/pam_constants.h", + ], + ignore_consts: vec![], + } +} + +#[cfg_pam_impl(not(any("LinuxPam", "OpenPam")))] +fn test_config() -> TestConfig { + panic!("This PAM implementation is not yet tested.") +} + +fn generate_const_test() { + let config = test_config(); + let builder = bindgen::Builder::default() + .header_contents("_.h", &config.header_contents()) + .merge_extern_blocks(true) + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + .blocklist_type(".*") + .blocklist_function(".*") + .allowlist_var(".*") + .default_macro_constant_type(MacroTypeVariation::Unsigned); + + let generated = builder.generate().unwrap().to_string(); + let file = syn::parse_file(&generated).unwrap(); + let mut tests = vec![]; + tests.push("{".into()); + tests.extend( + file.items + .iter() + .filter_map(|item| { + if let Item::Const(item) = item { + Some(item) + } else { + None + } + }) + .filter(|item| config.should_check_const(item)) + .map(|item| { + let tokens = item.expr.to_token_stream(); + format!( + "assert_eq!({tokens}, libpam_sys::{name});", + name = item.ident + ) + }), + ); + tests.push("}".into()); + fs::write( + PathBuf::from(env::var("OUT_DIR").unwrap()).join("constant_test.rs"), + tests.join("\n"), + ) + .unwrap(); +} + +struct TestConfig { + headers: Vec<String>, + ignore_consts: Vec<String>, +} + +impl TestConfig { + fn header_contents(&self) -> String { + let vec: Vec<_> = self + .headers + .iter() + .map(|h| format!("#include <{h}>\n")) + .collect(); + vec.join("") + } + + fn should_check_const(&self, item: &ItemConst) -> bool { + !self.ignore_consts.contains(&item.ident.to_string()) + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/libpam-sys-test/tests/test_constants.rs Sun Jun 29 02:15:46 2025 -0400 @@ -0,0 +1,5 @@ + +#[test] +fn check_constants() { + include!(concat!(env!("OUT_DIR"), "/constant_test.rs")) +}
--- a/libpam-sys/src/constants.rs Sat Jun 28 02:49:35 2025 -0400 +++ b/libpam-sys/src/constants.rs Sun Jun 29 02:15:46 2025 -0400 @@ -1,4 +1,7 @@ //! All the constants. +//! +//! These constants are tested on a per-platform basis by `libpam-sys-test`'s +//! `test_constants.rs`. use libpam_sys_impls::cfg_pam_impl; @@ -71,6 +74,7 @@ PAM_NO_MODULE_DATA = 18; PAM_CONV_ERR = 19; PAM_AUTHTOK_ERR = 20; + PAM_AUTHTOK_RECOVER_ERR = 21; PAM_AUTHTOK_RECOVERY_ERR = 21; PAM_AUTHTOK_LOCK_BUSY = 22; PAM_AUTHTOK_DISABLE_AGING = 23; @@ -106,7 +110,7 @@ PAM_FAIL_DELAY = 10; PAM_XDISPLAY = 11; PAM_XAUTHDATA = 12; - PAM_AUTHTOKTYPE = 13; + PAM_AUTHTOK_TYPE = 13; ); /// To suppress messages in the item cleanup function. @@ -184,10 +188,10 @@ } /// Constants exclusive to Illumos. -#[cfg_pam_impl("Illumos")] -pub use illumos::*; -#[cfg_pam_impl("Illumos")] -mod illumos { +#[cfg_pam_impl("Sun")] +pub use sun::*; +#[cfg_pam_impl("Sun")] +mod sun { /// The total number of PAM error codes. pub const PAM_TOTAL_ERRNUM: u32 = 28; @@ -227,5 +231,3 @@ pub const PAM_NUM_ITEMS: u32 = 14; } -#[cfg(test)] -mod test {}
--- a/libpam-sys/src/helpers.rs Sat Jun 28 02:49:35 2025 -0400 +++ b/libpam-sys/src/helpers.rs Sun Jun 29 02:15:46 2025 -0400 @@ -1,10 +1,10 @@ //! This module contains a few non-required helpers to deal with some of the //! more annoying memory management in the PAM API. use crate::structs::BinaryPayload; +use std::error::Error; use std::fmt; +use std::mem::ManuallyDrop; use std::ptr::NonNull; -use std::error::Error; -use std::mem::ManuallyDrop; /// Error returned when attempting to allocate a buffer that is too big. ///
--- a/libpam-sys/src/lib.rs Sat Jun 28 02:49:35 2025 -0400 +++ b/libpam-sys/src/lib.rs Sun Jun 29 02:15:46 2025 -0400 @@ -2,17 +2,54 @@ //! //! ## PAM implementation //! -#![doc = concat!("This documentation was built for the **", pam_impl_name!(), "** implementation.")] +#![doc = concat!("This documentation was built for the **", __pam_impl_name__!(), "** implementation.")] -#[doc(inline)] -pub use libpam_sys_impls::cfg_pam_impl; -use libpam_sys_impls::{pam_impl_enum, pam_impl_name}; +use libpam_sys_impls::{__pam_impl_enum__, __pam_impl_name__}; + mod constants; - -pam_impl_enum!(); - pub mod helpers; mod structs; +/// A `cfg`-like attribute macro for code specific to one PAM implementation. +/// +/// Different versions of PAM export different functions and have some +/// meaningful internal implementation differences, like the way `pam_conv` +/// is handled (see [the Linux-PAM man page for details][man7]). +/// +/// This macro will let you figure out which PAM you're compiling +/// (and eventually running) against so you can make those critical changes. +/// +/// The implementation names are the same as those in the [`PamImpl`] enum. +/// +/// ``` +/// use libpam_sys::cfg_pam_impl; +/// +/// #[cfg_pam_impl("Sun")] +/// fn do_something() { /* Illumos/Solaris-only code */ } +/// +/// #[cfg_pam_impl(not("Sun"))] +/// fn do_something() { /* non-Illumos code */ } +/// +/// #[cfg_pam_impl(any("LinuxPam", "MinimalOpenPam"))] +/// fn do_something_else() { /* Linux-PAM or minimal OpenPAM */ } +/// +/// #[cfg_pam_impl(not(any("Sun", "OpenPam")))] +/// fn do_a_third_thing() { /* Neither Sun nor OpenPAM */ } +/// +/// #[cfg_pam_impl(any())] +/// fn this_will_never_build() { /* why would you do this? */ } +/// +/// #[cfg_pam_impl(not(any()))] +/// fn this_will_always_build() { /* I, sure, whatever, you do you. */ } +/// ``` +/// +/// [man7]: https://man7.org/linux/man-pages/man3/pam_conv.3.html +#[doc(inline)] +pub use libpam_sys_impls::cfg_pam_impl; + +// Looking for the actual code defining this enum? +// It's in the build.rs file for libpam_sys_impls. +__pam_impl_enum__!(#[non_exhaustive]); + #[doc(inline)] pub use crate::{constants::*, structs::*};