/**
 * =========================================================================
 * File        : wposix.cpp
 * Project     : 0 A.D.
 * Description : emulate a subset of POSIX on Win32.
 *
 * @author Jan.Wassenberg@stud.uni-karlsruhe.de
 * =========================================================================
 */

/*
 * Copyright (c) 2004-2005 Jan Wassenberg
 *
 * Redistribution and/or modification are also permitted under the
 * terms of the GNU General Public License as published by the
 * Free Software Foundation (version 2 or later, at your option).
 *
 * 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.
 */

#include "precompiled.h"

#include <stdio.h>
#include <stdlib.h>

#include "lib/lib.h"
#include "win_internal.h"
#include "lib/allocators.h"
#include "lib/path_util.h"
#include "lib/posix.h"


// cast intptr_t to HANDLE; centralized for easier changing, e.g. avoiding
// warnings. i = -1 converts to INVALID_HANDLE_VALUE (same value).
static HANDLE HANDLE_from_intptr(intptr_t i)
{
	return (HANDLE)((char*)0 + i);
}


//////////////////////////////////////////////////////////////////////////////
//
// file
//
//////////////////////////////////////////////////////////////////////////////


int open(const char* fn, int oflag, ...)
{
	const bool is_com_port = strncmp(fn, "/dev/tty", 8) == 0;
		// also used later, before aio_reopen

	// translate "/dev/tty%d" to "COM%d"
	if(is_com_port)
	{
		char port[] = "COM1";
		const char digit = fn[8]+1;
		// PCs only support COM1..COM4.
		if(!('1' <= digit && digit <= '4'))
			return -1;
		port[3] = digit;
		fn = port;
	}

	mode_t mode = 0;
	if(oflag & O_CREAT)
	{
		va_list args;
		va_start(args, oflag);
		mode = va_arg(args, mode_t);
		va_end(args);
	}

	WIN_SAVE_LAST_ERROR;	// CreateFile
	int fd = _open(fn, oflag, mode);
	WIN_RESTORE_LAST_ERROR;

	// cases when we don't want to open a second AIO-capable handle:
	// .. stdin/stdout/stderr
	if(fd <= 2)
		goto no_aio;
	// .. COM port - we don't currently need AIO access for those, and
	//    aio_reopen's CreateFile would fail with "access denied".
	if(is_com_port)
		goto no_aio;
	// .. caller is requesting we skip it (see file_open)
	if(oflag & O_NO_AIO_NP)
		goto no_aio;

	// none of the above apply; now re-open the file.
	// note: this is possible because _open defaults to DENY_NONE sharing.
	WARN_ERR(aio_reopen(fd, fn, oflag));

no_aio:

	// CRT doesn't like more than 255 files open.
	// warn now, so that we notice why so many are open.
#ifndef NDEBUG
	if(fd > 256)
		WARN_ERR(ERR_LIMIT);
#endif

	return fd;
}


int close(int fd)
{
	debug_assert(3 <= fd && fd < 256);

	// note: there's no good way to notify us that <fd> wasn't opened for
	// AIO, so we could skip aio_close. storing a bit in the fd is evil and
	// a fd -> info map is redundant (waio already has one).
	// therefore, we require aio_close to fail gracefully.
	WARN_ERR(aio_close(fd));

	return _close(fd);
}


// we don't want to #define read to _read, since that's a fairly common
// identifier. therefore, translate from MS CRT names via thunk functions.
// efficiency is less important, and the overhead could be optimized away.

int read(int fd, void* buf, size_t nbytes)
{
	return _read(fd, buf, nbytes);
}

int write(int fd, void* buf, size_t nbytes)
{
	return _write(fd, buf, nbytes);
}



int ioctl(int fd, int op, int* data)
{
	const HANDLE h = HANDLE_from_intptr(_get_osfhandle(fd));

	switch(op)
	{
	case TIOCMGET:
		/* TIOCM_* mapped directly to MS_*_ON */
		GetCommModemStatus(h, (DWORD*)data);
		break;

	case TIOCMBIS:
		/* only RTS supported */
		if(*data & TIOCM_RTS)
			EscapeCommFunction(h, SETRTS);
		else
			EscapeCommFunction(h, CLRRTS);
		break;

	case TIOCMIWAIT:
		static DWORD mask;
		DWORD new_mask = 0;
		if(*data & TIOCM_CD)
			new_mask |= EV_RLSD;
		if(*data & TIOCM_CTS)
			new_mask |= EV_CTS;
		if(new_mask != mask)
			SetCommMask(h, mask = new_mask);
		WaitCommEvent(h, &mask, 0);
		break;
	}

	return 0;
}




//
// determine file system type on the current drive -
// needed to work around incorrect FAT time translation.
//

static enum Filesystem
{
	FS_INVALID,	// detect_filesystem() not yet called
	FS_FAT,		// FAT12, FAT16, or FAT32
	FS_NTFS,	// (most common)
	FS_UNKNOWN	// newer FS we don't know about
}
filesystem;


// rationale: the previous method of checking every path was way too slow
// (taking ~800ms total during init). instead, we only determine the FS once.
// this is quite a bit easier than intercepting chdir() calls and/or
// caching FS type per drive letter, but not foolproof.
//
// if some data files are on a different volume that is set up as FAT,
// the workaround below won't be triggered (=> timestamps may be off by
// 1 hour when DST is in effect). oh well, that is not a supported.
//
// the common case (everything is on a single NTFS volume) is more important
// and must run without penalty.


// called from the first filetime_to_time_t() call, not win.cpp init;
// this means we can rely on the current directory having been set to
// the app's directory (and therefore its appendant volume - see above).
static void detect_filesystem()
{
	char root_path[MAX_PATH] = "c:\\";	// default in case GCD fails
	DWORD gcd_ret = GetCurrentDirectory(sizeof(root_path), root_path);
	debug_assert(gcd_ret != 0);
		// if this fails, no problem - we have the default from above.
	root_path[3] = '\0';	// cut off after "c:\"

	char fs_name[32] = {0};
	BOOL ret = GetVolumeInformation(root_path, 0,0,0,0,0, fs_name, sizeof(fs_name));
	fs_name[ARRAY_SIZE(fs_name)-1] = '\0';
	debug_assert(ret != 0);
		// if this fails, no problem - we really only care if fs is FAT,
		// and will assume that's not the case (since fs_name != "FAT").

	filesystem = FS_UNKNOWN;

	if(!strncmp(fs_name, "FAT", 3))	// e.g. FAT32
		filesystem = FS_FAT;
	else if(!strcmp(fs_name, "NTFS"))
		filesystem = FS_NTFS;
}


// from wtime
extern time_t time_t_from_local_filetime(FILETIME* ft);
extern time_t utc_filetime_to_time_t(FILETIME* ft);

// convert Windows FILETIME to POSIX time_t (seconds-since-1970 UTC);
// used by stat and readdir_stat_np for st_mtime.
//
// works around a documented Windows bug in converting FAT file times
// (correct results are desired since VFS mount logic considers
// files 'equal' if their mtime and size are the same).
static time_t filetime_to_time_t(FILETIME* ft)
{
	ONCE(detect_filesystem());

	// the FAT file system stores local file times, while
	// NTFS records UTC. Windows does convert automatically,
	// but uses the current DST settings. (boo!)
	// we go back to local time, and convert properly.
	if(filesystem == FS_FAT)
	{
		FILETIME local_ft;
		FileTimeToLocalFileTime(ft, &local_ft);
		return time_t_from_local_filetime(&local_ft);
	}

	return utc_filetime_to_time_t(ft);
}

/*
// currently only sets st_mode (file or dir) and st_size.
int stat(const char* fn, struct stat* s)
{
	memset(s, 0, sizeof(struct stat));

	WIN32_FILE_ATTRIBUTE_DATA fad;
	if(!GetFileAttributesEx(fn, GetFileExInfoStandard, &fad))
		return -1;

	s->st_mtime = filetime_to_time_t(fad.ftLastAccessTime)

	// dir
	if(fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		s->st_mode = S_IFDIR;
	else
	{
		s->st_mode = S_IFREG;
		s->st_size = (off_t)((((u64)fad.nFileSizeHigh) << 32) | fad.nFileSizeLow);
	}

	return 0;
}
*/


//////////////////////////////////////////////////////////////////////////////
//
// dir
//
//////////////////////////////////////////////////////////////////////////////

#undef getcwd

char* getcwd(char* buf, size_t buf_size)
{
	return _getcwd(buf, buf_size);
}

char* realpath(const char* fn, char* path)
{
	if(!GetFullPathName(fn, PATH_MAX, path, 0))
		return 0;
	return path;
}


int mkdir(const char* path, mode_t)
{
	return CreateDirectory(path, 0)? 0 : -1;
}


// opendir/readdir/closedir
//
// note: we avoid opening directories or returning entries that have
// hidden or system attributes set. this is to prevent returning something
// like "\System Volume Information", which raises an error upon opening.

// 0-initialized by wdir_alloc for safety; this is required for
// num_entries_scanned.
struct WDIR
{
	HANDLE hFind;

	// the dirent returned by readdir.
	// note: having only one global instance is not possible because
	// multiple independent opendir/readdir sequences must be supported.
	struct dirent ent;

	WIN32_FIND_DATA fd;

	// since opendir calls FindFirstFile, we need a means of telling the
	// first call to readdir that we already have a file.
	// that's the case iff this is == 0; we use a counter rather than a
	// flag because that allows keeping statistics.
	int num_entries_scanned;
};


// suballocator - satisfies most requests with a reusable static instance,
// thus speeding up allocation and avoiding heap fragmentation.
// thread-safe.

static WDIR global_wdir;
static uintptr_t global_wdir_is_in_use;

// zero-initializes the WDIR (code below relies on this)
static inline WDIR* wdir_alloc()
{
	return (WDIR*)single_calloc(&global_wdir, &global_wdir_is_in_use, sizeof(WDIR));
}

static inline void wdir_free(WDIR* d)
{
	single_free(&global_wdir, &global_wdir_is_in_use, d);
}


static const DWORD hs = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM;

// make sure path exists and is a normal (according to attributes) directory.
static bool is_normal_dir(const char* path)
{
	const DWORD fa = GetFileAttributes(path);
	// .. path not found
	if(fa == INVALID_FILE_ATTRIBUTES)
		return false;
	// .. not a directory
	if((fa & FILE_ATTRIBUTE_DIRECTORY) == 0)
		return false;
	// .. hidden or system attribute(s) set
	//    this check is now disabled because wsnd's add_oal_dlls_in_dir
	//    needs to open the Windows system directory, which sometimes has
	//    these attributes set.
	//if((fa & hs) != 0)
	//	return false;
	return true;
}


DIR* opendir(const char* path)
{
	if(!is_normal_dir(path))
	{
		errno = ENOENT;
fail:
		debug_warn("opendir failed");
		return 0;
	}

	WDIR* d = wdir_alloc();
	if(!d)
	{
		errno = ENOMEM;
		goto fail;
	}

	// build search path for FindFirstFile. note: "path\\dir" only returns
	// information about that directory; trailing slashes aren't allowed.
	// for dir entries to be returned, we have to append "\\*".
	char search_path[PATH_MAX];
	snprintf(search_path, ARRAY_SIZE(search_path), "%s\\*", path);

	// note: we could store search_path and defer FindFirstFile until
	// readdir. this way is a bit more complex but required for
	// correctness (we must return a valid DIR iff <path> is valid).
	d->hFind = FindFirstFileA(search_path, &d->fd);
	if(d->hFind == INVALID_HANDLE_VALUE)
	{
		// not an error - the directory is just empty.
		if(GetLastError() == ERROR_NO_MORE_FILES)
			goto success;

		// translate Win32 error to errno.
		LibError err = LibError_from_win32(FALSE);
		LibError_set_errno(err);

		// release the WDIR allocated above.
		// unfortunately there's no way around this; we need to allocate
		// d before FindFirstFile because it uses d->fd. copying from a
		// temporary isn't nice either (this free doesn't happen often)
		wdir_free(d);
		goto fail;
	}

success:
	return d;
}


struct dirent* readdir(DIR* d_)
{
	WDIR* const d = (WDIR*)d_;

	// avoid polluting the last error.
	DWORD prev_err = GetLastError();

	// first call - skip FindNextFile (see opendir).
	if(d->num_entries_scanned == 0)
	{
		// this directory is empty.
		if(d->hFind == INVALID_HANDLE_VALUE)
			return 0;
		goto already_have_file;
	}

	// until end of directory or a valid entry was found:
	for(;;)
	{
		if(!FindNextFileA(d->hFind, &d->fd))
			goto fail;
already_have_file:

		d->num_entries_scanned++;

		// not a hidden or system entry -> it's valid.
		if((d->fd.dwFileAttributes & hs) == 0)
			break;
	}

	// this entry has passed all checks; return information about it.
	// (note: d_name is a pointer; see struct dirent definition)
	d->ent.d_name = d->fd.cFileName;
	return &d->ent;

fail:
	// FindNextFile failed; determine why and bail.
	// .. legit, end of dir reached. don't pollute last error code.
	if(GetLastError() == ERROR_NO_MORE_FILES)
		SetLastError(prev_err);
	else
		debug_warn("readdir: FindNextFile failed");
	return 0;
}


// return status for the dirent returned by the last successful
// readdir call from the given directory stream.
// currently sets st_size, st_mode, and st_mtime; the rest are zeroed.
// non-portable, but considerably faster than stat(). used by file_enum.
int readdir_stat_np(DIR* d_, struct stat* s)
{
	WDIR* d = (WDIR*)d_;

	memset(s, 0, sizeof(*s));
	s->st_size  = (off_t)u64_from_u32(d->fd.nFileSizeHigh, d->fd.nFileSizeLow);
	s->st_mode  = (d->fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)? S_IFDIR : S_IFREG;
	s->st_mtime = filetime_to_time_t(&d->fd.ftLastWriteTime);
	return 0;
}


int closedir(DIR* d_)
{
	WDIR* const d = (WDIR*)d_;

	FindClose(d->hFind);

	wdir_free(d);
	return 0;
}


//////////////////////////////////////////////////////////////////////////////
//
// terminal
//
//////////////////////////////////////////////////////////////////////////////


static HANDLE std_h[2] = { (HANDLE)((char*)0 + 3), (HANDLE)((char*)0 + 7) };


void _get_console()
{
	AllocConsole();
}

void _hide_console()
{
	FreeConsole();
}


int tcgetattr(int fd, struct termios* termios_p)
{
	if(fd >= 2)
		return -1;
	HANDLE h = std_h[fd];

	DWORD mode;
	GetConsoleMode(h, &mode);
	termios_p->c_lflag = mode & (ENABLE_ECHO_INPUT|ENABLE_LINE_INPUT);

	return 0;
}


int tcsetattr(int fd, int /* optional_actions */, const struct termios* termios_p)
{
	if(fd >= 2)
		return -1;
	HANDLE h = std_h[fd];
	SetConsoleMode(h, (DWORD)termios_p->c_lflag);
	FlushConsoleInputBuffer(h);

	return 0;
}


int poll(struct pollfd /* fds */[], int /* nfds */, int /* timeout */)
{
	return -1;
}


//////////////////////////////////////////////////////////////////////////////
//
// memory mapping
//
//////////////////////////////////////////////////////////////////////////////

// convert POSIX PROT_* flags to their Win32 PAGE_* enumeration equivalents.
// used by mprotect.
static DWORD win32_prot(int prot)
{
	// this covers all 8 combinations of read|write|exec
	// (note that "none" means all flags are 0).
	switch(prot & (PROT_READ|PROT_WRITE|PROT_EXEC))
	{
	case PROT_NONE:
		return PAGE_NOACCESS;
	case PROT_READ:
		return PAGE_READONLY;
	case PROT_WRITE:
		// not supported by Win32; POSIX allows us to also grant read access.
		return PAGE_READWRITE;
	case PROT_EXEC:
		return PAGE_EXECUTE;
	case PROT_READ|PROT_WRITE:
		return PAGE_READWRITE;
	case PROT_READ|PROT_EXEC:
		return PAGE_EXECUTE_READ;
	case PROT_WRITE|PROT_EXEC:
		// not supported by Win32; POSIX allows us to also grant read access.
		return PAGE_EXECUTE_READWRITE;
	case PROT_READ|PROT_WRITE|PROT_EXEC:
		return PAGE_EXECUTE_READWRITE;
	NODEFAULT;
	}
}


int mprotect(void* addr, size_t len, int prot)
{
	const DWORD flNewProtect = win32_prot(prot);
	DWORD flOldProtect;	// required by VirtualProtect
	BOOL ok = VirtualProtect(addr, len, flNewProtect, &flOldProtect);
	WARN_RETURN_IF_FALSE(ok);
	return 0;
}


// called when flags & MAP_ANONYMOUS
static LibError mmap_mem(void* start, size_t len, int prot, int flags, int fd, void** pp)
{
	// sanity checks. we don't care about these but enforce them to
	// ensure callers are compatible with mmap.
	// .. MAP_ANONYMOUS is documented to require this.
	debug_assert(fd == -1);
	// .. if MAP_SHARED, writes are to change "the underlying [mapped]
	//    object", but there is none here (we're backed by the page file).
	debug_assert(flags & MAP_PRIVATE);

	// see explanation at MAP_NORESERVE definition.
	bool want_commit = (prot != PROT_NONE && !(flags & MAP_NORESERVE));

	// decommit a given area (leaves its address space reserved)
	if(!want_commit && start != 0 && flags & MAP_FIXED)
	{
		MEMORY_BASIC_INFORMATION mbi;
		WARN_RETURN_IF_FALSE(VirtualQuery(start, &mbi, sizeof(mbi)));
		if(mbi.State == MEM_COMMIT)
		{
			WARN_IF_FALSE(VirtualFree(start, len, MEM_DECOMMIT));
			*pp = 0;
			// make sure *pp won't be misinterpreted as an error
			cassert(MAP_FAILED != 0);
			return INFO_OK;
		}
	}

	DWORD flAllocationType = want_commit? MEM_COMMIT : MEM_RESERVE;
	DWORD flProtect = win32_prot(prot);
	void* p = VirtualAlloc(start, len, flAllocationType, flProtect);
	if(!p)
		WARN_RETURN(ERR_NO_MEM);
	*pp = p;
	return INFO_OK;
}


// given mmap prot and flags, output protection/access values for use with
// CreateFileMapping / MapViewOfFile. they only support read-only,
// read/write and copy-on-write, so we dumb it down to that and later
// set the correct (and more restrictive) permission via mprotect.
static LibError mmap_file_access(int prot, int flags, DWORD& flProtect, DWORD& dwAccess)
{
	// assume read-only; other cases handled below.
	flProtect = PAGE_READONLY;
	dwAccess  = FILE_MAP_READ;

	if(prot & PROT_WRITE)
	{
		// determine write behavior: (whether they change the underlying file)
		switch(flags & (MAP_SHARED|MAP_PRIVATE))
		{
		// .. changes are written to file.
		case MAP_SHARED:
			flProtect = PAGE_READWRITE;
			dwAccess  = FILE_MAP_WRITE;	// read and write
			break;
		// .. copy-on-write mapping; writes do not affect the file.
		case MAP_PRIVATE:
			flProtect = PAGE_WRITECOPY;
			dwAccess  = FILE_MAP_COPY;
			break;
		// .. either none or both of the flags are set. the latter is
		//    definitely illegal according to POSIX and some man pages
		//    say exactly one must be set, so abort.
		default:
			WARN_RETURN(ERR_INVALID_PARAM);
		}
	}

	return INFO_OK;
}


static LibError mmap_file(void* start, size_t len, int prot, int flags,
	int fd, off_t ofs, void** pp)
{
	debug_assert(fd != -1);	// handled by mmap_mem

	WIN_SAVE_LAST_ERROR;

	HANDLE hFile = HANDLE_from_intptr(_get_osfhandle(fd));
	if(hFile == INVALID_HANDLE_VALUE)
		WARN_RETURN(ERR_INVALID_PARAM);

	// MapViewOfFileEx will fail if the "suggested" base address is
	// nonzero but cannot be honored, so wipe out <start> unless MAP_FIXED.
	if(!(flags & MAP_FIXED))
		start = 0;

	// choose protection and access rights for CreateFileMapping /
	// MapViewOfFile. these are weaker than what PROT_* allows and
	// are augmented below by subsequently mprotect-ing.
	DWORD flProtect; DWORD dwAccess;
	RETURN_ERR(mmap_file_access(prot, flags, flProtect, dwAccess));

	// enough foreplay; now actually map.
	const HANDLE hMap = CreateFileMapping(hFile, 0, flProtect, 0, 0, (LPCSTR)0);
	// .. create failed; bail now to avoid overwriting the last error value.
	if(!hMap)
		WARN_RETURN(ERR_NO_MEM);
	const DWORD ofs_hi = u64_hi(ofs), ofs_lo = u64_lo(ofs);
	void* p = MapViewOfFileEx(hMap, dwAccess, ofs_hi, ofs_lo, (SIZE_T)len, start);
	// .. make sure we got the requested address if MAP_FIXED was passed.
	debug_assert(!(flags & MAP_FIXED) || (p == start));
	// .. free the mapping object now, so that we don't have to hold on to its
	//    handle until munmap(). it's not actually released yet due to the
	//    reference held by MapViewOfFileEx (if it succeeded).
	CloseHandle(hMap);
	// .. map failed; bail now to avoid "restoring" the last error value.
	if(!p)
		WARN_RETURN(ERR_NO_MEM);

	// slap on correct (more restrictive) permissions. 
	(void)mprotect(p, len, prot);

	WIN_RESTORE_LAST_ERROR;
	*pp = p;
	return INFO_OK;
}


void* mmap(void* start, size_t len, int prot, int flags, int fd, off_t ofs)
{
	void* p;
	LibError err;
	if(flags & MAP_ANONYMOUS)
		err = mmap_mem(start, len, prot, flags, fd, &p);
	else
		err = mmap_file(start, len, prot, flags, fd, ofs, &p);
	if(err < 0)
	{
		WARN_ERR(err);
		LibError_set_errno(err);
		return MAP_FAILED;
	}

	return p;
}


int munmap(void* start, size_t UNUSED(len))
{
	// UnmapViewOfFile checks if start was returned by MapViewOfFile*;
	// if not, it will fail.
	BOOL ok = UnmapViewOfFile(start);
	if(!ok)
		// VirtualFree requires dwSize to be 0 (entire region is released).
		ok = VirtualFree(start, 0, MEM_RELEASE);

	WARN_RETURN_IF_FALSE(ok);	// both failed
	return 0;
}


//-----------------------------------------------------------------------------
// DLL
//-----------------------------------------------------------------------------

static HMODULE HMODULE_from_void(void* handle)
{
	return (HMODULE)handle;
}

static void* void_from_HMODULE(HMODULE hModule)
{
	return (void*)hModule;
}


int dlclose(void* handle)
{
	BOOL ok = FreeLibrary(HMODULE_from_void(handle));
	WARN_RETURN_IF_FALSE(ok);
	return 0;
}


char* dlerror(void)
{
	return 0;
}


void* dlopen(const char* so_name, int flags)
{
	if(flags & RTLD_GLOBAL)
		debug_warn("dlopen: unsupported flag(s)");

	// if present, strip .so extension; add .dll extension
	char dll_name[MAX_PATH];
	strcpy_s(dll_name, ARRAY_SIZE(dll_name)-5, so_name);
	char* ext = (char*)path_extension(dll_name);
	if(ext[0] == '\0')	// no extension
		strcat(dll_name, ".dll");	// safe
	else	// need to replace extension
		SAFE_STRCPY(ext, "dll");

	HMODULE hModule = LoadLibrary(dll_name);
	if(!hModule)
		debug_warn("dlopen failed");
	return void_from_HMODULE(hModule);
}


void* dlsym(void* handle, const char* sym_name)
{
	HMODULE hModule = HMODULE_from_void(handle);
	void* sym = GetProcAddress(hModule, sym_name);
	if(!sym)
		debug_warn("dlsym failed");
	return sym;
}


//-----------------------------------------------------------------------------


int uname(struct utsname* un)
{
	static OSVERSIONINFO vi;
	vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	GetVersionEx(&vi);

	// OS implementation name
	const char* family = "??";
	int ver = (vi.dwMajorVersion << 8) | vi.dwMinorVersion;
	if(vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
		family = (ver == 0x045a)? "ME" : "9x";
	if(vi.dwPlatformId == VER_PLATFORM_WIN32_NT)
	{
		if(ver == 0x0500)
			family = "2k";
		else if(ver == 0x0501)
			family = "XP";
		else
			family = "NT";
	}
	sprintf(un->sysname, "Win%s", family);

	// release info
	const char* vs = vi.szCSDVersion;
	int sp;
	if(sscanf(vs, "Service Pack %d", &sp) == 1)
		sprintf(un->release, "SP %d", sp);
	else
	{
		const char* release = "";
		if(vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
		{
			if(!strcmp(vs, " C"))
				release = "OSR2";
			else if(!strcmp(vs, " A"))
				release = "SE";
		}
		strcpy(un->release, release);	// safe
	}

	// version
	sprintf(un->version, "%lu.%02lu.%lu", vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber & 0xffff);

	// node name
	DWORD buf_size = sizeof(un->nodename);
	DWORD last_err = GetLastError();
	BOOL ok = GetComputerName(un->nodename, &buf_size);
	// GetComputerName sets last error even on success - suppress.
	if(ok)
		SetLastError(last_err);
	else
		debug_warn("GetComputerName failed");

	// hardware type
	static SYSTEM_INFO si;
	GetSystemInfo(&si);
	if(si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64)
		strcpy(un->machine, "AMD64");	// safe
	else
		strcpy(un->machine, "IA-32");	// safe

	return 0;
}


 




long sysconf(int name)
{
	// used by _SC_*_PAGES
	static DWORD page_size;
	static BOOL (WINAPI *pGlobalMemoryStatusEx)(MEMORYSTATUSEX*);  

	ONCE(
	{
		// get page size
		// (used by _SC_PAGESIZE and _SC_*_PAGES)
		SYSTEM_INFO si;
		GetSystemInfo(&si);		// can't fail => page_size always > 0.
		page_size = si.dwPageSize;

		// import GlobalMemoryStatusEx - it's not defined by the VC6 PSDK.
		// used by _SC_*_PAGES if available (provides better results).
		const HMODULE hKernel32Dll = LoadLibrary("kernel32.dll");  
		*(void**)&pGlobalMemoryStatusEx = GetProcAddress(hKernel32Dll, "GlobalMemoryStatusEx"); 
		FreeLibrary(hKernel32Dll);
			// make sure the reference is released so BoundsChecker
			// doesn't complain. it won't actually be unloaded anyway -
			// there is at least one other reference.
	}
	);


	switch(name)
	{
	case _SC_PAGESIZE:
	// note: don't add _SC_PAGE_SIZE - they are different names but
	// have the same value.
		return page_size;

	case _SC_PHYS_PAGES:
	case _SC_AVPHYS_PAGES:
		{
		u64 total_phys_mem;
		u64 avail_phys_mem;

		// first try GlobalMemoryStatus - cannot fail.
		// override its results if GlobalMemoryStatusEx is available.
		MEMORYSTATUS ms;
		GlobalMemoryStatus(&ms);
			// can't fail.
		total_phys_mem = ms.dwTotalPhys;
		avail_phys_mem = ms.dwAvailPhys;

		// newer API is available: use it to report correct results
		// (no overflow or wraparound) on systems with > 4 GB of memory.
		MEMORYSTATUSEX mse = { sizeof(mse) };
		if(pGlobalMemoryStatusEx && pGlobalMemoryStatusEx(&mse))
		{
			total_phys_mem = mse.ullTotalPhys;
			avail_phys_mem = mse.ullAvailPhys;
		}
		// else: not an error, since this isn't available before Win2k / XP.
		// we have results from GlobalMemoryStatus anyway.

		if(name == _SC_PHYS_PAGES)
			return (long)(round_up((uintptr_t)total_phys_mem, 2*MiB) / page_size);
				// Richter, "Programming Applications for Windows":
				// reported value doesn't include non-paged pool reserved
				// during boot; it's not considered available to kernel.
				// it's 528 KiB on my 512 MiB machine (WinXP and Win2k).
 		else
			return (long)(avail_phys_mem / page_size);
		}

	default:
		return -1;
	}
}
