From 5658e73f218d01b0e11bc4575cf0502b8235ec2c 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/tests/file.c | 13 ++++++++-
 dlls/ntdll/unix/file.c  | 61 ++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 72 insertions(+), 2 deletions(-)

diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c
index 9b510fe9057..0db7372a0c8 100644
--- a/dlls/ntdll/tests/file.c
+++ b/dlls/ntdll/tests/file.c
@@ -5258,9 +5258,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);
diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c
index 3652fbb1a0d..e62a2529bb2 100644
--- a/dlls/ntdll/unix/file.c
+++ b/dlls/ntdll/unix/file.c
@@ -5820,6 +5820,7 @@ NTSTATUS FILE_GetSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer, ULONG out_s
     BOOL dest_allocated = FALSE;
     int dest_fd, needs_close;
     int unix_dest_len;
+    int path_len = 0;
     DWORD max_length;
     NTSTATUS status;
     ULONG flags = 0;
@@ -5845,6 +5846,11 @@ NTSTATUS FILE_GetSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer, ULONG out_s
 
     /* Decode the reparse tag from the symlink */
     p = unix_dest;
+    if (*p == '.')
+    {
+        flags = SYMLINK_FLAG_RELATIVE;
+        p++;
+    }
     if (*p++ != '/')
     {
         status = STATUS_NOT_IMPLEMENTED;
@@ -5881,6 +5887,25 @@ NTSTATUS FILE_GetSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer, ULONG out_s
     unix_dest_len -= (p - unix_dest);
     memmove(unix_dest, p, unix_dest_len);
 
+    /* convert the relative path into an absolute path */
+    if (flags == SYMLINK_FLAG_RELATIVE)
+    {
+        int unix_src_len = strlen(unix_src);
+        int offset = unix_src_len + 2;
+        char *d;
+
+        memcpy( &unix_dest[offset], unix_dest, unix_dest_len );
+        unix_dest[offset+unix_dest_len] = 0;
+        memcpy( unix_dest, unix_src, unix_src_len );
+        unix_dest[unix_src_len] = 0;
+        d = dirname( unix_dest );
+        if (d != unix_dest) strcpy( unix_dest, d );
+        strcat( unix_dest, "/" );
+        path_len = strlen( unix_dest );
+        memmove( &unix_dest[path_len], &unix_dest[offset], unix_dest_len + 1 );
+        unix_dest_len = strlen( unix_dest );
+    }
+    /* resolve the NT path */
     for (;;)
     {
         nt_dest = malloc( nt_dest_len * sizeof(WCHAR) );
@@ -5898,7 +5923,41 @@ NTSTATUS FILE_GetSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer, ULONG out_s
         goto cleanup;
     nt_dest_len *= sizeof(WCHAR);
 
-    prefix_len = strlen("\\??\\");
+    /* remove the relative path from the NT path */
+    if (flags == SYMLINK_FLAG_RELATIVE)
+    {
+        SIZE_T nt_path_len = PATH_MAX;
+        int relative_offset;
+        WCHAR *nt_path;
+
+        unix_dest_len = path_len;
+        for (;;)
+        {
+            nt_path = malloc( nt_path_len * sizeof(WCHAR) );
+            if (!nt_path)
+            {
+                status = STATUS_NO_MEMORY;
+                goto cleanup;
+            }
+            status = wine_unix_to_nt_file_name( unix_dest, nt_path, &nt_path_len );
+            if (status != STATUS_BUFFER_TOO_SMALL) break;
+            free( nt_path );
+        }
+        if (status != STATUS_SUCCESS)
+            goto cleanup;
+        relative_offset = lstrlenW( nt_path );
+        if (wcsnicmp( nt_path, nt_dest, relative_offset ) != 0)
+        {
+            free( nt_path );
+            status = STATUS_IO_REPARSE_DATA_INVALID;
+            goto cleanup;
+        }
+        free( nt_path );
+        nt_dest_len = lstrlenW( &nt_dest[relative_offset] ) * sizeof(WCHAR);
+        memmove( nt_dest, &nt_dest[relative_offset], nt_dest_len + sizeof(WCHAR) );
+    }
+
+    prefix_len = (flags == SYMLINK_FLAG_RELATIVE) ? 0 : strlen("\\??\\");
     switch(buffer->ReparseTag)
     {
     case IO_REPARSE_TAG_MOUNT_POINT:
-- 
2.17.1

