nbdkit-ocaml-plugin - writing nbdkit plugins in OCaml
nbdkit /path/to/plugin.so [arguments...]
nbdkit plugin [arguments...]
This manual page describes how to write nbdkit plugins in natively compiled OCaml code. This requires OCaml ≥ 4.03.
For an example plugin written in OCaml, see: https://gitlab.com/nbdkit/nbdkit/blob/master/plugins/ocaml/example.ml
Broadly speaking, OCaml nbdkit plugins work like C ones, so you should read nbdkit-plugin(3) first.
You should also look at NBDKit(3) which describes the plugin interface for OCaml plugins.
Your OCaml code should call NBDKit.register_plugin
like this when the plugin is loaded:
NBDKit.register_plugin
~name: "myplugin"
~version: "1.0"
~open_connection: myplugin_open
~get_size: myplugin_get_size
~pread: myplugin_pread
~thread_model:
(fun () -> NBDKit.THREAD_MODEL_SERIALIZE_ALL_REQUESTS)
(* etc *)
()
~name
, ~open_connection
, ~get_size
and ~pread
are required. All other parameters are optional.
OCaml nbdkit plugins are natively compiled into shared object (*.so
) files which nbdkit loads like regular C plugins.
After writing your OCaml plugin (myplugin.ml
), compile and link it using this command:
ocamlopt.opt -output-obj -runtime-variant _pic \
-o nbdkit-myplugin-plugin.so \
-I +unix -I +threads unix.cmxa threads.cmxa \
NBDKit.cmx myplugin.ml \
-cclib -lnbdkitocaml
You can then use nbdkit-myplugin-plugin.so
as an nbdkit plugin (see nbdkit(1), nbdkit-plugin(3)):
nbdkit ./nbdkit-myplugin-plugin.so [args ...]
or if the .so
file is installed in the $plugindir
directory:
nbdkit myplugin [args ...]
Your open_connection
callback can return an OCaml value of any type. The same value is passed back to the per-connection callbacks like get_size
and pread
.
Typically (although this is not a requirement) you define your own handle struct in your plugin:
type handle = {
(* any per-connection data you want to store goes here *)
h_id : int; (* this is just an example field *)
h_readonly : bool;
}
let id = ref 0
let myplugin_open readonly =
(* return a newly allocated handle *)
incr id;
{ h_id = !id; h_readonly = readonly }
let myplugin_get_size handle =
printf "handle ID = %d\n" handle.h_id;
(* ... *)
If you don't need to store per-connection data, open_connection
can return ()
.
The pread
and pwrite
callbacks are declared:
val pread: 'a -> buf -> int64 -> flags -> unit
val pwrite: 'a -> buf -> int64 -> flags -> unit
NBDKit.buf
is an OCaml Bigarray wrapping the C buffer:
type buf =
(char, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t
This allows direct, zero-copy access to the underlying buffer using the functions in the OCaml stdlib Bigarray.Array1(3) module (https://v2.ocaml.org/api/Bigarray.Array1.html).
There are also helper functions in the NBDKit(3) module for fast blitting between buf
and OCaml string
and bytes
.
This type is compatible with the bigstring and bigstringaf libraries.
Note that the length of the data to read or write is implicit in buf
. Use NBDKit.buf_len buf
to get this.
Plugins can return errors from methods by raising an exception.
If you need to control which errno is sent back to the client you have to call NBDKit.set_error
before raising the exception.
Note if you call some function in the OCaml Unix
module or another library which fails, then the errno of the failing system call will not be returned to the client. You have to catch the exception and call NBDKit.set_error
before re-raising the exception if you need to control this.
One of the optional parameters of NBDKit.register_plugin
is ~thread_model
, which must return one of the values in the table below:
NBDKit.THREAD_MODEL_SERIALIZE_CONNECTIONS
NBDKit.THREAD_MODEL_SERIALIZE_ALL_REQUESTS
NBDKit.THREAD_MODEL_SERIALIZE_REQUESTS
NBDKit.THREAD_MODEL_PARALLEL
If this optional parameter is not provided, the thread model defaults to NBDKit.THREAD_MODEL_PARALLEL
. The garbage collector lock is acquired when calling into OCaml code and because of this callbacks are never truly concurrent.
For more information on thread models, see "THREADS" in nbdkit-plugin(3).
You can add debugging messages which are printed only when nbdkit is in verbose mode by calling:
NBDKit.debug fs [...]
This function works like Printf.printf
.
You can use debug flags in OCaml plugins. See plugins/ocaml/example-debug-flag.c in the nbdkit sources, and "Debug Flags" in nbdkit-plugin(3).
As with all nbdkit plugins, you can add a dump_plugin
callback in order to print additional facts about your plugin. The OCaml wrapper also adds some additional fields to help identify which version of OCaml was used:
$ nbdkit your-ocaml-plugin.so --dump-plugin
...
ocaml_version_major=4
ocaml_version_minor=5
ocaml_version=4.05.0
Using nbdkit-cc-plugin(1) it is possible to write OCaml plugins which are compiled just before use, and so appear to work more like scripts.
OCaml plugins first appeared in nbdkit 1.2.
NBDKit(3), nbdkit(1), nbdkit-plugin(3), Bigarray.Array1(3), ocamlopt(1).
Richard W.M. Jones
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.