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

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston,
   MA 02111-1307, USA.

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

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

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

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

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

   This file provides a generic SCSI interface.  */

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

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

#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 BEOS_INTERFACE		16

#ifdef __BEOS__
# define USE BEOS_INTERFACE
#define WE_HAVE_FIND_DEVICES
#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_DOSSEMAPHORES
# 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_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"
#endif

#ifndef USE
# define USE STUBBED_INTERFACE
#endif

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

#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
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

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

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
*/
# ifdef SG_IO
#  include "linux_sg3_err.h"  /* xxx contains several definitions of error codes */
# endif
#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
#endif
#ifndef SG_GET_VERSION_NUM
#define SG_GET_VERSION_NUM 0x2282
#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
        struct
          {
            struct sg_io_hdr hdr;
            u_char sense_buffer[SENSE_MAX];
          } sg3;
#endif
      }
    sgdata;
  }
req;

typedef struct Fdparms
  {
    int sg_queue_used, sg_queue_max;
    int 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

#ifndef __BEOS__
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;
#endif

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

#ifndef __BEOS__ // nuke all code, replace by BeOS code at the end of file

#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 HEV postSema = 0; /* Event Semaphore for posting SRB completion */
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 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 openSemaReturn;
  USHORT lockSegmentReturn;
  unsigned long cbreturn;
  unsigned long cbParam;
  SRB SRBlock;			/* SCSI Request Block */

  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. */
    }
  rc = DosAllocMem (&aspi_buf, sanei_scsi_max_request_size,
		    OBJ_TILE | PAG_READ | PAG_WRITE | PAG_COMMIT);
  if (rc)
    {
      DBG (1, "open_aspi: can't allocate memory\n");
      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;
    }
  rc = DosCreateEventSem (NULL, &postSema,	/* create event semaphore */
			  DC_SEM_SHARED, 0);
  if (rc)
    {
      /* DosCreateEventSem failed */
      DBG (1, "open_aspi:  couldn't create semaphore.\n");
      return 0;
    }
  rc = DosDevIOCtl (driver_handle, 0x92, 0x03,	/* pass semaphore handle */
		    (void *) &postSema, sizeof (HEV),	/* to driver */
		    &cbParam, (void *) &openSemaReturn,
		    sizeof (USHORT), &cbreturn);
  if (rc || openSemaReturn)
    {
      DBG (1, "open_aspi:  couldn't set semaphore.\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, "open_aspi:  Can't lock buffer.\n");
      return 0;
    }

  /* query number of installed adapters */
  memset (&SRBlock, 0, sizeof (SRBlock));
  SRBlock.cmd = SRB_Inquiry;	/* host adapter inquiry */

  SRBlock.ha_num = 0;		/* host adapter number */

  SRBlock.flags = 0;		/* no flags set */

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

  DBG (1, "OS/2: installed adapters %d\n", num_adapters);
  DBG (1, "OS/2: ASPI manager is '%s'\n", SRBlock.u.inq.aspimgr_id);
  DBG (1, "OS/2: host adapter is '%s'\n", SRBlock.u.inq.host_id);
  DBG (1, "OS/2: unique id is    '%s'\n", SRBlock.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 (&SRBlock, 0, sizeof (SRBlock));
      SRBlock.cmd = SRB_Inquiry;	/* host adapter inquiry */

      SRBlock.ha_num = i;	/* host adapter number */

      SRBlock.flags = 0;	/* no flags set */

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

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

	  memset (&SRBlock, 0, sizeof (SRBlock));
	  SRBlock.cmd = SRB_Device;	/* get device type */

	  SRBlock.ha_num = i;	/* host adapter number */

	  SRBlock.flags = 0;	/* no flags set */

	  SRBlock.u.dev.target = id;	/* target id */

	  SRBlock.u.dev.lun = 0;	/* target LUN */

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

	  /* skip if device not connected */
	  if (SRBlock.status == SRB_BadDevice)
	    continue;

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

	  /* query adapter string id */
	  memset (&SRBlock, 0, sizeof (SRBlock));
	  SRBlock.cmd = SRB_Command;	/* execute SCSI command */

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

	  /* query id string */
	  memset (&SRBlock, 0, sizeof (SRBlock));
	  SRBlock.cmd = SRB_Command;	/* execute SCSI command */
	  SRBlock.ha_num = i;	/* host adapter number */
	  SRBlock.flags = SRB_Read | SRB_Post;	/* data transfer, posting */
	  SRBlock.u.cmd.target = id;	/* Target SCSI ID */
	  SRBlock.u.cmd.lun = 0;	/* Target SCSI LUN */
	  SRBlock.u.cmd.data_len = 5 + len;	/* # of bytes transferred */
	  SRBlock.u.cmd.sense_len = 16;		/* length of sense buffer */
	  SRBlock.u.cmd.data_ptr = NULL;	/* pointer to data buffer */
	  SRBlock.u.cmd.link_ptr = NULL;	/* pointer to next SRB */
	  SRBlock.u.cmd.cdb_len = 6;	/* SCSI command length */
	  SRBlock.u.cmd.cdb_st[0] = 0x12;	/* inquiry command */
	  SRBlock.u.cmd.cdb_st[1] = 0;	/* ?? length */
	  SRBlock.u.cmd.cdb_st[2] = 0;	/* transfer length MSB */
	  SRBlock.u.cmd.cdb_st[3] = 0;	/* transfer length */
	  SRBlock.u.cmd.cdb_st[4] = 5 + len;	/* transfer length LSB */
	  SRBlock.u.cmd.cdb_st[5] = 0;
	  rc = DosDevIOCtl (driver_handle, 0x92, 0x02,
			    (void *) &SRBlock, sizeof (SRB), &cbParam,
			    (void *) &SRBlock, sizeof (SRB), &cbreturn);
	  DosWaitEventSem (postSema, -1);
	  DosResetEventSem (postSema, &rc);
	  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 (*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 (postSema)
    DosCloseEventSem (postSema);	/* Close event semaphore. */
  postSema = 0;
  if (driver_handle)		/* Close driver. */
    DosClose (driver_handle);
  driver_handle = 0;
  if (aspi_buf)			/* Free buffer. */
    DosFreeMem (aspi_buf);
  aspi_buf = 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;
static int sg_version = 0;

#if USE == LINUX_INTERFACE

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;
  int fd;
#ifdef __linux__
  static int first_time = 1;
#endif

  DBG_INIT ();

#if USE == LINUX_INTERFACE
  if (first_time)
    {
      char buf[32];
      size_t len;
      int fd;

      first_time = 0;

      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);
	  DBG (1, "sanei_scsi_open: sanei_scsi_max_request_size=%d bytes\n",
	       sanei_scsi_max_request_size);
	  close(fd);
	}
    }
#endif

#if USE == OS2_INTERFACE
  if (sscanf (dev, "b%dt%dl%d", &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 a valid\n",
	     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: device %s not found\n",
	     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 a valid\n", 
	      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; 
   } 
#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 = open (dev, O_RDWR | O_EXCL);
  if (fd < 0)
    {
      SANE_Status status = SANE_STATUS_INVAL;

      if (errno == EACCES)
	status = SANE_STATUS_ACCESS_DENIED;

      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 = 10 * 60 * HZ;	/* how about 10 minutes? ;-) */
    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;

    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);

        /* 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.

           NOTE: sanei_scsi_max_request_size is a global variable
           used for all devices/file handles, while version 2.0 and
           above of the SG driver allocate buffer memory for each
           opened file separately. Therefore, we have a possible
           inconsistency, if more than one file is opened and
           if the SG_GET_RESERVED_SIZE return different buffer sizes
           for different file handles. (See Douglas Gilbert's
           description of the SG driver for details:
           http://www.torque.net/sg/p/scsi-generic_long.txt)

           For this reason, sanei_scsi_open does not allow to open
           two or more file handles simultaneously.
        */
        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)
               {
                 sanei_scsi_max_request_size = *buffersize;
               }
             else
               {
                 sanei_scsi_max_request_size = real_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 the old SG driver: */
        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 */

  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
  /* 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, fd, len;
  int wanted_buffersize = SCSIBUFFERSIZE, real_buffersize;
  SANE_Status res;
  char *cc, *cc1, buf[32];

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

  /* wanted_buffersize might be too big, if we have the old SG driver.
     Therefore, check the driver version, and reduce wante_buffersize,
     if necessary. Otherwise, sanei_scsi_open_extended will fail.
  */
  fd = open(dev, O_RDWR);
  if (fd < 0)
    {
      res = SANE_STATUS_INVAL;
      if (errno == EACCES)
        res = SANE_STATUS_ACCESS_DENIED;
      DBG(1, "sanei_scsi_open: open of `%s' failed: %s",
          dev, strerror(errno));
      return res;
    }
  
  i = ioctl(fd, SG_GET_VERSION_NUM, &i);
  close(fd);
  if (i < 0)
    {
      /* we have the old 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';
	  i = atoi (buf);
	  if (wanted_buffersize > i)
	    wanted_buffersize = i;
	  close(fd);
	}
      else
        if (wanted_buffersize > SG_BIG_BUFF)
          wanted_buffersize = SG_BIG_BUFF;
    }
  
  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 (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;
    }

  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;
  if (!fd_info[fd].fake_fd)
    close (fd);

#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 == 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_enter (int fd, 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_enter: (fd=%x, src=%p, src_size=%x, "
	 "dst=%p, dst_size=%x, *idp=%p)\n",
	 fd, src, src_size, dst, *dst_size, idp);
  else
    DBG (1, "sanei_scsi_req_enter: (fd=%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 = CDB_SIZE (*(u_char *) src);
  if (dst_size)
    com->dst_size = *dst_size;
  memcpy (&com->cdb, src, 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->cdb_size;
      com->buf_size = src_size - com->cdb_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_cmd (int fd, const void *src, size_t src_size,
		void *dst, size_t * dst_size)
{
  SANE_Status status;
  void *id;

  DBG (1, "sanei_scsi_cmd: (fd=%d)\n", fd);
  status = sanei_scsi_req_enter (fd, 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;

  if (!req)
    return;

  fdp = (fdparms*) fd_info[req->fd].pdata;
  DBG (4, "sanei_scsi.issue: %p\n", 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)
        {
#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);
                      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;
                      nwritten = write (rp->fd, &rp->sgdata.sg3.hdr, sizeof(Sg_io_hdr));
                      if (nwritten < 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;
                            }
                        }
                     );
            }
#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)
/* xxx doesn't work yet with kernel 2.3.18:
          || (sg_version >= 30000 && nwritten < sizeof(Sg_io_hdr)))
*/
          || (sg_version >= 30000 && nwritten < 0))
#endif
        {
          if (rp->running)
            {
              DBG (1, "sanei_scsi.issue: bad write (errno=%i) %s %i\n",
                      errno, strerror (errno), nwritten);
              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
	req->status = SANE_STATUS_IO_ERROR;
      fdp->sg_queue_used++;
      rp = rp->next;
     }
}

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

  fdp = (fdparms*) fd_info[fd].pdata;
  for (req = fdp->sane_qhead; req; req = next_req)
    {
      if (req->running && !req->done)
        {
#ifdef SG_IO
          if (sg_version < 30000)
#endif
            read (fd, &req->sgdata.cdb, req->sgdata.cdb.hdr.reply_len);
#ifdef SG_IO
          else
            read (fd, &req->sgdata.sg3.hdr, sizeof(Sg_io_hdr));
#endif
          ((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_enter (int fd, 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);
#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 = 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, src, src_size);
#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 = CDB_SIZE (*(u_char *) src);
      req->sgdata.sg3.hdr.iovec_count = 0;
      req->sgdata.sg3.hdr.mx_sb_len = SENSE_MAX;
      /* read or write? */
      if (dst_size && *dst_size)
        {
          assert (req->sgdata.sg3.hdr.cmd_len == src_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 (req->sgdata.sg3.hdr.cmd_len < src_size)
        {
          req->sgdata.sg3.hdr.dxfer_direction = SG_DXFER_TO_DEV;
          req->sgdata.sg3.hdr.dxfer_len = src_size - req->sgdata.sg3.hdr.cmd_len;
          req->sgdata.sg3.hdr.dxferp = ((char*) src) + req->sgdata.sg3.hdr.cmd_len;
        }
      else
        {
          assert (req->sgdata.sg3.hdr.cmd_len == src_size);
          req->sgdata.sg3.hdr.dxfer_direction = SG_DXFER_NONE;
        }
      (const void*) req->sgdata.sg3.hdr.cmdp = src;
      req->sgdata.sg3.hdr.sbp = &(req->sgdata.sg3.sense_buffer[0]);
      /* 10 seconds should be ok even for slow scanners */
      req->sgdata.sg3.hdr.timeout = 10000;
      req->sgdata.sg3.hdr.flags = SG_FLAG_DIRECT_IO;
      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", 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", req);

  issue (req);			/* ensure the command is running */
  if (req->done)
    {
      issue (req->next);	/* issue next command, if any */
      status = req->status;
    }
  else
    {
      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: */
#ifdef SG_IO
      if (sg_version < 30000)
        {
#endif
          ATOMIC (nread = read (req->fd, &req->sgdata.cdb,
                                req->sgdata.cdb.hdr.reply_len);
                 req->done = 1);
#ifdef SG_IO
       }
      else
        {
          ATOMIC (nread = read (req->fd, &req->sgdata.sg3.hdr, sizeof(Sg_io_hdr));
                 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))
                {
                  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));

                 if (req->sgdata.cdb.hdr.result == EBUSY)
                   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)))
               {
                 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));

                 /* 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)
                   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.sg3.sense_buffer, arg);
                 else
                   status = SANE_STATUS_IO_ERROR;
               }

             if (status == SANE_STATUS_GOOD)
               {
                 if (req->dst_len)
                   *req->dst_len -= req->sgdata.sg3.hdr.resid;
               }
           }
#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_cmd (int fd, const void *src, size_t src_size,
		void *dst, size_t * dst_size)
{
  SANE_Status status;
  void *id;

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

#define PROCFILE	"/proc/scsi/scsi"

#define CSTRLEN(string_const)	(sizeof (string_const) - 1)

static int
get_devicename (int devnum, char *name, size_t name_len)
{
  int dev_fd, i;
  static const struct
    {
      const char *prefix;
      char base;
    }
  device_name_list[] =
    {
      {"/dev/sg",  'a'},
      {"/dev/sg",  '0'},
      {"/dev/uk",  '0'},
      {"/dev/gsc", '0'},
    };

  for (i = 0; i < NELEMS (device_name_list); ++i)
    {
      snprintf (name, name_len, "%s%c", device_name_list[i].prefix,
		device_name_list[i].base + devnum);

      dev_fd = open (name, O_RDWR);
      if (dev_fd >= 0)
	{
	  close (dev_fd);
	  DBG (1, "searched device is %s\n", name);
	  return 0;
	}
    }
  return -1;
}

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;
  /* These are static to make sure the param[] struct is computable at
     load time. */
  static char vendor[32], model[32], type[32], revision[32];
  static int bus, channel, id, lun;

  int number, i;
  char line[256], dev_name[128];
  const char *string;
  FILE *proc_fp;
  char *end;
  const 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, { vendor }},
      {"Model:",   6, 0, { model }},
      {"Type:",    5, 0, { type }},
      {"Rev:",     4, 0, { revision }},
      {"scsi",     4, 1, { &bus }},
      {"Channel:", 8, 1, { &channel }},
      {"Id:",      3, 1, { &id }},
      {"Lun:",     4, 1, { &lun }}
    };

  DBG_INIT ();

  proc_fp = fopen (PROCFILE, "r");
  if (!proc_fp)
    {
      DBG (1, "could not open %s for reading\n", 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);

  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;
		  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 (*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 (number, 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;
    }
  fclose (proc_fp);
}

#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_cmd (int fd, const void *src, size_t src_size,
		void *dst, size_t * dst_size)
{
  size_t cdb_size;
  scsireq_t hdr;
  int result;

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

  memset (&hdr, 0, sizeof (hdr));
  memcpy (hdr.cmd, src, cdb_size);
  if (dst_size && *dst_size)
    {
      assert (cdb_size == src_size);
      hdr.flags = SCCMD_READ;
      hdr.databuf = dst;
      hdr.datalen = *dst_size;
    }
  else
    {
      assert (cdb_size <= src_size);
      hdr.flags = SCCMD_WRITE;
      hdr.databuf = (char *) src + cdb_size;
      hdr.datalen = src_size;
    }
  hdr.timeout = 60000;		/* 1 minute timeout */
  hdr.cmdlen = cdb_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_cmd(int fd, const void *src, size_t src_size,
			   void *dst, size_t * dst_size) {

   size_t 		cdb_size; 
   struct cam_device	*dev;
   union ccb		*ccb;
   int			rv;
   u_int32_t		ccb_flags;
   char*		data_buf;
   size_t		data_len;
   
   if (fd < 0 || fd > CAM_MAXDEVS || cam_devices[fd] == NULL) {
      fprintf(stderr, "attempt to reference invalid unit %d\n", fd);
      return SANE_STATUS_INVAL;
   }

   cdb_size = CDB_SIZE (*(u_char *) src);
   dev = cam_devices[fd];
   ccb = cam_getccb(dev);
    
   /* Build the CCB */
   bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio));
   bcopy(src, &ccb->csio.cdb_io.cdb_bytes, cdb_size);

   /*
    * Set the data direction flags.
    */
   if(dst_size && *dst_size) {
      assert (cdb_size == src_size);
      ccb_flags = CAM_DIR_IN;
      data_buf = ((char*)(dst));
      data_len = *dst_size;
   }
   else if(src_size > cdb_size) {
      ccb_flags = CAM_DIR_OUT;
      data_buf = ((char*)(src + cdb_size));
      data_len = src_size - cdb_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 */ cdb_size,
		 /* timeout */ 60 * 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));

      if((ccb->ccb_h.status & CAM_STATUS_MASK) 
	 == SANE_STATUS_DEVICE_BUSY)
	 return SANE_STATUS_DEVICE_BUSY;

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



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

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

  memset (&hdr, 0, sizeof (hdr));
  memcpy (hdr.cdb, src, src_size);
  if (dst_size && *dst_size)
    {
      assert (cdb_size == src_size);
      hdr.flags = SCTL_READ;
      hdr.data = dst;
      hdr.data_length = *dst_size;
    }
  else
    {
      assert (cdb_size <= src_size);
      hdr.data = (char *) src + cdb_size;
      hdr.data_length = src_size - cdb_size;
    }
  hdr.cdb_length = cdb_size;
  hdr.max_msecs = 60000;	/* 1 minute timeout */
  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_cmd (int fd, const void *src, size_t src_size,
		void *dst, size_t * dst_size)
{
  struct scsi_req hdr;
  size_t cdb_size;

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

  memset (&hdr, 0, sizeof (hdr));
  memcpy (&hdr.sr_cdb, src, cdb_size);
  if (dst_size && *dst_size)
    {
      assert (cdb_size == src_size);
      hdr.sr_dma_dir = SR_DMA_RD;
      hdr.sr_addr = dst;
      hdr.sr_dma_max = *dst_size;
    }
  else
    {
      assert (cdb_size <= src_size);
      hdr.sr_dma_dir = SR_DMA_WR;
      hdr.sr_addr = (char *) src + cdb_size;
      hdr.sr_dma_max = src_size - cdb_size;
    }
  hdr.sr_ioto = 60;		/* I/O timeout in seconds */

  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_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_cmd (int fd, const void *src, size_t src_size,
		void *dst, size_t * dst_size)
{
  u_char sense[64];
  UAGT_CAM_CCB hdr;
  CCB_SCSIIO ccb;
  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)
    {
      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
    {
      assert (cdb_size <= src_size);
      if (cdb_size == 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 + cdb_size;
      ccb.cam_dxfer_len = src_size - cdb_size;
    }
  ccb.cam_timeout = 60;		/* set timeout in seconds */
  ccb.cam_cdb_len = cdb_size;
  memcpy (&ccb.cam_cdb_io.cam_cdb_bytes[0], src, cdb_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_cmd (int fd, 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;
  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;

  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 - cdb_size;
      sc->data_ptr = (char *) src + cdb_size;
      sc->is_write = 1;
    }
  memcpy (sc->cdb, src, cdb_size);
  sc->cdb_len = cdb_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_cmd (int fd, 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;
  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 *) src;
  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", cdb_size,
      cp[0],cp[1],cp[2],cp[3],cp[4],cp[5],cp[6],cp[7],cp[8],cp[9]);
  switch (cdb_size)
    {
    default:
      return SANE_STATUS_IO_ERROR;
    case 6:
      scs = (struct scs *) cmd;
      memcpy(SCS_AD(scs),src,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),src,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),src,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 (cdb_size == src_size);
      sb_ptr->SCB.sc_mode = SCB_READ;
      sb_ptr->SCB.sc_datapt = dst;
      sb_ptr->SCB.sc_datasz = *dst_size;
    }
  else
    {
      assert (cdb_size <= src_size);
      sb_ptr->SCB.sc_mode = SCB_WRITE;
      sb_ptr->SCB.sc_datapt = (char *) src + cdb_size;
      if ( (sb_ptr->SCB.sc_datasz = src_size - cdb_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 = 60000; /* 1 min timeout */
  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 *) src;
    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_cmd (int fd, 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;
  int cdb_size, mode;
  int i;

  if (fd < 0)
    return SANE_STATUS_IO_ERROR;

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

  if (dst_size && *dst_size)
    {
      assert (cdb_size == src_size);
      cmdblk.sc_datapt = (caddr_t) dst;
      cmdblk.sc_datasz = *dst_size;
      mode = SCB_READ;
    }
  else
    {
      assert (cdb_size <= src_size);
      cmdblk.sc_datapt = (char *) src + cdb_size;
      cmdblk.sc_datasz = src_size - cdb_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;
  const 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, { vendor }},
      {"Model:",   6, 0, { model }},
      {"Type:",    5, 0, { type }},
      {"Rev:",     4, 0, { revision }},
      {"scsi",     4, 1, { &bus }},
      {"Channel:", 8, 1, { &channel }},
      {"Id:",      3, 1, { &id }},
      {"Lun:",     4, 1, { &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 (*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_cmd (int fd, const void *src, size_t src_size,
		void *dst, size_t * dst_size)
{
  ULONG rc;			/* Returns. */
  unsigned long cbreturn;
  unsigned long cbParam;
  SRB srb;			/* SCSI Request Block */
  ULONG count = 0;		/* For semaphore. */
  size_t cdb_size;

  memset ((char *) &srb, 0, sizeof (srb));	/* Okay, I'm paranoid. */
  cdb_size = CDB_SIZE (*(u_char *) src);	/* Size of command block. */
  srb.cmd = SRB_Command;	/* execute SCSI cmd */
  srb.ha_num = fd_info[fd].bus;	/* host adapter number */
  srb.u.cmd.target = fd_info[fd].target;	/* Target SCSI ID */
  srb.u.cmd.lun = fd_info[fd].lun;	/* Target SCSI LUN */
  srb.flags = SRB_Post;		/* posting enabled */
  if (dst_size && *dst_size)
    {
      /* Reading. */
      assert (*dst_size <= sanei_scsi_max_request_size);
      assert (cdb_size == src_size);
      srb.u.cmd.data_len = *dst_size;
      srb.flags |= SRB_Read;
    }
  else
    {
      /* Writing. */
      srb.u.cmd.data_len = src_size - cdb_size;
      assert (cdb_size <= src_size);
      assert (srb.u.cmd.data_len <= sanei_scsi_max_request_size);
      if (srb.u.cmd.data_len)
	srb.flags |= SRB_Write;
      else
	srb.flags |= SRB_NoTransfer;
      memcpy (aspi_buf, (char *) src + cdb_size, srb.u.cmd.data_len);
    }
  srb.u.cmd.sense_len = 16;	/* length of sense buffer */
  srb.u.cmd.data_ptr = NULL;	/* pointer to data buffer already registered */
  srb.u.cmd.link_ptr = NULL;	/* pointer to next SRB */
  srb.u.cmd.cdb_len = cdb_size;	/* SCSI command length */
  memcpy (&srb.u.cmd.cdb_st[0], (char *) src, cdb_size);

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

  if (rc)
    {
      DBG (1, "sanei_scsi_cmd: DosDevIOCtl failed.\n");
      return SANE_STATUS_IO_ERROR;
    }
  if (DosWaitEventSem (postSema, -1) ||		/* wait forever for sema. */
      DosResetEventSem (postSema, &count))	/* reset semaphore. */
    {
      DBG (1, "sanei_scsi_cmd:  semaphore failure.\n");
      return SANE_STATUS_IO_ERROR;
    }

  /* Get sense data if available. */
  if ((srb.status == SRB_Aborted || srb.status == SRB_Error) &&
      srb.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, &srb.u.cmd.cdb_st[cdb_size],
			   fd_info[fd].sense_handler_arg);
    }
  if (srb.status != SRB_Done ||
      srb.u.cmd.ha_status != SRB_NoError ||
      srb.u.cmd.target_status != SRB_NoStatus) {
    DBG (1, "sanei_scsi_cmd:  command 0x%02x failed.\n", srb.u.cmd.cdb_st[0]);
    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_cmd (int fd, 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_cmd (int fd, const void *src, size_t src_size,
		void *dst, size_t * dst_size)
{
  dsreq_t scsi_req;			/* SCSI request */
  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);
      databuf  = malloc(MAX_DATA);

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

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

  DBG(1, "sanei_scsi_cmd: cdb_size = %d\n", cdb_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   = cdb_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 = sizeof (sensebuf);

      /*
       * Copy command to cmdbuf to assure 32-bit alignment.
       */
      memcpy(cmdbuf, src, cdb_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   = cdb_size;
      scsi_req.ds_databuf  = (caddr_t) databuf;
      scsi_req.ds_datalen  = src_size - cdb_size;
      scsi_req.ds_sensebuf = (caddr_t) sensebuf;
      scsi_req.ds_senselen = sizeof (sensebuf);

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

  /*
   * 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 ((sensebuf[0] & 0x80) && 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_cmd (int fd, const void *src, size_t src_size,
		void *dst, size_t * dst_size)
{
  scmd_t scmd;
  size_t cdb_size;
  char sense_buf[32];
  char status;

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

  memset (&scmd, 0, sizeof (scmd));
  if (dst_size && *dst_size)
    {
      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 + cdb_size;
      scmd.datalen = src_size - cdb_size;
    }
  scmd.cdb = (char *) src;
  scmd.cdblen = cdb_size;
  scmd.timeval = 60;		/* 1 minute 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_cmd (int fd, const void *src, size_t src_size,
		void *dst, size_t * dst_size)
{
  struct user_scsi us;
  size_t cdb_size;
  char sensebf[CCS_SENSE_LEN];

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

  /* first put the user scsi structure together.  */
  memset (&us, 0, sizeof (us));
  us.us_cdbp = (caddr_t) src;
  us.us_cdblen = cdb_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 + cdb_size;
      us.us_buflen = src_size - cdb_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 60;		/* 1 minute */

/* 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 *src, size_t src_size,
	  void *dst, size_t * dst_size, int probing)
{
  struct scg_cmd scmd;
  size_t cdb_size;
  SANEI_SCSI_Sense_Handler handler;

  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)
    {
      assert (cdb_size == src_size);
      scmd.flags |= SCG_RECV_DATA;
      scmd.addr = dst;
      scmd.size = *dst_size;
    }
  else
    {
      assert (cdb_size <= src_size);
      scmd.addr = (caddr_t) src + cdb_size;
      scmd.size = src_size - cdb_size;
    }
  scmd.cdb_len = cdb_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, src, cdb_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, 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_cmd (int fd, const void *src, size_t src_size,
		void *dst, size_t * dst_size)
{
  return scsi_cmd (fd, 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, 1);
  return (status == SANE_STATUS_GOOD);
}

#endif /* USE == SOLARIS_INTERFACE */


#ifndef 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)
{
  return sanei_scsi_cmd (fd, 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)
{
}

#endif /* WE_HAVE_ASYNC_SCSI */

#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 (1, "sanei_scsi_find_devices: not implemented for this platform\n");
}

#endif /* WE_HAVE_FIND_DEVICES */

#endif

#if USE == BEOS_INTERFACE

#include <scsi.h>
#include <CAM.h>
#include <dirent.h>
#include <sys/stat.h>

bool ScanDir(char *dirpath,const char *vendor, const char *model,
				     const char *type,
				     int bus, int channel, int id, int lun,
				     SANE_Status (*attach) (const char *dev));

bool ScanDir(char *dirpath,const char *vendor, const char *model,
				     const char *type,
				     int bus, int channel, int id, int lun,
				     SANE_Status (*attach) (const char *dev))
{
	DIR *dir;
	struct dirent *dirent;
	struct stat st;
	char path[1024],path1[1024];
	
	if(!vendor) return(true);
	
	strcpy(path,dirpath);
	dir=opendir(path);
	if(!dir) return false; 

	while((dirent=readdir(dir)))
	{
		if((strcmp(dirent->d_name,".")==0) || (strcmp(dirent->d_name,"..")==0)) continue;

		strcpy(path1,path);
		strcat(path1,"/");
		strcat(path1,dirent->d_name);		
		
		//get stat
		if(stat(path1,&st)!=0) continue;		
		if(S_ISDIR(st.st_mode)) ScanDir(path1,vendor,model,type,bus,channel,id,lun,attach);
		else 
		{ 
			int devfd; 
		  	int e; 
		  	char vendor1[20],device[20],scanner_type; 
		  	raw_device_command rdc; 
		  	uchar data[36], sense[16]; 

			if(strcmp(dirent->d_name, "raw") != 0) continue; // just raw devices 

			devfd = open(path1, O_RDONLY); 
			if(devfd < 0) continue; 

			// send inquiry
  			/* fill out our raw device command data */ 
  			rdc.data = data; 
  			rdc.data_length = 36; 
  			rdc.sense_data = sense; 
  			rdc.sense_data_length = 16; 
  			rdc.timeout = 1000000; 
  			rdc.flags = B_RAW_DEVICE_DATA_IN; 
  			rdc.command_length = 6; 
	  		rdc.command[0] = 0x12; 
		  	rdc.command[1] = 0x00; 
  			rdc.command[2] = 0x00; 
  			rdc.command[3] = 0x00; 
  			rdc.command[4] = 36; 
  			rdc.command[5] = 0x00; 

  			e = ioctl(devfd, B_RAW_DEVICE_COMMAND, &rdc, sizeof(rdc)); 
  			close(devfd); 
  			if((e != 0) || (rdc.cam_status != CAM_REQ_CMP)) continue; 

			scanner_type = data[0] & 0x1F; 
			memcpy(vendor1,data+8,8);
			vendor1[8]=0;
			memcpy(device,data+16,16);
			device[16]=0;
			// hack for scanners with wrong type id
  			if(scanner_type !=6 && (!(scanner_type==3))) continue; // not a scanner, skip 

			if(strstr(vendor1,vendor)==0) continue; // vendor does not match
						
			// call attach callback
			attach(path1);
		} 
	}
	return true;
}

void sanei_scsi_find_devices (const char *vendor, const char *model,
				     const char *type,
				     int bus, int channel, int id, int lun,
				     SANE_Status (*attach) (const char *dev))
{
	ScanDir("/dev/bus/scsi",vendor,model,type,bus,channel,id,lun,attach);
//	ScanDir("/dev/disk/ide/atapi",vendor,model,type,bus,channel,id,lun,attach);
}

struct BeOS_fdinfo
{
	int fd;
	SANEI_SCSI_Sense_Handler sense_handler;
	void *sense_arg;
};


SANE_Status sanei_scsi_open (const char * device_name, int * fd,
				    SANEI_SCSI_Sense_Handler sense_handler,
				    void *sense_arg)
{
	int lfd;
	struct BeOS_fdinfo *fdinfo;
	
	lfd=open(device_name,0);
	if(lfd<0) return(SANE_STATUS_INVAL);
	// build data structure
	fdinfo = malloc(sizeof(struct BeOS_fdinfo));
	if(!fdinfo) 
	{
		close(lfd);
		return(SANE_STATUS_INVAL);
	}
	fdinfo->fd=lfd;
	fdinfo->sense_handler=sense_handler;
	fdinfo->sense_arg=sense_arg;
	*fd=(int)fdinfo;
	return(SANE_STATUS_GOOD);
}

/* The extended open call allows a backend to ask for a specific
   buffer size. sanei_scsi_open tries to allocate a buffer of the
   size given by *buffersize upon entry to this function. If
   sanei_scsi_open_extended returns successfully, *buffersize
   contains the available buffer size. This value may be both
   smaller or larger than the value requested by the backend;
   it can even be zero. The backend must decide, if it got enough
   buffer memory to work.

   Note that the value of *buffersize may differ for different
   files.
*/
//SANE_Status sanei_scsi_open_extended (
//       const char * device_name, int * fd,
//       SANEI_SCSI_Sense_Handler sense_handler,
//       void *sense_arg, int *buffersize)
//{
//}

/* Let backends decide, which open call to use:
   if HAVE_SANEI_SCSI_OPEN_EXTENDED is defined, sanei_scsi_open_extended
   may be used.
   May also be used to decide, if sanei_scsi_req_flush_all or
   sanei_scsi_req_flush_all_extended should be used.
*/
//#define HAVE_SANEI_SCSI_OPEN_EXTENDED

/* One or more scsi commands can be enqueued by calling req_enter().
   SRC is the pointer to the SCSI command and associated write data
   and SRC_SIZE is the length of the command and data.  DST is a
   pointer to a buffer in which data is returned (if any).  It may be
   NULL if no data is returned by the command.  On input *DST_SIZE is
   the size of the buffer pointed to by DST, on exit, *DST_SIZE is set
   to the number of bytes returned in the buffer (which is less than
   or equal to the buffer size).  DST_SIZE may be NULL if no data is
   expected.  IDP is a pointer to a void* that uniquely identifies
   the entered request.

   NOTE: Some systems may not support multiple outstanding commands.
   On such systems, enter() may block.  In other words, it is not
   proper to assume that enter() is a non-blocking routine.  */
SANE_Status sanei_scsi_req_enter (int fd,
					 const void * src, size_t src_size,
					 void * dst, size_t * dst_size,
					 void **idp)
{
	return(sanei_scsi_cmd(fd,src,src_size,dst,dst_size));
}

/* Wait for the completion of the SCSI command with id ID.  */
SANE_Status sanei_scsi_req_wait (void *id)
{
	return(SANE_STATUS_GOOD);
}

/* This is a convenience function that is equivalent to a pair of
   enter()/wait() calls.  */

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)
{
	int e,i; 
  	//int path,targ,lun,type; 
  	raw_device_command rdc; 
  	uchar sense[64]; 
	struct BeOS_fdinfo *fdinfo=(struct BeOS_fdinfo *)fd;
	void *data;
	int data_size,flags;
	int lfd=fdinfo->fd;
	
	// calc command length
	//cmd_size=CDB_SIZE(((char *)src)[0]);
	for(i=0;i<cmd_size;i++) rdc.command[i]=((char *)cmd)[i];
	// calc direction
	if((!dst) && ((!dst_size) || (*dst_size==0)))
	{
		// write only
		if(src_size)
		{
			// there's data to send
			data=src;
			data_size=src_size;
		}
		else
		{
			data=0;
			data_size=0;
		}
		flags=B_RAW_DEVICE_REPORT_RESIDUAL;
	}
	else
	{
		// read too
		data=dst;
		data_size=*dst_size;
		flags=B_RAW_DEVICE_DATA_IN|B_RAW_DEVICE_SHORT_READ_VALID|B_RAW_DEVICE_REPORT_RESIDUAL;
	}
  	/* fill out our raw device command data */ 
  	rdc.data = data; 
  	rdc.data_length =data_size; 

  	rdc.sense_data = sense; 
  	rdc.sense_data_length = 64; 
  	rdc.timeout = 100000000; 

  	rdc.flags = flags; 
  	rdc.command_length = cmd_size; 

  	e = ioctl(lfd, B_RAW_DEVICE_COMMAND, &rdc, sizeof(rdc)); 
  	if((e != 0) || (rdc.cam_status != CAM_REQ_CMP)) return(SANE_STATUS_INVAL);
  
  	// call sense handler if there's sense data
  	if(rdc.cam_status& CAM_AUTOSNS_VALID) 
  				e=fdinfo->sense_handler(fd,sense,fdinfo->sense_arg);

	// return read length
	if(dst) *dst_size=rdc.data_length;
  	return(e); 
}


SANE_Status sanei_scsi_cmd (int fd,
				   const void * src, size_t src_size,
				   void * dst, size_t * dst_size)
{
	int e,i; 
  	//int path,targ,lun,type; 
  	raw_device_command rdc; 
  	uchar sense[64]; 
	struct BeOS_fdinfo *fdinfo=(struct BeOS_fdinfo *)fd;
	void *data;
	int data_size,cmd_size,flags;
	int lfd=fdinfo->fd;
	
	// calc command length
	cmd_size=CDB_SIZE(((char *)src)[0]);
	for(i=0;i<cmd_size;i++) rdc.command[i]=((char *)src)[i];
	// calc direction
	if((!dst) && ((!dst_size) || (*dst_size==0)))
	{
		// write only
		if(cmd_size<src_size)
		{
			// there's data to send
			data=&(((char *)src)[cmd_size]);
			data_size=src_size-cmd_size;
		}
		else
		{
			data=0;
			data_size=0;
		}
		flags=B_RAW_DEVICE_REPORT_RESIDUAL;
	}
	else
	{
		// read too
		data=dst;
		data_size=*dst_size;
		flags=B_RAW_DEVICE_DATA_IN|B_RAW_DEVICE_SHORT_READ_VALID|B_RAW_DEVICE_REPORT_RESIDUAL;
	}
  	/* fill out our raw device command data */ 
  	rdc.data = data; 
  	rdc.data_length =data_size; 

  	rdc.sense_data = sense; 
  	rdc.sense_data_length = 64; 
  	rdc.timeout = 100000000; 

  	rdc.flags = flags; 
  	rdc.command_length = cmd_size; 

  	e = ioctl(lfd, B_RAW_DEVICE_COMMAND, &rdc, sizeof(rdc)); 
  	if((e != 0) || (rdc.cam_status != CAM_REQ_CMP)) return(SANE_STATUS_INVAL);
  
  	// call sense handler if there's sense data
  	if(rdc.cam_status& CAM_AUTOSNS_VALID) 
  				e=fdinfo->sense_handler(fd,sense,fdinfo->sense_arg);

	// return read length
	if(dst) *dst_size=rdc.data_length;
  	return(e); 
}

/* Flush all pending SCSI commands. This function work only,
   if zero or one SCSI file handles are open.
*/
void sanei_scsi_req_flush_all (void)
{
}

/* Flush all SCSI commands pending for one handle */
void sanei_scsi_req_flush_all_extended (int fd)
{
}

void sanei_scsi_close (int fd)
{
	struct BeOS_fdinfo *fdinfo=(struct BeOS_fdinfo *)fd;

	close(fdinfo->fd);
	free(fdinfo);
}

#endif
