/*
* Author: Manoj Ampalam <manoj.ampalam@microsoft.com>
*
* Author: Bryan Berns <berns@uwalumni.com>
*   Modified group detection use s4u token information 
*
* Copyright(c) 2016 Microsoft Corp.
* All rights reserved
*
* Misc Unix POSIX routine implementations for Windows
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met :
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and / or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#define UMDF_USING_NTSTATUS 
#define SECURITY_WIN32
#include <Windows.h>
#include <stdio.h>
#include <time.h>
#include <Shlwapi.h>
#include <conio.h>
#include <LM.h>
#include <Sddl.h>
#include <Aclapi.h>
#include <Ntsecapi.h>
#include <security.h>
#include <ntstatus.h>

#include "inc\unistd.h"
#include "inc\sys\stat.h"
#include "inc\sys\statvfs.h"
#include "inc\sys\time.h"
#include "misc_internal.h"
#include "inc\dlfcn.h"
#include "inc\dirent.h"
#include "inc\sys\types.h"
#include "inc\sys\ioctl.h"
#include "inc\fcntl.h"
#include "inc\utf.h"
#include "signal_internal.h"
#include "misc_internal.h"
#include "debug.h"
#include "w32fd.h"
#include "inc\string.h"
#include "inc\grp.h"
#include "inc\time.h"

#include <wchar.h>

static char* s_programdir = NULL;

/* Maximum reparse buffer info size. The max user defined reparse
 * data is 16KB, plus there's a header. 
 */
#define MAX_REPARSE_SIZE 17000 
#define IO_REPARSE_TAG_SYMBOLIC_LINK IO_REPARSE_TAG_RESERVED_ZERO 
#define IO_REPARSE_TAG_MOUNT_POINT (0xA0000003L) /* winnt ntifs */
#define IO_REPARSE_TAG_HSM (0xC0000004L) /* winnt ntifs */
#define IO_REPARSE_TAG_SIS (0x80000007L) /* winnt ntifs */
#define REPARSE_MOUNTPOINT_HEADER_SIZE 8

 /* Difference in us between UNIX Epoch and Win32 Epoch */
#define EPOCH_DELTA  116444736000000000ULL /* in 100 nsecs intervals */
#define RATE_DIFF 10000000ULL /* 100 nsecs */

#define NSEC_IN_SEC 1000000000ULL // 10**9
#define USEC_IN_SEC 1000000ULL // 10**6

/* Windows CRT defines error string messages only till 43 in errno.h
 * This is an extended list that defines messages for EADDRINUSE through EWOULDBLOCK
 */
char* _sys_errlist_ext[] = {
	"Address already in use",				/* EADDRINUSE      100 */
	"Address not available",				/* EADDRNOTAVAIL   101 */
	"Address family not supported",				/* EAFNOSUPPORT    102 */
	"Connection already in progress",			/* EALREADY        103 */
	"Bad message",						/* EBADMSG         104 */
	"Operation canceled",					/* ECANCELED       105 */
	"Connection aborted",					/* ECONNABORTED    106 */
	"Connection refused",					/* ECONNREFUSED    107 */
	"Connection reset",					/* ECONNRESET      108 */
	"Destination address required",				/* EDESTADDRREQ    109 */
	"Host is unreachable",					/* EHOSTUNREACH    110 */
	"Identifier removed",					/* EIDRM           111 */
	"Operation in progress",				/* EINPROGRESS     112 */
	"Socket is connected",					/* EISCONN         113 */
	"Too many levels of symbolic links",			/* ELOOP           114 */
	"Message too long",					/* EMSGSIZE        115 */
	"Network is down",					/* ENETDOWN        116 */
	"Connection aborted by network",			/* ENETRESET       117 */
	"Network unreachable",					/* ENETUNREACH     118 */
	"No buffer space available",				/* ENOBUFS         119 */
	"No message is available on the STREAM head read queue",/* ENODATA         120 */
	"Link has been severed",				/* ENOLINK         121 */
	"No message of the desired type",			/* ENOMSG          122 */
	"Protocol not available",				/* ENOPROTOOPT     123 */
	"No STREAM resources",					/* ENOSR           124 */
	"Not a STREAM",						/* ENOSTR          125 */
	"The socket is not connected",				/* ENOTCONN        126 */
	"enotrecoverable",					/* ENOTRECOVERABLE 127 */
	"Not a socket",						/* ENOTSOCK        128 */
	"Operation not supported",				/* ENOTSUP         129 */
	"Operation not supported on socket",			/* EOPNOTSUPP      130 */
	"eother",						/* EOTHER          131 */
	"Value too large to be stored in data type",		/* EOVERFLOW       132 */
	"eownerdead",						/* EOWNERDEAD      133 */
	"Protocol error",					/* EPROTO          134 */
	"Protocol not supported",				/* EPROTONOSUPPORT 135 */
	"Protocol wrong type for socket",			/* EPROTOTYPE      136 */
	"Timer expired",					/* ETIME           137 */
	"Connection timed out",					/* ETIMEDOUT       138 */
	"Text file busy",					/* ETXTBSY         139 */
	"Operation would block"					/* EWOULDBLOCK     140 */
};

/* chroot state */
char* chroot_path = NULL;
int chroot_path_len = 0;
/* UTF-16 version of the above */
wchar_t* chroot_pathw = NULL;

int
usleep(unsigned int useconds)
{
	Sleep(useconds / 1000);
	return 1;
}

static LONGLONG
timespec_to_nsec(const struct timespec *req)
{
	LONGLONG sec = req->tv_sec;
	return sec * NSEC_IN_SEC + req->tv_nsec;
}


int
nanosleep(const struct timespec *req, struct timespec *rem)
{
	HANDLE timer;
	LARGE_INTEGER li;

	if (req->tv_sec < 0 || req->tv_nsec < 0 || req->tv_nsec > 999999999) {
		errno = EINVAL;
		return -1;
	}

	if ((timer = CreateWaitableTimerW(NULL, TRUE, NULL)) == NULL) {
		errno = EFAULT;
		return -1;
	}

	/* convert timespec to 100ns intervals */
	li.QuadPart = -(timespec_to_nsec(req) / 100);
	if (!SetWaitableTimer(timer, &li, 0, NULL, NULL, FALSE)) {
		CloseHandle(timer);
		errno = EFAULT;
		return -1;
	}

	/* TODO - use wait_for_any_event, since we want to wake up on interrupts*/
	switch (WaitForSingleObject(timer, INFINITE)) {
	case WAIT_OBJECT_0:
		CloseHandle(timer);
		return 0;
	default:
		CloseHandle(timer);
		errno = EFAULT;
		return -1;
	}
}

/* This routine is contributed by  * Author: NoMachine <developers@nomachine.com>
 * Copyright (c) 2009, 2010 NoMachine
 * All rights reserved
 */
int
gettimeofday(struct timeval *tv, void *tz)
{
	union {
		FILETIME ft;
		unsigned long long ns;
	} timehelper;
	unsigned long long us;

	/* Fetch time since Jan 1, 1601 in 100ns increments */
	GetSystemTimeAsFileTime(&timehelper.ft);	

	/* Remove the epoch difference & convert 100ns to us */
	us = (timehelper.ns - EPOCH_DELTA) / 10;

	/* Stuff result into the timeval */
	tv->tv_sec = (long)(us / USEC_IN_SEC);
	tv->tv_usec = (long)(us % USEC_IN_SEC);

	return 0;
}

void
explicit_bzero(void *b, size_t len)
{
	SecureZeroMemory(b, len);
}

static DWORD last_dlerror = ERROR_SUCCESS;

HMODULE
dlopen(const char *filename, int flags)
{
	wchar_t *wfilename = utf8_to_utf16(filename);
	if (wfilename == NULL) {
		last_dlerror = ERROR_INVALID_PARAMETER;
		return NULL;
	}

	HMODULE module = LoadLibraryW(wfilename);
	if (module == NULL)
		last_dlerror = GetLastError();

	free(wfilename);
	return module;
}

int
dlclose(HMODULE handle)
{
	FreeLibrary(handle);
	return 0;
}

void *
dlsym(HMODULE handle, const char *symbol)
{
	void *ptr = GetProcAddress(handle, symbol);
	if (ptr == NULL)
		last_dlerror = GetLastError();
	return ptr;
}

char *
dlerror()
{
	static char *message = NULL;
	if (message != NULL) {
		free(message);
		message = NULL;
	}
	if (last_dlerror == ERROR_SUCCESS)
		return NULL;

	wchar_t *wmessage = NULL;
	DWORD length = FormatMessageW(
		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL, last_dlerror, 0, (wchar_t *) &wmessage, 0, NULL);
	last_dlerror = ERROR_SUCCESS;

	if (length == 0)
		goto error;

	if (wmessage[length - 1] == L'\n')
		wmessage[length - 1] = L'\0';
	if (length > 1 && wmessage[length - 2] == L'\r')
		wmessage[length - 2] = L'\0';

	message = utf16_to_utf8(wmessage);
	LocalFree(wmessage);

	if (message == NULL)
		goto error;

	return message;

error:
	return "Failed to format error message";
}

/*fopen on Windows to mimic https://linux.die.net/man/3/fopen
* only r, w, a are supported for now
*/
FILE *
w32_fopen_utf8(const char *input_path, const char *mode)
{
	wchar_t *wmode = NULL, *wpath = NULL;
	FILE* f = NULL;
	char utf8_bom[] = { 0xEF,0xBB,0xBF };
	char first3_bytes[3];
	int status = 1;
	errno_t r = 0;
	int nonfs_dev = 0; /* opening a non file system device */

	if (mode == NULL || mode[1] != '\0') {
		errno = ENOTSUP;
		return NULL;
	}

	if(NULL == input_path) { 
		errno = EINVAL;
		debug3("fopen - ERROR:%d", errno);
		return NULL; 
	}

	/* if opening null device, point to Windows equivalent */
	if (strncmp(input_path, NULL_DEVICE, sizeof(NULL_DEVICE)) == 0) {
		nonfs_dev = 1;
		wpath = utf8_to_utf16(NULL_DEVICE_WIN);
	}
	else
		wpath = resolved_path_utf16(input_path);
	
	wmode = utf8_to_utf16(mode);
	if (wpath == NULL || wmode == NULL)
		goto cleanup;

	if ((_wfopen_s(&f, wpath, wmode) != 0) || (f == NULL)) {
		debug3("Failed to open file:%S error:%d", wpath, errno);
		goto cleanup;
	}	

	if (chroot_pathw && !nonfs_dev) {
		/* ensure final path is within chroot */
		HANDLE h = (HANDLE)_get_osfhandle(_fileno(f));
		if (!file_in_chroot_jail(h)) {
			debug3("%s is not in chroot jail", input_path);
			fclose(f);
			f = NULL;
			errno = EACCES;
			goto cleanup;
		}
	}

	/* BOM adjustments for file streams*/
	if (mode[0] == 'w' && fseek(f, 0, SEEK_SET) != EBADF) {
		/* write UTF-8 BOM - should we ?*/
		/*if (fwrite(utf8_bom, sizeof(utf8_bom), 1, f) != 1) {
			fclose(f);
			goto cleanup;
		}*/

	} else if (mode[0] == 'r' && fseek(f, 0, SEEK_SET) != EBADF) {
		/* read out UTF-8 BOM if present*/
		if (fread(first3_bytes, 3, 1, f) != 1 ||
			memcmp(first3_bytes, utf8_bom, 3) != 0) {
			fseek(f, 0, SEEK_SET);
		}
	}

cleanup:

	if (wpath) 
		free(wpath);
	if (wmode)
		free(wmode);

	return f;
}

/*
* fgets to support Unicode input 
* each UTF-16 char may bloat up to 4 utf-8 chars. We cannot determine if the length of 
* input unicode string until it is readed and converted to utf8 string.
* There is a risk to miss on unicode char when last unicode char read from console
* does not fit the remain space in str. use cautiously. 
*/
char*
 w32_fgets(char *str, int n, FILE *stream) {
	if (!str || !n || !stream) return NULL;

	HANDLE h = (HANDLE)_get_osfhandle(_fileno(stream));
	wchar_t* str_w = NULL;
	char *ret = NULL, *str_tmp = NULL, *cp = NULL;
	int actual_read = 0;
	errno_t r = 0;

	if (h != NULL && h != INVALID_HANDLE_VALUE
	    && GetFileType(h) == FILE_TYPE_CHAR) {

		/* Allocate memory for one UTF-16 char (up to 4 bytes) and a terminate char (\0) */
		if ((str_w = malloc(3 * sizeof(wchar_t))) == NULL) {
			errno = ENOMEM;
			goto cleanup;
		}
		/* prepare for Unicode input */
		_setmode(_fileno(stream), O_U16TEXT);
		cp = str;
		/*
		* each UTF-16 char may bloat up to 4 utf-8 chars
		* read one wide chars at time from console and convert it to utf8
		* stop reading until reach '\n' or the converted utf8 string length is n-1
		*/
		do {
			if (str_tmp)
				free(str_tmp);			
			if (fgetws(str_w, 2, stream) == NULL)
				goto cleanup;
			if ((str_tmp = utf16_to_utf8(str_w)) == NULL) {
				debug3("utf16_to_utf8 failed!");
				errno = ENOMEM;
				goto cleanup;
			}
			
			if((actual_read + (int)strlen(str_tmp)) >= n)
				break;
			if ((r = memcpy_s(cp, n - actual_read, str_tmp, strlen(str_tmp))) != 0) {
				debug3("memcpy_s failed with error: %d.", r);
				goto cleanup;
			}
			actual_read += (int)strlen(str_tmp);
			cp += strlen(str_tmp);
			
		} while ((actual_read < n - 1) && *str_tmp != '\n');
		*cp = '\0';

		if (actual_read > n - 1) {
			/* shouldn't happen. but handling in case */
			debug3("actual_read %d exceeds the limit:%d", actual_read, n-1);
			errno = EINVAL;
			goto cleanup;
		}		
		ret = str;
	}
	else
		ret = fgets(str, n, stream);
cleanup:
	if (str_w)
		free(str_w);
	if (str_tmp)
		free(str_tmp);
	return ret;
}

/* Account for differences between Unix's and Windows versions of setvbuf */
int 
w32_setvbuf(FILE *stream, char *buffer, int mode, size_t size) {
	
	/* BUG: setvbuf on console stream interferes with Unicode I/O	*/
	HANDLE h = (HANDLE)_get_osfhandle(_fileno(stream));
	
	if (h != NULL && h != INVALID_HANDLE_VALUE
	    && GetFileType(h) == FILE_TYPE_CHAR)
		return 0;

	/* BUG: setvbuf on file stream is interfering with w32_fopen */
	/* short circuit for now*/
	return 0;

	/*
	 * if size is 0, set no buffering. 
	 * Windows does not differentiate __IOLBF and _IOFBF
	 */
	if (size == 0)
		return setvbuf(stream, NULL, _IONBF, 0);
	else
		return setvbuf(stream, buffer, mode, size);
}

int
daemon(int nochdir, int noclose)
{
	FreeConsole();
	return 0;
}

int
w32_ioctl(int d, int request, ...)
{
	va_list valist;
	va_start(valist, request);

	switch (request) {
	case TIOCGWINSZ: {
		struct winsize* wsize = va_arg(valist, struct winsize*);
		CONSOLE_SCREEN_BUFFER_INFO c_info;
		if (wsize == NULL || !GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &c_info)) {
			errno = EINVAL;
			return -1;
		}

		wsize->ws_col = c_info.dwSize.X;
		wsize->ws_row = c_info.srWindow.Bottom - c_info.srWindow.Top + 1;
		wsize->ws_xpixel = 640;
		wsize->ws_ypixel = 480;

		return 0;
	}
	default:
		errno = ENOTSUP;
		return -1;
	}
}

/* p should be at least 12 bytes long*/
void
strmode(mode_t mode, char *p)
{
	/* print type */
	switch (mode & S_IFMT) {
	case S_IFDIR:			/* directory */
		*p++ = 'd';
		break;
	case S_IFCHR:			/* character special */
		*p++ = 'c';
		break;
	case S_IFREG:			/* regular */
		*p++ = '-';
		break;
	case S_IFLNK:			/* symbolic link */
		*p++ = 'l';
		break;			
#ifdef S_IFSOCK
	case S_IFSOCK:			/* socket */
		*p++ = 's';
		break;
#endif
	case _S_IFIFO:			/* fifo */
		*p++ = 'p';
		break;
	default:			/* unknown */
		*p++ = '?';
		break;
	}

	/* group, other are not applicable on the windows */

	/* usr */
	if (mode & S_IREAD)
		*p++ = 'r';
	else
		*p++ = '-';
	if (mode & S_IWRITE)
		*p++ = 'w';
	else
		*p++ = '-';
	if (mode & S_IEXEC)
		*p++ = 'x';
	else
		*p++ = '-';

	const char *permissions = "****** ";	
	for(int i = 0; i < (int)strlen(permissions); i++)
		*p++ = permissions[i];
	
	*p = '\0';
}

int
w32_chmod(const char *pathname, mode_t mode)
{
	/* TODO - 
	 * _wchmod() doesn't behave like unix "chmod" command.
	 * _wchmod() only toggles the read-only bit and it doesn't touch ACL.
	 */	
	int ret;
	wchar_t *resolvedPathName_utf16 = resolved_path_utf16(pathname);
	if (resolvedPathName_utf16 == NULL) 
		return -1;

	ret = _wchmod(resolvedPathName_utf16, mode);
	free(resolvedPathName_utf16);
	return ret;
}

int
w32_chown(const char *pathname, unsigned int owner, unsigned int group)
{
	/* TODO - implement this */
	errno = EOPNOTSUPP;
	return -1;
}

int 
w32_fchown( int fd, unsigned int owner, unsigned int group)
{
	/* TODO - implement this */
	errno = EOPNOTSUPP;
	return -1;
}

/* Convert a UNIX time into a Windows file time */
void
unix_time_to_file_time(ULONG t, LPFILETIME pft)
{
	ULONGLONG ull;
	ull = UInt32x32To64(t, RATE_DIFF) + EPOCH_DELTA;

	pft->dwLowDateTime = (DWORD)ull;
	pft->dwHighDateTime = (DWORD)(ull >> 32);
}

/* Convert a Windows file time into a UNIX time_t */
void
file_time_to_unix_time(const LPFILETIME pft, time_t * winTime)
{
	*winTime = ((long long)pft->dwHighDateTime << 32) + pft->dwLowDateTime;
	*winTime -= EPOCH_DELTA;
	*winTime /= RATE_DIFF;		 /* Nano to seconds resolution */
}

static BOOL
is_root_or_empty(wchar_t * path)
{
	wchar_t * path_start;
	int len;
	if (!path) 
		return FALSE;
	len = (int)wcslen(path);
	if((len > 1) && __ascii_iswalpha(path[0]) && path[1] == L':')
		path_start = path + 2;
	else
		path_start = path;
	/*path like  c:\, /, \ are root directory*/
	if ((*path_start == L'\0') || ((*path_start == L'\\' || *path_start == L'/' ) && path_start[1] == L'\0'))
		return TRUE;
	return FALSE;
}

static BOOL
has_executable_extension(wchar_t * path)
{
	wchar_t * last_dot;
	if (!path)
		return FALSE;

	last_dot = wcsrchr(path, L'.');
	if (!last_dot)
		return FALSE;
	if (_wcsnicmp(last_dot, L".exe", 4) != 0 && _wcsnicmp(last_dot, L".cmd", 4) != 0 &&
	_wcsnicmp(last_dot, L".bat", 4) != 0 && _wcsnicmp(last_dot, L".com", 4) != 0)
		return FALSE; 
	return TRUE;
}

int
file_attr_to_st_mode(wchar_t * path, DWORD attributes)
{
	int mode = S_IREAD;
	BOOL isReadOnlyFile = FALSE;
	if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0 || is_root_or_empty(path))
		mode |= S_IFDIR | _S_IEXEC;
	else {
		mode |= S_IFREG;
		/* See if file appears to be an executable by checking its extension */
		if (has_executable_extension(path))
			mode |= _S_IEXEC;

	}
	if (!(attributes & FILE_ATTRIBUTE_READONLY))
		mode |= S_IWRITE;
	else
		isReadOnlyFile = TRUE;

	// We don't populate the group permissions as its not applicable to windows OS.
	// propagate owner read/write/execute bits to other fields.	
	mode |= get_others_file_permissions(path, isReadOnlyFile);

	return mode;
}

static int
settimes(wchar_t * path, FILETIME *cretime, FILETIME *acttime, FILETIME *modtime)
{
	HANDLE handle;
	handle = CreateFileW(path, GENERIC_WRITE, FILE_SHARE_WRITE,
		NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);

	if (handle == INVALID_HANDLE_VALUE) {
		/* TODO - convert Win32 error to errno */
		errno = GetLastError();
		debug3("w32_settimes - CreateFileW ERROR:%d", errno);
		return -1;
	}

	if (SetFileTime(handle, cretime, acttime, modtime) == 0) {
		errno = GetLastError();
		debug3("w32_settimes - SetFileTime ERROR:%d", errno);
		CloseHandle(handle);
		return -1;
	}

	CloseHandle(handle);
	return 0;
}

int
w32_utimes(const char *filename, struct timeval *tvp)
{
	int ret;
	FILETIME acttime, modtime;
	wchar_t *resolvedPathName_utf16 = resolved_path_utf16(filename);
	if (resolvedPathName_utf16 == NULL) 
		return -1;

	memset(&acttime, 0, sizeof(FILETIME));
	memset(&modtime, 0, sizeof(FILETIME));

	unix_time_to_file_time((ULONG)tvp[0].tv_sec, &acttime);
	unix_time_to_file_time((ULONG)tvp[1].tv_sec, &modtime);
	ret = settimes(resolvedPathName_utf16, NULL, &acttime, &modtime);
	free(resolvedPathName_utf16);
	return ret;
}

int
w32_symlink(const char *target, const char *linkpath)
{
	return fileio_symlink(target, linkpath);
}

int
w32_link(const char *oldpath, const char *newpath)
{
	return fileio_link(oldpath, newpath);
}

int
w32_rename(const char *old_name, const char *new_name)
{
	if (old_name == NULL || new_name == NULL) {
		errno = EFAULT;
		return -1;
	}

	wchar_t *resolvedOldPathName_utf16 = resolved_path_utf16(old_name);
	wchar_t *resolvedNewPathName_utf16 = resolved_path_utf16(new_name);

	if (NULL == resolvedOldPathName_utf16 || NULL == resolvedNewPathName_utf16) 
		return -1;
	
	/*
	 * To be consistent with POSIX rename(),
	 * 1) if the new_name is file, then delete it so that _wrename will succeed.
	 * 2) if the new_name is directory and it is empty then delete it so that _wrename will succeed.
	 */
	struct _stat64 st_new;
	struct _stat64 st_old;
	if ((fileio_stat(new_name, &st_new) != -1) &&
	    (fileio_stat(old_name, &st_old) != -1)) {
		if (((st_old.st_mode & _S_IFMT) == _S_IFREG) &&
		    ((st_new.st_mode & _S_IFMT) == _S_IFREG))
			w32_unlink(new_name);

		if (((st_old.st_mode & _S_IFMT) == _S_IFDIR) &&
		    ((st_new.st_mode & _S_IFMT) == _S_IFDIR)) {
			DIR *dirp = opendir(new_name);
			if (NULL != dirp) {
				struct dirent *dp = readdir(dirp);
				closedir(dirp);

				if (dp == NULL)
					w32_rmdir(new_name);
			}
		}
	}

	int returnStatus = _wrename(resolvedOldPathName_utf16, resolvedNewPathName_utf16);
	free(resolvedOldPathName_utf16);
	free(resolvedNewPathName_utf16);

	return returnStatus;
}

int
w32_unlink(const char *path)
{
	wchar_t *resolvedPathName_utf16 = resolved_path_utf16(path);
	if (NULL == resolvedPathName_utf16) 
		return -1;

	int returnStatus = _wunlink(resolvedPathName_utf16);
	free(resolvedPathName_utf16);

	return returnStatus;
}

int
w32_rmdir(const char *path)
{
	wchar_t *resolvedPathName_utf16 = resolved_path_utf16(path);
	if (NULL == resolvedPathName_utf16) 
		return -1;

	int returnStatus = _wrmdir(resolvedPathName_utf16);
	free(resolvedPathName_utf16);

	return returnStatus;
}

int
w32_chdir(const char *dirname_utf8)
{
	wchar_t *dirname_utf16 = resolved_path_utf16(dirname_utf8);
	if (dirname_utf16 == NULL) 
		return -1;

	int returnStatus = _wchdir(dirname_utf16);
	free(dirname_utf16);

	return returnStatus;
}

char *
w32_getcwd(char *buffer, int maxlen)
{
	if(!buffer) return NULL;

	wchar_t wdirname[PATH_MAX];
	char* putf8 = NULL;

	if (_wgetcwd(wdirname, PATH_MAX) == NULL)
		return NULL;

	if ((putf8 = utf16_to_utf8(wdirname)) == NULL) {
		errno = ENOMEM;
		return NULL;
	}

	if ((int)strlen(putf8) >= maxlen) {
		errno = ERANGE;
		free(putf8);
		return NULL;
	}

	if (strcpy_s(buffer, maxlen, putf8))
		return NULL;
	free(putf8);

	to_lower_case(buffer);

	if (chroot_path) {
		/* ensure we are within chroot jail */
		char c = buffer[chroot_path_len];
		if ((int)strlen(buffer) < chroot_path_len ||
		    memcmp(chroot_path, buffer, chroot_path_len) != 0 ||
		    (c != '\0' && c!= '\\') ) {
			errno = EOTHER;
			error("cwd is not currently within chroot");
			return NULL;
		}

		/* is cwd chroot ?*/
		if (c == '\0') {
			buffer[0] = '\\';
			buffer[1] = '\0';
		}
		else {
			char *tail = buffer + chroot_path_len;
			memmove_s(buffer, maxlen, tail, strlen(tail) + 1);
		}
	} 

	return buffer;
}

int
w32_mkdir(const char *path_utf8, unsigned short mode)
{
	int curmask;
	wchar_t *path_utf16 = resolved_path_utf16(path_utf8);
	if (path_utf16 == NULL) 
		return -1;

	int returnStatus = _wmkdir(path_utf16);
	if (returnStatus < 0) {
		free(path_utf16);
		return -1;
	}

	errno_t error = _umask_s(0, &curmask);
	if(!error)
		_umask_s(curmask, &curmask);

	returnStatus = _wchmod(path_utf16, mode & ~curmask & (_S_IREAD | _S_IWRITE));
	free(path_utf16);

	return returnStatus;
}

int
w32_stat(const char *input_path, struct w32_stat *buf)
{
	return fileio_stat(input_path, (struct _stat64*)buf);
}

int
w32_lstat(const char *input_path, struct w32_stat *buf)
{
	return fileio_lstat(input_path, (struct _stat64*)buf);
}

/* if file is symbolic link, copy its link into "link" */
int
w32_readlink(const char *path, char *link, int linklen)
{
	return fileio_readlink(path, link, linklen);
}

/* convert forward slash to back slash */
void
convertToBackslash(char *str)
{
	while (*str) {
		if (*str == '/')
			*str = '\\';
		str++;
	}
}

void
convertToBackslashW(wchar_t *str)
{
	while (*str) {
		if (*str == L'/')
			*str = L'\\';
		str++;
	}
}

/* convert back slash to forward slash */
void
convertToForwardslash(char *str)
{
	while (*str) {
		if (*str == '\\')
			*str = '/';
		str++;
	}
}

/*
 * This method will resolves references to /./, /../ and extra '/' characters in the null-terminated string named by
 *  path to produce a canonicalized absolute pathname.
 */
char *
realpath(const char *inputpath, char * resolved)
{
	wchar_t* temppath_utf16 = NULL;
	wchar_t* resolved_utf16 = NULL;
	char path[PATH_MAX] = { 0, }, tempPath[PATH_MAX] = { 0, }, *ret = NULL;
	int is_win_path = 1;

	if (!inputpath || !resolved)
		return NULL;
	
	size_t path_len = strlen(inputpath);
	resolved[0] = '\0';

	if (path_len > PATH_MAX) {
		errno = EINVAL;
		return NULL;
	}

	if (is_bash_test_env() && bash_to_win_path(inputpath, path, _countof(path)))
		is_win_path = 0;

	if (is_win_path) {
		if (_strnicmp(inputpath, PROGRAM_DATA, strlen(PROGRAM_DATA)) == 0) {
			strcpy_s(path, PATH_MAX, __progdata);
			strcat_s(path, PATH_MAX, &inputpath[strlen(PROGRAM_DATA)]);
		} else {
			memcpy_s(path, PATH_MAX, inputpath, strlen(inputpath));
		}
	}

	path_len = strlen(path);
	if (path_len > PATH_MAX) {
		errno = EINVAL;
		return NULL;
	}

	/* resolve root directory to the same */
	if (path_len == 1 && (path[0] == '/' || path[0] == '\\')) {
		resolved[0] = '/';
		resolved[1] = '\0';
		ret = resolved;
		goto done;
	}

	/* resolve this common case scenario to root */
	/* "cd .." from within a drive root */
	if (path_len == 6 && !chroot_path) {
		char *tmplate = "/x:/..";
		strcat_s(resolved, PATH_MAX, path);
		resolved[1] = 'x';
		if (strcmp(tmplate, resolved) == 0) {
			resolved[0] = '/';
			resolved[1] = '\0';
			ret = resolved;
			goto done;
		}
	}

	if (chroot_path) {
		resolved[0] = '\0';
		strcat_s(resolved, PATH_MAX, chroot_path);
		/* if path is relative, add cwd within chroot */
		if (path[0] != '/' && path[0] != '\\') {
			w32_getcwd(resolved + chroot_path_len, PATH_MAX - chroot_path_len);
			strcat_s(resolved, PATH_MAX, "/");
		}
		/* TODO - This logic will fail if the chroot_path is more than PATH_MAX/2.
		 * resolved variable is of PATH_MAX.
		 * We first copy chroot_path to resolved variable then incoming path (which can be again chroot_path).
		 * In this case strcat_s will thrown a run time insufficient buffer exception.
		 */
		strcat_s(resolved, PATH_MAX, path);
	}
	else if ((path_len >= 2) && (path[0] == '/') && path[1] && (path[2] == ':')) {
		if((errno = strncpy_s(resolved, PATH_MAX, path + 1, path_len)) != 0 ) /* skip the first '/' */ {
			debug3("memcpy_s failed with error: %d.", errno);
			goto done;
		}
	}
	else if(( errno = strncpy_s(resolved, PATH_MAX, path, path_len + 1)) != 0) {
		debug3("memcpy_s failed with error: %d.", errno);
		goto done;
	}

	if ((resolved[0]) && (resolved[1] == ':') && (resolved[2] == '\0')) { /* make "x:" as "x:\\" */
		resolved[2] = '\\';
		resolved[3] = '\0';
	}

	/* note: _wfullpath() is required to resolve paths containing unicode characters */
	if ((resolved_utf16 = utf8_to_utf16(resolved)) == NULL ||
		(temppath_utf16 = _wfullpath(NULL, resolved_utf16, 0)) == NULL ||
		WideCharToMultiByte(CP_UTF8, 0, temppath_utf16, -1, tempPath, sizeof(tempPath), NULL, NULL) == 0) {
		errno = EINVAL;
		goto done;
	}

	if (chroot_path) {
		if (strlen(tempPath) < strlen(chroot_path)) {
			errno = EACCES;
			goto done;
		}
		if (memcmp(chroot_path, tempPath, strlen(chroot_path)) != 0) {
			errno = EACCES;
			goto done;
		}

		resolved[0] = '\0';
		
		if (strlen(tempPath) == strlen(chroot_path))
			/* realpath is the same as chroot_path */
			strcat_s(resolved, PATH_MAX, "\\");
		else
			strcat_s(resolved, PATH_MAX, tempPath + strlen(chroot_path));

		if (resolved[0] != '\\') {
			errno = EACCES;
			goto done;
		}

		convertToForwardslash(resolved);
		ret = resolved;
		goto done;
	}
	else {
		convertToForwardslash(tempPath);
		resolved[0] = '/'; /* will be our first slash in /x:/users/test1 format */
		if ((errno = strncpy_s(resolved + 1, PATH_MAX - 1, tempPath, sizeof(tempPath) - 1)) != 0) {
			debug3("memcpy_s failed with error: %d.", errno);
			goto done;
		}
		ret = resolved;
		goto done;
	}

done:
	if (resolved_utf16 != NULL)
		free(resolved_utf16);
	if (temppath_utf16 != NULL)
		free(temppath_utf16);
	return ret;
}

/* on error returns NULL and sets errno */
char* 
resolved_path_utf8(const char *input_path)
{
	wchar_t *resolved_path_w = resolved_path_utf16(input_path);
	char *resolved_path = NULL;

	if (resolved_path_w) {
		resolved_path = utf16_to_utf8(resolved_path_w);
		free(resolved_path_w);
	}

	return resolved_path;
}

/* on error returns NULL and sets errno */
wchar_t*
resolved_path_utf16(const char *input_path)
{
	wchar_t *resolved_path = NULL;
	char real_path[PATH_MAX];

	if (!input_path) {
		errno = EINVAL;
		return NULL;
	}

	if (realpath(input_path, real_path) == NULL)
		return NULL;

	if (chroot_path) {
		char actual_path[PATH_MAX] = { 0 };
		strcat_s(actual_path, _countof(actual_path), chroot_path);
		strcat_s(actual_path, _countof(actual_path), real_path);
		resolved_path = utf8_to_utf16(actual_path);
	} else {
		if ((strlen(real_path) == 1) && (real_path[0] == '/'))
			resolved_path = utf8_to_utf16(real_path);
		else
			resolved_path = utf8_to_utf16(real_path + 1); /* account for preceding / in real_path */
	}

	return resolved_path;
}

int
statvfs(const char *path, struct statvfs *buf)
{
	DWORD sectorsPerCluster;
	DWORD bytesPerSector;
	DWORD freeClusters;
	DWORD totalClusters;

	wchar_t* path_utf16 = resolved_path_utf16(path);
	if (path_utf16 == NULL)
		return -1;

	if (GetDiskFreeSpaceW(path_utf16, &sectorsPerCluster, &bytesPerSector,
	    &freeClusters, &totalClusters)) {
		debug5("path              : [%s]", path);
		debug5("sectorsPerCluster : [%lu]", sectorsPerCluster);
		debug5("bytesPerSector    : [%lu]", bytesPerSector);
		debug5("bytesPerCluster   : [%lu]", sectorsPerCluster * bytesPerSector);
		debug5("freeClusters      : [%lu]", freeClusters);
		debug5("totalClusters     : [%lu]", totalClusters);

		buf->f_bsize = sectorsPerCluster * bytesPerSector;
		buf->f_frsize = sectorsPerCluster * bytesPerSector;
		buf->f_blocks = totalClusters;
		buf->f_bfree = freeClusters;
		buf->f_bavail = freeClusters;
		buf->f_files = -1;
		buf->f_ffree = -1;
		buf->f_favail = -1;
		buf->f_fsid = 0;
		buf->f_flag = 0;
		buf->f_namemax = PATH_MAX - 1;

		free(path_utf16);
		return 0;
	} else {
		debug5("ERROR: Cannot get free space for [%s]. Error code is : %d.", path, GetLastError());
		errno = errno_from_Win32LastError();
		free(path_utf16);
		return -1;
	}
}

int
fstatvfs(int fd, struct statvfs *buf)
{
	errno = ENOTSUP;
	return -1;
}

char *
w32_strerror(int errnum)
{
	if (errnum >= EADDRINUSE  && errnum <= EWOULDBLOCK)
		return _sys_errlist_ext[errnum - EADDRINUSE];
	
	strerror_s(errorBuf, ERROR_MSG_MAXLEN, errnum);
	return errorBuf;
}

char *
readpassphrase(const char *prompt, char *outBuf, size_t outBufLen, int flags)
{
	int current_index = 0;
	char ch;
	wchar_t* wtmp = NULL;

	if (outBufLen == 0) {
		errno = EINVAL;
		return NULL;
	}

	while (_kbhit()) _getch();

	wtmp = utf8_to_utf16(prompt);
	if (wtmp == NULL)
		fatal("unable to alloc memory");

	_cputws(wtmp);
	free(wtmp);

	while (current_index < (int)outBufLen - 1) {
		ch = _getch();
		
		if (ch == '\r') {
			if (_kbhit()) _getch(); /* read linefeed if its there */
			break;
		} else if (ch == '\n') {
			break;
		} else if (ch == '\b') { /* backspace */
			if (current_index > 0) {
				if (flags & RPP_ECHO_ON)
					printf_s("%c \b", ch);

				current_index--; /* overwrite last character */
			}
		} else if (ch == '\003') { /* exit on Ctrl+C */
			fatal("");
		} else {
			if (flags & RPP_SEVENBIT)
				ch &= 0x7f;

			if (isalpha((unsigned char)ch)) {
				if(flags & RPP_FORCELOWER)
					ch = tolower((unsigned char)ch);
				if(flags & RPP_FORCEUPPER)
					ch = toupper((unsigned char)ch);
			}

			outBuf[current_index++] = ch;
			if(flags & RPP_ECHO_ON)
				printf_s("%c", ch);
		}
	}

	outBuf[current_index] = '\0';
	_cputs("\n");

	return outBuf;
}

void 
invalid_parameter_handler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved)
{	
	debug3("Invalid parameter in function: %ls. File: %ls Line: %d.", function, file, line);
	debug3("Expression: %s", expression);
}

void
to_lower_case(char *s)
{
	for (; *s; s++)
		*s = tolower((u_char)*s);
}

void 
to_wlower_case(wchar_t *s)
{
	for (; *s; s++)
		*s = towlower(*s);
}

static int
get_final_mode(int allow_mode, int deny_mode)
{	
	// If deny permissions are not specified then return allow permissions.
	if (!deny_mode) return allow_mode;

	// If allow permissions are not specified then return allow permissions (0).
	if (!allow_mode) return allow_mode;
	
	if(deny_mode & S_IROTH)
		allow_mode = allow_mode & ~S_IROTH;

	if (deny_mode & S_IWOTH)
		allow_mode = allow_mode & ~S_IWOTH;

	if (deny_mode & S_IXOTH)
		allow_mode = allow_mode & ~S_IXOTH;

	return allow_mode;
}

int
get_others_file_permissions(wchar_t * file_name, int isReadOnlyFile)
{
	PSECURITY_DESCRIPTOR pSD = NULL;
	PSID owner_sid = NULL, current_trustee_sid = NULL;
	PACL dacl = NULL;
	DWORD error_code = ERROR_SUCCESS;
	BOOL is_valid_sid = FALSE, is_valid_acl = FALSE;
	int ret = 0, allow_mode_world = 0, allow_mode_auth_users = 0, deny_mode_world = 0, deny_mode_auth_users = 0;
	wchar_t *w_sid = NULL;

	/*Get the owner sid of the file.*/
	if ((error_code = GetNamedSecurityInfoW(file_name, SE_FILE_OBJECT,
		OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
		&owner_sid, NULL, &dacl, NULL, &pSD)) != ERROR_SUCCESS) {
		debug3("failed to retrieve the owner sid and dacl of file: %ls with error code: %d", file_name, error_code);
		goto cleanup;
	}

	if (((is_valid_sid = IsValidSid(owner_sid)) == FALSE) || dacl == NULL ||
		((is_valid_acl = IsValidAcl(dacl)) == FALSE)) {
		debug3("IsValidSid: %d; NULL Acl: %d; IsValidAcl: %d", is_valid_sid, dacl == NULL, is_valid_acl);
		goto cleanup;
	}

	for (DWORD i = 0; i < dacl->AceCount; i++) {
		PVOID current_ace = NULL;
		PACE_HEADER current_aceHeader = NULL;
		ACCESS_MASK current_access_mask = 0;
		int mode_tmp = 0;
		if (!GetAce(dacl, i, &current_ace)) {
			debug3("GetAce() failed");
			goto cleanup;
		}

		current_aceHeader = (PACE_HEADER)current_ace;
		/* only interested in Allow ACE */
		if (current_aceHeader->AceType == ACCESS_ALLOWED_ACE_TYPE) {
			PACCESS_ALLOWED_ACE pAllowedAce = (PACCESS_ALLOWED_ACE)current_ace;
			current_trustee_sid = &(pAllowedAce->SidStart);
			current_access_mask = pAllowedAce->Mask;
		} else if (current_aceHeader->AceType == ACCESS_DENIED_ACE_TYPE) {
			PACCESS_DENIED_ACE pDeniedAce = (PACCESS_DENIED_ACE)current_ace;
			current_trustee_sid = &(pDeniedAce->SidStart);
			current_access_mask = pDeniedAce->Mask;
		} else continue;
		
		if (!(IsWellKnownSid(current_trustee_sid, WinWorldSid) || 
		    IsWellKnownSid(current_trustee_sid, WinAuthenticatedUserSid)))
			continue;
		
		if ((current_access_mask & READ_PERMISSIONS) == READ_PERMISSIONS)
			mode_tmp |= S_IROTH;

		if (!isReadOnlyFile && ((current_access_mask & WRITE_PERMISSIONS) == WRITE_PERMISSIONS))
			mode_tmp |= S_IWOTH;

		if ((current_access_mask & EXECUTE_PERMISSIONS) == EXECUTE_PERMISSIONS)
			mode_tmp |= S_IXOTH;

		if (IsWellKnownSid(current_trustee_sid, WinWorldSid)) {
			if(current_aceHeader->AceType == ACCESS_ALLOWED_ACE_TYPE)
				allow_mode_world |= mode_tmp;
			else
				deny_mode_world |= mode_tmp;
		} else if (IsWellKnownSid(current_trustee_sid, WinAuthenticatedUserSid)) {
			if (current_aceHeader->AceType == ACCESS_ALLOWED_ACE_TYPE)
				allow_mode_auth_users |= mode_tmp;
			else
				deny_mode_auth_users |= mode_tmp;
		}
	}
	
	allow_mode_world = get_final_mode(allow_mode_world, deny_mode_world);
	allow_mode_auth_users = get_final_mode(allow_mode_auth_users, deny_mode_auth_users);

	ret = allow_mode_world ? allow_mode_world : allow_mode_auth_users;
cleanup:
	if (pSD)
		LocalFree(pSD);
	return ret;
}

/* Windows absolute paths - \abc, /abc, c:\abc, c:/abc, __PROGRAMDATA__\openssh\sshd_config */
int
is_absolute_path(const char *path)
{
	int retVal = 0;
	if(*path == '\"' || *path == '\'') /* skip double quote if path is "c:\abc" */
		path++;

	if (*path == '/' || *path == '\\' || (*path != '\0' && isalpha(*path) && path[1] == ':') ||
	    ((strlen(path) >= strlen(PROGRAM_DATA)) && (memcmp(path, PROGRAM_DATA, strlen(PROGRAM_DATA)) == 0)))
		retVal = 1;

	return retVal;
}

/* return -1 - in case of failure, 0 - success */
int
create_directory_withsddl(wchar_t *path_w, wchar_t *sddl_w)
{
	if (GetFileAttributesW(path_w) == INVALID_FILE_ATTRIBUTES) {
		PSECURITY_DESCRIPTOR pSD = NULL;
		SECURITY_ATTRIBUTES sa;
		memset(&sa, 0, sizeof(SECURITY_ATTRIBUTES));
		sa.nLength = sizeof(SECURITY_ATTRIBUTES);
		sa.bInheritHandle = FALSE;

		if (ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl_w, SDDL_REVISION, &pSD, NULL) == FALSE) {
			error("ConvertStringSecurityDescriptorToSecurityDescriptorW failed with error code %d", GetLastError());
			return -1;
		}

		if (IsValidSecurityDescriptor(pSD) == FALSE) {
			error("IsValidSecurityDescriptor return FALSE");
			return -1;
		}

		sa.lpSecurityDescriptor = pSD;
		if (!CreateDirectoryW(path_w, &sa)) {
			error("Failed to create directory:%ls error:%d", path_w, GetLastError());
			return -1;
		}
	}

	return 0;
}

/* return -1 - in case of failure, 0 - success */
int
copy_file(char *source, char *destination)
{
	if (!source || !destination) return 0;

	struct stat st;
	if ((stat(source, &st) >= 0) && (stat(destination, &st) < 0)) {
		wchar_t *source_w = utf8_to_utf16(source);
		if (!source_w) {
			error("%s utf8_to_utf16() has failed to convert string:%s", __func__, source_w);
			return -1;
		}

		wchar_t *destination_w = utf8_to_utf16(destination);
		if (!destination_w) {
			error("%s utf8_to_utf16() has failed to convert string:%s", __func__, destination_w);
			return -1;
		}

		if (!CopyFileW(source_w, destination_w, FALSE)) {
			error("Failed to copy %ls to %ls, error:%d", source_w, destination_w, GetLastError());
			return -1;
		}
	}

	return 0;
}

struct tm *
localtime_r(const time_t *timep, struct tm *result)
{
	return localtime_s(result, timep) == 0 ? result : NULL;
}

void
freezero(void *ptr, size_t sz)
{
	if (ptr == NULL)
		return;
	explicit_bzero(ptr, sz);
	free(ptr);
}

int 
setenv(const char *name, const char *value, int rewrite)
{
	errno_t result = 0;

	/* If rewrite is 0, then set only if the variable name doesn't already exist in environment */
	if (!rewrite) {
		char *envValue = NULL;
		size_t len = 0;
		_dupenv_s(&envValue, &len, name);

		if (envValue)
			return result; /* return success (as per setenv manpage) */
	}

	if (!(result = _putenv_s(name, value)))
		return 0;
	else {
		error("failed to set the environment variable:%s to value:%s, error:%d", name, value, result);
		errno = result;
		return -1;
	}
}

int
chroot(const char *path)
{
	char cwd[MAX_PATH];

	if (strcmp(path, ".") == 0) {
		if (w32_getcwd(cwd, MAX_PATH) == NULL)
			return -1;
		path = (const char *)cwd;
	} else if (*(path + 1) != ':') {
		errno = ENOTSUP;
		error("chroot only supports absolute paths");
		return -1;
	} else {
		/* TODO - ensure path exists and is a directory */
	}

	if ((chroot_path = _strdup(path)) == NULL) {
		errno = ENOMEM;
		return -1;
	}

	to_lower_case(chroot_path);
	convertToBackslash(chroot_path);

	/* strip trailing \ */
	if (chroot_path[strlen(chroot_path) - 1] == '\\')
		chroot_path[strlen(chroot_path) - 1] = '\0';

	chroot_path_len = (int) strlen(chroot_path);

	if ((chroot_pathw = utf8_to_utf16(chroot_path)) == NULL) {
		errno = ENOMEM;
		return -1;
	}

	/* TODO - set the env variable just in time in a posix_spawn_chroot like API */
#define POSIX_CHROOTW L"c28fc6f98a2c44abbbd89d6a3037d0d9_POSIX_CHROOT"
	_wputenv_s(POSIX_CHROOTW, chroot_pathw);

	return 0;
}

/*
 * Am I running as SYSTEM ?
 * a security sensitive call - fatal exits if it cannot definitively conclude 
 */
int 
am_system()
{
	HANDLE proc_token = NULL;
	DWORD info_len;
	TOKEN_USER* info = NULL;
	static int running_as_system = -1;

	if (running_as_system != -1)
		return running_as_system;

	if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &proc_token) == FALSE ||
		GetTokenInformation(proc_token, TokenUser, NULL, 0, &info_len) == TRUE ||
		(info = (TOKEN_USER*)malloc(info_len)) == NULL ||
		GetTokenInformation(proc_token, TokenUser, info, info_len, &info_len) == FALSE)
		fatal("unable to know if I am running as system");

	if (IsWellKnownSid(info->User.Sid, WinLocalSystemSid))
		running_as_system = 1;
	else
		running_as_system = 0;

	CloseHandle(proc_token);
	free(info);
	return running_as_system;
}

/* 
 * returns SID of user/group or current user if (user = NULL) 
 * caller should free() return value
 */
PSID
get_sid(const char* name)
{
	HANDLE token = NULL;
	TOKEN_USER* info = NULL;
	DWORD info_len = 0;
	PSID ret = NULL, psid = NULL;
	wchar_t* name_utf16 = NULL;

	if (name) {
		DWORD sid_len = 0;
		SID_NAME_USE n_use;
		WCHAR dom[DNLEN + 1] = L"";
		DWORD dom_len = DNLEN + 1;

		if ((name_utf16 = utf8_to_utf16(name)) == NULL)
			goto cleanup;

		LookupAccountNameW(NULL, name_utf16, NULL, &sid_len, dom, &dom_len, &n_use);

		if (sid_len == 0) {
			errno = errno_from_Win32LastError();
			goto cleanup;
		}

		if ((psid = malloc(sid_len)) == NULL) {
			errno = ENOMEM;
			goto cleanup;
		}

		if (!LookupAccountNameW(NULL, name_utf16, psid, &sid_len, dom, &dom_len, &n_use)) {
			errno = errno_from_Win32LastError();
			goto cleanup;
		}
	}
	else {
		if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token) == FALSE ||
		    GetTokenInformation(token, TokenUser, NULL, 0, &info_len) == TRUE) {
			errno = EOTHER;
			goto cleanup;
		}

		if ((info = (TOKEN_USER*)malloc(info_len)) == NULL) {
			errno = ENOMEM;
			goto cleanup;
		}

		if (GetTokenInformation(token, TokenUser, info, info_len, &info_len) == FALSE) {
			errno = errno_from_Win32LastError();
			goto cleanup;
		}

		if ((psid = malloc(GetLengthSid(info->User.Sid))) == NULL) {
			errno = ENOMEM;
			goto cleanup;
		}

		if (!CopySid(GetLengthSid(info->User.Sid), psid, info->User.Sid)) {
			errno = errno_from_Win32LastError();
			goto cleanup;
		}
	}

	ret = psid;
	psid = NULL;
cleanup:

	if (token)
		CloseHandle(token);
	if (name_utf16)
		free(name_utf16);
	if (psid)
		free(psid);
	if (info)
		free(info);

	return ret;
}
/* Interpret scp and sftp executables*/
char *
build_exec_command(const char * command)
{
	enum cmd_type { CMD_OTHER, CMD_SFTP, CMD_SCP } command_type = CMD_OTHER;
	char *cmd_sp = NULL;
	int len = 0, command_len;
	const char *command_args = NULL;

	if (!command)
		return NULL;

	command_len = (int)strlen(command);
	/*TODO - replace numbers below with readable compile time operators*/
	if (command_len >= 13 && _memicmp(command, "internal-sftp", 13) == 0) {
		command_type = CMD_SFTP;
		command_args = command + 13;
	}
	else if (command_len >= 11 && _memicmp(command, "sftp-server", 11) == 0) {
		command_type = CMD_SFTP;

		/* account for possible .exe extension */
		if (command_len >= 15 && _memicmp(command + 11, ".exe", 4) == 0)
			command_args = command + 15;
		else
			command_args = command + 11;
	}
	else if (command_len >= 3 && _memicmp(command, "scp", 3) == 0) {
		command_type = CMD_SCP;

		/* account for possible .exe extension */
		if (command_len >= 7 && _memicmp(command + 3, ".exe", 4) == 0)
			command_args = command + 7;
		else
			command_args = command + 3;
	}

	len = command_len + 5; /* account for possible .exe addition and null term */
	if ((cmd_sp = malloc(len)) == NULL) {
		errno = ENOMEM;
		return NULL;
	}
	memset(cmd_sp, '\0', len);
	if (command_type == CMD_SCP) {
		strcpy_s(cmd_sp, len, "scp.exe");
		strcat_s(cmd_sp, len, command_args);
	}
	else if (command_type == CMD_SFTP) {
		strcpy_s(cmd_sp, len, "sftp-server.exe");
		strcat_s(cmd_sp, len, command_args);
	}
	else
		strcpy_s(cmd_sp, len, command);
	return cmd_sp;
}

/*
 * cmd is internally decoarated with a set of '"'
 * to account for any spaces within the commandline
 * the double quotes and backslash is escaped if needed 
 * this decoration is done only when additional arguments are passed in argv
*/
char *
build_commandline_string(const char* cmd, char *const argv[], BOOLEAN prepend_module_path)
{
	char *cmdline, *t, *tmp = NULL, *path = NULL, *ret = NULL;
	char * const *t1;
	DWORD cmdline_len = 0, path_len = 0;
	int add_module_path = 0;

	if (!cmd) {
		error("%s invalid argument cmd:%s", __func__, cmd);
		return NULL;
	}

	if (!(path = _strdup(cmd))) {
		error("failed to duplicate %s", cmd);
		return NULL;
	}

	path_len = (DWORD)strlen(path);

	if (is_bash_test_env()) {
		memset(path, 0, path_len + 1);
		bash_to_win_path(cmd, path, path_len + 1);
	}

	if (!is_absolute_path(path) && prepend_module_path)
		add_module_path = 1;

	/* compute total cmdline len*/
	if (add_module_path)
		cmdline_len += (DWORD)strlen(__progdir) + 1 + (DWORD)strlen(path) + 1 + 2;
	else
		cmdline_len += (DWORD)strlen(path) + 1 + 2;

	if (argv) {
		t1 = argv;
		while (*t1) {
			char *p = *t1++;
			for (int i = 0; i < (int)strlen(p); i++) {
				if (p[i] == '\\') {
					char * b = p + i;
					int additional_backslash = 0;
					int backslash_count = 0;
					/*
					Backslashes are interpreted literally, unless they immediately
					precede a double quotation mark.
					*/
					while (b != NULL && *b == '\\') {
						backslash_count++;
						b++;
						if (b != NULL &&  *b == '\"') {
							additional_backslash = 1;
							break;
						}
					}
					cmdline_len += backslash_count * (additional_backslash + 1);
					i += backslash_count - 1;
				}
				else if (p[i] == '\"')
					/* backslash will be added for every double quote.*/
					cmdline_len += 2;
				else
					cmdline_len++;
			}
			cmdline_len += 1 + 2; /*for "around cmd arg and traling space*/
		}
	}

	if ((cmdline = malloc(cmdline_len)) == NULL) {
		errno = ENOMEM;
		goto cleanup;
	}
	t = cmdline;
	*t++ = '\"';
	if (add_module_path) {
		/* add current module path to start if needed */
		memcpy(t, __progdir, strlen(__progdir));
		t += strlen(__progdir);
		*t++ = '\\';
	}
	if (path[0] != '\"') {
		memcpy(t, path, path_len);
		t += path_len;
		*t++ = '\"';
	}
	else {
		/*path already contains "*/
		memcpy(t, path + 1, path_len - 1);
		t += path_len - 1;
	}

	*t = '\0';
	t = cmdline + strlen(cmdline);

	if (argv) {
		t1 = argv;
		while (*t1) {
			*t++ = ' ';
			char * p1 = *t1++;
			BOOL add_quotes = FALSE;
			/* leave as is if the command is surrounded by single quotes*/
			if (p1[0] != '\'')
				for (int i = 0; i < (int)strlen(p1); i++) {
					if (p1[i] == ' ') {
						add_quotes = TRUE;
						break;
					}
				}
			if (add_quotes)
				*t++ = '\"';
			for (int i = 0; i < (int)strlen(p1); i++) {
				if (p1[i] == '\\') {
					char * b = p1 + i;
					int additional_backslash = 0;
					int backslash_count = 0;
					/*
					* Backslashes are interpreted literally, unless they immediately
					* precede a double quotation mark.
					*/
					while (b != NULL && *b == '\\') {
						backslash_count++;
						b++;
						if (b != NULL && *b == '\"') {
							additional_backslash = 1;
							break;
						}
					}
					i += backslash_count - 1;
					int escaped_backslash_count = backslash_count * (additional_backslash + 1);
					while (escaped_backslash_count--)
						*t++ = '\\';
				}
				else if (p1[i] == '\"') {
					/* Add backslash for every double quote.*/
					*t++ = '\\';
					*t++ = '\"';
				}
				else
					*t++ = p1[i];
			}
			if (add_quotes)
				*t++ = '\"';
		}
	}
	*t = '\0';
	ret = cmdline;
	cmdline = NULL;
cleanup:
	if (path)
		free(path);
	if (cmdline)
		free(cmdline);
	return ret;
}
BOOL
is_bash_test_env()
{
	char *envValue = NULL;
	size_t len = 0;
	BOOL retVal = FALSE;
	_dupenv_s(&envValue, &len, "SSH_TEST_ENVIRONMENT");

	if ((NULL != envValue) && atoi(envValue))
		retVal = TRUE;

	if (envValue)
		free(envValue);

	return retVal;
}

int
bash_to_win_path(const char *in, char *out, const size_t out_len)
{
	int retVal = 0;
	const size_t cygwin_path_prefix_len = strlen(CYGWIN_PATH_PREFIX);
	memset(out, 0, out_len);
	if (_strnicmp(in, CYGWIN_PATH_PREFIX, cygwin_path_prefix_len) == 0) {
		out[0] = in[cygwin_path_prefix_len];
		out[1] = ':';
		strcat_s(out, out_len, &in[cygwin_path_prefix_len + 1]);
		retVal = 1;
	} else
		strcpy_s(out, out_len, in);

	return retVal;
}

int
getpeereid(int s, uid_t *euid, gid_t *egid)
{
	verbose("%s is not supported", __func__);
	errno = ENOTSUP;
	return -1;
}

int
getrrsetbyname(const char *hostname, unsigned int rdclass,
	unsigned int rdtype, unsigned int flags,
	struct rrsetinfo **res)
{
	verbose("%s is not supported", __func__);
	errno = ENOTSUP;
	return -1;
}

int 
fnmatch(const char *pattern, const char *string, int flags)
{
	int r = -1;
	wchar_t *pw = NULL, *sw = NULL;

	if (flags) {
		verbose("%s is not supported with flags", __func__);
		goto done;
	}

	pw = utf8_to_utf16(pattern);
	sw = utf8_to_utf16(string);
	if (!pw || !sw)
		goto done;
	convertToBackslashW(pw);
	convertToBackslashW(sw);
	if (PathMatchSpecW(sw, pw))
		r = 0;
done:
	if (pw)
		free(pw);
	if (sw)
		free(sw);
	return r;
}

void
freerrset(struct rrsetinfo *rrset)
{
	verbose("%s is not supported", __func__);
	return;
}

void
debug_assert_internal()
{
	/* debug break on non-release builds */
#ifndef NDEBUG
	DebugBreak();
#endif
}

char
*crypt(const char *key, const char *salt)
{
	verbose("%s is not supported", __func__);
	errno = ENOTSUP;
	return NULL;
}

int
w32_system(const char *command)
{
	int ret = -1;
	wchar_t *command_w = NULL;

	if (!command) {
		errno = ENOTSUP;
		goto cleanup;
	}

	if ((command_w = utf8_to_utf16(command)) == NULL)
		goto cleanup;

	ret = _wsystem(command_w);

cleanup:
	if (command_w)
		free(command_w);

	return ret;
}