/*
 * Copyright (C) 2015 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
 * Copyright (C) 2010 Sourcefire, Inc.
 * Authors: aCaB <acab@clamav.net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation.
 *
 * 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 Street, Fifth Floor, Boston, MA 02110-1301
 * USA
 */

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

#include "clamav.h"
#include "others.h"
#include "shared/output.h"
#include "mpool.h"
#include "clscanapi.h"
#include "interface.h"
#include "cache.h"

const char *types[] = {
	"<<rsvd>>",		/*  0 */
	"MSEXE",		/*  1 */
	"HTML",		/*  2 */
	"HTML_UTF16",	/*  3 */
	"GRAPHICS",		/*  4 */
	"TEXT_ASCII",	/*  5 */
	"TEXT_UTF8",	/*  6 */
	"TEXT_UTF16LE",	/*  7 */
	"TEXT_UTF16BE",	/*  8 */
	"PDF",		/*  9 */
	"SCRIPT",		/* 10 */
	"RTF",		/* 11 */
	"RIFF",		/* 12 */
	"MSCHM",		/* 13 */
	"MSCAB",		/* 14 */
	"MSOLE2",		/* 15 */
	"MSSZDD",		/* 16 */
	"ZIP",		/* 17 */
	"RAR",		/* 18 */
	"7Z",		/* 19 */
	"BZ",		/* 20 */
	"GZ",		/* 21 */
	"ARJ",		/* 22 */
	"ZIPSFX",		/* 23 */
	"RARSFX",		/* 24 */
	"CABSFX",		/* 25 */
	"ARJSFX",		/* 26 */
	"NULSFT",		/* 27 */
	"AUTOIT",		/* 28 */
	"ISHIELD_MSI",	/* 29 */
	"SFX",		/* 30 */
	"BINHEX",		/* 31 */
	"MAIL",		/* 32 */
	"TNEF",		/* 33 */
	"BINARY_DATA",	/* 34 */
	"CRYPTFF",		/* 35 */
	"UUENCODED",	/* 36 */
	"SCRENC",		/* 37 */
	"POSIX_TAR",	/* 38 */
	"OLD_TAR",		/* 39 */
	"ELF",		/* 40 */
	"MACHO",		/* 41 */
	"MACHO_UNIBIN",	/* 42 */
	"SIS",		/* 43 */
	"SWF",		/* 44 */
	"CPIO_ODC",		/* 45 */
	"CPIO_NEWC",	/* 46 */
	"CPIO_CRC",		/* 47 */
	"ISO9660",		/* 48 */
	"JAVA",		/* 49 */
	"DMG",		/* 50 */
	"XAR",		/* 51 */
	"PART_ANY",		/* 52 */
	"PART_HFSPLUS",	/* 53 */
	"XZ",		/* 54 */
	"OOXML_WORD",	/* 55 */
	"OOXML_PPT",	/* 56 */
	"OOXML_XL",		/* 57 */
	"7SFX",			/* 58 */
	"APM",			/* 59 */
	"XDP",			/* 60 */
	"XML_WORD",		/* 61 */
	"XML_XL",		/* 62 */
	"XML_HWP",		/* 63 */
	"HWP3",			/* 64 */
	"OOXML_HWP",	/* 65 */
	"HWPOLE2",		/* 66 */
	"CPIO_OLD",		/* 67 */
	"MBR",			/* 68 */
	"GPT",			/* 69 */
	NULL
};

int WINAPI SHCreateDirectoryExA(HWND, LPCTSTR, SECURITY_ATTRIBUTES *); /* cannot include Shlobj.h due to DATADIR collision */

#define FMT(s) "!"__FUNCTION__": "s"\n"
#define FAIL(errcode, fmt, ...) do { logg(FMT(fmt), __VA_ARGS__); return (errcode); } while(0)
#define WIN() do { logg("*%s completed successfully\n", __FUNCTION__); return CLAMAPI_SUCCESS; } while(0)
#define INFN() do { logg("*in %s\n", __FUNCTION__); } while(0)

#define MAX_VIRNAME_LEN 1024

HANDLE reload_event;
volatile LONG reload_waiters = 0;

HANDLE monitor_event;
HANDLE monitor_hdl = NULL;

HANDLE engine_mutex;
/* protects the following items */
struct cl_engine *engine = NULL;
char dbdir[PATH_MAX];
char tmpdir[PATH_MAX];
FILETIME last_chk_time = {0, 0};
/* end of protected items */

typedef struct {
	CLAM_SCAN_CALLBACK scancb;
	void *scancb_ctx;
	struct cl_scan_options* scanopts;
	_int64 *filetype;
} instance;

struct {
	instance *inst;
	unsigned int refcnt;
} *instances = NULL;
unsigned int ninsts_total = 0;
unsigned int ninsts_avail = 0;
unsigned int official_sigs, custom_sigs;
HANDLE instance_mutex;

BOOL minimal_definitions = FALSE;

#define lock_engine()(WaitForSingleObject(engine_mutex, INFINITE) == WAIT_FAILED)
#define unlock_engine() do {ReleaseMutex(engine_mutex);} while(0)

#define lock_instances()(WaitForSingleObject(instance_mutex, INFINITE) == WAIT_FAILED)
#define unlock_instances() do {ReleaseMutex(instance_mutex);} while(0)

cl_error_t filetype_cb(int fd, const char *detected_file_type, void *context);
cl_error_t prescan_cb(int fd, const char *detected_file_type, void *context);
cl_error_t postscan_cb(int fd, int result, const char *virname, void *context);


DWORD WINAPI monitor_thread(VOID *p) {
	char watchme[PATH_MAX];
	HANDLE harr[2], fff;

	if(lock_engine()) {
	logg("^monitor_thread: failed to lock engine\n");
	return 0;
	}

	snprintf(watchme, sizeof(watchme), "%s\\forcerld", dbdir);
	watchme[sizeof(watchme)-1] = '\0';

	harr[0] = monitor_event;
	harr[1] = FindFirstChangeNotification(dbdir, FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_FILE_NAME);

	unlock_engine();

	if(harr[1] == INVALID_HANDLE_VALUE) {
	logg("^monitor_thread: failed to monitor directory changes on %s\n", dbdir);
	return 0;
	}

	logg("monitor_thread: watching directory changes on %s\n", dbdir);

	while(1) {
	WIN32_FIND_DATA wfd;
	SYSTEMTIME st;

	switch(WaitForMultipleObjects(2, harr, FALSE, INFINITE)) {
	case WAIT_OBJECT_0:
		logg("*monitor_thread: terminating upon request\n");
		FindCloseChangeNotification(harr[1]);
		return 0;
	case WAIT_OBJECT_0 + 1:
		break;
	default:
		logg("*monitor_thread: unexpected wait failure - %u\n", GetLastError());
		Sleep(1000);
		continue;
	}
	FindNextChangeNotification(harr[1]);
	if((fff = FindFirstFile(watchme, &wfd)) == INVALID_HANDLE_VALUE)
		continue;
	FindClose(fff);

	GetSystemTime(&st);
	SystemTimeToFileTime(&st, &wfd.ftCreationTime);
	if(CompareFileTime(&wfd.ftLastWriteTime, &wfd.ftCreationTime) > 0)
		wfd.ftLastWriteTime = wfd.ftCreationTime;
	if(CompareFileTime(&wfd.ftLastWriteTime, &last_chk_time) <= 0)
		continue;

	logg("monitor_thread: reload requested!\n");
	Scan_ReloadDatabase(minimal_definitions);
	GetSystemTime(&st);
	SystemTimeToFileTime(&st, &last_chk_time); /* FIXME: small race here */
	}
}

static wchar_t *threat_type(const char *virname) {
	if(!virname)
	return NULL;
	if(!strncmp(virname, "Trojan", 6))
	return L"Trojan";
	if(!strncmp(virname, "Worm", 4))
	return L"Worm";
	if(!strncmp(virname, "Exploit", 7))
	return L"Exploit";
	if(!strncmp(virname, "Adware", 6))
	return L"Adware";
	return L"Malware";
}

static int add_instance(instance *inst) {
	unsigned int i;

	INFN();
	if(lock_instances()) {
	logg("!add_instance: failed to lock instances\n");
	return 1;
	}
	if(!ninsts_avail) {
	void *freeme, *new_instances = calloc(ninsts_total + 256, sizeof(*instances));
	if(!new_instances) {
		unlock_instances();
		logg("!add_instance: failed to grow instances\n");
		return 1;
	}
	freeme = instances;
	if(instances && ninsts_total)
		memcpy(new_instances, instances, ninsts_total * sizeof(*instances));
	ninsts_total += 256;
	ninsts_avail += 256;
	instances = new_instances;
	if(freeme)
		free(freeme);
	logg("*add_instance: instances grown to %u\n", ninsts_total);
	}
	for(i=0; i<ninsts_total; i++) {
	if(instances[i].inst)
		continue;
	instances[i].inst = inst;
	instances[i].refcnt = 0;
	ninsts_avail--;
	logg("*add_instance: now %u/%u instances available\n", ninsts_avail, ninsts_total);
	unlock_instances();
	return 0;
	}
	logg("!add_instances: you should not be reading this\n");
	unlock_instances();
	return 1;
}

static int del_instance(instance *inst) {
	unsigned int i;

	INFN();
	if(lock_instances()) {
	logg("!del_instance: failed to lock instances\n");
	return CL_ELOCK;
	}
	for(i=0; i<ninsts_total; i++) {
	if(instances[i].inst != inst)
		continue;
	if(instances[i].refcnt) {
		logg("^del_instance: attempted to free instance with %d active scanners\n", instances[i].refcnt);
		unlock_instances();
		return CL_EBUSY;
	}
	instances[i].inst = NULL;
	instances[i].refcnt = 0;
	ninsts_avail++;
	logg("*del_instance: %u / %u instances now available\n", ninsts_avail, ninsts_total);
	unlock_instances();
	return CL_SUCCESS;
	}
	logg("!del_instances: instance %p not found\n", inst);
	unlock_instances();
	return CL_EARG;
}

/* To be called with the instances locked */
static int is_instance(instance *inst) {
	unsigned int i;
	INFN();
	for(i=0; i<ninsts_total; i++)
	if(instances[i].inst == inst)
		return 1;
	logg("^is_instance: lookup failed for instance %p\n", inst);
	return 0;
}

BOOL interface_setup(void) {
	if(!(engine_mutex = CreateMutex(NULL, FALSE, NULL)))
	return FALSE;
	if(!(reload_event = CreateEvent(NULL, TRUE, TRUE, NULL))) {
	CloseHandle(engine_mutex);
	return FALSE;
	}
	if(!(monitor_event = CreateEvent(NULL, TRUE, FALSE, NULL))) {
	CloseHandle(reload_event);
	CloseHandle(engine_mutex);
	return FALSE;
	}
	if(!(instance_mutex = CreateMutex(NULL, FALSE, NULL))) {
	CloseHandle(monitor_event);
	CloseHandle(reload_event);
	CloseHandle(engine_mutex);
	return FALSE;
	}
	return TRUE;
}

static int sigload_callback(const char *type, const char *name, unsigned int custom, void *context) {
	if(!strcmp(type, "fp"))
	return 0;
	if(minimal_definitions && !custom)
	return 1;
	if(custom)
	custom_sigs++;
	else
	official_sigs++;
	return 0;
}

const char* cli_ctime(const time_t *timep, char *buf, const size_t bufsize);
/* Must be called with engine_mutex locked ! */
static void touch_last_update(unsigned signo) {
	char touchme[PATH_MAX];
	HANDLE h;

	snprintf(touchme, sizeof(touchme), "%s\\lastupd", dbdir);
	touchme[sizeof(touchme)-1] = '\0';
	if((h = CreateFile(touchme, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)) != INVALID_HANDLE_VALUE) {
	DWORD d;
	int err;
	unsigned ver = (unsigned)cl_engine_get_num(engine, CL_ENGINE_DB_VERSION, &err);
	if (ver) {
		char timestr[32];
		const char *tstr;
		time_t t;
		t = cl_engine_get_num(engine, CL_ENGINE_DB_TIME, NULL);
		tstr = cli_ctime(&t, timestr, sizeof(timestr));
		/* cut trailing \n */
		timestr[strlen(tstr)-1] = '\0';
		snprintf(touchme, sizeof(touchme), "daily %u/%u sigs\n"
			 "Database version: %u/%s\n"
			 "Known viruses: %u\n"
			 "Reloaded at: %d\n",
			 ver, signo, ver, tstr, signo, (unsigned)time(NULL));
	} else {
		snprintf(touchme, sizeof(touchme), "no daily/%u sigs\n"
			 "Known viruses: %u\n"
			 "Reloaded at: %d\n",
			 signo, signo, (unsigned)time(NULL));
	}
	touchme[sizeof(touchme)-1] = '\0';
	if(WriteFile(h, touchme, strlen(touchme), &d, NULL)) {
		/* SetEndOfFile(h); */
		GetFileTime(h, NULL, NULL, &last_chk_time);
	}
	CloseHandle(h);
	} else
	logg("^touch_last_lastcheck: failed to touch lastreload\n");
}


/* Must be called with engine_mutex locked ! */
static int load_db(void) {
	unsigned int signo = 0;
	size_t used, total;
	int ret;


	INFN();

	cl_engine_set_clcb_sigload(engine, sigload_callback, NULL);
	official_sigs = custom_sigs = 0;
	if((ret = cl_load(dbdir, engine, &signo, CL_DB_STDOPT & ~CL_DB_PHISHING & ~CL_DB_PHISHING_URLS)) != CL_SUCCESS) {
	cl_engine_free(engine);
	engine = NULL;
	FAIL(ret, "Failed to load database: %s", cl_strerror(ret));
	}

	if((ret = cl_engine_compile(engine))) {
	cl_engine_free(engine);
	engine = NULL;
	FAIL(ret, "Failed to compile engine: %s", cl_strerror(ret));
	}

	logg("load_db: loaded %d signatures (%u official, %u custom)\n", signo, official_sigs, custom_sigs); 
	if (!mpool_getstats(engine, &used, &total))
	logg("load_db: memory %.3f MB / %.3f MB\n", used/(1024*1024.0), total/(1024*1024.0));

	touch_last_update(signo);

	WIN();
}

int CLAMAPI Scan_HaveSigs(unsigned int *official, unsigned int *custom) {
	int ret;
	if(lock_engine())
	FAIL(CL_ELOCK, "failed to lock engine");
	if(!engine) {
	unlock_engine();
	FAIL(CL_ESTATE, "Engine unavailable");
	}
	ret = ((official_sigs + custom_sigs) > 0);
	if(official) *official = official_sigs;
	if(custom) *custom = custom_sigs;
	unlock_engine();
	return ret;
}

static void free_engine_and_unlock(void) {
	cl_engine_free(engine);
	engine = NULL;
	unlock_engine();
}

int CLAMAPI Scan_Initialize(const wchar_t *pEnginesFolder, const wchar_t *pTempRoot, const wchar_t *pLicenseKey, BOOL bLoadMinDefs) {
	BOOL cant_convert;
	int ret;

	logg("*in Scan_Initialize(pEnginesFolder = %S, pTempRoot = %S)\n", pEnginesFolder, pTempRoot);
	if(!pEnginesFolder)
	FAIL(CL_ENULLARG, "pEnginesFolder is NULL");
	if(!pTempRoot)
	FAIL(CL_ENULLARG, "pTempRoot is NULL");
	if(lock_engine())
	FAIL(CL_ELOCK, "failed to lock engine");
	if(engine) {
	unlock_engine();
	FAIL(CL_ESTATE, "Already initialized");
	}

	if(!(engine = cl_engine_new())) {
	unlock_engine();
	FAIL(CL_EMEM, "Not enough memory for a new engine");
    }
    cl_engine_set_clcb_pre_cache(engine, filetype_cb);
    cl_engine_set_clcb_pre_scan(engine, prescan_cb);
    cl_engine_set_clcb_post_scan(engine, postscan_cb);

    /* In mindefs mode disable the cache
     * In this mode, no standard clamav signatures are loaded and it is used as an unpacking engine.
     * Turn off caching, so that files can always be unpacked/unarchived.
     */
    if (bLoadMinDefs) {
		if ((ret = cl_engine_set_num(engine, CL_ENGINE_DISABLE_CACHE, 1) != CL_SUCCESS)) {
		logg("Failed to disable engine cache: %s", cl_strerror(ret));
		}
	}

    minimal_definitions = bLoadMinDefs;
    if(bLoadMinDefs)
	logg("^MINIMAL DEFINITIONS MODE ON!\n");

	if(!WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, pTempRoot, -1, tmpdir, sizeof(tmpdir), NULL, &cant_convert) || cant_convert) {
	free_engine_and_unlock();
	FAIL(CL_EARG, "Can't translate pTempRoot");
	}
	ret = strlen(tmpdir);
	while(ret>0 && tmpdir[--ret] == '\\')
	tmpdir[ret] = '\0';
	if(!ret || ret + 8 + 1 >= sizeof(tmpdir)) {
	free_engine_and_unlock();
	FAIL(CL_EARG, "Bad or too long pTempRoot '%s'", tmpdir);
	}
	memcpy(&tmpdir[ret+1], "\\clamtmp", 9);
	cli_rmdirs(tmpdir);
	if((ret = SHCreateDirectoryExA(NULL, tmpdir, NULL) != ERROR_SUCCESS) && ret != ERROR_ALREADY_EXISTS) {
	free_engine_and_unlock();
	FAIL(CL_ETMPDIR, "Cannot create pTempRoot '%s': error %d", tmpdir, ret);
	}
	/* callbacks require cli_map_scan to generate fds */
	if((ret = cl_engine_set_num(engine, CL_ENGINE_FORCETODISK, 1))) {
	free_engine_and_unlock();
	FAIL(ret, "Failed to set engine forced-to-disk: %s", cl_strerror(ret));
	}
	if((ret = cl_engine_set_str(engine, CL_ENGINE_TMPDIR, tmpdir))) {
	free_engine_and_unlock();
	FAIL(ret, "Failed to set engine tempdir to '%s': %s", tmpdir, cl_strerror(ret));
	}
	if(!WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, pEnginesFolder, -1, dbdir, sizeof(dbdir), NULL, &cant_convert) || cant_convert) {
	free_engine_and_unlock();
	FAIL(CL_EARG, "Can't translate pEnginesFolder");
	}
	ret = load_db();
	unlock_engine();

	if(!ret) {
	ResetEvent(monitor_event);
	if(!(monitor_hdl = CreateThread(NULL, 0, monitor_thread, NULL, 0, NULL)))
		logg("^Failed to start db monitoring thread\n");
	}

	logg("*Scan_Initialize: returning %d\n", ret);
	return ret;
}

int uninitialize_called = 0;
int CLAMAPI Scan_Uninitialize(void) {
 //   int rett;
 //   __asm {
	//MOV eax, [ebp + 4]
	//mov rett, eax
 //   }
 //   logg("%x", rett);
	uninitialize_called = 1;
	INFN();

	if(monitor_hdl) {
	SetEvent(monitor_event);
	if(WaitForSingleObject(monitor_hdl, 5000) != WAIT_OBJECT_0) {
		logg("^Scan_Uninitialize: forcibly terminating monitor thread after 5 seconds\n");
		TerminateThread(monitor_hdl, 0);
	}
	}
	monitor_hdl = NULL;

	if(lock_engine())
	FAIL(CL_ELOCK, "failed to lock engine");
	if(!engine) {
	unlock_engine();
	FAIL(CL_ESTATE, "attempted to uninit a NULL engine");
	}

	if(lock_instances()) {
	unlock_engine();
	FAIL(CL_ELOCK, "failed to lock instances");
	}
	if(ninsts_avail != ninsts_total) {
	volatile unsigned int refcnt = ninsts_total - ninsts_avail;
	unlock_instances();
	unlock_engine();
	FAIL(CL_EBUSY, "Attempted to uninit the engine with %u active instances", refcnt);
	}
	unlock_instances();
	free_engine_and_unlock();

	WIN();
}

int CLAMAPI Scan_CreateInstance(CClamAVScanner **ppScanner) {
	instance *inst;
	struct cl_scan_options *scanopts;

	INFN();
	if(!ppScanner)
	FAIL(CL_ENULLARG, "NULL pScanner");
	inst = (instance *)calloc(1, sizeof(*inst));
	if(!inst)
		FAIL(CL_EMEM, "CreateInstance: OOM");

	scanopts = (struct cl_scan_options *)calloc(1, sizeof(struct cl_scan_options));
	memset(scanopts, 0, sizeof(struct cl_scan_options));
	if(!scanopts) {
		free(inst);
		FAIL(CL_EMEM, "CreateInstance: OOM");
	}
	inst->scanopts = scanopts;

	if(lock_engine()) {
		free(inst->scanopts);
		free(inst);
		FAIL(CL_ELOCK, "CreateInstance: Failed to lock engine");
	}
	if(!engine) {
		free(inst->scanopts);
		free(inst);
		unlock_engine();
		FAIL(CL_ESTATE, "CreateInstance: Create instance called with no engine");
	}
	if(add_instance(inst)) {
		free(inst->scanopts);
		free(inst);
		unlock_engine();
		FAIL(CL_EMEM, "CreateInstance: add_instance failed");
	}
	unlock_engine();
	inst->scanopts->parser |= ~0;  /* enable all parsers */
	inst->scanopts->general |= CL_SCAN_GENERAL_HEURISTICS;  /* enable heuristic alert options */
	if (logg_verbose)
		inst->scanopts->dev |= CL_SCAN_DEV_COLLECT_PERFORMANCE_INFO;

	*ppScanner = (CClamAVScanner *)inst;
	logg("Created new instance %p\n", inst);
	WIN();
}

// Caller: if we return error will retry once after 2 seconds.
// No point in retrying more times since we are shutting down anyway.
int CLAMAPI Scan_DestroyInstance(CClamAVScanner *pScanner) {
	int rc;
	instance *inst = (instance*)pScanner;
	INFN();
	if(!pScanner)
		FAIL(CL_ENULLARG, "NULL pScanner");
	if((rc = del_instance(inst))) {
		if (rc == CL_EBUSY) {
			// wait for one of the scanner threads to finish, and retry again,
			// that's better than caller always waiting 2 seconds to retry.
			if (WaitForSingleObject(reload_event, 1000) != WAIT_OBJECT_0)
			logg("Scan_DestroyInstance: timeout");
			rc = del_instance(inst);
		}
		if (rc)
			FAIL(rc, "del_instance failed for %p", pScanner);
	}
	free(inst->scanopts);
	free(inst);
	logg("in Scan_DestroyInstance: Instance %p destroyed\n", pScanner);
	WIN();
}

int CLAMAPI Scan_SetScanCallback(CClamAVScanner *pScanner, CLAM_SCAN_CALLBACK pfnCallback, void *pContext) {
	instance *inst;

	logg("*in SetScanCallback(pScanner = %p, pfnCallback = %p, pContext = %p)\n", pScanner, pfnCallback, pContext);
	if(!pScanner)
	FAIL(CL_ENULLARG, "NULL pScanner");
	if(lock_instances())
	FAIL(CL_ELOCK, "failed to lock instances for instance %p", pScanner);

	inst = (instance *)pScanner;
	if(is_instance(inst)) {
	inst->scancb = pfnCallback;
	inst->scancb_ctx = pContext;
	unlock_instances();
	WIN();
	}
	unlock_instances();
	FAIL(CL_EARG, "invalid instance %p", inst);
}

int CLAMAPI Scan_SetOption(CClamAVScanner *pScanner, int option, void *value, unsigned long inputLength) {
	instance *inst;
	unsigned int whichopt, newval;
	
	INFN();
	if(!pScanner)
	FAIL(CL_ENULLARG, "NULL pScanner");
	if(!value)
	FAIL(CL_ENULLARG, "NULL value");
	if(lock_instances())
	FAIL(CL_ELOCK, "failed to lock instances");

	inst = (instance *)pScanner;
	if(!is_instance(inst)) {
	unlock_instances();
	FAIL(CL_EARG, "invalid instance %p", inst);
	}
	newval = *(unsigned int *)value;
	switch(option) {
	case CLAM_OPTION_SCAN_ARCHIVE:
		logg("CLAM_OPTION_SCAN_ARCHIVE: %s on instance %p\n", newval ? "enabled" : "disabled", inst);
		whichopt = CL_SCAN_PARSE_ARCHIVE;
		break;
	case CLAM_OPTION_SCAN_MAIL:
		logg("CLAM_OPTION_SCAN_MAIL: %s on instance %p\n", newval ? "enabled" : "disabled", inst);
		whichopt = CL_SCAN_PARSE_MAIL;
		break;
	case CLAM_OPTION_SCAN_OLE2:
		logg("CLAM_OPTION_SCAN_OLE2: %s on instance %p\n", newval ? "enabled" : "disabled", inst);
		whichopt = CL_SCAN_PARSE_OLE2;
		break;
	case CLAM_OPTION_SCAN_HTML:
		logg("CLAM_OPTION_SCAN_HTML: %s on instance %p\n", newval ? "enabled" : "disabled", inst);
		whichopt = CL_SCAN_PARSE_HTML;
		break;
	case CLAM_OPTION_SCAN_PE:
		logg("CLAM_OPTION_SCAN_PE: %s on instance %p\n", newval ? "enabled" : "disabled", inst);
		whichopt = CL_SCAN_PARSE_PE;
		break;
	case CLAM_OPTION_SCAN_PDF:
		logg("CLAM_OPTION_SCAN_PDF: %s on instance %p\n", newval ? "enabled" : "disabled", inst);
		whichopt = CL_SCAN_PARSE_PDF;
		break;
	case CLAM_OPTION_SCAN_ALGORITHMIC:
		logg("CLAM_OPTION_SCAN_ALGORITHMIC: %s on instance %p\n", newval ? "enabled" : "disabled", inst);
		whichopt = CL_SCAN_GENERAL_HEURISTICS;
		break;
	case CLAM_OPTION_SCAN_ELF:
		logg("CLAM_OPTION_SCAN_ELF: %s on instance %p\n", newval ? "enabled" : "disabled", inst);
		whichopt = CL_SCAN_PARSE_ELF;
		break;
	case CLAM_OPTION_SCAN_SWF:
		logg("CLAM_OPTION_SCAN_SWF: %s on instance %p\n", newval ? "enabled" : "disabled", inst);
		whichopt = CL_SCAN_PARSE_SWF;
		break;
	default:
		unlock_instances();
		FAIL(CL_EARG, "Unsupported option: %d", option);
	}

	if(!newval)
	inst->scanopts &= ~whichopt;
	else
	inst->scanopts |= whichopt;
	unlock_instances();
	WIN();
}

int CLAMAPI Scan_GetOption(CClamAVScanner *pScanner, int option, void *value, unsigned long inputLength, unsigned long *outLength) {
	instance *inst;
	unsigned int whichopt;

	INFN();
	if(!pScanner)
	FAIL(CL_ENULLARG, "NULL pScanner");
	if(!value || !inputLength)
	FAIL(CL_ENULLARG, "NULL value");
	if(lock_instances())
	FAIL(CL_ELOCK, "failed to lock instances");

	inst = (instance *)pScanner;
	if(!is_instance(inst)) {
	unlock_instances();
	FAIL(CL_EARG, "invalid instance %p", inst);
	}
	switch(option) {
	case CLAM_OPTION_SCAN_ARCHIVE:
		whichopt = CL_SCAN_PARSE_ARCHIVE;
		break;
	case CLAM_OPTION_SCAN_MAIL:
		whichopt = CL_SCAN_PARSE_MAIL;
		break;
	case CLAM_OPTION_SCAN_OLE2:
		whichopt = CL_SCAN_PARSE_OLE2;
		break;
	case CLAM_OPTION_SCAN_HTML:
		whichopt = CL_SCAN_PARSE_HTML;
		break;
	case CLAM_OPTION_SCAN_PE:
		whichopt = CL_SCAN_PARSE_PE;
		break;
	case CLAM_OPTION_SCAN_PDF:
		whichopt = CL_SCAN_PARSE_PDF;
		break;
	case CLAM_OPTION_SCAN_ALGORITHMIC:
		whichopt = CL_SCAN_GENERAL_HEURISTICS;
		break;
	case CLAM_OPTION_SCAN_ELF:
		whichopt = CL_SCAN_PARSE_ELF;
		break;
	case CLAM_OPTION_SCAN_SWF:
		whichopt = CL_SCAN_PARSE_SWF;
		break;
	default:
		unlock_instances();
		FAIL(CL_EARG, "Unsupported option: %d", option);
	}

	*(unsigned int *)value = (inst->scanopts & whichopt) != 0;
	unlock_instances();
	WIN();
}


int CLAMAPI Scan_GetLimit(int option, unsigned int *value) {
	enum cl_engine_field limit;
	long long curlimit;
	int err;

	INFN();
	if(lock_engine())
	FAIL(CL_ELOCK, "Failed to lock engine");
	if(!engine) {
	unlock_engine();
	FAIL(CL_ESTATE, "Engine is NULL");
	}
	switch((enum CLAM_LIMIT_TYPE)option) {
	case CLAM_LIMIT_FILESIZE:
	limit = CL_ENGINE_MAX_FILESIZE;
	break;
	case CLAM_LIMIT_SCANSIZE:
	limit = CL_ENGINE_MAX_SCANSIZE;
	break;
	case CLAM_LIMIT_RECURSION:
	limit = CL_ENGINE_MAX_RECURSION;
	break;
	default:
	unlock_engine();
	FAIL(CL_EARG, "Unsupported limit type: %d", option);
	}
	curlimit = cl_engine_get_num(engine, limit, &err);
	if(err) {
	unlock_engine();
	FAIL(err, "Failed to get engine value: %s", cl_strerror(err));
	}
	if(curlimit > 0xffffffff)
	*value = 0xffffffff;
	else
	*value = (unsigned int)curlimit;
	unlock_engine();
	WIN();
}


int CLAMAPI Scan_SetLimit(int option, unsigned int value) {
	enum cl_engine_field limit;
	int err;

	INFN();
	if(lock_engine())
	FAIL(CL_ELOCK, "Failed to lock engine");
	if(!engine) {
	unlock_engine();
	FAIL(CL_ESTATE, "Engine is NULL");
	}
	switch((enum CLAM_LIMIT_TYPE)option) {
	case CLAM_LIMIT_FILESIZE:
	logg("CLAM_LIMIT_FILESIZE: set to %u\n", value);
	limit = CL_ENGINE_MAX_FILESIZE;
	break;
	case CLAM_LIMIT_SCANSIZE:
	logg("CLAM_LIMIT_SCANSIZE: set to %u\n", value);
	limit = CL_ENGINE_MAX_SCANSIZE;
	break;
	case CLAM_LIMIT_RECURSION:
	logg("CLAM_LIMIT_RECURSION: set to %u\n", value);
	limit = CL_ENGINE_MAX_RECURSION;
	break;
	default:
	unlock_engine();
	FAIL(CL_EARG, "Unsupported limit type: %d", option);
	}
	err = cl_engine_set_num(engine, limit, (long long)value);
	unlock_engine();
	if(err)
	FAIL(err, "Failed to set engine value: %s", cl_strerror(err));
	WIN();
}


static wchar_t *uncpathw(const wchar_t *path) {
	DWORD len = 0;
	unsigned int pathlen;
	wchar_t *stripme, *strip_from, *dest = malloc((PATH_MAX + 1) * sizeof(wchar_t));

	if(!dest)
	return NULL;

	pathlen = wcslen(path);
	if(wcsncmp(path, L"\\\\", 2)) {
	/* NOT already UNC */
	memcpy(dest, L"\\\\?\\", 8);
	if(pathlen < 2 || path[1] != L':' || *path < L'A' || *path > L'z' || (*path > L'Z' && *path < L'a')) {
		/* Relative path */
		len = GetCurrentDirectoryW(PATH_MAX - 5, &dest[4]);
		if(!len || len > PATH_MAX - 5) {
		free(dest);
		return NULL;
		}
		if(*path == L'\\')
		len = 6; /* Current drive root */
		else {
		len += 4; /* A 'really' relative path */
		dest[len] = L'\\';
		len++;
		}
	} else {
		/* C:\ and friends */
		len = 4;
	}
	} else {
	/* UNC already */
	len = 0;
	}

	if(pathlen >= PATH_MAX - len) {
	free(dest);
		return NULL;
	}
	wcscpy(&dest[len], path);
	len = wcslen(dest);
	strip_from = &dest[3];
	/* append a backslash to naked drives and get rid of . and .. */
	if(!wcsncmp(dest, L"\\\\?\\", 4) && (dest[5] == L':') && ((dest[4] >= L'A' && dest[4] <= L'Z') || (dest[4] >= L'a' && dest[4] <= L'z'))) {
	if(len == 6) {
		dest[6] = L'\\';
		dest[7] = L'\0';
	}
	strip_from = &dest[6];
	}
	while((stripme = wcsstr(strip_from, L"\\."))) {
	wchar_t *copy_from, *copy_to;
	if(!stripme[2] || stripme[2] == L'\\') {
		copy_from = &stripme[2];
		copy_to = stripme;
	} else if (stripme[2] == L'.' && (!stripme[3] || stripme[3] == L'\\')) {
		*stripme = L'\0';
		copy_from = &stripme[3];
		copy_to = wcsrchr(strip_from, L'\\');
		if(!copy_to)
		copy_to = stripme;
	} else {
		strip_from = &stripme[1];
		continue;
	}
	while(1) {
		*copy_to = *copy_from;
		if(!*copy_from) break;
		copy_to++;
		copy_from++;
	}
	}

	/* strip double slashes */
	if((stripme = wcsstr(&dest[4], L"\\\\"))) {
	strip_from = stripme;
	while(1) {
		wchar_t c = *strip_from;
		strip_from++;
		if(c == L'\\' && *strip_from == L'\\')
		continue;
		*stripme = c;
		stripme++;
		if(!c)
		break;
	}
	}
	if(wcslen(dest) == 6 && !wcsncmp(dest, L"\\\\?\\", 4) && (dest[5] == L':') && ((dest[4] >= L'A' && dest[4] <= L'Z') || (dest[4] >= L'a' && dest[4] <= L'z'))) {
	dest[6] = L'\\';
	dest[7] = L'\0';
	}
	return dest;
}


int CLAMAPI Scan_ScanObject(CClamAVScanner *pScanner, const wchar_t *pObjectPath, int *pScanStatus, PCLAM_SCAN_INFO_LIST *pInfoList) {
	HANDLE fhdl;
	int res;
	instance *inst = (instance *)pScanner;

	logg("*in Scan_ScanObject(pScanner = %p, pObjectPath = %S)\n", pScanner, pObjectPath);
	if((fhdl = CreateFileW(pObjectPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, NULL)) == INVALID_HANDLE_VALUE) {
	wchar_t *uncfname = uncpathw(pObjectPath);
	if(!uncfname)
		FAIL(CL_EMEM, "uncpathw() failed");
	fhdl = CreateFileW(uncfname, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, NULL);
	logg("*Scan_ScanObject translating '%S' to '%S'\n", pObjectPath, uncfname);
	free(uncfname);
	if(fhdl == INVALID_HANDLE_VALUE)
		FAIL(CL_EOPEN, "open() failed");
	}
	logg("*Scan_ScanObject (instance %p) invoking Scan_ScanObjectByHandle for handle %p (%S)\n", pScanner, fhdl, pObjectPath);
	res = Scan_ScanObjectByHandle(pScanner, fhdl, pScanStatus, pInfoList);
	logg("*Scan_ScanObject (instance %p) invoking Scan_ScanObjectByHandle returned %d\n", pScanner, res);
	CloseHandle(fhdl);
	return res;
}

struct scan_ctx {
	int entryfd;
	instance *inst;
	DWORD cb_times;
	DWORD copy_times;
};

int CLAMAPI Scan_ScanObjectByHandle(CClamAVScanner *pScanner, HANDLE object, int *pScanStatus, PCLAM_SCAN_INFO_LIST *pInfoList) {
	instance *inst;
	HANDLE duphdl, self;
	char *virname = NULL;
	int fd, res;
	unsigned int i;
	struct scan_ctx sctx;
	DWORD perf;

	logg("*in Scan_ScanObjectByHandle(pScanner = %p, HANDLE = %p, pScanStatus = %p, pInfoList = %p)\n", pScanner, object, pScanStatus, pInfoList);

	if(!pScanner)
	FAIL(CL_ENULLARG, "NULL pScanner");
	if(!pScanStatus)
	FAIL(CL_ENULLARG, "NULL pScanStatus on instance %p", pScanner);

	self = GetCurrentProcess();
	if(!DuplicateHandle(self, object, self, &duphdl, GENERIC_READ, FALSE, 0))
	FAIL(CL_EDUP, "Duplicate handle failed for instance %p", pScanner);

	if((fd = _open_osfhandle((intptr_t)duphdl, _O_RDONLY)) == -1) {
	CloseHandle(duphdl);
	FAIL(CL_EOPEN, "Open handle failed for instance %p", pScanner);
	}

	if(lock_instances()) {
	close(fd);
	FAIL(CL_ELOCK, "failed to lock instances for instance %p", pScanner);
	}
	inst = (instance *)pScanner;
	for(i=0; i<ninsts_total; i++) {
	if(instances[i].inst == inst)
		break;
	}
	if(i == ninsts_total) {
	unlock_instances();
	close(fd);
	FAIL(CL_EARG, "invalid instance %p", inst);
	}
	instances[i].refcnt++;
	ResetEvent(reload_event);
	unlock_instances();

	sctx.entryfd = fd;
	sctx.inst = inst;
	sctx.cb_times = 0;
	sctx.copy_times = 0;
	logg("*Scan_ScanObjectByHandle (instance %p) invoking cl_scandesc with clamav context %p\n", inst, &sctx);
	perf = GetTickCount();
	res = cl_scandesc_callback(fd, &virname, NULL, engine, inst->scanopts, &sctx);

	if(!inst->filetype) do {
	CLAM_SCAN_INFO si;
	CLAM_ACTION act;
	DWORD cbperf;
	wchar_t wvirname[MAX_VIRNAME_LEN] = L"Clam.";
	LONG lo = 0, hi = 0, hi2 = 0;

	si.cbSize = sizeof(si);
	si.flags = 0;
	si.scanPhase = SCAN_PHASE_FINAL;
	si.errorCode = CLAMAPI_SUCCESS;
	if(res == CL_VIRUS) {
		if(MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, virname, -1, &wvirname[5], MAX_VIRNAME_LEN - 5))
		si.pThreatName = wvirname;
		else
		si.pThreatName = L"Clam.UNOFFICIAL";
	} else
		si.pThreatName = NULL;
	logg("*in final_cb with clamav context %p, instance %p, fd %d, result %d, virusname %S)\n", &sctx, inst, fd, res, si.pThreatName);
	si.pThreatType = threat_type(virname);
	si.object = INVALID_HANDLE_VALUE;
	si.objectId = INVALID_HANDLE_VALUE;
	si.pInnerObjectPath = NULL;
	lo = SetFilePointer(duphdl, 0, &hi, FILE_CURRENT);
	SetFilePointer(duphdl, 0, &hi2, FILE_BEGIN);
	logg("*final_cb (clamav context %p, instance %p) invoking callback %p with context %p\n", &sctx, inst, inst->scancb, inst->scancb_ctx);
	cbperf = GetTickCount();
	inst->scancb(&si, &act, inst->scancb_ctx);
	cbperf = GetTickCount() - cbperf;
	sctx.cb_times += cbperf;
	logg("*final_cb (clamav context %p, instance %p) callback completed with %u (result ignored) in %u ms\n", &sctx, inst, act, cbperf);
	SetFilePointer(duphdl, lo, &hi, FILE_BEGIN);
	} while(0);

	perf = GetTickCount() - perf;
	close(fd);
	logg("*Scan_ScanObjectByHandle (instance %p): cl_scandesc returned %d in %u ms (%d ms own, %d ms copy)\n", inst, res, perf, perf - sctx.cb_times - sctx.copy_times, sctx.copy_times);

	if(lock_instances())
	FAIL(CL_ELOCK, "failed to lock instances for instance %p", pScanner);
	instances[i].refcnt--;
	if(!instances[i].refcnt)
	SetEvent(reload_event);
	unlock_instances();

	if(res == CL_VIRUS) {
	logg("Scan_ScanObjectByHandle (instance %p): file is INFECTED with %s\n", inst, virname);
	if(pInfoList) {
		CLAM_SCAN_INFO_LIST *infolist = (CLAM_SCAN_INFO_LIST *)calloc(1, sizeof(CLAM_SCAN_INFO_LIST) + sizeof(CLAM_SCAN_INFO) + MAX_VIRNAME_LEN * 2);
		PCLAM_SCAN_INFO scaninfo;
		wchar_t *wvirname;
		if(!infolist)
		FAIL(CL_EMEM, "ScanByHandle (instance %p): OOM while allocating result list", inst);
		scaninfo = (PCLAM_SCAN_INFO)(infolist + 1);
		infolist->cbCount = 1;
		scaninfo->cbSize = sizeof(*scaninfo);
		scaninfo->scanPhase = SCAN_PHASE_FINAL;
		scaninfo->errorCode = CLAMAPI_SUCCESS;
		scaninfo->pThreatType = threat_type(virname);
		scaninfo->object = INVALID_HANDLE_VALUE;
		scaninfo->objectId = INVALID_HANDLE_VALUE;
		wvirname = (wchar_t *)(scaninfo + 1);
		scaninfo->pThreatName = wvirname;
		memcpy(wvirname, L"Clam.", 10);
		if(!MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, virname, -1, &wvirname[5], MAX_VIRNAME_LEN-5))
		scaninfo->pThreatName = L"Clam.UNOFFICIAL";
		*pInfoList = infolist;
		logg("*Scan_ScanObjectByHandle (instance %p): created result list %p\n", inst, infolist);
	}
	*pScanStatus = CLAM_INFECTED;
	} else if(res == CL_CLEAN) {
	logg("*Scan_ScanObjectByHandle (instance %p): file is CLEAN\n", inst);
		if(pInfoList) *pInfoList = NULL;
	*pScanStatus = CLAM_CLEAN;
	} else {
	FAIL(res, "Scan failed for instance %p: %s", inst, cl_strerror(res));
	}
	WIN();
}

int CLAMAPI Scan_GetFileType(HANDLE hFile, _int64 *filetype) {
	instance *inst;
	int status, ret = Scan_CreateInstance((CClamAVScanner **)&inst);
	logg("*in Scan_GetFileType(HANDLE = %x, filetype = %p)\n", hFile, filetype);
	if(ret != CLAMAPI_SUCCESS) {
	FAIL(ret, "Failed to create instance, error %d", ret);
	}
	inst->filetype = filetype;
	ret = Scan_ScanObjectByHandle((CClamAVScanner *)inst, hFile, &status, NULL);
	logg("Scan_GetFileType: Scan_ScanObjectByHandle returned %d, type %016llx%016llx\n", ret, filetype[1], filetype[0]);
	Scan_DestroyInstance((CClamAVScanner *)inst);
	return ret;
}

int CLAMAPI Scan_DeleteScanInfo(CClamAVScanner *pScanner, PCLAM_SCAN_INFO_LIST pInfoList) {
	logg("*in Scan_DeleteScanInfo(pScanner = %p, pInfoList = %p)\n", pScanner, pInfoList);
	if(!pScanner)
	FAIL(CL_ENULLARG, "NULL pScanner");
	if(!pInfoList)
	FAIL(CL_ENULLARG, "NULL pInfoList");
	/* FIXME checking this is pointelss as the infolist is independent from pscanner */
 //   if(lock_instances())
	//FAIL(CL_EMEM, "failed to lock instances");

 //   inst = (instance *)pScanner;
 //   if(!is_instance(inst)) {
	//unlock_instances();
	//FAIL(CL_EARG, "invalid instance");
 //   }
 //   unlock_instances();

	free(pInfoList);
	WIN();
}

CLAMAPI BOOL Scan_FlushCache()
{
	cli_cache_destroy(engine);
	if (cli_cache_init(engine)) 
		return FALSE;
	return TRUE;
}

static void ftype_bits(const char *type, _int64 *filetype) {
	int i;
	if(!strncmp(type, "CL_TYPE_", 8)) {
	for(i=1; types[i]; i++) {
		if(!strcmp(&type[8], types[i]))
		break;
	}
	if(!types[i]) i = -1;
	} else
	i = -1;
	if(i<0) {
	filetype[0] = 0;
	filetype[1] = 0;
	} else if(i<64) {
	filetype[0] = 1LL << i;
	filetype[1] = 0;
	} else {
	filetype[0] = 0;
	filetype[1] = 1LL << (i-64);
	}
	logg("*ftype_bits setting type to %016llx%016llx\n", filetype[1], filetype[0]);
}

cl_error_t filetype_cb(int fd, const char *type, void *context) {
	struct scan_ctx *sctx = (struct scan_ctx *)context;
	if(sctx && sctx->inst && sctx->inst->filetype) {
	ftype_bits(type, sctx->inst->filetype);
		logg("*in filetype_cb with clamav context %p, instance %p, fd %d, type %s, typenum %016llx%016llx)\n", context, sctx->inst, fd, type, sctx->inst->filetype[1], sctx->inst->filetype[0]);
	return CL_BREAK;
	}
	return CL_CLEAN;
}

cl_error_t prescan_cb(int fd, const char *type, void *context) {
	struct scan_ctx *sctx = (struct scan_ctx *)context;
	char tmpf[4096];
	instance *inst;
	CLAM_SCAN_INFO si;
	CLAM_ACTION act;
	HANDLE fdhdl;
	DWORD perf, perf2 = 0;

	if(!context) {
	logg("!prescan_cb called with NULL clamav context\n");
	return CL_CLEAN;
	}
	inst = sctx->inst;
	if(inst && inst->filetype)
	return CL_CLEAN; /* Just in case, this shouldn't happen */

	logg("*in prescan_cb with clamav context %p, instance %p, fd %d, type %s)\n", context, inst, fd, type);
	ftype_bits(type, si.filetype);
	si.cbSize = sizeof(si);
	si.flags = 0;
	si.scanPhase = (fd == sctx->entryfd) ? SCAN_PHASE_INITIAL : SCAN_PHASE_PRESCAN;
	si.errorCode = CLAMAPI_SUCCESS;
	si.pThreatType = NULL;
	si.pThreatName = NULL;
	si.pInnerObjectPath = NULL;

	if(si.scanPhase == SCAN_PHASE_PRESCAN) {
	/* Windows requires lseek fd to be valid */
	if (fd < 0) {
		logg("!prescan_cb: failed to create tempfile - invalid fd %d\n", fd);
		logg("!prescan_cb: engine option (force-to-disk) is likely not set\n");
		si.object = INVALID_HANDLE_VALUE;
		si.objectId = INVALID_HANDLE_VALUE;
	} else {
		long fpos;
		int rsz;
		perf2 = GetTickCount();
		while(1) {
		static int tmpn;
		snprintf(tmpf, sizeof(tmpf), "%s\\%08x.tmp", tmpdir, ++tmpn);
		tmpf[sizeof(tmpf)-1] = '\0';
		fdhdl = CreateFile(tmpf, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL);
		if(fdhdl != INVALID_HANDLE_VALUE) {
			logg("*prescan_cb: dumping content to tempfile %s (handle %p)\n", tmpf, fdhdl);
			break;
		}
		if((perf = GetLastError()) != ERROR_FILE_EXISTS) {
			logg("!prescan_cb: failed to create tempfile %s - error %u\n", tmpf, perf);
			return CL_CLEAN;
		}
		}

		fpos = lseek(fd, 0, SEEK_CUR);
		lseek(fd, 0, SEEK_SET);
		while((rsz = read(fd, tmpf, sizeof(tmpf))) > 0) {
		int wsz = 0;
		while(wsz != rsz) {
			DWORD rwsz;
			if(!WriteFile(fdhdl, &tmpf[wsz], rsz - wsz, &rwsz, NULL)) {
			logg("!prescan_cb: failed to write to tempfile %s - error %u\n", tmpf, GetLastError());
			lseek(fd, fpos, SEEK_SET);
			CloseHandle(fdhdl);
			return CL_CLEAN;
			}
			wsz += rwsz;
		}
		}
		if(rsz) {
		logg("!prescan_cb: failed to read from clamav tempfile - errno = %d\n", errno);
		lseek(fd, fpos, SEEK_SET);
		CloseHandle(fdhdl);
		return CL_CLEAN;
		}
		lseek(fd, fpos, SEEK_SET);
		SetFilePointer(fdhdl, 0, NULL, FILE_BEGIN);
		si.object = fdhdl;
		si.objectId = (HANDLE)_get_osfhandle(fd);
		perf2 = GetTickCount() - perf2;
		sctx->copy_times += perf2;
	}
	} else { /* SCAN_PHASE_INITIAL */
	si.object = INVALID_HANDLE_VALUE;
	si.objectId = INVALID_HANDLE_VALUE;
	}
	logg("*prescan_cb (clamav context %p, instance %p) invoking callback %p with context %p\n", context, inst, inst->scancb, inst->scancb_ctx);
	perf = GetTickCount();
	inst->scancb(&si, &act, inst->scancb_ctx);
	perf = GetTickCount() - perf;
	sctx->cb_times += perf;
	logg("*prescan_cb (clamav context %p, instance %p) callback completed with %u in %u + %u ms\n", context, inst, act, perf, perf2);
	switch(act) {
	case CLAM_ACTION_SKIP:
		logg("*prescan_cb (clamav context %p, instance %p) cb result: SKIP\n", context, inst);
		return CL_BREAK;
	case CLAM_ACTION_ABORT:
		logg("*prescan_cb (clamav context %p, instance %p) cb result: ABORT\n", context, inst);
		return CL_VIRUS;
	case CLAM_ACTION_CONTINUE:
		logg("*prescan_cb (clamav context %p, instance %p) cb result: CONTINUE\n", context, inst);
		return CL_CLEAN;
	default:
		logg("^prescan_cb (clamav context %p, instance %p) cb result: INVALID result %d, assuming continue\n", context, inst, act);
		return CL_CLEAN;
	}
}

cl_error_t postscan_cb(int fd, int result, const char *virname, void *context) {
	struct scan_ctx *sctx = (struct scan_ctx *)context;
	instance *inst;
	CLAM_SCAN_INFO si;
	CLAM_ACTION act;
	DWORD perf;
	wchar_t wvirname[MAX_VIRNAME_LEN] = L"Clam.";

	if(!context) {
	logg("!postscan_cb called with NULL clamav context\n");
	return CL_CLEAN;
	}
	inst = sctx->inst;
	if(inst && inst->filetype)
	return CL_CLEAN; /* No callback, we are just filetyping */

	if(fd == sctx->entryfd)
	return CL_CLEAN; /* Moved to after cl_scandesc returns due to heuristic results not being yet set in magicscan */

	si.cbSize = sizeof(si);
	si.flags = 0;
	si.scanPhase = SCAN_PHASE_POSTSCAN;
	si.errorCode = CLAMAPI_SUCCESS;
	if(result == CL_VIRUS) {
	if(MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, virname, -1, &wvirname[5], MAX_VIRNAME_LEN-5))
		si.pThreatName = wvirname;
	else
		si.pThreatName = L"Clam.UNOFFICIAL";
	} else
		si.pThreatName = NULL;
	logg("*in postscan_cb with clamav context %p, instance %p, fd %d, result %d, virusname %S)\n", context, inst, fd, result, si.pThreatName);
	si.pThreatType = threat_type(virname);
	if (fd < 0) {
	logg("!postscan_cb: invalid handle value - invalid fd %d\n", fd);
	logg("!postscan_cb: engine option (force-to-disk) is likely not set\n");
	si.objectId = INVALID_HANDLE_VALUE;
	} else
	si.objectId = (HANDLE)_get_osfhandle(fd);
	si.object = INVALID_HANDLE_VALUE;
	si.pInnerObjectPath = NULL;
	logg("*postscan_cb (clamav context %p, instance %p) invoking callback %p with context %p\n", context, inst, inst->scancb, inst->scancb_ctx);
	perf = GetTickCount();
	inst->scancb(&si, &act, inst->scancb_ctx);
	perf = GetTickCount() - perf;
	sctx->cb_times += perf;
	logg("*postscan_cb (clamav context %p, instance %p) callback completed with %u in %u ms\n", context, inst, act, perf);
	switch(act) {
	case CLAM_ACTION_SKIP:
		logg("*postscan_cb (clamav context %p, instance %p) cb result: SKIP\n", context, inst);
		return CL_BREAK;
	case CLAM_ACTION_ABORT:
		logg("*postscan_cb (clamav context %p, instance %p) cb result: ABORT\n", context, inst);
		return CL_VIRUS;
	case CLAM_ACTION_CONTINUE:
		logg("*postscan_cb (clamav context %p, instance %p) cb result: CONTINUE\n", context, inst);
		return CL_CLEAN;
	default:
		logg("^postscan_cb (clamav context %p, instance %p) cb result: INVALID result %d, assuming continue\n", context, inst, act);
		return CL_CLEAN;
	}
}

CLAMAPI void Scan_ReloadDatabase(BOOL bLoadMinDefs) {
	if(InterlockedIncrement(&reload_waiters)==1) {
	int reload_ok = 0;
	logg("*Scan_ReloadDatabase: Database reload requested received, waiting for idle state\n");
	while(1) {
		unsigned int i;
		struct cl_settings *settings;

		if(WaitForSingleObject(reload_event, INFINITE) == WAIT_FAILED) {
		logg("!Scan_ReloadDatabase: failed to wait on reload event\n");
		continue;
		}
		logg("*Scan_ReloadDatabase: Now idle, acquiring engine lock\n");
		if(lock_engine()) {
		logg("!Scan_ReloadDatabase: failed to lock engine\n");
		break;
		}
		if(!engine) {
		logg("!Scan_ReloadDatabase: engine is NULL\n");
		unlock_engine();
		break;
		}
		logg("*Scan_ReloadDatabase: Engine locked, acquiring instance lock\n");
		if(lock_instances()) {
		logg("!Scan_ReloadDatabase: failed to lock instances\n");
		unlock_engine();
		break;
		}
			for(i=0; i<ninsts_total; i++) {
		if(instances[i].inst && instances[i].refcnt)
			break;
		}
		if(i!=ninsts_total) {
		logg("Scan_ScanObjectByHandle: some instances are still in use\n");
		ResetEvent(reload_event);
		unlock_instances();
		unlock_engine();
		continue;
		}
		settings = cl_engine_settings_copy(engine);
		if (!settings) {
		logg("!Scan_ReloadDatabase: Not enough memory for engine settings\n");
		unlock_instances();
		unlock_engine();
		break;
		}

		logg("Scan_ReloadDatabase: Destroying old engine\n");
		cl_engine_free(engine);
		minimal_definitions = bLoadMinDefs;
		logg("Scan_ReloadDatabase: Loading new engine\n");

		// NEW STUFF //
		if(!(engine = cl_engine_new())) {
		logg("!Scan_ReloadDatabase: Not enough memory for a new engine\n");
		unlock_instances();
		unlock_engine();
		break;
		}
		cl_engine_settings_apply(engine, settings);
		cl_engine_settings_free(settings);

		load_db(); /* FIXME: FIAL? */
		unlock_instances();
		unlock_engine();
		reload_ok = 1;
		break;
	}
	if(reload_ok)
		logg("Scan_ReloadDatabase: Database successfully reloaded\n");
	else
		logg("!Scan_ReloadDatabase: Database reload failed\n");
	} else
	logg("*Database reload requested received while reload is pending\n");
	InterlockedDecrement(&reload_waiters);
}

void msg_callback(enum cl_msg severity, const char *fullmsg, const char *msg, void *ctx)
{
	struct scan_ctx *sctx = (struct scan_ctx*)ctx;
	const void *instance = sctx ? sctx->inst : NULL;
	int fd = sctx ? sctx->entryfd : -1;
	char sv;
	switch (severity) {
	case CL_MSG_ERROR:
		sv = '!';
		break;
	case CL_MSG_WARN:
		sv = '^';
		break;
	default:
		sv = '*';
		break;
	}

	logg("%c[LibClamAV] (instance %p, clamav context %p, fd %d): %s",
	 sv, instance, sctx, fd, msg);
}