changeset 118:39760dfc9b3b

Detect PAM library based only on system lib; rename minimal lib to XSso. Also formats and assorted other cleanup.
author Paul Fisher <paul@pfish.zone>
date Sun, 29 Jun 2025 20:13:03 -0400
parents 20f7712a6857
children 476a22db8639
files libpam-sys/README.md libpam-sys/libpam-sys-impls/build.rs libpam-sys/libpam-sys-impls/src/lib.rs libpam-sys/libpam-sys-test/build.rs libpam-sys/src/constants.rs libpam-sys/src/functions.rs libpam-sys/src/helpers.rs libpam-sys/src/lib.rs libpam-sys/src/structs.rs src/_doc.rs src/libpam/handle.rs src/logging.rs
diffstat 12 files changed, 216 insertions(+), 138 deletions(-) [+]
line wrap: on
line diff
--- a/libpam-sys/README.md	Sun Jun 29 18:48:14 2025 -0400
+++ b/libpam-sys/README.md	Sun Jun 29 20:13:03 2025 -0400
@@ -9,10 +9,10 @@
 - Linux: `LinuxPam`
 - BSDs, including Mac OS: `OpenPam`
 - Illumos/Solaris: `Sun`
-- Unknown: `OpenPamMinimal`
+- Unknown: `XSso`
 
 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 Sun's implementation. 
+`XSso` exports only what is in the [X/SSO specification][xsso]. 
 
 ## References
 
--- a/libpam-sys/libpam-sys-impls/build.rs	Sun Jun 29 18:48:14 2025 -0400
+++ b/libpam-sys/libpam-sys-impls/build.rs	Sun Jun 29 20:13:03 2025 -0400
@@ -6,12 +6,14 @@
 //!  2. It detects the current PAM implementation and sets an env var for
 //!     the macros in `libpam-sys-impl`.
 
+use dlopen::raw::Library;
 use proc_macro2::TokenStream;
 use quote::quote;
+use std::ffi::c_void;
 use std::{env, fs};
-use std::ffi::c_void;
-use strum::EnumString;
-use dlopen::raw::Library;
+use strum::{EnumIter, EnumString, IntoEnumIterator};
+
+const DETECT: &str = "__detect__";
 
 fn main() {
     let pam_impl = match option_env!("LIBPAMSYS_IMPL") {
@@ -30,27 +32,31 @@
             } else if cfg!(any(target_os = "illumos", target_os = "solaris",)) {
                 PamImpl::Sun
             } else {
-                PamImpl::MinimalOpenPam
+                PamImpl::XSso
             }
         }
-        Some("_detect") => {
-            // Detect what library we're using based on the symbols.
+        Some(DETECT) => {
+            // Detect the library based on the symbols in libpam.so.
             let lib = Library::open("libpam.so").unwrap();
             if symbol_exists(&lib, "pam_syslog") {
                 PamImpl::LinuxPam
             } 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.
+            } else if symbol_exists(&lib, "__pam_get_authtok") {
                 PamImpl::Sun
             } else {
                 // If all else fails, assume the bare minimum.
-                PamImpl::MinimalOpenPam
+                PamImpl::XSso
             }
         }
         Some(other) => match PamImpl::try_from(other) {
             Ok(i) => i,
-            Err(_) => panic!("unknown PAM implementation {other:?}"),
+            Err(_) => {
+                let valid: Vec<_> = PamImpl::iter().collect();
+                panic!(
+                    "unknown PAM implementation {other:?}. valid LIBPAMSYS_IMPLs are {valid:?}, or use {DETECT:?}"
+                )
+            }
         },
     };
     fs::write(
@@ -95,7 +101,7 @@
 }
 
 self_aware_enum!(
-    #here[derive(EnumString, strum::Display)]
+    #here[derive(EnumString, strum::Display, EnumIter)]
     /// The PAM implementations supported by `libpam-sys`.
     #[derive(Clone, Copy, Debug, PartialEq)]
     PamImpl {
@@ -111,17 +117,9 @@
         ///
         /// [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.
+        /// Only the functionality and constants in [the PAM spec].
         ///
         /// [the PAM spec]: https://pubs.opengroup.org/onlinepubs/8329799/toc.htm
-        MinimalOpenPam,
+        XSso,
     }
 );
-
-fn header_exists(header: &str) -> bool {
-    bindgen::Builder::default()
-        .blocklist_item(".*")
-        .header_contents("header.h", &format!("#include <{header}>"))
-        .generate()
-        .is_ok()
-}
--- a/libpam-sys/libpam-sys-impls/src/lib.rs	Sun Jun 29 18:48:14 2025 -0400
+++ b/libpam-sys/libpam-sys-impls/src/lib.rs	Sun Jun 29 20:13:03 2025 -0400
@@ -253,7 +253,7 @@
             ("Sun", (any("Sun", "OpenPam"))),
             ("OpenPam", (any("Sun", "OpenPam"))),
             ("LinuxPam", (not("OpenPam"))),
-            ("MinimalOpenPam", (not("OpenPam"))),
+            ("XSso", (not("OpenPam"))),
             ("Other", (not(any("This", "That")))),
             ("OpenPam", (not(not("OpenPam")))),
             ("Anything", (not(any()))),
@@ -269,8 +269,8 @@
             ("OpenPam", (any("LinuxPam", "Sun"))),
             ("One", (not(any("One", "Another")))),
             ("Negatory", (not(not("Affirmative")))),
-            ("MinimalOpenPam", ("OpenPam")),
-            ("OpenPam", ("MinimalOpenPam")),
+            ("XSso", ("OpenPam")),
+            ("OpenPam", ("XSso")),
         ];
         for (bad, tree) in nonmatching {
             let pred = parse(tree);
--- a/libpam-sys/libpam-sys-test/build.rs	Sun Jun 29 18:48:14 2025 -0400
+++ b/libpam-sys/libpam-sys-test/build.rs	Sun Jun 29 20:13:03 2025 -0400
@@ -18,7 +18,11 @@
             "security/pam_ext.h".into(),
             "security/pam_modules.h".into(),
         ],
-        ignore_consts: vec!["__LINUX_PAM__".into(), "__LINUX_PAM_MINOR__".into()],
+        ignore_consts: vec![
+            "__LINUX_PAM__".into(),
+            "__LINUX_PAM_MINOR__".into(),
+            "PAM_AUTHTOK_RECOVER_ERR".into(),
+        ],
     }
 }
 
--- a/libpam-sys/src/constants.rs	Sun Jun 29 18:48:14 2025 -0400
+++ b/libpam-sys/src/constants.rs	Sun Jun 29 20:13:03 2025 -0400
@@ -39,6 +39,7 @@
 
 // There are a few truly universal constants.
 // They are defined here directly.
+/// The successful return code.
 pub const PAM_SUCCESS: i32 = 0;
 
 c_enum!(
@@ -51,6 +52,7 @@
     PAM_AUTHTOK,
     PAM_OLDAUTHTOK,
     PAM_RUSER,
+    PAM_USER_PROMPT,
 );
 
 c_enum!(
@@ -73,7 +75,7 @@
 #[cfg_pam_impl("LinuxPam")]
 mod linux_pam {
     c_enum!(
-        /// An error code.
+        /// An error return code.
         PAM_OPEN_ERR = 1,
         PAM_SYMBOL_ERR,
         PAM_SERVICE_ERR,
@@ -107,8 +109,6 @@
         PAM_INCOMPLETE,
         _PAM_RETURN_VALUES,
     );
-    /// An error code.
-    pub const PAM_AUTHTOK_RECOVER_ERR: i32 = 21;
 
     define!(
         /// A flag value.
@@ -127,8 +127,7 @@
     );
 
     c_enum!(
-        PAM_USER_PROMPT = 9,
-        PAM_FAIL_DELAY,
+        PAM_FAIL_DELAY = 10,
         PAM_XDISPLAY,
         PAM_XAUTHDATA,
         PAM_AUTHTOK_TYPE,
@@ -145,12 +144,12 @@
     );
 }
 
-#[cfg_pam_impl(any("OpenPam", "OpenPamMinimal", "Sun"))]
-pub use openpam_sun::*;
-#[cfg_pam_impl(any("OpenPam", "OpenPamMinimal", "Sun"))]
-mod openpam_sun {
+#[cfg_pam_impl(any("OpenPam", "Sun", "XSso"))]
+pub use xsso_shared::*;
+#[cfg_pam_impl(any("OpenPam", "Sun", "XSso"))]
+mod xsso_shared {
     c_enum!(
-        /// An error code.
+        /// An error return code.
         PAM_OPEN_ERR = 1,
         PAM_SYMBOL_ERR,
         PAM_SERVICE_ERR,
@@ -179,17 +178,13 @@
         PAM_ABORT,
         PAM_TRY_AGAIN,
     );
-
-    define!(
-        /// An item type.
-        PAM_USER_PROMPT = 9;
-        PAM_REPOSITORY = 10;
-    );
+    // While `PAM_MODULE_UNKNOWN` and `PAM_DOMAIN_UNKNOWN` are in X/SSO,
+    // Sun doesn't use them so we're omitting them here.
 
     /// A general flag for PAM operations.
     pub const PAM_SILENT: i32 = 0x80000000u32 as i32;
 
-    /// The password must be non-null.
+    /// A flag for `pam_authenticate`.
     pub const PAM_DISALLOW_NULL_AUTHTOK: i32 = 0b1;
 
     define!(
@@ -201,7 +196,7 @@
     );
 
     define!(
-        /// A flag for `pam_chauthtok`.
+        /// A flag for `pam_sm_chauthtok`.
         PAM_PRELIM_CHECK = 0b0001;
         PAM_UPDATE_AUTHTOK = 0b0010;
         PAM_CHANGE_EXPIRED_AUTHTOK = 0b0100;
@@ -213,7 +208,7 @@
 #[cfg_pam_impl("OpenPam")]
 mod openpam {
     c_enum!(
-        /// An error code.
+        /// An error return code.
         PAM_MODULE_UNKNOWN = 28,
         PAM_DOMAIN_UNKNOWN,
         PAM_BAD_HANDLE,
@@ -221,12 +216,13 @@
         PAM_BAD_FEATURE,
         PAM_BAD_CONSTANT,
     );
-    /// The total number of PAM error codes.
+    /// The total number of PAM error codes (including success).
     pub const PAM_NUM_ERRORS: i32 = 34;
 
     c_enum!(
         /// An item type.
-        PAM_AUTHTOK_PROMPT = 11,
+        PAM_REPOSITORY = 10,
+        PAM_AUTHTOK_PROMPT,
         PAM_OLDAUTHTOK_PROMPT,
         PAM_HOST,
     );
@@ -262,10 +258,11 @@
     /// The total number of PAM error codes.
     pub const PAM_TOTAL_ERRNUM: i32 = 28;
 
-    define!(
+    c_enum!(
         /// An item type.
-        PAM_RESOURCE = 11;
-        PAM_AUSER = 12;
+        PAM_REPOSITORY = 10,
+        PAM_RESOURCE,
+        PAM_AUSER,
     );
 
     /// A flag for `pam_chauthtok`.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpam-sys/src/functions.rs	Sun Jun 29 20:13:03 2025 -0400
@@ -0,0 +1,1 @@
+
--- a/libpam-sys/src/helpers.rs	Sun Jun 29 18:48:14 2025 -0400
+++ b/libpam-sys/src/helpers.rs	Sun Jun 29 20:13:03 2025 -0400
@@ -1,10 +1,11 @@
 //! 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::marker::{PhantomData, PhantomPinned};
 use std::mem::ManuallyDrop;
 use std::ptr::NonNull;
+use std::{fmt, slice};
 
 /// Error returned when attempting to allocate a buffer that is too big.
 ///
@@ -99,6 +100,77 @@
     }
 }
 
+/// The structure of the "binary message" payload for the `PAM_BINARY_PROMPT`
+/// extension from Linux-PAM.
+pub struct BinaryPayload {
+    /// The total byte size of the message, including this header,
+    /// as a u32 in network byte order (big endian).
+    pub total_bytes_u32be: [u8; 4],
+    /// A tag used to provide some kind of hint as to what the data is.
+    /// Its meaning is undefined.
+    pub data_type: u8,
+    /// Where the data itself would start, used as a marker to make this
+    /// not [`Unpin`] (since it is effectively an intrusive data structure
+    /// pointing to immediately after itself).
+    pub _marker: PhantomData<PhantomPinned>,
+}
+
+impl BinaryPayload {
+    /// The most data it's possible to put into a [`BinaryPayload`].
+    pub const MAX_SIZE: usize = (u32::MAX - 5) as usize;
+
+    /// Fills in the provided buffer with the given data.
+    ///
+    /// This uses [`copy_from_slice`](slice::copy_from_slice) internally,
+    /// so `buf` must be exactly 5 bytes longer than `data`, or this function
+    /// will panic.
+    pub fn fill(buf: &mut [u8], data_type: u8, data: &[u8]) {
+        let ptr: *mut Self = buf.as_mut_ptr().cast();
+        // SAFETY: We're given a slice, which always has a nonzero pointer.
+        let me = unsafe { ptr.as_mut().unwrap_unchecked() };
+        me.total_bytes_u32be = u32::to_be_bytes(buf.len() as u32);
+        me.data_type = data_type;
+        buf[5..].copy_from_slice(data)
+    }
+
+    /// The total storage needed for the message, including header.
+    pub fn total_bytes(&self) -> usize {
+        u32::from_be_bytes(self.total_bytes_u32be) as usize
+    }
+
+    /// Gets the total byte buffer of the BinaryMessage stored at the pointer.
+    ///
+    /// The returned data slice is borrowed from where the pointer points to.
+    ///
+    /// # Safety
+    ///
+    /// - The pointer must point to a valid `BinaryPayload`.
+    /// - The borrowed data must not outlive the pointer's validity.
+    pub unsafe fn buffer_of<'a>(ptr: *const Self) -> &'a [u8] {
+        let header: &Self = ptr.as_ref().unwrap_unchecked();
+        slice::from_raw_parts(ptr.cast(), header.total_bytes().max(5))
+    }
+
+    /// Gets the contents of the BinaryMessage stored at the given pointer.
+    ///
+    /// The returned data slice is borrowed from where the pointer points to.
+    /// This is a cheap operation and doesn't do *any* copying.
+    ///
+    /// We don't take a `&self` reference here because accessing beyond
+    /// the range of the `Self` data (i.e., beyond the 5 bytes of `self`)
+    /// is undefined behavior. Instead, you have to pass a raw pointer
+    /// directly to the data.
+    ///
+    /// # Safety
+    ///
+    /// - The pointer must point to a valid `BinaryPayload`.
+    /// - The borrowed data must not outlive the pointer's validity.
+    pub unsafe fn contents<'a>(ptr: *const Self) -> (u8, &'a [u8]) {
+        let header: &Self = ptr.as_ref().unwrap_unchecked();
+        (header.data_type, &Self::buffer_of(ptr)[5..])
+    }
+}
+
 /// A binary message owned by some storage.
 ///
 /// This is an owned, memory-managed version of [`BinaryPayload`].
@@ -132,9 +204,7 @@
 
     /// The total bytes needed to store this, including the header.
     pub fn total_bytes(&self) -> usize {
-        unsafe {
-            BinaryPayload::buffer_of(self.0.as_ptr().cast()).len()
-        }
+        unsafe { BinaryPayload::buffer_of(self.0.as_ptr().cast()).len() }
     }
 
     /// Unwraps this into the raw storage backing it.
--- a/libpam-sys/src/lib.rs	Sun Jun 29 18:48:14 2025 -0400
+++ b/libpam-sys/src/lib.rs	Sun Jun 29 20:13:03 2025 -0400
@@ -7,6 +7,7 @@
 use libpam_sys_impls::{__pam_impl_enum__, __pam_impl_name__};
 
 mod constants;
+mod functions;
 pub mod helpers;
 mod structs;
 
@@ -30,8 +31,8 @@
 /// #[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(any("LinuxPam", "XSso"))]
+/// fn do_something_else() { /* Linux-PAM or X/SSO-spec PAM */ }
 ///
 /// #[cfg_pam_impl(not(any("Sun", "OpenPam")))]
 /// fn do_a_third_thing() { /* Neither Sun nor OpenPAM */ }
--- a/libpam-sys/src/structs.rs	Sun Jun 29 18:48:14 2025 -0400
+++ b/libpam-sys/src/structs.rs	Sun Jun 29 20:13:03 2025 -0400
@@ -1,76 +1,71 @@
-use core::marker::{PhantomData, PhantomPinned};
-use core::slice;
+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(transparent)]
+struct ExtremelyUnsafe(PhantomData<(PhantomPinned, *mut c_void)>);
+
+impl fmt::Debug for ExtremelyUnsafe {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str("ExtremelyUnsafe")
+    }
+}
 
-/// The structure of the "binary message" payload for the `PAM_BINARY_PROMPT`
-/// extension from Linux-PAM.
-pub struct BinaryPayload {
-    /// The total byte size of the message, including this header,
-    /// as a u32 in network byte order (big endian).
-    pub total_bytes_u32be: [u8; 4],
-    /// A tag used to provide some kind of hint as to what the data is.
-    /// Its meaning is undefined.
-    pub data_type: u8,
-    /// Where the data itself would start, used as a marker to make this
-    /// not [`Unpin`] (since it is effectively an intrusive data structure
-    /// pointing to immediately after itself).
-    pub _marker: PhantomData<PhantomPinned>,
+/// An opaque structure that PAM uses to communicate.
+///
+/// This is only ever returned in pointer form and cannot be constructed.
+#[repr(C)]
+pub struct PamHandle {
+    _marker: ExtremelyUnsafe,
+}
+
+impl fmt::Debug for PamHandle {
+    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.
+pub struct AppData {
+    _marker: ExtremelyUnsafe,
 }
 
-impl BinaryPayload {
-    /// The most data it's possible to put into a [`BinaryPayload`].
-    pub const MAX_SIZE: usize = (u32::MAX - 5) as usize;
-
-    /// Fills in the provided buffer with the given data.
-    ///
-    /// This uses [`copy_from_slice`](slice::copy_from_slice) internally,
-    /// so `buf` must be exactly 5 bytes longer than `data`, or this function
-    /// will panic.
-    pub fn fill(buf: &mut [u8], data_type: u8, data: &[u8]) {
-        let ptr: *mut Self = buf.as_mut_ptr().cast();
-        // SAFETY: We're given a slice, which always has a nonzero pointer.
-        let me = unsafe { ptr.as_mut().unwrap_unchecked() };
-        me.total_bytes_u32be = u32::to_be_bytes(buf.len() as u32);
-        me.data_type = data_type;
-        buf[5..].copy_from_slice(data)
-    }
-
-    /// The total storage needed for the message, including header.
-    pub fn total_bytes(&self) -> usize {
-        u32::from_be_bytes(self.total_bytes_u32be) as usize
-    }
-
-    /// Gets the total byte buffer of the BinaryMessage stored at the pointer.
-    ///
-    /// The returned data slice is borrowed from where the pointer points to.
-    ///
-    /// # Safety
-    ///
-    /// - The pointer must point to a valid `BinaryPayload`.
-    /// - The borrowed data must not outlive the pointer's validity.
-    pub unsafe fn buffer_of<'a>(ptr: *const Self) -> &'a [u8] {
-        let header: &Self = ptr.as_ref().unwrap_unchecked();
-        slice::from_raw_parts(ptr.cast(), header.total_bytes().max(5))
-    }
-
-    /// Gets the contents of the BinaryMessage stored at the given pointer.
-    ///
-    /// The returned data slice is borrowed from where the pointer points to.
-    /// This is a cheap operation and doesn't do *any* copying.
-    ///
-    /// We don't take a `&self` reference here because accessing beyond
-    /// the range of the `Self` data (i.e., beyond the 5 bytes of `self`)
-    /// is undefined behavior. Instead, you have to pass a raw pointer
-    /// directly to the data.
-    ///
-    /// # Safety
-    ///
-    /// - The pointer must point to a valid `BinaryPayload`.
-    /// - The borrowed data must not outlive the pointer's validity.
-    pub unsafe fn contents<'a>(ptr: *const Self) -> (u8, &'a [u8]) {
-        let header: &Self = ptr.as_ref().unwrap_unchecked();
-        (
-            header.data_type,
-            &Self::buffer_of(ptr)[5..]
-        )
+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,
+    messages: *const *const Message,
+    responses: &mut *mut Response,
+    appdata: *const AppData,
+) -> c_int;
+
+/// Used by PAM to communicate between the module and the application.
+#[repr(C)]
+pub struct Conversation {
+    pub callback: ConversationCallback,
+    pub appdata: *const AppData,
+}
+
+/// A message sent into a PAM conversation.
+#[repr(C)]
+pub struct Message {
+    pub style: c_int,
+    pub data: *const c_void,
+}
+
+/// A response returned from a PAM conversation.
+#[repr(C)]
+pub struct Response {
+    pub data: *mut c_void,
+    pub _unused: c_int,
+}
--- a/src/_doc.rs	Sun Jun 29 18:48:14 2025 -0400
+++ b/src/_doc.rs	Sun Jun 29 20:13:03 2025 -0400
@@ -176,4 +176,4 @@
     };
 }
 
-pub(crate) use {linklist, guide, man7, manbsd, xsso, stdlinks};
\ No newline at end of file
+pub(crate) use {guide, linklist, man7, manbsd, stdlinks, xsso};
--- a/src/libpam/handle.rs	Sun Jun 29 18:48:14 2025 -0400
+++ b/src/libpam/handle.rs	Sun Jun 29 20:13:03 2025 -0400
@@ -8,8 +8,8 @@
 use crate::libpam::{memory, pam_ffi};
 use crate::logging::{Level, Location};
 use crate::{
-    Conversation, EnvironMap, Flags, PamHandleApplication, PamHandleModule, guide, linklist,
-    stdlinks,
+    guide, linklist, stdlinks, Conversation, EnvironMap, Flags, PamHandleApplication,
+    PamHandleModule,
 };
 use num_enum::{IntoPrimitive, TryFromPrimitive};
 use std::cell::Cell;
@@ -208,7 +208,12 @@
             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(level as c_int, func.as_ptr(), "%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(),
+                )
             }
         }
     }
--- a/src/logging.rs	Sun Jun 29 18:48:14 2025 -0400
+++ b/src/logging.rs	Sun Jun 29 20:13:03 2025 -0400
@@ -47,18 +47,23 @@
     Debug = levels::DEBUG,
 }
 
-/// The location of a log entry.
+/// The location of a log entry. Use [`location!`](crate::location!) to create this.
 #[derive(Clone, Copy, Debug, Default)]
+#[non_exhaustive]
 pub struct Location<'a> {
     pub file: &'a str,
     pub line: u32,
     pub function: &'a str,
-    _more: (),
 }
 
 impl<'a> Location<'a> {
+    /// Creates a new location. Just use [`location!`](crate::location!) instead.
     pub fn new(file: &'a str, line: u32, function: &'a str) -> Self {
-        Self {file, line, function, _more: ()}
+        Self {
+            file,
+            line,
+            function,
+        }
     }
 }
 
@@ -66,7 +71,9 @@
 #[doc(hidden)]
 #[macro_export]
 macro_rules! location {
-    () => { $crate::logging::Location::new(file!(), line!(), $crate::__function!()) }
+    () => {
+        $crate::logging::Location::new(file!(), line!(), $crate::__function!())
+    };
 }
 
 /// Here's the guts of the logger thingy. You shouldn't be using this!
@@ -90,7 +97,7 @@
             ::std::any::type_name::<T>()
         }
         f(p).trim_end_matches("::p")
-    }}
+    }};
 }
 
 /// Logs a message at error level via the given PAM handle.