#include <glib.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include "entity.h"
#ifndef BUFSIZ
#define BUFSIZ 1024
#endif
/*
* The way an io tag can look in the xml.
* <io fd="1"
* onnewdata="got_part"
* onwrite="progress"
* onerror="got_messed"
* mode="chunk,line"
* />
*/
typedef struct _WriteBuf {
char *buffer;
char *obuf;
int size;
} WriteBuf;
/* Releases all memory and watch tags involved with doing reading */
static void
rendio_release_read_watch (ENode * node)
{
void *rtag;
EBuf *buffer;
EDEBUG (("io-renderer", "releasing input stream information"));
rtag = enode_get_kv (node, "rendio-io-rtag");
enode_set_kv (node, "rendio-io-rtag", NULL);
if (rtag)
entity_mainloop->io_remove (rtag);
EDEBUG (("io-renderer", "releasing input stream tag %d", rtag));
buffer = enode_get_kv (node, "rendio-io-read-buffer");
if (buffer)
ebuf_free (buffer);
enode_set_kv (node, "rendio-io-read-buffer", NULL);
}
/* Releases watch tags involved with doing writes */
static void
rendio_release_write_watch (ENode * node)
{
void *wtag;
EDEBUG (("io-renderer", "releasing write stream information"));
wtag = enode_get_kv (node, "rendio-io-wtag");
enode_set_kv (node, "rendio-io-wtag", NULL);
if (wtag)
entity_mainloop->io_remove (wtag);
EDEBUG (("io-renderer", "releasing input stream tag %d", wtag));
enode_set_kv (node, "rendio-io-read-buffer", NULL);
}
/* chunk mode is real easy.. read chunk, pass it on */
static int
rendio_read_chunk_mode_callback (gint source, EIOCond cond, ENode * node)
{
EBuf *function;
gint nread;
gint fd;
gchar buffer[BUFSIZ];
function = enode_attrib (node, "onnewdata", NULL);
/* Get fd */
fd = GPOINTER_TO_INT (enode_get_kv (node, "rendio-io-fd"));
if (fd < 0)
return (FALSE);
EDEBUG (
("io-renderer",
"in chunk_mode read callback, gonna read from fd %d", fd));
nread = read (fd, buffer, BUFSIZ);
EDEBUG (("io-renderer", "binary, nread = %i", nread));
/* If we read -1, assume there's an error and quit watching for reads */
if (nread <= 0)
rendio_release_read_watch (node);
/* we pass nread straight so they can use it to see status too */
if (ebuf_not_empty (function))
enode_call_ignore_return (node, function->str, "bi", buffer, nread,
nread);
return (TRUE);
}
/* When new data arrives in line mode, we call this with the new data.. this
* will sort out the data and call a function if there's a new line to pass
* on */
static void
rendio_renderer_newdata (ENode * node, gchar * inbuf, gint has_newline)
{
EBuf *buffer;
gint nread;
gchar *function;
nread = strlen (inbuf);
function = enode_attrib_str (node, "onnewdata", NULL);
buffer = enode_get_kv (node, "rendio-io-read-buffer");
EDEBUG (("io-renderer", "newdata function is %s", function));
/* if we find an eol.. */
if (has_newline) {
EDEBUG (("io-renderer", "EOL found checking.."));
/* check to see if there's already something in the buffer from
* before */
if (!buffer || buffer->len == 0) {
EDEBUG (("io-renderer", "No previous buffer, sending directly."));
/* If not, we can pass this straight to the application */
if (function)
enode_call_ignore_return (node, function, "bi", inbuf, nread,
nread);
return;
} else {
EDEBUG (("io-renderer",
"EOL found, appending '%s' to previous buffer '%s' and sending.",
inbuf, buffer->str));
/* else we have to get the previous buffer first, and then pass
* it on */
ebuf_append_str (buffer, inbuf);
if (function)
enode_call_ignore_return (node, function, "bi",
buffer->str, buffer->len,
buffer->len);
/* truncate buffer */
ebuf_truncate (buffer, 0);
return;
}
} else {
/* If we make it here, it means there was no end of line found, and
* we have to store it for the next pass */
/* init buffer if we don't already have one */
if (!buffer) {
buffer = ebuf_new ();
enode_set_kv (node, "rendio-io-read-buffer", buffer);
}
EDEBUG (("io-renderer", "no EOL, appending '%s' to buffer.", inbuf));
/* append data to buffer */
ebuf_append_str (buffer, inbuf);
}
}
/* Line mode is a bit more tricky, must write to a queue until we've
* determined that we have a full line to pass to the application */
static gint
rendio_read_line_mode_callback (gint source, EIOCond cond, ENode * node)
{
gchar buffer[BUFSIZ + 1];
gchar *p;
gchar s;
gint nread;
gint fd;
gint i;
EDEBUG (("io-renderer", "in rendio_read_callback, cond = %i", cond));
fd = GPOINTER_TO_INT (enode_get_kv (node, "rendio-io-fd"));
if (fd < 0)
return (FALSE);
nread = read (fd, buffer, BUFSIZ);
/* insure \0 */
buffer[nread] = '\0';
p = buffer;
/* Walk through string and break it into chunks on newlines */
for (i = 0; i <= nread; i++) {
if (buffer[i] == '\n') {
/* Save old value right after \n */
s = buffer[i + 1];
buffer[i + 1] = '\0';
rendio_renderer_newdata (node, p, TRUE);
/* restore value after \n */
buffer[i + 1] = s;
/* update p */
p = &buffer[i + 1];
}
}
rendio_renderer_newdata (node, p, FALSE);
if (nread <= 0)
rendio_release_read_watch (node);
return (TRUE);
}
static GSList *
free_write_buffer (GSList * list, WriteBuf * buffer)
{
GSList *tmp;
if (list && list->data == buffer) {
tmp = list;
list = list->next;
tmp->next = NULL;
g_slist_free (tmp);
}
g_free (buffer->obuf);
g_free (buffer);
return (list);
}
static int
rendio_sendq_callback (gint source, EIOCond cond, ENode * node)
{
char *function;
WriteBuf *buffer;
GSList *write_list;
gint ret;
gint fd;
EDEBUG (("io-renderer", "rendio_sendq_callback"));
/* Get the fd */
fd = GPOINTER_TO_INT (enode_get_kv (node, "rendio-io-fd"));
write_list = enode_get_kv (node, "rendio-io-write-list");
EDEBUG (("io-renderer", "write_list = %i", write_list));
if (!write_list || !write_list->data) {
/* Nothing to write. */
rendio_release_write_watch (node);
return (FALSE);
}
buffer = (WriteBuf *) write_list->data;
ret = write (fd, buffer->buffer, buffer->size);
EDEBUG (("io-renderer", "write ret = %i", ret));
if (ret <= 0) {
/* Error on descriptor, effectivly and eof, clean up time */
g_warning ("Error writing to file: %s", g_strerror (errno));
rendio_release_write_watch (node);
return (FALSE);
} else if (ret < buffer->size) {
buffer->buffer += ret;
buffer->size -= ret;
} else if (ret == buffer->size) {
write_list = free_write_buffer (write_list, buffer);
enode_set_kv (node, "rendio-io-write-list", write_list);
if (!write_list) {
/* We have written everything. */
EDEBUG (("io-renderer", "done writing"));
rendio_release_write_watch (node);
}
}
function = enode_attrib_str (node, "onwrite", NULL);
enode_call_ignore_return (node, function, "i", ret);
return (TRUE);
}
gint rendio_sendq_attr_set (ENode * node, EBuf * attr, EBuf * value)
{
WriteBuf *buffer;
void *wtag;
gint fd;
GSList *write_list = NULL;
GSList *write_list_tail = NULL;
gint send_size = value->len;
gchar *data = value->str;
EDEBUG (("io-renderer", "sendq buffer set, buffer size = %i", send_size));
if (send_size <= 0) /* nothing to send. */
return (FALSE);
buffer = g_new0 (WriteBuf, 1);
buffer->size = send_size;
buffer->obuf = g_new (char, send_size);
memcpy (buffer->obuf, data, send_size);
buffer->buffer = buffer->obuf;
enode_attrib_quiet (node, attr->str, ebuf_new_with_str (""));
write_list = enode_get_kv (node, "rendio-io-write-list");
write_list_tail = enode_get_kv (node, "rendio-io-write-list-tail");
write_list = g_slist_append_tail (write_list, buffer, &write_list_tail);
enode_set_kv (node, "rendio-io-write-list", write_list);
enode_set_kv (node, "rendio-io-write-list-tail", write_list_tail);
/* Get fd */
fd = GPOINTER_TO_INT (enode_get_kv (node, "rendio-io-fd"));
wtag = enode_get_kv (node, "rendio-io-wtag");
if (fd && !wtag) {
wtag = entity_mainloop->io_add (fd, EIO_WRITE,
(void *) rendio_sendq_callback, node);
EDEBUG (("io-renderer", "added new write tag, wtag = %i", wtag));
enode_set_kv (node, "rendio-io-wtag", wtag);
}
return (TRUE);
}
static int
rendio_error_callback (gint source, EIOCond cond, ENode * node)
{
gchar *function;
EDEBUG (("io-renderer", "rendio_error_callback"));
function = enode_attrib_str (node, "onerror", NULL);
enode_call_ignore_return (node, function, "");
return (TRUE);
}
static void
rendio_release_watch (ENode * node)
{
void *old_etag;
/* Delete old io watchers if they exist ... */
/* This looks after all the read watcher stuff */
rendio_release_read_watch (node);
/* and the error tag... */
rendio_release_write_watch (node);
old_etag = enode_get_kv (node, "rendio-io-etag");
/* need to delete the old tag and callback connection. */
EDEBUG (("io-renderer", "going to delete old tags"));
if (old_etag)
entity_mainloop->io_remove (old_etag);
enode_set_kv (node, "rendio-io-etag", NULL);
}
/* Called with the fd is set, so we have to setup new handlers for
* everything. */
static int
rendio_fd_attr_set (ENode * node, EBuf * attr, EBuf * value)
{
gint source;
void *rtag;
void *etag;
EBuf *line_mode;
EDEBUG (("io-renderer", "fd attribute set"));
/* Release old fd watchers */
rendio_release_watch (node);
/* clear list */
enode_set_kv (node, "rendio-io-line", NULL);
enode_set_kv (node, "rendio-io-write-list", NULL);
/* if source is < 0 then they are telling us to stop watching, or they
* have a bad fd or something.. */
source = erend_get_integer (value);
if (source < 0)
return (TRUE);
EDEBUG (("io-renderer", "fd set to %d", source));
/* No blocking io... this could cause odd interactions, we'll have to see
*/
fcntl (source, F_SETFL, O_NONBLOCK);
EDEBUG (("io-renderer", "source = %i", source));
line_mode = enode_attrib (node, "mode", NULL);
if (ebuf_not_empty (line_mode) && ebuf_equal_str (line_mode, "line")) {
rtag = entity_mainloop->io_add (source, EIO_READ,
(void *) rendio_read_line_mode_callback,
node);
} else {
rtag = entity_mainloop->io_add (source, EIO_READ,
(void *)
rendio_read_chunk_mode_callback, node);
}
enode_set_kv (node, "rendio-io-rtag", rtag);
EDEBUG (("io-renderer", "rtag = %i", rtag));
etag = entity_mainloop->io_add (source, EIO_ERROR,
(void *) rendio_error_callback, node);
enode_set_kv (node, "rendio-io-etag", etag);
EDEBUG (("io-renderer", "etag = %i", etag));
enode_set_kv (node, "rendio-io-fd", GINT_TO_POINTER (source));
return (TRUE);
}
static void
rendio_render (ENode * node)
{
EDEBUG (("io-renderer", "rendering"));
enode_attribs_sync (node);
}
static void
rendio_parent (ENode * parent_node, ENode * child_node)
{
erend_short_curcuit_parent (parent_node, child_node);
}
static void
rendio_destroy (ENode * node)
{
/* Release old fd watchers */
rendio_release_watch (node);
}
void
io_renderer_register (void)
{
Element *element;
ElementAttr *e_attr;
element = g_new0 (Element, 1);
element->render_func = rendio_render;
element->parent_func = rendio_parent;
element->destroy_func = rendio_destroy;
element->tag = "io";
element->description =
"Buffered event driven IO. Set up a file descriptor, and use this to do input or output on it.";
element_register (element);
e_attr = g_new0 (ElementAttr, 1);
e_attr->attribute = "fd";
e_attr->description = "Specify the file descripter to watch.";
e_attr->value_desc = "integer";
e_attr->possible_values = "-1,*";
e_attr->set_attr_func = rendio_fd_attr_set;
element_register_attrib (element, e_attr);
e_attr = g_new0 (ElementAttr, 1);
e_attr->attribute = "_sendq";
e_attr->description = "Stuff data into the outbound send queue.";
e_attr->value_desc = "string";
e_attr->set_attr_func = rendio_sendq_attr_set;
element_register_attrib (element, e_attr);
e_attr = g_new0 (ElementAttr, 1);
e_attr->attribute = "mode";
e_attr->description = "Specify whether to pass lines or binary chunks.";
e_attr->value_desc = "string";
e_attr->possible_values = "chunk,line";
e_attr->set_attr_func = NULL;
element_register_attrib (element, e_attr);
e_attr = g_new0 (ElementAttr, 1);
e_attr->attribute = "onnewdata";
e_attr->description = "Function to call with data read from the fd.";
e_attr->value_desc = "function";
e_attr->set_attr_func = NULL;
element_register_attrib (element, e_attr);
e_attr = g_new0 (ElementAttr, 1);
e_attr->attribute = "onwrite";
e_attr->description =
"Function to call with the number of bytes written to fd.";
e_attr->value_desc = "function";
e_attr->set_attr_func = NULL;
element_register_attrib (element, e_attr);
e_attr = g_new0 (ElementAttr, 1);
e_attr->attribute = "onerror";
e_attr->description =
"Function to call when an exception occurs on the fd.";
e_attr->value_desc = "function";
e_attr->set_attr_func = NULL;
element_register_attrib (element, e_attr);
}
syntax highlighted by Code2HTML, v. 0.9.1