[Libguestfs] [nbdkit PATCH v3 4/4] python: Support zero callback

Eric Blake eblake at redhat.com
Fri Jan 27 02:58:37 UTC 2017


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 at 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




More information about the Libguestfs mailing list