/****************************************************************
 *
 * tapedrv.c -- Tape Device Firmware Utility
 *
 * Copyrighted as an unpublished work.
 * Copyright (c) 2000-2002 IBM Corporation.  All rights reserved.
 * RESTRICTED RIGHTS:  Use, copying, and/or disclosure of this program
 * is prohibited unless otherwise permitted by written agreement.  Any
 * copy must contain the above copyright notice and this notice.
 *
 * Compile with
 * 16-bit: Microsoft MSVC 1.5 (cl v8.00c)
 * 32-bit: Microsoft MSVC 6.0 (cl v12.00.8168)
 *
 * v0.0   brucej   Original
 * v0.1   brucej   Second pass
 * v0.2   brucej   Add DOS mode memory usage logging
 * v0.3   brucej   Reduced memory requirement by about 10K
 * v0.4   brucej   Reduced memory requirement about an additional 15K
 * v0.5   brucej   Move "fail on critical error" from open_window() to main().
 * v0.6   brucej   Add support for InquiryStdHex, ibm_travan, hp_library
 * v0.7   brucej   Add support for device access via NT port driver
 * v0.8   brucej   Fix for HP half-high and Win9x 3600 Library 126I firmware
 * v0.8a  brucej   Fix 'tapedrv status /r' broken in v0.8
 * v0.9   brucej   Fix 'tapedrv status /o'; 5K DOS memory reduction
 * v1.0   brucej   Debug version (partial non-zero LUN support)
 * v1.1   brucej   Add non-zero LUN support (Use /z option for LUN0 only scan)
 *                 Add "seagate_tr7" UpdateAlgorithm (for STT3401A)
 *                 Add "quantum_dlt_8k" UpdateAlgorithm (for DLT4000)
 *                 Add UpdateFlags = "<flagcharacters>" control
 * v1.2   brucej   Fix failure to update Seagate STT3401A TR7 in DOS mode
 * v1.3   brucej   Fix by disabling non-zero LUN scan when LUN0 returns error
 *
 ****************************************************************/

/*C4001: nonstandard extension 'single line comment' was used*/
#pragma warning(disable:4001)

char My_verstr[] = "1.3";       /* TAPEDRV version */
char *My_cmdname = "tapedrv";
#define MAGIC 0x5400            /* .INI file magic number */
int Magic = MAGIC;

#define MSOS 1  /* Microsoft operating system */
#define __ASPI__ 1
#define __TAPEDRV__ 1

#if _M_IX86>=300
#define WIN32 1
#define __WIN9X__ 1
#define __NT__ 1
#else
#define __DOS__ 1
#endif /*_M_IX86>=300*/

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <dos.h>
#include <conio.h>      /* getch() */
#include <fcntl.h>
#include <malloc.h>
#include <signal.h>
#include <time.h>
#include <direct.h>
#include <io.h>
#include <sys/stat.h>

#if WIN32
#define WIN32_LEAN_AND_MEAN 1
#include <windows.h>
#endif

#if __NT__
#if defined(_WIN64) /* used by SCSI_PASS_THROUGH */
    typedef unsigned __int64 ULONG_PTR, *PULONG_PTR;
#else
    typedef unsigned long ULONG_PTR, *PULONG_PTR;
#endif
#include "devioctl.h"
#include "ntddscsi.h"
#endif

#if WIN32
#define _MAX_PATH       260
#define IOFAR
#define _fmemcpy memcpy
#define _fmemcmp memcmp
#define _fstrlen strlen
#define halloc calloc
#define hfree free
#define my_canon(p) (p)

#else /*__DOS__*/
#undef _MAX_PATH
#define _MAX_PATH       80 /* "x:\63\12345678.123" = 2+1+63+1+12+1 */
#define USE_FATTIME 1
#define IOFAR   _far    /* far data frames */
#define BYTE    unsigned char
#define ULONG   unsigned long
#define USHORT  unsigned short
#define BOOL    int
#define HANDLE  int
#define GetLastError()  (_doserrno)
#define SetLastError(x) (_doserrno=(x))
#define getdcwd my_getdcwd
#define getcwd  my_getcwd
#define chdir   my_chdir
#endif

#define HAS_ALLOCA 1
#define LOTSOFMEMORY (!__DOS__)
#define MSOS_O_BINARY O_BINARY
#define PATHSEPSTR "\\/:"
#define DIRSEP '\\'

#if LOTSOFMEMORY
#define TAPEDRV_UPDATE 1
#endif

#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif

#ifndef offsetof
#define offsetof(s,m)   (unsigned/*size_t*/)&(((s *)0)->m)
#endif

/*
 * UNREFERENCED -- unreferenced variable
 * MIN -- minimum of two values
 * NELEM -- Number of elements in an array
 * ENDELEM -- Address of end-of-array
 */
#define UNREFERENCED(x) (x)=(x); /*if (x) ; else*/
#define MIN(a, b)       ((a<b)?a:b)
#define NELEM(a)        (sizeof(a)/sizeof((a)[0]))
#define ENDELEM(a)      &(a)[NELEM(a)]

struct codesym {
	int    cs_value;
	char   *cs_name;
};

#if WIN32
OSVERSIONINFO Osversioninfo= { sizeof(Osversioninfo) };
#define IS_NT() (Osversioninfo.dwPlatformId == VER_PLATFORM_WIN32_NT)
#define IS_WIN9X() (!IS_NT())

#else /*__DOS__ || unix */
#define IS_NT() 0
#define IS_WIN9X()
#endif

int Orig_argc;
char **Orig_argv;
char **Subcmd_argv;

#define PROGNAME()      (Prog_buf)
#define CPYRIGHT()      (plmsg_cprt)
char Prog_buf[100];
char *plmsg_prog_fmt;   /* preload messages */
char *plmsg_cprt;

#define LOGONLY 0x4000 /* report error via vlogf() only */
#define CMDPFX  0x2000 /* prefix with "<cmd>: error: " */
#define INIPFX  0x1000 /* prefix "%s: [%s]: " */
#define MSGSYN  0x0800 /* prefix "%s(%ld): Message file syntax error: " */
	/* error types for Err_type */
#define ERTYPE	0x0700 /* Mask for UXER, OSER, MYER, DVRER, HAER, SCER */
#define UXER    0x0100 /* errno has unix style system error */
#define OSER    0x0200 /* GetLastError() has operating system error */
#define MYER    0x0300 /* My_error has program defined error (MYE_*) */
#define DVRER   0x0400 /* Driver_stat has driver (e.g., ASPI) error */
#define HAER    0x0500 /* Ha_stat has host adapter error */
#define SCER    0x0600 /* Scsi_stat has SCSI status byte (SSTAT_*) */

	/* exit codes */

	/* autoupdate exit codes */
#define EXA_ZERO           0 /* Tape device successfully updated */
#define EXA_DEVICEFAIL     1 /* Tape device update failure */
#define EXA_NONEFOUND      2 /* No supported tape devices found */
#define EXA_UPTODATE       3 /* Tape device already up to date */
#define EXA_FATAL          4 /* Unrecoverable error unrelated to the tape device */
#define EXA_CANCEL         5 /* Operator canceled automatic update */
#define EXA_NEEDSALTERNATE 6 /* Tape device needs update via alternate method */
#define EXA_NEEDSDOS       7 /* Tape device needs update in DOS only mode */
#define EXA_INUSE          8 /* Tape device in use by another program */
#define EXA_INACCESSIBLE   9 /* Tape device inaccessible */

#define EX_FATAL        EXA_FATAL /* Fatal error exit code */
#define EX_CANCEL       EXA_CANCEL /* User cancel exit code */

int error_level;        /* error level for exit */

int Err_type;   /* Set to one of UXER, OSER, MYER, DVRER, SCER, or zero */
int My_error;   /* MYER: Program defined MYE_* error codes */
int Driver_stat;/* DVRER: Driver status (e.g., ASPI or unix driver status) */
int Ha_stat;    /* HAER: Host adapter status */
int Scsi_stat;  /* SCER: SCSI status byte SSTAT_* values */

#define SENSE_LEN       18          /* Default sense buffer length */
unsigned char Rsense[SENSE_LEN];    /* request sense buffer */

	/* values for My_error */
#define MYE_WAIT_TIMEOUT   1 /* "WaitForSingleObject(): WAIT_TIMEOUT" */
#define MYE_WAIT_ABANDONED 2 /* "WaitForSingleObject(): WAIT_ABONDONED" */
#define MYE_WAIT_UNKNOWN   3 /* "WaitForSingleObject(): Undecoded error" */

int Arg_targid= -1;
int Arg_lun0;           /* true: test only lun0 during ASPI scan */
int Arg_busflags;       /* Bus search control: BUSF_IDE or BUSF_SCSI */
int Arg_download_seconds = -1;
int Arg_download_mode;  /* Least significant 2-bits of Seagate DAT write buffer mode */
int Arg_force_flags;    /* Force download flags  */
#define FORCE_LINEAR    4 /* Use linear addressing for Seagate DAT */
#define FORCE_SEGMENT   2 /* Use segment:offset addressing Seagate DAT */
#define FORCE_FILE      1 /* Skip filename validation */

volatile int fCtlBrk;   /* flag for control-break hit */
int fDebug;             /* debug flag */
int fWindowMode;        /* window (not command line) mode */
int fMoreMode;          /* use --more-- prompt on screen full */
int fShowdots;          /* showing "." during firmware update */
int fSimulate;
int fUpdate;
int fUpdateSim;
int fAutoUpdate;
int fAutoSim;
int fStatus;
int fStatSim;
int fRecognizable;

char Cmsg_vbuf[0x1000];
char log_vbuf[ 0x800 ];     /* avoids disk access hangs during ide_init() */
char *stdout_vbufaddr;      /* ditto */
#define STDOUT_VBUFSIZE 0x800

char Fw_path[_MAX_PATH];        /* Pathname from /p option */
char Fw_fname[_MAX_PATH];       /* Firmware filename string */

	/* command function table */
struct maincmd {
	char *mcmd_name;
	int (*mcmd_func)(int, char **);
	int mcmd_flags;
#define MCMDF_OTHERPARMS   0x2 /* Other parameters allowed after options */
#define MCMDF_NOPREPARSE   0x1 /* Don't preparse */
	char *mcmd_optchars;
};
struct maincmd *Maincmdp;

/* forward reference */
char *my_section(char *section);
int is_accessible_drive(char *path);
int close_window_mode(void);
int error(int, char *, ...);
int verror_set(int, char *, va_list);
int my_printf(char *, ...);
int logf(char *, ...);
int is_dosmode_only_updatealg(char *updtalg_str);
int vmore(char *, va_list);
void my_exit(int status);
void usage(void);
int parse_options(int mcmdflags, char *optchars, int argc, char **argv);
extern char Fw_path[_MAX_PATH];
extern char Inifname[_MAX_PATH];

/*
 * 16/32 bit aspi interface
 */

#define SENSE_LEN       18      /* Default sense buffer length */

#define WM_ASPIPOST             0x4D42  /* ASPI Post message */

	/* SRB_HAInquiry -- host adapter inquiry ASPI request block */
#pragma pack(1)
typedef struct {
	unsigned char  SRB_Cmd;         /* w SC_HA_INQUIRY=0 */
	unsigned char  SRB_Status;      /* r SS_* values */
	unsigned char  SRB_HaId;        /* w host adapter number */
	unsigned char  SRB_Flags;       /* w SRB_* values */
#if WIN32
	unsigned long  SRB_Hdr_Rsvd;
#else /*__DOS__*/
	unsigned short SRB_55AASignature; /* wr Extended signature bytes */
	unsigned short SRB_ExtBufferSize; /* wr Extended buffer length */
#endif
	unsigned char  HA_Count;        /* r SCSI host adapters present */
	unsigned char  HA_SCSI_ID;      /* r SCSI ID of host atapter */
	unsigned char  HA_ManagerId[16];/* r ascii */
	unsigned char  HA_Identifier[16];/* r ascii */
	unsigned char  HA_Unique[16];   /* r adapter specific parameters */
#if WIN32
	unsigned short HA_Rsvd1;
#else /*__DOS__*/
	unsigned char  HA_ExtBuffer[2]; /* r Extended buffer area */
#endif
} SRB_HAInquiry, *PSRB_HAInquiry;
#pragma pack()

	/* Win32 offsets in HA_Unique[] */
#define HAUNIQ_ALIGNMASK        0 /* 16-bit Buffer Alignment mask */
#define HAUNIQ_FLAGS            2 /*  8-bit Flag values HAUNIQ_FLAG_*  */
#define HAUNIQ_NTARGS           3 /*  8-bit Max targets (special 0 for 8) */
#define HAUNIQ_MAXXFERLEN       4 /* 32-bit Max transfer length */

#define HAUNIQ_FLAG_RESIDUAL 0x02 /* bit1: Residual byte count supported */

	/*
	 * Win16 bit masks for HA_ExtBuffer[0] when SRB_55AASignature is 
	 * retured byte swapped (0xAA55 -> 0x55AA), and 
	 * SRB_ExtBufferSize has a return value >= 1
	 */
#define HAEX0_WIDE32    0x08 /* Wide SCSI 32-bit host adapter */
#define HAEX0_WIDE16    0x04 /* Wide SCSI 16-bit host adapter */
#define HAEX0_RESIDUAL  0x02 /* Residual byte count supported */

	/* SRB_GDEVBlock -- get device type ASPI request block */
typedef struct {
	unsigned char  SRB_Cmd;         /* w SC_GET_DEV_TYPE=1 */
	unsigned char  SRB_Status;      /* r SS_* values */
	unsigned char  SRB_HaId;        /* w host adapter number */
	unsigned char  SRB_Flags;       /* w SRB_* values */
	unsigned long  SRB_Hdr_Rsvd;
	unsigned char  SRB_Target;      /* w scsi device address */
	unsigned char  SRB_Lun;         /* w logical unit number */
	unsigned char  SRB_DeviceType;  /* r scsi device type */
	unsigned char  SRB_Rsvd1;
} SRB_GDEVBlock, *PSRB_GDEVBlock;

/*
 * SRB_ExecSCSICmd -- scsi command (or bus device reset) ASPI request block
 *
 * The following fields are not used for reset:
 * SRB_BufLen, SRB_BufPointer, SRB_SenseLen, SRB_CDBLen,
 * CDBByte, SenseArea.
 * Note: On completion of a 16-bit ASPI SRB_ExecSCSICmd with a 
 * Check Condition status, the sense data immediatly follows the 
 * final byte of the CDB.  For compatibility with WIN32, sense data 
 * can be moved to SenseArea.
 * E.g., memmove(sp->SenseArea, &sp->CDBByte[ cdblen ], sizeof(sp->SenseArea));
 */
#pragma pack(1)
typedef struct _SRB_ExecSCSICmd {
	unsigned char  SRB_Cmd;         /* w SC_EXEC_SCSI_CMD=2 or SC_RESET_DEV=4 */
	unsigned char  SRB_Status;      /* r SS_* values */
	unsigned char  SRB_HaId;        /* w host adapter number */
	unsigned char  SRB_Flags;       /* w SRB_* values */
	unsigned long  SRB_Hdr_Rsvd;
	unsigned char  SRB_Target;      /* w scsi device address */
	unsigned char  SRB_Lun;         /* w logical unit number */
#if WIN32
	unsigned short SRB_Rsvd1;
	unsigned long  SRB_BufLen;      /* w data buffer length */
	void  *SRB_BufPointer;          /* w data buffer address */
	unsigned char  SRB_SenseLen;    /* w sense buffer allocation */
	unsigned char  SRB_CDBLen;      /* w 6, 10, or 12 */
	unsigned char  SRB_HaStat;      /* r HASTAT_* values */
	unsigned char  SRB_TargStat;    /* r SSTAT_* values */
	void  *SRB_PostProc;            /* w Completion event handle */
	void  *SRB_Rsvd2;
	unsigned char  SRB_Rsvd3[16];
#else /*__DOS__*/
	unsigned long  SRB_BufLen;      /* w data buffer length */
	unsigned char  SRB_SenseLen;    /* w sense buffer allocation */
	void _far *SRB_BufPointer;      /* w data buffer address */
	void _far *SRB_Next;            /* w next linked SRB_ExecSCSICmd */
	unsigned char  SRB_CDBLen;      /* w 6, 10, or 12 */
	unsigned char  SRB_HaStat;      /* r HASTAT_* values */
	unsigned char  SRB_TargStat;    /* r SSTAT_* values */
	void (_far *SRB_PostProc)(struct _SRB_ExecSCSICmd _far *);/* w post function */
	unsigned char  SRB_Rsvd3[34];
#endif
	unsigned char  CDBByte[16];             /* w scsi cdb */
	unsigned char  SenseArea[SENSE_LEN];    /* r request sense buffer */
} SRB_ExecSCSICmd, *PSRB_ExecSCSICmd;
#pragma pack()

	/* SRB_Abort -- abort an SRB_ExecSCSICmd ASPI request block */
typedef struct {
	unsigned char  SRB_Cmd;         /* w SC_ABORT_SRB=3 */
	unsigned char  SRB_Status;      /* r SS_* values */
	unsigned char  SRB_HaId;        /* w host adapter number */
	unsigned char  SRB_Flags;       /* w SRB_* values */
	unsigned long  SRB_Hdr_Rsvd;
#if WIN32
	struct _SRB_ExecSCSICmd *SRB_ToAbort; /* w request to abort */
#else /*__DOS__*/
	struct _SRB_ExecSCSICmd _far *SRB_ToAbort; /* w request to abort */
#endif
} SRB_Abort, *PSRB_Abort;

	/* SRB_BusDeviceReset -- reset a SCSI device ASPI request block */
#pragma pack(1)
typedef struct {
	unsigned char  SRB_Cmd;         /* w SC_RESET_DEV=4 */
	unsigned char  SRB_Status;      /* r SS_* values */
	unsigned char  SRB_HaId;        /* w host adapter number */
	unsigned char  SRB_Flags;       /* w SRB_* values */
	unsigned long  SRB_Hdr_Rsvd;
	unsigned char  SRB_Target;      /* w scsi device address */
	unsigned char  SRB_Lun;         /* w logical unit number */
#if WIN32
	unsigned char  SRB_Rsvd1[12];
	unsigned char  SRB_HaStat;      /* r HASTAT_* values */
	unsigned char  SRB_TargStat;    /* r SSTAT_* values */
	void  *SRB_PostProc;            /* w Completion event handle */
	void  *SRB_Rsvd2;
	unsigned char  SRB_Rsvd3[32];
#else /*__DOS__*/
	unsigned char  SRB_Rsvd1[14];
	unsigned char  SRB_HaStat;      /* r HASTAT_* values */
	unsigned char  SRB_TargStat;    /* r SSTAT_* values */
	void (_far *SRB_PostProc)(struct _SRB_ExecSCSICmd _far *);/* w post function */
	unsigned char  SRB_Rsvd3[34];
#endif
} SRB_BusDeviceReset, *PSRB_BusDeviceReset;
#pragma pack()

	/*
	 * SRB_SetHAParms -- set host adapter parameters ASPI request block
	 * Not avialable under WIN32
	 */
typedef struct {
	unsigned char  SRB_Cmd;         /* w SC_SET_HA_PARMS=5 */
	unsigned char  SRB_Status;      /* r SS_* values */
	unsigned char  SRB_HaId;        /* w host adapter number */
	unsigned char  SRB_Flags;       /* w SRB_* values */
	unsigned long  SRB_Hdr_Rsvd;
	unsigned char  SRB_Parms[16];   /* w */
} SRB_SetHAParms, *PSRB_SetHAParms;

	/*
	 * SRB_GetDiskInfo -- get disk information ASPI request block
	 * Not available on NT
	 */
typedef struct {
	unsigned char  SRB_Cmd;         /* w SC_GET_DISK_INFO=6 */
	unsigned char  SRB_Status;      /* r */
	unsigned char  SRB_HaId;        /* w */
	unsigned char  SRB_Flags;       /* w */
	unsigned long  SRB_Hdr_Rsvd;
	unsigned char  SRB_Target;      /* w scsi device address */
	unsigned char  SRB_Lun;         /* w logical unit number */
	unsigned char  SRB_DriveFlags;  /* Driver flags */
	unsigned char  SRB_Int13HDriveInfo;     /* Host Adapter Status */
	unsigned char  SRB_Heads;       /* Preferred number of heads translation */
	unsigned char  SRB_Sectors;     /* Preferred number of sectors translation */
	unsigned char  SRB_Rsvd1[10];   /* Reserved */
} SRB_GetDiskInfo, *PSRB_GetDiskInfo;

	/* SRB_Cmd values */
#define SC_HA_INQUIRY           0 /* SRB_HAInquiry: Host adapter inquiry */
#define SC_GET_DEV_TYPE         1 /* SRB_GDEVBlock: Get device type */
#define SC_EXEC_SCSI_CMD        2 /* SRB_ExecSCSICmd: Execute SCSI command */
#define SC_ABORT_SRB            3 /* SRB_Abort: Abort an SRB */
#define SC_RESET_DEV            4 /* SRB_BusDeviceReset: SCSI bus device reset */
#define SC_SET_HA_PARMS         5 /* SRB_SetHAParms: Set host adapter parms */
#define SC_GET_DISK_INFO        6 /* SRB_GetDiskInfo: Get disk information */

	/* SRB_Flags values */
#define SRB_EVENT_NOTIFY        0x40 /* Enable ASPI event notification */
#define SRB_DIR_NONE            0x18 /* no data */
#define SRB_DIR_OUT             0x10 /* host to target data; length checked */
#define SRB_DIR_IN              0x08 /* target to host data; length checked */
#define SRB_DIR_AUTO            0x00 /* command implies direction; no length ck */
#define SRB_ENABLE_RESIDUAL_COUNT 0x04 /* Enable residual byte count reporting */
#if WIN32
#define SRB_DATA_SG_LIST        0x02 /* SRB_BufPointer points to scatter-gather list */
#else /*__DOS__*/
#define SRB_LINK                0x02 /* Linking enabled */
#endif
#define SRB_POSTING             0x01 /* Enable ASPI posting */

	/* SRB_Status values */
#define SS_PENDING              0x00 /* SRB busy */
#define SS_COMP                 0x01 /* SRB done without error */
#define SS_ABORTED              0x02 /* SRB aborted */
#define SS_ABORT_FAIL           0x03 /* Unable to abort SRB */
#define SS_ERR                  0x04 /* SRB done with error */
#define SS_INVALID_CMD          0x80 /* Invalid ASPI command */
#define SS_INVALID_HA           0x81 /* Invalid host adapter number */
#define SS_NO_DEVICE            0x82 /* SCSI device not installed */
#define SS_INVALID_SRB          0xe0 /* Invalid SRB parameter */
#define SS_OLD_MANAGER          0xe1 /* (W16) DOS ASPI manager doesn't support Win16 */
#define SS_BUFFER_ALIGN         0xe1 /* (W32) Buffer alignment error */
#define SS_ILLEGAL_MODE         0xe2 /* (W16) ASPI manager doesn't support Win16 real mode */
#define SS_SECURITY_VIOLATION   0xe2 /* (W32) Device access security violation */
#define SS_NO_ASPI              0xe3 /* (W16) No ASPI manager loaded */
#define SS_FAILED_INIT          0xe4 /* ASPI initialization error */
#define SS_ASPI_IS_BUSY         0xe5 /* ASPI manager busy */
#define SS_BUFFER_TO_BIG        0xe6 /* Buffer too large */

	/* SRB_HaStat values */
#define HASTAT_OK               0x00 /* No error */
#define HASTAT_TIMEOUT          0x09 /* SRB process timeout */
#define HASTAT_COMMAND_TIMEOUT  0x0b /* SRB adapter timeout */
#define HASTAT_MESSAGE_REJECT   0x0d /* CDB rejected via MESSAGE REJECT */
#define HASTAT_BUS_RESET        0x0e /* SCSI bus reset detected */
#define HASTAT_PARITY_ERROR     0x0f /* SCSI parity error */
#define HASTAT_REQUEST_SENSE_FAILED 0x10 /* REQUEST SENSE retrival error */
#define HASTAT_SEL_TO           0x11 /* SCSI select timeout */
#define HASTAT_DO_DU            0x12 /* DMA overrun/underrun */
#define HASTAT_BUS_FREE         0x13 /* SCSI unexpected bus free */
#define HASTAT_PHASE_ERR        0x14 /* SCSI bus phase sequence error */

/*
 * ideaspi -- ASPI interface for SCSI and ATAPI
 * Autosense issues a REQUEST SENSE on DSC for
 * REWIND, WRITE_FILEMARKS, SPACE, ERASE, LOAD_UNLOAD, LOCATE
 */
#if __DOS__
#if _MSC_VER
#pragma check_stack(off)
#endif

#define IDEPRF(args)    my_printf args

	/* ideaspi ata extensions */
#if ATAEXT
#define SC_ATA_PACKET_IDENTIFY  7 /* SRB_ExecSCSICmd: SRB_BufPointer: 512 byte data buffer */
#define SC_ATA_RESET            8 /* SRB_BusDeviceReset: Resets all SRB_Targets */
#define SC_ATA_WRITE_PORTS      9 /* SRB_ExecSCSICmd: CDBByte gives 8 register output values */
				/* &CDBByte[SRB_CDBLen] returns 8 register values */
#define SC_ATA_READ_PORTS       10 /* SRB_ExecSCSICmd: &CDBByte[SRB_CDBLen] returns 8 register values */
#endif

dos_winver(void)        /* returns: 0=not running; 0x30a:win3.10; 0x400=win95 */
{
	int ver= 0;

	_asm {
	mov ax, 1600h
	int 2fh
	or al, al       // al = 00h,80h: Win3.x/Enhanced or
	jz done         //              Win2.x/386 2.x NOT running
	cmp al, 80h
	je done
	cmp al, 0ffh    // al = 01h,ffh: Win2.x/386 running
	jne dwv1
	mov al, 1
	}
dwv1:   _asm {
	xchg ah, al
	mov ver, ax
	}
done:   return (ver);
}

dos_diskreset(void)     /* flushes disk buffers (e.g., smartdrv) */
{
	_asm {
	mov ah, 0dh
	int 21h
	}
	return (0);
}

/*
 * _dostermtrap() -- intercepts terminate address in psp
 *
 * When _dostermtrap() receives control from the terminate address, 
 * (in the psp) it calls the saved _dosterm_hndlr handler (set by 
 * dos_setterm()).  When the handler returns, the original program 
 * terminate address gains control.
 */
#define DOSTERM_DFL (void (*)(void))0   /* default terminate action */
void (*dos_setterm(void (*)(void)))(void);

void (*_dosterm_hndlr)(void)= DOSTERM_DFL;
	/* pointer to the terminate address in the psp */
void (_far * _far *_psp_termaddr_p)(void)=
				(void (_far * _far *)(void))(long)0xa;
	/* original terminate addr from psp:000a */
void (_far *_psp_termaddr_sv)(void);
unsigned _dosterm_ss_sv;
unsigned _dosterm_sp_sv;
unsigned _dosterm_sp;
/*extern _psp;*/

 void _loadds _far
_dostermtrap(void)
{
/*
 *	push bp         Compiler dependent prolog
 *	mov bp, sp
 *	push ds
 *	mov ax, DGROUP
 *	mov ds, ax
 *	ASSUME DS:DGROUP
 */
#if M_I86SM || M_I86MM || __SMALL__ || __MEDIUM__
	_asm {
	cld
	mov _dosterm_ss_sv, ss
	mov _dosterm_sp_sv, sp
	push ds
	cli
	pop ss
	mov sp, _dosterm_sp     // set stack in effect at call to dos_setterm()
	sti
	}
#endif
	*_psp_termaddr_p= _psp_termaddr_sv;
	(*_dosterm_hndlr)();
#if M_I86SM || M_I86MM || __SMALL__ || __MEDIUM__
	_asm {
	cli
	mov ss, _dosterm_ss_sv
	mov sp, _dosterm_sp_sv
	sti
	}
#endif
	(*_psp_termaddr_sv)();
/*
 *	pop ds          Compiler dependent epilog
 *	mov sp,bp
 *	pop bp
 *	ret
 */
}

/*
 * dos_setterm() -- installs a program termination handler
 *
 * #define DOSTERM_DFL (void (*)(void))0        default terminate action
 *
 * void (*dos_setterm(handler))(void)
 * void (*handler)(void);       terminate handler
 *
 * dos_setterm() installs a replacement for the program terminate handler 
 * and returns the address of the previously installed handler.  This is
 * useful for removing interrupt vectors when a program terminates for
 * any reason including the 'abort' option of the DOS critical error 
 * handler (int 24).
 *
 * DOSTERM_DFL is a special value which means the originally installed 
 * "program terminate address".  
 *
 * When the argument 'handler' is other than DOSTERM_DFL, the "program 
 * terinate address" is re-vectored to a low level handler, _dostermtrap().
 * When _dostermtrap() passes control is passed to the 'handler', 
 * following restrictions are in effect:  1) The handler should avoid 
 * system (DOS) calls other than the low numbered console I/O calls.  
 * 2) When the handler has performed it function, it must return control.
 *
 * When the 'handler' argument has the special value, DOSTERM_DFL, 
 * the original (installed at program entry) "program terminate address" 
 * is restored.
 *
 * A pointer to the previously installed handler is returned.
 * The special return value DOSTERM_DFL, means the default handler 
 * was previously installed.
 */
void (*dos_setterm(void (*handler)(void)))(void)
{
	void (*old_handler)(void);      /* old handler */

	_asm mov _dosterm_sp, sp        // save stack for use by _dostermtrap()

	*((unsigned *)&_psp_termaddr_p + 1)= _psp;
	if ((old_handler= _dosterm_hndlr) == DOSTERM_DFL) {
		_psp_termaddr_sv= *_psp_termaddr_p;
		/* assume new handler is not default and swap in l0 handler */
		*_psp_termaddr_p= (void (_far *)(void))_dostermtrap;
	}
	if ((_dosterm_hndlr= handler) == DOSTERM_DFL)
		*_psp_termaddr_p= _psp_termaddr_sv;
	return (old_handler);
}

 void _far *
my_canon(void _far *p)  /* makes a 16-bit real mode far pointer canonic */
{
	/* segment += (offset >> 4) */
	/* offset &= 0xf */
	((unsigned *)&p)[1] += ((unsigned *)&p)[0] >> 4;
	((unsigned *)&p)[0] &= 0xf;
	return (p);
}

 long
my_20addr( /* converts a 16-bit real mode far pointer to a 20-bit physical address */
	void _far *caddr)
{
	/* (segment<<4) + offset */
	return ((long)((unsigned *)&caddr)[1]<<4) + ((unsigned *)&caddr)[0];
}

	/* IDE defines */

	/* offsets from ide_port */
#define IREG_ERROR      1 /* features/error */
#define IREG_REASON     2 /* sector count / interrupt reason */
#define IREG_SECNO      3 /* sector number */
#define IREG_CNTLO      4 /* cylinder/byte_count lo */
#define IREG_CNTHI      5 /* cylinder/byte_count hi */
#define IREG_DEVHD      6 /* device / head */
#define IREG_STAT       7 /* command / status */
#define IREG_ALTSTAT    0x0206 /* control / alternate status */

	/* IREG_STAT/IREG_ALTSTAT status bits */
#define ISBSY   0x80 /* Busy */
#define ISDRDY  0x40 /* Device Ready */
#define ISDF    0x20 /* Device fault */
#define ISDSC   0x10 /* Device Seek Complete */
#define ISDRQ   0x08 /* Data Request */
#define ISCOR   0x04 /* Corrected Data */
#define ISIDX   0x02 /* Index (Atapi doesn't use) */
#define ISERR   0x01 /* Drive Error */

	/* IREG_REASON interrupt reason bits */
#define IRCD    0x01 /* Command/~Data 1/0=Cmd/Data */
#define IRIO    0x02 /* Input/~Output 1/0=Read/Write */
	/* IREG_ERROR error register bits (info available in req sense data) */
#define IEABT   0x04 /* Abort - Set for invalid CDB */
	/* IREG_ALTSTAT control bits */
#define ICSRST  0x04 /* SRST: ATA soft reset */
#define ICNIEN  0x02 /* NIEN: not (~)interrupt enable */

	/* timer indices */
#define TI_DEFAULT      0
#define TI_QUICK        1 /* quickley executing commands */
#define TI_SHORT        2
#define TI_FLASH        3 /* download and flash eeprom */
#define TI_IO           4 /* read/write */
#define TI_LOAD         5 /* load, rewind, retension */
#define TI_CHANGE       6 /* change cassette (medimum changer) */
#define TI_SEARCH       7 /* i/o pass over entire medium */
#define TI_DIAG         8 /* diagnostic */
#define TI_9            9
#define TI_10           10
#define TI_11           11
#define TI_12           12
#define TI_13           13
#define TI_MAX          14 /* largest of above timers */
#define TI_INFINITE     15

	/* atapi cdb timeouts */
int ide_cdb_seconds[]= {
	2,        /* 0 TI_DEFAULT */
	2,        /* 1 TI_QUICK  quickley executing commands */
	10,       /* 2 TI_SHORT  */
	60,       /* 3 TI_FLASH  download and flash eeprom */
	5*60,     /* 4 TI_IO     read/write */
	10*60,    /* 5 TI_LOAD   load, rewind, retension */
	10*60,    /* 6 TI_CHANGE change cassette (medimum changer) */
	60*60,    /* 7 TI_SEARCH i/o pass over entire medium */
	2*60,     /* 8 TI_DIAG   diagnostic */
	2,        /* 9 */
	2,        /* 10 */
	2,        /* 11 */
	2,        /* 12 */
	2,        /* 13 */
	60*60,    /* 14 TI_MAX largest of above */
	0,        /* 15 TI_INFINITE */
};

/*
 * ide_cdb_type[] is an array indexed by cdb operation code.
 * Each entry has a data direction flag and a timeout index.
 */
	/* bit masks for entries in cdb typing array, ide_cdb_type */
#define _CDTYPE_WAITDSC     (1<<7) /* request sense on DSC */
#define _CDTYPE_DATANONE    (1<<6) /* i/o none */
#define _CDTYPE_DATAOUT     (1<<5) /* i/o out */
#define _CDTYPE_DATAIN      (1<<4) /* i/o in */
#define _CDTYPE_DATAUNKNOWN 0      /* i/o unknown */
#define _CDTYPE_TMRIDX      0xf    /* TI_* timeout index for ide_cdb_seconds */
#define CDBTYPE_DATAIN(c) ((c>>4)&1)

#define W _CDTYPE_WAITDSC
#define N _CDTYPE_DATANONE
#define O _CDTYPE_DATAOUT
#define I _CDTYPE_DATAIN
#define U _CDTYPE_DATAUNKNOWN

unsigned char ide_cdb_type[]= {
	/*
	 *                 Device Columns M = Manditory
	 *                 Key:           O = Optional
	 *                                V = Vendor unique
	 *                                  = Reserved (blank)
	 *                    D          = Direct access
	 *                     T         = Sequential access
	 *                      L        = Printer
	 *                       P       = Processor
	 *                        W      = Write once read multiple
	 *                         R     = Read only (CD-ROM)
	 *                          S    = Scanner
	 *                           O   = Optical memory
	 *                            M  = Media changer
	 *                             C = Communication
	 *                 D Op DTLPWRSOMC Description
	 *                 - -- ---------- -----------
	 */
	N|TI_QUICK,     /* N 00 MMMMMMMMMM SCMD_TEST_UNIT_READY */
	N|TI_LOAD|W,    /* N 01  M         SCMD_REWIND */
	                /*      O V OO OO  SCMD_REZERO_UNIT */
	U,              /* U 02 VVVVVV V    */
	I|TI_QUICK,     /* I 03 MMMMMMMMMM SCMD_REQUEST_SENSE */
	O|TI_SEARCH,    /* O 04   O             FORMAT */
	                /* o    M      O        FORMAT_UNIT */
	I|TI_QUICK,     /* I 05 VMVVVV  V  SCMD_READ_BLOCK_LIMITS */
	U,              /* U 06 VVVVVV  V   */
	O,              /* O 07         O  SCMD_INITIAL_ELEMENT_STATUS */
	                /* o    OVV O  OV       REASSIGN_BLOCKS */
	I|TI_IO,        /* i 08          M      GET_MESSAGE_6 */
	                /* I    OMV OO OV  SCMD_READ_6 */
	                /* i       O            RECEIVE */
	U,              /* U 09 VVVVVV  V   */
	O|TI_IO,        /* O 0A   M             PRINT */
	                /* o             M      SEND_MESSAGE_6 */
	                /* o       M            SEND_6 */
	                /* o    OM  O  OV  SCMD_WRITE_6 */
	N|TI_IO,        /* N 0B O   OO OV       SEEK_6 */
	                /* n      O             SLEW_AND_PRINT */
	                /* n     O         SCMD_SET_CAPACITY */
	U,              /* U 0C VVVVVV  V   */
	U,              /* U 0D VVVVVV  V   */
	U,              /* U 0E VVVVVV  V   */
	I|TI_IO,        /* I 0F VOVVVV  V  SCMD_READ_REVERSE */
	N|TI_IO|W,      /* N 10   O O           SYNCHRONIZE_BUFFER */
	                /* n    VM VVV     SCMD_WRITE_FILEMARKS */
	N|TI_SEARCH|W,  /* N 11 VMVVVV     SCMD_SPACE */
	I|TI_QUICK,     /* I 12 MMMMMMMMMM SCMD_INQUIRY */
	N|TI_IO,        /* N 13 VOVVVV     SCMD_VERIFY_6 */
	I|TI_QUICK,     /* I 14 VOOVVV     SCMD_RECOVER_BUFFERED_DATA */
	O|TI_QUICK,     /* O 15 OMO OOOOOO SCMD_MODE_SELECT_6 */
	O|TI_QUICK,     /* O 16 M   MM MO  SCMD_RESERVE */
	                /* o     MM   M    SCMD_RESERVE_UNIT */
	N|TI_QUICK,     /* N 17 M   MM MO  SCMD_RELEASE */
	                /* n     MM   M    SCMD_RELEASE_UNIT */
	O|TI_SEARCH,    /* O 18 OOOOOOOO   SCMD_COPY */
	N|TI_SEARCH|W,  /* N 19 VMVVVV     SCMD_ERASE */
	I|TI_QUICK,     /* I 1A OMO OOOOOO SCMD_MODE_SENSE_6 */
	N|TI_LOAD|W,    /* N 1B  O         SCMD_LOAD_UNLOAD */
	                /* n          O         SCAN */
	                /* n      O             STOP_PRINT */
	                /* n    O   OO O        STOP_START_UNIT */
	I|TI_QUICK,     /* I 1C OOOOOOOOOO SCMD_RECEIVE_DIAGNOSTIC_RESULTS */
	O|TI_DIAG,      /* O 1D MMMMMMMMMM SCMD_SEND_DIAGNOSTIC */
	N|TI_QUICK,     /* N 1E OO  OO OO  SCMD_PREVENT_ALLOW_MEDIUM_REMOVAL */
	U,              /* U 1F             */
	U,              /* U 20 V   VV V    */
	U,              /* U 21 V   VV V    */
	U,              /* U 22 V   VV V    */
	U,              /* U 23 V   VV V    */
	O|TI_QUICK,     /* O 24 V   VVM         SET_WINDOW */
	I|TI_QUICK,     /* I 25       O         GET_WINDOW */
	                /* i    M   M  M        READ_CAPACITY */
	                /* i         M          READ_CDROM_CAPACITY */
	U,              /* U 26 V   VV      */
	U,              /* U 27 V   VV      */
	I|TI_IO,        /* I 28          O      GET_MESSAGE 10 */
	                /* i    M   MMMM        READ_10 */
	I|TI_IO,        /* I 29 V   VV O        READ_GENERATION */
	O|TI_IO,        /* O 2A          O      SEND_MESSAGE_10 */
	                /* o          O         SEND_10 */
	                /* o    M   M  M        WRITE_10 */
	N|TI_SEARCH|W,  /* N 2B  O         SCMD_LOCATE */
	                /* n            O  SCMD_POSITION_TO_ELEMENT */
	                /* n    O   OO O        SEEK_10 */
	N|TI_SEARCH|W,  /* N 2C V      O        ERASE_10 */
	I|TI_IO,        /* I 2D V   O  O        READ_UPDATED_BLOCK */
	O|TI_IO,        /* O 2E O   O  O        WRITE_AND_VERIFY_10 */
	N|TI_IO,        /* N 2F O   OO O        VERIFY_10 */
	O|TI_SEARCH,    /* O 30 O   OO O        SEARCH_DATA_HIGH_10 */
	O|TI_SEARCH,    /* O 31       O         OBJECT_POSITION */
	                /* o    O   OO O        SEARCH_DATA_EQUAL_10 */
	O|TI_SEARCH,    /* O 32 O   OO O        SEARCH_DATA_LOW_10 */
	N|TI_QUICK,     /* N 33 O   OO O        SET_LIMITS_10 */
	I|TI_QUICK,     /* I 34       O         GET_DATA_BUFFER_STATUS */
	                /* i    O   OO O        PREFETCH */
	                /* i     O         SCMD_READ_POSITION */
	N|TI_IO,        /* N 35 O   OO O        SYNCHRONIZE_CACHE */
	N|TI_IO,        /* N 36 O   OO O        LOCK_UNLOCK_CACHE */
	I|TI_IO,        /* I 37 O      O        READ_DEFECT_DATA_10 */
	O|TI_SEARCH,    /* O 38     O  O        MEDIUM_SCAN */
	O|TI_IO,        /* O 39 OOOOOOOO   SCMD_COMPARE */
	O|TI_SEARCH,    /* O 3A OOOOOOOO   SCMD_COPY_AND_VERIFY */
	O|TI_FLASH,     /* O 3B OOOOOOOOOO SCMD_WRITE_BUFFER */
	I|TI_QUICK,     /* I 3C OOOOOOOOOO SCMD_READ_BUFFER */
	O|TI_IO,        /* O 3D     O  O        UPDATE_BLOCK */
	I|TI_IO,        /* I 3E O   OO O        READ_LONG */
	O|TI_IO,        /* O 3F O   O  O        WRITE_LONG */
	O|TI_SHORT,     /* O 40 OOOOOOOOOO SCMD_CHANGE_DEFINITION */
	O|TI_IO,        /* O 41 O               WRITE_SAME */
	I|TI_IO,        /* I 42      O          READ_SUBCHANNEL */
	I|TI_IO,        /* I 43      O          READ_TOC */
	I|TI_IO,        /* I 44      O          READ_HEADER */
	                /* i     O         SCMD_REPORT_DENSITY_SUPPORT */
	N|TI_IO,        /* N 45      O          PLAY_AUDIO_10 */
	U,              /* U 46             */
	N|TI_IO,        /* B 47      O          PLAY_AUDIO_MSF */
	N|TI_IO,        /* N 48      O          PLAY_AUDIO_TRACK_INDEX */
	N|TI_IO,        /* N 49      O          PLAY_TRACK_RELATIVE_10 */
	U,              /* U 4A             */
	N|TI_IO,        /* N 4B      O          PAUSE_RESUME */
	O|TI_QUICK,     /* O 4C OOOOOOOOOO SCMD_LOG_SELECT */
	I|TI_QUICK,     /* I 4D OOOOOOOOOO SCMD_LOG_SENSE */
	U,              /* U 4E             */
	U,              /* U 4F             */
	U,              /* U 50             */
	U,              /* U 51             */
	U,              /* U 52             */
	U,              /* U 53             */
	U,              /* U 54             */
	O|TI_QUICK,     /* O 55 OOO OOOOOO SCMD_MODE_SELECT_10 */
	U,              /* U 56             */
	U,              /* U 57             */
	U,              /* U 58             */
	U,              /* U 59             */
	I|TI_QUICK,     /* I 5A OOO OOOOOO SCMD_MODE_SENSE_10 */
	U,              /* U 5B             */
	U,              /* U 5C             */
	U,              /* U 5D             */
	U,              /* U 5E             */
	U,              /* U 5F             */
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /*6-*/
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /*7-*/
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /*8-*/
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /*9-*/
	I|TI_QUICK,     /* U A0 OOOOOOOOOO SCMD_REPORT_LUNS */
	U,              /* U A1             */
	U,              /* U A2             */
	U,              /* U A3             */
	U,              /* U A4             */
	N|TI_CHANGE,    /* N A5         M  SCMD_MOVE_MEDIUM */
	                /* n          O         PLAY_AUDIO_12 */
	N|TI_CHANGE,    /* N A6         O  SCMD_EXCHANGE_MEDIUM */
	U,              /* U A7             */
	I|TI_IO,        /* I A8          O      GET_MESSAGE_12 */
	                /* i        OO O        READ_12 */
	N|TI_IO,        /* N A9      O          PLAY_TRACK_RELATIVE_12 */
	O|TI_IO,        /* O AA          O      SEND_MESSAGE_12 */
	                /* o        O  O        WRITE_12 */
	U,              /* U AB             */
	N|TI_SEARCH|W,  /* N AC        O        ERASE_12 */
	U,              /* U AD             */
	O|TI_IO,        /* O AE     O  O        WRITE_AND_VERIFY_12 */
	N|TI_IO,        /* N AF     OO O        VERIFY_12 */
	O|TI_SEARCH,    /* O B0     OO O        SEARCH_DATA_HIGH_12 */
	O|TI_SEARCH,    /* O B1     OO O        SEARCH_DATA_EQUAL_12 */
	O|TI_SEARCH,    /* O B2     OO O        SERACH_DATA_LOW_12 */
	N|TI_QUICK,     /* N B3     OO O        SET_LIMITS_12 */
	U,              /* U B4             */
	I|TI_QUICK,     /* I B5         O  SCMD_REQUEST_VOLUME_ELEMENT_STATUS */
	O|TI_IO,        /* O B6         O  SCMD_SEND_VOLUME_TAG */
	I|TI_IO,        /* I B7        O        READ_DEFECT_DATA_12 */
	I|TI_QUICK,     /* I B8         O  SCMD_READ_ELEMENT_STATUS */
	U,              /* U B9             */
	U,              /* U BA             */
	U,              /* U BB             */
	U,              /* U BC             */
	U,              /* U BD             */
	U,              /* U BE             */
	U,              /* U BF             */
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /*c-*/
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /*d-*/
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /*e-*/
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /*f-*/
};
#undef W
#undef N
#undef O
#undef I
#undef U

int ide_debug;
#define IDEF_ININIT 0x1
int ide_flags;
	/* Types of bus adapters for GetASPISupportInfo() to find */
#define BUSF_IDE        0x2
#define BUSF_SCSI       0x1
int ide_busflags= BUSF_SCSI | BUSF_IDE;
int ide_search_length= 2;       /* number of ports to search */
int ide_search_order[]= { 0, 1, 2, 3 };
int ide_phys_ports[] = { 0x1f0, 0x170, 0x1e8, 0x168 };
int ide_phys_irqs[]  = { 14, 15, 11, 10 };
int ide_enum_hano = -1; /* currently selected enumerated ide ha number */
int ide_enum_phys[4];
int ide_enum_ports[4];
int ide_enum_irqs[4];
char ide_enum_ha_type[4];
char ide_enum_dev_type[4][2];
int ide_ide_ha_count;
int ide_scsi_ha_count;

	/* bit masks for ide_enum_ha_type, ide_enum_dev_type */
#define ETF_INQUIRYWORKS 4
#define ETF_ATAPI       2
#define ETF_ATA         1

	/* current */
int (*ide_intr_handler)(void);
int ide_port;                           /* base IDE i/o port */
int ide_irq;
int ide_selectbyte;                     /* ide device select */
int ide_dma_in;                         /* Input/~Output 1/0=Read/Write */
int ide_dmachan;                        /* 0:none, 5,6,7 */
int ide_hastat;                         /* host adapter status */
int ide_targstat;                       /* target status */
unsigned char _far *ide_cdb_caddr;      /* address of cdb */
unsigned char _far *ide_sense_caddr;    /* address of sense */
unsigned char _far *ide_io_ptr;         /* i/o data pointer */
unsigned long ide_io_residual;          /* bytes remaining for transfer */
volatile int ide_intr_flags;            /* interrupt acknowledge */
int ide_intr_reason;                    /* interrupt reason */
int ide_intr_status;                    /* interrupt status */
int ide_spurious;
int ide_atapi_timer;                    /* system clock count down */
int ide_ata_timer;                      /* system clock count down */

	/* bit masks for ide_intr_flags */
#define INTRF_USER_ABORT        0x0040 /* abort srb received */
#define INTRF_IN_AUTOSENSE      0x0020 /* ERR bit set on atapi interrupt */
#define INTRF_IN_DSCSENSE       0x0010 /* retrieving request sense after DSC */
#define INTRF_IN_WAITDSC        0x0008 /* waiting for DSC to be set */
#define INTRF_DO_WAITDSC        0x0004 /* on completion, wait for DSC */
#define INTRF_TIMEOUT           0x0002 /* ata timeout */
#define INTRF_DONE              0x0001 /* ata interrupt */
#define INTRF_ALL (INTRF_USER_ABORT|INTRF_IN_AUTOSENSE|INTRF_IN_DSCSENSE \
		|INTRF_IN_WAITDSC|INTRF_DO_WAITDSC|INTRF_TIMEOUT|INTRF_DONE)

union srb_u {
	SRB_ExecSCSICmd    io;
	SRB_HAInquiry      hainq;
	SRB_GDEVBlock      gdt;
	SRB_Abort          abort;
	SRB_BusDeviceReset rst;
};

union srb_u _far *ide_srb_cur;
union srb_u _far *ide_srb_prv;

/*
 * ata command strings output in port order (1x1 to 1x7):
 * [0] = features
 * [1] = sct cnt
 * [2] = sct num
 * [3] = byte cnt lo
 * [4] = byte cnt hi
 * [5] = drv
 * [6] = cmd
 * [7] = pad
 */
	/* reset */
unsigned char ide_atacmd_reset[8] = { 0,0,0,0,0,0,8,0 };
	/* packet */
unsigned char ide_atacmd_packet[8] = { 0,0,0,0,2,0,0xa0,0 };
	/* general purpose */
unsigned char ide_atacmd_gp[8];
	/* request sense cdb */
unsigned char ide_cdb_requestsense[12] = { 3,0,0,0,0,0 };

	/* forward reference */
void _far ide_aspi(void _far *srbarg);
int ide_aspi_done(void _far *srbarg);
int ide_clkintr(void);

#define MY_INOUT 1
#define MY_SPL 1

	/* helpers */
#if MY_INOUT
#define INB(p)          my_inb(p)
#define OUTB(p,x)       my_outb(p,x)
#define INW(p)          my_inw(p)
#define OUTW(p,x)       my_outw(p,x)
#define IODELAY()
#endif

#if MY_SPL
#define SPL0()          my_enable()
#define SPLLPT()        my_disable()
#define SPLHI()         my_disable()
#define SPLX(x)         my_setpsw(x)
#endif

/*
 * my_inb() -- inputs a byte from a 16-bit port address
 * my_outb() -- outputs a byte to a 16-bit port address
 * my_inw() -- inputs a short from a 16-bit port address
 * my_outw() -- outputs a short to a 16-bit port address
 */
#if MY_INOUT
my_inb(int port)
{
	int value;

	_asm {
	mov dx, port
	xor ax, ax
	in al, dx
	mov value, ax
	}
	return (value);
}
#endif

#if MY_INOUT
my_outb(int port, int value)
{
	_asm {
	mov dx, port
	mov ax, value
	out dx, al
	}
	return (value);
}
#endif

#if MY_INOUT
my_inw(int port)
{
	int value;

	_asm {
	mov dx, port
	in ax, dx
	mov value, ax
	}
	return (value);
}
#endif

#if MY_INOUT
my_outw(int port, int value)
{
	_asm {
	mov dx, port
	mov ax, value
	out dx, ax
	}
	return (value);
}
#endif

/*
 * my_iodelay() -- flushes cpu instruction queue
 *
 * my_iodelay is called to delay (by flushing the cpu instruction
 * queue) to allow for recovery of certain controllers between
 * compiler generated "in" and "out" instructions.
 */
#if MY_INOUT
 void
my_iodelay(void)
{
}
#endif

/*
 * my_disable() -- disables interrupts and returns previous cpu status word
 * my_enable() -- enables interrupts and returns previous cpu status word
 * my_setpsw() -- sets cpu status word and returns previous cpu status word
 *
 * my_disable(), my_enable(), and my_setpsw() return the previous
 * processor status word.
 */
#if MY_SPL
my_disable(void)
{
	int psw;

	_asm {
	pushf
	cli             // disable
	pop psw
	}
	return (psw);
}
#endif

#if MY_SPL
my_enable(void)
{
	int psw;

	_asm {
	pushf
	sti             // enable
	pop psw
	}
	return (psw);
}
#endif

#if MY_SPL
my_setpsw(int psw)
{
	_asm {
	pushf           // get old status word
	push psw        // set new
	popf
	                // windows compatible interrupt flag restoration
	test psw, 200h  // set EI flag?
	jnz ei          //  y: -->
	cli
	jmp done
	}
ei:     _asm sti
done:
	_asm pop psw    // return old status word
	return (psw);
}
#endif

/*
 * my_eoi() -- sends end-of-interrupt command to interrupt controller
 *
 * The value 0 is returned if successful.
 * The value -1 is returned for an interrupt level greater than 15.
 */
my_eoi(
	int irq)        /* interrupt level 0-15 */
{
	if (irq >= 16)
		return (-1);
	if (irq >= 8) {
		/* Send specific EOI to slave pic */
		OUTB(0xa0, 0x60 | (irq & 7));
		IODELAY();
		irq= 2; /* cascade interrupt */
	}
	/* Send specific EOI to master pic */
	OUTB(0x20, 0x60 | irq);
	return (0);
}

/*
 * my_setimrbit() -- sets, clears, or gets state of interrupt mask reg bit
 *
 * state 1: sets imr bit (masks interrupt) for given irq and
 *      returns previous mask bit state
 * state 0: clears imr bit (unmasks interrupt) for given irq and
 *      returns previous mask bit state
 * state -1: returns current mask bit state for given irq
 */
my_setimrbit(
	int irq,        /* 0-16: hardware interrupt request (irq) line */
	int state)      /* 1:set; 0:clear; -1:do_not_modify */
{
	int mask;
	int imr;
	int imrsv;              /* saved */
	int port_imr;           /* port address of imr */
	int opl;

	if (irq >= 16)          /* validate irq */
		return (1);     /* return masked */

	mask= 1 << (irq & 7);
	/* find port address of programmable interrupt controller */
	port_imr= 0xa1;
	if (irq < 8)
		port_imr= 0x21;

	opl= SPLHI();
	imrsv= imr= INB(port_imr);      /* save previous state */
	imr |= mask;
	if (state >= 0) {
		if (state == 0)
			imr ^= mask;
		IODELAY();
		OUTB(port_imr, imr);
	}
	SPLX(opl);
	/* return previous mask state 0 or 1 */
	return ((imrsv >> (irq & 7)) & 1);
}

 void
my_delay_one(void)
{
}

my_delay_loop(int loops)
{
	while (loops--)
		my_delay_one();
	return (0);
}

/*
 * my_delay_1us() -- delays for 1 microseconds nominal
 */
int my_loops_1us= 5;    /* 1 uSec delay loop scaler */

my_delay_1us(void)
{
	return (my_delay_loop(my_loops_1us));
}

/*
 * my_t2count() -- returns timer 2 counts, for execution of my_delay_loop(n)
 *
 * The time it takes for my_delay_loop(n) do delay n iterations is 
 * measured using the system speaker timer.
 * The speaker timer is clocked from a 14.31818Mhz oscillator divided 
 * by 12.  Thus, the duration of each timer count is 0.838095344us 
 * in other words there are 1.193181667 counts/us.
 *
 * Because of the granularity of the timer and setup overhead,
 * a large number (1000) of loops is measured.
 *
 * loops/us = (1.193181667counts/us) * N / my_t2count(N)
 *
 * To find the number of loops for 10 us a fragment similar to:
 *
 * {
 *      ** find argument for my_delay_loop(n) to delay for 10us **
 *      int opl;
 *      int t2;
 *      int loops_10us;
 *
 *      ** disable interrupts **
 *      opl= SPLHI();
 *      ** subtract my_t2count(0) to account for overhead **
 *      t2= my_t2count(1000) - my_t2count(0);
 *      ** restore interrupts **
 *      SPLX(opl);
 *      ** avoid division by 0 **
 *      if (t2 < 1)
 *              t2= 1;
 *      ** find 10us delay loop scaler **
 *      my_loops_10us= (11932 + t2 - 1)/t2 + 1;
 *
 *      ** execute a 10us delay **
 *      my_delay_loop(my_loops_10us);
 * }
 *
 */
my_t2count(
	int loops)      /* number of my_delay_loop() iterations to time */
{
	int count;
	int portb;

	/* Disable 8253 timer 2 gate (via 8255) */
	portb= INB(0x61) & ~1;
	IODELAY();
	OUTB(0x61, portb);
	IODELAY();
	/* Select mode 2 for timer 2 */
	OUTB(0x43, 0xb4);
	IODELAY();
	/* Divide by 64536 */
	OUTB(0x42, 0);
	IODELAY();
	OUTB(0x42, 0);
	IODELAY();
	/* Open gate to start timer 2 */
	OUTB(0x61, portb | 1);
	my_delay_loop(loops);
	/* Close gate to stop timer 2 */
	OUTB(0x61, portb);
	IODELAY();
	/* read down count */
	count= INB(0x42);
	IODELAY();
	count |= INB(0x42) << 8;
	/* convert to up count */
	return (-count & 0xffff);
}

my_delay_us_init(void)
{
	static once= 0;
	int t2;
	int opl;

	if (once == 0) {
		once++;
		/*
		 * Find
		 *  1 uSec nominal delay loop scaler for timing
		 *  10 uSec nominal delay loop scaler for user reference
		 */
		opl= SPLHI();
		t2= my_t2count(1000) - my_t2count(0);
		SPLX(opl);
		if (t2 < 1)
			t2= 1;
		/* find 1 us delay loop scaler */
		my_loops_1us= ((1193 + t2 - 1)/t2) + 1;
	}
	return (0);
}

	/*
	 * DMAREG -- io address of dma controller registers for a channel
	 */
struct dmareg {
	int dr_page;    /* paddr bits 23-16 (8-bit); 23-17 (16-bit) */
	int dr_paddr;   /* paddr bits 15-0 (8-bit); 16-1 (16-bit) */
	int dr_count;
	int dr_mask;
	int dr_mode;
	int dr_flop;
};

struct dmareg my_dmareg[]= {
	/*0*/ { 0x87, 0x00, 0x01, 0x0a, 0x0b, 0x0c },
	/*1*/ { 0x83, 0x02, 0x03, 0x0a, 0x0b, 0x0c },
	/*2*/ { 0x81, 0x04, 0x05, 0x0a, 0x0b, 0x0c },
	/*3*/ { 0x82, 0x06, 0x07, 0x0a, 0x0b, 0x0c },
	/*4*/ { 0x8f, 0xc0, 0xc2, 0xd4, 0xd6, 0xd8 }, /* cascade */
	/*5*/ { 0x8b, 0xc4, 0xc6, 0xd4, 0xd6, 0xd8 },
	/*6*/ { 0x89, 0xc8, 0xca, 0xd4, 0xd6, 0xd8 },
	/*7*/ { 0x8a, 0xcc, 0xce, 0xd4, 0xd6, 0xd8 },
};

int my_dmachan;
int my_nodemanddma;

my_dmamask(void)
{
	if ((unsigned)my_dmachan-1  >= 8-1)
		return (-1);
#ifdef IS_UCHANNEL
	if (IS_UCHANNEL())
		/* Micro channel: Set channel mask (function 9) */
		OUTB(0x18/*FUNC*/, 0x90 | my_dmachan);
	else
#endif
		OUTB(my_dmareg[ my_dmachan ].dr_mask,
			4 | (my_dmachan & 3));
	return (0);
}

/*
 * my_dmaprog() -- sets up dma controller for data transfer
 *
 * The value 0 is returned if the dma setup was performed;
 * Otherwise, the value -1 is returned which indicates the
 * request crosses a dma page boundary and no dma setup
 * has been performed.
 */
	/* Values for dmamode argument of my_dmaprog() */
#define DMAMODE_VERIFY  0       /* Verify, no transfer */
#define DMAMODE_RDCHAN  1       /* Read channel, write memory */
#define DMAMODE_WRCHAN  2       /* Read memory, write channel */

my_dmaprog(
	int dmamode,            /* DMA controller mode (DMAMODE_*) */
	unsigned long paddr,    /* physical transfer address */
	unsigned count)         /* number of bytes to transfer */
{
	unsigned addr16;        /* low 16 bits of physical address */
	unsigned page;          /* high 16 bits of physical address */
	int mode;               /* ISA mode */
	int xmode;              /* EISA extended mode */

	if ((unsigned)my_dmachan-1  >= 8-1)
		return (-1);
	addr16= (unsigned)paddr & 0xffff;
	page= (unsigned)(paddr >> 16) & 0xffff;
	count--;        /* dma controller uses count - 1 */

#ifdef IS_EISA
	if (!IS_EISA())
#endif
	if (page & 0xff00)
		return (-1);    /* above 16 MB */
#ifdef IS_UCHANNEL
	if (!IS_UCHANNEL())
#endif
	{
		/* [E]ISA */
		/*
		 * Translate dmamode to 8237 dma mode
		 *      DMAMODE_VERIFY  0 -> 0 Verify, no transfer
		 *      DMAMODE_RDCHAN  1 -> 4 Read channel, write memory
		 *      DMAMODE_WRCHAN  2 -> 8 Read memory, write channel
		 */
		mode= xmode= my_dmachan & 3;
		mode |= dmamode << 2;
		if (my_nodemanddma > 0)
			mode |= 0x40;           /* Single mode */
		if (my_dmachan >= 4) {
			/* 16-bit dma: check for odd address or count */
			if ((addr16 | count) & 1)
				return (-1);
			/* program 8237 16-bit word aligned address */
			addr16= (unsigned)(paddr >> 1);
			/*addr16= (page << 15) | (addr16 >> 1);*/
			page &= ~1;
			count >>= 1;
			xmode |= 4;     /* count by 16-bit word */
		}
#ifdef IS_EISA
		if (!IS_EISA())
#endif
		if (dmamode && ((addr16+count)&0xffff) < addr16)
			return (-1);    /* crosses dma page boundary */
	}
#ifdef IS_UCHANNEL
	if (IS_UCHANNEL()) {
		/* Micro channel dma modes */
		static char uchan_dmamodes[]= {
			0x00,   /* 0 DMAMODE_VERIFY - Verify, no transfer */
			0x0c,   /* 1 DMAMODE_RDCHAN - Read channel, write mem */
			0x04    /* 2 DMAMODE_WRCHAN - Read memory, write chan */
		};

		/* Micro channel DMA controller setup */

		/* Set mode register (function 7) */
		OUTB(0x18/*FUNC*/, 0x70 | my_dmachan);
		IODELAY();
		OUTB(0x1a/*DATA*/, uchan_dmamodes[ dmamode ]);
		IODELAY();

		/* Set transfer address registers (function 2) */
		OUTB(0x18/*FUNC*/, 0x20 | my_dmachan);
		IODELAY();
		OUTB(0x1a/*DATA*/, addr16);             /* lo byte */
		IODELAY();
		OUTB(0x1a/*DATA*/, addr16 >> 8);        /* mid byte */
		IODELAY();
		OUTB(0x1a/*DATA*/, page);               /* hi byte */
		IODELAY();

		/* Set transfer count registers (function 4) */
		OUTB(0x18/*FUNC*/, 0x40 | my_dmachan);
		IODELAY();
		OUTB(0x1a/*DATA*/, count);              /* lo byte */
		IODELAY();
		OUTB(0x1a/*DATA*/, count >> 8);         /* hi byte */
		IODELAY();

		/* Clear channel mask (function a) */
		OUTB(0x18/*FUNC*/, 0xa0 | my_dmachan);
	} else
#endif
	{
		/* 8237 DMA setup */
		register struct dmareg *dp= &my_dmareg[ my_dmachan ];

		OUTB(dp->dr_mode, mode);        /* mode byte */
		IODELAY();
		OUTB(dp->dr_flop, 0);           /* clear 1st/last flop */
		IODELAY();
		OUTB(dp->dr_paddr, addr16);     /* lo byte of addr */
		IODELAY();
		OUTB(dp->dr_paddr, addr16 >> 8);/* hi byte */
		IODELAY();
		OUTB(dp->dr_count, count);      /* lo byte of count */
		IODELAY();
		OUTB(dp->dr_count, count >> 8); /* hi byte */
		IODELAY();
		OUTB(dp->dr_page, page);
#ifdef IS_EISA
		if (IS_EISA()) {
			IODELAY();      /* high page segment */
			OUTB(0x400 + dp->dr_page, page >> 8);
			IODELAY();      /* extended mode byte */
			OUTB(0x400 + dp->dr_mode, xmode);
		}
#endif
		IODELAY();
		OUTB(dp->dr_mask, my_dmachan & 3);      /* clear mask */
	}
	return (0);
}

/*
 * dos_setivec() -- sets an interrupt vector and returns the previous handler
 *
 * If NULL is given for 'handler', the current vector is returned.
 */
 int (_interrupt _far *
dos_setivec(
	int vecno,
	int (_interrupt _far *handler)(void)
	))(void)
{
	int (_interrupt _far *ohandler)(void);

	_asm {
	mov ax, 3300h           // dl= get_ctl_C_trapping();
	int 21h
	mov cx, dx              // save
	mov dl, 0               // 0=disable ctl C trapping
	mov ax, 3301h           // set_ctl_C_trapping(dl);
	int 21h
	mov ax, vecno           // vector number
	mov ah, 35h             // es:bx= get_vector(al);
	int 21h
	push ds
	lds dx, handler         // address of new handler
	mov ax, ds              // NULL new handler?
	or ax, dx
	jz iv1                  //  y: -->
	mov ax, vecno           // vector number
	mov ah, 25h             // set_vector(al, ds:dx);
	int 21h
	}
iv1:    _asm {
	pop ds
	mov dx, cx              // restore saved ctl C trapping
	mov ax, 3301h           // set_ctl_C_trapping(dl);
	int 21h
	mov word ptr ohandler + 2, es   // old handler segment
	mov word ptr ohandler, bx       // old handler offset
	}
	return (ohandler);
}


#define STACK_SIZE      1024
char my_stack[ STACK_SIZE ];
int my_save_ss;
int my_save_sp;

#define HZ              18
#define INT_CLK         0x1c
int (_interrupt _far *Clk_sv_ivec)(void); /* Saved clock interrupt vector */
int (*Clk_handler)(void)= ide_clkintr;

 int _interrupt _far
clk_dispatch(void)      /* clock interrupt dispatcher */
{
	(*Clk_sv_ivec)();
	_asm {
	mov my_save_ss, ss
	mov my_save_sp, sp
	push ds
	pop ss
	mov sp, offset (my_stack + STACK_SIZE)
	}
	/* Call current handler (NOTE SS != DS) */
	if (Clk_handler)
		(*Clk_handler)();
	_asm {
	mov ss, my_save_ss
	mov sp, my_save_sp
	}
	return (0);
}

int ide_hooked_irq= -1; /* Irq */
int ide_sv_imr;         /* Saved imr bit state */
int (_interrupt _far *ide_sv_ivec)(void); /* Saved interrupt dispatcher */

 int _interrupt _far
ide_dispatch(void)      /* printer port interrupt dispatcher */
{
	_asm {
	mov my_save_ss, ss
	mov my_save_sp, sp
	push ds
	pop ss
	mov sp, offset (my_stack + STACK_SIZE)
	}
	if (ide_intr_handler)
		(*ide_intr_handler)();
	else
		(*ide_sv_ivec)();
	my_eoi(ide_hooked_irq);
	_asm {
	mov ss, my_save_ss
	mov sp, my_save_sp
	}
	return (0);
}

ide_hook(       /* restores previous dispatcher; hooks in new dispatcher */
	int irq)        /* 0-15 or otherwise unhooked's only */
{
	/* remove clock interrupt handler */
	if (Clk_sv_ivec) {
		dos_setivec(INT_CLK, Clk_sv_ivec);
		Clk_sv_ivec= NULL;
	}
	/* restore previous dispatcher */
	if ((unsigned)ide_hooked_irq < 16) {
		int ivecno;

		/* convert irq to interrupt vector number */
		ivecno= 8;
		if (ide_hooked_irq >= 8)
			ivecno= 0x70;
		ivecno += ide_hooked_irq & 7;

		/* mask interrupts */
		my_setimrbit(ide_hooked_irq, 1);
		dos_setivec(ivecno, ide_sv_ivec);
		/* clear unserviced interrupt */
		my_eoi(ide_hooked_irq);
		/* restore previous dispatcher's interrupt mask state */
		my_setimrbit(ide_hooked_irq, ide_sv_imr);
		ide_hooked_irq = -1;
	}
	if ((unsigned)irq < 16) {
		int ivecno;

		/* convert irq to interrupt vector number */
		ivecno= 8;
		if (irq >= 8)
			ivecno= 0x70;
		ivecno += irq & 7;

		/* save old imr state and mask interrupts */
		ide_sv_imr= my_setimrbit(irq, 1);
		ide_hooked_irq= irq;
		ide_sv_ivec= dos_setivec(ivecno, ide_dispatch);
		/* clear unserviced interrupt */
		my_eoi(irq);
		/* unmask interrupts */
		my_setimrbit(irq, 0);

		/* Install clock interrupt handler */
		/* System clock rate freq: 18.2065 Hz = 1.19318 MHz / 65536 */
		Clk_sv_ivec= dos_setivec(INT_CLK, NULL);
		dos_setivec(INT_CLK, clk_dispatch);
		return (0);
	}
	return (-1);
}

ide_ckabort(void)       /* check for control-c */
{
	_asm {
	mov ah, 0bh     // check for character w/ control-c check
	int 21h
	}

	if (ide_intr_flags & INTRF_USER_ABORT)
		return (1);
	return (0);
}

 void
ide_datain(
	void _far *caddr,
	int count) /* bytes to xfer (assumed to be an even count) */
{
#if KOMMENT
	short _far *p= (short _far *)caddr;

	for (count>>=1; count; count--)
		*p++= INW(ide_port);
#endif
	_asm {
	mov dx, ide_port
	mov cx, count
	shr cx, 1
	les di, caddr
	rep insw
	}
}

 void
ide_dataout(
	void _far *caddr,
	int count) /* bytes to xfer (assumed to be an even count) */
{
#if KOMMENT
	short _far *p= (short _far *)caddr;

	for (count>>=1; count; count--)
		OUTW(ide_port, *p++);
#endif
	_asm {
	mov dx, ide_port
	mov cx, count
	shr cx, 1
	push ds
	lds si, caddr
	rep outsw
	pop ds
	}
}

 void
ide_dumpin(
	int count) /* bytes to xfer (assumed to be an even count) */
{
#if KOMMENT
	for ((unsigned)count>>=1; count; count--)
		INW(ide_port);
#endif
	_asm {
	mov dx, ide_port
	mov cx, count
	shr cx, 1
	jz dmpi1
	}
dmpi0:  _asm {
	in ax, dx
	loop dmpi0

	}
dmpi1:;
}

 void
ide_dumpout(
	int count) /* bytes to xfer (assumed to be an even count) */
{
#if KOMMENT
	for ((unsigned)count>>=1; count; count--)
		OUTW(ide_port, 0);
#endif
	_asm {
	mov dx, ide_port
	mov cx, count
	shr cx, 1
	jz dmpo1
	xor ax, ax
	}
dmpo0:  _asm {
	out dx, ax
	loop dmpo0
	}
dmpo1:;
}

/*
 * ide_dmachannel   dmachan = 0|5|6|7, for DMA channels none|5|6|7
 */
ide_dmachannel(int dmachan)
{
	int old_dmachan= ide_dmachan;

	if (dmachan >= 0) {
		if ((dmachan>=1 && dmachan<=3) || dmachan>=8)
			return (-1);    /* invalid channel */
		ide_dmachan = dmachan;  /* allow 0|5|6|7 for none|5|6|7 */
		if (dmachan == 0)
			dmachan= -1;
		my_dmachan = dmachan;
	}
	return (old_dmachan);
}

 void
ide_dmatransfer(
	void _far *caddr,       /* ptr to buffer */
	int count,              /* bytes to xfer */
	int isinput)            /* 1=input 0=output  */
{
	if ((unsigned)count < 2)
		return; /* 0 word transfer */

	my_dmaprog(
		(isinput) ?DMAMODE_RDCHAN :DMAMODE_WRCHAN,
		my_20addr(caddr),
		count
	);
}

 void
ide_atacmdout(unsigned char *cmd) /* Output ATA command */
{
	int i;

	for (i=1; i<8; i++)
		OUTB(ide_port+i, *cmd++);
}

ide_waitdrq(void)       /* wait for DRQ */
{
	int us= 10;

	for (;;) {
		if ((INB(ide_port+IREG_ALTSTAT) & (ISBSY|ISDRQ)) == ISDRQ)
			break;
		if (us-- == 0)
			return (-1);    /* timeout */
		my_delay_1us();
	}
	return (0);     /* drq is set */
}

ide_bug_inb(char *caller, int port)
{
	int val;

	val= INB(port);
	if (ide_debug)
		IDEPRF(("%s: inb(0x%x) = 0x%02x\n", caller, port, val));
	return (val);
}

ide_bug_outb(char *caller, int port, int val)
{
	if (ide_debug)
		IDEPRF(("%s: outb(0x%x, 0x%02x)\n", caller, port, val));
	return (OUTB(port, val));
}

ide_ha_present(int port)
{
	static char me[]= "ide_ha_present";
	static char *targnm[]= { "Master", "Slave" };
	int targid;

	/* See if ATA[PI] device responds */
	ide_bug_outb(me, port+IREG_DEVHD, 0xaa);
	if (ide_bug_inb(me, port+IREG_DEVHD) == 0xaa) {
		ide_bug_outb(me, port+IREG_DEVHD, 0xa5);
		if (ide_bug_inb(me, port+IREG_DEVHD) == 0xa5) {
			if (ide_debug)
				IDEPRF(("%s(0x%x): Standard ATA 0xaa 0xa5 response\n", me, port));
			return (0);
		}
	}
	/*
	 * Controllers having only ATAPI devices attached
	 * may not respond to the AA, A5 identification patterns. 
	 *
	 * However, avoid issuing the soft reset (or EXECUTE DEVICE DIAGNOSTIC) 
	 * required to get devices to show their signatures --
	 *    Signatures for: ATA    ATAPI
	 *    Sector Count    0x01   0x01
	 *    Sector Number   0x01   0x01
	 *    Cylinder Low    0x00   0x14
	 *    Cylinder High   0x00   0xeb
	 *    Device/Head     0x00   0x00 or 0x10
	 *
	 * Rather let the caller determine attached device 
	 * using ide_identify()
	 */
	for (targid=0; targid<2; targid++) {
		/* 0xa0=master; 0xb0=slave */
		int selectbyte = 0xa0 | ((targid&1)<<4);
		int sig;

		/* Select device */
		ide_bug_outb(me, port+IREG_DEVHD, selectbyte);

		/* Look for ATAPI signature */
		sig= (ide_bug_inb(me, port+IREG_CNTHI)<<8)
				+ ide_bug_inb(me, port+IREG_CNTLO);
		if (ide_bug_inb(me, port+IREG_DEVHD) == selectbyte
		 || sig == 0xeb14) {
			if (ide_debug)
				IDEPRF(("%s(0x%x): ATAPI %s found\n", me, port, targnm[targid]));
			return (0);
		}
	}
	if (ide_debug)
		IDEPRF(("%s(0x%x): Adapter not found\n", me, port));
	return (-1);
}

ide_ataintr(void)               /* ATA Interrupt handler */
{
	unsigned io_transfer;   /* bytes to transfer */
	unsigned io_dump;       /* bytes for bit bucket */

	ide_intr_status = INB(ide_port+IREG_STAT);      /* get status, clear intr */
	if ((ide_intr_status & (ISBSY|ISDRQ)) == ISDRQ) {
		ide_ata_timer= 0;
		/* get transfer count for IDENTIFY */
		/*io_transfer = (INB(ide_port+IREG_CNTHI)<<8) + INB(ide_port+IREG_CNTLO);*/
		io_transfer=512;
		io_dump = 0;
		if ((unsigned long)io_transfer > ide_io_residual) {
			io_dump = io_transfer - (unsigned)ide_io_residual;
			if (io_dump & 1)
				io_dump++;
			io_transfer = (unsigned)ide_io_residual;
		}
		ide_datain(ide_io_ptr, io_transfer);
		ide_io_ptr = my_canon(ide_io_ptr + io_transfer);
		ide_io_residual -= io_transfer;
		if (io_dump)
			ide_dumpin(io_dump);
		ide_intr_flags |= INTRF_DONE;
	}
	return (0);
}

 void
ide_atapi_done(int hastat, int targstat)
{
	/* call with interrupts disabled */
	union srb_u _far *srb;
	int sv_flags;

	ide_io_residual= 0;
	ide_atapi_timer= 0;
	sv_flags= ide_intr_flags;
	ide_intr_flags &= ~(INTRF_IN_AUTOSENSE|INTRF_IN_DSCSENSE
				|INTRF_IN_WAITDSC|INTRF_DO_WAITDSC);
	ide_intr_handler= NULL;
	my_dmamask();
	if ((srb= ide_srb_cur) != NULL) {
		ide_srb_cur= NULL;
		ide_srb_prv= srb;

		/*
		 * For WAITDSC, set target status based on
		 * sense error code in byte 0,
		 * and sense key byte (byte 2): Fmark,EOM,ILI,Key
		 */
		if ((sv_flags & INTRF_IN_DSCSENSE)
		 && (ide_sense_caddr[0] & 0x70) == 0x70
		 && ide_sense_caddr[2])
			targstat = 0x02; /* SSTAT_CHECK_CONDITION */
		if (targstat) {
			srb->io.SRB_TargStat = (char)targstat;
			srb->io.SRB_Status= SS_ERR;
		}
		if (hastat) {
			srb->io.SRB_HaStat = (char)hastat;
			srb->io.SRB_Status= SS_ERR;
		}
		ide_aspi_done(srb);
	}
}

ide_sense(      /* starts request sense */
	int senseflags) /* either INTRF_IN_AUTOSENSE or INTRF_IN_DSCSENSE */
{
	ide_intr_flags &= ~(INTRF_IN_WAITDSC|INTRF_DO_WAITDSC);
	if (ide_cdb_requestsense[4] == 0)
		return (-1);    /* auto/dsc sense not started */
	if (ide_intr_flags & (INTRF_IN_AUTOSENSE | INTRF_IN_DSCSENSE)) {
		ide_hastat= HASTAT_REQUEST_SENSE_FAILED;
		return (-1);    /* auto/dsc sense not started */
	}
	/* set a short timer for autosense */
	ide_atapi_timer= ide_cdb_seconds[ TI_QUICK ] * HZ;
	/* request sense is non-dma */
	ide_atacmd_packet[0] = 0;
	/* set address of sense data buffer */
	ide_io_ptr = ide_sense_caddr;
	/* size is senze size */
	ide_io_residual = ide_cdb_requestsense[4];
	/* issue ATA packet cmd */
	ide_atacmdout(ide_atacmd_packet);
	if (ide_waitdrq()) {
		ide_hastat= HASTAT_REQUEST_SENSE_FAILED;
		return (-1);    /* auto/dsc sense not started */
	}
	/* set either INTRF_IN_AUTOSENSE or INTRF_IN_DSCSENSE */
	ide_intr_flags |= senseflags;
	/* output request sense cdb */
	ide_dataout(ide_cdb_requestsense, 12);
	return (0); /* auto/dsc sense started */
}

ide_atapiintr(void) /* ATAPI Interrupt handler */
{
	unsigned io_transfer;   /* bytes to transfer */
	unsigned io_dump;       /* bytes for bit bucket */

	if (INB(ide_port+IREG_ALTSTAT) & ISBSY) { /* don't clear interrupt */
		ide_spurious++;
		return (0);
	}
	ide_intr_status = INB(ide_port+IREG_STAT); /* get status, clear intr */
	ide_intr_reason = INB(ide_port+IREG_REASON); /* get interrupt reason */
	if (ide_intr_status & ISBSY) {
		ide_spurious++;
		return (0);
	}
	switch (ide_intr_reason) {
	case 1: /* TAG=0 REL=0 I/O=0 C/D=1 output cmd */
		return (0);
	case 0: /* TAG=0 REL=0 I/O=0 C/D=0 output data */
	case 2: /* TAG=0 REL=0 I/O=1 C/D=0 input data */
		/* get transfer count */
		io_transfer = 0;
		io_dump = 0;
		if (ide_intr_status & ISDRQ)
			io_transfer = (INB(ide_port+IREG_CNTHI)<<8)
					+ INB(ide_port+IREG_CNTLO);
		if ((unsigned long)io_transfer > ide_io_residual) {
			io_dump = io_transfer - (unsigned)ide_io_residual;
			if (io_dump & 1)
				io_dump++;
			io_transfer = (unsigned)ide_io_residual;
		}
		if (ide_intr_reason == 0) {
			ide_dataout(ide_io_ptr, io_transfer);
			if (io_dump)
				ide_dumpout(io_dump);
		} else {
			ide_datain(ide_io_ptr, io_transfer);
			if (io_dump)
				ide_dumpin(io_dump);
		}
		ide_io_ptr = my_canon(ide_io_ptr + io_transfer);
		ide_io_residual -= io_transfer;
		return (0);
	case 3: /* TAG=0 REL=0 I/O=1 C/D=1 input cmd == done */
		my_dmamask();
		if (ide_intr_status & ISERR) { /* if error */
			ide_targstat = 0x02; /* SSTAT_CHECK_CONDITION */
			if (ide_sense(INTRF_IN_AUTOSENSE) == 0)
				return (0); /* auto sense started */
		} else if (ide_intr_flags & INTRF_DO_WAITDSC) {
			/* wait for drive to set DSC */
			ide_intr_flags &= ~INTRF_DO_WAITDSC;
			ide_intr_flags |= INTRF_IN_WAITDSC;
			return (0);
		}
		break;
	default: /* error */
		ide_hastat= HASTAT_PHASE_ERR;
		break;
	}
	ide_atapi_done(ide_hastat, ide_targstat);
	return (0);
}

ide_clkintr(void)       /* clock interrupt handler */
{
	if (ide_intr_flags & INTRF_IN_WAITDSC) {
		int stat= INB(ide_port+IREG_ALTSTAT);

		/* Poll DSC bit to see if motion command complete */
		if (stat & ISDSC) {
			ide_intr_flags &= ~INTRF_IN_WAITDSC;
			if (ide_sense(INTRF_IN_DSCSENSE))
				ide_atapi_done(ide_hastat, ide_targstat);
		}
	}
	if (ide_atapi_timer && --ide_atapi_timer == 0) {
		ide_atapi_done(
			(ide_intr_flags & (INTRF_IN_AUTOSENSE|INTRF_IN_DSCSENSE))
			?HASTAT_REQUEST_SENSE_FAILED :HASTAT_TIMEOUT,
			0
		);
	}
	if (ide_ata_timer && --ide_ata_timer == 0) {
		ide_intr_flags |= INTRF_TIMEOUT;
		ide_intr_handler= NULL;
	}
	return (0);
}

ide_wait_ataintr(       /* call with interrupts disabled */
	int intrflags,
	int ticks)
{
	int opl;

	ide_intr_flags &= ~INTRF_ALL;
	if ((ide_ata_timer= ticks) != 0)
		intrflags |= INTRF_TIMEOUT;
	ide_intr_handler= ide_ataintr;
	opl= SPL0();
	/* wait for interrupt handler to set acknowledge flag */
	while (!(ide_intr_flags & intrflags))
		ide_ckabort();  /* check for abort */
	SPLX(opl);
	intrflags &= ide_intr_flags;
	ide_intr_handler= NULL;
	ide_ata_timer= 0;
	if (intrflags & INTRF_TIMEOUT)
		return (-1);    /* interrupt timeout */
	if (intrflags & INTRF_USER_ABORT)
		return (1);
	return(0);
}

ide_select(int ideha, int targid)
{
	/* set interrupt handler to catch suprious interrupts */
	ide_io_residual= 0;
	ide_intr_handler = ide_ataintr;

	if (targid < 0)
		ideha= -1;
	if (ideha < 0) {
		if (ide_enum_hano != -1) {
			ide_hook(-1);
			ide_intr_handler = NULL;
			ide_enum_hano = -1;
		}
	} else {
		/*
		 * if ha changed changed, set new port, irq, 
		 * and change hooked vector
		 */
		if (ide_enum_hano != ideha) {
			/*
			 * if new ha has a disk, flush disk buffers first
			 * (e.g., smartdrv write behind)
			 */
			if (ide_enum_ha_type[ ideha ] & ETF_ATA)
				dos_diskreset();
			ide_port = ide_enum_ports[ideha];
			ide_irq = ide_enum_irqs[ideha];
			ide_hook(ide_irq);
			ide_enum_hano= ideha;
		}
		ide_selectbyte = 0xa0 | ((targid&1)<<4);
	}
	return (0);
}

unsigned char ide_identbuf[512];

ide_identify(
	int ideha,      /* index in ide_enum_* */
	int targid,     /* 0 or 1 */
	int identop)    /* 0xec:identify device; 0xa1:identify packet device */
{
	static char me[]= "ide_identify";
	int opl;
	int rc;
	int altstat;

	memset(ide_atacmd_gp, 0, sizeof(ide_atacmd_gp));
	memset(ide_identbuf, 0, sizeof(ide_identbuf));
	ide_select(ideha, targid);
	opl= SPLHI();
	ide_intr_flags &= ~(INTRF_TIMEOUT|INTRF_DONE);
	ide_io_ptr = ide_identbuf;
	ide_io_residual = sizeof(ide_identbuf);
	ide_atacmd_gp[6]= (char)identop;
	ide_atacmd_gp[5] = (char)ide_selectbyte;
	ide_atacmdout(ide_atacmd_gp);
	/* wait for interrupt */
	rc= ide_wait_ataintr(INTRF_DONE|INTRF_TIMEOUT, 2);
	if (rc == 0)
		altstat= INB(ide_port+IREG_ALTSTAT);
	SPLX(opl);
	if (rc) {
		if (ide_debug)
			IDEPRF(("%s(ha=%d, targ=%d, identop=0x%x): Interrupt timeout\n",
				me, ideha, targid, identop));
		return (-1);    /* interrupt timeout */
	}
	/* Liton CD-ROM returns 0xd0 status */
	if ((altstat & (ISDRDY|ISDF|ISDRQ|ISERR)) != ISDRDY) {
		if (ide_debug)
			IDEPRF(("%s(ha=%d, targ=%d, identop=0x%x): stat=0x%02x altstat=0x%02x: Command failed\n",
				me, ideha, targid, identop, INB(ide_port+IREG_STAT), altstat));
		return (-1);    /* command failed */
	}
	return (0);
}

unsigned char ide_inqdata[4];

ide_inquire(int hano, int targid, int lun)
{
	SRB_ExecSCSICmd srbbuf;
	SRB_ExecSCSICmd *io= &srbbuf;

	memset(io, 0, sizeof(*io));
	memset(ide_inqdata, 0, sizeof(ide_inqdata));
	io->SRB_Cmd= SC_EXEC_SCSI_CMD;
	io->SRB_HaId= (char)(hano + ide_scsi_ha_count); /* w host adapter number */
	io->SRB_Target= (char)targid;           /* w scsi device address */
	io->SRB_Lun= (char)lun;                 /* w logical unit number */
	io->SRB_BufLen= sizeof(ide_inqdata);    /* w data buffer length */
	io->SRB_SenseLen= sizeof(io->SenseArea);/* w sense buffer allocation */
	io->SRB_BufPointer= ide_inqdata;        /* w data buffer address */
	io->SRB_CDBLen= 6;
	io->CDBByte[0]= 0x12;   /* INQUIRY */
	io->CDBByte[4]= 4;      /* allocation length */
	ide_aspi(io);
	while (!(volatile unsigned char)io->SRB_Status)
		ide_ckabort();
	return ((unsigned)io->SRB_Status);
}

ide_aspi_done(void _far *srbarg)
{
	union srb_u _far *srb= (union srb_u _far *)srbarg;

	if (srb->io.SRB_Status == SS_PENDING /*0*/)
		srb->io.SRB_Status= SS_COMP;    /* success */
	switch (srb->io.SRB_Cmd) {
	case SC_EXEC_SCSI_CMD:
	case SC_RESET_DEV:
		if (ide_intr_flags & INTRF_USER_ABORT)
			srb->io.SRB_Status= SS_ABORTED;
		else {
			if (srb->io.SRB_Flags & SRB_POSTING
			 && srb->io.SRB_PostProc)
				srb->io.SRB_PostProc(&srb->io);
		}
	}
	return (0);
}

ide_mkinquiry(unsigned char _far *d, int dlen, int pdt)
{
	unsigned char *p;

	_fmemset(d, 0, dlen);
	/* swap bytes */
	for (p=&ide_identbuf[54]; p<&ide_identbuf[54+28]; p+=2) {
		char tmp= p[0];
		p[0]= p[1];
		p[1]= tmp;
	}
	if ((unsigned)dlen > 8)
		dlen -= 8;
	if ((unsigned)dlen > 28)
		dlen= 28;
	/* Peripheral device type */
	d[0]= (char)(pdt & 0x1f);
	/* Available inquiry length */
	d[4]= (char)(3+28);
	/* Copy inquiry text */
	_fmemcpy(&d[8], &ide_identbuf[54], dlen);
	return (0);
}

/*
 * ide_aspi IDE ASPI Compatable Driver
 *
 * Assumes DMA buffers don't wrap
 */
 void _far
ide_aspi(void _far *srbarg)
{
	union srb_u _far *srb= (union srb_u _far *)srbarg;
	int ideha;
	int opl;
	int cdbbyte0;
	int cdbtype;
	int len;
	int identop;
	int enumtype;
#if ATAEXT
	int i;
	int rc;
#endif

	/* zero initial status */
	srb->io.SRB_Status= SS_PENDING; /*0*/
	switch (srb->io.SRB_Cmd) {
	case SC_EXEC_SCSI_CMD:
	case SC_RESET_DEV:
#if ATAEXT
	case SC_ATA_PACKET_IDENTIFY:
	case SC_ATA_RESET:
	case SC_ATA_READ_PORTS:
	case SC_ATA_WRITE_PORTS:
#endif
		srb->io.SRB_HaStat= HASTAT_OK; /*0*/ /* r HASTAT_* values */
		srb->io.SRB_TargStat= 0;        /* r SSTAT_* values */
		break;
	}

	/* check for valid host adapter */
	ideha= srb->io.SRB_HaId - ide_scsi_ha_count;
	if ((unsigned)ideha >= (unsigned)ide_ide_ha_count) {
		srb->io.SRB_Status= SS_INVALID_HA;
		goto local_srb_done;
	}

	switch (srb->io.SRB_Cmd) {
	case SC_HA_INQUIRY:
		srb->hainq.HA_Count = (char)(ide_scsi_ha_count + ide_ide_ha_count);
		srb->hainq.HA_SCSI_ID = 7;
		/* set HA_ManagerId[16] and HA_Identifier[16] */
		_fmemcpy(
			srb->hainq.HA_ManagerId,
		/*       01234567890123456789012345678901 */
#if __TAPEDRV__
			"TAPEDRV ATAPIMGRIDE/ATAPI(?)    ",
#else
			"BUILTIN ATAPIMGRIDE             ",
#endif
			32
		);
		if ((unsigned)ideha < 4) {
			int physno= ide_enum_phys[ ideha ];
#if __TAPEDRV__
			srb->hainq.HA_Identifier[10] = (char)('0' + physno);
#else
			static char *ary[]= {
				"PRIMARY",
				"SECONDARY",
				"TERTIARY",
				"QUATERNARY",
			};
			_fmemcpy(
				&srb->hainq.HA_Identifier[4],
				ary[ physno ],
				strlen(ary[ physno ])
			);
#endif
		}
		_fmemset(srb->hainq.HA_Unique, 0, sizeof(srb->hainq.HA_Unique));
		if (srb->hainq.SRB_55AASignature == 0xaa55) {
			srb->hainq.SRB_55AASignature= 0x55aa;
			if (srb->hainq.SRB_ExtBufferSize > 2)
				srb->hainq.SRB_ExtBufferSize= 2;
			_fmemset(
				&srb->hainq.HA_ExtBuffer,
				0,
				srb->hainq.SRB_ExtBufferSize
			);
		}
		goto local_srb_done;
	case SC_GET_DEV_TYPE:
		srb->gdt.SRB_Status= (char)ide_inquire(
			ideha,
			srb->gdt.SRB_Target,
			srb->gdt.SRB_Lun
		);
		/* if error status, switch to no target */
		if (srb->gdt.SRB_Status != SS_COMP)
			srb->gdt.SRB_Status = SS_NO_DEVICE;
		srb->gdt.SRB_DeviceType= (char)(ide_inqdata[0] & 0x1f);
		goto local_srb_done;
	case SC_EXEC_SCSI_CMD:
		if (srb->io.SRB_Lun)
			goto no_device;
		if (srb->io.SRB_Target >= 2)
			goto no_device;
		if (ide_srb_cur) {      /* if busy */
			srb->io.SRB_Status= SS_ASPI_IS_BUSY;
			goto local_srb_done;
		}
		enumtype= ide_enum_dev_type[ideha][srb->io.SRB_Target];
		switch (enumtype & (ETF_ATAPI|ETF_ATA)) {
		case ETF_ATA:
			identop= 0xec; /* 0xec: identify device */
			goto ck_inquire;
		case ETF_ATAPI:
			if (enumtype & ETF_INQUIRYWORKS)
				break;  /* execute scsi command */
			identop= 0xa1; /* 0xa1: identify packet device */
		ck_inquire:
			/* Check for standard inquiry cdb */
			if (!(srb->io.CDBByte[0]==0x12/*INQUIRY*/
			   && srb->io.CDBByte[2]==0/*standardpage*/)) {
				srb->io.SRB_HaStat= HASTAT_COMMAND_TIMEOUT;
				srb->io.SRB_Status= SS_ERR;
				goto local_srb_done;
			}
			/* identop= 0xec: identify device */
			/* identop= 0xa1: identify packet device */
			if (ide_identify(ideha, srb->io.SRB_Target, identop)) {
				srb->io.SRB_HaStat= HASTAT_TIMEOUT;
				srb->io.SRB_Status= SS_ERR;
				goto local_srb_done;
			}
			len= 512;
			if (srb->io.SRB_BufLen < 512)
				len= (int)srb->io.SRB_BufLen;
			/*
			 * Simulate inquiry data.
			 * Peripheral device type for:
			 *  atapi device comes from ide_identdata[1]
			 *  ata device is 0.
			 */
			ide_mkinquiry(srb->io.SRB_BufPointer, len,
				(identop==0xa1) ?ide_identbuf[1] :0
			);
			goto local_srb_done;
		default:
			goto no_device;
		}
		break;
	case SC_RESET_DEV:
		if (srb->io.SRB_Target >= 2)
			goto no_device;
		enumtype= ide_enum_dev_type[ideha][srb->io.SRB_Target];
		switch (enumtype & (ETF_ATAPI|ETF_ATA)) {
		case ETF_ATAPI:
			break;
		case ETF_ATA:
		default:
no_device:              srb->io.SRB_Status= SS_NO_DEVICE;
			goto local_srb_done;
		}
		/* Abort current srb */
		opl= SPLHI();
		if (ide_srb_cur) {      /* if busy */
			ide_srb_cur->io.SRB_Status= SS_ABORTED;
			ide_atapi_done(0, 0);
		}
		SPLX(opl);
		break;
	case SC_ABORT_SRB:
		opl= SPLHI();
		if (srb->abort.SRB_ToAbort == NULL
		 || srb->abort.SRB_ToAbort != (SRB_ExecSCSICmd _far *)ide_srb_cur)
			srb->abort.SRB_Status= SS_ABORT_FAIL;
		else
			ide_intr_flags |= INTRF_USER_ABORT;
		SPLX(opl);
		goto local_srb_done;

#if ATAEXT
	case SC_ATA_PACKET_IDENTIFY:
	case SC_ATA_RESET:
	case SC_ATA_READ_PORTS:
	case SC_ATA_WRITE_PORTS:
		break;
#endif

	default:
	case SC_SET_HA_PARMS:
	case SC_GET_DISK_INFO:
		srb->io.SRB_Status= SS_INVALID_CMD;
		goto local_srb_done;
	}

	ide_hastat = 0;
	ide_targstat = 0;
	ide_select(ideha, srb->io.SRB_Target);

	switch (srb->io.SRB_Cmd) {
	case SC_RESET_DEV: /* bus device reset */
		OUTB(ide_port+IREG_DEVHD, ide_selectbyte);/* select device */
		if (INB(ide_port+IREG_ALTSTAT) & ISBSY) {
			srb->io.SRB_HaStat= HASTAT_SEL_TO;
			srb->io.SRB_Status= SS_ERR;
			goto local_srb_done;
		}
		opl= SPLHI();
		ide_intr_flags &= ~INTRF_TIMEOUT;
		ide_atacmd_reset[5] = (char)ide_selectbyte;
		ide_atacmdout(ide_atacmd_reset);        /* start reset */
		ide_ata_timer= HZ;
		SPLX(opl);
		/* wait loop */
		for (;;) {
			if (ide_ckabort())      /* check for control-c */
				break;
			OUTB(ide_port+IREG_DEVHD, ide_selectbyte);
			if ((INB(ide_port+IREG_ALTSTAT) & (ISBSY|ISDRDY)) == 0)
				break;
			if (ide_intr_flags & INTRF_TIMEOUT)
				break;
		}
		ide_ata_timer= 0;
		break;

	default:
	case SC_EXEC_SCSI_CMD:
		OUTB(ide_port+IREG_DEVHD, ide_selectbyte);/* select device */
		if (INB(ide_port+IREG_ALTSTAT) & ISBSY) {
			srb->io.SRB_HaStat= HASTAT_SEL_TO;
			srb->io.SRB_Status= SS_ERR;
			goto local_srb_done;
		}
		cdbbyte0= srb->io.CDBByte[0];
		cdbtype= ide_cdb_type[ cdbbyte0 ];
		/*
		 * Can't handle DMA transfers larger than 64K
		 * PIO is always used for REQUEST_SENSE and 
		 * WRITE_BUFFER.
		 */
		if (ide_dmachan
		 && (cdbtype & (_CDTYPE_DATAIN | _CDTYPE_DATAOUT))
		 && srb->io.SRB_BufLen > 0x10000L
		 && cdbbyte0 != 0x3b) { /*WRITE_BUFFER*/
			srb->io.SRB_Status= SS_BUFFER_TO_BIG;
			goto local_srb_done;
		}

		switch (cdbbyte0) {
		case 0x03: /*REQUEST_SENSE*/
			/* wait for DSC */
			opl= SPLHI();
			ide_srb_cur = srb;
			ide_intr_flags &= ~INTRF_ALL;
			ide_atapi_timer= ide_cdb_seconds[ TI_MAX ] * HZ;
			for (;;) {
				SPLX(opl);
				if (ide_ckabort())      /* check for control-c */
					goto local_srb_done;
				opl= SPLHI();
				if (INB(ide_port+IREG_ALTSTAT) & ISDSC) {
					ide_srb_cur = NULL;
					ide_atapi_timer= 0;
					SPLX(opl);
					break; /* DSC set */
				}
				if (srb->io.SRB_Status) {
					SPLX(opl);
					return; /* timed out */
				}
			}
			break;
		case 0x3b: /*WRITE_BUFFER*/
			break;
		default:
			if (ide_dmachan
			 && (cdbtype & (_CDTYPE_DATAIN | _CDTYPE_DATAOUT))) {
				/* set for dma mode */
				ide_atacmd_packet[0] = 1;
				ide_dma_in = CDBTYPE_DATAIN(cdbtype);
			}
			break;
		}
		/* localize params */
		ide_io_ptr = srb->io.SRB_BufPointer;
		ide_io_residual = srb->io.SRB_BufLen;
		ide_cdb_caddr = srb->io.CDBByte;
		ide_sense_caddr= &srb->io.CDBByte[ srb->io.SRB_CDBLen ];
		ide_cdb_requestsense[4] = srb->io.SRB_SenseLen;
		/* clear sense buffer */
		_fmemset(ide_sense_caddr, 0, ide_cdb_requestsense[4]);
		ide_atacmd_packet[5] = (char)ide_selectbyte;
		ide_atacmd_packet[0] = 0;       /* set for pio mode */

		opl= SPLHI();
		ide_srb_cur = srb;
		ide_intr_flags &= ~INTRF_ALL;
		if (cdbtype & _CDTYPE_WAITDSC)
			ide_intr_flags |= INTRF_DO_WAITDSC;
		ide_intr_handler = ide_atapiintr;  /* set for ATAPI mode */
		/* issue ATA packet command */
		ide_atacmdout(ide_atacmd_packet);
		if (ide_waitdrq()) {
			ide_atapi_done(HASTAT_COMMAND_TIMEOUT, 0);
			SPLX(opl);
			return;
		}
		ide_atapi_timer= ide_cdb_seconds[ cdbtype & _CDTYPE_TMRIDX ] * HZ;
		/* send CDB */
		ide_dataout(ide_cdb_caddr, 12);
		if (ide_atacmd_packet[0] & 1)
			ide_dmatransfer(ide_io_ptr, (int)ide_io_residual, ide_dma_in);
		SPLX(opl);
		return;

#if ATAEXT
	case SC_ATA_PACKET_IDENTIFY:
		/* 0xa1: identify packet device */
		if (ide_identify(ideha, srb->io.SRB_Target, 0xa1)) {
			srb->io.SRB_HaStat= HASTAT_TIMEOUT;
			srb->io.SRB_Status= SS_ERR;
		} else {
			len= 512;
			if (srb->io.SRB_BufLen < 512)
				len= (int)srb->io.SRB_BufLen;
			_fmemcpy(srb->io.SRB_BufPointer, ide_identbuf, len);
		}
		break;

	case SC_ATA_RESET:
		opl= SPLHI();
		OUTB(ide_port+IREG_DEVHD, 0xa0);        /* select device 0 */
		OUTB(ide_port+IREG_ALTSTAT, ICSRST);            /* start reset */
		for (i=0; i<5; i++)
			my_delay_1us();                 /* delay 5 usec */
		OUTB(ide_port+IREG_ALTSTAT, 0);                 /* stop reset */
		SPLX(opl);
		/*
		 * spec: wait for dev 0 not BSY
		 * do delay to allow slave only
		 */
		for (i=0; i<5000; i++)
			my_delay_1us();                 /* delay 5 msecs */
		ide_ata_timer= HZ;
		ide_intr_flags &= ~INTRF_TIMEOUT;
		/* wait for not busy */
		for (;;) {
			OUTB(ide_port+IREG_DEVHD, ide_selectbyte);
			if ((INB(ide_port+IREG_ALTSTAT) & ISBSY) == 0)
				break;
			if (ide_intr_flags & INTRF_TIMEOUT) {
				srb->io.SRB_HaStat= HASTAT_TIMEOUT;
				srb->io.SRB_Status= SS_ERR;
				goto local_srb_done;
			}
			ide_ckabort();
		}
		ide_ata_timer= 0;
		break;

	case SC_ATA_WRITE_PORTS:
		memset(ide_atacmd_gp, 0, sizeof(ide_atacmd_gp));
		len= srb->io.SRB_CDBLen;
		if (len > 8)
			len= 8;
		_fmemcpy(ide_atacmd_gp, srb->io.CDBByte, len);
		ide_atacmd_gp[5] = (char)ide_selectbyte;
		opl= SPLHI();
		ide_atacmdout(ide_atacmd_gp);   /* send command */
		/* wait for interrupt */
		rc= ide_wait_ataintr(INTRF_DONE|INTRF_TIMEOUT, 1*HZ);
		SPLX(opl);
		if (rc < 0) {
			srb->io.SRB_HaStat= HASTAT_TIMEOUT;
			srb->io.SRB_Status= SS_ERR;
			/* read registers even on timeout */
		}
		/*fall through*/

	case SC_ATA_READ_PORTS:
		_fmemset(&srb->io.CDBByte[srb->io.SRB_CDBLen], 0, srb->io.SRB_SenseLen);
		len= srb->io.SRB_SenseLen;
		if (len > 8)
			len= 8;
		for (i=0; i<len; i++) {                 /* read in ports */
			int value;

			switch (i) {
			case 0:
				value= 0;
				break;
			default: /* 1-6 */
				value= INB(ide_port+i);
				break;
			case 7:
				if (srb->io.SRB_Cmd == SC_ATA_WRITE_PORTS)
					value= ide_intr_status;
				else
					value= INB(ide_port+IREG_ALTSTAT);
				break;
			}
			srb->io.CDBByte[ srb->io.SRB_CDBLen+i ]= (char)value;
		}
		break;
#endif
	}
local_srb_done:
	ide_aspi_done(srb);
}

 void _far 
aspi_null(void _far *srbarg)
{
	union srb_u _far *srb= (union srb_u _far *)srbarg;

	srb->io.SRB_Status= SS_FAILED_INIT;
}

void (_far *Aspi_entry)(void _far *)= aspi_null;

getaspientry(void)
{
	/* c: needed for some networked computers */
	static char Aspi_devname[]= "c:SCSIMGR$";
	int rc= -1;

	_asm {
	mov ax, 3d00h           // open
	mov dx, offset Aspi_devname + 2
	int 21h
	jnc opened
	mov ax, 3d00h
	mov dx, offset Aspi_devname
	int 21h
	jc done
	}
opened:
	_asm {
	mov bx, ax              // file handle
	mov di, ax
	mov ax, 4402h           // ioctl read
	mov cx, 4
	mov dx, offset Aspi_entry       // ds:bx= addr
	int 21h
	pushf
	mov ah, 3eh             // close
	mov bx, di              // file handle
	int 21h
	popf
	jc done
	inc rc                  // return 0
	}
done:
	return (rc);
}

ide_term(void)
{
	ide_select(-1, -1);
	return (0);
}

SendASPICommand(void _far *srbarg)
{
	union srb_u _far *srb= (union srb_u _far *)srbarg;

	if (srb->io.SRB_HaId < (unsigned char)ide_scsi_ha_count) {
		(*Aspi_entry)(srb);
		switch (srb->io.SRB_Cmd) {
		case SC_HA_INQUIRY:
			srb->hainq.HA_Count =
				(char)(srb->hainq.HA_Count + ide_ide_ha_count);
			break;
		}
	} else
		ide_aspi(srb);
	return (0);
}

GetASPISupportInfo(void)
{
	SRB_HAInquiry inq;
	int nha= 0;
	int i;
	int winver;

	ide_term();
	Aspi_entry= aspi_null;
	ide_scsi_ha_count= 0;
	ide_ide_ha_count= 0;
	memset(ide_enum_ha_type, 0, sizeof(ide_enum_ha_type));
	memset(ide_enum_dev_type, 0, sizeof(ide_enum_dev_type));
	my_delay_us_init();
	winver= dos_winver();

	/*
	 * If find SCSI bus or
	 * find IDE bus and Windows 9x ...
	 */
	if ( (ide_busflags & BUSF_SCSI)
	 || ((ide_busflags & BUSF_IDE) && winver>=0x400) )
	if (getaspientry() == 0) {
		ide_scsi_ha_count= 1;
		memset(&inq, 0, sizeof(inq));
		SendASPICommand(&inq);
		ide_scsi_ha_count= inq.HA_Count;
	}
	if ((ide_busflags & BUSF_IDE) && winver==0) {

		/*-- Running under DOS (not windows) --*/
		/* flush disk buffers (e.g., smartdrv write behinds) */
		dos_diskreset();
		ide_flags |= IDEF_ININIT;

		for (i=0; i<ide_search_length; i++) {
			int physno= ide_search_order[ i ];

			if (ide_ha_present(ide_phys_ports[ physno ]) == 0) {
				int ideha;
				int targid;
				int type;

				ide_enum_ports[ ide_ide_ha_count ] = ide_phys_ports[ physno ];
				ide_enum_irqs[ ide_ide_ha_count ] = ide_phys_irqs[ physno ];
				ide_enum_phys[ ide_ide_ha_count ] = physno;
				ideha= ide_ide_ha_count++;
				for (targid=0; targid<2; targid++) {
					char *msg= "none";
					type= 0;

					/* 0xec: identify device */
					if (ide_identify(ideha, targid, 0xec) == 0) {
						type= ETF_ATA;
						msg= "ATA";
					/* 0xa1: identify packet device */
					} else if (ide_identify(ideha, targid, 0xa1) == 0) {
						type= ETF_ATAPI;
						msg= "ATAPI";
						ide_enum_dev_type[ideha][targid]= ETF_INQUIRYWORKS | ETF_ATAPI;
						if (ide_inquire(ideha, targid, 0) == SS_COMP) {
							type |= ETF_INQUIRYWORKS;
							msg= "ATAPI(INQUIRE)";
						}
					}
					ide_enum_ha_type[ideha] |= (char)(type & (ETF_ATAPI | ETF_ATA));
					ide_enum_dev_type[ideha][targid]= (char)type;
					if (ide_debug)
						IDEPRF(("ide%d targ%d: %s\n",
							ideha, targid, msg));
				}
			}
		}
		ide_select(-1, -1);
	}
	ide_flags &= ~IDEF_ININIT;
	nha= ide_scsi_ha_count + ide_ide_ha_count;
	if (nha <= 0)
		/* 0xe4 ASPI initialization error */
		return (SS_FAILED_INIT << 8);
	return ((SS_COMP << 8) | nha);
}
#endif /*__DOS__*/


#if WIN32 && __ASPI__
#ifdef __cplusplus
extern "C" {
#endif

 ULONG
sendaspi_null(void *sp)
{
	SRB_ExecSCSICmd *srb= (SRB_ExecSCSICmd *)sp;

	srb->SRB_Status= SS_FAILED_INIT;
	return (SS_FAILED_INIT << 8);
}

 ULONG
getaspi_null(void)
{
	return (SS_FAILED_INIT << 8);
}

ULONG (*SendASPICommand)(void *sp)= sendaspi_null;
ULONG (*GetASPISupportInfo)(void)= getaspi_null;
HANDLE ASPICompletionEvent;

#ifdef __cplusplus
}
#endif
#endif /*WIN32 && __ASPI__*/

	/* Video BIOS modes */
#define VM_40x25c16n    0x00 /*  0  40x25 BW text, color card */
#define VM_40x25c16     0x01 /*  1  40x25 color text */
#define VM_80x25c16n    0x02 /*  2  80x25 BW text */
#define VM_80x25c16     0x03 /*  3  80x25 color */
#define VM_320x200c4    0x04 /*  4 320x200 4-color graphics */
#define VM_320x200c4n   0x05 /*  5 320x200 4-color graphics */
#define VM_640x200c2    0x06 /*  6 640x200 2-color graphics */
#define VM_80x25c2      0x07 /*  7  80x25  monochrome text */
#define VM_160x200c16j  0x08 /*  8 160x200 16-color PCjr graphics */
#define VM_320x200c16j  0x09 /*  9 320x200 16-color PCjr    " */
#define VM_640x200c4j   0x0A /* 10 640x200 4-color PCjr     " */ 
#define VM_320x200c16   0x0D /* 13 320x200 16-color EGA     " */
#define VM_640x200c16   0x0E /* 14 640x200 16-color EGA     " */
#define VM_640x350c2    0x0F /* 15 640x350 monochrome */
#define VM_640x350c4    0x10 /* 16 640x350 4 or 16-color */
#define VM_640x350c16   0x10
#define VM_640x480c2    0x11 /* 17 640x480 VGA 2-color graphics */
#define VM_640x480c16   0x12 /* 18 640x480 VGA 16-color graphics */
#define VM_320x200c256  0x13 /* 19 320x200 VGA 256-color graphics */

#define CO_MAXPAGE 8  /* video pages */
	/* Color definitions */
#define FG_BLACK      0   /* Foreground colors */
#define FG_BLUE       1
#define FG_GREEN      2
#define FG_CYAN       3
#define FG_RED        4
#define FG_MAGENTA    5
#define FG_BROWN      6
#define FG_WHITE      7
#define FG_GRAY       8
#define FG_LBLUE      9
#define FG_LGREEN     10
#define FG_LCYAN      11
#define FG_LRED       12
#define FG_LMAGENTA   13
#define FG_YELLOW     14
#define FG_LWHITE     15

#define BG_BLACK      (0<<4)   /* Background colors */
#define BG_BLUE       (1<<4)
#define BG_GREEN      (2<<4)
#define BG_CYAN       (3<<4)
#define BG_RED        (4<<4)
#define BG_MAGENTA    (5<<4)
#define BG_BROWN      (6<<4)
#define BG_WHITE      (7<<4)
#define BG_BLINK      0x80

/* Console output via WIN32 or INT10 Video BIOS */

#if WIN32
HANDLE co_handle= INVALID_HANDLE_VALUE;
#else /*__DOS__*/
int co_curpage;
#endif

#if WIN32
#else
co_getmode(void) /* get current video mode */
{
	int mode;

	_asm {
	mov ah, 0fh     // 0xf: get_current_video_state()
	// al=video mode, ah=character columns, bh=display page
	int 10h
	xor ah, ah
	mov mode, ax
	}
	return (mode);
}
#endif

#if WIN32
#else
co_setmode(int mode) /* set video mode */
{
	_asm {
	mov ax, mode
	int 10h         // 0: set_mode(al=mode)
	}
	return (mode);
}
#endif

#if WIN32
#else
co_getpage(void) /* get active video page */
{
	int page;

	_asm {
	mov ah, 0fh     // 0xf: get_current_video_state()
	int 10h
	// al=video mode, ah=character columns, bh=display page
	xor ax, ax
	mov al, bh
	mov page, ax
	}
	return (page);
}
#endif


#if WIN32
#else
co_setpage(int page)
{
	int old_page= co_getpage();

	_asm {
	mov al, byte ptr page   //page:0-7 for modes:1,2; page:0-3 for modes:2,3
	mov ah, 5               // 5: set_display_page(al=page) (valid for alpha modes) 
	int 10h
	}
	return (old_page);
}
#endif

co_getrows(void) /* rows on the screen */
{
#if WIN32
	CONSOLE_SCREEN_BUFFER_INFO info;
		/* COORD dwSize; COORD dwCursorPosition; WORD  wAttributes; */
		/* SMALL_RECT srWindow; COORD dwMaximumWindowSize; */

	if (!GetConsoleScreenBufferInfo(co_handle, &info))
		return (25);
	return (info.dwSize.Y);
#else
	return (25);
#endif
}

co_getcols(void) /* returs 40 or 80 for alpha modes, 0 for graphics modes */
{
#if WIN32
	CONSOLE_SCREEN_BUFFER_INFO info;
		/* COORD dwSize; COORD dwCursorPosition; WORD  wAttributes; */
		/* SMALL_RECT srWindow; COORD dwMaximumWindowSize; */

	if (!GetConsoleScreenBufferInfo(co_handle, &info))
		return (80);
	return (info.dwSize.X);
#else
	int cols;

	_asm {
	mov ah, 0fh     // 0xf: get_current_video_state()
	int 10h
	// al=video mode, ah=character columns, bh=display page
	xor bx, bx
	mov bl, ah
	mov cols, bx
	cmp al, 3
	jbe done
	cmp al, 7
	je done
	xor bx, bx      // graphics
	mov cols, bx
	}
done:
	return (cols);
#endif
}

co_cursor_getpos(int *row, int *col) /* Get cursor coordinates */
{
#if WIN32
	BOOL true;

	CONSOLE_SCREEN_BUFFER_INFO info;
		/* COORD dwSize; COORD dwCursorPosition; WORD  wAttributes; */
		/* SMALL_RECT srWindow; COORD dwMaximumWindowSize; */

	if ((true= GetConsoleScreenBufferInfo(co_handle, &info)) == 0) {
		info.dwCursorPosition.Y = 0;
		info.dwCursorPosition.X = 0;
	}
	*row = info.dwCursorPosition.Y;
	*col = info.dwCursorPosition.X;
	if (!true)
		return (-1);
#else
	int r;
	int c;

	_asm {
	mov dx, col
	mov dh, byte ptr row
	mov bh, byte ptr co_curpage
	mov ah, 3        // 3: dh=row, dl=col get_cursor_position(bh=page)
	int 10h
	xor ax, ax
	mov al, dh;
	xor dh, dh
	mov r, ax
	mov c, dx
	}
	*row = r;
	*col = c;
#endif
	return (0);
}

co_cursor(int row, int col) /* Show the cursor */
{
#if WIN32
	COORD pos;

	pos.Y = (short)row;
	pos.X = (short)col;

	if (!SetConsoleCursorPosition(co_handle, pos))
		return (-1);
#else
	_asm {
	mov dx, col
	mov dh, byte ptr row
	mov bh, byte ptr co_curpage
	mov ah, 2       // 2: set_cursor_position(bh=page, dh=row, dl=col)
	int 10h
	}
#endif
	return (0);
}

co_cursor_show(void) /* show the cursor */
{
#if WIN32
	CONSOLE_CURSOR_INFO inf; /* DWORD dwSize; BOOL bVisible; */

	if (!GetConsoleCursorInfo(co_handle, &inf))
		return (-1);
	inf.bVisible= 1;
	if (!SetConsoleCursorInfo(co_handle, &inf))
		return (-1);
#else
#endif
	return (0);
}

co_cursor_hide(void) /* hide the cursor */
{
#if WIN32
	CONSOLE_CURSOR_INFO inf; /* DWORD dwSize; BOOL bVisible; */

	if (!GetConsoleCursorInfo(co_handle, &inf))
		return (-1);
	inf.bVisible= 0;
	if (!SetConsoleCursorInfo(co_handle, &inf))
		return (-1);
	return (0);
#else
	return (co_cursor(co_getrows(), 0));
#endif
}

/*
 * co_scroll() -- scrolls screen up or down using video bios
 */
#define CO_CLEAR 0
#define CO_LINEUP 1
#define CO_LINEDOWN (-1)

co_scroll(
	int lines, /* lines to scroll; negative:down, positive:up, 0:all */
	int row,
	int col,
	int height,
	int width,
	int attr) /* attribute for blank fill */
{
#if WIN32
	SMALL_RECT scroll; /* short Left, Top, Right, Bottom */
	COORD dest; /* short X, Y */
	CHAR_INFO fill;
	CONSOLE_SCREEN_BUFFER_INFO info;
		/* COORD dwSize; COORD dwCursorPosition; WORD  wAttributes; */
		/* SMALL_RECT srWindow; COORD dwMaximumWindowSize; */

	if (lines == CO_CLEAR)
		lines= height;
	if (IS_WIN9X()) {
		/* Clipping works correctly for one line in Windows 95/98 */
		while (lines > 1) {
			co_scroll(1, row, col, height, width, attr);
			lines--;
		}
		while (lines < -1) {
			co_scroll(-1, row, col, height, width, attr);
			lines++;
		}
	}
	scroll.Top= (short)row;
	scroll.Bottom= (short)(row + height - 1);
	scroll.Left= (short)col;
	scroll.Right= (short)(col + width - 1);

	dest.Y= (short)(row - lines);
	dest.X= (short)col;

	fill.Char.AsciiChar= ' ';
	fill.Attributes= (short)(attr & 0xff);
	if (GetConsoleScreenBufferInfo(co_handle, &info))
		fill.Attributes |= (short)(info.wAttributes & ~0xff);

	if (!ScrollConsoleScreenBuffer(co_handle, &scroll, &scroll, dest, &fill))
		return (-1);
#else
	int func;

	func= 6; /* scroll up */
	if (lines < 0) {
		lines= -lines;
		func= 7; /* scroll down */
	}
	_asm {
	mov ch, byte ptr row    // ch=upper left row
	mov cl, byte ptr col    // cl=upper left column
	mov dx, cx
	add dh, byte ptr height
	add dl, byte ptr width
	dec dh                  // dh=lower right row
	dec dl                  // dl=lower right column
	mov bh, byte ptr attr   // bh=fill attribute
	mov al, byte ptr lines  // al=lines to scroll; 0 for all
	mov ah, byte ptr func   // ah=6:up 7:down
	int 10h
	}
#endif
	return (0);
}

co_cls(int attr) /* Clear the screen */
{
	co_scroll(CO_CLEAR, 0, 0, co_getrows(), co_getcols(), attr);
	return (co_cursor(0, 0));
}

co_getca(int row, int col) /* get character/attribute */
{
#if WIN32
	COORD pos;
	char ch;
	unsigned short at;
	unsigned long c_nread;
	unsigned long a_nread;

	pos.Y = (short)row;
	pos.X = (short)col;

	if (ReadConsoleOutputCharacter(co_handle, &ch, 1, pos, &c_nread))
		return ((7<<8) | 'a');
	if (!ReadConsoleOutputAttribute(co_handle, &at, 1, pos, &a_nread))
		return ((7<<8) | 'c');
	return ((at<<8) | (unsigned char)ch);
#else
	int attr_char;

	_asm {
	mov dh, byte ptr row
	mov dl, byte ptr col
	mov bh, byte ptr co_curpage
	mov ah, 2       // 2: set_cursor_position(bh=page, dh=row, dl=col)
	int 10h
	mov bh, byte ptr co_curpage
	mov ah, 8       // 8: al=char, ah=attr read_char_attr(bh=page)
	int 10h
	mov attr_char, ax
	}
	return(attr_char);
#endif
}

/*
 * co_putca() -- display character/attribute
 *
 * Background    Foreground
 * (high nibble) (low nibble)
 * ------------  --------------------------
 * 0  black      0 black    8  gray
 * 1  blue       1 blue     9  light blue
 * 2  green      2 green   10  light green
 * 3  cyan       3 cyan    11  light cyan
 * 4  red        4 red     12  light red
 * 5  magenta    5 magenta 13  light magenta
 * 6  brown      6 brown   14  yellow
 * 7  white      7 white   15  light white
 *
 * Bit 3 sets intensity bit.
 * Bit 7 sets blink mode.
 */
co_putca(int row, int col, int c, int attr)
{
#if WIN32
	COORD pos;
	char ch;
	unsigned short at;
	unsigned long c_nwritten;
	unsigned long a_nwritten;

	pos.Y = (short)row;
	pos.X = (short)col;
	ch= (char)c;
	at= (unsigned short)attr;

	if (!WriteConsoleOutputCharacter(co_handle, &ch, 1, pos, &c_nwritten))
		return (-1);
	if (!WriteConsoleOutputAttribute(co_handle, &at, 1, pos, &a_nwritten))
		return (-1);
#else
	_asm {
	mov dx, col
	mov dh, byte ptr row
	mov bh, byte ptr co_curpage
	mov ah, 2       // 2: set_cursor_position(bh=page, dh=row, dl=col)
	int 10h
	mov cx, 1
	mov bx, attr
	mov bh, byte ptr co_curpage
	mov ax, c
	mov ah, 9       // 9: write_char_attr(bh=page, al=char, ah=attr)
	int 10h
	}
#endif
	return (0);
}

co_putfill(int row, int col, int width, int c, int attr)
{
	while (width-- > 0)
		co_putca(row, col++, c, attr);
	return (0);
}

co_putline(int row, int col, int width, char *s, int attr)
{
	for (; width > 0; width--) {
		if (!*s || *s == '\n')
			break;
		co_putca(row, col++, *s++, attr);
	}
	co_putfill(row, col, width, ' ', attr);
	return (0);
}

co_centerline(int row, int col, int width, char *s, int attr)
{
	char *p;
	int len;
	int offset;

	len= strlen(s);
	if ((p= strchr(s, '\n')) != NULL)
		len= p - s;
	if (len > width)
		len= width;
	offset= (width - len)/2;
	co_putfill(row, col, offset, ' ', attr);
	co_putline(row, col+offset, width-offset, s, attr);
	return (0);
}

co_init(void) /* initialize console output */
{
#if WIN32
	if ((co_handle= GetStdHandle(STD_OUTPUT_HANDLE)) == INVALID_HANDLE_VALUE)
		return (-1);
#else
	co_curpage= co_getpage();
#endif
	return (0);
}

	/* extended key codes */
#define KB_F1           (0x100+59)
#define KB_F2           (0x100+60)
#define KB_F3           (0x100+61)
#define KB_F4           (0x100+62)
#define KB_F5           (0x100+63)
#define KB_F6           (0x100+64)
#define KB_F7           (0x100+65)
#define KB_F8           (0x100+66)
#define KB_F9           (0x100+67)
#define KB_F10          (0x100+68)
#define KB_HOME         (0x100+71)
#define KB_UP           (0x100+72)
#define KB_PGUP         (0x100+73)
#define KB_LEFT         (0x100+75)
#define KB_RIGHT        (0x100+77)
#define KB_END          (0x100+79)
#define KB_DN           (0x100+80)
#define KB_PGDN         (0x100+81)
#define KB_INSERT       (0x100+82)
#define KB_DELETE       (0x100+83)

/*
 * kbget_nowait() -- gets key code; nowait, noecho, no control-c check
 *
 * returns:
 *      0 if no character ready
 *      0x01 through 0xff: ascii
 *      0x100 through 0x1ff extended code
 */
kbget_nowait(void)
{
	int key= 0;

#if WIN32
	if (kbhit())
		if ((key= getch()) == 0 || key == 0xe0 /*224*/)
			key = getch() | 0x100; /* extended key code */
#else /*__DOS__*/
	_asm {
	mov dl, 0ffh    // ff for get (rather than put) a character
	mov ah, 6       // get character no echo, no wait, no control-c check
	int 21h
	jz done
	mov ah, 0       // assume standard ascii in al
	mov key, ax
	or al, al       // extended code?
	jnz done        //  n: --> return char
	mov ah, 6       // get extended code
	int 21h
	mov ah, 1
	mov key, ax
	}
done:
#endif
	if (key == '\r')
		key = '\n';
	return (key);
}

kbget(void) /* wait for key stroke */
{
	int key;

	while ((key= kbget_nowait()) == 0)
		;
	return (key);
}

kbget_flush(void) /* flush input then wait for key */
{
	while (kbget_nowait())
		;
	return (kbget());
}

#if __DOS__
 void _interrupt _far
critintr(               /* handles int 24 critical errors */
#if __BORLANDC__
	int rbp, int rdi, int rsi, int rds, int res,
	int rdx, int rcx, int rbx, int rax,
#else /* Microsoft */
	int res, int rds, int rdi, int rsi, int rbp, int rsp,
	int rbx, int rdx, int rcx, int rax,
#endif
	int rip, int rcs, int rflags)
{
	UNREFERENCED(res); UNREFERENCED(rds); UNREFERENCED(rdi);
	UNREFERENCED(rsi); UNREFERENCED(rbp);
#if _MSC_VER
	UNREFERENCED(rsp);
#endif
	UNREFERENCED(rbx); UNREFERENCED(rdx); UNREFERENCED(rcx);
	UNREFERENCED(rax);
	UNREFERENCED(rip); UNREFERENCED(rcs); UNREFERENCED(rflags);
	rax= 3;         /* fail int 24 */
}
#endif /*__DOS__*/

#if __DOS__
	/* directory entry attributes */
#define FILE_ATTRIBUTE_READONLY   0x1
#define FILE_ATTRIBUTE_HIDDEN     0x2
#define FILE_ATTRIBUTE_SYSTEM     0x4
#define FILE_ATTRIBUTE_DIRECTORY  0x10
#define FILE_ATTRIBUTE_ARCHIVE    0x20

	/*
	 * _FILETIME -- win95/nt filetime, in 100ns, since 1-1-1601
	 */
struct _FILETIME {
	unsigned long dwLowDateTime;
	unsigned long dwHighDateTime;
};

	/*
	 * WIN32_FIND_DATA -- win95/nt find buffer
	 */
typedef struct _WIN32_FIND_DATA {
	unsigned long dwFileAttributes;
	struct _FILETIME ftCreationTime;        /* UTC */
	struct _FILETIME ftLastAccessTime;      /* UTC */
	struct _FILETIME ftLastWriteTime;       /* UTC */
	unsigned long nFileSizeHigh;
	unsigned long nFileSizeLow;
	unsigned long dwReserved0;
	unsigned long dwReserved1;
	char cFileName[260];                    /* entry name */
	char cAlternateFileName[14];
} WIN32_FIND_DATA, *PWIN32_FIND_DATA, *LPWIN32_FIND_DATA;

	/*
	 * FIND83 -- find buffer for directory searching
	 *
	 * FIND83 is a buffer argument for 'dos_83findfirst' 
	 * and 'dos_83findnext' directory searching services
	 */
struct find83 {
	unsigned short f83_attr;        /* entry's attribute */
	unsigned long  f83_fattime;     /* entry time as a long */
	unsigned long  f83_size;        /* entry's size in bytes */
	char           f83_name[13];    /* packed entry name 8.3\0+pad(1) */
};

/*
 * dos_avail() -- finds largest block of available memory
 *
 * Returns the large memory block size available in paragraphs.
 */
 unsigned
dos_avail(void)
{
	unsigned rc;

	_asm {
	mov bx, -1
	mov ah, 48h             // Allocate block function
	int 21h
	mov rc, bx
	}
	return (rc);
}

#if TAPEDRV_UPDATE
/*
 * dos_setdisk() -- select dos disk and return total number of disks
 *
 * Returns total number of DOS disks even if the argument is invalid.  
 * For DOS 3.xx the total number of disks is set in CONFIG.SYS with 
 * `lastdrive='.
 */
dos_setdisk(int disk) /* 0:A, 1:B, ... */
{
	int ndisks;

	_asm {
	mov dx, disk
	mov ah, 0eh     // Select disk in dl
	int 21h
	cbw
	mov ndisks, ax
	}
	return (ndisks);
}

dos_getdisk(void) /* returns current disk: 0:A, 1:B, ... */
{
	int disk;

	_asm {
	mov ah, 19h             // get current disk
	int 21h
	cbw
	mov disk, ax
	}
	return (disk);
}

dos_getdcwd(
	int driveno,    /* Drive number 0=current, 1=A: ... */
	char *buf)      /* 257 character destination buffer */
{
	int rc= 0;

	_asm {
#if M_I86CM || M_I86LM || __COMPACT__ || __LARGE__
	push ds
	lds si, buf
#else
	mov si, buf
#endif
	mov dx, driveno
	mov ax, 7147h   // lfn_get_cur_dir(dl=driveno, ds:si=buf)
	stc             // set carry in case not supported
	int 21h
	jnc done        // if success, -->
	cmp ax, 7100h   // longname support?
	jne fail        //  y: (ax=doserror) -->
	mov ah, 47h     // 8.3_get_cur_dir(dl=driveno, ds:si=buf)
	int 21h
	jnc done        // if success, -->
	}
 fail:
	_asm {
#if M_I86CM || M_I86LM || __COMPACT__ || __LARGE__
	pop ds
	push ds
#endif
	dec rc                  // return -1
	mov _doserrno, ax
	}
 done:
#if M_I86CM || M_I86LM || __COMPACT__ || __LARGE__
	_asm pop ds
#endif
	return (rc);
}

 char *
my_getdcwd(
	int driveno,    /* Drive number 0=current, 1=A: ... */
	char *bufaddr,  /* destination buffer or NULL to malloc() */
	int buflen)     /* length of destination (if bufaddr is non-NULL) */
{
	char path[260];
	int size;

	if (dos_getdcwd(driveno, &path[3])) {
		errno = 13; /*13 EACCES "Permission denied"*/
		return (NULL);
	}
	if (driveno-- == 0)
		driveno = dos_getdisk();
	path[0] = (char)(driveno + 'A');
	path[1] = ':';
	path[2] = '\\';
	size = strlen(path) + 1;
	if (bufaddr == NULL) {
		if ((bufaddr = malloc(size)) == NULL) {
			errno = 12; /*12 ENOMEM "Not enough memory space"*/
			return (NULL);
		}
	} else if (buflen < size) {
		errno = 34; /*34 ERANGE "Result too large"*/  
		return (NULL);
	}
	return (strcpy(bufaddr, path));
}

 char *
my_getcwd(
	char *bufaddr,  /* destination buffer or NULL to malloc() */
	int buflen)     /* length of destination (if bufaddr is non-NULL) */
{
	return (my_getdcwd(0, bufaddr, buflen));
}

dos_chdir(char *dir)
{
	int rc= 0;

	_asm {
#if M_I86CM || M_I86LM || __COMPACT__ || __LARGE__
	push ds
	lds dx, dir
#else
	mov dx, dir
#endif
	mov ax, 713bh		// lfn_chdir(ds:dx=path)
	stc			// set carry in case not supported
	int 21h
	jnc done
	cmp ax, 7100h		// longname support?
	jne fail		//  y: (ax=doserror) -->
	mov ah, 3bh		// 8.3_chdir(ds:dx=path)
	int 21h
	jnc done
	}
 fail:
	_asm {
#if M_I86CM || M_I86LM || __COMPACT__ || __LARGE__
	pop ds
	push ds
#endif
	dec rc			// return -1
	mov _doserrno, ax
	}
 done:
#if M_I86CM || M_I86LM || __COMPACT__ || __LARGE__
	_asm pop ds
#endif
	return (rc);
}

/*
 * my_chdir() -- emulates win32 chdir()
 */
my_chdir(char *dir)
{
	int driveno;

	driveno = is_accessible_drive(dir);
	/* If not a drive letter only (e.g., "a:") */
	if (!(driveno > 0 && strlen(dir) == 2))
		if (dos_chdir(dir)) {
			/* errno 2 (but not 20) is compatible with 16-bit ms compilers */
			/* errno = 20;*//* 20 ENOTDIR "Not a directory" */
			errno = 2; /* 2 ENOENT "No such file or directory" */
			if (_doserrno == 5) /* 5: ERROR_ACCESS_DENIED */
				errno = 13; /*13 EACCES "Permission denied" */
			return (-1);
		}
	if (driveno > 0)
		dos_setdisk(driveno-1);
	return (0);
}
#endif /*TAPEDRV_UPDATE*/

dos_83findclose(
	int dhandle)
{
	int rc= 0;

	_asm {
	mov ax, dhandle
	inc ax                  // test for dhandles of 0 or -1
	cmp ax, 1               // handle 0 or -1?
	mov ax, 6               // ERROR_INVALID_HANDLE
	jbe fail                // y: -->
	mov es, dhandle
	mov ah, 49h             // free block function
	int 21h
	jnc done
	}
fail:
	_asm {
	mov _doserrno, ax
	dec rc
	}
done:
	return (rc);
}

dos_83findnext(
	int dhandle,
	struct find83 *f83p)
{
	int rc= 0;

	_asm {
	push ds
	mov bx, dhandle         // bx= saved dhandle
	mov ds, bx
	xor dx, dx
	mov ah, 1ah             // set_dta(ds:dx=dta)
	int 21h                 // set dta to find buffer address
	mov ah, 4fh             // find_next() [dta=&findb]
	int 21h                 // call DOS to do the find operation
	jnc copyfindbuf
	pop ds
	dec rc                  // return -1
	mov _doserrno, ax
	jmp done
	}
copyfindbuf:
	_asm {
	// copy fb_attr, fb_fattime, fb_size, and fb_name[13]
	// to f83_attr, f83_fattime, f83_size, and f83_name[14]
#if M_I86CM || M_I86LM || __COMPACT__ || __LARGE__
	les di, f83p
#else
	pop es
	push es
	mov di, f83p
#endif
	mov ds, bx
	mov si, 21      // offset to fb_attr in findb
	xor ax, ax
	lodsb           // fb_attr
	stosw
	mov cx, 21/2    // fb_fattime, fb_size, fb_name[13]
	rep movsw
	movsb
	pop ds
	}
done:
	return (rc);
}

dos_83findfirst(
	char *path,
	struct find83 *f83p)
{
	int dhandle;

	_asm {
	push ds
	mov bx, (43+15)/16      // findb structure is 43 bytes
	mov ah, 48h             // allocate_block(bx=paragraphs)
	int 21h
	mov bx, ax              // bx= saved handle
	jc fail
	mov ds, bx
	xor dx, dx
	mov ah, 1ah             // set_dta(ds:dx=dta)
	int 21h                 // set dta to find buffer address
	mov cl, 37h             // cl= attributes for find first
#if M_I86CM || M_I86LM || __COMPACT__ || __LARGE__
	lds dx, path
#else
	pop ds
	push ds
	mov dx, path
#endif
	mov ah, 4eh             // find_first(ds:dx=path, cl=attr) [dta=&findb]
	int 21h                 // call DOS to do the find operation
	jnc copyfindbuf
	}
fail:
	_asm {
	pop ds
	mov dhandle, -1
	mov _doserrno, ax
	jmp done
	}

copyfindbuf:
	_asm {
	// copy fb_attr, fb_fattime, fb_size, and fb_name[13]
	// to f83_attr, f83_fattime, f83_size, and f83_name[14]
#if M_I86CM || M_I86LM || __COMPACT__ || __LARGE__
	les di, f83p
#else
	pop es
	push es
	mov di, f83p
#endif
	mov ds, bx
	mov si, 21      // offset to fb_attr in findb
	xor ax, ax
	lodsb           // fb_attr
	stosw
	mov cx, 21/2    // fb_fattime, fb_size, fb_name[13]
	rep movsw
	movsb
	pop ds
	mov dhandle, bx // handle is segment of find buffer
	}
done:
	return (dhandle);
}

dos_lnfindclose(
	int dhandle)
{
	int rc= 0;

	_asm {
	mov bx, dhandle         // bx = find-file handle
	mov ax, 71a1h           // longnm_findclose(bx=handle)
	stc                     // set carry in case function not supported
	int 21h
	jnc done                // if success, --> done
	dec rc                  // rc=-1
	mov _doserrno, ax
	cmp ax, 7100h           // longname support?
	jne done                //  y: (ax=doserror) -->
	mov _doserrno, 1        // 1 ERROR_INVALID_FUNCTION
	}
done:
	return (rc);
}

dos_lnfindnext(
	int dhandle,
	WIN32_FIND_DATA *wfdp)
{
	int rc= 0;

	_asm {
#if M_I86CM || M_I86LM || __COMPACT__ || __LARGE__
	les di, wfdp
#else
	push ds
	pop es
	mov di, wfdp            // es:di -> WIN32_FIND_DATA result buffer
#endif
#if USE_FATTIME
	mov si, 1               // si=0:64-bit filetime, 1:32-bit fattime
#else
	xor si, si
#endif
	mov bx, dhandle         // handle returned by dos_lnfindfirst
	mov ax, 714fh           // longnm_findnext(bx=handle, es:di=findbuf)
	stc                     // set carry in case function not supported
	int 21h
				// flags returned in cx:
				// 1 primary name contains underscores (_) 
				//   in place of UNICODE characters 
				//   unconvertable to OEM/ANSI.
				// 2 alternate name contains underscores (_) 
				//   in place of UNICODE characters 
				//   unconvertable to OEM/ANSI.
	jnc done                // if success -->
	dec rc                  // rc=-1
	mov _doserrno, ax
	cmp ax, 7100h           // longname support?
	jne done                //  y: (ax=doserror) -->
	mov _doserrno, 1        // 1 ERROR_INVALID_FUNCTION
	}
done:   return (rc);
}

/*
 * dos_lnfindfirst() -- longname find first
 *
 * returns: -1 fail; non-zero handle
 */
dos_lnfindfirst(
	char *path,
	WIN32_FIND_DATA *wfdp)
{
	int dhandle;

	_asm {
#if M_I86CM || M_I86LM || __COMPACT__ || __LARGE__
	push ds
	lds dx, path
	les di, wfdp
#else
	mov dx, path            // ds:dx -> search path
	push ds
	pop es
	mov di, wfdp            // es:di -> WIN32_FIND_DATA result buffer
#endif
#if USE_FATTIME
	mov si, 1               // si=0:64-bit filetime, 1:32-bit fattime
#else
	xor si, si
#endif
	mov cx, 37h             // cx = attribute mask
	mov ax, 714eh           // longnm_findfirst(ds:dx=pattern, cx=attr, es:di=findbuf)
	stc                     // set carry in case function not supported
	int 21h
#if M_I86CM || M_I86LM || __COMPACT__ || __LARGE__
	pop ds
#endif
				// flags returned in cx:
				// 1 primary name contains underscores (_) 
				//   in place of UNICODE characters 
				//   unconvertable to OEM/ANSI.
				// 2 alternate name contains underscores (_) 
				//   in place of UNICODE characters 
				//   unconvertable to OEM/ANSI.
	mov dhandle, ax
	jnc done                // if success -->
	mov dhandle, -1
	mov _doserrno, ax
	cmp ax, 7100h           // longname support?
	jne done                //  y: (ax=doserror) -->
	mov _doserrno, 1        // 1 ERROR_INVALID_FUNCTION
	}
done:   return (dhandle);
}

/*
 * FindFirstFile(), FindNextFile(), FindClose() -- directory entry read access
 *
 * FindFirstFile(), FindNextFile(), and FindClose(), provides a DOS
 * interface for recursive directory searching.
 *
 * In DOS mode, int 21 services findfirst/next (4E/4F) are used.
 *
 * FindFirstFile() opens a directory for searching.  If successful, 
 * information on the first matching entry is present in the argument
 * WIN32_FIND_DATA buffer at return.  Under DOS, the handle is the 
 * segment of an allocated DOS find buffer.
 *
 * FindNextFile() continues a directory search using the directory handle
 * returned by FindFirstFile().  If succesful, information on the next 
 * directory entry is present in the WIN32_FIND_DATA buffer.
 *
 * Under DOS FindClose() frees memory allocated for a DOS find buffer.
 *
 * FindFirstFile() returns a directory handle (or in DOS mode, the
 * segment of a DOS Find buffer) when successful.  Otherwise, the 
 * value -1 is returned and GetLastError() has a diagnostic.
 *
 * FindNextFile() and FindClose() return TRUE when successful.  
 * Otherwise, the value FALSE is returned and GetLastError() has a 
 * diagnostic.
 */

/*
 * find83_2win32finddata() -- translates 8.3 find buffer to win32 find buffer
 */
find83_2win32finddata(
	WIN32_FIND_DATA *wfdp,
	struct find83 *f83p)
{
	memset(wfdp, 0, sizeof(*wfdp));
#if KOMMENT
	wfdp->nFileSizeHigh= 0;
	wfdp->ftCreationTime.dwLowDateTime= 0;
	wfdp->ftCreationTime.dwHighDateTime= 0;
	wfdp->ftLastAccessTime.dwLowDateTime= 0;
	wfdp->ftLastAccessTime.dwHighDateTime= 0;
	wfdp->ftLastWriteTime.dwHighDateTime= 0;
	wfdp->dwReserved0= 0;
	wfdp->dwReserved1= 0;
	wfdp->cAlternateFileName[ 0 ]= 0;       /* flags cFileName as 8.3 */
#endif
#if USE_FATTIME
	wfdp->ftLastWriteTime.dwLowDateTime= f83p->f83_fattime;
#else
#error "32 bit filetime nout supported"
#endif
	wfdp->dwFileAttributes= f83p->f83_attr;
	wfdp->nFileSizeLow= f83p->f83_size;
	memcpy(wfdp->cFileName, f83p->f83_name, 12);
	return (0);
}

/*
 * FindClose() returns TRUE when successful.  
 * Otherwise, the value FALSE is returned and GetLastError() has a 
 * diagnostic.
 */
 BOOL
FindClose(
	int dhandle)
{
	if (dos_lnfindclose(dhandle)) {
		if (_doserrno != 1 /*ERROR_INVALID_FUNCTION*/)
			return (FALSE);
		if (dos_83findclose(dhandle))
			return (FALSE);
	}
	return (TRUE);
}

/*
 * FindNextFile() -- finds next entry in a directory
 *
 * FindNextFile() returns TRUE when successful.  
 * Otherwise, the value FALSE is returned and GetLastError() has a 
 * diagnostic.
 */
 BOOL
FindNextFile(
	int dhandle,
	WIN32_FIND_DATA *wfdp)
{
	if (dos_lnfindnext(dhandle, wfdp)) {
		struct find83 f83;

		if (_doserrno != 1 /*ERROR_INVALID_FUNCTION*/)
			return (FALSE);
		if (dos_83findnext(dhandle, &f83))
			return (FALSE);
		find83_2win32finddata(wfdp, &f83);
	}
	return (TRUE);
}

/*
 * FindFirstFile() -- finds first entry in a directory
 *
 * FindFirstFile() returns a directory handle (or in DOS mode, the
 * segment of a DOS Find buffer) when successful.  Otherwise, the 
 * value -1 is returned and GetLastError() has a diagnostic.
 */
 HANDLE
FindFirstFile(
	char *path,
	WIN32_FIND_DATA *wfdp)
{
	int dhandle;
	struct find83 f83;

	if ((dhandle= dos_lnfindfirst(path, wfdp)) == -1) {
		if (_doserrno != 1 /*ERROR_INVALID_FUNCTION*/)
			return (-1);
		if ((dhandle= dos_83findfirst(path, &f83)) == -1)
			return (-1);
		find83_2win32finddata(wfdp, &f83);
	}
	return (dhandle);
}

/*
 * _read_binary_far() -- like _read() but only in binary mode to a far buffer
 */
_read_binary_far(int fh, void _far *addr, int count)
{
	int nbytes;

	_asm {
	push ds
	lds dx, addr
	mov cx, count
	mov bx, fh
	mov ah, 3fh     // ax=nbytes = read(bx=fh, ds:dx=addr, cx=count);
	int 21h
	pop ds
	mov nbytes, ax
	jnc done
	mov _doserrno, ax
	mov nbytes, -1
	mov errno, 9	// 9 EBADF Bad file number
	}
done:
	return (nbytes);
}
#endif /*__DOS__*/

 void
my_sleep(int seconds)
{
#if WIN32
	Sleep(seconds * 1000);
#elif __DOS__
	time_t end_time= time(NULL) + seconds;

	do {
		_asm {
		mov ah, 0bh     // get kbstat to ck for ^C
		xor dx, dx
		int 21h
		}
	} while (end_time - time(NULL) > 0);
#elif unix
	sleep(seconds);
#endif
}

/*
 * MCB -- dos memory control block
 */
#pragma pack(1)
struct mcb {
	char       mc_type;	/* Block type 'M' (0x4d) normal, 'Z' (0x5a) last */
	unsigned   mc_pspseg;	/* Owner PSP */
	unsigned   mc_size;	/* Block size in paragraphs (not including this) */
	char       mc_resv[3];	/* Unused */
	char       mc_name[8];	/* Owner name */
};
#pragma pack()

/*
 * Returns:
 *    Positive drive number: 1=A, 2=B, ... if path begins with a drive 
 *       letter component (e.g "A:...") and the drive successfully returns 
 *       disk-free-space information.
 *    0: if path begins with *other* than a drive letter component
 *    -1: if path begins with a drive letter component but the fails to 
 *       return disk-free-space information.
 */
#if TAPEDRV_UPDATE
is_accessible_drive(char *path)
{
#if WIN32
	DWORD MaximumComponentLength; // maximum file name length
	DWORD FileSystemFlags;        // file system options
	static char driveroot[] = "x:\\";
	int diskno;

	if ((diskno = path[0]) == 0 || path[1] != ':')
		return (0); /* path does not begin with drive letter component */
	if ((unsigned)(diskno-'a') <= 'a'-'z')
		diskno &= ~0x20;   /* toupper(diskno) */
	diskno -= 'A' - 1;
	driveroot[0] = path[0];
	if (!GetVolumeInformation(
		driveroot,               //LPCTSTR lpRootPathName,           // root directory e.g., A:
		NULL,                    //LPTSTR lpVolumeNameBuffer,        // volume name buffer
	        0,                       //DWORD nVolumeNameSize,            // length of name buffer
	        NULL,                    //LPDWORD lpVolumeSerialNumber,     // volume serial number
		&MaximumComponentLength, //LPDWORD lpMaximumComponentLength, // maximum file name length
		&FileSystemFlags,        //LPDWORD lpFileSystemFlags,        // file system options
		NULL,                    //LPTSTR lpFileSystemNameBuffer,    // file system name buffer
		0                        //DWORD nFileSystemNameSize         // length of file system name buffer
	))
		return (-1);
#elif __DOS__
	int diskno;

	if ((diskno = path[0]) == 0 || path[1] != ':')
		return (0); /* path does not begin with drive letter component */
	if ((unsigned)(diskno-'a') <= 'a'-'z')
		diskno &= ~0x20;   /* toupper(diskno) */
	diskno -= 'A' - 1;
	_asm {
	mov dx, diskno          // dl=diskno - 0=default 1=A: ...
	mov ah, 36h             // Get disk free space
	int 21h	                // ax=sects_per_clust, bx=avl_clusts
	                        // cx=bytes_per_sect, dx=tot_clusts
	cmp ax, -1              // -1 means invalid drive
	jnz done                // other than -1 --> done
	mov _doserrno, 15       // 15: ERROR_INVALID_DRIVE
	mov diskno, ax          // return (-1);
	}
 done:
#endif
	return (diskno);
}
#endif /*TAPEDRV_UPDATE*/

log_memstats(void)
{
#if __DOS__
	/* DOS memory: psp, code, data, bss, stack, nearheap, farheap */
	extern char _far *_pspadr;
	extern unsigned _psp;
	struct mcb _far *mcb;
	int rcs, rds;
	unsigned progsize; /* in paragraphs */
	unsigned avail; /* in paragraphs */

	_asm mov rcs, cs
	_asm mov rds, ds

	_heapmin(); /* Release heap space to DOS */
	avail = dos_avail();

	FP_SEG(mcb) = _psp - 1;
	FP_OFF(mcb) = 0;
	progsize = mcb->mc_size;
	logf("  env=0x%04x psp=0x%04x cs=0x%04x ds=0x%04x progsize=0x%04x memavail=0x%04x\n",
		*(unsigned _far *)&_pspadr[0x2c], _psp, rcs, rds, progsize, avail
	);
	logf("  memavl=%u memmax=%u stackavail=%u\n", _memavl(), _memmax(), _stackavail());
	avail /= 1024/16;
	logf(
		"  Program memory:   %4uK\n"
		"  Available memory: %4uK\n"
		"  Travan headroom:  %4dK\n",
		progsize/(1024/16),
		avail,
		avail - 282
	);
#endif
	return (0);
}

nib2ahex(int nibble) /* converts a nibble to upper case ascii hex digit */
{
	nibble = (nibble & 0xf) + '0';
	if (nibble > '9')
		nibble += 'A' - '0' - 10;
	return (nibble);
}

#define HEX_ASCII       0x0400 /* print ascii after hex digit pairs */
#define HEX_SPACE4      0x0040 /* insert extra space between 4 digit pairs */

 char *
fmthex( /* formats bytes in ascii hex */
	int hexflags,           /* HEX_* values */
	void IOFAR *source,
	int n)                  /* count to format; truncated to 16 */
{
	static char text[80];
	register char *d;
	char IOFAR *s;
	int col;
	int pair;
	int n_sv;

	if (hexflags & HEX_ASCII) {
		if (n > 16)
			n = 16;
	} else {
		if (n > sizeof(text)/3 - 3)
			n = sizeof(text)/3 - 3;
	}
	d = text;
	n_sv = n;

	if (n > 0) {
		s = (char IOFAR *)source;
		pair = 0;
		do {
			*d++ = (char)nib2ahex((*s) >> 4);
			*d++ = (char)nib2ahex(*s++);
			if (--n) {
				*d++ = ' ';
				if (hexflags & HEX_SPACE4)
				if ((++pair & 3) == 0)
					*d++ = ' ';
			}
		} while (n);
		if (hexflags & HEX_ASCII) {
#define HEX_FIELDSEPARATOR '#'
#if HEX_FIELDSEPARATOR
			while (d < &text[51])
				*d++ = ' ';
			*d++ = HEX_FIELDSEPARATOR;
			*d++ = ' ';
#else
			while (d < &text[52])
				*d++ = ' ';
#endif
			s = (char IOFAR *)source;
			col = 0;
			while (n_sv--) {
				if (col++ == 8)
					*d++ = ' ';
				if (*s >= ' ' && *(unsigned char IOFAR *)s < 0x7f)
					*d++ = *s;
				else
					*d++ = '.';
				s++;
			}
		}
	}
	*d = '\0';
	return (text);
}

#if DBG
char Dbg_fname[]= "tapedbg.out";
FILE *Dbg_fp;

#define BUGPRF(args)       dbg_printf args
dbg_printf(char *fmt, ...)
{
	va_list args;

	if (Dbg_fp == NULL && (Dbg_fp= fopen(Dbg_fname, "w")) == NULL)
		return (-1);
	va_start(args, fmt);
	return (vfprintf(Dbg_fp, fmt, args));
}
#else /*!DBG*/

#define BUGPRF(args)
#endif

my_vsnprintf(char *bufaddr, int bufsize, const char *fmt, va_list args)
{
	int count;

	if (bufsize <= 0)
		return (0);
	count = _vsnprintf(bufaddr, bufsize, fmt, args);
	/* If buffer fills to bufsize, it is unterminated */
	if ((unsigned)count >= (unsigned)bufsize) {
		/* buffer would (or did) overflow */
		bufaddr[ bufsize - 1 ]= (char)0;
		return (bufsize - 1);
	}
	return (count);
}

/*
 * bufsize gives the buffer size in bytes as a signed integer
 * Returns:
 *    Actual number of characters printed to buffer excluding terminator.
 */
my_snprintf(char *bufaddr, size_t bufsize, const char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	return (my_vsnprintf(bufaddr, bufsize, fmt, args));
}

/*
 * strichr -- scan string for a character ignoring case
 *
 * The string, s, including the null terminatior, is scanned from 
 * left to right ignoring case.  If c is found, its address in s is 
 * returned.  If c is zero, the address of the null terminator, in 
 * s, is returned.  Otherwise if c is not found, NULL is returned.
 */
 char *
strichr(const char *s, int c) /* returns address of first occurance of character in a string */
{
	int c2= c;

	if ((unsigned)(c2|0x20)-'a' <= 'z'-'a') /* isalpha(c) */
		c2 ^= 0x20;
	do {
		if (*s == (char)c || *s == (char)c2)
			return ((char *)s);
	} while (*s++);
	return (NULL);
}

 char *
skipspace(char *p)      /* skips over white space */
{
	if (p)
		while (isspace(*p))
			p++;
	return (p);
}

/*
 * strcpyn -- copies string to buffer of length n w/ guaranteed terminator
 * strcatn -- concatinates string to buffer of length n w/ guaranteed terminator
 *
 * The argument, n, gives the destination buffer's length as a signed 
 * value.  For destination buffers with zero or negative lengths, 
 * no copy is performed.  For destination buffers having a positive 
 * length, strcpyn() and strcatn() guarantee the buffer is null 
 * terminated.  If the destination would be overflowed, the final 
 * element in the buffer contains the null terminator.
 *
 * Returns:
 *    The value 0 indicates the source string is completely 
 *    copied to the destination buffer.  Otherwise the value -1 
 *    indicates the destination buffer is of either insufficient 
 *    or negative length and the source string was either partially 
 *    or not copied to the destination buffer.
 *
 */
strcpyn(register char *d, register const char *s, register int n)
{
	if (n <= 0)
		return (-1); /* zero or negative destination buffer length */
	for ( ; (*d = *s)!=0; d++, s++)
		if (--n == 0) {
			*d = 0; /* terminate buffer */
			return (-1); /* buffer would overflow */
		}
#if STRCPYN_PAD
	while (--n)
		*d++ = 0;
#endif
	return (0); /* success */
}

strcatn(register char *d, register const char *s, register int n)
{
	if (n <= 0)
		return (-1); /* zero or negative destination buffer length */
	for ( ; *d; d++)
		if (--n == 0) {
			*d = 0; /* terminate buffer */
			return (-1); /* end-of-buffer and terminator not found */
		}
	return (strcpyn(d, s, n));
}

 char *
strtrim(char *s) /* removes leading and trailing white space from a string */
{
	char *p;

	for (p=s; isspace(*p); p++)
		;
	strcpy(s, p);
	for (p=strchr(s, 0); p>s; p--)
		if (!isspace(p[-1]))
			break;
	*p= (char)0;
	return (s);
}

#if WIN32
 char *
strsth(int n) /* returns: "st", "nd", "d", or "th" for a signed integer */
{
	static char *th[] = { "th", "st", "nd", "d" };
	int i;

	if (n < 0)
		n = -n;
	if ((unsigned)((n % 100) - 10) < 4 || (i= (n % 10)) >= 4)
		i= 0;
	return (th[ i ]);
}
#endif

#if WIN32
strtailtoi(char *s) /* gets integer from tail of string */
{
	char *p;

	for (p=strchr(s, 0); p>s; p--)
		if (!isdigit(p[-1]))
			break;
	if (!*p)
		return (-1);
	return (atoi(p));
}
#endif

memstricmp(void *m, char *s) /* compares memory with a string, ignoring case */
{
	return (memicmp(m, s, strlen(s)));
}

/*
 * strncpy_spacepad() -- like strncpy but space (rather than zero) pads
 */
 char *
strncpy_spacepad(char *d, const char *s, int n)
{
	char *p = d;

	for (; n>0; n--) {
		if ((*p = *s++) == 0)
			break;
		p++;
	}
	for (; n>0; n--)
		*p++ = ' ';
	return (d);
}

isdirsep(int c) /* returns true argument character is '\' or '/' */
{
	return (c == '\\' || c == '/');
}

istraildirsep(char *path) /* returns true if final character in path is '\' or '/' */
{
	char *p;

	return (path && (p = strchr(path, 0)) > path && isdirsep(p[-1]));
}

 char *
strfname(       /* scans back for directory seperator */
	char *path)
{
	char *p= strchr(path, 0);

	while (p > path) {
		if (strchr("\\/:", p[-1]))
			break;
		p--;
	}
	return (p);
}

 char *
strfsuffix(     /* scans for .suffix or end-of-filename if none */
	char *path)
{
	char *fname;
	char *p;

	fname= strfname(path);
	if ((p= strrchr(fname, '.')) == NULL)
		p= strchr(fname, 0);
	return (p);
}

/*
 * append_dirsep() -- appends directory separator to path when appropriate
 *
 * Returns: end of the argument path string
 */
 char *
append_dirsep(char *path)
{
	if (*path) {
		path= strchr(path, 0);
		if (strchr("\\/:", path[-1]) == NULL) {
			*path++ = '\\';
			*path = 0;
		}
	}
	return (path);
}

ahex2bin(int c) /* converts an ascii hex digit to binary */
{
	if ((unsigned)(c - '0') <= 9)
		return (c - '0');
	if ((unsigned)(c-'A') <= 'Z'-'A')
		c += 'a'-'A';   /* tolower(c) */
	c -= 'a';
	if ((unsigned)c < 6)
		return (c + 10 );
	return (-1);
}

/*
 * parse_ulong() -- converts an ascii hex/octal/binary/decimal number to binary
 */
parse_ulong(
	char **pp,
	long *result)
{
	register char *p= *pp;
	register base;          /* 8, 10, 16 */
	int signif;             /* accumulated significant digits */
	int insignif;
	int x;
	int overflow;
	unsigned long val;
	unsigned long tmp;

	if ((unsigned)*p - '0' >= 10)
		return (-1);    /* Got non-digit */

	val= 0L;
	/* assume decimal */
	base= 10;
	signif= 0;
	insignif= 1;
	if (*p == '0') {
		if (tolower(*(p+1)) == 'x') {
			/* hex */
			base= 16;
			insignif= 0;
			p += 2;
		} else if (tolower(*(p+1)) == 'b') {
			/* binary */
			base= 2;
			insignif= 0;
			p += 2;
		} else
			/* octal */
			base= 8;
		while (*p == '0') {
			p++;
			insignif++;
		}
	}
	overflow= 0;
	while ((unsigned)(x= ahex2bin(*p)) < (unsigned)base) {
		tmp = val*base + x;
		if (!overflow)
			if (val && tmp <= val)
				overflow= 1;
			else
				val= tmp;
		signif++;
		p++;
	}
	*pp= p;
	if (!(insignif | signif))
		return (-1);    /* Hexadecimal integer needs at least 1 digit */
	if (base <= 8 && x >= base && x < 10)
		return (-1);    /* Invalid digit in binary or octal integer */
	if (base == 2 && (unsigned)signif > 32)
		return (-1);    /* Binary integer too large */
	if (overflow)
		return (-1);    /* Integer too large */
	*result= val;
	return (0);
}

/*
 * parse_uint() -- converts an ascii hex/octal/decimal number to binary
 */
parse_uint(char **pp, int *result)
{
	long ltmp;

	if (parse_ulong(pp, &ltmp))
		return (-1);
#if _M_IX86 < 300       /*if 16-bit cpu*/
/****   if (sizeof(int) < sizeof(long)) ***/
	if ((unsigned)(ltmp>>16))
		return (-1);    /* Number too large */
#endif
	*result= (int)ltmp;
	return (0);
}

/*
 * parse_long() -- converts an ascii hex/octal/decimal number to binary
 */
parse_long(char **pp, long *result)
{
	char isneg= 0;
	long ltmp;

	switch (**pp) {
	case '+':
		(*pp)++;
		break;
	case '-':
		isneg= 1;
		(*pp)++;
		break;
	}
	if (parse_ulong(pp, &ltmp))
		return (-1);
	if (ltmp < 0)
		return (-1);
	if (isneg)
		ltmp = -ltmp;
	*result = ltmp;
	return (0);
}

/*
 * parse_int() -- converts an ascii hex/octal/decimal number to binary
 */
parse_int(char **pp, int *result)
{
	long ltmp;

	if (parse_long(pp, &ltmp))
		return (-1);
#if _M_IX86 < 300       /*if 16-bit cpu*/
/****   if (sizeof(int) < sizeof(long)) ***/
	if (ltmp < -32768 || ltmp > 32767)
		return (-1);    /* Number too large */
#endif
	*result= (int)ltmp;
	return (0);
}

getulong(char *p, long *result)
{
	if (parse_ulong(&p, result) || *p)
		return (-1);
	return (0);
}

getuint(char *p, int *result)
{
	if (parse_uint(&p, result) || *p)
		return (-1);
	return (0);
}

getlong(char *p, long *result)
{
	if (parse_long(&p, result) || *p)
		return (-1);
	return (0);
}

getint(char *p, int *result)
{
	if (parse_int(&p, result) || *p)
		return (-1);
	return (0);
}

/*
 * ww_count() -- gets character count in a word wrapped line
 *
 * Paragraphs are separated by a newline.  
 * A special case is the first word of a paragraph.
 * White space preceeding the first word of a paragraph, 
 * for up to 'width - 1' characters, is part of the first word.
 * Otherwise when white space preceeding the first word of a 
 * paragraph is 'width' or more characters, an empty line is 
 * returned and the first printing word starts at the beginning 
 * of the next line.  A word which is wider than a line is split.
 *
 * Normally white space following the last word on a line is 
 * eliminiated.  If, however, WW_FINALSPACE is defined to a 
 * non-zero value, and the parsed line is the final line in a buffer, 
 * and the line is not terminated with a newline ('\n'), then white 
 * space, which will not cause the printed line to exceed width, 
 * is returned.
 *
 * Returns:
 * -1 if given nextpp addresses a pointer to the end-of-buffer;
 * Otherwise returns the line's width of zero or more characters,
 * excluding newline ('\n') terminator.
 *
 * If termp is non-NULL, then the variable addressed by termp is 
 * also set to 0 or '\n'.  The value 0 reflects a buffer whose 
 * final word wrapped line is not terminated by a newline ('\n').
 */
#define WW_FINALSPACE 1
#define WW_EXPANDTABS 1
#define WW_TABCOLS 3

ww_count(
	char **nextpp,          /* output: first thing to print on next line */
	register char *p,       /* first thing to print on this line */
	int width,              /* line width */
	int *termp)             /* output: Normally '\n'; For final line, */
{                               /*    set to buffer terminator of 0 or '\n' */
	char *p_sv;
	int nchars_sv;
	int nchars;
	int col;
	int spaces;
	int term;

	if (p == NULL || *p == 0)
		return (-1);
	term= '\n';
	nchars= 0;
	col= 0;
	/* parse whitespace preceeding first word of a paragraph */
	for (;;)
		switch (*p) {
		case 0:
		case '\n':
			nchars= 0;
			goto scannext;
		case ' ':
			spaces= 1;
			goto case_w0spacetab;
		case '\t':
			spaces= WW_TABCOLS - (col % WW_TABCOLS);
		case_w0spacetab:
			p++;
			nchars++;
			if ((col += spaces) >= width) {
				nchars= 0;
				goto scannext;
			}
			break;
		default:
			goto fword;
		}
 fword: /* parse first word of a paragraph */
	for (;;)
		switch (*p) {
		case 0:
		case '\n':
			goto scannext;
		case ' ':
		case '\t':
			goto eoword;
		default:
			p++;
			nchars++;
			if (++col >= width) {
				switch (*p) {
				case 0:
				case ' ':
				case '\t':
				case '\n':
					goto scannext;
				default:
					goto _return;
				}
			}
			break;
		}
 eoword: /* parse space following a complete word */
	nchars_sv= nchars;
	for (;;)
		switch (*p) {
		case 0:
#if WW_FINALSPACE
			/*
			 * return trailing space if buffer not 
			 * terminated by a newline
			 */
			goto scannext;
#else
			/*fall through to truncate space*/
#endif
		case '\n':
			nchars= nchars_sv;
			goto scannext;
		case ' ':
			spaces= 1;
			goto case_w1spacetab;
		case '\t':
			spaces= WW_TABCOLS - (col % WW_TABCOLS);
		case_w1spacetab:
			p++;
			nchars++;
			if ((col += spaces) >= width) {
#if WW_FINALSPACE
				/*
				 * return trailing space if buffer not 
				 * terminated by a newline
				 */
				if (strchr(p, '\n') == NULL) {
#if !WW_EXPANDTABS
					if (col > width)
						/* delete the tab */
						nchars--;
#endif
					goto scannext;
				}
#endif
				nchars= nchars_sv;
				goto scannext;
			}
			break;
		default:
			goto boword;
		}

 boword: /* parse words beyond 1st in paragraph */
	p_sv= p;
	for (;;)
		switch (*p) {
		case 0:
		case '\n':
			goto scannext;
		case ' ':
		case '\t':
			goto eoword;
		default:
			p++;
			nchars++;
			if (++col >= width) {
				switch (*p) {
				case 0:
				case ' ':
				case '\t':
				case '\n':
					goto scannext;
				default:
					nchars= nchars_sv;
					p= p_sv;
					goto _return;
				}
			}
		}
 scannext: /* skip to beginning of next word or line */
	while (*p && (*p == ' ' || *p == '\t'))
		p++;
	switch (*p) {
	case 0:
		term= 0;
		break;
	case '\n':
		p++;
		break;
	}

 _return:
	if (nextpp)
		*nextpp= p;
	if (termp)
		*termp= term;
	return (nchars);
}

 char *
ww_line(        /* retrieves next word wrapped line from buffer */
	char **pp,      /* address of input buffer pointer */
	int width)      /* line width */
{
	static char buf[84];
	register char *d;
	register char *s;
	int count;
	int col;
	int term;

	if ((unsigned)width < 10)
		width= 10;
	if ((unsigned)width > 80)
		width= 80;
	s= *pp;
	if ((count= ww_count(pp, s, width, &term)) < 0)
		return (NULL);
	d= buf;
	col= 0;
	while (count--) {
		if (col >= width)
			goto done;
		switch (*d= *s++) {
		case 0:
			goto done;
#if WW_EXPANDTABS
		case '\t': { /* expand the tab */
			int spaces;

			spaces= WW_TABCOLS - (col % WW_TABCOLS);
			while (spaces--) {
				if (col >= width)
					goto done;
				*d++ = ' ';
				col++;
			}
			} break;
#endif
		default:
			d++;
			col++;
			break;
		}
	}
 done:
	if (term)
		*d++ = (char)term;
	*d= (char)0;
	return (buf);
}

ww_totlines(    /* returns total word wrapped lines in a buffer */
	char *p,        /* input buffer pointer */
	int width)      /* line width */
{
	int totlines= 0;

	while (ww_line(&p, width))
		totlines++;
	return (totlines);
}

/*
 * message file access
 */
#define MSGNO(n)        (n)
#define CMSGBASE        16 /* base of message numbers */

char Cmsg_fname[_MAX_PATH];
FILE *Cmsg_fp;          /* Message file handle */
int Cmsg_peekbuf[2];    /* cmsg_ungetchar() buffer */
int Cmsg_peekdepth;     /* Characters in cmsg_ungetchar() buffer */
long Cmsg_lineno= 1;    /* Most recent message file line read */
long Cmsg_msgno;        /* Most recent message number read */
long Cmsg_msgno_lineno; /* Text file line number of most recent message number */
char Cmsg_buf[2048];    /* Message buffer */
char *Cmsg_ptr;         /* Message buffer input pointer for @LINK messages */
long Cmsg_language;     /* Language number << 20 */

cmsg_syntax(char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	verror_set(MSGSYN|EX_FATAL, fmt, args);
	my_exit(error_level);
	return (-1);
}

cmsg_ungetchar(int c)
{
	if (Cmsg_peekdepth < sizeof(Cmsg_peekbuf)/sizeof(Cmsg_peekbuf[0]))
		Cmsg_peekbuf[Cmsg_peekdepth++] = c;
	return (c);
}

cmsg_getchar(void)
{
	int c;

	for (;;) {
		if (Cmsg_peekdepth)
			c= Cmsg_peekbuf[--Cmsg_peekdepth];
		else if ((c= getc(Cmsg_fp)) == '\n')
			Cmsg_lineno++;
		if (c != '\\')
			break;
		/* Handle escaped newlines */
		if (Cmsg_peekdepth)
			c= Cmsg_peekbuf[--Cmsg_peekdepth];
		else if ((c= getc(Cmsg_fp)) == '\n')
			Cmsg_lineno++;
		if (c != '\n') {
			cmsg_ungetchar(c);
			c= '\\';
			break;
		}
	}
	return (c);
}

/*
 * cmsg_number() -- parses message number
 */
cmsg_number(
	long *result)
{
	unsigned long tmp;
	unsigned long val;
	int overflow;
	int x;
	int c;

	if (!isxdigit( c= cmsg_getchar() )) {
		cmsg_ungetchar(c);
		return (-1);    /* Got non-digit */
	}
	Cmsg_msgno_lineno= Cmsg_lineno;
	val= 0L;
	overflow= 0;
	while ((unsigned)(x= ahex2bin(c)) < (unsigned)CMSGBASE) {
		if (!overflow) {
			tmp = val*CMSGBASE + x;
			if (val && tmp <= val)
				overflow= 1;
			else
				val= tmp;
		}
		c= cmsg_getchar();
	}
	cmsg_ungetchar(c);
	if (overflow) {
		cmsg_syntax("Message number too large");
		return (-1);    /* Integer too large */
	}
	Cmsg_msgno= val;
	if (result)
		*result= val;
	return (0);
}

/*
 * cmsg_cstring() -- gets a 'c' string converting escape sequences to binary
 *
 * Normally, the strlen() of the string in the destination
 * buffer is returned.
 * If the destination buffer's length is too short,
 * the sizeof(buffer)-1 needed is returned.
 */
cmsg_cstring(
	char *d,
	unsigned dlen,
	long *overflowp)        /* must be initialized to 0 before call */
{
	static char c_esc[]= "\a\b\f\n\r\t\v\\\"";
	static char c_special[]= "abfnrtv\\\"";
	char *p;
	int c;
	unsigned length= 0;

	if ((c= cmsg_getchar()) != '"') {
		cmsg_ungetchar(c);
		return (0);     /* Initial character not a double quote */
	}
	for (;;) {
		c= cmsg_getchar();
		switch (c) {
		case -1:
			cmsg_syntax("Unexpected end-of-file in c-string");
			/* fall through */
		case '"':
			if (dlen)
				*d = (char)0;
			return (length);
		case '\\':
			c= cmsg_getchar();
			if (c == '\n')
				continue;
			if ((p= strchr(c_special, c)) != (char *)0)
				c = c_esc[ p - c_special ];
			else if ((unsigned)(c - '0') < 8) {
				int i;

				c -= '0';
				/*
				 * First digit is octal.
				 * Use up to two additional
				 * characters if octal.
				 */
				for (i=0; i<2; i++) {
					int x;

					x= cmsg_getchar();
					if ((unsigned)x - '0' >= 8) {
						cmsg_ungetchar(x);
						break;
					}
					c <<= 3;
					c += x - '0';
				}
			}
		}
		if (dlen > 1) {
			*d++ = (char)c;
			dlen--;
			length++;
		} else if (overflowp)
			(*overflowp)++;
	}
}

cmsg_open(void)
{
	if (Cmsg_fp == NULL)
	if ((Cmsg_fp= fopen(Cmsg_fname, "r")) == NULL)
		error(EX_FATAL, "\"%s\": Message file open error", Cmsg_fname);
	setvbuf(Cmsg_fp, Cmsg_vbuf, _IOFBF, sizeof(Cmsg_vbuf));
	return (0);
}

cmsg_rewind(void)
{
	if (Cmsg_fp == NULL)
		cmsg_open();
	rewind(Cmsg_fp);
	Cmsg_peekdepth= 0;
	Cmsg_msgno_lineno= 0;
	Cmsg_lineno= 1;
	return (0);
}

cmsg_read(
	long *msgno,            /* may be NULL */
	char *buf,              /* may be NULL */
	unsigned blen,          /* may be 0 */
	long *overflowp)        /* may be NULL */
{
	long sv_lineno;
	int c;
	unsigned count;
	unsigned length= 0;
	int got_number= 0;

	if (overflowp)
		*overflowp= 0;
	if (Cmsg_fp == NULL)
		cmsg_open();
	if (buf == NULL)
		blen= 0;
	if (blen)
		*buf= (char)0;
	for (;;) {
		c= cmsg_getchar();
		switch (c) {
		case -1:
			if (got_number)
				return (length);
			return (-1);
		case '/':
			sv_lineno= Cmsg_lineno;
			c= cmsg_getchar();
			switch (c) {
			case '*':
				c= cmsg_getchar();
				for (;;) {
					int c_sv;

					if (c == -1)
						goto eof_in_comment;
					c_sv= c;
					c= cmsg_getchar();
					if (c_sv=='*' && c=='/')
						break;  /* end-of-comment */
				}
				break;
			case '/':
				while ((c= cmsg_getchar()) != '\n') {
					if (c == -1)
			eof_in_comment:
						return (cmsg_syntax(
	"Unexpected end-of-file in comment begun on line %ld",
							sv_lineno
						));
				}
				break;
			default:
				cmsg_ungetchar(c);
				c= '/';
				goto illegalchar;
			}
			break;
		case '"':
			cmsg_ungetchar(c);
			count= cmsg_cstring(buf+length, blen, overflowp);
			length += count;
			blen -= count;
			break;
		default:
			if (isxdigit(c)) {
				cmsg_ungetchar(c);
				if (got_number)
					return (length);
				if (msgno)
					*msgno= -1;
				if (cmsg_number(msgno) == 0)
					got_number= 1;
				break;
			}
			if (!isspace(c))
		illegalchar:
				cmsg_syntax(
					"'%c': Unquoted character", c
				);
			break;
		}
	}
}

cmsg_link(char **pp)    /* checks for an "@LINK nnnn" message */
{
	int tmp;
	int overflow;
	unsigned long msgno;
	char *linkp;
	char *p;

	if ((linkp= strchr(*pp, '@')) == NULL || memcmp(linkp, "@LINK", 5))
		return (0);
	p= skipspace(linkp + 5);
	msgno= 0;
	overflow= 0;
	while ((unsigned)(tmp= ahex2bin(*p++)) < (unsigned)CMSGBASE) {
		unsigned long sv;

		sv= msgno;
		msgno = msgno*CMSGBASE + tmp;
		if (!overflow && msgno < sv)
			overflow= 1;
	}
	*linkp= (char)0;
	*pp= linkp;
	return ((int)msgno);
}

 char *
cmsg_next(
	int end_msgno,  /* returns NULL if >= end_msgno */
	int *msgno)
{
	long number;
	long overflow;
	int len;

	len= cmsg_read(&number, Cmsg_ptr, &Cmsg_buf[sizeof(Cmsg_buf)] - Cmsg_ptr, &overflow);
	if (len == -1)
		return (NULL);
	/* clear language number */
	number &= 0xfffffL;
	if ((unsigned long)number >= (unsigned long)(unsigned)end_msgno)
		return (NULL);
	if (msgno)
		*msgno= (int)number;
	return (Cmsg_buf);
}

 char *
lcmsg(long msgno)       /* returns NULL if message not found */
{
	long number;
	int finalpass;

	/*
	 * If requested message should be further down file from 
	 * previously read, make two passes.  First pass from current 
	 * file position to EOF.  Second pass from BOF to EOF. 
	 * Otherwise do a single pass from BOF to EOF.
	 */
	finalpass= 1;
	if ((unsigned long)msgno > (unsigned long)Cmsg_msgno)
		finalpass= 0;

	for (;;) {
		if (finalpass)
			cmsg_rewind();
		while (cmsg_read(&number, Cmsg_ptr, &Cmsg_buf[sizeof(Cmsg_buf)] - Cmsg_ptr, NULL) != -1)
			if (number == msgno)
				return (Cmsg_buf);
		if (finalpass)
			return (NULL);
		finalpass= 1;
	}
}

 char *
cmsg(int msgno)         /* returns NULL if message not found */
{
	char *p= NULL;
	int nlinks;

	Cmsg_ptr= Cmsg_buf;
	for (nlinks=0; nlinks<4; nlinks++) {
		if ((p=lcmsg( Cmsg_language + (long)(unsigned)msgno )) == NULL)
		if (Cmsg_language)
			/*
			 * re-scan message file for 
			 * default (english) language message
			 */
			p= lcmsg( (long)(unsigned)msgno );
		if (p == NULL)
			break;
		/* Look for a message link */
		if ((msgno= cmsg_link(&Cmsg_ptr)) == 0)
			break;
	}
	return (p);
}

 char *
cmsgex(int msgno)       /* exits if message not found */
{
	char *msgp;
	if ((msgp= cmsg(msgno)) == NULL)
		if (Cmsg_language)
			error(CMDPFX|EX_FATAL,
				"\"%s\": %06lx (or %04x): Message not found",
				Cmsg_fname,
				Cmsg_language + (long)(unsigned)msgno,
				msgno
			);
		else
			error(CMDPFX|EX_FATAL,
				"\"%s\": %04x: Message not found",
				Cmsg_fname,
				msgno
			);
	return (msgp);
}

cmsg_check(void)        /* checks the message file */
{
	char *p;

	/* read all messages for syntax check */
	cmsg_rewind();
	while (cmsg_read(NULL, NULL, 0, NULL) != -1)
		;
	/* examine version message */
	if ((p= cmsg(MSGNO(0x20))) == NULL)
		error(CMDPFX|EX_FATAL, "\"%s\": Version message (20) not found", Cmsg_fname);
	if (strcmp(p, My_verstr))
		error(CMDPFX|EX_FATAL, "\"%s\": Incorrect message file version", Cmsg_fname);
	return (0);
}

 char *
strcodesymplus(struct codesym *csp, int value)
{
	for (;; csp++) {
		if (csp->cs_name == NULL) {
			csp++;
			break;
		}
		if (csp->cs_value == value)
			break;
	}
	return (csp->cs_name);
}

#if CDBLOG
struct codesym Cdb_op_list[]= {
	{ 0x00, "TEST_UNIT_READY" },
	{ 0x01, "REWIND" },
	{ 0x03, "REQUEST_SENSE" },
	{ 0x05, "READ_BLOCK_LIMITS" },
	{ 0x06, "PARK_UNPARK" },
	{ 0x08, "READ" },
	{ 0x0a, "WRITE" },
	{ 0x0b, "SET_CAPACITY" },
	{ 0x0f, "READ_REVERSE" },
	{ 0x10, "WRITE_FILEMARKS" },
	{ 0x11, "SPACE" },
	{ 0x12, "INQUIRY" },
	{ 0x13, "VERIFY" },
	{ 0x14, "RECOVER_BUFFERED_DATA" },
	{ 0x15, "MODE_SELECT_6" },
	{ 0x16, "RESERVE_UNIT" },
	{ 0x17, "RELEASE_UNIT" },
	{ 0x18, "COPY" },
	{ 0x19, "ERASE" },
	{ 0x1a, "MODE_SENSE_6" },
	{ 0x1b, "LOAD_UNLOAD" },
	{ 0x1c, "RECEIVE_DIAGNOSTIC_RESULTS" },
	{ 0x1d, "SEND_DIAGNOSTIC" },
	{ 0x1e, "PREVENT_ALLOW" },
	{ 0x2b, "LOCATE" },
	{ 0x34, "READ_POSITION" },
	{ 0x39, "COMPARE" },
	{ 0x3a, "COPY_AND_VERIFY" },
	{ 0x3b, "WRITE_BUFFER" },
	{ 0x3c, "READ_BUFFER" },
	{ 0x40, "CHANGE_DEFINITION" },
	{ 0x44, "REPORT_DENSITY" },
	{ 0x4c, "LOG_SELECT" },
	{ 0x4d, "LOG_SENSE" },
	{ 0x55, "MODE_SELECT_10" },
	{ 0x5a, "MODE_SENSE_10" },
	{ 0xa0, "REPORT_LUNS" },

	{ 0x07, "INITIALIZE_ELEMENT_STATUS" },
	{ 0x2b, "POSITION_TO_ELEMENT" },
	{ 0xa5, "MOVE_MEDIUM" },
	{ 0xa6, "EXCHANGE_MEDIUM" },
	{ 0xb5, "REQUEST_VOLUME_ELEMENT_ADDRESS" },
	{ 0xb6, "SEND_VOLUME_TAG" },
	{ 0xb8, "READ_ELEMENT_STATUS" },
	{ -1,   NULL },
	{ -1,	"UNKNOWN_CDB" },
};

 char *
str_cdb_op(int cdb_op)
{
	return (strcodesymplus(Cdb_op_list, cdb_op));
}
#endif /*CDBLOG*/

	/* scsi status */
struct codesym Scsi_stat_list[]= {
	{ 0x00,	"GOOD" },
	{ 0x02,	"CHECK CONDITION" },
	{ 0x04,	"CONDITION MET" },
	{ 0x08,	"BUSY" },
	{ 0x10,	"INTERMEDIATE" },
	{ 0x14,	"INTERMEDIATE-CONDITION MET" },
	{ 0x18,	"RESERVATION CONFLICT" },
	{ 0x22,	"COMMAND TERMINATED" },
	{ 0x28,	"QUEUE FULL" },
	{ -1,   NULL },
	{ -1,	"Unknown SCSI status" },
};

 char *
str_scsi_stat(int scsi_stat)
{
	return (strcodesymplus(Scsi_stat_list, scsi_stat));
}

#if NOT_USING
	/* sense error types */
struct codesym Sense_errortype_list[]= {
	{ 0x70, "CURRENT" },
	{ 0x20, "DEFERRED" },
	{ -1,   NULL },
	{ -1,	"UNKNOWN" },
};

 char *
str_sense_errortype(int errortype)
{
	return (strcodesymplus(Sense_errortype_list, errortype & 0x7f));
}
#endif /*NOT_USING*/

#if NOT_USING
	/* sense key flags */
struct codesym Sense_keyflags_list[]= {
	{ 0x10, "RESERVED_0x10" },
	{ 0x20, "ILI" },
	{ 0x40, "EOM" },
	{ 0x80, "FILEMARK" },
	{ -1,   NULL },
	{ -1,	"NONE" },
};

 char *
str_sense_keyflags(int skbyte) /* converts sense key byte flags to ascii */
{
	static char buf[36];	/*"FILEMARK, EOM, ILI, RESVERED_0x10"*/
	char *sep= "";
	int mask;

	skbyte &= 0xf0;	/* clear sense key */
	buf[0]= (char)0;
	for (mask=0x80; mask; mask=(mask>>1)&0xf0)
		if (skbyte & mask) {
			strcat(buf, sep);
			strcat(buf, strcodesymplus(Sense_keyflags_list, mask));
			sep= ", ";
		}
	return (buf);
}
#endif /*NOT_USING*/

	/* sense keys */
struct codesym Sense_key_list[]= {
	{ 0x0,	"NO SENSE" },
	{ 0x1,	"RECOVERED ERROR" },
	{ 0x2,	"NOT READY" },
	{ 0x3,	"MEDIUM ERROR" },
	{ 0x4,	"HARDWARE ERROR" },
	{ 0x5,	"ILLEGAL REQUEST" },
	{ 0x6,	"UNIT ATTENTION" },
	{ 0x7,	"DATA PROTECT" },
	{ 0x8,	"BLANK CHECK" },
	{ 0x9,	"VENDOR SPECIFIC" },
	{ 0xa,	"COPY ABORTED" },
	{ 0xb,	"ABORTED COMMAND" },
	{ 0xc,	"EQUAL" },
	{ 0xd,	"VOLUME OVERFLOW" },
	{ 0xe,	"MISCOMPARE" },
	{ 0xf,	"RESERVED" },
	{ -1,   NULL },
	{ -1,	"UNKNOWN SENSE KEY" },
};

 char *
str_sense_key(int skey)
{
	return (strcodesymplus(Sense_key_list, skey & 0xf));
}

 char *
str_pdt(int pdt)        /* gets peripheral device type string */
{
	static once;
	static char *pdt_names[32];

	if (!once) {
		int i;
		int msgno;
		char *p;
		char *undefmsg= NULL;

		once++;
		/* load peripheral device types from the message file */
		msgno= MSGNO(0x2000);
		for (p=cmsg(msgno); p; p=cmsg_next(MSGNO(0x2021), &msgno)) {
			if (msgno == MSGNO(0x2020)) {
				/*2020 "Undefined device"*/
				undefmsg= strdup(p);
				break;
			}
			pdt_names[msgno & 0x1f] = strdup(p);
		}
		if (undefmsg == NULL)
			undefmsg= "(missing device type message)";
		for (i=0; i<32; i++)
			if (pdt_names[i] == NULL)
				pdt_names[i]= undefmsg;
	}
	return (pdt_names[ pdt & 0x1f ]);
}

struct codesym Driver_stat_list[] = { /* Host interface status */
#if __ASPI__
{ /*SS_PENDING*/             0x00,  "SRB busy" },
{ /*SS_COMP*/                0x01,  "SRB done without error" },
{ /*SS_ABORTED*/             0x02,  "SRB aborted" },
{ /*SS_ABORT_FAIL*/          0x03,  "Unable to abort SRB" },
{ /*SS_ERR*/                 0x04,  "SRB done with error" },
{ /*SS_INVALID_CMD*/         0x80,  "Invalid ASPI command" },
{ /*SS_INVALID_HA*/          0x81,  "Invalid host adapter number" },
{ /*SS_NO_DEVICE*/           0x82,  "SCSI device not installed" },
{ /*SS_INVALID_SRB*/         0xe0,  "Invalid SRB parameter" },
#if WIN32
{ /*SS_BUFFER_ALIGN*/        0xe1,  "Buffer alignment error" },
{ /*SS_SECURITY_VIOLATION*/  0xe2,  "Device access security violation" },
#else /*__DOS__*/
{ /*SS_OLD_MANAGER*/         0xe1,  "DOS ASPI manager doesn't support Win16" },
{ /*SS_ILLEGAL_MODE*/        0xe2,  "ASPI manager doesn't support Win16 real mode" },
#endif
{ /*SS_NO_ASPI*/             0xe3,  "No ASPI manager loaded" },
{ /*SS_FAILED_INIT*/         0xe4,  "ASPI initialization error" },
{ /*SS_ASPI_IS_BUSY*/        0xe5,  "ASPI manager busy" },
{ /*SS_BUFFER_TO_BIG*/       0xe6,  "Buffer too large" },
#endif /*__ASPI__*/
{ -1,                               NULL },
{ -1,                               "Unrecognized driver error" },
};

 char *
str_driver_stat(int driver_stat) /* host interface (aspi or driver) error */
{
#if UNIXWARE
	driver_stat = HIMASK(driver_stat);
#endif
	return (strcodesymplus(Driver_stat_list, driver_stat));
}

	/* host adapter status */
struct codesym Ha_stat_list[]= {
#if __ASPI__ 
/*HASTAT_OK*/			{ 0x00, "No error" },
/*HASTAT_TIMEOUT*/		{ 0x09, "Process timeout" },
/*HASTAT_COMMAND_TIMEOUT*/	{ 0x0b, "Adapter timeout" },
/*HASTAT_MESSAGE_REJECT*/	{ 0x0d, "CDB rejected via MESSAGE REJECT" },
/*HASTAT_BUS_RESET*/		{ 0x0e, "SCSI bus reset detected" },
/*HASTAT_PARITY_ERROR*/		{ 0x0f, "SCSI parity error" },
/*HASTAT_REQUEST_SENSE_FAILED*/	{ 0x10, "REQUEST SENSE retrival error" },
/*HASTAT_SEL_TO*/		{ 0x11, "Select timeout" },
/*HASTAT_DO_DU*/		{ 0x12, "DMA overrun/underrun" },
/*HASTAT_BUS_FREE*/		{ 0x13, "SCSI unexpected bus free" },
/*HASTAT_PHASE_ERR*/		{ 0x14, "SCSI bus phase sequence error" },
#endif /*__ASPI__*/
				{ -1,   NULL },
				{ -1,   "Unrecognized host adapter status" },
};

#if __ASPI__ || LINUX
 char *
str_ha_stat(int ha_stat) /* host adapter error */
{
	return (strcodesymplus(Ha_stat_list, ha_stat));
}
#endif

#if WIN32
/*
 * stroserr() -- returns printable string for a win32 system error
 */
#define OSERR_SIZE 512 /* largest english message seen so far 246 */
#define OSLANGID MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL) /* 0: Language neutral */  
#if KOMMENT
#define OSLANGID MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) /* User default language */
#define OSLANGID MAKELANGID(LANG_NEUTRAL, SUBLANG_SYS_DEFAULT) /* System default language */ 
#endif

 char *
stroserr(
	int oserr,
	char *bufaddr, /* 1015: ERROR_REGISTRY_CORRUPT, in english requires 247 bytes */
	int buflen)
{
	static char unretrieved[]= "Unretrievable system error messge";
	char *p;

	if (buflen) {
		bufaddr[0]= (char)0;
		if (!FormatMessage(
				FORMAT_MESSAGE_FROM_SYSTEM,
				NULL,
				oserr,
				OSLANGID,
				bufaddr,
				buflen,
				NULL)) {
			/*
			 * Normal reasons for failure are:
			 * 122: ERROR_INSUFFICIENT_BUFFER
			 *   The data area passed to a system call is too
			 *   sall.
			 * 317: ERROR_MR_MID_NOT_FOUND
			 *   The system cannot find message text for message number 0x%1
			 *   in the message file for %2.
			 */
			int fmterr = GetLastError();
			char fmtbuf[1024];

			if (!FormatMessage(
					FORMAT_MESSAGE_FROM_SYSTEM,
					NULL,
					fmterr,
					OSLANGID,
					fmtbuf,
					sizeof(fmtbuf),
					NULL)) {
				strcpyn(fmtbuf, unretrieved, sizeof(fmtbuf));
			}
			strcpyn(bufaddr, fmtbuf, buflen);
		}
		/* remove cr lf */
		for (p=bufaddr; *p; p++) {
			switch (*p) {
			case '\r':
				strcpy(p, p+1);
				p--; /* overlayed CR, examine again */
				break;
			case '\n':
				if (!p[1])
					goto done;
				*p= ' ';
				break;
			}
		}
done:
		*p= (char)0;
	}
	return (bufaddr);
}
#endif

 char *
my_strerror(int errno)  /* gets printable string for a system error */
{
	static char buf[40];
	char *p;

	p = NULL;
	if ((unsigned)errno < 256)
		/* 0x2100 through 0x21ff are system errors */
		p = cmsg(MSGNO(0x2100 + errno));
	if (p == NULL)
/*2200 "Undefined error"*/
		p = cmsg(MSGNO(0x2200));
	if (p == NULL)
		p = "Unknown error";
	strcpyn(buf, p, sizeof(buf));
	return (buf);
}

 char *
strmyerror(int myerror)
{
	switch (myerror) {
#if WIN32
	case MYE_WAIT_TIMEOUT:
		return ("WaitForSingleObject(): WAIT_TIMEOUT");
	case MYE_WAIT_ABANDONED:
		return ("WaitForSingleObject(): WAIT_ABONDONED");
	case MYE_WAIT_UNKNOWN:
		return ("WaitForSingleObject(): Undecoded error");
#endif
	default:
		return ("Undecoded My_error");
	}
}

char szError[512];      /* error line buffer */
char szLine[128];       /* general purpose line buffer */

#define MW_WIDTH        76      /* text line width */

 void
my_exit(int code)
{
	close_window_mode();
	if (code) {
		char *p;
		char *q;

		q= szError;
		while ((p= ww_line(&q, MW_WIDTH)) != NULL)
			printf("%s", p);
		printf("\n");
	}
	logf("exit(%d)\n", code);
	exit(code);
}

 char *
error_format(
	int code)
{
	static char buf[256];
	int i;
	char *p;
	int errtype;
	int errnosv = errno;
#if MSOS
	int oserr = GetLastError();
#endif

	/* assume use error type embedded in code */
	errtype = code & ERTYPE;
	if (errtype == ERTYPE)
		/* use error type from Err_type global */
		errtype = Err_type & ERTYPE;

	p= buf;
	switch (errtype) {
	case 0:
	default:
		*p = 0;
		break;
	case MYER: /* My_error */
		my_snprintf(buf, sizeof(buf), "Error %d: %s", My_error, strmyerror(My_error));
		break;
	case UXER: /* error in errno */
		my_snprintf(buf, sizeof(buf), "System error %d: %s", errno, my_strerror(errno));
		break;
#if WIN32
	case OSER: { /* error from GetLastError() */
			int oserr= GetLastError();
			char osbuf[OSERR_SIZE];

			my_snprintf(buf, sizeof(buf),
				"Operating system error %u: %s",
				oserr, stroserr(oserr, osbuf, sizeof(osbuf))
			);
		} break;
#endif
	case DVRER: /* ASPI or driver error */
		my_snprintf(buf, sizeof(buf), "Driver status 0x%02x: %s", Driver_stat, str_driver_stat(Driver_stat));
		break;
	case HAER: /* Host adapter error */
		my_snprintf(buf, sizeof(buf), "Host adapter status 0x%02x: %s", Ha_stat, str_ha_stat(Ha_stat));
		break;
	case SCER: /* SCSI error */
		p += my_snprintf(p, &buf[sizeof(buf)] - p, "SCSI status 0x%02x", Scsi_stat);
		if (Scsi_stat == 0x02 /*SSTAT_CHECK_CONDITION*/) {
			p += my_snprintf(p, &buf[sizeof(buf)] - p, ": Sense");
			for (i=0; i<SENSE_LEN; i++)
				p += my_snprintf(p, &buf[sizeof(buf)] - p, " %02x", Rsense[i]);
			p += my_snprintf(p, &buf[sizeof(buf)] - p,
				":\nSense key 0x%x: %s, ASC/ASCQ 0x%02x/0x%02x",
				Rsense[2], str_sense_key(Rsense[2]), Rsense[12], Rsense[13]
			);
		}
		break;
	}
#if MSOS
	SetLastError(oserr);
#endif
	errno = errnosv;
	return (buf);
}

char *plmsg_cmderrorfmt= "%s error: ";

verror_set(int code, char *fmt, va_list args)
{
#define EBUFSIZE sizeof(szError)
	char logbuf[ EBUFSIZE ];
	char *ebufbase;
	char *p;
	char *q;

	ebufbase = szError;
	if (code & LOGONLY)
		ebufbase = logbuf;
	p= ebufbase;
	if (code & CMDPFX) {
		p += my_snprintf(
			p, &ebufbase[ EBUFSIZE ] - p,
			plmsg_cmderrorfmt,
			My_cmdname
		);
	}
	if (code & MSGSYN) {
		p += my_snprintf(
			p, &ebufbase[ EBUFSIZE ] - p,
			"%s(%ld): Message file syntax error: ",
			Cmsg_fname,
			Cmsg_lineno
		);
	}
	if (code & INIPFX) {
		p += my_snprintf(
			p, &ebufbase[ EBUFSIZE ] - p,
			"%s: [%s]: ",
			Inifname,
			my_section(NULL)
		);
	}
	if (fmt)
		p += my_vsnprintf(p, &ebufbase[ EBUFSIZE ] - p, fmt, args);
	q = error_format(code);
	if (*q)
		p += my_snprintf(p, &ebufbase[ EBUFSIZE ] - p, (p>ebufbase) ?": %s" :"%s", q);
	if (code & LOGONLY)
		logf("%s\n", ebufbase);
	else
		error_level= code & 0xff;
	return (-1);
}

error_msgset(int msgno, ...)
{
	va_list args;

	va_start(args, msgno);
	return (verror_set(EX_FATAL, cmsgex(msgno), args));
}

error_set(char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	return (verror_set(EX_FATAL, fmt, args));
}

error(int code, char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	verror_set(code, fmt, args);
	if (code &= 0x00ff)
		my_exit(code);
	return (0);
}

/*
 * Pattern matching
 */

/* Pattern match flags */
#define MMF_UNBRACERECURSE  0x80 /* unbrace recursion */
#define MMF_SYNTAXPASS      0x40 /* syntax check pass */
#define MMF_IGNORECASE      0x20 /* case insensitive */
#define MMF_FILENAME        0x10 /* filename pattern */
#define MMF_COLONWILD       0x4  /* ':' wild  cardable */
#define MMF_SLASHWILD       0x2  /* '\' or '/' wild cardable */
#define MMF_PREPC           0x1  /* Prepare character for use in comparison */
#define PATESC          0x100 /* flags a escaped character (preceded with a "^") */

char match_errbuf[40];

match_msgset(int msgno)
{
	if (match_errbuf[0] == 0)
		strcpyn(match_errbuf, cmsgex(msgno), sizeof(match_errbuf));
	return (-1);
}

/*
 * match_putc() -- puts character to a pattern buffer; handles '^' escape
 */
match_putc(char **ppat, int c)
{
	if (c & ~PATESC) {
		if (c & PATESC)
			*(*ppat)++ = '^';
		*(*ppat)++ = (char)c;
	} else {
		c &= ~PATESC;
		**ppat = 0;
	}
	return (c);
}

/*
 * match_is_wcardable() -- test whether a character is wild cardable
 */
match_is_wcardable(int mmflags, int c)
{
	if (mmflags & MMF_FILENAME)
		switch (c) {
#if MSOS
		case ':':
			if ( ! (mmflags & MMF_COLONWILD))
				return (0);
			break;
		case '\\':
#endif
		case '/':
			if ( ! (mmflags & MMF_SLASHWILD))
				return (0);
			break;
		}
	return (c);
}

/*
 * match_prepc() -- prepare character from pattern or string being matched
 */
match_prepc(int mmflags, int c)
{
	c &= 0xff;
#if MSOS
	if ((mmflags & MMF_FILENAME) && c == '/')
		return ('\\');
#else /*unix*/
	if ((mmflags & MMF_FILENAME) && c == '\\')
		return ('/');
#endif
	if (mmflags & (MMF_FILENAME | MMF_IGNORECASE))
		c = tolower(c);
	return (c);
}

/*
 * match_getc() -- gets character from a pattern string handle '^' escape
 */
match_getc(int mmflags, char **ppat)
{
	int escape = 0;
	int c;
	int c2;

	if ((c = (unsigned char)**ppat) != 0) {
		(*ppat)++;
		/* look for escape */
		if (c == '^' && (c2 = **ppat) != 0) {
			escape = PATESC;
			c = c2;
			(*ppat)++;
		}
	}
	if (mmflags & MMF_PREPC)
		c = match_prepc(mmflags, c);
	return (escape | c);
}

/*
 * match_lookc() -- gets character from a pattern string w/o pointer increment
 */
match_lookc(int mmflags, char *pat)
{
	return match_getc(mmflags, &pat);
}

/*
 * match_strchr() -- returns address of 1st occurance of character in a pattern
 */
 char *
match_strchr(int mmflags, char *pat, int c)
{
	c = match_prepc(mmflags, c);
	do {
		if (match_lookc(mmflags | MMF_PREPC, pat) == c)
			return (pat);
	} while (match_getc(mmflags, &pat));
	return (NULL);
}

/*
 * match_strcmp() -- compare a string with a pattern from the .ini
 * 
 * Each element in str is compared with the corrosponding element 
 * in pat unsigned 8-bit values.  Escape characters in pat are 
 * ignored.
 * 
 * The value 0 is returned if the strings compare, 
 * Otherwise, when MMF_FILENAME or MMF_IGNORECASE is set, the value 
 * 1 is returned when str is greater than pat, or -1 is returned 
 * when str is less than pat.  Otherise, when both MMF_FILENAME and 
 * MMF_IGNORECASE are clear, the lexical difference of str and 
 * pat is returned.
 */
match_strcmp(int mmflags, char *str, char *pat)
{
	int diff;

	while (*str || *pat)
		if ((diff= match_prepc(mmflags, *str++) - (match_getc(mmflags|MMF_PREPC, &pat) & ~PATESC)) != 0) {
			if (mmflags & (MMF_FILENAME | MMF_IGNORECASE))
				if (diff < 0)
					diff = -1;
				else
					diff = 1;
			return (diff);
		}
	return (0);
}

/*
 * match_unescapestr() -- clears a string of escape of escapes ('^')
 */
 char *
match_unescapestr(char *str)
{
	char *s = str;
	char *d = str;

	while ((*d++ = (char)match_getc(0, &s)) != 0)
		;
	return (str);
}

/*
 * amatch() -- matches a string with a pattern with control chararacters *?[
 *
 * Returns:
 *    1 match
 *    0 mismatch
 *   -1 syntax error
 */
amatch(
	int mmflags,   /* MMF_* flags */
	char *pattern, /* pattern containing *?[ */
	char *string)  /* string being matched */
{
	register c = 0; /* character from string being matched */
	int pc;         /* pattern character */
	char *pat = pattern;
	char *str = string;
	char *subbeg;    /* pattern substring begin */
	char *subfinal;
	char *subend;
	char *p;
	int nmatch = 1; /* assume match */

	mmflags |= MMF_PREPC; /* tell match_getc() to prep character */

	for (;;) {
		c = match_prepc(mmflags, *str);
		pc = match_getc(mmflags, &pat);
		switch (pc) {
		case 0: /* end-of-pattern */
			if (c)
				goto mismatch;
			goto done; /* match */

		case '*':
			/* look at next pattern character - scan past contiguous '*' */
			while ((pc = match_lookc(mmflags, pat)) == '*')
				match_getc(mmflags, &pat);
			while ((c = match_prepc(mmflags, *str)) != 0) {
				if ( ! match_is_wcardable(mmflags, c))
					goto mismatch;
				if (pc)
					/* additional pattern after '*' */
					if ((nmatch = amatch(mmflags, pat, str) ) != 0)
						goto done; /* match or syntax error */
				str++;
			}
			if (pc) /* additional pattern after '*' */
				goto mismatch;
			/* '*' is final pattern character */
			goto done; /* match */

		case '?':
			if (c == 0 || !match_is_wcardable(mmflags, c))
				goto mismatch;
			break;

		case '[':
			if (match_lookc(mmflags, pat) == ']')
				goto try_literal; /* "[<empty>]" */

			subbeg = pat; /* subbeg -> first after '[' */
			subfinal = pat; /* final character between [] */
			p = pat;
			while ((pc = match_lookc(mmflags, p)) != ']') {
				if (pc == 0) {
					/* terminator */
					/*printf("Missing ]\n");*/
					goto try_literal; /* take "[..." as literal */
				}
				subfinal = p; /* final character between [] */
				match_getc(mmflags, &p);
			}
			subend = p;     /* subend -> ']' */
			pat = p;
			match_getc(mmflags, &pat); /* pat -> next after ']' */
			p = subbeg;    /* p -> first after '[' */
			while (p < subend) {
				int pc2;

				pc = match_getc(mmflags, &p) & ~PATESC;
				/* look for "a-z" */
				if (p < subfinal && match_lookc(mmflags, p) == '-') {
					match_getc(mmflags, &p); /* discard '-' */
					pc2 = match_lookc(mmflags, p) & ~PATESC;
					if (c >= pc && c <= pc2)
						goto next; /* c is within range */
				}
				if (c == pc)
					goto next;
			}
			goto mismatch;

		default:
		try_literal:
			pc &= ~PATESC;
			if (c != pc) {
	mismatch:
				nmatch = 0; /* mismatch */
				goto done;
			}
			break;
		}
	next:
		if (*str)
			str++;
	}
 done:
	return (nmatch);
}

/*
 * unbrace() -- decodes braced strings; for each unbraced string, calls argument func
 *
 * "...{}...", "...{", and unbalanced "}" are taken as literal
 *
 * Returns:
 *    Normally the number of unbraced patterns which match the 
 *    argument string is returned and is zero if none matched.
 *    On syntax error, the value -1 is returned.
 */
#if OPENSERVER || UNIXWARE || LINUX || __DOS__
#define UNBRACE_FUNC_CAST (int (*)(int, char *, void *))
#else
#define UNBRACE_FUNC_CAST
#endif

unbrace(
	int mmflags,   /* MMF_* flags */
	char *pattern, /* pattern containing {*?[ */
	int (*func)(int mmflags, char *pattern, void *arg),
	void *arg)
{
	char buf[512];
	register c;     /* character from string being matched */
	char *pat = pattern;
	char *p;
	char *d = buf;  /* pointer to "unbraced" buffer */
	int nmatch = 0; /* start with no match */
	int msgno;

	if (!(mmflags & MMF_UNBRACERECURSE)) {
		/* clear error message */
		match_errbuf[0] = 0;
		mmflags |= MMF_UNBRACERECURSE;
	}

#define UNBRACE_PUTC(c) { \
		if (c & PATESC) \
			*d++ = '^'; \
		*d++ = (char)c; \
	}
	for (;;) {
		switch (c = match_getc(mmflags, &pat)) {
		case 0:     /* Pattern terminator */
			*d = 0; /* terminate buffer */
			/* end of pattern */
			if (pat > pattern) {
				/* Non-empty pattern */
				if (!(mmflags & MMF_SYNTAXPASS)) {
					int stat;

#if KOMMENT
					logf("unbrace: \"%s\"\n", buf);
#endif
					stat = func(mmflags, buf, arg);
					if (stat < 0) {
						nmatch = -1;
						goto done;
					}
					nmatch += stat;
				}
			}
			goto done;

		case '[':
			UNBRACE_PUTC(c);
			/* find matching ] */
			do {
				c= match_getc(mmflags, &pat);
				UNBRACE_PUTC(c);
				if (c == 0) { /* terminator */
					msgno = MSGNO(0x8131);
/*8131 "Pattern syntax: Missing ]"*/
					goto syntax_error;
				}
			} while (c != ']');
			break;

		case '{': { /* e.g.: a{b,c{d,e}f,}{g,h}i */
			int brace_level;
			char *subbeg; /* substring begin */
			char *subend; /* substring end */
			int c2;
			int stat;  /* recursive unbrace() status */

			/* Take "*{}*" or "*{" as literal */
			if ((c2 = match_lookc(mmflags, pat)) == 0 || c2 == '}')
				goto unbrace_putc;

			brace_level = 1;

			p = pat; /* initial character between {} */
			subbeg = pat;   /* initial substring character */

			/* find matching } */
			while (brace_level) {
				switch (match_getc(mmflags, &p)) {
				case 0:
					msgno = MSGNO(0x8132);
/*8132 "Pattern syntax: Missing }"*/
					goto syntax_error;
			syntax_error:
					match_msgset(msgno);
					return (-1);
				case '{':
					brace_level++;
					break;
				case '}':
					brace_level--;
					break;
				case '[':
					/* find matching ] */
					while ((c= match_getc(mmflags, &p)) != ']')
						if (c == 0) { /* terminator */
							msgno = MSGNO(0x8131);
/*8131 "Pattern syntax: Missing ]"*/
							goto syntax_error;
						}
					break;
				}
			}
			pat = p; /* pat -> first after "{x{y}z}" */
			p = subbeg;     /* p -> first after { */

			/* Try and match each ',' separated pattern between '{' and '}' */
			brace_level = 1;

			/* copy substrings to buf */
			while (p < pat) {
				subend = p;
				switch (c = match_getc(mmflags, &p)) {
				case '{':
					++brace_level;
					break;
				case ',':
				case '}':
					if (brace_level == 1) {
						char ch_sv;
						/*
						 * unbrace nested braced expressions
						 * unbrace remainder of expression
						 */
						ch_sv = *subend;
						*subend = 0;
						strcpy(d, subbeg);
						*subend = ch_sv;
						strcat(d, pat);
						stat = unbrace(mmflags, buf, func, arg);
						subbeg = p;
						if (stat < 0) {
							nmatch = stat;
							goto done; /* syntax error */
						}
						/* sum matches */
						nmatch += stat;
					}
					if (c == '}') {
						if (--brace_level <= 0)
							goto done;
						break;
					}
					break;
				case '[':
					/* find matching ] */
					while ((c = match_getc(mmflags, &p)) != ']')
						if (c == 0) { /* terminator */
							msgno = MSGNO(0x8131);
/*8131 "Pattern syntax: Missing ]"*/
							goto syntax_error;
						}
					break;
				}
			}
		} break;

		default:
		unbrace_putc:
			UNBRACE_PUTC(c);
			break;
		}
	}
 done:
	return (nmatch);
}

/*
 * fmatch() -- finds whether a given pattern matches a filename
 *
 * Accepts {[*? pattern control characters
 * Note: drive letter wildcarding not supported
 *
 * Returns:
 *    Number of unbraced matches which is zero if none.
 *    On error, the value -1 is returned and a messege is set in the 
 *    global match_errbuf.
 */
fmatch(
	char *pattern, /* Pattern */
	char *fname)   /* Filename */
{
	return (unbrace(
		MMF_FILENAME,   /* MMF_* values */
		pattern, /* pattern */
		UNBRACE_FUNC_CAST amatch,  /* function to call with de-braced pattern */
		fname)   /* filename */
	);
}

/*
 * findunique() -- finds whether a given pattern represents a unique directory entry
 *
 * Accepts {[*? pattern control characters
 * Note: drive letter wildcarding not supported
 *
 * Returns:
 *    Number of matching directory entries which is zero if none.
 *    If one or more matching entries is found, firstfoundpath 
 *    contains the first matching name and wdfp has the 
 *    associated find data.  Otherwise firstfoundpath and wdfp 
 *    are not altered.
 *    On error, the value -1 is returned and a messege is set in the 
 *    global match_errbuf.
 */
struct finduniquedata {
	char *fud_ospathbase;       /* Path construction buffer passed to Find*File() */
	char *fud_ospathtail;       /* Tail of 'ospathbase' -- NULL before first recursion */
	char *fud_firstfoundpath;   /* Returned first found path: Optionally NULL */
	WIN32_FIND_DATA *fud_wfdp;  /* Returned find data; Optionally NULL */
};

_findunique(
	int mmflags,            /* MMF_* values */
	char *pattern,          /* Search path pattern remainder */
	struct finduniquedata *fudp)
{
	static WIN32_FIND_DATA fbuf;
	char *ospathtail; /* current pointer to path construction buffer */
	char *osentry;          /* Where to append '\*.*' or '\fname' */
	HANDLE dhandle;         /* directory handle */
	int nmatch = 0;
	char *entrypattern = NULL;
	char *p;
	int c;

	if ((ospathtail = fudp->fud_ospathtail) == NULL) {
		/* Level 0 call - initialize */
		ospathtail = fudp->fud_ospathbase;
		/*
		 * Copy "drive:" entry in search pattern 
		 * to head of path construction buffer.
		 */
		p = pattern;
		while ((c = match_getc(mmflags, &p) & ~PATESC) != 0 && !isdirsep(c))
			if (c == ':') {
				while (pattern < p)
					*ospathtail++ = (char)match_getc(mmflags, &pattern);
				break;
			}
	}
	/* scan past leading \, .\, ..\, .......\ entries */
	for (;;) {
		/* prescan ....\ */
		p = pattern;
		while ((c = match_getc(mmflags, &p) & ~PATESC) == '.')
			;
		if (!isdirsep(c))
			break;
		/* copy ....\ */
		while (pattern < p)
			*ospathtail++ = (char)match_getc(mmflags, &pattern);
	}
	/* Missing pattern or pattern terminated with a path separator */
	if (*pattern == (char)0)
		return (0);
	/*
	 * Set osentry to where '*.*' can be appended, and to where 
	 * to re-plug in a null on exit of this recursion
	 */
	osentry = ospathtail;
	/*
	 * Copy next search path pattern entry to 
	 * tail of path construction buffer.
	 */
	while ((c = match_lookc(mmflags, pattern) & ~PATESC) != 0 && !isdirsep(c))
		*ospathtail++ = (char)match_getc(mmflags, &pattern);
	*ospathtail = (char)0;
	/* save pattern and append "*.*" for FindFirstFile */
	if ((entrypattern = alloca(strlen(osentry)+1)) == NULL) {
		match_msgset(MSGNO(0x8133));
/*8133 "Insufficient stack memory"*/
		return (-1);
	}
	strcpy(entrypattern, osentry);
	strcpy(osentry, "*.*");
	/* Find first matching entry */
	if ((dhandle = FindFirstFile(fudp->fud_ospathbase, &fbuf)) != (HANDLE)-1) {
		do {
			/* Files biginning with '.' must be explicitly matched */
			if (fbuf.cFileName[0] == '.' && (match_lookc(mmflags, entrypattern) & ~PATESC) != '.')
				continue;
			if (amatch(MMF_FILENAME, entrypattern, fbuf.cFileName) <= 0)
				continue;
			strcpy(osentry, fbuf.cFileName);
			if (!*pattern) {
				/* end of pattern string - depth reached */
				/* return optional information */
				if (fudp->fud_wfdp) {
					memcpy(fudp->fud_wfdp, &fbuf, sizeof(*fudp->fud_wfdp));
					fudp->fud_wfdp = NULL;
				}
				if (fudp->fud_firstfoundpath) {
					strcpy(fudp->fud_firstfoundpath, fudp->fud_ospathbase);
					fudp->fud_firstfoundpath = NULL;
				}
				nmatch++;
			} else {
				if (fbuf.dwFileAttributes & _A_SUBDIR)
				if (strcmp(fbuf.cFileName, "."))
				if (strcmp(fbuf.cFileName, "..")) {
					int stat;
					char *save = fudp->fud_ospathtail;

					fudp->fud_ospathtail = strchr(osentry, 0);
					stat = _findunique(
						mmflags,
						pattern,
						fudp
					);
					fudp->fud_ospathtail = save;
					nmatch += stat;
					if (stat < 0) {
						/* error, bail out */
						nmatch = stat;
						break;
					}
				}
			}
		} while (FindNextFile(dhandle, &fbuf) != FALSE);
		FindClose(dhandle);
	}
	*osentry = (char)0;
	return (nmatch);
}

findunique(
	char *pattern,          /* Search path pattern */
	char *firstfoundpath,   /* Returned first found path: Optionally NULL */
	WIN32_FIND_DATA *wfdp)  /* Returned find data; Optionally NULL */
{
	char buf[_MAX_PATH];
	struct finduniquedata fud;


	fud.fud_ospathbase = buf;
	fud.fud_ospathtail = NULL; /* null for initial entry to _findunique */
	fud.fud_firstfoundpath = firstfoundpath;
	fud.fud_wfdp = wfdp;

	return (unbrace(
		MMF_FILENAME, 
		pattern,        /* search path pattern remainder */
		UNBRACE_FUNC_CAST _findunique,
		&fud)
	);
}

/*
 * inqmatch() -- finds whether a given pattern matches string in inquiry buffer
 *
 * Accepts {[*? pattern control characters
 *
 * Returns:
 *    Number of unbraced matches which is zero if none.
 *    On error, the value -1 is returned and a messege is set in the 
 *    global match_errbuf.
 */
inqmatch(
	char *pattern,  /* Pattern */
	unsigned char *inqbuf)   /* 36 byte inquiry buffer */
{
	char string[28+1];

	/* Make a terminated copy of inquiry for string for matching */
	strcpyn(string, &((char *)inqbuf)[8], 28+1);

	return (unbrace(
		0,       /* MMF_* values */
		pattern, /* pattern */
		UNBRACE_FUNC_CAST amatch,  /* function to call with de-braced pattern */
		string)  /* inquiry string */
	);
}

int inqgen_count; /* used to generate only the first unbraced pattern */
int inqgen_limit;

/*
 * Generate a inquiry string from a pattern
 */
inqgenerate(
	int mmflags,   /* MMF_* flags */
	char *pattern, /* pattern containing *?[ */
	char *inqbuf)  /* Destination 36-byte inquiry simulation buffer or NULL */
{
	int pc;               /* pattern character */
	char *pat = pattern;
	char *d = &inqbuf[8]; /* destination string pointer */
	char *subbeg;         /* pattern substring begin */
	char *subfinal;
	char *subend;
	char *p;
	int len;              /* length of 28-byte inquiry string */

	if (inqgen_limit && inqgen_count >= inqgen_limit)
		return (0); /* ignore */
	inqgen_count++;

	mmflags |= MMF_PREPC; /* tell match_getc() to prep character */

	for (;;) {
		pc = match_getc(mmflags, &pat);
		switch (pc) {
		case 0: /* end-of-pattern */
			goto done; /* match */

		case '*':
			continue;

		case '?':
			pc = '0';
			if (d == &inqbuf[33])
				pc = '.';
			goto inqgen_putc;

		case '[':
			if (match_lookc(mmflags, pat) == ']')
				goto inqgen_putc; /* "[<empty>]" */

			subbeg = pat; /* subbeg -> first after '[' */
			subfinal = pat; /* final character between [] */
			p = pat;
			while ((pc = match_lookc(mmflags, p)) != ']') {
				if (pc == 0) {
					/* terminator */
					/*printf("Missing ]\n");*/
					pc = '[';
					goto inqgen_putc; /* take "[..." as literal */
				}
				subfinal = p; /* final character between [] */
				match_getc(mmflags, &p);
			}
			subend = p;     /* subend -> ']' */
			pat = p;
			match_getc(mmflags, &pat); /* pat -> next after ']' */
			p = subbeg;    /* p -> first after '[' */
			pc = 0xff;     /* Large value to be replaced w/ lesser */
			while (p < subend) {
				int c;

				if ((c = match_getc(mmflags, &p) & ~PATESC) < pc)
					pc = c;
				/* look for "a-z" */
				if (p < subfinal && match_lookc(mmflags, p) == '-')
					match_getc(mmflags, &p); /* discard '-' */
			}
			/* pc has character of least value represented by "[..]" expression */
			goto inqgen_putc;

		default:
		inqgen_putc:
			if (d >= &inqbuf[8+28])
				error(EX_FATAL|INIPFX, "\"%s\": Pattern length exceeds 28", pattern);
			if (inqbuf)
				*d = (char)pc;
			d++;
			break;
		}
	}
 done:
	len = d - &inqbuf[8]; /* number of ASCII bytes */
	if (inqbuf)
		/* set additional length field */
		inqbuf[4] = (char)(3+len);
	if (len != 28)
		error(EX_FATAL|INIPFX, "\"%s\": Expected pattern length 28: Actual %d", pattern, len);
	return (len) ?1 :0;
}

/*
 * inqgetpdt() -- parses peripheral type; adjusts pointer to inquiry pattern
 *
 * Returns:
 *    If successful, the argument pattern pointer is adjusted to the 
 *    first character in the inquiry pattern string, and a peripheral 
 *    device type (PDT_*) is returned.
 *    On error, the value -1 is returned and the argument pointer is 
 *    undisturbed
 */
inqgetpdt(
	char **patp) /* address of pointer to InquiryStd string */
{
	static struct codesym pdtlist[] = {
		{ 1, "Tape" },
		{ 8, "Changer" },
		{ -1, NULL }
	};
	struct codesym *cs;
	char *p;

	if ((p = match_strchr(0, *patp, ':')) == NULL)
		return (-1); /* Missing peripheral device type separator */

	*p = 0; /* temporarily replace ':' with null */
	for (cs=pdtlist; cs->cs_name; cs++)
		if (!match_strcmp(MMF_IGNORECASE, cs->cs_name, *patp))
			break; /* match */
	*p = ':'; /* restore ':' */
	if (!cs->cs_name)
		return (-1); /* Unrecognized peripheral device type */

	*patp = p+1; /* return pointer to beginning of inquiry pattern */
	return (cs->cs_value); /* peripheral device type */
}

inqsimulate(
	char *pattern,           /* InquiryStd string */
	unsigned char *inqbuf)   /* Standard 36-byte inquiry buffer or NULL to syntax check */
{
	int pdt;
	int rc;
	char *p = pattern;

	match_errbuf[0] = 0;
	pdt = inqgetpdt(&p);
	if (pdt < 0)
		error(EX_FATAL|INIPFX, "\"%s\": Missing or invalid Peripheral Type", pattern);
	inqgen_count = 0;
	inqgen_limit = 0; /* allow unbracing of all patterns */
	if (inqbuf) {
		inqgen_limit = 1; /* allow only first braced pattern to be unbraced */
		memset(inqbuf, 0, 36);
		inqbuf[0] = (char)pdt;
	}
	if (!*p)
		error(EX_FATAL|INIPFX, "\"%s\": Missing inquiry pattern string", pattern);
	if ((rc = unbrace(
		0,       /* MMF_* values */
		p,       /* pattern */
		UNBRACE_FUNC_CAST inqgenerate,  /* function to call with de-braced pattern */
		inqbuf        /* inquiry buffer */
	)) < 0)
		error(EX_FATAL|INIPFX, "\"%s\": %s", pattern, match_errbuf);
	return (rc);
}

/*
 * hexmatch() -- finds whether a given pattern matches a hexized inquiry string
 *
 * Accepts {[*? pattern control characters
 *
 * Returns:
 *    Number of unbraced matches which is zero if none.
 *    On error, the value -1 is returned and a messege is set in the 
 *    global match_errbuf.
 */
hexmatch(
	char *pattern,  /* Pattern */
	char *string)   /* hex string generated from inquiry */
{
	return (unbrace(
		MMF_IGNORECASE, /* MMF_* values */
		pattern, /* pattern */
		UNBRACE_FUNC_CAST amatch,  /* function to call with de-braced pattern */
		string)  /* inquiry string */
	);
}

/*
 * Generate a inquiry string from a hex pattern
 */
hexgenerate(
	int mmflags,   /* MMF_* flags */
	char *pattern, /* pattern containing *?[ */
	char *dummy)
{
	int pc;               /* pattern character */
	char *pat = pattern;
	char *inqbuf = NULL;  /* Destination inquiry simulation buffer or NULL */
	char *d = inqbuf;     /* destination string pointer */
	char *subbeg;         /* pattern substring begin */
	char *subfinal;
	char *subend;
	char *p;
	int len;              /* length of 28-byte inquiry string */

	UNREFERENCED(dummy);

	mmflags |= MMF_PREPC; /* tell match_getc() to prep character */

	for (;;) {
		pc = match_getc(mmflags, &pat);
		switch (pc) {
		case 0: /* end-of-pattern */
			goto done; /* match */

		case '*':
			continue;

		case '?':
			pc = '0';
			if (d == &inqbuf[33])
				pc = '.';
			goto hexgen_putc;

		case '[':
			if (match_lookc(mmflags, pat) == ']')
				goto hexgen_putc; /* "[<empty>]" */

			subbeg = pat; /* subbeg -> first after '[' */
			subfinal = pat; /* final character between [] */
			p = pat;
			while ((pc = match_lookc(mmflags, p)) != ']') {
				if (pc == 0) {
					/* terminator */
					/*printf("Missing ]\n");*/
					pc = '[';
					goto hexgen_putc; /* take "[..." as literal */
				}
				subfinal = p; /* final character between [] */
				match_getc(mmflags, &p);
			}
			subend = p;     /* subend -> ']' */
			pat = p;
			match_getc(mmflags, &pat); /* pat -> next after ']' */
			p = subbeg;    /* p -> first after '[' */
			pc = 0xff;     /* Large value to be replaced w/ lesser */
			while (p < subend) {
				int c;

				if ((c = match_getc(mmflags, &p) & ~PATESC) < pc)
					pc = c;
				/* look for "a-z" */
				if (p < subfinal && match_lookc(mmflags, p) == '-')
					match_getc(mmflags, &p); /* discard '-' */
			}
			/* pc has character of least value represented by "[..]" expression */
			goto hexgen_putc;

		default:
		hexgen_putc:
			if (!isxdigit(pc))
				error(EX_FATAL|INIPFX, "\"%s\": '%c': Invalid character in hex pattern", pattern, pc);
			if (d >= &inqbuf[255])
				error(EX_FATAL|INIPFX, "\"%s\": Pattern length exceeds 255", pattern);
#if KOMMENT
			if (inqbuf)
				*d = (char)pc;
#endif
			d++;
			break;
		}
	}
 done:
	len = d - &inqbuf[0];
	return (len) ?1 :0;
}

/*
 * hexgetoffset() -- parses offset field; adjusts pointer to hex pattern
 *
 * Returns:
 *    If successful, the argument pattern pointer is adjusted to the 
 *    first character in the hex pattern string, and an offset into 
 *    the target hex version of inquiry data is returned.
 *    On error, the value -1 is returned and the argument pointer is 
 *    undisturbed
 */
hexgetoffset(
	char **patp) /* address of pointer to InquiryStdHex string */
{
	char *p;
	int offset;

	p = *patp;
	if (parse_int(&p, &offset))
		return (-1); /* Missing offset value */
	if ((unsigned)offset > 254)
		return (-1); /* Offset too big */
	p = skipspace(p);
	if (*p != ':')
		return (-1); /* Missing offset separator */

	*patp = p+1; /* return pointer to beginning of hex pattern */
	return (offset);
}

hexsyntax(
	char *pattern)           /* InquiryStdHex string */
{
	int offset;
	int rc;
	char *p = pattern;

	match_errbuf[0] = 0;
	offset = hexgetoffset(&p);
	if ((unsigned)offset > 254)
		error(EX_FATAL|INIPFX, "\"%s\": Missing or invalid Hex String Offset", pattern);
	if (!*p)
		error(EX_FATAL|INIPFX, "\"%s\": Missing hex string pattern", pattern);
	if ((rc = unbrace(
		0,       /* MMF_* values */
		p,       /* pattern */
		UNBRACE_FUNC_CAST hexgenerate,  /* function to call with de-braced pattern */
		NULL        /* inquiry buffer */
	)) < 0)
		error(EX_FATAL|INIPFX, "\"%s\": %s", pattern, match_errbuf);
	return (rc);
}

inqcfr_simulate( /* Simulates Seagate Controller Firmware Revision Inquiry */
	char *oem,                 /* 1 to 3 digit OEM field */
	unsigned char *inqcfr_buf) /* 23-byte Seagate Controller Firmware Revision buffer */
{
	memset(inqcfr_buf, 0, 23);
	if (oem && *oem) {
		char dflt[] = {
			 1, 0, 0, 17, 16,
			'0','0','0','0',
			'0','1','/','0','1','/','9','9',
			'-','0',' ',' '
		};
		memcpy(inqcfr_buf, dflt, sizeof(dflt));
		/* Overlay the '0',' ',' ' */
		memcpy(&inqcfr_buf[18], oem, strlen(oem));
	}
	return (0);
}

 char *
my_strdup(char *s, char *caller)
{
	char *p;

	if ((p = strdup(s)) == NULL)
		error(EX_FATAL, cmsgex(MSGNO(0x8010)), caller);
/*8010 "TAPEDRV8010 error: \"%s\": Out of memory"*/
	return (p);
}

/*
 * find_exe_path() -- finds executable's path
 *
 * When exename argument does not contain path separators, 
 * directories in listed PATH enviroment variable is searched.
 *
 * Under Windows 9x and later versions of DOS, the full path to 
 * the executible is set in argv[0].  For Windows NT and unix 
 * varients, argv[0] has the name typed in at the shell.
 *  
 * Returns: If pathbuf is NULL, returns address of a malloced 
 *    buffer containing executable's path, otherwise returns 
 *    argument pathbuf.
 */
#if WIN32 || unix
 char *
find_exe_path(
	char *exename, /* typically argv[0] */
	char *pathbuf) /* buffer in which to construct path */
{
	static char me[] = "find_exe_path";
#if MSOS
	static char exe_suffix[] = ".exe";
#define PATHCOLON ';'

#else /*unix*/
#define PATHCOLON ':'
#endif
	char wrkbuf[_MAX_PATH]; /* buffer in which to construct path */
	char *p;
	int found = 0;

	if (exename == NULL)
		exename = "";
	/* Don't check names with path separators */
	if (strpbrk(exename, PATHSEPSTR) == NULL) {
#if MSOS
		char *envpath = NULL;
		char *p_next = ""; /* search local directory first */
#else /*unix*/
		char *p_next = getenv("PATH");
#endif
		struct stat buf;

		for (;;) {
			wrkbuf[0] = 0;
#if MSOS
			if (envpath)
#endif
			{
				p = p_next;
				if ((p_next = strchr(p, PATHCOLON)) == NULL)
					p_next = strchr(p, 0);
				strcpyn(wrkbuf, p, _MAX_PATH);
				if (p_next - p < _MAX_PATH)
					wrkbuf[ p_next - p ] = 0;
				append_dirsep(wrkbuf);
			}
			strcatn(wrkbuf, exename, _MAX_PATH);
#if MSOS
			p = strfsuffix(wrkbuf);
			strcpyn(p, exe_suffix, _MAX_PATH - (p - wrkbuf));
#endif
			if (stat(wrkbuf, &buf) == 0 && (buf.st_mode & S_IFMT) == S_IFREG) {
				found = 1;
				break; /* found */
			}
			/* skip semicolon in PATH */
			if (*p_next++ == (char)0) {
#if MSOS
				/* at e-o-path string */
				if (envpath)
					break; /* already did path */
				envpath = getenv("PATH");
				/* use PATH from environment */
				p_next = envpath;
#else /*unix*/
				/* at e-o-path string */
				break;
#endif
			}
		}
	}
	if (!found)
		strcpyn(wrkbuf, exename, _MAX_PATH);
#if MSOS
	p = strfsuffix(wrkbuf);
	strcpyn(p, exe_suffix, _MAX_PATH - (p - wrkbuf));
#endif
	if (pathbuf)
		return strcpy(pathbuf, wrkbuf);
	else
		return my_strdup(wrkbuf, me);
}
#endif /*WIN32 || unix*/

#define SIZE_PROFLINE   128 /*Max profile line length*/

FILE *prof_ifp;
FILE *prof_ofp;
char prof_ifname[_MAX_PATH];
char prof_nl[]= "\r\n";
int prof_flags;

	/* values for prof_flags */
#define PROFF_EXISTS    1
#define PROFF_SECT_NL   2       /* true: write a newline before [section] */

str_newext(     /* copys directory/file and replaces extension */
	char *dest,             /* target buffer */
	char *src,              /* directory/file[.EXT] */
	char *ext)              /* new .EXT */
{
	/* copy directory/file to buffer */
	if (strcpyn(dest, src, _MAX_PATH))
		return (-1);
	/* find old file suffix (or end of string) and remove */
	*strfsuffix(dest) = 0;
	/* concatinate new .EXT */
	return (strcatn(dest, ext, _MAX_PATH));
}

prof_open(char *fname)
{
	/*
	 * If profile file open and new profile file is different,
	 * close the current.
	 */
	if (prof_ifp) {
		if (!stricmp(prof_ifname, fname)) {
			rewind(prof_ifp);
			return (0);
		}
		fclose(prof_ifp);
		prof_ifp= NULL;
	}

	prof_flags &= ~PROFF_EXISTS;
	if ((prof_ifp= fopen(fname, "rb")) == NULL)
		return (-1);    /* file doesn't exist */

	/* Save profile file name */
	strcpy(prof_ifname, fname);
	prof_flags |= PROFF_EXISTS;
	return (0);     /* success */
}

prof_gets(char *line)
{
	for (;;) {
		char *p;

		if (prof_ifp == NULL
		 || fgets(line, SIZE_PROFLINE, prof_ifp) == NULL) {
			line[0]= (char)0;
			return (-1);
		}
		while (isspace(*line))
			strcpy(line, line+1);
		if ((p= strpbrk(line, prof_nl)) != NULL)
			*p= (char)0;
		switch (*line) {
		case 0:
		case '=':
			break;  /* empty line */
		case '[':
			if ((p= strchr(line, ']')) != NULL)
				*p= (char)0;
			goto done;      /* section */
		case ';':
			goto done;      /* comment */
		default:
			if ((p= strchr(line, '=')) == NULL)
				break;
			/* remove space between <entry> and = */
			while (p > line && isspace(p[-1])) {
				strcpy(p-1, p);
				p--;
			}
			*p= (char)0;    /* remove '=' */
			goto done;
		}
	}
done:
/*      printf("get: {%s}\n", line); */
	return (0);
}

prof_puts(char *line)
{
	char *nl;

#if TEST
	printf("put: {%s}\n", line);
#endif
	switch (*line) {
	case 0:         /* empty line */
	case '=':
		return (0);
	case '[':       /* section */
		nl= (prof_flags & PROFF_SECT_NL) ?prof_nl :"";
		prof_flags |= PROFF_SECT_NL;
		return (fprintf(prof_ofp, "%s%s]%s", nl,line, prof_nl));
	case ';':       /* comment */
		return (fprintf(prof_ofp, "%s%s", line, prof_nl));
	default:        /* entry */
		/* insert '=' as in <entry>=<value> */
		*strchr(line, 0)= (char)'=';
		return (fprintf(prof_ofp, "%s%s", line, prof_nl));
	}
}

/*
 * prof_getsection() -- scans .INI for [section]
 *
 * Returns: 0 if section found; -1 otherwise
 */
prof_getsection(char *line, char *section, int copyflag)
{
	while (prof_gets(line) == 0) {
		if (line[0] == '[' && !stricmp(line+1, section))
			return (0);     /* section match */
		if (copyflag)
			prof_puts(line);
	}
	return (-1);
}

/*
 * prof_getnextentry() -- gets next <entry>=<value> in current section
 *
 * Returns: Non-zero: size of entry; 0: end-of-section
 */
prof_getnextentry(char *line, int copyflag)
{
	/* Scan for a matching section entry til end-of-section */
	while (prof_gets(line) == 0)
		switch (*line) {
		case '[':
			return (0);     /* the start of a new section */
		case ';':
			if (copyflag)
				prof_puts(line);
			continue;       /* comment */
		default:
			return (strlen(line));
		}
	return (0);     /* end of file */
}

/*
 * prof_getentry() -- scans current section for <entry>=<value>
 *
 * Returns: 0 if entry found; -1 otherwise
 */
prof_getentry(char *line, char *entryname, int copyflag)
{
	/* Scan for a matching entry 'til end-of-section */
	while (prof_getnextentry(line, copyflag)) {
		if (!stricmp(entryname, line))
			return (0); /* match */
		if (copyflag)
			prof_puts(line);
	}
	return (-1);
}

/*
 * prof_enumentries() -- returns a list of all entries in a section
 *
 * Each entry in the list terminated by a null character and
 * the list is terminated by a null character.
 * That is, an empty list has a single null, and the last 
 * string in a non-empty list is followed by two nulls.
 *
 * Returns:  Number of bytes copied to the argument buffer not 
 * including the final null.
 */
prof_enumentries(
	char *dest,     /* address of buffer for entry names */
	int destlen)    /* size of buffer */
{
	char line[SIZE_PROFLINE];
	char *dest_sv= dest;
	int namelen;

	/* Starting from 1st line following a [section] ... */
	for (;;) {
		if (destlen > 0)
			*dest= (char)0; /* list terminating null */
		if (destlen <= 2)
			break;          /* No more string space */
		if ((namelen= prof_getnextentry(line, 0 /*copyflag*/)) == 0)
			break;          /* end of section */
		/*
		 * line has the form: <entry>'\0'<value>'\0'
		 * Copy the <entry> to buffer.
		 */
		if (namelen > destlen - 2)
			namelen= destlen - 2;
		memcpy(dest, line, namelen);
		dest += namelen;
		*dest++ = (char)0;
		destlen -= namelen + 1;
	}
	return ((int)(dest - dest_sv));
}

/*
 * myGetProfileString() -- gets string for profile entry or a list of entry names
 *
 * Returns: Bytes copied to argument string buffer excluding terminating null
 */
myGetPrivateProfileString(
	char *section,          /* section name */
	char *entry,
	char *default_val,
	char *dest,
	int destlen,
	char *fname)
{
	int n;
	char line[SIZE_PROFLINE];

	if (default_val == NULL)
		default_val= "";
	if (section == NULL || dest == NULL)
		goto return_default;
	if (prof_open(fname))
		goto return_default;
	if (prof_getsection(line, section, 0 /*copyflag*/))
		/* section not found; return default string */
		goto return_default;

	/* If entry is NULL, return list of entries for the section */
	if (entry == NULL)
		n= prof_enumentries(dest, destlen);
	else {
		/* Return 'value' for argument 'entry' */
		if (prof_getentry(line, entry, 0 /*copyflag*/) == 0)
			/* entry is seperated from value by a null character */
			default_val= strchr(line, 0) + 1;

return_default:
		n= strlen(default_val) + 1;
		if (n > destlen)
			n= destlen;
		if (n > 0) {
			strncpy(dest, default_val, n);
			n--;
			dest[n]= (char)0;
		}
	}
#if CLOSE_PROFILE
	fclose(prof_ifp);
	prof_ifp= NULL;
#endif
	return (n);
}

/*
 * myGetProfileInt() -- returns an integer value for a profile entry
 */
myGetPrivateProfileInt(
	char *section,
	char *entry,
	int default_int,
	char *fname)
{
	char line[SIZE_PROFLINE];

	if (myGetPrivateProfileString(section, entry, "",
				line, sizeof(line), fname) == 0)
		return (default_int);
	return (atoi(line));
}

prof_write(
	char *section,
	char *entry,    /* If NULL, delete section */
	char *value)    /* If NULL, delete entry */
{
	char line[SIZE_PROFLINE];

	if (prof_getsection(line, section, 1 /*copyflag*/) == 0) {
		/* [section] exists */
		if (entry) {
			/* Delete, update, or append entry */

			/* Copy section */
			prof_puts(line);
			prof_getentry(line, entry, 1 /*copyflag*/);
			if (value && fprintf(prof_ofp, "%s=%s%s",
					entry, value, prof_nl) <= 0)
				return (-1);
			if (line[0] == '[' && prof_puts(line) < 0)
				return (-1);
		} else {
			/* Entry is NULL, delete entire section */
			while (prof_gets(line) == 0)
				switch (line[0]) {
				case '[':
					/* found the next section */
					prof_puts(line);
					goto delete_done;
				case ';':
					/*
					 * Windows preserves comments 
					 * when a section is deleted
					 */
					prof_puts(line);
					break;
				}
		delete_done:;
		}
		/* Dump rest of .INI file out to the temp file. */
		while (prof_gets(line) == 0)
			if (prof_puts(line) < 0)
				return (-1);
	} else {
		/*
		 * [section] does not exist.
		 * Append [section] and entry=string to the file
		 */
		if (entry && value)
		if (fprintf(prof_ofp, "%s[%s]%s%s=%s%s",
				(prof_flags & PROFF_SECT_NL) ?prof_nl :"",
				section, prof_nl,
				entry, value, prof_nl) <= 0)
			return (-1);
	}
	return (0);     /* success */
}

myWritePrivateProfileString(
	char *section,
	char *entry,    /* If NULL, delete section */
	char *value,    /* If NULL, delete entry */
	char *ini_fname)
{
	char tmp_fname[_MAX_PATH];
	char bak_fname[_MAX_PATH];
	int stat= -1;   /* assume fail */
	void (*sv_sigint_handler)(int);

	if (section == NULL)
		return (FALSE);

	if (prof_open(ini_fname) && (prof_flags & PROFF_EXISTS))
		return (FALSE); /* Can't open existing profile */

	/* Build backup file name */
	if (str_newext(bak_fname, ini_fname, ".BAK"))
		goto fail;              /* buffer overflow */

	/* Build temporary file name */
	if (str_newext(tmp_fname, ini_fname, ".$$$"))
		goto fail;              /* buffer overflow */

	/* Open temporary file for output */
	if ((prof_ofp= fopen(tmp_fname, "wb")) == NULL)
		goto fail;

	prof_flags &= ~PROFF_SECT_NL;   /* true: write a newline before [section] */
	stat= prof_write(section, entry, value);
fail:
	if (prof_ifp) {
		fclose(prof_ifp);
		prof_ifp= NULL;
	}
	if (prof_ofp) {
		fclose(prof_ofp);
		prof_ofp= NULL;
	}

	if (stat && tmp_fname[0]) {
		/* Delete output file */
		unlink(tmp_fname);
		return (FALSE);
	}

	/* Remove old backup profile */
	unlink(bak_fname);
	/* disable <Ctrl><Break> */
	sv_sigint_handler= signal(SIGINT, SIG_IGN);
	rename(ini_fname, bak_fname);
	/* Output file is new profile */
	rename(tmp_fname, ini_fname);
	/* restore <Ctrl><Break> */
	signal(SIGINT, sv_sigint_handler);
	/* Set a flag saying profile exists */
	prof_flags |= PROFF_EXISTS;
	return (TRUE);
}

char Inifname[_MAX_PATH];
char Inisection_main[] = "tapedrv";
char *Inisection_ptr = Inisection_main;

my_find_profile(void)
{
	static char auto_profname[] = "Tapedrv.ini";
	char fnamebuf[_MAX_PATH];
	int nfound;
	static once;

	if (once)
		return (0);
	once = 1;
	if (Inifname[0]) {
		if ((nfound = findunique(Inifname, fnamebuf, NULL)) != 1)
			goto notfound;
		goto didfind; /* Initialzation file given by command line argument */
	}

	/*
	 * .INI Search order:
	 *    Current directory,
	 *    /p <firmware_path>,
	 *    executable directory
	 */
	/* Look Tapedrv.ini in current directory */
	strcpyn(Inifname, auto_profname, sizeof(Inifname));
	if ((nfound= findunique(Inifname, fnamebuf, NULL)) > 1)
		goto notfound;
	if (nfound == 1)
		goto didfind; /* Found Tapedrv.ini in current directory */

	/* Look for <firmware_path>/Tapedrv.ini */
	strcpyn(Inifname, Fw_path, sizeof(Inifname) - 1);
	append_dirsep(Inifname);
	strcatn(Inifname, auto_profname, sizeof(Inifname));
	if ((nfound= findunique(Inifname, fnamebuf, NULL)) > 1)
		goto notfound;
	if (nfound == 1)
		goto didfind; /* Found <firmware_path>/Tapedrv.ini */

	/* Look for exepath/Tapedrv.ini */
	strcpyn(Inifname, Cmsg_fname, sizeof(Inifname));
	*strfname(Inifname) = 0;
	strcatn(Inifname, auto_profname, sizeof(Inifname));
	if ((nfound= findunique(Inifname, fnamebuf, NULL)) > 1)
		goto notfound;
	if (nfound == 1)
		goto didfind; /* Found exepath/Tapedrv.ini */
notfound:
	if (nfound > 1)
		error(CMDPFX|EX_FATAL, "\"%s\": Pattern matches multiple files",
			Inifname
		);
	else
		error(CMDPFX|EX_FATAL, "\"%s\": No matching file found",
			Inifname
		);
	return (-1);
didfind:
	strcpy(Inifname, fnamebuf);
	return (0);
}

 char *
my_section(char *section)
{
	char *osection;

	my_find_profile();
	osection= Inisection_ptr;
	if (section)
		Inisection_ptr= section;
	return (osection);
}

my_profget_str(
	char *entry,
	char *default_val,
	char *dest,
	int destlen)
{
	if (destlen <= 1) {
		if (destlen == 1)
			*dest = 0;
		return (0);
	}
	return (myGetPrivateProfileString(
		Inisection_ptr,
		entry,
		default_val,
		dest,
		destlen,
		Inifname)
	);
}

/*
 * my_prof_parse_strlist() -- parses a .ini file string list
 *
 * my_prof_parse_strlist() converts an input comma separated text 
 * string into a pointer list of null terminated strings.
 * On return the address of each string is set in argv[].
 * Each comma separated string, may be an unquoted string, a 
 * quoted or a combination.  For unquoted strings, leading and 
 * trailing white space is stripped.  Between commas, unquoted 
 * and quoted strings are concatinated.  Strings that contain 
 * literal commas must be quoted.  A quoted string is closed by
 * by the end of line
 * For example, the input string text string
 *    "a", b, " c,d" e "fg",, "z
 * is converted to
 *    argv[0] = "a"
 *    argv[1] = "b"
 *    argv[2] = " c,defg"
 *    argv[3] = ""
 *    argv[4] = "z"
 *
 * Returns:
 *     The number of pointers set in argv
 */
my_prof_parse_strlist(
	int no_strip_esc,/* true: dont strip escape characters */
	char **argv,     /* argv to be populated */
	int maxarg,      /* number of elements in argv */
	char *str,       /* Input string */
	int *p_overflow) /* Returned number of additional argv entries needed */
{
	char *argp;
	char *s;
	char *d;
	int c;
	int argc;    /* returned argument count */
	int overflow;

	argc = 0;
	overflow = 0;
	s = d = str;
	do {
		if( argc < maxarg )
			argv[ argc++ ] = d;
		else
			overflow++;
		argp = d;

		do {
			/* Skip leading space */
			while ((c = match_lookc( 0, s )) == ' ')
				match_getc( 0, &s);
			if (c == '"') {
				/* Append quoted string */
				match_getc( 0, &s );
				while ((c = match_getc( 0, &s )) != 0 && c != '"')
					match_putc(&d, c);
			} else {
				/* Append unquoted string */
				char *d_space = NULL; /* address of trailing space */

				while ((c = match_lookc( 0, s ))!=0 && c!=',' && c!='"') {
					match_getc( 0, &s );
					if (isspace(c & ~PATESC)) {
						if (d_space == NULL)
							d_space = d;
					} else
						d_space = NULL;
					match_putc(&d, c);
				}
				if (c == ',') /* scan past comma */
					match_getc(0, &s);
				if (d_space)
					d = d_space; /* Back up to final white space */
			}
		} while (c && c != ',');
		*d = 0; /* Convert comma to string terminator */
		if (!no_strip_esc) {
			match_unescapestr(argp);
			d = argp + strlen(argp);
		}
		d++;
	} while (c);  /* Not end of input */

	if (p_overflow)
		*p_overflow = overflow;
	return (argc);
}

/*
 * Returns number of comma separated strings read 
 * Which (if maxarg is greater than 0) is always at least 1 
 * string.  The string will be empty if the parameter was 
 * not found.
 */
my_profget_strlist_unquote(
	int no_strip_esc, /* true: dont strip escape characters */
	char *entry,
	char **argv,     /* argv to be populated */
	int maxarg,      /* number of elements in argv */
	char *dest,
	int destlen)
{
	my_profget_str(entry, "", dest, destlen);
	return (my_prof_parse_strlist(no_strip_esc, argv, maxarg, dest, NULL));
}

my_profget_str_unquote(
	int no_strip_esc, /* true: don't strip escape characters */
	char *entry,
	char *dest,
	int destlen)
{
	char line[SIZE_PROFLINE];
	char *argv[1];

	my_profget_str(entry, "", line, sizeof(line));
	my_prof_parse_strlist(no_strip_esc, argv, NELEM(argv), line, NULL);
	strcpyn(dest, argv[0], destlen);
	return (strlen(dest));
}

my_profget_int(
	char *entry,
	int default_int)
{
	char line[SIZE_PROFLINE];
	int tmp= 0;

	if (my_profget_str_unquote(0, entry, line, sizeof(line)) == 0
	 || getint(skipspace(line), &tmp))
		return (default_int);
	return (tmp);
	/*return (atoi(line));*/
}

my_profput_str(
	char *entry,
	char *value)
{
/*      if (!(Sw & SW_UPDATE)) */
/*              return (TRUE); */
	return (myWritePrivateProfileString(
		Inisection_ptr,
		entry,
		value,
		Inifname)
	);
}

my_profput_int(
	char *entry,
	int value)
{
	char line[32];

	my_snprintf(line, sizeof(line), "%d", value);
	return (my_profput_str(
		entry,
		line)
	);
}

my_profput_hexint(
	char *entry,
	int value)
{
	char line[32];

	my_snprintf(line, sizeof(line), "0x%x", value);
	return (my_profput_str(
		entry,
		line)
	);
}

#if __DOS__
my_profio_ide(int put) /* gets/puts .INI values for ide_aspi() */
{
	char *section_save;
	static char searchnm[]= "ide_search_length";
	static char portnm[]= "ide0_port";
	static char irqnm[]= "ide0_irq";
	static got;
	int tmp;
	int i;

	section_save = my_section("tapedrv");
	switch (put) {
	case 0: /*GET*/
		/*"ide_search_length"*/
		tmp = my_profget_int(searchnm, -42);
		if (tmp != -42) {
			ide_search_length= tmp;
			got= 1;
		}
		if (ide_search_length > 4)
			ide_search_length= 4;
		for (i=0; i<4; i++) {
			char c= (char)('0' + i);

			portnm[3]= c;
			irqnm[3]= c;
			ide_phys_ports[i]= my_profget_int(portnm, ide_phys_ports[i]);
			ide_phys_irqs[i]= my_profget_int(irqnm, ide_phys_irqs[i]);
		}
		break;
	case 2: /*PUT if not got*/
		if (got)
			break;
		/* fall through */
	case 1: /*PUT*/
	default:
		/*"ide_search_length"*/
		my_profput_int(searchnm, ide_search_length);
		for (i=0; i<4; i++) {
			char c= (char)('0' + i);

			portnm[3]= c;
			irqnm[3]= c;
			my_profput_hexint(portnm, ide_phys_ports[i]);
			my_profput_int(irqnm, ide_phys_irqs[i]);
		}
		break;
	}
	my_section(section_save);
	return (0);
}
#endif /*__DOS__*/

/* inidev -- device information preloaded from .ini file */
struct inidev {
	struct inidev *idev_link;    /* linked list */
	char *idev_section;          /* device section name */
	char *idev_inquirystd;       /* InquiryStd */
	char *idev_inquiryhex;       /* InquiryStdHex */
	char idev_inquirycfr_oem[4]; /* InquiryCFR_OEM Seagate 3-digit CFR OEM */
};

struct inidev *Inidev_list;

log_inidev_list(void)
{
	struct inidev *idev;

	for (idev=Inidev_list; idev; idev=idev->idev_link) {
		logf("[%s]\n   InquiryStd=\"%s\"\n", idev->idev_section, idev->idev_inquirystd);
		if (idev->idev_inquiryhex)
			logf("   InquiryStdHex=\"%s\"\n", idev->idev_inquiryhex);
		if (idev->idev_inquirycfr_oem[0])
			logf("   InquiryCFR_OEM=\"%s\"\n", idev->idev_inquirycfr_oem);
	}
	return (0);
}

my_prof_preload(void) /* loads certain .INI entries */
{
	static char me[] = "prof_preload";
	static char device[] = "devices00";
	char *section_save;
	char line[SIZE_PROFLINE];
	struct inidev *idev_prev = NULL;
	struct inidev *idev;
	int j;
	int n;
	char *p;

	section_save = my_section("tapedrv");
	logf("Inifname: \"%s\"\n", Inifname);
	if ((Magic = my_profget_int("magic", -1)) != MAGIC) {
		char buf[24];

		sprintf(buf, (Magic == -1) ?"<none>" :"%#x", Magic);
		error(EX_FATAL, "\"%s\": [%s]: Bad or missing magic number: Need magic=0x%x: Got %s",
			Inifname,
			Inisection_ptr,
			MAGIC,
			buf
		);
	}

#if __DOS__
	my_profio_ide(0); /* get .INI values for ide_aspi() */
#endif
	/*
	 * Get [tapedrv] deviceNN entries
	 * Build an ini device list.
	 * Finally populate devicelist with InquiryStd strings
	 */
	for (j=0; j<100; j++) {
		sprintf(device, "devices%02d", j);
		my_profget_str( device, "", line, sizeof(line) );
		p = strtrim(line);
		for (n=0; ;n++) {
			p = skipspace(p);
			if (!*p)
				break;
			/* skip the device section name */
			while (*p && !isspace(*p))
				p++;
		}
		if (n == 0)
			continue;

		if ((idev = malloc(n*sizeof(struct inidev) + strlen(line) + 1)) == NULL)
			error(EX_FATAL, cmsgex(MSGNO(0x8010)), me);
/*8010 "TAPEDRV8010 error: \"%s\": Out of memory"*/
		memset(idev, 0, sizeof(*idev));
		if (Inidev_list == NULL)
			Inidev_list = idev;

		p = (char *)&idev[n];
		strcpy(p, line);
		while (n--) {
			p = skipspace(p);
			idev->idev_section = p;
			while (*p && !isspace(*p))
				p++;
			if (*p)
				*p++ = 0;
			idev->idev_inquirystd = NULL;
			idev->idev_link = NULL;
			if (idev_prev)
				idev_prev->idev_link = idev;
			idev_prev = idev++;
		}
	}
	/* Read and save standard inquiry strings */
	for (idev=Inidev_list; idev; idev=idev->idev_link) {
		my_section(idev->idev_section);

		my_profget_str_unquote(1, "InquiryStd", line, sizeof(line));
		idev->idev_inquirystd = my_strdup( line, me );
		/* Use inqsimulate to check syntax */
		inqsimulate(idev->idev_inquirystd, NULL);

		if (my_profget_str_unquote(1, "InquiryStdHex", line, sizeof(line))) {
			idev->idev_inquiryhex = my_strdup( line, me );
			/* Use inqhexsimulate to check syntax */
			hexsyntax(idev->idev_inquiryhex);
		}

		my_profget_str_unquote(0, "InquiryCFR_OEM",
			idev->idev_inquirycfr_oem, 
			sizeof(idev->idev_inquirycfr_oem)
		);
	}
	my_section(section_save);
	log_inidev_list();
	log_memstats();
	return (0);
}

struct box {
	int     y;              /* Upper left row (0-24) */
	int     x;              /* Upper left column (0-79) */
	int     height;         /* Rows (max=25) */
	int     width;          /* Columns (max=80) */
};

	/* Window structure */
struct win {
	char    title[30];
	int     num_win;
	short   *save;          /* Previous window area save buffer */
	struct box bbox;        /* Border box */
	int     border;         /* 0 = no border, 1 = single line, 2 = double line */
	int     battr;          /* Border attributes */
	int     wattr;          /* Window attributes */
	int     mattr;          /* Message attributes */
	int     w_crsrow;       /* text cursor */
	int     w_crscol;
};

struct win *Win_cur;

/* Screen characters  */
	/* Graphics character for single line box */
#define BXS_UPRL 218 /*0xda '\332'*/
#define BXS_UPRR 191 /*0xbf '\277'*/
#define BXS_LWRL 192 /*0xc0 '\300'*/
#define BXS_LWRR 217 /*0xd9 '\331'*/
#define BXS_HBAR 196 /*0xc4 '\304'*/
#define BXS_VBAR 179 /*0xb3 '\263'*/
#define BXS_TEEL 195 /*0xc3 '\303'*/
#define BXS_TEER 180 /*0xb4 '\264'*/
	/* Double line box */
#define BXD_UPRL 201 /*0xc9 '\311'*/
#define BXD_UPRR 187 /*0xbb '\273'*/
#define BXD_LWRL 200 /*0xc8 '\310'*/
#define BXD_LWRR 188 /*0xbc '\274'*/
#define BXD_HBAR 205 /*0xcd '\315'*/
#define BXD_VBAR 186 /*0xba '\272'*/
#define BXD_TEEL 204 /*0xcc '\314'*/
#define BXD_TEER 185 /*0xb9 '\271'*/


	/* Graphics characters */
#define RARROWH     16  /* Right arrowhead */
#define LARROWH     17  /* Left arrowhead */
#define UDARROW     18  /* Up-down arrow symbol */
#define UARROWSYM   24  /* Up arrow symbol */
#define DARROWSYM   25  /* Down arrow symbol */
#define UARROWH     30  /* Up arrowhead symbol */
#define DARROWH     31  /* Down arrowhead symbol */
#define LITEBOX     176 /* Light hatch area */
#define MEDIUMBOX   177 /* Horizontal background field */
#define HSCROLLFIELD 177 /* Horizontal background field */
#define HEAVYBOX    176 /* Heavy hatch area */
#define BLANK       219 /* Solid hatch area */
#define VSCROLLBAR  219 /* Vertical scroll bar */
#define HALFBLOCK   220 /* Half block */
#define HALFSQUARE  223 /* Half square */
#define CHECK       251 /* Check */
#define HOTSPOT     254 /* Square */
#define HSCROLLBAR  254 /* Horizontal scroll bar */

#define SPACE       32

#define NEWLINE      '\n'
#define ESCAPE       27

box_scroll(
	struct box *bx,
	int nlines,
	int attr)
{
	if (nlines)
		co_scroll(nlines, bx->y, bx->x, bx->height, bx->width, attr);
	return (0);
}

box_cls(
	struct box *bx,
	int attr)
{
	co_scroll(CO_CLEAR, bx->y, bx->x, bx->height, bx->width, attr);
	return (0);
}

 void
colorstr(int attr, char *s) /* Print a string in color at current cursor */
{
	int row;
	int col;
	int wide;
	int count;

	co_cursor_getpos(&row, &col);
	wide = co_getcols() - col;      /* width of display */
	if ((count= ww_count(NULL, s, wide, NULL)) >= 0)
		co_putline(row, col, count, s, attr);
	co_cursor_hide();
}

 void
colorline(int row, int col, int attr, char *s)
{
	co_putfill(row, col, co_getcols()-col, ' ', FG_WHITE|BG_BLACK /*7*/);
	co_cursor(row, col);
	colorstr(attr, s);
	logf("line%d: %s%s", row, s, (!s || !strchr(s, '\n')) ?"\n" :"");
}

 void
show_cell(int use, int row, int col, char *string, int width)
{
	int attr;
	int i;

	if (use)
		attr = FG_BLACK | BG_WHITE;
	else
		attr = FG_WHITE | BG_BLACK /*7*/;

	i=0;
	if (string)
		for (; i<width; i++) {
			if (!*string || *string == '\n')
				break;
			co_putca(row, col++, *string++, attr);
		}
	for (; i<width; i++)
		co_putca(row, col++, ' ', attr);
}

w_getcursor(int *row, int *col)
{
	struct win *wp;

	if ((wp= Win_cur) == NULL) {
		*row = *col = 0;
		return (-1);
	}
	*row = wp->w_crsrow;
	*col = wp->w_crscol;
	return (0);
}

 void
w_cursor(int row, int col)
{
	struct win *wp;

	if ((wp= Win_cur) == NULL)
		return;
	wp->w_crsrow= row;
	wp->w_crscol= col;
}

 struct win *
w_gettextbox(struct box *tbox)
{
	struct win *wp;

	if ((wp= Win_cur) == NULL)
		return (NULL);
	tbox->y = wp->bbox.y + 1;
	tbox->x = wp->bbox.x + 2;
	tbox->height = wp->bbox.height - 2;
	tbox->width = wp->bbox.width - 4;

	return (wp);
}

 void
w_cls(void)     /* clears the text area */
{
	struct win *wp;
	struct box tbox;

	if ((wp= w_gettextbox(&tbox)) == NULL)
		return;
#if OLDSTUFF
	co_scroll(CO_CLEAR, tbox.y, tbox.x, tbox.height, tbox.width, wp->wattr);
#endif
	box_cls(&tbox, wp->wattr);
	w_cursor(0, 0);
}

 void
w_ttyout_color(char *s, int attr)
{
	struct win *wp;
	struct box tbox;

	if ((wp= w_gettextbox(&tbox)) == NULL)
		return;
	while (*s) {
		if (*s == '\n')
			goto newline;
		co_putca(tbox.y+wp->w_crsrow, tbox.x+wp->w_crscol, (unsigned char)*s++, attr);
		wp->w_crscol++;
		if (wp->w_crscol >= tbox.width) {
			if (*s == '\n')
	newline:                s++;
			wp->w_crscol= 0;
			if (++wp->w_crsrow >= tbox.height) {
				wp->w_crsrow= tbox.height - 1;
#if OLDSTUFF
				co_scroll(CO_LINEUP, tbox.y, tbox.x, tbox.height, tbox.width, attr);
#endif
				box_scroll(&tbox, CO_LINEUP, attr);
			}
		}
	}
	co_cursor(tbox.y+wp->w_crsrow, tbox.x+wp->w_crscol);
}

 void
w_ttyout(char *s)
{
	if (Win_cur)
		w_ttyout_color(s, Win_cur->mattr);
}

 void
win_init(struct win *wp) /* sets default settings for window */
{
	wp->bbox.y = 0;
	wp->bbox.x = 0;
	wp->bbox.width = 0;
	wp->bbox.height = 0;
	wp->border = 1;
	wp->battr= FG_LWHITE | BG_BLACK;
	wp->wattr = FG_LWHITE | BG_BLACK;
	wp->mattr = FG_LWHITE | BG_BLACK;

	wp->save = NULL;
	wp->title[0] = '\0';
	wp->w_crsrow= 0;
	wp->w_crscol= 0;
}

 void
win_close( /* Redisplays content of screen overlayed by this window */
	struct win *wp)
{
	int i, j;
	short *svp;

	if (wp->save == NULL)
		return;
	svp= wp->save;
	for (i=wp->bbox.y; i<wp->bbox.height+1; i++)
		for (j=0; j<wp->bbox.width+1; j++) {
			co_putca(i, wp->bbox.x+j, *svp, *svp>>8);
			svp++;
		}
	free(wp->save);
	wp->save= NULL;
}

win_create( /* saves content of screen area to be overlayed */
	struct win *wp,
	int row,        /* window position and size */
	int col,
	int deep,
	int wide)
{
	wp->bbox.y = row;
	wp->bbox.x = col;
	wp->bbox.width = wide;
	wp->bbox.height = deep;

#if WINSAVE
	{
		int i,j, need;
		short *svp;

		/* figure the window size + shadow */
		need = (wp->bbox.width +1) * (wp->bbox.height +1);

		wp->save= calloc(need, sizeof(short));
		if (wp->save == NULL)
			return 0;       /* Insufficient memory */

		svp= wp->save;
		for (i=wp->bbox.y; i<wp->bbox.height+1; i++)
			for (j=0; j<wp->bbox.width+1; j++)
				*svp++ = (short)co_getca(i, wp->bbox.x+j);
	}
#endif
	return 1;
}

 void
w_frame(struct win *wp, int row, int left, int middle, int right, int attr)
{
	int col;
	int n;

	col= wp->bbox.x;
	n= wp->bbox.width-2;
	co_putca(row, col++, left, attr);
	co_putfill(row, col, n, middle, attr);
	co_putca(row, col+n, right, attr);
}

/* 
 * win_show() -- display a new WINDOW. 
 * Assumes win_init() and win_create() called previously 
 */
 void
win_show(struct win *wp)
{
	int i;
	int len;
	unsigned across, sides;
	unsigned ul, ur, ll, lr;
	int x;
	int y;

	Win_cur= wp;
	switch (wp->border) {
	case 1:
		across = BXS_HBAR;
		sides = BXS_VBAR;
		ul = BXS_UPRL; ur = BXS_UPRR; ll = BXS_LWRL; lr = BXS_LWRR;
		break;
	case 2:
		across = BXD_HBAR;
		sides = BXD_VBAR;
		ul = BXD_UPRL; ur = BXD_UPRR; ll = BXD_LWRL; lr = BXD_LWRR;
		break;
	case 0:
	default:
		ul = ur = ll = lr = sides = across = SPACE;
		break;
	}

	y= wp->bbox.y;
	w_frame(wp, y++, ul, across, ur, wp->battr);
	for (i=0; i<wp->bbox.height-2; i++) {
		co_putfill(y, wp->bbox.x, wp->bbox.width-1, ' ', wp->wattr);
		co_putca(y, wp->bbox.x, sides, wp->battr);
		co_putca(y++, wp->bbox.x+wp->bbox.width-1, sides, wp->battr);
	}
	w_frame(wp, y++, ll, across, lr, wp->battr);

	x= wp->bbox.x;
	if (wp->title) {
		int titleattr= (wp->mattr & 0xf) | (wp->battr & 0xf0);

		/* window title given */
		len = strlen(wp->title);
		x = (wp->bbox.width - len) >> 1;
		if (x > 0)
			co_putline(wp->bbox.y, x, len, wp->title, titleattr);
	}
	co_cursor_hide();
}

char ww_buf[2048];

ww_vprintf(char *fmt, va_list args)
{
	char *p;
	char *q;
	struct box tbox;
	int width;

	my_vsnprintf(ww_buf, sizeof(ww_buf), fmt, args);

	width= MW_WIDTH;
	if (fWindowMode && w_gettextbox(&tbox) != NULL) {
		width= tbox.width;
		co_cursor_show();
	}

	q= ww_buf;
	while ((p= ww_line(&q, width)) != NULL)
		if (fWindowMode)
			w_ttyout(p);
		else
			printf("%s", p);
	return (0);
}

ww_printf(char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	return (ww_vprintf(fmt, args));
}

#if NOT_USING
ww_msgprintf(int msgno, ...)
{
	va_list args;

	va_start(args, msgno);
	ww_vprintf(cmsgex(msgno), args);
	return (0);
}
#endif

#if NOT_USING
ww_newline(void)
{
	return (ww_printf("\n"));
}
#endif

FILE *log_fp;
char *log_fname;

log_close(void)
{
	if (log_fp) {
		fclose(log_fp);
		log_fp = NULL;
#if __DOS__
		dos_diskreset();
#endif
	}
	return  (0);
}

log_open(void)
{
	static char *wflag = "w";
	static once;

	if (!log_fname)
		return (-1);
	if (!once) {
		once++;
		if (*log_fname == '+') {
			wflag = "a";
			log_fname++;
		}
		if (!*log_fname) {
			log_fname = NULL;
			return(-1);
		}
	}
	if (log_fp == NULL) {
		if ((log_fp = fopen(log_fname, wflag)) == NULL)
			return (-1);
		wflag = "a";
		/*
		 * When scanning ide bus in realmode 16-bit DOS, logging 
		 * debug output to disk causes a system hang.  A buffer 
		 * used to catch ide scan output prevents this.
		 */
		setvbuf(log_fp, log_vbuf, _IOFBF, sizeof(log_vbuf));
	}
	return (0);
}

vlogf(char *fmt, va_list args)
{
	static once;
	int errnosv = errno;
#if MSOS
	int oserr = GetLastError();
#endif

	if (log_open() == 0) {
		if (!once) {
			once = 1;
			fprintf(log_fp, "\n");
		}
		vfprintf(log_fp, fmt, args);
#if __DOS__
		if (!(ide_flags & IDEF_ININIT))
			/* don't flush during ide search */
			return (0);
#endif
		fflush(log_fp);
#if LOGREOPEN
		log_close();
#endif
	}
#if MSOS
	SetLastError(oserr);
#endif
	errno = errnosv;
	return (0);
}

logf(char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	return (vlogf(fmt, args));
}

msglogf(int msgno, ...)
{
	va_list args;

	va_start(args, msgno);
	vlogf(cmsgex(msgno), args);
	return (0);
}

char *plmsg_more= "--More-- <Enter>|<Space>|<Esc> ";
char msg_unmore[]= "\r                                                     \r";

vmore(char *fmt, va_list args)
{
#if __DOS__ || WIN32
	static lineno;
	static col;
	static topline;
	static char text[1024];
	static once;
	int count;
	char *p;
	int quit= 0;

	if (!once) {
		int len= strlen(plmsg_more) + 2;

		if (len < sizeof(msg_unmore) - 2)
			strcpy(&msg_unmore[len], "\r");
		once++;
	}
	count= my_vsnprintf(text, sizeof(text), fmt, args);
	if (isatty(0) && isatty(1)) {
		for (p=text; *p; p++) {
			putchar(*p);
			switch (*p) {
			case '\t':
				col += 8 - (col % 8);
				break;
			case '\a': /* alert */
				break;
			case '\b': /* backspace */
				if (col)
					col--;
			case '\r':
				col= 0;
				break;
			default:
				col++;
				break;
			}
			if (*p != '\n' && col < 80)
				continue;
			lineno++;
			col= 0;
			if (lineno - topline < 24)
				continue;
			printf(plmsg_more);
			fflush(stdout);
			again:
			switch (kbget()) {
			case '\r':
			case '\n':
				topline++;
				break;
			case 0x1b: /*<Esc>*/
			case 'q':
			case 'Q':
				quit= 1;
				break;
			case ' ':
				topline += 20;
				break;
			default:
				goto again;
			}
			printf(msg_unmore);
			fflush(stdout);
			if (quit)
				my_exit(EX_CANCEL);
		}
	} else
		printf("%s", text);
	return (count);
#elif unix
	return (vprintf(fmt, args));
#endif
}

#if NOT_USING
more(char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	return (vmore(fmt, args));
}
#endif

#if NOT_USING
msgmore(int msgno, ...)
{
	va_list args;

	va_start(args, msgno);
	vmore(cmsgex(msgno), args);
	return (0);
}
#endif

my_vprintf(char *fmt, va_list args)
{
	vlogf(fmt, args);
	if (fWindowMode)
		return (ww_vprintf(fmt, args));
	else if (fMoreMode)
		return (vmore(fmt, args));
	else
		return (vprintf(fmt, args));
}

my_printf(char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	return (my_vprintf(fmt, args));
}

my_msgprintf(int msgno, ...)
{
	va_list args;

	va_start(args, msgno);
	my_vprintf(cmsgex(msgno), args);
	return (0);
}

my_newline(void)
{
	return (my_printf("\n"));
}

my_msgstatusf(int msgno, ...)
{
	char text[80];
	va_list args;
	char *fmt;

	va_start(args, msgno);
	fmt = cmsgex(msgno);
	if (fAutoUpdate) {
		/* display status line */
		my_vsnprintf(text, sizeof(text), fmt, args);
		colorline(1, 0, FG_YELLOW | BG_BLACK, text);
	}
	else
		my_vprintf(fmt, args);
	return (0);
}

my_msgstatus_newline(void)
{
	if (fAutoUpdate)
		/* clear status line */
		colorline(1, 0, FG_WHITE | BG_BLACK, NULL);
	else
		my_newline();
	return (0);
}

	/* SCSI related defines */
	/* scsi status codes */
#define SSTAT_GOOD                      0x00
#define SSTAT_CHECK_CONDITION           0x02
#define SSTAT_CONDITION_MET             0x04
#define SSTAT_BUSY                      0x08
#define SSTAT_INTERMEDIATE              0x10
#define SSTAT_INTERMEDIATE_COND_MET     0x14
#define SSTAT_RESERVATION_CONFLICT      0x18
#define SSTAT_COMMAND_TERMINATED        0x22
#define SSTAT_QUEUE_FULL                0x28
#define SSTAT_MASK                      0x3e

	/* scsi cdb control byte bits */
#define CDB_LINKED_BIT          0x01
#define CDB_FLAG_BIT            0x02

	/* SCSI command op codes -- tape commands */
#define SCMD_TEST_UNIT_READY    0x00
#define SCMD_REWIND             0x01
#define SCMD_REQUEST_SENSE      0x03
#define SCMD_READ_BLOCK_LIMITS  0x05
#define SCMD_PARK_UNPARK        0x06
#define SCMD_READ               0x08
#define SCMD_WRITE              0x0a
#define SCMD_SET_CAPACITY       0x0b
#define SCMD_READ_REVERSE       0x0f
#define SCMD_WRITE_FILEMARKS    0x10
#define SCMD_SPACE              0x11
#define SCMD_INQUIRY            0x12
#define SCMD_VERIFY             0x13
#define SCMD_RECOVER_BUFFERED   0x14
#define SCMD_MODE_SELECT_6      0x15
#define SCMD_RESERVE_UNIT       0x16
#define SCMD_RELEASE_UNIT       0x17
#define SCMD_COPY               0x18
#define SCMD_ERASE              0x19
#define SCMD_MODE_SENSE_6       0x1a
#define SCMD_LOAD_UNLOAD        0x1b
#define SCMD_RECEIVE_DIAGNOSTIC 0x1c
#define SCMD_SEND_DIAGNOSTIC    0x1d
#define SCMD_PREVENT_ALLOW      0x1e
#define SCMD_LOCATE             0x2b
#define SCMD_READ_POSITION      0x34
#define SCMD_COMPARE            0x39
#define SCMD_COPY_AND_VERIFY    0x3a
#define SCMD_WRITE_BUFFER       0x3b
#define SCMD_READ_BUFFER        0x3c
#define SCMD_CHANGE_DEFINITION  0x40
#define SCMD_WRITE_SAME         0x41
#define SCMD_REPORT_DENSITY     0x44
#define SCMD_LOG_SELECT         0x4c
#define SCMD_LOG_SENSE          0x4d
#define SCMD_MODE_SELECT_10     0x55
#define SCMD_MODE_SENSE_10      0x5a
#define SCMD_REPORT_LUNS        0xa0

	/* Medium changer */
#define SCMD_REZERO_UNIT        0x01
#define SCMD_INIT_ELEM_STATUS   0x07 /* INITIALIZE_ELEMENT_STATUS */
#define SCMD_RESERVE            0x16
#define SCMD_RELEASE            0x17
#define SCMD_POSITION_TO_ELEM   0x2b /* POSITION_TO_ELEMENT */
#define SCMD_MOVE_MEDIUM        0xa5
#define SCMD_EXCHANGE_MEDIUM    0xa6
#define SCMD_REQ_VOL_ELEM_ADDR  0xb5 /* REQUEST_VOLUME_ELEMENT_ADDRESS */
#define SCMD_SEND_VOL_TAG       0xb6 /* SEND_VOLUME_TAG */
#define SCMD_READ_ELEM_STATUS   0xb8 /* READ_ELEMENT_STATUS */

	/* sense keys: sense[2] bits 3..0, sn_key */
#define SKEY_NO_SENSE           0x0 /* No sense */
#define SKEY_RECOVERED_ERROR    0x1 /* Recovered error */
#define SKEY_NOT_READY          0x2 /* Not ready */
#define SKEY_MEDIUM_ERROR       0x3 /* Medium error */
#define SKEY_HARDWARE_ERROR     0x4 /* Hardware error */
#define SKEY_ILLEGAL_REQUEST    0x5 /* Request error */
#define SKEY_UNIT_ATTENTION     0x6 /* Unit attention */
#define SKEY_DATA_PROTECT       0x7 /* Data protect */
#define SKEY_BLANK_CHECK        0x8 /* Blank check */
#define SKEY_VENDOR             0x9 /* Vendor specific */
#define SKEY_COPY_ABORTED       0xa /* Copy aborted */
#define SKEY_ABORTED_COMMAND    0xb /* Command aborted */
#define SKEY_EQUAL              0xc /* Equal */
#define SKEY_VOLUME_OVERFLOW    0xd /* Volume overflow */
#define SKEY_MISCOMPARE         0xe /* Miscompare */
#define SKEY_RESERVED           0xf /* Reserved */

	/* Peripheral device type: inquiry[0] bits 4..0 */
#define PDT_DISK        0x00 /* SBC    Direct-access (disk) */
#define PDT_TAPE        0x01 /* SSC    Sequential-access (tape) */
#define PDT_PRINTER     0x02 /* SSC    Printer */
#define PDT_PROCESSOR   0x03 /* SPC-2  Processor */
#define PDT_WORM        0x04 /* SBC    Write-once (some optical disks) */
#define PDT_CDROM       0x05 /* MMC-2  CD-ROM */
#define PDT_SCANNER     0x06 /* SCSI-2 Scanner */
#define PDT_OPTICALMEM  0x07 /* SBC    Optical memory (some optical disks) */
#define PDT_CHANGER     0x08 /* SMC    Medium changer (jukeboxes) */
#define PDT_COMM        0x09 /* SCSI-2 Communications */
#define PDT_GRAPHICSA   0x0a /* ASC IT8 defined (Graphic arts pre-press) */
#define PDT_GRAPHICSB   0x0b /* ASC IT8 defined (Graphic arts pre-press) */
#define PDT_RAID        0x0c /* SCC-2  Storage array controller (RAID) */
#define PDT_ENCLOSURE   0x0d /* SES    Enclosure services */
#define PDT_SIMPLEDISK  0x0e /* RBC    Simplified direct-access */
#define PDT_OPTICALCARD 0x0f /* OCRW   Optical card reader/writer */
#define PDT_BRIDGING    0x10 /*        (reserved for) Bridging */
#define PDT_OBJSTORAGE  0x11 /* OSD    Object-based Storage Device */
#define PDT_UNKNOWN     0x1f /* Unknown or no device type */

#define NB_INQ_LEGACY   36 /* Conventional standard inquiry data size */
#define NB_INQ_MAX      64 /* Max standard inquiry bytes to retrieve */
#define NB_INQ_CFR      23 /* Seagate Controller firmware revision inquiry */
#define NB_INQ_DFR      64 /* Seagate Drive firmware revision inquiry */
#define NB_INQ_SERNO    64 /* Drive serial number inquiry */

struct hostinfo {
	int  hi_flags;          /* HIF_* flag values */
#define HIF_WIDE32      0x20  /* 32-bit wide SCSI bus */
#define HIF_WIDE16      0x10  /* 16-bit wide SCSI bus */
#define HIF_RESIDUAL    0x8   /* Supports DMA residual reporting */
#define HIF_W9XIDE      0x4   /* Accessing an IDE bus via Win9x ASPI */
	int  hi_hano;           /* Adapter's number (NT, port) */
	int  hi_hostid;         /* Adapter's SCSI ID */
	int  hi_nscsiids;       /* Number of scsi ids */
	int  hi_nluns;          /* Number of luns per scsi id */
	int  hi_busflag;        /* Bus type: BUSF_IDE or BUSF_SCSI */
	int  hi_alignmask;      /* Buffer alignment mask */
	long hi_maxxferlen;     /* Max dma capability */
	char hi_drivername[20]; /* Driver/manager name */
	char hi_adaptername[20];/* Adapter name */
};

struct tapedev {
	struct tapedev *td_link;
	struct inidev *td_idev; /* .INI device list entry */
	int td_flags;           /* TDF_* values */
#define TDF_RESIDUAL      0x8   /* Supports DMA residual reporting */
#define TDF_W9XIDE        0x4   /* Accessing an IDE bus via Win9x ASPI */
#define TDF_CLAIMED       0x2   /* Device claimed by a class driver */
#define TDF_PORTACCESS    0x1   /* Device access via a port driver */
	char *td_devname;       /* \\.\Tape<N> on NT/2000 */
	HANDLE td_fd;           /* File descriptor or handle */
	int td_inaccessible;    /* INACC_* value */
#define INACC_ACCESSIBLE  0
#define INACC_CANTOPEN    1
#define INACC_BUSY        2
#define INACC_GETPARMFAIL 3
#define INACC_CLAIMED     4
	int td_inacc_oserr;    /* set for INACC_ CANTOPEN, BUSY, GETPARMFAIL */
	int td_hano;
	int td_busno;
	int td_targid;
	int td_lun;
	int td_busflag;         /* A single BUSF_* value */
	int td_fwstate;         /* FWS_* values */
#define FWS_UNKNOWN         0 /* "Unknown" */
#define FWS_UNRECOGNIZED    1 /* "Unrecognized device" */
#define FWS_INACCESSIBLE    2 /* "Inaccssible device" */
#define FWS_INUSE           3 /* "Device in use" */
#define FWS_IGNORED         4 /* "Ignored device" */
#define FWS_CURRENT         5 /* "Up to date" */
#define FWS_NEWER           6 /* "Newer firmware" */
#define FWS_NEEDSDOS        7 /* "Needs DOS mode update" */
#define FWS_NEEDSALTERNATE  8 /* "Needs alternate method update" */
#define FWS_OUTOFDATE       9 /* "Out of date" */
#define N_FWSTATE           10
	int td_updatestatus;    /* UDST_* value */
#define UDST_NONE              0 /* "None" */
#define UDST_UPDATEWAITING     1 /* "Waiting" */
#define UDST_UPDATING          2 /* "Pending" */
#define UDST_UPDATEGOOD        3 /* "Success" */
#define UDST_UPDATEFAIL        4 /* "FAILED" */
#define N_UPDATESTATUS         5
	unsigned char td_inq_std[NB_INQ_MAX]; /* Standard inquiry data */
	int td_inq_len;         /* Actual valid bytes in td_inq_std */
	char td_cfr_oem[4];     /* 3-digit OEM string from Seagate CFR inquiry */
	char td_curver[5];      /* Current version in drive */
	char td_oldver[5];      /* Old version in drive */
	char td_newver[5];      /* New version with which to update drive */
	char td_autover[9];     /* AutoFileVersion from .INI file */
	char td_cmpcontrol[5];  /* CompareControl from .INI file */
	int  td_tclassno;       /* TCLASS_* value */
#define TCLASS_UNKNOWN     0
#define TCLASS_TRAVAN      1 /* seagate(*.fls,*.dfi) */
#define TCLASS_DAT         2 /* seagate(*.bin,*.hex) */
#define TCLASS_AIT         3 /* seagate(*.rom) */
#define TCLASS_ULTRIUM     4 /* ibm(*.fmr), hp(*.frm), seagate(*.img) */
#define TCLASS_MAMMOTH     5 /* exabyte(*.i) */
#define TCLASS_DLT         6 /* benchmark(*.img,*.image), quantum(*.img) */
#define TCLASS_ATL_LIBRARY 7 /* atl(*.img) */
#define TCLASS_HP_LIBRARY  8 /* hp(*.frm) */
	int  td_alignmask;      /* Buffer alignment mask */
	long td_maxxferlen;     /* Max dma capability */
};
struct tapedev *Tapedev_list;
struct tapedev *Tapedev_cur;
int Tapedev_count = 0; /* Entries in Tapedev_list */
int Fwstate_ctr[N_FWSTATE];

tapedev_init(struct tapedev *td)
{
	memset(td, 0, sizeof(*td));
	td->td_fd = (HANDLE)-1;
	return (0);
}

int scsienum_nadapters;     /* Total adapters in system */
#if __ASPI__
int scsienum_npdts[32];     /* Number of each peripheral device type */
#endif
#if __NT__
HKEY scsienum_scsikey_handle = (HANDLE)-1; /* handle of root of scsi tree in registry */
#endif

#define BUSF_IDE        0x2
#define BUSF_SCSI       0x1
int scsienum_busflags;    /* BUSF_*: Busses to search */

int scsienum_nchangers; /* number of recongnized medium changers */

#if TAPEDRV_UPDATE
/* Valid characters for Travan inquiry[33] */
char Travan_configs[]= ".ABNT";
#endif /*TAPEDRV_UPDATE*/

 char *
str_busflags(int busflags)
{
	switch (busflags & (BUSF_SCSI | BUSF_IDE)) {
	case BUSF_IDE:
		return ("IDE/ATAPI ");
	case BUSF_SCSI:
		return ("SCSI ");
	default:
		return ("");
	}
}

 char *
str_fwstate(int fwstate) /* gets firmware state string */
{
	static once;
	static char *fwst_names[N_FWSTATE];

	if (!once) {
		int i;
		int msgno;
		char *p;
		char *undefmsg= NULL;

		once++;
		/* load firmeare states from the message file */
		msgno= MSGNO(0x2300);
		for (p=cmsg(msgno); p; p=cmsg_next(MSGNO(0x2300 + N_FWSTATE), &msgno))
			fwst_names[msgno - 0x2300] = strdup(p);
		if ((undefmsg = fwst_names[0]) == NULL)
			undefmsg= "(missing firmware state message)";
		for (i=0; i<N_FWSTATE; i++)
			if (fwst_names[i] == NULL)
				fwst_names[i]= undefmsg;
	}
	if ((unsigned)fwstate >= N_FWSTATE)
		fwstate = 0;
	return (fwst_names[ fwstate ]);
}

 char *
str_updatestatus(int udstatus) /* gets update status string */
{
	static once;
	static char *udst_names[N_UPDATESTATUS];

	if (!once) {
		int i;
		int msgno;
		char *p;
		char *undefmsg= NULL;

		once++;
		/* load firmeare states from the message file */
		msgno= MSGNO(0x2500);
		for (p=cmsg(msgno); p; p=cmsg_next(MSGNO(0x2500 + N_UPDATESTATUS), &msgno))
			udst_names[msgno - 0x2500] = strdup(p);
		if ((undefmsg = udst_names[0]) == NULL)
			undefmsg= "(missing firmware state message)";
		for (i=0; i<N_UPDATESTATUS; i++)
			if (udst_names[i] == NULL)
				udst_names[i]= undefmsg;
	}
	if ((unsigned)udstatus >= N_UPDATESTATUS)
		udstatus = 0;
	return (udst_names[ udstatus ]);
}

#if KOMMENT
/* Seagate Travan bali/falcon/hornet models */
	/*atapi*/
	"CONNER  BBCM",            /*Bali-1 (maybe engineering) */
	"CONNER  CTMA  4000",      /*Bali-1*/
	"CONNER  CTT8000-A",       /*Bali-2, Bali-2A*/
	"Seagate STT8000A",        /*Hornet-8 ATAPI*/
	"TANDBERG TR4 4/8GB",      /*Hornet-8 ATAPI*/
	"Seagate STT20000A",       /*Hornet-20 ATAPI*/
	/*scsi*/
	"CONNER  CTMS  3200",      /*Bali-1/Falcon-1*/
	"CONNER  CTT8000-S",       /*Bali-2/Falcon-2*/
	"Seagate STT8000N",        /*Hornet-8 SCSI*/
	"Seagate STT20000N",       /*Hornet-20 SCSI*/
	"Seagate scsi    mfg",
	"TANDBERG NS8 Pro 4/8GB ", /*Hornet-NS8*/
	"TANDBERG NS20 Pro 20 GB", /*Hornet-NS20*/
/* Seagate DAT piranha/peregrine/scorpion models */
	"ARCHIVE 4322XX"
	"ARCHIVE 4326XX"
	"ARCHIVE 4586XX"
	"ARCHIVE IBM-STD224000N"
	"ARCHIVE IBM-STL496000N"
	"ARCHIVE IBM4326NP"
	"ARCHIVE IBM4586NP"
	"ARCHIVE Python"
	"SEAGATE DAT"
/* Seagate AIT sidewinder models */
	"SEAGATE AIT",
	"SEAGATE AIT-LDR",
/* ATL libraries */
	"ATL     L200",  /*???*/
	"ATL     L500",
/* Benchmark DLT */
	"BNCHMARK DLT1",
/* Exabyte Mammoth */
	"EXABYTE EXB-8900",  /*Mammoth-1*/
	"EXABYTE MAMMOTH2",  /*Mammoth-2*/
/* HP Ultrium */
	"HP      ULTRIUM",
/* HP Libraries */
	"HP      C7145",
	"HP      C7200",
/* IBM Ultrium */
	"IBM     ULTRIUM",
/* Quantum DLT */
	"QUANTUM DLT4000",
	"QUANTUM DLT7000",
	"QUANTUM DLT8000",
	"QUANTUM SUPERDLT",
#endif
#if LOTSOFMEMORY
struct tclass {
	int    tc_tclassno;     /* TCLASS_* value */
	char  *tc_name;         /* Class name for printf */
	char  *tc_inqpat;       /* Inquiry string pattern */
	char  *tc_filepat;      /* Firmware filename pattern */
};
struct tclass Tclasses[] ={
	{ TCLASS_TRAVAN,  
		"Travan ",
		"{{{CONNER ,Seagate,SEAGATE} {BBCM,CTM,CTT,STT}*},{TANDBERG {TR,NS}*}}",
		"*.{fls,dfi}",
		},
	{ TCLASS_DAT,
		"DAT ",
		"{SEAGATE,ARCHIVE} {432,458,IBM,Python,DAT}*",
		"*.{bin,hex}",
		},
	{ TCLASS_AIT,
		"AIT ",
		"SEAGATE AIT*",
		"sgt??j?.rom",
		},
	{ TCLASS_MAMMOTH,
		"Mammoth ",
		"EXABYTE {EXB-8900,MAMMOTH2}*",
		"????????.i",
		},
	{ TCLASS_DLT,
		"DLT ",
		"{QUANTUM {DLT,SUPERDLT},BNCHMARK DLT}*",
		"{{v*d[478],sdlt}*.img,dlt1*.{img,image}}",
		},
	{ TCLASS_ATL_LIBRARY,
		"ATL Library ",
		"ATL     {L200,L500}*",
		"v*{oem,atl}*.img",
		},
	{ TCLASS_HP_LIBRARY,
		"HP Library ",
		"HP      {C7145,C7200} *",
		"{1x9,2x20,{auto,lib}_ibm_lto_}*.frm",
		},
	{ TCLASS_ULTRIUM,
		"Ultrium ",
		"{IBM    ,HP     ,SEAGATE} ULT*",
//TOXIC HP(.frm) needs tape/changer discrminiation
		"{????.fmr,?????.frm,v???????.img}",
		},
	/* terminator */
	{ TCLASS_UNKNOWN,
		"",
		"",
		"",
	}
};
#endif /*LOTSOFMEMORY*/

#if LOTSOFMEMORY
get_tclassno(struct tapedev *td)  /* Assumes td_inq_std has inquiry data */
{
	static char me[]= "get_tclassno";
	struct tclass *tcp;
	static char *mfgnames[]= {
		"ARCHIVE", "CONNER", "SEAGATE", "TANDBERG",
		"ATL", "BNCHMARK", "QUANTUM",
		"EXABYTE", "HP", "IBM",
		 NULL
	};
	char **mv;
	char *emsg= NULL;
	int tclassno = TCLASS_UNKNOWN;

	/* Look for tape device */
	if (td->td_inq_std[4] < 3+28) {
		emsg= "Device name unavaliable";
		goto fail;
	}
	switch (td->td_inq_std[0]) {
	case PDT_TAPE: /*1*/
	case PDT_CHANGER: /*8*/
		break;
	default:
		emsg= "Not a tape device";
		goto fail;
	}
	for (mv=mfgnames; ;mv++) {
		if (*mv == NULL) {
			emsg= "Unrecognized manufacturer";
			goto fail;
		}
		if (!memicmp(&td->td_inq_std[8], *mv, strlen(*mv)))
			break;
	}
	for (tcp=Tclasses; ; tcp++) {
		if (tcp->tc_tclassno == TCLASS_UNKNOWN) {
			emsg= "Unrecognized model";
			goto fail;
		}
		if (inqmatch(tcp->tc_inqpat, td->td_inq_std) > 0) {
			tclassno = tcp->tc_tclassno;
			break;
		}
	}
fail:
	if (emsg && fDebug)
		logf("%s: pdt=0x%02x [%-28.28s]: %s\n", me, td->td_inq_std[0], &td->td_inq_std[8], emsg);
	return (tclassno);
}
#endif /*LOTSOFMEMORY*/

#if LOTSOFMEMORY
/* filename_to_tclassno() -- convert file name to a TCLASS_* value */
filename_to_tclassno(char *pathname)
{
	struct tclass *tcp;

	for (tcp=Tclasses; tcp->tc_tclassno != TCLASS_UNKNOWN; tcp++)
		if (fmatch(tcp->tc_filepat, pathname) > 0)
			break;
	return (tcp->tc_tclassno);
};
#endif /*LOTSOFMEMORY*/

#if __ASPI__
aspi_gethostinfo(      /* Host Adapter Inquiry */
	int hano,
	struct hostinfo *hip)
{
	SRB_HAInquiry hainq_buf;         /* ASPI HA Inquiry structure */
	SRB_HAInquiry *inqp= &hainq_buf;

	/* clear returned info structure */
	memset(hip, 0, sizeof(*hip));

	memset(inqp, 0, sizeof(*inqp));
	inqp->SRB_HaId= (char)hano;
#if WIN32
#else /*__DOS__*/
	inqp->SRB_55AASignature= 0xaa55;
	inqp->SRB_ExtBufferSize= sizeof(inqp->HA_ExtBuffer); /* wr Extended buffer length */
#endif

	SendASPICommand(inqp);
	if (inqp->SRB_Status != SS_COMP)
		return (-1);
	hip->hi_hano = hano;
	if (fDebug) {
		logf("HostAdapterInquiry: Status=0x%02x Id=%d Count=%d SCSI_ID=%d\n",
			inqp->SRB_Status,
			inqp->SRB_HaId,
			inqp->HA_Count,
			inqp->HA_SCSI_ID
		);
		logf("  HA_Manager=[%.16s] HA_Identifier=[%.16s]\n",
			inqp->HA_ManagerId,
			inqp->HA_Identifier
		);
		logf("  HA_Unique= %s\n", fmthex(0, inqp->HA_Unique, sizeof(inqp->HA_Unique)));
	}
	memcpy(hip->hi_drivername, inqp->HA_ManagerId, 16);
	strtrim(hip->hi_drivername);

	memcpy(hip->hi_adaptername, inqp->HA_Identifier, 16);
	strtrim(hip->hi_adaptername);
	hip->hi_hostid = inqp->HA_SCSI_ID;   /* r SCSI ID of host atapter */
#if WIN32
	{
		if (inqp->HA_Unique[HAUNIQ_FLAGS] & HAUNIQ_FLAG_RESIDUAL)
			hip->hi_flags |= HIF_RESIDUAL;
		hip->hi_alignmask= ((unsigned short *)&inqp->HA_Unique[HAUNIQ_ALIGNMASK])[0];
		hip->hi_maxxferlen= ((long *)&inqp->HA_Unique[HAUNIQ_MAXXFERLEN])[0];
		if (fDebug)
			logf(
"  HA_Unique: alignmask=0x%04x dmaresidual=%d ntargs=%d maxxferlen=%ld\n",
				hip->hi_alignmask,
				(inqp->HA_Unique[HAUNIQ_FLAGS] & HAUNIQ_FLAG_RESIDUAL) != 0,
				inqp->HA_Unique[HAUNIQ_NTARGS],
				hip->hi_maxxferlen
			);
		hip->hi_nscsiids= 8;
		if (inqp->HA_Unique[HAUNIQ_NTARGS])
			hip->hi_nscsiids = inqp->HA_Unique[HAUNIQ_NTARGS];
	}
#else /*__DOS__*/
	{
		if (fDebug)
			logf("  SRB_55AASignature=0x%04x SRB_ExtBufferSize=%d\n",
				inqp->SRB_55AASignature,
				inqp->SRB_ExtBufferSize
			);
		/* Zero nonconforming bytes */
		if (inqp->SRB_55AASignature != 0x55aa)
			inqp->SRB_ExtBufferSize= 0;
		memset(
			&inqp->HA_ExtBuffer[ inqp->SRB_ExtBufferSize ],
			0,
			sizeof(inqp->HA_ExtBuffer) - inqp->SRB_ExtBufferSize
		);
		if (inqp->HA_ExtBuffer[0] & HAEX0_RESIDUAL)
			hip->hi_flags |= HIF_RESIDUAL;
		if (fDebug)
			logf("  HA_ExtBuffer[0]: wide32=%d wide16=%d dmaresidual=%d\n",
				(inqp->HA_ExtBuffer[0] & HAEX0_WIDE32) != 0,
				(inqp->HA_ExtBuffer[0] & HAEX0_WIDE16) != 0,
				(inqp->HA_ExtBuffer[0] & HAEX0_RESIDUAL) != 0
			);
		hip->hi_nscsiids = 8;
		if (inqp->HA_ExtBuffer[0] & HAEX0_WIDE16) {
			hip->hi_flags |= HIF_WIDE16;
			hip->hi_nscsiids = 16;
		}
		if (inqp->HA_ExtBuffer[0] & HAEX0_WIDE32)
			hip->hi_flags |= HIF_WIDE32;
	}
	if (dos_winver() == 0) {
#if __TAPEDRV__
		if (!memstricmp(inqp->HA_ManagerId, "TAPEDRV"))
#else
		if (!memstricmp(inqp->HA_ManagerId, "BUILTIN"))
#endif
			hip->hi_busflag= BUSF_IDE;
		else
			hip->hi_busflag= BUSF_SCSI;
	}
	if (dos_winver() >= 0x400)
#endif /*WIN32 else __DOS__*/
	{
		if (!memstricmp(inqp->HA_Identifier, "ESDI_506")) {
			hip->hi_flags |= HIF_W9XIDE;
			hip->hi_busflag= BUSF_IDE;
		} else
			hip->hi_busflag= BUSF_SCSI;
	}
	hip->hi_nluns = 8;
	if (hip->hi_busflag & BUSF_IDE)
		hip->hi_nluns = 1;
	return (0);
}
#endif /*__ASPI__*/

aspiio( /* executes a SCSI CDB via ASPI */
	void *sparg, /* SC_EXEC_SCSI_CMD or SC_RESET_DEV SRB */
	int timeoutseconds) /* Timeout ignored by 16-bit DOS ASPI */
{
	static char me[] = "aspiio";
	SRB_ExecSCSICmd *sp= (SRB_ExecSCSICmd *)sparg;

#if WIN32
	int rc= 0;
	int aspistat;
	unsigned EventStatus;
	int myer;

	sp->SRB_Status= SS_PENDING; /*0*/

	sp->SRB_Flags |= SRB_EVENT_NOTIFY;
	sp->SRB_PostProc= ASPICompletionEvent;
	/* Reset event to non-signaled state */
	ResetEvent(ASPICompletionEvent);

	if (fDebug) {
		logf("%s: start SendASPICommand(haid=%d targid=%d CDB[0]=0x%02x)\n",
			me, sp->SRB_HaId, sp->SRB_Target, sp->CDBByte[0]
		);
		log_close();
	}
	aspistat= SendASPICommand(sp);
	if (fDebug)
		logf("%s: %s SendASPICommand() = %#x\n",
			me, (aspistat) ?"done" :"wait", aspistat
		);
	if (aspistat != SS_PENDING /*0*/) {
		if (aspistat != SS_COMP /*1*/) {
			Err_type = DVRER;
			Driver_stat= aspistat;
			return (-1);    /* start error */
		}
		return (0);     /* request done */
	}
	if (fDebug) {
		logf("%s: start WaitForSingleObject()\n", me);
		log_close();
	}
	/* Block on until event signaled */
	EventStatus= WaitForSingleObject(
		ASPICompletionEvent,
		timeoutseconds*1000 /*milliseconds*/
	);
	if (fDebug)
		logf("%s: end WaitForSingleObject(): = %d\n", me, EventStatus);
	switch (EventStatus) {
	case WAIT_OBJECT_0: /* 0: Success */
		break;
	case WAIT_TIMEOUT:
		myer= MYE_WAIT_TIMEOUT;
		goto set_myerror;
	case WAIT_ABANDONED:
		myer= MYE_WAIT_ABANDONED;
		goto set_myerror;
	default:
		myer= MYE_WAIT_UNKNOWN;
	set_myerror:
		My_error= myer;
		Err_type = MYER;
		rc= -1;
		break;
	}
	return (rc);
#else /*__DOS__*/
	time_t end_time;

	/* add 1 second to guarantee minimum */
	end_time = time(NULL) + timeoutseconds + 1;
	sp->SRB_Status = SS_PENDING; /*0*/
	if (fDebug) {
		logf("%s: start SendASPICommand(haid=%d targid=%d CDB[0]=0x%02x)\n",
			me, sp->SRB_HaId, sp->SRB_Target, sp->CDBByte[0]
		);
		log_close();
	}
	SendASPICommand(sp);
	if (fDebug)
		logf("%s: end SendASPICommand()\n", me);
	while (((SRB_ExecSCSICmd *)sp)->SRB_Status == SS_PENDING) {
		if (timeoutseconds && end_time - time(NULL) < 0) {
			SRB_Abort abort_buf;
			SRB_Abort *ap = &abort_buf;

			/* Abort the currently executing ASPI command */
			memset(ap, 0, sizeof(*ap));
			ap->SRB_Cmd = SC_ABORT_SRB;
			ap->SRB_ToAbort = sp; /* request to abort */
			logf("%s: %d second timeout: start SendASPICommand(ABORT)\n",
				me, timeoutseconds
			);
			log_close();
			SendASPICommand(ap);
			my_sleep(2);
			logf("%s: end SendASPICommand(ABORT) [SCSI.SRB_Status=0x%02x SCSI.SRB_HaStat=0x%02x Abort.SRB_Status=0x%02x]\n",
				me, sp->SRB_Status, sp->SRB_HaStat, ap->SRB_Status
			);
			log_close();
			/* Report timeout error to caller */
			sp->SRB_HaStat = HASTAT_TIMEOUT;
			sp->SRB_Status = SS_ERR;

#if RESET_ON_ABORT
			{
				SRB_BusDeviceReset bdr_buf;
				SRB_BusDeviceReset *rp = &bdr_buf;

				/* Issue a bus device reset */
				memset(rp, 0, sizeof(*rp));
				rp->SRB_Cmd = SC_RESET_DEV;
				rp->SRB_Target = sp->SRB_Target;
				SendASPICommand(rp);
				my_sleep(2);
			}
#endif
			break;
		}
	}
	return (0);
#endif /*__DOS__*/
}

/*
 * scsihook() -- for win9x/dos, bind with aspi; for nt, find scsi root in registry
 */
scsihook(void)
{
	int support;

	scsienum_nadapters= 0;
	if (fSimulate)
		return (0);
#if WIN32
	if (IS_WIN9X()) {
		static HINSTANCE Aspi_dllinstance;

		if (Aspi_dllinstance == NULL) {
			LPCSTR dllnm;
			LPCSTR entrynm;

			dllnm= "WNASPI32.DLL";
			if ((Aspi_dllinstance= LoadLibrary(dllnm)) == NULL)
				/* WNASPI32.DLL: LoadLibrary error */
				goto no_w9x_aspi;
			entrynm= "GetASPI32SupportInfo";
			if ((GetASPISupportInfo= (ULONG (*)(void))GetProcAddress(
					Aspi_dllinstance,
					entrynm)) == NULL)
				goto getprocfail;
			entrynm= "SendASPI32Command";
			if ((SendASPICommand= (ULONG (*)(void *))GetProcAddress(
					Aspi_dllinstance,
					entrynm)) == NULL) {
getprocfail:
				FreeLibrary(Aspi_dllinstance);
				Aspi_dllinstance= NULL;
				/* WNASPI32.DLL: Get DLL entry point error */
				goto no_w9x_aspi;
			}
		}
		support= GetASPISupportInfo();
		Driver_stat= (support>>8) & 0xff;
		scsienum_nadapters= support & 0xff;
		if (Driver_stat != SS_COMP)
			/* Get ASPI support information error */
			goto no_w9x_aspi;
		/* Get an event handle. Initial state: non-signaled, manual reset. */
		if ((ASPICompletionEvent = CreateEvent(NULL,FALSE,FALSE,NULL)) == NULL) {
			/* Event resource creation error */
no_w9x_aspi:            error_msgset(MSGNO(0x8113));
/*8113 "TAPEDRV8113 error: The Windows 95/98/ME ASPI Manager was not found."*/

			return (-1);
		}
	} else { /* NT ***********/
		HKEY keyhandle;
		char keyname[64];
		unsigned long keysize;
		FILETIME keymodtime;
		unsigned char drivername[64];
		unsigned long datatype;
		unsigned long datasize;
		int oserr;
		int i;
		static char keypath[]= "HARDWARE\\DEVICEMAP\\Scsi";

		/* Close handle if previously open */
		if (scsienum_scsikey_handle != (HANDLE)-1) {
			RegCloseKey(scsienum_scsikey_handle);
			scsienum_scsikey_handle = (HANDLE)-1;
		}
		/* Registry interface in advapi32.lib */
		/* Get global handle to scsi keys */
		if ((oserr= RegOpenKeyEx(
				HKEY_LOCAL_MACHINE, /* opened key */
				keypath,
				0, /*DWORD ulOptions reserved*/
				KEY_ENUMERATE_SUB_KEYS|KEY_QUERY_VALUE, 
				&scsienum_scsikey_handle)) == 0) {
			/* look for atapi and or scsi ports */
			for (i=0; ; i++) {
				keysize= sizeof(keyname);
				if ((oserr= RegEnumKeyEx(
						scsienum_scsikey_handle,
						i,
						keyname,
						&keysize,
						0, /*LPDWORD lpReserved*/
						NULL,
						NULL,
						&keymodtime)) == ERROR_NO_MORE_ITEMS)
					break;
				if (oserr)
					continue;
				if (memstricmp(keyname, "Scsi Port"))
					continue; /* next key */
				if ((oserr= RegOpenKeyEx(
						scsienum_scsikey_handle, /* opened key */
						keyname,
						0, /*LPDWORD lpReserved*/
						KEY_ENUMERATE_SUB_KEYS|KEY_QUERY_VALUE, 
						&keyhandle)) != 0)
					continue;
				datasize= sizeof(drivername);
				if ((oserr= RegQueryValueEx(
						keyhandle,
						"Driver",
						0, /*LPDWORD lpReserved*/
						&datatype,
						drivername,
						&datasize)) != 0 || datatype != REG_SZ)
					drivername[0]= 0;
				RegCloseKey(keyhandle);
				if (!memstricmp(drivername, "atapi")) {
					if (!(scsienum_busflags & BUSF_IDE))
						continue; /* Ignore NT IDE */
				} else
					if (!(scsienum_busflags & BUSF_SCSI))
						continue; /* Ignore NT SCSI */
				scsienum_nadapters++;
			}
		}
	}
#else /*__DOS__*/
	/* Tell ide/aspi driver which busses to hook */
	ide_busflags = scsienum_busflags;
	support= GetASPISupportInfo();
	Driver_stat= (support>>8) & 0xff;
	scsienum_nadapters= support & 0xff;
	if (Driver_stat != SS_COMP) {
		int msgno;

		if (dos_winver() >= 0x400)
			msgno= MSGNO(0x8113);
/*8113 "TAPEDRV8113 error: The Windows 95/98/ME ASPI Manager was not found."*/

		else
			switch (scsienum_busflags & (BUSF_IDE | BUSF_SCSI)) {
			case BUSF_IDE:
				msgno= MSGNO(0x8110);
/*8110 "TAPEDRV8110 error: No IDE adapter found."*/
				break;
			case BUSF_SCSI:
				msgno= MSGNO(0x8111);
/*8111 "TAPEDRV8111 error: The DOS ASPI Manager was not found.\n"*/
/*     "Check your CONFIG.SYS."*/
				break;
			default:
				msgno= MSGNO(0x8112);
/*8112 "TAPEDRV8112 error: No IDE adapter found, and\n"*/
/*     "the DOS ASPI Manager was not found.\n"*/
/*     "Check your CONFIG.SYS."*/
				break;
			}
		error_msgset(msgno);
		return (-1);
	}

#endif /*if WIN32 else __DOS__*/
	if (scsienum_nadapters == 0) {
		error_msgset(MSGNO(0x8115), str_busflags(scsienum_busflags));
/*8115 "TAPEDRV8115 error: No %shost adapters found."*/
		return (-1);
	}
	return (0);
}

	/* cdb opcode group (bits 7..5) to cdb size lookup table */
char Cdbsize[]= {
	6,  /* 0 scsi-1 */
	10, /* 1 scsi-1 */
	10, /* 2 scsi-2 (scsi-1:reserved)*/
	6,  /* 3 reserved */
	16, /* 4 scsi-3 (scsi-1,2:reserved) */
	12, /* 5 scsi-1 */
	6,  /* 6 vendor */
	10  /* 7 vendor */
};

	/* Timeouts in seconds */
#define TMO_INQUIRY             10
#define TMO_TEST_UNIT_READY     60
#define TMO_MOTION              600
 
#define CDBF_NOPREZERO   0x800000 /* Overrides CDBF_PREZERO */
#define CDBF_PREZERO     0x400000 /* Zero buffer before transfer */
#define CDBF_IMMED       0x10000 /* Command has immediate bit set */
#define CDBF_DATAOUT     0x800   /* Data direction is to target */
#define CDBF_DATAIN      0x400   /* Data direction is from target */
#define CDBF_CMDHNDLR1   0x200   /* Used by command handlers */
#define CDBF_CMDHNDLR0   0x100   /* Used by command handlers */
#define CDBF_NOTUSED     0xff    /* Don't use */

do_cdb(
	struct tapedev *td,
	int cdbflags,      /* CDBF_* values */
	unsigned char *cdb,
	void IOFAR *bufaddr,
	long buflen,
	int timeout)       /* seconds */
{
	static char me[] = "do_cdb";
	unsigned char *api_cdb = NULL;
	unsigned char *api_sense = NULL;
	int cdbsize;
#if CDBLOG
	unsigned parm_len;
	unsigned parm_retrieved;
	unsigned parm_unrequested;
	unsigned alloc_len;
	unsigned char IOFAR *parm_p;
	char *fmt;      /* format string for log */
#endif

	if (fSimulate)
		return (0);

	cdbsize= Cdbsize[ cdb[0] >> 5 ];

#if CDBLOG
	logf("CDB: %s # %d.%d/%d.%d %s\n", fmthex(0, cdb, cdbsize),
		td->td_hano, td->td_busno, td->td_targid, td->td_lun,
		str_cdb_op(cdb[0])
	);
	switch (cdb[0]) {
	case SCMD_MODE_SELECT_6:
	/*	parm_len= cdb[4]; */
		goto show_outparm;
	case SCMD_COPY:
	/*	parm_len= (cdb[2]<<8) + cdb[3]; */
		goto show_outparm;
	case SCMD_SEND_DIAGNOSTIC:
	/*	parm_len=  (cdb[3]<<8) + cdb[4]; */
		goto show_outparm;
	case SCMD_COMPARE:
	case SCMD_COPY_AND_VERIFY:
	/*	parm_len= (unsigned)((cdb[3]<<16) + (cdb[4]<<8) + cdb[5]); */
		goto show_outparm;
	case SCMD_LOG_SELECT:
	case SCMD_MODE_SELECT_10:
	/*	parm_len= (cdb[7]<<8) + cdb[8]; */
	show_outparm:
		parm_p= (unsigned char IOFAR *)bufaddr;
		parm_len= (int)buflen;
		while (parm_len) {
			int n= 16;

			if (parm_len < 16)
				n= (int)parm_len;
			logf("DOUT: %s\n", fmthex(0, parm_p, n));
			parm_p += n;
			parm_len -= n;
		}
		break;
	default:
		break;
	}
#endif /*CDBLOG*/

#if __NT__ && __ASPI__
	if (IS_NT())
#endif
#if __NT__
	{
		typedef struct _SCSI_PASS_THROUGH_DIRECT_WITH_SENSE {
			SCSI_PASS_THROUGH_DIRECT sptd;
			int pad0;       /* align to 8-byte boundary */
			unsigned char spt_sense[SENSE_LEN];
		} SCSI_PASS_THROUGH_DIRECT_WITH_SENSE;
		SCSI_PASS_THROUGH_DIRECT_WITH_SENSE Sptd_io;
		register SCSI_PASS_THROUGH_DIRECT_WITH_SENSE *ptp= &Sptd_io;
		ULONG returned;

		api_cdb= ptp->sptd.Cdb;
		api_sense= ptp->spt_sense;
		memset(ptp, 0, sizeof(*ptp));
		ptp->sptd.Length = sizeof(ptp->sptd);
		ptp->sptd.PathId = (char)td->td_busno;
		ptp->sptd.TargetId = (char)td->td_targid;
		ptp->sptd.Lun = (char)td->td_lun;
		ptp->sptd.CdbLength = (char)cdbsize;
		ptp->sptd.SenseInfoLength = SENSE_LEN;
		switch (cdbflags & (CDBF_DATAOUT|CDBF_DATAIN)) {
		case CDBF_DATAIN:
			ptp->sptd.DataIn= SCSI_IOCTL_DATA_IN;
			break;
		case CDBF_DATAOUT:
			ptp->sptd.DataIn= SCSI_IOCTL_DATA_OUT;
			break;
		default:
			ptp->sptd.DataIn= SCSI_IOCTL_DATA_UNSPECIFIED;
			break;
		}
		ptp->sptd.DataTransferLength = buflen;
		ptp->sptd.TimeOutValue = timeout; /* seconds */
		ptp->sptd.DataBuffer = bufaddr;
		ptp->sptd.SenseInfoOffset =
			offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_SENSE, spt_sense);
		memcpy(ptp->sptd.Cdb, cdb, cdbsize);
		if (!DeviceIoControl(
				td->td_fd,
				IOCTL_SCSI_PASS_THROUGH_DIRECT,
				ptp,
				sizeof(*ptp),
				ptp,
				sizeof(*ptp),
				&returned,
				FALSE)) {

			int oserr;

			switch (oserr= GetLastError()) {
			default:
			case ERROR_INVALID_PARAMETER:
				/* bad parameter passed to DeviceIoControl() */
			case ERROR_NOT_READY:
				/* Device might be unplugged */
				Err_type = OSER;
#if CDBLOG
				{
					char osbuf[OSERR_SIZE];
					logf("OSERROR: %u # %s\n",
						oserr, stroserr(oserr, osbuf, sizeof(osbuf)));
				}
#endif
				break;
			}
			goto fail;
		}
		Scsi_stat= ptp->sptd.ScsiStatus;
	}
#endif /*__NT__*/
#if __NT__ && __ASPI__
	else
#endif
#if __ASPI__
	{
		static SRB_ExecSCSICmd Srb_io;  /* SCSI command */
		register SRB_ExecSCSICmd *scp= &Srb_io;
		int iostat;
#if WIN32
		static unsigned char motion_ops[]= {
			SCMD_REWIND,
			SCMD_WRITE_FILEMARKS,
			SCMD_SPACE,
			SCMD_VERIFY,
			SCMD_COPY,
			SCMD_ERASE,
			SCMD_LOAD_UNLOAD,
			SCMD_SEND_DIAGNOSTIC,
			SCMD_LOCATE,
			SCMD_COMPARE,
			SCMD_COPY_AND_VERIFY,
			SCMD_WRITE_SAME,
			0
		};
		int motion_state= 0;

		if (td->td_flags & TDF_W9XIDE)
		if (cdb[0] && strchr((char *)motion_ops, cdb[0]))
			motion_state= 1;
#endif
		api_cdb= scp->CDBByte;
		api_sense= scp->SenseArea;
		memset(scp, 0, sizeof(*scp));
		scp->SRB_BufLen= buflen;
		scp->SRB_BufPointer= bufaddr;
		scp->SRB_CDBLen= (char)cdbsize;
		memcpy(scp->CDBByte, cdb, cdbsize);
		switch (cdbflags & (CDBF_DATAOUT|CDBF_DATAIN)) {
		case CDBF_DATAIN:
			scp->SRB_Flags = SRB_DIR_IN;
			break;
		case CDBF_DATAOUT:
			scp->SRB_Flags = SRB_DIR_OUT;
			break;
		default:
			scp->SRB_Flags = SRB_DIR_NONE;
			break;
		}
#if WIN32
	send_tur:
#endif
		scp->SRB_Cmd= SC_EXEC_SCSI_CMD;
		scp->SRB_HaId= (char)td->td_hano;
		scp->SRB_Target= (char)td->td_targid;
		scp->SRB_Lun= (char)td->td_lun;
		scp->SRB_SenseLen= SENSE_LEN;

		iostat= aspiio(scp, timeout/*seconds*/);
#if WIN32
		if (motion_state == 2) {
			motion_state= 0;
			/*
			 * restore original cdb after test unit ready
			 * used to await completion of motion
			 */
			scp->SRB_BufLen= buflen;
			scp->SRB_BufPointer= bufaddr;
			scp->SRB_CDBLen= (char)cdbsize;
			memcpy(scp->CDBByte, cdb, cdbsize);
			switch (cdbflags & (CDBF_DATAOUT|CDBF_DATAIN)) {
			case CDBF_DATAIN:
				scp->SRB_Flags = SRB_DIR_IN;
				break;
			case CDBF_DATAOUT:
				scp->SRB_Flags = SRB_DIR_OUT;
				break;
			default:
				scp->SRB_Flags = SRB_DIR_NONE;
				break;
			}
		}
		if (iostat) {
#if CDBLOG
			switch (Err_type & ERTYPE) {
			case MYER:
				logf("MYERROR: %d # %s\n",
					My_error, strmyerror(My_error)
				);
				break;
			case DVRER:
				logf("ASPISTAT: %04x # %s\n",
					Driver_stat, str_driver_stat(Driver_stat)
				);
				break;
			default:
				logf("ERROR: non-codified aspio() error\n");
				break;
			}
#endif /*CDBLOG*/
			goto fail;
		}
#else /*__DOS__*/
		if (scp->SRB_TargStat == SSTAT_CHECK_CONDITION)
			memmove(scp->SenseArea, &scp->CDBByte[ cdbsize ], SENSE_LEN);
#endif
		switch (scp->SRB_Status) {
		case SS_COMP:
		case SS_ERR:
			break;
#if WIN32
		case SS_PENDING:
			/*
			 * Typically as the result of INQUIRY when no device 
			 * is present (for either SCSI or IDE)
			 */
			scp->SRB_HaStat= HASTAT_SEL_TO;
			break;
#endif
		default:
			Err_type = DVRER;
			Driver_stat= scp->SRB_Status;
#if CDBLOG
			logf("ASPISTAT: %02x # %s\n", Driver_stat,
					str_driver_stat(Driver_stat));
#endif
			goto fail;
		}

		switch (scp->SRB_HaStat) {
		case 0:
			break;
		case HASTAT_DO_DU: /* 0x12: DMA overrun/underrun */
			if ((cdbflags & (CDBF_DATAOUT|CDBF_DATAIN)) == CDBF_DATAIN)
				break;
			/* fall through */
		default:
			Err_type = HAER;
			Ha_stat= scp->SRB_HaStat;
#if CDBLOG
			logf("HASTAT: %02x # %s\n", scp->SRB_HaStat, str_ha_stat(scp->SRB_HaStat));
#endif
			goto fail;
		}
#if WIN32
		/*
		 * OSR2 and Win98 ASPI32 returns check condition for every 
		 * ATAPI CDB.  If Sense Error Type and Sense Key byte are zero, 
		 * declare GOOD status.
		 */
		if (scp->SRB_TargStat == SSTAT_CHECK_CONDITION)
		if (td->td_flags & TDF_W9XIDE)
		if (scp->SenseArea[0] == 0 && scp->SenseArea[1] == 0)
			scp->SRB_TargStat= 0;

		if (motion_state == 1) {
			if (scp->SRB_TargStat == 0) {
				/*
				 * To retrieve ATAPI motion command status,
				 * send a SCMD_TEST_UNIT_READY after
				 * successfully starting the motion command.
				 */
				scp->SRB_BufLen= 0;
				scp->SRB_BufPointer= NULL;
				scp->SRB_CDBLen= 6;
				memset(scp->CDBByte, 0, sizeof(scp->CDBByte));
				scp->SRB_Flags= SRB_DIR_NONE;
				motion_state= 2;
				goto send_tur;
			}
			motion_state= 0;
		}
#endif
		Scsi_stat= scp->SRB_TargStat;
	}
#endif /*__ASPI__*/

	if (Scsi_stat) {
		Err_type = SCER;
#if CDBLOG
		logf("STAT: %02x # %s\n", Scsi_stat, str_scsi_stat(Scsi_stat));
#endif
		/* If check condition ... */
		if (Scsi_stat == SSTAT_CHECK_CONDITION) {
			memset(Rsense, 0, sizeof(Rsense));
			if (api_sense)
				memcpy(Rsense, api_sense, SENSE_LEN);
#if CDBLOG
			if (api_sense) {
				parm_unrequested= 0;
				parm_retrieved= 8 + api_sense[7];
				fmt= "SENSE: %s\n";
				if (parm_retrieved > SENSE_LEN) {
					parm_unrequested= parm_retrieved - SENSE_LEN;
					parm_retrieved= SENSE_LEN;
					fmt= "SENSE: %s # NOTE: %d additional unrequested bytes were available\n";
				}
				logf(
					fmt, 
					fmthex(0, api_sense, parm_retrieved),
					parm_unrequested
				);
			} else
				logf("SENSE: Data unavailable");
#endif
		}
		goto fail;
	}
#if CDBLOG
	parm_p= (unsigned char IOFAR *)bufaddr;
	switch (cdb[0]) {
	case SCMD_REQUEST_SENSE:
		alloc_len= cdb[4];
		parm_len= 8 + parm_p[7];
		goto show_inparm;
	case SCMD_MODE_SENSE_6:
		alloc_len= cdb[4];
		parm_len= 1 + parm_p[0];
		goto show_inparm;
	case SCMD_MODE_SENSE_10:
		alloc_len= (cdb[7]<<8) + cdb[8];
		parm_len= 2 + (parm_p[0]<<8) + parm_p[1];
		goto show_inparm;
	case SCMD_INQUIRY:
		alloc_len= cdb[4];
		parm_len= 5 + parm_p[4];
		goto show_inparm;
	case SCMD_READ_BLOCK_LIMITS:
		alloc_len= 6;
		parm_len= 6;
		goto show_inparm;
	case SCMD_READ_POSITION:
		alloc_len= 20;
		parm_len= 20;
		if (cdb[1] & 2) {
			/* Long read position data */
			alloc_len= 32;
			parm_len= 32;
		}
		goto show_inparm;
	case SCMD_RECEIVE_DIAGNOSTIC:
		alloc_len=  (cdb[3]<<8) + cdb[4];
		parm_len= 16; /* size determination is vendor specific */
		goto show_inparm;
	case SCMD_LOG_SENSE:
		alloc_len= (cdb[7]<<8) + cdb[8];
		parm_len= 4 + (parm_p[2]<<8) + parm_p[3];
		goto show_inparm;
	case SCMD_READ_ELEM_STATUS:
		alloc_len= (unsigned)((cdb[7]<<16) + (cdb[8]<<8) + cdb[9]);
		parm_len= 8 + (parm_p[5]<<8) + (parm_p[6]<<8) + parm_p[7];
		goto show_inparm;
	show_inparm:
		parm_retrieved= parm_len;
		parm_unrequested= 0;
		if (parm_len > alloc_len) {
			parm_retrieved= alloc_len;
			parm_unrequested= parm_len - alloc_len;
		}
		while (parm_retrieved) {
			int n= 16;
			int hexmode= 0;

			if (parm_retrieved < (unsigned)n)
				n= parm_retrieved;
			switch (cdb[0]) {
			case SCMD_INQUIRY:
				hexmode= HEX_ASCII;
			default:
				break;
			}
			logf("DIN:  %s\n", fmthex(hexmode, parm_p, n));
			parm_p += n;
			parm_retrieved -= n;
		}
		if (parm_unrequested)
			logf("# DIN: NOTE: %d additional unrequested bytes available\n",
				parm_unrequested
			);
		break;
	default:
		break;
	}
	logf("STAT: %02x # %s\n", Scsi_stat, str_scsi_stat(Scsi_stat));
#endif /*CDBLOG*/
	return (0);
fail:
	return (-1);
}

do_cdb_nodata(struct tapedev *td, unsigned char *cdb, int timeout)
{
	return (do_cdb(td, 0, cdb, NULL, 0, timeout));
}

/*
 * tp_inquiry() -- fetches standard, or vital product inquiry data
 *
 * tp_inquiry() front ends the SCSI Inquiry (0x12) command.
 *
 * Parameters:
 *     td  gives the address of an opened tape device structure.
 *     page selects whether standard or vital product inquiry data
 *         page data is to be returned, and other options, as
 *         follows:
 *         INQUIRY_NOPREZERO, a flag, may be ORd into page do
 *             disable pre-zeroing of buf.
 *         INQUIRY_VPD (bit 8), is a flag that is ORd into the
 *             page argument to indicate a Vital Product Data
 *             page is requested.  INQUIRY_VPD, is mandatory for
 *             Vital Product Data page 0 and optional for all
 *             other non-zero pages.
 *         0                         for Standard inquiry
 *         1 through 255 (0xff)      for corresponding vital
 *             product pages
 *         INQUIRY_VPD | 0 (or 256)  for Vital Product Data page 0
 *     buf gives the address of a buffer for receiving Inquiry
 *         data.
 *     length has an 8-bit data transfer length in bytes. Standard
 *         inquiry is 36 bytes in length.
 *     underflow gives the transfer length below which to set an
 *         UNDERFLOW error.
 *
 * Returns:
 *     Normally, the number of bytes transferred is returned.
 *     On error, the value -1 is returned and scsiTargp->st_error
 *     is set with an Irack error defined in scomdef.h.   The ERROR
 *     global is also set if not prevented by scsi_ERROR_disable.
 */
	/* INQUIRY_* values optionally ORed into tp_inquiry(,page,,,) */
#define INQUIRY_NOPREZERO   CDBF_NOPREZERO /* Don't prezero buf */
#define INQUIRY_VPD         CDBF_CMDHNDLR0 /* Fetch vital product data page 
                                           (required only for vpd page 0) */
tp_inquiry(
	struct tapedev *td,
	int page,             /* INQUIRY_VPD, 8-bit Vital Product Data page */
	void *buf,            /* data buffer address */
	unsigned length)      /* 8-bit data transfer length in bytes */
{
	static unsigned char cdb[6] = { SCMD_INQUIRY };
	unsigned char *p = (unsigned char *)buf;

	/*
	 * Inquiry 0x12
	 * cdb[1] bit0: Enable Vital Product Data (EVPD)
	 * cdb[2]:      Page code
	 * cdb[4]:      Allocation length
	 */
	memset(buf, 0, length);    /* zero the buffer */
	if (length > 1)
		*p = 0x1f; /* Set PDT to "Unknown" */
	if (length > 255)
		length = 255;

	/* For non-zero 8-bit page code, turn on vital product data */
	if (page & 0xff)
		page |= INQUIRY_VPD;
	cdb[1] = 0;
	if (page & INQUIRY_VPD)
		cdb[1] = 1; /* EVPD */
	cdb[2] = (char)page;
	cdb[4] = (char)length;
	if (do_cdb(td, CDBF_DATAIN, cdb, buf, length, TMO_INQUIRY) < 0)
		return (-1);
	if (length > (unsigned)(5 + p[4]))
		length = 5 + p[4];
	return (length); /* actual read */
}

/*
 * tp_unload() -- unloads tape
 *
 * tp_unload() front ends the SCSI Load Unload (0x1b) command's
 * unload function.  See also tp_load().
 *
 * Parameters:
 *     td  gives the address of an opened tape device structure.
 *     loadflags may have the following bits set:
 *         UNLOAD_IMMED, when set, causes the command to return
 *             completion immediately after the operation has
 *             begun.  Otherwise, when clear, completion is
 *             returned after the operation has completed.
 *         UNLOAD_HOLD, when set, and supported, causes a
 *             cartridge to be moved to the inserted but
 *             unthreaded state.
 *         UNLOAD_EOT, when set, and supported, causes tape to be
 *             positioned at end-of-tape.
 *         UNLOAD_RETEN, when set, and supported, causes tape to
 *             be tensioned.
 *
 * Returns:
 *     If successful the value 0 is returned.
 *     On error, the value -1 is returned and Err_type indicates
 *     the error variable to examine.
 */
#define UNLOAD_IMMED        CDBF_IMMED
#define UNLOAD_HOLD         0x8         /* Inserted but unthreaded */
#define UNLOAD_EOT          0x4         /* Position to EOT before unload */
#define UNLOAD_RETEN        0x2         /* Retension */

tp_unload(
	struct tapedev *td,
	int unloadflags)      /* UNLOAD_IMMED, UNLOAD_EOT, UNLOAD_RETEN */
{
	static unsigned char cdb[6] = { SCMD_LOAD_UNLOAD };

	/*
	 * Load Unload 0x1b
	 * cdb[1] bit0: Immediate (Immed)
	 * cdb[4] bit3: Hold
	 * cdb[4] bit2: End-of-tape (EOT)
	 * cdb[4] bit1: Retension (Re-ten)
	 * cdb[4] bit0: Load (not unload)
	 */
	if (unloadflags & UNLOAD_IMMED)
		cdb[1] |= 1; /* Immed */
	cdb[4] = (char)(unloadflags & (UNLOAD_HOLD | UNLOAD_EOT | UNLOAD_RETEN));
	return( do_cdb_nodata(td, cdb, TMO_MOTION) );
}

/*
 * tp_test_unit_ready() -- tests whether unit is read for medium access
 *
 * tp_test_unit_ready() front ends the SCSI Test Unit Ready (0x00)
 * command.
 *
 * Parameters:
 *     td  gives the address of an opened tape device structure.
 *
 * Returns:
 *     If the medium is ready for access, the value 0 is returned.
 *     On error, the value -1 is returned and Err_type indicates
 *     the error variable to examine.
 */
tp_test_unit_ready(
	struct tapedev *td)
{
	static unsigned char cdb[6] = { SCMD_TEST_UNIT_READY };

	/*
	 * Test Unit Ready 0x00
	 */
	return( do_cdb_nodata(td, cdb, TMO_TEST_UNIT_READY) );
}

/*
 * tp_write_firmware() -- downloads firmware
 *
 * tp_write_firmware() is an argument converting front end for
 * tp_write_buffer() and is used to download and/or commit firmware
 * to a tape drive's flash EEPROM.  The values for mode and adrs are
 * drive model specific.
 *
 * Parameters:
 *     td  gives the address of an opened tape device structure.
 *     mode has a 3-bit Write Buffer Mode:
 *         Bits 2..0 give the read buffer mode value that may be
 *             4=Download microcode, or
 *             5=Download microcode and save
 *             Other values may be valid.  Consult the drive
 *                 specification.
 *     adrs is a 32-bit value and corresponds to
 *         (MSB)CDB[2]..CDB[5](LSB)
 *         adrs is constructed as follows
 *             buffer_id:     bits 31..24 of adrs
 *             buffer_offset: bits 23..0 of adrs
 *     buf gives the address of buffer containing firmware (Write
 *         Buffer) data.
 *     length gives a 24-bit data transfer length in bytes.
 *
 * Returns:
 *     Normally, the number of bytes transferred is returned.
 *     On error, the value -1 is returned and Err_type indicates
 *     the error variable to examine.
 */
tp_write_firmware(
	struct tapedev *td,
	int mode,       /* Write buffer mode bits */
	long adrs,      /* ADRS field for CDB */
	void IOFAR *buf,
	long length,
	int timeout)
{
	unsigned char cdb[10] = { SCMD_WRITE_BUFFER };
	/*
	 * Write Buffer 0x3b
	 * cdb[1] bit2..0: Mode
	 *      4=Download microcode, 5=Download microcode and save
	 * cdb[2..5]:      ADRS (Seagate specific)
	 * cdb[6..8]:      Parameter list length
	 */
	logf("WriteBuffer(mode=0x%x, adrs=0x%08lx, length=0x%lx)\n",
			mode, adrs, length);
	cdb[1] = (char)mode;
	cdb[2] = (char)(adrs>>24);
	cdb[3] = (char)(adrs>>16);
	cdb[4] = (char)(adrs>>8);
	cdb[5] = (char)adrs;
	cdb[6] = (char)(length>>16);
	cdb[7] = (char)(length>>8);
	cdb[8] = (char)length;

	return (do_cdb(td, CDBF_DATAOUT, cdb, buf, length, timeout));
}

spinner(void)
{
	static char chars[] = { '-', '\\', '|', '/' };
	static idx;

	co_putca(2, 78, chars[ idx++ & 3 ], FG_LRED | BG_BLACK);
	co_cursor_hide();
	return (0);
}

sleepdot(int seconds)
{
	if (!fSimulate)
		my_sleep(seconds);
#if __TAPEDRV__
	spinner();
#endif
#if __FUPU__
	my_printf(".");
#endif
	return (0);
}

/*
 * updatealg -- firmware update algorithm
 */
struct updatealg {
	char  *ua_name;           /* Character name used in .ini */
	int    ua_updtalg;        /* Unique UPDTALG_ integer value */
#define UPDTALG_UNKNOWN             0
#define UPDTALG_STANDARD            1
#define UPDTALG_SEAGATE_QIC3080     2
#define UPDTALG_SEAGATE_TRAVAN      3
#define UPDTALG_SEAGATE_TR7         4
#define UPDTALG_SEAGATE_DDS2_256K   5
#define UPDTALG_SEAGATE_DDS2_512K   6
#define UPDTALG_SEAGATE_DDS3        7
#define UPDTALG_SEAGATE_DDS4        8
#define UPDTALG_SEAGATE_AIT         9
#define UPDTALG_SEAGATE_ULTRIUM     10
#define UPDTALG_EXABYTE_MAMMOTH     11
#define UPDTALG_HP_ULTRIUM          12
#define UPDTALG_HP_LIBRARY          13
#define UPDTALG_IBM_ULTRIUM         14
#define UPDTALG_QUANTUM_DLT         15
#define UPDTALG_QUANTUM_DLT_8K      16
	int    ua_fw_signature;   /* Firmware file signature verification alg: FWSIG_*  */
#define FWSIG_NONE                0
#define FWSIG_SEAGATE_DDS2_3      1
#define FWSIG_SEAGATE_AIT         2
#define FWSIG_SEAGATE_ULTRIUM     3
	char **ua_prepcmds;       /* Null terminated list of preperation commads */
	int    ua_flags;          /* UAF_* values */
#define UAF_HEX         0x40 /* If filename ends in .hex, decode intel hex */
#define UAF_DATAHDR     0x80 /* If filename ends in fmr, strip 1088 byte HP Data Hdr */
#define UAF_SEGOFS      0x20 /* Drive uses segment:offset (not linear) ADRS */
#define UAF_FIXEDADRS   0x10 /* Use fixed Write Buffer ADRS (don't adjust offset) */
#define UAF_ZCOMMIT     0x8  /* Use final zero length Write Buffer to commit to EEPROM */
#define UAF_ONEWRITE    0x4  /* Use single Write Buffer to download and commit */
	int    ua_wrbuf_mode_multi;/* Write Buffer mode for multiple transfers */
	int    ua_wrbuf_mode_final;/* Write Buffer mode for final or UAF_ONEWRITE transfer */
	long   ua_wrbuf_adrs;     /* Initial Write Buffer id(byte[3]), offset(byte[2..0]) */
	long   ua_wrbuf_size;     /* Write Buffer transfer block size */
	int    ua_wrbuf_seconds;  /* Max seconds for final write buffer */
	long   ua_image_fixedsize;/* Image must be of fixed size */
	long   ua_image_minsize;  /* Image must not be smaller than mininmum */
	long   ua_image_maxsize;  /* Image must not be larger than maximum */
	long   ua_image_pagesize; /* Image must be a multiple of page size */
};

/*
 * For unattended (no operator intervention) firmware update 
 * it is preferable to rewind tape rather than unload. 
 * It is sufficient to rewind Seagate AIT. Seagate DAT and Travan 
 * require unload.
 * WARNING: The Firmware download timeout, for a drive that doesn't 
 * need unload, may need to be increased to account for an 
 * auto-unloading procedure used by said drive.
 *
 * Note: On OpenServer (5.0.4, 5.0.5) sending InitElementStatus 
 * to a Seagatge Travan ATAPI causes a hang on a subsequent
 * TestUnitReady.
 */
char s_rewind[] = "Rewind";
char s_ies[] = "InitElementStatus";
char s_unload[] = "Unload";
char s_noop[] = "NoOp";

char *prepcmds_all[] = { s_rewind, s_ies, s_unload, s_noop, NULL };
char *prepcmds_rewind[] = { s_rewind, NULL };
char *prepcmds_ies[] = { s_ies, NULL };
char *prepcmds_unload[] = { s_unload, NULL };
char *prepcmds_ies_unload[] = { s_ies, s_unload, NULL };
char *prepcmds_none[] = { NULL };

#define KB(n) ((n)*1024L)
/*
 * Image sizes
 * KB( 154),  0x26800, 157696, (26K+128K) bali1/falcon1
 * KB( 282),  0x46800, 288768, (26K+256K) bali2,falcon2,hornet
 * KB( 256),  0x40000, 262144 piranha/peregrine/cobra2
 * KB( 512),  0x80000, 524288 cobra3 
 * KB( 640),  0xa0000, 655360 dds4 
 * KB( 992),  0xf8000,1015808 seagate ultrium
 * KB(1024), 0x100000,1048576 ibm ultrium
 */
#define D UAF_DATAHDR   /* If filename ends in fmr, strip 1088 byte HP Data Hdr */
#define H UAF_HEX       /* If filename ends in .hex, decode intel hex */
#define S UAF_SEGOFS    /* DAT drive uses segment:offset (not linear) ADRS */
#define F UAF_FIXEDADRS /* Fixed Write Buffer ADRS (don't adjust offset) */
#define Z UAF_ZCOMMIT   /* Use final zero length Write Buffer to commit to EEPROM */
#define O UAF_ONEWRITE  /* Use a single Write Buffer to download and commit */

/*      ua_name            , ua_updtalg               , ua_fw_signature      , ua_prepcmds, */
/*  ua_flags */
/*         , ua_wrbuf_mode_multi */
/*            , ua_wrbuf_mode_final */
/*               , ua_wrbuf_adrs */
/*                           , ua_wrbuf_size */
/*                                   , ua_wrbuf_seconds */
/*                                         , ua_image_fixedsize */
/*                                                   , ua_image_minsize */
/*                                                             , ua_image_maxsize */
/*                                                                       , ua_image_pagesize */
struct updatealg updatealgs[] = {
/* 1*/{ "standard"         , UPDTALG_STANDARD         , FWSIG_NONE           , NULL, 
    Z      , 4, 5, 0         , 0     , 120 , 0       , 0       , 0       , 0    },
/* 2*/{ "seagate_qic3080"  , UPDTALG_SEAGATE_QIC3080  , FWSIG_NONE           , prepcmds_unload,
    O      , 0, 5, 0         , 0     , 45  , KB(154) , 0       , 0       , 0    },
/* 3*/{ "seagate_travan"   , UPDTALG_SEAGATE_TRAVAN   , FWSIG_NONE           , prepcmds_unload,
    O      , 0, 5, 0         , 0     , 45  , KB(282) , 0       , 0       , 0    },
/* 4*/{ "seagate_tr7"      , UPDTALG_SEAGATE_TR7      , FWSIG_NONE           , prepcmds_unload,
    Z      , 4, 5, 0         , 0     , 120 , 0       , 0       , 0       , 0    },
/* 5*/{ "seagate_dds2_256k", UPDTALG_SEAGATE_DDS2_256K, FWSIG_SEAGATE_DDS2_3 , NULL,
    O|S|H  , 0, 5, 0x20000000, 0     , 60  , KB(256) , 0       , 0       , 0    },
/* 6*/{ "seagate_dds2_512k", UPDTALG_SEAGATE_DDS2_512K, FWSIG_SEAGATE_DDS2_3 , NULL,
    Z|S|H  , 4, 7, 0x20000000, 0     , 60  , KB(512) , 0       , 0       , 0    },
/* 7*/{ "seagate_dds3"     , UPDTALG_SEAGATE_DDS3     , FWSIG_SEAGATE_DDS2_3 , NULL,
    Z|H    , 4, 7, 0x00100000, 0     , 60  , KB(512) , 0       , 0       , 0    },
/* 8*/{ "seagate_dds4"     , UPDTALG_SEAGATE_DDS4     , FWSIG_NONE           , NULL,
    Z      , 4, 5, 0         , 0     , 60  , KB(640) , 0       , 0       , 0    },
/* 9*/{ "seagate_ait"      , UPDTALG_SEAGATE_AIT      , FWSIG_SEAGATE_AIT    , NULL,
    Z      , 2, 5, 0x00380000, 0     , 45  , 0       , KB(256) , KB(512) , 0    },
/*10*/{ "seagate_ultrium"  , UPDTALG_SEAGATE_ULTRIUM  , FWSIG_SEAGATE_ULTRIUM, NULL,
    Z      , 4, 5, 0         , 0     , 120 , KB(992) , 0       , 0       , 0    },
/*11*/{ "exabyte_mammoth"  , UPDTALG_EXABYTE_MAMMOTH  , FWSIG_NONE           , NULL,
     0     , 7, 7, 0         , 0     , 121 , 0       , 0       , 0       , 256  },
/*12*/{ "hp_ultrium"       , UPDTALG_HP_ULTRIUM       , FWSIG_NONE           , NULL,
    Z|D    , 4, 5, 0         , 0     , 120 , 0       , 0       , 0       , 0    },
/*13*/{ "hp_library"       , UPDTALG_HP_LIBRARY       , FWSIG_NONE           , NULL,
    F|D    , 4, 5, 0x01000000, KB(32), 420 , 0       , 0       , 0       , 0    },
/*14*/{ "ibm_ultrium"      , UPDTALG_IBM_ULTRIUM      , FWSIG_NONE           , NULL,
     0     , 4, 5, 0         , 0     , 121 , KB(1024), 0       , 0       , 0    },
/*15*/{ "quantum_dlt"      , UPDTALG_QUANTUM_DLT      , FWSIG_NONE           , NULL,
    Z      , 4, 5, 0         , 0     , 200 , 0       , 0       , 0       , 4096 },
/*16*/{ "quantum_dlt_8k"   , UPDTALG_QUANTUM_DLT_8K   , FWSIG_NONE           , NULL,
    Z      , 4, 5, 0         , KB(8) , 200 , 0       , 0       , 0       , 8192 },
};

#undef D
#undef H
#undef S
#undef F
#undef Z
#undef O

 struct updatealg *
get_updatealg_ptr(char *updtalg_str)
{
	struct updatealg *uap;

	for (uap=updatealgs; uap<&updatealgs[NELEM(updatealgs)]; uap++)
		if (!stricmp(uap->ua_name, updtalg_str))
			return (uap);
	return (NULL);
}

is_dosmode_only_updatealg(char *updtalg_str)
{
	struct updatealg *uap;

	if ((uap = get_updatealg_ptr(updtalg_str)) != NULL
	  && (uap->ua_flags & UAF_ONEWRITE))
		return (1); /* Can only update in DOS only mode */
	return (0);
}

 char *
fmt_inq_version(unsigned char *inq) /* parses version field of standard inquiry */
{
	static char version[4+1];

	memcpy(version, &inq[32], 4);
	return (version);
}

 char *
fmt_cfr_dashnum(unsigned char *inq) /* parses out dash number cfr inquiry */
{
	static char dashnum[3+1];
	char *p;
	int i;

	/*
	 * Seagate DAT Controller Firmware Revision VPD Inquiry Page (0xc0)
	 *   rev[0] bit7..5: Peripheral Qualifier
	 *   rev[0] bit4..0: Peripheral Device Type
	 *   rev[1]          Page code (0xc0)
	 *   rev[3]          Additional Page Length (17 or 19)
	 *   rev[4]          ASCII Length (16 or 18)
	 *   rev[5..8]       4-character Revision
	 * Pre-y2k fix:
	 *   rev[9..16]      8-character Date: mm/dd/yy
	 *   rev[17..20]     4-character OEM right space paddes
	 *                     E.g.:  "-0  ", "-52 ", "-400"
	 * Post-y2k fix:
	 *   rev[9..18]      10-character Date: yyyy/mm/dd??? (not sure of format)
	 *   rev[19..22]     4-character OEM right space paddes
	 *                     E.g.:  "-0  ", "-52 ", "-400"
	 */
	/*
	 * inq[4] = ASCII length: 16=pre y2k fix; 18=post y2k fix
	 * Skip dash ('-') and copy number from CFRinquiry[18 or 20]
	 */
	p = (char *)inq + 18;
	if (inq[4] >= 18)
		p += 2;
	for (i=0; i<3; i++) {
		dashnum[i]= p[i];
		if ((unsigned char)dashnum[i] <= ' ')
			break;
	}
	dashnum[i]= 0;
	return (dashnum);
}

/*
 * cmp_dashno() -- compares dash numbers from cfr inquiry, filename, 
 *   or validation table
 * strips insignificant zeros
 */
cmp_dashno(char *a, char *b)
{
	/* Skip insignificant '0's */
	while (a[0] == '0' && isdigit(a[1]))
		a++;
	while (b[0] == '0' && isdigit(b[1]))
		b++;
	return (stricmp(a, b));
}

/*
 * autoupdate_compare() -- builds version string; compares file version with drives
 *
 * Returns:
 *   0 if versions match
 *   1 if drive version is newer than file version
 *   -1 if drive version is older than file version
 */
autoupdate_compare(
	struct tapedev *td)
{
	static char me[] = "autoupdate_compare";
	char *p;
	int dc;
	int c;
	int i;
	int bitmap;
	int cmpstat = 0;

	p = td->td_autover;
	for (i=0; i<4; i++) {
		/* get drive character regardless if null */
		dc = (unsigned char)td->td_curver[ i ];
		/* use match_getc() to permit use of an escaped "?" ("^?") */
		if ((c = match_getc(0, &p)) == 0)
			c = '?';
		if (c == '?')
			c = dc;
		td->td_newver[i] = (char)c;
	}
	td->td_newver[4] = 0; /* guarantee termination */

	/* Check whether compare control has any valid characters */
	bitmap = 0;
	for (p=td->td_cmpcontrol; *p; p++)
		if ((unsigned)(i = *p - '0') < 4)
			bitmap |= 1<<i;
	p = td->td_cmpcontrol;
	if (!bitmap)
		p = "0123"; /* no valid characters, use default */
	for (; *p; p++) {
		int a, b;

		if ((unsigned)(i = *p - '0') >= 4)
			continue; /* ignore invalid indicies */
		a = toupper((unsigned char)td->td_curver[ i ]);
		b = toupper((unsigned char)td->td_newver[ i ]);
		if (a > b) {
			cmpstat = 1; /* Drive version is newer than file */
			break;
		}
		if (a < b) {
			cmpstat = -1; /* Drive version is older than file */
			break;
		}
	}
	logf("%s: curver=%s newver=%s autover=%s cmpcontrol=%s cmpstat=%d\n",
		me, td->td_curver, td->td_newver, td->td_autover, td->td_cmpcontrol, cmpstat);
	return (cmpstat); /* Version match */
}

#if LOTSOFMEMORY
scan_tclass_info(struct tapedev *td)
{
	unsigned char inq_cfr_buf[ NB_INQ_CFR ];
	unsigned char inq_dfr_buf[ NB_INQ_DFR ];
	unsigned char inq_serno_buf[ NB_INQ_SERNO ];
	int didprint= 1;

	switch (td->td_tclassno) {
	case TCLASS_TRAVAN:
		my_msgprintf(MSGNO(0x4530));
/*
 *4530 "   -- Firmware not updatable in Windows 95/98/ME Graphics mode,\n"
 *     "      or from Windows NT.\n"
 *     "   -- Use MS-DOS, or Windows 95/98/ME in MS-DOS Only mode.\n"
 */
		break;
	case TCLASS_DAT:
		/* Controller Firmware Revision page */
		if (tp_inquiry(td, 0xc0, inq_cfr_buf, NB_INQ_CFR) >= 5+16) {
			char rev[4+1];
			char date[8+2+1];
			char oem[4+1];
			int date_extra;

			/* ASCII length: 16=pre y2k fix; 18=post y2k fix */
			date_extra = 0;
			if (inq_cfr_buf[4] >= 18)
				date_extra = 2;
			strcpyn(rev, (char *)&inq_cfr_buf[5], 4+1);
			strcpyn(date, (char *)&inq_cfr_buf[9], 8 + date_extra + 1);
			strcpyn(oem, (char *)&inq_cfr_buf[17 + date_extra ], 4+1);

			my_msgprintf(
				MSGNO(0x4470),
/*4470 "   Tape Controller Firmware - Rev: %s"*/
				rev
			);
			if (inq_cfr_buf[4] > 4)
				my_msgprintf(
					MSGNO(0x4471),
/*4471 ", Date: %s"*/
					date
				);
			if (inq_cfr_buf[4] > 12)
				my_msgprintf(
					MSGNO(0x4472),
/*4472 ", OEM model: %s"*/
					oem
				);
			my_newline();
		}
		/* Drive Firmware Revision page */
		if (tp_inquiry(td, 0xc1, inq_dfr_buf, NB_INQ_DFR) > 5) {
			my_msgprintf(
				MSGNO(0x4480),
/*4480 "   Tape Drive Firmware Revision: %s\n"*/
				&inq_dfr_buf[5]
			);
		}
		/* Drive Serial Number page */
		if (tp_inquiry(td, 0x80, inq_serno_buf, NB_INQ_SERNO)  > 4) {
			my_msgprintf(
				MSGNO(0x4490),
/*4490 "   Tape Drive Serial Number: %s\n"*/
				&inq_serno_buf[4]
			);
		}
		break;
	case TCLASS_AIT:
		if (!memstricmp(&td->td_inq_std[16], "AIT-LDR"))
			my_msgprintf(MSGNO(0x4600));
/*4600 "   -- Seagate AIT Loader: Firmware update via SCSI bus not supported.\n"*/
		else
			didprint = 0;
		break;
	case TCLASS_ULTRIUM:
	default:
		didprint= 0;
		break;
	}
	return (didprint);
}
#endif /*LOTSOFMEMORY*/

status_print(struct tapedev *td)
{
	static char me[] = "status_print";
	static once;
	static char *addrfmt_str;
	static char *devicefmt_str;
	static char addrbuf[16];

	if (!once) {
		once = 1;
#if WIN32
		if (IS_NT())
		{
			addrfmt_str = my_strdup(cmsgex( MSGNO(0x6851) ), me);
/*6851 "%d.%d/%d.%d"*/
		}
		else
#endif
		{
			addrfmt_str = my_strdup(cmsgex( MSGNO(0x6250) ), me);
/*6850 "%d/%d.%d"*/
		}
		devicefmt_str = my_strdup(cmsgex( MSGNO(0x6860) ), me);
/*6860 "%-8s %-24.24s  %-4.4s    %-4.4s    %s"*/

	}
	if ((unsigned)td->td_fwstate < NELEM(Fwstate_ctr)) {
		if (fStatSim) {
			/* Simulate all possible states */
			static fwstate = FWS_UNRECOGNIZED; /* 1 */

			td->td_fwstate = fwstate;
			if (++fwstate >= N_FWSTATE) 
				fwstate = FWS_UNRECOGNIZED; /* 1 */
		}
		Fwstate_ctr[ td->td_fwstate ]++;
	}

#if WIN32
	if (IS_NT()) {
		my_snprintf(addrbuf, sizeof(addrbuf), addrfmt_str,
/*6851 "%d.%d/%d.%d"*/
			td->td_hano,
			td->td_busno,
			td->td_targid,
			td->td_lun
		);
	}
	else
#endif
	{
		my_snprintf(addrbuf, sizeof(addrbuf), addrfmt_str,
/*6850 "%d/%d.%d"*/
			td->td_hano,
			td->td_targid,
			td->td_lun
		);
	}
	my_printf(
		devicefmt_str,
/*6860 "%-8s %-24.24s  %-4.4s    %-4.4s    %s\n"*/
		addrbuf,
		&td->td_inq_std[8],
		td->td_curver,
		td->td_newver,
		str_fwstate(td->td_fwstate)
	);
	if (fRecognizable) {
		my_printf("##Device=%s,%s,%-24.24s,%-4.4s,%s,%s,%d,%s\n",
			addrbuf,
			str_pdt(td->td_inq_std[0]),
			&td->td_inq_std[8],
			&td->td_inq_std[32],
			td->td_curver,
			td->td_newver,
			td->td_fwstate,
			str_fwstate(td->td_fwstate)
		);
	}
	return (0);
}

#define ENUMF_HOST        0x20 /* enumerating ASPI or NT host (not device) */
#define ENUMF_SIMULATE    0x10 /* Simulation modifier (no actual drives) */
#define ENUMF_SELECTLIST  0x8 /* Build a device selection list */
#define ENUMF_AUTOLIST    0x2 /* Build an auto update device list */
#define ENUMF_STATUS      0x4 /* Build a device status list */
#define ENUMF_SCAN        0x1 /* Informational scan */

scsienum_is_addable_device(int enumflags, struct tapedev *td)
{
	if (!(enumflags & (ENUMF_SELECTLIST | ENUMF_AUTOLIST | ENUMF_STATUS)))
		return (0);
	switch (td->td_fwstate) {
	case FWS_UNKNOWN: /* Not a tape device */
		return (0);
	case FWS_UNRECOGNIZED: /* Unrecognized tape device */
		if (enumflags & ENUMF_SELECTLIST)
			return (0);
		break;
	}
	if (Arg_targid >= 0 && Arg_targid != td->td_targid)
		return (0); /* target id mismatch */
	return (1); /* ok to add */
}

/*
 * scsienum_device() -- evaluates device and possibly adds to Tapedev_list
 *
 * scsienum_device() evaluates the argument tape device (td). 
 * The primary basis for the evaluation is the device's inquiry 
 * data as well as inquiry strings from the .ini.  
 * An attempt is made to match device's inquiry string with 
 * a [<device_section>] from the .ini file.
 * The device's inquiry data, stored in td_inq_std[], is provided by 
 * the caller.  [<device_section>] names as well as InquryStd= values 
 * are assumed to be previously loaded and cached in Inidev_list.
 *
 * As a result of evaluation, the td_fwstate member of argument 
 * tapedev structure is set with a FWS_* value.  
 * Also, when the device is recognized as a tape device (whether or not 
 * supported) and ENUMF_SELECTLIST, ENUMF_AUTOLIST, or ENUMF_STATUS
 * is set, the argument tapedev structure is copied to an allocated 
 * block of memory which then is appended to the global Tapedev_list. 
 *
 * On entry to scsienum_device() the device should be opened except 
 * when enumflags has ENUMF_HOST set.
 */
 struct tapedev *
scsienum_device(
	int enumflags,
	struct tapedev *td) /* device description */
{
	static char me[] = "scsienum_device";
	char ascii_hex_buf[ NB_INQ_MAX*2 + 1]; /* +1 for NUL terminator */
	int ascii_hex_once = 0;
	unsigned char inq_cfr_buf[ NB_INQ_CFR ];
	int cfr_inquire_once = 0;
	struct tapedev *mtd = NULL; /* malloced tape device buffer */
	struct inidev *idev = NULL;
	int didprint = 0;
	char *section_save = NULL;
	char addrbuf[16];
	char *pdt_str;
	static char *addrfmt_str;
	static char *addrwidth_str;
	static char *inqfmt_str;
	static char *controller_str;
	int (*prf)(char *fmt, ...);  /* printf output */
	int (*msgf)(int msgno, ...); /* message printf output */

	if (enumflags & ENUMF_SCAN) {
		prf = my_printf;
		msgf = my_msgprintf;
	} else {
		prf = logf;
		msgf = msglogf;
	}

	td->td_fwstate = FWS_UNKNOWN; /*0 FWS_UNKNOWN: disk,cd,...,etc. */
	td->td_tclassno = TCLASS_UNKNOWN;
	logf("%s: \"%s\": %s [%-28.28s]\n",
		me, td->td_devname,
		fmthex(0, td->td_inq_std, 8), &td->td_inq_std[8]
	);
	if (!(scsienum_busflags & td->td_busflag))
		return (NULL);  /* Ignore bus */

	/*
	 * Check for a supported device
	 * - If a device (not a host adapter)
	 * - If logical unit zero.
	 * - If 28-characters of inquiry returned
	 */
	if (!(enumflags & ENUMF_HOST))
	if (td->td_inq_std[4] >= 3+28) {
		/*
		 * Preliminary .INI device list scan.
		 * If 28-character match read full length inquiry data.
		 */
		for (idev=Inidev_list; idev; idev=idev->idev_link) {
			unsigned char inq_std_buf[ NB_INQ_MAX ];
			int pdt;
			char *pat;
			int inq_len;

			pat = idev->idev_inquirystd;
			if ((pdt = inqgetpdt(&pat)) < 0)
				continue; /* Unrecognized or missing peripheral device type */
			if ((td->td_inq_std[0] & 0x1f) != pdt)
				continue; /* Peripheral device type mismatch */
			if (inqmatch(pat, td->td_inq_std) <= 0)
				continue; /* InquiryStd mismatch */
			if (enumflags & ENUMF_SIMULATE)
				break;
			if (!td->td_inaccessible && (inq_len = tp_inquiry(td, 0, inq_std_buf, NB_INQ_MAX)) >= 0) {
				/* use inquiry actually returned by device */
				memcpy(td->td_inq_std, inq_std_buf, NB_INQ_MAX);
				td->td_inq_len = inq_len;
			}
			break;
		}
#if LOTSOFMEMORY
		td->td_tclassno = get_tclassno(td);
#endif
		/*
		 * Main .INI device list scan.
		 * If 28-character match, then preform additional 
		 * hex or CFR_OEM match if specified in the .INI. 
		 */
		for (idev=Inidev_list; idev; idev=idev->idev_link) {
			int mstat;
			int pdt;
			char *pat;

			pat = idev->idev_inquirystd;
			if ((pdt = inqgetpdt(&pat)) < 0)
				continue; /* Unrecognized or missing peripheral device type */
			if ((td->td_inq_std[0] & 0x1f) != pdt)
				continue; /* Peripheral device type mismatch */
			if (inqmatch(pat, td->td_inq_std) <= 0)
				continue; /* InquiryStd mismatch */
			if (!(enumflags & ENUMF_SIMULATE) && idev->idev_inquiryhex) {
				int offset;
				/*
				 * Need to match InquiryStdHex pattern
				 */
				if (!ascii_hex_once) {
					int i;
					char *p;

					ascii_hex_once = 1;
					/* translate to hex string */
					memset(ascii_hex_buf, 0, sizeof(ascii_hex_buf));
					p = ascii_hex_buf;
					for (i=0; i<td->td_inq_len; i++) {
						int tmp = td->td_inq_std[ i ];
						*p++ = (char)nib2ahex(tmp>>4);
						*p++ = (char)nib2ahex(tmp);
					}
				}
				pat = idev->idev_inquiryhex;
				mstat = -1;
				if ((unsigned)(offset = hexgetoffset(&pat)) < sizeof(ascii_hex_buf)-1)
					mstat = hexmatch(pat, &ascii_hex_buf[ offset*2 ]);
				logf("%s: InquiryStdHex=\"%s\": M%satch\n",
					me, idev->idev_inquiryhex, (mstat<=0) ?"ism" :"");
				if (mstat <= 0)
					continue; /* mismatch */
			}
			if (idev->idev_inquirycfr_oem[0]) {
				/*
				 * Need to match 
				 * Seagate CFR Inquiry page OEM field
				 */
				if (td->td_inaccessible)
					continue; /* nomatch */
				if (!cfr_inquire_once) {
					cfr_inquire_once = 1;
					if (enumflags & ENUMF_SIMULATE)
						inqcfr_simulate( idev->idev_inquirycfr_oem, inq_cfr_buf );
					else
						/* Get Seagate DAT Controller Firmware Revision firmware page */
						tp_inquiry(td, 0xc0, inq_cfr_buf, sizeof(inq_cfr_buf));
					/* Format Seagate OEM field and save it in the tape device structure */
					strcpy(td->td_cfr_oem, fmt_cfr_dashnum(inq_cfr_buf));
				}
				mstat= cmp_dashno(idev->idev_inquirycfr_oem, td->td_cfr_oem);
				logf("%s: InquiryCFR_OEM=\"%s\": M%satch\n",
					me, idev->idev_inquirycfr_oem, (mstat) ?"ism" :"");
				if (mstat)
					continue; /* mismatch */
			}
			break; /* match: idev is non-NULL */
		}
		memcpy(td->td_curver, &td->td_inq_std[32], 4);
		if (idev == NULL) {
			/* No match with device list from .INI */
			switch (td->td_inq_std[0] & 0x1f) {
			case PDT_TAPE:
			case PDT_CHANGER:
				td->td_fwstate = FWS_UNRECOGNIZED;
				break;
			}
		} else {
			int cmpstat;

			/* Matches .INI section - Establish .INI section name */
			section_save = my_section(idev->idev_section);

			td->td_idev = idev; /* Save .INI device list entry */
			if (my_profget_str_unquote(1, "AutoFileVersion", td->td_autover, sizeof(td->td_autover)) == 0)
				td->td_fwstate = FWS_IGNORED;
			else {
				my_profget_str_unquote(0, "CompareControl", td->td_cmpcontrol, sizeof(td->td_cmpcontrol));
				memcpy(td->td_oldver, &td->td_inq_std[32], 4);
				cmpstat = autoupdate_compare(td);
				if (cmpstat < 0) {
					/* Drive version older than file version */
					if (my_profget_str_unquote(1, "AutoFileName", szLine, sizeof(szLine)) == 0)
						/* No file name */
						td->td_fwstate = FWS_NEEDSALTERNATE; /* "Needs update by another method" */
#if WIN32 || UNIXWARE || LINUX
					else if (my_profget_str_unquote(0, "UpdateAlgorithm", szLine, sizeof(szLine))
					 && is_dosmode_only_updatealg(szLine))
						td->td_fwstate = FWS_NEEDSDOS; /* "Needs DOS mode update" */
#endif
					else
						td->td_fwstate = FWS_OUTOFDATE; /* "Out of date" */
				} else if (cmpstat > 0)
					/* Drive version newer than file version */
					td->td_fwstate = FWS_NEWER; /* "Newer" */
				else
					/* Drive version same as file version */
					td->td_fwstate = FWS_CURRENT; /* "Up to date" */
			}
#if !__DOS__
			if (td->td_inaccessible) {
				td->td_fwstate = FWS_INACCESSIBLE;
				if (td->td_inaccessible == INACC_BUSY)
					td->td_fwstate = FWS_INUSE;
			}
#endif
		}
		if (scsienum_is_addable_device(enumflags, td)) {
			/* Allocate and copy tapedev structure */
			if ((mtd = malloc(sizeof(*mtd))) == NULL)
				error(EX_FATAL, cmsgex(MSGNO(0x8010)), me);
/*8010 "TAPEDRV8010 error: \"%s\": Out of memory"*/
			memcpy(mtd, td, sizeof(*mtd));
			mtd->td_devname = my_strdup(td->td_devname, me);
			mtd->td_fd = (HANDLE)-1;
			/* Append to list */
			if (Tapedev_list == NULL)
				Tapedev_list = mtd;
			else {
				struct tapedev *ltd_prev;
				struct tapedev *ltd;
				struct tapedev *ltd_final = NULL; /* first changer or e-o-list */

				/*
				 * When enumerating for autoupdate, enter tape 
				 * devices ahead of changers.
				 * If this is a tape (non-changer), 
				 * set ltd_final to first changer device, if any.
				 */
				if ((td->td_inq_std[0] & 0x1f) == PDT_CHANGER)
					scsienum_nchangers++;
				else if (enumflags & ENUMF_AUTOLIST)
					for (ltd=Tapedev_list; ltd; ltd=ltd->td_link)
						if ((ltd->td_inq_std[0] & 0x1f) == PDT_CHANGER) {
							ltd_final = ltd;
							break;
						}
				ltd_prev = NULL;
				for (ltd=Tapedev_list; ltd!=ltd_final; ltd=ltd->td_link)
					ltd_prev = ltd;
				if (!ltd_prev)
					Tapedev_list = mtd;
				else
					ltd_prev->td_link = mtd;
				mtd->td_link = ltd;
			}
			if (enumflags & ENUMF_STATUS) {
				status_print(td);
			}
			Tapedev_count++;
		}
	}

	/* Display and/or log the device */
	if (addrfmt_str == NULL) {
#if WIN32
		addrfmt_str= strdup(cmsgex( IS_NT() ?MSGNO(0x4451) :MSGNO(0x4450) ));
/*4450 "%d/%d.%d"*/
/*4451 "%d.%d/%d.%d*/
#elif __DOS__
		addrfmt_str= strdup(cmsgex( MSGNO(0x4450) ));
#endif
		addrwidth_str= strdup(cmsgex(MSGNO(0x4454)));
/*4454 "%-10s "*/
		inqfmt_str= strdup(cmsgex(MSGNO(0x4458)));
/*4458     "[%-28.28s] %s"*/
		controller_str= strdup(cmsgex(MSGNO(0x4460)));
/*4460         "Controller"*/
	}
#if WIN32
	if (IS_NT())
		my_snprintf(addrbuf, sizeof(addrbuf), addrfmt_str,
/*4451 "%d.%d/%d.%d*/
			td->td_hano,
			td->td_busno,
			td->td_targid,
			td->td_lun
		);
	else
#endif
		my_snprintf(addrbuf, sizeof(addrbuf), addrfmt_str,
/*4450 "%d/%d.%d"*/
			td->td_hano,
			td->td_targid,
			td->td_lun
		);

	prf(addrwidth_str, addrbuf);
/*4454 "%-8s "*/
	if (enumflags & ENUMF_HOST)
		pdt_str = controller_str;
	else
		pdt_str = str_pdt(td->td_inq_std[0]);
	if (!(enumflags & ENUMF_HOST) && td->td_inq_std[4] <= 3)
		msgf(MSGNO(0x4456));
/*4456     "Device name unavailable"*/
	else
		prf(
			inqfmt_str,
/*4458     "[%-28.28s] %s"*/
			&td->td_inq_std[8],
			pdt_str
		);
	prf("\n");

#if LOTSOFMEMORY
	if (enumflags & ENUMF_SCAN) {
		didprint += scan_tclass_info(td);
		if (didprint)
			my_newline();
	}
#endif
#if WIN32
	if (td->td_fwstate != FWS_UNKNOWN
	 && (enumflags & (ENUMF_SCAN | ENUMF_STATUS))) {
		char osbuf[OSERR_SIZE];

		switch (td->td_inaccessible) {
		case INACC_ACCESSIBLE:
			break;
		default:
		case INACC_CLAIMED:
			my_msgprintf(MSGNO(0x8250));
/*
 *8250 "   TAPEDRV8250: Above tape device inaccessible:\n"
 *     "      Device cannot be accessed via either a class driver\n" 
 *     "      or a SCSI port driver.\n"
 *     "         A class driver cannot be accessed as there is\n"
 *     "         no 'DeviceName' not in registry.  Possible causes:\n"
 *     "         Tape or changer device driver not installed, device service\n"
 *     "         disabled, or device driver failed to load.\n"
 */
			didprint++;
			break;
		case INACC_GETPARMFAIL:
		case INACC_CANTOPEN: /* Can't open device */
		case INACC_BUSY: /* Can't open device - in use */
			my_msgprintf(
				MSGNO(0x8252),
/*
 *  // %s=\\.\Tape<N>  %u=<NT error number>  %s=<NT error message>
 *8252 "   TAPEDRV8252: Above tape device inaccessible:\n"
 *     "      Can't open device \"%s\": Operating system error %u:\n"
 *     "      %s\n"
 */
				td->td_devname,
				td->td_inacc_oserr,
				stroserr(td->td_inacc_oserr, osbuf, sizeof(osbuf))
			);
			didprint++;
			break;
		}
	}
#endif /*WIN32*/
	/* restore previous section */
	my_section(section_save);
	return (mtd); /* tape device, if allocated */
}

#if WIN32
nt_scsi_reg_set_inquiry(struct tapedev *td) /* updates registry with latest inquiry */
{
	HKEY keyhandle;
	char keyname[128];
	unsigned long datatype;
	unsigned long inqsize;
	int oserr;
	int rc = 0;

	if (fSimulate)
		return (0);

	if (my_snprintf(
			keyname,
			sizeof(keyname),
"Scsi Port %d\\Scsi Bus %d\\Target Id %d\\Logical Unit Id %d",
			td->td_hano, /* portno */
			td->td_busno,
			td->td_targid,
			td->td_lun) >= sizeof(keyname) - 1)
		return (-1);

	if ((oserr= RegOpenKeyEx(
			scsienum_scsikey_handle, /* opened key */
			keyname,
			0, /*LPDWORD lpReserved*/
			KEY_ENUMERATE_SUB_KEYS|KEY_QUERY_VALUE|KEY_SET_VALUE, 
			&keyhandle)) != 0)
		return (-1);
	/* Set Identifier */
	if ((oserr= RegSetValueEx(
			keyhandle,
			"Identifier",
			0,      /* DWORD Reserved*/
			REG_SZ,
			&td->td_inq_std[8],
			28) /* characters excluding NUL-terminator */
			) != 0) {
		rc = -1;
		goto closekey;
	}

	/* Set Inquiry only if previously set */
	/*
	 * Normally on entry to QueryValueEx(), inqsize must be large 
	 * enough to retrieve all data otherwise ERROR_MORE_DATA 
	 * is returned.  The special inqsize value of zero says return 
	 * data's size, but no data.
	 */
	inqsize= 0;
	if ((oserr= RegQueryValueEx(
			keyhandle,
			"InquiryData",
			0, /*LPDWORD lpReserved*/
			&datatype,
			NULL,       /* don't actually get the data */
			&inqsize)) == 0) {
		if (inqsize > 36)
			inqsize = td->td_inq_len;
		if (inqsize < 36)
			inqsize = 36;
		if ((oserr= RegSetValueEx(
				keyhandle,
				"InquiryData",
				0,      /* DWORD Reserved*/
				REG_BINARY,
				td->td_inq_std,
				inqsize)) != 0) {
			rc = -1;
			goto closekey;
		}
	}
closekey:
	RegCloseKey(keyhandle);
	return (rc);
}
#endif /*WIN32*/

#if WIN32
scsienum_port( /* enumerate a given single device listed by port driver */
	int enumflags,  /* ENUMF_AUTOLIST, ENUMF_SELECTLIST, ENUMF_SCAN */
	struct tapedev *port_td) /* opened ScsiN: Scsi port device */
{
	static char me[] = "scsienum_port";

	/* assume accessible */
	port_td->td_inaccessible = INACC_ACCESSIBLE;
	port_td->td_inacc_oserr = 0;
	if (port_td->td_flags & TDF_CLAIMED) {
		struct tapedev tdbuf;
		struct tapedev *class_td= &tdbuf;
		IO_SCSI_CAPABILITIES capabilities;
		SCSI_ADDRESS scsiaddress;
		ULONG returned;
		HKEY keyhandle;
		char keyname[128];
		char          devicefile[64];
		unsigned long datasize;
		unsigned long datatype;
		int oserr;

		tapedev_init(class_td);
		class_td->td_flags = TDF_CLAIMED;
		class_td->td_busflag = port_td->td_busflag;
		memcpy(class_td->td_inq_std, port_td->td_inq_std, port_td->td_inq_len);
		class_td->td_inq_len = port_td->td_inq_len;

		/* Find class driver for device */
		my_snprintf(
				keyname,
				sizeof(keyname),
"Scsi Port %d\\Scsi Bus %d\\Target Id %d\\Logical Unit Id %d",
				port_td->td_hano, /* portno */
				port_td->td_busno,
				port_td->td_targid,
				port_td->td_lun
		);
		if ((oserr= RegOpenKeyEx(
				scsienum_scsikey_handle, /* opened key */
				keyname,
				0, /*LPDWORD lpReserved*/
				KEY_ENUMERATE_SUB_KEYS|KEY_QUERY_VALUE, 
				&keyhandle)) != 0) {
			goto get_devicename_error;
		}
		/* DeviceName: "Tape0" */
		strcpy(devicefile, "\\\\.\\");
		datasize= sizeof(devicefile) - 4;
		oserr= RegQueryValueEx(
				keyhandle,
				"DeviceName",
				0, /*LPDWORD lpReserved*/
				&datatype,
				(unsigned char *)&devicefile[4],
				&datasize);
		RegCloseKey(keyhandle);

		if (oserr != 0 || datatype != REG_SZ) {
get_devicename_error:
			/* Can't find 'DeviceName' in registry */
			port_td->td_inaccessible = INACC_CLAIMED;
			goto enum_port_device;
		}

		class_td->td_devname = devicefile;

		/* Try to open either class driver */
		if ((class_td->td_fd= CreateFile(
				class_td->td_devname,
				GENERIC_READ|GENERIC_WRITE,
				0, /* not shared */
				NULL,
				OPEN_EXISTING,
				0,
				0 )) == (HANDLE)-1) {
			port_td->td_inaccessible= INACC_CANTOPEN;
			port_td->td_inacc_oserr = GetLastError();
			if (port_td->td_inacc_oserr == 32) /*ERROR_SHARING_VIOLATION*/
				port_td->td_inaccessible= INACC_BUSY;
			error(LOGONLY|OSER, "%s: \"%s\": CreateFile error",
				me, class_td->td_devname
			);
			goto enum_port_device;
		}
		/* Get host adapter capabilities */
		if (!DeviceIoControl(
				class_td->td_fd,
				IOCTL_SCSI_GET_CAPABILITIES,
				NULL,
				0,
				&capabilities,
				sizeof(IO_SCSI_CAPABILITIES),
				&returned,
				FALSE)) {
			port_td->td_inaccessible = INACC_GETPARMFAIL;
			port_td->td_inacc_oserr= GetLastError();
			error(LOGONLY|OSER, "%s: \"%s\": IOCTL_SCSI_GET_CAPABILITIES error",
				me, class_td->td_devname
			);
			goto enum_port_device;
		}
		/* Get device SCSI address */
		if (!DeviceIoControl(
				class_td->td_fd,
				IOCTL_SCSI_GET_ADDRESS,
				NULL,
				0,
				&scsiaddress,
				sizeof(scsiaddress),
				&returned,
				FALSE)) {
			port_td->td_inaccessible = INACC_GETPARMFAIL;
			port_td->td_inacc_oserr= GetLastError();
			error(LOGONLY|OSER, "%s: \"%s\": IOCTL_SCSI_GET_ADDRESS error",
				me, class_td->td_devname
			);
			goto enum_port_device;
		}
		/* Use SCSI address returned by class driver */
		class_td->td_hano = scsiaddress.PortNumber;
		class_td->td_busno = scsiaddress.PathId;
		class_td->td_targid = scsiaddress.TargetId;
		class_td->td_lun = scsiaddress.Lun;
		scsienum_device(enumflags, class_td);
		CloseHandle(class_td->td_fd);
	} else {
		/* Access via port driver */
enum_port_device:
		scsienum_device(enumflags, port_td);
	}
}
#endif /*WIN32*/

/*
 * scsienumerate() -- enumerates tape devices with result in Tapedev_list
 *
 * Returns: none
 */
scsienumerate(
	int enumflags) /* ENUMF_SELECTLIST, ENUMF_SCAN, ENUMF_SIMULATE */
{
	static char me[] = "scsienumerate";

	scsienum_nchangers = 0;
	if ((scsienum_busflags & (BUSF_IDE | BUSF_SCSI)) == 0)
		scsienum_busflags |= BUSF_IDE | BUSF_SCSI;

	if (fSimulate || (enumflags & ENUMF_SIMULATE)) {
		struct tapedev tdbuf;
		struct tapedev *td = &tdbuf;
		int simdevno = 0;
		struct inidev *idev;
		char dnamebuf[32];

		for (idev=Inidev_list; idev; idev=idev->idev_link) {
			/* set section in for inqsimulate() error reporting */
			char *section_save = my_section(idev->idev_section);
			tapedev_init(td);

			inqsimulate(idev->idev_inquirystd, td->td_inq_std);
			td->td_inq_len = 5 + td->td_inq_std[4];
			my_snprintf(dnamebuf, sizeof(dnamebuf), "%s%d", str_pdt(td->td_inq_std[0]), scsienum_npdts[ td->td_inq_std[0]&0x1f ]++ );
			td->td_devname = dnamebuf;
			td->td_targid = simdevno++;
			td->td_busflag = scsienum_busflags;
			scsienum_device(enumflags | ENUMF_SIMULATE, td);
			my_section(section_save);
		}
		return (Tapedev_count);
	}
#if MSOS
	/* bind with aspi manager */
	if (scsihook() < 0)
		return (-1);

	if (enumflags & (ENUMF_SELECTLIST|ENUMF_AUTOLIST)) {
		my_msgprintf(
			(Arg_targid < 0) ?MSGNO(0x5210) :MSGNO(0x5220),
/*5210 "Searching for supported %stape devices...\n"*/
/*5220 "Searching for supported %stape device at Target ID %d...\n"*/
			str_busflags(scsienum_busflags),
			Arg_targid
		);
	}
	if (enumflags & ENUMF_SCAN) {
		my_msgprintf(MSGNO(0x4410), scsienum_nadapters);
/*4410 "%d Host Adapter(s) found\n"*/
		my_msgprintf(MSGNO(0x4420));
/*
 *4420 "c.b/t.l    [Make    Model           Vers] PeripheralType\n"
 *     "      c[.b] = Controller number [ .Bus number (for NT/2000/XP) ]\n"
 *     "      t = Target SCSI ID (for IDE/ATAPI: 0=Master, 1=Slave)\n"
 *     "      l = Logical Unit Number\n"
 */
	}
# if WIN32
	if (IS_NT()) {
		int scsikey_index;
		HKEY keyhandle;
		char keyname[64];
		unsigned long keysize;
		FILETIME keymodtime;
		unsigned long datatype;
		unsigned long datasize;
		char drivername[64];
		char portfile[64];
		HANDLE portfd = (HANDLE)-1;
		int busflag;
		int hano;
		int busno;
		IO_SCSI_CAPABILITIES capabilities;
		unsigned char haibuf[2048]; /* host atapter info */
		int hailength;
		SCSI_ADAPTER_BUS_INFO *adp = (SCSI_ADAPTER_BUS_INFO *)haibuf;
		ULONG returned;
		int oserr;

		/* Search SCSI keys for "Scsi port 0", "Scsi port 1", ... */
		for (scsikey_index=0; ; scsikey_index++) {
			keysize= sizeof(keyname);
			if ((oserr= RegEnumKeyEx(
					scsienum_scsikey_handle,
					scsikey_index,
					keyname,
					&keysize,
					0, /*LPDWORD lpReserved*/
					NULL,
					NULL,
					&keymodtime)) == ERROR_NO_MORE_ITEMS)
				break;
			if (oserr)
				continue;
			if (memstricmp(keyname, "Scsi Port"))
				continue; /* next key */
			/* get portno */
			hano= strtailtoi(keyname);

			drivername[0]= 0;
			if ((oserr= RegOpenKeyEx(
					scsienum_scsikey_handle, /* opened key */
					keyname,
					0, /*LPDWORD lpReserved*/
					KEY_ENUMERATE_SUB_KEYS|KEY_QUERY_VALUE,
					&keyhandle)) == 0) {
				datasize= sizeof(drivername);
				if ((oserr= RegQueryValueEx(
						keyhandle,
						"Driver",
						0, /*LPDWORD lpReserved*/
						&datatype,
						(unsigned char *)drivername,
						&datasize)) != 0 || datatype != REG_SZ)
					drivername[0]= 0;
				RegCloseKey(keyhandle);
			}
			if (!memstricmp(drivername, "atapi"))
				busflag = BUSF_IDE;
			else
				busflag = BUSF_SCSI;

			/* Open port driver */
			my_snprintf(portfile, sizeof(portfile), "\\\\.\\Scsi%d:", hano);
			if ((portfd= CreateFile(
					portfile,
					GENERIC_READ|GENERIC_WRITE,
					FILE_SHARE_READ|FILE_SHARE_WRITE,
					NULL,
					OPEN_EXISTING,
					0,
					0 )) == (HANDLE)-1) {
				error(LOGONLY|OSER, "%s: \"%s\": CreateFile error",
					me, portfile
				);
				continue;
			}
			/* Get host adapter capabilities */
			if (!DeviceIoControl(
					portfd,
					IOCTL_SCSI_GET_CAPABILITIES,
					NULL,
					0,
					&capabilities,
					sizeof(capabilities),
					&returned,
					FALSE)) {
				error(LOGONLY|OSER, "%s: \"%s\": IOCTL_SCSI_GET_CAPABILITIES error",
					me, portfile
				);
				goto port_get_parm_fail;
			}
			/* Get host adapter inquiry data */
			if (!DeviceIoControl(
					portfd,
					IOCTL_SCSI_GET_INQUIRY_DATA,
					NULL,
					0,
					haibuf,
					sizeof(haibuf),
					&returned,
					FALSE)) {
				error(LOGONLY|OSER, "%s: \"%s\": IOCTL_SCSI_GET_INQUIRY_DATA error",
					me, portfile
				);
				goto port_get_parm_fail;
			port_get_parm_fail:
				CloseHandle(portfd);
				portfd = (HANDLE)-1;
				continue;
			}

			hailength = returned;

			logf("%s: Host Controller Inquiry: %d bytes\n", portfile, hailength);
			logf("Bus TID LUN Claimed Inquiry Header          String\n");
			logf("--- --- --- ------- ----------------------- ----------------------------\n");
			for (busno=0; (unsigned char)busno<adp->NumberOfBuses; busno++) {
				SCSI_INQUIRY_DATA *inqp;
				int offset;

				for (offset=adp->BusData[ busno ].InquiryDataOffset; offset; offset=inqp->NextInquiryDataOffset) {
					inqp = (SCSI_INQUIRY_DATA *)(haibuf + offset);
					logf(" %d   %d  %3d    %s    %s %-28.28s\n",
						busno,
						inqp->TargetId,
						inqp->Lun,
						(inqp->DeviceClaimed) ? "Y" : "N",
						fmthex(0, inqp->InquiryData, 8),
						&inqp->InquiryData[8]
					);
				}
			}

			for (busno=0; (unsigned char)busno<adp->NumberOfBuses; busno++) {
				struct tapedev host_tdbuf;
				struct tapedev *host_td = &host_tdbuf;
				HKEY buskey_handle;
				char buskey_name[64];
				SCSI_INQUIRY_DATA *inqp;
				int offset;
				int inq_len;

				if (enumflags & ENUMF_SCAN)
					my_msgprintf(
						MSGNO(0x4442),
/*4442 "\n*** Controller %d: Bus %d: %s\n"*/
						hano, busno, drivername
					);

				/* Simulate host controller as a device */
				tapedev_init(host_td);
				host_td->td_busflag = busflag;
				host_td->td_hano = hano;
				host_td->td_busno = busno;
				host_td->td_targid = -1; /* -1: indicates "Initiator" "not yet enumerated" */
				host_td->td_lun = 0;
				host_td->td_devname = portfile;
				memset(host_td->td_inq_std, 0, sizeof(host_td->td_inq_std));
				host_td->td_inq_std[0] = 0x1f; /* unknown peripheral device type */
				host_td->td_inq_std[4] = 3+28; /* additionl length */
				strncpy_spacepad((char *)&host_td->td_inq_std[8], drivername, 28);
				host_td->td_inq_len = 5 + host_td->td_inq_std[4];

				/* Find host's SCSI target ID */
				my_snprintf(buskey_name, sizeof(buskey_name),
					"Scsi Port %d\\Scsi Bus %d",
					hano, busno
				);
				if ((oserr= RegOpenKeyEx(
						scsienum_scsikey_handle, /* opened key */
						buskey_name,
						0, /*LPDWORD lpReserved*/
						KEY_ENUMERATE_SUB_KEYS|KEY_QUERY_VALUE, 
						&buskey_handle)) == 0) {
					int buskey_index;

					for (buskey_index=0; ; buskey_index++) {
						char initiator_keyname[64];
						unsigned long initiator_keysize;
						FILETIME initiator_keymodtime;

						initiator_keysize= sizeof(initiator_keyname);
						if ((oserr= RegEnumKeyEx(
								buskey_handle,
								buskey_index,
								initiator_keyname,
								&initiator_keysize,
								0, /*LPDWORD lpReserved*/
								NULL,
								NULL,
								&initiator_keymodtime)) == ERROR_NO_MORE_ITEMS)
							break;
						if (oserr)
							continue;
						if (memstricmp(initiator_keyname, "Initiator Id"))
							continue; /* next key */
						host_td->td_targid = strtailtoi(initiator_keyname);
						break;
					}
					RegCloseKey(buskey_handle);
				}

				for (offset=adp->BusData[ busno ].InquiryDataOffset; offset; offset=inqp->NextInquiryDataOffset) {
					struct tapedev port_tdbuf;
					struct tapedev *port_td = &port_tdbuf;

					inqp = (SCSI_INQUIRY_DATA *)(haibuf + offset);
					if (Arg_lun0 && inqp->Lun)
						continue;
					inq_len = inqp->InquiryDataLength;
					tapedev_init(port_td); /* init tapedev structure */
					port_td->td_flags |= TDF_PORTACCESS;
					port_td->td_devname = portfile;
					port_td->td_fd = portfd;
					port_td->td_busflag = busflag;
					port_td->td_hano= hano;
					port_td->td_busno= busno;
					port_td->td_targid = inqp->TargetId;
					port_td->td_lun = inqp->Lun;
					port_td->td_inq_len = MIN(inq_len, sizeof(port_td->td_inq_std));
					memcpy(port_td->td_inq_std, inqp->InquiryData, port_td->td_inq_len);
					/* Allow IDE disks to show */
					if (5+(int)port_td->td_inq_std[4] < port_td->td_inq_len)
						port_td->td_inq_std[4] = (char)(port_td->td_inq_len - 5);
					if (inqp->DeviceClaimed)
						port_td->td_flags |= TDF_CLAIMED;
					/* Display host adapter first */
					if (host_td->td_targid >= 0
					 && host_td->td_targid <= port_td->td_targid) {
						scsienum_device(enumflags | ENUMF_HOST, host_td);
						host_td->td_targid = -1;
					}
					scsienum_port(enumflags, port_td);
				}
				/* Display host adapter if not displayed */
				if (host_td->td_targid >= 0)
					scsienum_device(enumflags | ENUMF_HOST, host_td);
			}
			CloseHandle(portfd);
			portfd = (HANDLE)-1;
		}
	} else
# endif /*WIN32*/
	{
		struct tapedev tdbuf;
		struct tapedev *td = &tdbuf;
		struct hostinfo hostbuf;
		struct hostinfo *hip= &hostbuf;
		char dnamebuf[32];
		int hano;
		int targid;
		int lun;
		int nluns;

		/* ASPI device search */
		for (hano=0; hano<scsienum_nadapters; hano++) {
			if (aspi_gethostinfo(hano, hip))
				continue;
			nluns = hip->hi_nluns;
			if (Arg_lun0)
				nluns = 1; /* Examine only lun 0 */
			if (!(hip->hi_busflag & scsienum_busflags))
				continue; /* Ignore bus */
			if (enumflags & ENUMF_SCAN)
				my_msgprintf(
					MSGNO(0x4440),
/*4440 "\n*** Controller %d: %s -- %s\n"*/
					hano, hip->hi_adaptername, hip->hi_drivername
				);
			for (targid=0; targid<hip->hi_nscsiids; targid++) {
				for (lun=0; lun<nluns; lun++) {
					int inq_len;

					tapedev_init(td); /* init tapedev structure */
					td->td_hano = hano;
					td->td_targid = targid;
					td->td_lun = lun;
					td->td_busflag = hip->hi_busflag;
					td->td_alignmask= hip->hi_alignmask;
					if (hip->hi_maxxferlen)
						td->td_maxxferlen= hip->hi_maxxferlen;
					if (hip->hi_flags & HIF_W9XIDE)
						td->td_flags |= TDF_W9XIDE;
					if (hip->hi_flags & HIF_RESIDUAL)
						td->td_flags |= TDF_RESIDUAL;
					if (td->td_targid == hip->hi_hostid) {
						/* Host Adapter's SCSI ID */
						td->td_devname = hip->hi_drivername;
						td->td_inq_std[0] = 0x1f; /* unknown peripheral device type */
						td->td_inq_std[4] = 3+28; /* additionl length */
						strncpy_spacepad((char *)&td->td_inq_std[8], hip->hi_adaptername, 28);
						td->td_inq_len = 5 + td->td_inq_std[4];
						scsienum_device(enumflags|ENUMF_HOST,td);
						break; /*goto next_targ;*/
					}
					/* SCSI device */
					if ((inq_len = tp_inquiry(td, 0, td->td_inq_std, NB_INQ_LEGACY)) < 0)
						break; /*goto next_targ;*/
					if (inq_len < 36 || (td->td_inq_std[0]&0x1f) == PDT_UNKNOWN /*0x1f*/)
						break; /*goto next_targ;*/
					td->td_inq_len = inq_len;
					my_snprintf(dnamebuf, sizeof(dnamebuf),
						"%s%d",
						str_pdt(td->td_inq_std[0]),
						scsienum_npdts[ td->td_inq_std[0]&0x1f ]++
					);
					td->td_devname = dnamebuf;
					scsienum_device(enumflags, td);
				}
			}
		}
	}
#endif /*MSOS*/
	return (Tapedev_count);
}

/*
 * unselectdevice -- unselects current tape device
 */
unselectdevice(void)
{
	int errnosv = errno;
#if MSOS
	int oserr = GetLastError();
#endif
	if (fSimulate)
		return (0);
#if unix
	if (Tapedev_cur && Tapedev_cur->td_fd != -1) {
		close(Tapedev_cur->td_fd);
		Tapedev_cur->td_fd = -1;
	}
#endif
#if WIN32
	if (IS_NT()) {
		if (Tapedev_cur && Tapedev_cur->td_fd != (HANDLE)-1) {
			CloseHandle(Tapedev_cur->td_fd);
			Tapedev_cur->td_fd = (HANDLE)-1;
		}
	}
#endif
	Tapedev_cur = NULL;
#if MSOS
	SetLastError(oserr);
#endif
	errno = errnosv;
	return (0);
}

/*
 * selectdevice -- reselects a device typically an entry in Tapedev_list
 */
selectdevice(struct tapedev *td)
{
	static char me[] = "selectdevice";
	unsigned char inq_std_buf[ NB_INQ_MAX ];
	int inq_len = 0;

	if (fSimulate)
		return (0);

	unselectdevice();
	if ((Tapedev_cur = td) == NULL)
		return (0);
#if WIN32
	if (IS_NT()) {
		IO_SCSI_CAPABILITIES capabilities;
		ULONG returned;

		if (td->td_fd != (HANDLE)-1)
			return (0); /* already open */
		/* Open either class or port driver */
		else if ((td->td_fd= CreateFile(
				td->td_devname,
				GENERIC_READ|GENERIC_WRITE,
				(td->td_flags & TDF_PORTACCESS) ?(FILE_SHARE_READ|FILE_SHARE_WRITE) :0,
				NULL,
				OPEN_EXISTING,
				0,
				0 )) == (HANDLE)-1) {
get_parm_error:
			error_msgset(
				MSGNO(0x8272),
/*
 *  // %s=\\.\Tape<N>  %u=<NT error number>  %s=<NT error message>
 *8272 "TAPEDRV8272 error: Tape drive inaccessible:\n"
 *     "Can't open device \"%s\": %s"
 */
				td->td_devname,
				error_format(OSER)
			);
			goto fail;
		/* Get host adapter capabilities */
		} else if (!DeviceIoControl(
				td->td_fd,
				IOCTL_SCSI_GET_CAPABILITIES,
				NULL,
				0,
				&capabilities,
				sizeof(IO_SCSI_CAPABILITIES),
				&returned,
				FALSE)) {
			logf("%s: IOCTL_SCSI_GET_CAPABILITIES error %u\n",
				me, GetLastError()
			);
			goto get_parm_error;
		}
	}
#endif
	if ((inq_len = tp_inquiry(td, 0, inq_std_buf, NB_INQ_MAX)) < 0)
		goto reinquire_error;
	if (memcmp(inq_std_buf, td->td_inq_std, MIN(td->td_inq_len, inq_len))) {
reinquire_error:
		error_msgset(MSGNO(0x8320));
/*8320 "TAPEDRV8320 error: Tape drive re-inquire failure"*/
		goto fail;
fail:
		unselectdevice();
		return (-1);
	}
	return (0);     /* drive selected */
}

waitready(struct tapedev *td)
{
	static char me[] = "waitready";
	time_t endtime;
	int uatns= 0;
	int unexpected= 0;
	int rc= 0;
	int seconds;
	int err_msg = 0;

	if (fSimulate)
		return (0);

	seconds = my_profget_int( "UpdateWaitReady", 600 );
	if (seconds <= 0)
		return (0);

	my_msgstatusf(MSGNO(0x3460));
/*3460 "Waiting for device to become stable..."*/

	/* Flush unreported sense */
	tp_test_unit_ready(td);
	endtime = time(NULL) + seconds;
	for (;;) {
		if (fCtlBrk) {
			rc= 1;
			goto done;
		}
		/* delay a total of 600 seconds */
		if (endtime - time(NULL) < 0) {
#if NO_LONGER
			err_msg = MSGNO(0x8360);
/*
 *8360 "TAPEDRV8360 error: Timeout waiting device to become stable:\n"
 *     "%s\n"
 *     "Firmware update not started."
 */
			goto fail;
#endif
			/* Let Write Buffer catch the error */
			error(LOGONLY|ERTYPE, "%s: %d second timeout: Assume stable device",
				me, seconds
			);
			goto done;
		}
		my_sleep(1);
		if (tp_test_unit_ready(td) == 0)
			goto done;
		if ((Err_type & ERTYPE) != SCER)
			goto bad_response;
		switch (Scsi_stat) {
		case SSTAT_CHECK_CONDITION:
			break;
		case SSTAT_BUSY:
			continue;
		default:
bad_response:
			err_msg = MSGNO(0x8330);
/*
 *8330 "TAPEDRV8330 error: Tape device is not responding:\n"
 *     "%s\n"
 *     "Firmware update not started."
 */
			goto fail;
		}
		switch (Rsense[2]&0xf) {
		case SKEY_UNIT_ATTENTION:       /* 0x6 Unit attention */
			/* clear unit attentions */
			if (uatns++ > 100) {
				err_msg = MSGNO(0x8340);
/*
 *8340 "TAPEDRV8340 error: Device communication error: Too many Unit Attentions:\n"
 *     "%s\n"
 *     "Firmware update not started."
 */
				goto fail;
			}
			break;

		case SKEY_NO_SENSE:             /* 0x0 No sense */
			goto done;

		case SKEY_NOT_READY:            /* 0x2 Not ready */
		case SKEY_HARDWARE_ERROR:       /* 0x4 Hardware error */
			switch (Rsense[12]) {
			default:
				goto done; /* e.g, 0x3A*: MEDIUM NOT PRESENT* */

			case 0x04:  /* NOT READY* */
	/* 0x0400: "LOGICAL UNIT NOT READY, CAUSE NOT REPORTABLE" */
	/* 0x0401: "LOGICAL UNIT IS IN PROCESS OF BECOMING READY" */
	/* 0x0402: "LOGICAL UNIT NOT READY, INITIALIZING COMMAND REQUIRED" */
	/* 0x0403: "LOGICAL UNIT NOT READY, MANUAL INTERVENTION REQUIRED" */
	/* 0x0404: "LOGICAL UNIT NOT READY, FORMAT IN PROGRESS" */
	/* 0x0405: "LOGICAL UNIT NOT READY, REBUILD IN PROGRESS" */
	/* 0x0406: "LOGICAL UNIT NOT READY, RECALCULATION IN PROGRESS" */
	/* 0x0407: "LOGICAL UNIT NOT READY, OPERATION IN PROGRESS" */
	/* 0x0409: "LOGICAL UNIT NOT READY, SELF-TEST IN PROGRESS" */
				switch (Rsense[13]) {
				case 0x02:
				case 0x03:
					goto done;
				default:
					break;
				}
				break;
			}
			break;

		default:
		case SKEY_RECOVERED_ERROR:      /* 0x1 Recovered error */
		case SKEY_MEDIUM_ERROR:         /* 0x3 Medium error */
		case SKEY_ILLEGAL_REQUEST:      /* 0x5 Request error */
		case SKEY_DATA_PROTECT:         /* 0x7 Data protect */
		case SKEY_BLANK_CHECK:          /* 0x8 Blank check */
		case SKEY_VENDOR:               /* 0x9 Vendor specific */
		case SKEY_COPY_ABORTED:         /* 0xa Copy aborted */
		case SKEY_ABORTED_COMMAND:      /* 0xb Command aborted */
		case SKEY_EQUAL:                /* 0xc Equal */
		case SKEY_VOLUME_OVERFLOW:      /* 0xd Volume overflow */
		case SKEY_MISCOMPARE:           /* 0xe Miscompare */
		case SKEY_RESERVED:             /* 0xf Reserved */
			/* unexpected sense keys */
			if (unexpected++ > 10) {
				err_msg = MSGNO(0x8350);
/*
 *8350 "TAPEDRV8350 error: Device communication error: Unexpected sense key:\n"
 *     "%s\n"
 *     "Firmware update not started."
 */
				goto fail;
			}
			break;
		}
	}
fail:
done:
	my_msgstatus_newline();
	if (err_msg) {
		error_msgset(err_msg, error_format(ERTYPE));
		rc = -1;
	}
	return (rc);
}

#define PREPF_TESTUNLOAD 0x1  /* return 1 if will unload, otherwise 0 */

update_prepare(
	struct tapedev *td,
	int prepflags,
	char **commands)
{
	static char me[] = "update_prepare";
	static unsigned char ies_cdb[6] = { SCMD_INIT_ELEM_STATUS };
	static unsigned char unload_cdb[6] = { SCMD_LOAD_UNLOAD };
	static unsigned char rewind_cdb[6] = { SCMD_REWIND };
	unsigned char *cdb;
	char **v;
	int pdt;
	int msg_cur = 0;
	int msg_new;

	pdt = td->td_inq_std[0] & 0x1f; /* Peripheral device type */
	if (commands == NULL || commands[0] == NULL) {
		/* Auto detection mode */
		switch (pdt) {
		case PDT_TAPE:
			commands = prepcmds_ies_unload;
			break;
		case PDT_CHANGER:
		default:
			commands = prepcmds_none;
			break;
		}
	}
	if (prepflags & PREPF_TESTUNLOAD) {
		for (v=commands; *v; v++)
			if (!stricmp(*v, s_ies) || !stricmp(*v, s_unload))
				return (1);
		return (0);
	}
	for (v=commands; *v; v++) {
		msg_new = msg_cur;
		if (!stricmp(*v, s_noop)) {
			logf("%s: %s\n", me, *v);
			continue;
		} else if (!stricmp(*v, s_ies)) {
			cdb = ies_cdb;
			msg_new = MSGNO(0x3480);
/*3480 "Unloading tape media..."*/
		} else if (!stricmp(*v, s_unload)) {
			if (pdt != PDT_TAPE) {
				logf("%s: \"%s\": Ignored for changer\n", me, *v);
				continue;
			}
			cdb = unload_cdb;
			msg_new = MSGNO(0x3480);
/*3480 "Unloading tape media..."*/
		} else if (!stricmp(*v, s_rewind)) {
			if (pdt != PDT_TAPE) {
				logf("%s: \"%s\": Ignored for changer\n", me, *v);
				continue;
			}
			cdb = rewind_cdb;
			msg_new = MSGNO(0x3470);
/*3470 "Rewinding tape..."*/
		} else {
			logf("%s: Command \"%s\": Unrecognized - Ignored\n", me, *v);
			continue;
		}
		logf("%s: %s\n", me, *v);
		if (msg_cur != msg_new) {
			if (msg_cur)
				my_msgstatus_newline();
			my_msgstatusf(msg_new);
			msg_cur = msg_new;
		}
		/* execute the command */
		if (tp_test_unit_ready(td) == 0)
			do_cdb_nodata(td, cdb, TMO_MOTION);
	}
	if (msg_cur)
		my_msgstatus_newline();
	return (0);
}

long Ihexi_lineno;
int Ihexi_peek= -1;
int Ihexi_record_cksum; /* hex record check sum */
int Ihexi_eof_record;   /* eof record read */
char *Ihexi_fname;
FILE *Ihexi_fp;
char *Ihexi_vbufaddr;   /* malloc()'ed for buffered i/o */
unsigned char Ihexi_data_buf[256];      /* binary data from ascii ihex line */
int Ihexi_data_size;
int Ihexi_data_residual;
unsigned Ihexi_load_segment;
unsigned Ihexi_load_offset;
unsigned long Ihexi_load_addr;

ihexi_ungetc(int c)
{
	Ihexi_peek= c;
	if (c == '\n')
		Ihexi_lineno--;
	return (c);
}

ihexi_getc(void)
{
	int c;

	if (Ihexi_peek >= 0) {
		c= Ihexi_peek;
		Ihexi_peek= -1;
	} else
		c= getc(Ihexi_fp);
	if (c == '\n')
		Ihexi_lineno++;
	return (c);
}

ihexi_getb(void)
{
	int c_hi;
	int c_lo;
	int hi;
	int val;

	if ((c_hi= ihexi_getc()) < 0)
		return (-1);
	if ((hi= ahex2bin(c_hi)) < 0) {
		ihexi_ungetc(c_hi);
		return (-1);
	}
	if ((c_lo= ihexi_getc()) < 0)
		return (-1);
	if ((val= ahex2bin(c_lo)) < 0) {
		ihexi_ungetc(c_lo);
		return (-1);
	}
	val |= hi<<4;
	Ihexi_record_cksum = (Ihexi_record_cksum + val) & 0xff;
	return (val);
}

ihexi_getrec(int *recid, unsigned *loadofs, void *dataaddr, int *datalen)
{
	static char msgbuf[60];
	int msgno;
	int c;
	int x;
	int hi;
	int lo;
	int len;
	char *p= (char *)dataaddr;

	Ihexi_record_cksum= 0;

again:
	/* Skip newlines */
	while ((c= ihexi_getc()) >= 0)
		if (c != '\n')
			break;
	if (c < 0) {
		msgno= MSGNO(0x8151);
/*8151 "End-of-file before EOF record"*/

		goto fail;
	}
	if (Ihexi_lineno == 0)
		Ihexi_lineno++;
	if (c != ':') {
		msgno= MSGNO(0x8152);
/*8152 "Missing colon (:)"*/
		goto again;
	}
	if ((len= ihexi_getb()) < 0) {
		msgno= MSGNO(0x8153);
/*8153 "Invalid or missing length field"*/
		goto fail;
	}
	if (datalen)
		*datalen= len;
	if ((hi= ihexi_getb()) < 0
	 || (lo= ihexi_getb()) < 0) {
		msgno= MSGNO(0x8154);
/*8154 "Invalid or missing address field"*/
		goto fail;
	}
	if (loadofs)
		*loadofs= (hi<<8) | lo;
	if ((x= ihexi_getb()) < 0) {
		msgno= MSGNO(0x8155);
/*8155 "Invalid or missing record type field"*/
		goto fail;
	}
	if (recid)
		*recid= x;
	while (len--) {
		if ((x= ihexi_getb()) < 0) {
			msgno= MSGNO(0x8156);
/*8156 "Invalid or missing data field"*/
			goto fail;
		}
		if (dataaddr)
			*p++= (char)x;
	}
	if (ihexi_getb() < 0) {
		msgno= MSGNO(0x8157);
/*8157 "Invalid or missing checksum field"*/
		goto fail;
	}
	while ((c= ihexi_getc()) >= 0) {
		switch (c) {
		case '\r':
			continue;
		case '\n':
			ihexi_ungetc(c);
			goto done;
		default:
			msgno= MSGNO(0x8158);
/*8158 "Characters beyond checksum field"*/
			goto fail;
		}
	}
done:
	if (Ihexi_record_cksum) {
		msgno= MSGNO(0x8159);
/*8159 "Checksum error"*/
fail:           strcpyn(msgbuf, cmsgex(msgno), sizeof(msgbuf));
		error_msgset(MSGNO(0x8150), Ihexi_fname, Ihexi_lineno, msgbuf);
/*8150 "TAPEDRV8150 error: %s(%ld): Hexfile record error: %s"*/
		return (-1);
	}
	return (0);
}

ihexi_close(void)
{
	if (Ihexi_fp) {
		fclose(Ihexi_fp);
		Ihexi_fp = NULL;
	}
	if (Ihexi_vbufaddr) {
		free(Ihexi_vbufaddr);
		Ihexi_vbufaddr = NULL;
	}
	return (0);
}

ihexi_open(char *fname)	/* opens hexfile */
{
	Ihexi_fname= fname;
	Ihexi_load_segment= 0;
	Ihexi_load_offset= 0;
	Ihexi_load_addr= 0;
	Ihexi_peek= -1;
	Ihexi_eof_record= 0;
	Ihexi_lineno= 0;
	ihexi_close();
	if ((Ihexi_fp= fopen(Ihexi_fname, "rb")) == NULL)
		return (-1);
	/* get big buffer for hex file buffered i/o */
#define IHEX_VBUFSIZE 0x1000
	if ((Ihexi_vbufaddr = malloc(IHEX_VBUFSIZE)) != NULL)
		setvbuf(Ihexi_fp, Ihexi_vbufaddr, _IOFBF, IHEX_VBUFSIZE);
	return (0);
}

ihexi_read_contig(void IOFAR *addr, unsigned count)
{
	static char msgbuf[60];
	int msgno;
	unsigned char IOFAR *p= (unsigned char IOFAR *)addr;
	int nbytes= 0;
	int recid;
	int datalen;

	for (;;) {
		/* transfer decoded data record to caller's buffer */
		for (;;) {
			/* copy binary data to caller's buffer */
			if (count == 0)
				goto done;
			if (Ihexi_data_residual <= 0)
				break;  /* get next hex data record */
			Ihexi_load_addr++;
			*p++ = Ihexi_data_buf[ Ihexi_data_size
					- Ihexi_data_residual ];
			Ihexi_data_residual--;
			count--;
			nbytes++;
		}
		if (Ihexi_eof_record)
			goto done;
		/* get next hexfile record */
		if (ihexi_getrec(&recid, &Ihexi_load_offset, Ihexi_data_buf, &datalen))
			return (-1);    /* szError already set */
		/* examine hexfile record */
		switch (recid) {
		case 0: /* IHEX_DATA */ {
			unsigned long offset;

			offset= (((long)(unsigned)Ihexi_load_segment) << 4)
					+ Ihexi_load_offset;
			if (Ihexi_load_addr != offset) {
				msgno= MSGNO(0x8161);
/*8161 "Data address discontinuity"*/
				goto fail;
			}
			Ihexi_data_size= Ihexi_data_residual= datalen;
			} break;
		case 1: /* IHEX_EOF */
			Ihexi_eof_record= 1;
			if (datalen) {
				msgno= MSGNO(0x8162);
/*8162 "EOF record with non-zero data length"*/
				goto fail;
			}
			goto done;
		case 2: /* IHEX_SEG */
			if (datalen != 2) {
				msgno= MSGNO(0x8163);
/*8163 "Segment record with data length other than \"02\""*/
				goto fail;
			}
			Ihexi_load_segment= (Ihexi_data_buf[0]<<8) + Ihexi_data_buf[1];
			break;
		case 3: /* IHEX_START */
			break;
		default:
			msgno= MSGNO(0x8164);
/*8164 "Unknown record type"*/
		fail:   strcpyn(msgbuf, cmsgex(msgno), sizeof(msgbuf));
			error_msgset(
				MSGNO(0x8160),
/*8160 "TAPEDRV8160 error: %s(%ld): Hexfile syntax error: %s"*/
				Ihexi_fname, Ihexi_lineno, msgbuf
			);
			return (-1);
		}
	}
done:
	return (nbytes);
}

/*
 * WRITE BUFFER mode, adrs, count
 * In the following comments, ADRS is a Seagate term used to mean 
 * bytes 2..5 of the Write Buffer CDB.  Adrs is the 4-byte 
 * combined view the Buffer ID byte (byte 2) as well as the Buffer 
 * Offset field (bytes 3..5).
 *
 * When Write buffer takes less than 15 seconds, 
 * assume we're at beginning of flash operation.
 * Delay for a long period so the flash opeation 
 * can complete. Drives of this type include
 *    Travan, Scorpion-2/4/8
 * Otherwise, When Write Buffer takes more than 15 seconds, 
 * then assume drive just completed flashing firmware 
 * Delay for a short period for firmware reset.  
 * Drives of this type include
 *    Scorpion-24/96/40/240, AIT
 *
 * Seagate Travan
 *     Entire image is transfered and comitted in a single command.
 *  5: download and commit code to flash EEPROM
 *     adrs=0 count=154K, 282K
 *     (Speculation: 154K applies only to Bali 1 and Falcon 1
 *     as every later drive appears to use 282K).
 * DAT DDS-1,2,3 256K or 512K EEPROM
 *     Entire image is transfered and comitted in a single command.
 *  5: download and commit operational code to flash EEPROM
 *  6: download and commit boot block to flash EEPROM
 *  7: download and commit operational code and boot block to flash EEPROM
 *     adrs=2000:0000 count=256K (any drive with 256K EEPROM)
 *                       or 512K (scorpion-2/4/8 w/ 512K EEPROM)
 *     adrs=0x00100000 count=512K (scorpion-24/96 (cobra3))
 * Seagate DAT DDS-1,2,3 512K EEPROM only
 *     Image may be transfered in segments using multiple mode 4
 *     single commands and then committed using a mode 5, 6, or 7 
 *     command.
 *  4: download image to buffer but don't write flash EEPROM yet
 *     begin adrs=2000:0000  count=N (scorpion-2/4/8 512K EEPROM))
 *     begin adrs=0x00100000 count=N (scorpion-24/96 (cobra3))
 *  5: commit operational code to flash EEPROM
 *  6: commit boot block to flash EEPROM
 *  7: commit operational code and boot block to flash EEPROM
 *     adrs=A000:0000  count=0 (scorpion-2/4/8 512K EEPROM)
 *     adrs=0x00180000 count=0 (scorpion-24/96 (cobra3))
 *     note: For modes 5,6,7 and count=0, current firmware does 
 *           not check ADRS field when count is 0.
 *           ADRS is set to begin ADRS value + download size
 *           for possible future compatibility.
 *     Scorpion 24: Takes about 35 seconds to download image and flash, 
 *     then firmware resets and issues a SCSI bus reset. Delay for a 
 *     3 seconds after reset.  
 * Seagate DAT DDS-4 640K image: 512K Drive EEPROM plus 128K Loader EEPROM
 *     Same firmware image used for both drive and loader. 
 *     Drives uses 512K.  When loader present, 128K is used by 
 *     loader mechanism.
 *  4: download image to buffer but don't write flash EEPROM yet
 *     begin adrs=0x00000000 count=N (scorpion-40/240)
 *  5: commit code to flash EEPROM
 *     adrs=0x000a0000 count=0 (scorpion-40/240)
 *     note: For mode 5 and count=0, current firmware does 
 *           not check ADRS field when count is 0.
 *           ADRS is set to begin ADRS value + download size
 *           for possible future compatibility.
 * Seagate (Sony) AIT: Sidewinder-50/70: Multiple of 512 byte image
 *  2: download image to buffer but don't write flash EEPROM yet
 *     begin adrs=0x00380000 count=N
 *  5: commit code to flash EEPROM
 *     adrs=0x00380000 count=0
 *     note: We do not yet know how to download AIT Loader 
 *           (sidewinder-200) via SCSI bus
 *     (future AIT versions might have a 68 byte header)
 * Ultrium: Viper-200: 992K image
 *  4,6: download image to buffer but don't write flash EEPROM yet
 *     begin adrs=0 count=N
 *  4,6: warm boot with previously downloaded image (don't commit to EEPROM)
 *     adrs=0+downloadsize count=0
 *  5,7: commit code to flash EEPROM
 *     adrs=0+downloadsize count=0
 *     note: For mode 5,7 and count=0, current firmware does 
 *           not check ADRS field offset when count is 0.
 *           ADRS is set to begin ADRS value + download size
 *           for possible future compatibility.
 *     After final transfer, Seagate Ultrium returns success. 
 *     That is, the device flashed and completed reset. 
 * Exebyte Mammoth, Mammoth-2
 *  7: download and commit using multiple write buffer commands.
 *     Mammoth automattically detects end-of-image and commits 
 *     on final transfer.
 *     begin adrs=0 count=N where N is a multiple of 256.
 * Quantum DLT4000, DLT7000, DLT8000, SuperDLT, Benchmark DLT1
 *  4: download image to buffer but don't write flash EEPROM yet
 *     begin adrs=0 count=N where N is a multiple of 4K.
 *  5: commit code to flash EEPROM
 *     adrs=0+downloadsize count=0
 * IBM Ultrium TD-1
 *  4,5,6,7: download and commit using multiple write buffer commands.
 *     begin adrs=0 count=N
 *     IBM Ultrium automattically detects end-of-image and commits 
 *     on final transfer. Then takes about 50 seconds to flash 
 *     returning success for final transfer.  Then enters a reset 
 *     cycle, during which a command is accepted, but locks up the 
 *     SCSI bus.  This unresponsive period is about ? seconds.
 * HP Ultrium
 *  4: download image to buffer but don't write flash EEPROM yet
 *     begin adrs=0x00000000 count=N
 *  5: commit code to flash EEPROM
 * HP 2x20 Library / 1x9 Autoloader
 *  4,5: download and commit using multiple write buffer commands.
 *     adrs=0x0100000 adrs remains fixed troughout (buffer id=1, offset=0)
 *     HP .frm firmware files have a 1088 byte header that begins with 
 *     data looking something like this:
 *     0xDE 0xAD 0xBE 0xEF "@(#)Data Hdr A.02.00"
 */
	/* download commands */
#define DNLCMD_CHECKIMAGE   0 /* Check image report but don't exit on error */
#define DNLCMD_SIZEIMAGE    1 /* Find image size and validate */
#define DNLCMD_WRITE        2 /* Download to drive using WRITE BUFFER */

download_recover( /* delay and test device communicative after final write buffer */
	struct tapedev *td,
	int *secondsp,    /* current time since beginning of final write buffer */
	int end_seconds)  /* delay to this number of seconds elapsed */
{
	static char me[]= "download_recover";
#if RECOVER_INQUIRY
	unsigned char inq_std_buf[ NB_INQ_LEGACY ];
#endif

	error(LOGONLY|ERTYPE, "%s: %d seconds elapsed: Final tp_write_firmware(): Actual device status not yet known",
		me, *secondsp
	);
	for (; *secondsp<end_seconds; *secondsp+=2)
		sleepdot(2);
#if RECOVER_INQUIRY
	logf("%s: %d seconds elapsed: Verify device communicative\n",
		me, *secondsp
	);
	if (tp_inquiry(td, 0, inq_std_buf, NB_INQ_LEGACY) < 0)
		return (-1);
#else
	UNREFERENCED(td);
	logf("%s: %d seconds elapsed: Ignoring error\n",
		me, *secondsp
	);
#endif
	return (0); /* success */
}

 long
download(       /* examines or transfers firmware image file to tape drive */
	struct tapedev *td,
	int dnlcommand,         /* DNLCMD_* value */
	struct updatealg *uap,  /* Update algorithm control */
	char *pathname,         /* filename */
	long dnlsize,           /* bytes to download */
	int max_seconds)        /* maximum commit time */
{
	static char me[]= "download";
	int errflags;           /* flags passed to error() */
	int fh = -1;            /* file handle for binary image */
	unsigned char IOFAR *dma_base = NULL;  /* Binary image transfer buffer */
	unsigned char IOFAR *dma_in;  /* Buffer adderss for reads */
	unsigned char IOFAR *dma_out; /* Buffer address for writes */
	long dma_size;          /* Size of binary image transfer buffer */
	int wb_mode;            /* write buffer mode */
	long wb_adrs;           /* write buffer combined id and offset fields */
	long wb_length;
	long dnl_readcount;
	long dnl_writecount;
	int ua_flags;           /* Local copy of uap->ua_flags */
	int dnlflags;           /* DNL_* download flags */
#define DNL_FIRSTIMAGE   0x80 /* First Image data read */

	errflags = CMDPFX | EX_FATAL; /* default: exit on error */
	if (dnlcommand == DNLCMD_CHECKIMAGE)
		errflags = 0; /* checking file only, report error but don't exit */

	/* use local copy of update flags */
	ua_flags = uap->ua_flags;
	if ((ua_flags & UAF_HEX) && stricmp(strfsuffix(pathname), ".hex"))
		/* Allowed to read hex, but no .hex filename suffix */
		ua_flags &= ~UAF_HEX;

	if (ua_flags & UAF_HEX) {
		/* intel hex file */
		if (ihexi_open(pathname))
			goto open_error;
	} else {
		/* binary image file */
		if ((fh= open(pathname, MSOS_O_BINARY|O_RDONLY)) < 0) {
open_error:
			error_msgset(MSGNO(0x8140),
/*8140 "TAPEDRV8140 error: \"%s\": Firmware file open error: <system_error_message>"*/
				pathname, my_strerror(errno)
			);
			goto file_error;
		}
	}
	log_memstats();

	/*
	 * For images which must be written to drive on a single, 
	 * single transfer, allocate buffer large enough for whole 
	 * image.  
	 */
	dma_size = 0x1000; /* 4K default */
	if (uap->ua_wrbuf_size > 0)
		/*
		 * Must write to drive using this size
		 * E.g, 32K for HP library
		 */
		dma_size = uap->ua_wrbuf_size;
	if ((dnlcommand == DNLCMD_WRITE) && (uap->ua_flags & UAF_ONEWRITE))
		/* Allocate buffer large enough for entire image */
		dma_size = dnlsize;
	if ((dma_base = halloc(dma_size, 1)) == NULL) {
#if WIN32
		unsigned k_requested = (unsigned)(dma_size >> 10);

		error_msgset(MSGNO(0x8200), k_requested);
/*8200 "TAPEDRV8200 error: Insufficient memory for %uK transfer buffer"*/
#elif __DOS__
		unsigned k_requested = (unsigned)(dma_size >> 10);
		unsigned k_avail = (unsigned)dos_avail() >> (10-4);

		error_msgset(MSGNO(0x8201), k_requested, k_requested - k_avail);
/*
 *8201 "TAPEDRV8201 error: Insufficient memory for %uK transfer buffer "
 *     "(additional %uK needed)"
 */
#endif
		goto file_error;
	}

	dnlflags |= DNL_FIRSTIMAGE;
	dnl_readcount = 0;
	dnl_writecount = 0;

	/* data transfer loop */
	for (;;) {
		long dma_readcount;     /* bytes read to dma buffer */
		long dma_readresidual;  /* remaining unread dma bytes */
		int pass_readsize;      /* read() request size */
		int pass_readcount;     /* read() return count */

		dma_in = dma_base;
		dma_out = dma_base;
		dma_readcount = 0;
		dma_readresidual = dma_size;
		wb_length = 0;
		/*
		 * Set default request size for each read()
		 * For DOS, limit to a large non-negative 16-bit number
		 */
		pass_readsize = 0x4000;

		/* Fill dma buffer */
		while (dma_readresidual) {
			if (dnlcommand == DNLCMD_WRITE) {
				if (dma_readresidual > (dnlsize - dnl_readcount))
					dma_readresidual = (dnlsize - dnl_readcount);
			}
			if ((long)pass_readsize > dma_readresidual)
				pass_readsize = (int)dma_readresidual;
			if (ua_flags & UAF_HEX) {
				if ((pass_readcount = ihexi_read_contig(dma_in, pass_readsize)) == -1)
					/* ihexi_read_contig() reported error */
					goto file_error;
			} else {
#if __DOS__
				pass_readcount = _read_binary_far(fh, dma_in, pass_readsize);
#else
				pass_readcount = read(fh, dma_in, pass_readsize);
#endif
				if (pass_readcount == -1) {
					error_msgset(MSGNO(0x8142),
/*8142 "TAPEDRV8142 error: \"%s\": Firmware file read error: %s"*/
						pathname, my_strerror(errno)
					);
					goto file_error;
				}
			}
			if (ua_flags & UAF_DATAHDR) {
				/*
				 * .frm file allowed.  If .frm suffix
				 * check for an 1088 byte HP Data Hdr
				 */
				ua_flags &= ~UAF_DATAHDR;
				if (!stricmp(strfsuffix(pathname), ".frm")) {
					/* File has a .frm suffix */
					if (_fmemcmp(
							dma_in, 
							/*0xDE,0xAD,0xBE,0xEF,"@(#)Data Hdr A.02.00"*/
							"\336\255\276\357@(#)Data Hdr A.02.",
							22))
						goto badmagic;
					/* Lseek to feed device full power of 2 blocks */
					lseek(fh, 1088L, SEEK_SET);
					continue; /* restart the read */
				}
			}
			/* If first read, check magic at beginning of image */
			if (dnlflags & DNL_FIRSTIMAGE) {
				dnlflags &= ~DNL_FIRSTIMAGE;
				switch (uap->ua_fw_signature) {
				case FWSIG_SEAGATE_DDS2_3:
					/*
					 * For DAT previous to DDS4, check 
					 * for "jmp far" op code at byte 0
					 */
					if (dma_in[0] != 0xea) {
						goto badmagic;
				badmagic:
						error_msgset(MSGNO(0x8170), pathname);
/*8170 "TAPEDRV8170 error: \"%s\": Firmware signature invalid"*/
						goto file_error;
					}
					break;
				case FWSIG_SEAGATE_AIT:
					if (dma_in[0] != 0x0e || dma_in[1] != 0x00)
						goto badmagic;
					break;
				case FWSIG_SEAGATE_ULTRIUM:
					/* 4C 00 00 EA */
					if (dma_in[3] != 0xea)
						goto badmagic;
					break;
				}
			}
			dma_in = my_canon(dma_in + pass_readcount);
			dma_readcount += pass_readcount;
			dma_readresidual -= pass_readcount;
			dnl_readcount += pass_readcount;
			if (pass_readcount == 0) {
				/*EOF*/
				if (dnlcommand == DNLCMD_WRITE) {
					if (dnl_readcount != dnlsize)
						goto invalid_image_size;
				} else {
					/* DNLCMD_CHECKIMAGE or DNLCMD_SIZEIMAGE */
					goto read_done;
				}
				break;
			}
		}

		/* Write dma buffer to device */
		if (dnlcommand == DNLCMD_WRITE) {
			wb_length = dma_readcount;
			if (ua_flags & UAF_ONEWRITE)
				goto read_done;
			if (!(ua_flags & UAF_ZCOMMIT)) {
				/* final transfer commits image (not separate command) */
				if (dnl_readcount >= dnlsize)
					goto read_done;
			}

			/* setup multiple transfer write buffer command */
			wb_mode = uap->ua_wrbuf_mode_multi;
#define SET_WB_ADRS() \
		{ \
			wb_adrs = uap->ua_wrbuf_adrs; \
			if (!(ua_flags & UAF_FIXEDADRS)) { \
				if (ua_flags & UAF_SEGOFS) \
					/* convert linear to seg:ofs */ \
					wb_adrs += ((dnl_writecount & ~0xfL) << 12) \
							| (dnl_writecount & 0xfL); \
				else \
					wb_adrs += dnl_writecount; \
			} \
		}

			SET_WB_ADRS();
			if (tp_write_firmware(td, wb_mode, wb_adrs, dma_out, wb_length, max_seconds + 10) < 0) {
#if WIN32
				if (IS_WIN9X()
				 && Err_type == HAER
				 && Ha_stat == HASTAT_DO_DU /* 0x12 DMA overrun/underrun */
				 && uap->ua_updtalg == UPDTALG_HP_LIBRARY
				 && stricmp(td->td_curver, "126I") <= 0)
					error(LOGONLY|ERTYPE,
"IGNORED: %s: Image offset 0x%lx: (%d%s transfer of %d) WriteBuffer(mode=%d, adrs=0x%08lx, length=%ld) error",
						me, dnl_writecount,
						dnl_writecount/dma_size + 1,
						strsth(dnl_writecount/dma_size + 1),
						(dnlsize + dma_size - 1)/dma_size,
						wb_mode, wb_adrs, wb_length
					);
				else
#endif
					goto write_firmware_error;
			}
			dnl_writecount += wb_length;
			wb_length = 0;
			if (dnl_readcount >= dnlsize)
				goto read_done;
		}
	}
read_done:
	if (dnlcommand != DNLCMD_WRITE) {
		/* DNLCMD_CHECKIMAGE or DNLCMD_SIZEIMAGE */
		if (uap->ua_image_fixedsize > 0 && dnl_readcount != uap->ua_image_fixedsize) {
			error_msgset(MSGNO(0x8190),
/*8190 "TAPEDRV8190 error: \"%s\": Firmware image size (%ld bytes)\n"*/
/*     "does not match drive requirement (%ld bytes)"*/
				pathname,
				dnl_readcount,
				uap->ua_image_fixedsize
			);
			goto image_error;
		}
		if (uap->ua_image_pagesize > 0 && (dnl_readcount % uap->ua_image_pagesize) != 0)
			goto invalid_image_size;
		if (uap->ua_image_minsize > 0 && dnl_readcount < uap->ua_image_minsize)
			goto invalid_image_size;
		if (uap->ua_image_maxsize > 0 && dnl_readcount > uap->ua_image_maxsize) {
			goto invalid_image_size;
		if (dnl_readcount < 16*1024) /* arbitrary */
			goto invalid_image_size;
		invalid_image_size:
			error_msgset(MSGNO(0x8180), pathname, dnl_readcount);
/*8180 "TAPEDRV8180 error: \"%s\": Firmware image size = %ld bytes: Invalid image size"*/
			goto image_error;
image_error:
			dnl_readcount = -1;
			goto done;
		}
#if MSOS
		if (dnlcommand != DNLCMD_CHECKIMAGE)
#if __DOS__
		if (dos_winver() >= 0x400)
#endif
		if (ua_flags & UAF_ONEWRITE) {
			if (fSimulate) {
				if (dnlcommand != DNLCMD_WRITE) {
#if WIN32
					if (IS_NT())
						logf("\n%s IGNORE: %s\n", My_cmdname, cmsgex(MSGNO(0x8220)));
/*
 *8220 "TAPEDRV8220 error: Cannot update this drive's firmware from\n"
 *     "Windows NT.  Use MS-DOS or Windows 95/98/ME in MS-DOS Only mode."
 */
					else
#endif
						logf("\n%s IGNORE: %s\n", My_cmdname, cmsgex(MSGNO(0x8210)));
/*
 *8210 "TAPEDRV8210 error: Cannot update this drive's firmware\n"
 *     "in Windows 95/98/ME Graphics mode (Within a DOS Box).\n"
 *     "reboot your system into MS-DOS only mode."
 */
				}
			} else {
#if WIN32
				if (IS_NT())
					error_msgset(MSGNO(0x8220));
/*
 *8220 "TAPEDRV8220 error: Cannot update this drive's firmware from\n"
 *     "Windows NT.  Use MS-DOS or Windows 95/98/ME in MS-DOS Only mode."
 */
				else
#endif
					error_msgset(MSGNO(0x8210));
/*
 *8210 "TAPEDRV8210 error: Cannot update this drive's firmware\n"
 *     "in Windows 95/98/ME Graphics mode (Within a DOS Box).\n"
 *     "reboot your system into MS-DOS only mode."
 */
				dnl_readcount = -1;
				goto done;
			}
		}
#endif /*MSOS*/
	} else {
		/* DNLCMD_WRITE */
		time_t timebegin; /* Begin time of final Write Buffer command */
		int end_seconds;
		int wb_seconds;  /* period consumed by final tp_write_firmware() */
		int seconds;
		int wb_stat;

		/*
		 * Setup for final Write Buffer -
		 *
		 * For single transfer drives (e.g., Travan, 
		 *   Seagate DDS w/256K EEPROM), 
		 *   setup for download and commit.
		 * For multiple transfer drives
		 *   Exabyte and IBM Ultrium use use the final 
		 *   image transfer to commit.
		 *   Otherwise a separate commit command 
		 *   is used after the final image transfer.
		 */

		time(&timebegin);

		/* setup final write buffer command */
		wb_mode = uap->ua_wrbuf_mode_final;

		switch (uap->ua_updtalg) {
		case UPDTALG_SEAGATE_DDS2_256K:
		case UPDTALG_SEAGATE_DDS2_512K:
		case UPDTALG_SEAGATE_DDS3:
		case UPDTALG_SEAGATE_DDS4:
			/*
			 * download and commit in a single write buffer, or
			 * commit after multi write buffer download
			 */
			if (Arg_download_mode > 0) {
				/*
				 * 1: Commit Seagate DAT operational code
				 * 2: Commit Seagate DAT boot block
				 */
				wb_mode &= ~3;
				wb_mode |= Arg_download_mode & 3;
			}
			break;
		}

		SET_WB_ADRS();
		wb_stat = tp_write_firmware(td, wb_mode, wb_adrs, dma_out, wb_length, max_seconds + 10);
		wb_seconds = seconds = (int)(time(NULL) - timebegin);

		/*
		 * Set end_seconds to be total delay between 
		 * beginning of final Write Buffer and next 
		 * device access.
		 */
		end_seconds = seconds + 3;
		if (end_seconds < max_seconds)
			end_seconds = max_seconds;
		if (wb_stat < 0) {
#if WIN32
			/*
			 * NT SCSI driver timesout and 
			 * returns a 1117 GetLastError()
			 * for Scorpion-24/96/40/240
			 */
			if (IS_NT()) {
				int oser_save = GetLastError();

				if (wb_seconds >= 10)
				if ((Err_type & ERTYPE) == OSER)
					switch (oser_save) {
					case 1117: /* I/O error */
						if (download_recover(td, &seconds, end_seconds) == 0)
							goto recovered; /* success */
						/* restore original error */
						Err_type = OSER;
						SetLastError(oser_save);
						break; /* fail */
					default:
						break; /* fail */
					}
			}
			else
#endif /*WIN32*/
#if MSOS
			{
				/*
				 * Workaround for ASPI drivers 
				 * that timeout long SCSI commands.
				 * (doesn't work with DOS IPSRASPI.SYS) 
				 */
				if (wb_seconds >= 10)
				if ((Err_type & ERTYPE) == HAER) {
					int ha_stat_save = Ha_stat;

					if (download_recover(td, &seconds, end_seconds) == 0)
						goto recovered; /* success */
					/* restore original error */
					Err_type = HAER;
					Ha_stat = ha_stat_save;
				}
				/* fail */
			}
#endif /*MSOS*/
write_firmware_error:
			error_msgset(MSGNO(0x8570),
/*
 *8570 "TAPEDRV8570 error: Image offset 0x%lx: WriteBuffer(mode=%d, adrs=0x%08lx, length=%ld) error:\n"
 *     "%s\n"
 *     "An error occurred while downloading firmware.\n"
 *     "Firmware update failed."
 */
				dnl_writecount,
				wb_mode,
				wb_adrs,
				wb_length,
				error_format(ERTYPE)
			);
#if __TAPEDRV__
			if (fAutoUpdate)
				error_level = EXA_DEVICEFAIL; /* Tape device update failure */
#endif
file_error:
			dnl_readcount= -1;
			goto done;
		}
recovered:
		dnl_writecount += wb_length;
		logf("\n%s: %d seconds elapsed: Final tp_write_firmware(): Success:\n"
			"  %ld (0x%lx) bytes downloaded: Begin final delay\n",
			me, seconds,
			dnl_writecount, dnl_writecount
		);
		for (; seconds<end_seconds; seconds+=2)
			sleepdot(2);
		logf("\n%s: %d seconds elapsed: Delay done\n", me, seconds);
	}
done:
	if (dma_base)
		hfree(dma_base);
	/* close hex or binary file if open */
	ihexi_close();
	if (fh >= 0)
		close(fh);
	return (dnl_readcount);
}

/*
 * Returns:
 *    0: Successful update
 *    1: Operator canceled
 *    -1: Error
 */
autoupdate_firmware(struct tapedev *td)
{
	static char me[] = "autoupdate_firmware";
	struct updatealg *uap;
	long imagesize;
	int stat;
	int download_loops;
	int max_seconds;
	int max_minutes;
	int filefound;
	char *p;
	char line[SIZE_PROFLINE];
	int ncmds;
	char *prepcmdvec[10];
	char **prepcmds;
	char fnamebuf[_MAX_PATH];
	int cmpstat;
	int inq_len;
	int is_verify_inquiry;

	/* re-establish the .INI device section name */
	my_section(td->td_idev->idev_section);
	logf("[%s]\n", td->td_idev->idev_section);

	my_profget_str_unquote(0, "UpdateAlgorithm", line, sizeof(line));
	if ((uap = get_updatealg_ptr(line)) == NULL) {
		error_msgset(MSGNO(0x8410), line);
/*8410 "TAPEDRV8410 error: UpdateAlgorithm = \"%s\": Unrecognized or missing"*/
		return (-1);
	}
	logf(" UpdateAlgorithm: %s\n", line);

	is_verify_inquiry = 1;
	if (my_profget_str_unquote(0, "UpdateFlags", line, sizeof(line))) {
		logf(" UpdateFlags: %s\n", line);
		for (p=line; *p; p++)
			switch (*p) {
			case ' ':
			case '\t':
				continue;
			case 'c':
				if (scsienum_nchangers)
					is_verify_inquiry = 0;
				break;
			default:
				logf("  '%c': Ignored\n", *p);
				break;
			}
	}

	/* Assemble firmware filename from /p <firmware_path> and AutoFileName */
	strcpy(Fw_fname, Fw_path);
	p = append_dirsep(Fw_fname);
	my_profget_str_unquote(1, "AutoFileName", p, &Fw_fname[sizeof(Fw_fname)] - p);
	filefound = findunique(Fw_fname, fnamebuf, NULL);
	logf(" AutoFileName: \"%s\"\n", Fw_fname);
	if (filefound != 1) {
		if (filefound > 1)
			match_msgset(MSGNO(0x8135));
/*8135 "Filename not unique"*/
		if (filefound == 0)
			match_msgset(MSGNO(0x8134));
/*8134 "File not found"*/
		error_msgset(MSGNO(0x8130), td->td_idev->idev_section, Fw_fname, match_errbuf);
/*8130 "TAPEDRV8130 error: [%s] \"%s\": File find error: %s"*/
		return (-1);
	}
	logf(" File found: \"%s\"\n", fnamebuf);
	strcpy(Fw_fname, fnamebuf);

	if (selectdevice(td))
		return (-1);

//my_printf("wait <Enter>...\n"); while(kbget_flush() != NEWLINE);

	my_msgstatusf(MSGNO(0x3410));
/*3410 "Examining firmware file content..."*/

	imagesize= download(td, DNLCMD_SIZEIMAGE, uap, Fw_fname, 0, 0);

	my_msgstatus_newline();
	if (imagesize < 0)
		return (-1); /* error set */

	/* clear unit attentions and wait for tape loading */
	if ((stat = waitready(td)) != 0)
		return (stat);

	/*
	 * Get a NULL terminated list of pre-update 
	 * preparation commands from the .INI.
	 * If this list of commands is present, 
	 * it overrides the default for the UpdateAlgorithm.
	 */
	memset(prepcmdvec, 0, sizeof(prepcmdvec));
	ncmds = my_profget_strlist_unquote(0, "UpdatePrepare",
		prepcmdvec, NELEM(prepcmdvec)-1, line, sizeof(line));
	prepcmds = prepcmdvec; /* assume: use commands from .ini */
	if (ncmds == 1 && *prepcmdvec[0] == 0)
		/*
		 * No commands loaded from .ini
		 * Use algorithm specific preperation commands
		 */
		prepcmds = uap->ua_prepcmds;

	/* send pre-download pereperation commands */
	update_prepare(td, 0, prepcmds);

	/* Read update timeout */
	max_seconds = my_profget_int( "UpdateSeconds", uap->ua_wrbuf_seconds );

	/* Command line argument overrides */
	if (Arg_download_seconds != -1)
		max_seconds = Arg_download_seconds;
	if (max_seconds <= 0)
		max_seconds= ((unsigned)-1) >> 1;

	/*
	 * Double download in order to properly update the boot block 
	 * on the following drive/firmware combination: 
	 *   Standard Scorpion-4/8 256K: STDx4000N/STDx8000N
	 *   already flashed with v6100-0 firmware
	 * Double download is disabled by 
	 * /force, /op, /bb, or /opbb.
	 */
	download_loops= 1;
	if (uap->ua_updtalg == UPDTALG_SEAGATE_DDS2_256K)
	if (Arg_force_flags == 0)
	if (Arg_download_mode <= 0)
	if (!memstricmp(&td->td_inq_std[23], "02779-XXX6100")) {
		logf(
"autosim NOTE: Double download to replace 02779 6100-0 firmware\n"
		);
		download_loops= 2;
	}

	max_minutes = (download_loops*max_seconds + 59)/60;
	if (max_minutes < 2)
		max_minutes = 2;

	if (fSimulate)
		max_seconds = 4;

	my_msgstatusf(MSGNO(0x6126));
/*6126 "Update in progress for the highlighted device..."*/
	my_snprintf(line, sizeof(line), cmsgex(0x6132), max_minutes);
/*6132 "This will take approximately %d minutes.   Do NOT power down!"*/
	colorline(2, 0, FG_LRED | BG_BLACK, line);
	if (fSimulate)
		my_sleep(1);

	/* Download firmware */
	while (download_loops--) {
		long lstat;
		void (*sv_sigint_handler)(int);

		sv_sigint_handler= signal(SIGINT, SIG_IGN);     /* IGNORE ctrl-break */
		lstat = download(td, DNLCMD_WRITE, uap, Fw_fname, imagesize, max_seconds);
		signal(SIGINT, sv_sigint_handler);
		if (lstat < 0)
			return (-1); /* error set */
		/* flush unit attentions */
		tp_test_unit_ready(td);
		tp_test_unit_ready(td);
		tp_test_unit_ready(td);
	}

	if (!is_verify_inquiry || fSimulate) {
		memcpy(&td->td_inq_std[32], td->td_newver, 4);
		if (!is_verify_inquiry)
			logf("tp_inquiry(): Skipped\n");
	} else {
		if ((inq_len = tp_inquiry(td, 0, td->td_inq_std, NB_INQ_MAX)) < 0) {
			error_msgset(
				MSGNO(0x8580),
				error_format(ERTYPE),
				td->td_oldver,
				td->td_newver
			);
/*
 *8580 "TAPEDRV8580 error: An inquiry error occurred after updateing firmware:\n"
 *     "%s\n"
 *     "Firmware might be successful.\n"
 *     "-- Please shutdown, power cycle all devices, then reboot\n"
 *     "and determine whether firmware was updated from \"%s\" to \"%s\""
 */
			error_level = EXA_DEVICEFAIL; /* Tape device update failure */
			return (-1);
		}
		td->td_inq_len = inq_len;
	}
	memcpy(td->td_curver, &td->td_inq_std[32], 4);
	cmpstat = autoupdate_compare(td);
	if (cmpstat < 0)
		/* Drive version older than file version */
		td->td_fwstate = FWS_OUTOFDATE; /* "Out of date" */
	else if (cmpstat > 0)
		/* Drive version newer than file version */
		td->td_fwstate = FWS_NEWER; /* "Newer" */
	else
		/* Drive version same as file version */
		td->td_fwstate = FWS_CURRENT; /* "Up to date" */
	if (td->td_fwstate != FWS_CURRENT) {
		error_msgset(MSGNO(0x8590), td->td_oldver, td->td_curver, td->td_newver);
/*
 *8590 "TAPEDRV8590 error: Old version \"%s\": Current version \"%s\": "
 *   "Expected new version \"%s\": "
 *   "The version did not change to the expected.\n"
 *   "-- Please shutdown, power cycle all devices, then reboot\n"
 *   "and determine whether firmware was updated."
 */
		error_level = EXA_DEVICEFAIL; /* Tape device update failure */
		return (-1);
	}
#if WIN32
	if (IS_NT())
		/* update registry with latest inquiry string */
		nt_scsi_reg_set_inquiry(td);
#endif
	/* Clear status and prompt lines */
	my_msgstatus_newline();
	colorline(2, 0, FG_WHITE | BG_BLACK, NULL);
	return (0);
}

struct win main_screen;

	/* main window work area just inside of border */
#define MW_SCREENROW    4       /* relative to absolute screen 0,0 */
#define MW_SCREENCOL    1
#define MW_LINES        19
#define MW_COLS         78
#define MW_WIDTH        76      /* text line width */

do_main_screen(void) /* displays main window screen */
{
	int col, deep, row, swindow, wide;

	co_cls(FG_WHITE|BG_BLACK /*7*/);

	win_init(&main_screen); /* set default values */

	main_screen.num_win = 0;
	main_screen.border = 1;

	main_screen.battr= FG_LWHITE | BG_BLACK;
	main_screen.wattr = FG_WHITE | BG_BLACK;
	main_screen.mattr = FG_WHITE | BG_BLACK;

	row  = 3;
	col  =  0;
	deep = 21;
	wide = 80;

	swindow = win_create(&main_screen, row, col, deep, wide);
	if (swindow == 0)   {
		error_msgset(MSGNO(0x8010), "do_main_screen");
/*8010 "TAPEDRV8010 error: \"%s\": Out of memory"*/
		return 0;
	}
	win_show(&main_screen);
	return 1;
}

close_window_mode(void)
{
	if (fWindowMode) {
		co_cls(FG_WHITE|BG_BLACK /*7*/);
		co_cursor_show();
		fWindowMode= 0;
	}
	return (0);
}

open_window_mode(void)
{
	fWindowMode= TRUE;

	co_init(); /* initialize console output */
	if (!do_main_screen()) {        /* paint the screen */
		fWindowMode= 0;
		return (-1);
	}
	colorline(0, 0, FG_WHITE | BG_BLACK, PROGNAME());
	colorline(1, 0, FG_WHITE | BG_BLACK, CPYRIGHT());
	return (0);
}

	/* galleys -- describes menuitem selection screen with galleys */
struct galleys {
	struct box gl_box;      /* Screen box containing galleys */
	int gl_cell_line;       /* Cells per line */
	int gl_cell_lmargin;    /* Offet of first cell text from gl_box.y */
	int gl_cell_width;      /* Width of text porton of cell */
	int gl_cell_space;      /* Spaces between cells */
};

	/* galleys -- keeps track of a menu item */
struct menuitem {
	struct menuitem *mi_fwd;  /* forward link */
	struct menuitem *mi_bak;  /* back link */
	char mi_flags;            /* MIF_* flags */
#define MIF_DRIVECOLON    0x8 /* mi_string has a drive - "X:" */
#define MIF_DRIVEDIR      0x4 /* special directory of drive letters */
#define MIF_DIRECTORY     0x2 /* mi_string has a "directorname" */
#define MIF_SELECTED      0x1 /* item selected */
	void *mi_pdatum;          /* general purpose */
	char mi_string[1];  /* menu item, filename, directoryname, or drive */
};

/* Doubly linked menu item list header */
struct menuitem menuitem_root = { &menuitem_root, &menuitem_root };

 struct menuitem *
menuitem_unlink( /* unlink from list */
	struct menuitem *mi)
{
	if (mi != NULL) {
		mi->mi_bak->mi_fwd= mi->mi_fwd;
		mi->mi_fwd->mi_bak= mi->mi_bak;
	}
	return (mi);
}

 struct menuitem *
menuitem_insert_after( /* insert after previous */
	struct menuitem *mi_prev,   /* insert after this */
	struct menuitem *mi_insert) /* descriptor to insert */
{
	if (mi_insert != NULL) {
		mi_insert->mi_bak= mi_prev;
		mi_insert->mi_fwd= mi_prev->mi_fwd;
		mi_insert->mi_fwd->mi_bak= mi_insert;
		mi_prev->mi_fwd= mi_insert;
	}
	return (mi_insert);
}

 struct menuitem *
menuitem_insert_before( /* insert before next */
	struct menuitem *mi_next,   /* insert before this */
	struct menuitem *mi_insert) /* descriptor to insert */
{
	if (mi_insert != NULL) {
		mi_insert->mi_fwd= mi_next;
		mi_insert->mi_bak= mi_next->mi_bak;
		mi_insert->mi_bak->mi_fwd= mi_insert;
		mi_next->mi_bak= mi_insert;
	}
	return (mi_insert);
}

menuitem_count( /* counts list entries */
	struct menuitem *mi_root)
{
	struct menuitem *mi;
	int count= 0;

	for (mi=mi_root->mi_fwd; mi!=mi_root; mi=mi->mi_fwd)
		count++;
	return (count);
}

/* get menuitem relative to current but stop just short of list header */
 struct menuitem *
menuitem_relative(struct menuitem *mi_root, struct menuitem *mi, int offset)
{
	if (offset >= 0)
		while (offset-- && mi->mi_fwd != mi_root)
			mi = mi->mi_fwd;
	else
		while (offset++ && mi->mi_bak != mi_root)
			mi = mi->mi_bak;
	return (mi);
}

#if TAPEDRV_UPDATE
menuitem_bubble( /* sorts menuitem list */
	struct menuitem *mi_root)
{
	struct menuitem *mi_a;
	struct menuitem *mi_b;
	int motion;

	do {
		motion= 0;
		for (mi_a=mi_root->mi_fwd; (mi_b=mi_a->mi_fwd)!=mi_root; mi_a=mi_a->mi_fwd)
			if (stricmp(mi_a->mi_string, mi_b->mi_string) > 0) {
				menuitem_unlink(mi_a);
				menuitem_insert_after(mi_b, mi_a);
				motion= 1;
			}
	} while (motion);
	return (0);
}
#endif /*TAPEDRV_UPDATE*/

menuitem_freeall(struct menuitem *mi_root)
{
	struct menuitem *mi;

	while ((mi=mi_root->mi_fwd) != mi_root)
		free(menuitem_unlink(mi));
	return (0);
}

 struct menuitem *
menuitem_alloc(int fname_size)
{
	return ( calloc(offsetof(struct menuitem, mi_string) + fname_size, 1) );
}

 void
menuitem_show_cell( /* sets/clears menuitem cell highlight */
	int miflags,    /* MIF_SELECTED=highlight */
	struct galleys *gl,      /* screen galley descriptor */
	int cell_row,   /* display relative cell row */
	int cell_col,   /* display relative cell column */
	struct menuitem *mi) /* menuitem structure */
{
	char buf[82];

	buf[0] = 0;
	if (mi) {
		int namelen;        /* full menuitem (filename) length */
		int bracketlen = 0; /* bracket length 0 or 2 */
		int fulllen;        /* full length of name and brackets */
		int textlen;        /* abbreviated length of name and breace */
		int copylen;        /* bytes of name to copy to buffer */
		char *p = buf;

		namelen = strlen(mi->mi_string);
		if (mi->mi_flags & MIF_DIRECTORY) {
			*p++ = '[';
			bracketlen = 2;
		}
		textlen = fulllen = namelen + bracketlen;
		if (textlen > gl->gl_cell_width)
			textlen = gl->gl_cell_width;
		if (textlen > sizeof(buf)-1)
			textlen = sizeof(buf)-1;
		copylen = textlen - bracketlen;
		if (copylen < 0)
			copylen = 0;
		strcpyn(p, mi->mi_string, copylen+1);
		p += copylen;
		if (mi->mi_flags & MIF_DIRECTORY) {
			*p++ = ']';
			*p = 0;
		}
		if (textlen < fulllen && textlen >= 5)
			memcpy(&buf[ (textlen - 3)/2 ], "...", 3);
	}
	show_cell(
		(miflags & MIF_SELECTED) ?1 :0,
		gl->gl_box.y + cell_row,
		gl->gl_box.x + gl->gl_cell_lmargin + cell_col*(gl->gl_cell_width + gl->gl_cell_space),
		buf,
		gl->gl_cell_width
	);
}

 void
menuitem_show_line( /* shows one line of menuitems */
	struct galleys *gl,      /* screen galley descriptor */
	struct menuitem *mi_root, /* menu item list */
	int index,      /* list starting position index */
	int cell_row)   /* cell index within row */
{
	struct menuitem *mi;      /* list entry */
	int i;
	int cell_col;

	for (mi=mi_root->mi_fwd, i=0; mi!=mi_root && i<index; mi=mi->mi_fwd, i++)
		;
	for (cell_col=0; cell_col<gl->gl_cell_line; cell_col++) {
		if (mi != mi_root) {
			menuitem_show_cell(mi->mi_flags, gl, cell_row, cell_col, mi);
			mi = mi->mi_fwd;
		} else
			menuitem_show_cell(0, gl, cell_row, cell_col, NULL);
	}
}

/* menuitem_show_page() -- displays page of menu items */
 void
menuitem_show_page(
	struct galleys *gl,      /* screen galley descriptor */
	struct menuitem *mi_root, /* menu item list */
	int index)      /* list starting position index */
{
	int cell_row;

	for (cell_row=0; cell_row<gl->gl_box.height; cell_row++) {
		menuitem_show_line(gl, mi_root, index, cell_row);
		index += gl->gl_cell_line;
	}
}

 void
menuitem_scroll_line(
	struct galleys *gl,      /* screen galley descriptor */
	int direction)
{
	box_scroll(&gl->gl_box, direction, FG_WHITE|BG_BLACK /*7*/);
}

/*
 * menuitem_select() -- Selects menu item from a list
 * Items may be selected with a mouse or the keyboard.
 * The left mouse button is used to select a item.  When a item is
 * selected, it is shown in a reverse color (highlighted).  If a
 * highlighted item is selected a second time, it reverts to a normal
 * display color, meaning that the item is deselected.  Arrow keys,
 * Home, End, Page Up, and Page Down also enable you to scroll through
 * the list of items.  The Escape key and the right mouse button abort
 * the selecetion process.
 *
 * Returns: number of menuitems selected
 */
#define MENUF_WRAP     0x8 /* Wrap when end-of-menu reached */
#define MENUF_AUTOCELL 0x4
#define MENUF_NOHILITE 0x2 /* Disables indexed item highlight when MENUF_STATIC */
#define MENUF_STATIC   0x1 /* Show screen menu screen and return immeditaly */

menuitem_select(
	int menuflags,
	struct galleys *gl,  /* screen galley descriptor */
	struct menuitem *mi_root, /* menu item list */
	int index)           /* initial index in menuitem list */
{
	struct menuitem *mi;
	int count;          /* Number of menuitems in list */
	int final;          /* Index of final menuitem in list */
	int end_c0_offset;  /* Index of end scroll margin item */
	int old_cell_row;
	int old_cell_col;
	int old_r0_offset;
	int old_index;      /* old item index */
	int rowthresh_top;  /* top vertical margin threshold */
	int rowthresh_bot;  /* bottom vertical margin bottom threshold */
	int pagecells;      /* cells to PgUp/PgDn */
	int scrupdate;      /* screen update method: SCRU_* */
#define SCRU_NONE      0
#define SCRU_SCROLLFWD 1
#define SCRU_SCROLLREV 2
#define SCRU_REPAINT   3

	if (gl->gl_box.width < 1 || gl->gl_box.height < 1)
		return (0);
	/* count items */
	if ((count = menuitem_count(mi_root)) <= 0)
		return (0);

	final = count - 1; /* final index */

	rowthresh_top = 0;
	if (gl->gl_box.height >= 3)
		rowthresh_top++;
	if (gl->gl_box.height >= 6)
		rowthresh_top++;
	rowthresh_bot = gl->gl_box.height - rowthresh_top;
	if (rowthresh_top)
		rowthresh_bot--;

	if (menuflags & MENUF_AUTOCELL) {
		gl->gl_cell_width = 1; /* width of column text */
		for (mi=mi_root->mi_fwd; mi!=mi_root; mi=mi->mi_fwd) {
			int n;

			n = strlen(mi->mi_string);
			if (mi->mi_flags & MIF_DIRECTORY)
				n += 2; /* 2 for bracket quoting: [directory] */
			if (n > gl->gl_box.width)
				n = gl->gl_box.width;
			if (n > gl->gl_cell_width)
				gl->gl_cell_width = n;
		}
		/* Figure the number of cells per line */
		gl->gl_cell_space = 1; /* space between cells */
		if (gl->gl_box.width > 9)
			gl->gl_cell_space = 3;
		gl->gl_cell_line = (gl->gl_box.width + gl->gl_cell_space)
				/ (gl->gl_cell_width + gl->gl_cell_space);
		if (gl->gl_cell_line <= 0) {
			gl->gl_cell_line = 1;
			gl->gl_cell_space = 0;
		}
		/* Find left margin */
		gl->gl_cell_lmargin = (gl->gl_box.width + 1
			 - (gl->gl_cell_line  * (gl->gl_cell_width + gl->gl_cell_space) - gl->gl_cell_space)
			) / 2;
	}

	/* cells to PgUp/PgDn */
	pagecells = gl->gl_cell_line * (gl->gl_box.height - rowthresh_top*2);

	/* figure scroll margin offset when end-of-list is approached */
	end_c0_offset = ((count + gl->gl_cell_line-1
			 - ((gl->gl_box.height - rowthresh_bot)*gl->gl_cell_line))
				/ gl->gl_cell_line) * gl->gl_cell_line;

	/* pretend that first page of screen was previously displayed */
	old_cell_col = 0;
	old_cell_row = 0;
	old_index = 0;
	old_r0_offset = 0;
	mi = menuitem_relative(mi_root, mi_root->mi_fwd, 0);
	scrupdate = SCRU_REPAINT;

	for (;;) {
		int c;
		int old_final_cell_row;
		int cell_row;
		int cell_col;
		int c0_offset;  /* item index for cell_row=<cursor>, cell_col=0 */
		int r0_offset;  /* item index for upper left corner of screen */
		int index_dif;

		if (index < 0)
			index = 0;
		if (index > final)
			index = final;
		index_dif = index - old_index;

		/* Get indexed menu item structure */
		mi = menuitem_relative(mi_root, mi, index - old_index);

		old_final_cell_row = (final + gl->gl_cell_line - old_r0_offset)/gl->gl_cell_line;

		c0_offset= (index/gl->gl_cell_line)*gl->gl_cell_line;
		cell_col = index % gl->gl_cell_line;
		cell_row = (old_cell_row*gl->gl_cell_line + old_cell_col + index_dif)/gl->gl_cell_line;

		if (cell_row > old_final_cell_row)
			cell_row = old_final_cell_row;
		if (cell_row < rowthresh_top) {
			cell_row = rowthresh_top;
			if (index < rowthresh_top*gl->gl_cell_line) {
				cell_row = index/gl->gl_cell_line;
			} else if (c0_offset >= end_c0_offset) {
				c0_offset = end_c0_offset;
				cell_row = (index - c0_offset)/gl->gl_cell_line;
			}
		} else if (cell_row >= rowthresh_bot) {
			cell_row = rowthresh_bot;
			if (index >= end_c0_offset)
				cell_row += (index - end_c0_offset)/gl->gl_cell_line;
		}

		/* Find item index of first cell in window */
		r0_offset= c0_offset - cell_row*gl->gl_cell_line;

		if (scrupdate == SCRU_NONE) {
			if (cell_row == old_cell_row) {
				int r0_dif = r0_offset - old_r0_offset;

				if (r0_dif == 0)
					;
				else if (-r0_dif == gl->gl_cell_line)
					scrupdate = SCRU_SCROLLREV;
				else if (r0_dif == gl->gl_cell_line)
					scrupdate = SCRU_SCROLLFWD;
				else
					scrupdate = SCRU_REPAINT;
			}
		}

		switch (scrupdate) {
		default:
		case SCRU_NONE:
			break;
		case SCRU_SCROLLREV:
			/* Reverse scroll */
			menuitem_scroll_line(gl, CO_LINEDOWN);
			menuitem_show_line(gl, mi_root, r0_offset, 0);
			break;
		case SCRU_SCROLLFWD:
			/* Forward scroll */
			menuitem_scroll_line(gl, CO_LINEUP);
			menuitem_show_line(gl, mi_root,
				r0_offset + (gl->gl_box.height - 1)*gl->gl_cell_line,
				(gl->gl_box.height - 1)
			);
			break;
		case SCRU_REPAINT:
			/* Paint entire page */
			menuitem_show_page(gl, mi_root, r0_offset);
			break;
		}

		if (!(menuflags & MENUF_NOHILITE))
			menuitem_show_cell(mi->mi_flags | MIF_SELECTED, gl, cell_row, cell_col, mi);
		if (menuflags & MENUF_STATIC)
			return (0);

		scrupdate = SCRU_NONE;
		old_index = index;
		old_cell_row = cell_row;
		old_cell_col = cell_col;
		old_r0_offset = r0_offset;

		co_cursor_hide();
		c = kbget_flush();

		switch (c) {
		case KB_HOME:
			index = 0;
			scrupdate = SCRU_REPAINT;
			break;
		case KB_END:
			index = final;
			scrupdate = SCRU_REPAINT;
			break;
		case KB_PGUP:
			index -= pagecells;
			scrupdate = SCRU_REPAINT;
			break;
		case KB_PGDN:
			index += pagecells;
			scrupdate = SCRU_REPAINT;
			break;
		case KB_UP:
			index -= gl->gl_cell_line;
			break;
		case KB_DN:
			index += gl->gl_cell_line;
			break;
		case KB_LEFT:
			index--;
			break;
		case KB_RIGHT:
			index++;
			break;
		case NEWLINE:
			mi->mi_flags |= MIF_SELECTED;
			menuitem_show_cell(mi->mi_flags, gl, cell_row, cell_col, mi);
			return (1); /* nselected */
		case ESCAPE: /* select none */
			return (0); /* nselected */
#if KOMMENT
		case KB_F10: /* select all */
			menuitem_show_cell(MIF_SELECTED, gl, cell_row, cell_col, mi);
			for (mi=mi_root->mi_fwd; mi!=mi_root; mi=mi->mi_fwd)
				mi->mi_flags |= MIF_SELECTED;
			return (count); /* nselected */

		case KB_F2: /* select none */
			return (0); /* nselected */
#endif
		default:
			break;
		}

		/* Lowlite the current cell */
		menuitem_show_cell(mi->mi_flags, gl, cell_row, cell_col, mi);
	}
}

#if TAPEDRV_UPDATE
/* Builds list of selectable disk drives */
menuitem_builddrivelist(
	struct menuitem *mi_root) /* list header on which to hang menuitems */
{
	static char driveroot[] = "X:\\";
	struct menuitem *mi;
	int nfound;

	nfound= 0;
	if ((mi = menuitem_alloc(sizeof("."))) != NULL) {
		mi->mi_flags = MIF_DIRECTORY | MIF_DRIVEDIR;
		strcpy(mi->mi_string, ".");
		menuitem_insert_after(mi_root->mi_bak, mi);
		nfound++;
	}
	for (driveroot[0]='A'; driveroot[0]<='Z'; driveroot[0]++) {
		if (is_accessible_drive(driveroot) > 0) {
			if ((mi = menuitem_alloc(sizeof(driveroot))) == NULL)
				break;
			mi->mi_flags = MIF_DIRECTORY;
			strcpy(mi->mi_string, driveroot);
			menuitem_insert_after(mi_root->mi_bak, mi);
			nfound++;
		}
	}
	return nfound;
}

/* Builds list of files matching argument pattern */
menuitem_buildfilelist(
	struct menuitem *mi_root, /* list header on which to hang menuitems */
	char *pattern)
{
	HANDLE dhandle;
	WIN32_FIND_DATA wfd;
	struct menuitem *mi;
	int found_dot_dir;
	int nfound;
#if SHOW_DIRECTORIES
	char *path; /* Just large enough for "x:\" */
#endif

	found_dot_dir = 0;
	nfound = 0;
	if ((dhandle= FindFirstFile("*.*", &wfd)) != (HANDLE)-1) {
		do {
//logf("FileAttributes=%x FileName=%s\n", wfd.dwFileAttributes, wfd.cFileName);
			if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
				/* directory */
#if SHOW_DIRECTORIES
				if (!strcmp(wfd.cFileName, "."))
					found_dot_dir |= 1;
				if (!strcmp(wfd.cFileName, ".."))
					found_dot_dir |= 2;
#else
				continue;
#endif
			} else
				/* file */
				if (pattern && fmatch(pattern, wfd.cFileName) <= 0)
					continue;
			if ((mi = menuitem_alloc(strlen(wfd.cFileName)+1)) == NULL)
				break;
			mi->mi_flags = 0;
			if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
				mi->mi_flags |= MIF_DIRECTORY;
			strcpy(mi->mi_string, wfd.cFileName);
			menuitem_insert_after(mi_root->mi_bak, mi);
			nfound++;
		} while (FindNextFile(dhandle, &wfd));
		FindClose(dhandle);
	}
#if SHOW_DIRECTORIES
	if (!(found_dot_dir & 1) && (mi = menuitem_alloc(sizeof("."))) != NULL) {
		mi->mi_flags = MIF_DIRECTORY;
		strcpy(mi->mi_string, ".");
		menuitem_insert_after(mi_root->mi_bak, mi);
		nfound++;
	}
	if (!(found_dot_dir & 2) && (path = getcwd(NULL, 0)) != NULL) {
		if (istraildirsep(path) && (mi = menuitem_alloc(sizeof(".."))) != NULL) {
			mi->mi_flags = MIF_DIRECTORY | MIF_DRIVEDIR;
			strcpy(mi->mi_string, "..");
			menuitem_insert_after(mi_root->mi_bak, mi);
			nfound++;
		}
		free(path);
	}
#endif
	return nfound;
}

	/* File selection positions (relative to absolute screen 0,0) */
#define FS_SCREENROW    4       /* Starting row */
#define FS_SCREENCOL    1       /* Starting column */
#define FS_LINES        19
#define FS_COLS         78

/*
 * choose_firmware_file()
 *
 * Returns:
 *  -1: fatal error;
 *   0: no file selected;
 *   1: file selected, name in Fw_fname
 */
choose_firmware_file(char *pattern)
{
	static struct galleys file_galleys = {
		FS_SCREENROW,   /* gl_box.y: Starting row */
		FS_SCREENCOL,   /* gl_box.x: Starting column */
		FS_LINES,       /* gl_box.height: Lines displayed */
		FS_COLS,        /* gl_box.width:  Max width of a menu item */
		5,              /* gl_cell_line;  Cells per line */
		3,              /* gl_cell_lmargin: Offet of first cell text from gl_box.y */
		12,             /* gl_cell_width: Width of text porton of cell */
		3,              /* gl_cell_space: Spaces between cells */
	};
	struct galleys *gl = &file_galleys;
	struct menuitem *mi_root = &menuitem_root;
	struct menuitem *mi;
	struct menuitem *mi_dirtail;
	int prev_miflags = 0;
	int count;
	int nselected;

	for (;;) {
		my_msgprintf(MSGNO(0x5360));
/*5360 "Looking for firmware files..."*/
		my_newline();

		if (pattern == NULL || !*pattern)
			pattern = "*";
		if (prev_miflags & MIF_DRIVEDIR)
			count = menuitem_builddrivelist(mi_root);
		else
			count = menuitem_buildfilelist(mi_root, pattern);
		if (count == 0) {
			my_msgprintf(MSGNO(0x5370));
/*5370 "\nNo firmware files found in the current directory.\n"*/
			my_msgprintf(MSGNO(0x5380));
/*5380 "-- Press any key to exit."*/
			kbget_flush();
			error_msgset(MSGNO(0x8120), "");
/*8120 "TAPEDRV8120 error: No %sfirmware files found"*/
			w_cls();
			menuitem_freeall(mi_root);
			return (-1);
		}
		w_cls();

		menuitem_bubble(mi_root); /* sort list */
		/* Move directory entries to head of list */
		mi_dirtail = mi_root;
		for (mi=mi_root->mi_fwd; mi!=mi_root; ) {
			struct menuitem *mi_tmp;

			mi_tmp = mi;
			mi = mi->mi_fwd;
			if (mi_tmp->mi_flags & MIF_DIRECTORY) {
				menuitem_unlink(mi_tmp);
				menuitem_insert_after(mi_dirtail, mi_tmp);
				mi_dirtail = mi_tmp;
			}
		}

		colorline(2, 0, FG_WHITE | BG_BLACK, cmsgex(MSGNO(0x5390)));
/*5390 "SELECT A FILE TO DOWNLOAD, or <Esc> to quit."*/
		nselected = menuitem_select(MENUF_AUTOCELL, gl, mi_root, 0/*index*/);

		/* Clear prompt */
		colorline(2, 0, FG_WHITE | BG_BLACK, NULL);

		/* Scan list for first selected file */
		for (mi=mi_root->mi_fwd; ; mi=mi->mi_fwd) {
			if (mi == mi_root) {
				mi = NULL;
				break;
			}
			if (mi->mi_flags & MIF_SELECTED)
				break;
		}
		box_cls(&gl->gl_box, FG_WHITE|BG_BLACK /*7*/);
		w_cls();
		if (nselected > 0 && mi == NULL)
			nselected = 0;
		if (nselected <= 0) {
			menuitem_freeall(mi_root);
			break; /* <Esc> */
		}
		if (!(mi->mi_flags & (MIF_DRIVEDIR | MIF_DIRECTORY))) {
			strcpy(Fw_fname, mi->mi_string);
			menuitem_freeall(mi_root);
			break;
		}
		prev_miflags = mi->mi_flags;
		chdir(mi->mi_string);
//logf("mi_flags=%x chdir(mi_string=%s)\n", mi->mi_flags, mi->mi_string);
		menuitem_freeall(mi_root);
	}
	return (nselected);
}
#endif /*TAPEDRV_UPDATE*/


	/* Menu selection positions (relative to absolute screen 0,0) */
#define SM_SCREENROW    4       /* Starting row */
#define SM_SCREENCOL    6       /* Starting column */
#define SM_LINES        19
#define SM_COLS         60      /* max width of a menu item */
#define SM_COLS_DEVLIST 76

/*
 * Returns:
 *  NULL: for <Esc>
 *  otherwise: address of selected menuitem
 */
 struct menuitem *
menuitem_menu(
	int menuflags,    /* MENUF_* flags */
	struct menuitem *mi_root, /* menu item list */
	int index,        /* initial index */
	int prompt_msgno,
	int head_msgno,
	int foot_msgno,
	int cols)       /* menu item width in columns; 0:use default columns */
{
	static struct galleys menu_galleys = {
		SM_SCREENROW,   /* gl_box.y: Starting row */
		SM_SCREENCOL,   /* gl_box.x: Starting column */
		SM_LINES,       /* gl_box.height: Lines displayed */
		SM_COLS,        /* gl_box.width:  Max width of a menu item */
		1,              /* gl_cell_line;  Cells per line */
		0,              /* gl_cell_lmargin: Offet of first cell text from gl_box.y */
		SM_COLS,        /* gl_cell_width: Width of text porton of cell */
		0,              /* gl_cell_space: Spaces between cells */
	};
	struct galleys *gl = &menu_galleys;
	struct menuitem *mi;
	int row;
	int col;
	char *p;
	char *q;
	int head_lines;
	int item_lines;
	int foot_lines;
	int nselected;
	int i;

	if ((item_lines = menuitem_count(mi_root)) == 0)
		return (NULL);     /* no menu lines to show */

	/* Find number of lines of instructional header text */
	head_lines= foot_lines= 0;
	if ((p= cmsg(head_msgno)) != NULL)
		head_lines= ww_totlines(p, MW_WIDTH);

	/* Find number of lines of instructional footer text */
	if ((p= cmsg(foot_msgno)) != NULL)
		foot_lines= ww_totlines(p, MW_WIDTH);

	/* Set menu selection area parameters */
	gl->gl_box.y= SM_SCREENROW + head_lines;
	gl->gl_box.height= item_lines;   /* Lines displayed */

	if ((gl->gl_box.width = cols) != 0) {
		gl->gl_box.x = 40 - cols/2; /* center */
	} else {
		/* use default */
		gl->gl_box.width = SM_COLS;    /* max width of a menu item */
		gl->gl_box.x = SM_SCREENCOL;
	}
	gl->gl_cell_width = gl->gl_box.width;

	/* Adjust head, menu display, and foot lines */
	if (head_lines + gl->gl_box.height + foot_lines > SM_LINES) {
		/* Minimize menu lines */
		if (gl->gl_box.height > 4) {
			gl->gl_box.height= SM_LINES - (head_lines + foot_lines);
			if (gl->gl_box.height < 4)
				gl->gl_box.height= 4;
		}
		for (i=0; head_lines+gl->gl_box.height+foot_lines > SM_LINES; i++)
			if (i & 1)
				head_lines--;
			else
				foot_lines--;
	}

	w_cls();

	/* Show (or clear) prompt line */
	p = cmsg(prompt_msgno);
	colorline(2, 0, FG_WHITE | BG_BLACK, p);

	/* Show instructional header text */
	if ((p= cmsg(head_msgno)) != NULL) {
		q= p;
		while (head_lines-- && (p= ww_line(&q, MW_WIDTH)) != NULL)
			my_printf("%s", p);
	}

	/*
	 * Show blank space for menu (to be overlayed shortly)
	 * and log all menu entries
	 */
	mi = mi_root->mi_fwd;
	i = 0;
	while (i<gl->gl_box.height || mi!=mi_root) {
		if (i<gl->gl_box.height) {
			logf("%s", mi->mi_string);
			my_newline();
		} else if (mi != mi_root) 
			logf("%s\n", mi->mi_string);
		if (mi!=mi_root) 
			mi = mi->mi_fwd;
		i++;
	}

	/* Show instructional footer text */
	if ((p= cmsg(foot_msgno)) != NULL) {
		q= p;
		while (foot_lines-- && (p= ww_line(&q, MW_WIDTH)) != NULL)
			my_printf("%s", p);
	}

	w_getcursor(&row, &col); /* save cursor */

	/* Select menu item */
	nselected = menuitem_select(menuflags, gl, mi_root, index);

	/* restore keyboard input functon */
	w_cursor(row, col); /* restore cursor */

	if (!(menuflags & MENUF_STATIC)) {
		w_cls();
		/* Clear prompt */
		colorline(2, 0, FG_WHITE | BG_BLACK, NULL);
	}

	/* Scan list for first selected item */
	for (mi=mi_root->mi_fwd; ; mi=mi->mi_fwd) {
		if (mi == mi_root) {
			mi = NULL;
			break;
		}
		if (mi->mi_flags & MIF_SELECTED)
			break;
	}

	/* Return selected item or NULL for none */
	return (mi);
}

#if TAPEDRV_UPDATE
/*
 * Returns:
 *  -1: for <Esc>
 *  otherwise: index in Menuitms of selected item
 */
msg_menu(
	int prompt_msgno,
	int head_msgno,
	int item_beg_msgno,
	int item_end_msgno,
	int item_initial_msgno, /* initially selected message */
	int foot_msgno,
	int *return_msgno)
{
	struct menuitem *mi_root = &menuitem_root;
	struct menuitem *mi;
	char *p;
	int msgno;
	int count;
	int index;

	index= 0; /* initially selected menu index */

	/* Load menu items */
	count = 0;
	for (msgno=item_beg_msgno; msgno<item_end_msgno; msgno++)
		if ((p=cmsg(msgno)) != NULL) {
			do {
				if ((mi = menuitem_alloc(strlen(p)+1)) == NULL)
					break;
				mi->mi_flags = 0;
				mi->mi_pdatum = (void *)msgno;
				strcpy(mi->mi_string, p);
				menuitem_insert_after(mi_root->mi_bak, mi);
				if (msgno == item_initial_msgno)
					index = count;
				count++;
			} while ((p=cmsg_next(item_end_msgno, &msgno)) != NULL);
			break;
		}

	msgno = 0;
	if (count) {
		struct menuitem *selected_mi;

		selected_mi = menuitem_menu(MENUF_WRAP, mi_root, index,
			prompt_msgno, head_msgno, foot_msgno, 0);
		/* Find index and message number of selected item */
		index = 0;
		for (mi=mi_root->mi_fwd; ; mi=mi->mi_fwd) {
			if (mi == mi_root) {
				index = -1;
				break;
			}
			if (mi == selected_mi) {
				msgno = (int)mi->mi_pdatum;
				break;
			}
			index++;
		}
		menuitem_freeall(mi_root);
	}
	if (return_msgno)
		*return_msgno = msgno;
	return (index);
}
#endif /*TAPEDRV_UPDATE*/

#if TAPEDRV_UPDATE
/*
 * choose_bus_type()
 *
 * Returns: -1: fatal error; 0:no item selected; 1:item selected
 */
choose_bus_type(void)
{
	int menu_index;
	int msgno;

	menu_index= msg_menu(
		MSGNO(0x5110), /*prompt_msgno*/
/*5110 "SELECT A BUS TO SEARCH FOR TAPE DEVICES, or <Esc> to quit."*/
		MSGNO(0x5111), /*head_msgno*/
/*5111 "\n"*/
/*     "Select a bus type.\n"*/
/*     "\n"*/
		MSGNO(0x5121), /*item_beg_msgno*/
		MSGNO(0x5130), /*item_end_msgno*/
		MSGNO(0x512f), /*item_initial_msgno*/
/*5121 "IDE/ATAPI"*/
/*5122 "SCSI or Fibre Channel"*/
/*512f "All the above"*/
		MSGNO(0x5130), /*foot_msgno*/
/*5130 "\n"*/
/*     "-- Highlight a bus type using arrow (\30\31) keys then press <Enter>,\n"*/
/*     "-- or press <Esc> to quit."*/
		&msgno
	);
	colorline(2, 0, FG_YELLOW | BG_BLACK, NULL);
	if (menu_index < 0)
		return (0);

	scsienum_busflags= 0;

	switch (msgno & 0xf) {
	case 1: /*5121 "IDE/ATAPI"*/
		scsienum_busflags |= BUSF_IDE;
		break;
	case 2: /*5122 "SCSI or Fibre Channel"*/
		scsienum_busflags |= BUSF_SCSI;
		break;
	default: /*any*/
		scsienum_busflags |= BUSF_IDE | BUSF_SCSI;
		break;
	}
	return (1);
}
#endif /*TAPEDRV_UPDATE*/

/*
 * Returns:
 *  address of selected tape device
 *  otherwise NULL for <Esc>
 */
#if TAPEDRV_UPDATE
 struct tapedev *
choose_device(void)
{
	struct menuitem *mi_root = &menuitem_root;
	struct menuitem *mi;
	struct tapedev *td;
	int count;
	int index;
	char text[80];

	index= 0; /* initially selected menu index */

	/* Load menu items */
	count = 0;
	for (td=Tapedev_list; td; td=td->td_link) {
		my_snprintf(text, sizeof(text), "[%-28.28s] %s", &td->td_inq_std[8], str_pdt(td->td_inq_std[0]));
		if ((mi = menuitem_alloc(strlen(text)+1)) == NULL)
			break;
		mi->mi_flags = 0;
		mi->mi_pdatum = (void *)td;
		strcpy(mi->mi_string, text);
		menuitem_insert_after(mi_root->mi_bak, mi);
		count++;
	}
	td = NULL;
	if (count) {
		struct menuitem *selected_mi;

		selected_mi = menuitem_menu(
			MENUF_WRAP,
			mi_root,
			index,
			MSGNO(0x5320), /*prompt_msgno*/
/*5320 "SELECT A TAPE DEVICE TO UPDATE, or <Esc> to quit."*/
			MSGNO(0x5321), /*head_msgno*/
/*5321 "\n"*/
/*     "Select a tape device.\n"*/
/*     "\n"*/
			MSGNO(0x5322), /*foot_msgno*/
/*5322 "\n"*/
/*     "-- Highlight a tape device using arrow (\30\31) keys then press <Enter>,\n"*/
/*     "-- or press <Esc> to quit."*/
			0 /* use default columns */
		);

		if (selected_mi)
			td = (struct tapedev *)selected_mi->mi_pdatum;
		menuitem_freeall(mi_root);
	}
	return (td); /* tapedev or NULL */
}
#endif /*TAPEDRV_UPDATE*/

 void
usage(void)
{
	char *p;

	fMoreMode= 1;
	my_printf("%s\n%s\n", PROGNAME(), CPYRIGHT());
	for (p=cmsg(MSGNO(0x1502)); p; p=cmsg_next(MSGNO(0x1600), NULL))
		my_printf("%s\n", p);
	my_exit(EX_FATAL);
}

help(int argc, char **argv)
{
	UNREFERENCED(argc);
	UNREFERENCED(argv);
	usage();
	return (0);
}

#if TAPEDRV_UPDATE
update(int argc, char **argv)
{
	unsigned char save_inq_std[ NB_INQ_MAX ];
	struct tapedev *td = NULL;
	struct updatealg *uap;
	FILE *fp;
	long imagesize;
	int stat;
	int download_loops;
	int is_updatesuccess= 0;
	int is_user_cancel= 0;
	char *drive_version;
	char line[SIZE_PROFLINE];
	int ncmds;
	char *prepcmdvec[10];
	char **prepcmds;
	int max_seconds;
	int max_minutes;
	int will_unload = 0;
	int inq_len;
	int c;

	UNREFERENCED(argc);
	UNREFERENCED(argv);
	fUpdate = 1;
	my_printf("%s\n%s\n\n", PROGNAME(), CPYRIGHT());

	/* Load device section inquiry strings */
	my_prof_preload();

	scsienum_busflags= Arg_busflags;
	if (Fw_fname[0]) {
		/* Firmware file given on command line, use batch mode */
	} else {
		/* No firmware file given, use interactive mode */
		if (open_window_mode())
			goto exit0;
		my_msgprintf(MSGNO(0x5100));
/*
 *5100 "\n"
 *     "This program will update the firmware in a supported tape drive.\n"
 *     "\n"
 *     "You can quit at any prompt, before the actual firmware download\n"
 *     "process begins, by pressing <Esc>.\n"
 *     "\n"
 *     "-- Press <Enter> to continue, or <Esc> to quit."
 */
		while ((c = kbget_flush()) != NEWLINE)
			if (c == ESCAPE) {
				my_newline();
				goto exit0; /* operator canceled */
			}
		my_newline();
		w_cls();
		/* Clear CPYRIGHT() */
		colorline(1, 0, FG_WHITE | BG_BLACK, NULL);
		/* If /b<bustype> not specified on command line ...*/
		if ((scsienum_busflags & (BUSF_IDE | BUSF_SCSI)) == 0)
		if ((stat= choose_bus_type()) <= 0) {
			if (stat == 0)
				goto user_cancel;
			goto exit0;
		}
	}
	/* Search for tape devices */
	if (scsienumerate(ENUMF_SELECTLIST) < 0)
		goto exit0;
	if (Tapedev_count == 0) {   /* no drives found */
		if (fWindowMode) {
			my_msgprintf((Arg_targid < 0) ?MSGNO(0x5300) :MSGNO(0x5301), str_busflags(scsienum_busflags), Arg_targid);
/*5300 "Supported %stape device NOT found."*/
/*5301 "Supported %stape device NOT found at target ID %d.\n"*/
			my_msgprintf(MSGNO(0x5310));
/*5310 "-- Press any key to exit."*/
			kbget_flush();
			my_newline();
		}
		error_msgset((Arg_targid < 0) ?MSGNO(0x8520) :MSGNO(0x8530), str_busflags(scsienum_busflags), Arg_targid);
/*
 *8520 "TAPEDRV8520 error: A supported %stape drive was NOT found.\n"
 *     "Make sure tape drive power is turned on,\n"
 *     "and cables are securely connected."
 *8530 "TAPEDRV8530 error: A supported %stape drive was NOT found at target SCSI ID %d."
 *     "Make sure tape drive power is turned on,\n"
 *     "and cables are securely connected."
 */
		goto exit0; /* drive not found */
	}
	/* assume use first found tape device */
	td = Tapedev_list;
	if (fWindowMode)
		td = choose_device();
	if (td == NULL)
		goto user_cancel; /* no drive selected (cancel) */

	/* re-establish the .INI device section name */
	my_section(td->td_idev->idev_section);
	logf("[%s]\n", td->td_idev->idev_section);

	my_profget_str_unquote(0, "UpdateAlgorithm", line, sizeof(line));
	if ((uap = get_updatealg_ptr(line)) == NULL) {
		error_msgset(MSGNO(0x8410), td->td_idev->idev_section, line);
/*8410 "TAPEDRV8410 error: [%s]: UpdateAlgorithm \"%s\": Unrecognized or missing"*/
		return (-1);
	}
	logf(" UpdateAlgorithm: %s\n", line);

	if (selectdevice(td))
		goto exit0;
#if WIN32
	if (IS_NT())
	{
		my_msgprintf(
			MSGNO(0x5341),
/*5341 "\n%d.%d/%d.%d [%-28.28s]: TAPE DEVICE SELECTED\n"*/
			td->td_hano,
			td->td_busno,
			td->td_targid,
			td->td_lun,
			&td->td_inq_std[8]
		);
	}
	else
#endif
	{
		my_msgprintf(
			MSGNO(0x5340),
/*5340 "\n%d/%d.%d [%-28.28s]: TAPE DEVICE SELECTED\n"*/
			td->td_hano,
			td->td_targid,
			td->td_lun,
			&td->td_inq_std[8]
		);
	}
	if (fWindowMode) {
		my_snprintf(line, sizeof(line), "[%-28.28s]", &td->td_inq_std[8]);
		colorline(1, 0, FG_YELLOW | BG_BLACK, line);
		my_msgprintf(MSGNO(0x5350));
/*5350  "-- Press <Enter> to continue, or <Esc> to quit."*/
		while ((c = kbget_flush()) != NEWLINE)
			if (c == ESCAPE) {
				my_newline();
				goto user_cancel; /* operator canceled */
			}
		my_newline(); /* drive selected */
		my_profget_str_unquote(1, "ManualFileName", line, sizeof(line));
		chdir(Fw_path);
		if ((stat= choose_firmware_file(line /*pattern*/)) <= 0) {
			if (stat == 0)
				goto user_cancel;
			goto exit0;
		}
		my_snprintf(line, sizeof(line), cmsgex(MSGNO(0x5400)), Fw_fname);
/*5400 "FILE: %s SELECTED"*/
		colorline(1, 40, FG_YELLOW | BG_BLACK, line);
	}

	if ((fp = fopen(Fw_fname,"rb")) == NULL) {
		error_msgset(MSGNO(0x8140), Fw_fname, my_strerror(errno));
/*8140 "TAPEDRV8140 error: \"%s\": Firmware file open error: <system_error_message>"*/
		goto exit0;
	}
	fclose(fp);

	/*
	 * Get a NULL terminated list of pre-update 
	 * preparation commands from the .INI.
	 * If this list of commands is present, 
	 * it overrides the default for the UpdateAlgorithm.
	 */
	memset(prepcmdvec, 0, sizeof(prepcmdvec));
	ncmds = my_profget_strlist_unquote(0, "UpdatePrepare",
		prepcmdvec, NELEM(prepcmdvec)-1, line, sizeof(line));
	prepcmds = prepcmdvec; /* assume: use commands from .ini */
	if (ncmds == 1 && *prepcmdvec[0] == 0)
		/*
		 * No commands loaded from .ini
		 * Use algorithm specific preperation commands
		 */
		prepcmds = uap->ua_prepcmds;

	/* Read update timeout */
	max_seconds = my_profget_int( "UpdateSeconds", uap->ua_wrbuf_seconds );

	/* Command line argument overrides */
	if (Arg_download_seconds != -1)
		max_seconds = Arg_download_seconds;
	if (max_seconds <= 0)
		max_seconds= ((unsigned)-1) >> 1;

	my_msgprintf(MSGNO(0x5410));
/*5410 "Examining firmware file content..."*/

	imagesize= download(td, DNLCMD_SIZEIMAGE, uap, Fw_fname, 0, 0);
	my_newline();
	if (imagesize < 0)
		goto exit0;

	drive_version= fmt_inq_version(td->td_inq_std);
	my_msgprintf(MSGNO(0x5420), drive_version, strfname(Fw_fname));
/*5420 "Firmware will be updated from version %s to %s.\n"*/

	if (fWindowMode) {
		my_msgprintf(MSGNO(0x5430));
/*
 *5430 "Ready to start firmware download.\n"
 *     "\n"
 */
		will_unload = update_prepare(td, PREPF_TESTUNLOAD, prepcmds);
#if WIN32
		if (will_unload)
#else /*__DOS__*/
		if (dos_winver() || will_unload)
#endif
		{
			my_msgprintf(MSGNO(0x5435));
/*5435 "CAUTION: Before continuing --\n",*/
#if __DOS__
			if (dos_winver())
#endif
				my_msgprintf(MSGNO(0x5440));
/*5440 "   Close any tape backup applications (such as Backup Exec).\n"*/
			if (will_unload)
				my_msgprintf(MSGNO(0x5450));
/*5450 "   Remove tape media from drive, if present.\n"*/
		}
		my_msgprintf(MSGNO(0x5455));
/*
 *5455 "\n"
 *     "-- Press <Enter> to download firmware, or <Esc> to quit."
 */
		while ((c = kbget_flush()) != NEWLINE)
			if (c == ESCAPE) {
				my_newline();
				goto user_cancel; /* operator canceled */
	user_cancel:            is_user_cancel= 1;
				goto exit0;
			}
		my_newline();
	}
	/* clear unit attentions and wait for tape loading */
	if (waitready(td))
		goto exit0;

	/* send pre-download pereperation commands */
	update_prepare(td, 0, prepcmds);

	/*
	 * Double download in order to properly update the boot block 
	 * on the following drive/firmware combination: 
	 *   Standard Scorpion-4/8 256K: STDx4000N/STDx8000N
	 *   already flashed with v6100-0 firmware
	 * Double download is disabled by 
	 * /force, /op, /bb, or /opbb.
	 */
	download_loops= 1;
	if (uap->ua_updtalg == UPDTALG_SEAGATE_DDS2_256K)
	if (Arg_force_flags == 0)
	if (Arg_download_mode <= 0)
	if (!memstricmp(&td->td_inq_std[23], "02779-XXX6100")) {
		logf(
"%s NOTE: Double download to replace 02779 6100-0 firmware\n", My_cmdname
		);
		download_loops= 2;
	}

	max_minutes = (download_loops*max_seconds + 59)/60;
	if (max_minutes < 2)
		max_minutes = 2;

	if (fSimulate)
		max_seconds = 4;
	if (fWindowMode)
		colorline(2, 0, FG_LRED | BG_BLACK, cmsgex(MSGNO(0x5480)));
/*5480 "FIRMWARE UPDATE IN PROGRESS.  DO NOT REMOVE POWER"*/
	my_msgprintf(MSGNO(0x5490), max_minutes);
/*
 *5490 "Firmware Update in progress.\n"
 *     "   DO NOT remove power!\n"
 *     "   DO NOT exit the program!\n"
 *     "This may take up to %d minutes.  PLEASE WAIT..."
 */

	memcpy(save_inq_std, td->td_inq_std, NB_INQ_MAX);

	fShowdots = 1;
	/* Download firmware */
	while (download_loops--) {
		long lstat;
		void (*sv_sigint_handler)(int);

		sv_sigint_handler= signal(SIGINT, SIG_IGN);     /* IGNORE ctrl-break */
		lstat = download(td, DNLCMD_WRITE, uap, Fw_fname, imagesize, max_seconds);
		signal(SIGINT, sv_sigint_handler);
		if (lstat < 0)
			goto exit0; /* error set */
		/* flush unit attentions */
		tp_test_unit_ready(td);
		tp_test_unit_ready(td);
		tp_test_unit_ready(td);
	}
	fShowdots = 0;

	if (fUpdateSim) {
		if (fWindowMode) {
			colorline(2, 0, FG_YELLOW | BG_BLACK,
					cmsgex(MSGNO(0x5500))
/*5500 "FIRMWARE UPDATE COMPLETE.  Press any key to exit."*/
			);
			my_printf(
	"\n-- Simulation complete.  Press any key to exit."
			);
			kbget_flush();
			my_newline();
		}
	} else {
		my_newline();
		if ((inq_len = tp_inquiry(td, 0, td->td_inq_std, NB_INQ_MAX)) < 0) {
			error_msgset(MSGNO(0x8580));
/*
 *8580 "TAPEDRV8580 error: An error was encountered while Flashing firmware.\n"
 *     "The drive was not updated."
 */
			goto exit0;
		}
		td->td_inq_len = inq_len;
#if WIN32
		if (IS_NT())
			/* update inquiry in registry */
			nt_scsi_reg_set_inquiry(td);
#endif
		if (fWindowMode) {
			w_cls();
			my_newline();
		}
		my_msgprintf(MSGNO(0x5520), &td->td_inq_std[8]);
/*5520 "Updated drive Inquiry: [%-28.28s]\n"*/
		if (error_level == 0) {
			is_updatesuccess= 1;
			if (fWindowMode) {
				colorline(2, 0, FG_YELLOW | BG_BLACK,
					cmsgex(MSGNO(0x5500))
/*5500 "FIRMWARE UPDATE COMPLETE.  Press any key to exit."*/
				);
				my_msgprintf(MSGNO(0x5510));
/*5510 "\n\nFirmware update completed successfully.  Press any key to exit."*/
				kbget_flush();
				my_newline();
			}
		}
	}
exit0:
	if (fWindowMode) {
		close_window_mode();
		my_printf("%s\n%s\n\n", PROGNAME(), CPYRIGHT());
		if (is_user_cancel) {
			error_msgset(MSGNO(0x5540));
/*5540 "Firmware update not attempted\n"*/
			return (EX_CANCEL);
		}
		if (is_updatesuccess)
			my_msgprintf(MSGNO(0x5520), &td->td_inq_std[8]);
/*5520 "Updated drive Inquiry: [%-28.28s]\n"*/
	}
	if (is_updatesuccess)
		my_msgprintf(MSGNO(0x5530), &td->td_inq_std[8]);
/*5530 "Firmware update completed successfully.\n"*/
	return (error_level);
}
#endif /*TAPEDRV_UPDATE*/

#if TAPEDRV_UPDATE
updatesim(int argc, char **argv)
{
	fUpdateSim = 1;
	fSimulate = 1;
	return (update(argc, argv));
}
#endif /*TAPEDRV_UPDATE*/

/*
 * Returns:
 *   0 Normal
 *   1 Operator pressed Enter (to cancel)
 */
autoupdate_show_devices(
	int status_msgno,  /* Message for status line */
	int prompt_msgno,  /* Message for prompt line */
	struct tapedev *td,/* First entry in linked list */
	int maxlines,      /* Number to display */
	int index,         /* Index of item to highlight */
	int seconds)       /* Seconds to delay: Negative value 
			      to return immediatly and retain messages */
{
	static char me[] = "autoupdate_show_devices";
	char text[80];
	time_t endtime;
	time_t prevtime;
	time_t curtime;
	struct menuitem *mi_root = &menuitem_root;
	struct menuitem *mi;
	char *fmt;
	char *p, *q;
	int count;
	int rc = 0;
	int head_msgno;
	static char *addrfmt_str;
	static char *oldnewfmt_str;
	static char *updatefmt_str;
	static char *noupdatefmt_str;

	if (!addrfmt_str) {
#if WIN32
		addrfmt_str = my_strdup(cmsgex( IS_NT() ?MSGNO(0x6251) :MSGNO(0x6250) ), me);
/*6250 "%d/%d.%d"*/
/*6251 "%d.%d/%d.%d"*/
#elif __DOS__
		addrfmt_str = my_strdup(cmsgex( MSGNO(0x4450) ), me);
#endif
		oldnewfmt_str   = my_strdup(cmsgex( MSGNO(0x6260) ), me);
		updatefmt_str   = my_strdup(cmsgex( MSGNO(0x6270) ), me);
		noupdatefmt_str = my_strdup(cmsgex( MSGNO(0x6271) ), me);
/*6260 "%-4.4s,%-4.4s %s"*/
/*6270 "%-8s %-24.24s  %-4.4s %-12.12s %s"*/
/*6271 "%-8s %-24.24s  %-4.4s %s"*/
	}
#if WIN32
	if (IS_NT())
	{
		head_msgno = MSGNO(0x6211);
/*
 *6211 "Device Address: Port_Number.Bus_Number/SCSI_Device_ID.Logical_Unit_Number\n"
 *     "\n"
 *     "                                      Current            Update\n"
 *     "Device                             Ver-               Version\n"
 *     "Address  Device Inquiry            sion  State        Old,New  Status\n"
 *     "-------  ------------------------  ----------------  -----------------\n"
 */
	}
	else
#endif
	{
		head_msgno = MSGNO(0x6210);
/*
 *6210 "Device Address: Controller_Number/SCSI_Device_ID.Logical_Unit_Number\n"
 *     "\n"
 *     "                                      Current            Update\n"
 *     "Device                             Ver-               Version\n"
 *     "Address  Device Inquiry            sion  State        Old,New  Status\n"
 *     "-------  ------------------------  ----------------  -----------------\n"
 */
	}

	/* Create dummy menu from Tapedevs[] */
	for (count=0; count<maxlines; count++, td=td->td_link) {
		char addrbuf[16];
		char oldnew[18+1]; /* "0.00,1.00 Updating" */
		char text[80];

		if (td == NULL)
			break;
#if WIN32
		if (IS_NT()) {
			my_snprintf(addrbuf, sizeof(addrbuf), addrfmt_str,
/*6251 "%d.%d/%d.%d"*/
				td->td_hano,
				td->td_busno,
				td->td_targid,
				td->td_lun
			);
		}
		else
#endif
		{
			my_snprintf(addrbuf, sizeof(addrbuf), addrfmt_str,
/*6250 "%d/%d.%d"*/
				td->td_hano,
				td->td_targid,
				td->td_lun
			);
		}

		oldnew[0] = 0;
		if (td->td_updatestatus)
			my_snprintf(oldnew, sizeof(oldnew),
				oldnewfmt_str,
/*6260 "%-4.4s,%-4.4s %s"*/
				td->td_oldver,
				td->td_newver,
				str_updatestatus(td->td_updatestatus)
			);
#if KOMMENT
Device Address: Controller_Number/SCSI_Device_ID.Logical_Unit_Number

				      Current            Update
Device                             Ver-               Version
Address  Device Description        sion  State        Old,New  Status
-------  ------------------------  ----------------  -----------------
0/1      Seagate STT8000A          5.51 Up to date   5.48,5.51 Success
1/0      IBM     ULTRIUM-TD1       09A1 Out of date  09A1,0BN1 Fail
1/1      IBM     ULTRIUM-TD1       0BN1 Up to date
#endif
		my_snprintf(text, sizeof(text),
			(oldnew[0])
				?updatefmt_str
/*6270 "%-8s %-24.24s  %-4.4s %-12.12s %s"*/
				:noupdatefmt_str,
/*6271 "%-8s %-24.24s  %-4.4s %s"*/
			addrbuf,
			&td->td_inq_std[8],
			td->td_curver,
			str_fwstate(td->td_fwstate),
			oldnew
		);
		if ((mi = menuitem_alloc(strlen(text)+1)) == NULL)
			break;
		strcpy(mi->mi_string, text);
		menuitem_insert_after(mi_root->mi_bak, mi);
	}
	if (count) {
		int menuflags = MENUF_STATIC;

		if ((unsigned)index >= (unsigned)count)
			menuflags |= MENUF_NOHILITE;
		menuitem_menu(
			menuflags,
			mi_root,
			index,
			0,             /*prompt_msgno*/
			head_msgno,
			0,             /*foot_msgno*/
			SM_COLS_DEVLIST
		);
	} else {
		w_cls();

		/* Show (or clear) prompt line */
		colorline(2, 0, FG_WHITE | BG_BLACK, cmsg(prompt_msgno));

		/* Show list header text */
		if ((p= cmsg(head_msgno)) != NULL) {
			q= p;
			while ((p= ww_line(&q, MW_WIDTH)) != NULL)
				my_printf("%s", p);
		}
		if ((p= cmsgex(0x6220)) != NULL) {
/*6220 "         (No tape devices found)\n"*/
			q= p;
			while ((p= ww_line(&q, MW_WIDTH)) != NULL)
				my_printf("%s", p);
		}

	}
	my_newline();
	menuitem_freeall(mi_root);

	/* Show error message */
	q = szError;
	while ((p= ww_line(&q, MW_WIDTH)) != NULL)
		my_printf("%s", p);
	/* Most error messages don't end with a newline, the log needs one */
	if (ww_totlines(szError, MW_WIDTH))
		logf("\n");

	curtime = time(NULL);
	prevtime = curtime -1;
	endtime = curtime + seconds;

	/* display prompt line */
	colorline(2, 0, FG_WHITE | BG_BLACK, cmsg(prompt_msgno));

	fmt = cmsg(MSGNO(status_msgno));
	/* flush keyboard */
	while (kbget_nowait())
		;
	for (;;) {
		int c;

		if (curtime != prevtime && fmt) {
			/* display status line */
			my_snprintf(text, sizeof(text), fmt, endtime - curtime);
			colorline(1, 0, FG_YELLOW | BG_BLACK, text);
			prevtime = curtime;
		}
		if (seconds < 0)
			return (0);
		if ((c = kbget_nowait()) == NEWLINE || c == ESCAPE) {
			rc = 1; /* canceled */
			break;
		}
		curtime = time(NULL);
		if (endtime - curtime < 0)
			break;
	}
	/* Clear both lines */
	colorline(1, 0, FG_WHITE | BG_BLACK, NULL);
	colorline(2, 0, FG_WHITE | BG_BLACK, NULL);
	return (rc);
}

autoupdate(int argc, char **argv)
{
	struct tapedev *td_top = Tapedev_list;
	struct tapedev *td;
	int is_user_cancel = 0;
	int updatedelay;
	int finaldelay;
	int devno_top;
	int devno_cur;
	int cursor;
	int is_page_shown;
	int i;
	int nupdated = 0;
	int status_msgno;

	UNREFERENCED(argc);
	UNREFERENCED(argv);
	fAutoUpdate = 1;
	my_printf("%s\n%s\n\n", PROGNAME(), CPYRIGHT());

	/* Load device section inquiry strings */
	my_prof_preload();

	/* get auto continue delay times */
	my_section("autoupdate");
	if ((updatedelay = my_profget_int("UpdateDelay", 25)) < 0)
		updatedelay = 25;
	if ((finaldelay = my_profget_int("FinalDelay", 25)) < 0)
		finaldelay = 25;
	if (open_window_mode())
		goto exit0;
#if SPLASH
	my_msgprintf(MSGNO(0x????));
/*
 *???? "\n"
 *     "This program will update the firmware in certain supported tape drives.\n"
 *     "\n"
 *     "Shortly, you will see a list of tape devices.  Devices that "
 *     "will be updated are indicated as \"Out of date\". 
 *     "\n"
 *     "You can cancel before the actual firmware update "
 *     "begins, by pressing <Enter> either at this screen "
 *     "or the next screen\n"
 *     "\n"
 *     "-- Press <Enter> to end withoug updating."
 */
	for (;;) {
		switch (kbget_flush()) {
		case NEWLINE:
			my_newline();
			goto ck_class;
		case ESCAPE:
			my_newline();
			goto exit0;
		}
	}
 ck_class:
	w_cls();
#endif
	/* Clear CPYRIGHT() */
	colorline(1, 0, FG_WHITE | BG_BLACK, NULL);

	/* setup scsienum_busflags for tapedrive search */
	scsienum_busflags= Arg_busflags;

//my_printf("wait <Enter>...\n"); while(kbget_flush() != NEWLINE);

	/* Search for tape devices */
	if (scsienumerate(ENUMF_AUTOLIST) < 0)
		goto device_not_found;

	td_top = Tapedev_list;
	/* set the tape device at top of screen */
	if (Tapedev_list == NULL) {
		error_msgset(MSGNO(0x8510), str_busflags(scsienum_busflags));
/*
 *    %s= "", "SCSI", "IDE/ATAPI"
 * 8510 "TAPEDRV8510: A %stape device was NOT found."
 */
		goto device_not_found;
	}

#define AUD_N_LINES      9 /* number of lines to display */
#define AUD_SCROLL_LINE  5 /* Scroll when this line reached */
#define AUD_N_SCROLL     1 /* number to scroll: 1 to N_AUD_LINES-2 */

	/*
	 * Examine all devices in the list
	 * Set status for devices that will be updated
	 */
	for (td=Tapedev_list; td; td=td->td_link) {
		if ((unsigned)td->td_fwstate < (unsigned)NELEM(Fwstate_ctr))
			Fwstate_ctr[ td->td_fwstate ]++;
		switch (td->td_fwstate) {
		case FWS_NEEDSDOS:
			if (fSimulate) {
				/* Simulate DOS update under WIN32 */
				Fwstate_ctr[ FWS_NEEDSDOS ]--;
				Fwstate_ctr[ FWS_OUTOFDATE ]++;
				goto case_needsupdate;
			}
			break;
		case FWS_OUTOFDATE:
		case_needsupdate:
			td->td_updatestatus = UDST_UPDATEWAITING;
			break;
		}
	}

	devno_top = 0;
	devno_cur = 0;
	cursor = 0;
	is_page_shown = 0;
	for (td=Tapedev_list; td; td=td->td_link, devno_cur++) {
		if (td->td_updatestatus == UDST_UPDATEWAITING) {
			if (nupdated == 0 && autoupdate_show_devices(
				MSGNO(0x6110),   /* status_msgno */
				MSGNO(0x6130),   /* prompt_msgno */
/*6110 "Update pending.                    Update will start in %d seconds."*/
/*6130 "Press Enter key to end without updating."*/
				td_top,          /* tape device for first display line */
				AUD_N_LINES,     /* number of lines to display */
				cursor,          /* device to highlight relative to top */
				updatedelay      /* seconds */
			)) {
				is_user_cancel = 1;
				goto exit0;
			}
			td->td_updatestatus = UDST_UPDATING;
			autoupdate_show_devices(
				MSGNO(0x6125),   /* status_msgno */
				MSGNO(0),        /* prompt_msgno */
/*6125 "Preparing to update the highlighted device..."*/
				td_top,          /* tape device for first display line */
				AUD_N_LINES,     /* number of lines to display */
				cursor,          /* device to highlight relative to top */
				-1               /* seconds */
			);
			my_sleep(1);
			if (autoupdate_firmware(td)) {
				td->td_updatestatus = UDST_UPDATEFAIL;
				autoupdate_show_devices(
					MSGNO(0x6119),   /* status_msgno */
					MSGNO(0x6131),   /* prompt_msgno */
/*6119 "Update failed.                              Will end in %d seconds."*/
/*6131 "Press Enter key to end immediately."*/
					td_top,
					AUD_N_LINES,
					cursor,      /* device to highlight relative to top */
					finaldelay   /* seconds */
				);
				/*
				 * error_level already set to either
				 * EXA_DEVICEFAIL or EXA_FATAL by
				 * autoupdate_firmware() or one of its 
				 * subroutines.
				 */
				goto exit0;
			}
			td->td_updatestatus = UDST_UPDATEGOOD;
			nupdated++;
			is_page_shown++;
		}
		cursor++;
		if (Tapedev_count - devno_top > AUD_N_LINES) {
			/* Undisplayed lines remaining */
			if (cursor >= AUD_SCROLL_LINE) {
				if (!is_page_shown) {
					/*
					 * No devices needed updating on this
					 * page and screen not yet shown
					 */ 
					autoupdate_show_devices(
						MSGNO(0),   /* status_msgno */
						MSGNO(0),   /* prompt_msgno */
						td_top,
						AUD_N_LINES,
						-1,         /* index - menu item to highlight */
						-1          /* seconds */
					);
					my_sleep(2);
				}
				is_page_shown = 0;
				for (i=0; i<AUD_N_SCROLL; i++) {
					if (Tapedev_count - devno_top <= AUD_N_LINES)
						break;
					if ((td_top=td_top->td_link) == NULL)
						break;
					devno_top++;
					cursor--;
				}
			}
		}
	}

	/* End of updating */
	if ( Fwstate_ctr[ FWS_INUSE ] ) {
		status_msgno = MSGNO(0x6113);
/*6113 "Tape device in use by another program.      Will end in %d seconds."*/
		error_level = EXA_INUSE;        /* 8: Tape device in use by another program */

	} else if ( Fwstate_ctr[ FWS_NEEDSDOS ] ) {
#if WIN32
		error_msgset((IS_NT()) ?MSGNO(0x8220) :MSGNO(0x8210));
/*
 *8210 "TAPEDRV8210: Cannot update device's firmware "
 *     "in Windows 95/98/ME Graphics mode (within a DOS Box).  "
 *     "Reboot your system into MS-DOS only mode."
 *8220 "TAPEDRV8220: Cannot update device's firmware from "
 *     "Windows NT/2000/XP.  Use MS-DOS or Windows 95/98/ME in MS-DOS Only mode."
 */
#endif
		status_msgno = MSGNO(0x6117);
/*6117 "Tape device needs DOS only mode update.     Will end in %d seconds."*/
		error_level = EXA_NEEDSDOS;     /* 7: Tape device needs update in DOS only mode */

	} else if ( Fwstate_ctr[ FWS_NEEDSALTERNATE ] ) {
		status_msgno = MSGNO(0x6118);
/*6118 "Tape device needs alternate method update.  Will end in %d seconds."*/
		error_level = EXA_NEEDSALTERNATE; /* 6: Tape device needs update via alternate method */

	} else if ( Fwstate_ctr[ FWS_INACCESSIBLE ] ) {
		status_msgno = MSGNO(0x6112);
/*6112 "Tape device inaccessible.                   Will end in %d seconds."*/
		error_level = EXA_INACCESSIBLE; /* 9: Tape device inaccessible */

	} else if (nupdated) {
		status_msgno = MSGNO(0x6120);
/*6120 "Update successful.                          Will end in %d seconds."*/
		error_level = EXA_ZERO; /* 0: Tape device successfully updated */

	} else if ( Fwstate_ctr[ FWS_CURRENT ] + Fwstate_ctr[ FWS_NEWER ] ) {
		status_msgno = MSGNO(0x6115);
/*6115 "Tape device(s) already up to date.          Will end in %d seconds."*/
		error_level = EXA_UPTODATE; /* 3: Tape device already up to date */

	} else {
 device_not_found:
		status_msgno = MSGNO(0x6111);
/*6111 "No updateable device found.                 Will end in %d seconds."*/
		error_level = EXA_NONEFOUND; /* 2: No supported tape devices found */
	}
	autoupdate_show_devices(
		status_msgno,
		MSGNO(0x6131),   /* prompt_msgno */
/*6131 "Press Enter key to end immediately."*/
		td_top,          /* tape device for first display line */
		AUD_N_LINES,     /* number of lines to display */
		-1,              /* device to highlight relative to top */
		finaldelay       /* seconds */
	);

exit0:
	if (fWindowMode) {
		close_window_mode();
		my_printf("%s\n%s\n\n", PROGNAME(), CPYRIGHT());
		if (is_user_cancel) {
			logf("Operator cancel\n");
			error_msgset(MSGNO(0x6160));
/*6160 "Firmware update not attempted.\n"*/
			/* 5: Operator canceled automatic update */
			return (EXA_CANCEL);
		}
		if (nupdated) {
			my_msgprintf(MSGNO(0x6150), nupdated);
/*6150 "%d tape devices successfully updated.\n"*/
			if (error_level)
				my_newline();
		}
	}
	return (error_level);
}

autosim(int argc, char **argv)
{
	fAutoSim = 1;
	fSimulate = 1;
	return (autoupdate(argc, argv));
}

status(int argc, char **argv)
{
	static char me[] = "status";

	UNREFERENCED(argc);
	UNREFERENCED(argv);
	fStatus = 1;
	fMoreMode = 1;

	if (fRecognizable)
		my_printf("##Tapedrv_status=1\n");

	my_printf("%s\n%s\n\n", PROGNAME(), CPYRIGHT());

	my_prof_preload(); /* Load device section inquiry strings */

	/* setup scsienum_busflags for tapedrive search */
	scsienum_busflags= Arg_busflags;

#if WIN32
	if (IS_NT())
	{
		my_msgprintf(MSGNO(0x6811));
/*6811 "Device Address: Port_Number.Bus_Number/SCSI_Device_ID.Logical_Unit_Number\n"*/
	}
	else
#endif
	{
		my_msgprintf(MSGNO(0x6810));
/*6810 "Device Address: Controller_Number/SCSI_Device_ID.Logical_Unit_Number\n"*/
	}
	my_msgprintf(MSGNO(0x6819));
/*
 *6819 "\n"
 *     "Device                            Device  Update\n"
 *     "Address  Device Inquiry           Version Version Device Firmware State\n"
 *     "-------  ------------------------ ------- ------- ---------------------\n"
 */
	/* Search for tape devices */
	if (scsienumerate(ENUMF_STATUS) < 0) {
		error_level = 0;
		goto device_not_found;
	}

	if (fRecognizable) {
		my_printf("##FwStateUnrecognized=%d\n",   Fwstate_ctr[ FWS_UNRECOGNIZED ]);
		my_printf("##FwStateInaccessible=%d\n",   Fwstate_ctr[ FWS_INACCESSIBLE ]);
		my_printf("##FwStateInUse=%d\n",          Fwstate_ctr[ FWS_INUSE ]);
		my_printf("##FwStateIgnored=%d\n",        Fwstate_ctr[ FWS_IGNORED ]);
		my_printf("##FwStateCurrent=%d\n",        Fwstate_ctr[ FWS_CURRENT ]);
		my_printf("##FwStateNewer=%d\n",          Fwstate_ctr[ FWS_NEWER ]);
		my_printf("##FwStateNeedsDOS=%d\n",       Fwstate_ctr[ FWS_NEEDSDOS ]);
		my_printf("##FwStateNeedsAlternate=%d\n", Fwstate_ctr[ FWS_NEEDSALTERNATE ]);
		my_printf("##FwStateOutOfDate=%d\n",      Fwstate_ctr[ FWS_OUTOFDATE ]);
	}

	/* set the tape device at top of screen */
	if (Tapedev_count == 0) {
		error_msgset(MSGNO(0x8510), str_busflags(scsienum_busflags));
/*
 *    %s= "", "SCSI", "IDE/ATAPI"
 * 8510 "TAPEDRV8510: A %stape device was NOT found."
 */
 device_not_found:
		my_msgprintf(MSGNO(0x6820));
/*6820 "         (No tape devices found)\n"*/

	}
	return (error_level);
}

statussim(int argc, char **argv)
{
	fStatSim = 1;
	fSimulate = 1;
	return (status(argc, argv));
}

scan(int argc, char **argv)
{
	/* setup scsienum_busflags for tapedrive search */
	scsienum_busflags= Arg_busflags;

	UNREFERENCED(argc);
	UNREFERENCED(argv);
	fMoreMode = 1;
	my_printf("%s\n\n", PROGNAME());
	my_prof_preload(); /* Load device section inquiry strings */

	/* Search for tape devices */
	if (scsienumerate(ENUMF_SCAN) < 0)
		return (EX_FATAL);
	return (0);
}

#if LOTSOFMEMORY
inick_printf(char *fmt, ...)
{
	va_list args;

	printf("%s: [%s]: ", Inifname, my_section(NULL));
	va_start(args, fmt);
	vprintf(fmt, args);
	printf("\n");
	return (0);
}
#endif /*LOTSOFMEMORY*/

#if LOTSOFMEMORY
inick_devsection(struct inidev *idev)
{
	struct updatealg *uap;
	FILE *fp;
	char line[SIZE_PROFLINE];
	char *prepcmdvec[10];
	int ncmds;
	char **cmdv;
	char **v;
	int filefound;
	char fnamebuf[_MAX_PATH];
	char *p;
	char *parmname;
	int errors = 0;

	UNREFERENCED(idev);
	parmname = "UpdateAlgorithm";
	my_profget_str_unquote(0, parmname, line, sizeof(line));
	if ((uap = get_updatealg_ptr(line)) == NULL) {
		inick_printf("%s error: \"%s\": Unrecognized or missing",
			parmname,
			line
		);
		errors++;
	}

	/* Assemble firmware filename from -p <firmware_path> and AutoFileName */
	strcpy(Fw_fname, Fw_path);
	p = append_dirsep(Fw_fname);

	parmname = "AutoFileName";
	my_profget_str_unquote(1, parmname, p, &Fw_fname[sizeof(Fw_fname)] - p);
	filefound = findunique(Fw_fname, fnamebuf, NULL);
	if (filefound != 1) {
		if (filefound > 1)
			inick_printf("%s error: \"%s\": Pattern matches multiple files",
				parmname,
				Fw_fname
			);
		else
			inick_printf("%s error: \"%s\": No matching file found",
				parmname,
				Fw_fname
			);
		errors++;
	} else {
		char *fname;
		char *fsuffix;

		/* found a single file */
		/* Check actual filename for 8.3 compatibility */
		fname= strfname(fnamebuf);
		fsuffix= strfsuffix(fname);
		if (strlen(fname) > 12
		 || strlen(fsuffix) > 4
		 || (*fsuffix == '.' && fsuffix != strchr(fname, '.')))
			inick_printf("%s=\"%s\": Warning: %s: Not a DOS compatible (8.3) filename",
				parmname,
				Fw_fname,
				fname
			);

		if ((fp = fopen(fnamebuf,"rb")) == NULL) {
			inick_printf("%s=\"%s\": %s: Open error %d: %s",
				parmname,
				Fw_fname,
				fnamebuf,
				strerror(errno)
			);
			errors++;
		} else {
			fclose(fp);
			/* Check firmware image if an update algorithm was found */
			if (uap)
			if (download(NULL, DNLCMD_CHECKIMAGE, uap, fnamebuf, 0, 0) < 0) {
#if __TAPEDRV__
				printf("%s\n", szError);
				szError[0] = 0;
				error_level = 0;
#endif
				errors++;
			}
		}
	}

	/*
	 * Get a NULL terminated list of pre-update 
	 * preparation commands from the .INI.
	 * If this list of commands is present, 
	 * it overrides the default for the UpdateAlgorithm.
	 */
	parmname = "UpdatePrepare";
	memset(prepcmdvec, 0, sizeof(prepcmdvec));
	ncmds = my_profget_strlist_unquote(0, parmname,
		prepcmdvec, NELEM(prepcmdvec)-1, line, sizeof(line));
	if (ncmds == 1 && *prepcmdvec[0] == 0) {
		ncmds = 0;
		prepcmdvec[ 0 ] = NULL;
	}

	for (cmdv=prepcmdvec; *cmdv; cmdv++)
		for (v=prepcmds_all; ;v++) {
			if (!*v) {
				inick_printf("%s: %s: Unrecognized update preparation command",
					parmname, *cmdv
				);
				errors++;
				break;
			}
			if (!stricmp(*cmdv, *v))
				break; /* match */
		}
	return (errors);
}
#endif /*LOTSOFMEMORY*/

#if LOTSOFMEMORY
inick(int argc, char **argv)
{
	UNREFERENCED(argc);
	UNREFERENCED(argv);
	my_prof_preload();
	{
		struct inidev *idev;
		int errors = 0;

		for (idev=Inidev_list; idev; idev=idev->idev_link) {
			/* Set ini device section name */
			my_section(idev->idev_section);
			errors += inick_devsection(idev);
		}
		printf("%s: %d errors\n", Inifname, errors);
		return (errors ?EX_FATAL :0);
	}
	return (0);
}
#endif /*LOTSOFMEMORY*/

#if LOTSOFMEMORY
msgck_printf(long number, char *fmt, ...)
{
	va_list args;

	printf("%s(%ld): %06lx: ", Cmsg_fname, Cmsg_msgno_lineno, number);
	va_start(args, fmt);
	vprintf(fmt, args);
	printf("\n");
	return (0);
}
#endif /*LOTSOFMEMORY*/

#if LOTSOFMEMORY
msgck(int argc, char **argv)
{
#define LANGNO(n) ((int)((n)>>20)&0xf)
#define BASEMSGNO(n) ((n)&0xfffff)
	long number;
	long overflow;
	int len;
	long prev;
	long *mnp_beg;
	long *mnp_end;
	long *mnp_max;
	long *mnp;
	int lang_bits;
	int once;
	int i;

	UNREFERENCED(argc);
	UNREFERENCED(argv);
	lang_bits= 0;
	prev= 0;
	once= 0;
#define MSGNUMBER_NELEM 0x1000 /* number of elements in message number buffer */
	mnp_beg= mnp_end= malloc(MSGNUMBER_NELEM * sizeof(*mnp_beg));
	mnp_max= &mnp_beg[ MSGNUMBER_NELEM ];

	cmsg_rewind();
	while ((len= cmsg_read(&number, Cmsg_buf, sizeof(Cmsg_buf), &overflow)) != -1) {
		if (len > sizeof(Cmsg_buf) - sizeof(Cmsg_buf)/10)
			msgck_printf(number, "Excessive length");
		for (mnp=mnp_beg; mnp<mnp_end; mnp++)
			if (number == *mnp)
				msgck_printf(number, "Duplicate");
		if ((unsigned long)number < (unsigned long)prev)
			msgck_printf(number, "Sequence");
		prev= number;
		if (len >= 7 && !memcmp(Cmsg_buf, "TAPEDRV", 3)) {
			long errnum= 0;
			int tmp;

			i = sizeof("TAPEDRV")-1;
			while ((tmp= ahex2bin(Cmsg_buf[i++])) >= 0) {
				errnum *= 16;
				errnum += tmp;
			}
			if (errnum != BASEMSGNO(number))
				msgck_printf(
					number,
			"\"TAPEDRV%04lx\": Suspicious error message header",
					errnum
				);
		}
		if (mnp_end >= mnp_max) {
			if (!once) {
				once++;
				msgck_printf(number, "Warning: %ld entry message number save buffer full",
					mnp_max - mnp_beg
				);
			}
		} else
			*mnp_end++ = number;
		if ((unsigned long)number >= 0x1000)
			lang_bits |= 1<<LANGNO(number);
	}

	/* Look for missing message numbers in alternate languages */
	cmsg_rewind();
	while ((len= cmsg_read(&number, Cmsg_buf, sizeof(Cmsg_buf), &overflow)) != -1) {
		long xnumber;
		int xbits;

		/* Skip non-translatable messages */
		if ((unsigned long)number < 0x1000)
			continue;
		/* Skip help messages 0x1502 - 0x15ff */
		if ((unsigned long)BASEMSGNO(number) - 0x1502 < 0x1502 + 0x100)
			continue;
		xbits= 1<<LANGNO(number);
		for (mnp=mnp_beg; mnp<mnp_end; mnp++) {
			xnumber= *mnp;
			if (LANGNO(xnumber) == LANGNO(number))
				continue;
			if (BASEMSGNO(xnumber) != BASEMSGNO(number))
				continue;
			xbits |= 1<<LANGNO(xnumber);
		}
		for (i=0; i<16; i++) {
			if (!(lang_bits & (1<<i)))
				continue;
			if (xbits & (1<<i))
				continue;
			xnumber= (number & ~(((long)0xf)<<20)) | ((long)i<<20);
			msgck_printf(
				number,
	"%06lx: Alternate language message number missing",
				xnumber
			);
		}
	}
#undef BASEMSGNO
#undef LANGNO
	return (0);
}
#endif /*LOTSOFMEMORY*/

ver(int argc, char **argv)
{
	UNREFERENCED(argc);
	UNREFERENCED(argv);
	printf("%s\n", My_verstr);
	return (0);
}

smallmem(int argc, char **argv)
{
	UNREFERENCED(argc);
	UNREFERENCED(argv);
	error(CMDPFX|EX_FATAL, "%s: Unavailable in 16-bit DOS mode", *argv);
	return (0);
}

char optchars_autoupdate[] =  "a:b:dgi:l:p:z";
char optchars_update[] =      "a:b:dgi:l:p:zf:";
char optchars_status[] =      "a:b:dgi:l:p:zo:r";
char optchars_scan[] =        "a:b:dgi:l:p:zo:";
char optchars_inick[] =       "dgi:l:p:o:";
char optchars_msgck[] =       "dgl:o:";

#define P MCMDF_OTHERPARMS
#define N MCMDF_NOPREPARSE

struct maincmd Maincmds[]= {
	{ "help",       help,           0,        NULL                 },
#define MCMDIDX_HELP 0
	{ "/?",         help,           0,        NULL                 },
	{ "autoupdate", autoupdate,     0,        optchars_autoupdate  },
	{ "autosim",    autosim,        0,        optchars_autoupdate  },
#if TAPEDRV_UPDATE
	{ "update",     update,         0,        optchars_update      },
	{ "updatesim",  updatesim,      0,        optchars_update      },
#endif
	{ "status",     status,         0,        optchars_status      },
	{ "statussim",  statussim,      0,        optchars_status      },
	{ "scan",       scan,           0,        optchars_scan        },
	{ "ver",        ver,            0,        NULL                 },
#if LOTSOFMEMORY
	{ "msgck",      msgck,          0,        optchars_msgck       },
	{ "inick",      inick,          0,        optchars_inick       },
#else
	{ "msgck",      smallmem,       N,        NULL                 },
	{ "inick",      smallmem,       N,        NULL                 },
#endif
	{ NULL,         NULL,           0,        NULL                 }
};

#undef P
#undef N

struct preloadmsg {
	int    plm_flags; /* PLF_* values */
	int    plm_msgno;
	char **plm_addr;
};
#define PLF_ALLOCED 0x1 /* message text in allocated buffer */

struct preloadmsg Preloadmsgs[]= {
	{ 0, MSGNO(0x1410), &plmsg_more },         /*--More-- ...*/
	{ 0, MSGNO(0x1500), &plmsg_prog_fmt },     /*tapedrv -- IBM ... Program - v%s*/
	{ 0, MSGNO(0x1501), &plmsg_cprt },         /*Copyright (c) ... */
	{ 0, MSGNO(0x3000), &plmsg_cmderrorfmt },  /*%s error: */
	{ 0, 0,             NULL }
};

lang_preload(void) /* preload certain language specific messages */
{
	struct preloadmsg *plp;
	char *p;
	char *s;

	/* Free previously allocated pre-loaded messages */
	for (plp=Preloadmsgs; plp->plm_msgno; plp++)
		if (plp->plm_flags & PLF_ALLOCED) {
			plp->plm_flags &= ~PLF_ALLOCED;
			free(*plp->plm_addr);
			*plp->plm_addr= NULL;
		}
	/* Read in pre-load messages */
	for (plp=Preloadmsgs; plp->plm_msgno; plp++) {
		if ((p= cmsg(plp->plm_msgno)) != NULL)
		if ((s= strdup(p)) != NULL) {
			*plp->plm_addr= s;
			plp->plm_flags |= PLF_ALLOCED;
		}
	}
	if (plmsg_prog_fmt)
		my_snprintf(Prog_buf, sizeof(Prog_buf), plmsg_prog_fmt, My_verstr);
#if NOT_USING
	/* Set translated command name used by error() */
	if (Maincmdp) {
		/* Set command name in sub argv */
		if (Subcmd_argv && Subcmd_argv[0])
			Subcmd_argv[0] = Maincmdp->mcmd_name;
		My_cmdname= Maincmdp->mcmd_name;
	}
#endif
	return (0);
}

/*
 * getopt(), optind, opterr, optarg -- get command line options
 *
 * getopt() aids the parsing of command line options.  
 * The standard command options syntax is:
 *
 *    command [options] [--|//] [otherparameters]
 *
 * where each option is of the form
 *
 *    option = -optLetter[optLetter[...]]
 *           | /optLetter[optLetter[...]]
 *           | -argLetter[ ]argument
 *           | /argLetter[ ]argument
 *           | -optLetter[optLetter[...]]argLetter[ ]argument
 *           | /optLetter[optLetter[...]]argLetter[ ]argument
 *
 *   -- or // are special tokens in that they optionally terminate 
 *      option parsing and are skipped.
 *
 *    otherparameters are the remaining parameters not introduced 
 *       by a '-' or '/' (a list of filenames for example) 
 *
 * where
 *    - optLetter a single character indicating an option.  
 *    - argLetter is a single character followed by a manditory 
 *      argument.  The argument is optionaly separated from the 
 *      argLetter by white space.  It is not an error for the argument 
 *      to begin with a '-' or '/'.  
 *    - there is no space between the '-' or '/' and optLetters or 
 *      argLetters.
 *    - optLetters and argLettes are case insensative.
 *
 * Immediatly following a single '-' or '/', multiple optLetter 
 * clusters with at most one argLetter (and possibly its argument) 
 * completing the cluster.  optLetters and argLetters may be repeated, 
 * it is up to the caller to decide if that is an error.
 * All options appear before any non-option, arguments. 
 *
 * The character "-" appearing alone terminates parsing but is 
 * not skipped.
 * The character "/" appearing alone is an error.
 * The special options "--" or "//" terminates parsing and is 
 * skipped.
 *
 * getopt() is normally called repeatedly until all options have 
 * been parsed.  At termination of parsing other parameters 
 * may remain (e.g., filenames).  The index, in argv[], of first 
 * remaining parameter is available from optind.
 *
 * The getopt() 'optchars' argument specifies valid optLetters and 
 * argLetters.  argLetters are followed with ':'.  getopt() returns 
 * the value of the option character found which is '?' if 
 * unrecognized, or EOF if no more options remain in the command line.  
 * If option is an argLetter then the global, optarg, gives the 
 * address of the argument string (having skipped any white-space).
 *
 * The global, optind, is initially 1 and has the index of the first 
 * argument of argv[] which getopt has not taken.  At termination of 
 * of parsing optind gives the index in argv[] of the next non-option 
 * parameter.
 *
 * When error occurs, such as an unrecognized option or a missing 
 * argument, getopt() normally prints an error and returns the 
 * '?' character.  When the global, opterr, is set to zero, before 
 * calling getopt(), the error message is not printed.
 *
 * For example, if
 *
 *    optchars is "a:b:cdef:"
 *
 * then 'c', 'd', and 'e' are option letters and 'a', 'b', and 
 * 'f' are followed by arguments.  A valid command line wold be: 
 *
 *     command  /cbyz /e /a x otherparameters
 *
 * where
 *    'c' will be returned as isolated option letters.
 *    'b' will return with "yz" as its argument string.
 *    'e' is an isolated option.
 *    'a' will return with "x" as its argument.
 *    "otherparameters" do not begin with '/' and thus terminates 
 *    getopt() parsing.  The caller may examine remaining arguments 
 *    using optind as the first index for *argv[].
 *
 * Configuration:
 *    optid lists the characters used to identify each option and 
 *       is set to "-", "/", or "-/".
 *
 *    GETOPT_CASEINSENSITIVE configures case sensitivity.
 *       Note the character returned by getopt() has the same 
 *       case as that given in the argument optchars string.
 *    GETOPT_NOREQUIRE disables the standard behaviour of returning 
 *       an '?' error if a required argument (as indicated by a colon
 *       in the 'optchars' parameter) is missing.  Instead, optarg 
 *       points to a "" when the arguement is missing.  Note that 
 *       when optarg points to an empty string, it may actually be 
 *       either a missing or empty argument.
 *    GETOPT_NOSPACEARGID specifies that when a given argLetter and 
 *       and its argument are separated by space, that the argument 
 *       may not begin with the same option id character (i.e., '-' 
 *       or '/') as that used with the argLetter.  For example, for 
 *       a given optchars value of "a:", in the command line 
 *          progname /a /b
 *       /b is normally treated as an argument of the /a option.  
 *       When GETOPT_NOSPACEARGID is configured, /b is treated as 
 *       a new option (as it also begins with a "/") and thus 
 *       /a is taken to have a missing or empty argument.
 *
 * Returns:
 *    If an option is recognized, its character value is returned.
 *    If an option is unrecognized, or recognized but missing a 
 *    required argument, the character '?' is returned.
 *    EOF (-1) is returned if all options have been parsed.
 */
#include <stdio.h>
#include <string.h>
#include <errno.h>

int optind = 1;         /* index of next argument in argv */
char *optarg;           /* pointer to argument of current option */
int opterr = 1;         /* true: show error message */

/* non-standard extensions */
char optname[3];        /* current option for use in error messages */
char *optid = "/";      /* list of option identifier characters: "-/", "-", or "-" */

static char *optptr;    /* remembers next option char's location */

getopt(int argc, char *argv[], char *optchars)
{
	int emsgno = 0;
	char *patp;
	int c;

	optarg = NULL;
	if (optind < argc) {
		if (optptr == NULL) {
			if ((optptr = argv[optind]) == NULL)
				goto goptEOF; /* shouldn't be NULL */
			optname[0] = *optptr++;
			if (strchr(optid, optname[0]) == NULL)
				goto goptEOF;
			if ((optname[1] = *optptr) == 0) {
				/* "-" or "/" by itself */
				if (optname[0] == '-' && strchr(optid, '-'))
					goto goptEOF; /* "-" by itself is ok */
				goto gopt_badopt; /* "/" by itself isn't ok*/
			}
			if (optname[0] == optname[1]) {
				optind++; /* skip either "--" or "//" */
				goto goptEOF;
			}
		}
		optname[1] = *optptr++;
		if ((c = (unsigned char)optname[1]) == 0) {
			optind++;
			goto goptEOF;
		}
		patp = strchr(optchars, c);
#define GETOPT_CASEINSENSITIVE 1
#if GETOPT_CASEINSENSITIVE
		/* Try opposite case if alpha */
		if (patp == NULL && (unsigned)(c|0x20)-'a' <= 'z'-'a')
			patp = strchr(optchars, c ^= 0x20);
#endif
		if (c == ':' || patp == NULL) {
	gopt_badopt:
			emsgno = MSGNO(0x3030);
/*3030 "%s: Unrecognized option"*/
			goto goptError;
		}
		if (*++patp == ':') {
			/* argLetter[ ]argument */
			optarg = optptr;
			optptr = NULL;
			optind++;
			if (*optarg == 0) {
#define GETOPT_NOREQUIRE 1
#if GETOPT_NOREQUIRE
				/*
				 * If caller sees optarg pointing to "", 
				 * arg is absent (or an empty string)
				 */
				if (optind < argc && argv[optind] != NULL)
#define GETOPT_NOSPACEARGID 1
#if GETOPT_NOSPACEARGID
				/*
				 * In order to qualify as space separated,
				 * an argument must begin with a character 
				 * other than the option id character.
				 * (E.g., other than a "-" ("/") if option 
				 * id was a "-" ("/"))
				 */
				if (optname[0] != argv[optind][0])
#endif
					optarg = argv[optind++];
#else
				/* standard "required arg" behaviour */
				if (optind >= argc || argv[optind] == NULL) {
					emsgno = MSGNO(0x3032);
/*3032 "%s: Missing required argument"*/
					goto goptError;
				}
				optarg = argv[optind++];
#endif
			}
		} else {
			/* optLetter */
			if (*optptr == 0) {
				optind++;
				optptr = NULL;
			}
		}
		return (c);
	}
goptEOF:
	optname[0] = 0;
	optptr = NULL;
	return EOF;

goptError:
	if (opterr)
		error(CMDPFX|EX_FATAL, cmsgex(emsgno), optname);
/*3030 "%s: Unrecognized option"*/
/*3032 "%s: Missing required argument"*/
	errno = EINVAL;
	return ('?');
}

parse_options(int mcmdflags, char *optchars, int argc, char **argv)
{
	int c;
	char *p;
	char **v;
	int busf= 0;
	int adbits= 0;
	int adno;
	char *outfname= NULL;
	char *sep= "";
#if __DOS__
	int searchidx = 0;
#endif

	if (optchars == NULL)
		optchars = "";

	/* interpret command line arguments using getopt */
	while ((c = getopt(argc, argv, optchars)) != -1) {
		p = optarg;
		switch (c) {
		case 'a': /* /a <ide adapter digits> */
			if (adbits) {
		onlyonce:
				error(CMDPFX|EX_FATAL,
					cmsgex(MSGNO(0x3034)),
/*3034 "%s: May only be used once"*/
					optname
				);
			}
			if (p == NULL || !*p)
				error(CMDPFX|EX_FATAL,
					cmsgex(MSGNO(0x3060)),
/*3060 "%s: Missing IDE adapter digit(s)"*/
					optname
				);
			for (; *p; p++) {
				if (!isdigit(*p))
					error(CMDPFX|EX_FATAL,
						cmsgex(MSGNO(0x3062)),
/*3062 "%s %s: '%c': Invalid IDE adapter digit"*/
						optname,
						optarg,
						*p
					);
				adno = *p - '0';
				if ((unsigned)adno >= 4)
					error(CMDPFX|EX_FATAL,
						cmsgex(MSGNO(0x3064)),
/*3064 "%s %s: '%c': IDE adapter digit not in the range 0 through 3"*/
						optname,
						optarg,
						*p
					);
				if (adbits & (1<<adno))
					error(CMDPFX|EX_FATAL,
						cmsgex(MSGNO(0x3066)),
/*3066 "%s %s: '%c': IDE adapter digit duplicate"*/
						optname,
						optarg,
						*p
					);
				adbits |= 1<<adno;
#if __DOS__
				ide_search_order[searchidx++]= adno;
				ide_search_length= searchidx;
#endif
			}
			break;

		case 'b': /* /b {ide|atapi|scsi} */
			if (p == NULL || !*p)
				error(CMDPFX|EX_FATAL,
					cmsgex(MSGNO(0x3070)),
/*3070 "%s: Missing bus name: ide, atapi, or scsi"*/
					optname
				);
			if (!stricmp(p, "ide") || !stricmp(p, "atapi")) {
				busf |= BUSF_IDE;
			} else if (!stricmp(p, "scsi")) {
				busf |= BUSF_SCSI;
			} else
				error(CMDPFX|EX_FATAL,
					cmsgex(MSGNO(0x3072)),
/*3072 "%s %s: Not ide, atapi, or scsi: Unrecognized bus name"*/
					optname,
					optarg
				);
			break;

		case 'd':
		case 'g': /* for compatiblity with fupu */
			fDebug++;
#if __DOS__
			ide_debug++;
#endif
			break;

		case 'i':
			if (Inifname[0])
				goto onlyonce;
			if (p == NULL || !*p)
				error(CMDPFX|EX_FATAL,
					cmsgex(MSGNO(0x3085)),
/*3085 "%s: Missing initialization filename"*/
					optname
				);
			strcpyn(Inifname, p, sizeof(Inifname));
			break;

#if TAPEDRV_UPDATE
		case 'f':
			if (Fw_fname[0])
				goto onlyonce;
			if (p == NULL || !*p)
				error(CMDPFX|EX_FATAL,
					cmsgex(MSGNO(0x3075)),
/*3075 "%s: Missing firmware filename"*/
					optname
				);
			strcpyn(Fw_fname, p, sizeof(Fw_fname));
			break;
#endif

		case 'l':
			if (log_fname)
				goto onlyonce;
			if (p == NULL || !*p || !strcmp(p, "+"))
				error(CMDPFX|EX_FATAL,
					cmsgex(MSGNO(0x3090)),
/*3090 "%s: Missing log filename"*/
					optname
				);
			log_fname = p;
			if (log_open())
				error(CMDPFX|EX_FATAL,
					cmsgex(MSGNO(0x3091)),
/*3091 "\"%s\": Log file open error"*/
					log_fname
				);
			for (v=Orig_argv; *v; v++) {
				logf("%s%s", sep, *v);
				sep = " ";
			}
			logf("\n");
			log_memstats();
			break;

		case 'o':
			if (outfname)
				goto onlyonce;
			if (p == NULL || !*p || !strcmp(p, "+"))
				error(CMDPFX|EX_FATAL,
					cmsgex(MSGNO(0x3095)),
/*3095 "%s: Missing output filename"*/
					optname
				);
			outfname = p;
			break;

		case 'p': /* /p <firmware_path> */
			if (Fw_path[0])
				goto onlyonce;
			if (p == NULL || !*p)
				error(CMDPFX|EX_FATAL,
					cmsgex(MSGNO(0x3080)),
/*3080 "%s: Missing firmware path"*/
					optname
				);
			strcpyn(Fw_path, p, sizeof(Fw_path));
			if (findunique(Fw_path, NULL, NULL) != 1)
				error(CMDPFX|EX_FATAL,
					cmsgex(MSGNO(0x3082)),
/*3082 "%s %s: Invalid firmware path"*/
					optname,
					optarg
				);
			break;

		case 'r': /* program recognizable output */
			fRecognizable = 1;
			break;

		case 'z': /* Examine only LUN 0 during ASPI scan */
			Arg_lun0 = 1;
			break;
		}
	}
	if (!(mcmdflags & MCMDF_OTHERPARMS) && optind < argc)
		error(CMDPFX|EX_FATAL, cmsgex(MSGNO(0x3020)), argv[optind]);
/*3020 "\"%s\": Invalid argument"*/

	/* possibly open output file */
	if (outfname && strcmp(outfname, "-")) {
		int ofd;

		if ((ofd= open(outfname, O_TRUNC|O_CREAT|O_WRONLY, 0777)) < 0)
			error(CMDPFX|EX_FATAL,
					cmsgex(MSGNO(0x3096)),
/*3096 "\"%s\": Output file open error"*/
					outfname
			);

		/* transfer handle to stdout for printf */
		dup2(ofd, 1);
		close(ofd);
	}
	/*
	 * When scanning ide bus in realmode 16-bit DOS, writing 
	 * to disk causes a system hang if ide_debug is set. 
	 * A buffer just to catch ide scan output prevents this.
	 */
	if (!isatty(1) && (stdout_vbufaddr = malloc(STDOUT_VBUFSIZE)) != NULL)
		setvbuf(stdout, stdout_vbufaddr, _IOFBF, STDOUT_VBUFSIZE);
	if (busf)
		Arg_busflags = busf;
	return (0);
}

 void
onterm(void)
{
	close_window_mode();
#if __DOS__
	/* restore original terminate address */
	dos_setterm(DOSTERM_DFL);
	ide_term();
#endif
}

#if __DOS__
/*
 * _setenvp() -- dummy prevents c runtime from copying environment to heap
 * See msvc15\source\startup\dos\stdenvp.asm
 */
void _setenvp(void) {}
#endif

 void
main(int argc, char **argv)
{
	char **avs;
	char **avd;
	char *argp;

	Orig_argc= argc;
	Orig_argv= argv;

	/*
	 * Intercept dos terminate vector in psp.
	 * The address in this vector gets control on normal exit,
	 * or when operator operator selects Abort
	 * (at an Abort, Ignore, Retry, Fail prompt).
	 */
#if WIN32
	GetVersionEx(&Osversioninfo);
	/* On critical error, fail */
	SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
	atexit(onterm); /* Last in, First executed */
	/* create message file pathname */
	find_exe_path(argv[0], Cmsg_fname);
#else /*__DOS__*/
	/* On critical error, fail */
	dos_setivec(0x24, (int (_interrupt _far *)(void))critintr);
	/*
	 * Set termination handler whose purpose is to remove 
	 * redirected hardware interrupt vectors even on an abort.
	 */
	dos_setterm(onterm);
	/* create message file pathname */
	strcpyn(Cmsg_fname, argv[0], sizeof(Cmsg_fname) - 4);
#endif
	*strfname(Cmsg_fname) = 0;
	strcatn(Cmsg_fname, "Tapedrv.msg", sizeof(Cmsg_fname));
	cmsg_check();   /* check the message file */

	/* get buffer used for copy of argv used by option parsing functions */
	if ((Subcmd_argv= (char **)malloc(sizeof(char *) * (argc+1))) == NULL)
		error(CMDPFX|EX_FATAL, cmsgex(MSGNO(0x8010)), "main");
/*8010 "TAPEDRV8010 error: \"%s\": Out of memory"*/

	/* copy argv to Subcmd_argv (except the TAPEDRV.EXE at argv[0]) */
	avs= &argv[1];
	avd= Subcmd_argv;
	/* If argv[1] is not a command, insert the default command */
	if ((*avd= *avs) == NULL)
		/* default tapedrv command: "help" */
		*avd = Maincmds[MCMDIDX_HELP].mcmd_name;
	else
		avs++;
	avd++;
	/* copy remaining options */
	while ((*avd = *avs) != NULL)
		avd++, avs++;

	/* Read in pre-load messages */
	lang_preload();

	/* Scan the command list for a matching command */
	argp= Subcmd_argv[0];
	for (Maincmdp=Maincmds; ; Maincmdp++) {
		if (Maincmdp->mcmd_name == NULL)
			error(CMDPFX|EX_FATAL, cmsgex(MSGNO(0x3010)), argp);
/*3010 "\"%s\": Unrecognized tapedrv command"*/
		if (!stricmp(argp, Maincmdp->mcmd_name)) {
			/* set help alias to "help" */
			if (Maincmdp->mcmd_func == help)
				Maincmdp=Maincmds;
			break;  /* command match */
		}
	}

	/* alias help */
	if (Maincmdp->mcmd_func == help)
		Maincmdp= &Maincmds[ MCMDIDX_HELP ];
	/* Set command name used by error() */
	My_cmdname= Maincmdp->mcmd_name;
	/* parse command line options */
	if (!(Maincmdp->mcmd_flags & MCMDF_NOPREPARSE))
		parse_options(Maincmdp->mcmd_flags, Maincmdp->mcmd_optchars, avd - Subcmd_argv, Subcmd_argv);
	/* Execute the subcommand */
	my_exit( (*Maincmdp->mcmd_func)(avd - Subcmd_argv, Subcmd_argv) );
}

