diff src/constants.rs @ 59:3f4a77aa88be

Fix string copyting and improve error situation. This change is too big and includes several things: - Fix copying strings from PAM by fixing const and mut on pam funcs. - Improve error enums by simplifying conversions and removing unnecessary and ambiguous "success" variants. - Make a bunch of casts nicer. - Assorted other cleanup.
author Paul Fisher <paul@pfish.zone>
date Wed, 21 May 2025 00:27:18 -0400
parents daa2cde64601
children 05cc2c27334f
line wrap: on
line diff
--- a/src/constants.rs	Mon May 05 00:16:04 2025 -0400
+++ b/src/constants.rs	Wed May 21 00:27:18 2025 -0400
@@ -1,7 +1,9 @@
 use bitflags::bitflags;
 use libc::{c_int, c_uint};
-use num_derive::{FromPrimitive, ToPrimitive};
-use num_traits::{FromPrimitive, ToPrimitive};
+use num_derive::FromPrimitive;
+use num_traits::FromPrimitive;
+use std::any;
+use std::marker::PhantomData;
 // TODO: Import constants from C header file at compile time.
 
 // The Linux-PAM flags
@@ -21,7 +23,7 @@
 }
 
 /// Styles of message that are shown to the user.
-#[derive(Debug, PartialEq, FromPrimitive, ToPrimitive)]
+#[derive(Debug, PartialEq, FromPrimitive)]
 #[non_exhaustive] // non-exhaustive because C might give us back anything!
 pub enum MessageStyle {
     /// Requests information from the user; will be masked when typing.
@@ -39,9 +41,16 @@
     BinaryPrompt = 7,
 }
 
+impl TryFrom<c_int> for MessageStyle {
+    type Error = InvalidEnum<Self>;
+    fn try_from(value: c_int) -> Result<Self, Self::Error> {
+        Self::from_i32(value).ok_or(value.into())
+    }
+}
+
 impl From<MessageStyle> for c_int {
     fn from(val: MessageStyle) -> Self {
-        val.to_i32().unwrap_or(0)
+        val as Self
     }
 }
 
@@ -51,7 +60,7 @@
 /// For more detailed information, see
 /// `/usr/include/security/_pam_types.h`.
 #[allow(non_camel_case_types, dead_code)]
-#[derive(Copy, Clone, Debug, PartialEq, thiserror::Error, FromPrimitive, ToPrimitive)]
+#[derive(Copy, Clone, Debug, PartialEq, thiserror::Error, FromPrimitive)]
 #[non_exhaustive] // C might give us anything!
 pub enum ErrorCode {
     #[error("dlopen() failure when dynamically loading a service module")]
@@ -129,16 +138,51 @@
     }
 
     /// Converts a C result code into a PamResult, with success as Ok.
+    /// Invalid values are returned as a [Self::SystemError].
     pub fn result_from(value: c_int) -> PamResult<()> {
         match value {
             0 => Ok(()),
-            value => Err(Self::from_i64(value as i64).unwrap_or(Self::ConversationError))
+            value => Err(value.try_into().unwrap_or(Self::SystemError)),
         }
     }
 }
 
+impl TryFrom<c_int> for ErrorCode {
+    type Error = InvalidEnum<Self>;
+
+    fn try_from(value: c_int) -> Result<Self, Self::Error> {
+        Self::from_i32(value).ok_or(value.into())
+    }
+}
+
 impl From<ErrorCode> for c_int {
     fn from(val: ErrorCode) -> Self {
-        val.to_i32().unwrap_or(0)
+        val as Self
+    }
+}
+
+/// Error returned when attempting to coerce an invalid C integer into an enum.
+#[derive(thiserror::Error)]
+#[error("{} is not a valid {}", .0, any::type_name::<T>())]
+#[derive(Debug, PartialEq)]
+pub struct InvalidEnum<T>(std::ffi::c_int, PhantomData<T>);
+
+impl<T> From<std::ffi::c_int> for InvalidEnum<T> {
+    fn from(value: std::ffi::c_int) -> Self {
+        Self(value, PhantomData)
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_enums() {
+        assert_eq!(Ok(MessageStyle::ErrorMsg), 3.try_into());
+        assert_eq!(Err(999.into()), ErrorCode::try_from(999));
+        assert_eq!(Ok(()), ErrorCode::result_from(0));
+        assert_eq!(Err(ErrorCode::Abort), ErrorCode::result_from(26));
+        assert_eq!(Err(ErrorCode::SystemError), ErrorCode::result_from(423));
+    }
+}