Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-126782: Support qualified referencing for ntpath.abspath() #126784

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Include/internal/pycore_fileutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,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(facility)
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Lib/ntpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,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
Expand All @@ -573,7 +574,7 @@ def abspath(path):
def abspath(path):
"""Return the absolute version of a path."""
try:
return _getfullpathname(normpath(path))
return _getfullpathname(_normpath(path, explicit_curdir=True))
except (OSError, ValueError):
# See gh-75230, handle outside for cleaner traceback
pass
Expand Down
14 changes: 14 additions & 0 deletions Lib/test/test_ntpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,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")

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support qualified referencing for :func:`os.path.abspath` on Windows.
74 changes: 73 additions & 1 deletion Modules/clinic/posixmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 26 additions & 4 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -5594,21 +5594,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('.');
}
Expand All @@ -5621,6 +5625,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

Expand Down Expand Up @@ -17059,6 +17080,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
Expand Down
77 changes: 53 additions & 24 deletions Python/fileutils.c
Original file line number Diff line number Diff line change
Expand Up @@ -2483,9 +2483,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) {
Expand All @@ -2497,6 +2500,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
Expand All @@ -2506,38 +2510,36 @@ _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];
while (IS_SEP(path)) {
path++;
p1 = &path[2];
while (IS_SEP(p1)) {
p1++;
}
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];
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 */
Expand Down Expand Up @@ -2569,9 +2571,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;
}
Expand All @@ -2591,6 +2595,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
Expand All @@ -2607,7 +2636,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);
}


Expand Down
Loading