/*
 * The contents of this file are subject to the AOLserver Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://aolserver.com/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is AOLserver Code and related documentation
 * distributed by AOL.
 * 
 * The Initial Developer of the Original Code is America Online,
 * Inc. Portions created by AOL are Copyright (C) 1999 America Online,
 * Inc. All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU General Public License (the "GPL"), in which case the
 * provisions of GPL are applicable instead of those above.  If you wish
 * to allow use of your version of this file only under the terms of the
 * GPL and not to allow others to use your version of this file under the
 * License, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the GPL.
 * If you do not delete the provisions above, a recipient may use your
 * version of this file under either the License or the GPL.
 */


/* 
 * win32.c --
 *
 *	Win32 specific routines.
 */

static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/aolserver/nsd/nswin32.c,v 1.14 2005/08/11 23:00:44 jgdavidson Exp $, compiled: " __DATE__ " " __TIME__;

#include "nsd.h"

static Ns_Mutex lock;
static Ns_Cond cond;
static Ns_Thread tickThread;
static Ns_ThreadProc ServiceTicker;
static void StopTicker(void);
static void StartTicker(DWORD pending);
static VOID WINAPI ServiceMain(DWORD argc, LPTSTR *argv);
static VOID WINAPI ServiceHandler(DWORD code);
static BOOL WINAPI ConsoleHandler(DWORD code);
static void ReportStatus(DWORD state, DWORD code, DWORD hint);
static void ExitService(void);
static char *GetServiceName(Ns_DString *dsPtr, char *server);
static SERVICE_STATUS_HANDLE hStatus = 0;
static SERVICE_STATUS curStatus;
static Ns_Tls   tls;
static int service;
static int tick;
static int sigpending;

#define SysErrMsg()    (NsWin32ErrMsg(GetLastError()))


/*
 *----------------------------------------------------------------------
 *
 * DllMain --
 *
 *	Init routine for the nsd.dll which setups TLS for Win32 errors
 *  	disables thread attach/detach calls.
 *
 * Results:
 *  	TRUE or FALSE.
 *
 * Side effects:
 *  	None.
 *
 *----------------------------------------------------------------------
 */

BOOL APIENTRY
DllMain(HANDLE hModule, DWORD why, LPVOID lpReserved)
{
    WSADATA         wsd;

    if (why == DLL_PROCESS_ATTACH) {
	NsThreads_LibInit();
	Ns_TlsAlloc(&tls, ns_free);
	if (WSAStartup(MAKEWORD(1, 1), &wsd) != 0) {
            return FALSE;
        }
        DisableThreadLibraryCalls(hModule);
	Ns_LibInit();
    } else if (why == DLL_PROCESS_DETACH) {
        WSACleanup();
    }
    return TRUE;
}


/*
 *----------------------------------------------------------------------
 *
 * NsWin32ErrMsg --
 *
 *	Get a string message for an error code in either the kernel or
 *  	wsock dll's.
 *
 * Results:
 *  	Pointer to per-thread LocalAlloc'ed memory.
 *
 * Side effects:
 *  	None.
 *
 *----------------------------------------------------------------------
 */

char *
NsWin32ErrMsg(int err)
{
    char           *msg;

    msg = Ns_TlsGet(&tls);
    if (msg == NULL) {
	msg = ns_malloc(100);
	Ns_TlsSet(&tls, msg);
    }
    sprintf(msg, "win32 error code: %d", err);
    return msg;
}


/*
 *----------------------------------------------------------------------
 *
 * NsConnectService --
 *
 *	Attach to the service control manager at startup.
 *
 * Results:
 *  	None.
 *
 * Side effects:
 *  	Service control manager will create a new thread running
 *  	ServiceMain().
 *
 *----------------------------------------------------------------------
 */

int
NsConnectService(void)
{
    SERVICE_TABLE_ENTRY table[2];
    char buf[PATH_MAX];
    char *log, *null;
    int fd;
    BOOL ok;

    /*
     * Open a temporary log, ensuring it's opened
     * on fd 2 for later dup2 in NsLogOpen().
     */

    log = null = "nul:";
    if (GetTempPath(sizeof(buf) - sizeof(NSD_NAME) - 7, buf) > 0) {
	strcat(buf, NSD_NAME ".XXXXXX");
	log = _mktemp(buf);
    }
    if (log == NULL) {
	log = null;
    }
    _fcloseall();
    for (fd = 0; fd < 3; ++fd) {
	close(fd);
    }
    freopen(null, "rt", stdin);
    freopen(log, "wt", stdout);
    freopen(log, "wt", stderr);
    Ns_Log(Notice, "nswin32: connecting to service control manager");
    service = 1;
    table[0].lpServiceName = NSD_NAME;
    table[0].lpServiceProc = ServiceMain;
    table[1].lpServiceName = NULL;
    table[1].lpServiceProc = NULL;
    ok = StartServiceCtrlDispatcher(table);
    if (!ok) {
        Ns_Log(Error, "nswin32: "
	       "failed to contact service control dispatcher: '%s'", SysErrMsg());
    } else if (log != null) {
	unlink(log);
    }
    return (ok ? NS_OK : NS_ERROR);
}


/*
 *----------------------------------------------------------------------
 *
 * NsRemoveService --
 *
 *	Remove a previously installed service.
 *
 * Results:
 *  	None.
 *
 * Side effects:
 *  	Service should stop and then disappear from the list in the
 *  	services control panel.
 *
 *----------------------------------------------------------------------
 */

int
NsRemoveService(char *server)
{
    SC_HANDLE       hmgr, hsrv;
    SERVICE_STATUS  status;
    Ns_DString	    name;
    BOOL            ok;

    Ns_DStringInit(&name);
    GetServiceName(&name, server);
    ok = FALSE;
    hmgr = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if (hmgr != NULL) {
        hsrv = OpenService(hmgr, name.string, SERVICE_ALL_ACCESS);
        if (hsrv != NULL) {
            ControlService(hsrv, SERVICE_CONTROL_STOP, &status);
            ok = DeleteService(hsrv);
            CloseServiceHandle(hsrv);
        }
        CloseServiceHandle(hmgr);
    }
    if (ok) {
	Ns_Log(Notice, "nswin32: removed service: %s", name.string);
    } else {
	Ns_Log(Error, "nswin32: failed to remove %s service: %s",
	       name.string, SysErrMsg());
    }
    Ns_DStringFree(&name);
    return (ok ? NS_OK : NS_ERROR);
}


/*
 *----------------------------------------------------------------------
 *
 * NsInstallService --
 *
 *	Install as an NT service.
 *
 * Results:
 *  	None.
 *
 * Side effects:
 *  	Service should appear in the list in the services control panel.
 *
 *----------------------------------------------------------------------
 */

int
NsInstallService(char *server)
{
    SC_HANDLE       hmgr, hsrv;
    BOOL	    ok;
    char	    nsd[PATH_MAX], config[PATH_MAX];
    Ns_DString	    name, cmd;

    ok = FALSE;
    if (_fullpath(config, nsconf.config, sizeof(config)) == NULL) {
	Ns_Log(Error, "nswin32: invalid config path '%s'", nsconf.config);
    } else if (!GetModuleFileName(NULL, nsd, sizeof(nsd))) {
	Ns_Log(Error, "nswin32: failed to find nsd.exe: '%s'", SysErrMsg());
    } else {
	Ns_DStringInit(&name);
	Ns_DStringInit(&cmd);
	Ns_DStringVarAppend(&cmd, "\"", nsd, "\"",
	    " -S -s ", server, " -t \"", config, "\"", NULL);
	GetServiceName(&name, server);
	Ns_Log(Notice, "nswin32: installing %s service: %s",
	    name.string, cmd.string);
	hmgr = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
	if (hmgr != NULL) {
	    hsrv = CreateService(hmgr, name.string, name.string,
		SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
		SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
		cmd.string, NULL, NULL, "TcpIp\0", NULL, NULL);
	    if (hsrv != NULL) {
		CloseServiceHandle(hsrv);
		ok = TRUE;
	    }
	    CloseServiceHandle(hmgr);
	}
	if (!ok) {
	    Ns_Log(Error, "nswin32: failed to install service '%s': '%s'",
		   name.string, SysErrMsg());
	}
	Ns_DStringFree(&name);
	Ns_DStringFree(&cmd);
    }
    return (ok ? NS_OK : NS_ERROR);
}


/*
 *----------------------------------------------------------------------
 *
 * NsHandleSignals --
 *
 *	Loop endlessly, processing HUP signals until a TERM
 *  	signal arrives.
 *
 * Results:
 *  	None.
 *
 * Side effects:
 *  	None.
 *
 *----------------------------------------------------------------------
 */

void
NsHandleSignals(void)
{
    int pending;

    /*
     * If running as a service, stop the ticker thread and report
     * startup complete. Otherwise, register a handler which will
     * initiate an orderly shutdown on Ctrl-C.
     */
     
    if (!service) {
    	SetConsoleCtrlHandler(ConsoleHandler, TRUE);
    } else {
	StopTicker();
	ReportStatus(SERVICE_RUNNING, NO_ERROR, 0);
    }

    Ns_MutexSetName2(&lock, "ns", "signal");
    do {
    	Ns_MutexLock(&lock);
	while (sigpending == 0) {
    	    Ns_CondWait(&cond, &lock);
	}
	pending = sigpending;
	sigpending = 0;
	Ns_MutexUnlock(&lock);
	if (pending & NS_SIGHUP) {
	    NsRunSignalProcs();
	}
    } while (!(pending & NS_SIGTERM));

    /*
     * If running as a service, startup the ticker thread again
     * to keep updating status until shutdown is complete.
     */
     
    if (service) {
	StartTicker(SERVICE_STOP_PENDING);
    }
}


/*
 *----------------------------------------------------------------------
 *
 * NsSendSignal --
 *
 *	Send a signal to wakeup NsHandleSignals. As on Unix, a signal
 *  	sent multiple times is only received once.
 *
 * Results:
 *  	None.
 *
 * Side effects:
 *  	Main thread will wakeup.
 *
 *----------------------------------------------------------------------
 */

void
NsSendSignal(int sig)
{
    switch (sig) {
    case NS_SIGTERM:
    case NS_SIGHUP:
        Ns_MutexLock(&lock);
        sigpending |= sig;
        Ns_CondSignal(&cond);
        Ns_MutexUnlock(&lock);
        break;
    default:
        Ns_Fatal("nswin32: invalid signal: %d", sig);
        break;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * ns_socknbclose --
 *
 *	Perform a non-blocking socket close via the socket callback
 *  	thread.  This is only called by a timeout in Ns_SockTimedConnect.
 *
 * Results:
 *  	0 or SOCKET_ERROR.
 *
 * Side effects:
 *  	Socket will be closed when writable.
 *
 *----------------------------------------------------------------------
 */

int
ns_socknbclose(SOCKET sock)
{
    if (Ns_SockCloseLater(sock) != NS_OK) {
	return SOCKET_ERROR;
    }
    return 0;
}


/*
 *----------------------------------------------------------------------
 *
 * ns_sockdup --
 *
 *	Duplicate a socket.  This is used in the old ns_sock Tcl cmds.
 *
 * Results:
 *  	New handle to underlying socket.
 *
 * Side effects:
 *  	None.
 *
 *----------------------------------------------------------------------
 */

SOCKET
ns_sockdup(SOCKET sock)
{
    HANDLE hp, src, dup;
    
    src = (HANDLE) sock;
    hp = GetCurrentProcess();    
    if (!DuplicateHandle(hp, src, hp, &dup, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
	return INVALID_SOCKET;
    }
    return (SOCKET) dup;
}   


/*
 *----------------------------------------------------------------------
 *
 * link, symlink, kill --
 *
 *  	Stubs for missing Unix routines.  This is done simply to avoid
 *  	more ifdef's in the code.
 *
 * Results:
 *  	-1.
 *
 * Side effects:
 *  	Sets errno to EINVAL.
 *
 *----------------------------------------------------------------------
 */

int
link(char *from, char *to)
{
    errno = EINVAL;
    return -1;
}

int
symlink(char *from, char *to)
{
    errno = EINVAL;
    return -1;
}

int
kill(int pid, int sig)
{
    errno = EINVAL;
    return -1;
}


/*
 *----------------------------------------------------------------------
 *
 * truncate --
 *
 *	Implement Unix truncate.
 *
 * Results:
 *  	0 if ok or -1 on error.
 *
 * Side effects:
 *  	File is opened, truncated, and closed.
 *
 *----------------------------------------------------------------------
 */

int
truncate(char *file, off_t size)
{
    int fd;

    fd = open(file, O_WRONLY|O_BINARY);
    if (fd < 0) {
	return -1;
    }
    size = _chsize(fd, size);
    close(fd);
    if (size != 0) {
	return -1;
    }
    return 0;
}


/*
 *----------------------------------------------------------------------
 *
 * ConsoleHandler --
 *
 *	Callback when the Ctrl-C is pressed.
 *
 * Results:
 *  	TRUE.
 *
 * Side effects:
 *  	Shutdown is initiated.
 *
 *----------------------------------------------------------------------
 */

static BOOL WINAPI
ConsoleHandler(DWORD ignored)
{
    SetConsoleCtrlHandler(ConsoleHandler, FALSE);
    NsSendSignal(NS_SIGTERM);
    return TRUE;
}


/*
 *----------------------------------------------------------------------
 *
 * GetServiceName --
 *
 *	Construct the service name for the corresponding server.
 *
 * Results:
 *  	Pointer to given dstring string.
 *
 * Side effects:
 *  	None.
 *
 *----------------------------------------------------------------------
 */

static char *
GetServiceName(Ns_DString *dsPtr, char *server)
{
    Ns_DStringVarAppend(dsPtr, NSD_NAME, "-", server, NULL);
    return dsPtr->string;
}


/*
 *----------------------------------------------------------------------
 *
 * StartTicker, StopTicker --
 *
 *	Start and stop the background ticker thread which keeps the
 *  	service control manager informed of during startup and
 *  	shutdown.
 *
 * Results:
 *  	None.
 *
 * Side effects:
 *  	Thread is created or signalled to stop and joined.
 *
 *----------------------------------------------------------------------
 */

static void
StartTicker(DWORD pending)
{
    Ns_MutexLock(&lock);
    tick = 1;
    Ns_MutexUnlock(&lock);
    Ns_ThreadCreate(ServiceTicker, (void *) pending, 0, &tickThread);
}

static void
StopTicker(void)
{
    Ns_MutexLock(&lock);
    tick = 0;
    Ns_CondBroadcast(&cond);
    Ns_MutexUnlock(&lock);
    Ns_ThreadJoin(&tickThread, NULL);
}


/*
 *----------------------------------------------------------------------
 *
 * ServiceTicker --
 *
 *	Background ticker created by StartTicker which does nothing
 *  	but send the message repeatedly until signaled to stop.
 *
 * Results:
 *  	None.
 *
 * Side effects:
 *  	Service control manager is kept informed of progress.
 *
 *----------------------------------------------------------------------
 */

static void
ServiceTicker(void *arg)
{
    Ns_Time timeout;
    DWORD pending = (DWORD) arg;

    Ns_ThreadSetName("-ticker-");

    Ns_MutexLock(&lock);
    do {
        ReportStatus(pending, NO_ERROR, 2000);
	Ns_GetTime(&timeout);
	Ns_IncrTime(&timeout, 1, 0);
	Ns_CondTimedWait(&cond, &lock, &timeout);
    } while (tick);
    Ns_MutexUnlock(&lock);
}


/*
 *----------------------------------------------------------------------
 *
 * ServiceMain --
 *
 *	Startup routine created by the service control manager.  This
 *  	routine initializes the structure for reporting status,
 *  	starts the ticker, and then re-enters Ns_Main() where it
 *  	was left off when NsServiceConnect() was called.
 *
 * Results:
 *  	None.
 *
 * Side effects:
 *  	Server startup continues.
 *
 *----------------------------------------------------------------------
 */

static VOID WINAPI
ServiceMain(DWORD argc, LPTSTR *argv)
{
    hStatus = RegisterServiceCtrlHandler(argv[0], ServiceHandler);
    if (hStatus == 0) {
        Ns_Fatal("nswin32: RegisterServiceCtrlHandler() failed: '%s'",SysErrMsg());
    }
    curStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    curStatus.dwServiceSpecificExitCode = 0;
    StartTicker(SERVICE_START_PENDING);
    Ns_Main(argc, argv, NULL);
    StopTicker();
    ReportStatus(SERVICE_STOP_PENDING, NO_ERROR, 100);
    ReportStatus(SERVICE_STOPPED, 0, 0);
    Ns_Log(Notice, "nswin32: service exiting");
}


/*
 *----------------------------------------------------------------------
 *
 * ServiceHandler --
 *
 *	Callback when the service control manager wants to signal the
 *  	server (i.e., when service is stopped via services control
 *  	panel).
 *
 * Results:
 *  	None.
 *
 * Side effects:
 *  	Signal may be sent.
 *
 *----------------------------------------------------------------------
 */

static VOID WINAPI
ServiceHandler(DWORD code)
{
    if (code == SERVICE_CONTROL_STOP || code == SERVICE_CONTROL_SHUTDOWN) {
    	NsSendSignal(NS_SIGTERM);
    } else {
        ReportStatus(code, NO_ERROR, 0);
    }
}


/*
 *----------------------------------------------------------------------
 *
 * ReportStatus --
 *
 *	Update the service control manager with the current state.
 *
 * Results:
 *  	None.
 *
 * Side effects:
 *  	None.
 *
 *----------------------------------------------------------------------
 */

static void
ReportStatus(DWORD state, DWORD code, DWORD hint)
{
    static DWORD    check = 1;

    if (state == SERVICE_START_PENDING) {
        curStatus.dwControlsAccepted = 0;
    } else {
        curStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
    }
    curStatus.dwCurrentState = state;
    curStatus.dwWin32ExitCode = code;
    curStatus.dwWaitHint = hint;
    if (state == SERVICE_RUNNING || state == SERVICE_STOPPED) {
        curStatus.dwCheckPoint = 0;
    } else {
        curStatus.dwCheckPoint = check++;
    }
    if (hStatus != 0 && SetServiceStatus(hStatus, &curStatus) != TRUE) {
        Ns_Fatal("nswin32: SetServiceStatus(%d) failed: '%s'", state, SysErrMsg());
    }
}
