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:
Brian Curtin 2012-12-26 07:03:03 -06:00
parent 2bf61abe02
commit 490b32a397
7 changed files with 104 additions and 139 deletions

View File

@ -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)

View File

@ -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.

View File

@ -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

View File

@ -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?)

View File

@ -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).

View File

@ -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

View File

@ -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