Mercurial > crates > nonstick
comparison src/libpam/handle.rs @ 163:a75a66cb4181
Add end-to-end tests; fix issues found by tests.
- Create tests and installer/remover shell script
- Fix Pointer/pointee problems
- Add Debug formatting
- Misc cleanup
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Mon, 14 Jul 2025 17:40:11 -0400 |
parents | 634cd5f2ac8b |
children | 2f5913131295 |
comparison
equal
deleted
inserted
replaced
162:180237d0b498 | 163:a75a66cb4181 |
---|---|
16 use std::cell::Cell; | 16 use std::cell::Cell; |
17 use std::ffi::{c_char, c_int, c_void, CString, OsStr, OsString}; | 17 use std::ffi::{c_char, c_int, c_void, CString, OsStr, OsString}; |
18 use std::mem::ManuallyDrop; | 18 use std::mem::ManuallyDrop; |
19 use std::os::unix::ffi::OsStrExt; | 19 use std::os::unix::ffi::OsStrExt; |
20 use std::ptr::NonNull; | 20 use std::ptr::NonNull; |
21 use std::{fmt, ptr}; | 21 use std::{any, fmt, ptr}; |
22 | 22 |
23 /// An owned PAM handle. | 23 /// An owned PAM handle. |
24 pub struct LibPamTransaction<C: Conversation> { | 24 pub struct LibPamTransaction<C: Conversation> { |
25 /// The handle itself. | 25 /// The handle itself. We guarantee this will not be null. |
26 handle: ManuallyDrop<LibPamHandle>, | 26 handle: *mut LibPamHandle, |
27 /// The last return value from the handle. | 27 /// The last return value from the handle. |
28 last_return: Cell<Result<()>>, | 28 last_return: Cell<Result<()>>, |
29 /// If set, the Conversation that this PAM handle owns. | 29 /// If set, the Conversation that this PAM handle owns. |
30 /// | 30 /// |
31 /// We have to hold on to this because the PAM specification doesn't | 31 /// We have to hold on to this because the PAM specification doesn't |
34 /// that you pass in to `pam_start`, but OpenPAM uses the pointer itself, | 34 /// that you pass in to `pam_start`, but OpenPAM uses the pointer itself, |
35 /// so you have to keep it in one place. | 35 /// so you have to keep it in one place. |
36 conversation: Box<OwnedConversation<C>>, | 36 conversation: Box<OwnedConversation<C>>, |
37 } | 37 } |
38 | 38 |
39 impl<C: Conversation> fmt::Debug for LibPamTransaction<C> { | |
40 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
41 f.debug_struct(any::type_name::<Self>()) | |
42 .field("handle", &format!("{:p}", self.handle)) | |
43 .field("last_return", &self.last_return.get()) | |
44 .field("conversation", &format!("{:p}", self.conversation)) | |
45 .finish() | |
46 } | |
47 } | |
48 | |
39 #[derive(Debug, PartialEq)] | 49 #[derive(Debug, PartialEq)] |
40 pub struct TransactionBuilder { | 50 pub struct TransactionBuilder { |
41 service_name: OsString, | 51 service_name: OsString, |
42 username: Option<OsString>, | 52 username: Option<OsString>, |
43 } | 53 } |
74 self.username = Some(username.as_ref().into()); | 84 self.username = Some(username.as_ref().into()); |
75 self | 85 self |
76 } | 86 } |
77 | 87 |
78 /// Builds the PAM handle and starts the transaction. | 88 /// Builds the PAM handle and starts the transaction. |
79 pub fn build(self, conv: impl Conversation) -> Result<LibPamTransaction<impl Conversation>> { | 89 pub fn build<C: Conversation>(self, conv: C) -> Result<LibPamTransaction<C>> { |
80 LibPamTransaction::start(self.service_name, self.username, conv) | 90 LibPamTransaction::start(self.service_name, self.username, conv) |
81 } | 91 } |
82 } | 92 } |
83 | 93 |
84 impl<C: Conversation> LibPamTransaction<C> { | 94 impl<C: Conversation> LibPamTransaction<C> { |
85 fn start(service_name: OsString, username: Option<OsString>, conversation: C) -> Result<Self> { | 95 fn start(service_name: OsString, username: Option<OsString>, conversation: C) -> Result<Self> { |
86 let conv = Box::new(OwnedConversation::new(conversation)); | 96 let mut conv = Box::new(OwnedConversation::new(conversation)); |
87 let service_cstr = CString::new(service_name.as_bytes()).expect("null is forbidden"); | 97 let service_cstr = CString::new(service_name.as_bytes()).expect("null is forbidden"); |
88 let username_cstr = memory::option_cstr_os(username.as_deref()); | 98 let username_cstr = memory::option_cstr_os(username.as_deref()); |
89 let username_cstr = memory::prompt_ptr(username_cstr.as_deref()); | 99 let username_cstr = memory::prompt_ptr(username_cstr.as_deref()); |
90 | 100 |
91 let mut handle: *mut libpam_sys::pam_handle = ptr::null_mut(); | 101 let mut handle: *mut libpam_sys::pam_handle = ptr::null_mut(); |
102 let conv_ptr: *mut OwnedConversation<_> = conv.as_mut() as _; | |
92 // SAFETY: We've set everything up properly to call `pam_start`. | 103 // SAFETY: We've set everything up properly to call `pam_start`. |
93 // The returned value will be a valid pointer provided the result is OK. | 104 // The returned value will be a valid pointer provided the result is OK. |
94 let result = unsafe { | 105 let result = unsafe { |
95 libpam_sys::pam_start( | 106 libpam_sys::pam_start( |
96 service_cstr.as_ptr(), | 107 service_cstr.as_ptr(), |
97 username_cstr, | 108 username_cstr, |
98 (conv.as_ref() as *const OwnedConversation<C>) | 109 conv_ptr.cast(), |
99 .cast_mut() | |
100 .cast(), | |
101 &mut handle, | 110 &mut handle, |
102 ) | 111 ) |
103 }; | 112 }; |
104 ErrorCode::result_from(result)?; | 113 ErrorCode::result_from(result)?; |
105 let handle = NonNull::new(handle).ok_or(ErrorCode::BufferError)?; | 114 let handle = NonNull::new(handle).ok_or(ErrorCode::BufferError)?; |
106 Ok(Self { | 115 Ok(Self { |
107 handle: ManuallyDrop::new(LibPamHandle(handle)), | 116 handle: handle.as_ptr().cast(), |
108 last_return: Cell::new(Ok(())), | 117 last_return: Cell::new(Ok(())), |
109 conversation: conv, | 118 conversation: conv, |
110 }) | 119 }) |
111 } | 120 } |
112 | 121 |
137 } | 146 } |
138 | 147 |
139 /// Internal "end" function, which binary-ORs the status with `or_with`. | 148 /// Internal "end" function, which binary-ORs the status with `or_with`. |
140 fn end_internal(&mut self, or_with: i32) { | 149 fn end_internal(&mut self, or_with: i32) { |
141 let result = ErrorCode::result_to_c(self.last_return.get()) | or_with; | 150 let result = ErrorCode::result_to_c(self.last_return.get()) | or_with; |
142 unsafe { libpam_sys::pam_end(self.handle.raw_mut(), result) }; | 151 unsafe { libpam_sys::pam_end(self.handle.cast(), result) }; |
143 } | 152 } |
144 } | 153 } |
145 | 154 |
146 macro_rules! wrap { | 155 macro_rules! wrap { |
147 (fn $name:ident { $pam_func:ident }) => { | 156 (fn $name:ident { $pam_func:ident }) => { |
148 fn $name(&mut self, flags: Flags) -> Result<()> { | 157 fn $name(&mut self, flags: Flags) -> Result<()> { |
149 ErrorCode::result_from(unsafe { libpam_sys::$pam_func(self.0.as_mut(), flags.bits()) }) | 158 ErrorCode::result_from(unsafe { |
159 libpam_sys::$pam_func((self as *mut Self).cast(), flags.bits()) | |
160 }) | |
150 } | 161 } |
151 }; | 162 }; |
152 } | 163 } |
153 | 164 |
154 impl Transaction for LibPamHandle { | 165 impl Transaction for LibPamHandle { |
158 } | 169 } |
159 | 170 |
160 // TODO: pam_setcred - app | 171 // TODO: pam_setcred - app |
161 // pam_open_session - app | 172 // pam_open_session - app |
162 // pam_close_session - app | 173 // pam_close_session - app |
163 // pam_set/get_data - module | |
164 | 174 |
165 impl<C: Conversation> Drop for LibPamTransaction<C> { | 175 impl<C: Conversation> Drop for LibPamTransaction<C> { |
166 /// Closes the PAM session on an owned PAM handle. | 176 /// Closes the PAM session on an owned PAM handle. |
167 /// | 177 /// |
168 /// This internally calls `pam_end` with the appropriate error code. | 178 /// This internally calls `pam_end` with the appropriate error code. |
179 | 189 |
180 macro_rules! delegate { | 190 macro_rules! delegate { |
181 // First have the kind that save the result after delegation. | 191 // First have the kind that save the result after delegation. |
182 (fn $meth:ident(&self $(, $param:ident: $typ:ty)*) -> Result<$ret:ty>) => { | 192 (fn $meth:ident(&self $(, $param:ident: $typ:ty)*) -> Result<$ret:ty>) => { |
183 fn $meth(&self $(, $param: $typ)*) -> Result<$ret> { | 193 fn $meth(&self $(, $param: $typ)*) -> Result<$ret> { |
184 let result = self.handle.$meth($($param),*); | 194 let result = unsafe { &*self.handle }.$meth($($param),*); |
185 self.last_return.set(split(&result)); | 195 self.last_return.set(split(&result)); |
186 result | 196 result |
187 } | 197 } |
188 }; | 198 }; |
189 (fn $meth:ident(&mut self $(, $param:ident: $typ:ty)*) -> Result<$ret:ty>) => { | 199 (fn $meth:ident(&mut self $(, $param:ident: $typ:ty)*) -> Result<$ret:ty>) => { |
190 fn $meth(&mut self $(, $param: $typ)*) -> Result<$ret> { | 200 fn $meth(&mut self $(, $param: $typ)*) -> Result<$ret> { |
191 let result = self.handle.$meth($($param),*); | 201 let result = unsafe { &mut *self.handle }.$meth($($param),*); |
192 self.last_return.set(split(&result)); | 202 self.last_return.set(split(&result)); |
193 result | 203 result |
194 } | 204 } |
195 }; | 205 }; |
196 // Then have the kind that are just raw delegates | 206 // Then have the kind that are just raw delegates |
197 (fn $meth:ident(&self $(, $param:ident: $typ:ty)*) -> $ret:ty) => { | 207 (fn $meth:ident(&self $(, $param:ident: $typ:ty)*) -> $ret:ty) => { |
198 fn $meth(&self $(, $param: $typ)*) -> $ret { | 208 fn $meth(&self $(, $param: $typ)*) -> $ret { |
199 self.handle.$meth($($param),*) | 209 unsafe { &*self.handle }.$meth($($param),*) |
200 } | 210 } |
201 }; | 211 }; |
202 (fn $meth:ident(&mut self $(, $param:ident: $typ:ty)*) -> $ret:ty) => { | 212 (fn $meth:ident(&mut self $(, $param:ident: $typ:ty)*) -> $ret:ty) => { |
203 fn $meth(&mut self $(, $param: $typ)*) -> $ret { | 213 fn $meth(&mut self $(, $param: $typ)*) -> $ret { |
204 self.handle.$meth($($param),*) | 214 unsafe { &mut *self.handle }.$meth($($param),*) |
205 } | 215 } |
206 }; | 216 }; |
207 // Then have item getters / setters | 217 // Then have item getters / setters |
208 (get = $get:ident$(, set = $set:ident)?) => { | 218 (get = $get:ident$(, set = $set:ident)?) => { |
209 delegate!(fn $get(&self) -> Result<Option<OsString>>); | 219 delegate!(fn $get(&self) -> Result<Option<OsString>>); |
243 /// a PAM handle created by another library. | 253 /// a PAM handle created by another library. |
244 /// | 254 /// |
245 /// If [`Self::end`] is not called, this will always call `pam_end` reporting | 255 /// If [`Self::end`] is not called, this will always call `pam_end` reporting |
246 /// successful completion. | 256 /// successful completion. |
247 #[repr(transparent)] | 257 #[repr(transparent)] |
248 pub struct LibPamHandle(NonNull<libpam_sys::pam_handle>); | 258 pub struct LibPamHandle(libpam_sys::pam_handle); |
249 | 259 |
250 impl LibPamHandle { | 260 impl LibPamHandle { |
251 /// Takes ownership of the pointer to the given PAM handle. | |
252 /// | |
253 /// **Do not use this just to get a reference to a PAM handle.** | |
254 /// | |
255 /// # Safety | |
256 /// | |
257 /// - The pointer must point to a valid PAM handle. | |
258 /// - The conversation associated with the handle must remain valid | |
259 /// for as long as the handle is open. | |
260 pub unsafe fn from_ptr(handle: NonNull<libpam_sys::pam_handle>) -> Self { | |
261 Self(handle) | |
262 } | |
263 | |
264 /// Ends the transaction, reporting `error_code` to cleanup callbacks. | 261 /// Ends the transaction, reporting `error_code` to cleanup callbacks. |
265 /// | 262 /// |
266 /// # References | 263 /// # References |
267 #[doc = linklist!(pam_end: adg, _std)] | 264 #[doc = linklist!(pam_end: adg, _std)] |
268 /// | 265 /// |
269 #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_end")] | 266 #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_end")] |
270 #[doc = stdlinks!(3 pam_end)] | 267 #[doc = stdlinks!(3 pam_end)] |
271 pub fn end(self, result: Result<()>) { | 268 pub fn end(&mut self, result: Result<()>) { |
272 let mut me = ManuallyDrop::new(self); | 269 unsafe { libpam_sys::pam_end(self.inner_mut(), ErrorCode::result_to_c(result)) }; |
273 unsafe { libpam_sys::pam_end(me.raw_mut(), ErrorCode::result_to_c(result)) }; | |
274 } | 270 } |
275 | 271 |
276 #[cfg_attr( | 272 #[cfg_attr( |
277 not(pam_impl = "LinuxPam"), | 273 not(pam_impl = "LinuxPam"), |
278 doc = "Exactly equivalent to [`Self::end`], except on Linux-PAM." | 274 doc = "Exactly equivalent to [`Self::end`], except on Linux-PAM." |
292 /// # References | 288 /// # References |
293 #[doc = linklist!(pam_end: adg, _std)] | 289 #[doc = linklist!(pam_end: adg, _std)] |
294 /// | 290 /// |
295 #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_end")] | 291 #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_end")] |
296 #[doc = stdlinks!(3 pam_end)] | 292 #[doc = stdlinks!(3 pam_end)] |
297 pub fn end_silent(self, result: Result<()>) { | 293 pub fn end_silent(&mut self, result: Result<()>) { |
298 let mut me = ManuallyDrop::new(self); | |
299 let result = ErrorCode::result_to_c(result); | 294 let result = ErrorCode::result_to_c(result); |
300 #[cfg(pam_impl = "LinuxPam")] | 295 #[cfg(pam_impl = "LinuxPam")] |
301 let result = result | libpam_sys::PAM_DATA_SILENT; | 296 let result = result | libpam_sys::PAM_DATA_SILENT; |
302 unsafe { | 297 unsafe { |
303 libpam_sys::pam_end(me.raw_mut(), result); | 298 libpam_sys::pam_end(self.inner_mut(), result); |
304 } | 299 } |
305 } | |
306 | |
307 /// Consumes this and gives you back the raw PAM handle. | |
308 pub fn into_inner(self) -> NonNull<libpam_sys::pam_handle> { | |
309 let me = ManuallyDrop::new(self); | |
310 me.0 | |
311 } | 300 } |
312 | 301 |
313 /// Gets a reference to the inner PAM handle. | 302 /// Gets a reference to the inner PAM handle. |
314 pub fn raw_ref(&self) -> &libpam_sys::pam_handle { | 303 pub fn inner(&self) -> &libpam_sys::pam_handle { |
315 unsafe { self.0.as_ref() } | 304 &self.0 |
316 } | 305 } |
317 /// Gets a mutable reference to the inner PAM handle. | 306 /// Gets a mutable reference to the inner PAM handle. |
318 pub fn raw_mut(&mut self) -> &mut libpam_sys::pam_handle { | 307 pub fn inner_mut(&mut self) -> &mut libpam_sys::pam_handle { |
319 unsafe { self.0.as_mut() } | 308 &mut self.0 |
320 } | |
321 } | |
322 | |
323 impl Drop for LibPamHandle { | |
324 fn drop(&mut self) { | |
325 unsafe { libpam_sys::pam_end(self.0.as_mut(), 0) }; | |
326 } | 309 } |
327 } | 310 } |
328 | 311 |
329 impl Logger for LibPamHandle { | 312 impl Logger for LibPamHandle { |
330 fn log(&self, level: Level, loc: Location<'_>, entry: fmt::Arguments) { | 313 fn log(&self, level: Level, loc: Location<'_>, entry: fmt::Arguments) { |
342 }; | 325 }; |
343 _ = loc; | 326 _ = loc; |
344 // SAFETY: We're calling this function with a known value. | 327 // SAFETY: We're calling this function with a known value. |
345 #[cfg(pam_impl = "LinuxPam")] | 328 #[cfg(pam_impl = "LinuxPam")] |
346 unsafe { | 329 unsafe { |
347 libpam_sys::pam_syslog( | 330 libpam_sys::pam_syslog(self.inner(), level, b"%s\0".as_ptr().cast(), entry.as_ptr()) |
348 self.raw_ref(), | |
349 level, | |
350 b"%s\0".as_ptr().cast(), | |
351 entry.as_ptr(), | |
352 ) | |
353 } | 331 } |
354 #[cfg(pam_impl = "Sun")] | 332 #[cfg(pam_impl = "Sun")] |
355 unsafe { | 333 unsafe { |
356 libpam_sys::__pam_log(level, b"%s\0".as_ptr().cast(), entry.as_ptr()) | 334 libpam_sys::__pam_log(level, b"%s\0".as_ptr().cast(), entry.as_ptr()) |
357 } | 335 } |
382 fn username(&mut self, prompt: Option<&OsStr>) -> Result<OsString> { | 360 fn username(&mut self, prompt: Option<&OsStr>) -> Result<OsString> { |
383 let prompt = memory::option_cstr_os(prompt); | 361 let prompt = memory::option_cstr_os(prompt); |
384 let mut output: *const c_char = ptr::null(); | 362 let mut output: *const c_char = ptr::null(); |
385 let ret = unsafe { | 363 let ret = unsafe { |
386 libpam_sys::pam_get_user( | 364 libpam_sys::pam_get_user( |
387 self.raw_mut(), | 365 self.inner_mut(), |
388 &mut output, | 366 &mut output, |
389 memory::prompt_ptr(prompt.as_deref()), | 367 memory::prompt_ptr(prompt.as_deref()), |
390 ) | 368 ) |
391 }; | 369 }; |
392 ErrorCode::result_from(ret)?; | 370 ErrorCode::result_from(ret)?; |
438 // and corrupt its value's data. | 416 // and corrupt its value's data. |
439 let full_key = module_data_key::<T>(key); | 417 let full_key = module_data_key::<T>(key); |
440 let mut ptr: *const c_void = ptr::null(); | 418 let mut ptr: *const c_void = ptr::null(); |
441 unsafe { | 419 unsafe { |
442 ErrorCode::result_from(libpam_sys::pam_get_data( | 420 ErrorCode::result_from(libpam_sys::pam_get_data( |
443 self.raw_ref(), | 421 self.inner(), |
444 full_key.as_ptr(), | 422 full_key.as_ptr(), |
445 &mut ptr, | 423 &mut ptr, |
446 )) | 424 )) |
447 .ok()?; | 425 .ok()?; |
448 | 426 |
453 fn set_module_data<T: 'static>(&mut self, key: &str, data: T) -> Result<()> { | 431 fn set_module_data<T: 'static>(&mut self, key: &str, data: T) -> Result<()> { |
454 let full_key = module_data_key::<T>(key); | 432 let full_key = module_data_key::<T>(key); |
455 let data = Box::new(data); | 433 let data = Box::new(data); |
456 ErrorCode::result_from(unsafe { | 434 ErrorCode::result_from(unsafe { |
457 libpam_sys::pam_set_data( | 435 libpam_sys::pam_set_data( |
458 self.raw_mut(), | 436 self.inner_mut(), |
459 full_key.as_ptr(), | 437 full_key.as_ptr(), |
460 Box::into_raw(data).cast(), | 438 Box::into_raw(data).cast(), |
461 drop_module_data::<T>, | 439 drop_module_data::<T>, |
462 ) | 440 ) |
463 }) | 441 }) |
502 let prompt = memory::option_cstr_os(prompt); | 480 let prompt = memory::option_cstr_os(prompt); |
503 let mut output: *const c_char = ptr::null(); | 481 let mut output: *const c_char = ptr::null(); |
504 // SAFETY: We're calling this with known-good values. | 482 // SAFETY: We're calling this with known-good values. |
505 let res = unsafe { | 483 let res = unsafe { |
506 libpam_sys::pam_get_authtok( | 484 libpam_sys::pam_get_authtok( |
507 self.raw_mut(), | 485 self.inner_mut(), |
508 item_type.into(), | 486 item_type.into(), |
509 &mut output, | 487 &mut output, |
510 memory::prompt_ptr(prompt.as_deref()), | 488 memory::prompt_ptr(prompt.as_deref()), |
511 ) | 489 ) |
512 }; | 490 }; |
523 // that you specify where you want the authtok to come from. | 501 // that you specify where you want the authtok to come from. |
524 // First we see if there's an authtok already set. | 502 // First we see if there's an authtok already set. |
525 let mut output: *mut c_char = ptr::null_mut(); | 503 let mut output: *mut c_char = ptr::null_mut(); |
526 let result = unsafe { | 504 let result = unsafe { |
527 libpam_sys::__pam_get_authtok( | 505 libpam_sys::__pam_get_authtok( |
528 self.raw_mut(), | 506 self.inner_mut(), |
529 libpam_sys::PAM_HANDLE, | 507 libpam_sys::PAM_HANDLE, |
530 item_type.into(), | 508 item_type.into(), |
531 ptr::null(), | 509 ptr::null(), |
532 &mut output, | 510 &mut output, |
533 ) | 511 ) |
541 drop(output); | 519 drop(output); |
542 let mut output: *mut c_char = ptr::null_mut(); | 520 let mut output: *mut c_char = ptr::null_mut(); |
543 let prompt = memory::option_cstr_os(prompt); | 521 let prompt = memory::option_cstr_os(prompt); |
544 let result = unsafe { | 522 let result = unsafe { |
545 libpam_sys::__pam_get_authtok( | 523 libpam_sys::__pam_get_authtok( |
546 self.raw_mut(), | 524 self.inner_mut(), |
547 libpam_sys::PAM_PROMPT, | 525 libpam_sys::PAM_PROMPT, |
548 item_type.into(), | 526 item_type.into(), |
549 memory::prompt_ptr(prompt.as_deref()), | 527 memory::prompt_ptr(prompt.as_deref()), |
550 &mut output, | 528 &mut output, |
551 ) | 529 ) |
557 .ok_or(ErrorCode::ConversationError) | 535 .ok_or(ErrorCode::ConversationError) |
558 } | 536 } |
559 | 537 |
560 /// Gets the `PAM_CONV` item from the handle. | 538 /// Gets the `PAM_CONV` item from the handle. |
561 fn conversation_item(&self) -> Result<&PamConv> { | 539 fn conversation_item(&self) -> Result<&PamConv> { |
562 let output: *const PamConv = ptr::null_mut(); | 540 let mut output: *const c_void = ptr::null(); |
563 let result = unsafe { | 541 let result = unsafe { |
564 libpam_sys::pam_get_item( | 542 libpam_sys::pam_get_item(self.inner(), ItemType::Conversation.into(), &mut output) |
565 self.raw_ref(), | |
566 ItemType::Conversation.into(), | |
567 &mut output.cast(), | |
568 ) | |
569 }; | 543 }; |
570 ErrorCode::result_from(result)?; | 544 ErrorCode::result_from(result)?; |
545 let output: *const PamConv = output.cast(); | |
571 // SAFETY: We got this result from PAM, and we're checking if it's null. | 546 // SAFETY: We got this result from PAM, and we're checking if it's null. |
572 unsafe { output.as_ref() }.ok_or(ErrorCode::ConversationError) | 547 unsafe { output.as_ref() }.ok_or(ErrorCode::ConversationError) |
573 } | 548 } |
574 } | 549 } |
575 | 550 |