--- generator/GoLang.ml | 336 +++++++++++++++++- .../libnbd/libnbd_200_connect_command_test.go | 35 ++ 2 files changed, 367 insertions(+), 4 deletions(-) diff --git a/generator/GoLang.ml b/generator/GoLang.ml index 1b8f20b..e5fb97c 100644 --- a/generator/GoLang.ml +++ b/generator/GoLang.ml @@ -23,6 +23,304 @@ open Printf open API open Utils +let go_name_of_arg = function + | Bool n -> n + | BytesIn (n, len) -> n + | BytesOut (n, len) -> n + | BytesPersistIn (n, len) -> n + | BytesPersistOut (n, len) -> n + | Closure { cbname } -> cbname + | Enum (n, _) -> n + | Fd n -> n + | Flags (n, _) -> n + | Int n -> n + | Int64 n -> n + | Path n -> n + | SockAddrAndLen (n, len) -> n + | String n -> n + | StringList n -> n + | UInt n -> n + | UInt32 n -> n + | UInt64 n -> n + +let go_arg_type_to_string = function + | Bool _ -> "bool" + | BytesIn _ -> "*string/*XXX*/" + | BytesPersistIn _ -> "*string/*XXX*/" + | BytesOut _ -> "*string/*XXX*/" + | BytesPersistOut _ -> "*string/*XXX*/" + | Closure { cbargs } -> "func (/*XXX*/)" + | Enum _ -> "uint32" + | Fd _ -> "int/*XXX*/" + | Flags _ -> "uint32" + | Int _ -> "int" + | Int64 _ -> "int64" + | Path _ -> "string" + | SockAddrAndLen _ -> "string" + | String _ -> "string" + | StringList _ -> "[]string" + | UInt _ -> "uint" + | UInt32 _ -> "uint32" + | UInt64 _ -> "uint64" + +let go_name_of_optarg = function + | OClosure { cbname } -> String.capitalize_ascii cbname + | OFlags (n, _) -> String.capitalize_ascii n + +(* For consistency every method will return (type, error) where + * type is defined by this function. However some functions + * never really have a type (RErr) and others never really have + * an error (RUInt). + *) +let go_ret_type = function + | RBool -> "bool" + | RStaticString -> "*string" + | RErr -> "int" (* always 0 *) + | RFd -> "int/*XXX*/" + | RInt -> "int" + | RInt64 -> "int64" + | RCookie -> "int64" + | RString -> "*string" + | RUInt -> "uint" + +let go_ret_error = function + | RBool -> "false" + | RStaticString -> "nil" + | RErr -> "0" + | RFd -> "0/*XXX*/" + | RInt -> "0" + | RInt64 -> "0" + | RCookie -> "0" + | RString -> "nil" + | RUInt -> "0" + +let go_ret_c_errcode = function + | RBool -> Some "-1" + | RStaticString -> Some "nil" + | RErr -> Some "-1" + | RFd -> Some "-1/*XXX*/" + | RInt -> Some "-1" + | RInt64 -> Some "-1" + | RCookie -> Some "-1" + | RString -> Some "nil" + | RUInt -> None + +(* We need a wrapper around every function (except Close) to + * handle errors because cgo calls are sequence points and + * could result in us being rescheduled on another thread, + * but libnbd error handling means we must call nbd_get_error + * etc from the same thread. + *) +let print_wrapper (name, { args; optargs; ret }) = + let ret_c_type = C.type_of_ret ret and errcode = C.errcode_of_ret ret in + pr "static %s\n" ret_c_type; + pr "_nbd_%s_wrapper (struct error *err,\n" name; + pr " "; + C.print_arg_list ~wrap:true ~handle:true ~parens:false args optargs; + pr ")\n"; + pr "{\n"; + pr " %s ret;\n" ret_c_type; + pr "\n"; + pr " ret = nbd_%s " name; + C.print_arg_list ~wrap:true ~handle:true ~types:false args optargs; + pr ";\n"; + (match errcode with + | None -> () + | Some errcode -> + pr " if (ret == %s)\n" errcode; + pr " save_error (err);\n"; + ); + pr " return ret;\n"; + pr "}\n"; + pr "\n" + +let print_binding (name, { args; optargs; ret; shortdesc }) = + let uname = String.capitalize_ascii name in + + (* Tedious method of passing optional arguments in golang. *) + if optargs <> [] then ( + pr "/* Struct carrying optional arguments for %s. */\n" uname; + pr "type Optargs_%s struct {\n" uname; + List.iter ( + fun optarg -> + let fname = go_name_of_optarg optarg in + pr " /* %s field is ignored unless %s_is_set == true. */\n" + fname fname; + pr " %s_is_set bool\n" fname; + pr " %s " fname; + (match optarg with + | OClosure { cbargs } -> pr "func (/*XXX*/)" + | OFlags _ -> pr "uint32" + ); + pr "\n" + ) optargs; + pr "}\n"; + pr "\n"; + ); + + (* Define the golang function which calls the C wrapper. *) + pr "/* %s: %s */\n" uname shortdesc; + pr "func (h *Libnbd) %s (" uname; + let comma = ref false in + List.iter ( + fun arg -> + if !comma then pr ", "; + comma := true; + pr "%s %s" (go_name_of_arg arg) (go_arg_type_to_string arg) + ) args; + if optargs <> [] then ( + if !comma then pr ", "; + comma := true; + pr "optargs *Optargs_%s" uname + ); + pr ") (%s, error)" (go_ret_type ret); + pr " {\n"; + pr " if h.h == nil {\n"; + pr " return %s, closed_handle_error (\"%s\")\n" + (go_ret_error ret) name; + pr " }\n"; + pr "\n"; + pr " c_err := C.struct_error{}\n"; + List.iter ( + function + | Bool n -> + pr " c_%s := C.bool (%s)\n" n n + | BytesIn (n, len) -> + pr " var c_%s unsafe.Pointer /* XXX */\n" n; + pr " var c_%s C.size_t /* XXX */\n" len; + | BytesOut (n, len) -> + pr " var c_%s unsafe.Pointer /* XXX */\n" n; + pr " var c_%s C.size_t /* XXX */\n" len; + | BytesPersistIn (n, len) -> + pr " var c_%s unsafe.Pointer /* XXX */\n" n; + pr " var c_%s C.size_t /* XXX */\n" len; + | BytesPersistOut (n, len) -> + pr " var c_%s unsafe.Pointer /* XXX */\n" n; + pr " var c_%s C.size_t /* XXX */\n" len; + | Closure { cbname } -> + pr " c_%s := C.nbd_%s_callback{} /* XXX */\n" cbname cbname + | Enum (n, _) -> + pr " c_%s := C.int (%s)\n" n n + | Fd n -> + pr " c_%s := C.int (%s) /* XXX */\n" n n + | Flags (n, _) -> + pr " c_%s := C.uint32_t (%s)\n" n n + | Int n -> + pr " c_%s := C.int (%s)\n" n n + | Int64 n -> + pr " c_%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 + | 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 + | String n -> + pr " c_%s := C.CString (%s)\n" n n; + pr " defer 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 + | UInt n -> + pr " c_%s := C.uint (%s)\n" n n + | UInt32 n -> + pr " c_%s := C.uint32_t (%s)\n" n n + | UInt64 n -> + pr " c_%s := C.uint64_t (%s)\n" n n + ) args; + if optargs <> [] then ( + List.iter ( + function + | OClosure { cbname } -> + pr " c_%s := C.nbd_%s_callback{} /* XXX */\n" cbname cbname + | OFlags (n, _) -> + pr " var c_%s C.uint32_t\n" n + ) optargs; + pr " if optargs != nil {\n"; + List.iter ( + fun optarg -> + pr " if optargs.%s_is_set {\n" (go_name_of_optarg optarg); + (match optarg with + | OClosure { cbname } -> () (* XXX *) + | OFlags (n, _) -> + pr " c_%s = C.uint32_t (optargs.%s)\n" + n (go_name_of_optarg optarg); + ); + pr " }\n"; + ) optargs; + pr " }\n"; + ); + pr "\n"; + pr " ret := C._nbd_%s_wrapper (&c_err, h.h" name; + List.iter ( + function + | Bool n -> pr ", c_%s" n + | BytesIn (n, len) -> pr ", c_%s, c_%s" n len + | BytesOut (n, len) -> pr ", c_%s, c_%s" n len + | BytesPersistIn (n, len) -> pr ", c_%s, c_%s" n len + | BytesPersistOut (n, len) -> pr ", c_%s, c_%s" n len + | Closure { cbname } -> pr ", c_%s" cbname + | Enum (n, _) -> pr ", c_%s" n + | Fd n -> pr ", c_%s" n + | Flags (n, _) -> pr ", c_%s" n + | Int n -> pr ", c_%s" n + | Int64 n -> pr ", c_%s" n + | Path n -> pr ", c_%s" n + | SockAddrAndLen (n, len) -> pr ", c_%s, c_%s" n len + | String n -> pr ", c_%s" n + | StringList n -> pr ", c_%s" n + | UInt n -> pr ", c_%s" n + | UInt32 n -> pr ", c_%s" n + | UInt64 n -> pr ", c_%s" n + ) args; + List.iter ( + function + | OClosure { cbname} -> pr ", c_%s" cbname + | OFlags (n, _) -> pr ", c_%s" n + ) optargs; + pr ")\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 " return %s, err\n" (go_ret_error ret); + pr " }\n"; + ); + (match ret with + | RBool -> + pr " r := int (ret)\n"; + pr " if r != 0 { return true, nil } else { return false, nil }\n" + | RStaticString -> + pr " /* ret is statically allocated, do not free it. */\n"; + pr " r := C.GoString (ret);\n"; + pr " return &r, nil\n" + | RErr -> + pr " /* Note that the return value 0 has no meaning here.\n"; + pr " * It is only returned for consistency.\n"; + pr " */\n"; + pr " return 0, nil\n" + | RFd -> + pr " return int/*XXX*/ (ret), nil\n" + | RInt -> + pr " return int (ret), nil\n" + | RInt64 -> + pr " return int64 (ret), nil\n" + | RCookie -> + pr " return int64 (ret), nil\n" + | RString -> + pr " r := C.GoString (ret)\n"; + pr " C.free (unsafe.Pointer (ret))\n"; + pr " return &r, nil\n" + | RUInt -> + pr " return uint (ret), nil\n" + ); + pr "}\n"; + pr "\n" + let generate_golang_libnbd_go () = generate_header CStyle; @@ -62,14 +360,20 @@ free_error (struct error *err) static struct nbd_handle * _nbd_create_wrapper (struct error *err) { - struct nbd_handle *r; + struct nbd_handle *ret; - r = nbd_create (); - if (r == NULL) + ret = nbd_create (); + if (ret == NULL) save_error (err); - return r; + return ret; } +"; + + (* Wrappers. *) + List.iter print_wrapper handle_calls; + + pr "\ // There must be no blank line between end comment and import! // https://github.com/golang/go/issues/9733 */ @@ -79,6 +383,7 @@ import ( \"fmt\" \"runtime\" \"syscall\" + \"unsafe\" ) /* Handle. */ @@ -147,4 +452,27 @@ func (h *Libnbd) Close () *LibnbdError { return nil } +/* Functions for translating between NULL-terminated lists of + * C strings and golang []string. + */ +func arg_string_list (xs []string) **C.char { + r := make ([]*C.char, 1 + len (xs)) + for i, x := range xs { + r[i] = C.CString (x) + } + r[len (xs)] = nil + return &r[0] +} + +func free_string_list (argv **C.char) { + for *argv != nil { + //C.free (*argv) + argv = (**C.char) (unsafe.Pointer (uintptr (unsafe.Pointer (argv)) + + unsafe.Sizeof (*argv))) + } +} + "; + + (* Bindings. *) + List.iter print_binding handle_calls; diff --git a/golang/src/libguestfs.org/libnbd/libnbd_200_connect_command_test.go b/golang/src/libguestfs.org/libnbd/libnbd_200_connect_command_test.go new file mode 100644 index 0000000..9faf244 --- /dev/null +++ b/golang/src/libguestfs.org/libnbd/libnbd_200_connect_command_test.go @@ -0,0 +1,35 @@ +/* libnbd golang tests + * Copyright (C) 2013-2020 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +package libnbd + +import "testing" + +func Test200ConnectCommand (t *testing.T) { + h, err := Create () + if err != nil { + t.Errorf ("could not create handle: %s", err) + } + _, err = h.Connect_command ([]string{ + "nbdkit", "-s", "--exit-with-parent", "-v", "null", + }) + if err != nil { + t.Errorf ("could not connect: %s", err) + } + h.Close () +} -- 2.25.0