Eric Blake
2023-May-25 13:00 UTC
[Libguestfs] [libnbd PATCH v3 07/22] generator: Add struct nbd_extent in prep for 64-bit extents
The existing nbd_block_status() callback is permanently stuck with an array of uint32_t pairs (len/2 extents), which is both constrained on maximum extent size (no larger than 4G) and on the status flags (must fit in 32 bits). While the "base:allocation" metacontext will never exceed 32 bits, it is not hard to envision other extension metacontexts where a 64-bit status would be useful (for example, Zoned Block Devices expressing a 64-bit offset[1]). Exposing 64-bit extents will require a new API; we now have the decision of whether to copy the existing API practice of returning a bare array containing len/2 extent pairs, or with a saner idea of an array of structs with len extents. Returning an array of structs is actually easier to map to various language bindings, particularly since we know the length field will generally fit in 63-bits (fallout from the fact that NBD exports are effectively capped in size by signed off_t), but where the status field must represent a full 64 bits (assuming that the user wants to connect to a metadata extension that utilizes 64 bits, rather than existing metadata contexts that only expose 32 bits). This patch introduces the struct we plan to use in the new API, along with language bindings. The bindings for Python and OCaml were relatively straightforward; the Golang bindings took a bit more effort for me to write. Temporary unused attributes are needed to keep the compiler happy until a later patch exposes a new API using the new callback type. Note that 'struct nbd_block_descriptor_ext' in lib/nbd-protocol.h is exactly the same as what we want to use in C. But it is easier to stick a new public type in <libnbd.h> than to figure out how to expose just part of a header we only want to use internally. [1] https://zonedstorage.io/docs/linux/zbd-api Signed-off-by: Eric Blake <eblake at redhat.com> --- generator/API.mli | 1 + generator/API.ml | 12 +++++++++++- generator/C.ml | 24 +++++++++++++++++++++--- generator/GoLang.ml | 24 ++++++++++++++++++++++++ generator/OCaml.ml | 24 +++++++++++++++++++++--- generator/Python.ml | 21 ++++++++++++++++++++- ocaml/helpers.c | 20 ++++++++++++++++++++ ocaml/nbd-c.h | 1 + golang/handle.go | 6 ++++++ 9 files changed, 125 insertions(+), 8 deletions(-) diff --git a/generator/API.mli b/generator/API.mli index c5bba8c2..96626c9b 100644 --- a/generator/API.mli +++ b/generator/API.mli @@ -52,6 +52,7 @@ and | BytesPersistOut of string * string | Closure of closure (** function pointer + void *opaque *) | Enum of string * enum (** enum/union type, int in C *) +| Extent64 of string (** extent descriptor, struct nbd_extent in C *) | Fd of string (** file descriptor *) | Flags of string * flags (** flags, uint32_t in C *) | Int of string (** small int *) diff --git a/generator/API.ml b/generator/API.ml index 02c1260d..1da1ec53 100644 --- a/generator/API.ml +++ b/generator/API.ml @@ -42,6 +42,7 @@ | BytesPersistOut of string * string | Closure of closure | Enum of string * enum +| Extent64 of string | Fd of string | Flags of string * flags | Int of string @@ -157,6 +158,14 @@ let extent_closure "nr_entries"); CBMutable (Int "error") ] } +let extent64_closure = { + cbname = "extent64"; + cbargs = [ CBString "metacontext"; + CBUInt64 "offset"; + CBArrayAndLen (Extent64 "entries", + "nr_entries"); + CBMutable (Int "error") ] +} let list_closure = { cbname = "list"; cbargs = [ CBString "name"; CBString "description" ] @@ -166,7 +175,8 @@ let context_closure cbargs = [ CBString "name" ] } let all_closures = [ chunk_closure; completion_closure; - debug_closure; extent_closure; list_closure; + debug_closure; extent_closure; extent64_closure; + list_closure; context_closure ] (* Enums. *) diff --git a/generator/C.ml b/generator/C.ml index f772117c..d8472153 100644 --- a/generator/C.ml +++ b/generator/C.ml @@ -94,6 +94,7 @@ let | Closure { cbname } -> [ sprintf "%s_callback" cbname; sprintf "%s_user_data" cbname ] | Enum (n, _) -> [n] + | Extent64 n -> [n] | Fd n -> [n] | Flags (n, _) -> [n] | Int n -> [n] @@ -129,7 +130,7 @@ let (* list of strings should be marked as non-null *) | StringList n -> [ true ] (* numeric and other non-pointer types are not able to be null *) - | Bool _ | Closure _ | Enum _ | Fd _ | Flags _ + | Bool _ | Closure _ | Enum _ | Extent64 _ | Fd _ | Flags _ | Int _ | Int64 _ | SizeT _ | UInt _ | UInt32 _ | UInt64 _ | UIntPtr _ -> [ false ] @@ -191,6 +192,9 @@ and | Enum (n, _) -> if types then pr "int "; pr "%s" n + | Extent64 n -> + if types then pr "nbd_extent "; + pr "%s" n | Flags (n, _) -> if types then pr "uint32_t "; pr "%s" n @@ -305,6 +309,11 @@ and pr "%s, " n; if types then pr "size_t "; pr "%s" len + | CBArrayAndLen (Extent64 n, len) -> + if types then pr "nbd_extent *"; + pr "%s, " n; + if types then pr "size_t "; + pr "%s" len | CBArrayAndLen _ -> assert false | CBBytesIn (n, len) -> if types then pr "const void *"; @@ -477,6 +486,13 @@ let pr "extern int nbd_get_errno (void);\n"; pr "#define LIBNBD_HAVE_NBD_GET_ERRNO 1\n"; pr "\n"; + pr "/* This is used in the callback for nbd_block_status_64.\n"; + pr " */\n"; + pr "typedef struct {\n"; + pr " uint64_t length;\n"; + pr " uint64_t flags;\n"; + pr "} nbd_extent;\n"; + pr "\n"; print_closure_structs (); List.iter ( fun (name, { args; optargs; ret }) -> @@ -740,7 +756,7 @@ let pr " char *%s_printable =\n" n; pr " nbd_internal_printable_string_list (%s);\n" n | BytesOut _ | BytesPersistOut _ - | Bool _ | Closure _ | Enum _ | Flags _ | Fd _ | Int _ + | Bool _ | Closure _ | Enum _ | Extent64 _ | Flags _ | Fd _ | Int _ | Int64 _ | SizeT _ | SockAddrAndLen _ | UInt _ | UInt32 _ | UInt64 _ | UIntPtr _ -> () ) args; @@ -766,6 +782,7 @@ let pr " %s=\\\"%%s\\\" %s=%%zu" n count | Closure { cbname } -> pr " %s=%%s" cbname | Enum (n, _) -> pr " %s=%%d" n + | Extent64 _ -> assert false (* only used in extent64_closure *) | Flags (n, _) -> pr " %s=0x%%x" n | Fd n | Int n -> pr " %s=%%d" n | Int64 n -> pr " %s=%%\"PRIi64\"" n @@ -804,6 +821,7 @@ let pr "%s_printable ? %s_printable : \"\", %s" n n count | Closure _ -> pr "\"<fun>\"" | Enum (n, _) -> pr "%s" n + | Extent64 _ -> assert false (* only used in extent64_closure *) | Flags (n, _) -> pr "%s" n | Fd n | Int n | Int64 n | SizeT n -> pr "%s" n | SockAddrAndLen (_, len) -> pr "(int) %s" len @@ -836,7 +854,7 @@ let | StringList n -> pr " free (%s_printable);\n" n | BytesOut _ | BytesPersistOut _ - | Bool _ | Closure _ | Enum _ | Flags _ | Fd _ | Int _ + | Bool _ | Closure _ | Enum _ | Extent64 _ | Flags _ | Fd _ | Int _ | Int64 _ | SizeT _ | SockAddrAndLen _ | UInt _ | UInt32 _ | UInt64 _ | UIntPtr _ -> () ) args; diff --git a/generator/GoLang.ml b/generator/GoLang.ml index 4ab6b262..8922812b 100644 --- a/generator/GoLang.ml +++ b/generator/GoLang.ml @@ -49,6 +49,7 @@ let | BytesPersistOut (n, len) -> n | Closure { cbname } -> cbname | Enum (n, _) -> n + | Extent64 n -> n | Fd n -> n | Flags (n, _) -> n | Int n -> n @@ -71,6 +72,7 @@ let | BytesPersistOut _ -> "AioBuffer" | Closure { cbname } -> sprintf "%sCallback" (camel_case cbname) | Enum (_, { enum_prefix }) -> camel_case enum_prefix + | Extent64 _ -> assert false (* only used in extent64_closure *) | Fd _ -> "int" | Flags (_, { flag_prefix }) -> camel_case flag_prefix | Int _ -> "int" @@ -262,6 +264,7 @@ let pr " c_%s.user_data = C.alloc_cbid(C.long(%s_cbid))\n" cbname cbname | Enum (n, _) -> pr " c_%s := C.int (%s)\n" n n + | Extent64 _ -> assert false (* only used in extent64_closure *) | Fd n -> pr " c_%s := C.int (%s)\n" n n | Flags (n, _) -> @@ -334,6 +337,7 @@ let | BytesPersistOut (n, len) -> pr ", c_%s, c_%s" n len | Closure { cbname } -> pr ", c_%s" cbname | Enum (n, _) -> pr ", c_%s" n + | Extent64 _ -> assert false (* only used in extent64_closure *) | Fd n -> pr ", c_%s" n | Flags (n, _) -> pr ", c_%s" n | Int n -> pr ", c_%s" n @@ -524,6 +528,18 @@ let copy(ret, s) return ret } + +func copy_extent_array (entries *C.nbd_extent, count C.size_t) []LibnbdExtent { + unsafePtr := unsafe.Pointer(entries) + arrayPtr := (*[1 << 20]C.nbd_extent)(unsafePtr) + slice := arrayPtr[:count:count] + ret := make([]LibnbdExtent, count) + for i := 0; i < int (count); i++ { + ret[i].Length = uint64 (slice[i].length) + ret[i].Flags = uint64 (slice[i].flags) + } + return ret +} "; List.iter ( @@ -537,6 +553,8 @@ let match cbarg with | CBArrayAndLen (UInt32 n, _) -> pr "%s []uint32" n; + | CBArrayAndLen (Extent64 n, _) -> + pr "%s []LibnbdExtent" n; | CBBytesIn (n, len) -> pr "%s []byte" n; | CBInt n -> @@ -563,6 +581,8 @@ let match cbarg with | CBArrayAndLen (UInt32 n, count) -> pr "%s *C.uint32_t, %s C.size_t" n count + | CBArrayAndLen (Extent64 n, count) -> + pr "%s *C.nbd_extent, %s C.size_t" n count | CBBytesIn (n, len) -> pr "%s unsafe.Pointer, %s C.size_t" n len | CBInt n -> @@ -605,6 +625,8 @@ let match cbarg with | CBArrayAndLen (UInt32 n, count) -> pr "copy_uint32_array (%s, %s)" n count + | CBArrayAndLen (Extent64 n, count) -> + pr "copy_extent_array (%s, %s)" n count | CBBytesIn (n, len) -> pr "C.GoBytes (%s, C.int (%s))" n len | CBInt n -> @@ -756,6 +778,8 @@ let match cbarg with | CBArrayAndLen (UInt32 n, count) -> pr "uint32_t *%s, size_t %s" n count + | CBArrayAndLen (Extent64 n, count) -> + pr "nbd_extent *%s, size_t %s" n count | CBBytesIn (n, len) -> pr "void *%s, size_t %s" n len | CBInt n -> diff --git a/generator/OCaml.ml b/generator/OCaml.ml index edb81f25..ea3b044a 100644 --- a/generator/OCaml.ml +++ b/generator/OCaml.ml @@ -44,6 +44,7 @@ and | Closure { cbargs } -> sprintf "(%s)" (ocaml_closuredecl_to_string cbargs) | Enum (_, { enum_prefix }) -> sprintf "%s.t" enum_prefix + | Extent64 _ -> "extent" | Fd _ -> "Unix.file_descr" | Flags (_, { flag_prefix }) -> sprintf "%s.t list" flag_prefix | Int _ -> "int" @@ -102,6 +103,7 @@ let | BytesPersistOut (n, len) -> n | Closure { cbname } -> cbname | Enum (n, _) -> n + | Extent64 n -> n | Fd n -> n | Flags (n, _) -> n | Int n -> n @@ -147,6 +149,9 @@ let type cookie = int64 +type extent = int64 * int64 +(** Length and flags of an extent in block_status_64 callback. *) + "; List.iter ( @@ -268,6 +273,7 @@ let exception Error of string * Unix.error option exception Closed of string type cookie = int64 +type extent = int64 * int64 (* Give the exceptions names so that they can be raised from the C code. *) let () @@ -498,7 +504,8 @@ let let argnames List.map ( function - | CBArrayAndLen (UInt32 n, _) | CBBytesIn (n, _) + | CBArrayAndLen (UInt32 n, _) | CBArrayAndLen (Extent64 n, _) + | CBBytesIn (n, _) | CBInt n | CBInt64 n | CBMutable (Int n) | CBString n | CBUInt n | CBUInt64 n -> n ^ "v" @@ -531,6 +538,14 @@ let pr "%s %s,\n" indent n; pr "%s %s\n" indent count; pr "%s);\n" indent + | CBArrayAndLen (Extent64 n, count) -> + pr " %sv = " n; + let fncol = output_column () in + let indent = spaces fncol in + pr "nbd_internal_ocaml_alloc_extent64_array (\n"; + pr "%s %s,\n" indent n; + pr "%s %s\n" indent count; + pr "%s);\n" indent | CBBytesIn (n, len) -> pr " %sv = caml_alloc_initialized_string (%s, %s);\n" n len n | CBInt n | CBUInt n -> @@ -554,7 +569,7 @@ let List.iter ( function - | CBArrayAndLen (UInt32 _, _) + | CBArrayAndLen (_, _) | CBBytesIn _ | CBInt _ | CBInt64 _ @@ -563,7 +578,7 @@ let | CBUInt64 _ -> () | CBMutable (Int n) -> pr " *%s = Int_val (Field (%sv, 0));\n" n n - | CBArrayAndLen _ | CBMutable _ -> assert false + | CBMutable _ -> assert false ) cbargs; pr " if (Is_exception_result (rv)) {\n"; @@ -578,6 +593,7 @@ let pr "}\n"; pr "\n"; pr "static int\n"; + pr "__attribute__ ((unused)) /* XXX temporary hack */\n"; pr "%s_wrapper " cbname; C.print_cbarg_list ~wrap:true cbargs; pr "\n"; @@ -695,6 +711,7 @@ let pr " %s_callback.free = free_user_data;\n" cbname | Enum (n, { enum_prefix }) -> pr " int %s = %s_val (%sv);\n" n enum_prefix n + | Extent64 _ -> assert false (* only used in extent64_closure *) | Fd n -> pr " /* OCaml Unix.file_descr is just an int, at least on Unix. */\n"; pr " int %s = Int_val (%sv);\n" n n @@ -780,6 +797,7 @@ let | BytesPersistOut _ | Closure _ | Enum _ + | Extent64 _ | Fd _ | Flags _ | Int _ diff --git a/generator/Python.ml b/generator/Python.ml index c81878de..ccac5fed 100644 --- a/generator/Python.ml +++ b/generator/Python.ml @@ -158,6 +158,7 @@ let let print_python_closure_wrapper { cbname; cbargs } pr "/* Wrapper for %s callback. */\n" cbname; pr "static int\n"; + pr "__attribute__ ((unused)) /* XXX temporary hack */\n"; pr "%s_wrapper " cbname; C.print_cbarg_list ~wrap:true cbargs; pr "\n"; @@ -170,6 +171,7 @@ let List.iter ( function | CBArrayAndLen (UInt32 n, _) + | CBArrayAndLen (Extent64 n, _) | CBBytesIn (n, _) | CBMutable (Int n) -> pr " PyObject *py_%s = NULL;\n" n @@ -187,6 +189,16 @@ let pr " if (!py_e_%s) { PyErr_PrintEx (0); goto out; }\n" n; pr " PyList_SET_ITEM (py_%s, i_%s, py_e_%s);\n" n n n; pr " }\n" + | CBArrayAndLen (Extent64 n, len) -> + pr " py_%s = PyList_New (%s);\n" n len; + pr " size_t i_%s;\n" n; + pr " for (i_%s = 0; i_%s < %s; ++i_%s) {\n" n n len n; + pr " PyObject *py_e_%s = Py_BuildValue (\"OO\",\n" n; + pr " PyLong_FromUnsignedLong (%s[i_%s].length),\n" n n; + pr " PyLong_FromUnsignedLong (%s[i_%s].flags));\n" n n; + pr " if (!py_e_%s) { PyErr_PrintEx (0); goto out; }\n" n; + pr " PyList_SET_ITEM (py_%s, i_%s, py_e_%s);\n" n n n; + pr " }\n" | CBBytesIn (n, len) -> pr " py_%s = nbd_internal_py_get_subview (data->view, %s, %s);\n" n n len; pr " if (!py_%s) { PyErr_PrintEx (0); goto out; }\n" n @@ -206,6 +218,7 @@ let List.map ( function | CBArrayAndLen (UInt32 n, _) -> "O", sprintf "py_%s" n + | CBArrayAndLen (Extent64 n, _) -> "O", sprintf "py_%s" n | CBBytesIn (n, _) -> "O", sprintf "py_%s" n | CBInt n -> "i", n | CBInt64 n -> "L", n @@ -254,7 +267,8 @@ let pr " out:\n"; List.iter ( function - | CBArrayAndLen (UInt32 n, _) -> + | CBArrayAndLen (UInt32 n, _) + | CBArrayAndLen (Extent64 n, _) -> pr " Py_XDECREF (py_%s);\n" n | CBBytesIn (n, _) -> pr " Py_XDECREF (py_%s);\n" n @@ -307,6 +321,7 @@ let pr ".callback = %s_wrapper, .free = free_user_data" cbname); pr " };\n" | Enum (n, _) -> pr " int %s;\n" n + | Extent64 _ -> assert false (* only used in extent64_closure *) | Flags (n, _) -> pr " uint32_t %s_u32;\n" n; pr " unsigned int %s; /* really uint32_t */\n" n @@ -363,6 +378,7 @@ let "O", sprintf "&%s" n, sprintf "py_%s->buf, py_%s->len" n n | Closure { cbname } -> "O", sprintf "&py_%s_fn" cbname, cbname | Enum (n, _) -> "i", sprintf "&%s" n, n + | Extent64 _ -> assert false (* only used in extent64_closure *) | Flags (n, _) -> "I", sprintf "&%s" n, sprintf "%s_u32" n | Fd n | Int n -> "i", sprintf "&%s" n, n | Int64 n -> "L", sprintf "&%s" n, sprintf "%s_i64" n @@ -454,6 +470,7 @@ let pr " if (!chunk_user_data->view) goto out;\n" ) | Enum _ -> () + | Extent64 _ -> () | Flags (n, _) -> pr " %s_u32 = %s;\n" n n | Fd _ | Int _ -> () | Int64 n -> pr " %s_i64 = %s;\n" n n @@ -552,6 +569,7 @@ let | Closure { cbname } -> pr " free_user_data (%s_user_data);\n" cbname | Enum _ -> () + | Extent64 _ -> () | Flags _ -> () | Fd _ | Int _ -> () | Int64 _ -> () @@ -881,6 +899,7 @@ let | BytesPersistOut (n, _) -> n, None | Closure { cbname } -> cbname, None | Enum (n, _) -> n, None + | Extent64 _ -> assert false (* only used in extent64_closure *) | Flags (n, _) -> n, None | Fd n | Int n -> n, None | Int64 n -> n, None diff --git a/ocaml/helpers.c b/ocaml/helpers.c index 3361a696..09666daf 100644 --- a/ocaml/helpers.c +++ b/ocaml/helpers.c @@ -133,6 +133,26 @@ nbd_internal_ocaml_alloc_i64_from_u32_array (uint32_t *a, size_t len) CAMLreturn (rv); } +value +nbd_internal_ocaml_alloc_extent64_array (nbd_extent *a, size_t len) +{ + CAMLparam0 (); + CAMLlocal3 (s, v, rv); + size_t i; + + rv = caml_alloc (len, 0); + for (i = 0; i < len; ++i) { + s = caml_alloc (2, 0); + v = caml_copy_int64 (a[i].length); + Store_field (s, 0, v); + v = caml_copy_int64 (a[i].flags); + Store_field (s, 1, v); + Store_field (rv, i, s); + } + + CAMLreturn (rv); +} + /* Convert a Unix.sockaddr to a C struct sockaddr. */ void nbd_internal_unix_sockaddr_to_sa (value sockaddrv, diff --git a/ocaml/nbd-c.h b/ocaml/nbd-c.h index e3abb912..adcdd15a 100644 --- a/ocaml/nbd-c.h +++ b/ocaml/nbd-c.h @@ -62,6 +62,7 @@ extern void nbd_internal_ocaml_raise_closed (const char *func) Noreturn; extern const char **nbd_internal_ocaml_string_list (value); extern value nbd_internal_ocaml_alloc_i64_from_u32_array (uint32_t *, size_t); +extern value nbd_internal_ocaml_alloc_extent64_array (nbd_extent *, size_t); extern void nbd_internal_unix_sockaddr_to_sa (value, struct sockaddr_storage *, socklen_t *); extern void nbd_internal_ocaml_exception_in_wrapper (const char *, value); diff --git a/golang/handle.go b/golang/handle.go index 5fe4ed4f..0b126005 100644 --- a/golang/handle.go +++ b/golang/handle.go @@ -58,6 +58,12 @@ func (h *Libnbd) String() string { return "&Libnbd{}" } +/* Used for block status callback. */ +type LibnbdExtent struct { + Length uint64 // length of the extent + Flags uint64 // flags describing properties of the extent +} + /* All functions (except Close) return ([result,] LibnbdError). */ type LibnbdError struct { Op string // operation which failed -- 2.40.1
Laszlo Ersek
2023-Jun-07 14:23 UTC
[Libguestfs] [libnbd PATCH v3 07/22] generator: Add struct nbd_extent in prep for 64-bit extents
On 5/25/23 15:00, Eric Blake wrote:> The existing nbd_block_status() callback is permanently stuck with an > array of uint32_t pairs (len/2 extents), which is both constrained on( so the terminology is: - we have "len" uint32_t elements in the array, - one extent is one uint32_t pair, - hence we have len/2 extents in the array )> maximum extent size (no larger than 4G) and on the status flags (must > fit in 32 bits). While the "base:allocation" metacontext will never > exceed 32 bits, it is not hard to envision other extension > metacontexts where a 64-bit status would be useful (for example, Zoned > Block Devices expressing a 64-bit offset[1]). Exposing 64-bit extents > will require a new API; we now have the decision of whether to copy > the existing API practice of returning a bare array containing len/2 > extent pairs,The term "extent pairs" is inconsistent with the above terminology. (1) We should say: - "len/2 extents", or - "len/2 uint64_t pairs".> or with a saner idea(2) s/or with/or go with/ ?> of an array of structs with len > extents.This sounds OK; if the element type of the array is a struct, then one struct describes one extent, and then the "len" elements of the array describe "len" extents.> Returning an array of structs is actually easier to map to > various language bindings, particularly since we know the length field > will generally fit in 63-bits (fallout from the fact that NBD exports > are effectively capped in size by signed off_t), but where the status > field must represent a full 64 bits (assuming that the user wants to > connect to a metadata extension that utilizes 64 bits, rather than > existing metadata contexts that only expose 32 bits).This is a good argument, but I needed to chew on the "but" conjunction for a good while until I understood the point. The "but" seemed out of place at first; only later did I realize it was actually the core of the argument ("easier to map"). The point is that for each extent, we have two properties that are same in size but that differ in meaning (hence in type), and *therefore* a struct is better than two adjacent, raw uint64_t elements. We're going to have two uint64_t fields, but conceptually, that's one off_t field (just serialized as a uint64_t) and a genuine uint64_t field, for status. (3) Can you append a sentence, "That is, conceptually, the fields have different types, so a structure is best to express them"? Or something similar?> > This patch introduces the struct we plan to use in the new API, along > with language bindings. The bindings for Python and OCaml were > relatively straightforward; the Golang bindings took a bit more effort > for me to write. Temporary unused attributes are needed to keep the > compiler happy until a later patch exposes a new API using the new > callback type.(4) Can you say "We need to insert '__attribute__ ((unused))' a number of times, temporarily, to keep the compiler happy..."? "Unused attributes" sounds as if we needed some attributes that were not used. Confusing to me. What we need instead is to mark some variables or functions with an attribute that says those vars/funcs are unused.> > Note that 'struct nbd_block_descriptor_ext' in lib/nbd-protocol.h is > exactly the same as what we want to use in C. But it is easier to > stick a new public type in <libnbd.h> than to figure out how to expose > just part of a header we only want to use internally.Could be OK or fishy; I should be able to tell later :)> > [1] https://zonedstorage.io/docs/linux/zbd-api > > Signed-off-by: Eric Blake <eblake at redhat.com> > --- > generator/API.mli | 1 + > generator/API.ml | 12 +++++++++++- > generator/C.ml | 24 +++++++++++++++++++++--- > generator/GoLang.ml | 24 ++++++++++++++++++++++++ > generator/OCaml.ml | 24 +++++++++++++++++++++--- > generator/Python.ml | 21 ++++++++++++++++++++- > ocaml/helpers.c | 20 ++++++++++++++++++++ > ocaml/nbd-c.h | 1 + > golang/handle.go | 6 ++++++ > 9 files changed, 125 insertions(+), 8 deletions(-) > > diff --git a/generator/API.mli b/generator/API.mli > index c5bba8c2..96626c9b 100644 > --- a/generator/API.mli > +++ b/generator/API.mli > @@ -52,6 +52,7 @@ and > | BytesPersistOut of string * string > | Closure of closure (** function pointer + void *opaque *) > | Enum of string * enum (** enum/union type, int in C *) > +| Extent64 of string (** extent descriptor, struct nbd_extent in C *) > | Fd of string (** file descriptor *) > | Flags of string * flags (** flags, uint32_t in C *) > | Int of string (** small int *) > diff --git a/generator/API.ml b/generator/API.ml > index 02c1260d..1da1ec53 100644 > --- a/generator/API.ml > +++ b/generator/API.ml > @@ -42,6 +42,7 @@ > | BytesPersistOut of string * string > | Closure of closure > | Enum of string * enum > +| Extent64 of string > | Fd of string > | Flags of string * flags > | Int of string > @@ -157,6 +158,14 @@ let extent_closure > "nr_entries"); > CBMutable (Int "error") ] > } > +let extent64_closure = { > + cbname = "extent64"; > + cbargs = [ CBString "metacontext"; > + CBUInt64 "offset"; > + CBArrayAndLen (Extent64 "entries", > + "nr_entries"); > + CBMutable (Int "error") ] > +} > let list_closure = { > cbname = "list"; > cbargs = [ CBString "name"; CBString "description" ] > @@ -166,7 +175,8 @@ let context_closure > cbargs = [ CBString "name" ] > } > let all_closures = [ chunk_closure; completion_closure; > - debug_closure; extent_closure; list_closure; > + debug_closure; extent_closure; extent64_closure; > + list_closure; > context_closure ] > > (* Enums. *)OK.> diff --git a/generator/C.ml b/generator/C.ml > index f772117c..d8472153 100644 > --- a/generator/C.ml > +++ b/generator/C.ml > @@ -94,6 +94,7 @@ let > | Closure { cbname } -> > [ sprintf "%s_callback" cbname; sprintf "%s_user_data" cbname ] > | Enum (n, _) -> [n] > + | Extent64 n -> [n] > | Fd n -> [n] > | Flags (n, _) -> [n] > | Int n -> [n]This extends "name_of_arg".> @@ -129,7 +130,7 @@ let > (* list of strings should be marked as non-null *) > | StringList n -> [ true ] > (* numeric and other non-pointer types are not able to be null *) > - | Bool _ | Closure _ | Enum _ | Fd _ | Flags _ > + | Bool _ | Closure _ | Enum _ | Extent64 _ | Fd _ | Flags _ > | Int _ | Int64 _ | SizeT _ > | UInt _ | UInt32 _ | UInt64 _ | UIntPtr _ -> [ false ] >This extends "arg_attr_nonnull".> @@ -191,6 +192,9 @@ and > | Enum (n, _) -> > if types then pr "int "; > pr "%s" n > + | Extent64 n -> > + if types then pr "nbd_extent "; > + pr "%s" n > | Flags (n, _) -> > if types then pr "uint32_t "; > pr "%s" nThis extends "print_arg_list".> @@ -305,6 +309,11 @@ and > pr "%s, " n; > if types then pr "size_t "; > pr "%s" len > + | CBArrayAndLen (Extent64 n, len) -> > + if types then pr "nbd_extent *"; > + pr "%s, " n; > + if types then pr "size_t "; > + pr "%s" len > | CBArrayAndLen _ -> assert false > | CBBytesIn (n, len) -> > if types then pr "const void *";This extends "print_cbarg_list".> @@ -477,6 +486,13 @@ let > pr "extern int nbd_get_errno (void);\n"; > pr "#define LIBNBD_HAVE_NBD_GET_ERRNO 1\n"; > pr "\n"; > + pr "/* This is used in the callback for nbd_block_status_64.\n";Pointing a bit into the future, but fine / unavoidable.> + pr " */\n"; > + pr "typedef struct {\n"; > + pr " uint64_t length;\n"; > + pr " uint64_t flags;\n"; > + pr "} nbd_extent;\n"; > + pr "\n"; > print_closure_structs (); > List.iter ( > fun (name, { args; optargs; ret }) ->The "nbd_" prefix for the new type seems consistent with existent practice (e.g. "nbd_handle"). The placement of the new typedef is good as well, IMO; looking at the generated library header, this typedef appears just before the callback structures. Also in the generator's source code / context above, we can see the call to "print_closure_structs" just after the insertion. That's where our new "extent64_closure" is going to be handled too. The question is how we're going to translate "nbd_block_descriptor_ext" to this new structure, in *C* code. If we populate the members individually, together with big-endian-to-host-endian conversion, that should be OK. I figure that belongs in a later patch though.> @@ -740,7 +756,7 @@ let > pr " char *%s_printable =\n" n; > pr " nbd_internal_printable_string_list (%s);\n" n > | BytesOut _ | BytesPersistOut _ > - | Bool _ | Closure _ | Enum _ | Flags _ | Fd _ | Int _ > + | Bool _ | Closure _ | Enum _ | Extent64 _ | Flags _ | Fd _ | Int _ > | Int64 _ | SizeT _ > | SockAddrAndLen _ | UInt _ | UInt32 _ | UInt64 _ | UIntPtr _ -> () > ) args;This extends "print_trace_enter" (near the top).> @@ -766,6 +782,7 @@ let > pr " %s=\\\"%%s\\\" %s=%%zu" n count > | Closure { cbname } -> pr " %s=%%s" cbname > | Enum (n, _) -> pr " %s=%%d" n > + | Extent64 _ -> assert false (* only used in extent64_closure *) > | Flags (n, _) -> pr " %s=0x%%x" n > | Fd n | Int n -> pr " %s=%%d" n > | Int64 n -> pr " %s=%%\"PRIi64\"" nAnd this extends "print_format_string" within "print_trace_enter". Now here's why I've inserted all those "this extends ..." remarks: I think your assertion is valid here, *but* it is inconsistent with the extensions to: - name_of_arg - arg_attr_nonnull - print_arg_list - print_trace_enter We only deal with Extent64 via the "penetrating pattern match" CBArrayAndLen (Extent64 n, len), in "print_cbarg_list". (5) Therefore, for consistency, *all* other Extent64 pattern matches should assert that they are unreachable -- not only in "print_format_string", but in those four functions too that I list above. Clearly, I could be missing some code paths leading to those functions / patterns; I've not tested my proposition. The idea is that the narrowest necessary subset of patterns should be handled. Even if it is easy to "handle" a pattern with just (), it makes the wrong impression.> @@ -804,6 +821,7 @@ let > pr "%s_printable ? %s_printable : \"\", %s" n n count > | Closure _ -> pr "\"<fun>\"" > | Enum (n, _) -> pr "%s" n > + | Extent64 _ -> assert false (* only used in extent64_closure *) > | Flags (n, _) -> pr "%s" n > | Fd n | Int n | Int64 n | SizeT n -> pr "%s" n > | SockAddrAndLen (_, len) -> pr "(int) %s" lenThis extends "print_args" within "print_trace_enter". The assertion is OK.> @@ -836,7 +854,7 @@ let > | StringList n -> > pr " free (%s_printable);\n" n > | BytesOut _ | BytesPersistOut _ > - | Bool _ | Closure _ | Enum _ | Flags _ | Fd _ | Int _ > + | Bool _ | Closure _ | Enum _ | Extent64 _ | Flags _ | Fd _ | Int _ > | Int64 _ | SizeT _ > | SockAddrAndLen _ | UInt _ | UInt32 _ | UInt64 _ | UIntPtr _ -> () > ) args;(6) This is near the end of "print_trace_enter", and again, we should assert here that "Extent64 _" will never match.> diff --git a/generator/GoLang.ml b/generator/GoLang.ml > index 4ab6b262..8922812b 100644 > --- a/generator/GoLang.ml > +++ b/generator/GoLang.ml > @@ -49,6 +49,7 @@ let > | BytesPersistOut (n, len) -> n > | Closure { cbname } -> cbname > | Enum (n, _) -> n > + | Extent64 n -> n > | Fd n -> n > | Flags (n, _) -> n > | Int n -> n > @@ -71,6 +72,7 @@ let > | BytesPersistOut _ -> "AioBuffer" > | Closure { cbname } -> sprintf "%sCallback" (camel_case cbname) > | Enum (_, { enum_prefix }) -> camel_case enum_prefix > + | Extent64 _ -> assert false (* only used in extent64_closure *) > | Fd _ -> "int" > | Flags (_, { flag_prefix }) -> camel_case flag_prefix > | Int _ -> "int" > @@ -262,6 +264,7 @@ let > pr " c_%s.user_data = C.alloc_cbid(C.long(%s_cbid))\n" cbname cbname > | Enum (n, _) -> > pr " c_%s := C.int (%s)\n" n n > + | Extent64 _ -> assert false (* only used in extent64_closure *) > | Fd n -> > pr " c_%s := C.int (%s)\n" n n > | Flags (n, _) -> > @@ -334,6 +337,7 @@ let > | BytesPersistOut (n, len) -> pr ", c_%s, c_%s" n len > | Closure { cbname } -> pr ", c_%s" cbname > | Enum (n, _) -> pr ", c_%s" n > + | Extent64 _ -> assert false (* only used in extent64_closure *) > | Fd n -> pr ", c_%s" n > | Flags (n, _) -> pr ", c_%s" n > | Int n -> pr ", c_%s" n(7) I care pretty little for the Go binding, but again: the hunk above that is the first for "GoLang.ml", we extend "go_name_of_arg", and I'd argue we should insert an "assert false" there.> @@ -524,6 +528,18 @@ let > copy(ret, s) > return ret > } > + > +func copy_extent_array (entries *C.nbd_extent, count C.size_t) []LibnbdExtent { > + unsafePtr := unsafe.Pointer(entries) > + arrayPtr := (*[1 << 20]C.nbd_extent)(unsafePtr) > + slice := arrayPtr[:count:count] > + ret := make([]LibnbdExtent, count) > + for i := 0; i < int (count); i++ { > + ret[i].Length = uint64 (slice[i].length) > + ret[i].Flags = uint64 (slice[i].flags) > + } > + return ret > +} > "; > > List.iter (The pre-existent copy_uint32_array() function uses the hideous trick at <https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices>, and needlessly so, IMO. - The trick is (a) hideous because it requires us to use arbitrary sizes such as "1<<30". - The trick is (b) unnecessary because we don't intend to hang on to the slice indefinitely. We only use it as a means to access the source object. But at the noted URL, the trick is "being sold" with the pitch "to create a Go slice backed by a C array (without copying the original data)" -- and we copy the original data *anyway*! So it's better to use pointer arithmetic IMO. Regarding the new copy_extent_array(), my additional complaints are: - whitespace usage before opening parens is inconsistent -- there is no space after "make" and "Pointer". - we cast "count" (a size_t in C) to a Go "int"; similarly the index variable "i" has Go type "int". (8) So how about this instead (should be split in two: the first part should update copy_uint32_array() in a separate patch, and the second part should be squashed into this patch):> diff --git a/generator/GoLang.ml b/generator/GoLang.ml > index 8922812b76a4..37b2240ef5bf 100644 > --- a/generator/GoLang.ml > +++ b/generator/GoLang.ml > @@ -521,22 +521,32 @@ let > /* Closures. */ > > func copy_uint32_array (entries *C.uint32_t, count C.size_t) []uint32 { > - ret := make([]uint32, int (count)) > - // See https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices > - // TODO: Use unsafe.Slice() when we require Go 1.17. > - s := (*[1<<30]uint32)(unsafe.Pointer(entries))[:count:count] > - copy(ret, s) > + ret := make ([]uint32, count) > + addr := uintptr (unsafe.Pointer (entries)) > + var i uint64 = 0 > + for i < uint64 (count) { > + ptr := (*C.uint32_t)(unsafe.Pointer (addr)) > + > + ret[i] = uint32 (*ptr) > + > + addr += unsafe.Sizeof (*ptr) > + i++ > + } > return ret > } > > func copy_extent_array (entries *C.nbd_extent, count C.size_t) []LibnbdExtent { > - unsafePtr := unsafe.Pointer(entries) > - arrayPtr := (*[1 << 20]C.nbd_extent)(unsafePtr) > - slice := arrayPtr[:count:count] > - ret := make([]LibnbdExtent, count) > - for i := 0; i < int (count); i++ { > - ret[i].Length = uint64 (slice[i].length) > - ret[i].Flags = uint64 (slice[i].flags) > + ret := make ([]LibnbdExtent, count) > + addr := uintptr (unsafe.Pointer (entries)) > + var i uint64 = 0 > + for i < uint64 (count) { > + ptr := (*C.nbd_extent)(unsafe.Pointer (addr)) > + > + ret[i].Length = uint64 ((*ptr).length) > + ret[i].Flags = uint64 ((*ptr).flags) > + > + addr += unsafe.Sizeof (*ptr) > + i++ > } > return ret > }(This passes make and make-check, when the tree is checked out at your patch#7, and this is applied on top.) Back to your patch: On 5/25/23 15:00, Eric Blake wrote:> @@ -537,6 +553,8 @@ let > match cbarg with > | CBArrayAndLen (UInt32 n, _) -> > pr "%s []uint32" n; > + | CBArrayAndLen (Extent64 n, _) -> > + pr "%s []LibnbdExtent" n; > | CBBytesIn (n, len) -> > pr "%s []byte" n; > | CBInt n -> > @@ -563,6 +581,8 @@ let > match cbarg with > | CBArrayAndLen (UInt32 n, count) -> > pr "%s *C.uint32_t, %s C.size_t" n count > + | CBArrayAndLen (Extent64 n, count) -> > + pr "%s *C.nbd_extent, %s C.size_t" n count > | CBBytesIn (n, len) -> > pr "%s unsafe.Pointer, %s C.size_t" n len > | CBInt n -> > @@ -605,6 +625,8 @@ let > match cbarg with > | CBArrayAndLen (UInt32 n, count) -> > pr "copy_uint32_array (%s, %s)" n count > + | CBArrayAndLen (Extent64 n, count) -> > + pr "copy_extent_array (%s, %s)" n count > | CBBytesIn (n, len) -> > pr "C.GoBytes (%s, C.int (%s))" n len > | CBInt n -> > @@ -756,6 +778,8 @@ let > match cbarg with > | CBArrayAndLen (UInt32 n, count) -> > pr "uint32_t *%s, size_t %s" n count > + | CBArrayAndLen (Extent64 n, count) -> > + pr "nbd_extent *%s, size_t %s" n count > | CBBytesIn (n, len) -> > pr "void *%s, size_t %s" n len > | CBInt n ->OK. Quoting the rest of the patch out of order, so I can leave the OCaml stuff for the end (this is a *terribly* large patch, when the contexts are increased suitably for reviewing, so I really wish we could split it up somehow):> diff --git a/golang/handle.go b/golang/handle.go > index 5fe4ed4f..0b126005 100644 > --- a/golang/handle.go > +++ b/golang/handle.go > @@ -58,6 +58,12 @@ func (h *Libnbd) String() string { > return "&Libnbd{}" > } > > +/* Used for block status callback. */ > +type LibnbdExtent struct { > + Length uint64 // length of the extent > + Flags uint64 // flags describing properties of the extent > +} > + > /* All functions (except Close) return ([result,] LibnbdError). */ > type LibnbdError struct { > Op string // operation which failedOK.> diff --git a/generator/Python.ml b/generator/Python.ml > index c81878de..ccac5fed 100644 > --- a/generator/Python.ml > +++ b/generator/Python.ml > @@ -158,6 +158,7 @@ let > let print_python_closure_wrapper { cbname; cbargs } > pr "/* Wrapper for %s callback. */\n" cbname; > pr "static int\n"; > + pr "__attribute__ ((unused)) /* XXX temporary hack */\n"; > pr "%s_wrapper " cbname; > C.print_cbarg_list ~wrap:true cbargs; > pr "\n";OK, this is as announced in the commit message.> @@ -170,6 +171,7 @@ let > List.iter ( > function > | CBArrayAndLen (UInt32 n, _) > + | CBArrayAndLen (Extent64 n, _) > | CBBytesIn (n, _) > | CBMutable (Int n) -> > pr " PyObject *py_%s = NULL;\n" nOK, this is responsible for generating: PyObject *py_entries = NULL;> @@ -187,6 +189,16 @@ let > pr " if (!py_e_%s) { PyErr_PrintEx (0); goto out; }\n" n; > pr " PyList_SET_ITEM (py_%s, i_%s, py_e_%s);\n" n n n; > pr " }\n" > + | CBArrayAndLen (Extent64 n, len) -> > + pr " py_%s = PyList_New (%s);\n" n len; > + pr " size_t i_%s;\n" n;(9) Under "CBArrayAndLen (UInt32 n, len)", we also check whether the New call succeeds; I think it should not be omitted here either: pr " if (!py_%s) { PyErr_PrintEx (0); goto out; }\n" n; producing (in the preexistent "extent_wrapper" function): if (!py_entries) { PyErr_PrintEx (0); goto out; }> + pr " for (i_%s = 0; i_%s < %s; ++i_%s) {\n" n n len n; > + pr " PyObject *py_e_%s = Py_BuildValue (\"OO\",\n" n; > + pr " PyLong_FromUnsignedLong (%s[i_%s].length),\n" n n; > + pr " PyLong_FromUnsignedLong (%s[i_%s].flags));\n" n n;We have two issues here: (10) We should call PyLong_FromUnsignedLongLong(), not PyLong_FromUnsignedLong(). On an ILP32 platform, the latter will not do the right thing. (11) PyLong_FromWhatever() returns a new reference, and so it can fail due to memory exhaustion (minimally, I think). That means zero, one, or two of our PyLong_FromUnsignedLongLong() calls can fail, and return NULL. We call Py_BuildValue() with an "OO" specification. The documentation says:> Pass a Python object untouched (except for its reference count, which > is incremented by one). If the object passed in is a NULL pointer, it > is assumed that this was caused because the call producing the > argument found an error and set an exception. Therefore, > Py_BuildValue() will return NULL but won?t raise an exception. If no > exception has been raised yet, SystemError is set.Now, I don't think PyLong_FromUnsignedLongLong() sets an exception when it fails (and returns NULL), but that should be fine -- at least Py_BuildValue() will raise SystemError. The problem is that we have three possible ways to leak memory: - If we pass two NULLs to "OO" (after both our PyLong_FromUnsignedLongLong() calls fail), there's no leak. Py_BuildValue() fails in response too, and nothing is leaked. - If we pass exactly one NULL to "OO" (after one PyLong_FromUnsignedLongLong() succeeds, but the other fails), then Py_BuildValue() fails again (as expected), but we leak the PyLongObject that we *did* successfully create. - If we pass two non-NULLs to "OO" (after both PyLong_FromUnsignedLongLong() succeed), and Py_BuildValue() fails because *it* cannot allocate further memory, then we leak *both* PyLongObjects. - If we pass two non-NULLs to "OO" (after both PyLong_FromUnsignedLongLong() succeed), and Py_BuildValue() *succeds*, then we leak both PyLongObjects again: PyLong_FromUnsignedLongLong() returns a new reference, but when Py_BuildValue() succeeds, it adds a *new* reference (it does not only take ownership). The actual leak will happen later, when the object just constructed with Py_BuildValue() is torn down. So, we should implement this in a much more "staged" way. Shown as a diff on top of the code generated by your patch, covering points (9) through (11) (even (12) below):> --- python/methods.c.orig 2023-06-07 13:29:01.947636166 +0200 > +++ python/methods.c 2023-06-07 13:44:23.845892043 +0200 > @@ -286,15 +286,24 @@ extent64_wrapper (void *user_data, const > PyObject *py_args, *py_ret; > PyObject *py_entries = NULL; > PyObject *py_error = NULL; > + PyObject *py_e_length = NULL; > + PyObject *py_e_flags = NULL; > > py_entries = PyList_New (nr_entries); > + if (!py_entries) { PyErr_PrintEx (0); goto out; } > size_t i_entries; > for (i_entries = 0; i_entries < nr_entries; ++i_entries) { > - PyObject *py_e_entries = Py_BuildValue ("OO", > - PyLong_FromUnsignedLong (entries[i_entries].length), > - PyLong_FromUnsignedLong (entries[i_entries].flags)); > + PyObject *py_e_entries; > + > + py_e_length = PyLong_FromUnsignedLongLong (entries[i_entries].length); > + py_e_flags = PyLong_FromUnsignedLongLong (entries[i_entries].flags); > + py_e_entries = Py_BuildValue ("OO", py_e_length, py_e_flags); > if (!py_e_entries) { PyErr_PrintEx (0); goto out; } > PyList_SET_ITEM (py_entries, i_entries, py_e_entries); > + Py_XDECREF (py_e_flags); > + Py_XDECREF (py_e_length); > + py_e_flags = NULL; > + py_e_length = NULL; > } > py_error = nbd_internal_py_wrap_errptr (*error); > if (!py_error) { PyErr_PrintEx (0); goto out; } > @@ -325,6 +334,8 @@ extent64_wrapper (void *user_data, const > }; > > out: > + Py_XDECREF (py_e_flags); > + Py_XDECREF (py_e_length); > Py_XDECREF (py_entries); > if (py_error) { > PyObject *py_error_ret = PyObject_GetAttrString (py_error, "value");In this variant, if at least one PyLong_FromUnsignedLongLong() call fails, then Py_BuildValue() catches that, fails itself, and at the "out" label, we release precisely those PyLongObjects that we managed to construct (zero or one objects). If both PyLongObjects get allocated, but Py_BuildValue() fails, then at the "out" label, we release both PyLongObjects. If even the Py_BuildValue() call succeeds, then we reach PyList_SET_ITEM(). PyList_SET_ITEM() "steals" a reference (i.e., takes ownership of one reference), it does not add one. Therefore the just constructed "py_e_entries" object is now owned by the "py_entries" list -- and we want the new "py_e_entries" entry in that list too to own our PyLongObjects. Therefore, we release our temporary references to the PyLongObjects (see the xdecref's in the loop body). We also NULL out the variables, reflecting the ownership transfer. This is generally good practice, but it even matters, for example, when this is the last iteration of the loop, and the subsequent nbd_internal_py_wrap_errptr() call fails. Then we jump to the "out" label, and at that point, the xdecref's *there* must be no-ops. The PyLongObjects will be released via "py_entries". (In theory we could NULL out the variables right after the loop body, rather than at the end of the loop body, given that the top of the loop body immediately overwrites both variables -- but as written, the code is more understandable.) The xdecref's and the NULL'ing happen in reverse, relative to order of construction (i.e., the inverse order is intentional). Back to your patch: On 5/25/23 15:00, Eric Blake wrote:> + pr " if (!py_e_%s) { PyErr_PrintEx (0); goto out; }\n" n; > + pr " PyList_SET_ITEM (py_%s, i_%s, py_e_%s);\n" n n n; > + pr " }\n" > | CBBytesIn (n, len) -> > pr " py_%s = nbd_internal_py_get_subview (data->view, %s, %s);\n" n n len; > pr " if (!py_%s) { PyErr_PrintEx (0); goto out; }\n" n > @@ -206,6 +218,7 @@ let > List.map ( > function > | CBArrayAndLen (UInt32 n, _) -> "O", sprintf "py_%s" n > + | CBArrayAndLen (Extent64 n, _) -> "O", sprintf "py_%s" n > | CBBytesIn (n, _) -> "O", sprintf "py_%s" n > | CBInt n -> "i", n > | CBInt64 n -> "L", nRight, this corresponds to "py_entries" being a list object.> @@ -254,7 +267,8 @@ let > pr " out:\n"; > List.iter ( > function > - | CBArrayAndLen (UInt32 n, _) -> > + | CBArrayAndLen (UInt32 n, _) > + | CBArrayAndLen (Extent64 n, _) -> > pr " Py_XDECREF (py_%s);\n" n > | CBBytesIn (n, _) -> > pr " Py_XDECREF (py_%s);\n" n(12) We do need this Py_XDECREF (py_entries); we just need the Py_XDECREF (py_e_flags); Py_XDECREF (py_e_length); calls before it, too, so I think ultimately you'll want to handle this pattern separately.> @@ -307,6 +321,7 @@ let > pr ".callback = %s_wrapper, .free = free_user_data" cbname); > pr " };\n" > | Enum (n, _) -> pr " int %s;\n" n > + | Extent64 _ -> assert false (* only used in extent64_closure *) > | Flags (n, _) -> > pr " uint32_t %s_u32;\n" n; > pr " unsigned int %s; /* really uint32_t */\n" n > @@ -363,6 +378,7 @@ let > "O", sprintf "&%s" n, sprintf "py_%s->buf, py_%s->len" n n > | Closure { cbname } -> "O", sprintf "&py_%s_fn" cbname, cbname > | Enum (n, _) -> "i", sprintf "&%s" n, n > + | Extent64 _ -> assert false (* only used in extent64_closure *) > | Flags (n, _) -> "I", sprintf "&%s" n, sprintf "%s_u32" n > | Fd n | Int n -> "i", sprintf "&%s" n, n > | Int64 n -> "L", sprintf "&%s" n, sprintf "%s_i64" nYes, these are an "easy sell".> @@ -454,6 +470,7 @@ let > pr " if (!chunk_user_data->view) goto out;\n" > ) > | Enum _ -> () > + | Extent64 _ -> () > | Flags (n, _) -> pr " %s_u32 = %s;\n" n n > | Fd _ | Int _ -> () > | Int64 n -> pr " %s_i64 = %s;\n" n n > @@ -552,6 +569,7 @@ let > | Closure { cbname } -> > pr " free_user_data (%s_user_data);\n" cbname > | Enum _ -> () > + | Extent64 _ -> () > | Flags _ -> () > | Fd _ | Int _ -> () > | Int64 _ -> ()(13) However I would turn these into assertions of "false", as well. They too are in "print_python_binding".> @@ -881,6 +899,7 @@ let > | BytesPersistOut (n, _) -> n, None > | Closure { cbname } -> cbname, None > | Enum (n, _) -> n, None > + | Extent64 _ -> assert false (* only used in extent64_closure *) > | Flags (n, _) -> n, None > | Fd n | Int n -> n, None > | Int64 n -> n, NoneOK.> diff --git a/ocaml/nbd-c.h b/ocaml/nbd-c.h > index e3abb912..adcdd15a 100644 > --- a/ocaml/nbd-c.h > +++ b/ocaml/nbd-c.h > @@ -62,6 +62,7 @@ extern void nbd_internal_ocaml_raise_closed (const char *func) Noreturn; > > extern const char **nbd_internal_ocaml_string_list (value); > extern value nbd_internal_ocaml_alloc_i64_from_u32_array (uint32_t *, size_t); > +extern value nbd_internal_ocaml_alloc_extent64_array (nbd_extent *, size_t); > extern void nbd_internal_unix_sockaddr_to_sa (value, struct sockaddr_storage *, > socklen_t *); > extern void nbd_internal_ocaml_exception_in_wrapper (const char *, value);OK.> diff --git a/generator/OCaml.ml b/generator/OCaml.ml > index edb81f25..ea3b044a 100644 > --- a/generator/OCaml.ml > +++ b/generator/OCaml.ml > @@ -44,6 +44,7 @@ and > | Closure { cbargs } -> > sprintf "(%s)" (ocaml_closuredecl_to_string cbargs) > | Enum (_, { enum_prefix }) -> sprintf "%s.t" enum_prefix > + | Extent64 _ -> "extent" > | Fd _ -> "Unix.file_descr" > | Flags (_, { flag_prefix }) -> sprintf "%s.t list" flag_prefix > | Int _ -> "int" > @@ -102,6 +103,7 @@ let > | BytesPersistOut (n, len) -> n > | Closure { cbname } -> cbname > | Enum (n, _) -> n > + | Extent64 n -> n > | Fd n -> n > | Flags (n, _) -> n > | Int n -> n(14) Could we use "assert false" with these too?> @@ -147,6 +149,9 @@ let > > type cookie = int64 > > +type extent = int64 * int64 > +(** Length and flags of an extent in block_status_64 callback. *) > + > "; > > List.iter ((15) Can we add a note here that negative values are not permitted? (See my further comments below.)> @@ -268,6 +273,7 @@ let > exception Error of string * Unix.error option > exception Closed of string > type cookie = int64 > +type extent = int64 * int64 > > (* Give the exceptions names so that they can be raised from the C code. *) > let () > @@ -498,7 +504,8 @@ let > let argnames > List.map ( > function > - | CBArrayAndLen (UInt32 n, _) | CBBytesIn (n, _) > + | CBArrayAndLen (UInt32 n, _) | CBArrayAndLen (Extent64 n, _) > + | CBBytesIn (n, _) > | CBInt n | CBInt64 n > | CBMutable (Int n) | CBString n | CBUInt n | CBUInt64 n -> > n ^ "v"This is inside "print_ocaml_closure_wrapper"; OK. (16) You can contract the new pattern as follows: | CBArrayAndLen ((UInt32 n | Extent64 n), _)> @@ -531,6 +538,14 @@ let > pr "%s %s,\n" indent n; > pr "%s %s\n" indent count; > pr "%s);\n" indent > + | CBArrayAndLen (Extent64 n, count) -> > + pr " %sv = " n; > + let fncol = output_column () in > + let indent = spaces fncol in > + pr "nbd_internal_ocaml_alloc_extent64_array (\n"; > + pr "%s %s,\n" indent n; > + pr "%s %s\n" indent count; > + pr "%s);\n" indent > | CBBytesIn (n, len) -> > pr " %sv = caml_alloc_initialized_string (%s, %s);\n" n len n > | CBInt n | CBUInt n ->Seems OK superficially; matches the 'CBArrayAndLen (UInt32 n, count)' case, with only the helper function differing.> @@ -554,7 +569,7 @@ let > > List.iter ( > function > - | CBArrayAndLen (UInt32 _, _) > + | CBArrayAndLen (_, _) > | CBBytesIn _ > | CBInt _ > | CBInt64 _ > @@ -563,7 +578,7 @@ let > | CBUInt64 _ -> () > | CBMutable (Int n) -> > pr " *%s = Int_val (Field (%sv, 0));\n" n n > - | CBArrayAndLen _ | CBMutable _ -> assert false > + | CBMutable _ -> assert false > ) cbargs; > > pr " if (Is_exception_result (rv)) {\n";(17) We're still inside "print_ocaml_closure_wrapper", so I think the patterns here should follow the patterns seen in the "argnames" definition: | CBArrayAndLen (UInt32 n, _) | CBArrayAndLen (Extent64 n, _) or even better (again) | CBArrayAndLen ((UInt32 _ | Extent64 _), _) IOW I think "CBArrayAndLen (_, _)" is too generic / inconsistent with previously added code. We should handle the Extent64 constructor, and not touch the "CBArrayAndLen _" fallback.> @@ -578,6 +593,7 @@ let > pr "}\n"; > pr "\n"; > pr "static int\n"; > + pr "__attribute__ ((unused)) /* XXX temporary hack */\n"; > pr "%s_wrapper " cbname; > C.print_cbarg_list ~wrap:true cbargs; > pr "\n";OK, announced in the commit message.> @@ -695,6 +711,7 @@ let > pr " %s_callback.free = free_user_data;\n" cbname > | Enum (n, { enum_prefix }) -> > pr " int %s = %s_val (%sv);\n" n enum_prefix n > + | Extent64 _ -> assert false (* only used in extent64_closure *) > | Fd n -> > pr " /* OCaml Unix.file_descr is just an int, at least on Unix. */\n"; > pr " int %s = Int_val (%sv);\n" n nOK, this is in "print_ocaml_binding".> @@ -780,6 +797,7 @@ let > | BytesPersistOut _ > | Closure _ > | Enum _ > + | Extent64 _ > | Fd _ > | Flags _ > | Int _This is still in "print_ocaml_binding", under comment "Any parameters which need to be freed". (18) For consistency I'd suggest "assert false" here as well.> diff --git a/ocaml/helpers.c b/ocaml/helpers.c > index 3361a696..09666daf 100644 > --- a/ocaml/helpers.c > +++ b/ocaml/helpers.c > @@ -133,6 +133,26 @@ nbd_internal_ocaml_alloc_i64_from_u32_array (uint32_t *a, size_t len) > CAMLreturn (rv); > } > > +value > +nbd_internal_ocaml_alloc_extent64_array (nbd_extent *a, size_t len) > +{ > + CAMLparam0 (); > + CAMLlocal3 (s, v, rv); > + size_t i; > + > + rv = caml_alloc (len, 0); > + for (i = 0; i < len; ++i) { > + s = caml_alloc (2, 0); > + v = caml_copy_int64 (a[i].length); > + Store_field (s, 0, v); > + v = caml_copy_int64 (a[i].flags); > + Store_field (s, 1, v); > + Store_field (rv, i, s); > + } > + > + CAMLreturn (rv); > +} > + > /* Convert a Unix.sockaddr to a C struct sockaddr. */ > void > nbd_internal_unix_sockaddr_to_sa (value sockaddrv,(19) I'd suggest the following addition:> diff --git a/ocaml/helpers.c b/ocaml/helpers.c > index 09666dafa7d1..db652943141d 100644 > --- a/ocaml/helpers.c > +++ b/ocaml/helpers.c > @@ -25,6 +25,7 @@ > #include <string.h> > #include <sys/socket.h> > #include <assert.h> > +#include <inttypes.h> > > #include <caml/alloc.h> > #include <caml/callback.h> > @@ -140,6 +141,16 @@ nbd_internal_ocaml_alloc_extent64_array (nbd_extent *a, size_t len) > CAMLlocal3 (s, v, rv); > size_t i; > > + for (i = 0; i < len; ++i) > + if (a[i].length > INT64_MAX || a[i].flags > INT64_MAX) { > + char errmsg[256]; > + > + snprintf (errmsg, sizeof errmsg, > + "%s: extent[%zu] = { .length = %"PRIu64", .flags = %"PRIu64"}", > + __func__, i, a[i].length, a[i].flags); > + caml_failwith (errmsg); > + } > + > rv = caml_alloc (len, 0); > for (i = 0; i < len; ++i) { > s = caml_alloc (2, 0);*However*, considering the nbd_internal_ocaml_alloc_extent64_array() call site, in the generated extent64_wrapper_locked() function [ocaml/nbd-c.c]:> /* Wrapper for extent64 callback. */ > static int > extent64_wrapper_locked (void *user_data, const char *metacontext, > uint64_t offset, nbd_extent *entries, > size_t nr_entries, int *error) > { > CAMLparam0 (); > CAMLlocal4 (metacontextv, offsetv, entriesv, errorv); > CAMLlocal2 (exn, rv); > const struct user_data *data = user_data; > int r; > value args[4]; > > metacontextv = caml_copy_string (metacontext); > offsetv = caml_copy_int64 (offset); > entriesv = nbd_internal_ocaml_alloc_extent64_array ( > entries, > nr_entries > ); > errorv = caml_alloc_tuple (1); > Store_field (errorv, 0, Val_int (*error)); > args[0] = metacontextv; > args[1] = offsetv; > args[2] = entriesv; > args[3] = errorv; > rv = caml_callbackN_exn (data->fnv, 4, args); > *error = Int_val (Field (errorv, 0)); > if (Is_exception_result (rv)) { > nbd_internal_ocaml_exception_in_wrapper ("extent64", rv); > CAMLreturnT (int, -1); > } > > r = Int_val (rv); > assert (r >= 0); > CAMLreturnT (int, r); > }I'm not sure if raising an OCaml exception like this, in an *inner* C function, is appropriate. caml_failwith() may only be suitable for C functions *directly* called by the OCaml runtime. If that's the case, then we should propagate the error from nbd_internal_ocaml_alloc_extent64_array() differently. We might have to change the signature of nbd_internal_ocaml_alloc_extent64_array(), and then the generated call site (i.e., extent64_wrapper_locked()) must take a potential failure into account. This is really hard to reason about, as the only "prior art", namely extent_wrapper_locked() -> nbd_internal_ocaml_alloc_i64_from_u32_array(), does not face such a conundrum. There we took the easy way: we convert from uint32_t to OCaml's int64, which cannot overflow. Again, this is a huge patch; I've now spent almost a full workday on reviewing it. I suggest splitting each language to its own patch; thus, we'd get at least 5 patches (core generator changes + 4 languages). If necessary to placate the OCaml compiler, I'd try introducing bare-bones place-holder code at first in the generator for each language, in the first ("core") patch, and then elaborate on each language in a separate patch (replacing the placeholders with actual code). Laszlo