1/* 2 * tclWinFile.c -- 3 * 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 7 * interfaces. 8 * 9 * Copyright (c) 1995-1998 Sun Microsystems, Inc. 10 * 11 * See the file "license.terms" for information on usage and redistribution of 12 * this file, and for a DISCLAIMER OF ALL WARRANTIES. 13 * 14 * RCS: @(#) $Id: tclWinFile.c,v 1.95.2.4 2010/04/19 07:40:41 nijtmans Exp $ 15 */ 16 17/* #define _WIN32_WINNT 0x0500 */ 18 19#include "tclWinInt.h" 20#include "tclFileSystem.h" 21#include <winioctl.h> 22#include <sys/stat.h> 23#include <shlobj.h> 24#include <lmaccess.h> /* For TclpGetUserHome(). */ 25 26/* 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). 29 */ 30 31#define POSIX_EPOCH_AS_FILETIME \ 32 ((Tcl_WideInt) 116444736 * (Tcl_WideInt) 1000000000) 33 34/* 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 37 * documented. 38 */ 39 40#ifndef IO_REPARSE_TAG_RESERVED_ONE 41# define IO_REPARSE_TAG_RESERVED_ONE 0x000000001 42#endif 43#ifndef IO_REPARSE_TAG_RESERVED_RANGE 44# define IO_REPARSE_TAG_RESERVED_RANGE 0x000000001 45#endif 46#ifndef IO_REPARSE_TAG_VALID_VALUES 47# define IO_REPARSE_TAG_VALID_VALUES 0x0E000FFFF 48#endif 49#ifndef IO_REPARSE_TAG_HSM 50# define IO_REPARSE_TAG_HSM 0x0C0000004 51#endif 52#ifndef IO_REPARSE_TAG_NSS 53# define IO_REPARSE_TAG_NSS 0x080000005 54#endif 55#ifndef IO_REPARSE_TAG_NSSRECOVER 56# define IO_REPARSE_TAG_NSSRECOVER 0x080000006 57#endif 58#ifndef IO_REPARSE_TAG_SIS 59# define IO_REPARSE_TAG_SIS 0x080000007 60#endif 61#ifndef IO_REPARSE_TAG_DFS 62# define IO_REPARSE_TAG_DFS 0x080000008 63#endif 64 65#ifndef IO_REPARSE_TAG_RESERVED_ZERO 66# define IO_REPARSE_TAG_RESERVED_ZERO 0x00000000 67#endif 68#ifndef FILE_FLAG_OPEN_REPARSE_POINT 69# define FILE_FLAG_OPEN_REPARSE_POINT 0x00200000 70#endif 71#ifndef IO_REPARSE_TAG_MOUNT_POINT 72# define IO_REPARSE_TAG_MOUNT_POINT 0xA0000003 73#endif 74#ifndef IsReparseTagValid 75# define IsReparseTagValid(x) \ 76 (!((x)&~IO_REPARSE_TAG_VALID_VALUES)&&((x)>IO_REPARSE_TAG_RESERVED_RANGE)) 77#endif 78#ifndef IO_REPARSE_TAG_SYMBOLIC_LINK 79# define IO_REPARSE_TAG_SYMBOLIC_LINK IO_REPARSE_TAG_RESERVED_ZERO 80#endif 81#ifndef FILE_SPECIAL_ACCESS 82# define FILE_SPECIAL_ACCESS (FILE_ANY_ACCESS) 83#endif 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) 91#endif 92#ifndef INVALID_FILE_ATTRIBUTES 93#define INVALID_FILE_ATTRIBUTES ((DWORD)-1) 94#endif 95 96/* 97 * Maximum reparse buffer info size. The max user defined reparse data is 98 * 16KB, plus there's a header. 99 */ 100 101#define MAX_REPARSE_SIZE 17000 102 103/* 104 * Undocumented REPARSE_MOUNTPOINT_HEADER_SIZE structure definition. This is 105 * found in winnt.h. 106 * 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. 112 */ 113 114#define REPARSE_MOUNTPOINT_HEADER_SIZE 8 115#ifndef REPARSE_DATA_BUFFER_HEADER_SIZE 116typedef struct _REPARSE_DATA_BUFFER { 117 DWORD ReparseTag; 118 WORD ReparseDataLength; 119 WORD Reserved; 120 union { 121 struct { 122 WORD SubstituteNameOffset; 123 WORD SubstituteNameLength; 124 WORD PrintNameOffset; 125 WORD PrintNameLength; 126 ULONG Flags; 127 WCHAR PathBuffer[1]; 128 } SymbolicLinkReparseBuffer; 129 struct { 130 WORD SubstituteNameOffset; 131 WORD SubstituteNameLength; 132 WORD PrintNameOffset; 133 WORD PrintNameLength; 134 WCHAR PathBuffer[1]; 135 } MountPointReparseBuffer; 136 struct { 137 BYTE DataBuffer[1]; 138 } GenericReparseBuffer; 139 }; 140} REPARSE_DATA_BUFFER; 141#endif 142 143typedef struct { 144 REPARSE_DATA_BUFFER dummy; 145 WCHAR dummyBuf[MAX_PATH * 3]; 146} DUMMY_REPARSE_BUFFER; 147 148#if defined(_MSC_VER) && (_MSC_VER <= 1100) 149#undef HAVE_NO_FINDEX_ENUMS 150#define HAVE_NO_FINDEX_ENUMS 151#elif !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0400) 152#undef HAVE_NO_FINDEX_ENUMS 153#define HAVE_NO_FINDEX_ENUMS 154#endif 155 156#ifdef HAVE_NO_FINDEX_ENUMS 157/* These two aren't in VC++ 5.2 headers */ 158typedef enum _FINDEX_INFO_LEVELS { 159 FindExInfoStandard, 160 FindExInfoMaxInfoLevel 161} FINDEX_INFO_LEVELS; 162typedef enum _FINDEX_SEARCH_OPS { 163 FindExSearchNameMatch, 164 FindExSearchLimitToDirectories, 165 FindExSearchLimitToDevices, 166 FindExSearchMaxSearchOp 167} FINDEX_SEARCH_OPS; 168#endif /* HAVE_NO_FINDEX_ENUMS */ 169 170/* 171 * Other typedefs required by this code. 172 */ 173 174static time_t ToCTime(FILETIME fileTime); 175static void FromCTime(time_t posixTime, FILETIME *fileTime); 176 177typedef NET_API_STATUS NET_API_FUNCTION NETUSERGETINFOPROC( 178 LPWSTR servername, LPWSTR username, DWORD level, LPBYTE *bufptr); 179 180typedef NET_API_STATUS NET_API_FUNCTION NETAPIBUFFERFREEPROC(LPVOID Buffer); 181 182typedef NET_API_STATUS NET_API_FUNCTION NETGETDCNAMEPROC( 183 LPWSTR servername, LPWSTR domainname, LPBYTE *bufptr); 184 185/* 186 * Declarations for local functions defined in this file: 187 */ 188 189static int NativeAccess(const TCHAR *path, int mode); 190static int NativeDev(const TCHAR *path); 191static int NativeStat(const TCHAR *path, Tcl_StatBuf *statPtr, 192 int checkLinks); 193static unsigned short NativeStatMode(DWORD attr, int checkLinks, 194 int isExec); 195static int NativeIsExec(const TCHAR *path); 196static int NativeReadReparse(const TCHAR *LinkDirectory, 197 REPARSE_DATA_BUFFER *buffer); 198static int NativeWriteReparse(const TCHAR *LinkDirectory, 199 REPARSE_DATA_BUFFER *buffer); 200static int NativeMatchType(int isDrive, DWORD attr, 201 const TCHAR *nativeName, Tcl_GlobTypeData *types); 202static int WinIsDrive(const char *name, int nameLen); 203static int WinIsReserved(const char *path); 204static Tcl_Obj * WinReadLink(const TCHAR *LinkSource); 205static Tcl_Obj * WinReadLinkDirectory(const TCHAR *LinkDirectory); 206static int WinLink(const TCHAR *LinkSource, 207 const TCHAR *LinkTarget, int linkAction); 208static int WinSymLinkDirectory(const TCHAR *LinkDirectory, 209 const TCHAR *LinkTarget); 210 211/* 212 *-------------------------------------------------------------------- 213 * 214 * WinLink -- 215 * 216 * Make a link from source to target. 217 * 218 *-------------------------------------------------------------------- 219 */ 220 221static int 222WinLink( 223 const TCHAR *linkSourcePath, 224 const TCHAR *linkTargetPath, 225 int linkAction) 226{ 227 WCHAR tempFileName[MAX_PATH]; 228 TCHAR *tempFilePart; 229 DWORD attr; 230 231 /* 232 * Get the full path referenced by the target. 233 */ 234 235 if (!(*tclWinProcs->getFullPathNameProc)(linkTargetPath, MAX_PATH, 236 tempFileName, &tempFilePart)) { 237 /* 238 * Invalid file. 239 */ 240 241 TclWinConvertError(GetLastError()); 242 return -1; 243 } 244 245 /* 246 * Make sure source file doesn't exist. 247 */ 248 249 attr = (*tclWinProcs->getFileAttributesProc)(linkSourcePath); 250 if (attr != INVALID_FILE_ATTRIBUTES) { 251 Tcl_SetErrno(EEXIST); 252 return -1; 253 } 254 255 /* 256 * Get the full path referenced by the source file/directory. 257 */ 258 259 if (!(*tclWinProcs->getFullPathNameProc)(linkSourcePath, MAX_PATH, 260 tempFileName, &tempFilePart)) { 261 /* 262 * Invalid file. 263 */ 264 265 TclWinConvertError(GetLastError()); 266 return -1; 267 } 268 269 /* 270 * Check the target. 271 */ 272 273 attr = (*tclWinProcs->getFileAttributesProc)(linkTargetPath); 274 if (attr == INVALID_FILE_ATTRIBUTES) { 275 /* 276 * The target doesn't exist. 277 */ 278 279 TclWinConvertError(GetLastError()); 280 return -1; 281 282 } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0) { 283 /* 284 * It is a file. 285 */ 286 287 if (tclWinProcs->createHardLinkProc == NULL) { 288 Tcl_SetErrno(ENOTDIR); 289 return -1; 290 } 291 292 if (linkAction & TCL_CREATE_HARD_LINK) { 293 if (!(*tclWinProcs->createHardLinkProc)(linkSourcePath, 294 linkTargetPath, NULL)) { 295 TclWinConvertError(GetLastError()); 296 return -1; 297 } 298 return 0; 299 300 } else if (linkAction & TCL_CREATE_SYMBOLIC_LINK) { 301 /* 302 * Can't symlink files. 303 */ 304 305 Tcl_SetErrno(ENOTDIR); 306 return -1; 307 } else { 308 Tcl_SetErrno(ENODEV); 309 return -1; 310 } 311 } else { 312 /* 313 * We've got a directory. Now check whether what we're trying to do is 314 * reasonable. 315 */ 316 317 if (linkAction & TCL_CREATE_SYMBOLIC_LINK) { 318 return WinSymLinkDirectory(linkSourcePath, linkTargetPath); 319 320 } else if (linkAction & TCL_CREATE_HARD_LINK) { 321 /* 322 * Can't hard link directories. 323 */ 324 325 Tcl_SetErrno(EISDIR); 326 return -1; 327 } else { 328 Tcl_SetErrno(ENODEV); 329 return -1; 330 } 331 } 332} 333 334/* 335 *-------------------------------------------------------------------- 336 * 337 * WinReadLink -- 338 * 339 * What does 'LinkSource' point to? 340 * 341 *-------------------------------------------------------------------- 342 */ 343 344static Tcl_Obj * 345WinReadLink( 346 const TCHAR *linkSourcePath) 347{ 348 WCHAR tempFileName[MAX_PATH]; 349 TCHAR *tempFilePart; 350 DWORD attr; 351 352 /* 353 * Get the full path referenced by the target. 354 */ 355 356 if (!(*tclWinProcs->getFullPathNameProc)(linkSourcePath, MAX_PATH, 357 tempFileName, &tempFilePart)) { 358 /* 359 * Invalid file. 360 */ 361 362 TclWinConvertError(GetLastError()); 363 return NULL; 364 } 365 366 /* 367 * Make sure source file does exist. 368 */ 369 370 attr = (*tclWinProcs->getFileAttributesProc)(linkSourcePath); 371 if (attr == INVALID_FILE_ATTRIBUTES) { 372 /* 373 * The source doesn't exist. 374 */ 375 376 TclWinConvertError(GetLastError()); 377 return NULL; 378 379 } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0) { 380 /* 381 * It is a file - this is not yet supported. 382 */ 383 384 Tcl_SetErrno(ENOTDIR); 385 return NULL; 386 } else { 387 return WinReadLinkDirectory(linkSourcePath); 388 } 389} 390 391/* 392 *-------------------------------------------------------------------- 393 * 394 * WinSymLinkDirectory -- 395 * 396 * This routine creates a NTFS junction, using the undocumented 397 * FSCTL_SET_REPARSE_POINT structure Win2K uses for mount points and 398 * junctions. 399 * 400 * Assumption that linkTargetPath is a valid, existing directory. 401 * 402 * Returns: 403 * Zero on success. 404 * 405 *-------------------------------------------------------------------- 406 */ 407 408static int 409WinSymLinkDirectory( 410 const TCHAR *linkDirPath, 411 const TCHAR *linkTargetPath) 412{ 413 DUMMY_REPARSE_BUFFER dummy; 414 REPARSE_DATA_BUFFER *reparseBuffer = (REPARSE_DATA_BUFFER *) &dummy; 415 int len; 416 WCHAR nativeTarget[MAX_PATH]; 417 WCHAR *loop; 418 419 /* 420 * Make the native target name. 421 */ 422 423 memcpy(nativeTarget, L"\\??\\", 4 * sizeof(WCHAR)); 424 memcpy(nativeTarget + 4, linkTargetPath, 425 sizeof(WCHAR) * (1+wcslen((WCHAR *) linkTargetPath))); 426 len = wcslen(nativeTarget); 427 428 /* 429 * We must have backslashes only. This is VERY IMPORTANT. If we have any 430 * forward slashes everything appears to work, but the resulting symlink 431 * is useless! 432 */ 433 434 for (loop = nativeTarget; *loop != 0; loop++) { 435 if (*loop == L'/') { 436 *loop = L'\\'; 437 } 438 } 439 if ((nativeTarget[len-1] == L'\\') && (nativeTarget[len-2] != L':')) { 440 nativeTarget[len-1] = 0; 441 } 442 443 /* 444 * Build the reparse info. 445 */ 446 447 memset(reparseBuffer, 0, sizeof(DUMMY_REPARSE_BUFFER)); 448 reparseBuffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; 449 reparseBuffer->MountPointReparseBuffer.SubstituteNameLength = 450 wcslen(nativeTarget) * sizeof(WCHAR); 451 reparseBuffer->Reserved = 0; 452 reparseBuffer->MountPointReparseBuffer.PrintNameLength = 0; 453 reparseBuffer->MountPointReparseBuffer.PrintNameOffset = 454 reparseBuffer->MountPointReparseBuffer.SubstituteNameLength 455 + sizeof(WCHAR); 456 memcpy(reparseBuffer->MountPointReparseBuffer.PathBuffer, nativeTarget, 457 sizeof(WCHAR) 458 + reparseBuffer->MountPointReparseBuffer.SubstituteNameLength); 459 reparseBuffer->ReparseDataLength = 460 reparseBuffer->MountPointReparseBuffer.SubstituteNameLength+12; 461 462 return NativeWriteReparse(linkDirPath, reparseBuffer); 463} 464 465/* 466 *-------------------------------------------------------------------- 467 * 468 * TclWinSymLinkCopyDirectory -- 469 * 470 * Copy a Windows NTFS junction. This function assumes that LinkOriginal 471 * exists and is a valid junction point, and that LinkCopy does not 472 * exist. 473 * 474 * Returns: 475 * Zero on success. 476 * 477 *-------------------------------------------------------------------- 478 */ 479 480int 481TclWinSymLinkCopyDirectory( 482 const TCHAR *linkOrigPath, /* Existing junction - reparse point */ 483 const TCHAR *linkCopyPath) /* Will become a duplicate junction */ 484{ 485 DUMMY_REPARSE_BUFFER dummy; 486 REPARSE_DATA_BUFFER *reparseBuffer = (REPARSE_DATA_BUFFER *) &dummy; 487 488 if (NativeReadReparse(linkOrigPath, reparseBuffer)) { 489 return -1; 490 } 491 return NativeWriteReparse(linkCopyPath, reparseBuffer); 492} 493 494/* 495 *-------------------------------------------------------------------- 496 * 497 * TclWinSymLinkDelete -- 498 * 499 * Delete a Windows NTFS junction. Once the junction information is 500 * deleted, the filesystem object becomes an ordinary directory. Unless 501 * 'linkOnly' is given, that directory is also removed. 502 * 503 * Assumption that LinkOriginal is a valid, existing junction. 504 * 505 * Returns: 506 * Zero on success. 507 * 508 *-------------------------------------------------------------------- 509 */ 510 511int 512TclWinSymLinkDelete( 513 const TCHAR *linkOrigPath, 514 int linkOnly) 515{ 516 /* 517 * It is a symbolic link - remove it. 518 */ 519 520 DUMMY_REPARSE_BUFFER dummy; 521 REPARSE_DATA_BUFFER *reparseBuffer = (REPARSE_DATA_BUFFER *) &dummy; 522 HANDLE hFile; 523 DWORD returnedLength; 524 525 memset(reparseBuffer, 0, sizeof(DUMMY_REPARSE_BUFFER)); 526 reparseBuffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; 527 hFile = (*tclWinProcs->createFileProc)(linkOrigPath, GENERIC_WRITE, 0, 528 NULL, OPEN_EXISTING, 529 FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, NULL); 530 531 if (hFile != INVALID_HANDLE_VALUE) { 532 if (!DeviceIoControl(hFile, FSCTL_DELETE_REPARSE_POINT, reparseBuffer, 533 REPARSE_MOUNTPOINT_HEADER_SIZE,NULL,0,&returnedLength,NULL)) { 534 /* 535 * Error setting junction. 536 */ 537 538 TclWinConvertError(GetLastError()); 539 CloseHandle(hFile); 540 } else { 541 CloseHandle(hFile); 542 if (!linkOnly) { 543 (*tclWinProcs->removeDirectoryProc)(linkOrigPath); 544 } 545 return 0; 546 } 547 } 548 return -1; 549} 550 551/* 552 *-------------------------------------------------------------------- 553 * 554 * WinReadLinkDirectory -- 555 * 556 * This routine reads a NTFS junction, using the undocumented 557 * FSCTL_GET_REPARSE_POINT structure Win2K uses for mount points and 558 * junctions. 559 * 560 * Assumption that LinkDirectory is a valid, existing directory. 561 * 562 * Returns: 563 * A Tcl_Obj with refCount of 1 (i.e. owned by the caller), or NULL if 564 * anything went wrong. 565 * 566 * In the future we should enhance this to return a path object rather 567 * than a string. 568 * 569 *-------------------------------------------------------------------- 570 */ 571 572static Tcl_Obj * 573WinReadLinkDirectory( 574 const TCHAR *linkDirPath) 575{ 576 int attr, len, offset; 577 DUMMY_REPARSE_BUFFER dummy; 578 REPARSE_DATA_BUFFER *reparseBuffer = (REPARSE_DATA_BUFFER *) &dummy; 579 Tcl_Obj *retVal; 580 Tcl_DString ds; 581 const char *copy; 582 583 attr = (*tclWinProcs->getFileAttributesProc)(linkDirPath); 584 if (!(attr & FILE_ATTRIBUTE_REPARSE_POINT)) { 585 goto invalidError; 586 } 587 if (NativeReadReparse(linkDirPath, reparseBuffer)) { 588 return NULL; 589 } 590 591 switch (reparseBuffer->ReparseTag) { 592 case 0x80000000|IO_REPARSE_TAG_SYMBOLIC_LINK: 593 case IO_REPARSE_TAG_SYMBOLIC_LINK: 594 case IO_REPARSE_TAG_MOUNT_POINT: 595 /* 596 * Certain native path representations on Windows have a special 597 * prefix to indicate that they are to be treated specially. For 598 * example extremely long paths, or symlinks, or volumes mounted 599 * inside directories. 600 * 601 * There is an assumption in this code that 'wide' interfaces are 602 * being used (see tclWin32Dll.c), which is true for the only systems 603 * which support reparse tags at present. If that changes in the 604 * future, this code will have to be generalised. 605 */ 606 607 offset = 0; 608 if (reparseBuffer->MountPointReparseBuffer.PathBuffer[0] == L'\\') { 609 /* 610 * Check whether this is a mounted volume. 611 */ 612 613 if (wcsncmp(reparseBuffer->MountPointReparseBuffer.PathBuffer, 614 L"\\??\\Volume{",11) == 0) { 615 char drive; 616 617 /* 618 * There is some confusion between \??\ and \\?\ which we have 619 * to fix here. It doesn't seem very well documented. 620 */ 621 622 reparseBuffer->MountPointReparseBuffer.PathBuffer[1]=L'\\'; 623 624 /* 625 * Check if a corresponding drive letter exists, and use that 626 * if it is found 627 */ 628 629 drive = TclWinDriveLetterForVolMountPoint( 630 reparseBuffer->MountPointReparseBuffer.PathBuffer); 631 if (drive != -1) { 632 char driveSpec[3] = { 633 '\0', ':', '\0' 634 }; 635 636 driveSpec[0] = drive; 637 retVal = Tcl_NewStringObj(driveSpec,2); 638 Tcl_IncrRefCount(retVal); 639 return retVal; 640 } 641 642 /* 643 * This is actually a mounted drive, which doesn't exists as a 644 * DOS drive letter. This means the path isn't actually a 645 * link, although we partially treat it like one ('file type' 646 * will return 'link'), but then the link will actually just 647 * be treated like an ordinary directory. I don't believe any 648 * serious inconsistency will arise from this, but it is 649 * something to be aware of. 650 */ 651 652 goto invalidError; 653 } else if (wcsncmp(reparseBuffer->MountPointReparseBuffer 654 .PathBuffer, L"\\\\?\\",4) == 0) { 655 /* 656 * Strip off the prefix. 657 */ 658 659 offset = 4; 660 } else if (wcsncmp(reparseBuffer->MountPointReparseBuffer 661 .PathBuffer, L"\\??\\",4) == 0) { 662 /* 663 * Strip off the prefix. 664 */ 665 666 offset = 4; 667 } 668 } 669 670 Tcl_WinTCharToUtf((const char *) 671 reparseBuffer->MountPointReparseBuffer.PathBuffer, 672 (int) reparseBuffer->MountPointReparseBuffer 673 .SubstituteNameLength, &ds); 674 675 copy = Tcl_DStringValue(&ds)+offset; 676 len = Tcl_DStringLength(&ds)-offset; 677 retVal = Tcl_NewStringObj(copy,len); 678 Tcl_IncrRefCount(retVal); 679 Tcl_DStringFree(&ds); 680 return retVal; 681 } 682 683 invalidError: 684 Tcl_SetErrno(EINVAL); 685 return NULL; 686} 687 688/* 689 *-------------------------------------------------------------------- 690 * 691 * NativeReadReparse -- 692 * 693 * Read the junction/reparse information from a given NTFS directory. 694 * 695 * Assumption that linkDirPath is a valid, existing directory. 696 * 697 * Returns: 698 * Zero on success. 699 * 700 *-------------------------------------------------------------------- 701 */ 702 703static int 704NativeReadReparse( 705 const TCHAR *linkDirPath, /* The junction to read */ 706 REPARSE_DATA_BUFFER *buffer)/* Pointer to buffer. Cannot be NULL */ 707{ 708 HANDLE hFile; 709 DWORD returnedLength; 710 711 hFile = (*tclWinProcs->createFileProc)(linkDirPath, GENERIC_READ, 0, 712 NULL, OPEN_EXISTING, 713 FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, NULL); 714 715 if (hFile == INVALID_HANDLE_VALUE) { 716 /* 717 * Error creating directory. 718 */ 719 720 TclWinConvertError(GetLastError()); 721 return -1; 722 } 723 724 /* 725 * Get the link. 726 */ 727 728 if (!DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, NULL, 0, buffer, 729 sizeof(DUMMY_REPARSE_BUFFER), &returnedLength, NULL)) { 730 /* 731 * Error setting junction. 732 */ 733 734 TclWinConvertError(GetLastError()); 735 CloseHandle(hFile); 736 return -1; 737 } 738 CloseHandle(hFile); 739 740 if (!IsReparseTagValid(buffer->ReparseTag)) { 741 Tcl_SetErrno(EINVAL); 742 return -1; 743 } 744 return 0; 745} 746 747/* 748 *-------------------------------------------------------------------- 749 * 750 * NativeWriteReparse -- 751 * 752 * Write the reparse information for a given directory. 753 * 754 * Assumption that LinkDirectory does not exist. 755 * 756 *-------------------------------------------------------------------- 757 */ 758 759static int 760NativeWriteReparse( 761 const TCHAR *linkDirPath, 762 REPARSE_DATA_BUFFER *buffer) 763{ 764 HANDLE hFile; 765 DWORD returnedLength; 766 767 /* 768 * Create the directory - it must not already exist. 769 */ 770 771 if ((*tclWinProcs->createDirectoryProc)(linkDirPath, NULL) == 0) { 772 /* 773 * Error creating directory. 774 */ 775 776 TclWinConvertError(GetLastError()); 777 return -1; 778 } 779 hFile = (*tclWinProcs->createFileProc)(linkDirPath, GENERIC_WRITE, 0, 780 NULL, OPEN_EXISTING, 781 FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, NULL); 782 if (hFile == INVALID_HANDLE_VALUE) { 783 /* 784 * Error creating directory. 785 */ 786 787 TclWinConvertError(GetLastError()); 788 return -1; 789 } 790 791 /* 792 * Set the link. 793 */ 794 795 if (!DeviceIoControl(hFile, FSCTL_SET_REPARSE_POINT, buffer, 796 (DWORD) buffer->ReparseDataLength + REPARSE_MOUNTPOINT_HEADER_SIZE, 797 NULL, 0, &returnedLength, NULL)) { 798 /* 799 * Error setting junction. 800 */ 801 802 TclWinConvertError(GetLastError()); 803 CloseHandle(hFile); 804 (*tclWinProcs->removeDirectoryProc)(linkDirPath); 805 return -1; 806 } 807 CloseHandle(hFile); 808 809 /* 810 * We succeeded. 811 */ 812 813 return 0; 814} 815 816/* 817 *--------------------------------------------------------------------------- 818 * 819 * TclpFindExecutable -- 820 * 821 * This function computes the absolute path name of the current 822 * application. 823 * 824 * Results: 825 * None. 826 * 827 * Side effects: 828 * The computed path is stored. 829 * 830 *--------------------------------------------------------------------------- 831 */ 832 833void 834TclpFindExecutable( 835 const char *argv0) /* The value of the application's argv[0] 836 * (native). */ 837{ 838 WCHAR wName[MAX_PATH]; 839 char name[MAX_PATH * TCL_UTF_MAX]; 840 841 /* 842 * Under Windows we ignore argv0, and return the path for the file used to 843 * create this process. 844 */ 845 846 if (GetModuleFileNameW(NULL, wName, MAX_PATH) == 0) { 847 GetModuleFileNameA(NULL, name, sizeof(name)); 848 849 /* 850 * Convert to WCHAR to get out of ANSI codepage 851 */ 852 853 MultiByteToWideChar(CP_ACP, 0, name, -1, wName, MAX_PATH); 854 } 855 856 WideCharToMultiByte(CP_UTF8, 0, wName, -1, name, sizeof(name), NULL,NULL); 857 TclWinNoBackslash(name); 858 TclSetObjNameOfExecutable(Tcl_NewStringObj(name, -1), NULL); 859} 860 861/* 862 *---------------------------------------------------------------------- 863 * 864 * TclpMatchInDirectory -- 865 * 866 * This routine is used by the globbing code to search a directory for 867 * all files which match a given pattern. 868 * 869 * Results: 870 * The return value is a standard Tcl result indicating whether an error 871 * occurred in globbing. Errors are left in interp, good results are 872 * lappended to resultPtr (which must be a valid object). 873 * 874 * Side effects: 875 * None. 876 * 877 *---------------------------------------------------------------------- 878 */ 879 880int 881TclpMatchInDirectory( 882 Tcl_Interp *interp, /* Interpreter to receive errors. */ 883 Tcl_Obj *resultPtr, /* List object to lappend results. */ 884 Tcl_Obj *pathPtr, /* Contains path to directory to search. */ 885 const char *pattern, /* Pattern to match against. */ 886 Tcl_GlobTypeData *types) /* Object containing list of acceptable types. 887 * May be NULL. In particular the directory 888 * flag is very important. */ 889{ 890 const TCHAR *native; 891 892 if (types != NULL && types->type == TCL_GLOB_TYPE_MOUNT) { 893 /* 894 * The native filesystem never adds mounts. 895 */ 896 897 return TCL_OK; 898 } 899 900 if (pattern == NULL || (*pattern == '\0')) { 901 Tcl_Obj *norm = Tcl_FSGetNormalizedPath(NULL, pathPtr); 902 if (norm != NULL) { 903 /* 904 * Match a single file directly. 905 */ 906 907 int len; 908 DWORD attr; 909 const char *str = Tcl_GetStringFromObj(norm,&len); 910 911 native = (const TCHAR *) Tcl_FSGetNativePath(pathPtr); 912 913 if (tclWinProcs->getFileAttributesExProc == NULL) { 914 attr = (*tclWinProcs->getFileAttributesProc)(native); 915 if (attr == 0xffffffff) { 916 return TCL_OK; 917 } 918 } else { 919 WIN32_FILE_ATTRIBUTE_DATA data; 920 if ((*tclWinProcs->getFileAttributesExProc)(native, 921 GetFileExInfoStandard, &data) != TRUE) { 922 return TCL_OK; 923 } 924 attr = data.dwFileAttributes; 925 } 926 927 if (NativeMatchType(WinIsDrive(str,len), attr, native, types)) { 928 Tcl_ListObjAppendElement(interp, resultPtr, pathPtr); 929 } 930 } 931 return TCL_OK; 932 } else { 933 DWORD attr; 934 HANDLE handle; 935 WIN32_FIND_DATAT data; 936 const char *dirName; /* UTF-8 dir name, later with pattern 937 * appended. */ 938 int dirLength; 939 int matchSpecialDots; 940 Tcl_DString ds; /* Native encoding of dir, also used 941 * temporarily for other things. */ 942 Tcl_DString dsOrig; /* UTF-8 encoding of dir. */ 943 Tcl_Obj *fileNamePtr; 944 char lastChar; 945 946 /* 947 * Get the normalized path representation (the main thing is we dont 948 * want any '~' sequences). 949 */ 950 951 fileNamePtr = Tcl_FSGetNormalizedPath(interp, pathPtr); 952 if (fileNamePtr == NULL) { 953 return TCL_ERROR; 954 } 955 956 /* 957 * Verify that the specified path exists and is actually a directory. 958 */ 959 960 native = Tcl_FSGetNativePath(pathPtr); 961 if (native == NULL) { 962 return TCL_OK; 963 } 964 attr = (*tclWinProcs->getFileAttributesProc)(native); 965 966 if ((attr == 0xffffffff) || ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0)) { 967 return TCL_OK; 968 } 969 970 /* 971 * Build up the directory name for searching, including a trailing 972 * directory separator. 973 */ 974 975 Tcl_DStringInit(&dsOrig); 976 dirName = Tcl_GetStringFromObj(fileNamePtr, &dirLength); 977 Tcl_DStringAppend(&dsOrig, dirName, dirLength); 978 979 lastChar = dirName[dirLength -1]; 980 if ((lastChar != '\\') && (lastChar != '/') && (lastChar != ':')) { 981 Tcl_DStringAppend(&dsOrig, "/", 1); 982 dirLength++; 983 } 984 dirName = Tcl_DStringValue(&dsOrig); 985 986 /* 987 * We need to check all files in the directory, so we append '*.*' to 988 * the path, unless the pattern we've been given is rather simple, 989 * when we can use that instead. 990 */ 991 992 if (strpbrk(pattern, "[]\\") == NULL) { 993 /* 994 * The pattern is a simple one containing just '*' and/or '?'. 995 * This means we can get the OS to help us, by passing it the 996 * pattern. 997 */ 998 999 dirName = Tcl_DStringAppend(&dsOrig, pattern, -1); 1000 } else { 1001 dirName = Tcl_DStringAppend(&dsOrig, "*.*", 3); 1002 } 1003 1004 native = Tcl_WinUtfToTChar(dirName, -1, &ds); 1005 if (tclWinProcs->findFirstFileExProc == NULL || (types == NULL) 1006 || (types->type != TCL_GLOB_TYPE_DIR)) { 1007 handle = (*tclWinProcs->findFirstFileProc)(native, &data); 1008 } else { 1009 /* 1010 * We can be more efficient, for pure directory requests. 1011 */ 1012 1013 handle = (*tclWinProcs->findFirstFileExProc)(native, 1014 FindExInfoStandard, &data, 1015 FindExSearchLimitToDirectories, NULL, 0); 1016 } 1017 1018 if (handle == INVALID_HANDLE_VALUE) { 1019 DWORD err = GetLastError(); 1020 Tcl_DStringFree(&ds); 1021 if (err == ERROR_FILE_NOT_FOUND) { 1022 /* 1023 * We used our 'pattern' above, and matched nothing. This 1024 * means we just return TCL_OK, indicating no results found. 1025 */ 1026 1027 Tcl_DStringFree(&dsOrig); 1028 return TCL_OK; 1029 } 1030 1031 TclWinConvertError(err); 1032 if (interp != NULL) { 1033 Tcl_ResetResult(interp); 1034 Tcl_AppendResult(interp, "couldn't read directory \"", 1035 Tcl_DStringValue(&dsOrig), "\": ", 1036 Tcl_PosixError(interp), NULL); 1037 } 1038 Tcl_DStringFree(&dsOrig); 1039 return TCL_ERROR; 1040 } 1041 Tcl_DStringFree(&ds); 1042 1043 /* 1044 * We may use this later, so we must restore it to its length 1045 * including the directory delimiter. 1046 */ 1047 1048 Tcl_DStringSetLength(&dsOrig, dirLength); 1049 1050 /* 1051 * Check to see if the pattern should match the special . and 1052 * .. names, referring to the current directory, or the directory 1053 * above. We need a special check for this because paths beginning 1054 * with a dot are not considered hidden on Windows, and so otherwise a 1055 * relative glob like 'glob -join * *' will actually return 1056 * './. ../..' etc. 1057 */ 1058 1059 if ((pattern[0] == '.') 1060 || ((pattern[0] == '\\') && (pattern[1] == '.'))) { 1061 matchSpecialDots = 1; 1062 } else { 1063 matchSpecialDots = 0; 1064 } 1065 1066 /* 1067 * Now iterate over all of the files in the directory, starting with 1068 * the first one we found. 1069 */ 1070 1071 do { 1072 const char *utfname; 1073 int checkDrive = 0, isDrive; 1074 DWORD attr; 1075 1076 if (tclWinProcs->useWide) { 1077 native = (const TCHAR *) data.w.cFileName; 1078 attr = data.w.dwFileAttributes; 1079 } else { 1080 native = (const TCHAR *) data.a.cFileName; 1081 attr = data.a.dwFileAttributes; 1082 } 1083 1084 utfname = Tcl_WinTCharToUtf(native, -1, &ds); 1085 1086 if (!matchSpecialDots) { 1087 /* 1088 * If it is exactly '.' or '..' then we ignore it. 1089 */ 1090 1091 if ((utfname[0] == '.') && (utfname[1] == '\0' 1092 || (utfname[1] == '.' && utfname[2] == '\0'))) { 1093 Tcl_DStringFree(&ds); 1094 continue; 1095 } 1096 } else if (utfname[0] == '.' && utfname[1] == '.' 1097 && utfname[2] == '\0') { 1098 /* 1099 * Have to check if this is a drive below, so we can correctly 1100 * match 'hidden' and not hidden files. 1101 */ 1102 1103 checkDrive = 1; 1104 } 1105 1106 /* 1107 * Check to see if the file matches the pattern. Note that we are 1108 * ignoring the case sensitivity flag because Windows doesn't 1109 * honor case even if the volume is case sensitive. If the volume 1110 * also doesn't preserve case, then we previously returned the 1111 * lower case form of the name. This didn't seem quite right since 1112 * there are non-case-preserving volumes that actually return 1113 * mixed case. So now we are returning exactly what we get from 1114 * the system. 1115 */ 1116 1117 if (Tcl_StringCaseMatch(utfname, pattern, 1)) { 1118 /* 1119 * If the file matches, then we need to process the remainder 1120 * of the path. 1121 */ 1122 1123 if (checkDrive) { 1124 const char *fullname = Tcl_DStringAppend(&dsOrig, utfname, 1125 Tcl_DStringLength(&ds)); 1126 isDrive = WinIsDrive(fullname, Tcl_DStringLength(&dsOrig)); 1127 Tcl_DStringSetLength(&dsOrig, dirLength); 1128 } else { 1129 isDrive = 0; 1130 } 1131 if (NativeMatchType(isDrive, attr, native, types)) { 1132 Tcl_ListObjAppendElement(interp, resultPtr, 1133 TclNewFSPathObj(pathPtr, utfname, 1134 Tcl_DStringLength(&ds))); 1135 } 1136 } 1137 1138 /* 1139 * Free ds here to ensure that native is valid above. 1140 */ 1141 1142 Tcl_DStringFree(&ds); 1143 } while ((*tclWinProcs->findNextFileProc)(handle, &data) == TRUE); 1144 1145 FindClose(handle); 1146 Tcl_DStringFree(&dsOrig); 1147 return TCL_OK; 1148 } 1149} 1150 1151/* 1152 * Does the given path represent a root volume? We need this special case 1153 * because for NTFS root volumes, the getFileAttributesProc returns a 'hidden' 1154 * attribute when it should not. 1155 */ 1156 1157static int 1158WinIsDrive( 1159 const char *name, /* Name (UTF-8) */ 1160 int len) /* Length of name */ 1161{ 1162 int remove = 0; 1163 1164 while (len > 4) { 1165 if ((name[len-1] != '.' || name[len-2] != '.') 1166 || (name[len-3] != '/' && name[len-3] != '\\')) { 1167 /* 1168 * We don't have '/..' at the end. 1169 */ 1170 1171 if (remove == 0) { 1172 break; 1173 } 1174 remove--; 1175 while (len > 0) { 1176 len--; 1177 if (name[len] == '/' || name[len] == '\\') { 1178 break; 1179 } 1180 } 1181 if (len < 4) { 1182 len++; 1183 break; 1184 } 1185 } else { 1186 /* 1187 * We do have '/..' 1188 */ 1189 1190 len -= 3; 1191 remove++; 1192 } 1193 } 1194 1195 if (len < 4) { 1196 if (len == 0) { 1197 /* 1198 * Not sure if this is possible, but we pass it on anyway. 1199 */ 1200 } else if (len == 1 && (name[0] == '/' || name[0] == '\\')) { 1201 /* 1202 * Path is pointing to the root volume. 1203 */ 1204 1205 return 1; 1206 } else if ((name[1] == ':') 1207 && (len == 2 || (name[2] == '/' || name[2] == '\\'))) { 1208 /* 1209 * Path is of the form 'x:' or 'x:/' or 'x:\' 1210 */ 1211 1212 return 1; 1213 } 1214 } 1215 1216 return 0; 1217} 1218 1219/* 1220 * Does the given path represent a reserved window path name? If not return 0, 1221 * if true, return the number of characters of the path that we actually want 1222 * (not any trailing :). 1223 */ 1224 1225static int 1226WinIsReserved( 1227 const char *path) /* Path in UTF-8 */ 1228{ 1229 if ((path[0] == 'c' || path[0] == 'C') 1230 && (path[1] == 'o' || path[1] == 'O')) { 1231 if ((path[2] == 'm' || path[2] == 'M') 1232 && path[3] >= '1' && path[3] <= '4') { 1233 /* 1234 * May have match for 'com[1-4]:?', which is a serial port. 1235 */ 1236 1237 if (path[4] == '\0') { 1238 return 4; 1239 } else if (path [4] == ':' && path[5] == '\0') { 1240 return 4; 1241 } 1242 } else if ((path[2] == 'n' || path[2] == 'N') && path[3] == '\0') { 1243 /* 1244 * Have match for 'con' 1245 */ 1246 1247 return 3; 1248 } 1249 1250 } else if ((path[0] == 'l' || path[0] == 'L') 1251 && (path[1] == 'p' || path[1] == 'P') 1252 && (path[2] == 't' || path[2] == 'T')) { 1253 if (path[3] >= '1' && path[3] <= '3') { 1254 /* 1255 * May have match for 'lpt[1-3]:?' 1256 */ 1257 1258 if (path[4] == '\0') { 1259 return 4; 1260 } else if (path [4] == ':' && path[5] == '\0') { 1261 return 4; 1262 } 1263 } 1264 1265 } else if (!strcasecmp(path, "prn") || !strcasecmp(path, "nul") 1266 || !strcasecmp(path, "aux")) { 1267 /* 1268 * Have match for 'prn', 'nul' or 'aux'. 1269 */ 1270 1271 return 3; 1272 } 1273 return 0; 1274} 1275 1276/* 1277 *---------------------------------------------------------------------- 1278 * 1279 * NativeMatchType -- 1280 * 1281 * This function needs a special case for a path which is a root volume, 1282 * because for NTFS root volumes, the getFileAttributesProc returns a 1283 * 'hidden' attribute when it should not. 1284 * 1285 * We never make any calss to a 'get attributes' routine here, since we 1286 * have arranged things so that our caller already knows such 1287 * information. 1288 * 1289 * Results: 1290 * 0 = file doesn't match 1291 * 1 = file matches 1292 * 1293 *---------------------------------------------------------------------- 1294 */ 1295 1296static int 1297NativeMatchType( 1298 int isDrive, /* Is this a drive. */ 1299 DWORD attr, /* We already know the attributes for the 1300 * file. */ 1301 const TCHAR *nativeName, /* Native path to check. */ 1302 Tcl_GlobTypeData *types) /* Type description to match against. */ 1303{ 1304 /* 1305 * 'attr' represents the attributes of the file, but we only want to 1306 * retrieve this info if it is absolutely necessary because it is an 1307 * expensive call. Unfortunately, to deal with hidden files properly, we 1308 * must always retrieve it. 1309 */ 1310 1311 if (types == NULL) { 1312 /* 1313 * If invisible, don't return the file. 1314 */ 1315 1316 if (attr & FILE_ATTRIBUTE_HIDDEN && !isDrive) { 1317 return 0; 1318 } 1319 } else { 1320 if (attr & FILE_ATTRIBUTE_HIDDEN && !isDrive) { 1321 /* 1322 * If invisible. 1323 */ 1324 1325 if ((types->perm == 0) || !(types->perm & TCL_GLOB_PERM_HIDDEN)) { 1326 return 0; 1327 } 1328 } else { 1329 /* 1330 * Visible. 1331 */ 1332 1333 if (types->perm & TCL_GLOB_PERM_HIDDEN) { 1334 return 0; 1335 } 1336 } 1337 1338 if (types->perm != 0) { 1339 if (((types->perm & TCL_GLOB_PERM_RONLY) && 1340 !(attr & FILE_ATTRIBUTE_READONLY)) || 1341 ((types->perm & TCL_GLOB_PERM_R) && 1342 (0 /* File exists => R_OK on Windows */)) || 1343 ((types->perm & TCL_GLOB_PERM_W) && 1344 (attr & FILE_ATTRIBUTE_READONLY)) || 1345 ((types->perm & TCL_GLOB_PERM_X) && 1346 (!(attr & FILE_ATTRIBUTE_DIRECTORY) 1347 && !NativeIsExec(nativeName)))) { 1348 return 0; 1349 } 1350 } 1351 if ((types->type & TCL_GLOB_TYPE_DIR) 1352 && (attr & FILE_ATTRIBUTE_DIRECTORY)) { 1353 /* 1354 * Quicker test for directory, which is a common case. 1355 */ 1356 1357 return 1; 1358 1359 } else if (types->type != 0) { 1360 unsigned short st_mode; 1361 int isExec = NativeIsExec(nativeName); 1362 1363 st_mode = NativeStatMode(attr, 0, isExec); 1364 1365 /* 1366 * In order bcdpfls as in 'find -t' 1367 */ 1368 1369 if (((types->type&TCL_GLOB_TYPE_BLOCK) && S_ISBLK(st_mode)) || 1370 ((types->type&TCL_GLOB_TYPE_CHAR) && S_ISCHR(st_mode)) || 1371 ((types->type&TCL_GLOB_TYPE_DIR) && S_ISDIR(st_mode)) || 1372 ((types->type&TCL_GLOB_TYPE_PIPE) && S_ISFIFO(st_mode)) || 1373#ifdef S_ISSOCK 1374 ((types->type&TCL_GLOB_TYPE_SOCK) && S_ISSOCK(st_mode)) || 1375#endif 1376 ((types->type&TCL_GLOB_TYPE_FILE) && S_ISREG(st_mode))) { 1377 /* 1378 * Do nothing - this file is ok. 1379 */ 1380 } else { 1381#ifdef S_ISLNK 1382 if (types->type & TCL_GLOB_TYPE_LINK) { 1383 st_mode = NativeStatMode(attr, 1, isExec); 1384 if (S_ISLNK(st_mode)) { 1385 return 1; 1386 } 1387 } 1388#endif 1389 return 0; 1390 } 1391 } 1392 } 1393 return 1; 1394} 1395 1396/* 1397 *---------------------------------------------------------------------- 1398 * 1399 * TclpGetUserHome -- 1400 * 1401 * This function takes the passed in user name and finds the 1402 * corresponding home directory specified in the password file. 1403 * 1404 * Results: 1405 * The result is a pointer to a string specifying the user's home 1406 * directory, or NULL if the user's home directory could not be 1407 * determined. Storage for the result string is allocated in bufferPtr; 1408 * the caller must call Tcl_DStringFree() when the result is no longer 1409 * needed. 1410 * 1411 * Side effects: 1412 * None. 1413 * 1414 *---------------------------------------------------------------------- 1415 */ 1416 1417char * 1418TclpGetUserHome( 1419 const char *name, /* User name for desired home directory. */ 1420 Tcl_DString *bufferPtr) /* Uninitialized or free DString filled with 1421 * name of user's home directory. */ 1422{ 1423 char *result; 1424 HINSTANCE netapiInst; 1425 1426 result = NULL; 1427 Tcl_DStringInit(bufferPtr); 1428 1429 netapiInst = LoadLibraryA("netapi32.dll"); 1430 if (netapiInst != NULL) { 1431 NETAPIBUFFERFREEPROC *netApiBufferFreeProc; 1432 NETGETDCNAMEPROC *netGetDCNameProc; 1433 NETUSERGETINFOPROC *netUserGetInfoProc; 1434 1435 netApiBufferFreeProc = (NETAPIBUFFERFREEPROC *) 1436 GetProcAddress(netapiInst, "NetApiBufferFree"); 1437 netGetDCNameProc = (NETGETDCNAMEPROC *) 1438 GetProcAddress(netapiInst, "NetGetDCName"); 1439 netUserGetInfoProc = (NETUSERGETINFOPROC *) 1440 GetProcAddress(netapiInst, "NetUserGetInfo"); 1441 if ((netUserGetInfoProc != NULL) && (netGetDCNameProc != NULL) 1442 && (netApiBufferFreeProc != NULL)) { 1443 USER_INFO_1 *uiPtr, **uiPtrPtr = &uiPtr; 1444 Tcl_DString ds; 1445 int nameLen, badDomain; 1446 char *domain; 1447 WCHAR *wName, *wHomeDir, *wDomain, **wDomainPtr = &wDomain; 1448 WCHAR buf[MAX_PATH]; 1449 1450 badDomain = 0; 1451 nameLen = -1; 1452 wDomain = NULL; 1453 domain = strchr(name, '@'); 1454 if (domain != NULL) { 1455 Tcl_DStringInit(&ds); 1456 wName = Tcl_UtfToUniCharDString(domain + 1, -1, &ds); 1457 badDomain = (netGetDCNameProc)(NULL, wName, 1458 (LPBYTE *) wDomainPtr); 1459 Tcl_DStringFree(&ds); 1460 nameLen = domain - name; 1461 } 1462 if (badDomain == 0) { 1463 Tcl_DStringInit(&ds); 1464 wName = Tcl_UtfToUniCharDString(name, nameLen, &ds); 1465 if ((netUserGetInfoProc)(wDomain, wName, 1, 1466 (LPBYTE *) uiPtrPtr) == 0) { 1467 wHomeDir = uiPtr->usri1_home_dir; 1468 if ((wHomeDir != NULL) && (wHomeDir[0] != L'\0')) { 1469 Tcl_UniCharToUtfDString(wHomeDir, lstrlenW(wHomeDir), 1470 bufferPtr); 1471 } else { 1472 /* 1473 * User exists but has no home dir. Return 1474 * "{Windows Drive}:/users/default". 1475 */ 1476 1477 GetWindowsDirectoryW(buf, MAX_PATH); 1478 Tcl_UniCharToUtfDString(buf, 2, bufferPtr); 1479 Tcl_DStringAppend(bufferPtr, "/users/default", -1); 1480 } 1481 result = Tcl_DStringValue(bufferPtr); 1482 (*netApiBufferFreeProc)((void *) uiPtr); 1483 } 1484 Tcl_DStringFree(&ds); 1485 } 1486 if (wDomain != NULL) { 1487 (*netApiBufferFreeProc)((void *) wDomain); 1488 } 1489 } 1490 FreeLibrary(netapiInst); 1491 } 1492 if (result == NULL) { 1493 /* 1494 * Look in the "Password Lists" section of system.ini for the local 1495 * user. There are also entries in that section that begin with a "*" 1496 * character that are used by Windows for other purposes; ignore user 1497 * names beginning with a "*". 1498 */ 1499 1500 char buf[MAX_PATH]; 1501 1502 if (name[0] != '*') { 1503 if (GetPrivateProfileStringA("Password Lists", name, "", buf, 1504 MAX_PATH, "system.ini") > 0) { 1505 /* 1506 * User exists, but there is no such thing as a home directory 1507 * in system.ini. Return "{Windows drive}:/". 1508 */ 1509 1510 GetWindowsDirectoryA(buf, MAX_PATH); 1511 Tcl_DStringAppend(bufferPtr, buf, 3); 1512 result = Tcl_DStringValue(bufferPtr); 1513 } 1514 } 1515 } 1516 1517 return result; 1518} 1519 1520/* 1521 *--------------------------------------------------------------------------- 1522 * 1523 * NativeAccess -- 1524 * 1525 * This function replaces the library version of access(), fixing the 1526 * following bugs: 1527 * 1528 * 1. access() returns that all files have execute permission. 1529 * 1530 * Results: 1531 * See access documentation. 1532 * 1533 * Side effects: 1534 * See access documentation. 1535 * 1536 *--------------------------------------------------------------------------- 1537 */ 1538 1539static int 1540NativeAccess( 1541 const TCHAR *nativePath, /* Path of file to access, native encoding. */ 1542 int mode) /* Permission setting. */ 1543{ 1544 DWORD attr; 1545 1546 attr = (*tclWinProcs->getFileAttributesProc)(nativePath); 1547 1548 if (attr == 0xffffffff) { 1549 /* 1550 * File doesn't exist. 1551 */ 1552 1553 TclWinConvertError(GetLastError()); 1554 return -1; 1555 } 1556 1557 if ((mode & W_OK) 1558 && (tclWinProcs->getFileSecurityProc == NULL) 1559 && (attr & FILE_ATTRIBUTE_READONLY)) { 1560 /* 1561 * We don't have the advanced 'getFileSecurityProc', and our 1562 * attributes say the file is not writable. If we do have 1563 * 'getFileSecurityProc', we'll do a more robust XP-related check 1564 * below. 1565 */ 1566 1567 Tcl_SetErrno(EACCES); 1568 return -1; 1569 } 1570 1571 if (mode & X_OK) { 1572 if (!(attr & FILE_ATTRIBUTE_DIRECTORY) && !NativeIsExec(nativePath)) { 1573 /* 1574 * It's not a directory and doesn't have the correct extension. 1575 * Therefore it can't be executable 1576 */ 1577 1578 Tcl_SetErrno(EACCES); 1579 return -1; 1580 } 1581 } 1582 1583 /* 1584 * It looks as if the permissions are ok, but if we are on NT, 2000 or XP, 1585 * we have a more complex permissions structure so we try to check that. 1586 * The code below is remarkably complex for such a simple thing as finding 1587 * what permissions the OS has set for a file. 1588 * 1589 * If we are simply checking for file existence, then we don't need all 1590 * these complications (which are really quite slow: with this code 'file 1591 * readable' is 5-6 times slower than 'file exists'). 1592 */ 1593 1594 if ((mode != F_OK) && (tclWinProcs->getFileSecurityProc != NULL)) { 1595 SECURITY_DESCRIPTOR *sdPtr = NULL; 1596 unsigned long size; 1597 GENERIC_MAPPING genMap; 1598 HANDLE hToken = NULL; 1599 DWORD desiredAccess = 0, grantedAccess = 0; 1600 BOOL accessYesNo = FALSE; 1601 PRIVILEGE_SET privSet; 1602 DWORD privSetSize = sizeof(PRIVILEGE_SET); 1603 int error; 1604 1605 /* 1606 * First find out how big the buffer needs to be 1607 */ 1608 1609 size = 0; 1610 (*tclWinProcs->getFileSecurityProc)(nativePath, 1611 OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION 1612 | DACL_SECURITY_INFORMATION, 0, 0, &size); 1613 1614 /* 1615 * Should have failed with ERROR_INSUFFICIENT_BUFFER 1616 */ 1617 1618 error = GetLastError(); 1619 if (error != ERROR_INSUFFICIENT_BUFFER) { 1620 /* 1621 * Most likely case is ERROR_ACCESS_DENIED, which we will convert 1622 * to EACCES - just what we want! 1623 */ 1624 1625 TclWinConvertError((DWORD) error); 1626 return -1; 1627 } 1628 1629 /* 1630 * Now size contains the size of buffer needed. 1631 */ 1632 1633 sdPtr = (SECURITY_DESCRIPTOR *) HeapAlloc(GetProcessHeap(), 0, size); 1634 1635 if (sdPtr == NULL) { 1636 goto accessError; 1637 } 1638 1639 /* 1640 * Call GetFileSecurity() for real. 1641 */ 1642 1643 if (!(*tclWinProcs->getFileSecurityProc)(nativePath, 1644 OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION 1645 | DACL_SECURITY_INFORMATION, sdPtr, size, &size)) { 1646 /* 1647 * Error getting owner SD 1648 */ 1649 1650 goto accessError; 1651 } 1652 1653 /* 1654 * Perform security impersonation of the user and open the resulting 1655 * thread token. 1656 */ 1657 1658 if (!(*tclWinProcs->impersonateSelfProc)(SecurityImpersonation)) { 1659 /* 1660 * Unable to perform security impersonation. 1661 */ 1662 1663 goto accessError; 1664 } 1665 if (!(*tclWinProcs->openThreadTokenProc)(GetCurrentThread(), 1666 TOKEN_DUPLICATE | TOKEN_QUERY, FALSE, &hToken)) { 1667 /* 1668 * Unable to get current thread's token. 1669 */ 1670 1671 goto accessError; 1672 } 1673 1674 (*tclWinProcs->revertToSelfProc)(); 1675 1676 /* 1677 * Setup desiredAccess according to the access priveleges we are 1678 * checking. 1679 */ 1680 1681 if (mode & R_OK) { 1682 desiredAccess |= FILE_GENERIC_READ; 1683 } 1684 if (mode & W_OK) { 1685 desiredAccess |= FILE_GENERIC_WRITE; 1686 } 1687 if (mode & X_OK) { 1688 desiredAccess |= FILE_GENERIC_EXECUTE; 1689 } 1690 1691 memset(&genMap, 0x0, sizeof(GENERIC_MAPPING)); 1692 genMap.GenericRead = FILE_GENERIC_READ; 1693 genMap.GenericWrite = FILE_GENERIC_WRITE; 1694 genMap.GenericExecute = FILE_GENERIC_EXECUTE; 1695 genMap.GenericAll = FILE_ALL_ACCESS; 1696 1697 /* 1698 * Perform access check using the token. 1699 */ 1700 1701 if (!(*tclWinProcs->accessCheckProc)(sdPtr, hToken, desiredAccess, 1702 &genMap, &privSet, &privSetSize, &grantedAccess, 1703 &accessYesNo)) { 1704 /* 1705 * Unable to perform access check. 1706 */ 1707 1708 accessError: 1709 TclWinConvertError(GetLastError()); 1710 if (sdPtr != NULL) { 1711 HeapFree(GetProcessHeap(), 0, sdPtr); 1712 } 1713 if (hToken != NULL) { 1714 CloseHandle(hToken); 1715 } 1716 return -1; 1717 } 1718 1719 /* 1720 * Clean up. 1721 */ 1722 1723 HeapFree(GetProcessHeap(), 0, sdPtr); 1724 CloseHandle(hToken); 1725 if (!accessYesNo) { 1726 Tcl_SetErrno(EACCES); 1727 return -1; 1728 } 1729 1730 /* 1731 * For directories the above checks are ok. For files, though, we must 1732 * still check the 'attr' value. 1733 */ 1734 1735 if ((mode & W_OK) 1736 && !(attr & FILE_ATTRIBUTE_DIRECTORY) 1737 && (attr & FILE_ATTRIBUTE_READONLY)) { 1738 Tcl_SetErrno(EACCES); 1739 return -1; 1740 } 1741 } 1742 return 0; 1743} 1744 1745/* 1746 *---------------------------------------------------------------------- 1747 * 1748 * NativeIsExec -- 1749 * 1750 * Determines if a path is executable. On windows this is simply defined 1751 * by whether the path ends in any of ".exe", ".com", or ".bat" 1752 * 1753 * Results: 1754 * 1 = executable, 0 = not. 1755 * 1756 *---------------------------------------------------------------------- 1757 */ 1758 1759static int 1760NativeIsExec( 1761 const TCHAR *nativePath) 1762{ 1763 if (tclWinProcs->useWide) { 1764 const WCHAR *path = (const WCHAR *) nativePath; 1765 int len = wcslen(path); 1766 1767 if (len < 5) { 1768 return 0; 1769 } 1770 1771 if (path[len-4] != L'.') { 1772 return 0; 1773 } 1774 1775 /* 1776 * Use wide-char case-insensitive comparison 1777 */ 1778 1779 if ((_wcsicmp(path+len-3, L"exe") == 0) 1780 || (_wcsicmp(path+len-3, L"com") == 0) 1781 || (_wcsicmp(path+len-3, L"bat") == 0)) { 1782 return 1; 1783 } 1784 } else { 1785 const char *p; 1786 1787 /* 1788 * We are only looking for pure ascii. 1789 */ 1790 1791 p = strrchr((const char *) nativePath, '.'); 1792 if (p != NULL) { 1793 p++; 1794 1795 /* 1796 * Note: in the old code, stat considered '.pif' files as 1797 * executable, whereas access did not. 1798 */ 1799 1800 if ((strcasecmp(p, "exe") == 0) 1801 || (strcasecmp(p, "com") == 0) 1802 || (strcasecmp(p, "bat") == 0)) { 1803 /* 1804 * File that ends with .exe, .com, or .bat is executable. 1805 */ 1806 1807 return 1; 1808 } 1809 } 1810 } 1811 return 0; 1812} 1813 1814/* 1815 *---------------------------------------------------------------------- 1816 * 1817 * TclpObjChdir -- 1818 * 1819 * This function replaces the library version of chdir(). 1820 * 1821 * Results: 1822 * See chdir() documentation. 1823 * 1824 * Side effects: 1825 * See chdir() documentation. 1826 * 1827 *---------------------------------------------------------------------- 1828 */ 1829 1830int 1831TclpObjChdir( 1832 Tcl_Obj *pathPtr) /* Path to new working directory. */ 1833{ 1834 int result; 1835 const TCHAR *nativePath; 1836#ifdef __CYGWIN__ 1837 extern int cygwin_conv_to_posix_path(const char *, char *); 1838 char posixPath[MAX_PATH+1]; 1839 const char *path; 1840 Tcl_DString ds; 1841#endif /* __CYGWIN__ */ 1842 1843 nativePath = (const TCHAR *) Tcl_FSGetNativePath(pathPtr); 1844 1845#ifdef __CYGWIN__ 1846 /* 1847 * Cygwin chdir only groks POSIX path. 1848 */ 1849 1850 path = Tcl_WinTCharToUtf(nativePath, -1, &ds); 1851 cygwin_conv_to_posix_path(path, posixPath); 1852 result = (chdir(posixPath) == 0 ? 1 : 0); 1853 Tcl_DStringFree(&ds); 1854#else /* __CYGWIN__ */ 1855 result = (*tclWinProcs->setCurrentDirectoryProc)(nativePath); 1856#endif /* __CYGWIN__ */ 1857 1858 if (result == 0) { 1859 TclWinConvertError(GetLastError()); 1860 return -1; 1861 } 1862 return 0; 1863} 1864 1865#ifdef __CYGWIN__ 1866/* 1867 *--------------------------------------------------------------------------- 1868 * 1869 * TclpReadlink -- 1870 * 1871 * This function replaces the library version of readlink(). 1872 * 1873 * Results: 1874 * The result is a pointer to a string specifying the contents of the 1875 * symbolic link given by 'path', or NULL if the symbolic link could not 1876 * be read. Storage for the result string is allocated in bufferPtr; the 1877 * caller must call Tcl_DStringFree() when the result is no longer 1878 * needed. 1879 * 1880 * Side effects: 1881 * See readlink() documentation. 1882 * 1883 *--------------------------------------------------------------------------- 1884 */ 1885 1886char * 1887TclpReadlink( 1888 const char *path, /* Path of file to readlink (UTF-8). */ 1889 Tcl_DString *linkPtr) /* Uninitialized or free DString filled with 1890 * contents of link (UTF-8). */ 1891{ 1892 char link[MAXPATHLEN]; 1893 int length; 1894 char *native; 1895 Tcl_DString ds; 1896 1897 native = Tcl_UtfToExternalDString(NULL, path, -1, &ds); 1898 length = readlink(native, link, sizeof(link)); /* INTL: Native. */ 1899 Tcl_DStringFree(&ds); 1900 1901 if (length < 0) { 1902 return NULL; 1903 } 1904 1905 Tcl_ExternalToUtfDString(NULL, link, length, linkPtr); 1906 return Tcl_DStringValue(linkPtr); 1907} 1908#endif /* __CYGWIN__ */ 1909 1910/* 1911 *---------------------------------------------------------------------- 1912 * 1913 * TclpGetCwd -- 1914 * 1915 * This function replaces the library version of getcwd(). (Obsolete 1916 * function, only retained for old extensions which may call it 1917 * directly). 1918 * 1919 * Results: 1920 * The result is a pointer to a string specifying the current directory, 1921 * or NULL if the current directory could not be determined. If NULL is 1922 * returned, an error message is left in the interp's result. Storage for 1923 * the result string is allocated in bufferPtr; the caller must call 1924 * Tcl_DStringFree() when the result is no longer needed. 1925 * 1926 * Side effects: 1927 * None. 1928 * 1929 *---------------------------------------------------------------------- 1930 */ 1931 1932const char * 1933TclpGetCwd( 1934 Tcl_Interp *interp, /* If non-NULL, used for error reporting. */ 1935 Tcl_DString *bufferPtr) /* Uninitialized or free DString filled with 1936 * name of current directory. */ 1937{ 1938 WCHAR buffer[MAX_PATH]; 1939 char *p; 1940 1941 if ((*tclWinProcs->getCurrentDirectoryProc)(MAX_PATH, buffer) == 0) { 1942 TclWinConvertError(GetLastError()); 1943 if (interp != NULL) { 1944 Tcl_AppendResult(interp, "error getting working directory name: ", 1945 Tcl_PosixError(interp), NULL); 1946 } 1947 return NULL; 1948 } 1949 1950 /* 1951 * Watch for the weird Windows c:\\UNC syntax. 1952 */ 1953 1954 if (tclWinProcs->useWide) { 1955 WCHAR *native; 1956 1957 native = (WCHAR *) buffer; 1958 if ((native[0] != '\0') && (native[1] == ':') 1959 && (native[2] == '\\') && (native[3] == '\\')) { 1960 native += 2; 1961 } 1962 Tcl_WinTCharToUtf((TCHAR *) native, -1, bufferPtr); 1963 } else { 1964 char *native; 1965 1966 native = (char *) buffer; 1967 if ((native[0] != '\0') && (native[1] == ':') 1968 && (native[2] == '\\') && (native[3] == '\\')) { 1969 native += 2; 1970 } 1971 Tcl_WinTCharToUtf((TCHAR *) native, -1, bufferPtr); 1972 } 1973 1974 /* 1975 * Convert to forward slashes for easier use in scripts. 1976 */ 1977 1978 for (p = Tcl_DStringValue(bufferPtr); *p != '\0'; p++) { 1979 if (*p == '\\') { 1980 *p = '/'; 1981 } 1982 } 1983 return Tcl_DStringValue(bufferPtr); 1984} 1985 1986int 1987TclpObjStat( 1988 Tcl_Obj *pathPtr, /* Path of file to stat. */ 1989 Tcl_StatBuf *statPtr) /* Filled with results of stat call. */ 1990{ 1991 /* 1992 * Ensure correct file sizes by forcing the OS to write any pending data 1993 * to disk. This is done only for channels which are dirty, i.e. have been 1994 * written to since the last flush here. 1995 */ 1996 1997 TclWinFlushDirtyChannels(); 1998 1999 return NativeStat((const TCHAR *) Tcl_FSGetNativePath(pathPtr), 2000 statPtr, 0); 2001} 2002 2003/* 2004 *---------------------------------------------------------------------- 2005 * 2006 * NativeStat -- 2007 * 2008 * This function replaces the library version of stat(), fixing the 2009 * following bugs: 2010 * 2011 * 1. stat("c:") returns an error. 2012 * 2. Borland stat() return time in GMT instead of localtime. 2013 * 3. stat("\\server\mount") would return error. 2014 * 4. Accepts slashes or backslashes. 2015 * 5. st_dev and st_rdev were wrong for UNC paths. 2016 * 2017 * Results: 2018 * See stat documentation. 2019 * 2020 * Side effects: 2021 * See stat documentation. 2022 * 2023 *---------------------------------------------------------------------- 2024 */ 2025 2026static int 2027NativeStat( 2028 const TCHAR *nativePath, /* Path of file to stat */ 2029 Tcl_StatBuf *statPtr, /* Filled with results of stat call. */ 2030 int checkLinks) /* If non-zero, behave like 'lstat' */ 2031{ 2032 DWORD attr; 2033 int dev, nlink = 1; 2034 unsigned short mode; 2035 unsigned int inode = 0; 2036 HANDLE fileHandle; 2037 2038 /* 2039 * If we can use 'createFile' on this, then we can use the resulting 2040 * fileHandle to read more information (nlink, ino) than we can get from 2041 * other attributes reading APIs. If not, then we try to fall back on the 2042 * 'getFileAttributesExProc', and if that isn't available, then on even 2043 * simpler routines. 2044 */ 2045 2046 fileHandle = (tclWinProcs->createFileProc)(nativePath, GENERIC_READ, 2047 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 2048 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL); 2049 2050 if (fileHandle != INVALID_HANDLE_VALUE) { 2051 BY_HANDLE_FILE_INFORMATION data; 2052 2053 if (GetFileInformationByHandle(fileHandle,&data) != TRUE) { 2054 CloseHandle(fileHandle); 2055 Tcl_SetErrno(ENOENT); 2056 return -1; 2057 } 2058 CloseHandle(fileHandle); 2059 2060 attr = data.dwFileAttributes; 2061 2062 statPtr->st_size = ((Tcl_WideInt) data.nFileSizeLow) | 2063 (((Tcl_WideInt) data.nFileSizeHigh) << 32); 2064 statPtr->st_atime = ToCTime(data.ftLastAccessTime); 2065 statPtr->st_mtime = ToCTime(data.ftLastWriteTime); 2066 statPtr->st_ctime = ToCTime(data.ftCreationTime); 2067 2068 /* 2069 * On Unix, for directories, nlink apparently depends on the number of 2070 * files in the directory. We could calculate that, but it would be a 2071 * bit of a performance penalty, I think. Hence we just use what 2072 * Windows gives us, which is the same as Unix for files, at least. 2073 */ 2074 2075 nlink = data.nNumberOfLinks; 2076 2077 /* 2078 * Unfortunately our stat definition's inode field (unsigned short) 2079 * will throw away most of the precision we have here, which means we 2080 * can't rely on inode as a unique identifier of a file. We'd really 2081 * like to do something like how we handle 'st_size'. 2082 */ 2083 2084 inode = data.nFileIndexHigh | data.nFileIndexLow; 2085 } else if (tclWinProcs->getFileAttributesExProc != NULL) { 2086 /* 2087 * Fall back on the less capable routines. This means no nlink or ino. 2088 */ 2089 2090 WIN32_FILE_ATTRIBUTE_DATA data; 2091 2092 if ((*tclWinProcs->getFileAttributesExProc)(nativePath, 2093 GetFileExInfoStandard, &data) != TRUE) { 2094 Tcl_SetErrno(ENOENT); 2095 return -1; 2096 } 2097 2098 attr = data.dwFileAttributes; 2099 2100 statPtr->st_size = ((Tcl_WideInt) data.nFileSizeLow) | 2101 (((Tcl_WideInt) data.nFileSizeHigh) << 32); 2102 statPtr->st_atime = ToCTime(data.ftLastAccessTime); 2103 statPtr->st_mtime = ToCTime(data.ftLastWriteTime); 2104 statPtr->st_ctime = ToCTime(data.ftCreationTime); 2105 } else { 2106 /* 2107 * We don't have the faster attributes proc, so we're probably running 2108 * on Win95. 2109 */ 2110 2111 WIN32_FIND_DATAT data; 2112 HANDLE handle; 2113 2114 handle = (*tclWinProcs->findFirstFileProc)(nativePath, &data); 2115 if (handle == INVALID_HANDLE_VALUE) { 2116 /* 2117 * FindFirstFile() doesn't work on root directories, so call 2118 * GetFileAttributes() to see if the specified file exists. 2119 */ 2120 2121 attr = (*tclWinProcs->getFileAttributesProc)(nativePath); 2122 if (attr == INVALID_FILE_ATTRIBUTES) { 2123 Tcl_SetErrno(ENOENT); 2124 return -1; 2125 } 2126 2127 /* 2128 * Make up some fake information for this file. It has the correct 2129 * file attributes and a time of 0. 2130 */ 2131 2132 memset(&data, 0, sizeof(data)); 2133 data.a.dwFileAttributes = attr; 2134 } else { 2135 FindClose(handle); 2136 } 2137 2138 attr = data.a.dwFileAttributes; 2139 2140 statPtr->st_size = ((Tcl_WideInt) data.a.nFileSizeLow) | 2141 (((Tcl_WideInt) data.a.nFileSizeHigh) << 32); 2142 statPtr->st_atime = ToCTime(data.a.ftLastAccessTime); 2143 statPtr->st_mtime = ToCTime(data.a.ftLastWriteTime); 2144 statPtr->st_ctime = ToCTime(data.a.ftCreationTime); 2145 } 2146 2147 dev = NativeDev(nativePath); 2148 mode = NativeStatMode(attr, checkLinks, NativeIsExec(nativePath)); 2149 2150 statPtr->st_dev = (dev_t) dev; 2151 statPtr->st_ino = inode; 2152 statPtr->st_mode = mode; 2153 statPtr->st_nlink = nlink; 2154 statPtr->st_uid = 0; 2155 statPtr->st_gid = 0; 2156 statPtr->st_rdev = (dev_t) dev; 2157 return 0; 2158} 2159 2160/* 2161 *---------------------------------------------------------------------- 2162 * 2163 * NativeDev -- 2164 * 2165 * Calculate just the 'st_dev' field of a 'stat' structure. 2166 * 2167 *---------------------------------------------------------------------- 2168 */ 2169 2170static int 2171NativeDev( 2172 const TCHAR *nativePath) /* Full path of file to stat */ 2173{ 2174 int dev; 2175 Tcl_DString ds; 2176 WCHAR nativeFullPath[MAX_PATH]; 2177 TCHAR *nativePart; 2178 const char *fullPath; 2179 2180 (*tclWinProcs->getFullPathNameProc)(nativePath, MAX_PATH, 2181 nativeFullPath, &nativePart); 2182 2183 fullPath = Tcl_WinTCharToUtf((TCHAR *) nativeFullPath, -1, &ds); 2184 2185 if ((fullPath[0] == '\\') && (fullPath[1] == '\\')) { 2186 const char *p; 2187 DWORD dw; 2188 const TCHAR *nativeVol; 2189 Tcl_DString volString; 2190 2191 p = strchr(fullPath + 2, '\\'); 2192 p = strchr(p + 1, '\\'); 2193 if (p == NULL) { 2194 /* 2195 * Add terminating backslash to fullpath or GetVolumeInformation() 2196 * won't work. 2197 */ 2198 2199 fullPath = Tcl_DStringAppend(&ds, "\\", 1); 2200 p = fullPath + Tcl_DStringLength(&ds); 2201 } else { 2202 p++; 2203 } 2204 nativeVol = Tcl_WinUtfToTChar(fullPath, p - fullPath, &volString); 2205 dw = (DWORD) -1; 2206 (*tclWinProcs->getVolumeInformationProc)(nativeVol, NULL, 0, &dw, 2207 NULL, NULL, NULL, 0); 2208 2209 /* 2210 * GetFullPathName() turns special devices like "NUL" into "\\.\NUL", 2211 * but GetVolumeInformation() returns failure for "\\.\NUL". This will 2212 * cause "NUL" to get a drive number of -1, which makes about as much 2213 * sense as anything since the special devices don't live on any 2214 * drive. 2215 */ 2216 2217 dev = dw; 2218 Tcl_DStringFree(&volString); 2219 } else if ((fullPath[0] != '\0') && (fullPath[1] == ':')) { 2220 dev = Tcl_UniCharToLower(fullPath[0]) - 'a'; 2221 } else { 2222 dev = -1; 2223 } 2224 Tcl_DStringFree(&ds); 2225 2226 return dev; 2227} 2228 2229/* 2230 *---------------------------------------------------------------------- 2231 * 2232 * NativeStatMode -- 2233 * 2234 * Calculate just the 'st_mode' field of a 'stat' structure. 2235 * 2236 * In many places we don't need the full stat structure, and it's much 2237 * faster just to calculate these pieces, if that's all we need. 2238 * 2239 *---------------------------------------------------------------------- 2240 */ 2241 2242static unsigned short 2243NativeStatMode( 2244 DWORD attr, 2245 int checkLinks, 2246 int isExec) 2247{ 2248 int mode; 2249 2250 if (checkLinks && (attr & FILE_ATTRIBUTE_REPARSE_POINT)) { 2251 /* 2252 * It is a link. 2253 */ 2254 2255 mode = S_IFLNK; 2256 } else { 2257 mode = (attr & FILE_ATTRIBUTE_DIRECTORY) ? S_IFDIR|S_IEXEC : S_IFREG; 2258 } 2259 mode |= (attr & FILE_ATTRIBUTE_READONLY) ? S_IREAD : S_IREAD|S_IWRITE; 2260 if (isExec) { 2261 mode |= S_IEXEC; 2262 } 2263 2264 /* 2265 * Propagate the S_IREAD, S_IWRITE, S_IEXEC bits to the group and other 2266 * positions. 2267 */ 2268 2269 mode |= (mode & 0x0700) >> 3; 2270 mode |= (mode & 0x0700) >> 6; 2271 return (unsigned short) mode; 2272} 2273 2274/* 2275 *------------------------------------------------------------------------ 2276 * 2277 * ToCTime -- 2278 * 2279 * Converts a Windows FILETIME to a time_t in UTC. 2280 * 2281 * Results: 2282 * Returns the count of seconds from the Posix epoch. 2283 * 2284 *------------------------------------------------------------------------ 2285 */ 2286 2287static time_t 2288ToCTime( 2289 FILETIME fileTime) /* UTC time */ 2290{ 2291 LARGE_INTEGER convertedTime; 2292 2293 convertedTime.LowPart = fileTime.dwLowDateTime; 2294 convertedTime.HighPart = (LONG) fileTime.dwHighDateTime; 2295 2296 return (time_t) ((convertedTime.QuadPart - 2297 (Tcl_WideInt) POSIX_EPOCH_AS_FILETIME) / (Tcl_WideInt) 10000000); 2298} 2299 2300/* 2301 *------------------------------------------------------------------------ 2302 * 2303 * FromCTime -- 2304 * 2305 * Converts a time_t to a Windows FILETIME 2306 * 2307 * Results: 2308 * Returns the count of 100-ns ticks seconds from the Windows epoch. 2309 * 2310 *------------------------------------------------------------------------ 2311 */ 2312 2313static void 2314FromCTime( 2315 time_t posixTime, 2316 FILETIME *fileTime) /* UTC Time */ 2317{ 2318 LARGE_INTEGER convertedTime; 2319 convertedTime.QuadPart = ((LONGLONG) posixTime) * 10000000 2320 + POSIX_EPOCH_AS_FILETIME; 2321 fileTime->dwLowDateTime = convertedTime.LowPart; 2322 fileTime->dwHighDateTime = convertedTime.HighPart; 2323} 2324 2325/* 2326 *--------------------------------------------------------------------------- 2327 * 2328 * TclpGetNativeCwd -- 2329 * 2330 * This function replaces the library version of getcwd(). 2331 * 2332 * Results: 2333 * The input and output are filesystem paths in native form. The result 2334 * is either the given clientData, if the working directory hasn't 2335 * changed, or a new clientData (owned by our caller), giving the new 2336 * native path, or NULL if the current directory could not be determined. 2337 * If NULL is returned, the caller can examine the standard posix error 2338 * codes to determine the cause of the problem. 2339 * 2340 * Side effects: 2341 * None. 2342 * 2343 *---------------------------------------------------------------------- 2344 */ 2345 2346ClientData 2347TclpGetNativeCwd( 2348 ClientData clientData) 2349{ 2350 WCHAR buffer[MAX_PATH]; 2351 2352 if ((*tclWinProcs->getCurrentDirectoryProc)(MAX_PATH, buffer) == 0) { 2353 TclWinConvertError(GetLastError()); 2354 return NULL; 2355 } 2356 2357 if (clientData != NULL) { 2358 if (tclWinProcs->useWide) { 2359 /* 2360 * Unicode representation when running on NT/2K/XP. 2361 */ 2362 2363 if (wcscmp((const WCHAR*)clientData, (const WCHAR*)buffer) == 0) { 2364 return clientData; 2365 } 2366 } else { 2367 /* 2368 * ANSI representation when running on 95/98/ME. 2369 */ 2370 2371 if (strcmp((const char*) clientData, (const char*) buffer) == 0) { 2372 return clientData; 2373 } 2374 } 2375 } 2376 2377 return TclNativeDupInternalRep((ClientData) buffer); 2378} 2379 2380int 2381TclpObjAccess( 2382 Tcl_Obj *pathPtr, 2383 int mode) 2384{ 2385 return NativeAccess((const TCHAR *) Tcl_FSGetNativePath(pathPtr), mode); 2386} 2387 2388int 2389TclpObjLstat( 2390 Tcl_Obj *pathPtr, 2391 Tcl_StatBuf *statPtr) 2392{ 2393 /* 2394 * Ensure correct file sizes by forcing the OS to write any pending data 2395 * to disk. This is done only for channels which are dirty, i.e. have been 2396 * written to since the last flush here. 2397 */ 2398 2399 TclWinFlushDirtyChannels(); 2400 2401 return NativeStat((const TCHAR *) Tcl_FSGetNativePath(pathPtr), 2402 statPtr, 1); 2403} 2404 2405#ifdef S_IFLNK 2406Tcl_Obj * 2407TclpObjLink( 2408 Tcl_Obj *pathPtr, 2409 Tcl_Obj *toPtr, 2410 int linkAction) 2411{ 2412 if (toPtr != NULL) { 2413 int res; 2414 TCHAR *LinkTarget; 2415 TCHAR *LinkSource = (TCHAR *) Tcl_FSGetNativePath(pathPtr); 2416 Tcl_Obj *normalizedToPtr = Tcl_FSGetNormalizedPath(NULL, toPtr); 2417 2418 if (normalizedToPtr == NULL) { 2419 return NULL; 2420 } 2421 2422 LinkTarget = (TCHAR *) Tcl_FSGetNativePath(normalizedToPtr); 2423 2424 if (LinkSource == NULL || LinkTarget == NULL) { 2425 return NULL; 2426 } 2427 res = WinLink(LinkSource, LinkTarget, linkAction); 2428 if (res == 0) { 2429 return toPtr; 2430 } else { 2431 return NULL; 2432 } 2433 } else { 2434 TCHAR *LinkSource = (TCHAR *) Tcl_FSGetNativePath(pathPtr); 2435 2436 if (LinkSource == NULL) { 2437 return NULL; 2438 } 2439 return WinReadLink(LinkSource); 2440 } 2441} 2442#endif 2443 2444/* 2445 *--------------------------------------------------------------------------- 2446 * 2447 * TclpFilesystemPathType -- 2448 * 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. 2452 * 2453 * Results: 2454 * NULL at present. 2455 * 2456 * Side effects: 2457 * None. 2458 * 2459 *--------------------------------------------------------------------------- 2460 */ 2461 2462Tcl_Obj * 2463TclpFilesystemPathType( 2464 Tcl_Obj *pathPtr) 2465{ 2466#define VOL_BUF_SIZE 32 2467 int found; 2468 WCHAR volType[VOL_BUF_SIZE]; 2469 char *firstSeparator; 2470 const char *path; 2471 Tcl_Obj *normPath = Tcl_FSGetNormalizedPath(NULL, pathPtr); 2472 2473 if (normPath == NULL) { 2474 return NULL; 2475 } 2476 path = Tcl_GetString(normPath); 2477 if (path == NULL) { 2478 return NULL; 2479 } 2480 2481 firstSeparator = strchr(path, '/'); 2482 if (firstSeparator == NULL) { 2483 found = tclWinProcs->getVolumeInformationProc( 2484 Tcl_FSGetNativePath(pathPtr), NULL, 0, NULL, NULL, NULL, 2485 (WCHAR *) volType, VOL_BUF_SIZE); 2486 } else { 2487 Tcl_Obj *driveName = Tcl_NewStringObj(path, firstSeparator - path+1); 2488 2489 Tcl_IncrRefCount(driveName); 2490 found = tclWinProcs->getVolumeInformationProc( 2491 Tcl_FSGetNativePath(driveName), NULL, 0, NULL, NULL, NULL, 2492 (WCHAR *) volType, VOL_BUF_SIZE); 2493 Tcl_DecrRefCount(driveName); 2494 } 2495 2496 if (found == 0) { 2497 return NULL; 2498 } else { 2499 Tcl_DString ds; 2500 Tcl_Obj *objPtr; 2501 2502 Tcl_WinTCharToUtf((const char *) volType, -1, &ds); 2503 objPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), 2504 Tcl_DStringLength(&ds)); 2505 Tcl_DStringFree(&ds); 2506 return objPtr; 2507 } 2508#undef VOL_BUF_SIZE 2509} 2510 2511/* 2512 * This define can be turned on to experiment with a different way of 2513 * normalizing paths (using a different Windows API). Unfortunately the new 2514 * path seems to take almost exactly the same amount of time as the old path! 2515 * The primary time taken by normalization is in 2516 * GetFileAttributesEx/FindFirstFile or GetFileAttributesEx/GetLongPathName. 2517 * Conversion to/from native is not a significant factor at all. 2518 * 2519 * Also, since we have to check for symbolic links (reparse points) then we 2520 * have to call GetFileAttributes on each path segment anyway, so there's no 2521 * benefit to doing anything clever there. 2522 */ 2523 2524/* #define TclNORM_LONG_PATH */ 2525 2526/* 2527 *--------------------------------------------------------------------------- 2528 * 2529 * TclpObjNormalizePath -- 2530 * 2531 * This function scans through a path specification and replaces it, in 2532 * place, with a normalized version. This means using the 'longname', and 2533 * expanding any symbolic links contained within the path. 2534 * 2535 * Results: 2536 * The new 'nextCheckpoint' value, giving as far as we could understand 2537 * in the path. 2538 * 2539 * Side effects: 2540 * The pathPtr string, which must contain a valid path, is possibly 2541 * modified in place. 2542 * 2543 *--------------------------------------------------------------------------- 2544 */ 2545 2546int 2547TclpObjNormalizePath( 2548 Tcl_Interp *interp, 2549 Tcl_Obj *pathPtr, 2550 int nextCheckpoint) 2551{ 2552 char *lastValidPathEnd = NULL; 2553 Tcl_DString dsNorm; /* This will hold the normalized string. */ 2554 char *path, *currentPathEndPosition; 2555 Tcl_Obj *temp = NULL; 2556 2557 Tcl_DStringInit(&dsNorm); 2558 path = Tcl_GetString(pathPtr); 2559 2560 if (TclWinGetPlatformId() == VER_PLATFORM_WIN32_WINDOWS) { 2561 /* 2562 * We're on Win95, 98 or ME. There are two assumptions in this block 2563 * of code. First that the native (NULL) encoding is basically ascii, 2564 * and second that symbolic links are not possible. Both of these 2565 * assumptions appear to be true of these operating systems. 2566 */ 2567 2568 int isDrive = 1; 2569 Tcl_DString ds; 2570 2571 currentPathEndPosition = path + nextCheckpoint; 2572 if (*currentPathEndPosition == '/') { 2573 currentPathEndPosition++; 2574 } 2575 2576 while (1) { 2577 char cur = *currentPathEndPosition; 2578 2579 if ((cur=='/' || cur==0) && (path != currentPathEndPosition)) { 2580 /* 2581 * Reached directory separator, or end of string. 2582 */ 2583 2584 const char *nativePath = Tcl_UtfToExternalDString(NULL, path, 2585 currentPathEndPosition - path, &ds); 2586 2587 /* 2588 * Now we convert the tail of the current path to its 'long 2589 * form', and append it to 'dsNorm' which holds the current 2590 * normalized path, if the file exists. 2591 */ 2592 2593 if (isDrive) { 2594 if (GetFileAttributesA(nativePath) 2595 == INVALID_FILE_ATTRIBUTES) { 2596 /* 2597 * File doesn't exist. 2598 */ 2599 2600 if (isDrive) { 2601 int len = WinIsReserved(path); 2602 2603 if (len > 0) { 2604 /* 2605 * Actually it does exist - COM1, etc. 2606 */ 2607 2608 int i; 2609 2610 for (i=0 ; i<len ; i++) { 2611 if (nativePath[i] >= 'a') { 2612 ((char *) nativePath)[i] -= ('a'-'A'); 2613 } 2614 } 2615 Tcl_DStringAppend(&dsNorm, nativePath, len); 2616 lastValidPathEnd = currentPathEndPosition; 2617 } 2618 } 2619 Tcl_DStringFree(&ds); 2620 break; 2621 } 2622 if (nativePath[0] >= 'a') { 2623 ((char *) nativePath)[0] -= ('a' - 'A'); 2624 } 2625 Tcl_DStringAppend(&dsNorm, nativePath, 2626 Tcl_DStringLength(&ds)); 2627 } else { 2628 char *checkDots = NULL; 2629 2630 if (lastValidPathEnd[1] == '.') { 2631 checkDots = lastValidPathEnd + 1; 2632 while (checkDots < currentPathEndPosition) { 2633 if (*checkDots != '.') { 2634 checkDots = NULL; 2635 break; 2636 } 2637 checkDots++; 2638 } 2639 } 2640 if (checkDots != NULL) { 2641 int dotLen = currentPathEndPosition-lastValidPathEnd; 2642 2643 /* 2644 * Path is just dots. We shouldn't really ever see a 2645 * path like that. However, to be nice we at least 2646 * don't mangle the path - we just add the dots as a 2647 * path segment and continue 2648 */ 2649 2650 Tcl_DStringAppend(&dsNorm, (TCHAR *) 2651 (nativePath + Tcl_DStringLength(&ds)-dotLen), 2652 dotLen); 2653 } else { 2654 /* 2655 * Normal path. 2656 */ 2657 2658 WIN32_FIND_DATA fData; 2659 HANDLE handle; 2660 2661 handle = FindFirstFileA(nativePath, &fData); 2662 if (handle == INVALID_HANDLE_VALUE) { 2663 if (GetFileAttributesA(nativePath) 2664 == INVALID_FILE_ATTRIBUTES) { 2665 /* 2666 * File doesn't exist. 2667 */ 2668 2669 Tcl_DStringFree(&ds); 2670 break; 2671 } 2672 2673 /* 2674 * This is usually the '/' in 'c:/' at end of 2675 * string. 2676 */ 2677 2678 Tcl_DStringAppend(&dsNorm,"/", 1); 2679 } else { 2680 char *nativeName; 2681 2682 if (fData.cFileName[0] != '\0') { 2683 nativeName = fData.cFileName; 2684 } else { 2685 nativeName = fData.cAlternateFileName; 2686 } 2687 FindClose(handle); 2688 Tcl_DStringAppend(&dsNorm,"/", 1); 2689 Tcl_DStringAppend(&dsNorm,nativeName,-1); 2690 } 2691 } 2692 } 2693 Tcl_DStringFree(&ds); 2694 lastValidPathEnd = currentPathEndPosition; 2695 if (cur == 0) { 2696 break; 2697 } 2698 2699 /* 2700 * If we get here, we've got past one directory delimiter, so 2701 * we know it is no longer a drive. 2702 */ 2703 2704 isDrive = 0; 2705 } 2706 currentPathEndPosition++; 2707 } 2708 } else { 2709 /* 2710 * We're on WinNT (or 2000 or XP; something with an NT core). 2711 */ 2712 2713 int isDrive = 1; 2714 Tcl_DString ds; 2715 2716 currentPathEndPosition = path + nextCheckpoint; 2717 if (*currentPathEndPosition == '/') { 2718 currentPathEndPosition++; 2719 } 2720 while (1) { 2721 char cur = *currentPathEndPosition; 2722 2723 if ((cur=='/' || cur==0) && (path != currentPathEndPosition)) { 2724 /* 2725 * Reached directory separator, or end of string. 2726 */ 2727 2728 WIN32_FILE_ATTRIBUTE_DATA data; 2729 const char *nativePath = Tcl_WinUtfToTChar(path, 2730 currentPathEndPosition - path, &ds); 2731 2732 if ((*tclWinProcs->getFileAttributesExProc)(nativePath, 2733 GetFileExInfoStandard, &data) != TRUE) { 2734 /* 2735 * File doesn't exist. 2736 */ 2737 2738 if (isDrive) { 2739 int len = WinIsReserved(path); 2740 2741 if (len > 0) { 2742 /* 2743 * Actually it does exist - COM1, etc. 2744 */ 2745 2746 int i; 2747 2748 for (i=0 ; i<len ; i++) { 2749 WCHAR wc = ((WCHAR *) nativePath)[i]; 2750 2751 if (wc >= L'a') { 2752 wc -= (L'a' - L'A'); 2753 ((WCHAR *) nativePath)[i] = wc; 2754 } 2755 } 2756 Tcl_DStringAppend(&dsNorm, nativePath, 2757 (int)(sizeof(WCHAR) * len)); 2758 lastValidPathEnd = currentPathEndPosition; 2759 } 2760 } 2761 Tcl_DStringFree(&ds); 2762 break; 2763 } 2764 2765 /* 2766 * File 'nativePath' does exist if we get here. We now want to 2767 * check if it is a symlink and otherwise continue with the 2768 * rest of the path. 2769 */ 2770 2771 /* 2772 * Check for symlinks, except at last component of path (we 2773 * don't follow final symlinks). Also a drive (C:/) for 2774 * example, may sometimes have the reparse flag set for some 2775 * reason I don't understand. We therefore don't perform this 2776 * check for drives. 2777 */ 2778 2779 if (cur != 0 && !isDrive && 2780 data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT){ 2781 Tcl_Obj *to = WinReadLinkDirectory(nativePath); 2782 2783 if (to != NULL) { 2784 /* 2785 * Read the reparse point ok. Now, reparse points need 2786 * not be normalized, otherwise we could use: 2787 * 2788 * Tcl_GetStringFromObj(to, &pathLen); 2789 * nextCheckpoint = pathLen 2790 * 2791 * So, instead we have to start from the beginning. 2792 */ 2793 2794 nextCheckpoint = 0; 2795 Tcl_AppendToObj(to, currentPathEndPosition, -1); 2796 2797 /* 2798 * Convert link to forward slashes. 2799 */ 2800 2801 for (path = Tcl_GetString(to); *path != 0; path++) { 2802 if (*path == '\\') { 2803 *path = '/'; 2804 } 2805 } 2806 path = Tcl_GetString(to); 2807 currentPathEndPosition = path + nextCheckpoint; 2808 if (temp != NULL) { 2809 Tcl_DecrRefCount(temp); 2810 } 2811 temp = to; 2812 2813 /* 2814 * Reset variables so we can restart normalization. 2815 */ 2816 2817 isDrive = 1; 2818 Tcl_DStringFree(&dsNorm); 2819 Tcl_DStringInit(&dsNorm); 2820 Tcl_DStringFree(&ds); 2821 continue; 2822 } 2823 } 2824 2825#ifndef TclNORM_LONG_PATH 2826 /* 2827 * Now we convert the tail of the current path to its 'long 2828 * form', and append it to 'dsNorm' which holds the current 2829 * normalized path 2830 */ 2831 2832 if (isDrive) { 2833 WCHAR drive = ((WCHAR *) nativePath)[0]; 2834 if (drive >= L'a') { 2835 drive -= (L'a' - L'A'); 2836 ((WCHAR *) nativePath)[0] = drive; 2837 } 2838 Tcl_DStringAppend(&dsNorm, nativePath, 2839 Tcl_DStringLength(&ds)); 2840 } else { 2841 char *checkDots = NULL; 2842 2843 if (lastValidPathEnd[1] == '.') { 2844 checkDots = lastValidPathEnd + 1; 2845 while (checkDots < currentPathEndPosition) { 2846 if (*checkDots != '.') { 2847 checkDots = NULL; 2848 break; 2849 } 2850 checkDots++; 2851 } 2852 } 2853 if (checkDots != NULL) { 2854 int dotLen = currentPathEndPosition-lastValidPathEnd; 2855 2856 /* 2857 * Path is just dots. We shouldn't really ever see a 2858 * path like that. However, to be nice we at least 2859 * don't mangle the path - we just add the dots as a 2860 * path segment and continue. 2861 */ 2862 2863 Tcl_DStringAppend(&dsNorm, (TCHAR *) 2864 ((WCHAR*)(nativePath + Tcl_DStringLength(&ds)) 2865 - dotLen), (int)(dotLen * sizeof(WCHAR))); 2866 } else { 2867 /* 2868 * Normal path. 2869 */ 2870 2871 WIN32_FIND_DATAW fData; 2872 HANDLE handle; 2873 2874 handle = FindFirstFileW((WCHAR *) nativePath, &fData); 2875 if (handle == INVALID_HANDLE_VALUE) { 2876 /* 2877 * This is usually the '/' in 'c:/' at end of 2878 * string. 2879 */ 2880 2881 Tcl_DStringAppend(&dsNorm, (const char *) L"/", 2882 sizeof(WCHAR)); 2883 } else { 2884 WCHAR *nativeName; 2885 2886 if (fData.cFileName[0] != '\0') { 2887 nativeName = fData.cFileName; 2888 } else { 2889 nativeName = fData.cAlternateFileName; 2890 } 2891 FindClose(handle); 2892 Tcl_DStringAppend(&dsNorm, (const char *) L"/", 2893 sizeof(WCHAR)); 2894 Tcl_DStringAppend(&dsNorm, (TCHAR *) nativeName, 2895 (int) (wcslen(nativeName)*sizeof(WCHAR))); 2896 } 2897 } 2898 } 2899#endif 2900 Tcl_DStringFree(&ds); 2901 lastValidPathEnd = currentPathEndPosition; 2902 if (cur == 0) { 2903 break; 2904 } 2905 2906 /* 2907 * If we get here, we've got past one directory delimiter, so 2908 * we know it is no longer a drive. 2909 */ 2910 2911 isDrive = 0; 2912 } 2913 currentPathEndPosition++; 2914 } 2915 2916#ifdef TclNORM_LONG_PATH 2917 /* 2918 * Convert the entire known path to long form. 2919 */ 2920 2921 if (1) { 2922 WCHAR wpath[MAX_PATH]; 2923 const char *nativePath = 2924 Tcl_WinUtfToTChar(path, lastValidPathEnd - path, &ds); 2925 DWORD wpathlen = (*tclWinProcs->getLongPathNameProc)( 2926 nativePath, (TCHAR *) wpath, MAX_PATH); 2927 2928 /* 2929 * We have to make the drive letter uppercase. 2930 */ 2931 2932 if (wpath[0] >= L'a') { 2933 wpath[0] -= (L'a' - L'A'); 2934 } 2935 Tcl_DStringAppend(&dsNorm, (TCHAR*)wpath, wpathlen*sizeof(WCHAR)); 2936 Tcl_DStringFree(&ds); 2937 } 2938#endif 2939 } 2940 2941 /* 2942 * Common code path for all Windows platforms. 2943 */ 2944 2945 nextCheckpoint = currentPathEndPosition - path; 2946 if (lastValidPathEnd != NULL) { 2947 /* 2948 * Concatenate the normalized string in dsNorm with the tail of the 2949 * path which we didn't recognise. The string in dsNorm is in the 2950 * native encoding, so we have to convert it to Utf. 2951 */ 2952 2953 Tcl_DString dsTemp; 2954 2955 Tcl_WinTCharToUtf(Tcl_DStringValue(&dsNorm), 2956 Tcl_DStringLength(&dsNorm), &dsTemp); 2957 nextCheckpoint = Tcl_DStringLength(&dsTemp); 2958 if (*lastValidPathEnd != 0) { 2959 /* 2960 * Not the end of the string. 2961 */ 2962 2963 int len; 2964 char *path; 2965 Tcl_Obj *tmpPathPtr; 2966 2967 tmpPathPtr = Tcl_NewStringObj(Tcl_DStringValue(&dsTemp), 2968 nextCheckpoint); 2969 Tcl_AppendToObj(tmpPathPtr, lastValidPathEnd, -1); 2970 path = Tcl_GetStringFromObj(tmpPathPtr, &len); 2971 Tcl_SetStringObj(pathPtr, path, len); 2972 Tcl_DecrRefCount(tmpPathPtr); 2973 } else { 2974 /* 2975 * End of string was reached above. 2976 */ 2977 2978 Tcl_SetStringObj(pathPtr, Tcl_DStringValue(&dsTemp), 2979 nextCheckpoint); 2980 } 2981 Tcl_DStringFree(&dsTemp); 2982 } 2983 Tcl_DStringFree(&dsNorm); 2984 2985 /* 2986 * This must be done after we are totally finished with 'path' as we are 2987 * sharing the same underlying string. 2988 */ 2989 2990 if (temp != NULL) { 2991 Tcl_DecrRefCount(temp); 2992 } 2993 return nextCheckpoint; 2994} 2995 2996/* 2997 *--------------------------------------------------------------------------- 2998 * 2999 * TclWinVolumeRelativeNormalize -- 3000 * 3001 * Only Windows has volume-relative paths. These paths are rather rare, 3002 * but it is nice if Tcl can handle them. It is much better if we can 3003 * handle them here, rather than in the native fs code, because we really 3004 * need to have a real absolute path just below. 3005 * 3006 * We do not let this block compile on non-Windows platforms because the 3007 * test suite's manual forcing of tclPlatform can otherwise cause this 3008 * code path to be executed, causing various errors because 3009 * volume-relative paths really do not exist. 3010 * 3011 * Results: 3012 * A valid normalized path. 3013 * 3014 * Side effects: 3015 * None. 3016 * 3017 *--------------------------------------------------------------------------- 3018 */ 3019 3020Tcl_Obj * 3021TclWinVolumeRelativeNormalize( 3022 Tcl_Interp *interp, 3023 const char *path, 3024 Tcl_Obj **useThisCwdPtr) 3025{ 3026 Tcl_Obj *absolutePath, *useThisCwd; 3027 3028 useThisCwd = Tcl_FSGetCwd(interp); 3029 if (useThisCwd == NULL) { 3030 return NULL; 3031 } 3032 3033 if (path[0] == '/') { 3034 /* 3035 * Path of form /foo/bar which is a path in the root directory of the 3036 * current volume. 3037 */ 3038 3039 const char *drive = Tcl_GetString(useThisCwd); 3040 3041 absolutePath = Tcl_NewStringObj(drive,2); 3042 Tcl_AppendToObj(absolutePath, path, -1); 3043 Tcl_IncrRefCount(absolutePath); 3044 3045 /* 3046 * We have a refCount on the cwd. 3047 */ 3048 } else { 3049 /* 3050 * Path of form C:foo/bar, but this only makes sense if the cwd is 3051 * also on drive C. 3052 */ 3053 3054 int cwdLen; 3055 const char *drive = 3056 Tcl_GetStringFromObj(useThisCwd, &cwdLen); 3057 char drive_cur = path[0]; 3058 3059 if (drive_cur >= 'a') { 3060 drive_cur -= ('a' - 'A'); 3061 } 3062 if (drive[0] == drive_cur) { 3063 absolutePath = Tcl_DuplicateObj(useThisCwd); 3064 3065 /* 3066 * We have a refCount on the cwd, which we will release later. 3067 */ 3068 3069 if (drive[cwdLen-1] != '/' && (path[2] != '\0')) { 3070 /* 3071 * Only add a trailing '/' if needed, which is if there isn't 3072 * one already, and if we are going to be adding some more 3073 * characters. 3074 */ 3075 3076 Tcl_AppendToObj(absolutePath, "/", 1); 3077 } 3078 } else { 3079 Tcl_DecrRefCount(useThisCwd); 3080 useThisCwd = NULL; 3081 3082 /* 3083 * The path is not in the current drive, but is volume-relative. 3084 * The way Tcl 8.3 handles this is that it treats such a path as 3085 * relative to the root of the drive. We therefore behave the same 3086 * here. This behaviour is, however, different to that of the 3087 * windows command-line. If we want to fix this at some point in 3088 * the future (at the expense of a behaviour change to Tcl), we 3089 * could use the '_dgetdcwd' Win32 API to get the drive's cwd. 3090 */ 3091 3092 absolutePath = Tcl_NewStringObj(path, 2); 3093 Tcl_AppendToObj(absolutePath, "/", 1); 3094 } 3095 Tcl_IncrRefCount(absolutePath); 3096 Tcl_AppendToObj(absolutePath, path+2, -1); 3097 } 3098 *useThisCwdPtr = useThisCwd; 3099 return absolutePath; 3100} 3101 3102/* 3103 *--------------------------------------------------------------------------- 3104 * 3105 * TclpNativeToNormalized -- 3106 * 3107 * Convert native format to a normalized path object, with refCount of 3108 * zero. 3109 * 3110 * Currently assumes all native paths are actually normalized already, so 3111 * if the path given is not normalized this will actually just convert to 3112 * a valid string path, but not necessarily a normalized one. 3113 * 3114 * Results: 3115 * A valid normalized path. 3116 * 3117 * Side effects: 3118 * None. 3119 * 3120 *--------------------------------------------------------------------------- 3121 */ 3122 3123Tcl_Obj * 3124TclpNativeToNormalized( 3125 ClientData clientData) 3126{ 3127 Tcl_DString ds; 3128 Tcl_Obj *objPtr; 3129 int len; 3130 char *copy, *p; 3131 3132 Tcl_WinTCharToUtf((const char *) clientData, -1, &ds); 3133 copy = Tcl_DStringValue(&ds); 3134 len = Tcl_DStringLength(&ds); 3135 3136 /* 3137 * Certain native path representations on Windows have this special prefix 3138 * to indicate that they are to be treated specially. For example 3139 * extremely long paths, or symlinks. 3140 */ 3141 3142 if (*copy == '\\') { 3143 if (0 == strncmp(copy,"\\??\\",4)) { 3144 copy += 4; 3145 len -= 4; 3146 } else if (0 == strncmp(copy,"\\\\?\\",4)) { 3147 copy += 4; 3148 len -= 4; 3149 } 3150 } 3151 3152 /* 3153 * Ensure we are using forward slashes only. 3154 */ 3155 3156 for (p = copy; *p != '\0'; p++) { 3157 if (*p == '\\') { 3158 *p = '/'; 3159 } 3160 } 3161 3162 objPtr = Tcl_NewStringObj(copy,len); 3163 Tcl_DStringFree(&ds); 3164 3165 return objPtr; 3166} 3167 3168/* 3169 *--------------------------------------------------------------------------- 3170 * 3171 * TclNativeCreateNativeRep -- 3172 * 3173 * Create a native representation for the given path. 3174 * 3175 * Results: 3176 * The nativePath representation. 3177 * 3178 * Side effects: 3179 * Memory will be allocated. The path may need to be normalized. 3180 * 3181 *--------------------------------------------------------------------------- 3182 */ 3183 3184ClientData 3185TclNativeCreateNativeRep( 3186 Tcl_Obj *pathPtr) 3187{ 3188 char *nativePathPtr, *str; 3189 Tcl_DString ds; 3190 Tcl_Obj *validPathPtr; 3191 int len; 3192 3193 if (TclFSCwdIsNative()) { 3194 /* 3195 * The cwd is native, which means we can use the translated path 3196 * without worrying about normalization (this will also usually be 3197 * shorter so the utf-to-external conversion will be somewhat faster). 3198 */ 3199 3200 validPathPtr = Tcl_FSGetTranslatedPath(NULL, pathPtr); 3201 if (validPathPtr == NULL) { 3202 return NULL; 3203 } 3204 } else { 3205 /* 3206 * Make sure the normalized path is set. 3207 */ 3208 3209 validPathPtr = Tcl_FSGetNormalizedPath(NULL, pathPtr); 3210 if (validPathPtr == NULL) { 3211 return NULL; 3212 } 3213 Tcl_IncrRefCount(validPathPtr); 3214 } 3215 3216 str = Tcl_GetStringFromObj(validPathPtr, &len); 3217 if (str[0] == '/' && str[1] == '/' && str[2] == '?' && str[3] == '/') { 3218 char *p; 3219 3220 for (p = str; p && *p; ++p) { 3221 if (*p == '/') { 3222 *p = '\\'; 3223 } 3224 } 3225 } 3226 Tcl_WinUtfToTChar(str, len, &ds); 3227 if (tclWinProcs->useWide) { 3228 len = Tcl_DStringLength(&ds) + sizeof(WCHAR); 3229 } else { 3230 len = Tcl_DStringLength(&ds) + sizeof(char); 3231 } 3232 Tcl_DecrRefCount(validPathPtr); 3233 nativePathPtr = ckalloc((unsigned) len); 3234 memcpy(nativePathPtr, Tcl_DStringValue(&ds), (size_t) len); 3235 3236 Tcl_DStringFree(&ds); 3237 return (ClientData) nativePathPtr; 3238} 3239 3240/* 3241 *--------------------------------------------------------------------------- 3242 * 3243 * TclNativeDupInternalRep -- 3244 * 3245 * Duplicate the native representation. 3246 * 3247 * Results: 3248 * The copied native representation, or NULL if it is not possible to 3249 * copy the representation. 3250 * 3251 * Side effects: 3252 * Memory allocation for the copy. 3253 * 3254 *--------------------------------------------------------------------------- 3255 */ 3256 3257ClientData 3258TclNativeDupInternalRep( 3259 ClientData clientData) 3260{ 3261 char *copy; 3262 size_t len; 3263 3264 if (clientData == NULL) { 3265 return NULL; 3266 } 3267 3268 if (tclWinProcs->useWide) { 3269 /* 3270 * Unicode representation when running on NT/2K/XP. 3271 */ 3272 3273 len = sizeof(WCHAR) * (wcslen((const WCHAR *) clientData) + 1); 3274 } else { 3275 /* 3276 * ANSI representation when running on 95/98/ME. 3277 */ 3278 3279 len = sizeof(char) * (strlen((const char *) clientData) + 1); 3280 } 3281 3282 copy = (char *) ckalloc(len); 3283 memcpy(copy, clientData, len); 3284 return (ClientData) copy; 3285} 3286 3287/* 3288 *--------------------------------------------------------------------------- 3289 * 3290 * TclpUtime -- 3291 * 3292 * Set the modification date for a file. 3293 * 3294 * Results: 3295 * 0 on success, -1 on error. 3296 * 3297 * Side effects: 3298 * Sets errno to a representation of any Windows problem that's observed 3299 * in the process. 3300 * 3301 *--------------------------------------------------------------------------- 3302 */ 3303 3304int 3305TclpUtime( 3306 Tcl_Obj *pathPtr, /* File to modify */ 3307 struct utimbuf *tval) /* New modification date structure */ 3308{ 3309 int res = 0; 3310 HANDLE fileHandle; 3311 const TCHAR *native; 3312 DWORD attr = 0; 3313 DWORD flags = FILE_ATTRIBUTE_NORMAL; 3314 FILETIME lastAccessTime, lastModTime; 3315 3316 FromCTime(tval->actime, &lastAccessTime); 3317 FromCTime(tval->modtime, &lastModTime); 3318 3319 native = (const TCHAR *) Tcl_FSGetNativePath(pathPtr); 3320 3321 attr = (*tclWinProcs->getFileAttributesProc)(native); 3322 3323 if (attr != INVALID_FILE_ATTRIBUTES && attr & FILE_ATTRIBUTE_DIRECTORY) { 3324 flags = FILE_FLAG_BACKUP_SEMANTICS; 3325 } 3326 3327 /* 3328 * We use the native APIs (not 'utime') because there are some daylight 3329 * savings complications that utime gets wrong. 3330 */ 3331 3332 fileHandle = (tclWinProcs->createFileProc)(native, FILE_WRITE_ATTRIBUTES, 3333 0, NULL, OPEN_EXISTING, flags, NULL); 3334 3335 if (fileHandle == INVALID_HANDLE_VALUE || 3336 !SetFileTime(fileHandle, NULL, &lastAccessTime, &lastModTime)) { 3337 TclWinConvertError(GetLastError()); 3338 res = -1; 3339 } 3340 if (fileHandle != INVALID_HANDLE_VALUE) { 3341 CloseHandle(fileHandle); 3342 } 3343 return res; 3344} 3345 3346/* 3347 * Local Variables: 3348 * mode: c 3349 * c-basic-offset: 4 3350 * fill-column: 78 3351 * End: 3352 */ 3353