mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-26 15:36:14 +01:00
5942cd7984
Add SPDX license (but not copyright) headers to all files which follow a certain pattern in their existing non-machine-readable header comment. This commit was entirely generated using the command: ``` git ls-files gio/*.[ch] | xargs perl -0777 -pi -e 's/\n \*\n \* This library is free software; you can redistribute it and\/or\n \* modify it under the terms of the GNU Lesser General Public/\n \*\n \* SPDX-License-Identifier: LGPL-2.1-or-later\n \*\n \* This library is free software; you can redistribute it and\/or\n \* modify it under the terms of the GNU Lesser General Public/igs' ``` Signed-off-by: Philip Withnall <pwithnall@endlessos.org> Helps: #1415
1284 lines
39 KiB
C
1284 lines
39 KiB
C
/* GIO - GLib Input, Output and Streaming Library
|
||
*
|
||
* Copyright © 2008 Christian Kellner, Samuel Cormier-Iijima
|
||
* Copyright © 2009 codethink
|
||
* Copyright © 2009 Red Hat, Inc
|
||
*
|
||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||
*
|
||
* 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.1 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, see <http://www.gnu.org/licenses/>.
|
||
*
|
||
* Authors: Christian Kellner <gicmo@gnome.org>
|
||
* Samuel Cormier-Iijima <sciyoshi@gmail.com>
|
||
* Ryan Lortie <desrt@desrt.ca>
|
||
* Alexander Larsson <alexl@redhat.com>
|
||
*/
|
||
|
||
#include "config.h"
|
||
#include "gsocketlistener.h"
|
||
|
||
#include <gio/gioenumtypes.h>
|
||
#include <gio/gtask.h>
|
||
#include <gio/gcancellable.h>
|
||
#include <gio/gsocketaddress.h>
|
||
#include <gio/ginetaddress.h>
|
||
#include <gio/gioerror.h>
|
||
#include <gio/gsocket.h>
|
||
#include <gio/gsocketconnection.h>
|
||
#include <gio/ginetsocketaddress.h>
|
||
#include "glibintl.h"
|
||
#include "gmarshal-internal.h"
|
||
|
||
|
||
/**
|
||
* 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
|
||
* of server sockets and helps you accept sockets from any of the
|
||
* socket, either sync or async.
|
||
*
|
||
* Add addresses and ports to listen on using g_socket_listener_add_address()
|
||
* and g_socket_listener_add_inet_port(). These will be listened on until
|
||
* g_socket_listener_close() is called. Dropping your final reference to the
|
||
* #GSocketListener will not cause g_socket_listener_close() to be called
|
||
* implicitly, as some references to the #GSocketListener may be held
|
||
* internally.
|
||
*
|
||
* If you want to implement a network server, also look at #GSocketService
|
||
* and #GThreadedSocketService which are subclasses of #GSocketListener
|
||
* that make this even easier.
|
||
*
|
||
* Since: 2.22
|
||
*/
|
||
|
||
enum
|
||
{
|
||
PROP_0,
|
||
PROP_LISTEN_BACKLOG
|
||
};
|
||
|
||
enum
|
||
{
|
||
EVENT,
|
||
LAST_SIGNAL
|
||
};
|
||
|
||
static guint signals[LAST_SIGNAL] = { 0 };
|
||
|
||
static GQuark source_quark = 0;
|
||
|
||
struct _GSocketListenerPrivate
|
||
{
|
||
GPtrArray *sockets;
|
||
GMainContext *main_context;
|
||
int listen_backlog;
|
||
guint closed : 1;
|
||
};
|
||
|
||
G_DEFINE_TYPE_WITH_PRIVATE (GSocketListener, g_socket_listener, G_TYPE_OBJECT)
|
||
|
||
static void
|
||
g_socket_listener_finalize (GObject *object)
|
||
{
|
||
GSocketListener *listener = G_SOCKET_LISTENER (object);
|
||
|
||
if (listener->priv->main_context)
|
||
g_main_context_unref (listener->priv->main_context);
|
||
|
||
/* 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)
|
||
->finalize (object);
|
||
}
|
||
|
||
static void
|
||
g_socket_listener_get_property (GObject *object,
|
||
guint prop_id,
|
||
GValue *value,
|
||
GParamSpec *pspec)
|
||
{
|
||
GSocketListener *listener = G_SOCKET_LISTENER (object);
|
||
|
||
switch (prop_id)
|
||
{
|
||
case PROP_LISTEN_BACKLOG:
|
||
g_value_set_int (value, listener->priv->listen_backlog);
|
||
break;
|
||
|
||
default:
|
||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
}
|
||
}
|
||
|
||
static void
|
||
g_socket_listener_set_property (GObject *object,
|
||
guint prop_id,
|
||
const GValue *value,
|
||
GParamSpec *pspec)
|
||
{
|
||
GSocketListener *listener = G_SOCKET_LISTENER (object);
|
||
|
||
switch (prop_id)
|
||
{
|
||
case PROP_LISTEN_BACKLOG:
|
||
g_socket_listener_set_backlog (listener, g_value_get_int (value));
|
||
break;
|
||
|
||
default:
|
||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
}
|
||
}
|
||
|
||
static void
|
||
g_socket_listener_class_init (GSocketListenerClass *klass)
|
||
{
|
||
GObjectClass *gobject_class G_GNUC_UNUSED = G_OBJECT_CLASS (klass);
|
||
|
||
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;
|
||
g_object_class_install_property (gobject_class, PROP_LISTEN_BACKLOG,
|
||
g_param_spec_int ("listen-backlog",
|
||
P_("Listen backlog"),
|
||
P_("outstanding connections in the listen queue"),
|
||
0,
|
||
2000,
|
||
10,
|
||
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||
|
||
/**
|
||
* GSocketListener::event:
|
||
* @listener: the #GSocketListener
|
||
* @event: the event that is occurring
|
||
* @socket: the #GSocket the event is occurring on
|
||
*
|
||
* Emitted when @listener's activity on @socket changes state.
|
||
* Note that when @listener is used to listen on both IPv4 and
|
||
* IPv6, a separate set of signals will be emitted for each, and
|
||
* the order they happen in is undefined.
|
||
*
|
||
* Since: 2.46
|
||
*/
|
||
signals[EVENT] =
|
||
g_signal_new (I_("event"),
|
||
G_TYPE_FROM_CLASS (gobject_class),
|
||
G_SIGNAL_RUN_LAST,
|
||
G_STRUCT_OFFSET (GSocketListenerClass, event),
|
||
NULL, NULL,
|
||
_g_cclosure_marshal_VOID__ENUM_OBJECT,
|
||
G_TYPE_NONE, 2,
|
||
G_TYPE_SOCKET_LISTENER_EVENT,
|
||
G_TYPE_SOCKET);
|
||
g_signal_set_va_marshaller (signals[EVENT],
|
||
G_TYPE_FROM_CLASS (gobject_class),
|
||
_g_cclosure_marshal_VOID__ENUM_OBJECTv);
|
||
|
||
source_quark = g_quark_from_static_string ("g-socket-listener-source");
|
||
}
|
||
|
||
static void
|
||
g_socket_listener_init (GSocketListener *listener)
|
||
{
|
||
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_new:
|
||
*
|
||
* Creates a new #GSocketListener with no sockets to listen for.
|
||
* New listeners can be added with e.g. g_socket_listener_add_address()
|
||
* or g_socket_listener_add_inet_port().
|
||
*
|
||
* Returns: a new #GSocketListener.
|
||
*
|
||
* Since: 2.22
|
||
*/
|
||
GSocketListener *
|
||
g_socket_listener_new (void)
|
||
{
|
||
return g_object_new (G_TYPE_SOCKET_LISTENER, NULL);
|
||
}
|
||
|
||
static gboolean
|
||
check_listener (GSocketListener *listener,
|
||
GError **error)
|
||
{
|
||
if (listener->priv->closed)
|
||
{
|
||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CLOSED,
|
||
_("Listener is already closed"));
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_add_socket:
|
||
* @listener: a #GSocketListener
|
||
* @socket: a listening #GSocket
|
||
* @source_object: (nullable): 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
|
||
* new clients from. The socket must be bound to a local
|
||
* address and listened to.
|
||
*
|
||
* @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.
|
||
*
|
||
* 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)
|
||
{
|
||
if (!check_listener (listener, error))
|
||
return FALSE;
|
||
|
||
/* TODO: Check that socket it is bound & not closed? */
|
||
|
||
if (g_socket_is_closed (socket))
|
||
{
|
||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||
_("Added socket is closed"));
|
||
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;
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_add_address:
|
||
* @listener: a #GSocketListener
|
||
* @address: a #GSocketAddress
|
||
* @type: a #GSocketType
|
||
* @protocol: a #GSocketProtocol
|
||
* @source_object: (nullable): Optional #GObject identifying this source
|
||
* @effective_address: (out) (optional): 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.
|
||
*
|
||
* Call g_socket_listener_close() to stop listening on @address; this will not
|
||
* be done automatically when you drop your final reference to @listener, as
|
||
* references may be held internally.
|
||
*
|
||
* 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,
|
||
GSocketAddress **effective_address,
|
||
GError **error)
|
||
{
|
||
GSocketAddress *local_address;
|
||
GSocketFamily family;
|
||
GSocket *socket;
|
||
|
||
if (!check_listener (listener, error))
|
||
return FALSE;
|
||
|
||
family = g_socket_address_get_family (address);
|
||
socket = g_socket_new (family, type, protocol, error);
|
||
if (socket == NULL)
|
||
return FALSE;
|
||
|
||
g_socket_set_listen_backlog (socket, listener->priv->listen_backlog);
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_BINDING, socket);
|
||
|
||
if (!g_socket_bind (socket, address, TRUE, error))
|
||
{
|
||
g_object_unref (socket);
|
||
return FALSE;
|
||
}
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_BOUND, socket);
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_LISTENING, socket);
|
||
|
||
if (!g_socket_listen (socket, error))
|
||
{
|
||
g_object_unref (socket);
|
||
return FALSE;
|
||
}
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_LISTENED, socket);
|
||
|
||
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 (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 (non-zero)
|
||
* @source_object: (nullable): Optional #GObject identifying this source
|
||
* @error: #GError for error reporting, or %NULL to ignore.
|
||
*
|
||
* Helper function for g_socket_listener_add_address() that
|
||
* creates a TCP/IP socket listening on IPv4 and IPv6 (if
|
||
* supported) on the specified port on all interfaces.
|
||
*
|
||
* @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.
|
||
*
|
||
* Call g_socket_listener_close() to stop listening on @port; this will not
|
||
* be done automatically when you drop your final reference to @listener, as
|
||
* references may be held internally.
|
||
*
|
||
* Returns: %TRUE on success, %FALSE on error.
|
||
*
|
||
* Since: 2.22
|
||
*/
|
||
gboolean
|
||
g_socket_listener_add_inet_port (GSocketListener *listener,
|
||
guint16 port,
|
||
GObject *source_object,
|
||
GError **error)
|
||
{
|
||
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;
|
||
|
||
/* 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;
|
||
|
||
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);
|
||
|
||
g_socket_set_listen_backlog (socket6, listener->priv->listen_backlog);
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_BINDING, socket6);
|
||
|
||
if (!g_socket_bind (socket6, address, TRUE, error))
|
||
{
|
||
g_object_unref (address);
|
||
g_object_unref (socket6);
|
||
return FALSE;
|
||
}
|
||
|
||
g_object_unref (address);
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_BOUND, socket6);
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_LISTENING, socket6);
|
||
|
||
if (!g_socket_listen (socket6, error))
|
||
{
|
||
g_object_unref (socket6);
|
||
return FALSE;
|
||
}
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_LISTENED, socket6);
|
||
|
||
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;
|
||
}
|
||
|
||
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.
|
||
*/
|
||
{
|
||
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;
|
||
|
||
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);
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_BINDING, socket4);
|
||
|
||
if (!g_socket_bind (socket4, address, TRUE, error))
|
||
{
|
||
g_object_unref (address);
|
||
g_object_unref (socket4);
|
||
if (socket6 != NULL)
|
||
g_object_unref (socket6);
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
g_object_unref (address);
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_BOUND, socket4);
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_LISTENING, socket4);
|
||
|
||
if (!g_socket_listen (socket4, error))
|
||
{
|
||
g_object_unref (socket4);
|
||
if (socket6 != NULL)
|
||
g_object_unref (socket6);
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_LISTENED, socket4);
|
||
|
||
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_assert (socket6 != NULL || socket4 != NULL);
|
||
|
||
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 *
|
||
add_sources (GSocketListener *listener,
|
||
GSocketSourceFunc callback,
|
||
gpointer callback_data,
|
||
GCancellable *cancellable,
|
||
GMainContext *context)
|
||
{
|
||
GSocket *socket;
|
||
GSource *source;
|
||
GList *sources;
|
||
guint i;
|
||
|
||
sources = NULL;
|
||
for (i = 0; i < listener->priv->sockets->len; i++)
|
||
{
|
||
socket = listener->priv->sockets->pdata[i];
|
||
|
||
source = g_socket_create_source (socket, G_IO_IN, cancellable);
|
||
g_source_set_callback (source,
|
||
(GSourceFunc) callback,
|
||
callback_data, NULL);
|
||
g_source_attach (source, context);
|
||
|
||
sources = g_list_prepend (sources, source);
|
||
}
|
||
|
||
return sources;
|
||
}
|
||
|
||
static void
|
||
free_sources (GList *sources)
|
||
{
|
||
GSource *source;
|
||
while (sources != NULL)
|
||
{
|
||
source = sources->data;
|
||
sources = g_list_delete_link (sources, sources);
|
||
g_source_destroy (source);
|
||
g_source_unref (source);
|
||
}
|
||
}
|
||
|
||
struct AcceptData {
|
||
GMainLoop *loop;
|
||
GSocket *socket;
|
||
};
|
||
|
||
static gboolean
|
||
accept_callback (GSocket *socket,
|
||
GIOCondition condition,
|
||
gpointer user_data)
|
||
{
|
||
struct AcceptData *data = user_data;
|
||
|
||
data->socket = socket;
|
||
g_main_loop_quit (data->loop);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_accept_socket:
|
||
* @listener: a #GSocketListener
|
||
* @source_object: (out) (transfer none) (optional) (nullable): location where #GObject pointer will be stored, or %NULL.
|
||
* @cancellable: (nullable): 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
|
||
* to the listener. Returns the #GSocket that was accepted.
|
||
*
|
||
* If you want to accept the high-level #GSocketConnection, not a #GSocket,
|
||
* which is often the case, then you should use g_socket_listener_accept()
|
||
* instead.
|
||
*
|
||
* If @source_object is not %NULL it will be filled out with the source
|
||
* 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
|
||
* triggering the cancellable object from another thread. If the operation
|
||
* was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
|
||
*
|
||
* Returns: (transfer full): a #GSocket on success, %NULL on error.
|
||
*
|
||
* Since: 2.22
|
||
*/
|
||
GSocket *
|
||
g_socket_listener_accept_socket (GSocketListener *listener,
|
||
GObject **source_object,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
GSocket *accept_socket, *socket;
|
||
|
||
g_return_val_if_fail (G_IS_SOCKET_LISTENER (listener), NULL);
|
||
|
||
if (!check_listener (listener, error))
|
||
return NULL;
|
||
|
||
if (listener->priv->sockets->len == 1)
|
||
{
|
||
accept_socket = listener->priv->sockets->pdata[0];
|
||
if (!g_socket_condition_wait (accept_socket, G_IO_IN,
|
||
cancellable, error))
|
||
return NULL;
|
||
}
|
||
else
|
||
{
|
||
GList *sources;
|
||
struct AcceptData data;
|
||
GMainLoop *loop;
|
||
|
||
if (listener->priv->main_context == NULL)
|
||
listener->priv->main_context = g_main_context_new ();
|
||
|
||
loop = g_main_loop_new (listener->priv->main_context, FALSE);
|
||
data.loop = loop;
|
||
sources = add_sources (listener,
|
||
accept_callback,
|
||
&data,
|
||
cancellable,
|
||
listener->priv->main_context);
|
||
g_main_loop_run (loop);
|
||
accept_socket = data.socket;
|
||
free_sources (sources);
|
||
g_main_loop_unref (loop);
|
||
}
|
||
|
||
if (!(socket = g_socket_accept (accept_socket, cancellable, error)))
|
||
return NULL;
|
||
|
||
if (source_object)
|
||
*source_object = g_object_get_qdata (G_OBJECT (accept_socket), source_quark);
|
||
|
||
return socket;
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_accept:
|
||
* @listener: a #GSocketListener
|
||
* @source_object: (out) (transfer none) (optional) (nullable): location where #GObject pointer will be stored, or %NULL
|
||
* @cancellable: (nullable): 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
|
||
* to the listener. Returns a #GSocketConnection for the socket that was
|
||
* accepted.
|
||
*
|
||
* If @source_object is not %NULL it will be filled out with the source
|
||
* 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
|
||
* triggering the cancellable object from another thread. If the operation
|
||
* was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
|
||
*
|
||
* Returns: (transfer full): a #GSocketConnection on success, %NULL on error.
|
||
*
|
||
* Since: 2.22
|
||
*/
|
||
GSocketConnection *
|
||
g_socket_listener_accept (GSocketListener *listener,
|
||
GObject **source_object,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
GSocketConnection *connection;
|
||
GSocket *socket;
|
||
|
||
socket = g_socket_listener_accept_socket (listener,
|
||
source_object,
|
||
cancellable,
|
||
error);
|
||
if (socket == NULL)
|
||
return NULL;
|
||
|
||
connection = g_socket_connection_factory_create_connection (socket);
|
||
g_object_unref (socket);
|
||
|
||
return connection;
|
||
}
|
||
|
||
typedef struct
|
||
{
|
||
GList *sources; /* (element-type GSource) */
|
||
gboolean returned_yet;
|
||
} AcceptSocketAsyncData;
|
||
|
||
static void
|
||
accept_socket_async_data_free (AcceptSocketAsyncData *data)
|
||
{
|
||
free_sources (data->sources);
|
||
g_free (data);
|
||
}
|
||
|
||
static gboolean
|
||
accept_ready (GSocket *accept_socket,
|
||
GIOCondition condition,
|
||
gpointer user_data)
|
||
{
|
||
GTask *task = user_data;
|
||
GError *error = NULL;
|
||
GSocket *socket;
|
||
GObject *source_object;
|
||
AcceptSocketAsyncData *data = g_task_get_task_data (task);
|
||
|
||
/* Don’t call g_task_return_*() multiple times if we have multiple incoming
|
||
* connections in the same #GMainContext iteration. */
|
||
if (data->returned_yet)
|
||
return G_SOURCE_REMOVE;
|
||
|
||
socket = g_socket_accept (accept_socket, g_task_get_cancellable (task), &error);
|
||
if (socket)
|
||
{
|
||
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);
|
||
}
|
||
else
|
||
{
|
||
g_task_return_error (task, error);
|
||
}
|
||
|
||
data->returned_yet = TRUE;
|
||
g_object_unref (task);
|
||
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_accept_socket_async:
|
||
* @listener: a #GSocketListener
|
||
* @cancellable: (nullable): 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.
|
||
*
|
||
* Since: 2.22
|
||
*/
|
||
void
|
||
g_socket_listener_accept_socket_async (GSocketListener *listener,
|
||
GCancellable *cancellable,
|
||
GAsyncReadyCallback callback,
|
||
gpointer user_data)
|
||
{
|
||
GTask *task;
|
||
GError *error = NULL;
|
||
AcceptSocketAsyncData *data = NULL;
|
||
|
||
task = g_task_new (listener, cancellable, callback, user_data);
|
||
g_task_set_source_tag (task, g_socket_listener_accept_socket_async);
|
||
|
||
if (!check_listener (listener, &error))
|
||
{
|
||
g_task_return_error (task, error);
|
||
g_object_unref (task);
|
||
return;
|
||
}
|
||
|
||
data = g_new0 (AcceptSocketAsyncData, 1);
|
||
data->returned_yet = FALSE;
|
||
data->sources = add_sources (listener,
|
||
accept_ready,
|
||
task,
|
||
cancellable,
|
||
g_main_context_get_thread_default ());
|
||
g_task_set_task_data (task, g_steal_pointer (&data),
|
||
(GDestroyNotify) accept_socket_async_data_free);
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_accept_socket_finish:
|
||
* @listener: a #GSocketListener
|
||
* @result: a #GAsyncResult.
|
||
* @source_object: (out) (transfer none) (optional) (nullable): 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: (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_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_task_propagate_pointer (G_TASK (result), error);
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_accept_async:
|
||
* @listener: a #GSocketListener
|
||
* @cancellable: (nullable): 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_finish()
|
||
* to get the result of the operation.
|
||
*
|
||
* Since: 2.22
|
||
*/
|
||
void
|
||
g_socket_listener_accept_async (GSocketListener *listener,
|
||
GCancellable *cancellable,
|
||
GAsyncReadyCallback callback,
|
||
gpointer user_data)
|
||
{
|
||
g_socket_listener_accept_socket_async (listener,
|
||
cancellable,
|
||
callback,
|
||
user_data);
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_accept_finish:
|
||
* @listener: a #GSocketListener
|
||
* @result: a #GAsyncResult.
|
||
* @source_object: (out) (transfer none) (optional) (nullable): 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: (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)
|
||
{
|
||
GSocket *socket;
|
||
GSocketConnection *connection;
|
||
|
||
socket = g_socket_listener_accept_socket_finish (listener,
|
||
result,
|
||
source_object,
|
||
error);
|
||
if (socket == NULL)
|
||
return NULL;
|
||
|
||
connection = g_socket_connection_factory_create_connection (socket);
|
||
g_object_unref (socket);
|
||
return connection;
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_set_backlog:
|
||
* @listener: a #GSocketListener
|
||
* @listen_backlog: an integer
|
||
*
|
||
* Sets the listen backlog on the sockets in the listener. This must be called
|
||
* before adding any sockets, addresses or ports to the #GSocketListener (for
|
||
* example, by calling g_socket_listener_add_inet_port()) to be effective.
|
||
*
|
||
* See g_socket_set_listen_backlog() for details
|
||
*
|
||
* Since: 2.22
|
||
*/
|
||
void
|
||
g_socket_listener_set_backlog (GSocketListener *listener,
|
||
int listen_backlog)
|
||
{
|
||
GSocket *socket;
|
||
guint i;
|
||
|
||
if (listener->priv->closed)
|
||
return;
|
||
|
||
listener->priv->listen_backlog = listen_backlog;
|
||
|
||
for (i = 0; i < listener->priv->sockets->len; i++)
|
||
{
|
||
socket = listener->priv->sockets->pdata[i];
|
||
g_socket_set_listen_backlog (socket, listen_backlog);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_close:
|
||
* @listener: a #GSocketListener
|
||
*
|
||
* Closes all the sockets in the listener.
|
||
*
|
||
* Since: 2.22
|
||
*/
|
||
void
|
||
g_socket_listener_close (GSocketListener *listener)
|
||
{
|
||
GSocket *socket;
|
||
guint i;
|
||
|
||
g_return_if_fail (G_IS_SOCKET_LISTENER (listener));
|
||
|
||
if (listener->priv->closed)
|
||
return;
|
||
|
||
for (i = 0; i < listener->priv->sockets->len; i++)
|
||
{
|
||
socket = listener->priv->sockets->pdata[i];
|
||
g_socket_close (socket, NULL);
|
||
}
|
||
listener->priv->closed = TRUE;
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_add_any_inet_port:
|
||
* @listener: a #GSocketListener
|
||
* @source_object: (nullable): 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);
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_BINDING, socket6);
|
||
|
||
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_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_BOUND, socket6);
|
||
|
||
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);
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_BINDING, socket4);
|
||
|
||
/* 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 */
|
||
{
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_BOUND, socket4);
|
||
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_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_BOUND, socket4);
|
||
|
||
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);
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_LISTENING, socket6);
|
||
|
||
if (!g_socket_listen (socket6, error))
|
||
{
|
||
g_object_unref (socket6);
|
||
if (socket4)
|
||
g_object_unref (socket4);
|
||
|
||
return 0;
|
||
}
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_LISTENED, socket6);
|
||
|
||
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);
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_LISTENING, socket4);
|
||
|
||
if (!g_socket_listen (socket4, error))
|
||
{
|
||
g_object_unref (socket4);
|
||
if (socket6)
|
||
g_object_unref (socket6);
|
||
|
||
return 0;
|
||
}
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_LISTENED, socket4);
|
||
|
||
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;
|
||
}
|