medit/moo/mooapp/mooappinput.c

766 lines
18 KiB
C

/*
* mooapp/mooappinput.c
*
* Copyright (C) 2004-2007 by Yevgen Muntyan <muntyan@math.tamu.edu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* See COPYING file that comes with this distribution.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef __WIN32__
#include <windows.h>
#include <io.h>
#include <fcntl.h>
#else /* !__WIN32__ */
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/poll.h>
#include <signal.h>
#endif /* !__WIN32__ */
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "mooapp/mooappinput.h"
#define MOO_APP_COMPILATION
#include "mooapp/mooapp-private.h"
#include "mooutils/mooutils-misc.h"
#include "mooutils/mooutils-thread.h"
struct _MooAppInput
{
guint ref_count;
int pipe;
char *pipe_basename;
char *pipe_name;
char *pipe_dir;
GIOChannel *io;
guint io_watch;
GString *buffer; /* messages are zero-terminated */
gboolean ready;
#ifdef __WIN32__
guint event_id;
#endif /* __WIN32__ */
};
#define MAX_BUFFER_SIZE 4096
MooAppInput *
_moo_app_input_new (const char *pipe_basename)
{
MooAppInput *ch;
g_return_val_if_fail (pipe_basename != NULL, NULL);
ch = g_new0 (MooAppInput, 1);
ch->ref_count = 1;
ch->pipe_basename = g_strdup (pipe_basename);
#ifndef __WIN32__
ch->pipe = -1;
#endif /* ! __WIN32__ */
ch->pipe_name = NULL;
ch->io = NULL;
ch->io_watch = 0;
ch->ready = FALSE;
ch->buffer = g_string_new_len (NULL, MAX_BUFFER_SIZE);
return ch;
}
static MooAppInput *
_moo_app_input_ref (MooAppInput *ch)
{
g_return_val_if_fail (ch != NULL, NULL);
++ch->ref_count;
return ch;
}
void
_moo_app_input_unref (MooAppInput *ch)
{
g_return_if_fail (ch != NULL);
if (--ch->ref_count)
return;
_moo_app_input_shutdown (ch);
g_string_free (ch->buffer, TRUE);
g_free (ch->pipe_basename);
g_free (ch);
}
void
_moo_app_input_shutdown (MooAppInput *ch)
{
g_return_if_fail (ch != NULL);
#ifdef __WIN32__
if (ch->event_id)
{
_moo_event_queue_disconnect (ch->event_id);
ch->event_id = 0;
}
#endif /* __WIN32__ */
if (ch->io)
{
g_io_channel_shutdown (ch->io, TRUE, NULL);
g_io_channel_unref (ch->io);
ch->io = NULL;
}
if (ch->pipe_name)
{
#ifndef __WIN32__
ch->pipe = -1;
unlink (ch->pipe_name);
#endif /* ! __WIN32__ */
g_free (ch->pipe_name);
ch->pipe_name = NULL;
}
if (ch->pipe_dir)
{
remove (ch->pipe_dir);
g_free (ch->pipe_dir);
ch->pipe_dir = NULL;
}
if (ch->io_watch)
{
g_source_remove (ch->io_watch);
ch->io_watch = 0;
}
ch->ready = FALSE;
}
const char *
_moo_app_input_get_name (MooAppInput *ch)
{
g_return_val_if_fail (ch != NULL, NULL);
return ch->pipe_name;
}
static void
commit (MooAppInput *self)
{
char buf[MAX_BUFFER_SIZE];
GString *freeme = NULL;
char *ptr;
gsize len;
if (!self->buffer->len)
{
g_warning ("%s: got empty command", G_STRLOC);
return;
}
if (self->buffer->len + 1 > MAX_BUFFER_SIZE)
{
freeme = self->buffer;
self->buffer = g_string_new_len (NULL, MAX_BUFFER_SIZE);
ptr = freeme->str;
len = freeme->len;
}
else
{
memcpy (buf, self->buffer->str, self->buffer->len + 1);
ptr = buf;
len = self->buffer->len;
g_string_truncate (self->buffer, 0);
}
if (0)
g_print ("%s: commit %c\n%s\n-----\n", G_STRLOC, ptr[0], ptr + 1);
_moo_app_exec_cmd (moo_app_get_instance (), ptr[0], ptr + 1, len - 1);
if (freeme)
g_string_free (freeme, TRUE);
}
/****************************************************************************/
/* WIN32
*/
#ifdef __WIN32__
typedef struct {
char *pipe_name;
guint event_id;
} ListenerInfo;
static ListenerInfo *
listener_info_new (const char *pipe_name,
guint event_id)
{
ListenerInfo *info = g_new (ListenerInfo, 1);
info->pipe_name = g_strdup (pipe_name);
info->event_id = event_id;
return info;
}
static void
listener_info_free (ListenerInfo *info)
{
if (info)
{
g_free (info->pipe_name);
g_free (info);
}
}
static gpointer
listener_main (ListenerInfo *info)
{
HANDLE input;
_moo_message_async ("%s: hi there", G_STRLOC);
input = CreateNamedPipe (info->pipe_name,
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
0, 0, 200, NULL);
if (input == INVALID_HANDLE_VALUE)
{
_moo_message_async ("%s: could not create input pipe", G_STRLOC);
listener_info_free (info);
return NULL;
}
_moo_message_async ("%s: opened pipe %s", G_STRLOC, info->pipe_name);
while (TRUE)
{
DWORD bytes_read;
char c;
DisconnectNamedPipe (input);
_moo_message_async ("%s: opening connection", G_STRLOC);
if (!ConnectNamedPipe (input, NULL))
{
DWORD err = GetLastError();
if (err != ERROR_PIPE_CONNECTED)
{
char *msg = g_win32_error_message (err);
_moo_message_async ("%s: error in ConnectNamedPipe()", G_STRLOC);
_moo_message_async ("%s: %s", G_STRLOC, msg);
CloseHandle (input);
g_free (msg);
break;
}
}
_moo_message_async ("%s: client connected", G_STRLOC);
while (ReadFile (input, &c, 1, &bytes_read, NULL))
{
if (bytes_read == 1)
{
_moo_event_queue_push (info->event_id, GINT_TO_POINTER ((int) c), NULL);
}
else
{
_moo_message_async ("%s: client disconnected", G_STRLOC);
break;
}
}
}
_moo_message_async ("%s: goodbye", G_STRLOC);
CloseHandle (input);
listener_info_free (info);
return NULL;
}
static char *
get_pipe_name (const char *pipe_basename,
const char *pid_string)
{
if (!pid_string)
pid_string = _moo_get_pid_string ();
return g_strdup_printf ("\\\\.\\pipe\\%s_in_%s",
pipe_basename, pid_string);
}
static void
event_callback (GList *events,
MooAppInput *chan)
{
while (events)
{
char c = GPOINTER_TO_INT (events->data);
if (c != 0)
g_string_append_c (chan->buffer, c);
else
{
gdk_threads_enter ();
commit (chan);
gdk_threads_leave ();
}
events = events->next;
}
}
gboolean
_moo_app_input_start (MooAppInput *ch)
{
ListenerInfo *info;
g_return_val_if_fail (ch != NULL && !ch->ready, FALSE);
g_free (ch->pipe_name);
ch->pipe_name = get_pipe_name (ch->pipe_basename, NULL);
ch->event_id = _moo_event_queue_connect ((MooEventQueueCallback) event_callback,
ch, NULL);
info = listener_info_new (ch->pipe_name, ch->event_id);
if (!g_thread_create ((GThreadFunc) listener_main, info, FALSE, NULL))
{
g_critical ("could not start listener thread");
listener_info_free (info);
g_free (ch->pipe_name);
ch->pipe_name = NULL;
_moo_event_queue_disconnect (ch->event_id);
ch->event_id = 0;
return FALSE;
}
ch->ready = TRUE;
return TRUE;
}
gboolean
_moo_app_input_send_msg (const char *pipe_basename,
const char *pid,
const char *data,
gssize len)
{
char *err_msg = NULL;
char *pipe_name;
HANDLE pipe_handle;
gboolean result = FALSE;
DWORD bytes_written;
g_return_val_if_fail (pipe_basename != NULL, FALSE);
g_return_val_if_fail (data != NULL, FALSE);
if (len < 0)
len = strlen (data);
if (!len)
return TRUE;
if (!pid)
return FALSE;
pipe_name = get_pipe_name (pipe_basename, pid);
pipe_handle = CreateFile (pipe_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (!pipe_handle)
{
err_msg = g_win32_error_message (GetLastError ());
g_warning ("could not open pipe '%s': %s", pipe_name, err_msg);
goto out;
}
if (!WriteFile(pipe_handle, data, len, &bytes_written, NULL))
{
err_msg = g_win32_error_message (GetLastError ());
g_warning ("could not write data to '%s': %s", pipe_name, err_msg);
goto out;
}
if (bytes_written < (DWORD) len)
{
g_warning ("written less data than requested to '%s'", pipe_name);
goto out;
}
result = TRUE;
out:
if (pipe_handle != INVALID_HANDLE_VALUE)
CloseHandle (pipe_handle);
g_free (pipe_name);
g_free (err_msg);
return result;
}
#endif /* __WIN32__ */
/****************************************************************************/
/* UNIX
*/
#ifndef __WIN32__
#define INPUT_PREFIX "input-"
static gboolean
read_input (GIOChannel *source,
GIOCondition condition,
MooAppInput *self)
{
gboolean error_occured = FALSE;
GError *err = NULL;
gboolean again = TRUE;
gboolean got_zero = FALSE;
g_return_val_if_fail (source == self->io, FALSE);
/* XXX */
if (condition & (G_IO_ERR | G_IO_HUP))
error_occured = TRUE;
while (again && !error_occured && !err)
{
char c;
int bytes_read;
struct pollfd fd;
fd.fd = self->pipe;
fd.events = POLLIN | POLLPRI;
fd.revents = 0;
switch (poll (&fd, 1, 0))
{
case -1:
if (errno != EINTR && errno != EAGAIN)
error_occured = TRUE;
perror ("poll");
again = FALSE;
break;
case 0:
if (0)
g_print ("%s: got nothing\n", G_STRLOC);
again = FALSE;
break;
case 1:
if (fd.revents & (POLLERR))
{
if (errno != EINTR && errno != EAGAIN)
error_occured = TRUE;
perror ("poll");
}
else
{
bytes_read = read (self->pipe, &c, 1);
if (bytes_read == 1)
{
g_string_append_c (self->buffer, c);
if (!c)
{
got_zero = TRUE;
again = FALSE;
}
}
else if (bytes_read == -1)
{
perror ("read");
if (errno != EINTR && errno != EAGAIN)
error_occured = TRUE;
again = FALSE;
}
else
{
again = FALSE;
}
}
break;
default:
g_assert_not_reached ();
}
}
if (error_occured || err)
{
g_critical ("%s: %s", G_STRLOC, err ? err->message : "error");
if (err)
g_error_free (err);
_moo_app_input_shutdown (self);
return FALSE;
}
if (got_zero)
commit (self);
return TRUE;
}
static char *
get_pipe_dir (const char *pipe_basename)
{
GdkDisplay *display;
char *display_name;
char *user_name;
char *name;
g_return_val_if_fail (pipe_basename != NULL, NULL);
display = gdk_display_get_default ();
g_return_val_if_fail (display != NULL, NULL);
display_name = g_strcanon (g_strdup (gdk_display_get_name (display)),
G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS,
'-');
user_name = g_strcanon (g_strdup (g_get_user_name ()),
G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS,
'-');
name = g_strdup_printf ("%s/%s-%s-%s", g_get_tmp_dir (), pipe_basename, user_name,
display_name[0] == '-' ? &display_name[1] : display_name);
g_free (display_name);
g_free (user_name);
return name;
}
/* TODO: could you finally learn non-blocking io? */
gboolean
_moo_app_input_start (MooAppInput *ch)
{
GSource *source;
g_return_val_if_fail (!ch->ready, FALSE);
if (!ch->pipe_dir)
ch->pipe_dir = get_pipe_dir (ch->pipe_basename);
g_return_val_if_fail (ch->pipe_dir != NULL, FALSE);
mkdir (ch->pipe_dir, S_IRWXU);
ch->pipe_name = g_strdup_printf ("%s/"INPUT_PREFIX"%d",
ch->pipe_dir,
getpid ());
unlink (ch->pipe_name);
if (mkfifo (ch->pipe_name, S_IRUSR | S_IWUSR))
{
int err = errno;
g_critical ("%s: error in mkfifo()", G_STRLOC);
g_critical ("%s: %s", G_STRLOC, g_strerror (err));
return FALSE;
}
/* XXX O_RDWR is not good (man 3p open), but it must be opened for
* writing by us, otherwise we get POLLHUP when a writer dies on the other end.
* So, open for writing separately. */
ch->pipe = open (ch->pipe_name, O_RDWR | O_NONBLOCK);
if (ch->pipe == -1)
{
int err = errno;
g_critical ("%s: error in open()", G_STRLOC);
g_critical ("%s: %s", G_STRLOC, g_strerror (err));
g_free (ch->pipe_name);
ch->pipe_name = NULL;
return FALSE;
}
_moo_message ("%s: opened input pipe %s with fd %d",
G_STRLOC, ch->pipe_name, ch->pipe);
ch->io = g_io_channel_unix_new (ch->pipe);
g_io_channel_set_encoding (ch->io, NULL, NULL);
ch->io_watch = _moo_io_add_watch (ch->io,
G_IO_IN | G_IO_PRI | G_IO_HUP | G_IO_ERR,
(GIOFunc) read_input,
ch);
source = g_main_context_find_source_by_id (NULL, ch->io_watch);
g_source_set_can_recurse (source, TRUE);
ch->ready = TRUE;
return TRUE;
}
static gboolean
try_send (const char *pipe_dir_name,
const char *pid_string,
const char *data,
gssize data_len)
{
GPid pid;
char *filename = NULL;
GIOChannel *chan = NULL;
GIOStatus status;
gboolean result = FALSE;
char *endptr;
GError *error = NULL;
if (!pid_string[0])
goto out;
errno = 0;
pid = strtol (pid_string, &endptr, 10);
if (errno != 0 || endptr == pid_string || *endptr != 0)
{
g_warning ("invalid pid string '%s'", pid_string);
goto out;
}
filename = g_strdup_printf ("%s/"INPUT_PREFIX"%s", pipe_dir_name, pid_string);
_moo_message ("try_send: sending data to pid %s, filename %s", pid_string, filename);
if (!g_file_test (filename, G_FILE_TEST_EXISTS))
{
_moo_message ("try_send: file %s doesn't exist", filename);
goto out;
}
if (kill (pid, 0) != 0)
{
_moo_message ("try_send: no process with pid %d", pid);
unlink (filename);
goto out;
}
chan = g_io_channel_new_file (filename, "w", &error);
if (!chan)
{
_moo_message ("try_send: could not open %s for writing: %s",
filename, error ? error->message : "<?>");
g_error_free (error);
goto out;
}
g_io_channel_set_encoding (chan, NULL, NULL);
status = g_io_channel_set_flags (chan, G_IO_FLAG_NONBLOCK, &error);
if (status != G_IO_STATUS_NORMAL)
{
_moo_message ("try_send: could not set NONBLOCK flag: %s",
error ? error->message : "<?>");
goto out;
}
status = g_io_channel_write_chars (chan, data, data_len, NULL, &error);
if (status != G_IO_STATUS_NORMAL)
{
_moo_message ("try_send: error writing to pipe: %s",
error ? error->message : "<?>");
goto out;
}
result = TRUE;
_moo_message ("try_send: successfully sent stuff to pid %s", pid_string);
out:
if (chan)
g_io_channel_unref (chan);
if (error)
g_error_free (error);
g_free (filename);
return result;
}
gboolean
_moo_app_input_send_msg (const char *pipe_basename,
const char *pid,
const char *data,
gssize data_len)
{
const char *entry;
GDir *pipe_dir = NULL;
char *pipe_dir_name;
gboolean success = FALSE;
g_return_val_if_fail (pipe_basename != NULL, FALSE);
g_return_val_if_fail (data != NULL, FALSE);
_moo_message ("_moo_app_input_send_msg: sending data to pid %s", pid ? pid : "NONE");
pipe_dir_name = get_pipe_dir (pipe_basename);
g_return_val_if_fail (pipe_dir_name != NULL, FALSE);
if (pid)
{
success = try_send (pipe_dir_name, pid, data, data_len);
goto out;
}
pipe_dir = g_dir_open (pipe_dir_name, 0, NULL);
if (!pipe_dir)
goto out;
while ((entry = g_dir_read_name (pipe_dir)))
{
if (!strncmp (entry, INPUT_PREFIX, strlen (INPUT_PREFIX)))
{
const char *pid_string = entry + strlen (INPUT_PREFIX);
if (try_send (pipe_dir_name, pid_string, data, data_len))
{
success = TRUE;
goto out;
}
}
}
out:
if (pipe_dir)
g_dir_close (pipe_dir);
g_free (pipe_dir_name);
return success;
}
#endif /* ! __WIN32__ */