/* Initialize a FILES-11 volume */

/* Timothe Litt March 2016
 * Copyright (C) 2016 Timothe litt
 *   litt at acm dot org
 *
 * Free for use with the ODS2 package.  All other rights reserved.
 */

/*
 *       This is distributed as part of ODS2, originally  written by
 *       Paul Nankervis, email address:  Paulnank@au1.ibm.com
 *
 *       ODS2 is distributed freely for all members of the
 *       VMS community to use. However all derived works
 *       must maintain comments in their source to acknowledge
 *       the contributions of the original author and
 *       subsequent contributors.   This is free software; no
 *       warranty is offered,  and while we believe it to be useful,
 *       you use it at your own risk.   This is free software; no
 *       warranty is offered,  and while we believe it to be useful,
 *       you use it at your own risk.
 */

#if !defined( DEBUG ) && defined( DEBUG_INITVOL )
#define DEBUG DEBUG_INITVOL
#else
#ifndef DEBUG
#define DEBUG 0
#endif
#endif

#include <ctype.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>

#include "compat.h"

#include "f11def.h"
#include "initvol.h"
#include "ods2.h"
#include "phyvirt.h"
#include "rms.h"
#include "stsdef.h"
#include "sysmsg.h"
#include "vmstime.h"

#define CLUSTERS(lbns) ( ((lbns) + clustersize - 1) / clustersize )
#define CLU2LBN(cluster) ( (cluster) * clustersize )

#define RE
#if DEBUG
#define DPRINTF(x) printf x
#else
#define DPRINTF(x)
#endif

static vmscond_t add2dir( void *mfdp, uint32_t *mfdlen, uint32_t mfdblocks,
                          struct HEAD *head );

static vmscond_t add2map( struct HEAD *fhd,
                          uint32_t start, uint32_t n, int sparse );

static vmscond_t writeHEAD( void *dev, struct HEAD *head, uint32_t hdrbase ) ;
static vmscond_t writeHDR( void *dev, struct HEAD *head, uint32_t lbn );
static vmscond_t writeHOM( void *dev,
                           struct HOME *hom,
                           uint32_t homlbn, uint32_t homvbn,
                           uint32_t ncopies );
static vmscond_t alloclbns( uint8_t *bitmap,
                            uint32_t clustersize,
                            uint32_t n_clusters,
                            uint32_t startlbn, uint32_t n,
                            uint32_t *found );
static int tstlbnbit( struct NEWVOL *params, uint8_t *bitmap,
                      uint32_t lbn );
static void modlbnbits( struct NEWVOL *params, uint8_t *bitmap,
                        int set, uint32_t start, uint32_t n );
static void modbits( uint8_t *bitmap, int set, uint32_t start, uint32_t n );
static f11word checksum( f11word *data, size_t length );
#if 0
static uint32_t delta_from_name( const char *diskname );
#endif
static uint32_t compute_delta( uint32_t sectorsize,
                               uint32_t sectors,
                               uint32_t tracks,
                               uint32_t cylinders );

static void *Malloc( void ***list, size_t size );
static void *Calloc( void ***list, size_t nmemb, size_t n );
#define MALLOC(name, size) do {                 \
        if( (name = Malloc( size )) == NULL )   \
            return SS$_INSFMEM;                 \
    } while( 0 )
#define CALLOC(name, nmemb, n) do {                     \
        if( (name = Calloc( nmemb, n )) == NULL )       \
            return SS$_INSFMEM;                         \
    } while( 0 )

static uint32_t eofmark = 0;

#define WRITE(dev, lbn, n, buf) do {                            \
        vmscond_t status;                                       \
                                                                \
        if( $FAILS(status = virt_write( dev, (lbn), ((n)*512),  \
                                        (char *)(buf) )) )      \
            return status;                                      \
        if( ( (lbn) + (n) ) > eofmark )                         \
            eofmark = (lbn) + (n)  -1;                          \
    } while( 0 )

#define WRITEP(dev, pbn, n, buf) do {                           \
        vmscond_t status;                                       \
                                                                \
        if( $FAILS(status = virt_writep( dev, (pbn),            \
                                         ((n)*dp->sectorsize),  \
                                         (char *)(buf) )) )     \
            return status;                                      \
    } while( 0 )

#define READ(dev, lbn, n, buf) do {                             \
        vmscond_t status;                                       \
                                                                \
        if( $FAILS(status = virt_read( dev, (lbn), ((n)*512),   \
                                       (char *)(buf) )) )       \
            return status;                                      \
    } while( 0 )

#define READP(dev, pbn, n, buf) do {                            \
        vmscond_t status;                                       \
                                                                \
        if( $FAILS(status = virt_readp( dev, (pbn),             \
                                        ((n)*dp->sectorsize),   \
                                        (char *)(buf) )) )      \
            return status;                                      \
    } while( 0 )

static vmscond_t doinit( void ***memlist, void *dev,
                        struct NEWVOL *params );

/************************************************************ BootBlock */

static uint16_t bootcode[] = { /* EHPLI M'T ARPPDEI SNDI E ADP-P11 */
    000240,  012706,  001000,  010700,  062700,  000036, 0112001,  001403,
    004767,  000006,  000773,  000005,  000000, 0110137, 0177566, 0105737,
   0177564, 0100375,  000207,  020040,  020040,  020040,  020040,  020040,
    020040,  020040,  020040,  071551,  067040,  072157,  060440,  071440,
    071571,  062564,  020155,  064544,  065563,  005015,  000012,  000000,
#define BOOT_LABEL_OFS 046
#define BOOT_LABEL_SIZE 15
};

/************************************************************ initvol() */

vmscond_t initvol( void *dev, struct NEWVOL *params ) {
    void **memlist, **mp;
    vmscond_t status;
    memlist = (void **)calloc( 1, sizeof( void * ) );

    if( params->options & INIT_LOG ) {
        printmsg( (params->options & INIT_VIRTUAL)? INIT_START_VIRT: INIT_START_PHY, 0,
                  params->label, params->devnam, params->devtype );
    }

    status = doinit( &memlist, dev, params );

    for( mp = memlist; *mp != NULL; mp++ )
        free( *mp );
    free( memlist );

    if( $SUCCESSFUL(status) && (params->options & INIT_LOG) ) {
        printmsg( INIT_VOLINIT, 0, params->label );
    }

    return status;
}

/************************************************************ Calloc() */

static void *Calloc( void ***list, size_t nmemb, size_t n ) {
    void *mp;

    n *= nmemb;
    mp = Malloc( list, n );
    if( mp == NULL )
        return NULL;
    memset( mp, 0, n );
    return mp;
}

/************************************************************ Malloc() */

static void *Malloc( void ***memlist, size_t size ) {
    void **mp;
    size_t n;
    for( n = 0, mp = *memlist; *mp != NULL; n++, mp++ )
        ;
    mp = realloc( *memlist, (n + 2) * sizeof( void * ) );
    if( mp == NULL )
        return NULL;
    *memlist = mp;
    mp[n] = malloc( size );
    if( mp[n] == NULL )
        return NULL;
    mp[n+1] = NULL;
    return mp[n];
}
#define Malloc( size ) Malloc( memlist, size )
#define Calloc( nmemb, size ) Calloc( memlist, nmemb, size )

/************************************************************ doinit() */

static vmscond_t doinit( void ***memlist, void *dev, struct NEWVOL *params ) {
    vmscond_t status;
    struct disktype *dp = NULL;
    struct HOME *hom = NULL;
    struct SCB *scb = NULL;
    struct HEAD
        *idxhead = NULL,
        *badhead = NULL,
        *corhead = NULL,
        *volhead = NULL,
        *conthead = NULL,
        *bakhead = NULL,
        *loghead = NULL,
        *bmphead = NULL,
        *mfdhead = NULL;
    struct IDENT  *ident = NULL;
    struct BAD144 *mbad = NULL;
    struct SWBAD  *sbad = NULL;
    uint32_t tmp, volstart, clustersize;
    uint32_t pbs, pb_per_lb, lb_per_pb, npb, nlb, n_clusters;
    uint32_t homlbn, homvbn, hm2lbn, hm2cluster, fhlbn;
    uint32_t mfdlbn, bmplbn, idxlbn, aidxlbn;
    const char *p = NULL;
    uint8_t *bitmap = NULL, *indexmap = NULL;
    uint32_t bitmap_size, indexmap_size;
    uint16_t *bootblock = NULL;
    time_t serial;
    char tbuf[12+1];
    char *mfdp = NULL;
    uint32_t mfdlen = 0, mfdblocks = 3, mfdalloc;

    if( params->username && (!params->username[0] ||
                             (tmp = (uint32_t)strlen( params->username )) > 12 ||
                             strspn( params->username, VOL_ALPHABET ) != tmp) ) {
        return printmsg( INIT_BADUSERNAME, 0 );
    }

    for( dp = disktype; dp->name != NULL; dp++ ) {
        if( !strcmp( params->devtype, dp->name ) )
            break;

    }
    if( dp->name == NULL ) {
        return printmsg( INIT_BADMEDIUM, 0, params->devtype );
    }

    pbs = dp->sectorsize;
    pb_per_lb = (pbs < 512)? 512/pbs: 1;
    lb_per_pb = (pbs >512)? pbs/512: 1;

    npb = (dp->sectors * dp->tracks * dp->cylinders ) - dp->reserved;
#ifdef UINT64_C
    nlb = (uint32_t) ((((uint64_t)npb) * lb_per_pb)
                            / pb_per_lb);
#else
    nlb = (uint32_t) ((((double)npb) * lb_per_pb)
                            / pb_per_lb);
#endif
    if( params->clustersize == 0 ) {
        if( nlb < 50000 )
            params->clustersize = 1;
        else {
            uint32_t tmp;
#ifdef UINT64_C
            tmp = (uint32_t)((((uint64_t)nlb) + (UINT64_C(255) * 4096) - 1) / (UINT64_C(255) * 4096));
#else
            tmp = (uint32_t)((((double)nlb) + (255 * 4096) - 1) / (255 * 4096));
#endif
            params->clustersize = (tmp > 3? tmp: 3);
        }
    }
    tmp = nlb / 50;
    if( tmp > 16382 )
        tmp = 16382;
    if( params->clustersize > tmp )
        params->clustersize = tmp;

    clustersize = params->clustersize;

    if( npb < 100 || nlb < 100 || clustersize < 1 ) {
        return printmsg( INIT_TOOSMALL, 0 );
    }

    n_clusters = CLUSTERS( nlb );

    if( params->maxfiles < 16 ) {
        params->maxfiles = nlb / ((clustersize +1) * 2);
        if( params->maxfiles < 16 )
            params->maxfiles = 16;
    }
    tmp = nlb / (clustersize + 1 );
    if( params->maxfiles > tmp )
        params->maxfiles = tmp;

    if( params->maxfiles > ((1 << 24) - 1) )
        params->maxfiles = ((1 << 24) - 1);

    if( params->headers < 16 )
        params->headers = 16;
    if( params->headers > 65500 )
        params->headers = 65500;

    if( params->headers > params->maxfiles )
        params->headers = params->maxfiles;

    if( params->directories > 16000 )
        params->directories = 16000;
    if( params->directories > params->maxfiles )
        params->directories = params->maxfiles;
    if( params->directories < 16 )
        params->directories = 16;

    mfdalloc = params->directories / 16;
    mfdalloc = CLU2LBN( CLUSTERS( mfdalloc ) );
    if( mfdalloc < mfdblocks )
        mfdalloc = mfdblocks;

    if( params->windows < 7 )
        params->windows = 7;
    else if( params->windows > 80 )
        params->windows = 80;

    if( params->extension > 65535 )
        params->extension = 65535;

    (void) time( &serial );

    /* Create the bootblock.  (This is PDP-11 code. )
     */

    CALLOC( bootblock, 256, sizeof( uint16_t ) );
    memcpy( bootblock, bootcode, sizeof( bootcode ) );

    for( p = params->label, tmp = 0; *p && tmp < BOOT_LABEL_SIZE; tmp++, p++ )
        ((char *)bootblock)[BOOT_LABEL_OFS+tmp] = toupper(*p);
#if ODS2_BIG_ENDIAN
    for( tmp = 0; tmp < 256; tmp++ )
        bootblock[tmp] = F11WORD(bootblock[tmp]);
#endif

#define ALLHEAD(name)                           \
    CALLOC( name, 1, sizeof(struct HEAD) )

    ALLHEAD( idxhead );
    ALLHEAD( badhead );
    ALLHEAD( corhead );
    ALLHEAD( volhead );
    ALLHEAD( conthead );
    ALLHEAD( bakhead );
    ALLHEAD( loghead );
    ALLHEAD( bmphead );
    ALLHEAD( mfdhead );

    CALLOC(hom, 1, sizeof( struct HOME ) );

    hom->hm2$w_struclev = F11WORD( STRUCLEV );
    hom->hm2$w_cluster = F11WORD( clustersize );
    hom->hm2$w_resfiles = F11WORD( FID$C_MAXRES );
    hom->hm2$l_maxfiles = F11LONG( params->maxfiles );

    if( params->options & INIT_OWNUIC ) {
        hom->hm2$w_volowner.uic$w_grp = F11WORD((uint16_t)
                                                (params->useruic >> 16));
        hom->hm2$w_volowner.uic$w_mem = F11WORD((uint16_t)params->useruic);
    } else {
        hom->hm2$w_volowner.uic$w_grp = F11WORD(1u);
        hom->hm2$w_volowner.uic$w_mem = F11WORD(1u);
    }

    /* Protection values: Set default, then replace any field specifed per mask */
    hom->hm2$w_protect = (f11word)~ /* N.B. Permissions are inverted here: noxx => xx allowed */
        SETPROT(prot$m_none,prot$m_none,prot$m_none,prot$m_none); /* S:RWED,O:RWED,G:RWED,W:RWED */
    hom->hm2$w_protect = ((hom->hm2$w_protect & ~(params->volprot >> 16)) |
                          (uint16_t)(params->volprot));
    hom->hm2$w_protect = F11WORD(hom->hm2$w_protect);

    hom->hm2$w_fileprot = (f11word)~ /* S:RWED,O:RWED,G:RE,W: */
        SETPROT(prot$m_noread|prot$m_nowrite|prot$m_noexe|prot$m_nodel,
                prot$m_noread|prot$m_nowrite|prot$m_noexe|prot$m_nodel,
                prot$m_noread|prot$m_noexe,
                prot$m_norestrict);
    hom->hm2$w_fileprot = ((hom->hm2$w_fileprot & ~(params->fileprot >> 16)) |
                          (uint16_t)(params->fileprot));
    hom->hm2$w_fileprot = F11WORD(hom->hm2$w_fileprot);

    hom->hm2$b_window = (uint8_t)params->windows;
    hom->hm2$b_lru_lim = 16;
    hom->hm2$w_extend = (f11word)F11WORD( params->extension );

    CALLOC( scb, 1, 512 );
    scb->scb$w_cluster = F11WORD(clustersize);
    scb->scb$w_struclev = F11WORD(STRUCLEV);
    scb->scb$l_volsize = F11LONG(nlb);
    scb->scb$l_blksize = F11LONG(pb_per_lb);
    scb->scb$l_sectors = F11LONG(dp->sectors);
    scb->scb$l_tracks = F11LONG(dp->tracks);
    scb->scb$l_cylinders = F11LONG(dp->cylinders);
    scb->scb$l_status = F11LONG( 0 );
    scb->scb$l_status2 = F11LONG( 0 );
    scb->scb$w_writecnt = 0;

    /* If necessary, scb$t_volockname will be initialized by mount under
     * an OS.
     */

    if( $FAILS(status = sys_gettim( scb->scb$q_mounttime )) ) {
        return status;
    }
    scb->scb$w_checksum = F11WORD(checksum((f11word *)scb,
                                           offsetof( struct SCB,
                                                     scb$w_checksum ) / 2  ) );

    bitmap_size = (n_clusters + 4095) / 4096;

    indexmap_size = (params->maxfiles + 4095) / 4096;
    hom->hm2$l_maxfiles = F11LONG( params->maxfiles );

    memcpy( hom->hm2$q_credate, scb->scb$q_mounttime,
            sizeof(hom->hm2$q_credate) );
    memcpy( hom->hm2$q_revdate, scb->scb$q_mounttime,
            sizeof(hom->hm2$q_revdate) );

    DPRINTF(("Total LBNs: %u cluster size: %u total clusters: %u, "
             "bitmap size: %u blocks maxfiles %u index map size %u blocks\n",
             nlb, clustersize, n_clusters, bitmap_size, params->maxfiles,
             indexmap_size));

    hom->hm2$l_serialnum = (f11long) F11LONG( serial );
    memset( hom->hm2$t_strucname, ' ', sizeof( hom->hm2$t_strucname ) );
    (void) snprintf( tbuf, sizeof( tbuf), "%-12.12s", params->label );
    for( tmp = 0; tmp < sizeof( tbuf ); tmp++ )
        tbuf[tmp] = toupper( tbuf[tmp] );
    memcpy( hom->hm2$t_volname, tbuf, sizeof( hom->hm2$t_volname ) );
    p = get_username();
    (void) snprintf( tbuf, sizeof( tbuf), "%-12.12s",
                     (params->username? params->username: (p == NULL)? "": p) );
    free( (void *)p );
    for( tmp = 0; tmp < sizeof( tbuf ); tmp++ )
        tbuf[tmp] = toupper( tbuf[tmp] );
    memcpy( hom->hm2$t_ownername, tbuf, sizeof( hom->hm2$t_ownername ) );
    (void) snprintf( tbuf, sizeof( tbuf ), "%-12.12s", "DECFILE11B" );
    memcpy( hom->hm2$t_format, tbuf, sizeof( hom->hm2$t_format ) );

    CALLOC( indexmap, 1, (size_t)indexmap_size * 512 );

    hom->hm2$w_ibmapsize = F11WORD( (f11word)indexmap_size );

    /* Index header will be template for all other files.
     * Index-specific settings must come after copy.
     */

    idxhead->fh2$b_idoffset = FH2$C_LENGTH / 2;
    idxhead->fh2$b_acoffset = offsetof( struct HEAD, fh2$w_checksum ) / 2;
    idxhead->fh2$b_rsoffset = offsetof( struct HEAD, fh2$w_checksum ) / 2;

    idxhead->fh2$w_seg_num = F11WORD( 0 );
    idxhead->fh2$w_struclev = F11WORD( STRUCLEV );

    idxhead->fh2$w_fid.fid$b_rvn = 0;
    idxhead->fh2$w_fid.fid$b_nmx = 0;
    idxhead->fh2$w_ext_fid.fid$w_num = F11WORD( 0 );
    idxhead->fh2$w_ext_fid.fid$w_seq = F11WORD( 0 );
    idxhead->fh2$w_ext_fid.fid$b_rvn = 0;
    idxhead->fh2$w_ext_fid.fid$b_nmx = 0;

    idxhead->fh2$w_recattr.fat$l_hiblk = F11SWAP( 0 );
    idxhead->fh2$w_recattr.fat$l_efblk = F11SWAP( 1 );
    idxhead->fh2$w_recattr.fat$w_ffbyte = F11WORD( 0 );
    idxhead->fh2$w_recattr.fat$b_bktsize = 0;
    idxhead->fh2$w_recattr.fat$b_vfcsize = 0;
    idxhead->fh2$w_recattr.fat$w_defext = F11WORD( 0 );
    idxhead->fh2$w_recattr.fat$w_gbc = F11WORD( 0 );
    idxhead->fh2$w_recattr.fat$w_versions = F11WORD( 0 );
    idxhead->fh2$l_filechar = F11LONG( 0 );
    idxhead->fh2$b_map_inuse = 0;
    idxhead->fh2$b_acc_mode = 0;
    idxhead->fh2$l_fileowner.uic$w_mem = F11WORD( 1 );
    idxhead->fh2$l_fileowner.uic$w_grp = F11WORD( 1 );
    idxhead->fh2$w_fileprot = (f11word)~
        SETPROT(prot$m_noread|prot$m_nowrite|prot$m_noexe|prot$m_nodel,
                prot$m_noread|prot$m_nowrite|prot$m_noexe|prot$m_nodel,
                prot$m_noread|prot$m_noexe,
                prot$m_norestrict);
    idxhead->fh2$w_fileprot = F11WORD( idxhead->fh2$w_fileprot );

    idxhead->fh2$w_backlink.fid$w_num = F11WORD( FID$C_MFD );
    idxhead->fh2$w_backlink.fid$w_seq = F11WORD( FID$C_MFD );
    idxhead->fh2$w_backlink.fid$b_rvn = 0;
    idxhead->fh2$w_backlink.fid$b_nmx = 0;
    idxhead->fh2$l_highwater = F11LONG( 1 );

#define IDENTp(head) ((struct IDENT *) (((f11word *)head) +     \
                                        head->fh2$b_idoffset))
    ident = IDENTp(idxhead);

    ident->fi2$w_revision = F11WORD( 1 );
    memcpy( ident->fi2$q_credate, scb->scb$q_mounttime,
            sizeof(ident->fi2$q_credate) );
    memcpy( ident->fi2$q_revdate, scb->scb$q_mounttime,
            sizeof(ident->fi2$q_credate) );
    /* These files can never be renamed or deleted.  But maximum map area
     * is helpful and all names are short. So omit filenamext.
     */
    idxhead->fh2$b_mpoffset = idxhead->fh2$b_idoffset +
                                      (offsetof( struct IDENT,
                                                 fi2$t_filenamext ) / 2);

    memcpy( badhead,  idxhead,  sizeof( *badhead ) );
    memcpy( corhead,  idxhead,  sizeof( *corhead ) );
    memcpy( volhead,  idxhead,  sizeof( *volhead ) );
    memcpy( conthead, idxhead, sizeof( *conthead ) );
    memcpy( bakhead,  idxhead,  sizeof( *bakhead ) );
    memcpy( loghead,  idxhead,  sizeof( *loghead ) );
    memcpy( bmphead,  idxhead,  sizeof( *bmphead ) );
    memcpy( mfdhead,  idxhead,  sizeof( *mfdhead ) );

    mfdhead->fh2$l_filechar = F11LONG( FH2$M_DIRECTORY );
    mfdhead->fh2$w_fileprot &= ~F11WORD( /* W:E allowed for MFD */
                                        SETPROT(0,
                                                0,
                                                0,
                                                prot$m_noexe) );

    mfdblocks = CLU2LBN( CLUSTERS( mfdblocks ) );
    CALLOC( mfdp, 1, (size_t)mfdblocks * 512 );

#define SETFILE(head, num, name, rtype, rattr, rsize )  do {    \
        vmscond_t status;                                       \
        struct IDENT *id;                                       \
        char fname[sizeof( id->fi2$t_filename )+1];             \
                                                                \
        id = IDENTp(head);                                      \
        (void) snprintf( fname, sizeof( fname ),                \
                         "%-20.20s", #name );                   \
        memcpy( id->fi2$t_filename, fname,                      \
                sizeof( id->fi2$t_filename ) );                 \
        head->fh2$w_fid.fid$w_num = F11WORD( FID$C_ ## num );   \
        head->fh2$w_fid.fid$w_seq = F11WORD( FID$C_ ## num );   \
        head->fh2$w_recattr.fat$b_rtype = ((FAB$C_SEQ << 4) |   \
                                           (FAB$C_ ## rtype));  \
        head->fh2$w_recattr.fat$b_rattrib = rattr;              \
        head->fh2$w_recattr.fat$w_rsize = F11WORD( rsize );     \
        head->fh2$w_recattr.fat$w_maxrec = F11WORD( rsize );    \
        if( $FAILS(status = add2dir( mfdp, &mfdlen,             \
                                     mfdblocks, head )) )       \
            return status;                                      \
        modbits( indexmap, 1, FID$C_ ## num, 1 );               \
    } while( 0 )

    /* Must be in alphabetical order by filename for MFD */
    SETFILE( mfdhead,  MFD,    000000.DIR;1,  VAR, FAB$M_BLK, 512 );
    SETFILE( bakhead,  BACKUP, BACKUP.SYS;1,  FIX, 0,          64 );
    SETFILE( badhead,  BADBLK, BADBLK.SYS;1,  FIX, 0,         512 );
    SETFILE( loghead,  BADLOG, BADLOG.SYS;1,  FIX, 0,          16 );
    SETFILE( bmphead,  BITMAP, BITMAP.SYS;1,  FIX, 0,         512 );
    SETFILE( conthead, CONTIN, CONTIN.SYS;1,  FIX, 0,         512 );
    SETFILE( corhead,  CORIMG, CORIMG.SYS;1,  FIX, 0,         512 );
    SETFILE( idxhead,  INDEXF, INDEXF.SYS;1,  FIX, 0,         512 );
    SETFILE( volhead,  VOLSET, VOLSET.SYS;1,  FIX, 0,          64 );
#if 0 /* Reserved for free space, but not defined */
    SETFILE( NULL,     FREFIL, FREFIL.SYS;1,  UDF, 0,           0 );
#endif

    MALLOC( bitmap, 512 * (size_t)bitmap_size );
    memset( bitmap, 0xFF, 512 * (size_t)bitmap_size );

    DPRINTF(("Non-existent bitmap bits %u / %u LBNs %u - %u\n",
             (bitmap_size *4096) - n_clusters, bitmap_size *4096,
             n_clusters * clustersize, bitmap_size * 4096 * clustersize));
    modbits( bitmap, 0, n_clusters, (bitmap_size * 4096) - n_clusters );

#define ADDMAPENTRY(head, start, n ) do {                               \
        vmscond_t status;                                               \
                                                                        \
        if( $FAILS(status = add2map( (head),                            \
                                     CLU2LBN( (start) / clustersize ),  \
                                     CLU2LBN( CLUSTERS( (n) ) ), 0)) )  \
            return status;                                              \
    } while( 0 )

    if( dp->flags & DISK_BAD144 ) {
        uint32_t start, count;

        tmp = 10 * dp->sectorsize;

        MALLOC( mbad, tmp );
        if( params->options &  INIT_VIRTUAL ) {
            memset( mbad->bbd$l_badblock, ~0,
                    dp->sectorsize - offsetof( struct BAD144, bbd$l_badblock) );
            mbad->bbd$l_serial = F11LONG( ((uint32_t) serial) & ~0x80000000 );
            mbad->bbd$w_reserved = 0;
            mbad->bbd$w_flags = 0;
            for( count = 1; count < 10; count++ )
                memcpy( ((char *)mbad)+(count * (size_t)dp->sectorsize),
                        mbad, dp->sectorsize );
        } else {
            ;/* Should read & mark bad */
        }

        /* Might have to deal with finding clusters more accurately
         * for disks with interleave/skew.
         */
        start = npb - 10;
        start = (uint32_t) ( ( ((off_t)tmp) * pb_per_lb ) / lb_per_pb );
        count = (uint32_t) ( ( ((off_t)10) * lb_per_pb ) / pb_per_lb );

        modlbnbits( params, bitmap, 0, start, count );
        ADDMAPENTRY( badhead, start, count );
        if( !(params->options &  INIT_VIRTUAL) ) {
            READP( dev, start, 10, mbad );
            /* *** TODO : Decode and add to bitmap/badblk.sys */
        }
    }
    if( dp->flags & DISK_BADSW ) {
        CALLOC( sbad, 1, sizeof( struct SWBAD ) );
        if( params->options &  INIT_VIRTUAL ) {
            sbad->bbm$b_countsize = 1;
            sbad->bbm$b_lbnsize = 3;
            sbad->bbm$b_inuse = 0;
            sbad->bbm$b_avail = (uint8_t)( (offsetof( struct SWBAD,
                                                       bbm$w_checksum ) -
                                             offsetof( struct SWBAD,
                                                       bbm$r_map )) /
                                           sizeof( uint8_t ) );
        }
        modlbnbits( params, bitmap, 0, nlb - 1, 1 );
        ADDMAPENTRY( badhead, nlb-1, 1 );
        if( !(params->options &  INIT_VIRTUAL) ) {
            READ( dev, nlb-1, 1, sbad );
            /* *** TODO : Decode and add to bitmap/badblk.sys */
        }
    }

    /* Make index file map entries (in increasing VBN order)
     */

    /* BOOT and HOM blocks.
     *
     * HOM1 and boot merge if clustersize >1 & primary HOM is good.
     */

    modlbnbits( params, bitmap, 0, params->bootlbn, 1 );

    homlbn = 1;
    tmp = delta_from_index( dp - disktype );

    do {
        if( tstlbnbit( params, bitmap, homlbn ) ||
            ( (homlbn != params->bootlbn ) &&
              (homlbn / clustersize == params->bootlbn / clustersize)) )
            break;

        /* Block is allocated, so it must already be in bitmap/badblk */

        homlbn += tmp;
    } while( homlbn < nlb );

    if( homlbn >= nlb ) {
        return printmsg( INIT_NOHOMLOC, 0 );
    }

    DPRINTF(("Boot block = %u, primary HOM block = %u\n", params->bootlbn, homlbn));

    if((homlbn == params->bootlbn + 1) && (params->bootlbn % clustersize == 0) &&
       params->bootlbn / clustersize == homlbn / clustersize ) {
        if( clustersize > 1 ) {
            modlbnbits( params, bitmap, 0, params->bootlbn, clustersize * 2 );
            ADDMAPENTRY( idxhead, params->bootlbn, clustersize * 2 );
        } else {
            modlbnbits( params, bitmap, 0, params->bootlbn, clustersize  );
            ADDMAPENTRY( idxhead, params->bootlbn, clustersize );
        }
    } else { /* This odd case is the exception to the rule that VBNS and
              * map pointers are always cluster aligned.
              */
        uint32_t offset;

        offset = homlbn % clustersize;
        if( offset != 0 ) {
            (void) add2map( badhead, homlbn, offset, 0 );
        }

        (void) add2map( idxhead, params->bootlbn, 1, 0 );
        if( clustersize > 1 ) {
            modlbnbits( params, bitmap, 0, homlbn, clustersize * 2 );
            (void) add2map( idxhead, homlbn, clustersize * 2 - offset, 0 );
        } else {
            modlbnbits( params, bitmap, 0, homlbn, clustersize );
            (void) add2map( idxhead, homlbn, clustersize - offset, 0 );
        }
    }

    /* Alternate HOM block
     */

    hm2lbn = homlbn;
    do {
        hm2lbn += tmp;

        if( tstlbnbit( params, bitmap, hm2lbn ) )
            break;
        /* Bad or allocated block */
    } while( hm2lbn < nlb );

    if( hm2lbn >= nlb ) {
        return printmsg( INIT_NOHOM2LOC, 0 );
    }

    modlbnbits( params, bitmap, 0, hm2lbn, 1 );

    hm2cluster = hm2lbn / clustersize;

    DPRINTF(("Alternate HOM block is %u\n", hm2lbn));

    ADDMAPENTRY( idxhead, CLU2LBN( hm2cluster ), 1 );

    hom->hm2$l_alhomelbn = F11LONG( hm2lbn );

    if( params->options & INIT_LOG ) {
        printmsg( INIT_SIZING1, 0,
                  nlb, clustersize, params->maxfiles );
        printmsg( INIT_SIZING2, MSG_CONTINUE, INIT_SIZING1,
                  params->headers, params->directories );
        printmsg( INIT_PLACED1, MSG_CONTINUE, INIT_SIZING1,
                  params->bootlbn, homlbn, hm2lbn );
    }

    /* Variable placement. */

    if( params->indexlbn == INIT_INDEX_MIDDLE )
        volstart = nlb / 2;
    else
        volstart = params->indexlbn;

#define ALLOCLBNS( start, number, result ) do {                         \
        vmscond_t status;                                               \
        if( $FAILS(status = alloclbns( bitmap, clustersize, n_clusters, \
                                       (start), (number), (result))) )  \
            return status;                                              \
    } while( 0 )

    /* This order is recommended for best placement */

    /* MFD */
    ALLOCLBNS( volstart, mfdalloc, &mfdlbn );

    /* SCB and storage bitmap */
    ALLOCLBNS( volstart, 1 + bitmap_size, &bmplbn );

    /* Index bitmap and initial file headers. */
    ALLOCLBNS( volstart, indexmap_size + params->headers, &idxlbn );

    /* Backup index header -- put it near the 2nd HOM block */
    ALLOCLBNS( hm2lbn, 1, &aidxlbn );

    hom->hm2$l_altidxlbn = F11LONG( aidxlbn );
    hom->hm2$w_altidxvbn = F11WORD( (clustersize * 3) +1 );

    DPRINTF(("Backup index header %u\n", aidxlbn));
    ADDMAPENTRY( idxhead, aidxlbn, 1 );

    fhlbn = idxlbn + indexmap_size;
    hom->hm2$l_ibmaplbn = F11LONG( idxlbn );

    DPRINTF(("Index bitmap LBN is %u, first extent %u\n",
             idxlbn, indexmap_size+params->headers));

    /* EOF is 1 block past the 9 used headers */

    tmp = (clustersize * 4) + indexmap_size + 9 +1;
    idxhead->fh2$w_recattr.fat$l_efblk = F11SWAP( tmp );
    idxhead->fh2$w_recattr.fat$w_ffbyte = F11WORD( 0 );
    idxhead->fh2$l_highwater = F11LONG( tmp );

    ADDMAPENTRY( idxhead, idxlbn, indexmap_size + params->headers );

    hom->hm2$w_ibmapvbn = F11WORD( (clustersize * 4) + 1 );

    /* Accurate EOF is not kept for directories, reclen = 32767 is
     * end marker in data.
     */
#if 0
    mfdhead->fh2$w_recattr.fat$l_efblk = F11SWAP( 1 + (mfdlen / 512) );
    mfdhead->fh2$w_recattr.fat$w_ffbyte = F11WORD( mfdlen % 512 );
#else
    mfdhead->fh2$w_recattr.fat$l_efblk = F11SWAP( 1 + ((mfdlen + 511) / 512) );
    mfdhead->fh2$w_recattr.fat$w_ffbyte = F11WORD( 0 );
#endif
    mfdhead->fh2$l_highwater = F11LONG( 1 + ((mfdlen + 511) / 512) );

    ADDMAPENTRY( mfdhead, mfdlbn, mfdalloc );

    /* Storage bitmap */

    bmphead->fh2$w_recattr.fat$l_efblk = F11SWAP( 1 + bitmap_size + 1);
    bmphead->fh2$w_recattr.fat$w_ffbyte = F11WORD( 0 );
    bmphead->fh2$l_highwater = F11LONG( 1 + bitmap_size + 1 );

    ADDMAPENTRY( bmphead, bmplbn, 1 + bitmap_size );

    if( params->options & INIT_LOG ) {
        printmsg( INIT_PLACED2, MSG_CONTINUE, INIT_SIZING1,
                  mfdlbn, idxlbn,
                  idxlbn + indexmap_size + params->headers -1 );
    }

    /* Let's try for a spiral write... */

    WRITE( dev, params->bootlbn, 1, bootblock );

    /* Write HOM blocks */

    homvbn = 2;
    hom->hm2$w_homevbn = F11WORD( homvbn );

    DPRINTF(("Primary HOM block lbn %u vbn %u\n", homlbn, homvbn ));

    homvbn = (clustersize * 2) + 1;
    hom->hm2$w_alhomevbn = F11WORD( homvbn );

    if( clustersize > 1 )                            /* Copies needed */
        tmp = ( clustersize * 2 ) - ( homlbn % clustersize );
    else
        tmp = 1;

    if( $FAILS(status = writeHOM( dev, hom, homlbn, 2, tmp )) )
        return status;

    DPRINTF(("Secondary HOM block lbn %u vbn %u\n", hm2lbn, homvbn));

    if( $FAILS(status = writeHOM( dev, hom, hm2cluster * clustersize,
                                  homvbn, clustersize )) )
        return status;

    /* MFD */

    DPRINTF(("MFD at %u, %u blocks, len %u\n", mfdlbn, mfdalloc, mfdlen));

    WRITE( dev, mfdlbn, mfdblocks, mfdp );

    DPRINTF(("Writing BITMAP.SYS at %u, %u blocks\n", bmplbn,
             1 + bitmap_size));

    WRITE( dev, bmplbn, 1, scb );
    WRITE( dev, bmplbn + 1, bitmap_size, bitmap );

    DPRINTF(("Index file bitmap\n"));
    WRITE( dev, idxlbn, indexmap_size, indexmap );

#define WRITEHEAD(head, name) do {                                      \
        vmscond_t status;                                               \
        head->fh2$w_checksum = checksum( (f11word *)(head),             \
                                         offsetof( struct HEAD,         \
                                                   fh2$w_checksum ) / 2 ); \
        DPRINTF(("Writing " #name " header\n"));                        \
        if( $FAILS(status = writeHEAD( dev, (head), fhlbn )) )          \
            return status;                                              \
    } while( 0 )

    WRITEHEAD( idxhead,  INDEXF.SYS;1 );
    WRITEHEAD( bmphead,  BITMAP.SYS;1 );
    WRITEHEAD( badhead,  BADBLK.SYS;1 );
    WRITEHEAD( mfdhead,  000000.DIR;1 );
    WRITEHEAD( corhead,  CORIMG.SYS;1 );
    WRITEHEAD( volhead,  VOLSET.SYS;1 );
    WRITEHEAD( conthead, CONTIN.SYS;1 );
    WRITEHEAD( bakhead,  BACKUP.SYS;1 );
    WRITEHEAD( loghead,  BADLOG.SYS;1 );

    if( $FAILS(status = writeHDR( dev, idxhead, aidxlbn )) )
        return status;

    if( params->options &  INIT_VIRTUAL ) {
        if( dp->flags & DISK_BAD144 ) {
            if( params->options & INIT_LOG ) {
                printmsg( INIT_MFGBAD, 0 );
            }
            WRITEP( dev, npb - 10, 10, mbad );
        }
        if( dp->flags & DISK_BADSW ) {
            if( params->options & INIT_LOG ) {
                printmsg( INIT_SWBAD, 0 );
            }
            sbad->bbm$w_checksum = checksum( (f11word *)sbad,
                                             offsetof( struct SWBAD,
                                                       bbm$w_checksum ) / 2 );
            WRITE( dev, nlb - 1, 1, sbad );
        }

        /*  Access last block so full size of file is allocated. (Hopefully
         * sparse.) To account for possible interleaving, do the last track.
         * Helps humans in various ways.
         */

        if( eofmark < nlb ) {
            for( tmp = nlb - ((dp->sectors * lb_per_pb) / pb_per_lb);
                 tmp < nlb; tmp++ ) {
                READ( dev, tmp, 1, idxhead );
                WRITE( dev, tmp, 1, idxhead );
            }
        }
    }

    return status;
}
#undef Malloc

/************************************************************ add2dir() */

static vmscond_t add2dir( void *dirp, uint32_t *dirlen, uint32_t dirblocks,
                         struct HEAD *head ) {
    struct dir$r_ent *de;
    struct dir$r_rec *dr;
    struct IDENT *ident;
    uint32_t block, offset;
    f11word reclen, paddednamelen, version, verlimit;
    size_t namelen, typlen;
    char fname[  sizeof( ident->fi2$t_filename ) +
                 sizeof( ident->fi2$t_filenamext ) + 1], *p;
    int extlen;

    ident = IDENTp(head);

    memcpy( fname, ident->fi2$t_filename, sizeof( ident->fi2$t_filename ) );

    extlen = (((size_t)head->fh2$b_mpoffset - head->fh2$b_idoffset) * sizeof( f11word )) -
              offsetof( struct IDENT, fi2$t_filenamext );
    if( extlen > 0 ) {
        if( extlen > (int) sizeof( ident->fi2$t_filenamext ) )
            extlen = sizeof( ident->fi2$t_filenamext );
        memcpy( fname + sizeof( ident->fi2$t_filename ),
                ident->fi2$t_filenamext, extlen );
    } else extlen = 0;

    namelen = sizeof( ident->fi2$t_filename ) + extlen;
    fname[namelen] = '\0';
    while( namelen > 1 && fname[namelen - 1] == ' ' )
        fname[--namelen] = '\0';

    if( (p = strrchr( fname, ';' )) != NULL ) {
        typlen = (size_t) ( fname + namelen - p );
        namelen = (size_t)(p -fname );
        *p++ = '\0';
        version = (f11word)strtoul( p, &p, 10 );
        if( p != fname + namelen + typlen || typlen < 1 )
            return SS$_BADFILENAME;
    } else
        version = 1;

    block = *dirlen / 512;
    offset = *dirlen % 512;

    verlimit = (head->fh2$l_filechar & F11LONG(FH2$M_DIRECTORY))? 1: 0;

    paddednamelen = (f11word) ( ((namelen + 1) / 2) * 2 );

    reclen = ( offsetof( struct dir$r_rec, dir$t_name ) + paddednamelen +
               sizeof( struct dir$r_ent ) );
    if( reclen + offset > 512 ) {
        block++;
        if( block >= dirblocks )
            return SS$_DEVICEFULL;
        if( offset <= 512 - sizeof(f11word) ) {
            ((f11word *)dirp)[offset / sizeof(f11word)] = F11WORD( -1 );
            offset += sizeof( f11word );
        }
        *dirlen += 512 - offset;
        offset = 0;
    }
    dr = (struct dir$r_rec *)( ((char *)dirp) + *dirlen );
    dr->dir$w_size = (f11word) F11WORD( reclen - sizeof( dr->dir$w_size ) );
    dr->dir$w_verlimit = F11WORD( verlimit );
    dr->dir$b_flags = 0; /* dir$c_fid */
    dr->dir$b_namecount = (uint8_t) namelen;
    memcpy( dr->dir$t_name, fname, paddednamelen );
    de = (struct dir$r_ent *) ( ((char *)dirp) + ( (size_t)block * 512 ) + offset +
                                reclen - sizeof( struct dir$r_ent ));
    de->dir$w_version = version;
    de->dir$w_fid = head->fh2$w_fid;
    offset += reclen;
    if( offset <= 512 - sizeof(f11word) )
        ((f11word *)dirp)[offset/sizeof(f11word)] = F11WORD( -1 );
    *dirlen += reclen;

    return SS$_NORMAL;
}

/************************************************************ add2map() */

static vmscond_t add2map( struct HEAD *fhd,
                         uint32_t start, uint32_t n, int sparse ) {

    return addmappointers( fhd, &start, &n, sparse );
}

/************************************************************ addmappointers() */

vmscond_t addmappointers( struct HEAD *fhd,
                         uint32_t *start, uint32_t *n, int sparse ) {
    f11word *mp, *ep;
    uint8_t mapsize, inuse, len;
    f11long hiblk;
    uint32_t pbn = 0, count = 0, msparse = 0, nextpbn = 0;
    int contig = -1;

    if( sparse )
        *start = ~0U;

    hiblk = F11SWAP( fhd->fh2$w_recattr.fat$l_hiblk );

    mapsize = fhd->fh2$b_acoffset - fhd->fh2$b_mpoffset;
    inuse = fhd->fh2$b_map_inuse;

    mp = ((f11word *)fhd) + fhd->fh2$b_mpoffset;
    ep = mp + inuse;

    len = 0;

    while( mp < ep ) {
        f11word e0;

        e0 = *mp++;
        e0 = F11WORD(e0);
        switch( e0 &  FM2$M_FORMAT3 ) {
        case  FM2$M_FORMAT1:
            len = 2;
            count = e0 & 0xff;
            pbn = F11WORD(*mp) | (((e0 & ~FM2$M_FORMAT3) >> 8) << 16);
            msparse = (pbn == FM2$C_FMT1_MAXLBN);
            mp++;
            break;
        case  FM2$M_FORMAT2:
            len = 3;
            count = e0 & ~FM2$M_FORMAT3;
            pbn = *mp++;
            pbn = F11WORD( pbn );
            pbn |= F11WORD (*mp ) << 16;
            msparse = (pbn == FM2$C_FMT2_MAXLBN);
            mp++;
            break;
        case FM2$M_FORMAT3:
            len = 4;
            count = ((e0 & ~FM2$M_FORMAT3) << 16);
            count |= F11WORD( *mp );
            mp++;
            pbn = *mp++;
            pbn = F11WORD( pbn );
            pbn |= F11WORD (*mp ) << 16;
            msparse = (pbn == FM2$C_FMT3_MAXLBN);
            mp++;
            break;
        default:
            len = 1;
            continue;
        }
        ++count;
        if( contig < 0 && !msparse ) {
            contig = 1;
            nextpbn = pbn + count;
        } else if( msparse || pbn != nextpbn )
            contig = 0;
        else
            nextpbn = pbn + count;
    }

    if( sparse || (contig == 1 && *n && nextpbn != *start) )
        contig = 0;
    if( contig )
        fhd->fh2$l_filechar |= F11LONG(FH2$M_CONTIG);
    else
        fhd->fh2$l_filechar &= ~F11LONG(FH2$M_CONTIG);

    if( len >= 2 && ((sparse && msparse) ||
                     (!sparse && *start == pbn + count) ) ) {
        mp -= len;
        inuse -= len;
        hiblk -= count;
        *n += count;
        *start = pbn;
    }

    while( *n > 0 ) {
        f11word freew =  mapsize - inuse;

        count = *n;
        if( *start < FM2$C_FMT1_MAXLBN || sparse ) {
            if( freew < 2 )
                break;
            if( count <= FM2$C_FMT1_MAXCNT ) {
                hiblk += count;
                *n -= count--;
                *mp++ = ( F11WORD( FM2$M_FORMAT1 |
                                   (((*start & FM2$C_FMT1_MAXLBN) >> 16) << 8) |
                                   count ) );
                *mp++ = F11WORD( (f11word)*start );
                inuse += 2;
                if( !sparse )
                    *start += count + 1;
                break;
            }
        }
        if( count <= FM2$C_FMT2_MAXCNT ) {
            if( freew < 3 )
                break;
            hiblk += count;
            *n -= count--;
            *mp++ = F11WORD( FM2$M_FORMAT2 | count );
            *mp++ = F11WORD( (f11word)*start );
            *mp++ = F11WORD( (f11word)(*start >> 16) );
            inuse += 3;
            if( !sparse )
                *start += count + 1;
            break;
        }
        if( freew < 4 )
            break;
        if( count > FM2$C_FMT3_MAXCNT )
            count = FM2$C_FMT3_MAXCNT;
        hiblk += count;
        *n -= count--;
        *mp++ = F11WORD( FM2$M_FORMAT3 | (f11word)(count >> 16) );
        *mp++ = F11WORD( (f11word)count );
        *mp++ = F11WORD( (f11word)*start );
        *mp++ = F11WORD( (f11word)(*start >> 16) );
        inuse += 4;
        if( !sparse )
            *start += count + 1;
    }

    fhd->fh2$w_recattr.fat$l_hiblk = F11SWAP( hiblk );
    fhd->fh2$b_map_inuse = inuse;

    if( *n != 0 )
        return SS$_DEVICEFULL;

    return SS$_NORMAL;
}

/************************************************************ writeHEAD() */

static vmscond_t writeHEAD( void *dev, struct HEAD *head, uint32_t hdrbase ) {
    hdrbase += head->fh2$w_fid.fid$w_num - 1;

    return writeHDR( dev, head, hdrbase );
}

/************************************************************ writeHDR() */

static vmscond_t writeHDR( void *dev, struct HEAD *head, uint32_t lbn ) {
    head->fh2$w_checksum = checksum( (f11word *)head, offsetof( struct HEAD, fh2$w_checksum ) / 2 );
    head->fh2$w_checksum = F11WORD( head->fh2$w_checksum );

    WRITE( dev, lbn, 1, head );
    return SS$_NORMAL;
}

/************************************************************ writeHOM() */

static vmscond_t writeHOM( void *dev, struct HOME *hom,
                          uint32_t homlbn, uint32_t homvbn,
                          uint32_t ncopies ) {
    uint32_t copy = 0;

    do {
        hom->hm2$l_homelbn = F11LONG( homlbn );
        hom->hm2$w_homevbn = F11WORD( homvbn );
        hom->hm2$w_checksum1 = checksum( (f11word *)hom,
                                         offsetof( struct HOME, hm2$w_checksum1 ) /2 );
        hom->hm2$w_checksum1 =  F11WORD( hom->hm2$w_checksum1 );
        hom->hm2$w_checksum2 = checksum( (f11word *)hom,
                                         offsetof( struct HOME, hm2$w_checksum2 ) /2 );
        hom->hm2$w_checksum2 =  F11WORD( hom->hm2$w_checksum2 );

        WRITE( dev, homlbn, 1, hom );

        homlbn++;
        homvbn++;
    } while( ++copy < ncopies );

    return SS$_NORMAL;
}

/************************************************************ alloclbns() */

static vmscond_t alloclbns( uint8_t *bitmap,
                            uint32_t clustersize,
                            uint32_t n_clusters,
                            uint32_t startlbn, uint32_t n,
                            uint32_t *found ) {
    uint32_t nfound;
    int pass;

    *found = ~0U;

    startlbn /= clustersize;
    n = CLUSTERS( n );

    for( pass = 0, nfound = 0; pass < 2 && nfound < n; pass++ ) {
        uint32_t lbn;

        if( pass > 0 )
            startlbn = 0;

        for( lbn = startlbn, nfound = 0;
             nfound < n && lbn + n < n_clusters; lbn++ ) {
            if( bitmap[ lbn >> 3 ] & (1 << (lbn & 0x7)) ) {
                if( nfound++ == 0 )
                    startlbn = lbn;
            } else
                nfound = 0;
        }
    }

    if( nfound < n )
        return SS$_DEVICEFULL;

    modbits( bitmap, 0, startlbn, n );

    *found = startlbn * clustersize;
    return SS$_NORMAL;
}

/************************************************************ modlbnbits() */

static void modlbnbits( struct NEWVOL *params, uint8_t *bitmap, int set, uint32_t start, uint32_t n ) {
    ldiv_t d;
    uint32_t first, clusters, clustersize;

    if( n == 0 )
        return;

    clustersize = params->clustersize;

    d = ldiv( start, clustersize );

    clusters = 1;

    if( (first = clustersize - d.rem) < n )
        clusters += CLUSTERS( n - first );

    modbits( bitmap, set, d.quot, clusters );
    return;
}

/************************************************************ tstlbnbit() */

static int tstlbnbit( struct NEWVOL *params, uint8_t *bitmap, uint32_t lbn ) {

    lbn = lbn / params->clustersize;

    return (bitmap[lbn >> 3] & (1 << (lbn & 0x7))) != 0;
}

/************************************************************ modbits() */

static void modbits( uint8_t *bitmap, int set, uint32_t start, uint32_t n ) {
    uint32_t q, bit, byte;

    if( set ) {
        while( n > 0 ) {
            bit = start & 0x7;
            byte = start >>3;

            if( bit == 0 && n > 8 ) {
                q = n >> 3;
                n &= 0x7;

                if( q == 1 )
                    bitmap[byte] = 0xff;
                else
                    memset( bitmap+byte, 0xff, q );
                start += q << 3;
            } else {
                bitmap[byte] |= 1 << bit;
                start++;
                n--;
            }
        }
        return;
    }
    while( n > 0 ) {
        bit = start & 0x7;
        byte = start >>3;

        if( bit == 0 && n > 8 ) {
            q = n >> 3;
            n &= 0x7;

            if( q == 1 )
                bitmap[byte] = 0;
            else
                memset( bitmap+byte, 0x00, q );
            start += q << 3;
        } else {
            bitmap[byte] &= ~(1 << bit);
            start++;
            n--;
        }
    }
    return;
}

/************************************************************ checksum() */

static f11word checksum( f11word *data, size_t length ) {
    f11word sum;
    size_t count;

    for( sum = 0, count = 0; count < length; count++ )
        sum += *data++;
    return sum;
}

/*************************************************************** compute_delta() */
/*
 * The search delta is computed from the
 * volume  geometry,  expressed  in  sectors,  tracks   (surfaces),   and
 * cylinders, according to the following rules, to handle the cases where
 * one or two dimensions of the volume have a size of 1.
 *
 *           Geometry:     Delta
 *
 *           s x 1 x 1:    1          Rule 1
 *           1 x t x 1:    1          Rule 2
 *           1 x 1 x c:    1          Rule 3
 *
 *           s x t x 1:    s+1        Rule 4
 *           s x 1 x c:    s+1        Rule 5
 *           1 x t x c:    t+1        Rule 6
 *
 *           s x t x c:    (t+1)*s+1  Rule 7
 */


static uint32_t compute_delta( uint32_t sectorsize,
                               uint32_t sectors,
                               uint32_t tracks,
                               uint32_t cylinders ) {
    if( sectorsize < 512 )
        sectors = (sectorsize * sectors) / 512;

    if( sectors > 1 && tracks > 1 && cylinders > 1 )        /* Rule 7 */
        return  (tracks + 1) * sectors +1;

    if( (sectors > 1 && tracks > 1 && cylinders == 1 ) ||   /* Rule 4 */
        (sectors > 1 && tracks == 1 && cylinders > 1 ) )    /* Rule 5 */
        return sectors + 1;

    if( sectors == 1 && tracks > 1 && cylinders > 1 )       /* Rule 6 */
        return tracks + 1;

    return 1;                                               /* Rules 1-3 */
}

/************************************************************ delta_from_name() */

#if 0
static uint32_t delta_from_name( const char *diskname ) {
    struct disktype *dp;

    for( dp = disktype; dp->name != NULL; dp++ ) {
        if( !strcmp( dp->name, diskname ) )
            return compute_delta( dp->sectorsize, dp->sectors, dp->tracks, dp->cylinders );
    }

    return ~0u;
}
#endif

/************************************************************ delta_from_index() */

uint32_t delta_from_index( size_t index ) {
    struct disktype *dp;
    uint32_t delta;

    if( index > max_disktype )
        abort();

    dp = disktype + index;
    delta = compute_delta( dp->sectorsize, dp->sectors, dp->tracks, dp->cylinders );

#if DEBUG  || HOME_SKIP > 0
    printf( "HOM search index for %s is %u\n", dp->name, delta );
#endif

    return delta;
}
