Kaydet (Commit) 490b32a3 authored tarafından Brian Curtin's avatar Brian Curtin

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.
üst 2bf61abe
...@@ -244,15 +244,14 @@ applications should use string objects to access all files. ...@@ -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 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. 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. Availability: Unix, Windows.
.. versionchanged:: 3.2 .. versionchanged:: 3.2
Added Windows support. Added Windows support.
.. versionchanged:: 3.4
Windows now uses the same implementation as all other platforms.
.. function:: sameopenfile(fp1, fp2) .. function:: sameopenfile(fp1, fp2)
...@@ -271,7 +270,10 @@ applications should use string objects to access all files. ...@@ -271,7 +270,10 @@ applications should use string objects to access all files.
:func:`stat`. This function implements the underlying comparison used by :func:`stat`. This function implements the underlying comparison used by
:func:`samefile` and :func:`sameopenfile`. :func:`samefile` and :func:`sameopenfile`.
Availability: Unix. Availability: Unix, Windows.
.. versionchanged:: 3.4
Added Windows support.
.. function:: split(path) .. function:: split(path)
......
...@@ -7,7 +7,8 @@ import os ...@@ -7,7 +7,8 @@ import os
import stat import stat
__all__ = ['commonprefix', 'exists', 'getatime', 'getctime', 'getmtime', __all__ = ['commonprefix', 'exists', 'getatime', 'getctime', 'getmtime',
'getsize', 'isdir', 'isfile'] 'getsize', 'isdir', 'isfile', 'samefile', 'sameopenfile',
'samestat']
# Does a path exist? # Does a path exist?
...@@ -75,6 +76,31 @@ def commonprefix(m): ...@@ -75,6 +76,31 @@ def commonprefix(m):
return s1[:i] return s1[:i]
return s1 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. # Split a path in root and extension.
# The extension is everything starting at the last dot in the last # The extension is everything starting at the last dot in the last
# pathname component; the root is everything before that. # pathname component; the root is everything before that.
......
...@@ -652,23 +652,6 @@ except (AttributeError, ImportError): ...@@ -652,23 +652,6 @@ except (AttributeError, ImportError):
def _getfinalpathname(f): def _getfinalpathname(f):
return normcase(abspath(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: try:
# The genericpath.isdir implementation uses os.stat and checks the mode # The genericpath.isdir implementation uses os.stat and checks the mode
......
...@@ -177,34 +177,6 @@ def lexists(path): ...@@ -177,34 +177,6 @@ def lexists(path):
return True 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? # Is a path a mount point?
# (Does this work for all UNIXes? Is it even guaranteed to work by Posix?) # (Does this work for all UNIXes? Is it even guaranteed to work by Posix?)
......
...@@ -190,6 +190,74 @@ class GenericTest(unittest.TestCase): ...@@ -190,6 +190,74 @@ class GenericTest(unittest.TestCase):
support.unlink(support.TESTFN) support.unlink(support.TESTFN)
safe_rmdir(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. # Following TestCase is not supposed to be run from test_genericpath.
# It is inherited by other test modules (macpath, ntpath, posixpath). # It is inherited by other test modules (macpath, ntpath, posixpath).
......
...@@ -186,63 +186,6 @@ class PosixPathTest(unittest.TestCase): ...@@ -186,63 +186,6 @@ class PosixPathTest(unittest.TestCase):
if not f.close(): if not f.close():
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): def test_ismount(self):
self.assertIs(posixpath.ismount("/"), True) self.assertIs(posixpath.ismount("/"), True)
with warnings.catch_warnings(): with warnings.catch_warnings():
...@@ -518,11 +461,6 @@ class PosixPathTest(unittest.TestCase): ...@@ -518,11 +461,6 @@ class PosixPathTest(unittest.TestCase):
finally: finally:
os.getcwdb = real_getcwdb 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): class PosixCommonTest(test_genericpath.CommonTest):
pathmodule = posixpath pathmodule = posixpath
......
...@@ -1249,6 +1249,8 @@ attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *info, ULONG reparse_tag, stru ...@@ -1249,6 +1249,8 @@ attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *info, ULONG reparse_tag, stru
memset(result, 0, sizeof(*result)); memset(result, 0, sizeof(*result));
result->st_mode = attributes_to_mode(info->dwFileAttributes); result->st_mode = attributes_to_mode(info->dwFileAttributes);
result->st_size = (((__int64)info->nFileSizeHigh)<<32) + info->nFileSizeLow; 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->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->ftLastWriteTime, &result->st_mtime, &result->st_mtime_nsec);
FILE_TIME_to_time_t_nsec(&info->ftLastAccessTime, &result->st_atime, &result->st_atime_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) ...@@ -3503,31 +3505,6 @@ posix__getfinalpathname(PyObject *self, PyObject *args)
} /* end of posix__getfinalpathname */ } /* 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__, PyDoc_STRVAR(posix__isdir__doc__,
"Return true if the pathname refers to an existing directory."); "Return true if the pathname refers to an existing directory.");
...@@ -10606,7 +10583,6 @@ static PyMethodDef posix_methods[] = { ...@@ -10606,7 +10583,6 @@ static PyMethodDef posix_methods[] = {
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
{"_getfullpathname", posix__getfullpathname, METH_VARARGS, NULL}, {"_getfullpathname", posix__getfullpathname, METH_VARARGS, NULL},
{"_getfinalpathname", posix__getfinalpathname, METH_VARARGS, NULL}, {"_getfinalpathname", posix__getfinalpathname, METH_VARARGS, NULL},
{"_getfileinformation", posix__getfileinformation, METH_VARARGS, NULL},
{"_isdir", posix__isdir, METH_VARARGS, posix__isdir__doc__}, {"_isdir", posix__isdir, METH_VARARGS, posix__isdir__doc__},
{"_getdiskusage", win32__getdiskusage, METH_VARARGS, win32__getdiskusage__doc__}, {"_getdiskusage", win32__getdiskusage, METH_VARARGS, win32__getdiskusage__doc__},
#endif #endif
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment