From ebea82dda5f30cb8471fdd4a63215424d695864a Mon Sep 17 00:00:00 2001 From: John Mulligan Date: Wed, 19 Aug 2020 16:39:23 -0400 Subject: [PATCH] cutil: add Iovec type wrapping C struct iovec arrays This type is useful for passing disparate buffers to be read or written in a single call. Functions using this type exist in cephfs and rbd. Currently this is needed for cephfs calls. Signed-off-by: John Mulligan --- internal/cutil/iovec.go | 78 ++++++++++++++++++++++++++++++++++++ internal/cutil/iovec_test.go | 57 ++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 internal/cutil/iovec.go create mode 100644 internal/cutil/iovec_test.go diff --git a/internal/cutil/iovec.go b/internal/cutil/iovec.go new file mode 100644 index 0000000..d89de06 --- /dev/null +++ b/internal/cutil/iovec.go @@ -0,0 +1,78 @@ +package cutil + +/* +#include +#include +*/ +import "C" + +import ( + "unsafe" +) + +var iovecSize uintptr + +// StructIovecPtr is an unsafe pointer wrapping C's `*struct iovec`. +type StructIovecPtr unsafe.Pointer + +// Iovec helps manage struct iovec arrays needed by some C functions. +type Iovec struct { + // cvec represents an array of struct iovec C memory + cvec unsafe.Pointer + // length of the array (in elements) + length int +} + +// NewIovec creates an Iovec, and underlying C memory, of the specified size. +func NewIovec(l int) *Iovec { + r := &Iovec{ + cvec: C.malloc(C.size_t(l) * C.size_t(iovecSize)), + length: l, + } + return r +} + +// ByteSlicesToIovec takes a slice of byte slices and returns a new iovec that +// maps the slice data to struct iovec entries. +func ByteSlicesToIovec(data [][]byte) *Iovec { + iov := NewIovec(len(data)) + for i := range data { + iov.Set(i, data[i]) + } + return iov +} + +// Pointer returns a StructIovecPtr that represents the C memory of the +// underlying array. +func (v *Iovec) Pointer() StructIovecPtr { + return StructIovecPtr(unsafe.Pointer(v.cvec)) +} + +// Len returns the number of entries in the Iovec. +func (v *Iovec) Len() int { + return v.length +} + +// Free the C memory in the Iovec. +func (v *Iovec) Free() { + if v.cvec != nil { + C.free(v.cvec) + v.cvec = nil + v.length = 0 + } +} + +// Set will map the memory of the given byte slice to the iovec at the +// specified position. +func (v *Iovec) Set(i int, buf []byte) { + offset := uintptr(i) * iovecSize + iov := (*C.struct_iovec)(unsafe.Pointer( + uintptr(unsafe.Pointer(v.cvec)) + offset)) + iov.iov_base = unsafe.Pointer(&buf[0]) + iov.iov_len = C.size_t(len(buf)) +} + +func init() { + var iovec C.struct_iovec + iovecSize = unsafe.Sizeof(iovec) +} diff --git a/internal/cutil/iovec_test.go b/internal/cutil/iovec_test.go new file mode 100644 index 0000000..9978877 --- /dev/null +++ b/internal/cutil/iovec_test.go @@ -0,0 +1,57 @@ +package cutil + +import ( + "testing" + "unsafe" + + "github.com/stretchr/testify/assert" +) + +func TestIovec(t *testing.T) { + t.Run("newAndFree", func(t *testing.T) { + iov := NewIovec(3) + iov.Free() + }) + t.Run("setBufs", func(t *testing.T) { + b1 := []byte("foo") + b2 := []byte("barbar") + b3 := []byte("bazbazbaz") + iov := NewIovec(3) + iov.Set(0, b1) + iov.Set(1, b2) + iov.Set(2, b3) + iov.Free() + // free also unsets internal values + assert.Equal(t, unsafe.Pointer(nil), iov.cvec) + assert.Equal(t, 0, iov.length) + }) + t.Run("testGetters", func(t *testing.T) { + b1 := []byte("foo") + b2 := []byte("barbar") + b3 := []byte("bazbazbaz") + b4 := []byte("zonk") + iov := NewIovec(4) + defer iov.Free() + iov.Set(0, b1) + iov.Set(1, b2) + iov.Set(2, b3) + iov.Set(3, b4) + + assert.NotNil(t, iov.Pointer()) + assert.Equal(t, 4, iov.Len()) + }) +} + +func TestByteSlicesToIovec(t *testing.T) { + d := [][]byte{ + []byte("ramekin"), + []byte("shuffleboard"), + []byte("tranche"), + []byte("phycobilisomes"), + } + iov := ByteSlicesToIovec(d) + defer iov.Free() + + assert.NotNil(t, iov.Pointer()) + assert.Equal(t, 4, iov.Len()) +}