From 40c4071e89f7108b6f3f15f4cc1f2534c76ba96e Mon Sep 17 00:00:00 2001 From: vincentdarley Date: Wed, 30 Jun 2004 14:46:08 +0000 Subject: fix to trailing slash documentation and to a filesystem 'file join' bug on windows --- ChangeLog | 8 ++++ doc/filename.n | 9 +++- tests/fileSystem.test | 67 +++++++++++++++++++++++++++ win/tclWinFile.c | 126 ++++++++++++++++++++++++++++++++++++-------------- 4 files changed, 173 insertions(+), 37 deletions(-) diff --git a/ChangeLog b/ChangeLog index f36f3ed..ee3a241 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2004-06-30 Vince Darley + + * doc/filename.n: clarified behaviour concerning trailing + slashes in filenames [Bug 971976] + + * win/tclWinFile.c: + * tests/fileSystem.test: fix and tests for [Bug 979879] + 2004-06-30 Donal K. Fellows TIP#188 IMPLEMENTATION diff --git a/doc/filename.n b/doc/filename.n index ef217e6..c2787ec 100644 --- a/doc/filename.n +++ b/doc/filename.n @@ -4,7 +4,7 @@ '\" See the file "license.terms" for information on usage and redistribution '\" of this file, and for a DISCLAIMER OF ALL WARRANTIES. '\" -'\" RCS: @(#) $Id: filename.n,v 1.12 2004/03/17 18:14:12 das Exp $ +'\" RCS: @(#) $Id: filename.n,v 1.13 2004/06/30 14:46:10 vincentdarley Exp $ '\" .so man.macros .TH filename n 7.5 Tcl "Tcl Built-In Commands" @@ -51,7 +51,12 @@ absolute, and file names may contain any character other than slash. The file names \fB\&.\fR and \fB\&..\fR are special and refer to the current directory and the parent of the current directory respectively. Multiple adjacent slash characters are interpreted as a single -separator. The following examples illustrate various forms of path +separator. Any number of trailing slash characters at the end of a +path are simply ignored, so the paths \fBfoo\fR, \fBfoo/\fR and +\fBfoo//\fR are all identical, and in particular \fBfoo/\fR does not +necessarily mean a directory is being referred. +.PP +The following examples illustrate various forms of path names: .RS .TP 15 diff --git a/tests/fileSystem.test b/tests/fileSystem.test index 6ae080d..45fb97d 100644 --- a/tests/fileSystem.test +++ b/tests/fileSystem.test @@ -942,6 +942,73 @@ test filesystem-8.3 {path objects and empty string} { lappend res $dst $yyy } {foo foo {}} +proc TestFind1 {d f} { + set r1 [file exists [file join $d $f]] + lappend res "[file join $d $f] found: $r1" + lappend res "is dir a dir? [file isdirectory $d]" + set r2 [file exists [file join $d $f]] + lappend res "[file join $d $f] found: $r2" + set res +} +proc TestFind2 {d f} { + set r1 [file exists [file join $d $f]] + lappend res "[file join $d $f] found: $r1" + lappend res "is dir a dir? [file isdirectory [file join $d]]" + set r2 [file exists [file join $d $f]] + lappend res "[file join $d $f] found: $r2" + set res +} + +test filesystem-9.1 {path objects and join and object rep} { + set origdir [pwd] + cd [tcltest::temporaryDirectory] + file mkdir [file join a b c] + set res [TestFind1 a [file join b . c]] + file delete -force [file join a b c] + cd $origdir + set res +} {{a/b/./c found: 1} {is dir a dir? 1} {a/b/./c found: 1}} + +test filesystem-9.2 {path objects and join and object rep} { + set origdir [pwd] + cd [tcltest::temporaryDirectory] + file mkdir [file join a b c] + set res [TestFind2 a [file join b . c]] + file delete -force [file join a b c] + cd $origdir + set res +} {{a/b/./c found: 1} {is dir a dir? 1} {a/b/./c found: 1}} + +test filesystem-9.2.1 {path objects and join and object rep} { + set origdir [pwd] + cd [tcltest::temporaryDirectory] + file mkdir [file join a b c] + set res [TestFind2 a [file join b .]] + file delete -force [file join a b c] + cd $origdir + set res +} {{a/b/. found: 1} {is dir a dir? 1} {a/b/. found: 1}} + +test filesystem-9.3 {path objects and join and object rep} { + set origdir [pwd] + cd [tcltest::temporaryDirectory] + file mkdir [file join a b c] + set res [TestFind1 a [file join b .. b c]] + file delete -force [file join a b c] + cd $origdir + set res +} {{a/b/../b/c found: 1} {is dir a dir? 1} {a/b/../b/c found: 1}} + +test filesystem-9.4 {path objects and join and object rep} { + set origdir [pwd] + cd [tcltest::temporaryDirectory] + file mkdir [file join a b c] + set res [TestFind2 a [file join b .. b c]] + file delete -force [file join a b c] + cd $origdir + set res +} {{a/b/../b/c found: 1} {is dir a dir? 1} {a/b/../b/c found: 1}} + cleanupTests unset -nocomplain drive } diff --git a/win/tclWinFile.c b/win/tclWinFile.c index 05462ed..6113b4e 100644 --- a/win/tclWinFile.c +++ b/win/tclWinFile.c @@ -11,7 +11,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclWinFile.c,v 1.65 2004/06/02 23:29:30 hobbs Exp $ + * RCS: @(#) $Id: tclWinFile.c,v 1.66 2004/06/30 14:46:11 vincentdarley Exp $ */ //#define _WIN32_WINNT 0x0500 @@ -2366,29 +2366,56 @@ TclpObjNormalizePath(interp, pathPtr, nextCheckpoint) } Tcl_DStringAppend(&dsNorm,nativePath,Tcl_DStringLength(&ds)); } else { - WIN32_FIND_DATA fData; - HANDLE handle; + char *checkDots = NULL; - handle = FindFirstFileA(nativePath, &fData); - if (handle == INVALID_HANDLE_VALUE) { - if (GetFileAttributesA(nativePath) - == 0xffffffff) { - /* File doesn't exist */ - Tcl_DStringFree(&ds); - break; + if (lastValidPathEnd[1] == '.') { + checkDots = lastValidPathEnd + 1; + while (checkDots < currentPathEndPosition) { + if (*checkDots != '.') { + checkDots = NULL; + break; + } + checkDots++; } - /* This is usually the '/' in 'c:/' at end of string */ - Tcl_DStringAppend(&dsNorm,"/", 1); + } + if (checkDots != NULL) { + int dotLen = currentPathEndPosition - lastValidPathEnd; + /* + * Path is just dots. We shouldn't really + * ever see a path like that. However, to be + * nice we at least don't mangle the path -- + * we just add the dots as a path segment and + * continue + */ + Tcl_DStringAppend(&dsNorm, (TCHAR*)(nativePath + + Tcl_DStringLength(&ds) + - dotLen), dotLen); } else { - char *nativeName; - if (fData.cFileName[0] != '\0') { - nativeName = fData.cFileName; + /* Normal path */ + WIN32_FIND_DATA fData; + HANDLE handle; + + handle = FindFirstFileA(nativePath, &fData); + if (handle == INVALID_HANDLE_VALUE) { + if (GetFileAttributesA(nativePath) + == 0xffffffff) { + /* File doesn't exist */ + Tcl_DStringFree(&ds); + break; + } + /* This is usually the '/' in 'c:/' at end of string */ + Tcl_DStringAppend(&dsNorm,"/", 1); } else { - nativeName = fData.cAlternateFileName; + char *nativeName; + if (fData.cFileName[0] != '\0') { + nativeName = fData.cFileName; + } else { + nativeName = fData.cAlternateFileName; + } + FindClose(handle); + Tcl_DStringAppend(&dsNorm,"/", 1); + Tcl_DStringAppend(&dsNorm,nativeName,-1); } - FindClose(handle); - Tcl_DStringAppend(&dsNorm,"/", 1); - Tcl_DStringAppend(&dsNorm,nativeName,-1); } } Tcl_DStringFree(&ds); @@ -2491,26 +2518,55 @@ TclpObjNormalizePath(interp, pathPtr, nextCheckpoint) } Tcl_DStringAppend(&dsNorm,nativePath,Tcl_DStringLength(&ds)); } else { - WIN32_FIND_DATAW fData; - HANDLE handle; + char *checkDots = NULL; - handle = FindFirstFileW((WCHAR*)nativePath, &fData); - if (handle == INVALID_HANDLE_VALUE) { - /* This is usually the '/' in 'c:/' at end of string */ - Tcl_DStringAppend(&dsNorm,(CONST char*)L"/", - sizeof(WCHAR)); + if (lastValidPathEnd[1] == '.') { + checkDots = lastValidPathEnd + 1; + while (checkDots < currentPathEndPosition) { + if (*checkDots != '.') { + checkDots = NULL; + break; + } + checkDots++; + } + } + if (checkDots != NULL) { + int dotLen = currentPathEndPosition - lastValidPathEnd; + /* + * Path is just dots. We shouldn't really + * ever see a path like that. However, to be + * nice we at least don't mangle the path -- + * we just add the dots as a path segment and + * continue + */ + Tcl_DStringAppend(&dsNorm, + (TCHAR*)((WCHAR*)(nativePath + + Tcl_DStringLength(&ds)) + - dotLen), + (int)(dotLen * sizeof(WCHAR))); } else { - WCHAR *nativeName; - if (fData.cFileName[0] != '\0') { - nativeName = fData.cFileName; + /* Normal path */ + WIN32_FIND_DATAW fData; + HANDLE handle; + + handle = FindFirstFileW((WCHAR*)nativePath, &fData); + if (handle == INVALID_HANDLE_VALUE) { + /* This is usually the '/' in 'c:/' at end of string */ + Tcl_DStringAppend(&dsNorm,(CONST char*)L"/", + sizeof(WCHAR)); } else { - nativeName = fData.cAlternateFileName; + WCHAR *nativeName; + if (fData.cFileName[0] != '\0') { + nativeName = fData.cFileName; + } else { + nativeName = fData.cAlternateFileName; + } + FindClose(handle); + Tcl_DStringAppend(&dsNorm,(CONST char*)L"/", + sizeof(WCHAR)); + Tcl_DStringAppend(&dsNorm,(TCHAR*)nativeName, + (int) (wcslen(nativeName)*sizeof(WCHAR))); } - FindClose(handle); - Tcl_DStringAppend(&dsNorm,(CONST char*)L"/", - sizeof(WCHAR)); - Tcl_DStringAppend(&dsNorm,(TCHAR*)nativeName, - (int) (wcslen(nativeName)*sizeof(WCHAR))); } } #endif -- cgit v0.12