From a7687c468ea88ec5fd2d71a48ed2e1d57183a8c6 Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Fri, 31 May 2024 09:43:07 +0200 Subject: [PATCH 01/13] Improve accuracy of `ntpath.normpath()` & `ntpath.abspath()` --- Include/internal/pycore_fileutils.h | 3 +- Lib/ntpath.py | 48 ++++++++++------ Lib/test/test_ntpath.py | 19 ++++++ Lib/test/test_posixpath.py | 1 + Modules/clinic/posixmodule.c.h | 73 ++++++++++++++++++++++- Modules/posixmodule.c | 30 ++++++++-- Python/fileutils.c | 89 ++++++++++++++++++++--------- 7 files changed, 211 insertions(+), 52 deletions(-) diff --git a/Include/internal/pycore_fileutils.h b/Include/internal/pycore_fileutils.h index 13f86b01bbfe8f..93c4118e5f7c20 100644 --- a/Include/internal/pycore_fileutils.h +++ b/Include/internal/pycore_fileutils.h @@ -279,7 +279,8 @@ extern size_t _Py_find_basename(const wchar_t *filename); // Export for '_testinternalcapi' shared extension PyAPI_FUNC(wchar_t*) _Py_normpath(wchar_t *path, Py_ssize_t size); -extern wchar_t *_Py_normpath_and_size(wchar_t *path, Py_ssize_t size, Py_ssize_t *length); +extern wchar_t *_Py_normpath_and_size(wchar_t *path, Py_ssize_t size, Py_ssize_t *length, + int explicit_curdir); // The Windows Games API family does not provide these functions // so provide our own implementations. Remove them in case they get added diff --git a/Lib/ntpath.py b/Lib/ntpath.py index 83e2d3b865757c..2836cbd6314d67 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -554,28 +554,21 @@ def normpath(path): return prefix + sep.join(comps) -def _abspath_fallback(path): - """Return the absolute version of a path as a fallback function in case - `nt._getfullpathname` is not available or raises OSError. See bpo-31047 for - more. - - """ - - path = os.fspath(path) - if not isabs(path): - if isinstance(path, bytes): - cwd = os.getcwdb() - else: - cwd = os.getcwd() - path = join(cwd, path) - return normpath(path) - # Return an absolute path. try: from nt import _getfullpathname except ImportError: # not running on Windows - mock up something sensible - abspath = _abspath_fallback + def abspath(path): + """Return the absolute version of a path.""" + path = os.fspath(path) + if not isabs(path): + if isinstance(path, bytes): + cwd = os.getcwdb() + else: + cwd = os.getcwd() + path = join(cwd, path) + return normpath(path) else: # use native Windows method on Windows def abspath(path): @@ -583,7 +576,26 @@ def abspath(path): try: return _getfullpathname(normpath(path)) except (OSError, ValueError): - return _abspath_fallback(path) + # See gh-75230, handle outside for cleaner traceback + pass + path = os.fspath(path) + if not isabs(path): + if isinstance(path, bytes): + sep = b'/' + cwd = os.getcwdb() + else: + sep = '/' + cwd = os.getcwd() + drive, root, path = splitroot(path) + if drive and drive != splitroot(cwd)[0]: + try: + path = join(_getfullpathname(drive), path) + except (OSError, ValueError): + # Invalid drive \x00: on Windows; assume root directory + path = drive + sep + path + else: + path = join(cwd, root + path) + return normpath(path) try: from nt import _findfirstfile, _getfinalpathname, readlink as _nt_readlink diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index 64cbfaaaaa0690..a0e5a8e66279be 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -347,6 +347,7 @@ def test_normpath(self): tester("ntpath.normpath('..')", r'..') tester("ntpath.normpath('.')", r'.') + tester("ntpath.normpath('c:.')", 'c:') tester("ntpath.normpath('')", r'.') tester("ntpath.normpath('/')", '\\') tester("ntpath.normpath('c:/')", 'c:\\') @@ -354,6 +355,7 @@ def test_normpath(self): tester("ntpath.normpath('c:/../../..')", 'c:\\') tester("ntpath.normpath('../.././..')", r'..\..\..') tester("ntpath.normpath('K:../.././..')", r'K:..\..\..') + tester("ntpath.normpath('./a/b')", r'a\b') tester("ntpath.normpath('C:////a/b')", r'C:\a\b') tester("ntpath.normpath('//machine/share//a/b')", r'\\machine\share\a\b') @@ -806,6 +808,9 @@ def test_abspath(self): tester('ntpath.abspath("C:\\spam. . .")', "C:\\spam") tester('ntpath.abspath("C:/nul")', "\\\\.\\nul") tester('ntpath.abspath("C:\\nul")', "\\\\.\\nul") + self.assertTrue(ntpath.isabs(ntpath.abspath("C:spam"))) + self.assertTrue(ntpath.isabs(ntpath.abspath("C:\x00"))) + self.assertTrue(ntpath.isabs(ntpath.abspath("\x00:spam"))) tester('ntpath.abspath("//..")', "\\\\") tester('ntpath.abspath("//../")', "\\\\..\\") tester('ntpath.abspath("//../..")', "\\\\..\\") @@ -836,6 +841,20 @@ def test_abspath(self): tester('ntpath.abspath("")', cwd_dir) tester('ntpath.abspath(" ")', cwd_dir + "\\ ") tester('ntpath.abspath("?")', cwd_dir + "\\?") + tester('ntpath.abspath("con")', r"\\.\con") + # bpo-45354: Windows 11 changed MS-DOS device name handling + if sys.getwindowsversion()[:3] < (10, 0, 22000): + tester('ntpath.abspath("./con")', r"\\.\con") + tester('ntpath.abspath("foo/../con")', r"\\.\con") + tester('ntpath.abspath("con/foo/..")', r"\\.\con") + tester('ntpath.abspath("con/.")', r"\\.\con") + else: + tester('ntpath.abspath("./con")', cwd_dir + r"\con") + tester('ntpath.abspath("foo/../con")', cwd_dir + r"\con") + tester('ntpath.abspath("con/foo/..")', cwd_dir + r"\con") + tester('ntpath.abspath("con/.")', cwd_dir + r"\con") + tester('ntpath.abspath("./Z:spam")', cwd_dir + r"\Z:spam") + tester('ntpath.abspath("spam/../Z:eggs")', cwd_dir + r"\Z:eggs") drive, _ = ntpath.splitdrive(cwd_dir) tester('ntpath.abspath("/abc/")', drive + "\\abc") diff --git a/Lib/test/test_posixpath.py b/Lib/test/test_posixpath.py index 57a24e9c70d5e5..5e6e8fc49234ec 100644 --- a/Lib/test/test_posixpath.py +++ b/Lib/test/test_posixpath.py @@ -382,6 +382,7 @@ def test_expanduser_pwd2(self): ("///..//./foo/.//bar", "/foo/bar"), (".", "."), (".//.", "."), + ("./foo/bar", "foo/bar"), ("..", ".."), ("../", ".."), ("../foo", "../foo"), diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index c7a447b455c594..4d428283e0d40b 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -2428,6 +2428,77 @@ os__path_splitroot_ex(PyObject *module, PyObject *const *args, Py_ssize_t nargs, return return_value; } +PyDoc_STRVAR(os__path_normpath_ex__doc__, +"_path_normpath_ex($module, /, path, *, explicit_curdir=False)\n" +"--\n" +"\n" +"Normalize path, eliminating double slashes, etc."); + +#define OS__PATH_NORMPATH_EX_METHODDEF \ + {"_path_normpath_ex", _PyCFunction_CAST(os__path_normpath_ex), METH_FASTCALL|METH_KEYWORDS, os__path_normpath_ex__doc__}, + +static PyObject * +os__path_normpath_ex_impl(PyObject *module, path_t *path, + int explicit_curdir); + +static PyObject * +os__path_normpath_ex(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 2 + 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), &_Py_ID(explicit_curdir), }, + }; + #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", "explicit_curdir", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "_path_normpath_ex", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; + path_t path = PATH_T_INITIALIZE("_path_normpath_ex", "path", 0, 1, 1, 0, 0); + int explicit_curdir = 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; + } + if (!noptargs) { + goto skip_optional_kwonly; + } + explicit_curdir = PyObject_IsTrue(args[1]); + if (explicit_curdir < 0) { + goto exit; + } +skip_optional_kwonly: + return_value = os__path_normpath_ex_impl(module, &path, explicit_curdir); + +exit: + /* Cleanup for path */ + path_cleanup(&path); + + return return_value; +} + PyDoc_STRVAR(os__path_normpath__doc__, "_path_normpath($module, /, path)\n" "--\n" @@ -12795,4 +12866,4 @@ os__supports_virtual_terminal(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF #define OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF #endif /* !defined(OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF) */ -/*[clinic end generated code: output=300bd1c54dc43765 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=41d3b3fd06c77934 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index bb35cfd9cdb138..e2f6a11094e371 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -5526,21 +5526,25 @@ os__path_splitroot_ex_impl(PyObject *module, path_t *path) /*[clinic input] -os._path_normpath +os._path_normpath_ex path: path_t(make_wide=True, nonstrict=True) + * + explicit_curdir: bool = False Normalize path, eliminating double slashes, etc. [clinic start generated code]*/ static PyObject * -os__path_normpath_impl(PyObject *module, path_t *path) -/*[clinic end generated code: output=d353e7ed9410c044 input=3d4ac23b06332dcb]*/ +os__path_normpath_ex_impl(PyObject *module, path_t *path, + int explicit_curdir) +/*[clinic end generated code: output=4c4c3bf33a70fe57 input=90fe0dfc4b3a751b]*/ { PyObject *result; Py_ssize_t norm_len; wchar_t *norm_path = _Py_normpath_and_size((wchar_t *)path->wide, - path->length, &norm_len); + path->length, &norm_len, + explicit_curdir); if (!norm_len) { result = PyUnicode_FromOrdinal('.'); } @@ -5553,6 +5557,23 @@ os__path_normpath_impl(PyObject *module, path_t *path) return result; } + +/*[clinic input] +os._path_normpath + + path: path_t(make_wide=True, nonstrict=True) + +Normalize path, eliminating double slashes, etc. +[clinic start generated code]*/ + +static PyObject * +os__path_normpath_impl(PyObject *module, path_t *path) +/*[clinic end generated code: output=d353e7ed9410c044 input=3d4ac23b06332dcb]*/ +{ + + return os__path_normpath_ex_impl(module, path, 0); +} + /*[clinic input] os.mkdir @@ -16891,6 +16912,7 @@ static PyMethodDef posix_methods[] = { OS__GETVOLUMEPATHNAME_METHODDEF OS__PATH_SPLITROOT_METHODDEF OS__PATH_SPLITROOT_EX_METHODDEF + OS__PATH_NORMPATH_EX_METHODDEF OS__PATH_NORMPATH_METHODDEF OS_GETLOADAVG_METHODDEF OS_URANDOM_METHODDEF diff --git a/Python/fileutils.c b/Python/fileutils.c index e6a5391a3a28b5..1bbc1d7489d6ea 100644 --- a/Python/fileutils.c +++ b/Python/fileutils.c @@ -2481,9 +2481,12 @@ _Py_find_basename(const wchar_t *filename) make the path longer, and will not fail. 'size' is the length of the path, if known. If -1, the first null character will be assumed to be the end of the path. 'normsize' will be set to contain the - length of the resulting normalized path. */ + length of the resulting normalized path. If 'explicit_curdir' is + set, an explicit curdir will be used for qualified referencing in + the cwd. */ wchar_t * -_Py_normpath_and_size(wchar_t *path, Py_ssize_t size, Py_ssize_t *normsize) +_Py_normpath_and_size(wchar_t *path, Py_ssize_t size, Py_ssize_t *normsize, + int explicit_curdir) { assert(path != NULL); if ((size < 0 && !path[0]) || size == 0) { @@ -2495,6 +2498,7 @@ _Py_normpath_and_size(wchar_t *path, Py_ssize_t size, Py_ssize_t *normsize) wchar_t *p2 = path; // destination of a scanned character to be ljusted wchar_t *minP2 = path; // the beginning of the destination range wchar_t lastC = L'\0'; // the last ljusted character, p2[-1] in most cases + int explicit = 0; // uses qualified referencing in the cwd #define IS_END(x) (pEnd ? (x) == pEnd : !*(x)) #ifdef ALTSEP @@ -2504,38 +2508,40 @@ _Py_normpath_and_size(wchar_t *path, Py_ssize_t size, Py_ssize_t *normsize) #endif #define SEP_OR_END(x) (IS_SEP(x) || IS_END(x)) - if (p1[0] == L'.' && IS_SEP(&p1[1])) { - // Skip leading '.\' - path = &path[2]; - while (IS_SEP(path)) { - path++; - } - p1 = p2 = minP2 = path; - lastC = SEP; - } - else { - Py_ssize_t drvsize, rootsize; - _Py_skiproot(path, size, &drvsize, &rootsize); - if (drvsize || rootsize) { - // Skip past root and update minP2 - p1 = &path[drvsize + rootsize]; + Py_ssize_t drvsize, rootsize; + _Py_skiproot(path, size, &drvsize, &rootsize); + if (drvsize || rootsize) { + // Skip past root and update minP2 + p1 = &path[drvsize + rootsize]; #ifndef ALTSEP - p2 = p1; + p2 = p1; #else - for (; p2 < p1; ++p2) { - if (*p2 == ALTSEP) { - *p2 = SEP; - } + for (; p2 < p1; ++p2) { + if (*p2 == ALTSEP) { + *p2 = SEP; } + } #endif - minP2 = p2 - 1; - lastC = *minP2; + minP2 = p2 - 1; + lastC = *minP2; #ifdef MS_WINDOWS - if (lastC != SEP) { - minP2++; - } + if (lastC != SEP) { + minP2++; + } #endif + } + if (p1[0] == L'.' && SEP_OR_END(&p1[1])) { + // Skip leading '.\' + lastC = *++p1; +#ifdef ALTSEP + if (lastC == ALTSEP) { + lastC = SEP; + } +#endif + while (IS_SEP(p1)) { + p1++; } + explicit = 1; } /* if pEnd is specified, check that. Else, check for null terminator */ @@ -2567,9 +2573,11 @@ _Py_normpath_and_size(wchar_t *path, Py_ssize_t size, Py_ssize_t *normsize) p2 = p3 + 1; } else { p2 = p3; + explicit = 1; } p1 += 1; } else if (sep_at_1) { + explicit = 1; } else { *p2++ = lastC = c; } @@ -2589,6 +2597,31 @@ _Py_normpath_and_size(wchar_t *path, Py_ssize_t size, Py_ssize_t *normsize) } else { --p2; } + if (explicit_curdir && !rootsize && explicit) { + // Add explicit curdir + if (p2 == minP2 - 1) { + // Set to '.' + p2++; + assert(p2 < p1); + *p2 = L'.'; + } + else if (minP2[0] != L'.' || minP2[1] != L'.' || + !SEP_OR_END(&minP2[2])) + { + // Add leading '.\' + wchar_t *p3 = p2; + p2 += 2; + assert(p2 < p1); + while (p3 != minP2) { + p3[2] = *p3; + p3--; + } + p3[2] = p3[0]; + p3[1] = SEP; + p3[0] = L'.'; + } + p2[1] = L'\0'; + } *normsize = p2 - path + 1; #undef SEP_OR_END #undef IS_SEP @@ -2605,7 +2638,7 @@ wchar_t * _Py_normpath(wchar_t *path, Py_ssize_t size) { Py_ssize_t norm_length; - return _Py_normpath_and_size(path, size, &norm_length); + return _Py_normpath_and_size(path, size, &norm_length, 0); } From 05326382194727e1df4e01503e5cf376c4bb6a6e Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Fri, 31 May 2024 09:52:56 +0200 Subject: [PATCH 02/13] Use `explicit_curdir` --- Lib/ntpath.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index 2836cbd6314d67..b8d6054ad1604b 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -556,6 +556,7 @@ def normpath(path): # Return an absolute path. try: + from nt import _path_normpath_ex as _normpath from nt import _getfullpathname except ImportError: # not running on Windows - mock up something sensible @@ -574,7 +575,7 @@ def abspath(path): def abspath(path): """Return the absolute version of a path.""" try: - return _getfullpathname(normpath(path)) + return _getfullpathname(_normpath(path, explict_curdir=True)) except (OSError, ValueError): # See gh-75230, handle outside for cleaner traceback pass From d7d06fa3fe20124d245124958e69d11818a5687a Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Fri, 31 May 2024 12:44:39 +0000 Subject: [PATCH 03/13] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst b/Misc/NEWS.d/next/Core and Builtins/2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst new file mode 100644 index 00000000000000..832bf6444dbdad --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst @@ -0,0 +1 @@ +Improve accuracy of `os.path.normpath()` & `os.path.abspath()` on Windows. From e68604d5e621c3acbd7ecf568ae4c10a01969a87 Mon Sep 17 00:00:00 2001 From: Nice Zombies Date: Fri, 31 May 2024 14:45:11 +0200 Subject: [PATCH 04/13] Update 2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst --- .../2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst b/Misc/NEWS.d/next/Core and Builtins/2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst index 832bf6444dbdad..0631635e477da3 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst @@ -1 +1 @@ -Improve accuracy of `os.path.normpath()` & `os.path.abspath()` on Windows. +Improve accuracy of :func:`os.path.normpath` & :func:`os.path.abspath` on Windows. From b76c131bb641242fe55656df635aeabf539dad7d Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Fri, 31 May 2024 15:06:09 +0200 Subject: [PATCH 05/13] Update global strings --- Include/internal/pycore_global_objects_fini_generated.h | 1 + Include/internal/pycore_global_strings.h | 1 + Include/internal/pycore_runtime_init_generated.h | 1 + Include/internal/pycore_unicodeobject_generated.h | 3 +++ 4 files changed, 6 insertions(+) diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index a0f8fb71c1ff37..6528f35675ce19 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -932,6 +932,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(exception)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(existing_file_name)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(exp)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(explicit_curdir)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(extend)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(extra_tokens)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(f)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 57d85020f14e05..87ebc099640cfe 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -421,6 +421,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(exception) STRUCT_FOR_ID(existing_file_name) STRUCT_FOR_ID(exp) + STRUCT_FOR_ID(explicit_curdir) STRUCT_FOR_ID(extend) STRUCT_FOR_ID(extra_tokens) STRUCT_FOR_ID(f) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index e62ebd659d30e8..caecd774c10e89 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -930,6 +930,7 @@ extern "C" { INIT_ID(exception), \ INIT_ID(existing_file_name), \ INIT_ID(exp), \ + INIT_ID(explicit_curdir), \ INIT_ID(extend), \ INIT_ID(extra_tokens), \ INIT_ID(f), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 892f580e8a6846..ca30b3062caac8 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -1104,6 +1104,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(exp); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(explicit_curdir); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(extend); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); From a9b6cc26569e4644c9984ab82c99c240be7ab68a Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Fri, 31 May 2024 15:11:18 +0200 Subject: [PATCH 06/13] Fix typo --- Lib/ntpath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index b8d6054ad1604b..662754edf37e29 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -575,7 +575,7 @@ def abspath(path): def abspath(path): """Return the absolute version of a path.""" try: - return _getfullpathname(_normpath(path, explict_curdir=True)) + return _getfullpathname(_normpath(path, explicit_curdir=True)) except (OSError, ValueError): # See gh-75230, handle outside for cleaner traceback pass From 3c4c3d2d064d88a3ea5c25312e422adcc6344533 Mon Sep 17 00:00:00 2001 From: Nice Zombies Date: Sun, 2 Jun 2024 13:51:14 +0200 Subject: [PATCH 07/13] Update 2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst --- .../2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst b/Misc/NEWS.d/next/Core and Builtins/2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst index 0631635e477da3..f4a5693a555f05 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst @@ -1 +1 @@ -Improve accuracy of :func:`os.path.normpath` & :func:`os.path.abspath` on Windows. +Improve accuracy of :func:`os.path.normpath` and support qualified referencing for :func:`os.path.abspath` on Windows. From a5cfbf2b6dc34bc08c8150010c60f0a742b3819d Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Sun, 2 Jun 2024 13:53:24 +0200 Subject: [PATCH 08/13] Split pull request --- Lib/ntpath.py | 48 ++++++++++++++++------------------------- Lib/test/test_ntpath.py | 3 --- 2 files changed, 18 insertions(+), 33 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index 662754edf37e29..828b8075ba01fd 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -554,22 +554,29 @@ def normpath(path): return prefix + sep.join(comps) +def _abspath_fallback(path): + """Return the absolute version of a path as a fallback function in case + `nt._getfullpathname` is not available or raises OSError. See bpo-31047 for + more. + + """ + + path = os.fspath(path) + if not isabs(path): + if isinstance(path, bytes): + cwd = os.getcwdb() + else: + cwd = os.getcwd() + path = join(cwd, path) + return normpath(path) + # Return an absolute path. try: from nt import _path_normpath_ex as _normpath from nt import _getfullpathname except ImportError: # not running on Windows - mock up something sensible - def abspath(path): - """Return the absolute version of a path.""" - path = os.fspath(path) - if not isabs(path): - if isinstance(path, bytes): - cwd = os.getcwdb() - else: - cwd = os.getcwd() - path = join(cwd, path) - return normpath(path) + abspath = _abspath_fallback else: # use native Windows method on Windows def abspath(path): @@ -577,26 +584,7 @@ def abspath(path): try: return _getfullpathname(_normpath(path, explicit_curdir=True)) except (OSError, ValueError): - # See gh-75230, handle outside for cleaner traceback - pass - path = os.fspath(path) - if not isabs(path): - if isinstance(path, bytes): - sep = b'/' - cwd = os.getcwdb() - else: - sep = '/' - cwd = os.getcwd() - drive, root, path = splitroot(path) - if drive and drive != splitroot(cwd)[0]: - try: - path = join(_getfullpathname(drive), path) - except (OSError, ValueError): - # Invalid drive \x00: on Windows; assume root directory - path = drive + sep + path - else: - path = join(cwd, root + path) - return normpath(path) + return _abspath_fallback(path) try: from nt import _findfirstfile, _getfinalpathname, readlink as _nt_readlink diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index a0e5a8e66279be..7aa758d179046f 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -808,9 +808,6 @@ def test_abspath(self): tester('ntpath.abspath("C:\\spam. . .")', "C:\\spam") tester('ntpath.abspath("C:/nul")', "\\\\.\\nul") tester('ntpath.abspath("C:\\nul")', "\\\\.\\nul") - self.assertTrue(ntpath.isabs(ntpath.abspath("C:spam"))) - self.assertTrue(ntpath.isabs(ntpath.abspath("C:\x00"))) - self.assertTrue(ntpath.isabs(ntpath.abspath("\x00:spam"))) tester('ntpath.abspath("//..")', "\\\\") tester('ntpath.abspath("//../")', "\\\\..\\") tester('ntpath.abspath("//../..")', "\\\\..\\") From 335a7378d1b7c9aed2a91184dcd1478a42531ffc Mon Sep 17 00:00:00 2001 From: Nice Zombies Date: Wed, 19 Jun 2024 13:22:25 +0200 Subject: [PATCH 09/13] Rename 2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst to 2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst --- .../2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/{Core and Builtins => Library}/2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst (100%) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst b/Misc/NEWS.d/next/Library/2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst similarity index 100% rename from Misc/NEWS.d/next/Core and Builtins/2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst rename to Misc/NEWS.d/next/Library/2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst From e3e42c501d1fb0fc35954f6d0cbedcfbaa3a43be Mon Sep 17 00:00:00 2001 From: Nice Zombies Date: Wed, 13 Nov 2024 13:43:39 +0100 Subject: [PATCH 10/13] Update and rename 2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst to 2024-05-31-12-44-38.gh-issue-126782.Atm9ol.rst --- .../next/Library/2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst | 1 - .../next/Library/2024-05-31-12-44-38.gh-issue-126782.Atm9ol.rst | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 Misc/NEWS.d/next/Library/2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst create mode 100644 Misc/NEWS.d/next/Library/2024-05-31-12-44-38.gh-issue-126782.Atm9ol.rst diff --git a/Misc/NEWS.d/next/Library/2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst b/Misc/NEWS.d/next/Library/2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst deleted file mode 100644 index f4a5693a555f05..00000000000000 --- a/Misc/NEWS.d/next/Library/2024-05-31-12-44-38.gh-issue-119826.Atm9ol.rst +++ /dev/null @@ -1 +0,0 @@ -Improve accuracy of :func:`os.path.normpath` and support qualified referencing for :func:`os.path.abspath` on Windows. diff --git a/Misc/NEWS.d/next/Library/2024-05-31-12-44-38.gh-issue-126782.Atm9ol.rst b/Misc/NEWS.d/next/Library/2024-05-31-12-44-38.gh-issue-126782.Atm9ol.rst new file mode 100644 index 00000000000000..e57fdcfc3b7aa8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-05-31-12-44-38.gh-issue-126782.Atm9ol.rst @@ -0,0 +1 @@ +Support qualified referencing for :func:`os.path.abspath` on Windows. From cb5b002a4c161f3f903238259cace2ca72355325 Mon Sep 17 00:00:00 2001 From: Nice Zombies Date: Wed, 13 Nov 2024 20:22:02 +0100 Subject: [PATCH 11/13] Split up PR --- Lib/test/test_ntpath.py | 2 -- Lib/test/test_posixpath.py | 1 - Python/fileutils.c | 53 +++++++++++++++++++------------------- 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index 7aa758d179046f..95be55af7557f5 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -347,7 +347,6 @@ def test_normpath(self): tester("ntpath.normpath('..')", r'..') tester("ntpath.normpath('.')", r'.') - tester("ntpath.normpath('c:.')", 'c:') tester("ntpath.normpath('')", r'.') tester("ntpath.normpath('/')", '\\') tester("ntpath.normpath('c:/')", 'c:\\') @@ -355,7 +354,6 @@ def test_normpath(self): tester("ntpath.normpath('c:/../../..')", 'c:\\') tester("ntpath.normpath('../.././..')", r'..\..\..') tester("ntpath.normpath('K:../.././..')", r'K:..\..\..') - tester("ntpath.normpath('./a/b')", r'a\b') tester("ntpath.normpath('C:////a/b')", r'C:\a\b') tester("ntpath.normpath('//machine/share//a/b')", r'\\machine\share\a\b') diff --git a/Lib/test/test_posixpath.py b/Lib/test/test_posixpath.py index fa6bb3a565a9f6..ca5cf42f8fcd71 100644 --- a/Lib/test/test_posixpath.py +++ b/Lib/test/test_posixpath.py @@ -388,7 +388,6 @@ def test_expanduser_pwd2(self): ("///..//./foo/.//bar", "/foo/bar"), (".", "."), (".//.", "."), - ("./foo/bar", "foo/bar"), ("..", ".."), ("../", ".."), ("../foo", "../foo"), diff --git a/Python/fileutils.c b/Python/fileutils.c index a6cceb7656c012..1dd470eb9ab35c 100644 --- a/Python/fileutils.c +++ b/Python/fileutils.c @@ -2510,40 +2510,39 @@ _Py_normpath_and_size(wchar_t *path, Py_ssize_t size, Py_ssize_t *normsize, #endif #define SEP_OR_END(x) (IS_SEP(x) || IS_END(x)) - Py_ssize_t drvsize, rootsize; - _Py_skiproot(path, size, &drvsize, &rootsize); - if (drvsize || rootsize) { - // Skip past root and update minP2 - p1 = &path[drvsize + rootsize]; + if (p1[0] == L'.' && IS_SEP(&p1[1])) { + // Skip leading '.\' + path = &path[2]; + while (IS_SEP(path)) { + path++; + } + p1 = p2 = minP2 = path; + lastC = SEP; + explicit = 1; + } + else { + Py_ssize_t drvsize, rootsize; + _Py_skiproot(path, size, &drvsize, &rootsize); + if (drvsize || rootsize) { + // Skip past root and update minP2 + p1 = &path[drvsize + rootsize]; #ifndef ALTSEP - p2 = p1; + p2 = p1; #else - for (; p2 < p1; ++p2) { - if (*p2 == ALTSEP) { - *p2 = SEP; + for (; p2 < p1; ++p2) { + if (*p2 == ALTSEP) { + *p2 = SEP; + } } - } #endif - minP2 = p2 - 1; - lastC = *minP2; + minP2 = p2 - 1; + lastC = *minP2; #ifdef MS_WINDOWS - if (lastC != SEP) { - minP2++; - } -#endif - } - if (p1[0] == L'.' && SEP_OR_END(&p1[1])) { - // Skip leading '.\' - lastC = *++p1; -#ifdef ALTSEP - if (lastC == ALTSEP) { - lastC = SEP; - } + if (lastC != SEP) { + minP2++; + } #endif - while (IS_SEP(p1)) { - p1++; } - explicit = 1; } /* if pEnd is specified, check that. Else, check for null terminator */ From b7743100c0ec61b811edb49e3ecaf140052f798f Mon Sep 17 00:00:00 2001 From: Nice Zombies Date: Wed, 13 Nov 2024 20:34:01 +0100 Subject: [PATCH 12/13] Fix undeclared variable --- Python/fileutils.c | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/Python/fileutils.c b/Python/fileutils.c index 1dd470eb9ab35c..ef8bea0ecb3064 100644 --- a/Python/fileutils.c +++ b/Python/fileutils.c @@ -2510,6 +2510,8 @@ _Py_normpath_and_size(wchar_t *path, Py_ssize_t size, Py_ssize_t *normsize, #endif #define SEP_OR_END(x) (IS_SEP(x) || IS_END(x)) + Py_ssize_t drvsize, rootsize; + _Py_skiproot(path, size, &drvsize, &rootsize); if (p1[0] == L'.' && IS_SEP(&p1[1])) { // Skip leading '.\' path = &path[2]; @@ -2520,29 +2522,25 @@ _Py_normpath_and_size(wchar_t *path, Py_ssize_t size, Py_ssize_t *normsize, lastC = SEP; explicit = 1; } - else { - Py_ssize_t drvsize, rootsize; - _Py_skiproot(path, size, &drvsize, &rootsize); - if (drvsize || rootsize) { - // Skip past root and update minP2 - p1 = &path[drvsize + rootsize]; + else if (drvsize || rootsize) { + // Skip past root and update minP2 + p1 = &path[drvsize + rootsize]; #ifndef ALTSEP - p2 = p1; + p2 = p1; #else - for (; p2 < p1; ++p2) { - if (*p2 == ALTSEP) { - *p2 = SEP; - } + for (; p2 < p1; ++p2) { + if (*p2 == ALTSEP) { + *p2 = SEP; } + } #endif - minP2 = p2 - 1; - lastC = *minP2; + minP2 = p2 - 1; + lastC = *minP2; #ifdef MS_WINDOWS - if (lastC != SEP) { - minP2++; - } -#endif + if (lastC != SEP) { + minP2++; } +#endif } /* if pEnd is specified, check that. Else, check for null terminator */ From 5a6cec544974bdb58f1350a4ddcaae564cb70e1a Mon Sep 17 00:00:00 2001 From: Nice Zombies Date: Wed, 13 Nov 2024 22:28:33 +0100 Subject: [PATCH 13/13] Fix assertion error --- Python/fileutils.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Python/fileutils.c b/Python/fileutils.c index ef8bea0ecb3064..4403adcf9fa3e2 100644 --- a/Python/fileutils.c +++ b/Python/fileutils.c @@ -2514,11 +2514,10 @@ _Py_normpath_and_size(wchar_t *path, Py_ssize_t size, Py_ssize_t *normsize, _Py_skiproot(path, size, &drvsize, &rootsize); if (p1[0] == L'.' && IS_SEP(&p1[1])) { // Skip leading '.\' - path = &path[2]; - while (IS_SEP(path)) { - path++; + p1 = &path[2]; + while (IS_SEP(p1)) { + p1++; } - p1 = p2 = minP2 = path; lastC = SEP; explicit = 1; }