cutil: use SyncBuffer for iovec type

The main motivation for PtrGuard was read and write buffers as they
are used in iovec.  This change uses SyncBuffer for the iovec
implementation, so that the no-copy PtrGuard implementation can be
enabled with the with_ptrguard build tag.

Signed-off-by: Sven Anderson <sven@redhat.com>
This commit is contained in:
Sven Anderson 2021-01-16 21:14:58 +01:00 committed by John Mulligan
parent 430dea5b7f
commit b8a803ccbb
3 changed files with 78 additions and 94 deletions

View File

@ -158,6 +158,7 @@ func (f *File) Preadv(data [][]byte, offset int64) (int, error) {
case ret == 0: case ret == 0:
return 0, io.EOF return 0, io.EOF
} }
iov.Sync()
return int(ret), nil return int(ret), nil
} }

View File

@ -5,74 +5,56 @@ package cutil
#include <sys/uio.h> #include <sys/uio.h>
*/ */
import "C" import "C"
import ( import (
"unsafe" "unsafe"
) )
var iovecSize uintptr // Iovec is a slice of iovec structs. Might have allocated C memory, so it must
// be freed with the Free() method.
// 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 { type Iovec struct {
// cvec represents an array of struct iovec C memory iovec []C.struct_iovec
cvec unsafe.Pointer sbs []*SyncBuffer
// length of the array (in elements)
length int
} }
// NewIovec creates an Iovec, and underlying C memory, of the specified size. const iovecSize = C.sizeof_struct_iovec
func NewIovec(l int) *Iovec {
r := &Iovec{ // ByteSlicesToIovec creates an Iovec and links it to Go buffers in data.
cvec: C.malloc(C.size_t(l) * C.size_t(iovecSize)), func ByteSlicesToIovec(data [][]byte) (v Iovec) {
length: l, n := len(data)
iovecMem := C.malloc(iovecSize * C.size_t(n))
v.iovec = (*[MaxIdx]C.struct_iovec)(iovecMem)[:n:n]
for i, b := range data {
sb := NewSyncBuffer(CPtr(&v.iovec[i].iov_base), b)
v.sbs = append(v.sbs, sb)
v.iovec[i].iov_len = C.size_t(len(b))
} }
return r return
} }
// ByteSlicesToIovec takes a slice of byte slices and returns a new iovec that // Sync makes sure the slices contain the same as the C buffers
// maps the slice data to struct iovec entries. func (v *Iovec) Sync() {
func ByteSlicesToIovec(data [][]byte) *Iovec { for _, sb := range v.sbs {
iov := NewIovec(len(data)) sb.Sync()
for i := range data {
iov.Set(i, data[i])
} }
return iov
} }
// Pointer returns a StructIovecPtr that represents the C memory of the // Pointer returns a pointer to the iovec
// underlying array. func (v *Iovec) Pointer() unsafe.Pointer {
func (v *Iovec) Pointer() StructIovecPtr { return unsafe.Pointer(&v.iovec[0])
return StructIovecPtr(unsafe.Pointer(v.cvec))
} }
// Len returns the number of entries in the Iovec. // Len returns a pointer to the iovec
func (v *Iovec) Len() int { func (v *Iovec) Len() int {
return v.length return len(v.iovec)
} }
// Free the C memory in the Iovec. // Free the C memory in the Iovec.
func (v *Iovec) Free() { func (v *Iovec) Free() {
if v.cvec != nil { for _, sb := range v.sbs {
C.free(v.cvec) sb.Release()
v.cvec = nil
v.length = 0
} }
} if len(v.iovec) != 0 {
C.free(unsafe.Pointer(&v.iovec[0]))
// Set will map the memory of the given byte slice to the iovec at the }
// specified position. v.iovec = nil
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)
} }

View File

@ -5,53 +5,54 @@ import (
"unsafe" "unsafe"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestIovec(t *testing.T) { func TestIovec(t *testing.T) {
t.Run("newAndFree", func(t *testing.T) { strs := []string{
iov := NewIovec(3) "foo",
iov.Free() "barbar",
}) "bazbazbaz",
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) var data [][]byte
defer iov.Free() for _, s := range strs {
data = append(data, []byte(s))
assert.NotNil(t, iov.Pointer()) }
assert.Equal(t, 4, iov.Len()) iovec := ByteSlicesToIovec(data)
p := iovec.Pointer()
assert.NotNil(t, p)
assert.Equal(t, iovec.Len(), len(data))
assert.Equal(t, p, unsafe.Pointer(&iovec.iovec[0]))
for i, iov := range iovec.iovec {
require.NotNil(t, iov.iov_base)
assert.Equal(t, int(iov.iov_len), len(data[i]))
assert.Equal(t, data[i], (*[MaxIdx]byte)(iov.iov_base)[:iov.iov_len:iov.iov_len])
}
// data didn't change
for i, b := range data {
assert.Equal(t, string(b), strs[i])
}
// clear iovec buffers
for _, iov := range iovec.iovec {
b := (*[MaxIdx]byte)(iov.iov_base)[:iov.iov_len:iov.iov_len]
for i := range b {
b[i] = 0
}
}
iovec.Sync()
// data must be cleared
for _, b := range data {
for i := range b {
assert.Zero(t, b[i])
}
}
iovec.Free()
for _, iov := range iovec.iovec {
assert.Equal(t, iov.iov_base, unsafe.Pointer(nil))
assert.Zero(t, iov.iov_len)
}
iovec.Free()
iovec.Sync()
iovec.Sync()
iovec.Free()
} }