Laszlo Ersek
2023-Jul-27 11:54 UTC
[Libguestfs] [libnbd PATCH 6/7] golang: Improve whitespace style in generated bindings
On 7/26/23 19:50, Eric Blake wrote:> Use Go-preferred whitespace: TAB for indent, and no space before > opening '(', better use of blank lines. These changes reduce the size > of a diff produced by gofmt, although there are still other places > where we are not idiomatic in what we generate (mainly in lining up > columns of = in const() blocks). > > Signed-off-by: Eric Blake <eblake at redhat.com> > --- > generator/GoLang.ml | 244 ++++++++++++++++++++++---------------------- > 1 file changed, 123 insertions(+), 121 deletions(-)Unfortunately: - This must have been an incredible amount of work for you. - The many \t escape sequences make the generator source code harder to read. - This patch depends on patch#4 (minimally), so if you modify that one like I've asked, it will create a conflict for this patch. :( Acked-by: Laszlo Ersek <lersek at redhat.com> Thanks Laszlo> > diff --git a/generator/GoLang.ml b/generator/GoLang.ml > index 77dacadb..a38aa19f 100644 > --- a/generator/GoLang.ml > +++ b/generator/GoLang.ml > @@ -175,6 +175,7 @@ let > let print_binding (name, { args; optargs; ret; shortdesc }) > let cname = camel_case name in > > + pr "\n"; > (* Tedious method of passing optional arguments in golang. *) > if optargs <> [] then ( > pr "/* Struct carrying optional arguments for %s. */\n" cname; > @@ -182,10 +183,10 @@ let > List.iter ( > fun optarg -> > let fname = go_name_of_optarg optarg in > - pr " /* %s field is ignored unless %sSet == true. */\n" > + pr "\t/* %s field is ignored unless %sSet == true. */\n" > fname fname; > - pr " %sSet bool\n" fname; > - pr " %s " fname; > + pr "\t%sSet bool\n" fname; > + pr "\t%s " fname; > (match optarg with > | OClosure { cbname } -> pr "%sCallback" (camel_case cbname) > | OFlags (_, {flag_prefix}, _) -> pr "%s" (camel_case flag_prefix) > @@ -198,7 +199,7 @@ let > > (* Define the golang function which calls the C wrapper. *) > pr "/* %s: %s */\n" cname shortdesc; > - pr "func (h *Libnbd) %s (" cname; > + pr "func (h *Libnbd) %s(" cname; > let comma = ref false in > List.iter ( > fun arg -> > @@ -217,103 +218,103 @@ let > | Some t -> pr "(%s, error)" t > ); > pr " {\n"; > - pr " if h.h == nil {\n"; > + pr "\tif h.h == nil {\n"; > (match go_ret_error ret with > - | None -> pr " return closed_handle_error (\"%s\")\n" name > - | Some v -> pr " return %s, closed_handle_error (\"%s\")\n" v name > + | None -> pr "\t\treturn closed_handle_error(\"%s\")\n" name > + | Some v -> pr "\t\treturn %s, closed_handle_error(\"%s\")\n" v name > ); > - pr " }\n"; > + pr "\t}\n"; > pr "\n"; > - pr " var c_err C.struct_error\n"; > + pr "\tvar c_err C.struct_error\n"; > List.iter ( > function > | Bool n -> > - pr " c_%s := C.bool (%s)\n" n n > + pr "\tc_%s := C.bool(%s)\n" n n > | BytesIn (n, len) -> > - pr " c_%s := unsafe.Pointer (&%s[0])\n" n n; > - pr " c_%s := C.size_t (len (%s))\n" len n; > + pr "\tc_%s := unsafe.Pointer(&%s[0])\n" n n; > + pr "\tc_%s := C.size_t(len(%s))\n" len n; > | BytesOut (n, len) -> > - pr " c_%s := unsafe.Pointer (&%s[0])\n" n n; > - pr " c_%s := C.size_t (len (%s))\n" len n; > + pr "\tc_%s := unsafe.Pointer(&%s[0])\n" n n; > + pr "\tc_%s := C.size_t(len(%s))\n" len n; > | BytesPersistIn (n, len) -> > - pr " c_%s := %s.P\n" n n; > - pr " c_%s := C.size_t (%s.Size)\n" len n; > + pr "\tc_%s := %s.P\n" n n; > + pr "\tc_%s := C.size_t(%s.Size)\n" len n; > | BytesPersistOut (n, len) -> > - pr " c_%s := %s.P\n" n n; > - pr " c_%s := C.size_t (%s.Size)\n" len n; > + pr "\tc_%s := %s.P\n" n n; > + pr "\tc_%s := C.size_t(%s.Size)\n" len n; > | Closure { cbname } -> > - pr " var c_%s C.nbd_%s_callback\n" cbname cbname; > - pr " c_%s.callback = (*[0]byte)(C._nbd_%s_callback_wrapper)\n" > + pr "\tvar c_%s C.nbd_%s_callback\n" cbname cbname; > + pr "\tc_%s.callback = (*[0]byte)(C._nbd_%s_callback_wrapper)\n" > cbname cbname; > - pr " c_%s.free = (*[0]byte)(C._nbd_%s_callback_free)\n" > + pr "\tc_%s.free = (*[0]byte)(C._nbd_%s_callback_free)\n" > cbname cbname; > - pr " %s_cbid := registerCallbackId(%s)\n" cbname cbname; > - pr " c_%s.user_data = C.alloc_cbid(C.long(%s_cbid))\n" cbname cbname > + pr "\t%s_cbid := registerCallbackId(%s)\n" cbname cbname; > + pr "\tc_%s.user_data = C.alloc_cbid(C.long(%s_cbid))\n" cbname cbname > | Enum (n, _) -> > - pr " c_%s := C.int (%s)\n" n n > + pr "\tc_%s := C.int(%s)\n" n n > | Fd n -> > - pr " c_%s := C.int (%s)\n" n n > + pr "\tc_%s := C.int(%s)\n" n n > | Flags (n, _) -> > - pr " c_%s := C.uint32_t (%s)\n" n n > + pr "\tc_%s := C.uint32_t(%s)\n" n n > | Int n -> > - pr " c_%s := C.int (%s)\n" n n > + pr "\tc_%s := C.int(%s)\n" n n > | Int64 n -> > - pr " c_%s := C.int64_t (%s)\n" n n > + pr "\tc_%s := C.int64_t(%s)\n" n n > | Path n -> > - pr " c_%s := C.CString (%s)\n" n n; > - pr " defer C.free (unsafe.Pointer (c_%s))\n" n > + pr "\tc_%s := C.CString(%s)\n" n n; > + pr "\tdefer C.free(unsafe.Pointer(c_%s))\n" n > | SizeT n -> > - pr " c_%s := C.size_t (%s)\n" n n > + pr "\tc_%s := C.size_t(%s)\n" n n > | SockAddrAndLen (n, len) -> > - pr " panic (\"SockAddrAndLen not supported\")\n"; > - pr " var c_%s *C.struct_sockaddr\n" n; > - pr " var c_%s C.uint\n" len > + pr "\tpanic(\"SockAddrAndLen not supported\")\n"; > + pr "\tvar c_%s *C.struct_sockaddr\n" n; > + pr "\tvar c_%s C.uint\n" len > | String n -> > - pr " c_%s := C.CString (%s)\n" n n; > - pr " defer C.free (unsafe.Pointer (c_%s))\n" n > + pr "\tc_%s := C.CString(%s)\n" n n; > + pr "\tdefer C.free(unsafe.Pointer(c_%s))\n" n > | StringList n -> > - pr " c_%s := arg_string_list (%s)\n" n n; > - pr " defer free_string_list (c_%s)\n" n > + pr "\tc_%s := arg_string_list(%s)\n" n n; > + pr "\tdefer free_string_list(c_%s)\n" n > | UInt n -> > - pr " c_%s := C.uint (%s)\n" n n > + pr "\tc_%s := C.uint(%s)\n" n n > | UInt32 n -> > - pr " c_%s := C.uint32_t (%s)\n" n n > + pr "\tc_%s := C.uint32_t(%s)\n" n n > | UInt64 n -> > - pr " c_%s := C.uint64_t (%s)\n" n n > + pr "\tc_%s := C.uint64_t(%s)\n" n n > | UIntPtr n -> > - pr " c_%s := C.uintptr_t (%s)\n" n n > + pr "\tc_%s := C.uintptr_t(%s)\n" n n > ) args; > if optargs <> [] then ( > List.iter ( > function > - | OClosure { cbname } -> pr " var c_%s C.nbd_%s_callback\n" > + | OClosure { cbname } -> pr "\tvar c_%s C.nbd_%s_callback\n" > cbname cbname > - | OFlags (n, _, _) -> pr " var c_%s C.uint32_t\n" n > + | OFlags (n, _, _) -> pr "\tvar c_%s C.uint32_t\n" n > ) optargs; > - pr " if optargs != nil {\n"; > + pr "\tif optargs != nil {\n"; > List.iter ( > fun optarg -> > - pr " if optargs.%sSet {\n" (go_name_of_optarg optarg); > + pr "\t\tif optargs.%sSet {\n" (go_name_of_optarg optarg); > (match optarg with > | OClosure { cbname } -> > - pr " c_%s.callback = (*[0]byte)(C._nbd_%s_callback_wrapper)\n" > + pr "\t\t\tc_%s.callback = (*[0]byte)(C._nbd_%s_callback_wrapper)\n" > cbname cbname; > - pr " c_%s.free = (*[0]byte)(C._nbd_%s_callback_free)\n" > + pr "\t\t\tc_%s.free = (*[0]byte)(C._nbd_%s_callback_free)\n" > cbname cbname; > - pr " %s_cbid := registerCallbackId(optargs.%s)\n" > + pr "\t\t\t%s_cbid := registerCallbackId(optargs.%s)\n" > cbname (go_name_of_optarg optarg); > - pr " c_%s.user_data = C.alloc_cbid(C.long(%s_cbid))\n" > + pr "\t\t\tc_%s.user_data = C.alloc_cbid(C.long(%s_cbid))\n" > cbname cbname > | OFlags (n, _, _) -> > - pr " c_%s = C.uint32_t (optargs.%s)\n" > + pr "\t\t\tc_%s = C.uint32_t(optargs.%s)\n" > n (go_name_of_optarg optarg); > ); > - pr " }\n"; > + pr "\t\t}\n"; > ) optargs; > - pr " }\n"; > + pr "\t}\n"; > ); > pr "\n"; > - pr " ret := C._nbd_%s_wrapper (&c_err, h.h" name; > + pr "\tret := C._nbd_%s_wrapper(&c_err, h.h" name; > List.iter ( > function > | Bool n -> pr ", c_%s" n > @@ -347,57 +348,56 @@ let > * function has completed, in case all other references > * to the handle have disappeared and the finalizer would run. > *) > - pr " runtime.KeepAlive (h.h)\n"; > + pr "\truntime.KeepAlive(h.h)\n"; > > let errcode = go_ret_c_errcode ret in > (match errcode with > | None -> () > | Some errcode -> > - pr " if ret == %s {\n" errcode; > - pr " err := get_error (\"%s\", c_err)\n" name; > - pr " C.free_error (&c_err)\n"; > + pr "\tif ret == %s {\n" errcode; > + pr "\t\terr := get_error(\"%s\", c_err)\n" name; > + pr "\t\tC.free_error(&c_err)\n"; > (match go_ret_error ret with > - | None -> pr " return err\n" > - | Some v -> pr " return %s, err\n" v > + | None -> pr "\t\treturn err\n" > + | Some v -> pr "\t\treturn %s, err\n" v > ); > - pr " }\n"; > + pr "\t}\n"; > ); > (match ret with > | RErr -> > - pr " return nil\n" > + pr "\treturn nil\n" > | RBool -> > - pr " return int (ret) != 0, nil\n" > + pr "\treturn int(ret) != 0, nil\n" > | RStaticString -> > - pr " /* ret is statically allocated, do not free it. */\n"; > - pr " r := C.GoString (ret);\n"; > - pr " return &r, nil\n" > + pr "\t/* ret is statically allocated, do not free it. */\n"; > + pr "\tr := C.GoString(ret)\n"; > + pr "\treturn &r, nil\n" > | RFd -> > - pr " return int (ret), nil\n" > + pr "\treturn int(ret), nil\n" > | RInt -> > - pr " return uint (ret), nil\n" > + pr "\treturn uint(ret), nil\n" > | RInt64 -> > - pr " return uint64 (ret), nil\n" > + pr "\treturn uint64(ret), nil\n" > | RCookie -> > - pr " return uint64 (ret), nil\n" > + pr "\treturn uint64(ret), nil\n" > | RSizeT -> > - pr " return uint (ret), nil\n" > + pr "\treturn uint(ret), nil\n" > | RString -> > - pr " r := C.GoString (ret)\n"; > - pr " C.free (unsafe.Pointer (ret))\n"; > - pr " return &r, nil\n" > + pr "\tr := C.GoString(ret)\n"; > + pr "\tC.free(unsafe.Pointer(ret))\n"; > + pr "\treturn &r, nil\n" > | RUInt -> > - pr " return uint (ret), nil\n" > + pr "\treturn uint(ret), nil\n" > | RUIntPtr -> > - pr " return uint (ret), nil\n" > + pr "\treturn uint(ret), nil\n" > | RUInt64 -> > - pr " return uint64 (ret), nil\n" > + pr "\treturn uint64(ret), nil\n" > | REnum { enum_prefix } -> > - pr " return %s (ret), nil\n" (camel_case enum_prefix) > + pr "\treturn %s(ret), nil\n" (camel_case enum_prefix) > | RFlags { flag_prefix } -> > - pr " return %s (ret), nil\n" (camel_case flag_prefix) > + pr "\treturn %s(ret), nil\n" (camel_case flag_prefix) > ); > - pr "}\n"; > - pr "\n" > + pr "}\n" > > let generate_golang_bindings_go () > generate_header CStyle; > @@ -422,8 +422,8 @@ let > import \"C\" > > import ( > - \"runtime\" > - \"unsafe\" > +\t\"runtime\" > +\t\"unsafe\" > ) > > /* Enums. */ > @@ -431,10 +431,11 @@ let > List.iter ( > fun { enum_prefix; enums } -> > pr "type %s int\n" (camel_case enum_prefix); > + pr "\n"; > pr "const (\n"; > List.iter ( > fun (enum, v) -> > - pr " %s_%s = %s(%d)\n" enum_prefix enum (camel_case enum_prefix) v > + pr "\t%s_%s = %s(%d)\n" enum_prefix enum (camel_case enum_prefix) v > ) enums; > pr ")\n"; > pr "\n" > @@ -448,13 +449,14 @@ let > let flag_type = camel_case flag_prefix in > let mask = ref 0 in > pr "type %s uint32\n" flag_type; > + pr "\n"; > pr "const (\n"; > List.iter ( > fun (flag, v) -> > - pr " %s_%s = %s(0x%02x)\n" flag_prefix flag flag_type v; > + pr "\t%s_%s = %s(0x%02x)\n" flag_prefix flag flag_type v; > mask := !mask lor v > ) flags; > - pr " %s_MASK = %s(0x%02x)\n" flag_prefix flag_type !mask; > + pr "\t%s_MASK = %s(0x%02x)\n" flag_prefix flag_type !mask; > pr ")\n"; > pr "\n" > ) all_flags; > @@ -464,26 +466,26 @@ let > const ( > "; > List.iter ( > - fun (n, v) -> pr " %s uint32 = %d\n" n v > + fun (n, v) -> pr "\t%s uint32 = %d\n" n v > ) constants; > List.iter ( > fun (ns, ctxts) -> > let ns_upper = String.uppercase_ascii ns in > - pr " /* Meta-context namespace \"%s\" */\n" ns; > - pr " NAMESPACE_%s = \"%s:\"\n" ns_upper ns; > + pr "\t/* Meta-context namespace \"%s\" */\n" ns; > + pr "\tNAMESPACE_%s = \"%s:\"\n" ns_upper ns; > List.iter ( > fun (ctxt, consts) -> > let ctxt_macro = String.uppercase_ascii (macro_name ctxt) in > - pr " CONTEXT_%s_%s = \"%s:%s\"\n" ns_upper ctxt_macro ns ctxt; > + pr "\tCONTEXT_%s_%s = \"%s:%s\"\n" ns_upper ctxt_macro ns ctxt; > if consts <> [] then > - pr " /* Defined bits in \"%s:%s\" */\n" ns ctxt; > + pr "\t/* Defined bits in \"%s:%s\" */\n" ns ctxt; > List.iter (fun (n, v) -> > - pr " %s uint32 = %d\n" n v > + pr "\t%s uint32 = %d\n" n v > ) consts > ) ctxts; > ) metadata_namespaces; > > - pr ")\n\n"; > + pr ")\n"; > > (* Bindings. *) > List.iter print_binding handle_calls > @@ -510,21 +512,22 @@ let > /* Closures. */ > > func copy_uint32_array(entries *C.uint32_t, count C.size_t) []uint32 { > - ret := make([]uint32, int(count)) > - addr := uintptr(unsafe.Pointer(entries)) > - for i := 0; i < int(count); i++ { > - ptr := (*C.uint32_t)(unsafe.Pointer(addr)) > - ret[i] = uint32(*ptr) > - addr += unsafe.Sizeof(*ptr) > - } > - return ret > +\tret := make([]uint32, int(count)) > +\taddr := uintptr(unsafe.Pointer(entries)) > +\tfor i := 0; i < int(count); i++ { > +\t\tptr := (*C.uint32_t)(unsafe.Pointer(addr)) > +\t\tret[i] = uint32(*ptr) > +\t\taddr += unsafe.Sizeof(*ptr) > +\t} > +\treturn ret > } > "; > > List.iter ( > fun { cbname; cbargs } -> > let uname = camel_case cbname in > - pr "type %sCallback func (" uname; > + pr "\n"; > + pr "type %sCallback func(" uname; > let comma = ref false in > List.iter ( > fun cbarg -> > @@ -551,7 +554,7 @@ let > pr ") int\n"; > pr "\n"; > pr "//export %s_callback\n" cbname; > - pr "func %s_callback (callbackid *C.long" cbname; > + pr "func %s_callback(callbackid *C.long" cbname; > List.iter ( > fun cbarg -> > pr ", "; > @@ -575,11 +578,11 @@ let > | CBArrayAndLen _ | CBMutable _ -> assert false > ) cbargs; > pr ") C.int {\n"; > - pr " callbackFunc := getCallbackId (int (*callbackid));\n"; > - pr " callback, ok := callbackFunc.(%sCallback);\n" uname; > - pr " if !ok {\n"; > - pr " panic (\"inappropriate callback type\");\n"; > - pr " }\n"; > + pr "\tcallbackFunc := getCallbackId(int(*callbackid))\n"; > + pr "\tcallback, ok := callbackFunc.(%sCallback)\n" uname; > + pr "\tif !ok {\n"; > + pr "\t\tpanic(\"inappropriate callback type\")\n"; > + pr "\t}\n"; > > (* Deal with mutable int by creating a local variable > * and passing a pointer to it to the callback. > @@ -588,30 +591,30 @@ let > fun cbarg -> > match cbarg with > | CBMutable (Int n) -> > - pr " go_%s := int (*%s)\n" n n > + pr "\tgo_%s := int(*%s)\n" n n > | _ -> () > ) cbargs; > > - pr " ret := callback ("; > + pr "\tret := callback("; > let comma = ref false in > List.iter ( > fun cbarg -> > if !comma then pr ", "; comma := true; > match cbarg with > | CBArrayAndLen (UInt32 n, count) -> > - pr "copy_uint32_array (%s, %s)" n count > + pr "copy_uint32_array(%s, %s)" n count > | CBBytesIn (n, len) -> > - pr "C.GoBytes (%s, C.int (%s))" n len > + pr "C.GoBytes(%s, C.int(%s))" n len > | CBInt n -> > - pr "int (%s)" n > + pr "int(%s)" n > | CBUInt n -> > - pr "uint (%s)" n > + pr "uint(%s)" n > | CBInt64 n -> > - pr "int64 (%s)" n > + pr "int64(%s)" n > | CBString n -> > - pr "C.GoString (%s)" n > + pr "C.GoString(%s)" n > | CBUInt64 n -> > - pr "uint64 (%s)" n > + pr "uint64(%s)" n > | CBMutable (Int n) -> > pr "&go_%s" n > | CBArrayAndLen _ | CBMutable _ -> assert false > @@ -622,12 +625,11 @@ let > fun cbarg -> > match cbarg with > | CBMutable (Int n) -> > - pr " *%s = C.int (go_%s)\n" n n > + pr "\t*%s = C.int(go_%s)\n" n n > | _ -> () > ) cbargs; > - pr " return C.int (ret);\n"; > - pr "}\n"; > - pr "\n" > + pr "\treturn C.int(ret)\n"; > + pr "}\n" > ) all_closures > > let generate_golang_wrappers_go () =
Eric Blake
2023-Jul-27 17:27 UTC
[Libguestfs] [libnbd PATCH 6/7] golang: Improve whitespace style in generated bindings
On Thu, Jul 27, 2023 at 01:54:25PM +0200, Laszlo Ersek wrote:> On 7/26/23 19:50, Eric Blake wrote: > > Use Go-preferred whitespace: TAB for indent, and no space before > > opening '(', better use of blank lines. These changes reduce the size > > of a diff produced by gofmt, although there are still other places > > where we are not idiomatic in what we generate (mainly in lining up > > columns of = in const() blocks). > > > > Signed-off-by: Eric Blake <eblake at redhat.com> > > --- > > generator/GoLang.ml | 244 ++++++++++++++++++++++---------------------- > > 1 file changed, 123 insertions(+), 121 deletions(-) > > Unfortunately: > > - This must have been an incredible amount of work for you.Some automation involved (emacs macros are nice), but yeah, it's grunt-work.> > - The many \t escape sequences make the generator source code harder to > read.Agreed. Maybe a better approach would be figuring out an optional argument to pr to insist on TAB instead of space indent? Or, as mentioned elsewhere, not bothering with TAB at all in OCaml but instead figuring out how to run gofmt itself as part of the generation pipeline.> > - This patch depends on patch#4 (minimally), so if you modify that one > like I've asked, it will create a conflict for this patch. :(Yep, I've already encountered a number of conflicts as I shuffle around patches, trying to pick out which changes are more than just whitespace (such as the RBool change). Not terribly difficult, but back in the grunt-work category.> > Acked-by: Laszlo Ersek <lersek at redhat.com> > > Thanks > Laszlo > > > > > diff --git a/generator/GoLang.ml b/generator/GoLang.ml > > index 77dacadb..a38aa19f 100644 > > --- a/generator/GoLang.ml > > +++ b/generator/GoLang.ml > > @@ -175,6 +175,7 @@ let > > let print_binding (name, { args; optargs; ret; shortdesc }) > > let cname = camel_case name in > > > > + pr "\n"; > > (* Tedious method of passing optional arguments in golang. *) > > if optargs <> [] then ( > > pr "/* Struct carrying optional arguments for %s. */\n" cname; > > @@ -182,10 +183,10 @@ let > > List.iter ( > > fun optarg -> > > let fname = go_name_of_optarg optarg in > > - pr " /* %s field is ignored unless %sSet == true. */\n" > > + pr "\t/* %s field is ignored unless %sSet == true. */\n" > > fname fname;I tried: + pr "\t" ^ "/* %s field is ignored unless %sSet == true. */\n" fname fname; File "GoLang.ml", line 186, characters 8-15: 186 | pr "\t" ^ "/* %s field is ignored unless %sSet == true. */\n" ^^^^^^^ Error: This expression has type unit but an expression was expected of type string and: File "GoLang.ml", line 186, characters 11-71: 186 | pr ("\t" ^ "/* %s field is ignored unless %sSet == true. */\n") ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Error: This expression has type string but an expression was expected of type ('weak2 -> 'weak3 -> 'weak4, unit, string, unit) format4 ('weak2 -> 'weak3 -> 'weak4, unit, string, string, string, unit) CamlinternalFormatBasics.format6 so those are non-starters. We can get more verbose with: + pr "\t"; + pr "/* %s field is ignored unless %sSet == true. */\n" fname fname; but that may not scale as nicely. Go's use of TAB does not specify what tabstop it prefers; our .editorconfig suggests using a tabstop of 4 (instead of the usual 8) when reading a .go file for less visual distraction, and which matches with GoLang.ml currently using 4-space indentation. Rich, do you have any ideas on any better approach to take here?> > - pr " %sSet bool\n" fname; > > - pr " %s " fname; > > + pr "\t%sSet bool\n" fname; > > + pr "\t%s " fname;I also debated about splitting this patch into two (yes, more gruntwork): one to address space before '(', and the other to address leading indentation. Lines like this would be touched by both patches if I split. -- Eric Blake, Principal Software Engineer Red Hat, Inc. Virtualization: qemu.org | libguestfs.org