Commit 2b47f0a2 authored by Jesus Cea's avatar Jesus Cea

Close #10142: Support for SEEK_HOLE/SEEK_DATA

parent 790a9b4c
...@@ -291,6 +291,11 @@ I/O Base Classes ...@@ -291,6 +291,11 @@ I/O Base Classes
.. versionadded:: 3.1 .. versionadded:: 3.1
The ``SEEK_*`` constants. The ``SEEK_*`` constants.
.. versionadded:: 3.3
Some operating systems could support additional values, like
:data:`os.SEEK_HOLE` or :data:`os.SEEK_DATA`. The valid values
for a file could depend on it being open in text or binary mode.
.. method:: seekable() .. method:: seekable()
Return ``True`` if the stream supports random access. If ``False``, Return ``True`` if the stream supports random access. If ``False``,
......
...@@ -992,6 +992,10 @@ as internal buffering of data. ...@@ -992,6 +992,10 @@ as internal buffering of data.
Parameters to the :func:`lseek` function. Their values are 0, 1, and 2, Parameters to the :func:`lseek` function. Their values are 0, 1, and 2,
respectively. Availability: Windows, Unix. respectively. Availability: Windows, Unix.
.. versionadded:: 3.3
Some operating systems could support additional values, like
:data:`os.SEEK_HOLE` or :data:`os.SEEK_DATA`.
.. function:: mkdirat(dirfd, path, mode=0o777) .. function:: mkdirat(dirfd, path, mode=0o777)
......
...@@ -306,6 +306,7 @@ class IOBase(metaclass=abc.ABCMeta): ...@@ -306,6 +306,7 @@ class IOBase(metaclass=abc.ABCMeta):
* 0 -- start of stream (the default); offset should be zero or positive * 0 -- start of stream (the default); offset should be zero or positive
* 1 -- current stream position; offset may be negative * 1 -- current stream position; offset may be negative
* 2 -- end of stream; offset is usually negative * 2 -- end of stream; offset is usually negative
Some operating systems / file systems could provide additional values.
Return an int indicating the new absolute position. Return an int indicating the new absolute position.
""" """
...@@ -866,7 +867,7 @@ class BytesIO(BufferedIOBase): ...@@ -866,7 +867,7 @@ class BytesIO(BufferedIOBase):
elif whence == 2: elif whence == 2:
self._pos = max(0, len(self._buffer) + pos) self._pos = max(0, len(self._buffer) + pos)
else: else:
raise ValueError("invalid whence value") raise ValueError("unsupported whence value")
return self._pos return self._pos
def tell(self): def tell(self):
...@@ -1041,8 +1042,6 @@ class BufferedReader(_BufferedIOMixin): ...@@ -1041,8 +1042,6 @@ class BufferedReader(_BufferedIOMixin):
return _BufferedIOMixin.tell(self) - len(self._read_buf) + self._read_pos return _BufferedIOMixin.tell(self) - len(self._read_buf) + self._read_pos
def seek(self, pos, whence=0): def seek(self, pos, whence=0):
if not (0 <= whence <= 2):
raise ValueError("invalid whence value")
with self._read_lock: with self._read_lock:
if whence == 1: if whence == 1:
pos -= len(self._read_buf) - self._read_pos pos -= len(self._read_buf) - self._read_pos
...@@ -1138,8 +1137,6 @@ class BufferedWriter(_BufferedIOMixin): ...@@ -1138,8 +1137,6 @@ class BufferedWriter(_BufferedIOMixin):
return _BufferedIOMixin.tell(self) + len(self._write_buf) return _BufferedIOMixin.tell(self) + len(self._write_buf)
def seek(self, pos, whence=0): def seek(self, pos, whence=0):
if not (0 <= whence <= 2):
raise ValueError("invalid whence")
with self._write_lock: with self._write_lock:
self._flush_unlocked() self._flush_unlocked()
return _BufferedIOMixin.seek(self, pos, whence) return _BufferedIOMixin.seek(self, pos, whence)
...@@ -1235,8 +1232,6 @@ class BufferedRandom(BufferedWriter, BufferedReader): ...@@ -1235,8 +1232,6 @@ class BufferedRandom(BufferedWriter, BufferedReader):
BufferedWriter.__init__(self, raw, buffer_size, max_buffer_size) BufferedWriter.__init__(self, raw, buffer_size, max_buffer_size)
def seek(self, pos, whence=0): def seek(self, pos, whence=0):
if not (0 <= whence <= 2):
raise ValueError("invalid whence")
self.flush() self.flush()
if self._read_buf: if self._read_buf:
# Undo read ahead. # Undo read ahead.
...@@ -1852,8 +1847,7 @@ class TextIOWrapper(TextIOBase): ...@@ -1852,8 +1847,7 @@ class TextIOWrapper(TextIOBase):
self._decoder.reset() self._decoder.reset()
return position return position
if whence != 0: if whence != 0:
raise ValueError("invalid whence (%r, should be 0, 1 or 2)" % raise ValueError("unsupported whence (%r)" % (whence,))
(whence,))
if cookie < 0: if cookie < 0:
raise ValueError("negative seek position %r" % (cookie,)) raise ValueError("negative seek position %r" % (cookie,))
self.flush() self.flush()
......
...@@ -116,6 +116,7 @@ del _names ...@@ -116,6 +116,7 @@ del _names
# Python uses fixed values for the SEEK_ constants; they are mapped # Python uses fixed values for the SEEK_ constants; they are mapped
# to native constants if necessary in posixmodule.c # to native constants if necessary in posixmodule.c
# Other possible SEEK values are directly imported from posixmodule.c
SEEK_SET = 0 SEEK_SET = 0
SEEK_CUR = 1 SEEK_CUR = 1
SEEK_END = 2 SEEK_END = 2
......
...@@ -1009,6 +1009,26 @@ class PosixTester(unittest.TestCase): ...@@ -1009,6 +1009,26 @@ class PosixTester(unittest.TestCase):
posix.RTLD_GLOBAL posix.RTLD_GLOBAL
posix.RTLD_LOCAL posix.RTLD_LOCAL
@unittest.skipUnless('PC_MIN_HOLE_SIZE' in os.pathconf_names,
"test needs an OS that reports file holes")
def test_fs_holes(self) :
# Even if the filesystem doesn't report holes,
# if the OS supports it the SEEK_* constants
# will be defined and will have a consistent
# behaviour:
# os.SEEK_DATA = current position
# os.SEEK_HOLE = end of file position
with open(support.TESTFN, 'r+b') as fp :
fp.write(b"hello")
fp.flush()
size = fp.tell()
fno = fp.fileno()
for i in range(size) :
self.assertEqual(i, os.lseek(fno, i, os.SEEK_DATA))
self.assertLessEqual(size, os.lseek(fno, i, os.SEEK_HOLE))
self.assertRaises(OSError, os.lseek, fno, size, os.SEEK_DATA)
self.assertRaises(OSError, os.lseek, fno, size, os.SEEK_HOLE)
class PosixGroupsTester(unittest.TestCase): class PosixGroupsTester(unittest.TestCase):
def setUp(self): def setUp(self):
......
...@@ -423,6 +423,8 @@ Extension Modules ...@@ -423,6 +423,8 @@ Extension Modules
- Issue #14259: The finditer() method of re objects did not take any - Issue #14259: The finditer() method of re objects did not take any
keyword arguments, contrary to the documentation. keyword arguments, contrary to the documentation.
- Issue #10142: Support for SEEK_HOLE/SEEK_DATA (for example, under ZFS).
Tests Tests
----- -----
......
...@@ -1157,9 +1157,20 @@ buffered_seek(buffered *self, PyObject *args) ...@@ -1157,9 +1157,20 @@ buffered_seek(buffered *self, PyObject *args)
if (!PyArg_ParseTuple(args, "O|i:seek", &targetobj, &whence)) { if (!PyArg_ParseTuple(args, "O|i:seek", &targetobj, &whence)) {
return NULL; return NULL;
} }
if (whence < 0 || whence > 2) {
/* Do some error checking instead of trusting OS 'seek()'
** error detection, just in case.
*/
if ((whence < 0 || whence >2)
#ifdef SEEK_HOLE
&& (whence != SEEK_HOLE)
#endif
#ifdef SEEK_DATA
&& (whence != SEEK_DATA)
#endif
) {
PyErr_Format(PyExc_ValueError, PyErr_Format(PyExc_ValueError,
"whence must be between 0 and 2, not %d", whence); "whence value %d unsupported", whence);
return NULL; return NULL;
} }
...@@ -1172,7 +1183,11 @@ buffered_seek(buffered *self, PyObject *args) ...@@ -1172,7 +1183,11 @@ buffered_seek(buffered *self, PyObject *args)
if (target == -1 && PyErr_Occurred()) if (target == -1 && PyErr_Occurred())
return NULL; return NULL;
if (whence != 2 && self->readable) { /* SEEK_SET and SEEK_CUR are special because we could seek inside the
buffer. Other whence values must be managed without this optimization.
Some Operating Systems can provide additional values, like
SEEK_HOLE/SEEK_DATA. */
if (((whence == 0) || (whence == 1)) && self->readable) {
Py_off_t current, avail; Py_off_t current, avail;
/* Check if seeking leaves us inside the current buffer, /* Check if seeking leaves us inside the current buffer,
so as to return quickly if possible. Also, we needn't take the so as to return quickly if possible. Also, we needn't take the
......
...@@ -11227,6 +11227,13 @@ all_ins(PyObject *d) ...@@ -11227,6 +11227,13 @@ all_ins(PyObject *d)
#endif #endif
#ifdef SEEK_HOLE
if (ins(d, "SEEK_HOLE", (long)SEEK_HOLE)) return -1;
#endif
#ifdef SEEK_DATA
if (ins(d, "SEEK_DATA", (long)SEEK_DATA)) return -1;
#endif
/* MS Windows */ /* MS Windows */
#ifdef O_NOINHERIT #ifdef O_NOINHERIT
/* Don't inherit in child processes. */ /* Don't inherit in child processes. */
......
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