Richard W.M. Jones
2020-Apr-24 14:18 UTC
[Libguestfs] [PATCH nbdkit] golang: Pass Plugin and Connection by reference not value.
For the current set of examples this doesn't matter. This also adds another example where we use the Connection to store data, just to check that actually works. Thanks: James @ purpleidea, Dan Berrangé --- plugins/golang/nbdkit-golang-plugin.pod | 10 +- plugins/golang/Makefile.am | 9 + plugins/golang/examples/disk/disk.go | 168 ++++++++++++++++++ .../golang/examples/dump-plugin/dumpplugin.go | 8 +- plugins/golang/examples/minimal/minimal.go | 6 +- plugins/golang/examples/ramdisk/ramdisk.go | 18 +- .../src/libguestfs.org/nbdkit/nbdkit.go | 42 ++--- plugins/golang/test/test.go | 14 +- 8 files changed, 226 insertions(+), 49 deletions(-) diff --git a/plugins/golang/nbdkit-golang-plugin.pod b/plugins/golang/nbdkit-golang-plugin.pod index b15bb481..90732289 100644 --- a/plugins/golang/nbdkit-golang-plugin.pod +++ b/plugins/golang/nbdkit-golang-plugin.pod @@ -62,16 +62,16 @@ C<GetSize> and C<PRead>. What connects the two is the C<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 MyPlugin) Load() { + func (p *MyPlugin) Load() { // global callback used for initializing the plugin } - func (p MyPlugin) Open(readonly bool) (nbdkit.ConnectionInterface, error) { + func (p *MyPlugin) Open(readonly bool) (nbdkit.ConnectionInterface, error) { // new client has connected return &MyConnection{}, nil } - func (c MyConnection) GetSize() (uint64, error) { + func (c *MyConnection) GetSize() (uint64, error) { // called per-client return virtual_size, nil } @@ -106,11 +106,11 @@ unless you also implement a C<CanWrite> callback that returns true. The same applies to C<Flush> (C<CanFlush>), C<Trim> (C<CanTrim>) and C<Zero> (C<CanZero>). - func (c MyConnection) CanWrite() (bool, error) { + func (c *MyConnection) CanWrite() (bool, error) { return true, nil } - func (c MyConnection) PWrite(buf []byte, offset uint64, + func (c *MyConnection) PWrite(buf []byte, offset uint64, flags uint32) error { // ... } diff --git a/plugins/golang/Makefile.am b/plugins/golang/Makefile.am index 74ad4a72..91711724 100644 --- a/plugins/golang/Makefile.am +++ b/plugins/golang/Makefile.am @@ -42,6 +42,7 @@ EXTRA_DIST = \ $(plugin_sources) \ config-test.go \ dump-plugin-examples.sh \ + examples/disk/disk.go \ examples/dump-plugin/dumpplugin.go \ examples/minimal/minimal.go \ examples/ramdisk/ramdisk.go \ @@ -58,11 +59,19 @@ if HAVE_GOLANG # Examples. noinst_DATA = \ + examples/disk/nbdkit-godisk-plugin.so \ examples/dump-plugin/nbdkit-godump-plugin.so \ examples/minimal/nbdkit-gominimal-plugin.so \ examples/ramdisk/nbdkit-goramdisk-plugin.so \ $(NULL) +examples/disk/nbdkit-godisk-plugin.so: \ + $(plugin_sources) examples/disk/disk.go + cd examples/disk && \ + PKG_CONFIG_PATH="$(abs_top_builddir)/server/local$${PKG_CONFIG_PATH:+:$$PKG_CONFIG_PATH}" \ + GOPATH="$(abs_builddir)" \ + $(GOLANG) build -o nbdkit-godisk-plugin.so -buildmode=c-shared + examples/dump-plugin/nbdkit-godump-plugin.so: \ $(plugin_sources) examples/dump-plugin/dumpplugin.go cd examples/dump-plugin && \ diff --git a/plugins/golang/examples/disk/disk.go b/plugins/golang/examples/disk/disk.go new file mode 100644 index 00000000..37f449e0 --- /dev/null +++ b/plugins/golang/examples/disk/disk.go @@ -0,0 +1,168 @@ +/* Example plugin. + * Copyright (C) 2013-2020 Red Hat Inc. + * + * 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. + */ + +package main + +import ( + "C" + "io/ioutil" + "libguestfs.org/nbdkit" + "os" + "strconv" + "unsafe" +) + +var pluginName = "disk" + +type DiskPlugin struct { + nbdkit.Plugin +} + +type DiskConnection struct { + nbdkit.Connection + fd *os.File +} + +var size uint64 +var size_set = false + +func (p *DiskPlugin) Config(key string, value string) error { + if key == "size" { + var err error + size, err = strconv.ParseUint(value, 0, 64) + if err != nil { + return err + } + size_set = true + return nil + } else { + return nbdkit.PluginError{Errmsg: "unknown parameter"} + } +} + +func (p *DiskPlugin) ConfigComplete() error { + if !size_set { + return nbdkit.PluginError{Errmsg: "size parameter is required"} + } + return nil +} + +func (p *DiskPlugin) Open(readonly bool) (nbdkit.ConnectionInterface, error) { + // Open a temporary file. + fd, err := ioutil.TempFile("/var/tmp", "nbdkitdisk") + if err != nil { + return nil, err + } + os.Remove(fd.Name()) + + // Truncate it to the right size. + err = fd.Truncate(int64(size)) + if err != nil { + return nil, err + } + + // Store the file descriptor of the temporary file in the + // Connection struct. + return &DiskConnection{fd: fd}, nil +} + +func (c *DiskConnection) Close() { + c.fd.Close() +} + +func (c *DiskConnection) GetSize() (uint64, error) { + // Return the size of the disk. We could just return the + // global "size" here, but make the example more interesting. + info, err := c.fd.Stat() + if err != nil { + return 0, err + } + return uint64(info.Size()), nil +} + +// Multi-conn is NOT safe because each client sees a different disk. +func (c *DiskConnection) CanMultiConn() (bool, error) { + return false, nil +} + +func (c *DiskConnection) PRead(buf []byte, offset uint64, + flags uint32) error { + n, err := c.fd.ReadAt(buf, int64(offset)) + if err != nil { + return err + } + // NBD requests must always read/write the whole requested + // amount, or else fail. Actually we should loop here (XXX). + if n != len(buf) { + return nbdkit.PluginError{Errmsg: "short read"} + } + return nil +} + +// Note that CanWrite is required in golang plugins, otherwise PWrite +// will never be called. +func (c *DiskConnection) CanWrite() (bool, error) { + return true, nil +} + +func (c *DiskConnection) PWrite(buf []byte, offset uint64, + flags uint32) error { + n, err := c.fd.WriteAt(buf, int64(offset)) + if err != nil { + return err + } + // NBD requests must always read/write the whole requested + // amount, or else fail. Actually we should loop here (XXX). + if n != len(buf) { + return nbdkit.PluginError{Errmsg: "short write"} + } + return nil +} + +//---------------------------------------------------------------------- +// +// The boilerplate below this line is required by all golang plugins, +// as well as importing "C" and "unsafe" modules at the top of the +// file. + +//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, &DiskPlugin{}) +} + +// This is never(?) called, but must exist. +func main() {} diff --git a/plugins/golang/examples/dump-plugin/dumpplugin.go b/plugins/golang/examples/dump-plugin/dumpplugin.go index 518be078..d30ec3f1 100644 --- a/plugins/golang/examples/dump-plugin/dumpplugin.go +++ b/plugins/golang/examples/dump-plugin/dumpplugin.go @@ -51,19 +51,19 @@ type DumpConnection struct { var size uint64 = 1024 * 1024 -func (p DumpPlugin) DumpPlugin() { +func (p *DumpPlugin) DumpPlugin() { fmt.Println("golang_dump_plugin=1") } -func (p DumpPlugin) Open(readonly bool) (nbdkit.ConnectionInterface, error) { +func (p *DumpPlugin) Open(readonly bool) (nbdkit.ConnectionInterface, error) { return &DumpConnection{}, nil } -func (c DumpConnection) GetSize() (uint64, error) { +func (c *DumpConnection) GetSize() (uint64, error) { return size, nil } -func (c DumpConnection) PRead(buf []byte, offset uint64, +func (c *DumpConnection) PRead(buf []byte, offset uint64, flags uint32) error { for i := 0; i < len(buf); i++ { buf[i] = 0 diff --git a/plugins/golang/examples/minimal/minimal.go b/plugins/golang/examples/minimal/minimal.go index e010bfcb..0f862889 100644 --- a/plugins/golang/examples/minimal/minimal.go +++ b/plugins/golang/examples/minimal/minimal.go @@ -50,15 +50,15 @@ type MinimalConnection struct { var size uint64 = 1024 * 1024 -func (p MinimalPlugin) Open(readonly bool) (nbdkit.ConnectionInterface, error) { +func (p *MinimalPlugin) Open(readonly bool) (nbdkit.ConnectionInterface, error) { return &MinimalConnection{}, nil } -func (c MinimalConnection) GetSize() (uint64, error) { +func (c *MinimalConnection) GetSize() (uint64, error) { return size, nil } -func (c MinimalConnection) PRead(buf []byte, offset uint64, +func (c *MinimalConnection) PRead(buf []byte, offset uint64, flags uint32) error { for i := 0; i < len(buf); i++ { buf[i] = 0 diff --git a/plugins/golang/examples/ramdisk/ramdisk.go b/plugins/golang/examples/ramdisk/ramdisk.go index c848f575..78cb1f4c 100644 --- a/plugins/golang/examples/ramdisk/ramdisk.go +++ b/plugins/golang/examples/ramdisk/ramdisk.go @@ -53,7 +53,7 @@ var size uint64 var size_set = false var disk []byte -func (p RAMDiskPlugin) Config(key string, value string) error { +func (p *RAMDiskPlugin) Config(key string, value string) error { if key == "size" { var err error size, err = strconv.ParseUint(value, 0, 64) @@ -67,33 +67,33 @@ func (p RAMDiskPlugin) Config(key string, value string) error { } } -func (p RAMDiskPlugin) ConfigComplete() error { +func (p *RAMDiskPlugin) ConfigComplete() error { if !size_set { return nbdkit.PluginError{Errmsg: "size parameter is required"} } return nil } -func (p RAMDiskPlugin) GetReady() error { +func (p *RAMDiskPlugin) GetReady() error { // Allocate the RAM disk. disk = make([]byte, size) return nil } -func (p RAMDiskPlugin) Open(readonly bool) (nbdkit.ConnectionInterface, error) { +func (p *RAMDiskPlugin) Open(readonly bool) (nbdkit.ConnectionInterface, error) { return &RAMDiskConnection{}, nil } -func (c RAMDiskConnection) GetSize() (uint64, error) { +func (c *RAMDiskConnection) GetSize() (uint64, error) { return size, nil } // Clients are allowed to make multiple connections safely. -func (c RAMDiskConnection) CanMultiConn() (bool, error) { +func (c *RAMDiskConnection) CanMultiConn() (bool, error) { return true, nil } -func (c RAMDiskConnection) PRead(buf []byte, offset uint64, +func (c *RAMDiskConnection) PRead(buf []byte, offset uint64, flags uint32) error { copy(buf, disk[offset:int(offset)+len(buf)]) return nil @@ -101,11 +101,11 @@ func (c RAMDiskConnection) PRead(buf []byte, offset uint64, // Note that CanWrite is required in golang plugins, otherwise PWrite // will never be called. -func (c RAMDiskConnection) CanWrite() (bool, error) { +func (c *RAMDiskConnection) CanWrite() (bool, error) { return true, nil } -func (c RAMDiskConnection) PWrite(buf []byte, offset uint64, +func (c *RAMDiskConnection) PWrite(buf []byte, offset uint64, flags uint32) error { copy(disk[offset:int(offset)+len(buf)], buf) return nil diff --git a/plugins/golang/src/libguestfs.org/nbdkit/nbdkit.go b/plugins/golang/src/libguestfs.org/nbdkit/nbdkit.go index 155034d9..8e9e9bbd 100644 --- a/plugins/golang/src/libguestfs.org/nbdkit/nbdkit.go +++ b/plugins/golang/src/libguestfs.org/nbdkit/nbdkit.go @@ -145,83 +145,83 @@ type ConnectionInterface interface { type Plugin struct{} type Connection struct{} -func (p Plugin) Load() { +func (p *Plugin) Load() { } -func (p Plugin) Unload() { +func (p *Plugin) Unload() { } -func (p Plugin) DumpPlugin() { +func (p *Plugin) DumpPlugin() { } -func (p Plugin) Config(key string, value string) error { +func (p *Plugin) Config(key string, value string) error { return nil } -func (p Plugin) ConfigComplete() error { +func (p *Plugin) ConfigComplete() error { return nil } -func (p Plugin) GetReady() error { +func (p *Plugin) GetReady() error { return nil } -func (p Plugin) PreConnect(readonly bool) error { +func (p *Plugin) PreConnect(readonly bool) error { return nil } -func (p Plugin) Open(readonly bool) (ConnectionInterface, error) { +func (p *Plugin) Open(readonly bool) (ConnectionInterface, error) { panic("plugin must implement Open()") } -func (c Connection) Close() { +func (c *Connection) Close() { } -func (c Connection) GetSize() (uint64, error) { +func (c *Connection) GetSize() (uint64, error) { panic("plugin must implement GetSize()") } -func (c Connection) CanWrite() (bool, error) { +func (c *Connection) CanWrite() (bool, error) { return false, nil } -func (c Connection) CanFlush() (bool, error) { +func (c *Connection) CanFlush() (bool, error) { return false, nil } -func (c Connection) IsRotational() (bool, error) { +func (c *Connection) IsRotational() (bool, error) { return false, nil } -func (c Connection) CanTrim() (bool, error) { +func (c *Connection) CanTrim() (bool, error) { return false, nil } -func (c Connection) CanZero() (bool, error) { +func (c *Connection) CanZero() (bool, error) { return false, nil } -func (c Connection) CanMultiConn() (bool, error) { +func (c *Connection) CanMultiConn() (bool, error) { return false, nil } -func (c Connection) PRead(buf []byte, offset uint64, flags uint32) error { +func (c *Connection) PRead(buf []byte, offset uint64, flags uint32) error { panic("plugin must implement PRead()") } -func (c Connection) PWrite(buf []byte, offset uint64, flags uint32) error { +func (c *Connection) PWrite(buf []byte, offset uint64, flags uint32) error { panic("plugin CanWrite returns true, but no PWrite() function") } -func (c Connection) Flush(flags uint32) error { +func (c *Connection) Flush(flags uint32) error { panic("plugin CanFlush returns true, but no Flush() function") } -func (c Connection) Trim(count uint32, offset uint64, flags uint32) error { +func (c *Connection) Trim(count uint32, offset uint64, flags uint32) error { panic("plugin CanTrim returns true, but no Trim() function") } -func (c Connection) Zero(count uint32, offset uint64, flags uint32) error { +func (c *Connection) Zero(count uint32, offset uint64, flags uint32) error { panic("plugin CanZero returns true, but no Zero() function") } diff --git a/plugins/golang/test/test.go b/plugins/golang/test/test.go index 7186ffa8..e585a971 100644 --- a/plugins/golang/test/test.go +++ b/plugins/golang/test/test.go @@ -52,15 +52,15 @@ type TestConnection struct { var size uint64 var size_set = false -func (p TestPlugin) Load() { +func (p *TestPlugin) Load() { nbdkit.Debug("golang code running in the .load callback") } -func (p TestPlugin) Unload() { +func (p *TestPlugin) Unload() { nbdkit.Debug("golang code running in the .unload callback") } -func (p TestPlugin) Config(key string, value string) error { +func (p *TestPlugin) Config(key string, value string) error { if key == "size" { var err error size, err = strconv.ParseUint(value, 0, 64) @@ -74,24 +74,24 @@ func (p TestPlugin) Config(key string, value string) error { } } -func (p TestPlugin) ConfigComplete() error { +func (p *TestPlugin) ConfigComplete() error { if !size_set { return nbdkit.PluginError{Errmsg: "size parameter is required"} } return nil } -func (p TestPlugin) Open(readonly bool) (nbdkit.ConnectionInterface, error) { +func (p *TestPlugin) Open(readonly bool) (nbdkit.ConnectionInterface, error) { nbdkit.Debug("golang code running in the .open callback") return &TestConnection{}, nil } -func (c TestConnection) GetSize() (uint64, error) { +func (c *TestConnection) GetSize() (uint64, error) { nbdkit.Debug("golang code running in the .get_size callback") return size, nil } -func (c TestConnection) PRead(buf []byte, offset uint64, flags uint32) error { +func (c *TestConnection) PRead(buf []byte, offset uint64, flags uint32) error { nbdkit.Debug("golang code running in the .pread callback") for i := 0; i < len(buf); i++ { buf[i] = 0 -- 2.25.0
Richard W.M. Jones
2020-Apr-24 18:48 UTC
Re: [Libguestfs] [PATCH nbdkit] golang: Pass Plugin and Connection by reference not value.
I have pushed this because in my testing it all seems fine. Just to be sure I also pushed an extra test which tests this specific plugin using libguestfs. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com virt-builder quickly builds VMs from scratch http://libguestfs.org/virt-builder.1.html
Apparently Analagous Threads
- Re: [PATCH nbdkit v2] Add the ability to write plugins in golang.
- [PATCH nbdkit v2] Add the ability to write plugins in golang.
- Re: [PATCH nbdkit UNFINISHED] Add the ability to write plugins in golang.
- 0.3.12 Pre-Release Gems Available
- Re: [PATCH nbdkit v2] Add the ability to write plugins in golang.