//
// Copyright(C) 2002, 2003 Marcel Telka
// Copyright(C) 2005-2014 Simon Howard
//
// 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.
//
// DESCRIPTION:
//     Interface to the ioperm.sys driver, based on code from the
//     Cygwin ioperm library.
//

#ifdef _WIN32

#include <stdio.h>

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winioctl.h>

#include <errno.h>

#include "ioperm_sys.h"

#define IOPERM_FILE L"\\\\.\\ioperm"

#define IOCTL_IOPERM               \
    CTL_CODE(FILE_DEVICE_UNKNOWN, 0xA00, METHOD_BUFFERED, FILE_ANY_ACCESS)

struct ioperm_data
{
    unsigned long from;
    unsigned long num;
    int turn_on;
};

// Function pointers for advapi32.dll.  This DLL does not exist on
// Windows 9x, so they are dynamically loaded from the DLL at runtime.

// haleyjd 09/09/10: Moved calling conventions into ()'s

static SC_HANDLE (WINAPI *MyOpenSCManagerW)(wchar_t *lpMachineName,
                                            wchar_t *lpDatabaseName,
                                            DWORD dwDesiredAccess) = NULL;
static SC_HANDLE (WINAPI *MyCreateServiceW)(SC_HANDLE hSCManager,
                                            wchar_t *lpServiceName,
                                            wchar_t *lpDisplayName,
                                            DWORD dwDesiredAccess,
                                            DWORD dwServiceType,
                                            DWORD dwStartType,
                                            DWORD dwErrorControl,
                                            wchar_t *lpBinaryPathName,
                                            wchar_t *lpLoadOrderGroup,
                                            LPDWORD lpdwTagId,
                                            wchar_t *lpDependencies,
                                            wchar_t *lpServiceStartName,
                                            wchar_t *lpPassword);
static SC_HANDLE (WINAPI *MyOpenServiceW)(SC_HANDLE hSCManager,
                                          wchar_t *lpServiceName,
                                          DWORD dwDesiredAccess);
static BOOL (WINAPI *MyStartServiceW)(SC_HANDLE hService,
                                      DWORD dwNumServiceArgs,
                                      wchar_t **lpServiceArgVectors);
static BOOL (WINAPI *MyControlService)(SC_HANDLE hService,
                                       DWORD dwControl,
                                       LPSERVICE_STATUS lpServiceStatus);
static BOOL (WINAPI *MyCloseServiceHandle)(SC_HANDLE hSCObject);
static BOOL (WINAPI *MyDeleteService)(SC_HANDLE hService);

static struct
{
    char *name;
    void **fn;
} dll_functions[] = {
    { "OpenSCManagerW",     (void **) &MyOpenSCManagerW },
    { "CreateServiceW",     (void **) &MyCreateServiceW },
    { "OpenServiceW",       (void **) &MyOpenServiceW },
    { "StartServiceW",      (void **) &MyStartServiceW },
    { "ControlService",     (void **) &MyControlService },
    { "CloseServiceHandle", (void **) &MyCloseServiceHandle },
    { "DeleteService",      (void **) &MyDeleteService },
};

// Globals

static SC_HANDLE scm = NULL;
static SC_HANDLE svc = NULL;
static int service_was_created = 0;
static int service_was_started = 0;

static int LoadLibraryPointers(void)
{
    HMODULE dll;
    int i;

    // Already loaded?

    if (MyOpenSCManagerW != NULL)
    {
        return 1;
    }

    dll = LoadLibraryW(L"advapi32.dll");

    if (dll == NULL)
    {
        fprintf(stderr, "LoadLibraryPointers: Failed to open advapi32.dll\n");
        return 0;
    }

    for (i = 0; i < sizeof(dll_functions) / sizeof(*dll_functions); ++i)
    {
        *dll_functions[i].fn = GetProcAddress(dll, dll_functions[i].name);

        if (*dll_functions[i].fn == NULL)
        {
            fprintf(stderr, "LoadLibraryPointers: Failed to get address "
                            "for '%s'\n", dll_functions[i].name);
            return 0;
        }
    }

    return 1;
}

int IOperm_EnablePortRange(unsigned int from, unsigned int num, int turn_on)
{
    HANDLE h;
    struct ioperm_data ioperm_data;
    DWORD BytesReturned;
    BOOL r;

    h = CreateFileW(IOPERM_FILE, GENERIC_READ, 0, NULL,
                    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    if (h == INVALID_HANDLE_VALUE)
    {
        errno = ENODEV;
        return -1;
    }

    ioperm_data.from = from;
    ioperm_data.num = num;
    ioperm_data.turn_on = turn_on;

    r = DeviceIoControl(h, IOCTL_IOPERM,
                        &ioperm_data, sizeof ioperm_data,
                        NULL, 0,
                        &BytesReturned, NULL);

    if (!r)
    {
        errno = EPERM;
    }

    CloseHandle(h);

    return r != 0;
}

// Load ioperm.sys driver.
// Returns 1 for success, 0 for failure.
// Remember to call IOperm_UninstallDriver to uninstall the driver later.

int IOperm_InstallDriver(void)
{
    wchar_t driver_path[MAX_PATH];
    int error;
    int result = 1;

    if (!LoadLibraryPointers())
    {
        return 0;
    }

    scm = MyOpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);

    if (scm == NULL)
    {
        error = GetLastError();
        fprintf(stderr, "IOperm_InstallDriver: OpenSCManager failed (%i).\n",
                        error);
        return 0;
    }

    // Get the full path to the driver file.

    GetFullPathNameW(L"ioperm.sys", MAX_PATH, driver_path, NULL);

    // Create the service.

    svc = MyCreateServiceW(scm,
                           L"ioperm",
                           L"ioperm support for Cygwin driver",
                           SERVICE_ALL_ACCESS,
                           SERVICE_KERNEL_DRIVER,
                           SERVICE_AUTO_START,
                           SERVICE_ERROR_NORMAL,
                           driver_path,
                           NULL,
                           NULL,
                           NULL,
                           NULL,
                           NULL);

    if (svc == NULL)
    {
        error = GetLastError();

        if (error != ERROR_SERVICE_EXISTS)
        {
            fprintf(stderr,
                    "IOperm_InstallDriver: Failed to create service (%i).\n",
                    error);
        }
        else
        {
            svc = MyOpenServiceW(scm, L"ioperm", SERVICE_ALL_ACCESS);

            if (svc == NULL)
            {
                error = GetLastError();

                fprintf(stderr,
                        "IOperm_InstallDriver: Failed to open service (%i).\n",
                        error);
            }
        }

        if (svc == NULL)
        {
            MyCloseServiceHandle(scm);
            return 0;
        }
    }
    else
    {
        service_was_created = 1;
    }

    // Start the service.  If the service already existed, it might have
    // already been running as well.

    if (!MyStartServiceW(svc, 0, NULL))
    {
        error = GetLastError();

        if (error != ERROR_SERVICE_ALREADY_RUNNING)
        {
            fprintf(stderr, "IOperm_InstallDriver: Failed to start service (%i).\n",
                            error);

            result = 0;
        }
        else
        {
            printf("IOperm_InstallDriver: ioperm driver already running.\n");
        }
    }
    else
    {
        printf("IOperm_InstallDriver: ioperm driver installed.\n");
        service_was_started = 1;
    }

    // If we failed to start the driver running, we need to clean up
    // before finishing.

    if (result == 0)
    {
        IOperm_UninstallDriver();
    }

    return result;
}

int IOperm_UninstallDriver(void)
{
    SERVICE_STATUS stat;
    int result = 1;
    int error;

    // If we started the service, stop it.

    if (service_was_started)
    {
        if (!MyControlService(svc, SERVICE_CONTROL_STOP, &stat))
        {
            error = GetLastError();

            if (error == ERROR_SERVICE_NOT_ACTIVE)
            {
                fprintf(stderr,
                        "IOperm_UninstallDriver: Service not active? (%i)\n",
                        error);
            }
            else
            {
                fprintf(stderr,
                        "IOperm_UninstallDriver: Failed to stop service (%i).\n",
                        error);
                result = 0;
            }
        }
    }

    // If we created the service, delete it.

    if (service_was_created)
    {
        if (!MyDeleteService(svc))
        {
            error = GetLastError();

            fprintf(stderr,
                    "IOperm_UninstallDriver: DeleteService failed (%i).\n",
                    error);

            result = 0;
        }
        else if (service_was_started)
        {
            printf("IOperm_UnInstallDriver: ioperm driver uninstalled.\n");
        }
    }

    // Close handles.

    if (svc != NULL)
    {
        MyCloseServiceHandle(svc);
        svc = NULL;
    }

    if (scm != NULL)
    {
        MyCloseServiceHandle(scm);
        scm = NULL;
    }

    service_was_created = 0;
    service_was_started = 0;

    return result;
}

#endif /* #ifndef _WIN32 */

