[newlib-cygwin] Cygwin: symlinks: create WSL symlinks on supporting filesystems

Corinna Vinschen corinna@sourceware.org
Fri Apr 3 19:45:06 GMT 2020


https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;h=44da5e4b8c89bfcee79eb83fd3b7142720489940

commit 44da5e4b8c89bfcee79eb83fd3b7142720489940
Author: Corinna Vinschen <corinna@vinschen.de>
Date:   Fri Apr 3 21:40:01 2020 +0200

    Cygwin: symlinks: create WSL symlinks on supporting filesystems
    
    WSL symlinks are reparse points containing a POSIX path in UTF-8.
    On filesystems supporting reparse points, use this symlink type.
    On other filesystems, or in case of error, fall back to the good
    old plain SYSTEM file.
    
    Signed-off-by: Corinna Vinschen <corinna@vinschen.de>

Diff:
---
 winsup/cygwin/path.cc       | 127 +++++++++++++++++++++++++++++++++++---------
 winsup/cygwin/release/3.1.5 |   2 +-
 winsup/doc/new-features.xml |   2 +-
 3 files changed, 105 insertions(+), 26 deletions(-)

diff --git a/winsup/cygwin/path.cc b/winsup/cygwin/path.cc
index 7d9d61fcd..e6dc03ffa 100644
--- a/winsup/cygwin/path.cc
+++ b/winsup/cygwin/path.cc
@@ -1845,6 +1845,97 @@ symlink_native (const char *oldpath, path_conv &win32_newpath)
   return 0;
 }
 
+#ifndef IO_REPARSE_TAG_LX_SYMLINK
+#define IO_REPARSE_TAG_LX_SYMLINK	(0xa000001d)
+#endif
+
+typedef struct _REPARSE_LX_SYMLINK_BUFFER
+{
+  DWORD	ReparseTag;
+  WORD	ReparseDataLength;
+  WORD	Reserved;
+  struct {
+    DWORD FileType;	/* Take member name with a grain of salt.  Value is
+			   apparently always 2 for symlinks. */
+    char  PathBuffer[1];/* POSIX path as given to symlink(2).
+			   Path is not \0 terminated.
+			   Length is ReparseDataLength - sizeof (FileType).
+			   Always UTF-8.
+			   Chars given in incompatible codesets, e. g. umlauts
+			   in ISO-8859-x, are converted to the Unicode
+			   REPLACEMENT CHARACTER 0xfffd == \xef\xbf\bd */
+  } LxSymlinkReparseBuffer;
+} REPARSE_LX_SYMLINK_BUFFER,*PREPARSE_LX_SYMLINK_BUFFER;
+
+static int
+symlink_wsl (const char *oldpath, path_conv &win32_newpath)
+{
+  tmp_pathbuf tp;
+  PREPARSE_LX_SYMLINK_BUFFER rpl = (PREPARSE_LX_SYMLINK_BUFFER) tp.c_get ();
+  char *path_buf = rpl->LxSymlinkReparseBuffer.PathBuffer;
+  const int max_pathlen = MAXIMUM_REPARSE_DATA_BUFFER_SIZE
+			  - offsetof (REPARSE_LX_SYMLINK_BUFFER,
+				      LxSymlinkReparseBuffer.PathBuffer);
+  PWCHAR utf16 = tp.w_get ();
+  NTSTATUS status;
+  IO_STATUS_BLOCK io;
+  OBJECT_ATTRIBUTES attr;
+  HANDLE fh;
+  int len;
+
+  rpl->ReparseTag = IO_REPARSE_TAG_LX_SYMLINK;
+  rpl->Reserved = 0;
+  rpl->LxSymlinkReparseBuffer.FileType = 2;
+  /* Convert cygdrive prefix to "/mnt" for WSL compatibility. */
+  if (path_prefix_p (mount_table->cygdrive, oldpath,
+		     mount_table->cygdrive_len, false))
+    stpcpy (stpcpy (path_buf, "/mnt"),
+	    oldpath + mount_table->cygdrive_len - 1);
+  else
+    *stpncpy (path_buf, oldpath, max_pathlen) = '\0';
+  /* Convert target path to UTF-16 and then back to UTF-8 to make sure the
+     WSL symlink is in UTF-8, independet of the current Cygwin codeset. */
+  sys_mbstowcs (utf16, NT_MAX_PATH, path_buf);
+  len = WideCharToMultiByte (CP_UTF8, 0, utf16, -1, path_buf, max_pathlen,
+			     NULL, NULL);
+  /* Length is omitting trailing \0. */
+  rpl->ReparseDataLength = sizeof (DWORD) + len - 1;
+  /* Create reparse point. */
+  status = NtCreateFile (&fh, DELETE | FILE_GENERIC_WRITE
+			     | READ_CONTROL | WRITE_DAC,
+			 win32_newpath.get_object_attr (attr, sec_none_nih),
+			 &io, NULL, FILE_ATTRIBUTE_NORMAL,
+			 FILE_SHARE_VALID_FLAGS, FILE_CREATE,
+			 FILE_SYNCHRONOUS_IO_NONALERT
+			 | FILE_NON_DIRECTORY_FILE
+			 | FILE_OPEN_FOR_BACKUP_INTENT
+			 | FILE_OPEN_REPARSE_POINT,
+			 NULL, 0);
+  if (!NT_SUCCESS (status))
+    {
+      SetLastError (RtlNtStatusToDosError (status));
+      return -1;
+    }
+  set_created_file_access (fh, win32_newpath, S_IFLNK | STD_RBITS | STD_WBITS);
+  status = NtFsControlFile (fh, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT,
+			    (LPVOID) rpl, REPARSE_DATA_BUFFER_HEADER_SIZE
+					 + rpl->ReparseDataLength,
+			    NULL, 0);
+  if (!NT_SUCCESS (status))
+    {
+      SetLastError (RtlNtStatusToDosError (status));
+      FILE_DISPOSITION_INFORMATION fdi = { TRUE };
+      status = NtSetInformationFile (fh, &io, &fdi, sizeof fdi,
+				     FileDispositionInformation);
+      NtClose (fh);
+      if (!NT_SUCCESS (status))
+	debug_printf ("Setting delete dispostion failed, status = %y", status);
+      return -1;
+    }
+  NtClose (fh);
+  return 0;
+}
+
 int
 symlink_worker (const char *oldpath, path_conv &win32_newpath, bool isdevice)
 {
@@ -1908,7 +1999,7 @@ symlink_worker (const char *oldpath, path_conv &win32_newpath, bool isdevice)
 	  __leave;
 	}
 
-      /* Handle NFS and native symlinks in their own functions. */
+      /* Handle NFS, native symlinks and WSL symlinks in their own functions. */
       switch (wsym_type)
 	{
 	case WSYM_nfs:
@@ -1928,6 +2019,17 @@ symlink_worker (const char *oldpath, path_conv &win32_newpath, bool isdevice)
 	    }
 	  /* Otherwise, fall back to default symlink type. */
 	  wsym_type = WSYM_sysfile;
+	  /*FALLTHRU*/
+	case WSYM_sysfile:
+	  if (win32_newpath.fs_flags () & FILE_SUPPORTS_REPARSE_POINTS)
+	    {
+	      res = symlink_wsl (oldpath, win32_newpath);
+	      if (!res)
+		__leave;
+	    }
+	  /* On FSes not supporting reparse points, or in case of an error
+	     creating the WSL symlink, fall back to creating the plain old
+	     SYSTEM file symlink. */
 	  break;
 	default:
 	  break;
@@ -2360,29 +2462,6 @@ check_reparse_point_string (PUNICODE_STRING subst)
   return false;
 }
 
-#ifndef IO_REPARSE_TAG_LX_SYMLINK
-#define IO_REPARSE_TAG_LX_SYMLINK	(0xa000001d)
-#endif
-
-typedef struct _REPARSE_LX_SYMLINK_BUFFER
-{
-  DWORD	ReparseTag;
-  WORD	ReparseDataLength;
-  WORD	Reserved;
-  struct {
-    DWORD FileType;	/* Take member name with a grain of salt.  Value is
-			   apparently always 2 for symlinks. */
-    char  PathBuffer[1];/* POSIX path as given to symlink(2).
-			   Path is not \0 terminated.
-			   Length is ReparseDataLength - sizeof (FileType).
-			   Always UTF-8.
-			   Chars given in incompatible codesets, e. g. umlauts
-			   in ISO-8859-x, are converted to the Unicode
-			   REPLACEMENT CHARACTER 0xfffd == \xef\xbf\bd */
-  } LxSymlinkReparseBuffer;
-} REPARSE_LX_SYMLINK_BUFFER,*PREPARSE_LX_SYMLINK_BUFFER;
-
-
 /* Return values:
     <0: Negative errno.
      0: No symlink.
diff --git a/winsup/cygwin/release/3.1.5 b/winsup/cygwin/release/3.1.5
index 6f16aad93..c1d1cd89d 100644
--- a/winsup/cygwin/release/3.1.5
+++ b/winsup/cygwin/release/3.1.5
@@ -1,7 +1,7 @@
 What changed:
 -------------
 
-- Support WSL symlinks.
+- Support WSL symlinks.  Create those by default now.
 
 
 Bug Fixes:
diff --git a/winsup/doc/new-features.xml b/winsup/doc/new-features.xml
index a200300f2..60ae60de4 100644
--- a/winsup/doc/new-features.xml
+++ b/winsup/doc/new-features.xml
@@ -83,7 +83,7 @@ https://gitlab.freedesktop.org/terminal-wg/specifications/issues/9.
 </para></listitem>
 
 <listitem><para>
-Support WSL symlinks.
+Support WSL symlinks.  Create those by default now.
 </para></listitem>
 
 </itemizedlist>


More information about the Cygwin-cvs mailing list