/* Unix socket Echo server
 * Copyright (C) 2001  Mark Ferlatte
 * Adapted from echoserver.c, Copyright (C) 2000  David Helder
 * 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.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <glib.h>
#include <gnet.h>

typedef enum { NORMAL, ASYNC} ServerType;

void cleanup_on_sig(int signum);

static void usage(int status);
static void normal_echoserver(gchar *path);
static void async_echoserver(gchar *path);

GUnixSocket *server;

int
main(int argc, char** argv)
{
  gchar *path = NULL;
  ServerType server_type = NORMAL;
	
  gnet_init ();

  if (argc !=  2 && argc != 3) {
    usage(EXIT_FAILURE);
  }
  if (argc == 3) {
    if (strcmp(argv[1], "--async") == 0)
      server_type = ASYNC;
    else {
      usage(EXIT_FAILURE);
    }
  }
	
  path = g_new0(gchar, strlen(argv[argc - 1]));
  path = memcpy(path, argv[argc - 1], strlen(argv[argc - 1]));

  signal(SIGINT, cleanup_on_sig);
  signal(SIGTERM, cleanup_on_sig);
		
  switch (server_type) {
  case NORMAL:
    g_print("Normal echo server running\n");
    normal_echoserver(path);
    break;
  case ASYNC:
    g_print("Async echo server running\n");
    async_echoserver(path);
    break;
  default:
    g_assert_not_reached();
  }
  return 0;
}

static void
usage(int status)
{
  g_print("usage: echoserver-unix [(nothing)|--async] <path>\n");
  exit(status);
}

void
cleanup_on_sig(int signum)
{
  gnet_unix_socket_delete(server);
  exit(EXIT_SUCCESS);
}

static void
normal_echoserver(gchar *path)
{
  GUnixSocket *client = NULL;
  gchar buffer[1024];
  guint n;
  GIOChannel *ioclient = NULL;
  GIOError e;

  g_assert(path != NULL);
	
  /* Create the server */
  server = gnet_unix_socket_server_new(path);
  g_assert(server != NULL);

  while ((client = gnet_unix_socket_server_accept(server)) != NULL) {
    ioclient = gnet_unix_socket_get_io_channel(client);
    g_assert(ioclient != NULL);

    while ((e = gnet_io_channel_readline(ioclient, buffer,
					 sizeof(buffer), &n))
	   == G_IO_ERROR_NONE && (n > 0)) {
      e = gnet_io_channel_writen(ioclient, buffer, n, &n);
      if (e != G_IO_ERROR_NONE)
	break;
      fwrite(buffer, n, 1, stdout);
    }
    if (e != G_IO_ERROR_NONE)
      fprintf(stderr,
	      "\nRecieved error %d (closing socket).\n",
	      e);
    gnet_unix_socket_delete (client);
  }
}

typedef struct
{
  GUnixSocket *socket;
  guint out_watch;
  gchar buffer[1024];
  guint n;
} client_state;

static void clientstate_delete(client_state *state);

static gboolean async_server_iofunc(GIOChannel *server,
				    GIOCondition c, gpointer data);
static gboolean async_client_iofunc(GIOChannel *client,
				    GIOCondition c, gpointer data);

static void
clientstate_delete(client_state *state)
{
  if (state->out_watch)
    g_source_remove(state->out_watch);
  gnet_unix_socket_delete(state->socket);
  g_free(state);
}

static void
async_echoserver(gchar *path)
{
  GIOChannel *iochannel = NULL;
  GMainLoop *main_loop = NULL;

  g_assert(path != NULL);
  server = gnet_unix_socket_server_new(path);
  g_assert(server != NULL);

  main_loop = g_main_new(FALSE);

  /* Add a watch for incoming clients */
  iochannel = gnet_unix_socket_get_io_channel(server);
  g_io_add_watch(iochannel,
		 G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
		 async_server_iofunc, server);

  /* Start the main loop */
  g_main_run(main_loop);
}

static gboolean
async_server_iofunc(GIOChannel *iochannel, GIOCondition c, gpointer data)
{
  GUnixSocket *server = (GUnixSocket *) data;
  g_assert(server != NULL);

  if (c & G_IO_IN) {
    GUnixSocket *client = NULL;
    GIOChannel *client_iochannel = NULL;
    client_state *cs = NULL;

    client = gnet_unix_socket_server_accept(server);
    g_assert(client != NULL);

    client_iochannel = gnet_unix_socket_get_io_channel(client);
    g_assert(client_iochannel != NULL);

    cs = g_new0(client_state, 1);
    cs->socket = client;

    g_io_add_watch(client_iochannel,
		   G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
		   async_client_iofunc, cs);
  } else {
    fprintf(stderr, "Server error %d\n", c);
    return FALSE;
  }
  return TRUE;
}

/*

  This callback is called for (IN|ERR) or OUT.  

  We add the watch for (IN|ERR) when the client connects in
  async_server_iofunc().  We remove it if the connection is closed
  (i.e. we read 0 bytes) or if there's an error.

  We add the watch for OUT when we have something to write and remove
  it when we're done writing or when the connection is closed.
 */
static gboolean
async_client_iofunc(GIOChannel *iochannel, GIOCondition c,
		    gpointer data)
{
  client_state *cs = (client_state *) data;

  g_assert(cs != NULL);

  /* Check for socket error */
  if (c & G_IO_ERR) {
    fprintf(stderr, "Client socket error!\n");
    goto error;
  }
  /* Check for data to be read (or if the socket was closed) */
  if (c & G_IO_IN) {
    GIOError e;
    guint bytes_read;

    /* Read the data into our buffer */
    e = g_io_channel_read(iochannel,
			  &cs->buffer[cs->n],
			  sizeof(cs->buffer) - cs->n,
			  &bytes_read);
    /* Check for socket error */
    if (e != G_IO_ERROR_NONE) {
      fprintf(stderr, "Client read error: %d\n", e);
      goto error;
    } else if (bytes_read == 0) {
      /* Check if we read 0 bytes, which means the connection
	 is closed */
      goto error;
    } else {
      g_assert(bytes_read > 0);
      /* If there isn't an out_watch, add one */
      if (cs->out_watch == 0) {
	cs->out_watch =
	  g_io_add_watch(iochannel,
			 G_IO_OUT,
			 async_client_iofunc,
			 cs);
      }
      fwrite(&cs->buffer[cs->n], bytes_read, 1, stdout);
      cs->n += bytes_read;
    }
  }
  if (c & G_IO_OUT) {
    GIOError e;
    guint bytes_written;
    /* Write the data out */
    e = g_io_channel_write(iochannel, cs->buffer, cs->n,
			   &bytes_written);
    if (e != G_IO_ERROR_NONE) {
      fprintf(stderr, "Client write error: %d\n", e);
      clientstate_delete(cs);
      return FALSE;
    } else if (bytes_written > 0) {
      /* Move the memory down some (you wouldn't do this
	 in a performance server because it's slow) */
      g_memmove(cs->buffer,
		&cs->buffer[bytes_written],
		bytes_written);
      cs->n -= bytes_written;
    }
    /* Check if done */
    if (cs->n == 0) {
      cs->out_watch = 0;
      return FALSE;
    }
  }
  return TRUE;

 error:
  clientstate_delete(cs);
  return FALSE;
}


syntax highlighted by Code2HTML, v. 0.9.1