diff --git a/Lib/ntpath.py b/Lib/ntpath.py --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -336,15 +336,22 @@ return True # Is a path a mount point? Either a root (with or without drive letter) -# or an UNC path with at most a / or \ after the mount point. +# or an UNC path with at most a / or \ after the mount point or a mounted +# drive. The canonical approach which detects the IO_REPARSE_TAG_MOUNT_POINT +# fails for drive roots. -def ismount(path): - """Test whether a path is a mount point (defined as root of drive)""" - seps = _get_bothseps(path) - root, rest = splitdrive(path) - if root and root[0] in seps: - return (not rest) or (rest in seps) - return rest in seps +try: + from nt import _getvolumepathname + def ismount(path): + return abspath(path) == abspath(_getvolumepathname(path)) +except ImportError: + def ismount(path): + """Test whether a path is a mount point (defined as root of drive)""" + seps = _get_bothseps(path) + root, rest = splitdrive(path) + if root and root[0] in seps: + return (not rest) or (rest in seps) + return rest in seps # Expand paths beginning with '~' or '~user'. diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -256,6 +256,26 @@ # dialogs (#4804) ntpath.sameopenfile(-1, -1) + def test_ismount(self): + self.assertTrue(ntpath.ismount("c:\\")) + self.assertTrue(ntpath.ismount("C:\\")) + self.assertTrue(ntpath.ismount("c:/")) + self.assertTrue(ntpath.ismount("C:/")) + self.assertTrue(ntpath.ismount("\\\\.\\c:\\")) + self.assertTrue(ntpath.ismount("\\\\.\\C:\\")) + + self.assertFalse(ntpath.ismount(support.TESTFN)) + with support.temp_dir() as d: + self.assertFalse(ntpath.ismount(d)) + + # + # Make sure the current folder isn't the root folder + # (or any other volume root) + # + with support.change_cwd(os.path.dirname(sys.executable)): + self.assertFalse(ntpath.ismount("c:")) + self.assertFalse(ntpath.ismount("C:")) + self.assertFalse(ntpath.ismount("\\\\.\\c:")) class NtCommonTest(test_genericpath.CommonTest, unittest.TestCase): pathmodule = ntpath diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -3711,6 +3711,46 @@ else Py_RETURN_FALSE; } + +PyDoc_STRVAR(posix__getvolumepathname__doc__, +"Return volume mount point of the specified path."); + +/* A helper function for ismount on windows */ +static PyObject * +posix__getvolumepathname(PyObject *self, PyObject *args) +{ + PyObject *po, *result; + wchar_t *path, *mountpath=NULL; + size_t bufsize; + BOOL ret; + + if (!PyArg_ParseTuple(args, "U|:_getvolumepathname", &po)) + return NULL; + path = PyUnicode_AsUnicode(po); + if (path == NULL) + return NULL; + + /* Volume path should be shorter than entire path */ + bufsize = max(MAX_PATH, wcslen(path) * 2 * sizeof(wchar_t)+1); + mountpath = malloc(bufsize); + if (mountpath == NULL) + return PyErr_NoMemory(); + + Py_BEGIN_ALLOW_THREADS + ret = GetVolumePathNameW(path, mountpath, bufsize); + Py_END_ALLOW_THREADS + + if (!ret) { + result = win32_error_object("_getvolumepathname", po); + goto exit; + } + result = PyUnicode_FromWideChar(mountpath, wcslen(mountpath)); +exit: + free(mountpath); + return result; +} +/* end of posix__getvolumepathname */ + #endif /* MS_WINDOWS */ PyDoc_STRVAR(posix_mkdir__doc__, @@ -10884,6 +10924,7 @@ {"_getfinalpathname", posix__getfinalpathname, METH_VARARGS, NULL}, {"_isdir", posix__isdir, METH_VARARGS, posix__isdir__doc__}, {"_getdiskusage", win32__getdiskusage, METH_VARARGS, win32__getdiskusage__doc__}, + {"_getvolumepathname", posix__getvolumepathname, METH_VARARGS, posix__getvolumepathname__doc__}, #endif #ifdef HAVE_GETLOADAVG {"getloadavg", posix_getloadavg, METH_NOARGS, posix_getloadavg__doc__},