shutdown can be called on fatal error, and only stops the BIO.
the underlying BIO is still there. This allows it to be called
from a BIO which is in the middle of a chain.
destructor calls shutdown first, and then frees the resources.
this allows a destructor to be called from anywhere, and then the
entire chain is shut down
#include <freeradius-devel/bio/null.h>
#include <freeradius-devel/util/syserror.h>
-#ifndef NDEBUG
/** Free this bio.
*
- * The bio can only be freed if it is not in any chain.
+ * We allow talloc_free() to be called on just about anything in the
+ * bio chain. But we ensure that the chain is always shut down in an
+ * orderly fashion.
*/
int fr_bio_destructor(fr_bio_t *bio)
{
- fr_assert(!fr_bio_prev(bio));
- fr_assert(!fr_bio_next(bio));
+ fr_bio_common_t *my = (fr_bio_common_t *) bio;
+
+ FR_BIO_DESTRUCTOR_COMMON;
- /*
- * It's safe to free this bio.
- */
return 0;
}
-#endif
/** Internal bio function which just reads from the "next" bio.
*
return rcode;
}
-/** Free this bio, and everything it calls.
- *
- * We unlink the bio chain, and then free it individually. If there's an error, the bio chain is relinked.
- * That way the error can be addressed (somehow) and this function can be called again.
- *
- * Note that we do not support talloc_free() for the bio chain. Each individual bio has to be unlinked from
- * the chain before the destructor will allow it to be freed. This functionality is by design.
- *
- * We want to have an API where bios are created "bottom up", so that it is impossible for an application to
- * create an incorrect chain. However, creating the chain bottom up means that the lower bios not parented
- * from the higher bios, and therefore talloc_free() won't free them. As a result, we need an explicit
- * bio_free() function.
- */
-int fr_bio_free(fr_bio_t *bio)
-{
- fr_bio_t *next = fr_bio_next(bio);
-
- /*
- * We cannot free a bio in the middle of a chain. It has to be unlinked first.
- */
- if (fr_bio_prev(bio)) return -1;
-
- /*
- * Unlink our bio, and recurse to free the next one. If we can't free it, re-chain it, but reset
- * the read/write functions to do nothing.
- */
- if (next) {
- next->entry.prev = NULL;
- if (fr_bio_free(next) < 0) {
- next->entry.prev = &bio->entry;
- bio->read = fr_bio_fail_read;
- bio->write = fr_bio_fail_write;
- return -1;
- }
-
- bio->entry.next = NULL;
- }
-
- /*
- * It's now safe to free this bio.
- */
- return talloc_free(bio);
-}
-
static ssize_t fr_bio_shutdown_read(UNUSED fr_bio_t *bio, UNUSED void *packet_ctx, UNUSED void *buffer, UNUSED size_t size)
{
return fr_bio_error(SHUTDOWN);
/** Shut down a set of BIOs
*
- * We shut down the BIOs from the top to the bottom. This gives the TLS BIO an opportunity to
- * call the SSL_shutdown() routine, which should then write to the FD BIO.
+ * We shut down the BIOs from the top to the bottom. This gives the
+ * TLS BIO an opportunity to call the SSL_shutdown() routine, which
+ * should then write to the FD BIO. Once that write is completed,
+ * the FD BIO can then close its socket.
+ *
+ * Any shutdown is "stop read / write", but is not "free all
+ * resources". A shutdown can happen when one of the intermediary
+ * BIOs hits a fatal error. It can't free the BIO, but it has to
+ * mark the entire BIO chain as being unusable.
+ *
+ * A destructor will first shutdown the BIOs, and then free all resources.
*/
int fr_bio_shutdown(fr_bio_t *bio)
{
}
/*
- * Call the application shutdown routine
+ * Call the application shutdown routine to tell it that
+ * the BIO has been successfully shut down.
*/
my = (fr_bio_common_t *) first;
int fr_bio_shutdown_intermediate(fr_bio_t *bio) CC_HINT(nonnull);
-#ifndef NDEBUG
-int fr_bio_destructor(fr_bio_t *bio) CC_HINT(nonnull);
-#else
-#define fr_bio_destructor (NULL)
-#endif
+int fr_bio_destructor(fr_bio_t *bio);
#define fr_bio_error(_x) (-(FR_BIO_ERROR_ ## _x))
int fr_bio_shutdown(fr_bio_t *bio) CC_HINT(nonnull);
-int fr_bio_free(fr_bio_t *bio) CC_HINT(nonnull);
-
char const *fr_bio_strerror(ssize_t error);
void fr_bio_cb_set(fr_bio_t *bio, fr_bio_cb_funcs_t const *cb) CC_HINT(nonnull(1));
FR_BIO_COMMON;
};
+/** Define a common destructor pattern.
+ *
+ * Ensure that talloc_free() is safe no matter what. The caller can free any BIO at any time. If that
+ * happens, then the entire chain is shut down. On successful shutdown, this BIO is removed from the chain.
+ */
+#define FR_BIO_DESTRUCTOR_COMMON \
+do { \
+ if (my->priv_cb.shutdown) { \
+ int rcode; \
+ rcode = fr_bio_shutdown(&my->bio); \
+ if (rcode < 0) return rcode; \
+ } \
+ fr_bio_unchain(&my->bio); \
+} while (0)
+
+
ssize_t fr_bio_next_read(fr_bio_t *bio, void *packet_ctx, void *buffer, size_t size);
ssize_t fr_bio_next_write(fr_bio_t *bio, void *packet_ctx, void const *buffer, size_t size);
/** Remove the dedup cache
*
*/
-static int fr_bio_dedup_destructor(fr_bio_dedup_t *my)
+static int fr_bio_dedup_shutdown(fr_bio_t *bio)
{
fr_rb_iter_inorder_t iter;
fr_bio_dedup_entry_t *item;
+ fr_bio_dedup_t *my = talloc_get_type_abort(bio, fr_bio_dedup_t);
talloc_const_free(my->ev);
fr_bio_chain(&my->bio, next);
- talloc_set_destructor(my, fr_bio_dedup_destructor);
+ my->priv_cb.shutdown = fr_bio_dedup_shutdown;
+
+ talloc_set_destructor((fr_bio_t *) my, fr_bio_destructor); /* always use a common destructor */
return (fr_bio_t *) my;
}
return fr_bio_fd_close(&my->bio);
}
-static int fr_bio_fd_destructor(fr_bio_fd_t *my)
-{
- return fr_bio_fd_shutdown((fr_bio_t *) my);
-}
-
static int fr_bio_fd_eof(fr_bio_t *bio)
{
fr_bio_fd_t *my = talloc_get_type_abort(bio, fr_bio_fd_t);
}
if (rcode < 0) {
- fr_bio_shutdown(&my->bio);
+ (void) fr_bio_shutdown(&my->bio);
return fr_bio_error(GENERIC);
}
}
fail:
- fr_bio_shutdown(&my->bio);
+ (void) fr_bio_shutdown(&my->bio);
return fr_bio_error(IO);
}
my->priv_cb.write_resume = fr_bio_fd_write_resume;
my->priv_cb.shutdown = fr_bio_fd_shutdown;
- talloc_set_destructor(my, fr_bio_fd_destructor);
+ talloc_set_destructor((fr_bio_t *) my, fr_bio_destructor); /* always use a common destructor */
return (fr_bio_t *) my;
}
my->connect.error(&my->bio);
}
- fr_bio_shutdown(&my->bio);
+ /*
+ * The entire bio is unusable.
+ */
+ (void) fr_bio_shutdown(&my->bio);
}
/** Connect callback for when the socket is writable.
my->connect.timeout(&my->bio);
- fr_bio_shutdown(&my->bio);
+ (void) fr_bio_shutdown(&my->bio);
}
my->info.connect_errno = ECONNREFUSED;
#endif
if (error_cb) error_cb(bio);
- fr_bio_shutdown(&my->bio);
+ (void) fr_bio_shutdown(&my->bio);
return fr_bio_error(GENERIC);
}
/*
* Shut down the BIO. It's no longer useable.
*/
-fr_bio_shutdown(&my->bio);
+(void) fr_bio_shutdown(&my->bio);
*/
if (memcmp(my->buffer.read, "PROXY TCP", 9) != 0) {
fail:
- fr_bio_shutdown(&my->bio);
+ (void) fr_bio_shutdown(&my->bio);
return fr_bio_error(VERIFY);
}
p += 9;
fr_bio_chain(&my->bio, next);
- talloc_set_destructor((fr_bio_t *) my, fr_bio_destructor);
+ talloc_set_destructor((fr_bio_t *) my, fr_bio_destructor); /* always use a common destructor */
return (fr_bio_t *) my;
}
break;
}
- fr_bio_shutdown(bio);
+ (void) fr_bio_shutdown(bio);
return fr_bio_error(VERIFY);
}
/*
* A fatal error. Shut down the entire BIO chain.
*/
- fr_bio_shutdown(bio);
+ (void) fr_bio_shutdown(bio);
return -1;
}
fr_bio_chain(&my->bio, next);
- talloc_set_destructor((fr_bio_t *) my, fr_bio_destructor);
+ talloc_set_destructor((fr_bio_t *) my, fr_bio_destructor); /* always use a common destructor */
return (fr_bio_t *) my;
}
my->bio.read = fr_bio_mem_read_buffer;
my->bio.write = fr_bio_mem_write_read_buffer; /* the upstream will write to our read buffer */
- talloc_set_destructor((fr_bio_t *) my, fr_bio_destructor);
+ talloc_set_destructor((fr_bio_t *) my, fr_bio_destructor); /* always use a common destructor */
return (fr_bio_t *) my;
}
static int fr_bio_packet_shutdown(fr_bio_t *bio)
{
+ int rcode;
fr_bio_packet_t *my = bio->uctx;
+ rcode = fr_bio_shutdown(bio);
+ if (rcode < 0) return rcode;
+
if (!my->cb.shutdown) return 0;
return my->cb.shutdown(my);
static int fr_bio_pipe_destructor(fr_bio_pipe_t *my)
{
+ FR_BIO_DESTRUCTOR_COMMON;
+
pthread_mutex_destroy(&my->mutex);
return 0;
static int fr_bio_queue_destructor(fr_bio_queue_t *my)
{
+ FR_BIO_DESTRUCTOR_COMMON;
+
fr_assert(my->cancel); /* otherwise it would be fr_bio_destructor */
fr_bio_queue_list_cancel(my);
return 0;
}
-static int fr_bio_retry_destructor(fr_bio_retry_t *my)
-{
- return fr_bio_retry_shutdown((fr_bio_t *) my);
-}
-
/** Allocate a #fr_bio_retry_t
*
*/
fr_bio_chain(&my->bio, next);
- talloc_set_destructor(my, fr_bio_retry_destructor);
-
+ talloc_set_destructor((fr_bio_t *) my, fr_bio_destructor); /* always use a common destructor */
return (fr_bio_t *) my;
}
fr_assert_fail("%u tracking entries still allocated at conn close", h->tt->num_requests);
}
- fr_bio_shutdown(h->bio.mem);
-
/*
* We have opened a limited number of outbound source ports. This means that when we close a
* port, we have to mark it unused.
return fr_radius_client_tcp_bio_alloc(ctx, cfg, fd_cfg);
}
-static int _radius_client_fd_bio_free(fr_radius_client_fd_bio_t *my)
-{
- if (fr_bio_shutdown(my->common.bio) < 0) return -1;
-
- if (fr_bio_free(my->common.bio) < 0) return -1;
-
- return 0;
-}
-
-
fr_radius_client_fd_bio_t *fr_radius_client_fd_bio_alloc(TALLOC_CTX *ctx, size_t read_size, fr_radius_client_config_t *cfg, fr_bio_fd_config_t const *fd_cfg)
{
int i;
*/
fr_bio_packet_init(&my->common);
- talloc_set_destructor(my, _radius_client_fd_bio_free);
-
/*
* Set up the connected status.
*/
// return fr_radius_server_tcp_bio_alloc(ctx, cfg, fd_cfg);
}
-static int _radius_server_fd_bio_free(fr_radius_server_fd_bio_t *my)
-{
- if (fr_bio_shutdown(my->common.bio) < 0) return -1;
-
- if (fr_bio_free(my->common.bio) < 0) return -1;
-
- return 0;
-}
-
-
fr_radius_server_fd_bio_t *fr_radius_server_fd_bio_alloc(TALLOC_CTX *ctx, size_t read_size, fr_radius_server_config_t *cfg, fr_bio_fd_config_t const *fd_cfg)
{
fr_radius_server_fd_bio_t *my;
my->common.bio = my->mem;
- talloc_set_destructor(my, _radius_server_fd_bio_free);
-
return my;
}