Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
package commands
import (
"encoding/json"
ccom "github.com/ceph/go-ceph/common/commands"
"github.com/ceph/go-ceph/rados"
)
func validate(m interface{}) error {
if m == nil {
return rados.ErrNotConnected
}
return nil
}
// RawMgrCommand takes a byte buffer and sends it to the MGR as a command.
// The buffer is expected to contain preformatted JSON.
func RawMgrCommand(m ccom.MgrCommander, buf []byte) Response {
if err := validate(m); err != nil {
return Response{err: err}
}
return NewResponse(m.MgrCommand([][]byte{buf}))
}
// MarshalMgrCommand takes an generic interface{} value, converts it to JSON
// and sends the json to the MGR as a command.
func MarshalMgrCommand(m ccom.MgrCommander, v interface{}) Response {
b, err := json.Marshal(v)
if err != nil {
return Response{err: err}
}
return RawMgrCommand(m, b)
}
// RawMonCommand takes a byte buffer and sends it to the MON as a command.
// The buffer is expected to contain preformatted JSON.
func RawMonCommand(m ccom.MonCommander, buf []byte) Response {
if err := validate(m); err != nil {
return Response{err: err}
}
return NewResponse(m.MonCommand(buf))
}
// MarshalMonCommand takes an generic interface{} value, converts it to JSON
// and sends the json to the MGR as a command.
func MarshalMonCommand(m ccom.MonCommander, v interface{}) Response {
b, err := json.Marshal(v)
if err != nil {
return Response{err: err}
}
return RawMonCommand(m, b)
}
// MarshalMgrCommandWithBuffer takes a generic interface{} value, converts
// it to JSON and sends the JSON, along with the buffer data, to the MGR as
// a command.
func MarshalMgrCommandWithBuffer(
m ccom.MgrBufferCommander, v interface{}, buf []byte) Response {
b, err := json.Marshal(v)
if err != nil {
return Response{err: err}
}
if err := validate(m); err != nil {
return Response{err: err}
}
return NewResponse(m.MgrCommandWithInputBuffer(
[][]byte{b},
buf,
))
}
// MarshalMonCommandWithBuffer takes a generic interface{} value, converts
// it to JSON and sends the JSON, along with the buffer data, to the MON(s)
// as a command.
func MarshalMonCommandWithBuffer(
m ccom.MonBufferCommander, v interface{}, buf []byte) Response {
b, err := json.Marshal(v)
if err != nil {
return Response{err: err}
}
if err := validate(m); err != nil {
return Response{err: err}
}
return NewResponse(m.MonCommandWithInputBuffer(
b,
buf,
))
}
+193
View File
@@ -0,0 +1,193 @@
package commands
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"strings"
)
var (
// ErrStatusNotEmpty may be returned if a call should not have a status
// string set but one is.
ErrStatusNotEmpty = errors.New("response status not empty")
// ErrBodyNotEmpty may be returned if a call should have an empty body but
// a body value is present.
ErrBodyNotEmpty = errors.New("response body not empty")
)
const (
deprecatedSuffix = "call is deprecated and will be removed in a future release"
missingPrefix = "No handler found"
einval = -22
)
type cephError interface {
ErrorCode() int
}
// NotImplementedError error values will be returned in the case that an API
// call is not available in the version of Ceph that is running in the target
// cluster.
type NotImplementedError struct {
Response
}
// Error implements the error interface.
func (e NotImplementedError) Error() string {
return fmt.Sprintf("API call not implemented server-side: %s", e.status)
}
// Response encapsulates the data returned by ceph and supports easy processing
// pipelines.
type Response struct {
body []byte
status string
err error
}
// Ok returns true if the response contains no error.
func (r Response) Ok() bool {
return r.err == nil
}
// Error implements the error interface.
func (r Response) Error() string {
if r.status == "" {
return r.err.Error()
}
return fmt.Sprintf("%s: %q", r.err, r.status)
}
// Unwrap returns the error this response contains.
func (r Response) Unwrap() error {
return r.err
}
// Status returns the status string value.
func (r Response) Status() string {
return r.status
}
// Body returns the response body as a raw byte-slice.
func (r Response) Body() []byte {
return r.body
}
// End returns an error if the response contains an error or nil, indicating
// that response is no longer needed for processing.
func (r Response) End() error {
if !r.Ok() {
if ce, ok := r.err.(cephError); ok {
if ce.ErrorCode() == einval && strings.HasPrefix(r.status, missingPrefix) {
return NotImplementedError{Response: r}
}
}
return r
}
return nil
}
// NoStatus asserts that the input response has no status value.
func (r Response) NoStatus() Response {
if !r.Ok() {
return r
}
if r.status != "" {
return Response{r.body, r.status, ErrStatusNotEmpty}
}
return r
}
// NoBody asserts that the input response has no body value.
func (r Response) NoBody() Response {
if !r.Ok() {
return r
}
if len(r.body) != 0 {
return Response{r.body, r.status, ErrBodyNotEmpty}
}
return r
}
// EmptyBody is similar to NoBody but also accepts an empty JSON object.
func (r Response) EmptyBody() Response {
if !r.Ok() {
return r
}
if len(r.body) != 0 {
d := map[string]interface{}{}
if err := json.Unmarshal(r.body, &d); err != nil {
return Response{r.body, r.status, err}
}
if len(d) != 0 {
return Response{r.body, r.status, ErrBodyNotEmpty}
}
}
return r
}
// NoData asserts that the input response has no status or body values.
func (r Response) NoData() Response {
return r.NoStatus().NoBody()
}
// FilterPrefix sets the status value to an empty string if the status
// value contains the given prefix string.
func (r Response) FilterPrefix(p string) Response {
if !r.Ok() {
return r
}
if strings.HasPrefix(r.status, p) {
return Response{r.body, "", r.err}
}
return r
}
// FilterSuffix sets the status value to an empty string if the status
// value contains the given suffix string.
func (r Response) FilterSuffix(s string) Response {
if !r.Ok() {
return r
}
if strings.HasSuffix(r.status, s) {
return Response{r.body, "", r.err}
}
return r
}
// FilterBodyPrefix sets the body value equivalent to an empty string if the
// body value contains the given prefix string.
func (r Response) FilterBodyPrefix(p string) Response {
if !r.Ok() {
return r
}
if bytes.HasPrefix(r.body, []byte(p)) {
return Response{[]byte(""), r.status, r.err}
}
return r
}
// FilterDeprecated removes deprecation warnings from the response status.
// Use it when checking the response from calls that may be deprecated in ceph
// if you want those calls to continue working if the warning is present.
func (r Response) FilterDeprecated() Response {
return r.FilterSuffix(deprecatedSuffix)
}
// Unmarshal data from the response body into v.
func (r Response) Unmarshal(v interface{}) Response {
if !r.Ok() {
return r
}
if err := json.Unmarshal(r.body, v); err != nil {
return Response{body: r.body, err: err}
}
return r
}
// NewResponse returns a response.
func NewResponse(b []byte, s string, e error) Response {
return Response{b, s, e}
}
+89
View File
@@ -0,0 +1,89 @@
package commands
import (
"fmt"
ccom "github.com/ceph/go-ceph/common/commands"
)
// NewTraceCommander is a RadosCommander that wraps a given RadosCommander
// and when commands are executes prints debug level "traces" to the
// standard output.
func NewTraceCommander(c ccom.RadosBufferCommander) ccom.RadosBufferCommander {
return &tracingCommander{c}
}
// tracingCommander serves two purposes: first, it allows one to trace the
// input and output json when running the tests. It can help with actually
// debugging the tests. Second, it demonstrates the rationale for using an
// interface in FSAdmin. You can layer any sort of debugging, error injection,
// or whatnot between the FSAdmin layer and the RADOS layer.
type tracingCommander struct {
conn ccom.RadosBufferCommander
}
func (t *tracingCommander) MgrCommand(buf [][]byte) ([]byte, string, error) {
fmt.Println("(MGR Command)")
for i := range buf {
fmt.Println("IN:", string(buf[i]))
}
r, s, err := t.conn.MgrCommand(buf)
fmt.Println("OUT(result):", string(r))
if s != "" {
fmt.Println("OUT(status):", s)
}
if err != nil {
fmt.Println("OUT(error):", err.Error())
}
return r, s, err
}
func (t *tracingCommander) MonCommand(buf []byte) ([]byte, string, error) {
fmt.Println("(MON Command)")
fmt.Println("IN:", string(buf))
r, s, err := t.conn.MonCommand(buf)
fmt.Println("OUT(result):", string(r))
if s != "" {
fmt.Println("OUT(status):", s)
}
if err != nil {
fmt.Println("OUT(error):", err.Error())
}
return r, s, err
}
func (t *tracingCommander) MgrCommandWithInputBuffer(
cbuf [][]byte, dbuf []byte) ([]byte, string, error) {
fmt.Println("(MGR Command w/buffer)")
for i := range cbuf {
fmt.Println("IN:", string(cbuf[i]))
}
fmt.Println("IN DATA:", string(dbuf))
r, s, err := t.conn.MgrCommandWithInputBuffer(cbuf, dbuf)
fmt.Println("OUT(result):", string(r))
if s != "" {
fmt.Println("OUT(status):", s)
}
if err != nil {
fmt.Println("OUT(error):", err.Error())
}
return r, s, err
}
func (t *tracingCommander) MonCommandWithInputBuffer(
cbuf []byte, dbuf []byte) ([]byte, string, error) {
fmt.Println("(MON Command w/buffer)")
fmt.Println("IN:", string(cbuf))
fmt.Println("IN DATA:", string(dbuf))
r, s, err := t.conn.MonCommandWithInputBuffer(cbuf, dbuf)
fmt.Println("OUT(result):", string(r))
if s != "" {
fmt.Println("OUT(status):", s)
}
if err != nil {
fmt.Println("OUT(error):", err.Error())
}
return r, s, err
}
+66
View File
@@ -0,0 +1,66 @@
package cutil
/*
#include <stdlib.h>
#include <string.h>
typedef void* voidptr;
*/
import "C"
import (
"math"
"unsafe"
)
const (
// MaxIdx is the maximum index on 32 bit systems
MaxIdx = math.MaxInt32 // 2GB, max int32 value, should be safe
// PtrSize is the size of a pointer
PtrSize = C.sizeof_voidptr
// SizeTSize is the size of C.size_t
SizeTSize = C.sizeof_size_t
)
// Compile-time assertion ensuring that Go's `int` is at least as large as C's.
const _ = unsafe.Sizeof(int(0)) - C.sizeof_int
// 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.
// CPtr is an unsafe.Pointer to C allocated memory
type CPtr unsafe.Pointer
// 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
// FreeFunc is a wrapper around calls to, or act like, C's free function.
type FreeFunc func(unsafe.Pointer)
// Malloc is C.malloc
func Malloc(s SizeT) CPtr { return CPtr(C.malloc(C.size_t(s))) }
// Free is C.free
func Free(p CPtr) { C.free(unsafe.Pointer(p)) }
// CString is C.CString
func CString(s string) CharPtr { return CharPtr((C.CString(s))) }
// CBytes is C.CBytes
func CBytes(b []byte) CPtr { return CPtr(C.CBytes(b)) }
// Memcpy is C.memcpy
func Memcpy(dst, src CPtr, n SizeT) {
C.memcpy(unsafe.Pointer(dst), unsafe.Pointer(src), C.size_t(n))
}
+89
View File
@@ -0,0 +1,89 @@
package cutil
// #include <stdlib.h>
import "C"
import (
"unsafe"
)
// BufferGroup is a helper structure that holds Go-allocated slices of
// C-allocated strings and their respective lengths. Useful for C functions
// that consume byte buffers with explicit length instead of null-terminated
// strings. When used as input arguments in C functions, caller must make sure
// the C code will not hold any pointers to either of the struct's attributes
// after that C function returns.
type BufferGroup struct {
// C-allocated buffers.
Buffers []CharPtr
// Lengths of C buffers, where Lengths[i] = length(Buffers[i]).
Lengths []SizeT
}
// TODO: should BufferGroup implementation change and the slices would contain
// nested Go pointers, they must be pinned with PtrGuard.
// NewBufferGroupStrings returns new BufferGroup constructed from strings.
func NewBufferGroupStrings(strs []string) *BufferGroup {
s := &BufferGroup{
Buffers: make([]CharPtr, len(strs)),
Lengths: make([]SizeT, len(strs)),
}
for i, str := range strs {
bs := []byte(str)
s.Buffers[i] = CharPtr(C.CBytes(bs))
s.Lengths[i] = SizeT(len(bs))
}
return s
}
// NewBufferGroupBytes returns new BufferGroup constructed
// from slice of byte slices.
func NewBufferGroupBytes(bss [][]byte) *BufferGroup {
s := &BufferGroup{
Buffers: make([]CharPtr, len(bss)),
Lengths: make([]SizeT, len(bss)),
}
for i, bs := range bss {
s.Buffers[i] = CharPtr(C.CBytes(bs))
s.Lengths[i] = SizeT(len(bs))
}
return s
}
// Free free()s the C-allocated memory.
func (s *BufferGroup) Free() {
for _, ptr := range s.Buffers {
C.free(unsafe.Pointer(ptr))
}
s.Buffers = nil
s.Lengths = nil
}
// BuffersPtr returns a pointer to the beginning of the Buffers slice.
func (s *BufferGroup) BuffersPtr() CharPtrPtr {
if len(s.Buffers) == 0 {
return nil
}
return CharPtrPtr(&s.Buffers[0])
}
// LengthsPtr returns a pointer to the beginning of the Lengths slice.
func (s *BufferGroup) LengthsPtr() SizeTPtr {
if len(s.Lengths) == 0 {
return nil
}
return SizeTPtr(&s.Lengths[0])
}
func testBufferGroupGet(s *BufferGroup, index int) (str string, length int) {
bs := C.GoBytes(unsafe.Pointer(s.Buffers[index]), C.int(s.Lengths[index]))
return string(bs), int(s.Lengths[index])
}
+62
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))
}
+100
View File
@@ -0,0 +1,100 @@
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 {
free FreeFunc
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: free,
}
}
// SetFreeFunc sets the function used to free memory held by CommandOutput.
// Not all uses of CommandOutput expect to use the basic C.free function
// and either require or prefer the use of a custom deallocation function.
// Use SetFreeFunc to change the free function and return the modified
// CommandOutput object.
func (co *CommandOutput) SetFreeFunc(f FreeFunc) *CommandOutput {
co.free = f
return co
}
// Free any C memory tracked by this object.
func (co *CommandOutput) Free() {
if co.outBuf != nil {
co.free(unsafe.Pointer(co.outBuf))
}
if co.outs != nil {
co.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))
}
// free wraps C.free.
// Required for unit tests that may not use cgo directly.
func free(p unsafe.Pointer) {
C.free(p)
}
+76
View File
@@ -0,0 +1,76 @@
package cutil
// The following code needs some explanation:
// This creates slices on top of the C memory buffers allocated before in
// order to safely and comfortably use them as arrays. First the void pointer
// is cast to a pointer to an array of the type that will be stored in the
// array. Because the size of an array is a constant, but the real array size
// is dynamic, we just use the biggest possible index value MaxIdx, to make
// sure it's always big enough. (Nothing is allocated by casting, so the size
// can be arbitrarily big.) So, if the array should store items of myType, the
// cast would be (*[MaxIdx]myItem)(myCMemPtr).
// From that array pointer a slice is created with the [start:end:capacity]
// syntax. The capacity must be set explicitly here, because by default it
// would be set to the size of the original array, which is MaxIdx, which
// doesn't reflect reality in this case. This results in definitions like:
// cSlice := (*[MaxIdx]myItem)(myCMemPtr)[:numOfItems:numOfItems]
////////// CPtr //////////
// CPtrCSlice is a C allocated slice of C pointers.
type CPtrCSlice []CPtr
// NewCPtrCSlice returns a CPtrSlice.
// Similar to CString it must be freed with slice.Free()
func NewCPtrCSlice(size int) CPtrCSlice {
if size == 0 {
return nil
}
cMem := Malloc(SizeT(size) * PtrSize)
cSlice := (*[MaxIdx]CPtr)(cMem)[:size:size]
return cSlice
}
// Ptr returns a pointer to CPtrSlice
func (v *CPtrCSlice) Ptr() CPtr {
if len(*v) == 0 {
return nil
}
return CPtr(&(*v)[0])
}
// Free frees a CPtrSlice
func (v *CPtrCSlice) Free() {
Free(v.Ptr())
*v = nil
}
////////// SizeT //////////
// SizeTCSlice is a C allocated slice of C.size_t.
type SizeTCSlice []SizeT
// NewSizeTCSlice returns a SizeTCSlice.
// Similar to CString it must be freed with slice.Free()
func NewSizeTCSlice(size int) SizeTCSlice {
if size == 0 {
return nil
}
cMem := Malloc(SizeT(size) * SizeTSize)
cSlice := (*[MaxIdx]SizeT)(cMem)[:size:size]
return cSlice
}
// Ptr returns a pointer to SizeTCSlice
func (v *SizeTCSlice) Ptr() CPtr {
if len(*v) == 0 {
return nil
}
return CPtr(&(*v)[0])
}
// Free frees a SizeTCSlice
func (v *SizeTCSlice) Free() {
Free(v.Ptr())
*v = nil
}
+60
View File
@@ -0,0 +1,60 @@
package cutil
/*
#include <stdlib.h>
#include <sys/uio.h>
*/
import "C"
import (
"unsafe"
)
// Iovec is a slice of iovec structs. Might have allocated C memory, so it must
// be freed with the Free() method.
type Iovec struct {
iovec []C.struct_iovec
sbs []*SyncBuffer
}
const iovecSize = C.sizeof_struct_iovec
// ByteSlicesToIovec creates an Iovec and links it to Go buffers in data.
func ByteSlicesToIovec(data [][]byte) (v Iovec) {
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
}
// Sync makes sure the slices contain the same as the C buffers
func (v *Iovec) Sync() {
for _, sb := range v.sbs {
sb.Sync()
}
}
// Pointer returns a pointer to the iovec
func (v *Iovec) Pointer() unsafe.Pointer {
return unsafe.Pointer(&v.iovec[0])
}
// Len returns a pointer to the iovec
func (v *Iovec) Len() int {
return len(v.iovec)
}
// Free the C memory in the Iovec.
func (v *Iovec) Free() {
for _, sb := range v.sbs {
sb.Release()
}
if len(v.iovec) != 0 {
C.free(unsafe.Pointer(&v.iovec[0]))
}
v.iovec = nil
}
+37
View File
@@ -0,0 +1,37 @@
//go:build !go1.21
// +build !go1.21
// This code assumes a non-moving garbage collector, which is the case until at
// least go 1.20
package cutil
import (
"unsafe"
)
// PtrGuard respresents a guarded Go pointer (pointing to memory allocated by Go
// runtime) stored in C memory (allocated by C)
type PtrGuard struct {
cPtr CPtr
goPtr unsafe.Pointer
}
// NewPtrGuard writes the goPtr (pointing to Go memory) into C memory at the
// position cPtr, and returns a PtrGuard object.
func NewPtrGuard(cPtr CPtr, goPtr unsafe.Pointer) *PtrGuard {
var v PtrGuard
v.cPtr = cPtr
v.goPtr = goPtr
p := (*unsafe.Pointer)(unsafe.Pointer(cPtr))
*p = goPtr
return &v
}
// Release removes the guarded Go pointer from the C memory by overwriting it
// with NULL.
func (v *PtrGuard) Release() {
p := (*unsafe.Pointer)(unsafe.Pointer(v.cPtr))
*p = nil
v.goPtr = nil
}
@@ -0,0 +1,35 @@
//go:build go1.21
// +build go1.21
package cutil
import (
"runtime"
"unsafe"
)
// PtrGuard respresents a guarded Go pointer (pointing to memory allocated by Go
// runtime) stored in C memory (allocated by C)
type PtrGuard struct {
cPtr CPtr
pinner runtime.Pinner
}
// NewPtrGuard writes the goPtr (pointing to Go memory) into C memory at the
// position cPtr, and returns a PtrGuard object.
func NewPtrGuard(cPtr CPtr, goPtr unsafe.Pointer) *PtrGuard {
var v PtrGuard
v.pinner.Pin(goPtr)
v.cPtr = cPtr
p := (*unsafe.Pointer)(unsafe.Pointer(cPtr))
*p = goPtr
return &v
}
// Release removes the guarded Go pointer from the C memory by overwriting it
// with NULL.
func (v *PtrGuard) Release() {
p := (*unsafe.Pointer)(unsafe.Pointer(v.cPtr))
*p = nil
v.pinner.Unpin()
}
+49
View File
@@ -0,0 +1,49 @@
package cutil
import "C"
import (
"bytes"
)
// SplitBuffer splits a byte-slice buffer, typically returned from C code,
// into a slice of strings.
// The contents of the buffer are assumed to be null-byte separated.
// If the buffer contains a sequence of null-bytes it will assume that the
// "space" between the bytes are meant to be empty strings.
func SplitBuffer(b []byte) []string {
return splitBufStrings(b, true)
}
// SplitSparseBuffer splits a byte-slice buffer, typically returned from C code,
// into a slice of strings.
// The contents of the buffer are assumed to be null-byte separated.
// This function assumes that buffer to be "sparse" such that only non-null-byte
// strings will be returned, and no "empty" strings exist if null-bytes
// are found adjacent to each other.
func SplitSparseBuffer(b []byte) []string {
return splitBufStrings(b, false)
}
// If keepEmpty is true, empty substrings will be returned, by default they are
// excluded from the results.
// This is almost certainly a suboptimal implementation, especially for
// keepEmpty=true case. Optimizing the functions is a job for another day.
func splitBufStrings(b []byte, keepEmpty bool) []string {
values := make([]string, 0)
// the final null byte should be the terminating null in C
// we never want to preserve the empty string after it
if len(b) > 0 && b[len(b)-1] == 0 {
b = b[:len(b)-1]
}
if len(b) == 0 {
return values
}
for _, s := range bytes.Split(b, []byte{0}) {
if !keepEmpty && len(s) == 0 {
continue
}
values = append(values, string(s))
}
return values
}
+29
View File
@@ -0,0 +1,29 @@
//go:build !no_ptrguard
// +build !no_ptrguard
package cutil
import (
"unsafe"
)
// SyncBuffer is a C buffer connected to a data slice
type SyncBuffer struct {
pg *PtrGuard
}
// NewSyncBuffer creates a C buffer from a data slice and stores it at CPtr
func NewSyncBuffer(cPtr CPtr, data []byte) *SyncBuffer {
var v SyncBuffer
v.pg = NewPtrGuard(cPtr, unsafe.Pointer(&data[0]))
return &v
}
// Release releases the C buffer and nulls its stored pointer
func (v *SyncBuffer) Release() {
v.pg.Release()
}
// Sync asserts that changes in the C buffer are available in the data
// slice
func (*SyncBuffer) Sync() {}
@@ -0,0 +1,38 @@
//go:build no_ptrguard
// +build no_ptrguard
package cutil
// SyncBuffer is a C buffer connected to a data slice
type SyncBuffer struct {
data []byte
cPtr *CPtr
}
// NewSyncBuffer creates a C buffer from a data slice and stores it at CPtr
func NewSyncBuffer(cPtr CPtr, data []byte) *SyncBuffer {
var v SyncBuffer
v.data = data
v.cPtr = (*CPtr)(cPtr)
*v.cPtr = CBytes(data)
return &v
}
// Release releases the C buffer and nulls its stored pointer
func (v *SyncBuffer) Release() {
if v.cPtr != nil {
Free(*v.cPtr)
*v.cPtr = nil
v.cPtr = nil
}
v.data = nil
}
// Sync asserts that changes in the C buffer are available in the data
// slice
func (v *SyncBuffer) Sync() {
if v.cPtr == nil {
return
}
Memcpy(CPtr(&v.data[0]), CPtr(*v.cPtr), SizeT(len(v.data)))
}
+39
View File
@@ -0,0 +1,39 @@
package dlsym
// #cgo LDFLAGS: -ldl
//
// #define _GNU_SOURCE
//
// #include <stdlib.h>
// #include <dlfcn.h>
import "C"
import (
"errors"
"fmt"
"unsafe"
)
// ErrUndefinedSymbol is returned by LookupSymbol when the requested symbol
// could not be found.
var ErrUndefinedSymbol = errors.New("symbol not found")
// LookupSymbol resolves the named symbol from the already dynamically loaded
// libraries. If the symbol is found, a pointer to it is returned, in case of a
// failure, the message provided by dlerror() is included in the error message.
func LookupSymbol(symbol string) (unsafe.Pointer, error) {
cSymName := C.CString(symbol)
defer C.free(unsafe.Pointer(cSymName))
// clear dlerror before looking up the symbol
C.dlerror()
// resolve the address of the symbol
sym := C.dlsym(C.RTLD_DEFAULT, cSymName)
e := C.dlerror()
dlerr := C.GoString(e)
if dlerr != "" {
return nil, fmt.Errorf("%w: %s", ErrUndefinedSymbol, dlerr)
}
return sym, nil
}
+57
View File
@@ -0,0 +1,57 @@
package errutil
type cephErrno int
// Error returns the error string for the errno.
func (e cephErrno) Error() string {
_, strerror := FormatErrno(int(e))
return strerror
}
// cephError combines the source/component that generated the error and its
// related errno.
type cephError struct {
source string
errno cephErrno
}
// Error returns the error string with the source and errno.
func (e cephError) Error() string {
return FormatErrorCode(e.source, int(e.errno))
}
// Unwrap returns an error without the source.
func (e cephError) Unwrap() error {
if e.errno == 0 {
return nil
}
return e.errno
}
// Is checks if both errors have the same errno.
func (e cephError) Is(err error) bool {
ce, ok := err.(cephError)
if !ok {
return false
}
return e.errno == ce.errno
}
// ErrorCode returns the errno of the error.
func (e cephError) ErrorCode() int {
return int(e.errno)
}
// GetError returns a new error that can be compared with errors.Is(),
// independently of the source/component of the error.
func GetError(source string, e int) error {
if e == 0 {
return nil
}
return cephError{
source: source,
errno: cephErrno(int(e)),
}
}
+52
View File
@@ -0,0 +1,52 @@
/*
Package errutil provides common functions for dealing with error conditions for
all ceph api wrappers.
*/
package errutil
/* force XSI-complaint strerror_r() */
// #define _POSIX_C_SOURCE 200112L
// #undef _GNU_SOURCE
// #include <stdlib.h>
// #include <errno.h>
// #include <string.h>
import "C"
import (
"fmt"
"unsafe"
)
// FormatErrno returns the absolute value of the errno as well as a string
// describing the errno. The string will be empty is the errno is not known.
func FormatErrno(errno int) (int, string) {
buf := make([]byte, 1024)
// strerror expects errno >= 0
if errno < 0 {
errno = -errno
}
ret := C.strerror_r(
C.int(errno),
(*C.char)(unsafe.Pointer(&buf[0])),
C.size_t(len(buf)))
if ret != 0 {
return errno, ""
}
return errno, C.GoString((*C.char)(unsafe.Pointer(&buf[0])))
}
// FormatErrorCode returns a string that describes the supplied error source
// and error code as a string. Suitable to use in Error() methods. If the
// error code maps to an errno the string will contain a description of the
// error. Otherwise the string will only indicate the source and value if the
// value does not map to a known errno.
func FormatErrorCode(source string, errValue int) string {
_, s := FormatErrno(errValue)
if s == "" {
return fmt.Sprintf("%s: ret=%d", source, errValue)
}
return fmt.Sprintf("%s: ret=%d, %s", source, errValue, s)
}
+14
View File
@@ -0,0 +1,14 @@
// Package log is the internal package for go-ceph logging. This package is only
// used from go-ceph code, not from consumers of go-ceph. go-ceph code uses the
// functions in this package to log information that can't be returned as
// errors. The functions default to no-ops and can be set with the external log
// package common/log by the go-ceph consumers.
package log
func noop(string, ...interface{}) {}
// These variables are set by the common log package.
var (
Warnf = noop
Debugf = noop
)
+64
View File
@@ -0,0 +1,64 @@
package retry
// Hint is a type for retry hints
type Hint interface {
If(bool) Hint
size() int
}
type hintInt int
func (hint hintInt) size() int {
return int(hint)
}
// If is a convenience function, that returns a given hint only if a certain
// condition is met (for example a test for a "buffer too small" error).
// Otherwise it returns a nil which stops the retries.
func (hint hintInt) If(cond bool) Hint {
if cond {
return hint
}
return nil
}
// DoubleSize is a hint to retry with double the size
const DoubleSize = hintInt(0)
// Size returns a hint for a specific size
func Size(s int) Hint {
return hintInt(s)
}
// SizeFunc is used to implement 'resize loops' that hides the complexity of the
// sizing away from most of the application. It's a function that takes a size
// argument and returns nil, if no retry is necessary, or a hint indicating the
// size for the next retry. If errors or other results are required from the
// function, the function can write them to function closures of the surrounding
// scope. See tests for examples.
type SizeFunc func(size int) (hint Hint)
// WithSizes repeatingly calls a SizeFunc with increasing sizes until either it
// returns nil, or the max size has been reached. If the returned hint is
// DoubleSize or indicating a size not greater than the current size, the size
// is doubled. If the hint or next size is greater than the max size, the max
// size is used for a last retry.
func WithSizes(size int, maxSize int, f SizeFunc) {
if size > maxSize {
return
}
for {
hint := f(size)
if hint == nil || size == maxSize {
break
}
if hint.size() > size {
size = hint.size()
} else {
size *= 2
}
if size > maxSize {
size = maxSize
}
}
}
+39
View File
@@ -0,0 +1,39 @@
package timespec
/*
#include <time.h>
*/
import "C"
import (
"unsafe"
"golang.org/x/sys/unix"
)
// Timespec behaves similarly to C's struct timespec.
// Timespec is used to retain fidelity to the C based file systems
// apis that could be lossy with the use of Go time types.
type Timespec unix.Timespec
// CTimespecPtr is an unsafe pointer wrapping C's `struct timespec`.
type CTimespecPtr unsafe.Pointer
// CStructToTimespec creates a new Timespec for the C 'struct timespec'.
func CStructToTimespec(cts CTimespecPtr) Timespec {
t := (*C.struct_timespec)(cts)
return Timespec{
Sec: int64(t.tv_sec),
Nsec: int64(t.tv_nsec),
}
}
// CopyToCStruct copies the time values from a Timespec to a previously
// allocated C `struct timespec`. Due to restrictions on Cgo the C pointer
// must be passed via the CTimespecPtr wrapper.
func CopyToCStruct(ts Timespec, cts CTimespecPtr) {
t := (*C.struct_timespec)(cts)
t.tv_sec = C.time_t(ts.Sec)
t.tv_nsec = C.long(ts.Nsec)
}