Mercurial > crates > nonstick
comparison src/libpam/question.rs @ 89:dd3e9c4bcde3
Simplify memory management in Questions.
When we're sending Questions to the client, we don't need them to be
C-managed, we just need the pointers going to the right place.
This replaces a bunch of Question management cruft with Vecs and Boxes.
| author | Paul Fisher <paul@pfish.zone> |
|---|---|
| date | Fri, 13 Jun 2025 05:22:48 -0400 |
| parents | 05291b601f0a |
| children | efc2b56c8928 |
comparison
equal
deleted
inserted
replaced
| 88:c9fc7e6257d3 | 89:dd3e9c4bcde3 |
|---|---|
| 9 use crate::libpam::{memory, pam_ffi}; | 9 use crate::libpam::{memory, pam_ffi}; |
| 10 use crate::ErrorCode; | 10 use crate::ErrorCode; |
| 11 use crate::Result; | 11 use crate::Result; |
| 12 use num_enum::{IntoPrimitive, TryFromPrimitive}; | 12 use num_enum::{IntoPrimitive, TryFromPrimitive}; |
| 13 use std::ffi::{c_void, CStr}; | 13 use std::ffi::{c_void, CStr}; |
| 14 use std::{iter, ptr, slice}; | 14 use std::{ptr, slice}; |
| 15 | 15 |
| 16 /// Abstraction of a collection of questions to be sent in a PAM conversation. | 16 /// Abstraction of a collection of questions to be sent in a PAM conversation. |
| 17 /// | 17 /// |
| 18 /// The PAM C API conversation function looks like this: | 18 /// The PAM C API conversation function looks like this: |
| 19 /// | 19 /// |
| 30 /// is treated as a pointer-to-pointers, like `int argc, char **argv`. | 30 /// is treated as a pointer-to-pointers, like `int argc, char **argv`. |
| 31 /// (In this situation, the value of `Questions.indirect` is | 31 /// (In this situation, the value of `Questions.indirect` is |
| 32 /// the pointer passed to `pam_conv`.) | 32 /// the pointer passed to `pam_conv`.) |
| 33 /// | 33 /// |
| 34 /// ```text | 34 /// ```text |
| 35 /// ╔═ Questions ═╗ points to ┌─ Indirect ─┐ ╔═ Question ═╗ | 35 /// points to ┌───────────────┐ ╔═ Question ═╗ |
| 36 /// ║ indirect ┄┄┄╫┄┄┄┄┄┄┄┄┄┄┄> │ base[0] ┄┄┄┼┄┄┄┄┄> ║ style ║ | 36 /// questions ┄┄┄┄┄┄┄┄┄┄> │ questions[0] ┄┼┄┄┄┄> ║ style ║ |
| 37 /// ║ count ║ │ base[1] ┄┄┄┼┄┄┄╮ ║ data ┄┄┄┄┄┄╫┄┄> ... | 37 /// │ questions[1] ┄┼┄┄┄╮ ║ data ┄┄┄┄┄┄╫┄┄> ... |
| 38 /// ╚═════════════╝ │ ... │ ┆ ╚════════════╝ | 38 /// │ ... │ ┆ ╚════════════╝ |
| 39 /// ┆ | 39 /// ┆ |
| 40 /// ┆ ╔═ Question ═╗ | 40 /// ┆ ╔═ Question ═╗ |
| 41 /// ╰┄┄> ║ style ║ | 41 /// ╰┄┄> ║ style ║ |
| 42 /// ║ data ┄┄┄┄┄┄╫┄┄> ... | 42 /// ║ data ┄┄┄┄┄┄╫┄┄> ... |
| 43 /// ╚════════════╝ | 43 /// ╚════════════╝ |
| 44 /// ``` | 44 /// ``` |
| 45 /// | 45 /// |
| 46 /// On OpenPAM and other compatible implementations (like Solaris), | 46 /// On OpenPAM and other compatible implementations (like Solaris), |
| 47 /// `messages` is a pointer-to-pointer-to-array. This appears to be | 47 /// `messages` is a pointer-to-pointer-to-array. This appears to be |
| 48 /// the correct implementation as required by the XSSO specification. | 48 /// the correct implementation as required by the XSSO specification. |
| 49 /// | 49 /// |
| 50 /// ```text | 50 /// ```text |
| 51 /// ╔═ Questions ═╗ points to ┌─ Indirect ─┐ ╔═ Question[] ═╗ | 51 /// points to ┌─────────────┐ ╔═ Question[] ═╗ |
| 52 /// ║ indirect ┄┄┄╫┄┄┄┄┄┄┄┄┄┄┄> │ base ┄┄┄┄┄┄┼┄┄┄┄┄> ║ style ║ | 52 /// questions ┄┄┄┄┄┄┄┄┄┄> │ *questions ┄┼┄┄┄┄┄> ║ style ║ |
| 53 /// ║ count ║ └────────────┘ ║ data ┄┄┄┄┄┄┄┄╫┄┄> ... | 53 /// └─────────────┘ ║ data ┄┄┄┄┄┄┄┄╫┄┄> ... |
| 54 /// ╚═════════════╝ ╟──────────────╢ | 54 /// ╟──────────────╢ |
| 55 /// ║ style ║ | 55 /// ║ style ║ |
| 56 /// ║ data ┄┄┄┄┄┄┄┄╫┄┄> ... | 56 /// ║ data ┄┄┄┄┄┄┄┄╫┄┄> ... |
| 57 /// ╟──────────────╢ | 57 /// ╟──────────────╢ |
| 58 /// ║ ... ║ | 58 /// ║ ... ║ |
| 59 /// ``` | 59 /// ``` |
| 60 #[derive(Debug)] | 60 pub trait QuestionsTrait { |
| 61 pub struct GenericQuestions<I: IndirectTrait> { | 61 /// Allocates memory for this indirector and all its members. |
| 62 /// An indirection to the questions themselves, stored on the C heap. | 62 fn new(messages: &[Message]) -> Result<Self> |
| 63 indirect: *mut I, | 63 where |
| 64 /// The number of questions. | 64 Self: Sized; |
| 65 count: usize, | 65 |
| 66 } | 66 /// Gets the pointer that is passed . |
| 67 | 67 fn ptr(&self) -> *const *const Question; |
| 68 impl<I: IndirectTrait> GenericQuestions<I> { | 68 |
| 69 /// Stores the provided questions on the C heap. | 69 /// Converts a pointer into a borrowed list of Questions. |
| 70 pub fn new(messages: &[Message]) -> Result<Self> { | |
| 71 let count = messages.len(); | |
| 72 let mut ret = Self { | |
| 73 indirect: I::alloc(count), | |
| 74 count, | |
| 75 }; | |
| 76 // Even if we fail partway through this, all our memory will be freed. | |
| 77 for (question, message) in iter::zip(ret.iter_mut(), messages) { | |
| 78 question.try_fill(message)? | |
| 79 } | |
| 80 Ok(ret) | |
| 81 } | |
| 82 | |
| 83 /// The pointer to the thing with the actual list. | |
| 84 pub fn indirect(&self) -> *const *const Question { | |
| 85 self.indirect.cast() | |
| 86 } | |
| 87 | |
| 88 pub fn iter(&self) -> impl Iterator<Item = &Question> { | |
| 89 // SAFETY: we're iterating over an amount we know. | |
| 90 unsafe { (*self.indirect).iter(self.count) } | |
| 91 } | |
| 92 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Question> { | |
| 93 // SAFETY: we're iterating over an amount we know. | |
| 94 unsafe { (*self.indirect).iter_mut(self.count) } | |
| 95 } | |
| 96 } | |
| 97 | |
| 98 impl<I: IndirectTrait> Drop for GenericQuestions<I> { | |
| 99 fn drop(&mut self) { | |
| 100 // SAFETY: We are valid and have a valid pointer. | |
| 101 // Once we're done, everything will be safe. | |
| 102 unsafe { | |
| 103 if let Some(indirect) = self.indirect.as_mut() { | |
| 104 indirect.free_contents(self.count) | |
| 105 } | |
| 106 memory::free(self.indirect); | |
| 107 self.indirect = ptr::null_mut(); | |
| 108 } | |
| 109 } | |
| 110 } | |
| 111 | |
| 112 /// The trait that each of the `Indirect` implementations implement. | |
| 113 /// | |
| 114 /// Basically a slice but with more meat. | |
| 115 pub trait IndirectTrait { | |
| 116 /// Converts a pointer into a borrowed `Self`. | |
| 117 /// | 70 /// |
| 118 /// # Safety | 71 /// # Safety |
| 119 /// | 72 /// |
| 120 /// You have to provide a valid pointer. | 73 /// You have to provide a valid pointer. |
| 121 unsafe fn borrow_ptr<'a>(ptr: *const *const Question) -> Option<&'a Self> | 74 unsafe fn borrow_ptr<'a>( |
| 122 where | 75 ptr: *const *const Question, |
| 123 Self: Sized, | 76 count: usize, |
| 124 { | 77 ) -> impl Iterator<Item = &'a Question>; |
| 125 ptr.cast::<Self>().as_ref() | 78 } |
| 126 } | 79 |
| 127 | |
| 128 /// Allocates memory for this indirector and all its members. | |
| 129 fn alloc(count: usize) -> *mut Self; | |
| 130 | |
| 131 /// Returns an iterator yielding the given number of messages. | |
| 132 /// | |
| 133 /// # Safety | |
| 134 /// | |
| 135 /// You have to provide the right count. | |
| 136 unsafe fn iter(&self, count: usize) -> impl Iterator<Item = &Question>; | |
| 137 | |
| 138 /// Returns a mutable iterator yielding the given number of messages. | |
| 139 /// | |
| 140 /// # Safety | |
| 141 /// | |
| 142 /// You have to provide the right count. | |
| 143 unsafe fn iter_mut(&mut self, count: usize) -> impl Iterator<Item = &mut Question>; | |
| 144 | |
| 145 /// Frees everything this points to. | |
| 146 /// | |
| 147 /// # Safety | |
| 148 /// | |
| 149 /// You have to pass the right size. | |
| 150 unsafe fn free_contents(&mut self, count: usize); | |
| 151 } | |
| 152 | |
| 153 /// An indirect reference to messages. | |
| 154 /// | |
| 155 /// This is kept separate to provide a place where we can separate | |
| 156 /// the pointer-to-pointer-to-list from pointer-to-list-of-pointers. | |
| 157 #[cfg(pam_impl = "linux-pam")] | 80 #[cfg(pam_impl = "linux-pam")] |
| 158 pub type Indirect = LinuxPamIndirect; | 81 pub type Questions = LinuxPamQuestions; |
| 159 | 82 |
| 160 /// An indirect reference to messages. | |
| 161 /// | |
| 162 /// This is kept separate to provide a place where we can separate | |
| 163 /// the pointer-to-pointer-to-list from pointer-to-list-of-pointers. | |
| 164 #[cfg(not(pam_impl = "linux-pam"))] | 83 #[cfg(not(pam_impl = "linux-pam"))] |
| 165 pub type Indirect = StandardIndirect; | 84 pub type Questions = XSsoQuestions; |
| 166 | 85 |
| 167 pub type Questions = GenericQuestions<Indirect>; | 86 /// The XSSO standard version of the pointer train to questions. |
| 168 | |
| 169 /// The XSSO standard version of the indirection layer between Question and Questions. | |
| 170 #[derive(Debug)] | 87 #[derive(Debug)] |
| 171 #[repr(C)] | 88 #[repr(C)] |
| 172 pub struct StandardIndirect { | 89 pub struct XSsoQuestions { |
| 173 base: *mut Question, | 90 /// Points to the memory address where the meat of `questions` is. |
| 91 /// **The memory layout of Vec is not specified**, and we need to return | |
| 92 /// a pointer to the pointer, hence we have to store it here. | |
| 93 pointer: *const Question, | |
| 94 questions: Vec<Question>, | |
| 174 _marker: Immovable, | 95 _marker: Immovable, |
| 175 } | 96 } |
| 176 | 97 |
| 177 impl IndirectTrait for StandardIndirect { | 98 impl XSsoQuestions { |
| 178 fn alloc(count: usize) -> *mut Self { | 99 fn len(&self) -> usize { |
| 179 let questions = memory::calloc(count); | 100 self.questions.len() |
| 180 let me_ptr: *mut Self = memory::calloc(1); | 101 } |
| 181 // SAFETY: We just allocated this, and we're putting a valid pointer in. | 102 fn iter_mut(&mut self) -> impl Iterator<Item = &mut Question> { |
| 182 unsafe { | 103 self.questions.iter_mut() |
| 183 let me = &mut *me_ptr; | 104 } |
| 184 me.base = questions; | 105 } |
| 185 } | 106 |
| 186 me_ptr | 107 impl QuestionsTrait for XSsoQuestions { |
| 187 } | 108 fn new(messages: &[Message]) -> Result<Self> { |
| 188 | 109 let questions: Result<Vec<_>> = messages.iter().map(Question::try_from).collect(); |
| 189 unsafe fn iter(&self, count: usize) -> impl Iterator<Item = &Question> { | 110 let questions = questions?; |
| 190 (0..count).map(|idx| &*self.base.add(idx)) | 111 Ok(Self { |
| 191 } | 112 pointer: questions.as_ptr(), |
| 192 | 113 questions, |
| 193 unsafe fn iter_mut(&mut self, count: usize) -> impl Iterator<Item = &mut Question> { | 114 _marker: Default::default(), |
| 194 (0..count).map(|idx| &mut *self.base.add(idx)) | 115 }) |
| 195 } | 116 } |
| 196 | 117 |
| 197 unsafe fn free_contents(&mut self, count: usize) { | 118 fn ptr(&self) -> *const *const Question { |
| 198 let msgs = slice::from_raw_parts_mut(self.base, count); | 119 &self.pointer as *const *const Question |
| 199 for msg in msgs { | 120 } |
| 200 msg.clear() | 121 |
| 201 } | 122 unsafe fn borrow_ptr<'a>( |
| 202 memory::free(self.base); | 123 ptr: *const *const Question, |
| 203 self.base = ptr::null_mut() | 124 count: usize, |
| 204 } | 125 ) -> impl Iterator<Item = &'a Question> { |
| 205 } | 126 slice::from_raw_parts(*ptr, count).iter() |
| 206 | 127 } |
| 207 /// The Linux version of the indirection layer between Question and Questions. | 128 } |
| 129 | |
| 130 /// The Linux version of the pointer train to questions. | |
| 208 #[derive(Debug)] | 131 #[derive(Debug)] |
| 209 #[repr(C)] | 132 #[repr(C)] |
| 210 pub struct LinuxPamIndirect { | 133 pub struct LinuxPamQuestions { |
| 211 base: [*mut Question; 0], | 134 #[allow(clippy::vec_box)] // we need to do this. |
| 135 /// The place where the questions are. | |
| 136 questions: Vec<Box<Question>>, | |
| 212 _marker: Immovable, | 137 _marker: Immovable, |
| 213 } | 138 } |
| 214 | 139 |
| 215 impl IndirectTrait for LinuxPamIndirect { | 140 impl LinuxPamQuestions { |
| 216 fn alloc(count: usize) -> *mut Self { | 141 fn len(&self) -> usize { |
| 217 // SAFETY: We're only allocating, and when we're done, | 142 self.questions.len() |
| 218 // everything will be in a known-good state. | 143 } |
| 219 let me_ptr: *mut Self = memory::calloc::<*mut Question>(count).cast(); | 144 |
| 220 unsafe { | 145 fn iter_mut(&mut self) -> impl Iterator<Item = &mut Question> { |
| 221 let me = &mut *me_ptr; | 146 self.questions.iter_mut().map(AsMut::as_mut) |
| 222 let ptr_list = slice::from_raw_parts_mut(me.base.as_mut_ptr(), count); | 147 } |
| 223 for entry in ptr_list { | 148 } |
| 224 *entry = memory::calloc(1); | 149 |
| 225 } | 150 impl QuestionsTrait for LinuxPamQuestions { |
| 226 } | 151 fn new(messages: &[Message]) -> Result<Self> { |
| 227 me_ptr | 152 let questions: Result<_> = messages |
| 228 } | 153 .iter() |
| 229 | 154 .map(|msg| Question::try_from(msg).map(Box::new)) |
| 230 unsafe fn iter(&self, count: usize) -> impl Iterator<Item = &Question> { | 155 .collect(); |
| 231 (0..count).map(|idx| &**self.base.as_ptr().add(idx)) | 156 Ok(Self { |
| 232 } | 157 questions: questions?, |
| 233 | 158 _marker: Default::default(), |
| 234 unsafe fn iter_mut(&mut self, count: usize) -> impl Iterator<Item = &mut Question> { | 159 }) |
| 235 (0..count).map(|idx| &mut **self.base.as_mut_ptr().add(idx)) | 160 } |
| 236 } | 161 |
| 237 | 162 fn ptr(&self) -> *const *const Question { |
| 238 unsafe fn free_contents(&mut self, count: usize) { | 163 self.questions.as_ptr().cast() |
| 239 let msgs = slice::from_raw_parts_mut(self.base.as_mut_ptr(), count); | 164 } |
| 240 for msg in msgs { | 165 |
| 241 if let Some(msg) = msg.as_mut() { | 166 unsafe fn borrow_ptr<'a>( |
| 242 msg.clear(); | 167 ptr: *const *const Question, |
| 243 } | 168 count: usize, |
| 244 memory::free(*msg); | 169 ) -> impl Iterator<Item = &'a Question> { |
| 245 *msg = ptr::null_mut(); | 170 slice::from_raw_parts(ptr.cast::<&Question>(), count) |
| 246 } | 171 .iter() |
| 172 .copied() | |
| 247 } | 173 } |
| 248 } | 174 } |
| 249 | 175 |
| 250 /// The C enum values for messages shown to the user. | 176 /// The C enum values for messages shown to the user. |
| 251 #[derive(Debug, PartialEq, TryFromPrimitive, IntoPrimitive)] | 177 #[derive(Debug, PartialEq, TryFromPrimitive, IntoPrimitive)] |
| 252 #[repr(u32)] | 178 #[repr(u32)] |
| 253 pub enum Style { | 179 enum Style { |
| 254 /// Requests information from the user; will be masked when typing. | 180 /// Requests information from the user; will be masked when typing. |
| 255 PromptEchoOff = pam_ffi::PAM_PROMPT_ECHO_OFF, | 181 PromptEchoOff = pam_ffi::PAM_PROMPT_ECHO_OFF, |
| 256 /// Requests information from the user; will not be masked. | 182 /// Requests information from the user; will not be masked. |
| 257 PromptEchoOn = pam_ffi::PAM_PROMPT_ECHO_ON, | 183 PromptEchoOn = pam_ffi::PAM_PROMPT_ECHO_ON, |
| 258 /// An error message. | 184 /// An error message. |
| 268 /// A Linux-PAM extension. | 194 /// A Linux-PAM extension. |
| 269 #[cfg(feature = "linux-pam-extensions")] | 195 #[cfg(feature = "linux-pam-extensions")] |
| 270 BinaryPrompt = pam_ffi::PAM_BINARY_PROMPT, | 196 BinaryPrompt = pam_ffi::PAM_BINARY_PROMPT, |
| 271 } | 197 } |
| 272 | 198 |
| 273 impl Default for Question { | 199 impl Question { |
| 274 fn default() -> Self { | 200 /// Gets this message's data pointer as a string. |
| 275 Self { | 201 /// |
| 276 style: Default::default(), | 202 /// # Safety |
| 277 data: ptr::null_mut(), | 203 /// |
| 278 _marker: Default::default(), | 204 /// It's up to you to pass this only on types with a string value. |
| 205 unsafe fn string_data(&self) -> Result<&str> { | |
| 206 if self.data.is_null() { | |
| 207 Ok("") | |
| 208 } else { | |
| 209 CStr::from_ptr(self.data.cast()) | |
| 210 .to_str() | |
| 211 .map_err(|_| ErrorCode::ConversationError) | |
| 279 } | 212 } |
| 280 } | 213 } |
| 281 } | 214 |
| 282 | 215 /// Gets this message's data pointer as borrowed binary data. |
| 283 impl Question { | 216 unsafe fn binary_data(&self) -> (&[u8], u8) { |
| 284 /// Replaces the contents of this question with the question | 217 self.data |
| 285 /// from the message. | 218 .cast::<CBinaryData>() |
| 286 /// | 219 .as_ref() |
| 287 /// If the message is not valid (invalid message type, bad contents, etc.), | 220 .map(Into::into) |
| 288 /// this will fail. | 221 .unwrap_or_default() |
| 289 pub fn try_fill(&mut self, msg: &Message) -> Result<()> { | 222 } |
| 223 } | |
| 224 | |
| 225 impl TryFrom<&Message<'_>> for Question { | |
| 226 type Error = ErrorCode; | |
| 227 fn try_from(msg: &Message) -> Result<Self> { | |
| 290 let alloc = |style, text| Ok((style, memory::malloc_str(text)?.cast())); | 228 let alloc = |style, text| Ok((style, memory::malloc_str(text)?.cast())); |
| 291 // We will only allocate heap data if we have a valid input. | 229 // We will only allocate heap data if we have a valid input. |
| 292 let (style, data): (_, *mut c_void) = match *msg { | 230 let (style, data): (_, *mut c_void) = match *msg { |
| 293 Message::MaskedPrompt(p) => alloc(Style::PromptEchoOff, p.question()), | 231 Message::MaskedPrompt(p) => alloc(Style::PromptEchoOff, p.question()), |
| 294 Message::Prompt(p) => alloc(Style::PromptEchoOn, p.question()), | 232 Message::Prompt(p) => alloc(Style::PromptEchoOn, p.question()), |
| 302 CBinaryData::alloc(p.question())?.cast(), | 240 CBinaryData::alloc(p.question())?.cast(), |
| 303 )), | 241 )), |
| 304 #[cfg(not(feature = "linux-pam-extensions"))] | 242 #[cfg(not(feature = "linux-pam-extensions"))] |
| 305 Message::RadioPrompt(_) | Message::BinaryPrompt(_) => Err(ErrorCode::ConversationError), | 243 Message::RadioPrompt(_) | Message::BinaryPrompt(_) => Err(ErrorCode::ConversationError), |
| 306 }?; | 244 }?; |
| 307 // Now that we know everything is valid, fill ourselves in. | 245 Ok(Self { |
| 308 self.clear(); | 246 style: style.into(), |
| 309 self.style = style.into(); | 247 data, |
| 310 self.data = data; | 248 _marker: Default::default(), |
| 311 Ok(()) | 249 }) |
| 312 } | 250 } |
| 313 | 251 } |
| 314 /// Gets this message's data pointer as a string. | 252 |
| 315 /// | 253 impl Drop for Question { |
| 316 /// # Safety | 254 fn drop(&mut self) { |
| 317 /// | |
| 318 /// It's up to you to pass this only on types with a string value. | |
| 319 unsafe fn string_data(&self) -> Result<&str> { | |
| 320 if self.data.is_null() { | |
| 321 Ok("") | |
| 322 } else { | |
| 323 CStr::from_ptr(self.data.cast()) | |
| 324 .to_str() | |
| 325 .map_err(|_| ErrorCode::ConversationError) | |
| 326 } | |
| 327 } | |
| 328 | |
| 329 /// Gets this message's data pointer as borrowed binary data. | |
| 330 unsafe fn binary_data(&self) -> (&[u8], u8) { | |
| 331 self.data | |
| 332 .cast::<CBinaryData>() | |
| 333 .as_ref() | |
| 334 .map(Into::into) | |
| 335 .unwrap_or_default() | |
| 336 } | |
| 337 | |
| 338 /// Zeroes out the data stored here. | |
| 339 fn clear(&mut self) { | |
| 340 // SAFETY: We either created this data or we got it from PAM. | 255 // SAFETY: We either created this data or we got it from PAM. |
| 341 // After this function is done, it will be zeroed out. | 256 // After this function is done, it will be zeroed out. |
| 342 unsafe { | 257 unsafe { |
| 343 // This is nice-to-have. We'll try to zero out the data | 258 // This is nice-to-have. We'll try to zero out the data |
| 344 // in the Question. If it's not a supported format, we skip it. | 259 // in the Question. If it's not a supported format, we skip it. |
| 407 macro_rules! tests { ($fn_name:ident<$typ:ident>) => { | 322 macro_rules! tests { ($fn_name:ident<$typ:ident>) => { |
| 408 mod $fn_name { | 323 mod $fn_name { |
| 409 use super::super::*; | 324 use super::super::*; |
| 410 #[test] | 325 #[test] |
| 411 fn standard() { | 326 fn standard() { |
| 412 let interrogation = GenericQuestions::<$typ>::new(&[ | 327 let interrogation = <$typ>::new(&[ |
| 413 MaskedQAndA::new("hocus pocus").message(), | 328 MaskedQAndA::new("hocus pocus").message(), |
| 414 QAndA::new("what").message(), | 329 QAndA::new("what").message(), |
| 415 QAndA::new("who").message(), | 330 QAndA::new("who").message(), |
| 416 InfoMsg::new("hey").message(), | 331 InfoMsg::new("hey").message(), |
| 417 ErrorMsg::new("gasp").message(), | 332 ErrorMsg::new("gasp").message(), |
| 418 ]) | 333 ]) |
| 419 .unwrap(); | 334 .unwrap(); |
| 420 let indirect = interrogation.indirect(); | 335 let indirect = interrogation.ptr(); |
| 421 | 336 |
| 422 let remade = unsafe { $typ::borrow_ptr(indirect) }.unwrap(); | 337 let remade = unsafe { $typ::borrow_ptr(indirect, interrogation.len()) }; |
| 423 let messages: Vec<OwnedMessage> = unsafe { remade.iter(interrogation.count) } | 338 let messages: Vec<OwnedMessage> = remade |
| 424 .map(TryInto::try_into) | 339 .map(TryInto::try_into) |
| 425 .collect::<Result<_>>() | 340 .collect::<Result<_>>() |
| 426 .unwrap(); | 341 .unwrap(); |
| 427 let [masked, what, who, hey, gasp] = messages.try_into().unwrap(); | 342 let [masked, what, who, hey, gasp] = messages.try_into().unwrap(); |
| 428 assert_matches!(masked => OwnedMessage::MaskedPrompt, "hocus pocus"); | 343 assert_matches!(masked => OwnedMessage::MaskedPrompt, "hocus pocus"); |
| 434 | 349 |
| 435 #[test] | 350 #[test] |
| 436 #[cfg(not(feature = "linux-pam-extensions"))] | 351 #[cfg(not(feature = "linux-pam-extensions"))] |
| 437 fn no_linux_extensions() { | 352 fn no_linux_extensions() { |
| 438 use crate::conv::{BinaryQAndA, RadioQAndA}; | 353 use crate::conv::{BinaryQAndA, RadioQAndA}; |
| 439 GenericQuestions::<$typ>::new(&[ | 354 <$typ>::new(&[ |
| 440 BinaryQAndA::new((&[5, 4, 3, 2, 1], 66)).message(), | 355 BinaryQAndA::new((&[5, 4, 3, 2, 1], 66)).message(), |
| 441 RadioQAndA::new("you must choose").message(), | 356 RadioQAndA::new("you must choose").message(), |
| 442 ]).unwrap_err(); | 357 ]).unwrap_err(); |
| 443 } | 358 } |
| 444 | 359 |
| 445 #[test] | 360 #[test] |
| 446 #[cfg(feature = "linux-pam-extensions")] | 361 #[cfg(feature = "linux-pam-extensions")] |
| 447 fn linux_extensions() { | 362 fn linux_extensions() { |
| 448 let interrogation = GenericQuestions::<$typ>::new(&[ | 363 let interrogation = <$typ>::new(&[ |
| 449 BinaryQAndA::new((&[5, 4, 3, 2, 1], 66)).message(), | 364 BinaryQAndA::new((&[5, 4, 3, 2, 1], 66)).message(), |
| 450 RadioQAndA::new("you must choose").message(), | 365 RadioQAndA::new("you must choose").message(), |
| 451 ]).unwrap(); | 366 ]).unwrap(); |
| 452 let indirect = interrogation.indirect(); | 367 let indirect = interrogation.ptr(); |
| 453 | 368 |
| 454 let remade = unsafe { $typ::borrow_ptr(indirect) }.unwrap(); | 369 let remade = unsafe { $typ::borrow_ptr(indirect, interrogation.len()) }; |
| 455 let messages: Vec<OwnedMessage> = unsafe { remade.iter(interrogation.count) } | 370 let messages: Vec<OwnedMessage> = unsafe { remade } |
| 456 .map(TryInto::try_into) | 371 .map(TryInto::try_into) |
| 457 .collect::<Result<_>>() | 372 .collect::<Result<_>>() |
| 458 .unwrap(); | 373 .unwrap(); |
| 459 let [bin, choose] = messages.try_into().unwrap(); | 374 let [bin, choose] = messages.try_into().unwrap(); |
| 460 assert_matches!(bin => OwnedMessage::BinaryPrompt, (&[5, 4, 3, 2, 1][..], 66)); | 375 assert_matches!(bin => OwnedMessage::BinaryPrompt, (&[5, 4, 3, 2, 1][..], 66)); |
| 461 assert_matches!(choose => OwnedMessage::RadioPrompt, "you must choose"); | 376 assert_matches!(choose => OwnedMessage::RadioPrompt, "you must choose"); |
| 462 } | 377 } |
| 463 } | 378 } |
| 464 }} | 379 }} |
| 465 | 380 |
| 466 tests!(test_xsso<StandardIndirect>); | 381 tests!(test_xsso<XSsoQuestions>); |
| 467 tests!(test_linux<LinuxPamIndirect>); | 382 tests!(test_linux<LinuxPamQuestions>); |
| 468 } | 383 } |
