X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gio%2Fgsocketlistener.c;h=7185745ff170b2ad867a8190c0374a74b9dcba8a;hb=33b9935efc82f8cc4747dfea2743129dfc418d19;hp=1d75a51a3544cca34d2b28b9277764c136033c59;hpb=5cd86fbda662defa03709a9277b25784d953541e;p=platform%2Fupstream%2Fglib.git diff --git a/gio/gsocketlistener.c b/gio/gsocketlistener.c index 1d75a51..7185745 100644 --- a/gio/gsocketlistener.c +++ b/gio/gsocketlistener.c @@ -15,9 +15,7 @@ * 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., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. + * Public License along with this library; if not, see . * * Authors: Christian Kellner * Samuel Cormier-Iijima @@ -28,7 +26,7 @@ #include "config.h" #include "gsocketlistener.h" -#include +#include #include #include #include @@ -38,12 +36,12 @@ #include #include "glibintl.h" -#include "gioalias.h" /** - * SECTION: gsocketlistener + * SECTION:gsocketlistener * @title: GSocketListener * @short_description: Helper for accepting network client connections + * @include: gio/gio.h * @see_also: #GThreadedSocketService, #GSocketService. * * A #GSocketListener is an object that keeps track of a set @@ -57,8 +55,6 @@ * Since: 2.22 */ -G_DEFINE_TYPE (GSocketListener, g_socket_listener, G_TYPE_OBJECT); - enum { PROP_0, @@ -76,6 +72,8 @@ struct _GSocketListenerPrivate guint closed : 1; }; +G_DEFINE_TYPE_WITH_PRIVATE (GSocketListener, g_socket_listener, G_TYPE_OBJECT) + static void g_socket_listener_finalize (GObject *object) { @@ -84,9 +82,11 @@ g_socket_listener_finalize (GObject *object) if (listener->priv->main_context) g_main_context_unref (listener->priv->main_context); - if (!listener->priv->closed) - g_socket_listener_close (listener); - + /* Do not explicitly close the sockets. Instead, let them close themselves if + * their final reference is dropped, but keep them open if a reference is + * held externally to the GSocketListener (which is possible if + * g_socket_listener_add_socket() was used). + */ g_ptr_array_free (listener->priv->sockets, TRUE); G_OBJECT_CLASS (g_socket_listener_parent_class) @@ -137,8 +137,6 @@ g_socket_listener_class_init (GSocketListenerClass *klass) { GObjectClass *gobject_class G_GNUC_UNUSED = G_OBJECT_CLASS (klass); - g_type_class_add_private (klass, sizeof (GSocketListenerPrivate)); - gobject_class->finalize = g_socket_listener_finalize; gobject_class->set_property = g_socket_listener_set_property; gobject_class->get_property = g_socket_listener_get_property; @@ -157,9 +155,7 @@ g_socket_listener_class_init (GSocketListenerClass *klass) static void g_socket_listener_init (GSocketListener *listener) { - listener->priv = G_TYPE_INSTANCE_GET_PRIVATE (listener, - G_TYPE_SOCKET_LISTENER, - GSocketListenerPrivate); + listener->priv = g_socket_listener_get_instance_private (listener); listener->priv->sockets = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); listener->priv->listen_backlog = 10; @@ -175,7 +171,7 @@ g_socket_listener_init (GSocketListener *listener) * Returns: a new #GSocketListener. * * Since: 2.22 - **/ + */ GSocketListener * g_socket_listener_new (void) { @@ -200,7 +196,7 @@ check_listener (GSocketListener *listener, * g_socket_listener_add_socket: * @listener: a #GSocketListener * @socket: a listening #GSocket - * @source_object: Optional #GObject identifying this source + * @source_object: (allow-none): Optional #GObject identifying this source * @error: #GError for error reporting, or %NULL to ignore. * * Adds @socket to the set of sockets that we try to accept @@ -212,15 +208,20 @@ check_listener (GSocketListener *listener, * useful if you're listening on multiple addresses and do * different things depending on what address is connected to. * + * The @socket will not be automatically closed when the @listener is finalized + * unless the listener held the final reference to the socket. Before GLib 2.42, + * the @socket was automatically closed on finalization of the @listener, even + * if references to it were held elsewhere. + * * Returns: %TRUE on success, %FALSE on error. * * Since: 2.22 - **/ + */ gboolean -g_socket_listener_add_socket (GSocketListener *listener, - GSocket *socket, - GObject *source_object, - GError **error) +g_socket_listener_add_socket (GSocketListener *listener, + GSocket *socket, + GObject *source_object, + GError **error) { if (!check_listener (listener, error)) return FALSE; @@ -234,12 +235,17 @@ g_socket_listener_add_socket (GSocketListener *listener, return FALSE; } + g_object_ref (socket); g_ptr_array_add (listener->priv->sockets, socket); if (source_object) g_object_set_qdata_full (G_OBJECT (socket), source_quark, g_object_ref (source_object), g_object_unref); + + if (G_SOCKET_LISTENER_GET_CLASS (listener)->changed) + G_SOCKET_LISTENER_GET_CLASS (listener)->changed (listener); + return TRUE; } @@ -249,30 +255,44 @@ g_socket_listener_add_socket (GSocketListener *listener, * @address: a #GSocketAddress * @type: a #GSocketType * @protocol: a #GSocketProtocol - * @source_object: Optional #GObject identifying this source + * @source_object: (allow-none): Optional #GObject identifying this source + * @effective_address: (out) (allow-none): location to store the address that was bound to, or %NULL. * @error: #GError for error reporting, or %NULL to ignore. * * Creates a socket of type @type and protocol @protocol, binds * it to @address and adds it to the set of sockets we're accepting * sockets from. * + * Note that adding an IPv6 address, depending on the platform, + * may or may not result in a listener that also accepts IPv4 + * connections. For more deterministic behavior, see + * g_socket_listener_add_inet_port(). + * * @source_object will be passed out in the various calls * to accept to identify this particular source, which is * useful if you're listening on multiple addresses and do * different things depending on what address is connected to. * + * If successful and @effective_address is non-%NULL then it will + * be set to the address that the binding actually occurred at. This + * is helpful for determining the port number that was used for when + * requesting a binding to port 0 (ie: "any port"). This address, if + * requested, belongs to the caller and must be freed. + * * Returns: %TRUE on success, %FALSE on error. * * Since: 2.22 - **/ + */ gboolean -g_socket_listener_add_address (GSocketListener *listener, - GSocketAddress *address, - GSocketType type, - GSocketProtocol protocol, - GObject *source_object, - GError **error) +g_socket_listener_add_address (GSocketListener *listener, + GSocketAddress *address, + GSocketType type, + GSocketProtocol protocol, + GObject *source_object, + GSocketAddress **effective_address, + GError **error) { + GSocketAddress *local_address; GSocketFamily family; GSocket *socket; @@ -287,17 +307,37 @@ g_socket_listener_add_address (GSocketListener *listener, g_socket_set_listen_backlog (socket, listener->priv->listen_backlog); if (!g_socket_bind (socket, address, TRUE, error) || - !g_socket_listen (socket, error) || - !g_socket_listener_add_socket (listener, socket, + !g_socket_listen (socket, error)) + { + g_object_unref (socket); + return FALSE; + } + + local_address = NULL; + if (effective_address) + { + local_address = g_socket_get_local_address (socket, error); + if (local_address == NULL) + { + g_object_unref (socket); + return FALSE; + } + } + + if (!g_socket_listener_add_socket (listener, socket, source_object, error)) { + if (local_address) + g_object_unref (local_address); g_object_unref (socket); return FALSE; } - if (G_SOCKET_LISTENER_GET_CLASS (listener)->changed) - G_SOCKET_LISTENER_GET_CLASS (listener)->changed (listener); + if (effective_address) + *effective_address = local_address; + + g_object_unref (socket); /* add_socket refs this */ return TRUE; } @@ -305,8 +345,8 @@ g_socket_listener_add_address (GSocketListener *listener, /** * g_socket_listener_add_inet_port: * @listener: a #GSocketListener - * @port: an ip port number - * @source_object: Optional #GObject identifying this source + * @port: an IP port number (non-zero) + * @source_object: (allow-none): Optional #GObject identifying this source * @error: #GError for error reporting, or %NULL to ignore. * * Helper function for g_socket_listener_add_address() that @@ -321,69 +361,152 @@ g_socket_listener_add_address (GSocketListener *listener, * Returns: %TRUE on success, %FALSE on error. * * Since: 2.22 - **/ + */ gboolean -g_socket_listener_add_inet_port (GSocketListener *listener, - int port, - GObject *source_object, - GError **error) +g_socket_listener_add_inet_port (GSocketListener *listener, + guint16 port, + GObject *source_object, + GError **error) { - GSocketAddress *address4, *address6; - GInetAddress *inet_address; - gboolean res; + gboolean need_ipv4_socket = TRUE; + GSocket *socket4 = NULL; + GSocket *socket6; + + g_return_val_if_fail (listener != NULL, FALSE); + g_return_val_if_fail (port != 0, FALSE); if (!check_listener (listener, error)) return FALSE; - inet_address = g_inet_address_new_any (G_SOCKET_FAMILY_IPV4); - address4 = g_inet_socket_address_new (inet_address, port); - g_object_unref (inet_address); + /* first try to create an IPv6 socket */ + socket6 = g_socket_new (G_SOCKET_FAMILY_IPV6, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + NULL); + + if (socket6 != NULL) + /* IPv6 is supported on this platform, so if we fail now it is + * a result of being unable to bind to our port. Don't fail + * silently as a result of this! + */ + { + GInetAddress *inet_address; + GSocketAddress *address; + gboolean result; + + inet_address = g_inet_address_new_any (G_SOCKET_FAMILY_IPV6); + address = g_inet_socket_address_new (inet_address, port); + g_object_unref (inet_address); - inet_address = g_inet_address_new_any (G_SOCKET_FAMILY_IPV6); - address6 = g_inet_socket_address_new (inet_address, port); - g_object_unref (inet_address); + g_socket_set_listen_backlog (socket6, listener->priv->listen_backlog); - if (!g_socket_listener_add_address (listener, - address6, - G_SOCKET_TYPE_STREAM, - G_SOCKET_PROTOCOL_DEFAULT, - source_object, - NULL)) - { - /* Failed, to create ipv6, socket, just use ipv4, - return any error */ - res = g_socket_listener_add_address (listener, - address4, - G_SOCKET_TYPE_STREAM, - G_SOCKET_PROTOCOL_DEFAULT, - source_object, - error); + result = g_socket_bind (socket6, address, TRUE, error) && + g_socket_listen (socket6, error); + + g_object_unref (address); + + if (!result) + { + g_object_unref (socket6); + + return FALSE; + } + + if (source_object) + g_object_set_qdata_full (G_OBJECT (socket6), source_quark, + g_object_ref (source_object), + g_object_unref); + + /* If this socket already speaks IPv4 then we are done. */ + if (g_socket_speaks_ipv4 (socket6)) + need_ipv4_socket = FALSE; } - else + + if (need_ipv4_socket) + /* We are here for exactly one of the following reasons: + * + * - our platform doesn't support IPv6 + * - we successfully created an IPv6 socket but it's V6ONLY + * + * In either case, we need to go ahead and create an IPv4 socket + * and fail the call if we can't bind to it. + */ { - /* Succeeded with ipv6, also try ipv4 in case its ipv6 only, - but ignore errors here */ - res = TRUE; - g_socket_listener_add_address (listener, - address4, - G_SOCKET_TYPE_STREAM, - G_SOCKET_PROTOCOL_DEFAULT, - source_object, - NULL); + socket4 = g_socket_new (G_SOCKET_FAMILY_IPV4, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + error); + + if (socket4 != NULL) + /* IPv4 is supported on this platform, so if we fail now it is + * a result of being unable to bind to our port. Don't fail + * silently as a result of this! + */ + { + GInetAddress *inet_address; + GSocketAddress *address; + gboolean result; + + inet_address = g_inet_address_new_any (G_SOCKET_FAMILY_IPV4); + address = g_inet_socket_address_new (inet_address, port); + g_object_unref (inet_address); + + g_socket_set_listen_backlog (socket4, + listener->priv->listen_backlog); + + result = g_socket_bind (socket4, address, TRUE, error) && + g_socket_listen (socket4, error); + + g_object_unref (address); + + if (!result) + { + g_object_unref (socket4); + + if (socket6 != NULL) + g_object_unref (socket6); + + return FALSE; + } + + if (source_object) + g_object_set_qdata_full (G_OBJECT (socket4), source_quark, + g_object_ref (source_object), + g_object_unref); + } + else + /* Ok. So IPv4 is not supported on this platform. If we + * succeeded at creating an IPv6 socket then that's OK, but + * otherwise we need to tell the user we failed. + */ + { + if (socket6 != NULL) + g_clear_error (error); + else + return FALSE; + } } - g_object_unref (address4); - g_object_unref (address6); + g_assert (socket6 != NULL || socket4 != NULL); + + if (socket6 != NULL) + g_ptr_array_add (listener->priv->sockets, socket6); - return res; + if (socket4 != NULL) + g_ptr_array_add (listener->priv->sockets, socket4); + + if (G_SOCKET_LISTENER_GET_CLASS (listener)->changed) + G_SOCKET_LISTENER_GET_CLASS (listener)->changed (listener); + + return TRUE; } static GList * -add_sources (GSocketListener *listener, - GSocketSourceFunc callback, - gpointer callback_data, - GCancellable *cancellable, - GMainContext *context) +add_sources (GSocketListener *listener, + GSocketSourceFunc callback, + gpointer callback_data, + GCancellable *cancellable, + GMainContext *context) { GSocket *socket; GSource *source; @@ -426,9 +549,9 @@ struct AcceptData { }; static gboolean -accept_callback (GSocket *socket, - GIOCondition condition, - gpointer user_data) +accept_callback (GSocket *socket, + GIOCondition condition, + gpointer user_data) { struct AcceptData *data = user_data; @@ -441,8 +564,8 @@ accept_callback (GSocket *socket, /** * g_socket_listener_accept_socket: * @listener: a #GSocketListener - * @source_object: location where #GObject pointer will be stored, or %NULL - * @cancellable: optional #GCancellable object, %NULL to ignore. + * @source_object: (out) (transfer none) (allow-none): location where #GObject pointer will be stored, or %NULL. + * @cancellable: (allow-none): optional #GCancellable object, %NULL to ignore. * @error: #GError for error reporting, or %NULL to ignore. * * Blocks waiting for a client to connect to any of the sockets added @@ -456,14 +579,14 @@ accept_callback (GSocket *socket, * object specified when the corresponding socket or address was added * to the listener. * - * If @cancellable is not NULL, then the operation can be cancelled by + * If @cancellable is not %NULL, then the operation can be cancelled by * triggering the cancellable object from another thread. If the operation - * was cancelled, the error G_IO_ERROR_CANCELLED will be returned. + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. * - * Returns: a #GSocket on success, %NULL on error. + * Returns: (transfer full): a #GSocket on success, %NULL on error. * * Since: 2.22 - **/ + */ GSocket * g_socket_listener_accept_socket (GSocketListener *listener, GObject **source_object, @@ -506,7 +629,7 @@ g_socket_listener_accept_socket (GSocketListener *listener, g_main_loop_unref (loop); } - if (!(socket = g_socket_accept (accept_socket, error))) + if (!(socket = g_socket_accept (accept_socket, cancellable, error))) return NULL; if (source_object) @@ -518,8 +641,8 @@ g_socket_listener_accept_socket (GSocketListener *listener, /** * g_socket_listener_accept: * @listener: a #GSocketListener - * @source_object: location where #GObject pointer will be stored, or %NULL - * @cancellable: optional #GCancellable object, %NULL to ignore. + * @source_object: (out) (transfer none) (allow-none): location where #GObject pointer will be stored, or %NULL + * @cancellable: (allow-none): optional #GCancellable object, %NULL to ignore. * @error: #GError for error reporting, or %NULL to ignore. * * Blocks waiting for a client to connect to any of the sockets added @@ -530,14 +653,14 @@ g_socket_listener_accept_socket (GSocketListener *listener, * object specified when the corresponding socket or address was added * to the listener. * - * If @cancellable is not NULL, then the operation can be cancelled by + * If @cancellable is not %NULL, then the operation can be cancelled by * triggering the cancellable object from another thread. If the operation - * was cancelled, the error G_IO_ERROR_CANCELLED will be returned. + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. * - * Returns: a #GSocketConnection on success, %NULL on error. + * Returns: (transfer full): a #GSocketConnection on success, %NULL on error. * * Since: 2.22 - **/ + */ GSocketConnection * g_socket_listener_accept (GSocketListener *listener, GObject **source_object, @@ -560,153 +683,121 @@ g_socket_listener_accept (GSocketListener *listener, return connection; } -struct AcceptAsyncData { - GSimpleAsyncResult *simple; - GCancellable *cancellable; - GList *sources; -}; - static gboolean -accept_ready (GSocket *accept_socket, - GIOCondition condition, - gpointer _data) +accept_ready (GSocket *accept_socket, + GIOCondition condition, + gpointer user_data) { - struct AcceptAsyncData *data = _data; + GTask *task = user_data; GError *error = NULL; + GSocket *socket; + GObject *source_object; - if (!g_cancellable_set_error_if_cancelled (data->cancellable, - &error)) + socket = g_socket_accept (accept_socket, g_task_get_cancellable (task), &error); + if (socket) { - GSocket *socket; - GObject *source_object; - - socket = g_socket_accept (accept_socket, &error); - if (socket) - { - g_simple_async_result_set_op_res_gpointer (data->simple, socket, - g_object_unref); - source_object = g_object_get_qdata (G_OBJECT (accept_socket), source_quark); - if (source_object) - g_object_set_qdata_full (G_OBJECT (data->simple), - source_quark, - g_object_ref (source_object), g_object_unref); - } + source_object = g_object_get_qdata (G_OBJECT (accept_socket), source_quark); + if (source_object) + g_object_set_qdata_full (G_OBJECT (task), + source_quark, + g_object_ref (source_object), g_object_unref); + g_task_return_pointer (task, socket, g_object_unref); } - - if (error) + else { - g_simple_async_result_set_from_error (data->simple, error); - g_error_free (error); + g_task_return_error (task, error); } - g_simple_async_result_complete_in_idle (data->simple); - g_object_unref (data->simple); - free_sources (data->sources); - g_free (data); - + g_object_unref (task); return FALSE; } /** * g_socket_listener_accept_socket_async: * @listener: a #GSocketListener - * @cancellable: a #GCancellable, or %NULL - * @callback: a #GAsyncReadyCallback - * @user_data: user data for the callback + * @cancellable: (allow-none): a #GCancellable, or %NULL + * @callback: (scope async): a #GAsyncReadyCallback + * @user_data: (closure): user data for the callback * * This is the asynchronous version of g_socket_listener_accept_socket(). * * When the operation is finished @callback will be - * called. You can then call g_socket_listener_accept_socket_finish() to get - * the result of the operation. + * called. You can then call g_socket_listener_accept_socket_finish() + * to get the result of the operation. * * Since: 2.22 - **/ + */ void -g_socket_listener_accept_socket_async (GSocketListener *listener, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) +g_socket_listener_accept_socket_async (GSocketListener *listener, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { - struct AcceptAsyncData *data; + GTask *task; + GList *sources; GError *error = NULL; + task = g_task_new (listener, cancellable, callback, user_data); + if (!check_listener (listener, &error)) { - g_simple_async_report_gerror_in_idle (G_OBJECT (listener), - callback, user_data, - error); - g_error_free (error); + g_task_return_error (task, error); + g_object_unref (task); return; } - data = g_new0 (struct AcceptAsyncData, 1); - data->simple = g_simple_async_result_new (G_OBJECT (listener), - callback, user_data, - g_socket_listener_accept_socket_async); - data->cancellable = cancellable; - data->sources = add_sources (listener, - accept_ready, - data, - cancellable, - NULL); + sources = add_sources (listener, + accept_ready, + task, + cancellable, + g_main_context_get_thread_default ()); + g_task_set_task_data (task, sources, (GDestroyNotify) free_sources); } /** * g_socket_listener_accept_socket_finish: * @listener: a #GSocketListener * @result: a #GAsyncResult. - * @source_object: Optional #GObject identifying this source - * @error: a #GError location to store the error occuring, or %NULL to + * @source_object: (out) (transfer none) (allow-none): Optional #GObject identifying this source + * @error: a #GError location to store the error occurring, or %NULL to * ignore. * * Finishes an async accept operation. See g_socket_listener_accept_socket_async() * - * Returns: a #GSocket on success, %NULL on error. + * Returns: (transfer full): a #GSocket on success, %NULL on error. * * Since: 2.22 - **/ + */ GSocket * -g_socket_listener_accept_socket_finish (GSocketListener *listener, - GAsyncResult *result, - GObject **source_object, - GError **error) +g_socket_listener_accept_socket_finish (GSocketListener *listener, + GAsyncResult *result, + GObject **source_object, + GError **error) { - GSocket *socket; - GSimpleAsyncResult *simple; - - g_return_val_if_fail (G_IS_SOCKET_LISTENER (listener), FALSE); - - simple = G_SIMPLE_ASYNC_RESULT (result); - - if (g_simple_async_result_propagate_error (simple, error)) - return NULL; - - g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == g_socket_listener_accept_socket_async); - - socket = g_simple_async_result_get_op_res_gpointer (simple); + g_return_val_if_fail (G_IS_SOCKET_LISTENER (listener), NULL); + g_return_val_if_fail (g_task_is_valid (result, listener), NULL); if (source_object) *source_object = g_object_get_qdata (G_OBJECT (result), source_quark); - return g_object_ref (socket); + return g_task_propagate_pointer (G_TASK (result), error); } /** * g_socket_listener_accept_async: * @listener: a #GSocketListener - * @cancellable: a #GCancellable, or %NULL - * @callback: a #GAsyncReadyCallback - * @user_data: user data for the callback + * @cancellable: (allow-none): a #GCancellable, or %NULL + * @callback: (scope async): a #GAsyncReadyCallback + * @user_data: (closure): user data for the callback * * This is the asynchronous version of g_socket_listener_accept(). * * When the operation is finished @callback will be - * called. You can then call g_socket_listener_accept_socket() to get - * the result of the operation. + * called. You can then call g_socket_listener_accept_socket() + * to get the result of the operation. * * Since: 2.22 - **/ + */ void g_socket_listener_accept_async (GSocketListener *listener, GCancellable *cancellable, @@ -723,21 +814,21 @@ g_socket_listener_accept_async (GSocketListener *listener, * g_socket_listener_accept_finish: * @listener: a #GSocketListener * @result: a #GAsyncResult. - * @source_object: Optional #GObject identifying this source - * @error: a #GError location to store the error occuring, or %NULL to + * @source_object: (out) (transfer none) (allow-none): Optional #GObject identifying this source + * @error: a #GError location to store the error occurring, or %NULL to * ignore. * * Finishes an async accept operation. See g_socket_listener_accept_async() * - * Returns: a #GSocketConnection on success, %NULL on error. + * Returns: (transfer full): a #GSocketConnection on success, %NULL on error. * * Since: 2.22 - **/ + */ GSocketConnection * -g_socket_listener_accept_finish (GSocketListener *listener, - GAsyncResult *result, - GObject **source_object, - GError **error) +g_socket_listener_accept_finish (GSocketListener *listener, + GAsyncResult *result, + GObject **source_object, + GError **error) { GSocket *socket; GSocketConnection *connection; @@ -764,10 +855,10 @@ g_socket_listener_accept_finish (GSocketListener *listener, * See g_socket_set_listen_backlog() for details * * Since: 2.22 - **/ + */ void g_socket_listener_set_backlog (GSocketListener *listener, - int listen_backlog) + int listen_backlog) { GSocket *socket; int i; @@ -791,7 +882,7 @@ g_socket_listener_set_backlog (GSocketListener *listener, * Closes all the sockets in the listener. * * Since: 2.22 - **/ + */ void g_socket_listener_close (GSocketListener *listener) { @@ -811,5 +902,227 @@ g_socket_listener_close (GSocketListener *listener) listener->priv->closed = TRUE; } -#define __G_SOCKET_LISTENER_C__ -#include "gioaliasdef.c" +/** + * g_socket_listener_add_any_inet_port: + * @listener: a #GSocketListener + * @source_object: (allow-none): Optional #GObject identifying this source + * @error: a #GError location to store the error occurring, or %NULL to + * ignore. + * + * Listens for TCP connections on any available port number for both + * IPv6 and IPv4 (if each is available). + * + * This is useful if you need to have a socket for incoming connections + * but don't care about the specific port number. + * + * @source_object will be passed out in the various calls + * to accept to identify this particular source, which is + * useful if you're listening on multiple addresses and do + * different things depending on what address is connected to. + * + * Returns: the port number, or 0 in case of failure. + * + * Since: 2.24 + **/ +guint16 +g_socket_listener_add_any_inet_port (GSocketListener *listener, + GObject *source_object, + GError **error) +{ + GSList *sockets_to_close = NULL; + guint16 candidate_port = 0; + GSocket *socket6 = NULL; + GSocket *socket4 = NULL; + gint attempts = 37; + + /* + * multi-step process: + * - first, create an IPv6 socket. + * - if that fails, create an IPv4 socket and bind it to port 0 and + * that's it. no retries if that fails (why would it?). + * - if our IPv6 socket also speaks IPv4 then we are done. + * - if not, then we need to create a IPv4 socket with the same port + * number. this might fail, of course. so we try this a bunch of + * times -- leaving the old IPv6 sockets open so that we get a + * different port number to try each time. + * - if all that fails then just give up. + */ + + while (attempts--) + { + GInetAddress *inet_address; + GSocketAddress *address; + gboolean result; + + g_assert (socket6 == NULL); + socket6 = g_socket_new (G_SOCKET_FAMILY_IPV6, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + NULL); + + if (socket6 != NULL) + { + inet_address = g_inet_address_new_any (G_SOCKET_FAMILY_IPV6); + address = g_inet_socket_address_new (inet_address, 0); + g_object_unref (inet_address); + result = g_socket_bind (socket6, address, TRUE, error); + g_object_unref (address); + + if (!result || + !(address = g_socket_get_local_address (socket6, error))) + { + g_object_unref (socket6); + socket6 = NULL; + break; + } + + g_assert (G_IS_INET_SOCKET_ADDRESS (address)); + candidate_port = + g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (address)); + g_assert (candidate_port != 0); + g_object_unref (address); + + if (g_socket_speaks_ipv4 (socket6)) + break; + } + + g_assert (socket4 == NULL); + socket4 = g_socket_new (G_SOCKET_FAMILY_IPV4, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + socket6 ? NULL : error); + + if (socket4 == NULL) + /* IPv4 not supported. + * if IPv6 is supported then candidate_port will be non-zero + * (and the error parameter above will have been NULL) + * if IPv6 is unsupported then candidate_port will be zero + * (and error will have been set by the above call) + */ + break; + + inet_address = g_inet_address_new_any (G_SOCKET_FAMILY_IPV4); + address = g_inet_socket_address_new (inet_address, candidate_port); + g_object_unref (inet_address); + /* a note on the 'error' clause below: + * + * if candidate_port is 0 then we report the error right away + * since it is strange that this binding would fail at all. + * otherwise, we ignore the error message (ie: NULL). + * + * the exception to this rule is the last time through the loop + * (ie: attempts == 0) in which case we want to set the error + * because failure here means that the entire call will fail and + * we need something to show to the user. + * + * an english summary of the situation: "if we gave a candidate + * port number AND we have more attempts to try, then ignore the + * error for now". + */ + result = g_socket_bind (socket4, address, TRUE, + (candidate_port && attempts) ? NULL : error); + g_object_unref (address); + + if (candidate_port) + { + g_assert (socket6 != NULL); + + if (result) + /* got our candidate port successfully */ + break; + + else + /* we failed to bind to the specified port. try again. */ + { + g_object_unref (socket4); + socket4 = NULL; + + /* keep this open so we get a different port number */ + sockets_to_close = g_slist_prepend (sockets_to_close, + socket6); + candidate_port = 0; + socket6 = NULL; + } + } + else + /* we didn't tell it a port. this means two things. + * - if we failed, then something really bad happened. + * - if we succeeded, then we need to find out the port number. + */ + { + g_assert (socket6 == NULL); + + if (!result || + !(address = g_socket_get_local_address (socket4, error))) + { + g_object_unref (socket4); + socket4 = NULL; + break; + } + + g_assert (G_IS_INET_SOCKET_ADDRESS (address)); + candidate_port = + g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (address)); + g_assert (candidate_port != 0); + g_object_unref (address); + break; + } + } + + /* should only be non-zero if we have a socket */ + g_assert ((candidate_port != 0) == (socket4 || socket6)); + + while (sockets_to_close) + { + g_object_unref (sockets_to_close->data); + sockets_to_close = g_slist_delete_link (sockets_to_close, + sockets_to_close); + } + + /* now we actually listen() the sockets and add them to the listener */ + if (socket6 != NULL) + { + g_socket_set_listen_backlog (socket6, listener->priv->listen_backlog); + if (!g_socket_listen (socket6, error)) + { + g_object_unref (socket6); + if (socket4) + g_object_unref (socket4); + + return 0; + } + + if (source_object) + g_object_set_qdata_full (G_OBJECT (socket6), source_quark, + g_object_ref (source_object), + g_object_unref); + + g_ptr_array_add (listener->priv->sockets, socket6); + } + + if (socket4 != NULL) + { + g_socket_set_listen_backlog (socket4, listener->priv->listen_backlog); + if (!g_socket_listen (socket4, error)) + { + g_object_unref (socket4); + if (socket6) + g_object_unref (socket6); + + return 0; + } + + if (source_object) + g_object_set_qdata_full (G_OBJECT (socket4), source_quark, + g_object_ref (source_object), + g_object_unref); + + g_ptr_array_add (listener->priv->sockets, socket4); + } + + if ((socket4 != NULL || socket6 != NULL) && + G_SOCKET_LISTENER_GET_CLASS (listener)->changed) + G_SOCKET_LISTENER_GET_CLASS (listener)->changed (listener); + + return candidate_port; +}