From 9740f34d11c458fd7a07a025810422a6db1ad374 Mon Sep 17 00:00:00 2001
From: Corinna Vinschen <corinna@vinschen.de>
Date: Tue, 31 Oct 2006 11:40:47 +0000
Subject: [PATCH] 	* fhandler_disk_file.cc
 (fhandler_base::fstat_by_handle): Drop 	directory attribute for
 reparse points to avoid mistreating. 	(fhandler_base::fstat_by_name): Ditto.
 	* path.cc (symlink_info::check_reparse_point): New method testing 
 reparse points for symbolic links. 	(symlink_info::check_shortcut): Move
 file attribute tesat to calling 	function. 	(symlink_info::check):
 Add handling for reparse points. 	* path.h (enum path_types): Add
 PATH_REP to denote reparse point based 	symlinks. 
 (path_conv::is_rep_symlink): New method. 	* syscalls.cc (unlink): Handle
 reparse points.

---
 winsup/cygwin/ChangeLog             | 15 +++++
 winsup/cygwin/fhandler_disk_file.cc | 10 +++-
 winsup/cygwin/path.cc               | 87 +++++++++++++++++++++++++----
 winsup/cygwin/path.h                |  2 +
 winsup/cygwin/syscalls.cc           | 12 +++-
 5 files changed, 112 insertions(+), 14 deletions(-)

diff --git a/winsup/cygwin/ChangeLog b/winsup/cygwin/ChangeLog
index 1487544c6..fddfd6bf7 100644
--- a/winsup/cygwin/ChangeLog
+++ b/winsup/cygwin/ChangeLog
@@ -1,3 +1,18 @@
+2006-10-31  Corinna Vinschen  <corinna@vinschen.de>
+
+	* fhandler_disk_file.cc (fhandler_base::fstat_by_handle): Drop
+	directory attribute for reparse points to avoid mistreating.
+	(fhandler_base::fstat_by_name): Ditto.
+	* path.cc (symlink_info::check_reparse_point): New method testing
+	reparse points for symbolic links.
+	(symlink_info::check_shortcut): Move file attribute tesat to calling
+	function.
+	(symlink_info::check): Add handling for reparse points.
+	* path.h (enum path_types): Add PATH_REP to denote reparse point based
+	symlinks.
+	(path_conv::is_rep_symlink): New method.
+	* syscalls.cc (unlink): Handle reparse points.
+
 2006-10-27  Corinna Vinschen  <corinna@vinschen.de>
 
 	* shared.cc (open_shared): Drop useless attempt from 2006-08-11.
diff --git a/winsup/cygwin/fhandler_disk_file.cc b/winsup/cygwin/fhandler_disk_file.cc
index da358bd1d..f3b5ec03e 100644
--- a/winsup/cygwin/fhandler_disk_file.cc
+++ b/winsup/cygwin/fhandler_disk_file.cc
@@ -240,6 +240,8 @@ fhandler_base::fstat_by_handle (struct __stat64 *buf)
 	  /* If the change time is 0, it's a file system which doesn't
 	     support a change timestamp.  In that case use the LastWriteTime
 	     entry, as in other calls to fstat_helper. */
+	  if (pc.is_rep_symlink ())
+	    pfai->BasicInformation.FileAttributes &= ~FILE_ATTRIBUTE_DIRECTORY;
 	  pc.file_attributes (pfai->BasicInformation.FileAttributes);
 	  return fstat_helper (buf,
 			   pfai->BasicInformation.ChangeTime.QuadPart ?
@@ -275,7 +277,11 @@ fhandler_base::fstat_by_handle (struct __stat64 *buf)
       local.dwFileAttributes = DWORD (pc);
     }
   else
-    pc.file_attributes (local.dwFileAttributes);
+    {
+      if (pc.is_rep_symlink ())
+	local.dwFileAttributes &= ~FILE_ATTRIBUTE_DIRECTORY;
+      pc.file_attributes (local.dwFileAttributes);
+    }
   return fstat_helper (buf,
 		       local.ftLastWriteTime, /* see fstat_helper comment */
 		       local.ftLastAccessTime,
@@ -306,6 +312,8 @@ fhandler_base::fstat_by_name (struct __stat64 *buf)
   else if ((handle = FindFirstFile (pc, &local)) != INVALID_HANDLE_VALUE)
     {
       FindClose (handle);
+      if (pc.is_rep_symlink ())
+	local.dwFileAttributes &= ~FILE_ATTRIBUTE_DIRECTORY;
       pc.file_attributes (local.dwFileAttributes);
       res = fstat_helper (buf,
 			  local.ftLastWriteTime, /* see fstat_helper comment */
diff --git a/winsup/cygwin/path.cc b/winsup/cygwin/path.cc
index cfe0735c5..f32cfb4c8 100644
--- a/winsup/cygwin/path.cc
+++ b/winsup/cygwin/path.cc
@@ -104,6 +104,7 @@ struct symlink_info
   bool case_check (char *path);
   int check_sysfile (const char *path, HANDLE h);
   int check_shortcut (const char *path, HANDLE h);
+  int check_reparse_point (const char *path, HANDLE h);
   bool set_error (int);
 };
 
@@ -2996,10 +2997,6 @@ symlink_info::check_shortcut (const char *path, HANDLE h)
   int res = 0;
   DWORD size, got = 0;
 
-  /* Valid Cygwin & U/WIN shortcuts are R/O. */
-  if (!(fileattr & FILE_ATTRIBUTE_READONLY))
-    goto file_not_symlink;
-
   if ((size = GetFileSize (h, NULL)) > 8192) /* Not a Cygwin symlink. */
     goto file_not_symlink;
   buf = (char *) alloca (size);
@@ -3033,7 +3030,6 @@ close_it:
   return res;
 }
 
-
 int
 symlink_info::check_sysfile (const char *path, HANDLE h)
 {
@@ -3093,6 +3089,61 @@ symlink_info::check_sysfile (const char *path, HANDLE h)
   return res;
 }
 
+int
+symlink_info::check_reparse_point (const char *path, HANDLE h)
+{
+  int res = 0;
+  PREPARSE_DATA_BUFFER rp = (PREPARSE_DATA_BUFFER)
+			    alloca (MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
+  DWORD size;
+
+  if (!DeviceIoControl (h, FSCTL_GET_REPARSE_POINT, NULL, 0, (LPVOID) rp,
+  			MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &size, NULL))
+    {
+      debug_printf ("DeviceIoControl(FSCTL_GET_REPARSE_POINT) failed, %E");
+      set_error (EIO);
+      goto close_it;
+    }
+  if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK)
+    {
+      if (rp->SymbolicLinkReparseBuffer.PrintNameLength > 2 * CYG_MAX_PATH)
+	{
+	  debug_printf ("Symlink name too long");
+	  set_error (EIO);
+	  goto close_it;
+	}
+      res = sys_wcstombs (contents, CYG_MAX_PATH,
+		    (WCHAR *)((char *)rp->SymbolicLinkReparseBuffer.PathBuffer
+			      + rp->SymbolicLinkReparseBuffer.PrintNameOffset),
+		    rp->SymbolicLinkReparseBuffer.PrintNameLength / 2);
+      pflags = PATH_SYMLINK | PATH_REP;
+      fileattr &= ~FILE_ATTRIBUTE_DIRECTORY;
+    }
+  else if (rp->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
+    {
+      if (rp->SymbolicLinkReparseBuffer.PrintNameLength == 0)
+        {
+	  /* Likely a volume mount point.  Not treated as symlink. */
+	  goto close_it;
+	}
+      if (rp->MountPointReparseBuffer.PrintNameLength > 2 * CYG_MAX_PATH)
+        {
+	  debug_printf ("Symlink name too long");
+	  set_error (EIO);
+	  goto close_it;
+	}
+      res = sys_wcstombs (contents, CYG_MAX_PATH,
+		      (WCHAR *)((char *)rp->MountPointReparseBuffer.PathBuffer
+				+ rp->MountPointReparseBuffer.PrintNameOffset),
+		      rp->MountPointReparseBuffer.PrintNameLength / 2);
+      pflags = PATH_SYMLINK | PATH_REP;
+      fileattr &= ~FILE_ATTRIBUTE_DIRECTORY;
+    }
+close_it:
+  CloseHandle (h);
+  return res;
+}
+
 enum
 {
   SCAN_BEG,
@@ -3337,26 +3388,35 @@ symlink_info::check (char *path, const suffix_info *suffixes, unsigned opt)
 
       sym_check = 0;
 
-      if (fileattr & FILE_ATTRIBUTE_DIRECTORY)
+      if ((fileattr & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT))
+	  == FILE_ATTRIBUTE_DIRECTORY)
 	goto file_not_symlink;
 
-      /* Windows shortcuts are treated as symlinks. */
-      if (suffix.lnk_match ())
-	sym_check = 1;
+      /* Reparse points are potentially symlinks. */
+      if (fileattr & FILE_ATTRIBUTE_REPARSE_POINT)
+        sym_check = 3;
 
       /* This is the old Cygwin method creating symlinks: */
       /* A symlink will have the `system' file attribute. */
       /* Only files can be symlinks (which can be symlinks to directories). */
-      if (fileattr & FILE_ATTRIBUTE_SYSTEM)
+      else if (fileattr & FILE_ATTRIBUTE_SYSTEM)
 	sym_check = 2;
 
+      /* Windows shortcuts are potentially treated as symlinks. */
+      /* Valid Cygwin & U/WIN shortcuts are R/O. */
+      else if ((fileattr & FILE_ATTRIBUTE_READONLY) && suffix.lnk_match ())
+	sym_check = 1;
+
       if (!sym_check)
 	goto file_not_symlink;
 
       /* Open the file.  */
 
       h = CreateFile (suffix.path, GENERIC_READ, FILE_SHARE_READ,
-		      &sec_none_nih, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
+		      &sec_none_nih, OPEN_EXISTING,
+		      sym_check == 3 ? FILE_FLAG_OPEN_REPARSE_POINT
+				       | FILE_FLAG_BACKUP_SEMANTICS
+				     : FILE_ATTRIBUTE_NORMAL, 0);
       res = -1;
       if (h == INVALID_HANDLE_VALUE)
 	goto file_not_symlink;
@@ -3383,6 +3443,11 @@ symlink_info::check (char *path, const suffix_info *suffixes, unsigned opt)
 	  if (!res)
 	    goto file_not_symlink;
 	  break;
+	case 3:
+	  res = check_reparse_point (suffix.path, h);
+	  if (!res)
+	    goto file_not_symlink;
+	  break;
 	}
       break;
 
diff --git a/winsup/cygwin/path.h b/winsup/cygwin/path.h
index b00adc76d..0b2decc0d 100644
--- a/winsup/cygwin/path.h
+++ b/winsup/cygwin/path.h
@@ -76,6 +76,7 @@ enum path_types
   PATH_NO_ACCESS_CHECK	= PC_NO_ACCESS_CHECK,
   PATH_LNK		= 0x01000000,
   PATH_TEXT		= 0x02000000,
+  PATH_REP		= 0x04000000,
   PATH_HAS_SYMLINKS	= 0x10000000,
   PATH_SOCKET		= 0x40000000
 };
@@ -163,6 +164,7 @@ class path_conv
   }
   int issymlink () const {return path_flags & PATH_SYMLINK;}
   int is_lnk_symlink () const {return path_flags & PATH_LNK;}
+  int is_rep_symlink () const {return path_flags & PATH_REP;}
   int isdevice () const {return dev.devn && dev.devn != FH_FS && dev.devn != FH_FIFO;}
   int isfifo () const {return dev == FH_FIFO;}
   int isspecial () const {return dev.devn && dev.devn != FH_FS;}
diff --git a/winsup/cygwin/syscalls.cc b/winsup/cygwin/syscalls.cc
index 66839be95..eb07890a1 100644
--- a/winsup/cygwin/syscalls.cc
+++ b/winsup/cygwin/syscalls.cc
@@ -205,8 +205,11 @@ unlink (const char *ourname)
   if (!win32_name.isremote () && wincap.has_delete_on_close ())
     {
       HANDLE h;
+      DWORD flags = FILE_FLAG_DELETE_ON_CLOSE;
+      if (win32_name.is_rep_symlink ())
+        flags |= FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS;
       h = CreateFile (win32_name, 0, FILE_SHARE_READ, &sec_none_nih,
-		      OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, 0);
+		      OPEN_EXISTING, flags, 0);
       if (h != INVALID_HANDLE_VALUE)
 	{
 	  if (wincap.has_hard_links () && setattrs)
@@ -229,7 +232,12 @@ unlink (const char *ourname)
     }
 
   /* Try a delete with attributes reset */
-  if (DeleteFile (win32_name))
+  if (win32_name.is_rep_symlink () && RemoveDirectory (win32_name))
+    {
+      syscall_printf ("RemoveDirectory after CreateFile/CloseHandle succeeded");
+      goto ok;
+    }
+  else if (DeleteFile (win32_name))
     {
       syscall_printf ("DeleteFile after CreateFile/CloseHandle succeeded");
       goto ok;