Tcl Source Code

Artifact [8d0d79246c]
Login

Artifact 8d0d79246cff8d6ffc0dcbd64a0663232354eccb:

Attachment "writable.patch" to ticket [1193497fff] added by dkf 2005-10-21 15:48:14.
Index: tclWinFile.c
===================================================================
RCS file: /cvsroot/tcl/tcl/win/tclWinFile.c,v
retrieving revision 1.77
diff -u -r1.77 tclWinFile.c
--- tclWinFile.c	31 Aug 2005 15:12:18 -0000	1.77
+++ tclWinFile.c	21 Oct 2005 08:40:33 -0000
@@ -186,6 +186,7 @@
 			    int checkLinks);
 static unsigned short	NativeStatMode(DWORD attr, int checkLinks, int isExec);
 static int		NativeIsExec(CONST TCHAR *path);
+static int		NativeIsWritable(CONST TCHAR *path);
 static int		NativeReadReparse(CONST TCHAR *LinkDirectory,
 			    REPARSE_DATA_BUFFER* buffer);
 static int		NativeWriteReparse(CONST TCHAR *LinkDirectory,
@@ -1541,7 +1542,7 @@
 	return -1;
     }
 
-    if ((mode & W_OK) && (attr & FILE_ATTRIBUTE_READONLY)) {
+    if ((mode & W_OK) && !NativeIsWritable(nativePath)) {
 	/*
 	 * File is not writable.
 	 */
@@ -1780,6 +1781,112 @@
 /*
  *----------------------------------------------------------------------
  *
+ * NativeIsWritable --
+ *
+ *	Determine if a path is writable. On Windows this requires more than a
+ *	simple read only flag check for directories because Windows uses this
+ *	as a flag to customize the folder options. [Bug 1193497]
+ *
+ * Results:
+ *	1 = writable, 0 = not.
+ *
+ * Side effects:
+ *	May smash the last error value (errors are just reported by this
+ *	function as non-writable files).
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+NativeIsWritable(
+    CONST TCHAR *nativePath)
+{
+    char secDescStaticBuffer[1024], *secDescPtr = secDescStaticBuffer;
+    DWORD secDescLen;
+    HANDLE hToken = NULL;
+    int isWritable = 0;		/* Any non-zero value indicates writability. */
+
+#define FILE_SEC_INFO_BITS \
+	OWNER_SECURITY_INFORMATION \
+	| GROUP_SECURITY_INFORMATION \
+	| DACL_SECURITY_INFORMATION
+
+    /*
+     * If we're using the wide version of the Win32 interface, we're not (in
+     * practice) on a platform that needs any of the security-descriptor
+     * managing anyway. So just use the old code.
+     */
+
+    if (tclWinProcs->useWide) {
+	int attr = (*tclWinProcs->getFileAttributesProc)(nativePath);
+
+	isWritable = attr & FILE_ATTRIBUTE_READONLY;
+	goto done;
+    }
+
+    /*
+     * Read the security descriptor for the file or directory. Note that this
+     * may take two syscalls if we have to allocate a larger buffer.
+     */
+
+    if (!GetFileSecurity(nativePath, FILE_SEC_INFO_BITS, secDescPtr,
+	    sizeof(secDescStaticBuffer), &secDescLen)) {
+	goto done;
+    }
+    if (sizeof(secDescStaticBuffer) < secDescLen) {
+	DWORD secDescLen2;
+
+	secDescPtr = (char *) ckalloc(secDescLen);
+	if (!GetFileSecurity(nativePath, FILE_SEC_INFO_BITS, secDescPtr,
+		secDescLen, &secDescLen2) || (secDescLen < secDescLen2)) {
+	    goto done;
+	}
+    }
+
+    /*
+     * Get the security identity of the process and (if successful) use it
+     * with the security descriptor of the file to find out if the file really
+     * is writable.
+     */
+
+    if (OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE, &hToken)) {
+	HANDLE hIToken = NULL;
+	BOOL status = DuplicateToken(hToken, SecurityIdentification, &hIToken);
+
+	CloseHandle(hToken);
+	if (status) {
+	    GENERIC_MAPPING mapping = {
+		FILE_GENERIC_READ,
+		FILE_GENERIC_WRITE,
+		FILE_GENERIC_EXECUTE,
+		FILE_ALL_ACCESS
+	    };
+	    char privBuf[sizeof(PRIVILEGE_SET)+3*sizeof(LUID_AND_ATTRIBUTES)];
+	    DWORD privLength = sizeof(privBuf);
+	    DWORD granted;			/* dummy */
+
+	    isWritable = AccessCheck(secDescPtr, hIToken, FILE_WRITE_DATA,
+		    &mapping, (PPRIVILEGE_SET) privBuf, &privLength, &granted,
+		    &status);
+	    CloseHandle(hIToken);
+	}
+    }
+
+    /*
+     * Release any temporary space we may be using and convert the writability
+     * value into a boolean for return.
+     */
+
+  done:
+    if (secDescPtr != secDescStaticBuffer) {
+	ckfree(secDescPtr);
+    }
+    return isWritable != 0;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
  * TclpObjChdir --
  *
  *	This function replaces the library version of chdir().