summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/file.n11
-rw-r--r--generic/tclBasic.c21
-rw-r--r--generic/tclCmdAH.c36
-rw-r--r--generic/tclCmdIL.c3
-rw-r--r--generic/tclCmdMZ.c12
-rw-r--r--generic/tclDictObj.c18
-rw-r--r--generic/tclEnsemble.c51
-rw-r--r--generic/tclIOUtil.c3
-rw-r--r--generic/tclInt.h4
-rw-r--r--generic/tclOO.c9
-rw-r--r--generic/tclOOCall.c9
-rw-r--r--generic/tclOOMethod.c6
-rw-r--r--generic/tclPathObj.c10
-rw-r--r--generic/tclTest.c3
-rw-r--r--tests/cmdAH.test85
-rw-r--r--tests/fileName.test14
-rwxr-xr-xwin/tclWinFile.c113
-rw-r--r--win/tclWinInt.h1
-rw-r--r--win/tclWinPort.h14
19 files changed, 288 insertions, 135 deletions
diff --git a/doc/file.n b/doc/file.n
index 8e765da..58b03d8 100644
--- a/doc/file.n
+++ b/doc/file.n
@@ -162,7 +162,9 @@ returns \fB/home\fR (or something similar).
\fBfile executable \fIname\fR
.
Returns \fB1\fR if file \fIname\fR is executable by the current user,
-\fB0\fR otherwise.
+\fB0\fR otherwise. On Windows, which does not have an executable attribute,
+the command treats all directories and any files with extensions
+\fBexe\fR, \fBcom\fR, \fBcmd\fR, \fBbat\fR or \fBps1\fR as executable.
.TP
\fBfile exists \fIname\fR
.
@@ -482,10 +484,9 @@ not the effective ones.
.TP
\fBWindows\fR\0\0\0\0
.
-The \fBfile owned\fR subcommand currently always reports that the current user
-is the owner of the file, without regard for what the operating system
-believes to be true, making an ownership test useless. This issue (#3613671)
-may be fixed in a future release of Tcl.
+The \fBfile owned\fR subcommand uses the user identifier (SID) of
+the process token, not the thread token which may be impersonating
+some other user.
.SH EXAMPLES
.PP
This procedure shows how to search for C files in a given directory
diff --git a/generic/tclBasic.c b/generic/tclBasic.c
index b0c31cc..d6a460d 100644
--- a/generic/tclBasic.c
+++ b/generic/tclBasic.c
@@ -128,7 +128,7 @@ static void MathFuncWrongNumArgs(Tcl_Interp *interp, int expected,
int actual, Tcl_Obj *const *objv);
static Tcl_NRPostProc NRCoroutineCallerCallback;
static Tcl_NRPostProc NRCoroutineExitCallback;
-static int NRCommand(ClientData data[], Tcl_Interp *interp, int result);
+static Tcl_NRPostProc NRCommand;
static Tcl_ObjCmdProc OldMathFuncProc;
static void OldMathFuncDeleteProc(ClientData clientData);
@@ -146,7 +146,6 @@ static int TEOV_RunEnterTraces(Tcl_Interp *interp,
Command **cmdPtrPtr, Tcl_Obj *commandPtr, int objc,
Tcl_Obj *const objv[]);
static Tcl_NRPostProc RewindCoroutineCallback;
-static Tcl_NRPostProc TailcallCleanup;
static Tcl_NRPostProc TEOEx_ByteCodeCallback;
static Tcl_NRPostProc TEOEx_ListCallback;
static Tcl_NRPostProc TEOV_Error;
@@ -8371,7 +8370,7 @@ TclNRTailcallEval(
* a now-gone namespace: cleanup and return.
*/
- TailcallCleanup(data, interp, result);
+ Tcl_DecrRefCount(listPtr);
return result;
}
@@ -8380,18 +8379,26 @@ TclNRTailcallEval(
*/
TclMarkTailcall(interp);
- TclNRAddCallback(interp, TailcallCleanup, listPtr, NULL, NULL,NULL);
+ TclNRAddCallback(interp, TclNRReleaseValues, listPtr, NULL, NULL,NULL);
iPtr->lookupNsPtr = (Namespace *) nsPtr;
return TclNREvalObjv(interp, objc-1, objv+1, 0, NULL);
}
-static int
-TailcallCleanup(
+int
+TclNRReleaseValues(
ClientData data[],
Tcl_Interp *interp,
int result)
{
- Tcl_DecrRefCount((Tcl_Obj *) data[0]);
+ int i = 0;
+ while (i < 4) {
+ if (data[i]) {
+ Tcl_DecrRefCount((Tcl_Obj *) data[i]);
+ } else {
+ break;
+ }
+ i++;
+ }
return result;
}
diff --git a/generic/tclCmdAH.c b/generic/tclCmdAH.c
index 54e0227..13d3df5 100644
--- a/generic/tclCmdAH.c
+++ b/generic/tclCmdAH.c
@@ -12,6 +12,9 @@
*/
#include "tclInt.h"
+#ifdef _WIN32
+# include "tclWinInt.h"
+#endif
#include <locale.h>
/*
@@ -1157,6 +1160,16 @@ FileAttrAccessTimeCmd(
if (GetStatBuf(interp, objv[1], Tcl_FSStat, &buf) != TCL_OK) {
return TCL_ERROR;
}
+#if defined(_WIN32)
+ /* We use a value of 0 to indicate the access time not available */
+ if (buf.st_atime == 0) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "could not get access time for file \"%s\"",
+ TclGetString(objv[1])));
+ return TCL_ERROR;
+ }
+#endif
+
if (objc == 3) {
/*
* Need separate variable for reading longs from an object on 64-bit
@@ -1229,6 +1242,15 @@ FileAttrModifyTimeCmd(
if (GetStatBuf(interp, objv[1], Tcl_FSStat, &buf) != TCL_OK) {
return TCL_ERROR;
}
+#if defined(_WIN32)
+ /* We use a value of 0 to indicate the modification time not available */
+ if (buf.st_mtime == 0) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "could not get modification time for file \"%s\"",
+ TclGetString(objv[1])));
+ return TCL_ERROR;
+ }
+#endif
if (objc == 3) {
/*
* Need separate variable for reading longs from an object on 64-bit
@@ -1581,21 +1603,13 @@ FileAttrIsOwnedCmd(
Tcl_WrongNumArgs(interp, 1, objv, "name");
return TCL_ERROR;
}
- if (GetStatBuf(NULL, objv[1], Tcl_FSStat, &buf) == TCL_OK) {
- /*
- * For Windows, there are no user ids associated with a file, so we
- * always return 1.
- *
- * TODO: use GetSecurityInfo to get the real owner of the file and
- * test for equivalence to the current user.
- */
-
#if defined(_WIN32) || defined(__CYGWIN__)
- value = 1;
+ value = TclWinFileOwned(objv[1]);
#else
+ if (GetStatBuf(NULL, objv[1], Tcl_FSStat, &buf) == TCL_OK) {
value = (geteuid() == buf.st_uid);
-#endif
}
+#endif
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(value));
return TCL_OK;
}
diff --git a/generic/tclCmdIL.c b/generic/tclCmdIL.c
index c93e593..0a1b4fe 100644
--- a/generic/tclCmdIL.c
+++ b/generic/tclCmdIL.c
@@ -105,8 +105,7 @@ typedef struct SortInfo {
*/
static int DictionaryCompare(const char *left, const char *right);
-static int IfConditionCallback(ClientData data[],
- Tcl_Interp *interp, int result);
+static Tcl_NRPostProc IfConditionCallback;
static int InfoArgsCmd(ClientData dummy, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
static int InfoBodyCmd(ClientData dummy, Tcl_Interp *interp,
diff --git a/generic/tclCmdMZ.c b/generic/tclCmdMZ.c
index 13f9e7d..885a0bc 100644
--- a/generic/tclCmdMZ.c
+++ b/generic/tclCmdMZ.c
@@ -22,14 +22,10 @@
static inline Tcl_Obj * During(Tcl_Interp *interp, int resultCode,
Tcl_Obj *oldOptions, Tcl_Obj *errorInfo);
-static int SwitchPostProc(ClientData data[], Tcl_Interp *interp,
- int result);
-static int TryPostBody(ClientData data[], Tcl_Interp *interp,
- int result);
-static int TryPostFinal(ClientData data[], Tcl_Interp *interp,
- int result);
-static int TryPostHandler(ClientData data[], Tcl_Interp *interp,
- int result);
+static Tcl_NRPostProc SwitchPostProc;
+static Tcl_NRPostProc TryPostBody;
+static Tcl_NRPostProc TryPostFinal;
+static Tcl_NRPostProc TryPostHandler;
static int UniCharIsAscii(int character);
static int UniCharIsHexDigit(int character);
diff --git a/generic/tclDictObj.c b/generic/tclDictObj.c
index c8474e6..428173d 100644
--- a/generic/tclDictObj.c
+++ b/generic/tclDictObj.c
@@ -70,18 +70,12 @@ static inline void DeleteChainTable(struct Dict *dict);
static inline Tcl_HashEntry *CreateChainEntry(struct Dict *dict,
Tcl_Obj *keyPtr, int *newPtr);
static inline int DeleteChainEntry(struct Dict *dict, Tcl_Obj *keyPtr);
-static int FinalizeDictUpdate(ClientData data[],
- Tcl_Interp *interp, int result);
-static int FinalizeDictWith(ClientData data[],
- Tcl_Interp *interp, int result);
-static int DictForNRCmd(ClientData dummy, Tcl_Interp *interp,
- int objc, Tcl_Obj *const *objv);
-static int DictMapNRCmd(ClientData dummy, Tcl_Interp *interp,
- int objc, Tcl_Obj *const *objv);
-static int DictForLoopCallback(ClientData data[],
- Tcl_Interp *interp, int result);
-static int DictMapLoopCallback(ClientData data[],
- Tcl_Interp *interp, int result);
+static Tcl_NRPostProc FinalizeDictUpdate;
+static Tcl_NRPostProc FinalizeDictWith;
+static Tcl_ObjCmdProc DictForNRCmd;
+static Tcl_ObjCmdProc DictMapNRCmd;
+static Tcl_NRPostProc DictForLoopCallback;
+static Tcl_NRPostProc DictMapLoopCallback;
/*
* Table of dict subcommand names and implementations.
diff --git a/generic/tclEnsemble.c b/generic/tclEnsemble.c
index 818534e..8e5e410 100644
--- a/generic/tclEnsemble.c
+++ b/generic/tclEnsemble.c
@@ -41,7 +41,6 @@ static int CompileBasicNArgCommand(Tcl_Interp *interp,
Tcl_Parse *parsePtr, Command *cmdPtr,
CompileEnv *envPtr);
-static Tcl_NRPostProc FreeObj;
static Tcl_NRPostProc FreeER;
/*
@@ -96,7 +95,7 @@ typedef struct {
int epoch; /* Used to confirm when the data in this
* really structure matches up with the
* ensemble. */
- Tcl_Command token; /* Reference to the comamnd for which this
+ Command *token; /* Reference to the command for which this
* structure is a cache of the resolution. */
Tcl_Obj *fix; /* Corrected spelling, if needed. */
Tcl_HashEntry *hPtr; /* Direct link to entry in the subcommand
@@ -1723,7 +1722,7 @@ NsEnsembleImplementationCmdNR(
EnsembleCmdRep *ensembleCmd = subObj->internalRep.twoPtrValue.ptr1;
if (ensembleCmd->epoch == ensemblePtr->epoch &&
- ensembleCmd->token == ensemblePtr->token) {
+ ensembleCmd->token == (Command *)ensemblePtr->token) {
prefixObj = Tcl_GetHashValue(ensembleCmd->hPtr);
Tcl_IncrRefCount(prefixObj);
if (ensembleCmd->fix) {
@@ -1848,31 +1847,24 @@ NsEnsembleImplementationCmdNR(
{
Tcl_Obj *copyPtr; /* The actual list of words to dispatch to.
* Will be freed by the dispatch engine. */
- int prefixObjc;
+ Tcl_Obj **copyObjv;
+ int copyObjc, prefixObjc;
Tcl_ListObjLength(NULL, prefixObj, &prefixObjc);
- if (0 && objc == 2) {
- /*
- * Branch disabled until it works. See oo-1[67].1.1
- *
- * Key here is the difference between the canonical list invocation
- * and compilation/execution paths.
- */
- copyPtr = prefixObj;
- Tcl_IncrRefCount(copyPtr);
- TclNRAddCallback(interp, FreeObj, copyPtr, NULL, NULL, NULL);
+ if (objc == 2) {
+ copyPtr = TclListObjCopy(NULL, prefixObj);
} else {
- int copyObjc = objc - 2 + prefixObjc;
-
- copyPtr = Tcl_NewListObj(copyObjc, NULL);
+ copyPtr = Tcl_NewListObj(objc - 2 + prefixObjc, NULL);
Tcl_ListObjAppendList(NULL, copyPtr, prefixObj);
Tcl_ListObjReplace(NULL, copyPtr, LIST_MAX, 0,
- ensemblePtr->numParameters, objv+1);
+ ensemblePtr->numParameters, objv + 1);
Tcl_ListObjReplace(NULL, copyPtr, LIST_MAX, 0,
objc - 2 - ensemblePtr->numParameters,
objv + 2 + ensemblePtr->numParameters);
}
+ Tcl_IncrRefCount(copyPtr);
+ TclNRAddCallback(interp, TclNRReleaseValues, copyPtr, NULL, NULL, NULL);
TclDecrRefCount(prefixObj);
/*
@@ -1892,7 +1884,8 @@ NsEnsembleImplementationCmdNR(
*/
TclSkipTailcall(interp);
- return TclNREvalObjEx(interp, copyPtr, TCL_EVAL_INVOKE, NULL,INT_MIN);
+ Tcl_ListObjGetElements(NULL, copyPtr, &copyObjc, &copyObjv);
+ return TclNREvalObjv(interp, copyObjc, copyObjv, TCL_EVAL_INVOKE, NULL);
}
unknownOrAmbiguousSubcommand:
@@ -2070,18 +2063,6 @@ FreeER(
return result;
}
-static int
-FreeObj(
- ClientData data[],
- Tcl_Interp *interp,
- int result)
-{
- Tcl_Obj *objPtr = (Tcl_Obj *)data[0];
-
- Tcl_DecrRefCount(objPtr);
- return result;
-}
-
void
TclSpellFix(
Tcl_Interp *interp,
@@ -2157,7 +2138,7 @@ TclSpellFix(
store[idx] = fix;
Tcl_IncrRefCount(fix);
- TclNRAddCallback(interp, FreeObj, fix, NULL, NULL, NULL);
+ TclNRAddCallback(interp, TclNRReleaseValues, fix, NULL, NULL, NULL);
}
/*
@@ -2367,6 +2348,7 @@ MakeCachedEnsembleCommand(
if (objPtr->typePtr == &ensembleCmdType) {
ensembleCmd = objPtr->internalRep.twoPtrValue.ptr1;
+ TclCleanupCommandMacro(ensembleCmd->token);
if (ensembleCmd->fix) {
Tcl_DecrRefCount(ensembleCmd->fix);
}
@@ -2387,7 +2369,8 @@ MakeCachedEnsembleCommand(
*/
ensembleCmd->epoch = ensemblePtr->epoch;
- ensembleCmd->token = ensemblePtr->token;
+ ensembleCmd->token = (Command *) ensemblePtr->token;
+ ensembleCmd->token->refCount++;
if (fix) {
Tcl_IncrRefCount(fix);
}
@@ -2773,6 +2756,7 @@ FreeEnsembleCmdRep(
{
EnsembleCmdRep *ensembleCmd = objPtr->internalRep.twoPtrValue.ptr1;
+ TclCleanupCommandMacro(ensembleCmd->token);
if (ensembleCmd->fix) {
Tcl_DecrRefCount(ensembleCmd->fix);
}
@@ -2810,6 +2794,7 @@ DupEnsembleCmdRep(
copyPtr->internalRep.twoPtrValue.ptr1 = ensembleCopy;
ensembleCopy->epoch = ensembleCmd->epoch;
ensembleCopy->token = ensembleCmd->token;
+ ensembleCopy->token->refCount++;
ensembleCopy->fix = ensembleCmd->fix;
if (ensembleCopy->fix) {
Tcl_IncrRefCount(ensembleCopy->fix);
diff --git a/generic/tclIOUtil.c b/generic/tclIOUtil.c
index 1330c02..3aa0ce5 100644
--- a/generic/tclIOUtil.c
+++ b/generic/tclIOUtil.c
@@ -71,8 +71,7 @@ typedef struct ThreadSpecificData {
* Prototypes for functions defined later in this file.
*/
-static int EvalFileCallback(ClientData data[],
- Tcl_Interp *interp, int result);
+static Tcl_NRPostProc EvalFileCallback;
static FilesystemRecord*FsGetFirstFilesystem(void);
static void FsThrExitProc(ClientData cd);
static Tcl_Obj * FsListMounts(Tcl_Obj *pathPtr, const char *pattern);
diff --git a/generic/tclInt.h b/generic/tclInt.h
index 6d2db5d..4f7ea6e 100644
--- a/generic/tclInt.h
+++ b/generic/tclInt.h
@@ -2756,6 +2756,7 @@ MODULE_SCOPE Tcl_ObjCmdProc TclNRYieldObjCmd;
MODULE_SCOPE Tcl_ObjCmdProc TclNRYieldmObjCmd;
MODULE_SCOPE Tcl_ObjCmdProc TclNRYieldToObjCmd;
MODULE_SCOPE Tcl_ObjCmdProc TclNRInvoke;
+MODULE_SCOPE Tcl_NRPostProc TclNRReleaseValues;
MODULE_SCOPE void TclSetTailcall(Tcl_Interp *interp, Tcl_Obj *tailcallPtr);
MODULE_SCOPE void TclPushTailcallPoint(Tcl_Interp *interp);
@@ -2866,8 +2867,7 @@ MODULE_SCOPE int TclCheckBadOctal(Tcl_Interp *interp,
MODULE_SCOPE int TclChanCaughtErrorBypass(Tcl_Interp *interp,
Tcl_Channel chan);
MODULE_SCOPE Tcl_ObjCmdProc TclChannelNamesCmd;
-MODULE_SCOPE int TclClearRootEnsemble(ClientData data[],
- Tcl_Interp *interp, int result);
+MODULE_SCOPE Tcl_NRPostProc TclClearRootEnsemble;
MODULE_SCOPE ContLineLoc *TclContinuationsEnter(Tcl_Obj *objPtr, int num,
int *loc);
MODULE_SCOPE void TclContinuationsEnterDerived(Tcl_Obj *objPtr,
diff --git a/generic/tclOO.c b/generic/tclOO.c
index 0454bfe..ec666ee 100644
--- a/generic/tclOO.c
+++ b/generic/tclOO.c
@@ -68,12 +68,9 @@ static int CloneObjectMethod(Tcl_Interp *interp, Object *oPtr,
static void DeletedDefineNamespace(ClientData clientData);
static void DeletedObjdefNamespace(ClientData clientData);
static void DeletedHelpersNamespace(ClientData clientData);
-static int FinalizeAlloc(ClientData data[],
- Tcl_Interp *interp, int result);
-static int FinalizeNext(ClientData data[],
- Tcl_Interp *interp, int result);
-static int FinalizeObjectCall(ClientData data[],
- Tcl_Interp *interp, int result);
+static Tcl_NRPostProc FinalizeAlloc;
+static Tcl_NRPostProc FinalizeNext;
+static Tcl_NRPostProc FinalizeObjectCall;
static int InitFoundation(Tcl_Interp *interp);
static void KillFoundation(ClientData clientData,
Tcl_Interp *interp);
diff --git a/generic/tclOOCall.c b/generic/tclOOCall.c
index facf90d..1797760 100644
--- a/generic/tclOOCall.c
+++ b/generic/tclOOCall.c
@@ -70,15 +70,12 @@ static void AddSimpleClassChainToCallContext(Class *classPtr,
Class *const filterDecl);
static int CmpStr(const void *ptr1, const void *ptr2);
static void DupMethodNameRep(Tcl_Obj *srcPtr, Tcl_Obj *dstPtr);
-static int FinalizeMethodRefs(ClientData data[],
- Tcl_Interp *interp, int result);
+static Tcl_NRPostProc FinalizeMethodRefs;
static void FreeMethodNameRep(Tcl_Obj *objPtr);
static inline int IsStillValid(CallChain *callPtr, Object *oPtr,
int flags, int reuseMask);
-static int ResetFilterFlags(ClientData data[],
- Tcl_Interp *interp, int result);
-static int SetFilterFlags(ClientData data[],
- Tcl_Interp *interp, int result);
+static Tcl_NRPostProc ResetFilterFlags;
+static Tcl_NRPostProc SetFilterFlags;
static inline void StashCallChain(Tcl_Obj *objPtr, CallChain *callPtr);
/*
diff --git a/generic/tclOOMethod.c b/generic/tclOOMethod.c
index a311ddb..99a8bfc 100644
--- a/generic/tclOOMethod.c
+++ b/generic/tclOOMethod.c
@@ -70,10 +70,8 @@ static Tcl_Obj ** InitEnsembleRewrite(Tcl_Interp *interp, int objc,
static int InvokeProcedureMethod(ClientData clientData,
Tcl_Interp *interp, Tcl_ObjectContext context,
int objc, Tcl_Obj *const *objv);
-static int FinalizeForwardCall(ClientData data[], Tcl_Interp *interp,
- int result);
-static int FinalizePMCall(ClientData data[], Tcl_Interp *interp,
- int result);
+static Tcl_NRPostProc FinalizeForwardCall;
+static Tcl_NRPostProc FinalizePMCall;
static int PushMethodCallFrame(Tcl_Interp *interp,
CallContext *contextPtr, ProcedureMethod *pmPtr,
int objc, Tcl_Obj *const *objv,
diff --git a/generic/tclPathObj.c b/generic/tclPathObj.c
index 99d576d..c2643bf 100644
--- a/generic/tclPathObj.c
+++ b/generic/tclPathObj.c
@@ -869,12 +869,16 @@ TclJoinPath(
* object which can be normalized more efficiently. Currently we only
* use the special case when we have exactly two elements, but we
* could expand that in the future.
+ *
+ * Bugfix [a47641a0]. TclNewFSPathObj requires first argument
+ * to be an absolute path. Added a check for that elt is absolute.
*/
if ((i == (elements-2)) && (i == 0)
- && (elt->typePtr == &tclFsPathType)
- && !((elt->bytes != NULL) && (elt->bytes[0] == '\0'))) {
- Tcl_Obj *tailObj = objv[i+1];
+ && (elt->typePtr == &tclFsPathType)
+ && !((elt->bytes != NULL) && (elt->bytes[0] == '\0'))
+ && TclGetPathType(elt, NULL, NULL, NULL) == TCL_PATH_ABSOLUTE) {
+ Tcl_Obj *tailObj = objv[i+1];
type = TclGetPathType(tailObj, NULL, NULL, NULL);
if (type == TCL_PATH_RELATIVE) {
diff --git a/generic/tclTest.c b/generic/tclTest.c
index 4695ab5..e33d263 100644
--- a/generic/tclTest.c
+++ b/generic/tclTest.c
@@ -413,8 +413,7 @@ static int TestHashSystemHashCmd(ClientData clientData,
Tcl_Interp *interp, int objc,
Tcl_Obj *const objv[]);
-static int NREUnwind_callback(ClientData data[], Tcl_Interp *interp,
- int result);
+static Tcl_NRPostProc NREUnwind_callback;
static int TestNREUnwind(ClientData clientData,
Tcl_Interp *interp, int objc,
Tcl_Obj *const objv[]);
diff --git a/tests/cmdAH.test b/tests/cmdAH.test
index 64cfeba..c74bddb 100644
--- a/tests/cmdAH.test
+++ b/tests/cmdAH.test
@@ -879,19 +879,18 @@ test cmdAH-18.3 {Tcl_FileObjCmd: executable} {unix testchmod} {
test cmdAH-18.5 {Tcl_FileObjCmd: executable} -constraints {win} -body {
# On pc, must be a .exe, .com, etc.
set x [file exe $gorpfile]
- set gorpexe [makeFile foo gorp.exe]
- lappend x [file exe $gorpexe]
-} -cleanup {
- removeFile $gorpexe
-} -result {0 1}
-test cmdAH-18.5.1 {Tcl_FileObjCmd: executable} -constraints {win} -body {
- # On pc, must be a .exe, .com, etc.
- set x [file exe $gorpfile]
- set gorpexe [makeFile foo gorp.exe]
- lappend x [file exe [string toupper $gorpexe]]
+ set gorpexes {}
+ foreach ext {exe com cmd bat ps1} {
+ set gorpexe [makeFile foo gorp.$ext]
+ lappend gorpexes $gorpexe
+ lappend x [file exe $gorpexe] [file exe [string toupper $gorpexe]]
+ }
+ set x
} -cleanup {
- removeFile $gorpexe
-} -result {0 1}
+ foreach gorpexe $gorpexes {
+ removeFile $gorpexe
+ }
+} -result {0 1 1 1 1 1 1 1 1 1 1}
test cmdAH-18.6 {Tcl_FileObjCmd: executable} {} {
# Directories are always executable.
file exe $dirfile
@@ -1027,6 +1026,16 @@ test cmdAH-20.6 {Tcl_FileObjCmd: atime touch} -setup {
set modatime [file atime $file $newatime]
expr {$newatime == $modatime ? 1 : "$newatime != $modatime"}
} -result 1
+test cmdAH-20.7 {
+ Tcl_FileObjCmd: atime (built-in Windows names)
+} -constraints {win} -body {
+ file atime con
+} -result "could not get access time for file \"con\"" -returnCodes error
+test cmdAH-20.7.1 {
+ Tcl_FileObjCmd: atime (built-in Windows names with dir path and extension)
+} -constraints {win} -body {
+ file atime [file join [temporaryDirectory] CON.txt]
+} -result "could not get access time for file \"[file join [temporaryDirectory] CON.txt]\"" -returnCodes error
if {[testConstraint unix] && [file exists /tmp]} {
removeFile touch.me /tmp
@@ -1258,6 +1267,16 @@ test cmdAH-24.13 {Tcl_FileObjCmd: directory mtime} -setup {
} -cleanup {
file delete -force $dirname
} -result {0 1}
+test cmdAH-24.14 {
+ Tcl_FileObjCmd: mtime (built-in Windows names)
+} -constraints {win} -body {
+ file mtime con
+} -result "could not get modification time for file \"con\"" -returnCodes error
+test cmdAH-24.14.1 {
+ Tcl_FileObjCmd: mtime (built-in Windows names with dir path and extension)
+} -constraints {win} -body {
+ file mtime [file join [temporaryDirectory] CON.txt]
+} -result "could not get modification time for file \"[file join [temporaryDirectory] CON.txt]\"" -returnCodes error
# owned
test cmdAH-25.1 {Tcl_FileObjCmd: owned} -returnCodes error -body {
@@ -1277,6 +1296,12 @@ test cmdAH-25.2.1 {Tcl_FileObjCmd: owned} -constraints unix -setup {
test cmdAH-25.3 {Tcl_FileObjCmd: owned} {unix notRoot} {
file owned /
} 0
+test cmdAH-25.3.1 {Tcl_FileObjCmd: owned} -constraints win -body {
+ file owned $env(windir)
+} -result 0
+test cmdAH-25.4 {Tcl_FileObjCmd: owned} -body {
+ file owned nosuchfile
+} -result 0
# readlink
test cmdAH-26.1 {Tcl_FileObjCmd: readlink} -returnCodes error -body {
@@ -1307,6 +1332,16 @@ test cmdAH-27.2 {Tcl_FileObjCmd: size} {
test cmdAH-27.3 {Tcl_FileObjCmd: size} {
list [catch {file size _bogus_} msg] [string tolower $msg] $errorCode
} {1 {could not read "_bogus_": no such file or directory} {POSIX ENOENT {no such file or directory}}}
+test cmdAH-27.4 {
+ Tcl_FileObjCmd: size (built-in Windows names)
+} -constraints {win} -body {
+ file size con
+} -result 0
+test cmdAH-27.4.1 {
+ Tcl_FileObjCmd: size (built-in Windows names with dir path and extension)
+} -constraints {win} -body {
+ file size [file join [temporaryDirectory] con.txt]
+} -result 0
catch {testsetplatform $platform}
removeFile $gorpfile
@@ -1398,12 +1433,24 @@ test cmdAH-28.12 {Tcl_FileObjCmd: stat} -setup {
} -cleanup {
removeFile $filename
} -result 1
+test cmdAH-28.13 {Tcl_FileObjCmd: stat (built-in Windows names)} -setup {
+ unset -nocomplain stat
+} -body {
+ file stat con stat
+ lmap elem {atime ctime dev gid ino mode mtime nlink size type uid} {set stat($elem)}
+} -result {0 0 -1 0 0 8630 0 0 0 characterSpecial 0}
+test cmdAH-28.13.1 {Tcl_FileObjCmd: stat (built-in Windows names)} -setup {
+ unset -nocomplain stat
+} -body {
+ file stat [file join [temporaryDirectory] CON.txt] stat
+ lmap elem {atime ctime dev gid ino mode mtime nlink size type uid} {set stat($elem)}
+} -result {0 0 -1 0 0 8630 0 0 0 characterSpecial 0}
unset -nocomplain stat
# type
test cmdAH-29.1 {Tcl_FileObjCmd: type} -returnCodes error -body {
- file size a b
-} -result {wrong # args: should be "file size name"}
+ file type a b
+} -result {wrong # args: should be "file type name"}
test cmdAH-29.2 {Tcl_FileObjCmd: type} {
file type $dirfile
} directory
@@ -1438,6 +1485,16 @@ test cmdAH-29.4.1 {Tcl_FileObjCmd: type} -constraints {linkDirectory} -setup {
test cmdAH-29.5 {Tcl_FileObjCmd: type} {
list [catch {file type _bogus_} msg] [string tolower $msg] $errorCode
} {1 {could not read "_bogus_": no such file or directory} {POSIX ENOENT {no such file or directory}}}
+test cmdAH-29.6 {
+ Tcl_FileObjCmd: type (built-in Windows names)
+} -constraints {win} -body {
+ file type con
+} -result "characterSpecial"
+test cmdAH-29.6.1 {
+ Tcl_FileObjCmd: type (built-in Windows names, with dir path and extension)
+} -constraints {win} -body {
+ file type [file join [temporaryDirectory] CON.txt]
+} -result "characterSpecial"
# Error conditions
test cmdAH-30.1 {Tcl_FileObjCmd: error conditions} -returnCodes error -body {
diff --git a/tests/fileName.test b/tests/fileName.test
index 51f00d1..a19bd1e 100644
--- a/tests/fileName.test
+++ b/tests/fileName.test
@@ -1468,14 +1468,16 @@ if {[testConstraint testsetplatform]} {
}
test filename-17.2 {windows specific glob with executable} -body {
makeDirectory execglob
- makeFile contents execglob/abc.exe
- makeFile contents execglob/abc.notexecutable
- glob -nocomplain -dir [temporaryDirectory]/execglob -tails -types x *
+ foreach ext {exe com cmd bat ps1 notexecutable} {
+ makeFile contents execglob/abc.$ext
+ }
+ lsort [glob -nocomplain -dir [temporaryDirectory]/execglob -tails -types x *]
} -constraints {win} -cleanup {
- removeFile execglob/abc.exe
- removeFile execglob/abc.notexecutable
+ foreach ext {exe com cmd bat ps1 notexecutable} {
+ removeFile execglob/abc.$ext
+ }
removeDirectory execglob
-} -result {abc.exe}
+} -result {abc.bat abc.cmd abc.com abc.exe abc.ps1}
test filename-17.3 {Bug 2571597} win {
set p /a
file pathtype $p
diff --git a/win/tclWinFile.c b/win/tclWinFile.c
index 25c6ea4..3e8a171 100755
--- a/win/tclWinFile.c
+++ b/win/tclWinFile.c
@@ -16,8 +16,9 @@
#include "tclFileSystem.h"
#include <winioctl.h>
#include <shlobj.h>
-#include <lm.h> /* For TclpGetUserHome(). */
+#include <lm.h> /* For TclpGetUserHome(). */
#include <userenv.h> /* For TclpGetUserHome(). */
+#include <aclapi.h> /* For GetNamedSecurityInfo */
#ifdef _MSC_VER
# pragma comment(lib, "userenv.lib")
@@ -1769,7 +1770,7 @@ NativeAccess(
* NativeIsExec --
*
* Determines if a path is executable. On windows this is simply defined
- * by whether the path ends in any of ".exe", ".com", or ".bat"
+ * by whether the path ends in a standard executable extension.
*
* Results:
* 1 = executable, 0 = not.
@@ -1793,6 +1794,8 @@ NativeIsExec(
if ((_tcsicmp(path+len-3, TEXT("exe")) == 0)
|| (_tcsicmp(path+len-3, TEXT("com")) == 0)
+ || (_tcsicmp(path+len-3, TEXT("cmd")) == 0)
+ || (_tcsicmp(path+len-3, TEXT("ps1")) == 0)
|| (_tcsicmp(path+len-3, TEXT("bat")) == 0)) {
return 1;
}
@@ -1951,6 +1954,7 @@ NativeStat(
unsigned short mode;
unsigned int inode = 0;
HANDLE fileHandle;
+ DWORD fileType = FILE_TYPE_UNKNOWN;
/*
* If we can use 'createFile' on this, then we can use the resulting
@@ -1958,6 +1962,14 @@ NativeStat(
* other attributes reading APIs. If not, then we try to fall back on the
* 'getFileAttributesExProc', and if that isn't available, then on even
* simpler routines.
+ *
+ * Special consideration must be given to Windows hardcoded names
+ * like CON, NULL, COM1, LPT1 etc. For these, we still need to
+ * do the CreateFile as some may not exist (e.g. there is no CON
+ * in wish by default). However the subsequent GetFileInformationByHandle
+ * will fail. We do a WinIsReserved to see if it is one of the special
+ * names, and if successful, mock up a BY_HANDLE_FILE_INFORMATION
+ * structure.
*/
fileHandle = CreateFile(nativePath, GENERIC_READ,
@@ -1968,19 +1980,26 @@ NativeStat(
BY_HANDLE_FILE_INFORMATION data;
if (GetFileInformationByHandle(fileHandle,&data) != TRUE) {
- CloseHandle(fileHandle);
- Tcl_SetErrno(ENOENT);
- return -1;
- }
- CloseHandle(fileHandle);
-
+ fileType = GetFileType(fileHandle);
+ CloseHandle(fileHandle);
+ if (fileType != FILE_TYPE_CHAR && fileType != FILE_TYPE_DISK) {
+ Tcl_SetErrno(ENOENT);
+ return -1;
+ }
+ /* Mock up the expected structure */
+ memset(&data, 0, sizeof(data));
+ statPtr->st_atime = 0;
+ statPtr->st_mtime = 0;
+ statPtr->st_ctime = 0;
+ } else {
+ CloseHandle(fileHandle);
+ statPtr->st_atime = ToCTime(data.ftLastAccessTime);
+ statPtr->st_mtime = ToCTime(data.ftLastWriteTime);
+ statPtr->st_ctime = ToCTime(data.ftCreationTime);
+ }
attr = data.dwFileAttributes;
-
statPtr->st_size = ((Tcl_WideInt) data.nFileSizeLow) |
(((Tcl_WideInt) data.nFileSizeHigh) << 32);
- statPtr->st_atime = ToCTime(data.ftLastAccessTime);
- statPtr->st_mtime = ToCTime(data.ftLastWriteTime);
- statPtr->st_ctime = ToCTime(data.ftCreationTime);
/*
* On Unix, for directories, nlink apparently depends on the number of
@@ -2036,6 +2055,13 @@ NativeStat(
dev = NativeDev(nativePath);
mode = NativeStatMode(attr, checkLinks, NativeIsExec(nativePath));
+ if (fileType == FILE_TYPE_CHAR) {
+ mode &= ~S_IFMT;
+ mode |= S_IFCHR;
+ } else if (fileType == FILE_TYPE_DISK) {
+ mode &= ~S_IFMT;
+ mode |= S_IFBLK;
+ }
statPtr->st_dev = (dev_t) dev;
statPtr->st_ino = inode;
@@ -3109,6 +3135,69 @@ TclpUtime(
}
/*
+ *---------------------------------------------------------------------------
+ *
+ * TclWinFileOwned --
+ *
+ * Returns 1 if the specified file exists and is owned by the current
+ * user and 0 otherwise. Like the Unix case, the check is made using
+ * the real process SID, not the effective (impersonation) one.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+int
+TclWinFileOwned(
+ Tcl_Obj *pathPtr) /* File whose ownership is to be checked */
+{
+ const TCHAR *native;
+ PSID ownerSid = NULL;
+ PSECURITY_DESCRIPTOR secd = NULL;
+ HANDLE token;
+ LPBYTE buf = NULL;
+ DWORD bufsz;
+ int owned = 0;
+
+ native = Tcl_FSGetNativePath(pathPtr);
+
+ if (GetNamedSecurityInfo(native, SE_FILE_OBJECT,
+ OWNER_SECURITY_INFORMATION, &ownerSid,
+ NULL, NULL, NULL, &secd) != ERROR_SUCCESS) {
+ /* Either not a file, or we do not have access to it in which
+ case we are in all likelihood not the owner */
+ return 0;
+ }
+
+ /*
+ * Getting the current process SID is a multi-step process.
+ * We make the assumption that if a call fails, this process is
+ * so underprivileged it could not possibly own anything. Normally
+ * a process can *always* look up its own token.
+ */
+ if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
+ /* Find out how big the buffer needs to be */
+ bufsz = 0;
+ GetTokenInformation(token, TokenUser, NULL, 0, &bufsz);
+ if (bufsz) {
+ buf = ckalloc(bufsz);
+ if (GetTokenInformation(token, TokenUser, buf, bufsz, &bufsz)) {
+ owned = EqualSid(ownerSid, ((PTOKEN_USER) buf)->User.Sid);
+ }
+ }
+ CloseHandle(token);
+ }
+
+vamoose:
+ /* Free allocations and be done */
+ if (secd)
+ LocalFree(secd); /* Also frees ownerSid */
+ if (buf)
+ ckfree(buf);
+
+ return (owned != 0); /* Convert non-0 to 1 */
+}
+
+/*
* Local Variables:
* mode: c
* c-basic-offset: 4
diff --git a/win/tclWinInt.h b/win/tclWinInt.h
index 9df424f..6b098f8 100644
--- a/win/tclWinInt.h
+++ b/win/tclWinInt.h
@@ -72,6 +72,7 @@ MODULE_SCOPE int TclWinSymLinkCopyDirectory(const TCHAR *LinkOriginal,
const TCHAR *LinkCopy);
MODULE_SCOPE int TclWinSymLinkDelete(const TCHAR *LinkOriginal,
int linkOnly);
+MODULE_SCOPE int TclWinFileOwned(Tcl_Obj *);
#if defined(TCL_THREADS) && defined(USE_THREAD_ALLOC)
MODULE_SCOPE void TclWinFreeAllocCache(void);
MODULE_SCOPE void TclFreeAllocCache(void *);
diff --git a/win/tclWinPort.h b/win/tclWinPort.h
index ca6b2bf..b486466 100644
--- a/win/tclWinPort.h
+++ b/win/tclWinPort.h
@@ -360,6 +360,20 @@ typedef DWORD_PTR * PDWORD_PTR;
# define S_IFLNK 0120000 /* Symbolic Link */
#endif
+/*
+ * Windows compilers do not define S_IFBLK. However, Tcl uses it in
+ * GetTypeFromMode to identify blockSpecial devices based on the
+ * value in the statsbuf st_mode field. We have no other way to pass this
+ * from NativeStat on Windows so are forced to define it here.
+ * The definition here is essentially what is seen on Linux and MingW.
+ * XXX - the root problem is Tcl using Unix definitions instead of
+ * abstracting the structure into a platform independent one. Sigh - perhaps
+ * Tcl 9
+ */
+#ifndef S_IFBLK
+# define S_IFBLK (S_IFDIR | S_IFCHR)
+#endif
+
#ifndef S_ISREG
# ifdef S_IFREG
# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)