From: Daniel Lehman Subject: [PATCH 3/5] ntdll: Add support for junction point creation. Message-Id: <34102a9974b64552b236838ffb37b15f@RED-INF-MXMB-P4.esri.com> Date: Thu, 16 Nov 2017 00:09:57 +0000 From 524abfab1076ff37678e630fa594f585062fd9e9 Mon Sep 17 00:00:00 2001 From: "Erich E. Hoover" Date: Thu, 16 Jan 2014 20:56:49 -0700 Subject: [PATCH 3/5] ntdll: Add support for junction point creation. Signed-off-by: Daniel Lehman --- dlls/ntdll/file.c | 115 ++++++++++++++++++++++++++++++++++++++++++++++ dlls/ntdll/tests/file.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++- include/ddk/ntifs.h | 55 ++++++++++++++++++++++ 3 files changed, 289 insertions(+), 1 deletion(-) diff --git a/dlls/ntdll/file.c b/dlls/ntdll/file.c index ca2afa0..17bdf1d 100644 --- a/dlls/ntdll/file.c +++ b/dlls/ntdll/file.c @@ -108,6 +108,7 @@ #include "winioctl.h" #include "ddk/ntddk.h" #include "ddk/ntddser.h" +#include "ddk/ntifs.h" WINE_DEFAULT_DEBUG_CHANNEL(ntdll); WINE_DECLARE_DEBUG_CHANNEL(winediag); @@ -1653,6 +1654,90 @@ 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) +{ + static const char XXXXXX[] = ".XXXXXX"; + int dest_len = buffer->u.MountPointReparseBuffer.SubstituteNameLength; + int offset = buffer->u.MountPointReparseBuffer.SubstituteNameOffset; + WCHAR *dest = &buffer->u.MountPointReparseBuffer.PathBuffer[offset/sizeof(WCHAR)]; + ANSI_STRING unix_src, unix_dest, tmp_link; + UNICODE_STRING nt_dest; + int fd, needs_close; + NTSTATUS status; + + /* ntifs.h header suggests FILE_SPECIAL_ACCESS but it does not return ACCESS_DENIED */ + if ((status = server_get_unix_fd( handle, FILE_WRITE_ACCESS, &fd, &needs_close, NULL, NULL ))) + return status; + if (needs_close) close(fd); + + if ((status = server_get_unix_name( handle, &unix_src ))) + return status; + + unix_dest.Buffer = NULL; + tmp_link.Length = unix_src.Length + sizeof(XXXXXX); + tmp_link.Buffer = RtlAllocateHeap( GetProcessHeap(), 0, tmp_link.Length + 1 ); + if (!tmp_link.Buffer) + { + status = STATUS_NO_MEMORY; + goto cleanup; + } + + memcpy( tmp_link.Buffer, unix_src.Buffer, unix_src.Length ); + tmp_link.Buffer[unix_src.Length] = 0; + + 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; + + TRACE("Linking %s to %s\n", unix_src.Buffer, unix_dest.Buffer); + + /* Produce temporary link where final link will be placed */ + while (1) + { + memcpy( &tmp_link.Buffer[unix_src.Length], XXXXXX, sizeof(XXXXXX) ); + fd = mkstemps( tmp_link.Buffer, 0 ); + if (fd != -1) + { + close(fd); + if (!unlink( tmp_link.Buffer )) + { + if (!symlink( unix_dest.Buffer, tmp_link.Buffer )) + break; + } + } + } + + /* Atomically move the link into position */ + if (rename( tmp_link.Buffer, unix_src.Buffer )) + { + unlink( tmp_link.Buffer ); + WARN("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: + RtlFreeAnsiString( &tmp_link ); + RtlFreeAnsiString( &unix_src ); + RtlFreeAnsiString( &unix_dest ); + return status; +} + + /************************************************************************** * NtFsControlFile [NTDLL.@] * ZwFsControlFile [NTDLL.@] @@ -1747,6 +1832,36 @@ NTSTATUS WINAPI NtFsControlFile(HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc io->Information = 0; status = STATUS_SUCCESS; break; + case FSCTL_SET_REPARSE_POINT: + { + REPARSE_DATA_BUFFER *buffer = in_buffer; + + if (!buffer) + { + status = STATUS_INVALID_BUFFER_SIZE; + break; + } + + if (buffer->ReparseDataLength > in_size) + { + status = STATUS_IO_REPARSE_DATA_INVALID; + break; + } + + switch(buffer->ReparseTag) + { + case IO_REPARSE_TAG_MOUNT_POINT: + status = FILE_CreateSymlink( handle, buffer ); + if (status == STATUS_SUCCESS) + io->Information = 0; + break; + default: + FIXME("stub: FSCTL_SET_REPARSE_POINT(%x)\n", buffer->ReparseTag); + status = STATUS_NOT_IMPLEMENTED; + break; + } + break; + } default: return server_ioctl_file( handle, event, apc, apc_context, io, code, in_buffer, in_size, out_buffer, out_size ); diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c index f88d16b..072d8cc 100644 --- a/dlls/ntdll/tests/file.c +++ b/dlls/ntdll/tests/file.c @@ -32,11 +32,14 @@ * definition errors when we get to winnt.h */ #define WIN32_NO_STATUS +#define NONAMELESSUNION #include "wine/test.h" #include "winternl.h" #include "winuser.h" #include "winioctl.h" +#include "ddk/wdm.h" +#include "ddk/ntifs.h" #ifndef IO_COMPLETION_ALL_ACCESS #define IO_COMPLETION_ALL_ACCESS 0x001F0003 @@ -4201,7 +4204,7 @@ static void test_ioctl(void) &peek_buf, sizeof(peek_buf)); todo_wine ok(status == STATUS_INVALID_DEVICE_REQUEST, "NtFsControlFile failed: %x\n", status); - ok(iosb.Status == 0x55555555, "iosb.Status = %x\n", iosb.Status); + ok(iosb.u.Status == 0x55555555, "iosb.Status = %x\n", iosb.u.Status); CloseHandle(event); CloseHandle(file); @@ -4254,6 +4257,120 @@ static void test_flush_buffers_file(void) DeleteFileA(buffer); } +static INT build_reparse_buffer(WCHAR *filename, REPARSE_DATA_BUFFER *buffer) +{ + INT buffer_len, string_len; + + string_len = (lstrlenW(filename)+1)*sizeof(WCHAR); + buffer_len = FIELD_OFFSET(REPARSE_DATA_BUFFER, u.MountPointReparseBuffer.PathBuffer[1]) + string_len; + buffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; + buffer->ReparseDataLength = sizeof(buffer->u.MountPointReparseBuffer) + string_len; + buffer->u.MountPointReparseBuffer.SubstituteNameLength = string_len - sizeof(WCHAR); + buffer->u.MountPointReparseBuffer.PrintNameOffset = string_len; + memcpy(buffer->u.MountPointReparseBuffer.PathBuffer, filename, string_len); + return buffer_len; +} + +static void test_junction_points(void) +{ + static BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; + REPARSE_DATA_BUFFER *buffer = (REPARSE_DATA_BUFFER *)buf; + 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}; + DWORD dwLen, dwFlags; + UNICODE_STRING nameW; + IO_STATUS_BLOCK iosb; + NTSTATUS status; + 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); + + /* Check access for creating junction point */ + hJunction = CreateFileW(junction_path, GENERIC_READ, 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); + memset(&iosb, 0xff, sizeof(iosb)); + status = pNtFsControlFile(hJunction, NULL, NULL, NULL, &iosb, FSCTL_SET_REPARSE_POINT, buffer, buffer_len, NULL, 0); + ok(status == STATUS_ACCESS_DENIED, "expected %x, got %x\n", STATUS_ACCESS_DENIED, status); + ok(iosb.Information == ~0, "expected ~0, got %lx\n", iosb.Information); + CloseHandle(hJunction); + + /* 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); + + status = pNtFsControlFile(hJunction, NULL, NULL, NULL, &iosb, FSCTL_SET_REPARSE_POINT, NULL, 0, NULL, 0); + ok(status == STATUS_INVALID_BUFFER_SIZE, "expected %x, got %x\n", STATUS_INVALID_BUFFER_SIZE, status); + ok(iosb.Information == ~0, "expected ~0, got %lx\n", iosb.Information); + + buffer_len = build_reparse_buffer(nameW.Buffer, buffer); + status = pNtFsControlFile(hJunction, NULL, NULL, NULL, &iosb, FSCTL_SET_REPARSE_POINT, buffer, REPARSE_DATA_BUFFER_HEADER_SIZE - 2, NULL, 0); + ok(status == STATUS_IO_REPARSE_DATA_INVALID, "expected %x, got %x\n", STATUS_IO_REPARSE_DATA_INVALID, status); + ok(iosb.Information == ~0, "expected ~0, got %lx\n", iosb.Information); + + status = pNtFsControlFile(hJunction, NULL, NULL, NULL, &iosb, FSCTL_SET_REPARSE_POINT, buffer, buffer_len / 2, NULL, 0); + ok(status == STATUS_IO_REPARSE_DATA_INVALID, "expected %x, got %x\n", STATUS_IO_REPARSE_DATA_INVALID, status); + ok(iosb.Information == ~0, "expected ~0, got %lx\n", iosb.Information); + + status = pNtFsControlFile(hJunction, NULL, NULL, NULL, &iosb, FSCTL_SET_REPARSE_POINT, buffer, buffer_len, NULL, 0); + ok(status == STATUS_SUCCESS, "expected 0, got %x\n", status); + ok(!iosb.Information, "expected 0, got %lx\n", iosb.Information); + CloseHandle(hJunction); + +cleanup: + /* Cleanup */ + pRtlFreeUnicodeString( &nameW ); + 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"); @@ -4318,4 +4435,5 @@ START_TEST(file) test_query_attribute_information_file(); test_ioctl(); test_flush_buffers_file(); + test_junction_points(); } diff --git a/include/ddk/ntifs.h b/include/ddk/ntifs.h index 32c9e30..55fcf72 100644 --- a/include/ddk/ntifs.h +++ b/include/ddk/ntifs.h @@ -32,4 +32,59 @@ typedef struct _KQUEUE NTSTATUS WINAPI ObQueryNameString(PVOID,POBJECT_NAME_INFORMATION,ULONG,PULONG); +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; + } DUMMYUNIONNAME; +} 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_RESERVED_ZERO ((DWORD) 0x00000000) +#define IO_REPARSE_TAG_RESERVED_ONE ((DWORD) 0x00000001) +#define IO_REPARSE_TAG_MOUNT_POINT ((DWORD) 0xA0000003) +#define IO_REPARSE_TAG_HSM ((DWORD) 0xC0000004) +#define IO_REPARSE_TAG_DRIVER_EXTENDER ((DWORD) 0x80000005) +#define IO_REPARSE_TAG_HSM2 ((DWORD) 0x80000006) +#define IO_REPARSE_TAG_SIS ((DWORD) 0x80000007) +#define IO_REPARSE_TAG_DFS ((DWORD) 0x8000000A) +#define IO_REPARSE_TAG_FILTER_MANAGER ((DWORD) 0x8000000B) +#define IO_REPARSE_TAG_SYMLINK ((DWORD) 0xA000000C) +#define IO_REPARSE_TAG_DFSR ((DWORD) 0x80000012) +#define IO_REPARSE_TAG_NFS ((DWORD) 0x80000014) + +#define SYMLINK_FLAG_RELATIVE 1 + +#define REPARSE_GUID_DATA_BUFFER_HEADER_SIZE FIELD_OFFSET(REPARSE_GUID_DATA_BUFFER, GenericReparseBuffer) +#define REPARSE_DATA_BUFFER_HEADER_SIZE FIELD_OFFSET(REPARSE_DATA_BUFFER, u.GenericReparseBuffer) +#define MAXIMUM_REPARSE_DATA_BUFFER_SIZE 0x4000 + #endif -- 1.9.5