// SPDX-License-Identifier: MPL-2.0

//! \file secure_file.c
//! \brief Defines functions for working with a file securely according to cert-c
//! recommendations and rules
//! \copyright
//! Do NOT modify or remove this copyright and license
//!
//! Copyright (c) 2024-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/.

#include "secure_file.h"
#include "common_types.h"
#include "io_utils.h"
#include "memory_safety.h"
#include "string_utils.h"
#include "time_utils.h"
#include "type_conversion.h"
#include "warning_ctl.h"

#include <stdarg.h>
#include <string.h>

#if defined(_WIN32)
#    include <direct.h> //getcwd
#else
#    include <unistd.h> //getcwd
#endif

M_PARAM_RW(1)
M_PARAM_RO(2)
eReturnValues replace_File_Name_In_Path(char                  fullPath[M_NONNULL_ARRAY OPENSEA_PATH_MAX],
                                        const char* M_NONNULL newFileName)
{
    char*  ptr        = strrchr(fullPath, SYSTEM_PATH_SEPARATOR);
    size_t ptrLen     = SIZE_T_C(0);
    size_t fullLength = SIZE_T_C(0);
    if (M_NULLPTR != ptr)
    {
        ptr += 1;
    }
    else
    {
        return M_ACCESS_ENUM(eReturnValues, FAILURE);
    }
    ptrLen = safe_strlen(ptr);
    // now that we have a valid pointer, set all the remaining characters to
    // null, then set the new file name in place.
    safe_memset(ptr, ptrLen + 1, 0,
                ptrLen); // destsz has + 1 for null terminator not included in strlen.
    fullLength = (OPENSEA_PATH_MAX - safe_strlen(fullPath));
    snprintf_err_handle(ptr, fullLength, "%s", newFileName);
    return M_ACCESS_ENUM(eReturnValues, SUCCESS);
}

//! \fn void set_Secure_File_error_message(char** outputError, const char* format, ...)
//! \brief This function is used to create the human readable error in secure file API calls
FUNC_ATTR_PRINTF(2, 3) static M_INLINE void set_Secure_File_error_message(char** outputError, const char* format, ...)
{
    if (outputError != M_NULLPTR)
    {
        va_list args;
        va_start(args, format);

        // If an error message is already present, then we need to free it before
        // creating a new one to prevent a memory leak
        if (*outputError != M_NULLPTR)
        {
            explicit_zeroes(*outputError, safe_strlen(*outputError));
            safe_free(outputError);
        }

        DISABLE_WARNING_FORMAT_NONLITERAL
        int result = vasprintf(outputError, format, args);
        RESTORE_WARNING_FORMAT_NONLITERAL

        va_end(args);
        if (result < 0 && *outputError != M_NULLPTR)
        {
            safe_free(outputError);
        }
    }
}

void free_File_Attributes(fileAttributes* M_NULLABLE* M_NULLABLE attributes)
{
    if (attributes != M_NULLPTR)
    {
        if (*attributes != M_NULLPTR)
        {
#if defined(_WIN32)
            if ((*attributes)->winSecurityDescriptor)
            {
                explicit_zeroes((*attributes)->winSecurityDescriptor, (*attributes)->securityDescriptorStringLength);
                safe_free_core(C_CAST(void**, &(*attributes)->winSecurityDescriptor));
                (*attributes)->securityDescriptorStringLength = 0;
            }
#endif //_WIN32
            explicit_zeroes(*attributes, sizeof(fileAttributes));
            safe_free_core(M_REINTERPRET_CAST(void**, attributes));
        }
    }
}

void free_Secure_File_Info(secureFileInfo* M_NULLABLE* M_NULLABLE fileInfo)
{
    if (fileInfo != M_NULLPTR)
    {
        if (*fileInfo != M_NULLPTR)
        {
            if ((*fileInfo)->attributes != M_NULLPTR)
            {
                free_File_Attributes(&(*fileInfo)->attributes);
                (*fileInfo)->attributes = M_NULLPTR;
            }
            if ((*fileInfo)->uniqueID != M_NULLPTR)
            {
                explicit_zeroes((*fileInfo)->uniqueID, sizeof(fileUniqueIDInfo));
                safe_free_core(C_CAST(void**, &(*fileInfo)->uniqueID));
            }
            if ((*fileInfo)->errorString != M_NULLPTR)
            {
                explicit_zeroes(M_CONST_CAST(void*, (*fileInfo)->errorString), safe_strlen((*fileInfo)->errorString));
                safe_free_core(M_CONST_CAST(void**, &(*fileInfo)->errorString));
            }
            explicit_zeroes(*fileInfo, sizeof(secureFileInfo));
        }
        safe_free_core(C_CAST(void**, fileInfo));
    }
}

// This exists to fix this cert-c identified issue
// https://wiki.sei.cmu.edu/confluence/display/c/EXP42-C.+Do+not+compare+padding+data
M_PARAM_RO(1)
M_PARAM_RO(2)
bool compare_File_Unique_ID(const fileUniqueIDInfo* M_NONNULL a, const fileUniqueIDInfo* M_NONNULL b)
{
    bool equal = false;
    if (a != M_NULLPTR && b != M_NULLPTR)
    {
        if (a->inode == b->inode && 0 == memcmp(a->fileid, b->fileid, FILE_UNIQUE_ID_ARR_MAX))
        {
            equal = true;
        }
    }
    return equal;
}

// The purpose of this function is to perform the security validation necessary
// to make sure this is a valid file on the system and minimize path traversal
// and validate permissions as much as reasonably possible. The goal is
// mitigation of https://cwe.mitre.org/data/definitions/22.html Will be using
// recommendations from
// https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87151932
//      as much as possible to accomplish this.-TJE
// expectedFileInfo can be M_NULLPTR for the first time opening a file.
//  If reopening a file used earlier, it is recommended to provide this info so
//  it can be validated as the same file It is recommended to not reopen files,
//  but that may not always be possible. So this exists to help validate that a
//  file has not changed in some unexpected way.-TJE
M_NODISCARD_REASON("The returned pointer must be freed by the caller using free_Secure_File_Info()")
M_NULL_TERM_STRING(1)
M_PARAM_RO(1)
M_NULL_TERM_STRING(2)
M_PARAM_RO(2)
M_PARAM_RO(3)
M_PARAM_RO(4)
M_PARAM_RO(5)
secureFileInfo* M_NULLABLE secure_Open_File(const char* M_NONNULL        filename,
                                            const char* M_NONNULL        mode,
                                            const fileExt* M_NULLABLE    extList /*optional*/,
                                            fileAttributes* M_NULLABLE   expectedFileInfo /*optional*/,
                                            fileUniqueIDInfo* M_NULLABLE uniqueIdInfo /*optional*/)
{
    secureFileInfo* fileInfo = M_REINTERPRET_CAST(secureFileInfo*, safe_calloc(1, sizeof(secureFileInfo)));
    if (fileInfo && filename != M_NULLPTR &&
        mode != M_NULLPTR) /* Not checking extList and outInfo because it is optional */
    {
        bool  creatingFile  = false;
        bool  exclusiveFlag = false;
        char* internalmode  = M_CONST_CAST(char*, mode); // we will dup mode if we need to modify it later, so
                                                         // this cast is to get rid of a warning
        bool  duplicatedModeForInternalUse = false;
        char* intFileName                  = M_NULLPTR; // allocated/dup'd later
        fileInfo->file                     = M_NULLPTR;
        if (strchr(internalmode, 'w') || strchr(internalmode, 'a'))
        {
            // file is being created and does not exist yet.
            // Get the canonical path to the directory that the file will be in
            creatingFile = true;
        }
        // C11 adds 'x' to mode so that on a write, this forces a failure
        // Since we may not always be in a system with C11 or support for
        // wx/w+x, handle this manually.
        if (strchr(internalmode, 'w') && strchr(internalmode, 'x'))
        {
            exclusiveFlag = true;
#if !defined(USING_C11) && !(defined(_MSC_VER) && _MSC_VER < MSVC_2013) && !(defined(THIS_IS_GLIBC)) &&                \
    !(defined(USING_MUSL_LIBC) && USING_MUSL_LIBC > 0) // Glibc supports x. VS2013+ support x
            // For systems that do not support 'x' need to adjust the mode
            // manually so that we do not pass an invalid 'x' flag when it is
            // not supported.
            //       While this can always be done regardless of
            //       standard/library, it is better to pass flags onwards when
            //       they are supported for additional verification by the
            //       library/system - TJE
            duplicatedModeForInternalUse = true;
            if (0 != safe_strdup(&internalmode, mode))
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_MODE);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "Invalid File Mode");
                return fileInfo;
            }
            char* thex = strchr(internalmode, 'x');
            if (thex != M_NULLPTR)
            {
                // remove it since it is not supported outside C11 and a few
                // \libraries that use it as an extension
                if (safe_strlen(thex) > 1)
                {
                    size_t lenx = safe_strlen(thex);
                    safe_memmove(thex, lenx, thex + 1, lenx - 1);
                    thex[lenx - 1] = '\0';
                }
                else
                {
                    thex[0] = '\0';
                }
            }
#endif //! C11 && !MSVC 2013+ && !GLIBC
        }
        else if (strchr(internalmode, 'x'))
        {
            // invalid mode. X is only allowed with W, W+
            fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_MODE);
            set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                          "Invalid File Mode. x is only allowed with w or w+");
            return fileInfo;
        }
        // Get canonical path here before doing anything else???
        if (creatingFile)
        {
            // setup path to the directory to make sure it exists since the file
            // does not yet. NOTE: If we only have the current file name, no
            // relative path, etc, need to call a function to get the current
            // working directory to
            //       use that as the path.
            if (strchr(filename, '/') // always check for forwards slash since
                                      // Windows can accept this
#if defined(_WIN32)
                || strchr(filename,
                          '\\') // only check for backslash on Windows since
                                // this is a common path seperator on Windows
                                // and is the "prefered" format
#endif                          //_WIN32
            )
            {
                // there is a path or relative path that can be used to setup
                // for getting the canonical path
                const char* lastsep = strrchr(filename, '/');
#if defined(_WIN32)
                // In Windows, we need to determine if the final seperator is a
                // / or \. So also check for \ and figure out which was the last
                // one. A user can pass a path with both and it can be accepted
                // by Windows, which is why we validate both of these.
                const char* lastwinsep = strrchr(filename, '\\');
                if (lastsep == M_NULLPTR || C_CAST(uintptr_t, lastwinsep) > C_CAST(uintptr_t, lastsep))
                {
                    // backslash was detected last, so change to this pointer
                    // instead for strndup
                    lastsep = lastwinsep;
                }
#endif //_WIN32
       // path only. No file name
                uintptr_t duplen = C_CAST(uintptr_t, lastsep) - C_CAST(uintptr_t, filename);
                if (duplen == 0)
                {
                    if (0 != safe_strndup(&intFileName, filename, 1))
                    {
                        fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_PATH);
                        set_Secure_File_error_message(
                            M_CONST_CAST(char**, &fileInfo->errorString),
                            "Failed setting up path for security verification - path + file name provided");
                        return fileInfo;
                    }
                }
                else if (0 != safe_strndup(&intFileName, filename, duplen))
                {
                    fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_PATH);
                    set_Secure_File_error_message(
                        M_CONST_CAST(char**, &fileInfo->errorString),
                        "Failed setting up path for security verification - path + file name provided");
                    return fileInfo;
                }
            }
            else
            {
                // no path, only the filename itself.
                // get the current working directory to use for the canonical
                // file and path. Will use getcwd to get this information. NOTE:
                // A common extension to getcwd is passing M_NULLPTR will have
                // it allocate and return a buffer.
                //       This seems to exist on Linux, FreeBSD, Windows
                //       (_getcwd), openbsd, netbsd, and solaris...possibly
                //       more. While this is convenient, we are not using this
                //       to make sure behavior is as expected according to POSIX
                // NOTE: Windows can also use
                // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getcurrentdirectory
                // need to allocate and call again
                char* workingdir = M_REINTERPRET_CAST(char*, safe_malloc(OPENSEA_PATH_MAX));
                if (workingdir == M_NULLPTR)
                {
                    // return an error for invalid path
                    set_Secure_File_error_message(
                        M_CONST_CAST(char**, &fileInfo->errorString),
                        "Failed setting up path for security verification - only file name provided");
                    fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_PATH);

                    return fileInfo;
                }
                workingdir = getcwd(workingdir, OPENSEA_PATH_MAX);
                if (workingdir == M_NULLPTR)
                {
                    // return an error for invalid path
                    fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_PATH);
                    set_Secure_File_error_message(
                        M_CONST_CAST(char**, &fileInfo->errorString),
                        "Failed getting the current working directory for secure path validation");
                    return fileInfo;
                }
                intFileName = workingdir; // we will free this later when we
                                          // free intfilename
            }
        }
        else
        {
            // not creating a new file, so we will have what we need to get a
            // full canonical path
            if (0 != safe_strdup(&intFileName, filename))
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_PATH);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                              "Failed duplicating filename for internal validation");
                return fileInfo;
            }
        }
        if (intFileName == M_NULLPTR)
        {
            // return an error for invalid path
            fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_PATH);
            set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                          "Invalid internal copy of filename");
            return fileInfo;
        }
        if (M_ACCESS_ENUM(eReturnValues, SUCCESS) !=
            get_Full_Path(intFileName, M_CONST_CAST(char*, fileInfo->fullpath)))
        {
            // unable to get the full path to this file.
            // This means something went wrong, and we need to return an error.
            fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_PATH);
            set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                          "Failure while looking up canonical file path");
            safe_free(&intFileName);
            return fileInfo;
        }

        bool fileexists = false;
        if (!creatingFile || exclusiveFlag)
        {
            fileexists = os_File_Exists(intFileName);
            if (exclusiveFlag && fileexists)
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FILE_ALREADY_EXISTS);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                              "Error: File already exists");
                if (duplicatedModeForInternalUse)
                {
                    safe_free(&internalmode);
                }
                safe_free(&intFileName);
                return fileInfo;
            }
            else if (!creatingFile && !fileexists)
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_FILE);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                              "Invalid File Specified - file does not exist");
                if (duplicatedModeForInternalUse)
                {
                    safe_free(&internalmode);
                }
                safe_free(&intFileName);
                return fileInfo;
            }
        }
        fileAttributes* beforeattrs = M_NULLPTR;
        if (creatingFile)
        {
            // append the filename to the end on fileInfo->fullpath
            // first add a trailing slash since one will not be present
            safe_strcat(M_CONST_CAST(char*, fileInfo->fullpath), OPENSEA_PATH_MAX, SYSTEM_PATH_SEPARATOR_STR);
            if (strchr(filename, '/') // always check for forwards slash since
                                      // Windows can accept this
#if defined(_WIN32)
                || strchr(filename,
                          '\\') // only check for backslash on Windows since
                                // this is a common path seperator on Windows
                                // and is the "prefered" format
#endif                          //_WIN32
            )
            {
                // there is a path or relative path that we do not want to
                // append to the canonical path
                const char* lastsep = strrchr(filename, '/');
#if defined(_WIN32)
                // In Windows, we need to determine if the final seperator is a
                // / or \. So also check for \ and figure out which was the last
                // one. A user can pass a path with both and it can be accepted
                // by Windows, which is why we validate both of these.
                const char* lastwinsep = strrchr(filename, '\\');
                if (C_CAST(uintptr_t, lastwinsep) > C_CAST(uintptr_t, lastsep))
                {
                    // backslash was detected last, so change to this pointer
                    // instead for strndup
                    lastsep = lastwinsep;
                }
#endif //_WIN32
                safe_strcat(M_CONST_CAST(char*, fileInfo->fullpath), OPENSEA_PATH_MAX, lastsep + 1);
            }
            else
            {
                safe_strcat(M_CONST_CAST(char*, fileInfo->fullpath), OPENSEA_PATH_MAX, filename);
            }
        }
        else
        {
            // Validate the file extension if one was provided to check for
            // since the full path will also resolve the extension.
            //         This check does not guarantee a file is correct since
            //         anyone can rename an extension, but it should help make
            //         sure the file being opened is closer to what is expected
            //         by the caller
            if (extList != M_NULLPTR)
            {
                fileExt* currentExtension = M_CONST_CAST(fileExt*, extList); // we will increment this pointer as
                                                                             // needed, so it cannot be const
                bool foundValidExtension = false;
                while (currentExtension && currentExtension->ext != M_NULLPTR)
                {
                    const char* extension = strrchr(fileInfo->fullpath, '.');
                    if (extension != M_NULLPTR &&
                        ((currentExtension->caseInsensitive &&
                          (wildcard_case_match(extension, currentExtension->ext) == true ||
                           wildcard_case_match(extension + 1, currentExtension->ext) == true)) ||
                         (wildcard_match(extension, currentExtension->ext) == true ||
                          wildcard_match(extension + 1, currentExtension->ext) == true)))
                    {
                        // valid extension
                        foundValidExtension = true;
                        break;
                    }
                    currentExtension += 1;
                }
                if (!foundValidExtension)
                {
                    safe_free(&intFileName);
                    if (duplicatedModeForInternalUse)
                    {
                        safe_free(&internalmode);
                    }
                    fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_FILE_EXTENSION);
                    set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                                  "Invalid File Extension. Does not match provided extension list");
                    return fileInfo;
                }
            }
            beforeattrs = os_Get_File_Attributes_By_Name(fileInfo->fullpath);
            // Do a first pass on file attribute comparison with the expected
            // file attributes. If no match, return an error This may be done
            // again later after opening the file since some things will not be
            // available without opening the file handle.
            if (expectedFileInfo != M_NULLPTR)
            {
                // check device ID and inode. These are unique on POSIX as a
                // file ID. Windows is not far enough along to get this info
                // yet.
                if ((expectedFileInfo->deviceID != beforeattrs->deviceID) ||
                    (expectedFileInfo->inode != beforeattrs->inode) ||
                    (expectedFileInfo->userID != beforeattrs->userID) ||
                    (expectedFileInfo->groupID != beforeattrs->groupID))
                {
                    fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_FILE_ATTRIBUTES);
                    set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                                  "Invalid file attributes detected. Does not match expected data");
                    safe_free(&intFileName);
                    free_File_Attributes(&beforeattrs);
                    if (duplicatedModeForInternalUse)
                    {
                        safe_free(&internalmode);
                    }
                    return fileInfo;
                }
#if defined(_WIN32)
                // Validate Windows SIDs are equivalent
                // Convert the strings back to structures and use EqualSid and
                // EqualAcl to do the comparisons. There may be other formatting
                // in the string that does not affect the permissions which is
                // why the conversion is necessary.
                if (!exact_Compare_SIDS_And_DACL_Strings(beforeattrs->winSecurityDescriptor,
                                                         expectedFileInfo->winSecurityDescriptor))
                {
                    fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_FILE_ATTRIBUTES);
                    set_Secure_File_error_message(
                        M_CONST_CAST(char**, &fileInfo->errorString),
                        "Invalid Windows File security descriptor. Does not match provided security descriptor.");
                    safe_free(&intFileName);
                    if (duplicatedModeForInternalUse)
                    {
                        safe_free(&internalmode);
                    }
                    free_File_Attributes(&beforeattrs);
                    return fileInfo;
                }
#endif //_WIN32
            }
        }
        free_File_Attributes(&beforeattrs);

        // Canonical path will already have the proper system path separator in
        // it. No need to search / in Windows
        fileInfo->filename =
            strrchr(fileInfo->fullpath, SYSTEM_PATH_SEPARATOR) + 1; // plus 1 to get past final seperator

        // Need to verify only the path. Passing the file in with it will cause
        // it to fail since it is not a directory
        char*       pathOnly               = M_CONST_CAST(char*, fileInfo->fullpath);
        bool        allocatedLocalPathOnly = false;
        const char* lastsep                = strrchr(fileInfo->fullpath, '/');
#if defined(_WIN32)
        // In Windows, we need to determine if the final seperator is a / or \.
        // So also check for \ and figure out which was the last one.
        // A user can pass a path with both and it can be accepted by Windows,
        // which is why we validate both of these.
        const char* lastwinsep = strrchr(fileInfo->fullpath, '\\');
        if (C_CAST(uintptr_t, lastwinsep) > C_CAST(uintptr_t, lastsep))
        {
            // backslash was detected last, so change to this pointer instead
            // for strndup
            lastsep = lastwinsep;
        }
#endif //_WIN32

        if (safe_strndup(&pathOnly, fileInfo->fullpath,
                         C_CAST(uintptr_t, lastsep) - C_CAST(uintptr_t, fileInfo->fullpath)) == 0 &&
            pathOnly != M_NULLPTR)
        {
            allocatedLocalPathOnly = true;
        }
        else
        {
            pathOnly = M_CONST_CAST(char*, fileInfo->fullpath);
        }

#if defined(_DEBUG)
        printf("Checking directory security: %s\n", pathOnly);
#endif
        // This flag can disable the file path security check.
        // NOTE: Currently disabling _WIN32 due to new Windows security feature breaking
        // how current code works.
        //       This will be reenabled once we have resolved the low-level issue.
#if defined(DISABLE_SECURE_FILE_PATH_CHECK) || defined(_WIN32)
#    if !defined(_WIN32)
#        pragma message(                                                                                               \
            "WARNING: Disabling Cert-C directory security check. This is not recommended for production level code.")
#    endif //!_WIN32
        if (/* DISABLES CODE */ (true))
#else
        // Check for secure directory - This code must traverse the full path
        // and validate permissions of the directories.
        if (os_Is_Directory_Secure(pathOnly, M_CONST_CAST(char**, &fileInfo->errorString)))
#endif // DISABLE_SECURE_FILE_PATH_CHECK || _WIN32
        {
            errno_t fopenError = 0;
            fileInfo->file     = M_NULLPTR;
            fopenError         = safe_fopen(&fileInfo->file, fileInfo->fullpath, internalmode);
            if (fopenError == 0 && fileInfo->file != M_NULLPTR)
            {
                // file is opened, check unique id if it was passed in to
                // compare against
                fileInfo->uniqueID = os_Get_File_Unique_Identifying_Information(fileInfo->file);
                // compre to provided unique ID
                if (uniqueIdInfo != M_NULLPTR)
                {
                    if (!compare_File_Unique_ID(uniqueIdInfo, fileInfo->uniqueID))
                    {
                        // This is not the same file as was meant to be opened.
                        // Close it and return an error.
                        M_STATIC_CAST(void, fclose(fileInfo->file));
                        fileInfo->file  = M_NULLPTR;
                        fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_FILE_UNIQUE_ID);
                        set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                                      "Invalid File Unique ID. Does not match expected unique ID");
                        safe_free(&intFileName);
                        if (duplicatedModeForInternalUse)
                        {
                            safe_free(&internalmode);
                        }
                        if (pathOnly && allocatedLocalPathOnly)
                        {
                            safe_free(&pathOnly);
                        }
                        return fileInfo;
                    }
                }
                fileInfo->attributes = os_Get_File_Attributes_By_File(fileInfo->file);
                // compare to user provided attributes
                if (expectedFileInfo != M_NULLPTR)
                {
                    // devid and inode already checked, but if user didn't
                    // provide unique id, it will not hurt to check again
                    if ((expectedFileInfo->deviceID != fileInfo->attributes->deviceID) ||
                        (expectedFileInfo->inode != fileInfo->attributes->inode) ||
                        (expectedFileInfo->userID != fileInfo->attributes->userID) ||
                        (expectedFileInfo->groupID != fileInfo->attributes->groupID))
                    {
                        M_STATIC_CAST(void, fclose(fileInfo->file));
                        fileInfo->file  = M_NULLPTR;
                        fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_FILE_ATTRIBUTES);
                        set_Secure_File_error_message(
                            M_CONST_CAST(char**, &fileInfo->errorString),
                            "Invalid File Attributes. Does not match provided attributes for identification");
                        safe_free(&intFileName);
                        if (duplicatedModeForInternalUse)
                        {
                            safe_free(&internalmode);
                        }
                        if (pathOnly && allocatedLocalPathOnly)
                        {
                            safe_free(&pathOnly);
                        }
                        return fileInfo;
                    }
#if defined(_WIN32)
                    // Validate Windows SIDs are equivalent
                    // Convert the strings back to structures and use EqualSid
                    // and EqualAcl to do the comparisons. There may be other
                    // formatting in the string that does not affect the
                    // permissions which is why the conversion is necessary.
                    if (!exact_Compare_SIDS_And_DACL_Strings(fileInfo->attributes->winSecurityDescriptor,
                                                             expectedFileInfo->winSecurityDescriptor))
                    {
                        M_STATIC_CAST(void, fclose(fileInfo->file));
                        fileInfo->file  = M_NULLPTR;
                        fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_FILE_ATTRIBUTES);
                        set_Secure_File_error_message(
                            M_CONST_CAST(char**, &fileInfo->errorString),
                            "Invalid Windows security descriptor. Does not match provided value.");
                        safe_free(&intFileName);
                        if (duplicatedModeForInternalUse)
                        {
                            safe_free(&internalmode);
                        }
                        if (pathOnly && allocatedLocalPathOnly)
                        {
                            safe_free(&pathOnly);
                        }
                        return fileInfo;
                    }
#endif //_WIN32
                }

                fileInfo->isValid = true;

#if defined(USING_C11) && defined(to_sizet)
                fileInfo->fileSize = to_sizet(fileInfo->attributes->filesize);
#else
                fileInfo->fileSize = int64_to_sizet(fileInfo->attributes->filesize);
#endif
                fileInfo->fileno = fileno(fileInfo->file);
#if defined(_DEBUG)
                printf("Filesize set to %zu\n", fileInfo->fileSize);
#endif
            }
            else
            {
                switch (fopenError)
                {
                    // add other errno values to check for?
                    // https://pubs.opengroup.org/onlinepubs/9699919799/functions/fopen.html
                case EINVAL:
                default:
                    fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE);
                    set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                                  "Failed to open file with fopen");
                    break;
                }
            }
        }
        else
        {
            fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INSECURE_PATH);
            printf("Insecure path detected: %s\n", fileInfo->errorString);
        }
        if (pathOnly && allocatedLocalPathOnly)
        {
            safe_free(&pathOnly);
        }
        if (duplicatedModeForInternalUse)
        {
            safe_free(&internalmode);
        }
        safe_free(&intFileName);
    }
    else if (fileInfo != M_NULLPTR)
    {
        fileInfo->file  = M_NULLPTR;
        fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE);
        set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                      "Failed to open file. No file specified or no mode specified");
    }
    return fileInfo;
}

// We can add additional things to do before closing the file to validate
// things, flush, etc as needed to make this better.
M_NODISCARD M_PARAM_RW(1) eSecureFileError secure_Close_File(secureFileInfo* M_NONNULL fileInfo)
{
    if (fileInfo != M_NULLPTR)
    {
        fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_FILE);
        set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "Invalid secureFileInfo");
        if (fileInfo->file != M_NULLPTR)
        {
            int closeres = fclose(fileInfo->file);
            if (closeres == 0)
            {
                fileInfo->file  = M_NULLPTR;
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_SUCCESS);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "File closed successfully");
            }
            else if (closeres == EOF)
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE);
                set_Secure_File_error_message(
                    M_CONST_CAST(char**, &fileInfo->errorString),
                    "Failed to close the file. Do not attempt access to this file descriptor again!");
            }
            else
            {
                // unknown result, so call this an error.
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                              "Unknown failure encountered while closing the file.");
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE);
            }
        }
        else if (fileInfo->error != M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE))
        {
            // File was never opened, so return no error
            set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                          "File was never opened. No error");
            fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_SUCCESS);
        }
        return fileInfo->error;
    }
    return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_SECURE_FILE);
}

M_NODISCARD M_PARAM_RW(1) M_PARAM_WO_SIZE(2, 3) M_PARAM_WO(6) eSecureFileError
    secure_Read_File(secureFileInfo* M_RESTRICT M_NONNULL fileInfo,
                     void* M_RESTRICT M_NONNULL           buffer,
                     size_t                               buffersize,
                     size_t                               elementsize,
                     size_t                               count,
                     size_t* M_NULLABLE                   numberread /*optional*/)
{
    if (fileInfo != M_NULLPTR)
    {
        if (fileInfo->error == M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE))
        {
            return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE);
        }
        fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_FILE);
        set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "Invalid secureFileInfo");
        if (fileInfo->file)
        {
            size_t readres = SIZE_T_C(0);
#if defined(HAVE_MSFT_SECURE_LIB) && !defined(__MINGW32__)
            readres = fread_s(buffer, buffersize, elementsize, count, fileInfo->file);
#else
            if (buffer == M_NULLPTR)
            {
                fileInfo->error = SEC_FILE_INVALID_PARAMETER;
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                              "Invalid memory buffer pointer provided for reading the file");
                return fileInfo->error;
            }
            else if (buffersize < (elementsize * count))
            {
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                              "Buffer is too small to read the file");
                fileInfo->error = SEC_FILE_BUFFER_TOO_SMALL;
                return fileInfo->error;
            }
            readres = fread(buffer, elementsize, count, fileInfo->file);
#endif
            if (numberread != M_NULLPTR)
            {
                *numberread = readres;
            }
            if (readres == 0)
            {
                if (elementsize == 0 || count == 0)
                {
                    fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_SUCCESS);
                    set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                                  "File read without error");
                }
                else
                {
                    fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_READ_WRITE_ERROR);
                    set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                                  "Read error occurred. No elements read.");
                }
            }
            else if (readres < count)
            {
                if (feof(fileInfo->file))
                {
                    // end of stream/file
                    fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_END_OF_FILE_REACHED);
                    set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "End of file reached");
                }
                else if (ferror(fileInfo->file))
                {
                    fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_READ_WRITE_ERROR);
                    set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                                  "File read error occurred");
                }
                else
                {
                    // some other kind of error???
                    set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                                  "Unknown error occurred");
                    fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE);
                }
            }
            else if (readres == count)
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_SUCCESS);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                              "All elements read successfully");
            }
            else
            {
                // unknown result, so call this an error.
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "Unknown error occurred");
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE);
            }
        }
        return fileInfo->error;
    }
    return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_SECURE_FILE);
}

M_NODISCARD M_PARAM_RW(1) M_PARAM_RO_SIZE(2, 3) M_PARAM_WO(6) eSecureFileError
    secure_Write_File(secureFileInfo* M_RESTRICT M_NONNULL fileInfo,
                      void* M_RESTRICT M_NONNULL           buffer,
                      size_t                               buffersize,
                      size_t                               elementsize,
                      size_t                               count,
                      size_t* M_NULLABLE                   numberwritten /*optional*/)
{
    if (fileInfo != M_NULLPTR)
    {
        if (fileInfo->error == M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE))
        {
            return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE);
        }
        fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_FILE);
        set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "Invalid secureFileInfo");
        if (fileInfo->file)
        {
            size_t writeres = SIZE_T_C(0);
            if (buffer == M_NULLPTR)
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_PARAMETER);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "Invalid write buffer");
                return fileInfo->error;
            }
            else if (buffersize < (elementsize * count))
            {
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                              "Write buffer too small for number of elements specified.");
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_BUFFER_TOO_SMALL);
                return fileInfo->error;
            }
            writeres = fwrite(buffer, elementsize, count, fileInfo->file);
            if (numberwritten != M_NULLPTR)
            {
                *numberwritten = writeres;
            }
            if (writeres == 0)
            {
                if (elementsize == 0 || count == 0)
                {
                    fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_SUCCESS);
                    set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                                  "File written successfully");
                }
                else
                {
                    fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_READ_WRITE_ERROR);
                    set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "File write error.");
                }
            }
            else if (writeres < count)
            {
                if (ferror(fileInfo->file))
                {
                    fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_WRITE_DISK_FULL);
                    set_Secure_File_error_message(
                        M_CONST_CAST(char**, &fileInfo->errorString),
                        "Error writing file. Check if file system is full and has more room.");
                }
                else
                {
                    // some other kind of error???
                    set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                                  "Failed to write all elements");
                    fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_READ_WRITE_ERROR);
                }
            }
            else if (writeres == count)
            {
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                              "File written successfully");
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_SUCCESS);
            }
            else
            {
                // unknown result, so call this an error.
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "Unknown file write error");
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE);
            }
        }
        return fileInfo->error;
    }
    return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_SECURE_FILE);
}

M_NODISCARD M_PARAM_RW(1) eSecureFileError
    secure_Seek_File(secureFileInfo* M_NONNULL fileInfo, oscoffset_t offset, int initialPosition)
{
    if (fileInfo != M_NULLPTR)
    {
        if (fileInfo->error == M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE))
        {
            return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE);
        }
        fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_FILE);
        set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "Invalid secureFileInfo");
        if (fileInfo->file)
        {
            int seekres = 0;
            // Windows has _fseeki64, which may be better to use instead for
            // larger files or to be compatible with larger files. Linux/posix
            // have fseeko and ftello which use off_t which can be wider as
            // well. (POSIX 2001)
#if defined(_WIN32) /*version check for this function?*/
            seekres = _fseeki64(fileInfo->file, offset, initialPosition);
#elif defined(POSIX_2001)
            seekres = fseeko(fileInfo->file, offset, initialPosition);
#else
            seekres = fseek(fileInfo->file, offset, initialPosition);
#endif
            if (seekres == 0)
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_SUCCESS);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "File seek success");
            }
            else
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_SEEK_FAILURE);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "Error seeking file");
            }
        }
        return fileInfo->error;
    }
    return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_SECURE_FILE);
}

M_NODISCARD M_PARAM_RW(1) eSecureFileError secure_Rewind_File(secureFileInfo* M_NONNULL fileInfo)
{
    if (fileInfo != M_NULLPTR)
    {
        if (fileInfo->error == M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE))
        {
            return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE);
        }
        fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_FILE);
        set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "Invalid secureFileInfo");
        if (fileInfo->file)
        {
            fileInfo->error = secure_Seek_File(fileInfo, 0, SEEK_SET);
#if defined(HAVE_MSFT_SECURE_LIB)
            clearerr_s(fileInfo->file);
#else
            clearerr(fileInfo->file); // rewind clears errors and eof indicators, so
                                      // call this to clear the errors in the stream.
#endif
        }
        return fileInfo->error;
    }
    return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_SECURE_FILE);
}

M_NODISCARD M_PARAM_RW(1) oscoffset_t secure_Tell_File(secureFileInfo* M_NONNULL fileInfo)
{
    if (fileInfo != M_NULLPTR)
    {
        if (fileInfo->error == M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE))
        {
            return -1;
        }
        fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_FILE);
        set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "Invalid secureFileInfo");
        if (fileInfo->file)
        {
            oscoffset_t tellres = 0;
            // Windows has _fseeki64, which may be better to use instead for larger files or to be compatible with
            // larger files. Linux/posix have fseeko and ftello which use off_t which can be wider as well. (POSIX 2001)
            errno = 0; // ISO secure coding standard recommends this to ensure errno is interpretted correctly after
                       // this call
#if defined(_WIN32)    /*version check for this function?*/
            tellres = _ftelli64(fileInfo->file);
#elif defined(POSIX_2001)
            tellres = ftello(fileInfo->file);
#else
            tellres = ftell(fileInfo->file);
#endif
            if (tellres >= 0)
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_SUCCESS);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "File tell success");
            }
            else
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_SEEK_FAILURE);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "File tell error");
            }
            return tellres;
        }
        return -1;
    }
    return -1;
}

M_PARAM_RW(1)
eSecureFileError secure_Flush_File(secureFileInfo* M_NONNULL fileInfo)
{
    if (fileInfo != M_NULLPTR)
    {
        if (fileInfo->error == M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE))
        {
            return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE);
        }
        fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_FILE);
        set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "Invalid secureFileInfo");
        if (fileInfo->file)
        {
            int fflushres = fflush(fileInfo->file);
            if (fflushres == 0)
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_SUCCESS);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "File flush successful");
            }
            else if (fflushres == EOF && ferror(fileInfo->file))
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FLUSH_FAILURE);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "Failed flushing file");
            }
            else /*not sure what to clasify this error as*/
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "Unknown file flush error");
            }
        }
        return fileInfo->error;
    }
    return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_SECURE_FILE);
}

M_NODISCARD M_PARAM_RW(1) eSecureFileError secure_Remove_File(secureFileInfo* M_NONNULL fileInfo)
{
    if (fileInfo != M_NULLPTR)
    {
        if (fileInfo->error == M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE))
        {
            return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE);
        }
        fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_PATH);
        set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "Invalid secureFileInfo");
        if (fileInfo->file && safe_strlen(fileInfo->fullpath) > 0)
        {
            // unlink the file is possible
#if defined(_WIN32)
            if (0 != _unlink(fileInfo->fullpath))
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                              "Failed to unlink file from file system");
            }
            else
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_SUCCESS);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                              "File unlinked successfully from file system");
            }
#elif defined(POSIX_2001) || defined(BSD4_3) || defined(__svr4__)
            if (0 != unlink(fileInfo->fullpath))
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                              "Failed to unlink file from file system");
            }
            else
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_SUCCESS);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                              "File unlinked successfully from file system");
            }
#else
            fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_CANNOT_REMOVE_FILE_STILL_OPEN);
            set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                          "Unable to remove file. File handle still open.");
#endif
        }
        else if (safe_strlen(fileInfo->fullpath) > 0)
        {
            // remove the file
            if (0 != remove(fileInfo->fullpath))
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "Failed to remove file");
            }
            else
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_SUCCESS);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                              "File removed successfully");
            }
        }
        return fileInfo->error;
    }
    return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_SECURE_FILE);
}

M_NODISCARD M_PARAM_RO(1) eSecureFileError
    secure_Delete_File_By_Name(const char* M_NONNULL filename, eSecureFileDeleteNameAction deleteAction)
{
    if (filename != M_NULLPTR)
    {
        // first get cannonical name
        DECLARE_ZERO_INIT_ARRAY(char, fullpath, 4096);
        if (M_ACCESS_ENUM(eReturnValues, SUCCESS) != get_Full_Path(filename, C_CAST(char*, fullpath)))
        {
            // unable to get the full path to this file.
            // This means something went wrong, and we need to return an error.
            return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_PATH);
        }
        char* lastsep = strrchr(fullpath, '/');
#if defined(_WIN32)
        // In Windows, we need to determine if the final seperator is a / or \.
        // So also check for \ and figure out which was the last one.
        // A user can pass a path with both and it can be accepted by Windows,
        // which is why we validate both of these.
        char* lastwinsep = strrchr(fullpath, '\\');
        if (C_CAST(uintptr_t, lastwinsep) > C_CAST(uintptr_t, lastsep))
        {
            // backslash was detected last, so change to this pointer instead
            // for strndup
            lastsep = lastwinsep;
        }
#endif //_WIN32
       // Check for secure directory - This code must traverse the full path and
       // validate permissions of the directories.
        char* pathOnly = M_NULLPTR;
        if (safe_strndup(&pathOnly, fullpath, C_CAST(uintptr_t, lastsep) - C_CAST(uintptr_t, fullpath)) != 0 ||
            pathOnly == M_NULLPTR)
        {
            pathOnly = C_CAST(char*, fullpath);
        }
        if (!os_Is_Directory_Secure(pathOnly, M_NULLPTR))
        {
            safe_free(&pathOnly);
            return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INSECURE_PATH);
        }
        safe_free(&pathOnly);
        // Check if the file is already open before attempting to remove it
        errno_t fileerror  = 0;
        FILE*   checkExist = M_NULLPTR;
        fileerror          = safe_fopen(&checkExist, fullpath, "r");
        if (checkExist != M_NULLPTR && fileerror == 0)
        {
            M_STATIC_CAST(void, fclose(checkExist));
            checkExist = M_NULLPTR;
            if (0 == remove(fullpath))
            {
                return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_SUCCESS);
            }
            else
            {
                return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE);
            }
        }
        else if (os_File_Exists(fullpath))
        {
            switch (deleteAction)
            {
            case M_ACCESS_ENUM(eSecureFileDeleteNameAction, SEC_DELETE_NAME_FAIL_IF_OPEN):
                return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_CANNOT_REMOVE_FILE_STILL_OPEN);
            case M_ACCESS_ENUM(eSecureFileDeleteNameAction, SEC_DELETE_NAME_UNLINK_IF_OPEN):
                // File cannot be opened, so we must assume something else has
                // it open, so unlink instead-TJE
#if defined(_WIN32)
                if (0 != _unlink(fullpath))
                {
                    return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE);
                }
                return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_SUCCESS);
#elif defined(POSIX_2001) || defined(BSD4_3) || defined(__svr4__)
                if (0 != unlink(fullpath))
                {
                    return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE);
                }
                return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_SUCCESS);
#else
                return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_CANNOT_REMOVE_FILE_STILL_OPEN);
#endif
            }
        }
        return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE);
    }
    return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_PATH);
}

M_PARAM_RW(1)
M_PARAM_WO(2)
eSecureFileError secure_GetPos_File(secureFileInfo* M_RESTRICT M_NONNULL fileInfo, fpos_t* M_RESTRICT M_NONNULL pos)
{
    if (fileInfo != M_NULLPTR && pos != M_NULLPTR)
    {
        if (fileInfo->error == M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE))
        {
            return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE);
        }
        fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_FILE);
        set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "Invalid secureFileInfo");
        if (fileInfo->file)
        {
            int getposres = fgetpos(fileInfo->file, pos);
            if (getposres == 0)
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_SUCCESS);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                              "File get position successful");
            }
            else
            {
                // TODO: inspect errno - ISO C security recommendation
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                              "Failed getting file position");
            }
        }
        return fileInfo->error;
    }
    else if (fileInfo != M_NULLPTR)
    {
        // pos is invalid
        fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_PARAMETER);
        set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "Error: Missing pos argument");
        return fileInfo->error;
    }
    return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_SECURE_FILE);
}

M_PARAM_RW(1)
M_PARAM_RO(2)
eSecureFileError secure_SetPos_File(secureFileInfo* M_NONNULL fileInfo, const fpos_t* M_NONNULL pos)
{
    if (fileInfo != M_NULLPTR && pos != M_NULLPTR)
    {
        if (fileInfo->error == M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE))
        {
            return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE);
        }
        fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_FILE);
        set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "Invalid secureFileInfo");
        if (fileInfo->file)
        {
            int setposres = fsetpos(fileInfo->file, pos);
            if (setposres == 0)
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_SUCCESS);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                              "File set position successful");
            }
            else
            {
                // TODO: inspect errno - ISO C security recommendation
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                              "Failed to set file position");
            }
        }
        return fileInfo->error;
    }
    else if (fileInfo != M_NULLPTR)
    {
        // pos is invalid
        fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_PARAMETER);
        set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString), "Error: Missing pos argument");
        return fileInfo->error;
    }
    return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_SECURE_FILE);
}

FUNC_ATTR_PRINTF(2, 0)
M_PARAM_RW(1)
M_PARAM_RO(2)
eSecureFileError secure_vfprintf_File(secureFileInfo* M_RESTRICT M_NONNULL fileInfo,
                                      const char* M_RESTRICT M_NONNULL     format,
                                      va_list                              args)
{
    if (fileInfo != M_NULLPTR && fileInfo->file != M_NULLPTR)
    {
        if (fileInfo->error == M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE))
        {
            return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE);
        }
        if (format != M_NULLPTR)
        {
            int vfprintfresult = 0;
#if defined(HAVE_C11_ANNEX_K) || defined(HAVE_MSFT_SECURE_LIB)
            vfprintfresult = vfprintf_s(fileInfo->file, format, args);
#else
            va_list verifyargs;
            va_list vfprintfargs;
            va_copy(verifyargs, args);
            if (verify_Format_String_And_Args(format, verifyargs) > 0)
            {
                va_copy(vfprintfargs, args);
                // nolint line is because of a false positive....clearly copying the args before calling this-TJE
                vfprintfresult =
                    vfprintf(fileInfo->file, format, vfprintfargs); // NOLINT(clang-analyzer-valist.Uninitialized)
                va_end(vfprintfargs);
            }
            else
            {
                vfprintfresult = -1;
            }
            va_end(verifyargs);
#endif
            if (vfprintfresult < 0 || ferror(fileInfo->file))
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_READ_WRITE_ERROR);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                              "Failed writing formatted text to file");
            }
            else
            {
                fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_SUCCESS);
                set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                              "Successfully wrote formatted text to file");
            }
        }
        else
        {
            // M_NULLPTR pointer for the format string
            set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                          "Error: Missing format string!");
            fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_PARAMETER);
        }
        return fileInfo->error;
    }
    return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_SECURE_FILE);
}

FUNC_ATTR_PRINTF(2, 3)
M_PARAM_RW(1)
M_PARAM_RO(2)
eSecureFileError secure_fprintf_File(secureFileInfo* M_RESTRICT M_NONNULL fileInfo,
                                     const char* M_RESTRICT M_NONNULL     format,
                                     ...)
{
    if (fileInfo != M_NULLPTR && fileInfo->file != M_NULLPTR)
    {
        if (fileInfo->error == M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE))
        {
            return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_FAILURE_CLOSING_FILE);
        }
        if (format != M_NULLPTR)
        {
            va_list args;
            va_start(args, format);
            eSecureFileError result = secure_vfprintf_File(fileInfo, format, args);
            va_end(args);
            return result;
        }
        else
        {
            // M_NULLPTR pointer for the format string
            fileInfo->error = M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_PARAMETER);
            set_Secure_File_error_message(M_CONST_CAST(char**, &fileInfo->errorString),
                                          "Error: Missing format string!");
        }
        return fileInfo->error;
    }
    return M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INVALID_SECURE_FILE);
}

M_NODISCARD_REASON("The returned pointer must be freed by the caller using free()/safe_free()")
M_PARAM_RO_SIZE(2, 3)
M_NONNULL_IF_NONZERO_PARAM(4, 5)
M_PARAM_RO_SIZE(4, 5)
M_NONNULL_IF_NONZERO_PARAM(6, 7)
M_PARAM_RO_SIZE(6, 7)
M_NONNULL_IF_NONZERO_PARAM(8, 9)
M_PARAM_RO_SIZE(8, 9)
M_FUNC_ATTR_MALLOC char* M_NULLABLE generate_Log_Name(eLogFileNamingConvention logFileNamingConvention,
                                                      const char* M_NONNULL    deviceIdentifier,
                                                      size_t                   deviceIDLen,
                                                      const char* M_NULLABLE   logPath,
                                                      size_t                   logPathLen,
                                                      const char* M_NULLABLE   logName,
                                                      size_t                   logNameLen,
                                                      const char* M_NULLABLE   logExt,
                                                      size_t                   logExtLen)
{
    DECLARE_ZERO_INIT_ARRAY(char, path, OPENSEA_PATH_MAX);
    char* logExtension  = M_NULLPTR;
    char* generatedName = M_NULLPTR;
    if ((deviceIdentifier == M_NULLPTR || deviceIDLen == 0) && logFileNamingConvention != NAMING_BYUSER)
    {
        return M_NULLPTR;
    }
    if (logPath != M_NULLPTR && logPathLen > 0)
    {
        snprintf_err_handle(path, OPENSEA_PATH_MAX, "%s", logPath);
        // if there is no path separator at the end of this string, then we need
        // to append one before using this path below
        if (path[safe_strnlen(path, OPENSEA_PATH_MAX) - 1] != SYSTEM_PATH_SEPARATOR)
        {
            safe_strcat(path, OPENSEA_PATH_MAX, SYSTEM_PATH_SEPARATOR_STR);
        }
    }
    else
    {
        // set to relative current working directory ./ or .\\ in Windows. This
        // will be expanded by secure file to a full path
        snprintf_err_handle(path, OPENSEA_PATH_MAX, ".%c", SYSTEM_PATH_SEPARATOR);
    }
    if (logExt != M_NULLPTR && logExtLen > SIZE_T_C(0))
    {
        // check if there is already an extension with a dot, otherwise add a
        // dot then the extension
        if (logExt[0] == '.')
        {
            if (0 != safe_strdup(&logExtension, logExt))
            {
                return M_NULLPTR;
            }
        }
        else
        {
            if (asprintf(&logExtension, ".%s", logExt) < 0)
            {
                if (logExtension != M_NULLPTR)
                {
                    // allocated, but had some other failure.
                    // free this and return M_NULLPTR
                    safe_free(&logExtension);
                }
                return M_NULLPTR;
            }
        }
    }
    else
    {
        // no extension provided, but allocate an empty string so as not to
        // cause undefined behavrior in the format string interpretation below
        logExtension = M_REINTERPRET_CAST(char*, safe_calloc(1, sizeof(char)));
    }
    if (logExtension == M_NULLPTR)
    {
        return M_NULLPTR;
    }
    get_current_timestamp(); // make sure timestamp is set
    switch (logFileNamingConvention)
    {
    case NAMING_SERIAL_NUMBER_ONLY:
        if (asprintf(&generatedName, "%s%s%s", path, deviceIdentifier, logExtension) < 0)
        {
            if (generatedName != M_NULLPTR)
            {
                // allocated, but had some other failure.
                // free this and return M_NULLPTR
                safe_free(&generatedName);
            }
            safe_free(&logExtension);
            return M_NULLPTR;
        }
        break;
    case NAMING_SERIAL_NUMBER_DATE_TIME:
        if (logName && logNameLen > 0)
        {
            if (asprintf(&generatedName, "%s%s_%s_%s%s", path, deviceIdentifier, logName, CURRENT_TIME_STRING,
                         logExtension) < 0)
            {
                if (generatedName != M_NULLPTR)
                {
                    // allocated, but had some other failure.
                    // free this and return M_NULLPTR
                    safe_free(&generatedName);
                }
                safe_free(&logExtension);
                return M_NULLPTR;
            }
        }
        else
        {
            if (asprintf(&generatedName, "%s%s_unknown_%s%s", path, deviceIdentifier, CURRENT_TIME_STRING,
                         logExtension) < 0)
            {
                if (generatedName != M_NULLPTR)
                {
                    // allocated, but had some other failure.
                    // free this and return M_NULLPTR
                    safe_free(&generatedName);
                }
                safe_free(&logExtension);
                return M_NULLPTR;
            }
        }
        break;
    case NAMING_BYUSER:
        if (asprintf(&generatedName, "%s%s%s", path, deviceIdentifier, logExtension) < 0)
        {
            if (generatedName != M_NULLPTR)
            {
                // allocated, but had some other failure.
                // free this and return M_NULLPTR
                safe_free(&generatedName);
            }
            safe_free(&logExtension);
            return M_NULLPTR;
        }
        break;
    }
    safe_free(&logExtension);
    return generatedName;
}

M_PARAM_RO_SIZE(1, 2)
M_NONNULL_IF_NONZERO_PARAM(5, 6)
M_PARAM_RO_SIZE(5, 6)
M_NONNULL_IF_NONZERO_PARAM(7, 8)
M_PARAM_RO_SIZE(7, 8)
M_NONNULL_IF_NONZERO_PARAM(9, 10)
M_PARAM_RO_SIZE(9, 10)
eReturnValues create_And_Open_Secure_Log_File(const char* M_NONNULL                 deviceIdentifier,
                                              size_t                                deviceIDLen,
                                              secureFileInfo* M_NONNULL* M_NULLABLE file,
                                              eLogFileNamingConvention              logFileNamingConvention,
                                              const char* M_NULLABLE                logPath,
                                              size_t                                logPathLen,
                                              const char* M_NULLABLE                logName,
                                              size_t                                logNameLen,
                                              const char* M_NULLABLE                logExt,
                                              size_t                                logExtLen)
{

    // NOTE: This function does not return the name used as that is part of the
    // secureFileInfo -TJE
    eReturnValues result = M_ACCESS_ENUM(eReturnValues, SUCCESS);
    // Step 1: Validate required parameters
    if (deviceIdentifier == M_NULLPTR || deviceIDLen == 0 || file == M_NULLPTR || logPathLen > OPENSEA_PATH_MAX ||
        logNameLen > RSIZE_MAX || logExtLen > RSIZE_MAX)
    {
        return M_ACCESS_ENUM(eReturnValues, BAD_PARAMETER);
    }
    // Step 2: Generate the logfile name from the provided parameters
    char* logGeneratedName = generate_Log_Name(logFileNamingConvention, deviceIdentifier, deviceIDLen, logPath,
                                               logPathLen, logName, logNameLen, logExt, logExtLen);
    if (logGeneratedName == M_NULLPTR)
    {
        return M_ACCESS_ENUM(eReturnValues, MEMORY_FAILURE);
    }
    // Step 3: Create the secure file (which will check directory security, etc
    // for us. NOTE: Always opened with X for exclusive so as not to overwrite
    // an existing file)
    *file = secure_Open_File(logGeneratedName, "wxb", M_NULLPTR, M_NULLPTR, M_NULLPTR);
    // Step 4: return error code depending on what happened from attempting to
    // do all of these steps
    if (*file == M_NULLPTR)
    {
        result = M_ACCESS_ENUM(eReturnValues, MEMORY_FAILURE);
    }
    else if ((*file)->error == M_ACCESS_ENUM(eSecureFileError, SEC_FILE_INSECURE_PATH))
    {
        result = M_ACCESS_ENUM(eReturnValues, INSECURE_PATH);
    }
    else if ((*file)->error != M_ACCESS_ENUM(eSecureFileError, SEC_FILE_SUCCESS))
    {
        result = M_ACCESS_ENUM(eReturnValues, FAILURE);
        // Should we give more error information here or just call it a
        // failure???-TJE
    }
    safe_free(&logGeneratedName);
    return result;
}
