Patchwork [2,of,7,V2] win32: implement util.getfstype()

login
register
mail settings
Submitter Matt Harbison
Date Dec. 31, 2017, 4:11 a.m.
Message ID <5b4803673e98e26e1d80.1514693469@Envy>
Download mbox | patch
Permalink /patch/26509/
State Accepted
Headers show

Comments

Matt Harbison - Dec. 31, 2017, 4:11 a.m.
# HG changeset patch
# User Matt Harbison <matt_harbison@yahoo.com>
# Date 1514600899 18000
#      Fri Dec 29 21:28:19 2017 -0500
# Node ID 5b4803673e98e26e1d80254b8ac3bab5ebc5bac7
# Parent  beede158ea8a740efb59b46522f74490381a2086
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
Yuya Nishihara - Dec. 31, 2017, 8:54 a.m.
On Sat, 30 Dec 2017 23:11:09 -0500, Matt Harbison wrote:
> # HG changeset patch
> # User Matt Harbison <matt_harbison@yahoo.com>
> # Date 1514600899 18000
> #      Fri Dec 29 21:28:19 2017 -0500
> # Node ID 5b4803673e98e26e1d80254b8ac3bab5ebc5bac7
> # Parent  beede158ea8a740efb59b46522f74490381a2086
> win32: implement util.getfstype()

Queued the series, thanks.

> +    size = len(realpath) + 1
> +    buf = ctypes.create_string_buffer(size)
> +
> +    if not _kernel32.GetVolumePathNameA(realpath, ctypes.byref(buf), size):

No idea why, but "hg debugfsinfo --cwd '/z/'" failed on WinXP saying something
like "more data available." Windows bug?

> --- a/mercurial/windows.py
> +++ b/mercurial/windows.py
> @@ -231,7 +231,7 @@
>  
>      Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
>      '''
> -    return None
> +    return win32.getfstype(dirpath)

Changed this to getfstype = win32.getfstype.
Matt Harbison - Jan. 1, 2018, 12:38 a.m.
On Sun, 31 Dec 2017 03:54:18 -0500, Yuya Nishihara <yuya@tcha.org> wrote:

> On Sat, 30 Dec 2017 23:11:09 -0500, Matt Harbison wrote:
>> # HG changeset patch
>> # User Matt Harbison <matt_harbison@yahoo.com>
>> # Date 1514600899 18000
>> #      Fri Dec 29 21:28:19 2017 -0500
>> # Node ID 5b4803673e98e26e1d80254b8ac3bab5ebc5bac7
>> # Parent  beede158ea8a740efb59b46522f74490381a2086
>> win32: implement util.getfstype()
>
> Queued the series, thanks.
>
>> +    size = len(realpath) + 1
>> +    buf = ctypes.create_string_buffer(size)
>> +
>> +    if not _kernel32.GetVolumePathNameA(realpath, ctypes.byref(buf),  
>> size):
>
> No idea why, but "hg debugfsinfo --cwd '/z/'" failed on WinXP saying  
> something
> like "more data available." Windows bug?

Odd.  I noticed different misbehavior on Win7 yesterday when trying to `cd  
/z/`, but can't explain it either.

$ ../hg debugfsinfo --cwd '/z/'
abort: The system cannot find the file specified: 'C:/MinGW/msys/1.0/z/'

$ df -h
Filesystem            Size  Used Avail Use% Mounted on
C:\Users\Matt\AppData\Local\Temp
                       435G  386G   50G  89% /tmp
C:\MinGW\msys\1.0     435G  386G   50G  89% /usr
C:\MinGW\msys\1.0     435G  386G   50G  89% /
C:\MinGW              435G  386G   50G  89% /mingw
c:                    435G  386G   50G  89% /c
d:                     31G   27G  4.6G  86% /d
z:                    435G  386G   50G  89% z

(Not sure why it is mounted on 'z', not '/z' here)

$ mkdir /z
$ ../hg debugfsinfo --cwd '/z/'
path: .
mounted on: C:\
exec: no
fstype: NTFS
symlink: no
hardlink: yes
case-sensitive: no

I guess falling back to C:\ makes sense, since nothing seems to actually  
be mounted on z here.  `mkdir /z` prior to mapping the share made no  
difference.

$ ../hg debugfsinfo --cwd 'z:\'
path: .
mounted on: z:\
exec: no
fstype: cifs
symlink: no
hardlink: yes
case-sensitive: no

Patch

diff --git a/mercurial/win32.py b/mercurial/win32.py
--- a/mercurial/win32.py
+++ b/mercurial/win32.py
@@ -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)
diff --git a/mercurial/windows.py b/mercurial/windows.py
--- a/mercurial/windows.py
+++ b/mercurial/windows.py
@@ -231,7 +231,7 @@ 
 
     Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
     '''
-    return None
+    return win32.getfstype(dirpath)
 
 def setbinary(fd):
     # When run without console, pipes may expose invalid