Add io_glib_shutdown_linger() for socket shutdown with wait for remote ACK via SO_LINGER. E.g. wait for L2CAP Disconnect Rsp. We don't want to block the main loop for the linger timeout, so call shutdown() in a separate thread, as socket API seems to provide only the blocking way to do it. Implement it with Glib source API, as that's more convenient for the AVDTP plugin that needs this. --- src/shared/io-ell.c | 6 +++ src/shared/io-glib.c | 89 ++++++++++++++++++++++++++++++++++++++++ src/shared/io-mainloop.c | 6 +++ src/shared/io.h | 3 ++ 4 files changed, 104 insertions(+) diff --git a/src/shared/io-ell.c b/src/shared/io-ell.c index 4d64cf3c5..e7baed0d3 100644 --- a/src/shared/io-ell.c +++ b/src/shared/io-ell.c @@ -315,3 +315,9 @@ unsigned int io_glib_add_err_watch(void *giochannel, io_glib_err_func_t func, { return 0; } + +unsigned int io_glib_shutdown_linger(void *giochannel, int how, int timeout, + io_destroy_func_t func, void *user_data) +{ + return 0; +} diff --git a/src/shared/io-glib.c b/src/shared/io-glib.c index 81cd1122b..efb7b9f5c 100644 --- a/src/shared/io-glib.c +++ b/src/shared/io-glib.c @@ -461,3 +461,92 @@ unsigned int io_glib_add_err_watch(void *giochannel, G_IO_ERR | G_IO_HUP | G_IO_NVAL, err_watch_callback, data, g_free); } + +/* + * shutdown() socket, enabling SO_LINGER to wait for close ACK, with + * asynchronous callback. + */ + +struct shutdown_linger { + GSource source; + io_destroy_func_t func; + void *user_data; + int how; + GIOChannel *io; + GThread *thread; +}; + +static gpointer shutdown_linger_thread(gpointer data) +{ + struct shutdown_linger *source = data; + + shutdown(g_io_channel_unix_get_fd(source->io), source->how); + g_source_set_ready_time(&source->source, 0); + g_source_unref(&source->source); + return NULL; +} + +static gboolean shutdown_linger_dispatch(GSource *gsource, GSourceFunc callback, + gpointer user_data) +{ + struct shutdown_linger *source = (void *)gsource; + + if (source->func) + source->func(source->user_data); + return FALSE; +} + +static void shutdown_linger_finalize(GSource *gsource) +{ + struct shutdown_linger *source = (void *)gsource; + + if (source->thread == g_thread_self()) + g_thread_unref(source->thread); + else + g_thread_join(source->thread); + + g_io_channel_unref(source->io); +} + +unsigned int io_glib_shutdown_linger(void *giochannel, int how, int timeout, + io_destroy_func_t func, void *user_data) +{ + static GSourceFuncs source_funcs = { + .dispatch = shutdown_linger_dispatch, + .finalize = shutdown_linger_finalize, + }; + struct linger linger = { + .l_onoff = 1, + .l_linger = timeout, + }; + GIOChannel *io = giochannel; + struct shutdown_linger *source; + guint id; + int fd; + + if (!io) + return 0; + + fd = g_io_channel_unix_get_fd(io); + if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger))) { + shutdown(fd, how); + return 0; + } + + source = (void *)g_source_new(&source_funcs, sizeof(*source)); + + g_source_set_name(&source->source, "shutdown_linger"); + source->func = func; + source->user_data = user_data; + source->how = how; + source->io = g_io_channel_ref(io); + + g_source_ref(&source->source); /* unref in thread */ + source->thread = g_thread_new("shutdown_linger", shutdown_linger_thread, + source); + + id = g_source_attach(&source->source, NULL); + g_source_unref(&source->source); + + return id; +} diff --git a/src/shared/io-mainloop.c b/src/shared/io-mainloop.c index 8fd49935e..abe76de1d 100644 --- a/src/shared/io-mainloop.c +++ b/src/shared/io-mainloop.c @@ -321,3 +321,9 @@ unsigned int io_glib_add_err_watch(void *giochannel, io_glib_err_func_t func, { return 0; } + +unsigned int io_glib_shutdown_linger(void *giochannel, int how, int timeout, + io_destroy_func_t func, void *user_data) +{ + return 0; +} diff --git a/src/shared/io.h b/src/shared/io.h index 87c3c001c..7909b1707 100644 --- a/src/shared/io.h +++ b/src/shared/io.h @@ -37,3 +37,6 @@ bool io_set_disconnect_handler(struct io *io, io_callback_func_t callback, typedef void (*io_glib_err_func_t)(int cond, void *user_data); unsigned int io_glib_add_err_watch(void *giochannel, io_glib_err_func_t func, void *user_data); + +unsigned int io_glib_shutdown_linger(void *giochannel, int how, int timeout, + io_destroy_func_t func, void *user_data); -- 2.51.0