New implementation for os.sameFile on Windows

Hard-links on Windows are now treated just as they are on POSIX.
The new implementation is faster than the previous, but still it's quite
slower than fstat (use with caution).
This commit is contained in:
Zahary Karadjov 2011-12-07 00:53:27 +02:00
commit 0e609d2101
3 changed files with 66 additions and 25 deletions

View file

@ -567,26 +567,45 @@ proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1".} =
result = path[0] == '/' result = path[0] == '/'
proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1".} = proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1".} =
## Returns True if both pathname arguments refer to the same file or ## Returns True if both pathname arguments refer to the same physical
## directory (as indicated by device number and i-node number). ## file or directory. Raises an exception if any of the files does not
## Raises an exception if an stat() call on either pathname fails. ## exist or information about it can not be obtained.
##
## This proc will return true if given two alternative hard-linked or
## sym-linked paths to the same file or directory.
when defined(Windows): when defined(Windows):
var var success = true
a, b: TWin32FindData
var resA = findfirstFileA(path1, a) template OpenHandle(path: expr): expr =
var resB = findfirstFileA(path2, b) CreateFileA(path, 0'i32, FILE_SHARE_DELETE or FILE_SHARE_READ or
if resA != -1 and resB != -1: FILE_SHARE_WRITE, nil, OPEN_EXISTING,
result = $a.cFileName == $b.cFileName FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL, 0)
else:
# work around some ``findfirstFileA`` bugs var f1 = OpenHandle(path1)
result = cmpPaths(path1, path2) == 0 var f2 = OpenHandle(path2)
if resA != -1: findclose(resA)
if resB != -1: findclose(resB) if f1 != INVALID_HANDLE_VALUE and f2 != INVALID_HANDLE_VALUE:
var fi1, fi2: TBY_HANDLE_FILE_INFORMATION
if GetFileInformationByHandle(f1, addr(fi1)) != 0 and
GetFileInformationByHandle(f2, addr(fi2)) != 0:
result = fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber and
fi1.nFileIndexHigh == fi2.nFileIndexHigh and
fi1.nFileIndexLow == fi2.nFileIndexLow
else: success = false
else: success = false
discard CloseHandle(f1)
discard CloseHandle(f2)
if not success:
OSError()
else: else:
var var
a, b: TStat a, b: TStat
if stat(path1, a) < 0'i32 or stat(path2, b) < 0'i32: if stat(path1, a) < 0'i32 or stat(path2, b) < 0'i32:
result = cmpPaths(path1, path2) == 0 # be consistent with Windows OSError()
else: else:
result = a.st_dev == b.st_dev and a.st_ino == b.st_ino result = a.st_dev == b.st_dev and a.st_ino == b.st_ino

View file

@ -302,7 +302,7 @@ when not defined(ECMAScript):
posix_gettimeofday(a) posix_gettimeofday(a)
result = toFloat(a.tv_sec) + toFloat(a.tv_usec)*0.00_0001 result = toFloat(a.tv_sec) + toFloat(a.tv_usec)*0.00_0001
elif defined(windows): elif defined(windows):
var f: winlean.Filetime var f: winlean.TFiletime
GetSystemTimeAsFileTime(f) GetSystemTimeAsFileTime(f)
var i64 = rdFileTime(f) - epochDiff var i64 = rdFileTime(f) - epochDiff
var secs = i64 div rateDiff var secs = i64 div rateDiff

View file

@ -47,6 +47,22 @@ type
dwProcessId*: int32 dwProcessId*: int32
dwThreadId*: int32 dwThreadId*: int32
TFILETIME* {.final, pure.} = object ## CANNOT BE int64 BECAUSE OF ALIGNMENT
dwLowDateTime*: DWORD
dwHighDateTime*: DWORD
TBY_HANDLE_FILE_INFORMATION* {.final, pure.} = object
dwFileAttributes*: DWORD
ftCreationTime*: TFILETIME
ftLastAccessTime*: TFILETIME
ftLastWriteTime*: TFILETIME
dwVolumeSerialNumber*: DWORD
nFileSizeHigh*: DWORD
nFileSizeLow*: DWORD
nNumberOfLinks*: DWORD
nFileIndexHigh*: DWORD
nFileIndexLow*: DWORD
const const
STARTF_USESHOWWINDOW* = 1'i32 STARTF_USESHOWWINDOW* = 1'i32
STARTF_USESTDHANDLES* = 256'i32 STARTF_USESTDHANDLES* = 256'i32
@ -149,14 +165,11 @@ const
MAX_PATH* = 260 MAX_PATH* = 260
type type
FILETIME* {.final, pure.} = object ## CANNOT BE int64 BECAUSE OF ALIGNMENT
dwLowDateTime*: int32
dwHighDateTime*: int32
TWIN32_FIND_DATA* {.pure.} = object TWIN32_FIND_DATA* {.pure.} = object
dwFileAttributes*: int32 dwFileAttributes*: int32
ftCreationTime*: FILETIME ftCreationTime*: TFILETIME
ftLastAccessTime*: FILETIME ftLastAccessTime*: TFILETIME
ftLastWriteTime*: FILETIME ftLastWriteTime*: TFILETIME
nFileSizeHigh*: int32 nFileSizeHigh*: int32
nFileSizeLow*: int32 nFileSizeLow*: int32
dwReserved0: int32 dwReserved0: int32
@ -192,13 +205,13 @@ proc FreeEnvironmentStringsA*(para1: cstring): int32 {.
proc GetCommandLineA*(): CString {.importc, stdcall, dynlib: "kernel32".} proc GetCommandLineA*(): CString {.importc, stdcall, dynlib: "kernel32".}
proc rdFileTime*(f: FILETIME): int64 = proc rdFileTime*(f: TFILETIME): int64 =
result = ze64(f.dwLowDateTime) or (ze64(f.dwHighDateTime) shl 32) result = ze64(f.dwLowDateTime) or (ze64(f.dwHighDateTime) shl 32)
proc rdFileSize*(f: TWin32FindData): int64 = proc rdFileSize*(f: TWin32FindData): int64 =
result = ze64(f.nFileSizeLow) or (ze64(f.nFileSizeHigh) shl 32) result = ze64(f.nFileSizeLow) or (ze64(f.nFileSizeHigh) shl 32)
proc GetSystemTimeAsFileTime*(lpSystemTimeAsFileTime: var FileTime) {. proc GetSystemTimeAsFileTime*(lpSystemTimeAsFileTime: var TFILETIME) {.
importc: "GetSystemTimeAsFileTime", dynlib: "kernel32", stdcall.} importc: "GetSystemTimeAsFileTime", dynlib: "kernel32", stdcall.}
proc Sleep*(dwMilliseconds: int32){.stdcall, dynlib: "kernel32", proc Sleep*(dwMilliseconds: int32){.stdcall, dynlib: "kernel32",
@ -209,12 +222,16 @@ proc ShellExecute*(HWND: THandle, lpOperation, lpFile,
nShowCmd: int32): THandle{. nShowCmd: int32): THandle{.
stdcall, dynlib: "shell32.dll", importc: "ShellExecuteA".} stdcall, dynlib: "shell32.dll", importc: "ShellExecuteA".}
proc GetFileInformationByHandle*(hFile: THandle,
lpFileInformation: ptr TBY_HANDLE_FILE_INFORMATION): WINBOOL{.
stdcall, dynlib: "kernel32", importc: "GetFileInformationByHandle".}
const const
WSADESCRIPTION_LEN* = 256 WSADESCRIPTION_LEN* = 256
WSASYS_STATUS_LEN* = 128 WSASYS_STATUS_LEN* = 128
FD_SETSIZE* = 64 FD_SETSIZE* = 64
MSG_PEEK* = 2 MSG_PEEK* = 2
INADDR_ANY* = 0 INADDR_ANY* = 0
INADDR_LOOPBACK* = 0x7F000001 INADDR_LOOPBACK* = 0x7F000001
INADDR_BROADCAST* = -1 INADDR_BROADCAST* = -1
@ -410,6 +427,9 @@ const
GENERIC_READ* = 0x80000000'i32 GENERIC_READ* = 0x80000000'i32
GENERIC_ALL* = 0x10000000'i32 GENERIC_ALL* = 0x10000000'i32
FILE_SHARE_READ* = 1'i32 FILE_SHARE_READ* = 1'i32
FILE_SHARE_DELETE* = 4'i32
FILE_SHARE_WRITE* = 2'i32
CREATE_ALWAYS* = 2'i32 CREATE_ALWAYS* = 2'i32
OPEN_EXISTING* = 3'i32 OPEN_EXISTING* = 3'i32
FILE_BEGIN* = 0'i32 FILE_BEGIN* = 0'i32
@ -421,6 +441,8 @@ const
FILE_MAP_WRITE* = 2'i32 FILE_MAP_WRITE* = 2'i32
INVALID_FILE_SIZE* = -1'i32 INVALID_FILE_SIZE* = -1'i32
FILE_FLAG_BACKUP_SEMANTICS* = 33554432'i32
proc CreateFileA*(lpFileName: cstring, dwDesiredAccess, dwShareMode: DWORD, proc CreateFileA*(lpFileName: cstring, dwDesiredAccess, dwShareMode: DWORD,
lpSecurityAttributes: pointer, lpSecurityAttributes: pointer,
dwCreationDisposition, dwFlagsAndAttributes: DWORD, dwCreationDisposition, dwFlagsAndAttributes: DWORD,