glib2.0/gio/gsocketconnection.c

690 lines
20 KiB
C

/* GIO - GLib Input, Output and Streaming Library
*
* Copyright © 2008 Christian Kellner, Samuel Cormier-Iijima
* © 2008 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 "gsocketconnection.h"
#include "gsocketoutputstream.h"
#include "gsocketinputstream.h"
#include "gioprivate.h"
#include <gio/giostream.h>
#include <gio/gtask.h>
#include "gunixconnection.h"
#include "gtcpconnection.h"
#include "glibintl.h"
/**
* SECTION:gsocketconnection
* @short_description: A socket connection
* @include: gio/gio.h
* @see_also: #GIOStream, #GSocketClient, #GSocketListener
*
* #GSocketConnection is a #GIOStream for a connected socket. They
* can be created either by #GSocketClient when connecting to a host,
* or by #GSocketListener when accepting a new client.
*
* The type of the #GSocketConnection object returned from these calls
* depends on the type of the underlying socket that is in use. For
* instance, for a TCP/IP connection it will be a #GTcpConnection.
*
* Choosing what type of object to construct is done with the socket
* connection factory, and it is possible for 3rd parties to register
* custom socket connection types for specific combination of socket
* family/type/protocol using g_socket_connection_factory_register_type().
*
* To close a #GSocketConnection, use g_io_stream_close(). Closing both
* substreams of the #GIOStream separately will not close the underlying
* #GSocket.
*
* Since: 2.22
*/
enum
{
PROP_NONE,
PROP_SOCKET,
};
struct _GSocketConnectionPrivate
{
GSocket *socket;
GInputStream *input_stream;
GOutputStream *output_stream;
GSocketAddress *cached_remote_address;
gboolean in_dispose;
};
static gboolean g_socket_connection_close (GIOStream *stream,
GCancellable *cancellable,
GError **error);
static void g_socket_connection_close_async (GIOStream *stream,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
static gboolean g_socket_connection_close_finish (GIOStream *stream,
GAsyncResult *result,
GError **error);
G_DEFINE_TYPE_WITH_PRIVATE (GSocketConnection, g_socket_connection, G_TYPE_IO_STREAM)
static GInputStream *
g_socket_connection_get_input_stream (GIOStream *io_stream)
{
GSocketConnection *connection = G_SOCKET_CONNECTION (io_stream);
if (connection->priv->input_stream == NULL)
connection->priv->input_stream = (GInputStream *)
_g_socket_input_stream_new (connection->priv->socket);
return connection->priv->input_stream;
}
static GOutputStream *
g_socket_connection_get_output_stream (GIOStream *io_stream)
{
GSocketConnection *connection = G_SOCKET_CONNECTION (io_stream);
if (connection->priv->output_stream == NULL)
connection->priv->output_stream = (GOutputStream *)
_g_socket_output_stream_new (connection->priv->socket);
return connection->priv->output_stream;
}
/**
* g_socket_connection_is_connected:
* @connection: a #GSocketConnection
*
* Checks if @connection is connected. This is equivalent to calling
* g_socket_is_connected() on @connection's underlying #GSocket.
*
* Returns: whether @connection is connected
*
* Since: 2.32
*/
gboolean
g_socket_connection_is_connected (GSocketConnection *connection)
{
return g_socket_is_connected (connection->priv->socket);
}
/**
* g_socket_connection_connect:
* @connection: a #GSocketConnection
* @address: a #GSocketAddress specifying the remote address.
* @cancellable: (nullable): a %GCancellable or %NULL
* @error: #GError for error reporting, or %NULL to ignore.
*
* Connect @connection to the specified remote address.
*
* Returns: %TRUE if the connection succeeded, %FALSE on error
*
* Since: 2.32
*/
gboolean
g_socket_connection_connect (GSocketConnection *connection,
GSocketAddress *address,
GCancellable *cancellable,
GError **error)
{
g_return_val_if_fail (G_IS_SOCKET_CONNECTION (connection), FALSE);
g_return_val_if_fail (G_IS_SOCKET_ADDRESS (address), FALSE);
return g_socket_connect (connection->priv->socket, address,
cancellable, error);
}
static gboolean g_socket_connection_connect_callback (GSocket *socket,
GIOCondition condition,
gpointer user_data);
/**
* g_socket_connection_connect_async:
* @connection: a #GSocketConnection
* @address: a #GSocketAddress specifying the remote address.
* @cancellable: (nullable): a %GCancellable or %NULL
* @callback: (scope async): a #GAsyncReadyCallback
* @user_data: (closure): user data for the callback
*
* Asynchronously connect @connection to the specified remote address.
*
* This clears the #GSocket:blocking flag on @connection's underlying
* socket if it is currently set.
*
* Use g_socket_connection_connect_finish() to retrieve the result.
*
* Since: 2.32
*/
void
g_socket_connection_connect_async (GSocketConnection *connection,
GSocketAddress *address,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
GError *tmp_error = NULL;
g_return_if_fail (G_IS_SOCKET_CONNECTION (connection));
g_return_if_fail (G_IS_SOCKET_ADDRESS (address));
task = g_task_new (connection, cancellable, callback, user_data);
g_task_set_source_tag (task, g_socket_connection_connect_async);
g_socket_set_blocking (connection->priv->socket, FALSE);
if (g_socket_connect (connection->priv->socket, address,
cancellable, &tmp_error))
{
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
else if (g_error_matches (tmp_error, G_IO_ERROR, G_IO_ERROR_PENDING))
{
GSource *source;
g_error_free (tmp_error);
source = g_socket_create_source (connection->priv->socket,
G_IO_OUT, cancellable);
g_task_attach_source (task, source,
(GSourceFunc) g_socket_connection_connect_callback);
g_source_unref (source);
}
else
{
g_task_return_error (task, tmp_error);
g_object_unref (task);
}
}
static gboolean
g_socket_connection_connect_callback (GSocket *socket,
GIOCondition condition,
gpointer user_data)
{
GTask *task = user_data;
GSocketConnection *connection = g_task_get_source_object (task);
GError *error = NULL;
if (g_socket_check_connect_result (connection->priv->socket, &error))
g_task_return_boolean (task, TRUE);
else
g_task_return_error (task, error);
g_object_unref (task);
return FALSE;
}
/**
* g_socket_connection_connect_finish:
* @connection: a #GSocketConnection
* @result: the #GAsyncResult
* @error: #GError for error reporting, or %NULL to ignore.
*
* Gets the result of a g_socket_connection_connect_async() call.
*
* Returns: %TRUE if the connection succeeded, %FALSE on error
*
* Since: 2.32
*/
gboolean
g_socket_connection_connect_finish (GSocketConnection *connection,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (G_IS_SOCKET_CONNECTION (connection), FALSE);
g_return_val_if_fail (g_task_is_valid (result, connection), FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
/**
* g_socket_connection_get_socket:
* @connection: a #GSocketConnection
*
* Gets the underlying #GSocket object of the connection.
* This can be useful if you want to do something unusual on it
* not supported by the #GSocketConnection APIs.
*
* Returns: (transfer none): a #GSocket or %NULL on error.
*
* Since: 2.22
*/
GSocket *
g_socket_connection_get_socket (GSocketConnection *connection)
{
g_return_val_if_fail (G_IS_SOCKET_CONNECTION (connection), NULL);
return connection->priv->socket;
}
/**
* g_socket_connection_get_local_address:
* @connection: a #GSocketConnection
* @error: #GError for error reporting, or %NULL to ignore.
*
* Try to get the local address of a socket connection.
*
* Returns: (transfer full): a #GSocketAddress or %NULL on error.
* Free the returned object with g_object_unref().
*
* Since: 2.22
*/
GSocketAddress *
g_socket_connection_get_local_address (GSocketConnection *connection,
GError **error)
{
return g_socket_get_local_address (connection->priv->socket, error);
}
/**
* g_socket_connection_get_remote_address:
* @connection: a #GSocketConnection
* @error: #GError for error reporting, or %NULL to ignore.
*
* Try to get the remote address of a socket connection.
*
* Since GLib 2.40, when used with g_socket_client_connect() or
* g_socket_client_connect_async(), during emission of
* %G_SOCKET_CLIENT_CONNECTING, this function will return the remote
* address that will be used for the connection. This allows
* applications to print e.g. "Connecting to example.com
* (10.42.77.3)...".
*
* Returns: (transfer full): a #GSocketAddress or %NULL on error.
* Free the returned object with g_object_unref().
*
* Since: 2.22
*/
GSocketAddress *
g_socket_connection_get_remote_address (GSocketConnection *connection,
GError **error)
{
if (!g_socket_is_connected (connection->priv->socket))
{
return connection->priv->cached_remote_address ?
g_object_ref (connection->priv->cached_remote_address) : NULL;
}
return g_socket_get_remote_address (connection->priv->socket, error);
}
/* Private API allowing applications to retrieve the resolved address
* now, before we start connecting.
*
* https://bugzilla.gnome.org/show_bug.cgi?id=712547
*/
void
g_socket_connection_set_cached_remote_address (GSocketConnection *connection,
GSocketAddress *address)
{
g_clear_object (&connection->priv->cached_remote_address);
connection->priv->cached_remote_address = address ? g_object_ref (address) : NULL;
}
static void
g_socket_connection_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GSocketConnection *connection = G_SOCKET_CONNECTION (object);
switch (prop_id)
{
case PROP_SOCKET:
g_value_set_object (value, connection->priv->socket);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
g_socket_connection_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GSocketConnection *connection = G_SOCKET_CONNECTION (object);
switch (prop_id)
{
case PROP_SOCKET:
connection->priv->socket = G_SOCKET (g_value_dup_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
g_socket_connection_constructed (GObject *object)
{
#ifndef G_DISABLE_ASSERT
GSocketConnection *connection = G_SOCKET_CONNECTION (object);
#endif
g_assert (connection->priv->socket != NULL);
}
static void
g_socket_connection_dispose (GObject *object)
{
GSocketConnection *connection = G_SOCKET_CONNECTION (object);
connection->priv->in_dispose = TRUE;
g_clear_object (&connection->priv->cached_remote_address);
G_OBJECT_CLASS (g_socket_connection_parent_class)
->dispose (object);
connection->priv->in_dispose = FALSE;
}
static void
g_socket_connection_finalize (GObject *object)
{
GSocketConnection *connection = G_SOCKET_CONNECTION (object);
if (connection->priv->input_stream)
g_object_unref (connection->priv->input_stream);
if (connection->priv->output_stream)
g_object_unref (connection->priv->output_stream);
g_object_unref (connection->priv->socket);
G_OBJECT_CLASS (g_socket_connection_parent_class)
->finalize (object);
}
static void
g_socket_connection_class_init (GSocketConnectionClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GIOStreamClass *stream_class = G_IO_STREAM_CLASS (klass);
gobject_class->set_property = g_socket_connection_set_property;
gobject_class->get_property = g_socket_connection_get_property;
gobject_class->constructed = g_socket_connection_constructed;
gobject_class->finalize = g_socket_connection_finalize;
gobject_class->dispose = g_socket_connection_dispose;
stream_class->get_input_stream = g_socket_connection_get_input_stream;
stream_class->get_output_stream = g_socket_connection_get_output_stream;
stream_class->close_fn = g_socket_connection_close;
stream_class->close_async = g_socket_connection_close_async;
stream_class->close_finish = g_socket_connection_close_finish;
g_object_class_install_property (gobject_class,
PROP_SOCKET,
g_param_spec_object ("socket",
P_("Socket"),
P_("The underlying GSocket"),
G_TYPE_SOCKET,
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
}
static void
g_socket_connection_init (GSocketConnection *connection)
{
connection->priv = g_socket_connection_get_instance_private (connection);
}
static gboolean
g_socket_connection_close (GIOStream *stream,
GCancellable *cancellable,
GError **error)
{
GSocketConnection *connection = G_SOCKET_CONNECTION (stream);
if (connection->priv->output_stream)
g_output_stream_close (connection->priv->output_stream,
cancellable, NULL);
if (connection->priv->input_stream)
g_input_stream_close (connection->priv->input_stream,
cancellable, NULL);
/* Don't close the underlying socket if this is being called
* as part of dispose(); when destroying the GSocketConnection,
* we only want to close the socket if we're holding the last
* reference on it, and in that case it will close itself when
* we unref it in finalize().
*/
if (connection->priv->in_dispose)
return TRUE;
return g_socket_close (connection->priv->socket, error);
}
static void
g_socket_connection_close_async (GIOStream *stream,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
GIOStreamClass *class;
GError *error;
class = G_IO_STREAM_GET_CLASS (stream);
task = g_task_new (stream, cancellable, callback, user_data);
g_task_set_source_tag (task, g_socket_connection_close_async);
/* socket close is not blocked, just do it! */
error = NULL;
if (class->close_fn &&
!class->close_fn (stream, cancellable, &error))
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static gboolean
g_socket_connection_close_finish (GIOStream *stream,
GAsyncResult *result,
GError **error)
{
return g_task_propagate_boolean (G_TASK (result), error);
}
typedef struct {
GSocketFamily socket_family;
GSocketType socket_type;
int protocol;
GType implementation;
} ConnectionFactory;
static guint
connection_factory_hash (gconstpointer key)
{
const ConnectionFactory *factory = key;
guint h;
h = factory->socket_family ^ (factory->socket_type << 4) ^ (factory->protocol << 8);
/* This is likely to be small, so spread over whole
hash space to get some distribution */
h = h ^ (h << 8) ^ (h << 16) ^ (h << 24);
return h;
}
static gboolean
connection_factory_equal (gconstpointer _a,
gconstpointer _b)
{
const ConnectionFactory *a = _a;
const ConnectionFactory *b = _b;
if (a->socket_family != b->socket_family)
return FALSE;
if (a->socket_type != b->socket_type)
return FALSE;
if (a->protocol != b->protocol)
return FALSE;
return TRUE;
}
static GHashTable *connection_factories = NULL;
G_LOCK_DEFINE_STATIC(connection_factories);
/**
* g_socket_connection_factory_register_type:
* @g_type: a #GType, inheriting from %G_TYPE_SOCKET_CONNECTION
* @family: a #GSocketFamily
* @type: a #GSocketType
* @protocol: a protocol id
*
* Looks up the #GType to be used when creating socket connections on
* sockets with the specified @family, @type and @protocol.
*
* If no type is registered, the #GSocketConnection base type is returned.
*
* Since: 2.22
*/
void
g_socket_connection_factory_register_type (GType g_type,
GSocketFamily family,
GSocketType type,
gint protocol)
{
ConnectionFactory *factory;
g_return_if_fail (g_type_is_a (g_type, G_TYPE_SOCKET_CONNECTION));
G_LOCK (connection_factories);
if (connection_factories == NULL)
connection_factories = g_hash_table_new_full (connection_factory_hash,
connection_factory_equal,
(GDestroyNotify)g_free,
NULL);
factory = g_new0 (ConnectionFactory, 1);
factory->socket_family = family;
factory->socket_type = type;
factory->protocol = protocol;
factory->implementation = g_type;
g_hash_table_insert (connection_factories,
factory, factory);
G_UNLOCK (connection_factories);
}
static void
init_builtin_types (void)
{
g_type_ensure (G_TYPE_UNIX_CONNECTION);
g_type_ensure (G_TYPE_TCP_CONNECTION);
}
/**
* g_socket_connection_factory_lookup_type:
* @family: a #GSocketFamily
* @type: a #GSocketType
* @protocol_id: a protocol id
*
* Looks up the #GType to be used when creating socket connections on
* sockets with the specified @family, @type and @protocol_id.
*
* If no type is registered, the #GSocketConnection base type is returned.
*
* Returns: a #GType
*
* Since: 2.22
*/
GType
g_socket_connection_factory_lookup_type (GSocketFamily family,
GSocketType type,
gint protocol_id)
{
ConnectionFactory *factory, key;
GType g_type;
init_builtin_types ();
G_LOCK (connection_factories);
g_type = G_TYPE_SOCKET_CONNECTION;
if (connection_factories)
{
key.socket_family = family;
key.socket_type = type;
key.protocol = protocol_id;
factory = g_hash_table_lookup (connection_factories, &key);
if (factory)
g_type = factory->implementation;
}
G_UNLOCK (connection_factories);
return g_type;
}
/**
* g_socket_connection_factory_create_connection:
* @socket: a #GSocket
*
* Creates a #GSocketConnection subclass of the right type for
* @socket.
*
* Returns: (transfer full): a #GSocketConnection
*
* Since: 2.22
*/
GSocketConnection *
g_socket_connection_factory_create_connection (GSocket *socket)
{
GType type;
type = g_socket_connection_factory_lookup_type (g_socket_get_family (socket),
g_socket_get_socket_type (socket),
g_socket_get_protocol (socket));
return g_object_new (type, "socket", socket, NULL);
}