pam_client/
error.rs

1//! Error structs and related helpers
2
3/***********************************************************************
4 * (c) 2021-2022 Christoph Grenz <christophg+gitorious @ grenz-bonn.de>*
5 *                                                                     *
6 * This Source Code Form is subject to the terms of the Mozilla Public *
7 * License, v. 2.0. If a copy of the MPL was not distributed with this *
8 * file, You can obtain one at http://mozilla.org/MPL/2.0/.            *
9 ***********************************************************************/
10
11use crate::char_ptr_to_str;
12use crate::context::PamHandle;
13#[doc(no_inline)]
14pub use crate::ErrorCode;
15use pam_sys::pam_strerror;
16
17use std::any::type_name;
18use std::cmp::{Eq, PartialEq};
19use std::error;
20use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
21use std::hash::{Hash, Hasher};
22use std::io;
23use std::marker::PhantomData;
24
25/// The error payload type for errors that never have payloads.
26///
27/// Like `std::convert::Infallible` but with a less confusing name, given
28/// the context it's used in here. Might become a type alias to `!` when
29/// the [`!` never type](https://doc.rust-lang.org/std/primitive.never.html)
30/// is stabilized.
31#[derive(Copy, Clone, Debug)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33pub enum NoPayload {}
34
35impl Display for NoPayload {
36	fn fmt(&self, _: &mut Formatter<'_>) -> FmtResult {
37		match *self {}
38	}
39}
40
41impl PartialEq for NoPayload {
42	fn eq(&self, _: &NoPayload) -> bool {
43		match *self {}
44	}
45}
46
47impl Eq for NoPayload {}
48
49impl Hash for NoPayload {
50	fn hash<H: Hasher>(&self, _: &mut H) {
51		match *self {}
52	}
53}
54
55/// Helper to implement `Debug` on `ErrorWith` with `T` not implementing `Debug`
56enum DisplayHelper<T> {
57	Some(PhantomData<T>),
58	None,
59}
60
61impl<T> DisplayHelper<T> {
62	#[inline]
63	fn new(option: &Option<T>) -> Self {
64		match option {
65			None => Self::None,
66			Some(_) => Self::Some(PhantomData),
67		}
68	}
69}
70
71impl<T> Debug for DisplayHelper<T> {
72	fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
73		match *self {
74			Self::None => write!(f, "None"),
75			Self::Some(_) => write!(f, "<{}>", type_name::<T>()),
76		}
77	}
78}
79
80/// Base error type for PAM operations (possibly with a payload)
81///
82/// Errors originate from the PAM library, PAM modules or helper structs
83/// in this crate. Currently no custom instances are supported.
84#[must_use]
85#[derive(Clone)]
86#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
87pub struct ErrorWith<T> {
88	code: ErrorCode,
89	msg: String,
90	payload: Option<T>,
91}
92
93impl<T> ErrorWith<T> {
94	/// Creates a new [`Error`] that takes a payload.
95	///
96	/// Functions that consume a struct can use the payload to transfer back
97	/// ownership in error cases.
98	pub(crate) fn with_payload(
99		handle: PamHandle,
100		code: ErrorCode,
101		payload: Option<T>,
102	) -> ErrorWith<T> {
103		Self {
104			code,
105			msg: char_ptr_to_str(unsafe { pam_strerror(handle.into(), code.repr()) })
106				.unwrap_or("")
107				.into(),
108			payload,
109		}
110	}
111
112	/// The error code.
113	pub const fn code(&self) -> ErrorCode {
114		self.code
115	}
116
117	/// Text representation of the error code, if available.
118	pub fn message(&self) -> Option<&str> {
119		if self.msg.is_empty() {
120			None
121		} else {
122			Some(&self.msg)
123		}
124	}
125
126	/// Returns a reference to an optional payload.
127	#[rustversion::attr(since(1.48), const)]
128	pub fn payload(&self) -> Option<&T> {
129		self.payload.as_ref()
130	}
131
132	/// Takes the payload out of the error message.
133	///
134	/// If a payload exists in this error, it will be moved into the returned
135	/// [`Option`]. All further calls to [`payload()`][`Self::payload()`] and
136	/// [`take_payload()`][`Self::take_payload()`] will return [`None`].
137	pub fn take_payload(&mut self) -> Option<T> {
138		match self.payload {
139			Some(_) => self.payload.take(),
140			None => None,
141		}
142	}
143
144	/// Maps the error payload to another type
145	pub fn map<U>(self, func: impl FnOnce(T) -> U) -> ErrorWith<U> {
146		ErrorWith::<U> {
147			code: self.code,
148			msg: self.msg,
149			payload: self.payload.map(func),
150		}
151	}
152
153	/// Removes the payload and converts to [`Error`]
154	#[inline]
155	pub fn into_without_payload(self) -> Error {
156		Error {
157			code: self.code,
158			msg: self.msg,
159			payload: None,
160		}
161	}
162}
163
164impl<T> Debug for ErrorWith<T> {
165	fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
166		// Hacky and not always correct, but the best we can do for now
167		// without specialization
168		if type_name::<T>() == type_name::<NoPayload>() {
169			f.debug_struct("pam_client::Error")
170				.field("code", &self.code)
171				.field("msg", &self.msg)
172				.finish()
173		} else {
174			f.debug_struct("pam_client::ErrorWith")
175				.field("code", &self.code)
176				.field("msg", &self.msg)
177				.field("payload", &DisplayHelper::new(&self.payload))
178				.finish()
179		}
180	}
181}
182
183/// Error type for PAM operations without error payload.
184///
185/// This variant never contains a payload.
186pub type Error = ErrorWith<NoPayload>;
187
188impl Error {
189	/// Creates a new [`Error`].
190	pub(crate) fn new(handle: PamHandle, code: ErrorCode) -> Error {
191		Self::with_payload(handle, code, None)
192	}
193
194	/// Adds the payload to the error message and returns a corresponding
195	/// [`ErrorWith<T>`] instance.
196	pub fn into_with_payload<T>(self, payload: T) -> ErrorWith<T> {
197		ErrorWith::<T> {
198			code: self.code,
199			msg: self.msg,
200			payload: Some(payload),
201		}
202	}
203
204	/// Converts the error message into a [`ErrorWith<T>`] instance without
205	/// a payload.
206	pub fn into<T>(self) -> ErrorWith<T> {
207		ErrorWith::<T> {
208			code: self.code,
209			msg: self.msg,
210			payload: None,
211		}
212	}
213}
214
215impl<T> Display for ErrorWith<T> {
216	fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
217		if self.msg.is_empty() {
218			write!(f, "<{}>", self.code as i32)
219		} else {
220			f.write_str(&self.msg)
221		}
222	}
223}
224
225impl<T> error::Error for ErrorWith<T> {}
226
227impl<T> PartialEq for ErrorWith<T>
228where
229	T: PartialEq,
230{
231	fn eq(&self, other: &Self) -> bool {
232		self.code == other.code && self.payload == other.payload
233	}
234}
235
236impl<T> Eq for ErrorWith<T> where T: Eq {}
237
238impl<T> Hash for ErrorWith<T>
239where
240	T: Hash,
241{
242	fn hash<H: Hasher>(&self, state: &mut H) {
243		(self.code as i32).hash(state);
244		self.payload.hash(state);
245	}
246}
247
248/// Wrapping of a [`ErrorCode`] in a [`Error`] without a PAM context.
249///
250/// This is used internally to construct [`Error`] instances when no PAM
251/// context is available. These instances won't have a message string, only
252/// a code.
253///
254/// Examples:
255/// ```rust
256/// # use pam_client::{Error, ErrorCode};
257///
258/// let error = Error::from(ErrorCode::ABORT);
259/// println!("{:?}", error);
260/// ```
261/// ```rust
262/// # use pam_client::{Error, ErrorCode};
263///
264/// let error: Error = ErrorCode::ABORT.into();
265/// println!("{:?}", error);
266/// ```
267impl From<ErrorCode> for Error {
268	#[inline]
269	fn from(code: ErrorCode) -> Self {
270		Error {
271			code,
272			msg: String::new(),
273			payload: None,
274		}
275	}
276}
277
278/// Automatic wrapping in [`std::io::Error`] (if payload type is compatible).
279///
280/// ```rust
281/// # use std::convert::TryInto;
282/// # use pam_client::{Result, Error, ErrorCode};
283/// # fn some_succeeding_pam_function() -> Result<()> { Ok(()) }
284/// fn main() -> std::result::Result<(), std::io::Error> {
285///     some_succeeding_pam_function()?;
286///     Ok(())
287/// }
288/// ```
289/// ```rust,should_panic
290/// # use std::convert::{Infallible, TryInto};
291/// # use pam_client::{Result, Error, ErrorCode};
292/// # fn some_failing_pam_function() -> Result<Infallible> {
293/// #     Err(ErrorCode::ABORT.into())
294/// # }
295/// fn main() -> std::result::Result<(), std::io::Error> {
296///     some_failing_pam_function()?;
297///     Ok(())
298/// }
299/// ```
300impl<T: Send + Sync + Debug + 'static> From<ErrorWith<T>> for io::Error {
301	fn from(error: ErrorWith<T>) -> Self {
302		io::Error::new(
303			match error.code {
304				ErrorCode::INCOMPLETE => io::ErrorKind::Interrupted,
305				ErrorCode::BAD_ITEM | ErrorCode::USER_UNKNOWN => io::ErrorKind::NotFound,
306				ErrorCode::CRED_INSUFFICIENT | ErrorCode::PERM_DENIED => {
307					io::ErrorKind::PermissionDenied
308				}
309				_ => io::ErrorKind::Other,
310			},
311			Box::new(error),
312		)
313	}
314}
315
316#[cfg(test)]
317mod tests {
318	use super::*;
319	use crate::conv_null::Conversation;
320	use crate::Context;
321
322	#[test]
323	fn test_basic() {
324		let context = Context::new("test", None, Conversation::default()).unwrap();
325		let error = Error::new(context.handle(), ErrorCode::CONV_ERR).into_with_payload("foo");
326		assert_eq!(error.payload(), Some(&"foo"));
327		assert!(error.message().is_some());
328		assert!(format!("{:?}", error).len() > 1);
329		let mut error = error.map(|_| usize::MIN);
330		assert_eq!(error.payload(), Some(&usize::MIN));
331		let _ = error.take_payload();
332		assert_eq!(error.take_payload(), None);
333		assert!(format!("{:?}", error).contains("None"));
334		let error = error.map(|_| usize::MIN);
335		assert_eq!(error.payload(), None);
336		let error = error.into_without_payload();
337		assert_eq!(error.payload(), None);
338		assert!(format!("{:?} {}", error, error).len() > 4);
339		assert_eq!(io::Error::from(error).kind(), io::ErrorKind::Other);
340	}
341
342	#[test]
343	fn test_no_msg() {
344		let error = Error::from(ErrorCode::BAD_ITEM);
345		assert_eq!(
346			format!("{}", error),
347			format!("<{}>", (ErrorCode::BAD_ITEM as i32))
348		);
349		assert_eq!(format!("{:?}", &error), format!("{:?}", error.clone()));
350		assert!(error.message().is_none());
351		let error: ErrorWith<()> = error.into();
352		assert_eq!(io::Error::from(error).kind(), io::ErrorKind::NotFound);
353	}
354
355	/// Check if a hash can be calculated and equality works
356	#[test]
357	fn test_traits() {
358		use std::collections::hash_map::DefaultHasher;
359		use std::hash::{Hash, Hasher};
360
361		fn calc_hash<T: Hash>(t: &T) -> u64 {
362			let mut s = DefaultHasher::new();
363			t.hash(&mut s);
364			s.finish()
365		}
366
367		let error = Error::from(ErrorCode::BUF_ERR);
368
369		assert_eq!(calc_hash(&error), calc_hash(&error.clone()));
370		assert_eq!(&error, &error.clone());
371	}
372}