[prev in list] [next in list] [prev in thread] [next in thread] 

List:       cygwin-patches
Subject:    [PATCH] Add ability to use NTFS native directory symlinks without admin rights on XP and later
From:       Evgeny Grin <k2k () yandex ! ru>
Date:       2015-09-23 15:23:51
Message-ID: 766021443021831 () web4g ! yandex ! ru
[Download RAW message or body]

(second try, previous was truncated) 
This patch will add ability to create directory junction which are supported from \
Windows 2000 and not require any special rights (unlike file/directory symbolic \
links). New three modes for symbolic links creation added: "safenative", \
"safenativestrict" and "safenativeonly". First two allow fallback to "native" and \
"nativesctrict", last one use only directory junction for symbolic links (file links \
will fail with this setting, but it can be useful for derived projects like MSys or \
Git for Windows).

Only creation of directory junctions is implemented in this patch, reading and \
resolving junction as symbolic links are already supported by Cygwin. I'd recommend \
to set default mode to "safenative" as it allow to use system functionality \
out-of-box with fallback to Cygwin's functionality where system's symlinks are not \
available.


---
 winsup/cygwin/environ.cc |   9 ++
 winsup/cygwin/globals.cc |   5 +-
 winsup/cygwin/path.cc    | 215 ++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 226 insertions(+), 3 deletions(-)

diff --git a/winsup/cygwin/environ.cc b/winsup/cygwin/environ.cc
index 8f25fb1..b02d685 100644
--- a/winsup/cygwin/environ.cc
+++ b/winsup/cygwin/environ.cc
@@ -84,6 +84,15 @@ set_winsymlinks (const char *buf)
     allow_winsymlinks = WSYM_lnk;
   else if (ascii_strncasematch (buf, "lnk", 3))
     allow_winsymlinks = WSYM_lnk;
+  else if (ascii_strncasematch (buf, "safenative", 10))
+    {
+      if (ascii_strcasematch (buf + 10, "strict"))
+        allow_winsymlinks = WSYM_safenativestrict;
+      else if (ascii_strcasematch (buf + 10, "only"))
+        allow_winsymlinks = WSYM_safenativeonly;
+      else
+        allow_winsymlinks = WSYM_safenative;
+    }
   /* Make sure to try native symlinks only on systems supporting them. */
   else if (ascii_strncasematch (buf, "native", 6))
     {
diff --git a/winsup/cygwin/globals.cc b/winsup/cygwin/globals.cc
index 09c08f2..c468741 100644
--- a/winsup/cygwin/globals.cc
+++ b/winsup/cygwin/globals.cc
@@ -59,7 +59,10 @@ enum winsym_t
   WSYM_lnk,
   WSYM_native,
   WSYM_nativestrict,
-  WSYM_nfs
+  WSYM_nfs,
+  WSYM_safenative,
+  WSYM_safenativestrict,
+  WSYM_safenativeonly
 };
 
 exit_states NO_COPY exit_state;
diff --git a/winsup/cygwin/path.cc b/winsup/cygwin/path.cc
index 89dbdab..fb7b191 100644
--- a/winsup/cygwin/path.cc
+++ b/winsup/cygwin/path.cc
@@ -1734,6 +1734,207 @@ symlink_native (const char *oldpath, path_conv \
&win32_newpath)  return 0;
 }
 
+static int
+symlink_safenative (const char *oldpath, path_conv &win32_newpath)
+{
+  tmp_pathbuf tp;
+  path_conv win32_oldpath;
+  PUNICODE_STRING nt_oldpath;
+  NTSTATUS status;
+  HANDLE fh;
+  OBJECT_ATTRIBUTES attr;
+  IO_STATUS_BLOCK io;
+  PREPARSE_DATA_BUFFER prdb;
+  ACCESS_MASK access_mask;
+
+  syscall_printf ("symlink_safenative (oldpath: '%s', newpath: '%s')", oldpath,
+                win32_newpath.get_posix ());
+
+  /* Directory junction cannot be created on remote drive.
+     Leave this check here to simplify routing in symlink_worker(). */
+  if (win32_newpath.isremote ())
+    {
+      debug_printf ("'%S' is on remote drive",
+                    win32_newpath.get_nt_native_path ());
+      set_errno (EXDEV);
+      return -1;
+    }
+
+  win32_oldpath.check (oldpath, PC_SYM_NOFOLLOW);
+
+  /* Make sure that symlink target is directory as directory junction
+     can point only to directory. If target is not exist yet then
+     don't create junction to prevent an invalid state with junction
+     pointing to later created regular file. */
+  if (!win32_oldpath.exists () || !win32_oldpath.isdir ())
+    {
+      debug_printf ("'%S' does not exist or is not a directory",
+                    win32_oldpath.get_nt_native_path ());
+      set_errno (ENOTDIR);
+      return -1;
+    }
+
+  nt_oldpath = win32_oldpath.get_nt_native_path ();
+
+  /* Directory junction can point only to local mounted drive. */
+  if (nt_oldpath->Length < 6 * sizeof (wchar_t) ||
+      nt_oldpath->Buffer[5] != L':')
+    {
+      debug_printf ("'%S' is not on local mounted drive", nt_oldpath);
+      set_errno (EXDEV);
+      return -1;
+    }
+
+  /* Make sure that slash for root directory is present otherwise OS will 
+     misbehavior when resolving such junction. This is an extra care as 
+     get_nt_native_path() should return correct path. */
+  if (nt_oldpath->Length < 7 * sizeof (wchar_t) ||
+      nt_oldpath->Buffer[6] != L'\\')
+    {
+      set_errno (ENOTDIR);
+      return -1;
+    }
+
+  if (offsetof (REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer) +
+      nt_oldpath->Length * 2 + (2 - 4) * sizeof (wchar_t)
+      > MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
+    {
+      set_errno (ENAMETOOLONG);
+      return -1;
+    }
+
+#if MAXIMUM_REPARSE_DATA_BUFFER_SIZE > NT_MAX_PATH
+#error REPARSE_DATA_BUFFER do not fit into tmp_pathbuf::c_get() buffer. Rewrite \
code. +#endif
+  prdb = (PREPARSE_DATA_BUFFER) tp.c_get ();
+
+  prdb->Reserved = 0;
+  prdb->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
+  prdb->MountPointReparseBuffer.SubstituteNameOffset = 0;
+  memcpy(prdb->MountPointReparseBuffer.PathBuffer, nt_oldpath->Buffer,
+         nt_oldpath->Length);
+  prdb->MountPointReparseBuffer.SubstituteNameLength =
+          (USHORT)(nt_oldpath->Length);
+  prdb->MountPointReparseBuffer.PathBuffer[
+          prdb->MountPointReparseBuffer.SubstituteNameLength / sizeof (wchar_t)] = \
0; +  prdb->MountPointReparseBuffer.PrintNameOffset =
+          prdb->MountPointReparseBuffer.SubstituteNameLength + 1 * sizeof (wchar_t);
+  memcpy(&(prdb->MountPointReparseBuffer.PathBuffer[
+         prdb->MountPointReparseBuffer.PrintNameOffset / sizeof (wchar_t)]),
+         &(nt_oldpath->Buffer[4]), nt_oldpath->Length - 4 * sizeof (wchar_t));
+  prdb->MountPointReparseBuffer.PrintNameLength =
+          (USHORT)(nt_oldpath->Length - 4 * sizeof (wchar_t));
+  prdb->MountPointReparseBuffer.PathBuffer[
+          (prdb->MountPointReparseBuffer.PrintNameOffset + 
+           prdb->MountPointReparseBuffer.PrintNameLength) / sizeof (wchar_t)] = 0;
+  prdb->ReparseDataLength = (USHORT) offsetof(REPARSE_DATA_BUFFER,
+                                              MountPointReparseBuffer.PathBuffer) -
+          REPARSE_DATA_BUFFER_HEADER_SIZE +
+          prdb->MountPointReparseBuffer.PrintNameOffset +
+          prdb->MountPointReparseBuffer.PrintNameLength + 1 * sizeof (wchar_t);
+
+  /* DELETE access is required to delete empty directory if it's not
+     transformed into directory junctions. */
+  access_mask = FILE_WRITE_ATTRIBUTES | FILE_LIST_DIRECTORY |
+      FILE_TRAVERSE | SYNCHRONIZE | DELETE;
+  /* READ_CONTROL and WRITE_DAC are required for reuse handle in
+     set_file_attribute() otherwise function will need to reopen file. */
+  if (win32_newpath.has_acls ())
+    access_mask |= READ_CONTROL | WRITE_DAC;
+
+  status = NtCreateFile (&fh, access_mask,
+                         win32_newpath.get_object_attr (attr, sec_none_nih),
+                         &io, NULL, FILE_ATTRIBUTE_DIRECTORY,
+                         0, FILE_CREATE, FILE_DIRECTORY_FILE |
+                         FILE_OPEN_REPARSE_POINT | FILE_OPEN_FOR_BACKUP_INTENT,
+                         NULL, 0);
+  if (!NT_SUCCESS (status))
+    {
+      if (status == STATUS_ACCESS_DENIED)
+        {
+          ULONG share_access;
+          /* Retry with less requested access rights. */
+          debug_printf ("Creating '%S' with restricted access rights",
+                        win32_newpath.get_nt_native_path ());
+
+          access_mask &= ~(DELETE | READ_CONTROL | WRITE_DAC);
+
+          /* Allow sharing otherwise set_file_attribute() will fail. */
+          share_access = win32_newpath.has_acls () ? (FILE_SHARE_READ |
+                                                      FILE_SHARE_WRITE) : 0;
+          status = NtCreateFile (&fh, access_mask,
+                                 win32_newpath.get_object_attr (attr, sec_none_nih),
+                                 &io, NULL, FILE_ATTRIBUTE_DIRECTORY,
+                                 share_access, FILE_CREATE, FILE_DIRECTORY_FILE |
+                                 FILE_OPEN_REPARSE_POINT |
+                                 FILE_OPEN_FOR_BACKUP_INTENT, NULL, 0);
+        }
+      if (!NT_SUCCESS (status))
+        {
+          debug_printf ("Creating '%S' failed, status = %y", 
+                        win32_newpath.get_nt_native_path (), status);
+          __seterrno_from_nt_status (status);
+          return -1;
+        }
+    }
+
+  if (win32_newpath.has_acls ())
+    set_file_attribute (fh, win32_newpath, ILLEGAL_UID, ILLEGAL_GID,
+                        S_JUSTCREATED | S_IFDIR | S_IFLNK |
+                        STD_RBITS | STD_WBITS | STD_XBITS);
+
+  debug_printf ("Setting SubstituteName '%W' and PrintName '%W' for directory \
junction '%S'",  +                prdb->MountPointReparseBuffer.PathBuffer +
+                  prdb->MountPointReparseBuffer.SubstituteNameOffset / sizeof \
(wchar_t), +                prdb->MountPointReparseBuffer.PathBuffer +
+                  prdb->MountPointReparseBuffer.PrintNameOffset / sizeof (wchar_t),
+                win32_newpath.get_nt_native_path ());
+  status = NtFsControlFile (fh, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT,
+                            prdb, (ULONG)(prdb->ReparseDataLength +
+                                    REPARSE_DATA_BUFFER_HEADER_SIZE), NULL, 0);
+  if (status == STATUS_PENDING)
+    {
+      if (WaitForSingleObject (fh, 2000) == WAIT_OBJECT_0)
+        status = io.Status;
+      else
+        status = STATUS_ACCESS_DENIED;
+    }
+
+  if (!NT_SUCCESS (status))
+    {
+      FILE_DISPOSITION_INFORMATION disp = { TRUE };
+      if (status == STATUS_IO_REPARSE_TAG_INVALID || status == \
STATUS_IO_REPARSE_DATA_INVALID) +        debug_printf ("Setting reparse point failed \
because reparse point data is invalid, status = %y", status); +      else
+        debug_printf ("Setting reparse point failed, status = %y", status);
+      __seterrno_from_nt_status (status);
+
+      /* Delete created junction blank. */
+      status = NtSetInformationFile (fh, &io, &disp, sizeof (disp),
+                                     FileDispositionInformation);
+      NtClose (fh);
+      if (!NT_SUCCESS (status))
+        {
+          /* Reopen junction blank for deletion. */
+          status = NtCreateFile (&fh, DELETE,
+                                 win32_newpath.get_object_attr (attr, sec_none_nih),
+                                 &io, NULL, FILE_ATTRIBUTE_DIRECTORY,
+                                 FILE_SHARE_DELETE, FILE_OPEN, FILE_DIRECTORY_FILE |
+                                 FILE_OPEN_REPARSE_POINT | \
FILE_OPEN_FOR_BACKUP_INTENT | +                                 FILE_DELETE_ON_CLOSE, \
NULL, 0); +          if (NT_SUCCESS (status))
+            NtClose (fh);
+        }
+      if (!NT_SUCCESS (status) && status != STATUS_DELETE_PENDING)
+        debug_printf ("Removing directory junction blank failed, status = %y", \
status); +
+      return -1;
+  }
+
+  NtClose (fh);
+  return 0;
+}
+
 int
 symlink_worker (const char *oldpath, const char *newpath, bool isdevice)
 {
@@ -1798,7 +1999,10 @@ symlink_worker (const char *oldpath, const char *newpath, bool \
isdevice)  wsym_type = WSYM_nativestrict;
 	}
       /* Don't try native symlinks on FSes not supporting reparse points. */
-      else if ((wsym_type == WSYM_native || wsym_type == WSYM_nativestrict)
+      else if ((wsym_type == WSYM_native || wsym_type == WSYM_nativestrict
+                || wsym_type == WSYM_safenative
+                || wsym_type == WSYM_safenativestrict
+                || wsym_type == WSYM_safenativeonly)
 	       && !(win32_newpath.fs_flags () & FILE_SUPPORTS_REPARSE_POINTS))
 	wsym_type = WSYM_sysfile;
 
@@ -1838,13 +2042,20 @@ symlink_worker (const char *oldpath, const char *newpath, \
bool isdevice)  case WSYM_nfs:
 	  res = symlink_nfs (oldpath, win32_newpath);
 	  __leave;
+        case WSYM_safenative:
+	case WSYM_safenativestrict:
+        case WSYM_safenativeonly:
+          res = symlink_safenative (oldpath, win32_newpath);
+	  if (!res || wsym_type == WSYM_safenativeonly)
+	    __leave;
+	/* Intentional fall-through */
 	case WSYM_native:
 	case WSYM_nativestrict:
 	  res = symlink_native (oldpath, win32_newpath);
 	  if (!res)
 	    __leave;
 	  /* Strictly native?  Too bad. */
-	  if (wsym_type == WSYM_nativestrict)
+	  if (wsym_type == WSYM_nativestrict || wsym_type == WSYM_safenativestrict)
 	    {
 	      __seterrno ();
 	      __leave;
-- 
2.5.1.windows.1


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic