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 }