From d98202d50cdb09fe199414b8a313fcf954725e2d 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/tests/file.c | 29 +++++++++++++++++++++++++++++
 dlls/ntdll/unix/file.c  | 27 ++++++++++++++++++++++-----
 2 files changed, 51 insertions(+), 5 deletions(-)

diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c
index 0db7372a0c8..58fec76bec4 100644
--- a/dlls/ntdll/tests/file.c
+++ b/dlls/ntdll/tests/file.c
@@ -5191,6 +5191,35 @@ 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);
+    ok(handle != INVALID_HANDLE_VALUE, "Failed to create symlink file.\n");
+    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);
diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c
index e62a2529bb2..242d344e1f5 100644
--- a/dlls/ntdll/unix/file.c
+++ b/dlls/ntdll/unix/file.c
@@ -5610,6 +5610,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;
@@ -5745,7 +5746,8 @@ NTSTATUS FILE_CreateSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer)
             status = errno_to_status( errno );
             goto cleanup;
         }
-        if (S_ISDIR(st.st_mode))
+        is_dir = S_ISDIR(st.st_mode);
+        if (is_dir)
             strcat( magic_dest, "." );
         strcat( magic_dest, "/" );
     }
@@ -5772,8 +5774,11 @@ NTSTATUS FILE_CreateSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer)
     /* Atomically move the link into position */
     if (!renameat2( -1, tmplink, -1, unix_src, 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)
     {
@@ -6011,6 +6016,7 @@ NTSTATUS FILE_RemoveSymlink(HANDLE handle, REPARSE_GUID_DATA_BUFFER *buffer)
     char tmpdir[PATH_MAX], tmpfile[PATH_MAX], *d;
     BOOL tempdir_created = FALSE;
     int dest_fd, needs_close;
+    BOOL is_dir = TRUE;
     NTSTATUS status;
     char *unix_name;
     struct stat st;
@@ -6023,12 +6029,13 @@ NTSTATUS FILE_RemoveSymlink(HANDLE handle, REPARSE_GUID_DATA_BUFFER *buffer)
 
     TRACE( "Deleting symlink %s\n", unix_name );
 
-    /* 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 = errno_to_status( errno );
         goto cleanup;
     }
+    is_dir = S_ISDIR(st.st_mode);
     strcpy( tmpdir, unix_name );
     d = dirname( tmpdir);
     if (d != tmpdir) strcpy( tmpdir, d );
@@ -6041,11 +6048,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 = errno_to_status( errno );
         goto cleanup;
     }
+    else if (!is_dir)
+    {
+        int fd = open( tmpfile, O_CREAT|O_WRONLY|O_TRUNC, st.st_mode );
+        if (fd < 0)
+            {
+            status = errno_to_status( errno );
+            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 */
-- 
2.17.1

