/* Distributed Checksum Clearinghouse
 *
 * routines to make WIN32 look sort of reasonable
 *
 * Copyright (c) 2005 by Rhyolite Software
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE
 * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 *
 * Rhyolite Software DCC 1.2.74-1.11 $Revision$
 */


#include "dcc_defs.h"

#ifdef DCC_WIN32

#ifdef DCC_WIN32
static DCC_PATH path_tmp;
const char *_PATH_TMP = path_tmp;
#endif

static u_char is_nt;


void
win32_init(void)
{
	OSVERSIONINFO ver;
	WSADATA WSAData;

	ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	GetVersionEx(&ver);
	is_nt = (ver.dwPlatformId == VER_PLATFORM_WIN32_NT);

	if (WSAStartup(MAKEWORD(2, 0), &WSAData))
		dcc_logbad(EX_SOFTWARE, "WSAStartup(): %s", ERROR_STR());

	atexit((void(*)(void))WSACleanup);

	GetTempPath(sizeof(path_tmp), path_tmp);

#ifdef __BORLANDC__
	_fmode = O_BINARY;
#endif
}



int
gettimeofday(struct timeval *tv, struct timezone *tzp)
{
	static SYSTEMTIME epoch_st = {1970, 1, 0, 1, 0, 0, 0, 0};
	static LONGLONG epoch;		/* the first second of the UNIX epoch */
	LONGLONG now;

	if (epoch == 0)
		SystemTimeToFileTime(&epoch_st, (FILETIME *)&epoch);
	GetSystemTimeAsFileTime((FILETIME *)&now);
	now -= epoch;
	now /= 10;
	tv->tv_sec = now/(1000*1000);
	tv->tv_usec = now%(1000*1000);
	return 0;
}



void
win32_unmap(HANDLE *hp, void *p, const char *nm)
{
	if (!UnmapViewOfFile(p))
		dcc_error_msg("UnmapViewOfFile(%s): %s", nm, ERROR_STR());
	if (!CloseHandle(*hp))
		dcc_error_msg("CloseHandle(%s): %s", nm, ERROR_STR());
	*hp = INVALID_HANDLE_VALUE;
}



void *
win32_map(DCC_EMSG emsg,
	  HANDLE *map_handle,		/* put handle for the map here */
	  const char *nm,		/* for this resolved path name */
	  int fd,			/* with this C style file descriptor */
	  int size)			/* with this size (to extend file) */
{
	static char junk;		/* foil optimizer */
	DCC_PATH map_nm;
	HANDLE h;
	void *p;
	int i;

	/* make a name for them mapping
	 * use DCC_NM2PATH() in caller */
	STRLIMCPY(map_nm, sizeof(DCC_PATH), nm);
	for (i = 0; i < sizeof(DCC_PATH) && map_nm[i] != '\0'; ++i) {
		if (map_nm[i] == '/' || map_nm[i] == '\\')
			map_nm[i] = '-';
	}

	h = CreateFileMapping((HANDLE)_get_osfhandle(fd),
			      0, PAGE_READWRITE, 0, size, map_nm);
	if (!h) {
		dcc_pemsg(EX_IOERR, emsg, "CreateFileMapping(%s): %s",
			  nm, ERROR_STR());
		*map_handle = INVALID_HANDLE_VALUE;
		return 0;
	}
	p = MapViewOfFile(h, FILE_MAP_ALL_ACCESS, 0,0, size);
	if (!p) {
		dcc_pemsg(EX_IOERR, emsg, "MapViewOfFile(%s): %s",
			  nm, ERROR_STR());
		CloseHandle(h);
		*map_handle = INVALID_HANDLE_VALUE;
		return 0;
	}

	/* If you immediately lock the file, the mapping is garbage
	 * on Win98.  It seems to help to poke at the mapping.
	 * A working theory is that a page fault on a locked file fails */
	for (i = 0; i < size; i += 512)
		junk += ((char *)p)[i];

	*map_handle = h;
	return p;
}



u_char
win32_lock(HANDLE h, DWORD flags)
{
	OVERLAPPED olap;
	int e;

	if (is_nt) {
		memset(&olap, 0, sizeof(olap));
		return LockFileEx(h, flags, 0, 1,0, &olap);
	}

	/* this is ugly, but so is Win95 */
	for (;;) {
		if (LockFile(h, 0,0, 1,0))
			return 1;
		e = GetLastError();
		if (e != ERROR_LOCKED
		    && e != ERROR_LOCK_VIOLATION
		    && e != ERROR_SHARING_VIOLATION)
			return 0;
		Sleep(100);
	}
}



u_char
win32_unlock(HANDLE h)
{
	return UnlockFile(h, 0,0, 1,0);
}



/* not all WIN32 systems have snprintf */
int
dcc_vsnprintf(char *buf, int buf_len, const char *pat, va_list args)
{
#	define PATBUF_LEN 512
	char c, patbuf[PATBUF_LEN+5+1], msgbuf[PATBUF_LEN*2];
	DWORD lasterror;
	int patlen, patnum, i;

	/* translate from printf to FormatMessage pattern */
	patlen = 0;
	patnum = 0;
	while (patnum < 20 && patlen < PATBUF_LEN && (c = *pat++) != '\0') {
		patbuf[patlen++] = c;
		if (c != '%')
			continue;

		if (*pat == '%') {
			patbuf[patlen++] = '%';
			++pat;
			continue;
		}

		if (++patnum >= 10)
			patbuf[patlen++] = '1';
		patbuf[patlen++] = '0' + (patnum % 10);
		patbuf[patlen++] = '!';
		while (((c = *pat++) == '.' || c == '#' || c == '*'
			|| c == '+' || c == '-' || c == 'l'
			|| (c >= '0' && c <= '9'))
		       && patlen < PATBUF_LEN) {
			patbuf[patlen++] = c;
		}
		patbuf[patlen++] = c;
		patbuf[patlen++] = '!';
	}
	patbuf[patlen] = '\0';

	/* FormatMessage() fails completely if the buffer is too short
	 * so waste time with a temporary buffer. */
	lasterror = GetLastError();
	i = FormatMessage(FORMAT_MESSAGE_FROM_STRING, patbuf,
			  0, 0, msgbuf, sizeof(msgbuf), &args);
	if (!i) {
		sprintf(msgbuf, "FormatMessage() failed: %d", GetLastError());
		i = strlen(msgbuf);
	}
	STRLIMCPY(buf, buf_len, msgbuf);
	SetLastError(lasterror);
	return i;
}



int
dcc_snprintf(char *buf, int buf_len, const char *pat, ...)
{
	int i;
	va_list args;

	va_start(args, pat);
	i = dcc_vsnprintf(buf, buf_len, pat, args);
	va_end(args);
	return i;
}





/* in NT, this should probably have something to do with the event log */

char syslog_prefix[64];

#pragma argsused
void
openlog(const char *ident, int logopt, int facility)
{
	BUFCPY(syslog_prefix, ident);
}



#pragma argsused
void
vsyslog(int priority, const char *msg, va_list args)
{
	struct tm tm;
	char *bp, buf[sizeof(syslog_prefix)+256];

	if (dcc_no_syslog)
		return;

	strcpy(buf, syslog_prefix);
	bp = buf+strlen(buf);
	dcc_localtime(time(0), &tm);
	bp += strftime(bp, &buf[sizeof(buf)-3]-bp, " %D %H:%M:%S ", &tm);
	if (bp >= &buf[sizeof(buf)-3])
		bp = &buf[sizeof(buf)-3];

	bp += vsnprintf(bp, &buf[sizeof(buf)-3]-bp, msg, args);
	if (bp >= &buf[sizeof(buf)-3])
		bp = &buf[sizeof(buf)-3];
	strcpy(bp, "\r\n");

	puts(buf);
}



void
syslog(int priority, const char *msg, ...)
{
	va_list args;

	va_start(args, msg);
	vsyslog(priority, msg, args);
	va_end(args);
}



/* Strip any CR or LF and convert the strange, non-ASCII
 *	garbage from Microsoft messages
 * Trim the trailing blanks and '.' from Borland messages */
static void
ws_strip(const char *begin, char *tgt, char *end)
{
	const char *src;
	char c;

	src = begin;
	do {
		if (tgt >= end) {
			*tgt++ = '\0';
			break;
		}
		c = *src++;
		if (c == '\r')		/* skip carriage return */
			continue;
		if (c == '\n')
			c = ' ';
		if ((c < ' ' && c != '\0') || c > 0x7e)
			c = '?';
		*tgt++ = c;
	} while (c != '\0');

	/* trim trailing whitespace */
	--tgt;
	while (--tgt >= begin
	       && ((c = *tgt) == ' ' || c == '\t' || c == '.')) {
		*tgt = '\0';
	}
}



/* complete the lame strerror() from Borland for WIN95 */
const char *
ws_strerror(int eno)
{
	static struct {
	    char    s[150];
	} ebufs[8];
	static int ebuf_num;
	int bn;
	const char *src;
	char *begin;

	/* Borland fopen() and who knows what else does not set
	 * the WIN32 GetLastError() value */
#ifdef __BORLANDC__
	/* sometimes the Borland wrapper for errno works better */
	if (eno == 0)
		eno = *__errno();
#endif
	if (eno == 0)
		return "unknown error";

	/* Use an array of static buffers to kludge around problems with
	 * threads */
	bn = ebuf_num;
	ebuf_num = (bn+1) % DIM(ebufs);
	begin = ebufs[bn].s;

	/* Use the Microsoft message if it is not silly. */
	if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
			  0, eno, 0, begin, sizeof(ebufs[bn].s), 0)) {

		/* strip any CR or LF and convert the strange, non-ASCII
		 * garbage from Microsoft messages */
		ws_strip(begin, begin, begin+sizeof(ebufs[bn].s)-1);
		return begin;
	}

	/* If Microsoft fails, try the Borland messages,
	 * and use anything other than "unknown error" */
	src = strerror(eno);
	if (strcmp(src, "Unknown error\n")) {
		ws_strip(src, begin, begin+sizeof(ebufs[bn].s)-1);
		return begin;
	}

	/* MicroSoft has only some of the BSD standard error messages */
	switch (eno) {
	case WSAEACCES:		return "SO_BROADCAST not enabled";
	case WSAEADDRINUSE:	return "address already in use";
	case WSAEADDRNOTAVAIL:	return "address not available";
	case WSAEAFNOSUPPORT:	return "Address family not supported";
	case WSAEALREADY:	return "nonblocking connect in progress";
	case WSAEBADF:		return "Bad file descriptor";
	case WSAECONNABORTED:	return "Software caused connection abort";
	case WSAECONNREFUSED:	return "Connection refused";
	case WSAECONNRESET:	return "Connection reset by peer";
	case WSAEDESTADDRREQ:	return "Destination address required";
	case WSAEDQUOT:		return "Disc quota exceeded";
	case WSAEFAULT:		return "WS bad address";
	case WSAEHOSTDOWN:	return "Host is down";
	case WSAEHOSTUNREACH:	return "No route to host";
	case WSAEINPROGRESS:	return "winsock 1.1 call in progress";
	case WSAEINTR:		return "cancelled by WSACancelBlockingCall";
	case WSAEINVAL:		return "WS invalid argument";
	case WSAEISCONN:	return "Socket is already connected";
	case WSAELOOP:		return "Too many levels of symbolic links";
	case WSAEMFILE:		return "Too many open files";
	case WSAEMSGSIZE:	return "Message too long";
	case WSAENAMETOOLONG:	return "File name too long";
	case WSAENETDOWN:	return "network is down";
	case WSAENETRESET:	return "Network dropped connection on reset";
	case WSAENETUNREACH:	return "network is unreachable";
	case WSAENOBUFS:	return "No buffer space available";
	case WSAENOPROTOOPT:	return "Protocol not available";
	case WSAENOTCONN:	return "Socket is not connected";
	case WSAENOTEMPTY:	return "Directory not empty";
	case WSAENOTSOCK:	return "socket operation on non-socket";
	case WSAEOPNOTSUPP:	return "Operation not supported";
	case WSAEPFNOSUPPORT:	return "Protocol family not supported";
	case WSAEPROCLIM:	return "Too many processes";
	case WSAEPROTONOSUPPORT:return "Protocol not supported";
	case WSAEPROTOTYPE:	return "Protocol wrong type for socket";
	case WSAEREMOTE:	return "Too many levels of remote in path";
	case WSAESHUTDOWN:	return "Can't send after socket shutdown";
	case WSAESOCKTNOSUPPORT:return "Socket type not supported";
	case WSAESTALE:		return "Stale NFS file handle";
	case WSAETIMEDOUT:	return "Operation timed out";
	case WSAETOOMANYREFS:	return "Too many references: can't splice";
	case WSAEUSERS:		return "Too many users";
	case WSAEWOULDBLOCK:	return "operation would block";
	case WSANOTINITIALISED:	return "WSAStartup not done";
	}

	/* fall back on Borland's "unkonwn error", but admit the error # */
	snprintf(begin, sizeof(ebufs[bn].s), "Unknown error %d", eno);
	return begin;
}
#endif /* !DCC_WIN32 */
