Operating-System-Simulator / Sim02 / simulator.c
simulator.c
Raw
#include "simulator.h"

/*
 * Function Name: changeProcessState
 * Algorithm: changes state in PCB struct and displays results as needed
 * Precondition: PCB passed into function
 * Postcondition: Process state changed and results shown
 * Exceptions: if the state parameter is the same as in the PCB do nothing 
 * Notes: None
 */
void changeProcessState( ConfigDataType *configPtr, fileWriteList **fileWriteList, PCB *PCB, int state )
   {
    char *writeStr;
    int pid = 0;
    char buffer[MAX_STR_LEN];

    if( PCB->state != state )
       {
        pid = PCB->pid;
        char *possibleStates[5];// = { "NEW", "READY", "RUNNING", "WAITING", "EXIT" };
                                // TODO clean up to be more elegant
            possibleStates[ 0 ] = "NEW";
            possibleStates[ 1 ] = "READY";
            possibleStates[ 2 ] = "RUNNING";
            possibleStates[ 3 ] = "WAITING";
            possibleStates[ 4 ] = "EXIT";

        writeStr = malloc( sizeof( char ) * MAX_STR_LEN );    // temporary solution due to
        intToChar( pid, writeStr );
        char *firstState = possibleStates[PCB->state];        // sprintf log file corruption
        char *nextState = possibleStates[state];
        
        sprintf( writeStr, "%d", pid );

        sprintf( buffer, "OS: Process %d set from %s -to-> %s\n", pid, firstState, nextState );
        copyString( writeStr, buffer );
        displayHandler( configPtr, fileWriteList, writeStr, false, false );
        
        PCB->state = state;
        free( writeStr );
       }
   }

/*
 * Function Name: checkProgramEnd
 * Algorithm: checks if all PCBs are in exit state
 * Precondition: PCBs are initialized in PCBLinkedList
 * PostCondition: bool result returned
 * Exceptions: None
 * Notes: None
 */
bool checkProgramEnd( PCBLinkedList **PCBLinkedListPtr )
   {
    // initialize function variables
    PCBLinkedList *tempPtr = *PCBLinkedListPtr;
    bool result = true;
    
    // while not at the end of PCB Linked List structure
    while( tempPtr->PCB->pid != -1 )
       {
        if( tempPtr->PCB->state != EXIT )
           {
            result = false;
           }
        tempPtr = tempPtr->nextPCB;
       }

    return result; 
   }

/*
 * Function Name: chooseNextProcess
 * Algorithm: depending on the scheduling type, 
 *            picks the next process based on PCB info
 * Precondition: All processes are not currently executing 
 *               (since we are assuming a single CPU), 
 *               and scheduling type is valid and selected
 * Postcondition: the next process to be run is selected and pointer to PCB is returned
 * Exceptions: If all processes are waiting or in exit state cannot choose a process
 * Notes: None
 */
PCB *chooseNextProcess( ConfigDataType *configPtr, fileWriteList **fileWriteList, 
                                                PCBLinkedList *PCBLinkedListPtr ) 
{
 // check which scheduler is chosen
 // FCFS-N, SJF-N, SRTF-P, FCFS-P, RR-P
 
 //printWriteList( fileWriteList );
 // switch statement to select based on scheduler
 switch( configPtr->cpuSchedCode )
    {
     // For now all cases use FCFS_N_Choice
     case CPU_SCHED_SJF_N_CODE:
         return FCFS_N_Choice( &PCBLinkedListPtr, configPtr, fileWriteList );
         break;
     case CPU_SCHED_SRTF_P_CODE:
         return FCFS_N_Choice( &PCBLinkedListPtr, configPtr, fileWriteList );
         break;
     case CPU_SCHED_FCFS_P_CODE:
         return FCFS_N_Choice( &PCBLinkedListPtr, configPtr, fileWriteList );
         break;
     case CPU_SCHED_RR_P_CODE:
         return FCFS_N_Choice( &PCBLinkedListPtr, configPtr, fileWriteList );
         break;
     case CPU_SCHED_FCFS_N_CODE:
         return FCFS_N_Choice( &PCBLinkedListPtr, configPtr, fileWriteList );
         break;
    }
 return NULL;
}

/*
 * Function Name: clearPCBList
 * Algorithm: frees memory used in Linked List structure
 * Precondition: None
 * Postcondition: Linked List is freed and NULL returned
 * Exceptions: None
 * Notes: None
 */
PCBLinkedList *clearPCBList( PCBLinkedList *pcbList )
   {
    // initialize function variables
    
    // check if pcbList is null
    if( pcbList->PCB->pid != -1 )
       {
        // recurse until empty PCB
        clearPCBList( pcbList->nextPCB );
       }
    // free PCB and PCBLinkedList node
    free( pcbList->PCB );
    free( pcbList );
    
    // if it is, return null
    return NULL; 
   }

/*
 * Function Name: clearWriteList
 * Algorithm: Recursive method to free memory of data structure used to 
 *            write to file
 * Precondition: None
 * Postcondition: Memory allocated for file write operations freed
 * Exceptions: None
 * Notes: None
 */
fileWriteList *clearWriteList( fileWriteList **fileWriteListPtr )
   {
    fileWriteList *fileWriteList = *fileWriteListPtr;
    if( compareString( fileWriteList->strToWrite, END_OF_STRLIST ) != STR_EQ )
       {
        clearWriteList( &( fileWriteList->nextStr ) );
       }  
    free( fileWriteList );
    return NULL;
   }

/*
 * Function Name: createPCB
 * Algorithm: helper function for scanForNewPCBs, makes new PCB struct 
 * Precondition: given pointer to start op code
 * Postcondition: new PCB created, pointer referencing now contains PCB struct
 * Exceptions: None
 * Notes: None
 */
void createPCB( OpCodeType *startOpCode, PCBLinkedList **PCBLLIterPtr, int cycleTime )
   {
    // allocate memory for next node in Linked List
    ( *PCBLLIterPtr )->nextPCB = ( PCBLinkedList * )malloc( sizeof( PCBLinkedList ) );

    // set pid to -1 to mark end of list
    ( *PCBLLIterPtr )->nextPCB->PCB = ( PCB * )malloc( sizeof( PCB ) );
    ( *PCBLLIterPtr )->nextPCB->PCB->pid = -1;

    // set various values for PCB
    ( *PCBLLIterPtr )->PCB->pid = getNextPID();
    ( *PCBLLIterPtr )->PCB->state = NEW;
    ( *PCBLLIterPtr )->PCB->programCounter = startOpCode;
    ( *PCBLLIterPtr )->PCB->timeRemaining = cycleTime;
    ( *PCBLLIterPtr )->PCB->interruptable = true;

    // step PCBLL pointer forward to nextPCB (empty)
    *PCBLLIterPtr = ( *PCBLLIterPtr )->nextPCB;
   }

/*
 * Function Name: displayHandler
 * Algorithm: different operations depending on if the output is to be 
 *            directed to a file, to std output, or both
 * Precondition: Config file specifies type of output 
 *               ( which must already be working at this point in the program )
 * Postcondition: program operations are properly displayed, can be during 
 *                simulator run as well as all at once to a file at termination
 * Exceptions: None
 * Notes: None
 */
void displayHandler( ConfigDataType *configPtr, fileWriteList **fileWriteListPtr, 
                                    char *toWrite, bool writeOnly, bool newLine )
   {
    // initialize variable for time string and string for output
    char *timeStr = malloc( sizeof(char) * MAX_STR_LEN );
    char *tempStr = malloc( sizeof(char) * MAX_STR_LEN );
    timeStr[0] = '\0';
    tempStr[0] = '\0';

    // get time from start of simulation
    accessTimer( LAP_TIMER, timeStr );
    
    // TODO: handle spacing of string so that the length of time string doesn't
    //       affect indendation
    
    // for readability of output, separate certain lines
    if( newLine )
       {
        sprintf( tempStr, "\n%s, %s", timeStr, toWrite );
       }
    else
       {
        sprintf( tempStr, "%s, %s", timeStr, toWrite );
       }

    // shortcut if need to write to monitor but config specifies to file
    if( !writeOnly )
       {
        // initialize function variables
        fileWriteList *ptrToIterNode = *fileWriteListPtr;

        // if only to file, write out to notify user that program is still working
        static bool firstStatement = false;

        // if we are writing to file (and using the data structure)
        if( configPtr->logToCode != LOGTO_MONITOR_CODE )
           {
            // make sure iter pointer is at end node and fileStrPtr points to the penultimate node
            while( compareString( ( ptrToIterNode->strToWrite), (END_OF_STRLIST) ) != STR_EQ )
               {
                ptrToIterNode = ptrToIterNode->nextStr;
               }
           }

        switch( configPtr->logToCode )
           {
            case LOGTO_MONITOR_CODE:
                fprintf( stderr, tempStr );
                break;       

            case LOGTO_FILE_CODE:
                if( !firstStatement )
                   {
                    fprintf( stderr, "Simulator Running - outputting to file only...\n" );
                    firstStatement = true;
                   }

                // create next node after last
                ptrToIterNode->nextStr = ( fileWriteList * )malloc( sizeof( fileWriteList ) );

                // swap values
                ptrToIterNode->nextStr->strToWrite = END_OF_STRLIST;
                ptrToIterNode->strToWrite = tempStr;
                break;

            case LOGTO_BOTH_CODE:
                fprintf( stderr, tempStr );

                // create next node after last
                ptrToIterNode->nextStr = ( fileWriteList * )malloc( sizeof( fileWriteList ) );

                // swap values
                ptrToIterNode->nextStr->strToWrite = END_OF_STRLIST;
                ptrToIterNode->strToWrite = tempStr;
                break;
           }
       }
    else
       {
        fprintf( stderr, toWrite );
       }
    free(timeStr);
   }

/*
 * Function Name: FCFS_N_Choice
 * Algorithm: first come first-served. chooses lowest pid process, non-preemptive
 * Precondition: Processes with PCBs created exist
 * Postcondition: PCB with lowest pid is chosen and returned
 * Exceptions: None
 * Notes: None
 */
PCB *FCFS_N_Choice( PCBLinkedList **pcbList, ConfigDataType *configPtr, fileWriteList **fileWriteListPtr )
   {
    // initialize function variables
    PCBLinkedList *pcbListPtr = *pcbList;
    char *outputStr = malloc( sizeof( char ) * MAX_STR_LEN );
    int pid = 0;
    int timeRemaining;

    // set return pointer to first pcb
    PCB *returnPtr = ( pcbListPtr->PCB );
    //printPCBList( &pcbListPtr );
    while ( pcbListPtr != NULL && pcbListPtr->PCB->pid != -1 )
       {
        // if pid is less than returnPtr pid set returnPtr to current PCB
        if( ( ( pcbListPtr->PCB->pid ) < ( returnPtr->pid ) && pcbListPtr->PCB->state == READY ) 
                        || ( returnPtr->state ) == EXIT )
           {
            returnPtr = ( pcbListPtr->PCB );
           }
        pcbListPtr = pcbListPtr->nextPCB;
       }

    pid = returnPtr->pid;
    timeRemaining = returnPtr->timeRemaining;
    sprintf( outputStr, "OS: Process %d selected with %d ms remaining\n", pid, timeRemaining );

    displayHandler( configPtr, fileWriteListPtr, outputStr, false, false ); 

    free( outputStr );

    return returnPtr; 
   }

/*
 * Function Name: getNextPID
 * Algorithm: keeps track of the pid by using a static variable to ensure 
 *            the process id of each process is always one more than the last
 * Precondition: None
 * Postcondition: PCB structs will be created with strictly linearly increasing pids
 * Exceptions: None
 * Notes: None
 */
int getNextPID()
   {
    static int pidCount = -1;
    pidCount++;
    return pidCount;
   }

/*
 * Function Name: getTimeFromOpCode
 * Algorithm: Given an op code, returns the time it will take in ms
 * Precondition: Valid op code
 * Postcondition: time in ms (int) returned
 * Exceptions: None
 * Notes: None
 */
int getTimeFromOpCode( ConfigDataType *configPtr, OpCodeType *opCodePtr )
   {
    // initialize function variables
    int multiplier = 1;
    char *cmd = opCodePtr->command;

    if( compareString( cmd, "dev" )== STR_EQ )
       {
        multiplier = configPtr->ioCycleRate;
        return ( long )( opCodePtr->intArg2 * multiplier );
       }
    else if( compareString( cmd, "cpu" ) == STR_EQ )
       {
        multiplier = configPtr->procCycleRate;
        return ( long )( opCodePtr->intArg2 * multiplier );
       }
    return 0; // if neither for now return 0
   }

/*
 * Function Name: initializeProcesses
 * Algorithm: Sets all PCBs to ready state and displays accordingly
 * Precondition: PCBs initialized
 * Postcondition: All processes in ready queue
 * Exceptions: None
 * Notes: None
 */
void initializeProcesses( ConfigDataType *configPtr, fileWriteList **fileWriteListPtr,
                          PCBLinkedList **PCBLinkedListPtr )
   {
     PCBLinkedList *pcbListPtr = *PCBLinkedListPtr;

     while( pcbListPtr != NULL && pcbListPtr->PCB->pid != -1 ) 
        {
         changeProcessState( configPtr, fileWriteListPtr, pcbListPtr->PCB, READY );
         pcbListPtr = ( pcbListPtr )->nextPCB;
        }
   }

/*
 * Function Name: printPCBList
 * Algorithm: prints out information about PCBs for testing purposes
 * Precondition: PCBLinkedList passed is not empty
 * Postcondition: Data about each PCB is shown
 * Exceptions: None
 * Notes: None
 */
void printPCBList( PCBLinkedList **pcbLLPtr )
   {
    PCBLinkedList *tempPtr = *pcbLLPtr;

    while( tempPtr != NULL )
       {
        if( tempPtr->PCB->pid != -1 )
           {
            // not used during simulation, only used for testing purposes
            fprintf( stderr, "Iter pointer location:\t%p\n", (void*)pcbLLPtr );
            printf( "Process:\t%d\n", (tempPtr)->PCB->pid );
            printf( "State:\t%d\n", (tempPtr)->PCB->state );
            printf( "Time Remaining:\t%d\n\n", (tempPtr)->PCB->timeRemaining );
            
            // step linked list pointer forward
            tempPtr = ( tempPtr )->nextPCB;
           }
        else
           {
            tempPtr = NULL;
           }
       }
   }

/*
 * Function Name: printWriteList
 * Algorithm: displays fileWriteList for testing purposes
 * Precondition: List has at least one node ( with END_OF_STRLIST as strToWrite )
 * Postcondition: Linked list of file display structs shown
 * Exceptions: None
 * Notes: None
 */
void printWriteList( fileWriteList **fileWriteListPtr )
   {
    // not used during simulation, only used for testing purposes
    fileWriteList *fileWriteList = *fileWriteListPtr;
    fprintf( stderr, "\nPrinting fileWriteList:\n" );

    while( compareString( fileWriteList->strToWrite, END_OF_STRLIST ) != STR_EQ )
       {
        fprintf( stderr, fileWriteList->strToWrite );
        fileWriteList = fileWriteList->nextStr;
       }

    fprintf( stderr, fileWriteList->strToWrite );
    fprintf( stderr, "\n" );
   }

/*
 * Function Name: scanForNewPCBs
 * Algorithm: looks through metadata file and creates a PCB struct for each app start
 * Precondition: given pointer to metadata file
 * Postcondition: all new PCBs have been created and initialized
 * Exceptions: None
 * Notes: None
 */
void scanForNewPCBs( OpCodeType **metaDataMstrPtr, PCBLinkedList **PCBLinkedListPtr, 
                     ConfigDataType *configPtr )
   {
    // initialize function variables
    PCBLinkedList *PCBLinkedListIterPtr = *PCBLinkedListPtr;  // create iter pointer to PCBLL
    OpCodeType *tempCode = *metaDataMstrPtr;   
    OpCodeType *appStartCode = *metaDataMstrPtr;
    int timeRemaining = 0;
   
    // loop through metadata
    while( tempCode != NULL )
       {
        // if start is found
        if( compareString(tempCode->command, "app" ) == STR_EQ )
           {
            if( compareString(tempCode->strArg1, "start" ) == STR_EQ )
               {
                appStartCode = tempCode;
               }
           }
        // if not app end
            // do the math, add the time for a given op code    create PCB after totaled timeRemaining?
        if( compareString( tempCode->command, "app" ) != STR_EQ ||
                        compareString(tempCode->strArg1, "end" ) != STR_EQ)
           {
            timeRemaining = timeRemaining + getTimeFromOpCode( configPtr, tempCode );
           }
        // else app end
        else if( compareString( tempCode->command, "app" ) == STR_EQ && 
                        compareString(tempCode->strArg1, "end") == STR_EQ )
           {
            // create new PCB node in linked list
                // function: createPCB
            createPCB( appStartCode, &PCBLinkedListIterPtr, timeRemaining );
            timeRemaining = 0;
           }
            
        // step op code
        tempCode = tempCode->nextNode;
       }
    // end loop through metadata
   }

/*
 * Function Name: runProcess
 * Algorithm: runs through op code operations by taking in PCB struct and 
 *            utilizing the program counter
 * Precondition: Process is in ready mode
 * Postcondition: Process has executed until app end or an interrupt
 * Exceptions: None
 * Notes: None
 */
PCB *runProcess( ConfigDataType *configPtr, fileWriteList **fileWriteList, 
                               PCBLinkedList *PCBLinkedListPtr, PCB *PCB )
   {
    // int to keep track of number of op codes
    // TODO: could cause issues with preemption, may need to add to PCB struct
    int count = 0;

    // set process to running
    changeProcessState( configPtr, fileWriteList, PCB, RUNNING );

    // initialize function variables
    OpCodeType *opCodePtr = PCB->programCounter;
    long opTime = 0;
    char *displayStr = ( char * ) malloc ( sizeof(char) * MAX_STR_LEN );
    char buffer[MAX_STR_LEN];

    // while op code is not app end
    while( compareString(opCodePtr->command, "app" ) != STR_EQ  || 
                    compareString(opCodePtr->strArg1, "end" ) != STR_EQ )
       {
        // get run time of op code 
        // function: getTimeFromOpCode
        opTime = getTimeFromOpCode( configPtr, opCodePtr );

        // output op code start
        sprintf( buffer, "Process: %d %s %s operation start\n", 
                        PCB->pid, opCodePtr->command, opCodePtr->strArg1 ); 
        copyString( displayStr, buffer );

        // if first op code of process, write newline
        if( count == 0 )
           {
            displayHandler( configPtr, fileWriteList, displayStr, false, true );
            count++;
           }
        else
           {
            displayHandler( configPtr, fileWriteList, displayStr, false, false );
            count++;
           }
        
        // start timer thread
        // function: runTimerFor
        runTimerFor( opTime );

        // output op code end
        sprintf( buffer, "Process: %d %s %s operation end\n", 
                        PCB->pid, opCodePtr->command, opCodePtr->strArg1 ); 
        copyString( displayStr, buffer );
        displayHandler( configPtr, fileWriteList, displayStr, false, false );
        
        // set next op code
        opCodePtr = opCodePtr->nextNode;

        // set pcb op code 
        PCB->programCounter = opCodePtr;
       }

    // write that process has ended
    sprintf( displayStr, "OS: Process %d ended\n", PCB->pid ); 
    displayHandler( configPtr, fileWriteList, displayStr, false, true );

    changeProcessState( configPtr, fileWriteList, PCB, EXIT );


    PCB->timeRemaining = 0;
    free( displayStr );

    return PCB; 
   }

/*
 * Function Name: runSim
 * Algorithm: master driver for simulator operations; conducts OS simulation 
 *            with varying scheduling strategies and varying numbers 
 *            of processes
 * Precondition: given head pointer to config data and meta data
 * Postcondition: simulation is provided, file output is provided as configured
 * Exceptions: None
 * Notes: None
 */
void runSim( ConfigDataType *configPtr, OpCodeType *metaDataMstrPtr )
   {
    // initialize function variables
    bool programEnding;
    PCB *currentProcess;

    // initialize PCBLinkedList node and PCB
    PCBLinkedList *PCBLinkedListPtr = ( PCBLinkedList * )malloc( sizeof( PCBLinkedList ) );
    PCBLinkedListPtr->PCB = ( PCB * )malloc( sizeof( PCB ) );

    // initialize timeStr to zero timer
    char *timeStr = ( char * )malloc( sizeof( char ) * MAX_STR_LEN );

    // initialize fileWriteList if logging to file
    fileWriteList *toFileList = ( fileWriteList * )malloc( sizeof( fileWriteList ) );
    // set end of list str for first node
    toFileList->strToWrite = END_OF_STRLIST;

    // TODO: check if -dm or -dc flags are used and output to file is selected
    //       if so add information to output file
    //       need to add boilerplate for beginning/ending of file as well

    // create all PCBs using helper function
    // function: scanForNewPCBs
    scanForNewPCBs( &metaDataMstrPtr, &PCBLinkedListPtr, configPtr );

    // start timer
    accessTimer( ZERO_TIMER, timeStr );

    displayHandler( configPtr, &toFileList, "OS: Simulator start\n", false, false );

    // search for processes in new and set them to ready
        // function: initializeProcesses
    initializeProcesses( configPtr, &toFileList, &PCBLinkedListPtr );

    // start main loop 
    programEnding = checkProgramEnd( &PCBLinkedListPtr );
    // while all processes are not in exit state
    while( !programEnding )
       {
        // find next process              TODO: more thorough state checking
            // function: chooseNextProcess
        currentProcess = chooseNextProcess( configPtr, &toFileList, PCBLinkedListPtr );
        if( currentProcess != NULL )
           {
            // run process
            // function: runProcess
            runProcess( configPtr, &toFileList, PCBLinkedListPtr, currentProcess );

            // TODO check for input (interrupts)
            // TODO check for output (interrupts)
            // TODO check for idle (no processes found / all processes are done)
            // TODO check for memory
            // TODO check for CPU
            // TODO check for and resolve interrupts ( sim04 )
            
            // check for app end ( all processes in exit state )
            programEnding = checkProgramEnd( &PCBLinkedListPtr );
           }
        else
           {
            programEnding = true;
           }
       }
    // end main loop
    displayHandler( configPtr, &toFileList, "OS: System stop\n", false, false );

    // free PCB struct memory
    // function: clearPCBList
    clearPCBList( PCBLinkedListPtr );

    displayHandler( configPtr, &toFileList, "OS: Simulation end\n", false, false );

    // if config specifies writing to file 
    if ( ( configPtr->logToCode == LOGTO_FILE_CODE ) 
           || ( configPtr->logToCode == LOGTO_BOTH_CODE ) )  
       {
        // write to file and free fileWriteList memory 
        // function: writeToFile
        writeToFile( configPtr, &toFileList );
       }

    free( timeStr );
   }

/*
 * Function Name: runTimerFor
 * Algorithm: creates a thread and sets limit for inputted milliseconds
 * Precondition: None
 * Postcondition: None
 * Exceptions: None
 * Notes: None
 */
void runTimerFor( long milliseconds )
   {
    // initialize threadInfo struct  
    threadInfo *timerThreadDataPtr = ( threadInfo * )malloc( sizeof( threadInfo ) );
    timerThreadDataPtr->milliseconds = milliseconds;
    timerThreadDataPtr->currentTime = 0;
    timerThreadDataPtr->timeString = malloc( 17 * sizeof( char ) );
    timerThreadDataPtr->outputTime = false;
    timerThreadDataPtr->keepGoing = true;

    void *threadExitStatus;

    pthread_t timerThread;
    
    // create pthread
    pthread_create( &timerThread, NULL, &runTimerThread, (void * )timerThreadDataPtr );
    
    // join pthread
    pthread_join( timerThread, &threadExitStatus );
    
    // free threadInfo struct
    free( timerThreadDataPtr );
   }

/*
 * Function Name: writeToFile
 * Algorithm: Destructive method that simultaneously writes from linked list 
 *            structure to file and frees memory from structures. Intended for 
 *            use at the end of the program
 * Precondition: Config file must specify log to file or both
 * Postcondition: File specified written to and data structure will be cleared,
 *                returns NULL
 * Exceptions: None
 * Notes: None
 */
fileWriteList *writeToFile( ConfigDataType *configPtr, fileWriteList **printList )
   {
    // initialize function variables
    char *filename = ( char * )configPtr->logToFileName;
    fileWriteList *fileWriteListPtr = *printList;

    // open file
    FILE *filePtr = fopen( filename, "w" );

    // if opening file fails, exit with error message
    if( filePtr == NULL )
       {
        displayHandler( configPtr, NULL, "File creation has failed, aborting operation\n", true, false );
        exit(-1);
       }

    // write linked list to file
    while( compareString( fileWriteListPtr->strToWrite, END_OF_STRLIST ) != STR_EQ )
       {
        // output to file
        fprintf( filePtr, "%s", fileWriteListPtr->strToWrite );
        
        // keep track of next location
        fileWriteListPtr = fileWriteListPtr->nextStr;
       }

    // close the file
    fclose( filePtr );

    // free linked list with recursive helper method
    // function: clearWriteList
    clearWriteList( printList );

    // return empty fileWriteList
    return NULL;
   }