[PATCH nbdkit 1/2] ocaml: Simplify NBDKit.set_error
Using the function code_of_unix_error from <caml/unixsupport.h> we can greatly simplify this function. This function was added in OCaml 4.01 which is ≤ 4.03 that we currently require, and also ≤ 4.02.2 that was the minimum version supported since the OCaml plugin was added in 2014. See: https://github.com/ocaml/ocaml/issues/4812 This does require a small change ot how OCaml plugins are linked -- we now need to link them with the standard OCaml Unix library (unix.cmxa). This commit also adds a comprehensive end-to-end test of error codes. --- plugins/cc/nbdkit-cc-plugin.pod | 4 +- plugins/ocaml/nbdkit-ocaml-plugin.pod | 2 +- plugins/ocaml/Makefile.am | 2 +- tests/Makefile.am | 22 ++++-- plugins/ocaml/NBDKit.ml | 25 +------ plugins/ocaml/bindings.c | 22 +----- tests/test-cc-ocaml.sh | 2 +- tests/cc_shebang.ml | 2 +- tests/test-ocaml-errorcodes.c | 97 +++++++++++++++++++++++++++ tests/test_ocaml_errorcodes_plugin.ml | 33 +++++++++ .gitignore | 1 + 11 files changed, 158 insertions(+), 54 deletions(-) diff --git a/plugins/cc/nbdkit-cc-plugin.pod b/plugins/cc/nbdkit-cc-plugin.pod index 0fe0d9ea..be4019f9 100644 --- a/plugins/cc/nbdkit-cc-plugin.pod +++ b/plugins/cc/nbdkit-cc-plugin.pod @@ -89,7 +89,7 @@ C<CC=g++> as a parameter to exec nbdkit. =head2 Using this plugin with OCaml nbdkit cc CC=ocamlopt \ - CFLAGS="-output-obj -runtime-variant _pic NBDKit.cmx -cclib -lnbdkitocaml" \ + CFLAGS="-output-obj -runtime-variant _pic unix.cmxa NBDKit.cmx -cclib -lnbdkitocaml" \ source.ml OCaml plugin scripts can be created using this trick: @@ -97,7 +97,7 @@ OCaml plugin scripts can be created using this trick: (*/.)>/dev/null 2>&1 exec nbdkit cc "$0" \ CC=ocamlopt \ - CFLAGS="-output-obj -runtime-variant _pic NBDKit.cmx -cclib -lnbdkitocaml" \ + CFLAGS="-output-obj -runtime-variant _pic unix.cmxa NBDKit.cmx -cclib -lnbdkitocaml" \ "$@" *) (* followed by OCaml code for the plugin here *) diff --git a/plugins/ocaml/nbdkit-ocaml-plugin.pod b/plugins/ocaml/nbdkit-ocaml-plugin.pod index 293f8143..efeb2240 100644 --- a/plugins/ocaml/nbdkit-ocaml-plugin.pod +++ b/plugins/ocaml/nbdkit-ocaml-plugin.pod @@ -53,7 +53,7 @@ using this command: ocamlopt.opt -output-obj -runtime-variant _pic \ -o nbdkit-myplugin-plugin.so \ - NBDKit.cmx myplugin.ml \ + unix.cmxa NBDKit.cmx myplugin.ml \ -cclib -lnbdkitocaml You can then use C<nbdkit-myplugin-plugin.so> as an nbdkit plugin (see diff --git a/plugins/ocaml/Makefile.am b/plugins/ocaml/Makefile.am index 1082fc0a..fcf3396d 100644 --- a/plugins/ocaml/Makefile.am +++ b/plugins/ocaml/Makefile.am @@ -81,7 +81,7 @@ noinst_SCRIPTS = nbdkit-ocamlexample-plugin.so nbdkit-ocamlexample-plugin.so: example.cmx libnbdkitocaml.la NBDKit.cmi NBDKit.cmx $(OCAMLOPT) $(OCAMLOPTFLAGS) \ -output-obj -runtime-variant _pic -o $@ \ - NBDKit.cmx $< \ + unix.cmxa NBDKit.cmx $< \ -cclib -L.libs -cclib -lnbdkitocaml example.cmx: example.ml NBDKit.cmi NBDKit.cmx $(OCAMLOPT) $(OCAMLOPTFLAGS) -c $< -o $@ diff --git a/tests/Makefile.am b/tests/Makefile.am index 4f63f9ab..20f5b06a 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1065,9 +1065,8 @@ EXTRA_DIST += test-zero.sh if HAVE_OCAML LIBGUESTFS_TESTS += test-ocaml +LIBNBD_TESTS += test-ocaml-errorcodes -# This is somewhat different from the other tests because we have -# to build an actual plugin here. test_ocaml_SOURCES = test-ocaml.c test.h test_ocaml_CFLAGS = \ $(WARNINGS_CFLAGS) \ @@ -1075,15 +1074,30 @@ test_ocaml_CFLAGS = \ $(NULL) test_ocaml_LDADD = libtest.la $(LIBGUESTFS_LIBS) -check_SCRIPTS += test-ocaml-plugin.so +test_ocaml_errorcodes_SOURCES = test-ocaml-errorcodes.c +test_ocaml_errorcodes_CFLAGS = $(WARNINGS_CFLAGS) $(LIBNBD_CFLAGS) +test_ocaml_errorcodes_LDADD = $(LIBNBD_LIBS) + +check_SCRIPTS += \ + test-ocaml-plugin.so \ + test-ocaml-errorcodes-plugin.so + test-ocaml-plugin.so: test_ocaml_plugin.cmx ../plugins/ocaml/libnbdkitocaml.la ../plugins/ocaml/NBDKit.cmi ../plugins/ocaml/NBDKit.cmx $(OCAMLOPT) $(OCAMLOPTFLAGS) -I ../plugins/ocaml \ -output-obj -runtime-variant _pic -o $@ \ - NBDKit.cmx $< \ + unix.cmxa NBDKit.cmx $< \ -cclib -L../plugins/ocaml/.libs -cclib -lnbdkitocaml test_ocaml_plugin.cmx: test_ocaml_plugin.ml ../plugins/ocaml/NBDKit.cmi ../plugins/ocaml/NBDKit.cmx $(OCAMLOPT) $(OCAMLOPTFLAGS) -I ../plugins/ocaml -c $< -o $@ +test-ocaml-errorcodes-plugin.so: test_ocaml_errorcodes_plugin.cmx ../plugins/ocaml/libnbdkitocaml.la ../plugins/ocaml/NBDKit.cmi ../plugins/ocaml/NBDKit.cmx + $(OCAMLOPT) $(OCAMLOPTFLAGS) -I ../plugins/ocaml \ + -output-obj -runtime-variant _pic -o $@ \ + unix.cmxa NBDKit.cmx $< \ + -cclib -L../plugins/ocaml/.libs -cclib -lnbdkitocaml +test_ocaml_errorcodes_plugin.cmx: test_ocaml_errorcodes_plugin.ml ../plugins/ocaml/NBDKit.cmi ../plugins/ocaml/NBDKit.cmx + $(OCAMLOPT) $(OCAMLOPTFLAGS) -I ../plugins/ocaml -c $< -o $@ + endif HAVE_OCAML EXTRA_DIST += \ diff --git a/plugins/ocaml/NBDKit.ml b/plugins/ocaml/NBDKit.ml index c9ce31b5..d28992c8 100644 --- a/plugins/ocaml/NBDKit.ml +++ b/plugins/ocaml/NBDKit.ml @@ -220,30 +220,7 @@ let register_plugin plugin = (* Bindings to nbdkit server functions. *) -external _set_error : int -> unit = "ocaml_nbdkit_set_error" [@@noalloc] - -let set_error unix_error = - (* There's an awkward triple translation going on here, because - * OCaml Unix.error codes, errno on the host system, and NBD_* - * errnos are not all the same integer value. Plus we cannot - * read the host system errno values from OCaml. - *) - let nbd_error = - match unix_error with - | Unix.EPERM -> 1 - | Unix.EIO -> 2 - | Unix.ENOMEM -> 3 - | Unix.EINVAL -> 4 - | Unix.ENOSPC -> 5 - | Unix.ESHUTDOWN -> 6 - | Unix.EOVERFLOW -> 7 - | Unix.EOPNOTSUPP -> 8 - | Unix.EROFS -> 9 - | Unix.EFBIG -> 10 - | _ -> 4 (* EINVAL *) in - - _set_error nbd_error - +external set_error : Unix.error -> unit = "ocaml_nbdkit_set_error" [@@noalloc] external parse_size : string -> int64 = "ocaml_nbdkit_parse_size" external parse_bool : string -> bool = "ocaml_nbdkit_parse_bool" external read_password : string -> string = "ocaml_nbdkit_read_password" diff --git a/plugins/ocaml/bindings.c b/plugins/ocaml/bindings.c index a6d57084..ba95fb4a 100644 --- a/plugins/ocaml/bindings.c +++ b/plugins/ocaml/bindings.c @@ -42,6 +42,7 @@ #include <caml/memory.h> #include <caml/mlvalues.h> #include <caml/threads.h> +#include <caml/unixsupport.h> #define NBDKIT_API_VERSION 2 #include <nbdkit-plugin.h> @@ -54,26 +55,7 @@ NBDKIT_DLL_PUBLIC value ocaml_nbdkit_set_error (value nv) { - int err; - - switch (Int_val (nv)) { - /* Host errno values that will map to NBD protocol values */ - case 1: err = EPERM; break; - case 2: err = EIO; break; - case 3: err = ENOMEM; break; - case 4: err = EINVAL; break; - case 5: err = ENOSPC; break; - case 6: err = ESHUTDOWN; break; - case 7: err = EOVERFLOW; break; - case 8: err = EOPNOTSUPP; break; - /* Other errno values that server/protocol.c treats specially */ - case 9: err = EROFS; break; - case 10: err = EFBIG; break; - default: abort (); - } - - nbdkit_set_error (err); - + nbdkit_set_error (code_of_unix_error (nv)); return Val_unit; } diff --git a/tests/test-cc-ocaml.sh b/tests/test-cc-ocaml.sh index e47bf26f..9ba7ee98 100755 --- a/tests/test-cc-ocaml.sh +++ b/tests/test-cc-ocaml.sh @@ -58,6 +58,6 @@ cleanup_fn rm -f $out rm -f $out nbdkit -U - cc $script a=1 b=2 c=3 \ - CC="$OCAMLOPT" CFLAGS="-output-obj -runtime-variant _pic -I $SRCDIR/../plugins/ocaml NBDKit.cmx -cclib -lnbdkitocaml" \ + CC="$OCAMLOPT" CFLAGS="-output-obj -runtime-variant _pic -I $SRCDIR/../plugins/ocaml unix.cmxa NBDKit.cmx -cclib -lnbdkitocaml" \ --run 'nbdinfo --size $uri' > $out test "$(cat $out)" -eq $((512 * 2048)) diff --git a/tests/cc_shebang.ml b/tests/cc_shebang.ml index 05036284..a6c79039 100755 --- a/tests/cc_shebang.ml +++ b/tests/cc_shebang.ml @@ -4,7 +4,7 @@ # shell as an impossible command which is ignored. The line below is # run by the shell and ignored by OCaml. -exec nbdkit cc "$0" CC=ocamlopt CFLAGS="-output-obj -runtime-variant _pic NBDKit.cmx -cclib -lnbdkitocaml" "$@" +exec nbdkit cc "$0" CC=ocamlopt CFLAGS="-output-obj -runtime-variant _pic unix.cmxa NBDKit.cmx -cclib -lnbdkitocaml" "$@" *) open Printf diff --git a/tests/test-ocaml-errorcodes.c b/tests/test-ocaml-errorcodes.c new file mode 100644 index 00000000..25397f48 --- /dev/null +++ b/tests/test-ocaml-errorcodes.c @@ -0,0 +1,97 @@ +/* nbdkit + * Copyright (C) 2013-2021 Red Hat Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Red Hat nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* See also test_ocaml_errorcodes_plugin.ml */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <assert.h> + +#include <libnbd.h> + +int +main (int argc, char *argv[]) +{ + struct nbd_handle *nbd; + char buf[512]; + + nbd = nbd_create (); + if (nbd == NULL) { + fprintf (stderr, "%s\n", nbd_get_error ()); + exit (EXIT_FAILURE); + } + + char *args[] = { + "nbdkit", "-s", "--exit-with-parent", + "./test-ocaml-errorcodes-plugin.so", NULL + }; + if (nbd_connect_command (nbd, args) == -1) { + fprintf (stderr, "%s\n", nbd_get_error ()); + exit (EXIT_FAILURE); + } + + /* Reading at various sector offsets in this plugin should produce + * predictable error codes. + */ + assert (nbd_pread (nbd, buf, 512, 0, 0) == 0); + + assert (nbd_pread (nbd, buf, 512, 1*512, 0) == -1); + printf ("%s %d\n", nbd_get_error (), nbd_get_errno ()); + fflush (stdout); + assert (nbd_get_errno () == EPERM); + + assert (nbd_pread (nbd, buf, 512, 2*512, 0) == -1); + printf ("%s %d\n", nbd_get_error (), nbd_get_errno ()); + fflush (stdout); + assert (nbd_get_errno () == EIO); + + assert (nbd_pread (nbd, buf, 512, 3*512, 0) == -1); + printf ("%s %d\n", nbd_get_error (), nbd_get_errno ()); + fflush (stdout); + assert (nbd_get_errno () == ENOMEM); + + assert (nbd_pread (nbd, buf, 512, 4*512, 0) == -1); + printf ("%s %d\n", nbd_get_error (), nbd_get_errno ()); + fflush (stdout); + assert (nbd_get_errno () == ESHUTDOWN); + + assert (nbd_pread (nbd, buf, 512, 5*512, 0) == -1); + printf ("%s %d\n", nbd_get_error (), nbd_get_errno ()); + fflush (stdout); + assert (nbd_get_errno () == EINVAL); + + nbd_close (nbd); + exit (EXIT_SUCCESS); +} diff --git a/tests/test_ocaml_errorcodes_plugin.ml b/tests/test_ocaml_errorcodes_plugin.ml new file mode 100644 index 00000000..6a628794 --- /dev/null +++ b/tests/test_ocaml_errorcodes_plugin.ml @@ -0,0 +1,33 @@ +open Unix + +let open_connection _ = () + +let get_size () = Int64.of_int (6 * 512) + +let pread () count offset _ = + let count = Int32.to_int count + and offset = Int64.to_int offset in + + (* Depending on the sector requested (offset), return a different + * error code. + *) + match offset / 512 with + | 0 -> (* good, return data *) String.make count '\000' + | 1 -> NBDKit.set_error EPERM; failwith "EPERM" + | 2 -> NBDKit.set_error EIO; failwith "EIO" + | 3 -> NBDKit.set_error ENOMEM; failwith "ENOMEM" + | 4 -> NBDKit.set_error ESHUTDOWN; failwith "ESHUTDOWN" + | 5 -> NBDKit.set_error EINVAL; failwith "EINVAL" + | _ -> assert false + +let plugin = { + NBDKit.default_callbacks with + NBDKit.name = "test-ocaml-errorcodes"; + version = NBDKit.version (); + + open_connection = Some open_connection; + get_size = Some get_size; + pread = Some pread; +} + +let () = NBDKit.register_plugin plugin diff --git a/.gitignore b/.gitignore index 91cbc810..4e2ae75d 100644 --- a/.gitignore +++ b/.gitignore @@ -146,6 +146,7 @@ plugins/*/*.3 /tests/test-nbd /tests/test-null /tests/test-ocaml +/tests/test-ocaml-errorcodes /tests/test-offset /tests/test-oldstyle /tests/test-old-plugins-*.sh -- 2.32.0
On RHEL 7 (GCC 4.8.5) we don't have __builtin_add_overflow and similar functions. They were first added in GCC 5. Provide a fallback path for these older compilers. Note that the fallback path does not actually implement overflow detection. --- configure.ac | 6 ++++++ common/include/checked-overflow.h | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/configure.ac b/configure.ac index 8980fdd4..30330813 100644 --- a/configure.ac +++ b/configure.ac @@ -220,6 +220,12 @@ CFLAGS="$old_CFLAGS" AC_MSG_RESULT([$supports_std_c90]) AM_CONDITIONAL([CAN_TEST_ANSI_C], [test "x$supports_std_c90" = "xyes"]) +dnl Check for __builtin_*_overflow. We need the dummy parameters +dnl else detection doesn't work correctly for some reason. +AC_CHECK_DECLS([__builtin_add_overflow(int, int, int *), + __builtin_mul_overflow(int, int, int *)], + [], [], []) + dnl On Haiku we must use BSD-compatibility headers to get the endian dnl macros we use. AC_MSG_CHECKING(whether OS-dependent include paths are required) diff --git a/common/include/checked-overflow.h b/common/include/checked-overflow.h index ddc4b487..c683b5a9 100644 --- a/common/include/checked-overflow.h +++ b/common/include/checked-overflow.h @@ -49,11 +49,19 @@ /* Add two values. *r = a + b * Returns true if overflow happened. */ +#if HAVE_DECL___BUILTIN_ADD_OVERFLOW #define ADD_OVERFLOW(a, b, r) __builtin_add_overflow((a), (b), (r)) +#else +#define ADD_OVERFLOW(a, b, r) (*(r) = (a) + (b), 0) +#endif /* Multiply two values. *r = a * b * Returns true if overflow happened. */ +#if HAVE_DECL___BUILTIN_MUL_OVERFLOW #define MUL_OVERFLOW(a, b, r) __builtin_mul_overflow((a), (b), (r)) +#else +#define MUL_OVERFLOW(a, b, r) (*(r) = (a) * (b), 0) +#endif #endif /* NBDKIT_CHECKED_OVERFLOW_H */ -- 2.32.0
On 11/15/21 13:14, Richard W.M. Jones wrote:
On RHEL 7 (GCC 4.8.5) we don't have __builtin_add_overflow and similar functions. They were first added in GCC 5. Provide a fallback path for these older compilers. Note that the fallback path does not actually implement overflow detection. --- configure.ac | 6 ++++++ common/include/checked-overflow.h | 8 ++++++++ 2 files changed, 14 insertions(+)
(1) I think that APIs that lie are wrong. I could contribute standard C implementations if we really think nbdkit should continue building on RHEL7. The difference with the built-ins should only be in performance, not in functionality. (2) Should nbdkit continue building on RHEL7? In the OCaml upgrade thread <https://listman.redhat.com/archives/libguestfs/2021-November/msg00095.html> I thought we practically abandoned RHEL7 for the v2v projects. (3) Red Hat Developer Toolset 10 (and 11 Beta) are available for RHEL7: https://developers.redhat.com/products/developertoolset/overview https://access.redhat.com/documentation/en-us/red_hat_developer_toolset/10/h... https://access.redhat.com/documentation/en-us/red_hat_developer_toolset/11-b... providing gcc 10 and 11 respectively. We could say that building nbdkit on RHEL7 requires devtoolset. Thanks, Laszlo
diff --git a/configure.ac b/configure.ac index 8980fdd4..30330813 100644 --- a/configure.ac +++ b/configure.ac @@ -220,6 +220,12 @@ CFLAGS="$old_CFLAGS" AC_MSG_RESULT([$supports_std_c90]) AM_CONDITIONAL([CAN_TEST_ANSI_C], [test "x$supports_std_c90" = "xyes"])
+dnl Check for __builtin_*_overflow. We need the dummy parameters +dnl else detection doesn't work correctly for some reason. +AC_CHECK_DECLS([__builtin_add_overflow(int, int, int *), + __builtin_mul_overflow(int, int, int *)], + [], [], []) + dnl On Haiku we must use BSD-compatibility headers to get the endian dnl macros we use. AC_MSG_CHECKING(whether OS-dependent include paths are required) diff --git a/common/include/checked-overflow.h b/common/include/checked-overflow.h index ddc4b487..c683b5a9 100644 --- a/common/include/checked-overflow.h +++ b/common/include/checked-overflow.h @@ -49,11 +49,19 @@ /* Add two values. *r = a + b * Returns true if overflow happened. */ +#if HAVE_DECL___BUILTIN_ADD_OVERFLOW #define ADD_OVERFLOW(a, b, r) __builtin_add_overflow((a), (b), (r)) +#else +#define ADD_OVERFLOW(a, b, r) (*(r) = (a) + (b), 0) +#endif
/* Multiply two values. *r = a * b * Returns true if overflow happened. */ +#if HAVE_DECL___BUILTIN_MUL_OVERFLOW #define MUL_OVERFLOW(a, b, r) __builtin_mul_overflow((a), (b), (r)) +#else +#define MUL_OVERFLOW(a, b, r) (*(r) = (a) * (b), 0) +#endif
#endif /* NBDKIT_CHECKED_OVERFLOW_H */
On Mon, Nov 15, 2021 at 02:11:01PM +0100, Laszlo Ersek wrote:
(2) Should nbdkit continue building on RHEL7? In the OCaml upgrade thread <https://listman.redhat.com/archives/libguestfs/2021-November/msg00095.html> I thought we practically abandoned RHEL7 for the v2v projects.
If I sound schizophrenic on RHEL 7 that'll be because I'm in two minds still about supporting it. In this particular case I was trying to test the OCaml changes on the oldest version of OCaml I have easy access to and found that there were various problems compiling on RHEL 7. I fixed the other problems, but this fix was a bit more controversial.
(3) Red Hat Developer Toolset 10 (and 11 Beta) are available for RHEL7:
https://developers.redhat.com/products/developertoolset/overview
https://access.redhat.com/documentation/en-us/red_hat_developer_toolset/10/h...
https://access.redhat.com/documentation/en-us/red_hat_developer_toolset/11-b...
providing gcc 10 and 11 respectively.
We could say that building nbdkit on RHEL7 requires devtoolset.
This doesn't sound very convenient, but there's a stronger reason to keep stuff working with GCC 3/4 and that's because OpenBSD sticks with GCC 4.2 as it was the last GPLv2+ version. I think the reason OpenBSD does this is wrong, but that's their choice. (Of course they have Clang).
(1) I think that APIs that lie are wrong. I could contribute standard C implementations if we really think nbdkit should continue building on RHEL7. The difference with the built-ins should only be in performance, not in functionality.
I agree. If you want to have a go at standard C versions that would be great. Even unsigned addition looks very hard. I found this lengthy paper on the subject: https://www.cs.utah.edu/~regehr/papers/overflow12.pdf Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 100 libraries supported. http://fedoraproject.org/wiki/MinGW
(... long email) On 11/17/21 19:07, Richard W.M. Jones wrote:
On Mon, Nov 15, 2021 at 02:11:01PM +0100, Laszlo Ersek wrote:
(2) Should nbdkit continue building on RHEL7? In the OCaml upgrade thread <https://listman.redhat.com/archives/libguestfs/2021-November/msg00095.html> I thought we practically abandoned RHEL7 for the v2v projects.
If I sound schizophrenic on RHEL 7 that'll be because I'm in two minds still about supporting it.
In this particular case I was trying to test the OCaml changes on the oldest version of OCaml I have easy access to and found that there were various problems compiling on RHEL 7. I fixed the other problems, but this fix was a bit more controversial.
(3) Red Hat Developer Toolset 10 (and 11 Beta) are available for RHEL7:
https://developers.redhat.com/products/developertoolset/overview
https://access.redhat.com/documentation/en-us/red_hat_developer_toolset/10/h...
https://access.redhat.com/documentation/en-us/red_hat_developer_toolset/11-b...
providing gcc 10 and 11 respectively.
We could say that building nbdkit on RHEL7 requires devtoolset.
This doesn't sound very convenient, but there's a stronger reason to keep stuff working with GCC 3/4 and that's because OpenBSD sticks with GCC 4.2 as it was the last GPLv2+ version. I think the reason OpenBSD does this is wrong, but that's their choice. (Of course they have Clang).
(1) I think that APIs that lie are wrong. I could contribute standard C implementations if we really think nbdkit should continue building on RHEL7. The difference with the built-ins should only be in performance, not in functionality.
I agree. If you want to have a go at standard C versions that would be great. Even unsigned addition looks very hard.
I found this lengthy paper on the subject: https://www.cs.utah.edu/~regehr/papers/overflow12.pdf
I've not checked the paper, but I can say two things at once: - "~regehr" means it's going to be great :) - signed integers are a huge complication, while at the same time, their use (or usefulness) is extremely limited for dealing with memory allocations, sizes, indexing, file offsets, and so on. I wouldn't try to do this in a type-transparent manner. I'd first revert commit b31859402d14 ("common/include/checked-overflow.h: Simplify", 2021-11-11), and then add separate functions for uint64_t and size_t. If we don't want to be 100% standard C, just make it work for gcc-4.2, then a type-generic(-ish) solution could exist using "typeof" and (perhaps) statement expressions. (Both are GCC extensions that exist in 4.2: - <https://gcc.gnu.org/onlinedocs/gcc-4.2.4/gcc/Statement-Exprs.html> - <https://gcc.gnu.org/onlinedocs/gcc-4.2.4/gcc/Typeof.html>.) To go into some details: (1) The idea is that we want the following C expression to evaluate to true, without OP overflowing first: a OP b <= max where "a", "b", "max" are all of the same unsigned integer type, and OP is either "+" or "*". (2) In case OP is "+": a + b <= max we can always subtract "b" from both sides: a <= max - b This condition is checkable in C as-is, because "max - b" cannot underflow. If the condition evaluates to true, then "a + b" won't overflow. (3) In case OP is "*": a * b <= max we need to see if "b" is zero, at first. If "b" is zero, the multiplication is safe. If "b" is nonzero, then we can divide both sides by it: a <= max / b This condition is checkable in C as-is, because "max / b" cannot wrap, or involve a division by zero. However, unlike in the addition/subtraction case (2), the *equivalence* of this "rearrangement as a division" with the original a * b <= max is not immediately clear, given that the "/" operator in C (between integer operands) is not an exact division, but division with a remainder. As the standard says, "the result ... is the algebraic quotient with any fractional part discarded". The usual way to prove "equivalence" between two logical formulae is to prove "implication" in both directions. That is, assuming "b" is nonzero, let's prove both of the following implications between C-language expressions: (a <= max / b) IMPLIES (a * b <= max) [1] (a * b <= max) IMPLIES (a <= max / b) [2] (5) At first, let's express what the max / b == q [3] expression in C means, expressed *mathematically*. That way, we can investigate [1] and [2] in pure math. The C expression [3] means, mathematically speaking: max = q * b + r [4] where "q" is a nonnegative integer, and "r" is a nonnegative integer strictly smaller than "b". And, this decomposition is unique, given any pair of (max, b). (And we posited earlier that "b" is nonzero, of course.) (6) Now, going back to [1] and [2]: - wherever we have a (max / b) C-language expression, substitute "q" (from [3]; - wherever we have a standalone "max" expression, substitute "q * b + r" (from [4]). We get the following *mathematical* (not C!) expressions: (a <= q) IMPLIES (a * b <= q * b + r) [5] (a * b <= q * b + r) IMPLIES (a <= q) [6] (7) Implication [5] is trivially true, as we multiply both sides of the premise inequality (a <= q) with the positive integer "b" at first: a * b <= q * b and then add the nonnegative integer "r" to the right hand side only: a * b <= q * b + r (8) Implication [6] is slightly more tricky. First, let's divide both sides of the premise inequality (a * b <= q * b + r) with the positive integer "b": a * b q * b + r ----- <= --------- b b That simplifies to: a <= q + (r/b) [7] Here's where we consider the restriction on "r" from [4]: "r" is a nonnegative integer *strictly smaller* than "b". That gives us, for the (r/b) addend in separation: 0 <= (r / b) < 1 Which means that we can tack another (strict) inequality to the right side of [7]: a <= q + (r/b) < q + 1 [8] ^^^^^^^ (Because, adding (r/b) to "q" produces a smaller number than adding 1 to "q" does.) Considering the two far sides of [8] together, we get a < q + 1 Then, as both "a" and "q" are integers, this is equivalent to a <= q which is what we wanted to reach in [6]. (9) And so the C-language functions are just: bool add_size_t_overflow (size_t a, size_t b, size_t *result) { if (a <= SIZE_MAX - b) { *result = a + b; return false; } return true; } bool mul_size_t_overflow (size_t a, size_t b, size_t *result) { if (b == 0 || a <= SIZE_MAX / b) { *result = a * b; return false; } return true; } (10) The (unsigned) type-generic macros could look something like this. First, we'd need to check if the types of "a", "b" and "result" were (i) integers, (ii) unsigned, and (iii) had identical range. If not, we should get a compilation failure (static assert, of sorts). Let's assume at least that none of our integer types have padding bits, and that signed integer types use two's complement. (Although the signed representation may be irrelevant in the discussion below.) I don't consider bit-fields at all because they are pure evil anyway. :) (i) The "integer" check for any arithmetic type can be done by dividing ((type)1) with 2. Every floating point type can represent 1/2 exactly (so the result compares uneqal to zero), and for every integer type, the result is zero. (ii) The "unsigned" check can be done by seeing if ((type)-1) is positive. This works even for _Bool, plus -1 is safely expressible by any signed integer type (IOW even if the check fails, it is safe to evaluate). (iii) "identical range" can be expressed by ((type1)-1 == (type2)-1), with the additional -- perhaps redundant -- restriction (sizeof a == sizeof b). This relies on our assumption about integer representation (no padding bits). The static assert can be done by intentionally violating a C constraint (which in turn requires a diagnostic to be emitted), dependent on an integer constant expression that combines (i), (ii) and (iii). A common trick is to create an array typedef, where the size of the array is -1 (if the condition we want to enforce fails) vs. +1 (otherwise). (11) Then we could do this, for example: #include <stdbool.h> #include <string.h> /* This macro does not evaluate any one of its arguments, unless the argument * has variably modified type. */ #define HAVE_SAME_UINT_TYPES(a, b) \ ((typeof (a))1 / 2 == 0 && \ (typeof (b))1 / 2 == 0 && \ (typeof (a))-1 > 0 && \ (typeof (b))-1 > 0 && \ (typeof (a))-1 == (typeof (b))-1 && \ sizeof a == sizeof b) /* This macro evaluates each of "a", "b" and "r" exactly once, assuming neither * has variably modified type. */ #define ADD_UINT_OVERFLOW(a, b, r) \ ({ \ typedef char static_assert_1[-1 + 2 * HAVE_SAME_UINT_TYPES(a, b)]; \ typedef char static_assert_2[-1 + 2 * HAVE_SAME_UINT_TYPES(a, *r)]; \ typeof (a) a2 = a, b2 = b, max = -1, result; \ void *r2 = r; \ bool overflow = true; \ \ if (a2 <= max - b2) { \ result = a2 + b2; \ memcpy(r2, &result, sizeof result); \ overflow = false; \ } \ overflow; \ }) Note that using this would require a bit more pedantry than usual at the *call sites*; for example, adding constants "1" and "2u" would not work. TBH, I think the macros I created above are hideous. I like call sites to be explicit about types, and so I'd prefer the much simpler; type-specific add_size_t_overflow() and mul_size_t_overflow() functions. (Beyond reverting b31859402d14, I'd even replace the type-specific ADD_UINT64_T_OVERFLOW, MUL_UINT64_T_OVERFLOW, ADD_SIZE_T_OVERFLOW, MUL_SIZE_T_OVERFLOW function-like macros with actual functions. Just let the compiler inline whatever it likes.) Comments? Thanks Laszlo
some additional comments: On 11/18/21 13:39, Laszlo Ersek wrote:
The usual way to prove "equivalence" between two logical formulae is to prove "implication" in both directions. That is, assuming "b" is nonzero, let's prove both of the following implications between C-language expressions:
(a <= max / b) IMPLIES (a * b <= max) [1]
(a * b <= max) IMPLIES (a <= max / b) [2]
- We can call [1] "correctness": wherever our reformulated condition works, we're sure to avoid overflow. I.e., our reworked formula isn't "too lax" (= a security issue). - We can call [2] "completeness": wherever our original condition can be satisfied, our reformulated condition will evaluate to "true" as well. In other words, our reworked formula isn't "too strict" (= a needless limit). - In both [1] and [2], it is understood (as I posited initially) that the multiplication operation "a * b" does not overflow. We posit that for the sake of argument; after the (proven) rearrangements, we'll only use multiplication if it indeed does not overflow. So perhaps it would be more precise to call [1] and [2] mathematical expressions already (not C expressions), just redefine "/" to be the C-style division (but let * have infinite precision).
/* This macro does not evaluate any one of its arguments, unless the argument * has variably modified type. */ #define HAVE_SAME_UINT_TYPES(a, b) \ ((typeof (a))1 / 2 == 0 && \ (typeof (b))1 / 2 == 0 && \ (typeof (a))-1 > 0 && \ (typeof (b))-1 > 0 && \ (typeof (a))-1 == (typeof (b))-1 && \ sizeof a == sizeof b)
Sorry, forgot the parens around (a) and (b) in the sizeof operands -- normally they are not needed, but here "a" and "b" are macro arguments, so the parents are needed. Thanks, Laszlo
On Thu, Nov 18, 2021 at 01:39:39PM +0100, Laszlo Ersek wrote:
(... long email)
And quite insightful. I'm trimming to portions where I'm replying...
- signed integers are a huge complication, while at the same time, their use (or usefulness) is extremely limited for dealing with memory allocations, sizes, indexing, file offsets, and so on.
Actually, the emacs project has been moving towards explicitly using signed types for memory sizes. Why? Because compilers have modes where they can diagnose signed overflows, but the C standard requires unsigned types to perform module 2^N arithmetic without warning. So using a signed type (gnulib calls it idx_t) that is otherwise the same width as size_t makes it possible for the compiler to help diagnose non-portable code. But yse, writing code that avoids overflow using signed types is a much trickier proposition.
I wouldn't try to do this in a type-transparent manner. I'd first revert commit b31859402d14 ("common/include/checked-overflow.h: Simplify", 2021-11-11), and then add separate functions for uint64_t and size_t.
Writing functions would allow a and b to undergo integer promotions, but the return type pointer forces an explicit choice of the result type. Unless we tell the compiler to warn on narrowing of integers to a function call, there is a risk that on 32-bit machines, someone passing a 64-bit off_t value to a 32-bit size_t overflow checker will undergo inadvertent overflow prior to the function getting a chance to perform its checks. One of the benefits of the gcc/clang builtin is that they are type-agnostic, even if I want to perform off_t*int and store the result in size_t, the builtin will correctly tell me if the infinite-precision result would overflow the destination, without me having to figure out whether promotion or implicit narrowing affected things. Using a function loses some of that benefit when compared to a fully-generic macro. However, writing a fully-generic macro that correctly handles mismatched operand types is indeed harder.
If we don't want to be 100% standard C, just make it work for gcc-4.2, then a type-generic(-ish) solution could exist using "typeof" and (perhaps) statement expressions. (Both are GCC extensions that exist in 4.2: - <https://gcc.gnu.org/onlinedocs/gcc-4.2.4/gcc/Statement-Exprs.html> - <https://gcc.gnu.org/onlinedocs/gcc-4.2.4/gcc/Typeof.html>.)
Indeed, that's also an intermediate option - we require gcc (even on RHEL 7) for our automatic cleanup usage, so relying on the features present in the oldest supported gcc is acceptable, rather than having to strive for strict C compliance.
To go into some details:
(1) The idea is that we want the following C expression to evaluate to true, without OP overflowing first:
a OP b <= max
where "a", "b", "max" are all of the same unsigned integer type, and OP is either "+" or "*".
Your analysis proceeds with all of "a", "b", and "max" of the same unsigned types. I understand why you skipped signed types (the analysis is harder); but it it worth supporting cases where "a" and "b" are possibly different widths? The gnulib file intprops.h does this, but of course, due to the incompatible licensing, me repeating how it does it here is not in our best interest (however, I will point out that it results in a MUCH larger preprocessor expansion than your simpler solution that enforces same size inputs).
(9) And so the C-language functions are just:
bool add_size_t_overflow (size_t a, size_t b, size_t *result) { if (a <= SIZE_MAX - b) { *result = a + b; return false; } return true;
The compiler builtins ALSO assign the low-order bits of the infinite-precision operation into *result on overflow, behaving more like: *result = a + b; return ! (a <= SIZE_MAX - b); for unsigned types (where C has guaranteed modulo semantics), and a much more complicated solution with signed types (basically, perform the operation in the unsigned counterparts, and then cast those low-order bits back to signed at the end).
}
bool mul_size_t_overflow (size_t a, size_t b, size_t *result) { if (b == 0 || a <= SIZE_MAX / b) { *result = a * b; return false; } return true; }
(10) The (unsigned) type-generic macros could look something like this. First, we'd need to check if the types of "a", "b" and "result" were (i) integers, (ii) unsigned, and (iii) had identical range.
Check (i) is a nice safety valve. Check (ii) is essential to avoid the nastiness that would be required for correct results with signed types, but with a larger macro, we could indeed support it (gnulib demonstrates that). But I'm inclined to insist on (ii) until we encounter a case where we really care about signed overflow; insisting that we use unsigned sizes (contrary to emacs' choice of using a signed idx_t type for compiler assistance) is easier to maintain, even if it reduces the places where the compiler can help us. Check (iii) is the one I question as being strictly necessary; if we can correctly predict the size of the result after arithmetic promotion, we can determine the maximum value that we must fit within even if the inputs underwent a size change. ...
I don't consider bit-fields at all because they are pure evil anyway. :)
Too true! ...
(11) Then we could do this, for example:
#include <stdbool.h> #include <string.h>
/* This macro does not evaluate any one of its arguments, unless the argument * has variably modified type. */ #define HAVE_SAME_UINT_TYPES(a, b) \ ((typeof (a))1 / 2 == 0 && \ (typeof (b))1 / 2 == 0 && \ (typeof (a))-1 > 0 && \ (typeof (b))-1 > 0 && \ (typeof (a))-1 == (typeof (b))-1 && \ sizeof a == sizeof b)
Yes, this is relying on gcc's typeof extension, but we said above that should be okay.
/* This macro evaluates each of "a", "b" and "r" exactly once, assuming neither * has variably modified type. */ #define ADD_UINT_OVERFLOW(a, b, r) \ ({ \ typedef char static_assert_1[-1 + 2 * HAVE_SAME_UINT_TYPES(a, b)]; \ typedef char static_assert_2[-1 + 2 * HAVE_SAME_UINT_TYPES(a, *r)]; \
We could perhaps relax this to: typedef char static_assert[-1 + 2 * HAVE_SAME_UINT_TYPES((a)+(b), *r)]; \ if we merely care about the promoted result type having the same type as where we are storing the result, and if we also assert that sizeof(*r)>=sizeof(int) (for result types smaller than int, the logic is a lot messier, because of arithmetic promotion).
typeof (a) a2 = a, b2 = b, max = -1, result; \ void *r2 = r; \ bool overflow = true; \ \ if (a2 <= max - b2) { \ result = a2 + b2; \ memcpy(r2, &result, sizeof result); \ overflow = false; \ } \ overflow; \ })
Note that using this would require a bit more pedantry than usual at the *call sites*; for example, adding constants "1" and "2u" would not work.
Per the above, constant "1" would never work (it is a signed constant). That's why figuring out a macro that works with arbitrary type inputs, so long as their integer promotion doesn't overflow (which is what the compiler builtins do) is nicer, but indeed harder. Using a function instead of a macro gets you the implicit promotion (you can pass 1 to a function taking size_t, whereas the macro computing typeof(1) will see a signed type).
TBH, I think the macros I created above are hideous. I like call sites to be explicit about types, and so I'd prefer the much simpler; type-specific add_size_t_overflow() and mul_size_t_overflow() functions.
Your macros were not too hideous, but only because they made limiting assumptions like forcing unsigned types and not caring about arithmetic promotion. The gnulib macros are more generic, but even more hideous.
(Beyond reverting b31859402d14, I'd even replace the type-specific ADD_UINT64_T_OVERFLOW, MUL_UINT64_T_OVERFLOW, ADD_SIZE_T_OVERFLOW, MUL_SIZE_T_OVERFLOW function-like macros with actual functions. Just let the compiler inline whatever it likes.)
Comments?
As long as we have good unit tests for the particular use cases we care about detecting, I can live with either a generic macro or with type-specific functional interfaces. I'm a bit worried that using functions may fail to catch overflow on the implicit type conversions of arguments passed to the function; maybe it's possible to do a hybrid, by writing a function that checks for type equality but then calls a function for the actual overflow check, so that we are ensured that callers use the correct types, but have the ease of reading the overflow check in a function that doesn't require even more macro expansions to see what it is doing. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org
On 11/18/21 16:27, Eric Blake wrote:
On Thu, Nov 18, 2021 at 01:39:39PM +0100, Laszlo Ersek wrote:
(... long email)
And quite insightful. I'm trimming to portions where I'm replying...
- signed integers are a huge complication, while at the same time, their use (or usefulness) is extremely limited for dealing with memory allocations, sizes, indexing, file offsets, and so on.
Actually, the emacs project has been moving towards explicitly using signed types for memory sizes. Why? Because compilers have modes where they can diagnose signed overflows, but the C standard requires unsigned types to perform module 2^N arithmetic without warning. So using a signed type (gnulib calls it idx_t) that is otherwise the same width as size_t makes it possible for the compiler to help diagnose non-portable code.
But yse, writing code that avoids overflow using signed types is a much trickier proposition.
I wouldn't try to do this in a type-transparent manner. I'd first revert commit b31859402d14 ("common/include/checked-overflow.h: Simplify", 2021-11-11), and then add separate functions for uint64_t and size_t.
Writing functions would allow a and b to undergo integer promotions, but the return type pointer forces an explicit choice of the result type. Unless we tell the compiler to warn on narrowing of integers to a function call, there is a risk that on 32-bit machines, someone passing a 64-bit off_t value to a 32-bit size_t overflow checker will undergo inadvertent overflow prior to the function getting a chance to perform its checks.
One of the benefits of the gcc/clang builtin is that they are type-agnostic, even if I want to perform off_t*int and store the result in size_t, the builtin will correctly tell me if the infinite-precision result would overflow the destination, without me having to figure out whether promotion or implicit narrowing affected things. Using a function loses some of that benefit when compared to a fully-generic macro. However, writing a fully-generic macro that correctly handles mismatched operand types is indeed harder.
If we don't want to be 100% standard C, just make it work for gcc-4.2, then a type-generic(-ish) solution could exist using "typeof" and (perhaps) statement expressions. (Both are GCC extensions that exist in 4.2: - <https://gcc.gnu.org/onlinedocs/gcc-4.2.4/gcc/Statement-Exprs.html> - <https://gcc.gnu.org/onlinedocs/gcc-4.2.4/gcc/Typeof.html>.)
Indeed, that's also an intermediate option - we require gcc (even on RHEL 7) for our automatic cleanup usage, so relying on the features present in the oldest supported gcc is acceptable, rather than having to strive for strict C compliance.
To go into some details:
(1) The idea is that we want the following C expression to evaluate to true, without OP overflowing first:
a OP b <= max
where "a", "b", "max" are all of the same unsigned integer type, and OP is either "+" or "*".
Your analysis proceeds with all of "a", "b", and "max" of the same unsigned types. I understand why you skipped signed types (the analysis is harder); but it it worth supporting cases where "a" and "b" are possibly different widths? The gnulib file intprops.h does this, but of course, due to the incompatible licensing, me repeating how it does it here is not in our best interest (however, I will point out that it results in a MUCH larger preprocessor expansion than your simpler solution that enforces same size inputs).
I imagine we could take each input (i.e., "a", "b") as uintmax_t, perform the addition / multiplication in uintmax_t (with the safeguards I proposed earlier adapted to UINTMAX_MAX), and then explicitly compare the result against the maximum of the desired result type, such as UINT64_MAX, SIZE_MAX, and so on. I didn't think of this because I'm acutely aware of types, promotions etc when I write C code, so "these two types must be the same" is not something I'd be bothered by.
(9) And so the C-language functions are just:
bool add_size_t_overflow (size_t a, size_t b, size_t *result) { if (a <= SIZE_MAX - b) { *result = a + b; return false; } return true;
The compiler builtins ALSO assign the low-order bits of the infinite-precision operation into *result on overflow, behaving more like:
*result = a + b; return ! (a <= SIZE_MAX - b);
Ah, interesting. I didn't notice that. I figured "output is indeterminate on error" is generally acceptable. Is there a particular use for the low-order bits, on overflow?
for unsigned types (where C has guaranteed modulo semantics), and a much more complicated solution with signed types (basically, perform the operation in the unsigned counterparts, and then cast those low-order bits back to signed at the end).
}
bool mul_size_t_overflow (size_t a, size_t b, size_t *result) { if (b == 0 || a <= SIZE_MAX / b) { *result = a * b; return false; } return true; }
(10) The (unsigned) type-generic macros could look something like this. First, we'd need to check if the types of "a", "b" and "result" were (i) integers, (ii) unsigned, and (iii) had identical range.
Check (i) is a nice safety valve. Check (ii) is essential to avoid the nastiness that would be required for correct results with signed types, but with a larger macro, we could indeed support it (gnulib demonstrates that). But I'm inclined to insist on (ii) until we encounter a case where we really care about signed overflow; insisting that we use unsigned sizes (contrary to emacs' choice of using a signed idx_t type for compiler assistance) is easier to maintain, even if it reduces the places where the compiler can help us. Check (iii) is the one I question as being strictly necessary; if we can correctly predict the size of the result after arithmetic promotion, we can determine the maximum value that we must fit within even if the inputs underwent a size change.
Yes, this might work with the above (vague) uintmax_t-based approach; we could use ((typeof (*r))-1) as the desired actual limit.
...
I don't consider bit-fields at all because they are pure evil anyway. :)
Too true!
...
(11) Then we could do this, for example:
#include <stdbool.h> #include <string.h>
/* This macro does not evaluate any one of its arguments, unless the argument * has variably modified type. */ #define HAVE_SAME_UINT_TYPES(a, b) \ ((typeof (a))1 / 2 == 0 && \ (typeof (b))1 / 2 == 0 && \ (typeof (a))-1 > 0 && \ (typeof (b))-1 > 0 && \ (typeof (a))-1 == (typeof (b))-1 && \ sizeof a == sizeof b)
Yes, this is relying on gcc's typeof extension, but we said above that should be okay.
/* This macro evaluates each of "a", "b" and "r" exactly once, assuming neither * has variably modified type. */ #define ADD_UINT_OVERFLOW(a, b, r) \ ({ \ typedef char static_assert_1[-1 + 2 * HAVE_SAME_UINT_TYPES(a, b)]; \ typedef char static_assert_2[-1 + 2 * HAVE_SAME_UINT_TYPES(a, *r)]; \
We could perhaps relax this to: typedef char static_assert[-1 + 2 * HAVE_SAME_UINT_TYPES((a)+(b), *r)]; \
if we merely care about the promoted result type having the same type as where we are storing the result, and if we also assert that sizeof(*r)>=sizeof(int) (for result types smaller than int, the logic is a lot messier, because of arithmetic promotion).
Right, I didn't consider types with lesser integer conversion rank than that of "unsigned int" -- I don't think we'd use "short" for memory allocation, sizes, indexing, file offsets etc. I wouldn't want to be more gnulib than gnulib :) (Not that I looked at it -- I intentionally didn't.)
typeof (a) a2 = a, b2 = b, max = -1, result; \ void *r2 = r; \ bool overflow = true; \ \ if (a2 <= max - b2) { \ result = a2 + b2; \ memcpy(r2, &result, sizeof result); \ overflow = false; \ } \ overflow; \ })
Note that using this would require a bit more pedantry than usual at the *call sites*; for example, adding constants "1" and "2u" would not work.
Per the above, constant "1" would never work (it is a signed constant). That's why figuring out a macro that works with arbitrary type inputs, so long as their integer promotion doesn't overflow (which is what the compiler builtins do) is nicer, but indeed harder. Using a function instead of a macro gets you the implicit promotion (you can pass 1 to a function taking size_t, whereas the macro computing typeof(1) will see a signed type).
TBH, I think the macros I created above are hideous. I like call sites to be explicit about types, and so I'd prefer the much simpler; type-specific add_size_t_overflow() and mul_size_t_overflow() functions.
Your macros were not too hideous, but only because they made limiting assumptions like forcing unsigned types and not caring about arithmetic promotion. The gnulib macros are more generic, but even more hideous.
(Beyond reverting b31859402d14, I'd even replace the type-specific ADD_UINT64_T_OVERFLOW, MUL_UINT64_T_OVERFLOW, ADD_SIZE_T_OVERFLOW, MUL_SIZE_T_OVERFLOW function-like macros with actual functions. Just let the compiler inline whatever it likes.)
Comments?
As long as we have good unit tests for the particular use cases we care about detecting, I can live with either a generic macro or with type-specific functional interfaces. I'm a bit worried that using functions may fail to catch overflow on the implicit type conversions of arguments passed to the function; maybe it's possible to do a hybrid, by writing a function that checks for type equality but then calls a function for the actual overflow check, so that we are ensured that callers use the correct types, but have the ease of reading the overflow check in a function that doesn't require even more macro expansions to see what it is doing.
Let me try this (later, only a sketch now): - use macros for checking that the input and output types are (ii) unsigned (i) integer types, ignore their (iii) particular ranges - convert the inputs to uintmax_t and perform the operation in uintmax_t unconditionally, only *remembering* -- using the same in-advance checks -- if UINTMAX_MAX is overflowed, - deduce the actual result limit from ((typeof (*r))-1), - assign the low-order bits in any case -- if either the uintmax_t operation overflows or the actual result limit is exceeded, it all happens in unsigned, so it's well-defined (modular reduction). Return the overflow status correctly. Thanks, Laszlo
On Thu, Nov 18, 2021 at 06:24:09PM +0100, Laszlo Ersek wrote:
Your analysis proceeds with all of "a", "b", and "max" of the same unsigned types. I understand why you skipped signed types (the analysis is harder); but it it worth supporting cases where "a" and "b" are possibly different widths? The gnulib file intprops.h does this, but of course, due to the incompatible licensing, me repeating how it does it here is not in our best interest (however, I will point out that it results in a MUCH larger preprocessor expansion than your simpler solution that enforces same size inputs).
I imagine we could take each input (i.e., "a", "b") as uintmax_t, perform the addition / multiplication in uintmax_t (with the safeguards I proposed earlier adapted to UINTMAX_MAX), and then explicitly compare the result against the maximum of the desired result type, such as UINT64_MAX, SIZE_MAX, and so on.
I didn't think of this because I'm acutely aware of types, promotions etc when I write C code, so "these two types must be the same" is not something I'd be bothered by.
It's also a tradeoff on how generic we happen to be, vs. whether all our callers actually do use the same size. If the code is easier to maintain by insisting on callers matching certain patterns, that's okay as long as we can easily detect callers that fail those patterns. But if the cost of detecting out-of-spec callers is worse than just biting the bullet and writing the code to support mixed types, then being fully generic wins out.
(9) And so the C-language functions are just:
bool add_size_t_overflow (size_t a, size_t b, size_t *result) { if (a <= SIZE_MAX - b) { *result = a + b; return false; } return true;
The compiler builtins ALSO assign the low-order bits of the infinite-precision operation into *result on overflow, behaving more like:
*result = a + b; return ! (a <= SIZE_MAX - b);
Ah, interesting. I didn't notice that. I figured "output is indeterminate on error" is generally acceptable. Is there a particular use for the low-order bits, on overflow?
I suspect there may be some backwards compatibility at play, and most hardware is already able to give us that for free. Even though you're right that most sane users of these macros won't use the result on overflow, I'm more worried about the subtle bugs that would occur on less-tested platforms if we don't actually provide a deterministic value (particularly if most of our testing occurs on platforms where the compiler builtins do have deterministic behavior). ...
Right, I didn't consider types with lesser integer conversion rank than that of "unsigned int" -- I don't think we'd use "short" for memory allocation, sizes, indexing, file offsets etc.
While it sounds impractical, I do have to wonder if anyone has ever tried to detect running out of TCP ports (a "short") by using overflow checking.
I wouldn't want to be more gnulib than gnulib :) (Not that I looked at it -- I intentionally didn't.)
And that's a good thing - since I'm tainted by knowing what gnulib has done, I'm trying to make sure I keep it possible for you to come up with a clean-room implementation if we want it, even though I shouldn't write it myself. ...
Comments?
As long as we have good unit tests for the particular use cases we care about detecting, I can live with either a generic macro or with type-specific functional interfaces. I'm a bit worried that using functions may fail to catch overflow on the implicit type conversions of arguments passed to the function; maybe it's possible to do a hybrid, by writing a function that checks for type equality but then calls a function for the actual overflow check, so that we are ensured that callers use the correct types, but have the ease of reading the overflow check in a function that doesn't require even more macro expansions to see what it is doing.
Let me try this (later, only a sketch now):
- use macros for checking that the input and output types are (ii) unsigned (i) integer types, ignore their (iii) particular ranges
- convert the inputs to uintmax_t and perform the operation in uintmax_t unconditionally, only *remembering* -- using the same in-advance checks -- if UINTMAX_MAX is overflowed,
Perhaps you can do this with a function signature like: bool check_op_overflow (uint64_t a, uint64_t b, uint64_t max, uint64_t *r)
- deduce the actual result limit from ((typeof (*r))-1),
- assign the low-order bits in any case -- if either the uintmax_t operation overflows or the actual result limit is exceeded, it all happens in unsigned, so it's well-defined (modular reduction). Return the overflow status correctly.
and then utilize it something like: #define CHECK_OP_OVERFLOW(a, b, r) \ ({ \ bool overflow; uint64_t tmp; \ CHECK_UNSIGNED_INT (a); CHECK_UNSIGNED_INT (b); \ overflow = check_op_overflow (a, b, (typeof (*(r)))-1, &tmp); \ *(r) = tmp; \ overflow; \ }) and maybe even have CHECK_UNSIGNED_INT() permit signed values >= 0, rather than insisting that the underlying type be unsigned. At any rate, the approach sounds good to me. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org
On 11/18/21 19:12, Eric Blake wrote:
Perhaps you can do this with a function signature like:
bool check_op_overflow (uint64_t a, uint64_t b, uint64_t max, uint64_t *r)
- deduce the actual result limit from ((typeof (*r))-1),
- assign the low-order bits in any case -- if either the uintmax_t operation overflows or the actual result limit is exceeded, it all happens in unsigned, so it's well-defined (modular reduction). Return the overflow status correctly.
and then utilize it something like:
#define CHECK_OP_OVERFLOW(a, b, r) \ ({ \ bool overflow; uint64_t tmp; \ CHECK_UNSIGNED_INT (a); CHECK_UNSIGNED_INT (b); \ overflow = check_op_overflow (a, b, (typeof (*(r)))-1, &tmp); \ *(r) = tmp; \ overflow; \ })
and maybe even have CHECK_UNSIGNED_INT() permit signed values >= 0, rather than insisting that the underlying type be unsigned.
At any rate, the approach sounds good to me.
How about this: -------- #include <stdbool.h> #include <stdint.h> /* Assert, at compile time, that the expression "x" has some unsigned integer * type. * * The expression "x" is not evaluated, unless it has variably modified type. */ #define STATIC_ASSERT_UNSIGNED_INT(x) \ do { \ typedef char x_has_uint_type[(typeof (x))-1 > 0 ? 1 : -1]; \ } while (0) /* Assign the sum (a + b) to (*r), using uintmax_t modular arithmetic. * * Return true iff the addition overflows or the result exceeds (max). */ static inline bool check_add_overflow (uintmax_t a, uintmax_t b, uintmax_t max, uintmax_t *r) { *r = a + b; return a > UINTMAX_MAX - b || *r > max; } /* Assign the product (a * b) to (*r), using uintmax_t modular arithmetic. * * Return true iff the multiplication overflows or the result exceeds (max). */ static inline bool check_mul_overflow (uintmax_t a, uintmax_t b, uintmax_t max, uintmax_t *r) { *r = a * b; return b > 0 && (a > UINTMAX_MAX / b || *r > max); } /* Add (a) and (b), both of (possibly different) unsigned integer types, and * store the sum in (*r), which must also have some unsigned integer type. * * Each macro argument is evaluated exactly once, as long as it does not have * variably modified type. * * The macro evaluates to (false) if (*r) can represent the mathematical sum. * Otherwise, the macro evaluates to (true), and the low order bits of the * mathematical sum are stored to (*r). */ #define ADD_OVERFLOW(a, b, r) \ ({ \ bool overflow; \ uintmax_t tmp; \ \ STATIC_ASSERT_UNSIGNED_INT (a); \ STATIC_ASSERT_UNSIGNED_INT (b); \ STATIC_ASSERT_UNSIGNED_INT (*(r)); \ overflow = check_add_overflow ((a), (b), (typeof (*(r)))-1, &tmp); \ *(r) = tmp; \ overflow; \ }) /* Multiply (a) and (b), both of (possibly different) unsigned integer types, * and store the product in (*r), which must also have some unsigned integer * type. * * Each macro argument is evaluated exactly once, as long as it does not have * variably modified type. * * The macro evaluates to (false) if (*r) can represent the mathematical * product. Otherwise, the macro evaluates to (true), and the low order bits of * the mathematical product are stored to (*r). */ #define MUL_OVERFLOW(a, b, r) \ ({ \ bool overflow; \ uintmax_t tmp; \ \ STATIC_ASSERT_UNSIGNED_INT (a); \ STATIC_ASSERT_UNSIGNED_INT (b); \ STATIC_ASSERT_UNSIGNED_INT (*(r)); \ overflow = check_mul_overflow ((a), (b), (typeof (*(r)))-1, &tmp); \ *(r) = tmp; \ overflow; \ }) -------- Questions: (1) What is the usual style in nbdkit to highlight parameters of functions and function-like macros in documentation (comments)? Here I used parentheses. Normally I use quotes "". (2) In the actual functions, we return "true" for "overflow". My original conditions expressed "no overflow". In this source code, I *open-coded* the negations of my original conditions, using De Morgan's laws. That's because I dislike expressions with an outermost "!" operator -- whenever I face such an expression, I always work the outermost negation *into* the formula, as deeply as I can. However, I realize the resultant conditions may not be easy to read this way. Here's an alternative for the body of check_add_overflow(), based on the original formulation: register bool in_range; *r = a + b; in_range = a <= UINTMAX_MAX - b && *r <= max; return !in_range; (3) In the overflow conditions, I used parentheses as sparingly as possible; only when needed. I could also use as many as possible. - Compare, for the addition: return ((a > UINTMAX_MAX - b) || (*r > max)); or in_range = ((a <= UINTMAX_MAX - b) && (*r <= max)); return !in_range; - Compare, for the multiplication: return ((b > 0) && ((a > UINTMAX_MAX / b) || (*r > max))); or in_range = ((b == 0) || ((a <= UINTMAX_MAX / b) && (*r <= max))); return !in_range; What's the preferred form? Thanks, Laszlo
On Fri, Nov 19, 2021 at 03:35:48PM +0100, Laszlo Ersek wrote:
(1) What is the usual style in nbdkit to highlight parameters of functions and function-like macros in documentation (comments)? Here I used parentheses. Normally I use quotes "".
We don't have one, so quotes are fine.
(2) In the actual functions, we return "true" for "overflow". My original conditions expressed "no overflow". In this source code, I *open-coded* the negations of my original conditions, using De Morgan's laws. That's because I dislike expressions with an outermost "!" operator -- whenever I face such an expression, I always work the outermost negation *into* the formula, as deeply as I can.
However, I realize the resultant conditions may not be easy to read this way. Here's an alternative for the body of check_add_overflow(), based on the original formulation:
register bool in_range;
*r = a + b; in_range = a <= UINTMAX_MAX - b && *r <= max; return !in_range;
Seems reasonable.
(3) In the overflow conditions, I used parentheses as sparingly as possible; only when needed. I could also use as many as possible.
- Compare, for the addition:
return ((a > UINTMAX_MAX - b) || (*r > max));
or
in_range = ((a <= UINTMAX_MAX - b) && (*r <= max)); return !in_range;
- Compare, for the multiplication:
return ((b > 0) && ((a > UINTMAX_MAX / b) || (*r > max)));
or
in_range = ((b == 0) || ((a <= UINTMAX_MAX / b) && (*r <= max))); return !in_range;
What's the preferred form?
Personally I use fewest parens, but indicate precedence by dropping whitespace, but I guess whatever works. However you definitely don't need the parens around the entire return value, C doesn't require that. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com virt-builder quickly builds VMs from scratch http://libguestfs.org/virt-builder.1.html
On Fri, Nov 19, 2021 at 03:15:58PM +0000, Richard W.M. Jones wrote:
On Fri, Nov 19, 2021 at 03:35:48PM +0100, Laszlo Ersek wrote:
(1) What is the usual style in nbdkit to highlight parameters of functions and function-like macros in documentation (comments)? Here I used parentheses. Normally I use quotes "".
We don't have one, so quotes are fine.
(2) In the actual functions, we return "true" for "overflow". My original conditions expressed "no overflow". In this source code, I *open-coded* the negations of my original conditions, using De Morgan's laws. That's because I dislike expressions with an outermost "!" operator -- whenever I face such an expression, I always work the outermost negation *into* the formula, as deeply as I can.
However, I realize the resultant conditions may not be easy to read this way. Here's an alternative for the body of check_add_overflow(), based on the original formulation:
register bool in_range;
I generally avoid 'register' in my C code; the compiler is smart enough to figure that out without me having to do extra typing.
*r = a + b; in_range = a <= UINTMAX_MAX - b && *r <= max; return !in_range;
Seems reasonable.
I can go with either form.
(3) In the overflow conditions, I used parentheses as sparingly as possible; only when needed. I could also use as many as possible.
- Compare, for the addition:
return ((a > UINTMAX_MAX - b) || (*r > max));
or
in_range = ((a <= UINTMAX_MAX - b) && (*r <= max)); return !in_range;
- Compare, for the multiplication:
return ((b > 0) && ((a > UINTMAX_MAX / b) || (*r > max)));
or
in_range = ((b == 0) || ((a <= UINTMAX_MAX / b) && (*r <= max))); return !in_range;
What's the preferred form?
Personally I use fewest parens, but indicate precedence by dropping whitespace, but I guess whatever works. However you definitely don't need the parens around the entire return value, C doesn't require that.
I use extra () when using &, |, << and >> (as those are less frequent operators), but for +, *, <, >, &&, ||, and ==, I generally assume the precedence is well-known enough to elide spurious (). I've seen other projects that use code linters to enforce a particular style. We don't do that here, which means we can probably find exceptions to a number of style suggestions, but it hasn't been enough to bother me. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org
On 11/18/21 16:27, Eric Blake wrote:
On Thu, Nov 18, 2021 at 01:39:39PM +0100, Laszlo Ersek wrote:
(10) The (unsigned) type-generic macros could look something like this. First, we'd need to check if the types of "a", "b" and "result" were (i) integers, (ii) unsigned, and (iii) had identical range.
Check (i) is a nice safety valve.
Meanwhile I've realized that (typeof (a))-1 > 0 covers (i) and (ii) both, because all floating point types can represent -1 (and exactly at that). So there is no need for (typeof (a))1 / 2 == 0 just for (i). Thanks Laszlo
(Catching up ...) The maths looked reasonable. Did you have a version of the patch for review? My only other thought is that a simple set of tests could be good. However it's not worth having tests that only test if __builtin* functions are correct (hopefully GCC is already testing that). So tests would have to check the fallback macros are correct, even if they are not used on the current platform. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com libguestfs lets you edit virtual machines. Supports shell scripting, bindings from many languages. http://libguestfs.org
On 11/22/21 23:31, Richard W.M. Jones wrote:
(Catching up ...)
Welcome back! :)
The maths looked reasonable.
Thanks!
Did you have a version of the patch for review?
No, not yet. Wanted to clear the style questions on the new code (the additions) at first. Next, I'll have to work those additions (the fallback code) into your original patch -- I'll steal the way you check for the necessity of the fallback etc. (Meanwhile I've worked on RHBZ#1931821, with the realization that the "dosfstools" change was inexcusable, and either way, we need "parted" to learn dealing with the bogus partition table.)
My only other thought is that a simple set of tests could be good. However it's not worth having tests that only test if __builtin* functions are correct (hopefully GCC is already testing that). So tests would have to check the fallback macros are correct, even if they are not used on the current platform.
So: the fallbacks need to be available (= built) in the source code unconditionally, so they can be directly called by the test suite. The actual "falling back" to them must be separate. Is that what you mean? Thanks Laszlo
On 11/23/21 11:44, Laszlo Ersek wrote:
On 11/22/21 23:31, Richard W.M. Jones wrote:
(Catching up ...)
Welcome back! :)
The maths looked reasonable.
Thanks!
Did you have a version of the patch for review?
No, not yet. Wanted to clear the style questions on the new code (the additions) at first. Next, I'll have to work those additions (the fallback code) into your original patch -- I'll steal the way you check for the necessity of the fallback etc.
(Meanwhile I've worked on RHBZ#1931821, with the realization that the "dosfstools" change was inexcusable, and either way, we need "parted" to learn dealing with the bogus partition table.)
My only other thought is that a simple set of tests could be good. However it's not worth having tests that only test if __builtin* functions are correct (hopefully GCC is already testing that). So tests would have to check the fallback macros are correct, even if they are not used on the current platform.
So: the fallbacks need to be available (= built) in the source code unconditionally, so they can be directly called by the test suite. The actual "falling back" to them must be separate. Is that what you mean?
... should this go into common/replacements, or common/utils? The former seems like a better fit. On the other hand, libnbd (assuming we'll want to port the same to libnbd) does not have common/replacements at all... Thanks Laszlo
On Tue, Nov 23, 2021 at 04:19:08PM +0100, Laszlo Ersek wrote:
On 11/23/21 11:44, Laszlo Ersek wrote:
So: the fallbacks need to be available (= built) in the source code unconditionally, so they can be directly called by the test suite. The actual "falling back" to them must be separate. Is that what you mean?
... should this go into common/replacements, or common/utils? The former seems like a better fit.
common/replacements is currently used to replace functions which are missing on Windows. Maybe this could be extended for RHEL 7? Probably easier to put it in common/utils/checked-overflow.h TBH (see my original version of the patch which did that and used a configure check to select which version is used).
On the other hand, libnbd (assuming we'll want to port the same to libnbd) does not have common/replacements at all...
Indeed, and libnbd is never likely to get a Windows port. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 100 libraries supported. http://fedoraproject.org/wiki/MinGW
On Tue, Nov 23, 2021 at 11:44:06AM +0100, Laszlo Ersek wrote:
On 11/22/21 23:31, Richard W.M. Jones wrote:
My only other thought is that a simple set of tests could be good. However it's not worth having tests that only test if __builtin* functions are correct (hopefully GCC is already testing that). So tests would have to check the fallback macros are correct, even if they are not used on the current platform.
So: the fallbacks need to be available (= built) in the source code unconditionally, so they can be directly called by the test suite. The actual "falling back" to them must be separate. Is that what you mean?
I just meant that if we have a test suite which tests the end result (macros like "ADD_OVERFLOW"), because we will only do CI on modern systems, it will only ever test that __builtin_add_overflow works. That's pointless because presumably GCC already tests that, and the fallback code would never be tested. So the tests would need to somehow check that the fallback path works. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://people.redhat.com/~rjones/virt-df/
On Tue, Nov 23, 2021 at 03:25:22PM +0000, Richard W.M. Jones wrote:
On Tue, Nov 23, 2021 at 11:44:06AM +0100, Laszlo Ersek wrote:
On 11/22/21 23:31, Richard W.M. Jones wrote:
My only other thought is that a simple set of tests could be good. However it's not worth having tests that only test if __builtin* functions are correct (hopefully GCC is already testing that). So tests would have to check the fallback macros are correct, even if they are not used on the current platform.
So: the fallbacks need to be available (= built) in the source code unconditionally, so they can be directly called by the test suite. The actual "falling back" to them must be separate. Is that what you mean?
I just meant that if we have a test suite which tests the end result (macros like "ADD_OVERFLOW"), because we will only do CI on modern systems, it will only ever test that __builtin_add_overflow works. That's pointless because presumably GCC already tests that, and the fallback code would never be tested. So the tests would need to somehow check that the fallback path works.
Something along the lines of: #if (...gcc or clang new enough...) && !defined(NBDKIT_FALLBACK) # define ADD_OVERFLOW(a, b, r) __builtin_add_overflow((a), (b), (r)) #else # define ADD_OVERFLOW(a, b, r) ...fallback... #endif so that the testsuite can then #define NBDKIT_FALLBACK for testing the fallback code even on new enough compilers. I'm not sure how best to namespace the witness macro for forcing a fallback if the code is copied between nbdkit and libnbd. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org
On 11/23/21 16:37, Eric Blake wrote:
On Tue, Nov 23, 2021 at 03:25:22PM +0000, Richard W.M. Jones wrote:
On Tue, Nov 23, 2021 at 11:44:06AM +0100, Laszlo Ersek wrote:
On 11/22/21 23:31, Richard W.M. Jones wrote:
My only other thought is that a simple set of tests could be good. However it's not worth having tests that only test if __builtin* functions are correct (hopefully GCC is already testing that). So tests would have to check the fallback macros are correct, even if they are not used on the current platform.
So: the fallbacks need to be available (= built) in the source code unconditionally, so they can be directly called by the test suite. The actual "falling back" to them must be separate. Is that what you mean?
I just meant that if we have a test suite which tests the end result (macros like "ADD_OVERFLOW"), because we will only do CI on modern systems, it will only ever test that __builtin_add_overflow works. That's pointless because presumably GCC already tests that, and the fallback code would never be tested. So the tests would need to somehow check that the fallback path works.
Something along the lines of:
#if (...gcc or clang new enough...) && !defined(NBDKIT_FALLBACK) # define ADD_OVERFLOW(a, b, r) __builtin_add_overflow((a), (b), (r)) #else # define ADD_OVERFLOW(a, b, r) ...fallback... #endif
so that the testsuite can then #define NBDKIT_FALLBACK for testing the fallback code even on new enough compilers. I'm not sure how best to namespace the witness macro for forcing a fallback if the code is copied between nbdkit and libnbd.
What I have in mind now is (also incorporating feedback from Rich): - add the functions under "common/utils/checked-overflow.c" unconditionally, add their declarations unconditionally in "common/include/checked-overflow.h" - there should be two sets of macros. Both sets should always perform the typeof / unsigned check, so that the call sites conform to that even if the actual fallback is not used. This is so that we not litter the source code with a bunch of signed arguments, which then blow up in the typeof static asserts once the codebase is compiled on FreeBSD or RHEL7 (even before calling the actual functions). The first set should then call the builtins, the second set should call the functions. - the central macros should be thin wrappers around the above sets of macros (dependent on the builtins' presence) - normal code can call the central macros - the test case should call the one set of lower-level macros that uses the functions. I really hope to start working on a patch soon... Thanks Laszlo
On 11/15/21 13:14, Richard W.M. Jones wrote: (aren't you on PTO?)
Using the function code_of_unix_error from <caml/unixsupport.h> we can greatly simplify this function. This function was added in OCaml 4.01 which is ≤ 4.03 that we currently require, and also ≤ 4.02.2 that was the minimum version supported since the OCaml plugin was added in 2014.
See: https://github.com/ocaml/ocaml/issues/4812
This does require a small change ot how OCaml plugins are linked -- we now need to link them with the standard OCaml Unix library (unix.cmxa).
This commit also adds a comprehensive end-to-end test of error codes. --- plugins/cc/nbdkit-cc-plugin.pod | 4 +- plugins/ocaml/nbdkit-ocaml-plugin.pod | 2 +- plugins/ocaml/Makefile.am | 2 +- tests/Makefile.am | 22 ++++-- plugins/ocaml/NBDKit.ml | 25 +------ plugins/ocaml/bindings.c | 22 +----- tests/test-cc-ocaml.sh | 2 +- tests/cc_shebang.ml | 2 +- tests/test-ocaml-errorcodes.c | 97 +++++++++++++++++++++++++++ tests/test_ocaml_errorcodes_plugin.ml | 33 +++++++++ .gitignore | 1 + 11 files changed, 158 insertions(+), 54 deletions(-)
diff --git a/plugins/cc/nbdkit-cc-plugin.pod b/plugins/cc/nbdkit-cc-plugin.pod index 0fe0d9ea..be4019f9 100644 --- a/plugins/cc/nbdkit-cc-plugin.pod +++ b/plugins/cc/nbdkit-cc-plugin.pod @@ -89,7 +89,7 @@ C<CC=g++> as a parameter to exec nbdkit. =head2 Using this plugin with OCaml
nbdkit cc CC=ocamlopt \ - CFLAGS="-output-obj -runtime-variant _pic NBDKit.cmx -cclib -lnbdkitocaml" \ + CFLAGS="-output-obj -runtime-variant _pic unix.cmxa NBDKit.cmx -cclib -lnbdkitocaml" \ source.ml
OCaml plugin scripts can be created using this trick: @@ -97,7 +97,7 @@ OCaml plugin scripts can be created using this trick: (*/.)>/dev/null 2>&1 exec nbdkit cc "$0" \ CC=ocamlopt \ - CFLAGS="-output-obj -runtime-variant _pic NBDKit.cmx -cclib -lnbdkitocaml" \ + CFLAGS="-output-obj -runtime-variant _pic unix.cmxa NBDKit.cmx -cclib -lnbdkitocaml" \ "$@" *) (* followed by OCaml code for the plugin here *) diff --git a/plugins/ocaml/nbdkit-ocaml-plugin.pod b/plugins/ocaml/nbdkit-ocaml-plugin.pod index 293f8143..efeb2240 100644 --- a/plugins/ocaml/nbdkit-ocaml-plugin.pod +++ b/plugins/ocaml/nbdkit-ocaml-plugin.pod @@ -53,7 +53,7 @@ using this command:
ocamlopt.opt -output-obj -runtime-variant _pic \ -o nbdkit-myplugin-plugin.so \ - NBDKit.cmx myplugin.ml \ + unix.cmxa NBDKit.cmx myplugin.ml \ -cclib -lnbdkitocaml
You can then use C<nbdkit-myplugin-plugin.so> as an nbdkit plugin (see diff --git a/plugins/ocaml/Makefile.am b/plugins/ocaml/Makefile.am index 1082fc0a..fcf3396d 100644 --- a/plugins/ocaml/Makefile.am +++ b/plugins/ocaml/Makefile.am @@ -81,7 +81,7 @@ noinst_SCRIPTS = nbdkit-ocamlexample-plugin.so nbdkit-ocamlexample-plugin.so: example.cmx libnbdkitocaml.la NBDKit.cmi NBDKit.cmx $(OCAMLOPT) $(OCAMLOPTFLAGS) \ -output-obj -runtime-variant _pic -o $@ \ - NBDKit.cmx $< \ + unix.cmxa NBDKit.cmx $< \ -cclib -L.libs -cclib -lnbdkitocaml example.cmx: example.ml NBDKit.cmi NBDKit.cmx $(OCAMLOPT) $(OCAMLOPTFLAGS) -c $< -o $@ diff --git a/tests/Makefile.am b/tests/Makefile.am index 4f63f9ab..20f5b06a 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1065,9 +1065,8 @@ EXTRA_DIST += test-zero.sh if HAVE_OCAML
LIBGUESTFS_TESTS += test-ocaml +LIBNBD_TESTS += test-ocaml-errorcodes
-# This is somewhat different from the other tests because we have -# to build an actual plugin here.
(1) I think this comment removal belongs to a separate patch. (Not a "hard requirement", just saying.) I mean, the change (IIUC) is that now we build *two* plugins, not one (test-ocaml-plugin). So the "difference" with other tests still exists, one would think. However, given that you didn't change the singular "an actual plugin" to the plural "actual plugins", the comment may not be relevant any longer -- the difference to other tests may not exist already. That's fine, but then the command removal should go into a different patch. Not a big deal of course.
test_ocaml_SOURCES = test-ocaml.c test.h test_ocaml_CFLAGS = \ $(WARNINGS_CFLAGS) \ @@ -1075,15 +1074,30 @@ test_ocaml_CFLAGS = \ $(NULL) test_ocaml_LDADD = libtest.la $(LIBGUESTFS_LIBS)
-check_SCRIPTS += test-ocaml-plugin.so +test_ocaml_errorcodes_SOURCES = test-ocaml-errorcodes.c +test_ocaml_errorcodes_CFLAGS = $(WARNINGS_CFLAGS) $(LIBNBD_CFLAGS) +test_ocaml_errorcodes_LDADD = $(LIBNBD_LIBS) + +check_SCRIPTS += \ + test-ocaml-plugin.so \ + test-ocaml-errorcodes-plugin.so + test-ocaml-plugin.so: test_ocaml_plugin.cmx ../plugins/ocaml/libnbdkitocaml.la ../plugins/ocaml/NBDKit.cmi ../plugins/ocaml/NBDKit.cmx $(OCAMLOPT) $(OCAMLOPTFLAGS) -I ../plugins/ocaml \ -output-obj -runtime-variant _pic -o $@ \ - NBDKit.cmx $< \ + unix.cmxa NBDKit.cmx $< \ -cclib -L../plugins/ocaml/.libs -cclib -lnbdkitocaml test_ocaml_plugin.cmx: test_ocaml_plugin.ml ../plugins/ocaml/NBDKit.cmi ../plugins/ocaml/NBDKit.cmx $(OCAMLOPT) $(OCAMLOPTFLAGS) -I ../plugins/ocaml -c $< -o $@
+test-ocaml-errorcodes-plugin.so: test_ocaml_errorcodes_plugin.cmx ../plugins/ocaml/libnbdkitocaml.la ../plugins/ocaml/NBDKit.cmi ../plugins/ocaml/NBDKit.cmx + $(OCAMLOPT) $(OCAMLOPTFLAGS) -I ../plugins/ocaml \ + -output-obj -runtime-variant _pic -o $@ \ + unix.cmxa NBDKit.cmx $< \ + -cclib -L../plugins/ocaml/.libs -cclib -lnbdkitocaml +test_ocaml_errorcodes_plugin.cmx: test_ocaml_errorcodes_plugin.ml ../plugins/ocaml/NBDKit.cmi ../plugins/ocaml/NBDKit.cmx + $(OCAMLOPT) $(OCAMLOPTFLAGS) -I ../plugins/ocaml -c $< -o $@ + endif HAVE_OCAML
EXTRA_DIST += \
(2) Is there a chance that these makefile changes break when building nbdkit against a local build libnbd tree? (I don't think so, but I had better ask.) Because in that case, perhaps I should build test (v2 of) this patch.
diff --git a/plugins/ocaml/NBDKit.ml b/plugins/ocaml/NBDKit.ml index c9ce31b5..d28992c8 100644 --- a/plugins/ocaml/NBDKit.ml +++ b/plugins/ocaml/NBDKit.ml @@ -220,30 +220,7 @@ let register_plugin plugin =
(* Bindings to nbdkit server functions. *)
-external _set_error : int -> unit = "ocaml_nbdkit_set_error" [@@noalloc] - -let set_error unix_error = - (* There's an awkward triple translation going on here, because - * OCaml Unix.error codes, errno on the host system, and NBD_* - * errnos are not all the same integer value. Plus we cannot - * read the host system errno values from OCaml. - *) - let nbd_error = - match unix_error with - | Unix.EPERM -> 1 - | Unix.EIO -> 2 - | Unix.ENOMEM -> 3 - | Unix.EINVAL -> 4 - | Unix.ENOSPC -> 5 - | Unix.ESHUTDOWN -> 6 - | Unix.EOVERFLOW -> 7 - | Unix.EOPNOTSUPP -> 8 - | Unix.EROFS -> 9 - | Unix.EFBIG -> 10 - | _ -> 4 (* EINVAL *) in - - _set_error nbd_error - +external set_error : Unix.error -> unit = "ocaml_nbdkit_set_error" [@@noalloc] external parse_size : string -> int64 = "ocaml_nbdkit_parse_size" external parse_bool : string -> bool = "ocaml_nbdkit_parse_bool" external read_password : string -> string = "ocaml_nbdkit_read_password" diff --git a/plugins/ocaml/bindings.c b/plugins/ocaml/bindings.c index a6d57084..ba95fb4a 100644 --- a/plugins/ocaml/bindings.c +++ b/plugins/ocaml/bindings.c @@ -42,6 +42,7 @@ #include <caml/memory.h> #include <caml/mlvalues.h> #include <caml/threads.h> +#include <caml/unixsupport.h>
#define NBDKIT_API_VERSION 2 #include <nbdkit-plugin.h> @@ -54,26 +55,7 @@ NBDKIT_DLL_PUBLIC value ocaml_nbdkit_set_error (value nv) { - int err; - - switch (Int_val (nv)) { - /* Host errno values that will map to NBD protocol values */ - case 1: err = EPERM; break; - case 2: err = EIO; break; - case 3: err = ENOMEM; break; - case 4: err = EINVAL; break; - case 5: err = ENOSPC; break; - case 6: err = ESHUTDOWN; break; - case 7: err = EOVERFLOW; break; - case 8: err = EOPNOTSUPP; break; - /* Other errno values that server/protocol.c treats specially */ - case 9: err = EROFS; break; - case 10: err = EFBIG; break; - default: abort (); - } - - nbdkit_set_error (err); - + nbdkit_set_error (code_of_unix_error (nv)); return Val_unit; }
diff --git a/tests/test-cc-ocaml.sh b/tests/test-cc-ocaml.sh index e47bf26f..9ba7ee98 100755 --- a/tests/test-cc-ocaml.sh +++ b/tests/test-cc-ocaml.sh @@ -58,6 +58,6 @@ cleanup_fn rm -f $out rm -f $out
nbdkit -U - cc $script a=1 b=2 c=3 \ - CC="$OCAMLOPT" CFLAGS="-output-obj -runtime-variant _pic -I $SRCDIR/../plugins/ocaml NBDKit.cmx -cclib -lnbdkitocaml" \ + CC="$OCAMLOPT" CFLAGS="-output-obj -runtime-variant _pic -I $SRCDIR/../plugins/ocaml unix.cmxa NBDKit.cmx -cclib -lnbdkitocaml" \ --run 'nbdinfo --size $uri' > $out test "$(cat $out)" -eq $((512 * 2048)) diff --git a/tests/cc_shebang.ml b/tests/cc_shebang.ml index 05036284..a6c79039 100755 --- a/tests/cc_shebang.ml +++ b/tests/cc_shebang.ml @@ -4,7 +4,7 @@ # shell as an impossible command which is ignored. The line below is # run by the shell and ignored by OCaml.
-exec nbdkit cc "$0" CC=ocamlopt CFLAGS="-output-obj -runtime-variant _pic NBDKit.cmx -cclib -lnbdkitocaml" "$@" +exec nbdkit cc "$0" CC=ocamlopt CFLAGS="-output-obj -runtime-variant _pic unix.cmxa NBDKit.cmx -cclib -lnbdkitocaml" "$@" *)
open Printf diff --git a/tests/test-ocaml-errorcodes.c b/tests/test-ocaml-errorcodes.c new file mode 100644 index 00000000..25397f48 --- /dev/null +++ b/tests/test-ocaml-errorcodes.c @@ -0,0 +1,97 @@ +/* nbdkit + * Copyright (C) 2013-2021 Red Hat Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Red Hat nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* See also test_ocaml_errorcodes_plugin.ml */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <assert.h> + +#include <libnbd.h> + +int +main (int argc, char *argv[]) +{ + struct nbd_handle *nbd; + char buf[512]; + + nbd = nbd_create (); + if (nbd == NULL) { + fprintf (stderr, "%s\n", nbd_get_error ()); + exit (EXIT_FAILURE); + } + + char *args[] = { + "nbdkit", "-s", "--exit-with-parent", + "./test-ocaml-errorcodes-plugin.so", NULL + };
(3) Style (well, at least my personal preference, not sure about nbdkit standards): this should be static const char * const args[] = ... or at least (cue <https://libguestfs.org/nbd_connect_command.3.html>) static char *args[] = ... and in either case it should go to the top of the block (not mixing declarations with code). Feel free to ignore of course if the pattern is already wide-spread in nbdkit.
+ if (nbd_connect_command (nbd, args) == -1) { + fprintf (stderr, "%s\n", nbd_get_error ()); + exit (EXIT_FAILURE); + } + + /* Reading at various sector offsets in this plugin should produce + * predictable error codes. + */ + assert (nbd_pread (nbd, buf, 512, 0, 0) == 0); + + assert (nbd_pread (nbd, buf, 512, 1*512, 0) == -1); + printf ("%s %d\n", nbd_get_error (), nbd_get_errno ()); + fflush (stdout); + assert (nbd_get_errno () == EPERM); + + assert (nbd_pread (nbd, buf, 512, 2*512, 0) == -1); + printf ("%s %d\n", nbd_get_error (), nbd_get_errno ()); + fflush (stdout); + assert (nbd_get_errno () == EIO); + + assert (nbd_pread (nbd, buf, 512, 3*512, 0) == -1); + printf ("%s %d\n", nbd_get_error (), nbd_get_errno ()); + fflush (stdout); + assert (nbd_get_errno () == ENOMEM); + + assert (nbd_pread (nbd, buf, 512, 4*512, 0) == -1); + printf ("%s %d\n", nbd_get_error (), nbd_get_errno ()); + fflush (stdout); + assert (nbd_get_errno () == ESHUTDOWN); + + assert (nbd_pread (nbd, buf, 512, 5*512, 0) == -1); + printf ("%s %d\n", nbd_get_error (), nbd_get_errno ()); + fflush (stdout); + assert (nbd_get_errno () == EINVAL);
(4) This shouldn't be difficult to turn into a table of { sector, errno } pairs, and a loop. Not necessarily for the space savings, but for ease of reading. (I've found it hard to discern the only two differences between each repetition of the block.) (5) The fflush(stdout) looks weird; if that really matters (e.g. for following the log when it is written to a regular file), then we might want to consider setvbuf() instead. Doesn't matter much if you implement (4), because in that case, the loop body will contain only one fflush().
+ + nbd_close (nbd); + exit (EXIT_SUCCESS); +} diff --git a/tests/test_ocaml_errorcodes_plugin.ml b/tests/test_ocaml_errorcodes_plugin.ml new file mode 100644 index 00000000..6a628794 --- /dev/null +++ b/tests/test_ocaml_errorcodes_plugin.ml @@ -0,0 +1,33 @@ +open Unix + +let open_connection _ = () + +let get_size () = Int64.of_int (6 * 512) + +let pread () count offset _ = + let count = Int32.to_int count + and offset = Int64.to_int offset in + + (* Depending on the sector requested (offset), return a different + * error code. + *) + match offset / 512 with + | 0 -> (* good, return data *) String.make count '\000' + | 1 -> NBDKit.set_error EPERM; failwith "EPERM" + | 2 -> NBDKit.set_error EIO; failwith "EIO" + | 3 -> NBDKit.set_error ENOMEM; failwith "ENOMEM" + | 4 -> NBDKit.set_error ESHUTDOWN; failwith "ESHUTDOWN" + | 5 -> NBDKit.set_error EINVAL; failwith "EINVAL" + | _ -> assert false + +let plugin = { + NBDKit.default_callbacks with + NBDKit.name = "test-ocaml-errorcodes"; + version = NBDKit.version (); + + open_connection = Some open_connection; + get_size = Some get_size; + pread = Some pread; +} + +let () = NBDKit.register_plugin plugin diff --git a/.gitignore b/.gitignore index 91cbc810..4e2ae75d 100644 --- a/.gitignore +++ b/.gitignore @@ -146,6 +146,7 @@ plugins/*/*.3 /tests/test-nbd /tests/test-null /tests/test-ocaml +/tests/test-ocaml-errorcodes /tests/test-offset /tests/test-oldstyle /tests/test-old-plugins-*.sh
Thanks, Laszlo
On Mon, Nov 15, 2021 at 02:00:25PM +0100, Laszlo Ersek wrote:
On 11/15/21 13:14, Richard W.M. Jones wrote:
(aren't you on PTO?)
Eh hem ..
-# This is somewhat different from the other tests because we have -# to build an actual plugin here.
(1) I think this comment removal belongs to a separate patch. (Not a "hard requirement", just saying.)
Done, with clearer explanation: https://gitlab.com/nbdkit/nbdkit/-/commit/6b821fdc4ced4fc0af3fe2480d3ca43cb6...
+ char *args[] = { + "nbdkit", "-s", "--exit-with-parent", + "./test-ocaml-errorcodes-plugin.so", NULL + };
(3) Style (well, at least my personal preference, not sure about nbdkit standards): this should be
static const char * const args[] = ...
or at least (cue <https://libguestfs.org/nbd_connect_command.3.html>)
static char *args[] = ...
and in either case it should go to the top of the block (not mixing declarations with code).
Feel free to ignore of course if the pattern is already wide-spread in nbdkit.
Thie pattern has been copied around over about a dozen tests. How about attached instead? Compound literals are a fun, little know feature!
+ assert (nbd_pread (nbd, buf, 512, 1*512, 0) == -1); + printf ("%s %d\n", nbd_get_error (), nbd_get_errno ()); + fflush (stdout); + assert (nbd_get_errno () == EPERM); + + assert (nbd_pread (nbd, buf, 512, 2*512, 0) == -1); + printf ("%s %d\n", nbd_get_error (), nbd_get_errno ()); + fflush (stdout); + assert (nbd_get_errno () == EIO); + + assert (nbd_pread (nbd, buf, 512, 3*512, 0) == -1); + printf ("%s %d\n", nbd_get_error (), nbd_get_errno ()); + fflush (stdout); + assert (nbd_get_errno () == ENOMEM); + + assert (nbd_pread (nbd, buf, 512, 4*512, 0) == -1); + printf ("%s %d\n", nbd_get_error (), nbd_get_errno ()); + fflush (stdout); + assert (nbd_get_errno () == ESHUTDOWN); + + assert (nbd_pread (nbd, buf, 512, 5*512, 0) == -1); + printf ("%s %d\n", nbd_get_error (), nbd_get_errno ()); + fflush (stdout); + assert (nbd_get_errno () == EINVAL);
(4) This shouldn't be difficult to turn into a table of { sector, errno } pairs, and a loop. Not necessarily for the space savings, but for ease of reading. (I've found it hard to discern the only two differences between each repetition of the block.)
(5) The fflush(stdout) looks weird; if that really matters (e.g. for following the log when it is written to a regular file), then we might want to consider setvbuf() instead. Doesn't matter much if you implement (4), because in that case, the loop body will contain only one fflush().
I fixed this all up in v2 which I'll post shortly. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 100 libraries supported. http://fedoraproject.org/wiki/MinGW
On 11/17/21 18:57, Richard W.M. Jones wrote:
On Mon, Nov 15, 2021 at 02:00:25PM +0100, Laszlo Ersek wrote:
On 11/15/21 13:14, Richard W.M. Jones wrote:
(aren't you on PTO?)
Eh hem ..
-# This is somewhat different from the other tests because we have -# to build an actual plugin here.
(1) I think this comment removal belongs to a separate patch. (Not a "hard requirement", just saying.)
Done, with clearer explanation: https://gitlab.com/nbdkit/nbdkit/-/commit/6b821fdc4ced4fc0af3fe2480d3ca43cb6...
+ char *args[] = { + "nbdkit", "-s", "--exit-with-parent", + "./test-ocaml-errorcodes-plugin.so", NULL + };
(3) Style (well, at least my personal preference, not sure about nbdkit standards): this should be
static const char * const args[] = ...
or at least (cue <https://libguestfs.org/nbd_connect_command.3.html>)
static char *args[] = ...
and in either case it should go to the top of the block (not mixing declarations with code).
Feel free to ignore of course if the pattern is already wide-spread in nbdkit.
Thie pattern has been copied around over about a dozen tests. How about attached instead? Compound literals are a fun, little know feature!
Looks fine to me, thanks! Laszlo
+ assert (nbd_pread (nbd, buf, 512, 1*512, 0) == -1); + printf ("%s %d\n", nbd_get_error (), nbd_get_errno ()); + fflush (stdout); + assert (nbd_get_errno () == EPERM); + + assert (nbd_pread (nbd, buf, 512, 2*512, 0) == -1); + printf ("%s %d\n", nbd_get_error (), nbd_get_errno ()); + fflush (stdout); + assert (nbd_get_errno () == EIO); + + assert (nbd_pread (nbd, buf, 512, 3*512, 0) == -1); + printf ("%s %d\n", nbd_get_error (), nbd_get_errno ()); + fflush (stdout); + assert (nbd_get_errno () == ENOMEM); + + assert (nbd_pread (nbd, buf, 512, 4*512, 0) == -1); + printf ("%s %d\n", nbd_get_error (), nbd_get_errno ()); + fflush (stdout); + assert (nbd_get_errno () == ESHUTDOWN); + + assert (nbd_pread (nbd, buf, 512, 5*512, 0) == -1); + printf ("%s %d\n", nbd_get_error (), nbd_get_errno ()); + fflush (stdout); + assert (nbd_get_errno () == EINVAL);
(4) This shouldn't be difficult to turn into a table of { sector, errno } pairs, and a loop. Not necessarily for the space savings, but for ease of reading. (I've found it hard to discern the only two differences between each repetition of the block.)
(5) The fflush(stdout) looks weird; if that really matters (e.g. for following the log when it is written to a regular file), then we might want to consider setvbuf() instead. Doesn't matter much if you implement (4), because in that case, the loop body will contain only one fflush().
I fixed this all up in v2 which I'll post shortly.
Rich.
participants (3)
-
Eric Blake -
Laszlo Ersek -
Richard W.M. Jones