From 10b06c5ee0712991bc7cec3688f8059069f9fcd2 Mon Sep 17 00:00:00 2001
From: Corinna Vinschen <corinna@vinschen.de>
Date: Wed, 21 Feb 2001 21:49:37 +0000
Subject: [PATCH]         * Makefile.in: Add `-lshell32 -luuid' to link pass
 for new-cygwin1.dll.         * autoload.cc: Add LoadDLLinitfunc for
 ole32.dll.         Add LoadDLLfuncEx statements for CoInitialize@4,
 CoUninitialize@0         and CoCreateInstance@20.         * dir.cc
 (dir_suffixes): New datastructure.         (readdir): Check for R/O *.lnk
 files to hide the suffix.         (opendir): Use `dir_suffixes' in path
 conversion.         (rmdir): Ditto.         * fhandler.cc
 (fhandler_disk_file::fstat): Add S_IFLNK flag         before calling
 `get_file_attribute'. Take FILE_ATTRIBUTE_READONLY         into account only
 if the file is no symlink.         * path.cc (inner_suffixes): New
 datastructure.         (SYMLINKATTR): Eliminated.         (path_conv::check):
 Use `inner_suffixes' on inner path components.         (shortcut_header): New
 global static variable.         (shortcut_initalized): Ditto.        
 (create_shortcut_header): New function.         (cmp_shortcut_header): Ditto.
         (symlink): Create symlinks by creating windows shortcuts. Preserve   
      the old code.         (symlink_info::check_shortcut): New method.       
  (symlink_info::check_sysfile): Ditto.         (symlink_info::check): Check
 for shortcuts. Move code reading         old system attribute symlinks into
 symlink_info::check_sysfile().         (chdir): Use `dir_suffixes' in path
 conversion.         * security.cc (get_file_attribute): Check for S_IFLNK
 flag.         Force 0777 permissions then.         * spawn.cc (std_suffixes):
 Add ".lnk" suffix.         * syscalls.cc (_unlink): Use `inner_suffixes' in
 path conversion.         Check for shortcut symlinks to eliminate R/O
 attribute before         calling DeleteFile().         (stat_suffixes): Add
 ".lnk" suffix.         (stat_worker): Force 0777 permissions if file is a
 symlink.

---
 winsup/cygwin/ChangeLog   |  36 ++++
 winsup/cygwin/Makefile.in |   2 +-
 winsup/cygwin/autoload.cc |  34 ++++
 winsup/cygwin/dir.cc      |  19 ++-
 winsup/cygwin/fhandler.cc |   5 +-
 winsup/cygwin/path.cc     | 349 +++++++++++++++++++++++++++++++-------
 winsup/cygwin/security.cc |  12 +-
 winsup/cygwin/spawn.cc    |   1 +
 winsup/cygwin/syscalls.cc |  15 +-
 9 files changed, 403 insertions(+), 70 deletions(-)

diff --git a/winsup/cygwin/ChangeLog b/winsup/cygwin/ChangeLog
index 73fd3a24d..13a5cf15f 100644
--- a/winsup/cygwin/ChangeLog
+++ b/winsup/cygwin/ChangeLog
@@ -1,3 +1,39 @@
+Wed Feb 21 22:41:00 2001  Corinna Vinschen <corinna@vinschen.de>
+
+	* Makefile.in: Add `-lshell32 -luuid' to link pass for new-cygwin1.dll.
+	* autoload.cc: Add LoadDLLinitfunc for ole32.dll.
+	Add LoadDLLfuncEx statements for CoInitialize@4, CoUninitialize@0
+	and CoCreateInstance@20.
+	* dir.cc (dir_suffixes): New datastructure.
+	(readdir): Check for R/O *.lnk files to hide the suffix.
+	(opendir): Use `dir_suffixes' in path conversion.
+	(rmdir): Ditto.
+	* fhandler.cc (fhandler_disk_file::fstat): Add S_IFLNK flag
+	before calling `get_file_attribute'. Take FILE_ATTRIBUTE_READONLY
+	into account only if the file is no symlink.
+	* path.cc (inner_suffixes): New datastructure.
+	(SYMLINKATTR): Eliminated.
+	(path_conv::check): Use `inner_suffixes' on inner path components.
+	(shortcut_header): New global static variable.
+	(shortcut_initalized): Ditto.
+	(create_shortcut_header): New function.
+	(cmp_shortcut_header): Ditto.
+	(symlink): Create symlinks by creating windows shortcuts. Preserve
+	the old code.
+	(symlink_info::check_shortcut): New method.
+	(symlink_info::check_sysfile): Ditto.
+	(symlink_info::check): Check for shortcuts. Move code reading
+	old system attribute symlinks into symlink_info::check_sysfile().
+	(chdir): Use `dir_suffixes' in path conversion.
+	* security.cc (get_file_attribute): Check for S_IFLNK flag.
+	Force 0777 permissions then.
+	* spawn.cc (std_suffixes): Add ".lnk" suffix.
+	* syscalls.cc (_unlink): Use `inner_suffixes' in path conversion.
+	Check for shortcut symlinks to eliminate R/O attribute before
+	calling DeleteFile().
+	(stat_suffixes): Add ".lnk" suffix.
+	(stat_worker): Force 0777 permissions if file is a symlink.
+
 2001-02-21  Egor Duda  <deo@logos-m.ru>
 
 	* sigproc.cc (getsem): Make semaphore always non-inheritable.
diff --git a/winsup/cygwin/Makefile.in b/winsup/cygwin/Makefile.in
index 18dbdc7c0..547b94727 100644
--- a/winsup/cygwin/Makefile.in
+++ b/winsup/cygwin/Makefile.in
@@ -190,7 +190,7 @@ new-$(LIB_NAME): $(LIB_NAME)
 
 new-$(DLL_NAME): $(DLL_OFILES) $(DEF_FILE) $(DLL_IMPORTS) $(LIBC) $(LIBM) Makefile winver_stamp
 	$(CXX) $(CXXFLAGS) -nostdlib -Wl,-shared -o $@ -e $(DLL_ENTRY) $(DEF_FILE) $(DLL_OFILES) version.o \
-	winver.o $(DLL_IMPORTS) $(MALLOC_OBJ) $(LIBM) $(LIBC) -lgcc -lstdc++
+	winver.o $(DLL_IMPORTS) $(MALLOC_OBJ) $(LIBM) $(LIBC) -lgcc -lstdc++ -lshell32 -luuid
 
 dll_ofiles: $(DLL_OFILES)
 
diff --git a/winsup/cygwin/autoload.cc b/winsup/cygwin/autoload.cc
index 00ee37b2c..bd035e397 100644
--- a/winsup/cygwin/autoload.cc
+++ b/winsup/cygwin/autoload.cc
@@ -90,6 +90,13 @@ LoadDLLinitfunc (advapi32)
 LoadDLLinitfunc (netapi32)
 {
   HANDLE h;
+  static NO_COPY LONG here = -1L;
+
+  while (InterlockedIncrement (&here))
+    {
+      InterlockedDecrement (&here);
+      Sleep (0);
+    }
 
   if ((h = LoadLibrary ("netapi32.dll")) != NULL)
     netapi32_handle = h;
@@ -197,6 +204,28 @@ LoadDLLinitfunc (iphlpapi)
   return 0;
 }
 
+LoadDLLinitfunc (ole32)
+{
+  HANDLE h;
+  static NO_COPY LONG here = -1L;
+
+  while (InterlockedIncrement (&here))
+    {
+      InterlockedDecrement (&here);
+      Sleep (0);
+    }
+
+  if (ole32_handle)
+    /* nothing to do */;
+  else if ((h = LoadLibrary ("ole32.dll")) != NULL)
+    ole32_handle = h;
+  else if (!ole32_handle)
+    api_fatal ("could not load ole32.dll, %E");
+
+  InterlockedDecrement (&here);
+  return 0;
+}
+
 static void __stdcall dummy_autoload (void) __attribute__ ((unused));
 static void __stdcall
 dummy_autoload (void)
@@ -339,5 +368,10 @@ LoadDLLfuncEx (WSASocketA, 24, ws2_32, 1)
 LoadDLLinit (iphlpapi)
 LoadDLLfuncEx (GetIfTable, 12, iphlpapi, 1)
 LoadDLLfuncEx (GetIpAddrTable, 12, iphlpapi, 1)
+
+LoadDLLinit (ole32)
+LoadDLLfunc (CoInitialize, 4, ole32)
+LoadDLLfunc (CoUninitialize, 0, ole32)
+LoadDLLfunc (CoCreateInstance, 20, ole32)
 }
 }
diff --git a/winsup/cygwin/dir.cc b/winsup/cygwin/dir.cc
index a14b44f8f..b4b93da20 100644
--- a/winsup/cygwin/dir.cc
+++ b/winsup/cygwin/dir.cc
@@ -59,6 +59,13 @@ writable_directory (const char *file)
 #endif
 }
 
+suffix_info dir_suffixes[] =
+{
+  suffix_info ("", 1),
+  suffix_info (".lnk", 1),
+  suffix_info (NULL)
+};
+
 /* opendir: POSIX 5.1.2.1 */
 extern "C" DIR *
 opendir (const char *dirname)
@@ -68,7 +75,7 @@ opendir (const char *dirname)
   DIR *res = 0;
   struct stat statbuf;
 
-  path_conv real_dirname (dirname, PC_SYM_FOLLOW | PC_FULL);
+  path_conv real_dirname (dirname, PC_SYM_FOLLOW | PC_FULL, dir_suffixes);
 
   if (real_dirname.error)
     {
@@ -174,6 +181,14 @@ readdir (DIR * dir)
   /* We get here if `buf' contains valid data.  */
   strcpy (dir->__d_dirent->d_name, buf.cFileName);
 
+  if (buf.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
+    {
+      char *c = dir->__d_dirent->d_name;
+      int len = strlen (c);
+      if (!strcasecmp (c + len - 4, ".lnk"))
+        c[len - 4] = '\0';
+    }
+
   /* Compute d_ino by combining filename hash with the directory hash
      (which was stored in dir->__d_dirhash when opendir was called). */
   if (buf.cFileName[0] == '.')
@@ -316,7 +331,7 @@ rmdir (const char *dir)
 {
   int res = -1;
 
-  path_conv real_dir (dir, PC_SYM_NOFOLLOW);
+  path_conv real_dir (dir, PC_SYM_NOFOLLOW, dir_suffixes);
 
   if (real_dir.error)
     {
diff --git a/winsup/cygwin/fhandler.cc b/winsup/cygwin/fhandler.cc
index 1c9e600a8..fdd6e91f8 100644
--- a/winsup/cygwin/fhandler.cc
+++ b/winsup/cygwin/fhandler.cc
@@ -921,6 +921,8 @@ fhandler_disk_file::fstat (struct stat *buf)
      directory. This is used, to set S_ISVTX, if needed.  */
   if (local.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
     buf->st_mode |= S_IFDIR;
+  if (get_symlink_p ())
+    buf->st_mode |= S_IFLNK;
   if (!get_file_attribute (has_acls (),
 			   get_win32_name (),
 			   &buf->st_mode,
@@ -928,7 +930,8 @@ fhandler_disk_file::fstat (struct stat *buf)
 			   &buf->st_gid))
     {
       /* If read-only attribute is set, modify ntsec return value */
-      if (local.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
+      if ((local.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
+          && !get_symlink_p ())
 	buf->st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
 
       buf->st_mode &= ~S_IFMT;
diff --git a/winsup/cygwin/path.cc b/winsup/cygwin/path.cc
index 20553920c..2b7b1eb53 100644
--- a/winsup/cygwin/path.cc
+++ b/winsup/cygwin/path.cc
@@ -57,6 +57,10 @@ details. */
 #include <errno.h>
 #include <ctype.h>
 #include <winioctl.h>
+#include <wingdi.h>
+#include <winuser.h>
+#include <winnls.h>
+#include <winnetwk.h>
 #include <sys/cygwin.h>
 #include <cygwin/version.h>
 #include "cygerrno.h"
@@ -70,6 +74,9 @@ details. */
 #include "registry.h"
 #include "security.h"
 #include <assert.h>
+#include <shlobj.h>
+#include <objidl.h>
+#include <objbase.h>
 
 static int normalize_win32_path (const char *src, char *dst);
 static void slashify (const char *src, char *dst, int trailing_slash_p);
@@ -87,19 +94,25 @@ struct symlink_info
   int is_symlink;
   int error;
   symlink_info (): known_suffix (NULL), contents (buf + MAX_PATH + 1) {}
+  int check_shortcut (const char *, DWORD, HANDLE);
+  int check_sysfile (const char *, DWORD, HANDLE);
   int check (const char *path, const suffix_info *suffixes);
 };
 
+/* These suffixes are the only ones allowed in inner path components. */
+suffix_info inner_suffixes[] =
+{
+  suffix_info ("", 1),
+  suffix_info (".lnk", 1),
+  suffix_info (NULL)
+};
+
 cwdstuff cygcwd;	/* The current working directory. */
 
 #define path_prefix_p(p1, p2, l1) \
        ((cyg_tolower(*(p1))==cyg_tolower(*(p2))) && \
        path_prefix_p_(p1, p2, l1))
 
-#define SYMLINKATTR(x) \
-  (((x) & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_DIRECTORY)) == \
-   FILE_ATTRIBUTE_SYSTEM)
-
 /* Determine if path prefix matches current cygdrive */
 #define iscygdrive(path) \
   (path_prefix_p (mount_table->cygdrive, (path), mount_table->cygdrive_len))
@@ -264,7 +277,7 @@ path_conv::check (const char *src, unsigned opt,
 	     class if we're working on an inner component of the path */
 	  if (component)
 	    {
-	      suff = NULL;
+	      suff = inner_suffixes;
 	      sym.pflags = 0;
 	    }
 	  else
@@ -2182,6 +2195,35 @@ endmntent (FILE *)
 
 /********************** Symbolic Link Support **************************/
 
+/* The header written to a shortcut by Cygwin or U/WIN. */
+#define SHORTCUT_HDR_SIZE	76
+static char shortcut_header[SHORTCUT_HDR_SIZE];
+static BOOL shortcut_initalized = FALSE;
+
+static void
+create_shortcut_header (void)
+{
+  if (!shortcut_initalized)
+    {
+      shortcut_header[0] = 'L';
+      shortcut_header[4] = '\001';
+      shortcut_header[5] = '\024';
+      shortcut_header[6] = '\002';
+      shortcut_header[12] = '\300';
+      shortcut_header[19] = 'F';
+      shortcut_header[20] = '\f';
+      shortcut_header[60] = '\001';
+      shortcut_initalized = TRUE;
+    }
+}
+
+static BOOL
+cmp_shortcut_header (const char *file_header)
+{
+  create_shortcut_header ();
+  return memcmp (shortcut_header, file_header, SHORTCUT_HDR_SIZE);
+}
+
 /* Create a symlink from FROMPATH to TOPATH. */
 
 extern "C"
@@ -2191,7 +2233,17 @@ symlink (const char *topath, const char *frompath)
   HANDLE h;
   int res = -1;
 
+#if 0
   path_conv win32_path (frompath, PC_SYM_NOFOLLOW);
+#else
+  char from[MAX_PATH];
+  unsigned short len = strlen (frompath);
+  strcpy (from, frompath);
+  if (len <= 4 || strcasecmp (from + len - 4, ".lnk"))
+    strcpy (from + len, ".lnk");
+  path_conv win32_path (from, PC_SYM_NOFOLLOW);
+#endif
+
   if (win32_path.error)
     {
       set_errno (win32_path.error);
@@ -2224,20 +2276,17 @@ symlink (const char *topath, const char *frompath)
       __seterrno ();
   else
     {
+      DWORD written;
+#if 0
+      /* This is the old technique creating a symlink.
+         Preserved to have a fallback. */
       char buf[sizeof (SYMLINK_COOKIE) + MAX_PATH + 10];
 
       __small_sprintf (buf, "%s%s", SYMLINK_COOKIE, topath);
       DWORD len = strlen (buf) + 1;
 
       /* Note that the terminating nul is written.  */
-      DWORD written;
-      if (!WriteFile (h, buf, len, &written, NULL) || written != len)
-	{
-	  __seterrno ();
-	  CloseHandle (h);
-	  DeleteFileA (win32_path.get_win32 ());
-	}
-      else
+      if (WriteFile (h, buf, len, &written, NULL) || written != len)
 	{
 	  CloseHandle (h);
 	  set_file_attribute (win32_path.has_acls (),
@@ -2246,6 +2295,36 @@ symlink (const char *topath, const char *frompath)
 	  SetFileAttributesA (win32_path.get_win32 (), FILE_ATTRIBUTE_SYSTEM);
 	  res = 0;
 	}
+#else
+      create_shortcut_header ();
+      path_conv win32_topath (topath, PC_SYM_NOFOLLOW);
+      len = strlen (topath);
+      unsigned short win_len = strlen (win32_topath.get_win32 ());
+      if (WriteFile (h, shortcut_header, SHORTCUT_HDR_SIZE, &written, NULL)
+          && written == SHORTCUT_HDR_SIZE
+	  && WriteFile (h, &len, sizeof len, &written, NULL)
+	  && written == sizeof len
+	  && WriteFile (h, topath, len, &written, NULL)
+	  && written == len
+	  && WriteFile (h, &win_len, sizeof win_len, &written, NULL)
+	  && written == sizeof win_len
+	  && WriteFile (h, win32_topath.get_win32 (), win_len, &written, NULL)
+	  && written == win_len)
+        {
+          CloseHandle (h);
+          set_file_attribute (win32_path.has_acls (),
+                              win32_path.get_win32 (),
+                              S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO);
+          SetFileAttributesA (win32_path.get_win32 (), FILE_ATTRIBUTE_READONLY);
+          res = 0;
+	}
+#endif
+      else
+	{
+	  __seterrno ();
+	  CloseHandle (h);
+	  DeleteFileA (win32_path.get_win32 ());
+	}
     }
 
 done:
@@ -2283,6 +2362,177 @@ next_suffix (char *ext_here, const suffix_info *&suffixes)
   return 0;
 }
 
+int
+symlink_info::check_shortcut (const char *path, DWORD fileattr, HANDLE h)
+{
+  HRESULT hres;
+  IShellLink *psl = NULL;
+  IPersistFile *ppf = NULL;
+  WCHAR wc_path[MAX_PATH];
+  char full_path[MAX_PATH];
+  WIN32_FIND_DATA wfd;
+  DWORD len = 0;
+  int res = 0;
+
+  /* Initialize COM library. */
+  CoInitialize (NULL);
+
+  /* Get a pointer to the IShellLink interface. */
+  hres = CoCreateInstance (CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
+			   IID_IShellLink, (void **)&psl);
+  if (FAILED (hres))
+    {
+      debug_printf ("CoCreateInstance failed");
+      goto close_it;
+    }
+  /* Get a pointer to the IPersistFile interface. */
+  hres = psl->QueryInterface (IID_IPersistFile, (void **)&ppf);
+  if (FAILED (hres))
+    {
+      debug_printf ("QueryInterface failed");
+      goto close_it;
+    }
+  /* Load the shortcut. */
+  MultiByteToWideChar(CP_ACP, 0, path, -1, wc_path, MAX_PATH);
+  hres = ppf->Load (wc_path, STGM_READ);
+  if (FAILED (hres))
+    {
+      debug_printf ("Load failed");
+      goto close_it;
+    }
+  /* Try the description (containing a POSIX path) first. */
+  if (fileattr & FILE_ATTRIBUTE_READONLY)
+    {
+      /* An additional check is needed to prove if it's a shortcut
+         really created by Cygwin or U/WIN. */
+      char file_header[SHORTCUT_HDR_SIZE];
+      DWORD got;
+
+      if (! ReadFile (h, file_header, SHORTCUT_HDR_SIZE, &got, 0))
+	{
+	  debug_printf ("ReadFile failed");
+	  error = EIO;
+	  goto close_it_dont_set_error;
+	}
+      if (got == SHORTCUT_HDR_SIZE && !cmp_shortcut_header (file_header))
+        {
+	  hres = psl->GetDescription (contents, MAX_PATH);
+	  if (FAILED (hres))
+	    {
+	      debug_printf ("GetDescription failed");
+	      goto close_it;
+	    }
+	  len = strlen (contents);
+	}
+    }
+  /* No description or not R/O: Check the "official" path. */
+  if (len == 0)
+    {
+      /* Convert to full path (easy way) */
+      if ((path[0] == '\\' && path[1] == '\\')
+	  || (_toupper (path[0]) >= 'A' && _toupper (path[0]) <= 'Z'
+	      && path[1] == ':'))
+	len = 0;
+      else
+	{
+	  len = GetCurrentDirectory (MAX_PATH, full_path);
+	  if (path[0] == '\\')
+	    len = 2;
+	  else if (full_path[len - 1] != '\\')
+	    strcpy (full_path + len++, "\\");
+	}
+      strcpy (full_path + len, path);
+      debug_printf ("full_path = <%s>", full_path);
+      /* Set relative path inside of IShellLink interface. */
+      hres = psl->SetRelativePath (full_path, 0);
+      if (FAILED (hres))
+	{
+	  debug_printf ("SetRelativePath failed");
+	  goto close_it;
+	}
+      /* Get the path to the shortcut target. */
+      hres = psl->GetPath (contents, MAX_PATH, &wfd, 0);
+      if (FAILED(hres))
+	{
+	  debug_printf ("GetPath failed");
+	  goto close_it;
+	}
+    }
+  /* It's a symlink.  */
+  pflags = PATH_SYMLINK;
+  res = strlen (contents);
+
+close_it:
+  if (FAILED (hres))
+    error = geterrno_from_win_error (HRESULT_CODE (hres), EACCES);
+
+close_it_dont_set_error:
+  /* Release the pointer to IPersistFile. */
+  if (ppf)
+    ppf->Release();
+  /* Release the pointer to IShellLink. */
+  if (psl)
+    psl->Release();
+  /* Uninitialize COM library. */
+  CoUninitialize ();
+
+  syscall_printf ("%d = symlink.check_shortcut (%s, %s) (%p)",
+		  res, path, contents, pflags);
+  return res;
+}
+
+int
+symlink_info::check_sysfile (const char *path, DWORD fileattr, HANDLE h)
+{
+  char cookie_buf[sizeof (SYMLINK_COOKIE) - 1];
+  DWORD got;
+  int res = 0;
+
+  if (! ReadFile (h, cookie_buf, sizeof (cookie_buf), &got, 0))
+    {
+      debug_printf ("ReadFile1 failed");
+      error = EIO;
+    }
+  else if (got == sizeof (cookie_buf)
+	   && memcmp (cookie_buf, SYMLINK_COOKIE, sizeof (cookie_buf)) == 0)
+    {
+      /* It's a symlink.  */
+      pflags = PATH_SYMLINK;
+
+      res = ReadFile (h, contents, MAX_PATH + 1, &got, 0);
+      if (!res)
+	{
+	  debug_printf ("ReadFile2 failed");
+	  error = EIO;
+	}
+      else
+	{
+	  /* Versions prior to b16 stored several trailing
+	     NULs with the path (to fill the path out to 1024
+	     chars).  Current versions only store one trailing
+	     NUL.  The length returned is the path without
+	     *any* trailing NULs.  We also have to handle (or
+	     at least not die from) corrupted paths.  */
+	  if (memchr (contents, 0, got) != NULL)
+	    res = strlen (contents);
+	  else
+	    res = got;
+	}
+    }
+  else if (got == sizeof (cookie_buf)
+	   && memcmp (cookie_buf, SOCKET_COOKIE, sizeof (cookie_buf)) == 0)
+    pflags |= PATH_SOCKET;
+  else
+    {
+      /* Not a symlink, see if executable.  */
+      if (!(pflags & PATH_ALL_EXEC) && has_exec_chars (cookie_buf, got))
+	pflags |= PATH_EXEC;
+    }
+  syscall_printf ("%d = symlink.check_sysfile (%s, %s) (%p)",
+		  res, path, contents, pflags);
+  return res;
+}
+
 /* Check if PATH is a symlink.  PATH must be a valid Win32 path name.
 
    If PATH is a symlink, put the value of the symlink--the file to
@@ -2340,9 +2590,22 @@ symlink_info::check (const char *in_path, const suffix_info *suffixes)
 	  continue;
 	}
 
+      int sym_check = 0;
+
+      if (fileattr & FILE_ATTRIBUTE_DIRECTORY)
+        goto file_not_symlink;
+
+      /* Windows shortcuts are treated as symlinks. */
+      if (!strcasecmp (path + strlen (path) - 4, ".lnk"))
+	sym_check = 1;
+
+      /* 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 (!(pflags & PATH_SYMLINK) && !SYMLINKATTR (fileattr))
+      if (fileattr & FILE_ATTRIBUTE_SYSTEM)
+	sym_check = 2;
+
+      if (!sym_check && !(pflags & PATH_SYMLINK))
 	goto file_not_symlink;
 
       /* Open the file.  */
@@ -2352,54 +2615,15 @@ symlink_info::check (const char *in_path, const suffix_info *suffixes)
       res = -1;
       if (h == INVALID_HANDLE_VALUE)
 	goto file_not_symlink;
-      else
+      else if (sym_check == 1 && !(res = check_shortcut (path, fileattr, h)))
 	{
-	  char cookie_buf[sizeof (SYMLINK_COOKIE) - 1];
-	  DWORD got;
-
-	  if (! ReadFile (h, cookie_buf, sizeof (cookie_buf), &got, 0))
-	    error = EIO;
-	  else if (got == sizeof (cookie_buf)
-		   && memcmp (cookie_buf, SYMLINK_COOKIE,
-			      sizeof (cookie_buf)) == 0)
-	    {
-	      /* It's a symlink.  */
-	      pflags = PATH_SYMLINK;
-
-	      res = ReadFile (h, contents, MAX_PATH + 1, &got, 0);
-	      if (!res)
-		error = EIO;
-	      else
-		{
-		  /* Versions prior to b16 stored several trailing
-		     NULs with the path (to fill the path out to 1024
-		     chars).  Current versions only store one trailing
-		     NUL.  The length returned is the path without
-		     *any* trailing NULs.  We also have to handle (or
-		     at least not die from) corrupted paths.  */
-		  if (memchr (contents, 0, got) != NULL)
-		    res = strlen (contents);
-		  else
-		    res = got;
-		}
-	    }
-	  else if (got == sizeof (cookie_buf)
-		   && memcmp (cookie_buf, SOCKET_COOKIE,
-			      sizeof (cookie_buf)) == 0)
-	    {
-	      pflags |= PATH_SOCKET;
-	      goto close_and_return;
-	    }
-	  else
-	    {
-	      /* Not a symlink, see if executable.  */
-	      if (!(pflags & PATH_ALL_EXEC) &&
-		  has_exec_chars (cookie_buf, got))
-		pflags |= PATH_EXEC;
-	    close_and_return:
-	      CloseHandle (h);
-	      goto file_not_symlink;
-	    }
+	  CloseHandle (h);
+	  goto file_not_symlink;
+	}
+      else if (sym_check == 2 && !(res = check_sysfile (path, fileattr, h)))
+	{
+	  CloseHandle (h);
+	  goto file_not_symlink;
 	}
 
       CloseHandle (h);
@@ -2553,8 +2777,9 @@ int
 chdir (const char *dir)
 {
   MALLOC_CHECK;
+  extern suffix_info dir_suffixes[];
   syscall_printf ("dir %s", dir);
-  path_conv path (dir, PC_FULL | PC_SYM_FOLLOW);
+  path_conv path (dir, PC_FULL | PC_SYM_FOLLOW, dir_suffixes);
 
   if (path.error)
     {
diff --git a/winsup/cygwin/security.cc b/winsup/cygwin/security.cc
index 34ffc02fe..0f1a52dae 100644
--- a/winsup/cygwin/security.cc
+++ b/winsup/cygwin/security.cc
@@ -808,8 +808,15 @@ int
 get_file_attribute (int use_ntsec, const char *file,
 		    int *attribute, uid_t *uidret, gid_t *gidret)
 {
+  int res;
+
   if (use_ntsec && allow_ntsec)
-    return get_nt_attribute (file, attribute, uidret, gidret);
+    {
+      res = get_nt_attribute (file, attribute, uidret, gidret);
+      if (attribute && (*attribute & S_IFLNK) == S_IFLNK)
+	*attribute |= S_IRWXU | S_IRWXG | S_IRWXO;
+      return res;
+    }
 
   if (uidret)
     *uidret = getuid ();
@@ -819,8 +826,7 @@ get_file_attribute (int use_ntsec, const char *file,
   if (!attribute)
     return 0;
 
-  int res = NTReadEA (file, ".UNIXATTR",
-		      (char *) attribute, sizeof (*attribute));
+  res = NTReadEA (file, ".UNIXATTR", (char *) attribute, sizeof (*attribute));
 
   /* symlinks are everything for everyone!*/
   if ((*attribute & S_IFLNK) == S_IFLNK)
diff --git a/winsup/cygwin/spawn.cc b/winsup/cygwin/spawn.cc
index cb30d6be7..507dfa563 100644
--- a/winsup/cygwin/spawn.cc
+++ b/winsup/cygwin/spawn.cc
@@ -41,6 +41,7 @@ details. */
 static suffix_info std_suffixes[] =
 {
   suffix_info (".exe", 1), suffix_info ("", 1),
+  suffix_info (".lnk", 1),
   suffix_info (".com"), suffix_info (".cmd"),
   suffix_info (".bat"), suffix_info (".dll"),
   suffix_info (NULL)
diff --git a/winsup/cygwin/syscalls.cc b/winsup/cygwin/syscalls.cc
index d5bb510ee..efe2c69ea 100644
--- a/winsup/cygwin/syscalls.cc
+++ b/winsup/cygwin/syscalls.cc
@@ -65,10 +65,11 @@ close_all_files (void)
 extern "C" int
 _unlink (const char *ourname)
 {
+  extern suffix_info inner_suffixes[];
   int res = -1;
   sigframe thisframe (mainthread);
 
-  path_conv win32_name (ourname, PC_SYM_NOFOLLOW | PC_FULL);
+  path_conv win32_name (ourname, PC_SYM_NOFOLLOW | PC_FULL, inner_suffixes);
 
   if (win32_name.error)
     {
@@ -94,6 +95,15 @@ _unlink (const char *ourname)
       goto done;
     }
 
+  /* Check for shortcut as symlink condition. */
+  if (atts != 0xffffffff && atts & FILE_ATTRIBUTE_READONLY)
+    {
+      int len = strlen (win32_name.get_win32 ());
+      if (len > 4 && !strcasecmp (win32_name.get_win32 () + len - 4, ".lnk"))
+	SetFileAttributes (win32_name.get_win32 (),
+	              win32_name.file_attributes () & ~FILE_ATTRIBUTE_READONLY);
+    }
+
   for (int i = 0; i < 2; i++)
     {
       if (DeleteFile (win32_name))
@@ -1021,6 +1031,7 @@ suffix_info stat_suffixes[] =
 {
   suffix_info ("", 1),
   suffix_info (".exe", 1),
+  suffix_info (".lnk", 1),
   suffix_info (NULL)
 };
 
@@ -1135,6 +1146,8 @@ stat_worker (const char *caller, const char *name, struct stat *buf,
 	  buf->st_mode |= STD_RBITS | STD_XBITS;
 	  if ((atts & FILE_ATTRIBUTE_READONLY) == 0)
 	    buf->st_mode |= STD_WBITS;
+	  if (real_path.issymlink ())
+	    buf->st_mode |= S_IRWXU | S_IRWXG | S_IRWXO;
 	  get_file_attribute (FALSE, real_path.get_win32 (),
 			      NULL, &buf->st_uid, &buf->st_gid);
 	}