# HG changeset patch
# User David Pope <d.e.pope@gmail.com>
# Date 1332997900 14400
# Node ID 9d528fecbf1af8c625b0d6aa5998f3afc4cee4ce
# Parent  2cfb68fa26cd10c54b17f833ba67e3f408c48ca4
This patch fixes the "can't save to symlinks" bug on Windows.  It augments the Windows version of mch_is_linked() to also return TRUE if the file is a symbolic link, so the delete-then-move pattern is avoided and the symlink is preserved.

The patch also addresses a separate bug I encountered while fixing the above.  If you set nowritebackup on Windows, and then save a file that you opened via a symbolic link, the readonly attribute gets set.

The cause was that the Windows version of mch_getperm() was returning Windows FILE_ATTRIBUTE_* flags instead of the Unix-style flags in mode_t, which was then later passed unchanged to the CRT open() function (which expects mode_t flags).

For a normal (non-symlink, non-whatever) file, this just happened to be FILE_ATTRIBUTE_NORMAL (0200, 0x80), which maps to the Unix S_IWUSR.  By sheer chance, this meant that normal files correctly received write permission, i.e. no readonly flag.

For a symlink, FILE_ATTRIBUTE_NORMAL is not set; for example, the symlink I was testing against returned FILE_ATTRIBUTE_ARCHIVE (040, 0x20) and FILE_ATTRIBUTE_REPARSE_POINT (02000, 0x400).  By the time these made it back to create(), it appeared as though we wanted no write permissions, i.e. set the readonly flag.

The patch changes os_win32.c so that all code outside of the file deals with mode_t flags, while os_win32.c itself deals with FILE_ATTRIBUTE_* flags.  There are a couple of new internal helper functions for this.

diff -r 2cfb68fa26cd -r 9d528fecbf1a src/os_win32.c
--- a/src/os_win32.c	Wed Mar 28 20:51:51 2012 +0200
+++ b/src/os_win32.c	Thu Mar 29 01:11:40 2012 -0400
@@ -208,6 +208,11 @@
 static char *vimrun_path = "vimrun ";
 #endif
 
+static int win32_getattrs(char_u *name);
+static int win32_setattrs(char_u *name, int attrs);
+static int win32_set_archive(char_u *name);
+static int win32_file_is_symbolic_link(char_u *name);
+
 #ifndef FEAT_GUI_W32
 static int suppress_winsize = 1;	/* don't fiddle with console */
 #endif
@@ -2568,57 +2573,85 @@
 /*
  * get file permissions for `name'
  * -1 : error
- * else FILE_ATTRIBUTE_* defined in winnt.h
+ * else mode_t
  */
     long
 mch_getperm(char_u *name)
 {
+    struct _stat st;
+    int n;
+
+    /*  The FindFirstFileEx call in the CRT _stat() implementation chokes on trailing slashes.
+     *  If needed, make a copy and strip off any slashes.
+     */
+    char_u trimmed[MAX_PATH];
+    size_t len = STRLEN(name);
+    if (name[len-1] == '\\') {
+	vim_strncpy(trimmed, name, len);
+	while (name[len-1] == '\\') {
+	    trimmed[len-1] = '\0';
+	    len--;
+	    name = trimmed;
+	}
+    }
+
 #ifdef FEAT_MBYTE
     if (enc_codepage >= 0 && (int)GetACP() != enc_codepage)
     {
 	WCHAR	*p = enc_to_utf16(name, NULL);
-	long	n;
 
 	if (p != NULL)
 	{
-	    n = (long)GetFileAttributesW(p);
+	    n = _wstat(p, &st);
 	    vim_free(p);
-	    if (n >= 0 || GetLastError() != ERROR_CALL_NOT_IMPLEMENTED)
-		return n;
+	    if (n == 0)
+		return st.st_mode;
+	    if (GetLastError() != ERROR_CALL_NOT_IMPLEMENTED)
+		return -1;
 	    /* Retry with non-wide function (for Windows 98). */
 	}
     }
 #endif
-    return (long)GetFileAttributes((char *)name);
+    n = _stat(name, &st);
+    return n == 0 ? (int)st.st_mode : -1;
 }
 
 
 /*
  * set file permission for `name' to `perm'
+ *
+ * return FAIL for failure, OK otherwise
  */
     int
 mch_setperm(
     char_u  *name,
     long    perm)
 {
-    perm |= FILE_ATTRIBUTE_ARCHIVE;	/* file has changed, set archive bit */
+    long	n;
 #ifdef FEAT_MBYTE
+    WCHAR *p;
     if (enc_codepage >= 0 && (int)GetACP() != enc_codepage)
     {
-	WCHAR	*p = enc_to_utf16(name, NULL);
-	long	n;
+	p = enc_to_utf16(name, NULL);
 
 	if (p != NULL)
 	{
-	    n = (long)SetFileAttributesW(p, perm);
+	    n = _wchmod(p, perm);
 	    vim_free(p);
-	    if (n || GetLastError() != ERROR_CALL_NOT_IMPLEMENTED)
-		return n ? OK : FAIL;
+	    if (n == -1 && GetLastError() != ERROR_CALL_NOT_IMPLEMENTED)
+		return FAIL;
 	    /* Retry with non-wide function (for Windows 98). */
 	}
     }
+    if (p == NULL)
 #endif
-    return SetFileAttributes((char *)name, perm) ? OK : FAIL;
+	n = _chmod(name, perm);
+    if (n == -1)
+	return FAIL;
+
+    win32_set_archive(name);
+
+    return OK;
 }
 
 /*
@@ -2627,49 +2660,12 @@
     void
 mch_hide(char_u *name)
 {
-    int		perm;
-#ifdef FEAT_MBYTE
-    WCHAR	*p = NULL;
-
-    if (enc_codepage >= 0 && (int)GetACP() != enc_codepage)
-	p = enc_to_utf16(name, NULL);
-#endif
-
-#ifdef FEAT_MBYTE
-    if (p != NULL)
-    {
-	perm = GetFileAttributesW(p);
-	if (perm < 0 && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED)
-	{
-	    /* Retry with non-wide function (for Windows 98). */
-	    vim_free(p);
-	    p = NULL;
-	}
-    }
-    if (p == NULL)
-#endif
-	perm = GetFileAttributes((char *)name);
-    if (perm >= 0)
-    {
-	perm |= FILE_ATTRIBUTE_HIDDEN;
-#ifdef FEAT_MBYTE
-	if (p != NULL)
-	{
-	    if (SetFileAttributesW(p, perm) == 0
-		    && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED)
-	    {
-		/* Retry with non-wide function (for Windows 98). */
-		vim_free(p);
-		p = NULL;
-	    }
-	}
-	if (p == NULL)
-#endif
-	    SetFileAttributes((char *)name, perm);
-    }
-#ifdef FEAT_MBYTE
-    vim_free(p);
-#endif
+    int attrs = win32_getattrs(name);
+    if (attrs == -1)
+	return;
+
+    attrs |= FILE_ATTRIBUTE_HIDDEN;
+    win32_setattrs(name, attrs);
 }
 
 /*
@@ -2679,7 +2675,7 @@
     int
 mch_isdir(char_u *name)
 {
-    int f = mch_getperm(name);
+    int f = win32_getattrs(name);
 
     if (f == -1)
 	return FALSE;		    /* file does not exist at all */
@@ -2712,15 +2708,21 @@
 }
 
 /*
- * Return TRUE if file "fname" has more than one link.
+ * Return TRUE if file "fname" has more than one link or if it is a symbolic link.
  */
     int
 mch_is_linked(char_u *fname)
 {
     BY_HANDLE_FILE_INFORMATION info;
 
-    return win32_fileinfo(fname, &info) == FILEINFO_OK
-						   && info.nNumberOfLinks > 1;
+    if (win32_fileinfo(fname, &info) == FILEINFO_OK
+						   && info.nNumberOfLinks > 1)
+ 	return TRUE;
+
+    if (win32_file_is_symbolic_link(fname))
+ 	return TRUE;
+
+    return FALSE;
 }
 
 /*
@@ -2786,6 +2788,144 @@
     return res;
 }
 
+    static
+    int
+win32_getattrs(char_u *name)
+{
+    int		attr;
+#ifdef FEAT_MBYTE
+    WCHAR	*p = NULL;
+
+    if (enc_codepage >= 0 && (int)GetACP() != enc_codepage)
+	p = enc_to_utf16(name, NULL);
+
+    if (p != NULL)
+    {
+	attr = GetFileAttributesW(p);
+	if (attr < 0 && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED)
+	{
+	    /* Retry with non-wide function (for Windows 98). */
+	    vim_free(p);
+	    p = NULL;
+	}
+    }
+    if (p == NULL)
+#endif
+	attr = GetFileAttributes((char *)name);
+#ifdef FEAT_MBYTE
+    vim_free(p);
+#endif
+    return attr;
+}
+
+    static
+    int
+win32_setattrs(char_u *name, int attrs)
+{
+    int res;
+#ifdef FEAT_MBYTE
+    WCHAR	*p = NULL;
+
+    if (enc_codepage >= 0 && (int)GetACP() != enc_codepage)
+	p = enc_to_utf16(name, NULL);
+
+    if (p != NULL)
+    {
+	res = SetFileAttributesW(p, attrs);
+	if (res == 0
+	    && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED)
+	{
+	    /* Retry with non-wide function (for Windows 98). */
+	    vim_free(p);
+	    p = NULL;
+	}
+	else
+	    res = -1;
+    }
+    if (p == NULL)
+#endif
+	SetFileAttributes((char *)name, attrs);
+#ifdef FEAT_MBYTE
+    vim_free(p);
+#endif
+    return res;
+}
+
+/*
+ * Set archive flag for "name".
+ */
+    static
+    int
+win32_set_archive(char_u *name)
+{
+    int attrs = win32_getattrs(name);
+    if (attrs == -1)
+	return -1;
+
+    attrs |= FILE_ATTRIBUTE_ARCHIVE;
+    return win32_setattrs(name, attrs);
+}
+
+    static
+    int
+win32_file_is_symbolic_link(char_u *fname)
+{
+    HANDLE hFind;
+    int res = FALSE;
+    WIN32_FIND_DATAA findDataA;
+    DWORD fileFlags = 0, reparseTag = 0;
+#ifdef FEAT_MBYTE
+    WCHAR	*wn = NULL;
+    WIN32_FIND_DATAW findDataW;
+
+    if (enc_codepage >= 0 && (int)GetACP() != enc_codepage)
+    {
+	wn = enc_to_utf16(fname, NULL);
+    }
+    if (wn != NULL)
+    {
+	hFind = FindFirstFileW(wn, &findDataW);
+	if (hFind == INVALID_HANDLE_VALUE)
+	{
+	    vim_free(wn);
+
+	    if(GetLastError() == ERROR_CALL_NOT_IMPLEMENTED)
+	    {
+		/* Retry with non-wide function (for Windows 98). */
+
+		hFind = FindFirstFile(fname, &findDataA);
+		if (hFind != INVALID_HANDLE_VALUE)
+		{
+		    fileFlags = findDataA.dwFileAttributes;
+		    reparseTag = findDataA.dwReserved0;
+		}
+	    }
+	}
+	else
+	{
+	    fileFlags = findDataW.dwFileAttributes;
+	    reparseTag = findDataW.dwReserved0;
+	}
+    }
+#else
+    hFind = FindFirstFile(fname, &findDataA);
+    if (hFind != INVALID_HANDLE_VALUE)
+    {
+	fileFlags = findDataA.dwFileAttributes;
+	reparseTag = findDataA.dwReserved0;
+    }
+#endif
+
+    if (hFind != INVALID_HANDLE_VALUE)
+	FindClose(hFind);
+
+    if ( (fileFlags & 0x400) /* FILE_ATTRIBUTE_REPARSE_POINT */ == 0x400
+	    && reparseTag == 0xA000000C /* IO_REPARSE_TAG_SYMLINK */ )
+	res = TRUE;
+
+    return res;
+}
+
 /*
  * Return TRUE if file or directory "name" is writable (not readonly).
  * Strange semantics of Win32: a readonly directory is writable, but you can't
@@ -2794,10 +2934,10 @@
     int
 mch_writable(char_u *name)
 {
-    int perm = mch_getperm(name);
-
-    return (perm != -1 && (!(perm & FILE_ATTRIBUTE_READONLY)
-				       || (perm & FILE_ATTRIBUTE_DIRECTORY)));
+    int attrs = win32_getattrs(name);
+
+    return (attrs != -1 && (!(attrs & FILE_ATTRIBUTE_READONLY)
+			  || (attrs & FILE_ATTRIBUTE_DIRECTORY)));
 }
 
 /*
@@ -4952,25 +5092,8 @@
     int
 mch_remove(char_u *name)
 {
-#ifdef FEAT_MBYTE
-    WCHAR	*wn = NULL;
-    int		n;
-
-    if (enc_codepage >= 0 && (int)GetACP() != enc_codepage)
-    {
-	wn = enc_to_utf16(name, NULL);
-	if (wn != NULL)
-	{
-	    SetFileAttributesW(wn, FILE_ATTRIBUTE_NORMAL);
-	    n = DeleteFileW(wn) ? 0 : -1;
-	    vim_free(wn);
-	    if (n == 0 || GetLastError() != ERROR_CALL_NOT_IMPLEMENTED)
-		return n;
-	    /* Retry with non-wide function (for Windows 98). */
-	}
-    }
-#endif
-    SetFileAttributes(name, FILE_ATTRIBUTE_NORMAL);
+    win32_setattrs(name, FILE_ATTRIBUTE_NORMAL);
+
     return DeleteFile(name) ? 0 : -1;
 }
 
# HG changeset patch
# User David Pope <d.e.pope@gmail.com>
# Date 1332999932 14400
# Node ID 549798ad1375e6dda2e5f63913e37480d652847c
# Parent  9d528fecbf1af8c625b0d6aa5998f3afc4cee4ce
tweak reassignment placement

diff -r 9d528fecbf1a -r 549798ad1375 src/os_win32.c
--- a/src/os_win32.c	Thu Mar 29 01:11:40 2012 -0400
+++ b/src/os_win32.c	Thu Mar 29 01:45:32 2012 -0400
@@ -2588,10 +2588,10 @@
     size_t len = STRLEN(name);
     if (name[len-1] == '\\') {
 	vim_strncpy(trimmed, name, len);
+	name = trimmed;
 	while (name[len-1] == '\\') {
 	    trimmed[len-1] = '\0';
 	    len--;
-	    name = trimmed;
 	}
     }
 
exporting patches:
<fdopen>
<fdopen>
