diff options
| author | jan.nijtmans <nijtmans@users.sourceforge.net> | 2023-03-07 20:14:23 (GMT) |
|---|---|---|
| committer | jan.nijtmans <nijtmans@users.sourceforge.net> | 2023-03-07 20:14:23 (GMT) |
| commit | 8dbd350305c20a179b0d97b9d69d099956dc5729 (patch) | |
| tree | e340a80ee45b28f112e226debbf7aa13a1143082 | |
| parent | 1ad16ea481af865cd4e72467fc045502837c2fc6 (diff) | |
| parent | 0e0d74624b670cf813951e61b3025182d9d0398b (diff) | |
| download | tcl-8dbd350305c20a179b0d97b9d69d099956dc5729.zip tcl-8dbd350305c20a179b0d97b9d69d099956dc5729.tar.gz tcl-8dbd350305c20a179b0d97b9d69d099956dc5729.tar.bz2 | |
Merge 8.7
| -rw-r--r-- | generic/tclDecls.h | 4 | ||||
| -rw-r--r-- | generic/tclStringObj.c | 1 | ||||
| -rw-r--r-- | generic/tclTest.c | 70 | ||||
| -rw-r--r-- | generic/tclZlib.c | 29 | ||||
| -rw-r--r-- | tests/basic.test | 2 | ||||
| -rw-r--r-- | tests/fCmd.test | 84 | ||||
| -rw-r--r-- | tests/fileSystem.test | 10 | ||||
| -rw-r--r-- | tests/stringObj.test | 6 | ||||
| -rw-r--r-- | tests/tcltest.test | 2 | ||||
| -rw-r--r-- | tests/winFCmd.test | 114 | ||||
| -rw-r--r-- | tests/winTime.test | 5 | ||||
| -rw-r--r-- | tools/valgrind_suppress | 137 | ||||
| -rw-r--r-- | unix/Makefile.in | 20 | ||||
| -rw-r--r-- | unix/tclUnixSock.c | 8 | ||||
| -rw-r--r-- | win/tclWin32Dll.c | 4 | ||||
| -rw-r--r-- | win/tclWinChan.c | 6 | ||||
| -rw-r--r-- | win/tclWinFile.c | 68 | ||||
| -rw-r--r-- | win/tclWinPanic.c | 4 | ||||
| -rw-r--r-- | win/tclWinTest.c | 308 |
19 files changed, 588 insertions, 294 deletions
diff --git a/generic/tclDecls.h b/generic/tclDecls.h index adad630..bee2ae2 100644 --- a/generic/tclDecls.h +++ b/generic/tclDecls.h @@ -4321,14 +4321,14 @@ extern const TclStubs *tclStubsPtr; /* Handle Win64 tk.dll being loaded in Cygwin64. */ # define Tcl_GetTime(t) \ do { \ - union { \ + struct { \ Tcl_Time now; \ long long reserved; \ } _t; \ _t.reserved = -1; \ tclStubsPtr->tcl_GetTime((&_t.now)); \ if (_t.reserved != -1) { \ - _t.now.usec = _t.reserved; \ + _t.now.usec = (long) _t.reserved; \ } \ *(t) = _t.now; \ } while (0) diff --git a/generic/tclStringObj.c b/generic/tclStringObj.c index 723d2e5..328e410 100644 --- a/generic/tclStringObj.c +++ b/generic/tclStringObj.c @@ -4849,6 +4849,7 @@ ExtendStringRepWithUnicode( copyBytes: dst = objPtr->bytes + origLength; + *dst = '\0'; for (i = 0; i < numChars; i++) { dst += Tcl_UniCharToUtf(unicode[i], dst); } diff --git a/generic/tclTest.c b/generic/tclTest.c index f4450ff..b3df8ec 100644 --- a/generic/tclTest.c +++ b/generic/tclTest.c @@ -70,6 +70,7 @@ static Tcl_Interp *delInterp; typedef struct TestCommandTokenRef { int id; /* Identifier for this reference. */ Tcl_Command token; /* Tcl's token for the command. */ + const char *value; struct TestCommandTokenRef *nextPtr; /* Next in list of references. */ } TestCommandTokenRef; @@ -1179,6 +1180,18 @@ TestcmdinfoCmd( } static int +CmdProc0( + void *clientData, /* String to return. */ + Tcl_Interp *interp, /* Current interpreter. */ + TCL_UNUSED(int) /*argc*/, + TCL_UNUSED(const char **) /*argv*/) +{ + TestCommandTokenRef *refPtr = (TestCommandTokenRef *) clientData; + Tcl_AppendResult(interp, "CmdProc1 ", refPtr->value, NULL); + return TCL_OK; +} + +static int CmdProc1( void *clientData, /* String to return. */ Tcl_Interp *interp, /* Current interpreter. */ @@ -1189,6 +1202,7 @@ CmdProc1( return TCL_OK; } + static int CmdProc2( void *clientData, /* String to return. */ @@ -1201,6 +1215,28 @@ CmdProc2( } static void +CmdDelProc0( + void *clientData) /* String to save. */ +{ + TestCommandTokenRef *thisRefPtr, *prevRefPtr = NULL; + TestCommandTokenRef *refPtr = (TestCommandTokenRef *) clientData; + int id = refPtr->id; + for (thisRefPtr = firstCommandTokenRef; refPtr != NULL; + thisRefPtr = thisRefPtr->nextPtr) { + if (thisRefPtr->id == id) { + if (prevRefPtr != NULL) { + prevRefPtr->nextPtr = thisRefPtr->nextPtr; + } else { + firstCommandTokenRef = thisRefPtr->nextPtr; + } + break; + } + prevRefPtr = thisRefPtr; + } + ckfree(refPtr); +} + +static void CmdDelProc1( void *clientData) /* String to save. */ { @@ -1253,17 +1289,16 @@ TestcmdtokenCmd( } if (strcmp(argv[1], "create") == 0) { refPtr = (TestCommandTokenRef *)Tcl_Alloc(sizeof(TestCommandTokenRef)); - refPtr->token = Tcl_CreateCommand(interp, argv[2], CmdProc1, - (void *) "original", NULL); + refPtr->token = Tcl_CreateCommand(interp, argv[2], CmdProc0, + refPtr, CmdDelProc0); refPtr->id = nextCommandTokenRefId; + refPtr->value = "original"; nextCommandTokenRefId++; refPtr->nextPtr = firstCommandTokenRef; firstCommandTokenRef = refPtr; sprintf(buf, "%d", refPtr->id); Tcl_AppendResult(interp, buf, NULL); - } else if (strcmp(argv[1], "name") == 0) { - Tcl_Obj *objPtr; - + } else { if (sscanf(argv[2], "%d", &id) != 1) { Tcl_AppendResult(interp, "bad command token \"", argv[2], "\"", NULL); @@ -1283,18 +1318,23 @@ TestcmdtokenCmd( return TCL_ERROR; } - objPtr = Tcl_NewObj(); - Tcl_GetCommandFullName(interp, refPtr->token, objPtr); + if (strcmp(argv[1], "name") == 0) { + Tcl_Obj *objPtr; - Tcl_AppendElement(interp, - Tcl_GetCommandName(interp, refPtr->token)); - Tcl_AppendElement(interp, Tcl_GetString(objPtr)); - Tcl_DecrRefCount(objPtr); - } else { - Tcl_AppendResult(interp, "bad option \"", argv[1], - "\": must be create or name", NULL); - return TCL_ERROR; + objPtr = Tcl_NewObj(); + Tcl_GetCommandFullName(interp, refPtr->token, objPtr); + + Tcl_AppendElement(interp, + Tcl_GetCommandName(interp, refPtr->token)); + Tcl_AppendElement(interp, Tcl_GetString(objPtr)); + Tcl_DecrRefCount(objPtr); + } else { + Tcl_AppendResult(interp, "bad option \"", argv[1], + "\": must be create, name, or free", NULL); + return TCL_ERROR; + } } + return TCL_OK; } diff --git a/generic/tclZlib.c b/generic/tclZlib.c index ae5615c..3182c27 100644 --- a/generic/tclZlib.c +++ b/generic/tclZlib.c @@ -449,12 +449,16 @@ GenerateHeader( headerPtr->nativeCommentBuf, MAX_COMMENT_LEN-1, NULL, &len, NULL); if (result != TCL_OK) { - if (result == TCL_CONVERT_UNKNOWN) { - Tcl_AppendResult(interp, "Comment contains characters > 0xFF", NULL); - } else { - Tcl_AppendResult(interp, "Comment too large for zip", NULL); + if (interp) { + if (result == TCL_CONVERT_UNKNOWN) { + Tcl_AppendResult( + interp, "Comment contains characters > 0xFF", NULL); + } + else { + Tcl_AppendResult(interp, "Comment too large for zip", NULL); + } } - result = TCL_ERROR; + result = TCL_ERROR; /* TCL_CONVERT_* -> TCL_ERROR*/ goto error; } headerPtr->nativeCommentBuf[len] = '\0'; @@ -481,12 +485,17 @@ GenerateHeader( headerPtr->nativeFilenameBuf, MAXPATHLEN-1, NULL, &len, NULL); if (result != TCL_OK) { - if (result == TCL_CONVERT_UNKNOWN) { - Tcl_AppendResult(interp, "Filename contains characters > 0xFF", NULL); - } else { - Tcl_AppendResult(interp, "Filename too large for zip", NULL); + if (interp) { + if (result == TCL_CONVERT_UNKNOWN) { + Tcl_AppendResult( + interp, "Filename contains characters > 0xFF", NULL); + } + else { + Tcl_AppendResult( + interp, "Filename too large for zip", NULL); + } } - result = TCL_ERROR; + result = TCL_ERROR; /* TCL_CONVERT_* -> TCL_ERROR*/ goto error; } headerPtr->nativeFilenameBuf[len] = '\0'; diff --git a/tests/basic.test b/tests/basic.test index f4c57fe..c90d80e 100644 --- a/tests/basic.test +++ b/tests/basic.test @@ -348,7 +348,7 @@ test basic-20.2 {Tcl_GetCommandInfo, names for commands created outside namespac test basic-20.3 {Tcl_GetCommandInfo, #-quoting} testcmdtoken { catch {rename \# ""} set x [testcmdtoken create \#] - testcmdtoken name $x + return [testcmdtoken name $x] } {{#} ::#} test basic-21.1 {Tcl_GetCommandName} {emptyTest} { diff --git a/tests/fCmd.test b/tests/fCmd.test index 13f4cf1..4c8e55c 100644 --- a/tests/fCmd.test +++ b/tests/fCmd.test @@ -27,7 +27,7 @@ testConstraint winLessThan10 0 testConstraint notNetworkFilesystem 0 testConstraint reg 0 if {[testConstraint win]} { - catch { + if {[catch { # Is the registry extension already static to this shell? try { load {} Registry @@ -38,8 +38,11 @@ if {[testConstraint win]} { load $::reglib Registry } testConstraint reg 1 + } regError]} { + catch {package require registry; testConstraint reg 1} } } + testConstraint notInCIenv [expr {![info exists ::env(CI)] || !$::env(CI)}] # File permissions broken on wsl without some "exotic" wsl configuration @@ -100,6 +103,45 @@ if {[testConstraint unix]} { } } +# Try getting a lower case glob pattern that will match the home directory of +# a given user to test ~user and [file tildeexpand ~user]. Note this may not +# be the same as ~ even when "user" is current user. For example, on Unix +# platforms ~ will return HOME envvar, but ~user will lookup password file +# bypassing HOME. If home directory not found, returns *$user* so caller can +# succeed by using glob matching under the hope that the path contains +# the user name. +proc gethomedirglob {user} { + if {[testConstraint unix]} { + if {![catch { + exec {*}[auto_execok sh] -c "echo ~$user" + } home]} { + set home [string trim $home] + if {$home ne ""} { + # Expect exact match (except case), no glob * added + return [string tolower $home] + } + } + } elseif {[testConstraint reg]} { + # Windows with registry extension loaded + if {![catch { + set sid [exec {*}[auto_execok powershell] -Command "(Get-LocalUser -Name '$user')\[0\].sid.Value"] + set sid [string trim $sid] + # Get path from the Windows registry + set home [registry get "HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\$sid" ProfileImagePath] + set home [string trim $home] + } result]} { + if {$home ne ""} { + # file join for \ -> / + return [file join [string tolower $home]] + } + } + } + + # Caller will need to use glob matching and hope user + # name is in the home directory path + return *$user* +} + proc createfile {file {string a}} { set f [open $file w] puts -nonewline $f $string @@ -1023,6 +1065,7 @@ test fCmd-10.3.1 {file copy: comprehensive: dir to new name} -setup { file mkdir [file join td1 tdx] file mkdir [file join td2 tdy] testchmod 0o555 td2 + testchmod 0o555 td2/tdy; # Above line removes inherited perms. So restore. file copy td1 td3 file copy td2 td4 list [lsort [glob td*]] [glob -directory td3 t*] \ @@ -1044,10 +1087,19 @@ test fCmd-10.4 {file copy: comprehensive: file to existing file} -setup { createfile tfd2 createfile tfd3 createfile tfd4 - testchmod 0o444 tfs3 - testchmod 0o444 tfs4 - testchmod 0o444 tfd2 - testchmod 0o444 tfd4 + if {$::tcl_platform(platform) eq "windows"} { + # On Windows testchmode will attach an ACL which file copy cannot handle + # so use good old attributes which file copy does understand + file attribute tfs3 -readonly 1 + file attribute tfs4 -readonly 1 + file attribute tfd2 -readonly 1 + file attribute tfd4 -readonly 1 + } else { + testchmod 0o444 tfs3 + testchmod 0o444 tfs4 + testchmod 0o444 tfd2 + testchmod 0o444 tfd4 + } set msg [list [catch {file copy tf1 tf2} msg] $msg] file copy -force tfs1 tfd1 file copy -force tfs2 tfd2 @@ -2602,13 +2654,20 @@ test fCmd-31.6 {file home USER} -body { # env(HOME) even when user is current user. Assume result contains user # name, else not sure how to check string tolower [file home $::tcl_platform(user)] -} -match glob -result [string tolower "*$::tcl_platform(user)*"] +} -match glob -result [gethomedirglob $::tcl_platform(user)] test fCmd-31.7 {file home UNKNOWNUSER} -body { file home nosuchuser } -returnCodes error -result {user "nosuchuser" doesn't exist} test fCmd-31.8 {file home extra arg} -body { file home $::tcl_platform(user) arg } -returnCodes error -result {wrong # args: should be "file home ?user?"} +test fCmd-31.9 {file home USER does not follow env(HOME)} -setup { + set ::env(HOME) [file join $::env(HOME) foo] +} -cleanup { + set ::env(HOME) [file dirname $::env(HOME)] +} -body { + string tolower [file home $::tcl_platform(user)] +} -match glob -result [gethomedirglob $::tcl_platform(user)] test fCmd-32.1 {file tildeexpand ~} -body { file tildeexpand ~ @@ -2644,7 +2703,7 @@ test fCmd-32.5 {file tildeexpand ~USER} -body { # env(HOME) even when user is current user. Assume result contains user # name, else not sure how to check string tolower [file tildeexpand ~$::tcl_platform(user)] -} -match glob -result [string tolower "*$::tcl_platform(user)*"] +} -match glob -result [gethomedirglob $::tcl_platform(user)] test fCmd-32.6 {file tildeexpand ~UNKNOWNUSER} -body { file tildeexpand ~nosuchuser } -returnCodes error -result {user "nosuchuser" doesn't exist} @@ -2659,7 +2718,7 @@ test fCmd-32.9 {file tildeexpand ~USER/bar} -body { # env(HOME) even when user is current user. Assume result contains user # name, else not sure how to check string tolower [file tildeexpand ~$::tcl_platform(user)/bar] -} -match glob -result [string tolower "*$::tcl_platform(user)*/bar"] +} -match glob -result [file join [gethomedirglob $::tcl_platform(user)] bar] test fCmd-32.10 {file tildeexpand ~UNKNOWNUSER} -body { file tildeexpand ~nosuchuser/foo } -returnCodes error -result {user "nosuchuser" doesn't exist} @@ -2683,7 +2742,14 @@ test fCmd-32.16 {file tildeexpand ~USER\\bar} -body { # env(HOME) even when user is current user. Assume result contains user # name, else not sure how to check string tolower [file tildeexpand ~$::tcl_platform(user)\\bar] -} -constraints win -match glob -result [string tolower "*$::tcl_platform(user)*/bar"] +} -constraints win -match glob -result [file join [gethomedirglob $::tcl_platform(user)] bar] +test fCmd-32.17 {file tildeexpand ~USER does not mirror HOME} -setup { + set ::env(HOME) [file join $::env(HOME) foo] +} -cleanup { + set ::env(HOME) [file dirname $::env(HOME)] +} -body { + string tolower [file tildeexpand ~$::tcl_platform(user)] +} -match glob -result [gethomedirglob $::tcl_platform(user)] # cleanup diff --git a/tests/fileSystem.test b/tests/fileSystem.test index 2bbf981..0b6fa1d 100644 --- a/tests/fileSystem.test +++ b/tests/fileSystem.test @@ -276,6 +276,16 @@ test filesystem-1.30.1 {normalisation of existing user} -body { test filesystem-1.30.2 {normalisation of nonexistent user specified as user@domain} -body { file normalize ~nonexistentuser@nonexistentdomain } -returnCodes error -result {user "nonexistentuser@nonexistentdomain" doesn't exist} +test filesystem-1.30.3 {file normalization should distinguish between ~ and ~user} -setup { + set oldhome $::env(HOME) + set olduserhome [file normalize ~$::tcl_platform(user)] + set ::env(HOME) [file join $oldhome temp] +} -cleanup { + set env(HOME) $oldhome +} -body { + list [string equal [file normalize ~] $::env(HOME)] \ + [string equal $olduserhome [file normalize ~$::tcl_platform(user)]] +} -result {1 1} test filesystem-1.31 {link normalisation: link near filesystem root} {testsetplatform} { testsetplatform unix file normalize /foo/../bar diff --git a/tests/stringObj.test b/tests/stringObj.test index 2fd4369..5c1d040 100644 --- a/tests/stringObj.test +++ b/tests/stringObj.test @@ -69,8 +69,8 @@ test stringObj-4.2 {Tcl_SetObjLength procedure, string gets longer} {testobj dep testobj freeallvars teststringobj set 1 abcdef teststringobj setlength 1 10 - list [teststringobj length 1] [teststringobj length2 1] -} {10 10} + teststringobj length 1 +} 10 test stringObj-4.3 {Tcl_SetObjLength procedure, string gets longer} {testobj utf32 deprecated} { testobj freeallvars teststringobj set 1 abcdef @@ -78,7 +78,7 @@ test stringObj-4.3 {Tcl_SetObjLength procedure, string gets longer} {testobj utf list [teststringobj length 1] [teststringobj length2 1] \ [teststringobj get 1] } {10 10 abcdefxyzq} -test stringObj-4.4 {Tcl_SetObjLength procedure, "expty string", length 0} {testobj deprecated} { +test stringObj-4.4 {Tcl_SetObjLength procedure, "empty string", length 0} {testobj deprecated} { testobj freeallvars testobj newobj 1 teststringobj setlength 1 0 diff --git a/tests/tcltest.test b/tests/tcltest.test index 49f31d5..29d40e2 100644 --- a/tests/tcltest.test +++ b/tests/tcltest.test @@ -555,7 +555,7 @@ switch -- $::tcl_platform(platform) { default { # note in FAT/NTFS we won't be able to protect directory with read-only attribute... catch {file attributes $notWriteableDir -readonly 1} - catch {testchmod 0 $notWriteableDir} + catch {testchmod 0o444 $notWriteableDir} } } test tcltest-8.3 {tcltest a.tcl -tmpdir notReadableDir} { diff --git a/tests/winFCmd.test b/tests/winFCmd.test index 43c7ced..5ebce10 100644 --- a/tests/winFCmd.test +++ b/tests/winFCmd.test @@ -45,15 +45,20 @@ proc contents {file} { set r } +proc cleanupRecurse {args} { + # Assumes no loops via links! + # Need to change permissions BEFORE deletion + testchmod 0o777 {*}$args + foreach victim $args { + if {[file isdirectory $victim]} { + cleanupRecurse {*}[glob -nocomplain -directory $victim td* tf* Test*] + } + file delete -force $victim + } +} proc cleanup {args} { - foreach p ". $args" { - set x "" - catch { - set x [glob -directory $p tf* td*] - } - if {$x != ""} { - catch {file delete -force -- {*}$x} - } + foreach p [list [pwd] {*}$args] { + cleanupRecurse {*}[glob -nocomplain -directory $p tf* td*] } } @@ -379,12 +384,12 @@ test winFCmd-1.38 {TclpRenameFile: check rename of conflicting inodes} -setup { cleanup } -constraints {win winNonZeroInodes knownMsvcBug notInCIenv} -body { file mkdir td1 - foreach {a b} [MakeFiles td1] break + lassign [MakeFiles td1] a b file rename -force $a $b file exists $a } -cleanup { cleanup -} -result {0} +} -result 0 test winFCmd-2.1 {TclpCopyFile: errno: EACCES} -setup { @@ -450,11 +455,11 @@ test winFCmd-2.12 {TclpCopyFile: CopyFile succeeds} -setup { cleanup } -constraints {win testfile} -body { createfile tf1 tf1 - testchmod 0 tf1 + file attribute tf1 -readonly 1 testfile cp tf1 tf2 list [contents tf2] [file writable tf2] } -cleanup { - catch {testchmod 0o666 tf1} + testchmod 0o660 tf1 cleanup } -result {tf1 0} test winFCmd-2.13 {TclpCopyFile: CopyFile fails} -setup { @@ -496,11 +501,10 @@ test winFCmd-2.17 {TclpCopyFile: dst is readonly} -setup { } -constraints {win testfile testchmod} -body { createfile tf1 tf1 createfile tf2 tf2 - testchmod 0 tf2 + file attribute tf2 -readonly 1 testfile cp tf1 tf2 list [file writable tf2] [contents tf2] } -cleanup { - catch {testchmod 0o666 tf2} cleanup } -result {1 tf1} @@ -578,7 +582,6 @@ test winFCmd-3.11 {TclpDeleteFile: still can't remove path} -setup { testfile rm tf1 } -cleanup { close $fd - catch {testchmod 0o666 tf1} cleanup } -returnCodes error -result EACCES @@ -617,15 +620,18 @@ test winFCmd-5.1 {TclpCopyDirectory: calls TraverseWinTree} -setup { test winFCmd-6.1 {TclpRemoveDirectory: errno: EACCES} -setup { cleanup -} -constraints {win testfile testchmod knownMsvcBug notInCIenv} -body { - file mkdir td1 - testchmod 0 td1 - testfile rmdir td1 - file exists td1 +} -constraints {win testfile testchmod notInCIenv} -body { + # Parent's FILE_DELETE_CHILD setting permits deletion of subdir + # even when subdir DELETE mask is clear. So we need an intermediate + # parent td0 with FILE_DELETE_CHILD turned off while allowing R/W. + file mkdir td0/td1 + testchmod 0o777 td0 + testchmod 0 td0/td1 + testfile rmdir td0/td1 + file exists td0/td1 } -returnCodes error -cleanup { - catch {testchmod 0o666 td1} cleanup -} -result {td1 EACCES} +} -result {td0/td1 EACCES} # This next test has a very hokey way of matching... test winFCmd-6.2 {TclpRemoveDirectory: errno: EEXIST} -setup { cleanup @@ -633,7 +639,7 @@ test winFCmd-6.2 {TclpRemoveDirectory: errno: EEXIST} -setup { file mkdir td1/td2 list [catch {testfile rmdir td1} msg] [file tail $msg] } -result {1 {td1 EEXIST}} -test winFCmd-6.3 {TclpRemoveDirectory: errno: EACCES} {win emptyTest} { +test winFCmd-6.3 {TclpRemoveDirectory: errno: EACCES} {win emptyTest trashSystem} { # can't test this w/o removing everything on your hard disk first! # testfile rmdir / } {} @@ -669,17 +675,7 @@ test winFCmd-6.8 {TclpRemoveDirectory: RemoveDirectory fails} -setup { createfile tf1 list [catch {testfile rmdir tf1} msg] [file tail $msg] } -result {1 {tf1 ENOTDIR}} -test winFCmd-6.9 {TclpRemoveDirectory: errno == EACCES} -setup { - cleanup -} -constraints {win testfile testchmod knownMsvcBug notInCIenv} -body { - file mkdir td1 - testchmod 0 td1 - testfile rmdir td1 - file exists td1 -} -returnCodes error -cleanup { - catch {testchmod 0o666 td1} - cleanup -} -result {td1 EACCES} +# winFCmd-6.9 removed - was exact dup of winFCmd-6.1 test winFCmd-6.11 {TclpRemoveDirectory: attr == -1} -setup { cleanup } -constraints {win testfile notInCIenv} -body { @@ -689,15 +685,19 @@ test winFCmd-6.11 {TclpRemoveDirectory: attr == -1} -setup { } -returnCodes error -match regexp -result {^/ E(ACCES|EXIST)$} test winFCmd-6.13 {TclpRemoveDirectory: write-protected} -setup { cleanup -} -constraints {win testfile testchmod knownMsvcBug notInCIenv} -body { - file mkdir td1 - testchmod 0 td1 - testfile rmdir td1 - file exists td1 +} -constraints {win testfile testchmod notInCIenv} -body { + # Parent's FILE_DELETE_CHILD setting permits deletion of subdir + # even when subdir DELETE mask is clear. So we need an intermediate + # parent td0 with FILE_DELETE_CHILD turned off while allowing R/W. + file mkdir td0/td1 + testchmod 0o770 td0 + testchmod 0o444 td0/td1 + testfile rmdir td0/td1 + file exists td0/td1 } -cleanup { - catch {testchmod 0o666 td1} + testchmod 0o770 td0/td1 cleanup -} -returnCodes error -result {td1 EACCES} +} -returnCodes error -result {td0/td1 EACCES} # This next test has a very hokey way of matching... test winFCmd-6.15 {TclpRemoveDirectory: !recursive} -setup { cleanup @@ -791,11 +791,12 @@ test winFCmd-7.11 {TraverseWinTree: call TraversalCopy: DOTREE_PRED} -setup { } -constraints {win testfile testchmod} -body { file mkdir td1 createfile td1/tf1 tf1 - testchmod 0 td1 + testchmod 0o770 td1/tf1; # Else tf2 will have no ACL after td1 testchmod + testchmod 0o400 td1 testfile cpdir td1 td2 list [file exists td2] [file writable td2] } -cleanup { - catch {testchmod 0o666 td1} + testchmod 0o660 td1 cleanup } -result {1 1} test winFCmd-7.12 {TraverseWinTree: call TraversalDelete: DOTREE_PRED} -setup { @@ -862,11 +863,12 @@ test winFCmd-7.19 {TraverseWinTree: call TraversalCopy: DOTREE_POSTD} -setup { } -constraints {win testfile testchmod} -body { file mkdir td1 createfile td1/tf1 tf1 - testchmod 0 td1 + testchmod 0o770 td1/tf1; # Else tf2 will have no ACL after td1 testchmod + testchmod 0o400 td1 testfile cpdir td1 td2 list [file exists td2] [file writable td2] } -cleanup { - catch {testchmod 0o666 td1} + testchmod 0o660 td1 cleanup } -result {1 1} test winFCmd-7.20 {TraverseWinTree: call TraversalDelete: DOTREE_POSTD} -setup { @@ -893,11 +895,12 @@ test winFCmd-8.2 {TraversalCopy: DOTREE_PRED} -setup { cleanup } -constraints {win testfile testchmod} -body { file mkdir td1/td2 - testchmod 0 td1 + testchmod 0o770 td1/td2; # Else td2 will have no ACL after td1 testchmod + testchmod 0o400 td1 testfile cpdir td1 td2 list [file writable td1] [file writable td1/td2] } -cleanup { - catch {testchmod 0o666 td1} + testchmod 0o660 td1 cleanup } -result {0 1} test winFCmd-8.3 {TraversalCopy: DOTREE_POSTD} -setup { @@ -918,15 +921,19 @@ test winFCmd-9.1 {TraversalDelete: DOTREE_F} -setup { } -result {} test winFCmd-9.3 {TraversalDelete: DOTREE_PRED} -setup { cleanup -} -constraints {win testfile testchmod knownMsvcBug notInCIenv} -body { - file mkdir td1/td2 - testchmod 0 td1 - testfile rmdir -force td1 +} -constraints {win testfile testchmod notInCIenv} -body { + # Parent's FILE_DELETE_CHILD setting permits deletion of subdir + # even when subdir DELETE mask is clear. So we need an intermediate + # parent td0 with FILE_DELETE_CHILD turned off while allowing R/W. + file mkdir td0/td1/td2 + testchmod 0o770 td0 + testchmod 0o400 td0/td1 + testfile rmdir -force td0/td1 file exists td1 } -cleanup { - catch {testchmod 0o666 td1} + testchmod 0o770 td0/td1 cleanup -} -returnCodes error -result {td1 EACCES} +} -returnCodes error -result {td0/td1 EACCES} test winFCmd-9.4 {TraversalDelete: DOTREE_POSTD} -setup { cleanup } -constraints {win testfile} -body { @@ -1417,7 +1424,6 @@ test winFCmd-19.9 {Windows devices path names} -constraints win -body { # } #} -# cleanup cleanup ::tcltest::cleanupTests return diff --git a/tests/winTime.test b/tests/winTime.test index ae1797d..0d7298f 100644 --- a/tests/winTime.test +++ b/tests/winTime.test @@ -19,9 +19,6 @@ if {"::tcltest" ni [namespace children]} { catch [list package require -exact tcl::test [info patchlevel]] testConstraint testwinclock [llength [info commands testwinclock]] -# Some things fail under all Continuous Integration systems for subtle reasons -# such as CI often running with elevated privileges in a container. -testConstraint notInCIenv [expr {![info exists ::env(CI)]}] # The next two tests will crash on Windows if the check for negative # clock values is not done properly. @@ -43,7 +40,7 @@ test winTime-1.2 {TclpGetDate} {win} { # with the Windows clock. 30 sec really isn't enough, # but how much time does a tester have patience for? -test winTime-2.1 {Synchronization of Tcl and Windows clocks} {testwinclock notInCIenv} { +test winTime-2.1 {Synchronization of Tcl and Windows clocks} testwinclock { # May fail due to OS/hardware discrepancies. See: # http://support.microsoft.com/default.aspx?scid=kb;en-us;274323 set failed {} diff --git a/tools/valgrind_suppress b/tools/valgrind_suppress index fb7f173..11ca880 100644 --- a/tools/valgrind_suppress +++ b/tools/valgrind_suppress @@ -1,3 +1,17 @@ +#{ +# Tcl_GetChannelOption/TcpGetOptionProc/TcphostPortList/getnameinfo/gethostbyaddr_r +# Memcheck:Leak +# match-leak-kinds: reachable +# fun:malloc +# fun:strdup +# ... +# fun:module_load +# ... +# fun:getnameinfo +# ... +# fun:Tcl_GetChannelOption +#} + { TclCreatesocketAddress/getaddrinfo/calloc Memcheck:Leak @@ -11,6 +25,16 @@ { TclCreatesocketAddress/getaddrinfo/malloc Memcheck:Leak + match-leak-kinds: definite + fun:malloc + ... + fun:getaddrinfo + fun:TclCreateSocketAddress +} + +{ + TclCreatesocketAddress/getaddrinfo/malloc + Memcheck:Leak match-leak-kinds: reachable fun:malloc ... @@ -19,6 +43,18 @@ } { + TclpDlopen/decompose_rpath + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:decompose_rpath + ... + fun:dlopen_doit + ... + fun:TclpDlopen +} + +{ TclpDlopen/load Memcheck:Leak match-leak-kinds: reachable @@ -72,6 +108,46 @@ } { + TclpGeHostByName/gethostbyname_r/strdup/malloc + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:strdup + ... + fun:dl_open_worker + ... + fun:do_dlopen + ... + fun:TclpGetHostByName +} + +{ + TclpGeHostByName/gethostbyname_r/calloc + Memcheck:Leak + match-leak-kinds: reachable + fun:calloc + ... + fun:dl_open_worker + ... + fun:do_dlopen + ... + fun:TclpGetHostByName +} + +{ + TclpGeHostByName/gethostbyname_r/malloc + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:dl_open_worker + ... + fun:do_dlopen + ... + fun:TclpGetHostByName +} + +{ TclpGetPwNam/getpwname_r/__nss_next2/calloc Memcheck:Leak match-leak-kinds: reachable @@ -105,6 +181,57 @@ } { + TclpGetGrGid/getgrgid_r/module_load + Memcheck:Leak + match-leak-kinds: reachable + fun:calloc + ... + fun:module_load + ... + fun:TclpGetGrGid +} + +{ + TclpGetGrGid/getgrgid_r/module_load + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:module_load + ... + fun:TclpGetGrGid +} + +{ + TcphostPortList/getnameinfo/module_load/calloc + Memcheck:Leak + match-leak-kinds: definite,reachable + fun:calloc + ... + fun:dl_open_worker_begin + ... + fun:module_load + ... + fun:getnameinfo + fun:TcpHostPortList +} + +{ + # see sourceware glibc Bug 14984 - getnameinfo() might be leaking memory + TcphostPortList/getnameinfo/module_load/mallco + Memcheck:Leak + match-leak-kinds: definite,reachable + fun:malloc + ... + fun:dl_open_worker_begin + ... + fun:module_load + ... + fun:getnameinfo + fun:TcpHostPortList +} + +{ TclpThreadExit/pthread_exit/calloc Memcheck:Leak match-leak-kinds: reachable @@ -124,3 +251,13 @@ fun:TclpThreadExit } +{ + TclpThreadExit/pthread_exit/malloc + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + ... + fun:pthread_exit + fun:TclpThreadExit +} + diff --git a/unix/Makefile.in b/unix/Makefile.in index dcaf6e3..1b2718e 100644 --- a/unix/Makefile.in +++ b/unix/Makefile.in @@ -952,6 +952,26 @@ valgrind: ${TCL_EXE} ${TCLTEST_EXE} $(TOP_DIR)/tests/all.tcl -singleproc 1 -constraints valgrind \ $(TESTFLAGS) +testresults/valgrind/%.result: ${TCL_EXE} ${TCLTEST_EXE} + @mkdir -p testresults/valgrind + $(SHELL_ENV) $(VALGRIND) $(VALGRINDARGS) ./${TCLTEST_EXE} \ + $(TOP_DIR)/tests/all.tcl -singleproc 1 -constraints valgrind \ + -file $(basename $(notdir $@)) > $@ 2>&1 +.PRECIOUS: testresults/valgrind/%.result + + +testresults/valgrind/%.success: testresults/valgrind/%.result + @printf '%s' valgrind >&2 + @printf ' %s' $(basename $(notdir $@)) >&2 + @printf '\n >&2' + @status=$$(./${TCLTEST_EXE} $(TOP_DIR)/tools/valgrind_check_success \ + file $(basename $@).result); \ + if [ "$$status" -eq 1 ]; then exit 0; else exit 1; fi + +valgrind_each: $(addprefix testresults/valgrind/,$(addsuffix .success,$(notdir\ + $(wildcard $(TOP_DIR)/tests/*.test)))) + + valgrindshell: ${TCL_EXE} $(SHELL_ENV) $(VALGRIND) $(VALGRINDARGS) ./${TCL_EXE} $(SCRIPT) diff --git a/unix/tclUnixSock.c b/unix/tclUnixSock.c index 70dfc61..0be10ad 100644 --- a/unix/tclUnixSock.c +++ b/unix/tclUnixSock.c @@ -1033,10 +1033,10 @@ TcpGetOptionProc( if ((len == 0) || ((len > 1) && (optionName[1] == 'k') && (strncmp(optionName, "-keepalive", len) == 0))) { + int opt = 0; #if defined(SO_KEEPALIVE) - socklen_t size; + socklen_t size = sizeof(opt); #endif - int opt = 0; if (len == 0) { Tcl_DStringAppendElement(dsPtr, "-keepalive"); @@ -1053,10 +1053,10 @@ TcpGetOptionProc( if ((len == 0) || ((len > 1) && (optionName[1] == 'n') && (strncmp(optionName, "-nodelay", len) == 0))) { + int opt = 0; #if defined(SOL_TCP) && defined(TCP_NODELAY) - socklen_t size; + socklen_t size = sizeof(opt); #endif - int opt = 0; if (len == 0) { Tcl_DStringAppendElement(dsPtr, "-nodelay"); diff --git a/win/tclWin32Dll.c b/win/tclWin32Dll.c index 0e86611..7c3d8a4 100644 --- a/win/tclWin32Dll.c +++ b/win/tclWin32Dll.c @@ -379,7 +379,7 @@ TclWinDriveLetterForVolMountPoint( if (!alreadyStored) { dlPtr2 = (MountPointMap *)ckalloc(sizeof(MountPointMap)); dlPtr2->volumeName = (WCHAR *)TclNativeDupInternalRep(Target); - dlPtr2->driveLetter = (char) drive[0]; + dlPtr2->driveLetter = (WCHAR) drive[0]; dlPtr2->nextPtr = driveLetterLookup; driveLetterLookup = dlPtr2; } @@ -405,7 +405,7 @@ TclWinDriveLetterForVolMountPoint( dlPtr2 = (MountPointMap *)ckalloc(sizeof(MountPointMap)); dlPtr2->volumeName = (WCHAR *)TclNativeDupInternalRep((void *)mountPoint); - dlPtr2->driveLetter = -1; + dlPtr2->driveLetter = (WCHAR)-1; dlPtr2->nextPtr = driveLetterLookup; driveLetterLookup = dlPtr2; Tcl_MutexUnlock(&mountPointMap); diff --git a/win/tclWinChan.c b/win/tclWinChan.c index 166636c..f0ee718 100644 --- a/win/tclWinChan.c +++ b/win/tclWinChan.c @@ -700,7 +700,7 @@ FileInputProc( if (ReadFile(infoPtr->handle, (LPVOID) buf, (DWORD) bufSize, &bytesRead, (LPOVERLAPPED) NULL) != FALSE) { - return bytesRead; + return (int)bytesRead; } Tcl_WinConvertError(GetLastError()); @@ -757,7 +757,7 @@ FileOutputProc( return -1; } infoPtr->dirty = 1; - return bytesWritten; + return (int)bytesWritten; } /* @@ -1572,7 +1572,7 @@ NativeIsComPort( const WCHAR *nativePath) /* Path of file to access, native encoding. */ { const WCHAR *p = (const WCHAR *) nativePath; - int i, len = wcslen(p); + size_t i, len = wcslen(p); /* * 1. Look for com[1-9]:? diff --git a/win/tclWinFile.c b/win/tclWinFile.c index 7e0a763..3e5cff5 100644 --- a/win/tclWinFile.c +++ b/win/tclWinFile.c @@ -1457,21 +1457,39 @@ TclpGetUserHome( if (domain == NULL) { const char *ptr; - /* - * No domain. Firstly check it's the current user - */ - + /* + * Treat the current user as a special case because the general case + * below does not properly retrieve the path. The NetUserGetInfo + * call returns an empty path and the code defaults to the user's + * name in the profiles directory. On modern Windows systems, this + * is generally wrong as when the account is a Microsoft account, + * for example abcdefghi@outlook.com, the directory name is + * abcde and not abcdefghi. + * + * Note we could have just used env(USERPROFILE) here but + * the intent is to retrieve (as on Unix) the system's view + * of the home irrespective of environment settings of HOME + * and USERPROFILE. + * + * Fixing this for the general user needs more investigating but + * at least for the current user we can use a direct call. + */ ptr = TclpGetUserName(&ds); if (ptr != NULL && strcasecmp(name, ptr) == 0) { - /* - * Try safest and fastest way to get current user home - */ - - ptr = TclGetEnv("HOME", &ds); - if (ptr != NULL) { - Tcl_JoinPath(1, &ptr, bufferPtr); - rc = 1; - result = Tcl_DStringValue(bufferPtr); + HANDLE hProcess; + WCHAR buf[MAX_PATH]; + DWORD nChars = sizeof(buf) / sizeof(buf[0]); + /* Sadly GetCurrentProcessToken not in Win 7 so slightly longer */ + hProcess = GetCurrentProcess(); /* Need not be closed */ + if (hProcess) { + HANDLE hToken; + if (OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) { + if (GetUserProfileDirectoryW(hToken, buf, &nChars)) { + result = Tcl_WCharToUtfDString(buf, nChars-1, (bufferPtr)); + rc = 1; + } + CloseHandle(hToken); + } } } Tcl_DStringFree(&ds); @@ -1543,30 +1561,6 @@ TclpGetUserHome( if (wDomain != NULL) { NetApiBufferFree((void *) wDomain); } - if (result == NULL) { - /* - * Look in the "Password Lists" section of system.ini for the local - * user. There are also entries in that section that begin with a "*" - * character that are used by Windows for other purposes; ignore user - * names beginning with a "*". - */ - - char buf[MAX_PATH]; - - if (name[0] != '*') { - if (GetPrivateProfileStringA("Password Lists", name, "", buf, - MAX_PATH, "system.ini") > 0) { - /* - * User exists, but there is no such thing as a home directory - * in system.ini. Return "{Windows drive}:/". - */ - - GetWindowsDirectoryA(buf, MAX_PATH); - Tcl_DStringAppend(bufferPtr, buf, 3); - result = Tcl_DStringValue(bufferPtr); - } - } - } return result; } diff --git a/win/tclWinPanic.c b/win/tclWinPanic.c index 364673e..3131286 100644 --- a/win/tclWinPanic.c +++ b/win/tclWinPanic.c @@ -56,10 +56,10 @@ Tcl_ConsolePanic( if (IsDebuggerPresent()) { OutputDebugStringW(msgString); } else if (_isatty(2)) { - WriteConsoleW(handle, msgString, wcslen(msgString), &dummy, 0); + WriteConsoleW(handle, msgString, (DWORD)wcslen(msgString), &dummy, 0); } else { buf[0] = '\xEF'; buf[1] = '\xBB'; buf[2] = '\xBF'; /* UTF-8 bom */ - WriteFile(handle, buf, strlen(buf), &dummy, 0); + WriteFile(handle, buf, (DWORD)strlen(buf), &dummy, 0); WriteFile(handle, "\n", 1, &dummy, 0); FlushFileBuffers(handle); } diff --git a/win/tclWinTest.c b/win/tclWinTest.c index c910bc5..0f65268 100644 --- a/win/tclWinTest.c +++ b/win/tclWinTest.c @@ -22,9 +22,8 @@ /* * For TestplatformChmod on Windows */ -#ifdef _WIN32 #include <aclapi.h> -#endif +#include <sddl.h> /* * MinGW 3.4.2 does not define this. @@ -414,176 +413,189 @@ TestExceptionCmd( return TCL_OK; } +/* + * This "chmod" works sufficiently for test script purposes. Do not expect + * it to be exact emulation of Unix chmod (not sure if that's even possible) + */ static int TestplatformChmod( const char *nativePath, int pmode) { - static const SECURITY_INFORMATION infoBits = OWNER_SECURITY_INFORMATION - | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION; - /* don't reset change permissions mask (WRITE_DAC, allow test-cases restore it to cleanup) */ - static const DWORD readOnlyMask = FILE_DELETE_CHILD | FILE_ADD_FILE - | FILE_ADD_SUBDIRECTORY | FILE_WRITE_EA | FILE_APPEND_DATA - | FILE_WRITE_DATA - | DELETE; - /* - * References to security functions (only available on NT and later). + * Note FILE_DELETE_CHILD missing from dirWriteMask because we do + * not want overriding of child's delete setting when testing */ - - const BOOL set_readOnly = !(pmode & 0222); - BOOL acl_readOnly_found = FALSE, curAclPresent, curAclDefaulted; - SID_IDENTIFIER_AUTHORITY userSidAuthority = { - SECURITY_WORLD_SID_AUTHORITY - }; - BYTE *secDesc = 0; - DWORD secDescLen, attr, newAclSize; - ACL_SIZE_INFORMATION ACLSize; - PACL curAcl, newAcl = 0; - WORD j; - SID *userSid = 0; - char *userDomain = 0; + static const DWORD dirWriteMask = + FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | + FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY | STANDARD_RIGHTS_WRITE | DELETE | + SYNCHRONIZE; + static const DWORD dirReadMask = + FILE_READ_ATTRIBUTES | FILE_READ_EA | FILE_LIST_DIRECTORY | + STANDARD_RIGHTS_READ | SYNCHRONIZE; + /* Note - default user privileges allow ignoring TRAVERSE setting */ + static const DWORD dirExecuteMask = + FILE_TRAVERSE | STANDARD_RIGHTS_READ | SYNCHRONIZE; + + static const DWORD fileWriteMask = + FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_WRITE_DATA | + FILE_APPEND_DATA | STANDARD_RIGHTS_WRITE | DELETE | SYNCHRONIZE; + static const DWORD fileReadMask = + FILE_READ_ATTRIBUTES | FILE_READ_EA | FILE_READ_DATA | + STANDARD_RIGHTS_READ | SYNCHRONIZE; + static const DWORD fileExecuteMask = + FILE_EXECUTE | STANDARD_RIGHTS_READ | SYNCHRONIZE; + + DWORD attr, newAclSize; + PACL newAcl = NULL; int res = 0; - /* - * Process the chmod request. - */ + HANDLE hToken = NULL; + int i; + int nSids = 0; + struct { + PSID pSid; + DWORD mask; + DWORD sidLen; + } aceEntry[3]; + DWORD dw; + int isDir; + TOKEN_USER *pTokenUser = NULL; - attr = GetFileAttributesA(nativePath); - - /* - * nativePath not found - */ + res = -1; /* Assume failure */ + attr = GetFileAttributesA(nativePath); if (attr == 0xFFFFFFFF) { - res = -1; - goto done; + goto done; /* Not found */ } - /* - * If nativePath is not a directory, there is no special handling. - */ + isDir = (attr & FILE_ATTRIBUTE_DIRECTORY) != 0; - if (!(attr & FILE_ATTRIBUTE_DIRECTORY)) { + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) { 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 (!GetFileSecurityA(nativePath, infoBits, NULL, 0, &secDescLen)) { - DWORD secDescLen2 = 0; - - if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { - goto done; - } - - secDesc = (BYTE *)ckalloc(secDescLen); - if (!GetFileSecurityA(nativePath, infoBits, - (PSECURITY_DESCRIPTOR) secDesc, secDescLen, &secDescLen2) - || (secDescLen < secDescLen2)) { - goto done; - } - } - - /* - * Get the World SID. - */ - - userSid = (SID *)ckalloc(GetSidLengthRequired((UCHAR) 1)); - InitializeSid(userSid, &userSidAuthority, (BYTE) 1); - *(GetSidSubAuthority(userSid, 0)) = SECURITY_WORLD_RID; - - /* - * If curAclPresent == false then curAcl and curAclDefaulted not valid. - */ - - if (!GetSecurityDescriptorDacl((PSECURITY_DESCRIPTOR) secDesc, - &curAclPresent, &curAcl, &curAclDefaulted)) { + /* Get process SID */ + if (!GetTokenInformation(hToken, TokenUser, NULL, 0, &dw) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) { goto done; } - if (!curAclPresent || !curAcl) { - ACLSize.AclBytesInUse = 0; - ACLSize.AceCount = 0; - } else if (!GetAclInformation(curAcl, &ACLSize, sizeof(ACLSize), - AclSizeInformation)) { + pTokenUser = (TOKEN_USER *)ckalloc(dw); + if (!GetTokenInformation(hToken, TokenUser, pTokenUser, dw, &dw)) { goto done; } - - /* - * Allocate memory for the new ACL. - */ - - newAclSize = ACLSize.AclBytesInUse + sizeof(ACCESS_DENIED_ACE) - + GetLengthSid(userSid) - sizeof(DWORD); - newAcl = (PACL) ckalloc(newAclSize); - - /* - * Initialize the new ACL. - */ - - if (!InitializeAcl(newAcl, newAclSize, ACL_REVISION)) { + aceEntry[nSids].sidLen = GetLengthSid(pTokenUser->User.Sid); + aceEntry[nSids].pSid = ckalloc(aceEntry[nSids].sidLen); + if (!CopySid(aceEntry[nSids].sidLen, + aceEntry[nSids].pSid, + pTokenUser->User.Sid)) { + ckfree(aceEntry[nSids].pSid); /* Since we have not ++'ed nSids */ goto done; } - /* - * Add denied to make readonly, this will be known as a "read-only tag". + * Always include DACL modify rights so we don't get locked out */ - - if (set_readOnly && !AddAccessDeniedAce(newAcl, ACL_REVISION, - readOnlyMask, userSid)) { - goto done; + aceEntry[nSids].mask = READ_CONTROL | WRITE_DAC | WRITE_OWNER | SYNCHRONIZE | + FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES; + if (pmode & 0700) { + /* Owner permissions. Assumes current process is owner */ + if (pmode & 0400) { + aceEntry[nSids].mask |= isDir ? dirReadMask : fileReadMask; + } + if (pmode & 0200) { + aceEntry[nSids].mask |= isDir ? dirWriteMask : fileWriteMask; + } + if (pmode & 0100) { + aceEntry[nSids].mask |= isDir ? dirExecuteMask : fileExecuteMask; + } } + ++nSids; - acl_readOnly_found = FALSE; - for (j = 0; j < ACLSize.AceCount; j++) { - LPVOID pACE2; - ACE_HEADER *phACE2; + if (pmode & 0070) { + /* Group permissions. */ - if (!GetAce(curAcl, j, &pACE2)) { + TOKEN_PRIMARY_GROUP *pTokenGroup; + + /* Get primary group SID */ + if (!GetTokenInformation( + hToken, TokenPrimaryGroup, NULL, 0, &dw) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) { goto done; } + pTokenGroup = (TOKEN_PRIMARY_GROUP *)ckalloc(dw); + if (!GetTokenInformation(hToken, TokenPrimaryGroup, pTokenGroup, dw, &dw)) { + ckfree(pTokenGroup); + goto done; + } + aceEntry[nSids].sidLen = GetLengthSid(pTokenGroup->PrimaryGroup); + aceEntry[nSids].pSid = ckalloc(aceEntry[nSids].sidLen); + if (!CopySid(aceEntry[nSids].sidLen, aceEntry[nSids].pSid, pTokenGroup->PrimaryGroup)) { + ckfree(pTokenGroup); + ckfree(aceEntry[nSids].pSid); /* Since we have not ++'ed nSids */ + goto done; + } + ckfree(pTokenGroup); - phACE2 = (ACE_HEADER *) pACE2; - - /* - * Do NOT propagate inherited ACEs. - */ + /* Generate mask for group ACL */ - if (phACE2->AceFlags & INHERITED_ACE) { - continue; + aceEntry[nSids].mask = 0; + if (pmode & 0040) { + aceEntry[nSids].mask |= isDir ? dirReadMask : fileReadMask; } + if (pmode & 0020) { + aceEntry[nSids].mask |= isDir ? dirWriteMask : fileWriteMask; + } + if (pmode & 0010) { + aceEntry[nSids].mask |= isDir ? dirExecuteMask : fileExecuteMask; + } + ++nSids; + } - /* - * Skip the "read-only tag" restriction (either added above, or it is - * being removed). - */ + if (pmode & 0007) { + /* World permissions */ + PSID pWorldSid; + if (!ConvertStringSidToSidA("S-1-1-0", &pWorldSid)) { + goto done; + } + aceEntry[nSids].sidLen = GetLengthSid(pWorldSid); + aceEntry[nSids].pSid = ckalloc(aceEntry[nSids].sidLen); + if (!CopySid(aceEntry[nSids].sidLen, aceEntry[nSids].pSid, pWorldSid)) { + LocalFree(pWorldSid); + ckfree(aceEntry[nSids].pSid); /* Since we have not ++'ed nSids */ + goto done; + } + LocalFree(pWorldSid); - if (phACE2->AceType == ACCESS_DENIED_ACE_TYPE) { - ACCESS_DENIED_ACE *pACEd = (ACCESS_DENIED_ACE *) phACE2; + /* Generate mask for world ACL */ - if (pACEd->Mask == readOnlyMask - && EqualSid(userSid, (PSID) &pACEd->SidStart)) { - acl_readOnly_found = TRUE; - continue; - } + aceEntry[nSids].mask = 0; + if (pmode & 0004) { + aceEntry[nSids].mask |= isDir ? dirReadMask : fileReadMask; + } + if (pmode & 0002) { + aceEntry[nSids].mask |= isDir ? dirWriteMask : fileWriteMask; } + if (pmode & 0001) { + aceEntry[nSids].mask |= isDir ? dirExecuteMask : fileExecuteMask; + } + ++nSids; + } - /* - * Copy the current ACE from the old to the new ACL. - */ + /* Allocate memory and initialize the new ACL. */ - if (!AddAce(newAcl, ACL_REVISION, MAXDWORD, (PACL *) pACE2, - ((PACE_HEADER) pACE2)->AceSize)) { + newAclSize = sizeof(ACL); + /* Add in size required for each ACE entry in the ACL */ + for (i = 0; i < nSids; ++i) { + newAclSize += + offsetof(ACCESS_ALLOWED_ACE, SidStart) + aceEntry[i].sidLen; + } + newAcl = (PACL)ckalloc(newAclSize); + if (!InitializeAcl(newAcl, newAclSize, ACL_REVISION)) { + goto done; + } + + for (i = 0; i < nSids; ++i) { + if (!AddAccessAllowedAce(newAcl, ACL_REVISION, aceEntry[i].mask, aceEntry[i].pSid)) { goto done; } } @@ -593,36 +605,38 @@ TestplatformChmod( * to remove inherited ACL (we need to overwrite the default ACL's in this case) */ - if (set_readOnly == acl_readOnly_found || SetNamedSecurityInfoA( - (LPSTR) nativePath, SE_FILE_OBJECT, - DACL_SECURITY_INFORMATION /*| PROTECTED_DACL_SECURITY_INFORMATION*/, - NULL, NULL, newAcl, NULL) == ERROR_SUCCESS) { + if (SetNamedSecurityInfoA((LPSTR)nativePath, + SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION | + PROTECTED_DACL_SECURITY_INFORMATION, + NULL, + NULL, + newAcl, + NULL) == ERROR_SUCCESS) { res = 0; } done: - if (secDesc) { - ckfree(secDesc); + if (pTokenUser) { + ckfree(pTokenUser); + } + if (hToken) { + CloseHandle(hToken); } if (newAcl) { ckfree(newAcl); } - if (userSid) { - ckfree(userSid); - } - if (userDomain) { - ckfree(userDomain); + for (i = 0; i < nSids; ++i) { + ckfree(aceEntry[i].pSid); } if (res != 0) { return res; } - /* - * Run normal chmod command. - */ - + /* Run normal chmod command */ return chmod(nativePath, pmode); + } /* |
