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