glib/gio/gwin32namedpipelistener.c
Ignacio Casal Quinteiro 6f49770e56 Add named pipe high level api
It provides a GSocketListener/Client like api using named pipes

https://bugzilla.gnome.org/show_bug.cgi?id=745410
2016-02-25 09:15:00 +01:00

567 lines
17 KiB
C

/* GIO - GLib Input, Output and Streaming Library
*
* Copyright (C) 2011 Red Hat, Inc.
* Copyright (C) 2016 NICE s.r.l.
*
* 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/>.
*/
#include "config.h"
#include "gwin32namedpipelistener.h"
#include "gwin32namedpipeconnection.h"
#include "gasynchelper.h"
#include "glibintl.h"
#include <windows.h>
#define DEFAULT_PIPE_BUF_SIZE 4096
typedef struct
{
gchar *pipe_name;
HANDLE handle;
OVERLAPPED overlapped;
GObject *source_object;
gboolean already_connected;
} PipeData;
typedef struct
{
GPtrArray *named_pipes;
GMainContext *main_context;
} GWin32NamedPipeListenerPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (GWin32NamedPipeListener, g_win32_named_pipe_listener, G_TYPE_OBJECT)
static GQuark source_quark = 0;
static PipeData *
pipe_data_new (const gchar *pipe_name,
HANDLE handle,
GObject *source_object)
{
PipeData *data;
data = g_slice_new0 (PipeData);
data->pipe_name = g_strdup (pipe_name);
data->handle = handle;
data->overlapped.hEvent = CreateEvent (NULL, /* default security attribute */
TRUE, /* manual-reset event */
TRUE, /* initial state = signaled */
NULL); /* unnamed event object */
if (source_object)
data->source_object = g_object_ref (source_object);
return data;
}
static void
pipe_data_free (PipeData *data)
{
g_free (data->pipe_name);
CloseHandle (data->handle);
CloseHandle (data->overlapped.hEvent);
g_clear_object (&data->source_object);
g_slice_free (PipeData, data);
}
static void
g_win32_named_pipe_listener_finalize (GObject *object)
{
GWin32NamedPipeListener *listener = G_WIN32_NAMED_PIPE_LISTENER (object);
GWin32NamedPipeListenerPrivate *priv;
priv = g_win32_named_pipe_listener_get_instance_private (listener);
if (priv->main_context)
g_main_context_unref (priv->main_context);
g_ptr_array_free (priv->named_pipes, TRUE);
G_OBJECT_CLASS (g_win32_named_pipe_listener_parent_class)->finalize (object);
}
static void
g_win32_named_pipe_listener_class_init (GWin32NamedPipeListenerClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = g_win32_named_pipe_listener_finalize;
source_quark = g_quark_from_static_string ("g-win32-named-pipe-listener-source");
}
static void
g_win32_named_pipe_listener_init (GWin32NamedPipeListener *listener)
{
GWin32NamedPipeListenerPrivate *priv;
priv = g_win32_named_pipe_listener_get_instance_private (listener);
priv->named_pipes = g_ptr_array_new_with_free_func ((GDestroyNotify) pipe_data_free);
}
/**
* g_win32_named_pipe_listener_new:
*
* Creates a new #GWin32NamedPipeListener.
*
* Returns: (transfer full): a new #GWin32NamedPipeListener.
*
* Since: 2.48
*/
GWin32NamedPipeListener *
g_win32_named_pipe_listener_new (void)
{
return g_object_new (G_TYPE_WIN32_NAMED_PIPE_LISTENER, NULL);
}
/**
* g_win32_named_pipe_listener_add_named_pipe:
* @listener: a #GWin32NamedPipeListener.
* @pipe_name: a name for the pipe.
* @source_object: (allow-none): Optional #GObject identifying this source
* @error: #GError for error reporting, or %NULL to ignore.
*
* Adds @named_pipe to the set of named pipes that we try to accept clients
* from.
*
* @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 pipes and do
* different things depending on what pipe is connected to.
*
* Returns: %TRUE on success, %FALSE on error.
*
* Since: 2.48
*/
gboolean
g_win32_named_pipe_listener_add_named_pipe (GWin32NamedPipeListener *listener,
const gchar *pipe_name,
GObject *source_object,
GError **error)
{
GWin32NamedPipeListenerPrivate *priv;
gunichar2 *pipe_namew;
PipeData *pipe_data;
HANDLE handle;
g_return_val_if_fail (G_IS_WIN32_NAMED_PIPE_LISTENER (listener), FALSE);
g_return_val_if_fail (pipe_name != NULL, FALSE);
priv = g_win32_named_pipe_listener_get_instance_private (listener);
pipe_namew = g_utf8_to_utf16 (pipe_name, -1, NULL, NULL, NULL);
handle = CreateNamedPipeW (pipe_namew,
PIPE_ACCESS_DUPLEX |
FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE |
PIPE_READMODE_BYTE |
PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
DEFAULT_PIPE_BUF_SIZE,
DEFAULT_PIPE_BUF_SIZE,
0, NULL);
g_free (pipe_namew);
if (handle == INVALID_HANDLE_VALUE)
{
int errsv = GetLastError ();
gchar *emsg = g_win32_error_message (errsv);
g_set_error (error,
G_IO_ERROR,
g_io_error_from_win32_error (errsv),
_("Error creating named pipe '%s': %s"),
pipe_name, emsg);
g_free (emsg);
return FALSE;
}
pipe_data = pipe_data_new (pipe_name, handle, source_object);
if (!ConnectNamedPipe (handle, &pipe_data->overlapped))
{
switch (GetLastError ())
{
case ERROR_IO_PENDING:
break;
case ERROR_PIPE_CONNECTED:
pipe_data->already_connected = TRUE;
break;
default:
{
int errsv = GetLastError ();
gchar *emsg = g_win32_error_message (errsv);
g_set_error (error,
G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
_("Failed to connect named pipe '%s': %s"),
pipe_name, emsg);
g_free (emsg);
pipe_data_free (pipe_data);
return FALSE;
}
}
}
g_ptr_array_add (priv->named_pipes, pipe_data);
return TRUE;
}
static gboolean
connect_ready (HANDLE handle,
gpointer user_data)
{
GTask *task = user_data;
GWin32NamedPipeListener *listener = g_task_get_source_object (task);
GWin32NamedPipeListenerPrivate *priv;
PipeData *pipe_data = NULL;
gulong cbret;
int i;
priv = g_win32_named_pipe_listener_get_instance_private (listener);
for (i = 0; i < priv->named_pipes->len; i++)
{
PipeData *pdata;
pdata = priv->named_pipes->pdata[i];
if (pdata->overlapped.hEvent == handle)
{
pipe_data = pdata;
break;
}
}
g_return_val_if_fail (pipe_data != NULL, FALSE);
if (!GetOverlappedResult (pipe_data->handle, &pipe_data->overlapped, &cbret, FALSE))
{
int errsv = GetLastError ();
gchar *emsg = g_win32_error_message (errsv);
g_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_INVALID_ARGUMENT,
_("There was an error querying the named pipe: %s"),
emsg);
g_free (emsg);
}
else
{
GWin32NamedPipeConnection *connection;
if (pipe_data->source_object != NULL)
g_object_set_qdata_full (G_OBJECT (task),
source_quark,
g_object_ref (pipe_data->source_object),
g_object_unref);
connection = g_object_new (G_TYPE_WIN32_NAMED_PIPE_CONNECTION,
"handle", pipe_data->handle,
"close-handle", FALSE,
NULL);
g_task_return_pointer (task, connection, g_object_unref);
}
g_object_unref (task);
return FALSE;
}
static GList *
add_sources (GWin32NamedPipeListener *listener,
GWin32HandleSourceFunc callback,
gpointer callback_data,
GCancellable *cancellable,
GMainContext *context)
{
GWin32NamedPipeListenerPrivate *priv;
PipeData *data;
GSource *source;
GList *sources;
int i;
priv = g_win32_named_pipe_listener_get_instance_private (listener);
sources = NULL;
for (i = 0; i < priv->named_pipes->len; i++)
{
data = priv->named_pipes->pdata[i];
source = _g_win32_handle_create_source (data->overlapped.hEvent,
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 {
GWin32NamedPipeListener *listener;
GMainLoop *loop;
PipeData *pipe_data;
};
static gboolean
accept_callback (HANDLE handle,
gpointer user_data)
{
struct AcceptData *data = user_data;
GWin32NamedPipeListenerPrivate *priv;
PipeData *pipe_data = NULL;
int i;
priv = g_win32_named_pipe_listener_get_instance_private (data->listener);
for (i = 0; i < priv->named_pipes->len; i++)
{
PipeData *pdata;
pdata = priv->named_pipes->pdata[i];
if (pdata->overlapped.hEvent == handle)
{
pipe_data = pdata;
break;
}
}
data->pipe_data = pipe_data;
g_main_loop_quit (data->loop);
return TRUE;
}
/**
* g_win32_named_pipe_listener_accept:
* @listener: a #GWin32NamedPipeListener
* @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 named pipes added
* to the listener. Returns the #GWin32NamedPipeConnection that was accepted.
*
* If @source_object is not %NULL it will be filled out with the source
* object specified when the corresponding named pipe 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 #GWin32NamedPipeConnection on success, %NULL on error.
*
* Since: 2.48
*/
GWin32NamedPipeConnection *
g_win32_named_pipe_listener_accept (GWin32NamedPipeListener *listener,
GObject **source_object,
GCancellable *cancellable,
GError **error)
{
GWin32NamedPipeListenerPrivate *priv;
PipeData *pipe_data = NULL;
GWin32NamedPipeConnection *connection = NULL;
g_return_val_if_fail (G_IS_WIN32_NAMED_PIPE_LISTENER (listener), NULL);
priv = g_win32_named_pipe_listener_get_instance_private (listener);
if (priv->named_pipes->len == 1)
{
gboolean success;
pipe_data = priv->named_pipes->pdata[0];
success = pipe_data->already_connected;
if (!success)
success = WaitForSingleObject (pipe_data->overlapped.hEvent, INFINITE) == WAIT_OBJECT_0;
if (!success)
pipe_data = NULL;
}
else
{
int i;
/* First we check if any of the named pipes is already connected and
* pick the the first one.
*/
for (i = 0; i < priv->named_pipes->len; i++)
{
PipeData *pdata = priv->named_pipes->pdata[i];
if (pdata->already_connected)
pipe_data = pdata;
}
if (pipe_data == NULL)
{
GList *sources;
struct AcceptData data;
GMainLoop *loop;
if (priv->main_context == NULL)
priv->main_context = g_main_context_new ();
loop = g_main_loop_new (priv->main_context, FALSE);
data.loop = loop;
data.listener = listener;
sources = add_sources (listener,
accept_callback,
&data,
cancellable,
priv->main_context);
g_main_loop_run (loop);
pipe_data = data.pipe_data;
free_sources (sources);
g_main_loop_unref (loop);
}
}
if (pipe_data != NULL)
{
connection = g_object_new (G_TYPE_WIN32_NAMED_PIPE_CONNECTION,
"handle", pipe_data->handle,
"close-handle", FALSE,
NULL);
if (source_object)
*source_object = pipe_data->source_object;
}
return connection;
}
/**
* g_win32_named_pipe_listener_accept_async:
* @listener: a #GWin32NamedPipeListener
* @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_win32_named_pipe_listener_accept().
*
* When the operation is finished @callback will be
* called. You can then call g_win32_named_pipe_listener_accept_finish()
* to get the result of the operation.
*
* Since: 2.48
*/
void
g_win32_named_pipe_listener_accept_async (GWin32NamedPipeListener *listener,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GWin32NamedPipeListenerPrivate *priv;
PipeData *pipe_data;
GTask *task;
GList *sources;
int i;
task = g_task_new (listener, cancellable, callback, user_data);
priv = g_win32_named_pipe_listener_get_instance_private (listener);
/* First we check if any of the named pipes is already connected and pick the
* the first one.
*/
for (i = 0; i < priv->named_pipes->len; i++)
{
pipe_data = priv->named_pipes->pdata[i];
if (pipe_data->already_connected)
{
GWin32NamedPipeConnection *connection;
if (pipe_data->source_object)
g_object_set_qdata_full (G_OBJECT (task),
source_quark,
g_object_ref (pipe_data->source_object),
g_object_unref);
connection = g_object_new (G_TYPE_WIN32_NAMED_PIPE_CONNECTION,
"handle", pipe_data->handle,
"close-handle", FALSE,
NULL);
g_task_return_pointer (task, connection, g_object_unref);
return;
}
}
sources = add_sources (listener,
connect_ready,
task,
cancellable,
g_main_context_get_thread_default ());
g_task_set_task_data (task, sources, (GDestroyNotify) free_sources);
}
/**
* g_win32_named_pipe_listener_accept_finish:
* @listener: a #GWin32NamedPipeListener.
* @result: a #GAsyncResult.
* @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_win32_named_pipe_listener_accept_async()
*
* Returns: (transfer full): a #GWin32NamedPipeConnection on success, %NULL on error.
*
* Since: 2.48
*/
GWin32NamedPipeConnection *
g_win32_named_pipe_listener_accept_finish (GWin32NamedPipeListener *listener,
GAsyncResult *result,
GObject **source_object,
GError **error)
{
g_return_val_if_fail (G_IS_WIN32_NAMED_PIPE_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);
}