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@redhat.com> --- 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 8b3d08c..b78c0e2 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 4a0ff50..644ced4 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"); + errno = EOPNOTSUPP; + return -1; +} + +static int py_can_write (void *handle) { PyObject *obj = handle; @@ -597,6 +642,7 @@ static struct nbdkit_plugin plugin = { .pwrite = py_pwrite, .flush = py_flush, .trim = py_trim, + .zero = py_zero, }; NBDKIT_REGISTER_PLUGIN(plugin) -- 2.9.3