// msDevTest.C

// $Source: /projects/HMS/nsr/udfAncil/devsTest/msACstr.cpp,v $
// $Revision: 1.6 $     $Author: hmgr $
// $State: Exp $        $Locker:  $
// $Date: 95/10/25 17:26:58 $

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
#include <time.h>

#include "nsrLib.h"
#include "dlist.h"
#include "msACobj.h"

//-----------------------------------------------------------------------------
// Some data types
//-----------------------------------------------------------------------------

struct srfcEntry {
    int         slot;
    int         side;

    srfcEntry(void) : slot(0), side(0) {}
};

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

static int mainTest(String *devf,String *logf,int iter,int ask,dlist *srfclist);
static int getDriveMaps(AC& acTest);
static int srfc_cmp(VOID_PTR d1,VOID_PTR d2);
static int srfc_free(VOID_PTR d1);
static void Usage(void);

//-----------------------------------------------------------------------------
// msACstress.C:  This program is an NON-INTERACTIVE test to verify the 
//                functionality of the hpACDev object.  It performs stress-
//                type data integrity testing using read, write, writeWOE, 
//                and erase.
//
// Usage:
//
// msACstress -d devfile -i iterations [-y] [-l logfile] surface_list 
//
// -d devfile:  Devfile is the passthru device file for the autochanger
// -i iterations:  Number of iterations for the test.
// [-y]:  Optional.  If this option is given, the test will ask the user
//        to provide device file mappings for drives in the autochanger.
//        Otherwise, the test will attempt to create device files for the
//        drives (you must be root for this to succeed).
// [-l logfile]:  Optional.  If specified, all test output will be appended
//                to the specified file.  If not specified, all output will
//                be sent to stdout.
// surface_list:  A list of one or more entries of the form [slot_number][A|B]
//
// Example:
//
// msACstress -d ac1 -i 100 1A 1B 2A 3A
//-----------------------------------------------------------------------------

int
main( int argc,CHAR_PTR argv[] )
{
    int         c;
    int         i;
    int         usage = 0;
    int         slot;
    CHAR        side;
    int         rval;
    int         argIter = 0;            // Number of iterations
    int         argAsk = 0;             // Ask user for drive mappings?
    String      *argDevfile = NULL;     // Device file for autochanger
    String      *argLogfile = NULL;     // Log file
    dlist       *srfcList;              // List of surfaces to use
    srfcEntry   *sptr;
    srfcEntry   srfc;
    dlist_node  *dnode;

    extern CHAR_PTR optarg;             // For getopt()
    extern int  optind,opterr;          // For getopt()

    // Parse command-line arguments.

    while((c = getopt(argc,argv,"d:i:yl:")) != EOF) {
        switch(c) {
        case 'd':
            argDevfile = new String(optarg);
            if(argDevfile == NULL) {
                fprintf(stderr,"%s:  memory allocation failure!\n",argv[0]);
                exit(2);
            }
            break;
        case 'i':
            if(sscanf(optarg,"%d",&argIter) != 1) {
                usage = 1;
            }
            break;
        case 'y':
            argAsk = 1;
            break;
        case 'l':
            argLogfile = new String(optarg);
            if(argLogfile == NULL) {
                fprintf(stderr,"%s:  memory allocation failure!\n",argv[0]);
                exit(2);
            }
            break;
        default:
            usage = 1;
        }
    }

    if(argDevfile == NULL) {
        fprintf(stderr,"%s:  You must specify a device file!\n",argv[0]);
        usage = 1;
    }
    if(argIter == 0) {
        fprintf(stderr,"%s:  You must specify the number of iterations!\n",
                argv[0]);
        usage = 1;
    }

    // Create a dlist to keep track of our surfaces.

    srfcList = dlistCreate(srfc_cmp);
    if(srfcList == NULL) {
        fprintf(stderr,"%s:  memory allocation failure!\n",argv[0]);
        exit(2);
    }

    // Now process any remaining arguments and interpret them as surfaces.
    // Add them to the list of surfaces.

    for(i = optind; i < argc; i++) {
        if(sscanf(argv[i],"%d%c",&slot,&side) != 2) {
            usage = 1;
            break;
        }
        side = toupper(side);
        if((side != 'A') && (side != 'B')) {
            usage = 1;
            break;
        }

        side = ((side == 'A') ? 0 : 1);

        // Check to see if the surface is already in our list.  If so, don't
        // add it.

        srfc.slot = slot;
        srfc.side = side;

        dnode = dlistFind(srfcList,&srfc);
        if(dnode != NULL) {
            continue;
        }
        sptr = new srfcEntry;
        if(sptr == NULL) {
            fprintf(stderr,"%s:  Memory allocation failure!\n",argv[0]);
            exit(2);
        }
        sptr->slot = slot;
        sptr->side = side;
        if(dlistInsert(srfcList,sptr)) {
            fprintf(stderr,"%s:  Memory allocation failure!\n",argv[0]);
            exit(2);
        }
    }

    if(dlistCount(srfcList) == 0) {
        fprintf(stderr,"%s:  You must specify at least one surface!\n",argv[0]);
        usage = 1;
    }

    if(usage) {
        Usage();
        exit(2);
    }

    // If we got here, we're ready to start testing.

    rval = mainTest(argDevfile,argLogfile,argIter,argAsk,srfcList);
    dlistApply(srfcList,srfc_free,0);
    dlistDestroy(srfcList);
    if(argDevfile != NULL) {
        delete argDevfile;
    }
    if(argLogfile != NULL) {
        delete argDevfile;
    }
    return rval;
}

//-----------------------------------------------------------------------------
// FUNCTION:  mainTest
//
// Controls all of the various sub-tests and creates the AC object.  Also
// responsible for querying the user for drive mappings if the ask parameter
// is nonzero.
//-----------------------------------------------------------------------------

static int 
mainTest(String *devf,String *logf,int iter,int ask,dlist *srfcList)
{
    int         i;
    FILE *      stream;
    int         numsids;

    assert(devf != NULL);
    assert(srfcList != NULL);

    // Open the stream for test output

    if(logf == NULL) {
        stream = stdout;
    }
    else {
        stream = fopen((CHAR_PTR) *logf,"a");
        if(stream == NULL) {
            fprintf(stderr,"mainTest():  Could not open logfile %s\n",
                    (CHAR_PTR) *logf);
            return 2;
        }
    }

    // Log some information.

    time_t timer;
    timer = time(NULL);
    fprintf(stream,"Starting Autochanger Device Test at %s\n",
            ctime(&timer));

    // Try to create an AC object.

    AC  acTest(stream);
    if(!acTest.accepting_commands()) {
        fprintf(stream,"AC object construction failed, aborting!\n");
        fclose(stream);
        return 2;
    }

    // Register the device file.

    acTest.registerAC((CHAR_PTR) *devf);
    if(acTest.acstate() != ac_ready) {
        fprintf(stream,"AC device registration failed, aborting!\n");
        fclose(stream);
        return 2;
    }

    // Now we have to get drive mappings if the user so desires.

    if(ask) {
        if(getDriveMaps(acTest)) {
            fclose(stream);
            return 2;
        }
    }

    // Map the surfaces.

    dlist_node *nptr = dlistHead(srfcList);
    srfcEntry  *sptr;
    for(i = 0; i < dlistCount(srfcList); i++) {
        sptr = (srfcEntry *) nptr->data;
        acTest.map(sptr->slot,sptr->side);
        nptr = nptr->next;
    }

    sid_t       *sidarr = new sid_t[dlistCount(srfcList)];
    if(sidarr == NULL) {
        fprintf(stream,"Memory allocation failure, aborting\n");
        return 2;
    }
    numsids = acTest.getValidSids(sidarr,dlistCount(srfcList));
    if(numsids == 0) {
        fprintf(stream,"No valid surfaces specified, aborting!\n");
        return 2;
    }

    // Finally, we should be ready to do some actual testing.  First fill up
    // the write buffer with some random data.

    srand48(time(NULL));
    BYTE *bptr = acTest.writeBuf();
    for(i = 0; i < MAX_WRITE_SIZE; i++) {
        bptr[i] = (BYTE) (lrand48() % 256);
    }

    // Go into a big loop.  Each iteration, we pick at random a surface to
    // read and write to.  Each iteration we write to ten different locations
    // on the disk.

    int         offset_arr[10];
    int         io_len[10];
    int         rval = 0;
    for(i = 0; i < iter; i++) {
        int     j;
        int     which = (lrand48() % numsids);
        int     len;
        int     offset = 0;
        int     sectorsize;

        // Write data first.  We pick an offset of [0,255blocks) and a write 
        // length of [0,256K).
   
        for(j = 0; j < 10; j++) {
            sectorsize = acTest.getsize(sidarr[which]);
            offset += (lrand48() % 256);
            offset_arr[j] = offset;
            len = (lrand48() % MAX_WRITE_SIZE);
            io_len[j] = len;
            fprintf(stream,"Writing to sid %d:  offset %d, length %d. . .\n",
                    sidarr[which],offset_arr[j]*sectorsize,io_len[j]);
            acTest.write(sidarr[which],offset_arr[j],io_len[j]);
            offset += (len / sectorsize) + 1;
        }

        // Now read the data back and CRC to verify data integrity.

        for(j = 0; j < 10; j++) {
            int crc1,crc2;

            memset(acTest.readBuf(),0,MAX_READ_SIZE);
            fprintf(stream,"Reading from sid %d:  offset %d, length %d. . .\n",
                    sidarr[which],offset_arr[j]*sectorsize,io_len[j]);
            acTest.read(sidarr[which],offset_arr[j],io_len[j]);
            crc1 = acTest.crc(acTest.writeBuf(),io_len[j]);
            crc2 = acTest.crc(acTest.readBuf(),io_len[j]);
            if(crc1 != crc2) {
                fprintf(stream,"Read crc %08x != written crc %08x!\n",crc1,
                        crc2);
                rval = 2;
            }
        }
    }
    delete [] sidarr;
    fclose(stream);
    return rval;
}
                       
//-----------------------------------------------------------------------------
// FUNCTIONS:  getDriveMaps
//
// Queries the user for drive map information.
//-----------------------------------------------------------------------------

static int
getDriveMaps( AC& acTest )
{
    int                      i;
    CHAR                     buffer[256];
    struct element_addresses eltaddr;
    struct element_status    eltstat;
    struct stat              statbuf;

    // We'll only ask about the drives that are in the autochanger.

    if(acTest.eltaddr(eltaddr)) {
        fprintf(stderr,"Can't get element addresses information, aborting!\n");
        return -1;
    }
    
    for(i = 0; i < eltaddr.num_data_transfers; i++) {
        if(acTest.eltstat(i + eltaddr.first_data_transfer,eltstat)) {
            fprintf(stderr,"Can't get element status information, aborting!\n");
            return -1;
        }
        while(1) {
            fprintf(stdout,"Please enter device file for drive %d, tgt=%d, \
lun=%d:  ",i+1,eltstat.bus_address,eltstat.lun);
            if(gets(buffer) == NULL) {
                fprintf(stdout,"Invalid input.\n");
                continue;
            }
            if(stat(buffer,&statbuf)) {
                fprintf(stdout,"Cannot stat specified file, please reenter\n");
                continue;
            }
            if(!S_ISCHR(statbuf.st_mode)) {
                fprintf(stdout,"Specified file is not a character special \
device file!\n");
                continue;
            }

            // Add the device file.

            acTest.drivemap(eltstat.bus_address,eltstat.lun,buffer);
            break;
        }
    }
    return 0;
}

//-----------------------------------------------------------------------------
// FUNCTION:  srfc_cmp
//
// Comparison function for the srfcEntry struct.
//-----------------------------------------------------------------------------

static int
srfc_cmp(VOID_PTR d1,VOID_PTR d2)
{
    srfcEntry   *s1 = (srfcEntry *) d1;
    srfcEntry   *s2 = (srfcEntry *) d2;

    if((s1 == NULL) || (s2 == NULL)) {
        return 0;
    }
    if(s1->slot != s2->slot) {
        return (s1->slot - s2->slot);
    }
    return (s1->side - s2->side);
}

//-----------------------------------------------------------------------------
// FUNCTION:  srfc_free
//
// Data free function for the srfcEntry struct.
//-----------------------------------------------------------------------------

static int
srfc_free(VOID_PTR d1)
{
    srfcEntry   *s1 = (srfcEntry *) d1;

    if(s1 == NULL) {
        return -1;
    }
    delete s1;
    return 0;
}

//-----------------------------------------------------------------------------
// FUNCTION:  Usage
//
// Prints usage information to screen.
//-----------------------------------------------------------------------------

static void
Usage( void )
{
    static CHAR_PTR usage_str[] = {
"Usage:\n",
"msACstress -d devfile -i iterations [-y] [-l logfile] surface_list\n",
"-d devfile:     Devfile is the passthru device file for the autochanger.",
"-i iterations:  Number of iterations for the test.",
"[-y]:           Optional.  If this option is given, the test will ask the",
"                user to provide device file mappings for drives in the",
"                autochanger.  Otherwise, the test will attempt to create",
"                device files for the drives (you must be root for this to",
"                succeed).",
"[-l logfile]:   Optional.  If specified, all test output will be appended",
"                to the specified file.  If not specified, all output will",
"                be send to stdout.",
"surface_list:  A list of one or more entries of the form [slot_number][A|B]\n",
"Example:\n",
"./msACstress -d ac1 -i 100 1A 1B 2A 3A\n",
NULL
};
    int i = 0;

    while(usage_str[i] != NULL) {
        printf("%s\n",usage_str[i]);
        i++;
    }
}
