Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2b5a29e

Browse files
committed
gh-82367: Use FindFirstFile Win32 API in ntpath.realpath() (#110298)
* Use `FindFirstFile` Win32 API to fix a bug where `ntpath.realpath()` breaks out of traversing a series of paths where a (handled) `ERROR_ACCESS_DENIED` or `ERROR_SHARING_VIOLATION` occurs. * Update docs to reflect that `ntpath.realpath()` eliminates MS-DOS style names.
1 parent 6592976 commit 2b5a29e

File tree

8 files changed

+132
-8
lines changed

8 files changed

+132
-8
lines changed

Doc/library/os.path.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,8 @@ the :mod:`glob` module.)
377377

378378
Return the canonical path of the specified filename, eliminating any symbolic
379379
links encountered in the path (if they are supported by the operating
380-
system).
380+
system). On Windows, this function will also resolve MS-DOS (also called 8.3)
381+
style names such as ``C:\\PROGRA~1`` to ``C:\\Program Files``.
381382

382383
If a path doesn't exist or a symlink loop is encountered, and *strict* is
383384
``True``, :exc:`OSError` is raised. If *strict* is ``False``, the path is

Doc/whatsnew/3.13.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ Other Language Changes
9797
if supported.
9898
(Contributed by Victor Stinner in :gh:`109649`.)
9999

100+
* :func:`os.path.realpath` now resolves MS-DOS style file names even if
101+
the file is not accessible.
102+
(Contributed by Moonsik Park in :gh:`82367`.)
100103

101104
New Modules
102105
===========

Lib/ntpath.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import genericpath
2424
from genericpath import *
2525

26-
2726
__all__ = ["normcase","isabs","join","splitdrive","splitroot","split","splitext",
2827
"basename","dirname","commonprefix","getsize","getmtime",
2928
"getatime","getctime", "islink","exists","lexists","isdir","isfile",
@@ -601,7 +600,7 @@ def abspath(path):
601600
return _abspath_fallback(path)
602601

603602
try:
604-
from nt import _getfinalpathname, readlink as _nt_readlink
603+
from nt import _findfirstfile, _getfinalpathname, readlink as _nt_readlink
605604
except ImportError:
606605
# realpath is a no-op on systems without _getfinalpathname support.
607606
realpath = abspath
@@ -688,10 +687,15 @@ def _getfinalpathname_nonstrict(path):
688687
except OSError:
689688
# If we fail to readlink(), let's keep traversing
690689
pass
691-
path, name = split(path)
692-
# TODO (bpo-38186): Request the real file name from the directory
693-
# entry using FindFirstFileW. For now, we will return the path
694-
# as best we have it
690+
# If we get these errors, try to get the real name of the file without accessing it.
691+
if ex.winerror in (1, 5, 32, 50, 87, 1920, 1921):
692+
try:
693+
name = _findfirstfile(path)
694+
path, _ = split(path)
695+
except OSError:
696+
path, name = split(path)
697+
else:
698+
path, name = split(path)
695699
if path and not name:
696700
return path + tail
697701
tail = join(name, tail) if tail else name

Lib/test/test_ntpath.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import ntpath
33
import os
44
import string
5+
import subprocess
56
import sys
67
import unittest
78
import warnings
@@ -637,6 +638,48 @@ def test_realpath_cwd(self):
637638
with os_helper.change_cwd(test_dir_short):
638639
self.assertPathEqual(test_file_long, ntpath.realpath("file.txt"))
639640

641+
@unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
642+
def test_realpath_permission(self):
643+
# Test whether python can resolve the real filename of a
644+
# shortened file name even if it does not have permission to access it.
645+
ABSTFN = ntpath.realpath(os_helper.TESTFN)
646+
647+
os_helper.unlink(ABSTFN)
648+
os_helper.rmtree(ABSTFN)
649+
os.mkdir(ABSTFN)
650+
self.addCleanup(os_helper.rmtree, ABSTFN)
651+
652+
test_file = ntpath.join(ABSTFN, "LongFileName123.txt")
653+
test_file_short = ntpath.join(ABSTFN, "LONGFI~1.TXT")
654+
655+
with open(test_file, "wb") as f:
656+
f.write(b"content")
657+
# Automatic generation of short names may be disabled on
658+
# NTFS volumes for the sake of performance.
659+
# They're not supported at all on ReFS and exFAT.
660+
subprocess.run(
661+
# Try to set the short name manually.
662+
['fsutil.exe', 'file', 'setShortName', test_file, 'LONGFI~1.TXT'],
663+
creationflags=subprocess.DETACHED_PROCESS
664+
)
665+
666+
try:
667+
self.assertPathEqual(test_file, ntpath.realpath(test_file_short))
668+
except AssertionError:
669+
raise unittest.SkipTest('the filesystem seems to lack support for short filenames')
670+
671+
# Deny the right to [S]YNCHRONIZE on the file to
672+
# force nt._getfinalpathname to fail with ERROR_ACCESS_DENIED.
673+
p = subprocess.run(
674+
['icacls.exe', test_file, '/deny', '*S-1-5-32-545:(S)'],
675+
creationflags=subprocess.DETACHED_PROCESS
676+
)
677+
678+
if p.returncode:
679+
raise unittest.SkipTest('failed to deny access to the test file')
680+
681+
self.assertPathEqual(test_file, ntpath.realpath(test_file_short))
682+
640683
def test_expandvars(self):
641684
with os_helper.EnvironmentVarGuard() as env:
642685
env.clear()

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,6 +1373,7 @@ Peter Parente
13731373
Alexandre Parenteau
13741374
Dan Parisien
13751375
HyeSoo Park
1376+
Moonsik Park
13761377
William Park
13771378
Claude Paroz
13781379
Heikki Partanen
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:func:`os.path.realpath` now resolves MS-DOS style file names even if
2+
the file is not accessible. Patch by Moonsik Park.

Modules/clinic/posixmodule.c.h

Lines changed: 39 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/posixmodule.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4809,6 +4809,37 @@ os__getfinalpathname_impl(PyObject *module, path_t *path)
48094809
return result;
48104810
}
48114811

4812+
/*[clinic input]
4813+
os._findfirstfile
4814+
path: path_t
4815+
/
4816+
A function to get the real file name without accessing the file in Windows.
4817+
[clinic start generated code]*/
4818+
4819+
static PyObject *
4820+
os__findfirstfile_impl(PyObject *module, path_t *path)
4821+
/*[clinic end generated code: output=106dd3f0779c83dd input=0734dff70f60e1a8]*/
4822+
{
4823+
PyObject *result;
4824+
HANDLE hFindFile;
4825+
WIN32_FIND_DATAW wFileData;
4826+
WCHAR *wRealFileName;
4827+
4828+
Py_BEGIN_ALLOW_THREADS
4829+
hFindFile = FindFirstFileW(path->wide, &wFileData);
4830+
Py_END_ALLOW_THREADS
4831+
4832+
if (hFindFile == INVALID_HANDLE_VALUE) {
4833+
path_error(path);
4834+
return NULL;
4835+
}
4836+
4837+
wRealFileName = wFileData.cFileName;
4838+
result = PyUnicode_FromWideChar(wRealFileName, wcslen(wRealFileName));
4839+
FindClose(hFindFile);
4840+
return result;
4841+
}
4842+
48124843

48134844
/*[clinic input]
48144845
os._getvolumepathname
@@ -15961,6 +15992,7 @@ static PyMethodDef posix_methods[] = {
1596115992
OS__GETFULLPATHNAME_METHODDEF
1596215993
OS__GETDISKUSAGE_METHODDEF
1596315994
OS__GETFINALPATHNAME_METHODDEF
15995+
OS__FINDFIRSTFILE_METHODDEF
1596415996
OS__GETVOLUMEPATHNAME_METHODDEF
1596515997
OS__PATH_SPLITROOT_METHODDEF
1596615998
OS__PATH_NORMPATH_METHODDEF

0 commit comments

Comments
 (0)