/* GLIB - Library of useful routines for C programming
 * Copyright (C) 1995-1997  Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * giounix.c: IO Channels using unix file descriptors
 * Copyright 1998 Owen Taylor
 *
 * 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 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, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/*
 * Modified by the GLib Team and others 1997-2000.  See the AUTHORS
 * file for a list of people on the GLib Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
 * GLib at ftp://ftp.gtk.org/pub/gtk/. 
 */

/* 
 * MT safe
 */

#include "glib.h"
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>

/*
 * Unix IO Channels
 */

typedef struct _GIOUnixChannel GIOUnixChannel;
typedef struct _GIOUnixWatch GIOUnixWatch;

struct _GIOUnixChannel
{
  GIOChannel channel;
  gint fd;
};

struct _GIOUnixWatch
{
  GSource       source;
  GPollFD       pollfd;
  GIOChannel   *channel;
  GIOCondition  condition;
  GIOFunc       callback;
};


static GIOError g_io_unix_read         (GIOChannel   *channel,
					gchar        *buf,
					guint         count,
					guint        *bytes_written);
static GIOError g_io_unix_write        (GIOChannel   *channel,
					gchar        *buf,
					guint         count,
					guint        *bytes_written);
static GIOError g_io_unix_seek         (GIOChannel   *channel,
					gint          offset,
					GSeekType     type);
static void     g_io_unix_close        (GIOChannel   *channel);
static void     g_io_unix_free         (GIOChannel   *channel);
static GSource *g_io_unix_create_watch (GIOChannel   *channel,
					GIOCondition  condition);

static gboolean g_io_unix_prepare  (GSource     *source,
				    gint        *timeout);
static gboolean g_io_unix_check    (GSource     *source);
static gboolean g_io_unix_dispatch (GSource     *source,
				    GSourceFunc  callback,
				    gpointer     user_data);
static void     g_io_unix_destroy  (GSource     *source);

GSourceFuncs unix_watch_funcs = {
  g_io_unix_prepare,
  g_io_unix_check,
  g_io_unix_dispatch,
  g_io_unix_destroy
};

GIOFuncs unix_channel_funcs = {
  g_io_unix_read,
  g_io_unix_write,
  g_io_unix_seek,
  g_io_unix_close,
  g_io_unix_create_watch,
  g_io_unix_free,
};

static gboolean 
g_io_unix_prepare (GSource  *source,
		   gint     *timeout)
{
  *timeout = -1;
  return FALSE;
}

static gboolean 
g_io_unix_check (GSource  *source)
{
  GIOUnixWatch *watch = (GIOUnixWatch *)source;

  return (watch->pollfd.revents & watch->condition);
}

static gboolean
g_io_unix_dispatch (GSource     *source,
		    GSourceFunc  callback,
		    gpointer     user_data)

{
  GIOFunc func = (GIOFunc)callback;
  GIOUnixWatch *watch = (GIOUnixWatch *)source;

  if (!func)
    {
      g_warning ("IO watch dispatched without callback\n"
		 "You must call g_source_connect().");
      return FALSE;
    }
  
  return (*func) (watch->channel,
		  watch->pollfd.revents & watch->condition,
		  user_data);
}

static void 
g_io_unix_destroy (GSource *source)
{
  GIOUnixWatch *watch = (GIOUnixWatch *)source;

  g_io_channel_unref (watch->channel);
}

static GIOError 
g_io_unix_read (GIOChannel *channel, 
		gchar     *buf, 
		guint      count,
		guint     *bytes_read)
{
  GIOUnixChannel *unix_channel = (GIOUnixChannel *)channel;
  gint result;

  result = read (unix_channel->fd, buf, count);

  if (result < 0)
    {
      *bytes_read = 0;
      switch (errno)
	{
	case EINVAL:
	  return G_IO_ERROR_INVAL;
	case EAGAIN:
	case EINTR:
	  return G_IO_ERROR_AGAIN;
	default:
	  return G_IO_ERROR_UNKNOWN;
	}
    }
  else
    {
      *bytes_read = result;
      return G_IO_ERROR_NONE;
    }
}
		       
static GIOError 
g_io_unix_write(GIOChannel *channel, 
		gchar     *buf, 
		guint      count,
		guint     *bytes_written)
{
  GIOUnixChannel *unix_channel = (GIOUnixChannel *)channel;
  gint result;

  result = write (unix_channel->fd, buf, count);

  if (result < 0)
    {
      *bytes_written = 0;
      switch (errno)
	{
	case EINVAL:
	  return G_IO_ERROR_INVAL;
	case EAGAIN:
	case EINTR:
	  return G_IO_ERROR_AGAIN;
	default:
	  return G_IO_ERROR_UNKNOWN;
	}
    }
  else
    {
      *bytes_written = result;
      return G_IO_ERROR_NONE;
    }
}

static GIOError 
g_io_unix_seek (GIOChannel *channel,
		gint      offset, 
		GSeekType type)
{
  GIOUnixChannel *unix_channel = (GIOUnixChannel *)channel;
  int whence;
  off_t result;

  switch (type)
    {
    case G_SEEK_SET:
      whence = SEEK_SET;
      break;
    case G_SEEK_CUR:
      whence = SEEK_CUR;
      break;
    case G_SEEK_END:
      whence = SEEK_END;
      break;
    default:
      g_warning ("g_io_unix_seek: unknown seek type");
      return G_IO_ERROR_UNKNOWN;
    }

  result = lseek (unix_channel->fd, offset, whence);

  if (result < 0)
    {
      switch (errno)
	{
	case EINVAL:
	  return G_IO_ERROR_INVAL;
	default:
	  return G_IO_ERROR_UNKNOWN;
	}
    }
  else
    return G_IO_ERROR_NONE;
}


static void 
g_io_unix_close (GIOChannel *channel)
{
  GIOUnixChannel *unix_channel = (GIOUnixChannel *)channel;

  close (unix_channel->fd);
}

static void 
g_io_unix_free (GIOChannel *channel)
{
  GIOUnixChannel *unix_channel = (GIOUnixChannel *)channel;

  g_free (unix_channel);
}

static GSource *
g_io_unix_create_watch (GIOChannel   *channel,
			GIOCondition  condition)
{
  GIOUnixChannel *unix_channel = (GIOUnixChannel *)channel;
  GSource *source;
  GIOUnixWatch *watch;


  source = g_source_new (&unix_watch_funcs, sizeof (GIOUnixWatch));
  watch = (GIOUnixWatch *)source;
  
  watch->channel = channel;
  g_io_channel_ref (channel);
  
  watch->condition = condition;

  watch->pollfd.fd = unix_channel->fd;
  watch->pollfd.events = condition;

  g_source_add_poll (source, &watch->pollfd);

  return source;
}

GIOChannel *
g_io_channel_unix_new (gint fd)
{
  GIOUnixChannel *unix_channel = g_new (GIOUnixChannel, 1);
  GIOChannel *channel = (GIOChannel *)unix_channel;

  g_io_channel_init (channel);
  channel->funcs = &unix_channel_funcs;

  unix_channel->fd = fd;
  return channel;
}

gint
g_io_channel_unix_get_fd (GIOChannel *channel)
{
  GIOUnixChannel *unix_channel = (GIOUnixChannel *)channel;
  return unix_channel->fd;
}
