changeset 174:9e4ce1631bd3

Dramatically expand documentation.
author Paul Fisher <paul@pfish.zone>
date Tue, 29 Jul 2025 18:58:27 -0400
parents 46e8ce5cd5d1
children e30775c80b49
files README.md libpam-sys/libpam-sys-helpers/src/lib.rs libpam-sys/src/aliases.rs libpam-sys/src/lib.rs src/_doc.rs src/handle.rs src/items.rs src/lib.rs src/libpam/handle.rs src/libpam/module.rs src/logging.rs
diffstat 11 files changed, 252 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- a/README.md	Tue Jul 29 16:52:32 2025 -0400
+++ b/README.md	Tue Jul 29 18:58:27 2025 -0400
@@ -1,24 +1,62 @@
 # 🍳 nonstick
 
-Nonstick lets you use PAM (Pluggable Authentication Modules) from Rust without getting stuck in unsafe code.
+Nonstick lets you use PAM (Pluggable Authentication Modules) from Rust safely.
+Don't worry about getting stuck on unsafe code.
+
+You can use nonstick for interacting with PAM from both sides:
+
+- PAM applications: call into PAM to authenticate users.
+- PAM modules: write a backend that PAM uses for authentication.
+
+It supports all known PAM implementations:
+
+- Linux-PAM, used on most (all?) Linux distributions.
+- OpenPAM, used on most BSDs, including Mac OS.
+- Sun's PAM, used on Solaris and derivatives like Illumos.
+
+Further documentation can be found in the crate's rustdoc.
 
 ## Status
 
-This is currently somewhat incomplete.
+- **Modules**: full support for all calls by PAM into modules.
+  You can use nonstick to implement a PAM module that performs any stage of the PAM lifecycle.
 
-It provides fairly robust functionality for developing PAM modules (i.e., backends that PAM calls to authenticate users or do something similar).
-[Linux-PAM](https://github.com/linux-pam/linux-pam) is the only _tested_ PAM implementation, but it compiles against OpenPAM.
+- **Applications**: supports only a subset of PAM features:
+  
+  - Authentication
+  - Account management
+  - Password change
+  
+  The remaining features (credential and session management) are coming in a future release.
+  (It needs work on a safe and ergonomic API.)
 
-*If you’re looking for a library to implement a PAM client* (i.e., something that authenticates using PAM), consider the [`pam` crate](https://crates.io/crates/pam) for now.
+The overall shape of the API is largely complete and its general shape should not change significantly.
+While there may still be minor source incompatibilities pre-1.0 (e.g., moving methods around), it is unlikely that the library will be radically reworked (and I will try to avoid them unless needed).
+
+## Testing
+
+Nonstick is tested against all supported PAM implementations.
+In addition to doctests and unit tests in the source itself, the (non-public) `testharness` sub-package performs end-to-end tests against the whole authentication process.
 
-APIs are likely to break before v0.1.0, but thereafter should stabilize to an eventual 1.0 release.
-After v0.1.0, the shape of the API should be mostly formed, and most of what happens will be adding new features.
+## Configuration
+
+By default, nonstick uses `libpam-sys` to detect which implementation of PAM it should build against.
+You can also select to build your library or application against a specific PAM implementation by setting the `LIBPAMSYS_IMPL` environment variable.
+See [the documentation for `libpam-sys`](https://docs.rs/libpam-sys/) for more details.
+
+## Cargo features
 
-Goals include:
+- `link` (enabled by default): Link against your system's PAM library.
+  If disabled, you can still use the PAM traits and enum types to build and test your own PAM code independent of your system PAM.
+- `basic-ext` (enabled by default): Include enum values provided by both OpenPAM and Linux-PAM.
+- `linux-pam-ext`: Include features specific to Linux-PAM, including enum values and the ability to send binary messages.
+- `openpam-ext`: Include features specific to OpenPAM (just enum values).
+- `sun-ext`: Include features specific to Sun PAM (just enum values).
 
-- Bindings for PAM clients.
-- Additional PAM features, like environment variables.
-- Way more documentation.
+When `link` is enabled, you can only use the PAM features available on the configured PAM implementation.
+For instance, when building with Linux-PAM, `link` and `openpam-ext` cannot be used together.
+
+However, when `link` is disabled, you could develop and test a crate with `sun-ext` enabled using any device.
 
 ## Credits
 
--- a/libpam-sys/libpam-sys-helpers/src/lib.rs	Tue Jul 29 16:52:32 2025 -0400
+++ b/libpam-sys/libpam-sys-helpers/src/lib.rs	Tue Jul 29 18:58:27 2025 -0400
@@ -55,7 +55,6 @@
 ///                                            β•‘ data β”„β”„β”„β”„β”„β”„β”„β•«β”„β”„> ...
 ///                                            β•Ÿβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β•’
 ///                                            β•‘ ...         β•‘
-///
 /// ```
 #[derive(Debug)]
 pub struct PtrPtrVec<T> {
--- a/libpam-sys/src/aliases.rs	Tue Jul 29 16:52:32 2025 -0400
+++ b/libpam-sys/src/aliases.rs	Tue Jul 29 18:58:27 2025 -0400
@@ -10,13 +10,16 @@
 /// (and a workaround).
 ///
 /// ```no_run
+/// use libpam_sys::aliases::ConversationCallback;
 /// use libpam_sys::pam_conv;
-/// use libpam_sys::aliases::ConversationCallback;
 /// fn convo() -> ConversationCallback {
 ///     // ...
 /// #    unimplemented!()
 /// }
-/// let conv = pam_conv{conv: convo(), appdata_ptr: std::ptr::null_mut()};
+/// let conv = pam_conv {
+///     conv: convo(),
+///     appdata_ptr: std::ptr::null_mut(),
+/// };
 /// ```
 pub type ConversationCallback = unsafe extern "C" fn(
     num_msg: c_int,
@@ -29,8 +32,8 @@
 ///
 /// ```no_run
 /// # use std::ffi::CString;
+/// use libpam_sys::aliases::CleanupCallback;
 /// use libpam_sys::pam_set_data;
-/// use libpam_sys::aliases::CleanupCallback;
 /// # use libpam_sys::pam_handle;
 /// # let handle: *mut pam_handle = std::ptr::null_mut();
 /// # let mut my_data = 100;
--- a/libpam-sys/src/lib.rs	Tue Jul 29 16:52:32 2025 -0400
+++ b/libpam-sys/src/lib.rs	Tue Jul 29 18:58:27 2025 -0400
@@ -8,7 +8,6 @@
 //! - The [`aliases`] submodule, which contains convenient aliases
 //!   for callback types used in libpam, so you don't have to type
 //!   `unsafe extern "C" fn(this is so long)` all the time.
-//!
 #![doc = concat!("This documentation was built for the **", pam_impl_name!(), "** implementation.")]
 //!
 //! You can override this **at build time** by setting the `LIBPAMSYS_IMPL`
@@ -411,13 +410,11 @@
     /// # let service = CString::new("whatever").unwrap();
     /// # let user = CString::new("whatever").unwrap();
     /// let mut handle: *mut pam_handle = ptr::null_mut();
-    /// let mut conv = pam_conv{
+    /// let mut conv = pam_conv {
     ///     conv: openpam_ttyconv,
     ///     appdata_ptr: ptr::null_mut(),
     /// };
-    /// let result = unsafe { pam_start(
-    ///     service.as_ptr(), user.as_ptr(), &mut conv, &mut handle
-    /// ) };
+    /// let result = unsafe { pam_start(service.as_ptr(), user.as_ptr(), &mut conv, &mut handle) };
     /// ```
     pub fn openpam_ttyconv(
         n: c_int,
@@ -437,13 +434,11 @@
     /// # let service = CString::new("whatever").unwrap();
     /// # let user = CString::new("whatever").unwrap();
     /// let mut handle: *mut pam_handle = ptr::null_mut();
-    /// let mut conv = pam_conv{
+    /// let mut conv = pam_conv {
     ///     conv: openpam_nullconv,
     ///     appdata_ptr: ptr::null_mut(),
     /// };
-    /// let result = unsafe { pam_start(
-    ///     service.as_ptr(), user.as_ptr(), &mut conv, &mut handle
-    /// ) };
+    /// let result = unsafe { pam_start(service.as_ptr(), user.as_ptr(), &mut conv, &mut handle) };
     /// ```
     pub fn openpam_nullconv(
         n: c_int,
--- a/src/_doc.rs	Tue Jul 29 16:52:32 2025 -0400
+++ b/src/_doc.rs	Tue Jul 29 18:58:27 2025 -0400
@@ -143,7 +143,7 @@
 ///
 /// ```ignore
 /// # use nonstick::_doc::mansun;
-/// // Both of these formulations create a link named `manbsd`.
+/// // Both of these formulations create a link named `mansun`.
 /// #[doc = mansun!(3pam fn_name)]
 /// #[doc = mansun!(5 "a.out" "synopsis")]
 /// // This one creates a link named `link_name`.
@@ -154,12 +154,18 @@
     ($n:literal $func:ident $($anchor:literal)?) => {
         $crate::_doc::mansun!(mansun: [$n ""] $func $($anchor)?)
     };
+    ($n:literal $func:literal $($anchor:literal)?) => {
+        $crate::_doc::mansun!(mansun: [$n ""] $func $($anchor)?)
+    };
     ([$n:literal $sect:literal] $func:ident $($anchor:literal)?) => {
         $crate::_doc::mansun!(mansun: [$n $sect] $func $($anchor)?)
     };
     ($name:ident: $n:literal $func:ident $($anchor:literal)?) => {
         $crate::_doc::mansun!($name: [$n ""] $func $($anchor)?)
     };
+    ($name:ident: $n:literal $func:literal $($anchor:literal)?) => {
+        $crate::_doc::mansun!($name: [$n ""] ($func) $($anchor)?)
+    };
     ($name:ident: [$n:literal $sect:literal] $func:ident $($anchor:literal)?) => {
         $crate::_doc::mansun!($name: [$n $sect] (stringify!($func)) $($anchor)?)
     };
--- a/src/handle.rs	Tue Jul 29 16:52:32 2025 -0400
+++ b/src/handle.rs	Tue Jul 29 18:58:27 2025 -0400
@@ -50,7 +50,6 @@
     /// The contents of the environment to set for the logged-in user.
     ///
     /// # References
-    ///
     #[doc = linklist!(pam_getenv: adg, mwg, _std)]
     ///
     #[doc = stdlinks!(3 pam_getenv)]
@@ -61,7 +60,6 @@
     /// A writable map of the environment to set for the logged-in user.
     ///
     /// # References
-    ///
     #[doc = linklist!(pam_putenv: adg, mwg, _std)]
     ///
     #[doc = stdlinks!(3 pam_putenv)]
@@ -76,7 +74,6 @@
     /// by PAM modules only.
     ///
     /// # References
-    ///
     #[doc = linklist!(pam_get_item: mwg, adg, _std)]
     ///
     #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_get_item")]
@@ -87,7 +84,6 @@
     /// Read-write access to PAM Items.
     ///
     /// # References
-    ///
     #[doc = linklist!(pam_set_item: mwg, adg, _std)]
     ///
     #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_set_item")]
@@ -165,7 +161,6 @@
     /// retrieves the existing item.
     ///
     /// # References
-    ///
     #[doc = linklist!(pam_get_authtok: man7, manbsd)]
     ///
     /// # Example
@@ -190,7 +185,6 @@
     /// This should only be used by a *password-change* module.
     ///
     /// # References
-    ///
     #[doc = linklist!(pam_get_authtok: man7, manbsd)]
     ///
     /// # Example
@@ -205,7 +199,6 @@
     /// Ok(())
     /// # }
     /// ```
-    ///
     #[doc = stdlinks!(3 pam_get_authtok)]
     fn old_authtok(&mut self, prompt: Option<&OsStr>) -> Result<OsString>;
 
@@ -229,7 +222,6 @@
     /// ```
     ///
     /// # References
-    ///
     #[doc = linklist!(pam_get_data: mwg, _std)]
     ///
     #[doc = guide!(mwg: "mwg-expected-by-module-item.html#mwg-pam_get_data")]
@@ -282,8 +274,8 @@
     ///     match client.get_module_data::<u64>("TOKEN_ID") {
     ///         Some(&tid) => {
     ///             // This will execute and tid will be 0x0fa1afe10000beef.
-    ///         },
-    ///         None => { /* This will not execute. */ },
+    ///         }
+    ///         None => { /* This will not execute. */ }
     ///     }
     ///     Ok(())
     /// }
@@ -305,10 +297,10 @@
     ///         Some(value) => {
     ///             // This will match, because pam_module_a's data
     ///             // is completely unrelated to pam_module_b's data.
-    ///         },
+    ///         }
     ///         None => {
     ///             // This branch will execute.
-    ///         },
+    ///         }
     ///     }
     ///     // ...
     /// #    Ok(())
@@ -349,7 +341,6 @@
     /// we do not (yet) offer this.
     ///
     /// # References
-    ///
     #[doc = linklist!(pam_set_data: mwg, _std)]
     ///
     #[doc = guide!(mwg: "mwg-expected-by-module-item.html#mwg-pam_set_data")]
--- a/src/items.rs	Tue Jul 29 16:52:32 2025 -0400
+++ b/src/items.rs	Tue Jul 29 18:58:27 2025 -0400
@@ -40,7 +40,6 @@
 /// modules, and the framework itself.
 ///
 /// # References
-///
 #[doc = linklist!(pam_get_item: mwg, adg, _std)]
 ///
 #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_get_item")]
@@ -104,7 +103,6 @@
 /// the framework, and modules.
 ///
 /// # References
-///
 #[doc = linklist!(pam_set_item: mwg, adg, _std)]
 ///
 #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_set_item")]
--- a/src/lib.rs	Tue Jul 29 16:52:32 2025 -0400
+++ b/src/lib.rs	Tue Jul 29 18:58:27 2025 -0400
@@ -1,26 +1,171 @@
-//! A safe, nonstick interface to PAM.
+//! A safe, nonstick interface to the Pluggable Authentication Module framework.
+//!
+//! Nonstick provides a fully type- and memory-safe interface to
+//! all implementations of PAM, both for PAM modules and PAM applications.
+//!
+//! # Usage
+//!
+//! nonstick can be used on either side of a PAM transaction,
+//! both to implement an application which calls into PAM,
+//! or a module which implements a PAM backend.
+//!
+//! For more information about how PAM works in general, or more pointers
+//! on how to implement a PAM module or application, see the
+//! [References](#references) section.
+//!
+//! ## PAM Application
+//!
+//! To implement a PAM application, first implement a [`Conversation`],
+//! then build a [`Transaction`] with the [`TransactionBuilder`].
+//! This can be built into any standard Rust library or binary.
+//!
+//! ```
+//! use nonstick::{
+//!     AuthnFlags, Conversation, ConversationAdapter, Result as PamResult, Transaction,
+//!     TransactionBuilder,
+//! };
+//! use std::ffi::{OsStr, OsString};
+//!
+//! /// A basic Conversation that assumes that any "regular" prompt is for
+//! /// the username, and that any "masked" prompt is for the password.
+//! ///
+//! /// A typical Conversation will provide the user with an interface
+//! /// to interact with PAM, e.g. a dialogue box or a terminal prompt.
+//! struct UsernamePassConvo {
+//!     username: String,
+//!     password: String,
+//! }
 //!
-//! This implements a type-safe library to interact with PAM.
-//! Currently, it implements a subset of PAM useful for implementing a module.
+//! // ConversationAdapter is a convenience wrapper for the common case
+//! // of only handling one request at a time.
+//! impl ConversationAdapter for UsernamePassConvo {
+//!     fn prompt(&self, request: impl AsRef<OsStr>) -> PamResult<OsString> {
+//!         Ok(OsString::from(&self.username))
+//!     }
+//!
+//!     fn masked_prompt(&self, request: impl AsRef<OsStr>) -> PamResult<OsString> {
+//!         Ok(OsString::from(&self.password))
+//!     }
+//!
+//!     fn error_msg(&self, message: impl AsRef<OsStr>) {
+//!         // Normally you would want to display this to the user somehow.
+//!         // In this case, we're just ignoring it.
+//!     }
+//!
+//!     fn info_msg(&self, message: impl AsRef<OsStr>) {
+//!         // ibid.
+//!     }
+//! }
 //!
-//! To write a new PAM module using this crate:
+//! fn authenticate(username: &str, password: &str) -> PamResult<()> {
+//!     let user_pass = UsernamePassConvo {
+//!         username: username.into(),
+//!         password: password.into(),
+//!     };
+//!
+//!     let mut txn = TransactionBuilder::new_with_service("cortex-sso")
+//!         .username(username)
+//!         .build(user_pass.into_conversation())?;
+//!     // If authentication fails, this will return an error.
+//!     // We immediately give up rather than re-prompting the user.
+//!     txn.authenticate(AuthnFlags::empty())?;
+//!     txn.account_management(AuthnFlags::empty())?;
+//!     Ok(())
+//! }
+//! ```
+//!
+//! PAM just tells you that the user is, in fact, who they say they are.
+//! It is up to your application to choose what to do with that information.
+//!
+//! ## PAM module
 //!
-//!  1. Create a `dylib` crate.
-//!  2. Implement a subset of the functions in the [`PamModule`] trait
-//!     corresponding to what you want your module to do.
-//!     In the simplest case (for a new password-based authenticator),
-//!     this will be the [`PamModule::authenticate`] function.
-//!  3. Export your PAM module using the [`pam_export!`] macro.
-//!  4. Build and install the dynamic library.
-//!     This usually entails placing it at
-//!     <code>/usr/lib/security/pam_<var>your_module</var>.so</code>,
-//!     or maybe
-//!     <code>/usr/lib/<var>your-architecture</var>/security/pam_<var>your_module</var>.so</code>.
+//! PAM modules are implemented as dynamic libraries loaded into
+//! the address space of the calling application. To implement a module,
+//! create a `dylib` crate and implement a [`PamModule`], and export it
+//! using the [`pam_export!`] macro.
+//!
+//! ```toml
+//! ## Your Cargo.toml
+//! [package]
+//! name = "example-package"
+//! ## ...
+//!
+//! [lib]
+//! crate-type = ["cdylib"]
+//! ```
+//!
+//! ```
+//! // Your lib.rs
+//!
+//! use nonstick::{
+//!     pam_export, AuthnFlags, ErrorCode, ModuleClient, PamModule, Result as PamResult,
+//! };
+//! use std::ffi::CStr;
+//!
+//! # // This needs to be here to make this doc example work.
+//! # fn main() {}
+//!
+//! /// A module that only allows you to log in if your username
+//! /// is the same as your password.
+//! struct SameName;
+//! pam_export!(SameName);
+//!
+//! impl<M: ModuleClient> PamModule<M> for SameName {
+//!     fn authenticate(handle: &mut M, _args: Vec<&CStr>, _flags: AuthnFlags) -> PamResult<()> {
+//!         // Using `None` as the prompt parameter here will tell PAM
+//!         // to use the default prompt.
+//!         let username = handle.username(None)?;
+//!         let password = handle.authtok(None)?;
+//!         if username == password {
+//!             Ok(())
+//!         } else {
+//!             Err(ErrorCode::AuthenticationError)
+//!         }
+//!     }
 //!
-//! For general information on writing PAM modules, see
-//! [The Linux-PAM Module Writers' Guide][module-guide]
+//!     // You can implement other methods of PamModule to provide additional
+//!     // features.
+//! }
+//! ```
+//!
+//! This gets built into a library like `pam_samename.so`. By installing this
+//! into your PAM library directory and configuring PAM to use it in
+//! the authentication stack (beyond the scope of this documentation), it will
+//! be used to authenticate users.
+//!
+//! # Configuration
+//!
+//! There are a few different PAM implementations available. By default,
+//! nonstick detects which implementation it should use for the current target.
+//! If you need to choose a different implementation, set the `LIBPAMSYS_IMPL`
+//! environment variable at build time. See the [`libpam_sys`] documentation.
+#![doc = concat!("This documentation was built for **", pam_impl_name!(), "**.")]
+//!
+//! # References
 //!
-//! [module-guide]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/Linux-PAM_MWG.html
+//! - The Linux-PAM guides provide information for a variety of audiences.
+//!   While some of it is specific to Linux-PAM, much of it applies to other
+//!   PAM implementations:
+//!   - [Application Developers' Guide][adg]
+//!   - [Module Writers' Guide][mwg]
+//!   - [System Administrators' Guide][sag]
+//! - PAM framework man page for developers:
+//!   - [Linux-PAM developer man page][man7]
+//!   - [OpenPAM developer man page][manbsd]
+//!   - [Illumos PAM developer man page][mansun]
+//! - PAM framework man page for system administrators:
+//!   - [Linux-PAM admin documentation][man7pam8]
+//!   - [OpenPAM admin documentation][bsdpam8]
+//!   - [Illumos pam.conf documentation][sunpam5]
+//! - [The original PAM specification][spec] (mostly of historical interest)
+#![doc = crate::_doc::man7!(man7pam8: 8 pam)]
+#![doc = crate::_doc::manbsd!(bsdpam8: 8 pam)]
+#![doc = crate::_doc::mansun!(sunpam5: 5 "pam.conf")]
+#![doc = crate::_doc::stdlinks!(3 pam)]
+#![doc = crate::_doc::guide!(adg: "Linux-PAM_ADG.html")]
+#![doc = crate::_doc::guide!(mwg: "Linux-PAM_MWG.html")]
+#![doc = crate::_doc::guide!(sag: "Linux-PAM_SAG.html")]
+#![doc = crate::_doc::xsso!(spec: "toc.htm")]
 
 #[cfg(feature = "link")]
 mod _compat_checker {
@@ -73,3 +218,4 @@
     handle::{ModuleClient, PamShared, Transaction},
     module::PamModule,
 };
+use libpam_sys::pam_impl_name;
--- a/src/libpam/handle.rs	Tue Jul 29 16:52:32 2025 -0400
+++ b/src/libpam/handle.rs	Tue Jul 29 18:58:27 2025 -0400
@@ -44,6 +44,9 @@
     }
 }
 
+/// Builder to start a [`LibPamTransaction`].
+///
+/// Use [`Self::new_with_service`] to build a new PAM transaction.
 #[derive(Debug, PartialEq)]
 pub struct TransactionBuilder {
     service_name: OsString,
@@ -57,6 +60,9 @@
     /// when authenticating a user. This corresponds to the configuration file
     /// usually at <code>/etc/pam.d/<var>service_name</var></code>.
     ///
+    /// You usually want to call [`username`](Self::username) to set
+    /// the username before starting the transaction.
+    ///
     /// # References
     #[doc = linklist!(pam_start: adg, _std)]
     ///
@@ -132,7 +138,6 @@
     ///
     /// On other platforms, this is no different than letting the transaction
     /// end on its own.
-    ///
     #[doc = man7!(3 pam_end)]
     pub fn end_silent(self) {
         #[cfg(pam_impl = "LinuxPam")]
--- a/src/libpam/module.rs	Tue Jul 29 16:52:32 2025 -0400
+++ b/src/libpam/module.rs	Tue Jul 29 18:58:27 2025 -0400
@@ -17,7 +17,7 @@
 ///
 /// ```no_run
 /// use nonstick::{
-///     pam_export, ConversationAdapter, AuthnFlags, LibPamTransaction, ModuleClient, PamModule,
+///     pam_export, AuthnFlags, ConversationAdapter, LibPamTransaction, ModuleClient, PamModule,
 ///     Result as PamResult,
 /// };
 /// use std::ffi::CStr;
@@ -35,7 +35,11 @@
 ///         Ok(())
 ///     }
 ///
-///     fn account_management(handle: &mut T, args: Vec<&CStr>, flags: AuthnFlags) -> PamResult<()> {
+///     fn account_management(
+///         handle: &mut T,
+///         args: Vec<&CStr>,
+///         flags: AuthnFlags,
+///     ) -> PamResult<()> {
 ///         let username = handle.username(None)?;
 ///         let response = format!("Hello {username:?}! I trust you unconditionally.");
 ///         handle.info_msg(&response);
--- a/src/logging.rs	Tue Jul 29 16:52:32 2025 -0400
+++ b/src/logging.rs	Tue Jul 29 18:58:27 2025 -0400
@@ -34,8 +34,8 @@
     ///
     /// ```no_run
     /// # use nonstick::PamShared;
+    /// use nonstick::location;
     /// use nonstick::logging::Level;
-    /// use nonstick::location;
     /// # fn _test(pam_hdl: impl PamShared) {
     /// # let delay_ms = 100;
     /// # let url = "https://zombo.com";
@@ -45,7 +45,11 @@
     /// 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::Warn, location!(), format_args!("this is unnecessarily verbose"));
+    /// pam_hdl.log(
+    ///     Level::Warn,
+    ///     location!(),
+    ///     format_args!("this is unnecessarily verbose"),
+    /// );
     /// # }
     /// ```
     #[doc = man7!(3 pam_syslog)]