// 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 depopulate.h
// \brief This file defines the functions for depopulating physical/storage elements on a drive (Remanufacture)

#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 "depopulate.h"
#include "logs.h" // To use get_ATA_Log etc.
#include "platform_helper.h"
#include "seagate_operations.h" //Including this so we can read the Seagate vendos specific version stuff and mask it to look like ACS4/SBC4

static bool is_Possible_Invalid_Depop_Time_Value(uint64_t depopTime)
{
    // according to the ACS4/SBC4 spec, valid depop times are 0x00000001 to 0xFFFFFFFE
    // 0 means "time not reported" and 0xFFFFFFFF means "depopulation not supported"
    if (depopTime == UINT64_MAX || depopTime == UINT32_MAX || depopTime == 0 || depopTime == UINT16_MAX)
    {
        return true;
    }
    return false;
}

bool is_Depopulation_Feature_Supported(const tDevice* device, uint64_t* depopulationTime)
{
    bool supported = false;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        // support is listed in the ID Data log, supported capabilities page
        DECLARE_ZERO_INIT_ARRAY(uint8_t, supportedCapabilities, LEGACY_DRIVE_SEC_SIZE);
        if (SUCCESS == send_ATA_Read_Log_Ext_Cmd(device, ATA_LOG_IDENTIFY_DEVICE_DATA,
                                                 ATA_ID_DATA_LOG_SUPPORTED_CAPABILITIES, supportedCapabilities,
                                                 LEGACY_DRIVE_SEC_SIZE, 0))
        {
            uint64_t supportedCapabilitiesQWord0 = M_BytesTo8ByteValue(
                supportedCapabilities[7], supportedCapabilities[6], supportedCapabilities[5], supportedCapabilities[4],
                supportedCapabilities[3], supportedCapabilities[2], supportedCapabilities[1], supportedCapabilities[0]);
            if (supportedCapabilitiesQWord0 & BIT63 && get_8bit_range_uint64(supportedCapabilitiesQWord0, 23, 16) ==
                                                           0x03) // make sure required bits/fields are there...checking
                                                                 // for bit63 to be 1 and page number to be 3
            {
                uint64_t supportedCapabilitiesQWord18 = M_BytesTo8ByteValue(
                    supportedCapabilities[159], supportedCapabilities[158], supportedCapabilities[157],
                    supportedCapabilities[156], supportedCapabilities[155], supportedCapabilities[154],
                    supportedCapabilities[153], supportedCapabilities[152]);
                if (supportedCapabilitiesQWord18 & BIT63) // making sure this is set for "validity"
                {
                    if (supportedCapabilitiesQWord18 & BIT1 &&
                        supportedCapabilitiesQWord18 & BIT0) // checking for both commands to be supported
                    {
                        supported = true;
                    }
                }
                // get depopulation execution time
                if (depopulationTime != M_NULLPTR)
                {
                    uint64_t supportedCapabilitiesQWord19 = M_BytesTo8ByteValue(
                        supportedCapabilities[167], supportedCapabilities[166], supportedCapabilities[165],
                        supportedCapabilities[164], supportedCapabilities[163], supportedCapabilities[162],
                        supportedCapabilities[161], supportedCapabilities[160]);
                    if (supportedCapabilitiesQWord19 & BIT63) // check for validity
                    {
                        *depopulationTime = supportedCapabilitiesQWord19 & UINT64_C(0x7FFFFFFFFFFFFFFF);
                    }
                    else
                    {
                        *depopulationTime =
                            UINT64_MAX; // so we can set the timeout on the command or say "time not reported"
                    }
                }
            }
        }
    }
    else if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        // send some report supported operation code commands to figure it out
        scsiOperationCodeInfoRequest getElementStatusSup;
        scsiOperationCodeInfoRequest removeAndTruncateSup;
        safe_memset(&getElementStatusSup, sizeof(scsiOperationCodeInfoRequest), 0,
                    sizeof(scsiOperationCodeInfoRequest));
        safe_memset(&removeAndTruncateSup, sizeof(scsiOperationCodeInfoRequest), 0,
                    sizeof(scsiOperationCodeInfoRequest));
        getElementStatusSup.operationCode          = 0x9E;
        getElementStatusSup.serviceActionValid     = true;
        getElementStatusSup.serviceAction          = 0x17;
        removeAndTruncateSup.operationCode         = 0x9E;
        removeAndTruncateSup.serviceActionValid    = true;
        removeAndTruncateSup.serviceAction         = 0x18;
        eSCSICmdSupport getElementStatusSupported  = is_SCSI_Operation_Code_Supported(device, &getElementStatusSup);
        eSCSICmdSupport removeAndTruncateSupported = is_SCSI_Operation_Code_Supported(device, &removeAndTruncateSup);
        if (removeAndTruncateSupported == SCSI_CMD_SUPPORT_SUPPORTED_TO_SCSI_STANDARD &&
            getElementStatusSupported == SCSI_CMD_SUPPORT_SUPPORTED_TO_SCSI_STANDARD)
        {
            supported = true;
        }
        if (depopulationTime != M_NULLPTR)
        {
            *depopulationTime = UINT64_MAX;
            DECLARE_ZERO_INIT_ARRAY(uint8_t, blockDeviceCharacteristics, VPD_BLOCK_DEVICE_CHARACTERISTICS_LEN);
            if (SUCCESS == scsi_Inquiry(device, blockDeviceCharacteristics, VPD_BLOCK_DEVICE_CHARACTERISTICS_LEN,
                                        BLOCK_DEVICE_CHARACTERISTICS, true, false))
            {
                *depopulationTime = M_BytesTo4ByteValue(blockDeviceCharacteristics[12], blockDeviceCharacteristics[13],
                                                        blockDeviceCharacteristics[14], blockDeviceCharacteristics[15]);
                // Work around for SATLs
                // Some do not support report supported operation codes, so this relies on this field being non-zero.
                // Only flip this if the commands above are reporting "unknown" which means the lookup returned
                // something like invalid command operation code, or something like that.
                if (!supported && *depopulationTime > 0 && getElementStatusSupported == SCSI_CMD_SUPPORT_UNKNOWN)
                {
                    supported = true;
                }
            }
        }
    }
    return supported;
}

eReturnValues get_Number_Of_Descriptors(const tDevice* device, uint32_t* numberOfDescriptors)
{
    eReturnValues ret = NOT_SUPPORTED;

    if (numberOfDescriptors == M_NULLPTR)
    {
        return BAD_PARAMETER;
    }

    DECLARE_ZERO_INIT_ARRAY(uint8_t, getPhysicalElementCount, LEGACY_DRIVE_SEC_SIZE);
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        if (SUCCESS == ata_Get_Physical_Element_Status(device, 0, 0, 0, getPhysicalElementCount, LEGACY_DRIVE_SEC_SIZE))
        {
            *numberOfDescriptors = M_BytesTo4ByteValue(getPhysicalElementCount[3], getPhysicalElementCount[2],
                                                       getPhysicalElementCount[1], getPhysicalElementCount[0]);
            ret                  = SUCCESS;
        }
    }
    else if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        if (SUCCESS ==
            scsi_Get_Physical_Element_Status(device, 0, LEGACY_DRIVE_SEC_SIZE, 0, 0, getPhysicalElementCount))
        {
            *numberOfDescriptors = M_BytesTo4ByteValue(getPhysicalElementCount[0], getPhysicalElementCount[1],
                                                       getPhysicalElementCount[2], getPhysicalElementCount[3]);
            ret                  = SUCCESS;
        }
    }
    return ret;
}

eReturnValues get_Physical_Element_Descriptors_2(const tDevice*     device,
                                                 uint32_t           numberOfElementsExpected,
                                                 uint32_t*          depopElementID,
                                                 uint16_t*          maximumDepopulatedElements,
                                                 uint16_t*          currentDepopulatedElements,
                                                 ptrPhysicalElement elementList)
{
    // NOTE: Seagate legacy method uses head numbers starting at zero, but STD spec starts at 1. Add 1 to anything from
    // Seagate legacy method
    eReturnValues ret = NOT_SUPPORTED;

    if (elementList == M_NULLPTR)
    {
        return BAD_PARAMETER;
    }

    // This should be a number of 512B blocks based on how many physical elements are supported by the drive.
    // NOTE: If this ever starts requesting a LOT of data, then this may need to be broken into multiple commands. - TJE
    uint32_t getPhysicalElementsDataSize =
        (numberOfElementsExpected * 32 /*bytes per descriptor*/) + 32 /*bytes for data header*/;
    // now round that to the nearest 512B sector
    getPhysicalElementsDataSize  = uint32_round_up_power2(getPhysicalElementsDataSize, LEGACY_DRIVE_SEC_SIZE);
    uint8_t* getPhysicalElements = C_CAST(
        uint8_t*, safe_calloc_aligned(getPhysicalElementsDataSize, sizeof(uint8_t), device->os_info.minimumAlignment));
    if (getPhysicalElements != M_NULLPTR)
    {
        uint32_t numberOfDescriptorsReturned = UINT32_C(0);
        if (device->drive_info.drive_type == ATA_DRIVE)
        {
            if (SUCCESS ==
                ata_Get_Physical_Element_Status(device, 0, 0, 0, getPhysicalElements, getPhysicalElementsDataSize))
            {
                ret = SUCCESS;
                // Fill in the struct here since ATA is little endian
                numberOfDescriptorsReturned = M_BytesTo4ByteValue(getPhysicalElements[7], getPhysicalElements[6],
                                                                  getPhysicalElements[5], getPhysicalElements[4]);

                if (depopElementID != M_NULLPTR)
                {
                    *depopElementID = M_BytesTo4ByteValue(getPhysicalElements[11], getPhysicalElements[10],
                                                          getPhysicalElements[9], getPhysicalElements[8]);
                }
                if (maximumDepopulatedElements != M_NULLPTR)
                {
                    *maximumDepopulatedElements = M_BytesTo2ByteValue(getPhysicalElements[13], getPhysicalElements[12]);
                }
                if (currentDepopulatedElements != M_NULLPTR)
                {
                    *currentDepopulatedElements = M_BytesTo2ByteValue(getPhysicalElements[15], getPhysicalElements[14]);
                }

                if (numberOfElementsExpected != numberOfDescriptorsReturned)
                {
                    if (device->deviceVerbosity >= VERBOSITY_DEFAULT)
                    {
                        printf("WARNING: Drive returned %" PRIu32 " elements, but %" PRIu32 " were expected\n",
                               numberOfDescriptorsReturned, numberOfElementsExpected);
                    }
                }
            }
        }
        else if (device->drive_info.drive_type == SCSI_DRIVE)
        {
            if (SUCCESS ==
                scsi_Get_Physical_Element_Status(device, 0, getPhysicalElementsDataSize, 0, 0, getPhysicalElements))
            {
                ret = SUCCESS;
                // Fill in the struct here since SCSI is big endian
                numberOfDescriptorsReturned = M_BytesTo4ByteValue(getPhysicalElements[4], getPhysicalElements[5],
                                                                  getPhysicalElements[6], getPhysicalElements[7]);

                if (depopElementID != M_NULLPTR)
                {
                    *depopElementID = M_BytesTo4ByteValue(getPhysicalElements[8], getPhysicalElements[9],
                                                          getPhysicalElements[10], getPhysicalElements[11]);
                }
                if (maximumDepopulatedElements != M_NULLPTR)
                {
                    *maximumDepopulatedElements = M_BytesTo2ByteValue(getPhysicalElements[12], getPhysicalElements[13]);
                }
                if (currentDepopulatedElements != M_NULLPTR)
                {
                    *currentDepopulatedElements = M_BytesTo2ByteValue(getPhysicalElements[14], getPhysicalElements[15]);
                }

                if (numberOfElementsExpected != numberOfDescriptorsReturned)
                {
                    printf("WARNING: Drive returned %" PRIu32 " elements, but %" PRIu32 " were expected\n",
                           numberOfDescriptorsReturned, numberOfElementsExpected);
                }
            }
        }
        if (ret == SUCCESS)
        {
            for (uint32_t elementIter = UINT32_C(0), offset = UINT32_C(32);
                 elementIter < numberOfDescriptorsReturned && offset < getPhysicalElementsDataSize;
                 ++elementIter, offset += UINT32_C(32))
            {
                // byte swap when we need it (ATA vs SCSI)
                elementList[elementIter].elementIdentifier =
                    M_BytesTo4ByteValue(getPhysicalElements[offset + 4], getPhysicalElements[offset + 5],
                                        getPhysicalElements[offset + 6], getPhysicalElements[offset + 7]);
                if (device->drive_info.drive_type == ATA_DRIVE)
                {
                    byte_Swap_32(&elementList[elementIter].elementIdentifier);
                }
                elementList[elementIter].restorationAllowed = M_ToBool(getPhysicalElements[offset + 13] & BIT0);
                elementList[elementIter].elementType        = getPhysicalElements[offset + 14];
                elementList[elementIter].elementHealth      = getPhysicalElements[offset + 15];
                // byte swap when we need it (ATA vs SCSI)
                elementList[elementIter].associatedCapacity =
                    M_BytesTo8ByteValue(getPhysicalElements[offset + 16], getPhysicalElements[offset + 17],
                                        getPhysicalElements[offset + 18], getPhysicalElements[offset + 19],
                                        getPhysicalElements[offset + 20], getPhysicalElements[offset + 21],
                                        getPhysicalElements[offset + 22], getPhysicalElements[offset + 23]);
                if (device->drive_info.drive_type == ATA_DRIVE)
                {
                    byte_Swap_64(&elementList[elementIter].associatedCapacity);
                }
            }
        }
        safe_free_aligned(&getPhysicalElements);
    }
    else
    {
        ret = MEMORY_FAILURE;
    }
    return ret;
}

eReturnValues get_Physical_Element_Descriptors(const tDevice*     device,
                                               uint32_t           numberOfElementsExpected,
                                               ptrPhysicalElement elementList)
{
    uint32_t depopElementID = UINT32_C(0);
    uint16_t maxDepop       = UINT16_C(0);
    uint16_t currentDepop   = UINT16_C(0);
    return get_Physical_Element_Descriptors_2(device, numberOfElementsExpected, &depopElementID, &maxDepop,
                                              &currentDepop, elementList);
}

void show_Physical_Element_Descriptors_2(uint32_t           numberOfElements,
                                         ptrPhysicalElement elementList,
                                         uint64_t           depopulateTime,
                                         uint32_t           depopElementID,
                                         uint16_t           maximumDepopulatedElements,
                                         uint16_t           currentDepopulatedElements)
{
    // print out the list of descriptors
    print_str("\nElement Types:\n");
    print_str("\t P - physical element\n");
    print_str("\t S - storage element\n");

    print_str("\nApproximate time to depopulate: ");
    if (is_Possible_Invalid_Depop_Time_Value(depopulateTime) == false)
    {
        uint16_t days    = UINT16_C(0);
        uint8_t  hours   = UINT8_C(0);
        uint8_t  minutes = UINT8_C(0);
        uint8_t  seconds = UINT8_C(0);
        convert_Seconds_To_Displayable_Time(depopulateTime, M_NULLPTR, &days, &hours, &minutes, &seconds);
        print_Time_To_Screen(M_NULLPTR, &days, &hours, &minutes, &seconds);
        print_str("\n");
    }
    else
    {
        printf("Not reported or possible invalid time reported: %" PRIu64 ".\n", depopulateTime);
    }
    if (depopElementID > 0)
    {
        printf("Current Element being depopulated: %" PRIu32 "\n", depopElementID);
    }
    if (maximumDepopulatedElements > 0)
    {
        printf("Maximum Depopulated Elements: %" PRIu16 "\n", maximumDepopulatedElements);
    }
    if (currentDepopulatedElements > 0)
    {
        printf("Current Depopulated Elements: %" PRIu16 "\n", currentDepopulatedElements);
    }
    print_str("\nElement #\tType\tHealth\tStatus\t\tAssociated MaxLBA\tRebuild Allowed\n");
    print_str("----------------------------------------------------------------------------------\n");
    for (uint32_t elementIter = UINT32_C(0); elementIter < numberOfElements; ++elementIter)
    {
#define PHYSICAL_ELEMENT_STATUS_STRING_MAX_LENGTH 23
        DECLARE_ZERO_INIT_ARRAY(char, statusString, PHYSICAL_ELEMENT_STATUS_STRING_MAX_LENGTH);
#define PHYSICAL_ELEMENT_CAPACITY_STRING_MAX_LENGTH 21
        DECLARE_ZERO_INIT_ARRAY(char, capacityString, PHYSICAL_ELEMENT_CAPACITY_STRING_MAX_LENGTH);
        char elementType = 'P'; // physical element
#define PHYSICAL_ELEMENT_REBUILD_ALLOWED_STRING_MAX_LENGTH 4
        DECLARE_ZERO_INIT_ARRAY(char, rebuildAllowed, PHYSICAL_ELEMENT_REBUILD_ALLOWED_STRING_MAX_LENGTH);
        if (elementList[elementIter].elementHealth == 0x0)
        {
            snprintf_err_handle(statusString, PHYSICAL_ELEMENT_STATUS_STRING_MAX_LENGTH, "Not reported");
        }
        else if (elementList[elementIter].elementHealth >= 0x1 && elementList[elementIter].elementHealth <= 0x63)
        {
            snprintf_err_handle(statusString, PHYSICAL_ELEMENT_STATUS_STRING_MAX_LENGTH, "In Limit");
        }
        else if (elementList[elementIter].elementHealth == 0x64)
        {
            snprintf_err_handle(statusString, PHYSICAL_ELEMENT_STATUS_STRING_MAX_LENGTH, "At Limit");
        }
        else if (elementList[elementIter].elementHealth >= 0x65 && elementList[elementIter].elementHealth <= 0xCF)
        {
            snprintf_err_handle(statusString, PHYSICAL_ELEMENT_STATUS_STRING_MAX_LENGTH, "Over Limit");
        }
        else if (elementList[elementIter].elementHealth == 0xFB)
        {
            snprintf_err_handle(statusString, PHYSICAL_ELEMENT_STATUS_STRING_MAX_LENGTH, "Repopulate Error");
        }
        else if (elementList[elementIter].elementHealth == 0xFC)
        {
            snprintf_err_handle(statusString, PHYSICAL_ELEMENT_STATUS_STRING_MAX_LENGTH, "Repopulate in progress");
        }
        else if (elementList[elementIter].elementHealth == 0xFD)
        {
            snprintf_err_handle(statusString, PHYSICAL_ELEMENT_STATUS_STRING_MAX_LENGTH, "Depopulate Error");
        }
        else if (elementList[elementIter].elementHealth == 0xFE)
        {
            snprintf_err_handle(statusString, PHYSICAL_ELEMENT_STATUS_STRING_MAX_LENGTH, "Depopulate in progress");
        }
        else if (elementList[elementIter].elementHealth == 0xFF)
        {
            snprintf_err_handle(statusString, PHYSICAL_ELEMENT_STATUS_STRING_MAX_LENGTH, "Depopulated");
        }
        else
        {
            snprintf_err_handle(statusString, PHYSICAL_ELEMENT_STATUS_STRING_MAX_LENGTH, "Reserved");
        }
        if (elementList[elementIter].associatedCapacity == UINT64_MAX)
        {
            // Drive doesn't report this
            snprintf_err_handle(capacityString, PHYSICAL_ELEMENT_CAPACITY_STRING_MAX_LENGTH, "N/A");
        }
        else
        {
            snprintf_err_handle(capacityString, PHYSICAL_ELEMENT_CAPACITY_STRING_MAX_LENGTH, "%" PRIu64,
                                elementList[elementIter].associatedCapacity);
        }
        if (elementList[elementIter].elementType == 1)
        {
            elementType = 'S';
        }
        if (elementList[elementIter].restorationAllowed)
        {
            snprintf_err_handle(rebuildAllowed, PHYSICAL_ELEMENT_REBUILD_ALLOWED_STRING_MAX_LENGTH, "Yes");
        }
        else
        {
            snprintf_err_handle(rebuildAllowed, PHYSICAL_ELEMENT_REBUILD_ALLOWED_STRING_MAX_LENGTH, "No");
        }
        printf("%9" PRIu32 "\t%c  \t%3" PRIu8 " \t%-23s\t%-17s\t%s\n", elementList[elementIter].elementIdentifier,
               elementType, elementList[elementIter].elementHealth, statusString, capacityString, rebuildAllowed);
    }
    print_str("\nNOTE: At least one element must be able to be rebuilt to repopulate and rebuild.\n");
}

void show_Physical_Element_Descriptors(uint32_t           numberOfElements,
                                       ptrPhysicalElement elementList,
                                       uint64_t           depopulateTime)
{
    show_Physical_Element_Descriptors_2(numberOfElements, elementList, depopulateTime, 0, 0, 0);
}

// NOTE: This definition belongs in opensea-transport cmds.h/.c
eReturnValues depopulate_Physical_Element(const tDevice* device, uint32_t elementDescriptorID, uint64_t requestedMaxLBA)
{
    eReturnValues ret = NOT_SUPPORTED;
    os_Get_Exclusive(M_CONST_CAST(tDevice*, device));
    os_Lock_Device(device);
    os_Unmount_File_Systems_On_Device(device);
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        ret = ata_Remove_Element_And_Truncate(device, elementDescriptorID, requestedMaxLBA);
    }
    else if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        ret = scsi_Remove_And_Truncate(device, requestedMaxLBA, elementDescriptorID);
    }
    os_Unlock_Device(device);
    return ret;
}

static eReturnValues ata_get_Depopulate_Progress(const tDevice* device, eDepopStatus* depopStatus, double* progress)
{
    eReturnValues ret                       = NOT_SUPPORTED;
    bool          workaroundIncompleteSense = false;
    uint8_t       senseKey                  = UINT8_C(0);
    uint8_t       asc                       = UINT8_C(0);
    uint8_t       ascq                      = UINT8_C(0);
    if (SUCCESS == ata_Request_Sense_Data(device, &senseKey, &asc, &ascq))
    {
        ret = SUCCESS;
        if (senseKey == SENSE_KEY_NOT_READY && asc == 0x04 && ascq == 0x24) // depop in progress
        {

            *depopStatus = DEPOP_IN_PROGRESS;
        }
        else if (senseKey == SENSE_KEY_NOT_READY && asc == 0x04 && ascq == 0x25) // repop in progress
        {
            *depopStatus = DEPOP_REPOP_IN_PROGRESS;
        }
        else if (senseKey == SENSE_KEY_NOT_READY && asc == 0x04 && ascq == 0x1E) // microcode activation required
        {
            *depopStatus = DEPOP_MICROCODE_NEEDS_ACTIVATION;
        }
        else if (senseKey == SENSE_KEY_ILLEGAL_REQUEST && asc == 0x24 && ascq == 0x00) // invalid field in CDB
        {
            *depopStatus = DEPOP_INVALID_FIELD;
        }
        else if (senseKey == SENSE_KEY_MEDIUM_ERROR && asc == 0x31 && ascq == 0x04) // depop failed
        {
            *depopStatus = DEPOP_FAILED;
        }
        else if (senseKey == SENSE_KEY_MEDIUM_ERROR && asc == 0x31 && ascq == 0x05) // repop failed
        {
            *depopStatus = DEPOP_REPOP_FAILED;
        }
        else
        {
            workaroundIncompleteSense = true;
            ret                       = NOT_SUPPORTED;
        }
    }
    else
    {
        workaroundIncompleteSense = true;
        ret                       = NOT_SUPPORTED;
    }
    if (workaroundIncompleteSense)
    {
        // Send the get physical element status command and check if any say depopulation/repopulation in progress
        // or had an error. read physical element status to see if any of the specified element number matches any
        // that were found
        uint32_t      numberOfDescriptors = UINT32_C(0);
        eReturnValues getDescirptors      = get_Number_Of_Descriptors(device, &numberOfDescriptors);
        if (SUCCESS == getDescirptors && numberOfDescriptors > 0)
        {
            ptrPhysicalElement elementList =
                M_REINTERPRET_CAST(ptrPhysicalElement, safe_calloc(numberOfDescriptors, sizeof(physicalElement)));
            if (elementList != M_NULLPTR)
            {
                if (SUCCESS == get_Physical_Element_Descriptors(device, numberOfDescriptors, elementList))
                {
                    // loop through and check associatedCapacity and elementIdentifiers
                    bool foundStatus = false;
                    ret              = SUCCESS;
                    for (uint32_t elementID = UINT32_C(0); !foundStatus && elementID < numberOfDescriptors; ++elementID)
                    {
                        switch (elementList[elementID].elementHealth)
                        {
                        case 0xFB: // repop error
                            *depopStatus = DEPOP_REPOP_FAILED;
                            foundStatus  = true;
                            break;
                        case 0xFC: // repop in progress
                            *depopStatus = DEPOP_REPOP_IN_PROGRESS;
                            foundStatus  = true;
                            break;
                        case 0xFD: // depop error
                            *depopStatus = DEPOP_FAILED;
                            foundStatus  = true;
                            break;
                        case 0xFE: // depop in progress
                            *depopStatus = DEPOP_IN_PROGRESS;
                            foundStatus  = true;
                            break;
                        case 0xFF: // depop completed successfully
                        default:
                            break;
                        }
                    }
                }
                else
                {
                    ret = NOT_SUPPORTED;
                }
                safe_free_physical_element(&elementList);
            }
        }
        else
        {
            ret = getDescirptors;
        }
    }

    if (progress != M_NULLPTR)
    {
        if (*depopStatus == DEPOP_REPOP_IN_PROGRESS)
        {
            *progress = 255.0;
        }
        else
        {
            *progress = 0.0;
        }
    }

    return ret;
}

static eReturnValues scsi_get_Depopulate_Progress(const tDevice* device, eDepopStatus* depopStatus, double* progress)
{
    eReturnValues ret = NOT_SUPPORTED;
    DECLARE_ZERO_INIT_ARRAY(uint8_t, senseData, SPC3_SENSE_LEN);
    senseDataFields senseFields;
    safe_memset(&senseFields, sizeof(senseDataFields), 0, sizeof(senseDataFields));
    if (SUCCESS == scsi_Request_Sense_Cmd(device, true, senseData, SPC3_SENSE_LEN))
    {
        ret = SUCCESS;
        get_Sense_Data_Fields(senseData, SPC3_SENSE_LEN, &senseFields);
        // now that we've read all the fields, check for known sense data and fill in progress if any.
        if (senseFields.scsiStatusCodes.senseKey == SENSE_KEY_NOT_READY && senseFields.scsiStatusCodes.asc == 0x04 &&
            senseFields.scsiStatusCodes.ascq == 0x24) // depop in progress
        {
            *depopStatus = DEPOP_IN_PROGRESS;
        }
        else if (senseFields.scsiStatusCodes.senseKey == SENSE_KEY_NOT_READY &&
                 senseFields.scsiStatusCodes.asc == 0x04 &&
                 senseFields.scsiStatusCodes.ascq == 0x25) // repop in progress
        {
            *depopStatus = DEPOP_REPOP_IN_PROGRESS;
        }
        else if (senseFields.scsiStatusCodes.senseKey == SENSE_KEY_NOT_READY &&
                 senseFields.scsiStatusCodes.asc == 0x04 &&
                 senseFields.scsiStatusCodes.ascq == 0x1E) // microcode activation required
        {
            *depopStatus = DEPOP_MICROCODE_NEEDS_ACTIVATION;
        }
        else if (senseFields.scsiStatusCodes.senseKey == SENSE_KEY_ILLEGAL_REQUEST &&
                 senseFields.scsiStatusCodes.asc == 0x24 &&
                 senseFields.scsiStatusCodes.ascq == 0x00) // invalid field in CDB
        {
            *depopStatus = DEPOP_INVALID_FIELD;
        }
        else if (senseFields.scsiStatusCodes.senseKey == SENSE_KEY_MEDIUM_ERROR &&
                 senseFields.scsiStatusCodes.asc == 0x31 && senseFields.scsiStatusCodes.ascq == 0x04) // depop failed
        {
            *depopStatus = DEPOP_FAILED;
        }
        else if (senseFields.scsiStatusCodes.senseKey == SENSE_KEY_MEDIUM_ERROR &&
                 senseFields.scsiStatusCodes.asc == 0x31 && senseFields.scsiStatusCodes.ascq == 0x05) // repop failed
        {
            *depopStatus = DEPOP_REPOP_FAILED;
        }
        else
        {
            // nothing to report.
            ret = NOT_SUPPORTED;
        }
    }
    else
    {
        // If this failed, there is likely a bigger problem! But we can try getting physical element status
        ret = FAILURE;
    }

    if (progress != M_NULLPTR)
    {
        if (*depopStatus == DEPOP_REPOP_IN_PROGRESS)
        {
            if (progress != M_NULLPTR && senseFields.senseKeySpecificInformation.senseKeySpecificValid &&
                senseFields.senseKeySpecificInformation.type == SENSE_KEY_SPECIFIC_PROGRESS_INDICATION)
            {
                *progress = get_SCSI_Progress_Indicator_PercentD(
                    senseFields.senseKeySpecificInformation.progress.progressIndication);
            }
            else if (progress != M_NULLPTR)
            {
                *progress = 255.0;
            }
        }
        else
        {
            *progress = 0.0;
        }
    }

    return ret;
}

// NOTE: This may NOT give percentage. This will happen on ATA drives, but you can check that it is still running or
// not. - TJE On ATA drives, if in progress, the progress variable will get set to 255 since it is not possible to
// determine actual progress
eReturnValues get_Depopulate_Progress(const tDevice* device, eDepopStatus* depopStatus, double* progress)
{
    eReturnValues ret = NOT_SUPPORTED;

    if (depopStatus == M_NULLPTR)
    {
        return BAD_PARAMETER;
    }

    *depopStatus = DEPOP_NOT_IN_PROGRESS;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        ret = ata_get_Depopulate_Progress(device, depopStatus, progress);
    }
    else if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        ret = scsi_get_Depopulate_Progress(device, depopStatus, progress);
    }
    return ret;
}

eReturnValues show_Depop_Repop_Progress(const tDevice* device)
{
    eReturnValues ret         = NOT_SUPPORTED;
    eDepopStatus  depopStatus = DEPOP_NOT_IN_PROGRESS;
    double        progress    = 0.0;
    if (SUCCESS == get_Depopulate_Progress(device, &depopStatus, &progress))
    {
        ret = SUCCESS;
        switch (depopStatus)
        {
        case DEPOP_NOT_IN_PROGRESS:
            print_str("Depopulation/repopulation is not in progress.\n");
            break;
        case DEPOP_IN_PROGRESS:
            print_str("Depopulation in progress: ");
            if (progress > 100)
            {
                print_str("Progress indication not available.\n");
            }
            else
            {
                printf("%0.02f%%\n", progress);
            }
            ret = IN_PROGRESS;
            break;
        case DEPOP_REPOP_IN_PROGRESS:
            print_str("Repopulation in progress: ");
            if (progress > 100)
            {
                print_str("Progress indication not available.\n");
            }
            else
            {
                printf("%0.02f%%\n", progress);
            }
            ret = IN_PROGRESS;
            break;
        case DEPOP_FAILED:
            print_str("Depopulation failed.\n");
            break;
        case DEPOP_REPOP_FAILED:
            print_str("Repopulation failed.\n");
            break;
        case DEPOP_MICROCODE_NEEDS_ACTIVATION:
            print_str("Depopulation/repopulation requires microcode activation before it can be run.\n");
            break;
        default:
            print_str(
                "Unknown depopulation/repopulation status. The feature may not be supported, or is not running.\n");
            break;
        }
    }
    else
    {
        ret = FAILURE;
        print_str("A failure was encountered when checking for progress on depopulation/repopulation.\n");
    }
    return ret;
}

#define DEPOP_OPERATION_STRING "Depopulation"
#define REPOP_OPERATION_STRING "Repopulation"

static M_INLINE void print_Depop_Start(uint64_t depopTime, const char* operation)
{
    if (is_Possible_Invalid_Depop_Time_Value(depopTime))
    {
        printf("Starting %s. Approximate time until completion is not available.\n", operation);
    }
    else
    {
        uint16_t days    = UINT16_C(0);
        uint8_t  hours   = UINT8_C(0);
        uint8_t  minutes = UINT8_C(0);
        uint8_t  seconds = UINT8_C(0);
        convert_Seconds_To_Displayable_Time(depopTime, M_NULLPTR, &days, &hours, &minutes, &seconds);
        printf("Starting %s. Approximate time until completion: ", operation);
        print_Time_To_Screen(M_NULLPTR, &days, &hours, &minutes, &seconds);
        print_str("\n");
    }
    print_str("Do not remove power or attempt other access as interrupting it may make\n");
    print_str("the drive unusable or require performing this command again!!\n");
}

M_NULL_TERM_STRING(3)
M_PARAM_RO(3)
static eReturnValues determine_Depop_Failure_Reason(const tDevice* M_NONNULL device,
                                                    eReturnValues            ret,
                                                    const char*              operation,
                                                    uint32_t                 elementDescriptorID,
                                                    uint64_t                 requestedMaxLBA)
{
    bool invalidElement             = false;
    bool invalidMaxLBA              = false;
    bool repopulatableElementsError = false;

    ret = FAILURE;
    // read physical element status to see if any of the specified element number matches any that were
    // found
    uint32_t repopulatableElements        = UINT32_C(0);
    uint32_t currentlyDepopulatedElements = UINT32_C(0);
    uint32_t numberOfDescriptors          = UINT32_C(0);
    get_Number_Of_Descriptors(device, &numberOfDescriptors);
    if (numberOfDescriptors > 0)
    {
        ptrPhysicalElement elementList =
            M_REINTERPRET_CAST(ptrPhysicalElement, safe_calloc(numberOfDescriptors, sizeof(physicalElement)));
        if (elementList != M_NULLPTR)
        {
            if (SUCCESS == get_Physical_Element_Descriptors(device, numberOfDescriptors, elementList))
            {
                // loop through and check associatedCapacity and elementIdentifiers
                bool foundDescriptor = false;
                for (uint32_t elementID = UINT32_C(0); elementID < numberOfDescriptors; ++elementID)
                {
                    if (elementList[elementID].elementIdentifier == elementDescriptorID)
                    {
                        // found the descriptor, so it's not a issue of not finding it
                        foundDescriptor = true;
                        // check associated maxLBA
                        if (elementList[elementID].associatedCapacity != UINT64_MAX && requestedMaxLBA != 0 &&
                            requestedMaxLBA > elementList[elementID].associatedCapacity)
                        {
                            // tried requesting a new capacity greater than what the device can support...so
                            // this will trigger an error
                            invalidMaxLBA = true;
                        }
                    }
                    if (elementList[elementID].elementHealth == 0xFF) // depopulated successfully
                    {
                        ++currentlyDepopulatedElements;
                        if (elementList[elementID].restorationAllowed)
                        {
                            ++repopulatableElements;
                        }
                    }
                }
                if (!foundDescriptor)
                {
                    invalidElement = true;
                }
            }
            safe_free_physical_element(&elementList);
        }
    }
    if (currentlyDepopulatedElements > 0)
    {
        if (repopulatableElements > 0)
        {
            repopulatableElementsError = true;
        }
    }
    if (device->deviceVerbosity >= VERBOSITY_DEFAULT)
    {
        if (strcmp(operation, REPOP_OPERATION_STRING) == 0)
        {
            if (repopulatableElementsError)
            {
                print_str("Unknown error when trying to repopulate elements.\n");
            }
            else
            {
                if (device->deviceVerbosity >= VERBOSITY_DEFAULT)
                {
                    print_str("Repopulation of elements is not supported as currently depopulated "
                              "elements do not support being repopulated.\n");
                }
            }
        }
        if (invalidElement)
        {
            printf("%s failed due to invalid element specified\n", operation);
        }
        else if (invalidMaxLBA)
        {
            printf("%s failed due to invalid new max LBA specified\n", operation);
        }
        else
        {
            printf("%s failed with invalid field. Invalid element specified, or invalid new max "
                   "LBA or some other error\n",
                   operation);
        }
    }
    return ret;
}

M_NULL_TERM_STRING(3)
M_PARAM_RO(3)
static eReturnValues check_Depop_Command_Result_SCSI(const tDevice* M_NONNULL device,
                                                     eReturnValues            ret,
                                                     const char*              operation,
                                                     uint32_t                 elementDescriptorID,
                                                     uint64_t                 requestedMaxLBA)
{
    // On SAS, we'll have sense data, on ATA we can attempt to request sense, but some systems/controllers
    // do this for us and make this impossible to retrieve...so we need to work around this
    if (is_Microcode_Activation_Required(device->drive_info.lastCommandSenseData,
                                         SPC3_SENSE_LEN)) // microcode activation required
    {
        if (device->deviceVerbosity >= VERBOSITY_DEFAULT)
        {
            printf("%s cannot be started. Microcode must be activated first.\n", operation);
        }
        ret = FAILURE;
    }
    else if (is_Invalid_Field_In_CDB(device->drive_info.lastCommandSenseData, SPC3_SENSE_LEN))
    {
        ret = FAILURE;
        ret = determine_Depop_Failure_Reason(device, ret, operation, elementDescriptorID, requestedMaxLBA);
    }
    else if (is_Command_Sequence_Error(device->drive_info.lastCommandSenseData, SPC3_SENSE_LEN))
    {
        // command sequence error means all depopulated elements do not allow restoration
        // (at least one needs to support this)
        {
            ret = FAILURE;
        }
    }
    return ret;
}

M_NULL_TERM_STRING(3)
M_PARAM_RO(3)
static eReturnValues check_Depop_Command_Result_ATA(const tDevice* M_NONNULL device,
                                                    eReturnValues            ret,
                                                    const char* M_NONNULL    operation,
                                                    uint32_t                 elementDescriptorID,
                                                    uint64_t                 requestedMaxLBA)
{
    bool    workaroundIncompleteSense = false;
    uint8_t senseKey                  = UINT8_C(0);
    uint8_t asc                       = UINT8_C(0);
    uint8_t ascq                      = UINT8_C(0);
    if (SUCCESS == ata_Request_Sense_Data(device, &senseKey, &asc, &ascq))
    {
        if (senseKey == SENSE_KEY_NOT_READY && asc == 0x04 && ascq == 0x1E) // microcode activation required
        {
            if (device->deviceVerbosity >= VERBOSITY_DEFAULT)
            {
                print_str("Depopulation cannot be started. Microcode must be activated first.\n");
            }
            ret = FAILURE;
        }
        else if (senseKey == SENSE_KEY_ILLEGAL_REQUEST && asc == 0x24 && ascq == 0x00)
        {
            ret = FAILURE;
            ret = determine_Depop_Failure_Reason(device, ret, operation, elementDescriptorID, requestedMaxLBA);
        }
        else if (senseKey == SENSE_KEY_ILLEGAL_REQUEST && asc == 0x2C && ascq == 0x00)
        {
            ret = FAILURE;
        }
        else
        {
            workaroundIncompleteSense = true;
        }
    }
    else
    {
        workaroundIncompleteSense = true;
    }
    if (workaroundIncompleteSense)
    {
        bool reasonFound = false;
        // This means that something about the command was not liked...first check if microcode needs
        // activation
        DECLARE_ZERO_INIT_ARRAY(uint8_t, currentSettings, LEGACY_DRIVE_SEC_SIZE);
        if (SUCCESS == send_ATA_Read_Log_Ext_Cmd(device, ATA_LOG_IDENTIFY_DEVICE_DATA, ATA_ID_DATA_LOG_CURRENT_SETTINGS,
                                                 currentSettings, LEGACY_DRIVE_SEC_SIZE, 0))
        {
            uint64_t currentSettingsHeader =
                M_BytesTo8ByteValue(currentSettings[7], currentSettings[6], currentSettings[5], currentSettings[4],
                                    currentSettings[3], currentSettings[2], currentSettings[1], currentSettings[0]);
            if (currentSettingsHeader & BIT63 && M_Byte2(currentSettingsHeader) == ATA_ID_DATA_LOG_CURRENT_SETTINGS &&
                M_Word0(currentSettingsHeader) >= 0x0001)
            {
                // valid data
                uint64_t currentSettingsQWord = M_BytesTo8ByteValue(
                    currentSettings[15], currentSettings[14], currentSettings[13], currentSettings[12],
                    currentSettings[11], currentSettings[10], currentSettings[9], currentSettings[8]);
                if (currentSettingsQWord & BIT63 && currentSettingsQWord & BIT19)
                {
                    if (device->deviceVerbosity >= VERBOSITY_DEFAULT)
                    {
                        printf("%s cannot be started. Microcode must be activated first.\n", operation);
                    }
                    ret         = FAILURE;
                    reasonFound = true;
                }
            }
        }
        if (!reasonFound)
        {
            ret = determine_Depop_Failure_Reason(device, ret, operation, elementDescriptorID, requestedMaxLBA);
        }
    }
    return ret;
}

M_NULL_TERM_STRING(2)
M_PARAM_RO(2) static eReturnValues poll_Depop_Progress(const tDevice* M_NONNULL device, const char* M_NONNULL operation)
{
    eReturnValues ret = SUCCESS;
    // SCSI and ATA will be handled differently.
    // SCSI can report progress percentage in sense data. ATA does not do this.
    // Furthermore, request sense data from ATA may or may not work or get the information we want
    eDepopStatus  depopStatus   = DEPOP_NOT_IN_PROGRESS; // start with this until we start polling
    double        progress      = 0.0;
    uint16_t      delayTime     = UINT16_C(15);
    eReturnValues progressCheck = SUCCESS;
    if (device->deviceVerbosity >= VERBOSITY_DEFAULT)
    {
        print_str("\n");
    }
    do
    {
        delay_Seconds(delayTime);
        progressCheck = get_Depopulate_Progress(device, &depopStatus, &progress);
        if (depopStatus == DEPOP_IN_PROGRESS)
        {
            if (progress > 100.0)
            {
                if (device->deviceVerbosity >= VERBOSITY_DEFAULT)
                {
                    printf("\r%s is progress, but progress indication is not available.", operation);
                }
            }
            else
            {
                if (device->deviceVerbosity >= VERBOSITY_DEFAULT)
                {
                    printf("\r%s progress: %0.02f%%", operation, progress);
                }
            }
        }
    } while (depopStatus == DEPOP_IN_PROGRESS && progressCheck == SUCCESS);
    switch (depopStatus)
    {
    case DEPOP_NOT_IN_PROGRESS:
        ret = SUCCESS;
        break;
    case DEPOP_FAILED:
    case DEPOP_REPOP_FAILED:
        ret = FAILURE;
        break;
    case DEPOP_INVALID_FIELD:
    case DEPOP_MICROCODE_NEEDS_ACTIVATION:
    default:
        ret = UNKNOWN;
        break;
    }
    return ret;
}

eReturnValues perform_Depopulate_Physical_Element(const tDevice* device,
                                                  uint32_t       elementDescriptorID,
                                                  uint64_t       requestedMaxLBA,
                                                  bool           pollForProgress)
{
    return perform_Depopulate_Physical_Element2(device, elementDescriptorID, requestedMaxLBA, pollForProgress, false);
}

eReturnValues perform_Depopulate_Physical_Element2(const tDevice* device,
                                                   uint32_t       elementDescriptorID,
                                                   uint64_t       requestedMaxLBA,
                                                   bool           pollForProgress,
                                                   bool           modifyZones)
{
    eReturnValues ret       = NOT_SUPPORTED;
    uint64_t      depopTime = UINT64_C(0);
    if (is_Depopulation_Feature_Supported(device, &depopTime))
    {
        if (device->deviceVerbosity >= VERBOSITY_DEFAULT)
        {
            print_Depop_Start(depopTime, DEPOP_OPERATION_STRING);
        }
        if (modifyZones)
        {
            ret = depopulate_Physical_Element_And_Modify_Zones(device, elementDescriptorID);
        }
        else
        {
            ret = depopulate_Physical_Element(device, elementDescriptorID, requestedMaxLBA);
        }
        if (ret != SUCCESS)
        {
            if (device->drive_info.drive_type == SCSI_DRIVE)
            {
                ret = check_Depop_Command_Result_SCSI(device, ret, DEPOP_OPERATION_STRING, elementDescriptorID,
                                                      requestedMaxLBA);
            }
            else if (device->drive_info.drive_type == ATA_DRIVE)
            {
                ret = check_Depop_Command_Result_ATA(device, ret, DEPOP_OPERATION_STRING, elementDescriptorID,
                                                     requestedMaxLBA);
            }
        }
        else
        {
            if (pollForProgress)
            {
                ret = poll_Depop_Progress(device, DEPOP_OPERATION_STRING);
            }
        }
    }
    return ret;
}

static bool is_Depopulate_And_Modify_Zones_Supported_ATA(const tDevice* device, uint64_t* depopulationTime)
{
    bool supported = false;
    // support is listed in the ID Data log, supported capabilities page
    DECLARE_ZERO_INIT_ARRAY(uint8_t, supportedCapabilities, LEGACY_DRIVE_SEC_SIZE);
    if (depopulationTime != M_NULLPTR)
    {
        if (SUCCESS == send_ATA_Read_Log_Ext_Cmd(device, ATA_LOG_IDENTIFY_DEVICE_DATA,
                                                 ATA_ID_DATA_LOG_SUPPORTED_CAPABILITIES, supportedCapabilities,
                                                 LEGACY_DRIVE_SEC_SIZE, 0))
        {
            uint64_t supportedCapabilitiesQWord0 = M_BytesTo8ByteValue(
                supportedCapabilities[7], supportedCapabilities[6], supportedCapabilities[5], supportedCapabilities[4],
                supportedCapabilities[3], supportedCapabilities[2], supportedCapabilities[1], supportedCapabilities[0]);
            if (supportedCapabilitiesQWord0 & BIT63 &&
                get_8bit_range_uint64(supportedCapabilitiesQWord0, 23, 16) ==
                    ATA_ID_DATA_LOG_SUPPORTED_CAPABILITIES) // make sure required bits/fields are there...checking
                                                            // for bit63 to be 1 and page number to be 3
            {
                // get depopulation execution time
                uint64_t supportedCapabilitiesQWord19 = M_BytesTo8ByteValue(
                    supportedCapabilities[167], supportedCapabilities[166], supportedCapabilities[165],
                    supportedCapabilities[164], supportedCapabilities[163], supportedCapabilities[162],
                    supportedCapabilities[161], supportedCapabilities[160]);
                if (supportedCapabilitiesQWord19 & BIT63) // check for validity
                {
                    *depopulationTime = supportedCapabilitiesQWord19 & UINT64_C(0x7FFFFFFFFFFFFFFF);
                }
                else
                {
                    *depopulationTime =
                        UINT64_MAX; // so we can set the timeout on the command or say "time not reported"
                }
            }
        }
        safe_memset(supportedCapabilities, LEGACY_DRIVE_SEC_SIZE, 0, LEGACY_DRIVE_SEC_SIZE);
    }
    if (SUCCESS == send_ATA_Read_Log_Ext_Cmd(device, ATA_LOG_IDENTIFY_DEVICE_DATA,
                                             ATA_ID_DATA_LOG_ZONED_DEVICE_INFORMATION, supportedCapabilities,
                                             LEGACY_DRIVE_SEC_SIZE, 0))
    {
        uint64_t supportedCapabilitiesQWord0 = M_BytesTo8ByteValue(
            supportedCapabilities[7], supportedCapabilities[6], supportedCapabilities[5], supportedCapabilities[4],
            supportedCapabilities[3], supportedCapabilities[2], supportedCapabilities[1], supportedCapabilities[0]);
        if (supportedCapabilitiesQWord0 & BIT63 &&
            get_8bit_range_uint64(supportedCapabilitiesQWord0, 23, 16) ==
                ATA_ID_DATA_LOG_ZONED_DEVICE_INFORMATION) // make sure required bits/fields are there...checking for
                                                          // bit63 to be 1 and page number to be 9
        {
            uint64_t supportedCapabilitiesQWord1 =
                M_BytesTo8ByteValue(supportedCapabilities[15], supportedCapabilities[14], supportedCapabilities[13],
                                    supportedCapabilities[12], supportedCapabilities[11], supportedCapabilities[10],
                                    supportedCapabilities[9], supportedCapabilities[8]);
            if (supportedCapabilitiesQWord1 & BIT63) // making sure this is set for "validity"
            {
                if (supportedCapabilitiesQWord1 & BIT1)
                {
                    supported = true;
                }
            }
        }
    }
    return supported;
}

static bool is_Depopulate_And_Modify_Zones_Supported_SCSI(const tDevice* device, uint64_t* depopulationTime)
{
    bool supported = false;
    // send some report supported operation code commands to figure it out
    scsiOperationCodeInfoRequest removeAndTruncateSup;
    safe_memset(&removeAndTruncateSup, sizeof(scsiOperationCodeInfoRequest), 0, sizeof(scsiOperationCodeInfoRequest));
    removeAndTruncateSup.operationCode         = 0x9E;
    removeAndTruncateSup.serviceActionValid    = true;
    removeAndTruncateSup.serviceAction         = 0x1A;
    eSCSICmdSupport removeAndTruncateSupported = is_SCSI_Operation_Code_Supported(device, &removeAndTruncateSup);
    if (removeAndTruncateSupported == SCSI_CMD_SUPPORT_SUPPORTED_TO_SCSI_STANDARD)
    {
        supported = true;
        if (depopulationTime != M_NULLPTR)
        {
            *depopulationTime = UINT64_MAX;
            DECLARE_ZERO_INIT_ARRAY(uint8_t, blockDeviceCharacteristics, VPD_BLOCK_DEVICE_CHARACTERISTICS_LEN);
            if (SUCCESS == scsi_Inquiry(device, blockDeviceCharacteristics, VPD_BLOCK_DEVICE_CHARACTERISTICS_LEN,
                                        BLOCK_DEVICE_CHARACTERISTICS, true, false))
            {
                *depopulationTime = M_BytesTo4ByteValue(blockDeviceCharacteristics[12], blockDeviceCharacteristics[13],
                                                        blockDeviceCharacteristics[14], blockDeviceCharacteristics[15]);
            }
        }
    }
    return supported;
}

bool is_Depopulate_And_Modify_Zones_Supported(const tDevice* device, uint64_t* depopulationTime)
{
    bool supported = false;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        supported = is_Depopulate_And_Modify_Zones_Supported_ATA(device, depopulationTime);
    }
    else if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        supported = is_Depopulate_And_Modify_Zones_Supported_SCSI(device, depopulationTime);
    }
    return supported;
}

// NOTE: This definition belongs in opensea-transport cmds.h/.c
eReturnValues depopulate_Physical_Element_And_Modify_Zones(const tDevice* device, uint32_t elementDescriptorID)
{
    eReturnValues ret = NOT_SUPPORTED;
    os_Get_Exclusive(M_CONST_CAST(tDevice*, device));
    os_Lock_Device(device);
    os_Unmount_File_Systems_On_Device(device);
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        ret = ata_Remove_Element_And_Modify_Zones(device, elementDescriptorID);
    }
    else if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        ret = scsi_Remove_Element_And_Modify_Zones(device, elementDescriptorID);
    }
    os_Unlock_Device(device);
    return ret;
}

static bool is_Repopulate_Feature_Supported_ATA(const tDevice* device, uint64_t* depopulationTime)
{
    bool supported = false;
    // support is listed in the ID Data log, supported capabilities page
    DECLARE_ZERO_INIT_ARRAY(uint8_t, supportedCapabilities, LEGACY_DRIVE_SEC_SIZE);
    if (SUCCESS == send_ATA_Read_Log_Ext_Cmd(device, ATA_LOG_IDENTIFY_DEVICE_DATA,
                                             ATA_ID_DATA_LOG_SUPPORTED_CAPABILITIES, supportedCapabilities,
                                             LEGACY_DRIVE_SEC_SIZE, 0))
    {
        uint64_t supportedCapabilitiesQWord0 = M_BytesTo8ByteValue(
            supportedCapabilities[7], supportedCapabilities[6], supportedCapabilities[5], supportedCapabilities[4],
            supportedCapabilities[3], supportedCapabilities[2], supportedCapabilities[1], supportedCapabilities[0]);
        if (supportedCapabilitiesQWord0 & BIT63 && get_8bit_range_uint64(supportedCapabilitiesQWord0, 23, 16) ==
                                                       0x03) // make sure required bits/fields are there...checking
                                                             // for bit63 to be 1 and page number to be 3
        {
            uint64_t supportedCapabilitiesQWord18 =
                M_BytesTo8ByteValue(supportedCapabilities[159], supportedCapabilities[158], supportedCapabilities[157],
                                    supportedCapabilities[156], supportedCapabilities[155], supportedCapabilities[154],
                                    supportedCapabilities[153], supportedCapabilities[152]);
            if (supportedCapabilitiesQWord18 & BIT63) // making sure this is set for "validity"
            {
                if (supportedCapabilitiesQWord18 & BIT2)
                {
                    supported = true;
                }
            }
            // get depopulation execution time
            if (depopulationTime != M_NULLPTR)
            {
                uint64_t supportedCapabilitiesQWord19 = M_BytesTo8ByteValue(
                    supportedCapabilities[167], supportedCapabilities[166], supportedCapabilities[165],
                    supportedCapabilities[164], supportedCapabilities[163], supportedCapabilities[162],
                    supportedCapabilities[161], supportedCapabilities[160]);
                if (supportedCapabilitiesQWord19 & BIT63) // check for validity
                {
                    *depopulationTime = supportedCapabilitiesQWord19 & UINT64_C(0x7FFFFFFFFFFFFFFF);
                }
                else
                {
                    *depopulationTime =
                        UINT64_MAX; // so we can set the timeout on the command or say "time not reported"
                }
            }
        }
    }
    return supported;
}

static bool is_Repopulate_Feature_Supported_SCSI(const tDevice* device, uint64_t* depopulationTime)
{
    bool supported = false;
    // send some report supported operation code commands to figure it out
    scsiOperationCodeInfoRequest repopulateSup;
    safe_memset(&repopulateSup, sizeof(scsiOperationCodeInfoRequest), 0, sizeof(scsiOperationCodeInfoRequest));
    repopulateSup.operationCode         = 0x9E;
    repopulateSup.serviceActionValid    = true;
    repopulateSup.serviceAction         = 0x19;
    eSCSICmdSupport repopulateSupported = is_SCSI_Operation_Code_Supported(device, &repopulateSup);
    if (repopulateSupported == SCSI_CMD_SUPPORT_SUPPORTED_TO_SCSI_STANDARD)
    {
        supported = true;
        if (depopulationTime != M_NULLPTR)
        {
            *depopulationTime = UINT64_MAX;
            DECLARE_ZERO_INIT_ARRAY(uint8_t, blockDeviceCharacteristics, VPD_BLOCK_DEVICE_CHARACTERISTICS_LEN);
            if (SUCCESS == scsi_Inquiry(device, blockDeviceCharacteristics, VPD_BLOCK_DEVICE_CHARACTERISTICS_LEN,
                                        BLOCK_DEVICE_CHARACTERISTICS, true, false))
            {
                *depopulationTime = M_BytesTo4ByteValue(blockDeviceCharacteristics[12], blockDeviceCharacteristics[13],
                                                        blockDeviceCharacteristics[14], blockDeviceCharacteristics[15]);
            }
        }
    }
    return supported;
}

bool is_Repopulate_Feature_Supported(const tDevice* device, uint64_t* depopulationTime)
{
    bool supported = false;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        supported = is_Repopulate_Feature_Supported_ATA(device, depopulationTime);
    }
    else if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        supported = is_Repopulate_Feature_Supported_SCSI(device, depopulationTime);
    }
    return supported;
}

eReturnValues repopulate_Elements(const tDevice* device)
{
    eReturnValues ret = NOT_SUPPORTED;
    os_Get_Exclusive(M_CONST_CAST(tDevice*, device));
    os_Lock_Device(device);
    os_Unmount_File_Systems_On_Device(device);
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        ret = ata_Restore_Elements_And_Rebuild(device);
    }
    else if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        ret = scsi_Restore_Elements_And_Rebuild(device);
    }
    os_Unlock_Device(device);
    return ret;
}

eReturnValues perform_Repopulate_Physical_Element(const tDevice* device, bool pollForProgress)
{
    eReturnValues ret       = NOT_SUPPORTED;
    uint64_t      depopTime = UINT64_C(0);
    if (is_Repopulate_Feature_Supported(device, &depopTime))
    {
        if (device->deviceVerbosity >= VERBOSITY_DEFAULT)
        {
            print_Depop_Start(depopTime, REPOP_OPERATION_STRING);
        }
        ret = repopulate_Elements(device);
        if (ret != SUCCESS)
        {
            if (device->drive_info.drive_type == SCSI_DRIVE)
            {
                ret = check_Depop_Command_Result_SCSI(device, ret, REPOP_OPERATION_STRING, UINT32_MAX, 0);
            }
            else if (device->drive_info.drive_type == ATA_DRIVE)
            {
                ret = check_Depop_Command_Result_ATA(device, ret, REPOP_OPERATION_STRING, UINT32_MAX, 0);
            }
        }
        else
        {
            if (pollForProgress)
            {
                ret = poll_Depop_Progress(device, REPOP_OPERATION_STRING);
            }
        }
    }
    return ret;
}

eReturnValues get_Number_Of_LBA_Status_Descriptors(const tDevice* device, uint64_t* numberOfDescriptors)
{
    eReturnValues ret = NOT_SUPPORTED;
    DISABLE_NONNULL_COMPARE
    if (numberOfDescriptors == M_NULLPTR)
    {
        return BAD_PARAMETER;
    }
    RESTORE_NONNULL_COMPARE
    DECLARE_ZERO_INIT_ARRAY(uint8_t, sectorBuffer, LEGACY_DRIVE_SEC_SIZE);
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        uint32_t logSize = UINT32_C(0);
        ret              = get_ATA_Log_Size(device, ATA_LOG_LBA_STATUS, &logSize, true, false);
        if (ret == SUCCESS)
        {
            ret = get_ATA_Log(device, ATA_LOG_LBA_STATUS, M_NULLPTR, M_NULLPTR, true, false, true, sectorBuffer,
                              LEGACY_DRIVE_SEC_SIZE, M_NULLPTR, 0, 0);
            *numberOfDescriptors =
                M_BytesTo8ByteValue(sectorBuffer[7], sectorBuffer[6], sectorBuffer[5], sectorBuffer[4], sectorBuffer[3],
                                    sectorBuffer[2], sectorBuffer[1], sectorBuffer[0]);
        }
        else
        {
            *numberOfDescriptors = 0;
        }
    }
    else // if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        *numberOfDescriptors = 0;
        ret                  = NOT_SUPPORTED;
    }
    return ret;
}

eReturnValues get_LBA_Status_Descriptors(const tDevice*         device,
                                         uint64_t               numberOfDescriptorsExpected,
                                         ptrLbaStatusDescriptor descriptorList)
{
    eReturnValues ret = NOT_SUPPORTED;
    DISABLE_NONNULL_COMPARE
    if (descriptorList == M_NULLPTR)
    {
        return BAD_PARAMETER;
    }
    RESTORE_NONNULL_COMPARE
    // 31 descriptors fit in a 512B sector
    uint64_t getLbaStatusDataSize = numberOfDescriptorsExpected / 31 * LEGACY_DRIVE_SEC_SIZE;
    // need an extra sector for the remaining descriptors
    if (numberOfDescriptorsExpected % 31 != 0)
    {
        getLbaStatusDataSize += LEGACY_DRIVE_SEC_SIZE;
    }
    if (getLbaStatusDataSize > LEGACY_DRIVE_SEC_SIZE * UINT16_MAX)
    {
        printf("WARNING: Drive expected %" PRIu64 " elements which exceed max page count.\n",
               numberOfDescriptorsExpected);
        getLbaStatusDataSize = LEGACY_DRIVE_SEC_SIZE * UINT16_MAX;
    }
    // Note we read not only descriptors but also page 0 which is header
    uint8_t* descriptorBuffer =
        C_CAST(uint8_t*, safe_calloc_aligned(getLbaStatusDataSize + LEGACY_DRIVE_SEC_SIZE, sizeof(uint8_t),
                                             device->os_info.minimumAlignment));
    if (descriptorBuffer != M_NULLPTR)
    {
        uint64_t numberOfDescriptorsReturned = UINT64_C(0);
        if (device->drive_info.drive_type == ATA_DRIVE)
        {
            ret = get_ATA_Log(device, ATA_LOG_LBA_STATUS, M_NULLPTR, M_NULLPTR, true, false, true, descriptorBuffer,
                              getLbaStatusDataSize + LEGACY_DRIVE_SEC_SIZE, M_NULLPTR, 0, 0);
            if (ret == SUCCESS)
            {
                // parse out the descriptors
                for (uint16_t page = 1; page < 1 + (getLbaStatusDataSize / LEGACY_DRIVE_SEC_SIZE); ++page)
                {
                    for (uint16_t pageOffset = 16; pageOffset < LEGACY_DRIVE_SEC_SIZE;
                         pageOffset += 16 /*bytes per descriptor*/)
                    {
                        uint64_t bufferOffset = (((uint64_t)page * LEGACY_DRIVE_SEC_SIZE) + pageOffset);
                        if (numberOfDescriptorsReturned < numberOfDescriptorsExpected)
                        {
                            descriptorList[numberOfDescriptorsReturned].startLba = M_BytesTo8ByteValue(
                                descriptorBuffer[bufferOffset + 7], descriptorBuffer[bufferOffset + 6],
                                descriptorBuffer[bufferOffset + 5], descriptorBuffer[bufferOffset + 4],
                                descriptorBuffer[bufferOffset + 3], descriptorBuffer[bufferOffset + 2],
                                descriptorBuffer[bufferOffset + 1], descriptorBuffer[bufferOffset + 0]);
                            descriptorList[numberOfDescriptorsReturned].numberOfLbas = M_BytesTo4ByteValue(
                                descriptorBuffer[bufferOffset + 11], descriptorBuffer[bufferOffset + 10],
                                descriptorBuffer[bufferOffset + 9], descriptorBuffer[bufferOffset + 8]);
                            descriptorList[numberOfDescriptorsReturned].lbaAccessibility =
                                C_CAST(eLbaAccessibility,
                                       get_8bit_range_uint16(M_BytesTo2ByteValue(descriptorBuffer[bufferOffset + 13],
                                                                                 descriptorBuffer[bufferOffset + 12]),
                                                             4, 1));
                            descriptorList[numberOfDescriptorsReturned].trimStatus =
                                M_ToBool(M_BytesTo2ByteValue(descriptorBuffer[bufferOffset + 13],
                                                             descriptorBuffer[bufferOffset + 12]) &
                                         BIT0);
                            if (descriptorList[numberOfDescriptorsReturned].numberOfLbas == 0)
                            {
                                printf("WARNING: Drive expected %" PRIu64
                                       " elements, but empty descriptor was returned\n",
                                       numberOfDescriptorsExpected);
                                break;
                            }
                            ++numberOfDescriptorsReturned;
                        }
                        else
                        {
                            printf("WARNING: Drive expected %" PRIu64 " elements, but extra were returned\n",
                                   numberOfDescriptorsExpected);
                            break;
                        }
                    }
                }
                ret = SUCCESS;
            }
        }
        else // if (device->drive_info.drive_type == SCSI_DRIVE)
        {
            ret = NOT_SUPPORTED;
        }
        safe_free_aligned(&descriptorBuffer);
    }
    else
    {
        ret = MEMORY_FAILURE;
    }
    return ret;
}

void show_LBA_Status_Descriptors(uint64_t numberOfDescriptors, ptrLbaStatusDescriptor elementList)
{
    print_str("\nStart LBA\tNo. of LBA\tLBA Accessibility             \tTrim Status\n");
    print_str("---------------------------------------------------------------------------\n");
    for (uint64_t descriptorIter = UINT64_C(0); descriptorIter < numberOfDescriptors; ++descriptorIter)
    {
#define LBA_ACCESSIBILITY_STRING_MAX_LEN 30
        DECLARE_ZERO_INIT_ARRAY(char, lbaAccessibilityString, LBA_ACCESSIBILITY_STRING_MAX_LEN);
        switch (elementList[descriptorIter].lbaAccessibility)
        {
        case LBA_ACCESSIBILITY_NOT_REPORTED:
            snprintf_err_handle(lbaAccessibilityString, LBA_ACCESSIBILITY_STRING_MAX_LEN,
                                "Not implemented or reported");
            break;
        case LBA_ACCESSIBILITY_UNACCESSIBLE:
            snprintf_err_handle(lbaAccessibilityString, LBA_ACCESSIBILITY_STRING_MAX_LEN,
                                "Unable to be read or written");
            break;
        case LBA_ACCESSIBILITY_READ_ONLY:
            snprintf_err_handle(lbaAccessibilityString, LBA_ACCESSIBILITY_STRING_MAX_LEN, "Read-only");
            break;
        case LBA_ACCESSIBILITY_WITH_RISK:
            snprintf_err_handle(lbaAccessibilityString, LBA_ACCESSIBILITY_STRING_MAX_LEN, "At risk of inaccessible");
            break;
        case LBA_ACCESSIBILITY_READ_ONLY_WITH_RISK:
            snprintf_err_handle(lbaAccessibilityString, LBA_ACCESSIBILITY_STRING_MAX_LEN, "Read-Only With Risk");
            break;
        default:
            snprintf_err_handle(lbaAccessibilityString, LBA_ACCESSIBILITY_STRING_MAX_LEN, "Unknown (%u)",
                                elementList[descriptorIter].lbaAccessibility);
            break;
        }
        char trimStatusChar = elementList[descriptorIter].trimStatus ? 'Y' : 'N';
        printf("%12" PRIu64 "\t%10" PRIu32 " \t%-30s\t%c\n", elementList[descriptorIter].startLba,
               elementList[descriptorIter].numberOfLbas, lbaAccessibilityString, trimStatusChar);
    }
}
