Skip to content

Commit

Permalink
gh-104803: Implement ntpath.isdevdrive for checking whether a path is…
Browse files Browse the repository at this point in the history
… on a Windows Dev Drive (GH-104805)
  • Loading branch information
zooba committed May 29, 2023
1 parent e92ac0a commit bfd20d2
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 1 deletion.
18 changes: 18 additions & 0 deletions Doc/library/os.path.rst
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,24 @@ the :mod:`glob` module.)
Accepts a :term:`path-like object`.


.. function:: isdevdrive(path)

Return ``True`` if pathname *path* is located on a Windows Dev Drive.
A Dev Drive is optimized for developer scenarios, and offers faster
performance for reading and writing files. It is recommended for use for
source code, temporary build directories, package caches, and other
IO-intensive operations.

May raise an error for an invalid path, for example, one without a
recognizable drive, but returns ``False`` on platforms that do not support
Dev Drives. See `the Windows documentation <https://learn.microsoft.com/windows/dev-drive/>`_
for information on enabling and creating Dev Drives.

.. availability:: Windows.

.. versionadded:: 3.12


.. function:: join(path, *paths)

Join one or more path segments intelligently. The return value is the
Expand Down
16 changes: 16 additions & 0 deletions Lib/ntpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -867,3 +867,19 @@ def commonpath(paths):
except ImportError:
# Use genericpath.* as imported above
pass


try:
from nt import _path_isdevdrive
except ImportError:
def isdevdrive(path):
"""Determines whether the specified path is on a Windows Dev Drive."""
# Never a Dev Drive
return False
else:
def isdevdrive(path):
"""Determines whether the specified path is on a Windows Dev Drive."""
try:
return _path_isdevdrive(abspath(path))
except OSError:
return False
20 changes: 20 additions & 0 deletions Lib/test/test_ntpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -992,6 +992,26 @@ def test_fast_paths_in_use(self):
self.assertTrue(os.path.exists is nt._path_exists)
self.assertFalse(inspect.isfunction(os.path.exists))

@unittest.skipIf(os.name != 'nt', "Dev Drives only exist on Win32")
def test_isdevdrive(self):
# Result may be True or False, but shouldn't raise
self.assertIn(ntpath.isdevdrive(os_helper.TESTFN), (True, False))
# ntpath.isdevdrive can handle relative paths
self.assertIn(ntpath.isdevdrive("."), (True, False))
self.assertIn(ntpath.isdevdrive(b"."), (True, False))
# Volume syntax is supported
self.assertIn(ntpath.isdevdrive(os.listvolumes()[0]), (True, False))
# Invalid volume returns False from os.path method
self.assertFalse(ntpath.isdevdrive(r"\\?\Volume{00000000-0000-0000-0000-000000000000}\\"))
# Invalid volume raises from underlying helper
with self.assertRaises(OSError):
nt._path_isdevdrive(r"\\?\Volume{00000000-0000-0000-0000-000000000000}\\")

@unittest.skipIf(os.name == 'nt', "isdevdrive fallback only used off Win32")
def test_isdevdrive_fallback(self):
# Fallback always returns False
self.assertFalse(ntpath.isdevdrive(os_helper.TESTFN))


class NtCommonTest(test_genericpath.CommonTest, unittest.TestCase):
pathmodule = ntpath
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add :func:`os.path.isdevdrive` to detect whether a path is on a Windows Dev
Drive. Returns ``False`` on platforms that do not support Dev Drive, and is
absent on non-Windows platforms.
70 changes: 69 additions & 1 deletion Modules/clinic/posixmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

90 changes: 90 additions & 0 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -4534,6 +4534,95 @@ os_listmounts_impl(PyObject *module, path_t *volume)
}


/*[clinic input]
os._path_isdevdrive
path: path_t
Determines whether the specified path is on a Windows Dev Drive.
[clinic start generated code]*/

static PyObject *
os__path_isdevdrive_impl(PyObject *module, path_t *path)
/*[clinic end generated code: output=1f437ea6677433a2 input=ee83e4996a48e23d]*/
{
#ifndef PERSISTENT_VOLUME_STATE_DEV_VOLUME
/* This flag will be documented at
https://learn.microsoft.com/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_file_fs_persistent_volume_information
after release, and will be available in the latest WinSDK.
We include the flag to avoid a specific version dependency
on the latest WinSDK. */
const int PERSISTENT_VOLUME_STATE_DEV_VOLUME = 0x00002000;
#endif
int err = 0;
PyObject *r = NULL;
wchar_t volume[MAX_PATH];

Py_BEGIN_ALLOW_THREADS
if (!GetVolumePathNameW(path->wide, volume, MAX_PATH)) {
/* invalid path of some kind */
/* Note that this also includes the case where a volume is mounted
in a path longer than 260 characters. This is likely to be rare
and problematic for other reasons, so a (soft) failure in this
check seems okay. */
err = GetLastError();
} else if (GetDriveTypeW(volume) != DRIVE_FIXED) {
/* only care about local dev drives */
r = Py_False;
} else {
HANDLE hVolume = CreateFileW(
volume,
FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL
);
if (hVolume == INVALID_HANDLE_VALUE) {
err = GetLastError();
} else {
FILE_FS_PERSISTENT_VOLUME_INFORMATION volumeState = {0};
volumeState.Version = 1;
volumeState.FlagMask = PERSISTENT_VOLUME_STATE_DEV_VOLUME;
if (!DeviceIoControl(
hVolume,
FSCTL_QUERY_PERSISTENT_VOLUME_STATE,
&volumeState,
sizeof(volumeState),
&volumeState,
sizeof(volumeState),
NULL,
NULL
)) {
err = GetLastError();
}
CloseHandle(hVolume);
if (err == ERROR_INVALID_PARAMETER) {
/* not supported on this platform */
r = Py_False;
} else if (!err) {
r = (volumeState.VolumeFlags & PERSISTENT_VOLUME_STATE_DEV_VOLUME)
? Py_True : Py_False;
}
}
}
Py_END_ALLOW_THREADS

if (err) {
PyErr_SetFromWindowsErr(err);
return NULL;
}

if (r) {
return Py_NewRef(r);
}

return NULL;
}


int
_PyOS_getfullpathname(const wchar_t *path, wchar_t **abspath_p)
{
Expand Down Expand Up @@ -15805,6 +15894,7 @@ static PyMethodDef posix_methods[] = {
OS_SETNS_METHODDEF
OS_UNSHARE_METHODDEF

OS__PATH_ISDEVDRIVE_METHODDEF
OS__PATH_ISDIR_METHODDEF
OS__PATH_ISFILE_METHODDEF
OS__PATH_ISLINK_METHODDEF
Expand Down

0 comments on commit bfd20d2

Please sign in to comment.