Skip to content
Closed
9 changes: 6 additions & 3 deletions Lib/posixpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,8 @@ def abspath(path):
# Return a canonical path (i.e. the absolute location of a file on the
# filesystem).

_MAXLINKS = 40 # TODO: use limit set by OS

def realpath(filename, *, strict=False):
"""Return the canonical path of the specified filename, eliminating any
symbolic links encountered in the path."""
Expand All @@ -402,7 +404,8 @@ def realpath(filename, *, strict=False):
curdir = '.'
pardir = '..'
getcwd = os.getcwd
return _realpath(filename, strict, sep, curdir, pardir, getcwd)
return _realpath(filename, strict, sep, curdir, pardir, getcwd,
maxlinks=_MAXLINKS if strict else None)

def _realpath(filename, strict=False, sep=sep, curdir=curdir, pardir=pardir,
getcwd=os.getcwd, lstat=os.lstat, readlink=os.readlink, maxlinks=None):
Expand Down Expand Up @@ -453,7 +456,7 @@ def _realpath(filename, strict=False, sep=sep, curdir=curdir, pardir=pardir,
if link_count > maxlinks:
if strict:
raise OSError(errno.ELOOP, os.strerror(errno.ELOOP),
newpath)
filename)
path = newpath
continue
elif newpath in seen:
Expand All @@ -465,7 +468,7 @@ def _realpath(filename, strict=False, sep=sep, curdir=curdir, pardir=pardir,
# The symlink is not resolved, so we must have a symlink loop.
if strict:
raise OSError(errno.ELOOP, os.strerror(errno.ELOOP),
newpath)
filename)
path = newpath
continue
target = readlink(newpath)
Expand Down
27 changes: 26 additions & 1 deletion Lib/test/test_posixpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import posixpath
import sys
import unittest
from posixpath import realpath, abspath, dirname, basename
from posixpath import _MAXLINKS, realpath, abspath, dirname, basename
from test import test_genericpath
from test.support import import_helper
from test.support import cpython_only, os_helper
Expand Down Expand Up @@ -689,6 +689,31 @@ def test_realpath_unreadable_symlink(self):
os.chmod(ABSTFN, 0o755, follow_symlinks=False)
os.unlink(ABSTFN)

@os_helper.skip_unless_symlink
@skip_if_ABSTFN_contains_backslash
def test_realpath_too_many_symlinks(self):
try:
os.mkdir(ABSTFN)
os.symlink('.', f'{ABSTFN}/link')
self.assertEqual(realpath(ABSTFN + '/link' * _MAXLINKS), ABSTFN)
self.assertEqual(realpath(ABSTFN + '/link' * _MAXLINKS,
strict=True), ABSTFN)
self.assertEqual(realpath(ABSTFN + '/link' * (_MAXLINKS+1)), ABSTFN)
with self.assertRaises(OSError):
realpath(ABSTFN + '/link' * (_MAXLINKS+1), strict=True)

# Test using relative path as well.
with os_helper.change_cwd(ABSTFN):
self.assertEqual(realpath('link/' * _MAXLINKS), ABSTFN)
self.assertEqual(realpath('link/' * _MAXLINKS, strict=True),
ABSTFN)
self.assertEqual(realpath('link/' * (_MAXLINKS+1)), ABSTFN)
with self.assertRaises(OSError):
realpath('link/' * (_MAXLINKS+1), strict=True)
finally:
os_helper.unlink(f'{ABSTFN}/link')
safe_rmdir(ABSTFN)

def test_relpath(self):
(real_getcwd, os.getcwd) = (os.getcwd, lambda: r"/home/user/bar")
try:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix error message for :func:`os.path.realpath` on Unix.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we remove this from the news entry?

:func:`os.path.realpath` now raises :exc:`OSError` when *strict* mode is enabled and a path with too many symlinks is supplied.