[PATCH STABLE V2] windows: implement nlinks() using Python's ctypes (issue1922)
Adrian Buehlmann
adrian at cadifra.com
Sun Jan 23 15:12:02 UTC 2011
On 2011-01-23 15:17, Adrian Buehlmann wrote:
> # HG changeset patch
> # User Adrian Buehlmann <adrian at cadifra.com>
> # Date 1295790385 -3600
> # Branch stable
> # Node ID 99f2b980756d1c22775cf6ec6e93c646bc910a55
> # Parent d0e0d3d43e1439d63564ab4dddfe0daa69ae2d86
> windows: implement nlinks() using Python's ctypes (issue1922)
>
> If the pywin32 package was not installed, the import of win32 in
> windows.py silently failed and (before this patch) util.nlinks was then
> used as a fallback when running on Windows.
>
> util.nlinks() returned 0 for all files when called on Windows, because
> Python's
>
> os.lstat(name).st_nlink
>
> is 0 on Windows for all files.
>
> If nlinks() returns 0, util.opener failed to break up hardlinks, which
> could lead to repository corruption when committing or pushing to a
> hardlinked clone (hg verify detects it).
>
> We now provide our own nlinks() in windows.py by using Python's ctypes
> library, so we don't depend on the pywin32 package being installed for
> nlinks().
>
> ** Since Python's ctypes were introduced in Python 2.5, we now
> ** require Python 2.5 or later for Mercurial on Windows
>
> Using ctypes also has the benefit that nlinks() also works correctly
> for the pure Python Mercurial.
>
> And we force breaking up hardlinks on every append file access in the
> opener if nlinks() returns < 1, thus making sure that we can't cause
> any hardlink repository corruption.
>
> It would have been possible to simply require the pywin32 package on
> Windows and abort with an import error if it's not installed, but such
> a policy change should be avoided on the stable branch. Previous packages
> like for example
>
> mercurial-1.7.3-1.win32-py2.6.exe
>
> didn't make it obvious that pywin32 was needed as a dependency. It just
> silently caused repository corruption in hardlinked clones if pywin32
> was not installed.
>
> (This patch is supposed to completely fix issue1922)
>
> (Based on contributions by Aaron Cohen <aaron at assonance.org>)
>
> diff --git a/mercurial/posix.py b/mercurial/posix.py
> --- a/mercurial/posix.py
> +++ b/mercurial/posix.py
> @@ -23,6 +23,10 @@ def openhardlinks():
> '''return true if it is safe to hold open file handles to hardlinks'''
> return True
>
> +def nlinks(name):
> + """return number of hardlinks for the given file"""
> + return os.lstat(name).st_nlink
> +
> def rcfiles(path):
> rcs = [os.path.join(path, 'hgrc')]
> rcdir = os.path.join(path, 'hgrc.d')
> diff --git a/mercurial/util.py b/mercurial/util.py
> --- a/mercurial/util.py
> +++ b/mercurial/util.py
> @@ -550,10 +550,6 @@ class path_auditor(object):
> # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
> self.auditeddir.update(prefixes)
>
> -def nlinks(pathname):
> - """Return number of hardlinks for the given file."""
> - return os.lstat(pathname).st_nlink
> -
> if hasattr(os, 'link'):
> os_link = os.link
> else:
> @@ -913,6 +909,8 @@ class opener(object):
> # shares if the file is open.
> fd = open(f)
> nlink = nlinks(f)
> + if nlink < 1:
> + nlink = 2 # force mktempcopy (issue1922)
> fd.close()
> except (OSError, IOError):
> nlink = 0
> diff --git a/mercurial/win32.py b/mercurial/win32.py
> --- a/mercurial/win32.py
> +++ b/mercurial/win32.py
> @@ -41,10 +41,6 @@ def _getfileinfo(pathname):
> finally:
> fh.Close()
>
> -def nlinks(pathname):
> - """Return number of hardlinks for the given file."""
> - return _getfileinfo(pathname)[7]
> -
> def samefile(fpath1, fpath2):
> """Returns whether fpath1 and fpath2 refer to the same file. This is only
> guaranteed to work for files, not directories."""
> diff --git a/mercurial/windows.py b/mercurial/windows.py
> --- a/mercurial/windows.py
> +++ b/mercurial/windows.py
> @@ -7,7 +7,7 @@
>
> from i18n import _
> import osutil, error
> -import errno, msvcrt, os, re, sys, random, subprocess
> +import errno, msvcrt, os, re, sys, random, subprocess, ctypes
>
> nulldev = 'NUL:'
> umask = 002
> @@ -20,6 +20,57 @@ def posixfile(name, mode='r', buffering=
> raise IOError(err.errno, '%s: %s' % (name, err.strerror))
> posixfile.__doc__ = osutil.posixfile.__doc__
>
> +class FILETIME(ctypes.Structure):
> + _fields_ = [
> + ('dwLowDateTime', ctypes.c_uint),
> + ('dwHighDateTime', ctypes.c_uint),
> + ]
> +
> +class BY_HANDLE_FILE_INFORMATION(ctypes.Structure):
> + _fields_ = [
> + ('dwFileAttributes', ctypes.c_uint),
> + ('ftCreationTime', FILETIME),
> + ('ftLastAccessTime', FILETIME),
> + ('ftLastWriteTime', FILETIME),
> + ('dwVolumeSerialNumber', ctypes.c_uint),
> + ('nFileSizeHigh', ctypes.c_uint),
> + ('nFileSizeLow', ctypes.c_uint),
> + ('nNumberOfLinks', ctypes.c_uint),
> + ('nFileIndexHigh', ctypes.c_uint),
> + ('nFileIndexLow', ctypes.c_uint),
> + ]
> +
I just found out that we can use:
class FILETIME(ctypes.Structure):
_fields_ = [
('dwLowDateTime', ctypes.wintypes.DWORD),
('dwHighDateTime', ctypes.wintypes.DWORD),
]
class BY_HANDLE_FILE_INFORMATION(ctypes.Structure):
_fields_ = [
('dwFileAttributes', ctypes.wintypes.DWORD),
('ftCreationTime', FILETIME),
('ftLastAccessTime', FILETIME),
('ftLastWriteTime', FILETIME),
('dwVolumeSerialNumber', ctypes.wintypes.DWORD),
('nFileSizeHigh', ctypes.wintypes.DWORD),
('nFileSizeLow', ctypes.wintypes.DWORD),
('nNumberOfLinks', ctypes.wintypes.DWORD),
('nFileIndexHigh', ctypes.wintypes.DWORD),
('nFileIndexLow', ctypes.wintypes.DWORD),
]
(Otherwise works pretty well so far. I found no noticeable speed change
on Python 2.6.5 32 bit)
> +_FILE_SHARE_READ = 0x00000001
> +_FILE_SHARE_WRITE = 0x00000002
> +_FILE_SHARE_DELETE = 0x00000004
> +
> +_OPEN_EXISTING = 3
> +
> +def _raiseoserror(name):
> + err = ctypes.WinError()
> + raise OSError(err.errno, '%s: %s' % (name, err.strerror))
> +
> +def _getfileinfo(name):
> + k = ctypes.windll.kernel32
> + fh = k.CreateFileA(name,
> + 0, _FILE_SHARE_READ | _FILE_SHARE_WRITE | _FILE_SHARE_DELETE,
> + None, _OPEN_EXISTING, 0, None)
> + if fh == -1:
> + _raiseoserror(name)
> +
> + try:
> + fi = BY_HANDLE_FILE_INFORMATION()
> + if (not k.GetFileInformationByHandle(fh, ctypes.pointer(fi))):
> + _raiseoserror(name)
> + return fi
> + finally:
> + if fh != -1:
> + k.CloseHandle(fh)
> +
> +def nlinks(name):
> + '''return number of hardlinks for the given file'''
> + return _getfileinfo(name).nNumberOfLinks
> +
> class winstdout(object):
> '''stdout on windows misbehaves if sent through a pipe'''
>
>
>
More information about the Mercurial-devel
mailing list