Tcl Source Code

Artifact [64030dcab1]
Login

Artifact 64030dcab17024c031c21f106ef551cf1850a083:

Attachment "win-tclWinFile.c.diff-u" to ticket [1344540fff] added by jfg118 2006-03-11 05:13:59.
--- ../../../tcl8.4.12-src/tcl8.4.12/win/tclWinFile.c	2005-11-15 08:41:41.000000000 -0800
+++ tclWinFile.c	2006-03-10 14:02:35.883563200 -0800
@@ -179,6 +179,7 @@
 static int NativeStat(CONST TCHAR *path, Tcl_StatBuf *statPtr, int checkLinks);
 static unsigned short NativeStatMode(DWORD attr, int checkLinks, int isExec);
 static int NativeIsExec(CONST TCHAR *path);
+static int NativeIsWritable(DWORD attr, CONST TCHAR *path);
 static int NativeReadReparse(CONST TCHAR* LinkDirectory, 
 			     REPARSE_DATA_BUFFER* buffer);
 static int NativeWriteReparse(CONST TCHAR* LinkDirectory, 
@@ -1341,7 +1342,7 @@
 	return -1;
     }
 
-    if ((mode & W_OK) && (attr & FILE_ATTRIBUTE_READONLY)) {
+    if ((mode & W_OK) && !NativeIsWritable(attr, nativePath)) {
 	/*
 	 * File is not writable.
 	 */
@@ -1368,6 +1369,164 @@
     return 0;
 }
 
+ /*
+  *----------------------------------------------------------------------
+  *
+  * 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(
+    DWORD attr,
+    CONST TCHAR *nativePath)
+{
+    BYTE *secDescPtr = 0;
+    DWORD secDescLen = 0;
+    HANDLE hToken = NULL;
+    BOOL isWritable = FALSE;
+
+    /*
+     * One time initialization, dynamically load Windows NT features
+     */
+
+    static const SECURITY_INFORMATION infoBits = OWNER_SECURITY_INFORMATION 
+      | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION;
+
+    static BOOL (WINAPI *getFileSecurityProc)(LPCSTR, SECURITY_INFORMATION, 
+      PSECURITY_DESCRIPTOR, DWORD, LPDWORD);
+    static BOOL (WINAPI *openProcessTokenProc)(HANDLE, DWORD, PHANDLE);
+    static BOOL (WINAPI *duplicateTokenProc)(HANDLE, SECURITY_IMPERSONATION_LEVEL, 
+      PHANDLE);
+    static VOID (WINAPI *mapGenericMaskProc)(PDWORD, PGENERIC_MAPPING);
+    static BOOL (WINAPI *accessCheckProc)(PSECURITY_DESCRIPTOR, HANDLE, DWORD, 
+      PGENERIC_MAPPING, PPRIVILEGE_SET, LPDWORD, LPDWORD, LPBOOL);
+
+    static int initialized = 0;
+
+    if (!initialized) {
+        TCL_DECLARE_MUTEX(writableMutex)
+        Tcl_MutexLock(&writableMutex);
+        if (!initialized) {
+            HINSTANCE hInstance = LoadLibrary("Advapi32");
+            if (hInstance != NULL) {
+                getFileSecurityProc = (BOOL (WINAPI *)(LPCSTR, 
+                  SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, DWORD, LPDWORD))
+                  GetProcAddress(hInstance, tclWinProcs->useWide ? 
+                  "GetFileSecurityW" : "GetFileSecurityA");
+                openProcessTokenProc = (BOOL (WINAPI *)(HANDLE, DWORD, 
+                  PHANDLE)) GetProcAddress(hInstance, "OpenProcessToken");
+                duplicateTokenProc = (BOOL (WINAPI *)(HANDLE, 
+                  SECURITY_IMPERSONATION_LEVEL, PHANDLE)) 
+                  GetProcAddress(hInstance, "DuplicateToken");
+                mapGenericMaskProc = (VOID (WINAPI *)(PDWORD, 
+                  PGENERIC_MAPPING)) GetProcAddress(hInstance, 
+                  "MapGenericMask");
+                accessCheckProc = (BOOL (WINAPI *)(PSECURITY_DESCRIPTOR,
+                  HANDLE, DWORD, PGENERIC_MAPPING, PPRIVILEGE_SET, 
+                  LPDWORD, LPDWORD, LPBOOL)) GetProcAddress(hInstance, 
+                  "AccessCheck");
+                if (getFileSecurityProc && openProcessTokenProc 
+                  && duplicateTokenProc && mapGenericMaskProc 
+                  && accessCheckProc) {
+                    initialized = 1;
+                }
+            }
+            if (!initialized)
+                initialized = -1;
+        }
+        Tcl_MutexUnlock(&writableMutex);
+    }
+
+    /*
+     * Windows NT features not available, default back to readonly flag
+     */
+    if (initialized < 0)
+        return (attr & FILE_ATTRIBUTE_READONLY) ? 0 : 1;
+
+    /*
+     * If this is not a directory and it is marked read-only then just return
+     * not writable.  No need to check the ACL.
+     */
+    if (!(attr & FILE_ATTRIBUTE_DIRECTORY) && (attr & FILE_ATTRIBUTE_READONLY))
+        return 0;
+
+    /*
+     * Read the security descriptor for the file or directory. Note the
+     * first call obtains the size of the security descriptor.
+     */
+
+    if (!getFileSecurityProc(nativePath, infoBits, NULL, 0, &secDescLen)) {
+        if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+            DWORD secDescLen2 = 0;
+            secDescPtr = (BYTE *) ckalloc(secDescLen);
+            if (!getFileSecurityProc(nativePath, infoBits, secDescPtr, 
+              secDescLen, &secDescLen2) 
+              || (secDescLen < secDescLen2)) {
+                goto done;
+            }
+        } else {
+            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 (openProcessTokenProc(GetCurrentProcess(), TOKEN_DUPLICATE, &hToken)) {
+        HANDLE hIToken = NULL;
+        BOOL status = duplicateTokenProc(hToken, SecurityIdentification, 
+          &hIToken);
+
+        CloseHandle(hToken);
+        if (status) {
+            PRIVILEGE_SET privilegeSet;
+            DWORD privilegeSetLength = sizeof(privilegeSet);
+            DWORD granted; /* unused */
+            DWORD accessDesired = ACCESS_WRITE;
+
+            /* Initialize generic mapping structure to map all. */
+            GENERIC_MAPPING GenericMapping;
+            memset(&GenericMapping, 0xff, sizeof(GENERIC_MAPPING));
+            GenericMapping.GenericRead = ACCESS_READ;
+            GenericMapping.GenericWrite = ACCESS_WRITE;
+            GenericMapping.GenericExecute = 0;
+            GenericMapping.GenericAll = ACCESS_READ | ACCESS_WRITE;
+            mapGenericMaskProc(&accessDesired, &GenericMapping);
+
+            accessCheckProc(secDescPtr, hIToken, accessDesired,
+              &GenericMapping, &privilegeSet, &privilegeSetLength, &granted, 
+              &isWritable);
+            CloseHandle(hIToken);
+        }
+    }
+
+    /*
+     * Release any temporary space we may be using and convert the writability
+     * value into a boolean for return.
+     */
+
+  done:
+    if (secDescPtr) {
+        ckfree(secDescPtr);
+    }
+    return isWritable != 0;
+}
+
+
 /*
  *----------------------------------------------------------------------
  *