diff options
Diffstat (limited to 'win/tclWinFile.c')
-rw-r--r-- | win/tclWinFile.c | 241 |
1 files changed, 161 insertions, 80 deletions
diff --git a/win/tclWinFile.c b/win/tclWinFile.c index f44140f..068a029 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.44.2.1 2003/03/18 10:51:31 vincentdarley Exp $ + * RCS: @(#) $Id: tclWinFile.c,v 1.44.2.2 2003/04/14 15:46:00 vincentdarley Exp $ */ //#define _WIN32_WINNT 0x0500 @@ -128,6 +128,20 @@ typedef struct { WCHAR dummyBuf[MAX_PATH*3]; } DUMMY_REPARSE_BUFFER; +#ifdef HAVE_NO_FINDEX_ENUMS +/* These two aren't in VC++ 5.2 headers */ +typedef enum _FINDEX_INFO_LEVELS { + FindExInfoStandard, + FindExInfoMaxInfoLevel +} FINDEX_INFO_LEVELS; +typedef enum _FINDEX_SEARCH_OPS { + FindExSearchNameMatch, + FindExSearchLimitToDirectories, + FindExSearchLimitToDevices, + FindExSearchMaxSearchOp +} FINDEX_SEARCH_OPS; +#endif /* HAVE_NO_FINDEX_ENUMS */ + /* Other typedefs required by this code */ static time_t ToCTime(FILETIME fileTime); @@ -141,6 +155,8 @@ typedef NET_API_STATUS NET_API_FUNCTION NETAPIBUFFERFREEPROC typedef NET_API_STATUS NET_API_FUNCTION NETGETDCNAMEPROC (LPWSTR servername, LPWSTR domainname, LPBYTE *bufptr); +extern Tcl_FSDupInternalRepProc NativeDupInternalRep; + /* * Declarations for local procedures defined in this file: */ @@ -162,7 +178,6 @@ static int WinLink(CONST TCHAR* LinkSource, CONST TCHAR* LinkTarget, int linkAction); static int WinSymLinkDirectory(CONST TCHAR* LinkDirectory, CONST TCHAR* LinkTarget); - /* *-------------------------------------------------------------------- @@ -249,8 +264,7 @@ WinLink(LinkSource, LinkTarget, linkAction) * * WinReadLink * - * What does 'LinkSource' point to? We need the original 'pathPtr' - * just so we can construct a path object in the correct filesystem. + * What does 'LinkSource' point to? *-------------------------------------------------------------------- */ static Tcl_Obj* @@ -429,7 +443,11 @@ TclWinSymLinkDelete(LinkOriginal, linkOnly) * * Assumption that LinkDirectory is a valid, existing directory. * - * Returns a Tcl_Obj with refCount of 1 (i.e. owned by the caller). + * Returns a Tcl_Obj with refCount of 1 (i.e. owned by the caller), + * or NULL if anything went wrong. + * + * In the future we should enhance this to return a path object + * rather than a string. *-------------------------------------------------------------------- */ static Tcl_Obj* @@ -457,38 +475,77 @@ WinReadLinkDirectory(LinkDirectory) Tcl_DString ds; CONST char *copy; int len; + int offset = 0; - Tcl_WinTCharToUtf( - (CONST char*)reparseBuffer->SymbolicLinkReparseBuffer.PathBuffer, - (int)reparseBuffer->SymbolicLinkReparseBuffer.SubstituteNameLength, - &ds); - - copy = Tcl_DStringValue(&ds); - len = Tcl_DStringLength(&ds); /* - * Certain native path representations on Windows have this special - * prefix to indicate that they are to be treated specially. For - * example extremely long paths, or symlinks + * Certain native path representations on Windows have a + * special prefix to indicate that they are to be treated + * specially. For example extremely long paths, or symlinks, + * or volumes mounted inside directories. + * + * There is an assumption in this code that 'wide' interfaces + * are being used (see tclWin32Dll.c), which is true for the + * only systems which support reparse tags at present. If + * that changes in the future, this code will have to be + * generalised. */ - if (*copy == '\\') { - if (0 == strncmp(copy,"\\??\\",4)) { - copy += 4; - len -= 4; - if (0 == strncmp(copy,"Volume{",7)) { - /* - * This is actually a mounted drive, which is in any - * case treated as being mounted in place, so it is - * in some sense a symlink to itself - */ - Tcl_DStringFree(&ds); - Tcl_SetErrno(EINVAL); - return NULL; + if (reparseBuffer->SymbolicLinkReparseBuffer.PathBuffer[0] + == L'\\') { + /* Check whether this is a mounted volume */ + if (wcsncmp(reparseBuffer->SymbolicLinkReparseBuffer.PathBuffer, + L"\\??\\Volume{",11) == 0) { + char drive; + /* + * There is some confusion between \??\ and \\?\ which + * we have to fix here. It doesn't seem very well + * documented. + */ + reparseBuffer->SymbolicLinkReparseBuffer + .PathBuffer[1] = L'\\'; + /* + * Check if a corresponding drive letter exists, and + * use that if it is found + */ + drive = TclWinDriveLetterForVolMountPoint(reparseBuffer + ->SymbolicLinkReparseBuffer.PathBuffer); + if (drive != -1) { + char driveSpec[3] = { + drive, ':', '\0' + }; + retVal = Tcl_NewStringObj(driveSpec,2); + Tcl_IncrRefCount(retVal); + return retVal; } - } else if (0 == strncmp(copy,"\\\\?\\",4)) { - copy += 4; - len -= 4; + /* + * This is actually a mounted drive, which doesn't + * exists as a DOS drive letter. This means the path + * isn't actually a link, although we partially treat + * it like one ('file type' will return 'link'), but + * then the link will actually just be treated like + * an ordinary directory. I don't believe any + * serious inconsistency will arise from this, but it + * is something to be aware of. + */ + Tcl_SetErrno(EINVAL); + return NULL; + } else if (wcsncmp(reparseBuffer->SymbolicLinkReparseBuffer + .PathBuffer, L"\\\\?\\",4) == 0) { + /* Strip off the prefix */ + offset = 4; + } else if (wcsncmp(reparseBuffer->SymbolicLinkReparseBuffer + .PathBuffer, L"\\??\\",4) == 0) { + /* Strip off the prefix */ + offset = 4; } } + + Tcl_WinTCharToUtf( + (CONST char*)reparseBuffer->SymbolicLinkReparseBuffer.PathBuffer, + (int)reparseBuffer->SymbolicLinkReparseBuffer + .SubstituteNameLength, &ds); + + copy = Tcl_DStringValue(&ds)+offset; + len = Tcl_DStringLength(&ds)-offset; retVal = Tcl_NewStringObj(copy,len); Tcl_IncrRefCount(retVal); Tcl_DStringFree(&ds); @@ -712,77 +769,97 @@ TclpMatchInDirectory(interp, resultPtr, pathPtr, pattern, types) DWORD attr; HANDLE handle; WIN32_FIND_DATAT data; - CONST char *dirName; + CONST char *dirName; /* utf-8 dir name, later + * with pattern appended */ int dirLength; int matchSpecialDots; - Tcl_DString ds; /* native encoding of dir */ + Tcl_DString ds; /* native encoding of dir, also used + * temporarily for other things. */ Tcl_DString dsOrig; /* utf-8 encoding of dir */ - Tcl_DString dirString; /* utf-8 encoding of dir with \'s */ Tcl_Obj *fileNamePtr; + char lastChar; /* - * Convert the path to normalized form since some interfaces only - * accept backslashes. Also, ensure that the directory ends with a - * separator character. + * Get the normalized path representation + * (the main thing is we dont want any '~' sequences). */ - fileNamePtr = Tcl_FSGetTranslatedPath(interp, pathPtr); + fileNamePtr = Tcl_FSGetNormalizedPath(interp, pathPtr); if (fileNamePtr == NULL) { return TCL_ERROR; } - Tcl_DStringInit(&dsOrig); - dirName = Tcl_GetStringFromObj(fileNamePtr, &dirLength); - Tcl_DStringAppend(&dsOrig, dirName, dirLength); - - Tcl_DStringInit(&dirString); - if (dirLength == 0) { - Tcl_DStringAppend(&dirString, ".\\", 2); - } else { - char *p; - - Tcl_DStringAppend(&dirString, dirName, dirLength); - for (p = Tcl_DStringValue(&dirString); *p != '\0'; p++) { - if (*p == '/') { - *p = '\\'; - } - } - p--; - /* Make sure we have a trailing directory delimiter */ - if ((*p != '\\') && (*p != ':')) { - Tcl_DStringAppend(&dirString, "\\", 1); - Tcl_DStringAppend(&dsOrig, "/", 1); - dirLength++; - } - } - dirName = Tcl_DStringValue(&dirString); /* - * First verify that the specified path is actually a directory. + * Verify that the specified path exists and + * is actually a directory. */ - - native = Tcl_WinUtfToTChar(dirName, Tcl_DStringLength(&dirString), - &ds); + native = Tcl_FSGetNativePath(pathPtr); + if (native == NULL) { + return TCL_OK; + } attr = (*tclWinProcs->getFileAttributesProc)(native); - Tcl_DStringFree(&ds); if ((attr == 0xffffffff) || ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0)) { - Tcl_DStringFree(&dirString); return TCL_OK; } + /* + * Build up the directory name for searching, including + * a trailing directory separator. + */ + + Tcl_DStringInit(&dsOrig); + dirName = Tcl_GetStringFromObj(fileNamePtr, &dirLength); + Tcl_DStringAppend(&dsOrig, dirName, dirLength); + + lastChar = dirName[dirLength -1]; + if ((lastChar != '\\') && (lastChar != '/') && (lastChar != ':')) { + Tcl_DStringAppend(&dsOrig, "/", 1); + dirLength++; + } + dirName = Tcl_DStringValue(&dsOrig); + /* - * We need to check all files in the directory, so append a *.* - * to the path. + * We need to check all files in the directory, so we append + * '*.*' to the path, unless the pattern we've been given is + * rather simple, when we can use that instead. */ - dirName = Tcl_DStringAppend(&dirString, "*.*", 3); + if (strpbrk(pattern, "[]\\") == NULL) { + /* + * The pattern is a simple one containing just '*' and/or '?'. + * This means we can get the OS to help us, by passing + * it the pattern. + */ + dirName = Tcl_DStringAppend(&dsOrig, pattern, -1); + } else { + dirName = Tcl_DStringAppend(&dsOrig, "*.*", 3); + } native = Tcl_WinUtfToTChar(dirName, -1, &ds); - handle = (*tclWinProcs->findFirstFileProc)(native, &data); + if (tclWinProcs->findFirstFileExProc == NULL + || (types == NULL) + || (types->type != TCL_GLOB_TYPE_DIR)) { + handle = (*tclWinProcs->findFirstFileProc)(native, &data); + } else { + /* We can be more efficient, for pure directory requests */ + handle = (*tclWinProcs->findFirstFileExProc)(native, + FindExInfoStandard, &data, + FindExSearchLimitToDirectories, NULL, 0); + } Tcl_DStringFree(&ds); if (handle == INVALID_HANDLE_VALUE) { - Tcl_DStringFree(&dirString); - TclWinConvertError(GetLastError()); + DWORD err = GetLastError(); + if (err == ERROR_FILE_NOT_FOUND) { + /* + * We used our 'pattern' above, and matched nothing + * This means we just return TCL_OK, indicating + * no results found. + */ + Tcl_DStringFree(&dsOrig); + return TCL_OK; + } + TclWinConvertError(err); Tcl_ResetResult(interp); Tcl_AppendResult(interp, "couldn't read directory \"", Tcl_DStringValue(&dsOrig), "\": ", @@ -791,6 +868,12 @@ TclpMatchInDirectory(interp, resultPtr, pathPtr, pattern, types) return TCL_ERROR; } + /* + * We may use this later, so we must restore it to its + * length including the directory delimiter + */ + Tcl_DStringSetLength(&dsOrig, dirLength); + /* * Check to see if the pattern should match the special * . and .. names, referring to the current directory, @@ -884,7 +967,6 @@ TclpMatchInDirectory(interp, resultPtr, pathPtr, pattern, types) } while ((*tclWinProcs->findNextFileProc)(handle, &data) == TRUE); FindClose(handle); - Tcl_DStringFree(&dirString); Tcl_DStringFree(&dsOrig); return TCL_OK; } @@ -952,10 +1034,9 @@ WinIsDrive( * volume, because for NTFS root volumes, the getFileAttributesProc * returns a 'hidden' attribute when it should not. * - * We only ever make one call to a 'get attributes' routine here, - * so that this function is as fast as possible. Unfortunately, - * it still means we have to make the call for every single file - * we return from 'glob', which is not ideal. + * We never make any calss to a 'get attributes' routine here, + * since we have arranged things so that our caller already knows + * such information. * * Results: * 0 = file doesn't match |