summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjan.nijtmans <nijtmans@users.sourceforge.net>2023-03-07 20:14:23 (GMT)
committerjan.nijtmans <nijtmans@users.sourceforge.net>2023-03-07 20:14:23 (GMT)
commit8dbd350305c20a179b0d97b9d69d099956dc5729 (patch)
treee340a80ee45b28f112e226debbf7aa13a1143082
parent1ad16ea481af865cd4e72467fc045502837c2fc6 (diff)
parent0e0d74624b670cf813951e61b3025182d9d0398b (diff)
downloadtcl-8dbd350305c20a179b0d97b9d69d099956dc5729.zip
tcl-8dbd350305c20a179b0d97b9d69d099956dc5729.tar.gz
tcl-8dbd350305c20a179b0d97b9d69d099956dc5729.tar.bz2
Merge 8.7
-rw-r--r--generic/tclDecls.h4
-rw-r--r--generic/tclStringObj.c1
-rw-r--r--generic/tclTest.c70
-rw-r--r--generic/tclZlib.c29
-rw-r--r--tests/basic.test2
-rw-r--r--tests/fCmd.test84
-rw-r--r--tests/fileSystem.test10
-rw-r--r--tests/stringObj.test6
-rw-r--r--tests/tcltest.test2
-rw-r--r--tests/winFCmd.test114
-rw-r--r--tests/winTime.test5
-rw-r--r--tools/valgrind_suppress137
-rw-r--r--unix/Makefile.in20
-rw-r--r--unix/tclUnixSock.c8
-rw-r--r--win/tclWin32Dll.c4
-rw-r--r--win/tclWinChan.c6
-rw-r--r--win/tclWinFile.c68
-rw-r--r--win/tclWinPanic.c4
-rw-r--r--win/tclWinTest.c308
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);
+
}
/*