/*========================== begin_copyright_notice ============================
Copyright (C) 2022-2024 Intel Corporation

SPDX-License-Identifier: MIT
============================= end_copyright_notice ===========================*/

/*!
 * @file Utility for filtering instruction-level and BBL-level instrumentation sites
 */

#include "gtpin_api.h"
#include "gtpin_tool_utils.h"
#include "gt_basic_defs.h"

#if defined (TARGET_WINDOWS)    
    #include <fcntl.h>
    #include <share.h>
    #include <sys/stat.h>
    #include <io.h>

#elif defined (TARGET_LINUX)
    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/file.h>
#endif

/* ===================================================================================================== */
// Configuration options
/* ===================================================================================================== */
#define MAX_INST_DEAFULT 100000000
Knob<int>  knobMinInstrumentBbl("min_instrument_bbl", 0, "Min BBL ID to be instrumented");
Knob<int>  knobMaxInstrumentBbl("max_instrument_bbl", MAX_INST_DEAFULT, "Max BBL ID to be instrumented");
Knob<int>  knobMinInstrumentIns("min_instrument_ins", 0, "Min INS ID to be instrumented");
Knob<int>  knobMaxInstrumentIns("max_instrument_ins", MAX_INST_DEAFULT, "Max INS ID to be instrumented");
Knob<int>  knobMaxOrigIns("max_orig_ins", MAX_INST_DEAFULT, "Max original kerenl instructions", KNOB_FLAG_INTERNAL);

/* ===================================================================================================== */
// GtTool implementation
/* ===================================================================================================== */
bool GtTool::HandleCommonInstrumnetationKnobs(IGtKernelInstrument& instrumentor)
{
    // Static variable to track EOT instrumantation
    static bool eotInstrumneted = false;
    // If max_orig_inst was used, instrument a EOT instruction in knobMaxOrigIns instruction number
    if (uint32_t(knobMaxOrigIns) < MAX_INST_DEAFULT && !eotInstrumneted)
    {
        const IGtCfg& cfg = instrumentor.Cfg();
        const IGtIns* eotins = nullptr;

        // Locate EOT instruction in kernel exits
        for (auto bblPtr : cfg.ExitBbls())
        {
            eotins = &(bblPtr->LastIns());
            GTPIN_ASSERT(eotins->IsEot());
            break;
        }

        GTPIN_ASSERT(eotins != nullptr);
        // Make a duplicate of the EOT instruction
        IGtInsFactory& insF = instrumentor.Coder().InstructionFactory();
        IGtIns& ins = insF.Make(*eotins);

        for (auto bblPtr : cfg.Bbls())
        {
            for (auto insPtr : bblPtr->Instructions())
            {
                if (uint32_t(knobMaxOrigIns) < insPtr->Id())
                {
                    eotInstrumneted = true;
                    return instrumentor.InstrumentInstruction(*insPtr, GtIpoint::Before(), GtGenProcedure({&ins}));
                }
            }
        }
    }

    // otherwise, skip the instrumentation
    return false;
}


bool GtTool::InstrumentInstruction(IGtKernelInstrument& instrumentor, const IGtIns& ins, GtIpoint ipoint, const IGtGenProcedure& proc)
{
    uint32_t bblId = instrumentor.Cfg().GetBbl(ins).Id();
    uint32_t insId = ins.Id();

    HandleCommonInstrumnetationKnobs(instrumentor);
    // check that the BBL containing the current instruction has an ID within the range [knobMinInstrumentBbl, knobMaxInstrumentBbl]
    if ((uint32_t)knobMinInstrumentBbl <= bblId && bblId <= (uint32_t)knobMaxInstrumentBbl)
    {
        // check that the current instruction has an ID within the range [knobMinInstrumentIns, knobMaxInstrumentIns]
        if ((uint32_t)knobMinInstrumentIns <= insId && insId <= (uint32_t)knobMaxInstrumentIns)
        {
            // perform instrumentation if both conditions are met
            return instrumentor.InstrumentInstruction(ins, ipoint, proc);
        }
    }

    // otherwise, skip the instrumentation
    return false;
}

bool GtTool::InstrumentBbl(IGtKernelInstrument& instrumentor, const IGtBbl& bbl, GtIpoint ipoint, const IGtGenProcedure& proc)
{
    uint32_t bblId = bbl.Id();
    uint32_t firstInsId = bbl.FirstIns().Id();
    uint32_t lastInsId  = bbl.LastIns().Id();

    HandleCommonInstrumnetationKnobs(instrumentor);
    // check that the current BBL has an ID within the range [knobMinInstrumentBbl, knobMaxInstrumentBbl]
    if ((uint32_t)knobMinInstrumentBbl <= bblId && bblId <= (uint32_t)knobMaxInstrumentBbl)
    {
        // if the instrumentation is inserted before the BBL
        if (ipoint == GtIpoint::Before())
        {
            // check that the first instruction of the current BBL has an ID within the range [knobMinInstrumentIns, knobMaxInstrumentIns]
            if ((uint32_t)knobMinInstrumentIns <= firstInsId && firstInsId <= (uint32_t)knobMaxInstrumentIns)
            {
                // perform instrumentation if both conditions are met
                return instrumentor.InstrumentBbl(bbl, ipoint, proc);
            }
        }
        // if the instrumentation is inserted after the BBL
        if (ipoint == GtIpoint::After())
        {
            // check that the last instruction of the current BBL has an ID within the range [knobMinInstrumentIns, knobMaxInstrumentIns]
            if ((uint32_t)knobMinInstrumentIns <= lastInsId && lastInsId <= (uint32_t)knobMaxInstrumentIns)
            {
                // perform instrumentation if both conditions are met
                return instrumentor.InstrumentBbl(bbl, ipoint, proc);
            }
        }
    }

    // otherwise, skip the instrumentation
    return false;
}

bool GtTool::InstrumentEdge(IGtKernelInstrument& instrumentor, const IGtEdge& edge, const IGtGenProcedure& proc)
{
    return instrumentor.InstrumentEdge(edge, proc);
}

/* ===================================================================================================== /
// LockableFile implementation
/ ===================================================================================================== */
bool LockableFile::Write(const std::string& content, bool append)
{
#if defined (TARGET_WINDOWS)
    // Open the file
    int fd;
    FILE* file;

    if (append)
    {
        errno_t err = _sopen_s(&fd, filePath.c_str(), O_WRONLY  | _O_CREAT | _O_APPEND, _SH_DENYNO, _S_IWRITE);
        if (err != 0) {
            GTPIN_WARNING(" Unable to open file " + filePath);
            return false;
        }
        file = _fdopen(fd, "a");
    }
    else
    {
        errno_t err = _sopen_s(&fd, filePath.c_str(), O_WRONLY | _O_CREAT | O_TRUNC, _SH_DENYNO,  _S_IWRITE);
        if (err != 0) {
            GTPIN_WARNING("Unable to open file " + filePath);
            return false;
        }
        file = _fdopen(fd, "w");
    }

    if(file == nullptr)
    {
        GTPIN_WARNING("Unable to open file " + filePath);
        return false;
    }

    // Lock the file
    _lock_file(file);

    // // Write to the file
    fprintf(file, "%s", content.c_str());

    // Unlock the file
    _unlock_file(file);

    // Close the file
    fclose(file);

#elif defined (TARGET_LINUX)

    // Open the file on append mode 
    int fd = open(filePath.c_str(), O_RDWR | O_CREAT , 0666 );
    if (fd == -1) 
    {
        GTPIN_WARNING("Unable to open file: " + filePath);
        return false;
    }

    std::ofstream file;
    if (append)
    {
        file.open(filePath, std::ios::out | std::ios::app);
    }
    else
    {
        file.open(filePath, std::ios::out | std::ios::trunc);
    }
    
    if (!file.is_open()) 
    {
        GTPIN_WARNING("Unable to open file stream for: " + filePath);
        close(fd);
        return false;
    }

    // Lock the file for writing
    if (flock(fd, LOCK_EX) == -1) 
    {
        GTPIN_WARNING("Unable to acquire lock");
        // not exiting, let it write to file anyway
    }

    // Write content to file
    file << content;
    
    // Unlock the file
    if (flock(fd, LOCK_UN) == -1) 
    {
        GTPIN_WARNING("Unable to release lock");
    }

    // Close the file
    file.close();
    close(fd);


#endif
    return true;
}
    
std::string LockableFile::Read(int bytes, int offset) const
{
#if defined (TARGET_WINDOWS)
    // Open the file
    int fd;
    FILE* file;
    errno_t err = _sopen_s(&fd, filePath.c_str(), _O_RDONLY | _O_CREAT, _SH_DENYNO, _S_IREAD );
    if (err != 0) 
    {
        GTPIN_WARNING("Unable to open file " + filePath);
        return "";
    }
    file = _fdopen(fd, "r");
    
    if(file == nullptr)
    {
        GTPIN_WARNING("Unable to open file " + filePath);
        return "";
    }

    // Lock the file
    _lock_file(file);

    //  Set offset for reading
    if (offset != 0)
    {
        if (fseek(file, offset, SEEK_SET) != 0)
        {
           GTPIN_WARNING("Failed to open file with offset " + std::to_string(offset));    
        }
    }

    // read content
    char* buffer = new char[bytes + 1];
    size_t bytesRead = fread(buffer, 1, bytes, file);
    buffer[bytesRead] = '\0';

    // Unlock the file
    _unlock_file(file);

    // Close the file
    fclose(file);

    std::string content(buffer, bytesRead);
    delete[] buffer;

    return content;

#elif defined (TARGET_LINUX)

    // Open the file on append mode 
    int fd = open(filePath.c_str(), O_RDWR | O_CREAT , 0666 );
    if (fd == -1) 
    {
        GTPIN_WARNING("Unable to open file: " + filePath);
        return "";
    }
    
    std::ifstream file(filePath);
    
    
    if (!file.is_open()) 
    {
        GTPIN_WARNING("Unable to open file stream for: " + filePath);
        close(fd);
        return "";
    }

    // Lock the file for writing
    if (flock(fd, LOCK_EX) == -1) 
    {
        GTPIN_WARNING("Unable to acquire lock");
        // not exiting, let it write to file anyway
    }


    if (offset != 0)
    {
        file.seekg(offset, std::ios::beg);
    }
    
    // allocate buffer and read content
    char * buffer = new char[bytes + 1];
    file.read(buffer, bytes);
    size_t bytesRead = file.gcount();
    buffer[bytesRead] = '\0';

    // Unlock the file
    if (flock(fd, LOCK_UN) == -1) 
    {
        GTPIN_WARNING("Unable to release lock");
    }

    // Close the file
    file.close();
    close(fd);

    std::string content(buffer, bytesRead);
    delete[] buffer;
    
    return content;
#endif
    return "";
}

std::string LockableFile::FilePath() const
{
    return filePath;
}

bool CreateCleanFile(const std::string& filePath)
{
    std::ofstream file(filePath.c_str(), std::ofstream::out | std::ofstream::trunc);
    if (!file.is_open()) 
    {
            return false;
    }
    file.close();
    return true;
}
