From bd6408058ab1beee37cdd484255d39847dc9028b Mon Sep 17 00:00:00 2001
From: Bryan Berns <berns@uwalumni.com>
Date: Sun, 1 Apr 2018 23:41:09 -0400
Subject: [PATCH 1/5] Reworked Path Resolution Function

- Reworked resolved_path() into resolved_path_utf16() that combined utf16 conversion and path conditioning into a single function.  This eliminated the previously non-threadsafe resolved_path() function.
- Adjusted functions to use resolved_path_utf16().
- Collapsed copy_file() function that was only used once.
- Corrected compilation errors when debug4() and debug5() are enabled.
---
 contrib/win32/win32compat/fileio.c            |  16 +-
 contrib/win32/win32compat/misc.c              | 215 +++++++-----------
 contrib/win32/win32compat/misc_internal.h     |  11 +-
 contrib/win32/win32compat/termio.c            |   4 +-
 contrib/win32/win32compat/w32-sshfileperm.c   |  10 +-
 contrib/win32/win32compat/w32fd.c             |   2 +-
 contrib/win32/win32compat/w32log.c            |  10 +-
 contrib/win32/win32compat/win32_dirent.c      |   2 +-
 contrib/win32/win32compat/wmain_sshd.c        |  36 ++-
 .../win32compat/miscellaneous_tests.c         |  33 +--
 10 files changed, 148 insertions(+), 191 deletions(-)

diff --git a/contrib/win32/win32compat/fileio.c b/contrib/win32/win32compat/fileio.c
index 6be260070..5e49bda5a 100644
--- a/contrib/win32/win32compat/fileio.c
+++ b/contrib/win32/win32compat/fileio.c
@@ -439,10 +439,10 @@ fileio_open(const char *path_utf8, int flags, mode_t mode)
 	}
 
 	/* if opening null device, point to Windows equivalent */
-	if (strncmp(path_utf8, NULL_DEVICE, strlen(NULL_DEVICE)+1) == 0) 
-		path_utf8 = "NUL";
+	if (strcmp(path_utf8, NULL_DEVICE) == 0) 
+		path_utf8 = NULL_DEVICE_WIN;
 
-	if ((path_utf16 = utf8_to_utf16(path_utf8)) == NULL) {
+	if ((path_utf16 = resolved_path_utf16(path_utf8)) == NULL) {
 		errno = ENOMEM;
 		debug3("utf8_to_utf16 failed for file:%s error:%d", path_utf8, GetLastError());
 		return NULL;
@@ -774,7 +774,7 @@ fileio_stat_or_lstat_internal(const char *path, struct _stat64 *buf, int do_lsta
 		return 0;
 	}
 
-	if ((wpath = utf8_to_utf16(path)) == NULL) {
+	if ((wpath = resolved_path_utf16(path)) == NULL) {
 		errno = ENOMEM;
 		debug3("utf8_to_utf16 failed for file:%s error:%d", path, GetLastError());
 		return -1;
@@ -1013,8 +1013,6 @@ fileio_readlink(const char *path, char *buf, size_t bufsiz)
 	 * for more info: https://msdn.microsoft.com/en-us/library/cc232006.aspx
 	 */
 
-	debug4("readlink - io:%p", pio);
-
 	typedef struct _REPARSE_DATA_BUFFER_SYMLINK {
 		ULONG ReparseTag;
 		USHORT ReparseDataLength;
@@ -1041,7 +1039,7 @@ fileio_readlink(const char *path, char *buf, size_t bufsiz)
 		goto cleanup;
 	}
 
-	if ((wpath = utf8_to_utf16(path)) == NULL) {
+	if ((wpath = resolved_path_utf16(path)) == NULL) {
 		errno = ENOMEM;
 		goto cleanup;
 	}
@@ -1131,8 +1129,8 @@ fileio_symlink(const char *target, const char *linkpath)
 	}
 
 	DWORD ret = 0;
-	wchar_t *target_utf16 = utf8_to_utf16(resolved_path(target));
-	wchar_t *linkpath_utf16 = utf8_to_utf16(resolved_path(linkpath));
+	wchar_t *target_utf16 = resolved_path_utf16(target);
+	wchar_t *linkpath_utf16 = resolved_path_utf16(linkpath);
 	wchar_t *resolved_utf16 = _wcsdup(target_utf16);
 	if (target_utf16 == NULL || linkpath_utf16 == NULL || resolved_utf16 == NULL) {
 		errno = ENOMEM;
diff --git a/contrib/win32/win32compat/misc.c b/contrib/win32/win32compat/misc.c
index 976267698..7878b9604 100644
--- a/contrib/win32/win32compat/misc.c
+++ b/contrib/win32/win32compat/misc.c
@@ -229,15 +229,14 @@ dlsym(HMODULE handle, const char *symbol)
 FILE *
 w32_fopen_utf8(const char *input_path, const char *mode)
 {
-	wchar_t wpath[PATH_MAX], wmode[5];
-	FILE* f;
+	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;	
-	char *path = NULL;
+	errno_t r = 0;
 
-	if (mode[1] != '\0') {
+	if (mode == NULL || mode[1] != '\0') {
 		errno = ENOTSUP;
 		return NULL;
 	}
@@ -248,28 +247,21 @@ w32_fopen_utf8(const char *input_path, const char *mode)
 		return NULL; 
 	}
 
-	path = resolved_path(input_path);
-
 	/* if opening null device, point to Windows equivalent */
-	if (0 == strncmp(path, NULL_DEVICE, strlen(NULL_DEVICE)+1)) {
-		if ((r = wcsncpy_s(wpath, PATH_MAX, L"NUL", 3)) != 0) {
-			debug3("wcsncpy_s failed with error: %d.", r);
-			return NULL;
-		}
-	}
-	else
-		status = MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, PATH_MAX);
+	if (strcmp(input_path, NULL_DEVICE) == 0)
+		input_path = NULL_DEVICE_WIN;
 
-	if ((0 == status) ||
-	    (0 == MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, 5))) {
-		errno = EFAULT;
-		debug3("WideCharToMultiByte failed for %c - ERROR:%d", path, GetLastError());
-		return NULL;
+	wpath = resolved_path_utf16(input_path);
+	wmode = utf8_to_utf16(mode);
+	if (wpath == NULL || wmode == NULL)
+	{
+		errno = ENOMEM;
+		goto cleanup;
 	}
 
 	if ((_wfopen_s(&f, wpath, wmode) != 0) || (f == NULL)) {
-		debug3("Failed to open file:%s error:%d", path, errno);
-		return NULL;
+		debug3("Failed to open file:%S error:%d", wpath, errno);
+		goto cleanup;
 	}		
 
 	/* BOM adjustments for file streams*/
@@ -277,7 +269,7 @@ w32_fopen_utf8(const char *input_path, const char *mode)
 		/* write UTF-8 BOM - should we ?*/
 		/*if (fwrite(utf8_bom, sizeof(utf8_bom), 1, f) != 1) {
 			fclose(f);
-			return NULL;
+			goto cleanup;
 		}*/
 
 	} else if (mode[0] == 'r' && fseek(f, 0, SEEK_SET) != EBADF) {
@@ -288,6 +280,13 @@ w32_fopen_utf8(const char *input_path, const char *mode)
 		}
 	}
 
+cleanup:
+
+	if (wpath) 
+		free(wpath);
+	if (wmode)
+		free(wmode);
+
 	return f;
 }
 
@@ -511,7 +510,7 @@ int
 w32_chmod(const char *pathname, mode_t mode)
 {
 	int ret;
-	wchar_t *resolvedPathName_utf16 = utf8_to_utf16(resolved_path(pathname));
+	wchar_t *resolvedPathName_utf16 = resolved_path_utf16(pathname);
 	if (resolvedPathName_utf16 == NULL) {
 		errno = ENOMEM;
 		return -1;
@@ -639,7 +638,7 @@ w32_utimes(const char *filename, struct timeval *tvp)
 {
 	int ret;
 	FILETIME acttime, modtime;
-	wchar_t *resolvedPathName_utf16 = utf8_to_utf16(resolved_path(filename));
+	wchar_t *resolvedPathName_utf16 = resolved_path_utf16(filename);
 	if (resolvedPathName_utf16 == NULL) {
 		errno = ENOMEM;
 		return -1;
@@ -671,19 +670,13 @@ link(const char *oldpath, const char *newpath)
 int
 w32_rename(const char *old_name, const char *new_name)
 {
-	char old_name_resolved[PATH_MAX] = {0, };
-	char new_name_resolved[PATH_MAX] = {0, };
-
 	if (old_name == NULL || new_name == NULL) {
 		errno = EFAULT;
 		return -1;
 	}
 
-	strcpy_s(old_name_resolved, _countof(old_name_resolved), resolved_path(old_name));
-	strcpy_s(new_name_resolved, _countof(new_name_resolved), resolved_path(new_name));
-
-	wchar_t *resolvedOldPathName_utf16 = utf8_to_utf16(old_name_resolved);
-	wchar_t *resolvedNewPathName_utf16 = utf8_to_utf16(new_name_resolved);
+	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) {
 		errno = ENOMEM;
@@ -695,18 +688,18 @@ w32_rename(const char *old_name, const char *new_name)
 	 * 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;
-	if (fileio_stat(resolved_path(new_name_resolved), &st) != -1) {
+	struct w32_stat st;
+	if (w32_stat(new_name, &st) != -1) {
 		if (((st.st_mode & _S_IFMT) == _S_IFREG))
-			w32_unlink(new_name_resolved);
+			w32_unlink(new_name);
 		else {
-			DIR *dirp = opendir(new_name_resolved);
+			DIR *dirp = opendir(new_name);
 			if (NULL != dirp) {
 				struct dirent *dp = readdir(dirp);
 				closedir(dirp);
 
 				if (dp == NULL)
-					w32_rmdir(new_name_resolved);
+					w32_rmdir(new_name);
 			}
 		}
 	}
@@ -721,7 +714,7 @@ w32_rename(const char *old_name, const char *new_name)
 int
 w32_unlink(const char *path)
 {
-	wchar_t *resolvedPathName_utf16 = utf8_to_utf16(resolved_path(path));
+	wchar_t *resolvedPathName_utf16 = resolved_path_utf16(path);
 	if (NULL == resolvedPathName_utf16) {
 		errno = ENOMEM;
 		return -1;
@@ -736,7 +729,7 @@ w32_unlink(const char *path)
 int
 w32_rmdir(const char *path)
 {
-	wchar_t *resolvedPathName_utf16 = utf8_to_utf16(resolved_path(path));
+	wchar_t *resolvedPathName_utf16 = resolved_path_utf16(path);
 	if (NULL == resolvedPathName_utf16) {
 		errno = ENOMEM;
 		return -1;
@@ -796,7 +789,7 @@ int
 w32_mkdir(const char *path_utf8, unsigned short mode)
 {
 	int curmask;
-	wchar_t *path_utf16 = utf8_to_utf16(resolved_path(path_utf8));
+	wchar_t *path_utf16 = resolved_path_utf16(path_utf8);
 	if (path_utf16 == NULL) {
 		errno = ENOMEM;
 		return -1;
@@ -820,20 +813,20 @@ w32_mkdir(const char *path_utf8, unsigned short mode)
 int
 w32_stat(const char *input_path, struct w32_stat *buf)
 {
-	return fileio_stat(resolved_path(input_path), (struct _stat64*)buf);
+	return fileio_stat(input_path, (struct _stat64*)buf);
 }
 
 int
 w32_lstat(const char *input_path, struct w32_stat *buf)
 {
-	return fileio_lstat(resolved_path(input_path), (struct _stat64*)buf);
+	return fileio_lstat(input_path, (struct _stat64*)buf);
 }
 
 /* if file is symbolic link, copy its link into "link" */
 int
 readlink(const char *path, char *link, int linklen)
 {
-	return fileio_readlink(resolved_path(path), link, linklen);
+	return fileio_readlink(path, link, linklen);
 }
 
 /* convert forward slash to back slash */
@@ -915,44 +908,55 @@ realpath(const char *path, char resolved[PATH_MAX])
 	return resolved;
 }
 
-/* This function is not thread safe. 
-* TODO - It uses static memory. Is this a good design?
-*/
-char*
-resolved_path(const char *input_path)
+wchar_t*
+resolved_path_utf16(const char *input_path)
 {
-	static char resolved_path[PATH_MAX] = {0,};
-	static char newPath[PATH_MAX] = { '\0', };
-	errno_t r = 0;
-
 	if (!input_path) return NULL;
 
-	/* If filename contains __PROGRAMDATA__ then expand it to %programData% and return the resolved path */
-	if ((strlen(input_path) >= strlen(PROGRAM_DATA)) && (memcmp(input_path, PROGRAM_DATA, strlen(PROGRAM_DATA)) == 0)) {
-		resolved_path[0] = '\0';
-		strcat_s(resolved_path, _countof(resolved_path), get_program_data_path());
-		strcat_s(resolved_path, _countof(resolved_path), &input_path[strlen(PROGRAM_DATA)]);
+	wchar_t * resolved_path = utf8_to_utf16(input_path);
+	if (resolved_path == NULL)
+		return NULL;
 
-		return resolved_path; /* return here as its doesn't start with "/" */
+	int resolved_len = (int) wcslen(resolved_path);
+	const int variable_len = (int) wcslen(PROGRAM_DATAW);
+
+	/* search for program data flag and switch it with the real path */
+	if (_wcsnicmp(resolved_path, PROGRAM_DATAW, variable_len) == 0) {
+		wchar_t * program_data = get_program_data_path();
+		const int programdata_len = (int) wcslen(program_data);
+		const int changed_req = programdata_len - variable_len;
+
+		/* allocate more memory if required */
+		if (changed_req > 0) {
+			wchar_t * resolved_path_new = realloc(resolved_path, 
+				(resolved_len + changed_req + 1) * sizeof(wchar_t));
+			if (resolved_path == NULL) {
+				free(resolved_path);
+				return NULL;
+			}
+			else resolved_path = resolved_path_new;
+		}
+
+		/* shift memory contents over based on side of the new string */
+		memmove(resolved_path + variable_len + changed_req, resolved_path + variable_len, 
+			(resolved_len - variable_len + 1) * sizeof(wchar_t));
+		memcpy(resolved_path, program_data, programdata_len * sizeof(wchar_t));
+		resolved_len += changed_req;
 	}
 
-	strcpy_s(resolved_path, _countof(resolved_path), input_path);
-	if (resolved_path[0] == '/' && resolved_path[1]) {
-		if (resolved_path[2] == ':') {
-			if (resolved_path[3] == '\0') { 
-				/* make "/x:" as "x:\\" */
-				resolved_path[0] = resolved_path[1];
-				resolved_path[1] = resolved_path[2];
-				resolved_path[2] = '\\';
-				resolved_path[3] = '\0';
+	if (resolved_path[0] == L'/' && iswalpha(resolved_path[1]) && resolved_path[2] == L':') {
 
-				return resolved_path;
-			} else
-				return (char *)(resolved_path + 1); /* skip the first "/" */
+		/* shift memory to remove forward slash including null terminator */
+		memmove(resolved_path, resolved_path + 1, (resolved_len + 1 - 1) * sizeof(wchar_t));
+
+		/* if just a drive letter path, make x: into x:\ */
+		if (resolved_path[2] == L'\0') {
+			resolved_path[2] = L'\\';
+			resolved_path[3] = L'\0';
 		}
 	}
 
-	return (char *)resolved_path;
+	return resolved_path;
 }
 
 int
@@ -963,7 +967,7 @@ statvfs(const char *path, struct statvfs *buf)
 	DWORD freeClusters;
 	DWORD totalClusters;
 
-	wchar_t* path_utf16 = utf8_to_utf16(resolved_path(path));
+	wchar_t* path_utf16 = resolved_path_utf16(path);
 	if (path_utf16 && (GetDiskFreeSpaceW(path_utf16, &sectorsPerCluster, &bytesPerSector,
 	    &freeClusters, &totalClusters) == TRUE)) {
 		debug5("path              : [%s]", path);
@@ -1433,23 +1437,19 @@ cleanup:
 	return ret;
 }
 
-char*
+wchar_t*
 get_program_data_path()
 {
-	if (ssh_cfg_dir_path) return ssh_cfg_dir_path;
+	static wchar_t ssh_cfg_dir_path_w[PATH_MAX] = L"";
+	if (wcslen(ssh_cfg_dir_path_w) > 0) return ssh_cfg_dir_path_w;
 
-	wchar_t ssh_cfg_dir_path_w[PATH_MAX] = {0, };
-	int return_val = ExpandEnvironmentStringsW(L"%programData%", ssh_cfg_dir_path_w, PATH_MAX);
+	int return_val = ExpandEnvironmentStringsW(L"%ProgramData%", ssh_cfg_dir_path_w, PATH_MAX);
 	if (return_val > PATH_MAX)
-		fatal("%s, buffer too small to expand:%s", __func__, "%programData%");
+		fatal("%s, buffer too small to expand:%s", __func__, "%ProgramData%");
 	else if (!return_val)
-		fatal("%s, failed to expand:%s error:%s", __func__, "%programData%", GetLastError());
-	
-	ssh_cfg_dir_path = utf16_to_utf8(ssh_cfg_dir_path_w);
-	if(!ssh_cfg_dir_path)
-		fatal("%s utf16_to_utf8 failed", __func__);
+		fatal("%s, failed to expand:%s error:%s", __func__, "%ProgramData%", GetLastError());
 
-	return ssh_cfg_dir_path;
+	return ssh_cfg_dir_path_w;
 }
 
 /* Windows absolute paths - \abc, /abc, c:\abc, c:/abc, __PROGRAMDATA__\openssh\sshd_config */
@@ -1469,28 +1469,15 @@ is_absolute_path(const char *path)
 
 /* return -1 - in case of failure, 0 - success */
 int
-create_directory_withsddl(char *path, char *sddl)
+create_directory_withsddl(wchar_t *path_w, wchar_t *sddl_w)
 {
-	struct stat st;
-	if (stat(path, &st) < 0) {
+	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;
 
-		wchar_t *path_w = utf8_to_utf16(path);
-		if (!path_w) {
-			error("%s utf8_to_utf16() has failed to convert string:%s", __func__, path);
-			return -1;
-		}
-
-		wchar_t *sddl_w = utf8_to_utf16(sddl);
-		if (!sddl_w) {
-			error("%s utf8_to_utf16() has failed to convert string:%s", __func__, sddl);
-			return -1;
-		}
-
 		if (ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl_w, SDDL_REVISION, &pSD, NULL) == FALSE) {
 			error("ConvertStringSecurityDescriptorToSecurityDescriptorW failed with error code %d", GetLastError());
 			return -1;
@@ -1510,33 +1497,3 @@ create_directory_withsddl(char *path, char *sddl)
 
 	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;
-}
-
diff --git a/contrib/win32/win32compat/misc_internal.h b/contrib/win32/win32compat/misc_internal.h
index d3d1ef881..ef7a563d7 100644
--- a/contrib/win32/win32compat/misc_internal.h
+++ b/contrib/win32/win32compat/misc_internal.h
@@ -9,21 +9,23 @@
         goto cleanup;                       \
     }                                       \
 } while(0)
+
 #define NULL_DEVICE "/dev/null"
+#define NULL_DEVICE_WIN "NUL"
 
 #define IsWin7OrLess() (!IsWindows8OrGreater())
 
 #define IS_INVALID_HANDLE(h) ( ((NULL == h) || (INVALID_HANDLE_VALUE == h)) ? 1 : 0 )
 #define IS_VALID_HANDLE(h) (!IS_INVALID_HANDLE(h))
 #define PROGRAM_DATA "__PROGRAMDATA__"
+#define PROGRAM_DATAW L"__PROGRAMDATA__"
 
 #define errno_from_Win32LastError() errno_from_Win32Error(GetLastError())
 
 static char *machine_domain_name;
-static char *ssh_cfg_dir_path = NULL;
 
 /* removes first '/' for Windows paths that are unix styled. Ex: /c:/ab.cd */
-char * resolved_path(const char *);
+wchar_t * resolved_path_utf16(const char *);
 void w32posix_initialize();
 void w32posix_done();
 char* w32_programdir();
@@ -37,9 +39,8 @@ int file_attr_to_st_mode(wchar_t * path, DWORD attributes);
 void invalid_parameter_handler(const wchar_t *, const wchar_t *, const wchar_t *, unsigned int, uintptr_t);
 void to_lower_case(char *s);
 int get_machine_domain_name(wchar_t *domain, int size);
-char* get_program_data_path();
+wchar_t* get_program_data_path();
 HANDLE get_user_token(char* user);
 int load_user_profile(HANDLE user_token, char* user);
-int copy_file(char *source, char *destination);
-int create_directory_withsddl(char *path, char *sddl);
+int create_directory_withsddl(wchar_t *path, wchar_t *sddl);
 int is_absolute_path(const char *);
\ No newline at end of file
diff --git a/contrib/win32/win32compat/termio.c b/contrib/win32/win32compat/termio.c
index 35b8a1723..fa662149e 100644
--- a/contrib/win32/win32compat/termio.c
+++ b/contrib/win32/win32compat/termio.c
@@ -57,7 +57,7 @@ static VOID CALLBACK
 ReadAPCProc(_In_ ULONG_PTR dwParam)
 {
 	struct w32_io* pio = (struct w32_io*)dwParam;
-	debug5("TermRead CB - io:%p, bytes: %d, pending: %d, error: %d", pio, read_status.transferred,
+	debug5("TermRead CB - io:%p, bytes: %d, pending: %d, error: %d", pio, pio->read_details.completed,
 		pio->read_details.pending, pio->sync_read_status.error);
 	pio->read_details.error = pio->sync_read_status.error;
 	pio->read_details.remaining = pio->sync_read_status.transferred;
@@ -171,7 +171,7 @@ static VOID CALLBACK
 WriteAPCProc(_In_ ULONG_PTR dwParam)
 {
 	struct w32_io* pio = (struct w32_io*)dwParam;
-	debug5("TermWrite CB - io:%p, bytes: %d, pending: %d, error: %d", pio, write_status.transferred,
+	debug5("TermWrite CB - io:%p, bytes: %d, pending: %d, error: %d", pio, pio->write_details.completed,
 		pio->write_details.pending, pio->sync_write_status.error);
 	pio->write_details.error = pio->sync_write_status.error;
 	pio->write_details.remaining -= pio->sync_write_status.transferred;
diff --git a/contrib/win32/win32compat/w32-sshfileperm.c b/contrib/win32/win32compat/w32-sshfileperm.c
index 652063e6f..2dda4adf4 100644
--- a/contrib/win32/win32compat/w32-sshfileperm.c
+++ b/contrib/win32/win32compat/w32-sshfileperm.c
@@ -57,7 +57,6 @@ check_secure_file_permission(const char *input_path, struct passwd * pw)
 	struct passwd * pwd = pw;
 	char *bad_user = NULL;
 	int ret = 0;
-	char *path = NULL;
 
 	if (pwd == NULL)
 		if ((pwd = getpwuid(0)) == NULL) 
@@ -70,8 +69,7 @@ check_secure_file_permission(const char *input_path, struct passwd * pw)
 		goto cleanup;
 	}
 
-	path = resolved_path(input_path);
-	if ((path_utf16 = utf8_to_utf16(path)) == NULL) {
+	if ((path_utf16 = resolved_path_utf16(input_path)) == NULL) {
 		ret = -1;
 		errno = ENOMEM;
 		goto cleanup;
@@ -81,7 +79,7 @@ check_secure_file_permission(const char *input_path, struct passwd * pw)
 	if ((error_code = GetNamedSecurityInfoW(path_utf16, 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 %s with error code: %d", path, error_code);
+		debug3("failed to retrieve the owner sid and dacl of file %S with error code: %d", path_utf16, error_code);
 		errno = EOTHER;
 		ret = -1;
 		goto cleanup;
@@ -94,7 +92,7 @@ check_secure_file_permission(const char *input_path, struct passwd * pw)
 	if (!IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) &&
 		!IsWellKnownSid(owner_sid, WinLocalSystemSid) &&
 		!EqualSid(owner_sid, user_sid)) {
-		debug3("Bad owner on %s", path);
+		debug3("Bad owner on %S", path_utf16);
 		ret = -1;
 		goto cleanup;
 	}
@@ -136,7 +134,7 @@ check_secure_file_permission(const char *input_path, struct passwd * pw)
 				debug3("ConvertSidToSidString failed with %d. ", GetLastError());
 				break;
 			}
-			debug3("Bad permissions. Try removing permissions for user: %s on file %s.", bad_user, path);
+			debug3("Bad permissions. Try removing permissions for user: %s on file %S.", bad_user, path_utf16);
 			break;
 		}
 	}	
diff --git a/contrib/win32/win32compat/w32fd.c b/contrib/win32/win32compat/w32fd.c
index 65757d542..371e58676 100644
--- a/contrib/win32/win32compat/w32fd.c
+++ b/contrib/win32/win32compat/w32fd.c
@@ -474,7 +474,7 @@ w32_open(const char *pathname, int flags, ... /* arg */)
 		va_end(valist);
 	}
 
-	pio = fileio_open(resolved_path(pathname), flags, mode);
+	pio = fileio_open(pathname, flags, mode);
 	
 	if (pio == NULL)
 		return -1;
diff --git a/contrib/win32/win32compat/w32log.c b/contrib/win32/win32compat/w32log.c
index acd0b9898..a0cb6f271 100644
--- a/contrib/win32/win32compat/w32log.c
+++ b/contrib/win32/win32compat/w32log.c
@@ -110,13 +110,11 @@ openlog_file()
 		while (tail > module_path && *tail != L'\\' && *tail != L'/')
 			tail--;
 		
-		char ssh_cfg_path[PATH_MAX] = {0 ,};
-		strcat_s(ssh_cfg_path, _countof(ssh_cfg_path), get_program_data_path()); /* "%programData%" */
-		strcat_s(ssh_cfg_path, _countof(ssh_cfg_path), "\\ssh"); /* "%programData%\\ssh" */
+		wchar_t ssh_root_path[PATH_MAX] = {0 ,};
+		wcscat_s(ssh_root_path, _countof(ssh_root_path), get_program_data_path()); /* "%programData%" */
+		wcscat_s(ssh_root_path, _countof(ssh_root_path), L"\\ssh"); /* "%programData%\\ssh" */
 
-		wchar_t* ssh_root_path_w = utf8_to_utf16(ssh_cfg_path); /* "%programData%\\ssh" */
-
-		if ((wcsncat_s(log_file, PATH_MAX + 12, ssh_root_path_w, wcslen(ssh_root_path_w)) != 0) ||
+		if ((wcsncat_s(log_file, PATH_MAX + 12, ssh_root_path, wcslen(ssh_root_path)) != 0) ||
 		    (wcsncat_s(log_file, PATH_MAX + 12, logs_dir, 6) != 0) ||
 		    (wcsncat_s(log_file, PATH_MAX + 12, tail + 1, wcslen(tail + 1) - 3) != 0 ) ||
 		    (wcsncat_s(log_file, PATH_MAX + 12, L"log", 3) != 0))
diff --git a/contrib/win32/win32compat/win32_dirent.c b/contrib/win32/win32compat/win32_dirent.c
index f2a5796bb..e04125311 100644
--- a/contrib/win32/win32compat/win32_dirent.c
+++ b/contrib/win32/win32compat/win32_dirent.c
@@ -108,7 +108,7 @@ opendir(const char *name)
 	if (name && strcmp(name, "/") == 0)
 		return openrootdir(name);
 
-	if ((wname = utf8_to_utf16(resolved_path(name))) == NULL) {
+	if ((wname = resolved_path_utf16(name)) == NULL) {
 		errno = ENOMEM;
 		return NULL;
 	}
diff --git a/contrib/win32/win32compat/wmain_sshd.c b/contrib/win32/win32compat/wmain_sshd.c
index 1072839c8..8a14c705d 100644
--- a/contrib/win32/win32compat/wmain_sshd.c
+++ b/contrib/win32/win32compat/wmain_sshd.c
@@ -162,34 +162,32 @@ static void
 create_prgdata_ssh_folder()
 {
 	/* create ssh cfg folder */
-	char ssh_cfg_dir[PATH_MAX] = { 0, };
-	strcpy_s(ssh_cfg_dir, _countof(ssh_cfg_dir), get_program_data_path());
-	strcat_s(ssh_cfg_dir, _countof(ssh_cfg_dir), "\\ssh");
-	if (create_directory_withsddl(ssh_cfg_dir, "O:BAD:PAI(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;0x1200a9;;;AU)") < 0) {
+	wchar_t ssh_cfg_dir[PATH_MAX] = { 0, };
+	wcscpy_s(ssh_cfg_dir, _countof(ssh_cfg_dir), get_program_data_path());
+	wcscat_s(ssh_cfg_dir, _countof(ssh_cfg_dir), L"\\ssh");
+	if (create_directory_withsddl(ssh_cfg_dir, L"O:BAD:PAI(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;0x1200a9;;;AU)") < 0) {
 		printf("failed to create %s", ssh_cfg_dir);
 		exit(255);
 	}
 
 	/* create logs folder */
-	char logs_dir[PATH_MAX] = { 0, };
-	strcat_s(logs_dir, _countof(logs_dir), ssh_cfg_dir);
-	strcat_s(logs_dir, _countof(logs_dir), "\\logs");
-	if (create_directory_withsddl(logs_dir, "O:BAD:PAI(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)") < 0) {
+	wchar_t logs_dir[PATH_MAX] = { 0, };
+	wcscat_s(logs_dir, _countof(logs_dir), ssh_cfg_dir);
+	wcscat_s(logs_dir, _countof(logs_dir), L"\\logs");
+	if (create_directory_withsddl(logs_dir, L"O:BAD:PAI(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)") < 0) {
 		printf("failed to create %s", logs_dir);
 		exit(255);
 	}
 
-	/* COPY sshd_config_default to %programData%\openssh\sshd_config */
-	char sshd_config_path[PATH_MAX] = { 0, };
-	strcat_s(sshd_config_path, _countof(sshd_config_path), ssh_cfg_dir);
-	strcat_s(sshd_config_path, _countof(sshd_config_path), "\\sshd_config");
-	struct stat st;
-	if (stat(sshd_config_path, &st) < 0) {
-		char sshd_config_default_path[PATH_MAX] = { 0, };
-		strcat_s(sshd_config_default_path, _countof(sshd_config_default_path), w32_programdir());
-		strcat_s(sshd_config_default_path, _countof(sshd_config_default_path), "\\sshd_config_default");
+	/* copy sshd_config_default to %programData%\ssh\sshd_config */
+	wchar_t sshd_config_path[PATH_MAX] = { 0, };
+	wcscat_s(sshd_config_path, _countof(sshd_config_path), ssh_cfg_dir);
+	wcscat_s(sshd_config_path, _countof(sshd_config_path), L"\\sshd_config");
+	if (GetFileAttributesW(sshd_config_path) == INVALID_FILE_ATTRIBUTES) {
+		wchar_t sshd_config_default_path[PATH_MAX] = { 0, };
+		swprintf_s(sshd_config_default_path, PATH_MAX, L"%S\\%s", w32_programdir(), L"sshd_config_default");
 
-		if (copy_file(sshd_config_default_path, sshd_config_path) < 0) {
+		if (CopyFileW(sshd_config_default_path, sshd_config_path, TRUE) == 0) {
 			printf("Failed to copy %s to %s, error:%d", sshd_config_default_path, sshd_config_path, GetLastError());
 			exit(255);
 		}
@@ -261,7 +259,7 @@ int wmain(int argc, wchar_t **wargv) {
 	wchar_t* path_utf16;
 	argc_original = argc;
 	wargv_original = wargv;
-	
+
 	/* change current directory to sshd.exe root */
 	if ( (path_utf16 = utf8_to_utf16(w32_programdir())) == NULL) 
 		return -1;
diff --git a/regress/unittests/win32compat/miscellaneous_tests.c b/regress/unittests/win32compat/miscellaneous_tests.c
index 27b61af95..746c1a5dd 100644
--- a/regress/unittests/win32compat/miscellaneous_tests.c
+++ b/regress/unittests/win32compat/miscellaneous_tests.c
@@ -64,31 +64,38 @@ test_sanitizedpath()
 {
 	TEST_START("win32 program dir");
 	
-	char *win32prgdir = w32_programdir();
-	ASSERT_PTR_NE(win32prgdir, NULL);
+	char *win32prgdir_utf8 = w32_programdir();
+	ASSERT_PTR_NE(win32prgdir_utf8, NULL);
 
-	ASSERT_PTR_EQ(resolved_path(NULL), NULL);
+	ASSERT_PTR_EQ(resolved_path_utf16(NULL), NULL);
 
-	char *ret = resolved_path(win32prgdir);
-	retValue = strcmp(win32prgdir, ret);
+	wchar_t *win32prgdir = utf8_to_utf16(win32prgdir_utf8);
+	wchar_t *ret = resolved_path_utf16(win32prgdir_utf8);
+	retValue = wcscmp(win32prgdir, ret);
 	ASSERT_INT_EQ(retValue, 0);
+	free(ret);
 
-	char win32prgdir_len = strlen(win32prgdir);
+	char win32prgdir_len = strlen(win32prgdir_utf8);
 	char *tmp_path = malloc(win32prgdir_len + 2); /* 1-NULL and 1-adding "/" */
 	tmp_path[0] = '/';
-	strncpy(tmp_path+1, win32prgdir, win32prgdir_len);
+	strcpy(tmp_path+1, win32prgdir_utf8);
 	tmp_path[win32prgdir_len+1] = '\0';
 
-	ret = resolved_path(tmp_path);
-	retValue = strcmp(win32prgdir, ret);
+	ret = resolved_path_utf16(tmp_path);
+	retValue = wcscmp(win32prgdir, ret);
 	ASSERT_INT_EQ(retValue, 0);
+	free(ret);
 
-	char *s1 = malloc(4), *s2 = malloc(4);
+	char s1[4];
+	wchar_t s2[4];
 	s1[0] = '/', s1[1] = win32prgdir[0],  s1[2] = ':', s1[3] = '\0';
-	s2[0] = win32prgdir[0], s2[1] = ':', s2[2] = '\\', s2[3] = '\0';
-	ret = resolved_path(s1);
-	retValue = strcmp(ret, s2);
+	s2[0] = win32prgdir[0], s2[1] = ':', s2[2] = '\\', s2[3] = '\0';	
+	ret = resolved_path_utf16(s1);
+	retValue = wcscmp(ret, s2);
 	ASSERT_INT_EQ(retValue, 0);
+	free(ret);
+
+	free(win32prgdir);
 
 	TEST_DONE();
 }

From 14ba410250c71fba2a38ab811229419239e6fd5f Mon Sep 17 00:00:00 2001
From: Bryan Berns <berns@uwalumni.com>
Date: Tue, 3 Apr 2018 23:17:44 -0400
Subject: [PATCH 2/5] Removed Problematic Debug Statements

- Removed debug statements that were interfering with APC wakeup due to the way that logging works in atomicio6().
- Filled in a missing parameter in a debug statement.
---
 contrib/win32/win32compat/signal.c   | 3 +--
 contrib/win32/win32compat/socketio.c | 1 -
 2 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/contrib/win32/win32compat/signal.c b/contrib/win32/win32compat/signal.c
index 0e4dcad87..ddf2cb044 100644
--- a/contrib/win32/win32compat/signal.c
+++ b/contrib/win32/win32compat/signal.c
@@ -150,7 +150,7 @@ w32_sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
 {
 	/* this is only used by sshd to block SIGCHLD while doing waitpid() */
 	/* our implementation of waidpid() is never interrupted, so no need to implement this for now*/
-	debug5("sigprocmask() how:%d");
+	debug5("sigprocmask() how:%d", how);
 	return 0;
 }
 
@@ -277,7 +277,6 @@ wait_for_any_event(HANDLE* events, int num_events, DWORD milli_seconds)
 		return -1;
 	}
 
-	debug5("wait() on %d events and %d children", num_events, live_children);
 	DWORD ret = wait_for_multiple_objects_enhanced(num_all_events, all_events, milli_seconds, TRUE);
 	if ((ret >= WAIT_OBJECT_0_ENHANCED) && (ret <= WAIT_OBJECT_0_ENHANCED + num_all_events - 1)) {
 		/* woken up by event signaled
diff --git a/contrib/win32/win32compat/socketio.c b/contrib/win32/win32compat/socketio.c
index 2211d6237..893a88413 100644
--- a/contrib/win32/win32compat/socketio.c
+++ b/contrib/win32/win32compat/socketio.c
@@ -579,7 +579,6 @@ socketio_send(struct w32_io* pio, const void *buf, size_t len, int flags)
 
 	if (ret == 0) {
 		/* send has completed and APC is scheduled, let it run */
-		debug4("send - WSASend() returned 0, APC scheduled io:%p", pio);
 		pio->write_details.pending = TRUE;
 		pio->write_details.remaining = wsabuf.len;
 		SleepEx(0, TRUE);

From 8c86f30a0ff5ad4d192bfdd30013948126ba8bf5 Mon Sep 17 00:00:00 2001
From: Bryan Berns <berns@uwalumni.com>
Date: Tue, 24 Apr 2018 04:47:09 -0400
Subject: [PATCH 3/5] Reworked Path Resolution Function - Review Changes

- Changes based on review comments.
---
 contrib/win32/win32compat/fileio.c |  2 +-
 contrib/win32/win32compat/misc.c   | 14 ++++++++------
 contrib/win32/win32compat/w32log.c |  8 ++++----
 3 files changed, 13 insertions(+), 11 deletions(-)

diff --git a/contrib/win32/win32compat/fileio.c b/contrib/win32/win32compat/fileio.c
index 5e49bda5a..7a5121b8d 100644
--- a/contrib/win32/win32compat/fileio.c
+++ b/contrib/win32/win32compat/fileio.c
@@ -439,7 +439,7 @@ fileio_open(const char *path_utf8, int flags, mode_t mode)
 	}
 
 	/* if opening null device, point to Windows equivalent */
-	if (strcmp(path_utf8, NULL_DEVICE) == 0) 
+	if (strncmp(path_utf8, NULL_DEVICE, sizeof(NULL_DEVICE)) == 0) 
 		path_utf8 = NULL_DEVICE_WIN;
 
 	if ((path_utf16 = resolved_path_utf16(path_utf8)) == NULL) {
diff --git a/contrib/win32/win32compat/misc.c b/contrib/win32/win32compat/misc.c
index 7878b9604..ea916f0a1 100644
--- a/contrib/win32/win32compat/misc.c
+++ b/contrib/win32/win32compat/misc.c
@@ -53,6 +53,7 @@
 #include "w32fd.h"
 #include "inc\string.h"
 #include "inc\grp.h"
+#include <wchar.h>
 
 static char* s_programdir = NULL;
 
@@ -248,7 +249,7 @@ w32_fopen_utf8(const char *input_path, const char *mode)
 	}
 
 	/* if opening null device, point to Windows equivalent */
-	if (strcmp(input_path, NULL_DEVICE) == 0)
+	if (strncmp(input_path, NULL_DEVICE, sizeof(NULL_DEVICE)) == 0)
 		input_path = NULL_DEVICE_WIN;
 
 	wpath = resolved_path_utf16(input_path);
@@ -260,7 +261,7 @@ w32_fopen_utf8(const char *input_path, const char *mode)
 	}
 
 	if ((_wfopen_s(&f, wpath, wmode) != 0) || (f == NULL)) {
-		debug3("Failed to open file:%S error:%d", wpath, errno);
+		debug3("Failed to open file:%s error:%d", input_path, errno);
 		goto cleanup;
 	}		
 
@@ -931,6 +932,7 @@ resolved_path_utf16(const char *input_path)
 			wchar_t * resolved_path_new = realloc(resolved_path, 
 				(resolved_len + changed_req + 1) * sizeof(wchar_t));
 			if (resolved_path == NULL) {
+				debug3("%s: memory allocation failed.", __FUNCTION__);
 				free(resolved_path);
 				return NULL;
 			}
@@ -938,16 +940,16 @@ resolved_path_utf16(const char *input_path)
 		}
 
 		/* shift memory contents over based on side of the new string */
-		memmove(resolved_path + variable_len + changed_req, resolved_path + variable_len, 
-			(resolved_len - variable_len + 1) * sizeof(wchar_t));
-		memcpy(resolved_path, program_data, programdata_len * sizeof(wchar_t));
+		wmemmove_s(&resolved_path[variable_len + changed_req], resolved_len - variable_len + 1,
+			&resolved_path[variable_len], resolved_len - variable_len + 1);
 		resolved_len += changed_req;
+		wmemcpy_s(resolved_path, resolved_len + 1, program_data, programdata_len);
 	}
 
 	if (resolved_path[0] == L'/' && iswalpha(resolved_path[1]) && resolved_path[2] == L':') {
 
 		/* shift memory to remove forward slash including null terminator */
-		memmove(resolved_path, resolved_path + 1, (resolved_len + 1 - 1) * sizeof(wchar_t));
+		wmemmove_s(resolved_path, resolved_len + 1, resolved_path + 1, (resolved_len + 1 - 1));
 
 		/* if just a drive letter path, make x: into x:\ */
 		if (resolved_path[2] == L'\0') {
diff --git a/contrib/win32/win32compat/w32log.c b/contrib/win32/win32compat/w32log.c
index a0cb6f271..a45aad5f9 100644
--- a/contrib/win32/win32compat/w32log.c
+++ b/contrib/win32/win32compat/w32log.c
@@ -110,11 +110,11 @@ openlog_file()
 		while (tail > module_path && *tail != L'\\' && *tail != L'/')
 			tail--;
 		
-		wchar_t ssh_root_path[PATH_MAX] = {0 ,};
-		wcscat_s(ssh_root_path, _countof(ssh_root_path), get_program_data_path()); /* "%programData%" */
-		wcscat_s(ssh_root_path, _countof(ssh_root_path), L"\\ssh"); /* "%programData%\\ssh" */
+		wchar_t ssh_cfg_path[PATH_MAX] = {0 ,};
+		wcscat_s(ssh_cfg_path, _countof(ssh_cfg_path), get_program_data_path()); /* "%programData%" */
+		wcscat_s(ssh_cfg_path, _countof(ssh_cfg_path), L"\\ssh"); /* "%programData%\\ssh" */
 
-		if ((wcsncat_s(log_file, PATH_MAX + 12, ssh_root_path, wcslen(ssh_root_path)) != 0) ||
+		if ((wcsncat_s(log_file, PATH_MAX + 12, ssh_cfg_path, wcslen(ssh_cfg_path)) != 0) ||
 		    (wcsncat_s(log_file, PATH_MAX + 12, logs_dir, 6) != 0) ||
 		    (wcsncat_s(log_file, PATH_MAX + 12, tail + 1, wcslen(tail + 1) - 3) != 0 ) ||
 		    (wcsncat_s(log_file, PATH_MAX + 12, L"log", 3) != 0))

From c24cf7945e85361c97dbfe0bb991384c23552a29 Mon Sep 17 00:00:00 2001
From: Manoj Ampalam <manojampalam@live.com>
Date: Thu, 5 Apr 2018 09:57:41 -0700
Subject: [PATCH 4/5] Added support for hard links over sftp

https://github.com/PowerShell/Win32-OpenSSH/issues/1119
Added link() support using the CreateHardLink() function.
Made readlink() and link() declarations consistent with other functions.
---
 contrib/win32/win32compat/fileio.c     | 33 ++++++++++++++++++++++++++
 contrib/win32/win32compat/inc/unistd.h |  9 ++++---
 contrib/win32/win32compat/misc.c       |  8 +++----
 contrib/win32/win32compat/w32fd.h      |  3 ++-
 4 files changed, 44 insertions(+), 9 deletions(-)

diff --git a/contrib/win32/win32compat/fileio.c b/contrib/win32/win32compat/fileio.c
index 7a5121b8d..2ce716eed 100644
--- a/contrib/win32/win32compat/fileio.c
+++ b/contrib/win32/win32compat/fileio.c
@@ -1202,3 +1202,36 @@ cleanup:
 		free(resolved_utf16);
 	return ret;
 }
+
+int 
+fileio_link(const char *oldpath, const char *newpath)
+{
+	if (oldpath == NULL || newpath == NULL) {
+		errno = EFAULT;
+		return -1;
+	}
+
+	DWORD ret = 0;
+	wchar_t *oldpath_utf16 = utf8_to_utf16(resolved_path(oldpath));
+	wchar_t *newpath_utf16 = utf8_to_utf16(resolved_path(newpath));
+	if (oldpath_utf16 == NULL || newpath_utf16 == NULL) {
+		errno = ENOMEM;
+		ret = -1;
+		goto cleanup;
+	}
+
+	if (CreateHardLinkW(newpath_utf16, oldpath_utf16, NULL) == 0) {
+		errno = errno_from_Win32LastError();
+		ret = -1;
+		goto cleanup;
+	}
+
+cleanup:
+
+	if (oldpath_utf16)
+		free(oldpath_utf16);
+	if (newpath_utf16)
+		free(newpath_utf16);
+
+	return ret;
+}
\ No newline at end of file
diff --git a/contrib/win32/win32compat/inc/unistd.h b/contrib/win32/win32compat/inc/unistd.h
index d37188924..25f594ad3 100644
--- a/contrib/win32/win32compat/inc/unistd.h
+++ b/contrib/win32/win32compat/inc/unistd.h
@@ -76,11 +76,14 @@ int w32_chdir(const char *dirname);
 char *w32_getcwd(char *buffer, int maxlen);
 #define getcwd w32_getcwd
 
+int w32_readlink(const char *path, char *link, int linklen);
+#define readlink w32_readlink
+
+int w32_link(const char *oldpath, const char *newpath);
+#define link w32_link
+
 int daemon(int nochdir, int noclose);
 char *crypt(const char *key, const char *salt);
-int link(const char *oldpath, const char *newpath);
-int readlink(const char *path, char *link, int linklen);
-
 int chroot(const char *path);
 
 /* 
diff --git a/contrib/win32/win32compat/misc.c b/contrib/win32/win32compat/misc.c
index ea916f0a1..0f60a759d 100644
--- a/contrib/win32/win32compat/misc.c
+++ b/contrib/win32/win32compat/misc.c
@@ -661,11 +661,9 @@ w32_symlink(const char *target, const char *linkpath)
 }
 
 int
-link(const char *oldpath, const char *newpath)
+w32_link(const char *oldpath, const char *newpath)
 {
-	/* Not supported in windows */
-	errno = EOPNOTSUPP;
-	return -1;
+	return fileio_link(oldpath, newpath);
 }
 
 int
@@ -825,7 +823,7 @@ w32_lstat(const char *input_path, struct w32_stat *buf)
 
 /* if file is symbolic link, copy its link into "link" */
 int
-readlink(const char *path, char *link, int linklen)
+w32_readlink(const char *path, char *link, int linklen)
 {
 	return fileio_readlink(path, link, linklen);
 }
diff --git a/contrib/win32/win32compat/w32fd.h b/contrib/win32/win32compat/w32fd.h
index 9a5158945..67e5c3dac 100644
--- a/contrib/win32/win32compat/w32fd.h
+++ b/contrib/win32/win32compat/w32fd.h
@@ -165,4 +165,5 @@ int fileio_lstat(const char *path, struct _stat64 *buf);
 long fileio_lseek(struct w32_io* pio, unsigned __int64 offset, int origin);
 FILE* fileio_fdopen(struct w32_io* pio, const char *mode);
 ssize_t fileio_readlink(const char *path, char *buf, size_t bufsiz);
-int fileio_symlink(const char *target, const char *linkpath);
\ No newline at end of file
+int fileio_symlink(const char *target, const char *linkpath);
+int fileio_link(const char *oldpath, const char *newpath);
\ No newline at end of file

From 3cb15761a98dd29605f7ada6724fad0b0a4c41b7 Mon Sep 17 00:00:00 2001
From: Bryan Berns <berns@uwalumni.com>
Date: Tue, 24 Apr 2018 05:04:56 -0400
Subject: [PATCH 5/5] Use Path Resolution Function For Hard Links

- Changed link() to use resolved_path_utf16().
---
 contrib/win32/win32compat/fileio.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/contrib/win32/win32compat/fileio.c b/contrib/win32/win32compat/fileio.c
index 2ce716eed..80148e02f 100644
--- a/contrib/win32/win32compat/fileio.c
+++ b/contrib/win32/win32compat/fileio.c
@@ -1212,8 +1212,8 @@ fileio_link(const char *oldpath, const char *newpath)
 	}
 
 	DWORD ret = 0;
-	wchar_t *oldpath_utf16 = utf8_to_utf16(resolved_path(oldpath));
-	wchar_t *newpath_utf16 = utf8_to_utf16(resolved_path(newpath));
+	wchar_t *oldpath_utf16 = resolved_path_utf16(oldpath);
+	wchar_t *newpath_utf16 = resolved_path_utf16(newpath);
 	if (oldpath_utf16 == NULL || newpath_utf16 == NULL) {
 		errno = ENOMEM;
 		ret = -1;