From nobody Tue Feb 10 11:55:47 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1602448913; cv=none; d=zohomail.com; s=zohoarc; b=A3/JrJJZFkuVATi5/NUsqAsb5X/q1ZGCo04JekzpvqezzQ237B35E4akiITLQ+ts0YT1S6He2E1QGxmSy6muSkYaYy0sEpqbxMEf8XyyyzJgvvXL9rnwJ3vCdlXoUK5ib2V/YpIpqnl4ADAZsedTYSIIflmBa/jYB2zrRwr6LXc= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1602448913; h=Content-Type:Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=oMGcivtxqfyttrxW+n4YbT+qnoQcOtRjXxzV/2OSLnk=; b=e0w95PXAyqeQgObSvxV60ghr/vd2jU8O1/LoLALmit3aEvruqMFBysgt9gUzLvvIsysWokA4/mfjIAikwA62+o3W4JS01D0Y4EK3E5a4xtuwWYPH8TgCnSQqJcmYc1gso8qLbLDj2iXzEiEmlOt0QguQxnXkT4dulNyOMQrcPEs= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail header.from= (p=none dis=none) header.from= Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1602448913830321.92571643802967; Sun, 11 Oct 2020 13:41:53 -0700 (PDT) Received: from localhost ([::1]:40314 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kRiAC-0000rn-Gp for importer@patchew.org; Sun, 11 Oct 2020 16:41:52 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:57456) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kRi4w-00017H-La for qemu-devel@nongnu.org; Sun, 11 Oct 2020 16:36:28 -0400 Received: from us-smtp-delivery-124.mimecast.com ([63.128.21.124]:50385) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.90_1) (envelope-from ) id 1kRi4s-0005UK-GC for qemu-devel@nongnu.org; Sun, 11 Oct 2020 16:36:26 -0400 Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-306-o_2hRVGHMz-OOlPLfEgsFA-1; Sun, 11 Oct 2020 16:36:18 -0400 Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.phx2.redhat.com [10.5.11.15]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id C77DF804001 for ; Sun, 11 Oct 2020 20:36:17 +0000 (UTC) Received: from localhost (unknown [10.36.110.19]) by smtp.corp.redhat.com (Postfix) with ESMTP id 8C8D67512E; Sun, 11 Oct 2020 20:36:15 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1602448580; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=oMGcivtxqfyttrxW+n4YbT+qnoQcOtRjXxzV/2OSLnk=; b=Ri/CRzxZoyxrVcz8252OYAOLlwRIYsiVVw4PkoKIhp3+4YbLFO98Wpot6EZarBQlt/LKlh v6E2IjQgtMPQnjFiUz7+YPC9gmBnkVyOtjjzzKWSQq35QgLzhe22g/empOhAxNSjDywhnr 764iqt1hXkToc8Dzp8RvPkrLQDngbyU= X-MC-Unique: o_2hRVGHMz-OOlPLfEgsFA-1 From: marcandre.lureau@redhat.com To: qemu-devel@nongnu.org Subject: [PoCv2 06/15] rust: provide a common crate for QEMU Date: Mon, 12 Oct 2020 00:35:04 +0400 Message-Id: <20201011203513.1621355-7-marcandre.lureau@redhat.com> In-Reply-To: <20201011203513.1621355-1-marcandre.lureau@redhat.com> References: <20201011203513.1621355-1-marcandre.lureau@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.15 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=marcandre.lureau@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=63.128.21.124; envelope-from=marcandre.lureau@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-detected-operating-system: by eggs.gnu.org: First seen = 2020/10/11 16:23:51 X-ACL-Warn: Detected OS = Linux 2.2.x-3.x [generic] [fuzzy] X-Spam_score_int: -30 X-Spam_score: -3.1 X-Spam_bar: --- X-Spam_report: (-3.1 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.001, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H5=-1, RCVD_IN_MSPIKE_WL=-0.01, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: pbonzini@redhat.com, berrange@redhat.com, armbru@redhat.com, stefanha@redhat.com, =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: fail (Header signature does not verify) From: Marc-Andr=C3=A9 Lureau 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=C3=A9 Lureau --- 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 =3D ["qga"] +members =3D [ + "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 =3D "common" +version =3D "0.1.0" +edition =3D "2018" +license =3D "GPLv2" + +[dependencies] +libc =3D "^0.2.76" + +[target."cfg(unix)".dependencies] +nix =3D "^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 =3D std::result::Result; + +impl Error { + fn message(&self) -> String { + use Error::*; + match self { + FailedAt(msg, _, _) =3D> msg.into(), + Io(io) =3D> format!("IO error: {}", io), + #[cfg(unix)] + Nix(nix) =3D> format!("Nix error: {}", nix), + } + } + + fn location(&self) -> Option<(&'static str, u32)> { + use Error::*; + match self { + FailedAt(_, file, line) =3D> Some((file, *line)), + Io(_) =3D> None, + #[cfg(unix)] + Nix(_) =3D> None, + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use Error::*; + match self { + FailedAt(msg, file, line) =3D> write!(f, "{} ({}:{})", msg, fi= le, line), + _ =3D> write!(f, "{}", self.message()), + } + } +} + +impl From for Error { + fn from(val: io::Error) -> Self { + Error::Io(val) + } +} + +#[cfg(unix)] +impl From for Error { + fn from(val: nix::Error) -> Self { + Error::Nix(val) + } +} + +impl QemuPtrDefault for Error { + type QemuType =3D *mut sys::Error; +} + +impl<'a> ToQemuPtr<'a, *mut sys::Error> for Error { + type Storage =3D qemu::CError; + + fn to_qemu_none(&'a self) -> Stash<'a, *mut sys::Error, Self> { + let err =3D self.to_qemu_full(); + + Stash(err, unsafe { from_qemu_full(err) }) + } + + fn to_qemu_full(&self) -> *mut sys::Error { + let cmsg =3D + ffi::CString::new(self.message()).expect("ToQemuPtr: un= expected '\0' character"); + let mut csrc =3D ffi::CString::new("").unwrap(); + let (src, line) =3D self.location().map_or((ptr::null(), 0 as i32)= , |loc| { + csrc =3D ffi::CString::new(loc.0).expect("ToQemuPtr:: u= nexpected '\0' character"); + (csrc.as_ptr() as *const libc::c_char, loc.1 as i32) + }); + let func =3D ptr::null(); + + let mut err: *mut sys::Error =3D 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) =3D> { + 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); + +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 =3D sys::error_get_pretty(self.0.as_ptr()); + let bytes =3D 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 defa= ult +//! 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 n= ot need +// and also because there are issues with the conversion traits for our sy= s::*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(ptr: *mut X) -> Self; + fn to(self) -> *mut X; +} + +impl Ptr for *const T { + #[inline] + fn is_null(&self) -> bool { + (*self).is_null() + } + + #[inline] + fn from(ptr: *mut X) -> *const T { + ptr as *const T + } + + #[inline] + fn to(self) -> *mut X { + self as *mut X + } +} + +impl Ptr for *mut T { + #[inline] + fn is_null(&self) -> bool { + (*self).is_null() + } + + #[inline] + fn from(ptr: *mut X) -> *mut T { + ptr as *mut T + } + + #[inline] + fn to(self) -> *mut X { + self as *mut X + } +} + +/// Macro for NewPtr. +/// +/// A macro to declare a newtype for pointers, to workaround that *T are n= ot +/// defined in our binding crates, and allow foreign traits implementation= s. +/// (this is used by qapi-gen bindings) +#[allow(unused_macros)] +#[macro_export] +macro_rules! new_ptr { + () =3D> { + #[derive(Copy, Clone)] + pub struct NewPtr(pub P); + + impl Ptr for NewPtr

{ + #[inline] + fn is_null(&self) -> bool { + self.0.is_null() + } + + #[inline] + fn from(ptr: *mut X) -> Self { + NewPtr(P::from(ptr)) + } + + #[inline] + fn to(self) -> *mut X { + self.0.to() + } + } + }; +} + +/// Provides the default pointer type to be used in some container convers= ions. +/// +/// It's `*mut c_char` for `String`, `*mut sys::GuestInfo` for `GuestInfo`= ... +pub trait QemuPtrDefault { + type QemuType: Ptr; +} + +impl QemuPtrDefault for String { + type QemuType =3D *mut c_char; +} + +pub struct Stash<'a, P: Copy, T: ?Sized + ToQemuPtr<'a, P>>( + pub P, + pub >::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 { + type Storage =3D Option<>::Storage>; + + #[inline] + fn to_qemu_none(&'a self) -> Stash<'a, P, Option> { + self.as_ref() + .map_or(Stash(Ptr::from::<()>(ptr::null_mut()), None), |s| { + let s =3D 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_f= ull) + } +} + +impl<'a> ToQemuPtr<'a, *mut c_char> for String { + type Storage =3D CString; + + #[inline] + fn to_qemu_none(&self) -> Stash<'a, *mut c_char, String> { + let tmp =3D CString::new(&self[..]) + .expect("String::ToQemuPtr<*mut c_char>: unexpected '\0' chara= cter"); + 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: 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: 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>(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>(ptr: P) -> T { + FromQemuPtrFull::from_qemu_full(ptr) +} + +impl> FromQemuPtrNone

for Option { + #[inline] + unsafe fn from_qemu_none(ptr: P) -> Option { + if ptr.is_null() { + None + } else { + Some(from_qemu_none(ptr)) + } + } +} + +impl> FromQemuPtrFull

for Option { + #[inline] + unsafe fn from_qemu_full(ptr: P) -> Option { + 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 =3D from_qemu_none(ptr as *const _); + sys::g_free(ptr as *mut _); + res + } +} + +/// A macro to help the implementation of `Vec -> P` translations. +#[allow(unused_macros)] +#[macro_export] +macro_rules! vec_to_qemu { + ($rs:ident, $sys:ident) =3D> { + #[allow(non_camel_case_types)] + pub struct $sys(*mut qapi_sys::$sys); + + impl Drop for $sys { + fn drop(&mut self) { + let mut list =3D self.0; + unsafe { + while !list.is_null() { + let next =3D (*list).next; + Box::from_raw(list); + list =3D next; + } + } + } + } + + impl<'a> ToQemuPtr<'a, NewPtr<*mut qapi_sys::$sys>> for Vec<$rs> { + type Storage =3D ( + Option<$sys>, + Vec::QemuType, $rs>>, + ); + + #[inline] + fn to_qemu_none(&self) -> Stash, S= elf> { + let stash_vec: Vec<_> =3D self.iter().rev().map(ToQemuPtr:= :to_qemu_none).collect(); + let mut list: *mut qapi_sys::$sys =3D std::ptr::null_mut(); + for stash in &stash_vec { + let b =3D Box::new(qapi_sys::$sys { + next: list, + value: Ptr::to(stash.0), + }); + list =3D 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<_> =3D self.iter().rev().map(ToQemuPtr::to_qemu= _full).collect(); + let mut list: *mut qapi_sys::$sys =3D std::ptr::null_mut(); + unsafe { + for val in v { + let l =3D sys::g_malloc0(std::mem::size_of::()) + as *mut qapi_sys::$sys; + (*l).next =3D list; + (*l).value =3D val; + list =3D l; + } + } + NewPtr(list) + } + } + }; +} + +/// A macro to help the implementation of `P -> Vec` translations. +#[allow(unused_macros)] +#[macro_export] +macro_rules! vec_from_qemu { + ($rs:ident, $sys:ident, $free_sys:ident) =3D> { + impl FromQemuPtrFull> for Vec<$rs> { + #[inline] + unsafe fn from_qemu_full(sys: NewPtr<*mut qapi_sys::$sys>) -> = Self { + let ret =3D from_qemu_none(NewPtr(sys.0 as *const _)); + qapi_sys::$free_sys(sys.0); + ret + } + } + + impl FromQemuPtrNone> for Vec<$rs> { + #[inline] + unsafe fn from_qemu_none(sys: NewPtr<*const qapi_sys::$sys>) -= > Self { + let mut ret =3D vec![]; + let mut it =3D sys.0; + while !it.is_null() { + let e =3D &*it; + ret.push(from_qemu_none(e.value as *const _)); + it =3D e.next; + } + ret + } + } + + impl From> for *mut qapi_sys::$sys { + fn from(p: NewPtr<*mut qapi_sys::$sys>) -> Self { + p.0 + } + } + }; +} --=20 2.28.0