/* sane - Scanner Access Now Easy.
   Copyright (C) 1996, 1997 David Mosberger-Tang
   Copyright (C) 2003 Frank Zago
   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 provides a generic SCSI interface.  */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/types.h>

#define STUBBED_INTERFACE	0
#define LINUX_INTERFACE		1
#define BSD_INTERFACE		2
#define	HPUX_INTERFACE		3
#define OPENSTEP_INTERFACE	4
#define DECUNIX_INTERFACE	5
#define SCO_OS5_INTERFACE	6
#define IRIX_INTERFACE		7
#define SOLARIS_INTERFACE	8
#define SOLARIS_SG_INTERFACE	9
#define OS2_INTERFACE		10
#define AIX_GSC_INTERFACE	11
#define DOMAINOS_INTERFACE	12
#define FREEBSD_CAM_INTERFACE	13
#define SYSVR4_INTERFACE	14
#define SCO_UW71_INTERFACE	15
#define SOLARIS_USCSI_INTERFACE	16
#define MACOSX_INTERFACE	17
#define WIN32_INTERFACE		18

#ifdef HAVE_RESMGR
# include <resmgr.h>
#endif

#if defined (HAVE_SCSI_SG_H)
# define USE LINUX_INTERFACE
# include <scsi/sg.h>
#elif defined (HAVE__USR_SRC_LINUX_INCLUDE_SCSI_SG_H)
# define USE LINUX_INTERFACE
# include "/usr/src/linux/include/scsi/sg.h"
#elif defined (HAVE_SYS_SCSICMD)
# define USE SCSO_OS5_INTERFACE
# include <sys/scsi.h>
# include <sys/scsicmd.h>
#elif defined (HAVE_CAMLIB_H)
# define USE FREEBSD_CAM_INTERFACE
# include <stdio.h>		/* there is a bug in scsi_all.h */
# include <cam/cam.h>
# include <cam/cam_ccb.h>
# include <cam/scsi/scsi_message.h>
# include <cam/scsi/scsi_pass.h>
# include <camlib.h>
#elif defined (HAVE_SYS_SCSIIO_H)
# define USE BSD_INTERFACE
# include <sys/scsiio.h>
# ifdef HAVE_SCSI_H
#  include <scsi.h>
# endif
#elif defined (HAVE_BSD_DEV_SCSIREG_H)
# define USE OPENSTEP_INTERFACE
# include <bsd/dev/scsireg.h>
#elif defined (HAVE_IO_CAM_CAM_H)
# define USE DECUNIX_INTERFACE
# include <io/common/iotypes.h>
# include <io/cam/cam.h>
# include <io/cam/dec_cam.h>
# include <io/cam/uagt.h>
# include <io/cam/scsi_all.h>
#elif defined (HAVE_SYS_DSREQ_H)
# define USE IRIX_INTERFACE
# include <sys/dsreq.h>
# include <invent.h>
#elif defined (HAVE_SYS_SCSI_H)
# include <sys/scsi.h>
# ifdef HAVE_SYS_SDI_COMM_H
#  ifdef HAVE_SYS_PASSTHRUDEF_H
#   define USE SCO_UW71_INTERFACE
#   include <sys/scsi.h>
#   include <sys/sdi_edt.h>
#   include <sys/sdi.h>
#   include <sys/passthrudef.h>
#  else
#   define USE SYSVR4_INTERFACE	/* Unixware 2.x tested */
#   define HAVE_SYSV_DRIVER
#   include <sys/sdi_edt.h>
#   include <sys/sdi_comm.h>
#  endif
# else
#  ifdef SCTL_READ
#   define USE HPUX_INTERFACE
#  else
#   ifdef HAVE_GSCDDS_H
#    define USE AIX_GSC_INTERFACE
#    include <gscdds.h>
#   else
    /* This happens for AIX without gsc and possibly other platforms... */
#   endif
#  endif
# endif
#elif defined (HAVE_OS2_H)
# define USE OS2_INTERFACE
# define INCL_DOSFILEMGR
# define INCL_DOS
# define INCL_DOSDEVICES
# define INCL_DOSDEVIOCTL
# define INCL_DOSMEMMGR
# include <os2.h>
# include "os2_srb.h"
#elif defined (HAVE_SYS_SCSI_SGDEFS_H)
# define USE SOLARIS_SG_INTERFACE
# include <sys/scsi/sgdefs.h>
#elif defined (HAVE_SYS_SCSI_TARGETS_SCGIO_H)
# define USE SOLARIS_INTERFACE
# define SOL2
# include <sys/scsi/targets/scgio.h>
#elif defined (HAVE_SYS_SCSI_SCSI_H)
  /*
   * the "offical" solaris uscsi(7I) interface; comes last, so that users
   * installing the SCG/SG driver can still use these generic scsi interfaces
   */
# define USE SOLARIS_USCSI_INTERFACE
# define SOL2
# include <sys/scsi/scsi.h>
#elif defined (HAVE_APOLLO_SCSI_H)
# define USE DOMAINOS_INTERFACE
# include <signal.h>		/* Only used for signal name for KillDomainServer */
# include <apollo/base.h>
# include <apollo/ec2.h>
# include <apollo/error.h>
# include <apollo/ms.h>
# include <apollo/mutex.h>
# include <apollo/scsi.h>
# include <apollo/time.h>
# include "sanei_DomainOS.h"
#elif defined (HAVE_IOKIT_CDB_IOSCSILIB_H) || \
      defined (HAVE_IOKIT_SCSI_SCSICOMMANDOPERATIONCODES_H) || \
      defined (HAVE_IOKIT_SCSI_COMMANDS_SCSICOMMANDOPERATIONCODES_H)
# define USE MACOSX_INTERFACE
# include <CoreFoundation/CoreFoundation.h>
# include <IOKit/IOKitLib.h>
# ifdef HAVE_IOKIT_CDB_IOSCSILIB_H
#  include <IOKit/IOCFPlugIn.h>
#  include <IOKit/cdb/IOSCSILib.h>
# endif
# ifdef HAVE_IOKIT_SCSI_SCSICOMMANDOPERATIONCODES_H
/* The def of VERSION causes problems in the following include files */
#  undef VERSION
#  include <IOKit/scsi/SCSICmds_INQUIRY_Definitions.h>
#  include <IOKit/scsi/SCSICommandOperationCodes.h>
#  include <IOKit/scsi/SCSITaskLib.h>
# else
# ifdef HAVE_IOKIT_SCSI_COMMANDS_SCSICOMMANDOPERATIONCODES_H
/* The def of VERSION causes problems in the following include files */
#  undef VERSION
#  include <IOKit/scsi-commands/SCSICmds_INQUIRY_Definitions.h>
#  include <IOKit/scsi-commands/SCSICommandOperationCodes.h>
#  include <IOKit/scsi-commands/SCSITaskLib.h>
# endif
# endif
#elif defined (HAVE_WINDOWS_H)
# define USE WIN32_INTERFACE
# include <windows.h>
# include <ddk/scsi.h>
# include <ddk/ntddscsi.h>
#endif

#ifndef USE
# define USE STUBBED_INTERFACE
#endif

#if USE == LINUX_INTERFACE
# include <dirent.h>
#endif

#include "sane/sanei.h"
#include "sane/sanei_config.h"
#include "sane/sanei_scsi.h"

#ifdef BACKEND_NAME
#undef BACKEND_NAME
#endif

#define BACKEND_NAME	sanei_scsi
#include "sane/sanei_debug.h"

#if USE == DECUNIX_INTERFACE
static int cam_fd = -1;		/* used for SCSI CAM based interfaces */
#endif

#if USE == SOLARIS_INTERFACE || USE == SOLARIS_USCSI_INTERFACE
static int unit_ready (int fd);
#endif

#ifdef SG_BIG_BUFF
# define MAX_DATA	SG_BIG_BUFF
#endif

#if USE == SYSVR4_INTERFACE
# define MAX_DATA 56*1024	/* don't increase or kernel will dump 
				 * tested with adsl, adsa and umax backend 
				 * it depends on the lowend scsi 
				 * drivers . But the most restriction 
				 * is in the UNIXWARE KERNEL witch do 
				 * not allow more then 64kB DMA transfers */
static char lastrcmd[16];	/* hold command block of last read command */
#endif

#if USE == OPENSTEP_INTERFACE
# define MAX_DATA	(120*1024)
#endif

#if USE == IRIX_INTERFACE
# define MAX_DATA	(256*1024)
#endif

#if USE == FREEBSD_CAM_INTERFACE
# define MAX_DATA	(DFLTPHYS - PAGE_SIZE)
#endif

#if USE == SOLARIS_INTERFACE
# define MAX_DATA	(128*1024)
#endif

#if USE == SOLARIS_SG_INTERFACE
# define MAX_DATA	(128*1024)
#endif

#if USE == SOLARIS_USCSI_INTERFACE
# define MAX_DATA	(64*1024)
#endif

#if USE == OS2_INTERFACE
# define MAX_DATA	(64*1024)
#endif

#if USE == MACOSX_INTERFACE
# define MAX_DATA	(128*1024)
#endif


#ifndef MAX_DATA
# define MAX_DATA	(32*1024)
#endif

#ifdef SG_SET_TIMEOUT
# ifdef _SC_CLK_TCK
#  define GNU_HZ  sysconf(_SC_CLK_TCK)
# else
#  ifdef HZ
#   define GNU_HZ  HZ
#  else
#   ifdef CLOCKS_PER_SEC
#    define GNU_HZ  CLOCKS_PER_SEC
#   endif
#  endif
# endif
#endif

/* default timeout value: 120 seconds */
static int sane_scsicmd_timeout = 120;
int sanei_scsi_max_request_size = MAX_DATA;
#if USE == LINUX_INTERFACE
/* the following #defines follow Douglas Gilbert's sample code
   to maintain run time compatibility with the old and the
   new SG driver for Linux
*/
#include "linux_sg3_err.h"	/* contains several definitions of error codes */
#ifndef SG_SET_COMMAND_Q
#define SG_SET_COMMAND_Q 0x2271
#endif
#ifndef SG_SET_RESERVED_SIZE
#define SG_SET_RESERVED_SIZE 0x2275
#endif
#ifndef SG_GET_RESERVED_SIZE
#define SG_GET_RESERVED_SIZE 0x2272
#endif
#ifndef SG_GET_SCSI_ID
#define SG_GET_SCSI_ID 0x2276
#else
#define SG_GET_SCSI_ID_FOUND
#endif
#ifndef SG_GET_VERSION_NUM
#define SG_GET_VERSION_NUM 0x2282
#endif
#ifndef SG_NEXT_CMD_LEN
#define SG_NEXT_CMD_LEN 0x2283
#endif

#ifndef SCSIBUFFERSIZE
#define SCSIBUFFERSIZE (128 * 1024)
#endif

/* the struct returned by the SG ioctl call SG_GET_SCSI_ID changed
   from version 2.1.34 to 2.1.35, and we need the informations from
   the field s_queue_depth, which was introduced in 2.1.35.
   To get this file compiling also with older versions of sg.h, the
   struct is re-defined here.
*/
typedef struct xsg_scsi_id
{
  int host_no;			/* as in "scsi<n>" where 'n' is one of 0, 1, 2 etc */
  int channel;
  int scsi_id;			/* scsi id of target device */
  int lun;
  int scsi_type;		/* TYPE_... defined in scsi/scsi.h */
  short h_cmd_per_lun;		/* host (adapter) maximum commands per lun */
  short d_queue_depth;		/* device (or adapter) maximum queue length */
  int unused1;			/* probably find a good use, set 0 for now */
  int unused2;			/* ditto */
}
SG_scsi_id;

typedef struct req
{
  struct req *next;
  int fd;
  u_int running:1, done:1;
  SANE_Status status;
  size_t *dst_len;
  void *dst;
/* take the definition of the ioctl parameter SG_IO as a
   compiler flag if the new SG driver is available
*/
  union
  {
    struct
    {
      struct sg_header hdr;
      /* Make sure this is the last element, the real size is
         SG_BIG_BUFF and machine dependant */
      u_int8_t data[1];
    }
    cdb;
#ifdef SG_IO
/* at present, Linux's SCSI system limits the sense buffer to 16 bytes
   which is definitely too small. Hoping that this will change at some time,
   let's set the sense buffer size to 64.
*/
#define SENSE_MAX 64
#define MAX_CDB 12
    struct
    {
      struct sg_io_hdr hdr;
      u_char sense_buffer[SENSE_MAX];
      u_int8_t data[1];
    }
    sg3;
#endif
  }
  sgdata;
}
req;

typedef struct Fdparms
{
  int sg_queue_used, sg_queue_max;
  size_t buffersize;
  req *sane_qhead, *sane_qtail, *sane_free_list;
}
fdparms;

#endif

#if USE == FREEBSD_CAM_INTERFACE
# define CAM_MAXDEVS	128
struct cam_device *cam_devices[CAM_MAXDEVS] = { NULL };
#endif

static struct
{
  u_int in_use:1;		/* is this fd_info in use? */
  u_int fake_fd:1;		/* is this a fake file descriptor? */
  u_int bus, target, lun;	/* nexus info; used for some interfaces only */
  SANEI_SCSI_Sense_Handler sense_handler;
  void *sense_handler_arg;
  void *pdata;			/* platform-specific data */
}
 *fd_info;

static u_char cdb_sizes[8] = {
  6, 10, 10, 12, 12, 12, 10, 10
};
#define CDB_SIZE(opcode)	cdb_sizes[(((opcode) >> 5) & 7)]


#if USE == DOMAINOS_INTERFACE

/*
   This includes the server code.  Most of these routines are private to the
   actual server.  The only public ones are:
   sanei_DomainOS_init     Used to initialize the server
   DomainErrorCheck        A common error handling routine
 */

#include "sanei_DomainOS.c"

int ServerInitialized = 0;
pid_t ServerPID;
struct DomainServerCommon *com;
long CommandTriggerValue[2];
ec2_$ptr_t CommandAcceptedPtr[2];
long ResultTriggerValue[2];
ec2_$ptr_t ResultReadyPtr[2];
time_$clock_t Wait16S = { 64, 0 };	/* Delay of about 16 Seconds */


/* This function is registered as an exit function.  It's purpose is
   to make sure that the Domain SANE Server is stopped.  It tries to
   send an Exit command, and if that fails, it will send SIGQUIT to
   the server.  It will also unmap the common area before it
   returns. */
static void
KillDomainServer (void)
{
  static boolean GotTheLock;
  static status_$t status;
  static pinteger index;

  DBG (1, "Asking Domain SANE Server to exit\n");
  /* First, try to send a command to exit */
  if (GotTheLock = mutex_$lock (&com->CommandLock, Wait16S))
    {
      /* Set the wait time to 16 Seconds (units are 4uS) */
      com->opcode = Exit;
      CommandTriggerValue[0] = ec2_$read (com->CommandAccepted) + 1;
      ec2_$advance (&com->CommandAvailable, &status);
      DomainErrorCheck (status, "Can't advance CommandAvailable EC");
      /* For this wait, we want to allow a timeout as well */
      CommandTriggerValue[1] = (ec2_$read (*CommandAcceptedPtr[1])
				+ DomainECWaitConstant);
      index = ec2_$wait_svc (CommandAcceptedPtr, CommandTriggerValue, 2,
			     &status);
      DomainErrorCheck (status,
			"Error waiting on Exit command acceptance EC");
      /* Release the lock */
      mutex_$unlock (&com->CommandLock);
      if (index == 1)
	DBG (1, "Domain SANE Server responded to exit request\n");
      else
	DBG (1, "Domain SANE Server did not respond to exit request\n");
    }
  else
    DBG (0, "Could not get mutex lock for killing server\n");
  if ((!GotTheLock) || (index != 1))
    {
      /* If we get here, then we never got the mutex lock, or we timed out
         waiting for an Exit command ack. */
      /* It's now time to be brutal with the server */
      DBG (1, "Sending QUIT signal to Domain SANE Server\n");
      kill (ServerPID, SIGQUIT);
    }
  /* unmap the common area */
  ms_$unmap (com, sizeof (struct DomainServerCommon), &status);
  DomainErrorCheck (status, "Error unmapping common area");
}
#endif /* USE == DOMAINOS_INTERFACE */


#if USE == OS2_INTERFACE

/* Driver info:  */
static HFILE driver_handle = 0;	/* file handle for device driver */
static PVOID aspi_buf = 0;	/* Big data buffer locked by driver. */
static int aspi_ref_count = 0;	/* # of fds using ASPI */
static SRB *PSRBlock = 0;	/* SCSI Request Block */
static char tmpAspi[MAXPATHLEN];	/* scsi chain scan */
#define INQUIRY					0x12
#define set_inquiry_return_size(icb,val)	icb[0x04]=val
#define IN_periph_devtype_cpu			0x03
#define IN_periph_devtype_scanner		0x06
#define get_inquiry_vendor(in, buf)		strncpy(buf, in + 0x08, 0x08)
#define get_inquiry_product(in, buf)		strncpy(buf, in + 0x10, 0x010)
#define get_inquiry_version(in, buf)		strncpy(buf, in + 0x20, 0x04)
#define get_inquiry_periph_devtype(in)		(in[0] & 0x1f)
#define get_inquiry_additional_length(in)	in[0x04]
#define set_inquiry_length(out,n)		out[0x04]=n-5

/* Open OS2 ASPI driver.

   Output: 0 if error, which is reported.  */
static int
open_aspi (void)
{
  ULONG rc;
  ULONG ActionTaken;
  USHORT lockSegmentReturn;
  unsigned long cbreturn = 0;
  unsigned long cbParam = 0;
  int i, num_adapters;		/* no. of scsi adapters installed */

  char *devtypes[] = {
    "disk", "tape", "printer", "processor", "CD-writer",
    "CD-drive", "scanner", "optical-drive", "jukebox",
    "communicator"
  };
  FILE *tmp;

  if (driver_handle)
    {
      aspi_ref_count++;		/* increment internal usage counter */
      return 1;			/* Already open. */
    }
  aspi_buf = _tcalloc (sanei_scsi_max_request_size, 1);
  if (aspi_buf == NULL)
    {
      DBG (1, "sanei_scsi_open_aspi: _tcalloc aspi_buf failed");
      return 0;
    }

  PSRBlock = _tcalloc (sizeof (SRB), 1);
  if (PSRBlock == NULL)
    {
      DBG (1, "sanei_scsi_open_aspi: _tcalloc PSRBlock failed");
      return 0;
    }

  rc = DosOpen ((PSZ) "aspirou$",	/* open driver */
		&driver_handle,
		&ActionTaken,
		0,
		0,
		FILE_OPEN,
		OPEN_SHARE_DENYREADWRITE | OPEN_ACCESS_READWRITE, NULL);
  if (rc)
    {
      /* opening failed -> return false */
      DBG (1, "open_aspi:  opening failed.\n");
      return 0;
    }

  /* Lock aspi_buf. */
  rc = DosDevIOCtl (driver_handle, 0x92, 0x04,	/* pass aspi_buf pointer */
		    (void *) aspi_buf, sizeof (PVOID),	/* to driver */
		    &cbParam, (void *) &lockSegmentReturn,
		    sizeof (USHORT), &cbreturn);
  if (rc || lockSegmentReturn)
    {
      /* DosDevIOCtl failed */
      DBG (1, "sanei_scsi_open_aspi:  Can't lock buffer. rc= %lu \n", rc);
      return 0;
    }

  /* query number of installed adapters */
  memset (PSRBlock, 0, sizeof (SRB));
  PSRBlock->cmd = SRB_Inquiry;	/* host adapter inquiry */

  PSRBlock->ha_num = 0;		/* host adapter number */

  PSRBlock->flags = 0;		/* no flags set */

  rc = DosDevIOCtl (driver_handle, 0x92, 0x02,
		    (void *) PSRBlock, sizeof (SRB), &cbParam,
		    (void *) PSRBlock, sizeof (SRB), &cbreturn);
  num_adapters = PSRBlock->u.inq.num_ha;

  DBG (1, "OS/2: installed adapters %d\n", num_adapters);
  DBG (1, "OS/2: ASPI manager is '%s'\n", PSRBlock->u.inq.aspimgr_id);
  DBG (1, "OS/2: host adapter is '%s'\n", PSRBlock->u.inq.host_id);
  DBG (1, "OS/2: unique id is    '%s'\n", PSRBlock->u.inq.unique_id);

  strcpy (tmpAspi, "asXXXXXX");
  mktemp (tmpAspi);
  DBG (2, "open_aspi: open temporary file '%s'\n", tmpAspi);
  tmp = fopen (tmpAspi, "w");
  if (!tmp)
    {				/* can't open tmp file */

      DBG (1, "open_aspi:  Can't open temporary file.\n");
      return 0;
    }

  /* scan all installed adapters */
  for (i = 0; i < num_adapters; i++)
    {
      int id;
      /* query adapter name */
      memset (PSRBlock, 0, sizeof (SRB));
      PSRBlock->cmd = SRB_Inquiry;	/* host adapter inquiry */

      PSRBlock->ha_num = i;	/* host adapter number */

      PSRBlock->flags = 0;	/* no flags set */

      rc = DosDevIOCtl (driver_handle, 0x92, 0x02,
			(void *) PSRBlock, sizeof (SRB), &cbParam,
			(void *) PSRBlock, sizeof (SRB), &cbreturn);
      DBG (1, "OS/2: adapter#%02d '%s'\n", i, PSRBlock->u.inq.host_id);

      /* scan scsi chain (need 15 for wide?) */
      for (id = 0; id < 7; id++)
	{
	  unsigned char len;
	  char vendor[9];
	  char product[17];
	  char version[5];
	  char *pp;

	  memset (PSRBlock, 0, sizeof (SRB));
	  PSRBlock->cmd = SRB_Device;	/* get device type */

	  PSRBlock->ha_num = i;	/* host adapter number */

	  PSRBlock->flags = 0;	/* no flags set */

	  PSRBlock->u.dev.target = id;	/* target id */

	  PSRBlock->u.dev.lun = 0;	/* target LUN */

	  rc = DosDevIOCtl (driver_handle, 0x92, 0x02,
			    (void *) PSRBlock, sizeof (SRB), &cbParam,
			    (void *) PSRBlock, sizeof (SRB), &cbreturn);
	  DBG (1, "OS/2:             id#%02d status=%02xh\n",
	       id, PSRBlock->status);

	  /* skip if device not connected */
	  if (PSRBlock->status == SRB_BadDevice)
	    continue;

	  DBG (1, "OS/2:                   type is '%s'\n",
	       PSRBlock->u.dev.devtype < sizeof (devtypes) / sizeof (char *)?
	       devtypes[PSRBlock->u.dev.devtype] : "unknown device");

	  /* query adapter string id */
	  memset (PSRBlock, 0, sizeof (SRB));
	  PSRBlock->cmd = SRB_Command;	/* execute SCSI command */

	  PSRBlock->ha_num = i;	/* host adapter number */
	  PSRBlock->flags = SRB_Read | SRB_Post;	/* data transfer, posting */
	  PSRBlock->u.cmd.target = id;	/* Target SCSI ID */
	  PSRBlock->u.cmd.lun = 0;	/* Target SCSI LUN */
	  PSRBlock->u.cmd.data_len = 5;	/* # of bytes transferred */
	  PSRBlock->u.cmd.sense_len = 32;	/* length of sense buffer */
	  PSRBlock->u.cmd.data_ptr = NULL;	/* pointer to data buffer */
	  PSRBlock->u.cmd.link_ptr = NULL;	/* pointer to next SRB */
	  PSRBlock->u.cmd.cdb_len = 6;	/* SCSI command length */
	  PSRBlock->u.cmd.cdb_st[0] = INQUIRY;	/* inquiry command */
	  PSRBlock->u.cmd.cdb_st[1] = 0;	/* ?? length */
	  PSRBlock->u.cmd.cdb_st[2] = 0;	/* transfer length MSB */
	  PSRBlock->u.cmd.cdb_st[3] = 0;	/* transfer length */
	  PSRBlock->u.cmd.cdb_st[4] = 5;	/* transfer length LSB */
	  PSRBlock->u.cmd.cdb_st[5] = 0;
	  rc = DosDevIOCtl (driver_handle, 0x92, 0x02,
			    (void *) PSRBlock, sizeof (SRB), &cbParam,
			    (void *) PSRBlock, sizeof (SRB), &cbreturn);
	  len = ((char *) aspi_buf)[4];	/* additional length */

	  /* query id string */
	  memset (PSRBlock, 0, sizeof (SRB));
	  PSRBlock->cmd = SRB_Command;	/* execute SCSI command */
	  PSRBlock->ha_num = i;	/* host adapter number */
	  PSRBlock->flags = SRB_Read | SRB_Post;	/* data transfer, posting */
	  PSRBlock->u.cmd.target = id;	/* Target SCSI ID */
	  PSRBlock->u.cmd.lun = 0;	/* Target SCSI LUN */
	  PSRBlock->u.cmd.data_len = 5 + len;	/* # of bytes transferred */
	  PSRBlock->u.cmd.sense_len = 32;	/* length of sense buffer */
	  PSRBlock->u.cmd.data_ptr = NULL;	/* pointer to data buffer */
	  PSRBlock->u.cmd.link_ptr = NULL;	/* pointer to next SRB */
	  PSRBlock->u.cmd.cdb_len = 6;	/* SCSI command length */
	  PSRBlock->u.cmd.cdb_st[0] = 0x12;	/* inquiry command */
	  PSRBlock->u.cmd.cdb_st[1] = 0;	/* ?? length */
	  PSRBlock->u.cmd.cdb_st[2] = 0;	/* transfer length MSB */
	  PSRBlock->u.cmd.cdb_st[3] = 0;	/* transfer length */
	  PSRBlock->u.cmd.cdb_st[4] = 5 + len;	/* transfer length LSB */
	  PSRBlock->u.cmd.cdb_st[5] = 0;
	  rc = DosDevIOCtl (driver_handle, 0x92, 0x02,
			    (void *) PSRBlock, sizeof (SRB), &cbParam,
			    (void *) PSRBlock, sizeof (SRB), &cbreturn);
	  DBG (1, "OS/2         '%s'\n", (char *) aspi_buf + 8);
	  /* write data */
	  get_inquiry_vendor ((char *) aspi_buf, vendor);
	  get_inquiry_product ((char *) aspi_buf, product);
	  get_inquiry_version ((char *) aspi_buf, version);

	  pp = &vendor[7];
	  vendor[8] = '\0';
	  while (pp >= vendor && (*pp == ' ' || *pp >= 127))
	    *pp-- = '\0';

	  pp = &product[15];
	  product[16] = '\0';
	  while (pp >= product && (*pp == ' ' || *pp >= 127))
	    *pp-- = '\0';

	  pp = product;
	  do
	    {
	      if (isspace ((int) *pp))
		*pp = '_';
	    }
	  while (*++pp);

	  pp = &version[3];
	  version[4] = '\0';
	  while (pp >= version && (*pp == ' ' || *(pp - 1) >= 127))
	    *pp-- = '\0';
	  fprintf (tmp, "Vendor: %s ", vendor);
	  fprintf (tmp, "Model: %s ", product);
	  fprintf (tmp, "Rev: %s ", version);
	  fprintf (tmp, "scsi %d Channel: 0 Id: %d Lun: 0\n", i, id);
	}
    }
  DBG (2, "open_aspi: close temporary file '%s'\n", tmpAspi);
  fclose (tmp);

  aspi_ref_count++;		/* increment internal usage counter */

  return 1;
}

/* Close driver and free everything.  */

static void
close_aspi (void)
{
  aspi_ref_count--;		/* decrement internal usage counter */

  if (aspi_ref_count)
    return;			/* wait for usage==0 */

  if (driver_handle)		/* Close driver. */
    DosClose (driver_handle);
  driver_handle = 0;
  if (aspi_buf)			/* Free buffer. */
    _tfree (aspi_buf);
  aspi_buf = 0;

  if (PSRBlock)
    _tfree (PSRBlock);
  PSRBlock = 0;

  errno = 0;
  if (unlink (tmpAspi))		/* remove scsi descriptions */
    DBG (2, "OS/2: error#%d while removing temporary '%s'\n", errno, tmpAspi);
  strcpy (tmpAspi, "");

  DBG (1, "OS/2: ASPI closed\n");
}

#endif /* USE_OS2_INTERFACE */

static int num_alloced = 0;

#if USE == LINUX_INTERFACE

static int sg_version = 0;

static SANE_Status
get_max_buffer_size (const char *file)
{
  int fd = -1;
  int buffersize = SCSIBUFFERSIZE, i;
  size_t len;
  char *cc, *cc1, buf[32];

#ifdef HAVE_RESMGR
  fd = rsm_open_device(file, O_RDWR);
#endif

  if (fd == -1)
    fd = open (file, O_RDWR);

  if (fd > 0)
    {
      cc = getenv ("SANE_SG_BUFFERSIZE");
      if (cc)
	{
	  i = strtol (cc, &cc1, 10);
	  if (cc != cc1 && i >= 32768)
	    buffersize = i;
	}

      ioctl (fd, SG_SET_RESERVED_SIZE, &buffersize);
      if (0 == ioctl (fd, SG_GET_RESERVED_SIZE, &buffersize))
	{
	  if (buffersize < sanei_scsi_max_request_size)
	    sanei_scsi_max_request_size = buffersize;
	  close (fd);
	  DBG (4, "get_max_buffer_size for %s: %i\n", file,
	       sanei_scsi_max_request_size);
	  return SANE_STATUS_GOOD;
	}
      else
	{
	  close (fd);
	  /* ioctl not available: we have the old SG driver */
	  fd = open ("/proc/sys/kernel/sg-big-buff", O_RDONLY);
	  if (fd > 0 && (len = read (fd, buf, sizeof (buf) - 1)) > 0)
	    {
	      buf[len] = '\0';
	      sanei_scsi_max_request_size = atoi (buf);
	      close (fd);
	    }
	  else
	    sanei_scsi_max_request_size = buffersize < SG_BIG_BUFF ?
	      buffersize : SG_BIG_BUFF;
	  return SANE_STATUS_IO_ERROR;
	}
    }
  else
    return SANE_STATUS_GOOD;
}


SANE_Status
sanei_scsi_open_extended (const char *dev, int *fdp,
			  SANEI_SCSI_Sense_Handler handler,
			  void *handler_arg, int *buffersize)
#else

SANE_Status
sanei_scsi_open (const char *dev, int *fdp,
		 SANEI_SCSI_Sense_Handler handler, void *handler_arg)
#endif
{
  u_int bus = 0, target = 0, lun = 0, fake_fd = 0;
  char *real_dev = 0;
  void *pdata = 0;
  char *cc, *cc1;
  int fd, i;
#if USE == LINUX_INTERFACE
  static int first_time = 1;
#elif USE == MACOSX_INTERFACE  
  UInt8 *guid;
  int len;
  u_int d;
#endif

  cc = getenv ("SANE_SCSICMD_TIMEOUT");
  if (cc)
    {
      i = strtol (cc, &cc1, 10);
      /* 20 minutes are hopefully enough as a timeout value ;) */
      if (cc != cc1 && i > 0 && i <= 1200)
	{
	  sane_scsicmd_timeout = i;
	}
      else
	{
	  DBG (1,
	       "sanei_scsi_open: timeout value must be between 1 and 1200 seconds\n");
	}
    }

  DBG_INIT ();

#if USE == LINUX_INTERFACE
  if (first_time)
    {
      first_time = 0;

      /* Try to determine a reliable value for sanei_scsi_max_request_size:

         With newer versions of the SG driver, check the available buffer
         size by opening all SG device files belonging to a scanner,
         issue the ioctl calls for setting and reading the reserved
         buffer size, and take the smallest value. 

         For older version of the SG driver, which don't support variable
         buffer size, try to read /proc/sys/kernel/sg-big-biff ; if
         this fails (SG driver too old, or loaded as a module), use
         SG_BIG_BUFF
       */

      sanei_scsi_max_request_size = SCSIBUFFERSIZE;
      cc = getenv ("SANE_SG_BUFFERSIZE");
      if (cc)
	{
	  i = strtol (cc, &cc1, 10);
	  if (cc != cc1 && i >= 32768)
	    sanei_scsi_max_request_size = i;
	}
      sanei_scsi_find_devices (0, 0, "Scanner", -1, -1, -1, -1,
			       get_max_buffer_size);
      sanei_scsi_find_devices (0, 0, "Processor", -1, -1, -1, -1,
			       get_max_buffer_size);
      DBG (4, "sanei_scsi_open: sanei_scsi_max_request_size=%d bytes\n",
	   sanei_scsi_max_request_size);
    }
#endif

#if USE == OS2_INTERFACE
  if (sscanf (dev, "b%ut%ul%u", &bus, &target, &lun) != 3)
    {
      DBG (1, "sanei_scsi_open: device name %s is not valid\n", dev);
      return SANE_STATUS_INVAL;
    }
  if (!open_aspi ())
    {
      /* Open driver if necessary. */
      close_aspi ();
      return SANE_STATUS_INVAL;
    }

  /* Find fake fd. */
  for (fd = 0; fd < num_alloced; ++fd)
    if (!fd_info[fd].in_use)
      break;
  fake_fd = 1;
#elif USE == DECUNIX_INTERFACE
  {
    UAGT_CAM_SCAN cam_scan;

    if (sscanf (dev, "b%dt%dl%d", &bus, &target, &lun) != 3)
      {
	DBG (1, "sanei_scsi_open: device name `%s' is not valid: %s\n",
	     dev, strerror (errno));
	return SANE_STATUS_INVAL;
      }

    if (cam_fd < 0)
      {
	cam_fd = open ("/dev/cam", O_RDWR);
	if (cam_fd < 0)
	  {
	    DBG (1, "sanei_scsi_open: open(/dev/cam) failed: %s\n",
		 strerror (errno));
	    return SANE_STATUS_INVAL;
	  }
      }
    cam_scan.ucs_bus = bus;
    cam_scan.ucs_target = target;
    cam_scan.ucs_lun = lun;
    if (ioctl (cam_fd, UAGT_CAM_SINGLE_SCAN, &cam_scan) < 0)
      {
	DBG (1, "sanei_scsi_open: ioctl(UAGT_CAM_SINGLE_SCAN) failed: %s\n",
	     strerror (errno));
	return SANE_STATUS_INVAL;
      }

    for (fd = 0; fd < num_alloced; ++fd)
      if (!fd_info[fd].in_use)
	break;
    fake_fd = 1;
  }
#elif USE == DOMAINOS_INTERFACE
  {
    static int index;
    static status_$t status;
    static unsigned long length_mapped;

    DBG (1, "sanei_scsi_open: (dev='%s', int * fdp=%p, "
	 "SANEI_SCSI_Sense_Handler handler=%p)\n", dev, fdp, handler);

    /* See if the server process has started yet */
    if (!ServerInitialized)
      {
	static char *CommonAreaPath;

	/* Initialize the server */
	DBG (2, "Initializing Domain Server\n");

	/* Map the area */
	CommonAreaPath = tmpnam (NULL);
	DBG (2, "Domain Server Common area name is '%s'\n", CommonAreaPath);
	com = ms_$crmapl (CommonAreaPath, strlen (CommonAreaPath), 0,
			  sizeof (struct DomainServerCommon), ms_$cowriters,
			  &status);
	DomainErrorCheck (status, "Can't open common area");
	DBG (2, "Domain Server common area mapped\n");

	/* Initialize the eventcounts */
	ec2_$init (&com->CommandAvailable);
	ec2_$init (&com->CommandAccepted);
	ec2_$init (&com->ResultReady);
	ec2_$init (&com->ResultAccepted);
	DBG (2, "Domain Server EC's initialized\n");
	/* Initialize the mutex locks */
	mutex_$init (&com->CommandLock);
	mutex_$init (&com->ResultLock);
	DBG (2, "Domain Server MutexLock's initialized\n");

	/* Initialize pointers to ECs */
	CommandAcceptedPtr[0] = &com->CommandAccepted;
	ResultReadyPtr[0] = &com->ResultReady;
	time_$get_ec (time_$clockh_key, &CommandAcceptedPtr[1], &status);
	DomainErrorCheck (status, "Can't get time EC");
	ResultReadyPtr[1] = CommandAcceptedPtr[1];

	/* Read the ResultReady EC value, to avoid race with the server */
	ResultTriggerValue[0] = ec2_$read (com->ResultReady) + 1;

	/* Now invoke the server */
	ServerPID = fork ();
	if (!ServerPID)
	  {
	    /* I am the child, call the initialization routine */
	    sanei_DomainOS_init (CommonAreaPath);
	    /* We get here when the server is done, so we just exit. */
	    exit (EXIT_SUCCESS);
	  }

	/* The communication area is open, wait for the initial response */
	ResultTriggerValue[1] = (ec2_$read (*ResultReadyPtr[1])
				 + DomainECWaitConstant);
	index =
	  ec2_$wait_svc (ResultReadyPtr, ResultTriggerValue, 2, &status);
	DomainErrorCheck (status, "Error waiting on initial open EC");
	if (index != 1)
	  {
	    DBG (0, "Domain SANE Server never responded on startup\n");
	    /* Send a quit signal to the server */
	    kill (ServerPID, SIGQUIT);
	    return SANE_STATUS_INVAL;
	  }
	/* Register a function to kill the server when we are done */
	assert (!atexit (KillDomainServer));
	ServerInitialized = 1;
      }

    /* Find fake fd. */
    for (fd = 0; fd < num_alloced; ++fd)
      if (!fd_info[fd].in_use)
	break;
    fake_fd = 1;

    /* Send the command open to the server */
    if (!mutex_$lock (&com->CommandLock, Wait16S))
      {
	DBG (0, "Could not obtain mutex lock for Open\n");
	return SANE_STATUS_INVAL;
      }
    com->opcode = Open;
    strcpy (com->open_path, dev);
    CommandTriggerValue[0] = ec2_$read (com->CommandAccepted) + 1;
    ec2_$advance (&com->CommandAvailable, &status);
    DomainErrorCheck (status, "Can't advance CommandAvailable EC");
    CommandTriggerValue[1] = (ec2_$read (*CommandAcceptedPtr[1])
			      + DomainECWaitConstant);
    index = ec2_$wait_svc (CommandAcceptedPtr, CommandTriggerValue, 2,
			   &status);
    DomainErrorCheck (status, "Error waiting on Open command acceptance EC");
    if (index != 1)
      {
	DBG (0, "Domain SANE Server never accepted Open Command\n");
	return SANE_STATUS_INVAL;
      }

    /* Read the result */
    status = com->CommandStatus;
    DomainErrorCheck (status, "Opening device in server");

    /* Now map the data area, and make it temporary */
    DBG (2, "Mapping server's data block, name is '%s'\n", com->open_path);
    pdata = ms_$mapl (com->open_path, strlen (com->open_path), 0,
		      DomainMaxDataSize + DomainSenseSize, ms_$cowriters,
		      ms_$wr, true, &length_mapped, &status);
    DomainErrorCheck (status, "Mapping Server Data block");
    assert (length_mapped >= DomainMaxDataSize + DomainSenseSize);
    ms_$mk_temporary (pdata, &status);
    DomainErrorCheck (status, "Can't make data block temporary");

    /* Release the lock */
    mutex_$unlock (&com->CommandLock);

    if (status.all != status_$ok)
      {
	/* we have a failure, return an error code, and generate debug
	   output */
	DBG (1, "sanei_scsi_open: acquire failed, Domain/OS status is %08x\n",
	     status.all);
	error_$print (status);
	return SANE_STATUS_INVAL;
      }
    else
      {
	/* device acquired, what else to do? */
	fd = com->fd;
      }
  }
#elif USE == FREEBSD_CAM_INTERFACE
  if (1)
    {				/* 'if(1) {' makes my emacs c-mode indent better than
				   just '{' unfortunately, this only works if all of
				   the '{' are that way. */

      struct cam_device *curdev;

      fake_fd = 1;
      fd = -1;

      if ((curdev = cam_open_pass (dev, O_RDWR, NULL)) != NULL)
	{
	  for (fd = 0; fd < CAM_MAXDEVS && cam_devices[fd] != NULL; fd++)
	    ;

	  if (fd == CAM_MAXDEVS)
	    {
	      DBG (1, "sanei_scsi_open: too many CAM devices (%d)\n", fd);
	      cam_close_device (curdev);
	      return SANE_STATUS_INVAL;
	    }
	  cam_devices[fd] = curdev;
	}
      else
	{
	  DBG (1, "sanei_scsi_open: can't open device `%s': %s\n", dev,
	       strerror (errno));
	  return SANE_STATUS_INVAL;
	}
    }
#elif USE == SCO_UW71_INTERFACE
  {
    pt_scsi_address_t dev_addr;
    pt_handle_t pt_handle;
    int bus, cnt, id, lun;

    if (4 !=
	sscanf (dev, "/dev/passthru0:%d,%d,%d,%d", &bus, &cnt, &id, &lun))
      {
	DBG (1, "sanei_scsi_open: device name `%s' is not valid: %s\n",
	     dev, strerror (errno));
	return SANE_STATUS_INVAL;
      }
    dev_addr.psa_bus = bus;
    dev_addr.psa_controller = cnt;
    dev_addr.psa_target = id;
    dev_addr.psa_lun = lun;

    if (0 != pt_open (PASSTHRU_SCSI_ADDRESS, &dev_addr, PT_EXCLUSIVE,
		      &pt_handle))
      {
	DBG (1, "sanei_scsi_open: pt_open failed: %s!\n", strerror (errno));
	return SANE_STATUS_INVAL;
      }
    else
      fd = (int) pt_handle;
  }
#elif USE == MACOSX_INTERFACE
  {
# if defined (HAVE_IOKIT_SCSI_SCSICOMMANDOPERATIONCODES_H) || \
     defined (HAVE_IOKIT_SCSI_COMMANDS_SCSICOMMANDOPERATIONCODES_H)
    len = strlen (dev);
    if (len > 2 && len % 2 == 0 && dev [0] == '<' && dev [len - 1] == '>')
      {
	len = (len - 2) / 2;
	guid = (UInt8 *) malloc (len);
	for (i = 0; i < len; i++)
	  {
	    if (sscanf (&dev [2 * i + 1], "%02x", &d) != 1)
	      break;
	    guid [i] = d;
	  }
	if (i == len)
	  pdata = (void *) CFDataCreate (kCFAllocatorDefault, guid, len);
	free (guid);
      }
# endif
# ifdef HAVE_IOKIT_CDB_IOSCSILIB_H
    if ((pdata == NULL) &&
	(sscanf (dev, "u%ut%ul%u", &bus, &target, &lun) != 3))
# else
    if (pdata == NULL)
# endif
      {
	DBG (1, "sanei_scsi_open: device name %s is not valid\n", dev);
	return SANE_STATUS_INVAL;
      }

    /* Find fake fd. */
    for (fd = 0; fd < num_alloced; ++fd)
      if (!fd_info[fd].in_use)
	break;
    fake_fd = 1;
  }
#elif USE == WIN32_INTERFACE
  {
	  char scsi_hca_name[20];
	  u_int hca = 0;
 
	  if (sscanf (dev, "h%ub%ut%ul%u", &hca, &bus, &target, &lun) != 4)
		  {
			  DBG (1, "sanei_scsi_open: device name %s is not valid\n", dev);
			  return SANE_STATUS_INVAL;
		  }
 
	  snprintf(scsi_hca_name, 19, "\\\\.\\Scsi%d:", hca);
	  scsi_hca_name[19] = 0;
	  
	  fd = CreateFile(scsi_hca_name, GENERIC_READ | GENERIC_WRITE,
					  FILE_SHARE_READ | FILE_SHARE_WRITE,
					  NULL, OPEN_EXISTING,
					  FILE_FLAG_RANDOM_ACCESS, NULL );
	  
	  if (fd == INVALID_HANDLE_VALUE) fd = -1;
  }
#else
#if defined(SGIOCSTL) || (USE == SOLARIS_INTERFACE)
  {
    size_t len;

    /* OpenStep and the Solaris SCG driver are a bit broken in that
       the device name refers to a scsi _bus_, not an individual scsi
       device.  Hence, SANE has to fudge with the device name so we
       know which target to connect to.  For this purpose, we use the
       last character in the device name as the target index.  'a' is
       target 0, 'b', target 1, and so on... */

    len = strlen (dev);
    if (len <= 1)
      {
	DBG (1, "sanei_scsi_open: devicename `%s' too short\n", dev);
	return SANE_STATUS_INVAL;
      }

    real_dev = strdup (dev);
    real_dev[len - 1] = '\0';

    target = dev[len - 1] - 'a';
    if (target > 7)
      {
	DBG (1, "sanei_scsi_open: `%c' is not a valid target id\n",
	     dev[len - 1]);
	return SANE_STATUS_INVAL;
      }
    dev = real_dev;
  }
#endif /* defined(SGIOCSTL) || (USE == SOLARIS_INTERFACE) */

  fd = -1;
#ifdef HAVE_RESMGR
  fd = rsm_open_device(dev, O_RDWR | O_EXCL | O_NONBLOCK);
#endif

  if (fd == -1)
    fd = open (dev, O_RDWR | O_EXCL
#if USE == LINUX_INTERFACE
	     | O_NONBLOCK
#endif
    );
  if (fd < 0)
    {
      SANE_Status status = SANE_STATUS_INVAL;

      if (errno == EACCES)
	status = SANE_STATUS_ACCESS_DENIED;
      else if (errno == EBUSY)
	status = SANE_STATUS_DEVICE_BUSY;

      DBG (1, "sanei_scsi_open: open of `%s' failed: %s\n",
	   dev, strerror (errno));
      return status;
    }

  if (real_dev)
    free (real_dev);

#ifdef SG_SET_TIMEOUT
  /* Set large timeout since some scanners are slow but do not
     disconnect... ;-( */
  {
    int timeout;
    timeout = sane_scsicmd_timeout * sysconf(_SC_CLK_TCK);
    ioctl (fd, SG_SET_TIMEOUT, &timeout);
  }
#endif

#ifdef SGIOCSTL
  {
    struct scsi_adr sa;

    sa.sa_target = target;
    sa.sa_lun = 0;
    if (ioctl (fd, SGIOCSTL, &sa) == -1)
      {
	DBG (1, "sanei_scsi_open: failed to attach to target: %u (%s)\n",
	     sa.sa_target, strerror (errno));
	return SANE_STATUS_INVAL;
      }
  }
#endif /* SGIOCSTL */
#if USE == LINUX_INTERFACE
  {
    SG_scsi_id sid;
    int ioctl_val;
    int real_buffersize;
    fdparms *fdpa = 0;
    SG_scsi_id devinfo;

    pdata = fdpa = malloc (sizeof (fdparms));
    if (!pdata)
      {
	close (fd);
	return SANE_STATUS_NO_MEM;
      }
    memset (fdpa, 0, sizeof (fdparms));
    /* default: allow only one command to be sent to the SG driver
     */
    fdpa->sg_queue_max = 1;

    /* Try to read the SG version. If the ioctl call is successful,
       we have the new SG driver, and we can increase the buffer size
       using another ioctl call.
       If we have SG version 2.1.35 or above, we can additionally enable
       command queueing.
     */
    if (0 == ioctl (fd, SG_GET_VERSION_NUM, &sg_version))
      {
	DBG (1, "sanei_scsi_open: SG driver version: %i\n", sg_version);

	ioctl_val = ioctl (fd, SG_GET_SCSI_ID, &devinfo);
	if (ioctl_val == EINVAL || ioctl_val == ENOTTY)
	  {
	    DBG (1, "sanei_scsi_open: The file %s is not an SG device file\n",
		 dev);
	    close (fd);
	    return SANE_STATUS_INVAL;
	  }

	if (devinfo.scsi_type != 6 && devinfo.scsi_type != 3)
	  {
	    DBG (1,
		 "sanei_scsi_open: The device found for %s does not look like a scanner\n",
		 dev);
	    close (fd);
	    return SANE_STATUS_INVAL;
	  }

	/* try to reserve a SG buffer of the size specified by *buffersize
	 */
	ioctl (fd, SG_SET_RESERVED_SIZE, buffersize);

	/* the set call may not be able to allocate as much memory
	   as requested, thus we read the actual buffer size.
	 */
	if (0 == ioctl (fd, SG_GET_RESERVED_SIZE, &real_buffersize))
	  {
	    /* if we got more memory than requested, we stick with
	       with the requested value, in order to allow
	       sanei_scsi_open to check the buffer size exactly.
	     */
	    if (real_buffersize < *buffersize)
	      *buffersize = real_buffersize;
	    fdpa->buffersize = *buffersize;
	  }
	else
	  {
	    DBG (1, "sanei_scsi_open: cannot read SG buffer size - %s\n",
		 strerror (errno));
	    close (fd);
	    return SANE_STATUS_NO_MEM;
	  }
	DBG (1, "sanei_scsi_open_extended: using %i bytes as SCSI buffer\n",
	     *buffersize);

	if (sg_version >= 20135)
	  {
	    DBG (1, "trying to enable low level command queueing\n");

	    if (0 == ioctl (fd, SG_GET_SCSI_ID, &sid))
	      {
		DBG (1, "sanei_scsi_open: Host adapter queue depth: %i\n",
		     sid.d_queue_depth);

		ioctl_val = 1;
		if (0 == ioctl (fd, SG_SET_COMMAND_Q, &ioctl_val))
		  {
		    fdpa->sg_queue_max = sid.d_queue_depth;
		    if (fdpa->sg_queue_max <= 0)
		      fdpa->sg_queue_max = 1;
		  }
	      }
	  }
      }
    else
      {
	/* we have a really old SG driver version, or we're not opening
	   an SG device file 
	 */
	if (ioctl (fd, SG_GET_TIMEOUT, &ioctl_val) < 0)
	  {
	    DBG (1, "sanei_scsi_open: The file %s is not an SG device file\n",
		 dev);
	    close (fd);
	    return SANE_STATUS_INVAL;
	  }
	if (sanei_scsi_max_request_size < *buffersize)
	  *buffersize = sanei_scsi_max_request_size;
	fdpa->buffersize = *buffersize;
      }
    if (sg_version == 0)
      {
	DBG (1, "sanei_scsi_open: using old SG driver logic\n");
      }
    else
      {
	DBG (1,
	     "sanei_scsi_open: SG driver can change buffer size at run time\n");
	if (fdpa->sg_queue_max > 1)
	  DBG (1, "sanei_scsi_open: low level command queueing enabled\n");
#ifdef SG_IO
	if (sg_version >= 30000)
	  {
	    DBG (1, "sanei_scsi_open: using new SG header structure\n");
	  }
#endif
      }
  }
#endif /* LINUX_INTERFACE */
#endif /* !DECUNIX_INTERFACE */

/* Note: this really relies on fd to start small. Windows starts a little higher than 3. */

  if (fd >= num_alloced)
    {
      size_t new_size, old_size;

      old_size = num_alloced * sizeof (fd_info[0]);
      num_alloced = fd + 8;
      new_size = num_alloced * sizeof (fd_info[0]);
      if (fd_info)
	fd_info = realloc (fd_info, new_size);
      else
	fd_info = malloc (new_size);
      memset ((char *) fd_info + old_size, 0, new_size - old_size);
      if (!fd_info)
	{
	  if (!fake_fd)
	    close (fd);
	  return SANE_STATUS_NO_MEM;
	}
    }
  fd_info[fd].in_use = 1;
  fd_info[fd].sense_handler = handler;
  fd_info[fd].sense_handler_arg = handler_arg;
  fd_info[fd].fake_fd = fake_fd;
  fd_info[fd].bus = bus;
  fd_info[fd].target = target;
  fd_info[fd].lun = lun;
  fd_info[fd].pdata = pdata;

#if USE == SOLARIS_INTERFACE || USE == SOLARIS_USCSI_INTERFACE
  /* verify that the device really exists: */
  if (!unit_ready (fd))
    {
      sanei_scsi_close (fd);
      return SANE_STATUS_INVAL;
    }
#endif
#if USE == SYSVR4_INTERFACE
  memset (lastrcmd, 0, 16);	/* reinitialize last read command block */
#endif

  if (fdp)
    *fdp = fd;

  return SANE_STATUS_GOOD;
}

#if USE == LINUX_INTERFACE
/* The "wrapper" for the old open call */
SANE_Status
sanei_scsi_open (const char *dev, int *fdp,
		 SANEI_SCSI_Sense_Handler handler, void *handler_arg)
{
  int i = 0;
  int wanted_buffersize = SCSIBUFFERSIZE, real_buffersize;
  SANE_Status res;
  char *cc, *cc1;
  static int first_time = 1;

  if (first_time)
    {
      cc = getenv ("SANE_SG_BUFFERSIZE");
      if (cc)
	{
	  i = strtol (cc, &cc1, 10);
	  if (cc != cc1 && i >= 32768)
	    wanted_buffersize = i;
	}
    }
  else
    wanted_buffersize = sanei_scsi_max_request_size;

  real_buffersize = wanted_buffersize;
  res = sanei_scsi_open_extended (dev, fdp, handler, handler_arg,
				  &real_buffersize);

  /* make sure that we got as much memory as we wanted, otherwise
     the backend might be confused
   */
  if (!first_time && real_buffersize != wanted_buffersize)
    {
      DBG (1, "sanei_scsi_open: could not allocate SG buffer memory "
	   "wanted: %i got: %i\n", wanted_buffersize, real_buffersize);
      sanei_scsi_close (*fdp);
      return SANE_STATUS_NO_MEM;
    }

  first_time = 0;
  return res;
}
#else
/* dummy for the proposed new open call */
SANE_Status
sanei_scsi_open_extended (const char *dev, int *fdp,
			  SANEI_SCSI_Sense_Handler handler,
			  void *handler_arg, int *buffersize)
{
  SANE_Status res;
  res = sanei_scsi_open (dev, fdp, handler, handler_arg);
  if (sanei_scsi_max_request_size < *buffersize)
    *buffersize = sanei_scsi_max_request_size;
  return res;
}
#endif

void
sanei_scsi_close (int fd)
{
#if USE == LINUX_INTERFACE
  if (fd_info[fd].pdata)
    {
      req *req, *next_req;

      /* make sure that there are no pending SCSI calls */
      sanei_scsi_req_flush_all_extended (fd);

      req = ((fdparms *) fd_info[fd].pdata)->sane_free_list;
      while (req)
	{
	  next_req = req->next;
	  free (req);
	  req = next_req;
	}
      free (fd_info[fd].pdata);
    }
#endif

  fd_info[fd].in_use = 0;
  fd_info[fd].sense_handler = 0;
  fd_info[fd].sense_handler_arg = 0;

#ifdef WIN32
  CloseHandle(fd);
#else
  if (!fd_info[fd].fake_fd)
    close (fd);
#endif

#if USE == FREEBSD_CAM_INTERFACE
  cam_close_device (cam_devices[fd]);
  cam_devices[fd] = NULL;
#elif USE == DOMAINOS_INTERFACE
  {
    static int index;
    static status_$t status;

    DBG (1, "sanei_scsi_close:  fd=%d\n", fd);

    /* Send the command to the server */
    if (!mutex_$lock (&com->CommandLock, Wait16S))
      {
	DBG (0, "Could not obtain mutex lock for Close command\n");
      }
    else
      {
	com->opcode = Close;
	com->fd = fd;
	CommandTriggerValue[0] = ec2_$read (com->CommandAccepted) + 1;
	ec2_$advance (&com->CommandAvailable, &status);
	DomainErrorCheck (status, "Can't advance CommandAvailable EC");
	CommandTriggerValue[1] = (ec2_$read (*CommandAcceptedPtr[1])
				  + DomainECWaitConstant);
	index = ec2_$wait_svc (CommandAcceptedPtr, CommandTriggerValue, 2,
			       &status);
	DomainErrorCheck (status,
			  "Error waiting on Close command acceptance EC");
	if (index != 1)
	  {
	    DBG (0, "Domain SANE Server never accepted Close Command\n");
	  }

	/* Read the result */
	status = com->CommandStatus;
	/* Release the lock */
	mutex_$unlock (&com->CommandLock);
      }

    /* Unmap the data area */
    ms_$unmap (fd_info[com->fd].pdata, DomainMaxDataSize + DomainSenseSize,
	       &status);
    DomainErrorCheck (status, "Error unmapping device data area");
  }
#endif /* USE == DOMAINOS_INTERFACE */

#if USE == OS2_INTERFACE
  close_aspi ();
#endif /* USE == OS2_INTERFACE */

#if USE == MACOSX_INTERFACE
  if (fd_info[fd].pdata)
    CFRelease (fd_info[fd].pdata);
#endif /* USE == MACOSX_INTERFACE */
}


#if USE == DOMAINOS_INTERFACE
# define WE_HAVE_ASYNC_SCSI

void
sanei_scsi_req_flush_all (void)
{
  status_$t status;

  DBG (1, "sanei_scsi_req_flush_all: ()\n");
  /* I have never seen this called, and I'm not sure what to do with it,
     so I guarantee that it will generate a fault, and I can add support
     for it.  */
  assert (1 == 0);
}


SANE_Status
sanei_scsi_req_enter2 (int fd,
		       const void *cmd, size_t cmd_size,
		       const void *src, size_t src_size,
		       void *dst, size_t * dst_size, void **idp)
{
  SANEI_SCSI_Sense_Handler handler;
  static int index;
  static SANE_Status sane_status;
  static status_$t status;
  static scsi_$status_t SCSIStatus;
  static void *buf_ptr;

  if (dst_size)
    DBG (1, "sanei_scsi_req_enter2: (fd=%x, cmd=%p, cmd_size=%x, "
	 "src=%p, src_size=%x, dst=%p, dst_size=%x, *idp=%p)\n",
	 fd, cmd, cmd_size, src, src_size, dst, *dst_size, idp);
  else
    DBG (1, "sanei_scsi_req_enter2: (fd=%x, cmd=%p, cmd_size=%x, "
	 "src=%p, src_size=%x, dst=%p, dst_size=NULL, *idp=%p)\n",
	 fd, src, src_size, dst, idp);

  /* Lock the command structure */
  if (!mutex_$lock (&com->CommandLock, mutex_$wait_forever))
    {
      DBG (0, "Could not obtain mutex lock for Enter Command\n");
      return SANE_STATUS_INVAL;
    }

  /* Fill in the command structure */
  com->opcode = Enter;
  com->fd = fd;
  com->cdb_size = cmd_size;
  if (dst_size)
    com->dst_size = *dst_size;
  memcpy (&com->cdb, cmd, com->cdb_size);

  /* figure out if this is a read or a write */
  if (dst_size && *dst_size)
    {
      /* dest buffer specified, must be a read */
      /* assert (com->cdb_size == src_size); */
      com->direction = scsi_read;
      buf_ptr = dst;
      com->buf_size = *dst_size;
    }
  else
    {
      /* no dest buffer, must be a write */
      /* assert (com->cdb_size <= src_size); */
      com->direction = scsi_write;
      buf_ptr = (char *) src;
      com->buf_size = src_size;
      if (com->buf_size)
	memcpy (fd_info[fd].pdata, buf_ptr, com->buf_size);
    }

  CommandTriggerValue[0] = ec2_$read (com->CommandAccepted) + 1;
  ec2_$advance (&com->CommandAvailable, &status);
  DomainErrorCheck (status, "Can't advance CommandAvailable EC");
  CommandTriggerValue[1] = (ec2_$read (*CommandAcceptedPtr[1])
			    + DomainECWaitConstant);
  index = ec2_$wait_svc (CommandAcceptedPtr, CommandTriggerValue, 2, &status);
  DomainErrorCheck (status, "Error waiting on Enter command acceptance EC");
  if (index != 1)
    {
      DBG (0, "Domain SANE Server never accepted Enter Command\n");
      return SANE_STATUS_INVAL;
    }

  /* Read the result */
  status = com->CommandStatus;
  SCSIStatus = com->SCSIStatus;

  /* Release the lock */
  mutex_$unlock (&com->CommandLock);

  /* Now decode the return status */
  if (status.all)
    DBG (1, "Server returned status %08x from Enter command\n", status.all);
  switch (status.all)
    {
    case status_$ok:
      sane_status = SANE_STATUS_GOOD;
      break;
    case scsi_$dma_underrun:
      sane_status = SANE_STATUS_IO_ERROR;
      /* This error is generated by the HP and UMAX backends.  They
         ask for too much data.  For now, the error is ignored :-( */
      sane_status = SANE_STATUS_GOOD;
      break;
    case scsi_$operation_timeout:
      sane_status = SANE_STATUS_DEVICE_BUSY;
      break;
    case scsi_$hdwr_failure:	/* received when both scanners were active */
      sane_status = SANE_STATUS_IO_ERROR;
      break;
    case (status_$ok | 0x80000000):
      /* Special - no Domain/OS error, but fail bit set means to check
         SCSI operation status. */
      DBG (1, "Server returned SCSI status of %08x\n", SCSIStatus);
      switch (SCSIStatus)
	{
	case scsi_check_condition:
	  /* Call the sense handler, if defined */
	  handler = fd_info[com->fd].sense_handler;
	  if (handler)
	    (*handler) (fd, ((u_char *) fd_info[fd].pdata
			     + DomainMaxDataSize),
			fd_info[com->fd].sense_handler_arg);
	  sane_status = SANE_STATUS_IO_ERROR;
	  break;
	case scsi_busy:
	  sane_status = SANE_STATUS_DEVICE_BUSY;
	  break;
	default:
	  DBG (0, "Error - Unrecognized SCSI status %08x returned from "
	       "Enter command\n", SCSIStatus);
	  sane_status = SANE_STATUS_IO_ERROR;
	  exit (EXIT_FAILURE);
	}
      break;
    default:
      DBG (0, "Unmapped status (%08x) returned from Domain SANE Server\n",
	   status.all);
      sane_status = SANE_STATUS_IO_ERROR;
    }

  /* If a read, copy the data into the destination buffer */
  if ((com->direction == scsi_read) && com->dst_size)
    memcpy (buf_ptr, fd_info[fd].pdata, com->dst_size);

  return sane_status;
}


SANE_Status
sanei_scsi_req_wait (void *id)
{
  SANE_Status status;
  DBG (1, "sanei_scsi_req_wait: (id=%p)\n", id);
  status = SANE_STATUS_GOOD;
  return status;
}


SANE_Status
sanei_scsi_cmd2 (int fd,
		 const void *cmd, size_t cmd_size,
		 const void *src, size_t src_size,
		 void *dst, size_t * dst_size)
{
  SANE_Status status;
  void *id;

  DBG (1, "sanei_scsi_cmd2: (fd=%d)\n", fd);
  status =
    sanei_scsi_req_enter2 (fd, cmd, cmd_size, src, src_size, dst, dst_size,
			   &id);
  if (status != SANE_STATUS_GOOD)
    return status;
  return sanei_scsi_req_wait (id);
}

#endif /* USE == DOMAINOS_INTERFACE */


#if USE == LINUX_INTERFACE

#include <ctype.h>
#include <signal.h>

#include <sys/time.h>

#define WE_HAVE_ASYNC_SCSI
#define WE_HAVE_FIND_DEVICES

static int pack_id = 0;
static int need_init = 1;
static sigset_t all_signals;

#define ATOMIC(s)					\
do							\
  {							\
    sigset_t old_mask;					\
							\
    if (need_init)					\
      {							\
	need_init = 0;					\
	sigfillset (&all_signals);			\
      }							\
    sigprocmask (SIG_BLOCK, &all_signals, &old_mask);	\
    {s;}						\
    sigprocmask (SIG_SETMASK, &old_mask, 0);		\
  }							\
while (0)

static void
issue (struct req *req)
{
  ssize_t nwritten;
  fdparms *fdp;
  struct req *rp;
  int retries;
  int ret;

  if (!req)
    return;

  fdp = (fdparms *) fd_info[req->fd].pdata;
  DBG (4, "sanei_scsi.issue: %p\n", (void *) req);

  rp = fdp->sane_qhead;
  while (rp && rp->running)
    rp = rp->next;

  while (rp && fdp->sg_queue_used < fdp->sg_queue_max)
    {
      retries = 20;
      while (retries)
	{
	  errno = 0;
#ifdef SG_IO
	  if (sg_version < 30000)
	    {
#endif
	      ATOMIC (rp->running = 1;
		      nwritten = write (rp->fd, &rp->sgdata.cdb,
					rp->sgdata.cdb.hdr.pack_len);
		      ret = 0;
		      if (nwritten != rp->sgdata.cdb.hdr.pack_len)
		      {
		      /* ENOMEM can easily happen, if both command queueing
		         inside the SG driver and large buffers are used.
		         Therefore, if ENOMEM does not occur for the first
		         command in the queue, we simply try to issue
		         it later again.
		       */
		      if (errno == EAGAIN
			  || (errno == ENOMEM && rp != fdp->sane_qhead))
		      {
		      /* don't try to send the data again, but
		         wait for the next call to issue()
		       */
		      rp->running = 0;}
		      }
	      );
#ifdef SG_IO
	    }
	  else
	    {
	      ATOMIC (rp->running = 1;
		      ret = ioctl(rp->fd, SG_IO, &rp->sgdata.sg3.hdr);
		      nwritten = 0;
		      if (ret < 0)
		      {
		      /* ENOMEM can easily happen, if both command queuein
		         inside the SG driver and large buffers are used.
		         Therefore, if ENOMEM does not occur for the first
		         command in the queue, we simply try to issue
		         it later again.
		       */
			if (errno == EAGAIN
			    || (errno == ENOMEM && rp != fdp->sane_qhead))
			  {
			    /* don't try to send the data again, but
			       wait for the next call to issue()
			    */
			    rp->running = 0;
			  }
			else /* game over */
			  {
			    rp->running = 0;
			    rp->done = 1;
			    rp->status = SANE_STATUS_IO_ERROR;
			  }
		      }
	      );
	      IF_DBG (if (DBG_LEVEL >= 255)
		      system ("cat /proc/scsi/sg/debug 1>&2");)
		}
#endif
		if (rp == fdp->sane_qhead && errno == EAGAIN)
		  {
		    retries--;
		    usleep (10000);
		  }
		else
		  retries = 0;
	    }

#ifndef SG_IO
	  if (nwritten != rp->sgdata.cdb.hdr.pack_len)
#else
	  if ((sg_version < 30000 && nwritten != rp->sgdata.cdb.hdr.pack_len)
	      || (sg_version >= 30000 && ret < 0))
#endif
	    {
	      if (rp->running)
		{
#ifdef SG_IO
		  if (sg_version < 30000)
#endif
		    DBG (1, "sanei_scsi.issue: bad write (errno=%i) %s %li\n",
			 errno, strerror (errno), (long)nwritten);
#ifdef SG_IO
		  else if (sg_version > 30000)
		    DBG (1, "sanei_scsi.issue: SG_IO ioctl error (errno=%i, ret=%d) %s\n",
			 errno, ret, strerror (errno));
#endif
		  rp->done = 1;
		  if (errno == ENOMEM)
		    {
		      DBG (1, "sanei_scsi.issue: SG_BIG_BUF inconsistency? "
			   "Check file PROBLEMS.\n");
		      rp->status = SANE_STATUS_NO_MEM;
		    }
		  else
		    rp->status = SANE_STATUS_IO_ERROR;
		}
	      else
		{
		  if (errno == ENOMEM)
		    DBG (1, "issue: ENOMEM - cannot queue SCSI command. "
			 "Trying again later.\n");
		  else
		    DBG (1, "issue: EAGAIN - cannot queue SCSI command. "
			 "Trying again later.\n");
		}
	      break;		/* in case of an error don't try to queue more commands */
	    }
	  else
	    {
#ifdef SG_IO
	      if (sg_version < 30000)
#endif
		req->status = SANE_STATUS_IO_ERROR;
#ifdef SG_IO
	      else if (sg_version > 30000) /* SG_IO is synchronous, we're all set */
		req->status = SANE_STATUS_GOOD;
#endif
	    }
	  fdp->sg_queue_used++;
	  rp = rp->next;
    }
}

  void sanei_scsi_req_flush_all_extended (int fd)
  {
    fdparms *fdp;
    struct req *req, *next_req;
    int len, count;

    fdp = (fdparms *) fd_info[fd].pdata;
    for (req = fdp->sane_qhead; req; req = next_req)
      {
	if (req->running && !req->done)
	  {
	    count = sane_scsicmd_timeout * 10;
	    while (count)
	      {
		errno = 0;
#ifdef SG_IO
		if (sg_version < 30000)
#endif
		  len =
		    read (fd, &req->sgdata.cdb,
			  req->sgdata.cdb.hdr.reply_len);
#ifdef SG_IO
		else
		  len = read (fd, &req->sgdata.sg3.hdr, sizeof (Sg_io_hdr));
#endif
		if (len >= 0 || (len < 0 && errno != EAGAIN))
		  break;
		usleep (100000);
		count--;
	      }
	    ((fdparms *) fd_info[req->fd].pdata)->sg_queue_used--;
	  }
	next_req = req->next;

	req->next = fdp->sane_free_list;
	fdp->sane_free_list = req;
      }

    fdp->sane_qhead = fdp->sane_qtail = 0;
  }

  void sanei_scsi_req_flush_all ()
  {
    int fd, i, j = 0;

    /* sanei_scsi_open allows only one open file handle, so we
       can simply look for the first entry where in_use is set
     */

    fd = num_alloced;
    for (i = 0; i < num_alloced; i++)
      if (fd_info[i].in_use)
	{
	  j++;
	  fd = i;
	}

    assert (j < 2);

    if (fd < num_alloced)
      sanei_scsi_req_flush_all_extended (fd);
  }

  SANE_Status
    sanei_scsi_req_enter2 (int fd,
			   const void *cmd, size_t cmd_size,
			   const void *src, size_t src_size,
			   void *dst, size_t * dst_size, void **idp)
  {
    struct req *req;
    size_t size;
    fdparms *fdp;

    fdp = (fdparms *) fd_info[fd].pdata;

    if (fdp->sane_free_list)
      {
	req = fdp->sane_free_list;
	fdp->sane_free_list = req->next;
	req->next = 0;
      }
    else
      {
#ifdef SG_IO
	if (sg_version < 30000)
#endif
	  size = (sizeof (*req) - sizeof (req->sgdata.cdb.data)
		  + fdp->buffersize);
#ifdef SG_IO
	else
	  size = sizeof (*req) + MAX_CDB + fdp->buffersize
	    - sizeof (req->sgdata.sg3.data);
#endif
	req = malloc (size);
	if (!req)
	  {
	    DBG (1, "sanei_scsi_req_enter: failed to malloc %lu bytes\n",
		 (u_long) size);
	    return SANE_STATUS_NO_MEM;
	  }
      }
    req->fd = fd;
    req->running = 0;
    req->done = 0;
    req->status = SANE_STATUS_GOOD;
    req->dst = dst;
    req->dst_len = dst_size;
#ifdef SG_IO
    if (sg_version < 30000)
      {
#endif
	memset (&req->sgdata.cdb.hdr, 0, sizeof (req->sgdata.cdb.hdr));
	req->sgdata.cdb.hdr.pack_id = pack_id++;
	req->sgdata.cdb.hdr.pack_len = cmd_size + src_size
	  + sizeof (req->sgdata.cdb.hdr);
	req->sgdata.cdb.hdr.reply_len = (dst_size ? *dst_size : 0)
	  + sizeof (req->sgdata.cdb.hdr);
	memcpy (&req->sgdata.cdb.data, cmd, cmd_size);
	memcpy (&req->sgdata.cdb.data[cmd_size], src, src_size);
	if (CDB_SIZE (*(const u_char *) cmd) != cmd_size)
	  {
	    if (ioctl (fd, SG_NEXT_CMD_LEN, &cmd_size))
	      {
		DBG (1,
		     "sanei_scsi_req_enter2: ioctl to set command length failed\n");
	      }
	  }
#ifdef SG_IO
      }
    else
      {
	memset (&req->sgdata.sg3.hdr, 0, sizeof (req->sgdata.sg3.hdr));
	req->sgdata.sg3.hdr.interface_id = 'S';
	req->sgdata.sg3.hdr.cmd_len = cmd_size;
	req->sgdata.sg3.hdr.iovec_count = 0;
	req->sgdata.sg3.hdr.mx_sb_len = SENSE_MAX;
	/* read or write? */
	if (dst_size && *dst_size)
	  {
	    req->sgdata.sg3.hdr.dxfer_direction = SG_DXFER_FROM_DEV;
	    req->sgdata.sg3.hdr.dxfer_len = *dst_size;
	    req->sgdata.sg3.hdr.dxferp = dst;
	  }
	else if (src_size)
	  {
	    req->sgdata.sg3.hdr.dxfer_direction = SG_DXFER_TO_DEV;
	    if (src_size > fdp->buffersize)
	      {
		DBG (1,
		     "sanei_scsi_req_enter2 warning: truncating write data "
		     "from requested %li bytes to allowed %li bytes\n",
		     (long)src_size, (long)fdp->buffersize);
		src_size = fdp->buffersize;
	      }
	    req->sgdata.sg3.hdr.dxfer_len = src_size;
	    memcpy (&req->sgdata.sg3.data[MAX_CDB], src, src_size);
	    req->sgdata.sg3.hdr.dxferp = &req->sgdata.sg3.data[MAX_CDB];
	  }
	else
	  {
	    req->sgdata.sg3.hdr.dxfer_direction = SG_DXFER_NONE;
	  }
	if (cmd_size > MAX_CDB)
	  {
	    DBG (1, "sanei_scsi_req_enter2 warning: truncating write data "
		 "from requested %li bytes to allowed %i bytes\n",
		 (long)cmd_size, MAX_CDB);
	    cmd_size = MAX_CDB;
	  }
	memcpy (req->sgdata.sg3.data, cmd, cmd_size);
	req->sgdata.sg3.hdr.cmdp = req->sgdata.sg3.data;
	req->sgdata.sg3.hdr.sbp = &(req->sgdata.sg3.sense_buffer[0]);
	req->sgdata.sg3.hdr.timeout = 1000 * sane_scsicmd_timeout;
#ifdef ENABLE_SCSI_DIRECTIO
	/* for the adventurous: If direct IO is used,
	   the kernel locks the buffer. This can lead to conflicts,
	   if a backend uses shared memory.
	   OTOH, direct IO may be faster, and it reduces memory usage
	 */
	req->sgdata.sg3.hdr.flags = SG_FLAG_DIRECT_IO;
#else
	req->sgdata.sg3.hdr.flags = 0;
#endif
	req->sgdata.sg3.hdr.pack_id = pack_id++;
	req->sgdata.sg3.hdr.usr_ptr = 0;
      }
#endif

    req->next = 0;
    ATOMIC (if (fdp->sane_qtail)
	    {
	    fdp->sane_qtail->next = req; fdp->sane_qtail = req;}
	    else
	    fdp->sane_qhead = fdp->sane_qtail = req);

    DBG (4, "scsi_req_enter: entered %p\n", (void *) req);

    *idp = req;
    issue (req);

    DBG (10, "scsi_req_enter: queue_used: %i, queue_max: %i\n",
	 ((fdparms *) fd_info[fd].pdata)->sg_queue_used,
	 ((fdparms *) fd_info[fd].pdata)->sg_queue_max);

    return SANE_STATUS_GOOD;
  }

  SANE_Status sanei_scsi_req_wait (void *id)
  {
    SANE_Status status = SANE_STATUS_GOOD;
    struct req *req = id;
    ssize_t nread = 0;

    /* we don't support out-of-order completion */
    assert (req == ((fdparms *) fd_info[req->fd].pdata)->sane_qhead);

    DBG (4, "sanei_scsi_req_wait: waiting for %p\n", (void *) req);

    issue (req);		/* ensure the command is running */
    if (req->done)
      {
	issue (req->next);	/* issue next command, if any */
	status = req->status;
      }
    else
      {
#ifdef SG_IO
	if (sg_version < 30000)
	  {
#endif
	    fd_set readable;

	    /* wait for command completion: */
	    FD_ZERO (&readable);
	    FD_SET (req->fd, &readable);
	    select (req->fd + 1, &readable, 0, 0, 0);

	    /* now atomically read result and set DONE: */
	    ATOMIC (nread = read (req->fd, &req->sgdata.cdb,
				  req->sgdata.cdb.hdr.reply_len);
		    req->done = 1);
#ifdef SG_IO
	  }
	else
	  {
	    IF_DBG (if (DBG_LEVEL >= 255)
		    system ("cat /proc/scsi/sg/debug 1>&2");)

	      /* set DONE: */
	      nread = 0; /* unused in this code path */
	      req->done = 1;
	  }
#endif

	if (fd_info[req->fd].pdata)
	  ((fdparms *) fd_info[req->fd].pdata)->sg_queue_used--;

	/* Now issue next command asap, if any.  We can't do this
	   earlier since the Linux kernel has space for just one big
	   buffer.  */
	issue (req->next);

	DBG (4, "sanei_scsi_req_wait: read %ld bytes\n", (long) nread);

	if (nread < 0)
	  {
	    DBG (1, "sanei_scsi_req_wait: read returned %ld (errno=%d)\n",
		 (long) nread, errno);
	    status = SANE_STATUS_IO_ERROR;
	  }
	else
	  {
#ifdef SG_IO
	    if (sg_version < 30000)
	      {
#endif
		nread -= sizeof (req->sgdata.cdb.hdr);

		/* check for errors, but let the sense_handler decide.... */
		if ((req->sgdata.cdb.hdr.result != 0) ||
		    (((req->sgdata.cdb.hdr.sense_buffer[0] & 0x7f) != 0)
#ifdef HAVE_SG_TARGET_STATUS
		     /* this is messy... Sometimes it happens that we have
		        a valid looking sense buffer, but the DRIVER_SENSE
		        bit is not set. Moreover, we can check this only for
		        not tooo old SG drivers
		      */
		     && (req->sgdata.cdb.hdr.driver_status & DRIVER_SENSE)
#endif
		    ))
		  {
		    SANEI_SCSI_Sense_Handler handler
		      = fd_info[req->fd].sense_handler;
		    void *arg = fd_info[req->fd].sense_handler_arg;

		    DBG (1,
			 "sanei_scsi_req_wait: SCSI command complained: %s\n",
			 strerror (req->sgdata.cdb.hdr.result));
		    DBG (10,
			 "sense buffer: %02x %02x %02x %02x %02x %02x %02x %02x"
			 " %02x %02x %02x %02x %02x %02x %02x %02x\n",
			 req->sgdata.cdb.hdr.sense_buffer[0],
			 req->sgdata.cdb.hdr.sense_buffer[1],
			 req->sgdata.cdb.hdr.sense_buffer[2],
			 req->sgdata.cdb.hdr.sense_buffer[3],
			 req->sgdata.cdb.hdr.sense_buffer[4],
			 req->sgdata.cdb.hdr.sense_buffer[5],
			 req->sgdata.cdb.hdr.sense_buffer[6],
			 req->sgdata.cdb.hdr.sense_buffer[7],
			 req->sgdata.cdb.hdr.sense_buffer[8],
			 req->sgdata.cdb.hdr.sense_buffer[9],
			 req->sgdata.cdb.hdr.sense_buffer[10],
			 req->sgdata.cdb.hdr.sense_buffer[11],
			 req->sgdata.cdb.hdr.sense_buffer[12],
			 req->sgdata.cdb.hdr.sense_buffer[13],
			 req->sgdata.cdb.hdr.sense_buffer[14],
			 req->sgdata.cdb.hdr.sense_buffer[15]);
#ifdef HAVE_SG_TARGET_STATUS
		    /* really old SG header do not define target_status,
		       host_status and driver_status
		     */
		    DBG (10, "target status: %02x host status: %02x"
			 " driver status: %02x\n",
			 req->sgdata.cdb.hdr.target_status,
			 req->sgdata.cdb.hdr.host_status,
			 req->sgdata.cdb.hdr.driver_status);

		    if (req->sgdata.cdb.hdr.host_status == DID_NO_CONNECT || req->sgdata.cdb.hdr.host_status == DID_BUS_BUSY || req->sgdata.cdb.hdr.host_status == DID_TIME_OUT || req->sgdata.cdb.hdr.driver_status == DRIVER_BUSY || req->sgdata.cdb.hdr.target_status == 0x04)	/* BUSY */
#else
		    if (req->sgdata.cdb.hdr.result == EBUSY)
#endif
		      status = SANE_STATUS_DEVICE_BUSY;
		    else if (handler)
		      /* sense handler should return SANE_STATUS_GOOD if it
		         decided all was ok afterall */
		      status =
			(*handler) (req->fd, req->sgdata.cdb.hdr.sense_buffer,
				    arg);
		    else
		      status = SANE_STATUS_IO_ERROR;
		  }

		/* if we are ok so far, copy over the return data */
		if (status == SANE_STATUS_GOOD)
		  {
		    if (req->dst)
		      memcpy (req->dst, req->sgdata.cdb.data, nread);

		    if (req->dst_len)
		      *req->dst_len = nread;
		  }
#ifdef SG_IO
	      }
	    else
	      {
		/* check for errors, but let the sense_handler decide.... */
		if (((req->sgdata.sg3.hdr.info & SG_INFO_CHECK) != 0)
		    || ((req->sgdata.sg3.hdr.sb_len_wr > 0)
			&& ((req->sgdata.sg3.sense_buffer[0] & 0x7f) != 0)
			&& (req->sgdata.sg3.hdr.
			    driver_status & DRIVER_SENSE)))
		  {
		    SANEI_SCSI_Sense_Handler handler
		      = fd_info[req->fd].sense_handler;
		    void *arg = fd_info[req->fd].sense_handler_arg;

		    DBG (1,
			 "sanei_scsi_req_wait: SCSI command complained: %s\n",
			 strerror (errno));
		    DBG (10,
			 "sense buffer: %02x %02x %02x %02x %02x %02x %02x %02x"
			 " %02x %02x %02x %02x %02x %02x %02x %02x\n",
			 req->sgdata.sg3.sense_buffer[0],
			 req->sgdata.sg3.sense_buffer[1],
			 req->sgdata.sg3.sense_buffer[2],
			 req->sgdata.sg3.sense_buffer[3],
			 req->sgdata.sg3.sense_buffer[4],
			 req->sgdata.sg3.sense_buffer[5],
			 req->sgdata.sg3.sense_buffer[6],
			 req->sgdata.sg3.sense_buffer[7],
			 req->sgdata.sg3.sense_buffer[8],
			 req->sgdata.sg3.sense_buffer[9],
			 req->sgdata.sg3.sense_buffer[10],
			 req->sgdata.sg3.sense_buffer[11],
			 req->sgdata.sg3.sense_buffer[12],
			 req->sgdata.sg3.sense_buffer[13],
			 req->sgdata.sg3.sense_buffer[14],
			 req->sgdata.sg3.sense_buffer[15]);
		    DBG (10,
			 "target status: %02x host status: %04x"
			 " driver status: %04x\n", req->sgdata.sg3.hdr.status,
			 req->sgdata.sg3.hdr.host_status,
			 req->sgdata.sg3.hdr.driver_status);

		    /* the first three tests below are an replacement of the
		       error "classification" as it was with the old SG driver,
		       the fourth test is new.
		     */
		    if (req->sgdata.sg3.hdr.host_status == SG_ERR_DID_NO_CONNECT || req->sgdata.sg3.hdr.host_status == SG_ERR_DID_BUS_BUSY || req->sgdata.sg3.hdr.host_status == SG_ERR_DID_TIME_OUT || req->sgdata.sg3.hdr.driver_status == DRIVER_BUSY || req->sgdata.sg3.hdr.masked_status == 0x04)	/* BUSY */
		      status = SANE_STATUS_DEVICE_BUSY;
		    else if (handler && req->sgdata.sg3.hdr.sb_len_wr)
		      /* sense handler should return SANE_STATUS_GOOD if it
		         decided all was ok afterall */
		      status =
			(*handler) (req->fd, req->sgdata.sg3.sense_buffer,
				    arg);

		    /* status bits INTERMEDIATE and CONDITION MET should not
		       result in an error; neither should reserved bits
		     */
		    else if (((req->sgdata.sg3.hdr.status & 0x2a) == 0)
			     && (req->sgdata.sg3.hdr.host_status ==
				 SG_ERR_DID_OK)
			     &&
			     ((req->sgdata.sg3.hdr.
			       driver_status & ~SG_ERR_DRIVER_SENSE) ==
			      SG_ERR_DRIVER_OK))
		      status = SANE_STATUS_GOOD;
		    else
		      status = SANE_STATUS_IO_ERROR;
		  }

#if 0
		/* Sometimes the Linux SCSI system reports bogus resid values. 
		   Observed with lk 2.4.5, 2.4.13, aic7xxx and sym53c8xx drivers, 
		   if command queueing is used. So we better issue only a warning
		 */
		if (status == SANE_STATUS_GOOD)
		  {
		    if (req->dst_len)
		      {
			*req->dst_len -= req->sgdata.sg3.hdr.resid;
		      }
		  }
#endif
		if (req->sgdata.sg3.hdr.resid)
		  {
		    DBG (1,
			 "sanei_scsi_req_wait: SG driver returned resid %i\n",
			 req->sgdata.sg3.hdr.resid);
		    DBG (1,
			 "                     NOTE: This value may be bogus\n");
		  }
	      }
#endif
	  }
      }

    /* dequeue and release processed request: */
    ATOMIC (((fdparms *) fd_info[req->fd].pdata)->sane_qhead
	    = ((fdparms *) fd_info[req->fd].pdata)->sane_qhead->next;
	    if (!((fdparms *) fd_info[req->fd].pdata)->sane_qhead)
	    ((fdparms *) fd_info[req->fd].pdata)->sane_qtail = 0;
	    req->next = ((fdparms *) fd_info[req->fd].pdata)->sane_free_list;
	    ((fdparms *) fd_info[req->fd].pdata)->sane_free_list = req);
    return status;
  }

  SANE_Status
    sanei_scsi_cmd2 (int fd,
		     const void *cmd, size_t cmd_size,
		     const void *src, size_t src_size,
		     void *dst, size_t * dst_size)
  {
    SANE_Status status;
    void *id;

    status =
      sanei_scsi_req_enter2 (fd, cmd, cmd_size, src, src_size, dst, dst_size,
			     &id);
    if (status != SANE_STATUS_GOOD)
      return status;
    return sanei_scsi_req_wait (id);
  }

/* The following code (up to and including sanei_scsi_find_devices() )
   is trying to match device/manufacturer names and/or SCSI addressing
   numbers (i.e. <host,bus,id,lun>) with a sg device file name
   (e.g. /dev/sg3).
*/
#define PROCFILE	"/proc/scsi/scsi"
#define DEVFS_MSK	"/dev/scsi/host%d/bus%d/target%d/lun%d/generic"
#define SCAN_MISSES 5

/* Some <scsi/scsi.h> headers don't have the following define */
#ifndef SCSI_IOCTL_GET_IDLUN
#define SCSI_IOCTL_GET_IDLUN 0x5382
#endif

  static int lx_sg_dev_base = -1;
  static int lx_devfs = -1;

  static const struct lx_device_name_list_tag
  {
    const char *prefix;
    char base;
  }
  lx_dnl[] =
  {
    {
    "/dev/sg", 0}
    ,
    {
    "/dev/sg", 'a'}
    ,
    {
    "/dev/uk", 0}
    ,
    {
    "/dev/gsc", 0}
  };

  static int			/* Returns open sg file descriptor, or -1 for no access,
				   or -2 for not found (or other error) */
    lx_mk_devicename (int guess_devnum, char *name, size_t name_len)
  {
    int dev_fd, k, dnl_len;
    const struct lx_device_name_list_tag *dnp;

    dnl_len = NELEMS (lx_dnl);
    k = ((-1 == lx_sg_dev_base) ? 0 : lx_sg_dev_base);
    for (; k < dnl_len; ++k)
      {
	dnp = &lx_dnl[k];
	if (dnp->base)
	  snprintf (name, name_len, "%s%c", dnp->prefix,
		    dnp->base + guess_devnum);
	else
	  snprintf (name, name_len, "%s%d", dnp->prefix, guess_devnum);
   dev_fd = -1;
#ifdef HAVE_RESMGR
   dev_fd = rsm_open_device (name, O_RDWR | O_NONBLOCK);
#endif
   if (dev_fd == -1)
     dev_fd = open (name, O_RDWR | O_NONBLOCK);
	if (dev_fd >= 0)
	  {
	    lx_sg_dev_base = k;
	    return dev_fd;
	  }
	else if ((EACCES == errno) || (EBUSY == errno))
	  {
	    lx_sg_dev_base = k;
	    return -1;
	  }
	if (-1 != lx_sg_dev_base)
	  return -2;
      }
    return -2;
  }

  static int			/* Returns 1 for match, else 0 */
    lx_chk_id (int dev_fd, int host, int channel, int id, int lun)
  {
#ifdef SG_GET_SCSI_ID_FOUND
    struct sg_scsi_id ssid;

    if ((ioctl (dev_fd, SG_GET_SCSI_ID, &ssid) >= 0))
      {
	DBG (2, "lx_chk_id: %d,%d  %d,%d  %d,%d  %d,%d\n", host, ssid.host_no,
	     channel, ssid.channel, id, ssid.scsi_id, lun, ssid.lun);
	if ((host == ssid.host_no) &&
	    (channel == ssid.channel) &&
	    (id == ssid.scsi_id) && (lun == ssid.lun))
	  return 1;
	else
	  return 0;
      }
#endif
    {
      struct my_scsi_idlun
      {
	int dev_id;
	int host_unique_id;
      }
      my_idlun;
      if (ioctl (dev_fd, SCSI_IOCTL_GET_IDLUN, &my_idlun) >= 0)
	{
	  if (((my_idlun.dev_id & 0xff) == id) &&
	      (((my_idlun.dev_id >> 8) & 0xff) == lun) &&
	      (((my_idlun.dev_id >> 16) & 0xff) == channel))
	    return 1;		/* cheating, assume 'host' number matches */
	}
    }
    return 0;
  }

  static int			/* Returns 1 if match with 'name' set, else 0 */
   
    lx_scan_sg (int exclude_devnum, char *name, size_t name_len,
		int host, int channel, int id, int lun)
  {
    int dev_fd, k, missed;

    if (-1 == lx_sg_dev_base)
      return 0;
    for (k = 0, missed = 0; (missed < SCAN_MISSES) && (k < 255);
	 ++k, ++missed)
      {
	DBG (2, "lx_scan_sg: k=%d, exclude=%d, missed=%d\n", k,
	     exclude_devnum, missed);
	if (k == exclude_devnum)
	  {
	    missed = 0;
	    continue;		/* assumed this one has been checked already */
	  }
	if ((dev_fd = lx_mk_devicename (k, name, name_len)) >= 0)
	  {
	    missed = 0;
	    if (lx_chk_id (dev_fd, host, channel, id, lun))
	      {
		close (dev_fd);
		return 1;
	      }
	    close (dev_fd);
	  }
	else if (-1 == dev_fd)
	  missed = 0;		/* no permissions but something found */
      }
    return 0;
  }

  static int			/* Returns 1 if match, else 0 */
   
    lx_chk_devicename (int guess_devnum, char *name, size_t name_len,
		       int host, int channel, int id, int lun)
  {
    int dev_fd;

    if (host < 0)
      return 0;
    if (0 != lx_devfs)
      {				/* simple mapping if we have devfs */
	if (-1 == lx_devfs)
	  {
	    if ((dev_fd =
		 lx_mk_devicename (guess_devnum, name, name_len)) >= 0)
	      close (dev_fd);	/* hack to load sg driver module */
	  }
	snprintf (name, name_len, DEVFS_MSK, host, channel, id, lun);
	dev_fd = open (name, O_RDWR | O_NONBLOCK);
	if (dev_fd >= 0)
	  {
	    close (dev_fd);
	    lx_devfs = 1;
	    DBG (1, "lx_chk_devicename: matched device(devfs): %s\n", name);
	    return 1;
	  }
	else if (ENOENT == errno)
	  lx_devfs = 0;
      }

    if ((dev_fd = lx_mk_devicename (guess_devnum, name, name_len)) < -1)
      {				/* no candidate sg device file name found, try /dev/sg0,1 */
	if ((dev_fd = lx_mk_devicename (0, name, name_len)) < -1)
	  {
	    if ((dev_fd = lx_mk_devicename (1, name, name_len)) < -1)
	      return 0;		/* no luck finding sg fd to open */
	  }
      }
    if (dev_fd >= 0)
      {
/* now check this fd for match on <host, channel, id, lun> */
	if (lx_chk_id (dev_fd, host, channel, id, lun))
	  {
	    close (dev_fd);
	    DBG (1, "lx_chk_devicename: matched device(direct): %s\n", name);
	    return 1;
	  }
	close (dev_fd);
      }
/* if mismatch then call scan algorithm */
    if (lx_scan_sg (guess_devnum, name, name_len, host, channel, id, lun))
      {
	DBG (1, "lx_chk_devicename: matched device(scan): %s\n", name);
	return 1;
      }
    return 0;
  }

/* Legacy /proc/scsi/scsi */
static void /* calls 'attach' function pointer with sg device file name iff match */
sanei_proc_scsi_find_devices (const char *findvendor, const char *findmodel,
			      const char *findtype,
			      int findbus, int findchannel, int findid,
			      int findlun,
			      SANE_Status (*attach) (const char *dev))
  {
#define FOUND_VENDOR  1
#define FOUND_MODEL   2
#define FOUND_TYPE    4
#define FOUND_REV     8
#define FOUND_HOST    16
#define FOUND_CHANNEL 32
#define FOUND_ID      64
#define FOUND_LUN     128
#define FOUND_ALL     255

    char *me = "sanei_proc_scsi_find_devices";

    size_t findvendor_len = 0, findmodel_len = 0, findtype_len = 0;
    char vendor[32], model[32], type[32], revision[32];
    int bus, channel, id, lun;

    int number, i, j, definedd;
    char line[256], dev_name[128], *c1, *c2, ctmp;
    const char *string;
    FILE *proc_fp;
    char *end;
    struct
    {
      const char *name;
      size_t name_len;
      int is_int;		/* integer valued? (not a string) */
      union
      {
	void *v;		/* avoids compiler warnings... */
	char *str;
	int *i;
      }
      u;
    }
    param[] =
    {
      {
	"Vendor:", 7, 0,
	{
	0}
      }
      ,
      {
	"Model:", 6, 0,
	{
	0}
      }
      ,
      {
	"Type:", 5, 0,
	{
	0}
      }
      ,
      {
	"Rev:", 4, 0,
	{
	0}
      }
      ,
      {
	"scsi", 4, 1,
	{
	0}
      }
      ,
      {
	"Channel:", 8, 1,
	{
	0}
      }
      ,
      {
	"Id:", 3, 1,
	{
	0}
      }
      ,
      {
	"Lun:", 4, 1,
	{
	0}
      }
    };

    param[0].u.str = vendor;
    param[1].u.str = model;
    param[2].u.str = type;
    param[3].u.str = revision;
    param[4].u.i = &bus;
    param[5].u.i = &channel;
    param[6].u.i = &id;
    param[7].u.i = &lun;

    DBG_INIT ();

    proc_fp = fopen (PROCFILE, "r");
    if (!proc_fp)
      {
	DBG (1, "%s: could not open %s for reading\n", me, PROCFILE);
	return;
      }

    number = bus = channel = id = lun = -1;

    vendor[0] = model[0] = type[0] = '\0';
    if (findvendor)
      findvendor_len = strlen (findvendor);
    if (findmodel)
      findmodel_len = strlen (findmodel);
    if (findtype)
      findtype_len = strlen (findtype);

    definedd = 0;
    while (!feof (proc_fp))
      {
	fgets (line, sizeof (line), proc_fp);
	string = sanei_config_skip_whitespace (line);

	while (*string)
	  {
	    for (i = 0; i < NELEMS (param); ++i)
	      {
		if (strncmp (string, param[i].name, param[i].name_len) == 0)
		  {
		    string += param[i].name_len;
		    /* Make sure that we don't read the next parameter name
		       as a value, if the real value consists only of spaces
		     */
		    c2 = (char *) (string + strlen (string));
		    for (j = 0; j < NELEMS (param); ++j)
		      {
			c1 = strstr (string, param[j].name);
			if ((j != i) && c1 && (c1 < c2))
			  c2 = c1;
		      }
		    ctmp = *c2;
		    *c2 = 0;
		    string = sanei_config_skip_whitespace (string);

		    if (param[i].is_int)
		      {
			if (*string)
			  {
			    *param[i].u.i = strtol (string, &end, 10);
			    string = (char *) end;
			  }
			else
			  *param[i].u.i = 0;
		      }
		    else
		      {
			strncpy (param[i].u.str, string, 32);
			param[i].u.str[31] = '\0';
			/* while (*string && !isspace (*string))
			   ++string;
			 */
		      }
		    /* string = sanei_config_skip_whitespace (string); */
		    *c2 = ctmp;
		    string = c2;
		    definedd |= 1 << i;

		    if (param[i].u.v == &bus)
		      {
			++number;
			definedd = FOUND_HOST;
		      }
		    break;
		  }
	      }
	    if (i >= NELEMS (param))
	      ++string;		/* no match */
	  }

	if (FOUND_ALL != definedd)
	  /* some info is still missing */
	  continue;

	definedd = 0;
	if ((!findvendor || strncmp (vendor, findvendor, findvendor_len) == 0)
	    && (!findmodel || strncmp (model, findmodel, findmodel_len) == 0)
	    && (!findtype || strncmp (type, findtype, findtype_len) == 0)
	    && (findbus == -1 || bus == findbus)
	    && (findchannel == -1 || channel == findchannel)
	    && (findid == -1 || id == findid)
	    && (findlun == -1 || lun == findlun))
	  {
	    DBG (2, "%s: found: vendor=%s model=%s type=%s\n\t"
		 "bus=%d chan=%d id=%d lun=%d num=%d\n",
		 me, findvendor, findmodel, findtype,
		 bus, channel, id, lun, number);
	    if (lx_chk_devicename (number, dev_name, sizeof (dev_name), bus,
				   channel, id, lun)
		&& ((*attach) (dev_name) != SANE_STATUS_GOOD))
	      {
		DBG(1,"sanei_scsi_find_devices: bad attach\n");
	      }
	  }
	else
	  {
	    DBG (2, "%s: no match\n", me);
	  }
	vendor[0] = model[0] = type[0] = 0;
	bus = channel = id = lun = -1;
      }
    fclose (proc_fp);
  }

#define SYSFS_SCSI_DEVICES "/sys/bus/scsi/devices"

/* From linux/drivers/scsi/scsi.c */
static char *lnxscsi_device_types[] = {
  "Direct-Access    ",
  "Sequential-Access",
  "Printer          ",
  "Processor        ",
  "WORM             ",
  "CD-ROM           ",
  "Scanner          ",
  "Optical Device   ",
  "Medium Changer   ",
  "Communications   ",
  "ASC IT8          ",
  "ASC IT8          ",
  "RAID             ",
  "Enclosure        ",
  "Direct-Access-RBC",
  "Optical card     ",
  "Bridge controller",
  "Object storage   ",
  "Automation/Drive "
};

void /* calls 'attach' function pointer with sg device file name iff match */
sanei_scsi_find_devices (const char *findvendor, const char *findmodel,
			 const char *findtype,
			 int findbus, int findchannel, int findid,
			 int findlun,
			 SANE_Status (*attach) (const char *dev))
  {
    char *me = "sanei_scsi_find_devices";
    char path[PATH_MAX];
    char dev_name[128];
    struct dirent buf;
    struct dirent *de;
    DIR *scsidevs;
    FILE *fp;
    char *ptr;
    char *end;
    int bcil[4]; /* bus, channel, id, lun */
    char vmt[3][33]; /* vendor, model, type */
    int vmt_len[3];
    char *vmtfiles[3] = { "vendor", "model", "type" };
    int lastbus;
    int number;
    int i;
    long val;
    int ret;

    DBG_INIT ();

    DBG (2, "%s: looking for: v=%s m=%s t=%s b=%d c=%d i=%d l=%d\n",
	 me, findvendor, findmodel, findtype,
	 findbus, findchannel, findid, findlun);

    scsidevs = opendir (SYSFS_SCSI_DEVICES);
    if (!scsidevs)
      {
	DBG (1, "%s: could not open %s; falling back to /proc\n",
	     me, SYSFS_SCSI_DEVICES);

	sanei_proc_scsi_find_devices (findvendor, findmodel, findtype,
				      findbus, findchannel, findid, findlun,
				      attach);
	return;
      }

    vmt_len[0] = (findvendor) ? strlen(findvendor) : 0;
    vmt_len[1] = (findmodel) ? strlen(findmodel) : 0;
    vmt_len[2] = (findtype) ? strlen(findtype) : 0;

    lastbus = -1;
    number = -1;
    for (;;)
      {
	ret = readdir_r(scsidevs, &buf, &de);
	if (ret != 0)
	  {
	    DBG (1, "%s: could not read directory %s: %s\n",
		 me, SYSFS_SCSI_DEVICES, strerror(errno));

	    break;
	  }

	if (de == NULL)
	  break;

	if (buf.d_name[0] == '.')
	  continue;

	/* Extract bus, channel, id, lun from directory name b:c:i:l */
	ptr = buf.d_name;
	for (i = 0; i < 4; i++)
	  {
	    errno = 0;
	    val = strtol (ptr, &end, 10);
	    if (((errno == ERANGE) && ((val == LONG_MAX) || (val == LONG_MIN)))
		|| ((errno != 0) && (val == 0)))
	      {
		DBG (1, "%s: invalid integer in string (%s): %s\n",
		     me, ptr, strerror(errno));

		i = 12; /* Skip */
		break;
	      }

	    if (end == ptr)
	      {
		DBG (1, "%s: no integer found in string: %s (%d)\n", me, ptr, i);

		i = 12; /* Skip */
		break;
	      }

	    if (*end && (*end != ':'))
	      {
		DBG (1, "%s: parse error on string %s (%d)\n", me, buf.d_name, i);

		i = 12; /* Skip */
		break;
	      }

	    if (val > INT_MAX)
	      {
		DBG (1, "%s: integer value too large (%s)\n", me, buf.d_name);

		i = 12; /* Skip */
		break;
	      }

	    bcil[i] = (int) val;
	    ptr = end + 1;
	  }

	/* Skip this one */
	if (i == 12)
	  continue;

	if (bcil[0] != lastbus)
	  {
	    lastbus = bcil[0];
	    number++;
	  }

	for (i = 0; i < 3; i++)
	  {
	    ret = snprintf (path, PATH_MAX, "%s/%s/%s",
			   SYSFS_SCSI_DEVICES, buf.d_name, vmtfiles[i]);
	    if ((ret < 0) || (ret >= PATH_MAX))
	      {
		DBG (1, "%s: skipping %s/%s, PATH_MAX exceeded on %s\n",
		     me, SYSFS_SCSI_DEVICES, buf.d_name, vmtfiles[i]);

		i = 12; /* Skip */
		break;
	      }

	    memset (vmt[i], 0, sizeof(vmt[i]));

	    fp = fopen(path, "r");
	    if (!fp)
	      {
		DBG (1, "%s: could not open %s: %s\n", me, path, strerror(errno));

		i = 12; /* Skip */
		break;
	      }

	    ret = fread (vmt[i], 1, sizeof(vmt[i]) - 1, fp);
	    if (ret <= 0)
	      {
		if (ferror(fp))
		  {
		    DBG (1, "%s: error reading %s\n", me, path);

		    i = 12; /* Skip */
		    break;
		  }
	      }

	    if (vmt[i][ret - 1] == '\n')
	      vmt[i][ret - 1] = '\0';

	    fclose (fp);
	  }

	/* Skip this one */
	if (i == 12)
	  continue;

	/* Type is a numeric string and must be converted back to a well-known string */
	errno = 0;
	val = strtol (vmt[2], &end, 10);
	if (((errno == ERANGE) && ((val == LONG_MAX) || (val == LONG_MIN)))
	    || ((errno != 0) && (val == 0)))
	  {
	    DBG (1, "%s: invalid integer in type string (%s): %s\n",
		 me, vmt[2], strerror(errno));
	    continue;
	  }

	if (end == vmt[2])
	  {
	    DBG (1, "%s: no integer found in type string: %s\n", me, vmt[2]);
	    continue;
	  }

	if ((val < 0) || (val >= (int)(sizeof(lnxscsi_device_types) / sizeof(lnxscsi_device_types[0]))))
	  {
	    DBG (1, "%s: invalid type %ld\n", me, val);
	    continue;
	  }

	strncpy(vmt[2], lnxscsi_device_types[val], sizeof(vmt[2]) - 1);

	if ((!findvendor || strncmp (vmt[0], findvendor, vmt_len[0]) == 0)
	    && (!findmodel || strncmp (vmt[1], findmodel, vmt_len[1]) == 0)
	    && (!findtype || strncmp (vmt[2], findtype, vmt_len[2]) == 0)
	    && (findbus == -1 || bcil[0] == findbus)
	    && (findchannel == -1 || bcil[1] == findchannel)
	    && (findid == -1 || bcil[2] == findid)
	    && (findlun == -1 || bcil[3] == findlun))
	  {
	    DBG (2, "%s: found: vendor=%s model=%s type=%s\n\t"
		 "bus=%d chan=%d id=%d lun=%d num=%d\n",
		 me, vmt[0], vmt[1], vmt[2],
		 bcil[0], bcil[1], bcil[2], bcil[3], number);

	    if (lx_chk_devicename (number, dev_name, sizeof (dev_name),
				   bcil[0], bcil[1], bcil[2], bcil[3])
		&& ((*attach) (dev_name) != SANE_STATUS_GOOD))
	      {
		DBG (1, "%s: bad attach\n", me);
	      }
	  }
	else
	  {
	    DBG (2, "%s: no match\n", me);
	  }
      }

    closedir(scsidevs);
  }

#endif /* USE == LINUX_INTERFACE */


#if USE == BSD_INTERFACE

#ifndef HAVE_SCSIREQ_ENTER
  static int scsireq_enter (int fd, scsireq_t * hdr)
  {
    return ioctl (fd, SCIOCCOMMAND, hdr);
  }
#endif /* !HAVE_SCSIREQ_ENTER */

  SANE_Status
    sanei_scsi_cmd2 (int fd,
		     const void *cmd, size_t cmd_size,
		     const void *src, size_t src_size,
		     void *dst, size_t * dst_size)
  {
    /* xxx obsolete: size_t cdb_size;
     */
    scsireq_t hdr;
    int result;

/* xxx obsolete: 
  cdb_size = CDB_SIZE (*(u_char *) src);
*/

    memset (&hdr, 0, sizeof (hdr));
    memcpy (hdr.cmd, cmd, cmd_size);
    if (dst_size && *dst_size)
      {
	/* xxx obsolete: assert (cdb_size == src_size);
	 */
	hdr.flags = SCCMD_READ;
	hdr.databuf = dst;
	hdr.datalen = *dst_size;
      }
    else
      {
	/* xxx obsolete: assert (cdb_size <= src_size);
	 */
	hdr.flags = SCCMD_WRITE;
	/* The old variant:
	   hdr.databuf = (char *) src + cdb_size;
	   hdr.datalen = src_size;
	   xxxxxx huh? Shouldn't the above line have been src_size - cdb_size)
	 */
	hdr.databuf = (char *) src;
	hdr.datalen = src_size;
      }
    hdr.timeout = sane_scsicmd_timeout * 1000;
    hdr.cmdlen = cmd_size;
    hdr.senselen = sizeof (hdr.sense);

    result = scsireq_enter (fd, &hdr);
    if (result < 0)
      {
	DBG (1, "sanei_scsi_cmd: scsi_reqenter() failed: %s\n",
	     strerror (errno));
	return SANE_STATUS_IO_ERROR;
      }
    if (hdr.retsts != SCCMD_OK)
      {
	SANEI_SCSI_Sense_Handler handler;

	DBG (1, "sanei_scsi_cmd: scsi returned with status %d\n", hdr.retsts);
	switch (hdr.retsts)
	  {
	  case SCCMD_TIMEOUT:
	  case SCCMD_BUSY:
	    return SANE_STATUS_DEVICE_BUSY;

	  case SCCMD_SENSE:
	    handler = fd_info[fd].sense_handler;
	    if (handler)
	      return (*handler) (fd, &hdr.sense[0],
				 fd_info[fd].sense_handler_arg);
	    /* fall through */
	  default:
	    return SANE_STATUS_IO_ERROR;
	  }
      }

    if (dst_size)
      *dst_size = hdr.datalen_used;

    return SANE_STATUS_GOOD;
  }
#endif /* USE == BSD_INTERFACE */

#if USE == FREEBSD_CAM_INTERFACE
  SANE_Status sanei_scsi_cmd2 (int fd,
			       const void *cmd, size_t cmd_size,
			       const void *src, size_t src_size,
			       void *dst, size_t * dst_size)
  {

    struct cam_device *dev;
    union ccb *ccb;
    int rv;
    u_int32_t ccb_flags;
    char *data_buf;
    size_t data_len;
    SANE_Status status;

    if (fd < 0 || fd > CAM_MAXDEVS || cam_devices[fd] == NULL)
      {
	fprintf (stderr, "attempt to reference invalid unit %d\n", fd);
	return SANE_STATUS_INVAL;
      }

    dev = cam_devices[fd];
    ccb = cam_getccb (dev);

    /* Build the CCB */
    bzero (&(&ccb->ccb_h)[1], sizeof (struct ccb_scsiio));
    bcopy (cmd, &ccb->csio.cdb_io.cdb_bytes, cmd_size);

    /*
     * Set the data direction flags.
     */
    if (dst_size && *dst_size)
      {
	/* xxx obsolete: assert (cdb_size == src_size);
	 */
	ccb_flags = CAM_DIR_IN;
	data_buf = ((char *) (dst));
	data_len = *dst_size;
      }
    else if (src_size > 0)
      {
	ccb_flags = CAM_DIR_OUT;
	data_buf = ((char *) (src));
	data_len = src_size;
      }
    else
      {
	ccb_flags = CAM_DIR_NONE;
	data_buf = NULL;
	data_len = 0;
      }

    cam_fill_csio (&ccb->csio,
		   /* retries */ 1,
		   /* cbfncp */ NULL,
		   /* flags */ ccb_flags,
		   /* tag_action */ MSG_SIMPLE_Q_TAG,
		   /* data_ptr */ (u_int8_t *) data_buf,
		   /* dxfer_len */ data_len,
		   /* sense_len */ SSD_FULL_SIZE,
		   /* cdb_len */ cmd_size,
		   /* timeout */ sane_scsicmd_timeout * 1000);

    /* Run the command */
    errno = 0;
    if ((rv = cam_send_ccb (dev, ccb)) == -1)
      {
	cam_freeccb (ccb);
	return (SANE_STATUS_IO_ERROR);
      }

    if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)
      {
	SANEI_SCSI_Sense_Handler handler;

	DBG (1, "sanei_scsi_cmd: scsi returned with status %d\n",
	     (ccb->ccb_h.status & CAM_STATUS_MASK));

	switch (ccb->ccb_h.status & CAM_STATUS_MASK)
	  {
	  case CAM_BUSY:
	  case CAM_SEL_TIMEOUT:
	  case CAM_SCSI_BUSY:
	    status = SANE_STATUS_DEVICE_BUSY;
	    break;
	  default:
	    status = SANE_STATUS_IO_ERROR;
	  }

	handler = fd_info[fd].sense_handler;
	if (handler && (ccb->ccb_h.status & CAM_AUTOSNS_VALID))
	  {
	    SANE_Status st = (*handler)
	      (fd, ((u_char *) (&ccb->csio.sense_data)),
	       fd_info[fd].sense_handler_arg);
	    cam_freeccb (ccb);
	    return st;
	  }
	else
	  {
	    cam_freeccb (ccb);
	    return status;
	  }
      }
    cam_freeccb (ccb);
    return SANE_STATUS_GOOD;
  }

#define WE_HAVE_FIND_DEVICES

  int
    cam_compare_inquiry (int fd, path_id_t path_id,
			 target_id_t target_id, lun_id_t target_lun,
			 const char *vendor, const char *product,
			 const char *type)
  {
    struct ccb_dev_match cdm;
    struct device_match_pattern *pattern;
    struct scsi_inquiry_data *inq;
    int retval = 0;

    /* build ccb for device match */
    bzero (&cdm, sizeof (cdm));
    cdm.ccb_h.func_code = XPT_DEV_MATCH;

    /* result buffer */
    cdm.match_buf_len = sizeof (struct dev_match_result);
    cdm.matches = (struct dev_match_result *) malloc (cdm.match_buf_len);
    cdm.num_matches = 0;

    /* pattern buffer */
    cdm.num_patterns = 1;
    cdm.pattern_buf_len = sizeof (struct dev_match_pattern);
    cdm.patterns = (struct dev_match_pattern *) malloc (cdm.pattern_buf_len);

    /* assemble conditions */
    cdm.patterns[0].type = DEV_MATCH_DEVICE;
    pattern = &cdm.patterns[0].pattern.device_pattern;
    pattern->flags = DEV_MATCH_PATH | DEV_MATCH_TARGET | DEV_MATCH_LUN;
    pattern->path_id = path_id;
    pattern->target_id = target_id;
    pattern->target_lun = target_lun;

    if (ioctl (fd, CAMIOCOMMAND, &cdm) == -1)
      {
	DBG (1, "error sending CAMIOCOMMAND ioctl");
	retval = -1;
	goto ret;
      }

    if ((cdm.ccb_h.status != CAM_REQ_CMP)
	|| ((cdm.status != CAM_DEV_MATCH_LAST)
	    && (cdm.status != CAM_DEV_MATCH_MORE)))
      {
	DBG (1, "got CAM error %#x, CDM error %d\n",
	     cdm.ccb_h.status, cdm.status);
	retval = -1;
	goto ret;
      }

    if (cdm.num_matches == 0)
      {
	DBG (1, "not found\n");
	retval = -1;
	goto ret;
      }

    if (cdm.matches[0].type != DEV_MATCH_DEVICE)
      {
	DBG (1, "no device match\n");
	retval = -1;
	goto ret;
      }

    inq = &cdm.matches[0].result.device_result.inq_data;
    if ((vendor && cam_strmatch (inq->vendor, vendor, SID_VENDOR_SIZE)) ||
	(product && cam_strmatch (inq->product, product, SID_PRODUCT_SIZE)))
      retval = 1;

  ret:
    free (cdm.patterns);
    free (cdm.matches);
    return (retval);
  }

  void
    sanei_scsi_find_devices (const char *findvendor, const char *findmodel,
			     const char *findtype,
			     int findbus, int findchannel, int findid,
			     int findlun,
			     SANE_Status (*attach) (const char *dev))
  {
    int fd;
    struct ccb_dev_match cdm;
    struct periph_match_pattern *pattern;
    struct periph_match_result *result;
    int i;
    char devname[16];

    DBG_INIT ();

    if ((fd = open (XPT_DEVICE, O_RDWR)) == -1)
      {
	DBG (1, "could not open %s\n", XPT_DEVICE);
	return;
      }

    /* build ccb for device match */
    bzero (&cdm, sizeof (cdm));
    cdm.ccb_h.func_code = XPT_DEV_MATCH;

    /* result buffer */
    cdm.match_buf_len = sizeof (struct dev_match_result) * 100;
    cdm.matches = (struct dev_match_result *) malloc (cdm.match_buf_len);
    cdm.num_matches = 0;

    /* pattern buffer */
    cdm.num_patterns = 1;
    cdm.pattern_buf_len = sizeof (struct dev_match_pattern);
    cdm.patterns = (struct dev_match_pattern *) malloc (cdm.pattern_buf_len);

    /* assemble conditions ... findchannel is ignored */
    cdm.patterns[0].type = DEV_MATCH_PERIPH;
    pattern = &cdm.patterns[0].pattern.periph_pattern;
    pattern->flags = PERIPH_MATCH_NAME;
    strcpy (pattern->periph_name, "pass");
    if (findbus != -1)
      {
	pattern->path_id = findbus;
	pattern->flags |= PERIPH_MATCH_PATH;
      }
    if (findid != -1)
      {
	pattern->target_id = findid;
	pattern->flags |= PERIPH_MATCH_TARGET;
      }
    if (findlun != -1)
      {
	pattern->target_lun = findlun;
	pattern->flags |= PERIPH_MATCH_LUN;
      }

    /* result loop */
    do
      {
	if (ioctl (fd, CAMIOCOMMAND, &cdm) == -1)
	  {
	    DBG (1, "error sending CAMIOCOMMAND ioctl");
	    break;
	  }

	if ((cdm.ccb_h.status != CAM_REQ_CMP)
	    || ((cdm.status != CAM_DEV_MATCH_LAST)
		&& (cdm.status != CAM_DEV_MATCH_MORE)))
	  {
	    DBG (1, "got CAM error %#x, CDM error %d\n",
		 cdm.ccb_h.status, cdm.status);
	    break;
	  }

	for (i = 0; i < cdm.num_matches; i++)
	  {
	    if (cdm.matches[i].type != DEV_MATCH_PERIPH)
	      continue;
	    result = &cdm.matches[i].result.periph_result;
	    DBG (4, "%s%d on scbus%d %d:%d\n",
		 result->periph_name, result->unit_number,
		 result->path_id, result->target_id, result->target_lun);
	    if (cam_compare_inquiry (fd, result->path_id,
				     result->target_id, result->target_lun,
				     findvendor, findmodel, findtype) == 0)
	      {
		sprintf (devname, "/dev/%s%d", result->periph_name,
			 result->unit_number);
		(*attach) (devname);
	      }
	  }
      }
    while ((cdm.ccb_h.status == CAM_REQ_CMP)
	   && (cdm.status == CAM_DEV_MATCH_MORE));

    free (cdm.patterns);
    free (cdm.matches);
    close (fd);
    return;
  }

#endif



#if USE == HPUX_INTERFACE
/* XXX untested code! */
  SANE_Status
    sanei_scsi_cmd2 (int fd,
		     const void *cmd, size_t cmd_size,
		     const void *src, size_t src_size,
		     void *dst, size_t * dst_size)
  {
    struct sctl_io hdr;
    /* xxx obsolete size_t cdb_size;

       cdb_size = CDB_SIZE (*(u_char *) src);
     */

    memset (&hdr, 0, sizeof (hdr));
    memcpy (hdr.cdb, cmd, cmd_size);
    if (dst_size && *dst_size)
      {
	/* xxx obsolete assert (cdb_size == src_size);
	 */
	hdr.flags = SCTL_READ;
	hdr.data = dst;
	hdr.data_length = *dst_size;
      }
    else
      {
	/* xxx obsolete assert (cdb_size <= src_size);
	 */
	hdr.data = (char *) src;
	hdr.data_length = src_size;
      }
    hdr.cdb_length = cmd_size;
    hdr.max_msecs = sane_scsicmd_timeout * 1000;
    if (ioctl (fd, SIOC_IO, &hdr) < 0)
      {
	DBG (1, "sanei_scsi_cmd: ioctl(SIOC_IO) failed: %s\n",
	     strerror (errno));
	return SANE_STATUS_IO_ERROR;
      }
    if (hdr.cdb_status)
      DBG (1, "sanei_scsi_cmd: SCSI completed with cdb_status=%d\n",
	   hdr.cdb_status);
    if (dst_size)
      *dst_size = hdr.data_xfer;

    if (hdr.sense_xfer > 0 && (hdr.sense[0] & 0x80)
	&& fd_info[fd].sense_handler)
      return (*fd_info[fd].sense_handler) (fd, hdr.sense,
					   fd_info[fd].sense_handler_arg);
    return SANE_STATUS_GOOD;
  }
#endif /* USE == HPUX_INTERFACE */


#if USE == OPENSTEP_INTERFACE
  SANE_Status
    sanei_scsi_cmd2 (int fd,
		     const void *cmd, size_t cmd_size,
		     const void *src, size_t src_size,
		     void *dst, size_t * dst_size)
  {
    struct scsi_req hdr;
    /* xxx obsolete size_t cdb_size;

       cdb_size = CDB_SIZE (*(u_char *) src);
     */

    memset (&hdr, 0, sizeof (hdr));
    memcpy (&hdr.sr_cdb, cmd, cmd_size);
    hdr.sr_cdb_length = cmd_size;

    if (dst_size && *dst_size)
      {
	/* xxx obsolete assert (cdb_size == src_size);
	 */
	hdr.sr_dma_dir = SR_DMA_RD;
	hdr.sr_addr = dst;
	hdr.sr_dma_max = *dst_size;
      }
    else
      {
	/* xxx obsolete assert (cdb_size <= src_size);
	 */
	hdr.sr_dma_dir = SR_DMA_WR;
	hdr.sr_addr = (char *) src;
	hdr.sr_dma_max = src_size;
      }
    hdr.sr_ioto = sane_scsicmd_timeout;

    if (ioctl (fd, SGIOCREQ, &hdr) == -1)
      {
	DBG (1, "sanei_scsi_cmd: ioctl(SGIOCREQ) failed: %s\n",
	     strerror (errno));
	return SANE_STATUS_IO_ERROR;
      }
    if (hdr.sr_io_status != 1)
      DBG (1, "sanei_scsi_cmd: SGIOCREQ completed with sr_io_status=%d\n",
	   hdr.sr_io_status);

    if (hdr.sr_io_status == SR_IOST_CHKSNV)
      {
	struct scsi_req sr;
	struct cdb_6 *cdbp = &sr.sr_cdb.cdb_c6;
	struct esense_reply sense_reply;
	int i;
	char *p;

	/* clear struct */
	p = (char *) cdbp;
	for (i = 0; i < sizeof (union cdb); i++)
	  *p++ = 0;
	memset (&sr, 0, sizeof (struct scsi_req));

	cdbp->c6_opcode = C6OP_REQSENSE;
	cdbp->c6_lun = 0;	/* where do I get the lun from? */
	cdbp->c6_len = 0x20;
	cdbp->c6_ctrl = 0;

	sr.sr_dma_dir = SR_DMA_RD;
	sr.sr_addr = (char *) &sense_reply;
	sr.sr_dma_max = sizeof (struct esense_reply);
	sr.sr_ioto = sane_scsicmd_timeout;
	sr.sr_cdb_length = 6;

	ioctl (fd, SGIOCREQ, &sr);
	if (sense_reply.er_ibvalid)
	  {
	    sr.sr_esense = sense_reply;
	    if (fd_info[fd].sense_handler)
	      return (*fd_info[fd].sense_handler)
		(fd, (u_char *) & sr.sr_esense,
		 fd_info[fd].sense_handler_arg);
	  }

	/* sense reply is invalid */
	return SANE_STATUS_INVAL;
      }

    if (hdr.sr_scsi_status == SR_IOST_CHKSV && fd_info[fd].sense_handler)
      return (*fd_info[fd].sense_handler) (fd, (u_char *) & hdr.sr_esense,
					   fd_info[fd].sense_handler_arg);
    if (dst_size)
      *dst_size = hdr.sr_dma_xfr;
    return SANE_STATUS_GOOD;
  }
#endif /* USE == OPENSTEP_INTERFACE */


#if USE == DECUNIX_INTERFACE
  SANE_Status
    sanei_scsi_cmd2 (int fd,
		     const void *cmd, size_t cmd_size,
		     const void *src, size_t src_size,
		     void *dst, size_t * dst_size)
  {
    u_char sense[64];
    UAGT_CAM_CCB hdr;
    CCB_SCSIIO ccb;
    /* xxx obsolete size_t cdb_size;

       cdb_size = CDB_SIZE (*(u_char *) src);
     */

    memset (&ccb, 0, sizeof (ccb));
    ccb.cam_ch.my_addr = (CCB_HEADER *) & ccb;
    ccb.cam_ch.cam_ccb_len = sizeof (ccb);
    ccb.cam_ch.cam_func_code = XPT_SCSI_IO;
    ccb.cam_ch.cam_path_id = fd_info[fd].bus;
    ccb.cam_ch.cam_target_id = fd_info[fd].target;
    ccb.cam_ch.cam_target_lun = fd_info[fd].lun;
    ccb.cam_ch.cam_flags = 0;

    if (dst_size && *dst_size)
      {
	/* xxx obsolete assert (cdb_size == src_size);
	 */
	ccb.cam_ch.cam_flags |= CAM_DIR_IN;
	ccb.cam_data_ptr = (u_char *) dst;
	ccb.cam_dxfer_len = *dst_size;
      }
    else
      {
	/* xxx obsolete assert (cdb_size <= src_size);
	 */
	if (0 == src_size)
	  ccb.cam_ch.cam_flags |= CAM_DIR_NONE;
	else
	  ccb.cam_ch.cam_flags |= CAM_DIR_OUT;
	ccb.cam_data_ptr = (u_char *) src;
	ccb.cam_dxfer_len = src_size;
      }
    ccb.cam_timeout = sane_scsicmd_timeout;
    ccb.cam_cdb_len = cmd_size;
    memcpy (&ccb.cam_cdb_io.cam_cdb_bytes[0], cmd, cmd_size);

    memset (&hdr, 0, sizeof (hdr));
    hdr.uagt_ccb = (CCB_HEADER *) & ccb;
    hdr.uagt_ccblen = sizeof (ccb);
    hdr.uagt_buffer = ccb.cam_data_ptr;
    hdr.uagt_buflen = ccb.cam_dxfer_len;
    hdr.uagt_snsbuf = sense;
    hdr.uagt_snslen = sizeof (sense);
    hdr.uagt_cdb = 0;		/* indicate that CDB is in CCB */
    hdr.uagt_cdblen = 0;

    if (ioctl (cam_fd, UAGT_CAM_IO, &hdr) < 0)
      {
	DBG (1, "sanei_scsi_cmd: ioctl(UAGT_CAM_IO) failed: %s\n",
	     strerror (errno));
	return SANE_STATUS_IO_ERROR;
      }
    if (ccb.cam_ch.cam_status != CAM_REQ_CMP)
      {
	DBG (1, "sanei_scsi_cmd: UAGT_CAM_IO completed with cam_status=%d\n",
	     ccb.cam_ch.cam_status);

	if (ccb.cam_ch.cam_status == CAM_AUTOSNS_VALID
	    && fd_info[fd].sense_handler)
	  return (*fd_info[fd].sense_handler) (fd, sense,
					       fd_info[fd].sense_handler_arg);
	else
	  return SANE_STATUS_INVAL;
      }
    if (dst_size)
      *dst_size = ccb.cam_dxfer_len;
    return SANE_STATUS_GOOD;
  }
#endif /* USE == DECUNIX_INTERFACE */


#if USE == SCO_OS5_INTERFACE
  SANE_Status
    sanei_scsi_cmd2 (int fd,
		     const void *cmd, size_t cmd_size,
		     const void *src, size_t src_size,
		     void *dst, size_t * dst_size)
  {
    static u_char sense_buffer[256];
    struct scsicmd2 sc2;
    struct scsicmd *sc;
    /* xxx obsolete int cdb_size;
     */
    int opcode;
    int i;

    if (fd < 0)
      return SANE_STATUS_IO_ERROR;

    memset (&sc2, 0, sizeof (sc2));
    sc = &sc2.cmd;
    sc2.sense_len = sizeof (sense_buffer);
    sc2.sense_ptr = sense_buffer;

    /* xxx obsolete cdb_size = CDB_SIZE (*(u_char *) src);
     */
    if (dst_size && *dst_size)
      {
	sc->is_write = 0;
	sc->data_ptr = dst;
	sc->data_len = *dst_size;
      }
    else
      {
	sc->data_len = src_size;
	sc->data_ptr = (char *) src;
	sc->is_write = 1;
      }
    memcpy (sc->cdb, cmd, cmd_size);
    sc->cdb_len = cmd_size;

    /* Send the command down via the "pass-through" interface */
    if (ioctl (fd, SCSIUSERCMD2, &sc2) < 0)
      {
	DBG (1, "sanei_scsi_cmd: ioctl(SCSIUSERCMD2) failed: %s\n",
	     strerror (errno));
	return SANE_STATUS_IO_ERROR;
      }
    if (sc->host_sts || sc->target_sts)
      {
	DBG (1, "sanei_scsi_cmd: SCSIUSERCMD2 completed with "
	     "host_sts=%x, target_sts=%x\n", sc->host_sts, sc->target_sts);
	if (fd_info[fd].sense_handler)
	  return (*fd_info[fd].sense_handler) (fd, sense_buffer,
					       fd_info[fd].sense_handler_arg);
	return SANE_STATUS_IO_ERROR;
      }
    return SANE_STATUS_GOOD;
  }
#endif /* USE == SCO_OS5_INTERFACE */
#if USE == SYSVR4_INTERFACE

/*
 * UNIXWARE 2.x interface
 * (c) R=I+S Rapp Informatik System Germany
 * Email: wolfgang@rapp-informatik.de
 *
 * The driver version should run with other scsi componets like disk
 * attached to the same controller at the same time.
 *
 * Attention : This port needs a sane kernel driver for Unixware 2.x
 * The driver is available in binary pkgadd format
 * Plese mail me.
 *
 */
  SANE_Status
    sanei_scsi_cmd2 (int fd,
		     const void *cmd, size_t cmd_size,
		     const void *src, size_t src_size,
		     void *dst, size_t * dst_size)
  {
    struct sb sb, *sb_ptr;	/* Command block and pointer */
    struct scs *scs;		/* group 6 command pointer */
    struct scm *scm;		/* group 10 command pointer */
    struct scv *scv;		/* group 12 command pointer */
    char sense[32];		/* for call of sens req */
    char cmd[16];		/* global for right alignment */
    char *cp;

    /* xxx obsolete size_t cdb_size;

       cdb_size = CDB_SIZE (*(u_char *) src);
     */
    memset (&cmd, 0, 16);
    sb_ptr = &sb;
    sb_ptr->sb_type = ISCB_TYPE;
    cp = (char *) cmd;
    DBG (1,
	 "cdb_size = %d src = {0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x ...}\n",
	 cmd_size, cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7],
	 cp[8], cp[9]);
    switch (cmd_size)
      {
      default:
	return SANE_STATUS_IO_ERROR;
      case 6:
	scs = (struct scs *) cmd;
	memcpy (SCS_AD (scs), cmd, SCS_SZ);
	scs->ss_lun = 0;
	sb_ptr->SCB.sc_cmdpt = SCS_AD (scs);
	sb_ptr->SCB.sc_cmdsz = SCS_SZ;
	break;
      case 10:
	scm = (struct scm *) cmd;
	memcpy (SCM_AD (scm), cmd, SCM_SZ);
	scm->sm_lun = 0;
	sb_ptr->SCB.sc_cmdpt = SCM_AD (scm);
	sb_ptr->SCB.sc_cmdsz = SCM_SZ;
	break;
      case 12:
	scv = (struct scv *) cmd;
	memcpy (SCV_AD (scv), cmd, SCV_SZ);
	scv->sv_lun = 0;
	sb_ptr->SCB.sc_cmdpt = SCV_AD (scv);
	sb_ptr->SCB.sc_cmdsz = SCV_SZ;
	break;
      }
    if (dst_size && *dst_size)
      {
	assert (0 == src_size);
	sb_ptr->SCB.sc_mode = SCB_READ;
	sb_ptr->SCB.sc_datapt = dst;
	sb_ptr->SCB.sc_datasz = *dst_size;
      }
    else
      {
	assert (0 <= src_size);
	sb_ptr->SCB.sc_mode = SCB_WRITE;
	sb_ptr->SCB.sc_datapt = (char *) src;
	if ((sb_ptr->SCB.sc_datasz = src_size) > 0)
	  {
	    sb_ptr->SCB.sc_mode = SCB_WRITE;
	  }
	else
	  {
	    /* also use READ mode if the backends have write with length 0 */
	    sb_ptr->SCB.sc_mode = SCB_READ;
	  }
      }
    sb_ptr->SCB.sc_time = sane_scsicmd_timeout * 1000;
    DBG (1, "sanei_scsi_cmd: sc_mode = %d, sc_cmdsz = %d, sc_datasz = %d\n",
	 sb_ptr->SCB.sc_mode, sb_ptr->SCB.sc_cmdsz, sb_ptr->SCB.sc_datasz);
    {
      /* do read write by normal read or write system calls */
      /* the driver will lock process in momory and do optimized transfer */
      cp = (char *) cmd;
      switch (*cp)
	{
	case 0x0:		/* test unit ready */
	  if (ioctl (fd, SS_TEST, NULL) < 0)
	    {
	      return SANE_STATUS_DEVICE_BUSY;
	    }
	  break;
	case SS_READ:
	case SM_READ:
	  if (*dst_size > 0x2048)
	    {
	      sb_ptr->SCB.sc_datapt = NULL;
	      sb_ptr->SCB.sc_datasz = 0;
	      if (memcmp
		  (sb_ptr->SCB.sc_cmdpt, lastrcmd, sb_ptr->SCB.sc_cmdsz))
		{
		  /* set the command block for the next read or write */
		  memcpy (lastrcmd, sb_ptr->SCB.sc_cmdpt,
			  sb_ptr->SCB.sc_cmdsz);
		  if (!ioctl (fd, SDI_SEND, sb_ptr))
		    {
		      *dst_size = read (fd, dst, *dst_size);
		      if (*dst_size == -1)
			{
			  perror ("sanei-scsi:UW-driver read ");
			  return SANE_STATUS_IO_ERROR;
			}
		      break;
		    }
		}
	      else
		{
		  *dst_size = read (fd, dst, *dst_size);
		  if (*dst_size == -1)
		    {
		      perror ("sanei-scsi:UW-driver read ");
		      return SANE_STATUS_IO_ERROR;
		    }
		  break;
		}
	      return SANE_STATUS_IO_ERROR;
	    }
	  /* fall through for small read */
	default:
	  if (ioctl (fd, SDI_SEND, sb_ptr) < 0)
	    {
	      DBG (1, "sanei_scsi_cmd: ioctl(SDI_SEND) FAILED: %s\n",
		   strerror (errno));
	      return SANE_STATUS_IO_ERROR;
	    }
	  if (dst_size)
	    *dst_size = sb_ptr->SCB.sc_datasz;
#ifdef UWSUPPORTED		/* at this time not supported by driver */
	  if (sb_ptr->SCB.sc_comp_code != SDI_ASW)
	    {
	      DBG (1, "sanei_scsi_cmd: scsi_cmd failture %x\n",
		   sb_ptr->SCB.sc_comp_code);
	      if (sb_ptr->SCB.sc_comp_code == SDI_CKSTAT
		  && sb_ptr->SCB.sc_status == S_CKCON)
		if (fd_info[fd].sense_handler)
		  {
		    void *arg = fd_info[fd].sense_handler_arg;
		    return (*fd_info[fd].sense_handler) (fd,
							 (u_char *) & sb_ptr->
							 SCB.sc_link, arg);
		  }
	      return SANE_STATUS_IO_ERROR;
	    }
#endif
	  break;
	}
      return SANE_STATUS_GOOD;
    }
  }
#endif /* USE == SYSVR4_INTERFACE */
#if USE == SCO_UW71_INTERFACE
  SANE_Status
    sanei_scsi_cmd2 (int fd,
		     const void *cmd, size_t cmd_size,
		     const void *src, size_t src_size,
		     void *dst, size_t * dst_size)
  {
    static u_char sense_buffer[24];
    struct scb cmdblk;
    time_t elapsed;
    uint_t compcode, status;
    /* xxx obsolete int cdb_size, mode;
     */
    int mode;
    int i;

    if (fd < 0)
      return SANE_STATUS_IO_ERROR;

    cmdblk.sc_cmdpt = (caddr_t) cmd;
    /* xxx obsolete cdb_size = CDB_SIZE (*(u_char *) src);
     */
    cmdblk.sc_cmdsz = cmd_size;
    cmdblk.sc_time = 60000;	/* 60 secs */

    if (dst_size && *dst_size)
      {
	/* xxx obsolete assert (cdb_size == src_size);
	 */
	cmdblk.sc_datapt = (caddr_t) dst;
	cmdblk.sc_datasz = *dst_size;
	mode = SCB_READ;
      }
    else
      {
	/* xxx obsolete assert (cdb_size <= src_size);
	 */
	cmdblk.sc_datapt = (char *) src;
	cmdblk.sc_datasz = src_size;
	mode = SCB_WRITE;
      }

    if (pt_send (fd, cmdblk.sc_cmdpt, cmdblk.sc_cmdsz, cmdblk.sc_datapt,
		 cmdblk.sc_datasz, mode, cmdblk.sc_time, &elapsed, &compcode,
		 &status, sense_buffer, sizeof (sense_buffer)) != 0)
      {
	DBG (1, "sanei_scsi_cmd: pt_send failed: %s!\n", strerror (errno));
      }
    else
      {
	DBG (2, "sanei_scsi_cmd completed with: compcode = %x, status = %x\n",
	     compcode, status);

	switch (compcode)
	  {
	  case SDI_ASW:	/* All seems well */
	    return SANE_STATUS_GOOD;
	  case SDI_CKSTAT:
	    DBG (2, "Sense Data:\n");
	    for (i = 0; i < sizeof (sense_buffer); i++)
	      DBG (2, "%.2X ", sense_buffer[i]);
	    DBG (2, "\n");
	    if (fd_info[fd].sense_handler)
	      return (*fd_info[fd].sense_handler) (fd, sense_buffer,
						   fd_info[fd].
						   sense_handler_arg);
	    /* fall through */
	  default:
	    return SANE_STATUS_IO_ERROR;
	  }
      }
  }
#endif /* USE == SCO_UW71_INTERFACE */

#if USE == OS2_INTERFACE

#define WE_HAVE_FIND_DEVICES

  static int
    get_devicename (int bus, int target, int lun, char *name, size_t name_len)
  {
    snprintf (name, name_len, "b%dt%dl%d", bus, target, lun);
    DBG (1, "OS/2 searched device is %s\n", name);
    return 0;
  }

  void
    sanei_scsi_find_devices (const char *findvendor, const char *findmodel,
			     const char *findtype,
			     int findbus, int findchannel, int findid,
			     int findlun,
			     SANE_Status (*attach) (const char *dev))
  {
    size_t findvendor_len = 0, findmodel_len = 0, findtype_len = 0;
    char vendor[32], model[32], type[32], revision[32];
    int bus, channel, id, lun, number, i;
    char line[256], dev_name[128];
    const char *string;
    FILE *proc_fp;
    char *end;
    struct
    {
      const char *name;
      size_t name_len;
      int is_int;		/* integer valued? (not a string) */
      union
      {
	void *v;		/* avoids compiler warnings... */
	char *str;
	int *i;
      }
      u;
    }
    param[] =
    {
      {
	"Vendor:", 7, 0,
	{
	0}
      }
      ,
      {
	"Model:", 6, 0,
	{
	0}
      }
      ,
      {
	"Type:", 5, 0,
	{
	0}
      }
      ,
      {
	"Rev:", 4, 0,
	{
	0}
      }
      ,
      {
	"scsi", 4, 1,
	{
	0}
      }
      ,
      {
	"Channel:", 8, 1,
	{
	0}
      }
      ,
      {
	"Id:", 3, 1,
	{
	0}
      }
      ,
      {
	"Lun:", 4, 1,
	{
	0}
      }
    };

    param[0].u.str = vendor;
    param[1].u.str = model;
    param[2].u.str = type;
    param[3].u.str = revision;
    param[4].u.i = &bus;
    param[5].u.i = &channel;
    param[6].u.i = &id;
    param[7].u.i = &lun;

    DBG_INIT ();

    open_aspi ();		/* open aspi manager if not already done */

    DBG (2, "find_devices: open temporary file '%s'\n", tmpAspi);
    proc_fp = fopen (tmpAspi, "r");
    if (!proc_fp)
      {
	DBG (1, "could not open %s for reading\n", tmpAspi);
	return;
      }

    number = bus = channel = id = lun = -1;

    vendor[0] = model[0] = type[0] = '\0';
    if (findvendor)
      findvendor_len = strlen (findvendor);
    if (findmodel)
      findmodel_len = strlen (findmodel);
    if (findtype)
      findtype_len = strlen (findtype);

    while (!feof (proc_fp))
      {
	if (!fgets (line, sizeof (line), proc_fp))
	  break;		/* at eof exit */

	string = sanei_config_skip_whitespace (line);

	while (*string)
	  {
	    for (i = 0; i < NELEMS (param); ++i)
	      {
		if (strncmp (string, param[i].name, param[i].name_len) == 0)
		  {
		    string += param[i].name_len;
		    string = sanei_config_skip_whitespace (string);
		    if (param[i].is_int)
		      {
			*param[i].u.i = strtol (string, &end, 10);
			string = (char *) end;
		      }
		    else
		      {
			strncpy (param[i].u.str, string, 32);
			param[i].u.str[31] = '\0';
			while (*string && !isspace ((int) *string))
			  ++string;
		      }
		    string = sanei_config_skip_whitespace (string);

		    if (param[i].u.v == &bus)
		      ++number;
		    break;
		  }
	      }
	    if (i >= NELEMS (param))
	      ++string;		/* no match */
	  }

	if ((findvendor && !vendor[0]) || (findmodel && !model[0])
	    || (findtype && !type[0])
	    || (findbus >= 0 && bus == -1) || (findchannel >= 0
					       && channel == -1)
	    || (findlun >= 0 && lun == -1))
	  /* some info is still missing */
	  continue;

	if ((!findvendor || strncmp (vendor, findvendor, findvendor_len) == 0)
	    && (!findmodel || strncmp (model, findmodel, findmodel_len) == 0)
	    && (!findtype || strncmp (type, findtype, findtype_len) == 0)
	    && (findbus == -1 || bus == findbus)
	    && (findchannel == -1 || channel == findchannel)
	    && (findid == -1 || id == findid)
	    && (findlun == -1 || lun == findlun)
	    && get_devicename (bus, id, lun, dev_name, sizeof (dev_name)) >= 0
	    && (*attach) (dev_name) != SANE_STATUS_GOOD)
	  return;

	vendor[0] = model[0] = type[0] = 0;
	bus = channel = id = lun = -1;
      }

    DBG (2, "find_devices: close temporary file '%s'\n", tmpAspi);
    fclose (proc_fp);

    close_aspi ();		/* close aspi manager */
  }

/* XXX untested code! */
  SANE_Status
    sanei_scsi_cmd2 (int fd,
		     const void *cmd, size_t cmd_size,
		     const void *src, size_t src_size,
		     void *dst, size_t * dst_size)
  {
    ULONG rc;			/* Returns. */
    unsigned long cbreturn;
    unsigned long cbParam;
    if (aspi_buf == NULL)	/* avoid SIGSEGV in memcpy() when calling
				   sanei_scsi_cmd2() while aspi-driver is closed */
      {
	DBG (1, "sanei_scsi_cmd: Error no device (aspi_buf == NULL)\n");
	return SANE_STATUS_INVAL;
      }

    if (PSRBlock == NULL)	/* avoid SIGSEGV in memcpy() when calling
				   sanei_scsi_cmd2() while aspi-driver is closed */
      {
	DBG (1, "sanei_scsi_cmd: Error no device (PSRBlock == NULL)\n");
	return SANE_STATUS_INVAL;
      }

    memset (PSRBlock, 0, sizeof (SRB));	/* Okay, I'm paranoid. */
    PSRBlock->cmd = SRB_Command;	/* execute SCSI cmd */
    PSRBlock->ha_num = fd_info[fd].bus;	/* host adapter number */
    PSRBlock->u.cmd.target = fd_info[fd].target;	/* Target SCSI ID */
    PSRBlock->u.cmd.lun = fd_info[fd].lun;	/* Target SCSI LUN */
    PSRBlock->flags = SRB_Post;	/* posting enabled */
    if (dst_size && *dst_size)
      {
	/* Reading. */
	assert (*dst_size <= (size_t) sanei_scsi_max_request_size);
	PSRBlock->u.cmd.data_len = *dst_size;
	DBG (1, "sanei_scsi_cmd: Reading PSRBlock->u.cmd.data_len= %lu\n",
	     PSRBlock->u.cmd.data_len);
	PSRBlock->flags |= SRB_Read;
      }
    else
      {
	/* Writing. */
	PSRBlock->u.cmd.data_len = src_size;
	DBG (1, "sanei_scsi_cmd: Writing PSRBlock->u.cmd.data_len= %lu\n",
	     PSRBlock->u.cmd.data_len);
	assert (PSRBlock->u.cmd.data_len <=
		(unsigned long) sanei_scsi_max_request_size);
	if (PSRBlock->u.cmd.data_len)
	  PSRBlock->flags |= SRB_Write;
	else
	  PSRBlock->flags |= SRB_NoTransfer;
	memcpy (aspi_buf, src, PSRBlock->u.cmd.data_len);
      }
    PSRBlock->u.cmd.sense_len = 32;	/* length of sense buffer */
    PSRBlock->u.cmd.data_ptr = NULL;	/* pointer to data buffer already registered */
    PSRBlock->u.cmd.link_ptr = NULL;	/* pointer to next SRB */
    PSRBlock->u.cmd.cdb_len = cmd_size;	/* SCSI command length */
    memcpy (PSRBlock->u.cmd.cdb_st, cmd, cmd_size);

    /* Do the command. */
    rc = DosDevIOCtl (driver_handle, 0x92, 0x02,
		      (void *) PSRBlock, sizeof (SRB), &cbParam,
		      (void *) PSRBlock, sizeof (SRB), &cbreturn);

    if (rc)
      {
	DBG (1, "sanei_scsi_cmd: DosDevIOCtl failed. rc= %lu \n", rc);
	return SANE_STATUS_IO_ERROR;
      }

    /* Get sense data if available. */
    if ((PSRBlock->status == SRB_Aborted || PSRBlock->status == SRB_Error) &&
	PSRBlock->u.cmd.target_status == SRB_CheckStatus
	&& fd_info[fd].sense_handler != 0)
      {
	SANEI_SCSI_Sense_Handler s_handler = fd_info[fd].sense_handler;
	return (*s_handler) (fd, &PSRBlock->u.cmd.cdb_st[cmd_size],
			     fd_info[fd].sense_handler_arg);
      }
    if (PSRBlock->status != SRB_Done ||
	PSRBlock->u.cmd.ha_status != SRB_NoError ||
	PSRBlock->u.cmd.target_status != SRB_NoStatus)
      {
	DBG (1, "sanei_scsi_cmd:  command 0x%02x failed.\n"
	     "PSRBlock->status= 0x%02x\n"
	     "PSRBlock->u.chm.ha_status= 0x%02x\n"
	     "PSRBlock->u.cmd.target_status= 0x%02x\n",
	     PSRBlock->u.cmd.cdb_st[0],
	     PSRBlock->status,
	     PSRBlock->u.cmd.ha_status, PSRBlock->u.cmd.target_status);
	return SANE_STATUS_IO_ERROR;
      }

    if (dst_size && *dst_size)	/* Reading? */
      memcpy ((char *) dst, aspi_buf, *dst_size);
    return SANE_STATUS_GOOD;
  }
#endif /* USE == OS2_INTERFACE */

#if USE == STUBBED_INTERFACE
  SANE_Status
    sanei_scsi_cmd2 (int fd,
		     const void *cmd, size_t cmd_size,
		     const void *src, size_t src_size,
		     void *dst, size_t * dst_size)
  {
    return SANE_STATUS_UNSUPPORTED;
  }
#endif /* USE == STUBBED_INTERFACE */

#if USE == IRIX_INTERFACE

#define WE_HAVE_FIND_DEVICES

  SANE_Status
    sanei_scsi_cmd2 (int fd,
		     const void *cmd, size_t cmd_size,
		     const void *src, size_t src_size,
		     void *dst, size_t * dst_size)
  {
    dsreq_t scsi_req;		/* SCSI request */
/* xxx obsolete size_t  cdb_size; *//* Size of SCSI command */
    static u_char *cmdbuf = NULL,	/* Command buffer */
     *sensebuf = NULL,		/* Request sense buffer */
     *databuf = NULL;		/* Data buffer */

    /*
     * Allocate the sense and command data buffers as necessary; we have
     * to do this to avoid buffer alignment problems, since some
     * hardware requires these buffers to be 32-bit aligned.
     */
    if (cmdbuf == NULL)
      {
	cmdbuf = malloc (64);
	sensebuf = malloc (1024);	/* may be can reduced to 128 */
	databuf = malloc (MAX_DATA);

	if (cmdbuf == NULL || sensebuf == NULL || databuf == NULL)
	  return SANE_STATUS_NO_MEM;
      }

    /*
     * Build the SCSI request...
     */
    /* xxx obsolete cdb_size = CDB_SIZE (*(u_char *) src);
     */

    DBG (1, "sanei_scsi_cmd: cmd_size = %d\n", cmd_size);

    if (dst != NULL)
      {
	/*
	 * SCSI command returning/reading data...
	 */
	scsi_req.ds_flags = DSRQ_READ | DSRQ_SENSE;
	scsi_req.ds_time = 120 * 1000;
	scsi_req.ds_cmdbuf = (caddr_t) cmdbuf;
	scsi_req.ds_cmdlen = cmd_size;
	scsi_req.ds_databuf = (caddr_t) databuf;
	scsi_req.ds_datalen = *dst_size;
	scsi_req.ds_sensebuf = (caddr_t) sensebuf;
	scsi_req.ds_senselen = 128;	/* 1024 does not work, 128 is tested (O.Rauch) */

	/*
	 * Copy command to cmdbuf to assure 32-bit alignment.
	 */
	memcpy (cmdbuf, cmd, cmd_size);
      }
    else
      {
	/*
	 * SCSI command sending/writing data...
	 */
	scsi_req.ds_flags = DSRQ_WRITE | DSRQ_SENSE;
	scsi_req.ds_time = 120 * 1000;
	scsi_req.ds_cmdbuf = (caddr_t) cmdbuf;
	scsi_req.ds_cmdlen = cmd_size;
	scsi_req.ds_databuf = (caddr_t) databuf;
	scsi_req.ds_datalen = src_size;
	scsi_req.ds_sensebuf = (caddr_t) sensebuf;
	scsi_req.ds_senselen = 128;

	/*
	 * Copy command and data to local buffers to ensure 32-bit alignment...
	 */
	memcpy (cmdbuf, (u_char *) cmd, cmd_size);
	memcpy (databuf, (u_char *) src, src_size);
      }

    bzero (sensebuf, 128);

    /*
     * Do SCSI request...
     */
    if (ioctl (fd, DS_ENTER, &scsi_req) < 0)
      {
	DBG (1, "sanei_scsi_cmd: ioctl failed - %s\n", strerror (errno));
	return SANE_STATUS_IO_ERROR;
      }

    DBG (1, "sanei_scsi_cmd: status = %d\n", scsi_req.ds_status);

    /*
     * Set the incoming data size and copy the destination data as needed...
     */
    if (dst != NULL)
      {
	*dst_size = scsi_req.ds_datasent;

	DBG (1, "sanei_scsi_cmd: read %d bytes\n", scsi_req.ds_datasent);

	if (scsi_req.ds_datasent > 0)
	  memcpy (dst, databuf, scsi_req.ds_datasent);
      }

    /*
     * Return the appropriate status code...
     */
    if (scsi_req.ds_status != 0)
      {
	if (scsi_req.ds_status == STA_BUSY)
	  return SANE_STATUS_DEVICE_BUSY;
	else if (fd_info[fd].sense_handler)
	  return (*fd_info[fd].sense_handler) (fd, sensebuf,
					       fd_info[fd].sense_handler_arg);
	else
	  return SANE_STATUS_IO_ERROR;
      }
    return SANE_STATUS_GOOD;
  }

  void
    sanei_scsi_find_devices (const char *findvendor, const char *findmodel,
			     const char *findtype,
			     int findbus, int findchannel, int findid,
			     int findlun,
			     SANE_Status (*attach) (const char *dev))
  {
    size_t findvendor_len = 0, findmodel_len = 0;
    /* Lengths of search strings */
    inventory_t *inv;		/* Current hardware inventory entry */
    int bus, id, lun;		/* Current Bus, ID, and LUN */
    char dev_name[128];		/* SCSI device name */
    int fd;			/* SCSI file */
    size_t inqsize;		/* Size of returned inquiry data */
    char vendor[9],		/* Vendor name */
      model[17];		/* Model/product name */
    u_char inqdata[128],	/* Inquiry data buffer */
      inqcommand[6];		/* Inquiry command (0x12) buffer */

    DBG_INIT ();

    vendor[0] = model[0] = '\0';
    if (findvendor)
      findvendor_len = strlen (findvendor);
    if (findmodel)
      findmodel_len = strlen (findmodel);

    if (findvendor != NULL)
      DBG (1, "sanei_scsi_find_devices: looking for vendors starting "
	   "with \"%s\".\n", findvendor);

    if (findmodel != NULL)
      DBG (1, "sanei_scsi_find_devices: looking for models starting "
	   "with \"%s\".\n", findmodel);

    setinvent ();

    while ((inv = getinvent ()) != NULL)
      {
	if (inv->inv_class != INV_SCSI ||
	    (inv->inv_type != INV_SCANNER && inv->inv_type != INV_CPU))
	  continue;

	bus = inv->inv_controller;
	id = inv->inv_unit;
	lun = inv->inv_state >> 8;

	DBG (1, "sanei_scsi_find_devices: found %s on controller %d, "
	     "ID %d, LUN %d.\n",
	     inv->inv_type == INV_SCANNER ? "scanner" : "processor",
	     bus, id, lun);

	if ((findbus >= 0 && bus != findbus) ||
	    (findid >= 0 && id != findid) || (findlun >= 0 && lun != findlun))
	  {
	    DBG (1, "sanei_scsi_find_devices: ignoring this device.\n");
	    continue;
	  }

	sprintf (dev_name, "/dev/scsi/sc%dd%dl%d", bus, id, lun);
	DBG (1, "sanei_scsi_find_devices: device name is \"%s\".\n",
	     dev_name);

	/*
	 * Open the SCSI device and get the inquiry data...
	 */

	if (sanei_scsi_open (dev_name, &fd, NULL, NULL) != SANE_STATUS_GOOD)
	  {
	    DBG (1,
		 "sanei_scsi_find_devices: unable to open device file - %s.\n",
		 strerror (errno));
	    continue;
	  }

	DBG (1, "sanei_scsi_find_devices: device fd = %d.\n", fd);

	inqsize = sizeof (inqdata);

	inqcommand[0] = 0x12;
	inqcommand[1] = 0;
	inqcommand[2] = 0;
	inqcommand[3] = sizeof (inqdata) >> 8;
	inqcommand[4] = sizeof (inqdata);
	inqcommand[5] = 0;

	if (sanei_scsi_cmd (fd, inqcommand, sizeof (inqcommand), inqdata,
			    &inqsize) != SANE_STATUS_GOOD)
	  {
	    DBG (1,
		 "sanei_scsi_find_devices: unable to get inquiry data - %s.\n",
		 strerror (errno));
	    continue;
	  }

	sanei_scsi_close (fd);

	strncpy (vendor, (char *) inqdata + 8, 8);
	vendor[8] = '\0';
	strncpy (model, (char *) inqdata + 16, 16);
	model[16] = '\0';

	DBG (1, "sanei_scsi_find_devices: vendor = \'%s\', model = \'%s'.\n",
	     vendor, model);

	/*
	 * Compare as necessary...
	 */

	if ((findvendor != NULL
	     && strncmp (findvendor, vendor, findvendor_len))
	    || (findmodel != NULL
		&& strncmp (findmodel, model, findmodel_len)))
	  {
	    DBG (1, "sanei_scsi_find_devices: ignoring this device.\n");
	    continue;
	  }

	/*
	 * OK, this one matches, so use it!
	 */

	DBG (1, "sanei_scsi_find_devices: attaching this device.\n");

	(*attach) (dev_name);
      }
  }
#endif /* USE == IRIX_INTERFACE */

#if USE == AIX_GSC_INTERFACE
  SANE_Status
    sanei_scsi_cmd2 (int fd,
		     const void *cmd, size_t cmd_size,
		     const void *src, size_t src_size,
		     void *dst, size_t * dst_size)
  {
    scmd_t scmd;
    /* xxx obsolete size_t cdb_size;
     */
    char sense_buf[32];
    char status;

    /* xxx obsolete cdb_size = CDB_SIZE (*(u_char *) src);
     */

    memset (&scmd, 0, sizeof (scmd));
    if (dst_size && *dst_size)
      {
	/* xxx obsolete assert (cdb_size == src_size);
	 */
	scmd.rw = 1;
	scmd.data_buf = dst;
	scmd.datalen = *dst_size;
      }
    else
      {
	/* assert (cdb_size <= src_size);
	 */
	scmd.data_buf = (char *) src;
	scmd.datalen = src_size;
      }
    scmd.cdb = (char *) cmd;
    scmd.cdblen = cmd_size;
    scmd.timeval = sane_scsicmd_timeout;
    scmd.sense_buf = sense_buf;
    scmd.senselen = sizeof (sense_buf);
    scmd.statusp = &status;
    DBG (1, "sanei_scsi_cmd: scmd.rw = %d, scmd.cdblen = %d, ",
	 scmd.rw, scmd.cdblen);
    DBG (1, "scmd.cdb = {0x%x,0x%x,0x%x,0x%x,0x%x,0x%x, ...}\n",
	 scmd.cdb[0], scmd.cdb[1], scmd.cdb[2],
	 scmd.cdb[3], scmd.cdb[4], scmd.cdb[5]);
    if (ioctl (fd, GSC_CMD, &scmd) < 0)
      {
	DBG (1, "sanei_scsi_cmd: ioctl(SIOC_IO) failed: %s\n",
	     strerror (errno));
	return SANE_STATUS_IO_ERROR;
      }
    if (*scmd.statusp)
      DBG (1, "sanei_scsi_cmd: SCSI completed with status=%d\n",
	   *scmd.statusp);

    DBG (1, "sanei_scsi_cmd: dst = {0x%x,0x%x,0x%x,0x%x,0x%x,0x%x, ...}\n",
	 *((char *) dst + 0), *((char *) dst + 1), *((char *) dst + 2),
	 *((char *) dst + 3), *((char *) dst + 4), *((char *) dst + 5));

    if (dst_size)
      *dst_size = scmd.datalen;

    if (scmd.senselen > 0
	&& (scmd.sense_buf[0] & 0x80) && fd_info[fd].sense_handler)
      return (*fd_info[fd].sense_handler) (fd, (u_char *) scmd.sense_buf,
					   fd_info[fd].sense_handler_arg);
    return SANE_STATUS_GOOD;
  }
#endif /* USE == AIX_GSC_INTERFACE */

#if USE == SOLARIS_SG_INTERFACE

#ifndef CCS_SENSE_LEN
# define CCS_SENSE_LEN 18
#endif

  SANE_Status
    sanei_scsi_cmd2 (int fd,
		     const void *cmd, size_t cmd_size,
		     const void *src, size_t src_size,
		     void *dst, size_t * dst_size)
  {
    struct user_scsi us;
    /* xxx obsolete size_t cdb_size;
     */
    char sensebf[CCS_SENSE_LEN];

    /* xxx obsolete cdb_size = CDB_SIZE (*(u_char *) src);
     */

    /* first put the user scsi structure together.  */
    memset (&us, 0, sizeof (us));
    us.us_cdbp = (caddr_t) cmd;
    us.us_cdblen = cmd_size;
    us.us_sensep = sensebf;
    us.us_senselen = CCS_SENSE_LEN;
    if (dst && dst_size && *dst_size)
      {
	us.us_bufp = (caddr_t) dst;
	us.us_buflen = *dst_size;
	us.us_flags = USER_SCSI_READ;
      }
    else
      {
	us.us_bufp = (caddr_t) src;
	us.us_buflen = src_size;
	us.us_flags = USER_SCSI_WRITE;
      }
    /* now run it */
    if (ioctl (fd, USER_SCSI, &us) < 0)
      return SANE_STATUS_IO_ERROR;
    if (dst_size)
      *dst_size -= us.us_resid;

    return SANE_STATUS_GOOD;
  }
#endif /* USE == SOLARIS_SG_INTERFACE */

#if USE == SOLARIS_INTERFACE

#ifndef SC_NOT_READ
# define SC_NOT_READY		0x02
#endif

#ifndef SC_BUSY
# define SC_BUSY		0x08
#endif
#define DEF_TIMEOUT sane_scsicmd_timeout;

/* Choosing one of the following DEF_SCG_FLG's SCG_DISRE_ENA allows
   the SCSI driver to disconnect/reconnect.  SCG_CMD_RETRY allows a
   retry if a retryable error occurs.

   Disallowing SCG_DISRE_ENA slows down the operation of the SCSI bus
   while the scanner is working. If you have severe problems try to
   set it to 0.

   SCG_CMD_RETRY allows the driver to retry some commands.  It should
   normally be set.  For some kinds of odd problems, it may cause the
   machine to hang for some time.  */

#define DEF_SCG_FLG	SCG_DISRE_ENA
/* #define DEF_SCG_FLG  0                               */
/* #define DEF_SCG_FLG  SCG_DISRE_ENA | SCG_CMD_RETRY   */
/* #define DEF_SCG_FLG  SCG_CMD_RETRY                   */

  static int d_errs = 100;

  static SANE_Status
    scsi_cmd (int fd,
	      const void *cmd, size_t cmd_size,
	      const void *src, size_t src_size,
	      void *dst, size_t * dst_size, int probing)
  {
    struct scg_cmd scmd;
    /* xxx obsolete size_t cdb_size;
     */
    SANEI_SCSI_Sense_Handler handler;

    /* xxx obsolete cdb_size = CDB_SIZE (*(u_char *) src);
     */

    memset (&scmd, 0, sizeof (scmd));
    scmd.flags = DEF_SCG_FLG | (probing ? SCG_SILENT : 0);
    if (dst && dst_size && *dst_size)
      {
	/* xxx obsolete assert (cdb_size == src_size);
	 */
	scmd.flags |= SCG_RECV_DATA;
	scmd.addr = dst;
	scmd.size = *dst_size;
      }
    else
      {
	/* xxx obsolete assert (cdb_size <= src_size);
	 */
	scmd.addr = (caddr_t) src;
	scmd.size = src_size;
      }
    scmd.cdb_len = cmd_size;
    scmd.sense_len = CCS_SENSE_LEN;
    scmd.target = fd_info[fd].target;
    /* use 2 second timeout when probing, 60 seconds otherwise: */
    scmd.timeout = probing ? 2 : DEF_TIMEOUT;
    memcpy (&scmd.cdb.g0_cdb.cmd, cmd, cmd_size);
    scmd.cdb.cmd_cdb[1] |= fd_info[fd].lun << 5;
    if (ioctl (fd, SCGIO_CMD, &scmd) < 0)
      return SANE_STATUS_IO_ERROR;
    if (dst_size)
      *dst_size = scmd.size - scmd.resid;
    if (scmd.error == 0 && scmd.errno == 0 && *(u_char *) & scmd.scb == 0)
      return SANE_STATUS_GOOD;

    if (scmd.error == SCG_TIMEOUT)
      DBG (0, "sanei_scsi_cmd %x: timeout\n", scmd.cdb.g0_cdb.cmd);
    else if (probing)
      {
	struct scsi_ext_sense *ext_sense =
	  (struct scsi_ext_sense *) &scmd.sense;

	if (scmd.error < SCG_FATAL
	    && ((scmd.sense.code < 0x70 && scmd.sense.code != 0x04)
		|| (scmd.sense.code >= 0x70
		    && ext_sense->key != SC_NOT_READY)))
	  return SANE_STATUS_GOOD;
      }
    else
      {
	char errbf[128];
	int i, rv, lifes;

	handler = fd_info[fd].sense_handler;
	DBG (3, "cmd=%x, error=%d:%s, bsiz=%d, stat=%x,%x,%x, slen=%d\n",
	     scmd.cdb.g0_cdb.cmd, scmd.error, strerror (scmd.errno),
	     ((dst_size != NULL) ? (*dst_size) : 0), scmd.u_scb.cmd_scb[0],
	     scmd.u_scb.cmd_scb[1], scmd.u_scb.cmd_scb[2], scmd.sense_count);
	*errbf = '\0';
	for (i = 0; i < scmd.sense_count; i++)
	  sprintf (errbf + strlen (errbf), "%x,", scmd.u_sense.cmd_sense[i]);
	DBG (3, "sense=%s\n", errbf);

	/* test_unit_ready on a busy unit returns error = 0 or 2 with
	   errno=EIO.  I've seen 0 on a CDrom without a CD, and 2 on a
	   scanner just busy.

	   If (SANE_DEBUG_SANEI_SCSI > 100) lifes =
	   SANE_DEBUG_SANEI_SCSI - 100 use up one life for every
	   scmd.error abort and dump core when no lifes left
	   test_unit_ready commands are not counted.  */
	if (scmd.error)
	  {
	    if (sanei_debug_sanei_scsi > 100 &&
		scmd.cdb.g0_cdb.cmd != SC_TEST_UNIT_READY)
	      {
		lifes = sanei_debug_sanei_scsi - ++d_errs;
		DBG (1, "sanei_scsi_cmd: %d lifes left\n", lifes);
		assert (lifes > 0);
	      }
	    return SANE_STATUS_IO_ERROR;
	  }
	if (scmd.u_scb.cmd_scb[0] == SC_BUSY)
	  return SANE_STATUS_DEVICE_BUSY;
	if (*(u_char *) & scmd.sense && handler)
	  {
	    rv = (*handler) (fd, scmd.u_sense.cmd_sense,
			     fd_info[fd].sense_handler_arg);
	    DBG (2, "sanei_scsi_cmd: sense-handler returns %d\n", rv);
	    return rv;
	  }
      }
    return SANE_STATUS_IO_ERROR;
  }

  SANE_Status
    sanei_scsi_cmd2 (int fd,
		     const void *cmd, size_t cmd_size,
		     const void *src, size_t src_size,
		     void *dst, size_t * dst_size)
  {
    return scsi_cmd (fd, cmd, cmd_size, src, src_size, dst, dst_size, 0);
  }

  static int unit_ready (int fd)
  {
    static const u_char test_unit_ready[] = { 0, 0, 0, 0, 0, 0 };
    int status;

    status = scsi_cmd (fd, test_unit_ready, sizeof (test_unit_ready),
		       0, 0, 0, 0, 1);
    return (status == SANE_STATUS_GOOD);
  }

#endif /* USE == SOLARIS_INTERFACE */


#if USE == SOLARIS_USCSI_INTERFACE

#define DEF_TIMEOUT sane_scsicmd_timeout;

  static int d_errs = 100;
  typedef struct scsi_extended_sense extended_sense_t;
  typedef struct scsi_inquiry scsi_inquiry_t;

  static SANE_Status
    scsi_cmd (int fd,
	      const void *cmd, size_t cmd_size,
	      const void *src, size_t src_size,
	      void *dst, size_t * dst_size, int probing)
  {
    struct uscsi_cmd us;
    scsi_inquiry_t inquiry, *iq = &inquiry;
    extended_sense_t sense, *sp = &sense;
    SANEI_SCSI_Sense_Handler handler;

    memset (&us, 0, sizeof (us));
    memset (sp, 0, sizeof (*sp));

    us.uscsi_flags = USCSI_SILENT | USCSI_RQENABLE | USCSI_DIAGNOSE;
    us.uscsi_timeout = probing ? 2 : DEF_TIMEOUT;
    us.uscsi_rqbuf = (caddr_t) sp;	/* sense data address */
    us.uscsi_rqlen = sizeof (extended_sense_t);	/* length of sense data */

    if (dst && dst_size && *dst_size)
      {
	us.uscsi_flags |= USCSI_READ;
	us.uscsi_bufaddr = (caddr_t) dst;
	us.uscsi_buflen = *dst_size;
      }
    else
      {
	us.uscsi_flags |= USCSI_WRITE;
	us.uscsi_bufaddr = (caddr_t) src;
	us.uscsi_buflen = src_size;
      }

    us.uscsi_cdblen = cmd_size;
    us.uscsi_cdb = (caddr_t) cmd;

    if (ioctl (fd, USCSICMD, &us) < 0)
      return SANE_STATUS_IO_ERROR;

    if (dst_size)
      *dst_size = us.uscsi_buflen - us.uscsi_resid;

    if ((us.uscsi_status & STATUS_MASK) == STATUS_GOOD)
      return SANE_STATUS_GOOD;

    if (sp->es_key == SUN_KEY_TIMEOUT)
      DBG (0, "sanei_scsi_cmd %x: timeout\n", *(char *) cmd);
    else
      {
	char errbf[128];
	int i, rv, lifes;

	handler = fd_info[fd].sense_handler;
	DBG (3, "cmd=%x, scsi_status=%x\n", *(char *) cmd, us.uscsi_status);
	*errbf = '\0';

	for (i = 0; i < us.uscsi_rqlen; i++)
	  sprintf (errbf + strlen (errbf), "%x,", *(sp + i));

	DBG (3, "sense=%s\n", errbf);

#if 0
	if (us.error)
	  {
	    if (sanei_debug_sanei_scsi > 100 &&
		scmd.cdb.g0_cdb.cmd != SC_TEST_UNIT_READY)
	      {
		lifes = sanei_debug_sanei_scsi - ++d_errs;
		DBG (1, "sanei_scsi_cmd: %d lifes left\n", lifes);
		assert (lifes > 0);
	      }
	    return SANE_STATUS_IO_ERROR;
	  }

	if (scmd.u_scb.cmd_scb[0] == SC_BUSY)
	  return SANE_STATUS_DEVICE_BUSY;
#endif

	if (handler)
	  {
	    rv = (*handler) (fd, (unsigned char *) sp,
			     fd_info[fd].sense_handler_arg);
	    DBG (2, "sanei_scsi_cmd: sense-handler returns %d\n", rv);
	    return rv;
	  }
      }

    return SANE_STATUS_IO_ERROR;
  }

  SANE_Status
    sanei_scsi_cmd2 (int fd,
		     const void *cmd, size_t cmd_size,
		     const void *src, size_t src_size,
		     void *dst, size_t * dst_size)
  {
    return scsi_cmd (fd, cmd, cmd_size, src, src_size, dst, dst_size, 0);
  }

  static int unit_ready (int fd)
  {
    static const u_char test_unit_ready[] = { 0, 0, 0, 0, 0, 0 };
    int status;

    status = scsi_cmd (fd, test_unit_ready, sizeof (test_unit_ready),
		       0, 0, 0, 0, 1);
    return (status == SANE_STATUS_GOOD);
  }
#endif /* USE == SOLARIS_USCSI_INTERFACE */

#if USE == WIN32_INTERFACE

SANE_Status
sanei_scsi_cmd2 (int fd, 
                const void *cmd, size_t cmd_size,
                const void *src, size_t src_size,
		void *dst, size_t * dst_size)
{
  struct pkt {
    SCSI_PASS_THROUGH_DIRECT sptd;
    unsigned char sense[255];
  } pkt;
  DWORD BytesReturned;
  BOOL ret;

  memset(&pkt, 0, sizeof( pkt ));
  pkt.sptd.Length = sizeof( SCSI_PASS_THROUGH_DIRECT );

  pkt.sptd.PathId = fd_info[fd].bus;
  pkt.sptd.TargetId = fd_info[fd].target;
  pkt.sptd.Lun = fd_info[fd].lun;

  assert(cmd_size == 6 || cmd_size == 10 || cmd_size == 12 || cmd_size == 16);
  memcpy(pkt.sptd.Cdb, cmd, cmd_size);
  pkt.sptd.CdbLength = cmd_size;

  if (dst_size && *dst_size)
    {
	pkt.sptd.DataIn = SCSI_IOCTL_DATA_IN;
	pkt.sptd.DataTransferLength = *dst_size;
        pkt.sptd.DataBuffer         = dst;
    }
  else if (src_size)
    {
	pkt.sptd.DataIn = SCSI_IOCTL_DATA_OUT;
	pkt.sptd.DataTransferLength = src_size;
        pkt.sptd.DataBuffer         = src;
    }
  else {
       pkt.sptd.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED; 
  }	

  pkt.sptd.TimeOutValue       = sane_scsicmd_timeout;

  pkt.sptd.SenseInfoOffset = offsetof(struct pkt, sense);
  pkt.sptd.SenseInfoLength = sizeof(pkt.sense);

  ret = DeviceIoControl(fd,
                        IOCTL_SCSI_PASS_THROUGH_DIRECT,
                        &pkt.sptd, sizeof( pkt ),
                        &pkt.sptd, sizeof( pkt ),
                        &BytesReturned, NULL );

  if (ret == 0)
    {
      DBG (1, "sanei_scsi_cmd2: DeviceIoControl() failed: %ld\n",
	   GetLastError());
      return SANE_STATUS_IO_ERROR;
    }

   if (pkt.sptd.ScsiStatus == 2){
	/* Check condition. */
      SANEI_SCSI_Sense_Handler handler;

      handler = fd_info[fd].sense_handler;
      if (handler) {
	 return handler(fd, pkt.sense, fd_info[fd].sense_handler_arg);
      }
      else {
	 return SANE_STATUS_IO_ERROR;
      }
   } 
   else if (pkt.sptd.ScsiStatus != 0) {
      DBG (1, "sanei_scsi_cmd2: ScsiStatus is %d\n",
	    pkt.sptd.ScsiStatus);
      return SANE_STATUS_IO_ERROR;
   }

  if (dst_size) {
    *dst_size = pkt.sptd.DataTransferLength;
  }

  return SANE_STATUS_GOOD;
}

#define WE_HAVE_FIND_DEVICES

/* This is almost the same algorithm used in sane-find-scanner. */
void
sanei_scsi_find_devices (const char *findvendor, const char *findmodel,
			 const char *findtype,
			 int findbus, int findchannel, int findid, int findlun,
			 SANE_Status (*attach) (const char *dev))
{
	int hca;
	HANDLE fd;
	char scsi_hca_name[20];
	char buffer[4096];
	DWORD BytesReturned;
	BOOL ret;
	PSCSI_ADAPTER_BUS_INFO adapter;
	PSCSI_INQUIRY_DATA inquiry;
	int i;

	DBG_INIT();

	hca = 0;

	for(hca = 0; ; hca++) {

	/* Open the adapter */
	snprintf(scsi_hca_name, 20, "\\\\.\\Scsi%d:", hca);
	fd = CreateFile(scsi_hca_name, GENERIC_READ | GENERIC_WRITE,
                               FILE_SHARE_READ | FILE_SHARE_WRITE,
                               NULL, OPEN_EXISTING,
                               FILE_FLAG_RANDOM_ACCESS, NULL );

	if (fd == INVALID_HANDLE_VALUE) {
	   /* Assume there is no more adapter. This is wrong in the case
	   * of hot-plug stuff, but I have yet to see it on a user
	   * machine. */
	   break;
	}

	/* Get the inquiry info for the devices on that hca. */
        ret = DeviceIoControl(fd,
                                 IOCTL_SCSI_GET_INQUIRY_DATA,
                                 NULL,
                                 0,
                                 buffer,
                                 sizeof(buffer),
                                 &BytesReturned,
                                 FALSE);

        if(ret == 0)
        {
	CloseHandle(fd);
            continue;
        }

	adapter = (PSCSI_ADAPTER_BUS_INFO)buffer;

	for(i = 0; i < adapter->NumberOfBuses; i++) {	

		if (adapter->BusData[i].InquiryDataOffset == 0) {
			/* No device here */
			continue;
		}

		inquiry = (PSCSI_INQUIRY_DATA) (buffer + 
                                   adapter->BusData[i].InquiryDataOffset);

		while(1) {

			if ((findvendor == NULL || strncmp(findvendor, (char *)&inquiry->InquiryData[8], 8) == 0)) {
				DBG(1, "OK1\n");
			} else {
				DBG(1, "failed for [%s] and [%s]\n",findvendor, (char *)&inquiry->InquiryData[8] );
			}


			/* Check if this device fits the criteria. */
			if ((findvendor == NULL || strncmp(findvendor, (char *)&inquiry->InquiryData[8], strlen(findvendor)) == 0) &&
			    (findmodel == NULL || strncmp(findmodel, (char *)&inquiry->InquiryData[16], strlen(findmodel)) == 0) &&
			    (findbus == -1 || findbus == hca) &&
			    (findchannel == -1 || findchannel == inquiry->PathId) &&
			    (findid == -1 || findid == inquiry->TargetId) &&
			    (findlun == -1 || findlun == inquiry->Lun)) {

				char device_name[20];
				sprintf(device_name, "h%db%dt%dl%d", hca, inquiry->PathId, inquiry->TargetId, inquiry->Lun);
				attach(device_name);
			}
			if (inquiry->NextInquiryDataOffset == 0) {
			   /* No device here */
			   break;
			} else {
			   inquiry =  (PSCSI_INQUIRY_DATA) (buffer +
                          inquiry->NextInquiryDataOffset);
			}
		}
	    }
	CloseHandle(fd);

        }
}
#endif /* USE == WIN32_INTERFACE */

#if USE == MACOSX_INTERFACE

# ifdef HAVE_IOKIT_CDB_IOSCSILIB_H

  static SANE_Status
    sanei_scsi_cmd2_old_api (int fd,
			     const void *cmd, size_t cmd_size,
			     const void *src, size_t src_size,
			     void *dst, size_t * dst_size)
  {
    mach_port_t masterPort;
    IOReturn ioReturnValue;
    io_object_t scsiDevice;
    int i;
    CFMutableDictionaryRef scsiMatchDictionary;
    int deviceTypeNumber;
    CFNumberRef deviceTypeRef;
    io_iterator_t scsiObjectIterator;
    io_object_t device;
    CFNumberRef IOUnitRef;
    int iounit;
    CFNumberRef scsiTargetRef;
    int scsitarget;
    CFNumberRef scsiLunRef;
    int scsilun;
    IOCFPlugInInterface **plugInInterface;
    SInt32 score;
    HRESULT plugInResult;
    IOSCSIDeviceInterface **scsiDeviceInterface;
    IOCDBCommandInterface **cdbCommandInterface;
    CDBInfo cdb;
    IOVirtualRange range;
    UInt32 transferCount;
    Boolean isWrite;
    SCSIResults results;
    UInt32 seqNumber;

    masterPort = 0;
    ioReturnValue = IOMasterPort (MACH_PORT_NULL, &masterPort);
    if (ioReturnValue != kIOReturnSuccess || masterPort == 0)
      {
	DBG (5, "Could not get I/O master port (0x%08x)\n", ioReturnValue);
	return SANE_STATUS_IO_ERROR;
      }

    scsiDevice = 0;
    for (i = 0; !scsiDevice && i < 2; i++)
      {
	scsiMatchDictionary = IOServiceMatching (kIOSCSIDeviceClassName);
	if (scsiMatchDictionary == NULL)
	  {
	    DBG (5, "Could not create SCSI matching dictionary\n");
	    return SANE_STATUS_NO_MEM;
	  }

	deviceTypeNumber =
	  (i == 0 ? kSCSIDevTypeScanner : kSCSIDevTypeProcessor);
	deviceTypeRef = CFNumberCreate (NULL, kCFNumberIntType,
					&deviceTypeNumber);
	CFDictionarySetValue (scsiMatchDictionary,
			      CFSTR (kSCSIPropertyDeviceTypeID),
			      deviceTypeRef);
	CFRelease (deviceTypeRef);

	scsiObjectIterator = 0;
	ioReturnValue = IOServiceGetMatchingServices (masterPort,
						      scsiMatchDictionary,
						      &scsiObjectIterator);
	if (ioReturnValue != kIOReturnSuccess)
	  {
	    DBG (5, "Could not match services (0x%08x)\n", ioReturnValue);
	    return SANE_STATUS_NO_MEM;
	  }

	while ((device = IOIteratorNext (scsiObjectIterator)))
	  {
	    IOUnitRef =
	      IORegistryEntryCreateCFProperty (device,
					       CFSTR (kSCSIPropertyIOUnit),
					       NULL, 0);
	    CFNumberGetValue (IOUnitRef, kCFNumberIntType, &iounit);
	    CFRelease (IOUnitRef);
	    scsiTargetRef =
	      IORegistryEntryCreateCFProperty (device,
					       CFSTR (kSCSIPropertyTarget),
					       NULL, 0);
	    CFNumberGetValue (scsiTargetRef, kCFNumberIntType, &scsitarget);
	    CFRelease (scsiTargetRef);
	    scsiLunRef =
	      IORegistryEntryCreateCFProperty (device,
					       CFSTR (kSCSIPropertyLun),
					       NULL, 0);
	    CFNumberGetValue (scsiLunRef, kCFNumberIntType, &scsilun);
	    CFRelease (scsiLunRef);

	    if (fd_info[fd].bus == iounit &&
		fd_info[fd].target == scsitarget &&
		fd_info[fd].lun == scsilun)
	      scsiDevice = device;
	    else
	      IOObjectRelease (device);
	  }
	IOObjectRelease (scsiObjectIterator);
      }
    if (!scsiDevice)
      {
	DBG (5, "Device not found (unit %i, target %i, lun %i)\n",
	     fd_info[fd].bus, fd_info[fd].target, fd_info[fd].lun);
	return SANE_STATUS_INVAL;
      }

    plugInInterface = NULL;
    score = 0;
    ioReturnValue = IOCreatePlugInInterfaceForService (scsiDevice,
						       kIOSCSIUserClientTypeID,
						       kIOCFPlugInInterfaceID,
						       &plugInInterface,
						       &score);
    if (ioReturnValue != kIOReturnSuccess || plugInInterface == NULL)
      {
	DBG (5, "Error creating plugin interface (0x%08x)\n", ioReturnValue);
	return SANE_STATUS_NO_MEM;
      }

    scsiDeviceInterface = NULL;
    plugInResult = (*plugInInterface)->
      QueryInterface (plugInInterface,
		      CFUUIDGetUUIDBytes (kIOSCSIDeviceInterfaceID),
		      (LPVOID) & scsiDeviceInterface);
    if (plugInResult != S_OK || scsiDeviceInterface == NULL)
      {
	DBG (5, "Couldn't create SCSI device interface (%ld)\n", plugInResult);
	return SANE_STATUS_NO_MEM;
      }

    (*plugInInterface)->Release (plugInInterface);
    IOObjectRelease (scsiDevice);

    ioReturnValue = (*scsiDeviceInterface)->open (scsiDeviceInterface);
    if (ioReturnValue != kIOReturnSuccess)
      {
	DBG (5, "Error opening SCSI interface (0x%08x)\n", ioReturnValue);
	return SANE_STATUS_IO_ERROR;
      }

    cdbCommandInterface = NULL;
    plugInResult = (*scsiDeviceInterface)->
      QueryInterface (scsiDeviceInterface,
		      CFUUIDGetUUIDBytes (kIOCDBCommandInterfaceID),
		      (LPVOID) & cdbCommandInterface);
    if (plugInResult != S_OK || cdbCommandInterface == NULL)
      {
	DBG (5, "Error creating CDB interface (%ld)\n", plugInResult);
	return SANE_STATUS_NO_MEM;
      }

    cdb.cdbLength = cmd_size;
    memcpy (&cdb.cdb, cmd, cmd_size);
    if (dst && dst_size)
      {
	bzero (dst, *dst_size);
	range.address = (IOVirtualAddress) dst;
	range.length = *dst_size;
	transferCount = *dst_size;
	isWrite = false;
      }
    else
      {
	range.address = (IOVirtualAddress) src;
	range.length = src_size;
	transferCount = src_size;
	isWrite = true;
      }

    seqNumber = 0;
    ioReturnValue = (*cdbCommandInterface)->
      setAndExecuteCommand (cdbCommandInterface, &cdb, transferCount,
			    &range, 1, isWrite, sane_scsicmd_timeout * 1000,
			    0, 0, 0, &seqNumber);
    if (ioReturnValue != kIOReturnSuccess &&
	ioReturnValue != kIOReturnUnderrun)
      {
	DBG (5, "Error executing CDB command (0x%08x)\n", ioReturnValue);
	return SANE_STATUS_IO_ERROR;
      }

    ioReturnValue = (*cdbCommandInterface)->getResults (cdbCommandInterface,
							&results);
    if (ioReturnValue != kIOReturnSuccess &&
	ioReturnValue != kIOReturnUnderrun)
      {
	DBG (5, "Error getting results from CDB Interface (0x%08x)\n",
	     ioReturnValue);
	return SANE_STATUS_IO_ERROR;
      }

    if (dst && dst_size)
      *dst_size = results.bytesTransferred;

    (*cdbCommandInterface)->Release (cdbCommandInterface);
    (*scsiDeviceInterface)->close (scsiDeviceInterface);
    (*scsiDeviceInterface)->Release (scsiDeviceInterface);

    return SANE_STATUS_GOOD;
  }


  static void
    sanei_scsi_find_devices_old_api (const char *findvendor,
				     const char *findmodel,
				     const char *findtype, int findbus,
				     int findchannel, int findid, int findlun,
				     SANE_Status (*attach) (const char *dev))
  {
    mach_port_t masterPort;
    IOReturn ioReturnValue;
    int i;
    CFMutableDictionaryRef scsiMatchDictionary;
    int deviceTypeNumber;
    CFNumberRef deviceTypeRef;
    io_iterator_t scsiObjectIterator;
    io_object_t scsiDevice;
    CFNumberRef IOUnitRef;
    int iounit;
    CFNumberRef scsiTargetRef;
    int scsitarget;
    CFNumberRef scsiLunRef;
    int scsilun;
    IOCFPlugInInterface **plugInInterface;
    SInt32 score;
    HRESULT plugInResult;
    IOSCSIDeviceInterface **scsiDeviceInterface;
    SCSIInquiry inquiry;
    UInt32 inquirySize;
    char devname[16];

    masterPort = 0;
    ioReturnValue = IOMasterPort (MACH_PORT_NULL, &masterPort);
    if (ioReturnValue != kIOReturnSuccess || masterPort == 0)
      {
	DBG (5, "Could not get I/O master port (0x%08x)\n", ioReturnValue);
	return;
      }

    for (i = 0; i < 2; i++)
      {
	scsiMatchDictionary = IOServiceMatching (kIOSCSIDeviceClassName);
	if (scsiMatchDictionary == NULL)
	  {
	    DBG (5, "Could not create SCSI matching dictionary\n");
	    return;
	  }
	deviceTypeNumber =
	  (i == 0 ? kSCSIDevTypeScanner : kSCSIDevTypeProcessor);
	deviceTypeRef = CFNumberCreate (NULL, kCFNumberIntType,
					&deviceTypeNumber);
	CFDictionarySetValue (scsiMatchDictionary,
			      CFSTR (kSCSIPropertyDeviceTypeID),
			      deviceTypeRef);
	CFRelease (deviceTypeRef);

	scsiObjectIterator = 0;
	ioReturnValue = IOServiceGetMatchingServices (masterPort,
						      scsiMatchDictionary,
						      &scsiObjectIterator);
	if (ioReturnValue != kIOReturnSuccess)
	  {
	    DBG (5, "Could not match services (0x%08x)\n", ioReturnValue);
	    return;
	  }

	while ((scsiDevice = IOIteratorNext (scsiObjectIterator)))
	  {
	    IOUnitRef =
	      IORegistryEntryCreateCFProperty (scsiDevice,
					       CFSTR (kSCSIPropertyIOUnit),
					       NULL, 0);
	    CFNumberGetValue (IOUnitRef, kCFNumberIntType, &iounit);
	    CFRelease (IOUnitRef);
	    scsiTargetRef =
	      IORegistryEntryCreateCFProperty (scsiDevice,
					       CFSTR (kSCSIPropertyTarget),
					       NULL, 0);
	    CFNumberGetValue (scsiTargetRef, kCFNumberIntType, &scsitarget);
	    CFRelease (scsiTargetRef);
	    scsiLunRef =
	      IORegistryEntryCreateCFProperty (scsiDevice,
					       CFSTR (kSCSIPropertyLun),
					       NULL, 0);
	    CFNumberGetValue (scsiLunRef, kCFNumberIntType, &scsilun);
	    CFRelease (scsiLunRef);

	    plugInInterface = NULL;
	    score = 0;
	    ioReturnValue =
	      IOCreatePlugInInterfaceForService (scsiDevice,
						 kIOSCSIUserClientTypeID,
						 kIOCFPlugInInterfaceID,
						 &plugInInterface, &score);
	    if (ioReturnValue != kIOReturnSuccess || plugInInterface == NULL)
	      {
		DBG (5, "Error creating plugin interface (0x%08x)\n",
		     ioReturnValue);
		return;
	      }

	    scsiDeviceInterface = NULL;
	    plugInResult = (*plugInInterface)->
	      QueryInterface (plugInInterface,
			      CFUUIDGetUUIDBytes (kIOSCSIDeviceInterfaceID),
			      (LPVOID) & scsiDeviceInterface);
	    if (plugInResult != S_OK || scsiDeviceInterface == NULL)
	      {
		DBG (5, "Couldn't create SCSI device interface (%ld)\n",
		     plugInResult);
		return;
	      }

	    (*plugInInterface)->Release (plugInInterface);
	    IOObjectRelease (scsiDevice);

	    ioReturnValue = (*scsiDeviceInterface)->
	      getInquiryData (scsiDeviceInterface, &inquiry,
			      sizeof (SCSIInquiry), &inquirySize);

	    (*scsiDeviceInterface)->Release (scsiDeviceInterface);

	    if ((findlun < 0 || findlun == scsilun) &&
		(findvendor == NULL || strncmp (findvendor,
						inquiry.vendorName,
						strlen (findvendor)) == 0) &&
		(findmodel == NULL || strncmp (findmodel,
					       inquiry.productName,
					       strlen (findmodel)) == 0))
	      {
		sprintf (devname, "u%dt%dl%d", iounit, scsitarget, scsilun);
		(*attach) (devname);
	      }
	  }
	IOObjectRelease (scsiObjectIterator);
      }
  }

# endif	/* ifdef HAVE_IOKIT_CDB_IOSCSILIB_H */

# if defined (HAVE_IOKIT_SCSI_SCSICOMMANDOPERATIONCODES_H) || \
     defined (HAVE_IOKIT_SCSI_COMMANDS_SCSICOMMANDOPERATIONCODES_H)

  static
  void CreateMatchingDictionaryForSTUC (SInt32 peripheralDeviceType,
					const char *findvendor,
					const char *findmodel,
					const CFDataRef scsiguid,
					CFMutableDictionaryRef * matchingDict)
  {
    CFMutableDictionaryRef subDict;
    CFNumberRef deviceTypeRef;
    CFStringRef str;

    /* Create the dictionaries */
    *matchingDict =
      CFDictionaryCreateMutable (kCFAllocatorDefault, 0,
				 &kCFTypeDictionaryKeyCallBacks,
				 &kCFTypeDictionaryValueCallBacks);
    if (*matchingDict == NULL)
      {
	return;
      }

    subDict =
      CFDictionaryCreateMutable (kCFAllocatorDefault, 0,
				 &kCFTypeDictionaryKeyCallBacks,
				 &kCFTypeDictionaryValueCallBacks);
    if (subDict == NULL)
      {
	CFRelease (*matchingDict);
	*matchingDict = NULL;
	return;
      }

    /* Create a dictionary with the "SCSITaskDeviceCategory" key with the
       appropriate value for the device type we're interested in.*/

    CFDictionarySetValue (subDict,
			  CFSTR (kIOPropertySCSITaskDeviceCategory),
			  CFSTR (kIOPropertySCSITaskUserClientDevice));

    deviceTypeRef = CFNumberCreate (kCFAllocatorDefault, kCFNumberIntType,
				    &peripheralDeviceType);
    CFDictionarySetValue (subDict,
			  CFSTR (kIOPropertySCSIPeripheralDeviceType),
			  deviceTypeRef);
    CFRelease (deviceTypeRef);

    /* Add search for a vendor or model */

    if (findvendor)
      {
	str = CFStringCreateWithCString (kCFAllocatorDefault, findvendor,
					 kCFStringEncodingUTF8);
	CFDictionarySetValue (subDict,
			      CFSTR (kIOPropertySCSIVendorIdentification),
			      str);
	CFRelease (str);
      }
    if (findmodel)
      {
	str = CFStringCreateWithCString (kCFAllocatorDefault, findmodel,
					 kCFStringEncodingUTF8);
	CFDictionarySetValue (subDict,
			      CFSTR (kIOPropertySCSIProductIdentification),
			      str);
	CFRelease (str);
      }
    if (scsiguid)
      {
	CFDictionarySetValue (subDict,
			      CFSTR
			      (kIOPropertySCSITaskUserClientInstanceGUID),
			      scsiguid);
      }

    /* Add the dictionary to the main dictionary with the key "IOPropertyMatch"
       to narrow the search to the above dictionary. */

    CFDictionarySetValue (*matchingDict, CFSTR (kIOPropertyMatchKey), subDict);
    CFRelease (subDict);
  }

  static
  void CreateDeviceInterfaceUsingSTUC (io_object_t scsiDevice,
				       IOCFPlugInInterface ***
				       thePlugInInterface,
				       SCSITaskDeviceInterface ***
				       theInterface)
  {
    IOReturn ioReturnValue;
    IOCFPlugInInterface **plugInInterface = NULL;
    SInt32 score = 0;
    HRESULT plugInResult;
    SCSITaskDeviceInterface **interface = NULL;

    /* Create the base interface of type IOCFPlugInInterface.
       This object will be used to create the SCSI device interface object. */

    ioReturnValue =
      IOCreatePlugInInterfaceForService (scsiDevice,
					 kIOSCSITaskDeviceUserClientTypeID,
					 kIOCFPlugInInterfaceID,
					 &plugInInterface, &score);
    if (ioReturnValue != kIOReturnSuccess)
      {
	DBG (5, "Error creating plugin interface (0x%08x)\n", ioReturnValue);
	return;
      }

    /* Query the base plugin interface for an instance of the specific
       SCSI device interface object. */

    plugInResult =
      (*plugInInterface)->QueryInterface (plugInInterface,
					  CFUUIDGetUUIDBytes
					  (kIOSCSITaskDeviceInterfaceID),
					  (LPVOID) & interface);
    if (plugInResult != S_OK)
      {
	DBG (5, "Couldn't create SCSI device interface (%ld)\n",
	     (long) plugInResult);
	return;
      }

    /* Set the return values. */

    *thePlugInInterface = plugInInterface;
    *theInterface = interface;
  }

  static SANE_Status
    ExecuteSCSITask (SCSITaskInterface ** task,
		     const void *cmd, size_t cmd_size,
		     const void *src, size_t src_size,
		     void *dst, size_t * dst_size)
  {
    SCSITaskStatus taskStatus;
    SCSI_Sense_Data senseData;
    SCSICommandDescriptorBlock cdb;
    IOReturn ioReturnValue;
#ifdef HAVE_SCSITASKSGELEMENT
    SCSITaskSGElement range;
#else
    IOVirtualRange range;
#endif
    UInt64 transferCount = 0;
    UInt64 data_length = 0;
    UInt8 transferType = 0;

    if (dst && dst_size)	/* isRead */
      {
	DBG (6, "isRead dst_size:%ld\n", *dst_size);

	/* Zero the buffer. */
	bzero (dst, *dst_size);

	/* Configure the virtual range for the buffer. */
	range.address = (long) dst;
	range.length = *dst_size;

	data_length = *dst_size;
	transferType = kSCSIDataTransfer_FromTargetToInitiator;
      }
    else
      {
	DBG (6, "isWrite src_size:%ld\n", src_size);

	/* Configure the virtual range for the buffer. */
	range.address = (long) src;
	range.length = src_size;

	data_length = src_size;
	transferType = kSCSIDataTransfer_FromInitiatorToTarget;
      }


    /* zero the senseData and CDB */
    bzero (&senseData, sizeof (senseData));
    bzero (cdb, sizeof (cdb));

    /* copy the command data */
    memcpy (cdb, cmd, cmd_size);

    /* Set the actual cdb in the task */
    ioReturnValue = (*task)->SetCommandDescriptorBlock (task, cdb, cmd_size);
    if (ioReturnValue != kIOReturnSuccess)
      {
	DBG (5, "Error setting CDB (0x%08x)\n", ioReturnValue);
	return SANE_STATUS_IO_ERROR;
      }

    /* Set the scatter-gather entry in the task */
    ioReturnValue = (*task)->SetScatterGatherEntries (task, &range, 1,
						      data_length,
						      transferType);
    if (ioReturnValue != kIOReturnSuccess)
      {
	DBG (5, "Error setting scatter-gather entries (0x%08x)\n",
	     ioReturnValue);
	return SANE_STATUS_IO_ERROR;
      }

    /* Set the timeout in the task */
    ioReturnValue = (*task)->SetTimeoutDuration (task,
						 sane_scsicmd_timeout * 1000);
    if (ioReturnValue != kIOReturnSuccess)
      {
	DBG (5, "Error setting timeout (0x%08x)\n", ioReturnValue);
	return SANE_STATUS_IO_ERROR;
      }

    DBG (5, "Executing command\n");

    /* Send it! */
    ioReturnValue = (*task)->ExecuteTaskSync (task, &senseData, &taskStatus,
					      &transferCount);
    if (ioReturnValue != kIOReturnSuccess)
      {
	DBG (5, "Error executing task (0x%08x)\n", ioReturnValue);
	return SANE_STATUS_IO_ERROR;
      }

    DBG (5, "ExecuteTaskSync OK Transferred %lld bytes\n", transferCount);

    if (taskStatus != kSCSITaskStatus_GOOD)
      {
	DBG (5, "taskStatus = 0x%08x\n", taskStatus);
	return SANE_STATUS_IO_ERROR;
      }

    /* Task worked correctly */
    if (dst && dst_size)
      *dst_size = transferCount;

    return SANE_STATUS_GOOD;
  }

  static SANE_Status
    ExecuteCommandUsingSTUC (SCSITaskDeviceInterface ** interface,
			     const void *cmd, size_t cmd_size,
			     const void *src, size_t src_size,
			     void *dst, size_t * dst_size)
  {
    SCSITaskInterface **task;
    IOReturn ioReturnValue;
    SANE_Status returnValue;

    /* Get exclusive access for the device if we can. This must be done
       before any SCSITasks can be created and sent to the device. */
    ioReturnValue = (*interface)->ObtainExclusiveAccess (interface);

    if (ioReturnValue != kIOReturnSuccess)
      {
	DBG (5, "ObtainExclusiveAccess failed (0x%08x)\n", ioReturnValue);
	return SANE_STATUS_NO_MEM;
      }

    /* Create a task now that we have exclusive access */
    task = (*interface)->CreateSCSITask (interface);

    if (task == NULL)
      {
	DBG (5, "CreateSCSITask returned NULL\n");
	(*interface)->ReleaseExclusiveAccess (interface);
	return SANE_STATUS_NO_MEM;
      }

    returnValue = ExecuteSCSITask (task, cmd, cmd_size,
				   src, src_size, dst, dst_size);

    /* Release the task interface */
    (*task)->Release (task);

    /* Release exclusive access */
    (*interface)->ReleaseExclusiveAccess (interface);

    return returnValue;
  }

  static SANE_Status
    sanei_scsi_cmd2_stuc_api (int fd,
			      const void *cmd, size_t cmd_size,
			      const void *src, size_t src_size,
			      void *dst, size_t * dst_size)
  {
    CFDataRef guid;
    mach_port_t masterPort;
    int i;
    io_object_t scsiDevice;
    SInt32 peripheralDeviceType;
    CFMutableDictionaryRef matchingDict;
    io_iterator_t iokIterator;
    IOReturn ioReturnValue;
    IOCFPlugInInterface **plugInInterface = NULL;
    SCSITaskDeviceInterface **interface = NULL;
    io_object_t nextDevice;
    SANE_Status returnValue;

    guid = fd_info[fd].pdata;
    if (!guid)
      {
	DBG (5, "No GUID\n");
	return SANE_STATUS_INVAL;
      }

    DBG (2, "cmd2: cmd_size:%ld src_size:%ld dst_size:%ld isWrite:%d\n",
	 cmd_size, src_size, (!dst_size) ? 0 : *dst_size, (!dst_size) ? 1 : 0);

    /* Use default master port */
    masterPort = 0;
    ioReturnValue = IOMasterPort (MACH_PORT_NULL, &masterPort);
    if (ioReturnValue != kIOReturnSuccess || masterPort == 0)
      {
	DBG (5, "Could not get I/O master port (0x%08x)\n", ioReturnValue);
	return SANE_STATUS_IO_ERROR;
      }

    /* Search for both Scanner type and Processor type devices */
    /* GB TDB This should only be needed for find */
    scsiDevice = 0;
    for (i = 0; !scsiDevice && i < 2; i++)
      {
	peripheralDeviceType =
	  (i == 0 ? kINQUIRY_PERIPHERAL_TYPE_ScannerSCSI2Device :
	            kINQUIRY_PERIPHERAL_TYPE_ProcessorSPCDevice);

	/* Set up a matching dictionary to search the I/O Registry for
	   the SCSI device */
	/* we are interested in, specifying the SCSITaskUserClient GUID. */
	matchingDict = NULL;
	CreateMatchingDictionaryForSTUC (peripheralDeviceType, NULL, NULL,
					 guid, &matchingDict);
	if (matchingDict == NULL)
	  {
	    DBG (5, "CreateMatchingDictionaryForSTUC Failed\n");
	    return SANE_STATUS_NO_MEM;
	  }

	/* Now search I/O Registry for the matching device */
	iokIterator = 0;
	ioReturnValue =
	  IOServiceGetMatchingServices (masterPort, matchingDict,
					&iokIterator);
	if (ioReturnValue != kIOReturnSuccess)
	  {
	    DBG (5, "IOServiceGetMatchingServices Failed\n");
	    return SANE_STATUS_NO_MEM;
	  }

	scsiDevice = IOIteratorNext (iokIterator);

	while ((nextDevice = IOIteratorNext (iokIterator)))
	  {
	    IOObjectRelease (nextDevice);
	  }

	IOObjectRelease (iokIterator);
      }

    if (!scsiDevice)
      {
	DBG (5, "Device not found\n");
	return SANE_STATUS_INVAL;
      }

    /* Found Device */
    /* Create interface */

    CreateDeviceInterfaceUsingSTUC (scsiDevice, &plugInInterface, &interface);

    /* Done with SCSI object from I/O Registry. */
    ioReturnValue = IOObjectRelease (scsiDevice);

    returnValue = SANE_STATUS_IO_ERROR;

    if (ioReturnValue != kIOReturnSuccess)
      {
	DBG (5, "Error releasing SCSI device. (0x%08x)\n", ioReturnValue);
      }
    else if (interface != NULL)
      {
	/* Execute the command */
	returnValue =
	  ExecuteCommandUsingSTUC (interface, cmd, cmd_size, src, src_size,
				   dst, dst_size);
      }

    if (interface != NULL)
      {
	(*interface)->Release (interface);
      }

    if (plugInInterface != NULL)
      {
	IODestroyPlugInInterface (plugInInterface);
      }

    return returnValue;
  }

  static void
    sanei_scsi_find_devices_stuc_api (const char *findvendor,
				      const char *findmodel,
				      const char *findtype, int findbus,
				      int findchannel, int findid, int findlun,
				      SANE_Status (*attach) (const char *dev))
  {
    mach_port_t masterPort;
    IOReturn ioReturnValue;
    int i;
    SInt32 peripheralDeviceType;
    CFMutableDictionaryRef matchingDict;
    io_iterator_t iokIterator;
    io_object_t scsiDevice;
    CFDataRef GUIDRef;
    char *devname;
    int len;
    const unsigned char *p;
    CFDictionaryRef protocolCharacteristics;
    CFNumberRef scsiLunRef;
    int scsilun;

    masterPort = 0;
    ioReturnValue = IOMasterPort (MACH_PORT_NULL, &masterPort);
    if (ioReturnValue != kIOReturnSuccess || masterPort == 0)
      return;

    DBG (5, "Search for Vendor: %s Model: %s\n",
	 (findvendor) ? findvendor : "(none)",
	 (findmodel) ? findmodel : "(none)");

    /* Search for both Scanner type and Processor type devices */

    for (i = 0; i < 2; i++)
      {
	peripheralDeviceType =
	  (i == 0 ? kINQUIRY_PERIPHERAL_TYPE_ScannerSCSI2Device :
	            kINQUIRY_PERIPHERAL_TYPE_ProcessorSPCDevice);

	/* Set up a matching dictionary to search the I/O Registry for SCSI
	   devices we are interested in. */

	matchingDict = NULL;
	CreateMatchingDictionaryForSTUC (peripheralDeviceType, findvendor,
					 findmodel, NULL, &matchingDict);
	if (matchingDict == NULL)
	  {
	    DBG (5, "CreateMatchingDictionaryForSTUC Failed\n");
	    return;
	  }

	/* Now search I/O Registry for matching devices. */

	iokIterator = 0;
	ioReturnValue =
	  IOServiceGetMatchingServices (masterPort, matchingDict,
					&iokIterator);
	if (ioReturnValue != kIOReturnSuccess)
	  {
	    DBG (5, "IOServiceGetMatchingServices Failed\n");
	    return;
	  }

	/* Check devices */

	while ((scsiDevice = IOIteratorNext (iokIterator)))
	  {
	    scsilun = 0;
            protocolCharacteristics = IORegistryEntryCreateCFProperty
	      (scsiDevice, CFSTR ("Protocol Characteristics"), NULL, 0);
	    if (protocolCharacteristics)
	      {
		scsiLunRef = CFDictionaryGetValue
		  (protocolCharacteristics,
		   CFSTR ("SCSI Logical Unit Number"));
		if (scsiLunRef)
		  CFNumberGetValue (scsiLunRef, kCFNumberIntType, &scsilun);
		CFRelease (protocolCharacteristics);
	      }

	    if (findlun < 0 || findlun == scsilun)
	      {
		/* Create device name from the SCSITaskUserClient GUID */

		GUIDRef = IORegistryEntryCreateCFProperty
		  (scsiDevice,
		   CFSTR (kIOPropertySCSITaskUserClientInstanceGUID),
		   NULL, 0);

		if (GUIDRef)
		  {
		    len = CFDataGetLength (GUIDRef);
		    p = CFDataGetBytePtr (GUIDRef);

		    devname = (char *) malloc (2 * len + 3);
		    devname [0] = '<';
		    for (i = 0; i < len; i++)
		      sprintf (&devname [2 * i + 1], "%02x", p [i]);
		    devname [2 * len + 1] = '>';
		    devname [2 * len + 2] = '\0';

		    CFRelease (GUIDRef);

		    DBG (1, "Found: %s\n", devname);

		    /* Attach to the device */
		    (*attach) (devname);
		    free (devname);
		  }
		else
		  DBG (1, "Can't find SCSITaskUserClient GUID\n");
	      }
	  }
	IOObjectRelease (iokIterator);
      }
  }

# endif	/* HAVE_IOKIT_SCSI_COMMANDS_SCSICOMMANDOPERATIONCODES_H */

  SANE_Status
    sanei_scsi_cmd2 (int fd,
		     const void *cmd, size_t cmd_size,
		     const void *src, size_t src_size,
		     void *dst, size_t * dst_size)
  {
    if (fd_info[fd].pdata)
# if defined (HAVE_IOKIT_SCSI_SCSICOMMANDOPERATIONCODES_H) || \
     defined (HAVE_IOKIT_SCSI_COMMANDS_SCSICOMMANDOPERATIONCODES_H)
      return sanei_scsi_cmd2_stuc_api (fd, cmd, cmd_size, src, src_size,
				       dst, dst_size);
# else
      return SANE_STATUS_INVAL;
# endif
    else
# ifdef HAVE_IOKIT_CDB_IOSCSILIB_H
      return sanei_scsi_cmd2_old_api (fd, cmd, cmd_size, src, src_size,
				      dst, dst_size);
# else
      return SANE_STATUS_INVAL;
# endif
  }

  void
    sanei_scsi_find_devices (const char *findvendor, const char *findmodel,
			     const char *findtype,
			     int findbus, int findchannel, int findid,
			     int findlun,
			     SANE_Status (*attach) (const char *dev))
  {
# if defined (HAVE_IOKIT_SCSI_SCSICOMMANDOPERATIONCODES_H) || \
     defined (HAVE_IOKIT_SCSI_COMMANDS_SCSICOMMANDOPERATIONCODES_H)
    sanei_scsi_find_devices_stuc_api (findvendor, findmodel, findtype,
				      findbus, findchannel, findid,
				      findlun, attach);
# endif
# ifdef HAVE_IOKIT_CDB_IOSCSILIB_H
    sanei_scsi_find_devices_old_api (findvendor, findmodel, findtype,
				     findbus, findchannel, findid,
				     findlun, attach);
# endif
  }

#define WE_HAVE_FIND_DEVICES

#endif /* USE == MACOSX_INTERFACE */


#ifndef WE_HAVE_ASYNC_SCSI

  SANE_Status
    sanei_scsi_req_enter2 (int fd, const void *cmd, size_t cmd_size,
			   const void *src, size_t src_size,
			   void *dst, size_t * dst_size, void **idp)
  {
    return sanei_scsi_cmd2 (fd, cmd, cmd_size, src, src_size, dst, dst_size);
  }

  SANE_Status sanei_scsi_req_wait (void *id)
  {
    return SANE_STATUS_GOOD;
  }

  void sanei_scsi_req_flush_all (void)
  {
  }

  void sanei_scsi_req_flush_all_extended (int fd)
  {
  }

#endif /* WE_HAVE_ASYNC_SCSI */

  SANE_Status sanei_scsi_req_enter (int fd,
				    const void *src, size_t src_size,
				    void *dst, size_t * dst_size, void **idp)
  {
    size_t cmd_size = CDB_SIZE (*(const char *) src);

    if (dst_size && *dst_size)
      assert (src_size == cmd_size);
    else
      assert (src_size >= cmd_size);

    return sanei_scsi_req_enter2 (fd, src, cmd_size,
				  (const char *) src + cmd_size,
				  src_size - cmd_size, dst, dst_size, idp);
  }

  SANE_Status
    sanei_scsi_cmd (int fd, const void *src, size_t src_size,
		    void *dst, size_t * dst_size)
  {
    size_t cmd_size = CDB_SIZE (*(const char *) src);

    if (dst_size && *dst_size)
      assert (src_size == cmd_size);
    else
      assert (src_size >= cmd_size);

    return sanei_scsi_cmd2 (fd, src, cmd_size,
			    (const char *) src + cmd_size,
			    src_size - cmd_size, dst, dst_size);
  }



#ifndef WE_HAVE_FIND_DEVICES

  void
    sanei_scsi_find_devices (const char *findvendor, const char *findmodel,
			     const char *findtype,
			     int findbus, int findchannel, int findid,
			     int findlun,
			     SANE_Status (*attach) (const char *dev))
  {
    DBG_INIT ();
    DBG (1, "sanei_scsi_find_devices: not implemented for this platform\n");
  }

#endif /* WE_HAVE_FIND_DEVICES */
