Hiroyuki Katsura
2019-Aug-05 06:59 UTC
[Libguestfs] [PATCH 2/2] Rust bindings: Implement callback handlers
This patch includes: - Event callback handlers - Tests related to events(410-430) src/bin/event.rs and src/bin/event_leak.rs are the PoCs that Boxes related to callbacks are not leaked. --- rust/src/bin/.gitkeep | 0 rust/src/bin/event.rs | 29 ++++++ rust/src/bin/event_leak.rs | 30 ++++++ rust/src/error.rs | 6 ++ rust/src/event.rs | 146 ++++++++++++++++++++++++++++ rust/tests/410_close_event.rs | 39 ++++++++ rust/tests/420_log_messages.rs | 60 ++++++++++++ rust/tests/430_progress_messages.rs | 59 +++++++++++ 8 files changed, 369 insertions(+) delete mode 100644 rust/src/bin/.gitkeep create mode 100644 rust/src/bin/event.rs create mode 100644 rust/src/bin/event_leak.rs create mode 100644 rust/tests/410_close_event.rs create mode 100644 rust/tests/420_log_messages.rs create mode 100644 rust/tests/430_progress_messages.rs diff --git a/rust/src/bin/.gitkeep b/rust/src/bin/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/rust/src/bin/event.rs b/rust/src/bin/event.rs new file mode 100644 index 000000000..3a3aa7d7c --- /dev/null +++ b/rust/src/bin/event.rs @@ -0,0 +1,29 @@ +extern crate guestfs; +use guestfs::*; + +fn main() { + for _ in 0..256 { + let mut g = match Handle::create() { + Ok(g) => g, + Err(e) => panic!(format!(" could not create handle {:?}", e)), + }; + g.set_event_callback( + |e, _, _, _| match e { + Event::Close => print!("c"), + _ => print!("o"), + }, + &EVENT_ALL, + ) + .unwrap(); + let eh = g + .set_event_callback(|_, _, _, _| print!("n"), &EVENT_ALL) + .unwrap(); + g.set_trace(true).unwrap(); + g.delete_event_callback(eh).unwrap(); + g.set_trace(false).unwrap(); + } + let _v = vec![0; 1024 * 1024]; + // no leak + // mem::forget(v); + println!() +} diff --git a/rust/src/bin/event_leak.rs b/rust/src/bin/event_leak.rs new file mode 100644 index 000000000..176de3c9a --- /dev/null +++ b/rust/src/bin/event_leak.rs @@ -0,0 +1,30 @@ +extern crate guestfs; +use guestfs::*; +use std::mem; + +fn main() { + for _ in 0..256 { + let mut g = match Handle::create() { + Ok(g) => g, + Err(e) => panic!(format!(" could not create handle {:?}", e)), + }; + g.set_event_callback( + |e, _, _, _| match e { + Event::Close => print!("c"), + _ => print!("o"), + }, + &EVENT_ALL, + ) + .unwrap(); + let eh = g + .set_event_callback(|_, _, _, _| print!("n"), &EVENT_ALL) + .unwrap(); + g.set_trace(true).unwrap(); + g.delete_event_callback(eh).unwrap(); + g.set_trace(false).unwrap(); + } + let v = vec![0; 1024 * 1024]; + // leak + mem::forget(v); + println!() +} diff --git a/rust/src/error.rs b/rust/src/error.rs index e526121e8..ce444e199 100644 --- a/rust/src/error.rs +++ b/rust/src/error.rs @@ -20,6 +20,7 @@ use crate::base; use crate::utils; use std::convert; use std::ffi; +use std::io; use std::os::raw::{c_char, c_int}; use std::str; @@ -41,6 +42,7 @@ pub enum Error { API(APIError), IllegalString(ffi::NulError), Utf8Error(str::Utf8Error), + UnixError(io::Error, &'static str), Create, } @@ -56,6 +58,10 @@ impl convert::From<str::Utf8Error> for Error { } } +pub(crate) fn unix_error(operation: &'static str) -> Error { + Error::UnixError(io::Error::last_os_error(), operation) +} + impl<'a> base::Handle<'a> { pub(crate) fn get_error_from_handle(&self, operation: &'static str) -> Error { let c_msg = unsafe { guestfs_last_error(self.g) }; diff --git a/rust/src/event.rs b/rust/src/event.rs index c363e913a..752e73610 100644 --- a/rust/src/event.rs +++ b/rust/src/event.rs @@ -1,4 +1,150 @@ +/* libguestfs Rust bindings + * Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +use crate::base; +use crate::error; +use crate::guestfs; +use crate::utils; +use std::os::raw::{c_char, c_void}; +use std::slice; + +type GuestfsEventCallback = extern "C" fn( + *const base::guestfs_h, + *const c_void, + u64, + i32, + i32, + *const i8, + usize, + *const u64, + usize, +); + +#[link(name = "guestfs")] +extern "C" { + fn guestfs_set_event_callback( + g: *const base::guestfs_h, + cb: GuestfsEventCallback, + event_bitmask: u64, + flags: i32, + opaque: *const c_void, + ) -> i32; + fn guestfs_delete_event_callback(g: *const base::guestfs_h, eh: i32); + fn guestfs_event_to_string(bitmask: u64) -> *const c_char; + fn free(buf: *const c_void); +} + #[derive(Hash, PartialEq, Eq)] pub struct EventHandle { eh: i32, } + +fn events_to_bitmask(v: &[guestfs::Event]) -> u64 { + let mut r = 0u64; + for x in v.iter() { + r |= x.to_u64(); + } + r +} + +pub fn event_to_string(events: &[guestfs::Event]) -> Result<String, error::Error> { + let bitmask = events_to_bitmask(events); + + let r = unsafe { guestfs_event_to_string(bitmask) }; + if r.is_null() { + Err(error::unix_error("event_to_string")) + } else { + let s = unsafe { utils::char_ptr_to_string(r) }; + unsafe { free(r as *const c_void) }; + Ok(s?) + } +} + +impl<'a> base::Handle<'a> { + pub fn set_event_callback<C: 'a>( + &mut self, + callback: C, + events: &[guestfs::Event], + ) -> Result<EventHandle, error::Error> + where + C: Fn(guestfs::Event, EventHandle, &[u8], &[u64]) + 'a, + { + extern "C" fn trampoline<C>( + _g: *const base::guestfs_h, + opaque: *const c_void, + event: u64, + event_handle: i32, + _flags: i32, + buf: *const c_char, + buf_len: usize, + array: *const u64, + array_len: usize, + ) where + C: Fn(guestfs::Event, EventHandle, &[u8], &[u64]), + { + // trampoline function + // c.f. https://s3.amazonaws.com/temp.michaelfbryan.com/callbacks/index.html + + let event = match guestfs::Event::from_bitmask(event) { + Some(x) => x, + None => panic!("Failed to parse bitmask: {}", event), + }; + let eh = EventHandle { eh: event_handle }; + let buf = unsafe { slice::from_raw_parts(buf as *const u8, buf_len) }; + let array = unsafe { slice::from_raw_parts(array, array_len) }; + + let callback: &Box<dyn Fn(guestfs::Event, EventHandle, &[u8], &[u64])> + Box::leak(unsafe { Box::from_raw(opaque as *mut _) }); + callback(event, eh, buf, array) + } + + // Because trait pointer is fat pointer, in order to pass it to API, + // double Box is used. + let callback: Box<Box<dyn Fn(guestfs::Event, EventHandle, &[u8], &[u64]) + 'a>> + Box::new(Box::new(callback)); + let ptr = Box::into_raw(callback); + let callback = unsafe { Box::from_raw(ptr) }; + let event_bitmask = events_to_bitmask(events); + + let eh = { + unsafe { + guestfs_set_event_callback( + self.g, + trampoline::<C>, + event_bitmask, + 0, + ptr as *const c_void, + ) + } + }; + if eh == -1 { + return Err(self.get_error_from_handle("set_event_callback")); + } + self.callbacks.insert(EventHandle { eh }, callback); + + Ok(EventHandle { eh }) + } + + pub fn delete_event_callback(&mut self, eh: EventHandle) -> Result<(), error::Error> { + unsafe { + guestfs_delete_event_callback(self.g, eh.eh); + } + self.callbacks.remove(&eh); + Ok(()) + } +} diff --git a/rust/tests/410_close_event.rs b/rust/tests/410_close_event.rs new file mode 100644 index 000000000..e22474d36 --- /dev/null +++ b/rust/tests/410_close_event.rs @@ -0,0 +1,39 @@ +/* libguestfs Rust bindings + * Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +extern crate guestfs; + +use std::cell::RefCell; +use std::rc::Rc; + +#[test] +fn close_event() { + let close_invoked = Rc::new(RefCell::new(0)); + { + let mut g = guestfs::Handle::create().expect("create"); + g.set_event_callback( + |_, _, _, _| { + *close_invoked.borrow_mut() += 1; + }, + &[guestfs::Event::Close], + ) + .unwrap(); + assert_eq!(*close_invoked.borrow(), 0); + } + assert_eq!(*close_invoked.borrow(), 1); +} diff --git a/rust/tests/420_log_messages.rs b/rust/tests/420_log_messages.rs new file mode 100644 index 000000000..78365995a --- /dev/null +++ b/rust/tests/420_log_messages.rs @@ -0,0 +1,60 @@ +/* libguestfs Rust bindings + * Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +extern crate guestfs; + +use std::cell::RefCell; +use std::rc::Rc; +use std::str; + +#[test] +fn log_messages() { + let callback_invoked = Rc::new(RefCell::new(0)); + { + let mut g = guestfs::Handle::create().expect("create"); + g.set_event_callback( + |ev, _, buf, array| { + *callback_invoked.borrow_mut() += 1; + + let event = guestfs::event_to_string(&[ev]).unwrap(); + + let buf = str::from_utf8(buf).unwrap(); + let array = array + .into_iter() + .map(|x| format!("{}", x)) + .collect::<Vec<String>>() + .join(","); + + eprintln!("event logged: event={} buf={} array={}", event, buf, array) + }, + &[ + guestfs::Event::Appliance, + guestfs::Event::Library, + guestfs::Event::Warning, + guestfs::Event::Trace, + ], + ) + .unwrap(); + + g.set_trace(true).unwrap(); + g.set_verbose(true).unwrap(); + g.add_drive_ro("/dev/null").unwrap(); + g.set_autosync(true).unwrap(); + } + assert!(*callback_invoked.borrow() > 0); +} diff --git a/rust/tests/430_progress_messages.rs b/rust/tests/430_progress_messages.rs new file mode 100644 index 000000000..08d8b59e7 --- /dev/null +++ b/rust/tests/430_progress_messages.rs @@ -0,0 +1,59 @@ +/* libguestfs Rust bindings + * Copyright (C) 2019 Hiroyuki Katsura <hiroyuki.katsura.0513@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +extern crate guestfs; + +use std::cell::RefCell; +use std::default::Default; +use std::rc::Rc; + +#[test] +fn progress_messages() { + let callback_invoked = Rc::new(RefCell::new(0)); + { + let mut g = guestfs::Handle::create().expect("create"); + g.add_drive("/dev/null", Default::default()).unwrap(); + g.launch().unwrap(); + + let eh = g + .set_event_callback( + |_, _, _, _| { + *callback_invoked.borrow_mut() += 1; + }, + &[guestfs::Event::Progress], + ) + .unwrap(); + assert_eq!("ok", g.debug("progress", &["5"]).unwrap()); + assert!(*callback_invoked.borrow() > 0); + + *callback_invoked.borrow_mut() = 0; + g.delete_event_callback(eh).unwrap(); + assert_eq!("ok", g.debug("progress", &["5"]).unwrap()); + assert_eq!(*callback_invoked.borrow(), 0); + + g.set_event_callback( + |_, _, _, _| { + *callback_invoked.borrow_mut() += 1; + }, + &[guestfs::Event::Progress], + ) + .unwrap(); + assert_eq!("ok", g.debug("progress", &["5"]).unwrap()); + } + assert!(*callback_invoked.borrow() > 0); +} -- 2.20.1 (Apple Git-117)
Richard W.M. Jones
2019-Aug-06 14:16 UTC
Re: [Libguestfs] [PATCH 2/2] Rust bindings: Implement callback handlers
These look fine to me and they don't break the build so I have pushed both. Thanks. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com virt-top is 'top' for virtual machines. Tiny program with many powerful monitoring features, net stats, disk stats, logging, etc. http://people.redhat.com/~rjones/virt-top
Katsura Hiroyuki
2019-Aug-06 14:50 UTC
Re: [Libguestfs] [PATCH 2/2] Rust bindings: Implement callback handlers
Dear Rich I waited for Pino’s response because I failed to divide this patch into two commits as Pino thought. Is it OK? Regards, Hiroyuki> On Aug 6, 2019, at 23:16, Richard W.M. Jones <rjones@redhat.com> wrote: > > > These look fine to me and they don't break the build so I have > pushed both. > > Thanks. > > Rich. > > -- > Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones > Read my programming and virtualization blog: http://rwmj.wordpress.com > virt-top is 'top' for virtual machines. Tiny program with many > powerful monitoring features, net stats, disk stats, logging, etc. > http://people.redhat.com/~rjones/virt-top