From f738cd4d976f4f72159bbcbfa7b451c33f0ea74a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 11 Sep 2019 01:15:20 +0200 Subject: [PATCH] More Rust FFI adventures We can now convert Rust Errors to C++ exceptions. At the Rust->C++ FFI boundary, Result will cause Error to be converted to and thrown as a C++ exception. --- nix-rust/src/error.rs | 27 ++++++++++- nix-rust/src/lib.rs | 29 ++++++++++-- nix-rust/src/tarfile.rs | 20 ++++---- src/libstore/builtins/unpack-channel.cc | 2 +- src/libstore/rust.cc | 12 +++++ src/libstore/rust.hh | 61 ++++++++++++++++++++++++- 6 files changed, 133 insertions(+), 18 deletions(-) create mode 100644 src/libstore/rust.cc diff --git a/nix-rust/src/error.rs b/nix-rust/src/error.rs index 28d0abdef..a2003be6f 100644 --- a/nix-rust/src/error.rs +++ b/nix-rust/src/error.rs @@ -1,5 +1,30 @@ #[derive(Debug)] pub enum Error { + IOError(std::io::Error), Misc(String), - Foreign(libc::c_void), // == std::exception_ptr + Foreign(CppException), +} + +impl From for Error { + fn from(err: std::io::Error) -> Self { + Error::IOError(err) + } +} + +impl From for CppException { + fn from(err: Error) -> Self { + match err { + Error::Foreign(ex) => ex, + Error::Misc(s) => unsafe { make_error(&s) }, + Error::IOError(err) => unsafe { make_error(&err.to_string()) }, + } + } +} + +#[repr(C)] +#[derive(Debug)] +pub struct CppException(*const libc::c_void); // == std::exception_ptr* + +extern "C" { + fn make_error(s: &str) -> CppException; } diff --git a/nix-rust/src/lib.rs b/nix-rust/src/lib.rs index 192ca29e4..b6b0d746d 100644 --- a/nix-rust/src/lib.rs +++ b/nix-rust/src/lib.rs @@ -4,7 +4,30 @@ mod tarfile; pub use error::Error; -#[no_mangle] -pub extern "C" fn unpack_tarfile(source: foreign::Source, dest_dir: &str) { - tarfile::unpack_tarfile(source, dest_dir).unwrap(); +pub struct CBox { + ptr: *mut libc::c_void, + phantom: std::marker::PhantomData, +} + +impl CBox { + fn new(t: T) -> Self { + unsafe { + let size = std::mem::size_of::(); + let ptr = libc::malloc(size); + eprintln!("PTR = {:?}, SIZE = {}", ptr, size); + *(ptr as *mut T) = t; // FIXME: probably UB + Self { + ptr, + phantom: std::marker::PhantomData, + } + } + } +} + +#[no_mangle] +pub extern "C" fn unpack_tarfile( + source: foreign::Source, + dest_dir: &str, +) -> CBox> { + CBox::new(tarfile::unpack_tarfile(source, dest_dir).map_err(|err| err.into())) } diff --git a/nix-rust/src/tarfile.rs b/nix-rust/src/tarfile.rs index 696118e4d..797aa5064 100644 --- a/nix-rust/src/tarfile.rs +++ b/nix-rust/src/tarfile.rs @@ -10,19 +10,19 @@ pub fn unpack_tarfile(source: Source, dest_dir: &str) -> Result<(), Error> { let mut tar = Archive::new(source); - for file in tar.entries().unwrap() { - let mut file = file.unwrap(); + for file in tar.entries()? { + let mut file = file?; - let dest_file = dest_dir.join(file.path().unwrap()); + let dest_file = dest_dir.join(file.path()?); - fs::create_dir_all(dest_file.parent().unwrap()).unwrap(); + fs::create_dir_all(dest_file.parent().unwrap())?; match file.header().entry_type() { tar::EntryType::Directory => { - fs::create_dir(dest_file).unwrap(); + fs::create_dir(dest_file)?; } tar::EntryType::Regular => { - let mode = if file.header().mode().unwrap() & libc::S_IXUSR == 0 { + let mode = if file.header().mode()? & libc::S_IXUSR == 0 { 0o666 } else { 0o777 @@ -31,13 +31,11 @@ pub fn unpack_tarfile(source: Source, dest_dir: &str) -> Result<(), Error> { .create(true) .write(true) .mode(mode) - .open(dest_file) - .unwrap(); - io::copy(&mut file, &mut f).unwrap(); + .open(dest_file)?; + io::copy(&mut file, &mut f)?; } tar::EntryType::Symlink => { - std::os::unix::fs::symlink(file.header().link_name().unwrap().unwrap(), dest_file) - .unwrap(); + std::os::unix::fs::symlink(file.header().link_name()?.unwrap(), dest_file)?; } t => return Err(Error::Misc(format!("unsupported tar entry type '{:?}'", t))), } diff --git a/src/libstore/builtins/unpack-channel.cc b/src/libstore/builtins/unpack-channel.cc index 88202ec6b..2da26d98e 100644 --- a/src/libstore/builtins/unpack-channel.cc +++ b/src/libstore/builtins/unpack-channel.cc @@ -27,7 +27,7 @@ void builtinUnpackChannel(const BasicDerivation & drv) decompressor->finish(); }); - unpack_tarfile(*source, out); + unpack_tarfile(*source, out).use()->unwrap(); auto entries = readDirectory(out); if (entries.size() != 1) diff --git a/src/libstore/rust.cc b/src/libstore/rust.cc new file mode 100644 index 000000000..a616d83a6 --- /dev/null +++ b/src/libstore/rust.cc @@ -0,0 +1,12 @@ +#include "logging.hh" +#include "rust.hh" + +namespace nix { + +extern "C" std::exception_ptr * make_error(rust::StringSlice s) +{ + // FIXME: leak + return new std::exception_ptr(std::make_exception_ptr(Error(std::string(s.ptr, s.size)))); +} + +} diff --git a/src/libstore/rust.hh b/src/libstore/rust.hh index 4366e4723..4c7720a44 100644 --- a/src/libstore/rust.hh +++ b/src/libstore/rust.hh @@ -4,7 +4,8 @@ namespace rust { // Depending on the internal representation of Rust slices is slightly // evil... -template struct Slice +template +struct Slice { T * ptr; size_t size; @@ -37,8 +38,64 @@ struct Source } }; +/* C++ representation of Rust's Result. */ +template +struct Result +{ + unsigned int tag; + + union { + T data; + std::exception_ptr * exc; + }; + + /* Rethrow the wrapped exception or return the wrapped value. */ + T unwrap() + { + if (tag == 0) + return data; + else if (tag == 1) + std::rethrow_exception(*exc); + else + abort(); + } +}; + +template +struct CBox +{ + T * ptr; + + T * operator ->() + { + return ptr; + } + + CBox(T * ptr) : ptr(ptr) { } + CBox(const CBox &) = delete; + CBox(CBox &&) = delete; + + ~CBox() + { + free(ptr); + } +}; + +// Grrr, this is only needed because 'extern "C"' functions don't +// support non-POD return types (and CBox has a destructor so it's not +// POD). +template +struct CBox2 +{ + T * ptr; + CBox use() + { + return CBox(ptr); + } +}; + } extern "C" { - void unpack_tarfile(rust::Source source, rust::StringSlice dest_dir); + rust::CBox2>> unpack_tarfile(rust::Source source, rust::StringSlice dest_dir); }