mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-21 22:53:37 +00:00
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.
This commit is contained in:
committed by
Richard W.M. Jones
parent
bb0cb3e730
commit
3f7ff1b068
29
rust/src/bin/event.rs
Normal file
29
rust/src/bin/event.rs
Normal file
@@ -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!()
|
||||
}
|
||||
30
rust/src/bin/event_leak.rs
Normal file
30
rust/src/bin/event_leak.rs
Normal file
@@ -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!()
|
||||
}
|
||||
@@ -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) };
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
39
rust/tests/410_close_event.rs
Normal file
39
rust/tests/410_close_event.rs
Normal file
@@ -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);
|
||||
}
|
||||
60
rust/tests/420_log_messages.rs
Normal file
60
rust/tests/420_log_messages.rs
Normal file
@@ -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);
|
||||
}
|
||||
59
rust/tests/430_progress_messages.rs
Normal file
59
rust/tests/430_progress_messages.rs
Normal file
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user