Add a python language binding for the .zero callback, used for
implementing NBD_CMD_WRITE_ZEROES. The caller doesn't have to
return anything, but should use nbdkit.set_error(errno.EOPNOTSUPP)
to get an automatic fallback to pwrite.
Enhance the example to show the use of the fallback mechanism,
and to serve as a test of nbdkit.set_error().
Signed-off-by: Eric Blake <eblake(a)redhat.com>
---
v2: rebase to .errno_is_reliable
---
plugins/python/example.py | 11 ++++++++
plugins/python/nbdkit-python-plugin.pod | 20 ++++++++++++++
plugins/python/python.c | 46 +++++++++++++++++++++++++++++++++
3 files changed, 77 insertions(+)
diff --git a/plugins/python/example.py b/plugins/python/example.py
index 184896e..8826eba 100644
--- a/plugins/python/example.py
+++ b/plugins/python/example.py
@@ -24,6 +24,9 @@
# ><fs> mount /dev/sda1 /
# ><fs> [etc]
+import nbdkit
+import errno
+
# This is the string used to store the emulated disk (initially all
# zero bytes). There is one disk per nbdkit instance, so if you
# reconnect to the same server you should see the same disk. You
@@ -56,3 +59,11 @@ def pwrite(h, buf, offset):
global disk
end = offset + len (buf)
disk[offset:end] = buf
+
+def zero(h, count, offset, may_trim):
+ global disk
+ if may_trim:
+ disk[offset:offset+count] = bytearray(count)
+ else:
+ nbdkit.set_error(errno.EOPNOTSUPP)
+ raise Exception
diff --git a/plugins/python/nbdkit-python-plugin.pod
b/plugins/python/nbdkit-python-plugin.pod
index a1e3d2b..1c57e15 100644
--- a/plugins/python/nbdkit-python-plugin.pod
+++ b/plugins/python/nbdkit-python-plugin.pod
@@ -215,6 +215,26 @@ The body of your C<trim> function should "punch a
hole" in the
backing store. If the trim fails, your function should throw an
exception, optionally using C<nbdkit.set_error> first.
+=item C<zero>
+
+(Optional)
+
+ def zero(h, count, offset, may_trim):
+ # no return value
+
+The body of your C<zero> function should ensure that C<count> bytes
+of the disk, starting at C<offset>, will read back as zero. If
+C<may_trim> is true, the operation may be optimized as a trim as long
+as subsequent reads see zeroes.
+
+NBD only supports whole writes, so your function should try to
+write the whole region (perhaps requiring a loop). If the write
+fails or is partial, your function should throw an exception,
+optionally using C<nbdkit.set_error> first. In particular, if
+you would like to automatically fall back to C<pwrite> (perhaps
+because there is nothing to optimize if C<may_trim> is false),
+use C<nbdkit.set_error(errno.EOPNOTSUPP)>.
+
=back
=head2 MISSING CALLBACKS
diff --git a/plugins/python/python.c b/plugins/python/python.c
index 631411e..895a361 100644
--- a/plugins/python/python.c
+++ b/plugins/python/python.c
@@ -54,6 +54,8 @@
static const char *script;
static PyObject *module;
+static int last_error;
+
static PyObject *
set_error (PyObject *self, PyObject *args)
{
@@ -62,6 +64,7 @@ set_error (PyObject *self, PyObject *args)
if (!PyArg_ParseTuple(args, "i", &err))
return NULL;
nbdkit_set_error (err);
+ last_error = err;
Py_RETURN_NONE;
}
@@ -441,6 +444,48 @@ py_trim (void *handle, uint32_t count, uint64_t offset)
}
static int
+py_zero (void *handle, uint32_t count, uint64_t offset, int may_trim)
+{
+ PyObject *obj = handle;
+ PyObject *fn;
+ PyObject *args;
+ PyObject *r;
+
+ if (callback_defined ("zero", &fn)) {
+ PyErr_Clear ();
+
+ last_error = 0;
+ args = PyTuple_New (4);
+ Py_INCREF (obj); /* decremented by Py_DECREF (args) */
+ PyTuple_SetItem (args, 0, obj);
+ PyTuple_SetItem (args, 1, PyLong_FromUnsignedLongLong (count));
+ PyTuple_SetItem (args, 2, PyLong_FromUnsignedLongLong (offset));
+ PyTuple_SetItem (args, 3, PyBool_FromLong (may_trim));
+ r = PyObject_CallObject (fn, args);
+ Py_DECREF (fn);
+ Py_DECREF (args);
+ if (last_error == EOPNOTSUPP) {
+ /* When user requests this particular error, we want to
+ gracefully fall back, and to accomodate both a normal return
+ and an exception. */
+ nbdkit_debug ("zero requested falling back to pwrite");
+ if (r)
+ Py_DECREF (r);
+ PyErr_Clear ();
+ return -1;
+ }
+ if (check_python_failure ("zero") == -1)
+ return -1;
+ Py_DECREF (r);
+ return 0;
+ }
+
+ nbdkit_debug ("zero missing, falling back to pwrite");
+ nbdkit_set_error (EOPNOTSUPP);
+ return -1;
+}
+
+static int
py_can_write (void *handle)
{
PyObject *obj = handle;
@@ -607,6 +652,7 @@ static struct nbdkit_plugin plugin = {
.pwrite = py_pwrite,
.flush = py_flush,
.trim = py_trim,
+ .zero = py_zero,
.errno_is_reliable = py_errno_is_reliable,
};
--
2.9.3