Merge lp:~jelmer/brz/win32-fstypes into lp:brz/3.2

Proposed by Jelmer Vernooij
Status: Merged
Approved by: Jelmer Vernooij
Approved revision: no longer in the source branch.
Merge reported by: The Breezy Bot
Merged at revision: not available
Proposed branch: lp:~jelmer/brz/win32-fstypes
Merge into: lp:brz/3.2
Diff against target: 241 lines (+101/-17)
3 files modified
breezy/osutils.py (+37/-7)
breezy/tests/test_osutils.py (+30/-2)
breezy/win32utils.py (+34/-8)
To merge this branch: bzr merge lp:~jelmer/brz/win32-fstypes
Reviewer Review Type Date Requested Status
Jelmer Vernooij Approve
Aleksandr Smyshliaev (community) Approve
Review via email: mp+414132@code.launchpad.net

Description of the change

Attempt to support finding filesystem type on Windows.

To post a comment you must log in.
Revision history for this message
Aleksandr Smyshliaev (a1s) wrote :

I found these problems with this patch:

1. Line 425 in win32utils.py is useless.
2. kernel32 is not defined in win32utils.py line 423.
3. The call in win32utils.py line 426 should use the API call computed in line 423. (It is possible to just put the whole path here and drop line 423 altogether.)
4. The argument to GetVolumeInformation must be a Unicode string and it must include the trailing backslash.
5. The path must be made absolute before passing it to splitdrive().
7. The type string returned by GetVolumeInformation() is upper case.

And one more note: for FAT volumes the type is "FAT32"; perhaps it should be added to the type checks.

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

Please take another look.

Revision history for this message
Aleksandr Smyshliaev (a1s) wrote :

Two errors:

  File "D:\py3\brz\win32-fstypes\breezy\osutils.py", line 2596, in get_fs_type
    return _FILESYSTEM_FINDER.find(path)
  File "D:\py3\brz\win32-fstypes\breezy\osutils.py", line 2568, in find
    fs_type = win32utils.get_fs_type(drive + "\\")
TypeError: can't concat str to bytes

and (after undoing path.encode() in osutils.py line 2594)

  File "D:\py3\brz\win32-fstypes\breezy\tests\test_osutils.py", line 2262, in test_returns_none
    osutils.FilesystemFinder([]))
TypeError: FilesystemFinder() takes no arguments

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

Fixed now I hope. PTAL.

Revision history for this message
Aleksandr Smyshliaev (a1s) wrote :

:-) This works, only GetFsTypeTests became mtab-specific.

Let it be this way, having Breezy functional is MUCH more important than split hairs on test coverage.

Thank you very much for working on this, Jelmer!

review: Approve
Revision history for this message
Aleksandr Smyshliaev (a1s) wrote :

I would add the requirements for the argument to the docstring on win32utils.get_fs_type: it requires the argument to be a Unicode string, and to include the trailing slash. Those requirements are very unobvious, and MS API docs only tell about one of them, that about the slash.

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

Thanks, I've clarified the docstring.

I agree that the test coverage could be better here; I'll leave that open for the moment in the interest of incremental improvement.

Revision history for this message
Jelmer Vernooij (jelmer) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'breezy/osutils.py'
2--- breezy/osutils.py 2022-01-12 23:43:40 +0000
3+++ breezy/osutils.py 2022-01-16 17:50:49 +0000
4@@ -1160,6 +1160,8 @@
5 trace.mutter('Unable to get fs type for %r: %s', path, e)
6 return True
7 else:
8+ if fs_type is None:
9+ return sys.platform != 'win32'
10 if fs_type in ('vfat', 'ntfs'):
11 # filesystems known to not support hardlinks
12 return False
13@@ -1595,6 +1597,8 @@
14 except errors.DependencyNotPresent as e:
15 trace.mutter('Unable to get fs type for %r: %s', path, e)
16 else:
17+ if fs_type is None:
18+ return sys.platform != 'win32'
19 if fs_type in ('vfat', 'ntfs'):
20 # filesystems known to not support executable bit
21 return False
22@@ -1612,6 +1616,8 @@
23 except errors.DependencyNotPresent as e:
24 trace.mutter('Unable to get fs type for %r: %s', path, e)
25 else:
26+ if fs_type is None:
27+ return sys.platform != 'win32'
28 if fs_type in ('vfat', 'ntfs'):
29 # filesystems known to not support symlinks
30 return False
31@@ -2510,11 +2516,18 @@
32 yield cols[1], cols[2].decode('ascii', 'replace')
33
34
35-MTAB_PATH = '/etc/mtab'
36-
37 class FilesystemFinder(object):
38 """Find the filesystem for a particular path."""
39
40+ def find(self, path):
41+ raise NotImplementedError
42+
43+
44+class MtabFilesystemFinder(FilesystemFinder):
45+ """Find the filesystem for a particular path."""
46+
47+ MTAB_PATH = '/etc/mtab'
48+
49 def __init__(self, mountpoints):
50 def key(x):
51 return len(x[0])
52@@ -2530,7 +2543,7 @@
53 # TODO(jelmer): Use inotify to be notified when /etc/mtab changes and
54 # we need to re-read it.
55 try:
56- return cls(read_mtab(MTAB_PATH))
57+ return cls(read_mtab(cls.MTAB_PATH))
58 except EnvironmentError as e:
59 trace.mutter('Unable to read mtab: %s', e)
60 return cls([])
61@@ -2542,12 +2555,29 @@
62 :return: Filesystem name (as text type) or None, if the filesystem is
63 unknown.
64 """
65+ if not isinstance(path, bytes):
66+ path = path.encode(_fs_enc)
67 for mountpoint, filesystem in self._mountpoints:
68 if is_inside(mountpoint, path):
69 return filesystem
70 return None
71
72
73+class Win32FilesystemFinder(FilesystemFinder):
74+
75+ def find(self, path):
76+ drive = os.path.splitdrive(os.path.abspath(path))[0]
77+ if isinstance(drive, bytes):
78+ drive = drive.decode(_fs_enc)
79+ fs_type = win32utils.get_fs_type(drive + "\\")
80+ if fs_type is None:
81+ return None
82+ return {
83+ 'FAT32': 'vfat',
84+ 'NTFS': 'ntfs',
85+ }.get(fs_type, fs_type)
86+
87+
88 _FILESYSTEM_FINDER = None
89
90
91@@ -2559,10 +2589,10 @@
92 """
93 global _FILESYSTEM_FINDER
94 if _FILESYSTEM_FINDER is None:
95- _FILESYSTEM_FINDER = FilesystemFinder.from_mtab()
96-
97- if not isinstance(path, bytes):
98- path = path.encode(_fs_enc)
99+ if sys.platform == 'win32':
100+ _FILESYSTEM_FINDER = Win32FilesystemFinder()
101+ else:
102+ _FILESYSTEM_FINDER = MtabFilesystemFinder.from_mtab()
103
104 return _FILESYSTEM_FINDER.find(path)
105
106
107=== modified file 'breezy/tests/test_osutils.py'
108--- breezy/tests/test_osutils.py 2022-01-12 23:43:40 +0000
109+++ breezy/tests/test_osutils.py 2022-01-16 17:50:49 +0000
110@@ -2217,9 +2217,37 @@
111
112 class SupportsSymlinksTests(tests.TestCaseInTempDir):
113
114+ def setUp(self):
115+ super(SupportsSymlinksTests, self).setUp()
116+ self.overrideAttr(
117+ osutils, '_FILESYSTEM_FINDER',
118+ osutils.MtabFilesystemFinder([
119+ (b'/usr', 'ext4'),
120+ (b'/home', 'vfat'),
121+ (b'/home/jelmer/smb', 'ntfs'),
122+ (b'/home/jelmer', 'ext2'),
123+ ]))
124+
125 def test_returns_bool(self):
126 self.assertIsInstance(osutils.supports_symlinks(self.test_dir), bool)
127
128+ def test_known(self):
129+ self.assertTrue(osutils.supports_symlinks("/usr"))
130+ self.assertFalse(osutils.supports_symlinks("/home/bogus"))
131+ self.assertTrue(osutils.supports_symlinks("/home/jelmer/osx"))
132+ self.assertFalse(osutils.supports_symlinks("/home/jelmer/smb"))
133+
134+ def test_unknown(self):
135+ have_symlinks = sys.platform != "win32"
136+ self.assertIs(osutils.supports_symlinks("/var"), have_symlinks)
137+
138+ def test_error(self):
139+ have_symlinks = sys.platform != "win32"
140+ def raise_error(path):
141+ raise errors.DependencyNotPresent('FS', 'TEST')
142+ self.overrideAttr(osutils, 'get_fs_type', raise_error)
143+ self.assertIs(osutils.supports_symlinks("/var"), have_symlinks)
144+
145
146 class MtabReader(tests.TestCaseInTempDir):
147
148@@ -2246,7 +2274,7 @@
149 def test_returns_most_specific(self):
150 self.overrideAttr(
151 osutils, '_FILESYSTEM_FINDER',
152- osutils.FilesystemFinder(
153+ osutils.MtabFilesystemFinder(
154 [(b'/', 'ext4'), (b'/home', 'vfat'),
155 (b'/home/jelmer', 'ext2')]))
156 self.assertEqual(osutils.get_fs_type(b'/home/jelmer/blah'), 'ext2')
157@@ -2259,7 +2287,7 @@
158 def test_returns_none(self):
159 self.overrideAttr(
160 osutils, '_FILESYSTEM_FINDER',
161- osutils.FilesystemFinder([]))
162+ osutils.MtabFilesystemFinder([]))
163 self.assertIs(osutils.get_fs_type('/home/jelmer/blah'), None)
164 self.assertIs(osutils.get_fs_type(b'/home/jelmer/blah'), None)
165 self.assertIs(osutils.get_fs_type('/home/jelmer'), None)
166
167=== modified file 'breezy/win32utils.py'
168--- breezy/win32utils.py 2022-01-09 13:12:27 +0000
169+++ breezy/win32utils.py 2022-01-16 17:50:49 +0000
170@@ -16,16 +16,17 @@
171
172 """Win32-specific helper functions"""
173
174-import ctypes
175 import glob
176 import os
177 import struct
178-import sys
179-
180-from breezy import (
181- cmdline,
182- )
183+
184+from .lazy_import import lazy_import
185+lazy_import(globals(), """
186+import ctypes
187+
188+from breezy import cmdline
189 from breezy.i18n import gettext
190+""")
191
192
193 # Special Win32 API constants
194@@ -344,7 +345,6 @@
195 def set_file_attr_hidden(path):
196 """Set file attributes to hidden if possible"""
197 from ctypes.wintypes import BOOL, DWORD, LPWSTR
198- import ctypes
199 # <https://docs.microsoft.com/windows/desktop/api/fileapi/nf-fileapi-setfileattributesw>
200 SetFileAttributes = ctypes.windll.kernel32.SetFileAttributesW
201 SetFileAttributes.argtypes = LPWSTR, DWORD
202@@ -399,8 +399,8 @@
203
204
205 def _ctypes_is_local_pid_dead(pid):
206- kernel32 = ctypes.windll.kernel32
207 """True if pid doesn't correspond to live process on this machine"""
208+ kernel32 = ctypes.windll.kernel32
209 handle = kernel32.OpenProcess(1, False, pid)
210 if not handle:
211 errorcode = ctypes.GetLastError()
212@@ -414,3 +414,29 @@
213 return False
214
215 is_local_pid_dead = _ctypes_is_local_pid_dead
216+
217+
218+def get_fs_type(drive):
219+ """Return file system type for a drive on the system.
220+
221+ Args:
222+ drive: Unicode string with drive including trailing backslash (e.g.
223+ "C:\\")
224+ Returns:
225+ Windows filesystem type name (e.g. "FAT32", "NTFS") or None
226+ if the drive can not be found
227+ """
228+ MAX_FS_TYPE_LENGTH = 16
229+ kernel32 = ctypes.windll.kernel32
230+ GetVolumeInformation = kernel32.GetVolumeInformationW
231+ fs_type = ctypes.create_unicode_buffer(MAX_FS_TYPE_LENGTH + 1)
232+ if GetVolumeInformation(
233+ drive,
234+ None, 0, # lpVolumeName
235+ None, # lpVolumeSerialNumber
236+ None, # lpMaximumComponentLength
237+ None, # lpFileSystemFlags
238+ fs_type, MAX_FS_TYPE_LENGTH,
239+ ):
240+ return fs_type.value
241+ return None

Subscribers

People subscribed via source and target branches