kopia lustrzana https://gitlab.com/sane-project/backends
999 wiersze
25 KiB
C
999 wiersze
25 KiB
C
/* 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];
|
|
char *net_resource;
|
|
|
|
dev->auth_active = 1;
|
|
|
|
memset (&req, 0, sizeof (req));
|
|
|
|
net_resource = malloc (strlen (resource) + 6 + strlen (dev->name));
|
|
|
|
if (net_resource != NULL)
|
|
sprintf (net_resource, "net:%s:%s", dev->name, resource);
|
|
else
|
|
{
|
|
SANE_Word ack;
|
|
|
|
DBG (1, "do_authorization: not enough memory\n");
|
|
req.resource = resource;
|
|
sanei_w_call (&dev->wire, SANE_NET_AUTHORIZE,
|
|
(WireCodecFunc) sanei_w_authorization_req, &req,
|
|
(WireCodecFunc) sanei_w_word, &ack);
|
|
|
|
return;
|
|
}
|
|
|
|
if (auth_callback)
|
|
(*auth_callback) (net_resource, username, password);
|
|
|
|
free (net_resource);
|
|
|
|
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 (sanei_config_read (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;
|
|
}
|
|
|
|
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, "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);
|
|
|
|
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)
|
|
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 (((SANE_Word) option >= s->opt.num_options) || (option < 0))
|
|
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 (((SANE_Word) option >= s->opt.num_options) || (option < 0))
|
|
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;
|
|
}
|
|
|
|
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;
|
|
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
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, "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;
|
|
socklen_t 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;
|
|
}
|
|
|
|
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;
|
|
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);
|
|
|
|
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)
|
|
{
|
|
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 == (size_t) - 1)
|
|
{
|
|
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 > (SANE_Int) s->bytes_remaining)
|
|
max_length = s->bytes_remaining;
|
|
#if 0
|
|
/* Make sure to get only complete pixel */
|
|
if (0 != max_length % bytes_per_pixel)
|
|
max_length = (max_length / bytes_per_pixel) * bytes_per_pixel;
|
|
#endif
|
|
/* XXX How do we handle endian problems */
|
|
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;
|
|
}
|