cutil: add a new internal package for c+go utility functions

Now we have sufficient boilerplate in our code for interacting with
various types and ceph calls with similar needs we establish a new
internal package, "cutil" (C utilities).

Note that many of the return types are wrapped. This is due to the
limits placed on us by cgo.  Despite the irritating limitations Go
places on "exporting" C types it still ought to help in the long run for
patterns that are very common or patterns that are subtle and we want to
write specific tests for.

Signed-off-by: John Mulligan <jmulligan@redhat.com>
This commit is contained in:
John Mulligan 2020-04-13 11:17:54 -04:00 committed by John Mulligan
parent d4079e3949
commit e40744fdf6
5 changed files with 261 additions and 0 deletions

View File

@ -0,0 +1,62 @@
package cutil
/*
#include <stdlib.h>
*/
import "C"
import (
"unsafe"
)
// CommandInput can be used to manage the input to ceph's *_command functions.
type CommandInput struct {
cmd []*C.char
inbuf []byte
}
// NewCommandInput creates C-level pointers from go byte buffers suitable
// for passing off to ceph's *_command functions.
func NewCommandInput(cmd [][]byte, inputBuffer []byte) *CommandInput {
ci := &CommandInput{
cmd: make([]*C.char, len(cmd)),
inbuf: inputBuffer,
}
for i := range cmd {
ci.cmd[i] = C.CString(string(cmd[i]))
}
return ci
}
// Free any C memory managed by this CommandInput.
func (ci *CommandInput) Free() {
for i := range ci.cmd {
C.free(unsafe.Pointer(ci.cmd[i]))
}
ci.cmd = nil
}
// Cmd returns an unsafe wrapper around an array of C-strings.
func (ci *CommandInput) Cmd() CharPtrPtr {
ptr := &ci.cmd[0]
return CharPtrPtr(ptr)
}
// CmdLen returns the length of the array of C-strings returned by
// Cmd.
func (ci *CommandInput) CmdLen() SizeT {
return SizeT(len(ci.cmd))
}
// InBuf returns an unsafe wrapper to a C char*.
func (ci *CommandInput) InBuf() CharPtr {
if len(ci.inbuf) == 0 {
return nil
}
return CharPtr(&ci.inbuf[0])
}
// InBufLen returns the length of the buffer returned by InBuf.
func (ci *CommandInput) InBufLen() SizeT {
return SizeT(len(ci.inbuf))
}

View File

@ -0,0 +1,50 @@
package cutil
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCommandInput(t *testing.T) {
t.Run("newAndFree", func(t *testing.T) {
ci := NewCommandInput(
[][]byte{[]byte("foobar")},
nil)
ci.Free()
})
t.Run("cmd", func(t *testing.T) {
ci := NewCommandInput(
[][]byte{[]byte("foobar")},
nil)
defer ci.Free()
assert.Len(t, ci.cmd, 1)
assert.EqualValues(t, 1, ci.CmdLen())
assert.NotNil(t, ci.Cmd())
})
t.Run("cmd2", func(t *testing.T) {
ci := NewCommandInput(
[][]byte{[]byte("foobar"), []byte("snarf")},
nil)
defer ci.Free()
assert.Len(t, ci.cmd, 2)
assert.EqualValues(t, 2, ci.CmdLen())
assert.NotNil(t, ci.Cmd())
})
t.Run("noInBuf", func(t *testing.T) {
ci := NewCommandInput(
[][]byte{[]byte("foobar")},
nil)
defer ci.Free()
assert.EqualValues(t, 0, ci.InBufLen())
assert.Equal(t, CharPtr(nil), ci.InBuf())
})
t.Run("hasInBuf", func(t *testing.T) {
ci := NewCommandInput(
[][]byte{[]byte("foobar")},
[]byte("original oregano"))
defer ci.Free()
assert.EqualValues(t, 16, ci.InBufLen())
assert.NotEqual(t, CharPtr(nil), ci.InBuf())
})
}

View File

@ -0,0 +1,81 @@
package cutil
/*
#include <stdlib.h>
*/
import "C"
import (
"unsafe"
)
// CommandOutput can be used to manage the outputs of ceph's *_command
// functions.
type CommandOutput struct {
outBuf *C.char
outBufLen C.size_t
outs *C.char
outsLen C.size_t
}
// NewCommandOutput returns an empty CommandOutput. The pointers that
// a CommandOutput provides can be used to get the results of ceph's
// *_command functions.
func NewCommandOutput() *CommandOutput {
return &CommandOutput{}
}
// Free any C memory tracked by this object.
func (co *CommandOutput) Free() {
if co.outBuf != nil {
C.free(unsafe.Pointer(co.outBuf))
}
if co.outs != nil {
C.free(unsafe.Pointer(co.outs))
}
}
// OutBuf returns an unsafe wrapper around a pointer to a `char*`.
func (co *CommandOutput) OutBuf() CharPtrPtr {
return CharPtrPtr(&co.outBuf)
}
// OutBufLen returns an unsafe wrapper around a pointer to a size_t.
func (co *CommandOutput) OutBufLen() SizeTPtr {
return SizeTPtr(&co.outBufLen)
}
// Outs returns an unsafe wrapper around a pointer to a `char*`.
func (co *CommandOutput) Outs() CharPtrPtr {
return CharPtrPtr(&co.outs)
}
// OutsLen returns an unsafe wrapper around a pointer to a size_t.
func (co *CommandOutput) OutsLen() SizeTPtr {
return SizeTPtr(&co.outsLen)
}
// GoValues returns native go values converted from the internal C-language
// values tracked by this object.
func (co *CommandOutput) GoValues() (buf []byte, status string) {
if co.outBufLen > 0 {
buf = C.GoBytes(unsafe.Pointer(co.outBuf), C.int(co.outBufLen))
}
if co.outsLen > 0 {
status = C.GoStringN(co.outs, C.int(co.outsLen))
}
return buf, status
}
// testSetString is only used by the unit tests for this file.
// It is located here due to the restriction on having import "C" in
// go test files. :-(
// It mimics a C function that takes a pointer to a
// string and length and allocates memory and sets the pointers
// to the new string and its length.
func testSetString(strp CharPtrPtr, lenp SizeTPtr, s string) {
sp := (**C.char)(strp)
lp := (*C.size_t)(lenp)
*sp = C.CString(s)
*lp = C.size_t(len(s))
}

View File

@ -0,0 +1,43 @@
package cutil
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCommandOutput(t *testing.T) {
t.Run("newAndFree", func(t *testing.T) {
co := NewCommandOutput()
assert.NotNil(t, co)
co.Free()
})
t.Run("setValues", func(t *testing.T) {
co := NewCommandOutput()
assert.NotNil(t, co)
defer co.Free()
testSetString(co.OutBuf(), co.OutBufLen(), "i got style")
testSetString(co.Outs(), co.OutsLen(), "i got rhythm")
b, s := co.GoValues()
assert.EqualValues(t, []byte("i got style"), b)
assert.EqualValues(t, "i got rhythm", s)
})
t.Run("setOnlyOutBuf", func(t *testing.T) {
co := NewCommandOutput()
assert.NotNil(t, co)
defer co.Free()
testSetString(co.OutBuf(), co.OutBufLen(), "i got style")
b, s := co.GoValues()
assert.EqualValues(t, []byte("i got style"), b)
assert.EqualValues(t, "", s)
})
t.Run("setOnlyOuts", func(t *testing.T) {
co := NewCommandOutput()
assert.NotNil(t, co)
defer co.Free()
testSetString(co.Outs(), co.OutsLen(), "i got rhythm")
b, s := co.GoValues()
assert.Nil(t, b)
assert.EqualValues(t, "i got rhythm", s)
})
}

View File

@ -0,0 +1,25 @@
package cutil
import "C"
import (
"unsafe"
)
// Basic types from C that we can make "public" without too much fuss.
// SizeT wraps size_t from C.
type SizeT C.size_t
// This section contains a bunch of types that are basically just
// unsafe.Pointer but have specific types to help "self document" what the
// underlying pointer is really meant to represent.
// CharPtrPtr is an unsafe pointer wrapping C's `char**`.
type CharPtrPtr unsafe.Pointer
// CharPtr is an unsafe pointer wrapping C's `char*`.
type CharPtr unsafe.Pointer
// SizeTPtr is an unsafe pointer wrapping C's `size_t*`.
type SizeTPtr unsafe.Pointer