From 398754456f977e063aa1c180301ce44fd5fdaf84 Mon Sep 17 00:00:00 2001
From: "Erich E. Hoover" <erich.e.hoover@gmail.com>
Date: Thu, 16 Jan 2014 21:02:11 -0700
Subject: server: Add support for deleting junction points with
 RemoveDirectory.

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

diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c
index 051237dd494..77a1be8307b 100644
--- a/dlls/ntdll/tests/file.c
+++ b/dlls/ntdll/tests/file.c
@@ -5010,7 +5010,7 @@ static void test_reparse_points(void)
     REPARSE_GUID_DATA_BUFFER guid_buffer;
     static const WCHAR dotW[] = {'.',0};
     REPARSE_DATA_BUFFER *buffer = NULL;
-    DWORD dwret, dwLen, dwFlags;
+    DWORD dwret, dwLen, dwFlags, err;
     INT buffer_len, string_len;
     IO_STATUS_BLOCK iosb;
     UNICODE_STRING nameW;
@@ -5099,6 +5099,38 @@ static void test_reparse_points(void)
        "Junction point folder's access time does not match.\n");
     CloseHandle(handle);
 
+    /* Check deleting a junction point as if it were a directory */
+    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, &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);
+    bret = RemoveDirectoryW(reparse_path);
+    ok(bret, "Failed to delete junction point as directory!\n");
+    dwret = GetFileAttributesW(reparse_path);
+    ok(dwret == (DWORD)~0, "Junction point still exists (attributes: 0x%x)!\n", dwret);
+
+    /* Check deleting a junction point as if it were a file */
+    HeapFree(GetProcessHeap(), 0, buffer);
+    bret = CreateDirectoryW(reparse_path, NULL);
+    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, &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);
+    bret = DeleteFileW(reparse_path);
+    ok(!bret, "Succeeded in deleting junction point as file!\n");
+    err = GetLastError();
+    ok(err == ERROR_ACCESS_DENIED, "Expected last error 0x%x for DeleteFile on junction point (actually 0x%x)!\n",
+                                   ERROR_ACCESS_DENIED, err);
+    dwret = GetFileAttributesW(reparse_path);
+    ok(dwret != (DWORD)~0, "Junction point doesn't exist (attributes: 0x%x)!\n", dwret);
+    ok(dwret & FILE_ATTRIBUTE_REPARSE_POINT, "File is not a junction point! (attributes: 0x%x)\n", dwret);
+
 cleanup:
     /* Cleanup */
     pRtlFreeUnicodeString(&nameW);
diff --git a/server/fd.c b/server/fd.c
index 7ea8ac273e5..c0d35e2fa4c 100644
--- a/server/fd.c
+++ b/server/fd.c
@@ -169,7 +169,8 @@ struct closed_fd
     struct list entry;       /* entry in inode closed list */
     int         unix_fd;     /* the unix file descriptor */
     int         unlink;      /* whether to unlink on close: -1 - implicit FILE_DELETE_ON_CLOSE, 1 - explicit disposition */
-    char       *unix_name;   /* name to unlink on close, points to parent fd unix_name */
+    char       *unlink_name; /* name to unlink on close, points to parent fd unix_name */
+    char       *unix_name;   /* name to real file path, points to parent fd unix_name */
 };
 
 struct fd
@@ -184,6 +185,7 @@ struct fd
     unsigned int         access;      /* file access (FILE_READ_DATA etc.) */
     unsigned int         options;     /* file options (FILE_DELETE_ON_CLOSE, FILE_SYNCHRONOUS...) */
     unsigned int         sharing;     /* file sharing mode */
+    char                *unlink_name; /* file name to unlink on close */
     char                *unix_name;   /* unix file name */
     int                  unix_fd;     /* unix file descriptor */
     unsigned int         no_fd_status;/* status to return when unix_fd is -1 */
@@ -1113,6 +1115,7 @@ static void inode_close_pending( struct inode *inode, int keep_unlinks )
         if (!keep_unlinks || !fd->unlink)  /* get rid of it unless there's an unlink pending on that file */
         {
             list_remove( ptr );
+            free( fd->unlink_name );
             free( fd->unix_name );
             free( fd );
         }
@@ -1147,12 +1150,13 @@ static void inode_destroy( struct object *obj )
         {
             /* make sure it is still the same file */
             struct stat st;
-            if (!stat( fd->unix_name, &st ) && st.st_dev == inode->device->dev && st.st_ino == inode->ino)
+            if (!lstat( fd->unlink_name, &st ) && st.st_dev == inode->device->dev && st.st_ino == inode->ino)
             {
-                if (S_ISDIR(st.st_mode)) rmdir( fd->unix_name );
-                else unlink( fd->unix_name );
+                if (S_ISDIR(st.st_mode)) rmdir( fd->unlink_name );
+                else unlink( fd->unlink_name );
             }
         }
+        free( fd->unlink_name );
         free( fd->unix_name );
         free( fd );
     }
@@ -1903,18 +1907,19 @@ struct fd *open_fd( struct fd *root, const char *name, int flags, mode_t *mode,
     fd->unix_name = NULL;
     if ((path = dup_fd_name( root, name )))
     {
+        fd->unlink_name = path;
         fd->unix_name = realpath( path, NULL );
-        free( path );
     }
 
     closed_fd->unix_fd = fd->unix_fd;
     closed_fd->unlink = 0;
+    closed_fd->unlink_name = fd->unlink_name;
     closed_fd->unix_name = fd->unix_name;
-    fstat( fd->unix_fd, &st );
+    lstat( fd->unlink_name, &st );
     *mode = st.st_mode;
 
     /* only bother with an inode for normal files and directories */
-    if (S_ISREG(st.st_mode) || S_ISDIR(st.st_mode))
+    if (S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))
     {
         unsigned int err;
         struct inode *inode = get_inode( st.st_dev, st.st_ino, fd->unix_fd );
@@ -1932,6 +1937,9 @@ struct fd *open_fd( struct fd *root, const char *name, int flags, mode_t *mode,
         list_add_head( &inode->open, &fd->inode_entry );
         closed_fd = NULL;
 
+        fstat( fd->unix_fd, &st );
+        *mode = st.st_mode;
+
         /* check directory options */
         if ((options & FILE_DIRECTORY_FILE) && !S_ISDIR(st.st_mode))
         {
@@ -2563,10 +2571,11 @@ static void set_fd_name( struct fd *fd, struct fd *root, const char *nameptr,
         fchmod( fd->unix_fd, st.st_mode );
     }
 
+    free( fd->unlink_name );
     free( fd->unix_name );
+    fd->closed->unlink_name = fd->unlink_name = name;
     fd->closed->unix_name = fd->unix_name = realpath( name, NULL );
-    free( name );
-    if (!fd->unix_name)
+    if (!fd->unlink_name || !fd->unix_name)
         set_error( STATUS_NO_MEMORY );
     return;
 
-- 
2.17.1

