// sortlog.c 
//
// this program sorts memsnap and poolsnap logs into a more readable form 
// sorts by pid 
// scans the data file one time, inserts record offsets based on PID into linked list 
// then writes data into new file in sorted order 
// determine whether we have a poolsnap or memsnap log - in pid is equivalent 
// to pooltag for our sorting 

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <io.h>
#include <stdlib.h>

// definitions 
#define RECSIZE     1024   // record size  (max line size)
#define MAXTAGSIZE  200    // max length of tag name

#define DEFAULT_INFILE  "memsnap.log"
#define DEFAULT_OUTFILE "memsort.log"

typedef enum _FILE_LOG_TYPES {
    MEMSNAPLOG=0,
    POOLSNAPLOG,
    UNKNOWNLOG
} FILE_LOG_TYPES;

//
// Linked list of unique PID or PoolTag
//

typedef struct PIDList {
    char            PIDItem[11];
    struct RecList* RecordList;
    struct PIDList* Next;
    DWORD           Count;                // number of items pointed to by RecordList
};

//
// For each PID or pool tag, we have a linked list of offsets into file of each line
//

typedef struct RecList {
    LONG            rOffset;
    struct RecList* Next;
};

// global data 
FILE_LOG_TYPES CurrentFileType= UNKNOWNLOG;
CHAR szHeader[RECSIZE];         // first line of file 
BOOL bIgnoreTransients= FALSE;  // Ignore tags or processes that aren't in every snapshot
DWORD g_MaxSnapShots= 0;        // max snap shots in the file 

#define INVALIDOFFSET (-2)   /* invalid file offset */

// prototypes 
VOID ScanFile(FILE *, struct PIDList *);
VOID WriteFilex(FILE *, FILE *, struct PIDList *);

VOID Usage(VOID)
{

    printf("sortlog [-?] [<logfile>] [<outfile>]\n");
    printf("Sorts an outputfile from memsnap.exe/poolsnap.exe in PID/PoolTag order\n");
    printf("-?        prints this help\n");
    printf("-i        ignore tags or processes that are not in every snapshot\n");
    printf("<logfile> = %s by default\n",DEFAULT_INFILE );
    printf("<outfile> = %s by default\n",DEFAULT_OUTFILE);
    exit(-1);
}

// CheckPointer
//
// Make sure it is not NULL.  Otherwise print error message and exit.
//


VOID CheckPointer( PVOID ptr )
{
    if( ptr == NULL ) {
        printf("Out of memory\n");
       exit(-1);
    }
}

#include "tags.c"

int __cdecl main(int argc, char* argv[])
{
    FILE* InFile;
    FILE* OutFile;
    struct PIDList ThePIDList = {0};
    CHAR* pszInFile= NULL;              // input filename
    CHAR* pszOutFile= NULL;             // output filename
    INT   iFileIndex= 0;
    INT   iCmdIndex;                    // index into argv

    ThePIDList.RecordList = (struct RecList *)LocalAlloc(LPTR, sizeof(struct RecList));
    CheckPointer( ThePIDList.RecordList );
    ThePIDList.RecordList->rOffset= INVALIDOFFSET;

    //
    // parse command line
    //

    for( iCmdIndex=1; iCmdIndex<argc; iCmdIndex++ ) {
        CHAR chr;

        chr= argv[iCmdIndex][0];

        if( (chr=='-') || (chr=='/') ) {
            chr= argv[iCmdIndex][1];
            switch( chr ) {
                case '?':
                    Usage();
                    break;
                case 'i':         // ignore all process that weren't running the whole time
                    bIgnoreTransients= TRUE;
                    break;
                default:
                    printf("Invalid switch %s\n",argv[iCmdIndex]);
                    Usage();
                    break;
            }
        }
        else {
            if( iFileIndex == 0 ) {
                pszInFile= argv[iCmdIndex];
                iFileIndex++;
            }
            else if( iFileIndex == 1 ) {
                pszOutFile= argv[iCmdIndex];
                iFileIndex++;
            }
            else {
                printf("Too many files specified\n");
                Usage();
            }
        }
    }

    //
    // fill in default filenames if some aren't given
    //

    switch( iFileIndex ) {
       case 0:
          pszInFile=  DEFAULT_INFILE;
          pszOutFile= DEFAULT_OUTFILE;
          break;

       case 1:
          pszOutFile= DEFAULT_OUTFILE;
          break;
      
       default:
           break;
    }


    //
    // open the files
    //

    InFile= fopen( pszInFile, "r" );
    if( InFile == NULL ) {
        printf("Error opening input file %s\n",pszInFile);
        return( 0 );
    }
    
    OutFile= fopen( pszOutFile, "a" );
    if( OutFile == NULL ) {
        printf("Error opening output file %s\n",pszOutFile);
        return( 0 );
    }

    //
    // read in the data and set up the list
    //

    ScanFile(InFile, &ThePIDList);

    //
    // write the output file 
    //

    WriteFilex(InFile, OutFile, &ThePIDList);

    // close and exit 
    _fcloseall();
    return 0;
}

// read the input file and get the offset to each record in order and put in list

VOID ScanFile(FILE *InFile, struct PIDList *ThePIDList)
{
    char inchar = 0;
    char inBuff[RECSIZE] = {0};
    char PID[11] = {0};
    LONG Offset = 0;
    BOOL Found = FALSE;
    struct PIDList *TmpPIDList;
    struct RecList *TmpRecordList;
    INT iGarb = 0;

    /* initialize temp list pointer */
    TmpPIDList = ThePIDList;

    /* read to the first newline, check for EOF */
    /* determine whether it is a poolsnap or memsnap log */
    if ((fscanf(InFile, "%[^\n]", &szHeader)) == EOF)
        return;
    if (strncmp("Process ID", szHeader, 10) == 0)
        CurrentFileType= MEMSNAPLOG;
    if (strncmp(" Tag  Type", szHeader, 10) == 0)
        CurrentFileType= POOLSNAPLOG;

    if( CurrentFileType == UNKNOWNLOG )
    {
        printf("unrecognized log file\n");
        return;
    }

    inBuff[0] = 0;

    /* read to the end of file */
    while (!feof(InFile)) {
        /* record the offset */
        Offset = ftell(InFile);

        /* if first char == newline, skip to next */
        if ((fscanf(InFile, "%[^\n]", &inBuff)) == EOF)
            return;
        /* read past delimiter */
        inchar = (char)fgetc(InFile);
        // skip if its an empty line
        if (strlen(inBuff) == 0) {
            continue;
        }
        // 
        // Handle tags if this is a tagged line
        //

        if( inBuff[0] == '!' )
        {
            ProcessTag( inBuff+1 );
            continue;
        }


        if (3 == sscanf(inBuff, "%2u\\%2u\\%4u", &iGarb, &iGarb, &iGarb)){
            continue;
        }

        /* read the PID */
        strncpy(PID,inBuff,10);

        // scan list of PIDS, find matching, if no matching, make new one
        // keep this list sorted

        TmpPIDList = ThePIDList;    /* point to top of list */
        Found= FALSE;
        while( TmpPIDList->Next != 0 ) {
            int iComp;

            iComp= strcmp( PID, TmpPIDList->PIDItem);
            if( iComp == 0 ) {  // found
                Found= TRUE;
                break;
            } else {            // not found
                if( iComp < 0 ) {  // exit if we have gone far enough
                   break;
                }
                TmpPIDList= TmpPIDList->Next;
            }
        }

        // if matching, append offset to RecordList
        // add offset to current PID list

        if( Found ) {
            TmpPIDList->Count= TmpPIDList->Count + 1;
            if( TmpPIDList->Count > g_MaxSnapShots ) g_MaxSnapShots= TmpPIDList->Count;

            TmpRecordList= TmpPIDList->RecordList;
            // walk to end of list
            while( TmpRecordList->Next != 0 ) {
                TmpRecordList= TmpRecordList->Next;
            }

            TmpRecordList->Next= (struct RecList*)LocalAlloc(LPTR, sizeof(struct RecList));
            CheckPointer( TmpRecordList->Next );
            TmpRecordList->Next->rOffset= Offset;
        }
        // make new PID list, add new PID, add offset
        else {
            struct PIDList* pNewPID;
            // allocate a new PID,
            // copy current PID information to it
            // overwrite current PID information with new PID information
            // have current PID point to new PID which may point on

            pNewPID= (struct PIDList*) LocalAlloc(LPTR, sizeof(struct PIDList));
            CheckPointer( pNewPID );
            memcpy( pNewPID, TmpPIDList, sizeof(*pNewPID) );

            strcpy( TmpPIDList->PIDItem, PID );
            TmpPIDList->RecordList= (struct RecList*) LocalAlloc(LPTR, sizeof(struct RecList));
            CheckPointer( TmpPIDList->RecordList );
            TmpPIDList->RecordList->rOffset= Offset;
            TmpPIDList->Next= pNewPID;
            TmpPIDList->Count= 1;
 
        }

        /* if EOF, return */
        /* clear the inBuff */
        inBuff[0] = 0;
    }

}

// look for the next PID line in the first table 

VOID WriteFilex(FILE *InFile, FILE *OutFile, struct PIDList *ThePIDList)
{
    struct PIDList *TmpPIDList;
    struct RecList *TmpRecordList;
    char inBuff[RECSIZE] = {0};    

    /* initialize temp list pointer */
    TmpPIDList = ThePIDList;

    /* heading */
    fprintf(OutFile,"%s\n",szHeader);

    OutputTags( OutFile );


    /* while not end of list, write records at offset to end of output file */
    while (TmpPIDList != 0) {
        TmpRecordList = TmpPIDList->RecordList;


        if( (!bIgnoreTransients) || (TmpPIDList->Count == g_MaxSnapShots) ) {
            while (TmpRecordList != 0) {
                LONG Offset;
    
                Offset= TmpRecordList->rOffset;
                if( Offset != INVALIDOFFSET ) {
                    /* read in record */
                    if (fseek(InFile, TmpRecordList->rOffset, SEEK_SET) == -1) break;
                    if (fscanf(InFile, "%[^\n]", &inBuff) != 1) break;
    
                    /* read out record */
                    fprintf(OutFile, "%s\n", &inBuff);
                 }
    
                /* get next record */
                TmpRecordList = TmpRecordList->Next;
            }
    
            /* add a line here */
            fputc('\n', OutFile);
        }

        /* get next record */
        TmpPIDList = TmpPIDList->Next;
    }

}