From 7c02ce3203b8796455d12423b960b720e90a9639 Mon Sep 17 00:00:00 2001
From: "Erich E. Hoover" <erich.e.hoover@gmail.com>
Date: Thu, 11 Apr 2019 12:31:16 -0600
Subject: ntdll: Add support for reading relative symlinks.

Signed-off-by: Erich E. Hoover <erich.e.hoover@gmail.com>
---
 dlls/ntdll/file.c       | 44 ++++++++++++++++++++++++++++++++++++++++-
 dlls/ntdll/tests/file.c | 13 +++++++++++-
 2 files changed, 55 insertions(+), 2 deletions(-)

diff --git a/dlls/ntdll/file.c b/dlls/ntdll/file.c
index 8487bb5158..5df2d8c3fa 100644
--- a/dlls/ntdll/file.c
+++ b/dlls/ntdll/file.c
@@ -1831,6 +1831,7 @@ NTSTATUS FILE_GetSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer, ULONG out_s
     BOOL dest_allocated = FALSE;
     int dest_fd, needs_close;
     UNICODE_STRING nt_dest;
+    int path_len = 0;
     DWORD max_length;
     NTSTATUS status;
     ULONG flags = 0;
@@ -1858,6 +1859,11 @@ NTSTATUS FILE_GetSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer, ULONG out_s
 
     /* Decode the reparse tag from the symlink */
     p = unix_dest.Buffer;
+    if (*p == '.')
+    {
+        flags = SYMLINK_FLAG_RELATIVE;
+        p++;
+    }
     if (*p++ != '/')
     {
         status = STATUS_NOT_IMPLEMENTED;
@@ -1894,10 +1900,46 @@ NTSTATUS FILE_GetSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer, ULONG out_s
     unix_dest.Length -= (p - unix_dest.Buffer);
     memmove(unix_dest.Buffer, p, unix_dest.Length);
 
+    /* convert the relative path into an absolute path */
+    if (flags == SYMLINK_FLAG_RELATIVE)
+    {
+        int offset = unix_src.Length + 2;
+        char *d;
+        memcpy( &unix_dest.Buffer[offset], unix_dest.Buffer, unix_dest.Length );
+        unix_dest.Buffer[offset+unix_dest.Length] = 0;
+        memcpy( unix_dest.Buffer, unix_src.Buffer, unix_src.Length );
+        unix_dest.Buffer[unix_src.Length] = 0;
+        d = dirname( unix_dest.Buffer );
+        if (d != unix_dest.Buffer) strcpy( unix_dest.Buffer, d );
+        strcat( unix_dest.Buffer, "/" );
+        path_len = strlen( unix_dest.Buffer );
+        memmove( &unix_dest.Buffer[path_len], &unix_dest.Buffer[offset], unix_dest.Length + 1 );
+        unix_dest.Length = strlen( unix_dest.Buffer );
+    }
     if ((status = wine_unix_to_nt_file_name( &unix_dest, &nt_dest )))
         goto cleanup;
+    /* remove the relative path from the NT path */
+    if (flags == SYMLINK_FLAG_RELATIVE)
+    {
+        UNICODE_STRING nt_path;
+        int relative_offset;
+
+        unix_dest.Length = path_len;
+        if ((status = wine_unix_to_nt_file_name( &unix_dest, &nt_path )))
+            goto cleanup;
+        relative_offset = strlenW( nt_path.Buffer );
+        if (strncmpW( nt_path.Buffer, nt_dest.Buffer, relative_offset ) != 0)
+        {
+            RtlFreeUnicodeString( &nt_path );
+            status = STATUS_IO_REPARSE_DATA_INVALID;
+            goto cleanup;
+        }
+        RtlFreeUnicodeString( &nt_path );
+        nt_dest.Length = strlenW( &nt_dest.Buffer[relative_offset] ) * sizeof(WCHAR);
+        memmove( nt_dest.Buffer, &nt_dest.Buffer[relative_offset], nt_dest.Length + sizeof(WCHAR) );
+    }
 
-    prefix_len = strlen("\\??\\");
+    prefix_len = (flags == SYMLINK_FLAG_RELATIVE) ? 0 : strlen("\\??\\");
     switch(buffer->ReparseTag)
     {
     case IO_REPARSE_TAG_MOUNT_POINT:
diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c
index 4a6a28e879..c13ee84bf0 100644
--- a/dlls/ntdll/tests/file.c
+++ b/dlls/ntdll/tests/file.c
@@ -5117,9 +5117,20 @@ static void test_reparse_points(void)
     ok(dwret == STATUS_SUCCESS, "Failed to get symlink folder's attributes (0x%x).\n", dwret);
     buffer_len = build_reparse_buffer(targetW, IO_REPARSE_TAG_SYMLINK, SYMLINK_FLAG_RELATIVE, &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());
 
+    /* Read back the relative symlink */
+    HeapFree(GetProcessHeap(), 0, buffer);
+    buffer_len = sizeof(*buffer) + MAX_PATH*sizeof(WCHAR);
+    buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, buffer_len);
+    bret = DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, (LPVOID)buffer, buffer_len, &dwret, 0);
+    ok(bret, "Failed to read relative symlink!\n");
+    string_len = buffer->SymbolicLinkReparseBuffer.SubstituteNameLength;
+    dest = &buffer->SymbolicLinkReparseBuffer.PathBuffer[buffer->SymbolicLinkReparseBuffer.SubstituteNameOffset/sizeof(WCHAR)];
+    ok((memcmp(dest, targetW, string_len) == 0), "Symlink destination does not match ('%s' != '%s')!\n",
+                                                 wine_dbgstr_w(dest), wine_dbgstr_w(targetW));
+    CloseHandle(handle);
+
 cleanup:
     /* Cleanup */
     pRtlFreeUnicodeString(&nameW);
-- 
2.17.1

