nbdkit-golang-plugin - writing nbdkit plugins in Go
nbdkit /path/to/plugin.so [arguments...]
This manual page describes how to write nbdkit plugins in compiled Golang code. Go plugins are compiled to *.so files (the same as plugins written in C) and are used in the same way.
Go plugins do not support nbdkit forking into the background (ie. the default behaviour if -f is not used; or if --run is used). Therefore you must use the -f option (or options which imply it, see nbdkit(1)).
Fixing this would require changes to the Go runtime. For details see https://gitlab.com/nbdkit/nbdkit/-/issues/29#note_2370463738
For examples of plugins written in Go, see: https://gitlab.com/nbdkit/nbdkit/blob/master/plugins/golang/examples
minimal/minimal.go is the smallest possible plugin, implementing only the required callbacks (Open, GetSize and PRead). Other examples show other nbdkit features.
Broadly speaking, Golang nbdkit plugins work like C ones, so you should read nbdkit-plugin(3) first.
Most Golang nbdkit plugins should start with:
package main
import (
"C"
"libguestfs.org/nbdkit"
"unsafe"
)
Plugins have to be main packages, because that's how Golang shared libraries work.
var pluginName = "myname"
type MyNamePlugin struct {
nbdkit.Plugin
}
The plugin struct is used to define the global callbacks, like Load, Config and GetReady.
type MyNameConnection struct {
nbdkit.Connection
}
The connection struct represents a client connection and is used to define connection callbacks like GetSize and PRead.
What connects Plugin and Connection is the Open callback which is called when the client has connected and where you should return a new instance of your connection struct. For example:
func (p *MyNamePlugin) Load() {
// global callback used for initializing the plugin
}
func (p *MyNamePlugin) Open(readonly bool) (nbdkit.ConnectionInterface, error) {
// new client has connected
return &MyNameConnection{}, nil
}
func (c *MyNameConnection) GetSize() (uint64, error) {
// called per-connection
return virtual_size, nil
}
func (c *MyNameConnection) PRead(buf []byte, offset uint64,
flags uint32) error {
// copy data into buf here
return nil
}
You can store per-connection data in the connection struct. Global data could be stored as global variables or kept in the plugin struct as you wish.
Most plugins should finish off with this boilerplate:
//export plugin_init
func plugin_init() unsafe.Pointer {
// If your plugin needs to do any initialization, you can
// either put it here or implement a Load() method.
// ...
// Then you must call the following function.
return nbdkit.PluginInitialize(pluginName, &MyNamePlugin{})
}
// This is never(?) called, but must exist.
func main() {}
Compile the plugin to a shared library:
go build -o nbdkit-mygolang-plugin.so -buildmode=c-shared
This builds the shared object (*.so) which is the plugin, and also a header file (*.h) which is irrelevant and you can delete.
Can* callbacksImportant: If you implement PWrite it will not be called unless you also implement a CanWrite callback that returns true. (This is different from plugins written in C or other languages).
The same applies to Flush (CanFlush), Trim (CanTrim), Zero (CanZero) and Extents (CanExtents).
func (c *MyNameConnection) CanWrite() (bool, error) {
return true, nil
}
func (c *MyNameConnection) PWrite(buf []byte, offset uint64,
flags uint32) error {
// ...
}
CanFUA callback and writesIf you do not define CanFUA then the default mode will be FUANone (NBDKIT_FUA_NONE). This means that FUA requests will never be sent, nor emulated.
Therefore if you are implementing writes you generally need to define CanFUA:
If your PWrite (and other write-like functions such as Trim and Zero) correctly implement the flags parameter FlagFUA, then CanFUA should return FUANative.
If they ignore flags but you have defined a Flush function, then CanFUA should return FUAEmulate. This causes nbdkit to turn FUA requests into Flush.
If your plugin does neither of the above, CanFUA should return FUANone.
The following callbacks are not yet implemented:
can_fast_zerothread_modelAt the moment the thread model is always NBDKIT_THREAD_MODEL_PARALLEL.
Golang plugins first appeared in nbdkit 1.20.
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.