/*
 *  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"
#include "defaults.h"

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

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

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 k;

	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 = strdup(dirname);

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

	k = strlen(dirname);
	if(k && dirname[k - 1] == '\\')
		ret->dir_name[--k] = '\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]) && 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_SHARED 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, *lctx = NULL;

	pthread_mutex_lock(&mmap_mutex);
	for(ctx = mmaps; ctx && (ctx->view != addr); ) {
		lctx = ctx;
		ctx = ctx->link;
	}
	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 - partial munmap unsupported\n");
		return -1;
	}
	if(NULL == lctx)
		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;
}