summaryrefslogtreecommitdiffstats
path: root/win
diff options
context:
space:
mode:
authorvincentdarley <vincentdarley>2006-03-14 19:34:25 (GMT)
committervincentdarley <vincentdarley>2006-03-14 19:34:25 (GMT)
commit54b14791b5842fe7e3c0ad6a77e2e6bd10237627 (patch)
tree174d43369770a1c8db6a61a7f76f4fe572109cfd /win
parent02d304b56fb79258a992e9f47e325445f2e75d40 (diff)
downloadtcl-54b14791b5842fe7e3c0ad6a77e2e6bd10237627.zip
tcl-54b14791b5842fe7e3c0ad6a77e2e6bd10237627.tar.gz
tcl-54b14791b5842fe7e3c0ad6a77e2e6bd10237627.tar.bz2
90% fix of file writable issues on Windows
Diffstat (limited to 'win')
-rw-r--r--win/tclWinFile.c6
-rw-r--r--win/tclWinTest.c321
2 files changed, 323 insertions, 4 deletions
diff --git a/win/tclWinFile.c b/win/tclWinFile.c
index 4a13d39..196c7ac 100644
--- a/win/tclWinFile.c
+++ b/win/tclWinFile.c
@@ -11,7 +11,7 @@
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tclWinFile.c,v 1.84 2006/03/10 10:33:55 vincentdarley Exp $
+ * RCS: @(#) $Id: tclWinFile.c,v 1.85 2006/03/14 19:34:30 vincentdarley Exp $
*/
/* #define _WIN32_WINNT 0x0500 */
@@ -1547,8 +1547,7 @@ NativeAccess(
}
if ((mode & W_OK)
- && !(attr & FILE_ATTRIBUTE_DIRECTORY)
- /* && (tclWinProcs->getFileSecurityProc == NULL) */
+ && (tclWinProcs->getFileSecurityProc == NULL)
&& (attr & FILE_ATTRIBUTE_READONLY)) {
/*
* We don't have the advanced 'getFileSecurityProc', and
@@ -1725,6 +1724,7 @@ NativeAccess(
* we must still check the 'attr' value.
*/
if ((mode & W_OK)
+ && !(attr & FILE_ATTRIBUTE_DIRECTORY)
&& (attr & FILE_ATTRIBUTE_READONLY)) {
Tcl_SetErrno(EACCES);
return -1;
diff --git a/win/tclWinTest.c b/win/tclWinTest.c
index 71d76e6..3d713c9 100644
--- a/win/tclWinTest.c
+++ b/win/tclWinTest.c
@@ -8,13 +8,20 @@
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tclWinTest.c,v 1.12 2005/11/04 00:06:51 dkf Exp $
+ * RCS: @(#) $Id: tclWinTest.c,v 1.13 2006/03/14 19:34:33 vincentdarley Exp $
*/
#define USE_COMPAT_CONST
#include "tclInt.h"
/*
+ * For Testplaftorm_chmod on Windows
+ */
+#ifdef __WIN32__
+#include <aclapi.h>
+#endif
+
+/*
* Forward declarations of functions defined later in this file:
*/
@@ -31,6 +38,9 @@ static int TestwinsleepCmd(ClientData dummy, Tcl_Interp* interp,
static Tcl_ObjCmdProc TestExceptionCmd;
static int TestwincpuidCmd(ClientData dummy, Tcl_Interp* interp,
int objc, Tcl_Obj *CONST objv[]);
+static int TestplatformChmod(CONST char *nativePath, int pmode);
+static int TestchmodCmd(ClientData dummy,
+ Tcl_Interp *interp, int argc, CONST char **argv);
/*
*----------------------------------------------------------------------
@@ -57,6 +67,8 @@ TclplatformtestInit(
* Add commands for platform specific tests for Windows here.
*/
+ Tcl_CreateCommand(interp, "testchmod", TestchmodCmd,
+ (ClientData) 0, NULL);
Tcl_CreateCommand(interp, "testeventloop", TesteventloopCmd,
(ClientData) 0, NULL);
Tcl_CreateObjCommand(interp, "testvolumetype", TestvolumetypeCmd,
@@ -485,3 +497,310 @@ TestExceptionCmd(
/* NOTREACHED */
return TCL_OK;
}
+
+static int
+TestplatformChmod(CONST char *nativePath, int pmode)
+{
+ static const char everyoneBuf[] = "EVERYONE";
+ static const SECURITY_INFORMATION infoBits = OWNER_SECURITY_INFORMATION
+ | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION;
+ static const DWORD readOnlyMask = FILE_DELETE_CHILD | FILE_ADD_FILE
+ | FILE_ADD_SUBDIRECTORY | FILE_WRITE_EA | FILE_APPEND_DATA
+ | FILE_WRITE_DATA | DELETE;
+
+ BYTE *secDesc = 0;
+ DWORD secDescLen;
+
+ const BOOL set_readOnly = !(pmode & 0222);
+ BOOL acl_readOnly_found = FALSE;
+
+ ACL_SIZE_INFORMATION ACLSize;
+ BOOL curAclPresent, curAclDefaulted;
+ PACL curAcl;
+ PACL newAcl = 0;
+ DWORD newAclSize;
+
+ WORD j;
+
+ DWORD userSidLen = 4096;
+ SID *userSid = 0;
+ DWORD userDomainLen = 32;
+ TCHAR *userDomain = 0;
+ SID_NAME_USE userSidUse;
+
+ DWORD attr;
+
+ int res = 0;
+
+ /*
+ * One time initialization, dynamically load Windows NT features
+ */
+ typedef DWORD (WINAPI *setNamedSecurityInfoADef)( IN LPSTR,
+ IN SE_OBJECT_TYPE, IN SECURITY_INFORMATION, IN PSID, IN PSID,
+ IN PACL, IN PACL );
+ typedef BOOL (WINAPI *getAceDef) (PACL, DWORD, LPVOID *);
+ typedef BOOL (WINAPI *addAceDef) ( PACL, DWORD, DWORD, LPVOID, DWORD );
+ typedef BOOL (WINAPI *equalSidDef) ( PSID, PSID );
+ typedef BOOL (WINAPI *addAccessDeniedAceDef) ( PACL, DWORD, DWORD, PSID );
+ typedef BOOL (WINAPI *initializeAclDef) ( PACL, DWORD, DWORD );
+ typedef DWORD (WINAPI *getLengthSidDef) ( PSID );
+ typedef BOOL (WINAPI *getAclInformationDef) (PACL, LPVOID, DWORD,
+ ACL_INFORMATION_CLASS );
+ typedef BOOL (WINAPI *getSecurityDescriptorDaclDef) (PSECURITY_DESCRIPTOR,
+ LPBOOL, PACL *, LPBOOL );
+ typedef BOOL (WINAPI *lookupAccountNameADef) ( LPCSTR, LPCSTR, PSID,
+ PDWORD, LPSTR, LPDWORD, PSID_NAME_USE );
+ typedef BOOL (WINAPI *getFileSecurityADef) ( LPCSTR, SECURITY_INFORMATION,
+ PSECURITY_DESCRIPTOR, DWORD, LPDWORD );
+
+ static setNamedSecurityInfoADef setNamedSecurityInfoProc;
+ static getAceDef getAceProc;
+ static addAceDef addAceProc;
+ static equalSidDef equalSidProc;
+ static addAccessDeniedAceDef addAccessDeniedAceProc;
+ static initializeAclDef initializeAclProc;
+ static getLengthSidDef getLengthSidProc;
+ static getAclInformationDef getAclInformationProc;
+ static getSecurityDescriptorDaclDef getSecurityDescriptorDaclProc;
+ static lookupAccountNameADef lookupAccountNameProc;
+ static getFileSecurityADef getFileSecurityProc;
+
+ static int initialized = 0;
+ if (!initialized) {
+ TCL_DECLARE_MUTEX(initialzeMutex)
+ Tcl_MutexLock(&initializeMutex);
+ if (!initialized) {
+ HINSTANCE hInstance = LoadLibrary("Advapi32");
+ if (hInstance != NULL) {
+ setNamedSecurityInfoProc = (setNamedSecurityInfoADef)
+ GetProcAddress(hInstance, "SetNamedSecurityInfoA");
+ getFileSecurityProc = (getFileSecurityADef)
+ GetProcAddress(hInstance, "GetFileSecurityA");
+ getAceProc = (getAceDef)
+ GetProcAddress(hInstance, "GetAce");
+ addAceProc = (addAceDef)
+ GetProcAddress(hInstance, "AddAce");
+ equalSidProc = (equalSidDef)
+ GetProcAddress(hInstance, "EqualSid");
+ addAccessDeniedAceProc = (addAccessDeniedAceDef)
+ GetProcAddress(hInstance, "AddAccessDeniedAce");
+ initializeAclProc = (initializeAclDef)
+ GetProcAddress(hInstance, "InitializeAcl");
+ getLengthSidProc = (getLengthSidDef)
+ GetProcAddress(hInstance, "GetLengthSid");
+ getAclInformationProc = (getAclInformationDef)
+ GetProcAddress(hInstance, "GetAclInformation");
+ getSecurityDescriptorDaclProc = (getSecurityDescriptorDaclDef)
+ GetProcAddress(hInstance, "GetSecurityDescriptorDacl");
+ lookupAccountNameProc = (lookupAccountNameADef)
+ GetProcAddress(hInstance, "LookupAccountNameA");
+ if (setNamedSecurityInfoProc && getAceProc
+ && addAceProc && equalSidProc && addAccessDeniedAceProc
+ && initializeAclProc && getLengthSidProc
+ && getAclInformationProc && getSecurityDescriptorDaclProc
+ && lookupAccountNameProc && getFileSecurityProc)
+ initialized = 1;
+ }
+ if (!initialized)
+ initialized = -1;
+ }
+ Tcl_MutexUnlock(&intializeMutex);
+ }
+
+ /* Process the chmod request */
+ attr = GetFileAttributes(nativePath);
+
+ /* nativePath not found */
+ if (attr == 0xffffffff) {
+ res = -1;
+ goto done;
+ }
+
+ /* If no ACL API is present or nativePath is not a directory,
+ * there is no special handling
+ */
+ if (initialized < 0 || !(attr & FILE_ATTRIBUTE_DIRECTORY)) {
+ goto done;
+ }
+
+ /* Set the result to error, if the ACL change is successful it will
+ * be reset to 0
+ */
+ res = -1;
+
+ /*
+ * Read the security descriptor for the 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;
+ secDesc = (BYTE *) ckalloc(secDescLen);
+ if (!getFileSecurityProc(nativePath, infoBits, secDesc,
+ secDescLen, &secDescLen2)
+ || (secDescLen < secDescLen2)) {
+ goto done;
+ }
+ } else {
+ goto done;
+ }
+ }
+
+ /* Get the "Everyone" SID */
+ userSid = (SID *) ckalloc(userSidLen);
+ userDomain = (TCHAR *) ckalloc(userDomainLen);
+ if (!lookupAccountNameProc(NULL, everyoneBuf, userSid, &userSidLen,
+ userDomain, &userDomainLen, &userSidUse)) {
+ if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+ ckfree((char *)userSid);
+ userSid = (SID *) ckalloc(userSidLen);
+ ckfree(userDomain);
+ userDomain = (TCHAR *) ckalloc(userDomainLen);
+ if (!lookupAccountNameProc(NULL, everyoneBuf, userSid,
+ &userSidLen, userDomain, &userDomainLen, &userSidUse))
+ goto done;
+ } else
+ goto done;
+ }
+
+ /* If curAclPresent == false then curAcl and curAclDefaulted not valid */
+ if (!getSecurityDescriptorDaclProc(secDesc, &curAclPresent,
+ &curAcl, &curAclDefaulted))
+ goto done;
+
+ if (!curAclPresent || !curAcl) {
+ ACLSize.AclBytesInUse = 0;
+ ACLSize.AceCount = 0;
+ } else if (!getAclInformationProc(curAcl, &ACLSize, sizeof(ACLSize),
+ AclSizeInformation))
+ goto done;
+
+ /* Allocate memory for the new ACL */
+ newAclSize = ACLSize.AclBytesInUse + sizeof (ACCESS_DENIED_ACE)
+ + getLengthSidProc(userSid) - sizeof (DWORD);
+ newAcl = (ACL *) ckalloc (newAclSize);
+
+ /* Initialize the new ACL */
+ if(!initializeAclProc(newAcl, newAclSize, ACL_REVISION)) {
+ goto done;
+ }
+
+ /* Add denied to make readonly, this will be known as a "read-only tag" */
+ if (set_readOnly && !addAccessDeniedAceProc(newAcl, ACL_REVISION,
+ readOnlyMask, userSid)) {
+ goto done;
+ }
+
+ acl_readOnly_found = FALSE;
+ for (j = 0; j < ACLSize.AceCount; j++) {
+ PACL *pACE2;
+ ACE_HEADER *phACE2;
+ if (! getAceProc (curAcl, j, (LPVOID*) &pACE2)) {
+ goto done;
+ }
+
+ phACE2 = ((ACE_HEADER *) pACE2);
+
+ /* Do NOT propagate inherited ACEs */
+ if (phACE2->AceFlags & INHERITED_ACE) {
+ continue;
+ }
+
+ /* Skip the "read-only tag" restriction (either added above, or it
+ * is being removed)
+ */
+ if (phACE2->AceType == ACCESS_DENIED_ACE_TYPE) {
+ ACCESS_DENIED_ACE *pACEd = (ACCESS_DENIED_ACE *)phACE2;
+ if (pACEd->Mask == readOnlyMask && equalSidProc(userSid,
+ (PSID)&(pACEd->SidStart))) {
+ acl_readOnly_found = TRUE;
+ continue;
+ }
+ }
+
+ /* Copy the current ACE from the old to the new ACL */
+ if(! addAceProc (newAcl, ACL_REVISION, MAXDWORD, pACE2,
+ ((PACE_HEADER) pACE2)->AceSize)) {
+ goto done;
+ }
+ }
+
+ /* Apply the new ACL */
+ if (set_readOnly == acl_readOnly_found
+ || setNamedSecurityInfoProc((LPSTR)nativePath, SE_FILE_OBJECT,
+ DACL_SECURITY_INFORMATION, NULL, NULL, newAcl, NULL)
+ == ERROR_SUCCESS ) {
+ res = 0;
+ }
+
+ done:
+ if (secDesc) ckfree(secDesc);
+ if (newAcl) ckfree((char *)newAcl);
+ if (userSid) ckfree((char *)userSid);
+ if (userDomain) ckfree(userDomain);
+
+ if (res != 0)
+ return res;
+
+ /* Run normal chmod command */
+ return chmod(nativePath, pmode);
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * TestchmodCmd --
+ *
+ * Implements the "testchmod" cmd. Used when testing "file" command.
+ * The only attribute used by the Windows platform is the user write
+ * flag; if this is not set, the file is made read-only. Otehrwise, the
+ * file is made read-write.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * Changes permissions of specified files.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+static int
+TestchmodCmd(dummy, interp, argc, argv)
+ ClientData dummy; /* Not used. */
+ Tcl_Interp *interp; /* Current interpreter. */
+ int argc; /* Number of arguments. */
+ CONST char **argv; /* Argument strings. */
+{
+ int i, mode;
+ char *rest;
+
+ if (argc < 2) {
+ usage:
+ Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+ " mode file ?file ...?", NULL);
+ return TCL_ERROR;
+ }
+
+ mode = (int) strtol(argv[1], &rest, 8);
+ if ((rest == argv[1]) || (*rest != '\0')) {
+ goto usage;
+ }
+
+ for (i = 2; i < argc; i++) {
+ Tcl_DString buffer;
+ CONST char *translated;
+
+ translated = Tcl_TranslateFileName(interp, argv[i], &buffer);
+ if (translated == NULL) {
+ return TCL_ERROR;
+ }
+ if (TestplatformChmod(translated, (unsigned) mode) != 0) {
+ Tcl_AppendResult(interp, translated, ": ", Tcl_PosixError(interp),
+ NULL);
+ return TCL_ERROR;
+ }
+ Tcl_DStringFree(&buffer);
+ }
+ return TCL_OK;
+}