4 * This file contains temporary wrappers around UNIX file handling
5 * functions. These wrappers map the UNIX functions to Win32 HANDLE-style
6 * files, which can be manipulated through the Win32 console redirection
9 * Copyright (c) 1995-1998 Sun Microsystems, Inc.
11 * See the file "license.terms" for information on usage and redistribution of
12 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
15 #include "tclWinInt.h"
16 #include "tclFileSystem.h"
19 #include <lm.h> /* For TclpGetUserHome(). */
20 #include <userenv.h> /* For TclpGetUserHome(). */
21 #include <aclapi.h> /* For GetNamedSecurityInfo */
24 # pragma comment(lib, "userenv.lib")
27 * The number of 100-ns intervals between the Windows system epoch (1601-01-01
28 * on the proleptic Gregorian calendar) and the Posix epoch (1970-01-01).
31 #define POSIX_EPOCH_AS_FILETIME \
32 ((Tcl_WideInt) 116444736 * (Tcl_WideInt) 1000000000)
35 * Declarations for 'link' related information. This information should come
36 * with VC++ 6.0, but is not in some older SDKs. In any case it is not well
40 #ifndef IO_REPARSE_TAG_RESERVED_ONE
41 # define IO_REPARSE_TAG_RESERVED_ONE 0x000000001
43 #ifndef IO_REPARSE_TAG_RESERVED_RANGE
44 # define IO_REPARSE_TAG_RESERVED_RANGE 0x000000001
46 #ifndef IO_REPARSE_TAG_VALID_VALUES
47 # define IO_REPARSE_TAG_VALID_VALUES 0x0E000FFFF
49 #ifndef IO_REPARSE_TAG_HSM
50 # define IO_REPARSE_TAG_HSM 0x0C0000004
52 #ifndef IO_REPARSE_TAG_NSS
53 # define IO_REPARSE_TAG_NSS 0x080000005
55 #ifndef IO_REPARSE_TAG_NSSRECOVER
56 # define IO_REPARSE_TAG_NSSRECOVER 0x080000006
58 #ifndef IO_REPARSE_TAG_SIS
59 # define IO_REPARSE_TAG_SIS 0x080000007
61 #ifndef IO_REPARSE_TAG_DFS
62 # define IO_REPARSE_TAG_DFS 0x080000008
65 #ifndef IO_REPARSE_TAG_RESERVED_ZERO
66 # define IO_REPARSE_TAG_RESERVED_ZERO 0x00000000
68 #ifndef FILE_FLAG_OPEN_REPARSE_POINT
69 # define FILE_FLAG_OPEN_REPARSE_POINT 0x00200000
71 #ifndef IO_REPARSE_TAG_MOUNT_POINT
72 # define IO_REPARSE_TAG_MOUNT_POINT 0xA0000003
74 #ifndef IsReparseTagValid
75 # define IsReparseTagValid(x) \
76 (!((x)&~IO_REPARSE_TAG_VALID_VALUES)&&((x)>IO_REPARSE_TAG_RESERVED_RANGE))
78 #ifndef IO_REPARSE_TAG_SYMBOLIC_LINK
79 # define IO_REPARSE_TAG_SYMBOLIC_LINK IO_REPARSE_TAG_RESERVED_ZERO
81 #ifndef FILE_SPECIAL_ACCESS
82 # define FILE_SPECIAL_ACCESS (FILE_ANY_ACCESS)
84 #ifndef FSCTL_SET_REPARSE_POINT
85 # define FSCTL_SET_REPARSE_POINT \
86 CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 41, METHOD_BUFFERED, FILE_SPECIAL_ACCESS)
87 # define FSCTL_GET_REPARSE_POINT \
88 CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS)
89 # define FSCTL_DELETE_REPARSE_POINT \
90 CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 43, METHOD_BUFFERED, FILE_SPECIAL_ACCESS)
92 #ifndef INVALID_FILE_ATTRIBUTES
93 #define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
97 * Maximum reparse buffer info size. The max user defined reparse data is
98 * 16KB, plus there's a header.
101 #define MAX_REPARSE_SIZE 17000
104 * Undocumented REPARSE_MOUNTPOINT_HEADER_SIZE structure definition. This is
107 * IMPORTANT: caution when using this structure, since the actual structures
108 * used will want to store a full path in the 'PathBuffer' field, but there
109 * isn't room (there's only a single WCHAR!). Therefore one must artificially
110 * create a larger space of memory and then cast it to this type. We use the
111 * 'DUMMY_REPARSE_BUFFER' struct just below to deal with this problem.
114 #define REPARSE_MOUNTPOINT_HEADER_SIZE 8
115 #ifndef REPARSE_DATA_BUFFER_HEADER_SIZE
116 typedef struct _REPARSE_DATA_BUFFER {
118 WORD ReparseDataLength;
122 WORD SubstituteNameOffset;
123 WORD SubstituteNameLength;
124 WORD PrintNameOffset;
125 WORD PrintNameLength;
128 } SymbolicLinkReparseBuffer;
130 WORD SubstituteNameOffset;
131 WORD SubstituteNameLength;
132 WORD PrintNameOffset;
133 WORD PrintNameLength;
135 } MountPointReparseBuffer;
138 } GenericReparseBuffer;
140 } REPARSE_DATA_BUFFER;
144 REPARSE_DATA_BUFFER dummy;
145 WCHAR dummyBuf[MAX_PATH * 3];
146 } DUMMY_REPARSE_BUFFER;
149 * Other typedefs required by this code.
152 static time_t ToCTime(FILETIME fileTime);
153 static void FromCTime(time_t posixTime, FILETIME *fileTime);
156 * Declarations for local functions defined in this file:
159 static int NativeAccess(const WCHAR *path, int mode);
160 static int NativeDev(const WCHAR *path);
161 static int NativeStat(const WCHAR *path, Tcl_StatBuf *statPtr,
163 static unsigned short NativeStatMode(DWORD attr, int checkLinks,
165 static int NativeIsExec(const WCHAR *path);
166 static int NativeReadReparse(const WCHAR *LinkDirectory,
167 REPARSE_DATA_BUFFER *buffer, DWORD desiredAccess);
168 static int NativeWriteReparse(const WCHAR *LinkDirectory,
169 REPARSE_DATA_BUFFER *buffer);
170 static int NativeMatchType(int isDrive, DWORD attr,
171 const WCHAR *nativeName, Tcl_GlobTypeData *types);
172 static int WinIsDrive(const char *name, size_t nameLen);
173 static int WinIsReserved(const char *path);
174 static Tcl_Obj * WinReadLink(const WCHAR *LinkSource);
175 static Tcl_Obj * WinReadLinkDirectory(const WCHAR *LinkDirectory);
176 static int WinLink(const WCHAR *LinkSource,
177 const WCHAR *LinkTarget, int linkAction);
178 static int WinSymLinkDirectory(const WCHAR *LinkDirectory,
179 const WCHAR *LinkTarget);
180 MODULE_SCOPE TCL_NORETURN void tclWinDebugPanic(const char *format, ...);
183 *--------------------------------------------------------------------
187 * Make a link from source to target.
189 *--------------------------------------------------------------------
194 const WCHAR *linkSourcePath,
195 const WCHAR *linkTargetPath,
198 WCHAR tempFileName[MAX_PATH];
203 * Get the full path referenced by the target.
206 if (!GetFullPathNameW(linkTargetPath, MAX_PATH, tempFileName,
212 TclWinConvertError(GetLastError());
217 * Make sure source file doesn't exist.
220 attr = GetFileAttributesW(linkSourcePath);
221 if (attr != INVALID_FILE_ATTRIBUTES) {
222 Tcl_SetErrno(EEXIST);
227 * Get the full path referenced by the source file/directory.
230 if (!GetFullPathNameW(linkSourcePath, MAX_PATH, tempFileName,
236 TclWinConvertError(GetLastError());
244 attr = GetFileAttributesW(linkTargetPath);
245 if (attr == INVALID_FILE_ATTRIBUTES) {
247 * The target doesn't exist.
250 TclWinConvertError(GetLastError());
251 } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
256 if (linkAction & TCL_CREATE_HARD_LINK) {
257 if (CreateHardLinkW(linkSourcePath, linkTargetPath, NULL)) {
265 TclWinConvertError(GetLastError());
266 } else if (linkAction & TCL_CREATE_SYMBOLIC_LINK) {
267 if (!tclWinProcs.createSymbolicLink) {
269 * Can't symlink files.
271 Tcl_SetErrno(EINVAL);
272 } else if (tclWinProcs.createSymbolicLink(linkSourcePath, linkTargetPath,
273 0x2 /* SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE */)) {
280 TclWinConvertError(GetLastError());
283 Tcl_SetErrno(ENODEV);
287 * We've got a directory. Now check whether what we're trying to do is
291 if (linkAction & TCL_CREATE_SYMBOLIC_LINK) {
292 return WinSymLinkDirectory(linkSourcePath, linkTargetPath);
294 } else if (linkAction & TCL_CREATE_HARD_LINK) {
296 * Can't hard link directories.
299 Tcl_SetErrno(EISDIR);
301 Tcl_SetErrno(ENODEV);
308 *--------------------------------------------------------------------
312 * What does 'LinkSource' point to?
314 *--------------------------------------------------------------------
319 const WCHAR *linkSourcePath)
321 WCHAR tempFileName[MAX_PATH];
326 * Get the full path referenced by the target.
329 if (!GetFullPathNameW(linkSourcePath, MAX_PATH, tempFileName,
335 TclWinConvertError(GetLastError());
340 * Make sure source file does exist.
343 attr = GetFileAttributesW(linkSourcePath);
344 if (attr == INVALID_FILE_ATTRIBUTES) {
346 * The source doesn't exist.
349 TclWinConvertError(GetLastError());
352 } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
354 * It is a file - this is not yet supported.
357 Tcl_SetErrno(ENOTDIR);
361 return WinReadLinkDirectory(linkSourcePath);
365 *--------------------------------------------------------------------
367 * WinSymLinkDirectory --
369 * This routine creates a NTFS junction, using the undocumented
370 * FSCTL_SET_REPARSE_POINT structure Win2K uses for mount points and
373 * Assumption that linkTargetPath is a valid, existing directory.
378 *--------------------------------------------------------------------
383 const WCHAR *linkDirPath,
384 const WCHAR *linkTargetPath)
386 DUMMY_REPARSE_BUFFER dummy;
387 REPARSE_DATA_BUFFER *reparseBuffer = (REPARSE_DATA_BUFFER *) &dummy;
389 WCHAR nativeTarget[MAX_PATH];
393 * Make the native target name.
396 memcpy(nativeTarget, L"\\??\\", 4 * sizeof(WCHAR));
397 memcpy(nativeTarget + 4, linkTargetPath,
398 sizeof(WCHAR) * (1+wcslen((WCHAR *) linkTargetPath)));
399 len = wcslen(nativeTarget);
402 * We must have backslashes only. This is VERY IMPORTANT. If we have any
403 * forward slashes everything appears to work, but the resulting symlink
407 for (loop = nativeTarget; *loop != 0; loop++) {
412 if ((nativeTarget[len-1] == '\\') && (nativeTarget[len-2] != ':')) {
413 nativeTarget[len-1] = 0;
417 * Build the reparse info.
420 memset(reparseBuffer, 0, sizeof(DUMMY_REPARSE_BUFFER));
421 reparseBuffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
422 reparseBuffer->MountPointReparseBuffer.SubstituteNameLength =
423 wcslen(nativeTarget) * sizeof(WCHAR);
424 reparseBuffer->Reserved = 0;
425 reparseBuffer->MountPointReparseBuffer.PrintNameLength = 0;
426 reparseBuffer->MountPointReparseBuffer.PrintNameOffset =
427 reparseBuffer->MountPointReparseBuffer.SubstituteNameLength
429 memcpy(reparseBuffer->MountPointReparseBuffer.PathBuffer, nativeTarget,
431 + reparseBuffer->MountPointReparseBuffer.SubstituteNameLength);
432 reparseBuffer->ReparseDataLength =
433 reparseBuffer->MountPointReparseBuffer.SubstituteNameLength+12;
435 return NativeWriteReparse(linkDirPath, reparseBuffer);
439 *--------------------------------------------------------------------
441 * TclWinSymLinkCopyDirectory --
443 * Copy a Windows NTFS junction. This function assumes that LinkOriginal
444 * exists and is a valid junction point, and that LinkCopy does not
450 *--------------------------------------------------------------------
454 TclWinSymLinkCopyDirectory(
455 const WCHAR *linkOrigPath, /* Existing junction - reparse point */
456 const WCHAR *linkCopyPath) /* Will become a duplicate junction */
458 DUMMY_REPARSE_BUFFER dummy;
459 REPARSE_DATA_BUFFER *reparseBuffer = (REPARSE_DATA_BUFFER *) &dummy;
461 if (NativeReadReparse(linkOrigPath, reparseBuffer, GENERIC_READ)) {
464 return NativeWriteReparse(linkCopyPath, reparseBuffer);
468 *--------------------------------------------------------------------
470 * TclWinSymLinkDelete --
472 * Delete a Windows NTFS junction. Once the junction information is
473 * deleted, the filesystem object becomes an ordinary directory. Unless
474 * 'linkOnly' is given, that directory is also removed.
476 * Assumption that LinkOriginal is a valid, existing junction.
481 *--------------------------------------------------------------------
486 const WCHAR *linkOrigPath,
490 * It is a symbolic link - remove it.
493 DUMMY_REPARSE_BUFFER dummy;
494 REPARSE_DATA_BUFFER *reparseBuffer = (REPARSE_DATA_BUFFER *) &dummy;
496 DWORD returnedLength;
498 memset(reparseBuffer, 0, sizeof(DUMMY_REPARSE_BUFFER));
499 reparseBuffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
500 hFile = CreateFileW(linkOrigPath, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
501 FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
503 if (hFile != INVALID_HANDLE_VALUE) {
504 if (!DeviceIoControl(hFile, FSCTL_DELETE_REPARSE_POINT, reparseBuffer,
505 REPARSE_MOUNTPOINT_HEADER_SIZE,NULL,0,&returnedLength,NULL)) {
507 * Error setting junction.
510 TclWinConvertError(GetLastError());
515 RemoveDirectoryW(linkOrigPath);
524 *--------------------------------------------------------------------
526 * WinReadLinkDirectory --
528 * This routine reads a NTFS junction, using the undocumented
529 * FSCTL_GET_REPARSE_POINT structure Win2K uses for mount points and
532 * Assumption that LinkDirectory is a valid, existing directory.
535 * A Tcl_Obj with refCount of 1 (i.e. owned by the caller), or NULL if
536 * anything went wrong.
538 * In the future we should enhance this to return a path object rather
541 *--------------------------------------------------------------------
544 #if defined (__clang__) || ((__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5))))
545 #pragma GCC diagnostic push
546 #pragma GCC diagnostic ignored "-Warray-bounds"
550 WinReadLinkDirectory(
551 const WCHAR *linkDirPath)
553 int attr, len, offset;
554 DUMMY_REPARSE_BUFFER dummy;
555 REPARSE_DATA_BUFFER *reparseBuffer = (REPARSE_DATA_BUFFER *) &dummy;
560 attr = GetFileAttributesW(linkDirPath);
561 if (!(attr & FILE_ATTRIBUTE_REPARSE_POINT)) {
564 if (NativeReadReparse(linkDirPath, reparseBuffer, 0)) {
568 switch (reparseBuffer->ReparseTag) {
569 case 0x80000000|IO_REPARSE_TAG_SYMBOLIC_LINK:
570 case IO_REPARSE_TAG_SYMBOLIC_LINK:
571 case IO_REPARSE_TAG_MOUNT_POINT:
573 * Certain native path representations on Windows have a special
574 * prefix to indicate that they are to be treated specially. For
575 * example extremely long paths, or symlinks, or volumes mounted
576 * inside directories.
578 * There is an assumption in this code that 'wide' interfaces are
579 * being used (see tclWin32Dll.c), which is true for the only systems
580 * which support reparse tags at present. If that changes in the
581 * future, this code will have to be generalised.
585 if (reparseBuffer->MountPointReparseBuffer.PathBuffer[0] == '\\') {
587 * Check whether this is a mounted volume.
590 if (wcsncmp(reparseBuffer->MountPointReparseBuffer.PathBuffer,
591 L"\\??\\Volume{",11) == 0) {
595 * There is some confusion between \??\ and \\?\ which we have
596 * to fix here. It doesn't seem very well documented.
599 reparseBuffer->MountPointReparseBuffer.PathBuffer[1] = '\\';
602 * Check if a corresponding drive letter exists, and use that
606 drive = TclWinDriveLetterForVolMountPoint(
607 reparseBuffer->MountPointReparseBuffer.PathBuffer);
609 char driveSpec[3] = {
613 driveSpec[0] = drive;
614 retVal = Tcl_NewStringObj(driveSpec,2);
615 Tcl_IncrRefCount(retVal);
620 * This is actually a mounted drive, which doesn't exists as a
621 * DOS drive letter. This means the path isn't actually a
622 * link, although we partially treat it like one ('file type'
623 * will return 'link'), but then the link will actually just
624 * be treated like an ordinary directory. I don't believe any
625 * serious inconsistency will arise from this, but it is
626 * something to be aware of.
630 } else if (wcsncmp(reparseBuffer->MountPointReparseBuffer
631 .PathBuffer, L"\\\\?\\",4) == 0) {
633 * Strip off the prefix.
637 } else if (wcsncmp(reparseBuffer->MountPointReparseBuffer
638 .PathBuffer, L"\\??\\",4) == 0) {
640 * Strip off the prefix.
647 Tcl_WinTCharToUtf((TCHAR *)
648 reparseBuffer->MountPointReparseBuffer.PathBuffer,
649 reparseBuffer->MountPointReparseBuffer
650 .SubstituteNameLength, &ds);
652 copy = Tcl_DStringValue(&ds)+offset;
653 len = Tcl_DStringLength(&ds)-offset;
654 retVal = Tcl_NewStringObj(copy,len);
655 Tcl_IncrRefCount(retVal);
656 Tcl_DStringFree(&ds);
661 Tcl_SetErrno(EINVAL);
665 #if defined (__clang__) || ((__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5))))
666 #pragma GCC diagnostic pop
670 *--------------------------------------------------------------------
672 * NativeReadReparse --
674 * Read the junction/reparse information from a given NTFS directory.
676 * Assumption that linkDirPath is a valid, existing directory.
681 *--------------------------------------------------------------------
686 const WCHAR *linkDirPath, /* The junction to read */
687 REPARSE_DATA_BUFFER *buffer,/* Pointer to buffer. Cannot be NULL */
691 DWORD returnedLength;
693 hFile = CreateFileW(linkDirPath, desiredAccess, FILE_SHARE_READ, NULL,
695 FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
697 if (hFile == INVALID_HANDLE_VALUE) {
699 * Error creating directory.
702 TclWinConvertError(GetLastError());
710 if (!DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, NULL, 0, buffer,
711 sizeof(DUMMY_REPARSE_BUFFER), &returnedLength, NULL)) {
713 * Error setting junction.
716 TclWinConvertError(GetLastError());
722 if (!IsReparseTagValid(buffer->ReparseTag)) {
723 Tcl_SetErrno(EINVAL);
730 *--------------------------------------------------------------------
732 * NativeWriteReparse --
734 * Write the reparse information for a given directory.
736 * Assumption that LinkDirectory does not exist.
738 *--------------------------------------------------------------------
743 const WCHAR *linkDirPath,
744 REPARSE_DATA_BUFFER *buffer)
747 DWORD returnedLength;
750 * Create the directory - it must not already exist.
753 if (CreateDirectoryW(linkDirPath, NULL) == 0) {
755 * Error creating directory.
758 TclWinConvertError(GetLastError());
761 hFile = CreateFileW(linkDirPath, GENERIC_WRITE, 0, NULL,
762 OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT
763 | FILE_FLAG_BACKUP_SEMANTICS, NULL);
764 if (hFile == INVALID_HANDLE_VALUE) {
766 * Error creating directory.
769 TclWinConvertError(GetLastError());
777 if (!DeviceIoControl(hFile, FSCTL_SET_REPARSE_POINT, buffer,
778 (DWORD) buffer->ReparseDataLength + REPARSE_MOUNTPOINT_HEADER_SIZE,
779 NULL, 0, &returnedLength, NULL)) {
781 * Error setting junction.
784 TclWinConvertError(GetLastError());
786 RemoveDirectoryW(linkDirPath);
799 *----------------------------------------------------------------------
801 * tclWinDebugPanic --
803 * Display a message. If a debugger is present, present it directly to
804 * the debugger, otherwise use a MessageBox.
812 *----------------------------------------------------------------------
817 const char *format, ...)
819 #define TCL_MAX_WARN_LEN 1024
821 char buf[TCL_MAX_WARN_LEN * 3];
822 WCHAR msgString[TCL_MAX_WARN_LEN];
824 va_start(argList, format);
825 vsnprintf(buf, sizeof(buf), format, argList);
827 msgString[TCL_MAX_WARN_LEN-1] = '\0';
828 MultiByteToWideChar(CP_UTF8, 0, buf, -1, msgString, TCL_MAX_WARN_LEN);
831 * Truncate MessageBox string if it is too long to not overflow the screen
832 * and cause possible oversized window error.
835 if (msgString[TCL_MAX_WARN_LEN-1] != '\0') {
836 memcpy(msgString + (TCL_MAX_WARN_LEN - 5), L" ...", 5 * sizeof(WCHAR));
838 if (IsDebuggerPresent()) {
839 OutputDebugStringW(msgString);
841 MessageBeep(MB_ICONEXCLAMATION);
842 MessageBoxW(NULL, msgString, L"Fatal Error",
843 MB_ICONSTOP | MB_OK | MB_TASKMODAL | MB_SETFOREGROUND);
845 #if defined(__GNUC__)
847 #elif defined(_WIN64)
849 #elif defined(_MSC_VER) && defined (_M_IX86)
858 *---------------------------------------------------------------------------
860 * TclpFindExecutable --
862 * This function computes the absolute path name of the current
869 * The computed path is stored.
871 *---------------------------------------------------------------------------
876 const char *argv0) /* If NULL, install PanicMessageBox, otherwise
879 WCHAR wName[MAX_PATH];
880 char name[MAX_PATH * 3];
883 * Under Windows we ignore argv0, and return the path for the file used to
884 * create this process. Only if it is NULL, install a new panic handler.
888 Tcl_SetPanicProc(tclWinDebugPanic);
891 GetModuleFileNameW(NULL, wName, sizeof(wName)/sizeof(WCHAR));
892 WideCharToMultiByte(CP_UTF8, 0, wName, -1, name, sizeof(name), NULL, NULL);
893 TclWinNoBackslash(name);
894 TclSetObjNameOfExecutable(Tcl_NewStringObj(name, -1), NULL);
898 *----------------------------------------------------------------------
900 * TclpMatchInDirectory --
902 * This routine is used by the globbing code to search a directory for
903 * all files which match a given pattern.
906 * The return value is a standard Tcl result indicating whether an error
907 * occurred in globbing. Errors are left in interp, good results are
908 * lappended to resultPtr (which must be a valid object).
913 *----------------------------------------------------------------------
917 TclpMatchInDirectory(
918 Tcl_Interp *interp, /* Interpreter to receive errors. */
919 Tcl_Obj *resultPtr, /* List object to lappend results. */
920 Tcl_Obj *pathPtr, /* Contains path to directory to search. */
921 const char *pattern, /* Pattern to match against. */
922 Tcl_GlobTypeData *types) /* Object containing list of acceptable types.
923 * May be NULL. In particular the directory
924 * flag is very important. */
928 if (types != NULL && types->type == TCL_GLOB_TYPE_MOUNT) {
930 * The native filesystem never adds mounts.
936 if (pattern == NULL || (*pattern == '\0')) {
937 Tcl_Obj *norm = Tcl_FSGetNormalizedPath(NULL, pathPtr);
941 * Match a single file directly.
946 WIN32_FILE_ATTRIBUTE_DATA data;
947 const char *str = Tcl_GetStringFromObj(norm,&len);
949 native = Tcl_FSGetNativePath(pathPtr);
951 if (GetFileAttributesExW(native,
952 GetFileExInfoStandard, &data) != TRUE) {
955 attr = data.dwFileAttributes;
957 if (NativeMatchType(WinIsDrive(str,len), attr, native, types)) {
958 Tcl_ListObjAppendElement(interp, resultPtr, pathPtr);
965 WIN32_FIND_DATAW data;
966 const char *dirName; /* UTF-8 dir name, later with pattern
969 int matchSpecialDots;
970 Tcl_DString ds; /* Native encoding of dir, also used
971 * temporarily for other things. */
972 Tcl_DString dsOrig; /* UTF-8 encoding of dir. */
973 Tcl_Obj *fileNamePtr;
977 * Get the normalized path representation (the main thing is we dont
978 * want any '~' sequences).
981 fileNamePtr = Tcl_FSGetNormalizedPath(interp, pathPtr);
982 if (fileNamePtr == NULL) {
987 * Verify that the specified path exists and is actually a directory.
990 native = Tcl_FSGetNativePath(pathPtr);
991 if (native == NULL) {
994 attr = GetFileAttributesW(native);
996 if ((attr == INVALID_FILE_ATTRIBUTES)
997 || ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0)) {
1002 * Build up the directory name for searching, including a trailing
1003 * directory separator.
1006 Tcl_DStringInit(&dsOrig);
1007 dirName = Tcl_GetStringFromObj(fileNamePtr, &dirLength);
1008 Tcl_DStringAppend(&dsOrig, dirName, dirLength);
1010 lastChar = dirName[dirLength -1];
1011 if ((lastChar != '\\') && (lastChar != '/') && (lastChar != ':')) {
1012 TclDStringAppendLiteral(&dsOrig, "/");
1015 dirName = Tcl_DStringValue(&dsOrig);
1018 * We need to check all files in the directory, so we append '*.*' to
1019 * the path, unless the pattern we've been given is rather simple,
1020 * when we can use that instead.
1023 if (strpbrk(pattern, "[]\\") == NULL) {
1025 * The pattern is a simple one containing just '*' and/or '?'.
1026 * This means we can get the OS to help us, by passing it the
1030 dirName = Tcl_DStringAppend(&dsOrig, pattern, -1);
1032 dirName = TclDStringAppendLiteral(&dsOrig, "*.*");
1035 native = (WCHAR *)Tcl_WinUtfToTChar(dirName, -1, &ds);
1036 if ((types == NULL) || (types->type != TCL_GLOB_TYPE_DIR)) {
1037 handle = FindFirstFileW(native, &data);
1040 * We can be more efficient, for pure directory requests.
1043 handle = FindFirstFileExW(native,
1044 FindExInfoStandard, &data,
1045 FindExSearchLimitToDirectories, NULL, 0);
1048 if (handle == INVALID_HANDLE_VALUE) {
1049 DWORD err = GetLastError();
1051 Tcl_DStringFree(&ds);
1052 if (err == ERROR_FILE_NOT_FOUND) {
1054 * We used our 'pattern' above, and matched nothing. This
1055 * means we just return TCL_OK, indicating no results found.
1058 Tcl_DStringFree(&dsOrig);
1062 TclWinConvertError(err);
1063 if (interp != NULL) {
1064 Tcl_SetObjResult(interp, Tcl_ObjPrintf(
1065 "couldn't read directory \"%s\": %s",
1066 Tcl_DStringValue(&dsOrig), Tcl_PosixError(interp)));
1068 Tcl_DStringFree(&dsOrig);
1071 Tcl_DStringFree(&ds);
1074 * We may use this later, so we must restore it to its length
1075 * including the directory delimiter.
1078 Tcl_DStringSetLength(&dsOrig, dirLength);
1081 * Check to see if the pattern should match the special . and
1082 * .. names, referring to the current directory, or the directory
1083 * above. We need a special check for this because paths beginning
1084 * with a dot are not considered hidden on Windows, and so otherwise a
1085 * relative glob like 'glob -join * *' will actually return
1089 if ((pattern[0] == '.')
1090 || ((pattern[0] == '\\') && (pattern[1] == '.'))) {
1091 matchSpecialDots = 1;
1093 matchSpecialDots = 0;
1097 * Now iterate over all of the files in the directory, starting with
1098 * the first one we found.
1102 const char *utfname;
1103 int checkDrive = 0, isDrive;
1105 native = data.cFileName;
1106 attr = data.dwFileAttributes;
1107 utfname = Tcl_WinTCharToUtf((TCHAR *)native, -1, &ds);
1109 if (!matchSpecialDots) {
1111 * If it is exactly '.' or '..' then we ignore it.
1114 if ((utfname[0] == '.') && (utfname[1] == '\0'
1115 || (utfname[1] == '.' && utfname[2] == '\0'))) {
1116 Tcl_DStringFree(&ds);
1119 } else if (utfname[0] == '.' && utfname[1] == '.'
1120 && utfname[2] == '\0') {
1122 * Have to check if this is a drive below, so we can correctly
1123 * match 'hidden' and not hidden files.
1130 * Check to see if the file matches the pattern. Note that we are
1131 * ignoring the case sensitivity flag because Windows doesn't
1132 * honor case even if the volume is case sensitive. If the volume
1133 * also doesn't preserve case, then we previously returned the
1134 * lower case form of the name. This didn't seem quite right since
1135 * there are non-case-preserving volumes that actually return
1136 * mixed case. So now we are returning exactly what we get from
1140 if (Tcl_StringCaseMatch(utfname, pattern, 1)) {
1142 * If the file matches, then we need to process the remainder
1147 const char *fullname = Tcl_DStringAppend(&dsOrig, utfname,
1148 Tcl_DStringLength(&ds));
1150 isDrive = WinIsDrive(fullname, Tcl_DStringLength(&dsOrig));
1151 Tcl_DStringSetLength(&dsOrig, dirLength);
1155 if (NativeMatchType(isDrive, attr, native, types)) {
1156 Tcl_ListObjAppendElement(interp, resultPtr,
1157 TclNewFSPathObj(pathPtr, utfname,
1158 Tcl_DStringLength(&ds)));
1163 * Free ds here to ensure that native is valid above.
1166 Tcl_DStringFree(&ds);
1167 } while (FindNextFileW(handle, &data) == TRUE);
1170 Tcl_DStringFree(&dsOrig);
1176 * Does the given path represent a root volume? We need this special case
1177 * because for NTFS root volumes, the getFileAttributesProc returns a 'hidden'
1178 * attribute when it should not.
1183 const char *name, /* Name (UTF-8) */
1184 size_t len) /* Length of name */
1189 if ((name[len-1] != '.' || name[len-2] != '.')
1190 || (name[len-3] != '/' && name[len-3] != '\\')) {
1192 * We don't have '/..' at the end.
1201 if (name[len] == '/' || name[len] == '\\') {
1222 * Not sure if this is possible, but we pass it on anyway.
1224 } else if (len == 1 && (name[0] == '/' || name[0] == '\\')) {
1226 * Path is pointing to the root volume.
1230 } else if ((name[1] == ':')
1231 && (len == 2 || (name[2] == '/' || name[2] == '\\'))) {
1233 * Path is of the form 'x:' or 'x:/' or 'x:\'
1244 * Does the given path represent a reserved window path name? If not return 0,
1245 * if true, return the number of characters of the path that we actually want
1246 * (not any trailing :).
1251 const char *path) /* Path in UTF-8 */
1253 if ((path[0] == 'c' || path[0] == 'C')
1254 && (path[1] == 'o' || path[1] == 'O')) {
1255 if ((path[2] == 'm' || path[2] == 'M')
1256 && path[3] >= '1' && path[3] <= '9') {
1258 * May have match for 'com[1-9]:?', which is a serial port.
1261 if (path[4] == '\0') {
1263 } else if (path[4] == ':' && path[5] == '\0') {
1266 } else if ((path[2] == 'n' || path[2] == 'N') && path[3] == '\0') {
1268 * Have match for 'con'
1274 } else if ((path[0] == 'l' || path[0] == 'L')
1275 && (path[1] == 'p' || path[1] == 'P')
1276 && (path[2] == 't' || path[2] == 'T')) {
1277 if (path[3] >= '1' && path[3] <= '9') {
1279 * May have match for 'lpt[1-9]:?'
1282 if (path[4] == '\0') {
1284 } else if (path[4] == ':' && path[5] == '\0') {
1289 } else if (!strcasecmp(path, "prn") || !strcasecmp(path, "nul")
1290 || !strcasecmp(path, "aux")) {
1292 * Have match for 'prn', 'nul' or 'aux'.
1301 *----------------------------------------------------------------------
1303 * NativeMatchType --
1305 * This function needs a special case for a path which is a root volume,
1306 * because for NTFS root volumes, the getFileAttributesProc returns a
1307 * 'hidden' attribute when it should not.
1309 * We never make any calls to a 'get attributes' routine here, since we
1310 * have arranged things so that our caller already knows such
1314 * 0 = file doesn't match
1317 *----------------------------------------------------------------------
1322 int isDrive, /* Is this a drive. */
1323 DWORD attr, /* We already know the attributes for the
1325 const WCHAR *nativeName, /* Native path to check. */
1326 Tcl_GlobTypeData *types) /* Type description to match against. */
1329 * 'attr' represents the attributes of the file, but we only want to
1330 * retrieve this info if it is absolutely necessary because it is an
1331 * expensive call. Unfortunately, to deal with hidden files properly, we
1332 * must always retrieve it.
1335 if (types == NULL) {
1337 * If invisible, don't return the file.
1340 return !(attr & FILE_ATTRIBUTE_HIDDEN && !isDrive);
1343 if (attr & FILE_ATTRIBUTE_HIDDEN && !isDrive) {
1348 if ((types->perm == 0) || !(types->perm & TCL_GLOB_PERM_HIDDEN)) {
1356 if (types->perm & TCL_GLOB_PERM_HIDDEN) {
1361 if (types->perm != 0) {
1362 if (((types->perm & TCL_GLOB_PERM_RONLY) &&
1363 !(attr & FILE_ATTRIBUTE_READONLY)) ||
1364 ((types->perm & TCL_GLOB_PERM_R) &&
1365 (0 /* File exists => R_OK on Windows */)) ||
1366 ((types->perm & TCL_GLOB_PERM_W) &&
1367 (attr & FILE_ATTRIBUTE_READONLY)) ||
1368 ((types->perm & TCL_GLOB_PERM_X) &&
1369 (!(attr & FILE_ATTRIBUTE_DIRECTORY)
1370 && !NativeIsExec(nativeName)))) {
1375 if ((types->type & TCL_GLOB_TYPE_DIR)
1376 && (attr & FILE_ATTRIBUTE_DIRECTORY)) {
1378 * Quicker test for directory, which is a common case.
1383 } else if (types->type != 0) {
1384 unsigned short st_mode;
1385 int isExec = NativeIsExec(nativeName);
1387 st_mode = NativeStatMode(attr, 0, isExec);
1390 * In order bcdpfls as in 'find -t'
1393 if (((types->type&TCL_GLOB_TYPE_BLOCK) && S_ISBLK(st_mode)) ||
1394 ((types->type&TCL_GLOB_TYPE_CHAR) && S_ISCHR(st_mode)) ||
1395 ((types->type&TCL_GLOB_TYPE_DIR) && S_ISDIR(st_mode)) ||
1396 ((types->type&TCL_GLOB_TYPE_PIPE) && S_ISFIFO(st_mode)) ||
1398 ((types->type&TCL_GLOB_TYPE_SOCK) && S_ISSOCK(st_mode)) ||
1400 ((types->type&TCL_GLOB_TYPE_FILE) && S_ISREG(st_mode))) {
1402 * Do nothing - this file is ok.
1406 if (types->type & TCL_GLOB_TYPE_LINK) {
1407 st_mode = NativeStatMode(attr, 1, isExec);
1408 if (S_ISLNK(st_mode)) {
1412 #endif /* S_ISLNK */
1420 *----------------------------------------------------------------------
1422 * TclpGetUserHome --
1424 * This function takes the passed in user name and finds the
1425 * corresponding home directory specified in the password file.
1428 * The result is a pointer to a string specifying the user's home
1429 * directory, or NULL if the user's home directory could not be
1430 * determined. Storage for the result string is allocated in bufferPtr;
1431 * the caller must call Tcl_DStringFree() when the result is no longer
1437 *----------------------------------------------------------------------
1442 const char *name, /* User name for desired home directory. */
1443 Tcl_DString *bufferPtr) /* Uninitialized or free DString filled with
1444 * name of user's home directory. */
1446 char *result = NULL;
1452 WCHAR *wName, *wHomeDir, *wDomain;
1454 Tcl_DStringInit(bufferPtr);
1457 domain = Tcl_UtfFindFirst(name, '@');
1458 if (domain == NULL) {
1462 * No domain. Firstly check it's the current user
1465 ptr = TclpGetUserName(&ds);
1466 if (ptr != NULL && strcasecmp(name, ptr) == 0) {
1468 * Try safest and fastest way to get current user home
1471 ptr = TclGetEnv("HOME", &ds);
1473 Tcl_JoinPath(1, &ptr, bufferPtr);
1475 result = Tcl_DStringValue(bufferPtr);
1478 Tcl_DStringFree(&ds);
1480 wName = (WCHAR *)Tcl_WinUtfToTChar(domain + 1, -1, &ds);
1481 rc = NetGetDCName(NULL, wName, (LPBYTE *) &wDomain);
1482 Tcl_DStringFree(&ds);
1483 nameLen = domain - name;
1486 wName = (WCHAR *)Tcl_WinUtfToTChar(name, nameLen, &ds);
1487 while (NetUserGetInfo(wDomain, wName, 1, (LPBYTE *) &uiPtr) != 0) {
1489 * User does not exist; if domain was not specified, try again
1490 * using current domain.
1494 if (domain != NULL) {
1499 * Get current domain
1502 rc = NetGetDCName(NULL, NULL, (LPBYTE *) &wDomain);
1506 domain = INT2PTR(-1); /* repeat once */
1509 DWORD i, size = MAX_PATH;
1511 wHomeDir = uiPtr->usri1_home_dir;
1512 if ((wHomeDir != NULL) && (wHomeDir[0] != '\0')) {
1513 size = lstrlenW(wHomeDir);
1514 Tcl_WinTCharToUtf((TCHAR *)wHomeDir, size*sizeof(WCHAR), bufferPtr);
1516 WCHAR buf[MAX_PATH];
1518 * User exists but has no home dir. Return
1519 * "{GetProfilesDirectory}/<user>".
1522 GetProfilesDirectoryW(buf, &size);
1523 Tcl_WinTCharToUtf((TCHAR *)buf, (size-1)*sizeof(WCHAR), bufferPtr);
1524 Tcl_DStringAppend(bufferPtr, "/", 1);
1525 Tcl_DStringAppend(bufferPtr, name, nameLen);
1527 result = Tcl_DStringValue(bufferPtr);
1530 * Be sure we return normalized path
1533 for (i = 0; i < size; ++i) {
1534 if (result[i] == '\\') {
1538 NetApiBufferFree((void *) uiPtr);
1540 Tcl_DStringFree(&ds);
1542 if (wDomain != NULL) {
1543 NetApiBufferFree((void *) wDomain);
1545 if (result == NULL) {
1547 * Look in the "Password Lists" section of system.ini for the local
1548 * user. There are also entries in that section that begin with a "*"
1549 * character that are used by Windows for other purposes; ignore user
1550 * names beginning with a "*".
1555 if (name[0] != '*') {
1556 if (GetPrivateProfileStringA("Password Lists", name, "", buf,
1557 MAX_PATH, "system.ini") > 0) {
1559 * User exists, but there is no such thing as a home directory
1560 * in system.ini. Return "{Windows drive}:/".
1563 GetWindowsDirectoryA(buf, MAX_PATH);
1564 Tcl_DStringAppend(bufferPtr, buf, 3);
1565 result = Tcl_DStringValue(bufferPtr);
1574 *---------------------------------------------------------------------------
1578 * This function replaces the library version of access(), fixing the
1581 * 1. access() returns that all files have execute permission.
1584 * See access documentation.
1587 * See access documentation.
1589 *---------------------------------------------------------------------------
1594 const WCHAR *nativePath, /* Path of file to access, native encoding. */
1595 int mode) /* Permission setting. */
1599 attr = GetFileAttributesW(nativePath);
1601 if (attr == INVALID_FILE_ATTRIBUTES) {
1603 * File might not exist.
1606 DWORD lasterror = GetLastError();
1607 if (lasterror != ERROR_SHARING_VIOLATION) {
1608 TclWinConvertError(lasterror);
1615 * File exists, nothing else to check.
1622 * If it's not a directory (assume file), do several fast checks:
1625 if (!(attr & FILE_ATTRIBUTE_DIRECTORY)) {
1627 * If the attributes say this is not writable at all. The file is a
1628 * regular file (i.e., not a directory), then the file is not
1629 * writable, full stop. For directories, the read-only bit is
1630 * (mostly) ignored by Windows, so we can't ascertain anything about
1631 * directory access from the attrib data. However, if we have the
1632 * advanced 'getFileSecurityProc', then more robust ACL checks will be
1636 if ((mode & W_OK) && (attr & FILE_ATTRIBUTE_READONLY)) {
1637 Tcl_SetErrno(EACCES);
1642 * If doesn't have the correct extension, it can't be executable
1645 if ((mode & X_OK) && !NativeIsExec(nativePath)) {
1646 Tcl_SetErrno(EACCES);
1651 * Special case for read/write/executable check on file
1654 if ((mode & (R_OK|W_OK|X_OK)) && !(mode & ~(R_OK|W_OK|X_OK))) {
1659 mask |= GENERIC_READ;
1662 mask |= GENERIC_WRITE;
1665 mask |= GENERIC_EXECUTE;
1668 hFile = CreateFileW(nativePath, mask,
1669 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
1670 NULL, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, NULL);
1671 if (hFile != INVALID_HANDLE_VALUE) {
1677 * Fast exit if access was denied
1680 if (GetLastError() == ERROR_ACCESS_DENIED) {
1681 Tcl_SetErrno(EACCES);
1687 * We cannnot verify the access fast, check it below using security
1693 * It looks as if the permissions are ok, but if we are on NT, 2000 or XP,
1694 * we have a more complex permissions structure so we try to check that.
1695 * The code below is remarkably complex for such a simple thing as finding
1696 * what permissions the OS has set for a file.
1700 SECURITY_DESCRIPTOR *sdPtr = NULL;
1704 SID_IDENTIFIER_AUTHORITY samba_unmapped = {{0, 0, 0, 0, 0, 22}};
1705 GENERIC_MAPPING genMap;
1706 HANDLE hToken = NULL;
1707 DWORD desiredAccess = 0, grantedAccess = 0;
1708 BOOL accessYesNo = FALSE;
1709 PRIVILEGE_SET privSet;
1710 DWORD privSetSize = sizeof(PRIVILEGE_SET);
1714 * First find out how big the buffer needs to be.
1718 GetFileSecurityW(nativePath,
1719 OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION
1720 | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
1724 * Should have failed with ERROR_INSUFFICIENT_BUFFER
1727 error = GetLastError();
1728 if (error != ERROR_INSUFFICIENT_BUFFER) {
1730 * Most likely case is ERROR_ACCESS_DENIED, which we will convert
1731 * to EACCES - just what we want!
1734 TclWinConvertError((DWORD) error);
1739 * Now size contains the size of buffer needed.
1742 sdPtr = (SECURITY_DESCRIPTOR *) HeapAlloc(GetProcessHeap(), 0, size);
1744 if (sdPtr == NULL) {
1749 * Call GetFileSecurityW() for real.
1752 if (!GetFileSecurityW(nativePath,
1753 OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION
1754 | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
1755 sdPtr, size, &size)) {
1757 * Error getting owner SD
1764 * As of Samba 3.0.23 (10-Jul-2006), unmapped users and groups are
1765 * assigned to SID domains S-1-22-1 and S-1-22-2, where "22" is the
1766 * top-level authority. If the file owner and group is unmapped then
1767 * the ACL access check below will only test against world access,
1768 * which is likely to be more restrictive than the actual access
1769 * restrictions. Since the ACL tests are more likely wrong than
1770 * right, skip them. Moreover, the unix owner access permissions are
1771 * usually mapped to the Windows attributes, so if the user is the
1772 * file owner then the attrib checks above are correct (as far as they
1776 if(!GetSecurityDescriptorOwner(sdPtr,&pSid,&SidDefaulted) ||
1777 memcmp(GetSidIdentifierAuthority(pSid),&samba_unmapped,
1778 sizeof(SID_IDENTIFIER_AUTHORITY))==0) {
1779 HeapFree(GetProcessHeap(), 0, sdPtr);
1780 return 0; /* Attrib tests say access allowed. */
1784 * Perform security impersonation of the user and open the resulting
1788 if (!ImpersonateSelf(SecurityImpersonation)) {
1790 * Unable to perform security impersonation.
1795 if (!OpenThreadToken(GetCurrentThread(),
1796 TOKEN_DUPLICATE | TOKEN_QUERY, FALSE, &hToken)) {
1798 * Unable to get current thread's token.
1807 * Setup desiredAccess according to the access priveleges we are
1812 desiredAccess |= FILE_GENERIC_READ;
1815 desiredAccess |= FILE_GENERIC_WRITE;
1818 desiredAccess |= FILE_GENERIC_EXECUTE;
1821 memset(&genMap, 0x0, sizeof(GENERIC_MAPPING));
1822 genMap.GenericRead = FILE_GENERIC_READ;
1823 genMap.GenericWrite = FILE_GENERIC_WRITE;
1824 genMap.GenericExecute = FILE_GENERIC_EXECUTE;
1825 genMap.GenericAll = FILE_ALL_ACCESS;
1828 * Perform access check using the token.
1831 if (!AccessCheck(sdPtr, hToken, desiredAccess,
1832 &genMap, &privSet, &privSetSize, &grantedAccess,
1835 * Unable to perform access check.
1839 TclWinConvertError(GetLastError());
1840 if (sdPtr != NULL) {
1841 HeapFree(GetProcessHeap(), 0, sdPtr);
1843 if (hToken != NULL) {
1844 CloseHandle(hToken);
1853 HeapFree(GetProcessHeap(), 0, sdPtr);
1854 CloseHandle(hToken);
1856 Tcl_SetErrno(EACCES);
1865 *----------------------------------------------------------------------
1869 * Determines if a path is executable. On windows this is simply defined
1870 * by whether the path ends in a standard executable extension.
1873 * 1 = executable, 0 = not.
1875 *----------------------------------------------------------------------
1882 size_t len = wcslen(path);
1888 if (path[len-4] != '.') {
1893 if ((_wcsicmp(path, L"exe") == 0)
1894 || (_wcsicmp(path, L"com") == 0)
1895 || (_wcsicmp(path, L"cmd") == 0)
1896 || (_wcsicmp(path, L"bat") == 0)) {
1903 *----------------------------------------------------------------------
1907 * This function replaces the library version of chdir().
1910 * See chdir() documentation.
1913 * See chdir() documentation.
1915 *----------------------------------------------------------------------
1920 Tcl_Obj *pathPtr) /* Path to new working directory. */
1923 const WCHAR *nativePath;
1925 nativePath = Tcl_FSGetNativePath(pathPtr);
1930 result = SetCurrentDirectoryW(nativePath);
1933 TclWinConvertError(GetLastError());
1940 *----------------------------------------------------------------------
1944 * This function replaces the library version of getcwd(). (Obsolete
1945 * function, only retained for old extensions which may call it
1949 * The result is a pointer to a string specifying the current directory,
1950 * or NULL if the current directory could not be determined. If NULL is
1951 * returned, an error message is left in the interp's result. Storage for
1952 * the result string is allocated in bufferPtr; the caller must call
1953 * Tcl_DStringFree() when the result is no longer needed.
1958 *----------------------------------------------------------------------
1963 Tcl_Interp *interp, /* If non-NULL, used for error reporting. */
1964 Tcl_DString *bufferPtr) /* Uninitialized or free DString filled with
1965 * name of current directory. */
1967 WCHAR buffer[MAX_PATH];
1971 if (GetCurrentDirectoryW(MAX_PATH, buffer) == 0) {
1972 TclWinConvertError(GetLastError());
1973 if (interp != NULL) {
1974 Tcl_SetObjResult(interp, Tcl_ObjPrintf(
1975 "error getting working directory name: %s",
1976 Tcl_PosixError(interp)));
1982 * Watch for the weird Windows c:\\UNC syntax.
1985 native = (WCHAR *) buffer;
1986 if ((native[0] != '\0') && (native[1] == ':')
1987 && (native[2] == '\\') && (native[3] == '\\')) {
1990 Tcl_WinTCharToUtf((TCHAR *) native, -1, bufferPtr);
1993 * Convert to forward slashes for easier use in scripts.
1996 for (p = Tcl_DStringValue(bufferPtr); *p != '\0'; p++) {
2001 return Tcl_DStringValue(bufferPtr);
2006 Tcl_Obj *pathPtr, /* Path of file to stat. */
2007 Tcl_StatBuf *statPtr) /* Filled with results of stat call. */
2010 * Ensure correct file sizes by forcing the OS to write any pending data
2011 * to disk. This is done only for channels which are dirty, i.e. have been
2012 * written to since the last flush here.
2015 TclWinFlushDirtyChannels();
2017 return NativeStat(Tcl_FSGetNativePath(pathPtr), statPtr, 0);
2021 *----------------------------------------------------------------------
2025 * This function replaces the library version of stat(), fixing the
2028 * 1. stat("c:") returns an error.
2029 * 2. Borland stat() return time in GMT instead of localtime.
2030 * 3. stat("\\server\mount") would return error.
2031 * 4. Accepts slashes or backslashes.
2032 * 5. st_dev and st_rdev were wrong for UNC paths.
2035 * See stat documentation.
2038 * See stat documentation.
2040 *----------------------------------------------------------------------
2045 const WCHAR *nativePath, /* Path of file to stat */
2046 Tcl_StatBuf *statPtr, /* Filled with results of stat call. */
2047 int checkLinks) /* If non-zero, behave like 'lstat' */
2051 unsigned short mode;
2052 unsigned int inode = 0;
2054 DWORD fileType = FILE_TYPE_UNKNOWN;
2057 * If we can use 'createFile' on this, then we can use the resulting
2058 * fileHandle to read more information (nlink, ino) than we can get from
2059 * other attributes reading APIs. If not, then we try to fall back on the
2060 * 'getFileAttributesExProc', and if that isn't available, then on even
2063 * Special consideration must be given to Windows hardcoded names like
2064 * CON, NULL, COM1, LPT1 etc. For these, we still need to do the
2065 * CreateFile as some may not exist (e.g. there is no CON in wish by
2066 * default). However the subsequent GetFileInformationByHandle will
2067 * fail. We do a WinIsReserved to see if it is one of the special names,
2068 * and if successful, mock up a BY_HANDLE_FILE_INFORMATION structure.
2071 fileHandle = CreateFileW(nativePath, GENERIC_READ,
2072 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
2073 NULL, OPEN_EXISTING,
2074 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
2076 if (fileHandle != INVALID_HANDLE_VALUE) {
2077 BY_HANDLE_FILE_INFORMATION data;
2079 if (GetFileInformationByHandle(fileHandle,&data) != TRUE) {
2080 fileType = GetFileType(fileHandle);
2081 CloseHandle(fileHandle);
2082 if (fileType != FILE_TYPE_CHAR && fileType != FILE_TYPE_DISK) {
2083 Tcl_SetErrno(ENOENT);
2088 * Mock up the expected structure
2091 memset(&data, 0, sizeof(data));
2092 statPtr->st_atime = 0;
2093 statPtr->st_mtime = 0;
2094 statPtr->st_ctime = 0;
2096 CloseHandle(fileHandle);
2097 statPtr->st_atime = ToCTime(data.ftLastAccessTime);
2098 statPtr->st_mtime = ToCTime(data.ftLastWriteTime);
2099 statPtr->st_ctime = ToCTime(data.ftCreationTime);
2101 attr = data.dwFileAttributes;
2102 statPtr->st_size = ((Tcl_WideInt) data.nFileSizeLow) |
2103 (((Tcl_WideInt) data.nFileSizeHigh) << 32);
2106 * On Unix, for directories, nlink apparently depends on the number of
2107 * files in the directory. We could calculate that, but it would be a
2108 * bit of a performance penalty, I think. Hence we just use what
2109 * Windows gives us, which is the same as Unix for files, at least.
2112 nlink = data.nNumberOfLinks;
2115 * Unfortunately our stat definition's inode field (unsigned short)
2116 * will throw away most of the precision we have here, which means we
2117 * can't rely on inode as a unique identifier of a file. We'd really
2118 * like to do something like how we handle 'st_size'.
2121 inode = data.nFileIndexHigh | data.nFileIndexLow;
2124 * Fall back on the less capable routines. This means no nlink or ino.
2127 WIN32_FILE_ATTRIBUTE_DATA data;
2129 if (GetFileAttributesExW(nativePath,
2130 GetFileExInfoStandard, &data) != TRUE) {
2132 WIN32_FIND_DATAW ffd;
2133 DWORD lasterror = GetLastError();
2135 if (lasterror != ERROR_SHARING_VIOLATION) {
2136 TclWinConvertError(lasterror);
2139 hFind = FindFirstFileW(nativePath, &ffd);
2140 if (hFind == INVALID_HANDLE_VALUE) {
2141 TclWinConvertError(GetLastError());
2144 memcpy(&data, &ffd, sizeof(data));
2148 attr = data.dwFileAttributes;
2150 statPtr->st_size = ((Tcl_WideInt) data.nFileSizeLow) |
2151 (((Tcl_WideInt) data.nFileSizeHigh) << 32);
2152 statPtr->st_atime = ToCTime(data.ftLastAccessTime);
2153 statPtr->st_mtime = ToCTime(data.ftLastWriteTime);
2154 statPtr->st_ctime = ToCTime(data.ftCreationTime);
2157 dev = NativeDev(nativePath);
2158 mode = NativeStatMode(attr, checkLinks, NativeIsExec(nativePath));
2159 if (fileType == FILE_TYPE_CHAR) {
2162 } else if (fileType == FILE_TYPE_DISK) {
2167 statPtr->st_dev = (dev_t) dev;
2168 statPtr->st_ino = inode;
2169 statPtr->st_mode = mode;
2170 statPtr->st_nlink = nlink;
2171 statPtr->st_uid = 0;
2172 statPtr->st_gid = 0;
2173 statPtr->st_rdev = (dev_t) dev;
2178 *----------------------------------------------------------------------
2182 * Calculate just the 'st_dev' field of a 'stat' structure.
2184 *----------------------------------------------------------------------
2189 const WCHAR *nativePath) /* Full path of file to stat */
2193 WCHAR nativeFullPath[MAX_PATH];
2195 const char *fullPath;
2197 GetFullPathNameW(nativePath, MAX_PATH, nativeFullPath, &nativePart);
2198 fullPath = Tcl_WinTCharToUtf((TCHAR *)nativeFullPath, -1, &ds);
2200 if ((fullPath[0] == '\\') && (fullPath[1] == '\\')) {
2203 const WCHAR *nativeVol;
2204 Tcl_DString volString;
2206 p = strchr(fullPath + 2, '\\');
2207 p = strchr(p + 1, '\\');
2210 * Add terminating backslash to fullpath or GetVolumeInformation()
2214 fullPath = TclDStringAppendLiteral(&ds, "\\");
2215 p = fullPath + Tcl_DStringLength(&ds);
2219 nativeVol = (WCHAR *)Tcl_WinUtfToTChar(fullPath, p - fullPath, &volString);
2221 GetVolumeInformationW(nativeVol, NULL, 0, &dw, NULL, NULL, NULL, 0);
2224 * GetFullPathNameW() turns special devices like "NUL" into "\\.\NUL",
2225 * but GetVolumeInformationW() returns failure for "\\.\NUL". This will
2226 * cause "NUL" to get a drive number of -1, which makes about as much
2227 * sense as anything since the special devices don't live on any
2232 Tcl_DStringFree(&volString);
2233 } else if ((fullPath[0] != '\0') && (fullPath[1] == ':')) {
2234 dev = Tcl_UniCharToLower(fullPath[0]) - 'a';
2238 Tcl_DStringFree(&ds);
2244 *----------------------------------------------------------------------
2248 * Calculate just the 'st_mode' field of a 'stat' structure.
2250 * In many places we don't need the full stat structure, and it's much
2251 * faster just to calculate these pieces, if that's all we need.
2253 *----------------------------------------------------------------------
2256 static unsigned short
2264 if (checkLinks && (attr & FILE_ATTRIBUTE_REPARSE_POINT)) {
2271 mode = (attr & FILE_ATTRIBUTE_DIRECTORY) ? S_IFDIR|S_IEXEC : S_IFREG;
2273 mode |= (attr & FILE_ATTRIBUTE_READONLY) ? S_IREAD : S_IREAD|S_IWRITE;
2279 * Propagate the S_IREAD, S_IWRITE, S_IEXEC bits to the group and other
2283 mode |= (mode & (S_IREAD|S_IWRITE|S_IEXEC)) >> 3;
2284 mode |= (mode & (S_IREAD|S_IWRITE|S_IEXEC)) >> 6;
2285 return (unsigned short) mode;
2289 *------------------------------------------------------------------------
2293 * Converts a Windows FILETIME to a time_t in UTC.
2296 * Returns the count of seconds from the Posix epoch.
2298 *------------------------------------------------------------------------
2303 FILETIME fileTime) /* UTC time */
2305 LARGE_INTEGER convertedTime;
2307 convertedTime.LowPart = fileTime.dwLowDateTime;
2308 convertedTime.HighPart = (LONG) fileTime.dwHighDateTime;
2310 return (time_t) ((convertedTime.QuadPart -
2311 (Tcl_WideInt) POSIX_EPOCH_AS_FILETIME) / (Tcl_WideInt) 10000000);
2315 *------------------------------------------------------------------------
2319 * Converts a time_t to a Windows FILETIME
2322 * Returns the count of 100-ns ticks seconds from the Windows epoch.
2324 *------------------------------------------------------------------------
2330 FILETIME *fileTime) /* UTC Time */
2332 LARGE_INTEGER convertedTime;
2334 convertedTime.QuadPart = ((LONGLONG) posixTime) * 10000000
2335 + POSIX_EPOCH_AS_FILETIME;
2336 fileTime->dwLowDateTime = convertedTime.LowPart;
2337 fileTime->dwHighDateTime = convertedTime.HighPart;
2341 *---------------------------------------------------------------------------
2343 * TclpGetNativeCwd --
2345 * This function replaces the library version of getcwd().
2348 * The input and output are filesystem paths in native form. The result
2349 * is either the given clientData, if the working directory hasn't
2350 * changed, or a new clientData (owned by our caller), giving the new
2351 * native path, or NULL if the current directory could not be determined.
2352 * If NULL is returned, the caller can examine the standard posix error
2353 * codes to determine the cause of the problem.
2358 *----------------------------------------------------------------------
2363 ClientData clientData)
2365 WCHAR buffer[MAX_PATH];
2367 if (GetCurrentDirectoryW(MAX_PATH, buffer) == 0) {
2368 TclWinConvertError(GetLastError());
2372 if (clientData != NULL) {
2373 if (wcscmp((const WCHAR *) clientData, buffer) == 0) {
2378 return TclNativeDupInternalRep(buffer);
2386 return NativeAccess(Tcl_FSGetNativePath(pathPtr), mode);
2392 Tcl_StatBuf *statPtr)
2395 * Ensure correct file sizes by forcing the OS to write any pending data
2396 * to disk. This is done only for channels which are dirty, i.e. have been
2397 * written to since the last flush here.
2400 TclWinFlushDirtyChannels();
2402 return NativeStat(Tcl_FSGetNativePath(pathPtr), statPtr, 1);
2412 if (toPtr != NULL) {
2414 const WCHAR *LinkTarget;
2415 const WCHAR *LinkSource = Tcl_FSGetNativePath(pathPtr);
2416 Tcl_Obj *normalizedToPtr = Tcl_FSGetNormalizedPath(NULL, toPtr);
2418 if (normalizedToPtr == NULL) {
2422 LinkTarget = Tcl_FSGetNativePath(normalizedToPtr);
2424 if (LinkSource == NULL || LinkTarget == NULL) {
2427 res = WinLink(LinkSource, LinkTarget, linkAction);
2434 const WCHAR *LinkSource = Tcl_FSGetNativePath(pathPtr);
2436 if (LinkSource == NULL) {
2439 return WinReadLink(LinkSource);
2442 #endif /* S_IFLNK */
2445 *---------------------------------------------------------------------------
2447 * TclpFilesystemPathType --
2449 * This function is part of the native filesystem support, and returns
2450 * the path type of the given path. Returns NTFS or FAT or whatever is
2451 * returned by the 'volume information' proc.
2459 *---------------------------------------------------------------------------
2463 TclpFilesystemPathType(
2466 #define VOL_BUF_SIZE 32
2468 WCHAR volType[VOL_BUF_SIZE];
2469 char *firstSeparator;
2471 Tcl_Obj *normPath = Tcl_FSGetNormalizedPath(NULL, pathPtr);
2473 if (normPath == NULL) {
2476 path = Tcl_GetString(normPath);
2481 firstSeparator = strchr(path, '/');
2482 if (firstSeparator == NULL) {
2483 found = GetVolumeInformationW(Tcl_FSGetNativePath(pathPtr),
2484 NULL, 0, NULL, NULL, NULL, volType, VOL_BUF_SIZE);
2486 Tcl_Obj *driveName = Tcl_NewStringObj(path, firstSeparator - path+1);
2488 Tcl_IncrRefCount(driveName);
2489 found = GetVolumeInformationW(Tcl_FSGetNativePath(driveName),
2490 NULL, 0, NULL, NULL, NULL, volType, VOL_BUF_SIZE);
2491 Tcl_DecrRefCount(driveName);
2499 Tcl_WinTCharToUtf((TCHAR *)volType, -1, &ds);
2500 return TclDStringToObj(&ds);
2506 * This define can be turned on to experiment with a different way of
2507 * normalizing paths (using a different Windows API). Unfortunately the new
2508 * path seems to take almost exactly the same amount of time as the old path!
2509 * The primary time taken by normalization is in
2510 * GetFileAttributesEx/FindFirstFile or GetFileAttributesEx/GetLongPathName.
2511 * Conversion to/from native is not a significant factor at all.
2513 * Also, since we have to check for symbolic links (reparse points) then we
2514 * have to call GetFileAttributes on each path segment anyway, so there's no
2515 * benefit to doing anything clever there.
2518 /* #define TclNORM_LONG_PATH */
2521 *---------------------------------------------------------------------------
2523 * TclpObjNormalizePath --
2525 * This function scans through a path specification and replaces it, in
2526 * place, with a normalized version. This means using the 'longname', and
2527 * expanding any symbolic links contained within the path.
2530 * The new 'nextCheckpoint' value, giving as far as we could understand
2534 * The pathPtr string, which must contain a valid path, is possibly
2535 * modified in place.
2537 *---------------------------------------------------------------------------
2541 TclpObjNormalizePath(
2543 Tcl_Obj *pathPtr, /* An unshared object containing the path to
2545 int nextCheckpoint) /* offset to start at in pathPtr */
2547 char *lastValidPathEnd = NULL;
2548 Tcl_DString dsNorm; /* This will hold the normalized string. */
2549 char *path, *currentPathEndPosition;
2550 Tcl_Obj *temp = NULL;
2552 Tcl_DString ds; /* Some workspace. */
2554 Tcl_DStringInit(&dsNorm);
2555 path = Tcl_GetString(pathPtr);
2557 currentPathEndPosition = path + nextCheckpoint;
2558 if (*currentPathEndPosition == '/') {
2559 currentPathEndPosition++;
2562 char cur = *currentPathEndPosition;
2564 if ((cur=='/' || cur==0) && (path != currentPathEndPosition)) {
2566 * Reached directory separator, or end of string.
2569 WIN32_FILE_ATTRIBUTE_DATA data;
2570 const WCHAR *nativePath = (WCHAR *)Tcl_WinUtfToTChar(path,
2571 currentPathEndPosition - path, &ds);
2573 if (GetFileAttributesExW(nativePath,
2574 GetFileExInfoStandard, &data) != TRUE) {
2576 * File doesn't exist.
2580 int len = WinIsReserved(path);
2584 * Actually it does exist - COM1, etc.
2589 for (i=0 ; i<len ; i++) {
2590 WCHAR wc = ((WCHAR *) nativePath)[i];
2594 ((WCHAR *) nativePath)[i] = wc;
2597 Tcl_DStringAppend(&dsNorm,
2598 (const char *)nativePath,
2599 (int)(sizeof(WCHAR) * len));
2600 lastValidPathEnd = currentPathEndPosition;
2601 } else if (nextCheckpoint == 0) {
2603 * Path starts with a drive designation that's not
2604 * actually on the system. We still must normalize up
2605 * past the first separator. [Bug 3603434]
2608 currentPathEndPosition++;
2611 Tcl_DStringFree(&ds);
2616 * File 'nativePath' does exist if we get here. We now want to
2617 * check if it is a symlink and otherwise continue with the
2622 * Check for symlinks, except at last component of path (we don't
2623 * follow final symlinks). Also a drive (C:/) for example, may
2624 * sometimes have the reparse flag set for some reason I don't
2625 * understand. We therefore don't perform this check for drives.
2628 if (cur != 0 && !isDrive &&
2629 data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT){
2630 Tcl_Obj *to = WinReadLinkDirectory(nativePath);
2634 * Read the reparse point ok. Now, reparse points need not
2635 * be normalized, otherwise we could use:
2637 * Tcl_GetStringFromObj(to, &pathLen);
2638 * nextCheckpoint = pathLen;
2640 * So, instead we have to start from the beginning.
2644 Tcl_AppendToObj(to, currentPathEndPosition, -1);
2647 * Convert link to forward slashes.
2650 for (path = Tcl_GetString(to); *path != 0; path++) {
2651 if (*path == '\\') {
2655 path = Tcl_GetString(to);
2656 currentPathEndPosition = path + nextCheckpoint;
2658 Tcl_DecrRefCount(temp);
2663 * Reset variables so we can restart normalization.
2667 Tcl_DStringFree(&dsNorm);
2668 Tcl_DStringFree(&ds);
2673 #ifndef TclNORM_LONG_PATH
2675 * Now we convert the tail of the current path to its 'long form',
2676 * and append it to 'dsNorm' which holds the current normalized
2681 WCHAR drive = ((WCHAR *) nativePath)[0];
2684 drive -= ('a' - 'A');
2685 ((WCHAR *) nativePath)[0] = drive;
2687 Tcl_DStringAppend(&dsNorm, (const char *)nativePath,
2688 Tcl_DStringLength(&ds));
2690 char *checkDots = NULL;
2692 if (lastValidPathEnd[1] == '.') {
2693 checkDots = lastValidPathEnd + 1;
2694 while (checkDots < currentPathEndPosition) {
2695 if (*checkDots != '.') {
2702 if (checkDots != NULL) {
2703 int dotLen = currentPathEndPosition-lastValidPathEnd;
2706 * Path is just dots. We shouldn't really ever see a path
2707 * like that. However, to be nice we at least don't mangle
2708 * the path - we just add the dots as a path segment and
2712 Tcl_DStringAppend(&dsNorm, ((const char *)nativePath)
2713 + Tcl_DStringLength(&ds)
2714 - (dotLen * sizeof(WCHAR)),
2715 dotLen * sizeof(WCHAR));
2721 WIN32_FIND_DATAW fData;
2724 handle = FindFirstFileW((WCHAR *) nativePath, &fData);
2725 if (handle == INVALID_HANDLE_VALUE) {
2727 * This is usually the '/' in 'c:/' at end of string.
2730 Tcl_DStringAppend(&dsNorm, (const char *) L"/",
2735 if (fData.cFileName[0] != '\0') {
2736 nativeName = fData.cFileName;
2738 nativeName = fData.cAlternateFileName;
2741 Tcl_DStringAppend(&dsNorm, (const char *) L"/",
2743 Tcl_DStringAppend(&dsNorm,
2744 (const char *) nativeName,
2745 (int) (wcslen(nativeName)*sizeof(WCHAR)));
2749 #endif /* !TclNORM_LONG_PATH */
2750 Tcl_DStringFree(&ds);
2751 lastValidPathEnd = currentPathEndPosition;
2757 * If we get here, we've got past one directory delimiter, so we
2758 * know it is no longer a drive.
2763 currentPathEndPosition++;
2765 #ifdef TclNORM_LONG_PATH
2767 * Convert the entire known path to long form.
2771 WCHAR wpath[MAX_PATH];
2772 const WCHAR *nativePath =
2773 Tcl_WinUtfToTChar(path, lastValidPathEnd - path, &ds);
2774 DWORD wpathlen = GetLongPathNameProc(nativePath,
2775 (WCHAR *) wpath, MAX_PATH);
2778 * We have to make the drive letter uppercase.
2781 if (wpath[0] >= 'a') {
2782 wpath[0] -= ('a' - 'A');
2784 Tcl_DStringAppend(&dsNorm, (const char *) wpath,
2785 wpathlen * sizeof(WCHAR));
2786 Tcl_DStringFree(&ds);
2788 #endif /* TclNORM_LONG_PATH */
2792 * Common code path for all Windows platforms.
2795 nextCheckpoint = currentPathEndPosition - path;
2796 if (lastValidPathEnd != NULL) {
2798 * Concatenate the normalized string in dsNorm with the tail of the
2799 * path which we didn't recognise. The string in dsNorm is in the
2800 * native encoding, so we have to convert it to Utf.
2803 Tcl_WinTCharToUtf((TCHAR *) Tcl_DStringValue(&dsNorm),
2804 Tcl_DStringLength(&dsNorm), &ds);
2805 nextCheckpoint = Tcl_DStringLength(&ds);
2806 if (*lastValidPathEnd != 0) {
2808 * Not the end of the string.
2812 Tcl_Obj *tmpPathPtr;
2814 tmpPathPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds),
2816 Tcl_AppendToObj(tmpPathPtr, lastValidPathEnd, -1);
2817 path = Tcl_GetStringFromObj(tmpPathPtr, &len);
2818 Tcl_SetStringObj(pathPtr, path, len);
2819 Tcl_DecrRefCount(tmpPathPtr);
2822 * End of string was reached above.
2825 Tcl_SetStringObj(pathPtr, Tcl_DStringValue(&ds), nextCheckpoint);
2827 Tcl_DStringFree(&ds);
2829 Tcl_DStringFree(&dsNorm);
2832 * This must be done after we are totally finished with 'path' as we are
2833 * sharing the same underlying string.
2837 Tcl_DecrRefCount(temp);
2840 return nextCheckpoint;
2844 *---------------------------------------------------------------------------
2846 * TclWinVolumeRelativeNormalize --
2848 * Only Windows has volume-relative paths. These paths are rather rare,
2849 * but it is nice if Tcl can handle them. It is much better if we can
2850 * handle them here, rather than in the native fs code, because we really
2851 * need to have a real absolute path just below.
2853 * We do not let this block compile on non-Windows platforms because the
2854 * test suite's manual forcing of tclPlatform can otherwise cause this
2855 * code path to be executed, causing various errors because
2856 * volume-relative paths really do not exist.
2859 * A valid normalized path.
2864 *---------------------------------------------------------------------------
2868 TclWinVolumeRelativeNormalize(
2871 Tcl_Obj **useThisCwdPtr)
2873 Tcl_Obj *absolutePath, *useThisCwd;
2875 useThisCwd = Tcl_FSGetCwd(interp);
2876 if (useThisCwd == NULL) {
2880 if (path[0] == '/') {
2882 * Path of form /foo/bar which is a path in the root directory of the
2886 const char *drive = Tcl_GetString(useThisCwd);
2888 absolutePath = Tcl_NewStringObj(drive,2);
2889 Tcl_AppendToObj(absolutePath, path, -1);
2890 Tcl_IncrRefCount(absolutePath);
2893 * We have a refCount on the cwd.
2897 * Path of form C:foo/bar, but this only makes sense if the cwd is
2903 Tcl_GetStringFromObj(useThisCwd, &cwdLen);
2904 char drive_cur = path[0];
2906 if (drive_cur >= 'a') {
2907 drive_cur -= ('a' - 'A');
2909 if (drive[0] == drive_cur) {
2910 absolutePath = Tcl_DuplicateObj(useThisCwd);
2913 * We have a refCount on the cwd, which we will release later.
2916 if (drive[cwdLen-1] != '/' && (path[2] != '\0')) {
2918 * Only add a trailing '/' if needed, which is if there isn't
2919 * one already, and if we are going to be adding some more
2923 Tcl_AppendToObj(absolutePath, "/", 1);
2926 Tcl_DecrRefCount(useThisCwd);
2930 * The path is not in the current drive, but is volume-relative.
2931 * The way Tcl 8.3 handles this is that it treats such a path as
2932 * relative to the root of the drive. We therefore behave the same
2933 * here. This behaviour is, however, different to that of the
2934 * windows command-line. If we want to fix this at some point in
2935 * the future (at the expense of a behaviour change to Tcl), we
2936 * could use the '_dgetdcwd' Win32 API to get the drive's cwd.
2939 absolutePath = Tcl_NewStringObj(path, 2);
2940 Tcl_AppendToObj(absolutePath, "/", 1);
2942 Tcl_IncrRefCount(absolutePath);
2943 Tcl_AppendToObj(absolutePath, path+2, -1);
2945 *useThisCwdPtr = useThisCwd;
2946 return absolutePath;
2950 *---------------------------------------------------------------------------
2952 * TclpNativeToNormalized --
2954 * Convert native format to a normalized path object, with refCount of
2957 * Currently assumes all native paths are actually normalized already, so
2958 * if the path given is not normalized this will actually just convert to
2959 * a valid string path, but not necessarily a normalized one.
2962 * A valid normalized path.
2967 *---------------------------------------------------------------------------
2971 TclpNativeToNormalized(
2972 ClientData clientData)
2979 Tcl_WinTCharToUtf((TCHAR *) clientData, -1, &ds);
2980 copy = Tcl_DStringValue(&ds);
2981 len = Tcl_DStringLength(&ds);
2984 * Certain native path representations on Windows have this special prefix
2985 * to indicate that they are to be treated specially. For example
2986 * extremely long paths, or symlinks.
2989 if (*copy == '\\') {
2990 if (0 == strncmp(copy,"\\??\\",4)) {
2993 } else if (0 == strncmp(copy,"\\\\?\\",4)) {
3000 * Ensure we are using forward slashes only.
3003 for (p = copy; *p != '\0'; p++) {
3009 objPtr = Tcl_NewStringObj(copy,len);
3010 Tcl_DStringFree(&ds);
3016 *---------------------------------------------------------------------------
3018 * TclNativeCreateNativeRep --
3020 * Create a native representation for the given path.
3023 * The nativePath representation.
3026 * Memory will be allocated. The path might be normalized.
3028 *---------------------------------------------------------------------------
3032 TclNativeCreateNativeRep(
3035 WCHAR *nativePathPtr = NULL;
3037 Tcl_Obj *validPathPtr;
3041 if (TclFSCwdIsNative()) {
3043 * The cwd is native, which means we can use the translated path
3044 * without worrying about normalization (this will also usually be
3045 * shorter so the utf-to-external conversion will be somewhat faster).
3048 validPathPtr = Tcl_FSGetTranslatedPath(NULL, pathPtr);
3049 if (validPathPtr == NULL) {
3054 * refCount of validPathPtr was already incremented in
3055 * Tcl_FSGetTranslatedPath
3059 * Make sure the normalized path is set.
3062 validPathPtr = Tcl_FSGetNormalizedPath(NULL, pathPtr);
3063 if (validPathPtr == NULL) {
3068 * validPathPtr returned from Tcl_FSGetNormalizedPath is owned by Tcl,
3069 * so incr refCount here
3072 Tcl_IncrRefCount(validPathPtr);
3075 str = Tcl_GetString(validPathPtr);
3076 len = validPathPtr->length;
3078 if (strlen(str) != len) {
3080 * String contains NUL-bytes. This is invalid.
3087 * For a reserved device, strip a possible postfix ':'
3090 len = WinIsReserved(str);
3093 * Let MultiByteToWideChar check for other invalid sequences, like
3094 * 0xC0 0x80 (== overlong NUL). See bug [3118489]: NUL in filenames
3097 len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str, -1, 0, 0);
3104 * Overallocate 6 chars, making some room for extended paths
3107 wp = nativePathPtr = ckalloc((len + 6) * sizeof(WCHAR));
3108 if (nativePathPtr==0) {
3111 MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str, -1, nativePathPtr,
3113 nativePathPtr[len] = 0;
3116 * If path starts with "//?/" or "\\?\" (extended path), translate any
3117 * slashes to backslashes but leave the '?' intact
3120 if ((str[0] == '\\' || str[0] == '/') && (str[1] == '\\' || str[1] == '/')
3121 && str[2] == '?' && (str[3] == '\\' || str[3] == '/')) {
3122 wp[0] = wp[1] = wp[3] = '\\';
3128 * If there is no "\\?\" prefix but there is a drive or UNC path prefix
3129 * and the path is larger than MAX_PATH chars, no Win32 API function can
3130 * handle that unless it is prefixed with the extended path prefix. See:
3131 * <https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maxpath>
3134 if (((str[0] >= 'A' && str[0] <= 'Z') || (str[0] >= 'a' && str[0] <= 'z'))
3136 if (wp == nativePathPtr && len > MAX_PATH
3137 && (str[2] == '\\' || str[2] == '/')) {
3138 memmove(wp + 4, wp, len * sizeof(WCHAR));
3139 memcpy(wp, L"\\\\?\\", 4 * sizeof(WCHAR));
3144 * If (remainder of) path starts with "<drive>:", leave the ':'
3149 } else if (wp == nativePathPtr && len > MAX_PATH
3150 && (str[0] == '\\' || str[0] == '/')
3151 && (str[1] == '\\' || str[1] == '/') && str[2] != '?') {
3152 memmove(wp + 6, wp, len * sizeof(WCHAR));
3153 memcpy(wp, L"\\\\?\\UNC", 7 * sizeof(WCHAR));
3158 * In the remainder of the path, translate invalid characters to
3159 * characters in the Unicode private use area.
3162 while (*wp != '\0') {
3163 if ((*wp < ' ') || wcschr(L"\"*:<>?|", *wp)) {
3165 } else if (*wp == '/') {
3172 TclDecrRefCount(validPathPtr);
3173 return nativePathPtr;
3177 *---------------------------------------------------------------------------
3179 * TclNativeDupInternalRep --
3181 * Duplicate the native representation.
3184 * The copied native representation, or NULL if it is not possible to
3185 * copy the representation.
3188 * Memory allocation for the copy.
3190 *---------------------------------------------------------------------------
3194 TclNativeDupInternalRep(
3195 ClientData clientData)
3200 if (clientData == NULL) {
3204 len = sizeof(WCHAR) * (wcslen((const WCHAR *) clientData) + 1);
3206 copy = ckalloc(len);
3207 memcpy(copy, clientData, len);
3212 *---------------------------------------------------------------------------
3216 * Set the modification date for a file.
3219 * 0 on success, -1 on error.
3222 * Sets errno to a representation of any Windows problem that's observed
3225 *---------------------------------------------------------------------------
3230 Tcl_Obj *pathPtr, /* File to modify */
3231 struct utimbuf *tval) /* New modification date structure */
3235 const WCHAR *native;
3237 DWORD flags = FILE_ATTRIBUTE_NORMAL;
3238 FILETIME lastAccessTime, lastModTime;
3240 FromCTime(tval->actime, &lastAccessTime);
3241 FromCTime(tval->modtime, &lastModTime);
3243 native = Tcl_FSGetNativePath(pathPtr);
3245 attr = GetFileAttributesW(native);
3247 if (attr != INVALID_FILE_ATTRIBUTES && attr & FILE_ATTRIBUTE_DIRECTORY) {
3248 flags = FILE_FLAG_BACKUP_SEMANTICS;
3252 * We use the native APIs (not 'utime') because there are some daylight
3253 * savings complications that utime gets wrong.
3256 fileHandle = CreateFileW(native, FILE_WRITE_ATTRIBUTES, 0, NULL,
3257 OPEN_EXISTING, flags, NULL);
3259 if (fileHandle == INVALID_HANDLE_VALUE ||
3260 !SetFileTime(fileHandle, NULL, &lastAccessTime, &lastModTime)) {
3261 TclWinConvertError(GetLastError());
3264 if (fileHandle != INVALID_HANDLE_VALUE) {
3265 CloseHandle(fileHandle);
3271 *---------------------------------------------------------------------------
3273 * TclWinFileOwned --
3275 * Returns 1 if the specified file exists and is owned by the current
3276 * user and 0 otherwise. Like the Unix case, the check is made using
3277 * the real process SID, not the effective (impersonation) one.
3279 *---------------------------------------------------------------------------
3284 Tcl_Obj *pathPtr) /* File whose ownership is to be checked */
3286 const WCHAR *native;
3287 PSID ownerSid = NULL;
3288 PSECURITY_DESCRIPTOR secd = NULL;
3294 native = Tcl_FSGetNativePath(pathPtr);
3296 if (GetNamedSecurityInfoW((LPWSTR) native, SE_FILE_OBJECT,
3297 OWNER_SECURITY_INFORMATION, &ownerSid, NULL, NULL, NULL,
3298 &secd) != ERROR_SUCCESS) {
3300 * Either not a file, or we do not have access to it in which case we
3301 * are in all likelihood not the owner.
3308 * Getting the current process SID is a multi-step process. We make the
3309 * assumption that if a call fails, this process is so underprivileged it
3310 * could not possibly own anything. Normally a process can *always* look
3314 if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
3316 * Find out how big the buffer needs to be.
3320 GetTokenInformation(token, TokenUser, NULL, 0, &bufsz);
3322 buf = ckalloc(bufsz);
3323 if (GetTokenInformation(token, TokenUser, buf, bufsz, &bufsz)) {
3324 owned = EqualSid(ownerSid, ((PTOKEN_USER) buf)->User.Sid);
3331 * Free allocations and be done.
3335 LocalFree(secd); /* Also frees ownerSid */
3341 return (owned != 0); /* Convert non-0 to 1 */