>From 558998bcb59198a45c5f008a192802813b339547 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Wed, 21 Oct 2015 13:48:39 +0100 Subject: [PATCH] Add Rust (language) bindings. On Fedora you will need to install the Rust copr from: https://copr.fedoraproject.org/coprs/fabiand/rust-binary/ until the package is added to Fedora: https://bugzilla.redhat.com/show_bug.cgi?id=915043 --- .gitignore | 5 + Makefile.am | 3 + README | 3 + configure.ac | 17 ++ generator/Makefile.am | 2 + generator/main.ml | 3 + generator/rust.ml | 441 ++++++++++++++++++++++++++++++++++++++++++++++++++ rust/Cargo.toml.in | 9 ++ rust/Makefile.am | 39 +++++ rust/src/.gitignore | 0 src/guestfs.pod | 2 + 11 files changed, 524 insertions(+) create mode 100644 generator/rust.ml create mode 100644 rust/Cargo.toml.in create mode 100644 rust/Makefile.am create mode 100644 rust/src/.gitignore diff --git a/.gitignore b/.gitignore index d5c5d1e..dcf9d63 100644 --- a/.gitignore +++ b/.gitignore @@ -468,6 +468,11 @@ Makefile.in /ruby/ext/guestfs/mkmf.log /ruby/Rakefile /ruby/stamp-rdoc +/rust/Cargo.lock +/rust/Cargo.toml +/rust/src/ffi.rs +/rust/src/lib.rs +/rust/target /run /sparsify/.depend /sparsify/stamp-virt-sparsify.pod diff --git a/Makefile.am b/Makefile.am index 713df42..aa96fc3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -123,6 +123,9 @@ endif if HAVE_GOLANG SUBDIRS += golang golang/examples endif +if HAVE_RUST +SUBDIRS += rust +endif # Unconditional because nothing is built yet. SUBDIRS += csharp diff --git a/README b/README index 19a1fb2..e8b2e9b 100644 --- a/README +++ b/README @@ -223,6 +223,9 @@ The full requirements are described below. +--------------+-------------+---+-----------------------------------------+ | golang | 1.1.1 | O | For the Go bindings. | +--------------+-------------+---+-----------------------------------------+ +| rustc | 1.0 | O | For the Rust bindings. | +| cargo | | | | ++--------------+-------------+---+-----------------------------------------+ | valgrind | | O | For testing for memory problems. | +--------------+-------------+---+-----------------------------------------+ | Sys::Virt | | O | Perl bindings for libvirt. | diff --git a/configure.ac b/configure.ac index 4f6650e..e5b8ade 100644 --- a/configure.ac +++ b/configure.ac @@ -1578,6 +1578,21 @@ AS_IF([test "x$enable_golang" != "xno"],[ ],[GOLANG=no]) AM_CONDITIONAL([HAVE_GOLANG],[test "x$GOLANG" != "xno"]) +dnl Rust compiler. +AC_ARG_ENABLE([rust], + AS_HELP_STRING([--disable-rust], [disable Rust language bindings]), + [], + [enable_rust=yes]) +AS_IF([test "x$enable_rust" != "xno"],[ + AC_CHECK_PROG([RUSTC],[rustc],[rustc],[no]) + AC_CHECK_PROG([CARGO],[cargo],[cargo],[no]) +],[ + RUSTC=no + CARGO=no +]) +AM_CONDITIONAL([HAVE_RUST], + [test "x$RUSTC" != "xno" && test "x$CARGO" != "xno"]) + dnl Check for Perl modules needed by Perl virt tools (virt-df, etc.) AS_IF([test "x$PERL" != "xno"],[ missing_perl_modules=no @@ -1791,6 +1806,8 @@ AC_CONFIG_FILES([Makefile ruby/Rakefile ruby/examples/Makefile ruby/ext/guestfs/extconf.rb + rust/Cargo.toml + rust/Makefile sparsify/Makefile src/Makefile src/libguestfs.pc diff --git a/generator/Makefile.am b/generator/Makefile.am index a3fe50d..34ac5c5 100644 --- a/generator/Makefile.am +++ b/generator/Makefile.am @@ -48,6 +48,7 @@ sources = \ prepopts.mli \ python.ml \ ruby.ml \ + rust.ml \ structs.ml \ structs.mli \ tests_c_api.ml \ @@ -85,6 +86,7 @@ objects = \ lua.cmo \ gobject.cmo \ golang.cmo \ + rust.cmo \ bindtests.cmo \ errnostring.cmo \ customize.cmo \ diff --git a/generator/main.ml b/generator/main.ml index 1e0e7d6..d56f028 100644 --- a/generator/main.ml +++ b/generator/main.ml @@ -43,6 +43,7 @@ open Erlang open Lua open Gobject open Golang +open Rust open Bindtests open Errnostring open Customize @@ -165,6 +166,8 @@ Run it from the top source directory using the command output_to "lua/bindtests.lua" generate_lua_bindtests; output_to "golang/src/libguestfs.org/guestfs/guestfs.go" generate_golang_go; output_to "golang/bindtests.go" generate_golang_bindtests; + output_to "rust/src/ffi.rs" generate_rust_ffi_rs; + output_to "rust/src/lib.rs" generate_rust_lib_rs; output_to "gobject/bindtests.js" generate_gobject_js_bindtests; output_to "gobject/Makefile.inc" generate_gobject_makefile; diff --git a/generator/rust.ml b/generator/rust.ml new file mode 100644 index 0000000..f73b0e7 --- /dev/null +++ b/generator/rust.ml @@ -0,0 +1,441 @@ +(* libguestfs + * Copyright (C) 2015 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 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 + *) + +(* Please read generator/README first. *) + +open Printf + +open Types +open Utils +open Pr +open Docstrings +open Optgroups +open Actions +open Structs +open C +open Events + +(* Generate rust bindings. *) + +let generate_rust_ffi_rs () = + generate_header CPlusPlusStyle LGPLv2plus; + + pr "\ +use libc::{ + c_char, c_float, c_int, c_uint, c_void, + int32_t, int64_t, + size_t, + uint32_t, uint64_t, +}; + +// Represents the opaque handle (guestfs_h). +#[allow(non_camel_case_types)] +pub enum guestfs_h {} + +"; + + (* Generate FFI C structs. *) + pr "// FFI C structs.\n"; + pr "\n"; + List.iter ( + fun { s_name = typ; s_cols = cols } -> + pr "#[repr(C)]\n"; + pr "struct %s {\n" typ; + List.iter ( + function + | name, FString -> + pr " pub %s: *const c_char,\n" name + | name, FBuffer -> + pr " pub %s_len: uint32_t,\n" name; + pr " pub %s: *const c_char,\n" name + | name, FUUID -> + pr " pub %s: [c_char; 32],\n" name + | name, (FBytes|FInt64) -> + pr " pub %s: int64_t,\n" name + | name, FUInt64 -> + pr " pub %s: uint64_t,\n" name + | name, FInt32 -> + pr " pub %s: int32_t,\n" name + | name, FUInt32 -> + pr " pub %s: uint32_t,\n" name + | name, FOptPercent -> + pr " pub %s: c_float,\n" name + | name, FChar -> + pr " pub %s: c_char,\n" name + ) cols; + pr "}\n"; + pr "\n"; + pr "#[repr(C)]\n"; + pr "struct %s_list {\n" typ; + pr " pub len: uint32_t,\n"; + pr " pub val: *mut %s,\n" typ; + pr "}\n"; + pr "\n" + ) external_structs; + + (* Generate FFI C calls. *) + pr "\ +#[link (name = \"guestfs\")] +extern { + pub fn guestfs_create () -> *mut guestfs_h; + pub fn guestfs_create_flags (flags: c_uint, ...) -> *mut guestfs_h; + pub fn guestfs_close (g: *mut guestfs_h); + + pub fn guestfs_last_error (g: *mut guestfs_h) -> *const c_char; + + // FFI C calls. +"; + + List.iter ( + fun { name = name; style = ret, args, optargs } -> + pr " pub fn guestfs_%s (g: *mut guestfs_h" name; + List.iter ( + function + | Bool n + | Int n -> pr ", %s: c_int" n + | Int64 n -> pr ", %s: int64_t" n + | String n | Device n | Mountable n | Pathname n + | Dev_or_Path n | Mountable_or_Path n + | Key n | FileIn n | FileOut n + | GUID n + | OptString n -> + pr ", %s: *const c_char" n + | BufferIn n -> + pr ", %s: *const c_char, %s_size: size_t" n n + | StringList n + | DeviceList n + | FilenameList n -> + pr ", %s: *const *const c_char" n + | Pointer (t, n) -> + pr ", %s: *mut c_void" n + ) args; + (match ret with + | RBufferOut _ -> pr ", size_r: size_t" + | _ -> () + ); + if optargs <> [] then + pr ", ..."; + pr ")"; + (match ret with + | RErr -> pr " -> c_int" + | RInt _ -> pr " -> c_int" + | RInt64 _ -> pr " -> int64_t" + | RBool _ -> pr " -> c_int" + | RConstString _ -> pr " -> *const c_char" + | RConstOptString _ -> pr " -> *const c_char" + | RString _ -> pr " -> *mut c_char" + | RStringList _ -> pr " -> *mut *mut c_char" + | RStruct (_, typ) -> pr " -> *mut %s" typ + | RStructList (_, typ) -> pr " -> *mut %s_list" typ + | RHashtable _ -> pr " -> *mut *mut c_char" + | RBufferOut _ -> pr " -> *mut c_char" + ); + pr ";\n" + ) public_functions_sorted; + + pr " + // FFI C struct functions. +"; + + List.iter ( + fun { s_name = typ; s_cols = cols } -> + pr " pub fn guestfs_free_%s (%s: *mut %s);\n" typ typ typ + ) external_structs; + + pr "}" + +let generate_rust_lib_rs () = + generate_header CPlusPlusStyle LGPLv2plus; + + pr "\ +extern crate libc; + +use libc::{ + c_char, c_float, c_int, c_uint, c_void, + int32_t, int64_t, + size_t, + uint32_t, uint64_t, +}; + +use std::collections::HashMap; +use std::ffi::CStr; +use std::ffi::CString; +use std::ptr; +use std::str; +use std::sync::Arc; + +// Temporarily allow dead_code in the ffi submodule. Eventually +// we can remove this once every ffi function has a safe wrapper. XXX +#[allow(dead_code)] +mod ffi; + +struct GuestfsHandleInternal { + g: *mut ffi::guestfs_h, +} + +impl Drop for GuestfsHandleInternal { + fn drop (&mut self) { + unsafe { ffi::guestfs_close (self.g); } + } +} + +pub struct GuestfsHandle { + rc: Arc, +} + +"; + + List.iter ( + fun { s_camel_name = typ; s_cols = cols } -> + pr "pub struct %s {\n" typ; + List.iter ( + function + | name, FString -> + pr " pub %s: String,\n" name + | name, FBuffer -> + pr " pub %s: Vec,\n" name + | name, FUUID -> + pr " pub %s: [u8;32],\n" name + | name, (FBytes|FInt64) -> + pr " pub %s: i64,\n" name + | name, FUInt64 -> + pr " pub %s: u64,\n" name + | name, FInt32 -> + pr " pub %s: i32,\n" name + | name, FUInt32 -> + pr " pub %s: u32,\n" name + | name, FOptPercent -> + pr " pub %s: f32,\n" name + | name, FChar -> + pr " pub %s: char,\n" name + ) cols; + pr "}\n"; + pr "type %sList = Vec<%s>;\n" typ typ; + pr "\n" + ) external_structs; + + pr "\ +fn get_last_error (g: *mut ffi::guestfs_h) -> String { + let error_cstr = unsafe { ffi::guestfs_last_error (g) }; + let error_buf = unsafe { CStr::from_ptr (error_cstr).to_bytes() }; + let error = str::from_utf8 (error_buf).unwrap().to_owned(); + return error; +} + +impl GuestfsHandle { + pub fn create () -> Result { + let g = unsafe { ffi::guestfs_create () }; + if g.is_null() { + //let errno = Error::last_os_error().raw_os_error(); + // XXX convert errno to a string + return Err (\"XXX\".to_string()); + } + + let rc = Arc::new (GuestfsHandleInternal { g: g }); + Ok (GuestfsHandle { rc: rc }) + } + +"; + + List.iter ( + function + | { name = name; style = _, _, (_::_) } -> + pr " // pub fn %s has optional arguments, functions with\n" name; + pr " // optional arguments are not yet implemented (XXX)\n" + + | { name = name; style = ret, args, [] } -> + pr " pub fn %s (&mut self" name; + List.iter ( + function + | Bool n -> pr ", %s: bool" n + | Int n -> pr ", %s: i32" n + | Int64 n -> pr ", %s: i64" n + | String n | Device n | Mountable n | Pathname n + | Dev_or_Path n | Mountable_or_Path n + | Key n | FileIn n | FileOut n + | GUID n -> + pr ", %s: String" n + | OptString n -> + pr ", %s: Option" n + | BufferIn n -> + pr ", %s: Vec" n + | StringList n + | DeviceList n + | FilenameList n -> + pr ", %s: Vec" n + | Pointer (t, n) -> + pr ", %s: i64 /* not impl */" n + ) args; + pr ") -> Result<"; + (match ret with + | RErr -> pr "()" + | RInt _ -> pr "i32" + | RInt64 _ -> pr "i64" + | RBool _ -> pr "bool" + | RConstString _ + | RConstOptString _ + | RString _ -> pr "String" + | RStringList _ -> pr "Vec" + | RStruct (_, typ) -> pr "%s" (camel_name_of_struct typ) + | RStructList (_, typ) -> pr "Vec<%s>" (camel_name_of_struct typ) + | RHashtable _ -> pr "HashMap" + | RBufferOut _ -> pr "Vec" + ); + pr ", String> {\n"; + + (* Some of the arguments need some pre-conversion. *) + List.iter ( + function + | Bool n + | Int n + | Int64 n -> () + | String n | Device n | Mountable n | Pathname n + | Dev_or_Path n | Mountable_or_Path n + | Key n | FileIn n | FileOut n + | GUID n + | BufferIn n -> + (* http://stackoverflow.com/a/28649572 *) + pr " let %s_cstr = CString::new(%s).unwrap();\n" n n; + pr " let %s_cptr = %s_cstr.as_ptr();\n" n n + | OptString n -> + pr " let %s_cstr = match %s {\n" n n; + pr " None => None,\n"; + pr " Some (%s) => Some (CString::new(%s).unwrap()),\n" + n n; + pr " };\n"; + pr " let %s_cptr = match %s_cstr {\n" n n; + pr " None => ptr::null(),\n"; + pr " Some (%s_cstr) => %s_cstr.as_ptr(),\n" n n; + pr " };\n"; + | StringList n + | DeviceList n + | FilenameList n -> + pr " let %s_1 = %s.iter()\n" n n; + pr " .map(|x| CString::new(x).unwrap())\n"; + pr " .collect::>();\n"; + pr " let %s_2 = %s_1.iter()\n" n n; + pr " .map(|x| x.as_ptr())\n"; + pr " .collect();\n"; + pr " %s_2.push (ptr::null());\n" n; + | Pointer (t, n) -> + pr " panic! (\"Pointer is not implemented\");\n" + ) args; + + (match ret with + | RBufferOut _ -> pr " let mut size_r : size_t = 0;\n"; + | _ -> () + ); + + (* Call the C function. *) + pr " let r = unsafe { ffi::guestfs_%s (self.rc.g" name; + List.iter ( + function + | Bool n -> pr ", %s as c_int" n + | Int n -> pr ", %s as int32_t" n + | Int64 n -> pr ", %s as int64_t" n + | String n | Device n | Mountable n | Pathname n + | Dev_or_Path n | Mountable_or_Path n + | Key n | FileIn n | FileOut n + | GUID n -> + pr ", %s_cptr" n + | OptString n -> + pr ", %s_cptr" n + | BufferIn n -> + pr ", %s.as_ptr(), %s.len() as size_t" n n + | StringList n + | DeviceList n + | FilenameList n -> + pr ", %s_2.as_ptr() as *const *const c_char" n + | Pointer (t, n) -> + pr ", 0 /* not impl */" + ) args; + (match ret with + | RBufferOut _ -> pr ", &mut size_r"; + | _ -> () + ); + + pr ") };\n"; + + (* Check for errors. *) + (match errcode_of_ret ret with + | `CannotReturnError -> () + | `ErrorIsMinusOne -> + pr " if r == -1 {\n"; + pr " return Err (get_last_error (self.rc.g));\n"; + pr " }\n"; + | `ErrorIsNULL -> + pr " if r.is_null() {\n"; + pr " return Err (get_last_error (self.rc.g));\n"; + pr " }\n"; + ); + + (* Convert the result from FFI to Rust. *) + (match ret with + | RErr -> + pr " Ok (())\n" + | RInt _ -> + pr " Ok (r)\n" + | RInt64 _ -> + pr " Ok (r)\n" + | RBool _ -> + pr " Ok (r)\n" + | RConstString _ + | RConstOptString _ + | RString _ -> + pr " let r_buf = unsafe { CStr::from_ptr (r).to_bytes() };\n"; + pr " Ok (str::from_utf8 (r_buf).unwrap().to_owned())\n"; + | RStringList _ -> + pr " panic! (\"not impl! XXX\");\n"; + | RStruct (_, typ) -> + pr " panic! (\"not impl! XXX\");\n"; + | RStructList (_, typ) -> + pr " panic! (\"not impl! XXX\");\n"; + | RHashtable _ -> + pr " panic! (\"not impl! XXX\");\n"; + | RBufferOut _ -> + pr " panic! (\"not impl! XXX\");\n"; + ); + + pr " }\n"; + pr "\n"; + ) public_functions_sorted; + + pr "} // impl GuestfsHandle\n" + +(* + // XXX generate these + pub fn version (&mut self) -> Result { + let v = unsafe { ffi::guestfs_version (self.rc.g) }; + if v.is_null() { + return Err (get_last_error (self.rc.g)); + } + let major = unsafe { ( *v).major }; + let minor = unsafe { ( *v).minor }; + let release = unsafe { ( *v).release }; + let extra_cstr = unsafe { ( *v).extra }; + let extra_buf = unsafe { CStr::from_ptr (extra_cstr).to_bytes() }; + let extra = str::from_utf8 (extra_buf).unwrap().to_owned(); + unsafe { ffi::guestfs_free_version (v) }; + Ok (Version { + major: major, + minor: minor, + release: release, + extra: extra, + }) + } +*) diff --git a/rust/Cargo.toml.in b/rust/Cargo.toml.in new file mode 100644 index 0000000..11d113e --- /dev/null +++ b/rust/Cargo.toml.in @@ -0,0 +1,9 @@ +[package] +name = "guestfs" +version = "@PACKAGE_VERSION@" +authors = ["libguestfs authors "] +description = "Library for accessing and modifying virtual machine images" +repository = "https://github.com/libguestfs/libguestfs" + +[dependencies] +libc = "*" diff --git a/rust/Makefile.am b/rust/Makefile.am new file mode 100644 index 0000000..7bcbfe6 --- /dev/null +++ b/rust/Makefile.am @@ -0,0 +1,39 @@ +# libguestfs Rust bindings +# Copyright (C) 2015 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 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. + +include $(top_srcdir)/subdir-rules.mk + +generator_built = \ + src/ffi.rs \ + src/lib.rs + +EXTRA_DIST = \ + $(generator_built) + +CLEANFILES = \ + *~ \ + Cargo.lock + +clean-local: + -rm -rf target + +if HAVE_RUST + +all-local: + $(CARGO) build + +endif diff --git a/rust/src/.gitignore b/rust/src/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/src/guestfs.pod b/src/guestfs.pod index f8d7e2c..31b0ddf 100644 --- a/src/guestfs.pod +++ b/src/guestfs.pod @@ -4606,6 +4606,8 @@ L command and documentation. =item F +=item F + Language bindings. =back -- 2.5.0