view libpam-sys/build.rs @ 107:49c6633f6fd2

Add Cargo.lock file. This ensures that we get reproducible builds, particularly because we want to support Rust v1.75.
author Paul Fisher <paul@pfish.zone>
date Thu, 26 Jun 2025 22:42:32 -0400
parents 49d9e2b5c189
children e97534be35e3
line wrap: on
line source

use bindgen::MacroTypeVariation;
use std::error::Error;
use std::fmt::{Debug, Display, Formatter};
use std::path::PathBuf;
use std::{env, fs};

enum PamImpl {
    Illumos,
    LinuxPam,
    OpenPam,
}

#[derive(Debug)]
struct InvalidEnum(String);

impl Display for InvalidEnum {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "invalid PAM impl {:?}", self.0)
    }
}

impl Error for InvalidEnum {}

impl TryFrom<&str> for PamImpl {
    type Error = InvalidEnum;
    fn try_from(value: &str) -> Result<Self, Self::Error> {
        Ok(match value {
            "illumos" => Self::Illumos,
            "linux-pam" => Self::LinuxPam,
            "openpam" => Self::OpenPam,
            other => return Err(InvalidEnum(other.to_owned())),
        })
    }
}

impl Debug for PamImpl {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Debug::fmt(
            match self {
                Self::Illumos => "illumos",
                Self::LinuxPam => "linux-pam",
                Self::OpenPam => "openpam",
            },
            f,
        )
    }
}

fn main() {
    println!("cargo:rustc-link-lib=pam");
    let out_file = PathBuf::from(env::var("OUT_DIR").unwrap()).join("constants.rs");
    let pam_impl = do_detection();

    if cfg!(feature = "use-system-headers") {
        let builder = bindgen::Builder::default()
            .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
            .blocklist_function(".*")
            .blocklist_type(".*")
            .allowlist_var(".*")
            .default_macro_constant_type(MacroTypeVariation::Unsigned);

        let builder = match pam_impl {
            PamImpl::Illumos => builder.header_contents(
                "illumos.h",
                "\
                        #include <security/pam_appl.h>
                        #include <security/pam_modules.h>
                    ",
            ),
            PamImpl::LinuxPam => builder.header_contents(
                "linux-pam.h",
                "\
                        #include <security/_pam_types.h>
                        #include <security/pam_appl.h>
                        #include <security/pam_ext.h>
                        #include <security/pam_modules.h>
                    ",
            ),
            PamImpl::OpenPam => builder.header_contents(
                "openpam.h",
                "\
                        #include <security/pam_types.h>
                        #include <security/openpam.h>
                        #include <security/pam_appl.h>
                        #include <security/pam_constants.h>
                    ",
            ),
        };
        let bindings = builder.generate().unwrap();
        bindings.write_to_file(out_file).unwrap();
    } else {
        // Just write empty data to the file to avoid conditional compilation
        // shenanigans.
        fs::write(out_file, "").unwrap();
    }
}

fn do_detection() -> PamImpl {
    println!(r#"cargo:rustc-check-cfg=cfg(pam_impl, values("illumos", "linux-pam", "openpam"))"#);
    let pam_impl = _detect_internal();
    println!("cargo:rustc-cfg=pam_impl={pam_impl:?}");
    pam_impl
}

fn _detect_internal() -> PamImpl {
    if let Some(pam_impl) = option_env!("LIBPAMSYS_PAM_IMPL") {
        pam_impl.try_into().unwrap()
    } else if cfg!(feature = "use-system-headers") {
        // Detect which impl it is from system headers.
        if header_exists("security/_pam_types.h") {
            PamImpl::LinuxPam
        } else if header_exists("security/openpam.h") {
            PamImpl::OpenPam
        } else if header_exists("security/pam_appl.h") {
            PamImpl::Illumos
        } else {
            panic!("could not detect PAM implementation")
        }
    } else {
        // Otherwise, guess what PAM impl we're using based on the OS.
        if cfg!(target_os = "linux") {
            PamImpl::LinuxPam
        } else if cfg!(any(
            target_os = "macos",
            target_os = "freebsd",
            target_os = "netbsd",
            target_os = "dragonfly",
            target_os = "openbsd"
        )) {
            PamImpl::OpenPam
        } else {
            PamImpl::Illumos
        }
    }
}

fn header_exists(header: &str) -> bool {
    bindgen::Builder::default()
        .blocklist_item(".*")
        .header_contents("header.h", &format!("#include <{header}>"))
        .generate()
        .is_ok()
}