/** @file
 * @brief Provides wrappers with POSIXy semantics.
 */
/* Copyright (C) 2007 Lemur Consulting Ltd
 * Copyright (C) 2007,2012,2018,2023,2025 Olly Betts
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see
 * <https://www.gnu.org/licenses/>.
 */

#include <config.h>

#include <cerrno>

#ifdef __CYGWIN__
# include "posixy_wrapper.h"

# include "filetests.h"

int
posixy_unlink(const char * filename)
{
    // On Cygwin we seem to inexplicably get ECHILD from unlink() sometimes.
    // The path doesn't actually exists after the call when we get ECHILD, but
    // the correct return value depends on whether it existed before so we
    // need to check here as well.
    if (!path_exists(filename)) {
        errno = ENOENT;
        return -1;
    }

    int r = unlink(filename);
    if (r < 0) {
        int unlink_errno = errno;
        if (unlink_errno == ECHILD && !path_exists(filename)) {
            errno = 0;
            return 0;
        }

        errno = unlink_errno;
    }
    return r;
}

#elif defined __WIN32__

#include "posixy_wrapper.h"

#include <io.h>

#include "safefcntl.h"
#include "safewindows.h"

int
posixy_set_errno_from_getlasterror()
{
    int e;
    unsigned long winerr = GetLastError();
    switch (winerr) {
        case ERROR_FILENAME_EXCED_RANGE:
        case ERROR_FILE_NOT_FOUND:
        case ERROR_PATH_NOT_FOUND:
        case ERROR_INVALID_DRIVE:
        case ERROR_NO_MORE_FILES:
        case ERROR_BAD_NETPATH:
        case ERROR_BAD_NET_NAME:
        case ERROR_BAD_PATHNAME:
            e = ENOENT;
            break;
        case ERROR_ARENA_TRASHED:
        case ERROR_NOT_ENOUGH_MEMORY:
        case ERROR_INVALID_BLOCK:
        case ERROR_NOT_ENOUGH_QUOTA:
            e = ENOMEM;
            break;
        case ERROR_LOCK_VIOLATION:
        case ERROR_LOCK_FAILED:
        case ERROR_SEEK_ON_DEVICE:
        case ERROR_NETWORK_ACCESS_DENIED:
        case ERROR_NOT_LOCKED:
        case ERROR_ACCESS_DENIED:
        case ERROR_CANNOT_MAKE:
        case ERROR_FAIL_I24:
        case ERROR_DRIVE_LOCKED:
        case ERROR_CURRENT_DIRECTORY:
            e = EACCES;
            break;
        case ERROR_INVALID_FUNCTION:
        case ERROR_INVALID_ACCESS:
        case ERROR_NEGATIVE_SEEK:
        case ERROR_INVALID_DATA:
        case ERROR_INVALID_PARAMETER:
            e = EINVAL;
            break;
        case ERROR_NO_PROC_SLOTS:
        case ERROR_NESTING_NOT_ALLOWED:
        case ERROR_MAX_THRDS_REACHED:
            e = EAGAIN;
            break;
        case ERROR_INVALID_HANDLE:
        case ERROR_INVALID_TARGET_HANDLE:
        case ERROR_DIRECT_ACCESS_HANDLE:
            e = EBADF;
            break;
        case ERROR_ALREADY_EXISTS:
        case ERROR_FILE_EXISTS:
            e = EEXIST;
            break;
        case ERROR_BROKEN_PIPE:
            e = EPIPE;
            break;
        case ERROR_DISK_FULL:
            e = ENOSPC;
            break;
        case ERROR_TOO_MANY_OPEN_FILES:
            e = EMFILE;
            break;
        case ERROR_WAIT_NO_CHILDREN:
        case ERROR_CHILD_NOT_COMPLETE:
            e = ECHILD;
            break;
        case ERROR_DIR_NOT_EMPTY:
            e = ENOTEMPTY;
            break;
        case ERROR_BAD_ENVIRONMENT:
            e = E2BIG;
            break;
        case ERROR_BAD_FORMAT:
            e = ENOEXEC;
            break;
        case ERROR_NOT_SAME_DEVICE:
            e = EXDEV;
            break;
        default:
            if (winerr >= ERROR_WRITE_PROTECT && winerr <= ERROR_SHARING_BUFFER_EXCEEDED)
                e = EACCES;
            else if (winerr >= ERROR_INVALID_STARTING_CODESEG && winerr <= ERROR_INFLOOP_IN_RELOC_CHAIN)
                e = ENOEXEC;
            else
                e = EINVAL;
            break;
    }
    errno = e;
    return -1;
}

int
posixy_unlink(const char * filename)
{
    /* We must use DeleteFile as this can delete files that are open. */
    if (DeleteFile(filename) != 0) {
        return 0;
    }

    return posixy_set_errno_from_getlasterror();
}

int
posixy_open(const char *filename, int flags)
{
    /* Translate POSIX read mode to Windows access mode */
    DWORD dwDesiredAccess = GENERIC_READ;
    switch (flags & (O_RDONLY | O_RDWR | O_WRONLY)) {
        case O_RDONLY:
            dwDesiredAccess = GENERIC_READ;
            break;
        case O_RDWR:
            dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
            break;
        case O_WRONLY:
            dwDesiredAccess = GENERIC_WRITE;
            break;
    }
    /* Subsequent operations may open this file to read, write or delete it */
    DWORD dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;

    /* Translate POSIX creation mode to Windows creation mode */
    DWORD dwCreationDisposition = OPEN_EXISTING;
    switch (flags & (O_CREAT | O_TRUNC | O_EXCL)) {
        case O_EXCL:
            dwCreationDisposition = OPEN_EXISTING;
            break;

        case O_CREAT:
            dwCreationDisposition = OPEN_ALWAYS;
            break;

        case O_CREAT | O_TRUNC:
            dwCreationDisposition = CREATE_ALWAYS;
            break;

        case O_CREAT | O_EXCL:
        case O_CREAT | O_TRUNC | O_EXCL:
            dwCreationDisposition = CREATE_NEW;
            break;

        case O_TRUNC:
        case O_TRUNC | O_EXCL:
            dwCreationDisposition = TRUNCATE_EXISTING;
            break;
    }

    HANDLE handleWin =
        CreateFile(filename,
                   dwDesiredAccess,
                   dwShareMode,
                   NULL,
                   dwCreationDisposition,
                   FILE_ATTRIBUTE_NORMAL,
                   NULL);
    if (handleWin == INVALID_HANDLE_VALUE) {
        return posixy_set_errno_from_getlasterror();
    }

    /* Return a standard file descriptor. */
    return _open_osfhandle(intptr_t(handleWin), flags|O_BINARY);
}

int
posixy_rename(const char *from, const char *to)
{
    if (MoveFileEx(from, to, MOVEFILE_REPLACE_EXISTING) != 0) {
        return 0;
    }

    return posixy_set_errno_from_getlasterror();
}

#endif // __WIN32__
