changeset 127:c77846f3a979

GET CTEST WORKING. This will verify that the functions we're exporting are correct. It has been a nightmare.
author Paul Fisher <paul@pfish.zone>
date Mon, 30 Jun 2025 22:56:26 -0400
parents 57c812e308bd
children ad77f2af5ff4
files Cargo.lock libpam-sys/Cargo.toml libpam-sys/build.rs libpam-sys/libpam-sys-impls/build.rs libpam-sys/libpam-sys-test/Cargo.toml libpam-sys/libpam-sys-test/build.rs libpam-sys/libpam-sys-test/tests/ctest.rs libpam-sys/libpam-sys-test/tests/runner.rs libpam-sys/libpam-sys-test/tests/test_constants.rs libpam-sys/src/constants.rs libpam-sys/src/ffi.rs libpam-sys/src/funcs/xsso_base.rs libpam-sys/src/helpers.rs libpam-sys/src/lib.rs libpam-sys/src/structs.rs testharness/src/lib.rs
diffstat 16 files changed, 555 insertions(+), 216 deletions(-) [+]
line wrap: on
line diff
--- a/Cargo.lock	Mon Jun 30 21:52:10 2025 -0400
+++ b/Cargo.lock	Mon Jun 30 22:56:26 2025 -0400
@@ -29,7 +29,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4f72209734318d0b619a5e0f5129918b848c416e122a3c4ce054e03cb87b726f"
 dependencies = [
- "bitflags",
+ "bitflags 2.9.1",
  "cexpr",
  "clang-sys",
  "itertools",
@@ -45,6 +45,12 @@
 
 [[package]]
 name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
 version = "2.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
@@ -55,13 +61,22 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "46e3374c604fb39d1a2f35ed5e4a4e30e60d01fab49446e08f1b3e9a90aef202"
 dependencies = [
- "semver",
+ "semver 0.9.0",
  "serde",
  "serde_derive",
  "serde_json",
 ]
 
 [[package]]
+name = "cc"
+version = "1.2.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
+dependencies = [
+ "shlex",
+]
+
+[[package]]
 name = "cexpr"
 version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -72,6 +87,12 @@
 
 [[package]]
 name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "cfg-if"
 version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
@@ -88,6 +109,39 @@
 ]
 
 [[package]]
+name = "ctest"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f18c94d081f9a0355affbeee3f8e677ce206e9aea89b952421f0be6bc588dde"
+dependencies = [
+ "cc",
+ "garando_syntax",
+ "indoc",
+ "rustc_version",
+]
+
+[[package]]
+name = "dirs"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
+dependencies = [
+ "cfg-if 0.1.10",
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
 name = "dlopen"
 version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -123,6 +177,54 @@
 checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
 
 [[package]]
+name = "garando_errors"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18495ec4aced5922809efe4d2862918ff0e8d75e122bde57bba9bae45965256a"
+dependencies = [
+ "garando_pos",
+ "libc",
+ "serde",
+ "term",
+ "unicode-xid 0.2.6",
+]
+
+[[package]]
+name = "garando_pos"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c9386fc75dca486daefbbf5a8d2ea6f379237f95c9b982776159cd66f220aaf"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "garando_syntax"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d8a383861d12fc78c251bbcb1ec252dd8338714ce02ab0cc393cfd02f40d32b"
+dependencies = [
+ "bitflags 1.3.2",
+ "garando_errors",
+ "garando_pos",
+ "log",
+ "serde",
+ "serde_json",
+ "unicode-xid 0.2.6",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+dependencies = [
+ "cfg-if 1.0.1",
+ "libc",
+ "wasi",
+]
+
+[[package]]
 name = "glob"
 version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -151,6 +253,12 @@
 ]
 
 [[package]]
+name = "indoc"
+version = "2.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
+
+[[package]]
 name = "itertools"
 version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -183,7 +291,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
 dependencies = [
- "cfg-if",
+ "cfg-if 1.0.1",
  "windows-targets",
 ]
 
@@ -192,6 +300,7 @@
 version = "0.1.0"
 dependencies = [
  "libpam-sys-impls",
+ "strum",
 ]
 
 [[package]]
@@ -211,13 +320,26 @@
 version = "0.0.0"
 dependencies = [
  "bindgen",
+ "ctest",
+ "libc",
  "libpam-sys",
  "libpam-sys-impls",
+ "proc-macro2 1.0.95",
  "quote 1.0.40",
  "syn 2.0.104",
 ]
 
 [[package]]
+name = "libredox"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638"
+dependencies = [
+ "bitflags 2.9.1",
+ "libc",
+]
+
+[[package]]
 name = "log"
 version = "0.4.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -259,7 +381,7 @@
 version = "0.0.8-alpha0"
 dependencies = [
  "bindgen",
- "bitflags",
+ "bitflags 2.9.1",
  "libc",
  "libpam-sys",
  "memoffset",
@@ -274,7 +396,7 @@
  "anyhow",
  "nonstick",
  "test-cdylib",
- "thiserror",
+ "thiserror 2.0.12",
 ]
 
 [[package]]
@@ -324,7 +446,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
 dependencies = [
- "unicode-xid",
+ "unicode-xid 0.1.0",
 ]
 
 [[package]]
@@ -355,6 +477,17 @@
 ]
 
 [[package]]
+name = "redox_users"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
+dependencies = [
+ "getrandom",
+ "libredox",
+ "thiserror 1.0.69",
+]
+
+[[package]]
 name = "regex"
 version = "1.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -390,6 +523,15 @@
 checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
 
 [[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver 1.0.26",
+]
+
+[[package]]
 name = "rustversion"
 version = "1.0.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -412,6 +554,12 @@
 ]
 
 [[package]]
+name = "semver"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
+
+[[package]]
 name = "semver-parser"
 version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -485,7 +633,7 @@
 dependencies = [
  "proc-macro2 0.4.30",
  "quote 0.6.13",
- "unicode-xid",
+ "unicode-xid 0.1.0",
 ]
 
 [[package]]
@@ -500,6 +648,16 @@
 ]
 
 [[package]]
+name = "term"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0863a3345e70f61d613eab32ee046ccd1bcc5f9105fe402c61fcd0c13eeb8b5"
+dependencies = [
+ "dirs",
+ "winapi",
+]
+
+[[package]]
 name = "test-cdylib"
 version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -513,11 +671,31 @@
 
 [[package]]
 name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
 version = "2.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
 dependencies = [
- "thiserror-impl",
+ "thiserror-impl 2.0.12",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2 1.0.95",
+ "quote 1.0.40",
+ "syn 2.0.104",
 ]
 
 [[package]]
@@ -570,6 +748,18 @@
 checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
 
 [[package]]
+name = "unicode-xid"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
 name = "winapi"
 version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
--- a/libpam-sys/Cargo.toml	Mon Jun 30 21:52:10 2025 -0400
+++ b/libpam-sys/Cargo.toml	Mon Jun 30 22:56:26 2025 -0400
@@ -17,3 +17,4 @@
 
 [build-dependencies]
 libpam-sys-impls = { path = "libpam-sys-impls" }
+strum = { version = "0.27.1", features = ["derive"] }
--- a/libpam-sys/build.rs	Mon Jun 30 21:52:10 2025 -0400
+++ b/libpam-sys/build.rs	Mon Jun 30 22:56:26 2025 -0400
@@ -1,3 +1,13 @@
+use libpam_sys_impls::__pam_impl_enum__;
+use strum::{EnumIter, IntoEnumIterator};
+
+__pam_impl_enum__!(#[derive(EnumIter)]);
+
 fn main() {
     println!("cargo:rustc-link-lib=pam");
+    let pam_impl_strs: Vec<_> = PamImpl::iter().map(|e| format!("\"{:?}\"", e)).collect();
+    let pam_impls = pam_impl_strs.join(",");
+    // We use this for ctest. Don't do what we've done; just use cfg_pam_impl.
+    println!("cargo:check-cfg=cfg(_private_pam_impl_hack, values({pam_impls}))");
+    println!("cargo:rustc-cfg=_private_pam_impl_hack=\"{:?}\"", PamImpl::CURRENT);
 }
--- a/libpam-sys/libpam-sys-impls/build.rs	Mon Jun 30 21:52:10 2025 -0400
+++ b/libpam-sys/libpam-sys-impls/build.rs	Mon Jun 30 22:56:26 2025 -0400
@@ -5,6 +5,9 @@
 //!     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`.
+//!
+//! We need to have this detection here for `#[cfg_pam_impl]` to work properly
+//! when exported to downstream crates.
 
 use dlopen::raw::Library;
 use proc_macro2::TokenStream;
--- a/libpam-sys/libpam-sys-test/Cargo.toml	Mon Jun 30 21:52:10 2025 -0400
+++ b/libpam-sys/libpam-sys-test/Cargo.toml	Mon Jun 30 22:56:26 2025 -0400
@@ -6,10 +6,13 @@
 publish = false
 
 [dependencies]
+libc = "0.2.174"
 libpam-sys = { path = ".." }
 
 [build-dependencies]
 bindgen = "0.72.0"
+ctest = "0.4.11"
 libpam-sys-impls = { path = "../libpam-sys-impls" }
+proc-macro2 = "1.0.95"
 quote = "1.0.40"
 syn = { version = "2.0.104", features = ["full"] }
--- a/libpam-sys/libpam-sys-test/build.rs	Mon Jun 30 21:52:10 2025 -0400
+++ b/libpam-sys/libpam-sys-test/build.rs	Mon Jun 30 22:56:26 2025 -0400
@@ -1,7 +1,10 @@
 use bindgen::MacroTypeVariation;
 use libpam_sys_impls::__pam_impl_enum__;
+use proc_macro2::{Group, TokenStream, TokenTree};
 use quote::{format_ident, ToTokens};
-use std::path::PathBuf;
+use std::path::Path;
+use std::process::Command;
+use std::str::FromStr;
 use std::{env, fs};
 use syn::{Item, ItemConst, Type, TypePath};
 
@@ -46,6 +49,7 @@
         },
     };
     generate_const_test(&config);
+    generate_ctest(&config);
 }
 
 fn generate_const_test(config: &TestConfig) {
@@ -63,8 +67,15 @@
 
     let generated = builder.generate().unwrap().to_string();
     let file = syn::parse_file(&generated).unwrap();
-    let mut tests = vec![];
-    tests.push("{".into());
+    let mut tests = vec![
+        "use libpam_sys::*;".into(),
+        "#[allow(deprecated, overflowing_literals)]".into(),
+        "fn main() {".into(),
+        format!(
+            "assert_eq!(PamImpl::CURRENT, PamImpl::{:?});",
+            PamImpl::CURRENT
+        ),
+    ];
     tests.extend(
         file.items
             .iter()
@@ -83,18 +94,97 @@
                     path: format_ident!("i32").into(),
                 }));
                 format!(
-                    "assert_eq!({tokens}, libpam_sys::{name});",
+                    "assert_eq!({tokens}, {name});",
                     tokens = item.expr.to_token_stream(),
                     name = item.ident
                 )
             }),
     );
     tests.push("}".into());
-    fs::write(
-        PathBuf::from(env::var("OUT_DIR").unwrap()).join("constant_test.rs"),
-        tests.join("\n"),
+    let const_test = test_file("constant_test.rs");
+    fs::write(&const_test, tests.join("\n")).unwrap();
+    rustfmt(&const_test);
+}
+
+fn generate_ctest(config: &TestConfig) {
+    let mut test = ctest::TestGenerator::new();
+
+    for header in config.headers.iter() {
+        if header.starts_with('"') {
+            test.include(env::var("CARGO_MANIFEST_DIR").unwrap());
+        }
+        test.header(&header[1..header.len() - 1]);
+    }
+    // These are opaque structs.
+    test.skip_struct(|name| matches!(name, "pam_handle" | "AppData"));
+    test.skip_type(|name| matches!(name, "ConversationCallback" | "CleanupCallback"));
+    test.type_name(|name, _is_struct, is_union| {
+        assert!(!is_union); // we scabbin'
+        match name {
+            "pam_handle" => "struct pam_handle",
+            "pam_conv" => "struct pam_conv",
+            "pam_message" => "struct pam_message",
+            "pam_response" => "struct pam_response",
+            "AppData" => "void",
+            other => other,
+        }
+        .into()
+    });
+
+    //
+    // Welcome to THE HACK ZONE.
+    //
+
+    // Define away constness because the various PAM implementations
+    // have different const annotations and this will surely drive you crazy.
+    test.define("const", Some(""));
+
+    // Also replace all the `const`s with `mut`s in the ffi.rs file.
+    let file_contents = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../src/ffi.rs"));
+    let deconsted_file = test_file("ffi.rs");
+    remove_consts(file_contents, &deconsted_file);
+
+    test.generate(&deconsted_file, "ctest.rs");
+}
+
+fn remove_consts(file_contents: &str, out_file: impl AsRef<Path>) {
+    let deconstified = deconstify(
+        TokenStream::from_str(file_contents).unwrap(),
+        &TokenStream::from_str("mut")
+            .unwrap()
+            .into_iter()
+            .next()
+            .unwrap(),
     )
-    .unwrap();
+    .to_string();
+    let out_file = out_file.as_ref();
+    fs::write(out_file, deconstified).unwrap();
+    rustfmt(out_file)
+}
+
+fn rustfmt(file: impl AsRef<Path>) {
+    let status = Command::new(env!("CARGO"))
+        .args(["fmt", "--", file.as_ref().to_str().unwrap()])
+        .status()
+        .unwrap();
+    assert!(status.success(), "rustfmt exited with code {status}");
+}
+
+fn deconstify(stream: TokenStream, mut_token: &TokenTree) -> TokenStream {
+    TokenStream::from_iter(stream.into_iter().map(|token| {
+        match token {
+            TokenTree::Group(g) => {
+                TokenTree::Group(Group::new(g.delimiter(), deconstify(g.stream(), mut_token)))
+                    .into_token_stream()
+            }
+            TokenTree::Ident(id) if id == "const" => mut_token.into_token_stream(),
+            other => other.into_token_stream(),
+        }
+    }))
+}
+
+fn test_file(name: impl AsRef<str>) -> String {
+    format!("{}/{}", env::var("OUT_DIR").unwrap(), name.as_ref())
 }
 
 #[derive(Default)]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpam-sys/libpam-sys-test/tests/ctest.rs	Mon Jun 30 22:56:26 2025 -0400
@@ -0,0 +1,4 @@
+#![allow(unused_imports, clippy::all)]
+use libpam_sys::*;
+
+include!(concat!(env!("OUT_DIR"), "/ctest.rs"));
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpam-sys/libpam-sys-test/tests/runner.rs	Mon Jun 30 22:56:26 2025 -0400
@@ -0,0 +1,1 @@
+include!(concat!(env!("OUT_DIR"), "/constant_test.rs"));
--- a/libpam-sys/libpam-sys-test/tests/test_constants.rs	Mon Jun 30 21:52:10 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-#[allow(deprecated, overflowing_literals)]
-#[test]
-fn check_constants() {
-    include!(concat!(env!("OUT_DIR"), "/constant_test.rs"))
-}
--- a/libpam-sys/src/constants.rs	Mon Jun 30 21:52:10 2025 -0400
+++ b/libpam-sys/src/constants.rs	Mon Jun 30 22:56:26 2025 -0400
@@ -127,6 +127,7 @@
     );
 
     c_enum!(
+        /// An item type (Linux-only).
         PAM_FAIL_DELAY = 10,
         PAM_XDISPLAY,
         PAM_XAUTHDATA,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpam-sys/src/ffi.rs	Mon Jun 30 22:56:26 2025 -0400
@@ -0,0 +1,224 @@
+#![allow(non_camel_case_types)]
+
+use std::ffi::{c_char, c_int, c_void};
+use std::fmt;
+use std::marker::{PhantomData, PhantomPinned};
+
+/// A marker struct to make whatever it's in `!Sync`, `!Send`, and `!Unpin`.
+#[derive(Default, PartialOrd, PartialEq, Ord, Eq)]
+#[repr(C)]
+struct ExtremelyUnsafe {
+    _value: (),
+    _marker: PhantomData<(PhantomPinned, *mut c_void)>,
+}
+
+impl fmt::Debug for ExtremelyUnsafe {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str("ExtremelyUnsafe")
+    }
+}
+
+/// An opaque structure that PAM uses to communicate.
+///
+/// This is only ever returned in pointer form and cannot be constructed.
+#[repr(C)]
+pub struct pam_handle(ExtremelyUnsafe);
+
+impl fmt::Debug for pam_handle {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "PamHandle({self:p}")
+    }
+}
+
+/// An opaque structure that is passed through PAM in a conversation.
+#[repr(C)]
+pub struct AppData(ExtremelyUnsafe);
+
+impl fmt::Debug for AppData {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "AppData({self:p}")
+    }
+}
+
+/// Just an alias for the type of [`pam_conv::conv`].
+///
+/// For important details about the format of `messages`,
+/// see the [`helpers`](crate::helpers) module.
+///
+/// ```no_run
+/// # use libpam_sys::{ConversationCallback, pam_conv};
+/// fn convo() -> ConversationCallback {
+///     // ...
+/// #    unimplemented!()
+/// }
+/// let conv = pam_conv{conv: convo(), appdata_ptr: std::ptr::null_mut()};
+/// ```
+pub type ConversationCallback = unsafe extern "C" fn(
+    num_msg: c_int,
+    msg: *const *const pam_message,
+    resp: *mut *mut pam_response,
+    appdata: *mut AppData,
+) -> c_int;
+
+/// Alias for the callback to [`pam_set_data`](crate::pam_set_data).
+///
+/// ```no_run
+/// # use std::ffi::CString;
+/// use libpam_sys::{CleanupCallback, pam_set_data};
+/// # use libpam_sys::pam_handle;
+/// # let handle: *mut pam_handle = std::ptr::null_mut();
+/// # let mut my_data = 100;
+/// # let data_ptr = &mut my_data as *mut i32;
+/// fn cleanup() -> CleanupCallback {
+///     // ...
+/// #    unimplemented!()
+/// }
+/// let name = CString::new("name").unwrap();
+/// unsafe {
+///     pam_set_data(handle, name.as_ptr().cast_mut(), data_ptr.cast(), cleanup());
+/// }
+/// ```
+pub type CleanupCallback = unsafe extern "C" fn(
+    pamh: *mut pam_handle,
+    data: *mut c_void,
+    pam_end_status: c_int,
+);
+
+/// Used by PAM to communicate between the module and the application.
+#[repr(C)]
+pub struct pam_conv {
+    pub conv: unsafe extern "C" fn(
+        num_msg: c_int,
+        msg: *const *const pam_message,
+        resp: *mut *mut pam_response,
+        appdata: *mut AppData,
+    ) -> c_int,
+    pub appdata_ptr: *mut AppData,
+}
+
+/// A message sent into a PAM conversation.
+#[repr(C)]
+pub struct pam_message {
+    pub msg_style: c_int,
+    pub msg: *const c_char,
+}
+
+/// A response returned from a PAM conversation.
+#[repr(C)]
+pub struct pam_response {
+    pub resp: *mut c_char,
+    /// Completely unused.
+    pub resp_retcode: c_int,
+}
+
+
+// These are the functions specified in X/SSO. Everybody exports them.
+extern "C" {
+    /// Account validation.
+    pub fn pam_acct_mgmt(pamh: *mut pam_handle, flags: c_int) -> c_int;
+
+    /// Authenticate a user.
+    pub fn pam_authenticate(pamh: *mut pam_handle, flags: c_int) -> c_int;
+
+    // Nobody implements pam_authenticate_secondary.
+
+    /// Manage authentication tokens.
+    pub fn pam_chauthtok(pamh: *mut pam_handle, flags: c_int) -> c_int;
+
+    /// Close an opened user session.
+    pub fn pam_close_session(pamh: *mut pam_handle, flags: c_int) -> c_int;
+
+    /// Ends the PAM transaction.
+    pub fn pam_end(pamh: *mut pam_handle, flags: c_int) -> c_int;
+
+    /// Gets module-specific data. PAM still owns the data.
+    pub fn pam_get_data(
+        pamh: *mut pam_handle,
+        module_data_name: *const c_char,
+        data: *mut *const c_void,
+    ) -> c_int;
+
+    /// Gets an environment variable.  You own the return value.
+    pub fn pam_getenv(pamh: *mut pam_handle, name: *const c_char) -> *mut c_char;
+
+    /// Gets all the environment variables.  You own everything it points to.
+    pub fn pam_getenvlist(pamh: *mut pam_handle) -> *mut *mut c_char;
+
+    /// Get information about the transaction.
+    ///
+    /// The item is owned by PAM.
+    pub fn pam_get_item(
+        pamh: *mut pam_handle,
+        item_type: c_int,
+        item: *mut *const c_void,
+    ) -> c_int;
+
+    // Nobody implements pam_get_mapped_authtok.
+    // Nobody implements pam_get_mapped_username.
+
+    /// Get the username. PAM owns it.
+    pub fn pam_get_user(
+        pamh: *mut pam_handle,
+        user: *mut *const c_char,
+        prompt: *const c_char,
+    ) -> c_int;
+
+    /// Opens a user session.
+    pub fn pam_open_session(pamh: *mut pam_handle, flags: c_int) -> c_int;
+
+    /// Sets the value of an environment variable. `namevalue` is copied.
+    pub fn pam_putenv(pamh: *mut pam_handle, namevalue: *const c_char) -> c_int;
+
+    /// Update or delete user credentials.
+    pub fn pam_setcred(pamh: *mut pam_handle, flags: c_int) -> c_int;
+
+    /// Set module-specific data. PAM will call `cleanup` when completed.
+    pub fn pam_set_data(
+        pamh: *mut pam_handle,
+        module_data_name: *const c_char,
+        data: *mut c_void,
+        cleanup: unsafe extern "C" fn(
+            pamh: *mut pam_handle,
+            data: *mut c_void,
+            pam_end_status: c_int,
+        ),
+    ) -> c_int;
+
+    /// Set information about the transaction.  The `item` is copied.
+    pub fn pam_set_item(pamh: *mut pam_handle, item_type: c_int, item: *const c_void) -> c_int;
+
+    // Nobody implements pam_set_mapped_authtok.
+    // Nobody implements pam_set_mapped_username.
+
+    // The pam_sm_whatever functions are prototypes for the functions that
+    // a PAM module should implement, not symbols provided by PAM.
+
+    // Nobody implements pam_authenticate_secondary.
+
+    /// Starts a PAM transaction.  The `conv` may or may not be copied.
+    pub fn pam_start(
+        service: *const c_char,
+        user: *const c_char,
+        pam_conv: *mut pam_conv,
+        pamh: *mut *mut pam_handle,
+    ) -> c_int;
+
+    /// Gets a statically-allocated error string.
+    ///
+    /// All implementations of PAM known to this library (Linux-PAM, OpenPAM,
+    /// and Sun) ignore `pamh` and will accept a null pointer.
+    pub fn pam_strerror(pamh: *const pam_handle, error_number: c_int) -> *mut c_char;
+}
+
+// We use `_private_pam_impl_hack` because ctest loses its mind
+// when it comes across the `cfg_pam_impl` macro.
+// This is a custom cfg variable set in our build.rs. Don't do this; just use
+// cfg_pam_impl.
+#[cfg(_private_pam_impl_hack = "LinuxPam")]
+extern "C" {
+    pub fn pam_get_authtok(pamh: *mut pam_handle, x: c_int, token: *mut *const c_char, prompt: *const c_char) -> c_int;
+}
+
+
+// int (*)(struct pam_handle *, char *, void *, void (*)(struct pam_handle *, void *, int))
+// int (*)(struct pam_handle *, char *, void *, int (*)(struct pam_handle *, void *, int))
\ No newline at end of file
--- a/libpam-sys/src/funcs/xsso_base.rs	Mon Jun 30 21:52:10 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,97 +0,0 @@
-//! Only the very base functions described in the X/SSO specification.
-
-use crate::pam_conv;
-use crate::structs::{pam_handle_t, CleanupCallback};
-use std::ffi::{c_char, c_int, c_void};
-
-extern "C" {
-    /// Account validation.
-    pub fn pam_acct_mgmt(pamh: *mut pam_handle_t, flags: c_int) -> c_int;
-
-    /// Authenticate a user.
-    pub fn pam_authenticate(pamh: *mut pam_handle_t, flags: c_int) -> c_int;
-
-    // Nobody implements pam_authenticate_secondary.
-
-    /// Manage authentication tokens.
-    pub fn pam_chauthtok(pamh: *mut pam_handle_t, flags: c_int) -> c_int;
-
-    /// Close an opened user session.
-    pub fn pam_close_session(pamh: *mut pam_handle_t, flags: c_int) -> c_int;
-
-    /// Ends the PAM transaction.
-    pub fn pam_end(pamh: *mut pam_handle_t, flags: c_int) -> c_int;
-
-    /// Gets module-specific data. PAM still owns the data.
-    pub fn pam_get_data(
-        pamh: *mut pam_handle_t,
-        module_data_name: *const c_char,
-        data: &mut *const c_void,
-    ) -> c_int;
-
-    /// Gets an environment variable.  You own the return value.
-    pub fn pam_getenv(pamh: *mut pam_handle_t, name: *const c_char) -> *mut c_char;
-
-    /// Gets all the environment variables.  You own everything it points to.
-    pub fn pam_getenvlist(pamh: *mut pam_handle_t) -> *mut *mut c_char;
-
-    /// Get information about the transaction.
-    pub fn pam_get_item(
-        pamh: *mut pam_handle_t,
-        item_type: c_int,
-        item: &mut *const c_void,
-    ) -> c_int;
-
-    // Nobody implements pam_get_mapped_authtok.
-    // Nobody implements pam_get_mapped_username.
-
-    /// Get the username.
-    pub fn pam_get_user(
-        pamh: *mut pam_handle_t,
-        user: &mut *const c_char,
-        prompt: *const c_char,
-    ) -> c_int;
-
-    /// Opens a user session.
-    pub fn pam_open_session(pamh: *mut pam_handle_t, flags: c_int) -> c_int;
-
-    /// Sets the value of an environment variable. `namevalue` is copied.
-    pub fn pam_putenv(pamh: *mut pam_handle_t, namevalue: *const c_char) -> c_int;
-
-    /// Update or delete user credentials.
-    pub fn pam_setcred(pamh: *mut pam_handle_t, flags: c_int) -> c_int;
-
-    /// Set module-specific data.
-    pub fn pam_set_data(
-        pamh: *mut pam_handle_t,
-        module_data_name: *const c_char,
-        data: *mut c_void,
-        cleanup: CleanupCallback,
-    ) -> c_int;
-
-    /// Set information about the transaction.  The `item` is copied.
-    pub fn pam_set_item(pamh: *mut pam_handle_t, item_type: c_int, item: *const c_void) -> c_int;
-
-    // Nobody implements pam_set_mapped_authtok.
-    // Nobody implements pam_set_mapped_username.
-
-    // The pam_sm_whatever functions are prototypes for the functions that
-    // a PAM module should implement, not symbols provided by PAM.
-
-    // Nobody implements pam_authenticate_secondary.
-
-    /// Starts a PAM transaction.  The `conv` may or may not be copied.
-    pub fn pam_start(
-        service: *const c_char,
-        user: *const c_char,
-        pam_conv: *mut pam_conv,
-        pamh: &mut *mut pam_handle_t,
-    );
-
-    /// Gets a statically-allocated error string.
-    ///
-    /// All implementations of PAM known to this library (Linux-PAM, OpenPAM,
-    /// and Sun) ignore `pamh` and will accept a null pointer.
-    pub fn pam_strerror(pamh: *const pam_handle_t, error_number: c_int) -> *const c_char;
-
-}
--- a/libpam-sys/src/helpers.rs	Mon Jun 30 21:52:10 2025 -0400
+++ b/libpam-sys/src/helpers.rs	Mon Jun 30 22:56:26 2025 -0400
@@ -1,6 +1,7 @@
 //! This module contains a few non-required helpers to deal with some of the
 //! more annoying memory management in the PAM API.
 
+use super::cfg_pam_impl;
 use std::error::Error;
 use std::marker::{PhantomData, PhantomPinned};
 use std::mem::ManuallyDrop;
@@ -132,7 +133,7 @@
         slice::from_raw_parts(*ptr_ptr.cast(), count).iter()
     }
 
-    #[crate::cfg_pam_impl("LinuxPam")]
+    #[cfg_pam_impl("LinuxPam")]
     unsafe fn _iter_over<'a, Src>(
         ptr_ptr: *const *const Src,
         count: usize,
@@ -144,7 +145,7 @@
         Self::iter_over_linux(ptr_ptr, count)
     }
 
-    #[crate::cfg_pam_impl(not("LinuxPam"))]
+    #[cfg_pam_impl(not("LinuxPam"))]
     unsafe fn _iter_over<'a, Src>(
         ptr_ptr: *const *const Src,
         count: usize,
@@ -478,7 +479,7 @@
     #[should_panic]
     fn test_iter_xsso_wrong_size() {
         unsafe {
-            _ = PtrPtrVec::<u8>::iter_over_xsso::<f64>(ptr::null(), 1);
+            let _ = PtrPtrVec::<u8>::iter_over_xsso::<f64>(ptr::null(), 1);
         }
     }
 
@@ -488,7 +489,7 @@
     #[should_panic]
     fn test_iter_linux_wrong_size() {
         unsafe {
-            _ = PtrPtrVec::<u128>::iter_over_linux::<()>(ptr::null(), 1);
+            let _ = PtrPtrVec::<u128>::iter_over_linux::<()>(ptr::null(), 1);
         }
     }
 
--- a/libpam-sys/src/lib.rs	Mon Jun 30 21:52:10 2025 -0400
+++ b/libpam-sys/src/lib.rs	Mon Jun 30 22:56:26 2025 -0400
@@ -44,15 +44,11 @@
 pub use libpam_sys_impls::cfg_pam_impl;
 
 mod constants;
-// We get `funcs` from different places depending upon the PAM implementation.
-// This is because
-#[path = "funcs/xsso_base.rs"]
-mod funcs;
+mod ffi;
 pub mod helpers;
-mod structs;
 
 #[doc(inline)]
-pub use crate::{constants::*, funcs::*, structs::*};
+pub use crate::{constants::*, ffi::*};
 
 // Looking for the actual code defining this enum?
 // It's in the build.rs file for libpam_sys_impls.
--- a/libpam-sys/src/structs.rs	Mon Jun 30 21:52:10 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-//! Structs and wrappers that PAM is made of.
-#![allow(non_camel_case_types)]
-
-use std::ffi::{c_int, c_void};
-use std::fmt;
-use std::marker::{PhantomData, PhantomPinned};
-
-/// A marker struct to make whatever it's in `!Sync`, `!Send`, and `!Unpin`.
-#[derive(Default, PartialOrd, PartialEq, Ord, Eq)]
-#[repr(C)]
-struct ExtremelyUnsafe {
-    _value: (),
-    _marker: PhantomData<(PhantomPinned, *mut c_void)>,
-}
-
-impl fmt::Debug for ExtremelyUnsafe {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.write_str("ExtremelyUnsafe")
-    }
-}
-
-/// An opaque structure that PAM uses to communicate.
-///
-/// This is only ever returned in pointer form and cannot be constructed.
-#[repr(C)]
-pub struct pam_handle_t(ExtremelyUnsafe);
-
-impl fmt::Debug for pam_handle_t {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "PamHandle({self:p}")
-    }
-}
-
-/// An opaque structure that is passed through PAM in a conversation.
-#[repr(C)]
-pub struct AppData(ExtremelyUnsafe);
-
-impl fmt::Debug for AppData {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "AppData({self:p}")
-    }
-}
-
-/// The callback that PAM uses to get information in a conversation.
-///
-/// For important details about the format of `messages`,
-/// see the [`helpers`](crate::helpers) module.
-pub type ConversationCallback = unsafe extern "C" fn(
-    num_msg: c_int,
-    // This is a *const *const because accessing memory from a reference
-    // outside its bounds is undefined behavior, and *messages is an array
-    // in X/SSO PAM impls.
-    msg: *const *const pam_message,
-    // This is a &mut *mut because the caller sets the pointer in `resp`
-    // but does not mess around outside its memory space.
-    resp: &mut *mut pam_response,
-    appdata: *const AppData,
-) -> c_int;
-
-/// Called to clean up data set using [`pam_set_data`](crate::pam_set_data).
-pub type CleanupCallback = unsafe extern "C" fn(
-    pamh: *mut pam_handle_t,
-    data: *mut c_void,
-    pam_end_status: c_int,
-) -> c_int;
-
-/// Used by PAM to communicate between the module and the application.
-#[repr(C)]
-pub struct pam_conv {
-    pub conv: ConversationCallback,
-    pub appdata_ptr: *const AppData,
-}
-
-/// A message sent into a PAM conversation.
-#[repr(C)]
-pub struct pam_message {
-    pub msg_style: c_int,
-    pub msg: *const c_void,
-}
-
-/// A response returned from a PAM conversation.
-#[repr(C)]
-pub struct pam_response {
-    pub resp: *mut c_void,
-    /// Completely unused.
-    pub resp_retcode: c_int,
-}
--- a/testharness/src/lib.rs	Mon Jun 30 21:52:10 2025 -0400
+++ b/testharness/src/lib.rs	Mon Jun 30 22:56:26 2025 -0400
@@ -11,7 +11,11 @@
         Ok(())
     }
 
-    fn account_management(_handle: &mut M, _args: Vec<&CStr>, _flags: Flags) -> nonstick::Result<()> {
+    fn account_management(
+        _handle: &mut M,
+        _args: Vec<&CStr>,
+        _flags: Flags,
+    ) -> nonstick::Result<()> {
         Ok(())
     }
 }