Mercurial > crates > nonstick
comparison libpam-sys/src/helpers.rs @ 119:476a22db8639
Add PtrPtrVec to make it easy to pass pointer-to-pointers to PAM.
| author | Paul Fisher <paul@pfish.zone> |
|---|---|
| date | Mon, 30 Jun 2025 01:40:28 -0400 |
| parents | 39760dfc9b3b |
| children | 98a624cacd82 |
comparison
equal
deleted
inserted
replaced
| 118:39760dfc9b3b | 119:476a22db8639 |
|---|---|
| 3 | 3 |
| 4 use std::error::Error; | 4 use std::error::Error; |
| 5 use std::marker::{PhantomData, PhantomPinned}; | 5 use std::marker::{PhantomData, PhantomPinned}; |
| 6 use std::mem::ManuallyDrop; | 6 use std::mem::ManuallyDrop; |
| 7 use std::ptr::NonNull; | 7 use std::ptr::NonNull; |
| 8 use std::{fmt, slice}; | 8 use std::{any, fmt, mem, slice}; |
| 9 | |
| 10 /// A pointer-to-pointer-to-message container for the [conversation callback]. | |
| 11 /// | |
| 12 /// The PAM conversation callback requires a pointer to a pointer of [message]s. | |
| 13 /// Linux-PAM handles this differently than all other PAM implementations | |
| 14 /// (including the X/SSO PAM standard). | |
| 15 /// | |
| 16 /// X/SSO appears to specify a pointer-to-pointer-to-array: | |
| 17 /// | |
| 18 /// ```text | |
| 19 /// points to ┌────────────┐ ╔═ Message[] ═╗ | |
| 20 /// messages ┄┄┄┄┄┄┄┄┄┄> │ *messages ┄┼┄┄┄┄┄> ║ style ║ | |
| 21 /// └────────────┘ ║ data ┄┄┄┄┄┄┄╫┄┄> ... | |
| 22 /// ╟─────────────╢ | |
| 23 /// ║ style ║ | |
| 24 /// ║ data ┄┄┄┄┄┄┄╫┄┄> ... | |
| 25 /// ╟─────────────╢ | |
| 26 /// ║ ... ║ | |
| 27 /// ``` | |
| 28 /// | |
| 29 /// whereas Linux-PAM uses an `**argv`-style pointer-to-array-of-pointers: | |
| 30 /// | |
| 31 /// ```text | |
| 32 /// points to ┌──────────────┐ ╔═ Message ═╗ | |
| 33 /// messages ┄┄┄┄┄┄┄┄┄┄> │ messages[0] ┄┼┄┄┄┄> ║ style ║ | |
| 34 /// │ messages[1] ┄┼┄┄┄╮ ║ data ┄┄┄┄┄╫┄┄> ... | |
| 35 /// │ ... │ ┆ ╚═══════════╝ | |
| 36 /// ┆ | |
| 37 /// ┆ ╔═ Message ═╗ | |
| 38 /// ╰┄┄> ║ style ║ | |
| 39 /// ║ data ┄┄┄┄┄╫┄┄> ... | |
| 40 /// ╚═══════════╝ | |
| 41 /// ``` | |
| 42 /// | |
| 43 /// Because the `messages` remain owned by the application which calls into PAM, | |
| 44 /// we can solve this with One Simple Trick: make the intermediate list point | |
| 45 /// into the same array: | |
| 46 /// | |
| 47 /// ```text | |
| 48 /// points to ┌──────────────┐ ╔═ Message[] ═╗ | |
| 49 /// messages ┄┄┄┄┄┄┄┄┄┄> │ messages[0] ┄┼┄┄┄┄> ║ style ║ | |
| 50 /// │ messages[1] ┄┼┄┄╮ ║ data ┄┄┄┄┄┄┄╫┄┄> ... | |
| 51 /// │ ... │ ┆ ╟─────────────╢ | |
| 52 /// ╰┄> ║ style ║ | |
| 53 /// ║ data ┄┄┄┄┄┄┄╫┄┄> ... | |
| 54 /// ╟─────────────╢ | |
| 55 /// ║ ... ║ | |
| 56 /// | |
| 57 /// ``` | |
| 58 /// | |
| 59 /// [conversation callback]: crate::ConversationCallback | |
| 60 /// [message]: crate::Message | |
| 61 #[derive(Debug)] | |
| 62 pub struct PtrPtrVec<T> { | |
| 63 data: Vec<T>, | |
| 64 pointers: Vec<*const T>, | |
| 65 } | |
| 66 | |
| 67 impl<T> PtrPtrVec<T> { | |
| 68 /// Takes ownership of the given Vec and creates a vec of pointers to it. | |
| 69 pub fn new(data: Vec<T>) -> Self { | |
| 70 let pointers: Vec<_> = data.iter().map(|r| r as *const T).collect(); | |
| 71 Self { data, pointers } | |
| 72 } | |
| 73 | |
| 74 /// Gives you back your Vec. | |
| 75 pub fn into_inner(self) -> Vec<T> { | |
| 76 self.data | |
| 77 } | |
| 78 | |
| 79 /// Gets a pointer-to-pointer suitable for passing into the Conversation. | |
| 80 pub fn as_ptr<Dest>(&self) -> *const *const Dest { | |
| 81 Self::assert_size::<Dest>(); | |
| 82 self.pointers.as_ptr().cast::<*const Dest>() | |
| 83 } | |
| 84 | |
| 85 /// Iterates over a Linux-PAM–style pointer-to-array-of-pointers. | |
| 86 /// | |
| 87 /// # Safety | |
| 88 /// | |
| 89 /// `ptr_ptr` must be a valid pointer to an array of pointers, | |
| 90 /// there must be at least `count` valid pointers in the array, | |
| 91 /// and each pointer in that array must point to a valid `T`. | |
| 92 #[deprecated = "use [`Self::iter_over`] instead, unless you really need this specific version"] | |
| 93 #[allow(dead_code)] | |
| 94 pub unsafe fn iter_over_linux<'a, Src>( | |
| 95 ptr_ptr: *const *const Src, | |
| 96 count: usize, | |
| 97 ) -> impl Iterator<Item = &'a T> | |
| 98 where | |
| 99 T: 'a, | |
| 100 { | |
| 101 Self::assert_size::<Src>(); | |
| 102 slice::from_raw_parts(ptr_ptr.cast::<&T>(), count) | |
| 103 .iter() | |
| 104 .copied() | |
| 105 } | |
| 106 | |
| 107 /// Iterates over an X/SSO–style pointer-to-pointer-to-array. | |
| 108 /// | |
| 109 /// # Safety | |
| 110 /// | |
| 111 /// You must pass a valid pointer to a valid pointer to an array, | |
| 112 /// there must be at least `count` elements in the array, | |
| 113 /// and each value in that array must be a valid `T`. | |
| 114 #[deprecated = "use [`Self::iter_over`] instead, unless you really need this specific version"] | |
| 115 #[allow(dead_code)] | |
| 116 pub unsafe fn iter_over_xsso<'a, Src>( | |
| 117 ptr_ptr: *const *const Src, | |
| 118 count: usize, | |
| 119 ) -> impl Iterator<Item = &'a T> | |
| 120 where | |
| 121 T: 'a, | |
| 122 { | |
| 123 Self::assert_size::<Src>(); | |
| 124 slice::from_raw_parts(*ptr_ptr.cast(), count).iter() | |
| 125 } | |
| 126 | |
| 127 #[crate::cfg_pam_impl("LinuxPam")] | |
| 128 unsafe fn _iter_over<'a, Src>( | |
| 129 ptr_ptr: *const *const Src, | |
| 130 count: usize, | |
| 131 ) -> impl Iterator<Item = &'a T> | |
| 132 where | |
| 133 T: 'a, | |
| 134 { | |
| 135 #[allow(deprecated)] | |
| 136 Self::iter_over_linux(ptr_ptr, count) | |
| 137 } | |
| 138 | |
| 139 #[crate::cfg_pam_impl(not("LinuxPam"))] | |
| 140 unsafe fn _iter_over<'a, Src>( | |
| 141 ptr_ptr: *const *const Src, | |
| 142 count: usize, | |
| 143 ) -> impl Iterator<Item = &'a T> | |
| 144 where | |
| 145 T: 'a, | |
| 146 { | |
| 147 #[allow(deprecated)] | |
| 148 Self::iter_over_xsso(ptr_ptr, count) | |
| 149 } | |
| 150 | |
| 151 /// Iterates over a PAM message list appropriate to your system's impl. | |
| 152 /// | |
| 153 /// This selects the correct pointer/array structure to use for a message | |
| 154 /// that was given to you by your system. | |
| 155 /// | |
| 156 /// # Safety | |
| 157 /// | |
| 158 /// `ptr_ptr` must point to a valid message list, there must be at least | |
| 159 /// `count` messages in the list, and all messages must be a valid `Src`. | |
| 160 pub unsafe fn iter_over<'a, Src>( | |
| 161 ptr_ptr: *const *const Src, | |
| 162 count: usize, | |
| 163 ) -> impl Iterator<Item = &'a T> | |
| 164 where | |
| 165 T: 'a, | |
| 166 { | |
| 167 Self::_iter_over(ptr_ptr, count) | |
| 168 } | |
| 169 | |
| 170 fn assert_size<That>() { | |
| 171 debug_assert_eq!( | |
| 172 mem::size_of::<T>(), | |
| 173 mem::size_of::<That>(), | |
| 174 "type {t} is not the size of {that}", | |
| 175 t = any::type_name::<T>(), | |
| 176 that = any::type_name::<That>(), | |
| 177 ); | |
| 178 } | |
| 179 } | |
| 9 | 180 |
| 10 /// Error returned when attempting to allocate a buffer that is too big. | 181 /// Error returned when attempting to allocate a buffer that is too big. |
| 11 /// | 182 /// |
| 12 /// This is specifically used in [`OwnedBinaryPayload`] when you try to allocate | 183 /// This is specifically used in [`OwnedBinaryPayload`] when you try to allocate |
| 13 /// a message larger than 2<sup>32</sup> bytes. | 184 /// a message larger than 2<sup>32</sup> bytes. |
| 239 } | 410 } |
| 240 | 411 |
| 241 #[cfg(test)] | 412 #[cfg(test)] |
| 242 mod tests { | 413 mod tests { |
| 243 use super::*; | 414 use super::*; |
| 415 use std::ptr; | |
| 244 | 416 |
| 245 type VecPayload = OwnedBinaryPayload<Vec<u8>>; | 417 type VecPayload = OwnedBinaryPayload<Vec<u8>>; |
| 246 | 418 |
| 247 #[test] | 419 #[test] |
| 248 fn test_binary_payload() { | 420 fn test_binary_payload() { |
| 280 size: 0x1_0000_0001 | 452 size: 0x1_0000_0001 |
| 281 }, | 453 }, |
| 282 VecPayload::new(5, &data).unwrap_err() | 454 VecPayload::new(5, &data).unwrap_err() |
| 283 ) | 455 ) |
| 284 } | 456 } |
| 285 } | 457 |
| 458 #[test] | |
| 459 #[should_panic] | |
| 460 #[cfg(debug_assertions)] | |
| 461 fn test_new_wrong_size() { | |
| 462 let bad_vec = vec![0; 19]; | |
| 463 let msg = PtrPtrVec::new(bad_vec); | |
| 464 let _ = msg.as_ptr::<u64>(); | |
| 465 } | |
| 466 | |
| 467 #[test] | |
| 468 #[should_panic] | |
| 469 #[cfg(debug_assertions)] | |
| 470 fn test_iter_xsso_wrong_size() { | |
| 471 unsafe { | |
| 472 _ = PtrPtrVec::<u8>::iter_over_xsso::<f64>(ptr::null(), 1); | |
| 473 } | |
| 474 } | |
| 475 | |
| 476 #[test] | |
| 477 #[should_panic] | |
| 478 #[cfg(debug_assertions)] | |
| 479 fn test_iter_linux_wrong_size() { | |
| 480 unsafe { | |
| 481 _ = PtrPtrVec::<u128>::iter_over_linux::<()>(ptr::null(), 1); | |
| 482 } | |
| 483 } | |
| 484 | |
| 485 #[allow(deprecated)] | |
| 486 #[test] | |
| 487 fn test_right_size() { | |
| 488 let good_vec = vec![(1u64, 2u64), (3, 4), (5, 6)]; | |
| 489 let ptr = good_vec.as_ptr(); | |
| 490 let msg = PtrPtrVec::new(good_vec); | |
| 491 let msg_ref: *const *const (i64, i64) = msg.as_ptr(); | |
| 492 assert_eq!(unsafe { *msg_ref }, ptr.cast()); | |
| 493 | |
| 494 let linux_result: Vec<(i64, i64)> = unsafe { PtrPtrVec::iter_over_linux(msg_ref, 3) } | |
| 495 .cloned() | |
| 496 .collect(); | |
| 497 let xsso_result: Vec<(i64, i64)> = unsafe { PtrPtrVec::iter_over_xsso(msg_ref, 3) } | |
| 498 .cloned() | |
| 499 .collect(); | |
| 500 assert_eq!(vec![(1, 2), (3, 4), (5, 6)], linux_result); | |
| 501 assert_eq!(vec![(1, 2), (3, 4), (5, 6)], xsso_result); | |
| 502 drop(msg) | |
| 503 } | |
| 504 | |
| 505 #[allow(deprecated)] | |
| 506 #[test] | |
| 507 fn test_iter_ptr_ptr() { | |
| 508 let strs = vec![Box::new("a"), Box::new("b"), Box::new("c"), Box::new("D")]; | |
| 509 let ptr: *const *const &str = strs.as_ptr().cast(); | |
| 510 let got: Vec<&str> = unsafe { | |
| 511 PtrPtrVec::iter_over_linux(ptr, 4) | |
| 512 }.cloned().collect(); | |
| 513 assert_eq!(vec!["a", "b", "c", "D"], got); | |
| 514 | |
| 515 let nums = vec![-1i8, 2, 3]; | |
| 516 let ptr = nums.as_ptr(); | |
| 517 let got: Vec<u8> = unsafe { PtrPtrVec::iter_over_xsso(&ptr, 3)}.cloned().collect(); | |
| 518 assert_eq!(vec![255, 2, 3], got); | |
| 519 } | |
| 520 } |
