This adds a C-only semi-private function for freeing various types of persistent data passed to libnbd. There are some similarities with nbd_add_close_callback which we removed in commit 7f191b150b52ed50098976309a6af883d245fc56. --- generator/generator | 57 ++++++++++++++++++++ lib/Makefile.am | 1 + lib/free.c | 129 ++++++++++++++++++++++++++++++++++++++++++++ lib/handle.c | 6 +++ lib/internal.h | 14 +++++ 5 files changed, 207 insertions(+) diff --git a/generator/generator b/generator/generator index c52200b..d6b9352 100755 --- a/generator/generator +++ b/generator/generator @@ -3284,6 +3284,7 @@ let generate_lib_libnbd_syms () = pr "LIBNBD_%d.%d {\n" major minor; pr " global:\n"; if (major, minor) = (1, 0) then ( + pr " nbd_add_free_callback;\n"; pr " nbd_create;\n"; pr " nbd_close;\n"; pr " nbd_get_errno;\n"; @@ -3581,6 +3582,13 @@ let generate_include_libnbd_h () = pr "extern int nbd_get_errno (void);\n"; pr "#define LIBNBD_HAVE_NBD_GET_ERRNO 1\n"; pr "\n"; + pr "typedef void (*nbd_free_callback) (void *ptr, void *user_data);\n"; + pr "extern int nbd_add_free_callback (struct nbd_handle *h,\n"; + pr " void *ptr,\n"; + pr " nbd_free_callback cb,\n"; + pr " void *user_data);\n"; + pr "#define LIBNBD_HAVE_NBD_ADD_FREE_CALLBACK 1\n"; + pr "\n"; print_closure_typedefs (); List.iter ( fun (name, { args; optargs; ret }) -> @@ -4005,6 +4013,55 @@ C<E<lt>errno.hE<gt>>. List.iter print_api handle_calls; pr "\ +=head1 FREE CALLBACKS + +B<Note:> The API described in this section is only +available from C. It is designed to help when writing +bindings to libnbd in other programming languages. +As such it is B<not> covered by the usual API stability +guarantee offered by other parts of the C API. Use it with care. + +Some pointers passed to libnbd calls are saved in the +S<C<struct nbd_handle>>. These include pointers to buffers +passed to C<nbd_aio_pread>, C<nbd_aio_pwrite>, etc., +and pointers to the C<user_data> for callbacks. If you +want to know when it is safe to free these objects then +you can register a free callback using: + + typedef void (*nbd_free_callback) (void *ptr, void *user_data); + int nbd_add_free_callback (struct nbd_handle *h, + void *ptr, + nbd_free_callback cb, + void *user_data); + +C<ptr> is a pointer to the object (ie. the buffer or +callback C<user_data>). C<cb (ptr)> is called when libnbd +no longer holds a reference to that object. + +To illustrate how this works with an example: + + struct write_completed_user_data *my_user_data; + void *buf; + + my_user_data = malloc (sizeof *my_user_data); + buf = malloc (512); + nbd_add_free_callback (nbd, my_user_data, my_free, NULL); + nbd_add_free_callback (nbd, buf, my_free, NULL); + nbd_aio_pwrite_callback (nbd, buf, 512, 0, + write_completed, my_user_data, 0); + +where C<my_free> is a simple wrapper around the L<free(3)> +function (we are not using the extra C<user_data> pointer +in this case): + + void my_free (void *ptr, void *) + { + free (ptr); + } + +This would free both C<my_user_data> and C<buf> once libnbd +has finished with them. + =head1 SEE ALSO L<libnbd(3)>. diff --git a/lib/Makefile.am b/lib/Makefile.am index 3657b9f..08e9d25 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -42,6 +42,7 @@ libnbd_la_SOURCES = \ disconnect.c \ errors.c \ flags.c \ + free.c \ handle.c \ internal.h \ is-state.c \ diff --git a/lib/free.c b/lib/free.c new file mode 100644 index 0000000..67ba752 --- /dev/null +++ b/lib/free.c @@ -0,0 +1,129 @@ +/* NBD client library in userspace + * Copyright (C) 2013-2019 Red Hat Inc. + * + * 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 + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> + +#include "internal.h" + +/* This is not generated because we don't want to offer it to other + * programming languages. Also it's a semi-private API liable to + * evolve in its exact semantics. + */ +int +nbd_add_free_callback (struct nbd_handle *h, void *ptr, + nbd_free_callback cb, void *user_data) +{ + int ret = -1; + size_t i; + + if (ptr == NULL) + return 0; + + nbd_internal_set_error_context ("nbd_add_free_callback"); + pthread_mutex_lock (&h->lock); + + /* Extend the list of callbacks in the handle. */ + if (h->nr_free_callbacks >= h->alloc_free_callbacks) { + size_t new_alloc; + struct free_callback *new_callbacks; + + if (h->alloc_free_callbacks == 0) + new_alloc = 8; + else + new_alloc = 2 * h->alloc_free_callbacks; + + new_callbacks = realloc (h->free_callbacks, + sizeof (struct free_callback) * new_alloc); + if (new_callbacks == NULL) { + set_error (errno, "realloc"); + goto out; + } + h->alloc_free_callbacks = new_alloc; + h->free_callbacks = new_callbacks; + } + + /* Need to keep the list sorted by pointer so we can use bsearch. + * Insert the new entry before the i'th entry. + * + * Note the same pointer can be added multiple times (eg. if a + * buffer is shared between commands), and that is not a bug. Which + * free function is called is undefined, but they should all be + * called eventually. + */ + for (i = 0; i < h->nr_free_callbacks; ++i) { + if (ptr <= h->free_callbacks[i].ptr) + break; + } + memmove (&h->free_callbacks[i+1], &h->free_callbacks[i], + (h->nr_free_callbacks - i) * sizeof (struct free_callback)); + + h->free_callbacks[i].ptr = ptr; + h->free_callbacks[i].cb = cb; + h->free_callbacks[i].user_data = user_data; + h->nr_free_callbacks++; + + ret = 0; + out: + pthread_mutex_unlock (&h->lock); + return ret; +} + +static int +compare_free_callbacks (const void *v1, const void *v2) +{ + const void *ptr = v1; + const struct free_callback *cb = v2; + + if (ptr < cb->ptr) return -1; + else if (ptr > cb->ptr) return 1; + else return 0; +} + +/* Called before the library frees 'ptr', where 'ptr' is something + * that might potentially be associated with a free callback. This is + * called often so must be fast. + */ +void +nbd_internal_free_callback (struct nbd_handle *h, void *ptr) +{ + struct free_callback *free_cb; + + if (ptr == NULL) + return; + + free_cb = bsearch (ptr, h->free_callbacks, h->nr_free_callbacks, + sizeof (struct free_callback), + compare_free_callbacks); + if (free_cb) { + assert (ptr == free_cb->ptr); + + free_cb->cb (ptr, free_cb->user_data); + + /* Remove it from the free list. */ + memmove (free_cb, free_cb+1, + sizeof (struct free_callback) * + (&h->free_callbacks[h->nr_free_callbacks] - (free_cb+1))); + h->nr_free_callbacks--; + } +} diff --git a/lib/handle.c b/lib/handle.c index 054c8a7..ae0d196 100644 --- a/lib/handle.c +++ b/lib/handle.c @@ -126,6 +126,12 @@ nbd_close (struct nbd_handle *h) free_cmd_list (h->cmds_to_issue); free_cmd_list (h->cmds_in_flight); free_cmd_list (h->cmds_done); + + /* Any remaining free callbacks indicate an error. */ + if (h->nr_free_callbacks != 0) + abort (); + free (h->free_callbacks); + nbd_internal_free_string_list (h->argv); free (h->unixsocket); free (h->hostname); diff --git a/lib/internal.h b/lib/internal.h index 301b798..d8b0eed 100644 --- a/lib/internal.h +++ b/lib/internal.h @@ -47,6 +47,7 @@ struct meta_context; struct socket; struct command; +struct free_callback; struct nbd_handle { /* Unique name assigned to this handle for debug messages @@ -87,6 +88,10 @@ struct nbd_handle { nbd_debug_callback debug_callback; void *debug_data; + /* Free callbacks kept in pointer order. */ + struct free_callback *free_callbacks; + size_t nr_free_callbacks, alloc_free_callbacks; + /* State machine. * * The actual current state is ‘state’. ‘public_state’ is updated @@ -220,6 +225,12 @@ struct nbd_handle { bool disconnect_request; /* True if we've queued NBD_CMD_DISC */ }; +struct free_callback { + void *ptr; + nbd_free_callback cb; + void *user_data; +}; + struct meta_context { struct meta_context *next; /* Linked list. */ char *name; /* Name of meta context. */ @@ -318,6 +329,9 @@ extern int nbd_internal_set_size_and_flags (struct nbd_handle *h, uint64_t exportsize, uint16_t eflags); +/* free.c */ +extern void nbd_internal_free_callback (struct nbd_handle *h, void *ptr); + /* is-state.c */ extern bool nbd_internal_is_state_created (enum state state); extern bool nbd_internal_is_state_connecting (enum state state); -- 2.22.0