/* sane - Scanner Access Now Easy.
   Copyright (C) 1997 David Mosberger-Tang
   This file is part of the SANE package.

   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.

   As a special exception, the authors of SANE give permission for
   additional uses of the libraries contained in this release of SANE.

   The exception is that, if you link a SANE library with other files
   to produce an executable, this does not by itself cause the
   resulting executable to be covered by the GNU General Public
   License.  Your use of that executable is in no way restricted on
   account of linking the SANE library code into it.

   This exception does not, however, invalidate any other reasons why
   the executable file might be covered by the GNU General Public
   License.

   If you submit changes to SANE to the maintainers to be included in
   a subsequent release, you agree by submitting the changes that
   those changes may be distributed with this exception intact.

   If you write modifications of your own for SANE, it is your choice
   whether to permit this exception to apply to your modifications.
   If you do not wish that, delete this exception notice.

   This file implements a SANE network-based meta backend.  */

#ifdef _AIX
# include <lalloca.h>	/* MUST come first for AIX! */
#endif

#include <sane/config.h>
#include <lalloca.h>

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifdef HAVE_LIBC_H
# include <libc.h>	/* NeXTStep/OpenStep */
#endif

#include <sys/time.h>
#include <sys/types.h>

#include <netinet/in.h>
#include <netdb.h>	/* OS/2 needs this _after_ <netinet/in.h>, grrr... */

#include <sane/sane.h>
#include <sane/sanei.h>
#include <sane/sanei_net.h>
#include <sane/sanei_codec_bin.h>
#include <net.h>

#define BACKEND_NAME	net
#include <sane/sanei_backend.h>

#ifndef PATH_MAX
# define PATH_MAX	1024
#endif

#include <sane/sanei_config.h>
#define NET_CONFIG_FILE "net.conf"

static SANE_Auth_Callback auth_callback;
static Net_Device *first_device;
static Net_Scanner *first_handle;
static int saned_port;

static SANE_Status
add_device (const char *name, Net_Device **ndp)
{
  struct hostent *he;
  Net_Device *nd;

  DBG(1, "adding backend %s\n", name);

  he = gethostbyname (name);
  if (!he)
    {
      DBG(1, "can't get address of host %s\n", name);
      return SANE_STATUS_IO_ERROR;
    }

  if (he->h_addrtype != AF_INET)
    {
      DBG(1, "don't know how to deal with addr family %d\n", he->h_addrtype);
      return SANE_STATUS_INVAL;
    }

  nd = malloc (sizeof (*nd));
  if (!nd)
    return SANE_STATUS_NO_MEM;

  memset (nd, 0, sizeof (*nd));
  nd->name = strdup (name);
  if (!nd->name)
    return SANE_STATUS_NO_MEM;
  nd->addr.sa_family = he->h_addrtype;
  if (nd->addr.sa_family == AF_INET)
    {
      struct sockaddr_in *sin = (struct sockaddr_in *) &nd->addr;
      memcpy (&sin->sin_addr, he->h_addr_list[0], he->h_length);
    }

  nd->ctl = -1;
  nd->next = first_device;
  first_device = nd;
  if (ndp)
    *ndp = nd;
  return SANE_STATUS_GOOD;
}

static SANE_Status
connect_dev (Net_Device *dev)
{
  struct sockaddr_in *sin;
  SANE_Word version_code;
  SANE_Init_Reply reply;
  SANE_Status status;
  SANE_Init_Req req;
#ifdef TCP_NODELAY
  int on = 1;
  int level = -1;
#endif

  if (dev->addr.sa_family != AF_INET)
    {
      DBG(1, "connect_dev: don't know how to deal with addr family %d\n",
	  dev->addr.sa_family);
      return SANE_STATUS_IO_ERROR;
    }

  dev->ctl = socket (dev->addr.sa_family, SOCK_STREAM, 0);
  if (dev->ctl < 0)
    {
      DBG(1, "connect_dev: failed to obtain socket (%s)\n", strerror (errno));
      dev->ctl = -1;
      return SANE_STATUS_IO_ERROR;
    }
  sin = (struct sockaddr_in*) &dev->addr;
  sin->sin_port = saned_port;

  if (connect (dev->ctl, &dev->addr, sizeof (dev->addr)) < 0)
    {
      DBG(1, "connect_dev: failed to connect (%s)\n", strerror (errno));
      dev->ctl = -1;
      return SANE_STATUS_IO_ERROR;
    }

#ifdef TCP_NODELAY
# ifdef SOL_TCP
  level = SOL_TCP;
# else /* !SOL_TCP */
  /* Look up the protocol level in the protocols database. */
  {
    struct protoent *p;
    p = getprotobyname ("tcp");
    if (p == 0)
      DBG(1, "connect_dev: cannot look up `tcp' protocol number");
    else
      level = p->p_proto;
  }
# endif /* SOL_TCP */

  if (level == -1 ||
      setsockopt (dev->ctl, level, TCP_NODELAY, &on, sizeof (on)))
    DBG(1, "connect_dev: failed to put send socket in TCP_NODELAY mode (%s)",
	strerror (errno));
#endif /* !TCP_NODELAY */

  sanei_w_init (&dev->wire, sanei_codec_bin_init);
  dev->wire.io.fd = dev->ctl;
  dev->wire.io.read = read;
  dev->wire.io.write = write;

  /* exchange version codes with the server: */
  req.version_code = SANE_VERSION_CODE (V_MAJOR, V_MINOR,
					SANEI_NET_PROTOCOL_VERSION);
  req.username = getlogin ();
  sanei_w_call (&dev->wire, SANE_NET_INIT,
		(WireCodecFunc) sanei_w_init_req, &req,
		(WireCodecFunc) sanei_w_init_reply, &reply);

  if (dev->wire.status != 0)
    {
      DBG(1, "connect_dev: argument marshalling error (%s)\n",
	  strerror (dev->wire.status));
      goto fail;
    }

  status = reply.status;
  version_code = reply.version_code;

  sanei_w_free (&dev->wire, (WireCodecFunc) sanei_w_init_reply, &reply);

  if (SANE_VERSION_MAJOR(version_code) != V_MAJOR)
    {
      DBG(1, "connect_dev: major version mismatch: got %d, expected %d\n",
	  SANE_VERSION_MAJOR(version_code), V_MAJOR);
      goto fail;
    }
  if (SANE_VERSION_BUILD(version_code) != SANEI_NET_PROTOCOL_VERSION
      && SANE_VERSION_BUILD(version_code) != 2)
    {
      DBG(1, "connect_dev: network protocol version mismatch: "
	  "got %d, expected %d\n",
	  SANE_VERSION_BUILD(version_code), SANEI_NET_PROTOCOL_VERSION);
      goto fail;
    }
  dev->wire.version = SANE_VERSION_BUILD (version_code);
  return SANE_STATUS_GOOD;

fail:
  close (dev->ctl);
  dev->ctl = -1;
  return SANE_STATUS_IO_ERROR;
}

static SANE_Status
fetch_options (Net_Scanner *s)
{
  if (s->opt.num_options)
    {
      sanei_w_set_dir (&s->hw->wire, WIRE_FREE);
      s->hw->wire.status = 0;
      sanei_w_option_descriptor_array (&s->hw->wire, &s->opt);
      if (s->hw->wire.status)
	return SANE_STATUS_IO_ERROR;
    }
  sanei_w_call (&s->hw->wire, SANE_NET_GET_OPTION_DESCRIPTORS,
		(WireCodecFunc) sanei_w_word, &s->handle,
		(WireCodecFunc) sanei_w_option_descriptor_array, &s->opt);
  if (s->hw->wire.status)
    return SANE_STATUS_IO_ERROR;

  s->options_valid = 1;
  return SANE_STATUS_GOOD;
}

static SANE_Status
do_cancel (Net_Scanner *s)
{
  s->hw->auth_active = 0;
  if (s->data >= 0)
    {
      close (s->data);
      s->data = -1;
    }
  return SANE_STATUS_CANCELLED;
}

static void
do_authorization (Net_Device *dev, SANE_String resource)
{
  SANE_Authorization_Req req;
  SANE_Char username[SANE_MAX_USERNAME_LEN];
  SANE_Char password[SANE_MAX_PASSWORD_LEN];

  dev->auth_active = 1;

  memset (&req, 0, sizeof (req));

  if (auth_callback)
    (*auth_callback) (resource, username, password);

  if (dev->auth_active)
    {
      SANE_Word ack;

      req.resource = resource;
      req.username = username;
      req.password = password;
      sanei_w_call (&dev->wire, SANE_NET_AUTHORIZE,
		    (WireCodecFunc) sanei_w_authorization_req, &req,
		    (WireCodecFunc) sanei_w_word, &ack);
    }
}

SANE_Status
sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
{
  char device_name[PATH_MAX];
  struct servent *serv;
  const char *env;
  size_t len;
  FILE *fp;

  DBG_INIT();

  auth_callback = authorize;

  if (version_code)
    *version_code = SANE_VERSION_CODE (V_MAJOR, V_MINOR, 0);

  serv = getservbyname ("sane", "tcp");
  if (serv)
    saned_port = serv->s_port;
  else
    {
      saned_port = htons (6566);
      DBG(1,
	  "init: could not find `sane' service (%s); using default port %d\n",
	  strerror (errno), htons (saned_port));
    }

  fp = sanei_config_open (NET_CONFIG_FILE);
  if (fp)
    {
      while (fgets (device_name, sizeof (device_name), fp))
	{
	  if (device_name[0] == '#')	/* ignore line comments */
	    continue;
	  len = strlen (device_name);
	  if (device_name[len - 1] == '\n')
	    device_name[--len] = '\0';

	  if (!len)
	    continue;		/* ignore empty lines */

	  add_device (device_name, 0);
	}
      fclose (fp);
    }

  env = getenv ("SANE_NET_HOSTS");
  if (env)
    {
      char *copy, *next, *host;
      copy = strdup (env);
      next = copy;
      while ((host = strsep (&next, ":")))
	add_device (host, 0);
      free (copy);
    }

  return SANE_STATUS_GOOD;
}

void
sane_exit (void)
{
  Net_Scanner *handle, *next_handle;
  Net_Device *dev, *next_device;

  DBG(1, "exiting\n");

  /* first, close all handles: */
  for (handle = first_handle; handle; handle = next_handle)
    {
      next_handle = handle->next;
      sane_close (handle);
    }
  first_handle = 0;

  /* now close all devices: */
  for (dev = first_device; dev; dev = next_device)
    {
      next_device = dev->next;

      DBG(2, "closing dev %p, ctl=%d\n", dev, dev->ctl);

      if (dev->ctl >= 0)
	{
	  sanei_w_call (&dev->wire, SANE_NET_EXIT,
			(WireCodecFunc) sanei_w_void, 0,
			(WireCodecFunc) sanei_w_void, 0);
	  close (dev->ctl);
	}
      free (dev);
    }
}

/* Note that a call to get_devices() implies that we'll have to
   connect to all remote hosts.  To avoid this, you can call
   sane_open() directly (assuming you know the name of the
   backend/device).  This is appropriate for the command-line
   interface of SANE, for example.
 */
SANE_Status
sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
{
  static int devlist_size = 0, devlist_len = 0;
  static const SANE_Device **devlist;
  static const SANE_Device *empty_devlist[1] = {0};
  SANE_Get_Devices_Reply reply;
  SANE_Status status;
  Net_Device *dev;
  char *full_name;
  int i, num_devs;
  size_t len;
#define ASSERT_SPACE(n)							   \
  {									   \
    if (devlist_len + (n) > devlist_size)				   \
      {									   \
	devlist_size += (n) + 15;					   \
	if (devlist)							   \
	  devlist = realloc (devlist, devlist_size * sizeof (devlist[0])); \
	else								   \
	  devlist = malloc (devlist_size * sizeof (devlist[0]));	   \
	if (!devlist)							   \
	  return SANE_STATUS_NO_MEM;					   \
      }									   \
  }

  if (local_only)
    {
      *device_list = empty_devlist;
      return SANE_STATUS_GOOD;
    }

  if (devlist)
    for (i = 0; i < devlist_len; ++i)
      free ((void *) devlist[i]);
  devlist_len = 0;

  for (dev = first_device; dev; dev = dev->next)
    {
      if (dev->ctl < 0)
	{
	  status = connect_dev (dev);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG(1, "get_devices: ignoring failure to connect to %s\n",
		  dev->name);
	      continue;
	    }
	}

      sanei_w_call (&dev->wire, SANE_NET_GET_DEVICES,
		    (WireCodecFunc) sanei_w_void, 0,
		    (WireCodecFunc) sanei_w_get_devices_reply, &reply);
      if (reply.status != SANE_STATUS_GOOD)
	{
	  DBG(1, "get_devices: ignoring rpc-returned status %s\n",
	      sane_strstatus (reply.status));
	  sanei_w_free (&dev->wire,
			(WireCodecFunc) sanei_w_get_devices_reply, &reply);
	  continue;
	}

      /* count the number of devices for this backend: */
      for (num_devs = 0; reply.device_list[num_devs]; ++num_devs);

      ASSERT_SPACE (num_devs);

      for (i = 0; i < num_devs; ++i)
	{
	  SANE_Device *rdev;
	  char *mem;

	  /* create a new device entry with a device name that is the
	     sum of the backend name a colon and the backend's device
	     name: */
	  len = strlen (dev->name) + 1 + strlen (reply.device_list[i]->name);
	  mem = malloc (sizeof (*dev) + len + 1);
	  if (!mem)
	    return SANE_STATUS_NO_MEM;

	  full_name = mem + sizeof (*dev);
	  strcpy (full_name, dev->name);
	  strcat (full_name, ":");
	  strcat (full_name, reply.device_list[i]->name);

	  rdev = (SANE_Device *) mem;
	  rdev->name = full_name;
	  rdev->vendor = strdup (reply.device_list[i]->vendor);
	  rdev->model = strdup (reply.device_list[i]->model);
	  rdev->type = strdup (reply.device_list[i]->type);

	  devlist[devlist_len++] = rdev;
	}
      /* now free up the rpc return value: */
      sanei_w_free (&dev->wire,
		    (WireCodecFunc) sanei_w_get_devices_reply, &reply);
    }

  /* terminate device list with NULL entry: */
  ASSERT_SPACE (1);
  devlist[devlist_len++] = 0;

  *device_list = devlist;
  return SANE_STATUS_GOOD;
}

SANE_Status
sane_open (SANE_String_Const full_name, SANE_Handle * meta_handle)
{
  SANE_Open_Reply reply;
  const char *dev_name;
  SANE_String nd_name;
  SANE_Status status;
  SANE_Word handle;
  Net_Device *dev;
  Net_Scanner *s;
  int need_auth;

  DBG(3, "open(\"%s\")\n", full_name);

  dev_name = strchr (full_name, ':');
  if (dev_name)
    {
#ifdef strndupa
      nd_name = strndupa (full_name, dev_name - full_name);
#else
      char *tmp;

      tmp = alloca (dev_name - full_name + 1);
      memcpy (tmp, full_name, dev_name - full_name);
      tmp[dev_name - full_name] = '\0';
      nd_name = tmp;
#endif
      ++dev_name;		/* skip colon */
    }
  else
    {
      /* if no colon interpret full_name as the host name; an empty
         device name will cause us to open the first device of that
         host.  */
      nd_name = (char *) full_name;
      dev_name = "";
    }

  if (!nd_name[0])
    /* Unlike other backends, we never allow an empty backend-name.
       Otherwise, it's possible that sane_open("") will result in
       endless looping (consider the case where NET is the first
       backend...)*/
    return SANE_STATUS_INVAL;
  else
    for (dev = first_device; dev; dev = dev->next)
      if (strcmp (dev->name, nd_name) == 0)
	break;

  if (!dev)
    {
      status = add_device (nd_name, &dev);
      if (status != SANE_STATUS_GOOD)
	return status;
    }

  if (dev->ctl < 0)
    {
      status = connect_dev (dev);
      if (status != SANE_STATUS_GOOD)
	return status;
    }

  do
    {
      sanei_w_call (&dev->wire, SANE_NET_OPEN,
		    (WireCodecFunc) sanei_w_string, &dev_name,
		    (WireCodecFunc) sanei_w_open_reply, &reply);
      if (dev->wire.status != 0)
	{
	  DBG(1, "open rpc call failed (%s)\n", strerror (dev->wire.status));
	  return SANE_STATUS_IO_ERROR;
	}

      status = reply.status;
      handle = reply.handle;
      need_auth = (reply.resource_to_authorize != 0);

      if (need_auth)
	do_authorization (dev, reply.resource_to_authorize);

      sanei_w_free (&dev->wire, (WireCodecFunc) sanei_w_open_reply, &reply);

      if (need_auth && !dev->auth_active)
	return SANE_STATUS_CANCELLED;

      if (status != SANE_STATUS_GOOD)
	{
	  DBG(1, "remote open failed\n");
	  return reply.status;
	}
    }
  while (need_auth);

  s = malloc (sizeof (*s));
  if (!s)
    return SANE_STATUS_NO_MEM;

  memset (s, 0, sizeof (*s));
  s->hw = dev;
  s->handle = handle;
  s->data = -1;
  s->next = first_handle;
  first_handle = s;
  *meta_handle = s;

  return SANE_STATUS_GOOD;
}

void
sane_close (SANE_Handle handle)
{
  Net_Scanner *prev, *s;
  SANE_Word ack;

  prev = 0;
  for (s = first_handle; s; s = s->next)
    {
      if (s == handle)
	break;
      prev = s;
    }
  if (!s)
    {
      DBG(1, "close: invalid handle %p\n", handle);
      return;		/* oops, not a handle we know about */
    }
  if (prev)
    prev->next = s->next;
  else
    first_handle = s->next;

  sanei_w_call (&s->hw->wire, SANE_NET_CLOSE,
		(WireCodecFunc) sanei_w_word, &s->handle,
		(WireCodecFunc) sanei_w_word, &ack);
  if (s->data >= 0)
    close (s->data);
  free (s);
}

const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
{
  Net_Scanner *s = handle;
  SANE_Status status;

  if (!s->options_valid)
    {
      status = fetch_options (s);
      if (status != SANE_STATUS_GOOD)
	return 0;
    }

  if ((unsigned) option >= s->opt.num_options)
    return 0;
  return s->opt.desc[option];
}

SANE_Status
sane_control_option (SANE_Handle handle, SANE_Int option,
		     SANE_Action action, void *value, SANE_Word * info)
{
  Net_Scanner *s = handle;
  SANE_Control_Option_Req req;
  SANE_Control_Option_Reply reply;
  SANE_Status status;
  size_t value_size;
  int need_auth;

  if (!s->options_valid)
    {
      status = fetch_options (s);
      if (status != SANE_STATUS_GOOD)
	return status;
    }
  if ((unsigned) option >= s->opt.num_options)
    return SANE_STATUS_INVAL;

  switch (s->opt.desc[option]->type)
    {
    case SANE_TYPE_BUTTON:
    case SANE_TYPE_GROUP:	/* shouldn't happen... */
      /* the SANE standard defines that the option size of a BUTTON or
	 GROUP is IGNORED.  */
      value_size = 0;
      break;

    default:
      value_size = s->opt.desc[option]->size;
      break;
    }

  req.handle = s->handle;
  req.option = option;
  req.action = action;
  req.value_type = s->opt.desc[option]->type;
  req.value_size = value_size;
  req.value = value;

  do
    {
      sanei_w_call (&s->hw->wire, SANE_NET_CONTROL_OPTION,
		    (WireCodecFunc) sanei_w_control_option_req, &req,
		    (WireCodecFunc) sanei_w_control_option_reply, &reply);
      status = reply.status;
      need_auth = (reply.resource_to_authorize != 0);
      if (need_auth)
	do_authorization (s->hw, reply.resource_to_authorize);
      else if (status == SANE_STATUS_GOOD)
	{
	  if (info)
	    *info = reply.info;
	  if (value_size > 0)
	    {
	      if (value_size == reply.value_size)
		memcpy (value, reply.value, reply.value_size);
	      else
		DBG(1, "control_option: size changed from %d to %d\n",
		    s->opt.desc[option]->size, reply.value_size);
	    }

	  if (reply.info & SANE_INFO_RELOAD_OPTIONS)
	    s->options_valid = 0;
	}
      sanei_w_free (&s->hw->wire,
		    (WireCodecFunc) sanei_w_control_option_reply, &reply);
      if (need_auth && !s->hw->auth_active)
	return SANE_STATUS_CANCELLED;
    }
  while (need_auth);
  return status;
}

SANE_Status
sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
{
  Net_Scanner *s = handle;
  SANE_Get_Parameters_Reply reply;
  SANE_Status status;

  if (!params)
    return SANE_STATUS_INVAL;

  sanei_w_call (&s->hw->wire, SANE_NET_GET_PARAMETERS,
		(WireCodecFunc) sanei_w_word, &s->handle,
		(WireCodecFunc) sanei_w_get_parameters_reply, &reply);

  status = reply.status;
  *params = reply.params;
  sanei_w_free (&s->hw->wire,
		(WireCodecFunc) sanei_w_get_parameters_reply, &reply);

  return status;
}

SANE_Status
sane_start (SANE_Handle handle)
{
  Net_Scanner *s = handle;
  SANE_Start_Reply reply;
  struct sockaddr_in sin;
  SANE_Status status;
  int fd, need_auth;
  int len;
  short port;		/* Internet-specific */

  if (s->data >= 0)
    return SANE_STATUS_INVAL;

  /* Do this ahead of time so in case anything fails, we can
     recover gracefully (without hanging our server).  */
  len = sizeof (sin);
  if (getpeername (s->hw->ctl, (struct sockaddr *) &sin, &len) < 0)
    {
      DBG(1, "start: getpeername() failed (%s)\n", strerror (errno));
      return SANE_STATUS_IO_ERROR;
    }

  fd = socket (s->hw->addr.sa_family, SOCK_STREAM, 0);
  if (fd < 0)
    {
      DBG(1, "start: socket() failed (%s)\n", strerror (errno));
      return SANE_STATUS_IO_ERROR;
    }

  do
    {
      sanei_w_call (&s->hw->wire, SANE_NET_START,
		    (WireCodecFunc) sanei_w_word, &s->handle,
		    (WireCodecFunc) sanei_w_start_reply, &reply);

      status = reply.status;
      port = reply.port;
      need_auth = (reply.resource_to_authorize != 0);
      if (need_auth)
	do_authorization (s->hw, reply.resource_to_authorize);
      sanei_w_free (&s->hw->wire, (WireCodecFunc) sanei_w_start_reply, &reply);
      if (need_auth && !s->hw->auth_active)
	return SANE_STATUS_CANCELLED;

      if (status != SANE_STATUS_GOOD)
	{
	  close (fd);
	  return status;
	}
    }
  while (need_auth);

  sin.sin_port = htons (port);

  if (connect (fd, (struct sockaddr *) &sin, len) < 0)
    {
      DBG(1, "start: connect() failed (%s)\n", strerror (errno));
      close (fd);
      return SANE_STATUS_IO_ERROR;
    }
  shutdown (fd, 1);
  s->data = fd;
  s->reclen_buf_offset = 0;
  s->bytes_remaining = 0;
  return status;
}

SANE_Status
sane_read (SANE_Handle handle, SANE_Byte * data, SANE_Int max_length,
	   SANE_Int * length)
{
  Net_Scanner *s = handle;
  ssize_t nread;

  if (s->data < 0)
    return SANE_STATUS_CANCELLED;

  *length = 0;

  if (s->data < 0)
    return SANE_STATUS_INVAL;

  if (s->bytes_remaining == 0)
    {
      /* boy, is this painful or what? */

      nread = read (s->data, s->reclen_buf + s->reclen_buf_offset,
		    4 - s->reclen_buf_offset);
      if (nread < 0)
	{
	  if (errno == EAGAIN)
	    return SANE_STATUS_GOOD;
	  else
	    {
	      do_cancel (s);
	      return SANE_STATUS_IO_ERROR;
	    }
	}
      s->reclen_buf_offset += nread;
      if (s->reclen_buf_offset < 4)
	return SANE_STATUS_GOOD;

      s->reclen_buf_offset = 0;
      s->bytes_remaining = (  ((u_long) s->reclen_buf[0] << 24)
			    | ((u_long) s->reclen_buf[1] << 16)
			    | ((u_long) s->reclen_buf[2] <<  8)
			    | ((u_long) s->reclen_buf[3] <<  0));
      DBG(3, "read: next record length=%ld bytes\n",
	  (long) s->bytes_remaining);
      if (s->bytes_remaining == 0xffffffff)
	{
	  char ch;

	  /* turn off non-blocking I/O (s->data will be closed anyhow): */
	  fcntl (s->data, F_SETFL, 0);

	  /* read the status byte: */
	  if (read (s->data, &ch, sizeof (ch)) != 1)
	    ch = SANE_STATUS_IO_ERROR;
	  do_cancel (s);
	  return (SANE_Status) ch;
	}
    }

  if (max_length > s->bytes_remaining)
    max_length = s->bytes_remaining;

  nread = read (s->data, data, max_length);
  if (nread < 0)
    {
      if (errno == EAGAIN)
	return SANE_STATUS_GOOD;
      else
	{
	  do_cancel (s);
	  return SANE_STATUS_IO_ERROR;
	}
    }
  s->bytes_remaining -= nread;
  *length = nread;
  return SANE_STATUS_GOOD;
}

void
sane_cancel (SANE_Handle handle)
{
  Net_Scanner *s = handle;
  SANE_Word ack;

  sanei_w_call (&s->hw->wire, SANE_NET_CANCEL,
		(WireCodecFunc) sanei_w_word, &s->handle,
		(WireCodecFunc) sanei_w_word, &ack);
  do_cancel (s);
}

SANE_Status
sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
{
  Net_Scanner *s = handle;

  if (s->data < 0)
    return SANE_STATUS_INVAL;

  if (fcntl (s->data, F_SETFL, non_blocking ? O_NONBLOCK : 0) < 0)
    return SANE_STATUS_IO_ERROR;

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_get_select_fd (SANE_Handle handle, SANE_Int *fd)
{
  Net_Scanner *s = handle;

  if (s->data < 0)
    return SANE_STATUS_INVAL;

  *fd = s->data;
  return SANE_STATUS_GOOD;
}
