diff --git a/perlmod-macro/src/attribs.rs b/perlmod-macro/src/attribs.rs index 8d938d8..d439a91 100644 --- a/perlmod-macro/src/attribs.rs +++ b/perlmod-macro/src/attribs.rs @@ -129,6 +129,7 @@ pub struct FunctionAttrs { pub cv_variable: Option, pub prototype: Option, pub serialize_error: bool, + pub errno: bool, } impl TryFrom for FunctionAttrs { @@ -160,6 +161,8 @@ impl TryFrom for FunctionAttrs { attrs.raw_return = true; } else if path.is_ident("serialize_error") { attrs.serialize_error = true; + } else if path.is_ident("errno") { + attrs.errno = true; } else { error!(path => "unknown attribute"); } diff --git a/perlmod-macro/src/function.rs b/perlmod-macro/src/function.rs index 5f18bb3..52aec1f 100644 --- a/perlmod-macro/src/function.rs +++ b/perlmod-macro/src/function.rs @@ -368,6 +368,12 @@ fn handle_return_kind( TokenStream::new() }; + let copy_errno = if attr.errno { + quote! { ::perlmod::error::copy_errno_to_libc(); } + } else { + TokenStream::new() + }; + let pthx = crate::pthx_param(); match ret.value { ReturnValue::None => { @@ -398,7 +404,9 @@ fn handle_return_kind( #[doc(hidden)] #vis extern "C" fn #xs_name(#pthx #cv_arg_name: *mut ::perlmod::ffi::CV) { unsafe { - match #impl_xs_name(#cv_arg_passed) { + let res = #impl_xs_name(#cv_arg_passed); + #copy_errno + match res { Ok(()) => (), Err(sv) => ::perlmod::ffi::croak(sv), } @@ -441,7 +449,9 @@ fn handle_return_kind( #[doc(hidden)] #vis extern "C" fn #xs_name(#pthx #cv_arg_name: *mut ::perlmod::ffi::CV) { unsafe { - match #impl_xs_name(#cv_arg_passed) { + let res = #impl_xs_name(#cv_arg_passed); + #copy_errno + match res { Ok(sv) => ::perlmod::ffi::stack_push_raw(sv), Err(sv) => ::perlmod::ffi::croak(sv), } @@ -523,7 +533,9 @@ fn handle_return_kind( #[doc(hidden)] #vis extern "C" fn #xs_name(#pthx #cv_arg_name: *mut ::perlmod::ffi::CV) { unsafe { - match #impl_xs_name(#cv_arg_passed) { + let res = #impl_xs_name(#cv_arg_passed); + #copy_errno + match res { Ok(sv) => { #push }, Err(sv) => ::perlmod::ffi::croak(sv), } diff --git a/perlmod-test/src/pkg142.rs b/perlmod-test/src/pkg142.rs index becdc7f..28d33e4 100644 --- a/perlmod-test/src/pkg142.rs +++ b/perlmod-test/src/pkg142.rs @@ -92,7 +92,7 @@ mod export { b: String, } - #[export(serialize_error)] + #[export(serialize_error, errno)] fn test_deserialized_error(fail: bool) -> Result<&'static str, MyError> { if fail { Err(MyError { @@ -100,6 +100,7 @@ mod export { b: "second".to_string(), }) } else { + ::perlmod::error::set_errno(77); Ok("worked") } } diff --git a/perlmod/src/error.rs b/perlmod/src/error.rs index b47fe0a..0a942c3 100644 --- a/perlmod/src/error.rs +++ b/perlmod/src/error.rs @@ -1,6 +1,8 @@ //! Error types. +use std::cell::Cell; use std::fmt; +use std::os::raw::c_int; /// Error returned by `TryFrom` implementations between `Scalar`, `Array` and `Hash`. #[derive(Debug)] @@ -84,3 +86,30 @@ impl fmt::Display for MagicError { } impl std::error::Error for MagicError {} + +thread_local! { + static ERRNO: Cell = Cell::new(0); +} + +/// Set the perlmod-specific `errno` value. This is *not* libc's `errno`, but a separate storage +/// location not touched by other C or perl functions. An `#[export(errno)]` function will copy +/// this value to libc's errno location at right before returning into the perl stack (*after* all +/// side effects such as destructors have already finished). +pub fn set_errno(value: c_int) { + ERRNO.with(|v| v.set(value)) +} + +/// *Not* `libc`'s `errno`, this retrieves a value previously set with [`set_errno`], see its +/// description for details. +pub fn get_errno() -> c_int { + ERRNO.with(|v| v.get()) +} + +/// This is part of the proc-macro API and is of little use to users of this crate directly. +/// When manually implementing "xsubs" this can be used before returning as a shortcut to copying +/// the perlmod errno value to libc. +pub unsafe fn copy_errno_to_libc() { + unsafe { + libc::__errno_location().write(get_errno()); + } +} diff --git a/test.pl b/test.pl index 2958728..722bda8 100644 --- a/test.pl +++ b/test.pl @@ -122,6 +122,7 @@ my $sub = RSPM::Foo142::test_substr_return($orig); print("[$orig] [$sub]\n"); my $ok = RSPM::Foo142::test_deserialized_error(0); +die "test_deserialized_error failed to set errno value\n" if $! != 77; die "test_deserialized_error failed to return a value\n" if $ok ne 'worked'; $ok = eval { RSPM::Foo142::test_deserialized_error(1) }; die "test_deserialized_error error case returned a value\n" if defined $ok;