/*
 * Service process to load a kernel driver
 *
 * Copyright 2007 Alexandre Julliard
 * Copyright 2016 Sebastian Lackner
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <stdarg.h>

#include "ntstatus.h"
#define WIN32_NO_STATUS
#include "windef.h"
#include "winternl.h"
#include "ddk/wdm.h"
#include "wine/svcctl.h"
#include "wine/debug.h"
#include "wine/heap.h"

WINE_DEFAULT_DEBUG_CHANNEL(winedevice);

static const WCHAR servicesW[] = L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\";

extern NTSTATUS CDECL wine_ntoskrnl_main_loop( HANDLE stop_event );
extern void CDECL wine_enumerate_root_devices( const WCHAR *driver_name );

static WCHAR winedeviceW[] = L"winedevice";
static SERVICE_STATUS_HANDLE service_handle;
static SC_HANDLE manager_handle;
static HANDLE stop_event;

/* helper function to update service status */
static void set_service_status( SERVICE_STATUS_HANDLE handle, DWORD state, DWORD accepted )
{
    SERVICE_STATUS status;
    status.dwServiceType             = SERVICE_WIN32;
    status.dwCurrentState            = state;
    status.dwControlsAccepted        = accepted;
    status.dwWin32ExitCode           = 0;
    status.dwServiceSpecificExitCode = 0;
    status.dwCheckPoint              = 0;
    status.dwWaitHint                = (state == SERVICE_START_PENDING) ? 10000 : 0;
    SetServiceStatus( handle, &status );
}

#define SERVICE_CONTROL_REENUMERATE_ROOT_DEVICES 128

static DWORD device_handler( DWORD ctrl, const WCHAR *driver_name )
{
    UNICODE_STRING service_name;
    DWORD result = NO_ERROR;
    WCHAR *str;

    if (!(str = heap_alloc( sizeof(servicesW) + lstrlenW(driver_name)*sizeof(WCHAR) )))
        return STATUS_NO_MEMORY;

    lstrcpyW( str, servicesW );
    lstrcatW( str, driver_name );
    RtlInitUnicodeString( &service_name, str );

    switch (ctrl)
    {
    case SERVICE_CONTROL_START:
        result = RtlNtStatusToDosError(ZwLoadDriver( &service_name ));
        break;

    case SERVICE_CONTROL_STOP:
        result = RtlNtStatusToDosError(ZwUnloadDriver( &service_name ));
        break;

    case SERVICE_CONTROL_REENUMERATE_ROOT_DEVICES:
        wine_enumerate_root_devices( driver_name );
        break;

    default:
        FIXME( "got driver ctrl %lx for %s\n", ctrl, wine_dbgstr_w(driver_name) );
        break;
    }

    RtlFreeUnicodeString( &service_name );
    return result;
}

static DWORD WINAPI service_handler( DWORD ctrl, DWORD event_type, LPVOID event_data, LPVOID context )
{
    const WCHAR *service_group = context;

    if (ctrl & SERVICE_CONTROL_FORWARD_FLAG)
    {
        if (!event_data) return ERROR_INVALID_PARAMETER;
        return device_handler( ctrl & ~SERVICE_CONTROL_FORWARD_FLAG, (const WCHAR *)event_data );
    }

    switch (ctrl)
    {
    case SERVICE_CONTROL_STOP:
    case SERVICE_CONTROL_SHUTDOWN:
        TRACE( "shutting down %s\n", wine_dbgstr_w(service_group) );
        set_service_status( service_handle, SERVICE_STOP_PENDING, 0 );
        SetEvent( stop_event );
        return NO_ERROR;
    default:
        FIXME( "got service ctrl %lx for %s\n", ctrl, wine_dbgstr_w(service_group) );
        set_service_status( service_handle, SERVICE_RUNNING,
                            SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN );
        return NO_ERROR;
    }
}

static void WINAPI ServiceMain( DWORD argc, LPWSTR *argv )
{
    WCHAR driver_dir[MAX_PATH];
    const WCHAR *service_group = (argc >= 2) ? argv[1] : argv[0];

    if (!(stop_event = CreateEventW( NULL, TRUE, FALSE, NULL )))
        return;
    if (!(manager_handle = OpenSCManagerW( NULL, NULL, SC_MANAGER_CONNECT )))
        return;
    if (!(service_handle = RegisterServiceCtrlHandlerExW( winedeviceW, service_handler, (void *)service_group )))
        return;

    GetSystemDirectoryW( driver_dir, MAX_PATH );
    wcscat( driver_dir, L"\\drivers" );
    AddDllDirectory( driver_dir );

    TRACE( "starting service group %s\n", wine_dbgstr_w(service_group) );
    set_service_status( service_handle, SERVICE_RUNNING,
                        SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN );

    wine_ntoskrnl_main_loop( stop_event );

    TRACE( "service group %s stopped\n", wine_dbgstr_w(service_group) );
    set_service_status( service_handle, SERVICE_STOPPED, 0 );
    CloseServiceHandle( manager_handle );
    CloseHandle( stop_event );
}

int __cdecl wmain( int argc, WCHAR *argv[] )
{
    SERVICE_TABLE_ENTRYW service_table[2];

    service_table[0].lpServiceName = winedeviceW;
    service_table[0].lpServiceProc = ServiceMain;
    service_table[1].lpServiceName = NULL;
    service_table[1].lpServiceProc = NULL;

    StartServiceCtrlDispatcherW( service_table );
    return 0;
}