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

This commit is contained in:
Steve Dower 2023-05-29 10:05:32 +01:00 committed by GitHub
parent e92ac0a741
commit bfd20d257e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 216 additions and 1 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -1715,6 +1715,70 @@ exit:
#if defined(MS_WINDOWS)
PyDoc_STRVAR(os__path_isdevdrive__doc__,
"_path_isdevdrive($module, /, path)\n"
"--\n"
"\n"
"Determines whether the specified path is on a Windows Dev Drive.");
#define OS__PATH_ISDEVDRIVE_METHODDEF \
{"_path_isdevdrive", _PyCFunction_CAST(os__path_isdevdrive), METH_FASTCALL|METH_KEYWORDS, os__path_isdevdrive__doc__},
static PyObject *
os__path_isdevdrive_impl(PyObject *module, path_t *path);
static PyObject *
os__path_isdevdrive(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
#define NUM_KEYWORDS 1
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
PyObject *ob_item[NUM_KEYWORDS];
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_item = { &_Py_ID(path), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
#else // !Py_BUILD_CORE
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
static const char * const _keywords[] = {"path", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "_path_isdevdrive",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
PyObject *argsbuf[1];
path_t path = PATH_T_INITIALIZE("_path_isdevdrive", "path", 0, 0);
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
if (!args) {
goto exit;
}
if (!path_converter(args[0], &path)) {
goto exit;
}
return_value = os__path_isdevdrive_impl(module, &path);
exit:
/* Cleanup for path */
path_cleanup(&path);
return return_value;
}
#endif /* defined(MS_WINDOWS) */
#if defined(MS_WINDOWS)
PyDoc_STRVAR(os__getfullpathname__doc__,
"_getfullpathname($module, path, /)\n"
"--\n"
@ -11379,6 +11443,10 @@ exit:
#define OS_LISTMOUNTS_METHODDEF
#endif /* !defined(OS_LISTMOUNTS_METHODDEF) */
#ifndef OS__PATH_ISDEVDRIVE_METHODDEF
#define OS__PATH_ISDEVDRIVE_METHODDEF
#endif /* !defined(OS__PATH_ISDEVDRIVE_METHODDEF) */
#ifndef OS__GETFULLPATHNAME_METHODDEF
#define OS__GETFULLPATHNAME_METHODDEF
#endif /* !defined(OS__GETFULLPATHNAME_METHODDEF) */
@ -11922,4 +11990,4 @@ exit:
#ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF
#define OS_WAITSTATUS_TO_EXITCODE_METHODDEF
#endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */
/*[clinic end generated code: output=47750e0e29c8d707 input=a9049054013a1b77]*/
/*[clinic end generated code: output=9d8b0d6717c9af54 input=a9049054013a1b77]*/

View file

@ -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)
{
@ -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