All of lore.kernel.org
 help / color / mirror / Atom feed
* [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now)
@ 2020-10-11 20:34 marcandre.lureau
  2020-10-11 20:34 ` [PoCv2 01/15] mingw: fix error __USE_MINGW_ANSI_STDIO redefined marcandre.lureau
                   ` (15 more replies)
  0 siblings, 16 replies; 18+ messages in thread
From: marcandre.lureau @ 2020-10-11 20:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: pbonzini, berrange, armbru, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Hi,

Among the QEMU developers, there is a desire to use Rust. (see previous
thread from Stefan "Why QEMU should move from C to Rust", the rust-vmm
related projects and other experiments).

Thanks to our QAPI type system and the associate code generator, it is
relatively straightforward to create Rust bindings for the generated C
types (also called sys/ffi binding) and functions. (rust-bindgen could
probably do a similar job, but it would probably bring other issues).
This provides an important internal API already.

Slightly more complicated is to expose a Rust API for those, and provide
convenient conversions C<->Rust. Taking inspiration from glib-rs
binding, I implemented a simplified version of the FromGlib/ToGlib
traits, with simpler ownership model, sufficient for QAPI needs.

The usage is relatively simple:

- from_qemu_none(ptr: *const sys::P) -> T
  Return a Rust type T for a const ffi pointer P.

- from_qemu_full(ptr: *mut sys::P) -> T
  Return a Rust type T for a ffi pointer P, taking ownership.

- T::to_qemu_none() -> Stash<P>
  Returns a borrowed ffi pointer P (using a Stash to destroy "glue"
  storage data, if any).

- T::to_qemu_full() -> P
  Returns a ffi pointer P. (P resources are leaked/passed to C/ffi)

With those traits, it's relatively easy to implement the QMP callbacks. With
enough interest, we could eventually add new commands in Rust, and start
rewriting QGA in Rust, as it is a simple service. See qga/qmp/ for some
examples. QEMU would be the next obvious target.

My biggest pain-point right now is the handling of 'if' conditions. I tried
different approaches, but none of them are satisfying. I am planning to tackle
this next again, along with full QEMU API schema support and hopefully some t=
est
integration.

v2:
 - split the original patch in smaller patches and more digestable form
 - dropped the DBus interface experiment from this series
 - various build-sys improvements, new configure options --with-rust(-target)
 - various attempts at better meson integration, finally satisfied enough wit=
h the
   current solution, which handles getting link flags from Rust sanely.
   (more meson stuff to come to handle config-host/features mapping etc).
 - rebased, QGA QMP now uses unions, added support for it.
 - start a common crate (which re-surfaced issues with foreign types and trai=
ts,
   worked around with a NewPtr wrapper)
 - explicit errors when ifcond are presents (after various unsucessful attemp=
ts,
   I will try to tackle it in v3)
 - mingw cross compilation support
 - some attempts to add it to CI
 - actually implement {get,set}-vcpus
 - vendor the external crates

Marc-Andr=C3=A9 Lureau (15):
  mingw: fix error __USE_MINGW_ANSI_STDIO redefined
  scripts/qapi: teach c_param_type() to return const argument type
  build-sys: add --with-rust{-target} & basic build infrastructure
  build-sys: add a cargo-wrapper script
  qga/rust: build and link an empty static library
  rust: provide a common crate for QEMU
  scripts/qapi: add Rust sys bindings generation
  qga/rust: generate QGA QAPI sys bindings
  scripts/qapi: add generation of Rust bindings for types
  qga/rust: build Rust types
  qga: add qmp! macro helper
  qga: implement get-host-name in Rust
  qga: implement {get,set}-vcpus in Rust
  travis: add Rust
  rust: use vendored-sources

 .cargo/config                |   5 +
 .gitmodules                  |   3 +
 .travis.yml                  |  18 +-
 Cargo.toml                   |   5 +
 configure                    |  26 ++
 include/qemu/osdep.h         |  10 -
 meson.build                  |  29 ++-
 migration/dirtyrate.c        |   3 +-
 qga/Cargo.toml               |  20 ++
 qga/commands-posix.c         | 159 -------------
 qga/commands-win32.c         |  76 ------
 qga/commands.c               |  34 +--
 qga/lib.rs                   |   5 +
 qga/meson.build              |  30 ++-
 qga/qapi.rs                  |   6 +
 qga/qapi_sys.rs              |   5 +
 qga/qmp/hostname.rs          |   9 +
 qga/qmp/mod.rs               |  61 +++++
 qga/qmp/vcpus.rs             | 161 +++++++++++++
 rust/common/Cargo.toml       |  11 +
 rust/common/src/error.rs     | 109 +++++++++
 rust/common/src/lib.rs       |  10 +
 rust/common/src/qemu.rs      |  30 +++
 rust/common/src/sys.rs       |  58 +++++
 rust/common/src/translate.rs | 309 ++++++++++++++++++++++++
 rust/vendored                |   1 +
 scripts/cargo_wrapper.py     | 102 ++++++++
 scripts/qapi-gen.py          |  18 +-
 scripts/qapi/rs.py           | 204 ++++++++++++++++
 scripts/qapi/rs_sys.py       | 254 ++++++++++++++++++++
 scripts/qapi/rs_types.py     | 447 +++++++++++++++++++++++++++++++++++
 scripts/qapi/schema.py       |  14 +-
 tests/test-bitmap.c          |   1 -
 tests/test-qga.c             |   4 +
 util/oslib-posix.c           |  35 ---
 util/oslib-win32.c           |  13 -
 36 files changed, 1962 insertions(+), 323 deletions(-)
 create mode 100644 .cargo/config
 create mode 100644 Cargo.toml
 create mode 100644 qga/Cargo.toml
 create mode 100644 qga/lib.rs
 create mode 100644 qga/qapi.rs
 create mode 100644 qga/qapi_sys.rs
 create mode 100644 qga/qmp/hostname.rs
 create mode 100644 qga/qmp/mod.rs
 create mode 100644 qga/qmp/vcpus.rs
 create mode 100644 rust/common/Cargo.toml
 create mode 100644 rust/common/src/error.rs
 create mode 100644 rust/common/src/lib.rs
 create mode 100644 rust/common/src/qemu.rs
 create mode 100644 rust/common/src/sys.rs
 create mode 100644 rust/common/src/translate.rs
 create mode 160000 rust/vendored
 create mode 100644 scripts/cargo_wrapper.py
 create mode 100644 scripts/qapi/rs.py
 create mode 100644 scripts/qapi/rs_sys.py
 create mode 100644 scripts/qapi/rs_types.py

--=20
2.28.0




^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PoCv2 01/15] mingw: fix error __USE_MINGW_ANSI_STDIO redefined
  2020-10-11 20:34 [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now) marcandre.lureau
@ 2020-10-11 20:34 ` marcandre.lureau
  2020-10-11 20:37   ` Marc-André Lureau
  2020-10-11 20:35 ` [PoCv2 02/15] scripts/qapi: teach c_param_type() to return const argument type marcandre.lureau
                   ` (14 subsequent siblings)
  15 siblings, 1 reply; 18+ messages in thread
From: marcandre.lureau @ 2020-10-11 20:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: pbonzini, berrange, armbru, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Always put osdep.h first, and remove redundant stdlib.h include.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 migration/dirtyrate.c | 3 ++-
 tests/test-bitmap.c   | 1 -
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/migration/dirtyrate.c b/migration/dirtyrate.c
index 68577ef250..47f761e67a 100644
--- a/migration/dirtyrate.c
+++ b/migration/dirtyrate.c
@@ -10,8 +10,9 @@
  * See the COPYING file in the top-level directory.
  */
 
-#include <zlib.h>
 #include "qemu/osdep.h"
+
+#include <zlib.h>
 #include "qapi/error.h"
 #include "cpu.h"
 #include "qemu/config-file.h"
diff --git a/tests/test-bitmap.c b/tests/test-bitmap.c
index 2f5b71458a..8db4f67883 100644
--- a/tests/test-bitmap.c
+++ b/tests/test-bitmap.c
@@ -8,7 +8,6 @@
  * Author: Peter Xu <peterx@redhat.com>
  */
 
-#include <stdlib.h>
 #include "qemu/osdep.h"
 #include "qemu/bitmap.h"
 
-- 
2.28.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PoCv2 02/15] scripts/qapi: teach c_param_type() to return const argument type
  2020-10-11 20:34 [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now) marcandre.lureau
  2020-10-11 20:34 ` [PoCv2 01/15] mingw: fix error __USE_MINGW_ANSI_STDIO redefined marcandre.lureau
@ 2020-10-11 20:35 ` marcandre.lureau
  2020-10-11 20:35 ` [PoCv2 03/15] build-sys: add --with-rust{-target} & basic build infrastructure marcandre.lureau
                   ` (13 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: marcandre.lureau @ 2020-10-11 20:35 UTC (permalink / raw)
  To: qemu-devel; +Cc: pbonzini, berrange, armbru, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

As it should be, since the argument isn't owned by the callee,
but a lot of code in QEMU rely on non-const arguments to tweak it.

Since Rust types / bindings are derived from the C version, we have to
be more accurate there to do correct ownership conversions.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 scripts/qapi/schema.py | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index d1307ec661..ca85c7273a 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -167,8 +167,14 @@ class QAPISchemaType(QAPISchemaEntity):
         pass
 
     # Return the C type to be used in a parameter list.
-    def c_param_type(self):
-        return self.c_type()
+    #
+    # The argument should be considered const, since no ownership is given to the callee,
+    # but qemu C code frequently tweaks it. Set const=True for a stricter declaration.
+    def c_param_type(self, const=False):
+        c_type = self.c_type()
+        if const and c_type.endswith(pointer_suffix):
+            c_type = 'const ' + c_type
+        return c_type
 
     # Return the C type to be used where we suppress boxing.
     def c_unboxed_type(self):
@@ -221,10 +227,10 @@ class QAPISchemaBuiltinType(QAPISchemaType):
     def c_type(self):
         return self._c_type_name
 
-    def c_param_type(self):
+    def c_param_type(self, const=False):
         if self.name == 'str':
             return 'const ' + self._c_type_name
-        return self._c_type_name
+        return super().c_param_type(const)
 
     def json_type(self):
         return self._json_type_name
-- 
2.28.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PoCv2 03/15] build-sys: add --with-rust{-target} & basic build infrastructure
  2020-10-11 20:34 [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now) marcandre.lureau
  2020-10-11 20:34 ` [PoCv2 01/15] mingw: fix error __USE_MINGW_ANSI_STDIO redefined marcandre.lureau
  2020-10-11 20:35 ` [PoCv2 02/15] scripts/qapi: teach c_param_type() to return const argument type marcandre.lureau
@ 2020-10-11 20:35 ` marcandre.lureau
  2020-10-11 20:35 ` [PoCv2 04/15] build-sys: add a cargo-wrapper script marcandre.lureau
                   ` (12 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: marcandre.lureau @ 2020-10-11 20:35 UTC (permalink / raw)
  To: qemu-devel; +Cc: pbonzini, berrange, armbru, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Add the build-sys infrastructure to build Rust code. Introduce a
top-level workspace, so various sub-projects (libraries, executables
etc) can be developed together, sharing the dependencies and output
directory.

If not Tier 1, many of the platforms QEMU supports are considered Tier
2: https://doc.rust-lang.org/nightly/rustc/platform-support.html

Rust is generally available on various distributions (thanks to Firefox,
I suppose). If not, it can usually be installed with rustup by
developpers.

configure will enable Rust support automatically if cargo is present.
Rust support can be disabled --without-rust. When detecting windows
cross building, it will use the $cpu-pc-windows-gnu target by
default (more default mappings could be added over time). This can be
changed with --with-rust-target=RTT.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 Cargo.toml  |  2 ++
 configure   | 18 ++++++++++++++++++
 meson.build | 23 +++++++++++++++++++++++
 3 files changed, 43 insertions(+)
 create mode 100644 Cargo.toml

diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000000..c4b464ff15
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = []
diff --git a/configure b/configure
index b553288c5e..7945ceac63 100755
--- a/configure
+++ b/configure
@@ -446,6 +446,8 @@ meson=""
 ninja=""
 skip_meson=no
 gettext=""
+with_rust="auto"
+with_rust_target=""
 
 bogus_os="no"
 malloc_trim="auto"
@@ -1519,6 +1521,12 @@ for opt do
   ;;
   --disable-libdaxctl) libdaxctl=no
   ;;
+  --with-rust) with_rust=yes
+  ;;
+  --without-rust) with_rust=no
+  ;;
+  --with-rust-target=*) with_rust_target="$optarg"
+  ;;
   *)
       echo "ERROR: unknown option $opt"
       echo "Try '$0 --help' for more information"
@@ -1666,6 +1674,8 @@ Advanced options (experts only):
   --extra-ldflags=LDFLAGS  append extra linker flags LDFLAGS
   --cross-cc-ARCH=CC       use compiler when building ARCH guest test cases
   --cross-cc-flags-ARCH=   use compiler flags when building ARCH guest tests
+  --with-rust              enable Rust compilation
+  --with-rust-target=RTT   use the given Rust target triple
   --make=MAKE              use specified make [$make]
   --python=PYTHON          use specified python [$python]
   --sphinx-build=SPHINX    use specified sphinx-build [$sphinx_build]
@@ -1918,6 +1928,10 @@ if test -z "$ninja"; then
     done
 fi
 
+if test "$with_rust" = auto && has cargo; then
+    with_rust=yes
+fi
+
 # Check that the C compiler works. Doing this here before testing
 # the host CPU ensures that we had a valid CC to autodetect the
 # $cpu var (and we should bail right here if that's not the case).
@@ -7046,6 +7060,10 @@ fi
 if test "$safe_stack" = "yes"; then
   echo "CONFIG_SAFESTACK=y" >> $config_host_mak
 fi
+if test "$with_rust" = "yes" ; then
+  echo "CONFIG_WITH_RUST=y" >> $config_host_mak
+  echo "CONFIG_WITH_RUST_TARGET=$with_rust_target" >> $config_host_mak
+fi
 
 # If we're using a separate build tree, set it up now.
 # DIRS are directories which we simply mkdir in the build tree;
diff --git a/meson.build b/meson.build
index 17c89c87c6..d8526dc999 100644
--- a/meson.build
+++ b/meson.build
@@ -73,6 +73,28 @@ if cpu in ['x86', 'x86_64']
   }
 endif
 
+with_rust = 'CONFIG_WITH_RUST' in config_host
+cargo = find_program('cargo', required: with_rust)
+
+if with_rust
+  rs_target_triple = config_host['CONFIG_WITH_RUST_TARGET']
+  if meson.is_cross_build()
+    # more default target mappings may be added over time
+    if rs_target_triple == '' and targetos == 'windows'
+      rs_target_triple = host_machine.cpu() + '-pc-windows-gnu'
+    endif
+    if rs_target_triple == ''
+      error('cross-compiling, but no Rust target-triple defined.')
+    endif
+  endif
+endif
+
+if get_option('optimization') in ['0', '1', 'g']
+  rs_build_type = 'debug'
+else
+  rs_build_type = 'release'
+endif
+
 ##################
 # Compiler flags #
 ##################
@@ -1770,6 +1792,7 @@ endif
 if targetos == 'darwin'
   summary_info += {'Objective-C compiler': meson.get_compiler('objc').cmd_array()[0]}
 endif
+summary_info += {'Rust support':      with_rust}
 summary_info += {'ARFLAGS':           config_host['ARFLAGS']}
 summary_info += {'CFLAGS':            ' '.join(get_option('c_args')
                                                + ['-O' + get_option('optimization')]
-- 
2.28.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PoCv2 04/15] build-sys: add a cargo-wrapper script
  2020-10-11 20:34 [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now) marcandre.lureau
                   ` (2 preceding siblings ...)
  2020-10-11 20:35 ` [PoCv2 03/15] build-sys: add --with-rust{-target} & basic build infrastructure marcandre.lureau
@ 2020-10-11 20:35 ` marcandre.lureau
  2020-10-11 20:35 ` [PoCv2 05/15] qga/rust: build and link an empty static library marcandre.lureau
                   ` (11 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: marcandre.lureau @ 2020-10-11 20:35 UTC (permalink / raw)
  To: qemu-devel; +Cc: pbonzini, berrange, armbru, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Introduce a script to help calling cargo from Rust.

Cargo is the most convenient way to build Rust code, with various
crates, and has many features that meson lacks in general for Rust.
Trying to convert projects to meson automatically is an option I
considered (see for ex https://github.com/badboy/bygge for ninja
conversion), but the complexity of the task and the need of build.rs
mechanism in various crates makes this endeavour out of scope at this
point.

This script will help to invoke cargo in different ways. For now, it
supports building static libraries. It sets up a common environment, run
the compiler and grep its output for the linker flags. Finally it copies
the built library to the expected meson output directory, and create a
file with the linker arguments.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 meson.build              |   1 +
 scripts/cargo_wrapper.py | 101 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 102 insertions(+)
 create mode 100644 scripts/cargo_wrapper.py

diff --git a/meson.build b/meson.build
index d8526dc999..c30bb290c5 100644
--- a/meson.build
+++ b/meson.build
@@ -75,6 +75,7 @@ endif
 
 with_rust = 'CONFIG_WITH_RUST' in config_host
 cargo = find_program('cargo', required: with_rust)
+cargo_wrapper = find_program('scripts/cargo_wrapper.py')
 
 if with_rust
   rs_target_triple = config_host['CONFIG_WITH_RUST_TARGET']
diff --git a/scripts/cargo_wrapper.py b/scripts/cargo_wrapper.py
new file mode 100644
index 0000000000..164fad5123
--- /dev/null
+++ b/scripts/cargo_wrapper.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python3
+# Copyright (c) 2020 Red Hat, Inc.
+#
+# Author:
+#  Marc-André Lureau <marcandre.lureau@gmail.com>
+#
+# This work is licensed under the terms of the GNU GPL, version 2 or
+# later.  See the COPYING file in the top-level directory.
+
+import argparse
+import configparser
+import distutils.file_util
+import glob
+import os
+import os.path
+import re
+import subprocess
+import sys
+from typing import List
+
+
+def get_cargo_target_dir(args: argparse.Namespace) -> str:
+    # avoid conflict with qemu "target" directory
+    return os.path.join(args.build_dir, "rs-target")
+
+
+def get_manifest_path(args: argparse.Namespace) -> str:
+    return os.path.join(args.src_dir, "Cargo.toml")
+
+
+def build_lib(args: argparse.Namespace) -> None:
+    target_dir = get_cargo_target_dir(args)
+    manifest_path = get_manifest_path(args)
+    # let's pretend it's an INI file to avoid extra dependency
+    config = configparser.ConfigParser()
+    config.read(manifest_path)
+    package_name = config["package"]["name"].strip('"')
+    liba = os.path.join(
+        target_dir, args.target_triple, args.build_type, "lib" + package_name + ".a"
+    )
+    libargs = os.path.join(args.build_dir, "lib" + package_name + ".args")
+
+    env = {}
+    env["MESON_CURRENT_BUILD_DIR"] = args.build_dir
+    env["MESON_BUILD_ROOT"] = args.build_root
+    env["WINAPI_NO_BUNDLED_LIBRARIES"] = "1"
+    cargo_cmd = [
+        "cargo",
+        "rustc",
+        "--target-dir",
+        target_dir,
+        "--manifest-path",
+        manifest_path,
+    ]
+    if args.target_triple:
+        cargo_cmd += ["--target", args.target_triple]
+    if args.build_type == "release":
+        cargo_cmd += ["--release"]
+    cargo_cmd += ["--", "--print", "native-static-libs"]
+    cargo_cmd += args.EXTRA
+    try:
+        out = subprocess.check_output(
+            cargo_cmd,
+            env=dict(os.environ, **env),
+            stderr=subprocess.STDOUT,
+            universal_newlines=True,
+        )
+        native_static_libs = re.search(r"native-static-libs:(.*)", out)
+        link_args = native_static_libs.group(1)
+        with open(libargs, "w") as f:
+            print(link_args, file=f)
+
+        distutils.file_util.copy_file(liba, args.build_dir, update=True)
+    except subprocess.CalledProcessError as e:
+        print(
+            "Environment: " + " ".join(["{}={}".format(k, v) for k, v in env.items()])
+        )
+        print("Command: " + " ".join(cargo_cmd))
+        print(e.output)
+        sys.exit(1)
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser()
+    parser.add_argument("command")
+    parser.add_argument("build_dir")
+    parser.add_argument("src_dir")
+    parser.add_argument("build_root")
+    parser.add_argument("build_type")
+    parser.add_argument("target_triple")
+    parser.add_argument("EXTRA", nargs="*")
+    args = parser.parse_args()
+
+    if args.command == "build-lib":
+        build_lib(args)
+    else:
+        raise argparse.ArgumentTypeError("Unknown command: %s" % args.command)
+
+
+if __name__ == "__main__":
+    main()
-- 
2.28.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PoCv2 05/15] qga/rust: build and link an empty static library
  2020-10-11 20:34 [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now) marcandre.lureau
                   ` (3 preceding siblings ...)
  2020-10-11 20:35 ` [PoCv2 04/15] build-sys: add a cargo-wrapper script marcandre.lureau
@ 2020-10-11 20:35 ` marcandre.lureau
  2020-10-11 20:35 ` [PoCv2 06/15] rust: provide a common crate for QEMU marcandre.lureau
                   ` (10 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: marcandre.lureau @ 2020-10-11 20:35 UTC (permalink / raw)
  To: qemu-devel; +Cc: pbonzini, berrange, armbru, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Meson doesn't integrate very smoothly with Cargo. Use the cargo-wrapper
script as a custom_target() always stale to build the Rust code. The
"build-lib" command will produce a static library in the meson expected
output directory, as well as link flags that must be employed to do the
final link.

Those link flags can't be queried during configure time (Cargo doesn't
have a user-queriable configure step), so we pass them to the linker
thanks to @file argument support at build time.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 Cargo.toml      |  2 +-
 qga/Cargo.toml  |  9 +++++++++
 qga/lib.rs      |  0
 qga/meson.build | 18 +++++++++++++++++-
 4 files changed, 27 insertions(+), 2 deletions(-)
 create mode 100644 qga/Cargo.toml
 create mode 100644 qga/lib.rs

diff --git a/Cargo.toml b/Cargo.toml
index c4b464ff15..e69b04200f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,2 +1,2 @@
 [workspace]
-members = []
+members = ["qga"]
diff --git a/qga/Cargo.toml b/qga/Cargo.toml
new file mode 100644
index 0000000000..50c3415ab2
--- /dev/null
+++ b/qga/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "qga"
+version = "0.1.0"
+edition = "2018"
+license = "GPLv2"
+
+[lib]
+path = "lib.rs"
+crate-type = ["staticlib"]
diff --git a/qga/lib.rs b/qga/lib.rs
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/qga/meson.build b/qga/meson.build
index cd08bd953a..62e13a11b3 100644
--- a/qga/meson.build
+++ b/qga/meson.build
@@ -45,9 +45,25 @@ qga_ss.add(when: 'CONFIG_WIN32', if_true: files(
 
 qga_ss = qga_ss.apply(config_host, strict: false)
 
+qga_rs = declare_dependency()
+if with_rust
+  cargo_qga = custom_target('cargo-qga',
+                            build_by_default: true,
+                            output: ['libqga.args', 'libqga.a'],
+                            build_always_stale: true,
+                            command: [cargo_wrapper,
+                                      'build-lib',
+                                      meson.current_build_dir(),
+                                      meson.current_source_dir(),
+                                      meson.build_root(),
+                                      rs_build_type,
+                                      rs_target_triple])
+  qga_rs = declare_dependency(link_args: '@' + cargo_qga.full_path(), sources: cargo_qga)
+endif
+
 qga = executable('qemu-ga', qga_ss.sources(),
                  link_args: config_host['LIBS_QGA'].split(),
-                 dependencies: [qemuutil, libudev],
+                 dependencies: [qemuutil, libudev, qga_rs],
                  install: true)
 all_qga = [qga]
 
-- 
2.28.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PoCv2 06/15] rust: provide a common crate for QEMU
  2020-10-11 20:34 [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now) marcandre.lureau
                   ` (4 preceding siblings ...)
  2020-10-11 20:35 ` [PoCv2 05/15] qga/rust: build and link an empty static library marcandre.lureau
@ 2020-10-11 20:35 ` marcandre.lureau
  2020-10-11 20:35 ` [PoCv2 07/15] scripts/qapi: add Rust sys bindings generation marcandre.lureau
                   ` (9 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: marcandre.lureau @ 2020-10-11 20:35 UTC (permalink / raw)
  To: qemu-devel; +Cc: pbonzini, berrange, armbru, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

This crates provides common bindings and facilities for QEMU C API
shared by various projects.

Most importantly, it defines the conversion traits used to convert from
C to Rust types. Those traits are largely adapted from glib-rs, since
those have prooven to be very flexible, and should guide us to bind
further QEMU types such as QOM. If glib-rs becomes a dependency, we
should consider adopting glib translate traits. For QAPI, we need a
smaller subset.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 Cargo.toml                   |   5 +-
 rust/common/Cargo.toml       |  11 ++
 rust/common/src/error.rs     | 109 ++++++++++++
 rust/common/src/lib.rs       |  10 ++
 rust/common/src/qemu.rs      |  30 ++++
 rust/common/src/sys.rs       |  58 +++++++
 rust/common/src/translate.rs | 309 +++++++++++++++++++++++++++++++++++
 7 files changed, 531 insertions(+), 1 deletion(-)
 create mode 100644 rust/common/Cargo.toml
 create mode 100644 rust/common/src/error.rs
 create mode 100644 rust/common/src/lib.rs
 create mode 100644 rust/common/src/qemu.rs
 create mode 100644 rust/common/src/sys.rs
 create mode 100644 rust/common/src/translate.rs

diff --git a/Cargo.toml b/Cargo.toml
index e69b04200f..26bd083f79 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,2 +1,5 @@
 [workspace]
-members = ["qga"]
+members = [
+  "qga",
+  "rust/common",
+]
diff --git a/rust/common/Cargo.toml b/rust/common/Cargo.toml
new file mode 100644
index 0000000000..f0584dcc83
--- /dev/null
+++ b/rust/common/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "common"
+version = "0.1.0"
+edition = "2018"
+license = "GPLv2"
+
+[dependencies]
+libc = "^0.2.76"
+
+[target."cfg(unix)".dependencies]
+nix = "^0.18.0"
diff --git a/rust/common/src/error.rs b/rust/common/src/error.rs
new file mode 100644
index 0000000000..b89f788833
--- /dev/null
+++ b/rust/common/src/error.rs
@@ -0,0 +1,109 @@
+use std::{self, ffi, fmt, io, ptr};
+
+use crate::translate::*;
+use crate::{qemu, sys};
+
+/// Common error type for QEMU and related projects.
+#[derive(Debug)]
+pub enum Error {
+    FailedAt(String, &'static str, u32),
+    Io(io::Error),
+    #[cfg(unix)]
+    Nix(nix::Error),
+}
+
+/// Alias for a `Result` with the error type for QEMU.
+pub type Result<T> = std::result::Result<T, Error>;
+
+impl Error {
+    fn message(&self) -> String {
+        use Error::*;
+        match self {
+            FailedAt(msg, _, _) => msg.into(),
+            Io(io) => format!("IO error: {}", io),
+            #[cfg(unix)]
+            Nix(nix) => format!("Nix error: {}", nix),
+        }
+    }
+
+    fn location(&self) -> Option<(&'static str, u32)> {
+        use Error::*;
+        match self {
+            FailedAt(_, file, line) => Some((file, *line)),
+            Io(_) => None,
+            #[cfg(unix)]
+            Nix(_) => None,
+        }
+    }
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use Error::*;
+        match self {
+            FailedAt(msg, file, line) => write!(f, "{} ({}:{})", msg, file, line),
+            _ => write!(f, "{}", self.message()),
+        }
+    }
+}
+
+impl From<io::Error> for Error {
+    fn from(val: io::Error) -> Self {
+        Error::Io(val)
+    }
+}
+
+#[cfg(unix)]
+impl From<nix::Error> for Error {
+    fn from(val: nix::Error) -> Self {
+        Error::Nix(val)
+    }
+}
+
+impl QemuPtrDefault for Error {
+    type QemuType = *mut sys::Error;
+}
+
+impl<'a> ToQemuPtr<'a, *mut sys::Error> for Error {
+    type Storage = qemu::CError;
+
+    fn to_qemu_none(&'a self) -> Stash<'a, *mut sys::Error, Self> {
+        let err = self.to_qemu_full();
+
+        Stash(err, unsafe { from_qemu_full(err) })
+    }
+
+    fn to_qemu_full(&self) -> *mut sys::Error {
+        let cmsg =
+            ffi::CString::new(self.message()).expect("ToQemuPtr<Error>: unexpected '\0' character");
+        let mut csrc = ffi::CString::new("").unwrap();
+        let (src, line) = self.location().map_or((ptr::null(), 0 as i32), |loc| {
+            csrc = ffi::CString::new(loc.0).expect("ToQemuPtr<Error>:: unexpected '\0' character");
+            (csrc.as_ptr() as *const libc::c_char, loc.1 as i32)
+        });
+        let func = ptr::null();
+
+        let mut err: *mut sys::Error = ptr::null_mut();
+        unsafe {
+            sys::error_setg_internal(
+                &mut err as *mut *mut _,
+                src,
+                line,
+                func,
+                cmsg.as_ptr() as *const libc::c_char,
+            );
+            err
+        }
+    }
+}
+
+/// Convenience macro to build a `Error::FailedAt` error.
+///
+/// (this error type can be nicely converted to a QEMU `sys::Error`)
+#[allow(unused_macros)]
+#[macro_export]
+macro_rules! err {
+    ($err:expr) => {
+        Err(Error::FailedAt($err.into(), file!(), line!()))
+    };
+}
diff --git a/rust/common/src/lib.rs b/rust/common/src/lib.rs
new file mode 100644
index 0000000000..2632a2b92b
--- /dev/null
+++ b/rust/common/src/lib.rs
@@ -0,0 +1,10 @@
+mod error;
+pub use error::*;
+
+mod qemu;
+pub use qemu::*;
+
+mod translate;
+pub use translate::*;
+
+pub mod sys;
diff --git a/rust/common/src/qemu.rs b/rust/common/src/qemu.rs
new file mode 100644
index 0000000000..e1e47d3623
--- /dev/null
+++ b/rust/common/src/qemu.rs
@@ -0,0 +1,30 @@
+use std::{ffi::CStr, ptr, str};
+
+use crate::{sys, translate};
+
+/// A type representing an owned C QEMU Error.
+pub struct CError(ptr::NonNull<sys::Error>);
+
+impl translate::FromQemuPtrFull<*mut sys::Error> for CError {
+    unsafe fn from_qemu_full(ptr: *mut sys::Error) -> Self {
+        assert!(!ptr.is_null());
+        Self(ptr::NonNull::new_unchecked(ptr))
+    }
+}
+
+impl CError {
+    pub fn pretty(&self) -> &str {
+        unsafe {
+            let pretty = sys::error_get_pretty(self.0.as_ptr());
+            let bytes = CStr::from_ptr(pretty).to_bytes();
+            str::from_utf8(bytes)
+                .unwrap_or_else(|err| str::from_utf8(&bytes[..err.valid_up_to()]).unwrap())
+        }
+    }
+}
+
+impl Drop for CError {
+    fn drop(&mut self) {
+        unsafe { sys::error_free(self.0.as_ptr()) }
+    }
+}
diff --git a/rust/common/src/sys.rs b/rust/common/src/sys.rs
new file mode 100644
index 0000000000..de37144860
--- /dev/null
+++ b/rust/common/src/sys.rs
@@ -0,0 +1,58 @@
+//! Bindings to the raw low-level C API commonly provided by QEMU projects.
+//!
+//! Manual bindings to C API availabe when linking QEMU projects.
+//! It includes minimal glib allocation functions too, since it's the default
+//! allocator used by QEMU, and we don't depend on glib-rs crate yet).
+//!
+//! Higher-level Rust-friendly bindings are provided by different modules.
+
+use libc::{c_char, c_void, size_t};
+
+extern "C" {
+    pub fn g_malloc0(n_bytes: size_t) -> *mut c_void;
+    pub fn g_free(ptr: *mut c_void);
+    pub fn g_strndup(str: *const c_char, n: size_t) -> *mut c_char;
+}
+
+#[repr(C)]
+pub struct QObject(c_void);
+
+impl ::std::fmt::Debug for QObject {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+        f.debug_struct(&format!("QObject @ {:?}", self as *const _))
+            .finish()
+    }
+}
+
+#[repr(C)]
+pub struct QNull(c_void);
+
+impl ::std::fmt::Debug for QNull {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+        f.debug_struct(&format!("QNull @ {:?}", self as *const _))
+            .finish()
+    }
+}
+
+#[repr(C)]
+pub struct Error(c_void);
+
+impl ::std::fmt::Debug for Error {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+        f.debug_struct(&format!("Error @ {:?}", self as *const _))
+            .finish()
+    }
+}
+
+extern "C" {
+    pub fn error_setg_internal(
+        errp: *mut *mut Error,
+        src: *const c_char,
+        line: i32,
+        func: *const c_char,
+        fmt: *const c_char,
+        ...
+    );
+    pub fn error_get_pretty(err: *const Error) -> *const c_char;
+    pub fn error_free(err: *mut Error);
+}
diff --git a/rust/common/src/translate.rs b/rust/common/src/translate.rs
new file mode 100644
index 0000000000..3be6e91987
--- /dev/null
+++ b/rust/common/src/translate.rs
@@ -0,0 +1,309 @@
+// largely adapted from glib-rs
+// we don't depend on glib-rs as this brings a lot more code that we may not need
+// and also because there are issues with the conversion traits for our sys::*mut.
+use libc::{c_char, size_t};
+use std::ffi::{CStr, CString};
+use std::ptr;
+
+use crate::sys;
+
+/// A pointer.
+pub trait Ptr: Copy + 'static {
+    fn is_null(&self) -> bool;
+    fn from<X>(ptr: *mut X) -> Self;
+    fn to<X>(self) -> *mut X;
+}
+
+impl<T: 'static> Ptr for *const T {
+    #[inline]
+    fn is_null(&self) -> bool {
+        (*self).is_null()
+    }
+
+    #[inline]
+    fn from<X>(ptr: *mut X) -> *const T {
+        ptr as *const T
+    }
+
+    #[inline]
+    fn to<X>(self) -> *mut X {
+        self as *mut X
+    }
+}
+
+impl<T: 'static> Ptr for *mut T {
+    #[inline]
+    fn is_null(&self) -> bool {
+        (*self).is_null()
+    }
+
+    #[inline]
+    fn from<X>(ptr: *mut X) -> *mut T {
+        ptr as *mut T
+    }
+
+    #[inline]
+    fn to<X>(self) -> *mut X {
+        self as *mut X
+    }
+}
+
+/// Macro for NewPtr.
+///
+/// A macro to declare a newtype for pointers, to workaround that *T are not
+/// defined in our binding crates, and allow foreign traits implementations.
+/// (this is used by qapi-gen bindings)
+#[allow(unused_macros)]
+#[macro_export]
+macro_rules! new_ptr {
+    () => {
+        #[derive(Copy, Clone)]
+        pub struct NewPtr<P: Ptr>(pub P);
+
+        impl<P: Ptr> Ptr for NewPtr<P> {
+            #[inline]
+            fn is_null(&self) -> bool {
+                self.0.is_null()
+            }
+
+            #[inline]
+            fn from<X>(ptr: *mut X) -> Self {
+                NewPtr(P::from(ptr))
+            }
+
+            #[inline]
+            fn to<X>(self) -> *mut X {
+                self.0.to()
+            }
+        }
+    };
+}
+
+/// Provides the default pointer type to be used in some container conversions.
+///
+/// It's `*mut c_char` for `String`, `*mut sys::GuestInfo` for `GuestInfo`...
+pub trait QemuPtrDefault {
+    type QemuType: Ptr;
+}
+
+impl QemuPtrDefault for String {
+    type QemuType = *mut c_char;
+}
+
+pub struct Stash<'a, P: Copy, T: ?Sized + ToQemuPtr<'a, P>>(
+    pub P,
+    pub <T as ToQemuPtr<'a, P>>::Storage,
+);
+
+/// Translate to a pointer.
+pub trait ToQemuPtr<'a, P: Copy> {
+    type Storage;
+
+    /// The pointer in the `Stash` is only valid for the lifetime of the `Stash`.
+    fn to_qemu_none(&'a self) -> Stash<'a, P, Self>;
+
+    /// Transfer the ownership to the ffi.
+    fn to_qemu_full(&self) -> P {
+        unimplemented!();
+    }
+}
+
+impl<'a, P: Ptr, T: ToQemuPtr<'a, P>> ToQemuPtr<'a, P> for Option<T> {
+    type Storage = Option<<T as ToQemuPtr<'a, P>>::Storage>;
+
+    #[inline]
+    fn to_qemu_none(&'a self) -> Stash<'a, P, Option<T>> {
+        self.as_ref()
+            .map_or(Stash(Ptr::from::<()>(ptr::null_mut()), None), |s| {
+                let s = s.to_qemu_none();
+                Stash(s.0, Some(s.1))
+            })
+    }
+
+    #[inline]
+    fn to_qemu_full(&self) -> P {
+        self.as_ref()
+            .map_or(Ptr::from::<()>(ptr::null_mut()), ToQemuPtr::to_qemu_full)
+    }
+}
+
+impl<'a> ToQemuPtr<'a, *mut c_char> for String {
+    type Storage = CString;
+
+    #[inline]
+    fn to_qemu_none(&self) -> Stash<'a, *mut c_char, String> {
+        let tmp = CString::new(&self[..])
+            .expect("String::ToQemuPtr<*mut c_char>: unexpected '\0' character");
+        Stash(tmp.as_ptr() as *mut c_char, tmp)
+    }
+
+    #[inline]
+    fn to_qemu_full(&self) -> *mut c_char {
+        unsafe { sys::g_strndup(self.as_ptr() as *const c_char, self.len() as size_t) }
+    }
+}
+
+/// Translate from a pointer type, without taking ownership.
+pub trait FromQemuPtrNone<P: Ptr>: Sized {
+    /// # Safety
+    ///
+    /// `ptr` must be a valid pointer. It is not referenced after the call.
+    unsafe fn from_qemu_none(ptr: P) -> Self;
+}
+
+/// Translate from a pointer type, taking ownership.
+pub trait FromQemuPtrFull<P: Ptr>: Sized {
+    /// # Safety
+    ///
+    /// `ptr` must be a valid pointer. Ownership is transferred.
+    unsafe fn from_qemu_full(ptr: P) -> Self;
+}
+
+/// See [`FromQemuPtrNone`](trait.FromQemuPtrNone.html).
+#[inline]
+#[allow(clippy::missing_safety_doc)]
+pub unsafe fn from_qemu_none<P: Ptr, T: FromQemuPtrNone<P>>(ptr: P) -> T {
+    FromQemuPtrNone::from_qemu_none(ptr)
+}
+
+/// See [`FromQemuPtrFull`](trait.FromQemuPtrFull.html).
+#[inline]
+#[allow(clippy::missing_safety_doc)]
+pub unsafe fn from_qemu_full<P: Ptr, T: FromQemuPtrFull<P>>(ptr: P) -> T {
+    FromQemuPtrFull::from_qemu_full(ptr)
+}
+
+impl<P: Ptr, T: FromQemuPtrNone<P>> FromQemuPtrNone<P> for Option<T> {
+    #[inline]
+    unsafe fn from_qemu_none(ptr: P) -> Option<T> {
+        if ptr.is_null() {
+            None
+        } else {
+            Some(from_qemu_none(ptr))
+        }
+    }
+}
+
+impl<P: Ptr, T: FromQemuPtrFull<P>> FromQemuPtrFull<P> for Option<T> {
+    #[inline]
+    unsafe fn from_qemu_full(ptr: P) -> Option<T> {
+        if ptr.is_null() {
+            None
+        } else {
+            Some(from_qemu_full(ptr))
+        }
+    }
+}
+
+impl FromQemuPtrNone<*const c_char> for String {
+    #[inline]
+    unsafe fn from_qemu_none(ptr: *const c_char) -> Self {
+        assert!(!ptr.is_null());
+        String::from_utf8_lossy(CStr::from_ptr(ptr).to_bytes()).into_owned()
+    }
+}
+
+impl FromQemuPtrFull<*mut c_char> for String {
+    #[inline]
+    unsafe fn from_qemu_full(ptr: *mut c_char) -> Self {
+        let res = from_qemu_none(ptr as *const _);
+        sys::g_free(ptr as *mut _);
+        res
+    }
+}
+
+/// A macro to help the implementation of `Vec<T> -> P` translations.
+#[allow(unused_macros)]
+#[macro_export]
+macro_rules! vec_to_qemu {
+    ($rs:ident, $sys:ident) => {
+        #[allow(non_camel_case_types)]
+        pub struct $sys(*mut qapi_sys::$sys);
+
+        impl Drop for $sys {
+            fn drop(&mut self) {
+                let mut list = self.0;
+                unsafe {
+                    while !list.is_null() {
+                        let next = (*list).next;
+                        Box::from_raw(list);
+                        list = next;
+                    }
+                }
+            }
+        }
+
+        impl<'a> ToQemuPtr<'a, NewPtr<*mut qapi_sys::$sys>> for Vec<$rs> {
+            type Storage = (
+                Option<$sys>,
+                Vec<Stash<'a, <$rs as QemuPtrDefault>::QemuType, $rs>>,
+            );
+
+            #[inline]
+            fn to_qemu_none(&self) -> Stash<NewPtr<*mut qapi_sys::$sys>, Self> {
+                let stash_vec: Vec<_> = self.iter().rev().map(ToQemuPtr::to_qemu_none).collect();
+                let mut list: *mut qapi_sys::$sys = std::ptr::null_mut();
+                for stash in &stash_vec {
+                    let b = Box::new(qapi_sys::$sys {
+                        next: list,
+                        value: Ptr::to(stash.0),
+                    });
+                    list = Box::into_raw(b);
+                }
+                Stash(NewPtr(list), (Some($sys(list)), stash_vec))
+            }
+
+            #[inline]
+            fn to_qemu_full(&self) -> NewPtr<*mut qapi_sys::$sys> {
+                let v: Vec<_> = self.iter().rev().map(ToQemuPtr::to_qemu_full).collect();
+                let mut list: *mut qapi_sys::$sys = std::ptr::null_mut();
+                unsafe {
+                    for val in v {
+                        let l = sys::g_malloc0(std::mem::size_of::<qapi_sys::$sys>())
+                            as *mut qapi_sys::$sys;
+                        (*l).next = list;
+                        (*l).value = val;
+                        list = l;
+                    }
+                }
+                NewPtr(list)
+            }
+        }
+    };
+}
+
+/// A macro to help the implementation of `P -> Vec<T>` translations.
+#[allow(unused_macros)]
+#[macro_export]
+macro_rules! vec_from_qemu {
+    ($rs:ident, $sys:ident, $free_sys:ident) => {
+        impl FromQemuPtrFull<NewPtr<*mut qapi_sys::$sys>> for Vec<$rs> {
+            #[inline]
+            unsafe fn from_qemu_full(sys: NewPtr<*mut qapi_sys::$sys>) -> Self {
+                let ret = from_qemu_none(NewPtr(sys.0 as *const _));
+                qapi_sys::$free_sys(sys.0);
+                ret
+            }
+        }
+
+        impl FromQemuPtrNone<NewPtr<*const qapi_sys::$sys>> for Vec<$rs> {
+            #[inline]
+            unsafe fn from_qemu_none(sys: NewPtr<*const qapi_sys::$sys>) -> Self {
+                let mut ret = vec![];
+                let mut it = sys.0;
+                while !it.is_null() {
+                    let e = &*it;
+                    ret.push(from_qemu_none(e.value as *const _));
+                    it = e.next;
+                }
+                ret
+            }
+        }
+
+        impl From<NewPtr<*mut qapi_sys::$sys>> for *mut qapi_sys::$sys {
+            fn from(p: NewPtr<*mut qapi_sys::$sys>) -> Self {
+                p.0
+            }
+        }
+    };
+}
-- 
2.28.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PoCv2 07/15] scripts/qapi: add Rust sys bindings generation
  2020-10-11 20:34 [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now) marcandre.lureau
                   ` (5 preceding siblings ...)
  2020-10-11 20:35 ` [PoCv2 06/15] rust: provide a common crate for QEMU marcandre.lureau
@ 2020-10-11 20:35 ` marcandre.lureau
  2020-10-11 20:35 ` [PoCv2 08/15] qga/rust: generate QGA QAPI sys bindings marcandre.lureau
                   ` (8 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: marcandre.lureau @ 2020-10-11 20:35 UTC (permalink / raw)
  To: qemu-devel; +Cc: pbonzini, berrange, armbru, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Generate the C QAPI types in Rust, with a few common niceties to
Debug/Clone/Copy the Rust type.

An important question that remains unsolved to be usable with the QEMU
schema in this version, is the handling of the 'if' compilation
conditions. Since the 'if' value is a C pre-processor condition, it is
hard to evaluate from Rust (we could implement a minimalistic CPP
evaluator, or invoke CPP and somehow parse the output...).

The normal Rust way of handling conditional compilation is via #[cfg]
features, which rely on feature arguments being passed to rustc from
Cargo. This would require a long Rust feature list, and new 'if-cfg'
conditions in the schema (an idea would be for Cargo to read features
from meson in the future?)

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 meson.build            |   4 +-
 scripts/qapi-gen.py    |  16 ++-
 scripts/qapi/rs.py     | 126 ++++++++++++++++++++
 scripts/qapi/rs_sys.py | 254 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 394 insertions(+), 6 deletions(-)
 create mode 100644 scripts/qapi/rs.py
 create mode 100644 scripts/qapi/rs_sys.py

diff --git a/meson.build b/meson.build
index c30bb290c5..b6b8330b97 100644
--- a/meson.build
+++ b/meson.build
@@ -1147,7 +1147,9 @@ qapi_gen_depends = [ meson.source_root() / 'scripts/qapi/__init__.py',
                      meson.source_root() / 'scripts/qapi/types.py',
                      meson.source_root() / 'scripts/qapi/visit.py',
                      meson.source_root() / 'scripts/qapi/common.py',
-                     meson.source_root() / 'scripts/qapi-gen.py'
+                     meson.source_root() / 'scripts/qapi/rs.py',
+                     meson.source_root() / 'scripts/qapi/rs_sys.py',
+                     meson.source_root() / 'scripts/qapi-gen.py',
 ]
 
 tracetool = [
diff --git a/scripts/qapi-gen.py b/scripts/qapi-gen.py
index 541e8c1f55..5bfe9c8cd1 100644
--- a/scripts/qapi-gen.py
+++ b/scripts/qapi-gen.py
@@ -16,10 +16,13 @@ from qapi.schema import QAPIError, QAPISchema
 from qapi.types import gen_types
 from qapi.visit import gen_visit
 
+from qapi.rs_sys import gen_rs_systypes
 
 def main(argv):
     parser = argparse.ArgumentParser(
         description='Generate code from a QAPI schema')
+    parser.add_argument('-r', '--rust', action='store_true',
+                        help="generate Rust code")
     parser.add_argument('-b', '--builtins', action='store_true',
                         help="generate code for built-in types")
     parser.add_argument('-o', '--output-dir', action='store', default='',
@@ -45,11 +48,14 @@ def main(argv):
         print(err, file=sys.stderr)
         exit(1)
 
-    gen_types(schema, args.output_dir, args.prefix, args.builtins)
-    gen_visit(schema, args.output_dir, args.prefix, args.builtins)
-    gen_commands(schema, args.output_dir, args.prefix)
-    gen_events(schema, args.output_dir, args.prefix)
-    gen_introspect(schema, args.output_dir, args.prefix, args.unmask)
+    if args.rust:
+        gen_rs_systypes(schema, args.output_dir, args.prefix, args.builtins)
+    else:
+        gen_types(schema, args.output_dir, args.prefix, args.builtins)
+        gen_visit(schema, args.output_dir, args.prefix, args.builtins)
+        gen_commands(schema, args.output_dir, args.prefix)
+        gen_events(schema, args.output_dir, args.prefix)
+        gen_introspect(schema, args.output_dir, args.prefix, args.unmask)
 
 
 if __name__ == '__main__':
diff --git a/scripts/qapi/rs.py b/scripts/qapi/rs.py
new file mode 100644
index 0000000000..daa946580b
--- /dev/null
+++ b/scripts/qapi/rs.py
@@ -0,0 +1,126 @@
+# This work is licensed under the terms of the GNU GPL, version 2.
+# See the COPYING file in the top-level directory.
+"""
+QAPI Rust generator
+"""
+
+import os
+import subprocess
+
+from qapi.common import *
+from qapi.gen import QAPIGen, QAPISchemaVisitor
+
+
+rs_name_trans = str.maketrans('.-', '__')
+
+# Map @name to a valid Rust identifier.
+# If @protect, avoid returning certain ticklish identifiers (like
+# keywords) by prepending raw identifier prefix 'r#'.
+def rs_name(name, protect=True):
+    name = name.translate(rs_name_trans)
+    if name[0].isnumeric():
+        name = '_' + name
+    if not protect:
+        return name
+    # based from the list:
+    # https://doc.rust-lang.org/reference/keywords.html
+    if name in ('Self', 'abstract', 'as', 'async',
+                'await','become', 'box', 'break',
+                'const', 'continue', 'crate', 'do',
+                'dyn', 'else', 'enum', 'extern',
+                'false', 'final', 'fn', 'for',
+                'if', 'impl', 'in', 'let',
+                'loop', 'macro', 'match', 'mod',
+                'move', 'mut', 'override', 'priv',
+                'pub', 'ref', 'return', 'self',
+                'static', 'struct', 'super', 'trait',
+                'true', 'try', 'type', 'typeof',
+                'union', 'unsafe', 'unsized', 'use',
+                'virtual', 'where', 'while', 'yield',
+                ):
+        name = 'r#' + name
+    return name
+
+
+def rs_ctype_parse(c_type):
+    is_pointer = False
+    if c_type.endswith(pointer_suffix):
+        is_pointer = True
+        c_type = c_type.rstrip(pointer_suffix).strip()
+    is_list = c_type.endswith('List')
+    is_const = False
+    if c_type.startswith('const '):
+        is_const = True
+        c_type = c_type[6:]
+
+    return (is_pointer, is_const, is_list, c_type)
+
+
+def rs_systype(c_type, sys_ns='qapi_sys::', list_as_newp=False):
+    (is_pointer, is_const, is_list, c_type) = rs_ctype_parse(c_type)
+
+    to_rs = {
+        'char': 'libc::c_char',
+        'int8_t': 'i8',
+        'uint8_t': 'u8',
+        'int16_t': 'i16',
+        'uint16_t': 'u16',
+        'int32_t': 'i32',
+        'uint32_t': 'u32',
+        'int64_t': 'libc::c_longlong',
+        'uint64_t': 'libc::c_ulonglong',
+        'double': 'libc::c_double',
+        'bool': 'bool',
+    }
+
+    rs = ''
+    if is_const and is_pointer:
+        rs += '*const '
+    elif is_pointer:
+        rs += '*mut '
+    if c_type in to_rs:
+        rs += to_rs[c_type]
+    else:
+        rs += sys_ns + c_type
+
+    if is_list and list_as_newp:
+        rs = 'NewPtr<{}>'.format(rs)
+
+    return rs
+
+
+def to_camel_case(value):
+    if value[0] == '_':
+        return value
+    raw_id = False
+    if value.startswith('r#'):
+        raw_id = True
+        value = value[2:]
+    value = ''.join(word.title() for word in filter(None, re.split("[-_]+", value)))
+    if raw_id:
+        return 'r#' + value
+    else:
+        return value
+
+
+class QAPIGenRs(QAPIGen):
+
+    def __init__(self, fname):
+        super().__init__(fname)
+
+
+class QAPISchemaRsVisitor(QAPISchemaVisitor):
+
+    def __init__(self, prefix, what):
+        self._prefix = prefix
+        self._what = what
+        self._gen = QAPIGenRs(self._prefix + self._what + '.rs')
+
+    def write(self, output_dir):
+        self._gen.write(output_dir)
+
+        pathname = os.path.join(output_dir, self._gen.fname)
+        try:
+            subprocess.check_call(['rustfmt', pathname])
+        except FileNotFoundError:
+            pass
diff --git a/scripts/qapi/rs_sys.py b/scripts/qapi/rs_sys.py
new file mode 100644
index 0000000000..551a910c57
--- /dev/null
+++ b/scripts/qapi/rs_sys.py
@@ -0,0 +1,254 @@
+# This work is licensed under the terms of the GNU GPL, version 2.
+# See the COPYING file in the top-level directory.
+"""
+QAPI Rust sys/ffi generator
+"""
+
+from qapi.common import *
+from qapi.rs import *
+from qapi.schema import QAPISchemaEnumMember, QAPISchemaObjectType
+
+
+objects_seen = set()
+
+
+def gen_rs_sys_enum(name, ifcond, members, prefix=None):
+    if ifcond:
+        raise NotImplementedError("ifcond are not implemented")
+    # append automatically generated _max value
+    enum_members = members + [QAPISchemaEnumMember('_MAX', None)]
+
+    ret = mcgen('''
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[repr(C)]
+pub enum %(rs_name)s {
+''',
+    rs_name=rs_name(name))
+
+    for m in enum_members:
+        if m.ifcond:
+            raise NotImplementedError("ifcond are not implemented")
+        ret += mcgen('''
+    %(c_enum)s,
+''',
+                     c_enum=to_camel_case(rs_name(m.name, False)))
+    ret += mcgen('''
+}
+''')
+    return ret
+
+
+def gen_rs_sys_struct_members(members):
+    ret = ''
+    for memb in members:
+        if memb.ifcond:
+            raise NotImplementedError("ifcond are not implemented")
+        if memb.optional:
+            ret += mcgen('''
+    pub has_%(rs_name)s: bool,
+''',
+                         rs_name=rs_name(memb.name, protect=False))
+        ret += mcgen('''
+    pub %(rs_name)s: %(rs_systype)s,
+''',
+                     rs_systype=rs_systype(memb.type.c_type(), ''), rs_name=rs_name(memb.name))
+    return ret
+
+
+def gen_rs_sys_free(ty):
+    return mcgen('''
+
+extern "C" {
+    pub fn qapi_free_%(ty)s(obj: *mut %(ty)s);
+}
+''', ty=ty)
+
+
+def gen_rs_sys_variants(name, variants):
+    ret = mcgen('''
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union %(rs_name)s { /* union tag is @%(tag_name)s */
+''',
+                tag_name=rs_name(variants.tag_member.name),
+                rs_name=name)
+
+    for var in variants.variants:
+        if var.ifcond:
+            raise NotImplementedError("ifcond are not implemented")
+        if var.type.name == 'q_empty':
+            continue
+        ret += mcgen('''
+    pub %(rs_name)s: %(rs_systype)s,
+''',
+                     rs_systype=rs_systype(var.type.c_unboxed_type(), ''),
+                     rs_name=rs_name(var.name))
+
+    ret += mcgen('''
+}
+
+impl ::std::fmt::Debug for %(rs_name)s {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+        f.debug_struct(&format!("%(rs_name)s @ {:?}", self as *const _))
+            .finish()
+    }
+}
+''', rs_name=name)
+
+    return ret
+
+
+def gen_rs_sys_object(name, ifcond, base, members, variants):
+    if ifcond:
+        raise NotImplementedError("ifcond are not implemented")
+    if name in objects_seen:
+        return ''
+
+    ret = ''
+    objects_seen.add(name)
+    unionty = name + 'Union'
+    if variants:
+        for v in variants.variants:
+            if v.ifcond:
+                raise NotImplementedError("ifcond are not implemented")
+            if isinstance(v.type, QAPISchemaObjectType):
+                ret += gen_rs_sys_object(v.type.name, v.type.ifcond, v.type.base,
+                                         v.type.local_members, v.type.variants)
+        ret += gen_rs_sys_variants(unionty, variants)
+
+    ret += gen_rs_sys_free(rs_name(name))
+    ret += mcgen('''
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug)]
+pub struct %(rs_name)s {
+''',
+                 rs_name=rs_name(name))
+
+    if base:
+        if not base.is_implicit():
+            ret += mcgen('''
+    // Members inherited:
+''')
+        ret += gen_rs_sys_struct_members(base.members)
+        if not base.is_implicit():
+            ret += mcgen('''
+    // Own members:
+''')
+
+    ret += gen_rs_sys_struct_members(members)
+    if variants:
+        ret += mcgen('''
+        pub u: %(unionty)s
+''', unionty=unionty)
+    ret += mcgen('''
+}
+''')
+    return ret
+
+
+def gen_rs_sys_variant(name, ifcond, variants):
+    if ifcond:
+        raise NotImplementedError("ifcond are not implemented")
+    if name in objects_seen:
+        return ''
+
+    objects_seen.add(name)
+
+    vs = ''
+    for var in variants.variants:
+        if var.type.name == 'q_empty':
+            continue
+        vs += mcgen('''
+    pub %(mem_name)s: %(rs_systype)s,
+''',
+                     rs_systype=rs_systype(var.type.c_unboxed_type(), ''),
+                     mem_name=rs_name(var.name))
+
+    return mcgen('''
+
+#[repr(C)]
+#[derive(Copy,Clone)]
+pub union %(rs_name)sUnion {
+    %(variants)s
+}
+
+impl ::std::fmt::Debug for %(rs_name)sUnion {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+        f.debug_struct(&format!("%(rs_name)sUnion @ {:?}", self as *const _))
+            .finish()
+    }
+}
+
+#[repr(C)]
+#[derive(Copy,Clone,Debug)]
+pub struct %(rs_name)s {
+    pub ty: QType,
+    pub u: %(rs_name)sUnion,
+}
+''',
+                 rs_name=rs_name(name), variants=vs)
+
+
+def gen_rs_sys_array(name, ifcond, element_type):
+    if ifcond:
+        raise NotImplementedError("ifcond are not implemented")
+    ret = mcgen('''
+
+#[repr(C)]
+#[derive(Copy,Clone)]
+pub struct %(rs_name)s {
+    pub next: *mut %(rs_name)s,
+    pub value: %(rs_systype)s,
+}
+
+impl ::std::fmt::Debug for %(rs_name)s {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+        f.debug_struct(&format!("%(rs_name)s @ {:?}", self as *const _))
+            .finish()
+    }
+}
+''',
+                 rs_name=rs_name(name), rs_systype=rs_systype(element_type.c_type(), ''))
+    ret += gen_rs_sys_free(rs_name(name))
+    return ret
+
+
+class QAPISchemaGenRsSysTypeVisitor(QAPISchemaRsVisitor):
+
+    def __init__(self, prefix):
+        super().__init__(prefix, 'qapi-sys-types')
+
+    def visit_begin(self, schema):
+        # gen_object() is recursive, ensure it doesn't visit the empty type
+        objects_seen.add(schema.the_empty_object_type.name)
+        self._gen.preamble_add(
+            mcgen('''
+// generated by qapi-gen, DO NOT EDIT
+
+use common::sys::{QNull, QObject};
+
+'''))
+
+    def visit_enum_type(self, name, info, ifcond, features, members, prefix):
+        self._gen.add(gen_rs_sys_enum(name, ifcond, members, prefix))
+
+    def visit_array_type(self, name, info, ifcond, element_type):
+        self._gen.add(gen_rs_sys_array(name, ifcond, element_type))
+
+    def visit_object_type(self, name, info, ifcond, features,
+                          base, members, variants):
+        if name.startswith('q_'):
+            return
+        self._gen.add(gen_rs_sys_object(name, ifcond, base, members, variants))
+
+    def visit_alternate_type(self, name, info, ifcond, features, variants):
+        self._gen.add(gen_rs_sys_variant(name, ifcond, variants))
+
+
+def gen_rs_systypes(schema, output_dir, prefix, opt_builtins):
+    vis = QAPISchemaGenRsSysTypeVisitor(prefix)
+    schema.visit(vis)
+    vis.write(output_dir)
-- 
2.28.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PoCv2 08/15] qga/rust: generate QGA QAPI sys bindings
  2020-10-11 20:34 [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now) marcandre.lureau
                   ` (6 preceding siblings ...)
  2020-10-11 20:35 ` [PoCv2 07/15] scripts/qapi: add Rust sys bindings generation marcandre.lureau
@ 2020-10-11 20:35 ` marcandre.lureau
  2020-10-11 20:35 ` [PoCv2 09/15] scripts/qapi: add generation of Rust bindings for types marcandre.lureau
                   ` (7 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: marcandre.lureau @ 2020-10-11 20:35 UTC (permalink / raw)
  To: qemu-devel; +Cc: pbonzini, berrange, armbru, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Use qapi-gen to generate low-level C sys bindings for QAPI types,
include them to the build.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qga/Cargo.toml  |  4 ++++
 qga/lib.rs      |  1 +
 qga/meson.build | 11 +++++++++++
 qga/qapi_sys.rs |  5 +++++
 4 files changed, 21 insertions(+)
 create mode 100644 qga/qapi_sys.rs

diff --git a/qga/Cargo.toml b/qga/Cargo.toml
index 50c3415ab2..9966057594 100644
--- a/qga/Cargo.toml
+++ b/qga/Cargo.toml
@@ -4,6 +4,10 @@ version = "0.1.0"
 edition = "2018"
 license = "GPLv2"
 
+[dependencies]
+common = { path = "../rust/common" }
+libc = "^0.2.76"
+
 [lib]
 path = "lib.rs"
 crate-type = ["staticlib"]
diff --git a/qga/lib.rs b/qga/lib.rs
index e69de29bb2..050a47e2a3 100644
--- a/qga/lib.rs
+++ b/qga/lib.rs
@@ -0,0 +1 @@
+mod qapi_sys;
diff --git a/qga/meson.build b/qga/meson.build
index 62e13a11b3..dbc8f1623b 100644
--- a/qga/meson.build
+++ b/qga/meson.build
@@ -47,10 +47,21 @@ qga_ss = qga_ss.apply(config_host, strict: false)
 
 qga_rs = declare_dependency()
 if with_rust
+  qga_qapi_rs_outputs = [
+    'qga-qapi-sys-types.rs',
+  ]
+
+  qapi_gen_rs_files = custom_target('QGA QAPI Rust bindings',
+                                    output: qga_qapi_rs_outputs,
+                                    input: 'qapi-schema.json',
+                                    command: [ qapi_gen, '-r', '-o', 'qga', '-p', 'qga-', '@INPUT0@' ],
+                                    depend_files: qapi_gen_depends)
+
   cargo_qga = custom_target('cargo-qga',
                             build_by_default: true,
                             output: ['libqga.args', 'libqga.a'],
                             build_always_stale: true,
+                            depends: [qapi_gen_rs_files],
                             command: [cargo_wrapper,
                                       'build-lib',
                                       meson.current_build_dir(),
diff --git a/qga/qapi_sys.rs b/qga/qapi_sys.rs
new file mode 100644
index 0000000000..06fc49b826
--- /dev/null
+++ b/qga/qapi_sys.rs
@@ -0,0 +1,5 @@
+#![allow(dead_code)]
+include!(concat!(
+    env!("MESON_BUILD_ROOT"),
+    "/qga/qga-qapi-sys-types.rs"
+));
-- 
2.28.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PoCv2 09/15] scripts/qapi: add generation of Rust bindings for types
  2020-10-11 20:34 [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now) marcandre.lureau
                   ` (7 preceding siblings ...)
  2020-10-11 20:35 ` [PoCv2 08/15] qga/rust: generate QGA QAPI sys bindings marcandre.lureau
@ 2020-10-11 20:35 ` marcandre.lureau
  2020-10-11 20:35 ` [PoCv2 10/15] qga/rust: build Rust types marcandre.lureau
                   ` (6 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: marcandre.lureau @ 2020-10-11 20:35 UTC (permalink / raw)
  To: qemu-devel; +Cc: pbonzini, berrange, armbru, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Generate high-level idiomatic Rust code for the QAPI types, with to/from
translations for C FFI.

- no conditional support yet

- C FFI types are mapped to higher-level types (char*->String,
  Foo*->Foo, has_foo/foo->Option<foo> etc)

- C enums are simply aliased

- C structures have corresponding Rust structures

- alternate are represented as Rust enum (TODO: to/from conversion)

- variants A are represented as Rust AEnum enums. A AEnum::kind() method
  allows to easily get the variant C kind enum value. The translation code
  also uses a helper enum ACEnum to hold the C pointer variant. There
  might be better ways to achieve the translation, but that one works
  for now.

- unions are represented in a similar way as C, as a struct S with a "u"
  member (since S may have extra 'base' fields). However, the discriminant
  isn't a member of S, as Rust enum variant don't need that.

- lists are represented as Vec and translated thanks to vec_to/from
  translate macros

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 meson.build              |   1 +
 scripts/qapi-gen.py      |   2 +
 scripts/qapi/rs.py       |  78 +++++++
 scripts/qapi/rs_types.py | 447 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 528 insertions(+)
 create mode 100644 scripts/qapi/rs_types.py

diff --git a/meson.build b/meson.build
index b6b8330b97..666e38033e 100644
--- a/meson.build
+++ b/meson.build
@@ -1149,6 +1149,7 @@ qapi_gen_depends = [ meson.source_root() / 'scripts/qapi/__init__.py',
                      meson.source_root() / 'scripts/qapi/common.py',
                      meson.source_root() / 'scripts/qapi/rs.py',
                      meson.source_root() / 'scripts/qapi/rs_sys.py',
+                     meson.source_root() / 'scripts/qapi/rs_types.py',
                      meson.source_root() / 'scripts/qapi-gen.py',
 ]
 
diff --git a/scripts/qapi-gen.py b/scripts/qapi-gen.py
index 5bfe9c8cd1..556bfe9e7b 100644
--- a/scripts/qapi-gen.py
+++ b/scripts/qapi-gen.py
@@ -17,6 +17,7 @@ from qapi.types import gen_types
 from qapi.visit import gen_visit
 
 from qapi.rs_sys import gen_rs_systypes
+from qapi.rs_types import gen_rs_types
 
 def main(argv):
     parser = argparse.ArgumentParser(
@@ -50,6 +51,7 @@ def main(argv):
 
     if args.rust:
         gen_rs_systypes(schema, args.output_dir, args.prefix, args.builtins)
+        gen_rs_types(schema, args.output_dir, args.prefix, args.builtins)
     else:
         gen_types(schema, args.output_dir, args.prefix, args.builtins)
         gen_visit(schema, args.output_dir, args.prefix, args.builtins)
diff --git a/scripts/qapi/rs.py b/scripts/qapi/rs.py
index daa946580b..cab5bb9b72 100644
--- a/scripts/qapi/rs.py
+++ b/scripts/qapi/rs.py
@@ -11,6 +11,8 @@ from qapi.common import *
 from qapi.gen import QAPIGen, QAPISchemaVisitor
 
 
+rs_lists = set()
+
 rs_name_trans = str.maketrans('.-', '__')
 
 # Map @name to a valid Rust identifier.
@@ -42,12 +44,55 @@ def rs_name(name, protect=True):
     return name
 
 
+def rs_type(c_type, ns='qapi::', optional=False):
+    vec = False
+    to_rs = {
+        'char': 'i8',
+        'int8_t': 'i8',
+        'uint8_t': 'u8',
+        'int16_t': 'i16',
+        'uint16_t': 'u16',
+        'int32_t': 'i32',
+        'uint32_t': 'u32',
+        'int64_t': 'i64',
+        'uint64_t': 'u64',
+        'double': 'f64',
+        'bool': 'bool',
+        'str': 'String',
+    }
+    if c_type.startswith('const '):
+        c_type = c_type[6:]
+    if c_type.endswith(pointer_suffix):
+        c_type = c_type.rstrip(pointer_suffix).strip()
+        if c_type.endswith('List'):
+            c_type = c_type[:-4]
+            vec = True
+        else:
+            to_rs = {
+                'char': 'String',
+            }
+
+    if c_type in to_rs:
+        ret = to_rs[c_type]
+    else:
+        ret = ns + c_type
+
+    if vec:
+        ret = 'Vec<%s>' % ret
+    if optional:
+        return 'Option<%s>' % ret
+    else:
+        return ret
+
+
 def rs_ctype_parse(c_type):
     is_pointer = False
     if c_type.endswith(pointer_suffix):
         is_pointer = True
         c_type = c_type.rstrip(pointer_suffix).strip()
     is_list = c_type.endswith('List')
+    if is_list:
+        rs_lists.add(c_type)
     is_const = False
     if c_type.startswith('const '):
         is_const = True
@@ -103,6 +148,39 @@ def to_camel_case(value):
         return value
 
 
+def to_qemu_none(c_type, name):
+    (is_pointer, _, is_list, _) = rs_ctype_parse(c_type)
+
+    if is_pointer:
+        if c_type == 'char':
+            return mcgen('''
+    let %(name)s_ = CString::new(%(name)s).unwrap();
+    let %(name)s = %(name)s_.as_ptr();
+''', name=name)
+        elif is_list:
+            return mcgen('''
+    let %(name)s_ = NewPtr(%(name)s).to_qemu_none();
+    let %(name)s = %(name)s_.0.0;
+''', name=name)
+        else:
+            return mcgen('''
+    let %(name)s_ = %(name)s.to_qemu_none();
+    let %(name)s = %(name)s_.0;
+''', name=name)
+    return ''
+
+
+def from_qemu(var_name, c_type, full=False):
+    (is_pointer, _, is_list, _) = rs_ctype_parse(c_type)
+    ptr = '{} as *{} _'.format(var_name, 'mut' if full else 'const')
+    if is_list:
+        ptr = 'NewPtr({})'.format(ptr)
+    if is_pointer:
+        return 'from_qemu_{}({})'.format('full' if full else 'none', ptr)
+    else:
+        return var_name
+
+
 class QAPIGenRs(QAPIGen):
 
     def __init__(self, fname):
diff --git a/scripts/qapi/rs_types.py b/scripts/qapi/rs_types.py
new file mode 100644
index 0000000000..3a7d8914c8
--- /dev/null
+++ b/scripts/qapi/rs_types.py
@@ -0,0 +1,447 @@
+# This work is licensed under the terms of the GNU GPL, version 2.
+# See the COPYING file in the top-level directory.
+"""
+QAPI Rust types generator
+"""
+
+from qapi.common import *
+from qapi.rs import *
+
+
+objects_seen = set()
+
+
+def gen_rs_variants_enum_kind(name, kind_arms):
+    return mcgen('''
+
+impl %(rs_name)sEnum {
+    pub fn kind(&self) -> %(rs_name)sKind {
+        match self {
+            %(kind_arms)s
+        }
+    }
+}
+''', rs_name=rs_name(name), kind_arms=kind_arms)
+
+
+def gen_rs_variants_to_qemu(name, variants):
+    ret = mcgen('''
+
+pub enum %(rs_name)sCEnum<'a> {
+''', rs_name=rs_name(name))
+
+    none_arms = ''
+    full_arms = ''
+    for var in variants.variants:
+        var_name = to_camel_case(rs_name(var.name, False))
+        type_name = var.type.name
+        if type_name == 'q_empty':
+            continue
+        if type_name.endswith('-wrapper'):
+            type_name = type_name[6:-8]
+        ret += mcgen('''
+    %(var_name)s(<%(rs_type)s as ToQemuPtr<'a, *mut %(rs_systype)s>>::Storage),
+''', var_name=var_name, rs_type=rs_type(type_name, ''), rs_systype=rs_systype(type_name))
+        none_arms += mcgen('''
+             %(rs_name)sEnum::%(var_name)s(v) => {
+                 let stash_ = v.to_qemu_none();
+                 (stash_.0 as *mut std::ffi::c_void, %(rs_name)sCEnum::%(var_name)s(stash_.1))
+             },
+''', rs_name=rs_name(name), var_name=var_name)
+        full_arms += mcgen('''
+             %(rs_name)sEnum::%(var_name)s(v) => {
+                 let ptr = v.to_qemu_full();
+                 ptr as *mut std::ffi::c_void
+             },
+''', rs_name=rs_name(name), var_name=var_name)
+
+    ret += mcgen('''
+}
+
+impl QemuPtrDefault for %(rs_name)sEnum {
+    type QemuType = *mut std::ffi::c_void;
+}
+
+impl<'a> ToQemuPtr<'a, *mut std::ffi::c_void> for %(rs_name)sEnum {
+    type Storage = %(rs_name)sCEnum<'a>;
+
+    #[inline]
+    fn to_qemu_none(&'a self) -> Stash<'a, *mut std::ffi::c_void, %(rs_name)sEnum> {
+        let (ptr_, cenum_) = match self {
+             %(none_arms)s
+        };
+
+        Stash(ptr_, cenum_)
+    }
+
+    #[inline]
+    fn to_qemu_full(&self) -> *mut std::ffi::c_void {
+        match self {
+            %(full_arms)s
+        }
+    }
+}
+''', rs_name=rs_name(name), none_arms=none_arms, full_arms=full_arms)
+    return ret
+
+
+def gen_rs_variants(name, variants):
+    ret = mcgen('''
+
+#[derive(Clone,Debug)]
+pub enum %(rs_name)sEnum {
+''', rs_name=rs_name(name))
+
+    kind_arms = ''
+    for var in variants.variants:
+        if var.ifcond:
+            raise NotImplementedError("ifcond are not implemented")
+        type_name = var.type.name
+        var_name = to_camel_case(rs_name(var.name, False))
+        if type_name == 'q_empty':
+            continue
+        if type_name.endswith('-wrapper'):
+            type_name = type_name[6:-8] # remove q_obj*-wrapper
+        kind_arms += mcgen('''
+        Self::%(var_name)s(_) => { %(rs_name)sKind::%(var_name)s },
+''', var_name=var_name, rs_name=rs_name(name))
+        ret += mcgen('''
+    %(var_name)s(%(rs_type)s),
+''', var_name=var_name, rs_type=rs_type(type_name, ''))
+
+    ret += mcgen('''
+}
+''')
+
+    ret += gen_rs_variants_enum_kind(name, kind_arms)
+    ret += gen_rs_variants_to_qemu(name, variants)
+    # TODO FromQemu
+    return ret
+
+
+def gen_rs_object_to_qemu(name, base, members, variants):
+    storage = []
+    stash = []
+    sys_memb = []
+    memb_none = ''
+    memb_full = ''
+    for memb in members:
+        memb_name = rs_name(memb.name)
+        c_type = memb.type.c_type()
+        (is_pointer, _, is_list, _) = rs_ctype_parse(c_type)
+        if is_pointer:
+            t = rs_type(memb.type.c_type(), optional=memb.optional, ns='')
+            p = rs_systype(memb.type.c_type(), list_as_newp=True)
+            s = "Stash<'a, %s, %s>" % (p, t)
+            storage.append(s)
+        if memb.optional:
+            has_memb_name = 'has_%s' % rs_name(memb.name, protect=False)
+            sys_memb.append(has_memb_name)
+            has_memb = mcgen('''
+    let %(has_memb_name)s = self.%(memb_name)s.is_some();
+''', memb_name=memb_name, has_memb_name=has_memb_name)
+            memb_none += has_memb
+            memb_full += has_memb
+
+        if is_pointer:
+            stash_name = '{}_stash_'.format(memb_name)
+            stash.append(stash_name)
+            var = 'NewPtr({})'.format(memb_name) if is_list else memb_name
+            memb_none += mcgen('''
+    let %(stash_name)s = self.%(memb_name)s.to_qemu_none();
+    let %(var)s = %(stash_name)s.0;
+''', stash_name=stash_name, memb_name=memb_name, var=var)
+            memb_full += mcgen('''
+    let %(var)s = self.%(memb_name)s.to_qemu_full();
+''', memb_name=memb_name, var=var)
+        else:
+            unwrap = ''
+            if memb.optional:
+                unwrap = '.unwrap_or_default()'
+            memb = mcgen('''
+    let %(memb_name)s = self.%(memb_name)s%(unwrap)s;
+''', memb_name=memb_name, unwrap=unwrap)
+            memb_none += memb
+            memb_full += memb
+
+        sys_memb.append(memb_name)
+
+    if variants:
+        tag_name = rs_name(variants.tag_member.name)
+        sys_memb.append(tag_name)
+        sys_memb.append('u')
+        p = '*mut std::ffi::c_void'
+        s = "Stash<'a, %s, %sEnum>" % (p, name)
+        storage.append(s)
+        tag = mcgen('''
+    let %(tag_name)s = self.u.kind();
+''', sys=rs_systype(name), tag_name=tag_name)
+        memb_none += tag
+        memb_full += tag
+        arms = ''
+        for var in variants.variants:
+            if var.type.name == 'q_empty':
+                continue
+            arms += mcgen('%(rs_name)sEnum::%(kind_name)s(_) => qapi_sys::%(rs_name)sUnion { %(var_name)s: %(var_type)s { data: u_ptr_ as *mut _ } },',
+                          rs_name=rs_name(name), kind_name=to_camel_case(var.name), var_name=rs_name(var.name), var_type=rs_systype(var.type.c_name()))
+        memb_none += mcgen('''
+    let u_stash_ = self.u.to_qemu_none();
+    let u_ptr_ = u_stash_.0;
+    let u = match self.u {
+        %(arms)s
+    };
+''', arms=arms)
+        stash.append('u_stash_')
+        memb_full += mcgen('''
+    let u_ptr_ = self.u.to_qemu_full();
+    let u = match self.u {
+        %(arms)s
+    };
+''', arms=arms)
+
+    return mcgen('''
+
+impl QemuPtrDefault for %(rs_name)s {
+    type QemuType = *mut qapi_sys::%(rs_name)s;
+}
+
+impl<'a> ToQemuPtr<'a, *mut qapi_sys::%(rs_name)s> for %(rs_name)s {
+    type Storage = (Box<qapi_sys::%(rs_name)s>, %(storage)s);
+
+    #[inline]
+    fn to_qemu_none(&'a self) -> Stash<'a, *mut qapi_sys::%(rs_name)s, %(rs_name)s> {
+        %(memb_none)s
+        let mut box_ = Box::new(qapi_sys::%(rs_name)s { %(sys_memb)s });
+
+        Stash(&mut *box_, (box_, %(stash)s))
+    }
+
+    #[inline]
+    fn to_qemu_full(&self) -> *mut qapi_sys::%(rs_name)s {
+        unsafe {
+            %(memb_full)s
+            let ptr = sys::g_malloc0(std::mem::size_of::<*const %(rs_name)s>()) as *mut _;
+            *ptr = qapi_sys::%(rs_name)s { %(sys_memb)s };
+            ptr
+        }
+    }
+}
+''',
+                 rs_name=rs_name(name), storage=', '.join(storage),
+                 sys_memb=', '.join(sys_memb), memb_none=memb_none, memb_full=memb_full, stash=', '.join(stash))
+
+
+def gen_rs_object_from_qemu(name, base, members, variants):
+    memb_names = []
+    if base:
+        memb_names.extend([rs_name(memb.name) for memb in base.members])
+    memb_names.extend([rs_name(memb.name) for memb in members])
+
+    ret = mcgen('''
+}
+
+impl FromQemuPtrFull<*mut qapi_sys::%(rs_name)s> for %(rs_name)s {
+    unsafe fn from_qemu_full(sys: *mut qapi_sys::%(rs_name)s) -> Self {
+        let ret = from_qemu_none(sys as *const _);
+        qapi_sys::qapi_free_%(rs_name)s(sys);
+        ret
+    }
+}
+
+impl FromQemuPtrNone<*const qapi_sys::%(rs_name)s> for %(rs_name)s {
+    unsafe fn from_qemu_none(sys: *const qapi_sys::%(rs_name)s) -> Self {
+        let sys = & *sys;
+''', rs_name=rs_name(name))
+
+    for memb in members:
+        memb_name = rs_name(memb.name)
+        val = from_qemu('sys.' + memb_name, memb.type.c_type())
+        if memb.optional:
+            val = mcgen('''{
+            if sys.has_%(memb_name)s {
+                Some(%(val)s)
+            } else {
+                None
+            }
+}''', memb_name=rs_name(memb.name, protect=False), val=val)
+
+        ret += mcgen('''
+        let %(memb_name)s = %(val)s;
+''', memb_name=memb_name, val=val)
+
+    if variants:
+        arms = ''
+        for variant in variants.tag_member.type.member_names():
+            arms += mcgen('''
+            %(enum)s::%(variant)s => { %(rs_name)sEnum::%(variant)s(from_qemu_none(sys.u.%(memb)s.data as *const _)) },
+''', enum=variants.tag_member.type.name, memb=rs_name(variant),
+                          variant=to_camel_case(variant), rs_name=rs_name(name))
+        ret += mcgen('''
+        let u = match sys.%(tag)s {
+            %(arms)s
+            _ => panic!("Variant with invalid tag"),
+        };
+''', tag=rs_name(variants.tag_member.name), arms=arms)
+        memb_names.append('u')
+
+    ret += mcgen('''
+            Self { %(memb_names)s }
+        }
+}
+''', rs_name=rs_name(name), memb_names=', '.join(memb_names))
+    return ret
+
+
+def gen_struct_members(members):
+    ret = ''
+    for memb in members:
+        if memb.ifcond:
+            raise NotImplementedError("ifcond are not implemented")
+        rsname = rs_name(memb.name)
+        ret += mcgen('''
+    pub %(rs_name)s: %(rs_type)s,
+''',
+                     rs_type=rs_type(memb.type.c_type(), '', optional=memb.optional), rs_name=rsname)
+    return ret
+
+
+def gen_rs_object(name, base, members, variants):
+    if name in objects_seen:
+        return ''
+
+    if variants:
+        members = [m for m in members if m.name != variants.tag_member.name]
+
+    ret = ''
+    objects_seen.add(name)
+
+    if variants:
+        ret += gen_rs_variants(name, variants)
+
+    ret += mcgen('''
+
+#[derive(Clone, Debug)]
+pub struct %(rs_name)s {
+''',
+                 rs_name=rs_name(name))
+
+    if base:
+        if not base.is_implicit():
+            ret += mcgen('''
+    // Members inherited:
+''',
+                         c_name=base.c_name())
+        ret += gen_struct_members(base.members)
+        if not base.is_implicit():
+            ret += mcgen('''
+    // Own members:
+''')
+
+    ret += gen_struct_members(members)
+
+    if variants:
+        ret += mcgen('''
+    pub u: %(rs_type)sEnum,
+''', rs_type=name)
+
+    ret += gen_rs_object_from_qemu(name, base, members, variants)
+    ret += gen_rs_object_to_qemu(name, base, members, variants)
+    return ret
+
+
+def gen_rs_alternate(name, variants):
+    if name in objects_seen:
+        return ''
+
+    ret = ''
+    objects_seen.add(name)
+
+    ret += mcgen('''
+
+#[derive(Clone,Debug)]
+pub enum %(rs_name)s {
+''',
+                 rs_name=rs_name(name))
+
+    for var in variants.variants:
+        if var.ifcond:
+            raise NotImplementedError("ifcond are not implemented")
+        if var.type.name == 'q_empty':
+            continue
+        ret += mcgen('''
+        %(mem_name)s(%(rs_type)s),
+''',
+                     rs_type=rs_type(var.type.c_unboxed_type(), ''),
+                     mem_name=to_camel_case(rs_name(var.name)))
+    ret += mcgen('''
+}
+''')
+    # TODO: add to/from conversion
+    return ret
+
+
+def gen_rs_enum(name):
+        return mcgen('''
+
+pub type %(rs_name)s = qapi_sys::%(rs_name)s;
+''', rs_name=rs_name(name))
+
+
+class QAPISchemaGenRsTypeVisitor(QAPISchemaRsVisitor):
+
+    def __init__(self, prefix):
+        super().__init__(prefix, 'qapi-types')
+
+    def visit_begin(self, schema):
+        # gen_object() is recursive, ensure it doesn't visit the empty type
+        objects_seen.add(schema.the_empty_object_type.name)
+        self._gen.preamble_add(
+            mcgen('''
+// generated by qapi-gen, DO NOT EDIT
+
+use crate::qapi_sys;
+
+'''))
+
+    def visit_end(self):
+        for name in rs_lists:
+            self._gen.add(mcgen('''
+
+vec_from_qemu!(%(rs)s, %(sys)s, qapi_free_%(sys)s);
+vec_to_qemu!(%(rs)s, %(sys)s);
+''', sys=name, rs=name[:-4]))
+
+    def visit_command(self, name, info, ifcond, features,
+                      arg_type, ret_type, gen, success_response, boxed,
+                      allow_oob, allow_preconfig, coroutine):
+        if ifcond:
+            raise NotImplementedError("ifcond are not implemented")
+        if not gen:
+            return
+        # call from_qemu() to eventually register a list
+        if ret_type:
+            from_qemu('', ret_type.c_type())
+
+    def visit_object_type(self, name, info, ifcond, features,
+                          base, members, variants):
+        if ifcond:
+            raise NotImplementedError("ifcond are not implemented")
+        if name.startswith('q_'):
+            return
+        self._gen.add(gen_rs_object(name, base, members, variants))
+
+    def visit_enum_type(self, name, info, ifcond, features, members, prefix):
+        if ifcond:
+            raise NotImplementedError("ifcond are not implemented")
+        self._gen.add(gen_rs_enum(name))
+
+    def visit_alternate_type(self, name, info, ifcond, features, variants):
+        if ifcond:
+            raise NotImplementedError("ifcond are not implemented")
+        self._gen.add(gen_rs_alternate(name, variants))
+
+
+def gen_rs_types(schema, output_dir, prefix, opt_builtins):
+    vis = QAPISchemaGenRsTypeVisitor(prefix)
+    schema.visit(vis)
+    vis.write(output_dir)
-- 
2.28.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PoCv2 10/15] qga/rust: build Rust types
  2020-10-11 20:34 [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now) marcandre.lureau
                   ` (8 preceding siblings ...)
  2020-10-11 20:35 ` [PoCv2 09/15] scripts/qapi: add generation of Rust bindings for types marcandre.lureau
@ 2020-10-11 20:35 ` marcandre.lureau
  2020-10-11 20:35 ` [PoCv2 11/15] qga: add qmp! macro helper marcandre.lureau
                   ` (5 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: marcandre.lureau @ 2020-10-11 20:35 UTC (permalink / raw)
  To: qemu-devel; +Cc: pbonzini, berrange, armbru, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qga/lib.rs      | 1 +
 qga/meson.build | 1 +
 qga/qapi.rs     | 6 ++++++
 3 files changed, 8 insertions(+)
 create mode 100644 qga/qapi.rs

diff --git a/qga/lib.rs b/qga/lib.rs
index 050a47e2a3..bff4107569 100644
--- a/qga/lib.rs
+++ b/qga/lib.rs
@@ -1 +1,2 @@
+mod qapi;
 mod qapi_sys;
diff --git a/qga/meson.build b/qga/meson.build
index dbc8f1623b..aedbd07a04 100644
--- a/qga/meson.build
+++ b/qga/meson.build
@@ -49,6 +49,7 @@ qga_rs = declare_dependency()
 if with_rust
   qga_qapi_rs_outputs = [
     'qga-qapi-sys-types.rs',
+    'qga-qapi-types.rs',
   ]
 
   qapi_gen_rs_files = custom_target('QGA QAPI Rust bindings',
diff --git a/qga/qapi.rs b/qga/qapi.rs
new file mode 100644
index 0000000000..e4b9113300
--- /dev/null
+++ b/qga/qapi.rs
@@ -0,0 +1,6 @@
+#![allow(dead_code)]
+use common::*;
+
+new_ptr!();
+
+include!(concat!(env!("MESON_BUILD_ROOT"), "/qga/qga-qapi-types.rs"));
-- 
2.28.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PoCv2 11/15] qga: add qmp! macro helper
  2020-10-11 20:34 [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now) marcandre.lureau
                   ` (9 preceding siblings ...)
  2020-10-11 20:35 ` [PoCv2 10/15] qga/rust: build Rust types marcandre.lureau
@ 2020-10-11 20:35 ` marcandre.lureau
  2020-10-11 20:35 ` [PoCv2 12/15] qga: implement get-host-name in Rust marcandre.lureau
                   ` (4 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: marcandre.lureau @ 2020-10-11 20:35 UTC (permalink / raw)
  To: qemu-devel; +Cc: pbonzini, berrange, armbru, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Add a macro to help wrapping higher-level qmp handlers, by taking care
of errors and return value pointer translation.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qga/lib.rs     |  1 +
 qga/qmp/mod.rs | 36 ++++++++++++++++++++++++++++++++++++
 2 files changed, 37 insertions(+)
 create mode 100644 qga/qmp/mod.rs

diff --git a/qga/lib.rs b/qga/lib.rs
index bff4107569..5fe08c25a3 100644
--- a/qga/lib.rs
+++ b/qga/lib.rs
@@ -1,2 +1,3 @@
 mod qapi;
 mod qapi_sys;
+mod qmp;
diff --git a/qga/qmp/mod.rs b/qga/qmp/mod.rs
new file mode 100644
index 0000000000..38060100af
--- /dev/null
+++ b/qga/qmp/mod.rs
@@ -0,0 +1,36 @@
+use common::*;
+
+use crate::*;
+
+macro_rules! qmp {
+    // the basic return value variant
+    ($e:expr, $errp:ident, $errval:expr) => {{
+        assert!(!$errp.is_null());
+        unsafe {
+            *$errp = std::ptr::null_mut();
+        }
+
+        match $e {
+            Ok(val) => val,
+            Err(err) => unsafe {
+                *$errp = err.to_qemu_full();
+                $errval
+            },
+        }
+    }};
+    // the ptr return value variant
+    ($e:expr, $errp:ident) => {{
+        assert!(!$errp.is_null());
+        unsafe {
+            *$errp = std::ptr::null_mut();
+        }
+
+        match $e {
+            Ok(val) => val.to_qemu_full().into(),
+            Err(err) => unsafe {
+                *$errp = err.to_qemu_full();
+                std::ptr::null_mut()
+            },
+        }
+    }};
+}
-- 
2.28.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PoCv2 12/15] qga: implement get-host-name in Rust
  2020-10-11 20:34 [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now) marcandre.lureau
                   ` (10 preceding siblings ...)
  2020-10-11 20:35 ` [PoCv2 11/15] qga: add qmp! macro helper marcandre.lureau
@ 2020-10-11 20:35 ` marcandre.lureau
  2020-10-11 20:35 ` [PoCv2 13/15] qga: implement {get,set}-vcpus " marcandre.lureau
                   ` (3 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: marcandre.lureau @ 2020-10-11 20:35 UTC (permalink / raw)
  To: qemu-devel; +Cc: pbonzini, berrange, armbru, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Use the "hostname" crate (https://github.com/svartalf/hostname)

(notice the wrong error message in our win32 implementation)

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 include/qemu/osdep.h | 10 ----------
 qga/Cargo.toml       |  1 +
 qga/commands.c       | 20 ++++----------------
 qga/lib.rs           |  2 ++
 qga/qmp/hostname.rs  |  9 +++++++++
 qga/qmp/mod.rs       |  7 +++++++
 tests/test-qga.c     |  2 ++
 util/oslib-posix.c   | 35 -----------------------------------
 util/oslib-win32.c   | 13 -------------
 9 files changed, 25 insertions(+), 74 deletions(-)
 create mode 100644 qga/qmp/hostname.rs

diff --git a/include/qemu/osdep.h b/include/qemu/osdep.h
index f9ec8c84e9..1ea244fc06 100644
--- a/include/qemu/osdep.h
+++ b/include/qemu/osdep.h
@@ -664,16 +664,6 @@ static inline void qemu_reset_optind(void)
 #endif
 }
 
-/**
- * qemu_get_host_name:
- * @errp: Error object
- *
- * Operating system agnostic way of querying host name.
- *
- * Returns allocated hostname (caller should free), NULL on failure.
- */
-char *qemu_get_host_name(Error **errp);
-
 /**
  * qemu_get_host_physmem:
  *
diff --git a/qga/Cargo.toml b/qga/Cargo.toml
index 9966057594..63a419255d 100644
--- a/qga/Cargo.toml
+++ b/qga/Cargo.toml
@@ -7,6 +7,7 @@ license = "GPLv2"
 [dependencies]
 common = { path = "../rust/common" }
 libc = "^0.2.76"
+hostname = "^0.3.1"
 
 [lib]
 path = "lib.rs"
diff --git a/qga/commands.c b/qga/commands.c
index 3dcd5fbe5c..15478a16e7 100644
--- a/qga/commands.c
+++ b/qga/commands.c
@@ -512,25 +512,13 @@ int ga_parse_whence(GuestFileWhence *whence, Error **errp)
     return -1;
 }
 
+#ifndef CONFIG_WITH_RUST
 GuestHostName *qmp_guest_get_host_name(Error **errp)
 {
-    GuestHostName *result = NULL;
-    g_autofree char *hostname = qemu_get_host_name(errp);
-
-    /*
-     * We want to avoid using g_get_host_name() because that
-     * caches the result and we wouldn't reflect changes in the
-     * host name.
-     */
-
-    if (!hostname) {
-        hostname = g_strdup("localhost");
-    }
-
-    result = g_new0(GuestHostName, 1);
-    result->host_name = g_steal_pointer(&hostname);
-    return result;
+    error_setg(errp, QERR_UNSUPPORTED);
+    return NULL;
 }
+#endif
 
 GuestTimezone *qmp_guest_get_timezone(Error **errp)
 {
diff --git a/qga/lib.rs b/qga/lib.rs
index 5fe08c25a3..f4967f59e5 100644
--- a/qga/lib.rs
+++ b/qga/lib.rs
@@ -1,3 +1,5 @@
+pub use common::{err, Error, Result};
+
 mod qapi;
 mod qapi_sys;
 mod qmp;
diff --git a/qga/qmp/hostname.rs b/qga/qmp/hostname.rs
new file mode 100644
index 0000000000..c3eb1f6fd2
--- /dev/null
+++ b/qga/qmp/hostname.rs
@@ -0,0 +1,9 @@
+use crate::*;
+
+pub(crate) fn get() -> Result<qapi::GuestHostName> {
+    let host_name = hostname::get()?
+        .into_string()
+        .or_else(|_| err!("Invalid hostname"))?;
+
+    Ok(qapi::GuestHostName { host_name })
+}
diff --git a/qga/qmp/mod.rs b/qga/qmp/mod.rs
index 38060100af..e855aa4bd7 100644
--- a/qga/qmp/mod.rs
+++ b/qga/qmp/mod.rs
@@ -34,3 +34,10 @@ macro_rules! qmp {
         }
     }};
 }
+
+mod hostname;
+
+#[no_mangle]
+extern "C" fn qmp_guest_get_host_name(errp: *mut *mut sys::Error) -> *mut qapi_sys::GuestHostName {
+    qmp!(hostname::get(), errp)
+}
diff --git a/tests/test-qga.c b/tests/test-qga.c
index c1b173b3cb..6190e93e0e 100644
--- a/tests/test-qga.c
+++ b/tests/test-qga.c
@@ -863,6 +863,7 @@ static void test_qga_guest_exec_invalid(gconstpointer fix)
 
 static void test_qga_guest_get_host_name(gconstpointer fix)
 {
+#ifdef CONFIG_WITH_RUST
     const TestFixture *fixture = fix;
     QDict *ret, *val;
 
@@ -874,6 +875,7 @@ static void test_qga_guest_get_host_name(gconstpointer fix)
     g_assert(qdict_haskey(val, "host-name"));
 
     qobject_unref(ret);
+#endif
 }
 
 static void test_qga_guest_get_timezone(gconstpointer fix)
diff --git a/util/oslib-posix.c b/util/oslib-posix.c
index f15234b5c0..1722c269b8 100644
--- a/util/oslib-posix.c
+++ b/util/oslib-posix.c
@@ -804,41 +804,6 @@ void sigaction_invoke(struct sigaction *action,
     action->sa_sigaction(info->ssi_signo, &si, NULL);
 }
 
-#ifndef HOST_NAME_MAX
-# ifdef _POSIX_HOST_NAME_MAX
-#  define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
-# else
-#  define HOST_NAME_MAX 255
-# endif
-#endif
-
-char *qemu_get_host_name(Error **errp)
-{
-    long len = -1;
-    g_autofree char *hostname = NULL;
-
-#ifdef _SC_HOST_NAME_MAX
-    len = sysconf(_SC_HOST_NAME_MAX);
-#endif /* _SC_HOST_NAME_MAX */
-
-    if (len < 0) {
-        len = HOST_NAME_MAX;
-    }
-
-    /* Unfortunately, gethostname() below does not guarantee a
-     * NULL terminated string. Therefore, allocate one byte more
-     * to be sure. */
-    hostname = g_new0(char, len + 1);
-
-    if (gethostname(hostname, len) < 0) {
-        error_setg_errno(errp, errno,
-                         "cannot get hostname");
-        return NULL;
-    }
-
-    return g_steal_pointer(&hostname);
-}
-
 size_t qemu_get_host_physmem(void)
 {
 #ifdef _SC_PHYS_PAGES
diff --git a/util/oslib-win32.c b/util/oslib-win32.c
index 051afb217b..804e303c4f 100644
--- a/util/oslib-win32.c
+++ b/util/oslib-win32.c
@@ -822,19 +822,6 @@ bool qemu_write_pidfile(const char *filename, Error **errp)
     return true;
 }
 
-char *qemu_get_host_name(Error **errp)
-{
-    wchar_t tmp[MAX_COMPUTERNAME_LENGTH + 1];
-    DWORD size = G_N_ELEMENTS(tmp);
-
-    if (GetComputerNameW(tmp, &size) == 0) {
-        error_setg_win32(errp, GetLastError(), "failed close handle");
-        return NULL;
-    }
-
-    return g_utf16_to_utf8(tmp, size, NULL, NULL, NULL);
-}
-
 size_t qemu_get_host_physmem(void)
 {
     MEMORYSTATUSEX statex;
-- 
2.28.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PoCv2 13/15] qga: implement {get,set}-vcpus in Rust
  2020-10-11 20:34 [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now) marcandre.lureau
                   ` (11 preceding siblings ...)
  2020-10-11 20:35 ` [PoCv2 12/15] qga: implement get-host-name in Rust marcandre.lureau
@ 2020-10-11 20:35 ` marcandre.lureau
  2020-10-11 20:35 ` [PoCv2 14/15] travis: add Rust marcandre.lureau
                   ` (2 subsequent siblings)
  15 siblings, 0 replies; 18+ messages in thread
From: marcandre.lureau @ 2020-10-11 20:35 UTC (permalink / raw)
  To: qemu-devel; +Cc: pbonzini, berrange, armbru, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

This is a rewrite of the C version (using the nix & winapi crates).

The main difference is that Rust doesn't let you mix const/mut logic,
the way transfer_vcpu in C does. The Rust version does introduce some
duplication, but is also more strict and can prevent mistakes.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qga/Cargo.toml       |   6 ++
 qga/commands-posix.c | 159 ------------------------------------------
 qga/commands-win32.c |  76 --------------------
 qga/commands.c       |  14 ++++
 qga/qmp/mod.rs       |  18 +++++
 qga/qmp/vcpus.rs     | 161 +++++++++++++++++++++++++++++++++++++++++++
 tests/test-qga.c     |   2 +
 7 files changed, 201 insertions(+), 235 deletions(-)
 create mode 100644 qga/qmp/vcpus.rs

diff --git a/qga/Cargo.toml b/qga/Cargo.toml
index 63a419255d..bb86fc543d 100644
--- a/qga/Cargo.toml
+++ b/qga/Cargo.toml
@@ -9,6 +9,12 @@ common = { path = "../rust/common" }
 libc = "^0.2.76"
 hostname = "^0.3.1"
 
+[target."cfg(unix)".dependencies]
+nix = "^0.18.0"
+
+[target."cfg(windows)".dependencies]
+winapi = { version = "^0.3.9", features = ["sysinfoapi", "winnt"] }
+
 [lib]
 path = "lib.rs"
 crate-type = ["staticlib"]
diff --git a/qga/commands-posix.c b/qga/commands-posix.c
index 3bffee99d4..2c2c97fbca 100644
--- a/qga/commands-posix.c
+++ b/qga/commands-posix.c
@@ -2092,165 +2092,6 @@ error:
     return NULL;
 }
 
-#define SYSCONF_EXACT(name, errp) sysconf_exact((name), #name, (errp))
-
-static long sysconf_exact(int name, const char *name_str, Error **errp)
-{
-    long ret;
-
-    errno = 0;
-    ret = sysconf(name);
-    if (ret == -1) {
-        if (errno == 0) {
-            error_setg(errp, "sysconf(%s): value indefinite", name_str);
-        } else {
-            error_setg_errno(errp, errno, "sysconf(%s)", name_str);
-        }
-    }
-    return ret;
-}
-
-/* Transfer online/offline status between @vcpu and the guest system.
- *
- * On input either @errp or *@errp must be NULL.
- *
- * In system-to-@vcpu direction, the following @vcpu fields are accessed:
- * - R: vcpu->logical_id
- * - W: vcpu->online
- * - W: vcpu->can_offline
- *
- * In @vcpu-to-system direction, the following @vcpu fields are accessed:
- * - R: vcpu->logical_id
- * - R: vcpu->online
- *
- * Written members remain unmodified on error.
- */
-static void transfer_vcpu(GuestLogicalProcessor *vcpu, bool sys2vcpu,
-                          char *dirpath, Error **errp)
-{
-    int fd;
-    int res;
-    int dirfd;
-    static const char fn[] = "online";
-
-    dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
-    if (dirfd == -1) {
-        error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
-        return;
-    }
-
-    fd = openat(dirfd, fn, sys2vcpu ? O_RDONLY : O_RDWR);
-    if (fd == -1) {
-        if (errno != ENOENT) {
-            error_setg_errno(errp, errno, "open(\"%s/%s\")", dirpath, fn);
-        } else if (sys2vcpu) {
-            vcpu->online = true;
-            vcpu->can_offline = false;
-        } else if (!vcpu->online) {
-            error_setg(errp, "logical processor #%" PRId64 " can't be "
-                       "offlined", vcpu->logical_id);
-        } /* otherwise pretend successful re-onlining */
-    } else {
-        unsigned char status;
-
-        res = pread(fd, &status, 1, 0);
-        if (res == -1) {
-            error_setg_errno(errp, errno, "pread(\"%s/%s\")", dirpath, fn);
-        } else if (res == 0) {
-            error_setg(errp, "pread(\"%s/%s\"): unexpected EOF", dirpath,
-                       fn);
-        } else if (sys2vcpu) {
-            vcpu->online = (status != '0');
-            vcpu->can_offline = true;
-        } else if (vcpu->online != (status != '0')) {
-            status = '0' + vcpu->online;
-            if (pwrite(fd, &status, 1, 0) == -1) {
-                error_setg_errno(errp, errno, "pwrite(\"%s/%s\")", dirpath,
-                                 fn);
-            }
-        } /* otherwise pretend successful re-(on|off)-lining */
-
-        res = close(fd);
-        g_assert(res == 0);
-    }
-
-    res = close(dirfd);
-    g_assert(res == 0);
-}
-
-GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
-{
-    int64_t current;
-    GuestLogicalProcessorList *head, **link;
-    long sc_max;
-    Error *local_err = NULL;
-
-    current = 0;
-    head = NULL;
-    link = &head;
-    sc_max = SYSCONF_EXACT(_SC_NPROCESSORS_CONF, &local_err);
-
-    while (local_err == NULL && current < sc_max) {
-        GuestLogicalProcessor *vcpu;
-        GuestLogicalProcessorList *entry;
-        int64_t id = current++;
-        char *path = g_strdup_printf("/sys/devices/system/cpu/cpu%" PRId64 "/",
-                                     id);
-
-        if (g_file_test(path, G_FILE_TEST_EXISTS)) {
-            vcpu = g_malloc0(sizeof *vcpu);
-            vcpu->logical_id = id;
-            vcpu->has_can_offline = true; /* lolspeak ftw */
-            transfer_vcpu(vcpu, true, path, &local_err);
-            entry = g_malloc0(sizeof *entry);
-            entry->value = vcpu;
-            *link = entry;
-            link = &entry->next;
-        }
-        g_free(path);
-    }
-
-    if (local_err == NULL) {
-        /* there's no guest with zero VCPUs */
-        g_assert(head != NULL);
-        return head;
-    }
-
-    qapi_free_GuestLogicalProcessorList(head);
-    error_propagate(errp, local_err);
-    return NULL;
-}
-
-int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
-{
-    int64_t processed;
-    Error *local_err = NULL;
-
-    processed = 0;
-    while (vcpus != NULL) {
-        char *path = g_strdup_printf("/sys/devices/system/cpu/cpu%" PRId64 "/",
-                                     vcpus->value->logical_id);
-
-        transfer_vcpu(vcpus->value, false, path, &local_err);
-        g_free(path);
-        if (local_err != NULL) {
-            break;
-        }
-        ++processed;
-        vcpus = vcpus->next;
-    }
-
-    if (local_err != NULL) {
-        if (processed == 0) {
-            error_propagate(errp, local_err);
-        } else {
-            error_free(local_err);
-        }
-    }
-
-    return processed;
-}
-
 void qmp_guest_set_user_password(const char *username,
                                  const char *password,
                                  bool crypted,
diff --git a/qga/commands-win32.c b/qga/commands-win32.c
index 0c3c05484f..1e140b68a6 100644
--- a/qga/commands-win32.c
+++ b/qga/commands-win32.c
@@ -1740,82 +1740,6 @@ void qmp_guest_set_time(bool has_time, int64_t time_ns, Error **errp)
     }
 }
 
-GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
-{
-    PSYSTEM_LOGICAL_PROCESSOR_INFORMATION pslpi, ptr;
-    DWORD length;
-    GuestLogicalProcessorList *head, **link;
-    Error *local_err = NULL;
-    int64_t current;
-
-    ptr = pslpi = NULL;
-    length = 0;
-    current = 0;
-    head = NULL;
-    link = &head;
-
-    if ((GetLogicalProcessorInformation(pslpi, &length) == FALSE) &&
-        (GetLastError() == ERROR_INSUFFICIENT_BUFFER) &&
-        (length > sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION))) {
-        ptr = pslpi = g_malloc0(length);
-        if (GetLogicalProcessorInformation(pslpi, &length) == FALSE) {
-            error_setg(&local_err, "Failed to get processor information: %d",
-                       (int)GetLastError());
-        }
-    } else {
-        error_setg(&local_err,
-                   "Failed to get processor information buffer length: %d",
-                   (int)GetLastError());
-    }
-
-    while ((local_err == NULL) && (length > 0)) {
-        if (pslpi->Relationship == RelationProcessorCore) {
-            ULONG_PTR cpu_bits = pslpi->ProcessorMask;
-
-            while (cpu_bits > 0) {
-                if (!!(cpu_bits & 1)) {
-                    GuestLogicalProcessor *vcpu;
-                    GuestLogicalProcessorList *entry;
-
-                    vcpu = g_malloc0(sizeof *vcpu);
-                    vcpu->logical_id = current++;
-                    vcpu->online = true;
-                    vcpu->has_can_offline = true;
-
-                    entry = g_malloc0(sizeof *entry);
-                    entry->value = vcpu;
-
-                    *link = entry;
-                    link = &entry->next;
-                }
-                cpu_bits >>= 1;
-            }
-        }
-        length -= sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION);
-        pslpi++; /* next entry */
-    }
-
-    g_free(ptr);
-
-    if (local_err == NULL) {
-        if (head != NULL) {
-            return head;
-        }
-        /* there's no guest with zero VCPUs */
-        error_setg(&local_err, "Guest reported zero VCPUs");
-    }
-
-    qapi_free_GuestLogicalProcessorList(head);
-    error_propagate(errp, local_err);
-    return NULL;
-}
-
-int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
-{
-    error_setg(errp, QERR_UNSUPPORTED);
-    return -1;
-}
-
 static gchar *
 get_net_error_message(gint error)
 {
diff --git a/qga/commands.c b/qga/commands.c
index 15478a16e7..a5f8e32ece 100644
--- a/qga/commands.c
+++ b/qga/commands.c
@@ -577,3 +577,17 @@ GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count,
 
     return read_data;
 }
+
+#ifndef CONFIG_WITH_RUST
+GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
+{
+    error_setg(errp, QERR_UNSUPPORTED);
+    return NULL;
+}
+
+int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
+{
+    error_setg(errp, QERR_UNSUPPORTED);
+    return 0;
+}
+#endif
diff --git a/qga/qmp/mod.rs b/qga/qmp/mod.rs
index e855aa4bd7..7c4d30c7de 100644
--- a/qga/qmp/mod.rs
+++ b/qga/qmp/mod.rs
@@ -41,3 +41,21 @@ mod hostname;
 extern "C" fn qmp_guest_get_host_name(errp: *mut *mut sys::Error) -> *mut qapi_sys::GuestHostName {
     qmp!(hostname::get(), errp)
 }
+
+mod vcpus;
+
+#[no_mangle]
+extern "C" fn qmp_guest_get_vcpus(
+    errp: *mut *mut sys::Error,
+) -> *mut qapi_sys::GuestLogicalProcessorList {
+    qmp!(vcpus::get(), errp)
+}
+
+#[no_mangle]
+extern "C" fn qmp_guest_set_vcpus(
+    vcpus: *const qapi_sys::GuestLogicalProcessorList,
+    errp: *mut *mut sys::Error,
+) -> libc::c_longlong {
+    let vcpus = unsafe { from_qemu_none(qapi::NewPtr(vcpus)) };
+    qmp!(vcpus::set(vcpus), errp, -1)
+}
diff --git a/qga/qmp/vcpus.rs b/qga/qmp/vcpus.rs
new file mode 100644
index 0000000000..f86838355e
--- /dev/null
+++ b/qga/qmp/vcpus.rs
@@ -0,0 +1,161 @@
+#[cfg(unix)]
+use std::fs::OpenOptions;
+#[cfg(unix)]
+use std::io::ErrorKind;
+#[cfg(unix)]
+use std::os::unix::fs::FileExt;
+
+#[cfg(windows)]
+use winapi::um::{sysinfoapi, winnt};
+
+use crate::*;
+
+#[cfg(target_os = "linux")]
+fn get_sysfs_cpu_path(id: i64) -> String {
+    format!("/sys/devices/system/cpu/cpu{}", id)
+}
+
+#[cfg(target_os = "linux")]
+fn set_vcpu(vcpu: &qapi::GuestLogicalProcessor) -> Result<()> {
+    let path = get_sysfs_cpu_path(vcpu.logical_id);
+    std::fs::metadata(&path)?;
+
+    let path = format!("{}/online", path);
+    match OpenOptions::new().read(true).write(true).open(&path) {
+        Ok(file) => {
+            let mut buf = [0u8; 1];
+            file.read_exact_at(&mut buf, 0)?;
+            let online = buf[0] != 0;
+            if vcpu.online != online {
+                buf[0] = if vcpu.online { b'1' } else { b'0' };
+                file.write_all_at(&buf, 0)?;
+            }
+        }
+        Err(e) => {
+            if e.kind() != ErrorKind::NotFound {
+                return Err(e.into());
+            } else if !vcpu.online {
+                return err!(format!(
+                    "logical processor #{} can't be offlined",
+                    vcpu.logical_id
+                ));
+            }
+        }
+    }
+
+    Ok(())
+}
+
+#[cfg(not(target_os = "linux"))]
+fn set_vcpu(_vcpu: &qapi::GuestLogicalProcessor) -> Result<()> {
+    err!("unimplemented")
+}
+
+pub(crate) fn set(vcpus: Vec<qapi::GuestLogicalProcessor>) -> Result<i64> {
+    let mut processed = 0;
+
+    for vcpu in &vcpus {
+        if let Err(e) = set_vcpu(vcpu) {
+            if processed != 0 {
+                break;
+            }
+            return Err(e);
+        }
+
+        processed += 1;
+    }
+
+    Ok(processed)
+}
+
+#[cfg(target_os = "linux")]
+pub(crate) fn get() -> Result<Vec<qapi::GuestLogicalProcessor>> {
+    use nix::unistd::sysconf;
+
+    let mut vcpus = vec![];
+    let nproc_conf = match sysconf(unsafe { std::mem::transmute(libc::_SC_NPROCESSORS_CONF) })? {
+        Some(nproc) => nproc,
+        None => {
+            return err!("Indefinite number of processors.");
+        }
+    };
+
+    for logical_id in 0..nproc_conf {
+        let path = get_sysfs_cpu_path(logical_id);
+        if std::fs::metadata(&path).is_err() {
+            continue;
+        }
+
+        let path = format!("{}/online", path);
+        let (online, can_offline) = match OpenOptions::new().read(true).open(&path) {
+            Ok(file) => {
+                let mut buf = [0u8; 1];
+                file.read_exact_at(&mut buf, 0)?;
+                (buf[0] != 0, Some(true))
+            }
+            Err(e) => {
+                if e.kind() != ErrorKind::NotFound {
+                    return Err(e.into());
+                }
+                (true, Some(false))
+            }
+        };
+
+        vcpus.push(qapi::GuestLogicalProcessor {
+            logical_id,
+            online,
+            can_offline,
+        });
+    }
+
+    Ok(vcpus)
+}
+
+#[cfg(target_os = "windows")]
+fn get_logical_processor_info() -> Result<Vec<winnt::SYSTEM_LOGICAL_PROCESSOR_INFORMATION>> {
+    unsafe {
+        let mut needed_size = 0;
+        sysinfoapi::GetLogicalProcessorInformation(std::ptr::null_mut(), &mut needed_size);
+        let struct_size = std::mem::size_of::<winnt::SYSTEM_LOGICAL_PROCESSOR_INFORMATION>() as u32;
+        if needed_size == 0 || needed_size < struct_size || needed_size % struct_size != 0 {
+            return err!("Failed to get processor information");
+        }
+
+        let nstruct = needed_size / struct_size;
+        let mut buf = Vec::with_capacity(nstruct as usize);
+        let result = sysinfoapi::GetLogicalProcessorInformation(buf.as_mut_ptr(), &mut needed_size);
+        if result == 0 {
+            return err!("Failed to get processor information");
+        }
+
+        let nstruct = needed_size / struct_size;
+        buf.set_len(nstruct as usize);
+        Ok(buf)
+    }
+}
+
+#[cfg(target_os = "windows")]
+pub(crate) fn get() -> Result<Vec<qapi::GuestLogicalProcessor>> {
+    let mut vcpus = vec![];
+
+    get_logical_processor_info()?.iter().map(|info| {
+        for _ in 0..info.ProcessorMask.count_ones() {
+            vcpus.push(qapi::GuestLogicalProcessor {
+                logical_id: vcpus.len() as i64,
+                online: true,
+                can_offline: Some(false),
+            });
+        }
+    });
+
+    if vcpus.is_empty() {
+        return err!("Guest reported zero VCPUs");
+    }
+
+    Ok(vcpus)
+}
+
+#[cfg(not(any(target_os = "linux", target_os = "windows")))]
+pub(crate) fn get() -> Result<Vec<qapi::GuestLogicalProcessor>> {
+    err!("unimplemented")
+}
diff --git a/tests/test-qga.c b/tests/test-qga.c
index 6190e93e0e..ff1807531c 100644
--- a/tests/test-qga.c
+++ b/tests/test-qga.c
@@ -307,6 +307,7 @@ static void test_qga_info(gconstpointer fix)
 
 static void test_qga_get_vcpus(gconstpointer fix)
 {
+#ifdef CONFIG_WITH_RUST
     const TestFixture *fixture = fix;
     QDict *ret;
     QList *list;
@@ -323,6 +324,7 @@ static void test_qga_get_vcpus(gconstpointer fix)
     g_assert(qdict_haskey(qobject_to(QDict, entry->value), "logical-id"));
 
     qobject_unref(ret);
+#endif
 }
 
 static void test_qga_get_fsinfo(gconstpointer fix)
-- 
2.28.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PoCv2 14/15] travis: add Rust
  2020-10-11 20:34 [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now) marcandre.lureau
                   ` (12 preceding siblings ...)
  2020-10-11 20:35 ` [PoCv2 13/15] qga: implement {get,set}-vcpus " marcandre.lureau
@ 2020-10-11 20:35 ` marcandre.lureau
  2020-10-11 20:35 ` [PoCv2 15/15] rust: use vendored-sources marcandre.lureau
  2020-10-11 20:54 ` [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now) no-reply
  15 siblings, 0 replies; 18+ messages in thread
From: marcandre.lureau @ 2020-10-11 20:35 UTC (permalink / raw)
  To: qemu-devel; +Cc: pbonzini, berrange, armbru, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 .travis.yml | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/.travis.yml b/.travis.yml
index 1054ec5d29..b2835316bc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -23,6 +23,8 @@ addons:
   apt:
     packages:
       # Build dependencies
+      - cargo
+      - rustc
       - libaio-dev
       - libattr1-dev
       - libbrlapi-dev
@@ -71,7 +73,7 @@ env:
   global:
     - SRC_DIR=".."
     - BUILD_DIR="build"
-    - BASE_CONFIG="--disable-docs --disable-tools"
+    - BASE_CONFIG="--disable-docs --disable-tools --with-rust"
     - TEST_BUILD_CMD=""
     - TEST_CMD="make check V=1"
     # This is broadly a list of "mainline" softmmu targets which have support across the major distros
@@ -258,6 +260,8 @@ jobs:
             # Extra toolchains
             - gcc-9
             - g++-9
+            - cargo
+            - rustc
             # Build dependencies
             - libaio-dev
             - libattr1-dev
@@ -325,6 +329,8 @@ jobs:
       dist: focal
       addons:
         apt_packages:
+          - cargo
+          - rustc
           - libaio-dev
           - libattr1-dev
           - libbrlapi-dev
@@ -358,6 +364,8 @@ jobs:
       dist: focal
       addons:
         apt_packages:
+          - cargo
+          - rustc
           - libaio-dev
           - libattr1-dev
           - libbrlapi-dev
@@ -390,6 +398,8 @@ jobs:
       dist: bionic
       addons:
         apt_packages:
+          - cargo
+          - rustc
           - libaio-dev
           - libattr1-dev
           - libbrlapi-dev
@@ -432,6 +442,8 @@ jobs:
       dist: bionic
       addons:
         apt_packages:
+          - cargo
+          - rustc
           - libaio-dev
           - libattr1-dev
           - libcap-ng-dev
@@ -461,6 +473,8 @@ jobs:
       dist: bionic
       addons:
         apt_packages:
+          - cargo
+          - rustc
           - libgcrypt20-dev
           - libgnutls28-dev
       env:
@@ -472,6 +486,8 @@ jobs:
       compiler: clang
       addons:
         apt_packages:
+          - cargo
+          - rustc
           - libaio-dev
           - libattr1-dev
           - libbrlapi-dev
-- 
2.28.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PoCv2 15/15] rust: use vendored-sources
  2020-10-11 20:34 [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now) marcandre.lureau
                   ` (13 preceding siblings ...)
  2020-10-11 20:35 ` [PoCv2 14/15] travis: add Rust marcandre.lureau
@ 2020-10-11 20:35 ` marcandre.lureau
  2020-10-11 20:54 ` [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now) no-reply
  15 siblings, 0 replies; 18+ messages in thread
From: marcandre.lureau @ 2020-10-11 20:35 UTC (permalink / raw)
  To: qemu-devel; +Cc: pbonzini, berrange, armbru, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Most likely, QEMU will want tighter control over the sources, rather
than relying on crates.io downloading, use a git submodule with all the
dependencies.

"cargo vendor" makes that simple.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 .cargo/config            | 5 +++++
 .gitmodules              | 3 +++
 configure                | 8 ++++++++
 rust/vendored            | 1 +
 scripts/cargo_wrapper.py | 1 +
 5 files changed, 18 insertions(+)
 create mode 100644 .cargo/config
 create mode 160000 rust/vendored

diff --git a/.cargo/config b/.cargo/config
new file mode 100644
index 0000000000..a8c55940d5
--- /dev/null
+++ b/.cargo/config
@@ -0,0 +1,5 @@
+[source.crates-io]
+replace-with = "vendored-sources"
+
+[source.vendored-sources]
+directory = "rust/vendored"
diff --git a/.gitmodules b/.gitmodules
index 2bdeeacef8..62a2be12b9 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -64,3 +64,6 @@
 [submodule "roms/vbootrom"]
 	path = roms/vbootrom
 	url = https://git.qemu.org/git/vbootrom.git
+[submodule "rust/vendored"]
+	path = rust/vendored
+	url = https://github.com/elmarco/qemu-rust-vendored.git
diff --git a/configure b/configure
index 7945ceac63..702546d87f 100755
--- a/configure
+++ b/configure
@@ -1932,6 +1932,14 @@ if test "$with_rust" = auto && has cargo; then
     with_rust=yes
 fi
 
+case "$with_rust" in
+  yes)
+    if test -e "${source_path}/.git" && test $git_update = 'yes' ; then
+      git_submodules="${git_submodules} rust/vendored"
+    fi
+    ;;
+esac
+
 # Check that the C compiler works. Doing this here before testing
 # the host CPU ensures that we had a valid CC to autodetect the
 # $cpu var (and we should bail right here if that's not the case).
diff --git a/rust/vendored b/rust/vendored
new file mode 160000
index 0000000000..71ee65d042
--- /dev/null
+++ b/rust/vendored
@@ -0,0 +1 @@
+Subproject commit 71ee65d042606d18de3175d67f9e4e4b78a1f865
diff --git a/scripts/cargo_wrapper.py b/scripts/cargo_wrapper.py
index 164fad5123..4a0673407b 100644
--- a/scripts/cargo_wrapper.py
+++ b/scripts/cargo_wrapper.py
@@ -51,6 +51,7 @@ def build_lib(args: argparse.Namespace) -> None:
         target_dir,
         "--manifest-path",
         manifest_path,
+        "--offline",
     ]
     if args.target_triple:
         cargo_cmd += ["--target", args.target_triple]
-- 
2.28.0



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [PoCv2 01/15] mingw: fix error __USE_MINGW_ANSI_STDIO redefined
  2020-10-11 20:34 ` [PoCv2 01/15] mingw: fix error __USE_MINGW_ANSI_STDIO redefined marcandre.lureau
@ 2020-10-11 20:37   ` Marc-André Lureau
  0 siblings, 0 replies; 18+ messages in thread
From: Marc-André Lureau @ 2020-10-11 20:37 UTC (permalink / raw)
  To: qemu-devel
  Cc: Bonzini, Paolo, P. Berrange, Daniel, Armbruster, Markus, Stefan Hajnoczi

Hi

On Mon, Oct 12, 2020 at 12:35 AM <marcandre.lureau@redhat.com> wrote:
>
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Always put osdep.h first, and remove redundant stdlib.h include.
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

(ignore this patch, which was already sent earlier)

> ---
>  migration/dirtyrate.c | 3 ++-
>  tests/test-bitmap.c   | 1 -
>  2 files changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/migration/dirtyrate.c b/migration/dirtyrate.c
> index 68577ef250..47f761e67a 100644
> --- a/migration/dirtyrate.c
> +++ b/migration/dirtyrate.c
> @@ -10,8 +10,9 @@
>   * See the COPYING file in the top-level directory.
>   */
>
> -#include <zlib.h>
>  #include "qemu/osdep.h"
> +
> +#include <zlib.h>
>  #include "qapi/error.h"
>  #include "cpu.h"
>  #include "qemu/config-file.h"
> diff --git a/tests/test-bitmap.c b/tests/test-bitmap.c
> index 2f5b71458a..8db4f67883 100644
> --- a/tests/test-bitmap.c
> +++ b/tests/test-bitmap.c
> @@ -8,7 +8,6 @@
>   * Author: Peter Xu <peterx@redhat.com>
>   */
>
> -#include <stdlib.h>
>  #include "qemu/osdep.h"
>  #include "qemu/bitmap.h"
>
> --
> 2.28.0
>



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now)
  2020-10-11 20:34 [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now) marcandre.lureau
                   ` (14 preceding siblings ...)
  2020-10-11 20:35 ` [PoCv2 15/15] rust: use vendored-sources marcandre.lureau
@ 2020-10-11 20:54 ` no-reply
  15 siblings, 0 replies; 18+ messages in thread
From: no-reply @ 2020-10-11 20:54 UTC (permalink / raw)
  To: marcandre.lureau
  Cc: berrange, qemu-devel, armbru, stefanha, marcandre.lureau, pbonzini

Patchew URL: https://patchew.org/QEMU/20201011203513.1621355-1-marcandre.lureau@redhat.com/



Hi,

This series seems to have some coding style problems. See output below for
more information:

Type: series
Message-id: 20201011203513.1621355-1-marcandre.lureau@redhat.com
Subject: [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now)

=== TEST SCRIPT BEGIN ===
#!/bin/bash
git rev-parse base > /dev/null || exit 0
git config --local diff.renamelimit 0
git config --local diff.renames True
git config --local diff.algorithm histogram
./scripts/checkpatch.pl --mailback base..
=== TEST SCRIPT END ===

Updating 3c8cf5a9c21ff8782164d1def7f44bd888713384
From https://github.com/patchew-project/qemu
 * [new tag]         patchew/20201011203513.1621355-1-marcandre.lureau@redhat.com -> patchew/20201011203513.1621355-1-marcandre.lureau@redhat.com
Switched to a new branch 'test'
e5abacf rust: use vendored-sources
f8125dc travis: add Rust
0bb6095b qga: implement {get,set}-vcpus in Rust
6f1cb07 qga: implement get-host-name in Rust
6cc3f63 qga: add qmp! macro helper
abc816c qga/rust: build Rust types
8273b34 scripts/qapi: add generation of Rust bindings for types
3068424 qga/rust: generate QGA QAPI sys bindings
8d23431 scripts/qapi: add Rust sys bindings generation
eb4bb2d rust: provide a common crate for QEMU
f19d443 qga/rust: build and link an empty static library
03097d9 build-sys: add a cargo-wrapper script
d8ec366 build-sys: add --with-rust{-target} & basic build infrastructure
e524eda scripts/qapi: teach c_param_type() to return const argument type
e68868d mingw: fix error __USE_MINGW_ANSI_STDIO redefined

=== OUTPUT BEGIN ===
1/15 Checking commit e68868d9d68b (mingw: fix error __USE_MINGW_ANSI_STDIO redefined)
2/15 Checking commit e524eda108f7 (scripts/qapi: teach c_param_type() to return const argument type)
WARNING: line over 80 characters
#27: FILE: scripts/qapi/schema.py:171:
+    # The argument should be considered const, since no ownership is given to the callee,

WARNING: line over 80 characters
#28: FILE: scripts/qapi/schema.py:172:
+    # but qemu C code frequently tweaks it. Set const=True for a stricter declaration.

total: 0 errors, 2 warnings, 28 lines checked

Patch 2/15 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
3/15 Checking commit d8ec36655d29 (build-sys: add --with-rust{-target} & basic build infrastructure)
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#29: 
new file mode 100644

total: 0 errors, 1 warnings, 85 lines checked

Patch 3/15 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
4/15 Checking commit 03097d99222b (build-sys: add a cargo-wrapper script)
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#39: 
new file mode 100644

WARNING: line over 80 characters
#82: FILE: scripts/cargo_wrapper.py:39:
+        target_dir, args.target_triple, args.build_type, "lib" + package_name + ".a"

WARNING: line over 80 characters
#119: FILE: scripts/cargo_wrapper.py:76:
+            "Environment: " + " ".join(["{}={}".format(k, v) for k, v in env.items()])

total: 0 errors, 3 warnings, 108 lines checked

Patch 4/15 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
5/15 Checking commit f19d443c4992 (qga/rust: build and link an empty static library)
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#29: 
new file mode 100644

total: 0 errors, 1 warnings, 38 lines checked

Patch 5/15 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
6/15 Checking commit eb4bb2d04ac5 (rust: provide a common crate for QEMU)
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#32: 
new file mode 100644

total: 0 errors, 1 warnings, 533 lines checked

Patch 6/15 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
7/15 Checking commit 8d23431862a8 (scripts/qapi: add Rust sys bindings generation)
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#79: 
new file mode 100644

WARNING: line over 80 characters
#182: FILE: scripts/qapi/rs.py:99:
+    value = ''.join(word.title() for word in filter(None, re.split("[-_]+", value)))

ERROR: line over 90 characters
#270: FILE: scripts/qapi/rs_sys.py:55:
+                     rs_systype=rs_systype(memb.type.c_type(), ''), rs_name=rs_name(memb.name))

WARNING: line over 80 characters
#332: FILE: scripts/qapi/rs_sys.py:117:
+                ret += gen_rs_sys_object(v.type.name, v.type.ifcond, v.type.base,

WARNING: line over 80 characters
#429: FILE: scripts/qapi/rs_sys.py:214:
+                 rs_name=rs_name(name), rs_systype=rs_systype(element_type.c_type(), ''))

total: 1 errors, 4 warnings, 422 lines checked

Patch 7/15 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.

8/15 Checking commit 306842468f88 (qga/rust: generate QGA QAPI sys bindings)
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#61: 
new file mode 100644

total: 0 errors, 1 warnings, 37 lines checked

Patch 8/15 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
9/15 Checking commit 8273b3417e30 (scripts/qapi: add generation of Rust bindings for types)
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#179: 
new file mode 100644

WARNING: line over 80 characters
#227: FILE: scripts/qapi/rs_types.py:44:
+''', var_name=var_name, rs_type=rs_type(type_name, ''), rs_systype=rs_systype(type_name))

ERROR: line over 90 characters
#231: FILE: scripts/qapi/rs_types.py:48:
+                 (stash_.0 as *mut std::ffi::c_void, %(rs_name)sCEnum::%(var_name)s(stash_.1))

WARNING: line over 80 characters
#252: FILE: scripts/qapi/rs_types.py:69:
+    fn to_qemu_none(&'a self) -> Stash<'a, *mut std::ffi::c_void, %(rs_name)sEnum> {

ERROR: line over 90 characters
#368: FILE: scripts/qapi/rs_types.py:185:
+            arms += mcgen('%(rs_name)sEnum::%(kind_name)s(_) => qapi_sys::%(rs_name)sUnion { %(var_name)s: %(var_type)s { data: u_ptr_ as *mut _ } },',

ERROR: line over 90 characters
#369: FILE: scripts/qapi/rs_types.py:186:
+                          rs_name=rs_name(name), kind_name=to_camel_case(var.name), var_name=rs_name(var.name), var_type=rs_systype(var.type.c_name()))

WARNING: line over 80 characters
#395: FILE: scripts/qapi/rs_types.py:212:
+    fn to_qemu_none(&'a self) -> Stash<'a, *mut qapi_sys::%(rs_name)s, %(rs_name)s> {

WARNING: line over 80 characters
#406: FILE: scripts/qapi/rs_types.py:223:
+            let ptr = sys::g_malloc0(std::mem::size_of::<*const %(rs_name)s>()) as *mut _;

ERROR: line over 90 characters
#414: FILE: scripts/qapi/rs_types.py:231:
+                 sys_memb=', '.join(sys_memb), memb_none=memb_none, memb_full=memb_full, stash=', '.join(stash))

ERROR: line over 90 characters
#459: FILE: scripts/qapi/rs_types.py:276:
+            %(enum)s::%(variant)s => { %(rs_name)sEnum::%(variant)s(from_qemu_none(sys.u.%(memb)s.data as *const _)) },

ERROR: line over 90 characters
#487: FILE: scripts/qapi/rs_types.py:304:
+                     rs_type=rs_type(memb.type.c_type(), '', optional=memb.optional), rs_name=rsname)

total: 6 errors, 5 warnings, 570 lines checked

Patch 9/15 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.

10/15 Checking commit abc816c08984 (qga/rust: build Rust types)
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#30: 
new file mode 100644

total: 0 errors, 1 warnings, 15 lines checked

Patch 10/15 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
11/15 Checking commit 6cc3f639e6bc (qga: add qmp! macro helper)
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#22: 
new file mode 100644

total: 0 errors, 1 warnings, 39 lines checked

Patch 11/15 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
12/15 Checking commit 6f1cb07dcab6 (qga: implement get-host-name in Rust)
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#92: 
new file mode 100644

total: 0 errors, 1 warnings, 150 lines checked

Patch 12/15 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
13/15 Checking commit 0bb6095b4294 (qga: implement {get,set}-vcpus in Rust)
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#339: 
new file mode 100644

total: 0 errors, 1 warnings, 472 lines checked

Patch 13/15 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
14/15 Checking commit f8125dcffce4 (travis: add Rust)
15/15 Checking commit e5abacf1af78 (rust: use vendored-sources)
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#17: 
new file mode 100644

total: 0 errors, 1 warnings, 33 lines checked

Patch 15/15 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
=== OUTPUT END ===

Test command exited with code: 1


The full log is available at
http://patchew.org/logs/20201011203513.1621355-1-marcandre.lureau@redhat.com/testing.checkpatch/?type=message.
---
Email generated automatically by Patchew [https://patchew.org/].
Please send your feedback to patchew-devel@redhat.com

^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2020-10-11 20:56 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-10-11 20:34 [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now) marcandre.lureau
2020-10-11 20:34 ` [PoCv2 01/15] mingw: fix error __USE_MINGW_ANSI_STDIO redefined marcandre.lureau
2020-10-11 20:37   ` Marc-André Lureau
2020-10-11 20:35 ` [PoCv2 02/15] scripts/qapi: teach c_param_type() to return const argument type marcandre.lureau
2020-10-11 20:35 ` [PoCv2 03/15] build-sys: add --with-rust{-target} & basic build infrastructure marcandre.lureau
2020-10-11 20:35 ` [PoCv2 04/15] build-sys: add a cargo-wrapper script marcandre.lureau
2020-10-11 20:35 ` [PoCv2 05/15] qga/rust: build and link an empty static library marcandre.lureau
2020-10-11 20:35 ` [PoCv2 06/15] rust: provide a common crate for QEMU marcandre.lureau
2020-10-11 20:35 ` [PoCv2 07/15] scripts/qapi: add Rust sys bindings generation marcandre.lureau
2020-10-11 20:35 ` [PoCv2 08/15] qga/rust: generate QGA QAPI sys bindings marcandre.lureau
2020-10-11 20:35 ` [PoCv2 09/15] scripts/qapi: add generation of Rust bindings for types marcandre.lureau
2020-10-11 20:35 ` [PoCv2 10/15] qga/rust: build Rust types marcandre.lureau
2020-10-11 20:35 ` [PoCv2 11/15] qga: add qmp! macro helper marcandre.lureau
2020-10-11 20:35 ` [PoCv2 12/15] qga: implement get-host-name in Rust marcandre.lureau
2020-10-11 20:35 ` [PoCv2 13/15] qga: implement {get,set}-vcpus " marcandre.lureau
2020-10-11 20:35 ` [PoCv2 14/15] travis: add Rust marcandre.lureau
2020-10-11 20:35 ` [PoCv2 15/15] rust: use vendored-sources marcandre.lureau
2020-10-11 20:54 ` [PoCv2 00/15] Rust binding for QAPI (qemu-ga only, for now) no-reply

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.