Richard W.M. Jones
2019-Jun-27 09:01 UTC
Re: [Libguestfs] [PATCH 9/9] Rust bindings: Complete bindings
Patch 9 is a kind of dumping ground of all kinds of stuff. It may be better to spend some time with git rebase -i trying to work this into more coherent patches. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com virt-p2v converts physical machines to virtual machines. Boot with a live CD or over the network (PXE) and turn machines into KVM guests. http://libguestfs.org/virt-v2v
Hiroyuki Katsura
2019-Jun-27 10:31 UTC
Re: [Libguestfs] [PATCH 08/11] Rust bindings: Fix memory management and format the file
--- generator/rust.ml | 461 +++++++++++++++++++++++++++++++--------------- 1 file changed, 311 insertions(+), 150 deletions(-) diff --git a/generator/rust.ml b/generator/rust.ml index 79e16dfc6..ee65b1073 100644 --- a/generator/rust.ml +++ b/generator/rust.ml @@ -61,14 +61,16 @@ let generate_rust () pr " use std::collections; use std::convert; +use std::convert::TryFrom; use std::ffi; -use std::slice; +use std::os::raw::{c_char, c_int, c_void}; use std::ptr; -use std::os::raw::{c_char, c_int}; +use std::slice; +use std::str; #[allow(non_camel_case_types)] -enum guestfs_h {} // opaque struct +enum guestfs_h {} // opaque struct #[link(name = \"guestfs\")] extern \"C\" { @@ -79,6 +81,11 @@ extern \"C\" { fn guestfs_last_errno(g: *mut guestfs_h) -> c_int; } +extern \"C\" { + fn free(buf: *const c_void); +} + + const GUESTFS_CREATE_NO_ENVIRONMENT: i64 = 1; const GUESTFS_CREATE_NO_CLOSE_ON_EXIT: i64 = 2; @@ -136,77 +143,136 @@ impl CreateFlags { } struct NullTerminatedIter<T: Copy + Clone> { - p: *const T + p: *const *const T, } impl<T: Copy + Clone> NullTerminatedIter<T> { - fn new(p: *const T) -> NullTerminatedIter<T> { - NullTerminatedIter{ p } + fn new(p: *const *const T) -> NullTerminatedIter<T> { + NullTerminatedIter { p } } } impl<T: Copy + Clone> Iterator for NullTerminatedIter<T> { - type Item = T; - fn next(&mut self) -> Option<T> { - if self.p.is_null() { + type Item = *const T; + fn next(&mut self) -> Option<*const T> { + let r = unsafe { *(self.p) }; + if r.is_null() { None } else { - let r = unsafe { *(self.p) }; self.p = unsafe { self.p.offset(1) }; Some(r) } } } -fn arg_string_list (v: &Vec<String>) -> Vec<*const i8> { - let length = v.len(); +#[repr(C)] +struct RawList<T> { + size: u32, + ptr: *const T, +} + +struct RawListIter<'a, T> { + current: u32, + list: &'a RawList<T>, +} + +impl<T> RawList<T> { + fn iter<'a>(&'a self) -> RawListIter<'a, T> { + RawListIter { + current: 0, + list: self, + } + } +} + +impl<'a, T> Iterator for RawListIter<'a, T> { + type Item = *const T; + fn next(&mut self) -> Option<*const T> { + if self.current >= self.list.size { + None + } else { + let elem = unsafe { self.list.ptr.offset(self.current as isize) }; + self.current += 1; + Some(elem) + } + } +} + +fn arg_string_list(v: &[&str]) -> Result<Vec<ffi::CString>, Error> { let mut w = Vec::new(); for x in v.iter() { let y: &str = x; - let s = ffi::CString::new(y).unwrap(); - w.push(s.as_ptr()); + w.push(ffi::CString::new(y)?); + } + Ok(w) +} + +fn free_string_list(l: *const *const c_char) { + for buf in NullTerminatedIter::new(l) { + unsafe { free(buf as * const c_void) }; } - w.push(ptr::null()); - w + unsafe { free(l as *const c_void) }; } -fn hashmap (l: *const *const c_char) -> collections::HashMap<String, String> { +fn hashmap(l: *const *const c_char) -> Result<collections::HashMap<String, String>, Error> { let mut map = collections::HashMap::new(); let mut iter = NullTerminatedIter::new(l); while let Some(key) = iter.next() { if let Some(val) = iter.next() { - let key = unsafe { ffi::CStr::from_ptr(key) }.to_str().unwrap(); - let val = unsafe { ffi::CStr::from_ptr(val) }.to_str().unwrap(); + let key = unsafe { ffi::CStr::from_ptr(key) }.to_str()?; + let val = unsafe { ffi::CStr::from_ptr(val) }.to_str()?; map.insert(key.to_string(), val.to_string()); } else { + // Internal Error -> panic panic!(\"odd number of items in hash table\"); } } - map + Ok(map) } -fn struct_list<T, S: convert::From<*const T>>(l: *const *const T) -> Vec<S> { +fn struct_list<T, S: TryFrom<*const T, Error = Error>>( + l: *const RawList<T>, +) -> Result<Vec<S>, Error> { let mut v = Vec::new(); - for x in NullTerminatedIter::new(l) { - v.push(S::from(x)); + for x in unsafe { &*l }.iter() { + v.push(S::try_from(x)?); } - v + Ok(v) } -fn string_list (l: *const *const c_char) -> Vec<String> { +fn string_list(l: *const *const c_char) -> Result<Vec<String>, Error> { let mut v = Vec::new(); for x in NullTerminatedIter::new(l) { - let s = unsafe { ffi::CStr::from_ptr(x) }.to_str().unwrap(); + let s = unsafe { ffi::CStr::from_ptr(x) }.to_str()?; v.push(s.to_string()); } - v + Ok(v) } #[derive(Debug)] -pub struct Error { +pub struct APIError { operation: &'static str, message: String, - errno: i32 + errno: i32, +} + +#[derive(Debug)] +pub enum Error { + API(APIError), + IllegalString(ffi::NulError), + Utf8Error(str::Utf8Error), +} + +impl convert::From<ffi::NulError> for Error { + fn from(error: ffi::NulError) -> Self { + Error::IllegalString(error) + } +} + +impl convert::From<str::Utf8Error> for Error { + fn from(error: str::Utf8Error) -> Self { + Error::Utf8Error(error) + } } impl Handle { @@ -231,13 +297,17 @@ impl Handle { fn get_error_from_handle(&self, operation: &'static str) -> Error { let c_msg = unsafe { guestfs_last_error(self.g) }; let message = unsafe { ffi::CStr::from_ptr(c_msg).to_str().unwrap().to_string() }; - let errno = unsafe { guestfs_last_errno(self.g) } ; - Error { operation, message, errno } + let errno = unsafe { guestfs_last_errno(self.g) }; + Error::API(APIError { + operation, + message, + errno, + }) } } pub struct UUID { - uuid: [u8; 32] + uuid: [u8; 32], } impl UUID { @@ -284,22 +354,24 @@ impl UUID { ) cols; pr "}\n"; pr "\n"; - pr "impl convert::From<*const Raw%s> for %s {\n" name name; - pr " fn from(raw: *const Raw%s) -> Self {\n" name; - pr " unsafe { %s {\n" name; + pr "impl TryFrom<*const Raw%s> for %s {\n" name name; + pr " type Error = Error;\n"; + pr " fn try_from(raw: *const Raw%s) -> Result<Self, Self::Error> {\n" name; + pr " Ok(unsafe {\n"; + pr " %s {\n" name; List.iter ( fun x -> - indent 3; + indent 4; match x with | n, FChar -> pr "%s: (*raw).%s as i8,\n" n n; | n, FString -> pr "%s: {\n" n; - indent 4; + indent 5; pr "let s = ffi::CStr::from_ptr((*raw).%s);\n" n; + indent 5; + pr "s.to_str()?.to_string()\n"; indent 4; - pr "s.to_str().unwrap().to_string()\n"; - indent 3; pr "},\n" | n, FBuffer -> pr "%s: slice::from_raw_parts((*raw).%s as *const u8, (*raw).%s_len).to_vec(),\n" n n n @@ -308,43 +380,129 @@ impl UUID { | n, (FUInt32 | FInt32 | FUInt64 | FInt64 | FBytes) -> pr "%s: (*raw).%s,\n" n n | n, FOptPercent -> - pr "%s: if (*raw).%s < 0.0 { None } else { Some((*raw).%s) },\n" n n n + pr "%s: if (*raw).%s < 0.0 {\n" n n; + indent 4; pr " None\n"; + indent 4; pr "} else {\n"; + indent 4; pr " Some((*raw).%s)\n" n; + indent 4; pr"},\n" ) cols; - pr " } }\n"; + pr " }\n"; + pr " })\n"; pr " }\n"; pr "}\n" ) external_structs; + (* generate free functionf of structs *) + pr "\n"; + pr "extern \"C\" {\n"; + List.iter ( + fun { s_camel_name = name; s_name = c_name; } -> + pr " #[allow(dead_code)]\n"; + pr " fn guestfs_free_%s(v: *const Raw%s);\n" c_name name; + pr " #[allow(dead_code)]\n"; + pr " fn guestfs_free_%s_list(l: *const RawList<Raw%s>);\n" c_name name; + ) external_structs; + pr "}\n"; + + (* [Outline] There are three types for each optional structs: SOptArgs, + * CExprSOptArgs, RawSOptArgs. + * SOptArgs: for Rust bindings' API. This can be seen by bindings' users. + * CExprSOptArgs: Each field has C expression(e.g. CString, *const c_char) + * RawSOptArgs: Each field has raw pointers or integer values + * + * SOptArgs ---try_into()---> CExprSOptArgs ---to_raw()---> RawSOptArgs + * + * Note: direct translation from SOptArgs to RawSOptArgs will cause a memory + * management problem. Using into_raw/from_raw, this problem can be avoided, + * but it is complex to handle freeing memories manually in Rust because of + * panic/?/etc. + *) + (* generate structs for optional arguments *) List.iter ( fun ({ name = name; shortdesc = shortdesc; style = (ret, args, optargs) }) -> let cname = snake2caml name in + let rec contains_ptr args = match args with + | [] -> false + | OString _ ::_ + | OStringList _::_ -> true + | _::xs -> contains_ptr xs + in + let opt_life_parameter = if contains_ptr optargs then "<'a>" else "" in if optargs <> [] then ( pr "\n"; pr "/* Optional Structs */\n"; pr "#[derive(Default)]\n"; - pr "pub struct OptArgs%s {\n" cname; + pr "pub struct %sOptArgs%s {\n" cname opt_life_parameter; List.iter ( fun optarg -> let n = translate_bad_symbols (name_of_optargt optarg) in match optarg with | OBool _ -> - pr " _%s: Option<bool>,\n" n + pr " pub %s: Option<bool>,\n" n | OInt _ -> - pr " _%s: Option<i32>,\n" n + pr " pub %s: Option<i32>,\n" n | OInt64 _ -> - pr " _%s: Option<i64>,\n" n + pr " pub %s: Option<i64>,\n" n | OString _ -> - pr " _%s: Option<String>,\n" n + pr " pub %s: Option<&'a str>,\n" n | OStringList _ -> - pr " _%s: Option<Vec<String>>,\n" n + pr " pub %s: Option<&'a [&'a str]>,\n" n ) optargs; pr "}\n\n"; + pr "struct CExpr%sOptArgs {\n" cname; + List.iter ( + fun optarg -> + let n = translate_bad_symbols (name_of_optargt optarg) in + match optarg with + | OBool _ | OInt _ -> + pr " %s: Option<c_int>,\n" n + | OInt64 _ -> + pr " %s: Option<i64>,\n" n + | OString _ -> + pr " %s: Option<ffi::CString>,\n" n + | OStringList _ -> + (* buffers and their pointer vector *) + pr " %s: Option<(Vec<ffi::CString>, Vec<*const c_char>)>,\n" n + ) optargs; + pr "}\n\n"; + + pr "impl%s TryFrom<%sOptArgs%s> for CExpr%sOptArgs {\n" + opt_life_parameter cname opt_life_parameter cname; + pr " type Error = Error;\n"; + pr " fn try_from(optargs: %sOptArgs%s) -> Result<Self, Self::Error> {\n" cname opt_life_parameter; + pr " Ok(CExpr%sOptArgs {\n" cname; + List.iteri ( + fun index optarg -> + let n = translate_bad_symbols (name_of_optargt optarg) in + match optarg with + | OBool _ -> + pr " %s: optargs.%s.map(|b| if b { 1 } else { 0 }),\n" n n; + | OInt _ | OInt64 _ -> + pr " %s: optargs.%s, \n" n n; + | OString _ -> + pr " %s: optargs.%s.map(|v| ffi::CString::new(v)).transpose()?,\n" n n; + | OStringList _ -> + pr " %s: optargs.%s.map(\n" n n; + pr " |v| Ok::<_, Error>({\n"; + pr " let v = arg_string_list(v)?;\n"; + pr " let mut w = (&v).into_iter()\n"; + pr " .map(|v| v.as_ptr())\n"; + pr " .collect::<Vec<_>>();\n"; + pr " w.push(ptr::null());\n"; + pr " (v, w)\n"; + pr " })\n"; + pr " ).transpose()?,\n"; + ) optargs; + pr " })\n"; + pr " }\n"; + pr "}\n"; + (* raw struct for C bindings *) pr "#[repr(C)]\n"; - pr "struct RawOptArgs%s {\n" cname; + pr "struct Raw%sOptArgs {\n" cname; pr " bitmask: u64,\n"; List.iter ( fun optarg -> @@ -363,40 +521,33 @@ impl UUID { ) optargs; pr "}\n\n"; - pr "impl convert::From<OptArgs%s> for RawOptArgs%s {\n" cname cname; - pr " fn from(optargs: OptArgs%s) -> Self {\n" cname; + pr "impl convert::From<&CExpr%sOptArgs> for Raw%sOptArgs {\n" + cname cname; + pr " fn from(optargs: &CExpr%sOptArgs) -> Self {\n" cname; pr " let mut bitmask = 0;\n"; - pr " RawOptArgs%s {\n" cname; + pr " Raw%sOptArgs {\n" cname; List.iteri ( fun index optarg -> let n = translate_bad_symbols (name_of_optargt optarg) in match optarg with - | OBool _ -> - pr " %s: if let Some(v) = optargs._%s {\n" n n; - pr " bitmask |= 1 << %d;\n" index; - pr " if v { 1 } else { 0 }\n"; - pr " } else {\n"; - pr " 0\n"; - pr " },\n"; - | OInt _ | OInt64 _ -> - pr " %s: if let Some(v) = optargs._%s {\n" n n; + | OBool _ | OInt _ | OInt64 _ -> + pr " %s: if let Some(v) = optargs.%s {\n" n n; pr " bitmask |= 1 << %d;\n" index; pr " v\n"; pr " } else {\n"; pr " 0\n"; pr " },\n"; | OString _ -> - pr " %s: if let Some(v) = optargs._%s {\n" n n; + pr " %s: if let Some(ref v) = optargs.%s {\n" n n; pr " bitmask |= 1 << %d;\n" index; - pr " let y: &str = &v;\n"; - pr " ffi::CString::new(y).unwrap().as_ptr()\n"; + pr " v.as_ptr()\n"; pr " } else {\n"; pr " ptr::null()\n"; pr " },\n"; | OStringList _ -> - pr " %s: if let Some(v) = optargs._%s {\n" n n; + pr " %s: if let Some((_, ref v)) = optargs.%s {\n" n n; pr " bitmask |= 1 << %d;\n" index; - pr " arg_string_list(&v).as_ptr()"; + pr " v.as_ptr()\n"; pr " } else {\n"; pr " ptr::null()\n"; pr " },\n"; @@ -405,29 +556,6 @@ impl UUID { pr " }\n"; pr " }\n"; pr "}\n"; - - pr "impl OptArgs%s {\n" cname; - List.iter ( - fun optarg -> - let n = translate_bad_symbols (name_of_optargt optarg) in - pr " pub fn %s(self, %s: " n n; - (match optarg with - | OBool _ -> - pr "bool" - | OInt _ -> - pr "i32" - | OInt64 _ -> - pr "i64" - | OString _ -> - pr "String" - | OStringList _ -> - pr "Vec<String>" - ); - pr ") -> OptArgs%s {\n" cname; - pr " OptArgs%s { _%s: Some(%s), ..self }\n" cname n n; - pr " }\n" - ) optargs; - pr "}\n\n"; ); ) (actions |> external_functions |> sort); @@ -437,7 +565,8 @@ impl UUID { fun ({ name = name; shortdesc = shortdesc; style = (ret, args, optargs) } as f) -> let cname = snake2caml name in - pr "fn %s(g: *const guestfs_h" f.c_function; + pr " #[allow(non_snake_case)]\n"; + pr " fn %s(g: *const guestfs_h" f.c_function; List.iter ( fun arg -> pr ", "; @@ -456,7 +585,7 @@ impl UUID { | _ -> () ); if optargs <> [] then - pr ", optarg: *const RawOptArgs%s" cname; + pr ", optarg: *const Raw%sOptArgs" cname; pr ") -> "; @@ -471,7 +600,7 @@ impl UUID { pr "*const Raw%s" n | RStructList (_, n) -> let n = camel_name_of_struct n in - pr "*const *const Raw%s" n + pr "*const RawList<Raw%s>" n ); pr ";\n"; @@ -481,14 +610,16 @@ impl UUID { pr "impl Handle {\n"; List.iter ( - fun ({ name = name; shortdesc = shortdesc; + fun ({ name = name; shortdesc = shortdesc; longdesc = longdesc; style = (ret, args, optargs) } as f) -> let cname = snake2caml name in - pr " /// %s \n" shortdesc; + pr " /// %s\n" shortdesc; + pr " #[allow(non_snake_case)]\n"; pr " pub fn %s" name; (* generate arguments *) pr "(&self, "; + let comma = ref false in List.iter ( fun arg -> @@ -498,16 +629,16 @@ impl UUID { | Bool n -> pr "%s: bool" n | Int n -> pr "%s: i32" n | Int64 n -> pr "%s: i64" n - | String (_, n) -> pr "%s: String" n - | OptString n -> pr "%s: Option<String>" n - | StringList (_, n) -> pr "%s: Vec<String>" n - | BufferIn n -> pr "%s: Vec<u8>" n - | Pointer (_, n) -> pr "%s: usize" n + | String (_, n) -> pr "%s: &str" n + | OptString n -> pr "%s: Option<&str>" n + | StringList (_, n) -> pr "%s: &[&str]" n + | BufferIn n -> pr "%s: &[u8]" n + | Pointer (_, n) -> pr "%s: *mut c_void" n ) args; if optargs <> [] then ( if !comma then pr ", "; comma := true; - pr "optargs: OptArgs%s" cname + pr "optargs: %sOptArgs" cname ); pr ")"; @@ -532,45 +663,37 @@ impl UUID { | RBufferOut _ -> pr "Vec<u8>"); pr ", Error> {\n"; + let _pr = pr in let pr fs = indent 2; pr fs in List.iter ( function | Bool n -> - pr "let c_%s = if %s { 1 } else { 0 };\n" n n + pr "let %s = if %s { 1 } else { 0 };\n" n n | String (_, n) -> - (* TODO: handle errors *) - pr "let c_%s = \n" n; - pr " ffi::CString::new(%s).expect(\"CString::new failed\").as_ptr();\n" n; + pr "let c_%s = ffi::CString::new(%s)?;\n" n n; | OptString n -> - pr "let c_%s = match %s {" n n; - pr " Some(s) => \n"; - pr " ffi::CString::new(s)\n"; - pr " .expect(\"CString::new failed\")\n"; - pr " .as_ptr(),\n"; - pr " None => ptr::null(),\n"; - pr "};\n"; + pr "let c_%s = %s.map(|s| ffi::CString::new(s)).transpose()?;\n" n n; | StringList (_, n) -> - pr "let c_%s = arg_string_list(&%s).as_ptr();\n" n n; + pr "let c_%s_v = arg_string_list(%s)?;\n" n n; + pr "let mut c_%s = (&c_%s_v).into_iter().map(|v| v.as_ptr()).collect::<Vec<_>>();\n" n n; + pr "c_%s.push(ptr::null());\n" n; | BufferIn n -> - pr "let c_%s_len = (&%s).len();\n" n n; - pr "let c_%s = ffi::CString::new(%s)\n" n n; - pr " .expect(\"CString::new failed\")\n"; - pr " .as_ptr();\n"; + pr "let c_%s_len = %s.len();\n" n n; + pr "let c_%s = unsafe { ffi::CString::from_vec_unchecked(%s.to_vec())};\n" n n; | Int _ | Int64 _ | Pointer _ -> () ) args; - (* TODO: handle optargs *) - if optargs <> [] then ( - pr "let optargs_raw = RawOptArgs%s::from(optargs);\n" cname; - ); - (match ret with | RBufferOut _ -> pr "let mut size = 0usize;\n" | _ -> () ); + if optargs <> [] then ( + pr "let optargs_cexpr = CExpr%sOptArgs::try_from(optargs)?;\n" cname; + ); + pr "\n"; pr "let r = unsafe { %s(self.g" f.c_function; @@ -579,18 +702,19 @@ impl UUID { fun arg -> pr ", "; match arg with - | Bool n | String (_, n) | OptString n -> pr "c_%s" n - | Int n | Int64 n -> pr "%s" n - | Pointer _ -> pr "ptr::null()" (* XXX: what is pointer? *) - | StringList (_, n) -> pr "c_%s as *const *const c_char" n - | BufferIn n -> pr "c_%s, c_%s_len" n n + | String (_, n) -> pr "(&c_%s).as_ptr()" n + | OptString n -> pr "match &c_%s { Some(ref s) => s.as_ptr(), None => ptr::null() }\n" n + | StringList (_, n) -> pr "(&c_%s).as_ptr() as *const *const c_char" n + | Bool n | Int n | Int64 n | Pointer (_, n) -> pr "%s" n + | BufferIn n -> pr "(&c_%s).as_ptr(), c_%s_len" n n ) args; (match ret with - | RBufferOut _ -> pr ", &size as *const usize" + | RBufferOut _ -> pr ", &mut size as *mut usize" | _ -> () ); if optargs <> [] then ( - pr ", &optargs_raw as *const RawOptArgs%s" cname; + pr ", &(Raw%sOptArgs::from(&optargs_cexpr)) as *const Raw%sOptArgs" + cname cname; ); pr ") };\n"; @@ -600,48 +724,85 @@ impl UUID { | `CannotReturnError -> () | `ErrorIsMinusOne -> pr "if r == -1 {\n"; - pr " return Err(self.get_error_from_handle (\"%s\"));\n" name; + pr " return Err(self.get_error_from_handle(\"%s\"));\n" name; pr "}\n" | `ErrorIsNULL -> pr "if r.is_null() {\n"; - pr " return Err(self.get_error_from_handle (\"%s\"));\n" name; + pr " return Err(self.get_error_from_handle(\"%s\"));\n" name; pr "}\n" ); + + (* This part is not required, but type system will guarantee that + * the buffers are still alive. This is useful because Rust cannot + * know whether raw pointers used above are alive or not. + *) + List.iter ( + function + | Bool _ | Int _ | Int64 _ | Pointer _ -> () + | String (_, n) + | OptString n + | BufferIn n -> pr "drop(c_%s);\n" n; + | StringList (_, n) -> + pr "drop(c_%s);\n" n; + pr "drop(c_%s_v);\n" n; + ) args; + if optargs <> [] then ( + pr "drop(optargs_cexpr);\n"; + ); + pr "Ok("; let pr = _pr in + let pr3 fs = indent 3; pr fs in (match ret with | RErr -> pr "()" | RInt _ | RInt64 _ -> pr "r" | RBool _ -> pr "r != 0" | RConstString _ -> - pr "unsafe{ ffi::CStr::from_ptr(r) }.to_str().unwrap()\n" + pr "unsafe{ ffi::CStr::from_ptr(r) }.to_str()?" | RString _ -> - (* TODO: free r *) - pr "unsafe { ffi::CStr::from_ptr(r) }.to_str().unwrap().to_string()" + pr "{\n"; + pr3 "let s = unsafe { ffi::CStr::from_ptr(r) };\n"; + pr3 "unsafe { free(r as *const c_void) };\n"; + pr3 "s.to_str()?.to_string()\n"; + indent 2; pr "}"; | RConstOptString _ -> - (* TODO: free ? not? *) - indent 3; pr "if r.is_null() {\n"; - indent 3; pr " None\n"; - indent 3; pr "} else {\n"; - indent 3; pr " Some(unsafe { ffi::CStr::from_ptr(r) }.to_str().unwrap())\n"; - indent 3; pr "}"; + pr "if r.is_null() {\n"; + pr3 "None\n"; + indent 2; pr "} else {\n"; + pr3 "Some(unsafe { ffi::CStr::from_ptr(r) }.to_str()?)\n"; + indent 2; pr "}"; | RStringList _ -> - (* TODO: free r *) - pr "string_list(r)" + pr "{\n"; + pr3 "let s = string_list(r);\n"; + pr3 "free_string_list(r);\n"; + pr3 "s?\n"; + indent 2; pr "}"; | RStruct (_, n) -> - (* TODO: free r *) - let n = camel_name_of_struct n in - pr "%s::from(r)" n + let sn = camel_name_of_struct n in + pr "{\n"; + pr3 "let s = %s::try_from(r);\n" sn; + pr3 "unsafe { guestfs_free_%s(r) };\n" n; + pr3 "s?\n"; + indent 2; pr "}"; | RStructList (_, n) -> - (* TODO: free r *) - let n = camel_name_of_struct n in - pr "struct_list::<Raw%s, %s>(r)" n n + let sn = camel_name_of_struct n in + pr "{\n"; + pr3 "let l = struct_list::<Raw%s, %s>(r);\n" sn sn; + pr3 "unsafe { guestfs_free_%s_list(r) };\n" n; + pr3 "l?\n"; + indent 2; pr "}"; | RHashtable _ -> - (* TODO: free r *) - pr "hashmap(r)" + pr "{\n"; + pr3 "let h = hashmap(r);\n"; + pr3 "free_string_list(r);\n"; + pr3 "h?\n"; + indent 2; pr "}"; | RBufferOut _ -> - (* TODO: free r *) - pr "unsafe { slice::from_raw_parts(r, size) }.to_vec()" + pr "{\n"; + pr3 "let s = unsafe { slice::from_raw_parts(r, size) }.to_vec();\n"; + pr3 "unsafe { free(r as *const c_void) } ;\n"; + pr3 "s\n"; + indent 2; pr "}"; ); pr ")\n"; pr " }\n\n" -- 2.20.1 (Apple Git-117)
Hiroyuki Katsura
2019-Jun-27 10:31 UTC
Re: [Libguestfs] [PATCH 09/11] Rust bindings: Add bindtests
--- generator/bindtests.ml | 63 ++++++++++++++++++++++++++++++++++++++++++ generator/main.ml | 2 +- generator/rust.mli | 5 +++- 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/generator/bindtests.ml b/generator/bindtests.ml index 41aef47ea..e88e71c8a 100644 --- a/generator/bindtests.ml +++ b/generator/bindtests.ml @@ -986,6 +986,69 @@ and generate_php_bindtests () and generate_rust_bindtests () generate_header CStyle GPLv2plus; + pr "extern crate guestfs;\n"; + pr "use guestfs::*;\n"; + pr "use std::default::Default;\n"; + pr "\n"; + pr "fn main() {\n"; + pr " let g = match Handle::create() {\n"; + pr " Ok(g) => g,\n"; + pr " Err(e) => panic!(format!(\" could not create handle {}\", e)),\n"; + pr " };\n"; + generate_lang_bindtests ( + fun f args optargs -> + pr " g.%s(" f; + let needs_comma = ref false in + List.iter ( + fun arg -> + if !needs_comma then pr ", "; + needs_comma := true; + match arg with + | CallString s -> pr "\"%s\"" s + | CallOptString None -> pr "None" + | CallOptString (Some s) -> pr "Some(\"%s\")" s + | CallStringList xs -> + pr "&vec![%s]" + (String.concat ", " (List.map (sprintf "\"%s\"") xs)) + | CallInt i -> pr "%d" i + | CallInt64 i -> pr "%Ldi64" i + | CallBool b -> pr "%b" b + | CallBuffer s -> + let f = fun x -> sprintf "%d" (Char.code x) in + pr "&[%s]" + (String.concat ", " (List.map f (String.explode s))) + ) args; + if !needs_comma then pr ", "; + (match optargs with + | None -> pr "Default::default()" + | Some optargs -> + pr "%sOptArgs{" (Rust.snake2caml f); + needs_comma := false; + List.iter ( + fun optarg -> + if !needs_comma then pr ", "; + needs_comma := true; + match optarg with + | CallOBool (n, v) -> + pr "%s: Some(%b)" n v + | CallOInt (n, v) -> + pr "%s: Some(%d)" n v + | CallOInt64 (n, v) -> + pr "%s: Some(%Ldi64)" n v + | CallOString (n, v) -> + pr "%s: Some(\"%s\")" n v + | CallOStringList (n, xs) -> + pr "%s: Some(&[%s])" + n (String.concat ", " (List.map (sprintf "\"%s\"") xs)) + ) optargs; + if !needs_comma then pr ", "; + pr ".. Default::default()}"; + ); + pr ").expect(\"failed to run\");\n"; + ); + pr " println!(\"EOF\");\n"; + pr "}\n"; + (* Language-independent bindings tests - we do it this way to * ensure there is parity in testing bindings across all languages. *) diff --git a/generator/main.ml b/generator/main.ml index 5de89138c..ffe1bb95c 100644 --- a/generator/main.ml +++ b/generator/main.ml @@ -374,7 +374,7 @@ Run it from the top source directory using the command output_to "rust/src/lib.rs" Rust.generate_rust; - output_to "rust/tests/bind_test.rs" + output_to "rust/src/bin/bindtests.rs" Bindtests.generate_rust_bindtests; (* Generate the list of files generated -- last. *) diff --git a/generator/rust.mli b/generator/rust.mli index 4fef55d4e..5410286c8 100644 --- a/generator/rust.mli +++ b/generator/rust.mli @@ -16,4 +16,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *) -val generate_rust: unit -> unit \ No newline at end of file +val generate_rust: unit -> unit + +(* for bindtests.ml *) +val snake2caml: string -> string -- 2.20.1 (Apple Git-117)
Hiroyuki Katsura
2019-Jun-27 10:31 UTC
[Libguestfs] [PATCH 10/11] Rust bindings: Format test files
--- rust/tests/010_load.rs | 28 ++++++++++++++-------------- rust/tests/020_create.rs | 28 ++++++++++++++-------------- rust/tests/030_create_flags.rs | 29 ++++++++++++++--------------- rust/tests/040_create_multiple.rs | 28 ++++++++++++++-------------- 4 files changed, 56 insertions(+), 57 deletions(-) diff --git a/rust/tests/010_load.rs b/rust/tests/010_load.rs index eadd78896..4cb43f2c1 100644 --- a/rust/tests/010_load.rs +++ b/rust/tests/010_load.rs @@ -1,20 +1,20 @@ -/* libguestfs Python bindings - Copyright (C) 2009-2019 Red Hat Inc. +/* libguestfs Rust bindings +Copyright (C) 2009-2019 Red Hat Inc. - 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 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. +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. - */ +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; diff --git a/rust/tests/020_create.rs b/rust/tests/020_create.rs index 0b57b19d7..017dbbac0 100644 --- a/rust/tests/020_create.rs +++ b/rust/tests/020_create.rs @@ -1,20 +1,20 @@ -/* libguestfs Python bindings - Copyright (C) 2009-2019 Red Hat Inc. +/* libguestfs Rust bindings +Copyright (C) 2009-2019 Red Hat Inc. - 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 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. +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. - */ +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; diff --git a/rust/tests/030_create_flags.rs b/rust/tests/030_create_flags.rs index 5de0589c1..df3190d4c 100644 --- a/rust/tests/030_create_flags.rs +++ b/rust/tests/030_create_flags.rs @@ -1,19 +1,19 @@ -/* libguestfs Python bindings - Copyright (C) 2009-2019 Red Hat Inc. +/* libguestfs Rust bindings +Copyright (C) 2009-2019 Red Hat Inc. - 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 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. +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. - */ +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; @@ -23,8 +23,7 @@ use guestfs::*; fn create_flags() { let _h = Handle::create_flags(CreateFlags::none()).expect("create_flags fail"); // TODO: Add parse_environment to check the flag is created correctly - let flags = CreateFlags::new() - .create_no_environment(true); + let flags = CreateFlags::new().create_no_environment(true); let _h = Handle::create_flags(flags).expect("create_flags fail"); // TODO: Add parse_environment to check the flag is created correctly } diff --git a/rust/tests/040_create_multiple.rs b/rust/tests/040_create_multiple.rs index ee481c278..372fad7ee 100644 --- a/rust/tests/040_create_multiple.rs +++ b/rust/tests/040_create_multiple.rs @@ -1,20 +1,20 @@ -/* libguestfs Python bindings - Copyright (C) 2009-2019 Red Hat Inc. +/* libguestfs Rust bindings +Copyright (C) 2009-2019 Red Hat Inc. - 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 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. +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. - */ +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; -- 2.20.1 (Apple Git-117)
Hiroyuki Katsura
2019-Jun-27 10:31 UTC
[Libguestfs] [PATCH 11/11] Rust bindings: Incorporate bindings to build system
--- configure.ac | 2 ++ rust/Makefile.am | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index f9bdbe54b..b35b1ce0f 100644 --- a/configure.ac +++ b/configure.ac @@ -431,6 +431,8 @@ AS_ECHO_N(["Vala bindings ....................... "]) if test "x$ENABLE_VAPIGEN_TRUE" = "x"; then echo "yes"; else echo "no"; fi AS_ECHO_N(["bash completion ..................... "]) if test "x$HAVE_BASH_COMPLETION_TRUE" = "x"; then echo "yes"; else echo "no"; fi +AS_ECHO_N(["Rust bindings ....................... "]) +if test "x$HAVE_RUST_TRUE" = "x"; then echo "yes"; else echo "no"; fi echo echo "If any optional component is configured 'no' when you expected 'yes'" echo "then you should check the preceding messages." diff --git a/rust/Makefile.am b/rust/Makefile.am index e8bf27894..261cf4a5c 100644 --- a/rust/Makefile.am +++ b/rust/Makefile.am @@ -18,12 +18,26 @@ include $(top_srcdir)/subdir-rules.mk generator_built = \ + src/bin/bindtests.rs \ src/lib.rs EXTRA_DIST = \ - $(generator_built) + .gitignore \ + $(generator_built) \ + tests/*.rs \ + Cargo.toml \ + Cargo.lock \ + run-bindtests \ + run-tests if HAVE_RUST +all: src/lib.rs + $(top_builddir)/run $(CARGO) build --release + +TESTS = run-bindtests run-tests + +CLEANFILES += target/*~ + endif -- 2.20.1 (Apple Git-117)