/*From:	SMTP%"john@argent-software.com" 28-AUG-1997 18:37:34.30
To:	EVERHART@GCE.COM
CC:	
Subj:	CDWRITE20_VMS.C source code

Return-Path: john@argent-software.com
Received: by arisia.gce.com (UCX V4.1-12C, OpenVMS V7.1 VAX);
	Thu, 28 Aug 1997 18:34:27 -0400
Received: from grape.Argent-Software.com ([205.133.96.3]) by bort.mv.net (8.8.5/mem-951016) with SMTP id SAA05151 for <EVERHART@GCE.COM>; Thu, 28 Aug 1997 18:12:18 -0400 (EDT)
From: john@Argent-Software.com
Received: by Argent-Software.com (MX V4.2 VAX) id 3; Thu, 28 Aug 1997 18:11:54
          EST
Date: Thu, 28 Aug 1997 18:11:40 EST
To: EVERHART@GCE.COM
Message-ID: <009B9766.4BB83D5C.3@Argent-Software.com>
Subject: CDWRITE20_VMS.C source code
*/
#ifdef __DECC
#pragma module CDWRITER "V02-03"
#else
#module CDWRITER "V02-03"
#endif

/* Copyright 1994 Yggdrasil Computing, Inc. */
/* Written by Adam J. Richter (adam@yggdrasil.com) */

/* Rewritten February 1997 by Eberhard Heuser-Hofmann*/
/* using the OpenVMS generic scsi-interface */
/* see the README-file, how to setup your machine */

/*
**  Modified March 1997 by John Vottero to use overlapped, async I/O
**  and lots of buffers to help prevent buffer underruns.
**  Also improved error reporting.
*/

/* This file may be copied under the terms and conditions of version 2
   of the GNU General Public License, as published by the Free
   Software Foundation (Cambridge, Massachusetts).

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

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* The second notice comes from sys$examples:gktest.c (OpenVMS 7.0)*/

/*
** COPYRIGHT (c) 1993 BY
** DIGITAL EQUIPMENT CORPORATION, MAYNARD, MASSACHUSETTS.
** ALL RIGHTS RESERVED.
**
** THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND COPIED
** ONLY  IN  ACCORDANCE  OF  THE  TERMS  OF  SUCH  LICENSE  AND WITH THE
** INCLUSION OF THE ABOVE COPYRIGHT NOTICE. THIS SOFTWARE OR  ANY  OTHER
** COPIES THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY
** OTHER PERSON.  NO TITLE TO AND  OWNERSHIP OF THE  SOFTWARE IS  HEREBY
** TRANSFERRED.
**
** THE INFORMATION IN THIS SOFTWARE IS	SUBJECT TO CHANGE WITHOUT NOTICE
** AND	SHOULD	NOT  BE  CONSTRUED  AS A COMMITMENT BY DIGITAL EQUIPMENT
** CORPORATION.
**
** DIGITAL ASSUMES NO RESPONSIBILITY FOR THE USE  OR  RELIABILITY OF ITS
** SOFTWARE ON EQUIPMENT WHICH IS NOT SUPPLIED BY DIGITAL.
*/

/*
**
**  INCLUDE FILES
**
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>
#include <iodef.h>
#include <signal.h>

#include <ssdef.h>
#include <descrip.h>
#include <rms.h>
#include <dvidef.h>
#include <starlet.h>
#include <lib$routines.h>

/*
**  Turn off data alignment on Alpha
*/
#ifdef __ALPHA
#pragma nomember_alignment
#endif

/*
**
**  MACRO DEFINITIONS
**
*/
#define dprintf if (debug_mode) (void)printf

/*
**  Symbolic Parameters
*/
#define LOAD 0
#define UNLOAD 1

#define START 1
#define STOP 0

/*
**  VMS Status evaluation macros
*/
#define status_is_ok(sts) ((sts) & 1)
#define status_is_bad(sts) (!((sts) & 1))
#define insure_ok(sts) if (!((sts) & 1)) {(void)lib$signal((sts)); sys$exit((sts));}

/*
**  Event flag numbers
*/
#define buffers_are_empty 2
#define writes_are_done 3
#define GK_EFN 4

/*
**  SCSI command opcodes
*/
#define TEST_UNIT_READY		0x00
#define REZERO_UNIT		0x01
#define REQUEST_SENSE		0x03
#define WRITE_6			0x0A
#define INQUIRY			0x12
#define MODE_SELECT		0x15
#define START_STOP		0x1B
#define PREVENT_ALLOW_MEDIUM_REMOVAL 0x1E
#define READ_CAPACITY		0x25
#define SYNCRONIZE_CACHE	0x35

/*
**  Philips specific SCSI-commands
*/
#define READ_DISC_ID	  0xD1
#define READ_OPC	  0xD2
#define WRITE_OPC	  0xD3
#define FIRST_WRITEABLE_ADDRESS 0xE2
#define FORMAT_TRACK	  0xE3
#define RESERVE_TRACK	  0xE4
#define READ_TRACK_INFO   0xE5
#define WRITE_TRACK	  0xE6
#define LOAD_UNLOAD	  0xE7
#define FINISH_TRACK	  0xE8
#define FIXATION	  0xE9	  /* Write table of contents */
#define SEND_ABSORPTION_CONTROL_ERRORS 0xEB
#define RECOVER		  0xEC
#define WRITE_SESSION	  0xED
#define READ_SESSION_INFO 0xEE

#define CD_BLOCK_SIZE	  2048

/*
** SCSI definitions:
**
** Ideally, these definitions should come from a header file provided
** with the system.  At the time that this example was written and at
** the time of last update, no such file was available. For now, we
** define right here fields we need from the SCSI descriptor for this
** example; this should be replaced with the appropriate #include,
** should such a header file become available.	The reader should note
** that some of the field names and types in that header file may
** differ slightly from what's shown here; when and if the header file
** becomes available, code which does depend on the names should use
** the appropriate header file names.  Code which depends on getting
** the types right may need to re-cast these members when referencing
** them.
*/

/* Generic SCSI command descriptor */

struct SCSI$DESC {
    unsigned int    SCSI$L_OPCODE;	     /* SCSI Operation Code */
    unsigned int    SCSI$L_FLAGS;	     /* SCSI Flags Bit Map */
    char	* SCSI$A_CMD_ADDR;	     /* ->SCSI command buffer */
    unsigned int	SCSI$L_CMD_LEN;      /* SCSI command length, bytes */
    char	* SCSI$A_DATA_ADDR;	     /* ->SCSI data buffer */
    unsigned int	SCSI$L_DATA_LEN;     /* SCSI data length, bytes */
    unsigned int	SCSI$L_PAD_LEN;      /* SCSI pad length, bytes */
    unsigned int	SCSI$L_PH_CH_TMOUT;  /* SCSI phase change timeout, sec*/
    unsigned int	SCSI$L_DISCON_TMOUT; /* SCSI disconnect timeout, sec */
    unsigned int	SCSI$L_RES_1;	     /* Reserved */
    unsigned int	SCSI$L_RES_2;	     /* Reserved */
    unsigned int	SCSI$L_RES_3;	     /* Reserved */
    unsigned int	SCSI$L_RES_4;	     /* Reserved */
    unsigned int	SCSI$L_RES_5;	     /* Reserved */
    unsigned int	SCSI$L_RES_6;	     /* Reserved */
    } ;

/* SCSI Input/Output Status Block */

#ifdef __ALPHA
#pragma member_alignment save
#pragma nomember_alignment
#endif

struct SCSI$IOSB {
    unsigned short int	SCSI$W_VMS_STAT;	    /* VMS status code */
    unsigned long int	SCSI$L_IOSB_TFR_CNT; /* Actual #bytes transferred */
    char	SCSI$B_IOSB_FILL_1;
    unsigned char	SCSI$B_IOSB_STS;		/* SCSI device status */
    };

#ifdef __ALPHA
#pragma member_alignment restore
#endif

/*
**  SCSI status codes
*/
#define SCSI$K_GOOD 0
#define SCSI$K_CHECK_CONDITION 0x2
#define SCSI$K_CONDITION_MET 0x4
#define SCSI$K_BUSY 0x8
#define SCSI$K_INTERMEDIATE 0x10
#define SCSI$K_INTERMEDIATE_C_MET 0x14
#define SCSI$K_RESERVATION_CONFLICT 0x18
#define SCSI$K_COMMAND_TERMINATED 0x22
#define SCSI$K_QUEUE_FULL 0x28

/*
**  SCSI flag field constants
*/
#define SCSI$K_WRITE		0x0	/* direction of transfer=write */
#define SCSI$K_READ		0x1	/* direction of transfer=read */
#define SCSI$K_FL_ENAB_DIS	0x2	/* enable disconnects */
#define SCSI$K_FL_ENAB_SYNC	0x4	/* enable sync */

struct SCSI_SENSE_DATA
    {
    unsigned error_code : 7;
    unsigned valid : 1;
    unsigned char segment;
    unsigned sense_key : 4;
    unsigned reserved1 : 1;
    unsigned ILI : 1;	    /* Incorrect Length Indicator */
    unsigned EOM : 1;	    /* End of Medium */
    unsigned filemark : 1;
    unsigned char info[4];		    /* Big Endian !! */
    unsigned char additional_sense_length;
    unsigned char cmd_info[4];	    /* Big Endian !! */
    unsigned char additional_sense_code;
    unsigned char additional_sense_code_qualifier;
    unsigned char FRU_code;
    unsigned bit_pointer : 3;
    unsigned bit_pointer_valid : 1;
    unsigned reserved2 : 2;
    unsigned command_data : 1;
    unsigned sense_key_specific_valid : 1;
    unsigned char field_pointer_msb;
    unsigned char field_pointer_lsb;
    unsigned char additional_bytes[31];
    };

/* end of SCSI definitions */



/*
**  Global Static data
*/
static int debug_mode;
static void** buf;
static int buffer_size;
static int blocks_per_buffer;
static int buffer_count;
static int read_threshold;
static int available_buffers;
static int next_empty;		/* The next buffer available for the source */
static int next_full;		/* The next buffer which needs to be written */
static int end_of_file;
static int final_write_size;	/* Size of the final write, if different */
static int started_writing;	/* For the condition handler */
static int started_unit;	/* For the condition handler */
static int old_priority;	/* Saved for condition handler */

static unsigned short gk_channel;
static struct FAB srcfab;	/* Declare FAB and RAB for the file */
static struct RAB srcrab;
static struct XABFHC srcxabfhc;
static struct SCSI_SENSE_DATA sense_data;

static struct cdr_write_cmd
    {
    char opcode;
    unsigned lba_msb : 5;
    unsigned LUN : 3;
    unsigned char lba;
    unsigned char lba_lsb;
    unsigned char transfer_length;
    unsigned linked : 1;
    unsigned flag : 1;
    unsigned reserved1 : 4;
    unsigned mixed_fmt : 1;
    unsigned reserved2 : 1;
    } cdr_write_command;
static struct SCSI$DESC cdr_write_desc;
static struct SCSI$IOSB cdr_write_iosb;

/*
**  Statistics
*/
static int buffer_reads;
static int buffer_writes;
static int all_buffers_full;
static int max_empty_buffers;
static int max_empty_after_initial;
static int size_in_blocks;	   /* Size of CD we're cooking, in VMS blocks */
static int size_in_buffers;	   /* Size of CD in buffers */
static long start_time[2];	   /* Time at which timing started */
static long last_stat_time[2];	   /* Time at which we printed last estimate */
static int last_buffer_writes;	   /* buffer_writes last time we printed */
static int status_interval;	   /* Number of writes between status messages */
static int write_toc;
static int speed_factor;
static int fixation_time;	   /* Seconds to fixate the disk, calculated */
#define FIXATION_BASE (250)	   /* Seconds to fixate the disk at single */
				   /* speed, empirical */


/*
**  Function Prototypes
*/

/*
**  Read and Write Routines
*/
static void fill_source_buffers(void);
static void issue_cdr_write(void);
static int  cdr_write_ast(void);

/*
**  Standard SCSI Operations
*/
static void test_unit_ready(void);
static void rezero_unit(void);
static void request_sense(void);
static void inquiry(void);
static void start_stop_unit(
    int start_it);
static void allow_removal(
    int removal_allowed);
static void sync_cache(void);

/*
**  Philips CDD2600 Specific Operations
*/
static void mode_select_x23(
    int speed,
    int dummy_write);
static void write_track(void);
static void load_unload(
    int unload_it);
static void fixation(void);
static void send_absorption_control_errors(void);

/*
**  Support Routines
*/
static int busy_scsi_status (
    unsigned char scsi_status);
static void check_scsi_status (
    unsigned char scsi_status);
static void log_scsi_status (
    unsigned char scsi_status,
    char *log_text);
static void print_sense_data(
    struct SCSI_SENSE_DATA *sense_data);
static unsigned int cdwrite_handler(
    void *sigargs,
    void *mechargs);
static unsigned long Seconds_Since(long *First, long *Second);
static int seconds_to_write(int buffers_to_write);


/*
**  Main Entry Point - Write a CD-R
*/
int main (
    int argc,
    char **argv )
{
    int argi;
    int dummy_write;
    int open_door;
    int close_door;
    int unlock_door;
    int unload_when_done;
    int priority;
    int rms_status;
    char *default_device = "CDWRITE_DEVICE";
    char *source = NULL;
    char *device = NULL;
    struct dsc$descriptor gk_device_d;
    long Now[2], Elapsed_Time;

    /*
    **	Set defaults
    */
    debug_mode = FALSE;
    speed_factor = 2;
    dummy_write = TRUE;
    buffer_count = 32;
    read_threshold = 0;
    blocks_per_buffer = 25;
    write_toc = TRUE;
    open_door = FALSE;
    close_door = FALSE;
    unlock_door = FALSE;
    unload_when_done = FALSE;
    final_write_size = 0;
    started_writing = FALSE;
    started_unit = FALSE;
    priority = 0;   /* Don't set */
    old_priority = 0;
    status_interval = -1;

    sense_data.valid = FALSE;


    /*
    **  Parse the command line
    */
    for(argi=1; argi<argc; argi++)
	{
	if (strcmp(argv[argi], "-write") == 0)
	    {
	    dummy_write = FALSE;
	    }
	else if (strcmp(argv[argi], "-speed") == 0)
	    {
	    argi++;
	    if (argi<argc)
		{
		speed_factor = atoi(argv[argi]);
		switch (speed_factor)
		    {
		    case 1:
		    case 2:
			{
			break;
			}
		    default:
			{
			fprintf (stderr, "%s is not a support speed factor\n",
			    argv[argi]);
			exit(1);
			}
		    }
		}
	    else
		{
		/*
		**  They specified -speed without anything following
		*/
		fprintf (stderr, "-speed must be followed by a supported speed.\n");
		exit(1);
		}
	    }
	else if (strcmp(argv[argi], "-bpb") == 0)
	    {
	    argi++;
	    if (argi<argc)
		{
		blocks_per_buffer = atoi(argv[argi]);
		}
	    else
		{
		/*
		**  They specified -bpb without anything following
		*/
		fprintf (stderr, "-bpb must be followed by a number.\n");
		exit(1);
		}
	    if ((blocks_per_buffer < 1) || (blocks_per_buffer > 31))
		{
		fprintf (stderr, "Blocks per buffer must be > 0 and < 32\n");
		exit(1);
		}
	    }
	else if (strcmp(argv[argi], "-bc") == 0)
	    {
	    argi++;
	    if (argi<argc)
		{
		buffer_count = atoi(argv[argi]);
		}
	    else
		{
		/*
		**  They specified -bc without anything following
		*/
		fprintf (stderr, "-bc must be followed by a number.\n");
		exit(1);
		}
	    if (buffer_count < 2)
		{
		fprintf (stderr, "Buffer count must be > 2\n");
		exit(1);
		}
	    }
	else if (strcmp(argv[argi], "-priority") == 0)
	    {
	    argi++;
	    if (argi<argc)
		{
		priority = atoi(argv[argi]);
		}
	    else
		{
		/*
		**  They specified -priority without anything following
		*/
		fprintf (stderr, "-priority must be followed by a number.\n");
		exit(1);
		}
	    }
	else if (strcmp(argv[argi], "-empty") == 0)
	    {
	    argi++;
	    if (argi<argc)
		{
		read_threshold = atoi(argv[argi]);
		}
	    else
		{
		/*
		**  They specified -empty without anything following
		*/
		fprintf (stderr, "-empty must be followed by a number.\n");
		exit(1);
		}
	    }
	else if (strcmp(argv[argi], "-status") == 0)
	    {
	    argi++;
	    if (argi<argc)
		{
		status_interval = atoi(argv[argi]);
		}
	    else
		{
		/*
		**  They specified -ststus without anything following
		*/
		fprintf (stderr, "-status must be followed by a number.\n");
		exit(1);
		}
	    }
	else if (strcmp(argv[argi], "-debug") == 0)
	    {
	    debug_mode = TRUE;
	    }
	else if (strcmp(argv[argi], "-notoc") == 0)
	    {
	    write_toc = FALSE;
	    }
	else if (strcmp(argv[argi], "-unload") == 0)
	    {
	    unload_when_done = TRUE;
	    }
	else if (strcmp(argv[argi], "-open") == 0)
	    {
	    open_door = TRUE;
	    if (argc > 2)
		{
		fprintf(stderr,
		    "\n-open option overrides all others\n");
		}
	    }
	else if (strcmp(argv[argi], "-close") == 0)
	    {
	    close_door = TRUE;
	    if (argc > 2)
		{
		fprintf(stderr,
		    "\n-close option overrides all others\n");
		}
	    }
	else if (strcmp(argv[argi], "-unlock") == 0)
	    {
	    unlock_door = TRUE;
	    if (argc > 2)
		{
		fprintf(stderr,
		    "\n-unlock option overrides all others\n");
		}
	    }
	else if ((strcmp(argv[argi], "-help") == 0)
	       ||(strcmp(argv[argi], "-h") == 0)
	       ||(strcmp(argv[argi], "-?") == 0))
	    {
	    fprintf (stderr,
		"Usage: cdwrite [options] source [burner-device]\nOptions are:\n");
	    fprintf (stderr,
		"    -speed 1|2  (Single speed or double speed)\n");
	    fprintf (stderr,
		"    -write      (Really write, otherwise it's only a test)\n");
	    fprintf (stderr,
		"    -debug      (Debug mode, displays more output)\n");
	    fprintf (stderr,
		"    -bpb n      (Blocks Per Buffer, default is 25)\n");
	    fprintf (stderr,
		"    -bc n       (Buffer Count, default is 32)\n");
	    fprintf (stderr,
		"    -empty n    (Sets the empty buffer read threshold,\n                 default is the minimum of half the buffers or 4)\n");
	    fprintf (stderr,
		"    -status n   (Write status every n buffers)\n");
	    fprintf (stderr,
		"    -notoc      (Does NOT write table of contents)\n");
	    fprintf (stderr,
		"    -priority n (Specify VMS priority)\n");
	    fprintf (stderr,
		"    -unload     (Unloads when done)\n");
	    fprintf (stderr,
		"    -open       (Opens the door)\n");
	    fprintf (stderr,
		"    -close      (Closes the door)\n");
	    fprintf (stderr,
		"    -unlock     (Unlocks the door)\n");
	    fprintf (stderr,
		"    -help       (Display this message)\n");
	    fprintf (stderr,
		"\n    The source can be a disk image file or a device\n");
	    fprintf (stderr,
		"    The burner device defaults to CDWRITE_DEVICE\n");
	    exit(0);
	    }
	else
	    {
	    if (argv[argi][0] == '-')
		{
		/*
		**  This is an option but not a valid one
		*/
		fprintf (stderr, "%s is not a recognized option.\n",
		    argv[argi]);
		exit(1);
		}
	    else if (source == NULL)
		{
		/*
		**  This must be the source specification
		*/
		source = argv[argi];
		}
	    else if (device == NULL)
		{
		/*
		**  This must be the device specification
		*/
		device = argv[argi];
		}
	    else
		{
		/*
		**  This is not an option and we already have a source
		**  and device specified so something is wrong
		*/
		fprintf (stderr, "Command line error, %s is not an option and both\nsource and device have already been specified.\n",
		    argv[argi]);
		exit(1);
		}
	    }
	}

    /*
    **  If they used the -open, -close or -unlock options
    **  we don't have a source
    */
    if ((open_door)
	|| (close_door)
	|| (unlock_door))
	{
	if ((source != NULL) && (device != NULL))
	    {
	    fprintf (stderr, "Command line error, source is not valid with -open, -close and -unlock.\n",
		argv[argi]);
	    exit(1);
	    }
	device = source;
	source = NULL;
	}

    /*
    **  If they didn't specify a device, take the default
    */
    if (device == NULL)
	{
	device = default_device;
	}

    /*
    **  Sanity check on read threshold vs buffer count
    */
    if (read_threshold > buffer_count)
	{
	fprintf (stderr,
	    "Empty buffer threshold can't be greater than buffer count.\n");
	exit(1);
	}

    /*
    **	Assign a channel to the CD-R device
    */
    dprintf("Assigning channel\n");
    gk_device_d.dsc$w_length = strlen(device);
    gk_device_d.dsc$b_dtype = DSC$K_DTYPE_T;
    gk_device_d.dsc$b_class = DSC$K_CLASS_S;
    gk_device_d.dsc$a_pointer = device;
    rms_status = sys$assign (
	&gk_device_d,
	&gk_channel,
	0,
	0);
    insure_ok(rms_status);

    /*
    **	If we are just opening, closing or unlocking, do it and exit
    */
    if (open_door)
	{
	load_unload(UNLOAD);
	sys$dassgn(gk_channel);
	sys$exit(SS$_NORMAL);
	}

    if (close_door)
	{
	load_unload(LOAD);
	rezero_unit();
	sys$dassgn(gk_channel);
	sys$exit(SS$_NORMAL);
	}

    if (unlock_door)
	{
	allow_removal(TRUE);
	rezero_unit();
	sys$dassgn(gk_channel);
	sys$exit(SS$_NORMAL);
	}

    /*
    **  If we have gotten this far, make sure they specified the source
    */
    dprintf("Source is %s\nDestination is %s\n",
	source,
	device);
    if (source == NULL)
	{
	fprintf (stderr,
	    "Usage: cdwrite [options] source [burner-device]\n\t-help for more information\n");
	exit(1);
	}

    /*
    **	Open the Source
    */
    srcfab = cc$rms_fab;
    srcfab.fab$b_fac = FAB$M_GET | FAB$M_BIO;

    srcfab.fab$l_fna = source;
    srcfab.fab$b_fns = strlen(source);

    srcfab.fab$l_fop = FAB$M_SQO;   /* Sequential only */
		 /*  | FAB$M_NFS;   /* Non file structured */

    srcfab.fab$b_shr = FAB$M_SHRGET;
    srcfab.fab$l_xab = (void *) &srcxabfhc;
    srcxabfhc = cc$rms_xabfhc;

    rms_status = sys$open (&srcfab, NULL, NULL);
    insure_ok(rms_status);

    size_in_blocks = srcxabfhc.xab$l_ebk-1;
    if (srcxabfhc.xab$w_ffb)
	size_in_blocks++;

    srcrab = cc$rms_rab;
    srcrab.rab$l_fab = &srcfab;
    srcrab.rab$l_rop = RAB$M_BIO;
    rms_status = sys$connect(&srcrab, NULL, NULL);
    insure_ok(rms_status);

    /*
    **  Figure out the size of the source
    */
    if (size_in_blocks <= 0)
	{
	/*
	**  It must be a device
	*/
	struct dsc$descriptor device_d;

	device_d.dsc$w_length = strlen(source);
	device_d.dsc$b_dtype = DSC$K_DTYPE_T;
	device_d.dsc$b_class = DSC$K_CLASS_S;
	device_d.dsc$a_pointer = source;
	lib$getdvi (
            &DVI$_MAXBLOCK, 
            0, 
            &device_d, 
            &size_in_blocks, 
            0, 
            0);
	}

    size_in_buffers = ((((size_in_blocks +
			(CD_BLOCK_SIZE/512 - 1)) / (CD_BLOCK_SIZE/512)) +
			(blocks_per_buffer - 1)) / blocks_per_buffer);

    dprintf("Input is %d blocks, %d buffers\n",
	size_in_blocks,
	size_in_buffers);

    /*
    **	Make sure the CD-R is ready
    */
    test_unit_ready();

    /*
    **	Put the CD-R into a known state.
    */
    rezero_unit ();

    /*
    **	Say hello to the CD-R
    */
    inquiry();

    /*
    **	Make sure the CD-R is ready
    */
    test_unit_ready();

    /*
    **	Set the condition handler (to unlock the door if we fail)
    */
    VAXC$ESTABLISH(cdwrite_handler);

    /*
    **	Disable the "Open" button
    */
    allow_removal(FALSE);

    /*
    **	Tell the CD-R to get ready for media access
    */
    start_stop_unit (START);
    started_unit = TRUE;

    /*
    **	Set speed and emulation mode
    */
    mode_select_x23(speed_factor,dummy_write);

    /*
    **
    **	Start of real writing
    **
    **	Here's the plan:
    **
    ** 1. Read from the source and fill all of the buffers.
    **
    ** 2. Clear the buffers-are-empty event flag.
    **
    ** 3. Issue an async write to the CD-R with a completion AST
    **	  3.1 The completion AST:
    **	      - Issues another write
    **	      - Increments the empty buffer count
    **	      - If the empty buffer count is high enough,
    **		set the buffers-are-empty event flag
    **
    ** 4. Beginning of writing loop
    **
    **	  5. Wait for the buffers-are-empty event flag to be set.
    **
    **	  6. Read source to refill empty buffers (sync read)
    **
    **	  7. Loop until we have reached the end of the source
    **
    ** 8. Wait until all buffers are empty.
    **
    **
    */

    /*
    **  Calculate the time to write the table of contents
    */
    fixation_time = FIXATION_BASE / speed_factor;

    /*
    **	Initialize our buffer counters etc.
    */
    buffer_size = CD_BLOCK_SIZE * blocks_per_buffer;
    available_buffers = buffer_count;
    if (read_threshold == 0)
	{
	read_threshold = buffer_count / 2;
	if (read_threshold > 4) read_threshold = 4;
	}
    next_empty = 0;
    next_full = 0;
    end_of_file = FALSE;
    fprintf (stderr,
	"%d buffers of %d bytes requires %d pages of memory\n",
	buffer_count,
	buffer_size,
	((buffer_count * buffer_size) / 512));

    /*
    **  Set the statistics interval
    */
    if (status_interval == 0)
	{
	/*
	**  They specified 0 which means never
	*/
	status_interval = size_in_buffers + 1;
	}
    else if (status_interval < 0)
	{
	/*
	**  They didn't specify default to 5% or 2 minutes
	**  whichever is more frequent
	**
	**  150KB/s * 1024 * 120 = Number of bytes in 2 minutes = 18432000
	*/
	status_interval = (150 * 1024 * 120 * speed_factor) / buffer_size;
	dprintf("2 minute status interval is %d buffers\n",
	    status_interval);
	if (status_interval > (size_in_buffers / 20))
	    {
	    status_interval = size_in_buffers / 20;
	    dprintf("20%% status interval is %d buffers\n",
		status_interval);
	    }
	if (status_interval == 0) status_interval = size_in_buffers + 1;
	}

    /*
    **	Print an estimate of how long these buffers will last
    **	At single speed, a CD-R will write 150KB per second.
    */
    fprintf (stderr,
	"With the selected speed factor of %d (%dKB/s)\nthese buffers will last for about %d seconds.\n",
	speed_factor,
	(speed_factor * 150),
	seconds_to_write(buffer_count));
    fprintf (stderr,
	"The source is %d blocks which will take about %d:%02d to write\n",
	size_in_blocks,
	(seconds_to_write(size_in_buffers) / 60),
	(seconds_to_write(size_in_buffers) % 60));
    if (write_toc)
	{
	fprintf (stderr,
	    "plus %d:%02d to write the table of contents\n",
	    (fixation_time / 60),
	    (fixation_time % 60));
	}

    /*
    **	Allocate the buffers
    **	First we allocate an array of pointers to our buffers
    **	then we fill that array with pointers to the actual buffers
    */
    buf = malloc((sizeof(void*) * buffer_count));
    for(available_buffers = 0;
	available_buffers < buffer_count;
	available_buffers++)
	{
	buf[available_buffers] = malloc(buffer_size);
	if (buf[available_buffers] == NULL)
	    {
	    fprintf (stderr,
		"Unable to allocate memory at buffer number %d\n",
		available_buffers);
	    sys$exit(SS$_ABORT);
	    }
	}

    /*
    **	Initialize statistics
    */
    lib$init_timer (0);
    buffer_reads = 0;
    buffer_writes = 0;
    all_buffers_full = 0;

    /*
    **	Fill all of the buffers
    */
    fill_source_buffers();
    max_empty_after_initial = 0;
    max_empty_buffers = 0;

    /*
    **  Set event flags
    */
    sys$clref(buffers_are_empty);
    sys$clref(writes_are_done);

    /*
    **	If requested, elevate our priority...
    **
    */
    if (priority) {
	rms_status = sys$setpri(0, 0, priority, &old_priority, 0, 0);
	insure_ok(rms_status);
    }

    /*
    **	Tell the CD-R we are going to start writing a track
    **	This begins the writing process, the only commands available
    **	now are: WRITE, REQUEST_SENSE, TEST_UNIT_READY and FLUSH_CACHE
    **	The FLUSH_CACHE command ends the writing process.
    */
    write_track();
    started_writing = TRUE;

    /*
    **	Start the timers...
    **/
    (void) sys$gettim(start_time);
    last_stat_time[0] = start_time[0];
    last_stat_time[1] = start_time[1];

    /*
    **	Issue CD-R Write
    **
    **	This is an async write which has a completion AST which
    **	issues another write.  It keeps doing this until all buffers
    **	are empty and hopefully, all of the buffers are empty because
    **	we have reached the end of the source, not because we haven't
    **	been reading the source fast enough.
    */
    issue_cdr_write();

    /*
    **	Read loop
    **
    **	This loop keeps filling buffers which are being emptied in the
    **	background by the issue_cdr_write/cdr_write_ast routines.
    */
    while(!end_of_file)
	{
	/*
	**  Wait until we have buffers to fill
	*/
	sys$waitfr(buffers_are_empty);
	sys$clref(buffers_are_empty);

	/*
	**  Start filling buffers again
	*/
	dprintf("\n");
	fill_source_buffers();
	all_buffers_full++;
	}

    /*
    **  We are nearly done, we have to wait for all of the buffers to be
    **  flushed to the CD-R
    */
    dprintf("\nEnd of Source, waiting for buffers to empty\n");
    sys$waitfr(writes_are_done);
    dprintf("\n");

    /*
    **	Tell the CD to flush its cache to disk
    */
    sync_cache();
    started_writing = FALSE;

    /*
    **	Write TOC
    */
    if (write_toc)
	{
	/*
	 *  Make a final report before begining fixation...
	 */
	int Time_Left = fixation_time;
	int Time_Running;

	(void) sys$gettim(Now);
	Time_Running = Seconds_Since(Now, start_time);

	fprintf(stderr,"%d%% done; estimate %d:%02d left, writing TOC\n",
	  (int)( (double) Time_Running * 100.0 / (double) (Time_Running + Time_Left)),
	  Time_Left / 60, Time_Left % 60);

	fixation();
	}

    /*
    **	Make a final report...
    */
    (void) sys$gettim(Now);
    Elapsed_Time = Seconds_Since(Now, start_time);
    if (Elapsed_Time == 0) Elapsed_Time = 1;
    fprintf(stderr,"Wrote %dMB, %d seconds, %u KB/sec\n",
	size_in_blocks / 2048, Elapsed_Time, (unsigned int ) ((double)size_in_blocks / 2.0 / (double) Elapsed_Time));

    /*
    **	If we elevated, go back...
    */
    if (old_priority) {
	rms_status = sys$setpri(0, 0, old_priority, 0, 0, 0);
	insure_ok(rms_status);
    }

    /*
    **	Close the source
    */
    rms_status = sys$close(&srcfab);
    insure_ok(rms_status);

    /*
    **	Print statistics
    */
    fprintf(stderr, "\n%d reads of %d bytes\n",
	buffer_reads,
	buffer_size);
    if ((blocks_per_buffer * CD_BLOCK_SIZE) != buffer_size)
	{
	/*
	**  This means that the blocks_per_buffer was changed to the
	**  size of the final read
	*/
	fprintf(stderr, "and one read of %d bytes\n",
	    (blocks_per_buffer * CD_BLOCK_SIZE));
	}
    fprintf(stderr, "%d writes\n", buffer_writes);
    fprintf(stderr, "Maximum of %d empty buffers out of %d available\n",
	max_empty_buffers,
	buffer_count);
    fprintf(stderr, "Maximum of %d empty buffers after initial fill of CD-R\n",
	max_empty_after_initial);
    fprintf(stderr, "We did not start refilling buffers until %d were empty.\n",
	read_threshold);
    fprintf(stderr, "We had all of the buffers full %d times\n\n",
	all_buffers_full);
    lib$show_timer (0, &(int)1, 0, 0);
    lib$show_timer (0, &(int)2, 0, 0);
    lib$show_timer (0, &(int)3, 0, 0);
    lib$show_timer (0, &(int)4, 0, 0);
    lib$show_timer (0, &(int)5, 0, 0);

    /*
    **	Show any SUSPECTED errors
    */
    send_absorption_control_errors();

    /*
    **	Tell the CD-R that we are done dinking with the media
    */
    start_stop_unit(STOP);
    started_unit = FALSE;

    /*
    **	Unlock the front door
    */
    allow_removal(TRUE);

    /*
    **	If asked to, open the pod bay door
    */
    if (unload_when_done)
	{
	load_unload(UNLOAD);
	}

    /*
    **	Deassign the CD-R Channel
    */
    sys$dassgn(gk_channel);

    return;
}


/*
**  Read and Write Routines
*/

/*
**  FILL_SOURCE_BUFFERS - Read blocks from the source to fill buffers.
**			  Keep reading until all buffers are full which may
**			  never happen since an async write operation is
**			  going on which is trying to empty these buffers.
*/
static void fill_source_buffers()
{
    int rms_status;

    /*
    ** Fill all of the buffers
    */
    while(available_buffers > 0)
	{
	/*
	**  Check max statistics
	*/
	if (available_buffers > max_empty_buffers)
	    max_empty_buffers = available_buffers;
	if ((available_buffers > max_empty_after_initial)
	    && (all_buffers_full > 0))
	    max_empty_after_initial = available_buffers;

	/*
	**  Statistics...
	*/
	if (buffer_writes > (last_buffer_writes + status_interval))
	    {
	    long Now[2];
	    int Time_Left;
	    int Time_Running;

	    sys$gettim(Now);

	    Time_Left = seconds_to_write((size_in_buffers - buffer_writes)) +
			(write_toc ? fixation_time : 0);
	    Time_Running = Seconds_Since(Now, start_time);

	    fprintf(stderr," %d%% done; estimate %d:%02d left\n",
	      (int)( (double) Time_Running * 100.0 / (double) (Time_Running + Time_Left)),
	      Time_Left / 60, Time_Left % 60);

	    last_buffer_writes = buffer_writes;
	    last_stat_time[0] = Now[0], last_stat_time[1] = Now[1];
	    }

	/*
	**  Set the buffer to point to the next available buffer
	*/
	srcrab.rab$l_rop = RAB$M_BIO;
	srcrab.rab$l_ubf = buf[next_empty];
	srcrab.rab$w_usz = buffer_size;

	/*
	**  Issue the read
	*/
	rms_status = sys$read(&srcrab, NULL, NULL);

	/*
	**  Make sure the read was OK
	*/
	if (rms_status == RMS$_EOF)
	    {
	    /*
	    **	Status is not good but it's a normal end of file
	    **	This also means that the source was an even multiple
	    **	of our buffer size.
	    */
	    end_of_file = TRUE;
	    break;
	    }
	else if (rms_status == RMS$_RER)
	    {
	    /*
	    **	This error usually means that we are reading a disk
	    **	as the source and we have reached the end of the disk.
	    **
	    **	There may be additional data to read if the disk size
	    **	is not an even multiple of the buffer size.  We will
	    **	read the last part of the disk one disk block at a time.
	    */
	    if (srcrab.rab$l_stv == SS$_ILLBLKNUM)
		{
		char *block_buf;
		int last_size;

		/*
		**  Set the buffer to point to the next available buffer
		*/
		block_buf = buf[next_empty];
		srcrab.rab$l_rop = RAB$M_BIO;
		srcrab.rab$l_ubf = block_buf;
		srcrab.rab$w_usz = 512;
		last_size = 0;

		/*
		**  Read the final blocks
		*/
		do
		    {
		    rms_status = sys$read(&srcrab, NULL, NULL);
		    if (status_is_ok(rms_status))
			{
			last_size += 512;
			block_buf += 512;
			}
		    if (last_size >= buffer_size)
			{
			/*
			**  What??  We just filled an entire buffer
			**  which should not have happened.
			*/
			fprintf(stderr,
			    "Read returned ILLBLKNUM when it wasn't at the end of the disk\n");
			lib$signal (SS$_ABORT);
			}
		    } while (status_is_ok(rms_status));

		/*
		**  Calculate the final Write size (in CD blocks)
		*/
		final_write_size = last_size / CD_BLOCK_SIZE;
		if ((last_size % CD_BLOCK_SIZE) > 0)
		    {
		    final_write_size++;
		    }
		if (final_write_size > 0)
		    {
		    fprintf(stderr,
			"The final write will be %d blocks\n",
			final_write_size);
		    }
		}
	    else
		{
		/*
		**  An unexpected error
		*/
		lib$signal(SS$_ABORT, 0, rms_status, srcrab.rab$l_stv);
		}
	    end_of_file = TRUE;
	    break;
	    }
	else
	    {
	    /*
	    **	It's not one of the expected errors, make sure it's ok
	    */
	    insure_ok(rms_status);

	    /*
	    **	If we got this far, the read was OK
	    **
	    **	Check the number of bytes read
	    **	It should always be the same as the buffer size
	    **	except for the last read.
	    */
	    if (srcrab.rab$w_rsz == buffer_size)
		{
		/*
		**  Just a normal read
		*/
		buffer_reads++;
		}
	    else
		{
		/*
		**  Must be the last read
		*/
		char last_buf[512];

		fprintf(stderr,
		    "Read only returned %d bytes\n",
		    srcrab.rab$w_rsz);

		/*
		**  Figure out how big the final write should be
		*/
		final_write_size = srcrab.rab$w_rsz / CD_BLOCK_SIZE;
		if ((srcrab.rab$w_rsz % CD_BLOCK_SIZE) > 0)
		    {
		    /*
		    **	We have a partial CD block, write a whole one
		    **	Note that I think this causes minor problems on the CD
		    **	If you do an anal/disk of the CD you get:
		    **
		    **	Analyze/Disk_Structure for _TIGHT$DKA400: started on 17-MAR-1997 13:30:36.62
		    **
		    **	%ANALDISK-W-CHKSCB, invalid storage control block, RVN 1
		    **	%ANALDISK-I-OPENQUOTA, error opening QUOTA.SYS
		    **	-SYSTEM-W-NOSUCHFILE, no such file
		    **	%ANALDISK-W-ALLOCCLR, blocks incorrectly marked allocated
		    **	    LBN 40002 to 40107, RVN 1
		    **	%ANALDISK-W-FREESPADRIFT, free block count of 13507 is incorrect (RVN 1);
		    **	    the correct value is 13613
		    **
		    **  But I haven't found a CD which didn't have this problem.
		    */
		    final_write_size++;
		    }
		fprintf(stderr,
		    "The final write will be %d blocks\n",
		    final_write_size);

		/*
		**  Clear the tail end of the buffer just so we
		**  don't confuse anyone reading the disk
		*/
		memset(
		    ((char*)buf[next_empty] + srcrab.rab$w_rsz),
		    '\0',
		    (buffer_size - srcrab.rab$w_rsz));

		/*
		**  Now read again and we had better get EOF
		*/
		srcrab.rab$l_rop = RAB$M_BIO;
		srcrab.rab$l_ubf = last_buf;
		srcrab.rab$w_usz = sizeof(last_buf);
		rms_status = sys$read(&srcrab, NULL, NULL);
		if (rms_status == RMS$_EOF)
		    {
		    /*
		    **	We had better get EOF
		    **	Note that we break out here so we don't
		    **	adjust the available_buffers counter which is
		    **	what we want, the last read will happen when the
		    **	main line checks to see if final_write_size is
		    **	non zero (which it is now).
		    */
		    end_of_file = TRUE;
		    break;
		    }
		else
		    {
		    /*
		    **	If we didn't get EOF here, we are in trouble
		    */
		    fprintf(stderr,
			"Short read was NOT at the end of the source!!\n");
		    }
		}
	    }

	/*
	**  Adjust buffer counters etc.
	*/
	sys$setast(0);	    /*	Disable AST's */
	next_empty++;
	if (next_empty >= buffer_count) next_empty = 0;
	available_buffers--;
	sys$setast(1);	    /*	Enable AST's */
	}

    return;
}


/*
**  ISSUE_CDR_WRITE - Send a buffer full of data to the CD-R.  This issues
**		      an async write with a completion AST.
*/
static void issue_cdr_write()
{
    int return_status;

    if (next_full == 0)
	{
	dprintf("*");
	}
    else
	{
	dprintf(".");
	}

    cdr_write_command.opcode = WRITE_6;
    cdr_write_command.lba_msb = 0;
    cdr_write_command.LUN = 0;
    cdr_write_command.lba = 0;
    cdr_write_command.lba_lsb = 0;
    cdr_write_command.transfer_length = blocks_per_buffer;
    cdr_write_command.linked = 0;
    cdr_write_command.flag = 0;
    cdr_write_command.reserved1 = 0;
    cdr_write_command.mixed_fmt = 0;
    cdr_write_command.reserved2 = 0;

    cdr_write_desc.SCSI$L_OPCODE = 1;
    cdr_write_desc.SCSI$L_FLAGS = SCSI$K_FL_ENAB_SYNC
			 | SCSI$K_WRITE
			 | SCSI$K_FL_ENAB_DIS;
    cdr_write_desc.SCSI$A_CMD_ADDR = (char*)&cdr_write_command;
    cdr_write_desc.SCSI$L_CMD_LEN = sizeof(cdr_write_command);
    cdr_write_desc.SCSI$A_DATA_ADDR = buf[next_full];
    cdr_write_desc.SCSI$L_DATA_LEN = (blocks_per_buffer * CD_BLOCK_SIZE);
    cdr_write_desc.SCSI$L_PAD_LEN = 0;
    cdr_write_desc.SCSI$L_PH_CH_TMOUT = 180;  /* SCSI phase change timeout, sec */
    cdr_write_desc.SCSI$L_DISCON_TMOUT = 180; /* SCSI disconnect timeout, sec */
    cdr_write_desc.SCSI$L_RES_1 = 0;
    cdr_write_desc.SCSI$L_RES_2 = 0;
    cdr_write_desc.SCSI$L_RES_3 = 0;
    cdr_write_desc.SCSI$L_RES_4 = 0;
    cdr_write_desc.SCSI$L_RES_5 = 0;
    cdr_write_desc.SCSI$L_RES_6 = 0;

    /*
    **	Issue the QIO
    */
    return_status = sys$qio (
			GK_EFN,
			gk_channel,
			IO$_DIAGNOSE,
			&cdr_write_iosb,
			cdr_write_ast,
			0, /* AST Param */
			&cdr_write_desc,
			sizeof(cdr_write_desc),
			0,  0,	0,  0);

    insure_ok(return_status);

    buffer_writes++;

    return;
}

static int cdr_write_ast()
{
    int ef_state;

    /*
    **	Make sure the write was OK
    */
    insure_ok(cdr_write_iosb.SCSI$W_VMS_STAT);
    check_scsi_status(cdr_write_iosb.SCSI$B_IOSB_STS);

    /*
    **	Adjust buffer counters etc.
    */
    next_full++;
    if (next_full >= buffer_count) next_full = 0;
    available_buffers++;

    /*
    **	See if we are done
    */
    if (available_buffers >= buffer_count)
	{
	/*
	**  We seem to be done, do we have a final write to do?
	*/
	if (final_write_size > 0)
	    {
	    blocks_per_buffer = final_write_size;
	    final_write_size = 0;
	    issue_cdr_write();
	    }
	else
	    {
	    /*
	    **	All of the buffers are empty, we had better be at the
	    **	end of the source file or we have just created a shiny
	    **	gold coaster.
	    */
	    if (!end_of_file)
		{
		fprintf (stderr,
		    "\nBuffers emptied before EOF\n");
		lib$signal (SS$_ABORT);
		}
	    sys$setef(writes_are_done);
	    }
	}
    else
	{
	/*
	**  Issue another write
	*/
	issue_cdr_write();
	}

    /*
    **	See if we need to wake the non-AST thread to read more...
    */
    if (available_buffers > read_threshold)
	{
	sys$setef(buffers_are_empty);
	}

    return SS$_NORMAL;
}



/*
**  Standard SCSI Operations
*/

/*
**  TEST_UNIT_READY - Make sure the CD-R is ready
*/
static void test_unit_ready()
{
    int return_status;
    int number_of_attempts;
    struct test_unit_ready_cmd
	{
	char opcode;
	unsigned reserved1 : 5;
	unsigned LUN : 3;
	char reserved2[3];
	char control;
	} scsi_command;
    struct SCSI$IOSB gk_iosb;
    struct SCSI$DESC gk_desc;

    dprintf("Testing for unit ready\n");

    scsi_command.opcode = TEST_UNIT_READY;
    scsi_command.reserved1 = 0;
    scsi_command.LUN = 0;
    scsi_command.reserved2[0] = 0;
    scsi_command.reserved2[1] = 0;
    scsi_command.reserved2[2] = 0;
    scsi_command.control = 0;

    gk_desc.SCSI$L_OPCODE = 1;
    gk_desc.SCSI$L_FLAGS = SCSI$K_FL_ENAB_SYNC
			 | SCSI$K_READ
			 | SCSI$K_FL_ENAB_DIS;
    gk_desc.SCSI$A_CMD_ADDR = (char*)&scsi_command;
    gk_desc.SCSI$L_CMD_LEN = sizeof(scsi_command);
    gk_desc.SCSI$A_DATA_ADDR = NULL;
    gk_desc.SCSI$L_DATA_LEN = 0;
    gk_desc.SCSI$L_PAD_LEN = 0;
    gk_desc.SCSI$L_PH_CH_TMOUT = 180;  /* SCSI phase change timeout, sec */
    gk_desc.SCSI$L_DISCON_TMOUT = 180; /* SCSI disconnect timeout, sec */
    gk_desc.SCSI$L_RES_1 = 0;
    gk_desc.SCSI$L_RES_2 = 0;
    gk_desc.SCSI$L_RES_3 = 0;
    gk_desc.SCSI$L_RES_4 = 0;
    gk_desc.SCSI$L_RES_5 = 0;
    gk_desc.SCSI$L_RES_6 = 0;


    number_of_attempts = 0;
    do
	{
	/*
	**  Issue the QIO
	*/
	return_status = sys$qiow (
			    GK_EFN,
			    gk_channel,
			    IO$_DIAGNOSE,
			    &gk_iosb,
			    0,
			    0,
			    &gk_desc,
			    sizeof(gk_desc),
			    0,	0,  0,	0);

	insure_ok(return_status);
	insure_ok(gk_iosb.SCSI$W_VMS_STAT);
	number_of_attempts++;
	if (number_of_attempts == 3)
	    {
	    /*
	    **  Let them know we are waiting
	    */
	    fprintf(stderr, "Waiting for CD-R to become ready...\n");
	    }
	} while (busy_scsi_status(gk_iosb.SCSI$B_IOSB_STS));

    check_scsi_status(gk_iosb.SCSI$B_IOSB_STS);

    return;
}


/*
**  REZERO_UNIT - Put the device into a known start.  The known state is
**		  vendor specific although the command to do it is part of
**		  the SCSI standard.
*/
static void rezero_unit()
{
    int return_status;
    struct rezero_unit_cmd
	{
	char opcode;
	unsigned reserved1 : 5;
	unsigned LUN : 3;
	char reserved2[3];
	char control;
	} scsi_command;
    struct SCSI$IOSB gk_iosb;
    struct SCSI$DESC gk_desc;

    dprintf("Rezero unit\n");

    scsi_command.opcode = REZERO_UNIT;
    scsi_command.reserved1 = 0;
    scsi_command.LUN = 0;
    scsi_command.reserved2[0] = 0;
    scsi_command.reserved2[1] = 0;
    scsi_command.reserved2[2] = 0;
    scsi_command.control = 0;

    gk_desc.SCSI$L_OPCODE = 1;
    gk_desc.SCSI$L_FLAGS = SCSI$K_FL_ENAB_SYNC
			 | SCSI$K_READ
			 | SCSI$K_FL_ENAB_DIS;
    gk_desc.SCSI$A_CMD_ADDR = (char*)&scsi_command;
    gk_desc.SCSI$L_CMD_LEN = sizeof(scsi_command);
    gk_desc.SCSI$A_DATA_ADDR = NULL;
    gk_desc.SCSI$L_DATA_LEN = 0;
    gk_desc.SCSI$L_PAD_LEN = 0;
    gk_desc.SCSI$L_PH_CH_TMOUT = 180;  /* SCSI phase change timeout, sec */
    gk_desc.SCSI$L_DISCON_TMOUT = 180; /* SCSI disconnect timeout, sec */
    gk_desc.SCSI$L_RES_1 = 0;
    gk_desc.SCSI$L_RES_2 = 0;
    gk_desc.SCSI$L_RES_3 = 0;
    gk_desc.SCSI$L_RES_4 = 0;
    gk_desc.SCSI$L_RES_5 = 0;
    gk_desc.SCSI$L_RES_6 = 0;

    do
	{
	/*
	**  Issue the QIO
	*/
	return_status = sys$qiow (
			    GK_EFN,
			    gk_channel,
			    IO$_DIAGNOSE,
			    &gk_iosb,
			    0,
			    0,
			    &gk_desc,
			    sizeof(gk_desc),
			    0,	0,  0,	0);
	} while (busy_scsi_status(gk_iosb.SCSI$B_IOSB_STS));

    insure_ok(return_status);
    insure_ok(gk_iosb.SCSI$W_VMS_STAT);
    check_scsi_status(gk_iosb.SCSI$B_IOSB_STS);

    return;
}


/*
**  REQUEST_SENSE - Ask a device to Sense information and try to make
**		    some sense out of it.
*/
static void request_sense()
{
    int return_status;

    struct request_sense_cmd
	{
	char opcode;
	unsigned reserved1 : 5;
	unsigned LUN : 3;
	char reserved2[2];
	char allocation_length;
	char control;
	} scsi_command;

    struct SCSI$IOSB gk_iosb;
    struct SCSI$DESC gk_desc;

    scsi_command.opcode = REQUEST_SENSE;
    scsi_command.reserved1 = 0;     /* Was 1 ??? */
    scsi_command.LUN = 0;
    scsi_command.reserved2[0] = 0;
    scsi_command.reserved2[1] = 0;
    scsi_command.allocation_length = sizeof(sense_data);
    scsi_command.control = 0;

    memset(&sense_data, '\0', sizeof(sense_data));

    gk_desc.SCSI$L_OPCODE = 1;
    gk_desc.SCSI$L_FLAGS = SCSI$K_FL_ENAB_SYNC
			 | SCSI$K_READ
			 | SCSI$K_FL_ENAB_DIS;
    gk_desc.SCSI$A_CMD_ADDR = (char*)&scsi_command;
    gk_desc.SCSI$L_CMD_LEN = sizeof(scsi_command);
    gk_desc.SCSI$A_DATA_ADDR = (char*)&sense_data;
    gk_desc.SCSI$L_DATA_LEN = sizeof(sense_data);
    gk_desc.SCSI$L_PAD_LEN = 0;
    gk_desc.SCSI$L_PH_CH_TMOUT = 180;  /* SCSI phase change timeout, sec */
    gk_desc.SCSI$L_DISCON_TMOUT = 180; /* SCSI disconnect timeout, sec */
    gk_desc.SCSI$L_RES_1 = 0;
    gk_desc.SCSI$L_RES_2 = 0;
    gk_desc.SCSI$L_RES_3 = 0;
    gk_desc.SCSI$L_RES_4 = 0;
    gk_desc.SCSI$L_RES_5 = 0;
    gk_desc.SCSI$L_RES_6 = 0;


    /*
    **	Issue the QIO
    */
    return_status = sys$qiow (
			GK_EFN,
			gk_channel,
			IO$_DIAGNOSE,
			&gk_iosb,
			0,
			0,
			&gk_desc,
			sizeof(gk_desc),
			0,  0,	0,  0);

    insure_ok(return_status);
    insure_ok(gk_iosb.SCSI$W_VMS_STAT);
    if (gk_iosb.SCSI$B_IOSB_STS != SCSI$K_GOOD)
	{
	/*
	**  We're getting errors on errors!!
	*/
	fprintf(stderr,
	    "Bad SCSI status returned to REQUEST_SENSE: %02.2x\n",
	    gk_iosb.SCSI$B_IOSB_STS);
	}

    return;
}


/*
**  INQUIRY - Send the SCSI INQUIRY command.
*/
static void inquiry()
{
    int return_status;
    int i;
    char trimmed_string[20];

    struct inquiry_cmd
	{
	char opcode;
	unsigned EVPD : 1;  /* Enable Vital Product Data */
	unsigned reserved1 : 4;
	unsigned LUN : 3;
	char page_code;
	char reserved2;
	char allocation_length;
	char control;
	} scsi_command;

    struct
	{
	unsigned device_type : 5;
	unsigned qualifier : 3;
	unsigned modifier : 7;
	unsigned RMB : 1;
	unsigned ANSI_version : 3;
	unsigned ECMA_version : 3;
	unsigned ISO_version : 2;
	unsigned data_format : 4;
	unsigned reserved1 : 2;
	unsigned trmIOP : 1;
	unsigned AENC : 1;
	unsigned char additional_length;
	char reserved2[2];
	unsigned sftRe : 1;
	unsigned cmdQue : 1;
	unsigned reserved3 : 1;
	unsigned linked : 1;
	unsigned sync : 1;
	unsigned WBus16 : 1;
	unsigned WBus32 : 1;
	unsigned relAdr : 1;
	char vendor_id[8];
	char product_id[16];
	char product_revision[4];

	/*
	**  Start of optional data
	*/
	char vendor_specific[19];
	char reserved4[39];

	} std_inquiry_data;

    struct SCSI$IOSB gk_iosb;
    struct SCSI$DESC gk_desc;

    dprintf("SCSI Inquiry\n");

    scsi_command.opcode = INQUIRY;
    scsi_command.EVPD = 0;
    scsi_command.reserved1 = 0;
    scsi_command.LUN = 0;
    scsi_command.page_code = 0;
    scsi_command.reserved2 = 0;
    scsi_command.allocation_length = sizeof(std_inquiry_data);
    scsi_command.control = 0;

    memset(&std_inquiry_data, '\0', sizeof(std_inquiry_data));

    gk_desc.SCSI$L_OPCODE = 1;
    gk_desc.SCSI$L_FLAGS = SCSI$K_FL_ENAB_SYNC
			 | SCSI$K_READ
			 | SCSI$K_FL_ENAB_DIS;
    gk_desc.SCSI$A_CMD_ADDR = (char*)&scsi_command;
    gk_desc.SCSI$L_CMD_LEN = sizeof(scsi_command);
    gk_desc.SCSI$A_DATA_ADDR = (char*)&std_inquiry_data;
    gk_desc.SCSI$L_DATA_LEN = sizeof(std_inquiry_data);
    gk_desc.SCSI$L_PAD_LEN = 0;
    gk_desc.SCSI$L_PH_CH_TMOUT = 180;  /* SCSI phase change timeout, sec */
    gk_desc.SCSI$L_DISCON_TMOUT = 180; /* SCSI disconnect timeout, sec */
    gk_desc.SCSI$L_RES_1 = 0;
    gk_desc.SCSI$L_RES_2 = 0;
    gk_desc.SCSI$L_RES_3 = 0;
    gk_desc.SCSI$L_RES_4 = 0;
    gk_desc.SCSI$L_RES_5 = 0;
    gk_desc.SCSI$L_RES_6 = 0;

    /*
    **	Issue the QIO to send the inquiry command and
    **	receive the inquiry data
    */
    return_status = sys$qiow (
			GK_EFN,
			gk_channel,
			IO$_DIAGNOSE,
			&gk_iosb,
			0,
			0,
			&gk_desc,
			sizeof(gk_desc),
			0,  0,	0,  0);

    insure_ok(return_status);
    insure_ok(gk_iosb.SCSI$W_VMS_STAT);
    check_scsi_status(gk_iosb.SCSI$B_IOSB_STS);

    /*
    **	The command succeeded. Display the SCSI data returned from the target
    */
    memset(trimmed_string, '\0', sizeof(trimmed_string));
    memcpy(
	trimmed_string,
	std_inquiry_data.vendor_id,
	sizeof(std_inquiry_data.vendor_id));
    for(i=(sizeof(trimmed_string) - 1); i>=0; i--)
	{
	if (trimmed_string[i] > ' ') break;
	trimmed_string[i] = '\0';
	}
    fprintf(stderr,
	" CD-R Vendor ID: %s\n",
	trimmed_string);

    memset(trimmed_string, '\0', sizeof(trimmed_string));
    memcpy(
	trimmed_string,
	std_inquiry_data.product_id,
	sizeof(std_inquiry_data.product_id));
    for(i=(sizeof(trimmed_string) - 1); i>=0; i--)
	{
	if (trimmed_string[i] > ' ') break;
	trimmed_string[i] = '\0';
	}
    fprintf(stderr,
	"CD-R Product ID: %s\n",
	trimmed_string);

    memset(trimmed_string, '\0', sizeof(trimmed_string));
    memcpy(
	trimmed_string,
	std_inquiry_data.product_revision,
	sizeof(std_inquiry_data.product_revision));
    for(i=(sizeof(trimmed_string) - 1); i>=0; i--)
	{
	if (trimmed_string[i] > ' ') break;
	trimmed_string[i] = '\0';
	}
    fprintf(stderr,
	"  CD-R Revision: %s\n",
	trimmed_string);

    if (debug_mode)
	{
	printf ("SCSI inquiry data returned %lu bytes of data: \n",
	    gk_iosb.SCSI$L_IOSB_TFR_CNT);

	printf ("             Device type: %02.2X\n", std_inquiry_data.device_type);
	printf ("    Peripheral Qualifier: %02.2X\n", std_inquiry_data.qualifier);
	printf ("    Device Type Modifier: %02.2X\n", std_inquiry_data.modifier);
	printf ("         Removable Media: %d\n", std_inquiry_data.RMB);
	printf ("            ANSI Version: %d\n", std_inquiry_data.ANSI_version);
	printf ("            ECMA Version: %d\n", std_inquiry_data.ECMA_version);
	printf ("             ISO Version: %d\n", std_inquiry_data.ISO_version);
	printf ("             Data Format: %d\n", std_inquiry_data.data_format);
	printf ("    Terminate IO Support: %d\n", std_inquiry_data.trmIOP);
	printf ("Async Event Notification: %d\n", std_inquiry_data.AENC);
	printf ("       Additional Length: %d\n", std_inquiry_data.additional_length);
	printf ("              Soft Reset: %d\n", std_inquiry_data.sftRe);
	printf ("        Command Queueing: %d\n", std_inquiry_data.cmdQue);
	printf ("                  Linked: %d\n", std_inquiry_data.linked);
	printf ("             Synchronous: %d\n", std_inquiry_data.sync);
	printf ("             16 bit wide: %d\n", std_inquiry_data.WBus16);
	printf ("             32 bit wide: %d\n", std_inquiry_data.WBus32);
	printf ("     Relative Addressing: %d\n", std_inquiry_data.relAdr);
	}

    return;
}


/*
**  START_STOP_UNIT
*/
static void start_stop_unit(
    int start_it)
{
    int return_status;
    struct start_stop_unit_cmd
	{
	char opcode;
	unsigned reserved1 : 5;
	unsigned LUN : 3;
	char reserved2[2];
	unsigned start : 1;
	unsigned load_eject : 1;
	unsigned reserved3 : 6;
	char control;
	} scsi_command;
    struct SCSI$IOSB gk_iosb;
    struct SCSI$DESC gk_desc;

    if (start_it)
	{
	dprintf("Starting Unit\n");
	}
    else
	{
	dprintf("Stopping Unit\n");
	}

    scsi_command.opcode = START_STOP;
    scsi_command.reserved1 = 0;
    scsi_command.LUN = 0;
    scsi_command.reserved2[0] = 0;
    scsi_command.reserved2[1] = 0;
    scsi_command.start = start_it;
    scsi_command.load_eject = 0;
    scsi_command.reserved3 = 0;
    scsi_command.control = 0;

    gk_desc.SCSI$L_OPCODE = 1;
    gk_desc.SCSI$L_FLAGS = SCSI$K_FL_ENAB_SYNC
			 | SCSI$K_READ
			 | SCSI$K_FL_ENAB_DIS;
    gk_desc.SCSI$A_CMD_ADDR = (char*)&scsi_command;
    gk_desc.SCSI$L_CMD_LEN = sizeof(scsi_command);
    gk_desc.SCSI$A_DATA_ADDR = NULL;
    gk_desc.SCSI$L_DATA_LEN = 0;
    gk_desc.SCSI$L_PAD_LEN = 0;
    gk_desc.SCSI$L_PH_CH_TMOUT = 180;  /* SCSI phase change timeout, sec */
    gk_desc.SCSI$L_DISCON_TMOUT = 180; /* SCSI disconnect timeout, sec */
    gk_desc.SCSI$L_RES_1 = 0;
    gk_desc.SCSI$L_RES_2 = 0;
    gk_desc.SCSI$L_RES_3 = 0;
    gk_desc.SCSI$L_RES_4 = 0;
    gk_desc.SCSI$L_RES_5 = 0;
    gk_desc.SCSI$L_RES_6 = 0;

    do
	{
	/*
	**  Issue the QIO
	*/
	return_status = sys$qiow (
			    GK_EFN,
			    gk_channel,
			    IO$_DIAGNOSE,
			    &gk_iosb,
			    0,
			    0,
			    &gk_desc,
			    sizeof(gk_desc),
			    0,	0,  0,	0);

	} while (busy_scsi_status(gk_iosb.SCSI$B_IOSB_STS));

    insure_ok(return_status);
    insure_ok(gk_iosb.SCSI$W_VMS_STAT);
    if (start_it)
	{
	/*
	**  Not being able to start the unit is a fatal error
	*/
	check_scsi_status(gk_iosb.SCSI$B_IOSB_STS);
	}
    else
	{
	log_scsi_status(gk_iosb.SCSI$B_IOSB_STS, "START_STOP");
	}

    return;
}


/*
**  ALLOW_REMOVAL - Allow or prevent removal of the CD-R.  This essentially
**		    disables the "open" button on the front panel.
*/
static void allow_removal(
    int removal_allowed)
{
    int return_status;
    struct prevent_allow_medium_removal_cmd
	{
	char opcode;
	unsigned reserved1 : 5;
	unsigned LUN : 3;
	char reserved2[2];
	unsigned prevent : 1;
	unsigned reserved3 : 7;
	char control;
	} scsi_command;
    struct SCSI$IOSB gk_iosb;
    struct SCSI$DESC gk_desc;

    if (removal_allowed)
	{
	dprintf("Unlocking CD door\n");
	}
    else
	{
	dprintf("Locking CD door\n");
	}

    scsi_command.opcode = PREVENT_ALLOW_MEDIUM_REMOVAL;
    scsi_command.reserved1 = 0;
    scsi_command.LUN = 0;
    scsi_command.reserved2[0] = 0;
    scsi_command.reserved2[1] = 0;
    scsi_command.prevent = (removal_allowed ? 0 : 1);
    scsi_command.reserved3 = 0;
    scsi_command.control = 0;

    gk_desc.SCSI$L_OPCODE = 1;
    gk_desc.SCSI$L_FLAGS = SCSI$K_FL_ENAB_SYNC
			 | SCSI$K_READ
			 | SCSI$K_FL_ENAB_DIS;
    gk_desc.SCSI$A_CMD_ADDR = (char*)&scsi_command;
    gk_desc.SCSI$L_CMD_LEN = sizeof(scsi_command);
    gk_desc.SCSI$A_DATA_ADDR = NULL;
    gk_desc.SCSI$L_DATA_LEN = 0;
    gk_desc.SCSI$L_PAD_LEN = 0;
    gk_desc.SCSI$L_PH_CH_TMOUT = 180;  /* SCSI phase change timeout, sec */
    gk_desc.SCSI$L_DISCON_TMOUT = 180; /* SCSI disconnect timeout, sec */
    gk_desc.SCSI$L_RES_1 = 0;
    gk_desc.SCSI$L_RES_2 = 0;
    gk_desc.SCSI$L_RES_3 = 0;
    gk_desc.SCSI$L_RES_4 = 0;
    gk_desc.SCSI$L_RES_5 = 0;
    gk_desc.SCSI$L_RES_6 = 0;

    do
	{
	/*
	**  Issue the QIO
	*/
	return_status = sys$qiow (
			    GK_EFN,
			    gk_channel,
			    IO$_DIAGNOSE,
			    &gk_iosb,
			    0,
			    0,
			    &gk_desc,
			    sizeof(gk_desc),
			    0,	0,  0,	0);

	} while (busy_scsi_status(gk_iosb.SCSI$B_IOSB_STS));

    insure_ok(return_status);
    insure_ok(gk_iosb.SCSI$W_VMS_STAT);
    log_scsi_status(gk_iosb.SCSI$B_IOSB_STS, "PREVENT_ALLOW_REMOVAL");

    return;
}


/*
**  SYNC_CACHE - Tell the CD-R to syncronize it's cache which will
**		 insure that all of the cached data has been written
*/
static void sync_cache()
{
    int return_status;
    struct syncronize_cache_cmd
	{
	char	opcode;

	unsigned reladr : 1;
	unsigned immed : 1;
	unsigned reserved1 : 3;
	unsigned LUN : 3;
	unsigned char LBA[4];	/* Big Endian!! */
	char reserved2;
	unsigned char block_count[2]; /* Big Endian! */
	char control;
	} scsi_command;
    struct SCSI$IOSB gk_iosb;
    struct SCSI$DESC gk_desc;

    dprintf("Sync cache\n");

    scsi_command.opcode = SYNCRONIZE_CACHE;
    scsi_command.reladr = 0;
    scsi_command.immed = 0;
    scsi_command.reserved1 = 0;
    scsi_command.LUN = 0;
    scsi_command.LBA[0] = 0;
    scsi_command.LBA[1] = 0;
    scsi_command.LBA[2] = 0;
    scsi_command.LBA[3] = 0;
    scsi_command.reserved2 = 0;
    scsi_command.block_count[0] = 0;
    scsi_command.block_count[1] = 0;
    scsi_command.control = 0;

    gk_desc.SCSI$L_OPCODE = 1;
    gk_desc.SCSI$L_FLAGS = SCSI$K_FL_ENAB_SYNC
			 | SCSI$K_READ
			 | SCSI$K_FL_ENAB_DIS;
    gk_desc.SCSI$A_CMD_ADDR = (char*)&scsi_command;
    gk_desc.SCSI$L_CMD_LEN = sizeof(scsi_command);
    gk_desc.SCSI$A_DATA_ADDR = NULL;
    gk_desc.SCSI$L_DATA_LEN = 0;
    gk_desc.SCSI$L_PAD_LEN = 0;
    gk_desc.SCSI$L_PH_CH_TMOUT = 180;  /* SCSI phase change timeout, sec */
    gk_desc.SCSI$L_DISCON_TMOUT = 180; /* SCSI disconnect timeout, sec */
    gk_desc.SCSI$L_RES_1 = 0;
    gk_desc.SCSI$L_RES_2 = 0;
    gk_desc.SCSI$L_RES_3 = 0;
    gk_desc.SCSI$L_RES_4 = 0;
    gk_desc.SCSI$L_RES_5 = 0;
    gk_desc.SCSI$L_RES_6 = 0;

    /*
    **	Issue the QIO
    */
    return_status = sys$qiow (
			GK_EFN,
			gk_channel,
			IO$_DIAGNOSE,
			&gk_iosb,
			0,
			0,
			&gk_desc,
			sizeof(gk_desc),
			0,  0,	0,  0);

    insure_ok(return_status);
    insure_ok(gk_iosb.SCSI$W_VMS_STAT);
    log_scsi_status(gk_iosb.SCSI$B_IOSB_STS, "Sync Cache");

    return;
}


/*
**  Philips CDD2600 Specific Operations
*/

/*
**  MODE_SELECT_x23 - Set mode for page 0x23.  MODE_SELECT is a standard SCSI
**		      command but the code page we are setting (page 23 hex)
**		      is Philips specific.
*/
static void mode_select_x23(
    int speed,
    int dummy_write)
{
    int return_status;
    struct mode_select_6_cmd
	{
	char opcode;
	unsigned sp : 1;
	unsigned reserved1 : 3;
	unsigned pf : 1;
	unsigned LUN : 3;
	char reserved2;
	char reserved3;
	char param_length;
	char control;
	} scsi_command;
    struct page_code_23
	{
	/*
	**  Standard mode select header
	*/
	char reserved1;
	char medium_type;
	unsigned host_application_code : 7;
	unsigned reserved2 : 1;
	char block_descriptor_length;

	/*
	**  Page 23 specific portion
	*/
	unsigned page_code : 6;
	unsigned reserved3 : 2;
	char parameter_length;
	char write_speed;
	char write_emulation;
	char read_speed;
	char reserved4;
	char reserved5;
	unsigned reserved6 : 7;
	unsigned scrambled : 1;
	} page;
    struct SCSI$IOSB gk_iosb;
    struct SCSI$DESC gk_desc;

    dprintf("Setting speed to %d, dummy=%d (1=yes, 0=no)\n",
	speed,
	dummy_write);

    scsi_command.opcode = MODE_SELECT;
    scsi_command.sp = 0;
    scsi_command.reserved1 = 0;
    scsi_command.pf = 1;
    scsi_command.LUN = 0;
    scsi_command.reserved2 = 0;
    scsi_command.reserved3 = 0;
    scsi_command.param_length = sizeof(page);
    scsi_command.control = 0;

    /*
    **	Mode select header:
    */
    page.reserved1 = 0;
    page.medium_type = 0;
    page.host_application_code = 0;
    page.reserved2 = 0;
    page.block_descriptor_length = 0;

    /*
    **	Mode Page 0x23
    */
    page.page_code = 0x23;
    page.reserved3 = 0;
    page.parameter_length = 6;
    page.write_speed = speed;
    page.write_emulation = dummy_write; /* 1 = dummy, 0 = real write*/
    page.read_speed = 0;
    page.reserved4 = 0;
    page.reserved5 = 0;
    page.reserved6 = 0;
    page.scrambled = 0;

    gk_desc.SCSI$L_OPCODE = 1;
    gk_desc.SCSI$L_FLAGS = SCSI$K_FL_ENAB_SYNC
			 | SCSI$K_WRITE
			 | SCSI$K_FL_ENAB_DIS;
    gk_desc.SCSI$A_CMD_ADDR = (char*)&scsi_command;
    gk_desc.SCSI$L_CMD_LEN = sizeof(scsi_command);
    gk_desc.SCSI$A_DATA_ADDR = (char*)&page;
    gk_desc.SCSI$L_DATA_LEN = sizeof(page);
    gk_desc.SCSI$L_PAD_LEN = 0;
    gk_desc.SCSI$L_PH_CH_TMOUT = 180;  /* SCSI phase change timeout, sec */
    gk_desc.SCSI$L_DISCON_TMOUT = 180; /* SCSI disconnect timeout, sec */
    gk_desc.SCSI$L_RES_1 = 0;
    gk_desc.SCSI$L_RES_2 = 0;
    gk_desc.SCSI$L_RES_3 = 0;
    gk_desc.SCSI$L_RES_4 = 0;
    gk_desc.SCSI$L_RES_5 = 0;
    gk_desc.SCSI$L_RES_6 = 0;

    /*
    **	Issue the QIO
    */
    return_status = sys$qiow (
			GK_EFN,
			gk_channel,
			IO$_DIAGNOSE,
			&gk_iosb,
			0,
			0,
			&gk_desc,
			sizeof(gk_desc),
			0,  0,	0,  0);

    insure_ok(return_status);
    insure_ok(gk_iosb.SCSI$W_VMS_STAT);
    check_scsi_status(gk_iosb.SCSI$B_IOSB_STS);

    return;
}


/*
**  WRITE_TRACK
*/
static void write_track()
{
    int return_status;
    struct write_track_cmd
	{
	char opcode;
	unsigned reserved1 : 5;
	unsigned LUN : 3;
	char reserved2[3];
	unsigned char track_number;
	unsigned track_mode : 2;
	unsigned audio : 1;
	unsigned raw : 1;
	unsigned copy_control : 2;
	unsigned reserved3 : 2;
	unsigned char transfer_length[2];
	unsigned linked : 1;
	unsigned flag : 1;
	unsigned reserved4 : 4;
	unsigned mixed_fmt : 1;
	unsigned reserved5 : 1;
	} scsi_command;
    struct SCSI$IOSB gk_iosb;
    struct SCSI$DESC gk_desc;

    dprintf("Write track\n");

    scsi_command.opcode = WRITE_TRACK;
    scsi_command.reserved1 = 0;
    scsi_command.LUN = 0;
    scsi_command.reserved2[0] = 0;
    scsi_command.reserved2[1] = 0;
    scsi_command.reserved2[2] = 0;
    scsi_command.track_number = 0;  /* 0 means the next one */
    scsi_command.track_mode = 1;
    scsi_command.audio = 0;
    scsi_command.raw = 0;
    scsi_command.copy_control = 0;
    scsi_command.reserved3 = 0;
    scsi_command.transfer_length[0] = 0;
    scsi_command.transfer_length[1] = 0;
    scsi_command.linked = 0;
    scsi_command.flag = 0;
    scsi_command.reserved4 = 0;
    scsi_command.mixed_fmt = 0;
    scsi_command.reserved5 = 0;

    gk_desc.SCSI$L_OPCODE = 1;
    gk_desc.SCSI$L_FLAGS = SCSI$K_FL_ENAB_SYNC
			 | SCSI$K_READ
			 | SCSI$K_FL_ENAB_DIS;
    gk_desc.SCSI$A_CMD_ADDR = (char*)&scsi_command;
    gk_desc.SCSI$L_CMD_LEN = sizeof(scsi_command);
    gk_desc.SCSI$A_DATA_ADDR = NULL;
    gk_desc.SCSI$L_DATA_LEN = 0;
    gk_desc.SCSI$L_PAD_LEN = 0;
    gk_desc.SCSI$L_PH_CH_TMOUT = 180;  /* SCSI phase change timeout, sec */
    gk_desc.SCSI$L_DISCON_TMOUT = 180; /* SCSI disconnect timeout, sec */
    gk_desc.SCSI$L_RES_1 = 0;
    gk_desc.SCSI$L_RES_2 = 0;
    gk_desc.SCSI$L_RES_3 = 0;
    gk_desc.SCSI$L_RES_4 = 0;
    gk_desc.SCSI$L_RES_5 = 0;
    gk_desc.SCSI$L_RES_6 = 0;

    /*
    **	Issue the QIO
    */
    return_status = sys$qiow (
			GK_EFN,
			gk_channel,
			IO$_DIAGNOSE,
			&gk_iosb,
			0,
			0,
			&gk_desc,
			sizeof(gk_desc),
			0,  0,	0,  0);

    insure_ok(return_status);
    insure_ok(gk_iosb.SCSI$W_VMS_STAT);
    check_scsi_status(gk_iosb.SCSI$B_IOSB_STS);

    return;
}


/*
**  LOAD_UNLOAD - Open or close the CD-R door.	This is a Philips specific
**		  command even though there is a SCSI standard command for
**		  loading and unloading media.
*/
static void load_unload(
    int unload_it)
{
    int return_status;
    struct load_unload_cmd
	{
	char	opcode;

	unsigned immed : 1;
	unsigned reserved1 : 4;
	unsigned LUN : 3;
	char reserved2[6];
	unsigned medium_load_unload: 1;
	unsigned reserved3 : 7;
	char control;
	} scsi_command;
    struct SCSI$IOSB gk_iosb;
    struct SCSI$DESC gk_desc;

    if (unload_it)
	{
	dprintf("Unloading CD\n");
	}
    else
	{
	dprintf("Loading CD\n");
	}

    scsi_command.opcode = LOAD_UNLOAD;
    scsi_command.immed = 0;
    scsi_command.reserved1 = 0;
    scsi_command.LUN = 0;
    scsi_command.reserved2[0] = 0;
    scsi_command.reserved2[1] = 0;
    scsi_command.reserved2[2] = 0;
    scsi_command.reserved2[3] = 0;
    scsi_command.reserved2[4] = 0;
    scsi_command.reserved2[5] = 0;
    scsi_command.medium_load_unload = unload_it; /* 1 = unload, 0 = load */
    scsi_command.reserved3 = 0;
    scsi_command.control = 0;

    gk_desc.SCSI$L_OPCODE = 1;
    gk_desc.SCSI$L_FLAGS = SCSI$K_FL_ENAB_SYNC
			 | SCSI$K_READ
			 | SCSI$K_FL_ENAB_DIS;
    gk_desc.SCSI$A_CMD_ADDR = (char*)&scsi_command;
    gk_desc.SCSI$L_CMD_LEN = sizeof(scsi_command);
    gk_desc.SCSI$A_DATA_ADDR = NULL;
    gk_desc.SCSI$L_DATA_LEN = 0;
    gk_desc.SCSI$L_PAD_LEN = 0;
    gk_desc.SCSI$L_PH_CH_TMOUT = 180;  /* SCSI phase change timeout, sec */
    gk_desc.SCSI$L_DISCON_TMOUT = 180; /* SCSI disconnect timeout, sec */
    gk_desc.SCSI$L_RES_1 = 0;
    gk_desc.SCSI$L_RES_2 = 0;
    gk_desc.SCSI$L_RES_3 = 0;
    gk_desc.SCSI$L_RES_4 = 0;
    gk_desc.SCSI$L_RES_5 = 0;
    gk_desc.SCSI$L_RES_6 = 0;

    do
	{
	/*
	**  Issue the QIO
	*/
	return_status = sys$qiow (
			    GK_EFN,
			    gk_channel,
			    IO$_DIAGNOSE,
			    &gk_iosb,
			    0,
			    0,
			    &gk_desc,
			    sizeof(gk_desc),
			    0,	0,  0,	0);
	} while (busy_scsi_status(gk_iosb.SCSI$B_IOSB_STS));

    insure_ok(return_status);
    insure_ok(gk_iosb.SCSI$W_VMS_STAT);
    log_scsi_status(gk_iosb.SCSI$B_IOSB_STS, "LOAD_UNLOAD");

    return;
}


/*
**  FIXATION - Write the table of contents
*/
static void fixation()
{
    int return_status;
    struct fixation_cmd
	{
	char	opcode;

	unsigned immed : 1;
	unsigned reserved1 : 4;
	unsigned LUN : 3;
	char reserved2[6];
	unsigned toc_type : 3;
	unsigned open_next_program : 1;
	unsigned reserved3 : 4;
	char control;
	} scsi_command;
    struct SCSI$IOSB gk_iosb;
    struct SCSI$DESC gk_desc;

    dprintf("Fixation\n");

    scsi_command.opcode = FIXATION;
    scsi_command.immed = 0;
    scsi_command.reserved1 = 0;
    scsi_command.LUN = 0;
    scsi_command.reserved2[0] = 0;
    scsi_command.reserved2[1] = 0;
    scsi_command.reserved2[2] = 0;
    scsi_command.reserved2[3] = 0;
    scsi_command.reserved2[4] = 0;
    scsi_command.reserved2[5] = 0;
    scsi_command.toc_type = 1;	    /* CD-ROM */
    scsi_command.open_next_program = 0;
    scsi_command.reserved3 = 0;
    scsi_command.control = 0;

    gk_desc.SCSI$L_OPCODE = 1;
    gk_desc.SCSI$L_FLAGS = SCSI$K_FL_ENAB_SYNC
			 | SCSI$K_READ
			 | SCSI$K_FL_ENAB_DIS;
    gk_desc.SCSI$A_CMD_ADDR = (char*)&scsi_command;
    gk_desc.SCSI$L_CMD_LEN = sizeof(scsi_command);
    gk_desc.SCSI$A_DATA_ADDR = NULL;
    gk_desc.SCSI$L_DATA_LEN = 0;
    gk_desc.SCSI$L_PAD_LEN = 0;
    gk_desc.SCSI$L_PH_CH_TMOUT = 300;  /* SCSI phase change timeout, sec */
    gk_desc.SCSI$L_DISCON_TMOUT = 300; /* SCSI disconnect timeout, sec */
    gk_desc.SCSI$L_RES_1 = 0;
    gk_desc.SCSI$L_RES_2 = 0;
    gk_desc.SCSI$L_RES_3 = 0;
    gk_desc.SCSI$L_RES_4 = 0;
    gk_desc.SCSI$L_RES_5 = 0;
    gk_desc.SCSI$L_RES_6 = 0;

    /*
    **	Issue the QIO
    */
    return_status = sys$qiow (
			GK_EFN,
			gk_channel,
			IO$_DIAGNOSE,
			&gk_iosb,
			0,
			0,
			&gk_desc,
			sizeof(gk_desc),
			0,  0,	0,  0);

    insure_ok(return_status);
    insure_ok(gk_iosb.SCSI$W_VMS_STAT);
    check_scsi_status(gk_iosb.SCSI$B_IOSB_STS);

    return;
}


/*
**  SEND_ABSORPTION_CONTROL_ERRORS - Get the list of absorption control errors
**				     and display them.
*/
static void send_absorption_control_errors()
{
    int return_status;
    int i;
    struct send_absorption_control_errors_cmd
	{
	char	opcode;

	unsigned reserved1 : 5;
	unsigned LUN : 3;
	char reserved2[5];
	unsigned char allocation_length_msb;
	unsigned char allocation_length_lsb;
	char control;
	} scsi_command;
    struct ACE_descriptor
	{
	char reserved1[2];
	unsigned error_number : 4;
	unsigned reserved2 : 4;
	char reserved3;
	unsigned char lba_msb;	/* Most significant byte */
	unsigned char lba_vsb;	/* Very significant byte */
	unsigned char lba_nsb;	/* Not significant byte */
	unsigned char lba_lsb;	/* Least significant byte */
	};
    struct
	{
	unsigned char error_data_length_msb;
	unsigned char error_data_length_lsb;
	unsigned char reserved1;
	unsigned char number_of_errors;
	struct ACE_descriptor ACE_d[16];
	} ACE_buffer;
    struct SCSI$IOSB gk_iosb;
    struct SCSI$DESC gk_desc;

    dprintf("Checking for Absorption Control Errors\n");

    scsi_command.opcode = SEND_ABSORPTION_CONTROL_ERRORS;
    scsi_command.reserved1 = 0;
    scsi_command.LUN = 0;
    scsi_command.reserved2[0] = 0;
    scsi_command.reserved2[1] = 0;
    scsi_command.reserved2[2] = 0;
    scsi_command.reserved2[3] = 0;
    scsi_command.reserved2[4] = 0;
    scsi_command.allocation_length_msb = ((sizeof(ACE_buffer) & 0xFF00) >> 8);
    scsi_command.allocation_length_lsb = (sizeof(ACE_buffer) & 0x00FF);
    scsi_command.control = 0;

    memset(&ACE_buffer, '\0', sizeof(ACE_buffer));

    gk_desc.SCSI$L_OPCODE = 1;
    gk_desc.SCSI$L_FLAGS = SCSI$K_FL_ENAB_SYNC
			 | SCSI$K_READ
			 | SCSI$K_FL_ENAB_DIS;
    gk_desc.SCSI$A_CMD_ADDR = (char*)&scsi_command;
    gk_desc.SCSI$L_CMD_LEN = sizeof(scsi_command);
    gk_desc.SCSI$A_DATA_ADDR = (char*)&ACE_buffer;
    gk_desc.SCSI$L_DATA_LEN = sizeof(ACE_buffer);
    gk_desc.SCSI$L_PAD_LEN = 0;
    gk_desc.SCSI$L_PH_CH_TMOUT = 180;  /* SCSI phase change timeout, sec */
    gk_desc.SCSI$L_DISCON_TMOUT = 180; /* SCSI disconnect timeout, sec */
    gk_desc.SCSI$L_RES_1 = 0;
    gk_desc.SCSI$L_RES_2 = 0;
    gk_desc.SCSI$L_RES_3 = 0;
    gk_desc.SCSI$L_RES_4 = 0;
    gk_desc.SCSI$L_RES_5 = 0;
    gk_desc.SCSI$L_RES_6 = 0;

    do
	{
	/*
	**  Issue the QIO
	*/
	return_status = sys$qiow (
			    GK_EFN,
			    gk_channel,
			    IO$_DIAGNOSE,
			    &gk_iosb,
			    0,
			    0,
			    &gk_desc,
			    sizeof(gk_desc),
			    0,	0,  0,	0);
	} while (busy_scsi_status(gk_iosb.SCSI$B_IOSB_STS));

    insure_ok(return_status);
    insure_ok(gk_iosb.SCSI$W_VMS_STAT);
    log_scsi_status(gk_iosb.SCSI$B_IOSB_STS, "SEND_ABSORPTION_ERRORS");

    /*
    **	Now display the error information
    */
    fprintf (stderr, "%d absorption control errors during the write\n",
	ACE_buffer.number_of_errors);

    for(i=0; i<ACE_buffer.number_of_errors; i++)
	{
	fprintf (stderr,
	    "Error in area %d which starts at LBA %02.2X %02.2X %02.2X %02.2X\n",
	    ACE_buffer.ACE_d[i].error_number,
	    ACE_buffer.ACE_d[i].lba_msb,
	    ACE_buffer.ACE_d[i].lba_vsb,
	    ACE_buffer.ACE_d[i].lba_nsb,
	    ACE_buffer.ACE_d[i].lba_lsb);
	}

    return;
}


/*
**  Support Routines
*/

/*
**  BUSY_SCSI_STATUS - Check the SCSI status, if it's SCSI$K_BUSY or
**                     some other status which means we should retry
**                     then return TRUE
*/
static int busy_scsi_status (
    unsigned char scsi_status)
{
    int return_boolean;

    return_boolean = FALSE;

    dprintf("Checking to see if SCSI status of %d is busy\n",
	scsi_status);

    if (scsi_status == SCSI$K_BUSY)
	{
	lib$wait(&(float)2.0);
	return_boolean = TRUE;
	}
    else if (scsi_status == SCSI$K_CHECK_CONDITION)
	{
	request_sense();
	switch (sense_data.sense_key)
	    {
	    case 0x01:	/* RECOVERED_ERROR */
		{
		fprintf(stderr,
		    "Recovered Error, additional: %02.2X/%02.2X\n",
		    sense_data.additional_sense_code,
		    sense_data.additional_sense_code_qualifier);

		return_boolean = TRUE;
		break;
		}
	    case 0x02:	/* NOT_READY */
		{
		dprintf("CD-R is not ready, additional: %02.2X/%02.2X\n",
		    sense_data.additional_sense_code,
		    sense_data.additional_sense_code_qualifier);

		lib$wait(&(float)2.0);
		return_boolean = TRUE;
		break;
		}
	    case 0x06:	/* UNIT_ATTENTION */
		{
		switch (sense_data.additional_sense_code)
		    {
		    case 0x28:
			{
			dprintf("Not ready to ready transition occurred\n");
			return_boolean = TRUE;
			break;
			}
		    case 0x29:
			{
			dprintf("Power on, Reset or Bus device reset occurred\n");
			return_boolean = TRUE;
			break;
			}
		    }
		break;
		}
	    }
	}

    return return_boolean;
}

/*
**  CHECK_SCSI_STATUS - Check the SCSI status, if it's not good, blow up
*/
static void check_scsi_status (
    unsigned char scsi_status)
{
    if (scsi_status != SCSI$K_GOOD)
	{
	fprintf(stderr,
	    "Bad SCSI status returned: %02.2x\n",
	    scsi_status);
	if (scsi_status == SCSI$K_CHECK_CONDITION)
	    {
	    /*
	    **  We may have already read the sense data (via the
	    **  busy_scsi_status routine) but if we haven't, read it
	    */
	    if (!sense_data.valid)
		{
		request_sense();
		}

	    /*
	    **  Now print the sense data
	    **  and mark it as invalid
	    */
	    print_sense_data(&sense_data);
	    sense_data.valid = FALSE;
	    }
	lib$signal (SS$_ABORT);
	}

    return;
}

/*
**  LOG_SCSI_STATUS - Check the SCSI status but don't exit
*/
static void log_scsi_status (
    unsigned char scsi_status,
    char *log_text)
{
    if (scsi_status != SCSI$K_GOOD)
	{
	fprintf(stderr,
	    "%s returned a bad SCSI status: %02.2x\n",
	    log_text,
	    scsi_status);
	if (scsi_status == SCSI$K_CHECK_CONDITION)
	    {
	    /*
	    **  We may have already read the sense data (via the
	    **  busy_scsi_status routine) but if we haven't, read it
	    */
	    if (!sense_data.valid)
		{
		request_sense();
		}

	    /*
	    **  Now print the sense data
	    **  and mark it as invalid
	    */
	    print_sense_data(&sense_data);
	    sense_data.valid = FALSE;
	    }
	}

    return;
}


/*
**  PRINT_SENSE_DATA - Display the request sense data
*/
static void print_sense_data(
    struct SCSI_SENSE_DATA *sense_data)
{
    /*
    **  We got some sense data, make some sense out of it
    */
    if ((sense_data->error_code == 0x70) || (sense_data->error_code == 0x71))
	{
	switch (sense_data->sense_key)
	    {
	    case 0x00:	/* NO_SENSE */
		{
		/*
		** No Sense
		*/
		if (sense_data->ILI)
		    {
		    fprintf(stderr, "Illegal Length!\n");
		    }
		else if (sense_data->EOM)
		    {
		    fprintf(stderr, "End of Medium (you filled the disk!)\n");
		    }
		else if (sense_data->filemark)
		    {
		    fprintf(stderr, "Filemark\n");
		    }
		else
		    {
		    /*
		    **  Who knows ??
		    */
		    fprintf(stderr,
			"NO SENSE Info, additional: %02.2X/%02.2X\n",
			sense_data->additional_sense_code,
			sense_data->additional_sense_code_qualifier);
		    }
		break;
		}

	    case 0x01:	/* RECOVERED_ERROR */
		{
		fprintf(stderr,
		    "Recovered Error, additional: %02.2X/%02.2X\n",
		    sense_data->additional_sense_code,
		    sense_data->additional_sense_code_qualifier);
		break;
		}

	    case 0x02:	/* NOT_READY */
		{
		fprintf(stderr,
		    "Not Ready, additional: %02.2X/%02.2X\n",
		    sense_data->additional_sense_code,
		    sense_data->additional_sense_code_qualifier);
		break;
		}

	    case 0x03:	/* MEDIUM_ERROR */
		{
		fprintf(stderr,
		    "Medium Error, additional: %02.2X/%02.2X\n",
		    sense_data->additional_sense_code,
		    sense_data->additional_sense_code_qualifier);
		break;
		}

	    case 0x04:	/* HARDWARE_ERROR */
		{
		fprintf(stderr,
		    "Hardware Error, additional: %02.2X/%02.2X\n",
		    sense_data->additional_sense_code,
		    sense_data->additional_sense_code_qualifier);
		break;
		}

	    case 0x05:	/* ILLEGAL_REQUEST */
		{
		fprintf(stderr,
		    "Illegal Request, additional: %02.2X/%02.2X\n",
		    sense_data->additional_sense_code,
		    sense_data->additional_sense_code_qualifier);
		break;
		}

	    case 0x06:	/* UNIT_ATTENTION */
		{
		switch (sense_data->additional_sense_code)
		    {
		    case 0x28:
			{
			dprintf("Not ready to ready transition occurred\n");
			break;
			}
		    case 0x29:
			{
			dprintf("Power on, Reset or Bus device reset occurred\n");
			break;
			}
		    default:
			{
			fprintf(stderr,
			    "Unit Attention, additional: %02.2X/%02.2X\n",
			    sense_data->additional_sense_code,
			    sense_data->additional_sense_code_qualifier);
			break;
			}
		    }
		break;
		}

	    case 0x07:	/* DATA_PROTECT */
		{
		fprintf(stderr,
		    "Data Protect, additional: %02.2X/%02.2X\n",
		    sense_data->additional_sense_code,
		    sense_data->additional_sense_code_qualifier);
		break;
		}

	    case 0x08:	/* BLANK_CHECK */
		{
		fprintf(stderr,
		    "Blank Check, additional: %02.2X/%02.2X\n",
		    sense_data->additional_sense_code,
		    sense_data->additional_sense_code_qualifier);
		break;
		}

	    case 0x0A:	/* COPY_ABORTED */
		{
		fprintf(stderr,
		    "Copy Aborted, additional: %02.2X/%02.2X\n",
		    sense_data->additional_sense_code,
		    sense_data->additional_sense_code_qualifier);
		break;
		}

	    case 0x0B:	/* ABORTED_COMMAND */
		{
		fprintf(stderr,
		    "Command Aborted, additional: %02.2X/%02.2X\n",
		    sense_data->additional_sense_code,
		    sense_data->additional_sense_code_qualifier);
		break;
		}

	    case 0x0D:	/* VOLUME_OVERFLOW */
		{
		fprintf(stderr,
		    "Volume Overflow, additional: %02.2X/%02.2X\n",
		    sense_data->additional_sense_code,
		    sense_data->additional_sense_code_qualifier);
		break;
		}

	    case 0x0E:	/* MISCOMPARE */
		{
		fprintf(stderr,
		    "Miscompare, additional: %02.2X/%02.2X\n",
		    sense_data->additional_sense_code,
		    sense_data->additional_sense_code_qualifier);
		break;
		}

	    default:
		{
		fprintf(stderr,
		    "Unknown Sense: %02.2X, additional: %02.2X/%02.2X\n",
		    sense_data->sense_key,
		    sense_data->additional_sense_code,
		    sense_data->additional_sense_code_qualifier);
		break;
		}

	    }
	}
    else
	{
	/*
	**	Some kind of vendor specific error format
	*/
	fprintf(stderr, "Error code: %02.2X\n", sense_data->error_code);
	fprintf(stderr,
	    "Sense Key: %02.2X, additional: %02.2X/%02.2X\n",
	    sense_data->sense_key,
	    sense_data->additional_sense_code,
	    sense_data->additional_sense_code_qualifier);
	}

    /*
    **  See if we should print any Sense Key specific stuff
    */
    if (sense_data->sense_key_specific_valid)
	{
	if (sense_data->command_data)
	    {
	    fprintf(stderr,
		"Command Descriptor field error,\n");
	    }
	else
	    {
	    fprintf(stderr,
		"Parameter list (data) field error,\n");
	    }
	fprintf(stderr,
	    "Field Pointer: %02.2X %02.2X (%d)\n",
	    sense_data->field_pointer_msb,
	    sense_data->field_pointer_lsb,
	    sense_data->field_pointer_lsb);
	if (sense_data->bit_pointer_valid)
	    {
	    fprintf(stderr,
		"Bit Pointer: %d\n",
		sense_data->bit_pointer);
	    }
	}

    return;
}


/*
**  CDWRITE_HANDLER - Error Handler
*/
static unsigned int cdwrite_handler(
    void *sigargs,
    void *mechargs)
{
    int *sig_condition;
    int return_status;

    sig_condition = sigargs;
    sig_condition++;

    if (status_is_bad(*sig_condition))
	{
	/*
	**  We are failing, unlock the door
	*/
	if (started_writing)
	    {
	    sync_cache();
	    }
	if (started_unit)
	    {
	    start_stop_unit(STOP);
	    }
	allow_removal(TRUE);
	/*
	**  If we elevated, go back...
	*/
	if (old_priority)
	    {
	    return_status = sys$setpri(0, 0, old_priority, 0, 0, 0);
	    insure_ok(return_status);
	    }
	}

    return SS$_RESIGNAL;
}

static unsigned long Seconds_Since(long *First, long *Second)
{
	unsigned long Seconds;
	long TempTime[2];
	struct {
	    unsigned short Year;
	    unsigned short Month;
	    unsigned short Day;
	    unsigned short Hour;
	    unsigned short Minute;
	    unsigned short Second;
	    unsigned short Hundredths;
	} TimBuf;

	(void) lib$sub_times(First, Second, TempTime);
	(void) sys$numtim(&TimBuf, TempTime);

	Seconds = (((TimBuf.Day * 24 + TimBuf.Hour) * 60 + TimBuf.Minute) *
		   60 + TimBuf.Second);
	if (TimBuf.Hundredths >= 50)
	    Seconds++;
	return(Seconds);
}

/*
**  SECONDS_TO_WRITE - Calculate how long it will take to write
**		       the passed in number of buffers.
*/
static int seconds_to_write(int buffers_to_write)
{
    return (((buffers_to_write * buffer_size) / 1024) / 150) / speed_factor;
}
