From ac69cecc3f024819efae83723b77d9f310b43251 Mon Sep 17 00:00:00 2001
From: "Erich E. Hoover" <erich.e.hoover@gmail.com>
Date: Thu, 11 Apr 2019 17:57:53 -0600
Subject: ntdll: Add support for file symlinks.

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

diff --git a/dlls/ntdll/file.c b/dlls/ntdll/file.c
index 5df2d8c3fa..03a07d46cd 100644
--- a/dlls/ntdll/file.c
+++ b/dlls/ntdll/file.c
@@ -1665,6 +1665,7 @@ NTSTATUS FILE_CreateSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer)
     int relative_offset = 0;
     UNICODE_STRING nt_dest;
     int dest_len, offset;
+    BOOL is_dir = TRUE;
     NTSTATUS status;
     struct stat st;
     WCHAR *dest;
@@ -1757,7 +1758,8 @@ NTSTATUS FILE_CreateSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer)
             status = FILE_GetNtStatus();
             goto cleanup;
         }
-        if (S_ISDIR(st.st_mode))
+        is_dir = S_ISDIR(st.st_mode);
+        if (is_dir)
             strcat( magic_dest, "." );
         strcat( magic_dest, "/" );
     }
@@ -1784,8 +1786,11 @@ NTSTATUS FILE_CreateSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer)
     /* Atomically move the link into position */
     if (!renameat2( -1, tmplink, -1, unix_src.Buffer, RENAME_EXCHANGE ))
     {
-        /* success: link and folder have switched locations */
-        rmdir( tmplink ); /* remove the folder (at link location) */
+        /* success: link and folder/file have switched locations */
+        if (is_dir)
+            rmdir( tmplink ); /* remove the folder (at link location) */
+        else
+            unlink( tmplink ); /* remove the file (at link location) */
     }
     else if (errno == ENOSYS)
     {
@@ -1994,6 +1999,7 @@ NTSTATUS FILE_RemoveSymlink(HANDLE handle, REPARSE_GUID_DATA_BUFFER *buffer)
     BOOL tempdir_created = FALSE;
     int dest_fd, needs_close;
     ANSI_STRING unix_name;
+    BOOL is_dir = TRUE;
     NTSTATUS status;
     struct stat st;
 
@@ -2005,12 +2011,13 @@ NTSTATUS FILE_RemoveSymlink(HANDLE handle, REPARSE_GUID_DATA_BUFFER *buffer)
 
     TRACE("Deleting symlink %s\n", unix_name.Buffer);
 
-    /* Produce the directory in a temporary location in the same folder */
+    /* Produce the file/directory in a temporary location in the same folder */
     if (fstat( dest_fd, &st ) == -1)
     {
         status = FILE_GetNtStatus();
         goto cleanup;
     }
+    is_dir = S_ISDIR(st.st_mode);
     strcpy( tmpdir, unix_name.Buffer );
     d = dirname( tmpdir);
     if (d != tmpdir) strcpy( tmpdir, d );
@@ -2023,11 +2030,21 @@ NTSTATUS FILE_RemoveSymlink(HANDLE handle, REPARSE_GUID_DATA_BUFFER *buffer)
     tempdir_created = TRUE;
     strcpy( tmpfile, tmpdir );
     strcat( tmpfile, "/tmpfile" );
-    if (mkdir( tmpfile, st.st_mode ))
+    if (is_dir && mkdir( tmpfile, st.st_mode ))
     {
         status = FILE_GetNtStatus();
         goto cleanup;
     }
+    else if (!is_dir)
+    {
+        int fd = open( tmpfile, O_CREAT|O_WRONLY|O_TRUNC, st.st_mode );
+        if (fd < 0)
+            {
+            status = FILE_GetNtStatus();
+            goto cleanup;
+        }
+        close( fd );
+    }
     /* attemp to retain the ownership (if possible) */
     lchown( tmpfile, st.st_uid, st.st_gid );
     /* Atomically move the directory into position */
diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c
index c13ee84bf0..5bf8ebf704 100644
--- a/dlls/ntdll/tests/file.c
+++ b/dlls/ntdll/tests/file.c
@@ -5050,6 +5050,39 @@ static void test_reparse_points(void)
     /* 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");
+
+    /* Create the file symlink */
+    HeapFree(GetProcessHeap(), 0, buffer);
+    handle = CreateFileW(reparse_path, GENERIC_READ | GENERIC_WRITE, 0, 0, CREATE_NEW,
+                         FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0);
+    if (handle == INVALID_HANDLE_VALUE)
+    {
+        win_skip("Failed to open symlink file 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 file's attributes (0x%x).\n", dwret);
+    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());
+    CloseHandle(handle);
+
+    /* Check deleting a file symlink as if it were a directory */
+    bret = RemoveDirectoryW(reparse_path);
+    todo_wine ok(!bret, "Succeeded in deleting file symlink as a directory!\n");
+    err = GetLastError();
+    todo_wine ok(err == ERROR_DIRECTORY,
+                 "Expected last error 0x%x for RemoveDirectory on file symlink (actually 0x%x)!\n",
+                 ERROR_DIRECTORY, err);
+    dwret = GetFileAttributesW(reparse_path);
+    todo_wine 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: 0x%x)\n", dwret);
+
+    /* Delete the symlink as a file */
+    bret = DeleteFileW(reparse_path);
+    todo_wine ok(bret, "Failed to delete symlink as a file!\n");
+
+    /* Create a blank slate for directory symlink tests */
     bret = CreateDirectoryW(reparse_path, NULL);
     ok(bret, "Failed to create junction point directory.\n");
     dwret = GetFileAttributesW(reparse_path);
-- 
2.17.1

