Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2b47f0a

Browse files
committed
Close #10142: Support for SEEK_HOLE/SEEK_DATA
1 parent 790a9b4 commit 2b47f0a

8 files changed

Lines changed: 60 additions & 12 deletions

File tree

Doc/library/io.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,11 @@ I/O Base Classes
291291
.. versionadded:: 3.1
292292
The ``SEEK_*`` constants.
293293

294+
.. versionadded:: 3.3
295+
Some operating systems could support additional values, like
296+
:data:`os.SEEK_HOLE` or :data:`os.SEEK_DATA`. The valid values
297+
for a file could depend on it being open in text or binary mode.
298+
294299
.. method:: seekable()
295300

296301
Return ``True`` if the stream supports random access. If ``False``,

Doc/library/os.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -992,6 +992,10 @@ as internal buffering of data.
992992
Parameters to the :func:`lseek` function. Their values are 0, 1, and 2,
993993
respectively. Availability: Windows, Unix.
994994

995+
.. versionadded:: 3.3
996+
Some operating systems could support additional values, like
997+
:data:`os.SEEK_HOLE` or :data:`os.SEEK_DATA`.
998+
995999

9961000
.. function:: mkdirat(dirfd, path, mode=0o777)
9971001

Lib/_pyio.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ def seek(self, pos, whence=0):
306306
* 0 -- start of stream (the default); offset should be zero or positive
307307
* 1 -- current stream position; offset may be negative
308308
* 2 -- end of stream; offset is usually negative
309+
Some operating systems / file systems could provide additional values.
309310
310311
Return an int indicating the new absolute position.
311312
"""
@@ -866,7 +867,7 @@ def seek(self, pos, whence=0):
866867
elif whence == 2:
867868
self._pos = max(0, len(self._buffer) + pos)
868869
else:
869-
raise ValueError("invalid whence value")
870+
raise ValueError("unsupported whence value")
870871
return self._pos
871872

872873
def tell(self):
@@ -1041,8 +1042,6 @@ def tell(self):
10411042
return _BufferedIOMixin.tell(self) - len(self._read_buf) + self._read_pos
10421043

10431044
def seek(self, pos, whence=0):
1044-
if not (0 <= whence <= 2):
1045-
raise ValueError("invalid whence value")
10461045
with self._read_lock:
10471046
if whence == 1:
10481047
pos -= len(self._read_buf) - self._read_pos
@@ -1138,8 +1137,6 @@ def tell(self):
11381137
return _BufferedIOMixin.tell(self) + len(self._write_buf)
11391138

11401139
def seek(self, pos, whence=0):
1141-
if not (0 <= whence <= 2):
1142-
raise ValueError("invalid whence")
11431140
with self._write_lock:
11441141
self._flush_unlocked()
11451142
return _BufferedIOMixin.seek(self, pos, whence)
@@ -1235,8 +1232,6 @@ def __init__(self, raw,
12351232
BufferedWriter.__init__(self, raw, buffer_size, max_buffer_size)
12361233

12371234
def seek(self, pos, whence=0):
1238-
if not (0 <= whence <= 2):
1239-
raise ValueError("invalid whence")
12401235
self.flush()
12411236
if self._read_buf:
12421237
# Undo read ahead.
@@ -1852,8 +1847,7 @@ def seek(self, cookie, whence=0):
18521847
self._decoder.reset()
18531848
return position
18541849
if whence != 0:
1855-
raise ValueError("invalid whence (%r, should be 0, 1 or 2)" %
1856-
(whence,))
1850+
raise ValueError("unsupported whence (%r)" % (whence,))
18571851
if cookie < 0:
18581852
raise ValueError("negative seek position %r" % (cookie,))
18591853
self.flush()

Lib/os.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ def _get_exports_list(module):
116116

117117
# Python uses fixed values for the SEEK_ constants; they are mapped
118118
# to native constants if necessary in posixmodule.c
119+
# Other possible SEEK values are directly imported from posixmodule.c
119120
SEEK_SET = 0
120121
SEEK_CUR = 1
121122
SEEK_END = 2

Lib/test/test_posix.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,26 @@ def test_rtld_constants(self):
10091009
posix.RTLD_GLOBAL
10101010
posix.RTLD_LOCAL
10111011

1012+
@unittest.skipUnless('PC_MIN_HOLE_SIZE' in os.pathconf_names,
1013+
"test needs an OS that reports file holes")
1014+
def test_fs_holes(self) :
1015+
# Even if the filesystem doesn't report holes,
1016+
# if the OS supports it the SEEK_* constants
1017+
# will be defined and will have a consistent
1018+
# behaviour:
1019+
# os.SEEK_DATA = current position
1020+
# os.SEEK_HOLE = end of file position
1021+
with open(support.TESTFN, 'r+b') as fp :
1022+
fp.write(b"hello")
1023+
fp.flush()
1024+
size = fp.tell()
1025+
fno = fp.fileno()
1026+
for i in range(size) :
1027+
self.assertEqual(i, os.lseek(fno, i, os.SEEK_DATA))
1028+
self.assertLessEqual(size, os.lseek(fno, i, os.SEEK_HOLE))
1029+
self.assertRaises(OSError, os.lseek, fno, size, os.SEEK_DATA)
1030+
self.assertRaises(OSError, os.lseek, fno, size, os.SEEK_HOLE)
1031+
10121032
class PosixGroupsTester(unittest.TestCase):
10131033

10141034
def setUp(self):

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,8 @@ Extension Modules
423423
- Issue #14259: The finditer() method of re objects did not take any
424424
keyword arguments, contrary to the documentation.
425425

426+
- Issue #10142: Support for SEEK_HOLE/SEEK_DATA (for example, under ZFS).
427+
426428
Tests
427429
-----
428430

Modules/_io/bufferedio.c

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,9 +1157,20 @@ buffered_seek(buffered *self, PyObject *args)
11571157
if (!PyArg_ParseTuple(args, "O|i:seek", &targetobj, &whence)) {
11581158
return NULL;
11591159
}
1160-
if (whence < 0 || whence > 2) {
1160+
1161+
/* Do some error checking instead of trusting OS 'seek()'
1162+
** error detection, just in case.
1163+
*/
1164+
if ((whence < 0 || whence >2)
1165+
#ifdef SEEK_HOLE
1166+
&& (whence != SEEK_HOLE)
1167+
#endif
1168+
#ifdef SEEK_DATA
1169+
&& (whence != SEEK_DATA)
1170+
#endif
1171+
) {
11611172
PyErr_Format(PyExc_ValueError,
1162-
"whence must be between 0 and 2, not %d", whence);
1173+
"whence value %d unsupported", whence);
11631174
return NULL;
11641175
}
11651176

@@ -1172,7 +1183,11 @@ buffered_seek(buffered *self, PyObject *args)
11721183
if (target == -1 && PyErr_Occurred())
11731184
return NULL;
11741185

1175-
if (whence != 2 && self->readable) {
1186+
/* SEEK_SET and SEEK_CUR are special because we could seek inside the
1187+
buffer. Other whence values must be managed without this optimization.
1188+
Some Operating Systems can provide additional values, like
1189+
SEEK_HOLE/SEEK_DATA. */
1190+
if (((whence == 0) || (whence == 1)) && self->readable) {
11761191
Py_off_t current, avail;
11771192
/* Check if seeking leaves us inside the current buffer,
11781193
so as to return quickly if possible. Also, we needn't take the

Modules/posixmodule.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11227,6 +11227,13 @@ all_ins(PyObject *d)
1122711227
#endif
1122811228

1122911229

11230+
#ifdef SEEK_HOLE
11231+
if (ins(d, "SEEK_HOLE", (long)SEEK_HOLE)) return -1;
11232+
#endif
11233+
#ifdef SEEK_DATA
11234+
if (ins(d, "SEEK_DATA", (long)SEEK_DATA)) return -1;
11235+
#endif
11236+
1123011237
/* MS Windows */
1123111238
#ifdef O_NOINHERIT
1123211239
/* Don't inherit in child processes. */

0 commit comments

Comments
 (0)