/*                                              10 November 2006.  SMS.
        Make an image file from a CD-ROM.

    P1 = CD-ROM device name.
    P2 = Output file name.

    For accurate byte counts on DVDs, compile with large-file support:
    CC /DEFINE = _LARGEFILE=1
*/

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include <descrip.h>
#include <dvidef.h>
#include <iodef.h>
#include <mntdef.h>
#include <psldef.h>
#include <starlet.h>
#include <stsdef.h>
#include <ssdef.h>

/* Use <iosbdef.h> if available.  Otherwise declare IOSB here. */

#if !defined( __VAX) && (__CRTL_VER >= 70000000)
#include <iosbdef.h>
#else /* !defined( __VAX) && (__CRTL_VER >= 70000000) */
typedef struct _iosb {
        unsigned short int iosb$w_status;       /* Final I/O status.  */
        unsigned int iosb$l_bcnt;               /* 32-bit byte count. */
        unsigned int iosb$w_dev_depend_high;    /* 16-bit dev depend. */
    } IOSB;
#endif /* !defined( __VAX) && (__CRTL_VER >= 70000000) */

/* Serious C complaint reduction. */

#define printf (void)printf

/* CD-ROM block size and buffer size parameters. */

#define BLOCK_BYTES_HD 512			/* Disk block size. */
#define BLOCK_BYTES_CD (4* BLOCK_BYTES_HD)      /* CD-ROM block size. */

/* MAX_READ_BYTES must be a power-of-two times BLOCK_BYTES_CD.
   It may be halved repeatedly near the end of the CD-ROM,
   and it must remain a multiple of BLOCK_BYTES_CD.
*/
#define MAX_READ_BYTES (32* BLOCK_BYTES_CD)     /* Max CD-ROM read size. */
#define MIN_READ_BYTES BLOCK_BYTES_CD           /* Min CD-ROM read size. */


/* sysmsg_text().  VMS system message look-up. */

char *sysmsg_text( unsigned int sts)
{
    static char sysmsg[ 257];
    unsigned short int sysmsg_len;

    $DESCRIPTOR( sysmsg_dsc, sysmsg);

    (void) sys$getmsg( sts,
                       &sysmsg_len,
                       &sysmsg_dsc,
                       0xF,
                       0);

    sysmsg[ (int)sysmsg_len] = '\0';    /* NUL-terminate the message. */

    return sysmsg;
}


/* assign_dev().  Assign a device. */

unsigned int assign_dev( struct dsc$descriptor_s *name_dsc,
                         unsigned short *chan)
{
    int sts;

    sts = sys$assign( name_dsc, chan, 0, 0, 0);

    if ((sts & STS$M_SEVERITY) != STS$M_SUCCESS)
    {
        printf( " Error in assign of %s.  sts = %%x%08x.\n",
         (*name_dsc).dsc$a_pointer, sts);

        printf( " %s.\n", sysmsg_text( sts));
    }
    return sts;
}


/* Main. */

int main( int argc, char ** argv)
{
/* Miscellaneous storage. */

int sts;                                /* System service status. */

unsigned short int chan_in;             /* CD-ROM device channel. */
struct _iosb iosb_in;                   /* I/O status block. */
unsigned int lbn;                       /* Logical block number. */
int req_bytes;                          /* Read request bytes. */

$DESCRIPTOR( name_in_dsc, "");          /* CD-ROM name descriptor. */
static char name_in_phy[ 65];           /* CD-ROM physical device */
$DESCRIPTOR( name_in_phy_dsc,           /* name and descriptor. */
 name_in_phy);

int mounted;                            /* CD-ROM already mounted. */

char *name_out;                         /* Image file name. */
int fd_out;                             /* Image file UNIX fd. */

off_t size_io;                          /* Bytes read. */
off_t size_io_total;                    /* Total bytes read. */
time_t time_start;                      /* I/O start time (s). */

unsigned char buf[ MAX_READ_BYTES];     /* I/O buffer. */

/* Item list structures for GETDVI. */

static unsigned int dvi_buf;
static int dvi_buf_len;

typedef struct
{
    short buf_len_avail;
    short itm_cod;
    unsigned int *buf;
    int *buf_len_used;
    int term;
} dvi_itmlst_t;

dvi_itmlst_t dvi_itmlst_mnt =
 { 4,
   DVI$_MNT,                    /* Mounted? */
   &dvi_buf,
   &dvi_buf_len,
   0
 };

dvi_itmlst_t dvi_itmlst_for =
 { 4,
   DVI$_FOR,                    /* Mounted foreign? */
   &dvi_buf,
   &dvi_buf_len,
   0
 };

/* Item list structures for MOUNT. */

typedef struct
{
    short buf_len_avail;
    short itm_cod;
    void *buf;
    int *buf_len_used;
} mnt_itm_t;

int mnt_flags[2] =
 { MNT$M_FOREIGN| MNT$M_NOASSIST| MNT$M_NOWRITE, 0};

struct
{
    mnt_itm_t name;  /*  */
    mnt_itm_t flags;
    int term;
} mnt_itmlst;


/* Start of executable code. */

/* Check command-line argument count. */

if (argc != 3)
{
    printf( " Usage: %s <CD-ROM_device> <out_file_name>\n",
     argv[ 0]);
}

/* Set command-line argument storage. */

name_in_dsc.dsc$a_pointer = argv[ 1];
name_in_dsc.dsc$w_length = strlen( name_in_dsc.dsc$a_pointer);

name_out = argv[ 2];

/* Allocate input device. */

sts = sys$alloc( &name_in_dsc,
                 &name_in_phy_dsc.dsc$w_length,
                 &name_in_phy_dsc,
                 0,
                 0);

if ((sts & STS$M_SEVERITY) != STS$M_SUCCESS)
{
    printf( " Error in alloc of %s.  sts = %%x%08x.\n",
     name_in_dsc.dsc$a_pointer, sts);

    printf( " %s.\n", sysmsg_text( sts));

    return sts;
}

/* NUL-terminate the physical device name. */

name_in_phy_dsc.dsc$a_pointer[ (int)name_in_phy_dsc.dsc$w_length] = '\0';

/* Open (assign) input device. */

sts = assign_dev( &name_in_phy_dsc, &chan_in);
if ((sts & STS$M_SEVERITY) != STS$M_SUCCESS)
{
    return sts;
}

/* Check status of input device.  If mounted, must be mounted foreign. */

sts = sys$getdviw( 0,                   /* Event flag nr. */
                   chan_in,             /* Channel. */
                   0,                   /* Device name. */
                   &dvi_itmlst_mnt,     /* Item list. */
                   0,                   /* IOSB. */
                   0,                   /* AST address. */
                   0,                   /* AST parameter. */
                   0);                  /* Null argument. */

if ((sts & STS$M_SEVERITY) != STS$M_SUCCESS)
{
    printf( " Error in getdvi of %s (%s).  sts = %%x%08x.\n",
     name_in_dsc.dsc$a_pointer, name_in_phy_dsc.dsc$a_pointer, sts);

    printf( " %s.\n", sysmsg_text( sts));

    return sts;
}

mounted = (dvi_buf != 0);

if (mounted)
{
    sts = sys$getdviw( 0,               /* Event flag nr. */
                       chan_in,         /* Channel. */
                       0,               /* Device name. */
                       &dvi_itmlst_for, /* Item list. */
                       0,               /* IOSB. */
                       0,               /* AST address. */
                       0,               /* AST parameter. */
                       0);              /* Null argument. */

    if ((sts & STS$M_SEVERITY) != STS$M_SUCCESS)
    {
        printf( " Error in getdvi of %s (%s).  sts = %%x%08x.\n",
         name_in_dsc.dsc$a_pointer, name_in_phy_dsc.dsc$a_pointer, sts);

        printf( " %s.\n", sysmsg_text( sts));

        return sts;
    }
}

if (mounted && (dvi_buf == 0))
{
    printf( " Device %s (%s) is mounted but not mounted foreign.\n",
     name_in_dsc.dsc$a_pointer, name_in_phy_dsc.dsc$a_pointer);

    return SS$_DEVNOTMOUNT;
}

if (! mounted)
{
    /* Release the channel to allow mounting. */

    sts = sys$dassgn( chan_in);

    /* Fill in MOUNT item list data. */

    mnt_itmlst.name.buf_len_avail = name_in_phy_dsc.dsc$w_length;
    mnt_itmlst.name.itm_cod = MNT$_DEVNAM;
    mnt_itmlst.name.buf = name_in_phy_dsc.dsc$a_pointer;

    mnt_itmlst.flags.buf_len_avail = sizeof( mnt_flags);
    mnt_itmlst.flags.itm_cod = MNT$_FLAGS;
    mnt_itmlst.flags.buf = &mnt_flags;

    mnt_itmlst.term = 0;

    /* Mount the device. */

    sts = sys$mount( &mnt_itmlst);

    if ((sts & STS$M_SEVERITY) != STS$M_SUCCESS)
    {
        printf( " Error in mount of %s (%s).  sts = %%x%08x.\n",
         name_in_dsc.dsc$a_pointer, name_in_phy_dsc.dsc$a_pointer, sts);

        printf( " %s.\n", sysmsg_text( sts));

        return sts;
    }

    printf( " %s (%s) mounted.\n",
     name_in_dsc.dsc$a_pointer, name_in_phy_dsc.dsc$a_pointer);

    /* Re-assign the device. */

    sts = assign_dev( &name_in_phy_dsc, &chan_in);
    if ((sts & STS$M_SEVERITY) != STS$M_SUCCESS)
    {
        return sts;
    }
}

/* Start time-keeping here.
   (Include initial allocation of output file.)
*/
time_start = time( NULL);

/* Open output file. */

/* fd_out = open( name_out, (O_RDWR| O_CREAT), 0777, */

fd_out = creat(
 name_out,                      /* File name. */
 0777,                          /* No mode/protection limits. */
 "alq = 32768",                 /* Allocate initial 32K block (16MB) chunk. */
 "deq = 32768",                 /* Extend in 32K block (16MB) chunks. */
 "fop = mxv, sqo, tef",         /* Create new, sequential-only, truncate. */
 "rop = wbh", "mbf = 2",        /* Write-behind.  Multi-buffer (2). */
 "mbc = 127",                   /* Maximum multi-block. */
 "rfm = fix",                   /* "Binary" attributes: */
 "mrs = 512",                   /*    fixed-length records */
 "bls = 512");                  /*    512-bytes */

if (fd_out <= 0)
{
    printf( " Error opening (creat) %s.  Errno = %d.\n",
     name_out, errno);

    printf( " %s.\n", strerror( errno));
    return errno;
}

/* Prepare for read and write operations. */

size_io_total = 0;
lbn = 0;
req_bytes = MAX_READ_BYTES;

/* Loop until even the smallest read fails. */

while (req_bytes >= MIN_READ_BYTES)
{
    /* Read a chunk. */

    sts = sys$qiow( 0,                  /* Event flag nr. */
                    chan_in,            /* Channel. */
                    IO$_READLBLK,       /* Function code. */
                    &iosb_in,           /* IOSB. */
                    0,                  /* AST address. */
                    0,                  /* AST parameter. */
                    buf,                /* P1 = buffer address. */
                    req_bytes,          /* P2 = byte count. */
                    lbn,                /* P3 = logical block nr. */
                    0,                  /* P4. */
                    0,                  /* P5. */
                    0);                 /* P6. */

    size_io = iosb_in.iosb$l_bcnt;

    /* If initial status is success, use final status. */

    if ((sts & STS$M_SEVERITY) == STS$M_SUCCESS)
    {
        sts = iosb_in.iosb$w_status;
    }

    if ((sts & STS$M_SEVERITY) != STS$M_SUCCESS)
    {
        if ((sts == SS$_ILLBLKNUM) || (sts == SS$_MEDOFL))
        {
            /* Expected failure.  Reduce read request size, and retry. */
            req_bytes = req_bytes/ 2;
        }
        else
        {
            printf( " Error in read (QIOW) on %s (%s).  sts = %%x%08x.\n",
             name_in_dsc.dsc$a_pointer, name_in_phy_dsc.dsc$a_pointer, sts);
    
            printf( " %s.\n", sysmsg_text( sts));
            return sts;
        }
    }

    /* If read got data, write them out, and increment counters. */

    if (size_io > 0)
    {
        size_io = write( fd_out, buf, size_io);

        if (size_io <= 0)
        {
            printf( " Error writing output file.  Errno = %d.\n",
             errno);

            printf( " %s.\n", strerror( errno));
        }

        size_io_total += size_io;
        lbn += size_io/ BLOCK_BYTES_HD;
    }
}

#if __USE_OFF64_T
printf( " Total bytes read = %8lld.\n", size_io_total);
#else /* __USE_OFF64_T */
printf( " Total bytes read = %8d.\n", size_io_total);
#endif /* __USE_OFF64_T [else] */

printf( " Time (s) = %6d.\n", time( NULL)- time_start);

/* Close files. */

sts = sys$dassgn( chan_in);

/* If we mounted the CD-ROM device, then dismount it. */

if (! mounted)
{
    sts = sys$dismou( &name_in_phy_dsc, 0);

    if ((sts & STS$M_SEVERITY) != STS$M_SUCCESS)
    {
        printf( " Error in dismou of %s.  sts = %%x%08x.\n",
         name_in_phy_dsc.dsc$a_pointer, sts);

        printf( " %s.\n", sysmsg_text( sts));
    }

    printf( " %s (%s) dismounted.\n",
     name_in_dsc.dsc$a_pointer, name_in_phy_dsc.dsc$a_pointer);

    sts = sys$dalloc( &name_in_phy_dsc, PSL$C_USER);

    if ((sts & STS$M_SEVERITY) != STS$M_SUCCESS)
    {
        printf( " Error in dalloc of %s.  sts = %%x%08x.\n",
         name_in_phy_dsc.dsc$a_pointer, sts);

        printf( " %s.\n", sysmsg_text( sts));
    }
}

sts = close( fd_out);

return sts;
}

