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

Skip to content

Commit e17852c

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 21a6263 commit e17852c

File tree

8 files changed

+121
-7
lines changed

8 files changed

+121
-7
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: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,7 @@ def abspath(path):
601601
return _abspath_fallback(path)
602602

603603
try:
604-
from nt import _getfinalpathname, readlink as _nt_readlink
604+
from nt import _findfirstfile, _getfinalpathname, readlink as _nt_readlink
605605
except ImportError:
606606
# realpath is a no-op on systems without _getfinalpathname support.
607607
realpath = abspath
@@ -688,10 +688,16 @@ def _getfinalpathname_nonstrict(path):
688688
except OSError:
689689
# If we fail to readlink(), let's keep traversing
690690
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
691+
if ex.winerror in (5, 32):
692+
# If we get ERROR_ACCESS_DENIED or ERROR_SHARING_VIOLATION,
693+
# try to get the real name of the file without accessing it.
694+
try:
695+
name = _findfirstfile(path)
696+
path, _ = split(path)
697+
except OSError:
698+
path, name = split(path)
699+
else:
700+
path, name = split(path)
695701
if path and not name:
696702
return path + tail
697703
tail = join(name, tail) if tail else name

Lib/test/test_ntpath.py

Lines changed: 31 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,36 @@ 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.abspath(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+
658+
self.assertPathEqual(test_file, ntpath.realpath(test_file_short))
659+
# Deny the right to [S]YNCHRONIZE on the file to
660+
# force GetFinalPathName to fail with ERROR_ACCESS_DENIED.
661+
DETACHED_PROCESS = 8
662+
subprocess.check_call(
663+
# bpo-30584: Use security identifier *S-1-5-32-545 instead
664+
# of localized "Users" to not depend on the locale.
665+
['icacls.exe', test_file, '/deny', '*S-1-5-32-545:(S)'],
666+
creationflags=DETACHED_PROCESS
667+
)
668+
669+
self.assertPathEqual(test_file, ntpath.realpath(test_file_short))
670+
640671
def test_expandvars(self):
641672
with os_helper.EnvironmentVarGuard() as env:
642673
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)