From c3cc1222c92372b8f260618835e547910ba6a07d Mon Sep 17 00:00:00 2001
From: "Erich E. Hoover" <erich.e.hoover@gmail.com>
Date: Thu, 11 Apr 2019 12:16:49 -0600
Subject: [PATCH] ntdll: Add support for relative symlink creation.

Signed-off-by: Erich E. Hoover <erich.e.hoover@gmail.com>
---
 dlls/ntdll/tests/file.c | 30 +++++++++++---
 dlls/ntdll/unix/file.c  | 90 +++++++++++++++++++++++++++++++++++++----
 include/ntifs.h         |  2 +
 3 files changed, 109 insertions(+), 13 deletions(-)

diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c
index 6e4e90b4bbf..9e05270a25e 100644
--- a/dlls/ntdll/tests/file.c
+++ b/dlls/ntdll/tests/file.c
@@ -5205,7 +5205,8 @@ static void test_mailslot_name(void)
     CloseHandle( device );
 }
 
-static INT build_reparse_buffer(const WCHAR *filename, ULONG tag, REPARSE_DATA_BUFFER **pbuffer)
+static INT build_reparse_buffer(const WCHAR *filename, ULONG tag, ULONG flags,
+                                REPARSE_DATA_BUFFER **pbuffer)
 {
     static INT header_size = offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer);
     INT buffer_size, struct_size, data_size, string_len, prefix_len;
@@ -5223,7 +5224,7 @@ static INT build_reparse_buffer(const WCHAR *filename, ULONG tag, REPARSE_DATA_B
     default:
         return 0;
     }
-    prefix_len = strlen("\\??\\");
+    prefix_len = (flags == SYMLINK_FLAG_RELATIVE) ? 0 : strlen("\\??\\");
     string_len = lstrlenW(&filename[prefix_len]);
     data_size = (prefix_len + 2 * string_len + 2) * sizeof(WCHAR);
     buffer_size = struct_size + data_size;
@@ -5243,6 +5244,7 @@ static INT build_reparse_buffer(const WCHAR *filename, ULONG tag, REPARSE_DATA_B
         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);
+        buffer->SymbolicLinkReparseBuffer.Flags = flags;
         subst_dest = &buffer->SymbolicLinkReparseBuffer.PathBuffer[0];
         print_dest = &buffer->SymbolicLinkReparseBuffer.PathBuffer[prefix_len + string_len + 1];
         break;
@@ -5320,7 +5322,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, IO_REPARSE_TAG_MOUNT_POINT, &buffer);
+    buffer_len = build_reparse_buffer(nameW.Buffer, IO_REPARSE_TAG_MOUNT_POINT, 0, &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());
 
@@ -5361,7 +5363,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, IO_REPARSE_TAG_MOUNT_POINT, &buffer);
+    buffer_len = build_reparse_buffer(nameW.Buffer, IO_REPARSE_TAG_MOUNT_POINT, 0, &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);
@@ -5376,7 +5378,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, IO_REPARSE_TAG_MOUNT_POINT, &buffer);
+    buffer_len = build_reparse_buffer(nameW.Buffer, IO_REPARSE_TAG_MOUNT_POINT, 0, &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);
@@ -5438,7 +5440,7 @@ static void test_reparse_points(void)
     }
     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);
+    buffer_len = build_reparse_buffer(nameW.Buffer, IO_REPARSE_TAG_SYMLINK, 0, &buffer);
     bret = DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT, (LPVOID)buffer, buffer_len, NULL, 0, &dwret, 0);
     ok(bret, "Failed to create symlink! (0x%x)\n", GetLastError());
 
@@ -5475,6 +5477,22 @@ static void test_reparse_points(void)
        "Symlink folder's access time does not match.\n");
     CloseHandle(handle);
 
+    /* Create a relative 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(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());
+
 cleanup:
     /* Cleanup */
     pRtlFreeUnicodeString(&nameW);
diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c
index 0c6426242e8..fcad9fd68bb 100644
--- a/dlls/ntdll/unix/file.c
+++ b/dlls/ntdll/unix/file.c
@@ -5663,17 +5663,20 @@ 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;
+    BOOL src_allocated = FALSE, path_allocated = FALSE, dest_allocated = FALSE;
+    BOOL nt_dest_allocated = FALSE, tempdir_created = FALSE;
+    char *unix_src, *unix_dest, *unix_path = NULL;
     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;
+    int relative_offset = 0;
     UNICODE_STRING nt_dest;
     int dest_len, offset;
     NTSTATUS status;
     struct stat st;
     WCHAR *dest;
+    ULONG flags;
     int i;
 
     switch(buffer->ReparseTag)
@@ -5682,11 +5685,13 @@ NTSTATUS FILE_CreateSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer)
         dest_len = buffer->MountPointReparseBuffer.SubstituteNameLength;
         offset = buffer->MountPointReparseBuffer.SubstituteNameOffset;
         dest = &buffer->MountPointReparseBuffer.PathBuffer[offset];
+        flags = 0;
         break;
     case IO_REPARSE_TAG_SYMLINK:
         dest_len = buffer->SymbolicLinkReparseBuffer.SubstituteNameLength;
         offset = buffer->SymbolicLinkReparseBuffer.SubstituteNameOffset;
         dest = &buffer->SymbolicLinkReparseBuffer.PathBuffer[offset];
+        flags = buffer->SymbolicLinkReparseBuffer.Flags;
         break;
     default:
         return STATUS_NOT_IMPLEMENTED;
@@ -5698,8 +5703,64 @@ NTSTATUS FILE_CreateSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer)
     if ((status = server_get_unix_name( handle, &unix_src )))
         goto cleanup;
     src_allocated = TRUE;
-    nt_dest.Buffer = dest;
-    nt_dest.Length = dest_len;
+    if (flags == SYMLINK_FLAG_RELATIVE)
+    {
+        SIZE_T nt_path_len = PATH_MAX, unix_path_len = PATH_MAX;
+        WCHAR *nt_path;
+
+        /* resolve the NT path of the source */
+        unix_path = malloc( strlen(unix_src) + 2 );
+        path_allocated = TRUE;
+        strcpy( unix_path, unix_src );
+        d = dirname( unix_path );
+        if (d != unix_path) strcpy( unix_path, d );
+        strcat( unix_path, "/");
+        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_path, nt_path, &nt_path_len );
+            if (status != STATUS_BUFFER_TOO_SMALL) break;
+            free( nt_path );
+        }
+        if (status != STATUS_SUCCESS)
+            goto cleanup;
+        free(unix_path);
+        /* re-resolve the unix path for the source */
+        for (;;)
+        {
+            UNICODE_STRING nt_path_tmp;
+            unix_path = malloc( unix_path_len );
+            if (!unix_path)
+            {
+                status = STATUS_NO_MEMORY;
+                goto cleanup;
+            }
+            nt_path_tmp.Buffer = nt_path;
+            nt_path_tmp.Length = lstrlenW(nt_path) * sizeof(WCHAR);
+            status = wine_nt_to_unix_file_name( &nt_path_tmp, unix_path, &unix_path_len, FALSE );
+            if (status != STATUS_BUFFER_TOO_SMALL) break;
+            free( unix_path );
+        }
+        /* append the destination */
+        nt_dest.MaximumLength = dest_len + (lstrlenW( nt_path ) + 1) * sizeof(WCHAR);
+        nt_dest.Buffer = RtlAllocateHeap( GetProcessHeap(), 0, nt_dest.MaximumLength );
+        lstrcpyW( nt_dest.Buffer, nt_path );
+        free( nt_path );
+        memcpy( &nt_dest.Buffer[lstrlenW(nt_dest.Buffer)], dest, dest_len + sizeof(WCHAR));
+        nt_dest.Length = lstrlenW( nt_dest.Buffer ) * sizeof(WCHAR);
+    }
+    else
+    {
+        RtlCreateUnicodeString( &nt_dest, dest );
+        nt_dest.Length = dest_len;
+    }
+    nt_dest_allocated = TRUE;
+    /* resolve the NT path of the destination */
     for (;;)
     {
         unix_dest = malloc( unix_dest_len );
@@ -5715,11 +5776,24 @@ NTSTATUS FILE_CreateSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer)
     if (status != STATUS_SUCCESS && status != STATUS_NO_SUCH_FILE)
         goto cleanup;
     dest_allocated = TRUE;
+    /* check that the source and destination paths are the same up to the relative path */
+    if (flags == SYMLINK_FLAG_RELATIVE)
+    {
+        relative_offset = strlen(unix_path);
+        if (strncmp( unix_path, unix_dest, relative_offset ) != 0)
+        {
+            status = STATUS_IO_REPARSE_DATA_INVALID;
+            goto cleanup;
+        }
+    }
 
-    TRACE( "Linking %s to %s\n", unix_src, unix_dest );
+    TRACE( "Linking %s to %s\n", unix_src, &unix_dest[relative_offset] );
 
     /* Encode the reparse tag into the symlink */
-    strcpy( magic_dest, "/" );
+    strcpy( magic_dest, "" );
+    if (flags == SYMLINK_FLAG_RELATIVE)
+        strcat( magic_dest, "." );
+    strcat( magic_dest, "/" );
     for (i = 0; i < sizeof(ULONG)*8; i++)
     {
         if ((buffer->ReparseTag >> i) & 1)
@@ -5738,7 +5812,7 @@ NTSTATUS FILE_CreateSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer)
             strcat( magic_dest, "." );
         strcat( magic_dest, "/" );
     }
-    strcat( magic_dest, unix_dest );
+    strcat( magic_dest, &unix_dest[relative_offset] );
 
     /* Produce the link in a temporary location in the same folder */
     strcpy( tmpdir, unix_src );
@@ -5788,7 +5862,9 @@ NTSTATUS FILE_CreateSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer)
 
 cleanup:
     if (tempdir_created) rmdir( tmpdir );
+    if (path_allocated) free( unix_path );
     if (dest_allocated) free( unix_dest );
+    if (nt_dest_allocated) RtlFreeUnicodeString( &nt_dest );
     if (src_allocated) free( unix_src );
     if (needs_close) close( dest_fd );
     return status;
diff --git a/include/ntifs.h b/include/ntifs.h
index ab3273d3f81..0d02225bc4f 100644
--- a/include/ntifs.h
+++ b/include/ntifs.h
@@ -61,4 +61,6 @@ typedef struct _REPARSE_GUID_DATA_BUFFER {
 
 #define REPARSE_GUID_DATA_BUFFER_HEADER_SIZE FIELD_OFFSET(REPARSE_GUID_DATA_BUFFER, GenericReparseBuffer)
 
+#define SYMLINK_FLAG_RELATIVE 0x00000001
+
 #endif /* __WINE_NTIFS_H */
-- 
2.27.0

