mirror of https://github.com/python/cpython
gh-104803: Implement ntpath.isdevdrive for checking whether a path is on a Windows Dev Drive (GH-104805)
This commit is contained in:
parent
e92ac0a741
commit
bfd20d257e
|
@ -304,6 +304,24 @@ the :mod:`glob` module.)
|
||||||
Accepts a :term:`path-like object`.
|
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)
|
.. function:: join(path, *paths)
|
||||||
|
|
||||||
Join one or more path segments intelligently. The return value is the
|
Join one or more path segments intelligently. The return value is the
|
||||||
|
|
|
@ -867,3 +867,19 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Use genericpath.* as imported above
|
# Use genericpath.* as imported above
|
||||||
pass
|
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
|
||||||
|
|
|
@ -992,6 +992,26 @@ class TestNtpath(NtpathTestCase):
|
||||||
self.assertTrue(os.path.exists is nt._path_exists)
|
self.assertTrue(os.path.exists is nt._path_exists)
|
||||||
self.assertFalse(inspect.isfunction(os.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):
|
class NtCommonTest(test_genericpath.CommonTest, unittest.TestCase):
|
||||||
pathmodule = ntpath
|
pathmodule = ntpath
|
||||||
|
|
|
@ -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.
|
|
@ -1715,6 +1715,70 @@ exit:
|
||||||
|
|
||||||
#if defined(MS_WINDOWS)
|
#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__,
|
PyDoc_STRVAR(os__getfullpathname__doc__,
|
||||||
"_getfullpathname($module, path, /)\n"
|
"_getfullpathname($module, path, /)\n"
|
||||||
"--\n"
|
"--\n"
|
||||||
|
@ -11379,6 +11443,10 @@ exit:
|
||||||
#define OS_LISTMOUNTS_METHODDEF
|
#define OS_LISTMOUNTS_METHODDEF
|
||||||
#endif /* !defined(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
|
#ifndef OS__GETFULLPATHNAME_METHODDEF
|
||||||
#define OS__GETFULLPATHNAME_METHODDEF
|
#define OS__GETFULLPATHNAME_METHODDEF
|
||||||
#endif /* !defined(OS__GETFULLPATHNAME_METHODDEF) */
|
#endif /* !defined(OS__GETFULLPATHNAME_METHODDEF) */
|
||||||
|
@ -11922,4 +11990,4 @@ exit:
|
||||||
#ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF
|
#ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF
|
||||||
#define OS_WAITSTATUS_TO_EXITCODE_METHODDEF
|
#define OS_WAITSTATUS_TO_EXITCODE_METHODDEF
|
||||||
#endif /* !defined(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]*/
|
||||||
|
|
|
@ -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
|
int
|
||||||
_PyOS_getfullpathname(const wchar_t *path, wchar_t **abspath_p)
|
_PyOS_getfullpathname(const wchar_t *path, wchar_t **abspath_p)
|
||||||
{
|
{
|
||||||
|
@ -15805,6 +15894,7 @@ static PyMethodDef posix_methods[] = {
|
||||||
OS_SETNS_METHODDEF
|
OS_SETNS_METHODDEF
|
||||||
OS_UNSHARE_METHODDEF
|
OS_UNSHARE_METHODDEF
|
||||||
|
|
||||||
|
OS__PATH_ISDEVDRIVE_METHODDEF
|
||||||
OS__PATH_ISDIR_METHODDEF
|
OS__PATH_ISDIR_METHODDEF
|
||||||
OS__PATH_ISFILE_METHODDEF
|
OS__PATH_ISFILE_METHODDEF
|
||||||
OS__PATH_ISLINK_METHODDEF
|
OS__PATH_ISLINK_METHODDEF
|
||||||
|
|
Loading…
Reference in New Issue