Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-41625: Expose the splice() system call in the os module #21947

Merged
merged 1 commit into from Nov 17, 2020
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

@@ -1419,6 +1419,38 @@ or `the MSDN <https://msdn.microsoft.com/en-us/library/z0kc8e3z.aspx>`_ on Windo
.. versionadded:: 3.3


.. function:: splice(src, dst, count, offset_src=None, offset_dst=None)

Transfer *count* bytes from file descriptor *src*, starting from offset
*offset_src*, to file descriptor *dst*, starting from offset *offset_dst*.
At least one of the file descriptors must refer to a pipe. If *offset_src*
is None, then *src* is read from the current position; respectively for
*offset_dst*. The offset associated to the file descriptor that refers to a
pipe must be ``None``. The files pointed by *src* and *dst* must reside in
the same filesystem, otherwise an :exc:`OSError` is raised with
:attr:`~OSError.errno` set to :data:`errno.EXDEV`.

This copy is done without the additional cost of transferring data
from the kernel to user space and then back into the kernel. Additionally,
some filesystems could implement extra optimizations. The copy is done as if
both files are opened as binary.

Upon successful completion, returns the number of bytes spliced to or from
the pipe. A return value of 0 means end of input. If *src* refers to a
pipe, then this means that there was no data to transfer, and it would not
make sense to block because there are no writers connected to the write end
of the pipe.

.. availability:: Linux kernel >= 2.6.17 or glibc >= 2.5

.. versionadded:: 3.10


.. data:: SPLICE_F_MOVE
SPLICE_F_NONBLOCK
SPLICE_F_MORE


.. function:: readv(fd, buffers)

Read from a file descriptor *fd* into a number of mutable :term:`bytes-like
@@ -233,6 +233,11 @@ Added a new function :func:`os.eventfd` and related helpers to wrap the
``eventfd2`` syscall on Linux.
(Contributed by Christian Heimes in :issue:`41001`.)

Added :func:`os.splice()` that allows to move data between two file
descriptors without copying between kernel address space and user
address space, where one of the file descriptors must refer to a
pipe. (Contributed by Pablo Galindo in :issue:`41625`.)

py_compile
----------

@@ -381,6 +381,123 @@ def test_copy_file_range_offset(self):
self.assertEqual(read[out_seek:],
data[in_skip:in_skip+i])

@unittest.skipUnless(hasattr(os, 'splice'), 'test needs os.splice()')
def test_splice_invalid_values(self):
with self.assertRaises(ValueError):
os.splice(0, 1, -10)

@unittest.skipUnless(hasattr(os, 'splice'), 'test needs os.splice()')
def test_splice(self):
TESTFN2 = os_helper.TESTFN + ".3"
data = b'0123456789'

create_file(os_helper.TESTFN, data)
self.addCleanup(os_helper.unlink, os_helper.TESTFN)

in_file = open(os_helper.TESTFN, 'rb')
self.addCleanup(in_file.close)
in_fd = in_file.fileno()

read_fd, write_fd = os.pipe()
self.addCleanup(lambda: os.close(read_fd))
self.addCleanup(lambda: os.close(write_fd))

try:
i = os.splice(in_fd, write_fd, 5)
except OSError as e:
# Handle the case in which Python was compiled
# in a system with the syscall but without support
# in the kernel.
if e.errno != errno.ENOSYS:
raise
self.skipTest(e)
else:
# The number of copied bytes can be less than
# the number of bytes originally requested.
self.assertIn(i, range(0, 6));

self.assertEqual(os.read(read_fd, 100), data[:i])

@unittest.skipUnless(hasattr(os, 'splice'), 'test needs os.splice()')
def test_splice_offset_in(self):
TESTFN4 = os_helper.TESTFN + ".4"
data = b'0123456789'
bytes_to_copy = 6
in_skip = 3

create_file(os_helper.TESTFN, data)
self.addCleanup(os_helper.unlink, os_helper.TESTFN)

in_file = open(os_helper.TESTFN, 'rb')
self.addCleanup(in_file.close)
in_fd = in_file.fileno()

read_fd, write_fd = os.pipe()
self.addCleanup(lambda: os.close(read_fd))
self.addCleanup(lambda: os.close(write_fd))

try:
i = os.splice(in_fd, write_fd, bytes_to_copy, offset_src=in_skip)
This conversation was marked as resolved by pablogsal

This comment has been minimized.

@ammaraskar

ammaraskar Aug 24, 2020
Member

Should there be an similar test for offset_dst?

except OSError as e:
# Handle the case in which Python was compiled
# in a system with the syscall but without support
# in the kernel.
if e.errno != errno.ENOSYS:
raise
self.skipTest(e)
else:
# The number of copied bytes can be less than
# the number of bytes originally requested.
self.assertIn(i, range(0, bytes_to_copy+1));

read = os.read(read_fd, 100)
# 012 are skipped (in_skip)
# 345678 are copied in the file (in_skip + bytes_to_copy)
self.assertEqual(read, data[in_skip:in_skip+i])

@unittest.skipUnless(hasattr(os, 'splice'), 'test needs os.splice()')
def test_splice_offset_out(self):
TESTFN4 = os_helper.TESTFN + ".4"
data = b'0123456789'
bytes_to_copy = 6
out_seek = 3

create_file(os_helper.TESTFN, data)
self.addCleanup(os_helper.unlink, os_helper.TESTFN)

read_fd, write_fd = os.pipe()
self.addCleanup(lambda: os.close(read_fd))
self.addCleanup(lambda: os.close(write_fd))
os.write(write_fd, data)

out_file = open(TESTFN4, 'w+b')
self.addCleanup(os_helper.unlink, TESTFN4)
self.addCleanup(out_file.close)
out_fd = out_file.fileno()

try:
i = os.splice(read_fd, out_fd, bytes_to_copy, offset_dst=out_seek)
except OSError as e:
# Handle the case in which Python was compiled
# in a system with the syscall but without support
# in the kernel.
if e.errno != errno.ENOSYS:
raise
self.skipTest(e)
else:
# The number of copied bytes can be less than
# the number of bytes originally requested.
self.assertIn(i, range(0, bytes_to_copy+1));

with open(TESTFN4, 'rb') as in_file:
read = in_file.read()
# seeked bytes (5) are zero'ed
self.assertEqual(read[:out_seek], b'\x00'*out_seek)
# 012 are skipped (in_skip)
# 345678 are copied in the file (in_skip + bytes_to_copy)
self.assertEqual(read[out_seek:], data[:i])


# Test attributes on return values from os.*stat* family.
class StatAttributeTests(unittest.TestCase):
def setUp(self):
@@ -0,0 +1,2 @@
Expose the :c:func:`splice` as :func:`os.splice` in the :mod:`os` module.
Patch by Pablo Galindo

Some generated files are not rendered by default. Learn more.

ProTip! Use n and p to navigate between commits in a pull request.