From 40aaeb61f840bcc85ab5214fc012073b77b5f4e5 Mon Sep 17 00:00:00 2001 From: "Erich E. Hoover" Date: Thu, 16 Jan 2014 21:15:21 -0700 Subject: [PATCH] Support for junction points/reparse points. --- ...-support-for-junction-point-creation.patch | 310 ++++++++++++++++++ ...-support-for-reading-junction-points.patch | 128 ++++++++ ...support-for-deleting-junction-points.patch | 159 +++++++++ ...tise-that-a-file-is-a-junction-point.patch | 57 ++++ ...dd-support-for-deleting-junction-poi.patch | 103 ++++++ ...l32-Advertise-junction-point-support.patch | 26 ++ ...test-for-deleting-junction-point-tar.patch | 34 ++ ...se-relative-paths-for-creating-links.patch | 69 ++++ .../4cd13e94-7f2d-11e3-b5eb-0090f5c75ad5.def | 4 + patches/patch-list.patch | 3 +- 10 files changed, 892 insertions(+), 1 deletion(-) create mode 100644 patches/08-Junction_Points/0001-ntdll-Add-support-for-junction-point-creation.patch create mode 100644 patches/08-Junction_Points/0002-ntdll-Add-support-for-reading-junction-points.patch create mode 100644 patches/08-Junction_Points/0003-ntdll-Add-support-for-deleting-junction-points.patch create mode 100644 patches/08-Junction_Points/0004-ntdll-Advertise-that-a-file-is-a-junction-point.patch create mode 100644 patches/08-Junction_Points/0005-kernel32-ntdll-Add-support-for-deleting-junction-poi.patch create mode 100644 patches/08-Junction_Points/0006-kernel32-Advertise-junction-point-support.patch create mode 100644 patches/08-Junction_Points/0007-ntdll-tests-Add-test-for-deleting-junction-point-tar.patch create mode 100644 patches/08-Junction_Points/0008-ntdll-Use-relative-paths-for-creating-links.patch create mode 100644 patches/08-Junction_Points/4cd13e94-7f2d-11e3-b5eb-0090f5c75ad5.def diff --git a/patches/08-Junction_Points/0001-ntdll-Add-support-for-junction-point-creation.patch b/patches/08-Junction_Points/0001-ntdll-Add-support-for-junction-point-creation.patch new file mode 100644 index 00000000..1e1cacf5 --- /dev/null +++ b/patches/08-Junction_Points/0001-ntdll-Add-support-for-junction-point-creation.patch @@ -0,0 +1,310 @@ +From 4d377524363a3e33e2df29c38fe16542b6cd0aa1 Mon Sep 17 00:00:00 2001 +From: "Erich E. Hoover" +Date: Thu, 16 Jan 2014 20:56:49 -0700 +Subject: ntdll: Add support for junction point creation. + +--- + dlls/ntdll/file.c | 89 ++++++++++++++++++++++++++++++++++++++++++++ + dlls/ntdll/tests/file.c | 94 +++++++++++++++++++++++++++++++++++++++++++++++ + include/ntifs.h | 52 ++++++++++++++++++++++++++ + 3 files changed, 235 insertions(+) + create mode 100644 include/ntifs.h + +diff --git a/dlls/ntdll/file.c b/dlls/ntdll/file.c +index d2efcc1..7164d1e 100644 +--- a/dlls/ntdll/file.c ++++ b/dlls/ntdll/file.c +@@ -89,12 +89,14 @@ + #include "winioctl.h" + #include "ddk/ntddk.h" + #include "ddk/ntddser.h" ++#include "ntifs.h" + + WINE_DEFAULT_DEBUG_CHANNEL(ntdll); + WINE_DECLARE_DEBUG_CHANNEL(winediag); + + mode_t FILE_umask = 0; + ++#define WINE_TEMPLINK P_tmpdir"/winelink.XXXXXX" + #define SECSPERDAY 86400 + #define SECS_1601_TO_1970 ((369 * 365 + 89) * (ULONGLONG)SECSPERDAY) + +@@ -1470,6 +1472,76 @@ NTSTATUS WINAPI NtDeviceIoControlFile(HANDLE handle, HANDLE event, + } + + ++/* ++ * Retrieve the unix name corresponding to a file handle, remove that directory, and then symlink the ++ * requested directory to the location of the old directory. ++ */ ++NTSTATUS FILE_CreateSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer) ++{ ++ int dest_len = buffer->MountPointReparseBuffer.SubstituteNameLength; ++ int offset = buffer->MountPointReparseBuffer.SubstituteNameOffset; ++ WCHAR *dest = &buffer->MountPointReparseBuffer.PathBuffer[offset]; ++ char tmplink[] = WINE_TEMPLINK; ++ ANSI_STRING unix_src, unix_dest; ++ BOOL dest_allocated = FALSE; ++ int dest_fd, needs_close; ++ UNICODE_STRING nt_dest; ++ NTSTATUS status; ++ ++ if ((status = server_get_unix_fd( handle, FILE_SPECIAL_ACCESS, &dest_fd, &needs_close, NULL, NULL ))) ++ return status; ++ ++ if ((status = server_get_unix_name( handle, &unix_src ))) ++ goto cleanup; ++ ++ nt_dest.Buffer = dest; ++ nt_dest.Length = dest_len; ++ if ((status = wine_nt_to_unix_file_name( &nt_dest, &unix_dest, FILE_OPEN, FALSE ))) ++ goto cleanup; ++ dest_allocated = TRUE; ++ ++ TRACE("Linking %s to %s\n", unix_src.Buffer, unix_dest.Buffer); ++ ++ /* Produce the link in a temporary location */ ++ while(1) ++ { ++ int fd; ++ ++ memcpy( tmplink, WINE_TEMPLINK, sizeof(tmplink) ); ++ fd = mkstemps( tmplink, 0 ); ++ if (fd == -1) break; ++ if (!unlink( tmplink )) ++ { ++ if (!symlink( unix_dest.Buffer, tmplink )) ++ break; ++ } ++ close(fd); ++ } ++ /* Atomically move the link into position */ ++ if (rename( tmplink, unix_src.Buffer )) ++ { ++ unlink( tmplink ); ++ FIXME("Atomic replace of directory with symbolic link unsupported on this system, may result in race condition.\n"); ++ if (rmdir( unix_src.Buffer ) < 0) ++ { ++ status = FILE_GetNtStatus(); ++ goto cleanup; ++ } ++ if (symlink( unix_dest.Buffer, unix_src.Buffer ) < 0) ++ { ++ status = FILE_GetNtStatus(); ++ goto cleanup; ++ } ++ } ++ status = STATUS_SUCCESS; ++ ++cleanup: ++ if (dest_allocated) RtlFreeAnsiString( &unix_dest ); ++ if (needs_close) close( dest_fd ); ++ return status; ++} ++ ++ + /************************************************************************** + * NtFsControlFile [NTDLL.@] + * ZwFsControlFile [NTDLL.@] +@@ -1617,6 +1689,23 @@ NTSTATUS WINAPI NtFsControlFile(HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc + } + break; + } ++ ++ case FSCTL_SET_REPARSE_POINT: ++ { ++ REPARSE_DATA_BUFFER *buffer = (REPARSE_DATA_BUFFER *)in_buffer; ++ ++ switch(buffer->ReparseTag) ++ { ++ case IO_REPARSE_TAG_MOUNT_POINT: ++ status = FILE_CreateSymlink( handle, buffer ); ++ break; ++ default: ++ FIXME("stub: FSCTL_SET_REPARSE_POINT(%x)\n", buffer->ReparseTag); ++ status = STATUS_NOT_IMPLEMENTED; ++ break; ++ } ++ break; ++ } + case FSCTL_PIPE_LISTEN: + case FSCTL_PIPE_WAIT: + default: +diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c +index 695f034..127681a 100644 +--- a/dlls/ntdll/tests/file.c ++++ b/dlls/ntdll/tests/file.c +@@ -37,6 +37,7 @@ + #include "winternl.h" + #include "winuser.h" + #include "winioctl.h" ++#include "ntifs.h" + + #ifndef IO_COMPLETION_ALL_ACCESS + #define IO_COMPLETION_ALL_ACCESS 0x001F0003 +@@ -2672,6 +2673,98 @@ todo_wine + CloseHandle(hfile); + } + ++static INT build_reparse_buffer(WCHAR *filename, REPARSE_DATA_BUFFER **pbuffer) ++{ ++ REPARSE_DATA_BUFFER *buffer; ++ INT buffer_len, string_len; ++ WCHAR *dest; ++ ++ string_len = (lstrlenW(filename)+1)*sizeof(WCHAR); ++ buffer_len = FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer[1]) + string_len; ++ buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, buffer_len); ++ buffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; ++ buffer->ReparseDataLength = sizeof(buffer->MountPointReparseBuffer) + string_len; ++ buffer->MountPointReparseBuffer.SubstituteNameLength = string_len - sizeof(WCHAR); ++ buffer->MountPointReparseBuffer.PrintNameOffset = string_len; ++ dest = &buffer->MountPointReparseBuffer.PathBuffer[0]; ++ memcpy(dest, filename, string_len); ++ *pbuffer = buffer; ++ return buffer_len; ++} ++ ++static void test_junction_points(void) ++{ ++ static const WCHAR junctionW[] = {'\\','j','u','n','c','t','i','o','n',0}; ++ WCHAR path[MAX_PATH], junction_path[MAX_PATH], target_path[MAX_PATH]; ++ static const WCHAR targetW[] = {'\\','t','a','r','g','e','t',0}; ++ static const WCHAR fooW[] = {'f','o','o',0}; ++ static WCHAR volW[] = {'c',':','\\',0}; ++ static const WCHAR dotW[] = {'.',0}; ++ REPARSE_DATA_BUFFER *buffer = NULL; ++ DWORD dwret, dwLen, dwFlags; ++ UNICODE_STRING nameW; ++ HANDLE hJunction; ++ INT buffer_len; ++ BOOL bret; ++ ++ /* Create a temporary folder for the junction point tests */ ++ GetTempFileNameW(dotW, fooW, 0, path); ++ DeleteFileW(path); ++ if (!CreateDirectoryW(path, NULL)) ++ { ++ win_skip("Unable to create a temporary junction point directory.\n"); ++ return; ++ } ++ ++ /* Check that the volume this folder is located on supports junction points */ ++ pRtlDosPathNameToNtPathName_U(path, &nameW, NULL, NULL); ++ volW[0] = nameW.Buffer[4]; ++ pRtlFreeUnicodeString( &nameW ); ++ GetVolumeInformationW(volW, 0, 0, 0, &dwLen, &dwFlags, 0, 0); ++ if (!(dwFlags & FILE_SUPPORTS_REPARSE_POINTS)) ++ { ++ skip("File system does not support junction points.\n"); ++ RemoveDirectoryW(path); ++ return; ++ } ++ ++ /* Create the folder to be replaced by a junction point */ ++ lstrcpyW(junction_path, path); ++ lstrcatW(junction_path, junctionW); ++ bret = CreateDirectoryW(junction_path, NULL); ++ ok(bret, "Failed to create junction point directory.\n"); ++ ++ /* Create a destination folder for the junction point to target */ ++ lstrcpyW(target_path, path); ++ lstrcatW(target_path, targetW); ++ bret = CreateDirectoryW(target_path, NULL); ++ ok(bret, "Failed to create junction point target directory.\n"); ++ pRtlDosPathNameToNtPathName_U(target_path, &nameW, NULL, NULL); ++ ++ /* Create the junction point */ ++ hJunction = CreateFileW(junction_path, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, ++ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0); ++ if (hJunction == INVALID_HANDLE_VALUE) ++ { ++ win_skip("Failed to open junction point directory handle (0x%x).\n", GetLastError()); ++ goto cleanup; ++ } ++ buffer_len = build_reparse_buffer(nameW.Buffer, &buffer); ++ bret = DeviceIoControl(hJunction, FSCTL_SET_REPARSE_POINT, (LPVOID)buffer, buffer_len, NULL, 0, &dwret, 0); ++ ok(bret, "Failed to create junction point! (0x%x)\n", GetLastError()); ++ CloseHandle(hJunction); ++ ++cleanup: ++ /* Cleanup */ ++ pRtlFreeUnicodeString( &nameW ); ++ HeapFree(GetProcessHeap(), 0, buffer); ++ bret = RemoveDirectoryW(junction_path); ++ ok(bret, "Failed to remove temporary junction point directory!\n"); ++ bret = RemoveDirectoryW(target_path); ++ ok(bret, "Failed to remove temporary target directory!\n"); ++ RemoveDirectoryW(path); ++} ++ + START_TEST(file) + { + HMODULE hkernel32 = GetModuleHandleA("kernel32.dll"); +@@ -2725,4 +2818,5 @@ START_TEST(file) + test_file_disposition_information(); + test_query_volume_information_file(); + test_query_attribute_information_file(); ++ test_junction_points(); + } +diff --git a/include/ntifs.h b/include/ntifs.h +new file mode 100644 +index 0000000..db07c28 +--- /dev/null ++++ b/include/ntifs.h +@@ -0,0 +1,52 @@ ++/* ++ * Win32 definitions for Windows NT ++ * ++ * Copyright 2012 Erich Hoover ++ * ++ * This library is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * This library is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with this library; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA ++ */ ++ ++#ifndef __WINE_NTIFS_H ++#define __WINE_NTIFS_H ++ ++typedef struct _REPARSE_DATA_BUFFER { ++ ULONG ReparseTag; ++ USHORT ReparseDataLength; ++ USHORT Reserved; ++ union { ++ struct { ++ USHORT SubstituteNameOffset; ++ USHORT SubstituteNameLength; ++ USHORT PrintNameOffset; ++ USHORT PrintNameLength; ++ ULONG Flags; ++ WCHAR PathBuffer[1]; ++ } SymbolicLinkReparseBuffer; ++ struct { ++ USHORT SubstituteNameOffset; ++ USHORT SubstituteNameLength; ++ USHORT PrintNameOffset; ++ USHORT PrintNameLength; ++ WCHAR PathBuffer[1]; ++ } MountPointReparseBuffer; ++ struct { ++ UCHAR DataBuffer[1]; ++ } GenericReparseBuffer; ++ }; ++} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; ++ ++#define IO_REPARSE_TAG_MOUNT_POINT 0xa0000003 ++ ++#endif /* __WINE_NTIFS_H */ +-- +1.7.9.5 + diff --git a/patches/08-Junction_Points/0002-ntdll-Add-support-for-reading-junction-points.patch b/patches/08-Junction_Points/0002-ntdll-Add-support-for-reading-junction-points.patch new file mode 100644 index 00000000..e82ef8a1 --- /dev/null +++ b/patches/08-Junction_Points/0002-ntdll-Add-support-for-reading-junction-points.patch @@ -0,0 +1,128 @@ +From c191da3e2cf2136800fbf0658ced3a3534280806 Mon Sep 17 00:00:00 2001 +From: "Erich E. Hoover" +Date: Thu, 16 Jan 2014 20:57:57 -0700 +Subject: ntdll: Add support for reading junction points. + +--- + dlls/ntdll/file.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++ + dlls/ntdll/tests/file.c | 14 ++++++++++- + 2 files changed, 76 insertions(+), 1 deletion(-) + +diff --git a/dlls/ntdll/file.c b/dlls/ntdll/file.c +index 7164d1e..56e0ef6 100644 +--- a/dlls/ntdll/file.c ++++ b/dlls/ntdll/file.c +@@ -1542,6 +1542,60 @@ cleanup: + } + + ++/* ++ * Retrieve the unix name corresponding to a file handle and use that to find the destination of the symlink ++ * corresponding to that file handle. ++ */ ++NTSTATUS FILE_GetSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer) ++{ ++ ANSI_STRING unix_src, unix_dest; ++ BOOL dest_allocated = FALSE; ++ int dest_fd, needs_close; ++ UNICODE_STRING nt_dest; ++ NTSTATUS status; ++ VOID *dest_name; ++ ssize_t ret; ++ ++ if ((status = server_get_unix_fd( handle, FILE_ANY_ACCESS, &dest_fd, &needs_close, NULL, NULL ))) ++ return status; ++ ++ if ((status = server_get_unix_name( handle, &unix_src ))) ++ goto cleanup; ++ ++ unix_dest.Buffer = RtlAllocateHeap( GetProcessHeap(), 0, PATH_MAX ); ++ unix_dest.MaximumLength = PATH_MAX; ++ dest_allocated = TRUE; ++ ret = readlink( unix_src.Buffer, unix_dest.Buffer, unix_dest.MaximumLength ); ++ if (ret < 0) ++ { ++ status = FILE_GetNtStatus(); ++ goto cleanup; ++ } ++ unix_dest.Length = ret; ++ ++ if ((status = wine_unix_to_nt_file_name( &unix_dest, &nt_dest ))) ++ goto cleanup; ++ ++ if (nt_dest.Length > buffer->MountPointReparseBuffer.SubstituteNameLength) ++ { ++ status = STATUS_BUFFER_TOO_SMALL; ++ goto cleanup; ++ } ++ ++ buffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; ++ buffer->MountPointReparseBuffer.SubstituteNameLength = nt_dest.Length; ++ buffer->MountPointReparseBuffer.SubstituteNameOffset = 0; ++ dest_name = &buffer->MountPointReparseBuffer.PathBuffer[buffer->MountPointReparseBuffer.SubstituteNameOffset]; ++ memcpy( dest_name, nt_dest.Buffer, nt_dest.Length ); ++ status = STATUS_SUCCESS; ++ ++cleanup: ++ if (dest_allocated) RtlFreeAnsiString( &unix_dest ); ++ if (needs_close) close( dest_fd ); ++ return status; ++} ++ ++ + /************************************************************************** + * NtFsControlFile [NTDLL.@] + * ZwFsControlFile [NTDLL.@] +@@ -1690,6 +1744,15 @@ NTSTATUS WINAPI NtFsControlFile(HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc + break; + } + ++ case FSCTL_GET_REPARSE_POINT: ++ { ++ REPARSE_DATA_BUFFER *buffer = (REPARSE_DATA_BUFFER *)out_buffer; ++ DWORD max_length = out_size-FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer[1]); ++ ++ buffer->MountPointReparseBuffer.SubstituteNameLength = max_length; ++ status = FILE_GetSymlink( handle, buffer ); ++ break; ++ } + case FSCTL_SET_REPARSE_POINT: + { + REPARSE_DATA_BUFFER *buffer = (REPARSE_DATA_BUFFER *)in_buffer; +diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c +index 127681a..595276a 100644 +--- a/dlls/ntdll/tests/file.c ++++ b/dlls/ntdll/tests/file.c +@@ -2702,9 +2702,10 @@ static void test_junction_points(void) + static const WCHAR dotW[] = {'.',0}; + REPARSE_DATA_BUFFER *buffer = NULL; + DWORD dwret, dwLen, dwFlags; ++ INT buffer_len, string_len; + UNICODE_STRING nameW; + HANDLE hJunction; +- INT buffer_len; ++ WCHAR *dest; + BOOL bret; + + /* Create a temporary folder for the junction point tests */ +@@ -2752,6 +2753,17 @@ static void test_junction_points(void) + buffer_len = build_reparse_buffer(nameW.Buffer, &buffer); + bret = DeviceIoControl(hJunction, FSCTL_SET_REPARSE_POINT, (LPVOID)buffer, buffer_len, NULL, 0, &dwret, 0); + ok(bret, "Failed to create junction point! (0x%x)\n", GetLastError()); ++ ++ /* Read back the junction point */ ++ HeapFree(GetProcessHeap(), 0, buffer); ++ buffer_len = sizeof(*buffer) + MAX_PATH*sizeof(WCHAR); ++ buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, buffer_len); ++ bret = DeviceIoControl(hJunction, FSCTL_GET_REPARSE_POINT, NULL, 0, (LPVOID)buffer, buffer_len, &dwret, 0); ++ string_len = buffer->MountPointReparseBuffer.SubstituteNameLength; ++ dest = &buffer->MountPointReparseBuffer.PathBuffer[buffer->MountPointReparseBuffer.SubstituteNameOffset/sizeof(WCHAR)]; ++ ok(bret, "Failed to read junction point!\n"); ++ ok((memcmp(dest, nameW.Buffer, string_len) == 0), "Junction point destination does not match ('%s' != '%s')!\n", ++ wine_dbgstr_w(dest), wine_dbgstr_w(nameW.Buffer)); + CloseHandle(hJunction); + + cleanup: +-- +1.7.9.5 + diff --git a/patches/08-Junction_Points/0003-ntdll-Add-support-for-deleting-junction-points.patch b/patches/08-Junction_Points/0003-ntdll-Add-support-for-deleting-junction-points.patch new file mode 100644 index 00000000..ea83dcb3 --- /dev/null +++ b/patches/08-Junction_Points/0003-ntdll-Add-support-for-deleting-junction-points.patch @@ -0,0 +1,159 @@ +From 9a592e9dd065355f785e997fb72ee5a586665dc0 Mon Sep 17 00:00:00 2001 +From: "Erich E. Hoover" +Date: Thu, 16 Jan 2014 21:00:21 -0700 +Subject: ntdll: Add support for deleting junction points. + +--- + dlls/ntdll/file.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++ + dlls/ntdll/tests/file.c | 23 +++++++++++++++++++++ + include/ntifs.h | 11 ++++++++++ + 3 files changed, 85 insertions(+) + +diff --git a/dlls/ntdll/file.c b/dlls/ntdll/file.c +index 56e0ef6..396bdf9 100644 +--- a/dlls/ntdll/file.c ++++ b/dlls/ntdll/file.c +@@ -1596,6 +1596,41 @@ cleanup: + } + + ++/* ++ * Retrieve the unix name corresponding to a file handle, remove that symlink, and then recreate ++ * a directory at the location of the old filename. ++ */ ++NTSTATUS FILE_RemoveSymlink(HANDLE handle, REPARSE_GUID_DATA_BUFFER *buffer) ++{ ++ int dest_fd, needs_close; ++ ANSI_STRING unix_name; ++ NTSTATUS status; ++ ++ if ((status = server_get_unix_fd( handle, FILE_SPECIAL_ACCESS, &dest_fd, &needs_close, NULL, NULL ))) ++ return status; ++ ++ if ((status = server_get_unix_name( handle, &unix_name ))) ++ goto cleanup; ++ ++ TRACE("Deleting symlink %s\n", unix_name.Buffer); ++ if (unlink( unix_name.Buffer ) < 0) ++ { ++ status = FILE_GetNtStatus(); ++ goto cleanup; ++ } ++ if (mkdir( unix_name.Buffer, 0775 ) < 0) ++ { ++ status = FILE_GetNtStatus(); ++ goto cleanup; ++ } ++ status = STATUS_SUCCESS; ++ ++cleanup: ++ if (needs_close) close( dest_fd ); ++ return status; ++} ++ ++ + /************************************************************************** + * NtFsControlFile [NTDLL.@] + * ZwFsControlFile [NTDLL.@] +@@ -1744,6 +1779,22 @@ NTSTATUS WINAPI NtFsControlFile(HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc + break; + } + ++ case FSCTL_DELETE_REPARSE_POINT: ++ { ++ REPARSE_GUID_DATA_BUFFER *buffer = (REPARSE_GUID_DATA_BUFFER *)in_buffer; ++ ++ switch(buffer->ReparseTag) ++ { ++ case IO_REPARSE_TAG_MOUNT_POINT: ++ status = FILE_RemoveSymlink( handle, buffer ); ++ break; ++ default: ++ FIXME("stub: FSCTL_DELETE_REPARSE_POINT(%x)\n", buffer->ReparseTag); ++ status = STATUS_NOT_IMPLEMENTED; ++ break; ++ } ++ break; ++ } + case FSCTL_GET_REPARSE_POINT: + { + REPARSE_DATA_BUFFER *buffer = (REPARSE_DATA_BUFFER *)out_buffer; +diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c +index 595276a..d3c6cf0 100644 +--- a/dlls/ntdll/tests/file.c ++++ b/dlls/ntdll/tests/file.c +@@ -2697,12 +2697,15 @@ static void test_junction_points(void) + static const WCHAR junctionW[] = {'\\','j','u','n','c','t','i','o','n',0}; + WCHAR path[MAX_PATH], junction_path[MAX_PATH], target_path[MAX_PATH]; + static const WCHAR targetW[] = {'\\','t','a','r','g','e','t',0}; ++ FILE_BASIC_INFORMATION old_attrib, new_attrib; + static const WCHAR fooW[] = {'f','o','o',0}; + static WCHAR volW[] = {'c',':','\\',0}; ++ REPARSE_GUID_DATA_BUFFER guid_buffer; + static const WCHAR dotW[] = {'.',0}; + REPARSE_DATA_BUFFER *buffer = NULL; + DWORD dwret, dwLen, dwFlags; + INT buffer_len, string_len; ++ IO_STATUS_BLOCK iosb; + UNICODE_STRING nameW; + HANDLE hJunction; + WCHAR *dest; +@@ -2750,6 +2753,8 @@ static void test_junction_points(void) + win_skip("Failed to open junction point directory handle (0x%x).\n", GetLastError()); + goto cleanup; + } ++ dwret = NtQueryInformationFile(hJunction, &iosb, &old_attrib, sizeof(old_attrib), FileBasicInformation); ++ ok(dwret == STATUS_SUCCESS, "Failed to get junction point folder's attributes (0x%x).\n", dwret); + buffer_len = build_reparse_buffer(nameW.Buffer, &buffer); + bret = DeviceIoControl(hJunction, FSCTL_SET_REPARSE_POINT, (LPVOID)buffer, buffer_len, NULL, 0, &dwret, 0); + ok(bret, "Failed to create junction point! (0x%x)\n", GetLastError()); +@@ -2764,6 +2769,24 @@ static void test_junction_points(void) + ok(bret, "Failed to read junction point!\n"); + ok((memcmp(dest, nameW.Buffer, string_len) == 0), "Junction point destination does not match ('%s' != '%s')!\n", + wine_dbgstr_w(dest), wine_dbgstr_w(nameW.Buffer)); ++ ++ /* Delete the junction point */ ++ memset(&old_attrib, 0x00, sizeof(old_attrib)); ++ old_attrib.LastAccessTime.QuadPart = 0x200deadcafebeef; ++ dwret = NtSetInformationFile(hJunction, &iosb, &old_attrib, sizeof(old_attrib), FileBasicInformation); ++ ok(dwret == STATUS_SUCCESS, "Failed to set junction point folder's attributes (0x%x).\n", dwret); ++ memset(&guid_buffer, 0x00, sizeof(guid_buffer)); ++ guid_buffer.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; ++ bret = DeviceIoControl(hJunction, FSCTL_DELETE_REPARSE_POINT, (LPVOID)&guid_buffer, ++ REPARSE_GUID_DATA_BUFFER_HEADER_SIZE, NULL, 0, &dwret, 0); ++ ok(bret, "Failed to delete junction point! (0x%x)\n", GetLastError()); ++ memset(&new_attrib, 0x00, sizeof(new_attrib)); ++ dwret = NtQueryInformationFile(hJunction, &iosb, &new_attrib, sizeof(new_attrib), FileBasicInformation); ++ ok(dwret == STATUS_SUCCESS, "Failed to get junction point folder's attributes (0x%x).\n", dwret); ++ /* conversion bug: we see 0x1c9c380deadbee6 on Wine */ ++ todo_wine ok(old_attrib.LastAccessTime.QuadPart == new_attrib.LastAccessTime.QuadPart, ++ "Junction point folder's access time does not match (0x%llx != 0x%llx).\n", ++ new_attrib.LastAccessTime.QuadPart, old_attrib.LastAccessTime.QuadPart); + CloseHandle(hJunction); + + cleanup: +diff --git a/include/ntifs.h b/include/ntifs.h +index db07c28..cb8638b 100644 +--- a/include/ntifs.h ++++ b/include/ntifs.h +@@ -47,6 +47,17 @@ typedef struct _REPARSE_DATA_BUFFER { + }; + } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; + ++typedef struct _REPARSE_GUID_DATA_BUFFER { ++ DWORD ReparseTag; ++ WORD ReparseDataLength; ++ WORD Reserved; ++ GUID ReparseGuid; ++ struct { ++ BYTE DataBuffer[1]; ++ } GenericReparseBuffer; ++} REPARSE_GUID_DATA_BUFFER, *PREPARSE_GUID_DATA_BUFFER; ++ + #define IO_REPARSE_TAG_MOUNT_POINT 0xa0000003 ++#define REPARSE_GUID_DATA_BUFFER_HEADER_SIZE FIELD_OFFSET(REPARSE_GUID_DATA_BUFFER, GenericReparseBuffer) + + #endif /* __WINE_NTIFS_H */ +-- +1.7.9.5 + diff --git a/patches/08-Junction_Points/0004-ntdll-Advertise-that-a-file-is-a-junction-point.patch b/patches/08-Junction_Points/0004-ntdll-Advertise-that-a-file-is-a-junction-point.patch new file mode 100644 index 00000000..856609eb --- /dev/null +++ b/patches/08-Junction_Points/0004-ntdll-Advertise-that-a-file-is-a-junction-point.patch @@ -0,0 +1,57 @@ +From f35e4dc0c96de3e9fbeb4ff1bdefc9db7c7d56d8 Mon Sep 17 00:00:00 2001 +From: "Erich E. Hoover" +Date: Thu, 16 Jan 2014 21:01:25 -0700 +Subject: ntdll: Advertise that a file is a junction point. + +--- + dlls/ntdll/file.c | 7 ++++++- + dlls/ntdll/tests/file.c | 5 +++++ + 2 files changed, 11 insertions(+), 1 deletion(-) + +diff --git a/dlls/ntdll/file.c b/dlls/ntdll/file.c +index 396bdf9..b4e06d1 100644 +--- a/dlls/ntdll/file.c ++++ b/dlls/ntdll/file.c +@@ -1988,10 +1988,11 @@ NTSTATUS fill_stat_info( const struct stat *st, void *ptr, FILE_INFORMATION_CLAS + + get_file_times( st, &info->LastWriteTime, &info->ChangeTime, + &info->LastAccessTime, &info->CreationTime ); +- if (S_ISDIR(st->st_mode)) info->FileAttributes = FILE_ATTRIBUTE_DIRECTORY; ++ if (st->st_mode & S_IFDIR) info->FileAttributes = FILE_ATTRIBUTE_DIRECTORY; + else info->FileAttributes = FILE_ATTRIBUTE_ARCHIVE; + if (!(st->st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))) + info->FileAttributes |= FILE_ATTRIBUTE_READONLY; ++ if ((st->st_mode & S_IFLNK) == S_IFLNK) info->FileAttributes |= FILE_ATTRIBUTE_REPARSE_POINT; + } + break; + case FileStandardInformation: +@@ -2657,6 +2658,10 @@ NTSTATUS WINAPI NtQueryAttributesFile( const OBJECT_ATTRIBUTES *attr, FILE_BASIC + status = STATUS_INVALID_INFO_CLASS; + else + { ++ struct stat lst; ++ ++ if (lstat( unix_name.Buffer, &lst ) != -1) ++ st.st_mode |= (lst.st_mode & S_IFLNK); + status = fill_stat_info( &st, info, FileBasicInformation ); + if (DIR_is_hidden_file( attr->ObjectName )) + info->FileAttributes |= FILE_ATTRIBUTE_HIDDEN; +diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c +index d3c6cf0..c75ba47 100644 +--- a/dlls/ntdll/tests/file.c ++++ b/dlls/ntdll/tests/file.c +@@ -2759,6 +2759,11 @@ static void test_junction_points(void) + bret = DeviceIoControl(hJunction, FSCTL_SET_REPARSE_POINT, (LPVOID)buffer, buffer_len, NULL, 0, &dwret, 0); + ok(bret, "Failed to create junction point! (0x%x)\n", GetLastError()); + ++ /* Check the file attributes of the junction point */ ++ dwret = GetFileAttributesW(junction_path); ++ ok(dwret != (DWORD)~0, "Junction point doesn't exist (attributes: 0x%x)!\n", dwret); ++ ok(dwret & FILE_ATTRIBUTE_REPARSE_POINT, "File is not a junction point! (attributes: %d)\n", dwret); ++ + /* Read back the junction point */ + HeapFree(GetProcessHeap(), 0, buffer); + buffer_len = sizeof(*buffer) + MAX_PATH*sizeof(WCHAR); +-- +1.7.9.5 + diff --git a/patches/08-Junction_Points/0005-kernel32-ntdll-Add-support-for-deleting-junction-poi.patch b/patches/08-Junction_Points/0005-kernel32-ntdll-Add-support-for-deleting-junction-poi.patch new file mode 100644 index 00000000..1f78f93a --- /dev/null +++ b/patches/08-Junction_Points/0005-kernel32-ntdll-Add-support-for-deleting-junction-poi.patch @@ -0,0 +1,103 @@ +From e6a876330230784a2b2be6588b94e5555de169da Mon Sep 17 00:00:00 2001 +From: "Erich E. Hoover" +Date: Thu, 16 Jan 2014 21:02:11 -0700 +Subject: kernel32,ntdll: Add support for deleting junction points with + RemoveDirectory. + +--- + dlls/kernel32/path.c | 12 ++++++++++-- + dlls/ntdll/tests/file.c | 34 +++++++++++++++++++++++++++++++++- + 2 files changed, 43 insertions(+), 3 deletions(-) + +diff --git a/dlls/kernel32/path.c b/dlls/kernel32/path.c +index 09fb04b..c328cc0 100644 +--- a/dlls/kernel32/path.c ++++ b/dlls/kernel32/path.c +@@ -1587,6 +1587,7 @@ BOOL WINAPI CreateDirectoryExW( LPCWSTR template, LPCWSTR path, LPSECURITY_ATTRI + */ + BOOL WINAPI RemoveDirectoryW( LPCWSTR path ) + { ++ FILE_BASIC_INFORMATION info; + OBJECT_ATTRIBUTES attr; + UNICODE_STRING nt_name; + ANSI_STRING unix_name; +@@ -1614,15 +1615,22 @@ BOOL WINAPI RemoveDirectoryW( LPCWSTR path ) + FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT ); + if (status == STATUS_SUCCESS) + status = wine_nt_to_unix_file_name( &nt_name, &unix_name, FILE_OPEN, FALSE ); +- RtlFreeUnicodeString( &nt_name ); + + if (status != STATUS_SUCCESS) + { ++ RtlFreeUnicodeString( &nt_name ); + SetLastError( RtlNtStatusToDosError(status) ); + return FALSE; + } + +- if (!(ret = (rmdir( unix_name.Buffer ) != -1))) FILE_SetDosError(); ++ status = NtQueryAttributesFile( &attr, &info ); ++ RtlFreeUnicodeString( &nt_name ); ++ if ((info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && (info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY)) ++ ret = (unlink( unix_name.Buffer ) != -1); ++ else ++ ret = (rmdir( unix_name.Buffer ) != -1); ++ if (!ret) FILE_SetDosError(); ++ + RtlFreeAnsiString( &unix_name ); + NtClose( handle ); + return ret; +diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c +index c75ba47..f94a61c 100644 +--- a/dlls/ntdll/tests/file.c ++++ b/dlls/ntdll/tests/file.c +@@ -2703,7 +2703,7 @@ static void test_junction_points(void) + REPARSE_GUID_DATA_BUFFER guid_buffer; + static const WCHAR dotW[] = {'.',0}; + REPARSE_DATA_BUFFER *buffer = NULL; +- DWORD dwret, dwLen, dwFlags; ++ DWORD dwret, dwLen, dwFlags, err; + INT buffer_len, string_len; + IO_STATUS_BLOCK iosb; + UNICODE_STRING nameW; +@@ -2794,6 +2794,38 @@ static void test_junction_points(void) + new_attrib.LastAccessTime.QuadPart, old_attrib.LastAccessTime.QuadPart); + CloseHandle(hJunction); + ++ /* Check deleting a junction point as if it were a directory */ ++ HeapFree(GetProcessHeap(), 0, buffer); ++ hJunction = CreateFileW(junction_path, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, ++ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0); ++ buffer_len = build_reparse_buffer(nameW.Buffer, &buffer); ++ bret = DeviceIoControl(hJunction, FSCTL_SET_REPARSE_POINT, (LPVOID)buffer, buffer_len, NULL, 0, &dwret, 0); ++ ok(bret, "Failed to create junction point! (0x%x)\n", GetLastError()); ++ CloseHandle(hJunction); ++ bret = RemoveDirectoryW(junction_path); ++ ok(bret, "Failed to delete junction point as directory!\n"); ++ dwret = GetFileAttributesW(junction_path); ++ ok(dwret == (DWORD)~0, "Junction point still exists (attributes: 0x%x)!\n", dwret); ++ ++ /* Check deleting a junction point as if it were a file */ ++ HeapFree(GetProcessHeap(), 0, buffer); ++ bret = CreateDirectoryW(junction_path, NULL); ++ ok(bret, "Failed to create junction point target directory.\n"); ++ hJunction = CreateFileW(junction_path, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, ++ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0); ++ buffer_len = build_reparse_buffer(nameW.Buffer, &buffer); ++ bret = DeviceIoControl(hJunction, FSCTL_SET_REPARSE_POINT, (LPVOID)buffer, buffer_len, NULL, 0, &dwret, 0); ++ ok(bret, "Failed to create junction point! (0x%x)\n", GetLastError()); ++ CloseHandle(hJunction); ++ bret = DeleteFileW(junction_path); ++ ok(!bret, "Succeeded in deleting junction point as file!\n"); ++ err = GetLastError(); ++ ok(err == ERROR_ACCESS_DENIED, "Expected last error 0x%x for DeleteFile on junction point (actually 0x%x)!\n", ++ ERROR_ACCESS_DENIED, err); ++ dwret = GetFileAttributesW(junction_path); ++ ok(dwret != (DWORD)~0, "Junction point doesn't exist (attributes: 0x%x)!\n", dwret); ++ ok(dwret & FILE_ATTRIBUTE_REPARSE_POINT, "File is not a junction point! (attributes: 0x%x)\n", dwret); ++ + cleanup: + /* Cleanup */ + pRtlFreeUnicodeString( &nameW ); +-- +1.7.9.5 + diff --git a/patches/08-Junction_Points/0006-kernel32-Advertise-junction-point-support.patch b/patches/08-Junction_Points/0006-kernel32-Advertise-junction-point-support.patch new file mode 100644 index 00000000..666a157b --- /dev/null +++ b/patches/08-Junction_Points/0006-kernel32-Advertise-junction-point-support.patch @@ -0,0 +1,26 @@ +From cc928f1a52250242fd9e3dec8cd159216535f08f Mon Sep 17 00:00:00 2001 +From: "Erich E. Hoover" +Date: Thu, 16 Jan 2014 21:03:47 -0700 +Subject: kernel32: Advertise junction point support. + +--- + dlls/kernel32/volume.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/dlls/kernel32/volume.c b/dlls/kernel32/volume.c +index 1509d73..1e3ff7b 100644 +--- a/dlls/kernel32/volume.c ++++ b/dlls/kernel32/volume.c +@@ -853,7 +853,8 @@ fill_fs_info: /* now fill in the information that depends on the file system ty + default: + if (fsname) lstrcpynW( fsname, ntfsW, fsname_len ); + if (filename_len) *filename_len = 255; +- if (flags) *flags = FILE_CASE_PRESERVED_NAMES | FILE_PERSISTENT_ACLS; ++ if (flags) *flags = FILE_CASE_PRESERVED_NAMES | FILE_PERSISTENT_ACLS ++ | FILE_SUPPORTS_REPARSE_POINTS; + break; + } + ret = TRUE; +-- +1.7.9.5 + diff --git a/patches/08-Junction_Points/0007-ntdll-tests-Add-test-for-deleting-junction-point-tar.patch b/patches/08-Junction_Points/0007-ntdll-tests-Add-test-for-deleting-junction-point-tar.patch new file mode 100644 index 00000000..c8f54f1f --- /dev/null +++ b/patches/08-Junction_Points/0007-ntdll-tests-Add-test-for-deleting-junction-point-tar.patch @@ -0,0 +1,34 @@ +From f70c0d8ae00629148df5e16efb30960704b8bbf8 Mon Sep 17 00:00:00 2001 +From: "Erich E. Hoover" +Date: Thu, 16 Jan 2014 21:06:24 -0700 +Subject: ntdll/tests: Add test for deleting junction point target. + +--- + dlls/ntdll/tests/file.c | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c +index f94a61c..ffb9ff2 100644 +--- a/dlls/ntdll/tests/file.c ++++ b/dlls/ntdll/tests/file.c +@@ -2826,6 +2826,17 @@ static void test_junction_points(void) + ok(dwret != (DWORD)~0, "Junction point doesn't exist (attributes: 0x%x)!\n", dwret); + ok(dwret & FILE_ATTRIBUTE_REPARSE_POINT, "File is not a junction point! (attributes: 0x%x)\n", dwret); + ++ /* Test deleting a junction point's target */ ++ dwret = GetFileAttributesW(junction_path); ++ ok(dwret == 0x410 || broken(dwret == 0x430) /* win2k */, ++ "Unexpected junction point attributes (0x%x != 0x410)!\n", dwret); ++ bret = RemoveDirectoryW(target_path); ++ ok(bret, "Failed to delete junction point target!\n"); ++ ok(dwret == 0x410 || broken(dwret == 0x430) /* win2k */, ++ "Unexpected junction point attributes (0x%x != 0x410)!\n", dwret); ++ bret = CreateDirectoryW(target_path, NULL); ++ ok(bret, "Failed to create junction point target directory.\n"); ++ + cleanup: + /* Cleanup */ + pRtlFreeUnicodeString( &nameW ); +-- +1.7.9.5 + diff --git a/patches/08-Junction_Points/0008-ntdll-Use-relative-paths-for-creating-links.patch b/patches/08-Junction_Points/0008-ntdll-Use-relative-paths-for-creating-links.patch new file mode 100644 index 00000000..255e9393 --- /dev/null +++ b/patches/08-Junction_Points/0008-ntdll-Use-relative-paths-for-creating-links.patch @@ -0,0 +1,69 @@ +From f8bf15e30d5e0b9e30ceb644c07449b7782efb72 Mon Sep 17 00:00:00 2001 +From: "Erich E. Hoover" +Date: Thu, 16 Jan 2014 21:07:43 -0700 +Subject: ntdll: Use relative paths for creating links. + +--- + dlls/ntdll/file.c | 39 +++++++++++++++++++++++++++++++++++++++ + 1 file changed, 39 insertions(+) + +diff --git a/dlls/ntdll/file.c b/dlls/ntdll/file.c +index b4e06d1..6907b2b 100644 +--- a/dlls/ntdll/file.c ++++ b/dlls/ntdll/file.c +@@ -1487,6 +1487,7 @@ NTSTATUS FILE_CreateSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer) + int dest_fd, needs_close; + UNICODE_STRING nt_dest; + NTSTATUS status; ++ char *p; + + if ((status = server_get_unix_fd( handle, FILE_SPECIAL_ACCESS, &dest_fd, &needs_close, NULL, NULL ))) + return status; +@@ -1500,6 +1501,44 @@ NTSTATUS FILE_CreateSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer) + goto cleanup; + dest_allocated = TRUE; + ++ p = strstr(unix_src.Buffer, "/dosdevices/"); ++ if (p) ++ { ++ int count = -1; /* do not count the slash at the end of dosdevices or the last directory */ ++ ++ p += 11; /* strlen("/dosdevices") */ ++ do ++ { ++ p++; /* skip the slash */ ++ count++; ++ p = strchr(p, '/'); ++ } while(p); ++ FIXME("found %d directories up.\n", count); ++ p = strstr(unix_dest.Buffer, "/dosdevices/"); ++ if (p) ++ { ++ ANSI_STRING tmp; ++ int dest_len; ++ char *d; ++ ++ p += 12; /* strlen("/dosdevices/") */ ++ dest_len = unix_dest.Length - (p-unix_dest.Buffer) + 1; ++ tmp.Length = dest_len + 3*count; /* strlen("../") = 3 */ ++ tmp.Buffer = RtlAllocateHeap(GetProcessHeap(), 0, tmp.Length); ++ d = tmp.Buffer; ++ for(; count > 0; count--) ++ { ++ (d++)[0] = '.'; ++ (d++)[0] = '.'; ++ (d++)[0] = '/'; ++ } ++ memcpy(d, p, dest_len); ++ RtlFreeAnsiString( &unix_dest ); ++ unix_dest.Length = tmp.Length; ++ unix_dest.Buffer = tmp.Buffer; ++ } ++ } ++ + TRACE("Linking %s to %s\n", unix_src.Buffer, unix_dest.Buffer); + + /* Produce the link in a temporary location */ +-- +1.7.9.5 + diff --git a/patches/08-Junction_Points/4cd13e94-7f2d-11e3-b5eb-0090f5c75ad5.def b/patches/08-Junction_Points/4cd13e94-7f2d-11e3-b5eb-0090f5c75ad5.def new file mode 100644 index 00000000..58021752 --- /dev/null +++ b/patches/08-Junction_Points/4cd13e94-7f2d-11e3-b5eb-0090f5c75ad5.def @@ -0,0 +1,4 @@ +Revision: 1 +Author: Erich E. Hoover +Title: Support for junction points/reparse points. + diff --git a/patches/patch-list.patch b/patches/patch-list.patch index 93a166cc..677d5185 100644 --- a/patches/patch-list.patch +++ b/patches/patch-list.patch @@ -33,7 +33,7 @@ diff --git a/libs/wine/config.c b/libs/wine/config.c index a273502..5fa0cd5 100644 --- a/libs/wine/config.c +++ b/libs/wine/config.c -@@ -478,6 +478,34 @@ const char *wine_get_version(void) +@@ -478,6 +478,35 @@ const char *wine_get_version(void) return PACKAGE_VERSION; } @@ -50,6 +50,7 @@ index a273502..5fa0cd5 100644 + { "cbe240e8-2c58-430a-b61c-7fbb9d0e1e11:1", "Sebastian Lackner", "Change return value of stub SetNamedPipeHandleState to TRUE." }, + { "00273da7-72f8-4025-9e96-0c2bc95dacdb:2", "Maarten Lankhorst", "Winepulse patches extracted from https://launchpad.net/~mlankhorst/+archive/ppa/+files/wine1.7_1.7.10-0ubuntu1~saucy1.debian.tar.gz." }, + { "29b2af38-7edd-11e3-a08d-0090f5c75ad5:1", "Erich E. Hoover", "Add support for security access parameters for named pipes." }, ++ { "4cd13e94-7f2d-11e3-b5eb-0090f5c75ad5:1", "Erich E. Hoover", "Support for junction points/reparse points." }, + { "5fb1f5c8-7f17-11e3-9b62-0090f5c75ad5:1", "Erich E. Hoover", "Implement TransmitFile." }, + { "0b21d7ac-0387-4493-aa38-fbafe3e749f5:1", "Michael Müller", "Decrease minimum SetTimer interval from 15 to 5 ms." }, + { "19835498-8d90-4673-867e-2376af4d7c76:1", "Sebastian Lackner", "Allow to set wined3d strictDrawOrdering via environment variable." },