From 78fe8add28f8385a2d2aae635ff16c027f6240f7 Mon Sep 17 00:00:00 2001
From: "Erich E. Hoover" <erich.e.hoover@gmail.com>
Date: Thu, 16 Jan 2014 21:06:24 -0700
Subject: [PATCH] ntdll: Add support for absolute symlink creation.

Signed-off-by: Erich E. Hoover <erich.e.hoover@gmail.com>
---
 dlls/ntdll/tests/file.c | 117 ++++++++++++++++++++++++++++++++++------
 dlls/ntdll/unix/file.c  |  35 ++++++++++--
 include/ntifs.h         |  10 ++++
 3 files changed, 143 insertions(+), 19 deletions(-)

diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c
index 2953c77f0ad..81fa9592e3d 100644
--- a/dlls/ntdll/tests/file.c
+++ b/dlls/ntdll/tests/file.c
@@ -5205,26 +5205,50 @@ static void test_mailslot_name(void)
     CloseHandle( device );
 }
 
-static INT build_reparse_buffer(const WCHAR *filename, REPARSE_DATA_BUFFER **pbuffer)
+static INT build_reparse_buffer(const WCHAR *filename, ULONG tag, REPARSE_DATA_BUFFER **pbuffer)
 {
     static INT header_size = offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer);
     INT buffer_size, struct_size, data_size, string_len, prefix_len;
     WCHAR *subst_dest, *print_dest;
     REPARSE_DATA_BUFFER *buffer;
 
-    struct_size = offsetof(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer[0]);
+    switch(tag)
+    {
+    case IO_REPARSE_TAG_MOUNT_POINT:
+        struct_size = offsetof(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer[0]);
+        break;
+    case IO_REPARSE_TAG_SYMLINK:
+        struct_size = offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer[0]);
+        break;
+    default:
+        return 0;
+    }
     prefix_len = strlen("\\??\\");
     string_len = lstrlenW(&filename[prefix_len]);
     data_size = (prefix_len + 2 * string_len + 2) * sizeof(WCHAR);
     buffer_size = struct_size + data_size;
     buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, buffer_size);
-    buffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
+    buffer->ReparseTag = tag;
     buffer->ReparseDataLength = struct_size - header_size + data_size;
-    buffer->MountPointReparseBuffer.SubstituteNameLength = (prefix_len + string_len) * sizeof(WCHAR);
-    buffer->MountPointReparseBuffer.PrintNameOffset = (prefix_len + string_len + 1) * sizeof(WCHAR);
-    buffer->MountPointReparseBuffer.PrintNameLength = string_len * sizeof(WCHAR);
-    subst_dest = &buffer->MountPointReparseBuffer.PathBuffer[0];
-    print_dest = &buffer->MountPointReparseBuffer.PathBuffer[prefix_len + string_len + 1];
+    switch(tag)
+    {
+    case IO_REPARSE_TAG_MOUNT_POINT:
+        buffer->MountPointReparseBuffer.SubstituteNameLength = (prefix_len + string_len) * sizeof(WCHAR);
+        buffer->MountPointReparseBuffer.PrintNameOffset = (prefix_len + string_len + 1) * sizeof(WCHAR);
+        buffer->MountPointReparseBuffer.PrintNameLength = string_len * sizeof(WCHAR);
+        subst_dest = &buffer->MountPointReparseBuffer.PathBuffer[0];
+        print_dest = &buffer->MountPointReparseBuffer.PathBuffer[prefix_len + string_len + 1];
+        break;
+    case IO_REPARSE_TAG_SYMLINK:
+        buffer->SymbolicLinkReparseBuffer.SubstituteNameLength = (prefix_len + string_len) * sizeof(WCHAR);
+        buffer->SymbolicLinkReparseBuffer.PrintNameOffset = (prefix_len + string_len + 1) * sizeof(WCHAR);
+        buffer->SymbolicLinkReparseBuffer.PrintNameLength = string_len * sizeof(WCHAR);
+        subst_dest = &buffer->SymbolicLinkReparseBuffer.PathBuffer[0];
+        print_dest = &buffer->SymbolicLinkReparseBuffer.PathBuffer[prefix_len + string_len + 1];
+        break;
+    default:
+        return 0;
+    }
     lstrcpyW(subst_dest, filename);
     lstrcpyW(print_dest, &filename[prefix_len]);
     *pbuffer = buffer;
@@ -5244,10 +5268,12 @@ static void test_reparse_points(void)
     REPARSE_DATA_BUFFER *buffer = NULL;
     DWORD dwret, dwLen, dwFlags, err;
     INT buffer_len, string_len;
+    HANDLE handle, token;
     IO_STATUS_BLOCK iosb;
     UNICODE_STRING nameW;
-    HANDLE handle;
+    TOKEN_PRIVILEGES tp;
     WCHAR *dest;
+    LUID luid;
     BOOL bret;
 
     /* Create a temporary folder for the junction point tests */
@@ -5294,7 +5320,7 @@ static void test_reparse_points(void)
     }
     dwret = NtQueryInformationFile(handle, &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);
+    buffer_len = build_reparse_buffer(nameW.Buffer, IO_REPARSE_TAG_MOUNT_POINT, &buffer);
     bret = DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT, (LPVOID)buffer, buffer_len, NULL, 0, &dwret, 0);
     ok(bret, "Failed to create junction point! (0x%x)\n", GetLastError());
 
@@ -5335,7 +5361,7 @@ static void test_reparse_points(void)
     HeapFree(GetProcessHeap(), 0, buffer);
     handle = CreateFileW(reparse_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);
+    buffer_len = build_reparse_buffer(nameW.Buffer, IO_REPARSE_TAG_MOUNT_POINT, &buffer);
     bret = DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT, (LPVOID)buffer, buffer_len, NULL, 0, &dwret, 0);
     ok(bret, "Failed to create junction point! (0x%x)\n", GetLastError());
     CloseHandle(handle);
@@ -5350,7 +5376,7 @@ static void test_reparse_points(void)
     ok(bret, "Failed to create junction point target directory.\n");
     handle = CreateFileW(reparse_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);
+    buffer_len = build_reparse_buffer(nameW.Buffer, IO_REPARSE_TAG_MOUNT_POINT, &buffer);
     bret = DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT, (LPVOID)buffer, buffer_len, NULL, 0, &dwret, 0);
     ok(bret, "Failed to create junction point! (0x%x)\n", GetLastError());
     CloseHandle(handle);
@@ -5363,14 +5389,73 @@ static void test_reparse_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(reparse_path);
+    ok(dwret == 0x410 || broken(dwret == 0x430) /* win2k */ || broken(dwret == 0xc10) /* vista */,
+       "Unexpected junction point attributes (0x%x != 0x410)!\n", dwret);
+    bret = RemoveDirectoryW(target_path);
+    ok(bret, "Failed to delete junction point target!\n");
+    bret = CreateDirectoryW(target_path, NULL);
+    ok(bret, "Failed to create junction point target directory.\n");
+
+    /* Establish permissions for symlink creation */
+    bret = OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token);
+    ok(bret, "OpenProcessToken failed: %u\n", GetLastError());
+    bret = LookupPrivilegeValueA(NULL, "SeCreateSymbolicLinkPrivilege", &luid);
+    todo_wine ok(bret || broken(!bret && GetLastError() == ERROR_NO_SUCH_PRIVILEGE) /* winxp */,
+                 "LookupPrivilegeValue failed: %u\n", GetLastError());
+    if (bret)
+    {
+        tp.PrivilegeCount = 1;
+        tp.Privileges[0].Luid = luid;
+        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+        bret = AdjustTokenPrivileges(token, FALSE, &tp, 0, NULL, NULL);
+        ok(bret, "AdjustTokenPrivileges failed: %u\n", GetLastError());
+        if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
+        {
+            win_skip("Insufficient permissions to perform symlink tests.\n");
+            goto cleanup;
+        }
+    }
+
+    /* Delete the junction point directory and create a blank slate for symlink tests */
+    bret = RemoveDirectoryW(reparse_path);
+    ok(bret, "Failed to delete junction point!\n");
+    bret = CreateDirectoryW(reparse_path, NULL);
+    ok(bret, "Failed to create junction point directory.\n");
+    dwret = GetFileAttributesW(reparse_path);
+    ok(dwret != (DWORD)~0, "Path doesn't exist (attributes: 0x%x)!\n", dwret);
+    ok(!(dwret & FILE_ATTRIBUTE_REPARSE_POINT), "File is already a reparse point! (attributes: %d)\n", dwret);
+
+    /* Create the directory symlink */
+    HeapFree(GetProcessHeap(), 0, buffer);
+    handle = CreateFileW(reparse_path, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING,
+                         FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0);
+    if (handle == INVALID_HANDLE_VALUE)
+    {
+        win_skip("Failed to open symlink directory handle (0x%x).\n", GetLastError());
+        goto cleanup;
+    }
+    dwret = NtQueryInformationFile(handle, &iosb, &old_attrib, sizeof(old_attrib), FileBasicInformation);
+    ok(dwret == STATUS_SUCCESS, "Failed to get symlink folder's attributes (0x%x).\n", dwret);
+    buffer_len = build_reparse_buffer(nameW.Buffer, IO_REPARSE_TAG_SYMLINK, &buffer);
+    bret = DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT, (LPVOID)buffer, buffer_len, NULL, 0, &dwret, 0);
+    CloseHandle(handle);
+    ok(bret, "Failed to create symlink! (0x%x)\n", GetLastError());
+
+    /* Check the file attributes of the symlink */
+    dwret = GetFileAttributesW(reparse_path);
+    ok(dwret != (DWORD)~0, "Symlink doesn't exist (attributes: 0x%x)!\n", dwret);
+    ok(dwret & FILE_ATTRIBUTE_REPARSE_POINT, "File is not a symlink! (attributes: %d)\n", dwret);
+
 cleanup:
     /* Cleanup */
     pRtlFreeUnicodeString(&nameW);
     HeapFree(GetProcessHeap(), 0, buffer);
-    bret = RemoveDirectoryW(reparse_path);
-    ok(bret, "Failed to remove temporary reparse point directory!\n");
-    bret = RemoveDirectoryW(target_path);
-    ok(bret, "Failed to remove temporary target directory!\n");
+    RemoveDirectoryW(reparse_path);
+    DeleteFileW(reparse_path);
+    RemoveDirectoryW(target_path);
+    DeleteFileW(target_path);
     RemoveDirectoryW(path);
 }
 
diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c
index 9ad0d7dcb39..7150099f750 100644
--- a/dlls/ntdll/unix/file.c
+++ b/dlls/ntdll/unix/file.c
@@ -5664,18 +5664,34 @@ static void ignore_server_ioctl_struct_holes( ULONG code, const void *in_buffer,
 NTSTATUS FILE_CreateSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer)
 {
     BOOL src_allocated = FALSE, dest_allocated = FALSE, tempdir_created = FALSE;
-    int dest_len = buffer->MountPointReparseBuffer.SubstituteNameLength;
-    int offset = buffer->MountPointReparseBuffer.SubstituteNameOffset;
-    WCHAR *dest = &buffer->MountPointReparseBuffer.PathBuffer[offset];
     char tmpdir[PATH_MAX], tmplink[PATH_MAX], *d;
     SIZE_T unix_dest_len = PATH_MAX;
     char *unix_src, *unix_dest;
     char magic_dest[PATH_MAX];
     int dest_fd, needs_close;
     UNICODE_STRING nt_dest;
+    int dest_len, offset;
     NTSTATUS status;
+    struct stat st;
+    WCHAR *dest;
     int i;
 
+    switch(buffer->ReparseTag)
+    {
+    case IO_REPARSE_TAG_MOUNT_POINT:
+        dest_len = buffer->MountPointReparseBuffer.SubstituteNameLength;
+        offset = buffer->MountPointReparseBuffer.SubstituteNameOffset;
+        dest = &buffer->MountPointReparseBuffer.PathBuffer[offset];
+        break;
+    case IO_REPARSE_TAG_SYMLINK:
+        dest_len = buffer->SymbolicLinkReparseBuffer.SubstituteNameLength;
+        offset = buffer->SymbolicLinkReparseBuffer.SubstituteNameOffset;
+        dest = &buffer->SymbolicLinkReparseBuffer.PathBuffer[offset];
+        break;
+    default:
+        return STATUS_NOT_IMPLEMENTED;
+    }
+
     if ((status = server_get_unix_fd( handle, FILE_SPECIAL_ACCESS, &dest_fd, &needs_close, NULL, NULL )))
         return status;
 
@@ -5710,6 +5726,18 @@ NTSTATUS FILE_CreateSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer)
             strcat( magic_dest, "." );
         strcat( magic_dest, "/" );
     }
+    /* Encode the type (file or directory) if NT symlink */
+    if (buffer->ReparseTag == IO_REPARSE_TAG_SYMLINK)
+    {
+        if (fstat( dest_fd, &st ) == -1)
+        {
+            status = errno_to_status( errno );
+            goto cleanup;
+        }
+        if (S_ISDIR(st.st_mode))
+            strcat( magic_dest, "." );
+        strcat( magic_dest, "/" );
+    }
     strcat( magic_dest, unix_dest );
 
     /* Produce the link in a temporary location in the same folder */
@@ -6072,6 +6100,7 @@ NTSTATUS WINAPI NtFsControlFile( HANDLE handle, HANDLE event, PIO_APC_ROUTINE ap
         switch(buffer->ReparseTag)
         {
         case IO_REPARSE_TAG_MOUNT_POINT:
+        case IO_REPARSE_TAG_SYMLINK:
             status = FILE_CreateSymlink( handle, buffer );
             break;
         default:
diff --git a/include/ntifs.h b/include/ntifs.h
index 4539b89d583..ab3273d3f81 100644
--- a/include/ntifs.h
+++ b/include/ntifs.h
@@ -21,11 +21,20 @@
 #ifndef __WINE_NTIFS_H
 #define __WINE_NTIFS_H
 
+#include <pshpack2.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;
@@ -38,6 +47,7 @@ typedef struct _REPARSE_DATA_BUFFER {
         } GenericReparseBuffer;
     };
 } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
+#include <poppack.h>
 
 typedef struct _REPARSE_GUID_DATA_BUFFER {
     DWORD ReparseTag;
-- 
2.27.0

