1/* Example plugin.
2 * Copyright (C) 2013-2020 Red Hat Inc.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * * Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * * Neither the name of Red Hat nor the names of its contributors may be
16 * used to endorse or promote products derived from this software without
17 * specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33package main
34
35import (
36	"C"
37	"io/ioutil"
38	"libguestfs.org/nbdkit"
39	"os"
40	"strconv"
41	"unsafe"
42)
43
44var pluginName = "disk"
45
46// The plugin global struct.
47type DiskPlugin struct {
48	nbdkit.Plugin
49}
50
51// The per-client struct.
52type DiskConnection struct {
53	nbdkit.Connection
54	fd *os.File // Per-client temporary disk.
55}
56
57var size uint64
58var size_set = false
59
60// Parse the size parameter on the command line.
61func (p *DiskPlugin) Config(key string, value string) error {
62	if key == "size" {
63		var err error
64		size, err = strconv.ParseUint(value, 0, 64)
65		if err != nil {
66			return err
67		}
68		size_set = true
69		return nil
70	} else {
71		return nbdkit.PluginError{Errmsg: "unknown parameter"}
72	}
73}
74
75// Make sure the user specified the size parameter.
76func (p *DiskPlugin) ConfigComplete() error {
77	if !size_set {
78		return nbdkit.PluginError{Errmsg: "size parameter is required"}
79	}
80	return nil
81}
82
83func (p *DiskPlugin) Open(readonly bool) (nbdkit.ConnectionInterface, error) {
84	// Open a temporary file.
85	fd, err := ioutil.TempFile("/var/tmp", "nbdkitdisk")
86	if err != nil {
87		return nil, err
88	}
89	os.Remove(fd.Name())
90
91	// Truncate it to the right size.
92	err = fd.Truncate(int64(size))
93	if err != nil {
94		return nil, err
95	}
96
97	// Store the file descriptor of the temporary file in the
98	// Connection struct.
99	return &DiskConnection{fd: fd}, nil
100}
101
102func (c *DiskConnection) Close() {
103	c.fd.Close()
104}
105
106// Return the size of the disk.  We could just return the global
107// "size" here, but make the example more interesting.
108func (c *DiskConnection) GetSize() (uint64, error) {
109	info, err := c.fd.Stat()
110	if err != nil {
111		return 0, err
112	}
113	return uint64(info.Size()), nil
114}
115
116// Multi-conn is NOT safe because each client sees a different disk.
117func (c *DiskConnection) CanMultiConn() (bool, error) {
118	return false, nil
119}
120
121func (c *DiskConnection) PRead(buf []byte, offset uint64,
122	flags uint32) error {
123	n, err := c.fd.ReadAt(buf, int64(offset))
124	if err != nil {
125		return err
126	}
127	// NBD requests must always read/write the whole requested
128	// amount, or else fail.  Actually we should loop here (XXX).
129	if n != len(buf) {
130		return nbdkit.PluginError{Errmsg: "short read"}
131	}
132	return nil
133}
134
135// Note that CanWrite() is required in golang plugins that implement
136// PWrite(), otherwise PWrite() will never be called.
137func (c *DiskConnection) CanWrite() (bool, error) {
138	return true, nil
139}
140
141func (c *DiskConnection) PWrite(buf []byte, offset uint64,
142	flags uint32) error {
143	n, err := c.fd.WriteAt(buf, int64(offset))
144	if err != nil {
145		return err
146	}
147	// NBD requests must always read/write the whole requested
148	// amount, or else fail.  Actually we should loop here (XXX).
149	if n != len(buf) {
150		return nbdkit.PluginError{Errmsg: "short write"}
151	}
152	return nil
153}
154
155// Note that CanFlush() is required in golang plugins that implement
156// Flush(), otherwise Flush() will never be called.
157func (c *DiskConnection) CanFlush() (bool, error) {
158	return true, nil
159}
160
161// This is only an example, but if this was a real plugin, because
162// these disks are transient and deleted when the client exits, it
163// would make no sense to implement a Flush() callback.
164func (c *DiskConnection) Flush(flags uint32) error {
165	return c.fd.Sync()
166}
167
168//----------------------------------------------------------------------
169//
170// The boilerplate below this line is required by all golang plugins,
171// as well as importing "C" and "unsafe" modules at the top of the
172// file.
173
174//export plugin_init
175func plugin_init() unsafe.Pointer {
176	// If your plugin needs to do any initialization, you can
177	// either put it here or implement a Load() method.
178	// ...
179
180	// Then you must call the following function.
181	return nbdkit.PluginInitialize(pluginName, &DiskPlugin{})
182}
183
184// This is never(?) called, but must exist.
185func main() {}
186