changeset 116:a12706e42c9d default tip

Logging, macros, and building: - Changes logging API to accept the `Location` of the log statement. Fixes OpenPAM implementation. - Stops publicly exporting doc macros. - Uses dlopen to detect the PAM library rather than header jankery.
author Paul Fisher <paul@pfish.zone>
date Sun, 29 Jun 2025 18:27:51 -0400
parents 1e11a52b4665
children
files Cargo.lock build.rs libpam-sys/libpam-sys-impls/Cargo.toml libpam-sys/libpam-sys-impls/build.rs src/_doc.rs src/constants.rs src/handle.rs src/lib.rs src/libpam/handle.rs src/logging.rs
diffstat 10 files changed, 265 insertions(+), 151 deletions(-) [+]
line wrap: on
line diff
--- a/Cargo.lock	Sun Jun 29 03:35:59 2025 -0400
+++ b/Cargo.lock	Sun Jun 29 18:27:51 2025 -0400
@@ -35,12 +35,12 @@
  "itertools",
  "log",
  "prettyplease",
- "proc-macro2",
- "quote",
+ "proc-macro2 1.0.95",
+ "quote 1.0.40",
  "regex",
  "rustc-hash",
  "shlex",
- "syn",
+ "syn 2.0.104",
 ]
 
 [[package]]
@@ -88,6 +88,29 @@
 ]
 
 [[package]]
+name = "dlopen"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71e80ad39f814a9abe68583cd50a2d45c8a67561c3361ab8da240587dda80937"
+dependencies = [
+ "dlopen_derive",
+ "lazy_static",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "dlopen_derive"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f236d9e1b1fbd81cea0f9cbdc8dcc7e8ebcd80e6659cd7cb2ad5f6c05946c581"
+dependencies = [
+ "libc",
+ "quote 0.6.13",
+ "syn 0.15.44",
+]
+
+[[package]]
 name = "either"
 version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -143,6 +166,12 @@
 checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
 
 [[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
 name = "libc"
 version = "0.2.174"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -170,10 +199,11 @@
 version = "0.0.1"
 dependencies = [
  "bindgen",
- "proc-macro2",
- "quote",
+ "dlopen",
+ "proc-macro2 1.0.95",
+ "quote 1.0.40",
  "strum",
- "syn",
+ "syn 2.0.104",
 ]
 
 [[package]]
@@ -183,8 +213,8 @@
  "bindgen",
  "libpam-sys",
  "libpam-sys-impls",
- "quote",
- "syn",
+ "quote 1.0.40",
+ "syn 2.0.104",
 ]
 
 [[package]]
@@ -264,9 +294,9 @@
 checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d"
 dependencies = [
  "proc-macro-crate",
- "proc-macro2",
- "quote",
- "syn",
+ "proc-macro2 1.0.95",
+ "quote 1.0.40",
+ "syn 2.0.104",
 ]
 
 [[package]]
@@ -275,8 +305,8 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a"
 dependencies = [
- "proc-macro2",
- "syn",
+ "proc-macro2 1.0.95",
+ "syn 2.0.104",
 ]
 
 [[package]]
@@ -290,6 +320,15 @@
 
 [[package]]
 name = "proc-macro2"
+version = "0.4.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "proc-macro2"
 version = "1.0.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
@@ -299,11 +338,20 @@
 
 [[package]]
 name = "quote"
+version = "0.6.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
+dependencies = [
+ "proc-macro2 0.4.30",
+]
+
+[[package]]
+name = "quote"
 version = "1.0.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
 dependencies = [
- "proc-macro2",
+ "proc-macro2 1.0.95",
 ]
 
 [[package]]
@@ -384,9 +432,9 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
 dependencies = [
- "proc-macro2",
- "quote",
- "syn",
+ "proc-macro2 1.0.95",
+ "quote 1.0.40",
+ "syn 2.0.104",
 ]
 
 [[package]]
@@ -423,10 +471,21 @@
 checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
 dependencies = [
  "heck",
- "proc-macro2",
- "quote",
+ "proc-macro2 1.0.95",
+ "quote 1.0.40",
  "rustversion",
- "syn",
+ "syn 2.0.104",
+]
+
+[[package]]
+name = "syn"
+version = "0.15.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
+dependencies = [
+ "proc-macro2 0.4.30",
+ "quote 0.6.13",
+ "unicode-xid",
 ]
 
 [[package]]
@@ -435,8 +494,8 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
 dependencies = [
- "proc-macro2",
- "quote",
+ "proc-macro2 1.0.95",
+ "quote 1.0.40",
  "unicode-ident",
 ]
 
@@ -467,9 +526,9 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
 dependencies = [
- "proc-macro2",
- "quote",
- "syn",
+ "proc-macro2 1.0.95",
+ "quote 1.0.40",
+ "syn 2.0.104",
 ]
 
 [[package]]
@@ -505,6 +564,34 @@
 checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
 
 [[package]]
+name = "unicode-xid"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
 name = "windows-targets"
 version = "0.53.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
--- a/build.rs	Sun Jun 29 03:35:59 2025 -0400
+++ b/build.rs	Sun Jun 29 18:27:51 2025 -0400
@@ -43,7 +43,7 @@
             // This function is not available in Linux-PAM.
             // That means if somebody tries to run a binary compiled for
             // OpenPAM against a different impl, it will fail.
-            .allowlist_function("openpam_log")
+            .allowlist_function("_openpam_log")
             .header_contents(
                 "openpam.h",
                 r#"
--- a/libpam-sys/libpam-sys-impls/Cargo.toml	Sun Jun 29 03:35:59 2025 -0400
+++ b/libpam-sys/libpam-sys-impls/Cargo.toml	Sun Jun 29 18:27:51 2025 -0400
@@ -11,6 +11,7 @@
 
 [build-dependencies]
 bindgen = "0.72.0"
+dlopen = "0.1.8"
 proc-macro2 = "1.0.95"
 quote = "1.0.40"
 strum = { version = "0.27.1", features = ["derive"] }
--- a/libpam-sys/libpam-sys-impls/build.rs	Sun Jun 29 03:35:59 2025 -0400
+++ b/libpam-sys/libpam-sys-impls/build.rs	Sun Jun 29 18:27:51 2025 -0400
@@ -9,7 +9,9 @@
 use proc_macro2::TokenStream;
 use quote::quote;
 use std::{env, fs};
+use std::ffi::c_void;
 use strum::EnumString;
+use dlopen::raw::Library;
 
 fn main() {
     let pam_impl = match option_env!("LIBPAMSYS_IMPL") {
@@ -32,10 +34,11 @@
             }
         }
         Some("_detect") => {
-            // Detect which impl it is from system headers.
-            if header_exists("security/_pam_types.h") {
+            // Detect what library we're using based on the symbols.
+            let lib = Library::open("libpam.so").unwrap();
+            if symbol_exists(&lib, "pam_syslog") {
                 PamImpl::LinuxPam
-            } else if header_exists("security/openpam.h") {
+            } else if symbol_exists(&lib, "_openpam_log") {
                 PamImpl::OpenPam
             } else if header_exists("security/pam_appl.h") {
                 // We figure we're *probably* on a Sun derivative.
@@ -58,6 +61,10 @@
     println!("cargo:rustc-env=LIBPAMSYS_IMPL={pam_impl:?}");
 }
 
+fn symbol_exists(lib: &Library, symbol: &str) -> bool {
+    unsafe { lib.symbol::<*mut c_void>(symbol) }.is_ok()
+}
+
 /// This defines a local enum with an `enum_tokens()` method that can spit out
 /// its own contents.
 macro_rules! self_aware_enum {
--- a/src/_doc.rs	Sun Jun 29 03:35:59 2025 -0400
+++ b/src/_doc.rs	Sun Jun 29 18:27:51 2025 -0400
@@ -6,51 +6,49 @@
 ///
 /// # Examples
 ///
-/// ```
-/// # use nonstick::{_linklist, _stdlinks};
+/// ```ignore
+/// # use nonstick::{linklist, stdlinks};
 /// /// Here is a list of links:
 /// ///
-/// #[doc = _linklist!(pam_get_authtok: man7, manbsd)]
+/// #[doc = linklist!(pam_get_authtok: man7, manbsd)]
 /// ///
 /// /// The links are defined in the `stdlinks!` invocation below:
 /// ///
-/// #[doc = _stdlinks!(3 pam_get_authtok)]
+/// #[doc = stdlinks!(3 pam_get_authtok)]
 /// # fn do_whatever() {}
 /// ```
-#[macro_export]
-#[doc(hidden)]
-macro_rules! _linklist {
+macro_rules! linklist {
     ($func:ident: adg$(, $rest:ident)*) => {
         concat!(
             "- [Application Developers' Guide on `", stringify!($func), "`][adg]\n",
-            $crate::_linklist!($func: $($rest),*)
+            $crate::linklist!($func: $($rest),*)
         )
     };
     ($func:ident: mwg$(, $rest:ident)*) => {
         concat!(
             "- [Module Writers' Guide on `", stringify!($func), "`][mwg]\n",
-            $crate::_linklist!($func: $($rest),*)
+            $crate::linklist!($func: $($rest),*)
         )
     };
     ($func:ident: _std$(, $rest:ident)*) => {
-        $crate::_linklist!($func: man7, manbsd, xsso$(, $rest)*)
+        $crate::linklist!($func: man7, manbsd, xsso$(, $rest)*)
     };
     ($func:ident: man7$(, $rest:ident)*) => {
         concat!(
             "- [Linux-PAM manpage for `", stringify!($func), "`][man7]\n",
-            $crate::_linklist!($func: $($rest),*)
+            $crate::linklist!($func: $($rest),*)
         )
     };
     ($func:ident: manbsd$(, $rest:ident)*) => {
         concat!(
             "- [OpenPAM manpage for `", stringify!($func), "`][manbsd]\n",
-            $crate::_linklist!($func: $($rest),*)
+            $crate::linklist!($func: $($rest),*)
         )
     };
     ($func:ident: xsso$(, $rest:ident)*) => {
         concat!(
             "- [X/SSO spec for `", stringify!($func), "`][xsso]",
-            $crate::_linklist!($func: $($rest),*)
+            $crate::linklist!($func: $($rest),*)
         )
     };
     ($func:ident:$(,)?) => { "" };
@@ -60,16 +58,14 @@
 ///
 /// # Examples
 ///
-/// ```
-/// # use nonstick::{_guide};
+/// ```ignore
+/// # use nonstick::{guide};
 /// /// See [the guide][mwg].
 /// ///
-/// #[doc = _guide!(mwg: "mwg-expected-by-module-item.html#mwg-pam_get_user")]
+/// #[doc = guide!(mwg: "mwg-expected-by-module-item.html#mwg-pam_get_user")]
 /// # fn do_whatever() {}
 /// ```
-#[macro_export]
-#[doc(hidden)]
-macro_rules! _guide {
+macro_rules! guide {
     ($name:ident: $page_link:literal) => {
         concat!(
             "[",
@@ -84,24 +80,22 @@
 ///
 /// # Examples
 ///
-/// ```
-/// # use nonstick::_man7;
+/// ```ignore
+/// # use nonstick::man7;
 /// /// This contains a [link to the man page for malloc][man7].
-/// #[doc = _man7!(3 malloc)]
+/// #[doc = man7!(3 malloc)]
 /// # fn do_whatever() {}
 ///
 /// /// This contains both a link to the ["structure" section of `hgrc`][man7]
 /// /// and a link to the ["environment" section of `systemd`][sysd_env].
 /// ///
-/// #[doc = _man7!(5 hgrc "STRUCTURE")]
-/// #[doc = _man7!(sysd_env: 1 systemd "ENVIRONMENT")]
+/// #[doc = man7!(5 hgrc "STRUCTURE")]
+/// #[doc = man7!(sysd_env: 1 systemd "ENVIRONMENT")]
 /// # fn do_whatever2() {}
 /// ```
-#[macro_export]
-#[doc(hidden)]
-macro_rules! _man7 {
+macro_rules! man7 {
     ($n:literal $fn:ident $($anchor:literal)?) => {
-        $crate::_man7!(man7: $n $fn $($anchor)?)
+        $crate::man7!(man7: $n $fn $($anchor)?)
     };
     ($name:ident: $n:literal $fn:ident $($anchor:literal)?) => {
         concat!(
@@ -116,20 +110,18 @@
 ///
 /// # Examples
 ///
-/// ```
-/// # use nonstick::_manbsd;
+/// ```ignore
+/// # use nonstick::manbsd;
 /// // Both of these formulations create a link named `manbsd`.
-/// #[doc = _manbsd!(3 fn_name)]
-/// #[doc = _manbsd!(5 thing_name "SECTION")]
+/// #[doc = manbsd!(3 fn_name)]
+/// #[doc = manbsd!(5 thing_name "SECTION")]
 /// // This one creates a link named `link_name`.
-/// #[doc = _manbsd!(link_name: 1 prog_name "SECTION")]
+/// #[doc = manbsd!(link_name: 1 prog_name "SECTION")]
 /// # fn do_whatever() {}
 /// ```
-#[macro_export]
-#[doc(hidden)]
-macro_rules! _manbsd {
+macro_rules! manbsd {
     ($n:literal $func:ident $($anchor:literal)?) => {
-        $crate::_manbsd!(manbsd: $n $func $($anchor)?)
+        $crate::manbsd!(manbsd: $n $func $($anchor)?)
     };
     ($name:ident: $n:literal $func:ident $($anchor:literal)?) => {
         concat!("[", stringify!($name), "]: ",
@@ -143,25 +135,23 @@
 ///
 /// # Examples
 ///
-/// ```
-/// # use nonstick::_xsso;
+/// ```ignore
+/// # use nonstick::xsso;
 /// /// This docstring will [link to the X/SSO spec for `pam_set_item`][xsso].
 /// ///
-/// #[doc = _xsso!(pam_set_item)]
+/// #[doc = xsso!(pam_set_item)]
 /// # fn link_one() {}
 ///
 /// /// This docstring will link to [`some_page`][xsso].
 /// /// I can also link to [the table of contents][spec_toc].
 /// ///
-/// #[doc = _xsso!("some_page.htm#section-id")]
-/// #[doc = _xsso!(spec_toc: "toc.htm")]
+/// #[doc = xsso!("some_page.htm#section-id")]
+/// #[doc = xsso!(spec_toc: "toc.htm")]
 /// # fn do_whatever() {}
 /// ```
-#[macro_export]
-#[doc(hidden)]
-macro_rules! _xsso {
-    ($func:ident) => { $crate::_xsso!(xsso: concat!(stringify!($func), ".htm")) };
-    ($page:literal) => { $crate::_xsso!(xsso: $page) };
+macro_rules! xsso {
+    ($func:ident) => { $crate::xsso!(xsso: concat!(stringify!($func), ".htm")) };
+    ($page:literal) => { $crate::xsso!(xsso: $page) };
     ($name:ident: $page:expr) => {
         concat!("[", stringify!($name), "]: https://pubs.opengroup.org/onlinepubs/8329799/", $page)
     };
@@ -169,21 +159,21 @@
 
 /// Generates Markdown link references to Linux-PAM, OpenPAM, and X/SSO.
 ///
-/// A shortcut to `_man7!`, `_manbsd!`, and `_xsso!`.
+/// A shortcut to `man7!`, `manbsd!`, and `xsso!`.
 ///
 /// # Examples
 ///
-/// ```
-/// # use nonstick::_stdlinks;
+/// ```ignore
+/// # use nonstick::stdlinks;
 /// /// Check out [this][man7], [that][manbsd], or [the other][xsso].
 /// ///
-/// #[doc = _stdlinks!(3 pam_get_item)]
+/// #[doc = stdlinks!(3 pam_get_item)]
 /// # fn do_whatever() {}
 /// ```
-#[macro_export]
-#[doc(hidden)]
-macro_rules! _stdlinks {
+macro_rules! stdlinks {
     ($n:literal $func:ident) => {
-        concat!($crate::_man7!($n $func), "\n", $crate::_manbsd!($n $func), "\n", $crate::_xsso!($func))
+        concat!($crate::man7!($n $func), "\n", $crate::manbsd!($n $func), "\n", $crate::xsso!($func))
     };
 }
+
+pub(crate) use {linklist, guide, man7, manbsd, xsso, stdlinks};
\ No newline at end of file
--- a/src/constants.rs	Sun Jun 29 03:35:59 2025 -0400
+++ b/src/constants.rs	Sun Jun 29 18:27:51 2025 -0400
@@ -6,7 +6,7 @@
 
 #[cfg(feature = "link")]
 use crate::libpam::pam_ffi;
-use crate::{_linklist, _man7, _manbsd, _xsso};
+use crate::{linklist, man7, manbsd, xsso};
 use bitflags::bitflags;
 use libc::c_int;
 use num_enum::{IntoPrimitive, TryFromPrimitive};
@@ -154,12 +154,12 @@
 ///
 /// # References
 ///
-#[doc = _linklist!(pam: man7, manbsd)]
+#[doc = linklist!(pam: man7, manbsd)]
 /// - [X/SSO error code specification][xsso]
 ///
-#[doc = _man7!(3 pam "RETURN_VALUES")]
-#[doc = _manbsd!(3 pam "RETURN%20VALUES")]
-#[doc = _xsso!("chap5.htm#tagcjh_06_02")]
+#[doc = man7!(3 pam "RETURN_VALUES")]
+#[doc = manbsd!(3 pam "RETURN%20VALUES")]
+#[doc = xsso!("chap5.htm#tagcjh_06_02")]
 #[allow(non_camel_case_types, dead_code)]
 #[derive(Copy, Clone, Debug, PartialEq, TryFromPrimitive, IntoPrimitive)]
 #[non_exhaustive] // C might give us anything!
--- a/src/handle.rs	Sun Jun 29 03:35:59 2025 -0400
+++ b/src/handle.rs	Sun Jun 29 18:27:51 2025 -0400
@@ -3,8 +3,8 @@
 use crate::constants::{Flags, Result};
 use crate::conv::Conversation;
 use crate::environ::{EnvironMap, EnvironMapMut};
-use crate::logging::Level;
-use crate::{_guide, _linklist, _man7, _manbsd, _stdlinks};
+use crate::logging::{Level, Location};
+use crate::{guide, linklist, man7, manbsd, stdlinks};
 
 macro_rules! trait_item {
     ($(#[$md:meta])* get = $getter:ident, item = $item:literal $(, see = $see:path)?) => {
@@ -21,11 +21,11 @@
         ///
         /// # References
         ///
-        #[doc = _linklist!(pam_get_item: mwg, adg, _std)]
+        #[doc = linklist!(pam_get_item: mwg, adg, _std)]
         ///
-        #[doc = _guide!(adg: "adg-interface-by-app-expected.html#adg-pam_get_item")]
-        #[doc = _guide!(mwg: "mwg-expected-by-module-item.html#mwg-pam_get_item")]
-        #[doc = _stdlinks!(3 pam_get_item)]
+        #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_get_item")]
+        #[doc = guide!(mwg: "mwg-expected-by-module-item.html#mwg-pam_get_item")]
+        #[doc = stdlinks!(3 pam_get_item)]
         fn $getter(&self) -> Result<Option<String>>;
     };
     ($(#[$md:meta])* set = $setter:ident, item = $item:literal $(, see = $see:path)?) => {
@@ -42,11 +42,11 @@
         ///
         /// # References
         ///
-        #[doc = _linklist!(pam_set_item: mwg, adg, _std)]
+        #[doc = linklist!(pam_set_item: mwg, adg, _std)]
         ///
-        #[doc = _guide!(adg: "adg-interface-by-app-expected.html#adg-pam_set_item")]
-        #[doc = _guide!(mwg: "mwg-expected-by-module-item.html#mwg-pam_set_item")]
-        #[doc = _stdlinks!(3 pam_set_item)]
+        #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_set_item")]
+        #[doc = guide!(mwg: "mwg-expected-by-module-item.html#mwg-pam_set_item")]
+        #[doc = stdlinks!(3 pam_set_item)]
         fn $setter(&mut self, value: Option<&str>) -> Result<()>;
     };
 }
@@ -75,8 +75,9 @@
     /// # Example
     ///
     /// ```no_run
-    /// # use nonstick::{PamShared};
-    /// # use nonstick::logging::Level;
+    /// # use nonstick::PamShared;
+    /// use nonstick::logging::Level;
+    /// use nonstick::location;
     /// # fn _test(pam_hdl: impl PamShared) {
     /// # let delay_ms = 100;
     /// # let url = "https://zombo.com";
@@ -86,12 +87,12 @@
     /// nonstick::info!(pam_hdl, "using network backend");
     /// nonstick::debug!(pam_hdl, "sending GET request to {url}");
     /// // But if you really want to, you can call this yourself:
-    /// pam_hdl.log(Level::Warning, "this is unnecessarily verbose");
+    /// pam_hdl.log(Level::Warning, location!(), "this is unnecessarily verbose");
     /// # }
     /// ```
-    #[doc = _man7!(3 pam_syslog)]
-    #[doc = _manbsd!(3 openpam_log)]
-    fn log(&self, level: Level, entry: &str);
+    #[doc = man7!(3 pam_syslog)]
+    #[doc = manbsd!(3 openpam_log)]
+    fn log(&self, level: Level, loc: Location<'_>, entry: &str);
 
     /// Retrieves the name of the user who is authenticating or logging in.
     ///
@@ -103,7 +104,7 @@
     ///  3. The default prompt, `login: `.
     ///
     /// # References
-    #[doc = _linklist!(pam_get_user: mwg, _std)]
+    #[doc = linklist!(pam_get_user: mwg, _std)]
     ///
     /// # Example
     ///
@@ -119,8 +120,8 @@
     /// # Ok(())
     /// # }
     /// ```
-    #[doc = _stdlinks!(3 pam_get_user)]
-    #[doc = _guide!(mwg: "mwg-expected-by-module-item.html#mwg-pam_get_user")]
+    #[doc = stdlinks!(3 pam_get_user)]
+    #[doc = guide!(mwg: "mwg-expected-by-module-item.html#mwg-pam_get_user")]
     fn username(&mut self, prompt: Option<&str>) -> Result<String>;
 
     /// The contents of the environment to set, read-only.
@@ -276,10 +277,10 @@
     /// any PAM application call.
     ///
     /// # References
-    #[doc = _linklist!(pam_authenticate: adg, _std)]
+    #[doc = linklist!(pam_authenticate: adg, _std)]
     ///
-    #[doc = _guide!(adg: "adg-interface-by-app-expected.html#adg-pam_authenticate")]
-    #[doc = _stdlinks!(3 pam_authenticate)]
+    #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_authenticate")]
+    #[doc = stdlinks!(3 pam_authenticate)]
     fn authenticate(&mut self, flags: Flags) -> Result<()>;
 
     /// Verifies the validity of the user's account (and other stuff).
@@ -289,19 +290,19 @@
     /// for token expiration or that the user's account is not locked.
     ///
     /// # References
-    #[doc = _linklist!(pam_acct_mgmt: adg, _std)]
+    #[doc = linklist!(pam_acct_mgmt: adg, _std)]
     ///
-    #[doc = _guide!(adg: "adg-interface-by-app-expected.html#adg-pam_acct_mgmt")]
-    #[doc = _stdlinks!(3 pam_acct_mgmt)]
+    #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_acct_mgmt")]
+    #[doc = stdlinks!(3 pam_acct_mgmt)]
     fn account_management(&mut self, flags: Flags) -> Result<()>;
 
     /// Changes the authentication token.
     ///
     /// # References
-    #[doc = _linklist!(pam_chauthtok: adg, _std)]
+    #[doc = linklist!(pam_chauthtok: adg, _std)]
     ///
-    #[doc = _guide!(adg: "adg-interface-by-app-expected.html#adg-pam_chauthtok")]
-    #[doc = _stdlinks!(3 pam_chauthtok)]
+    #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_chauthtok")]
+    #[doc = stdlinks!(3 pam_chauthtok)]
     fn change_authtok(&mut self, flags: Flags) -> Result<()>;
 }
 
@@ -321,7 +322,7 @@
     ///
     /// # References
     ///
-    #[doc = _linklist!(pam_get_authtok: man7, manbsd)]
+    #[doc = linklist!(pam_get_authtok: man7, manbsd)]
     ///
     /// # Example
     ///
@@ -335,8 +336,8 @@
     /// Ok(())
     /// # }
     /// ```
-    #[doc = _man7!(3 pam_get_authtok)]
-    #[doc = _manbsd!(3 pam_get_authtok)]
+    #[doc = man7!(3 pam_get_authtok)]
+    #[doc = manbsd!(3 pam_get_authtok)]
     fn authtok(&mut self, prompt: Option<&str>) -> Result<String>;
 
     trait_item!(
--- a/src/lib.rs	Sun Jun 29 03:35:59 2025 -0400
+++ b/src/lib.rs	Sun Jun 29 18:27:51 2025 -0400
@@ -32,6 +32,7 @@
 pub mod handle;
 
 mod _doc;
+pub(crate) use _doc::*;
 mod environ;
 #[cfg(feature = "link")]
 mod libpam;
--- a/src/libpam/handle.rs	Sun Jun 29 03:35:59 2025 -0400
+++ b/src/libpam/handle.rs	Sun Jun 29 18:27:51 2025 -0400
@@ -6,10 +6,10 @@
 use crate::libpam::environ::{LibPamEnviron, LibPamEnvironMut};
 pub use crate::libpam::pam_ffi::LibPamHandle;
 use crate::libpam::{memory, pam_ffi};
-use crate::logging::Level;
+use crate::logging::{Level, Location};
 use crate::{
-    Conversation, EnvironMap, Flags, PamHandleApplication, PamHandleModule, _guide, _linklist,
-    _stdlinks,
+    Conversation, EnvironMap, Flags, PamHandleApplication, PamHandleModule, guide, linklist,
+    stdlinks,
 };
 use num_enum::{IntoPrimitive, TryFromPrimitive};
 use std::cell::Cell;
@@ -82,10 +82,10 @@
     /// named <code>/etc/pam.d/<var>service_name</var></code>.
     ///
     /// # References
-    #[doc = _linklist!(pam_start: adg, _std)]
+    #[doc = linklist!(pam_start: adg, _std)]
     ///
-    #[doc = _stdlinks!(3 pam_start)]
-    #[doc = _guide!(adg: "adg-interface-by-app-expected.html#adg-pam_start")]
+    #[doc = stdlinks!(3 pam_start)]
+    #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_start")]
     pub fn build_with_service(service_name: String) -> HandleBuilder {
         HandleBuilder {
             service_name,
@@ -161,10 +161,10 @@
     /// This internally calls `pam_end` with the appropriate error code.
     ///
     /// # References
-    #[doc = _linklist!(pam_end: adg, _std)]
+    #[doc = linklist!(pam_end: adg, _std)]
     ///
-    #[doc = _guide!(adg: "adg-interface-by-app-expected.html#adg-pam_end")]
-    #[doc = _stdlinks!(3 pam_end)]
+    #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_end")]
+    #[doc = stdlinks!(3 pam_end)]
     fn drop(&mut self) {
         unsafe {
             pam_ffi::pam_end(
@@ -190,13 +190,14 @@
 }
 
 impl PamShared for LibPamHandle {
-    fn log(&self, level: Level, entry: &str) {
+    fn log(&self, level: Level, loc: Location<'_>, entry: &str) {
         let entry = match CString::new(entry).or_else(|_| CString::new(dbg!(entry))) {
             Ok(cstr) => cstr,
             _ => return,
         };
         #[cfg(pam_impl = "linux-pam")]
         {
+            _ = loc;
             // SAFETY: We're calling this function with a known value.
             unsafe {
                 pam_ffi::pam_syslog(self, level as c_int, "%s\0".as_ptr().cast(), entry.as_ptr())
@@ -204,9 +205,10 @@
         }
         #[cfg(pam_impl = "openpam")]
         {
+            let func = CString::new(loc.function).unwrap_or(CString::default());
             // SAFETY: We're calling this function with a known value.
             unsafe {
-                pam_ffi::openpam_log(self, level as c_int, "%s\0".as_ptr().cast(), entry.as_ptr())
+                pam_ffi::_openpam_log(level as c_int, func.as_ptr(), "%s\0".as_ptr().cast(), entry.as_ptr())
             }
         }
     }
@@ -382,7 +384,7 @@
 }
 
 impl PamShared for OwnedLibPamHandle<'_> {
-    delegate!(fn log(&self, level: Level, entry: &str) -> ());
+    delegate!(fn log(&self, level: Level, location: Location<'_>, entry: &str) -> ());
     delegate!(fn environ(&self) -> impl EnvironMap);
     delegate!(fn environ_mut(&mut self) -> impl EnvironMapMut);
     delegate!(fn username(&mut self, prompt: Option<&str>) -> Result<String>);
--- a/src/logging.rs	Sun Jun 29 03:35:59 2025 -0400
+++ b/src/logging.rs	Sun Jun 29 18:27:51 2025 -0400
@@ -38,7 +38,7 @@
 ///
 /// Their values are ordered monotonically, either increasing or decreasing,
 /// depending upon the implementation.
-#[derive(Debug, PartialEq, Eq)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
 #[repr(i32)]
 pub enum Level {
     Error = levels::ERROR,
@@ -47,15 +47,52 @@
     Debug = levels::DEBUG,
 }
 
+/// The location of a log entry.
+#[derive(Clone, Copy, Debug, Default)]
+pub struct Location<'a> {
+    pub file: &'a str,
+    pub line: u32,
+    pub function: &'a str,
+    _more: (),
+}
+
+impl<'a> Location<'a> {
+    pub fn new(file: &'a str, line: u32, function: &'a str) -> Self {
+        Self {file, line, function, _more: ()}
+    }
+}
+
+/// The [`Location`] where this macro is inserted.
+#[doc(hidden)]
+#[macro_export]
+macro_rules! location {
+    () => { $crate::logging::Location::new(file!(), line!(), $crate::__function!()) }
+}
+
 /// Here's the guts of the logger thingy. You shouldn't be using this!
 #[doc(hidden)]
 #[macro_export]
 macro_rules! __log_internal {
     ($handle:expr, $level:ident, $($arg:tt)+) => {
-        $handle.log($crate::logging::Level::$level, &format!($($arg)+));
+        $handle.log($crate::logging::Level::$level, $crate::location!(), &format!($($arg)+));
     }
 }
 
+/// Ugly, hacky macro to get the current function name.
+///
+/// [Stolen from Stack Overflow.][https://stackoverflow.com/a/40234666/39808]
+#[doc(hidden)]
+#[macro_export]
+macro_rules! __function {
+    () => {{
+        fn p() {}
+        fn f<T>(_: T) -> &'static str {
+            ::std::any::type_name::<T>()
+        }
+        f(p).trim_end_matches("::p")
+    }}
+}
+
 /// Logs a message at error level via the given PAM handle.
 ///
 /// This supports `format!`-style formatting.
@@ -125,17 +162,12 @@
 /// # let userinfo_url = "https://zombo.com/";
 /// nonstick::debug!(pam_handle, "making HTTP GET request to {userinfo_url}");
 /// // Will log a message like
-/// // "pam_http/lib.rs:39:14: making HTTP GET request to https://zombo.com/"
+/// // making HTTP GET request to https://zombo.com/"
 /// // at DEBUG level on syslog.
 /// # }
 /// ```
 #[macro_export]
-macro_rules! debug {($handle:expr, $($arg:tt)+) => {
-    $crate::__log_internal!(
-        $handle, Debug,
-        "{}:{}:{}: {}", file!(), line!(), column!(), format_args!($($arg)+),
-    );
-}}
+macro_rules! debug { ($handle:expr, $($arg:tt)+) => { $crate::__log_internal!($handle, Debug, $($arg)+);}}
 
 #[cfg(test)]
 mod tests {
@@ -148,7 +180,7 @@
         struct Logger(RefCell<Vec<(Level, String)>>);
 
         impl Logger {
-            fn log(&self, level: Level, text: &str) {
+            fn log(&self, level: Level, loc: Location<'_>, text: &str) {
                 self.0.borrow_mut().push((level, text.to_owned()))
             }
         }
@@ -161,21 +193,14 @@
         info!(logger, "here is some info: {info}", info = "information");
         debug!(logger, "here is something: {something:?}");
 
-        let mut logged = logger.0.into_inner();
-
-        let (last_level, last_string) = logged.pop().unwrap();
-        assert_eq!(Level::Debug, last_level);
-        let expr = Regex::new(r"^[^:]+:\d+:\d+: here is something: Error$").unwrap();
-        assert!(
-            expr.is_match(&last_string),
-            "{last_string:?} did not match {expr:?}"
-        );
+        let logged = logger.0.into_inner();
 
         assert_eq!(
             vec![
                 (Level::Error, "here is another thing: 99".to_owned()),
                 (Level::Warning, "watch out!".to_owned()),
                 (Level::Info, "here is some info: information".to_owned()),
+                (Level::Debug, "here is something: Error".to_owned()),
             ],
             logged
         );