Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package libnbd for openSUSE:Factory checked in at 2026-06-10 15:51:41 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/libnbd (Old) and /work/SRC/openSUSE:Factory/.libnbd.new.2375 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "libnbd" Wed Jun 10 15:51:41 2026 rev:29 rq:1358246 version:1.25.5 Changes: -------- --- /work/SRC/openSUSE:Factory/libnbd/libnbd.changes 2026-03-19 17:39:02.001926428 +0100 +++ /work/SRC/openSUSE:Factory/.libnbd.new.2375/libnbd.changes 2026-06-10 15:51:59.841844322 +0200 @@ -1,0 +2,47 @@ +Tue Jun 09 16:13:39 UTC 2026 - Charles Arnold <[email protected]> + +- Update to version 1.25.5: + * Version 1.25.5. + * docs: Improve documentation for nbd_add_close_callback(3) + * Rename nbd_set_close_callback to nbd_add_close_callback + * python: Test nbd.set_close_callback method + * ocaml: Add a test of NBD.set_close_callback + * tests: Add a test of new nbd_set_close_callback function + * lib: Add new API to call a close callback on nbd_close + * ocaml: Extend generator to support no-argument closures + * docs/nbd_create.pod: Introduce link to libnbd(3) early + * Version 1.25.4. + * golang: Rename more exported functions + * Version 1.25.3. + * tests/connect-tcp.c: Add trivial test of keepalive features + * lib: Add a new API to allow setting TCP options on the socket + * fuse: Add --keepalive flag + * ublk: Add --keepalive flag + * ublk/nbdublk.pod: Simplify duplicated sections of the synopsis + * lib: Add new APIs to set and get keepalives on the socket + * ci: Rebuild CI files + * ci/manifest.yml: Skip Rust on some platforms + * ci: Allow Rust to be skipped by setting variable $RUST == "skip" + * ci/manifest.yml: Drop freebsd-13, add freebsd-15 + * ci/manifest.yml: Add Alpine 3.23 + * ci/manifest.yml: Drop Alpine 3.21 + * golang: Rename global freeCallbackId to nbd_internal_freeCallbackId + * generator: Compile-time error if a reserved word is used + * python/utils.c: Split up the long get_sockaddr function + * dump/nbddump.pod: Link to nbdkit_debug_hexdump(3) + * Version 1.25.2. + * tests/socket-activation-name.c: Fix test for nbdkit >= 1.47.5 + * lib/utils.c: Fix whitespace + * fuse/operations.c: Remove out of date comment + * python: Check for URI support before running the new tests + * Version 1.25.1. + * python: Add new tests to EXTRA_DIST + * python: Test h.aio_connect using an IP address + * python: Test h.aio_connect with old and new Unix domain socket formats + * python/utils.c: Implement parsing of IP addresses for nbd.aio_connect + * tests/aio-connect-port.c: Delete this test + * tests/aio-connect-port.c: Replace broken link in comment + * configure.ac: Remove use of "which" command + * info: Add nbdinfo --can open + +------------------------------------------------------------------- Old: ---- libnbd-1.24.2.tar.bz2 New: ---- libnbd-1.25.5.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ libnbd.spec ++++++ --- /var/tmp/diff_new_pack.eWsJBR/_old 2026-06-10 15:52:00.953890405 +0200 +++ /var/tmp/diff_new_pack.eWsJBR/_new 2026-06-10 15:52:00.957890570 +0200 @@ -19,7 +19,7 @@ %define sover 0 Name: libnbd -Version: 1.24.2 +Version: 1.25.5 Release: 0 Summary: NBD client library in userspace License: LGPL-2.1-or-later ++++++ _service ++++++ --- /var/tmp/diff_new_pack.eWsJBR/_old 2026-06-10 15:52:00.993892062 +0200 +++ /var/tmp/diff_new_pack.eWsJBR/_new 2026-06-10 15:52:00.997892228 +0200 @@ -1,7 +1,7 @@ <services> <service name="tar_scm" mode="manual"> <param name="filename">libnbd</param> - <param name="revision">v1.24.2</param> + <param name="revision">v1.25.5</param> <param name="scm">git</param> <param name="submodules">disable</param> <param name="url">https://gitlab.com/nbdkit/libnbd.git</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.eWsJBR/_old 2026-06-10 15:52:01.025893388 +0200 +++ /var/tmp/diff_new_pack.eWsJBR/_new 2026-06-10 15:52:01.029893554 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://gitlab.com/nbdkit/libnbd.git</param> - <param name="changesrevision">091731b81e2a9f494db392ad32b1ff6bdca4a471</param></service></servicedata> + <param name="changesrevision">2a0d2262bfd805c4b35b63eb2fcfffb38dfaed71</param></service></servicedata> (No newline at EOF) ++++++ libnbd-1.24.2.tar.bz2 -> libnbd-1.25.5.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/.gitignore new/libnbd-1.25.5/.gitignore --- old/libnbd-1.24.2/.gitignore 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/.gitignore 2026-04-30 12:51:52.000000000 +0200 @@ -214,6 +214,7 @@ /tests/aio-parallel-load-tls /tests/aio-parallel-tls /tests/can-*-flag +/tests/close-callback /tests/close-null /tests/closure-lifetimes /tests/compile-c diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/configure.ac new/libnbd-1.25.5/configure.ac --- old/libnbd-1.24.2/configure.ac 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/configure.ac 2026-04-30 12:51:52.000000000 +0200 @@ -15,7 +15,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -AC_INIT([libnbd],[1.24.2]) +AC_INIT([libnbd],[1.25.5]) AC_CONFIG_MACRO_DIR([m4]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/docs/nbd_create.pod new/libnbd-1.25.5/docs/nbd_create.pod --- old/libnbd-1.24.2/docs/nbd_create.pod 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/docs/nbd_create.pod 2026-04-30 12:51:52.000000000 +0200 @@ -30,7 +30,8 @@ =head1 DESCRIPTION B<struct nbd_handle> is an opaque structure which describes an NBD -client handle and the connection to an NBD server. +client handle and the connection to an NBD server. For an overview of +the NBD client and NBD handles, see L<libnbd(3)>. =head2 Creating a libnbd handle diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/dump/nbddump.pod new/libnbd-1.25.5/dump/nbddump.pod --- old/libnbd-1.24.2/dump/nbddump.pod 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/dump/nbddump.pod 2026-04-30 12:51:52.000000000 +0200 @@ -134,6 +134,7 @@ L<file(1)>, L<qemu-img(1)>, L<nbdkit(1)>, +L<nbdkit_debug_hexdump(3)>, L<qemu-nbd(8)>. =head1 AUTHORS diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/fuse/nbdfuse.c new/libnbd-1.25.5/fuse/nbdfuse.c --- old/libnbd-1.24.2/fuse/nbdfuse.c 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/fuse/nbdfuse.c 2026-04-30 12:51:52.000000000 +0200 @@ -42,6 +42,7 @@ handles nbd = empty_vector; unsigned connections = 4; +bool keepalive = false; bool readonly; bool file_mode = false; struct timespec start_t; @@ -74,7 +75,8 @@ "Mount NBD server as a virtual file:\n" "\n" #ifdef HAVE_LIBXML2 -" nbdfuse [-C N|--connections N] [-d] [-o FUSE-OPTION] [-P PIDFILE]\n" +" nbdfuse [-C N|--connections N] [-d] [--keepalive]\n" +" [-o FUSE-OPTION] [-P PIDFILE]\n" " [-r] [-s] [-v]\n" " MOUNTPOINT[/FILENAME] URI\n" "\n" @@ -165,6 +167,7 @@ enum { HELP_OPTION = CHAR_MAX + 1, FUSE_HELP_OPTION, + KEEPALIVE_OPTION, LONG_OPTIONS, SHORT_OPTIONS, }; @@ -176,8 +179,10 @@ const struct option long_options[] = { { "fuse-help", no_argument, NULL, FUSE_HELP_OPTION }, { "help", no_argument, NULL, HELP_OPTION }, - { "long-options", no_argument, NULL, LONG_OPTIONS }, { "connections", required_argument, NULL, 'C' }, + { "keepalive", no_argument, NULL, KEEPALIVE_OPTION }, + { "keep-alive", no_argument, NULL, KEEPALIVE_OPTION }, + { "long-options", no_argument, NULL, LONG_OPTIONS }, { "pidfile", required_argument, NULL, 'P' }, { "pid-file", required_argument, NULL, 'P' }, { "readonly", no_argument, NULL, 'r' }, @@ -211,6 +216,10 @@ fuse_help (argv[0]); exit (EXIT_SUCCESS); + case KEEPALIVE_OPTION: + keepalive = true; + break; + case LONG_OPTIONS: for (i = 0; long_options[i].name != NULL; ++i) { if (strcmp (long_options[i].name, "long-options") != 0 && @@ -538,6 +547,7 @@ exit (EXIT_FAILURE); } nbd_set_debug (h, verbose); + nbd_set_keepalive (h, keepalive); /* Allow ?tls-psk-file and ?tls-certificates */ nbd_set_uri_allow_local_file (h, true); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/fuse/nbdfuse.pod new/libnbd-1.25.5/fuse/nbdfuse.pod --- old/libnbd-1.24.2/fuse/nbdfuse.pod 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/fuse/nbdfuse.pod 2026-04-30 12:51:52.000000000 +0200 @@ -4,7 +4,8 @@ =head1 SYNOPSIS - nbdfuse [-C N|--connections N] [-d] [-o FUSE-OPTION] [-P PIDFILE] + nbdfuse [-C N|--connections N] [-d] [--keepalive] + [-o FUSE-OPTION] [-P PIDFILE] [-r] [-s] [-v|--verbose] MOUNTPOINT[/FILENAME] URI @@ -186,6 +187,11 @@ Display FUSE options and exit. See I<-o> below. +=item B<--keepalive> + +Set the keepalive (C<"SO_KEEPALIVE">) flag on the connection. See +L<nbd_set_keepalive(3)> and L<socket(7)>. + =item B<-o> FUSE-OPTION Pass extra options to FUSE. To get a list of all the extra options diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/generator/API.ml new/libnbd-1.25.5/generator/API.ml --- old/libnbd-1.24.2/generator/API.ml 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/generator/API.ml 2026-04-30 12:51:52.000000000 +0200 @@ -154,6 +154,10 @@ CBUInt64 "offset"; CBUInt "status"; CBMutable (Int "error") ] } +let close_closure = { + cbname = "close"; + cbargs = [] +} let completion_closure = { cbname = "completion"; cbargs = [ CBMutable (Int "error") ] @@ -186,7 +190,7 @@ cbname = "context"; cbargs = [ CBString "name" ] } -let all_closures = [ chunk_closure; completion_closure; +let all_closures = [ chunk_closure; close_closure; completion_closure; debug_closure; extent_closure; extent64_closure; list_closure; context_closure ] @@ -444,8 +448,9 @@ Libnbd does not use or interpret the value at all (except to return it when you call L<nbd_get_private_data(3)>). In particular, if the -value is a pointer then it is not freed when the handle is closed.|}; - see_also = [Link "get_private_data"]; +value is a pointer then it is not freed when the handle is closed. +If you want that behaviour, see L<nbd_add_close_callback(3)>.|}; + see_also = [Link "get_private_data"; Link "add_close_callback"]; }; "get_private_data", { @@ -4400,6 +4405,116 @@ see_also = [Link "connect_uri"; Link "aio_connect_uri"; Link "supports_uri"; Link "get_uri"]; }; + + "set_keepalive", { + default_call with + args = [Bool "enable"]; ret = RErr; + permitted_states = [ Created ]; + shortdesc = "set keepalive on the socket"; + longdesc = {|Set the keepalive (C<SO_KEEPALIVE>) flag on the +socket after creating it. + +On operating systems and protocols that support it, this means the +OS sends periodic messages to the NBD server to detect if the socket +is still connected. If this feature is not supported then setting +this does nothing. + +If using TCP on Linux, keepalive can be tuned by calling +L<nbd_set_tcp_option(3)>. + +This only works if called on the handle before calls such as +L<nbd_connect_tcp(3)> where libnbd itself creates the socket. +It does not apply to L<nbd_connect_socket(3)> where a socket +is created somewhere else and passed to libnbd.|}; + see_also = [Link "get_keepalive"; + Link "set_tcp_option"; + ExternalLink ("socket", 7); + ExternalLink ("setsockopt", 2)]; + }; + + "get_keepalive", { + default_call with + args = []; ret = RBool; + shortdesc = "return keepalive status"; + longdesc = {|Return the keepalive status. + +If the handle is not connected, this returns the keepalive flag +from the handle as set by L<nbd_set_keepalive(3)>. + +If the handle is connected, this attempts to read the keepalive +(C<SO_KEEPALIVE>) flag from the socket itself, telling you if +keepalive was successfully enabled or not.|}; + see_also = [Link "set_keepalive"; + ExternalLink ("socket", 7); + ExternalLink ("getsockopt", 2)]; + }; + + "set_tcp_option", { + default_call with + args = [Int "option"; Int "v"]; ret = RErr; + permitted_states = [ Created ]; + shortdesc = "set TCP options on the socket"; + longdesc = {|If making a TCP connection then you can call +this function to set a subset of TCP options on the socket. + +Currently the C<option> parameter may be one of: + +=over 4 + +=item * + +TCP_KEEPCNT + +=item * + +TCP_KEEPIDLE + +=item * + +TCP_KEEPINTVL + +=back + +These options may not work on non-Linux systems. + +For the C<TCP_KEEP*> options, keepalive must also be enabled +by calling L<nbd_set_keepalive(3)>).|}; + see_also = [Link "set_keepalive"; + ExternalLink ("tcp", 7); + ExternalLink ("setsockopt", 2)]; + }; + + "add_close_callback", { + default_call with + args = [ Closure close_closure ]; + ret = RErr; + shortdesc = "add a callback which is called on close"; + longdesc = {|libnbd handles maintain a list of callbacks +which are called when the handle is closed. This adds a +callback to this list. + +Callbacks cannot be removed from the list once added. +Callbacks might not necessarily be called in all circumstances +(eg. if the program crashes). + +The callback function is declared like this (in the C bindings): + + int callback (void *user_data); + +where the parameter is the C<user_data> passed to this function, +and an C<int> is returned. The return value is ignored because +L<nbd_close(3)> returns C<void> and cannot be cancelled. + +The callback should not call C<nbd_*> APIs on the same handle since it can +be called while holding the handle lock and will cause a deadlock. + +If there are multiple close callbacks the order in which they +are called is not defined. But all the callbacks are called +first followed by all the free functions.|}; + see_also = [Link "set_private_data"; Link "close"; + ExternalLink ("atexit", 3)]; +}; + ] (* The first stable version that the symbol appeared in, for @@ -4587,6 +4702,12 @@ "get_tls_priority", (1, 24); "set_uri_allow_tls_priority", (1, 24); + (* Added in 1.25.x development cycle, will be stable and supported in 1.26 *) + "set_keepalive", (1, 26); + "get_keepalive", (1, 26); + "set_tcp_option", (1, 26); + "add_close_callback", (1, 26); + (* These calls are proposed for a future version of libnbd, but * have not been added to any released version so far. "get_tls_certificates", (1, ??); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/generator/OCaml.ml new/libnbd-1.25.5/generator/OCaml.ml --- old/libnbd-1.24.2/generator/OCaml.ml 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/generator/OCaml.ml 2026-04-30 12:51:52.000000000 +0200 @@ -79,9 +79,13 @@ sprintf "?%s:(%s)" cbname (ocaml_closuredecl_to_string cbargs) | OFlags (n, { flag_prefix }, _) -> sprintf "?%s:%s.t list" n flag_prefix -and ocaml_closuredecl_to_string cbargs = - let cbargs = List.map ocaml_cbarg_to_string cbargs in - String.concat " -> " (cbargs @ ["int"]) +and ocaml_closuredecl_to_string = function + | [] -> + (* For no-argument closures we need to add an implicit unit arg. *) + "unit -> int" + | cbargs -> + let cbargs = List.map ocaml_cbarg_to_string cbargs in + String.concat " -> " (cbargs @ ["int"]) and ocaml_cbarg_to_string = function | CBArrayAndLen (arg, _) -> @@ -574,13 +578,15 @@ pr "\n"; pr "{\n"; pr " CAMLparam0 ();\n"; - assert (List.length argnames <= 5); - pr " CAMLlocal%d (%s);\n" (List.length argnames) - (String.concat ", " argnames); + let argnames_len = List.length argnames in + assert (argnames_len <= 5); + if argnames_len > 0 then + pr " CAMLlocal%d (%s);\n" argnames_len (String.concat ", " argnames); pr " CAMLlocal2 (exn, rv);\n"; pr " const struct user_data *data = user_data;\n"; pr " int r;\n"; - pr " value args[%d];\n" (List.length argnames); + if argnames_len > 0 then + pr " value args[%d];\n" argnames_len; pr "\n"; List.iter ( @@ -619,8 +625,12 @@ List.iteri (fun i n -> pr " args[%d] = %s;\n" i n) argnames; - pr " rv = caml_callbackN_exn (data->fnv, %d, args);\n" - (List.length argnames); + if argnames_len > 0 then + pr " rv = caml_callbackN_exn (data->fnv, %d, args);\n" + (List.length argnames) + else + (* If no callback arguments, call with unit parameter. *) + pr " rv = caml_callback_exn (data->fnv, Val_unit);\n"; List.iter ( function diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/generator/states-connect.c new/libnbd-1.25.5/generator/states-connect.c --- old/libnbd-1.24.2/generator/states-connect.c 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/generator/states-connect.c 2026-04-30 12:51:52.000000000 +0200 @@ -83,6 +83,44 @@ #endif } +/* Set SO_KEEPALIVE on the socket. This is best effort so don't fail. */ +static void +set_keepalive (struct nbd_handle *h, int sock) +{ + if (h->keepalive) { + int opt = 1; + if (setsockopt (sock, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof opt) == -1) { + debug (h, "setsockopt: SOL_SOCKET: SO_KEEPALIVE: %s (ignored)", + strerror (errno)); + return; + } + } +} + +static void +set_tcp_option (struct nbd_handle *h, int sock, + const char *optname, int option, int *v) +{ + if (*v >= 0 && setsockopt (sock, IPPROTO_TCP, option, v, sizeof *v) == -1) { + debug (h, "setsockopt: IPPROTO_TCP: %s: %s (ignored)", + optname, strerror (errno)); + } +} + +static void +set_tcp_options (struct nbd_handle *h, int sock) +{ +#ifdef TCP_KEEPCNT + set_tcp_option (h, sock, "TCP_KEEPCNT", TCP_KEEPCNT, &h->tcp_keepcnt); +#endif +#ifdef TCP_KEEPIDLE + set_tcp_option (h, sock, "TCP_KEEPIDLE", TCP_KEEPIDLE, &h->tcp_keepidle); +#endif +#ifdef TCP_KEEPINTVL + set_tcp_option (h, sock, "TCP_KEEPINTVL", TCP_KEEPINTVL, &h->tcp_keepintvl); +#endif +} + STATE_MACHINE { CONNECT.START: sa_family_t family; @@ -106,6 +144,8 @@ disable_sigpipe (fd); if (family == AF_UNIX) set_buffers (fd); + set_keepalive (h, fd); + set_tcp_options (h, fd); r = connect (fd, (struct sockaddr *)&h->connaddr, h->connaddrlen); if (r == 0 || (r == -1 && errno == EINPROGRESS)) @@ -219,6 +259,8 @@ disable_nagle (fd); disable_sigpipe (fd); + set_keepalive (h, fd); + set_tcp_options (h, fd); if (connect (fd, h->rp->ai_addr, h->rp->ai_addrlen) == -1) { if (errno != EINPROGRESS) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/info/can.c new/libnbd-1.25.5/info/can.c --- old/libnbd-1.24.2/info/can.c 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/info/can.c 2026-04-30 12:51:52.000000000 +0200 @@ -38,10 +38,16 @@ assert (can); - if (strcasecmp (can, "connect") == 0 || - strcasecmp (can, "read") == 0) + if (strcasecmp (can, "connect") == 0) feature = 1; + else if (strcasecmp (can, "open") == 0 || + strcasecmp (can, "read") == 0) + /* nbd_get_size won't work unless the server has sent export + * flags, which means we are able to open the export. + */ + feature = nbd_get_size (nbd) >= 0; + else if (strcasecmp (can, "tls") == 0) feature = nbd_get_tls_negotiated (nbd); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/info/info-can-read.sh new/libnbd-1.25.5/info/info-can-read.sh --- old/libnbd-1.24.2/info/info-can-read.sh 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/info/info-can-read.sh 2026-04-30 12:51:52.000000000 +0200 @@ -21,9 +21,32 @@ set -e set -x -# --can read always returns true. +requires $NBDKIT eval --version -requires $NBDKIT null --version +# --can open / --can read are only successful when the export can be +# opened. -$NBDKIT -v -U - null \ +$NBDKIT -v -U - eval \ + get_size=' echo 64M ' \ + --run '$VG nbdinfo --can open "nbd+unix:///?socket=$unixsocket"' + +$NBDKIT -v -U - eval \ + get_size=' echo 64M ' \ --run '$VG nbdinfo --can read "nbd+unix:///?socket=$unixsocket"' + +# But if the export cannot be opened, we expected them to fail. + +st=0 +$NBDKIT -v -U - eval \ + open='echo EIO go away >&2; exit 1' \ + get_size=' echo 64M ' \ + --run '$VG nbdinfo --can open "nbd+unix:///?socket=$unixsocket"' \ + || st=$? +test $st = 2 + +$NBDKIT -v -U - eval \ + open='echo EIO go away >&2; exit 1' \ + get_size=' echo 64M ' \ + --run '$VG nbdinfo --can read "nbd+unix:///?socket=$unixsocket"' \ + || st=$? +test $st = 2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/info/info-can.sh new/libnbd-1.25.5/info/info-can.sh --- old/libnbd-1.24.2/info/info-can.sh 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/info/info-can.sh 2026-04-30 12:51:52.000000000 +0200 @@ -30,7 +30,7 @@ # --can connect is tested in info-can-connect.sh -# --can read is tested in info-can-read.sh +# --can open and --can read are tested in info-can-read.sh # --can zero is tested in info-can-zero.sh diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/info/nbdinfo.pod new/libnbd-1.25.5/info/nbdinfo.pod --- old/libnbd-1.24.2/info/nbdinfo.pod 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/info/nbdinfo.pod 2026-04-30 12:51:52.000000000 +0200 @@ -164,16 +164,18 @@ For convenience this is the opposite of I<--is read-only>. +=item nbdinfo --can open URI + =item nbdinfo --can read URI -All NBD servers must support read, so this always exits with success -(unless there is a failure connecting to the URI). +Test if we can connect to the NBD URI and open the named export. As +all NBD servers must support read, I<--can read> is a synonym. =item nbdinfo --can connect URI Test if we can connect to the NBD URI. This tests the NBD handshake as far as NBD options processing, but does not guarantee that the -export can be opened. +export can be opened (see I<--can open>). =item nbdinfo --is tls URI @@ -355,6 +357,8 @@ =item B<--can multi-conn> +=item B<--can open> + =item B<--can read> =item B<--can trim> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/lib/handle.c new/libnbd-1.25.5/lib/handle.c --- old/libnbd-1.24.2/lib/handle.c 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/lib/handle.c 2026-04-30 12:51:52.000000000 +0200 @@ -29,6 +29,7 @@ #include <netdb.h> #include <sys/types.h> #include <sys/wait.h> +#include <netinet/tcp.h> #include "ascii-ctype.h" #include "internal.h" @@ -68,6 +69,8 @@ goto error1; } + h->close_cbs = (close_cb_vector) empty_vector; + h->unique = 1; h->tls_verify_peer = true; h->tls_priority = strdup (TLS_PRIORITY); @@ -75,6 +78,9 @@ set_error (errno, "strdup"); goto error1; } + h->tcp_keepcnt = -1; + h->tcp_keepidle = -1; + h->tcp_keepintvl = -1; h->request_eh = true; h->request_sr = true; h->request_meta = true; @@ -128,6 +134,18 @@ return NULL; } +static void +call_close_callback (nbd_close_callback cb) +{ + CALL_CALLBACK (cb); +} + +static void +free_close_callback (nbd_close_callback cb) +{ + FREE_CALLBACK (cb); +} + void nbd_close (struct nbd_handle *h) { @@ -142,6 +160,14 @@ debug (h, "closing handle"); + /* Call any close callbacks, and free them. We do this in two + * phases so that close callbacks can share structs without them + * being freed early. + */ + close_cb_vector_iter (&h->close_cbs, call_close_callback); + close_cb_vector_iter (&h->close_cbs, free_close_callback); + close_cb_vector_reset (&h->close_cbs); + /* Free user callbacks first. */ nbd_unlocked_clear_debug_callback (h); @@ -224,6 +250,18 @@ } int +nbd_unlocked_add_close_callback (struct nbd_handle *h, + nbd_close_callback *close_callback) +{ + if (close_cb_vector_append (&h->close_cbs, *close_callback) == -1) { + set_error (errno, "realloc"); + return -1; + } + SET_CALLBACK_TO_NULL (*close_callback); + return 0; +} + +int nbd_unlocked_set_socket_activation_name (struct nbd_handle *h, const char *name) { @@ -331,6 +369,7 @@ return sizeof *h + estimate_string (h->hname) + + h->close_cbs.cap * sizeof (nbd_close_callback) + estimate_string (h->export_name) + estimate_string (h->sact_name) + estimate_string (h->tls_certificates) @@ -780,3 +819,60 @@ { return h->chunks_received; } + +int +nbd_unlocked_set_keepalive (struct nbd_handle *h, bool enable) +{ + h->keepalive = enable; + return 0; +} + +int +nbd_unlocked_get_keepalive (struct nbd_handle *h) +{ + if (!h->sock) { + /* Not connected so return the flag from the handle. */ + return h->keepalive; + } + else { + /* Read the SO_KEEPALIVE status from the underlying socket. */ + int opt; + socklen_t optlen = sizeof opt; + int fd; + + fd = h->sock->ops->get_fd (h->sock); + if (fd == -1) { + set_error (EBADF, "socket open but no associated file descriptor"); + return -1; + } + if (getsockopt (fd, SOL_SOCKET, SO_KEEPALIVE, &opt, &optlen) == -1) { + set_error (errno, "getsockopt: SOL_SOCKET: SO_KEEPALIVE"); + return -1; + } + if (optlen != sizeof opt) { + set_error (ENOTSUP, "getsockopt: SOL_SOCKET: SO_KEEPALIVE: " + "unexpected parameter size"); + } + return opt; + } +} + +int +nbd_unlocked_set_tcp_option (struct nbd_handle *h, + int option, int value) +{ + switch (option) { +#ifdef TCP_KEEPCNT + case TCP_KEEPCNT: h->tcp_keepcnt = value; return 0; +#endif +#ifdef TCP_KEEPIDLE + case TCP_KEEPIDLE: h->tcp_keepidle = value; return 0; +#endif +#ifdef TCP_KEEPINTVL + case TCP_KEEPINTVL: h->tcp_keepintvl = value; return 0; +#endif + default: + set_error (ENOTSUP, "unknown or unsupported TCP option (%d)", option); + return -1; + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/lib/internal.h new/libnbd-1.25.5/lib/internal.h --- old/libnbd-1.24.2/lib/internal.h 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/lib/internal.h 2026-04-30 12:51:52.000000000 +0200 @@ -72,10 +72,12 @@ char *name; /* Name of meta context. */ uint32_t context_id; /* Context ID negotiated with the server. */ }; -DEFINE_VECTOR_TYPE (meta_vector, struct meta_context); +DEFINE_VECTOR_TYPE(meta_vector, struct meta_context); DEFINE_VECTOR_TYPE(uint32_vector, uint32_t); +DEFINE_VECTOR_TYPE(close_cb_vector, nbd_close_callback); + struct export { char *name; char *description; @@ -109,6 +111,9 @@ /* Private data, for the application to use. */ _Atomic uintptr_t private_data; + /* Close callback(s). */ + close_cb_vector close_cbs; + char *export_name; /* Export name, never NULL. */ char *sact_name; /* Socket activation name, can be NULL. */ @@ -321,6 +326,10 @@ struct addrinfo hints; struct addrinfo *result, *rp; int connect_errno; + bool keepalive; + int tcp_keepcnt; + int tcp_keepidle; + int tcp_keepintvl; /* When sending metadata contexts, this is used. */ string_vector querylist; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/ocaml/tests/Makefile.am new/libnbd-1.25.5/ocaml/tests/Makefile.am --- old/libnbd-1.24.2/ocaml/tests/Makefile.am 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/ocaml/tests/Makefile.am 2026-04-30 12:51:52.000000000 +0200 @@ -27,6 +27,7 @@ test_120_set_non_defaults.ml \ test_130_private_data.ml \ test_140_explicit_close.ml \ + test_150_close_callback.ml \ test_200_connect_command.ml \ test_210_opt_abort.ml \ test_220_opt_list.ml \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/ocaml/tests/test_150_close_callback.ml new/libnbd-1.25.5/ocaml/tests/test_150_close_callback.ml --- old/libnbd-1.24.2/ocaml/tests/test_150_close_callback.ml 1970-01-01 01:00:00.000000000 +0100 +++ new/libnbd-1.25.5/ocaml/tests/test_150_close_callback.ml 2026-04-30 12:51:52.000000000 +0200 @@ -0,0 +1,39 @@ +(* hey emacs, this is OCaml code: -*- tuareg -*- *) +(* libnbd OCaml test case + * Copyright Red Hat + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + *) + +let () = + let a = ref 0 and b = ref 0 in + let nbd1 = NBD.create () and nbd2 = NBD.create () in + + NBD.add_close_callback nbd1 (fun () -> a := 1; 0); + NBD.add_close_callback nbd1 (fun () -> b := 2; 0); + NBD.add_close_callback nbd2 (fun () -> a := 2; 0); + + assert (!a = 0); + assert (!b = 0); + + NBD.close nbd1; + assert (!a = 1); + assert (!b = 2); + + NBD.close nbd2; + assert (!a = 2); + assert (!b = 2) + +let () = Gc.compact () diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/python/Makefile.am new/libnbd-1.25.5/python/Makefile.am --- old/libnbd-1.24.2/python/Makefile.am 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/python/Makefile.am 2026-04-30 12:51:52.000000000 +0200 @@ -37,6 +37,8 @@ libnbd-python.pod \ nbdsh.py \ pycodestyle.sh \ + test-aio-connect-ip.sh \ + test-aio-connect-unix-oldformat.sh \ test-aio-connect-unix.sh \ $(srcdir)/t/*.py \ $(NULL) @@ -98,7 +100,9 @@ LOG_COMPILER = $(top_builddir)/run TESTS += \ run-python-tests \ + test-aio-connect-unix-oldformat.sh \ test-aio-connect-unix.sh \ + test-aio-connect-ip.sh \ $(NULL) endif HAVE_NBDKIT diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/python/t/150-close-callback.py new/libnbd-1.25.5/python/t/150-close-callback.py --- old/libnbd-1.24.2/python/t/150-close-callback.py 1970-01-01 01:00:00.000000000 +0100 +++ new/libnbd-1.25.5/python/t/150-close-callback.py 2026-04-30 12:51:52.000000000 +0200 @@ -0,0 +1,50 @@ +# libnbd Python bindings +# Copyright Red Hat +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +import nbd + +a = 0 +b = 0 + + +def set_a(i): + global a + a = i + + +def set_b(i): + global b + b = i + + +h1 = nbd.NBD() +h2 = nbd.NBD() + +h1.add_close_callback(lambda: set_a(1)) +h1.add_close_callback(lambda: set_b(2)) +h2.add_close_callback(lambda: set_a(2)) + +assert a == 0 +assert b == 0 + +h1.close() +assert a == 1 +assert b == 2 + +h2.close() +assert a == 2 +assert b == 2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/python/test-aio-connect-ip.sh new/libnbd-1.25.5/python/test-aio-connect-ip.sh --- old/libnbd-1.24.2/python/test-aio-connect-ip.sh 1970-01-01 01:00:00.000000000 +0100 +++ new/libnbd-1.25.5/python/test-aio-connect-ip.sh 2026-04-30 12:51:52.000000000 +0200 @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# nbd client library in userspace +# Copyright Red Hat +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +. ../tests/functions.sh + +set -e +set -x + +requires $PYTHON --version + +# Python tests all fail on macOS because of SIP misfeature. +requires_not test "$(uname)" = "Darwin" + +# Requires libxml2 for h.get_uri +requires_uri + +# Requires nbdkit >= 1.47 supporting --port=0 properly. +requires $NBDKIT --version +minor=$( $NBDKIT --dump-config | grep ^version_minor | $CUT -d= -f2 ) +requires test $minor -ge 47 + +# Check the host supports IPv6. +requires ip -V +requires bash -c 'ip -o -6 addr show scope host | grep inet6' + +define script <<'EOF' +import nbd +import sys + +h = nbd.NBD() +port = sys.argv[1] +addr = ("AF_INET6", ("::1", port)) +h.aio_connect(addr) +while h.aio_is_connecting(): + h.poll(1) +assert h.aio_is_ready() + +# libnbd should be able to form a URI from this address. +uri = h.get_uri() +print("uri:", uri) +assert uri == ("nbd://[::1]:%s/" % port) +EOF +export script + +$NBDKIT --port=0 null --run '$PYTHON -c "$script" "$port"' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/python/test-aio-connect-unix-oldformat.sh new/libnbd-1.25.5/python/test-aio-connect-unix-oldformat.sh --- old/libnbd-1.24.2/python/test-aio-connect-unix-oldformat.sh 1970-01-01 01:00:00.000000000 +0100 +++ new/libnbd-1.25.5/python/test-aio-connect-unix-oldformat.sh 2026-04-30 12:51:52.000000000 +0200 @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# nbd client library in userspace +# Copyright Red Hat +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +. ../tests/functions.sh + +set -e +set -x + +requires $NBDKIT --version +requires $PYTHON --version + +# Python tests all fail on macOS because of SIP misfeature. +requires_not test "$(uname)" = "Darwin" + +# Requires libxml2 for h.get_uri +requires_uri + +define script <<'EOF' +import nbd +import sys + +h = nbd.NBD() +unixsocket = sys.argv[1] +addr = unixsocket +h.aio_connect(addr) +while h.aio_is_connecting(): + h.poll(1) +assert h.aio_is_ready() + +# libnbd should be able to form a URI from this address. +uri = h.get_uri() +print("uri:", uri) +assert uri == ("nbd+unix:///?socket=%s" % unixsocket) +EOF +export script + +$NBDKIT -U - null --run '$PYTHON -c "$script" "$unixsocket"' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/python/test-aio-connect-unix.sh new/libnbd-1.25.5/python/test-aio-connect-unix.sh --- old/libnbd-1.24.2/python/test-aio-connect-unix.sh 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/python/test-aio-connect-unix.sh 2026-04-30 12:51:52.000000000 +0200 @@ -27,13 +27,26 @@ # Python tests all fail on macOS because of SIP misfeature. requires_not test "$(uname)" = "Darwin" -$NBDKIT -U - null --run '$PYTHON -c " +# Requires libxml2 for h.get_uri +requires_uri + +define script <<'EOF' import nbd import sys + h = nbd.NBD() -addr = sys.argv[1] +unixsocket = sys.argv[1] +addr = ("AF_UNIX", unixsocket) h.aio_connect(addr) while h.aio_is_connecting(): h.poll(1) assert h.aio_is_ready() -" "$unixsocket"' + +# libnbd should be able to form a URI from this address. +uri = h.get_uri() +print("uri:", uri) +assert uri == ("nbd+unix:///?socket=%s" % unixsocket) +EOF +export script + +$NBDKIT -U - null --run '$PYTHON -c "$script" "$unixsocket"' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/python/utils.c new/libnbd-1.25.5/python/utils.c --- old/libnbd-1.24.2/python/utils.c 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/python/utils.c 2026-04-30 12:51:52.000000000 +0200 @@ -28,6 +28,8 @@ #include <assert.h> #include <sys/socket.h> #include <sys/un.h> +#include <arpa/inet.h> +#include <netinet/in.h> #include <libnbd.h> @@ -99,63 +101,250 @@ * general rules described here: * https://docs.python.org/3/library/socket.html * + * Because that mapping is not unique (it only makes sense in the + * context of knowing the address family), the caller has to pass in + * the address family as the first parameter. Therefore the formats + * that we parse are: + * + * ("AF_UNIX", path) + * ("AF_UNIX", (path)) + * ("AF_INET", (addr, port)) + * ("AF_INET6", (addr, port, [additional elements ignored])) + * + * For backwards compatibility with libnbd <= 1.24, we also allow a + * single string here which is parsed as a Unix domain socket. + * * There is a function in cpython called getsockaddrarg which roughly * does the same thing, but in cpython they know the socket family - * already (which we do not). In any case that function cannot be - * called directly. + * already. In any case that function cannot be called directly. */ +static int parse_path_to_sockaddr_un (PyObject *path, + struct sockaddr_un *sun, socklen_t *len); +static int parse_addr_to_sockaddr_in (PyObject *tuple, + struct sockaddr_in *sin, socklen_t *len); +static int parse_addr_to_sockaddr_in6 (PyObject *tuple, + struct sockaddr_in6 *sin6, + socklen_t *len); + int nbd_internal_py_get_sockaddr (PyObject *addr, struct sockaddr_storage *ss, socklen_t *len) { memset (ss, 0, sizeof *ss); - if (PyUnicode_Check (addr)) { /* AF_UNIX */ + /* For backwards compatibility with libnbd <= 1.24, parse a bare + * string as AF_UNIX. + */ + if (PyUnicode_Check (addr)) { struct sockaddr_un *sun = (struct sockaddr_un *)ss; - const char *unixsocket; - size_t namelen; + return parse_path_to_sockaddr_un (addr, sun, len); + } - sun->sun_family = AF_UNIX; - unixsocket = PyUnicode_AsUTF8 (addr); - if (!unixsocket) - goto err; - namelen = strlen (unixsocket); - if (namelen > sizeof sun->sun_path) { + else if (PyTuple_Check (addr)) { + const char *af; + Py_ssize_t n; + PyObject *sockaddr; + + /* Must be a 2-element tuple. */ + n = PyTuple_Size (addr); + if (n != 2) { PyErr_SetString (PyExc_RuntimeError, - "get_sockaddr: Unix domain socket name too long"); + "get_sockaddr: must be a 2-element tuple, " + "with the first element being the address family, " + "for example: (\"AF_UNIX\", path) or " + "(\"AF_INET\", (ipaddr, port))"); return -1; } - memcpy (sun->sun_path, unixsocket, namelen); - *len = sizeof *sun; - return 0; - } -#if 0 - else if (PyTuple_Check (addr)) { - Py_ssize_t n = PyTuple_Size (addr); + /* First element is the address family. */ + af = PyUnicode_AsUTF8 (PyTuple_GetItem (addr, 0)); + if (!af) return -1; + + /* Second element is the sockaddr. */ + sockaddr = PyTuple_GetItem (addr, 1); + + /* AF_UNIX */ + if (strcasecmp (af, "UNIX") == 0 || strcasecmp (af, "AF_UNIX") == 0) { + if (PyTuple_Check (sockaddr) && PyTuple_Size (sockaddr) == 1) + sockaddr = PyTuple_GetItem (sockaddr, 0); + + if (PyUnicode_Check (sockaddr)) { + struct sockaddr_un *sun = (struct sockaddr_un *)ss; + return parse_path_to_sockaddr_un (sockaddr, sun, len); + } - switch (n) { - case 2: /* AF_INET */ - /* XXX TODO */ - break; - - case 4: /* AF_INET6 */ - /* XXX TODO */ - break; + PyErr_SetString (PyExc_RuntimeError, + "get_sockaddr: wrong format for Unix domain socket"); + return -1; + } - default: - goto err; + /* AF_INET */ + else if (strcasecmp (af, "INET") == 0 || + strcasecmp (af, "AF_INET") == 0) { + if (PyTuple_Check (sockaddr)) { + struct sockaddr_in *sin = (struct sockaddr_in *)ss; + return parse_addr_to_sockaddr_in (sockaddr, sin, len); + } + + PyErr_SetString (PyExc_RuntimeError, + "get_sockaddr: wrong format for AF_INET socket"); + return -1; + } + + /* AF_INET6 */ + else if (strcasecmp (af, "INET6") == 0 || + strcasecmp (af, "AF_INET6") == 0) { + if (PyTuple_Check (sockaddr)) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ss; + return parse_addr_to_sockaddr_in6 (sockaddr, sin6, len); + } + + PyErr_SetString (PyExc_RuntimeError, + "get_sockaddr: wrong format for AF_INET6 socket"); + return -1; + } + + else { + PyErr_SetString (PyExc_TypeError, + "get_sockaddr: unknown address family"); + return -1; } } -#endif else { - err: - PyErr_SetString (PyExc_TypeError, "get_sockaddr: unknown address type"); + PyErr_SetString (PyExc_TypeError, "get_sockaddr: " + "unknown socket address type"); return -1; } } +/* Parse path (which must be PyUnicode) to sockaddr_un. */ +static int +parse_path_to_sockaddr_un (PyObject *path, + struct sockaddr_un *sun, socklen_t *len) +{ + const char *unixsocket; + size_t namelen; + + sun->sun_family = AF_UNIX; + *len = sizeof *sun; + + unixsocket = PyUnicode_AsUTF8 (path); + if (!unixsocket) { + PyErr_SetString (PyExc_TypeError, "get_sockaddr: cannot parse socket path"); + return -1; + } + namelen = strlen (unixsocket); + if (namelen > sizeof sun->sun_path) { + PyErr_SetString (PyExc_RuntimeError, + "get_sockaddr: Unix domain socket name too long"); + return -1; + } + memcpy (sun->sun_path, unixsocket, namelen); + return 0; +} + +/* Parse (addr, port) tuple (which must be PyTuple) to sockaddr_in. */ +static int +parse_addr_to_sockaddr_in (PyObject *tuple, + struct sockaddr_in *sin, socklen_t *len) +{ + const char *addr_str, *port_str; + int port; + const Py_ssize_t n = PyTuple_Size (tuple); + + if (n < 2) { + PyErr_SetString (PyExc_TypeError, "get_sockaddr: need (addr, port) " + "for socket address"); + return -1; + } + + addr_str = PyUnicode_AsUTF8 (PyTuple_GetItem (tuple, 0)); + port_str = PyUnicode_AsUTF8 (PyTuple_GetItem (tuple, 1)); + + if (!addr_str || !port_str) { + PyErr_SetString (PyExc_TypeError, "get_sockaddr: cannot parse " + "address or port number in socket address"); + return -1; + } + + sin->sin_family = AF_INET; + *len = sizeof *sin; + + switch (inet_pton (AF_INET, addr_str, &sin->sin_addr)) { + case 1: break; /* success */ + case 0: /* invalid address string */ + PyErr_SetString (PyExc_RuntimeError, + "get_sockaddr: inet_pton: cannot parse IPv4 address"); + return -1; + case -1: /* invalid address family, probably impossible */ + PyErr_SetString (PyExc_RuntimeError, + "get_sockaddr: inet_pton: invalid address family"); + return -1; + } + + if (sscanf (port_str, "%d", &port) != 1) { + PyErr_SetString (PyExc_RuntimeError, + "get_sockaddr: cannot parse port number"); + return -1; + } + sin->sin_port = htons (port); + + return 0; +} + +/* Parse (addr, port) tuple (which must be PyTuple) to sockaddr_in6. */ +static int +parse_addr_to_sockaddr_in6 (PyObject *tuple, + struct sockaddr_in6 *sin6, socklen_t *len) +{ + const char *addr_str, *port_str; + int port; + const Py_ssize_t n = PyTuple_Size (tuple); + + if (n < 2) { + PyErr_SetString (PyExc_TypeError, "get_sockaddr: need (addr, port) " + "for socket address"); + return -1; + } + + /* element 2 = flowinfo, element 3 = scope_id, both are + * currently ignored. + */ + addr_str = PyUnicode_AsUTF8 (PyTuple_GetItem (tuple, 0)); + port_str = PyUnicode_AsUTF8 (PyTuple_GetItem (tuple, 1)); + + if (!addr_str || !port_str) { + PyErr_SetString (PyExc_TypeError, "get_sockaddr: cannot parse " + "address or port number in socket address"); + return -1; + } + + sin6->sin6_family = AF_INET6; + *len = sizeof *sin6; + + switch (inet_pton (AF_INET6, addr_str, &sin6->sin6_addr)) { + case 1: break; /* success */ + case 0: /* invalid address string */ + PyErr_SetString (PyExc_RuntimeError, + "get_sockaddr: inet_pton: cannot parse IPv6 address"); + return -1; + case -1: /* invalid address family, probably impossible */ + PyErr_SetString (PyExc_RuntimeError, + "get_sockaddr: inet_pton: invalid address family"); + return -1; + } + + if (sscanf (port_str, "%d", &port) != 1) { + PyErr_SetString (PyExc_RuntimeError, + "get_sockaddr: cannot parse port number"); + return -1; + } + sin6->sin6_port = htons (port); + + return 0; +} + /* Obtain the type object for nbd.Buffer */ PyObject * nbd_internal_py_get_nbd_buffer_type (void) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/tests/Makefile.am new/libnbd-1.25.5/tests/Makefile.am --- old/libnbd-1.24.2/tests/Makefile.am 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/tests/Makefile.am 2026-04-30 12:51:52.000000000 +0200 @@ -73,6 +73,7 @@ compile-header-only \ compile-c \ compile-iso-c99 \ + close-callback \ close-null \ debug \ debug-environment \ @@ -90,6 +91,7 @@ compile-header-only \ compile-c \ compile-iso-c99 \ + close-callback \ close-null \ debug \ debug-environment \ @@ -114,6 +116,9 @@ $(NULL) compile_iso_c99_LDADD = $(top_builddir)/lib/libnbd.la +close_callback_SOURCES = close-callback.c +close_callback_LDADD = $(top_builddir)/lib/libnbd.la + close_null_SOURCES = close-null.c close_null_LDADD = $(top_builddir)/lib/libnbd.la diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/tests/close-callback.c new/libnbd-1.25.5/tests/close-callback.c --- old/libnbd-1.24.2/tests/close-callback.c 1970-01-01 01:00:00.000000000 +0100 +++ new/libnbd-1.25.5/tests/close-callback.c 2026-04-30 12:51:52.000000000 +0200 @@ -0,0 +1,105 @@ +/* NBD client library in userspace + * Copyright Red Hat + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* Check that nbd_close callbacks work. */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +#include <libnbd.h> + +static int +set_to_1 (void *vp) +{ + int *i = vp; + + printf ("set_to_1: %d -> 1\n", *i); + fflush (stdout); + *i = 1; + return 0; +} + +static int +set_to_2 (void *vp) +{ + int *i = vp; + + printf ("set_to_2: %d -> 2\n", *i); + fflush (stdout); + *i = 2; + return 0; +} + +int +main (int argc, char *argv[]) +{ + struct nbd_handle *nbd1, *nbd2; + int a = 0, b = 0; + void *data; + + nbd1 = nbd_create (); + if (nbd1 == NULL) { + fprintf (stderr, "%s\n", nbd_get_error ()); + exit (EXIT_FAILURE); + } + + nbd2 = nbd_create (); + if (nbd2 == NULL) { + fprintf (stderr, "%s\n", nbd_get_error ()); + exit (EXIT_FAILURE); + } + + /* Add some close callbacks to both handles. */ + nbd_add_close_callback (nbd1, + (nbd_close_callback){ .callback = set_to_1, + .user_data = &a }); + nbd_add_close_callback (nbd1, + (nbd_close_callback){ .callback = set_to_2, + .user_data = &b }); + nbd_add_close_callback (nbd2, + (nbd_close_callback){ .callback = set_to_2, + .user_data = &a }); + + /* This tests that the callback is freed only once. It relies on + * the C library detecting double frees. + */ + data = malloc (64); + assert (data); + nbd_add_close_callback (nbd1, + (nbd_close_callback){ .user_data = data, + .free = free }); + + /* Initial state. */ + assert (a == 0); + assert (b == 0); + + /* Close the first handle. */ + nbd_close (nbd1); + assert (a == 1); + assert (b == 2); + + /* Close the second handle. */ + nbd_close (nbd2); + assert (a == 2); + assert (b == 2); + + exit (EXIT_SUCCESS); +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/tests/connect-tcp.c new/libnbd-1.25.5/tests/connect-tcp.c --- old/libnbd-1.24.2/tests/connect-tcp.c 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/tests/connect-tcp.c 2026-04-30 12:51:52.000000000 +0200 @@ -25,6 +25,7 @@ #include <string.h> #include <fcntl.h> #include <unistd.h> +#include <netinet/tcp.h> #include <libnbd.h> @@ -41,6 +42,7 @@ pid_t pid; size_t i; char *actual_uri, *expected_uri; + int r; unlink (PIDFILE); @@ -73,11 +75,41 @@ exit (EXIT_FAILURE); } + /* Setting keepalive won't do anything for this short connection. + * However it should be possible to set these parameters even on + * non-Linux. + */ + r = nbd_get_keepalive (nbd); + if (r != 0) { + fprintf (stderr, "expected nbd_get_keepalive == 0, but got %d\n", r); + exit (EXIT_FAILURE); + } + nbd_set_keepalive (nbd, 1); /* in the current impl this cannot fail */ + r = nbd_get_keepalive (nbd); + if (r != 1) { + fprintf (stderr, "expected nbd_get_keepalive == 1, but got %d\n", r); + exit (EXIT_FAILURE); + } +#ifdef TCP_KEEPCNT + nbd_set_tcp_option (nbd, TCP_KEEPCNT, 10); +#endif +#ifdef TCP_KEEPIDLE + nbd_set_tcp_option (nbd, TCP_KEEPIDLE, 60); +#endif +#ifdef TCP_KEEPINTVL + nbd_set_tcp_option (nbd, TCP_KEEPINTVL, 10); +#endif + if (nbd_connect_tcp (nbd, "localhost", port_str) == -1) { fprintf (stderr, "%s\n", nbd_get_error ()); exit (EXIT_FAILURE); } + /* See if keepalive was established, but don't fail if it was not. */ + r = nbd_get_keepalive (nbd); + printf ("nbd_get_keepalive after connection returned %d\n", r); + fflush (stdout); + if (nbd_supports_uri (nbd) == 1) { /* libnbd should be able to construct a URI for this connection. */ if (asprintf (&expected_uri, "nbd://localhost:%s/", port_str) == -1) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/ublk/nbdublk.c new/libnbd-1.25.5/ublk/nbdublk.c --- old/libnbd-1.24.2/ublk/nbdublk.c 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/ublk/nbdublk.c 2026-04-30 12:51:52.000000000 +0200 @@ -46,6 +46,7 @@ handles nbd = empty_vector; unsigned connections = 4; +bool keepalive = false; bool readonly = false; bool rotational; bool can_fua; @@ -78,7 +79,7 @@ "Mount NBD server as a virtual device:\n" "\n" #ifdef HAVE_LIBXML2 -" nbdublk [-C N|--connections N] [-r] [-v|--verbose]\n" +" nbdublk [-C N|--connections N] [--keepalive] [-r] [-v|--verbose]\n" " " DEVICE_PREFIX "<N> URI\n" "\n" "Other modes:\n" @@ -143,6 +144,7 @@ enum mode mode = MODE_URI; enum { HELP_OPTION = CHAR_MAX + 1, + KEEPALIVE_OPTION, LONG_OPTIONS, SHORT_OPTIONS, }; @@ -153,8 +155,10 @@ const char *short_options = "+C:rvV"; const struct option long_options[] = { { "help", no_argument, NULL, HELP_OPTION }, - { "long-options", no_argument, NULL, LONG_OPTIONS }, { "connections", required_argument, NULL, 'C' }, + { "keepalive", no_argument, NULL, KEEPALIVE_OPTION }, + { "keep-alive", no_argument, NULL, KEEPALIVE_OPTION }, + { "long-options", no_argument, NULL, LONG_OPTIONS }, { "readonly", no_argument, NULL, 'r' }, { "read-only", no_argument, NULL, 'r' }, { "short-options", no_argument, NULL, SHORT_OPTIONS }, @@ -182,6 +186,10 @@ case HELP_OPTION: usage (stdout, EXIT_SUCCESS); + case KEEPALIVE_OPTION: + keepalive = true; + break; + case LONG_OPTIONS: for (i = 0; long_options[i].name != NULL; ++i) { if (strcmp (long_options[i].name, "long-options") != 0 && @@ -489,6 +497,7 @@ exit (EXIT_FAILURE); } nbd_set_debug (h, verbose); + nbd_set_keepalive (h, keepalive); /* Connect to the NBD server synchronously. */ switch (mode) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnbd-1.24.2/ublk/nbdublk.pod new/libnbd-1.25.5/ublk/nbdublk.pod --- old/libnbd-1.24.2/ublk/nbdublk.pod 2026-03-03 18:12:42.000000000 +0100 +++ new/libnbd-1.25.5/ublk/nbdublk.pod 2026-04-30 12:51:52.000000000 +0200 @@ -4,7 +4,7 @@ =head1 SYNOPSIS - nbdublk [-C N|--connections N] [-r] [-v|--verbose] + nbdublk [-C N|--connections N] [--keepalive] [-r] [-v|--verbose] { /dev/ublkb<N> | <N> | - } URI =for paragraph @@ -121,6 +121,11 @@ Disable multi-conn. Only use a single connection to the NBD server. See L</THREAD MODEL> below. +=item B<--keepalive> + +Set the keepalive (C<"SO_KEEPALIVE">) flag on the connection. See +L<nbd_set_keepalive(3)> and L<socket(7)>. + =item B<-r> =item B<--readonly>
