/*
 *  Copyright (C) 2006 Nigel Horne <njh@bandsman.co.uk>
 *
 *  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, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *  MA 02110-1301, USA.
 *
 * Unix/Linux compatibility for Windows
 * Inspired by glib and the cygwin source code
 * Tested under Microsoft Visual Studio 2005
 */
#include <windows.h>

#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif

#include <errno.h>
#include <string.h>

#include "clamav.h"
#include "others.h"

#ifndef	CL_DEBUG
#define	NDEBUG	/* map CLAMAV debug onto standard */
#endif

#include <stdlib.h>
#include <direct.h>
#include <io.h>
#include <pthread.h>

#ifdef	USE_SYSLOG
#include <strsafe.h>
#include "syslog.h"
#endif

static	const	char	*basename(const char *file_name);

/* Offset between 1/1/1601 and 1/1/1970 in 100 nanosec units */
#define _W32_FT_OFFSET (116444736000000000ULL)

/*
 * Patches for 64 bit support in opendir by
 *	Mark Pizzolato clamav-win32@subscriptions.pizzolato.net
 */
DIR *
opendir(const char *dirname)
{
	DIR *ret = cli_calloc(1, sizeof(DIR));
	char mask[_MAX_PATH + 3];
	size_t i, j;

	if(ret == NULL)
		return NULL;

	/* struct _WIN32_FIND_DATAA is what a LPWIN32_FIND_DATA points to */
	ret->find_file_data = cli_calloc(1, sizeof(struct _WIN32_FIND_DATAA));

	if(ret->find_file_data == NULL) {
		free(ret);
		return NULL;
	}
	ret->dir_name = cli_strdup(dirname);

	if(ret->dir_name == NULL) {
		free(ret->find_file_data);
		free(ret);
		return NULL;
	}

	j = strlen(dirname);
	for(i = 0; i < j; i++)
		if(ret->dir_name[i] == '/')
			ret->dir_name[i] = '\\';

	if(j && dirname[j - 1] == '\\')
		ret->dir_name[--j] = '\0';

	sprintf(mask, "%s\\*", ret->dir_name);

	ret->find_file_handle = FindFirstFile(mask,
				    (LPWIN32_FIND_DATA)ret->find_file_data);

	if(ret->find_file_handle == INVALID_HANDLE_VALUE) {
		free(ret->find_file_data);
		free(ret->dir_name);
		free(ret);

		cli_warnmsg("Can't opendir(%s)\n", dirname);
		return NULL;
	}

	ret->just_opened = TRUE;

	return ret;
}

struct dirent *
readdir(DIR *dir)
{
	/* NOTE: not thread safe */
	static struct dirent result;

	if(dir == NULL)
		return NULL;

	if(dir->just_opened)
		dir->just_opened = FALSE;
	else if(!FindNextFile((HANDLE)dir->find_file_handle, (LPWIN32_FIND_DATA)dir->find_file_data))
		switch(GetLastError ()) {
			case ERROR_NO_MORE_FILES:
				return NULL;
			default:
				errno = EIO;
				return NULL;
		}

	strcpy(result.d_name, basename(((LPWIN32_FIND_DATA)dir->find_file_data)->cFileName));

	return &result;
}

int
readdir_r(DIR *dir, struct dirent *dirent, struct dirent **output)
{
	if(dir == NULL)
		return -1;
	if(dirent == NULL)
		return -1;
	if(output == NULL)
		return -1;

	if(dir->just_opened)
		dir->just_opened = FALSE;
	else if(!FindNextFile((HANDLE)dir->find_file_handle, (LPWIN32_FIND_DATA)dir->find_file_data))
		switch(GetLastError()) {
			case ERROR_NO_MORE_FILES:
				*output = NULL;
				return -1;
			default:
				errno = EIO;
				*output = NULL;
				return -1;
		}

	strcpy(dirent->d_name, basename(((LPWIN32_FIND_DATA)dir->find_file_data)->cFileName));
	*output = dirent;

	return 0;
}

void
rewinddir(DIR *dir)
{
	char mask[_MAX_PATH + 3];

	if(dir == NULL)
		return;

	if(!FindClose((HANDLE)dir->find_file_handle))
		cli_warnmsg("rewinddir(): FindClose() failed\n");

	sprintf(mask, "%s\\*", dir->dir_name);

	dir->find_file_handle = FindFirstFile (mask,
					(LPWIN32_FIND_DATA)dir->find_file_data);

	if(dir->find_file_handle == INVALID_HANDLE_VALUE) {
		errno = EIO;
		return;
	}
	dir->just_opened = TRUE;
}

int
closedir(DIR *dir)
{
	if(dir == NULL)
		return -1;

	if(!FindClose((HANDLE)dir->find_file_handle)) {
		errno = EIO;
		return -1;
	}

	free(dir->dir_name);
	free(dir->find_file_data);
	free(dir);

	return 0;
}

static const char *
basename(const char *file_name)
{
	const char *base;

	if(file_name == NULL)
		return NULL;

	base = strrchr(file_name, '\\');

	if(base)
		return base + 1;

	if(isalpha(file_name[0] & 0xFF) && (file_name[1] == ':'))
		return (const char *)(file_name + 2);

	return file_name;
}

/* From the cygwin source code */
int
gettimeofday(struct timeval *tp, void *tz)
{
	if(tp) {
		union {
			unsigned long long ns100; /*time since 1 Jan 1601 in 100ns units */
			FILETIME ft;
		} _now;

		GetSystemTimeAsFileTime(&_now.ft);
		tp->tv_usec = (long)((_now.ns100 / 10ULL) % 1000000ULL );
		tp->tv_sec = (long)((_now.ns100 - _W32_FT_OFFSET) / 10000000ULL);
	}
	/*
	 * Always return 0 as per Open Group Base Specifications Issue 6.
	 * Do not set errno on error.
	 */
	return 0;
}

/* TODO */
int
geteuid(void)
{
	return 0;
}

int
getuid(void)
{
	return 0;
}

int
getgid(void)
{
	return 0;
}

/*
 * mmap patches for more than one map area by
 *	Mark Pizzolato clamav-win32@subscriptions.pizzolato.net
 */
static pthread_mutex_t mmap_mutex = PTHREAD_MUTEX_INITIALIZER;

static struct mmap_context {
	struct mmap_context *link;
	HANDLE h;
	LPVOID view;
	size_t length;
} *mmaps = NULL;

caddr_t
mmap(caddr_t address, size_t length, int protection, int flags, int fd, off_t offset)
{
	LPVOID addr;
	HANDLE h;
	struct mmap_context *ctx;

	if(flags != MAP_PRIVATE) {
		cli_errmsg("mmap: only MAP_PRIVATE is supported\n");
		return MAP_FAILED;
	}
	if(protection != PROT_READ) {
		cli_errmsg("mmap: only PROT_READ is supported\n");
		return MAP_FAILED;
	}
	if(address != NULL) {
		cli_errmsg("mmap: only NULL map address is supported\n");
		return MAP_FAILED;
	}
	h = CreateFileMapping((HANDLE)_get_osfhandle(fd), NULL, PAGE_READONLY, 0, 0, NULL);

	if(h == NULL) {
		cli_errmsg("mmap: CreateFileMapping failed - error %d\n",
			GetLastError());
		return MAP_FAILED;
	}
	if(GetLastError() == ERROR_ALREADY_EXISTS) {
		cli_errmsg("mmap: ERROR_ALREADY_EXISTS\n");
		CloseHandle(h);
		return MAP_FAILED;
	}
	addr = MapViewOfFile(h, FILE_MAP_READ,
		(DWORD)0, ((DWORD)offset & 0xFFFFFFFF),
		length);

	if(addr == NULL) {
		cli_errmsg("mmap failed - error %d\n", GetLastError());
		CloseHandle(h);
		return MAP_FAILED;
	}
	pthread_mutex_lock(&mmap_mutex);
	ctx = cli_malloc(sizeof(*ctx));
	if(NULL == ctx) {
		pthread_mutex_unlock(&mmap_mutex);
		cli_errmsg("mmap: can't create context block\n");
		UnmapViewOfFile(addr);
		CloseHandle(h);
		return MAP_FAILED;
	}
	ctx->h = h;
	ctx->view = addr;
	ctx->length = length;
	ctx->link = mmaps;
	mmaps = ctx;
	pthread_mutex_unlock(&mmap_mutex);
	return (caddr_t)addr;
}

int
munmap(caddr_t addr, size_t length)
{
	struct mmap_context *ctx = mmaps, *lctx = NULL;

	pthread_mutex_lock(&mmap_mutex);
	
	for(; ctx && (ctx->view != addr); ctx = ctx->link)
		lctx = ctx;

	if(ctx == NULL) {
		pthread_mutex_unlock(&mmap_mutex);
		cli_warnmsg("munmap with no corresponding mmap\n");
		return -1;
	}

	if(ctx->length != length) {
		pthread_mutex_unlock(&mmap_mutex);
		cli_warnmsg("munmap with incorrect length specified (%u != %u) - partial munmap unsupported\n",
			length, ctx->length);
		return -1;
	}

	if(lctx == NULL)
		mmaps = ctx->link;
	else
		lctx->link = ctx->link;

	pthread_mutex_unlock(&mmap_mutex);

	UnmapViewOfFile(ctx->view);
	CloseHandle(ctx->h);
	free(ctx);

	return 0;
}

int
chown(const char *filename, short uid, short gid)
{
	return 0;
}

#ifdef	USE_SYSLOG
/*
 * Put into the Windows Event Log
 *	Right Click My Computer->Manage->Event Viewer->Application
 * See http://cybertiggyr.com/gene/wel/src/insert-log.c for inspiration
 * http://msdn2.microsoft.com/en-gb/library/aa363634(VS.85).aspx
 * and http://msdn2.microsoft.com/en-us/library/aa363680(VS.85).aspx
 *
 * FIXME: Not thread safe, but see shared/output.c, which ensures this code is
 *	single threaded - therefore don't call this code directly
 */
static	HANDLE	logg_handle;
static	int	initlog(const char *source);

void
openlog(const char *name, int options, int facility)
{
	if(logg_handle != NULL)
		closelog();
	else
		(void)initlog(name);

	logg_handle = RegisterEventSource(NULL, name);

	if(logg_handle == NULL)
		cli_warnmsg("openlog: Can't register source %s - error %d\n", name, GetLastError());
}

void
closelog(void)
{
	if(logg_handle != NULL)
		DeregisterEventSource(logg_handle);
}

void
syslog(int level, const char *format, ...)
{
	if(logg_handle == NULL)
		openlog("Clam AntiVirus", 0, LOG_LOCAL6);
		
	if(logg_handle != NULL) {
		va_list args;
		char buff[512];
		char *ptr;

		va_start(args, format);
		(void)vsnprintf(buff, sizeof(buff), format, args);
		va_end(args);
		
		ptr = buff;

		/*
		 * Category = 0, eventId = CLAMAV_EVENTMSG, SID = NULL, 1 string
		 */
		if(!ReportEventA(logg_handle, (WORD)level, 0, CLAMAV_EVENTMSG, NULL, 1, 0, (LPCSTR *)&ptr, NULL))
			cli_warnmsg("syslog: ReportEventA(%d, %d, 0, %d, NULL, 1, 0, %s, NULL) failed: %d\n",
				logg_handle, level, CLAMAV_EVENTMSG, buff, GetLastError());
	}
}

static int
initlog(const char *source)
{
	/*DWORD dwCategoryNum = 1;	/* The number of categories for the event source. */
	HKEY hk; 
	DWORD dwData, /*dwDisp,*/ len;
	char path[MAX_PATH];

	/* Create the event source as a subkey of the log. */
	(void)snprintf(path, sizeof(path),
		"SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\%s",
		source);
 
	if(RegCreateKey(HKEY_LOCAL_MACHINE, path, &hk) != 0) {
		cli_warnmsg("Could not create the registry key\n"); 
		/*return 0;*/
	}
 
	/* Set the name of the message file. */
	GetModuleFileName(NULL, path, sizeof(path));
	path[sizeof(path) - 1] = '\0';
	len = (DWORD)(strlen(path) + 1);

	if(RegSetValueEx(hk, "EventMessageFile", 0, REG_EXPAND_SZ, (LPBYTE)path, len)) {
		cli_warnmsg("Could not set the event message file\n"); 
		RegCloseKey(hk); 
		return 0;
	}
 
	/* Set the supported event types. */
	dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE; 
 
	if(RegSetValueEx(hk, "TypesSupported", 0, REG_DWORD, (LPBYTE)&dwData, sizeof(DWORD))) { 
		cli_warnmsg("Could not set the supported types\n"); 
		RegCloseKey(hk); 
		return 0;
	}
 
#if	0
	/* Set the category message file and number of categories. */
	if(RegSetValueEx(hk, "CategoryMessageFile", 0, REG_EXPAND_SZ,
	    (LPBYTE)path, len)) {
		cli_warnmsg("Could not set the category message file\n"); 
		RegCloseKey(hk); 
		return 0;
	}
 
	if(RegSetValueEx(hk, "CategoryCount", 0, REG_DWORD, (LPBYTE)&dwCategoryNum, sizeof(DWORD))) {
		cli_warnmsg("Could not set the category count\n"); 
		RegCloseKey(hk); 
		return 0;
	}
#endif

	RegCloseKey(hk); 
	return 1;
}
#endif
