summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
Diffstat (limited to 'Lib')
-rw-r--r--Lib/ntpath.py16
-rw-r--r--Lib/test/test_ntpath.py43
2 files changed, 53 insertions, 6 deletions
diff --git a/Lib/ntpath.py b/Lib/ntpath.py
index df3402d..3061a4a 100644
--- a/Lib/ntpath.py
+++ b/Lib/ntpath.py
@@ -23,7 +23,6 @@ import stat
import genericpath
from genericpath import *
-
__all__ = ["normcase","isabs","join","splitdrive","splitroot","split","splitext",
"basename","dirname","commonprefix","getsize","getmtime",
"getatime","getctime", "islink","exists","lexists","isdir","isfile",
@@ -601,7 +600,7 @@ else: # use native Windows method on Windows
return _abspath_fallback(path)
try:
- from nt import _getfinalpathname, readlink as _nt_readlink
+ from nt import _findfirstfile, _getfinalpathname, readlink as _nt_readlink
except ImportError:
# realpath is a no-op on systems without _getfinalpathname support.
realpath = abspath
@@ -688,10 +687,15 @@ else:
except OSError:
# If we fail to readlink(), let's keep traversing
pass
- path, name = split(path)
- # TODO (bpo-38186): Request the real file name from the directory
- # entry using FindFirstFileW. For now, we will return the path
- # as best we have it
+ # If we get these errors, try to get the real name of the file without accessing it.
+ if ex.winerror in (1, 5, 32, 50, 87, 1920, 1921):
+ try:
+ name = _findfirstfile(path)
+ path, _ = split(path)
+ except OSError:
+ path, name = split(path)
+ else:
+ path, name = split(path)
if path and not name:
return path + tail
tail = join(name, tail) if tail else name
diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py
index d91dcdf..3e710d1 100644
--- a/Lib/test/test_ntpath.py
+++ b/Lib/test/test_ntpath.py
@@ -2,6 +2,7 @@ import inspect
import ntpath
import os
import string
+import subprocess
import sys
import unittest
import warnings
@@ -637,6 +638,48 @@ class TestNtpath(NtpathTestCase):
with os_helper.change_cwd(test_dir_short):
self.assertPathEqual(test_file_long, ntpath.realpath("file.txt"))
+ @unittest.skipUnless(HAVE_GETFINALPATHNAME, 'need _getfinalpathname')
+ def test_realpath_permission(self):
+ # Test whether python can resolve the real filename of a
+ # shortened file name even if it does not have permission to access it.
+ ABSTFN = ntpath.realpath(os_helper.TESTFN)
+
+ os_helper.unlink(ABSTFN)
+ os_helper.rmtree(ABSTFN)
+ os.mkdir(ABSTFN)
+ self.addCleanup(os_helper.rmtree, ABSTFN)
+
+ test_file = ntpath.join(ABSTFN, "LongFileName123.txt")
+ test_file_short = ntpath.join(ABSTFN, "LONGFI~1.TXT")
+
+ with open(test_file, "wb") as f:
+ f.write(b"content")
+ # Automatic generation of short names may be disabled on
+ # NTFS volumes for the sake of performance.
+ # They're not supported at all on ReFS and exFAT.
+ subprocess.run(
+ # Try to set the short name manually.
+ ['fsutil.exe', 'file', 'setShortName', test_file, 'LONGFI~1.TXT'],
+ creationflags=subprocess.DETACHED_PROCESS
+ )
+
+ try:
+ self.assertPathEqual(test_file, ntpath.realpath(test_file_short))
+ except AssertionError:
+ raise unittest.SkipTest('the filesystem seems to lack support for short filenames')
+
+ # Deny the right to [S]YNCHRONIZE on the file to
+ # force nt._getfinalpathname to fail with ERROR_ACCESS_DENIED.
+ p = subprocess.run(
+ ['icacls.exe', test_file, '/deny', '*S-1-5-32-545:(S)'],
+ creationflags=subprocess.DETACHED_PROCESS
+ )
+
+ if p.returncode:
+ raise unittest.SkipTest('failed to deny access to the test file')
+
+ self.assertPathEqual(test_file, ntpath.realpath(test_file_short))
+
def test_expandvars(self):
with os_helper.EnvironmentVarGuard() as env:
env.clear()