//-*-c++-*--------------------------------------------------------------
//  flipTest.cpp
//   Test program for testing the Head(), HeadX(), Tail(), TailX(), and
//   logBlockNo() methods of the NSR LogExtentList object.
//
//   This program performs DESTRUCTIVE TESTING on an MO disk!!!!!
//
//      Unpublished Confidential Information of Hewlett-Packard
//      Company. Do not disclose.  Copyright (c) Hewlett-Packard
//      Company 1995. All rights reserved
// 
// The contents of this software are proprietary and confidential to
// the Hewlett- Packard Company, and are limited in distribution to
// those with a direct need to know.  Individuals having access to
// this software are responsible for maintaining the confidentiality
// of the content and for keeping the software secure when not in use.
// Transfer to any party is strictly forbidden other than as expressly
// permitted in writing by Hewlett-Packard Company.  Unauthorized
// transfer to or possession by any unauthorized party may be a
// criminal offense.
//
//                  RESTRICTED RIGHTS LEGEND
//
//      Use,  duplication,  or disclosure by the U. S. Government is
//      subject to restrictions as set forth in subparagraph (c) (1)
//      (ii) in the Rights in Technical Data and Computer Software
//      clause in DFARS 252.227-7013 or any other successor clause.
//
//                      HEWLETT-PACKARD COMPANY
//                      3000 Hanover St.
//                      Palo Alto, CA  94304 U.S.A.
//
//      Rights for non-DOD U.S. Government Departments and Agencies
//      are as set forth in FAR 52.227-19 (c) (1,2) or any other
//      successor clause.
//
//  CLASSES:
//   ClassName1
//   ClassName2
//  
//----------------------------------------------------------------------
//
// $Source: /projects/HMS/nsr/udfAncil/ssTest/flipTest.cpp,v $
// $Revision: 1.22 $    $Author: hmgr $
// $State: Exp $        $Locker:  $
// $Date: 96/04/30 23:31:47 $
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>               
#include <errno.h>
#include <assert.h>
#include "nsrLib.h"
#include "nsrRSSdv.h"

//-----------------------------------------------------------------------------
// Local defines and constants
//-----------------------------------------------------------------------------

#define BUFSIZE         49152   // 256K
#define NUM_EXTENTS     8       // Use 8 extents in our list; avg 6K/extent
#define ITERATIONS      100     

static CONST_CHAR_PTR DEFAULT_DEVFILE = "/dev/rdsk/c201d4s0";

//-----------------------------------------------------------------------------
// Function prototypes
//-----------------------------------------------------------------------------

static INT32    CheckSum(INT32,BYTE_PTR,UINT32);
static void     initData(void);
static INT32    testFlip(void);
static INT32    testHead(INT32 testno);
static INT32    testHeadX(INT32 testno);
static INT32    testTail(INT32 testno);
static INT32    testTailX(INT32 testno);
static INT32    testAll(INT32 testno);
static INT32    getBlocksize(const LogExtentList& list);

//-----------------------------------------------------------------------------
// Some global data
//-----------------------------------------------------------------------------

// (begin) CBAY added these magic lines ONLY for a Partition and LogicalVolume.
// THE FOLLOWING STATIC DATA IS INSTANTIATED ONLY SO WE CAN INTERACT WITH 
//   THE SURFACE.
// !!! DO WE WANT TO ASSUME A DEVICE OF "tbox"!!??  NO!!  FIX!
CONST_CHAR_PTR devFileName = "tbox";
static DriveDevice driveDevice(devFileName);
static PhysicalVolume physVolume(&driveDevice);
static UINT32 startSector = 0;  // Assumed partition start secotr.
static UINT32 numSectors = physVolume.numSectors() - startSector -2;
static PhysExtent physExtent(&physVolume, startSector, numSectors);
static Partition partition(physExtent);
static LogicalVolume lvol(&partition);
// (end) CBAY added these magic lines ONLY for a Partition and LogicalVolume.

static BYTE_PTR         data_buffer = new BYTE[BUFSIZE];
static BYTE_PTR         zero_buffer = new BYTE[BUFSIZE];
static DriveDevice      *ddevp = &driveDevice;
static PhysicalVolume   *pvolp = &physVolume;
static PhysExtent       *pextp = &physExtent;
static Partition        *partp = &partition;
static LogExtentList    theList(lvol);
static ByteArray        *big_buffer;



//-----------------------------------------------------------------------------
// main():
//
// We expect one argument, the device file to test on; if we don't
// get any arguments, or an argument we don't understand, we just use some
// default ("/dev/rdsk/c201d4s0").  Note that this program performs
// DESTRUCTIVE WRITES on the disk device given as argument!!!!!!
//-----------------------------------------------------------------------------


INT32 main (INT16 argc, CHAR_PTR argv[])
{
    String      *str;
    INT32       rc;

    // Prepare a String variable for use with the DriveDevice constructor.

    if(argc != 2) 
    {
        str = new String(DEFAULT_DEVFILE);
        (void) printf("WARNING:  Using devfile %s as argument!\n",
                      DEFAULT_DEVFILE);
    }
    else
    {
        str = new String(argv[1]);
    }
    if(str == NULL) 
    {
        (void) fprintf(stderr,"ddevp construction failed:  no free store!\n");
        exit(1);
    }

    ddevp = new DriveDevice(*str);
    if(ddevp == NULL)
    {
        (void) fprintf(stderr,"ddevp construction failed:  no free store!\n");
        exit(1);
    }
    if(ddevp->error() != error_None)
    {
        (void) fprintf(stderr,"ddevp construction failed, error = %ld\n",
                       (INT32) ddevp->error());
        delete ddevp;
        delete str;
        exit(1);
    }

    // At this point, we've successfully created our DriveDevice.

    initData();
    rc = testFlip();
    if(rc == 0) {
        printf("\nPassed all tests\n");
    }
    else {
        printf("\nFAILED %ld test(s)!\n",rc);
    }

    // Clean up

    delete str;
    delete ddevp;

    return rc;
}

//-----------------------------------------------------------------------------
// testFlip():  The actual test.  This function actually just calls all of the
//              sub-tests in sequence.  All sub-tests are assumed to return 0
//              on success and -1 on failure.
//-----------------------------------------------------------------------------

static INT32
testFlip(void)
{
    INT32 rval = 0;

#ifndef NO_TEST1
    rval += testHead(1);
#endif
#ifndef NO_TEST2
    rval += testHeadX(2);
#endif
#ifndef NO_TEST3
    rval += testTail(3);
#endif
#ifndef NO_TEST4
    rval += testTailX(4);
#endif
#ifndef NO_TEST5
    rval += testAll(5);
#endif

    return -rval;
}

//-----------------------------------------------------------------------------
// testHead():  Tests the Head() method.
//
// ALGORITHM:
//
// Take Heads() of random lengths from the global list theList, and perform
// reads on the new Lists.  Verify with CRC's.  
//
// Returns 0 on success, -1 on failure.
//-----------------------------------------------------------------------------

static INT32
testHead(INT32  testno)
{
    INT32               i;
    INT32               rval = 0;
    INT32               length;
    INT32               crc1,crc2;
    INT32               nbytes;
    ByteArray           barr(BUFSIZE*2);
    ByteArrayRef        bref(barr);
    Error               err;
    LogExtentList       newList(theList.logicalVolume());

    printf("\n***** Test %ld:  Head() Verification *****\n\n",testno);

    //
    // It is an axiom of Head() that the following is true:
    //
    // LogExtentList newlist = oldList.Head(length);
    // newlist.infoLen() == length
    //

    for(i = 0; i < ITERATIONS; i++) {
        length = lrand48() % ((3*BUFSIZE)/2);
        printf("*** Taking Head of %ld bytes ***\n",length);

        newList.HeadOf(theList,length,err);
        if(err != error_None) {
            printf("HeadOf returned error %s\n",err.message());
            rval = -1;
        }
        if(length > BUFSIZE) {          // Should get the whole list back
            length = BUFSIZE;
        }
        bref.length(newList.infoLen());
        bref.fromMem(zero_buffer);
        nbytes = newList.read(bref,err);
        crc1 = CheckSum(0,data_buffer,newList.infoLen());
        crc2 = CheckSum(0,(BYTE_PTR) bref,newList.infoLen());
        if(crc1 != crc2) {
            printf("\tFAILED! Read crc %8lx != Written crc %8lx\n",crc1,crc2);
#ifdef DEBUG
            newList.print(0,"");
#endif
            if(nbytes == -1) {
                printf("\tnewList read  returns %ld: %s\n",(UINT32) err,
                       err.message());
            }
            printf("\tRead %ld bytes, expected %ld\n\n",nbytes,bref.length());
            rval = -1;
        }
        else {
            printf("\tsucceeded.\n\n");
        }
    }

    return rval;
}

//-----------------------------------------------------------------------------
// testHeadX():  Tests the HeadX() method.
//
// Returns 0 on success, -1 on failure.
//-----------------------------------------------------------------------------

static INT32
testHeadX(INT32 testno)
{
    INT32               i;
    INT32               rval = 0;
    INT32               length;
    INT32               expected;
    INT32               blocksize;
    INT32               crc1,crc2;
    INT32               nbytes;
    ByteArray           barr(BUFSIZE*2);
    ByteArrayRef        bref(barr);
    LogExtentList       newList(theList.logicalVolume());
    Error               err;

    printf("\n***** Test %ld:  HeadX() Verification *****\n\n",testno);

    //
    // It is an axiom of HeadX() that the following is true:
    //
    // LogExtentList newlist = oldList.HeadX(length);
    // newlist.infoLen() == ((length / sector_size) * sector_size);
    //

    blocksize = getBlocksize(theList);
    if(blocksize == 0) {
         printf("TEST ABORTED:  List is not initialized\n");
         return -1;
    }
    for(i = 0; i < ITERATIONS; i++) {
        length = lrand48() % ((3*BUFSIZE)/2);
        if(length > BUFSIZE) {
            expected = BUFSIZE;
        }
        else {
            expected = length - blocksize;
        }
        if(expected < 0) {
            expected = 0;
        }
        printf("*** Taking HeadX of %ld bytes ***\n",length);

        newList.HeadXOf(theList,length,err);
        if(err != error_None) {
            printf("newList.HeadXOf returned error %s\n",err.message());
            rval = -1; 
        }
        if(length > BUFSIZE) {          // Should get the whole list back
            length = BUFSIZE;
        }
        bref.length(newList.infoLen());
        bref.fromMem(zero_buffer);
        nbytes = newList.read(bref,err);
        crc1 = CheckSum(0,data_buffer,expected);
        crc2 = CheckSum(0,(BYTE_PTR) bref,expected);
        if(crc1 != crc2) {
            printf("\tFAILED! Read crc %8lx != Written crc %8lx\n",crc1,crc2);
#ifdef DEBUG
            newList.print(0,"");
#endif
            if(nbytes == -1) {
                printf("\tnewList.read() returns %ld: %s\n",(INT32) err,
                       err.message());
            }
            printf("\tRead %ld bytes, expected %ld\n\n",nbytes,bref.length());
            rval = -1;
        }
        else {
            printf("\tsucceeded.\n\n");
        }
    }

    return rval;
}

//-----------------------------------------------------------------------------
// testTail():  Tests the Tail() method.  
//
// Returns 0 on success, -1 on failure.
//-----------------------------------------------------------------------------

static INT32
testTail(INT32  testno)
{
    INT32               i;
    INT32               rval = 0;
    INT32               length;
    INT32               expected;
    INT32               blocksize;
    INT32               crc1,crc2;
    INT32               nbytes;
    ByteArray           barr(BUFSIZE*2);
    ByteArrayRef        bref(barr);
    LogExtentList       newList(theList.logicalVolume());
    Error               err;

    printf("\n***** Test %ld:  Tail() Testing *****\n\n",testno);

    //
    // It is an axiom of Tail() that the following is true:
    //
    // LogExtentList newList = oldList.Tail(length);
    // newList.infoLen() = oldList.infoLen() - ((length / ss) * ss);
    //
    // ss = sector size
    //

    blocksize = getBlocksize(theList);
    printf("blocksize = %ld\n",blocksize);
    if(blocksize == 0) {
         printf("TEST ABORTED:  List is not initialized\n");
         return -1;
    }
    for(i = 0; i < ITERATIONS; i++) {
        length = lrand48() % ((3*BUFSIZE)/2);
        printf("*** Taking Tail of %ld bytes ***\n",length);

        newList.TailOf(theList,length,err);
        if(err != error_None) {
            printf("newList.TailOf returned error %s\n",err.message());
        }
        if(err != error_None) {
            printf("newList.TailOf returned error %s\n",err.message());
            rval = -1;
        }
        if(length > BUFSIZE) {          // Should get the whole list back
            length = BUFSIZE;
        }
        expected = theList.infoLen() - ((length / blocksize) * blocksize);
        printf("newList.infoLen() = %ld, expected = %ld\n",(UINT32)newList.infoLen(),
               expected);
        bref.length(expected);
        bref.fromMem(zero_buffer);
        nbytes = newList.read(bref,err);
        crc1 = CheckSum(0,data_buffer + (BUFSIZE - expected),expected);
        crc2 = CheckSum(0,(BYTE_PTR) bref,expected);
        if(crc1 != crc2) {
            printf("\tFAILED! Read crc %8lx != Written crc %8lx\n",crc1,crc2);
#ifdef DEBUG
            newList.print(0,"");
#endif
            if(nbytes == -1) {
                printf("\tnewList.read() returns %ld: %s\n",(INT32) err,
                       err.message());
            }
            printf("\tRead %ld bytes, expected %ld\n\n",nbytes,bref.length());
            rval = -1;
        }
        else {
            printf("\tsucceeded.\n\n");
        }
    }

    return rval;
}

//-----------------------------------------------------------------------------
// testTailX():  Tests the TailX() method.
//
// Returns 0 on success, -1 on failure.
//-----------------------------------------------------------------------------

static INT32
testTailX(INT32  testno)
{
    INT32               i;
    INT32               rval = 0;
    INT32               length;
    INT32               expected;
    INT32               blocksize;
    INT32               crc1,crc2;
    INT32               nbytes;
    ByteArray           barr(BUFSIZE*2);
    ByteArrayRef        bref(barr);
    LogExtentList       newList(theList.logicalVolume());
    Error               err;

    printf("\n***** Test %ld:  TailX() Testing *****\n\n",testno);

    blocksize = getBlocksize(theList);
    printf("blocksize = %ld\n",blocksize);
    if(blocksize == 0) {
         printf("TEST ABORTED:  List is not initialized\n");
         return -1;
    }
    for(i = 0; i < ITERATIONS; i++) {
        length = lrand48() % ((3*BUFSIZE)/2);
        printf("*** Taking TailX of %ld bytes ***\n",length);

        newList.TailXOf(theList,length,err);
        if(err != error_None) {
            printf("newList.TailXOf returned error %s\n",err.message());
            rval = -1;
        }
        if(length > BUFSIZE) {          // Should get the whole list back
            length = BUFSIZE;
        }
        expected = theList.infoLen() - 
                   (((length / blocksize) + 1) * blocksize);
        if(expected < 0) {
            expected = 0;
        }
        printf("newList.infoLen() = %ld, expected = %ld\n",(UINT32)newList.infoLen(),
               expected);
        bref.length(expected);
        bref.fromMem(zero_buffer);
        nbytes = newList.read(bref,err);
        crc1 = CheckSum(0,data_buffer + (BUFSIZE - expected),expected);
        crc2 = CheckSum(0,(BYTE_PTR) bref,expected);
        if(crc1 != crc2) {
            printf("\tFAILED! Read crc %8lx != Written crc %8lx\n",crc1,crc2);
#ifdef DEBUG
            newList.print(0,"");
#endif
            if(nbytes == -1) {
                printf("\tnewList.read() returns %ld: %s\n",(INT32) err,
                       err.message());
            }
            printf("\tRead %ld bytes, expected %ld\n\n",nbytes,bref.length());
            rval = -1;
        }
        else {
            printf("\tsucceeded.\n\n");
        }
    }

    return rval;
}        

//-----------------------------------------------------------------------------
// testAll():  Tests all of the new methods together.
//
// To extract a sub-region of a file given by an (offset,length) pair, we must
// perform roughly the following operation:
//
//              newList = List.Tail(offset).Head(length)
//
// This operation will yield an extent list describing the following sub-region
// of the file:
//
//              ( (offset / bs)*bs, length )
//
// Clearly, if offset is not a multiple of bs (blocksize), the subregion will
// be "shifted" left by (offset % bs) bytes from the desired subregion.
//
// As a result, to read the desired subregion, we must do the 
// following:
//
// LogExtList   newList = List.Tail(offset).Head(length + (offset % bs));
// ByteArrayRef baref(byte_array,0,newList.infoLen());
// 
// newList.read(baref);         
// baref.offset(offset % bs);
// baref.length(length);
//
// At this stage, baref will point to the desired subregion read from disk.
//
// Returns 0 on success, -1 on failure.
//-----------------------------------------------------------------------------

static INT32
testAll(INT32  testno)
{
    INT32               i;
    INT32               rval = 0;
    INT32               offset;
    INT32               length;
    INT32               expected;
    INT32               blocksize;
    INT32               crc1,crc2;
    INT32               nbytes;
    ByteArray           barr(BUFSIZE*2);
    ByteArrayRef        bref(barr);
    LogExtentList       newList(theList.logicalVolume());
    Error               err(error_None);

    printf("\n***** Test %ld:  Method Integration Testing *****\n\n",testno);

    // The LogExtentList theList represents a file of size BUFSIZE (which
    // happens to be extremely fragmented).  In the test loop, the 
    // offset/length pairs indicate which portion of the file we want to
    // extract.  

    blocksize = getBlocksize(theList);
    if(blocksize == 0) {
        printf("TEST ABORTED:  List is not initialized\n");
        return -1;
    }
    for(i = 0; i < ITERATIONS; i++) {
        offset = lrand48() % (BUFSIZE / 2);     // start in the 1st half
        length = lrand48() % (BUFSIZE - offset);

        printf("*** Reading at offset = %ld, length = %ld... ***\n",offset,
               length);
        
        err = error_None;
        newList.TailOf(theList,offset,err);
        if(err != error_None) {
            printf("newList.TailOf failed, err is %s\n",err.message());
            rval = -1;
            break;
        }
        err = error_None;
        newList.HeadOf(newList,length + (offset % blocksize),err);
        if(err != error_None) {
            printf("newList.HeadOf failed, err is %s\n",err.message());
            rval = -1;
            break;
        }

        bref.length(newList.infoLen());
        err = error_None;
        bref.offset(0);
        nbytes = newList.read(bref,err);
        if(nbytes < 0 ) {
            printf("newList.read() failed, error is %s\n",err.message());
            rval = -1;
            break;
        }
        bref.offset(offset % blocksize);
        bref.length(length);

        // Get expected and read crc's, and compare

        crc1 = CheckSum(0,data_buffer + offset,length);
        crc2 = CheckSum(0,(BYTE_PTR) bref,length);

        if(crc1 != crc2) { 
            printf("\tFAILED! Read crc %8lx != Written crc %8lx\n",crc1,crc2);
#ifdef DEBUG
            newList.print(0,"");
#endif
            printf("\tRead %ld bytes, expected %ld, err is %s\n\n",nbytes,
                   bref.length(),err.message());
            rval = -1;
            break;
        }
        else {
            printf("\tsucceeded.\n\n");
        }
    }
    return rval;
}        

//-----------------------------------------------------------------------------
// CheckSum():  The usual checksum/CRC code, stolen once again from 
//              distress.c...
//-----------------------------------------------------------------------------

static INT32
CheckSum(INT32 csum,BYTE_PTR data,UINT32 size)
{
    INT32 i;

    for(i = 0; i < size; i++) {
        csum = ((unsigned)csum<<1 | (unsigned)csum>>31) ^ (unsigned)data[i];
    }
    return csum;
}

//-----------------------------------------------------------------------------
// initData():  Fill up the data array with random data, zero out the zero
//              array, and create a random LogExtentList.  
//
// The LogExtentList will contain 256K worth of data, but will be randomly
// distributed among many LogExtents.
//-----------------------------------------------------------------------------

static void
initData(void)
{
    INT32       i;
    INT32       blocksize;
    INT32       nblocks;
    Error       err;

    // Fill up the data buffers

    srand48(time(NULL));                // Seed random number generator
    for(i = 0; i < BUFSIZE; i++) {
        data_buffer[i] = (BYTE) (lrand48() % 256);
        zero_buffer[i] = 0;
    }

    // We need to construct several ancillary objects so that we can construct
    // LogExtents.

    pvolp = new PhysicalVolume(ddevp);
    pextp = new PhysExtent(pvolp,257,pvolp->numSectors() - 260);
    partp = new Partition(*pextp);
    if((pvolp == NULL) || (pextp == NULL) || (partp == NULL)) {
        (void) fprintf(stderr,"Construction of pvolp, pextp, or partp failed:  \
no free store!");
        delete ddevp;
        exit(1);
    }
    printf("physextent error is %ld:  %s\n",(INT32) pextp->error(),
           (pextp->error()).message());
    printf("physical volume num sectors is:  %ld\n",pvolp->numSectors());
    printf("partition blocksize is:  %ld, numblocks:  %ld, startsector:  %ld\n",
         partp->blockSize(),partp->numBlocks(),partp->startSector());

    // We now generate NUM_EXTENTS extents which will encompass the first
    // BUFSIZE bytes of the disk.  The extents will be contiguous, but
    // will be in random order.

    nblocks = partp->numBlocks();
    blocksize = partp->blockSize();
    if(nblocks == 0) {                  // Probably no media in drive!
        (void) fprintf(stderr,"nblocks = 0, probably no media in drive!\n");
        delete pvolp;
        delete pextp;
        delete partp;
        delete ddevp;
        exit(1);
    }

    nblocks = BUFSIZE / blocksize;
    INT32       where_arr[NUM_EXTENTS] = { 0 };
    INT32       used_arr[nblocks];
    INT32       where;

    for(i = 0; i < nblocks; i++) {
        used_arr[i] = 0;
    }

    // This first for-loop simply determines (randomly) where each of the
    // extents should begin.  The first extent MUST begin at block 0, in
    // order to make sure we use the entire first BUFSIZE bytes of the disk.

    where_arr[0] = 0;
    used_arr[0] = 1;
    for(i = 1; i < NUM_EXTENTS; i++) {
        do {
            where = lrand48() % nblocks;
        } while(used_arr[where]);
        where_arr[i] = where;
        used_arr[where] = 1;
    }

    // Now that we know where each extent begins, we loop again and actually
    // create the LogExtents, and add them to the list.  We have to do a 
    // small messy loop to figure out how _long_ each extent needs to be.
            
    LogExtent   *lextp;
    INT32       len;
    for(i = 0; i < NUM_EXTENTS; i++) {
        INT32   j;
        // Determine length of the current extent
        len = 1;
        for(j = where_arr[i] + 1; j < nblocks; j++) {
            if(used_arr[j] != 0) {
                break;
            }
            len++;
        }
        lextp = new LogExtent(partp,where_arr[i],len*blocksize);
        if(lextp == NULL) {
            fprintf(stderr,"Couldn't construct lextp, no free store!\n");
            delete pvolp;
            delete partp;
            delete pextp;
            delete ddevp;
            exit(1);
        }
        theList.append(*lextp,err);
        delete lextp;
    }

    // Now write data to disk

    ByteArray           barr(data_buffer,BUFSIZE);
    ByteArrayRef        baref(barr);
    INT32               rval;

    rval = theList.write(baref,err);
    if(err != error_None) {
        printf("theList.write() failed, err is %ld:  %s\n",
               (INT32) err,err.message());
    }
    if(rval < baref.length()) {
        fprintf(stderr,"initData:  write of data returned %ld < %ld!\n",
                rval,baref.length());
        delete pvolp;
        delete partp;
        delete pextp;
        delete ddevp;
        exit(1);
    }
#ifdef DEBUG
    theList.print(0,"");
#endif

    // For a sanity check, read the data back and verify that we got the
    // same stuff back.

    ByteArray           newbarr(BUFSIZE);
    ByteArrayRef        newbaref(newbarr);
    INT32               crc1,crc2;

    newbaref.fromMem(zero_buffer);
    theList.read(newbaref,err);
    crc1 = CheckSum(0,data_buffer,BUFSIZE);
    crc2 = CheckSum(0,(BYTE_PTR) newbaref,BUFSIZE);
    if(crc1 != crc2) {
        printf("PROBLEMS:  Read crc %8lx != written crc %8lx\n",crc1,crc2);
    }
    else {
        printf("Sanity check succeeded\n");
    }
}

//-----------------------------------------------------------------------------
// getBlocksize():  returns the block size of the extents in the given
//                  LogExtentList.
//-----------------------------------------------------------------------------

static INT32
getBlocksize(const LogExtentList& list)
{
    Error       err;

    if(list.numExtents() == 0) {                // Empty list; we have to return 0!
        return 0;
    }
    const LogExtent *   lextp = list.at(0,err);

    return lextp->blockSize();
}
