summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteve Dower <steve.dower@python.org>2023-05-29 09:05:32 (GMT)
committerGitHub <noreply@github.com>2023-05-29 09:05:32 (GMT)
commitbfd20d257e4ad16a25f4bac0ea4dbb719cdf6bc7 (patch)
tree325056d3c4bbd55dd1b81be6d7ab150b5c0749b1
parente92ac0a741b125f1cffe8c07b054d1dea7b0a05a (diff)
downloadcpython-bfd20d257e4ad16a25f4bac0ea4dbb719cdf6bc7.zip
cpython-bfd20d257e4ad16a25f4bac0ea4dbb719cdf6bc7.tar.gz
cpython-bfd20d257e4ad16a25f4bac0ea4dbb719cdf6bc7.tar.bz2
gh-104803: Implement ntpath.isdevdrive for checking whether a path is on a Windows Dev Drive (GH-104805)
-rw-r--r--Doc/library/os.path.rst18
-rw-r--r--Lib/ntpath.py16
-rw-r--r--Lib/test/test_ntpath.py20
-rw-r--r--Misc/NEWS.d/next/Windows/2023-05-23-19-26-28.gh-issue-104803.gqxYml.rst3
-rw-r--r--Modules/clinic/posixmodule.c.h70
-rw-r--r--Modules/posixmodule.c90
6 files changed, 216 insertions, 1 deletions
diff --git a/Doc/library/os.path.rst b/Doc/library/os.path.rst
index 7881c52..3a668e2 100644
--- a/Doc/library/os.path.rst
+++ b/Doc/library/os.path.rst
@@ -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
diff --git a/Lib/ntpath.py b/Lib/ntpath.py
index 0f3674f..dadcdc0 100644
--- a/Lib/ntpath.py
+++ b/Lib/ntpath.py
@@ -867,3 +867,19 @@ try:
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
diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py
index 0e57c16..538d758 100644
--- a/Lib/test/test_ntpath.py
+++ b/Lib/test/test_ntpath.py
@@ -992,6 +992,26 @@ class TestNtpath(NtpathTestCase):
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
diff --git a/Misc/NEWS.d/next/Windows/2023-05-23-19-26-28.gh-issue-104803.gqxYml.rst b/Misc/NEWS.d/next/Windows/2023-05-23-19-26-28.gh-issue-104803.gqxYml.rst
new file mode 100644
index 0000000..d2242c7
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2023-05-23-19-26-28.gh-issue-104803.gqxYml.rst
@@ -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.
diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h
index 8b0550d..3312bd6 100644
--- a/Modules/clinic/posixmodule.c.h
+++ b/Modules/clinic/posixmodule.c.h
@@ -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]*/
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 294e9a2..8a0c160 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -4534,6 +4534,95 @@ exit:
}
+/*[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