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 } |