|
|
efa7a1 |
From 49ef7e7d7c3602cc8e53d2052fce9d3a12840ea2 Mon Sep 17 00:00:00 2001
|
|
|
efa7a1 |
From: "Richard W.M. Jones" <rjones@redhat.com>
|
|
|
efa7a1 |
Date: Thu, 21 Nov 2019 15:44:39 +0000
|
|
|
60545c |
Subject: [PATCH 05/19] python: Implement nbdkit API version 2.
|
|
|
efa7a1 |
|
|
|
efa7a1 |
To avoid breaking existing plugins, Python plugins wishing to use
|
|
|
efa7a1 |
version 2 of the API must opt in by declaring:
|
|
|
efa7a1 |
|
|
|
efa7a1 |
API_VERSION = 2
|
|
|
efa7a1 |
|
|
|
efa7a1 |
(Plugins which do not do this are assumed to want API version 1).
|
|
|
efa7a1 |
|
|
|
efa7a1 |
For v2 API, we also avoid a copy by passing a buffer into pread.
|
|
|
efa7a1 |
|
|
|
efa7a1 |
It's more efficient if we pass the C buffer directly to Python code.
|
|
|
efa7a1 |
In some cases the Python code will be able to write directly into the
|
|
|
efa7a1 |
C buffer using functions like file.readinto and socket.recv_into.
|
|
|
efa7a1 |
This avoids an extra copy.
|
|
|
efa7a1 |
|
|
|
efa7a1 |
Thanks: Nir Soffer
|
|
|
efa7a1 |
https://www.redhat.com/archives/libguestfs/2019-November/thread.html#00220
|
|
|
efa7a1 |
(cherry picked from commit a9b2637cf4f00fb8a25ffaf31ee83be5fe019ae2)
|
|
|
efa7a1 |
---
|
|
|
efa7a1 |
plugins/python/example.py | 20 +++-
|
|
|
efa7a1 |
plugins/python/nbdkit-python-plugin.pod | 69 +++++++-----
|
|
|
efa7a1 |
plugins/python/python.c | 139 +++++++++++++++++++-----
|
|
|
efa7a1 |
tests/python-exception.py | 4 +-
|
|
|
efa7a1 |
tests/shebang.py | 5 +-
|
|
|
efa7a1 |
tests/test.py | 28 +++--
|
|
|
efa7a1 |
6 files changed, 190 insertions(+), 75 deletions(-)
|
|
|
efa7a1 |
|
|
|
efa7a1 |
diff --git a/plugins/python/example.py b/plugins/python/example.py
|
|
|
60545c |
index 60f9d7f0..c04b7e29 100644
|
|
|
efa7a1 |
--- a/plugins/python/example.py
|
|
|
efa7a1 |
+++ b/plugins/python/example.py
|
|
|
efa7a1 |
@@ -34,6 +34,12 @@ import errno
|
|
|
efa7a1 |
disk = bytearray(1024 * 1024)
|
|
|
efa7a1 |
|
|
|
efa7a1 |
|
|
|
efa7a1 |
+# There are several variants of the API. nbdkit will call this
|
|
|
efa7a1 |
+# function first to determine which one you want to use. This is the
|
|
|
efa7a1 |
+# latest version at the time this example was written.
|
|
|
efa7a1 |
+API_VERSION = 2
|
|
|
efa7a1 |
+
|
|
|
efa7a1 |
+
|
|
|
efa7a1 |
# This just prints the extra command line parameters, but real plugins
|
|
|
efa7a1 |
# should parse them and reject any unknown parameters.
|
|
|
efa7a1 |
def config(key, value):
|
|
|
efa7a1 |
@@ -54,20 +60,22 @@ def get_size(h):
|
|
|
efa7a1 |
return len(disk)
|
|
|
efa7a1 |
|
|
|
efa7a1 |
|
|
|
efa7a1 |
-def pread(h, count, offset):
|
|
|
efa7a1 |
+def pread(h, buf, offset, flags):
|
|
|
efa7a1 |
global disk
|
|
|
efa7a1 |
- return disk[offset:offset+count]
|
|
|
efa7a1 |
+ end = offset + len(buf)
|
|
|
efa7a1 |
+ buf[:] = disk[offset:end]
|
|
|
efa7a1 |
+ # or if reading from a file you can use:
|
|
|
efa7a1 |
+ #f.readinto(buf)
|
|
|
efa7a1 |
|
|
|
efa7a1 |
-
|
|
|
efa7a1 |
-def pwrite(h, buf, offset):
|
|
|
efa7a1 |
+def pwrite(h, buf, offset, flags):
|
|
|
efa7a1 |
global disk
|
|
|
efa7a1 |
end = offset + len(buf)
|
|
|
efa7a1 |
disk[offset:end] = buf
|
|
|
efa7a1 |
|
|
|
efa7a1 |
|
|
|
efa7a1 |
-def zero(h, count, offset, may_trim):
|
|
|
efa7a1 |
+def zero(h, count, offset, flags):
|
|
|
efa7a1 |
global disk
|
|
|
efa7a1 |
- if may_trim:
|
|
|
efa7a1 |
+ if flags & nbdkit.FLAG_MAY_TRIM:
|
|
|
efa7a1 |
disk[offset:offset+count] = bytearray(count)
|
|
|
efa7a1 |
else:
|
|
|
efa7a1 |
nbdkit.set_error(errno.EOPNOTSUPP)
|
|
|
efa7a1 |
diff --git a/plugins/python/nbdkit-python-plugin.pod b/plugins/python/nbdkit-python-plugin.pod
|
|
|
60545c |
index 3680fd65..4923d9da 100644
|
|
|
efa7a1 |
--- a/plugins/python/nbdkit-python-plugin.pod
|
|
|
efa7a1 |
+++ b/plugins/python/nbdkit-python-plugin.pod
|
|
|
efa7a1 |
@@ -33,11 +33,12 @@ To write a Python nbdkit plugin, you create a Python file which
|
|
|
efa7a1 |
contains at least the following required functions (in the top level
|
|
|
efa7a1 |
C<__main__> module):
|
|
|
efa7a1 |
|
|
|
efa7a1 |
+ API_VERSION = 2
|
|
|
efa7a1 |
def open(readonly):
|
|
|
efa7a1 |
# see below
|
|
|
efa7a1 |
def get_size(h):
|
|
|
efa7a1 |
# see below
|
|
|
efa7a1 |
- def pread(h, count, offset):
|
|
|
efa7a1 |
+ def pread(h, buf, offset, flags):
|
|
|
efa7a1 |
# see below
|
|
|
efa7a1 |
|
|
|
efa7a1 |
Note that the subroutines must have those literal names (like C<open>),
|
|
|
efa7a1 |
@@ -82,6 +83,18 @@ I<--dump-plugin> option, eg:
|
|
|
efa7a1 |
python_version=3.7.0
|
|
|
efa7a1 |
python_pep_384_abi_version=3
|
|
|
efa7a1 |
|
|
|
efa7a1 |
+=head2 API versions
|
|
|
efa7a1 |
+
|
|
|
efa7a1 |
+The nbdkit API has evolved and new versions are released periodically.
|
|
|
efa7a1 |
+To ensure backwards compatibility plugins have to opt in to the new
|
|
|
efa7a1 |
+version. From Python you do this by declaring a constant in your
|
|
|
efa7a1 |
+module:
|
|
|
efa7a1 |
+
|
|
|
efa7a1 |
+ API_VERSION = 2
|
|
|
efa7a1 |
+
|
|
|
efa7a1 |
+(where 2 is the latest version at the time this documentation was
|
|
|
efa7a1 |
+written). All newly written Python modules must have this constant.
|
|
|
efa7a1 |
+
|
|
|
efa7a1 |
=head2 Executable script
|
|
|
efa7a1 |
|
|
|
efa7a1 |
If you want you can make the script executable and include a "shebang"
|
|
|
efa7a1 |
@@ -199,16 +212,12 @@ contents will be garbage collected.
|
|
|
efa7a1 |
|
|
|
efa7a1 |
(Required)
|
|
|
efa7a1 |
|
|
|
efa7a1 |
- def pread(h, count, offset):
|
|
|
efa7a1 |
- # construct a buffer of length count bytes and return it
|
|
|
efa7a1 |
+ def pread(h, buf, offset, flags):
|
|
|
efa7a1 |
+ # read into the buffer
|
|
|
efa7a1 |
|
|
|
efa7a1 |
-The body of your C<pread> function should construct a buffer of length
|
|
|
efa7a1 |
-(at least) C<count> bytes. You should read C<count> bytes from the
|
|
|
efa7a1 |
-disk starting at C<offset>.
|
|
|
efa7a1 |
-
|
|
|
efa7a1 |
-The returned buffer can be any type compatible with the Python 3
|
|
|
efa7a1 |
-buffer protocol, such as bytearray, bytes or memoryview
|
|
|
efa7a1 |
-(L<https://docs.python.org/3/c-api/buffer.html>)
|
|
|
efa7a1 |
+The body of your C<pread> function should read exactly C<len(buf)>
|
|
|
efa7a1 |
+bytes of data starting at disk C<offset> and write it into the buffer
|
|
|
efa7a1 |
+C<buf>. C<flags> is always 0.
|
|
|
efa7a1 |
|
|
|
efa7a1 |
NBD only supports whole reads, so your function should try to read
|
|
|
efa7a1 |
the whole region (perhaps requiring a loop). If the read fails or
|
|
|
efa7a1 |
@@ -219,13 +228,13 @@ C<nbdkit.set_error> first.
|
|
|
efa7a1 |
|
|
|
efa7a1 |
(Optional)
|
|
|
efa7a1 |
|
|
|
efa7a1 |
- def pwrite(h, buf, offset):
|
|
|
efa7a1 |
+ def pwrite(h, buf, offset, flags):
|
|
|
efa7a1 |
length = len (buf)
|
|
|
efa7a1 |
# no return value
|
|
|
efa7a1 |
|
|
|
efa7a1 |
The body of your C<pwrite> function should write the buffer C<buf> to
|
|
|
efa7a1 |
the disk. You should write C<count> bytes to the disk starting at
|
|
|
efa7a1 |
-C<offset>.
|
|
|
efa7a1 |
+C<offset>. C<flags> may contain C<nbdkit.FLAG_FUA>.
|
|
|
efa7a1 |
|
|
|
efa7a1 |
NBD only supports whole writes, so your function should try to
|
|
|
efa7a1 |
write the whole region (perhaps requiring a loop). If the write
|
|
|
efa7a1 |
@@ -236,11 +245,12 @@ fails or is partial, your function should throw an exception,
|
|
|
efa7a1 |
|
|
|
efa7a1 |
(Optional)
|
|
|
efa7a1 |
|
|
|
efa7a1 |
- def flush(h):
|
|
|
efa7a1 |
+ def flush(h, flags):
|
|
|
efa7a1 |
# no return value
|
|
|
efa7a1 |
|
|
|
efa7a1 |
The body of your C<flush> function should do a L<sync(2)> or
|
|
|
efa7a1 |
L<fdatasync(2)> or equivalent on the backing store.
|
|
|
efa7a1 |
+C<flags> is always 0.
|
|
|
efa7a1 |
|
|
|
efa7a1 |
If the flush fails, your function should throw an exception, optionally
|
|
|
efa7a1 |
using C<nbdkit.set_error> first.
|
|
|
efa7a1 |
@@ -249,32 +259,35 @@ using C<nbdkit.set_error> first.
|
|
|
efa7a1 |
|
|
|
efa7a1 |
(Optional)
|
|
|
efa7a1 |
|
|
|
efa7a1 |
- def trim(h, count, offset):
|
|
|
efa7a1 |
+ def trim(h, count, offset, flags):
|
|
|
efa7a1 |
# no return value
|
|
|
efa7a1 |
|
|
|
efa7a1 |
-The body of your C<trim> function should "punch a hole" in the
|
|
|
efa7a1 |
-backing store. If the trim fails, your function should throw an
|
|
|
efa7a1 |
-exception, optionally using C<nbdkit.set_error> first.
|
|
|
efa7a1 |
+The body of your C<trim> function should "punch a hole" in the backing
|
|
|
efa7a1 |
+store. C<flags> may contain C<nbdkit.FLAG_FUA>. If the trim fails,
|
|
|
efa7a1 |
+your function should throw an exception, optionally using
|
|
|
efa7a1 |
+C<nbdkit.set_error> first.
|
|
|
efa7a1 |
|
|
|
efa7a1 |
=item C<zero>
|
|
|
efa7a1 |
|
|
|
efa7a1 |
(Optional)
|
|
|
efa7a1 |
|
|
|
efa7a1 |
- def zero(h, count, offset, may_trim):
|
|
|
efa7a1 |
+ def zero(h, count, offset, flags):
|
|
|
efa7a1 |
# no return value
|
|
|
efa7a1 |
|
|
|
efa7a1 |
-The body of your C<zero> function should ensure that C<count> bytes
|
|
|
efa7a1 |
-of the disk, starting at C<offset>, will read back as zero. If
|
|
|
efa7a1 |
-C<may_trim> is true, the operation may be optimized as a trim as long
|
|
|
efa7a1 |
-as subsequent reads see zeroes.
|
|
|
efa7a1 |
+The body of your C<zero> function should ensure that C<count> bytes of
|
|
|
efa7a1 |
+the disk, starting at C<offset>, will read back as zero. C<flags> is
|
|
|
efa7a1 |
+a bitmask which may include C<nbdkit.FLAG_MAY_TRIM>,
|
|
|
efa7a1 |
+C<nbdkit.FLAG_FUA>, C<nbdkit.FLAG_FAST_ZERO>.
|
|
|
efa7a1 |
|
|
|
efa7a1 |
NBD only supports whole writes, so your function should try to
|
|
|
efa7a1 |
-write the whole region (perhaps requiring a loop). If the write
|
|
|
efa7a1 |
-fails or is partial, your function should throw an exception,
|
|
|
efa7a1 |
-optionally using C<nbdkit.set_error> first. In particular, if
|
|
|
efa7a1 |
-you would like to automatically fall back to C<pwrite> (perhaps
|
|
|
efa7a1 |
-because there is nothing to optimize if C<may_trim> is false),
|
|
|
efa7a1 |
-use C<nbdkit.set_error(errno.EOPNOTSUPP)>.
|
|
|
efa7a1 |
+write the whole region (perhaps requiring a loop).
|
|
|
efa7a1 |
+
|
|
|
efa7a1 |
+If the write fails or is partial, your function should throw an
|
|
|
efa7a1 |
+exception, optionally using C<nbdkit.set_error> first. In particular,
|
|
|
efa7a1 |
+if you would like to automatically fall back to C<pwrite> (perhaps
|
|
|
efa7a1 |
+because there is nothing to optimize if
|
|
|
efa7a1 |
+S<C<flags & nbdkit.FLAG_MAY_TRIM>> is false), use
|
|
|
efa7a1 |
+S<C<nbdkit.set_error (errno.EOPNOTSUPP)>>.
|
|
|
efa7a1 |
|
|
|
efa7a1 |
=back
|
|
|
efa7a1 |
|
|
|
efa7a1 |
diff --git a/plugins/python/python.c b/plugins/python/python.c
|
|
|
60545c |
index 47da0838..0f28595f 100644
|
|
|
efa7a1 |
--- a/plugins/python/python.c
|
|
|
efa7a1 |
+++ b/plugins/python/python.c
|
|
|
efa7a1 |
@@ -46,6 +46,8 @@
|
|
|
efa7a1 |
#define PY_SSIZE_T_CLEAN 1
|
|
|
efa7a1 |
#include <Python.h>
|
|
|
efa7a1 |
|
|
|
efa7a1 |
+#define NBDKIT_API_VERSION 2
|
|
|
efa7a1 |
+
|
|
|
efa7a1 |
#include <nbdkit-plugin.h>
|
|
|
efa7a1 |
|
|
|
efa7a1 |
#include "cleanup.h"
|
|
|
efa7a1 |
@@ -60,6 +62,7 @@
|
|
|
efa7a1 |
*/
|
|
|
efa7a1 |
static const char *script;
|
|
|
efa7a1 |
static PyObject *module;
|
|
|
efa7a1 |
+static int py_api_version = 1;
|
|
|
efa7a1 |
|
|
|
efa7a1 |
static int last_error;
|
|
|
efa7a1 |
|
|
|
efa7a1 |
@@ -285,9 +288,14 @@ py_dump_plugin (void)
|
|
|
efa7a1 |
PyObject *fn;
|
|
|
efa7a1 |
PyObject *r;
|
|
|
efa7a1 |
|
|
|
efa7a1 |
+ /* Python version and ABI. */
|
|
|
efa7a1 |
printf ("python_version=%s\n", PY_VERSION);
|
|
|
efa7a1 |
printf ("python_pep_384_abi_version=%d\n", PYTHON_ABI_VERSION);
|
|
|
efa7a1 |
|
|
|
efa7a1 |
+ /* Maximum nbdkit API version supported. */
|
|
|
efa7a1 |
+ printf ("nbdkit_python_maximum_api_version=%d\n", NBDKIT_API_VERSION);
|
|
|
efa7a1 |
+
|
|
|
efa7a1 |
+ /* If the script has a dump_plugin function, call it. */
|
|
|
efa7a1 |
if (script && callback_defined ("dump_plugin", &fn)) {
|
|
|
efa7a1 |
PyErr_Clear ();
|
|
|
efa7a1 |
|
|
|
efa7a1 |
@@ -297,6 +305,30 @@ py_dump_plugin (void)
|
|
|
efa7a1 |
}
|
|
|
efa7a1 |
}
|
|
|
efa7a1 |
|
|
|
efa7a1 |
+static int
|
|
|
efa7a1 |
+get_py_api_version (void)
|
|
|
efa7a1 |
+{
|
|
|
efa7a1 |
+ PyObject *obj;
|
|
|
efa7a1 |
+ long value;
|
|
|
efa7a1 |
+
|
|
|
efa7a1 |
+ obj = PyObject_GetAttrString (module, "API_VERSION");
|
|
|
efa7a1 |
+ if (obj == NULL)
|
|
|
efa7a1 |
+ return 1; /* Default to API version 1. */
|
|
|
efa7a1 |
+
|
|
|
efa7a1 |
+ value = PyLong_AsLong (obj);
|
|
|
efa7a1 |
+ Py_DECREF (obj);
|
|
|
efa7a1 |
+
|
|
|
efa7a1 |
+ if (value < 1 || value > NBDKIT_API_VERSION) {
|
|
|
efa7a1 |
+ nbdkit_error ("%s: API_VERSION requested unknown version: %ld. "
|
|
|
efa7a1 |
+ "This plugin supports API versions between 1 and %d.",
|
|
|
efa7a1 |
+ script, value, NBDKIT_API_VERSION);
|
|
|
efa7a1 |
+ return -1;
|
|
|
efa7a1 |
+ }
|
|
|
efa7a1 |
+
|
|
|
efa7a1 |
+ nbdkit_debug ("module requested API_VERSION %ld", value);
|
|
|
efa7a1 |
+ return (int) value;
|
|
|
efa7a1 |
+}
|
|
|
efa7a1 |
+
|
|
|
efa7a1 |
static int
|
|
|
efa7a1 |
py_config (const char *key, const char *value)
|
|
|
efa7a1 |
{
|
|
|
efa7a1 |
@@ -359,6 +391,11 @@ py_config (const char *key, const char *value)
|
|
|
efa7a1 |
"nbdkit requires these callbacks.", script);
|
|
|
efa7a1 |
return -1;
|
|
|
efa7a1 |
}
|
|
|
efa7a1 |
+
|
|
|
efa7a1 |
+ /* Get the API version. */
|
|
|
efa7a1 |
+ py_api_version = get_py_api_version ();
|
|
|
efa7a1 |
+ if (py_api_version == -1)
|
|
|
efa7a1 |
+ return -1;
|
|
|
efa7a1 |
}
|
|
|
efa7a1 |
else if (callback_defined ("config", &fn)) {
|
|
|
efa7a1 |
/* Other parameters are passed to the Python .config callback. */
|
|
|
efa7a1 |
@@ -469,8 +506,8 @@ py_get_size (void *handle)
|
|
|
efa7a1 |
}
|
|
|
efa7a1 |
|
|
|
efa7a1 |
static int
|
|
|
efa7a1 |
-py_pread (void *handle, void *buf,
|
|
|
efa7a1 |
- uint32_t count, uint64_t offset)
|
|
|
efa7a1 |
+py_pread (void *handle, void *buf, uint32_t count, uint64_t offset,
|
|
|
efa7a1 |
+ uint32_t flags)
|
|
|
efa7a1 |
{
|
|
|
efa7a1 |
PyObject *obj = handle;
|
|
|
efa7a1 |
PyObject *fn;
|
|
|
efa7a1 |
@@ -485,24 +522,40 @@ py_pread (void *handle, void *buf,
|
|
|
efa7a1 |
|
|
|
efa7a1 |
PyErr_Clear ();
|
|
|
efa7a1 |
|
|
|
efa7a1 |
- r = PyObject_CallFunction (fn, "OiL", obj, count, offset);
|
|
|
efa7a1 |
+ switch (py_api_version) {
|
|
|
efa7a1 |
+ case 1:
|
|
|
efa7a1 |
+ r = PyObject_CallFunction (fn, "OiL", obj, count, offset);
|
|
|
efa7a1 |
+ break;
|
|
|
efa7a1 |
+ case 2:
|
|
|
efa7a1 |
+ r = PyObject_CallFunction (fn, "ONLI", obj,
|
|
|
efa7a1 |
+ PyMemoryView_FromMemory ((char *)buf, count, PyBUF_WRITE),
|
|
|
efa7a1 |
+ offset, flags);
|
|
|
efa7a1 |
+ break;
|
|
|
efa7a1 |
+ default: abort ();
|
|
|
efa7a1 |
+ }
|
|
|
efa7a1 |
Py_DECREF (fn);
|
|
|
efa7a1 |
if (check_python_failure ("pread") == -1)
|
|
|
efa7a1 |
return ret;
|
|
|
efa7a1 |
|
|
|
efa7a1 |
- if (PyObject_GetBuffer (r, &view, PyBUF_SIMPLE) == -1) {
|
|
|
efa7a1 |
- nbdkit_error ("%s: value returned from pread does not support the "
|
|
|
efa7a1 |
- "buffer protocol",
|
|
|
efa7a1 |
- script);
|
|
|
efa7a1 |
- goto out;
|
|
|
efa7a1 |
- }
|
|
|
efa7a1 |
+ if (py_api_version == 1) {
|
|
|
efa7a1 |
+ /* In API v1 the Python pread function had to return a buffer
|
|
|
efa7a1 |
+ * protocol compatible function. In API v2+ it writes directly to
|
|
|
efa7a1 |
+ * the C buffer so this code is not used.
|
|
|
efa7a1 |
+ */
|
|
|
efa7a1 |
+ if (PyObject_GetBuffer (r, &view, PyBUF_SIMPLE) == -1) {
|
|
|
efa7a1 |
+ nbdkit_error ("%s: value returned from pread does not support the "
|
|
|
efa7a1 |
+ "buffer protocol",
|
|
|
efa7a1 |
+ script);
|
|
|
efa7a1 |
+ goto out;
|
|
|
efa7a1 |
+ }
|
|
|
efa7a1 |
|
|
|
efa7a1 |
- if (view.len < count) {
|
|
|
efa7a1 |
- nbdkit_error ("%s: buffer returned from pread is too small", script);
|
|
|
efa7a1 |
- goto out;
|
|
|
efa7a1 |
- }
|
|
|
efa7a1 |
+ if (view.len < count) {
|
|
|
efa7a1 |
+ nbdkit_error ("%s: buffer returned from pread is too small", script);
|
|
|
efa7a1 |
+ goto out;
|
|
|
efa7a1 |
+ }
|
|
|
efa7a1 |
|
|
|
efa7a1 |
- memcpy (buf, view.buf, count);
|
|
|
efa7a1 |
+ memcpy (buf, view.buf, count);
|
|
|
efa7a1 |
+ }
|
|
|
efa7a1 |
ret = 0;
|
|
|
efa7a1 |
|
|
|
efa7a1 |
out:
|
|
|
efa7a1 |
@@ -515,8 +568,8 @@ out:
|
|
|
efa7a1 |
}
|
|
|
efa7a1 |
|
|
|
efa7a1 |
static int
|
|
|
efa7a1 |
-py_pwrite (void *handle, const void *buf,
|
|
|
efa7a1 |
- uint32_t count, uint64_t offset)
|
|
|
efa7a1 |
+py_pwrite (void *handle, const void *buf, uint32_t count, uint64_t offset,
|
|
|
efa7a1 |
+ uint32_t flags)
|
|
|
efa7a1 |
{
|
|
|
efa7a1 |
PyObject *obj = handle;
|
|
|
efa7a1 |
PyObject *fn;
|
|
|
efa7a1 |
@@ -525,9 +578,19 @@ py_pwrite (void *handle, const void *buf,
|
|
|
efa7a1 |
if (callback_defined ("pwrite", &fn)) {
|
|
|
efa7a1 |
PyErr_Clear ();
|
|
|
efa7a1 |
|
|
|
efa7a1 |
- r = PyObject_CallFunction (fn, "ONL", obj,
|
|
|
efa7a1 |
+ switch (py_api_version) {
|
|
|
efa7a1 |
+ case 1:
|
|
|
efa7a1 |
+ r = PyObject_CallFunction (fn, "ONL", obj,
|
|
|
efa7a1 |
PyMemoryView_FromMemory ((char *)buf, count, PyBUF_READ),
|
|
|
efa7a1 |
offset);
|
|
|
efa7a1 |
+ break;
|
|
|
efa7a1 |
+ case 2:
|
|
|
efa7a1 |
+ r = PyObject_CallFunction (fn, "ONLI", obj,
|
|
|
efa7a1 |
+ PyMemoryView_FromMemory ((char *)buf, count, PyBUF_READ),
|
|
|
efa7a1 |
+ offset, flags);
|
|
|
efa7a1 |
+ break;
|
|
|
efa7a1 |
+ default: abort ();
|
|
|
efa7a1 |
+ }
|
|
|
efa7a1 |
Py_DECREF (fn);
|
|
|
efa7a1 |
if (check_python_failure ("pwrite") == -1)
|
|
|
efa7a1 |
return -1;
|
|
|
efa7a1 |
@@ -542,7 +605,7 @@ py_pwrite (void *handle, const void *buf,
|
|
|
efa7a1 |
}
|
|
|
efa7a1 |
|
|
|
efa7a1 |
static int
|
|
|
efa7a1 |
-py_flush (void *handle)
|
|
|
efa7a1 |
+py_flush (void *handle, uint32_t flags)
|
|
|
efa7a1 |
{
|
|
|
efa7a1 |
PyObject *obj = handle;
|
|
|
efa7a1 |
PyObject *fn;
|
|
|
efa7a1 |
@@ -551,7 +614,15 @@ py_flush (void *handle)
|
|
|
efa7a1 |
if (callback_defined ("flush", &fn)) {
|
|
|
efa7a1 |
PyErr_Clear ();
|
|
|
efa7a1 |
|
|
|
efa7a1 |
- r = PyObject_CallFunctionObjArgs (fn, obj, NULL);
|
|
|
efa7a1 |
+ switch (py_api_version) {
|
|
|
efa7a1 |
+ case 1:
|
|
|
efa7a1 |
+ r = PyObject_CallFunctionObjArgs (fn, obj, NULL);
|
|
|
efa7a1 |
+ break;
|
|
|
efa7a1 |
+ case 2:
|
|
|
efa7a1 |
+ r = PyObject_CallFunction (fn, "OI", obj, flags);
|
|
|
efa7a1 |
+ break;
|
|
|
efa7a1 |
+ default: abort ();
|
|
|
efa7a1 |
+ }
|
|
|
efa7a1 |
Py_DECREF (fn);
|
|
|
efa7a1 |
if (check_python_failure ("flush") == -1)
|
|
|
efa7a1 |
return -1;
|
|
|
efa7a1 |
@@ -566,7 +637,7 @@ py_flush (void *handle)
|
|
|
efa7a1 |
}
|
|
|
efa7a1 |
|
|
|
efa7a1 |
static int
|
|
|
efa7a1 |
-py_trim (void *handle, uint32_t count, uint64_t offset)
|
|
|
efa7a1 |
+py_trim (void *handle, uint32_t count, uint64_t offset, uint32_t flags)
|
|
|
efa7a1 |
{
|
|
|
efa7a1 |
PyObject *obj = handle;
|
|
|
efa7a1 |
PyObject *fn;
|
|
|
efa7a1 |
@@ -575,7 +646,15 @@ py_trim (void *handle, uint32_t count, uint64_t offset)
|
|
|
efa7a1 |
if (callback_defined ("trim", &fn)) {
|
|
|
efa7a1 |
PyErr_Clear ();
|
|
|
efa7a1 |
|
|
|
efa7a1 |
- r = PyObject_CallFunction (fn, "OiL", obj, count, offset);
|
|
|
efa7a1 |
+ switch (py_api_version) {
|
|
|
efa7a1 |
+ case 1:
|
|
|
efa7a1 |
+ r = PyObject_CallFunction (fn, "OiL", obj, count, offset);
|
|
|
efa7a1 |
+ break;
|
|
|
efa7a1 |
+ case 2:
|
|
|
efa7a1 |
+ r = PyObject_CallFunction (fn, "OiLI", obj, count, offset, flags);
|
|
|
efa7a1 |
+ break;
|
|
|
efa7a1 |
+ default: abort ();
|
|
|
efa7a1 |
+ }
|
|
|
efa7a1 |
Py_DECREF (fn);
|
|
|
efa7a1 |
if (check_python_failure ("trim") == -1)
|
|
|
efa7a1 |
return -1;
|
|
|
efa7a1 |
@@ -590,7 +669,7 @@ py_trim (void *handle, uint32_t count, uint64_t offset)
|
|
|
efa7a1 |
}
|
|
|
efa7a1 |
|
|
|
efa7a1 |
static int
|
|
|
efa7a1 |
-py_zero (void *handle, uint32_t count, uint64_t offset, int may_trim)
|
|
|
efa7a1 |
+py_zero (void *handle, uint32_t count, uint64_t offset, uint32_t flags)
|
|
|
efa7a1 |
{
|
|
|
efa7a1 |
PyObject *obj = handle;
|
|
|
efa7a1 |
PyObject *fn;
|
|
|
efa7a1 |
@@ -600,9 +679,19 @@ py_zero (void *handle, uint32_t count, uint64_t offset, int may_trim)
|
|
|
efa7a1 |
PyErr_Clear ();
|
|
|
efa7a1 |
|
|
|
efa7a1 |
last_error = 0;
|
|
|
efa7a1 |
- r = PyObject_CallFunction (fn, "OiLO",
|
|
|
efa7a1 |
- obj, count, offset,
|
|
|
efa7a1 |
- may_trim ? Py_True : Py_False);
|
|
|
efa7a1 |
+ switch (py_api_version) {
|
|
|
efa7a1 |
+ case 1: {
|
|
|
efa7a1 |
+ int may_trim = flags & NBDKIT_FLAG_MAY_TRIM;
|
|
|
efa7a1 |
+ r = PyObject_CallFunction (fn, "OiLO",
|
|
|
efa7a1 |
+ obj, count, offset,
|
|
|
efa7a1 |
+ may_trim ? Py_True : Py_False);
|
|
|
efa7a1 |
+ break;
|
|
|
efa7a1 |
+ }
|
|
|
efa7a1 |
+ case 2:
|
|
|
efa7a1 |
+ r = PyObject_CallFunction (fn, "OiLI", obj, count, offset, flags);
|
|
|
efa7a1 |
+ break;
|
|
|
efa7a1 |
+ default: abort ();
|
|
|
efa7a1 |
+ }
|
|
|
efa7a1 |
Py_DECREF (fn);
|
|
|
efa7a1 |
if (last_error == EOPNOTSUPP || last_error == ENOTSUP) {
|
|
|
efa7a1 |
/* When user requests this particular error, we want to
|
|
|
efa7a1 |
diff --git a/tests/python-exception.py b/tests/python-exception.py
|
|
|
60545c |
index d0c79bb0..ee4a3f3a 100644
|
|
|
efa7a1 |
--- a/tests/python-exception.py
|
|
|
efa7a1 |
+++ b/tests/python-exception.py
|
|
|
efa7a1 |
@@ -62,5 +62,5 @@ def get_size(h):
|
|
|
efa7a1 |
return 0
|
|
|
efa7a1 |
|
|
|
efa7a1 |
|
|
|
efa7a1 |
-def pread(h, count, offset):
|
|
|
efa7a1 |
- return ""
|
|
|
efa7a1 |
+def pread(h, buf, offset):
|
|
|
efa7a1 |
+ buf[:] = bytearray(len(buf))
|
|
|
efa7a1 |
diff --git a/tests/shebang.py b/tests/shebang.py
|
|
|
60545c |
index 6f336230..0634589a 100755
|
|
|
efa7a1 |
--- a/tests/shebang.py
|
|
|
efa7a1 |
+++ b/tests/shebang.py
|
|
|
efa7a1 |
@@ -13,6 +13,7 @@ def get_size(h):
|
|
|
efa7a1 |
return len(disk)
|
|
|
efa7a1 |
|
|
|
efa7a1 |
|
|
|
efa7a1 |
-def pread(h, count, offset):
|
|
|
efa7a1 |
+def pread(h, buf, offset):
|
|
|
efa7a1 |
global disk
|
|
|
efa7a1 |
- return disk[offset:offset+count]
|
|
|
efa7a1 |
+ end = offset + len(buf)
|
|
|
efa7a1 |
+ buf[:] = disk[offset:end]
|
|
|
efa7a1 |
diff --git a/tests/test.py b/tests/test.py
|
|
|
60545c |
index 9a2e947d..4db56623 100644
|
|
|
efa7a1 |
--- a/tests/test.py
|
|
|
efa7a1 |
+++ b/tests/test.py
|
|
|
efa7a1 |
@@ -3,6 +3,9 @@ import nbdkit
|
|
|
efa7a1 |
disk = bytearray(1024*1024)
|
|
|
efa7a1 |
|
|
|
efa7a1 |
|
|
|
efa7a1 |
+API_VERSION = 2
|
|
|
efa7a1 |
+
|
|
|
efa7a1 |
+
|
|
|
efa7a1 |
def config_complete():
|
|
|
efa7a1 |
print ("set_error = %r" % nbdkit.set_error)
|
|
|
efa7a1 |
|
|
|
efa7a1 |
@@ -32,25 +35,26 @@ def can_trim(h):
|
|
|
efa7a1 |
return True
|
|
|
efa7a1 |
|
|
|
efa7a1 |
|
|
|
efa7a1 |
-def pread(h, count, offset):
|
|
|
efa7a1 |
+def pread(h, buf, offset, flags):
|
|
|
efa7a1 |
global disk
|
|
|
efa7a1 |
- return disk[offset:offset+count]
|
|
|
efa7a1 |
+ end = offset + len(buf)
|
|
|
efa7a1 |
+ buf[:] = disk[offset:end]
|
|
|
efa7a1 |
|
|
|
efa7a1 |
|
|
|
efa7a1 |
-def pwrite(h, buf, offset):
|
|
|
efa7a1 |
+def pwrite(h, buf, offset, flags):
|
|
|
efa7a1 |
global disk
|
|
|
efa7a1 |
end = offset + len(buf)
|
|
|
efa7a1 |
disk[offset:end] = buf
|
|
|
efa7a1 |
|
|
|
efa7a1 |
|
|
|
efa7a1 |
-def zero(h, count, offset, may_trim=False):
|
|
|
efa7a1 |
+def flush(h, flags):
|
|
|
efa7a1 |
+ pass
|
|
|
efa7a1 |
+
|
|
|
efa7a1 |
+
|
|
|
efa7a1 |
+def trim(h, count, offset, flags):
|
|
|
efa7a1 |
+ pass
|
|
|
efa7a1 |
+
|
|
|
efa7a1 |
+
|
|
|
efa7a1 |
+def zero(h, count, offset, flags):
|
|
|
efa7a1 |
global disk
|
|
|
efa7a1 |
disk[offset:offset+count] = bytearray(count)
|
|
|
efa7a1 |
-
|
|
|
efa7a1 |
-
|
|
|
efa7a1 |
-def flush(h):
|
|
|
efa7a1 |
- pass
|
|
|
efa7a1 |
-
|
|
|
efa7a1 |
-
|
|
|
efa7a1 |
-def trim(h, count, offset):
|
|
|
efa7a1 |
- pass
|
|
|
efa7a1 |
--
|
|
|
efa7a1 |
2.18.2
|
|
|
efa7a1 |
|