summaryrefslogtreecommitdiff
path: root/backend/net.c
diff options
context:
space:
mode:
Diffstat (limited to 'backend/net.c')
-rw-r--r--backend/net.c2378
1 files changed, 2378 insertions, 0 deletions
diff --git a/backend/net.c b/backend/net.c
new file mode 100644
index 0000000..16fba2f
--- /dev/null
+++ b/backend/net.c
@@ -0,0 +1,2378 @@
+/* sane - Scanner Access Now Easy.
+ Copyright (C) 1997 David Mosberger-Tang
+ Copyright (C) 2003, 2008 Julien BLACHE <jb@jblache.org>
+ AF-independent code + IPv6, Avahi support
+
+ 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 "../include/lalloca.h" /* MUST come first for AIX! */
+#endif
+
+#include "../include/sane/config.h"
+#include "../include/lalloca.h"
+#include "../include/_stdint.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... */
+
+#ifdef WITH_AVAHI
+# include <avahi-client/client.h>
+# include <avahi-client/lookup.h>
+
+# include <avahi-common/thread-watch.h>
+# include <avahi-common/malloc.h>
+# include <avahi-common/error.h>
+
+# define SANED_SERVICE_DNS "_sane-port._tcp"
+
+static AvahiClient *avahi_client = NULL;
+static AvahiThreadedPoll *avahi_thread = NULL;
+static AvahiServiceBrowser *avahi_browser = NULL;
+#endif /* WITH_AVAHI */
+
+#include "../include/sane/sane.h"
+#include "../include/sane/sanei.h"
+#include "../include/sane/sanei_net.h"
+#include "../include/sane/sanei_codec_bin.h"
+#include "net.h"
+
+#define BACKEND_NAME net
+#include "../include/sane/sanei_backend.h"
+
+#ifndef PATH_MAX
+# define PATH_MAX 1024
+#endif
+
+#include "../include/sane/sanei_config.h"
+#define NET_CONFIG_FILE "net.conf"
+
+/* Please increase version number with every change
+ (don't forget to update net.desc) */
+
+/* define the version string depending on which network code is used */
+#if defined (HAVE_GETADDRINFO) && defined (HAVE_GETNAMEINFO)
+# define NET_USES_AF_INDEP
+# ifdef ENABLE_IPV6
+# define NET_VERSION "1.0.14 (AF-indep+IPv6)"
+# else
+# define NET_VERSION "1.0.14 (AF-indep)"
+# endif /* ENABLE_IPV6 */
+#else
+# undef ENABLE_IPV6
+# define NET_VERSION "1.0.14"
+#endif /* HAVE_GETADDRINFO && HAVE_GETNAMEINFO */
+
+static SANE_Auth_Callback auth_callback;
+static Net_Device *first_device;
+static Net_Scanner *first_handle;
+static const SANE_Device **devlist;
+static int client_big_endian; /* 1 == big endian; 0 == little endian */
+static int server_big_endian; /* 1 == big endian; 0 == little endian */
+static int depth; /* bits per pixel */
+static int connect_timeout = -1; /* timeout for connection to saned */
+
+#ifndef NET_USES_AF_INDEP
+static int saned_port;
+#endif /* !NET_USES_AF_INDEP */
+
+/* This variable is only needed, if the depth is 16bit/channel and
+ client/server have different endianness. A value of -1 means, that there's
+ no hang over; otherwise the value has to be casted to SANE_Byte. hang_over
+ means, that there is a remaining byte from a previous call to sane_read,
+ which could not be byte-swapped, e.g. because the frontend requested an odd
+ number of bytes.
+*/
+static int hang_over;
+
+/* This variable is only needed, if the depth is 16bit/channel and
+ client/server have different endianness. A value of -1 means, that there's
+ no left over; otherwise the value has to be casted to SANE_Byte. left_over
+ means, that there is a remaining byte from a previous call to sane_read,
+ which already is in the the correct byte order, but could not be returned,
+ e.g. because the frontend requested only one byte per call.
+*/
+static int left_over;
+
+
+#ifdef NET_USES_AF_INDEP
+static SANE_Status
+add_device (const char *name, Net_Device ** ndp)
+{
+ struct addrinfo hints;
+ struct addrinfo *res;
+ struct addrinfo *resp;
+ struct sockaddr_in *sin;
+#ifdef ENABLE_IPV6
+ struct sockaddr_in6 *sin6;
+#endif /* ENABLE_IPV6 */
+
+ Net_Device *nd = NULL;
+
+ int error;
+ short sane_port = htons (6566);
+
+ DBG (1, "add_device: adding backend %s\n", name);
+
+ for (nd = first_device; nd; nd = nd->next)
+ if (strcmp (nd->name, name) == 0)
+ {
+ DBG (1, "add_device: already in list\n");
+
+ if (ndp)
+ *ndp = nd;
+
+ return SANE_STATUS_GOOD;
+ }
+
+ memset (&hints, 0, sizeof(hints));
+
+# ifdef ENABLE_IPV6
+ hints.ai_family = PF_UNSPEC;
+# else
+ hints.ai_family = PF_INET;
+# endif /* ENABLE_IPV6 */
+
+ error = getaddrinfo (name, "sane-port", &hints, &res);
+ if (error)
+ {
+ error = getaddrinfo (name, NULL, &hints, &res);
+ if (error)
+ {
+ DBG (1, "add_device: error while getting address of host %s: %s\n",
+ name, gai_strerror (error));
+
+ return SANE_STATUS_IO_ERROR;
+ }
+ else
+ {
+ for (resp = res; resp != NULL; resp = resp->ai_next)
+ {
+ switch (resp->ai_family)
+ {
+ case AF_INET:
+ sin = (struct sockaddr_in *) resp->ai_addr;
+ sin->sin_port = sane_port;
+ break;
+#ifdef ENABLE_IPV6
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) resp->ai_addr;
+ sin6->sin6_port = sane_port;
+ break;
+#endif /* ENABLE_IPV6 */
+ }
+ }
+ }
+ }
+
+ nd = malloc (sizeof (Net_Device));
+ if (!nd)
+ {
+ DBG (1, "add_device: not enough memory for Net_Device struct\n");
+
+ freeaddrinfo (res);
+ return SANE_STATUS_NO_MEM;
+ }
+
+ memset (nd, 0, sizeof (Net_Device));
+ nd->name = strdup (name);
+ if (!nd->name)
+ {
+ DBG (1, "add_device: not enough memory to duplicate name\n");
+ free(nd);
+ return SANE_STATUS_NO_MEM;
+ }
+
+ nd->addr = res;
+ nd->ctl = -1;
+
+ nd->next = first_device;
+
+ first_device = nd;
+
+ if (ndp)
+ *ndp = nd;
+ DBG (2, "add_device: backend %s added\n", name);
+ return SANE_STATUS_GOOD;
+}
+
+#else /* !NET_USES_AF_INDEP */
+
+static SANE_Status
+add_device (const char *name, Net_Device ** ndp)
+{
+ struct hostent *he;
+ Net_Device *nd;
+ struct sockaddr_in *sin;
+
+ DBG (1, "add_device: adding backend %s\n", name);
+
+ for (nd = first_device; nd; nd = nd->next)
+ if (strcmp (nd->name, name) == 0)
+ {
+ DBG (1, "add_device: already in list\n");
+
+ if (ndp)
+ *ndp = nd;
+
+ return SANE_STATUS_GOOD;
+ }
+
+ he = gethostbyname (name);
+ if (!he)
+ {
+ DBG (1, "add_device: can't get address of host %s\n", name);
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ if (he->h_addrtype != AF_INET)
+ {
+ DBG (1, "add_device: don't know how to deal with addr family %d\n",
+ he->h_addrtype);
+ return SANE_STATUS_INVAL;
+ }
+
+ nd = malloc (sizeof (*nd));
+ if (!nd)
+ {
+ DBG (1, "add_device: not enough memory for Net_Device struct\n");
+ return SANE_STATUS_NO_MEM;
+ }
+
+ memset (nd, 0, sizeof (*nd));
+ nd->name = strdup (name);
+ if (!nd->name)
+ {
+ DBG (1, "add_device: not enough memory to duplicate name\n");
+ free (nd);
+ return SANE_STATUS_NO_MEM;
+ }
+ nd->addr.sa_family = he->h_addrtype;
+
+ 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;
+ DBG (2, "add_device: backend %s added\n", name);
+ return SANE_STATUS_GOOD;
+}
+#endif /* NET_USES_AF_INDEP */
+
+
+#ifdef NET_USES_AF_INDEP
+static SANE_Status
+connect_dev (Net_Device * dev)
+{
+ struct addrinfo *addrp;
+
+ SANE_Word version_code;
+ SANE_Init_Reply reply;
+ SANE_Status status = SANE_STATUS_IO_ERROR;
+ SANE_Init_Req req;
+ SANE_Bool connected = SANE_FALSE;
+#ifdef TCP_NODELAY
+ int on = 1;
+ int level = -1;
+#endif
+ struct timeval tv;
+
+ int i;
+
+ DBG (2, "connect_dev: trying to connect to %s\n", dev->name);
+
+ for (addrp = dev->addr, i = 0; (addrp != NULL) && (connected == SANE_FALSE); addrp = addrp->ai_next, i++)
+ {
+# ifdef ENABLE_IPV6
+ if ((addrp->ai_family != AF_INET) && (addrp->ai_family != AF_INET6))
+# else /* !ENABLE_IPV6 */
+ if (addrp->ai_family != AF_INET)
+# endif /* ENABLE_IPV6 */
+ {
+ DBG (1, "connect_dev: [%d] don't know how to deal with addr family %d\n",
+ i, addrp->ai_family);
+ continue;
+ }
+
+ dev->ctl = socket (addrp->ai_family, SOCK_STREAM, 0);
+ if (dev->ctl < 0)
+ {
+ DBG (1, "connect_dev: [%d] failed to obtain socket (%s)\n",
+ i, strerror (errno));
+ dev->ctl = -1;
+ continue;
+ }
+
+ /* Set SO_SNDTIMEO for the connection to saned */
+ if (connect_timeout > 0)
+ {
+ tv.tv_sec = connect_timeout;
+ tv.tv_usec = 0;
+
+ if (setsockopt (dev->ctl, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0)
+ {
+ DBG (1, "connect_dev: [%d] failed to set SO_SNDTIMEO (%s)\n", i, strerror (errno));
+ }
+ }
+
+ if (connect (dev->ctl, addrp->ai_addr, addrp->ai_addrlen) < 0)
+ {
+ DBG (1, "connect_dev: [%d] failed to connect (%s)\n", i, strerror (errno));
+ dev->ctl = -1;
+ continue;
+ }
+ DBG (3, "connect_dev: [%d] connection succeeded (%s)\n", i, (addrp->ai_family == AF_INET6) ? "IPv6" : "IPv4");
+ dev->addr_used = addrp;
+ connected = SANE_TRUE;
+ }
+
+ if (connected != SANE_TRUE)
+ {
+ DBG (1, "connect_dev: couldn't connect to host (see messages above)\n");
+ return SANE_STATUS_IO_ERROR;
+ }
+
+#else /* !NET_USES_AF_INDEP */
+
+static SANE_Status
+connect_dev (Net_Device * dev)
+{
+ struct sockaddr_in *sin;
+ SANE_Word version_code;
+ SANE_Init_Reply reply;
+ SANE_Status status = SANE_STATUS_IO_ERROR;
+ SANE_Init_Req req;
+#ifdef TCP_NODELAY
+ int on = 1;
+ int level = -1;
+#endif
+ struct timeval tv;
+
+ DBG (2, "connect_dev: trying to connect to %s\n", dev->name);
+
+ 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;
+
+
+ /* Set SO_SNDTIMEO for the connection to saned */
+ if (connect_timeout > 0)
+ {
+ tv.tv_sec = connect_timeout;
+ tv.tv_usec = 0;
+
+ if (setsockopt (dev->ctl, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0)
+ {
+ DBG (1, "connect_dev: failed to set SO_SNDTIMEO (%s)\n", strerror (errno));
+ }
+ }
+
+ 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;
+ }
+ DBG (3, "connect_dev: connection succeeded\n");
+#endif /* NET_USES_AF_INDEP */
+
+ /* We're connected now, so reset SO_SNDTIMEO to the default value of 0 */
+ if (connect_timeout > 0)
+ {
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+
+ if (setsockopt (dev->ctl, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0)
+ {
+ DBG (1, "connect_dev: failed to reset SO_SNDTIMEO (%s)\n", strerror (errno));
+ }
+ }
+
+#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 */
+
+ DBG (2, "connect_dev: sanei_w_init\n");
+ 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 ();
+ DBG (2, "connect_dev: net_init (user=%s, local version=%d.%d.%d)\n",
+ req.username, V_MAJOR, V_MINOR, SANEI_NET_PROTOCOL_VERSION);
+ 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));
+ status = SANE_STATUS_IO_ERROR;
+ goto fail;
+ }
+
+ status = reply.status;
+ version_code = reply.version_code;
+ DBG (2, "connect_dev: freeing init reply (status=%s, remote "
+ "version=%d.%d.%d)\n", sane_strstatus (status),
+ SANE_VERSION_MAJOR (version_code),
+ SANE_VERSION_MINOR (version_code), SANE_VERSION_BUILD (version_code));
+ sanei_w_free (&dev->wire, (WireCodecFunc) sanei_w_init_reply, &reply);
+
+ if (status != 0)
+ {
+ DBG (1, "connect_dev: access to %s denied\n", dev->name);
+ goto fail;
+ }
+ 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);
+ status = SANE_STATUS_IO_ERROR;
+ 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);
+ status = SANE_STATUS_IO_ERROR;
+ goto fail;
+ }
+ dev->wire.version = SANE_VERSION_BUILD (version_code);
+ DBG (4, "connect_dev: done\n");
+ return SANE_STATUS_GOOD;
+
+fail:
+ DBG (2, "connect_dev: closing connection to %s\n", dev->name);
+ close (dev->ctl);
+ dev->ctl = -1;
+ return status;
+}
+
+
+static SANE_Status
+fetch_options (Net_Scanner * s)
+{
+ int option_number;
+ DBG (3, "fetch_options: %p\n", (void *) s);
+
+ if (s->opt.num_options)
+ {
+ DBG (2, "fetch_options: %d option descriptors cached... freeing\n",
+ 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)
+ {
+ DBG (1, "fetch_options: failed to free old list (%s)\n",
+ strerror (s->hw->wire.status));
+ return SANE_STATUS_IO_ERROR;
+ }
+ }
+ DBG (3, "fetch_options: get_option_descriptors\n");
+ 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)
+ {
+ DBG (1, "fetch_options: failed to get option descriptors (%s)\n",
+ strerror (s->hw->wire.status));
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ if (s->local_opt.num_options == 0)
+ {
+ DBG (3, "fetch_options: creating %d local option descriptors\n",
+ s->opt.num_options);
+ s->local_opt.desc =
+ malloc (s->opt.num_options * sizeof (s->local_opt.desc));
+ if (!s->local_opt.desc)
+ {
+ DBG (1, "fetch_options: couldn't malloc s->local_opt.desc\n");
+ return SANE_STATUS_NO_MEM;
+ }
+ for (option_number = 0;
+ option_number < s->opt.num_options;
+ option_number++)
+ {
+ s->local_opt.desc[option_number] =
+ malloc (sizeof (SANE_Option_Descriptor));
+ if (!s->local_opt.desc[option_number])
+ {
+ DBG (1, "fetch_options: couldn't malloc "
+ "s->local_opt.desc[%d]\n", option_number);
+ return SANE_STATUS_NO_MEM;
+ }
+ }
+ s->local_opt.num_options = s->opt.num_options;
+ }
+ else if (s->local_opt.num_options != s->opt.num_options)
+ {
+ DBG (1, "fetch_options: option number count changed during runtime?\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ DBG (3, "fetch_options: copying %d option descriptors\n",
+ s->opt.num_options);
+
+ for (option_number = 0; option_number < s->opt.num_options; option_number++)
+ {
+ memcpy (s->local_opt.desc[option_number], s->opt.desc[option_number],
+ sizeof (SANE_Option_Descriptor));
+ }
+
+ s->options_valid = 1;
+ DBG (3, "fetch_options: %d options fetched\n", s->opt.num_options);
+ return SANE_STATUS_GOOD;
+}
+
+static SANE_Status
+do_cancel (Net_Scanner * s)
+{
+ DBG (2, "do_cancel: %p\n", (void *) s);
+ s->hw->auth_active = 0;
+ if (s->data >= 0)
+ {
+ DBG (3, "do_cancel: closing data pipe\n");
+ 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];
+ char *net_resource;
+
+ DBG (2, "do_authorization: dev=%p resource=%s\n", (void *) dev, resource);
+
+ dev->auth_active = 1;
+
+ memset (&req, 0, sizeof (req));
+ memset (username, 0, sizeof (SANE_Char) * SANE_MAX_USERNAME_LEN);
+ memset (password, 0, sizeof (SANE_Char) * SANE_MAX_PASSWORD_LEN);
+
+ net_resource = malloc (strlen (resource) + 6 + strlen (dev->name));
+
+ if (net_resource != NULL)
+ {
+ sprintf (net_resource, "net:%s:%s", dev->name, resource);
+ if (auth_callback)
+ {
+ DBG (2, "do_authorization: invoking auth_callback, resource = %s\n",
+ net_resource);
+ (*auth_callback) (net_resource, username, password);
+ }
+ else
+ DBG (1, "do_authorization: no auth_callback present\n");
+ free (net_resource);
+ }
+ else /* Is this necessary? If we don't have these few bytes we will get
+ in trouble later anyway */
+ {
+ DBG (1, "do_authorization: not enough memory for net_resource\n");
+ if (auth_callback)
+ {
+ DBG (2, "do_authorization: invoking auth_callback, resource = %s\n",
+ resource);
+ (*auth_callback) (resource, username, password);
+ }
+ else
+ DBG (1, "do_authorization: no auth_callback present\n");
+ }
+
+ if (dev->auth_active)
+ {
+ SANE_Word ack;
+
+ req.resource = resource;
+ req.username = username;
+ req.password = password;
+ DBG (2, "do_authorization: relaying authentication data\n");
+ sanei_w_call (&dev->wire, SANE_NET_AUTHORIZE,
+ (WireCodecFunc) sanei_w_authorization_req, &req,
+ (WireCodecFunc) sanei_w_word, &ack);
+ }
+ else
+ DBG (1, "do_authorization: auth_active is false... strange\n");
+}
+
+
+#ifdef WITH_AVAHI
+static void
+net_avahi_resolve_callback (AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol,
+ AvahiResolverEvent event, const char *name, const char *type,
+ const char *domain, const char *host_name, const AvahiAddress *address,
+ uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags,
+ void *userdata)
+{
+ char a[AVAHI_ADDRESS_STR_MAX];
+ char *t;
+
+ /* unused */
+ interface = interface;
+ protocol = protocol;
+ userdata = userdata;
+
+ if (!r)
+ return;
+
+ switch (event)
+ {
+ case AVAHI_RESOLVER_FAILURE:
+ DBG (1, "net_avahi_resolve_callback: failed to resolve service '%s' of type '%s' in domain '%s': %s\n",
+ name, type, domain, avahi_strerror (avahi_client_errno (avahi_service_resolver_get_client (r))));
+ break;
+
+ case AVAHI_RESOLVER_FOUND:
+ DBG (3, "net_avahi_resolve_callback: service '%s' of type '%s' in domain '%s':\n", name, type, domain);
+
+ avahi_address_snprint(a, sizeof (a), address);
+ t = avahi_string_list_to_string (txt);
+
+ DBG (3, "\t%s:%u (%s)\n\tTXT=%s\n\tcookie is %u\n\tis_local: %i\n\tour_own: %i\n"
+ "\twide_area: %i\n\tmulticast: %i\n\tcached: %i\n",
+ host_name, port, a, t, avahi_string_list_get_service_cookie (txt),
+ !!(flags & AVAHI_LOOKUP_RESULT_LOCAL), !!(flags & AVAHI_LOOKUP_RESULT_OUR_OWN),
+ !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA), !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST),
+ !!(flags & AVAHI_LOOKUP_RESULT_CACHED));
+
+ /* TODO: evaluate TXT record */
+
+ /* Try first with the name */
+ if (add_device (host_name, NULL) != SANE_STATUS_GOOD)
+ {
+ DBG (1, "net_avahi_resolve_callback: couldn't add backend with name %s\n", host_name);
+
+ /* Then try the raw IP address */
+ if (add_device (t, NULL) != SANE_STATUS_GOOD)
+ DBG (1, "net_avahi_resolve_callback: couldn't add backend with IP address %s either\n", t);
+ }
+
+ avahi_free (t);
+ break;
+ }
+
+ avahi_service_resolver_free(r);
+}
+
+static void
+net_avahi_browse_callback (AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol,
+ AvahiBrowserEvent event, const char *name, const char *type,
+ const char *domain, AvahiLookupResultFlags flags, void *userdata)
+{
+ AvahiProtocol proto;
+
+ /* unused */
+ flags = flags;
+ userdata = userdata;
+
+ if (!b)
+ return;
+
+ switch (event)
+ {
+ case AVAHI_BROWSER_FAILURE:
+ DBG (1, "net_avahi_browse_callback: %s\n", avahi_strerror (avahi_client_errno (avahi_service_browser_get_client (b))));
+ avahi_threaded_poll_quit (avahi_thread);
+ return;
+
+ case AVAHI_BROWSER_NEW:
+ DBG (3, "net_avahi_browse_callback: NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
+
+ /* The server will actually be added to our list in the resolver callback */
+
+ /* The resolver object will be freed in the resolver callback, or by
+ * the server if it terminates before the callback is called.
+ */
+#ifdef ENABLE_IPV6
+ proto = AVAHI_PROTO_UNSPEC;
+#else
+ proto = AVAHI_PROTO_INET;
+#endif /* ENABLE_IPV6 */
+ if (!(avahi_service_resolver_new (avahi_client, interface, protocol, name, type, domain, proto, 0, net_avahi_resolve_callback, NULL)))
+ DBG (2, "net_avahi_browse_callback: failed to resolve service '%s': %s\n", name, avahi_strerror (avahi_client_errno (avahi_client)));
+ break;
+
+ case AVAHI_BROWSER_REMOVE:
+ DBG (3, "net_avahi_browse_callback: REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
+ /* With the current architecture, we cannot safely remove a server from the list */
+ break;
+
+ case AVAHI_BROWSER_ALL_FOR_NOW:
+ case AVAHI_BROWSER_CACHE_EXHAUSTED:
+ DBG (3, "net_avahi_browse_callback: %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
+ break;
+ }
+}
+
+static void
+net_avahi_callback (AvahiClient *c, AvahiClientState state, void * userdata)
+{
+ AvahiProtocol proto;
+ int error;
+
+ /* unused */
+ userdata = userdata;
+
+ if (!c)
+ return;
+
+ switch (state)
+ {
+ case AVAHI_CLIENT_CONNECTING:
+ break;
+
+ case AVAHI_CLIENT_S_COLLISION:
+ case AVAHI_CLIENT_S_REGISTERING:
+ case AVAHI_CLIENT_S_RUNNING:
+ if (avahi_browser)
+ return;
+
+#ifdef ENABLE_IPV6
+ proto = AVAHI_PROTO_UNSPEC;
+#else
+ proto = AVAHI_PROTO_INET;
+#endif /* ENABLE_IPV6 */
+
+ avahi_browser = avahi_service_browser_new (c, AVAHI_IF_UNSPEC, proto, SANED_SERVICE_DNS, NULL, 0, net_avahi_browse_callback, NULL);
+ if (avahi_browser == NULL)
+ {
+ DBG (1, "net_avahi_callback: could not create service browser: %s\n", avahi_strerror (avahi_client_errno (c)));
+ avahi_threaded_poll_quit (avahi_thread);
+ }
+ break;
+
+ case AVAHI_CLIENT_FAILURE:
+ error = avahi_client_errno (c);
+
+ if (error == AVAHI_ERR_DISCONNECTED)
+ {
+ /* Server disappeared - try to reconnect */
+ avahi_client_free (avahi_client);
+ avahi_client = NULL;
+
+ if (avahi_browser)
+ {
+ avahi_service_browser_free (avahi_browser);
+ avahi_browser = NULL;
+ }
+
+ avahi_client = avahi_client_new (avahi_threaded_poll_get (avahi_thread), AVAHI_CLIENT_NO_FAIL, net_avahi_callback, NULL, &error);
+ if (avahi_client == NULL)
+ {
+ DBG (1, "net_avahi_init: could not create Avahi client: %s\n", avahi_strerror (error));
+ avahi_threaded_poll_quit (avahi_thread);
+ }
+ }
+ else
+ {
+ /* Another error happened - game over */
+ DBG (1, "net_avahi_callback: server connection failure: %s\n", avahi_strerror (error));
+ avahi_threaded_poll_quit (avahi_thread);
+ }
+ break;
+ }
+}
+
+
+static void
+net_avahi_init (void)
+{
+ int error;
+
+ avahi_thread = avahi_threaded_poll_new ();
+ if (avahi_thread == NULL)
+ {
+ DBG (1, "net_avahi_init: could not create threaded poll object\n");
+ goto fail;
+ }
+
+ avahi_client = avahi_client_new (avahi_threaded_poll_get (avahi_thread), AVAHI_CLIENT_NO_FAIL, net_avahi_callback, NULL, &error);
+ if (avahi_client == NULL)
+ {
+ DBG (1, "net_avahi_init: could not create Avahi client: %s\n", avahi_strerror (error));
+ goto fail;
+ }
+
+ if (avahi_threaded_poll_start (avahi_thread) < 0)
+ {
+ DBG (1, "net_avahi_init: Avahi thread failed to start\n");
+ goto fail;
+ }
+
+ /* All done */
+ return;
+
+ fail:
+ DBG (1, "net_avahi_init: Avahi init failed, support disabled\n");
+
+ if (avahi_client)
+ {
+ avahi_client_free (avahi_client);
+ avahi_client = NULL;
+ }
+
+ if (avahi_thread)
+ {
+ avahi_threaded_poll_free (avahi_thread);
+ avahi_thread = NULL;
+ }
+}
+
+static void
+net_avahi_cleanup (void)
+{
+ if (!avahi_thread)
+ return;
+
+ DBG (1, "net_avahi_cleanup: stopping thread\n");
+
+ avahi_threaded_poll_stop (avahi_thread);
+
+ if (avahi_browser)
+ avahi_service_browser_free (avahi_browser);
+
+ if (avahi_client)
+ avahi_client_free (avahi_client);
+
+ avahi_threaded_poll_free (avahi_thread);
+
+ DBG (1, "net_avahi_cleanup: done\n");
+}
+#endif /* WITH_AVAHI */
+
+
+SANE_Status
+sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
+{
+ char device_name[PATH_MAX];
+ const char *optval;
+ const char *env;
+ size_t len;
+ FILE *fp;
+ short ns = 0x1234;
+ unsigned char *p = (unsigned char *)(&ns);
+
+#ifndef NET_USES_AF_INDEP
+ struct servent *serv;
+#endif /* !NET_USES_AF_INDEP */
+
+ DBG_INIT ();
+
+ DBG (2, "sane_init: authorize %s null, version_code %s null\n", (authorize) ? "!=" : "==",
+ (version_code) ? "!=" : "==");
+
+ devlist = NULL;
+ first_device = NULL;
+ first_handle = NULL;
+
+#ifdef WITH_AVAHI
+ net_avahi_init ();
+#endif /* WITH_AVAHI */
+
+ auth_callback = authorize;
+
+ /* Return the version number of the sane-backends package to allow
+ the frontend to print them. This is done only for net and dll,
+ because these backends are usually called by the frontend. */
+ if (version_code)
+ *version_code = SANE_VERSION_CODE (SANE_DLL_V_MAJOR, SANE_DLL_V_MINOR,
+ SANE_DLL_V_BUILD);
+
+ DBG (1, "sane_init: SANE net backend version %s from %s\n", NET_VERSION,
+ PACKAGE_STRING);
+
+ /* determine (client) machine byte order */
+ if (*p == 0x12)
+ {
+ client_big_endian = 1;
+ DBG (3, "sane_init: Client has big endian byte order\n");
+ }
+ else
+ {
+ client_big_endian = 0;
+ DBG (3, "sane_init: Client has little endian byte order\n");
+ }
+
+#ifndef NET_USES_AF_INDEP
+ DBG (2, "sane_init: determining sane service port\n");
+ serv = getservbyname ("sane-port", "tcp");
+
+ if (serv)
+ {
+ DBG (2, "sane_init: found port %d\n", ntohs (serv->s_port));
+ saned_port = serv->s_port;
+ }
+ else
+ {
+ saned_port = htons (6566);
+ DBG (1, "sane_init: could not find `sane-port' service (%s); using default "
+ "port %d\n", strerror (errno), ntohs (saned_port));
+ }
+#endif /* !NET_USES_AF_INDEP */
+
+ DBG (2, "sane_init: searching for config file\n");
+ fp = sanei_config_open (NET_CONFIG_FILE);
+ if (fp)
+ {
+ while (sanei_config_read (device_name, sizeof (device_name), fp))
+ {
+ if (device_name[0] == '#') /* ignore line comments */
+ continue;
+ len = strlen (device_name);
+
+ if (!len)
+ continue; /* ignore empty lines */
+
+ /*
+ * Check for net backend options.
+ * Anything that isn't an option is a saned host.
+ */
+ if (strstr(device_name, "connect_timeout") != NULL)
+ {
+ /* Look for the = sign; if it's not there, error out */
+ optval = strchr(device_name, '=');
+
+ if (!optval)
+ continue;
+
+ optval = sanei_config_skip_whitespace (++optval);
+ if ((optval != NULL) && (*optval != '\0'))
+ {
+ connect_timeout = atoi(optval);
+
+ DBG (2, "sane_init: connect timeout set to %d seconds\n", connect_timeout);
+ }
+
+ continue;
+ }
+
+ DBG (2, "sane_init: trying to add %s\n", device_name);
+ add_device (device_name, 0);
+ }
+
+ fclose (fp);
+ DBG (2, "sane_init: done reading config\n");
+ }
+ else
+ DBG (1, "sane_init: could not open config file (%s): %s\n",
+ NET_CONFIG_FILE, strerror (errno));
+
+ DBG (2, "sane_init: evaluating environment variable SANE_NET_HOSTS\n");
+ env = getenv ("SANE_NET_HOSTS");
+ if (env)
+ {
+ char *copy, *next, *host;
+ if ((copy = strdup (env)) != NULL)
+ {
+ next = copy;
+ while ((host = strsep (&next, ":")))
+ {
+#ifdef ENABLE_IPV6
+ if (host[0] == '[')
+ {
+ /* skip '[' (host[0]) */
+ host++;
+ /* get the rest of the IPv6 addr (we're screwed if ] is missing)
+ * Is it worth checking for the matching ] ? Not for now. */
+ strsep (&next, "]");
+ /* add back the ":" that got removed by the strsep() */
+ host[strlen (host)] = ':';
+ /* host now holds the IPv6 address */
+
+ /* skip the ':' that could be after ] (avoids a call to strsep() */
+ if (next[0] == ':')
+ next++;
+ }
+
+ /*
+ * if the IPv6 is last in the list, the strsep() call in the while()
+ * will return a string with the first char being '\0'. Skip it.
+ */
+ if (host[0] == '\0')
+ continue;
+#endif /* ENABLE_IPV6 */
+ DBG (2, "sane_init: trying to add %s\n", host);
+ add_device (host, 0);
+ }
+ free (copy);
+ }
+ else
+ DBG (1, "sane_init: not enough memory to duplicate "
+ "environment variable\n");
+ }
+
+ DBG (2, "sane_init: evaluating environment variable SANE_NET_TIMEOUT\n");
+ env = getenv ("SANE_NET_TIMEOUT");
+ if (env)
+ {
+ connect_timeout = atoi(env);
+ DBG (2, "sane_init: connect timeout set to %d seconds from env\n", connect_timeout);
+ }
+
+ DBG (2, "sane_init: done\n");
+ return SANE_STATUS_GOOD;
+}
+
+void
+sane_exit (void)
+{
+ Net_Scanner *handle, *next_handle;
+ Net_Device *dev, *next_device;
+ int i;
+
+ DBG (1, "sane_exit: exiting\n");
+
+#ifdef WITH_AVAHI
+ net_avahi_cleanup ();
+#endif /* WITH_AVAHI */
+
+ /* 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, "sane_exit: closing dev %p, ctl=%d\n", (void *) 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);
+ sanei_w_exit (&dev->wire);
+ close (dev->ctl);
+ }
+ if (dev->name)
+ free ((void *) dev->name);
+
+#ifdef NET_USES_AF_INDEP
+ if (dev->addr)
+ freeaddrinfo(dev->addr);
+#endif /* NET_USES_AF_INDEP */
+
+ free (dev);
+ }
+ if (devlist)
+ {
+ for (i = 0; devlist[i]; ++i)
+ {
+ if (devlist[i]->vendor)
+ free ((void *) devlist[i]->vendor);
+ if (devlist[i]->model)
+ free ((void *) devlist[i]->model);
+ if (devlist[i]->type)
+ free ((void *) devlist[i]->type);
+ free ((void *) devlist[i]);
+ }
+ free (devlist);
+ }
+ DBG (3, "sane_exit: finished.\n");
+}
+
+/* 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 *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) \
+ { \
+ DBG (1, "sane_get_devices: not enough memory\n"); \
+ return SANE_STATUS_NO_MEM; \
+ } \
+ } \
+ }
+
+ DBG (3, "sane_get_devices: local_only = %d\n", local_only);
+
+ if (local_only)
+ {
+ *device_list = empty_devlist;
+ return SANE_STATUS_GOOD;
+ }
+
+ if (devlist)
+ {
+ DBG (2, "sane_get_devices: freeing devlist\n");
+ for (i = 0; devlist[i]; ++i)
+ {
+ if (devlist[i]->vendor)
+ free ((void *) devlist[i]->vendor);
+ if (devlist[i]->model)
+ free ((void *) devlist[i]->model);
+ if (devlist[i]->type)
+ free ((void *) devlist[i]->type);
+ free ((void *) devlist[i]);
+ }
+ free (devlist);
+ devlist = 0;
+ }
+ devlist_len = 0;
+ devlist_size = 0;
+
+ for (dev = first_device; dev; dev = dev->next)
+ {
+ if (dev->ctl < 0)
+ {
+ status = connect_dev (dev);
+ if (status != SANE_STATUS_GOOD)
+ {
+ DBG (1, "sane_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, "sane_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;
+#ifdef ENABLE_IPV6
+ SANE_Bool IPv6 = SANE_FALSE;
+#endif /* ENABLE_IPV6 */
+
+ /* 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);
+
+#ifdef ENABLE_IPV6
+ if (strchr (dev->name, ':') != NULL)
+ {
+ len += 2;
+ IPv6 = SANE_TRUE;
+ }
+#endif /* ENABLE_IPV6 */
+
+ mem = malloc (sizeof (*dev) + len + 1);
+ if (!mem)
+ {
+ DBG (1, "sane_get_devices: not enough free memory\n");
+ sanei_w_free (&dev->wire,
+ (WireCodecFunc) sanei_w_get_devices_reply,
+ &reply);
+ return SANE_STATUS_NO_MEM;
+ }
+
+ memset (mem, 0, sizeof (*dev) + len);
+ full_name = mem + sizeof (*dev);
+
+#ifdef ENABLE_IPV6
+ if (IPv6 == SANE_TRUE)
+ strcat (full_name, "[");
+#endif /* ENABLE_IPV6 */
+
+ strcat (full_name, dev->name);
+
+#ifdef ENABLE_IPV6
+ if (IPv6 == SANE_TRUE)
+ strcat (full_name, "]");
+#endif /* ENABLE_IPV6 */
+
+ strcat (full_name, ":");
+ strcat (full_name, reply.device_list[i]->name);
+ DBG (3, "sane_get_devices: got %s\n", full_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);
+
+ if ((!rdev->vendor) || (!rdev->model) || (!rdev->type))
+ {
+ DBG (1, "sane_get_devices: not enough free memory\n");
+ if (rdev->vendor)
+ free ((void *) rdev->vendor);
+ if (rdev->model)
+ free ((void *) rdev->model);
+ if (rdev->type)
+ free ((void *) rdev->type);
+ free (rdev);
+ sanei_w_free (&dev->wire,
+ (WireCodecFunc) sanei_w_get_devices_reply,
+ &reply);
+ return SANE_STATUS_NO_MEM;
+ }
+
+ 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;
+ DBG (2, "sane_get_devices: finished (%d devices)\n", devlist_len - 1);
+ 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;
+#ifdef ENABLE_IPV6
+ const char *tmp_name;
+ SANE_Bool v6addr = SANE_FALSE;
+#endif /* ENABLE_IPV6 */
+ SANE_String nd_name;
+ SANE_Status status;
+ SANE_Word handle;
+ SANE_Word ack;
+ Net_Device *dev;
+ Net_Scanner *s;
+ int need_auth;
+
+ DBG (3, "sane_open(\"%s\")\n", full_name);
+
+#ifdef ENABLE_IPV6
+ /*
+ * Check whether a numerical IPv6 host was specified
+ * [2001:42:42::12] <== check for '[' as full_name[0]
+ * ex: [2001:42:42::12]:test:0 (syntax taken from Apache 2)
+ */
+ if (full_name[0] == '[')
+ {
+ v6addr = SANE_TRUE;
+ tmp_name = strchr (full_name, ']');
+ if (!tmp_name)
+ {
+ DBG (1, "sane_open: incorrect host address: missing matching ']'\n");
+ return SANE_STATUS_INVAL;
+ }
+ }
+ else
+ tmp_name = full_name;
+
+ dev_name = strchr (tmp_name, ':');
+#else /* !ENABLE_IPV6 */
+
+ dev_name = strchr (full_name, ':');
+#endif /* ENABLE_IPV6 */
+
+ if (dev_name)
+ {
+#ifdef strndupa
+# ifdef ENABLE_IPV6
+ if (v6addr == SANE_TRUE)
+ nd_name = strndupa (full_name + 1, dev_name - full_name - 2);
+ else
+ nd_name = strndupa (full_name, dev_name - full_name);
+
+# else /* !ENABLE_IPV6 */
+
+ nd_name = strndupa (full_name, dev_name - full_name);
+# endif /* ENABLE_IPV6 */
+
+ if (!nd_name)
+ {
+ DBG (1, "sane_open: not enough free memory\n");
+ return SANE_STATUS_NO_MEM;
+ }
+#else
+ char *tmp;
+
+# ifdef ENABLE_IPV6
+ if (v6addr == SANE_TRUE)
+ tmp = alloca (dev_name - full_name - 2 + 1);
+ else
+ tmp = alloca (dev_name - full_name + 1);
+
+# else /* !ENABLE_IPV6 */
+
+ tmp = alloca (dev_name - full_name + 1);
+# endif /* ENABLE_IPV6 */
+
+ if (!tmp)
+ {
+ DBG (1, "sane_open: not enough free memory\n");
+ return SANE_STATUS_NO_MEM;
+ }
+
+# ifdef ENABLE_IPV6
+ if (v6addr == SANE_TRUE)
+ {
+ memcpy (tmp, full_name + 1, dev_name - full_name - 2);
+ tmp[dev_name - full_name - 2] = '\0';
+ }
+ else
+ {
+ memcpy (tmp, full_name, dev_name - full_name);
+ tmp[dev_name - full_name] = '\0';
+ }
+
+# else /* !ENABLE_IPV6 */
+
+ memcpy (tmp, full_name, dev_name - full_name);
+ tmp[dev_name - full_name] = '\0';
+# endif /* ENABLE_IPV6 */
+
+ 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. */
+#ifdef ENABLE_IPV6
+ if (v6addr == SANE_TRUE)
+ {
+ nd_name = alloca (strlen (full_name) - 2 + 1);
+ if (!nd_name)
+ {
+ DBG (1, "sane_open: not enough free memory\n");
+ return SANE_STATUS_NO_MEM;
+ }
+ memcpy (nd_name, full_name + 1, strlen (full_name) - 2);
+ nd_name[strlen (full_name) - 2] = '\0';
+ }
+ else
+ nd_name = (char *) full_name;
+
+#else /* !ENABLE_IPV6 */
+
+ nd_name = (char *) full_name;
+#endif /* ENABLE_IPV6 */
+
+ dev_name = "";
+ }
+ DBG (2, "sane_open: host = %s, device = %s\n", nd_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...) */
+
+ DBG (1, "sane_open: empty backend name is not allowed\n");
+ return SANE_STATUS_INVAL;
+ }
+ else
+ for (dev = first_device; dev; dev = dev->next)
+ if (strcmp (dev->name, nd_name) == 0)
+ break;
+
+ if (!dev)
+ {
+ DBG (1,
+ "sane_open: device %s not found, trying to register it anyway\n",
+ nd_name);
+ status = add_device (nd_name, &dev);
+ if (status != SANE_STATUS_GOOD)
+ {
+ DBG (1, "sane_open: could not open device\n");
+ return status;
+ }
+ }
+ else
+ DBG (2, "sane_open: device found in list\n");
+
+ if (dev->ctl < 0)
+ {
+ DBG (2, "sane_open: device not connected yet...\n");
+ status = connect_dev (dev);
+ if (status != SANE_STATUS_GOOD)
+ {
+ DBG (1, "sane_open: could not connect to device\n");
+ return status;
+ }
+ }
+
+ DBG (3, "sane_open: net_open\n");
+ sanei_w_call (&dev->wire, SANE_NET_OPEN,
+ (WireCodecFunc) sanei_w_string, &dev_name,
+ (WireCodecFunc) sanei_w_open_reply, &reply);
+ do
+ {
+ if (dev->wire.status != 0)
+ {
+ DBG (1, "sane_open: 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)
+ {
+ DBG (3, "sane_open: authorization required\n");
+ do_authorization (dev, reply.resource_to_authorize);
+
+ sanei_w_free (&dev->wire, (WireCodecFunc) sanei_w_open_reply,
+ &reply);
+
+ if (dev->wire.direction != WIRE_DECODE)
+ sanei_w_set_dir (&dev->wire, WIRE_DECODE);
+ sanei_w_open_reply (&dev->wire, &reply);
+
+ continue;
+ }
+ else
+ sanei_w_free (&dev->wire, (WireCodecFunc) sanei_w_open_reply, &reply);
+
+ if (need_auth && !dev->auth_active)
+ {
+ DBG (2, "sane_open: open cancelled\n");
+ return SANE_STATUS_CANCELLED;
+ }
+
+ if (status != SANE_STATUS_GOOD)
+ {
+ DBG (1, "sane_open: remote open failed\n");
+ return reply.status;
+ }
+ }
+ while (need_auth);
+
+ s = malloc (sizeof (*s));
+ if (!s)
+ {
+ DBG (1, "sane_open: not enough free memory\n");
+ return SANE_STATUS_NO_MEM;
+ }
+
+ memset (s, 0, sizeof (*s));
+ s->hw = dev;
+ s->handle = handle;
+ s->data = -1;
+ s->next = first_handle;
+ s->local_opt.desc = 0;
+ s->local_opt.num_options = 0;
+
+ DBG (3, "sane_open: getting option descriptors\n");
+ status = fetch_options (s);
+ if (status != SANE_STATUS_GOOD)
+ {
+ DBG (1, "sane_open: fetch_options failed (%s), closing device again\n",
+ sane_strstatus (status));
+
+ sanei_w_call (&s->hw->wire, SANE_NET_CLOSE,
+ (WireCodecFunc) sanei_w_word, &s->handle,
+ (WireCodecFunc) sanei_w_word, &ack);
+
+ free (s);
+
+ return status;
+ }
+
+ first_handle = s;
+ *meta_handle = s;
+
+ DBG (3, "sane_open: success\n");
+ return SANE_STATUS_GOOD;
+}
+
+void
+sane_close (SANE_Handle handle)
+{
+ Net_Scanner *prev, *s;
+ SANE_Word ack;
+ int option_number;
+
+ DBG (3, "sane_close: handle %p\n", handle);
+
+ prev = 0;
+ for (s = first_handle; s; s = s->next)
+ {
+ if (s == handle)
+ break;
+ prev = s;
+ }
+ if (!s)
+ {
+ DBG (1, "sane_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;
+
+ if (s->opt.num_options)
+ {
+ DBG (2, "sane_close: removing cached option descriptors\n");
+ 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)
+ DBG (1, "sane_close: couldn't free sanei_w_option_descriptor_array "
+ "(%s)\n", sane_strstatus (s->hw->wire.status));
+ }
+
+ DBG (2, "sane_close: removing local option descriptors\n");
+ for (option_number = 0; option_number < s->local_opt.num_options;
+ option_number++)
+ free (s->local_opt.desc[option_number]);
+ if (s->local_opt.desc)
+ free (s->local_opt.desc);
+
+ DBG (2, "sane_close: net_close\n");
+ sanei_w_call (&s->hw->wire, SANE_NET_CLOSE,
+ (WireCodecFunc) sanei_w_word, &s->handle,
+ (WireCodecFunc) sanei_w_word, &ack);
+ if (s->data >= 0)
+ {
+ DBG (2, "sane_close: closing data pipe\n");
+ close (s->data);
+ }
+ free (s);
+ DBG (2, "sane_close: done\n");
+}
+
+const SANE_Option_Descriptor *
+sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
+{
+ Net_Scanner *s = handle;
+ SANE_Status status;
+
+ DBG (3, "sane_get_option_descriptor: option %d\n", option);
+
+ if (!s->options_valid)
+ {
+ DBG (3, "sane_get_option_descriptor: getting option descriptors\n");
+ status = fetch_options (s);
+ if (status != SANE_STATUS_GOOD)
+ {
+ DBG (1, "sane_get_option_descriptor: fetch_options failed (%s)\n",
+ sane_strstatus (status));
+ return 0;
+ }
+ }
+
+ if (((SANE_Word) option >= s->opt.num_options) || (option < 0))
+ {
+ DBG (2, "sane_get_option_descriptor: invalid option number\n");
+ return 0;
+ }
+ return s->local_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;
+ SANE_Word local_info;
+
+ DBG (3, "sane_control_option: option %d, action %d\n", option, action);
+
+ if (!s->options_valid)
+ {
+ DBG (1, "sane_control_option: FRONTEND BUG: option descriptors reload needed\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ if (((SANE_Word) option >= s->opt.num_options) || (option < 0))
+ {
+ DBG (1, "sane_control_option: invalid option number\n");
+ 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;
+ case SANE_TYPE_STRING: /* strings can be smaller than size */
+ value_size = s->opt.desc[option]->size;
+ if ((action == SANE_ACTION_SET_VALUE)
+ && (((SANE_Int) strlen ((SANE_String) value) + 1)
+ < s->opt.desc[option]->size))
+ value_size = strlen ((SANE_String) value) + 1;
+ break;
+ default:
+ value_size = s->opt.desc[option]->size;
+ break;
+ }
+
+ /* Avoid leaking memory bits */
+ if (value && (action != SANE_ACTION_SET_VALUE))
+ memset (value, 0, value_size);
+
+ /* for SET_AUTO the parameter ``value'' is ignored */
+ if (action == SANE_ACTION_SET_AUTO)
+ value_size = 0;
+
+ 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;
+
+ local_info = 0;
+
+ DBG (3, "sane_control_option: remote control option\n");
+ sanei_w_call (&s->hw->wire, SANE_NET_CONTROL_OPTION,
+ (WireCodecFunc) sanei_w_control_option_req, &req,
+ (WireCodecFunc) sanei_w_control_option_reply, &reply);
+
+ do
+ {
+ status = reply.status;
+ need_auth = (reply.resource_to_authorize != 0);
+ if (need_auth)
+ {
+ DBG (3, "sane_control_option: auth required\n");
+ do_authorization (s->hw, reply.resource_to_authorize);
+ sanei_w_free (&s->hw->wire,
+ (WireCodecFunc) sanei_w_control_option_reply, &reply);
+
+ sanei_w_set_dir (&s->hw->wire, WIRE_DECODE);
+
+ sanei_w_control_option_reply (&s->hw->wire, &reply);
+ continue;
+
+ }
+ else if (status == SANE_STATUS_GOOD)
+ {
+ local_info = reply.info;
+
+ if (info)
+ *info = reply.info;
+ if (value_size > 0)
+ {
+ if ((SANE_Word) value_size == reply.value_size)
+ memcpy (value, reply.value, reply.value_size);
+ else
+ DBG (1, "sane_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);
+
+ DBG (2, "sane_control_option: remote done (%s, info %x)\n", sane_strstatus (status), local_info);
+
+ if ((status == SANE_STATUS_GOOD) && (info == NULL) && (local_info & SANE_INFO_RELOAD_OPTIONS))
+ {
+ DBG (2, "sane_control_option: reloading options as frontend does not care\n");
+
+ status = fetch_options (s);
+
+ DBG (2, "sane_control_option: reload done (%s)\n", sane_strstatus (status));
+ }
+
+ DBG (2, "sane_control_option: done (%s, info %x)\n", sane_strstatus (status), local_info);
+
+ 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;
+
+ DBG (3, "sane_get_parameters\n");
+
+ if (!params)
+ {
+ DBG (1, "sane_get_parameters: parameter params not supplied\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ DBG (3, "sane_get_parameters: remote get parameters\n");
+ 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;
+ depth = reply.params.depth;
+ sanei_w_free (&s->hw->wire,
+ (WireCodecFunc) sanei_w_get_parameters_reply, &reply);
+
+ DBG (3, "sane_get_parameters: returned status %s\n",
+ sane_strstatus (status));
+ return status;
+}
+
+#ifdef NET_USES_AF_INDEP
+SANE_Status
+sane_start (SANE_Handle handle)
+{
+ Net_Scanner *s = handle;
+ SANE_Start_Reply reply;
+ struct sockaddr_in sin;
+ struct sockaddr *sa;
+#ifdef ENABLE_IPV6
+ struct sockaddr_in6 sin6;
+#endif /* ENABLE_IPV6 */
+ SANE_Status status;
+ int fd, need_auth;
+ socklen_t len;
+ uint16_t port; /* Internet-specific */
+
+
+ DBG (3, "sane_start\n");
+
+ hang_over = -1;
+ left_over = -1;
+
+ if (s->data >= 0)
+ {
+ DBG (2, "sane_start: data pipe already exists\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ /* Do this ahead of time so in case anything fails, we can
+ recover gracefully (without hanging our server). */
+
+ switch (s->hw->addr_used->ai_family)
+ {
+ case AF_INET:
+ len = sizeof (sin);
+ sa = (struct sockaddr *) &sin;
+ break;
+#ifdef ENABLE_IPV6
+ case AF_INET6:
+ len = sizeof (sin6);
+ sa = (struct sockaddr *) &sin6;
+ break;
+#endif /* ENABLE_IPV6 */
+ default:
+ DBG (1, "sane_start: unknown address family : %d\n",
+ s->hw->addr_used->ai_family);
+ return SANE_STATUS_INVAL;
+ }
+
+ if (getpeername (s->hw->ctl, sa, &len) < 0)
+ {
+ DBG (1, "sane_start: getpeername() failed (%s)\n", strerror (errno));
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ fd = socket (s->hw->addr_used->ai_family, SOCK_STREAM, 0);
+ if (fd < 0)
+ {
+ DBG (1, "sane_start: socket() failed (%s)\n", strerror (errno));
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ DBG (3, "sane_start: remote start\n");
+ sanei_w_call (&s->hw->wire, SANE_NET_START,
+ (WireCodecFunc) sanei_w_word, &s->handle,
+ (WireCodecFunc) sanei_w_start_reply, &reply);
+ do
+ {
+ status = reply.status;
+ port = reply.port;
+ if (reply.byte_order == 0x1234)
+ {
+ server_big_endian = 0;
+ DBG (1, "sane_start: server has little endian byte order\n");
+ }
+ else
+ {
+ server_big_endian = 1;
+ DBG (1, "sane_start: server has big endian byte order\n");
+ }
+
+ need_auth = (reply.resource_to_authorize != 0);
+ if (need_auth)
+ {
+ DBG (3, "sane_start: auth required\n");
+ do_authorization (s->hw, reply.resource_to_authorize);
+
+ sanei_w_free (&s->hw->wire,
+ (WireCodecFunc) sanei_w_start_reply, &reply);
+
+ sanei_w_set_dir (&s->hw->wire, WIRE_DECODE);
+
+ sanei_w_start_reply (&s->hw->wire, &reply);
+
+ continue;
+ }
+ 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)
+ {
+ DBG (1, "sane_start: remote start failed (%s)\n",
+ sane_strstatus (status));
+ close (fd);
+ return status;
+ }
+ }
+ while (need_auth);
+ DBG (3, "sane_start: remote start finished, data at port %hu\n", port);
+
+ switch (s->hw->addr_used->ai_family)
+ {
+ case AF_INET:
+ sin.sin_port = htons (port);
+ break;
+#ifdef ENABLE_IPV6
+ case AF_INET6:
+ sin6.sin6_port = htons (port);
+ break;
+#endif /* ENABLE_IPV6 */
+ }
+
+ if (connect (fd, sa, len) < 0)
+ {
+ DBG (1, "sane_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;
+ DBG (3, "sane_start: done (%s)\n", sane_strstatus (status));
+ return status;
+}
+
+#else /* !NET_USES_AF_INDEP */
+
+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;
+ socklen_t len;
+ uint16_t port; /* Internet-specific */
+
+
+ DBG (3, "sane_start\n");
+
+ hang_over = -1;
+ left_over = -1;
+
+ if (s->data >= 0)
+ {
+ DBG (2, "sane_start: data pipe already exists\n");
+ 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, "sane_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, "sane_start: socket() failed (%s)\n", strerror (errno));
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ DBG (3, "sane_start: remote start\n");
+ sanei_w_call (&s->hw->wire, SANE_NET_START,
+ (WireCodecFunc) sanei_w_word, &s->handle,
+ (WireCodecFunc) sanei_w_start_reply, &reply);
+ do
+ {
+
+ status = reply.status;
+ port = reply.port;
+ if (reply.byte_order == 0x1234)
+ {
+ server_big_endian = 0;
+ DBG (1, "sane_start: server has little endian byte order\n");
+ }
+ else
+ {
+ server_big_endian = 1;
+ DBG (1, "sane_start: server has big endian byte order\n");
+ }
+
+ need_auth = (reply.resource_to_authorize != 0);
+ if (need_auth)
+ {
+ DBG (3, "sane_start: auth required\n");
+ do_authorization (s->hw, reply.resource_to_authorize);
+
+ sanei_w_free (&s->hw->wire,
+ (WireCodecFunc) sanei_w_start_reply, &reply);
+
+ sanei_w_set_dir (&s->hw->wire, WIRE_DECODE);
+
+ sanei_w_start_reply (&s->hw->wire, &reply);
+
+ continue;
+ }
+ 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)
+ {
+ DBG (1, "sane_start: remote start failed (%s)\n",
+ sane_strstatus (status));
+ close (fd);
+ return status;
+ }
+ }
+ while (need_auth);
+ DBG (3, "sane_start: remote start finished, data at port %hu\n", port);
+ sin.sin_port = htons (port);
+
+ if (connect (fd, (struct sockaddr *) &sin, len) < 0)
+ {
+ DBG (1, "sane_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;
+ DBG (3, "sane_start: done (%s)\n", sane_strstatus (status));
+ return status;
+}
+#endif /* NET_USES_AF_INDEP */
+
+
+SANE_Status
+sane_read (SANE_Handle handle, SANE_Byte * data, SANE_Int max_length,
+ SANE_Int * length)
+{
+ Net_Scanner *s = handle;
+ ssize_t nread;
+ SANE_Int cnt;
+ SANE_Int start_cnt;
+ SANE_Int end_cnt;
+ SANE_Byte swap_buf;
+ SANE_Byte temp_hang_over;
+ int is_even;
+
+ DBG (3, "sane_read: handle=%p, data=%p, max_length=%d, length=%p\n",
+ handle, data, max_length, (void *) length);
+ if (!length)
+ {
+ DBG (1, "sane_read: length == NULL\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ is_even = 1;
+ *length = 0;
+
+ /* If there's a left over, i.e. a byte already in the correct byte order,
+ return it immediately; otherwise read may fail with a SANE_STATUS_EOF and
+ the caller never can read the last byte */
+ if ((depth == 16) && (server_big_endian != client_big_endian))
+ {
+ if (left_over > -1)
+ {
+ DBG (3, "sane_read: left_over from previous call, return "
+ "immediately\n");
+ /* return the byte, we've currently scanned; hang_over becomes
+ left_over */
+ *data = (SANE_Byte) left_over;
+ left_over = -1;
+ *length = 1;
+ return SANE_STATUS_GOOD;
+ }
+ }
+
+ if (s->data < 0)
+ {
+ DBG (1, "sane_read: data pipe doesn't exist, scan cancelled?\n");
+ return SANE_STATUS_CANCELLED;
+ }
+
+ if (s->bytes_remaining == 0)
+ {
+ /* boy, is this painful or what? */
+
+ DBG (4, "sane_read: reading packet length\n");
+ nread = read (s->data, s->reclen_buf + s->reclen_buf_offset,
+ 4 - s->reclen_buf_offset);
+ if (nread < 0)
+ {
+ DBG (3, "sane_read: read failed (%s)\n", strerror (errno));
+ if (errno == EAGAIN)
+ {
+ DBG (3, "sane_read: try again later\n");
+ return SANE_STATUS_GOOD;
+ }
+ else
+ {
+ DBG (1, "sane_read: cancelling read\n");
+ do_cancel (s);
+ return SANE_STATUS_IO_ERROR;
+ }
+ }
+ DBG (4, "sane_read: read %lu bytes, %d from 4 total\n", (u_long) nread,
+ s->reclen_buf_offset);
+ s->reclen_buf_offset += nread;
+ if (s->reclen_buf_offset < 4)
+ {
+ DBG (4, "sane_read: enough for now\n");
+ 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, "sane_read: next record length=%ld bytes\n",
+ (long) s->bytes_remaining);
+ if (s->bytes_remaining == 0xffffffff)
+ {
+ char ch;
+
+ DBG (2, "sane_read: received error signal\n");
+
+ /* 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)
+ {
+ DBG (1, "sane_read: failed to read error code\n");
+ ch = SANE_STATUS_IO_ERROR;
+ }
+ DBG (1, "sane_read: error code %s\n",
+ sane_strstatus ((SANE_Status) ch));
+ do_cancel (s);
+ return (SANE_Status) ch;
+ }
+ }
+
+ if (max_length > (SANE_Int) s->bytes_remaining)
+ max_length = s->bytes_remaining;
+
+ nread = read (s->data, data, max_length);
+
+ if (nread < 0)
+ {
+ DBG (2, "sane_read: error code %s\n", strerror (errno));
+ if (errno == EAGAIN)
+ return SANE_STATUS_GOOD;
+ else
+ {
+ DBG (1, "sane_read: cancelling scan\n");
+ do_cancel (s);
+ return SANE_STATUS_IO_ERROR;
+ }
+ }
+
+ s->bytes_remaining -= nread;
+
+ *length = nread;
+ /* Check whether we are scanning with a depth of 16 bits/pixel and whether
+ server and client have different byte order. If this is true, then it's
+ neccessary to check whether read returned an odd number. If an odd number
+ has been returned, we must save the last byte.
+ */
+ if ((depth == 16) && (server_big_endian != client_big_endian))
+ {
+ DBG (1,"sane_read: client/server have different byte order; "
+ "must swap\n");
+ /* special case: 1 byte scanned and hang_over */
+ if ((nread == 1) && (hang_over > -1))
+ {
+ /* return the byte, we've currently scanned; hang_over becomes
+ left_over */
+ left_over = hang_over;
+ hang_over = -1;
+ return SANE_STATUS_GOOD;
+ }
+ /* check whether an even or an odd number of bytes has been scanned */
+ if ((nread % 2) == 0)
+ is_even = 1;
+ else
+ is_even = 0;
+ /* check, whether there's a hang over from a previous call;
+ in this case we memcopy the data up one byte */
+ if ((nread > 1) && (hang_over > -1))
+ {
+ /* store last byte */
+ temp_hang_over = *(data + nread - 1);
+ memmove (data + 1, data, nread - 1);
+ *data = (SANE_Byte) hang_over;
+ /* what happens with the last byte depends on whether the number
+ of bytes is even or odd */
+ if (is_even == 1)
+ {
+ /* number of bytes is even; no new hang_over, exchange last
+ byte with hang over; last byte becomes left_over */
+ left_over = *(data + nread - 1);
+ *(data + nread - 1) = temp_hang_over;
+ hang_over = -1;
+ start_cnt = 0;
+ /* last byte already swapped */
+ end_cnt = nread - 2;
+ }
+ else
+ {
+ /* number of bytes is odd; last byte becomes new hang_over */
+ hang_over = temp_hang_over;
+ left_over = -1;
+ start_cnt = 0;
+ end_cnt = nread - 1;
+ }
+ }
+ else if (nread == 1)
+ {
+ /* if only one byte has been read, save it as hang_over and return
+ length=0 */
+ hang_over = (int) *data;
+ *length = 0;
+ return SANE_STATUS_GOOD;
+ }
+ else
+ {
+ /* no hang_over; test for even or odd byte number */
+ if(is_even == 1)
+ {
+ start_cnt = 0;
+ end_cnt = *length;
+ }
+ else
+ {
+ start_cnt = 0;
+ hang_over = *(data + *length - 1);
+ *length -= 1;
+ end_cnt = *length;
+ }
+ }
+ /* swap the bytes */
+ for (cnt = start_cnt; cnt < end_cnt - 1; cnt += 2)
+ {
+ swap_buf = *(data + cnt);
+ *(data + cnt) = *(data + cnt + 1);
+ *(data + cnt + 1) = swap_buf;
+ }
+ }
+ DBG (3, "sane_read: %lu bytes read, %lu remaining\n", (u_long) nread,
+ (u_long) s->bytes_remaining);
+
+ return SANE_STATUS_GOOD;
+}
+
+void
+sane_cancel (SANE_Handle handle)
+{
+ Net_Scanner *s = handle;
+ SANE_Word ack;
+
+ DBG (3, "sane_cancel: sending net_cancel\n");
+
+ sanei_w_call (&s->hw->wire, SANE_NET_CANCEL,
+ (WireCodecFunc) sanei_w_word, &s->handle,
+ (WireCodecFunc) sanei_w_word, &ack);
+ do_cancel (s);
+ DBG (4, "sane_cancel: done\n");
+}
+
+SANE_Status
+sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
+{
+ Net_Scanner *s = handle;
+
+ DBG (3, "sane_set_io_mode: non_blocking = %d\n", non_blocking);
+ if (s->data < 0)
+ {
+ DBG (1, "sane_set_io_mode: pipe doesn't exist\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ if (fcntl (s->data, F_SETFL, non_blocking ? O_NONBLOCK : 0) < 0)
+ {
+ DBG (1, "sane_set_io_mode: fcntl failed (%s)\n", strerror (errno));
+ 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;
+
+ DBG (3, "sane_get_select_fd\n");
+
+ if (s->data < 0)
+ {
+ DBG (1, "sane_get_select_fd: pipe doesn't exist\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ *fd = s->data;
+ DBG (3, "sane_get_select_fd: done; *fd = %d\n", *fd);
+ return SANE_STATUS_GOOD;
+}