// SPDX-License-Identifier: MPL-2.0
//
// Do NOT modify or remove this copyright and license
//
// Copyright (c) 2012-2026 Seagate Technology LLC and/or its Affiliates, All Rights Reserved
//
// This software is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// ******************************************************************************************
//
// \file sanitize.c
// \brief This file defines the functions for sanitize operations on SCSI and ATA drives

#include "bit_manip.h"
#include "code_attributes.h"
#include "common_types.h"
#include "error_translation.h"
#include "io_utils.h"
#include "math_utils.h"
#include "memory_safety.h"
#include "precision_timer.h"
#include "sleep.h"
#include "string_utils.h"
#include "time_utils.h"
#include "type_conversion.h"

#include "operations_Common.h"
#include "platform_helper.h"
#include "sanitize.h"

static eReturnValues get_ATA_Sanitize_Progress(const tDevice*   device,
                                               double*          percentComplete,
                                               eSanitizeStatus* sanitizeStatus)
{
    eReturnValues result             = ata_Sanitize_Status(device, false);
    uint16_t      ataPercentComplete = UINT16_C(0);
    if (result == SUCCESS)
    {
        ataPercentComplete =
            M_BytesTo2ByteValue(device->drive_info.lastCommandRTFRs.lbaMid, device->drive_info.lastCommandRTFRs.lbaLow);
        if (device->drive_info.lastCommandRTFRs.secCntExt & BIT7)
        {
            *sanitizeStatus = SANITIZE_STATUS_SUCCESS;
        }
        else if (device->drive_info.lastCommandRTFRs.secCntExt & BIT6)
        {
            *sanitizeStatus = SANITIZE_STATUS_IN_PROGRESS;
        }
        else if (device->drive_info.lastCommandRTFRs.secCntExt & BIT5)
        {
            *sanitizeStatus = SANITIZE_STATUS_FROZEN;
        }
        else if (device->drive_info.lastCommandRTFRs.secCntExt & BIT4)
        {
            *sanitizeStatus = SANITIZE_STATUS_FREEZELOCK_FAILED_DUE_TO_ANTI_FREEZE_LOCK;
        }
        // NOTE: If more status bits are added to the spec, then we will need to add support for detecting them here.
        else if (get_bit_range_uint8(device->drive_info.lastCommandRTFRs.secCntExt, 4, 0) != 0)
        {
            // unknown status
            *sanitizeStatus = SANITIZE_STATUS_UNKNOWN;
        }
        else
        {
            *sanitizeStatus = SANITIZE_STATUS_NEVER_SANITIZED;
        }
    }
    else if (result != OS_COMMAND_BLOCKED && result != OS_PASSTHROUGH_FAILURE && result != OS_COMMAND_NOT_AVAILABLE)
    {
        // need to check if there was a reason reported for failing this command.
        // first check that the abort bit was set because if that isn't there, then we won't be able to identify a
        // reason due to a SATL issue
        if (device->drive_info.lastCommandRTFRs.error & ATA_ERROR_BIT_ABORT)
        {
            // LBA Low will have the failure reason, if any
            switch (device->drive_info.lastCommandRTFRs.lbaLow)
            {
            case 0:
                *sanitizeStatus = SANITIZE_STATUS_FAILED;
                break;
            case 1:
                *sanitizeStatus = SANITIZE_STATUS_FAILED_PHYSICAL_SECTORS_REMAIN;
                break;
            case 2:
                *sanitizeStatus = SANITIZE_STATUS_UNSUPPORTED_FEATURE;
                break;
            case 3:
                *sanitizeStatus = SANITIZE_STATUS_FROZEN;
                break;
            case 4:
                *sanitizeStatus = SANITIZE_STATUS_FREEZELOCK_FAILED_DUE_TO_ANTI_FREEZE_LOCK;
                break;
            default:
                *sanitizeStatus = SANITIZE_STATUS_UNKNOWN;
                break;
            }
            result = SUCCESS; // seems weird but may get around an unknown progress later
        }
        else
        {
            *sanitizeStatus = SANITIZE_STATUS_UNKNOWN;
        }
    }
    *percentComplete = get_SCSI_Progress_Indicator_PercentD(ataPercentComplete);
    return result;
}

static eReturnValues get_NVMe_Sanitize_Progress(const tDevice*   device,
                                                double*          percentComplete,
                                                eSanitizeStatus* sanitizeStatus)
{
    eReturnValues result = UNKNOWN;
    // read the sanitize status log
    DECLARE_ZERO_INIT_ARRAY(uint8_t, sanitizeStatusLog, 512);
    nvmeGetLogPageCmdOpts getLogOpts;
    uint16_t              sprog = UINT16_C(0);
    uint16_t              sstat = UINT16_C(0);
    safe_memset(&getLogOpts, sizeof(nvmeGetLogPageCmdOpts), 0, sizeof(nvmeGetLogPageCmdOpts));
    getLogOpts.dataLen = 512;
    getLogOpts.lid     = 0x81;
    getLogOpts.addr    = sanitizeStatusLog;
    if (SUCCESS == nvme_Get_Log_Page(device, &getLogOpts))
    {
        result = SUCCESS;
        sprog  = M_BytesTo2ByteValue(sanitizeStatusLog[1], sanitizeStatusLog[0]);
        sstat  = M_BytesTo2ByteValue(sanitizeStatusLog[3], sanitizeStatusLog[2]);

        switch (get_8bit_range_uint16(sstat, 2, 0))
        {
        case 0:
            *sanitizeStatus = SANITIZE_STATUS_NEVER_SANITIZED;
            break;
        case 1:
            *sanitizeStatus = SANITIZE_STATUS_SUCCESS;
            break;
        case 2:
            *sanitizeStatus = SANITIZE_STATUS_IN_PROGRESS;
            break;
        case 3:
            *sanitizeStatus = SANITIZE_STATUS_FAILED;
            break;
        default:
            *sanitizeStatus = SANITIZE_STATUS_UNKNOWN;
            break;
        }
    }
    else
    {
        result = NOT_SUPPORTED;
    }
    *percentComplete = get_SCSI_Progress_Indicator_PercentD(sprog);
    return result;
}

static eReturnValues get_SCSI_Sanitize_Progress(const tDevice*   device,
                                                double*          percentComplete,
                                                eSanitizeStatus* sanitizeStatus)
{
    DECLARE_ZERO_INIT_ARRAY(uint8_t, req_sense_buf, SPC3_SENSE_LEN);
    uint8_t       acq                   = UINT8_C(0);
    uint8_t       ascq                  = UINT8_C(0);
    uint8_t       senseKey              = UINT8_C(0);
    uint8_t       fru                   = UINT8_C(0);
    uint16_t      scsiProgressIndicator = UINT16_C(0);
    eReturnValues result                = scsi_Request_Sense_Cmd(
        device, false, req_sense_buf,
        SPC3_SENSE_LEN); // get fixed format sense data to make this easier to parse the progress from.
    get_Sense_Key_ASC_ASCQ_FRU(&req_sense_buf[0], SPC3_SENSE_LEN, &senseKey, &acq, &ascq, &fru);
    result = check_Sense_Key_ASC_ASCQ_And_FRU(device, senseKey, acq, ascq, fru);
    // set this for now. It will be changed below if necessary.
    *sanitizeStatus = SANITIZE_STATUS_NOT_IN_PROGRESS;
    if (result == SUCCESS || result == IN_PROGRESS)
    {
        if (acq == 0x04 && ascq == 0x1B) // this is making sure that a sanitize command is in progress
        {
            *sanitizeStatus = SANITIZE_STATUS_IN_PROGRESS;
            scsiProgressIndicator =
                M_BytesTo2ByteValue(req_sense_buf[16], req_sense_buf[17]); // sense key specific information
        }
    }
    else
    {
        if (acq == 0x31 && ascq == 0x03)
        {
            // sanitize command failed
            *sanitizeStatus = SANITIZE_STATUS_FAILED;
        }
    }
    *percentComplete = get_SCSI_Progress_Indicator_PercentD(scsiProgressIndicator);
    return result;
}

eReturnValues get_Sanitize_Progress(const tDevice* device, double* percentComplete, eSanitizeStatus* sanitizeStatus)
{
    eReturnValues result = UNKNOWN;
    *sanitizeStatus      = 0;
    switch (device->drive_info.drive_type)
    {
    case ATA_DRIVE:
        result = get_ATA_Sanitize_Progress(device, percentComplete, sanitizeStatus);
        break;
    case NVME_DRIVE:
        result = get_NVMe_Sanitize_Progress(device, percentComplete, sanitizeStatus);
        break;
    case SCSI_DRIVE:
        result = get_SCSI_Sanitize_Progress(device, percentComplete, sanitizeStatus);
        break;
    default:
        if (VERBOSITY_QUIET < device->deviceVerbosity)
        {
            print_str("Not supported on this device type at this time");
        }
        return NOT_SUPPORTED;
    }
    return result;
}

static void print_Sanitize_Status_To_Screen(eSanitizeStatus sanitizeInProgress, double percentComplete)
{
    if (sanitizeInProgress == SANITIZE_STATUS_IN_PROGRESS)
    {
        printf("\tSanitize Progress = %3.2f%% \n", percentComplete);
    }
    else if (sanitizeInProgress == SANITIZE_STATUS_NOT_IN_PROGRESS)
    {
        print_str("\tSanitize command is not currently in progress. It is either complete or has not been run.\n");
    }
    else if (sanitizeInProgress == SANITIZE_STATUS_NEVER_SANITIZED)
    {
        print_str("\tThis device has never been sanitized.\n");
    }
    else if (sanitizeInProgress == SANITIZE_STATUS_SUCCESS)
    {
        print_str("\tThe last sanitize operation completed successfully\n");
    }
    else if (sanitizeInProgress == SANITIZE_STATUS_FROZEN)
    {
        print_str("\tSanitize is frozen on this device. It must be power cycled to clear the freeze lock.\n");
    }
    else if (sanitizeInProgress == SANITIZE_STATUS_UNSUPPORTED_FEATURE)
    {
        print_str("\tThe last sanitize command specified an unsupported sanitize mode.\n");
    }
    else if (sanitizeInProgress == SANITIZE_STATUS_FREEZELOCK_FAILED_DUE_TO_ANTI_FREEZE_LOCK)
    {
        print_str("\tSanitize freezelock command failed due to anti-freezelock.\n");
    }
    else if (sanitizeInProgress == SANITIZE_STATUS_FAILED)
    {
        print_str("\tSanitize command failed!\n");
    }
    else if (sanitizeInProgress == SANITIZE_STATUS_FAILED_PHYSICAL_SECTORS_REMAIN)
    {
        // ATA- Completed with physical sectors that are available to be allocated for user data that were not
        // successfully sanitized
        print_str(
            "\tSanitize command failed: completed with physical sectors that are available for user data and were "
            "not successfully sanitized!\n");
    }
    else
    {
        print_str("\tError occurred while retrieving sanitize progress!\n");
    }
}

eReturnValues show_Sanitize_Progress(const tDevice* device)
{
    eReturnValues   ret                = UNKNOWN;
    double          percentComplete    = 0.0;
    eSanitizeStatus sanitizeInProgress = 0;

    ret = get_Sanitize_Progress(device, &percentComplete, &sanitizeInProgress);

    print_Sanitize_Status_To_Screen(sanitizeInProgress, percentComplete);

    return ret;
}

eReturnValues get_ATA_Sanitize_Device_Features(const tDevice* device, sanitizeFeaturesSupported* sanitizeOptions)
{
    eReturnValues ret = FAILURE;
    if (le16_to_host(device->drive_info.IdentifyData.ata.Word255) == 0)
    {
        DECLARE_ZERO_INIT_ARRAY(uint8_t, iddata, LEGACY_DRIVE_SEC_SIZE);
        ret = ata_Identify(device, iddata, LEGACY_DRIVE_SEC_SIZE);
    }
    else
    {
        ret = SUCCESS;
    }
    if (ret == SUCCESS)
    {
        if (is_ATA_Identify_Word_Valid(le16_to_host(device->drive_info.IdentifyData.ata.Word059)) &&
            le16_to_host(device->drive_info.IdentifyData.ata.Word059) & ATA_IDENTIFY_SANITIZE_SUPPORTED)
        {
            sanitizeOptions->sanitizeCmdEnabled = true;
            sanitizeOptions->exitFailMode       = true;
            sanitizeOptions->freezelock         = true;
            if (le16_to_host(device->drive_info.IdentifyData.ata.Word059) & ATA_IDENTIFY_CRYPTO_SUPPORTED)
            {
                sanitizeOptions->crypto = true;
            }
            if (le16_to_host(device->drive_info.IdentifyData.ata.Word059) & ATA_IDENTIFY_OVERWRITE_SUPPORTED)
            {
                sanitizeOptions->overwrite              = true;
                sanitizeOptions->maximumOverwritePasses = ATA_NVME_MAX_SANITIZE_OVERWRITE_PASSES;
            }
            if (le16_to_host(device->drive_info.IdentifyData.ata.Word059) & ATA_IDENTIFY_BLOCK_ERASE_SUPPORTED)
            {
                sanitizeOptions->blockErase = true;
            }
            // bit 10 = antifreeze-lock command supported
            // ata identify device data log also contains "Definitive ending pattern supported" bit on capabilities page
            // ata identify device data log sets copies of word 59 on security page.
            // another bit describes commands allowed matching ACS-2 or not. Modern drives should set this to one to
            // match current standards. ACS-2 may set this to zero
            if (le16_to_host(device->drive_info.IdentifyData.ata.Word059) & BIT10)
            {
                sanitizeOptions->antiFreezeLock = true;
            }
            sanitizeOptions->writeAfterCryptoErase = WAEREQ_NOT_SPECIFIED; // or WAEREQ_READ_COMPLETES_GOOD_STATUS???
            sanitizeOptions->writeAfterBlockErase  = WAEREQ_NOT_SPECIFIED; // or WAEREQ_READ_COMPLETES_GOOD_STATUS???
        }
    }
    else
    {
#if defined(_DEBUG)
        printf("ATA IDENTIFY Failed %d\n", ret);
#endif
    }

    return ret;
}

eReturnValues get_SCSI_Sanitize_Supported_Features(const tDevice* device, sanitizeFeaturesSupported* sanitizeOptions)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.scsiVersion >=
        SCSI_VERSION_SPC_3) // check for this version of SPC first since the report supported Operation codes and
                            // Sanitize command should only be on drives with this version or highter.
    {
        if (device->drive_info.passThroughHacks.scsiHacks.noReportSupportedOperations)
        {
            return NOT_SUPPORTED;
        }
        scsiOperationCodeInfoRequest sanitizeSupReq;
        safe_memset(&sanitizeSupReq, sizeof(scsiOperationCodeInfoRequest), 0, sizeof(scsiOperationCodeInfoRequest));
        sanitizeSupReq.operationCode      = SANITIZE_CMD;
        sanitizeSupReq.serviceActionValid = true;
        sanitizeSupReq.serviceAction      = SCSI_SANITIZE_OVERWRITE;
        eSCSICmdSupport sanitizeSupport   = is_SCSI_Operation_Code_Supported(device, &sanitizeSupReq);
        if (sanitizeSupport == SCSI_CMD_SUPPORT_SUPPORTED_TO_SCSI_STANDARD)
        {
            sanitizeOptions->definitiveEndingPattern = true;
            sanitizeOptions->sanitizeCmdEnabled      = true;
            sanitizeOptions->overwrite               = true;
            if (strstr("ATA", device->drive_info.T10_vendor_ident) ||
                strstr("NVMe", device->drive_info.T10_vendor_ident))
            {
                // Assuming that only a compliant translator will support this, so screening for ATA and NVMe which
                // are limited to 16 passes
                sanitizeOptions->maximumOverwritePasses = ATA_NVME_MAX_SANITIZE_OVERWRITE_PASSES;
            }
            else
            {
                sanitizeOptions->maximumOverwritePasses = SCSI_MAX_SANITIZE_OVERWRITE_PASSES;
            }
            ret = SUCCESS;
        }
        sanitizeSupReq.serviceAction = SCSI_SANITIZE_BLOCK_ERASE;
        sanitizeSupport              = is_SCSI_Operation_Code_Supported(device, &sanitizeSupReq);
        if (sanitizeSupport == SCSI_CMD_SUPPORT_SUPPORTED_TO_SCSI_STANDARD)
        {
            sanitizeOptions->sanitizeCmdEnabled = true;
            sanitizeOptions->blockErase         = true;
            ret                                 = SUCCESS;
        }
        sanitizeSupReq.serviceAction = SCSI_SANITIZE_CRYPTOGRAPHIC_ERASE;
        sanitizeSupport              = is_SCSI_Operation_Code_Supported(device, &sanitizeSupReq);
        if (sanitizeSupport == SCSI_CMD_SUPPORT_SUPPORTED_TO_SCSI_STANDARD)
        {
            sanitizeOptions->sanitizeCmdEnabled = true;
            sanitizeOptions->crypto             = true;
            ret                                 = SUCCESS;
        }
        sanitizeSupReq.serviceAction = SCSI_SANITIZE_EXIT_FAILURE_MODE;
        sanitizeSupport              = is_SCSI_Operation_Code_Supported(device, &sanitizeSupReq);
        if (sanitizeSupport == SCSI_CMD_SUPPORT_SUPPORTED_TO_SCSI_STANDARD)
        {
            sanitizeOptions->sanitizeCmdEnabled = true;
            sanitizeOptions->exitFailMode       = true;
            ret                                 = SUCCESS;
        }
        else
        {
            sanitizeOptions->exitFailMode = false;
        }
        writeAfterErase writeAfterEraseRequirements;
        safe_memset(&writeAfterEraseRequirements, sizeof(writeAfterErase), 0, sizeof(writeAfterErase));
        if (SUCCESS == is_Write_After_Erase_Required(device, &writeAfterEraseRequirements))
        {
            sanitizeOptions->writeAfterBlockErase  = writeAfterEraseRequirements.blockErase;
            sanitizeOptions->writeAfterCryptoErase = writeAfterEraseRequirements.cryptoErase;
        }
        if (sanitizeOptions->sanitizeCmdEnabled)
        {
            sanitizeOptions->antiFreezeLock = false;
            sanitizeOptions->freezelock     = false;
        }
    }
    return ret;
}

eReturnValues get_NVMe_Sanitize_Supported_Features(const tDevice* device, sanitizeFeaturesSupported* sanitizeOptions)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (le32_to_host(device->drive_info.IdentifyData.nvme.ctrl.sanicap) > 0)
    {
        ret                                 = SUCCESS;
        sanitizeOptions->sanitizeCmdEnabled = true;
        sanitizeOptions->exitFailMode       = true;
        sanitizeOptions->antiFreezeLock     = false;
        sanitizeOptions->freezelock         = false;
        // Cntl identify bytes 331:328
        // BIT0 = crypto
        // bit1 = block erase
        // bit 2 = overwrite
        if (le32_to_host(device->drive_info.IdentifyData.nvme.ctrl.sanicap) & BIT0)
        {
            sanitizeOptions->crypto = true;
        }
        if (le32_to_host(device->drive_info.IdentifyData.nvme.ctrl.sanicap) & BIT1)
        {
            sanitizeOptions->blockErase = true;
        }
        if (le32_to_host(device->drive_info.IdentifyData.nvme.ctrl.sanicap) & BIT2)
        {
            sanitizeOptions->overwrite               = true;
            sanitizeOptions->definitiveEndingPattern = true;
            sanitizeOptions->maximumOverwritePasses  = ATA_NVME_MAX_SANITIZE_OVERWRITE_PASSES;
        }
        if (le32_to_host(device->drive_info.IdentifyData.nvme.ctrl.sanicap) & BIT29)
        {
            sanitizeOptions->noDeallocateInhibited = true;
        }
        sanitizeOptions->nodmmas =
            C_CAST(noDeallocateModifiesAfterSanitize,
                   get_8bit_range_uint32(le32_to_host(device->drive_info.IdentifyData.nvme.ctrl.sanicap), 31, 30));
        sanitizeOptions->writeAfterCryptoErase = WAEREQ_NOT_SPECIFIED; // or WAEREQ_READ_COMPLETES_GOOD_STATUS???
        sanitizeOptions->writeAfterBlockErase  = WAEREQ_NOT_SPECIFIED; // or WAEREQ_READ_COMPLETES_GOOD_STATUS???
        if (sanitizeOptions->noDeallocateInhibited)
        {
            // get the sanitize config feature status to know which mode it is operating in.
            nvmeFeaturesCmdOpt feat;
            safe_memset(&feat, sizeof(nvmeFeaturesCmdOpt), 0, sizeof(nvmeFeaturesCmdOpt));
            feat.fid        = NVME_FEAT_SANITIZE_CONFIG_;
            feat.nsid       = NVME_ALL_NAMESPACES;
            feat.sel        = NVME_CURRENT_FEAT_SEL;
            feat.dataPtr    = M_NULLPTR;
            feat.dataLength = 0;
            // reported in completion dword 0
            if (SUCCESS == nvme_Get_Features(device, &feat))
            {
                if (device->drive_info.lastNVMeResult.lastNVMeCommandSpecific & BIT0)
                {
                    sanitizeOptions->responseMode = NO_DEALLOC_RESPONSE_WARNING;
                }
                else
                {
                    sanitizeOptions->responseMode = NO_DEALLOC_RESPONSE_ERROR;
                }
            }
            else
            {
                sanitizeOptions->responseMode = NO_DEALLOC_RESPONSE_INV;
            }
        }
    }
    return ret;
}

eReturnValues get_Sanitize_Device_Features(const tDevice* device, sanitizeFeaturesSupported* opts)
{
    eReturnValues ret = UNKNOWN;
    switch (device->drive_info.drive_type)
    {
    case NVME_DRIVE:
        ret = get_NVMe_Sanitize_Supported_Features(device, opts);
        break;
    case SCSI_DRIVE:
        ret = get_SCSI_Sanitize_Supported_Features(device, opts);
        break;
    case ATA_DRIVE:
        ret = get_ATA_Sanitize_Device_Features(device, opts);
        break;
    default:
        ret = NOT_SUPPORTED;
        break;
    }

    // NOTE: Quick hack to disable block erase and sanitize command on Rugged SSD4 devices
    //       This is here because of some strange behavior when issued that is still under investigation.
    if (strcasecmp("Rugged SSD4", device->drive_info.product_identification) == 0 && opts != M_NULLPTR)
    {
        opts->blockErase         = false;
        opts->sanitizeCmdEnabled = false;
    }

    return ret;
}

eReturnValues sanitize_Freezelock(const tDevice* device)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        ret = ata_Sanitize_Freeze_Lock(device);
        if (ret != SUCCESS)
        {
            if (device->drive_info.lastCommandRTFRs.lbaLow == 0x02)
            {
                ret = NOT_SUPPORTED;
            }
            else if (device->drive_info.lastCommandRTFRs.lbaLow == 0x03)
            {
                ret = FROZEN;
            }
            else if (device->drive_info.lastCommandRTFRs.lbaLow == 0x04)
            {
                ret = ABORTED;
            }
            else // || device->drive_info.lastCommandRTFRs.lbaLow == 0x00
            {
                ret = FAILURE;
            }
        }
    }
    return ret;
}

eReturnValues sanitize_Anti_Freezelock(const tDevice* device)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        ret = ata_Sanitize_Anti_Freeze_Lock(device);
        if (ret != SUCCESS)
        {
            if (device->drive_info.lastCommandRTFRs.lbaLow == 0x02)
            {
                ret = NOT_SUPPORTED;
            }
            else if (device->drive_info.lastCommandRTFRs.lbaLow == 0x03)
            {
                ret = FROZEN;
            }
            else if (device->drive_info.lastCommandRTFRs.lbaLow == 0x04)
            {
                ret = ABORTED;
            }
            else // || device->drive_info.lastCommandRTFRs.lbaLow == 0x00
            {
                ret = FAILURE;
            }
        }
    }
    return ret;
}

eReturnValues run_Sanitize_Operation(const tDevice*      device,
                                     eSanitizeOperations sanitizeOperation,
                                     bool                pollForProgress,
                                     uint8_t*            pattern,
                                     uint32_t            patternLength)
{
    // convert to calling new functions since this one is obsolete.
    sanitizeOperationOptions sanitizeOptions;
    safe_memset(&sanitizeOptions, sizeof(sanitizeOperationOptions), 0, sizeof(sanitizeOperationOptions));
    sanitizeOptions.version                                     = SANITIZE_OPERATION_OPTIONS_VERSION;
    sanitizeOptions.size                                        = sizeof(sanitizeOperationOptions);
    sanitizeOptions.commonOptions.allowUnrestrictedSanitizeExit = false;
    sanitizeOptions.commonOptions.zoneNoReset                   = false;
    sanitizeOptions.commonOptions.noDeallocate                  = false;
    sanitizeOptions.pollForProgress                             = pollForProgress;
    switch (sanitizeOperation)
    {
    case SANITIZE_BLOCK_ERASE:
        sanitizeOptions.sanitizeEraseOperation = BLOCK_ERASE;
        break;
    case SANITIZE_CRYPTO_ERASE:
        sanitizeOptions.sanitizeEraseOperation = CRYPTO_ERASE;
        break;
    case SANITIZE_OVERWRITE_ERASE:
        sanitizeOptions.sanitizeEraseOperation                      = OVERWRITE_ERASE;
        sanitizeOptions.overwriteOptions.invertPatternBetweenPasses = false;
        sanitizeOptions.overwriteOptions.numberOfPasses             = UINT8_C(1);
        if (pattern != M_NULLPTR)
        {
            safe_memcpy(&sanitizeOptions.overwriteOptions.pattern, sizeof(uint32_t), pattern,
                        M_Min(patternLength, sizeof(uint32_t)));
        }
        else
        {
            sanitizeOptions.overwriteOptions.pattern = 0;
        }
        break;
    case SANTIZIE_FREEZE_LOCK:
        return sanitize_Freezelock(device);
    case SANITIZE_ANTI_FREEZE_LOCK:
        return sanitize_Anti_Freezelock(device);
    default:
        return NOT_SUPPORTED;
    }
    return run_Sanitize_Operation2(device, sanitizeOptions);
}

static eReturnValues sanitize_Poll_For_Progress(const tDevice* device,
                                                uint32_t       delayTime,
                                                eSanitizeErase sanitizeEraseOperation)
{
    eReturnValues ret             = IN_PROGRESS;
    uint8_t       minutes         = UINT8_C(0);
    uint8_t       seconds         = UINT8_C(0);
    double        percentComplete = 0.0;
    convert_Seconds_To_Displayable_Time(delayTime, M_NULLPTR, M_NULLPTR, M_NULLPTR, &minutes, &seconds);
    print_str("Sanitize progress will be updated every");
    print_Time_To_Screen(M_NULLPTR, M_NULLPTR, M_NULLPTR, &minutes, &seconds);
    print_str("\n");
    eSanitizeStatus sanitizeInProgress = SANITIZE_STATUS_IN_PROGRESS;
    while (sanitizeInProgress == SANITIZE_STATUS_IN_PROGRESS)
    {
        delay_Seconds(delayTime);
        ret = get_Sanitize_Progress(device, &percentComplete, &sanitizeInProgress);
        if (VERBOSITY_QUIET < device->deviceVerbosity)
        {
            if ((ret == SUCCESS || ret == IN_PROGRESS))
            {
                if (sanitizeInProgress != SANITIZE_STATUS_IN_PROGRESS &&
                    percentComplete < 100.0) // if we get to the end, percent complete may not say 100%, so we need this
                                             // condition to correct it
                {
                    print_str("\r\tSanitize Progress = 100.00%");
                    flush_stdout();
                }
                else
                {
                    printf("\r\tSanitize Progress = %3.2f%%", percentComplete);
                    flush_stdout();
                }
            }
        }
        if (sanitizeInProgress != SANITIZE_STATUS_IN_PROGRESS)
        {
            if (VERBOSITY_QUIET < device->deviceVerbosity)
            {
                print_Sanitize_Status_To_Screen(sanitizeInProgress, percentComplete);
            }
            break;
        }
    }
    if (VERBOSITY_QUIET < device->deviceVerbosity)
    {
        print_str("\n");
    }
    if (sanitizeInProgress == SANITIZE_STATUS_SUCCESS)
    {
        writeAfterErase writeReq;
        safe_memset(&writeReq, sizeof(writeAfterErase), 0, sizeof(writeAfterErase));
        if (SUCCESS == is_Write_After_Erase_Required(device, &writeReq))
        {
            if (sanitizeEraseOperation == OVERWRITE_ERASE ||
                !(writeReq.blockErase > WAEREQ_READ_COMPLETES_GOOD_STATUS ||
                  writeReq.cryptoErase > WAEREQ_READ_COMPLETES_GOOD_STATUS))
            {
                // Only run this file system rescan if we are confident the drive is in a state where it can respond to
                // the read commands properly. -TJE
                os_Update_File_System_Cache(device);
            }
        }
    }
    return ret;
}

// Sanitize API V1 structs/enums/defs
// These must be preserved to offer backwards compatibility if this changes in the future -TJE

#define SANITIZE_OPERATION_OPTIONS_VERSION_V1 (1)
typedef struct s_sanitizeOperationOptions_V1
{
    size_t         size;    // sizeof(sanitizeOperationOptions)
    uint32_t       version; // SANITIZE_OPERATION_OPTIONS_VERSION
    eSanitizeErase sanitizeEraseOperation;
    bool           pollForProgress; // crypto, block, and overwrite erases
    struct
    {
        bool allowUnrestrictedSanitizeExit;
        bool zoneNoReset;  // zoned devices only.
        bool noDeallocate; // NVMe only today. May not be supported by a controller.
    } commonOptions;       // options that apply to all Sanitize erase's
    struct
    {
        bool invertPatternBetweenPasses; // SATA note: Some drives may or may not set a definitive ending pattern upon
                                         // completion. By default, this function will set the definitive ending pattern
                                         // bit whenever possible-TJE
        uint8_t  numberOfPasses; // 0 = BAD_PARAMETER, 1 = 1, 2 = 2, etc. NVMe and SATA max at 16. SCSI maxes at 32
        uint32_t pattern;
    } overwriteOptions; // overwrite unique options
} sanitizeOperationOptions_V1;

eReturnValues run_Sanitize_Operation2(const tDevice* device, sanitizeOperationOptions sanitizeOptions)
{
    eReturnValues ret = UNKNOWN;
    if (sanitizeOptions.version >= SANITIZE_OPERATION_OPTIONS_VERSION_V1 &&
        sanitizeOptions.size >= sizeof(sanitizeOperationOptions_V1))
    {
        // NOTE: If sanitizeoptions changes version, need to adjust code to offer backwards compatibility when
        // possible.-TJE
        //       If new options are added, set to backwards compatible defaults.
        uint32_t        delayTime           = UINT32_C(1);
        double          percentComplete     = 0.0;
        eSanitizeStatus sanitizeInProgress  = 0;
        bool            sendExitFailureMode = false;

        // NOTE: Quick hack to disable block erase and sanitize command on Rugged SSD4 devices
        //       This is here because of some strange behavior when issued that is still under investigation.
        if (strcasecmp("Rugged SSD4", device->drive_info.product_identification) == 0)
        {
            return NOT_SUPPORTED; // Rugged SSD4 does not support sanitize operations at this time.
        }

        // first check if a sanitize test is in progress (and that the drive isn't frozen or in a failure state)
        ret = get_Sanitize_Progress(device, &percentComplete, &sanitizeInProgress);
        if (sanitizeInProgress == SANITIZE_STATUS_IN_PROGRESS || ret == IN_PROGRESS)
        {
            if (sanitizeOptions.pollForProgress)
            {
                return sanitize_Poll_For_Progress(device, delayTime, sanitizeOptions.sanitizeEraseOperation);
            }
            else
            {
                return IN_PROGRESS;
            }
        }
        else if (sanitizeInProgress == SANITIZE_STATUS_FAILED ||
                 sanitizeInProgress == SANITIZE_STATUS_FAILED_PHYSICAL_SECTORS_REMAIN)
        {
            // failure mode need to be cleared
            sendExitFailureMode = true;
        }
        else if (sanitizeInProgress == SANITIZE_STATUS_FROZEN)
        {
            // device is frozen.
            return FROZEN;
        }
        if (sendExitFailureMode)
        {
            ret = send_Sanitize_Exit_Failure_Mode(device);
            if (ret != SUCCESS)
            {
                ret = get_Sanitize_Progress(device, &percentComplete, &sanitizeInProgress);
                if (sanitizeInProgress == SANITIZE_STATUS_FAILED ||
                    sanitizeInProgress == SANITIZE_STATUS_FAILED_PHYSICAL_SECTORS_REMAIN)
                {
                    // need to run the sanitize command in restricted exit mode, so you can only get it to run by
                    // reissuing the command in restricted mode again.
                    return DEVICE_ACCESS_DENIED; // use this to inform the user that you cannot use AUSE and need to
                                                 // retry sanitize without that field.
                }
            }
        }

        os_Lock_Device(device);
        os_Unmount_File_Systems_On_Device(device);
        // start the sanitize operation requested
        switch (sanitizeOptions.sanitizeEraseOperation)
        {
        case BLOCK_ERASE:
            ret       = send_Sanitize_Block_Erase(device, sanitizeOptions.commonOptions.allowUnrestrictedSanitizeExit,
                                                  sanitizeOptions.commonOptions.zoneNoReset);
            delayTime = 1;
            break;
        case CRYPTO_ERASE:
            ret       = send_Sanitize_Crypto_Erase(device, sanitizeOptions.commonOptions.allowUnrestrictedSanitizeExit,
                                                   sanitizeOptions.commonOptions.zoneNoReset);
            delayTime = 1;
            break;
        case OVERWRITE_ERASE:
            if (sanitizeOptions.overwriteOptions.numberOfPasses == 0)
            {
                return BAD_PARAMETER;
            }
            if (sanitizeOptions.overwriteOptions.numberOfPasses >=
                ATA_NVME_MAX_SANITIZE_OVERWRITE_PASSES) // 16 is max number of passes for NVMe/SATA
            {
                if (device->drive_info.drive_type != SCSI_DRIVE)
                {
                    if (sanitizeOptions.overwriteOptions.numberOfPasses > ATA_NVME_MAX_SANITIZE_OVERWRITE_PASSES)
                    {
                        return BAD_PARAMETER;
                    }
                    else
                    {
                        // change to zero to do all 16 passes in NVMe and SATA
                        sanitizeOptions.overwriteOptions.numberOfPasses = 0;
                    }
                }
                else
                {
                    if (sanitizeOptions.overwriteOptions.numberOfPasses >
                        SCSI_MAX_SANITIZE_OVERWRITE_PASSES) // 31 passes is the maximum in SCSI
                    {
                        return BAD_PARAMETER;
                    }
                    // In SCSI, a number of passes == 0 is reserved, so do not use this-TJE
                }
            }
            ret = send_Sanitize_Overwrite_Erase(device, sanitizeOptions.commonOptions.allowUnrestrictedSanitizeExit,
                                                sanitizeOptions.overwriteOptions.invertPatternBetweenPasses,
                                                sanitizeOptions.overwriteOptions.numberOfPasses,
                                                C_CAST(uint8_t*, &sanitizeOptions.overwriteOptions.pattern),
                                                sizeof(uint32_t), sanitizeOptions.commonOptions.zoneNoReset);
            delayTime = 600; // this is 10 minute delay between progress updates
            break;
        }

        if (sanitizeOptions.pollForProgress && ret == SUCCESS)
        {
            ret = sanitize_Poll_For_Progress(device, delayTime, sanitizeOptions.sanitizeEraseOperation);
        }
        os_Unlock_Device(device);
    }
    else
    {
        ret = BAD_PARAMETER;
    }
    return ret;
}
