This plugin lets you use an external subprocess, like nbdkit-sh-plugin
except that the subprocess is persistent across calls.  There is one
subprocess handling all connections.  Think of this as "FastCGI for
plugins" - because the subprocess is persistent instead of having to
be forked on every request, it may be faster for some use cases.

Communication between nbdkit and the subprocess uses a simple remote
procedure call (RPC) over stdin/stdout, which is string based and
similar to the shell commands used by nbdkit-sh-plugin.

The new plugin shares most code with nbdkit-sh-plugin /
nbdkit-eval-plugin.
---
 plugins/cc/nbdkit-cc-plugin.pod     |   1 +
 plugins/eval/nbdkit-eval-plugin.pod |   1 +
 plugins/proc/nbdkit-proc-plugin.pod | 304 +++++++++++++++++++++++++++
 plugins/sh/nbdkit-sh-plugin.pod     |   4 +
 configure.ac                        |   3 +
 plugins/proc/Makefile.am            |  95 +++++++++
 plugins/proc/examples/Makefile.am   |  49 +++++
 plugins/proc/proc-protocol.h        |  81 +++++++
 plugins/proc/examples/simple.c      | 125 +++++++++++
 plugins/proc/proc-protocol.c        | 290 +++++++++++++++++++++++++
 plugins/proc/proc.c                 | 314 ++++++++++++++++++++++++++++
 .gitignore                          |   2 +
 12 files changed, 1269 insertions(+)

diff --git a/plugins/cc/nbdkit-cc-plugin.pod b/plugins/cc/nbdkit-cc-plugin.pod
index c05d6a72b5..26a525fe81 100644
--- a/plugins/cc/nbdkit-cc-plugin.pod
+++ b/plugins/cc/nbdkit-cc-plugin.pod
@@ -210,6 +210,7 @@ L<nbdkit(1)>,
 L<nbdkit-plugin(3)>,
 L<nbdkit-eval-plugin(3)>,
 L<nbdkit-ocaml-plugin(3)>,
+L<nbdkit-proc-plugin(3)>,
 L<nbdkit-sh-plugin(3)>.
 
 =head1 AUTHORS
diff --git a/plugins/eval/nbdkit-eval-plugin.pod 
b/plugins/eval/nbdkit-eval-plugin.pod
index 2807955b24..cff4cc6916 100644
--- a/plugins/eval/nbdkit-eval-plugin.pod
+++ b/plugins/eval/nbdkit-eval-plugin.pod
@@ -201,6 +201,7 @@ C<nbdkit-eval-plugin> first appeared in nbdkit 1.18.
 L<nbdkit(1)>,
 L<nbdkit-plugin(3)>,
 L<nbdkit-sh-plugin(3)>,
+L<nbdkit-proc-plugin(1)>,
 L<nbdkit-cc-plugin(1)>.
 
 =head1 AUTHORS
diff --git a/plugins/proc/nbdkit-proc-plugin.pod 
b/plugins/proc/nbdkit-proc-plugin.pod
new file mode 100644
index 0000000000..35e879e1e8
--- /dev/null
+++ b/plugins/proc/nbdkit-proc-plugin.pod
@@ -0,0 +1,304 @@
+=head1 NAME
+
+nbdkit-proc-plugin - use an external program to implement a plugin
+
+=head1 SYNOPSIS
+
+ nbdkit proc program [arguments...]
+
+=head1 DESCRIPTION
+
+C<nbdkit-proc-plugin> allows you to write plugins for L<nbdkit(1)>
+using an external program written in any programming or scripting
+language.
+
+It is similar to L<nbdkit-sh-plugin(3)>, except that the subprocess is
+persistent across calls.  If you think of nbdkit-sh-plugin as being
+like CGI, then this is like
+L<FastCGI|https://en.wikipedia.org/wiki/FastCGI>.  Because the
+subprocess is persistent instead of having to be forked on every
+request, it may be faster than nbdkit-sh-plugin in some cases.
+
+Alternatives to this plugin include L<nbdkit-sh-plugin(3)>,
+L<nbdkit-eval-plugin(1)>, or writing a regular nbdkit plugin which is
+dynamically loaded into nbdkit (L<nbdkit-plugin(3)>).
+
+=head2 If you have been given an nbdkit proc plugin
+
+You can use it like this:
+
+ nbdkit proc program
+
+You may have to add further C<key=value> arguments to the command
+line.  The program must be executable (C<chmod +x>).
+
+=head1 WRITING AN NBDKIT PROC PLUGIN
+
+For example plugins, see:
+L<https://gitlab.com/nbdkit/nbdkit/blob/master/plugins/proc/examples/>
+
+As this plugin works like L<nbdkit-sh-plugin(3)> and shares the
+implementation, reading the documentation for that plugin first as
+well as L<nbdkit-plugin(3)> is recommended.
+
+=head2 The external program
+
+C<nbdkit proc program> starts the external C<program> when nbdkit
+starts, and kills it when nbdkit shuts down.
+
+While nbdkit is running, requests from nbdkit are forwarded to the
+external program over its stdin, and the external program should write
+replies to its stdout.  The protocol is a simple string-based remote
+procedure call (RPC) described below.
+
+It is possible to configure the RPC to allow only one, or multiple
+requests to be in flight at once.  This gives you flexibility: you can
+choose to write a simple read request + write reply loop that handles
+requests one at a time; or you can write something more sophisticated
+that keeps track of multiple requests in parallel.
+
+This plugin only ever starts one external program (per instance of
+nbdkit) across all nbdkit clients, so if your program needs
+parallelism it should start its own threads.
+
+=head2 Remote procedure call protocol
+
+The remote procedure call protocol is a simple string-based encoding
+which is very similar to the parameters used by
+L<nbdkit-sh-plugin(3)>, but serialized over the stdin and stdout of
+your program.
+
+There is a simple implementation available in
+L<https://gitlab.com/nbdkit/nbdkit/blob/master/plugins/proc/proc-protocol.c>
+and
+L<https://gitlab.com/nbdkit/nbdkit/blob/master/plugins/proc/proc-protocol.h>
+or you can write your own by following the specification below.
+
+Requests and replies are lists of NUL-terminated (C-like) strings.
+For example, this sequence of bytes:
+
+ 'R', '9', '9', '\0',
+ '0', '\0',
+ '1', '\0',
+ 'c', 'l', 'o', 's', 'e', '\0'
+
+represents the list:
+
+ [ "R99", "0", "1", "close" ]
+
+=over 4
+
+=item request ID
+
+The first element is the request identifier, a string, which is used
+to correlate requests with replies when there are multiple outstanding
+requests in flight.
+
+=item optional binary buffer length
+
+The second element is used to indicate that an optional binary buffer
+follows the list.  It is currently non-C<"0"> only in the request
+message of C<"pwrite"> and the reply message of C<"pread">.
+
+If this element is C<"0">, then the buffer is zero length / not sent,
+else it is the length of the buffer in bytes.
+
+The binary buffer is written to the stream directly following the end
+of the payload.  There are no delimiting bytes before or after the
+buffer.
+
+=item payload length
+
+The third element of the list is always the number of elements in the
+payload, encoded as a string.
+
+=item payload
+
+There follow payload length list elements, which contain the request
+or reply.
+
+=item binary buffer
+
+If the second list element is not C<"0"> then immediately after the
+payload a binary buffer of the given length in bytes is sent.
+
+=back
+
+=head3 Security
+
+The third element (payload length) is always in the range
+S<["1"..."64"]>.  Individual strings in the list always have length
+E<le> 4096 bytes (not including the terminating NUL byte).  If
+present, the binary buffer has length E<le> 67108864 bytes.
+
+List elements that are meant to be numbers should parse without any
+leading or trailing garbage, and every number in the basic protocol is
+representable as a C C<unsigned>.  (Note this does not apply to
+certain request and reply payloads, and there are list elements like
+the request ID which are not numbers at all.)
+
+Messages which do not conform to this indicate a fatal, unrecoverable
+error.  The external program should exit.  If nbdkit sees the external
+program write a non-conforming message, it will kill the external
+program and any further NBD client requests will receive C<EIO> error.
+
+=head3 Requests
+
+Requests are sent as the list:
+
+ [ request ID, buffer length, payload length, method [, arguments...] ]
+
+The method and arguments (fourth and subsequent elements of the list)
+are the same set of strings that are passed to L<nbdkit-sh-plugin(3)>
+scripts.  Examples:
+
+ [ "R1", "0", "3", "config", "size", "1048576" ]
+ [ "R2", "0", "1", "thread_model" ]
+ [ "R3", "0", "1", "get_ready" ]
+ [ "R4", "0", "4", "open", "false", "", "true" ]
+ [ "R5", "0", "2", "get_size", "myhandle" ]
+ [ "R6", "0", "2", "flush", "myhandle" ]
+
+=head3 pwrite request
+
+The C<"pwrite"> request message is special because the second element
+may be non-zero, in which case a binary buffer immediately follows the
+request message (containing the data to be written).
+
+=head3 Replies
+
+Replies are sent as the list:
+
+ [ request ID, buffer length, payload length, exit code [, reply] ]
+
+or for errors:
+
+ [ request ID, buffer length, payload length, "1", [, errno [, error]] ]
+
+The request ID should usually match the identifier of the
+corresponding request (but in some cases can be set to anything, see
+L</Thread models> below).  The buffer length is only used in replies
+to C<"pread"> requests, see next section.  The exit code should
+correspond to one of the codes in L<nbdkit-sh-plugin(3)/Exit codes>.
+
+There may be an additional list element for some methods.  For
+C<"get_size"> it would be the returned size of the disk, encoded as a
+string, in the same formats as supported by L<nbdkit-sh-plugin(3)>.
+
+Examples of replies corresponding to the request messages above:
+
+ [ "R1", "0", "1", "0" ]                  # reply to "config"
+ [ "R2", "0", "2", "0", "serialize_all_requests" ] # "thread_model"
+ [ "R3", "0", "1", "2" ]                  # reply to "get_ready"
+ [ "R4", "0", "2", "0", "myhandle" ]      # reply to "open"
+ [ "R5", "0" ,"2", "0", "1048576" ]       # reply to "get_size"
+ [ "R6", "0", "3", "1", "EIO", "I/O error" ] # reply to "flush"
+
+=head3 pread reply
+
+The reply message associated with an earlier C<"pread"> request is
+special (in the non-error case).  The second element of the reply
+message may be non-zero and the reply message is immediately followed
+by a binary buffer (containing the data read).
+
+=head3 Requests that the program does not understand
+
+If a program does not understand a request, it must send a reply with
+exit code 2, ie:
+
+ [ <request ID>, "0", "1", "2" ]
+
+=head3 Thread models
+
+The C<"thread_model"> request message is used by nbdkit to ask the
+program what thread model it supports, and all of the thread model
+strings as described in L<nbdkit-sh-plugin(3)> are supported.
+
+In addition, to make it easier to write external programs that use
+this plugin, if the thread model that you return is
+C<"serialize_connections"> or C<"serialize_all_requests">, then this
+plugin will operate in a simpler mode.
+
+For C<"serialize_all_requests">:
+
+=over 4
+
+=item *
+
+There is never more than one request in flight.
+
+=item *
+
+The request-ID element in replies is ignored (so you can put anything
+there).
+
+=back
+
+For C<"serialize_connections">, the above, plus:
+
+=over 4
+
+=item *
+
+There is only one client, so the handle may be ignored.
+
+=back
+
+This allows you to write external programs which are a simple read
+request + write reply loop that don't have to consider request-IDs or
+deal with parallelism, although they may be slower.
+
+=head1 PARAMETERS
+
+=over 4
+
+=item [B<script=>]PROGRAM
+
+The name or path of the external program to run.
+
+=back
+
+All other parameters are passed as C<"config"> requests to the program.
+
+=head1 ENVIRONMENT VARIABLES
+
+=over 4
+
+=item C<tmpdir>
+
+This contains the name of a temporary directory which can be used by
+the external program.  It is deleted when nbdkit exits.
+
+=back
+
+=head1 FILES
+
+=over 4
+
+=item F<$plugindir/nbdkit-proc-plugin.so>
+
+The plugin.
+
+Use C<nbdkit --dump-config> to find the location of C<$plugindir>.
+
+=back
+
+=head1 VERSION
+
+C<nbdkit-proc-plugin> first appeared in nbdkit 1.40.
+
+=head1 SEE ALSO
+
+L<nbdkit(1)>,
+L<nbdkit-plugin(3)>,
+L<nbdkit-sh-plugin(3)>,
+L<nbdkit-eval-plugin(3)>,
+L<nbdkit-cc-plugin(1)>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones
+
+=head1 COPYRIGHT
+
+Copyright Red Hat
diff --git a/plugins/sh/nbdkit-sh-plugin.pod b/plugins/sh/nbdkit-sh-plugin.pod
index 8b045547b5..5836ba2647 100644
--- a/plugins/sh/nbdkit-sh-plugin.pod
+++ b/plugins/sh/nbdkit-sh-plugin.pod
@@ -26,6 +26,9 @@ those will be more efficient (see L<nbdkit(1)> for a complete 
list).
 To use shell script fragments from the nbdkit command line (rather
 than a separate script) see L<nbdkit-eval-plugin(1)>.
 
+To use a persistent subprocess instead of forking on every request see
+L<nbdkit-proc-plugin(1)>.
+
 =head2 If you have been given an nbdkit sh plugin
 
 Assuming you have a shell script which is an nbdkit plugin, you run it
@@ -607,6 +610,7 @@ C<nbdkit-sh-plugin> first appeared in nbdkit 1.8.
 L<nbdkit(1)>,
 L<nbdkit-plugin(3)>,
 L<nbdkit-eval-plugin(1)>,
+L<nbdkit-proc-plugin(1)>,
 L<nbdkit-cc-plugin(1)>.
 
 =head1 AUTHORS
diff --git a/configure.ac b/configure.ac
index 241a869f31..955ee6c956 100644
--- a/configure.ac
+++ b/configure.ac
@@ -98,6 +98,7 @@ non_lang_plugins="\
         ones \
         partitioning \
         pattern \
+        proc \
         random \
         S3 \
         sparse-random \
@@ -1596,6 +1597,8 @@ AC_CONFIG_FILES([Makefile
                  plugins/partitioning/Makefile
                  plugins/pattern/Makefile
                  plugins/perl/Makefile
+                 plugins/proc/Makefile
+                 plugins/proc/examples/Makefile
                  plugins/python/Makefile
                  plugins/random/Makefile
                  plugins/rust/Makefile
diff --git a/plugins/proc/Makefile.am b/plugins/proc/Makefile.am
new file mode 100644
index 0000000000..9d96f3f2f8
--- /dev/null
+++ b/plugins/proc/Makefile.am
@@ -0,0 +1,95 @@
+# nbdkit
+# Copyright Red Hat
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of Red Hat nor the names of its contributors may be
+# used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+include $(top_srcdir)/common-rules.mk
+
+SUBDIRS = . examples
+
+EXTRA_DIST = nbdkit-proc-plugin.pod
+
+plugin_LTLIBRARIES = nbdkit-proc-plugin.la
+
+# This plugin shares most of the same sources as nbdkit-sh-plugin.  In
+# RHEL 7 we cannot add the C files from ../sh directly to SOURCES
+# because subdir-objects is broken.  Instead we create symlinks to
+# them.
+BUILT_SOURCES = \
+       methods.c \
+       tmpdir.c \
+       $(NULL)
+methods.c: $(srcdir)/../sh/methods.c
+       ln -f -s $(srcdir)/../sh/$@
+tmpdir.c: $(srcdir)/../sh/tmpdir.c
+       ln -f -s $(srcdir)/../sh/$@
+CLEANFILES += $(BUILT_SOURCES)
+
+nbdkit_proc_plugin_la_SOURCES = \
+       proc.c \
+       proc-protocol.c \
+       proc-protocol.h \
+       $(BUILT_SOURCES) \
+       $(srcdir)/../sh/methods.h \
+       $(srcdir)/../sh/subplugin.h \
+       $(top_srcdir)/include/nbdkit-plugin.h \
+       $(NULL)
+nbdkit_proc_plugin_la_CPPFLAGS = \
+       -I$(top_srcdir)/include \
+       -I$(top_builddir)/include \
+       -I$(top_srcdir)/plugins/sh \
+       -I$(top_srcdir)/common/include \
+       -I$(top_srcdir)/common/utils \
+       -DIN_PROC_PLUGIN=1 \
+       $(NULL)
+nbdkit_proc_plugin_la_CFLAGS = $(WARNINGS_CFLAGS)
+nbdkit_proc_plugin_la_LIBADD = \
+       $(top_builddir)/common/utils/libutils.la \
+       $(IMPORT_LIBRARY_ON_WINDOWS) \
+       $(NULL)
+nbdkit_proc_plugin_la_LDFLAGS = \
+       -module -avoid-version -shared $(NO_UNDEFINED_ON_WINDOWS) \
+       $(NULL)
+if USE_LINKER_SCRIPT
+nbdkit_proc_plugin_la_LDFLAGS += \
+       -Wl,--version-script=$(top_srcdir)/plugins/plugins.syms
+endif
+
+if HAVE_POD
+
+man_MANS = nbdkit-proc-plugin.1
+CLEANFILES += $(man_MANS)
+
+nbdkit-proc-plugin.1: nbdkit-proc-plugin.pod \
+               $(top_builddir)/podwrapper.pl
+       $(PODWRAPPER) --section=1 --man $@ \
+           --html $(top_builddir)/html/$@.html \
+           $<
+
+endif HAVE_POD
diff --git a/plugins/proc/examples/Makefile.am 
b/plugins/proc/examples/Makefile.am
new file mode 100644
index 0000000000..de4e2eeca4
--- /dev/null
+++ b/plugins/proc/examples/Makefile.am
@@ -0,0 +1,49 @@
+# nbdkit
+# Copyright Red Hat
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of Red Hat nor the names of its contributors may be
+# used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+include $(top_srcdir)/common-rules.mk
+
+noinst_PROGRAMS = simple
+
+simple_SOURCES = \
+       simple.c \
+       ../proc-protocol.c \
+       ../proc-protocol.h \
+       $(NULL)
+simple_CPPFLAGS = \
+       -I$(srcdir)/.. \
+       -I$(top_srcdir)/common/include \
+       -I$(top_srcdir)/common/utils \
+       $(NULL)
+simple_CFLAGS = $(WARNINGS_CFLAGS)
+simple_LDADD = \
+       $(top_builddir)/common/utils/libutils.la \
+       $(NULL)
diff --git a/plugins/proc/proc-protocol.h b/plugins/proc/proc-protocol.h
new file mode 100644
index 0000000000..582a004f39
--- /dev/null
+++ b/plugins/proc/proc-protocol.h
@@ -0,0 +1,81 @@
+/* nbdkit
+ * Copyright Red Hat
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Red Hat nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef NBDKIT_PROC_PROTOCOL_H
+#define NBDKIT_PROC_PROTOCOL_H
+
+#include <stdbool.h>
+
+struct proc_list {
+  char *request_id;             /* Request-ID */
+  char **payload;               /* Request or reply payload. */
+  char *buffer;                 /* Optional binary buffer. */
+  unsigned buffer_len;
+};
+
+/* Free all fields of proc_list and the struct itself. */
+extern void proc_list_free (struct proc_list *);
+
+/* Read a list of strings and optional binary buffer from the file,
+ * decoding the binary format that nbdkit-proc-plugin uses.  The file
+ * is usually 'stdin'.
+ *
+ * The first element is copied into the 'request_id' field.  The
+ * second element is used to determine if a binary buffer follows the
+ * input, and if so that is read into 'buffer' + 'buffer_len'.  The
+ * third element of the incoming list is parsed and checked and used
+ * to determine how many further payload elements to read.  The
+ * subsequent elements are copied into the 'payload' list followed by
+ * NULL.
+ *
+ * This function never returns an error.  If there is a problem with
+ * the input then it exits (see "Security" section in
+ * nbdkit-proc-plugin(1)).
+ *
+ * The caller should call 'proc_list_free' on the return value after
+ * use.
+ */
+extern struct proc_list *proc_read_list (FILE *fp);
+
+/* Write a list of strings and optional binary buffer to the file,
+ * encoding them in the binary format that nbdkit-proc-plugin uses.
+ * The file is usually 'stdout.
+ *
+ * This is the reverse of 'proc_read_list'.
+ *
+ * This function always returns 0.  If there is a problem with the
+ * list of strings or the file, then it exits (see "Security" in
+ * nbdkit-proc-plugin(1)).
+ */
+extern int proc_write_list (FILE *fp, const struct proc_list *list);
+
+#endif /* NBDKIT_PROC_PROTOCOL_H */
diff --git a/plugins/proc/examples/simple.c b/plugins/proc/examples/simple.c
new file mode 100644
index 0000000000..f3376be875
--- /dev/null
+++ b/plugins/proc/examples/simple.c
@@ -0,0 +1,125 @@
+/* nbdkit
+ * Copyright Red Hat
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Red Hat nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* A very simple example proc plugin which acts like a RAM disk.
+ *
+ * This example won't make any sense unless you read
+ * nbdkit-proc-plugin(1) and plugins/proc/proc-protocol.h first.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <assert.h>
+
+#include "proc-protocol.h"
+
+static char *disk;
+static size_t size;
+
+static void
+ok_reply (const char *request_id)
+{
+  const struct proc_list reply = {
+    .request_id = request_id,
+    .payload = { "0", NULL },
+  };
+  proc_write_list (stdout, &reply);
+}
+
+static void
+error_reply (const char *request_id, const char *errno, const char *error)
+{
+  const struct proc_list reply = {
+    .request_id = request_id,
+    .payload = { "1", errno, error, NULL },
+  };
+  proc_write_list (stdout, &reply);
+}
+
+static void
+missing_command_reply (const char *request_id)
+{
+  const struct proc_list reply = {
+    .request_id = request_id,
+    .payload = { "2", NULL },
+  };
+  proc_write_list (stdout, &reply);
+}
+
+static void
+do_config (const char *request_id,
+           const char *key, const char *value)
+{
+  if (strcmp (key, "size") == 0) {
+    if (sscanf (value, "%zu", &size) != 1) {
+      error_reply (request_id, "EINVAL", "cannot parse size parameter");
+      return;
+    }
+    ok_reply (request_id);
+    return;
+  }
+  else {
+    error_reply (request_id, "EINVAL", "unknown command");
+  }
+}
+
+int
+main (int argc, char *argv[])
+{
+  for (;;) {
+    struct proc_list *request;
+
+    /* Get the request from nbdkit. */
+    request = proc_read_list (stdin);
+    assert (request->payload[0]); /* proc_read_list ensures this */
+
+    /* Pick the request command. */
+    if (strcmp (request->payload[0], "config") == 0) {
+      /* Handle .config commands. */
+      assert (request->payload[1]);
+      assert (request->payload[2]);
+      do_config (request->request_id,
+                 request->payload[1], request->payload[2]);
+    }
+    else {
+      /* It's some other request we don't understand, reply with the
+       * missing command code.
+       */
+      missing_command_reply (request->request_id);
+    }
+
+    /* Free up the request message. */
+    proc_list_free (request);
+  }
+}
diff --git a/plugins/proc/proc-protocol.c b/plugins/proc/proc-protocol.c
new file mode 100644
index 0000000000..d11b01cbc1
--- /dev/null
+++ b/plugins/proc/proc-protocol.c
@@ -0,0 +1,290 @@
+/* nbdkit
+ * Copyright Red Hat
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Red Hat nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <errno.h>
+
+#include "string-vector.h"
+#include "nbdkit-string.h"
+
+#include "proc-protocol.h"
+
+/* If IN_PROC_PLUGIN is defined then we are running inside the plugin
+ * (ie. in nbdkit) and unlike what the documentation says, we do
+ * return an error on failures.  On errors (fatal or not) we call
+ * nbdkit_error, either the one defined in nbdkit, or a replacement
+ * supplied here.
+ */
+#if IN_PROC_PLUGIN
+extern void nbdkit_error (const char *msg, ...);
+#else /* !IN_PROC_PLUGIN */
+void
+nbdkit_error (const char *msg, ...)
+{
+  va_list args;
+  va_start (args, msg);
+  vfprintf (stderr, msg, args);
+  va_end (args);
+  fprintf (stderr, "\n");
+}
+#endif /* !IN_PROC_PLUGIN */
+
+void
+proc_list_free (struct proc_list *list)
+{
+  size_t i;
+
+  if (list) {
+    free (list->request_id);
+    for (i = 0; list->payload[i] != NULL; ++i)
+      free (list->payload[i]);
+    free (list->payload);
+    free (list->buffer);
+    free (list);
+  }
+}
+
+static int
+read_a_string (FILE *fp, string *ret)
+{
+  ret->len = 0;
+  while (ret->len <= 4096 /* allow it to grow to 4096+1 */) {
+    int c = fgetc (fp);
+    if (c == EOF) {
+      nbdkit_error ("proc-protocol: unexpected end of input");
+      return -1;
+    }
+    if (string_append (ret, (char)c) == -1) {
+      nbdkit_error ("realloc: %m");
+      return -1;
+    }
+    if (c == '\0')
+      return 0;
+  }
+  nbdkit_error ("proc-protocol: input string exceeded 4096 bytes");
+  return -1;
+}
+
+/* Try to copy what nbdkit_parse_unsigned does, although this is more strict. 
*/
+static int
+parse_unsigned (const char *str, unsigned *u)
+{
+  unsigned long r;
+  char *end;
+
+  if (str[0] < '0' || str[0] > '9') {
+    nbdkit_error ("proc-protocol: parse_unsigned: "
+                  "first character is not a number");
+    return -1;
+  }
+  errno = 0;
+  r = strtoul (str, &end, 0);
+#if UINT_MAX != ULONG_MAX
+  if (r > UINT_MAX)
+    errno = ERANGE;
+#endif
+  if (errno != 0) {
+    nbdkit_error ("proc-protocol: parse_unsigned: "
+                  "could not parse number: \"%s\": %m", str);
+    return -1;
+  }
+  if (end == str) {
+    nbdkit_error ("proc-protocol: parse_unsigned: "
+                  "empty string where we expected a number");
+    return -1;
+  }
+  if (*end) {
+    nbdkit_error ("proc-protocol: parse_unsigned: "
+                  "could not parse number: \"%s\": trailing garbage", str);
+    return -1;
+  }
+  *u = r;
+  return 0;
+}
+
+static int
+read_buffer (FILE *fp, string *ret, unsigned len)
+{
+  ret->len = 0;
+  if (string_reserve (ret, len) == -1) {
+    nbdkit_error ("realloc: %m");
+    return -1;
+  }
+  if (fread (ret, 1, len, fp) < len) {
+    nbdkit_error ("proc-protocol: unexpected end of input");
+    return -1;
+  }
+  ret->len = len;
+  return 0;
+}
+
+struct proc_list *
+proc_read_list (FILE *fp)
+{
+  struct proc_list *list;
+  string input = empty_vector;
+  string_vector payload = empty_vector;
+  unsigned payload_len, buffer_len, i;
+
+  list = calloc (1, sizeof *list);
+  if (list == NULL) {
+    nbdkit_error ("calloc: %m");
+    goto error;
+  }
+
+  /* Read the request-ID. */
+  if (read_a_string (fp, &input) == -1)
+    goto error;
+  list->request_id = input.ptr;
+  input = (string) empty_vector;
+
+  /* Read the binary buffer length. */
+  if (read_a_string (fp, &input) == -1)
+    goto error;
+  if (parse_unsigned (input.ptr, &buffer_len) == -1)
+    goto error;
+  if (buffer_len > 67108864) {
+    nbdkit_error ("proc-protocol: buffer length is too long");
+    goto error;
+  }
+
+  /* Read the payload length. */
+  if (read_a_string (fp, &input) == -1)
+    goto error;
+  if (parse_unsigned (input.ptr, &payload_len) == -1)
+    goto error;
+  if (payload_len < 1 || payload_len > 64) {
+    nbdkit_error ("proc-protocol: "
+                  "payload length must be in range [1..64]");
+    goto error;
+  }
+
+  /* Read the payload elements. */
+  if (string_vector_reserve (&payload, payload_len + 1 /* + NULL */) == -1) {
+    nbdkit_error ("realloc: %m");
+    goto error;
+  }
+  for (i = 0; i < payload_len; ++i) {
+    if (read_a_string (fp, &input) == -1)
+      goto error;
+    payload.ptr[i] = input.ptr;
+    input = (string) empty_vector;
+  }
+  payload.ptr[payload_len] = NULL;
+  list->payload = payload.ptr;
+  string_vector_reset (&payload);
+
+  /* Read the optional binary buffer. */
+  if (buffer_len > 0 &&
+      read_buffer (fp, &input, buffer_len) == -1)
+    goto error;
+  list->buffer = input.ptr;
+  list->buffer_len = buffer_len;
+  input = (string) empty_vector;
+
+  return list;
+
+ error:
+  if (list) proc_list_free (list);
+  string_reset (&input);
+  string_vector_reset (&payload);
+#if IN_PROC_PLUGIN
+  return NULL;
+#else
+  exit (EXIT_FAILURE);
+#endif
+}
+
+static int
+write_buffer (FILE *fp, const void *data, size_t len)
+{
+  if (fwrite (data, 1, len, fp) < len) {
+    nbdkit_error ("proc-protocol: write_buffer: %m");
+    return -1;
+  }
+  return 0;
+}
+
+static int
+write_a_string (FILE *fp, const char *str)
+{
+  return write_buffer (fp, str, strlen (str) + 1 /* also write \0 */);
+}
+
+int
+proc_write_list (FILE *fp, const struct proc_list *list)
+{
+  size_t i;
+  unsigned len;
+  char str[64];
+
+  /* Request-ID. */
+  if (write_a_string (fp, list->request_id) == -1)
+    goto error;
+
+  /* Optional binary buffer length. */
+  len = list->buffer_len;
+  snprintf (str, sizeof str, "%u", len);
+  if (write_a_string (fp, str) == -1)
+    goto error;
+
+  /* Number of payload elements. */
+  for (len = 0; list->payload[len] != NULL; ++len)
+    ;
+  snprintf (str, sizeof str, "%u", len);
+  if (write_a_string (fp, str) == -1)
+    goto error;
+
+  /* Payload. */
+  for (i = 0; i < len; ++i) {
+    if (write_a_string (fp, list->payload[i]) == -1)
+      goto error;
+  }
+
+  /* Optional binary buffer. */
+  if (list->buffer_len > 0 &&
+      write_buffer (fp, list->buffer, list->buffer_len) == -1)
+    goto error;
+
+  return 0;
+
+ error:
+#if IN_PROC_PLUGIN
+  return -1;
+#else
+  exit (EXIT_FAILURE);
+#endif
+}
diff --git a/plugins/proc/proc.c b/plugins/proc/proc.c
new file mode 100644
index 0000000000..5a444fc599
--- /dev/null
+++ b/plugins/proc/proc.c
@@ -0,0 +1,314 @@
+/* nbdkit
+ * Copyright Red Hat
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Red Hat nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+#define NBDKIT_API_VERSION 2
+
+#include <nbdkit-plugin.h>
+
+#include "cleanup.h"
+#include "vector.h"
+
+#include "methods.h"
+#include "subplugin.h"
+#include "tmpdir.h"
+
+static char *script;
+static char *magic_config_key;
+
+static const char *get_script (const char *method);
+
+static exit_code invoke (const char **argv)
+  __attribute__ ((__nonnull__ (1)));
+static exit_code invoke_read (string *rbuf, const char **argv)
+  __attribute__ ((__nonnull__ (1, 2)));
+static exit_code invoke_write (const char *wbuf, size_t wbuflen,
+                               const char **argv)
+  __attribute__ ((__nonnull__ (1, 3)));
+
+/* This abstracts the nbdkit-proc-plugin sub-plugin. */
+struct subplugin sub = {
+  get_script,
+  invoke,
+  invoke_read,
+  invoke_write,
+};
+
+static const char *
+get_script (const char *method)
+{
+  return script;                /* It's never actually used. */
+}
+
+static exit_code
+invoke (const char **argv)
+{
+  XXX;
+}
+
+static exit_code
+invoke_read (string *rbuf, const char **argv)
+{
+  XXX;
+}
+
+static exit_code
+invoke_write (const char *wbuf, size_t wbuflen,
+              const char **argv)
+{
+  XXX;
+}
+
+static void
+proc_load (void)
+{
+  tmpdir_load ();
+}
+
+static void
+proc_unload (void)
+{
+  const char *method = "unload";
+
+  /* Run the unload method.  Ignore all errors. */
+  if (script) {
+    const char *args[] = { script, method, NULL };
+
+    sub.call (args);
+  }
+
+  tmpdir_unload ();
+  free (script);
+  free (magic_config_key);
+}
+
+static int
+proc_config (const char *key, const char *value)
+{
+  if (!script) {
+    /* The first parameter MUST be "script". */
+    if (strcmp (key, "script") != 0) {
+      nbdkit_error ("the first parameter must be script=/path/to/script");
+      return -1;
+    }
+
+    script = nbdkit_realpath (value);
+    if (script == NULL)
+      return -1;
+
+    /* Call the load method. */
+    const char *args[] = { script, "load", NULL };
+    switch (sub.call (args)) {
+    case OK:
+    case MISSING:
+      break;
+
+    case ERROR:
+      return -1;
+
+    case RET_FALSE:
+      nbdkit_error ("%s: %s method returned unexpected code (3/false)",
+                    script, "load");
+      errno = EIO;
+      return -1;
+
+    default: abort ();
+    }
+
+    /* Call the magic_config_key method if it exists. */
+    const char *args2[] = { script, "magic_config_key", NULL };
+    CLEANUP_FREE_STRING string s = empty_vector;
+    switch (sub.call_read (&s, args2)) {
+    case OK:
+      if (s.len > 0 && s.ptr[s.len-1] == '\n')
+        s.ptr[s.len-1] = '\0';
+      magic_config_key = strdup (s.ptr);
+      if (magic_config_key == NULL) {
+        nbdkit_error ("strdup: %m");
+        return -1;
+      }
+      break;
+
+    case MISSING:
+      break;
+
+    case ERROR:
+      return -1;
+
+    case RET_FALSE:
+      nbdkit_error ("%s: %s method returned unexpected code (3/false)",
+                    script, "magic_config_key");
+      errno = EIO;
+      return -1;
+
+    default: abort ();
+    }
+  }
+  else {
+    /* If the script sets a magic_config_key then it's possible that
+     * we will be called here with key == "script" (which is the
+     * plugin.magic_config_key).  If that happens then swap in the
+     * script magic_config_key as the key.  However if the script
+     * didn't define a magic_config_key then it's an error, emulating
+     * the behaviour of the core server.
+     */
+    if (strcmp (key, "script") == 0) {
+      if (magic_config_key)
+        key = magic_config_key;
+      else {
+        nbdkit_error ("%s: expecting key=value on the command line but got: "
+                      "%s\n",
+                      script, value);
+        return -1;
+      }
+    }
+
+    const char *args[] = { script, "config", key, value, NULL };
+    switch (sub.call (args)) {
+    case OK:
+      return 0;
+
+    case MISSING:
+      /* Emulate what core nbdkit does if a config callback is NULL. */
+      nbdkit_error ("%s: this plugin does not need command line configuration",
+                    script);
+      return -1;
+
+    case ERROR:
+      return -1;
+
+    case RET_FALSE:
+      nbdkit_error ("%s: %s method returned unexpected code (3/false)",
+                    script, "config");
+      errno = EIO;
+      return -1;
+
+    default: abort ();
+    }
+  }
+
+  return 0;
+}
+
+static int
+proc_config_complete (void)
+{
+  const char *args[] = { script, "config_complete", NULL };
+
+  if (!script) {
+    nbdkit_error ("missing script parameter");
+    return -1;
+  }
+
+  switch (sub.call (args)) {
+  case OK:
+  case MISSING:
+    return 0;
+
+  case ERROR:
+    return -1;
+
+  case RET_FALSE:
+    nbdkit_error ("%s: %s method returned unexpected code (3/false)",
+                  script, "config_complete");
+    errno = EIO;
+    return -1;
+
+  default: abort ();
+  }
+}
+
+#define proc_config_help \
+  "script=<FILENAME>     (required) The shell script to run.\n" \
+  "[other arguments may be used by the plugin that you load]"
+
+/* Default to simple mode.  External programs can override this. */
+#define THREAD_MODEL NBDKIT_THREAD_MODEL_SERIALIZE_ALL_REQUESTS
+
+static struct nbdkit_plugin plugin = {
+  .name               = "proc",
+  .version            = PACKAGE_VERSION,
+  .load               = proc_load,
+  .unload             = proc_unload,
+
+  .dump_plugin        = sh_dump_plugin,
+
+  .config             = proc_config,
+  .config_complete    = proc_config_complete,
+  .config_help        = proc_config_help,
+  .magic_config_key   = "script",
+  .thread_model       = sh_thread_model,
+  .get_ready          = sh_get_ready,
+  .after_fork         = sh_after_fork,
+
+  .preconnect         = sh_preconnect,
+  .list_exports       = sh_list_exports,
+  .default_export     = sh_default_export,
+  .open               = sh_open,
+  .close              = sh_close,
+
+  .export_description = sh_export_description,
+  .get_size           = sh_get_size,
+  .block_size         = sh_block_size,
+  .can_write          = sh_can_write,
+  .can_flush          = sh_can_flush,
+  .is_rotational      = sh_is_rotational,
+  .can_trim           = sh_can_trim,
+  .can_zero           = sh_can_zero,
+  .can_extents        = sh_can_extents,
+  .can_fua            = sh_can_fua,
+  .can_multi_conn     = sh_can_multi_conn,
+  .can_cache          = sh_can_cache,
+  .can_fast_zero      = sh_can_fast_zero,
+
+  .pread              = sh_pread,
+  .pwrite             = sh_pwrite,
+  .flush              = sh_flush,
+  .trim               = sh_trim,
+  .zero               = sh_zero,
+  .extents            = sh_extents,
+  .cache              = sh_cache,
+
+  .errno_is_preserved = 1,
+};
+
+NBDKIT_REGISTER_PLUGIN (plugin)
diff --git a/.gitignore b/.gitignore
index a05231b859..c59cc5f262 100644
--- a/.gitignore
+++ b/.gitignore
@@ -106,6 +106,8 @@ plugins/*/*.3
 /plugins/golang/examples/*/nbdkit-*-plugin.so
 /plugins/ocaml/nbdkit-ocamlexample-plugin.so
 /plugins/ondemand/default-command.c
+/plugins/proc/methods.c
+/plugins/proc/tmpdir.c
 /plugins/rust/Cargo.lock
 /plugins/rust/target
 /plugins/S3/nbdkit-S3-plugin
-- 
2.44.0
_______________________________________________
Libguestfs mailing list -- guestfs@lists.libguestfs.org
To unsubscribe send an email to guestfs-le...@lists.libguestfs.org

Reply via email to