* 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 <http://www.gnu.org/licenses/>.
*
* Authors: Christian Kellner <gicmo@gnome.org>
* Samuel Cormier-Iijima <sciyoshi@gmail.com>
#include "config.h"
#include "gsocketlistener.h"
-#include <gio/gsimpleasyncresult.h>
+#include <gio/gtask.h>
#include <gio/gcancellable.h>
#include <gio/gsocketaddress.h>
#include <gio/ginetaddress.h>
#include <gio/ginetsocketaddress.h>
#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
* Since: 2.22
*/
-G_DEFINE_TYPE (GSocketListener, g_socket_listener, G_TYPE_OBJECT);
-
enum
{
PROP_0,
guint closed : 1;
};
+G_DEFINE_TYPE_WITH_PRIVATE (GSocketListener, g_socket_listener, G_TYPE_OBJECT)
+
static void
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)
{
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;
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;
* 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
* 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
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;
}
* @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
GSocketType type,
GSocketProtocol protocol,
GObject *source_object,
+ GSocketAddress **effective_address,
GError **error)
{
+ GSocketAddress *local_address;
GSocketFamily family;
GSocket *socket;
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;
}
/**
* 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
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);
- address6 = g_inet_socket_address_new (inet_address, port);
- g_object_unref (inet_address);
+ 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);
- 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);
+ g_socket_set_listen_backlog (socket6, listener->priv->listen_backlog);
+
+ 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);
- return res;
+ if (socket6 != NULL)
+ g_ptr_array_add (listener->priv->sockets, socket6);
+
+ 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 *
/**
* 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
* triggering the cancellable object from another thread. If the operation
* 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
*/
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)
/**
* 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
* triggering the cancellable object from another thread. If the operation
* 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
*/
return connection;
}
-struct AcceptAsyncData {
- GSimpleAsyncResult *simple;
- GCancellable *cancellable;
- GList *sources;
-};
-
static gboolean
accept_ready (GSocket *accept_socket,
GIOCondition condition,
- gpointer _data)
+ 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().
*
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
*/
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().
*
* 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
*/
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;
+}