Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-104803: Implement ntpath.isdevdrive for checking whether a path is on a Windows Dev Drive #104805

Merged
merged 3 commits into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

81 changes: 81 additions & 0 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -4530,6 +4530,86 @@ 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
const int PERSISTENT_VOLUME_STATE_DEV_VOLUME = 0x00002000;
zooba marked this conversation as resolved.
Show resolved Hide resolved
#endif
int err = 0;
PyObject *r = NULL;
wchar_t volume[MAX_PATH];
zooba marked this conversation as resolved.
Show resolved Hide resolved

Py_BEGIN_ALLOW_THREADS
if (!GetVolumePathNameW(path->wide, volume, MAX_PATH)) {
/* invalid path of some kind */
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 @@ -15797,6 +15877,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