Skip to content
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
12 changes: 11 additions & 1 deletion Doc/library/os.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2758,7 +2758,7 @@ features:
.. availability:: Unix.


.. function:: readlink(path, *, dir_fd=None)
.. function:: readlink(path, *, dir_fd=None, printname=False)

Return a string representing the path to which the symbolic link points. The
result may be either an absolute or relative pathname; if it is relative, it
Expand All @@ -2773,6 +2773,13 @@ features:
This function can also support :ref:`paths relative to directory descriptors
<dir_fd>`.

On Windows, if *printname* is true, return the *print name* of the link --
the target path as it was specified when the link was created --
instead of the *substitute name* used by the system to resolve the link,
which typically includes the ``\\?\`` prefix.
The substitute name is returned if the link has no print name.
*printname* is ignored on non-Windows platforms.

When trying to resolve a path that may contain links, use
:func:`~os.path.realpath` to properly handle recursion and platform
differences.
Expand All @@ -2795,6 +2802,9 @@ features:
substitution path (which typically includes ``\\?\`` prefix) rather
than the optional "print name" field that was previously returned.

.. versionchanged:: next
Added the *printname* parameter.

.. function:: remove(path, *, dir_fd=None)

Remove (delete) the file *path*. If *path* is a directory, an
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,11 @@ os
process via a pidfd. Available on Linux 5.6+.
(Contributed by Maurycy Pawłowski-Wieroński in :gh:`149464`.)

* :func:`os.readlink` has a new *printname* parameter
to return the print name of a link on Windows --
the target path as it was specified when the link was created.
(Contributed by Serhiy Storchaka in :gh:`85004`.)


pydoc
-----
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 @@ -725,6 +725,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(prec)
STRUCT_FOR_ID(preserve_exc)
STRUCT_FOR_ID(print_file_and_line)
STRUCT_FOR_ID(printname)
STRUCT_FOR_ID(priority)
STRUCT_FOR_ID(progress)
STRUCT_FOR_ID(progress_callback)
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.

27 changes: 27 additions & 0 deletions Lib/test/test_os/test_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -3236,6 +3236,33 @@ def test_bytes(self):
self.assertPathEqual(path, self.filelinkb_target)
self.assertIsInstance(path, bytes)

@os_helper.skip_unless_symlink
def test_printname(self):
# The print name is the target as it was specified, without the
# "\\?\" prefix which Windows adds to the substitute name.
os.symlink(self.filelink_target, self.filelink)
self.addCleanup(os_helper.unlink, self.filelink)
self.assertEqual(os.readlink(self.filelink, printname=True),
self.filelink_target)
self.assertPathEqual(os.readlink(self.filelink), self.filelink_target)

@os_helper.skip_unless_symlink
def test_printname_bytes(self):
os.symlink(self.filelinkb_target, self.filelinkb)
self.addCleanup(os_helper.unlink, self.filelinkb)
path = os.readlink(self.filelinkb, printname=True)
self.assertEqual(path, self.filelinkb_target)
self.assertIsInstance(path, bytes)

@os_helper.skip_unless_symlink
def test_printname_relative(self):
# A relative target is not converted to the substitute name.
os.symlink('relative-target', self.filelink)
self.addCleanup(os_helper.unlink, self.filelink)
self.assertEqual(os.readlink(self.filelink, printname=True),
'relative-target')
self.assertEqual(os.readlink(self.filelink), 'relative-target')


@os_helper.skip_unless_symlink
class NonLocalSymlinkTests(unittest.TestCase):
Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_os/test_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,12 @@ def test_create_junction(self):
self.assertEqual(os.path.normcase("\\\\?\\" + self.junction_target),
os.path.normcase(os.readlink(self.junction)))

def test_readlink_printname(self):
_winapi.CreateJunction(self.junction_target, self.junction)
self.assertEqual(os.path.normcase(self.junction_target),
os.path.normcase(os.readlink(self.junction,
printname=True)))

def test_unlink_removes_junction(self):
_winapi.CreateJunction(self.junction_target, self.junction)
self.assertTrue(os.path.exists(self.junction))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add the *printname* parameter in :func:`os.readlink`. On Windows it makes the
function return the print name of the link -- the target path as it was
specified when the link was created.
36 changes: 26 additions & 10 deletions Modules/clinic/posixmodule.c.h

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

42 changes: 33 additions & 9 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -10930,6 +10930,7 @@ os.readlink
path: path_t
*
dir_fd: dir_fd(requires='readlinkat') = None
printname: bool = False

Return a string representing the path to which the symbolic link points.

Expand All @@ -10939,11 +10940,17 @@ that directory.

dir_fd may not be implemented on your platform. If it is unavailable,
using it will raise a NotImplementedError.

On Windows, if printname is true, return the print name of the link --
the target path as it was specified when the link was created -- instead
of the substitute name used by the system to resolve the link.

printname is ignored on non-Windows platforms.
[clinic start generated code]*/

static PyObject *
os_readlink_impl(PyObject *module, path_t *path, int dir_fd)
/*[clinic end generated code: output=d21b732a2e814030 input=03d10130870dbca8]*/
os_readlink_impl(PyObject *module, path_t *path, int dir_fd, int printname)
/*[clinic end generated code: output=f4a4454719a32798 input=9877a2bcf1aa0726]*/
{
#if defined(HAVE_READLINK)
char buffer[MAXPATHLEN+1];
Expand Down Expand Up @@ -11019,24 +11026,41 @@ os_readlink_impl(PyObject *module, path_t *path, int dir_fd)

wchar_t *name = NULL;
Py_ssize_t nameLen = 0;
/* The print name is optional, fall back to the substitute name. */
int is_printname = 0;
if (rdb->ReparseTag == IO_REPARSE_TAG_SYMLINK)
{
name = (wchar_t *)((char*)rdb->SymbolicLinkReparseBuffer.PathBuffer +
rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset);
nameLen = rdb->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(wchar_t);
USHORT offset = rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset;
USHORT length = rdb->SymbolicLinkReparseBuffer.SubstituteNameLength;
if (printname && rdb->SymbolicLinkReparseBuffer.PrintNameLength) {
offset = rdb->SymbolicLinkReparseBuffer.PrintNameOffset;
length = rdb->SymbolicLinkReparseBuffer.PrintNameLength;
is_printname = 1;
}
name = (wchar_t *)((char*)rdb->SymbolicLinkReparseBuffer.PathBuffer
+ offset);
nameLen = length / sizeof(wchar_t);
}
else if (rdb->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
{
name = (wchar_t *)((char*)rdb->MountPointReparseBuffer.PathBuffer +
rdb->MountPointReparseBuffer.SubstituteNameOffset);
nameLen = rdb->MountPointReparseBuffer.SubstituteNameLength / sizeof(wchar_t);
USHORT offset = rdb->MountPointReparseBuffer.SubstituteNameOffset;
USHORT length = rdb->MountPointReparseBuffer.SubstituteNameLength;
if (printname && rdb->MountPointReparseBuffer.PrintNameLength) {
offset = rdb->MountPointReparseBuffer.PrintNameOffset;
length = rdb->MountPointReparseBuffer.PrintNameLength;
is_printname = 1;
}
name = (wchar_t *)((char*)rdb->MountPointReparseBuffer.PathBuffer
+ offset);
nameLen = length / sizeof(wchar_t);
}
else
{
PyErr_SetString(PyExc_ValueError, "not a symbolic link");
}
if (name) {
if (nameLen > 4 && wcsncmp(name, L"\\??\\", 4) == 0) {
/* Only the substitute name is in the NT namespace. */
if (!is_printname && nameLen > 4 && wcsncmp(name, L"\\??\\", 4) == 0) {
/* Our buffer is mutable, so this is okay */
name[1] = L'\\';
}
Expand Down
Loading