memory scribbling while using IOChannels



Folks,
	On 8/5, I updated my CVS to discover my IOChannel code would
have problems.  I've been working with Ron on this since then, and it
hasn't been solved.  Ron now has a g_assert() that traps the error.
Basically, what I saw was that the GString buffers in
g_io_channel_fill_buffer() had invalid or corrupt data.  This would
cause looping or crashing, depending on the corrupt data.
	This error is not constant.  It only happens some of the time.
Hence my feeling there is either a race or a smash happening here.
	CVS from 8/3 (cvs get -D "2001-08-03") works just fine.  I've
been over the changes to giochannel.c from 8/3 to 8/5 with a fine comb,
and I cannot see any problems.  There were additional changes in that
timeframe to random glib bits and gobject.  I can't see anything in the
debugger that even gives me a place to start.  I've traced the
 _fill_buffer() execution to my wit's end with no luck.
	Help.  I cannot use a glib later than 8/3, and I would like to
start using later versions, especially as we approach 2.0.

Test code attached.  Compile with
gcc -g -o iotest iotest.c `pkg-config --cflags --libs gtk+-2.0`

Thanks
Joel

-- 

"We will have to repent in this generation not merely for the
 vitriolic words and actions of the bad people, but for the 
 appalling silence of the good people."
	- Rev. Dr. Martin Luther King, Jr.

			http://www.jlbec.org/
			jlbec evilplan org
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <glib.h>
#include <gtk/gtk.h>


#define NUM_READ_PAGES 16

typedef struct _IOTest IOTest;

struct _IOTest
{
    pid_t child_pid;
    gint child_status;
    gchar *child_out_data;
    gchar *child_err_data;
    GIOChannel *child_out_chan;
    GIOChannel *child_err_chan;
    gint timeout_id;
    gint pulse_id;
    GtkWidget *label;
    GtkWidget *bar;
};


/* 
 * Prototypes
 */
static void ic_start(IOTest *ctxt);
static gboolean ic_timeout(gpointer user_data);

static gboolean ic_out_func(GIOChannel *chan,
                            GIOCondition cond,
                            gpointer user_data);
static gboolean ic_err_func(GIOChannel *chan,
                            GIOCondition cond,
                            gpointer user_data);
gint exec_command_v(pid_t *pid,
                    gint *infd,
                    gint *outfd,
                    gint *errfd,
                    const gchar *cmd,
                    gchar * const args[]);



/*
 * static void ic_start(IOTest *ctxt)
 *
 * Fires off an INITCOMMAND
 */
static void ic_start(IOTest *ctxt)
{
    gint outfd, errfd, rc;
    const gchar *encoding;
    gchar *args[] = {"/bin/sh", "-c", "/bin/ls /usr/lib", NULL};

    rc = exec_command_v(&(ctxt->child_pid),
                        NULL, &outfd, &errfd,
                        args[0], args);
    if (rc != 0)
    {
        gtk_label_set_text(GTK_LABEL(ctxt->label),
                           "Unable to start process.");
        return;
    }

    g_get_charset(&encoding);

    fcntl(outfd, F_SETFL, O_NONBLOCK);
    ctxt->child_out_chan = g_io_channel_unix_new(outfd);
    g_io_channel_set_encoding(ctxt->child_out_chan, encoding, NULL);
    g_io_add_watch_full(ctxt->child_out_chan,
                        G_PRIORITY_LOW,
                        G_IO_IN | G_IO_HUP | G_IO_ERR,
                        ic_out_func,
                        ctxt,
                        NULL);
    g_io_channel_unref(ctxt->child_out_chan);

    fcntl(errfd, F_SETFL, O_NONBLOCK);
    ctxt->child_err_chan = g_io_channel_unix_new(errfd);
    g_io_channel_set_encoding(ctxt->child_err_chan, encoding, NULL);
    g_io_add_watch_full(ctxt->child_err_chan,
                        G_PRIORITY_LOW,
                        G_IO_IN | G_IO_HUP | G_IO_ERR,
                        ic_err_func,
                        ctxt,
                        NULL);
    g_io_channel_unref(ctxt->child_err_chan);

    ctxt->timeout_id = g_timeout_add(100, ic_timeout, ctxt);

    gtk_progress_bar_pulse(GTK_PROGRESS_BAR(ctxt->bar));
    ctxt->pulse_id = g_timeout_add(100,
                                   (GtkFunction)gtk_progress_bar_pulse,
                                   ctxt->bar);
}  /* ic_start() */


/*
 * static gboolean ic_timeout(gpointer user_data)
 *
 * Timeout function to check whether the INITCOMMAND has finished
 */
static gboolean ic_timeout(gpointer user_data)
{
    gint rc;
    IOTest *ctxt;

    g_return_val_if_fail(user_data != NULL, FALSE);

    ctxt = (IOTest *)user_data;

    if (ctxt->child_pid > 0)
    {
        rc = waitpid(ctxt->child_pid,
                     &(ctxt->child_status),
                     WNOHANG);
        if (rc > 0)
            ctxt->child_pid = -1;
        else if ((rc < 0) && (errno == ECHILD))
            g_assert_not_reached();
    }

    if ((ctxt->child_pid < 0) &&
        (ctxt->child_out_chan == NULL) &&
        (ctxt->child_err_chan == NULL))
    {
        /* Clear our timeout before doing work */
        g_source_remove(ctxt->pulse_id);
        g_source_remove(ctxt->timeout_id);
        gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ctxt->bar), 0.0);

        if (WIFEXITED(ctxt->child_status) &&
            (WEXITSTATUS(ctxt->child_status) == 0))
        {
            if (ctxt->child_out_data == NULL)
                ctxt->child_out_data = g_strdup("");

            gtk_label_set_text(GTK_LABEL(ctxt->label),
                               "Process returned successfully");
        }
        else
        {
            gtk_label_set_text(GTK_LABEL(ctxt->label),
                               "Process returned an error");
        }

        if (ctxt->child_out_data != NULL)
        {
            g_free(ctxt->child_out_data);
            ctxt->child_out_data = NULL;
        }
        if (ctxt->child_err_data != NULL)
        {
            g_free(ctxt->child_err_data);
            ctxt->child_err_data = NULL;
        }
        return(FALSE);
    }

    return(TRUE);
}  /* ic_timeout() */


/*
 * static gboolean ic_out_func(GIOChannel *chan,
 *                             GIOCondition cond,
 *                             gpointer user_data)
 *
 * Function to read data from an INITCOMMAND's STDOUT.
 */
static gboolean ic_out_func(GIOChannel *chan,
                            GIOCondition cond,
                            gpointer user_data)
{
    IOTest *ctxt;
    GIOStatus rc;
    gint bytes_read;
    gboolean cont;
    GError *err;
    static GString *out = NULL;
    static gchar *buffer = NULL;
    static size_t page_size = 0;

    g_return_val_if_fail(user_data != NULL, FALSE);

    if (out == NULL)
        out = g_string_new(NULL);

    if (buffer == NULL)
    {
        page_size = getpagesize();
        buffer = g_new0(gchar, page_size * NUM_READ_PAGES);
    }

    g_print("IN = %s, HUP = %s, ERR = %s\n",
            cond & G_IO_IN ? "true" : "false",
            cond & G_IO_HUP ? "true" : "false",
            cond & G_IO_ERR ? "true" : "false");
    /* cont = (cond & (G_IO_ERR | G_IO_HUP)) ? FALSE : TRUE; */
    cont = TRUE;
    ctxt = (IOTest *)user_data;
    err = NULL;
    g_print("Calling read_chars\n");
    rc = g_io_channel_read_chars(chan, buffer,
                                 page_size * NUM_READ_PAGES,
                                 &bytes_read, &err);
    switch (rc)
    {
        case G_IO_STATUS_EOF:
            g_print("Status EOF\n");
            cont = FALSE;
            if (bytes_read < 1)
                break;
            /* Fall through with read data */

        case G_IO_STATUS_NORMAL:
            g_print("Status NORMAL\n");
            g_print("Bytes read = %d\n", bytes_read);
            g_assert(bytes_read > 0);
            g_string_append_len(out, buffer, bytes_read);
            break;

        case G_IO_STATUS_AGAIN:
            /* Do nothing */
            g_print("Status AGAIN\n");
            break;

        case G_IO_STATUS_ERROR:
            g_print("Status ERROR\n");
            cont = FALSE;
            /* FIXME: Flag an error? */
            break;
    }

    g_print("Chan refcount = %d\n", chan->ref_count);
    if (cont == FALSE)
    {
        g_print("Calling shutdown\n");
        g_io_channel_shutdown(chan, FALSE, NULL);
        g_free(buffer);
        buffer = NULL;
        ctxt->child_out_chan = NULL;
        if (out->len > 0)
            ctxt->child_out_data = g_strdup(out->str);
        g_string_free(out, TRUE);
        out = NULL;
    }

    g_print("cont = %s\n", cont ? "true" : "false");
    return(cont);
}  /* ic_out_func() */


/*
 * static gboolean ic_err_func(GIOChannel *chan,
 *                             GIOCondition cond,
 *                             gpointer user_data)
 *
 * Function to read data from an INITCOMMAND's STDERR.
 */
static gboolean ic_err_func(GIOChannel *chan,
                            GIOCondition cond,
                            gpointer user_data)
{
    IOTest *ctxt;
    GIOStatus rc;
    gint bytes_read;
    gboolean cont;
    GError *err;
    static GString *ers = NULL;
    static gchar *buffer = NULL;
    static size_t page_size = 0;

    g_return_val_if_fail(user_data != NULL, FALSE);

    if (ers == NULL)
        ers = g_string_new(NULL);

    if (buffer == NULL)
    {
        page_size = getpagesize();
        buffer = g_new0(gchar, page_size * NUM_READ_PAGES);
    }

    cont = TRUE;
    ctxt = (IOTest *)user_data;
    err = NULL;
    rc = g_io_channel_read_chars(chan, buffer,
                                 page_size * NUM_READ_PAGES,
                                 &bytes_read, &err);
    switch (rc)
    {
        case G_IO_STATUS_EOF:
            cont = FALSE;
            if (bytes_read < 1)
                break;
            /* Fall through with read data */

        case G_IO_STATUS_NORMAL:
            g_assert(bytes_read > 0);
            g_string_append_len(ers, buffer, bytes_read);
            break;

        case G_IO_STATUS_AGAIN:
            /* Do nothing */
            break;

        case G_IO_STATUS_ERROR:
            cont = FALSE;
            /* FIXME: Flag an error? */
            break;
    }

    if (cont == FALSE)
    {
        g_io_channel_shutdown(chan, FALSE, NULL);
        g_free(buffer);
        buffer = NULL;
        ctxt->child_err_chan = NULL;
        if (ers->len > 0)
            ctxt->child_err_data = g_strdup(ers->str);
        g_string_free(ers, TRUE);
        ers = NULL;
    }
    return(cont);
}  /* ic_err_func() */


/*
 * gint exec_command_v(pid_t *pid,
 *                     gint *infd,
 *                     gint *outfd,
 *                     gint *errfd,
 *                     const gchar *command,
 *                     gchar * const args[])
 *
 * Executes a command, returning the process id and the 
 * stdout and stderr file descriptors via the passed parameters.
 */
gint exec_command_v(pid_t *pid,
                    gint *infd,
                    gint *outfd,
                    gint *errfd,
                    const gchar *command,
                    gchar * const args[])
{
    gint inp[2], outp[2], errp[2], rc;

    if (pipe(inp))
        return -errno;

    if (pipe(outp))
    {
        rc = -errno;
        close(inp[0]);
        close(inp[1]);
        return rc;
    }

    if (pipe(errp))
    {
        rc = -errno;
        close(inp[0]);
        close(inp[1]);
        close(outp[0]);
        close(outp[1]);
        return rc;
    }

    *pid = fork();

    if (*pid == 0)
    {
        close (inp[1]);
	close (outp[0]);
	close (errp[0]);

        if (infd == NULL)
            close(inp[0]);
        else
	{
	    if (inp[0] != STDIN_FILENO)
	    {
		gint fd;

		fd = dup2(inp[0], STDIN_FILENO);
		if (fd == -1)
		    _exit(-errno);
		close(inp[0]);
	    }
	}

	if (outfd == NULL)
	    close(outp[1]);
	else
	{
	    if (outp[1] != STDOUT_FILENO)
	    {
		gint fd;

		fd = dup2(outp[1], STDOUT_FILENO);
		if (fd == -1)
		    _exit(-errno);
		close(outp[1]);
	    }
	}

	if (errfd == NULL)
	    close(errp[1]);
	else
	{
	    if (errp[1] != STDERR_FILENO)
	    {
		gint fd;

		fd = dup2(errp[1], STDERR_FILENO);
		if (fd == -1)
		    _exit(-errno);
		close(errp[1]);
	    }
	}

        execv(command, args);
	g_error("Couldn't exec: %s\n", g_strerror(errno));
	_exit(-errno);
    }
    else if (*pid < 0)
        return -errno;

    close(inp[0]);
    close(outp[1]);
    close(errp[1]);

    if (infd)
        *infd = inp[1];
    else
        close(inp[1]);

    if (outfd)
	*outfd = outp[0];
    else
	close(outp[0]);

    if (errfd)
	*errfd = errp[0];
    else
	close(errp[0]);

    return 0;
}


void do_start(GtkWidget *widget, gpointer user_data)
{
    IOTest *ctxt;

    ctxt = (IOTest *)user_data;
    ic_start(ctxt);
}


void do_quit(GtkWidget *widget, gpointer user_data)
{
    gtk_main_quit();
}


gint main(gint argc, gchar *argv[])
{
    IOTest *ctxt;
    GtkWidget *top, *button, *box;

    ctxt = g_new0(IOTest, 1);

    gtk_set_locale();
    gtk_init(&argc, &argv);

    top = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_container_set_border_width(GTK_CONTAINER(top), 5);
    gtk_signal_connect(GTK_OBJECT(top),
                       "delete-event",
                       GTK_SIGNAL_FUNC(do_quit), NULL);
    gtk_signal_connect(GTK_OBJECT(top),
                       "destroy",
                       GTK_SIGNAL_FUNC(do_quit), NULL);

    box = gtk_vbox_new(FALSE, 5);

    ctxt->label = gtk_label_new("IOTest - testing GIOChannel via ls(1)");
    gtk_box_pack_start(GTK_BOX(box), ctxt->label, FALSE, FALSE, 1);

    button = gtk_button_new_with_label("Run");
    gtk_signal_connect(GTK_OBJECT(button),
                       "clicked",
                       GTK_SIGNAL_FUNC(do_start), ctxt);
    gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 1);

    button = gtk_button_new_with_label("Quit");
    gtk_signal_connect(GTK_OBJECT(button),
                       "clicked",
                       GTK_SIGNAL_FUNC(do_quit), NULL);
    gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 1);

    ctxt->bar = gtk_progress_bar_new();
    gtk_progress_bar_set_orientation(GTK_PROGRESS_BAR(ctxt->bar),
                                     GTK_PROGRESS_LEFT_TO_RIGHT);
    gtk_progress_bar_set_bar_style(GTK_PROGRESS_BAR(ctxt->bar),
                                   GTK_PROGRESS_CONTINUOUS);
    gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(ctxt->bar), 0.05);
    gtk_box_pack_end(GTK_BOX(box), ctxt->bar, FALSE, FALSE, 0);

    gtk_container_add(GTK_CONTAINER(top), box);
    gtk_widget_show_all(top);
    
    gtk_main();

    return(0);
}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]