win32: implement util.getfstype()
authorMatt Harbison <matt_harbison@yahoo.com>
Fri, 29 Dec 2017 21:28:19 -0500
changeset 35510 2062f7c2ac83
parent 35509 beede158ea8a
child 35511 d8f408d999f9
win32: implement util.getfstype() This will allow NTFS to be added to the hardlink whitelist, and resume creating hardlinks in transactions (which was disabled globally in 07a92bbd02e5; see also e5ce49a30146). I opted to report "cifs" for remote volumes because this shows in `hg debugfs`, which also reports that hardlinks are supported for these volumes. So being able to distinguish it from "unknown" seems useful. The documentation [1] seems to indicate that SMB isn't supported by these functions, but experimenting shows that mapped drives are reported as "NTFS" on Windows 7. I don't have a second Windows machine, but instead shared a temp directory on C:\. In this setup, both of the following were detected as 'cifs' with the explicit GetDriveType() check: Z:\repo>hg ci -A C:\>hg -R \\hostname\temp\repo ci -A # (without Z:\ being mapped) It looks like this is called 6 times to add and commit a single new file, so I'm a little surprised this isn't cached. [1] https://msdn.microsoft.com/en-us/library/windows/desktop/aa364993(v=vs.85).aspx
mercurial/win32.py
mercurial/windows.py
--- a/mercurial/win32.py	Sat Dec 30 21:07:03 2017 -0500
+++ b/mercurial/win32.py	Fri Dec 29 21:28:19 2017 -0500
@@ -223,6 +223,24 @@
 _kernel32.SetFileAttributesA.argtypes = [_LPCSTR, _DWORD]
 _kernel32.SetFileAttributesA.restype = _BOOL
 
+_DRIVE_UNKNOWN = 0
+_DRIVE_NO_ROOT_DIR = 1
+_DRIVE_REMOVABLE = 2
+_DRIVE_FIXED = 3
+_DRIVE_REMOTE = 4
+_DRIVE_CDROM = 5
+_DRIVE_RAMDISK = 6
+
+_kernel32.GetDriveTypeA.argtypes = [_LPCSTR]
+_kernel32.GetDriveTypeA.restype = _UINT
+
+_kernel32.GetVolumeInformationA.argtypes = [_LPCSTR, ctypes.c_void_p, _DWORD,
+    ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, _DWORD]
+_kernel32.GetVolumeInformationA.restype = _BOOL
+
+_kernel32.GetVolumePathNameA.argtypes = [_LPCSTR, ctypes.c_void_p, _DWORD]
+_kernel32.GetVolumePathNameA.restype = _BOOL
+
 _kernel32.OpenProcess.argtypes = [_DWORD, _BOOL, _DWORD]
 _kernel32.OpenProcess.restype = _HANDLE
 
@@ -410,6 +428,37 @@
         raise ctypes.WinError(_ERROR_INSUFFICIENT_BUFFER)
     return buf.value
 
+def getfstype(path):
+    """Get the filesystem type name from a directory or file (best-effort)
+
+    Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
+    """
+    # realpath() calls GetFullPathName()
+    realpath = os.path.realpath(path)
+
+    size = len(realpath) + 1
+    buf = ctypes.create_string_buffer(size)
+
+    if not _kernel32.GetVolumePathNameA(realpath, ctypes.byref(buf), size):
+        raise ctypes.WinError() # Note: WinError is a function
+
+    t = _kernel32.GetDriveTypeA(buf.value)
+
+    if t == _DRIVE_REMOTE:
+        return 'cifs'
+    elif t not in (_DRIVE_REMOVABLE, _DRIVE_FIXED, _DRIVE_CDROM,
+                   _DRIVE_RAMDISK):
+        return None
+
+    size = 256
+    name = ctypes.create_string_buffer(size)
+
+    if not _kernel32.GetVolumeInformationA(buf.value, None, 0, None, None, None,
+                                           ctypes.byref(name), size):
+        raise ctypes.WinError() # Note: WinError is a function
+
+    return name.value
+
 def getuser():
     '''return name of current user'''
     size = _DWORD(300)
--- a/mercurial/windows.py	Sat Dec 30 21:07:03 2017 -0500
+++ b/mercurial/windows.py	Fri Dec 29 21:28:19 2017 -0500
@@ -32,6 +32,7 @@
 osutil = policy.importmod(r'osutil')
 
 executablepath = win32.executablepath
+getfstype = win32.getfstype
 getuser = win32.getuser
 hidewindow = win32.hidewindow
 makedir = win32.makedir
@@ -226,13 +227,6 @@
 def checklink(path):
     return False
 
-def getfstype(dirpath):
-    '''Get the filesystem type name from a directory (best-effort)
-
-    Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
-    '''
-    return None
-
 def setbinary(fd):
     # When run without console, pipes may expose invalid
     # fileno(), usually set to -1.