Fix #11939. Set st_dev attribute on Windows to simplify os.path.samefile.
By setting the st_dev attribute, we can then remove some Windows-specific code and move os.path.samefile/sameopenfile/samestat to Lib/genericpath.py so all platforms share the same implementation.
This commit is contained in:
parent
2bf61abe02
commit
490b32a397
|
@ -244,15 +244,14 @@ applications should use string objects to access all files.
|
|||
On Unix, this is determined by the device number and i-node number and raises an
|
||||
exception if a :func:`os.stat` call on either pathname fails.
|
||||
|
||||
On Windows, two files are the same if they resolve to the same final path
|
||||
name using the Windows API call GetFinalPathNameByHandle. This function
|
||||
raises an exception if handles cannot be obtained to either file.
|
||||
|
||||
Availability: Unix, Windows.
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
Added Windows support.
|
||||
|
||||
.. versionchanged:: 3.4
|
||||
Windows now uses the same implementation as all other platforms.
|
||||
|
||||
|
||||
.. function:: sameopenfile(fp1, fp2)
|
||||
|
||||
|
@ -271,7 +270,10 @@ applications should use string objects to access all files.
|
|||
:func:`stat`. This function implements the underlying comparison used by
|
||||
:func:`samefile` and :func:`sameopenfile`.
|
||||
|
||||
Availability: Unix.
|
||||
Availability: Unix, Windows.
|
||||
|
||||
.. versionchanged:: 3.4
|
||||
Added Windows support.
|
||||
|
||||
|
||||
.. function:: split(path)
|
||||
|
|
|
@ -7,7 +7,8 @@ import os
|
|||
import stat
|
||||
|
||||
__all__ = ['commonprefix', 'exists', 'getatime', 'getctime', 'getmtime',
|
||||
'getsize', 'isdir', 'isfile']
|
||||
'getsize', 'isdir', 'isfile', 'samefile', 'sameopenfile',
|
||||
'samestat']
|
||||
|
||||
|
||||
# Does a path exist?
|
||||
|
@ -75,6 +76,31 @@ def commonprefix(m):
|
|||
return s1[:i]
|
||||
return s1
|
||||
|
||||
# Are two stat buffers (obtained from stat, fstat or lstat)
|
||||
# describing the same file?
|
||||
def samestat(s1, s2):
|
||||
"""Test whether two stat buffers reference the same file"""
|
||||
return (s1.st_ino == s2.st_ino and
|
||||
s1.st_dev == s2.st_dev)
|
||||
|
||||
|
||||
# Are two filenames really pointing to the same file?
|
||||
def samefile(f1, f2):
|
||||
"""Test whether two pathnames reference the same actual file"""
|
||||
s1 = os.stat(f1)
|
||||
s2 = os.stat(f2)
|
||||
return samestat(s1, s2)
|
||||
|
||||
|
||||
# Are two open files really referencing the same file?
|
||||
# (Not necessarily the same file descriptor!)
|
||||
def sameopenfile(fp1, fp2):
|
||||
"""Test whether two open file objects reference the same file"""
|
||||
s1 = os.fstat(fp1)
|
||||
s2 = os.fstat(fp2)
|
||||
return samestat(s1, s2)
|
||||
|
||||
|
||||
# Split a path in root and extension.
|
||||
# The extension is everything starting at the last dot in the last
|
||||
# pathname component; the root is everything before that.
|
||||
|
|
|
@ -652,23 +652,6 @@ except (AttributeError, ImportError):
|
|||
def _getfinalpathname(f):
|
||||
return normcase(abspath(f))
|
||||
|
||||
def samefile(f1, f2):
|
||||
"Test whether two pathnames reference the same actual file"
|
||||
return _getfinalpathname(f1) == _getfinalpathname(f2)
|
||||
|
||||
|
||||
try:
|
||||
from nt import _getfileinformation
|
||||
except ImportError:
|
||||
# On other operating systems, just return the fd and see that
|
||||
# it compares equal in sameopenfile.
|
||||
def _getfileinformation(fd):
|
||||
return fd
|
||||
|
||||
def sameopenfile(f1, f2):
|
||||
"""Test whether two file objects reference the same file"""
|
||||
return _getfileinformation(f1) == _getfileinformation(f2)
|
||||
|
||||
|
||||
try:
|
||||
# The genericpath.isdir implementation uses os.stat and checks the mode
|
||||
|
|
|
@ -177,34 +177,6 @@ def lexists(path):
|
|||
return True
|
||||
|
||||
|
||||
# Are two filenames really pointing to the same file?
|
||||
|
||||
def samefile(f1, f2):
|
||||
"""Test whether two pathnames reference the same actual file"""
|
||||
s1 = os.stat(f1)
|
||||
s2 = os.stat(f2)
|
||||
return samestat(s1, s2)
|
||||
|
||||
|
||||
# Are two open files really referencing the same file?
|
||||
# (Not necessarily the same file descriptor!)
|
||||
|
||||
def sameopenfile(fp1, fp2):
|
||||
"""Test whether two open file objects reference the same file"""
|
||||
s1 = os.fstat(fp1)
|
||||
s2 = os.fstat(fp2)
|
||||
return samestat(s1, s2)
|
||||
|
||||
|
||||
# Are two stat buffers (obtained from stat, fstat or lstat)
|
||||
# describing the same file?
|
||||
|
||||
def samestat(s1, s2):
|
||||
"""Test whether two stat buffers reference the same file"""
|
||||
return s1.st_ino == s2.st_ino and \
|
||||
s1.st_dev == s2.st_dev
|
||||
|
||||
|
||||
# Is a path a mount point?
|
||||
# (Does this work for all UNIXes? Is it even guaranteed to work by Posix?)
|
||||
|
||||
|
|
|
@ -190,6 +190,74 @@ class GenericTest(unittest.TestCase):
|
|||
support.unlink(support.TESTFN)
|
||||
safe_rmdir(support.TESTFN)
|
||||
|
||||
@staticmethod
|
||||
def _create_file(filename):
|
||||
with open(filename, 'wb') as f:
|
||||
f.write(b'foo')
|
||||
|
||||
def test_samefile(self):
|
||||
try:
|
||||
test_fn = support.TESTFN + "1"
|
||||
self._create_file(test_fn)
|
||||
self.assertTrue(self.pathmodule.samefile(test_fn, test_fn))
|
||||
self.assertRaises(TypeError, self.pathmodule.samefile)
|
||||
finally:
|
||||
os.remove(test_fn)
|
||||
|
||||
@support.skip_unless_symlink
|
||||
def test_samefile_on_links(self):
|
||||
try:
|
||||
test_fn1 = support.TESTFN + "1"
|
||||
test_fn2 = support.TESTFN + "2"
|
||||
self._create_file(test_fn1)
|
||||
|
||||
os.symlink(test_fn1, test_fn2)
|
||||
self.assertTrue(self.pathmodule.samefile(test_fn1, test_fn2))
|
||||
os.remove(test_fn2)
|
||||
|
||||
self._create_file(test_fn2)
|
||||
self.assertFalse(self.pathmodule.samefile(test_fn1, test_fn2))
|
||||
finally:
|
||||
os.remove(test_fn1)
|
||||
os.remove(test_fn2)
|
||||
|
||||
def test_samestat(self):
|
||||
try:
|
||||
test_fn = support.TESTFN + "1"
|
||||
self._create_file(test_fn)
|
||||
test_fns = [test_fn]*2
|
||||
stats = map(os.stat, test_fns)
|
||||
self.assertTrue(self.pathmodule.samestat(*stats))
|
||||
finally:
|
||||
os.remove(test_fn)
|
||||
|
||||
@support.skip_unless_symlink
|
||||
def test_samestat_on_links(self):
|
||||
try:
|
||||
test_fn1 = support.TESTFN + "1"
|
||||
test_fn2 = support.TESTFN + "2"
|
||||
self._create_file(test_fn1)
|
||||
test_fns = (test_fn1, test_fn2)
|
||||
os.symlink(*test_fns)
|
||||
stats = map(os.stat, test_fns)
|
||||
self.assertTrue(self.pathmodule.samestat(*stats))
|
||||
os.remove(test_fn2)
|
||||
|
||||
self._create_file(test_fn2)
|
||||
stats = map(os.stat, test_fns)
|
||||
self.assertFalse(self.pathmodule.samestat(*stats))
|
||||
|
||||
self.assertRaises(TypeError, self.pathmodule.samestat)
|
||||
finally:
|
||||
os.remove(test_fn1)
|
||||
os.remove(test_fn2)
|
||||
|
||||
def test_sameopenfile(self):
|
||||
fname = support.TESTFN + "1"
|
||||
with open(fname, "wb") as a, open(fname, "wb") as b:
|
||||
self.assertTrue(self.pathmodule.sameopenfile(
|
||||
a.fileno(), b.fileno()))
|
||||
|
||||
|
||||
# Following TestCase is not supposed to be run from test_genericpath.
|
||||
# It is inherited by other test modules (macpath, ntpath, posixpath).
|
||||
|
|
|
@ -186,63 +186,6 @@ class PosixPathTest(unittest.TestCase):
|
|||
if not f.close():
|
||||
f.close()
|
||||
|
||||
@staticmethod
|
||||
def _create_file(filename):
|
||||
with open(filename, 'wb') as f:
|
||||
f.write(b'foo')
|
||||
|
||||
def test_samefile(self):
|
||||
test_fn = support.TESTFN + "1"
|
||||
self._create_file(test_fn)
|
||||
self.assertTrue(posixpath.samefile(test_fn, test_fn))
|
||||
self.assertRaises(TypeError, posixpath.samefile)
|
||||
|
||||
@unittest.skipIf(
|
||||
sys.platform.startswith('win'),
|
||||
"posixpath.samefile does not work on links in Windows")
|
||||
@unittest.skipUnless(hasattr(os, "symlink"),
|
||||
"Missing symlink implementation")
|
||||
def test_samefile_on_links(self):
|
||||
test_fn1 = support.TESTFN + "1"
|
||||
test_fn2 = support.TESTFN + "2"
|
||||
self._create_file(test_fn1)
|
||||
|
||||
os.symlink(test_fn1, test_fn2)
|
||||
self.assertTrue(posixpath.samefile(test_fn1, test_fn2))
|
||||
os.remove(test_fn2)
|
||||
|
||||
self._create_file(test_fn2)
|
||||
self.assertFalse(posixpath.samefile(test_fn1, test_fn2))
|
||||
|
||||
|
||||
def test_samestat(self):
|
||||
test_fn = support.TESTFN + "1"
|
||||
self._create_file(test_fn)
|
||||
test_fns = [test_fn]*2
|
||||
stats = map(os.stat, test_fns)
|
||||
self.assertTrue(posixpath.samestat(*stats))
|
||||
|
||||
@unittest.skipIf(
|
||||
sys.platform.startswith('win'),
|
||||
"posixpath.samestat does not work on links in Windows")
|
||||
@unittest.skipUnless(hasattr(os, "symlink"),
|
||||
"Missing symlink implementation")
|
||||
def test_samestat_on_links(self):
|
||||
test_fn1 = support.TESTFN + "1"
|
||||
test_fn2 = support.TESTFN + "2"
|
||||
self._create_file(test_fn1)
|
||||
test_fns = (test_fn1, test_fn2)
|
||||
os.symlink(*test_fns)
|
||||
stats = map(os.stat, test_fns)
|
||||
self.assertTrue(posixpath.samestat(*stats))
|
||||
os.remove(test_fn2)
|
||||
|
||||
self._create_file(test_fn2)
|
||||
stats = map(os.stat, test_fns)
|
||||
self.assertFalse(posixpath.samestat(*stats))
|
||||
|
||||
self.assertRaises(TypeError, posixpath.samestat)
|
||||
|
||||
def test_ismount(self):
|
||||
self.assertIs(posixpath.ismount("/"), True)
|
||||
with warnings.catch_warnings():
|
||||
|
@ -518,11 +461,6 @@ class PosixPathTest(unittest.TestCase):
|
|||
finally:
|
||||
os.getcwdb = real_getcwdb
|
||||
|
||||
def test_sameopenfile(self):
|
||||
fname = support.TESTFN + "1"
|
||||
with open(fname, "wb") as a, open(fname, "wb") as b:
|
||||
self.assertTrue(posixpath.sameopenfile(a.fileno(), b.fileno()))
|
||||
|
||||
|
||||
class PosixCommonTest(test_genericpath.CommonTest):
|
||||
pathmodule = posixpath
|
||||
|
|
|
@ -1249,6 +1249,8 @@ attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *info, ULONG reparse_tag, stru
|
|||
memset(result, 0, sizeof(*result));
|
||||
result->st_mode = attributes_to_mode(info->dwFileAttributes);
|
||||
result->st_size = (((__int64)info->nFileSizeHigh)<<32) + info->nFileSizeLow;
|
||||
result->st_dev = info->dwVolumeSerialNumber;
|
||||
result->st_rdev = result->st_dev;
|
||||
FILE_TIME_to_time_t_nsec(&info->ftCreationTime, &result->st_ctime, &result->st_ctime_nsec);
|
||||
FILE_TIME_to_time_t_nsec(&info->ftLastWriteTime, &result->st_mtime, &result->st_mtime_nsec);
|
||||
FILE_TIME_to_time_t_nsec(&info->ftLastAccessTime, &result->st_atime, &result->st_atime_nsec);
|
||||
|
@ -3503,31 +3505,6 @@ posix__getfinalpathname(PyObject *self, PyObject *args)
|
|||
|
||||
} /* end of posix__getfinalpathname */
|
||||
|
||||
static PyObject *
|
||||
posix__getfileinformation(PyObject *self, PyObject *args)
|
||||
{
|
||||
HANDLE hFile;
|
||||
BY_HANDLE_FILE_INFORMATION info;
|
||||
int fd;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "i:_getfileinformation", &fd))
|
||||
return NULL;
|
||||
|
||||
if (!_PyVerify_fd(fd))
|
||||
return posix_error();
|
||||
|
||||
hFile = (HANDLE)_get_osfhandle(fd);
|
||||
if (hFile == INVALID_HANDLE_VALUE)
|
||||
return posix_error();
|
||||
|
||||
if (!GetFileInformationByHandle(hFile, &info))
|
||||
return PyErr_SetFromWindowsErr(0);
|
||||
|
||||
return Py_BuildValue("iii", info.dwVolumeSerialNumber,
|
||||
info.nFileIndexHigh,
|
||||
info.nFileIndexLow);
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(posix__isdir__doc__,
|
||||
"Return true if the pathname refers to an existing directory.");
|
||||
|
||||
|
@ -10606,7 +10583,6 @@ static PyMethodDef posix_methods[] = {
|
|||
#ifdef MS_WINDOWS
|
||||
{"_getfullpathname", posix__getfullpathname, METH_VARARGS, NULL},
|
||||
{"_getfinalpathname", posix__getfinalpathname, METH_VARARGS, NULL},
|
||||
{"_getfileinformation", posix__getfileinformation, METH_VARARGS, NULL},
|
||||
{"_isdir", posix__isdir, METH_VARARGS, posix__isdir__doc__},
|
||||
{"_getdiskusage", win32__getdiskusage, METH_VARARGS, win32__getdiskusage__doc__},
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue