Skip to content

bpo-31368: Expose preadv (preadv2) and pwritev (pwritev2) in the os module #5239

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

Merged
merged 14 commits into from
Jan 27, 2018
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions Doc/library/os.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1101,6 +1101,49 @@ or `the MSDN <https://msdn.microsoft.com/en-us/library/z0kc8e3z.aspx>`_ on Windo
.. versionadded:: 3.3


.. function:: pwritev(fd, buffers, offset, flags=0)

Combines the functionality of :func:`os.writev` and :func:`os.pwrite`. It
writes the contents of *buffers* to file descriptor *fd* at offset *offset*.
*buffers* must be a sequence of :term:`bytes-like objects <bytes-like object>`.
Buffers are processed in array order. Entire contents of first buffer is written
before proceeding to second, and so on. The operating system may set a limit
(sysconf() value SC_IOV_MAX) on the number of buffers that can be used.
:func:`~os.pwritev` writes the contents of each object to the file descriptor
and returns the total number of bytes written.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a single empty line is enough.


The *flags* argument contains a bitwise OR of zero or more of the following
flags:

- RWF_DSYNC
- RWF_SYNC

:func:`~os.pwritev` will call *pwritev2* system call if available, which modifies
the behavior on a per-call basis based on the value of the *flags* argument. The
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that ", which modifies the behavior on a per-call basis based on the value of the flags argument." is needed. Nobody expects a flag passed to a function to have an effect on following calls. And the usage of pwrite2() doesn't mean that the caller must pass a non-zero flags. This sentence is confusing.

:func:`~os.pwritev2` function is required to pass flags.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, there is no :func:~os.pwritev2 function, stop creating links to it.

I suggested "The C function :func:pwritev2", or you can just write pwrite2().


The :func:`~os.pwritev2` function is required to pass flags.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: I would move this sentence before describing on which Linux platforms version it is available.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto, don't write ":func:~os.pwritev2 function" but "C :c:func:pwritev2 function" or just pwrite2().

Note the "c" prefix before "func".


*pwritev2* and *flags* different than zero are available since Linux version
4.7.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"The :func:~os.pwritev2 function is required to pass flags. pwritev2 and flags different than zero are available since Linux version 4.7."

I'm not sure that the Python has to mention pwritev2(). It's confusing and useless. I don't think that the documentation of other os function lists each internal called C functions.

I suggest something simpler like: "Using non-zero flags requires Linux 4.7 or newer."


Availability: Linux (version 2.6.30), FreeBSD 6.0 and newer,
OpenBSD (version 2.7 and newer).

.. versionadded:: 3.7


.. data:: RWF_DSYNC
RWF_SYNC

The above constants are available on Linux Kernel 4.7 (or newer).

Availability: Linux.

.. versionadded:: 3.7


.. function:: read(fd, n)

Read at most *n* bytes from file descriptor *fd*. Return a bytestring containing the
Expand Down Expand Up @@ -1195,6 +1238,47 @@ or `the MSDN <https://msdn.microsoft.com/en-us/library/z0kc8e3z.aspx>`_ on Windo
.. versionadded:: 3.3


.. function:: preadv(fd, buffers, offset, flags=0)

Combines the functionality of :func:`os.readv` and :func:`os.pread`. It
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed you didn't duplicate the definition of fd and buffers from readv. I think most of docs explain all the parms, even if they are duplicated from another section.

Copy link
Member Author

@pablogsal pablogsal Jan 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I has hessitant to duplicate because readv is the function described inmediately before this one but I suppose that consistency makes sense. Thanks.

Corrected in c7b7285

reads from a file descriptor *fd* into a number of mutable :term:`bytes-like
objects <bytes-like object>` *buffers*. As :func:`os.readv`, it will transfer
data into each buffer until it is full and then move on to the next buffer in
the sequence to hold the rest of the data. Its fourth argument, *offset*,
specifies the file offset at which the input operation is to be performed.
:func:`~os.preadv` return the total number of bytes read (which can be less than
the total capacity of all the objects).

The flags argument contains a bitwise OR of zero or more of the following
flags:

- RWF_HIPRI
- RWF_NOWAIT
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: please align "-" on the previous paragraph (fix the indentation).


:func:`~os.preadv` will call *preadv2* system call if available, which modifies
the behavior on a per-call basis based on the value of the *flags* argument. The
:c:func:`preadv2` function is required to pass flags.

*preadv2* and flags different than zero are available since Linux version
4.6 and newer.

Availability: Linux (version 2.6.30), FreeBSD 6.0 and newer,
OpenBSD (version 2.7 and newer).

.. versionadded:: 3.7


.. data:: RWF_HIPRI
RWF_NOWAIT
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hum, I suggest to have one ".. data" entry per constant and copy the documentation from the manual page. Extract of Fedora 27 manual pages:

       RWF_DSYNC (since Linux 4.7)
              Provide  a  per-write  equivalent  of  the O_DSYNC open(2) flag.
              This flag is meaningful only  for  pwritev2(),  and  its  effect
              applies only to the data range written by the system call.

       RWF_HIPRI (since Linux 4.6)
              High priority read/write.  Allows block-based filesystems to use
              polling of the device, which provides lower latency, but may use
              additional  resources.   (Currently, this feature is usable only
              on a file descriptor opened using the O_DIRECT flag.)

       RWF_SYNC (since Linux 4.7)
              Provide a per-write equivalent of the O_SYNC open(2) flag.  This
              flag  is  meaningful only for pwritev2(), and its effect applies
              only to the data range written by the system call.

It would become simpler to describe the availability.


The above constants are available on Linux Kernel 4.11 and 4.6 (or newer)
respectively.

Availability: Linux.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to remove the previous sentence and just write: "Availability: Linux 4.11 and newer."

As done for other functions documentation like getrandom().
https://docs.python.org/dev/library/os.html#os.getrandom


.. versionadded:: 3.7


.. function:: tcgetpgrp(fd)

Return the process group associated with the terminal given by *fd* (an open
Expand Down
53 changes: 53 additions & 0 deletions Lib/test/test_posix.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,28 @@ def test_pread(self):
finally:
os.close(fd)

@unittest.skipUnless(hasattr(posix, 'preadv'), "test needs posix.preadv()")
def test_preadv(self):
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
try:
os.write(fd, b'test1tt2t3t5t6t6t8')
buf = [bytearray(i) for i in [5, 3, 2]]
self.assertEqual(posix.preadv(fd, buf, 3), 10)
self.assertEqual([b't1tt2', b't3t', b'5t'], list(buf))
finally:
os.close(fd)

@unittest.skipUnless(hasattr(posix, 'RWF_HIPRI'), "test needs posix.RWF_HIPRI")
def test_preadv_flags(self):
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
try:
os.write(fd, b'test1tt2t3t5t6t6t8')
buf = [bytearray(i) for i in [5, 3, 2]]
self.assertEqual(posix.preadv(fd, buf, 3, os.RWF_HIPRI), 10)
self.assertEqual([b't1tt2', b't3t', b'5t'], list(buf))
finally:
os.close(fd)

@unittest.skipUnless(hasattr(posix, 'pwrite'), "test needs posix.pwrite()")
def test_pwrite(self):
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
Expand All @@ -283,6 +305,37 @@ def test_pwrite(self):
finally:
os.close(fd)

@unittest.skipUnless(hasattr(posix, 'pwritev'), "test needs posix.pwritev()")
def test_pwritev(self):
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
try:
os.write(fd,b"xx")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PEP 8: add a space after ","

os.lseek(fd, 0, os.SEEK_SET)
n = os.pwritev(fd, [b'test1', b'tt2', b't3'], 2)
self.assertEqual(n, 10)

os.lseek(fd, 0, os.SEEK_SET)
self.assertEqual(b'xxtest1tt2', posix.read(fd, 10))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest posix.read(fd, 100) to make sure that no extra data is written. Same comment for the other test method below.


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: IMHO this empty line is useless

finally:
os.close(fd)


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PEP 8: only one empty line between methods of class, please. (remove this empty line)

@unittest.skipUnless(hasattr(posix, 'os.RWF_SYNC'), "test needs os.RWF_SYNC")
def test_pwritev_flags(self):
fd = os.open(support.TESTFN, os.O_RDWR | os.O_CREAT)
try:
os.write(fd,b"xx")
os.lseek(fd, 0, os.SEEK_SET)
n = os.pwritev(fd, [b'test1', b'tt2', b't3'], 2, os.RWF_SYNC)
self.assertEqual(n, 10)

os.lseek(fd, 0, os.SEEK_SET)
self.assertEqual(b'xxtest1tt2', posix.read(fd, 10))
finally:
os.close(fd)


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto: remove this empty line

@unittest.skipUnless(hasattr(posix, 'posix_fallocate'),
"test needs posix.posix_fallocate()")
def test_posix_fallocate(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Expose preadv (preadv2) and pwritev (pwritev2) system calls in the os module.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest: Add os.preadv() and os.pwritev() functions.

Again, I don't think that it's needed to mention the underlying C functions.

Patch by Pablo Galindo
99 changes: 98 additions & 1 deletion Modules/clinic/posixmodule.c.h
Original file line number Diff line number Diff line change
Expand Up @@ -3705,6 +3705,50 @@ os_pread(PyObject *module, PyObject *const *args, Py_ssize_t nargs)

#endif /* defined(HAVE_PREAD) */

#if (defined(HAVE_PREADV) || defined (HAVE_PREADV2))

PyDoc_STRVAR(os_preadv__doc__,
"preadv($module, fd, buffers, offset, flags=0, /)\n"
"--\n"
"\n"
"Read a number of bytes from a file descriptor starting at a particular offset.\n"
"\n"
"Read length bytes from file descriptor fd, starting at offset bytes from\n"
"the beginning of the file. The file offset remains unchanged.");

#define OS_PREADV_METHODDEF \
{"preadv", (PyCFunction)os_preadv, METH_FASTCALL, os_preadv__doc__},

static Py_ssize_t
os_preadv_impl(PyObject *module, int fd, PyObject *buffers, Py_off_t offset,
int flags);

static PyObject *
os_preadv(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
{
PyObject *return_value = NULL;
int fd;
PyObject *buffers;
Py_off_t offset;
int flags = 0;
Py_ssize_t _return_value;

if (!_PyArg_ParseStack(args, nargs, "iOO&|i:preadv",
&fd, &buffers, Py_off_t_converter, &offset, &flags)) {
goto exit;
}
_return_value = os_preadv_impl(module, fd, buffers, offset, flags);
if ((_return_value == -1) && PyErr_Occurred()) {
goto exit;
}
return_value = PyLong_FromSsize_t(_return_value);

exit:
return return_value;
}

#endif /* (defined(HAVE_PREADV) || defined (HAVE_PREADV2)) */

PyDoc_STRVAR(os_write__doc__,
"write($module, fd, data, /)\n"
"--\n"
Expand Down Expand Up @@ -3963,6 +4007,51 @@ os_pwrite(PyObject *module, PyObject *const *args, Py_ssize_t nargs)

#endif /* defined(HAVE_PWRITE) */

#if (defined(HAVE_PWRITEV) || defined (HAVE_PWRITEV2))

PyDoc_STRVAR(os_pwritev__doc__,
"pwritev($module, fd, buffers, offset, flags=0, /)\n"
"--\n"
"\n"
"Write bytes to a file descriptor starting at a particular offset.\n"
"\n"
"Write buffer to fd, starting at offset bytes from the beginning of\n"
"the file. Returns the number of bytes writte. Does not change the\n"
"current file offset.");

#define OS_PWRITEV_METHODDEF \
{"pwritev", (PyCFunction)os_pwritev, METH_FASTCALL, os_pwritev__doc__},

static Py_ssize_t
os_pwritev_impl(PyObject *module, int fd, PyObject *buffers, Py_off_t offset,
int flags);

static PyObject *
os_pwritev(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
{
PyObject *return_value = NULL;
int fd;
PyObject *buffers;
Py_off_t offset;
int flags = 0;
Py_ssize_t _return_value;

if (!_PyArg_ParseStack(args, nargs, "iOO&|i:pwritev",
&fd, &buffers, Py_off_t_converter, &offset, &flags)) {
goto exit;
}
_return_value = os_pwritev_impl(module, fd, buffers, offset, flags);
if ((_return_value == -1) && PyErr_Occurred()) {
goto exit;
}
return_value = PyLong_FromSsize_t(_return_value);

exit:
return return_value;
}

#endif /* (defined(HAVE_PWRITEV) || defined (HAVE_PWRITEV2)) */

#if defined(HAVE_MKFIFO)

PyDoc_STRVAR(os_mkfifo__doc__,
Expand Down Expand Up @@ -6239,6 +6328,10 @@ os_getrandom(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
#define OS_PREAD_METHODDEF
#endif /* !defined(OS_PREAD_METHODDEF) */

#ifndef OS_PREADV_METHODDEF
#define OS_PREADV_METHODDEF
#endif /* !defined(OS_PREADV_METHODDEF) */

#ifndef OS_PIPE_METHODDEF
#define OS_PIPE_METHODDEF
#endif /* !defined(OS_PIPE_METHODDEF) */
Expand All @@ -6255,6 +6348,10 @@ os_getrandom(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
#define OS_PWRITE_METHODDEF
#endif /* !defined(OS_PWRITE_METHODDEF) */

#ifndef OS_PWRITEV_METHODDEF
#define OS_PWRITEV_METHODDEF
#endif /* !defined(OS_PWRITEV_METHODDEF) */

#ifndef OS_MKFIFO_METHODDEF
#define OS_MKFIFO_METHODDEF
#endif /* !defined(OS_MKFIFO_METHODDEF) */
Expand Down Expand Up @@ -6410,4 +6507,4 @@ os_getrandom(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
#ifndef OS_GETRANDOM_METHODDEF
#define OS_GETRANDOM_METHODDEF
#endif /* !defined(OS_GETRANDOM_METHODDEF) */
/*[clinic end generated code: output=6345053cd5992caf input=a9049054013a1b77]*/
/*[clinic end generated code: output=d50830cf566cb297 input=a9049054013a1b77]*/
Loading