comparison libpam-sys/libpam-sys-test/build.rs @ 131:a632a8874131

Get all the Linux-PAM functions into libpam-sys, and get tests right.
author Paul Fisher <paul@pfish.zone>
date Wed, 02 Jul 2025 02:24:21 -0400
parents 80c07e5ab22f
children 0b6a17f8c894
comparison
equal deleted inserted replaced
130:80c07e5ab22f 131:a632a8874131
4 use quote::{format_ident, ToTokens}; 4 use quote::{format_ident, ToTokens};
5 use std::path::Path; 5 use std::path::Path;
6 use std::process::Command; 6 use std::process::Command;
7 use std::str::FromStr; 7 use std::str::FromStr;
8 use std::{env, fs}; 8 use std::{env, fs};
9 use syn::{Item, ItemConst, Type, TypePath}; 9 use syn::{Item, ItemConst};
10 10
11 // We're using the macro directly so we can match exhaustively. 11 // We're using the macro directly so we can match exhaustively.
12 __pam_impl_enum__!(); 12 __pam_impl_enum__!();
13
14 const REDIR_FD: &str = "pam_modutil_redirect_fd";
13 15
14 fn main() { 16 fn main() {
15 let config = match PamImpl::CURRENT { 17 let config = match PamImpl::CURRENT {
16 PamImpl::LinuxPam => TestConfig { 18 PamImpl::LinuxPam => TestConfig {
17 headers: vec![ 19 headers: vec![
18 "<security/_pam_types.h>", 20 "<security/_pam_types.h>",
19 "<security/pam_appl.h>", 21 "<security/pam_appl.h>",
20 "<security/pam_ext.h>", 22 "<security/pam_ext.h>",
21 "<security/pam_modules.h>", 23 "<security/pam_modules.h>",
24 "<security/pam_modutil.h>",
22 ], 25 ],
26 allow_types: vec![REDIR_FD],
23 ignore_consts: vec![ 27 ignore_consts: vec![
24 "__LINUX_PAM__", 28 "__LINUX_PAM__",
25 "__LINUX_PAM_MINOR__", 29 "__LINUX_PAM_MINOR__",
26 "PAM_AUTHTOK_RECOVER_ERR", 30 "PAM_AUTHTOK_RECOVER_ERR",
27 ], 31 ],
37 ignore_consts: vec!["OPENPAM_VERSION", "OPENPAM_RELEASE", "PAM_SOEXT"], 41 ignore_consts: vec!["OPENPAM_VERSION", "OPENPAM_RELEASE", "PAM_SOEXT"],
38 ..Default::default() 42 ..Default::default()
39 }, 43 },
40 PamImpl::Sun => TestConfig { 44 PamImpl::Sun => TestConfig {
41 headers: vec!["<security/pam_appl.h>", "<security/pam_modules.h>"], 45 headers: vec!["<security/pam_appl.h>", "<security/pam_modules.h>"],
42 block_headers: vec!["sys/.*"],
43 ..Default::default() 46 ..Default::default()
44 }, 47 },
45 PamImpl::XSso => TestConfig { 48 PamImpl::XSso => TestConfig {
46 headers: vec!["\"xsso_pam_appl.h\""], 49 headers: vec!["\"xsso_pam_appl.h\""],
47 ignore_consts: vec!["PAM_CRED_PRELIM_CHECK"],
48 ..Default::default() 50 ..Default::default()
49 }, 51 },
50 }; 52 };
51 generate_const_test(&config); 53 generate_const_test(&config);
52 generate_ctest(&config); 54 generate_ctest(&config);
55 fn generate_const_test(config: &TestConfig) { 57 fn generate_const_test(config: &TestConfig) {
56 let mut builder = bindgen::Builder::default() 58 let mut builder = bindgen::Builder::default()
57 .header_contents("_.h", &config.header_contents()) 59 .header_contents("_.h", &config.header_contents())
58 .merge_extern_blocks(true) 60 .merge_extern_blocks(true)
59 .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) 61 .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
60 .blocklist_type(".*") 62 .allowlist_var("(OPEN)?PAM_.*")
61 .blocklist_function(".*")
62 .allowlist_var(".*")
63 .default_macro_constant_type(MacroTypeVariation::Signed); 63 .default_macro_constant_type(MacroTypeVariation::Signed);
64 for hdr in config.block_headers.iter() { 64
65 builder = builder.blocklist_file(".*?/".to_owned() + hdr) 65 for &typ in config.allow_types.iter() {
66 } 66 builder = builder.allowlist_type(typ);
67 67 }
68 let generated = builder.generate().unwrap().to_string(); 68
69 let file = syn::parse_file(&generated).unwrap(); 69 let generated = builder.generate().unwrap();
70 generated.write_to_file(test_file("bindgen.rs")).unwrap();
71 let file = syn::parse_file(&generated.to_string()).unwrap();
70 let mut tests = vec![ 72 let mut tests = vec![
71 "use libpam_sys::*;".into(), 73 "\
72 "#[allow(deprecated, overflowing_literals)]".into(), 74 #[allow(dead_code, non_camel_case_types, non_upper_case_globals)]
73 "fn main() {".into(), 75 mod generated {
76 include!(\"bindgen.rs\");
77 }
78 #[allow(deprecated, overflowing_literals)]
79 fn main() {
80 "
81 .into(),
74 format!( 82 format!(
75 "assert_eq!(PamImpl::CURRENT, PamImpl::{:?});", 83 "assert_eq!(libpam_sys::PamImpl::CURRENT, libpam_sys::PamImpl::{:?});",
76 PamImpl::CURRENT 84 PamImpl::CURRENT
77 ), 85 ),
78 ]; 86 ];
79 tests.extend( 87 tests.extend(
80 file.items 88 file.items
84 Some(item) 92 Some(item)
85 } else { 93 } else {
86 None 94 None
87 } 95 }
88 }) 96 })
89 .filter(|item| config.should_check_const(item)) 97 .filter(|&item| config.should_check_const(item))
90 .cloned() 98 .map(|item| {
91 .map(|mut item| { 99 let name = item.ident.to_string();
92 item.ty = Box::new(Type::Path(TypePath { 100 if let Some(stripped) = name.strip_prefix(&format!("{REDIR_FD}_")) {
93 qself: None, 101 format!("assert_eq!(generated::{name} as i32, libpam_sys::{REDIR_FD}::{stripped}.into());")
94 path: format_ident!("i32").into(), 102 } else {
95 })); 103 format!("assert_eq!(generated::{name}, libpam_sys::{name});")
96 format!( 104 }
97 "assert_eq!({tokens}, {name});",
98 tokens = item.expr.to_token_stream(),
99 name = item.ident
100 )
101 }), 105 }),
102 ); 106 );
103 tests.push("}".into()); 107 tests.push("}".into());
104 let const_test = test_file("constant_test.rs"); 108 let const_test = test_file("constant_test.rs");
105 fs::write(&const_test, tests.join("\n")).unwrap(); 109 fs::write(&const_test, tests.join("\n")).unwrap();
106 rustfmt(&const_test); 110 rustfmt(&const_test);
107 } 111 }
108 112
109 fn generate_ctest(config: &TestConfig) { 113 fn generate_ctest(config: &TestConfig) {
110 let mut test = ctest::TestGenerator::new(); 114 let mut test = ctest::TestGenerator::new();
111 115 test.cfg("_hack_impl", Some(&format!("{:?}", PamImpl::CURRENT)));
112 for header in config.headers.iter() { 116
117 for &header in config.headers.iter() {
113 if header.starts_with('"') { 118 if header.starts_with('"') {
114 test.include(env::var("CARGO_MANIFEST_DIR").unwrap()); 119 test.include(env::var("CARGO_MANIFEST_DIR").unwrap());
115 } 120 }
116 test.header(&header[1..header.len() - 1]); 121 test.header(&header[1..header.len() - 1]);
117 } 122 }
118 // These are opaque structs. 123 // These are opaque structs.
119 test.skip_struct(|name| matches!(name, "pam_handle" | "AppData")); 124 test.skip_struct(|name| matches!(name, "pam_handle" | "AppData"));
120 test.skip_type(|name| matches!(name, "ConversationCallback" | "CleanupCallback")); 125 test.skip_type(|name| matches!(name, "ConversationCallback" | "CleanupCallback"));
121 test.type_name(|name, _is_struct, is_union| { 126 test.type_name(|name, is_struct, is_union| {
122 assert!(!is_union); // we scabbin' 127 assert!(!is_union); // we scabbin'
123 match name { 128 match (name, is_struct) {
124 "pam_handle" => "struct pam_handle", 129 ("AppData", _) => "void".into(),
125 "pam_conv" => "struct pam_conv", 130 (REDIR_FD, _) => format!("enum {REDIR_FD}"),
126 "pam_message" => "struct pam_message", 131 ("passwd", _) => "struct passwd".into(),
127 "pam_response" => "struct pam_response", 132 ("group", _) => "struct group".into(),
128 "AppData" => "void", 133 ("spwd", _) => "struct spwd".into(),
129 other => other, 134 (name, true) => format!("struct {name}"),
135 (other, false) => other.into(),
130 } 136 }
131 .into()
132 }); 137 });
133 138
134 // 139 //
135 // Welcome to THE HACK ZONE. 140 // Welcome to THE HACK ZONE.
136 // 141 //
184 } 189 }
185 190
186 #[derive(Default)] 191 #[derive(Default)]
187 struct TestConfig { 192 struct TestConfig {
188 headers: Vec<&'static str>, 193 headers: Vec<&'static str>,
189 block_headers: Vec<&'static str>, 194 allow_types: Vec<&'static str>,
190 ignore_consts: Vec<&'static str>, 195 ignore_consts: Vec<&'static str>,
191 } 196 }
192 197
193 impl TestConfig { 198 impl TestConfig {
194 fn header_contents(&self) -> String { 199 fn header_contents(&self) -> String {