Discussion:
Msg HHC00102E from Hyperion when using DASDLOAD to build MFT starter system.
(too old to reply)
stephen.orso@yahoo.com [hercules-390]
2016-03-09 13:23:13 UTC
Permalink
Has anyone received message HHC00102E, shown below, when using DASDLOAD to create empty files?


HHC02567I File dasdsetup\work01.ctl[0008]: sys1.sysut4 empty cyl 50 20 0
fthread_create: MyCreateThread failed
HHC00102E Error in function create_thread(): Resource temporarily unavailable


HHC00102E and the previous message are repeated multiple (2,756) times, and then DASDLOAD continues with the next control card. Different executions result in different message repetition frequencies and triggering control cards. There is "no" other activity on the system, a Windows 10 Home 4G 32-bit Inspiron 1545 with a SSD.


The issue is intermittent and occurs maybe 20% of the time (anecdotal; I have not--yet--done controlled trials). The issue occurs with both empty datasets and empty PDS datasets (which are not really empty; DASDLOAD formats an empty PDS directory). I have not seen the issue when the control card processes an XMIT file. The issue occurs with the Hyperion build posted on 6-Oct-15 at http://www.softdevlabs.com/hyperion.html http://www.softdevlabs.com/hyperion.html and with a build from the latest commit on 22-Feb-16. The later build was done on Win10 Home using VS 2015 CE as the IDE, VS2008 C++, and the Win 7.1 SDK. (VS 2015 CE has very nice Github integration.)


I have resulting logs and created DASD volumes for each of those runs and from a clean run from the 6-Oct-15 build, about 40MB total zipped.


I believe that when the issue occurs with an empty dataset, there is no long term impact. But when the issue occurs creating an "empty" PDS, I believe the directory structure is corrupted. I haven't tried repeating the DASDLOAD script until I get HHC00102E on an empty PDS, then building MVT, and then looking for abend S106 RC=F, but I may.


This was discovered as I built (multiple times) MVT 21.8 using Jay Maynard's instruction set at http://www.conmicro.com/hercos360/index.html http://www.conmicro.com/hercos360/index.html


If others have experienced this, please let me know. If not, I'll test 3.12 and/or post an issue to Github as appropriate.


Best Regards,
Steve Orso
Rahim Azizarab rahimazizarab@yahoo.com [hercules-390]
2016-03-09 14:10:05 UTC
Permalink
You might find zdasdload from Paul Edwards a better alternative for it can write iplable dasd also.  I was able to compile it with Sandhawk after minor modification.   Here is the code as it stands now.

/* ZDASDLOAD.C   (c) Copyright  Roger Bowler, 1999-2009                */
/*              Hercules DASD Utilities: DASD image loader           */
 
// $Id: zdasdload.c 5125 2009-01-23 12:01:44Z bernard $
 
/*-------------------------------------------------------------------*/
/* This program creates a virtual iplable DASD volume from a list of */
/* datasets previously unloaded using the TSO XMIT command.          */
/*-------------------------------------------------------------------*/
 
/*-------------------------------------------------------------------*/
/* Additional credits:                                               */
/*      Corrections to CVOL initialization logic by Jay Maynard      */
/*      IEBCOPY native dataset support by Ronen Tzur                 */
/*-------------------------------------------------------------------*/
 
// $Log$
// Revision 1.54  2008/11/04 04:50:46  fish
// Ensure consistent utility startup
//
// Revision 1.53  2007/06/23 00:04:08  ivan
// Update copyright notices to include current year (2007)
//
// Revision 1.52  2007/05/24 21:13:27  rbowler
// Circumvent MSVC optimizer bug in cvol_initialize function
//
// Revision 1.51  2006/12/08 09:43:19  jj
// Add CVS message log
//
 
#include "hstdinc.h"
 
#include "hercules.h"
#include "dasdblks.h"
 
/*-------------------------------------------------------------------*/
/* Internal table sizes                                              */
/*-------------------------------------------------------------------*/
#define MAXDBLK 10000                   /* Maximum number of directory
                                           blocks per dataset        */
#define MAXTTR  50000                   /* Maximum number of TTRs
                                           per dataset               */
#define MAXDSCB 1000                    /* Maximum number of DSCBs   */
 
/*-------------------------------------------------------------------*/
/* Internal macro definitions                                        */
/*-------------------------------------------------------------------*/
#define CASERET(s)      case s: return (#s)
#define XMINF           info_msg
#define XMINFF          info_msg
#define XMERR           printf
#define XMERRF          printf
 
#define R0_DATALEN      8
#define IPL1_KEYLEN     4
#define IPL1_DATALEN    24
#define IPL2_KEYLEN     4
#define IPL2_DATALEN    144
#define VOL1_KEYLEN     4
#define VOL1_DATALEN    80
 
#define EBCDIC_END      "\xC5\xD5\xC4"
#define EBCDIC_TXT      "\xE3\xE7\xE3"
 
/*-------------------------------------------------------------------*/
/* Definition of LOGREC header record                                */
/*-------------------------------------------------------------------*/
typedef struct _DIPHDR {
        HWORD   recid;                  /* Record identifier (0xFFFF)*/
        HWORD   bcyl;                   /* Extent begin cylinder     */
        HWORD   btrk;                   /* Extent begin track        */
        HWORD   ecyl;                   /* Extent end cylinder       */
        HWORD   etrk;                   /* Extent end track          */
        BYTE    resv;                   /* Unused                    */
        BYTE    restart[7];             /* Restart area BBCCHHR      */
        HWORD   trkbal;                 /* Bytes remaining on track  */
        HWORD   trklen;                 /* Total bytes on track      */
        BYTE    reused[7];              /* Last reused BBCCHHR       */
        HWORD   lasthead;               /* Last track on cylinder    */
        HWORD   trklen90;               /* 90% of track length       */
        BYTE    devcode;                /* Device type code          */
        BYTE    cchh90[4];              /* 90% full track CCHH       */
        BYTE    switches;               /* Switches                  */
        BYTE    endid;                  /* Check byte (0xFF)         */
    } DIPHDR;
 
/*-------------------------------------------------------------------*/
/* Definition of internal extent descriptor array entry              */
/*-------------------------------------------------------------------*/
typedef struct _EXTDESC {
        U16     bcyl;                   /* Begin cylinder            */
        U16     btrk;                   /* Begin track               */
        U16     ecyl;                   /* End cylinder              */
        U16     etrk;                   /* End track                 */
        U16     ntrk;                   /* Number of tracks          */
    } EXTDESC;
 
/*-------------------------------------------------------------------*/
/* Definition of internal TTR conversion table array entry           */
/*-------------------------------------------------------------------*/
typedef struct _TTRCONV {
        BYTE    origttr[3];             /* TTR in original dataset   */
        BYTE    outpttr[3];             /* TTR in output dataset     */
    } TTRCONV;
 
/*-------------------------------------------------------------------*/
/* Definitions for dataset initialization methods                    */
/*-------------------------------------------------------------------*/
#define METHOD_EMPTY    0
#define METHOD_XMIT     1
#define METHOD_DIP      2
#define METHOD_CVOL     3
#define METHOD_VTOC     4
#define METHOD_VS       5
#define METHOD_SEQ      6
 
/*-------------------------------------------------------------------*/
/* Static data areas                                                 */
/*-------------------------------------------------------------------*/
static  BYTE eighthexFF[] = {0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff};
BYTE twelvehex00[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
BYTE cvol_low_key[] = {0, 0, 0, 0, 0, 0, 0, 1};
 
 
/* Note that the copious comments below were written by Paul
   Edwards and are released to the public domain.
 
   At IPL time, the hardware places a single CCW (8 bytes)
   at location 0 in memory, and then executes it. That
   single CCW looks like:
   0x02, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x18
   The 0x02 is an IPL read. An "IPL read" is different
   from a normal read (0x06) in that the IPL read includes a
   seek to cylinder 0, head 0. The next 3 bytes are location
   0, then we have 0x60 which is the command chaining bit (0x40)
   plus the SLI bit (0x20) which says to ignore a length
   mismatch, then 0x00 which is a reserved byte, then
   a 2-byte length of 0x0018, ie 24 bytes. This CCW is
   thus expecting the first record of the disk to be 24
   bytes, and to overlay memory address 0 with that 24
   bytes. Don't forget that this CCW is already in location 0,
   so it will be overlaid (but that's OK, it has already
   been fetched at that point). Also don't forget that the
   chaining bit is on, so it will continue executing the
   next CCW in memory. ie since the CCW at location 0 has
   already been executed, it will now execute the CCW
   at location 8. There didn't use to be anything at this
   location, but after reading 24 bytes, there should be
   a new CCW at location 8.
   
   For more information on IPL see pages 4-35 to 4-37 in 
   GA-22-7000-10 S/370 Principles of Operation available here:
   http://www.bitsavers.org/
   pdf/ibm/370/princOps/GA22-7000-10_370_Principles_of_Operation_Sep87.pdf
   
      
   Record 1 of an IPLable DASD consists of 24 bytes, plus a
   4-byte key of "IPL1".
   These 24 bytes are loaded at location 0 in memory.
   The first 8 data bytes are a PSW of 0, which is never
   actually used. This field is known as "NEWPSW" and later
   on in the IPL process, the PSW located at location 0 is
   indeed used. But that will only happen after the IPL
   program is loaded from record 4.
*/
 
BYTE iplpsw[8] =    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
 
 
/* The first CCW starts with a command of x'06' which means to
   read a data block. Normally you would use a command of x'07'
   to position to the right cylinder/head/record, but we do not
   need to do this at the moment, because the disk is already
   correctly positioned ready to read the next record of interest,
   which is record 2.
   
   After the command x'06' we have an address of 0x003a98, which
   is where the data will be loaded as it is read from disk.
   Next we have "flags" of 0x60. The 0x40 is the CC bit which
   means after executing this CCW, go and execute the next one,
   also known as "CCW chaining". The 0x20 is the SLI bit which
   says don't worry if the length of the block on the disk is
   different from what we have specified in the CCW.
   
   After the 0x60 we have a reserved byte of 0x00 and then
   the next 2 bytes are the length, in this case 0x60, ie
   we intend to read 96 bytes. This is longer than we will
   actually have data for, as we will be reading the IPL2
   record which in our case is only 4 * 8 + 6 + 5 = 43 bytes.
   
   Also note that because we are using a command of x'06',
   we will only be reading the data portion of the IPL2
   record, ignoring the key which is the bytes "IPL2". If
   we had use a command of x'0e' we would have the text
   "IPL2" including in the output from the read. We are
   not at all interested in the text "IPL2".
*/
 
BYTE iplccw1[8] =   {0x06, 0x00, 0x3A, 0x98, 0x60, 0x00, 0x00, 0x60};
 
 
/* The second CCW starts with command of x'08' which is TIC,
   or "Transfer in Channel". This command contains an address
   of 0x3a98, which means it will start executing whatever is
   found at address 0x3a98. We are expecting that will now
   be filled in by data from the previous read. The 0x40 bit is
   set as well, which tells it to automatically execute the
   CCW at that new location.
*/
 
BYTE iplccw2[8] =   {0x08, 0x00, 0x3A, 0x98, 0x00, 0x00, 0x00, 0x00};
 
 
/* Next we have the data that is designed to go into the IPL2
   record. This is just the data, not the key. There is a 4-byte
   key preceding the data, containing the text "IPL2". The CCWs
   from IPL1 will load all this data at address 0x3A98. The data
   below consists of 4 CCWs, 8 bytes each, ie 32 bytes. Those 32
   bytes will take us from 0x3A98 to 0x3AB8. At 0x3AB8 we have
   6 bytes containing the BBCCHH, followed by 5 bytes containing
   the CCHHR. IPL2 is normally designed to load record 4 of
   cylinder 0, head 0. That is why the very last byte below
   is 0x04. The real IPL program is designed to be located in
   record 4 of cylinder 0 head 0, and that is where you will
   find real code instead of just CCWs. That IPL program will
   be read in to location 0 in memory, and the first 8 bytes
   will be the "new PSW" which will execute real code. Note that
   record 3 of cylinder 0 head 0 is the "VOL1" record which
   contains the name of the disk. This record is unusual in
   that it has a 4-byte key of "VOL1" and also the first 4
   bytes of the data contain the same "VOL1" string, so when
   you dump the key+data you will see "VOL1" twice.
*/
 
BYTE ipl2data[] =   {0x07, 0x00, 0x3A, 0xB8, 0x40, 0x00, 0x00, 0x06,
                     /* 07 ... = go to the track number found at
                        address 003AB8, which is the 6-byte
                        BBCCHH. The flag of 0x40 says to chain
                        the CCWs, and the length of 0x06 is
                        the number of bytes for the BBCCHH. */
                     0x31, 0x00, 0x3A, 0xBE, 0x40, 0x00, 0x00, 0x05,
                     /* 31 ... = search for the identifier found at
                        memory address 003ABE. This is the CCHHR
                        data below. You can see that this is
                        cylinder 0, head 0, record 4, which is
                        the normal place for the IPL program. 
                        Note that the flag is 0x40, signalling
                        continued CCW chaining, and the length
                        is 5, which is how long the CCHHR is. */
                     0x08, 0x00, 0x3A, 0xA0, 0x00, 0x00, 0x00, 0x00,
                     /* 08 ... = go back (TIC) to address 0x3AA0
                        which is the search (0x31) until
                        it is successful */
                     0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x7f, 0xff,
                     /* 06 ... = read the data block. The first
                        3 0x00 constitute the memory address to
                        load the data to, ie location 0 in
                        memory. The flag of 0x20 is SLI to ignore
                        length errors, and the length of the data
                        is 0x7fff, ie approx 32K. Note that we
                        don't have bit 0x40 (chain CCW) set, so
                        this is the end of the CCW chain and we
                        will now load the PSW stored at location 0
                        in memory and start executing code. */
                     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*BBCCHH*/
                     0x00, 0x00, 0x00, 0x00, 0x04}; /*CCHHR*/
 
 
BYTE noiplpsw[8] =  {0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F};
BYTE noiplccw1[8] = {0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
BYTE noiplccw2[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
 
/* Information message level: 0=None, 1=File name, 2=File information,
   3=Member information, 4=Text units, record headers, 5=Dump data */
int  infolvl = 1;
 
/*-------------------------------------------------------------------*/
/* Subroutine to display command syntax and exit                     */
/*-------------------------------------------------------------------*/
static void
argexit ( int code )
{
    fprintf (stderr,
            "zdasdload creates an iplable DASD image file from a list "
            "of TSO XMIT files\n"
            "Syntax:\tdasdload [options] ctlfile outfile [msglevel]\n"
            "where:\tctlfile  = name of input control file\n"
            "\toutfile  = name of DASD image file to be created\n"
            "\tmsglevel = Value 0-5 controls output verbosity\n"
            "\noptions:\n"
            "\t-0:   no compression (default)\n"
            "\t-a:   output disk will include alternate cylinders\n"
#ifdef CCKD_COMPRESS_ZLIB
            "\t-z:   compress using zlib\n"
#endif
#ifdef CCKD_COMPRESS_BZIP2
            "\t-bz2: compress using bzip2\n"
#endif
            );
    if (sizeof(off_t) > 4)
        fprintf (stderr,
            "\t-lfs: create single large output file\n"
            );
    exit(code);
} /* end function argexit */
 
/*-------------------------------------------------------------------*/
/* Subroutine to display an informational message                    */
/*-------------------------------------------------------------------*/
static void
info_msg (int lvl, char *msg, ...)
{
va_list vl;
 
    if (infolvl >= lvl)
    {
        va_start(vl, msg);
        vprintf (msg, vl); 
    }
} /* end function info_msg */
 
/*-------------------------------------------------------------------*/
/* Subroutine to load a S/390 integer value from a buffer            */
/*-------------------------------------------------------------------*/
static int
make_int (BYTE *src, int srclen)
{
int             result = 0;             /* Result accumulator        */
int             i;                      /* Array subscript           */
 
    for (i=0; i < srclen; i++)
    {
        result <<= 8;
        result |= src[i];
    }
 
    return result;
 
} /* end function make_int */
 
/*-------------------------------------------------------------------*/
/* Subroutine to return the name of a dataset organization           */
/*-------------------------------------------------------------------*/
static char *
dsorg_name (BYTE  *dsorg)
{
static char     name[8];                /* Name of dsorg             */
 
    if (dsorg[0] & DSORG_IS)
        strcpy (name, "IS");
    else if (dsorg[0] & DSORG_PS)
        strcpy (name, "PS");
    else if (dsorg[0] & DSORG_DA)
        strcpy (name, "DA");
    else if (dsorg[0] & DSORG_PO)
        strcpy (name, "PO");
 
    if (dsorg[0] & DSORG_U) strcat (name, "U");
 
    return name;
} /* end function dsorg_name */
 
/*-------------------------------------------------------------------*/
/* Subroutine to return the name of a record format                  */
/*-------------------------------------------------------------------*/
static char *
recfm_name (BYTE *recfm)
{
static char     name[8];                /* Name of record format     */
 
    switch (recfm[0] & RECFM_FORMAT) {
    case RECFM_FORMAT_V:
        strcpy (name, "V"); break;
    case RECFM_FORMAT_F:
        strcpy (name, "F"); break;
    case RECFM_FORMAT_U:
        strcpy (name, "U"); break;
    default:
        strcpy (name,"??");
    } /* end switch */
 
    if (recfm[0] & RECFM_TRKOFLOW) strcat (name, "T");
    if (recfm[0] & RECFM_BLOCKED) strcat (name, "B");
    if (recfm[0] & RECFM_SPANNED) strcat (name, "S");
 
    switch (recfm[0] & RECFM_CTLCHAR) {
    case RECFM_CTLCHAR_A:
        strcpy (name, "A"); break;
    case RECFM_CTLCHAR_M:
        strcpy (name, "M"); break;
    } /* end switch */
 
    return name;
} /* end function recfm_name */
 
/*-------------------------------------------------------------------*/
/* Subroutine to return the name of a DASD device from the UCB type  */
/*-------------------------------------------------------------------*/
static char *
dasd_name (FWORD ucbtype)
{
    if (ucbtype[2] != 0x20) return "????";
 
    switch (ucbtype[3]) {
    case 0x01: return "2311";
    case 0x02: return "2301";
    case 0x03: return "2303";
    case 0x04: if (ucbtype[1] == 0x00) return "2302";
               else return "9345";
    case 0x05: return "2321";
    case 0x06: return "2305-1";
    case 0x07: return "2305-2";
    case 0x08: return "2314";
    case 0x09: return "3330";
    case 0x0A: return "3340";
    case 0x0B: return "3350";
    case 0x0C: return "3375";
    case 0x0D: return "3330-11";
    case 0x0E: return "3380";
    case 0x0F: return "3390";
    } /* end switch(key) */
 
    return "????";
 
} /* end function dasd_name */
 
/*-------------------------------------------------------------------*/
/* Subroutine to return the UCBTYPE of a DASD device                 */
/*-------------------------------------------------------------------*/
static U32
ucbtype_code (U16 devtype)
{
    switch (devtype) {
    case 0x2311: return 0x30002001;
    case 0x2301: return 0x30402002;
    case 0x2303: return 0x30002003;
    case 0x2302: return 0x30002004;
    case 0x2321: return 0x30002005;
    case 0x2305: return 0x30002006;
    case 0x2314: return 0x30C02008;
    case 0x3330: return 0x30502009;
    case 0x3340: return 0x3050200A;
    case 0x3350: return 0x3050200B;
    case 0x3375: return 0x3050200C;
    case 0x3380: return 0x3050200E;
    case 0x3390: return 0x3050200F;
    case 0x9345: return 0x30502004;
    } /* end switch(key) */
 
    return 0;
 
} /* end function ucbtype_code */
 
/*-------------------------------------------------------------------*/
/* Subroutine to calculate relative track address                    */
/* Input:                                                            */
/*      cyl     Cylinder number                                      */
/*      head    Head number                                          */
/*      heads   Number of heads per cylinder                         */
/*      numext  Number of extents                                    */
/*      xarray  Array containing 1-16 extent descriptions            */
/* Output:                                                           */
/*      The return value is the relative track number,               */
/*      or -1 if an error occurred.                                  */
/*-------------------------------------------------------------------*/
static int
calculate_ttr (int cyl, int head, int heads, int numext,
                EXTDESC xarray[])
{
int     i;                              /* Array subscript           */
int     track;                          /* Relative track number     */
 
    /* Search the extent descriptor array */
    for (i = 0, track = 0; i < numext; track += xarray[i++].ntrk)
    {
        if (cyl < xarray[i].bcyl || cyl > xarray[i].ecyl)
            continue;
 
        if (cyl == xarray[i].bcyl && head < xarray[i].btrk)
            continue;
 
        if (cyl == xarray[i].ecyl && head > xarray[i].etrk)
            continue;
 
        track += (cyl - xarray[i].bcyl) * heads
                - xarray[i].btrk + head;
        break;
    } /* end for(i) */
 
    /* Error if track was not found in extent table */
    if (i == numext)
    {
        XMERRF ("HHCDL033E CCHH=%4.4X%4.4X not found in extent table\n",
                cyl, head);
        return -1;
    }
 
    /* Return relative track number */
    return track;
} /* end function calculate_ttr */
 
/*-------------------------------------------------------------------*/
/* Subroutine to read IPL text from an EBCDIC object file            */
/* Input:                                                            */
/*      iplfnm  Name of EBCDIC card image object file                */
/*      iplbuf  Address of buffer in which to build IPL text record  */
/*      buflen  Length of buffer                                     */
/* Output:                                                           */
/*      The return value is the length of the IPL text built         */
/*      in the buffer if successful, or -1 if error                  */
/* Note:                                                             */
/*      Only TXT records are processed; ESD and RLD records are      */
/*      ignored because the IPL text is non-relocatable and is       */
/*      assumed to have zero origin.  An END card must be present.   */
/*-------------------------------------------------------------------*/
static int
read_ipl_text (char *iplfnm, BYTE *iplbuf, int buflen)
{
int             rc;                     /* Return code               */
int             ipllen = 0;             /* Length of IPL text        */
int             txtlen;                 /* Byte count from TXT card  */
int             txtadr;                 /* Address from TXT card     */
int             tfd;                    /* Object file descriptor    */
BYTE            objrec[80];             /* Object card image         */
char            pathname[MAX_PATH];     /* iplfnm in host path format*/
 
    fprintf (stderr, "read_ipl_text \n");
    /* Open the object file */
    hostpath(pathname, iplfnm, sizeof(pathname));
    tfd = open (pathname, O_RDONLY|O_BINARY);
    if (tfd < 0)
    {
        XMERRF ("HHCDL034E Cannot open %s: %s\n",
                iplfnm, strerror(errno));
        return -1;
    }
 
    /* Read the object file */
    while (1)
    {
        /* Read a card image from the object file */
        rc = read (tfd, objrec, 80);
        if (rc < 80)
        {
            XMERRF ("HHCDL035E Cannot read %s: %s\n",
                    iplfnm, strerror(errno));
            close (tfd);
            return -1;
        }
 
        /* Column 1 of each object card must contain X'02' */
        if (objrec[0] != 0x02)
        {
            XMERRF ("HHCDL036E %s is not a valid object file\n",
                    iplfnm);
            close (tfd);
            return -1;
        }
 
        /* Exit if END card has been read */
        if (memcmp(objrec+1, EBCDIC_END, 3) == 0)
            break;
 
        /* Ignore any cards which are not TXT cards */
        if (memcmp(objrec+1, EBCDIC_TXT, 3) != 0)
            continue;
 
        /* Load the address from TXT card columns 6-8 */
        txtadr = (objrec[5] << 16) | (objrec[6] << 8) | objrec[7];
 
        /* Load the byte count from TXT card columns 11-12 */
        txtlen = (objrec[10] << 8) | objrec[11];
 
        XMINFF (5, "HHCDL037I IPL text address=%6.6X length=%4.4X\n",
                txtadr, txtlen);
 
        /* Check that the byte count is valid */
        if (txtlen > 56)
        {
            XMERRF ("HHCDL038E TXT record in %s has invalid count %d\n",
                    iplfnm, txtlen);
            close (tfd);
            return -1;
        }
 
        /* Check that the text falls within the buffer */
        if (txtadr + txtlen > buflen)
        {
            XMERRF ("HHCDL039E IPL text in %s exceeds %d bytes\n",
                    iplfnm, buflen);
            close (tfd);
            return -1;
        }
 
        /* Copy the IPL text to the buffer */
        memcpy (iplbuf + txtadr, objrec+16, txtlen);
 
        /* Update the total size of the IPL text */
        if (txtadr + txtlen > ipllen)
            ipllen = txtadr + txtlen;
 
    } /* end while */
 
    return ipllen;
} /* end function read_ipl_text */
 
/*-------------------------------------------------------------------*/
/* Subroutine to initialize the output track buffer                  */
/* Input:                                                            */
/*      trklen  Track length of virtual output device                */
/*      trkbuf  Pointer to track buffer                              */
/*      cyl     Cylinder number on output device                     */
/*      head    Head number on output device                         */
/* Output:                                                           */
/*      usedv   Number of bytes written to track of virtual device   */
/*-------------------------------------------------------------------*/
static void
init_track (int trklen, BYTE *trkbuf, int cyl, int head, int *usedv)
{
CKDDASD_TRKHDR *trkhdr;                 /* -> Track header           */
CKDDASD_RECHDR *rechdr;                 /* -> Record header          */
 
    fprintf (stderr, "init_track \n");
    /* Clear the track buffer to zeroes */
    memset (trkbuf, 0, trklen);
 
    /* Build the home address in the track buffer */
    trkhdr = (CKDDASD_TRKHDR*)trkbuf;
    trkhdr->bin = 0;
    trkhdr->cyl[0] = (cyl >> 8) & 0xFF;
    trkhdr->cyl[1] = cyl & 0xFF;
    trkhdr->head[0] = (head >> 8) & 0xFF;
    trkhdr->head[1] = head & 0xFF;
 
    /* Build a standard record zero in the track buffer */
    rechdr = (CKDDASD_RECHDR*)(trkbuf + CKDDASD_TRKHDR_SIZE);
    rechdr->cyl[0] = (cyl >> 8) & 0xFF;
    rechdr->cyl[1] = cyl & 0xFF;
    rechdr->head[0] = (head >> 8) & 0xFF;
    rechdr->head[1] = head & 0xFF;
    rechdr->rec = 0;
    rechdr->klen = 0;
    rechdr->dlen[0] = (R0_DATALEN >> 8) & 0xFF;
    rechdr->dlen[1] = R0_DATALEN & 0xFF;
 
    /* Set number of bytes used in track buffer */
    *usedv = CKDDASD_TRKHDR_SIZE + CKDDASD_RECHDR_SIZE + R0_DATALEN;
 
    /* Build end of track marker at end of buffer */
    memcpy (trkbuf + *usedv, eighthexFF, 8);
 
} /* end function init_track */
 
/*-------------------------------------------------------------------*/
/* Subroutine to write track buffer to output file                   */
/* Input:                                                            */
/*      cif     -> CKD image file descriptor                         */
/*      ofname  Output file name                                     */
/*      heads   Number of tracks per cylinder on output device       */
/*      trklen  Track length of virtual output device                */
/* Input/output:                                                     */
/*      usedv   Number of bytes written to track of virtual device   */
/*      reltrk  Relative track number on output device               */
/*      cyl     Cylinder number on output device                     */
/*      head    Head number on output device                         */
/* Output:                                                           */
/*      The return value is 0 if successful, -1 if error occurred.   */
/*-------------------------------------------------------------------*/
static int
write_track (CIFBLK *cif, char *ofname, int heads, int trklen,
             int *usedv, int *reltrk, int *cyl, int *head)
{
int             rc;                     /* Return code               */
 
    fprintf (stderr, "write_track \n");
    UNREFERENCED(ofname);
    UNREFERENCED(trklen);
 
    /* Don't overwrite HA */
    if (*usedv == 0)
        *usedv = CKDDASD_TRKHDR_SIZE;
 
    /* Build end of track marker at end of buffer */
    memcpy (cif->trkbuf + *usedv, eighthexFF, 8);
    cif->trkmodif = 1;
 
    /* Reset values for next track */
    (*reltrk)++;
    (*head)++;
    if (*head >= heads)
    {
        (*cyl)++;
        *head = 0;
    }
    *usedv = 0;
 
    /* Read the next track */
    if (*cyl < (int)cif->devblk.ckdcyls)
    {
        rc = read_track (cif, *cyl, *head);
        if (rc < 0) return -1;
    }
 
    return 0;
} /* end function write_track */
 
/*-------------------------------------------------------------------*/
/* Subroutine to add a data block to the output track buffer         */
/* Input:                                                            */
/*      cif     -> CKD image file descriptor                         */
/*      ofname  Output file name                                     */
/*      blk     Pointer to data block                                */
/*      keylen  Key length                                           */
/*      datalen Data length                                          */
/*      devtype Output device type                                   */
/*      heads   Number of tracks per cylinder on output device       */
/*      trklen  Track length of virtual output device                */
/*      maxtrk  Maximum number of tracks to be written               */
/* Input/output:                                                     */
/*      usedv   Number of bytes written to track of virtual device   */
/*      usedr   Number of bytes written to track, calculated         */
/*              according to the formula for a real device           */
/*      trkbal  Number of bytes remaining on track, calculated       */
/*              according to the formula for a real device           */
/*      reltrk  Relative track number on output device               */
/*      cyl     Cylinder number on output device                     */
/*      head    Head number on output device                         */
/*      rec     Record number on output device                       */
/* Output:                                                           */
/*      The return value is 0 if successful, -1 if error occurred.   */
/*-------------------------------------------------------------------*/
static int
write_block (CIFBLK *cif, char *ofname, DATABLK *blk, int keylen,
            int datalen, U16 devtype, int heads, int trklen,
            int maxtrk, int *usedv, int *usedr,
            int *trkbal, int *reltrk, int *cyl, int *head, int *rec)
{
int             rc;                     /* Return code               */
int             cc;                     /* Capacity calculation code */
CKDDASD_RECHDR *rechdr;                 /* -> Record header          */
 
    UNREFERENCED(devtype);
 
    /* Determine whether record will fit on current track */
    cc = capacity_calc (cif, *usedr, keylen, datalen,
                        usedr, trkbal, NULL, NULL, NULL, NULL, NULL,
                        NULL, NULL, NULL, NULL, NULL);
    if (cc < 0) return -1;
 
    /* Move to next track if record will not fit */
    if (cc > 0 && *usedr > 0)
    {
        /* Write current track to output file */
        rc = write_track (cif, ofname, heads, trklen,
                          usedv, reltrk, cyl, head);
        if (rc < 0) return -1;
 
        /* Clear bytes used and record number for new track */
        *usedr = 0;
        *rec = 0;
 
        /* Determine whether record will fit on new track */
        cc = capacity_calc (cif, *usedr, keylen, datalen,
                            usedr, trkbal, NULL, NULL, NULL, NULL,
                            NULL, NULL, NULL, NULL, NULL, NULL);
        if (cc < 0) return -1;
 
    } /* end if */
 
    /* Error if record will not even fit on an empty track */
    if (cc > 0)
    {
        XMERRF ("HHCDL040E Input record CCHHR=%2.2X%2.2X%2.2X%2.2X%2.2X "
                "exceeds output device track size\n",
                blk->cyl[0], blk->cyl[1],
                blk->head[0], blk->head[1], blk->rec);
        return -1;
    }
 
    /* Determine whether end of extent has been reached */
    if (*reltrk >= maxtrk)
    {
        XMERRF ("HHCDL041E Dataset exceeds extent size: reltrk=%d, "
                "maxtrk=%d\n",
                *reltrk, maxtrk);
        return -1;
    }
 
    /* Build home address and record 0 if new track */
    if (*usedv == 0)
    {
        init_track (trklen, cif->trkbuf, *cyl, *head, usedv);
    }
 
    /* Double check that record will not exceed virtual track size */
    if (*usedv + CKDDASD_RECHDR_SIZE + keylen + datalen + 8
        > trklen)
    {
        XMERRF ("HHCDL042E Input record CCHHR=%2.2X%2.2X%2.2X%2.2X%2.2X "
                "exceeds virtual device track size\n",
                blk->cyl[0], blk->cyl[1],
                blk->head[0], blk->head[1], blk->rec);
        return -1;
    }
 
    /* Add data block to virtual track buffer */
    (*rec)++;
    rechdr = (CKDDASD_RECHDR*)(cif->trkbuf + *usedv);
    rechdr->cyl[0] = (*cyl >> 8) & 0xFF;
    rechdr->cyl[1] = *cyl & 0xFF;
    rechdr->head[0] = (*head >> 8) & 0xFF;
    rechdr->head[1] = *head & 0xFF;
    rechdr->rec = *rec;
    rechdr->klen = keylen;
    rechdr->dlen[0] = (datalen >> 8) & 0xFF;
    rechdr->dlen[1] = datalen & 0xFF;
    *usedv += CKDDASD_RECHDR_SIZE;
    memcpy (cif->trkbuf + *usedv, blk->kdarea, keylen + datalen);
    *usedv += keylen + datalen;
    cif->trkmodif = 1;
 
    return 0;
} /* end function write_block */
 
/*-------------------------------------------------------------------*/
/* Subroutine to write track zero                                    */
/* Input:                                                            */
/*      cif     -> CKD image file descriptor                         */
/*      ofname  Output file name                                     */
/*      volser  Volume serial number (ASCIIZ)                        */
/*      devtype Output device type                                   */
/*      heads   Number of tracks per cylinder on output device       */
/*      trklen  Track length of virtual output device                */
/*      iplfnm  Name of file containing IPL text object deck         */
/* Output:                                                           */
/*      reltrk  Next relative track number on output device          */
/*      outcyl  Cylinder number of next track on output device       */
/*      outhead Head number of next track on output device           */
/*      The return value is 0 if successful, -1 if error occurred.   */
/*-------------------------------------------------------------------*/
static int
write_track_zero (CIFBLK *cif, char *ofname, char *volser, U16 devtype,
            int heads, int trklen, char *iplfnm,
            int *reltrk, int *outcyl, int *outhead)
{
int             rc;                     /* Return code               */
int             outusedv = 0;           /* Output bytes used on track
                                           of virtual device         */
int             outusedr = 0;           /* Output bytes used on track
                                           of real device            */
int             outtrkbr = 0;           /* Output bytes remaining on
                                           track of real device      */
int             outtrk = 0;             /* Output relative track     */
int             outrec = 0;             /* Output record number      */
int             keylen;                 /* Key length                */
int             datalen;                /* Data length               */
int             maxtrks = 1;            /* Maximum track count       */
DATABLK        *datablk;                /* -> data block             */
static BYTE     buf[32767];           /* Buffer for data block     */
int             separate = 0;           /* Is IPL code a
    fprintf (stderr, "write_track_zero \n");
  separate file on the disk instead of the normal situation of
  placing it in record 4 of track 0? */
 
    if ((iplfnm != NULL) && (strcmp(iplfnm, "separate") == 0))
    {
        separate = 1;
    }
    
    /* For 2311 the IPL text will not fit on track 0 record 4,
       so adjust the IPL2 so that it loads from track 1 record 1 */
    /* Ditto for sepearate loaders */
    if ((devtype == 0x2311) || separate)
    {
        /* first clobber the BBCCHH with head 1 instead of head 0 */
        memcpy (ipl2data + 32, "\x00\x00\x00\x00\x00\x01", 6);
        
        /* Then clobber the CCHHR with head 1 and record 1 instead
           of the normal head 0 record 4 */
        memcpy (ipl2data + 38, "\x00\x00\x00\x01\x01", 5);
        maxtrks = 2;
    }
    
    if (separate)
    {
        /* large binary files need more room. This will put the
           CCWs above the 1 MB mark, which is hopefully
           enough for now. Note that the CCW data contained in
           record 2 (IPL2) is normally placed at location
           0x003a98. This restricts the IPL code to 15000 bytes
           which is too small for some systems such as PDOS.
           Adding 0x100000 (1 MiB) to all the addresses means
           that we have a bit more than 1 MiB for the IPL
           program. Note that we need to change record 1 (IPL1),
           to tell it the new location to load the data, and 
           the new location to "goto". We need to change
           record 2 (IPL2) so that it knows the new location
           of the BBCCHH, the new location of the CCHHR and
           the new location to "goto". */
        iplccw1[1] = iplccw2[1] = ipl2data[1] = ipl2data[9] 
            = ipl2data[17] = 0x10;
    }
 
    /* Read track 0 */
    rc = read_track (cif, 0, 0);
    if (rc < 0) return -1;
 
    /* Initialize the track buffer */
    *outcyl = 0; *outhead = 0;
    init_track (trklen, cif->trkbuf, *outcyl, *outhead, &outusedv);
    cif->trkmodif = 1;
 
    /* Build the IPL1 record (record 1) */
    memset (buf, 0, sizeof(buf));
    datablk = (DATABLK*)buf;
    convert_to_ebcdic (datablk->kdarea, 4, "IPL1");
 
    if (iplfnm != NULL)
    {
        memcpy (datablk->kdarea+4, iplpsw, 8);
        memcpy (datablk->kdarea+12, iplccw1, 8);
        memcpy (datablk->kdarea+20, iplccw2, 8);
    }
    else
    {
        memcpy (datablk->kdarea+4, noiplpsw, 8);
        memcpy (datablk->kdarea+12, noiplccw1, 8);
        memcpy (datablk->kdarea+20, noiplccw2, 8);
    }
 
    keylen = IPL1_KEYLEN;
    datalen = IPL1_DATALEN;
 
    rc = write_block (cif, ofname, datablk, keylen, datalen,
                devtype, heads, trklen, maxtrks,
                &outusedv, &outusedr, &outtrkbr,
                &outtrk, outcyl, outhead, &outrec);
    if (rc < 0) return -1;
 
    /* Build the IPL2 record (record 2) */
    memset (buf, 0, sizeof(buf));
    datablk = (DATABLK*)buf;
    convert_to_ebcdic (datablk->kdarea, 4, "IPL2");
 
    if (iplfnm != NULL)
    {
        memcpy (datablk->kdarea+4, ipl2data, sizeof(ipl2data));
    }
 
    keylen = IPL2_KEYLEN;
    datalen = IPL2_DATALEN;
 
    rc = write_block (cif, ofname, datablk, keylen, datalen,
                devtype, heads, trklen, maxtrks,
                &outusedv, &outusedr, &outtrkbr,
                &outtrk, outcyl, outhead, &outrec);
    if (rc < 0) return -1;
 
    /* Build the VOL1 record (record 3) */
    memset (buf, 0x40, sizeof(buf));
    datablk = (DATABLK*)buf;
    convert_to_ebcdic (datablk->kdarea, 4, "VOL1");
    convert_to_ebcdic (datablk->kdarea+4, 4, "VOL1");
    convert_to_ebcdic (datablk->kdarea+8, 6, volser);
    memset(datablk->kdarea+15, 0, 5);
    convert_to_ebcdic (datablk->kdarea+45, 8, "HERCULES");
    keylen = VOL1_KEYLEN;
    datalen = VOL1_DATALEN;
 
    rc = write_block (cif, ofname, datablk, keylen, datalen,
                devtype, heads, trklen, maxtrks,
                &outusedv, &outusedr, &outtrkbr,
                &outtrk, outcyl, outhead, &outrec);
    if (rc < 0) return -1;
 
    /* Build the IPL text from the object file */
    /* Note that if we have a "separate" file then we
       will not build this (record 4). */
    if (iplfnm != NULL && !separate)
    {
        memset (buf, 0, sizeof(buf));
        datalen = read_ipl_text (iplfnm, buf+12, sizeof(buf)-12);
        if (datalen < 0) return -1;
 
        datablk = (DATABLK*)buf;
        keylen = 0;
 
        rc = write_block (cif, ofname, datablk, keylen, datalen,
                    devtype, heads, trklen, maxtrks,
                    &outusedv, &outusedr, &outtrkbr,
                    &outtrk, outcyl, outhead, &outrec);
        if (rc < 0) return -1;
    }
 
    /* Write track zero to the output file */
    rc = write_track (cif, ofname, heads, trklen,
                    &outusedv, reltrk, outcyl, outhead);
    if (rc < 0) return -1;
 
    return 0;
} /* end function write_track_zero */
 
/*-------------------------------------------------------------------*/
/* Subroutine to update a data block in the output file              */
/* Input:                                                            */
/*      cif     -> CKD image file descriptor                         */
/*      ofname  Output file name                                     */
/*      blk     Pointer to data block structure                      */
/*      cyl     Cylinder number                                      */
/*      head    Head number                                          */
/*      rec     Record number                                        */
/*      keylen  Key length                                           */
/*      datalen Data length                                          */
/*      heads   Number of tracks per cylinder on output device       */
/*      trklen  Track length of virtual output device                */
/* Output:                                                           */
/*      The return value is 0 if successful, -1 if error occurred.   */
/*-------------------------------------------------------------------*/
static int
update_block (CIFBLK *cif, char *ofname, DATABLK *blk, int cyl,
    int head, int rec, int keylen, int datalen, int heads, int trklen)
{
int             rc;                     /* Return code               */
int             curcyl;                 /* Original cylinder         */
int             curhead;                /* Original head             */
int             klen;                   /* Record key length         */
int             dlen;                   /* Record data length        */
int             skiplen;                /* Number of bytes to skip   */
int             offset;                 /* Offset into trkbuf        */
CKDDASD_TRKHDR  trkhdr;                 /* Track header              */
CKDDASD_RECHDR  rechdr;                 /* Record header             */
 
    fprintf (stderr, "update_block \n");
    UNREFERENCED(heads);
    UNREFERENCED(trklen);
 
    /* Save the current position in the output file */
    curcyl = cif->curcyl;
    curhead = cif->curhead;
 
    /* Read the requested track */
    rc = read_track (cif, cyl, head);
    if (rc < 0)
    {
        XMERRF ("HHCDL043E %s cyl %d head %d read error\n",
                ofname, cyl, head);
        return -1;
    }
 
    /* Copy the track header */
    memcpy (&trkhdr, cif->trkbuf, CKDDASD_TRKHDR_SIZE);
    offset = CKDDASD_TRKHDR_SIZE;
 
    /* Validate the track header */
    if (trkhdr.bin != 0
        || trkhdr.cyl[0] != (cyl >> 8)
        || trkhdr.cyl[1] != (cyl & 0xFF)
        || trkhdr.head[0] != (head >> 8)
        || trkhdr.head[1] != (head & 0xFF))
    {
        XMERRF ("HHCDL044E %s cyl %d head %d invalid track header "
                "%2.2X%2.2X%2.2X%2.2X%2.2X\n",
                ofname, cyl, head,
                trkhdr.bin, trkhdr.cyl[0], trkhdr.cyl[1],
                trkhdr.head[0], trkhdr.head[1]);
        return -1;
    }
 
    /* Search for the record to be updated */
    while (1)
    {
        /* Copy the next record header */
        memcpy (&rechdr, cif->trkbuf + offset, CKDDASD_RECHDR_SIZE);
        offset += CKDDASD_RECHDR_SIZE;
 
        /* Check for end of track */
        if (memcmp(&rechdr, eighthexFF, 8) == 0)
        {
            XMERRF ("HHCDL045E %s cyl %d head %d rec %d record not found\n",
                    ofname, cyl, head, rec);
            return -1;
        }
 
        /* Extract record key length and data length */
        klen = rechdr.klen;
        dlen = (rechdr.dlen[0] << 8) | rechdr.dlen[1];
 
        /* Exit loop if matching record number */
        if (rechdr.rec == rec)
            break;
 
        /* Skip the key and data areas */
        skiplen = klen + dlen;
        offset += skiplen;
    } /* end while */
 
    /* Check for attempt to change key length or data length */
    if (keylen != klen || datalen != dlen)
    {
        XMERRF ("HHCDL046E Cannot update cyl %d head %d rec %d: "
                "Unmatched KL/DL\n",
                cyl, head, rec);
        return -1;
    }
 
    /* Copy the updated block to the trkbuf */
    memcpy (cif->trkbuf + offset, blk->kdarea, keylen + datalen);
    cif->trkmodif = 1;
 
    /* Restore original track */
    rc = read_track (cif, curcyl, curhead);
    if (rc < 0)
    {
        XMERRF ("HHCDL047E %s cyl %d head %d read error\n",
                ofname, curcyl, curhead);
        return -1;
    }
 
    XMINFF (4, "HHCDL048I Updating cyl %u head %u rec %d kl %d dl %d\n",
                cyl, head, rec, keylen, datalen);
 
    return 0;
} /* end function update_block */
 
/*-------------------------------------------------------------------*/
/* Subroutine to build a format 1 DSCB                               */
/* Input:                                                            */
/*      dscbtab Array of pointers to DSCB data blocks                */
/*      dscbnum Number of entries in dscbtab array                   */
/*      dsname  Dataset name (ASCIIZ)                                */
/*      volser  Volume serial number (ASCIIZ)                        */
/*      dsorg   1st byte of dataset organization bits                */
/*      recfm   1st byte of record format bits                       */
/*      lrecl   Logical record length                                */
/*      blksz   Block size                                           */
/*      keyln   Key length                                           */
/*      dirblu  Bytes used in last directory block                   */
/*      lasttrk Relative track number of last-used track of dataset  */
/*      lastrec Record number of last-used block of dataset          */
/*      trkbal  Bytes remaining on last-used track                   */
/*      units   Allocation units (C=CYL, T=TRK)                      */
/*      spsec   Secondary allocation quantity                        */
/*      bcyl    Extent begin cylinder number                         */
/*      bhead   Extent begin head number                             */
/*      ecyl    Extent end cylinder number                           */
/*      ehead   Extent end head number                               */
/* Output:                                                           */
/*      The return value is 0 if successful, or -1 if error          */
/*                                                                   */
/* This subroutine allocates a DATABLK structure, builds a DSCB      */
/* within the structure, and adds the structure to the DSCB array.   */
/*-------------------------------------------------------------------*/
static int
build_format1_dscb (DATABLK *dscbtab[], int dscbnum,
                char *dsname, char *volser,
                BYTE dsorg, BYTE recfm, int lrecl, int blksz,
                int keyln, int dirblu, int lasttrk, int lastrec,
                int trkbal, BYTE units, int spsec,
                int bcyl, int bhead, int ecyl, int ehead)
{
DATABLK        *datablk;                /* -> Data block structure   */
FORMAT1_DSCB   *f1dscb;                 /* -> DSCB within data block */
int             blklen;                 /* Size of data block        */
struct tm      *tmptr;                  /* -> Date and time structure*/
time_t          timeval;                /* Current time value        */
 
    /* Obtain the current time */
    time(&timeval);
    tmptr = localtime(&timeval);
 
    /* Allocate storage for a DATABLK structure */
    blklen = 12 + sizeof(FORMAT1_DSCB);
    datablk = (DATABLK*)malloc(blklen);
    if (datablk == NULL)
    {
        XMERRF ("HHCDL049E Cannot obtain storage for DSCB: %s\n",
                strerror(errno));
        return -1;
    }
 
    /* Check that there is room in the DSCB pointer array */
    if (dscbnum >= MAXDSCB)
    {
        XMERRF ("HHCDL050E DSCB count exceeds %d, increase MAXDSCB\n",
                MAXDSCB);
        return -1;
    }
 
    /* Clear the data block and save its address in the DSCB array */
    memset (datablk, 0, blklen);
    dscbtab[dscbnum] = datablk;
 
    /* Point to the DSCB within the data block */
    f1dscb = (FORMAT1_DSCB*)(datablk->kdarea);
 
    /* Build the format 1 DSCB */
    convert_to_ebcdic (f1dscb->ds1dsnam, 44, dsname);
    f1dscb->ds1fmtid = 0xF1;
    convert_to_ebcdic (f1dscb->ds1dssn, 6, volser);
    f1dscb->ds1volsq[0] = 0;
    f1dscb->ds1volsq[1] = 1;
    f1dscb->ds1credt[0] = tmptr->tm_year;
    f1dscb->ds1credt[1] = (++tmptr->tm_yday >> 8) & 0xFF;
    f1dscb->ds1credt[2] = tmptr->tm_yday & 0xFF;
    f1dscb->ds1expdt[0] = 0;
    f1dscb->ds1expdt[1] = 0;
    f1dscb->ds1expdt[2] = 0;
    f1dscb->ds1noepv = 1;
    f1dscb->ds1bodbd = dirblu;
    convert_to_ebcdic (f1dscb->ds1syscd, 13, "HERCULES");
    f1dscb->ds1dsorg[0] = dsorg;
    f1dscb->ds1dsorg[1] = 0;
    f1dscb->ds1recfm = recfm;
    f1dscb->ds1optcd = 0;
    f1dscb->ds1blkl[0] = (blksz >> 8) & 0xFF;
    f1dscb->ds1blkl[1] = blksz & 0xFF;
    f1dscb->ds1lrecl[0] = (lrecl >> 8) & 0xFF;
    f1dscb->ds1lrecl[1] = lrecl & 0xFF;
    f1dscb->ds1keyl = keyln;
    f1dscb->ds1rkp[0] = 0;
    f1dscb->ds1rkp[1] = 0;
    f1dscb->ds1dsind = DS1DSIND_LASTVOL;
    if ((blksz & 0x07) == 0)
        f1dscb->ds1dsind |= DS1DSIND_BLKSIZ8;
    f1dscb->ds1scalo[0] =
        (units == 'C' ? DS1SCALO_UNITS_CYL : DS1SCALO_UNITS_TRK);
    f1dscb->ds1scalo[1] = (spsec >> 16) & 0xFF;
    f1dscb->ds1scalo[2] = (spsec >> 8) & 0xFF;
    f1dscb->ds1scalo[3] = spsec & 0xFF;
    f1dscb->ds1lstar[0] = (lasttrk >> 8) & 0xFF;
    f1dscb->ds1lstar[1] = lasttrk & 0xFF;
    f1dscb->ds1lstar[2] = lastrec;
    f1dscb->ds1trbal[0] = (trkbal >> 8) & 0xFF;
    f1dscb->ds1trbal[1] = trkbal & 0xFF;
    f1dscb->ds1ext1.xttype =
        (units == 'C' ? XTTYPE_CYLBOUND : XTTYPE_DATA);
    f1dscb->ds1ext1.xtseqn = 0;
    f1dscb->ds1ext1.xtbcyl[0] = (bcyl >> 8) & 0xFF;
    f1dscb->ds1ext1.xtbcyl[1] = bcyl & 0xFF;
    f1dscb->ds1ext1.xtbtrk[0] = (bhead >> 8) & 0xFF;
    f1dscb->ds1ext1.xtbtrk[1] = bhead & 0xFF;
    f1dscb->ds1ext1.xtecyl[0] = (ecyl >> 8) & 0xFF;
    f1dscb->ds1ext1.xtecyl[1] = ecyl & 0xFF;
    f1dscb->ds1ext1.xtetrk[0] = (ehead >> 8) & 0xFF;
    f1dscb->ds1ext1.xtetrk[1] = ehead & 0xFF;
 
    return 0;
} /* end function build_format1_dscb */
 
/*-------------------------------------------------------------------*/
/* Subroutine to build a format 4 DSCB                               */
/* Input:                                                            */
/*      dscbtab Array of pointers to DSCB data blocks                */
/*      dscbnum Number of entries in dscbtab array                   */
/*      devtype Output device type                                   */
/* Output:                                                           */
/*      The return value is 0 if successful, or -1 if error          */
/*                                                                   */
/* This subroutine allocates a DATABLK structure, builds a DSCB      */
/* within the structure, and adds the structure to the DSCB array.   */
/*                                                                   */
/* Note: The VTOC extent descriptor, the highest F1 DSCB address,    */
/* and the number of unused DSCBs, are set to zeroes here and must   */
/* be updated later when the VTOC size and location are known.       */
/* The device size in cylinders is set to the normal size for the    */
/* device type and must be updated when the actual total number of   */
/* cylinders written to the volume is known.                         */
/*-------------------------------------------------------------------*/
static int
build_format4_dscb (DATABLK *dscbtab[], int dscbnum, CIFBLK *cif)
{
DATABLK        *datablk;                /* -> Data block structure   */
FORMAT4_DSCB   *f4dscb;                 /* -> DSCB within data block */
int             blklen;                 /* Size of data block        */
int             numdscb;                /* Number of DSCBs per track */
int             numdblk;                /* Number of dir blks/track  */
int             physlen;                /* Physical track length     */
int             numcyls;                /* Device size in cylinders  */
int             numheads;               /* Number of heads/cylinder  */
int             kbconst;                /* Keyed block constant      */
int             lbconst;                /* Last keyed block constant */
int             nkconst;                /* Non-keyed block constant  */
BYTE            devflag;                /* Device flags for VTOC     */
int             tolfact;                /* Device tolerance          */
 
    /* Calculate the physical track length, block overheads, device
       size, and the number of DSCBs and directory blocks per track */
    capacity_calc (cif, 0, 44, 96, NULL, NULL, &physlen, &kbconst,
                    &lbconst, &nkconst, &devflag, &tolfact, NULL,
                    &numdscb, &numheads, &numcyls);
    capacity_calc (cif, 0, 8, 256, NULL, NULL, NULL,
                    NULL, NULL, NULL, NULL, NULL, NULL,
                    &numdblk, NULL, NULL);
 
    /* Allocate storage for a DATABLK structure */
    blklen = 12 + sizeof(FORMAT4_DSCB);
    datablk = (DATABLK*)malloc(blklen);
    if (datablk == NULL)
    {
        XMERRF ("HHCDL051E Cannot obtain storage for DSCB: %s\n",
                strerror(errno));
        return -1;
    }
 
    /* Check that there is room in the DSCB pointer array */
    if (dscbnum >= MAXDSCB)
    {
        XMERRF ("HHCDL052E DSCB count exceeds %d, increase MAXDSCB\n",
                MAXDSCB);
        return -1;
    }
 
    /* Clear the data block and save its address in the DSCB array */
    memset (datablk, 0, blklen);
    dscbtab[dscbnum] = datablk;
 
    /* Point to the DSCB within the data block */
    f4dscb = (FORMAT4_DSCB*)(datablk->kdarea);
 
    /* Build the format 4 DSCB */
    memset (f4dscb->ds4keyid, 0x04, 44);
    f4dscb->ds4fmtid = 0xF4;
    f4dscb->ds4hcchh[0] = (numcyls >> 8) & 0xFF;
    f4dscb->ds4hcchh[1] = numcyls & 0xFF;
    f4dscb->ds4hcchh[2] = 0;
    f4dscb->ds4hcchh[3] = 0;
    f4dscb->ds4noatk[0] = 0;
    f4dscb->ds4noatk[1] = 0;
    f4dscb->ds4vtoci = DS4VTOCI_DOS;
    f4dscb->ds4noext = 1;
    f4dscb->ds4devsz[0] = (numcyls >> 8) & 0xFF;
    f4dscb->ds4devsz[1] = numcyls & 0xFF;
    f4dscb->ds4devsz[2] = (numheads >> 8) & 0xFF;
    f4dscb->ds4devsz[3] = numheads & 0xFF;
    f4dscb->ds4devtk[0] = (physlen >> 8) & 0xFF;
    f4dscb->ds4devtk[1] = physlen & 0xFF;
    f4dscb->ds4devi = kbconst;
    f4dscb->ds4devl = lbconst;
    f4dscb->ds4devk = nkconst;
    f4dscb->ds4devfg = devflag;
    f4dscb->ds4devtl[0] = (tolfact >> 8) & 0xFF;
    f4dscb->ds4devtl[1] = tolfact & 0xFF;
    f4dscb->ds4devdt = numdscb;
    f4dscb->ds4devdb = numdblk;
 
    return 0;
} /* end function build_format4_dscb */
 
/*-------------------------------------------------------------------*/
/* Subroutine to build a format 5 DSCB                               */
/* Input:                                                            */
/*      dscbtab Array of pointers to DSCB data blocks                */
/*      dscbnum Number of entries in dscbtab array                   */
/* Output:                                                           */
/*      The return value is 0 if successful, or -1 if error          */
/*                                                                   */
/* This subroutine allocates a DATABLK structure, builds a DSCB      */
/* within the structure, and adds the structure to the DSCB array.   */
/*                                                                   */
/* Note: The format 5 DSCB is built with no free space extents.      */
/* The DOS bit which is set in ds4vtoci forces the operating system  */
/* VTOC conversion routine to calculate the free space and update    */
/* the format 5 DSCB the first time the volume is accessed.          */
/*-------------------------------------------------------------------*/
static int
build_format5_dscb (DATABLK *dscbtab[], int dscbnum)
{
DATABLK        *datablk;                /* -> Data block structure   */
FORMAT5_DSCB   *f5dscb;                 /* -> DSCB within data block */
int             blklen;                 /* Size of data block        */
 
    /* Allocate storage for a DATABLK structure */
    blklen = 12 + sizeof(FORMAT5_DSCB);
    datablk = (DATABLK*)malloc(blklen);
    if (datablk == NULL)
    {
        XMERRF ("HHCDL053E Cannot obtain storage for DSCB: %s\n",
                strerror(errno));
        return -1;
    }
 
    /* Check that there is room in the DSCB pointer array */
    if (dscbnum >= MAXDSCB)
    {
        XMERRF ("HHCDL054E DSCB count exceeds %d, increase MAXDSCB\n",
                MAXDSCB);
        return -1;
    }
 
    /* Clear the data block and save its address in the DSCB array */
    memset (datablk, 0, blklen);
    dscbtab[dscbnum] = datablk;
 
    /* Point to the DSCB within the data block */
    f5dscb = (FORMAT5_DSCB*)(datablk->kdarea);
 
    /* Build the format 5 DSCB */
    memset (f5dscb->ds5keyid, 0x05, 4);
    f5dscb->ds5fmtid = 0xF5;
 
    return 0;
} /* end function build_format5_dscb */
 
/*-------------------------------------------------------------------*/
/* Subroutine to write the VTOC                                      */
/* Input:                                                            */
/*      dscbtab Array of pointers to DSCB data blocks                */
/*      numdscb Number of DSCBs including format 4 and format 5      */
/*      cif     -> CKD image file descriptor                         */
/*      ofname  Output file name                                     */
/*      devtype Output device type                                   */
/*      reqcyls Requested device size in cylinders, or zero          */
/*      heads   Number of tracks per cylinder on output device       */
/*      trklen  Track length of virtual output device                */
/*      vtoctrk Starting relative track number for VTOC, or zero     */
/*      vtocext Number of tracks in VTOC, or zero                    */
/* Input/output:                                                     */
/*      nxtcyl  Starting cylinder number for next dataset            */
/*      nxthead Starting head number for next dataset                */
/* Output:                                                           */
/*      volvtoc VTOC starting CCHHR (5 bytes)                        */
/*      The return value is 0 if successful, or -1 if error          */
/*                                                                   */
/* If vtoctrk and vtocext are non-zero, then the VTOC is written     */
/* into the space previously reserved at the indicated location.     */
/* Otherwise, the VTOC is written at the next available dataset      */
/* location, using as many tracks as are necessary, and nextcyl      */
/* and nexthead are updated to point past the end of the VTOC.       */
/*-------------------------------------------------------------------*/
static int
write_vtoc (DATABLK *dscbtab[], int numdscb, CIFBLK *cif, char *ofname,
            U16 devtype, int reqcyls, int heads, int trklen,
            int vtoctrk, int vtocext,
            int *nxtcyl, int *nxthead, BYTE volvtoc[])
{
int             rc;                     /* Return code               */
int             i;                      /* Array subscript           */
DATABLK        *datablk;                /* -> Data block structure   */
FORMAT1_DSCB   *f1dscb;                 /* -> Format 1 DSCB          */
FORMAT4_DSCB   *f4dscb;                 /* -> Format 4 DSCB          */
int             dscbpertrk;             /* Number of DSCBs per track */
int             mintrks;                /* Minimum VTOC size (tracks)*/
int             numtrks;                /* Actual VTOC size (tracks) */
int             highcyl;                /* Last used cylinder number */
int             highhead;               /* Last used head number     */
int             highrec;                /* Last used record number   */
int             numf0dscb;              /* Number of unused DSCBs    */
int             abstrk;                 /* Absolute track number     */
int             endcyl;                 /* VTOC end cylinder number  */
int             endhead;                /* VTOC end head number      */
int             numcyls;                /* Volume size in cylinders  */
int             outusedv = 0;           /* Bytes used in track buffer*/
int             outusedr = 0;           /* Bytes used on real track  */
int             outtrkbr;               /* Bytes left on real track  */
int             outcyl;                 /* Output cylinder number    */
int             outhead;                /* Output head number        */
int             outtrk = 0;             /* Relative track number     */
int             outrec = 0;             /* Output record number      */
int             prealloc = 0;           /* 1=VTOC is preallocated    */
BYTE            blankblk[152];          /* Data block for blank DSCB */
int             curcyl;                 /* Current cylinder in file  */
int             curhead;                /* Current head in file      */
char            dsnama[45];             /* Dataset name (ASCIIZ)     */
 
    fprintf (stderr, "write_vtoc \n");
    /* Determine if the VTOC is preallocated */
    prealloc = (vtoctrk != 0 && vtocext != 0);
 
    /* Point to the format 4 DSCB within the first data block */
    f4dscb = (FORMAT4_DSCB*)(dscbtab[0]->kdarea);
 
    /* Calculate the minimum number of tracks required for the VTOC */
    dscbpertrk = f4dscb->ds4devdt;
    mintrks = (numdscb + dscbpertrk - 1) / dscbpertrk;
 
    /* Save the current position in the output file */
    curcyl = cif->curcyl;
    curhead = cif->curhead;
 
    /* Obtain the VTOC starting location and size */
    if (prealloc)
    {
        /* Use preallocated VTOC location */
        outcyl = vtoctrk / heads;
        outhead = vtoctrk % heads;
        numtrks = vtocext;
    }
    else
    {
        /* Use next available dataset location for VTOC */
        outcyl = *nxtcyl;
        outhead = *nxthead;
        numtrks = mintrks;
    }
 
    /* Check that VTOC extent size is sufficient */
    if (numtrks < mintrks)
    {
        XMERRF ("HHCDL055E VTOC too small, %d track%s required\n",
                mintrks, (mintrks == 1 ? "" : "s"));
        return -1;
    }
 
    /* Read the first track of the VTOC */
    rc = read_track (cif, outcyl, outhead);
    if (rc < 0)
    {
        XMERRF ("HHCDL056E Error reading VTOC cyl %d head %d\n",
                outcyl, outhead);
        return -1;
    }
 
    /* Calculate the CCHHR of the last format 1 DSCB */
    abstrk = (outcyl * heads) + outhead;
    abstrk += mintrks - 1;
    highcyl = abstrk / heads;
    highhead = abstrk % heads;
    highrec = ((numdscb - 1) % dscbpertrk) + 1;
 
    /* Update the last format 1 CCHHR in the format 4 DSCB */
    f4dscb->ds4hpchr[0] = (highcyl >> 8) & 0xFF;
    f4dscb->ds4hpchr[1] = highcyl & 0xFF;
    f4dscb->ds4hpchr[2] = (highhead >> 8) & 0xFF;
    f4dscb->ds4hpchr[3] = highhead & 0xFF;
    f4dscb->ds4hpchr[4] = highrec;
 
    /* Build the VTOC start CCHHR */
    volvtoc[0] = (outcyl >> 8) & 0xFF;
    volvtoc[1] = outcyl & 0xFF;
    volvtoc[2] = (outhead >> 8) & 0xFF;
    volvtoc[3] = outhead & 0xFF;
    volvtoc[4] = 1;
 
    XMINFF (1, "HHCDL057I VTOC starts at cyl %d head %d and is %d track%s\n",
            outcyl, outhead, numtrks, (numtrks == 1 ? "" : "s"));
 
    /* Calculate the number of format 0 DSCBs required to
       fill out the unused space at the end of the VTOC */
    numf0dscb = (numtrks * dscbpertrk) - numdscb;
 
    /* Update the format 0 DSCB count in the format 4 DSCB */
    f4dscb->ds4dsrec[0] = (numf0dscb >> 8) & 0xFF;
    f4dscb->ds4dsrec[1] = numf0dscb & 0xFF;
 
    /* Calculate the CCHH of the last track of the VTOC */
    abstrk = (outcyl * heads) + outhead;
    abstrk += numtrks - 1;
    endcyl = abstrk / heads;
    endhead = abstrk % heads;
 
    /* Update the VTOC extent descriptor in the format 4 DSCB */
    f4dscb->ds4vtoce.xttype =
        (endhead == heads - 1 ? XTTYPE_CYLBOUND : XTTYPE_DATA);
    f4dscb->ds4vtoce.xtseqn = 0;
    f4dscb->ds4vtoce.xtbcyl[0] = (outcyl >> 8) & 0xFF;
    f4dscb->ds4vtoce.xtbcyl[1] = outcyl & 0xFF;
    f4dscb->ds4vtoce.xtbtrk[0] = (outhead >> 8) & 0xFF;
    f4dscb->ds4vtoce.xtbtrk[1] = outhead & 0xFF;
    f4dscb->ds4vtoce.xtecyl[0] = (endcyl >> 8) & 0xFF;
    f4dscb->ds4vtoce.xtecyl[1] = endcyl & 0xFF;
    f4dscb->ds4vtoce.xtetrk[0] = (endhead >> 8) & 0xFF;
    f4dscb->ds4vtoce.xtetrk[1] = endhead & 0xFF;
 
    /* Calculate the mimimum volume size */
    if (prealloc)
    {
        /* The VTOC was preallocated, so the minimum volume
           size equals the next available cylinder number */
        numcyls = *nxtcyl;
        if (*nxthead != 0) numcyls++;
    }
    else
    {
        /* The VTOC will be written into the available space,
           so the minimum volume size is one more than the
           ending cylinder number of the VTOC */
        numcyls = endcyl + 1;
    }
 
    /* If the minimum volume size is less than the requested
       size then use the requested size as the actual size */
    if (numcyls < reqcyls) numcyls = reqcyls;
 
    /* Update the volume size in cylinders in the format 4 DSCB */
    f4dscb->ds4devsz[0] = (numcyls >> 8) & 0xFF;
    f4dscb->ds4devsz[1] = numcyls & 0xFF;
 
    /* Format the track buffer */
    init_track (trklen, cif->trkbuf, outcyl, outhead, &outusedv);
    cif->trkmodif = 1;
 
    /* Write the format 4, format 5, and format 1 DSCBs to the VTOC */
    for (i = 0; i < numdscb; i++)
    {
        /* Load the data block pointer from the DSCB table */
        datablk = dscbtab[i];
 
        /* Extract the dataset name from the format 1 DSCB */
        memset (dsnama, 0, sizeof(dsnama));
        f1dscb = (FORMAT1_DSCB*)(datablk->kdarea);
        if (f1dscb->ds1fmtid == 0xF1)
        {
            make_asciiz (dsnama, sizeof(dsnama), f1dscb->ds1dsnam,
                        sizeof(f1dscb->ds1dsnam));
        }
 
        /* Add next DSCB to the track buffer */
        rc = write_block (cif, ofname, datablk, 44, 96,
                    devtype, heads, trklen, numtrks,
                    &outusedv, &outusedr, &outtrkbr,
                    &outtrk, &outcyl, &outhead, &outrec);
        if (rc < 0) return -1;
 
        XMINFF (4, "HHCDL058I Format %d DSCB CCHHR=%4.4X%4.4X%2.2X "
                "(TTR=%4.4X%2.2X) %s\n",
                datablk->kdarea[0] == 0x04 ? 4 :
                datablk->kdarea[0] == 0x05 ? 5 : 1,
                outcyl, outhead, outrec, outtrk, outrec, dsnama);
        if (infolvl >= 5) data_dump (datablk, 152);
 
    } /* end for(i) */
 
    /* Fill the remainder of the VTOC with format 0 DSCBs */
    for (i = 0; i < numf0dscb; i++)
    {
        /* Add a format 0 DSCB to the track buffer */
        memset (blankblk, 0, sizeof(blankblk));
        datablk = (DATABLK*)blankblk;
        rc = write_block (cif, ofname, datablk, 44, 96,
                    devtype, heads, trklen, numtrks,
                    &outusedv, &outusedr, &outtrkbr,
                    &outtrk, &outcyl, &outhead, &outrec);
        if (rc < 0) return -1;
 
        XMINFF (4, "HHCDL059I Format 0 DSCB CCHHR=%4.4X%4.4X%2.2X "
                "(TTR=%4.4X%2.2X)\n",
                outcyl, outhead, outrec, outtrk, outrec);
 
    } /* end for(i) */
 
    /* Write data remaining in last track buffer */
    rc = write_track (cif, ofname, heads, trklen,
                    &outusedv, &outtrk, &outcyl, &outhead);
    if (rc < 0) return -1;
 
    /* Restore original file position if VTOC was preallocated */
    if (prealloc)
    {
        /* Read the original track again */
        rc = read_track (cif, curcyl, curhead);
        if (rc < 0)
        {
            XMERRF ("HHCDL060E Error reading track cyl %d head %d\n",
                    curcyl, curhead);
            return -1;
        }
    }
    else
    {
        /* Update next cyl and head if VTOC not preallocated */
        *nxtcyl = outcyl;
        *nxthead = outhead;
    }
 
    return 0;
} /* end function write_vtoc */
 
/*-------------------------------------------------------------------*/
/* Subroutine to return the name of a text unit                      */
/*-------------------------------------------------------------------*/
static char *
tu_name (U16 key)
{
    switch (key) {
    CASERET(INMDDNAM);
    CASERET(INMDSNAM);
    CASERET(INMMEMBR);
    CASERET(INMDIR  );
    CASERET(INMEXPDT);
    CASERET(INMTERM );
    CASERET(INMBLKSZ);
    CASERET(INMDSORG);
    CASERET(INMLRECL);
    CASERET(INMRECFM);
    CASERET(INMTNODE);
    CASERET(INMTUID );
    CASERET(INMFNODE);
    CASERET(INMFUID );
    CASERET(INMLREF );
    CASERET(INMLCHG );
    CASERET(INMCREAT);
    CASERET(INMFVERS);
    CASERET(INMFTIME);
    CASERET(INMTTIME);
    CASERET(INMFACK );
    CASERET(INMERRCD);
    CASERET(INMUTILN);
    CASERET(INMUSERP);
    CASERET(INMRECCT);
    CASERET(INMSIZE );
    CASERET(INMFFM  );
    CASERET(INMNUMF );
    CASERET(INMTYPE );
    } /* end switch(key) */
 
    return "????????";
 
} /* end function tu_name */
 
/*-------------------------------------------------------------------*/
/* Subroutine to extract next text unit from buffer                  */
/* Input:                                                            */
/*      xbuf    Pointer to start of buffer                           */
/*      bufpos  Position of next text unit within buffer             */
/*      bufrem  Number of bytes remaining in buffer                  */
/*      pkey    Pointer to field to receive text unit key            */
/*      pnum    Pointer to field to receive number of data items     */
/*      maxnum  Maximum number of data items expected                */
/*      plen    Pointer to array to receive data item lengths        */
/*      pdata   Pointer to array to receive data item pointers       */
/* Output:                                                           */
/*      The function return value is the total length of the         */
/*      text unit, or -1 if error.                                   */
/*                                                                   */
/* Text units are listed if infolvl is 4 or greater.                 */
/*-------------------------------------------------------------------*/
static int
next_tu (BYTE *xbuf, int bufpos, int bufrem, U16 *pkey, U16 *pnum,
        U16 maxnum, U16 plen[], BYTE *pdata[])
{
int     i, j;                           /* Array subscripts          */
U16     key, num;                       /* Text unit header          */
int     field;                          /* Field number              */
int     offset;                         /* Offset into text unit     */
U16     len;                            /* Field length              */
char   *name;                           /* Text unit name            */
BYTE    c, chars[9];                    /* Character work areas      */
char    hex[17];                        /* Character work areas      */
 
    set_codepage(NULL);
 
    /* Error if remaining length is insufficient for header */
    if (bufrem < 4)
    {
        XMERR ("HHCDL061E Incomplete text unit\n");
        return -1;
    }
 
    /* Load the key and field count from the first 4 bytes */
    key = (xbuf[bufpos] << 8) | xbuf[bufpos+1];
    num = (xbuf[bufpos+2] << 8) | xbuf[bufpos+3];
 
    /* Obtain the text unit name */
    name = tu_name(key);
 
    /* Print the text unit name and field count */
    XMINFF (4, "HHCDL062I \t+%4.4X %-8.8s %4.4X %4.4X ",
            bufpos, name, key, num);
 
    /* Error if number of fields exceeds maximum */
    if (num > maxnum)
    {
        XMINF (4, "\n");
        XMERR ("HHCDL063E Too many fields in text unit\n");
        return -1;
    }
 
    /* Point to first field */
    offset = 4;
    bufrem -= 4;
 
    /* Process each field in text unit */
    for (field = 0; field < num; field++)
    {
        /* Error if remaining length is insufficient for length */
        if (bufrem < 2)
        {
            XMINF (4, "\n");
            XMERR ("HHCDL064E Incomplete text unit\n");
            return -1;
        }
 
        /* Load field length from next 2 bytes */
        len = (xbuf[bufpos+offset] << 8) | xbuf[bufpos+offset+1];
        offset += 2;
        bufrem -= 2;
 
        /* Error if remaining length is insufficient for data */
        if (bufrem < len)
        {
            XMINF (4, "\n");
            XMERR ("HHCDL065E Incomplete text unit\n");
            return -1;
        }
 
        /* Print field length and data */
        if (field > 0) XMINF (4, "\n\t\t\t\t ");
        XMINFF (4, "%4.4X ", len);
        memset (hex, '\0', sizeof(hex));
        memset (chars, '\0', sizeof(chars));
        for (i = 0, j = 0; i < len; i++, j++)
        {
            if (i > 0 && (i & 0x07) == 0)
            {
                XMINFF (4, "%-16.16s %-8.8s\n\t\t\t\t      ",
                    hex, chars);
                memset (hex, '\0', sizeof(hex));
                memset (chars, '\0', sizeof(chars));
                j = 0;
            }
            sprintf(hex+2*j, "%2.2X", xbuf[bufpos+offset+i]);
            c = guest_to_host(xbuf[bufpos+offset+i]);
            if (!isprint(c)) c = '.';
            chars[j] = c;
        } /* end for(i) */
        XMINFF (4, "%-16.16s %-8.8s", hex, chars);
 
        /* Save field length and pointer in array */
        plen[field] = len;
        pdata[field] = xbuf + bufpos + offset;
 
        /* Get offset of next field in text unit */
        offset += len;
        bufrem -= len;
 
    } /* end for */
 
    /* Print newline at end of text unit */
    XMINF (4, "\n");
 
    /* Return key, number of fields, and total length */
    *pkey = key;
    *pnum = num;
    return offset;
 
} /* end function next_tu */
 
/*-------------------------------------------------------------------*/
/* Subroutine to assemble a logical record from segments             */
/* Input:                                                            */
/*      xfd     Input file descriptor                                */
/*      xfname  Input file name                                      */
/*      xbuf    Pointer to buffer to receive logical record          */
/* Output:                                                           */
/*      ctl     Zero=data record, non-zero=control record            */
/*      The return value is the logical record length,               */
/*      or -1 if an error occurred.                                  */
/*-------------------------------------------------------------------*/
static int
read_xmit_rec (int xfd, char *xfname, BYTE *xbuf, BYTE *ctl)
{
int             rc;                     /* Return code               */
int             xreclen = 0;            /* Cumulative record length  */
int             segnum;                 /* Segment counter           */
int             seglen;                 /* Segment data length       */
BYTE            ctlind = 0x00;          /* 0x20=Control record       */
BYTE            seghdr[2];              /* Segment length and flags  */
 
    for (segnum = 0; ; segnum++)
    {
        /* Read the segment length and flags */
        rc = read (xfd, seghdr, 2);
        if (rc < 2)
        {
            XMERRF ("HHCDL066E %s read error: %s\n",
                    xfname,
                    (rc < 0 ? strerror(errno) :
                    "Unexpected end of file"));
            return -1;
        }
 
        /* Check for valid segment header */
        if (seghdr[0] < 2 || (seghdr[1] & 0x1F) != 0)
        {
            XMERRF ("HHCDL067E %s invalid segment header: %2.2X%2.2X\n",
                    xfname, seghdr[0], seghdr[1]);
            return -1;
        }
 
        /* Check flags for first segment */
        if (segnum == 0)
        {
            /* Check that first segment indicator is set */
            if ((seghdr[1] & 0x80) == 0)
            {
                XMERRF ("HHCDL068E %s first segment indicator expected\n",
                        xfname);
                return -1;
            }
 
            /* Save the control record indicator */
            ctlind = (seghdr[1] & 0x20);
        }
 
        /* Check flags for subsequent segments */
        if (segnum > 0)
        {
            /* Check that first segment indicator is not set */
            if (seghdr[1] & 0x80)
            {
                XMERRF ("HHCDL069E %s first segment indicator not expected\n",
                        xfname);
                return -1;
            }
 
            /* Check if ctlrec indicator matches first segment */
            if ((seghdr[1] & 0x20) != ctlind)
            {
                XMERRF ("HHCDL070E %s control record indicator mismatch\n",
                        xfname);
                return -1;
            }
        }
 
        /* Read segment data into buffer */
        seglen = seghdr[0] - 2;
        rc = read (xfd, xbuf + xreclen, seglen);
        if (rc < seglen)
        {
            XMERRF ("HHCDL071E %s read error: %s\n",
                    xfname,
                    (rc < 0 ? strerror(errno) :
                    "Unexpected end of file"));
            return -1;
        }
 
        /* Accumulate total record length */
        xreclen += seglen;
 
        /* Exit if last segment of record */
        if (seghdr[1] & 0x40)
            break;
 
    } /* end for(segnum) */
 
    /* Return record length and control indicator */
    *ctl = ctlind;
    return xreclen;
 
} /* end function read_xmit_rec */
 
/*-------------------------------------------------------------------*/
/* Subroutine to assemble a logical record from DSORG=VS file        */
/* Input:                                                            */
/*      xfd     Input file descriptor                                */
/*      xfname  Input file name                                      */
/*      xbuf    Pointer to buffer to receive logical record          */
/*      recnum  Relative number for the record to be read            */
/* Output:                                                           */
/*      The return value is the logical record length,               */
/*      or -1 if an error occurred.                                  */
/*-------------------------------------------------------------------*/
static int
read_vs_rec (int xfd, char *xfname, BYTE *xbuf, int recnum)
{
int             rc;                     /* Return code               */
int             xreclen;                /* Cumulative record length  */
DATABLK        *datablk;                /* Data block                */
 
    if (recnum == 0) {
       xreclen = read(xfd, xbuf, 56);   /* read COPYR1 plus some extras */
       if (xreclen < 56)
        {
            XMERRF ("HHCDL072E %s read error: %s\n",
                    xfname,
                    (xreclen < 0 ? strerror(errno) :
                    "Unexpected end of file"));
            return -1;
        }
     }
 
    else if (recnum == 1) {
       xreclen = read(xfd, xbuf, sizeof(COPYR2));  /* read COPYR2 */
       if (xreclen < (int)sizeof(COPYR2))
        {
            XMERRF ("HHCDL073E %s read error: %s\n",
                    xfname,
                    (xreclen < 0 ? strerror(errno) :
                    "Unexpected end of file"));
            return -1;
        }
     }
 
    else {
       rc = read(xfd, xbuf, 12);        /* read header of DATABLK */
       if (rc == 0)                     /* read nothing? */
          return 0;
       if (rc < 12)
        {
            XMERRF ("HHCDL074E %s read error: %s\n",
                    xfname,
                    (rc < 0 ? strerror(errno) :
                    "Unexpected end of file"));
            return -1;
        }
       datablk = (DATABLK *)xbuf;
       xreclen = ((datablk->dlen[0] << 8) | datablk->dlen[1])
               + datablk->klen;
       rc = read(xfd, xbuf + 12, xreclen);  /* read kdarea of DATABLK */
       if (rc < xreclen)
        {
            XMERRF ("HHCDL075E %s read error: %s\n",
                    xfname,
                    (rc < 0 ? strerror(errno) :
                    "Unexpected end of file"));
            return -1;
        }
       xreclen += 12;                   /* also count the header */
    }
 
    /* Return record length */
    return xreclen;
 
} /* end function read_vs_rec */
 
/*-------------------------------------------------------------------*/
/* Subroutine to process an INMR02 control record                    */
/* Input:                                                            */
/*      xbuf    Pointer to buffer containing control record          */
/*      xreclen Length of control record                             */
/*      filen   Pointer to field to receive file sequence number     */
/*      dsorg   Pointer to byte to receive dataset organization      */
/*      recfm   Pointer to byte to receive record format             */
/*      lrecl   Pointer to integer to receive logical record length  */
/*      blksz   Pointer to integer to receive block size             */
/*      keyln   Pointer to integer to receive key length             */
/*      dirnm   Pointer to integer to number of directory blocks     */
/* Output:                                                           */
/*      If the record contains the text unit INMUTILN=IEBCOPY        */
/*      then the dataset attributes are returned and the function    */
/*      return value is 1.  Otherwise the return value is 0          */
/*      and the dataset attributes remain unchanged.                 */
/*      If an error occurs then the return value is -1.              */
/*                                                                   */
/* File information is listed if infolvl is 2 or greater.            */
/*-------------------------------------------------------------------*/
static int
process_inmr02 (BYTE *xbuf, int xreclen, int *filen,
                BYTE *dsorg, BYTE *recfm, U16 *lrecl, U16 *blksz,
                U16 *keyln, U16 *dirnm)
{
int             rc;                     /* Return code               */
int             i;                      /* Array subscript           */
int             len;                    /* String length             */
U32             filenum;                /* File number               */
int             bufpos;                 /* Position of TU in buffer  */
int             bufrem;                 /* Bytes remaining in buffer */
char            tuutiln[9];             /* Utility name              */
BYTE            tukeyln;                /* Key length                */
HWORD           tudsorg;                /* Data set organization     */
HWORD           turecfm;                /* Record format             */
U16             tulrecl;                /* Logical record length     */
U16             tublksz;                /* Block size                */
int             tudirct;                /* Number of directory blocks*/
U16             tukey;                  /* Text unit key             */
U16             tunum;                  /* Number of text unit fields*/
char            tudsnam[45];            /* Data set name             */
#define MAXNUM  20                      /* Maximum number of fields  */
U16             fieldlen[MAXNUM];       /* Array of field lengths    */
BYTE           *fieldptr[MAXNUM];       /* Array of field pointers   */
 
    /* Extract the file number which follows the record name */
    filenum = (xbuf[6] << 24) | (xbuf[7] << 16)
            | (xbuf[8] << 8) | xbuf[9];
    XMINFF (4, "HHCDL076I File number: %d\n", filenum);
 
    /* Point to the first text unit */
    bufpos = 10;
    bufrem = xreclen-10;
 
    /* Clear values to be loaded from text units */
    memset (tudsnam, 0, sizeof(tudsnam));
    memset (tuutiln, 0, sizeof(tuutiln));
    memset (tudsorg, 0, sizeof(tudsorg));
    memset (turecfm, 0, sizeof(turecfm));
    tulrecl = 0;
    tublksz = 0;
    tukeyln = 0;
    tudirct = 0;
 
    /* Process each text unit */
    while (bufrem > 0)
    {
        /* Extract the next text unit */
        rc = next_tu (xbuf, bufpos, bufrem, &tukey, &tunum,
                MAXNUM, fieldlen, fieldptr);
        if (rc < 0)
        {
            XMERRF ("HHCDL077E Invalid text unit at offset %4.4X\n",
                    bufpos + 2);
            return -1;
        }
        bufpos += rc;
        bufrem -= rc;
 
        /* Save the values from selected text units */
        switch (tukey) {
        case INMUTILN:
            make_asciiz (tuutiln, sizeof(tuutiln),
                        fieldptr[0], fieldlen[0]);
            break;
        case INMDSORG:
            if (fieldlen[0] > sizeof(tudsorg))
                fieldlen[0] = sizeof(tudsorg);
            memcpy (tudsorg, fieldptr[0], fieldlen[0]);
            break;
        case INMRECFM:
            if (fieldlen[0] > sizeof(turecfm))
                fieldlen[0] = sizeof(turecfm);
            memcpy (turecfm, fieldptr[0], fieldlen[0]);
            break;
        case INMLRECL:
            tulrecl = make_int (fieldptr[0], fieldlen[0]);
            break;
        case INMBLKSZ:
            tublksz = make_int (fieldptr[0], fieldlen[0]);
            break;
        case INMTYPE:
            tukeyln = make_int (fieldptr[0], fieldlen[0]);
            break;
        case INMDIR:
            tudirct = make_int (fieldptr[0], fieldlen[0]);
            break;
        case INMDSNAM:
            memset (tudsnam, 0, sizeof(tudsnam));
            for (i = 0; i < tunum; i++)
            {
                len = strlen(tudsnam);
                if (i > 0 && len < (int)(sizeof(tudsnam) - 1))
                    tudsnam[len++] = '.';
                make_asciiz (tudsnam + len, sizeof(tudsnam) - len,
                                fieldptr[i], fieldlen[i]);
            } /* end for(i) */
        } /* end switch(tukey) */
    } /* end while(bufrem) */
 
    /* Return the dataset values if this is the IEBCOPY record */
    if (strcmp(tuutiln, "IEBCOPY") == 0)
    {
        XMINFF (2, "HHCDL078I File %u: DSNAME=%s\n",
                filenum, tudsnam);
        XMINFF (2, "HHCDL079I DSORG=%s RECFM=%s "
                "LRECL=%d BLKSIZE=%d KEYLEN=%d DIRBLKS=%d\n",
                dsorg_name(tudsorg), recfm_name(turecfm),
                tulrecl, tublksz, tukeyln, tudirct);
        *filen = filenum;
        *dsorg = tudsorg[0];
        *recfm = turecfm[0];
        *lrecl = tulrecl;
        *blksz = tublksz;
        *keyln = tukeyln;
        *dirnm = tudirct;
    }
 
    return 0;
} /* end function process_inmr02 */
 
/*-------------------------------------------------------------------*/
/* Subroutine to process a control record other than INMR02          */
/* Input:                                                            */
/*      xbuf    Pointer to buffer containing control record          */
/*      xreclen Length of control record                             */
/* Output:                                                           */
/*      The return value is 0 if successful, or -1 if error.         */
/*-------------------------------------------------------------------*/
static int
process_inmrxx (BYTE *xbuf, int xreclen)
{
int             rc;                     /* Return code               */
int             bufpos;                 /* Position of TU in buffer  */
int             bufrem;                 /* Bytes remaining in buffer */
U16             tukey;                  /* Text unit key             */
U16             tunum;                  /* Number of text unit fields*/
#define MAXNUM  20                      /* Maximum number of fields  */
U16             fieldlen[MAXNUM];       /* Array of field lengths    */
BYTE           *fieldptr[MAXNUM];       /* Array of field pointers   */
 
    fprintf (stderr, "process_inmrxx \n");
    /* Point to the first text unit */
    bufpos = 6;
    bufrem = xreclen-6;
 
    /* Process each text unit */
    while (bufrem > 0)
    {
        /* Extract the next text unit */
        rc = next_tu (xbuf, bufpos, bufrem, &tukey, &tunum,
                MAXNUM, fieldlen, fieldptr);
        if (rc < 0)
        {
            XMERRF ("HHCDL080E Invalid text unit at offset %4.4X\n",
                    bufpos + 2);
            return -1;
        }
 
        bufpos += rc;
        bufrem -= rc;
 
    } /* end while(bufrem) */
 
    return 0;
} /* end function process_inmrxx */
 
/*-------------------------------------------------------------------*/
/* Subroutine to process a COPYR1 header record                      */
/* Input:                                                            */
/*      xbuf    Pointer to buffer containing header record           */
/*      xreclen Length of header record                              */
/* Output:                                                           */
/*      The return value is the number of tracks per cylinder,       */
/*      or -1 if an error occurred.                                  */
/*-------------------------------------------------------------------*/
static int
process_copyr1 (BYTE *xbuf, int xreclen)
{
COPYR1 *copyr1 = (COPYR1*)xbuf;         /* -> COPYR1 header record   */
U16     blksize;                        /* Block size                */
U16     lrecl;                          /* Logical record length     */
BYTE    keylen;                         /* Key length                */
U16     cyls;                           /* Number of cylinders       */
U16     heads;                          /* Number of tracks/cylinder */
 
    fprintf (stderr, "process_copyr1 \n");
    /* Check COPYR1 record for correct length */
    if (xreclen != sizeof(COPYR1)
        && xreclen != sizeof(COPYR1) - 4)
    {
        XMERR ("HHCDL081E COPYR1 record length is invalid\n");
        return -1;
    }
 
    /* Check that COPYR1 header identifier is correct */
    if (memcmp(copyr1->hdrid, COPYR1_HDRID, 3) != 0)
    {
        XMERR ("HHCDL082E COPYR1 header identifier not correct\n");
        return -1;
    }
 
    /* Check that the dataset is an old format unload */
    if ((copyr1->uldfmt & COPYR1_ULD_FORMAT)
            != COPYR1_ULD_FORMAT_OLD)
    {
        XMERR ("HHCDL083E COPYR1 unload format is unsupported\n");
        return -1;
    }
 
    blksize = (copyr1->ds1blkl[0] << 8) | copyr1->ds1blkl[1];
    lrecl = (copyr1->ds1lrecl[0] << 8) | copyr1->ds1lrecl[1];
    keylen = copyr1->ds1keyl;
 
    /* Display original dataset information */
    XMINFF (2, "HHCDL084I Original dataset: "
            "DSORG=%s RECFM=%s LRECL=%d BLKSIZE=%d KEYLEN=%d\n",
            dsorg_name(copyr1->ds1dsorg),
            recfm_name(&copyr1->ds1recfm),
            lrecl, blksize, keylen);
 
    XMINFF (2, "HHCDL085I Dataset was unloaded from device type "
            "%2.2X%2.2X%2.2X%2.2X (%s)\n",
            copyr1->ucbtype[0], copyr1->ucbtype[1],
            copyr1->ucbtype[2], copyr1->ucbtype[3],
            dasd_name(copyr1->ucbtype));
 
    cyls = (copyr1->cyls[0] << 8) | copyr1->cyls[1];
    heads = (copyr1->heads[0] << 8) | copyr1->heads[1];
 
    XMINFF (2, "HHCDL086I Original device has %d cyls and %d heads\n",
            cyls, heads);
 
    return heads;
} /* end function process_copyr1 */
 
/*-------------------------------------------------------------------*/
/* Subroutine to process a COPYR2 header record                      */
/* Input:                                                            */
/*      xbuf    Pointer to buffer containing header record           */
/*      xreclen Length of header record                              */
/*      xarray  Pointer to array to receive 1-16 extent descriptions */
/* Output:                                                           */
/*      The return value is the number of extents,                   */
/*      or -1 if an error occurred.                                  */
/*                                                                   */
/* Extent information is listed if infolvl is 4 or greater.          */
/*-------------------------------------------------------------------*/
static int
process_copyr2 (BYTE *xbuf, int xreclen, EXTDESC xarray[])
{
COPYR2 *copyr2 = (COPYR2*)xbuf;         /* -> COPYR2 header record   */
int     numext;                         /* Number of extents         */
int     i;                              /* Array subscript           */
 
    fprintf (stderr, "process_copyr2 \n");
    /* Check COPYR2 record for correct length */
    if (xreclen != sizeof(COPYR2))
    {
        XMERR ("HHCDL087I COPYR2 record length is invalid\n");
        return -1;
    }
 
    /* Get number of extents from DEB basic section */
    numext = copyr2->debbasic[0];
    if (numext < 1 || numext > 16)
    {
        XMERRF ("HHCDL088I Invalid number of extents %d\n", numext);
        return -1;
    }
 
    /* Copy each extent descriptor into the array */
    for (i = 0; i < numext; i++)
    {
        xarray[i].bcyl = (copyr2->debxtent[i][6] << 8)
                        | copyr2->debxtent[i][7];
        xarray[i].btrk = (copyr2->debxtent[i][8] << 8)
                        | copyr2->debxtent[i][9];
        xarray[i].ecyl = (copyr2->debxtent[i][10] << 8)
                        | copyr2->debxtent[i][11];
        xarray[i].etrk = (copyr2->debxtent[i][12] << 8)
                        | copyr2->debxtent[i][13];
        xarray[i].ntrk = (copyr2->debxtent[i][14] << 8)
                        | copyr2->debxtent[i][15];
 
        XMINFF (4, "HHCDL089I Extent %d: Begin CCHH=%4.4X%4.4X "
                "End CCHH=%4.4X%4.4X Tracks=%4.4X\n",
                i, xarray[i].bcyl, xarray[i].btrk,
                xarray[i].ecyl, xarray[i].etrk, xarray[i].ntrk);
 
    } /* end for(i) */
 
    /* Return number of extents */
    return numext;
} /* end function process_copyr2 */
 
/*-------------------------------------------------------------------*/
/* Subroutine to process a directory block record                    */
/* Input:                                                            */
/*      xbuf    Pointer to directory block                           */
/*      blklen  Length of directory block                            */
/*      cyl     Cylinder number of directory block in output file    */
/*      head    Head number of directory block in output file        */
/*      rec     Record number of directory block in output file      */
/*      dirblka Array of pointers to directory blocks                */
/*      dirblkn Number of directory blocks in dirblka array          */
/* Output:                                                           */
/*      dirblu  Number of bytes used in directory block              */
/*      The return value is 0 if successful, 1 if end of directory,  */
/*      or -1 if an error occurred.                                  */
/*                                                                   */
/* A pointer to the directory block is saved in the dirblka array.   */
/* The copy of the data block is updated with the cylinder, head,    */
/* and record number of the directory block in the output file.      */
/* Directory information is listed if infolvl is 3 or greater.       */
/*-------------------------------------------------------------------*/
static int
process_dirblk (DATABLK *xbuf, int blklen, int cyl, int head, int rec,
                DATABLK *dirblka[], int dirblkn, int *dirblu)
{
int             size;                   /* Size of directory entry   */
int             i, j;                   /* Array subscripts          */
int             k;                      /* Userdata halfword count   */
DATABLK        *blkp;                   /* -> Copy of directory block*/
BYTE           *dirptr;                 /* -> Next byte within block */
int             dirrem;                 /* Number of bytes remaining */
PDSDIR         *dirent;                 /* -> Directory entry        */
char            memname[9];             /* Member name (ASCIIZ)      */
BYTE            c, chars[25];           /* Character work areas      */
char            hex[49];                /* Character work areas      */
 
    fprintf (stderr, "process_dirblk \n");
    set_codepage(NULL);
 
 
    /* Check for end of directory */
    if (blklen == 12 && memcmp(xbuf, twelvehex00, 12) == 0)
    {
        XMINF (3, "HHCDL090I End of directory\n");
        return 1;
    }
 
    /* Check directory block record for correct length */
    if (blklen != 276)
    {
        XMERR ("HHCDL091E Directory block record length is invalid\n");
        return -1;
    }
 
    /* Obtain storage for a copy of the directory block */
    blkp = (DATABLK*)malloc(blklen);
    if (blkp == NULL)
    {
        XMERRF ("HHCDL092E Cannot obtain storage for directory block: %s\n",
                strerror(errno));
        return -1;
    }
 
    /* Copy the directory block */
    memcpy (blkp, xbuf, blklen);
 
    /* Check that there is room in the directory block pointer array */
    if (dirblkn >= MAXDBLK)
    {
        XMERRF ("HHCDL093E Number of directory blocks exceeds %d, "
                "increase MAXDBLK\n",
                MAXDBLK);
        return -1;
    }
 
    /* Add the directory block to the pointer array */
    dirblka[dirblkn] = blkp;
 
    /* Update the CCHHR in the copy of the directory block */
    blkp->cyl[0] = (cyl >> 8) & 0xFF;
    blkp->cyl[1] = cyl & 0xFF;
    blkp->head[0] = (head >> 8) & 0xFF;
    blkp->head[1] = head & 0xFF;
    blkp->rec = rec;
 
    /* Load number of bytes in directory block */
    dirptr = xbuf->kdarea + 8;
    dirrem = (dirptr[0] << 8) | dirptr[1];
    if (dirrem < 2 || dirrem > 256)
    {
        XMERR ("HHCDL094E Directory block byte count is invalid\n");
        return -1;
    }
 
    /* Return number of bytes used in directory block */
    *dirblu = dirrem;
 
    /* Point to first directory entry */
    dirptr += 2;
    dirrem -= 2;
 
    /* Process each directory entry */
    while (dirrem > 0)
    {
        /* Point to next directory entry */
        dirent = (PDSDIR*)dirptr;
 
        /* Test for end of directory */
        if (memcmp(dirent->pds2name, eighthexFF, 8) == 0)
            break;
 
        /* Extract the member name */
        make_asciiz (memname, sizeof(memname), dirent->pds2name, 8);
 
        /* Display the directory entry */
        XMINFF (3, "HHCDL095I %s %-8.8s TTR=%2.2X%2.2X%2.2X ",
                (dirent->pds2indc & PDS2INDC_ALIAS) ?
                        " Alias" : "Member",
                memname, dirent->pds2ttrp[0],
                dirent->pds2ttrp[1], dirent->pds2ttrp[2]);
 
        /* Load the user data halfword count */
        k = dirent->pds2indc & PDS2INDC_LUSR;
 
        /* Print the user data */
        if (k > 0) XMINF (3, "Userdata=");
        memset (hex, '\0', sizeof(hex));
        memset (chars, '\0', sizeof(chars));
        for (i = 0, j = 0; i < k*2; i++, j++)
        {
            if (i == 8 || i == 32 || i == 56)
            {
                if (i == 8)
                    XMINFF (3, "%-16.16s %-8.8s\n  ", hex, chars);
                else
                    XMINFF (3, "%-16.16s %-16.16s %16.16s %-24.24s\n  ",
                        hex, hex+16, hex+32, chars);
                memset (hex, '\0', sizeof(hex));
                memset (chars, '\0', sizeof(chars));
                j = 0;
            }
            sprintf(hex+2*j, "%2.2X", dirent->pds2usrd[i]);
            c = guest_to_host(dirent->pds2usrd[i]);
            if (!isprint(c)) c = '.';
            chars[j] = c;
        } /* end for(i) */
        if (i <= 8)
            XMINFF (3, "%-16.16s %-8.8s\n", hex, chars);
        else
            XMINFF (3, "%-16.16s %-16.16s %-16.16s %-24.24s\n",
                hex, hex+16, hex+32, chars);
 
        /* Point to next directory entry */
        size = 12 + k*2;
        dirptr += size;
        dirrem -= size;
    }
 
    return 0;
} /* end function process_dirblk */
 
/*-------------------------------------------------------------------*/
/* Subroutine to replace a TTR in a PDS directory                    */
/* Input:                                                            */
/*      memname Member name (ASCIIZ)                                 */
/*      ttrptr  Pointer to TTR to be replaced                        */
/*      ttrtab  Pointer to TTR conversion table                      */
/*      numttr  Number of entries in TTR conversion table            */
/* Output:                                                           */
/*      The TTR is replaced using the TTR conversion table.          */
/*                                                                   */
/* Return value is 0 if successful, or -1 if TTR not in table.       */
/* Directory information is listed if infolvl is 3 or greater.       */
/*-------------------------------------------------------------------*/
static int
replace_ttr (char *memname, BYTE *ttrptr, TTRCONV *ttrtab, int numttr)
{
int             i;                      /* Array subscript           */
 
    fprintf (stderr, "replace_ttr \n");
    /* Search for the TTR in the conversion table */
    for (i = 0; i < numttr; i++)
    {
        if (memcmp(ttrptr, ttrtab[i].origttr, 3) == 0)
        {
            XMINFF (4, "HHCDL096I Member %s TTR=%2.2X%2.2X%2.2X "
                    "replaced by TTR=%2.2X%2.2X%2.2X\n",
                    memname, ttrptr[0], ttrptr[1], ttrptr[2],
                    ttrtab[i].outpttr[0], ttrtab[i].outpttr[1],
                    ttrtab[i].outpttr[2]);
            memcpy (ttrptr, ttrtab[i].outpttr, 3);
            return 0;
        }
    }
 
    /* Return error if TTR not found in conversion table */
    XMERRF ("HHCDL097E Member %s TTR=%2.2X%2.2X%2.2X not found in dataset\n",
            memname, ttrptr[0], ttrptr[1], ttrptr[2]);
    return -1;
 
} /* end function replace_ttr */
 
/*-------------------------------------------------------------------*/
/* Subroutine to update a note list record                           */
/* Input:                                                            */
/*      cif     -> CKD image file descriptor                         */
/*      ofname  Output file name                                     */
/*      heads   Number of tracks per cylinder on output device       */
/*      trklen  Track length of virtual output device                */
/*      dsstart Relative track number of start of dataset            */
/*      memname Member name (ASCIIZ)                                 */
/*      ttrn    Pointer to TTRN of note list record                  */
/*      ttrtab  Pointer to TTR conversion table                      */
/*      numttr  Number of entries in TTR conversion table            */
/* Output:                                                           */
/*      Each original TTR in the note list record is replaced by the */
/*      corresponding output TTR from the TTR conversion table.      */
/*                                                                   */
/* Return value is 0 if successful, or -1 if error.                  */
/*-------------------------------------------------------------------*/
static int
update_note_list (CIFBLK *cif, char *ofname, int heads, int trklen,
                int dsstart, char *memname, BYTE *ttrn,
                TTRCONV *ttrtab, int numttr)
{
int             rc;                     /* Return code               */
int             i;                      /* Loop counter              */
int             trk;                    /* Relative track number     */
int             cyl;                    /* Cylinder number           */
int             head;                   /* Head number               */
int             rec;                    /* Record number             */
int             klen;                   /* Record key length         */
int             dlen;                   /* Record data length        */
int             numnl;                  /* Number of note list TTRs  */
int             nllen;                  /* Note list length          */
BYTE           *ttrptr;                 /* -> Note list TTR          */
int             curcyl;                 /* Current cylinder          */
int             curhead;                /* Current head              */
int             offset;                 /* Offset into track buffer  */
int             skiplen;                /* Number of bytes to skip   */
CKDDASD_TRKHDR  trkhdr;                 /* Track header              */
CKDDASD_RECHDR  rechdr;                 /* Record header             */
BYTE            notelist[1024];         /* Note list                 */
 
    UNREFERENCED(trklen);
 
    fprintf (stderr, "update_note_list \n");
    /* Load the TTR of the note list record */
    trk = (ttrn[0] << 8) | ttrn[1];
    rec = ttrn[2];
 
    /* Load number of note list TTRs and calculate note list length */
    numnl = ttrn[3];
    nllen = numnl * 4;
 
    /* Calculate the CCHHR of the note list record */
    cyl = (dsstart + trk) / heads;
    head = (dsstart + trk) % heads;
 
    XMINFF (4, "HHCDL098I Updating note list for member %s "
            "at TTR=%4.4X%2.2X CCHHR=%4.4X%4.4X%2.2X\n",
            memname, trk, rec, cyl, head, rec);
 
    /* Save the current position in the output file */
    curcyl = cif->curcyl;
    curhead = cif->curhead;
 
    /* Seek to start of track header */
    rc = read_track (cif, cyl, head);
    if (rc < 0)
    {
        XMERRF ("HHCDL099E %s cyl %d head %d read error\n",
                ofname, cyl, head);
        return -1;
    }
 
    /* Copy the track header */
    memcpy (&trkhdr, cif->trkbuf, CKDDASD_TRKHDR_SIZE);
    offset = CKDDASD_TRKHDR_SIZE;
 
    /* Validate the track header */
    if (trkhdr.bin != 0
        || trkhdr.cyl[0] != (cyl >> 8)
        || trkhdr.cyl[1] != (cyl & 0xFF)
        || trkhdr.head[0] != (head >> 8)
        || trkhdr.head[1] != (head & 0xFF))
    {
        XMERRF ("HHCDL100E %s cyl %d head %d invalid track header "
                "%2.2X%2.2X%2.2X%2.2X%2.2X\n",
                ofname, cyl, head,
                trkhdr.bin, trkhdr.cyl[0], trkhdr.cyl[1],
                trkhdr.head[0], trkhdr.head[1]);
        return -1;
    }
 
    /* Search for the note list record */
    while (1)
    {
        /* Copy the next record header */
        memcpy (&rechdr, cif->trkbuf + offset, CKDDASD_RECHDR_SIZE);
        offset += CKDDASD_RECHDR_SIZE;
 
        /* Check for end of track */
        if (memcmp(&rechdr, eighthexFF, 8) == 0)
        {
            XMERRF ("HHCDL101E %s cyl %d head %d rec %d "
                    "note list record not found\n",
                    ofname, cyl, head, rec);
            return -1;
        }
 
        /* Extract record key length and data length */
        klen = rechdr.klen;
        dlen = (rechdr.dlen[0] << 8) | rechdr.dlen[1];
 
        /* Exit loop if matching record number */
        if (rechdr.rec == rec)
            break;
 
        /* Skip the key and data areas */
        skiplen = klen + dlen;
        offset += skiplen;
    } /* end while */
 
    /* Check that the data length is sufficient */
    if (dlen < nllen)
    {
        XMERRF ("HHCDL102E Member %s note list at cyl %d head %d rec %d "
                "dlen %d is too short for %d TTRs",
                memname, cyl, head, rec, dlen, numnl);
        return -1;
    }
 
    /* Skip the key area if present */
    offset += klen;
 
    /* Copy the note list from the data area */
    memcpy (&notelist, cif->trkbuf + offset, nllen);
 
    /* Replace the TTRs in the note list record */
    ttrptr = notelist;
    for (i = 0; i < numnl; i++)
    {
        rc = replace_ttr (memname, ttrptr, ttrtab, numttr);
        if (rc < 0) return -1;
        ttrptr += 4;
    } /* end for(i) */
 
    /* Copy the updated note list to the buffer */
    memcpy (cif->trkbuf + offset, &notelist, nllen);
    cif->trkmodif = 1;
 
    /* Restore original file position */
    rc = read_track (cif, curcyl, curhead);
    if (rc < 0)
    {
        XMERRF ("HHCDL103E %s track read error cyl %d head %d\n",
                ofname, curcyl, curhead);
        return -1;
    }
 
    XMINFF (4, "HHCDL104I Updating cyl %u head %u rec %d kl %d dl %d\n",
                cyl, head, rec, klen, dlen);
 
    return 0;
} /* end function update_note_list */
 
/*-------------------------------------------------------------------*/
/* Subroutine to update a directory block record                     */
/* Input:                                                            */
/*      cif     -> CKD image file descriptor                         */
/*      ofname  Output file name                                     */
/*      heads   Number of tracks per cylinder on output device       */
/*      trklen  Track length of virtual output device                */
/*      dsstart Relative track number of start of dataset            */
/*      xbuf    Pointer to directory block                           */
/*      ttrtab  Pointer to TTR conversion table                      */
/*      numttr  Number of entries in TTR conversion table            */
/* Output:                                                           */
/*      Each original TTR in the directory block is replaced by the  */
/*      corresponding output TTR from the TTR conversion table.      */
/*      For any module which has note list entries, the note list    */
/*      TTRs in the note list record are also updated.               */
/*                                                                   */
/* Return value is 0 if successful, or -1 if any directory entry     */
/* contains a TTR which is not found in the TTR conversion table.    */
/*-------------------------------------------------------------------*/
static int
update_dirblk (CIFBLK *cif, char *ofname, int heads, int trklen,
                int dsstart, DATABLK *xbuf,
                TTRCONV *ttrtab, int numttr)
{
int             rc;                     /* Return code               */
int             size;                   /* Size of directory entry   */
int             k;                      /* Userdata halfword count   */
BYTE           *dirptr;                 /* -> Next byte within block */
int             dirrem;                 /* Number of bytes remaining */
PDSDIR         *dirent;                 /* -> Directory entry        */
BYTE           *ttrptr;                 /* -> User TTR               */
int             n;                      /* Number of user TTRs       */
int             i;                      /* Loop counter              */
char            memname[9];             /* Member name (ASCIIZ)      */
 
    fprintf (stderr, "update_dirblk \n");
    /* Load number of bytes in directory block */
    dirptr = xbuf->kdarea + 8;
    dirrem = (dirptr[0] << 8) | dirptr[1];
    if (dirrem < 2 || dirrem > 256)
    {
        XMERR ("HHCDL105E Directory block byte count is invalid\n");
        return -1;
    }
 
    /* Point to first directory entry */
    dirptr += 2;
    dirrem -= 2;
 
    /* Process each directory entry */
    while (dirrem > 0)
    {
        /* Point to next directory entry */
        dirent = (PDSDIR*)dirptr;
 
        /* Test for end of directory */
        if (memcmp(dirent->pds2name, eighthexFF, 8) == 0)
            break;
 
        /* Extract the member name */
        make_asciiz (memname, sizeof(memname), dirent->pds2name, 8);
 
        /* Replace the member TTR */
        rc = replace_ttr (memname, dirent->pds2ttrp, ttrtab, numttr);
        if (rc < 0) return -1;
 
        /* Load the number of user TTRs */
        n = (dirent->pds2indc & PDS2INDC_NTTR) >> PDS2INDC_NTTR_SHIFT;
 
        /* Replace the user TTRs */
        ttrptr = dirent->pds2usrd;
        for (i = 0; i < n; i++)
        {
            rc = replace_ttr (memname, ttrptr, ttrtab, numttr);
            if (rc < 0) return -1;
            ttrptr += 4;
        } /* end for(i) */
 
        /* Update the note list record if note list TTRs exist */
        if ((dirent->pds2indc & PDS2INDC_ALIAS) == 0
            && n >= 2 && dirent->pds2usrd[7] != 0)
        {
            rc = update_note_list (cif, ofname, heads, trklen,
                                dsstart, memname, dirent->pds2usrd+4,
                                ttrtab, numttr);
            if (rc < 0) return -1;
        }
 
        /* Load the user data halfword count */
        k = dirent->pds2indc & PDS2INDC_LUSR;
 
        /* Point to next directory entry */
        size = 12 + k*2;
        dirptr += size;
        dirrem -= size;
    }
 
    return 0;
} /* end function update_dirblk */
 
/*-------------------------------------------------------------------*/
/* Subroutine to read an IEBCOPY file and write to DASD image file   */
/* Input:                                                            */
/*      xfname  XMIT input file name                                 */
/*      ofname  DASD image file name                                 */
/*      cif     -> CKD image file descriptor                         */
/*      devtype Output device type                                   */
/*      heads   Output device number of tracks per cylinder          */
/*      trklen  Output device virtual track length                   */
/*      outcyl  Output starting cylinder number                      */
/*      outhead Output starting head number                          */
/*      maxtrks Maximum extent size in tracks                        */
/*      method  METHOD_XMIT or METHOD_VS                             */
/* Output:                                                           */
/*      odsorg  Dataset organization                                 */
/*      orecfm  Record format                                        */
/*      olrecl  Logical record length                                */
/*      oblksz  Block size                                           */
/*      okeyln  Key length                                           */
/*      dirblu  Bytes used in last directory block                   */
/*      lastrec Record number of last block written                  */
/*      trkbal  Number of bytes remaining on last track              */
/*      numtrks Number of tracks written                             */
/*      nxtcyl  Starting cylinder number for next dataset            */
/*      nxthead Starting head number for next dataset                */
/*-------------------------------------------------------------------*/
static int
process_iebcopy_file (char *xfname, char *ofname, CIFBLK *cif,
                U16 devtype, int heads, int trklen,
                int outcyl, int outhead, int maxtrks,
                BYTE method,
                BYTE *odsorg, BYTE *orecfm,
                int *olrecl, int *oblksz, int *okeyln,
                int *dirblu, int *lastrec, int *trkbal,
                int *numtrks, int *nxtcyl, int *nxthead)
{
int             rc = 0;                 /* Return code               */
int             i;                      /* Array subscript           */
int             xfd;                    /* XMIT file descriptor      */
int             dsstart;                /* Relative track number of
                                           start of output dataset   */
BYTE           *xbuf;                   /* -> Logical record buffer  */
int             xreclen;                /* Logical record length     */
BYTE            xctl;                   /* 0x20=Control record       */
char            xrecname[8];            /* XMIT control record name  */
int             datarecn = 0;           /* Data record counter       */
int             datafiln = 0;           /* Data file counter         */
int             copyfiln = 0;           /* Seq num of file to copy   */
BYTE            dsorg=0;                /* Dataset organization      */
BYTE            recfm=0;                /* Dataset record format     */
U16             lrecl=0;                /* Dataset record length     */
U16             blksz=0;                /* Dataset block size        */
U16             keyln=0;                /* Dataset key length        */
U16             dirnm;                  /* Number of directory blocks*/
int             enddir = 0;             /* 1=End of directory found  */
BYTE           *blkptr;                 /* -> Data block in record   */
DATABLK        *datablk;                /* -> Data block             */
int             blktrk;                 /* Data block relative track */
int             blkcyl;                 /* Data block cylinder number*/
int             blkhead;                /* Data block head number    */
int             blkrec;                 /* Data block record number  */
int             keylen;                 /* Key length of data block  */
int             datalen;                /* Data length of data block */
int             blklen;                 /* Total length of data block*/
int             origheads = 0;          /* Number of tracks/cylinder
                                           on original dataset       */
int             numext = 0;             /* Number of extents         */
EXTDESC         xarray[16];             /* Extent descriptor array   */
DATABLK       **dirblka;                /* -> Directory block array  */
int             dirblkn = 0;            /* #of directory blocks read */
int             outusedv = 0;           /* Output bytes used on track
                                           of virtual device         */
int             outusedr = 0;           /* Output bytes used on track
                                           of real device            */
int             outtrkbr = 0;           /* Output bytes remaining on
                                           track of real device      */
int             outtrk = 0;             /* Output relative track     */
int             outrec = 0;             /* Output record number      */
TTRCONV        *ttrtab;                 /* -> TTR conversion table   */
int             numttr = 0;             /* TTR table array index     */
COPYR1         *copyr1;                 /* -> header record 1        */
char            pathname[MAX_PATH];     /* xfname in host path format*/
 
    fprintf (stderr, "process_IEBCOPY \n");
    /* Open the input file */
    hostpath(pathname, xfname, sizeof(pathname));
    xfd = open (pathname, O_RDONLY|O_BINARY);
    if (xfd < 0)
    {
        XMERRF ("HHCDL106E Cannot open %s: %s\n",
                xfname, strerror(errno));
        return -1;
    }
 
    /* Obtain the input logical record buffer */
    xbuf = malloc (65536);
    if (xbuf == NULL)
    {
        XMERRF ("HHCDL107E Cannot obtain input buffer: %s\n",
                strerror(errno));
        close (xfd);
        return -1;
    }
 
    /* Obtain storage for the directory block array */
    dirblka = (DATABLK**)malloc (sizeof(DATABLK*) * MAXDBLK);
    if (dirblka == NULL)
    {
        XMERRF ("HHCDL108E Cannot obtain storage for directory block array: %s\n",
                strerror(errno));
        free (xbuf);
        close (xfd);
        return -1;
    }
 
    /* Obtain storage for the TTR conversion table */
    ttrtab = (TTRCONV*)malloc (sizeof(TTRCONV) * MAXTTR);
    if (ttrtab == NULL)
    {
        XMERRF ("HHCDL109E Cannot obtain storage for TTR table: %s\n",
                strerror(errno));
        free (xbuf);
        free (dirblka);
        close (xfd);
        return -1;
    }
 
    /* Calculate the relative track number of the dataset */
    dsstart = (outcyl * heads) + outhead;
 
    /* Display the file information message */
    XMINFF (1, "HHCDL110I Processing file %s\n", xfname);
 
    /* Read each logical record */
    while (1)
    {
        xctl=0;
        if (method == METHOD_XMIT)
           rc = read_xmit_rec (xfd, xfname, xbuf, &xctl);
        else if (method == METHOD_VS) {
           rc = read_vs_rec (xfd, xfname, xbuf, datarecn);
           if (rc == 0)                 /* end-of-file */
              break;
        } else
           rc = -1;
        if (rc < 0) return -1;
        xreclen = rc;
 
        /* Process control records */
        if (method == METHOD_XMIT && xctl)
        {
            /* Extract the control record name */
            make_asciiz (xrecname, sizeof(xrecname), xbuf, 6);
            XMINFF (4, "HHCDL111I Control record: %s length %d\n",
                        xrecname, xreclen);
 
            /* Exit if control record is a trailer record */
            if (strcmp(xrecname, "INMR06") == 0)
                break;
 
            /* Process control record according to type */
            if (strcmp(xrecname, "INMR02") == 0)
            {
                rc = process_inmr02 (xbuf, xreclen, &copyfiln,
                                     &dsorg, &recfm, &lrecl, &blksz,
                                     &keyln, &dirnm);
                if (rc < 0) return -1;
            }
            else
            {
                rc = process_inmrxx (xbuf, xreclen);
                if (rc < 0) return -1;
            }
 
            /* Reset the data counter if data control record */
            if (strcmp(xrecname, "INMR03") == 0)
            {
                datafiln++;
                datarecn = 0;
                XMINFF (4, "HHCDL112I File number: %d %s\n", datafiln,
                    (datafiln == copyfiln) ? "(selected)"
                                           : "(not selected)");
            }
 
            /* Loop to get next record */
            continue;
 
        } /* end if(xctl) */
 
        /* Process data records */
        datarecn++;
        XMINFF (4, "HHCDL113I Data record: length %d\n", xreclen);
        if (infolvl >= 5) data_dump (xbuf, xreclen);
 
        /* If this is not the IEBCOPY file then ignore data record */
        if (method == METHOD_XMIT && datafiln != copyfiln)
        {
            continue;
        }
 
        /* Process IEBCOPY header record 1 */
        if (datarecn == 1)
        {
            origheads = process_copyr1 (xbuf, xreclen);
            if (origheads < 0) exit(1);
            if (method == METHOD_VS) {
               copyr1 = (COPYR1 *)xbuf;
               dsorg = copyr1->ds1dsorg[0];
               recfm = copyr1->ds1recfm;
               lrecl = (copyr1->ds1lrecl[0] << 8) | copyr1->ds1lrecl[1];
               blksz = (copyr1->ds1blkl[0] << 8) | copyr1->ds1blkl[1];
               keyln = copyr1->ds1keyl;
            }
            continue;
        }
 
        /* Process IEBCOPY header record 2 */
        if (datarecn == 2)
        {
            numext = process_copyr2 (xbuf, xreclen, xarray);
            if (numext < 0) exit(1);
            continue;
        }
 
        /* Process each data block in data record */
        blkptr = xbuf;
        while (xreclen > 0)
        {
            /* Compute the length of the block */
            datablk = (DATABLK*)blkptr;
            blkcyl = (datablk->cyl[0] << 8)
                    | datablk->cyl[1];
            blkhead = (datablk->head[0] << 8)
                    | datablk->head[1];
            blkrec = datablk->rec;
            keylen = datablk->klen;
            datalen = (datablk->dlen[0] << 8)
                    | datablk->dlen[1];
            blklen = 12 + keylen + datalen;
 
            /* Calculate the TTR in the original dataset */
            blktrk = (enddir == 0) ? 0 :
                        calculate_ttr (blkcyl, blkhead,
                                origheads, numext, xarray);
 
            /* Write the data block to the output file */
            rc = write_block (cif, ofname, datablk, keylen, datalen,
                        devtype, heads, trklen, maxtrks,
                        &outusedv, &outusedr, &outtrkbr,
                        &outtrk, &outcyl, &outhead, &outrec);
            if (rc < 0)
            {
                XMERRF ("HHCDL114E write error: input record "
                        "CCHHR=%4.4X%4.4X%2.2X "
                        "(TTR=%4.4X%2.2X) KL=%d DL=%d\n",
                        blkcyl, blkhead, blkrec,
                        blktrk, blkrec, keylen, datalen);
                return -1;
            }
 
            XMINFF (4, "HHCDL115I CCHHR=%4.4X%4.4X%2.2X "
                        "(TTR=%4.4X%2.2X) KL=%d DL=%d "
                        "-> CCHHR=%4.4X%4.4X%2.2X "
                        "(TTR=%4.4X%2.2X)\n",
                        blkcyl, blkhead, blkrec,
                        blktrk, blkrec, keylen, datalen,
                        outcyl, outhead, outrec, outtrk, outrec);
 
            /* Process directory block or member block */
            if (enddir == 0)
            {
                rc = process_dirblk (datablk, blklen,
                                    outcyl, outhead, outrec,
                                    dirblka, dirblkn, dirblu);
                if (rc < 0) return -1;
                enddir = rc;
 
                /* Count the number of directory blocks read */
                if (enddir == 0) dirblkn++;
            }
            else /* Not a directory block */
            {
                /* Check that TTR conversion table is not full */
                if (numttr >= MAXTTR)
                {
                    XMERRF ("HHCDL116E TTR count exceeds %d, "
                            "increase MAXTTR\n",
                            MAXTTR);
                    return -1;
                }
 
                /* Add an entry to the TTR conversion table */
                ttrtab[numttr].origttr[0] = (blktrk >> 8) & 0xFF;
                ttrtab[numttr].origttr[1] = blktrk & 0xFF;
                ttrtab[numttr].origttr[2] = blkrec;
                ttrtab[numttr].outpttr[0] = (outtrk >> 8) & 0xFF;
                ttrtab[numttr].outpttr[1] = outtrk & 0xFF;
                ttrtab[numttr].outpttr[2] = outrec;
                numttr++;
            }
 
            /* Point to next data block in data record */
            xreclen -= blklen;
            blkptr += blklen;
 
        } /* end while(xreclen) */
 
    } /* end while(1) */
 
    /* Check for unsupported xmit utility */
    if (method == METHOD_XMIT && copyfiln == 0)
    {
        XMERRF ("HHCDL130W WARNING -- XMIT file utility is not IEBCOPY;"
                " file %s not loaded\n", xfname);
    }
 
    /* Return the last record number and track balance */
    *lastrec = outrec;
    *trkbal = outtrkbr;
 
    /* Write any data remaining in track buffer */
    rc = write_track (cif, ofname, heads, trklen,
                    &outusedv, &outtrk, &outcyl, &outhead);
    if (rc < 0) return -1;
 
    /* Update the directory and rewrite to output file */
    for (i = 0; i < dirblkn; i++)
    {
        /* Obtain the directory block pointer from the array */
        datablk = dirblka[i];
 
        /* Update TTR pointers in this directory block */
        rc = update_dirblk (cif, ofname, heads, trklen, dsstart,
                            datablk, ttrtab, numttr);
        if (rc < 0) return -1;
 
        /* Rewrite the updated directory block */
        blkcyl = (datablk->cyl[0] << 8) | datablk->cyl[1];
        blkhead = (datablk->head[0] << 8) | datablk->head[1];
        blkrec = datablk->rec;
        keylen = datablk->klen;
        datalen = (datablk->dlen[0] << 8) | datablk->dlen[1];
 
        rc = update_block (cif, ofname, datablk, blkcyl, blkhead,
                        blkrec, keylen, datalen, heads, trklen);
        if (rc < 0) return -1;
 
    } /* end for(i) */
 
    /* Close input file and release buffers */
    close (xfd);
    for (i = 0; i < dirblkn; i++)
        free (dirblka[i]);
    free (dirblka);
    free (xbuf);
    free (ttrtab);
 
    /* Return the dataset attributes */
    *odsorg = dsorg;
    *orecfm = recfm;
    *olrecl = lrecl;
    *oblksz = blksz;
    *okeyln = keyln;
 
    /* Return number of tracks and starting address of next dataset */
    *numtrks = outtrk;
    *nxtcyl = outcyl;
    *nxthead = outhead;
    return 0;
 
} /* end function process_iebcopy_file */
 
/*-------------------------------------------------------------------*/
/* Subroutine to initialize a SYSCTLG dataset as an OS CVOL          */
/* Input:                                                            */
/*      ofname  DASD image file name                                 */
/*      cif     -> CKD image file descriptor                         */
/*      volser  Volume serial number                                 */
/*      devtype Output device type                                   */
/*      heads   Output device number of tracks per cylinder          */
/*      trklen  Output device virtual track length                   */
/*      outcyl  Output starting cylinder number                      */
/*      outhead Output starting head number                          */
/*      extsize Extent size in tracks                                */
/* Output:                                                           */
/*      lastrec Record number of last block written                  */
/*      trkbal  Number of bytes remaining on last track              */
/*      numtrks Number of tracks written                             */
/*      nxtcyl  Starting cylinder number for next dataset            */
/*      nxthead Starting head number for next dataset                */
/* Note:                                                             */
/*      This subroutine builds a minimal SYSCTLG containing only     */
/*      the entries required on an OS/360 IPL volume.                */
/*-------------------------------------------------------------------*/
static int
cvol_initialize (char *ofname, CIFBLK *cif, char *volser,
                U16 devtype, int heads, int trklen,
                int outcyl, int outhead, int extsize,
                int *lastrec, int *trkbal,
                int *numtrks, int *nxtcyl, int *nxthead)
{
int             rc;                     /* Return code               */
int             i;                      /* Array subscript           */
int             keylen;                 /* Key length of data block  */
int             datalen;                /* Data length of data block */
int             outusedv = 0;           /* Output bytes used on track
                                           of virtual device         */
int             outusedr = 0;           /* Output bytes used on track
                                           of real device            */
int             outtrkbr = 0;           /* Output bytes remaining on
                                           track of real device      */
int             outtrk = 0;             /* Output relative track     */
int             outrec = 0;             /* Output record number      */
int             blkptrk;                /* Number of blocks per track*/
int             totblks;                /* Number of blocks in CVOL  */
int             bytes;                  /* Bytes used in this block  */
U32             ucbtype;                /* UCB device type           */
PDSDIR         *catent;                 /* -> Catalog entry          */
DATABLK         datablk;                /* Data block                */
#define NUM_SYS1_DATASETS       8       /* Number of SYS1 datasets   */
static char    *sys1name[NUM_SYS1_DATASETS] =
                {"DUMP", "IMAGELIB", "LINKLIB", "NUCLEUS",
                "PARMLIB", "PROCLIB", "SAMPLIB", "SYSJOBQE"};
 
    fprintf (stderr, "cvol_initialize \n");
    /* Set the key length and data length for SYSCTLG dataset */
    keylen = 8;
    datalen = 256;
 
    /* Obtain the number of blocks which will fit on a track */
    capacity_calc (cif, 0, keylen, datalen, NULL, NULL,
                    NULL, NULL, NULL, NULL, NULL,
                    NULL, NULL, &blkptrk, NULL, NULL);
 
    /* Calculate the total number of blocks in the catalog */
    totblks = extsize * blkptrk;
 
    /* Get the UCB device type */
    ucbtype = ucbtype_code (devtype);
 
    /*-----------------------------------*/
    /* Initialize the volume index block */
    /*-----------------------------------*/
    memset (datablk.kdarea, 0, keylen + datalen);
 
    /* The key field contains all X'FF' */
    memcpy (datablk.kdarea, eighthexFF, 8);
 
    /* The first entry begins after the 2 byte count field */
    bytes = 2;
    catent = (PDSDIR*)(datablk.kdarea + keylen + bytes);
 
    /* Build the volume index control entry (VICE) */
 
    /* The VICE name is X'0000000000000001' */
    memcpy (catent->pds2name, cvol_low_key, 8);
 
    /* Set TTR to highest block in volume index, i.e. X'000001' */
    catent->pds2ttrp[0] = 0;
    catent->pds2ttrp[1] = 0;
    catent->pds2ttrp[2] = 1;
 
    /* Indicator byte X'05' means 5 user halfwords follow, and
       uniquely identifies this catalog entry as a VICE */
    catent->pds2indc = 5;
 
    /* Set the TTR of the last block of the catalog */
    catent->pds2usrd[0] = ((extsize - 1) >> 8) & 0xFF;
    catent->pds2usrd[1] = (extsize - 1) & 0xFF;
    catent->pds2usrd[2] = blkptrk;
    catent->pds2usrd[3] = 0;
 
    /* Set the TTR of the first unused block (X'000003') */
    catent->pds2usrd[4] = 0;
    catent->pds2usrd[5] = 0;
    catent->pds2usrd[6] = 3;
 
    /* Remainder of user data is 0 */
    catent->pds2usrd[7] = 0;
    catent->pds2usrd[8] = 0;
    catent->pds2usrd[9] = 0;
 
    /* Increment bytes used by the length of the VICE */
    bytes += 22;
    catent = (PDSDIR*)(datablk.kdarea + keylen + bytes);
 
    /* Build the index pointer for SYS1 */
    convert_to_ebcdic (catent->pds2name, 8, "SYS1");
 
    /* Set TTR of the SYS1 index block, i.e. X'000002' */
    catent->pds2ttrp[0] = 0;
    catent->pds2ttrp[1] = 0;
    catent->pds2ttrp[2] = 2;
 
    /* Indicator byte X'00' means no user halfwords follow, and
       uniquely identifies this catalog entry as an index pointer */
    catent->pds2indc = 0;
 
    /* Increment bytes used by the length of the index pointer */
    bytes += 12;
    catent = (PDSDIR*)(datablk.kdarea + keylen + bytes);
 
    /* Set the last entry in block marker */
    memcpy (catent->pds2name, eighthexFF, 8);
 
    /* Increment bytes used by the last entry marker */
    bytes += 12;
    catent = (PDSDIR*)(datablk.kdarea + keylen + bytes);
 
    /* Set the number of bytes used in this block */
    datablk.kdarea[keylen+0] = (bytes >> 8) & 0xFF;
    datablk.kdarea[keylen+1] = bytes & 0xFF;
 
    /* Write the volume index block to the output file */
    rc = write_block (cif, ofname, &datablk, keylen, datalen,
                devtype, heads, trklen, extsize,
                &outusedv, &outusedr, &outtrkbr,
                &outtrk, &outcyl, &outhead, &outrec);
    if (rc < 0) return -1;
 
    XMINFF (4, "HHCDL117I Catalog block at cyl %d head %d rec %d\n",
            outcyl, outhead, outrec);
    if (infolvl >= 5) data_dump (datablk.kdarea, keylen + datalen);
 
    /* Count number of blocks written */
    totblks--;
 
    /*---------------------------------*/
    /* Initialize the SYS1 index block */
    /*---------------------------------*/
    memset (datablk.kdarea, 0, keylen + datalen);
 
    /* The key field contains all X'FF' */
    memcpy (datablk.kdarea, eighthexFF, 8);
 
    /* The first entry begins after the 2 byte count field */
    bytes = 2;
    catent = (PDSDIR*)(datablk.kdarea + keylen + bytes);
 
    /* Build the index control entry (ICE) */
 
    /* The ICE name is X'0000000000000001' */
    memcpy (catent->pds2name, cvol_low_key, 8);
 
    /* Set TTR to highest block in this index, i.e. X'000002' */
    catent->pds2ttrp[0] = 0;
    catent->pds2ttrp[1] = 0;
    catent->pds2ttrp[2] = 2;
 
    /* Indicator byte X'03' means 3 user halfwords follow, and
       uniquely identifies this catalog entry as an ICE */
    catent->pds2indc = 3;
 
    /* Set the TTR of this block */
    catent->pds2usrd[0] = 0;
    catent->pds2usrd[1] = 0;
    catent->pds2usrd[2] = 2;
 
    /* The next byte contains the alias count */
    catent->pds2usrd[3] = 0;
 
    /* The remaining 2 bytes of userdata are zeroes */
    catent->pds2usrd[4] = 0;
    catent->pds2usrd[5] = 0;
 
    /* Increment bytes used by the length of the ICE */
    bytes += 18;
 
    /* Build the dataset pointers for SYS1.xxxxxxxx datasets */
    for (i = 0; i < NUM_SYS1_DATASETS; i++)
    {
        /* Point to next dataset pointer entry */
        catent = (PDSDIR*)(datablk.kdarea + keylen + bytes);
 
        /* Set the name of the dataset pointer entry */
        convert_to_ebcdic (catent->pds2name, 8, sys1name[i]);
 
        /* Set the TTR to zero */
        catent->pds2ttrp[0] = 0;
        catent->pds2ttrp[1] = 0;
        catent->pds2ttrp[2] = 0;
 
        /* Indicator byte X'07' means 7 user halfwords follow, and
           uniquely identifies the entry as a dataset pointer */
        catent->pds2indc = 7;
 
        /* The next two bytes contain the volume count (X'0001') */
        catent->pds2usrd[0] = 0;
        catent->pds2usrd[1] = 1;
 
        /* The next four bytes contain the UCB type */
        catent->pds2usrd[2] = (ucbtype >> 24) & 0xFF;
        catent->pds2usrd[3] = (ucbtype >> 16) & 0xFF;
        catent->pds2usrd[4] = (ucbtype >> 8) & 0xFF;
        catent->pds2usrd[5] = ucbtype & 0xFF;
 
        /* The next six bytes contain the volume serial number */
        convert_to_ebcdic (catent->pds2usrd+6, 6, volser);
 
        /* The next two bytes contain the volume seq.no. (X'0000') */
        catent->pds2usrd[12] = 0;
        catent->pds2usrd[13] = 0;
 
        /* Increment bytes used by the length of the dataset pointer */
        bytes += 26;
 
    } /* end for(i) */
 
    /* Point to last entry in block */
    catent = (PDSDIR*)(datablk.kdarea + keylen + bytes);
 
    /* Set the last entry in block marker */
    memcpy (catent->pds2name, eighthexFF, 8);
 
    /* Increment bytes used by the last entry marker */
    bytes += 12;
    catent = (PDSDIR*)(datablk.kdarea + keylen + bytes);
 
    /* Set the number of bytes used in this block */
    datablk.kdarea[keylen+0] = (bytes >> 8) & 0xFF;
    datablk.kdarea[keylen+1] = bytes & 0xFF;
 
    /* Write the index block to the output file */
    rc = write_block (cif, ofname, &datablk, keylen, datalen,
                devtype, heads, trklen, extsize,
                &outusedv, &outusedr, &outtrkbr,
                &outtrk, &outcyl, &outhead, &outrec);
    if (rc < 0) return -1;
 
    XMINFF (4, "HHCDL118I Catalog block at cyl %d head %d rec %d\n",
            outcyl, outhead, outrec);
    if (infolvl >= 5) data_dump (datablk.kdarea, keylen + datalen);
 
    /* Count number of blocks written */
    totblks--;
 
    /*--------------------------------------------*/
    /* Initialize remaining unused catalog blocks */
    /*--------------------------------------------*/
    while (totblks > 0)
    {
        memset (datablk.kdarea, 0, keylen + datalen);
 
        /* Write the volume index block to the output file */
        rc = write_block (cif, ofname, &datablk, keylen, datalen,
                    devtype, heads, trklen, extsize,
                    &outusedv, &outusedr, &outtrkbr,
                    &outtrk, &outcyl, &outhead, &outrec);
        if (rc < 0) return -1;
 
        XMINFF (4, "HHCDL119I Catalog block at cyl %d head %d rec %d\n",
                outcyl, outhead, outrec);
        if (infolvl >= 5) data_dump (datablk.kdarea, keylen + datalen);
 
        /* Count number of blocks written */
        totblks--;
 
    } /* end while(totblks) */
 
    /* Set the last record number to X'FF' so that OS/360 catalog
       management routines can recognize that the CVOL has been
       initialized by detecting X'FF' at ds1lstar+2 in the VTOC */
    *lastrec = 0xFF;
 
    /* Return the track balance */
    *trkbal = outtrkbr;
 
    /* Write data remaining in track buffer */
    rc = write_track (cif, ofname, heads, trklen,
                    &outusedv, &outtrk, &outcyl, &outhead);
    if (rc < 0) return -1;
 
    /* Return number of tracks and starting address of next dataset */
    *numtrks = outtrk;
    *nxtcyl = outcyl;
    *nxthead = outhead;
    return 0;
 
} /* end function cvol_initialize */
 
/*-------------------------------------------------------------------*/
/* Subroutine to initialize a LOGREC dataset with IFCDIP00 header    */
/* Input:                                                            */
/*      ofname  DASD image file name                                 */
/*      cif     -> CKD image file descriptor                         */
/*      devtype Output device type                                   */
/*      heads   Output device number of tracks per cylinder          */
/*      trklen  Output device virtual track length                   */
/*      outcyl  Output starting cylinder number                      */
/*      outhead Output starting head number                          */
/*      extsize Extent size in tracks                                */
/* Output:                                                           */
/*      lastrec Record number of last block written                  */
/*      trkbal  Number of bytes remaining on last track              */
/*      numtrks Number of tracks written                             */
/*      nxtcyl  Starting cylinder number for next dataset            */
/*      nxthead Starting head number for next dataset                */
/*-------------------------------------------------------------------*/
static int
dip_initialize (char *ofname, CIFBLK *cif,
                U16 devtype, int heads, int trklen,
                int outcyl, int outhead, int extsize,
                int *lastrec, int *trkbal,
                int *numtrks, int *nxtcyl, int *nxthead)
{
int             rc;                     /* Return code               */
int             keylen;                 /* Key length of data block  */
int             datalen;                /* Data length of data block */
int             outusedv = 0;           /* Output bytes used on track
                                           of virtual device         */
int             outusedr = 0;           /* Output bytes used on track
                                           of real device            */
int             outtrkbr = 0;           /* Output bytes remaining on
                                           track of real device      */
int             outtrk = 0;             /* Output relative track     */
int             outrec = 0;             /* Output record number      */
int             remlen;                 /* Bytes remaining on 1st trk*/
int             physlen;                /* Physical track length     */
int             lasthead;               /* Highest head on cylinder  */
int             endcyl;                 /* Extent end cylinder       */
int             endhead;                /* Extent end head           */
int             trklen90;               /* 90% of track length       */
int             cyl90;                  /* 90% full cylinder number  */
int             head90;                 /* 90% full head number      */
int             reltrk90;               /* 90% full relative track   */
DIPHDR         *diphdr;                 /* -> Record in data block   */
DATABLK         datablk;                /* Data block                */
 
    fprintf (stderr, "dip_initialize \n");
    /* Set the key length and data length for the header record */
    keylen = 0;
    datalen = sizeof(DIPHDR);
 
    /* Obtain the physical track size and the track balance
       remaining on the first track after the header record */
    capacity_calc (cif, 0, keylen, datalen, NULL, &remlen,
                    &physlen, NULL, NULL, NULL, NULL, NULL,
                    NULL, NULL, NULL, NULL);
 
    /* Calculate the end of extent cylinder and head */
    lasthead = heads - 1;
    endcyl = outcyl;
    endhead = outhead + extsize - 1;
    while (endhead >= heads)
    {
        endhead -= heads;
        endcyl++;
    }
 
    /* Calculate the 90% full cylinder and head */
    trklen90 = physlen * 9 / 10;
    reltrk90 = extsize * trklen90 / physlen;
    if (reltrk90 == 0) reltrk90 = 1;
    cyl90 = outcyl;
    head90 = outhead + reltrk90 - 1;
    while (head90 >= heads)
    {
        head90 -= heads;
        cyl90++;
    }
 
    /* Initialize the DIP header record */
    diphdr = (DIPHDR*)(datablk.kdarea);
    memset (diphdr, 0, sizeof(DIPHDR));
    diphdr->recid[0] = 0xFF;
    diphdr->recid[1] = 0xFF;
    diphdr->bcyl[0] = (outcyl >> 8) & 0xFF;
    diphdr->bcyl[1] = outcyl & 0xFF;
    diphdr->btrk[0] = (outhead >> 8) & 0xFF;
    diphdr->btrk[1] = outhead & 0xFF;
    diphdr->ecyl[0] = (endcyl >> 8) & 0xFF;
    diphdr->ecyl[1] = endcyl & 0xFF;
    diphdr->etrk[0] = (endhead >> 8) & 0xFF;
    diphdr->etrk[1] = endhead & 0xFF;
    diphdr->restart[2] = (outcyl >> 8) & 0xFF;
    diphdr->restart[3] = outcyl & 0xFF;
    diphdr->restart[4] = (outhead >> 8) & 0xFF;
    diphdr->restart[5] = outhead & 0xFF;
    diphdr->restart[6] = 1;
    diphdr->trkbal[0] = (remlen >> 8) & 0xFF;
    diphdr->trkbal[1] = remlen & 0xFF;
    diphdr->trklen[0] = (physlen >> 8) & 0xFF;
    diphdr->trklen[1] = physlen & 0xFF;
    diphdr->reused[2] = (outcyl >> 8) & 0xFF;
    diphdr->reused[3] = outcyl & 0xFF;
    diphdr->reused[4] = (outhead >> 8) & 0xFF;
    diphdr->reused[5] = outhead & 0xFF;
    diphdr->reused[6] = 1;
    diphdr->lasthead[0] = (lasthead >> 8) & 0xFF;
    diphdr->lasthead[1] = lasthead & 0xFF;
    diphdr->trklen90[0] = (trklen90 >> 8) & 0xFF;
    diphdr->trklen90[1] = trklen90 & 0xFF;
    diphdr->devcode = (ucbtype_code(devtype) & 0x0F) | 0xF0;
    diphdr->cchh90[0] = (cyl90 >> 8) & 0xFF;
    diphdr->cchh90[1] = cyl90 & 0xFF;
    diphdr->cchh90[2] = (head90 >> 8) & 0xFF;
    diphdr->cchh90[3] = head90 & 0xFF;
    diphdr->endid = 0xFF;
 
    /* Write the data block to the output file */
    rc = write_block (cif, ofname, &datablk, keylen, datalen,
                devtype, heads, trklen, extsize,
                &outusedv, &outusedr, &outtrkbr,
                &outtrk, &outcyl, &outhead, &outrec);
    if (rc < 0) return -1;
 
    XMINFF (3, "HHCDL120I DIP complete at cyl %d head %d rec %d\n",
            outcyl, outhead, outrec);
    if (infolvl >= 5) data_dump (diphdr, sizeof(DIPHDR));
 
    /* Return the last record number and track balance */
    *lastrec = outrec;
    *trkbal = outtrkbr;
 
    /* Write data remaining in track buffer */
    rc = write_track (cif, ofname, heads, trklen,
                    &outusedv, &outtrk, &outcyl, &outhead);
    if (rc < 0) return -1;
 
    /* Return number of tracks and starting address of next dataset */
    *numtrks = outtrk;
    *nxtcyl = outcyl;
    *nxthead = outhead;
    return 0;
 
} /* end function dip_initialize */
 
/*-------------------------------------------------------------------*/
/* Subroutine to initialize a sequential dataset                     */
/* Input:                                                            */
/*      sfname  SEQ input file name                                  */
/*      ofname  DASD image file name                                 */
/*      cif     -> CKD image file descriptor                         */
/*      devtype Output device type                                   */
/*      heads   Output device number of tracks per cylinder          */
/*      trklen  Output device virtual track length                   */
/*      outcyl  Output starting cylinder number                      */
/*      outhead Output starting head number                          */
/*      extsize Extent size in tracks                                */
/*      dsorg   Dataset organization  (DA or PS)                     */
/*      recfm   Record Format (F or FB)                              */
/*      lrecl   Record length                                        */
/*      blksz   Block size                                           */
/*      keyln   Key length                                           */
/* Output:                                                           */
/*      lastrec Record number of last block written                  */
/*      trkbal  Number of bytes remaining on last track              */
/*      numtrks Number of tracks written                             */
/*      nxtcyl  Starting cylinder number for next dataset            */
/*      nxthead Starting head number for next dataset                */
/*-------------------------------------------------------------------*/
static int
seq_initialize (char *sfname, char *ofname, CIFBLK *cif, U16 devtype,
                int heads, int trklen, int outcyl, int outhead,
                int extsize, BYTE dsorg, BYTE recfm,
                int lrecl, int blksz, int keyln,
                int *lastrec, int *trkbal,
                int *numtrks, int *nxtcyl, int *nxthead)
{
int             rc;                     /* Return code               */
int             sfd;                    /* Input seq file descriptor */
int             size;                   /* Size left in input file   */
int             outusedv = 0;           /* Output bytes used on track
                                           of virtual device         */
int             outusedr = 0;           /* Output bytes used on track
                                           of real device            */
int             outtrkbr = 0;           /* Output bytes remaining on
                                           track of real device      */
int             outtrk = 0;             /* Output relative track     */
int             outrec = 0;             /* Output record number      */
struct stat     st;                     /* Data area for fstat()     */
DATABLK         datablk;                /* Data block                */
char            pathname[MAX_PATH];     /* sfname in host path format*/
 
    fprintf (stderr, "seq_initialize \n");
    /* Perform some checks */
    if (!(dsorg & DSORG_PS) && !(dsorg & DSORG_DA))
    {
        XMERRF ("HHCDL121E SEQ dsorg must be PS or DA: dsorg=0x%2.2x\n",dsorg);
        return -1;
    }
    if (recfm != RECFM_FORMAT_F 
        && recfm != (RECFM_FORMAT_F|RECFM_BLOCKED)
        && recfm != RECFM_FORMAT_U)
    {
        XMERRF ("HHCDL122E SEQ recfm must be F or FB or U: "
                "recfm=0x%2.2x\n",recfm);
        return -1;
    }
    if ((lrecl == 0) && (recfm == RECFM_FORMAT_U)) lrecl = 1;
    if (blksz == 0) blksz = lrecl;
    if (lrecl == 0) lrecl = blksz;
    if (lrecl == 0 || blksz % lrecl != 0
     || (blksz != lrecl && recfm == RECFM_FORMAT_F))
    {
        XMERRF ("HHCDL123E SEQ invalid lrecl or blksz: lrecl=%d blksz=%d\n",
                lrecl,blksz);
        return -1;
    }
    if (keyln > 0 && blksz > lrecl)
    {
        XMERR ("HHCDL124E SEQ keyln must be 0 for blocked files\n");
        return -1;
    }
 
    /* Open the input file */
    hostpath(pathname, sfname, sizeof(pathname));
    sfd = open (pathname, O_RDONLY|O_BINARY);
    if (sfd < 0)
    {
        XMERRF ("HHCDL125E Cannot open %s: %s\n",
                sfname, strerror(errno));
        return -1;
    }
 
    /* Get input file status */
    rc = fstat(sfd, &st);
    if (rc < 0)
    {
        XMERRF ("HHCDL126E Cannot stat %s: %s\n",
                sfname, strerror(errno));
        close (sfd);
        return -1;
    }
    size = st.st_size;
 
    /* Read the first track */
    rc = read_track (cif, *nxtcyl, *nxthead);
    if (rc < 0)
    {
        XMERRF ("HHCDL127E %s cyl %d head %d read error\n",
                ofname, *nxtcyl, *nxthead);
        close (sfd);
        return -1;
    }
 
    while (size > 0)
    {
        /* Read a block of data from the input file */
        rc = read (sfd, &datablk.kdarea, blksz < size ? blksz : size);
        if (rc < (blksz < size ? blksz : size))
        {
            XMERRF ("HHCDL128E %s read error: %s\n",
                    sfname, strerror(errno));
            close (sfd);
            return -1;
        }
        size -= rc;
 
        /* Pad the block if necessary */
        if (rc < blksz)
        {
            /* Adjust blksize down to next
               highest multiple of lrecl */
            blksz = (((rc-1) / lrecl) + 1) * lrecl;
            memset (&datablk.kdarea[rc], 0, blksz - rc);
        }
 
        rc = write_block (cif, ofname, &datablk, keyln, blksz - keyln,
                        devtype, heads, trklen, extsize,
                        &outusedv, &outusedr, &outtrkbr,
                        &outtrk, &outcyl, &outhead, &outrec);
        if (rc < 0)
        {
            close (sfd);
            return -1;
        }
    }
 
    /* Close the input file */
    close (sfd);
 
    /* Create the end of file record */
    rc = write_block (cif, ofname, &datablk, 0, 0,
                devtype, heads, trklen, extsize,
                &outusedv, &outusedr, &outtrkbr,
                &outtrk, &outcyl, &outhead, &outrec);
    if (rc < 0) return -1;
 
    /* Return the last record number and track balance */
    *lastrec = outrec;
    *trkbal = outtrkbr;
 
    /* Write data remaining in track buffer */
    rc = write_track (cif, ofname, heads, trklen,
                    &outusedv, &outtrk, &outcyl, &outhead);
    if (rc < 0) return -1;
 
    /* Return number of tracks and starting address of next dataset */
    *numtrks = outtrk;
    *nxtcyl = outcyl;
    *nxthead = outhead;
    return 0;
 
} /* end function seq_initialize */
 
 
/*-------------------------------------------------------------------*/
/* Subroutine to initialize an empty dataset                         */
/* Input:                                                            */
/*      ofname  DASD image file name                                 */
/*      cif     -> CKD image file descriptor                         */
/*      devtype Output device type                                   */
/*      heads   Output device number of tracks per cylinder          */
/*      trklen  Output device virtual track length                   */
/*      outcyl  Output starting cylinder number                      */
/*      outhead Output starting head number                          */
/*      extsize Extent size in tracks                                */
/*      dsorg   Dataset organization                                 */
/*      dirblks Number of directory blocks                           */
/* Output:                                                           */
/*      dirblu  Bytes used in last directory block                   */
/*      lastrec Record number of last block written                  */
/*      trkbal  Number of bytes remaining on last track              */
/*      numtrks Number of tracks written                             */
/*      nxtcyl  Starting cylinder number for next dataset            */
/*      nxthead Starting head number for next dataset                */
/*-------------------------------------------------------------------*/
static int
empty_initialize (char *ofname, CIFBLK *cif, U16 devtype,
                int heads, int trklen, int outcyl, int outhead,
                int extsize, BYTE dsorg, int dirblks,
                int *dirblu, int *lastrec, int *trkbal,
                int *numtrks, int *nxtcyl, int *nxthead)
{
int             rc;                     /* Return code               */
int             i;                      /* Loop counter              */
int             keylen;                 /* Key length of data block  */
int             datalen;                /* Data length of data block */
int             outusedv = 0;           /* Output bytes used on track
                                           of virtual device         */
int             outusedr = 0;           /* Output bytes used on track
                                           of real device            */
int             outtrkbr = 0;           /* Output bytes remaining on
                                           track of real device      */
int             outdblu = 0;            /* Output bytes used in last
                                           directory block           */
int             outtrk = 0;             /* Output relative track     */
int             outrec = 0;             /* Output record number      */
DATABLK         datablk;                /* Data block                */
 
    fprintf (stderr, "empty_initialize \n");
    /* Initialize the directory if dataset is a PDS */
    if (dsorg & DSORG_PO)
    {
        /* Build the first directory block */
        keylen = 8;
        datalen = 256;
        outdblu = 14;
        memset (datablk.kdarea, 0, keylen + datalen);
        memcpy (datablk.kdarea, eighthexFF, 8);
        datablk.kdarea[keylen] = (outdblu >> 8);
        datablk.kdarea[keylen+1] = outdblu & 0xFF;
        memcpy (datablk.kdarea + keylen + 2, eighthexFF, 8);
 
        /* Write directory blocks to output dataset */
        for (i = 0; i < dirblks; i++)
        {
            /* Write a directory block */
            rc = write_block (cif, ofname, &datablk, keylen, datalen,
                        devtype, heads, trklen, extsize,
                        &outusedv, &outusedr, &outtrkbr,
                        &outtrk, &outcyl, &outhead, &outrec);
            if (rc < 0) return -1;
 
            /* Clear subsequent directory blocks to zero */
            memset (datablk.kdarea, 0, keylen + datalen);
 
        } /* end for(i) */
 
    } /* end if(DSORG_PO) */
 
    /* Create the end of file record */
    keylen = 0;
    datalen = 0;
    rc = write_block (cif, ofname, &datablk, keylen, datalen,
                devtype, heads, trklen, extsize,
                &outusedv, &outusedr, &outtrkbr,
                &outtrk, &outcyl, &outhead, &outrec);
    if (rc < 0) return -1;
 
    /* Return number of bytes used in last directory block */
    *dirblu = outdblu;
 
    /* Return the last record number and track balance */
    *lastrec = outrec;
    *trkbal = outtrkbr;
 
    /* Write data remaining in track buffer */
    rc = write_track (cif, ofname, heads, trklen,
                    &outusedv, &outtrk, &outcyl, &outhead);
    if (rc < 0) return -1;
 
    /* Return number of tracks and starting address of next dataset */
    *numtrks = outtrk;
    *nxtcyl = outcyl;
    *nxthead = outhead;
    return 0;
 
} /* end function empty_initialize */
 
/*-------------------------------------------------------------------*/
/* Subroutine to read a statement from the control file              */
/* Input:                                                            */
/*      cfp     Control file pointer                                 */
/*      cfname  Control file name                                    */
/*      stmt    Buffer to receive control statement                  */
/*      sbuflen Length of statement buffer                           */
/* Output:                                                           */
/*      pstmtno Statement number                                     */
/*      The return value is 0 if a statement was successfully read,  */
/*      +1 if end of file, or -1 if error                            */
/*-------------------------------------------------------------------*/
static int
read_ctrl_stmt (FILE *cfp, char *cfname, char *stmt, int sbuflen,
                int *pstmtno)
{
int             stmtlen;                /* Length of input statement */
static int      stmtno = 0;             /* Statement number          */
 
    fprintf (stderr, "read_ctl_stmt \n");
    while (1)
    {
        /* Read next record from control file */
        stmtno++;
        *pstmtno = stmtno;
        if (fgets (stmt, sbuflen, cfp) == NULL)
        {
            /* Return code +1 if end of control file */
            if (feof(cfp)) return +1;
 
            /* Return code -1 if control file input error */
            XMERRF ("HHCDL019E Cannot read %s line %d: %s\n",
                    cfname, stmtno, strerror(errno));
            return -1;
        }
 
#ifdef EXTERNALGUI
        /* Indicate input file progess */
        if (extgui) fprintf (stderr, "IPOS=%" I64_FMT "d\n", (U64)ftell(cfp));
#endif /*EXTERNALGUI*/
 
        /* Check for DOS end of file character */
        if (stmt[0] == '\x1A')
            return +1;
 
        /* Check that end of statement has been read */
        stmtlen = strlen(stmt);
        if (stmtlen == 0 || stmt[stmtlen-1] != '\n')
        {
            XMERRF ("HHCDL020E Line too long in %s line %d\n",
                    cfname, stmtno);
            return -1;
        }
 
        /* Remove trailing carriage return and line feed */
        stmtlen--;
        if (stmtlen > 0 && stmt[stmtlen-1] == '\r') stmtlen--;
 
        /* Remove trailing spaces and tab characters */
        while (stmtlen > 0 && (stmt[stmtlen-1] == SPACE
                || stmt[stmtlen-1] == '\t')) stmtlen--;
        stmt[stmtlen] = '\0';
 
        /* Print the input statement */
        XMINFF (0, "--------- %s\n", stmt);
 
        /* Ignore comment statements */
        if (stmtlen == 0 || stmt[0] == '#' || stmt[0] == '*')
            continue;
 
        break;
    } /* end while */
 
    return 0;
} /* end function read_ctrl_stmt */
 
/*-------------------------------------------------------------------*/
/* Subroutine to parse a dataset statement from the control file     */
/* Input:                                                            */
/*      stmt    Control statement                                    */
/* Output:                                                           */
/*      dsname  ASCIIZ dataset name (1-44 bytes + terminator)        */
/*      method  Processing method (see METHOD_xxx defines)           */
/*                                                                   */
/*      The following field is returned only for the XMIT method:    */
/*      ifptr   Pointer to XMIT initialization file name             */
/*                                                                   */
/*      The following fields are returned for non-XMIT methods:      */
/*      units   Allocation units (C=CYL, T=TRK)                      */
/*      sppri   Primary allocation quantity                          */
/*      spsec   Secondary allocation quantity                        */
/*      spdir   Directory allocation quantity                        */
/*      dsorg   1st byte of dataset organization bits                */
/*      recfm   1st byte of record format bits                       */
/*      lrecl   Logical record length                                */
/*      blksz   Block size                                           */
/*      keyln   Key length                                           */
/*      The return value is 0 if successful, or -1 if error.         */
/* Control statement format:                                         */
/*      dsname method [initfile] [space [dcbattrib]]                 */
/*      The method can be:                                           */
/*      XMIT = load PDS from initfile containing an IEBCOPY unload   */
/*             dataset created using the TSO TRANSMIT command        */
/*      EMPTY = create empty dataset (do not specify initfile)       */
/*      DIP = initialize LOGREC dataset with IFCDIP00 header record  */
/*      CVOL = initialize SYSCTLG dataset as an OS CVOL              */
/*      VTOC = reserve space for the VTOC (dsname is ignored)        */
/*      The space allocation can be:                                 */
/*      CYL [pri [sec [dir]]]                                        */
/*      TRK [pri [sec [dir]]]                                        */
/*      If primary quantity is omitted then the dataset will be      */
/*      allocated the minimum number of tracks or cylinders needed   */
/*      to contain the data loaded from the initfile.                */
/*      Default allocation is in tracks.                             */
/*      The dcb attributes can be:                                   */
/*      dsorg recfm lrecl blksize keylen                             */
/*      For the XMIT method the dcb attributes are taken from the    */
/*      initialization file and need not be specified.               */
/*      Examples:                                                    */
/*      SYS1.PARMLIB XMIT /cdrom/os360/reslibs/parmlib.xmi           */
/*      SYS1.NUCLEUS XMIT /cdrom/os360/reslibs/nucleus.xmi CYL       */
/*      SYS1.SYSJOBQE EMPTY CYL 10 0 0 DA F 176 176 0                */
/*      SYS1.DUMP EMPTY CYL 10 2 0 PS FB 4104 4104 0                 */
/*      SYS1.OBJPDS EMPTY CYL 10 2 50 PO FB 80 3120 0                */
/*      SYSVTOC VTOC CYL 1                                           */
/*      SYSCTLG CVOL TRK 10                                          */
/*      SYS1.LOGREC DIP CYL 1                                        */
/*-------------------------------------------------------------------*/
static int
parse_ctrl_stmt (char *stmt, char *dsname, BYTE *method, char **ifptr,
                BYTE *units, int *sppri, int *spsec, int *spdir,
                BYTE *dsorg, BYTE *recfm,
                int *lrecl, int *blksz, int *keyln)
{
char           *pdsnam;                 /* -> dsname in input stmt   */
char           *punits;                 /* -> allocation units       */
char           *psppri;                 /* -> primary space quantity */
char           *pspsec;                 /* -> secondary space qty.   */
char           *pspdir;                 /* -> directory space qty.   */
char           *pdsorg;                 /* -> dataset organization   */
char           *precfm;                 /* -> record format          */
char           *plrecl;                 /* -> logical record length  */
char           *pblksz;                 /* -> block size             */
char           *pkeyln;                 /* -> key length             */
char           *pimeth;                 /* -> initialization method  */
char           *pifile;                 /* -> initialization filename*/
BYTE            c;                      /* Character work area       */
 
    fprintf (stderr, "parse_ctl_stmt \n");
    /* Parse the input statement */
    pdsnam = strtok (stmt, " \t");
    pimeth = strtok (NULL, " \t");
 
    /* Check that all mandatory fields are present */
    if (pdsnam == NULL || pimeth == NULL)
    {
        XMERR ("HHCDL021E DSNAME or initialization method missing\n");
        return -1;
    }
 
    /* Return the dataset name in EBCDIC and ASCII */
    string_to_upper (pdsnam);
    memset (dsname, 0, 45);
    strncpy (dsname, pdsnam, 44);
 
    /* Set default dataset attribute values */
    *units = 'T';
    *sppri = 1;
    *spsec = 0;
    *spdir = 0;
    *dsorg = 0x00;
    *recfm = 0x00;
    *lrecl = 0;
    *blksz = 0;
    *keyln = 0;
    *ifptr = NULL;
 
    /* Test for valid initialization method */
    if (strcasecmp(pimeth, "XMIT") == 0)
        *method = METHOD_XMIT;
    else if (strcasecmp(pimeth, "VS") == 0)
        *method = METHOD_VS;
    else if (strcasecmp(pimeth, "EMPTY") == 0)
        *method = METHOD_EMPTY;
    else if (strcasecmp(pimeth, "DIP") == 0)
        *method = METHOD_DIP;
    else if (strcasecmp(pimeth, "CVOL") == 0)
        *method = METHOD_CVOL;
    else if (strcasecmp(pimeth, "VTOC") == 0)
        *method = METHOD_VTOC;
    else if (strcasecmp(pimeth, "SEQ") == 0)
        *method = METHOD_SEQ;
    else
    {
        XMERRF ("HHCDL022E Invalid initialization method: %s\n", pimeth);
        return -1;
    }
 
    /* Locate the initialization file name */
    if (*method == METHOD_XMIT || *method == METHOD_VS || *method == METHOD_SEQ)
    {
        pifile = strtok (NULL, " \t");
        if (pifile == NULL)
        {
            XMERR ("HHCDL023E Initialization file name missing\n");
            return -1;
        }
        *ifptr = pifile;
    }
 
    /* Determine the space allocation units */
    punits = strtok (NULL, " \t");
    if (punits == NULL) return 0;
 
    string_to_upper (punits);
    if (strcmp(punits, "CYL") == 0)
        *units = 'C';
    else if (strcmp(punits, "TRK") == 0)
        *units = 'T';
    else
    {
        XMERRF ("HHCDL024E Invalid allocation units: %s\n",
                punits);
        return -1;
    }
 
    /* Determine the primary space allocation quantity */
    psppri = strtok (NULL, " \t");
    if (psppri == NULL) return 0;
 
    if (sscanf(psppri, "%u%c", sppri, &c) != 1)
    {
        XMERRF ("HHCDL025E Invalid primary space: %s\n",
                psppri);
        return -1;
    }
 
    /* Determine the secondary space allocation quantity */
    pspsec = strtok (NULL, " \t");
    if (pspsec == NULL) return 0;
 
    if (sscanf(pspsec, "%u%c", spsec, &c) != 1)
    {
        XMERRF ("HHCDL026E Invalid secondary space: %s\n",
                pspsec);
        return -1;
    }
 
    /* Determine the directory space allocation quantity */
    pspdir = strtok (NULL, " \t");
    if (pspdir == NULL) return 0;
 
    if (sscanf(pspdir, "%u%c", spdir, &c) != 1)
    {
        XMERRF ("HHCDL027E Invalid directory space: %s\n",
                pspsec);
        return -1;
    }
 
    /* Determine the dataset organization */
    pdsorg = strtok (NULL, " \t");
    if (pdsorg == NULL) return 0;
 
    string_to_upper (pdsorg);
    if (strcmp(pdsorg, "IS") == 0)
        *dsorg = DSORG_IS;
    else if (strcmp(pdsorg, "PS") == 0)
        *dsorg = DSORG_PS;
    else if (strcmp(pdsorg, "DA") == 0)
        *dsorg = DSORG_DA;
    else if (strcmp(pdsorg, "PO") == 0)
        *dsorg = DSORG_PO;
    else
    {
        XMERRF ("HHCDL028E Invalid dataset organization: %s\n",
                pdsorg);
        return -1;
    }
 
    /* Determine the record format */
    precfm = strtok (NULL, " \t");
    if (precfm == NULL) return 0;
 
    string_to_upper (precfm);
    if (strcmp(precfm, "F") == 0)
        *recfm = RECFM_FORMAT_F;
    else if (strcmp(precfm, "FB") == 0)
        *recfm = RECFM_FORMAT_F | RECFM_BLOCKED;
    else if (strcmp(precfm, "FBS") == 0)
        *recfm = RECFM_FORMAT_F | RECFM_BLOCKED | RECFM_SPANNED;
    else if (strcmp(precfm, "V") == 0)
        *recfm = RECFM_FORMAT_V;
    else if (strcmp(precfm, "VB") == 0)
        *recfm = RECFM_FORMAT_V | RECFM_BLOCKED;
    else if (strcmp(precfm, "VBS") == 0)
        *recfm = RECFM_FORMAT_V | RECFM_BLOCKED | RECFM_SPANNED;
    else if (strcmp(precfm, "U") == 0)
        *recfm = RECFM_FORMAT_U;
    else
    {
        XMERRF ("HHCDL029E Invalid record format: %s\n",
                precfm);
        return -1;
    }
 
    /* Determine the logical record length */
    plrecl = strtok (NULL, " \t");
    if (plrecl == NULL) return 0;
 
    if (sscanf(plrecl, "%u%c", lrecl, &c) != 1
        || *lrecl > MAX_DATALEN)
    {
        XMERRF ("HHCDL030E Invalid logical record length: %s\n",
                plrecl);
        return -1;
    }
 
    /* Determine the block size */
    pblksz = strtok (NULL, " \t");
    if (pblksz == NULL) return 0;
 
    if (sscanf(pblksz, "%u%c", blksz, &c) != 1
        || *blksz > MAX_DATALEN)
    {
        XMERRF ("HHCDL031E Invalid block size: %s\n",
                pblksz);
        return -1;
    }
 
    /* Determine the key length */
    pkeyln = strtok (NULL, " \t");
    if (pkeyln == NULL) return 0;
 
    if (sscanf(pkeyln, "%u%c", keyln, &c) != 1
        || *keyln > 255)
    {
        XMERRF ("HHCDL032E Invalid key length: %s\n",
                pkeyln);
        return -1;
    }
 
    return 0;
} /* end function parse_ctrl_stmt */
 
/*-------------------------------------------------------------------*/
/* Subroutine to process the control file                            */
/* Input:                                                            */
/*      cfp     Control file pointer                                 */
/*      cfname  Control file name                                    */
/*      ofname  DASD image file name                                 */
/*      cif     -> CKD image file descriptor                         */
/*      volser  Output volume serial number (ASCIIZ)                 */
/*      devtype Output device type                                   */
/*      reqcyls Requested device size in cylinders, or zero          */
/*      heads   Output device number of tracks per cylinder          */
/*      trklen  Output device virtual track length                   */
/*      outcyl  Output starting cylinder number                      */
/*      outhead Output starting head number                          */
/* Output:                                                           */
/*      Datasets are written to the DASD image file as indicated     */
/*      by the control statements.                                   */
/*-------------------------------------------------------------------*/
static int
process_control_file (FILE *cfp, char *cfname, char *ofname,
                CIFBLK *cif, char *volser, U16 devtype, int reqcyls,
                int heads, int trklen, int outcyl, int outhead)
{
int             rc;                     /* Return code               */
int             i;                      /* Array subscript           */
int             n;                      /* Integer work area         */
char            dsname[45];             /* Dataset name (ASCIIZ)     */
BYTE            method;                 /* Initialization method     */
char           *ifname;                 /* ->Initialization file name*/
BYTE            units;                  /* C=CYL, T=TRK              */
int             sppri;                  /* Primary space quantity    */
int             spsec;                  /* Secondary space quantity  */
int             spdir;                  /* Directory space quantity  */
BYTE            dsorg;                  /* Dataset organization      */
BYTE            recfm;                  /* Record format             */
int             lrecl;                  /* Logical record length     */
int             blksz;                  /* Block size                */
int             keyln;                  /* Key length                */
char            stmt[256];              /* Control file statement    */
int             stmtno;                 /* Statement number          */
int             mintrks;                /* Minimum size of dataset   */
int             maxtrks;                /* Maximum size of dataset   */
int             outusedv;               /* Bytes used in track buffer*/
int             tracks = 0;             /* Tracks used in dataset    */
int             numdscb = 0;            /* Number of DSCBs           */
DATABLK       **dscbtab;                /* -> Array of DSCB pointers */
int             dirblu;                 /* Bytes used in last dirblk */
int             lasttrk;                /* Relative track number of
                                           last used track of dataset*/
int             lastrec;                /* Record number of last used
                                           block of dataset          */
int             trkbal;                 /* Bytes unused on last track*/
int             bcyl;                   /* Dataset begin cylinder    */
int             bhead;                  /* Dataset begin head        */
int             ecyl;                   /* Dataset end cylinder      */
int             ehead;                  /* Dataset end head          */
int             vtoctrk = 0;            /* VTOC start relative track */
int             vtocext = 0;            /* VTOC extent size (tracks) */
BYTE            volvtoc[5];             /* VTOC begin CCHHR          */
int             offset = 0;             /* Offset into trkbuf        */
int             fsflag = 0;             /* 1=Free space message sent */
 
    /* Obtain storage for the array of DSCB pointers */
    dscbtab = (DATABLK**)malloc (sizeof(DATABLK*) * MAXDSCB);
    if (dscbtab == NULL)
    {
        XMERRF ("HHCDL010E Cannot obtain storage for DSCB pointer array: %s\n",
                strerror(errno));
        return -1;
    }
 
    /* Initialize the DSCB array with format 4 and format 5 DSCBs */
    rc = build_format4_dscb (dscbtab, numdscb, cif);
    if (rc < 0) return -1;
    numdscb++;
 
    rc = build_format5_dscb (dscbtab, numdscb);
    if (rc < 0) return -1;
    numdscb++;
 
    /* Read dataset statements from control file */
    while (1)
    {
        /* Read next statement from control file */
        rc = read_ctrl_stmt (cfp, cfname, stmt, sizeof(stmt), &stmtno);
        if (rc < 0) return -1;
 
        /* Exit if end of file */
        if (rc > 0)
            break;
 
        /* Parse dataset statement from control file */
        rc = parse_ctrl_stmt (stmt, dsname, &method, &ifname,
                &units, &sppri, &spsec, &spdir,
                &dsorg, &recfm, &lrecl, &blksz, &keyln);
 
        /* Exit if error in control file */
        if (rc < 0)
        {
            XMERRF ("HHCDL011E Invalid statement in %s line %d\n",
                    cfname, stmtno);
            return -1;
        }
 
        /* Write empty tracks if allocation is in cylinders */
        while (units == 'C' && outhead != 0)
        {
            /* Initialize track buffer with empty track */
            init_track (trklen, cif->trkbuf, outcyl, outhead, &outusedv);
 
            /* Write track to output file */
            rc = write_track (cif, ofname, heads, trklen,
                            &outusedv, &tracks, &outcyl, &outhead);
            if (rc < 0) break;
 
        } /* end while */
 
        XMINFF (1, "HHCDL012I Creating dataset %s at cyl %d head %d\n",
                dsname, outcyl, outhead);
        bcyl = outcyl;
        bhead = outhead;
 
        /* Calculate minimum size of dataset in tracks */
        mintrks = (units == 'C' ? sppri * heads : sppri);
 
        /* Create dataset according to method specified */
        switch (method) {
 
        case METHOD_XMIT:               /* IEBCOPY wrapped in XMIT */
        case METHOD_VS:                 /* "straight" IEBCOPY */
            /* Create dataset using IEBCOPY file as input */
            maxtrks = MAX_TRACKS;
            rc = process_iebcopy_file (ifname, ofname, cif,
                                    devtype, heads, trklen,
                                    outcyl, outhead, maxtrks,
                                    method,
                                    &dsorg, &recfm,
                                    &lrecl, &blksz, &keyln,
                                    &dirblu, &lastrec, &trkbal,
                                    &tracks, &outcyl, &outhead);
            if (rc < 0) return -1;
            break;
 
        case METHOD_DIP:
            /* Initialize LOGREC dataset */
            rc = dip_initialize (ofname, cif,
                                    devtype, heads, trklen,
                                    outcyl, outhead, mintrks,
                                    &lastrec, &trkbal,
                                    &tracks, &outcyl, &outhead);
            if (rc < 0) return -1;
            break;
 
        case METHOD_CVOL:
            /* Initialize SYSCTLG dataset */
            rc = cvol_initialize (ofname, cif, volser,
                                    devtype, heads, trklen,
                                    outcyl, outhead, mintrks,
                                    &lastrec, &trkbal,
                                    &tracks, &outcyl, &outhead);
            if (rc < 0) return -1;
            break;
 
        case METHOD_VTOC:
            /* Reserve space for VTOC */
            vtoctrk = (outcyl * heads) + outhead;
            vtocext = mintrks;
            tracks = 0;
            lastrec = 0;
            trkbal = 0;
            break;
 
        case METHOD_SEQ:
            /* Create sequential dataset */
            rc = seq_initialize (ifname, ofname, cif,
                                    devtype, heads, trklen,
                                    outcyl, outhead, mintrks,
                                    dsorg, recfm, lrecl, blksz,
                                    keyln, &lastrec, &trkbal,
                                    &tracks, &outcyl, &outhead);
            if (rc < 0) return -1;
            break;
 
        default:
        case METHOD_EMPTY:
            /* Create empty dataset */
            rc = empty_initialize (ofname, cif,
                                    devtype, heads, trklen,
                                    outcyl, outhead, mintrks,
                                    dsorg, spdir,
                                    &dirblu, &lastrec, &trkbal,
                                    &tracks, &outcyl, &outhead);
            if (rc < 0) return -1;
            break;
 
        } /* end switch(method) */
 
        /* Calculate the relative track number of last used track */
        lasttrk = tracks - 1;
 
        /* Round up space allocation if allocated in cylinders */
        if (units == 'C')
        {
            n = (tracks + heads - 1) / heads * heads;
            if (mintrks < n) mintrks = n;
        }
 
        /* Fill unused space in dataset with empty tracks */
        while (tracks < mintrks)
        {
            /* Initialize track buffer with empty track */
            init_track (trklen, cif->trkbuf, outcyl, outhead, &outusedv);
 
            /* Write track to output file */
            rc = write_track (cif, ofname, heads, trklen,
                            &outusedv, &tracks, &outcyl, &outhead);
            if (rc < 0) return -1;
 
        } /* end while(tracks) */
 
        /* Print number of tracks written to dataset */
        XMINFF (2, "HHCDL013I Dataset %s contains %d track%s\n",
                dsname, tracks, (tracks == 1 ? "" : "s"));
 
        /* Calculate end of extent cylinder and head */
        ecyl = (outhead > 0 ? outcyl : outcyl - 1);
        ehead = (outhead > 0 ? outhead - 1 : heads - 1);
 
        /* Create format 1 DSCB for the dataset */
        if (method != METHOD_VTOC)
        {
            rc = build_format1_dscb (dscbtab, numdscb, dsname, volser,
                                    dsorg, recfm, lrecl, blksz,
                                    keyln, dirblu, lasttrk, lastrec,
                                    trkbal, units, spsec,
                                    bcyl, bhead, ecyl, ehead);
            if (rc < 0) return -1;
            numdscb++;
        }
 
    } /* end while */
 
    /* Write the VTOC */
    rc = write_vtoc (dscbtab, numdscb, cif, ofname, devtype,
                    reqcyls, heads, trklen, vtoctrk, vtocext,
                    &outcyl, &outhead, volvtoc);
    if (rc < 0) return -1;
 
    /* Write empty tracks up to end of volume */
    while (outhead != 0 || outcyl < reqcyls)
    {
        /* Issue free space information message */
        if (fsflag == 0)
        {
            XMINFF (1, "HHCDL014I Free space starts at cyl %d head %d\n",
                    outcyl, outhead);
            fsflag = 1;
        }
 
#ifdef EXTERNALGUI
        /* Indicate output file progess */
        if (extgui)
            if ((outcyl % 10) == 0)
                fprintf (stderr, "OUTCYL=%d\n", outcyl);
#endif /*EXTERNALGUI*/
 
        /* Initialize track buffer with empty track */
        init_track (trklen, cif->trkbuf, outcyl, outhead, &outusedv);
 
        /* Write track to output file */
        rc = write_track (cif, ofname, heads, trklen,
                        &outusedv, &tracks, &outcyl, &outhead);
        if (rc < 0) return -1;
 
    } /* end while */
 
    if (outcyl > reqcyls && reqcyls != 0)
    {
        XMINFF (0, "HHCDL015W Volume exceeds %d cylinders\n",
                reqcyls);
    }
 
    XMINFF (0, "HHCDL016I Total of %d cylinders written to %s\n",
            outcyl, ofname);
 
    /* Update the VTOC pointer in the volume label */
    offset = CKDDASD_TRKHDR_SIZE + CKDDASD_RECHDR_SIZE + 8
           + CKDDASD_RECHDR_SIZE + IPL1_KEYLEN + IPL1_DATALEN
           + CKDDASD_RECHDR_SIZE + IPL2_KEYLEN + IPL2_DATALEN
           + CKDDASD_RECHDR_SIZE + VOL1_KEYLEN + 11;
 
    XMINFF (5, "HHCDL017I Updating VTOC pointer %2.2X%2.2X%2.2X%2.2X%2.2X\n",
            volvtoc[0], volvtoc[1], volvtoc[2], volvtoc[3],
            volvtoc[4]);
 
    rc = read_track (cif, 0, 0);
    if (rc < 0)
    {
        XMERR ("HHCDL018E Cannot read VOL1 record\n");
        return -1;
    }
 
    memcpy (cif->trkbuf + offset, volvtoc, sizeof(volvtoc));
    cif->trkmodif = 1;
 
    /* Release the DSCB buffers */
    for (i = 0; i < numdscb; i++)
        free (dscbtab[i]);
 
    /* Release the array of DSCB pointers */
    free (dscbtab);
 
    return 0;
 
} /* end function process_control_file */
 
/*-------------------------------------------------------------------*/
/* DASDLOAD main entry point                                         */
/*-------------------------------------------------------------------*/
int main (int argc, char *argv[])
{
int             rc = 0;                 /* Return code               */
char           *cfname;                 /* -> Control file name      */
char           *ofname;                 /* -> Output file name       */
FILE           *cfp;                    /* Control file pointer      */
CIFBLK         *cif;                    /* -> CKD image block        */
CKDDEV         *ckd;                    /* -> CKD table entry        */
char           *volser;                 /* -> Volume serial (ASCIIZ) */
char           *sdevtp;                 /* -> Device type (ASCIIZ)   */
char           *sdevsz;                 /* -> Device size (ASCIIZ)   */
char           *iplfnm;                 /* -> IPL text file or NULL  */
BYTE            c;                      /* Character work area       */
U16             devtype;                /* Output device type        */
int             devcyls;                /* Default device size (cyls)*/
int             reqcyls;                /* Requested device size (cyls)
                                           or 0 = use minimum size   */
int             outheads;               /* Output device trks/cyl    */
int             outmaxdl;               /* Output device maximum size
                                           record data length value  */
int             outtrklv;               /* Output device track length
                                           of virtual device         */
int             reltrk;                 /* Output track number       */
int             outcyl;                 /* Output cylinder number    */
int             outhead;                /* Output head number        */
char            stmt[256];              /* Control file statement    */
int             stmtno;                 /* Statement number          */
BYTE            comp = 0xff;            /* Compression algoritm      */
int             altcylflag = 0;         /* Alternate cylinders flag  */
int             lfs = 0;                /* 1 = Large file            */
char            pathname[MAX_PATH];     /* cfname in host path format*/
 
    INITIALIZE_UTILITY("dasdload");
 
    /* Display the program identification message */
    display_version (stderr,
                     "Hercules DASD loader program ", FALSE);
 
    /* Process optional arguments */
    for ( ; argc > 1 && argv[1][0] == '-'; argv++, argc--)
    {
        /* a single - can be used for the control file */
        if (argv[1][1] == '\0') break;
        if (strcmp("0", &argv[1][1]) == 0)
            comp = CCKD_COMPRESS_NONE;
#ifdef CCKD_COMPRESS_ZLIB
        else if (strcmp("z", &argv[1][1]) == 0)
            comp = CCKD_COMPRESS_ZLIB;
#endif
#ifdef CCKD_COMPRESS_BZIP2
        else if (strcmp("bz2", &argv[1][1]) == 0)
            comp = CCKD_COMPRESS_BZIP2;
#endif
        else if (strcmp("a", &argv[1][1]) == 0)
            altcylflag = 1;
        else if (strcmp("lfs", &argv[1][1]) == 0 && sizeof(off_t) > 4)
            lfs = 1;
        else argexit(0);
    }
 
    /* Check the number of arguments */
    if (argc < 3 || argc > 4)
        argexit(4);
 
    /* The first argument is the control file name */
    cfname = argv[1];
    if (argv[1] == NULL || strlen(argv[1]) == 0)
        argexit(1);
 
    /* The second argument is the DASD image file name */
    ofname = argv[2];
    if (argv[2] == NULL || strlen(argv[2]) == 0)
        argexit(2);
 
    /* The optional third argument is the message level */
    if (argc > 3 && argv[3] != NULL)
    {
        if (sscanf(argv[3], "%u%c", &infolvl, &c) != 1
            || infolvl > 5)
            argexit(3);
    }
 
    /* Open the control file */
    if (strcmp(cfname, "-") == 0)
    {
        cfp = stdin;
    }
    else
    {
        hostpath(pathname, cfname, sizeof(pathname));
        cfp = fopen (pathname, "r");
    }
    if (cfp == NULL)
    {
        XMERRF ("HHCDL001E Cannot open %s: %s\n",
                cfname, strerror(errno));
        return -1;
    }
 
    /* Read first statement from control file */
    rc = read_ctrl_stmt (cfp, cfname, stmt, sizeof(stmt), &stmtno);
    if (rc < 0) return -1;
 
    /* Error if end of file */
    if (rc > 0)
    {
        XMERRF ("HHCDL002E Volume serial statement missing from %s\n",
                cfname);
        return -1;
    }
 
    /* Parse the volume serial statement */
    volser = strtok (stmt, " \t");
    sdevtp = strtok (NULL, " \t");
    sdevsz = strtok (NULL, " \t");
    iplfnm = strtok (NULL, " \t");
 
    /* Validate the volume serial number */
    if (volser == NULL || strlen(volser) == 0 || strlen(volser) > 6)
    {
        XMERRF ("HHCDL003E Volume serial %s in %s line %d is not valid\n",
                volser, cfname, stmtno);
        return -1;
    }
    string_to_upper (volser);
 
    /* Validate the device type */
    ckd = dasd_lookup (DASD_CKDDEV, sdevtp, 0, 0);
    if (ckd == NULL)
    {
        XMERRF ("HHCDL004E Device type %s in %s line %d is not recognized\n",
                sdevtp, cfname, stmtno);
        return -1;
    }
    devtype = ckd->devt;
 
    /* Obtain number of heads per cylinder, maximum data length per
       track, and default number of cylinders per device */
    outheads = ckd->heads;
    devcyls = ckd->cyls;
    if (altcylflag) devcyls += ckd->altcyls;
    outmaxdl = ckd->r1;
 
    /* Use default device size if requested size is omitted or
       is zero or is "*" or compression is specified */
    reqcyls = 0;
    if (sdevsz != NULL && strcmp(sdevsz, "*") != 0 && comp == 0xff)
    {
        /* Validate the requested device size in cylinders */
        if (sscanf(sdevsz, "%u%c", &reqcyls, &c) != 1)
        {
            XMERRF ("HHCDL005E %s in %s line %d is not a valid cylinder "
                    "count\n",
                    sdevsz, cfname, stmtno);
            return -1;
        }
    }
    if (reqcyls == 0)
        reqcyls = devcyls;
 
    /* Calculate the track size of the virtual device */
    outtrklv = sizeof(CKDDASD_TRKHDR)
                + sizeof(CKDDASD_RECHDR) + R0_DATALEN
                + sizeof(CKDDASD_RECHDR) + outmaxdl
                + sizeof(eighthexFF);
    outtrklv = ROUND_UP(outtrklv,512);
 
    /* Display progress message */
    XMINFF (0, "HHCDL006I Creating %4.4X volume %s: "
            "%u trks/cyl, %u bytes/track\n",
            devtype, volser, outheads, outtrklv);
 
    /* Create the output file */
#ifdef EXTERNALGUI
    if (extgui) fprintf (stderr, "REQCYLS=%d\n", reqcyls);
#endif /*EXTERNALGUI*/
//DUT_DLL_IMPORT int create_ckd (char *fname, U16 devtype,
//        U32 heads, U32 maxdlen,
//        U32 volcyls, char *volser, BYTE comp, int lfs, int dasdcopy,
//        int nullfmt, int rawflag, int flagECmode, int flagMachinecheck);
//   two entries were  added to end of list. (1,0)  rahim
 
   rc = create_ckd (ofname, devtype, outheads, outmaxdl, reqcyls, volser, comp, lfs, 0, 0, 0, 1, 0);
    if (rc < 0)
    {
        XMERRF ("HHCDL007E Cannot create %s\n", ofname);
        return -1;
    }
 
    /* Open the output file */
    cif = open_ckd_image (ofname, NULL, O_RDWR | O_BINARY, 0);
    if (!cif)
    {
        XMERRF ("HHCDL008E Cannot open %s\n", ofname);
        return -1;
    }
 
    /* Display progress message */
    XMINFF (0, "HHCDL009I Loading %4.4X volume %s\n", devtype, volser);
 
    /* Write track zero to the DASD image file */
    rc = write_track_zero (cif, ofname, volser, devtype,
                        outheads, outtrklv, iplfnm,
                        &reltrk, &outcyl, &outhead);
    if (rc < 0)
        return -1;
 
    /* Process the control file to create the datasets */
    rc = process_control_file (cfp, cfname, ofname, cif, volser,
                        devtype, reqcyls, outheads, outtrklv,
                        outcyl, outhead);
 
    /* Close files and release buffers */
    if (strcmp(cfname, "-") != 0)
    {
        fclose (cfp);
    }
    close_ckd_image (cif);
 
    return rc;
 
} /* end function main */ 
regards;

Rahim Azizarab
 

  
kerravon86@yahoo.com.au [hercules-390]
2016-03-10 00:30:57 UTC
Permalink
Post by Rahim Azizarab ***@yahoo.com [hercules-390]
You might find zdasdload from Paul Edwards
a better alternative for it can write iplable
dasd also.
Hi Rahim. I just checked my system, and
my version is called dasdload.c, same as
standard Hercules.

I appear to have made 3 changes:

1. Allow a large IPL program.
2. Allow RECFM=U files
3. Allow control file to be provided on
stdin instead of requiring a separate
file.

I have also added lots of comments.

I don't believe any of these changes
will help the OP.

BFN. Paul.
stephen.orso@yahoo.com [hercules-390]
2016-03-16 16:16:32 UTC
Permalink
Hi Paul,

I agree; because the message appears when creating empty datasets or "formatting" volume free space, I don't believe your changes will affect the results.


Best Regards,
Steve Orso

---In hercules-***@yahoogroups.com, <***@...> wrote :


...

1. Allow a large IPL program.
2. Allow RECFM=U files
3. Allow control file to be provided on stdin instead of requiring a separate file.

...
stephen.orso@yahoo.com [hercules-390]
2016-03-16 16:20:28 UTC
Permalink
Hi Rahim:

Many thanks; unfortunately your posting was truncated just after the static int write_track () function by Yahoo (I think).


No worries. My interest is in understanding why the message is being issued, and there is no urgency to making it disappear.


Best Regards,
Steve Orso
stephen.orso@yahoo.com [hercules-390]
2016-03-16 16:38:11 UTC
Permalink
Message HHC00102E is issued because Hercules-390 exceeds the number of threads available on the host machine. For my Windows 10 32-bit system, this is about 1900 threads (validated by testlimit -t), and other 32-bit Windows systems will experience a similar limit. The limit is defined by running out of stack space in a 2GB address space. I don't know the limit on 64-bit Windows systems nor on *nix systems.


Based on test results, it seems that when Windows creates a thread, it does not immediately dispatch the newly-created thread. Quite some time can pass before the created thread is dispatched. Enough time in fact, for the original process to create (many many) more threads. I don't believe the thread dispatch order is a Windows defect; Windows documentation makes no promises about order of thread execution. I have limited experience with *nix. I do not recall getting HHC00102E when building OS/MVT on Windows 7, so the current behavior may be unique to Windows 10.


Everything below was done on a 32-bit windows system; *nix uses different function calls (with differences adroitly masked by C++ preprocessor coding) and may well create different results. All disks under consideration are compressed.


In two sets of controlled trials of 50 executions each of the makedasd script included in Jay Maynard's OS/MVT installation procedure, HHC00102E appeared 34 times and 37 times (68% & 74% respectively), much more frequently than my original 20% anecdotal estimate. Both sets of 50 were done using the Feb-22 commit of Hyperion.


There does not appear to be any impact on the emulated disk volume created by dasdload when these messages appear. I created an empty volume without the message and with the message, attached them to a DOS/VS machine, and used DITTO to dump each volume in its entirety. They appeared identical, including the pre-formatted directory portion of an empty PDS on the volume.


Dasdload uses the I/O subsystem from Hercules. While ckddasd.c does not start any threads, cckddasd.c does. I instrumented thread initiation code in cckddasd.c with the new messages HHC00106E, an enhanced version of HHC00102E that includes a string to identify which thread failed to start (read-ahead, writer, garbage collection). Trials with HHC00106E showed that every failed attempt to start a thread was a start of the garbage collector thread issued from routine cckd_writer.


Message HHC00107I was then added to cckddasd.c to indicate when an attempt was made to start the garbage collector thread; it was issued just prior to the create_thread() function call for the garbage collector and therefore appears in logs whether or not thread creation was successful. And message HHC00108E was added to cckd_gcol and issued when a garbage collector thread discovers it is an excess thread.


The garbage collector's first lines increment the count of garbage collectors, test that count against the maximum (defaults to 1), and if the count is exceeded, decrements the count and exits. Because the new thread is not immediately dispatched, the increment does not happen immediately after thread creation; the creating process appears to have enough time to test the count again and start a new thread. If this happens enough times, then the 32-bit Windows thread limit is reached. And logs show this is what happens.


Excess thread creation also happens without the thread limit being reached, and cannot be detected without the added messages. The log shows in several cases a series of HHC00107I messages, followed by HHC00100I successful start of the garbage collector, followed by HHC00108E messages as the extra threads abort, without reaching a started thread count that generates HHC00102E/HHC00106E. There is exactly one more attempt to start message (HHC00107I) than start aborted count exceeded messages (HHC00108E) for each execution of dasdload in the script.


Because this is a timing issue affecting cckddasd.c, not just dasdload.c, one would expect to see HHC00107I/HHC00108E messages in the Hercules-390 log. I ran Hercules-390 with the instrumented cckddasd.c, started a previously built DOS/VS system, started POWER/VS, and ran a supervisor assembly. One does see such messages in the Hercules log, although not so many as in my OS/MVT makedasd script trials; Windows 10 thread limits were not reached.


HHC00102E appeared only when attempting to start the garbage collector, but the code is the same for starting the read-ahead and writer threads. Why no HHC00102E message? I instrumented the cckd_ra and cckd_writer thread start and execution codes with HHC00107I and HHC00108E messages and found that excess threads were started. The excess threads just did not reach a point where Windows limits were exceeded. And the excess threads are created by both the makedasd script and by Hercules.


A possible enhancement would be to move the increment of the count of garbage collectors from the garbage collector to the process that creates the garbage collector thread; if thread creation fails, decrement the counter. This addresses the thread scheduling issue presented by Windows and should be compatible with alternative thread scheduling algorithms.


I tested this coding change, including such locking as needed to ensure read and update integrity, and it seems to prevent the creation of excess threads. One can find the code used to diagnose the issue and change current behavior at branch dasdload in my fork of Hyperion at:


https://github.com/srorso/hyperion/compare/master...srorso:dasdload https://github.com/srorso/hyperion/compare/master...srorso:dasdload


Outstanding issues:


1. There is no convenient way to communicate message level from dasdload to cckddasd; cckddasd could become very verbose. This issue applies only to "I" messages; "E" messages should always appear. dev->batch is a candidate that could be changed into a batch message level flag.


2. The changes to the thread start coding for cckd_gcol need to be propagated to cckd_ra and cckd_writer for the sake of completeness.


3. Additional testing is needed in Windows and non-Windows platforms.


4. Testing is needed using Hercules-390 3.12.


The test dasd generation script, logs from execution, and spreadsheet summaries of events are posted here:


https://sites.google.com/site/dos360install/hhc00102e.zip https://sites.google.com/site/dos360install/hhc00102e.zip


Best Regards,
Steve Orso


---In hercules-***@yahoogroups.com, <***@...> wrote :

Has anyone received message HHC00102E, shown below, when using DASDLOAD to create empty files?


HHC02567I File dasdsetup\work01.ctl[0008]: sys1.sysut4 empty cyl 50 20 0
fthread_create: MyCreateThread failed
HHC00102E Error in function create_thread(): Resource temporarily unavailable


.... <content deleted> ....


This was discovered as I built (multiple times) MVT 21.8 using Jay Maynard's instruction set at http://www.conmicro.com/hercos360/index.html http://www.conmicro.com/hercos360/index.html


If others have experienced this, please let me know. If not, I'll test 3.12 and/or post an issue to Github as appropriate.


Best Regards,
Steve Orso
Greg Smith gsmith@nc.rr.com [hercules-390]
2016-03-16 18:37:32 UTC
Permalink
On 16 Mar 2016 09:38:11 -0700
Post by ***@yahoo.com [hercules-390]
Message HHC00102E is issued because Hercules-390 exceeds the number
of threads available on the host machine.
Based on test results, it seems that when Windows creates a thread,
it does not immediately dispatch the newly-created thread. Quite
some time can pass before the created thread is dispatched. Enough
time in fact, for the original process to create (many many) more
threads.
Trials with HHC00106E showed that every failed attempt
to start a thread was a start of the garbage collector thread issued
from routine cckd_writer.
A possible enhancement would be to move the increment of the count
of garbage collectors from the garbage collector to the process that
creates the garbage collector thread; if thread creation fails,
decrement the counter. This addresses the thread scheduling issue
presented by Windows and should be compatible with alternative thread
scheduling algorithms.
Wow, that's pretty damn interesting. And an awesome job on debugging
the problem to boot!

I would guess that the error occurs less frequently (if at all) for the
writer and readahead threads is that a starvation can occur when the
cache fills up. Before a track can be written it is read. But before it
is read, a cache buffer needs to be obtained. If no buffer is available
then the i/o is blocked until a buffer is available.

Your solution (above) seems to be the correct one.

Good job!

Greg
stephen.orso@yahoo.com [hercules-390]
2016-03-19 12:39:39 UTC
Permalink
Hi Greg:

Thanks for providing the scenario that explains why garbage collection thread starts can run away and read ahead and writer thread starts are less likely to. I did see excess read ahead and write thread creation, but dispatch of the created threads, likely for the reasons you suggest, catches up before Windows resources are completely consumed.


3.12 does not check return codes from CreateThread() in cckddasd.c, so there's no way to know if this is an issue in 3.12.


I've rebased the code changes into a single commit at:


https://github.com/srorso/hyperion/compare/master...srorso:dasdload https://github.com/srorso/hyperion/compare/master...srorso:dasdload


Testing included a complete build of OS/MVT from Jay Maynard's site, a complete build of DOS/VS, execution of Bill C.'s excellent demo suite under DOS/VS, and use of the console commands cckd ra= and cckd wr= to see that additional ra and writer threads are created and destroyed as thread limits are changed. If anyone wishes to suggest additional tests (maybe Jay Moseley's MVS 3.8j?) I'll be happy to run them.


A Hyperion-390 issue and pull request are next.


Thanks for the kind words. I'm new at this (threaded code, c++, github, and development in an IDE). It's been a steep and exhilarating learning curve. Great fun for a superannuated DOS/VS & VM systems programmer who once looked upon an IBM 129 keypunch as an upgrade.


Best Regards,
Steve Orso


---In hercules-***@yahoogroups.com, <***@...> wrote :


... <earlier content deleted> ...

Wow, that's pretty damn interesting. And an awesome job on debugging
the problem to boot!

I would guess that the error occurs less frequently (if at all) for the
writer and readahead threads is that a starvation can occur when the
cache fills up. Before a track can be written it is read. But before it
is read, a cache buffer needs to be obtained. If no buffer is available
then the i/o is blocked until a buffer is available.

Your solution (above) seems to be the correct one.

Good job!

Greg
stephen.orso@yahoo.com [hercules-390]
2016-04-07 12:19:33 UTC
Permalink
I have posted a Pull request (https://github.com/hercules-390/hyperion/pull/95) with code changes to address this issue.

Steve Orso
Harold Grovesteen h.grovsteen@tx.rr.com [hercules-390]
2016-04-08 15:28:17 UTC
Permalink
Post by ***@yahoo.com [hercules-390]
I have posted a Pull request
(https://github.com/hercules-390/hyperion/pull/95) with code changes
to address this issue.
Steve Orso
Merged into Hyperion by Enrico Sorichetti.

Harold Grovesteen
stephen.orso@yahoo.com [hercules-390]
2016-04-08 17:45:01 UTC
Permalink
Hi Harold:

Thanks, and thanks to Enrico for the merge.

Best Regards,
Steve Orso

Loading...