//-*-c++-*--------------------------------------------------------------
//  newsrTst.cpp
//   Desctructive test for SpaceResources
//
//      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.
//
//----------------------------------------------------------------------
//
// $Source: /projects/HMS/nsr/udfAncil/ssTest/newsrTst.cpp,v $
// $Revision: 1.20 $    $Author: hmgr $
// $State: Exp $        $Locker:  $
// $Date: 96/06/13 15:22:59 $
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>               
#include <errno.h>
#include <assert.h>
#include "nsrLib.h"
#include "nsrRSSdv.h"

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

#define BYTEARRAY_MAX           49152L  // 48K
#define BUFSIZE                 49152L  // 48K
#define ITERATIONS              100     
#define RAGGED                  0
#define ALIGNED                 1
#define EXTALIGNED_FILES        15
#define EXTRAGGED_FILES         15
#define LISTALIGNED_FILES       20
#define LISTRAGGED_FILES        20
#define LISTALIGNED_ITERATIONS  25
#define MAX_FILES               32

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

//-----------------------------------------------------------------------------
// Some local type definitions
//-----------------------------------------------------------------------------

struct File             // A struct because I don't care about privacy
{
    // Data members

    INT32               crc;            // CRC for file data
    UINT32              size;           // In bytes
    INT32               fileno;         // File number; a "name"
    LogExtent           *lextp;         // For extent-based allocation
    LogExtentList       *listp;         // For list-based allocation

    // Methods

    File(UINT32 len);   
    ~File();    
    INT32       isAllocated(void) { return (lextp || listp); }
    INT32       allocExtent(SpaceResource& sr);
    INT32       allocList(SpaceResource& sr);
    void        freeExtent(SpaceResource& sr);
    void        freeList(SpaceResource& sr);
    INT32       writeData(void);
    INT32       checkData(void);
};

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

static INT32    CheckSum(INT32,BYTE_PTR,UINT32);
static void     initData(void);
static INT32    testSR(void);
static INT32    getBlocksize(const LogExtentList& list);
static void     makeFiles(File **farr,INT32 num,UINT32 nbytes,INT32 aligned);

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

static BYTE_PTR data_buffer = new BYTE[BUFSIZE];
static BYTE_PTR zero_buffer = new BYTE[BUFSIZE];

static DriveDevice      *ddevp = NULL;
static PhysicalVolume   *pvolp = NULL;
static PhysExtent       *pextp = NULL;
static Partition        *partp = NULL;
static LogicalVolume    *logvp = NULL;

//-----------------------------------------------------------------------------
// 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 default 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 = testSR();

    // Clean up

    delete str;
    delete ddevp;

    return rc;
}

//-----------------------------------------------------------------------------
// testSR():  The actual test.  This is an interactive test that uses the
//            following commands:
//
// q                    Quit.
// a size               Allocate a file of specified size.
// f file_no            Free the specified file.
// d                    Print "directory" of files.
// p                    Print the bitmap in the SpaceResource
// m [ 0 1 ]            Mode:  if 0, use Extent-based; if 1, use List-based
// h                    Help.
// 
//-----------------------------------------------------------------------------

static INT32
testSR(void)
{
    LELSpaceResource    sr(*partp->logVolumep(),
                           srSpace_UnerasedOnly);
    LogExtent           lext(partp,0,BUFSIZE);
    CHAR                buf[128];
    INT32               quit = 0;
    INT32               i;
    File *              dir[MAX_FILES];
    INT32               mode;

    INT32               getFileno(File **,INT32);
    void                printDirectory(File **,INT32);

    Error               err(error_None);
    if (err = sr.error()) {
        printf("ABORTING: sr.error() -> %s\n",err.message());
        return -1;
    }
    
    // First we need to fill up our space resource.

    sr.putLogExtent(lext,err);
    if(err) {
        printf("ABORTING: testSR[putLogExtent()] -> %s\n",err.message());
        return -1;
    }
    if(sr.availBytes() != BUFSIZE) {
        printf("Space Resource allocation failed!  Aborting. . .\n");
        return -1;
    }
    printf("Type 'h' for help.\n\n");
    printf("Allocated Space Resource containing %ld bytes\n",(INT32)BUFSIZE);

    for(i = 0; i < MAX_FILES; i++) {
        dir[i] = NULL;
    }

    // Go into a big loop now.

    for(;;) {
        INT32   itmp;
        INT32   fileno;

        if(gets(buf) == NULL) {
            buf[0] = 'q';
        }
        switch(buf[0]) 
        {
        case 'q':               // Quit
            quit = 1;
            break;

        case 'a':               // Allocate a File
            if(sscanf(buf,"%*c %ld",&itmp) != 1) {
                printf("Bad parse:  Did not understand 'a' command\n");
                break;
            }
            fileno = getFileno(dir,MAX_FILES);
            if(fileno == -1) {
                printf("Cannot allocate File:  none left\n");
                break;
            }
            dir[fileno] = new File(itmp);
            if(dir[fileno] == NULL) {
                printf("Cannot allocate File:  no free store\n");
                break;
            }
            if(mode == 0) {
                if(dir[fileno]->allocExtent(sr)) {
                    printf("File allocation failed on allocExtent()\n");
                    delete dir[fileno];
                    dir[fileno] = NULL;
                    break;
                }
            }
            else {
                if(dir[fileno]->allocList(sr)) {
                    printf("File allocation failed on allocList()\n");
                    delete dir[fileno];
                    dir[fileno] = NULL;
                    break;
                }
            }
            if(dir[fileno]->writeData()) {
                printf("File write failed\n");
                delete dir[fileno];
                dir[fileno] = NULL;
                break;
            }
            printf("Successfully allocated file %d.\n",fileno);
            break;

        case 'f':               // Free a File
            if(sscanf(buf,"%*c %ld",&itmp) != 1) {
                printf("Bad parse:  Did not understand 'f' command\n");
                break;
            }
            if((dir[itmp] == NULL) || (itmp >= MAX_FILES)) {
                printf("No file %ld to free\n",itmp);
                break;
            }
            // We don't know how the file was allocated, so just free it
            // both ways.  This shouldn't cause any problems.
            if(dir[itmp]->checkData()) {
                printf("File %ld data verify failed\n",itmp);
            }
            dir[itmp]->freeExtent(sr);
            dir[itmp]->freeList(sr);
            delete dir[itmp];
            dir[itmp] = NULL;
            printf("File %ld freed\n",itmp);
            break;

        case 'd':               // Print "directory" of Files
            printDirectory(dir,MAX_FILES);
            break;

        case 'p':               // Print bitmap of SR
            printf("Block Size:      %d\n",sr.blockSize());
            printf("Available Bytes: %ld\n",sr.availBytes);
            printf("Largest Extent:  %ld\n",sr.maxExtLen());
            printf("Error:           %s\n",sr.error().message());
            printf("Allows Erased:   %s\n",sr.allowsErased()?"True":"False");
            printf("Allows Unerased: %s\n",sr.allowsUnerased()?"True":"False");
            ((LogExtentList)sr).print(2,"Free Extents");
            break;
        
        case 'm':               // Change the mode
            if(sscanf(buf,"%*c %ld",&itmp) != 1) {
                printf("Bad parse:  Did not understand 'm' command\n");
                break;
            }
            if(!(itmp == 0 || itmp == 1)) {
                printf("Bad mode:  Must be 0 or 1\n");
                break;
            }
            mode = itmp;
            break;

        case 'h':               // Help
            printf("\n");
            printf("q\t\t\tQuit.\n");
            printf("a size\t\t\tAllocate a file of specified size.\n");
            printf("f file_no\t\tFree the specified file.\n");
            printf("d \t\t\tPrint \"directory\" of files.\n");
            printf("p \t\t\tPrint the SpaceResource.\n");
            printf("m [ 0 1 ]\t\tMode:  if 0, use Extent-based; if 1, use List-based.\n");
            printf("h\t\t\tHelp.\n");
            printf("\n");
            break;

        default:                // Oops
            printf("Unknown command\n");
            break;

        }                       // End of switch

        if(quit) {
            for(i = 0; i < MAX_FILES; i++) {
                if(dir[i] != NULL) {
                    delete dir[i];
                }
            }
            break;
        }
    }
}

INT32 
getFileno(File **farr,INT32 max) 
{
    INT32       i;

    for(i = 0; i < max; i++) {
        if(farr[i] == NULL) {
            return i;
        }
    }
    return -1;
}

void
printDirectory(File **farr,INT32 max)
{
    INT32       i;

    printf("Fileno\tSize\n\n");
    for(i = 0; i < max; i++) {
        if(farr[i] != NULL) {
            printf("%6ld\t%ld\n",i,farr[i]->size);
        }
    }
}

//-----------------------------------------------------------------------------
// 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;

    // Fill up the data buffers

    srand48(23);
//    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.  Each individual test will decide what kind of extents
    // and/or lists to create, so we'll just get the ancillary objects ready
    // here (primarily the Partition).

    pvolp = new PhysicalVolume(ddevp);
    pextp = new PhysExtent(pvolp,257,pvolp->numSectors() - 2 - 257);
    partp = new Partition(*pextp);
    logvp = new LogicalVolume(partp);
    if( (pvolp == NULL) || (pextp == NULL) ||
        (partp == NULL) || (logvp == NULL)) {
        (void) fprintf(stderr,"Construction of pvolp, pextp, or partp failed:  \
no free store!");
        delete ddevp;
        exit(1);
    }
}

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

static INT32
getBlocksize(const LogExtentList& list)
{
    Error error;
    const LogExtent *   lextp;

    if(list.numExtents == 0) {          // Empty list; we have to return 0!
        return 0;
    }
    lextp = list.at(0,error);
    assert(lextp != NULL);

    return lextp->blockSize();
}

//-----------------------------------------------------------------------------
// makeFiles():  allocates File structs and randomly distributes their sizes
//               to the given size.  In other words, each of the Files will
//               be of random size, and the sum of the lengths of all the Files
//               will be nbytes.
//-----------------------------------------------------------------------------

static void
makeFiles(File **farr,INT32 num,UINT32 nbytes,INT32 aligned)
{
    INT32       i;
    INT32       which,howmuch;
    UINT32      nblocks,blocksize;
    UINT32      *sizes;

    sizes = new UINT32[num];

    // First we fill in the sizes array relatively evenly.  The last File 
    // will get any "leftovers."  

    if(num == 0) {      // Avoid divide-by-0 traps
        return;
    }

    blocksize = partp->blockSize();
    nblocks = nbytes / blocksize;
    for(i = 0; i < num; i++) {
        sizes[i] = (nblocks / num)*blocksize;
    }
    sizes[num-1] += (nblocks % num)*blocksize;

    // To make "ragged" sizes, we subtract [0,bs - 1] from each size.  This 
    // won't affect the space in terms of blocks required by each file,
    // but it will make the byte size "ragged."

    if(aligned == RAGGED) {
        for(i = 0; i < num; i++) {
            sizes[i] -= (lrand48() % blocksize);
        }
    }

    // Now the heart of this algorithm:  each size will "pick" another size
    // in the array and either donate or steal some size.  I have no real
    // idea what kind of distribution this will produce, but it should be
    // interesting in any case.  We steal or donate only in block-multiples
    // to get the RAGGED case working (as well as for the ALIGNED case).

    for(i = 0; i < num; i++) {
        which = lrand48() % num;
        if(lrand48() % 2) {     // We'll donate
            if((sizes[i] / blocksize) == 0) {  // Nothing left
                continue;
            }
            howmuch = lrand48() % ((sizes[i] / blocksize));
            howmuch *= blocksize;
            sizes[i] -= howmuch;
            sizes[which] += howmuch;
        }
        else {                  // We'll steal
            if((sizes[which] / blocksize) == 0) {  // Nothing left
                continue;
            }
            howmuch = lrand48() % ((sizes[which] / blocksize));
            howmuch *= blocksize;
            sizes[i] += howmuch;
            sizes[which] -= howmuch;
        }
    }

    // Now allocate the File structs and fill in their sizes, and we're done.

    for(i = 0; i < num; i++) {
        farr[i] = new File(sizes[i]);
        printf("Creating file of size %ld\n",sizes[i]);
    }
    delete [] sizes;
}



//-----------------------------------------------------------------------------
// File struct methods
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// File():  constructor.
//-----------------------------------------------------------------------------

File::File(UINT32 len)
{
    // Don't exceed the length of our data buffer

    if(len > BUFSIZE) {
        len = BUFSIZE;
    }

    size = len;
    crc = CheckSum(0,data_buffer,size);
    lextp = NULL;
    listp = NULL;
}

//-----------------------------------------------------------------------------
// ~File():  destructor.
//-----------------------------------------------------------------------------

File::~File()
{
    if(lextp) {
        delete lextp;
    }
    if(listp) {
        delete listp;
    }
}

//-----------------------------------------------------------------------------
// allocExtent():  Perform extent-based allocation.  
//
// Return Values:
//
// -1 on failure, 0 on success.
//-----------------------------------------------------------------------------

INT32
File::allocExtent(SpaceResource& sr)
{
    Error       error;
    LogExtent   lext;
    
    sr.takeLogExtent(lext,size,error);
    
    if(error != error_None) {
        return -1;
    }
    if(lextp != NULL) {
        delete lextp;
    }
    lextp = new LogExtent(lext.partitionp(),lext.startBlock(),lext.infoLen());
    if(lextp == NULL) {
        return -1;
    }
    return 0;
}

//-----------------------------------------------------------------------------
// allocList():  Perform list-based allocation.
//
// RETURN VALUES:
//
// Returns -1 on failure, 0 on success.
//-----------------------------------------------------------------------------

INT32
File::allocList(SpaceResource& sr)
{
    LogExtentList       list(sr.logVolume());
    Error               error;

    sr.takeLogExtentList(list,size,error);

    if(error != error_None) {
        return -1;
    }
    if(listp != NULL) {
        delete listp;
    }
    listp = new LogExtentList(sr.logVolume());
    if(listp == NULL) {
        return -1;
    }
    *listp = list;
    return 0;
}

//-----------------------------------------------------------------------------
// freeExtent():  Return file's extent to SpaceResource.
//-----------------------------------------------------------------------------

void
File::freeExtent(SpaceResource& sr)
{
    Error err(error_None);
    if(lextp == NULL) {
        return;
    }
    sr.putLogExtent(*lextp,err);
    if (err) {
        printf("ERROR:freeExtent[putLogExtent()] -> %s\n",err.message());
    }
    delete lextp;
    lextp = NULL;
}

//-----------------------------------------------------------------------------
// freeList():  Return file's list to SpaceResource.
//-----------------------------------------------------------------------------

void
File::freeList(SpaceResource& sr)
{
    Error err(error_None);
    if(listp == NULL) {
        return;
    }
    sr.putLogExtentList(*listp,err);
    if (err) {
        printf("ERROR:freeList[putLogExtent()] -> %s\n",err.message());
    }
    delete listp;
    listp = NULL;
}

//-----------------------------------------------------------------------------
// writeData():  writes data, if possible, to the file's extent.  Currently
//               hard-coded to use the global variable data_buffer as the
//               data source.
//
// RETURN VALUES:
//
// Returns -1 on failure, 0 on success.
//-----------------------------------------------------------------------------

INT32
File::writeData(void)
{
    ByteArray           barr(size);
    ByteArrayRef        bref(barr);
    Error               error;

    if(!isAllocated()) {
        return -1;
    }

    bref.fromMem(data_buffer);
    if(lextp) {
        if(lextp->write(bref,error) < size) {
            printf("lextp->write() failed, error.msg = %s\n",error.message());
            return -1;
        }
        else {
            return 0;
        }
    }
    else {
        if(listp->write(bref,error) < size) {
            printf("listp->write() failed, error.msg = %s\n",error.message());
            return -1;
        }
        else {
            return 0;
        }
    }
}

//-----------------------------------------------------------------------------
// checkData():  Reads in the file's data and verifies that its CRC is what
//               it's supposed to be.
//
// RETURN VALUES:
//
// Returns -1 on failure, 0 on success.
//-----------------------------------------------------------------------------

INT32
File::checkData(void)
{
    INT32               readcrc;
    ByteArray           barr(size);
    ByteArrayRef        bref(barr);
    Error               error;

    if(!isAllocated()) {
        return -1;
    }
    bref.fromMem(zero_buffer);          // Clear it out
    if(lextp) {
        if(lextp->read(bref,error) < size) {
            return -1;
        }
    }
    else {
        if(listp->read(bref,error) < size) {
            return -1;
        }
    }
    readcrc = CheckSum(0,(BYTE_PTR) bref,size);
    return ((readcrc == crc) ? 0 : -1);
}



