From 9a808d848f2a57140822c31047ce882c75d74b99 Mon Sep 17 00:00:00 2001
From: "Erich E. Hoover" <erich.e.hoover@gmail.com>
Date: Sat, 30 Mar 2019 12:00:51 -0600
Subject: ntdll: Correctly report file symbolic links as files.

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

diff --git a/dlls/ntdll/file.c b/dlls/ntdll/file.c
index 04d34dd572..57d7d334d5 100644
--- a/dlls/ntdll/file.c
+++ b/dlls/ntdll/file.c
@@ -124,6 +124,9 @@ mode_t FILE_umask = 0;
 
 static const WCHAR ntfsW[] = {'N','T','F','S'};
 
+NTSTATUS FILE_DecodeSymlink(const char *unix_src, char *unix_dest, USHORT *unix_dest_len,
+                            DWORD *tag, ULONG *flags, BOOL *is_dir);
+
 /* fetch the attributes of a file */
 static inline ULONG get_file_attributes( const struct stat *st )
 {
@@ -160,10 +163,15 @@ int get_file_info( const char *path, struct stat *st, ULONG *attr )
     if (ret == -1) return ret;
     if (S_ISLNK( st->st_mode ))
     {
-        ret = stat( path, st );
-        if (ret == -1) return ret;
-        /* is a symbolic link and a directory, consider these "reparse points" */
-        if (S_ISDIR( st->st_mode )) *attr |= FILE_ATTRIBUTE_REPARSE_POINT;
+        BOOL is_dir;
+
+        /* return information about the destination (unless this is a dangling symlink) */
+        stat( path, st );
+        /* symbolic links (either junction points or NT symlinks) are "reparse points" */
+        *attr |= FILE_ATTRIBUTE_REPARSE_POINT;
+        /* whether a reparse point is a file or a directory is stored inside the link target */
+        if (FILE_DecodeSymlink( path, NULL, NULL, NULL, NULL, &is_dir ) == STATUS_SUCCESS)
+            st->st_mode = (st->st_mode & ~S_IFMT) | (is_dir ? S_IFDIR : S_IFREG);
     }
     *attr |= get_file_attributes( st );
     return ret;
@@ -1826,48 +1834,33 @@ cleanup:
 }
 
 
-/*
- * Retrieve the unix name corresponding to a file handle and use that to find the destination of the
- * symlink corresponding to that file handle.
- */
-NTSTATUS FILE_GetSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer, ULONG out_size)
+NTSTATUS FILE_DecodeSymlink(const char *unix_src, char *unix_dest, USHORT *unix_dest_len,
+                            DWORD *tag, ULONG *flags, BOOL *is_dir)
 {
-    ANSI_STRING unix_src, unix_dest;
-    VOID *subst_name, *print_name;
-    BOOL dest_allocated = FALSE;
-    int dest_fd, needs_close;
-    UNICODE_STRING nt_dest;
-    int path_len = 0;
-    DWORD max_length;
+    USHORT len = MAX_PATH;
+    DWORD reparse_tag;
     NTSTATUS status;
-    ULONG flags = 0;
-    INT prefix_len;
+    BOOL dir_flag;
+    char *p, *tmp;
     ssize_t ret;
-    char *p;
     int i;
 
-    if ((status = server_get_unix_fd( handle, FILE_ANY_ACCESS, &dest_fd, &needs_close, NULL, NULL )))
-        return status;
-
-    if ((status = server_get_unix_name( handle, &unix_src )))
-        goto cleanup;
-
-    unix_dest.Buffer = RtlAllocateHeap( GetProcessHeap(), 0, PATH_MAX );
-    unix_dest.MaximumLength = PATH_MAX;
-    dest_allocated = TRUE;
-    ret = readlink( unix_src.Buffer, unix_dest.Buffer, unix_dest.MaximumLength );
-    if (ret < 0)
+    if (unix_dest_len) len = *unix_dest_len;
+    if (!unix_dest)
+        tmp = RtlAllocateHeap( GetProcessHeap(), 0, len );
+    else
+        tmp = unix_dest;
+    if ((ret = readlink( unix_src, tmp, len )) < 0)
     {
         status = FILE_GetNtStatus();
         goto cleanup;
     }
-    unix_dest.Length = ret;
-
+    len = ret;
     /* Decode the reparse tag from the symlink */
-    p = unix_dest.Buffer;
+    p = tmp;
     if (*p == '.')
     {
-        flags = SYMLINK_FLAG_RELATIVE;
+        if (flags) *flags = SYMLINK_FLAG_RELATIVE;
         p++;
     }
     if (*p++ != '/')
@@ -1875,7 +1868,7 @@ NTSTATUS FILE_GetSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer, ULONG out_s
         status = STATUS_NOT_IMPLEMENTED;
         goto cleanup;
     }
-    buffer->ReparseTag = 0;
+    reparse_tag = 0;
     for (i = 0; i < sizeof(ULONG)*8; i++)
     {
         char c = *p++;
@@ -1890,21 +1883,68 @@ NTSTATUS FILE_GetSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer, ULONG out_s
             status = STATUS_NOT_IMPLEMENTED;
             goto cleanup;
         }
-        buffer->ReparseTag |= (val << i);
+        reparse_tag |= (val << i);
     }
     /* skip past the directory/file flag */
-    if (buffer->ReparseTag == IO_REPARSE_TAG_SYMLINK)
+    if (reparse_tag == IO_REPARSE_TAG_SYMLINK)
     {
         char c = *p++;
 
-        if ((c != '/' && c != '.') || (c == '.' && *p++ != '/'))
+        if (c == '/')
+            dir_flag = FALSE;
+        else if (c == '.' && *p++ == '/')
+            dir_flag = TRUE;
+        else
         {
             status = STATUS_NOT_IMPLEMENTED;
             goto cleanup;
         }
     }
-    unix_dest.Length -= (p - unix_dest.Buffer);
-    memmove(unix_dest.Buffer, p, unix_dest.Length);
+    else
+        dir_flag = TRUE;
+    len -= (p - tmp);
+    if (tag) *tag = reparse_tag;
+    if (is_dir) *is_dir = dir_flag;
+    if (unix_dest) memmove(unix_dest, p, len);
+    if (unix_dest_len) *unix_dest_len = len;
+    status = STATUS_SUCCESS;
+
+cleanup:
+    if (!unix_dest) RtlFreeHeap( GetProcessHeap(), 0, tmp );
+    return status;
+}
+
+
+/*
+ * Retrieve the unix name corresponding to a file handle and use that to find the destination of the
+ * symlink corresponding to that file handle.
+ */
+NTSTATUS FILE_GetSymlink(HANDLE handle, REPARSE_DATA_BUFFER *buffer, ULONG out_size)
+{
+    ANSI_STRING unix_src, unix_dest;
+    VOID *subst_name, *print_name;
+    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;
+    INT prefix_len;
+
+    if ((status = server_get_unix_fd( handle, FILE_ANY_ACCESS, &dest_fd, &needs_close, NULL, NULL )))
+        return status;
+
+    if ((status = server_get_unix_name( handle, &unix_src )))
+        goto cleanup;
+
+    unix_dest.MaximumLength = PATH_MAX;
+    unix_dest.Buffer = RtlAllocateHeap( GetProcessHeap(), 0, unix_dest.MaximumLength );
+    unix_dest.Length = unix_dest.MaximumLength;
+    dest_allocated = TRUE;
+    if ((status = FILE_DecodeSymlink( unix_src.Buffer, unix_dest.Buffer, &unix_dest.Length,
+                                      &buffer->ReparseTag, &flags, NULL )))
+        goto cleanup;
 
     /* convert the relative path into an absolute path */
     if (flags == SYMLINK_FLAG_RELATIVE)
diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c
index 5bf8ebf704..bacfad266e 100644
--- a/dlls/ntdll/tests/file.c
+++ b/dlls/ntdll/tests/file.c
@@ -5069,13 +5069,13 @@ static void test_reparse_points(void)
 
     /* 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");
+    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 != (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 */
@@ -5084,10 +5084,10 @@ static void test_reparse_points(void)
 
     /* Create a blank slate for directory symlink tests */
     bret = CreateDirectoryW(reparse_path, NULL);
-    ok(bret, "Failed to create junction point directory.\n");
+    todo_wine ok(bret, "Failed to create junction point directory.\n");
     dwret = GetFileAttributesW(reparse_path);
     ok(dwret != (DWORD)~0, "Path doesn't exist (attributes: 0x%x)!\n", dwret);
-    ok(!(dwret & FILE_ATTRIBUTE_REPARSE_POINT), "File is already a reparse point! (attributes: %d)\n", dwret);
+    todo_wine ok(!(dwret & FILE_ATTRIBUTE_REPARSE_POINT), "File is already a reparse point! (attributes: %d)\n", dwret);
 
     /* Create the directory symlink */
     HeapFree(GetProcessHeap(), 0, buffer);
-- 
2.17.1

