Initial QSfera import
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Noah Watkins
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ByteCount represents the size of a volume in bytes.
|
||||
type ByteCount uint64
|
||||
|
||||
// SI byte size constants. keep these private for now.
|
||||
const (
|
||||
kibiByte ByteCount = 1024
|
||||
mebiByte = 1024 * kibiByte
|
||||
gibiByte = 1024 * mebiByte
|
||||
tebiByte = 1024 * gibiByte
|
||||
)
|
||||
|
||||
// resizeValue returns a size value as a string, as needed by the subvolume
|
||||
// resize command json.
|
||||
func (bc ByteCount) resizeValue() string {
|
||||
return uint64String(uint64(bc))
|
||||
}
|
||||
|
||||
// QuotaSize interface values can be used to change the size of a volume.
|
||||
type QuotaSize interface {
|
||||
resizeValue() string
|
||||
}
|
||||
|
||||
// specialSize is a custom non-numeric quota size value.
|
||||
type specialSize string
|
||||
|
||||
// resizeValue for a specialSize returns the original string value.
|
||||
func (s specialSize) resizeValue() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
// Infinite is a special QuotaSize value that can be used to clear size limits
|
||||
// on a subvolume.
|
||||
const Infinite = specialSize("infinite")
|
||||
|
||||
// quotaSizePlaceholder types are helpful to extract QuotaSize typed values
|
||||
// from JSON responses.
|
||||
type quotaSizePlaceholder struct {
|
||||
Value QuotaSize
|
||||
}
|
||||
|
||||
func (p *quotaSizePlaceholder) UnmarshalJSON(b []byte) error {
|
||||
var val interface{}
|
||||
if err := json.Unmarshal(b, &val); err != nil {
|
||||
return err
|
||||
}
|
||||
switch v := val.(type) {
|
||||
case string:
|
||||
if v == string(Infinite) {
|
||||
p.Value = Infinite
|
||||
} else {
|
||||
return fmt.Errorf("quota size: invalid string value: %q", v)
|
||||
}
|
||||
case float64:
|
||||
p.Value = ByteCount(v)
|
||||
default:
|
||||
return fmt.Errorf("quota size: invalid type, string or number required: %v (%T)", val, val)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const notProtectedSuffix = "is not protected"
|
||||
|
||||
// NotProtectedError error values will be returned by CloneSubVolumeSnapshot in
|
||||
// the case that the source snapshot needs to be protected but is not. The
|
||||
// requirement for a snapshot to be protected prior to cloning varies by Ceph
|
||||
// version.
|
||||
type NotProtectedError struct {
|
||||
response
|
||||
}
|
||||
|
||||
// CloneOptions are used to specify optional values to be used when creating a
|
||||
// new subvolume clone.
|
||||
type CloneOptions struct {
|
||||
TargetGroup string
|
||||
PoolLayout string
|
||||
}
|
||||
|
||||
// CloneSubVolumeSnapshot clones the specified snapshot from the subvolume.
|
||||
// The group, subvolume, and snapshot parameters specify the source for the
|
||||
// clone, and only the source. Additional properties of the clone, such as the
|
||||
// subvolume group that the clone will be created in and the pool layout may be
|
||||
// specified using the clone options parameter.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume snapshot clone <volume> --group_name=<group> <subvolume> <snapshot> <name> [...]
|
||||
func (fsa *FSAdmin) CloneSubVolumeSnapshot(volume, group, subvolume, snapshot, name string, o *CloneOptions) error {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolume snapshot clone",
|
||||
"vol_name": volume,
|
||||
"sub_name": subvolume,
|
||||
"snap_name": snapshot,
|
||||
"target_sub_name": name,
|
||||
"format": "json",
|
||||
}
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
if o != nil && o.TargetGroup != NoGroup {
|
||||
m["target_group_name"] = group
|
||||
}
|
||||
if o != nil && o.PoolLayout != "" {
|
||||
m["pool_layout"] = o.PoolLayout
|
||||
}
|
||||
return checkCloneResponse(fsa.marshalMgrCommand(m))
|
||||
}
|
||||
|
||||
func checkCloneResponse(res response) error {
|
||||
if strings.HasSuffix(res.Status(), notProtectedSuffix) {
|
||||
return NotProtectedError{response: res}
|
||||
}
|
||||
return res.NoData().End()
|
||||
}
|
||||
|
||||
// CloneState is used to define constant values used to determine the state of
|
||||
// a clone.
|
||||
type CloneState string
|
||||
|
||||
const (
|
||||
// ClonePending is the state of a pending clone.
|
||||
ClonePending = CloneState("pending")
|
||||
// CloneInProgress is the state of a clone in progress.
|
||||
CloneInProgress = CloneState("in-progress")
|
||||
// CloneComplete is the state of a complete clone.
|
||||
CloneComplete = CloneState("complete")
|
||||
// CloneFailed is the state of a failed clone.
|
||||
CloneFailed = CloneState("failed")
|
||||
)
|
||||
|
||||
// CloneSource contains values indicating the source of a clone.
|
||||
type CloneSource struct {
|
||||
Volume string `json:"volume"`
|
||||
Group string `json:"group"`
|
||||
SubVolume string `json:"subvolume"`
|
||||
Snapshot string `json:"snapshot"`
|
||||
}
|
||||
|
||||
// CloneProgressReport contains the progress report of a subvolume clone.
|
||||
type CloneProgressReport struct {
|
||||
PercentageCloned string `json:"percentage cloned"`
|
||||
AmountCloned string `json:"amount cloned"`
|
||||
FilesCloned string `json:"files cloned"`
|
||||
}
|
||||
|
||||
// CloneStatus reports on the status of a subvolume clone.
|
||||
type CloneStatus struct {
|
||||
State CloneState `json:"state"`
|
||||
Source CloneSource `json:"source"`
|
||||
ProgressReport CloneProgressReport `json:"progress_report"`
|
||||
|
||||
// failure can be obtained through .GetFailure()
|
||||
failure *CloneFailure
|
||||
}
|
||||
|
||||
// CloneFailure reports details of a failure after a subvolume clone failed.
|
||||
type CloneFailure struct {
|
||||
Errno string `json:"errno"`
|
||||
ErrStr string `json:"errstr"`
|
||||
}
|
||||
|
||||
type cloneStatusWrapper struct {
|
||||
Status CloneStatus `json:"status"`
|
||||
Failure CloneFailure `json:"failure"`
|
||||
}
|
||||
|
||||
func parseCloneStatus(res response) (*CloneStatus, error) {
|
||||
var status cloneStatusWrapper
|
||||
if err := res.NoStatus().Unmarshal(&status).End(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if status.Failure.Errno != "" || status.Failure.ErrStr != "" {
|
||||
status.Status.failure = &status.Failure
|
||||
}
|
||||
return &status.Status, nil
|
||||
}
|
||||
|
||||
// CloneStatus returns data reporting the status of a subvolume clone.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs clone status <volume> --group_name=<group> <clone>
|
||||
func (fsa *FSAdmin) CloneStatus(volume, group, clone string) (*CloneStatus, error) {
|
||||
m := map[string]string{
|
||||
"prefix": "fs clone status",
|
||||
"vol_name": volume,
|
||||
"clone_name": clone,
|
||||
"format": "json",
|
||||
}
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
return parseCloneStatus(fsa.marshalMgrCommand(m))
|
||||
}
|
||||
|
||||
// CancelClone stops the background processes that populate a clone.
|
||||
// CancelClone does not delete the clone.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs clone cancel <volume> --group_name=<group> <clone>
|
||||
func (fsa *FSAdmin) CancelClone(volume, group, clone string) error {
|
||||
m := map[string]string{
|
||||
"prefix": "fs clone cancel",
|
||||
"vol_name": volume,
|
||||
"clone_name": clone,
|
||||
"format": "json",
|
||||
}
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
return fsa.marshalMgrCommand(m).NoData().End()
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package admin
|
||||
|
||||
// GetFailure returns details about the CloneStatus when in CloneFailed state.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// Reading the .failure object from the JSON returned by "ceph fs subvolume
|
||||
// snapshot clone"
|
||||
func (cs *CloneStatus) GetFailure() *CloneFailure {
|
||||
return cs.failure
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
Package admin is a convenience layer to support the administration of
|
||||
CephFS volumes, subvolumes, etc.
|
||||
|
||||
Unlike the cephfs package this API does not map to APIs provided by
|
||||
ceph libraries themselves. This API is not yet stable and is subject
|
||||
to change.
|
||||
|
||||
This package only supports ceph "nautilus" and "octopus" at this time.
|
||||
*/
|
||||
package admin
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package admin
|
||||
|
||||
// For APIs that accept extra sets of "boolean" flags we may end up wanting
|
||||
// multiple different sets of supported flags. Example: most rm functions
|
||||
// accept a force flag, but only subvolume delete has retain snapshots.
|
||||
// To make this somewhat uniform in the admin package we define a utility
|
||||
// interface and helper function to merge flags with naming options.
|
||||
|
||||
type flagSet interface {
|
||||
flags() map[string]bool
|
||||
}
|
||||
|
||||
type commonRmFlags struct {
|
||||
force bool
|
||||
}
|
||||
|
||||
func (f commonRmFlags) flags() map[string]bool {
|
||||
o := make(map[string]bool)
|
||||
if f.force {
|
||||
o["force"] = true
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
// SubVolRmFlags does not embed other types to simplify and keep the
|
||||
// interface with the type flat and simple. At the cost of some code
|
||||
// duplication we get a nicer UX for those using the library.
|
||||
|
||||
// SubVolRmFlags may be used to specify behavior modifying flags when
|
||||
// removing sub volumes.
|
||||
type SubVolRmFlags struct {
|
||||
Force bool
|
||||
RetainSnapshots bool
|
||||
}
|
||||
|
||||
func (f SubVolRmFlags) flags() map[string]bool {
|
||||
o := make(map[string]bool)
|
||||
if f.Force {
|
||||
o["force"] = true
|
||||
}
|
||||
if f.RetainSnapshots {
|
||||
o["retain_snapshots"] = true
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
// mergeFlags combines a set of key-value settings with any type implementing
|
||||
// the flagSet interface.
|
||||
func mergeFlags(m map[string]string, f flagSet) map[string]interface{} {
|
||||
o := make(map[string]interface{})
|
||||
for k, v := range m {
|
||||
o[k] = v
|
||||
}
|
||||
for k, v := range f.flags() {
|
||||
o[k] = v
|
||||
}
|
||||
return o
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
package admin
|
||||
|
||||
import "fmt"
|
||||
|
||||
// fixedPointFloat is a custom type that implements the MarshalJSON interface.
|
||||
// This is used to format float64 values to two decimal places.
|
||||
// By default these get converted to integers in the JSON output and
|
||||
// fail the command.
|
||||
type fixedPointFloat float64
|
||||
|
||||
// MarshalJSON provides a custom implementation for the JSON marshalling
|
||||
// of fixedPointFloat. It formats the float to two decimal places.
|
||||
func (fpf fixedPointFloat) MarshalJSON() ([]byte, error) {
|
||||
return []byte(fmt.Sprintf("%.2f", float64(fpf))), nil
|
||||
}
|
||||
|
||||
// fSQuiesceFields is the internal type used to create JSON for ceph.
|
||||
// See FSQuiesceOptions for the type that users of the library
|
||||
// interact with.
|
||||
type fSQuiesceFields struct {
|
||||
Prefix string `json:"prefix"`
|
||||
VolName string `json:"vol_name"`
|
||||
GroupName string `json:"group_name,omitempty"`
|
||||
Members []string `json:"members,omitempty"`
|
||||
SetId string `json:"set_id,omitempty"`
|
||||
Timeout fixedPointFloat `json:"timeout,omitempty"`
|
||||
Expiration fixedPointFloat `json:"expiration,omitempty"`
|
||||
AwaitFor fixedPointFloat `json:"await_for,omitempty"`
|
||||
Await bool `json:"await,omitempty"`
|
||||
IfVersion int `json:"if_version,omitempty"`
|
||||
Include bool `json:"include,omitempty"`
|
||||
Exclude bool `json:"exclude,omitempty"`
|
||||
Reset bool `json:"reset,omitempty"`
|
||||
Release bool `json:"release,omitempty"`
|
||||
Query bool `json:"query,omitempty"`
|
||||
All bool `json:"all,omitempty"`
|
||||
Cancel bool `json:"cancel,omitempty"`
|
||||
}
|
||||
|
||||
// FSQuiesceOptions are used to specify optional, non-identifying, values
|
||||
// to be used when quiescing a cephfs volume.
|
||||
type FSQuiesceOptions struct {
|
||||
Timeout float64
|
||||
Expiration float64
|
||||
AwaitFor float64
|
||||
Await bool
|
||||
IfVersion int
|
||||
Include bool
|
||||
Exclude bool
|
||||
Reset bool
|
||||
Release bool
|
||||
Query bool
|
||||
All bool
|
||||
Cancel bool
|
||||
}
|
||||
|
||||
// toFields is used to convert the FSQuiesceOptions to the internal
|
||||
// fSQuiesceFields type.
|
||||
func (o *FSQuiesceOptions) toFields(volume, group string, subvolumes []string, setId string) *fSQuiesceFields {
|
||||
return &fSQuiesceFields{
|
||||
Prefix: "fs quiesce",
|
||||
VolName: volume,
|
||||
GroupName: group,
|
||||
Members: subvolumes,
|
||||
SetId: setId,
|
||||
Timeout: fixedPointFloat(o.Timeout),
|
||||
Expiration: fixedPointFloat(o.Expiration),
|
||||
AwaitFor: fixedPointFloat(o.AwaitFor),
|
||||
Await: o.Await,
|
||||
IfVersion: o.IfVersion,
|
||||
Include: o.Include,
|
||||
Exclude: o.Exclude,
|
||||
Reset: o.Reset,
|
||||
Release: o.Release,
|
||||
Query: o.Query,
|
||||
All: o.All,
|
||||
Cancel: o.Cancel,
|
||||
}
|
||||
}
|
||||
|
||||
// QuiesceState is used to report the state of a quiesced fs volume.
|
||||
type QuiesceState struct {
|
||||
Name string `json:"name"`
|
||||
Age float64 `json:"age"`
|
||||
}
|
||||
|
||||
// QuiesceInfoMember is used to report the state of a quiesced fs volume.
|
||||
// This is part of sets members object array in the json.
|
||||
type QuiesceInfoMember struct {
|
||||
Excluded bool `json:"excluded"`
|
||||
State QuiesceState `json:"state"`
|
||||
}
|
||||
|
||||
// QuiesceInfo reports various informational values about a quiesced volume.
|
||||
// This is returned as sets object array in the json.
|
||||
type QuiesceInfo struct {
|
||||
Version int `json:"version"`
|
||||
AgeRef float64 `json:"age_ref"`
|
||||
State QuiesceState `json:"state"`
|
||||
Timeout float64 `json:"timeout"`
|
||||
Expiration float64 `json:"expiration"`
|
||||
Members map[string]QuiesceInfoMember `json:"members"`
|
||||
}
|
||||
|
||||
// FSQuiesceInfo reports various informational values about quiesced volumes.
|
||||
type FSQuiesceInfo struct {
|
||||
Epoch int `json:"epoch"`
|
||||
SetVersion int `json:"set_version"`
|
||||
Sets map[string]QuiesceInfo `json:"sets"`
|
||||
}
|
||||
|
||||
// parseFSQuiesceInfo is used to parse the response from the quiesce command. It returns a FSQuiesceInfo object.
|
||||
func parseFSQuiesceInfo(res response) (*FSQuiesceInfo, error) {
|
||||
var info FSQuiesceInfo
|
||||
if err := res.NoStatus().Unmarshal(&info).End(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
// FSQuiesce will quiesce the specified subvolumes in a volume.
|
||||
// Quiescing a fs will prevent new writes to the subvolumes.
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs quiesce <volume>
|
||||
func (fsa *FSAdmin) FSQuiesce(volume, group string, subvolumes []string, setId string, o *FSQuiesceOptions) (*FSQuiesceInfo, error) {
|
||||
if o == nil {
|
||||
o = &FSQuiesceOptions{}
|
||||
}
|
||||
f := o.toFields(volume, group, subvolumes, setId)
|
||||
|
||||
return parseFSQuiesceInfo(fsa.marshalMgrCommand(f))
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
ccom "github.com/ceph/go-ceph/common/commands"
|
||||
"github.com/ceph/go-ceph/internal/commands"
|
||||
"github.com/ceph/go-ceph/rados"
|
||||
)
|
||||
|
||||
// RadosCommander provides an interface to execute JSON-formatted commands that
|
||||
// allow the cephfs administrative functions to interact with the Ceph cluster.
|
||||
type RadosCommander = ccom.RadosCommander
|
||||
|
||||
// FSAdmin is used to administrate CephFS within a ceph cluster.
|
||||
type FSAdmin struct {
|
||||
conn RadosCommander
|
||||
}
|
||||
|
||||
// NewFromConn creates an FSAdmin management object from a preexisting
|
||||
// rados connection. The existing connection can be rados.Conn or any
|
||||
// type implementing the RadosCommander interface. This may be useful
|
||||
// if the calling layer needs to inject additional logging, error handling,
|
||||
// fault injection, etc.
|
||||
func NewFromConn(conn RadosCommander) *FSAdmin {
|
||||
return &FSAdmin{conn}
|
||||
}
|
||||
|
||||
func (fsa *FSAdmin) validate() error {
|
||||
if fsa.conn == 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 (fsa *FSAdmin) rawMgrCommand(buf []byte) response {
|
||||
return commands.RawMgrCommand(fsa.conn, buf)
|
||||
}
|
||||
|
||||
// marshalMgrCommand takes an generic interface{} value, converts it to JSON and
|
||||
// sends the json to the MGR as a command.
|
||||
func (fsa *FSAdmin) marshalMgrCommand(v interface{}) response {
|
||||
return commands.MarshalMgrCommand(fsa.conn, v)
|
||||
}
|
||||
|
||||
// rawMonCommand takes a byte buffer and sends it to the MON as a command.
|
||||
// The buffer is expected to contain preformatted JSON.
|
||||
func (fsa *FSAdmin) rawMonCommand(buf []byte) response {
|
||||
return commands.RawMonCommand(fsa.conn, buf)
|
||||
}
|
||||
|
||||
// marshalMonCommand takes an generic interface{} value, converts it to JSON and
|
||||
// sends the json to the MGR as a command.
|
||||
func (fsa *FSAdmin) marshalMonCommand(v interface{}) response {
|
||||
return commands.MarshalMonCommand(fsa.conn, v)
|
||||
}
|
||||
|
||||
type listNamedResult struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func parseListNames(res response) ([]string, error) {
|
||||
var r []listNamedResult
|
||||
if err := res.NoStatus().Unmarshal(&r).End(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vl := make([]string, len(r))
|
||||
for i := range r {
|
||||
vl[i] = r[i].Name
|
||||
}
|
||||
return vl, nil
|
||||
}
|
||||
|
||||
func parseListKeyValues(res response) (map[string]string, error) {
|
||||
var x map[string]string
|
||||
if err := res.NoStatus().Unmarshal(&x).End(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// parsePathResponse returns a cleaned up path from requests that get a path
|
||||
// unless an error is encountered, then an error is returned.
|
||||
func parsePathResponse(res response) (string, error) {
|
||||
if res2 := res.NoStatus(); !res2.Ok() {
|
||||
return "", res.End()
|
||||
}
|
||||
b := res.Body()
|
||||
// if there's a trailing newline in the buffer strip it.
|
||||
// ceph assumes a CLI wants the output of the buffer and there's
|
||||
// no format=json mode available currently.
|
||||
for len(b) >= 1 && b[len(b)-1] == '\n' {
|
||||
b = b[:len(b)-1]
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
// modeString converts a unix-style mode value to a string-ified version in an
|
||||
// octal representation (e.g. "777", "700", etc). This format is expected by
|
||||
// some of the ceph JSON command inputs.
|
||||
func modeString(m int, force bool) string {
|
||||
if force || m != 0 {
|
||||
return strconv.FormatInt(int64(m), 8)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// uint64String converts a uint64 to a string. Some of the ceph json commands
|
||||
// can take a string or "int" (as a string). This is a common function for
|
||||
// doing that conversion.
|
||||
func uint64String(v uint64) string {
|
||||
return strconv.FormatUint(uint64(v), 10)
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
//go:build !(nautilus || octopus || pacific)
|
||||
// +build !nautilus,!octopus,!pacific
|
||||
|
||||
package admin
|
||||
|
||||
// GetMetadata gets custom metadata on the subvolume in a volume belonging to
|
||||
// an optional subvolume group based on provided key name.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume metadata get <vol_name> <sub_name> <key_name> [--group_name <subvol_group_name>]
|
||||
func (fsa *FSAdmin) GetMetadata(volume, group, subvolume, key string) (string, error) {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolume metadata get",
|
||||
"format": "json",
|
||||
"vol_name": volume,
|
||||
"sub_name": subvolume,
|
||||
"key_name": key,
|
||||
}
|
||||
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
|
||||
return parsePathResponse(fsa.marshalMgrCommand(m))
|
||||
}
|
||||
|
||||
// SetMetadata sets custom metadata on the subvolume in a volume belonging to
|
||||
// an optional subvolume group as a key-value pair.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume metadata set <vol_name> <sub_name> <key_name> <value> [--group_name <subvol_group_name>]
|
||||
func (fsa *FSAdmin) SetMetadata(volume, group, subvolume, key, value string) error {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolume metadata set",
|
||||
"format": "json",
|
||||
"vol_name": volume,
|
||||
"sub_name": subvolume,
|
||||
"key_name": key,
|
||||
"value": value,
|
||||
}
|
||||
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
|
||||
return fsa.marshalMgrCommand(m).NoData().End()
|
||||
}
|
||||
|
||||
// RemoveMetadata removes custom metadata set on the subvolume in a volume
|
||||
// belonging to an optional subvolume group using the metadata key.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume metadata rm <vol_name> <sub_name> <key_name> [--group_name <subvol_group_name>]
|
||||
func (fsa *FSAdmin) RemoveMetadata(volume, group, subvolume, key string) error {
|
||||
return fsa.rmSubVolumeMetadata(volume, group, subvolume, key, commonRmFlags{})
|
||||
}
|
||||
|
||||
// ForceRemoveMetadata attempt to forcefully remove custom metadata set on
|
||||
// the subvolume in a volume belonging to an optional subvolume group using
|
||||
// the metadata key.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume metadata rm <vol_name> <sub_name> <key_name> [--group_name <subvol_group_name>] --force
|
||||
func (fsa *FSAdmin) ForceRemoveMetadata(volume, group, subvolume, key string) error {
|
||||
return fsa.rmSubVolumeMetadata(volume, group, subvolume, key, commonRmFlags{force: true})
|
||||
}
|
||||
|
||||
func (fsa *FSAdmin) rmSubVolumeMetadata(volume, group, subvolume, key string, o commonRmFlags) error {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolume metadata rm",
|
||||
"format": "json",
|
||||
"vol_name": volume,
|
||||
"sub_name": subvolume,
|
||||
"key_name": key,
|
||||
}
|
||||
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
|
||||
return fsa.marshalMgrCommand(mergeFlags(m, o)).NoData().End()
|
||||
}
|
||||
|
||||
// ListMetadata lists custom metadata (key-value pairs) set on the subvolume
|
||||
// in a volume belonging to an optional subvolume group.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume metadata ls <vol_name> <sub_name> [--group_name <subvol_group_name>]
|
||||
func (fsa *FSAdmin) ListMetadata(volume, group, subvolume string) (map[string]string, error) {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolume metadata ls",
|
||||
"format": "json",
|
||||
"vol_name": volume,
|
||||
"sub_name": subvolume,
|
||||
}
|
||||
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
|
||||
return parseListKeyValues(fsa.marshalMgrCommand(m))
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"github.com/ceph/go-ceph/common/admin/manager"
|
||||
)
|
||||
|
||||
const mirroring = "mirroring"
|
||||
|
||||
// EnableMirroringModule will enable the mirroring module for cephfs.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph mgr module enable mirroring [--force]
|
||||
func (fsa *FSAdmin) EnableMirroringModule(force bool) error {
|
||||
mgradmin := manager.NewFromConn(fsa.conn)
|
||||
return mgradmin.EnableModule(mirroring, force)
|
||||
}
|
||||
|
||||
// DisableMirroringModule will disable the mirroring module for cephfs.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph mgr module disable mirroring
|
||||
func (fsa *FSAdmin) DisableMirroringModule() error {
|
||||
mgradmin := manager.NewFromConn(fsa.conn)
|
||||
return mgradmin.DisableModule(mirroring)
|
||||
}
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
ccom "github.com/ceph/go-ceph/common/commands"
|
||||
"github.com/ceph/go-ceph/internal/commands"
|
||||
)
|
||||
|
||||
// SnapshotMirrorAdmin helps administer the snapshot mirroring features of
|
||||
// cephfs. Snapshot mirroring is only available in ceph pacific and later.
|
||||
type SnapshotMirrorAdmin struct {
|
||||
conn ccom.MgrCommander
|
||||
}
|
||||
|
||||
// SnapshotMirror returns a new SnapshotMirrorAdmin to be used for the
|
||||
// administration of snapshot mirroring features.
|
||||
func (fsa *FSAdmin) SnapshotMirror() *SnapshotMirrorAdmin {
|
||||
return &SnapshotMirrorAdmin{conn: fsa.conn}
|
||||
}
|
||||
|
||||
// Enable snapshot mirroring for the given file system.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs snapshot mirror enable <fs_name>
|
||||
func (sma *SnapshotMirrorAdmin) Enable(fsname string) error {
|
||||
m := map[string]string{
|
||||
"prefix": "fs snapshot mirror enable",
|
||||
"fs_name": fsname,
|
||||
"format": "json",
|
||||
}
|
||||
return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
|
||||
}
|
||||
|
||||
// Disable snapshot mirroring for the given file system.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs snapshot mirror disable <fs_name>
|
||||
func (sma *SnapshotMirrorAdmin) Disable(fsname string) error {
|
||||
m := map[string]string{
|
||||
"prefix": "fs snapshot mirror disable",
|
||||
"fs_name": fsname,
|
||||
"format": "json",
|
||||
}
|
||||
return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
|
||||
}
|
||||
|
||||
// Add a path in the file system to be mirrored.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs snapshot mirror add <fs_name> <path>
|
||||
func (sma *SnapshotMirrorAdmin) Add(fsname, path string) error {
|
||||
m := map[string]string{
|
||||
"prefix": "fs snapshot mirror add",
|
||||
"fs_name": fsname,
|
||||
"path": path,
|
||||
"format": "json",
|
||||
}
|
||||
return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
|
||||
}
|
||||
|
||||
// Remove a path in the file system from mirroring.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs snapshot mirror remove <fs_name> <path>
|
||||
func (sma *SnapshotMirrorAdmin) Remove(fsname, path string) error {
|
||||
m := map[string]string{
|
||||
"prefix": "fs snapshot mirror remove",
|
||||
"fs_name": fsname,
|
||||
"path": path,
|
||||
"format": "json",
|
||||
}
|
||||
return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
|
||||
}
|
||||
|
||||
type bootstrapTokenResponse struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// CreatePeerBootstrapToken returns a token that can be used to create
|
||||
// a peering association between this site an another site.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs snapshot mirror peer_bootstrap create <fs_name> <client_entity> <site-name>
|
||||
func (sma *SnapshotMirrorAdmin) CreatePeerBootstrapToken(
|
||||
fsname, client, site string) (string, error) {
|
||||
m := map[string]string{
|
||||
"prefix": "fs snapshot mirror peer_bootstrap create",
|
||||
"fs_name": fsname,
|
||||
"client_name": client,
|
||||
"format": "json",
|
||||
}
|
||||
if site != "" {
|
||||
m["site_name"] = site
|
||||
}
|
||||
var bt bootstrapTokenResponse
|
||||
err := commands.MarshalMgrCommand(sma.conn, m).NoStatus().Unmarshal(&bt).End()
|
||||
return bt.Token, err
|
||||
}
|
||||
|
||||
// ImportPeerBoostrapToken creates an association between another site, one
|
||||
// that has provided a token, with the current site.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs snapshot mirror peer_bootstrap import <fs_name> <token>
|
||||
func (sma *SnapshotMirrorAdmin) ImportPeerBoostrapToken(fsname, token string) error {
|
||||
m := map[string]string{
|
||||
"prefix": "fs snapshot mirror peer_bootstrap import",
|
||||
"fs_name": fsname,
|
||||
"token": token,
|
||||
"format": "json",
|
||||
}
|
||||
return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
|
||||
}
|
||||
|
||||
// DaemonID represents the ID of a cephfs mirroring daemon.
|
||||
type DaemonID uint
|
||||
|
||||
// FileSystemID represents the ID of a cephfs file system.
|
||||
type FileSystemID uint
|
||||
|
||||
// PeerUUID represents the UUID of a cephfs mirroring peer.
|
||||
type PeerUUID string
|
||||
|
||||
// DaemonStatusPeer contains fields detailing a remote peer.
|
||||
type DaemonStatusPeer struct {
|
||||
ClientName string `json:"client_name"`
|
||||
ClusterName string `json:"cluster_name"`
|
||||
FSName string `json:"fs_name"`
|
||||
}
|
||||
|
||||
// DaemonStatusPeerStats contains fields detailing the a remote peer's stats.
|
||||
type DaemonStatusPeerStats struct {
|
||||
FailureCount uint64 `json:"failure_count"`
|
||||
RecoveryCount uint64 `json:"recovery_count"`
|
||||
}
|
||||
|
||||
// DaemonStatusPeerInfo contains fields representing information about a remote peer.
|
||||
type DaemonStatusPeerInfo struct {
|
||||
UUID PeerUUID `json:"uuid"`
|
||||
Remote DaemonStatusPeer `json:"remote"`
|
||||
Stats DaemonStatusPeerStats `json:"stats"`
|
||||
}
|
||||
|
||||
// DaemonStatusFileSystemInfo represents information about a mirrored file system.
|
||||
type DaemonStatusFileSystemInfo struct {
|
||||
FileSystemID FileSystemID `json:"filesystem_id"`
|
||||
Name string `json:"name"`
|
||||
DirectoryCount int64 `json:"directory_count"`
|
||||
Peers []DaemonStatusPeerInfo `json:"peers"`
|
||||
}
|
||||
|
||||
// DaemonStatusInfo maps file system IDs to information about that file system.
|
||||
type DaemonStatusInfo struct {
|
||||
DaemonID DaemonID `json:"daemon_id"`
|
||||
FileSystems []DaemonStatusFileSystemInfo `json:"filesystems"`
|
||||
}
|
||||
|
||||
// DaemonStatusResults maps mirroring daemon IDs to information about that
|
||||
// mirroring daemon.
|
||||
type DaemonStatusResults []DaemonStatusInfo
|
||||
|
||||
func parseDaemonStatus(res response) (DaemonStatusResults, error) {
|
||||
var dsr DaemonStatusResults
|
||||
if err := res.NoStatus().Unmarshal(&dsr).End(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dsr, nil
|
||||
}
|
||||
|
||||
// DaemonStatus returns information on the status of cephfs mirroring daemons
|
||||
// associated with the given file system.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs snapshot mirror daemon status <fs_name>
|
||||
func (sma *SnapshotMirrorAdmin) DaemonStatus(fsname string) (
|
||||
DaemonStatusResults, error) {
|
||||
// ---
|
||||
m := map[string]string{
|
||||
"prefix": "fs snapshot mirror daemon status",
|
||||
"fs_name": fsname,
|
||||
"format": "json",
|
||||
}
|
||||
return parseDaemonStatus(commands.MarshalMgrCommand(sma.conn, m))
|
||||
}
|
||||
|
||||
// PeerInfo includes information about a cephfs mirroring peer.
|
||||
type PeerInfo struct {
|
||||
ClientName string `json:"client_name"`
|
||||
SiteName string `json:"site_name"`
|
||||
FSName string `json:"fs_name"`
|
||||
MonHost string `json:"mon_host"`
|
||||
}
|
||||
|
||||
// PeerListResults maps a peer's UUID to information about that peer.
|
||||
type PeerListResults map[PeerUUID]PeerInfo
|
||||
|
||||
func parsePeerList(res response) (PeerListResults, error) {
|
||||
var plr PeerListResults
|
||||
if err := res.NoStatus().Unmarshal(&plr).End(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return plr, nil
|
||||
}
|
||||
|
||||
// PeerList returns information about peers associated with the given file system.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs snapshot mirror peer_list <fs_name>
|
||||
func (sma *SnapshotMirrorAdmin) PeerList(fsname string) (
|
||||
PeerListResults, error) {
|
||||
// ---
|
||||
m := map[string]string{
|
||||
"prefix": "fs snapshot mirror peer_list",
|
||||
"fs_name": fsname,
|
||||
"format": "json",
|
||||
}
|
||||
return parsePeerList(commands.MarshalMgrCommand(sma.conn, m))
|
||||
}
|
||||
|
||||
/*
|
||||
DirMap - figure out what last_shuffled is supposed to mean and, if it is a time
|
||||
like it seems to be, how best to represent in Go.
|
||||
|
||||
DirMap TODO
|
||||
ceph fs snapshot mirror dirmap
|
||||
func (sma *SnapshotMirrorAdmin) DirMap(fsname, path string) error {
|
||||
m := map[string]string{
|
||||
"prefix": "fs snapshot mirror dirmap",
|
||||
"fs_name": fsname,
|
||||
"path": path,
|
||||
"format": "json",
|
||||
}
|
||||
return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
|
||||
}
|
||||
*/
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
//go:build !nautilus
|
||||
// +build !nautilus
|
||||
|
||||
package admin
|
||||
|
||||
// PinSubVolume pins subvolume to ranks according to policies. A valid pin
|
||||
// setting value depends on the type of pin as described in the docs from
|
||||
// https://docs.ceph.com/en/latest/cephfs/multimds/#cephfs-pinning and
|
||||
// https://docs.ceph.com/en/latest/cephfs/multimds/#setting-subtree-partitioning-policies
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume pin <vol_name> <sub_name> <pin_type> <pin_setting>
|
||||
func (fsa *FSAdmin) PinSubVolume(volume, subvolume, pintype, pinsetting string) (string, error) {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolume pin",
|
||||
"format": "json",
|
||||
"vol_name": volume,
|
||||
"sub_name": subvolume,
|
||||
"pin_type": pintype,
|
||||
"pin_setting": pinsetting,
|
||||
}
|
||||
|
||||
return parsePathResponse(fsa.marshalMgrCommand(m))
|
||||
}
|
||||
|
||||
// PinSubVolumeGroup pins subvolume to ranks according to policies. A valid pin
|
||||
// setting value depends on the type of pin as described in the docs from
|
||||
// https://docs.ceph.com/en/latest/cephfs/multimds/#cephfs-pinning and
|
||||
// https://docs.ceph.com/en/latest/cephfs/multimds/#setting-subtree-partitioning-policies
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolumegroup pin <vol_name> <group_name> <pin_type> <pin_setting>
|
||||
func (fsa *FSAdmin) PinSubVolumeGroup(volume, group, pintype, pinsetting string) (string, error) {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolumegroup pin",
|
||||
"format": "json",
|
||||
"vol_name": volume,
|
||||
"group_name": group,
|
||||
"pin_type": pintype,
|
||||
"pin_setting": pinsetting,
|
||||
}
|
||||
|
||||
return parsePathResponse(fsa.marshalMgrCommand(m))
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"github.com/ceph/go-ceph/internal/commands"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrStatusNotEmpty is an alias for commands.ErrStatusNotEmpty
|
||||
ErrStatusNotEmpty = commands.ErrStatusNotEmpty
|
||||
// ErrBodyNotEmpty is an alias for commands.ErrBodyNotEmpty
|
||||
ErrBodyNotEmpty = commands.ErrBodyNotEmpty
|
||||
)
|
||||
|
||||
type response = commands.Response
|
||||
|
||||
// NotImplementedError is an alias for commands.NotImplementedError.
|
||||
type NotImplementedError = commands.NotImplementedError
|
||||
|
||||
// newResponse returns a response.
|
||||
func newResponse(b []byte, s string, e error) response {
|
||||
return commands.NewResponse(b, s, e)
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
//go:build !(nautilus || octopus || pacific)
|
||||
// +build !nautilus,!octopus,!pacific
|
||||
|
||||
package admin
|
||||
|
||||
// GetSnapshotMetadata gets custom metadata on the subvolume snapshot in a
|
||||
// volume belonging to an optional subvolume group based on provided key name.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume snapshot metadata get <vol_name> <sub_name> <snap_name> <key_name> [--group_name <subvol_group_name>]
|
||||
func (fsa *FSAdmin) GetSnapshotMetadata(volume, group, subvolume, snapname, key string) (string, error) {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolume snapshot metadata get",
|
||||
"format": "json",
|
||||
"vol_name": volume,
|
||||
"sub_name": subvolume,
|
||||
"snap_name": snapname,
|
||||
"key_name": key,
|
||||
}
|
||||
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
|
||||
return parsePathResponse(fsa.marshalMgrCommand(m))
|
||||
}
|
||||
|
||||
// SetSnapshotMetadata sets custom metadata on the subvolume snapshot in a
|
||||
// volume belonging to an optional subvolume group as a key-value pair.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume snapshot metadata set <vol_name> <sub_name> <snap_name> <key_name> <value> [--group_name <subvol_group_name>]
|
||||
func (fsa *FSAdmin) SetSnapshotMetadata(volume, group, subvolume, snapname, key, value string) error {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolume snapshot metadata set",
|
||||
"format": "json",
|
||||
"vol_name": volume,
|
||||
"sub_name": subvolume,
|
||||
"snap_name": snapname,
|
||||
"key_name": key,
|
||||
"value": value,
|
||||
}
|
||||
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
|
||||
return fsa.marshalMgrCommand(m).NoData().End()
|
||||
}
|
||||
|
||||
// RemoveSnapshotMetadata removes custom metadata set on the subvolume
|
||||
// snapshot in a volume belonging to an optional subvolume group using the
|
||||
// metadata key.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume snapshot metadata rm <vol_name> <sub_name> <snap_name> <key_name> [--group_name <subvol_group_name>]
|
||||
func (fsa *FSAdmin) RemoveSnapshotMetadata(volume, group, subvolume, snapname, key string) error {
|
||||
return fsa.rmSubVolumeSnapShotMetadata(volume, group, subvolume, snapname, key, commonRmFlags{})
|
||||
}
|
||||
|
||||
// ForceRemoveSnapshotMetadata attempt to forcefully remove custom metadata
|
||||
// set on the subvolume snapshot in a volume belonging to an optional
|
||||
// subvolume group using the metadata key.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume snapshot metadata rm <vol_name> <sub_name> <snap_name> <key_name> [--group_name <subvol_group_name>] --force
|
||||
func (fsa *FSAdmin) ForceRemoveSnapshotMetadata(volume, group, subvolume, snapname, key string) error {
|
||||
return fsa.rmSubVolumeSnapShotMetadata(volume, group, subvolume, snapname, key, commonRmFlags{force: true})
|
||||
}
|
||||
|
||||
func (fsa *FSAdmin) rmSubVolumeSnapShotMetadata(volume, group, subvolume, snapname, key string, o commonRmFlags) error {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolume snapshot metadata rm",
|
||||
"format": "json",
|
||||
"vol_name": volume,
|
||||
"sub_name": subvolume,
|
||||
"snap_name": snapname,
|
||||
"key_name": key,
|
||||
}
|
||||
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
|
||||
return fsa.marshalMgrCommand(mergeFlags(m, o)).NoData().End()
|
||||
}
|
||||
|
||||
// ListSnapshotMetadata lists custom metadata (key-value pairs) set on the subvolume
|
||||
// snapshot in a volume belonging to an optional subvolume group.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume snapshot metadata ls <vol_name> <sub_name> <snap_name> [--group_name <subvol_group_name>]
|
||||
func (fsa *FSAdmin) ListSnapshotMetadata(volume, group, subvolume, snapname string) (map[string]string, error) {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolume snapshot metadata ls",
|
||||
"format": "json",
|
||||
"vol_name": volume,
|
||||
"sub_name": subvolume,
|
||||
"snap_name": snapname,
|
||||
}
|
||||
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
|
||||
return parseListKeyValues(fsa.marshalMgrCommand(m))
|
||||
}
|
||||
+425
@@ -0,0 +1,425 @@
|
||||
package admin
|
||||
|
||||
// this is the internal type used to create JSON for ceph.
|
||||
// See SubVolumeOptions for the type that users of the library
|
||||
// interact with.
|
||||
// note that the ceph json takes mode as a string.
|
||||
type subVolumeFields struct {
|
||||
Prefix string `json:"prefix"`
|
||||
Format string `json:"format"`
|
||||
VolName string `json:"vol_name"`
|
||||
GroupName string `json:"group_name,omitempty"`
|
||||
SubName string `json:"sub_name"`
|
||||
Size ByteCount `json:"size,omitempty"`
|
||||
Uid int `json:"uid,omitempty"`
|
||||
Gid int `json:"gid,omitempty"`
|
||||
Mode string `json:"mode,omitempty"`
|
||||
PoolLayout string `json:"pool_layout,omitempty"`
|
||||
NamespaceIsolated bool `json:"namespace_isolated"`
|
||||
}
|
||||
|
||||
// SubVolumeOptions are used to specify optional, non-identifying, values
|
||||
// to be used when creating a new subvolume.
|
||||
type SubVolumeOptions struct {
|
||||
Size ByteCount
|
||||
Uid int
|
||||
Gid int
|
||||
Mode int
|
||||
PoolLayout string
|
||||
NamespaceIsolated bool
|
||||
}
|
||||
|
||||
func (s *SubVolumeOptions) toFields(v, g, n string) *subVolumeFields {
|
||||
return &subVolumeFields{
|
||||
Prefix: "fs subvolume create",
|
||||
Format: "json",
|
||||
VolName: v,
|
||||
GroupName: g,
|
||||
SubName: n,
|
||||
Size: s.Size,
|
||||
Uid: s.Uid,
|
||||
Gid: s.Gid,
|
||||
Mode: modeString(s.Mode, false),
|
||||
PoolLayout: s.PoolLayout,
|
||||
NamespaceIsolated: s.NamespaceIsolated,
|
||||
}
|
||||
}
|
||||
|
||||
// NoGroup should be used when an optional subvolume group name is not
|
||||
// specified.
|
||||
const NoGroup = ""
|
||||
|
||||
// CreateSubVolume sends a request to create a CephFS subvolume in a volume,
|
||||
// belonging to an optional subvolume group.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume create <volume> --group-name=<group> <name> ...
|
||||
func (fsa *FSAdmin) CreateSubVolume(volume, group, name string, o *SubVolumeOptions) error {
|
||||
if o == nil {
|
||||
o = &SubVolumeOptions{}
|
||||
}
|
||||
f := o.toFields(volume, group, name)
|
||||
return fsa.marshalMgrCommand(f).NoData().End()
|
||||
}
|
||||
|
||||
// ListSubVolumes returns a list of subvolumes belonging to the volume and
|
||||
// optional subvolume group.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume ls <volume> --group-name=<group>
|
||||
func (fsa *FSAdmin) ListSubVolumes(volume, group string) ([]string, error) {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolume ls",
|
||||
"vol_name": volume,
|
||||
"format": "json",
|
||||
}
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
return parseListNames(fsa.marshalMgrCommand(m))
|
||||
}
|
||||
|
||||
// RemoveSubVolume will delete a CephFS subvolume in a volume and optional
|
||||
// subvolume group.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume rm <volume> --group-name=<group> <name>
|
||||
func (fsa *FSAdmin) RemoveSubVolume(volume, group, name string) error {
|
||||
return fsa.RemoveSubVolumeWithFlags(volume, group, name, SubVolRmFlags{})
|
||||
}
|
||||
|
||||
// ForceRemoveSubVolume will delete a CephFS subvolume in a volume and optional
|
||||
// subvolume group.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume rm <volume> --group-name=<group> <name> --force
|
||||
func (fsa *FSAdmin) ForceRemoveSubVolume(volume, group, name string) error {
|
||||
return fsa.RemoveSubVolumeWithFlags(volume, group, name, SubVolRmFlags{Force: true})
|
||||
}
|
||||
|
||||
// RemoveSubVolumeWithFlags will delete a CephFS subvolume in a volume and
|
||||
// optional subvolume group. This function accepts a SubVolRmFlags type that
|
||||
// can be used to specify flags that modify the operations behavior.
|
||||
// Equivalent to RemoveSubVolume with no flags set.
|
||||
// Equivalent to ForceRemoveSubVolume if only the "Force" flag is set.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume rm <volume> --group-name=<group> <name> [...flags...]
|
||||
func (fsa *FSAdmin) RemoveSubVolumeWithFlags(volume, group, name string, o SubVolRmFlags) error {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolume rm",
|
||||
"vol_name": volume,
|
||||
"sub_name": name,
|
||||
"format": "json",
|
||||
}
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
return fsa.marshalMgrCommand(mergeFlags(m, o)).NoData().End()
|
||||
}
|
||||
|
||||
type subVolumeResizeFields struct {
|
||||
Prefix string `json:"prefix"`
|
||||
Format string `json:"format"`
|
||||
VolName string `json:"vol_name"`
|
||||
GroupName string `json:"group_name,omitempty"`
|
||||
SubName string `json:"sub_name"`
|
||||
NewSize string `json:"new_size"`
|
||||
NoShrink bool `json:"no_shrink"`
|
||||
}
|
||||
|
||||
// SubVolumeResizeResult reports the size values returned by the
|
||||
// ResizeSubVolume function, as reported by Ceph.
|
||||
type SubVolumeResizeResult struct {
|
||||
BytesUsed ByteCount `json:"bytes_used"`
|
||||
BytesQuota ByteCount `json:"bytes_quota"`
|
||||
BytesPercent string `json:"bytes_pcent"`
|
||||
}
|
||||
|
||||
// ResizeSubVolume will resize a CephFS subvolume. The newSize value may be a
|
||||
// ByteCount or the special Infinite constant. Setting noShrink to true will
|
||||
// prevent reducing the size of the volume below the current used size.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume resize <volume> --group-name=<group> <name> ...
|
||||
func (fsa *FSAdmin) ResizeSubVolume(
|
||||
volume, group, name string,
|
||||
newSize QuotaSize, noShrink bool) (*SubVolumeResizeResult, error) {
|
||||
|
||||
f := &subVolumeResizeFields{
|
||||
Prefix: "fs subvolume resize",
|
||||
Format: "json",
|
||||
VolName: volume,
|
||||
GroupName: group,
|
||||
SubName: name,
|
||||
NewSize: newSize.resizeValue(),
|
||||
NoShrink: noShrink,
|
||||
}
|
||||
var result []*SubVolumeResizeResult
|
||||
res := fsa.marshalMgrCommand(f)
|
||||
if err := res.NoStatus().Unmarshal(&result).End(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result[0], nil
|
||||
}
|
||||
|
||||
// SubVolumePath returns the path to the subvolume from the root of the file system.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume getpath <volume> --group-name=<group> <name>
|
||||
func (fsa *FSAdmin) SubVolumePath(volume, group, name string) (string, error) {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolume getpath",
|
||||
"vol_name": volume,
|
||||
"sub_name": name,
|
||||
// ceph doesn't respond in json for this cmd (even if you ask)
|
||||
}
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
return parsePathResponse(fsa.marshalMgrCommand(m))
|
||||
}
|
||||
|
||||
// Feature is used to define constant values for optional features on
|
||||
// subvolumes.
|
||||
type Feature string
|
||||
|
||||
const (
|
||||
// SnapshotCloneFeature indicates a subvolume supports cloning.
|
||||
SnapshotCloneFeature = Feature("snapshot-clone")
|
||||
// SnapshotAutoprotectFeature indicates a subvolume does not require
|
||||
// manually protecting a subvolume before cloning.
|
||||
SnapshotAutoprotectFeature = Feature("snapshot-autoprotect")
|
||||
// SnapshotRetentionFeature indicates a subvolume supports retaining
|
||||
// snapshots on subvolume removal.
|
||||
SnapshotRetentionFeature = Feature("snapshot-retention")
|
||||
)
|
||||
|
||||
// SubVolumeState is used to define constant value for the state of
|
||||
// a subvolume.
|
||||
type SubVolumeState string
|
||||
|
||||
const (
|
||||
// StateUnset indicates a subvolume without any state.
|
||||
StateUnset = SubVolumeState("")
|
||||
// StateInit indicates that the subvolume is in initializing state.
|
||||
StateInit = SubVolumeState("init")
|
||||
// StatePending indicates that the subvolume is in pending state.
|
||||
StatePending = SubVolumeState("pending")
|
||||
// StateInProgress indicates that the subvolume is in in-progress state.
|
||||
StateInProgress = SubVolumeState("in-progress")
|
||||
// StateFailed indicates that the subvolume is in failed state.
|
||||
StateFailed = SubVolumeState("failed")
|
||||
// StateComplete indicates that the subvolume is in complete state.
|
||||
StateComplete = SubVolumeState("complete")
|
||||
// StateCanceled indicates that the subvolume is in canceled state.
|
||||
StateCanceled = SubVolumeState("canceled")
|
||||
// StateSnapRetained indicates that the subvolume is in
|
||||
// snapshot-retained state.
|
||||
StateSnapRetained = SubVolumeState("snapshot-retained")
|
||||
)
|
||||
|
||||
// SubVolumeInfo reports various informational values about a subvolume.
|
||||
type SubVolumeInfo struct {
|
||||
Type string `json:"type"`
|
||||
Path string `json:"path"`
|
||||
State SubVolumeState `json:"state"`
|
||||
Uid int `json:"uid"`
|
||||
Gid int `json:"gid"`
|
||||
Mode int `json:"mode"`
|
||||
BytesPercent string `json:"bytes_pcent"`
|
||||
BytesUsed ByteCount `json:"bytes_used"`
|
||||
BytesQuota QuotaSize `json:"-"`
|
||||
DataPool string `json:"data_pool"`
|
||||
PoolNamespace string `json:"pool_namespace"`
|
||||
Atime TimeStamp `json:"atime"`
|
||||
Mtime TimeStamp `json:"mtime"`
|
||||
Ctime TimeStamp `json:"ctime"`
|
||||
CreatedAt TimeStamp `json:"created_at"`
|
||||
Features []Feature `json:"features"`
|
||||
}
|
||||
|
||||
type subVolumeInfoWrapper struct {
|
||||
SubVolumeInfo
|
||||
VBytesQuota *quotaSizePlaceholder `json:"bytes_quota"`
|
||||
}
|
||||
|
||||
func parseSubVolumeInfo(res response) (*SubVolumeInfo, error) {
|
||||
var info subVolumeInfoWrapper
|
||||
if err := res.NoStatus().Unmarshal(&info).End(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if info.VBytesQuota != nil {
|
||||
info.BytesQuota = info.VBytesQuota.Value
|
||||
}
|
||||
return &info.SubVolumeInfo, nil
|
||||
}
|
||||
|
||||
// SubVolumeInfo returns information about the specified subvolume.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume info <volume> --group-name=<group> <name>
|
||||
func (fsa *FSAdmin) SubVolumeInfo(volume, group, name string) (*SubVolumeInfo, error) {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolume info",
|
||||
"vol_name": volume,
|
||||
"sub_name": name,
|
||||
"format": "json",
|
||||
}
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
return parseSubVolumeInfo(fsa.marshalMgrCommand(m))
|
||||
}
|
||||
|
||||
// CreateSubVolumeSnapshot creates a new snapshot from the source subvolume.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume snapshot create <volume> --group-name=<group> <source> <name>
|
||||
func (fsa *FSAdmin) CreateSubVolumeSnapshot(volume, group, source, name string) error {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolume snapshot create",
|
||||
"vol_name": volume,
|
||||
"sub_name": source,
|
||||
"snap_name": name,
|
||||
"format": "json",
|
||||
}
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
return fsa.marshalMgrCommand(m).NoData().End()
|
||||
}
|
||||
|
||||
// RemoveSubVolumeSnapshot removes the specified snapshot from the subvolume.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume snapshot rm <volume> --group-name=<group> <subvolume> <name>
|
||||
func (fsa *FSAdmin) RemoveSubVolumeSnapshot(volume, group, subvolume, name string) error {
|
||||
return fsa.rmSubVolumeSnapshot(volume, group, subvolume, name, commonRmFlags{})
|
||||
}
|
||||
|
||||
// ForceRemoveSubVolumeSnapshot removes the specified snapshot from the subvolume.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume snapshot rm <volume> --group-name=<group> <subvolume> <name> --force
|
||||
func (fsa *FSAdmin) ForceRemoveSubVolumeSnapshot(volume, group, subvolume, name string) error {
|
||||
return fsa.rmSubVolumeSnapshot(volume, group, subvolume, name, commonRmFlags{force: true})
|
||||
}
|
||||
|
||||
func (fsa *FSAdmin) rmSubVolumeSnapshot(volume, group, subvolume, name string, o commonRmFlags) error {
|
||||
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolume snapshot rm",
|
||||
"vol_name": volume,
|
||||
"sub_name": subvolume,
|
||||
"snap_name": name,
|
||||
"format": "json",
|
||||
}
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
return fsa.marshalMgrCommand(mergeFlags(m, o)).NoData().End()
|
||||
}
|
||||
|
||||
// ListSubVolumeSnapshots returns a listing of snapshots for a given subvolume.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume snapshot ls <volume> --group-name=<group> <name>
|
||||
func (fsa *FSAdmin) ListSubVolumeSnapshots(volume, group, name string) ([]string, error) {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolume snapshot ls",
|
||||
"vol_name": volume,
|
||||
"sub_name": name,
|
||||
"format": "json",
|
||||
}
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
return parseListNames(fsa.marshalMgrCommand(m))
|
||||
}
|
||||
|
||||
// SubVolumeSnapshotInfo reports various informational values about a subvolume.
|
||||
type SubVolumeSnapshotInfo struct {
|
||||
CreatedAt TimeStamp `json:"created_at"`
|
||||
DataPool string `json:"data_pool"`
|
||||
HasPendingClones string `json:"has_pending_clones"`
|
||||
Protected string `json:"protected"`
|
||||
Size ByteCount `json:"size"`
|
||||
}
|
||||
|
||||
func parseSubVolumeSnapshotInfo(res response) (*SubVolumeSnapshotInfo, error) {
|
||||
var info SubVolumeSnapshotInfo
|
||||
if err := res.NoStatus().Unmarshal(&info).End(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
// SubVolumeSnapshotInfo returns information about the specified subvolume snapshot.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume snapshot info <volume> --group-name=<group> <subvolume> <name>
|
||||
func (fsa *FSAdmin) SubVolumeSnapshotInfo(volume, group, subvolume, name string) (*SubVolumeSnapshotInfo, error) {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolume snapshot info",
|
||||
"vol_name": volume,
|
||||
"sub_name": subvolume,
|
||||
"snap_name": name,
|
||||
"format": "json",
|
||||
}
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
return parseSubVolumeSnapshotInfo(fsa.marshalMgrCommand(m))
|
||||
}
|
||||
|
||||
// ProtectSubVolumeSnapshot protects the specified snapshot.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume snapshot protect <volume> --group-name=<group> <subvolume> <name>
|
||||
func (fsa *FSAdmin) ProtectSubVolumeSnapshot(volume, group, subvolume, name string) error {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolume snapshot protect",
|
||||
"vol_name": volume,
|
||||
"sub_name": subvolume,
|
||||
"snap_name": name,
|
||||
"format": "json",
|
||||
}
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
return fsa.marshalMgrCommand(m).FilterDeprecated().NoData().End()
|
||||
}
|
||||
|
||||
// UnprotectSubVolumeSnapshot removes protection from the specified snapshot.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume snapshot unprotect <volume> --group-name=<group> <subvolume> <name>
|
||||
func (fsa *FSAdmin) UnprotectSubVolumeSnapshot(volume, group, subvolume, name string) error {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolume snapshot unprotect",
|
||||
"vol_name": volume,
|
||||
"sub_name": subvolume,
|
||||
"snap_name": name,
|
||||
"format": "json",
|
||||
}
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
return fsa.marshalMgrCommand(m).FilterDeprecated().NoData().End()
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
//go:build !(octopus || pacific || quincy || reef || squid)
|
||||
|
||||
package admin
|
||||
|
||||
// SubVolumeSnapshotPath returns the path for a snapshot from the source subvolume.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolume snapshot getpath <volume> --group-name=<group> <source> <name>
|
||||
func (fsa *FSAdmin) SubVolumeSnapshotPath(volume, group, source, name string) (string, error) {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolume snapshot getpath",
|
||||
"vol_name": volume,
|
||||
"sub_name": source,
|
||||
"snap_name": name,
|
||||
"format": "json",
|
||||
}
|
||||
if group != NoGroup {
|
||||
m["group_name"] = group
|
||||
}
|
||||
return parsePathResponse(fsa.marshalMgrCommand(m))
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package admin
|
||||
|
||||
// this is the internal type used to create JSON for ceph.
|
||||
// See SubVolumeGroupOptions for the type that users of the library
|
||||
// interact with.
|
||||
// note that the ceph json takes mode as a string.
|
||||
type subVolumeGroupFields struct {
|
||||
Prefix string `json:"prefix"`
|
||||
Format string `json:"format"`
|
||||
VolName string `json:"vol_name"`
|
||||
GroupName string `json:"group_name"`
|
||||
Uid int `json:"uid,omitempty"`
|
||||
Gid int `json:"gid,omitempty"`
|
||||
Mode string `json:"mode,omitempty"`
|
||||
PoolLayout string `json:"pool_layout,omitempty"`
|
||||
}
|
||||
|
||||
// SubVolumeGroupOptions are used to specify optional, non-identifying, values
|
||||
// to be used when creating a new subvolume group.
|
||||
type SubVolumeGroupOptions struct {
|
||||
Uid int
|
||||
Gid int
|
||||
Mode int
|
||||
PoolLayout string
|
||||
}
|
||||
|
||||
func (s *SubVolumeGroupOptions) toFields(v, g string) *subVolumeGroupFields {
|
||||
return &subVolumeGroupFields{
|
||||
Prefix: "fs subvolumegroup create",
|
||||
Format: "json",
|
||||
VolName: v,
|
||||
GroupName: g,
|
||||
Uid: s.Uid,
|
||||
Gid: s.Gid,
|
||||
Mode: modeString(s.Mode, false),
|
||||
PoolLayout: s.PoolLayout,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateSubVolumeGroup sends a request to create a subvolume group in a volume.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolumegroup create <volume> <group_name> ...
|
||||
func (fsa *FSAdmin) CreateSubVolumeGroup(volume, name string, o *SubVolumeGroupOptions) error {
|
||||
if o == nil {
|
||||
o = &SubVolumeGroupOptions{}
|
||||
}
|
||||
res := fsa.marshalMgrCommand(o.toFields(volume, name))
|
||||
return res.NoData().End()
|
||||
}
|
||||
|
||||
// ListSubVolumeGroups returns a list of subvolume groups belonging to the
|
||||
// specified volume.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolumegroup ls cephfs <volume>
|
||||
func (fsa *FSAdmin) ListSubVolumeGroups(volume string) ([]string, error) {
|
||||
res := fsa.marshalMgrCommand(map[string]string{
|
||||
"prefix": "fs subvolumegroup ls",
|
||||
"vol_name": volume,
|
||||
"format": "json",
|
||||
})
|
||||
return parseListNames(res)
|
||||
}
|
||||
|
||||
// RemoveSubVolumeGroup will delete a subvolume group in a volume.
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolumegroup rm <volume> <group_name>
|
||||
func (fsa *FSAdmin) RemoveSubVolumeGroup(volume, name string) error {
|
||||
return fsa.rmSubVolumeGroup(volume, name, commonRmFlags{})
|
||||
}
|
||||
|
||||
// ForceRemoveSubVolumeGroup will delete a subvolume group in a volume.
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolumegroup rm <volume> <group_name> --force
|
||||
func (fsa *FSAdmin) ForceRemoveSubVolumeGroup(volume, name string) error {
|
||||
return fsa.rmSubVolumeGroup(volume, name, commonRmFlags{force: true})
|
||||
}
|
||||
|
||||
func (fsa *FSAdmin) rmSubVolumeGroup(volume, name string, o commonRmFlags) error {
|
||||
res := fsa.marshalMgrCommand(mergeFlags(map[string]string{
|
||||
"prefix": "fs subvolumegroup rm",
|
||||
"vol_name": volume,
|
||||
"group_name": name,
|
||||
"format": "json",
|
||||
}, o))
|
||||
return res.NoData().End()
|
||||
}
|
||||
|
||||
// SubVolumeGroupPath returns the path to the subvolume from the root of the
|
||||
// file system.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs subvolumegroup getpath <volume> <group_name>
|
||||
func (fsa *FSAdmin) SubVolumeGroupPath(volume, name string) (string, error) {
|
||||
m := map[string]string{
|
||||
"prefix": "fs subvolumegroup getpath",
|
||||
"vol_name": volume,
|
||||
"group_name": name,
|
||||
// ceph doesn't respond in json for this cmd (even if you ask)
|
||||
}
|
||||
return parsePathResponse(fsa.marshalMgrCommand(m))
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// golang's date parsing approach is rather bizarre
|
||||
var cephTSLayout = "2006-01-02 15:04:05"
|
||||
|
||||
// TimeStamp abstracts some of the details about date+time stamps
|
||||
// returned by ceph via JSON.
|
||||
type TimeStamp struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
// String returns a string representing the date+time as presented
|
||||
// by ceph.
|
||||
func (ts TimeStamp) String() string {
|
||||
return ts.Format(cephTSLayout)
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json Unmarshaler interface.
|
||||
func (ts *TimeStamp) UnmarshalJSON(b []byte) error {
|
||||
var raw string
|
||||
if err := json.Unmarshal(b, &raw); err != nil {
|
||||
return err
|
||||
}
|
||||
// AFAICT, ceph always returns the time in UTC so Parse, as opposed to
|
||||
// ParseInLocation, is appropriate here.
|
||||
t, err := time.Parse(cephTSLayout, raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*ts = TimeStamp{t}
|
||||
return nil
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
var (
|
||||
listVolumesCmd = []byte(`{"prefix":"fs volume ls"}`)
|
||||
dumpVolumesCmd = []byte(`{"prefix":"fs dump","format":"json"}`)
|
||||
listFsCmd = []byte(`{"prefix":"fs ls","format":"json"}`)
|
||||
)
|
||||
|
||||
// ListVolumes return a list of volumes in this Ceph cluster.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs volume ls
|
||||
func (fsa *FSAdmin) ListVolumes() ([]string, error) {
|
||||
res := fsa.rawMgrCommand(listVolumesCmd)
|
||||
return parseListNames(res)
|
||||
}
|
||||
|
||||
// FSPoolInfo contains the name of a file system as well as the metadata and
|
||||
// data pools. Pool information is available by ID or by name.
|
||||
type FSPoolInfo struct {
|
||||
Name string `json:"name"`
|
||||
MetadataPool string `json:"metadata_pool"`
|
||||
MetadataPoolID int `json:"metadata_pool_id"`
|
||||
DataPools []string `json:"data_pools"`
|
||||
DataPoolIDs []int `json:"data_pool_ids"`
|
||||
}
|
||||
|
||||
// ListFileSystems lists file systems along with the pools occupied by those
|
||||
// file systems.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs ls
|
||||
func (fsa *FSAdmin) ListFileSystems() ([]FSPoolInfo, error) {
|
||||
res := fsa.rawMonCommand(listFsCmd)
|
||||
return parseFsList(res)
|
||||
}
|
||||
|
||||
func parseFsList(res response) ([]FSPoolInfo, error) {
|
||||
var listing []FSPoolInfo
|
||||
if err := res.NoStatus().Unmarshal(&listing).End(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return listing, nil
|
||||
}
|
||||
|
||||
// VolumeIdent contains a pair of file system identifying values: the volume
|
||||
// name and the volume ID.
|
||||
type VolumeIdent struct {
|
||||
Name string
|
||||
ID int64
|
||||
}
|
||||
|
||||
type cephFileSystem struct {
|
||||
ID int64 `json:"id"`
|
||||
MDSMap struct {
|
||||
FSName string `json:"fs_name"`
|
||||
} `json:"mdsmap"`
|
||||
}
|
||||
|
||||
type fsDump struct {
|
||||
FileSystems []cephFileSystem `json:"filesystems"`
|
||||
}
|
||||
|
||||
const (
|
||||
dumpOkPrefix = "dumped fsmap epoch"
|
||||
dumpOkLen = len(dumpOkPrefix)
|
||||
|
||||
invalidTextualResponse = "this ceph version returns a non-parsable volume status response"
|
||||
)
|
||||
|
||||
func parseDumpToIdents(res response) ([]VolumeIdent, error) {
|
||||
if !res.Ok() {
|
||||
return nil, res.End()
|
||||
}
|
||||
var dump fsDump
|
||||
if err := res.FilterPrefix(dumpOkPrefix).NoStatus().Unmarshal(&dump).End(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// copy the dump json into the simpler enumeration list
|
||||
idents := make([]VolumeIdent, len(dump.FileSystems))
|
||||
for i := range dump.FileSystems {
|
||||
idents[i].ID = dump.FileSystems[i].ID
|
||||
idents[i].Name = dump.FileSystems[i].MDSMap.FSName
|
||||
}
|
||||
return idents, nil
|
||||
}
|
||||
|
||||
// EnumerateVolumes returns a list of volume-name volume-id pairs.
|
||||
func (fsa *FSAdmin) EnumerateVolumes() ([]VolumeIdent, error) {
|
||||
// We base our enumeration on the ceph fs dump json. This may not be the
|
||||
// only way to do it, but it's the only one I know of currently. Because of
|
||||
// this and to keep our initial implementation simple, we expose our own
|
||||
// simplified type only, rather do a partial implementation of dump.
|
||||
return parseDumpToIdents(fsa.rawMonCommand(dumpVolumesCmd))
|
||||
}
|
||||
|
||||
// VolumePool reports on the pool status for a CephFS volume.
|
||||
type VolumePool struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Available uint64 `json:"avail"`
|
||||
Used uint64 `json:"used"`
|
||||
}
|
||||
|
||||
// VolumeStatus reports various properties of a CephFS volume.
|
||||
// TODO: Fill in.
|
||||
type VolumeStatus struct {
|
||||
MDSVersion string `json:"mds_version"`
|
||||
Pools []VolumePool `json:"pools"`
|
||||
}
|
||||
|
||||
type mdsVersionField struct {
|
||||
Version string
|
||||
Items []struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mdsVersionField) UnmarshalJSON(data []byte) (err error) {
|
||||
if err = json.Unmarshal(data, &m.Version); err == nil {
|
||||
return
|
||||
}
|
||||
return json.Unmarshal(data, &m.Items)
|
||||
}
|
||||
|
||||
// volumeStatusResponse deals with the changing output of the mgr
|
||||
// api json
|
||||
type volumeStatusResponse struct {
|
||||
Pools []VolumePool `json:"pools"`
|
||||
MDSVersion mdsVersionField `json:"mds_version"`
|
||||
}
|
||||
|
||||
func (v *volumeStatusResponse) volumeStatus() *VolumeStatus {
|
||||
vstatus := &VolumeStatus{}
|
||||
vstatus.Pools = v.Pools
|
||||
if v.MDSVersion.Version != "" {
|
||||
vstatus.MDSVersion = v.MDSVersion.Version
|
||||
} else if len(v.MDSVersion.Items) > 0 {
|
||||
vstatus.MDSVersion = v.MDSVersion.Items[0].Version
|
||||
}
|
||||
return vstatus
|
||||
}
|
||||
|
||||
func parseVolumeStatus(res response) (*volumeStatusResponse, error) {
|
||||
var vs volumeStatusResponse
|
||||
res = res.NoStatus()
|
||||
if !res.Ok() {
|
||||
return nil, res.End()
|
||||
}
|
||||
res = res.Unmarshal(&vs)
|
||||
if !res.Ok() {
|
||||
if bytes.HasPrefix(res.Body(), []byte("ceph")) {
|
||||
return nil, NotImplementedError{
|
||||
Response: newResponse(res.Body(), invalidTextualResponse, res.Unwrap()),
|
||||
}
|
||||
}
|
||||
return nil, res.End()
|
||||
}
|
||||
return &vs, nil
|
||||
}
|
||||
|
||||
// VolumeStatus returns a VolumeStatus object for the given volume name.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs status cephfs <name>
|
||||
func (fsa *FSAdmin) VolumeStatus(name string) (*VolumeStatus, error) {
|
||||
res := fsa.marshalMgrCommand(map[string]string{
|
||||
"fs": name,
|
||||
"prefix": "fs status",
|
||||
"format": "json",
|
||||
})
|
||||
v, err := parseVolumeStatus(res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v.volumeStatus(), nil
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
//go:build !(nautilus || octopus)
|
||||
// +build !nautilus,!octopus
|
||||
|
||||
package admin
|
||||
|
||||
// PoolInfo reports various properties of a pool.
|
||||
type PoolInfo struct {
|
||||
Available int `json:"avail"`
|
||||
Name string `json:"name"`
|
||||
Used int `json:"used"`
|
||||
}
|
||||
|
||||
// PoolType indicates the type of pool related to a volume.
|
||||
type PoolType struct {
|
||||
DataPool []PoolInfo `json:"data"`
|
||||
MetadataPool []PoolInfo `json:"metadata"`
|
||||
}
|
||||
|
||||
// VolInfo holds various informational values about a volume.
|
||||
type VolInfo struct {
|
||||
MonAddrs []string `json:"mon_addrs"`
|
||||
PendingSubvolDels int `json:"pending_subvolume_deletions"`
|
||||
Pools PoolType `json:"pools"`
|
||||
UsedSize int `json:"used_size"`
|
||||
}
|
||||
|
||||
func parseVolumeInfo(res response) (*VolInfo, error) {
|
||||
var info VolInfo
|
||||
if err := res.NoStatus().Unmarshal(&info).End(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
// FetchVolumeInfo fetches the information of a CephFS volume.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph fs volume info <vol_name>
|
||||
func (fsa *FSAdmin) FetchVolumeInfo(volume string) (*VolInfo, error) {
|
||||
m := map[string]string{
|
||||
"prefix": "fs volume info",
|
||||
"vol_name": volume,
|
||||
"format": "json",
|
||||
}
|
||||
|
||||
return parseVolumeInfo(fsa.marshalMgrCommand(m))
|
||||
}
|
||||
+326
@@ -0,0 +1,326 @@
|
||||
//go:build ceph_preview
|
||||
|
||||
package cephfs
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lcephfs
|
||||
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
||||
#include <stdlib.h>
|
||||
#include <dirent.h>
|
||||
#include <cephfs/libcephfs.h>
|
||||
|
||||
// Types and constants are copied from libcephfs.h with added "_" as prefix. This
|
||||
// prevents redefinition of the types on libcephfs versions that have them
|
||||
// already.
|
||||
|
||||
// struct ceph_file_blockdiff_result lacks a definition, so create an alias and
|
||||
// treat it as opaque.
|
||||
typedef struct _ceph_file_blockdiff_result _ceph_file_blockdiff_result;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
struct ceph_mount_info* cmount;
|
||||
struct _ceph_file_blockdiff_result* blockp;
|
||||
} _ceph_file_blockdiff_info;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint64_t offset;
|
||||
uint64_t len;
|
||||
} _cblock;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint64_t num_blocks;
|
||||
struct _cblock *b;
|
||||
} _ceph_file_blockdiff_changedblocks;
|
||||
|
||||
// ceph_file_blockdiff_init_fn matches the ceph_file_blockdiff_init function signature.
|
||||
typedef int(*ceph_file_blockdiff_init_fn)(struct ceph_mount_info* cmount,
|
||||
const char* root_path,
|
||||
const char* rel_path,
|
||||
const char* snap1,
|
||||
const char* snap2,
|
||||
_ceph_file_blockdiff_info* out_info);
|
||||
|
||||
// ceph_file_blockdiff_init_dlsym take *fn as ceph_file_blockdiff_init and calls the dynamically loaded
|
||||
// ceph_file_blockdiff_init function passed as 1st argument.
|
||||
static inline int ceph_file_blockdiff_init_dlsym(void *fn,
|
||||
struct ceph_mount_info* cmount,
|
||||
const char* root_path,
|
||||
const char* rel_path,
|
||||
const char* snap1,
|
||||
const char* snap2,
|
||||
_ceph_file_blockdiff_info* out_info) {
|
||||
// cast function pointer fn to ceph_file_blockdiff_init and call the function
|
||||
return ((ceph_file_blockdiff_init_fn) fn)(cmount, root_path, rel_path, snap1, snap2, out_info);
|
||||
}
|
||||
|
||||
// ceph_file_blockdiff_fn matches the ceph_file_blockdiff function signature.
|
||||
typedef int(*ceph_file_blockdiff_fn)(_ceph_file_blockdiff_info* info,
|
||||
_ceph_file_blockdiff_changedblocks* blocks);
|
||||
|
||||
// ceph_file_blockdiff_dlsym take *fn as ceph_file_blockdiff and calls the dynamically loaded
|
||||
// ceph_file_blockdiff function passed as 1st argument.
|
||||
static inline int ceph_file_blockdiff_dlsym(void *fn,
|
||||
_ceph_file_blockdiff_info* info,
|
||||
_ceph_file_blockdiff_changedblocks* blocks) {
|
||||
// cast function pointer fn to ceph_file_blockdiff and call the function
|
||||
return ((ceph_file_blockdiff_fn) fn)(info, blocks);
|
||||
}
|
||||
|
||||
// ceph_free_file_blockdiff_buffer_fn matches the ceph_free_file_blockdiff_buffer function signature.
|
||||
typedef void(*ceph_free_file_blockdiff_buffer_fn)(_ceph_file_blockdiff_changedblocks* blocks);
|
||||
|
||||
// ceph_free_file_blockdiff_buffer_dlsym take *fn as ceph_free_file_blockdiff_buffer and calls the dynamically loaded
|
||||
// ceph_free_file_blockdiff_buffer function passed as 1st argument.
|
||||
static inline void ceph_free_file_blockdiff_buffer_dlsym(void *fn,
|
||||
_ceph_file_blockdiff_changedblocks* blocks) {
|
||||
// cast function pointer fn to ceph_free_file_blockdiff_buffer and call the function
|
||||
((ceph_free_file_blockdiff_buffer_fn) fn)(blocks);
|
||||
}
|
||||
|
||||
// ceph_file_blockdiff_finish_fn matches the ceph_file_blockdiff_finish function signature.
|
||||
typedef int(*ceph_file_blockdiff_finish_fn)(_ceph_file_blockdiff_info* info);
|
||||
|
||||
// ceph_file_blockdiff_finish_dlsym take *fn as ceph_file_blockdiff_finish and calls the dynamically loaded
|
||||
// ceph_file_blockdiff_finish function passed as 1st argument.
|
||||
static inline int ceph_file_blockdiff_finish_dlsym(void *fn,
|
||||
_ceph_file_blockdiff_info* info) {
|
||||
// cast function pointer fn to ceph_file_blockdiff_finish and call the function
|
||||
return ((ceph_file_blockdiff_finish_fn) fn)(info);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ceph/go-ceph/internal/dlsym"
|
||||
)
|
||||
|
||||
var (
|
||||
cephFileBlockDiffInitOnce sync.Once
|
||||
cephFileBlockDiffOnce sync.Once
|
||||
cephFreeFileBlockDiffBufferOnce sync.Once
|
||||
cephFileBlockDiffFinishOnce sync.Once
|
||||
|
||||
cephFileBlockDiffInitErr error
|
||||
cephFileBlockDiffErr error
|
||||
cephFreeFileBlockDiffBufferErr error
|
||||
cephFileBlockDiffFinishErr error
|
||||
|
||||
cephFileBlockDiffInit unsafe.Pointer
|
||||
cephFileBlockDiff unsafe.Pointer
|
||||
cephFreeFileBlockDiffBuffer unsafe.Pointer
|
||||
cephFileBlockDiffFinish unsafe.Pointer
|
||||
)
|
||||
|
||||
// FileBlockDiffInfo is a struct that holds the block diff stream handle.
|
||||
type FileBlockDiffInfo struct {
|
||||
cMount *MountInfo
|
||||
cephFileBlockDiffResult *C._ceph_file_blockdiff_result
|
||||
more bool
|
||||
}
|
||||
|
||||
// ChangedBlock is a struct that holds the offset and length of a block.
|
||||
type ChangedBlock struct {
|
||||
Offset uint64
|
||||
Len uint64
|
||||
}
|
||||
|
||||
// FileBlockDiffChangedBlocks is a struct that holds the number of blocks
|
||||
// and list of Changed Blocks.
|
||||
type FileBlockDiffChangedBlocks struct {
|
||||
NumBlocks uint64
|
||||
ChangedBlocks []ChangedBlock
|
||||
}
|
||||
|
||||
// FileBlockDiffInit initializes the block diff stream to get file block deltas.
|
||||
// It takes the mount handle, root path, relative path, snapshot names and returns
|
||||
// a FileBlockDiffInfo struct that contains the block diff stream handle.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_file_blockdiff_init(struct ceph_mount_info* cmount,
|
||||
// const char* root_path,
|
||||
// const char* rel_path,
|
||||
// const char* snap1,
|
||||
// const char* snap2,
|
||||
// struct ceph_file_blockdiff_info* out_info);
|
||||
func FileBlockDiffInit(mount *MountInfo, rootPath, relPath, snap1, snap2 string) (*FileBlockDiffInfo, error) {
|
||||
if mount == nil || rootPath == "" || relPath == "" ||
|
||||
snap1 == "" || snap2 == "" {
|
||||
return nil, errInvalid
|
||||
}
|
||||
cRootPath := C.CString(rootPath)
|
||||
cRelPath := C.CString(relPath)
|
||||
cSnap1 := C.CString(snap1)
|
||||
cSnap2 := C.CString(snap2)
|
||||
defer func() {
|
||||
C.free(unsafe.Pointer(cRootPath))
|
||||
C.free(unsafe.Pointer(cRelPath))
|
||||
C.free(unsafe.Pointer(cSnap1))
|
||||
C.free(unsafe.Pointer(cSnap2))
|
||||
}()
|
||||
|
||||
// Load the ceph_file_blockdiff_init function from the shared library.
|
||||
cephFileBlockDiffInitOnce.Do(func() {
|
||||
cephFileBlockDiffInit, cephFileBlockDiffInitErr = dlsym.LookupSymbol("ceph_file_blockdiff_init")
|
||||
})
|
||||
if cephFileBlockDiffInitErr != nil {
|
||||
return nil, fmt.Errorf("%w: %w", ErrNotImplemented, cephFileBlockDiffInitErr)
|
||||
}
|
||||
|
||||
rawCephBlockDiffInfo := &C._ceph_file_blockdiff_info{}
|
||||
|
||||
// Call the ceph_file_blockdiff_init function with the provided arguments.
|
||||
ret := C.ceph_file_blockdiff_init_dlsym(cephFileBlockDiffInit,
|
||||
mount.mount,
|
||||
cRootPath,
|
||||
cRelPath,
|
||||
cSnap1,
|
||||
cSnap2,
|
||||
rawCephBlockDiffInfo,
|
||||
)
|
||||
if ret != 0 {
|
||||
return nil, getError(ret)
|
||||
}
|
||||
|
||||
mountInfo := &MountInfo{
|
||||
mount: rawCephBlockDiffInfo.cmount,
|
||||
}
|
||||
|
||||
cephFileBlockDiffInfo := &FileBlockDiffInfo{
|
||||
cMount: mountInfo,
|
||||
cephFileBlockDiffResult: rawCephBlockDiffInfo.blockp,
|
||||
more: true,
|
||||
}
|
||||
|
||||
return cephFileBlockDiffInfo, nil
|
||||
}
|
||||
|
||||
// validate checks if the FileBlockDiffInfo struct is valid.
|
||||
func (info *FileBlockDiffInfo) validate() error {
|
||||
if info.cMount == nil || info.cephFileBlockDiffResult == nil {
|
||||
return errInvalid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read retrieves the next set of changed blocks for a file.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_file_blockdiff(struct ceph_file_blockdiff_info* info,
|
||||
// struct ceph_file_blockdiff_changedblocks* blocks);
|
||||
func (info *FileBlockDiffInfo) Read() (*FileBlockDiffChangedBlocks, error) {
|
||||
err := info.validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Load the ceph_file_blockdiff function from the shared library.
|
||||
cephFileBlockDiffOnce.Do(func() {
|
||||
cephFileBlockDiff, cephFileBlockDiffErr = dlsym.LookupSymbol("ceph_file_blockdiff")
|
||||
})
|
||||
if cephFileBlockDiffErr != nil {
|
||||
return nil, fmt.Errorf("%w: %w", ErrNotImplemented, cephFileBlockDiffErr)
|
||||
}
|
||||
cephFreeFileBlockDiffBufferOnce.Do(func() {
|
||||
cephFreeFileBlockDiffBuffer, cephFreeFileBlockDiffBufferErr = dlsym.LookupSymbol("ceph_free_file_blockdiff_buffer")
|
||||
})
|
||||
if cephFreeFileBlockDiffBufferErr != nil {
|
||||
return nil, fmt.Errorf("%w: %w", ErrNotImplemented, cephFreeFileBlockDiffBufferErr)
|
||||
}
|
||||
|
||||
rawCephBlockDiffChangedBlocks := &C._ceph_file_blockdiff_changedblocks{}
|
||||
rawCephFileBlockDiffInfo := &C._ceph_file_blockdiff_info{
|
||||
cmount: info.cMount.mount,
|
||||
blockp: info.cephFileBlockDiffResult,
|
||||
}
|
||||
|
||||
// Call the ceph_file_blockdiff function with the provided arguments.
|
||||
ret := C.ceph_file_blockdiff_dlsym(cephFileBlockDiff,
|
||||
rawCephFileBlockDiffInfo,
|
||||
rawCephBlockDiffChangedBlocks,
|
||||
)
|
||||
if ret < 0 {
|
||||
return nil, getError(ret)
|
||||
}
|
||||
// ret > 0 indicates that there are more entries after this call.
|
||||
info.more = (ret > 0)
|
||||
|
||||
// Free the memory allocated for the blocks by ceph_file_blockdiff.
|
||||
defer C.ceph_free_file_blockdiff_buffer_dlsym(cephFreeFileBlockDiffBuffer,
|
||||
rawCephBlockDiffChangedBlocks)
|
||||
|
||||
cNumBlocks := uint64(rawCephBlockDiffChangedBlocks.num_blocks)
|
||||
|
||||
// Convert the C struct to Go struct.
|
||||
cBlocks := make([]ChangedBlock, int(cNumBlocks))
|
||||
if cNumBlocks == 0 {
|
||||
return &FileBlockDiffChangedBlocks{
|
||||
NumBlocks: 0,
|
||||
ChangedBlocks: cBlocks,
|
||||
}, nil
|
||||
}
|
||||
|
||||
currentCBlock := (*C._cblock)(unsafe.Pointer(rawCephBlockDiffChangedBlocks.b))
|
||||
for i := uint64(0); i < cNumBlocks; i++ {
|
||||
cBlocks[i] = ChangedBlock{
|
||||
Offset: uint64(currentCBlock.offset),
|
||||
Len: uint64(currentCBlock.len),
|
||||
}
|
||||
currentCBlock = (*C._cblock)(unsafe.Pointer(uintptr(unsafe.Pointer(currentCBlock)) + unsafe.Sizeof(C._cblock{})))
|
||||
}
|
||||
|
||||
return &FileBlockDiffChangedBlocks{
|
||||
NumBlocks: cNumBlocks,
|
||||
ChangedBlocks: cBlocks,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// More returns true if there are more entries to read in the block diff stream.
|
||||
func (info *FileBlockDiffInfo) More() bool {
|
||||
return info.more
|
||||
}
|
||||
|
||||
// Close closes the block diff stream.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_file_blockdiff_finish(struct ceph_file_blockdiff_info* info);
|
||||
func (info *FileBlockDiffInfo) Close() error {
|
||||
err := info.validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load the ceph_file_blockdiff_finish function from the shared library.
|
||||
cephFileBlockDiffFinishOnce.Do(func() {
|
||||
cephFileBlockDiffFinish, cephFileBlockDiffFinishErr = dlsym.LookupSymbol("ceph_file_blockdiff_finish")
|
||||
})
|
||||
if cephFileBlockDiffFinishErr != nil {
|
||||
return fmt.Errorf("%w: %w", ErrNotImplemented, cephFileBlockDiffFinishErr)
|
||||
}
|
||||
|
||||
rawCephFileBlockDiffInfo := &C._ceph_file_blockdiff_info{
|
||||
cmount: info.cMount.mount,
|
||||
blockp: info.cephFileBlockDiffResult,
|
||||
}
|
||||
|
||||
// Call the ceph_file_blockdiff_finish function with the provided arguments.
|
||||
ret := C.ceph_file_blockdiff_finish_dlsym(
|
||||
cephFileBlockDiffFinish,
|
||||
rawCephFileBlockDiffInfo,
|
||||
)
|
||||
if ret != 0 {
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
package cephfs
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lcephfs
|
||||
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
||||
#include <stdlib.h>
|
||||
#include <cephfs/libcephfs.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/ceph/go-ceph/internal/retry"
|
||||
"github.com/ceph/go-ceph/rados"
|
||||
)
|
||||
|
||||
// MountInfo exports ceph's ceph_mount_info from libcephfs.cc
|
||||
type MountInfo struct {
|
||||
mount *C.struct_ceph_mount_info
|
||||
}
|
||||
|
||||
func createMount(id *C.char) (*MountInfo, error) {
|
||||
mount := &MountInfo{}
|
||||
ret := C.ceph_create(&mount.mount, id)
|
||||
if ret != 0 {
|
||||
return nil, getError(ret)
|
||||
}
|
||||
return mount, nil
|
||||
}
|
||||
|
||||
// validate checks whether mount.mount is ready to use or not.
|
||||
func (mount *MountInfo) validate() error {
|
||||
if mount.mount == nil {
|
||||
return ErrNotConnected
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Version returns the major, minor, and patch level of the libcephfs library.
|
||||
func Version() (int, int, int) {
|
||||
var cMajor, cMinor, cPatch C.int
|
||||
C.ceph_version(&cMajor, &cMinor, &cPatch)
|
||||
return int(cMajor), int(cMinor), int(cPatch)
|
||||
}
|
||||
|
||||
// CreateMount creates a mount handle for interacting with Ceph.
|
||||
func CreateMount() (*MountInfo, error) {
|
||||
return createMount(nil)
|
||||
}
|
||||
|
||||
// CreateMountWithId creates a mount handle for interacting with Ceph.
|
||||
// The caller can specify a unique id that will identify this client.
|
||||
func CreateMountWithId(id string) (*MountInfo, error) {
|
||||
cid := C.CString(id)
|
||||
defer C.free(unsafe.Pointer(cid))
|
||||
return createMount(cid)
|
||||
}
|
||||
|
||||
// CreateFromRados creates a mount handle using an existing rados cluster
|
||||
// connection.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_create_from_rados(struct ceph_mount_info **cmount, rados_t cluster);
|
||||
func CreateFromRados(conn *rados.Conn) (*MountInfo, error) {
|
||||
mount := &MountInfo{}
|
||||
ret := C.ceph_create_from_rados(&mount.mount, C.rados_t(conn.Cluster()))
|
||||
if ret != 0 {
|
||||
return nil, getError(ret)
|
||||
}
|
||||
return mount, nil
|
||||
}
|
||||
|
||||
// ReadDefaultConfigFile loads the ceph configuration from the default config file.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_conf_read_file(struct ceph_mount_info *cmount, const char *path_list);
|
||||
func (mount *MountInfo) ReadDefaultConfigFile() error {
|
||||
ret := C.ceph_conf_read_file(mount.mount, nil)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// ReadConfigFile loads the ceph configuration from the specified config file.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_conf_read_file(struct ceph_mount_info *cmount, const char *path_list);
|
||||
func (mount *MountInfo) ReadConfigFile(path string) error {
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
ret := C.ceph_conf_read_file(mount.mount, cPath)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// ParseConfigArgv configures the mount using a unix style command line
|
||||
// argument vector.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_conf_parse_argv(struct ceph_mount_info *cmount, int argc, const char **argv);
|
||||
func (mount *MountInfo) ParseConfigArgv(argv []string) error {
|
||||
if err := mount.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(argv) == 0 {
|
||||
return ErrEmptyArgument
|
||||
}
|
||||
cargv := make([]*C.char, len(argv))
|
||||
for i := range argv {
|
||||
cargv[i] = C.CString(argv[i])
|
||||
defer C.free(unsafe.Pointer(cargv[i]))
|
||||
}
|
||||
|
||||
ret := C.ceph_conf_parse_argv(mount.mount, C.int(len(cargv)), &cargv[0])
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// ParseDefaultConfigEnv configures the mount from the default Ceph
|
||||
// environment variable CEPH_ARGS.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_conf_parse_env(struct ceph_mount_info *cmount, const char *var);
|
||||
func (mount *MountInfo) ParseDefaultConfigEnv() error {
|
||||
if err := mount.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
ret := C.ceph_conf_parse_env(mount.mount, nil)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// SetConfigOption sets the value of the configuration option identified by
|
||||
// the given name.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_conf_set(struct ceph_mount_info *cmount, const char *option, const char *value);
|
||||
func (mount *MountInfo) SetConfigOption(option, value string) error {
|
||||
cOption := C.CString(option)
|
||||
defer C.free(unsafe.Pointer(cOption))
|
||||
cValue := C.CString(value)
|
||||
defer C.free(unsafe.Pointer(cValue))
|
||||
return getError(C.ceph_conf_set(mount.mount, cOption, cValue))
|
||||
}
|
||||
|
||||
// GetConfigOption returns the value of the Ceph configuration option
|
||||
// identified by the given name.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_conf_get(struct ceph_mount_info *cmount, const char *option, char *buf, size_t len);
|
||||
func (mount *MountInfo) GetConfigOption(option string) (string, error) {
|
||||
cOption := C.CString(option)
|
||||
defer C.free(unsafe.Pointer(cOption))
|
||||
|
||||
var (
|
||||
err error
|
||||
buf []byte
|
||||
)
|
||||
// range from 4k to 256KiB
|
||||
retry.WithSizes(4096, 1<<18, func(size int) retry.Hint {
|
||||
buf = make([]byte, size)
|
||||
ret := C.ceph_conf_get(
|
||||
mount.mount,
|
||||
cOption,
|
||||
(*C.char)(unsafe.Pointer(&buf[0])),
|
||||
C.size_t(len(buf)))
|
||||
err = getError(ret)
|
||||
return retry.DoubleSize.If(err == errNameTooLong)
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
value := C.GoString((*C.char)(unsafe.Pointer(&buf[0])))
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// Init the file system client without actually mounting the file system.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_init(struct ceph_mount_info *cmount);
|
||||
func (mount *MountInfo) Init() error {
|
||||
return getError(C.ceph_init(mount.mount))
|
||||
}
|
||||
|
||||
// Mount the file system, establishing a connection capable of I/O.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_mount(struct ceph_mount_info *cmount, const char *root);
|
||||
func (mount *MountInfo) Mount() error {
|
||||
ret := C.ceph_mount(mount.mount, nil)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// MountWithRoot mounts the file system using the path provided for the root of
|
||||
// the mount. This establishes a connection capable of I/O.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_mount(struct ceph_mount_info *cmount, const char *root);
|
||||
func (mount *MountInfo) MountWithRoot(root string) error {
|
||||
croot := C.CString(root)
|
||||
defer C.free(unsafe.Pointer(croot))
|
||||
return getError(C.ceph_mount(mount.mount, croot))
|
||||
}
|
||||
|
||||
// Unmount the file system.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_unmount(struct ceph_mount_info *cmount);
|
||||
func (mount *MountInfo) Unmount() error {
|
||||
ret := C.ceph_unmount(mount.mount)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// Release destroys the mount handle.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_release(struct ceph_mount_info *cmount);
|
||||
func (mount *MountInfo) Release() error {
|
||||
if mount.mount == nil {
|
||||
return nil
|
||||
}
|
||||
ret := C.ceph_release(mount.mount)
|
||||
if err := getError(ret); err != nil {
|
||||
return err
|
||||
}
|
||||
mount.mount = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// SyncFs synchronizes all filesystem data to persistent media.
|
||||
func (mount *MountInfo) SyncFs() error {
|
||||
ret := C.ceph_sync_fs(mount.mount)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// IsMounted checks mount status.
|
||||
func (mount *MountInfo) IsMounted() bool {
|
||||
ret := C.ceph_is_mounted(mount.mount)
|
||||
return ret == 1
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package cephfs
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lcephfs
|
||||
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
||||
#include <stdlib.h>
|
||||
#include <cephfs/libcephfs.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/ceph/go-ceph/internal/cutil"
|
||||
)
|
||||
|
||||
func cephBufferFree(p unsafe.Pointer) {
|
||||
C.ceph_buffer_free((*C.char)(p))
|
||||
}
|
||||
|
||||
// MdsCommand sends commands to the specified MDS.
|
||||
//
|
||||
// The args parameter takes a slice of byte slices but typically a single
|
||||
// slice element is sufficient. The use of two slices exists to best match
|
||||
// the structure of the underlying C call which is often a legacy interface
|
||||
// in Ceph.
|
||||
func (mount *MountInfo) MdsCommand(mdsSpec string, args [][]byte) ([]byte, string, error) {
|
||||
return mount.mdsCommand(mdsSpec, args, nil)
|
||||
}
|
||||
|
||||
// MdsCommandWithInputBuffer sends commands to the specified MDS, with an input
|
||||
// buffer.
|
||||
//
|
||||
// The args parameter takes a slice of byte slices but typically a single
|
||||
// slice element is sufficient. The use of two slices exists to best match
|
||||
// the structure of the underlying C call which is often a legacy interface
|
||||
// in Ceph.
|
||||
func (mount *MountInfo) MdsCommandWithInputBuffer(mdsSpec string, args [][]byte, inputBuffer []byte) ([]byte, string, error) {
|
||||
return mount.mdsCommand(mdsSpec, args, inputBuffer)
|
||||
}
|
||||
|
||||
// mdsCommand supports sending formatted commands to MDS.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_mds_command(struct ceph_mount_info *cmount,
|
||||
// const char *mds_spec,
|
||||
// const char **cmd,
|
||||
// size_t cmdlen,
|
||||
// const char *inbuf, size_t inbuflen,
|
||||
// char **outbuf, size_t *outbuflen,
|
||||
// char **outs, size_t *outslen);
|
||||
func (mount *MountInfo) mdsCommand(mdsSpec string, args [][]byte, inputBuffer []byte) (buffer []byte, info string, err error) {
|
||||
spec := C.CString(mdsSpec)
|
||||
defer C.free(unsafe.Pointer(spec))
|
||||
ci := cutil.NewCommandInput(args, inputBuffer)
|
||||
defer ci.Free()
|
||||
co := cutil.NewCommandOutput().SetFreeFunc(cephBufferFree)
|
||||
defer co.Free()
|
||||
|
||||
ret := C.ceph_mds_command(
|
||||
mount.mount, // cephfs mount ref
|
||||
spec, // mds spec
|
||||
(**C.char)(ci.Cmd()),
|
||||
C.size_t(ci.CmdLen()),
|
||||
(*C.char)(ci.InBuf()),
|
||||
C.size_t(ci.InBufLen()),
|
||||
(**C.char)(co.OutBuf()),
|
||||
(*C.size_t)(co.OutBufLen()),
|
||||
(**C.char)(co.Outs()),
|
||||
(*C.size_t)(co.OutsLen()))
|
||||
buf, status := co.GoValues()
|
||||
return buf, status, getError(ret)
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package cephfs
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lcephfs
|
||||
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
||||
#include <cephfs/libcephfs.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// Some general connectivity and mounting functions are new in
|
||||
// Ceph Nautilus.
|
||||
|
||||
// GetFsCid returns the cluster ID for a mounted ceph file system.
|
||||
// If the object does not refer to a mounted file system, an error
|
||||
// will be returned.
|
||||
//
|
||||
// Note:
|
||||
//
|
||||
// Only supported in Ceph Nautilus and newer.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int64_t ceph_get_fs_cid(struct ceph_mount_info *cmount);
|
||||
func (mount *MountInfo) GetFsCid() (int64, error) {
|
||||
ret := C.ceph_get_fs_cid(mount.mount)
|
||||
if ret < 0 {
|
||||
return 0, getError(C.int(ret))
|
||||
}
|
||||
return int64(ret), nil
|
||||
}
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
package cephfs
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lcephfs
|
||||
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
||||
#include <stdlib.h>
|
||||
#include <dirent.h>
|
||||
#include <cephfs/libcephfs.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Directory represents an open directory handle.
|
||||
type Directory struct {
|
||||
mount *MountInfo
|
||||
dir *C.struct_ceph_dir_result
|
||||
}
|
||||
|
||||
// OpenDir returns a new Directory handle open for I/O.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_opendir(struct ceph_mount_info *cmount, const char *name, struct ceph_dir_result **dirpp);
|
||||
func (mount *MountInfo) OpenDir(path string) (*Directory, error) {
|
||||
var dir *C.struct_ceph_dir_result
|
||||
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
ret := C.ceph_opendir(mount.mount, cPath, &dir)
|
||||
if ret != 0 {
|
||||
return nil, getError(ret)
|
||||
}
|
||||
|
||||
return &Directory{
|
||||
mount: mount,
|
||||
dir: dir,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close the open directory handle.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_closedir(struct ceph_mount_info *cmount, struct ceph_dir_result *dirp);
|
||||
func (dir *Directory) Close() error {
|
||||
if dir.dir == nil {
|
||||
return nil
|
||||
}
|
||||
if err := getError(C.ceph_closedir(dir.mount.mount, dir.dir)); err != nil {
|
||||
return err
|
||||
}
|
||||
dir.dir = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Inode represents an inode number in the file system.
|
||||
type Inode uint64
|
||||
|
||||
// DType values are used to determine, when possible, the file type
|
||||
// of a directory entry.
|
||||
type DType uint8
|
||||
|
||||
const (
|
||||
// DTypeBlk indicates a directory entry is a block device.
|
||||
DTypeBlk = DType(C.DT_BLK)
|
||||
// DTypeChr indicates a directory entry is a character device.
|
||||
DTypeChr = DType(C.DT_CHR)
|
||||
// DTypeDir indicates a directory entry is a directory.
|
||||
DTypeDir = DType(C.DT_DIR)
|
||||
// DTypeFIFO indicates a directory entry is a named pipe (FIFO).
|
||||
DTypeFIFO = DType(C.DT_FIFO)
|
||||
// DTypeLnk indicates a directory entry is a symbolic link.
|
||||
DTypeLnk = DType(C.DT_LNK)
|
||||
// DTypeReg indicates a directory entry is a regular file.
|
||||
DTypeReg = DType(C.DT_REG)
|
||||
// DTypeSock indicates a directory entry is a UNIX domain socket.
|
||||
DTypeSock = DType(C.DT_SOCK)
|
||||
// DTypeUnknown indicates that the file type could not be determined.
|
||||
DTypeUnknown = DType(C.DT_UNKNOWN)
|
||||
)
|
||||
|
||||
// DirEntry represents an entry within a directory.
|
||||
type DirEntry struct {
|
||||
inode Inode
|
||||
name string
|
||||
dtype DType
|
||||
}
|
||||
|
||||
// Name returns the directory entry's name.
|
||||
func (d *DirEntry) Name() string {
|
||||
return d.name
|
||||
}
|
||||
|
||||
// Inode returns the directory entry's inode number.
|
||||
func (d *DirEntry) Inode() Inode {
|
||||
return d.inode
|
||||
}
|
||||
|
||||
// DType returns the Directory-entry's Type, indicating if it
|
||||
// is a regular file, directory, etc.
|
||||
// DType may be unknown and thus require an additional call
|
||||
// (stat for example) if Unknown.
|
||||
func (d *DirEntry) DType() DType {
|
||||
return d.dtype
|
||||
}
|
||||
|
||||
// DirEntryPlus is a DirEntry plus additional data (stat) for an entry
|
||||
// within a directory.
|
||||
type DirEntryPlus struct {
|
||||
DirEntry
|
||||
// statx: the converted statx returned by ceph_readdirplus_r
|
||||
statx *CephStatx
|
||||
}
|
||||
|
||||
// Statx returns cached stat metadata for the directory entry.
|
||||
// This call does not incur an actual file system stat.
|
||||
func (d *DirEntryPlus) Statx() *CephStatx {
|
||||
return d.statx
|
||||
}
|
||||
|
||||
// toDirEntry converts a c struct dirent to our go wrapper.
|
||||
func toDirEntry(de *C.struct_dirent) *DirEntry {
|
||||
return &DirEntry{
|
||||
inode: Inode(de.d_ino),
|
||||
name: C.GoString(&de.d_name[0]),
|
||||
dtype: DType(de.d_type),
|
||||
}
|
||||
}
|
||||
|
||||
// toDirEntryPlus converts c structs set by ceph_readdirplus_r to our go
|
||||
// wrapper.
|
||||
func toDirEntryPlus(de *C.struct_dirent, s C.struct_ceph_statx) *DirEntryPlus {
|
||||
return &DirEntryPlus{
|
||||
DirEntry: *toDirEntry(de),
|
||||
statx: cStructToCephStatx(s),
|
||||
}
|
||||
}
|
||||
|
||||
// ReadDir reads a single directory entry from the open Directory.
|
||||
// A nil DirEntry pointer will be returned when the Directory stream has been
|
||||
// exhausted.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_readdir_r(struct ceph_mount_info *cmount, struct ceph_dir_result *dirp, struct dirent *de);
|
||||
func (dir *Directory) ReadDir() (*DirEntry, error) {
|
||||
if dir.dir == nil {
|
||||
return nil, errBadFile
|
||||
}
|
||||
var de C.struct_dirent
|
||||
ret := C.ceph_readdir_r(dir.mount.mount, dir.dir, &de)
|
||||
if ret < 0 {
|
||||
return nil, getError(ret)
|
||||
}
|
||||
if ret == 0 {
|
||||
return nil, nil // End-of-stream
|
||||
}
|
||||
return toDirEntry(&de), nil
|
||||
}
|
||||
|
||||
// ReadDirPlus reads a single directory entry and stat information from the
|
||||
// open Directory.
|
||||
// A nil DirEntryPlus pointer will be returned when the Directory stream has
|
||||
// been exhausted.
|
||||
// See Statx for a description of the wants and flags parameters.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_readdirplus_r(struct ceph_mount_info *cmount, struct ceph_dir_result *dirp, struct dirent *de,
|
||||
// struct ceph_statx *stx, unsigned want, unsigned flags, struct Inode **out);
|
||||
func (dir *Directory) ReadDirPlus(
|
||||
want StatxMask, flags AtFlags) (*DirEntryPlus, error) {
|
||||
|
||||
if dir.dir == nil {
|
||||
return nil, errBadFile
|
||||
}
|
||||
var (
|
||||
de C.struct_dirent
|
||||
s C.struct_ceph_statx
|
||||
)
|
||||
ret := C.ceph_readdirplus_r(
|
||||
dir.mount.mount,
|
||||
dir.dir,
|
||||
&de,
|
||||
&s,
|
||||
C.uint(want),
|
||||
C.uint(flags),
|
||||
nil, // unused, internal Inode type not needed for high level api
|
||||
)
|
||||
if ret < 0 {
|
||||
return nil, getError(ret)
|
||||
}
|
||||
if ret == 0 {
|
||||
return nil, nil // End-of-stream
|
||||
}
|
||||
return toDirEntryPlus(&de, s), nil
|
||||
}
|
||||
|
||||
// RewindDir sets the directory stream to the beginning of the directory.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void ceph_rewinddir(struct ceph_mount_info *cmount, struct ceph_dir_result *dirp);
|
||||
func (dir *Directory) RewindDir() {
|
||||
if dir.dir == nil {
|
||||
return
|
||||
}
|
||||
C.ceph_rewinddir(dir.mount.mount, dir.dir)
|
||||
}
|
||||
|
||||
// dirEntries provides a convenient wrapper around slices of DirEntry items.
|
||||
// For example, use the Names() call to easily get only the names from a
|
||||
// DirEntry slice.
|
||||
type dirEntries []*DirEntry
|
||||
|
||||
// list returns all the contents of a directory as a dirEntries slice.
|
||||
//
|
||||
// list is implemented using ReadDir. If any of the calls to ReadDir returns
|
||||
// an error List will return an error. However, all previous entries
|
||||
// collected will still be returned. Callers of this function may want to check
|
||||
// the entries return value even when an error is returned.
|
||||
// List rewinds the handle every time it is called to get a full
|
||||
// listing of directory contents.
|
||||
func (dir *Directory) list() (dirEntries, error) {
|
||||
var (
|
||||
err error
|
||||
entry *DirEntry
|
||||
entries = make(dirEntries, 0)
|
||||
)
|
||||
dir.RewindDir()
|
||||
for {
|
||||
entry, err = dir.ReadDir()
|
||||
if err != nil || entry == nil {
|
||||
break
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
return entries, err
|
||||
}
|
||||
|
||||
// names returns a slice of only the name fields from dir entries.
|
||||
func (entries dirEntries) names() []string {
|
||||
names := make([]string, len(entries))
|
||||
for i, v := range entries {
|
||||
names[i] = v.Name()
|
||||
}
|
||||
return names
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
Package cephfs contains a set of wrappers around Ceph's libcephfs API.
|
||||
*/
|
||||
package cephfs
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package cephfs
|
||||
|
||||
/*
|
||||
#include <errno.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ceph/go-ceph/internal/errutil"
|
||||
)
|
||||
|
||||
func getError(e C.int) error {
|
||||
return errutil.GetError("cephfs", int(e))
|
||||
}
|
||||
|
||||
// getErrorIfNegative converts a ceph return code to error if negative.
|
||||
// This is useful for functions that return a usable positive value on
|
||||
// success but a negative error number on error.
|
||||
func getErrorIfNegative(ret C.int) error {
|
||||
if ret >= 0 {
|
||||
return nil
|
||||
}
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// Public go errors:
|
||||
|
||||
var (
|
||||
// ErrEmptyArgument may be returned if a function argument is passed
|
||||
// a zero-length slice or map.
|
||||
ErrEmptyArgument = errors.New("Argument must contain at least one item")
|
||||
|
||||
// ErrNotConnected may be returned when client is not connected
|
||||
// to a cluster.
|
||||
ErrNotConnected = getError(-C.ENOTCONN)
|
||||
// ErrNotExist indicates a non-specific missing resource.
|
||||
ErrNotExist = getError(-C.ENOENT)
|
||||
// ErrOpNotSupported is returned in general for operations that are not
|
||||
// supported.
|
||||
ErrOpNotSupported = getError(-C.EOPNOTSUPP)
|
||||
// ErrNotImplemented indicates a function is not implemented in by libcephfs.
|
||||
ErrNotImplemented = getError(-C.ENOSYS)
|
||||
|
||||
// Private errors:
|
||||
|
||||
errInvalid = getError(-C.EINVAL)
|
||||
errNameTooLong = getError(-C.ENAMETOOLONG)
|
||||
errRange = getError(-C.ERANGE)
|
||||
errBadFile = getError(-C.EBADF)
|
||||
errNotDir = getError(-C.ENOTDIR)
|
||||
)
|
||||
+440
@@ -0,0 +1,440 @@
|
||||
package cephfs
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lcephfs
|
||||
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
||||
#define _GNU_SOURCE
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <cephfs/libcephfs.h>
|
||||
|
||||
int _go_ceph_fchown(struct ceph_mount_info *cmount, int fd, uid_t uid, gid_t gid) {
|
||||
return ceph_fchown(cmount, fd, uid, gid);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"io"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ceph/go-ceph/internal/cutil"
|
||||
)
|
||||
|
||||
const (
|
||||
// SeekSet is used with Seek to set the absolute position in the file.
|
||||
SeekSet = int(C.SEEK_SET)
|
||||
// SeekCur is used with Seek to position the file relative to the current
|
||||
// position.
|
||||
SeekCur = int(C.SEEK_CUR)
|
||||
// SeekEnd is used with Seek to position the file relative to the end.
|
||||
SeekEnd = int(C.SEEK_END)
|
||||
)
|
||||
|
||||
// SyncChoice is used to control how metadata and/or data is sync'ed to
|
||||
// the file system.
|
||||
type SyncChoice int
|
||||
|
||||
const (
|
||||
// SyncAll will synchronize both data and metadata.
|
||||
SyncAll = SyncChoice(0)
|
||||
// SyncDataOnly will synchronize only data.
|
||||
SyncDataOnly = SyncChoice(1)
|
||||
)
|
||||
|
||||
// File represents an open file descriptor in cephfs.
|
||||
type File struct {
|
||||
mount *MountInfo
|
||||
fd C.int
|
||||
}
|
||||
|
||||
// Open a file at the given path. The flags are the same os flags as
|
||||
// a local open call. Mode is the same mode bits as a local open call.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_open(struct ceph_mount_info *cmount, const char *path, int flags, mode_t mode);
|
||||
func (mount *MountInfo) Open(path string, flags int, mode uint32) (*File, error) {
|
||||
if mount.mount == nil {
|
||||
return nil, ErrNotConnected
|
||||
}
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
ret := C.ceph_open(mount.mount, cPath, C.int(flags), C.mode_t(mode))
|
||||
if ret < 0 {
|
||||
return nil, getError(ret)
|
||||
}
|
||||
return &File{mount: mount, fd: ret}, nil
|
||||
}
|
||||
|
||||
func (f *File) validate() error {
|
||||
if f.mount == nil {
|
||||
return ErrNotConnected
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close the file.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_close(struct ceph_mount_info *cmount, int fd);
|
||||
func (f *File) Close() error {
|
||||
if f.fd == -1 {
|
||||
// already closed
|
||||
return nil
|
||||
}
|
||||
if err := f.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := getError(C.ceph_close(f.mount.mount, f.fd)); err != nil {
|
||||
return err
|
||||
}
|
||||
f.fd = -1
|
||||
return nil
|
||||
}
|
||||
|
||||
// read directly wraps the ceph_read call. Because read is such a common
|
||||
// operation we deviate from the ceph naming and expose Read and ReadAt
|
||||
// wrappers for external callers of the library.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_read(struct ceph_mount_info *cmount, int fd, char *buf, int64_t size, int64_t offset);
|
||||
func (f *File) read(buf []byte, offset int64) (int, error) {
|
||||
if err := f.validate(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(buf) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
bufptr := (*C.char)(unsafe.Pointer(&buf[0]))
|
||||
ret := C.ceph_read(
|
||||
f.mount.mount, f.fd, bufptr, C.int64_t(len(buf)), C.int64_t(offset))
|
||||
switch {
|
||||
case ret < 0:
|
||||
return 0, getError(ret)
|
||||
case ret == 0:
|
||||
return 0, io.EOF
|
||||
}
|
||||
return int(ret), nil
|
||||
}
|
||||
|
||||
// Read data from file. Up to len(buf) bytes will be read from the file.
|
||||
// The number of bytes read will be returned.
|
||||
// When nothing is left to read from the file, Read returns, 0, io.EOF.
|
||||
func (f *File) Read(buf []byte) (int, error) {
|
||||
// to-consider: should we mimic Go's behavior of returning an
|
||||
// io.ErrShortWrite error if write length < buf size?
|
||||
return f.read(buf, -1)
|
||||
}
|
||||
|
||||
// ReadAt will read data from the file starting at the given offset.
|
||||
// Up to len(buf) bytes will be read from the file.
|
||||
// The number of bytes read will be returned.
|
||||
// When nothing is left to read from the file, ReadAt returns, 0, io.EOF.
|
||||
func (f *File) ReadAt(buf []byte, offset int64) (int, error) {
|
||||
if offset < 0 {
|
||||
return 0, errInvalid
|
||||
}
|
||||
return f.read(buf, offset)
|
||||
}
|
||||
|
||||
// Preadv will read data from the file, starting at the given offset,
|
||||
// into the byte-slice data buffers sequentially.
|
||||
// The number of bytes read will be returned.
|
||||
// When nothing is left to read from the file the return values will be:
|
||||
// 0, io.EOF.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_preadv(struct ceph_mount_info *cmount, int fd, const struct iovec *iov, int iovcnt,
|
||||
// int64_t offset);
|
||||
func (f *File) Preadv(data [][]byte, offset int64) (int, error) {
|
||||
if err := f.validate(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
iov := cutil.ByteSlicesToIovec(data)
|
||||
defer iov.Free()
|
||||
|
||||
ret := C.ceph_preadv(
|
||||
f.mount.mount,
|
||||
f.fd,
|
||||
(*C.struct_iovec)(iov.Pointer()),
|
||||
C.int(iov.Len()),
|
||||
C.int64_t(offset))
|
||||
switch {
|
||||
case ret < 0:
|
||||
return 0, getError(ret)
|
||||
case ret == 0:
|
||||
return 0, io.EOF
|
||||
}
|
||||
iov.Sync()
|
||||
return int(ret), nil
|
||||
}
|
||||
|
||||
// write directly wraps the ceph_write call. Because write is such a common
|
||||
// operation we deviate from the ceph naming and expose Write and WriteAt
|
||||
// wrappers for external callers of the library.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_write(struct ceph_mount_info *cmount, int fd, const char *buf,
|
||||
// int64_t size, int64_t offset);
|
||||
func (f *File) write(buf []byte, offset int64) (int, error) {
|
||||
if err := f.validate(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(buf) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
bufptr := (*C.char)(unsafe.Pointer(&buf[0]))
|
||||
ret := C.ceph_write(
|
||||
f.mount.mount, f.fd, bufptr, C.int64_t(len(buf)), C.int64_t(offset))
|
||||
if ret < 0 {
|
||||
return 0, getError(ret)
|
||||
}
|
||||
return int(ret), nil
|
||||
}
|
||||
|
||||
// Write data from buf to the file.
|
||||
// The number of bytes written is returned.
|
||||
func (f *File) Write(buf []byte) (int, error) {
|
||||
return f.write(buf, -1)
|
||||
}
|
||||
|
||||
// WriteAt writes data from buf to the file at the specified offset.
|
||||
// The number of bytes written is returned.
|
||||
func (f *File) WriteAt(buf []byte, offset int64) (int, error) {
|
||||
if offset < 0 {
|
||||
return 0, errInvalid
|
||||
}
|
||||
return f.write(buf, offset)
|
||||
}
|
||||
|
||||
// Pwritev writes data from the slice of byte-slice buffers to the file at the
|
||||
// specified offset.
|
||||
// The number of bytes written is returned.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_pwritev(struct ceph_mount_info *cmount, int fd, const struct iovec *iov, int iovcnt,
|
||||
// int64_t offset);
|
||||
func (f *File) Pwritev(data [][]byte, offset int64) (int, error) {
|
||||
if err := f.validate(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
iov := cutil.ByteSlicesToIovec(data)
|
||||
defer iov.Free()
|
||||
|
||||
ret := C.ceph_pwritev(
|
||||
f.mount.mount,
|
||||
f.fd,
|
||||
(*C.struct_iovec)(iov.Pointer()),
|
||||
C.int(iov.Len()),
|
||||
C.int64_t(offset))
|
||||
if ret < 0 {
|
||||
return 0, getError(ret)
|
||||
}
|
||||
return int(ret), nil
|
||||
}
|
||||
|
||||
// Seek will reposition the file stream based on the given offset.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int64_t ceph_lseek(struct ceph_mount_info *cmount, int fd, int64_t offset, int whence);
|
||||
func (f *File) Seek(offset int64, whence int) (int64, error) {
|
||||
if err := f.validate(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// validate the seek whence value in case the caller skews
|
||||
// from the seek values we technically support from C as documented.
|
||||
// TODO: need to support seek-(hole|data) in mimic and later.
|
||||
switch whence {
|
||||
case SeekSet, SeekCur, SeekEnd:
|
||||
default:
|
||||
return 0, errInvalid
|
||||
}
|
||||
|
||||
ret := C.ceph_lseek(f.mount.mount, f.fd, C.int64_t(offset), C.int(whence))
|
||||
if ret < 0 {
|
||||
return 0, getError(C.int(ret))
|
||||
}
|
||||
return int64(ret), nil
|
||||
}
|
||||
|
||||
// Fchmod changes the mode bits (permissions) of a file.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_fchmod(struct ceph_mount_info *cmount, int fd, mode_t mode);
|
||||
func (f *File) Fchmod(mode uint32) error {
|
||||
if err := f.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ret := C.ceph_fchmod(f.mount.mount, f.fd, C.mode_t(mode))
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// Fchown changes the ownership of a file.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_fchown(struct ceph_mount_info *cmount, int fd, uid_t uid, gid_t gid);
|
||||
func (f *File) Fchown(user uint32, group uint32) error {
|
||||
if err := f.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ret := C._go_ceph_fchown(f.mount.mount, f.fd, C.uid_t(user), C.gid_t(group))
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// Fstatx returns information about an open file.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_fstatx(struct ceph_mount_info *cmount, int fd, struct ceph_statx *stx,
|
||||
// unsigned int want, unsigned int flags);
|
||||
func (f *File) Fstatx(want StatxMask, flags AtFlags) (*CephStatx, error) {
|
||||
if err := f.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var stx C.struct_ceph_statx
|
||||
ret := C.ceph_fstatx(
|
||||
f.mount.mount,
|
||||
f.fd,
|
||||
&stx,
|
||||
C.uint(want),
|
||||
C.uint(flags),
|
||||
)
|
||||
if err := getError(ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cStructToCephStatx(stx), nil
|
||||
}
|
||||
|
||||
// FallocFlags represent flags which determine the operation to be
|
||||
// performed on the given range.
|
||||
// CephFS supports only following two flags.
|
||||
type FallocFlags int
|
||||
|
||||
const (
|
||||
// FallocNoFlag means default option.
|
||||
FallocNoFlag = FallocFlags(0)
|
||||
// FallocFlKeepSize specifies that the file size will not be changed.
|
||||
FallocFlKeepSize = FallocFlags(C.FALLOC_FL_KEEP_SIZE)
|
||||
// FallocFlPunchHole specifies that the operation is to deallocate
|
||||
// space and zero the byte range.
|
||||
FallocFlPunchHole = FallocFlags(C.FALLOC_FL_PUNCH_HOLE)
|
||||
)
|
||||
|
||||
// Fallocate preallocates or releases disk space for the file for the
|
||||
// given byte range, the flags determine the operation to be performed
|
||||
// on the given range.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_fallocate(struct ceph_mount_info *cmount, int fd, int mode,
|
||||
// int64_t offset, int64_t length);
|
||||
func (f *File) Fallocate(mode FallocFlags, offset, length int64) error {
|
||||
if err := f.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
ret := C.ceph_fallocate(f.mount.mount, f.fd, C.int(mode), C.int64_t(offset), C.int64_t(length))
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// LockOp determines operations/type of locks which can be applied on a file.
|
||||
type LockOp int
|
||||
|
||||
const (
|
||||
// LockSH places a shared lock.
|
||||
// More than one process may hold a shared lock for a given file at a given time.
|
||||
LockSH = LockOp(C.LOCK_SH)
|
||||
// LockEX places an exclusive lock.
|
||||
// Only one process may hold an exclusive lock for a given file at a given time.
|
||||
LockEX = LockOp(C.LOCK_EX)
|
||||
// LockUN removes an existing lock held by this process.
|
||||
LockUN = LockOp(C.LOCK_UN)
|
||||
// LockNB can be ORed with any of the above to make a nonblocking call.
|
||||
LockNB = LockOp(C.LOCK_NB)
|
||||
)
|
||||
|
||||
// Flock applies or removes an advisory lock on an open file.
|
||||
// Param owner is the user-supplied identifier for the owner of the
|
||||
// lock, must be an arbitrary integer.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_flock(struct ceph_mount_info *cmount, int fd, int operation, uint64_t owner);
|
||||
func (f *File) Flock(operation LockOp, owner uint64) error {
|
||||
if err := f.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// validate the operation values before passing it on.
|
||||
switch operation &^ LockNB {
|
||||
case LockSH, LockEX, LockUN:
|
||||
default:
|
||||
return errInvalid
|
||||
}
|
||||
|
||||
ret := C.ceph_flock(f.mount.mount, f.fd, C.int(operation), C.uint64_t(owner))
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// Fsync ensures the file content that may be cached is committed to stable
|
||||
// storage.
|
||||
// Pass SyncAll to have this call behave like standard fsync and synchronize
|
||||
// all data and metadata.
|
||||
// Pass SyncDataOnly to have this call behave more like fdatasync (on linux).
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_fsync(struct ceph_mount_info *cmount, int fd, int syncdataonly);
|
||||
func (f *File) Fsync(sync SyncChoice) error {
|
||||
if err := f.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ret := C.ceph_fsync(
|
||||
f.mount.mount,
|
||||
f.fd,
|
||||
C.int(sync),
|
||||
)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// Sync ensures the file content that may be cached is committed to stable
|
||||
// storage.
|
||||
// Sync behaves like Go's os package File.Sync function.
|
||||
func (f *File) Sync() error {
|
||||
return f.Fsync(SyncAll)
|
||||
}
|
||||
|
||||
// Truncate sets the size of the open file.
|
||||
// NOTE: In some versions of ceph a bug exists where calling ftruncate on a
|
||||
// file open for read-only is permitted. The go-ceph wrapper does no additional
|
||||
// checking and will inherit the issue on affected versions of ceph. Please
|
||||
// refer to the following issue for details:
|
||||
// https://tracker.ceph.com/issues/48202
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_ftruncate(struct ceph_mount_info *cmount, int fd, int64_t size);
|
||||
func (f *File) Truncate(size int64) error {
|
||||
if err := f.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ret := C.ceph_ftruncate(
|
||||
f.mount.mount,
|
||||
f.fd,
|
||||
C.int64_t(size),
|
||||
)
|
||||
return getError(ret)
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package cephfs
|
||||
|
||||
// Fd returns the integer open file descriptor in cephfs.
|
||||
// NOTE: It doesn't make sense to consume the returned integer fd anywhere
|
||||
// outside CephFS and is recommended not to do so given the undefined behaviour.
|
||||
// Also, as seen with the Go standard library, the fd is only valid as long as
|
||||
// the corresponding File object is intact in the sense that an fd from a closed
|
||||
// File object is invalid.
|
||||
func (f *File) Fd() int {
|
||||
if f == nil || f.mount == nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
return int(f.fd)
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package cephfs
|
||||
|
||||
// Futime changes file/directory last access and modification times.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_futime(struct ceph_mount_info *cmount, int fd, struct utimbuf *buf);
|
||||
func (f *File) Futime(times *Utime) error {
|
||||
if err := f.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return f.mount.Futime(int(f.fd), times)
|
||||
}
|
||||
|
||||
// Futimens changes file/directory last access and modification times, here times param
|
||||
// is an array of Timespec struct having length 2, where times[0] represents the access time
|
||||
// and times[1] represents the modification time.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_futimens(struct ceph_mount_info *cmount, int fd, struct timespec times[2]);
|
||||
func (f *File) Futimens(times []Timespec) error {
|
||||
if err := f.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return f.mount.Futimens(int(f.fd), times)
|
||||
}
|
||||
|
||||
// Futimes changes file/directory last access and modification times, here times param
|
||||
// is an array of Timeval struct type having length 2, where times[0] represents the access time
|
||||
// and times[1] represents the modification time.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_futimes(struct ceph_mount_info *cmount, int fd, struct timeval times[2]);
|
||||
func (f *File) Futimes(times []Timeval) error {
|
||||
if err := f.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return f.mount.Futimes(int(f.fd), times)
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
//go:build !nautilus
|
||||
// +build !nautilus
|
||||
|
||||
package cephfs
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lcephfs
|
||||
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <cephfs/libcephfs.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
ts "github.com/ceph/go-ceph/internal/timespec"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Mknod creates a regular, block or character special file.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_mknod(struct ceph_mount_info *cmount, const char *path, mode_t mode,
|
||||
// dev_t rdev);
|
||||
func (mount *MountInfo) Mknod(path string, mode uint16, dev uint16) error {
|
||||
if err := mount.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
ret := C.ceph_mknod(mount.mount, cPath, C.mode_t(mode), C.dev_t(dev))
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// Utime struct is the equivalent of C.struct_utimbuf
|
||||
type Utime struct {
|
||||
// AcTime represents the file's access time in seconds since the Unix epoch.
|
||||
AcTime int64
|
||||
// ModTime represents the file's modification time in seconds since the Unix epoch.
|
||||
ModTime int64
|
||||
}
|
||||
|
||||
// Futime changes file/directory last access and modification times.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_futime(struct ceph_mount_info *cmount, int fd, struct utimbuf *buf);
|
||||
func (mount *MountInfo) Futime(fd int, times *Utime) error {
|
||||
if err := mount.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cFd := C.int(fd)
|
||||
uTimeBuf := &C.struct_utimbuf{
|
||||
actime: C.time_t(times.AcTime),
|
||||
modtime: C.time_t(times.ModTime),
|
||||
}
|
||||
|
||||
ret := C.ceph_futime(mount.mount, cFd, uTimeBuf)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// Timeval struct is the go equivalent of C.struct_timeval type
|
||||
type Timeval struct {
|
||||
// Sec represents seconds
|
||||
Sec int64
|
||||
// USec represents microseconds
|
||||
USec int64
|
||||
}
|
||||
|
||||
// Futimens changes file/directory last access and modification times, here times param
|
||||
// is an array of Timespec struct having length 2, where times[0] represents the access time
|
||||
// and times[1] represents the modification time.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_futimens(struct ceph_mount_info *cmount, int fd, struct timespec times[2]);
|
||||
func (mount *MountInfo) Futimens(fd int, times []Timespec) error {
|
||||
if err := mount.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(times) != 2 {
|
||||
return getError(-C.EINVAL)
|
||||
}
|
||||
|
||||
cFd := C.int(fd)
|
||||
cTimes := []C.struct_timespec{}
|
||||
for _, val := range times {
|
||||
cTs := &C.struct_timespec{}
|
||||
ts.CopyToCStruct(
|
||||
ts.Timespec(val),
|
||||
ts.CTimespecPtr(cTs),
|
||||
)
|
||||
cTimes = append(cTimes, *cTs)
|
||||
}
|
||||
|
||||
ret := C.ceph_futimens(mount.mount, cFd, &cTimes[0])
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// Futimes changes file/directory last access and modification times, here times param
|
||||
// is an array of Timeval struct type having length 2, where times[0] represents the access time
|
||||
// and times[1] represents the modification time.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_futimes(struct ceph_mount_info *cmount, int fd, struct timeval times[2]);
|
||||
func (mount *MountInfo) Futimes(fd int, times []Timeval) error {
|
||||
if err := mount.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(times) != 2 {
|
||||
return getError(-C.EINVAL)
|
||||
}
|
||||
|
||||
cFd := C.int(fd)
|
||||
cTimes := []C.struct_timeval{}
|
||||
for _, val := range times {
|
||||
cTimes = append(cTimes, C.struct_timeval{
|
||||
tv_sec: C.time_t(val.Sec),
|
||||
tv_usec: C.suseconds_t(val.USec),
|
||||
})
|
||||
}
|
||||
|
||||
ret := C.ceph_futimes(mount.mount, cFd, &cTimes[0])
|
||||
return getError(ret)
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
package cephfs
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lcephfs
|
||||
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
||||
#define _GNU_SOURCE
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/xattr.h>
|
||||
#include <cephfs/libcephfs.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/ceph/go-ceph/internal/cutil"
|
||||
"github.com/ceph/go-ceph/internal/retry"
|
||||
)
|
||||
|
||||
// XattrFlags are used to control the behavior of set-xattr calls.
|
||||
type XattrFlags int
|
||||
|
||||
const (
|
||||
// XattrDefault specifies that set-xattr calls use the default behavior of
|
||||
// creating or updating an xattr.
|
||||
XattrDefault = XattrFlags(0)
|
||||
// XattrCreate specifies that set-xattr calls only set new xattrs.
|
||||
XattrCreate = XattrFlags(C.XATTR_CREATE)
|
||||
// XattrReplace specifies that set-xattr calls only replace existing xattr
|
||||
// values.
|
||||
XattrReplace = XattrFlags(C.XATTR_REPLACE)
|
||||
)
|
||||
|
||||
// SetXattr sets an extended attribute on the open file.
|
||||
//
|
||||
// NOTE: Attempting to set an xattr value with an empty value may cause the
|
||||
// xattr to be unset on some older versions of ceph.
|
||||
// Please refer to https://tracker.ceph.com/issues/46084
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_fsetxattr(struct ceph_mount_info *cmount, int fd, const char *name,
|
||||
// const void *value, size_t size, int flags);
|
||||
func (f *File) SetXattr(name string, value []byte, flags XattrFlags) error {
|
||||
if err := f.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if name == "" {
|
||||
return errInvalid
|
||||
}
|
||||
var vptr unsafe.Pointer
|
||||
if len(value) > 0 {
|
||||
vptr = unsafe.Pointer(&value[0])
|
||||
}
|
||||
cName := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
|
||||
ret := C.ceph_fsetxattr(
|
||||
f.mount.mount,
|
||||
f.fd,
|
||||
cName,
|
||||
vptr,
|
||||
C.size_t(len(value)),
|
||||
C.int(flags))
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// GetXattr gets an extended attribute from the open file.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_fgetxattr(struct ceph_mount_info *cmount, int fd, const char *name,
|
||||
// void *value, size_t size);
|
||||
func (f *File) GetXattr(name string) ([]byte, error) {
|
||||
if err := f.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if name == "" {
|
||||
return nil, errInvalid
|
||||
}
|
||||
cName := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
|
||||
var (
|
||||
ret C.int
|
||||
err error
|
||||
buf []byte
|
||||
)
|
||||
// range from 1k to 64KiB
|
||||
retry.WithSizes(1024, 1<<16, func(size int) retry.Hint {
|
||||
buf = make([]byte, size)
|
||||
ret = C.ceph_fgetxattr(
|
||||
f.mount.mount,
|
||||
f.fd,
|
||||
cName,
|
||||
unsafe.Pointer(&buf[0]),
|
||||
C.size_t(size))
|
||||
err = getErrorIfNegative(ret)
|
||||
return retry.DoubleSize.If(err == errRange)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf[:ret], nil
|
||||
}
|
||||
|
||||
// ListXattr returns a slice containing strings for the name of each xattr set
|
||||
// on the file.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_flistxattr(struct ceph_mount_info *cmount, int fd, char *list, size_t size);
|
||||
func (f *File) ListXattr() ([]string, error) {
|
||||
if err := f.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
ret C.int
|
||||
err error
|
||||
buf []byte
|
||||
)
|
||||
// range from 1k to 64KiB
|
||||
retry.WithSizes(1024, 1<<16, func(size int) retry.Hint {
|
||||
buf = make([]byte, size)
|
||||
ret = C.ceph_flistxattr(
|
||||
f.mount.mount,
|
||||
f.fd,
|
||||
(*C.char)(unsafe.Pointer(&buf[0])),
|
||||
C.size_t(size))
|
||||
err = getErrorIfNegative(ret)
|
||||
return retry.DoubleSize.If(err == errRange)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
names := cutil.SplitSparseBuffer(buf[:ret])
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// RemoveXattr removes the named xattr from the open file.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_fremovexattr(struct ceph_mount_info *cmount, int fd, const char *name);
|
||||
func (f *File) RemoveXattr(name string) error {
|
||||
if err := f.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if name == "" {
|
||||
return errInvalid
|
||||
}
|
||||
cName := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
|
||||
ret := C.ceph_fremovexattr(
|
||||
f.mount.mount,
|
||||
f.fd,
|
||||
cName)
|
||||
return getError(ret)
|
||||
}
|
||||
+406
@@ -0,0 +1,406 @@
|
||||
package cephfs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ceph/go-ceph/internal/log"
|
||||
)
|
||||
|
||||
var (
|
||||
errIsDir = errors.New("is a directory")
|
||||
)
|
||||
|
||||
// MountWrapper provides a wrapper type that adapts a CephFS Mount into a
|
||||
// io.FS compatible type.
|
||||
type MountWrapper struct {
|
||||
mount *MountInfo
|
||||
enableTrace bool
|
||||
}
|
||||
|
||||
type fileWrapper struct {
|
||||
parent *MountWrapper
|
||||
file *File
|
||||
name string
|
||||
}
|
||||
|
||||
type dirWrapper struct {
|
||||
parent *MountWrapper
|
||||
directory *Directory
|
||||
name string
|
||||
}
|
||||
|
||||
type dentryWrapper struct {
|
||||
parent *MountWrapper
|
||||
de *DirEntryPlus
|
||||
}
|
||||
|
||||
type infoWrapper struct {
|
||||
parent *MountWrapper
|
||||
sx *CephStatx
|
||||
name string
|
||||
}
|
||||
|
||||
// Wrap a CephFS Mount object into a new type that is compatible with Go's io.FS
|
||||
// interface. CephFS Mounts are not compatible with io.FS directly because the
|
||||
// go-ceph library predates the addition of io.FS to Go as well as the fact that
|
||||
// go-ceph attempts to provide APIs that match the cephfs libraries first and
|
||||
// foremost.
|
||||
func Wrap(mount *MountInfo) *MountWrapper {
|
||||
wm := &MountWrapper{mount: mount}
|
||||
debugf(wm, "Wrap", "created")
|
||||
return wm
|
||||
}
|
||||
|
||||
/* MountWrapper:
|
||||
** Implements https://pkg.go.dev/io/fs#FS
|
||||
** Wraps cephfs.MountInfo
|
||||
*/
|
||||
|
||||
// SetTracing configures the MountWrapper and objects connected to it for debug
|
||||
// tracing. True enables tracing and false disables it. A debug logging
|
||||
// function must also be set using go-ceph's common.log.SetDebugf function.
|
||||
func (mw *MountWrapper) SetTracing(enable bool) {
|
||||
mw.enableTrace = enable
|
||||
}
|
||||
|
||||
// identify the MountWrapper object for logging purposes.
|
||||
func (mw *MountWrapper) identify() string {
|
||||
return fmt.Sprintf("MountWrapper<%p>", mw)
|
||||
}
|
||||
|
||||
// trace returns true if debug tracing is enabled.
|
||||
func (mw *MountWrapper) trace() bool {
|
||||
return mw.enableTrace
|
||||
}
|
||||
|
||||
// Open opens the named file. This may be either a regular file or a directory.
|
||||
// Directories opened with this function will return object compatible with the
|
||||
// io.ReadDirFile interface.
|
||||
func (mw *MountWrapper) Open(name string) (fs.File, error) {
|
||||
debugf(mw, "Open", "(%v)", name)
|
||||
// there are a bunch of patterns that fsTetster/testfs looks for that seems
|
||||
// under-documented. They mainly seem to try and enforce "clean" paths.
|
||||
// look for them and reject them here because ceph libs won't reject on
|
||||
// its own
|
||||
if strings.HasPrefix(name, "/") ||
|
||||
strings.HasSuffix(name, "/.") ||
|
||||
strings.Contains(name, "//") ||
|
||||
strings.Contains(name, "/./") ||
|
||||
strings.Contains(name, "/../") {
|
||||
return nil, &fs.PathError{Op: "open", Path: name, Err: errInvalid}
|
||||
}
|
||||
|
||||
d, err := mw.mount.OpenDir(name)
|
||||
if err == nil {
|
||||
debugf(mw, "Open", "(%v): dir ok", name)
|
||||
dw := &dirWrapper{parent: mw, directory: d, name: name}
|
||||
return dw, nil
|
||||
}
|
||||
if !errors.Is(err, errNotDir) {
|
||||
debugf(mw, "Open", "(%v): dir error: %v", name, err)
|
||||
return nil, &fs.PathError{Op: "open", Path: name, Err: err}
|
||||
}
|
||||
|
||||
f, err := mw.mount.Open(name, os.O_RDONLY, 0)
|
||||
if err == nil {
|
||||
debugf(mw, "Open", "(%v): file ok", name)
|
||||
fw := &fileWrapper{parent: mw, file: f, name: name}
|
||||
return fw, nil
|
||||
}
|
||||
debugf(mw, "Open", "(%v): file error: %v", name, err)
|
||||
return nil, &fs.PathError{Op: "open", Path: name, Err: err}
|
||||
}
|
||||
|
||||
/* fileWrapper:
|
||||
** Implements https://pkg.go.dev/io/fs#FS
|
||||
** Wraps cephfs.File
|
||||
*/
|
||||
|
||||
func (fw *fileWrapper) Stat() (fs.FileInfo, error) {
|
||||
debugf(fw, "Stat", "()")
|
||||
sx, err := fw.file.Fstatx(StatxBasicStats, AtSymlinkNofollow)
|
||||
if err != nil {
|
||||
debugf(fw, "Stat", "() -> err:%v", err)
|
||||
return nil, &fs.PathError{Op: "stat", Path: fw.name, Err: err}
|
||||
}
|
||||
debugf(fw, "Stat", "() ok")
|
||||
return &infoWrapper{fw.parent, sx, path.Base(fw.name)}, nil
|
||||
}
|
||||
|
||||
func (fw *fileWrapper) Read(b []byte) (int, error) {
|
||||
debugf(fw, "Read", "(...)")
|
||||
return fw.file.Read(b)
|
||||
}
|
||||
|
||||
func (fw *fileWrapper) Close() error {
|
||||
debugf(fw, "Close", "()")
|
||||
return fw.file.Close()
|
||||
}
|
||||
|
||||
func (fw *fileWrapper) identify() string {
|
||||
return fmt.Sprintf("fileWrapper<%p>[%v]", fw, fw.name)
|
||||
}
|
||||
|
||||
func (fw *fileWrapper) trace() bool {
|
||||
return fw.parent.trace()
|
||||
}
|
||||
|
||||
/* dirWrapper:
|
||||
** Implements https://pkg.go.dev/io/fs#ReadDirFile
|
||||
** Wraps cephfs.Directory
|
||||
*/
|
||||
|
||||
func (dw *dirWrapper) Stat() (fs.FileInfo, error) {
|
||||
debugf(dw, "Stat", "()")
|
||||
sx, err := dw.parent.mount.Statx(dw.name, StatxBasicStats, AtSymlinkNofollow)
|
||||
if err != nil {
|
||||
debugf(dw, "Stat", "() -> err:%v", err)
|
||||
return nil, &fs.PathError{Op: "stat", Path: dw.name, Err: err}
|
||||
}
|
||||
debugf(dw, "Stat", "() ok")
|
||||
return &infoWrapper{dw.parent, sx, path.Base(dw.name)}, nil
|
||||
}
|
||||
|
||||
func (dw *dirWrapper) Read(_ []byte) (int, error) {
|
||||
debugf(dw, "Read", "(...)")
|
||||
return 0, &fs.PathError{Op: "read", Path: dw.name, Err: errIsDir}
|
||||
}
|
||||
|
||||
func (dw *dirWrapper) ReadDir(n int) ([]fs.DirEntry, error) {
|
||||
debugf(dw, "ReadDir", "(%v)", n)
|
||||
if n > 0 {
|
||||
return dw.readDirSome(n)
|
||||
}
|
||||
return dw.readDirAll()
|
||||
}
|
||||
|
||||
const defaultDirReadCount = 256 // how many entries to read per loop
|
||||
|
||||
func (dw *dirWrapper) readDirAll() ([]fs.DirEntry, error) {
|
||||
debugf(dw, "readDirAll", "()")
|
||||
var (
|
||||
err error
|
||||
egroup []fs.DirEntry
|
||||
entries = make([]fs.DirEntry, 0)
|
||||
size = defaultDirReadCount
|
||||
)
|
||||
for {
|
||||
egroup, err = dw.readDirSome(size)
|
||||
entries = append(entries, egroup...)
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
debugf(dw, "readDirAll", "() -> len:%v, err:%v", len(entries), err)
|
||||
return entries, err
|
||||
}
|
||||
|
||||
func (dw *dirWrapper) readDirSome(n int) ([]fs.DirEntry, error) {
|
||||
debugf(dw, "readDirSome", "(%v)", n)
|
||||
var (
|
||||
idx int
|
||||
err error
|
||||
entry *DirEntryPlus
|
||||
entries = make([]fs.DirEntry, n)
|
||||
)
|
||||
for {
|
||||
entry, err = dw.directory.ReadDirPlus(StatxBasicStats, AtSymlinkNofollow)
|
||||
debugf(dw, "readDirSome", "(%v): got entry:%v, err:%v", n, entry, err)
|
||||
if err != nil || entry == nil {
|
||||
break
|
||||
}
|
||||
switch entry.Name() {
|
||||
case ".", "..":
|
||||
continue
|
||||
}
|
||||
entries[idx] = &dentryWrapper{dw.parent, entry}
|
||||
idx++
|
||||
if idx >= n {
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx == 0 {
|
||||
debugf(dw, "readDirSome", "(%v): EOF", n)
|
||||
return nil, io.EOF
|
||||
}
|
||||
debugf(dw, "readDirSome", "(%v): got entry:%v, err:%v", n, entries[:idx], err)
|
||||
return entries[:idx], err
|
||||
}
|
||||
|
||||
func (dw *dirWrapper) Close() error {
|
||||
debugf(dw, "Close", "()")
|
||||
return dw.directory.Close()
|
||||
}
|
||||
|
||||
func (dw *dirWrapper) identify() string {
|
||||
return fmt.Sprintf("dirWrapper<%p>[%v]", dw, dw.name)
|
||||
}
|
||||
|
||||
func (dw *dirWrapper) trace() bool {
|
||||
return dw.parent.trace()
|
||||
}
|
||||
|
||||
/* dentryWrapper:
|
||||
** Implements https://pkg.go.dev/io/fs#DirEntry
|
||||
** Wraps cephfs.DirEntryPlus
|
||||
*/
|
||||
|
||||
func (dew *dentryWrapper) Name() string {
|
||||
debugf(dew, "Name", "()")
|
||||
return dew.de.Name()
|
||||
}
|
||||
|
||||
func (dew *dentryWrapper) IsDir() bool {
|
||||
v := dew.de.DType() == DTypeDir
|
||||
debugf(dew, "IsDir", "() -> %v", v)
|
||||
return v
|
||||
}
|
||||
|
||||
func (dew *dentryWrapper) Type() fs.FileMode {
|
||||
m := dew.de.Statx().Mode
|
||||
v := cephModeToFileMode(m).Type()
|
||||
debugf(dew, "Type", "() -> %v", v)
|
||||
return v
|
||||
}
|
||||
|
||||
func (dew *dentryWrapper) Info() (fs.FileInfo, error) {
|
||||
debugf(dew, "Info", "()")
|
||||
sx := dew.de.Statx()
|
||||
name := dew.de.Name()
|
||||
return &infoWrapper{dew.parent, sx, name}, nil
|
||||
}
|
||||
|
||||
func (dew *dentryWrapper) identify() string {
|
||||
return fmt.Sprintf("dentryWrapper<%p>[%v]", dew, dew.de.Name())
|
||||
}
|
||||
|
||||
func (dew *dentryWrapper) trace() bool {
|
||||
return dew.parent.trace()
|
||||
}
|
||||
|
||||
/* infoWrapper:
|
||||
** Implements https://pkg.go.dev/io/fs#FileInfo
|
||||
** Wraps cephfs.CephStatx
|
||||
*/
|
||||
|
||||
func (iw *infoWrapper) Name() string {
|
||||
debugf(iw, "Name", "()")
|
||||
return iw.name
|
||||
}
|
||||
|
||||
func (iw *infoWrapper) Size() int64 {
|
||||
debugf(iw, "Size", "() -> %v", iw.sx.Size)
|
||||
return int64(iw.sx.Size)
|
||||
}
|
||||
|
||||
func (iw *infoWrapper) Sys() any {
|
||||
debugf(iw, "Sys", "()")
|
||||
return iw.sx
|
||||
}
|
||||
|
||||
func (iw *infoWrapper) Mode() fs.FileMode {
|
||||
v := cephModeToFileMode(iw.sx.Mode)
|
||||
debugf(iw, "Mode", "() -> %#o -> %#o/%v", iw.sx.Mode, uint32(v), v.Type())
|
||||
return v
|
||||
}
|
||||
|
||||
func (iw *infoWrapper) IsDir() bool {
|
||||
v := iw.sx.Mode&modeIFMT == modeIFDIR
|
||||
debugf(iw, "IsDir", "() -> %v", v)
|
||||
return v
|
||||
}
|
||||
|
||||
func (iw *infoWrapper) ModTime() time.Time {
|
||||
v := time.Unix(iw.sx.Mtime.Sec, iw.sx.Mtime.Nsec)
|
||||
debugf(iw, "ModTime", "() -> %v", v)
|
||||
return v
|
||||
}
|
||||
|
||||
func (iw *infoWrapper) identify() string {
|
||||
return fmt.Sprintf("infoWrapper<%p>[%v]", iw, iw.name)
|
||||
}
|
||||
|
||||
func (iw *infoWrapper) trace() bool {
|
||||
return iw.parent.trace()
|
||||
}
|
||||
|
||||
/* copy and paste values from the linux headers. We always need to use
|
||||
** the linux header values, regardless of the platform go-ceph is built
|
||||
** for. Rather than jumping through header hoops, copy and paste is
|
||||
** more consistent and reliable.
|
||||
*/
|
||||
const (
|
||||
/* file type mask */
|
||||
modeIFMT = uint16(0170000)
|
||||
/* file types */
|
||||
modeIFDIR = uint16(0040000)
|
||||
modeIFCHR = uint16(0020000)
|
||||
modeIFBLK = uint16(0060000)
|
||||
modeIFREG = uint16(0100000)
|
||||
modeIFIFO = uint16(0010000)
|
||||
modeIFLNK = uint16(0120000)
|
||||
modeIFSOCK = uint16(0140000)
|
||||
/* protection bits */
|
||||
modeISUID = uint16(0004000)
|
||||
modeISGID = uint16(0002000)
|
||||
modeISVTX = uint16(0001000)
|
||||
)
|
||||
|
||||
// cephModeToFileMode takes a linux compatible cephfs mode value
|
||||
// and returns a Go-compatiable os-agnostic FileMode value.
|
||||
func cephModeToFileMode(m uint16) fs.FileMode {
|
||||
// start with permission bits
|
||||
mode := fs.FileMode(m & 0777)
|
||||
// file type - inspired by go's src/os/stat_linux.go
|
||||
switch m & modeIFMT {
|
||||
case modeIFBLK:
|
||||
mode |= fs.ModeDevice
|
||||
case modeIFCHR:
|
||||
mode |= fs.ModeDevice | fs.ModeCharDevice
|
||||
case modeIFDIR:
|
||||
mode |= fs.ModeDir
|
||||
case modeIFIFO:
|
||||
mode |= fs.ModeNamedPipe
|
||||
case modeIFLNK:
|
||||
mode |= fs.ModeSymlink
|
||||
case modeIFREG:
|
||||
// nothing to do
|
||||
case modeIFSOCK:
|
||||
mode |= fs.ModeSocket
|
||||
}
|
||||
// protection bits
|
||||
if m&modeISUID != 0 {
|
||||
mode |= fs.ModeSetuid
|
||||
}
|
||||
if m&modeISGID != 0 {
|
||||
mode |= fs.ModeSetgid
|
||||
}
|
||||
if m&modeISVTX != 0 {
|
||||
mode |= fs.ModeSticky
|
||||
}
|
||||
return mode
|
||||
}
|
||||
|
||||
// wrapperObject helps identify an object to be logged.
|
||||
type wrapperObject interface {
|
||||
identify() string
|
||||
trace() bool
|
||||
}
|
||||
|
||||
// debugf formats info about a function and logs it.
|
||||
func debugf(o wrapperObject, fname, format string, args ...any) {
|
||||
if o.trace() {
|
||||
log.Debugf(fmt.Sprintf("%v.%v: %s", o.identify(), fname, format), args...)
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package cephfs
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lcephfs
|
||||
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
||||
#include <stdlib.h>
|
||||
#include <cephfs/libcephfs.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// MakeDirs creates multiple directories at once.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_mkdirs(struct ceph_mount_info *cmount, const char *path, mode_t mode);
|
||||
func (mount *MountInfo) MakeDirs(path string, mode uint32) error {
|
||||
if err := mount.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
ret := C.ceph_mkdirs(mount.mount, cPath, C.mode_t(mode))
|
||||
return getError(ret)
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// ceph_mount_perms_set available in mimic & later
|
||||
|
||||
package cephfs
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lcephfs
|
||||
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
||||
#include <cephfs/libcephfs.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// SetMountPerms applies the given UserPerm to the mount object, which it will
|
||||
// then use to define the connection's ownership credentials.
|
||||
// This function must be called after Init but before Mount.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_mount_perms_set(struct ceph_mount_info *cmount, UserPerm *perm);
|
||||
func (mount *MountInfo) SetMountPerms(perm *UserPerm) error {
|
||||
return getError(C.ceph_mount_perms_set(mount.mount, perm.userPerm))
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
package cephfs
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lcephfs
|
||||
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
||||
#include <stdlib.h>
|
||||
#include <cephfs/libcephfs.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// CurrentDir gets the current working directory.
|
||||
func (mount *MountInfo) CurrentDir() string {
|
||||
if err := mount.validate(); err != nil {
|
||||
return ""
|
||||
}
|
||||
cDir := C.ceph_getcwd(mount.mount)
|
||||
return C.GoString(cDir)
|
||||
}
|
||||
|
||||
// ChangeDir changes the current working directory.
|
||||
func (mount *MountInfo) ChangeDir(path string) error {
|
||||
if err := mount.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
ret := C.ceph_chdir(mount.mount, cPath)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// MakeDir creates a directory.
|
||||
func (mount *MountInfo) MakeDir(path string, mode uint32) error {
|
||||
if err := mount.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
ret := C.ceph_mkdir(mount.mount, cPath, C.mode_t(mode))
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// RemoveDir removes a directory.
|
||||
func (mount *MountInfo) RemoveDir(path string) error {
|
||||
if err := mount.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
ret := C.ceph_rmdir(mount.mount, cPath)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// Unlink removes a file.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_unlink(struct ceph_mount_info *cmount, const char *path);
|
||||
func (mount *MountInfo) Unlink(path string) error {
|
||||
if err := mount.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
ret := C.ceph_unlink(mount.mount, cPath)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// Link creates a new link to an existing file.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_link (struct ceph_mount_info *cmount, const char *existing, const char *newname);
|
||||
func (mount *MountInfo) Link(oldname, newname string) error {
|
||||
if err := mount.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
cOldname := C.CString(oldname)
|
||||
defer C.free(unsafe.Pointer(cOldname))
|
||||
|
||||
cNewname := C.CString(newname)
|
||||
defer C.free(unsafe.Pointer(cNewname))
|
||||
|
||||
ret := C.ceph_link(mount.mount, cOldname, cNewname)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// Symlink creates a symbolic link to an existing path.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_symlink(struct ceph_mount_info *cmount, const char *existing, const char *newname);
|
||||
func (mount *MountInfo) Symlink(existing, newname string) error {
|
||||
if err := mount.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
cExisting := C.CString(existing)
|
||||
defer C.free(unsafe.Pointer(cExisting))
|
||||
|
||||
cNewname := C.CString(newname)
|
||||
defer C.free(unsafe.Pointer(cNewname))
|
||||
|
||||
ret := C.ceph_symlink(mount.mount, cExisting, cNewname)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// Readlink returns the value of a symbolic link.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_readlink(struct ceph_mount_info *cmount, const char *path, char *buf, int64_t size);
|
||||
func (mount *MountInfo) Readlink(path string) (string, error) {
|
||||
if err := mount.validate(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
buf := make([]byte, 4096)
|
||||
ret := C.ceph_readlink(mount.mount,
|
||||
cPath,
|
||||
(*C.char)(unsafe.Pointer(&buf[0])),
|
||||
C.int64_t(len(buf)))
|
||||
if ret < 0 {
|
||||
return "", getError(ret)
|
||||
}
|
||||
|
||||
return string(buf[:ret]), nil
|
||||
}
|
||||
|
||||
// Statx returns information about a file/directory.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_statx(struct ceph_mount_info *cmount, const char *path, struct ceph_statx *stx,
|
||||
// unsigned int want, unsigned int flags);
|
||||
func (mount *MountInfo) Statx(path string, want StatxMask, flags AtFlags) (*CephStatx, error) {
|
||||
if err := mount.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
var stx C.struct_ceph_statx
|
||||
ret := C.ceph_statx(
|
||||
mount.mount,
|
||||
cPath,
|
||||
&stx,
|
||||
C.uint(want),
|
||||
C.uint(flags),
|
||||
)
|
||||
if err := getError(ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cStructToCephStatx(stx), nil
|
||||
}
|
||||
|
||||
// Rename a file or directory.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_rename(struct ceph_mount_info *cmount, const char *from, const char *to);
|
||||
func (mount *MountInfo) Rename(from, to string) error {
|
||||
if err := mount.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
cFrom := C.CString(from)
|
||||
defer C.free(unsafe.Pointer(cFrom))
|
||||
cTo := C.CString(to)
|
||||
defer C.free(unsafe.Pointer(cTo))
|
||||
|
||||
ret := C.ceph_rename(mount.mount, cFrom, cTo)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// Truncate sets the size of the specified file.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_truncate(struct ceph_mount_info *cmount, const char *path, int64_t size);
|
||||
func (mount *MountInfo) Truncate(path string, size int64) error {
|
||||
if err := mount.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
ret := C.ceph_truncate(
|
||||
mount.mount,
|
||||
cPath,
|
||||
C.int64_t(size),
|
||||
)
|
||||
return getError(ret)
|
||||
}
|
||||
+291
@@ -0,0 +1,291 @@
|
||||
package cephfs
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lcephfs
|
||||
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
||||
#define _GNU_SOURCE
|
||||
#include <stdlib.h>
|
||||
#include <cephfs/libcephfs.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/ceph/go-ceph/internal/cutil"
|
||||
"github.com/ceph/go-ceph/internal/retry"
|
||||
)
|
||||
|
||||
// SetXattr sets an extended attribute on the file at the supplied path.
|
||||
//
|
||||
// NOTE: Attempting to set an xattr value with an empty value may cause
|
||||
// the xattr to be unset. Please refer to https://tracker.ceph.com/issues/46084
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_setxattr(struct ceph_mount_info *cmount, const char *path, const char *name,
|
||||
// const void *value, size_t size, int flags);
|
||||
func (mount *MountInfo) SetXattr(path, name string, value []byte, flags XattrFlags) error {
|
||||
if err := mount.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if name == "" {
|
||||
return errInvalid
|
||||
}
|
||||
var vptr unsafe.Pointer
|
||||
if len(value) > 0 {
|
||||
vptr = unsafe.Pointer(&value[0])
|
||||
}
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
cName := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
|
||||
ret := C.ceph_setxattr(
|
||||
mount.mount,
|
||||
cPath,
|
||||
cName,
|
||||
vptr,
|
||||
C.size_t(len(value)),
|
||||
C.int(flags))
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// GetXattr gets an extended attribute from the file at the supplied path.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_getxattr(struct ceph_mount_info *cmount, const char *path, const char *name,
|
||||
// void *value, size_t size);
|
||||
func (mount *MountInfo) GetXattr(path, name string) ([]byte, error) {
|
||||
if err := mount.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if name == "" {
|
||||
return nil, errInvalid
|
||||
}
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
cName := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
|
||||
var (
|
||||
ret C.int
|
||||
err error
|
||||
buf []byte
|
||||
)
|
||||
// range from 1k to 64KiB
|
||||
retry.WithSizes(1024, 1<<16, func(size int) retry.Hint {
|
||||
buf = make([]byte, size)
|
||||
ret = C.ceph_getxattr(
|
||||
mount.mount,
|
||||
cPath,
|
||||
cName,
|
||||
unsafe.Pointer(&buf[0]),
|
||||
C.size_t(size))
|
||||
err = getErrorIfNegative(ret)
|
||||
return retry.DoubleSize.If(err == errRange)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf[:ret], nil
|
||||
}
|
||||
|
||||
// ListXattr returns a slice containing strings for the name of each xattr set
|
||||
// on the file at the supplied path.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_listxattr(struct ceph_mount_info *cmount, const char *path, char *list, size_t size);
|
||||
func (mount *MountInfo) ListXattr(path string) ([]string, error) {
|
||||
if err := mount.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
var (
|
||||
ret C.int
|
||||
err error
|
||||
buf []byte
|
||||
)
|
||||
// range from 1k to 64KiB
|
||||
retry.WithSizes(1024, 1<<16, func(size int) retry.Hint {
|
||||
buf = make([]byte, size)
|
||||
ret = C.ceph_listxattr(
|
||||
mount.mount,
|
||||
cPath,
|
||||
(*C.char)(unsafe.Pointer(&buf[0])),
|
||||
C.size_t(size))
|
||||
err = getErrorIfNegative(ret)
|
||||
return retry.DoubleSize.If(err == errRange)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
names := cutil.SplitSparseBuffer(buf[:ret])
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// RemoveXattr removes the named xattr from the open file.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_removexattr(struct ceph_mount_info *cmount, const char *path, const char *name);
|
||||
func (mount *MountInfo) RemoveXattr(path, name string) error {
|
||||
if err := mount.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if name == "" {
|
||||
return errInvalid
|
||||
}
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
cName := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
|
||||
ret := C.ceph_removexattr(
|
||||
mount.mount,
|
||||
cPath,
|
||||
cName)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// LsetXattr sets an extended attribute on the file at the supplied path.
|
||||
//
|
||||
// NOTE: Attempting to set an xattr value with an empty value may cause
|
||||
// the xattr to be unset. Please refer to https://tracker.ceph.com/issues/46084
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_lsetxattr(struct ceph_mount_info *cmount, const char *path, const char *name,
|
||||
// const void *value, size_t size, int flags);
|
||||
func (mount *MountInfo) LsetXattr(path, name string, value []byte, flags XattrFlags) error {
|
||||
if err := mount.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if name == "" {
|
||||
return errInvalid
|
||||
}
|
||||
var vptr unsafe.Pointer
|
||||
if len(value) > 0 {
|
||||
vptr = unsafe.Pointer(&value[0])
|
||||
}
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
cName := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
|
||||
ret := C.ceph_lsetxattr(
|
||||
mount.mount,
|
||||
cPath,
|
||||
cName,
|
||||
vptr,
|
||||
C.size_t(len(value)),
|
||||
C.int(flags))
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// LgetXattr gets an extended attribute from the file at the supplied path.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_lgetxattr(struct ceph_mount_info *cmount, const char *path, const char *name,
|
||||
// void *value, size_t size);
|
||||
func (mount *MountInfo) LgetXattr(path, name string) ([]byte, error) {
|
||||
if err := mount.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if name == "" {
|
||||
return nil, errInvalid
|
||||
}
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
cName := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
|
||||
var (
|
||||
ret C.int
|
||||
err error
|
||||
buf []byte
|
||||
)
|
||||
// range from 1k to 64KiB
|
||||
retry.WithSizes(1024, 1<<16, func(size int) retry.Hint {
|
||||
buf = make([]byte, size)
|
||||
ret = C.ceph_lgetxattr(
|
||||
mount.mount,
|
||||
cPath,
|
||||
cName,
|
||||
unsafe.Pointer(&buf[0]),
|
||||
C.size_t(size))
|
||||
err = getErrorIfNegative(ret)
|
||||
return retry.DoubleSize.If(err == errRange)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf[:ret], nil
|
||||
}
|
||||
|
||||
// LlistXattr returns a slice containing strings for the name of each xattr set
|
||||
// on the file at the supplied path.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_llistxattr(struct ceph_mount_info *cmount, const char *path, char *list, size_t size);
|
||||
func (mount *MountInfo) LlistXattr(path string) ([]string, error) {
|
||||
if err := mount.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
var (
|
||||
ret C.int
|
||||
err error
|
||||
buf []byte
|
||||
)
|
||||
// range from 1k to 64KiB
|
||||
retry.WithSizes(1024, 1<<16, func(size int) retry.Hint {
|
||||
buf = make([]byte, size)
|
||||
ret = C.ceph_llistxattr(
|
||||
mount.mount,
|
||||
cPath,
|
||||
(*C.char)(unsafe.Pointer(&buf[0])),
|
||||
C.size_t(size))
|
||||
err = getErrorIfNegative(ret)
|
||||
return retry.DoubleSize.If(err == errRange)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
names := cutil.SplitSparseBuffer(buf[:ret])
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// LremoveXattr removes the named xattr from the open file.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_lremovexattr(struct ceph_mount_info *cmount, const char *path, const char *name);
|
||||
func (mount *MountInfo) LremoveXattr(path, name string) error {
|
||||
if err := mount.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if name == "" {
|
||||
return errInvalid
|
||||
}
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
cName := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
|
||||
ret := C.ceph_lremovexattr(
|
||||
mount.mount,
|
||||
cPath,
|
||||
cName)
|
||||
return getError(ret)
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package cephfs
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lcephfs
|
||||
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
||||
#include <stdlib.h>
|
||||
#include <cephfs/libcephfs.h>
|
||||
|
||||
int _go_ceph_chown(struct ceph_mount_info *cmount, const char *path, uid_t uid, gid_t gid) {
|
||||
return ceph_chown(cmount, path, uid, gid);
|
||||
}
|
||||
|
||||
int _go_ceph_lchown(struct ceph_mount_info *cmount, const char *path, uid_t uid, gid_t gid) {
|
||||
return ceph_lchown(cmount, path, uid, gid);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Chmod changes the mode bits (permissions) of a file/directory.
|
||||
func (mount *MountInfo) Chmod(path string, mode uint32) error {
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
ret := C.ceph_chmod(mount.mount, cPath, C.mode_t(mode))
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// Chown changes the ownership of a file/directory.
|
||||
func (mount *MountInfo) Chown(path string, user uint32, group uint32) error {
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
ret := C._go_ceph_chown(mount.mount, cPath, C.uid_t(user), C.gid_t(group))
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// Lchown changes the ownership of a file/directory/etc without following symbolic links
|
||||
func (mount *MountInfo) Lchown(path string, user uint32, group uint32) error {
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
ret := C._go_ceph_lchown(mount.mount, cPath, C.uid_t(user), C.gid_t(group))
|
||||
return getError(ret)
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package cephfs
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lcephfs
|
||||
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
||||
#define _GNU_SOURCE
|
||||
#include <stdlib.h>
|
||||
#include <cephfs/libcephfs.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// SelectFilesystem selects a file system to be mounted. If the ceph cluster
|
||||
// supports more than one cephfs this optional function selects which one to
|
||||
// use. Can only be called prior to calling Mount. The name of the file system
|
||||
// is not validated by this call - if the supplied file system name is not
|
||||
// valid then only the subsequent mount call will fail.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_select_filesystem(struct ceph_mount_info *cmount, const char *fs_name);
|
||||
func (mount *MountInfo) SelectFilesystem(name string) error {
|
||||
cName := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
|
||||
ret := C.ceph_select_filesystem(mount.mount, cName)
|
||||
return getError(ret)
|
||||
}
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
package cephfs
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lcephfs
|
||||
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
||||
#include <stdlib.h>
|
||||
#include <dirent.h>
|
||||
#include <cephfs/libcephfs.h>
|
||||
|
||||
// Types and constants are copied from libcephfs.h with added "_" as prefix. This
|
||||
// prevents redefinition of the types on libcephfs versions that have them
|
||||
// already.
|
||||
|
||||
typedef struct {
|
||||
struct dirent dir_entry;
|
||||
uint64_t snapid;
|
||||
} _ceph_snapdiff_entry_t;
|
||||
|
||||
typedef struct {
|
||||
struct ceph_mount_info* cmount;
|
||||
struct ceph_dir_result* dir1;
|
||||
struct ceph_dir_result* dir_aux;
|
||||
} _ceph_snapdiff_info;
|
||||
|
||||
// open_snapdiff_fn matches the open_snapdiff function signature.
|
||||
typedef int(*open_snapdiff_fn)(struct ceph_mount_info* cmount,
|
||||
const char* root_path,
|
||||
const char* rel_path,
|
||||
const char* snap1,
|
||||
const char* snap2,
|
||||
_ceph_snapdiff_info* out);
|
||||
|
||||
// open_snapdiff_dlsym take *fn as open_snapdiff_fn and calls the dynamically loaded
|
||||
// open_snapdiff function passed as 1st argument.
|
||||
static inline int open_snapdiff_dlsym(void *fn,
|
||||
struct ceph_mount_info* cmount,
|
||||
const char* root_path,
|
||||
const char* rel_path,
|
||||
const char* snap1,
|
||||
const char* snap2,
|
||||
_ceph_snapdiff_info* out) {
|
||||
// cast function pointer fn to open_snapdiff and call the function
|
||||
return ((open_snapdiff_fn) fn)(cmount, root_path, rel_path, snap1, snap2, out);
|
||||
}
|
||||
|
||||
// readdir_snapdiff_fn matches the readdir_snapdiff function signature.
|
||||
typedef int(*readdir_snapdiff_fn)(_ceph_snapdiff_info* snapdiff,
|
||||
_ceph_snapdiff_entry_t* out);
|
||||
|
||||
// readdir_snapdiff_dlsym take *fn as readdir_snapdiff_fn and calls the dynamically loaded
|
||||
// readdir_snapdiff function passed as 1st argument.
|
||||
static inline int readdir_snapdiff_dlsym(void *fn,
|
||||
_ceph_snapdiff_info* snapdiff,
|
||||
_ceph_snapdiff_entry_t* out) {
|
||||
// cast function pointer fn to readdir_snapdiff and call the function
|
||||
return ((readdir_snapdiff_fn) fn)(snapdiff, out);
|
||||
}
|
||||
|
||||
// close_snapdiff_fn matches the close_snapdiff function signature.
|
||||
typedef int(*close_snapdiff_fn)(_ceph_snapdiff_info* snapdiff);
|
||||
|
||||
// close_snapdiff_dlsym take *fn as close_snapdiff_fn and calls the dynamically loaded
|
||||
// close_snapdiff function passed as 1st argument.
|
||||
static inline int close_snapdiff_dlsym(void *fn,
|
||||
_ceph_snapdiff_info* snapdiff) {
|
||||
// cast function pointer fn to close_snapdiff and call the function
|
||||
return ((close_snapdiff_fn) fn)(snapdiff);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ceph/go-ceph/internal/dlsym"
|
||||
)
|
||||
|
||||
var (
|
||||
cephOpenSnapDiffOnce sync.Once
|
||||
cephReaddirSnapDiffOnce sync.Once
|
||||
cephCloseSnapDiffOnce sync.Once
|
||||
cephOpenSnapDiff unsafe.Pointer
|
||||
cephReaddirSnapDiff unsafe.Pointer
|
||||
cephCloseSnapDiff unsafe.Pointer
|
||||
cephOpenSnapDiffErr error
|
||||
cephReaddirSnapDiffErr error
|
||||
cephCloseSnapDiffErr error
|
||||
)
|
||||
|
||||
// SnapDiffInfo is a handle to a snapshot diff API.
|
||||
type SnapDiffInfo struct {
|
||||
cMount *MountInfo
|
||||
dir1 *Directory
|
||||
dirAux *Directory
|
||||
}
|
||||
|
||||
// SnapDiffEntry is a single entry in the snapshot diff.
|
||||
// It contains a DirEntry and the ID of the snapshot to
|
||||
// which the directory belongs.
|
||||
type SnapDiffEntry struct {
|
||||
DirEntry *DirEntry
|
||||
SnapID uint64
|
||||
}
|
||||
|
||||
// SnapDiffConfig is used to define the parameters of a open_snapdiff call.
|
||||
// Snapshot Delta is generated between the passed snapshots snap1 and snap2.
|
||||
// All fields must be specified before passing to OpenSnapDiff().
|
||||
type SnapDiffConfig struct {
|
||||
// CMount is the ceph mount handle that will be used for snap.diff retrieval.
|
||||
CMount *MountInfo
|
||||
// RootPath represents the root path for snapshots-in-question.
|
||||
RootPath string
|
||||
// RelPath is the subpath under the root to build delta for.
|
||||
RelPath string
|
||||
// Snap1 is the first snapshot name.
|
||||
Snap1 string
|
||||
// Snap2 is the second snapshot name.
|
||||
Snap2 string
|
||||
}
|
||||
|
||||
// OpenSnapDiff opens a snapshot diff stream to get snapshots delta
|
||||
// and returns a SnapDiffInfo struct containing the diff information.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_open_snapdiff(struct ceph_mount_info* cmount,
|
||||
// const char* root_path,
|
||||
// const char* rel_path,
|
||||
// const char* snap1,
|
||||
// const char* snap2,
|
||||
// struct ceph_snapdiff_info* out);
|
||||
func OpenSnapDiff(config SnapDiffConfig) (*SnapDiffInfo, error) {
|
||||
if config.CMount == nil || config.RootPath == "" || config.RelPath == "" ||
|
||||
config.Snap1 == "" || config.Snap2 == "" {
|
||||
return nil, errInvalid
|
||||
}
|
||||
|
||||
cephOpenSnapDiffOnce.Do(func() {
|
||||
cephOpenSnapDiff, cephOpenSnapDiffErr = dlsym.LookupSymbol("ceph_open_snapdiff")
|
||||
})
|
||||
|
||||
if cephOpenSnapDiffErr != nil {
|
||||
return nil, fmt.Errorf("%w: %w", ErrNotImplemented, cephOpenSnapDiffErr)
|
||||
}
|
||||
|
||||
rawCephSnapDiffInfo := &C._ceph_snapdiff_info{}
|
||||
|
||||
ret := C.open_snapdiff_dlsym(
|
||||
cephOpenSnapDiff,
|
||||
config.CMount.mount,
|
||||
C.CString(config.RootPath),
|
||||
C.CString(config.RelPath),
|
||||
C.CString(config.Snap1),
|
||||
C.CString(config.Snap2),
|
||||
rawCephSnapDiffInfo)
|
||||
|
||||
if ret != 0 {
|
||||
return nil, getError(ret)
|
||||
}
|
||||
|
||||
mountInfo := &MountInfo{
|
||||
mount: rawCephSnapDiffInfo.cmount,
|
||||
}
|
||||
cephSnapDiffInfo := &SnapDiffInfo{
|
||||
cMount: mountInfo,
|
||||
dir1: &Directory{
|
||||
mount: mountInfo,
|
||||
dir: rawCephSnapDiffInfo.dir1,
|
||||
},
|
||||
dirAux: &Directory{
|
||||
mount: mountInfo,
|
||||
dir: rawCephSnapDiffInfo.dir_aux,
|
||||
},
|
||||
}
|
||||
|
||||
return cephSnapDiffInfo, nil
|
||||
}
|
||||
|
||||
// validate checks that the SnapDiffInfo struct is valid.
|
||||
func (info *SnapDiffInfo) validate() error {
|
||||
if info.cMount == nil || info.dir1 == nil || info.dirAux == nil {
|
||||
return errInvalid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Readdir returns the next entry in the snapshot diff stream.
|
||||
// If there are no more entries, it returns (nil, nil).
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_readdir_snapdiff(struct ceph_snapdiff_info* snapdiff,
|
||||
// struct ceph_snapdiff_entry_t* out);
|
||||
func (info *SnapDiffInfo) Readdir() (*SnapDiffEntry, error) {
|
||||
if err := info.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cephReaddirSnapDiffOnce.Do(func() {
|
||||
cephReaddirSnapDiff, cephReaddirSnapDiffErr = dlsym.LookupSymbol("ceph_readdir_snapdiff")
|
||||
})
|
||||
if cephReaddirSnapDiffErr != nil {
|
||||
return nil, fmt.Errorf("%w: %w", ErrNotImplemented, cephReaddirSnapDiffErr)
|
||||
}
|
||||
|
||||
rawSnapDiffEntry := &C._ceph_snapdiff_entry_t{}
|
||||
rawSnapDiffInfo := &C._ceph_snapdiff_info{
|
||||
cmount: info.cMount.mount,
|
||||
dir1: info.dir1.dir,
|
||||
dir_aux: info.dirAux.dir,
|
||||
}
|
||||
|
||||
ret := C.readdir_snapdiff_dlsym(
|
||||
cephReaddirSnapDiff,
|
||||
rawSnapDiffInfo,
|
||||
rawSnapDiffEntry)
|
||||
if ret < 0 {
|
||||
return nil, getError(ret)
|
||||
}
|
||||
if ret == 0 {
|
||||
// return 0 indicates there is not more entries to return.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
snapDiffEntry := &SnapDiffEntry{
|
||||
DirEntry: toDirEntry(&rawSnapDiffEntry.dir_entry),
|
||||
SnapID: uint64(rawSnapDiffEntry.snapid),
|
||||
}
|
||||
return snapDiffEntry, nil
|
||||
}
|
||||
|
||||
// Close closes the snapshot diff handle.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_close_snapdiff(struct ceph_snapdiff_info* snapdiff);
|
||||
func (info *SnapDiffInfo) Close() error {
|
||||
if err := info.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cephCloseSnapDiffOnce.Do(func() {
|
||||
cephCloseSnapDiff, cephCloseSnapDiffErr = dlsym.LookupSymbol("ceph_close_snapdiff")
|
||||
})
|
||||
if cephCloseSnapDiffErr != nil {
|
||||
return fmt.Errorf("%w: %w", ErrNotImplemented, cephCloseSnapDiffErr)
|
||||
}
|
||||
|
||||
rawCephSnapDiffInfo := &C._ceph_snapdiff_info{
|
||||
cmount: info.cMount.mount,
|
||||
dir1: info.dir1.dir,
|
||||
dir_aux: info.dirAux.dir,
|
||||
}
|
||||
ret := C.close_snapdiff_dlsym(
|
||||
cephCloseSnapDiff,
|
||||
rawCephSnapDiffInfo)
|
||||
|
||||
if ret != 0 {
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package cephfs
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lcephfs
|
||||
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
||||
#include <stdlib.h>
|
||||
#include <cephfs/libcephfs.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// CephStatVFS instances are returned from the StatFS call. It reports
|
||||
// file-system wide statistics.
|
||||
type CephStatVFS struct {
|
||||
// Bsize reports the file system's block size.
|
||||
Bsize int64
|
||||
// Fragment reports the file system's fragment size.
|
||||
Frsize int64
|
||||
// Blocks reports the number of blocks in the file system.
|
||||
Blocks uint64
|
||||
// Bfree reports the number of free blocks.
|
||||
Bfree uint64
|
||||
// Bavail reports the number of free blocks for unprivileged users.
|
||||
Bavail uint64
|
||||
// Files reports the number of inodes in the file system.
|
||||
Files uint64
|
||||
// Ffree reports the number of free indoes.
|
||||
Ffree uint64
|
||||
// Favail reports the number of free indoes for unprivileged users.
|
||||
Favail uint64
|
||||
// Fsid reports the file system ID number.
|
||||
Fsid int64
|
||||
// Flag reports the file system mount flags.
|
||||
Flag int64
|
||||
// Namemax reports the maximum file name length.
|
||||
Namemax int64
|
||||
}
|
||||
|
||||
// StatFS returns file system wide statistics.
|
||||
// NOTE: Many of the statistics fields reported by ceph are not filled in with
|
||||
// useful values.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int ceph_statfs(struct ceph_mount_info *cmount, const char *path, struct statvfs *stbuf);
|
||||
func (mount *MountInfo) StatFS(path string) (*CephStatVFS, error) {
|
||||
if err := mount.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
var statvfs C.struct_statvfs
|
||||
ret := C.ceph_statfs(mount.mount, cPath, &statvfs)
|
||||
if ret != 0 {
|
||||
return nil, getError(ret)
|
||||
}
|
||||
csfs := &CephStatVFS{
|
||||
Bsize: int64(statvfs.f_bsize),
|
||||
Frsize: int64(statvfs.f_frsize),
|
||||
Blocks: uint64(statvfs.f_blocks),
|
||||
Bfree: uint64(statvfs.f_bfree),
|
||||
Bavail: uint64(statvfs.f_bavail),
|
||||
Files: uint64(statvfs.f_files),
|
||||
Ffree: uint64(statvfs.f_ffree),
|
||||
Favail: uint64(statvfs.f_favail),
|
||||
Fsid: int64(statvfs.f_fsid),
|
||||
Flag: int64(statvfs.f_flag),
|
||||
Namemax: int64(statvfs.f_namemax),
|
||||
}
|
||||
return csfs, nil
|
||||
}
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
package cephfs
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lcephfs
|
||||
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
||||
#include <cephfs/libcephfs.h>
|
||||
#ifndef AT_STATX_DONT_SYNC
|
||||
// for versions earlier than Pacific
|
||||
#define AT_STATX_DONT_SYNC AT_NO_ATTR_SYNC
|
||||
#endif
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
ts "github.com/ceph/go-ceph/internal/timespec"
|
||||
)
|
||||
|
||||
// Timespec is a public type for the internal C 'struct timespec'
|
||||
type Timespec ts.Timespec
|
||||
|
||||
// StatxMask values contain bit-flags indicating what data should be
|
||||
// populated by a statx-type call.
|
||||
type StatxMask uint32
|
||||
|
||||
const (
|
||||
// StatxMode requests the mode value be filled in.
|
||||
StatxMode = StatxMask(C.CEPH_STATX_MODE)
|
||||
// StatxNlink requests the nlink value be filled in.
|
||||
StatxNlink = StatxMask(C.CEPH_STATX_NLINK)
|
||||
// StatxUid requests the uid value be filled in.
|
||||
StatxUid = StatxMask(C.CEPH_STATX_UID)
|
||||
// StatxRdev requests the rdev value be filled in.
|
||||
StatxRdev = StatxMask(C.CEPH_STATX_RDEV)
|
||||
// StatxAtime requests the access-time value be filled in.
|
||||
StatxAtime = StatxMask(C.CEPH_STATX_ATIME)
|
||||
// StatxMtime requests the modified-time value be filled in.
|
||||
StatxMtime = StatxMask(C.CEPH_STATX_MTIME)
|
||||
// StatxIno requests the inode be filled in.
|
||||
StatxIno = StatxMask(C.CEPH_STATX_INO)
|
||||
// StatxSize requests the size value be filled in.
|
||||
StatxSize = StatxMask(C.CEPH_STATX_SIZE)
|
||||
// StatxBlocks requests the blocks value be filled in.
|
||||
StatxBlocks = StatxMask(C.CEPH_STATX_BLOCKS)
|
||||
// StatxBasicStats requests all the fields that are part of a
|
||||
// traditional stat call.
|
||||
StatxBasicStats = StatxMask(C.CEPH_STATX_BASIC_STATS)
|
||||
// StatxBtime requests the birth-time value be filled in.
|
||||
StatxBtime = StatxMask(C.CEPH_STATX_BTIME)
|
||||
// StatxVersion requests the version value be filled in.
|
||||
StatxVersion = StatxMask(C.CEPH_STATX_VERSION)
|
||||
// StatxAllStats requests all known stat values be filled in.
|
||||
StatxAllStats = StatxMask(C.CEPH_STATX_ALL_STATS)
|
||||
)
|
||||
|
||||
// AtFlags represent flags to be passed to calls that control how files
|
||||
// are used or referenced. For example, not following symlinks.
|
||||
type AtFlags uint
|
||||
|
||||
const (
|
||||
// AtStatxDontSync requests that the stat call only fetch locally-cached
|
||||
// values if possible, avoiding round trips to a back-end server.
|
||||
AtStatxDontSync = AtFlags(C.AT_STATX_DONT_SYNC)
|
||||
// AtNoAttrSync requests that the stat call only fetch locally-cached
|
||||
// values if possible, avoiding round trips to a back-end server.
|
||||
//
|
||||
// Deprecated: replaced by AtStatxDontSync
|
||||
AtNoAttrSync = AtStatxDontSync
|
||||
// AtSymlinkNofollow indicates the call should not follow symlinks
|
||||
// but operate on the symlink itself.
|
||||
AtSymlinkNofollow = AtFlags(C.AT_SYMLINK_NOFOLLOW)
|
||||
)
|
||||
|
||||
// NOTE: CephStatx fields are meant to be settable by the callers.
|
||||
// This is the primary reason we use public fields and not accessors
|
||||
// for the CephStatx type.
|
||||
|
||||
// CephStatx instances are returned by extended stat (statx) calls.
|
||||
// Note that CephStatx results are similar to but not identical
|
||||
// to (Linux) system statx results.
|
||||
type CephStatx struct {
|
||||
// Mask is a bitmask indicating what fields have been set.
|
||||
Mask StatxMask
|
||||
// Blksize represents the file system's block size.
|
||||
Blksize uint32
|
||||
// Nlink is the number of links for the file.
|
||||
Nlink uint32
|
||||
// Uid (user id) value for the file.
|
||||
Uid uint32
|
||||
// Gid (group id) value for the file.
|
||||
Gid uint32
|
||||
// Mode is the file's type and mode value.
|
||||
Mode uint16
|
||||
// Inode value for the file.
|
||||
Inode Inode
|
||||
// Size of the file in bytes.
|
||||
Size uint64
|
||||
// Blocks indicates the number of blocks allocated to the file.
|
||||
Blocks uint64
|
||||
// Dev describes the device containing this file system.
|
||||
Dev uint64
|
||||
// Rdev describes the device of this file, if the file is a device.
|
||||
Rdev uint64
|
||||
// Atime is the access time of this file.
|
||||
Atime Timespec
|
||||
// Ctime is the status change time of this file.
|
||||
Ctime Timespec
|
||||
// Mtime is the modification time of this file.
|
||||
Mtime Timespec
|
||||
// Btime is the creation (birth) time of this file.
|
||||
Btime Timespec
|
||||
// Version value for the file.
|
||||
Version uint64
|
||||
}
|
||||
|
||||
func cStructToCephStatx(s C.struct_ceph_statx) *CephStatx {
|
||||
return &CephStatx{
|
||||
Mask: StatxMask(s.stx_mask),
|
||||
Blksize: uint32(s.stx_blksize),
|
||||
Nlink: uint32(s.stx_nlink),
|
||||
Uid: uint32(s.stx_uid),
|
||||
Gid: uint32(s.stx_gid),
|
||||
Mode: uint16(s.stx_mode),
|
||||
Inode: Inode(s.stx_ino),
|
||||
Size: uint64(s.stx_size),
|
||||
Blocks: uint64(s.stx_blocks),
|
||||
Dev: uint64(s.stx_dev),
|
||||
Rdev: uint64(s.stx_rdev),
|
||||
Atime: Timespec(ts.CStructToTimespec(ts.CTimespecPtr(&s.stx_atime))),
|
||||
Ctime: Timespec(ts.CStructToTimespec(ts.CTimespecPtr(&s.stx_ctime))),
|
||||
Mtime: Timespec(ts.CStructToTimespec(ts.CTimespecPtr(&s.stx_mtime))),
|
||||
Btime: Timespec(ts.CStructToTimespec(ts.CTimespecPtr(&s.stx_btime))),
|
||||
Version: uint64(s.stx_version),
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO:
|
||||
- enable later when we can test round -trips
|
||||
- add time fields
|
||||
|
||||
func (c *CephStatx) toCStruct() C.struct_ceph_statx {
|
||||
var s C.struct_ceph_statx
|
||||
s.stx_mask = C.uint32_t(c.Mask)
|
||||
s.stx_blksize = C.uint32_t(c.Blksize)
|
||||
s.stx_nlink = C.uint32_t(c.Nlink)
|
||||
s.stx_uid = C.uint32_t(c.Uid)
|
||||
s.stx_gid = C.uint32_t(c.Gid)
|
||||
s.stx_mode = C.uint16_t(c.Mode)
|
||||
s.stx_ino = C.uint64_t(c.Inode)
|
||||
s.stx_size = C.uint64_t(c.Size)
|
||||
s.stx_blocks = C.uint64_t(c.Blocks)
|
||||
s.stx_dev = C.uint64_t(c.Dev)
|
||||
s.stx_rdev = C.uint64_t(c.Rdev)
|
||||
s.stx_version = C.uint64_t(c.Version)
|
||||
return s
|
||||
}
|
||||
*/
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package cephfs
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lcephfs
|
||||
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
||||
#include <cephfs/libcephfs.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ceph/go-ceph/internal/log"
|
||||
)
|
||||
|
||||
// UserPerm types may be used to get or change the credentials used by the
|
||||
// connection or some operations.
|
||||
type UserPerm struct {
|
||||
userPerm *C.UserPerm
|
||||
|
||||
// cache create-time params
|
||||
managed bool // if set, the userPerm was created by go-ceph
|
||||
uid C.uid_t
|
||||
gid C.gid_t
|
||||
gidList []C.gid_t
|
||||
}
|
||||
|
||||
// NewUserPerm creates a UserPerm pointer and the underlying ceph resources.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// UserPerm *ceph_userperm_new(uid_t uid, gid_t gid, int ngids, gid_t *gidlist);
|
||||
func NewUserPerm(uid, gid int, gidlist []int) *UserPerm {
|
||||
// the C code does not copy the content of the gid list so we keep the
|
||||
// inputs stashed in the go type. For completeness we stash everything.
|
||||
p := &UserPerm{
|
||||
managed: true,
|
||||
uid: C.uid_t(uid),
|
||||
gid: C.gid_t(gid),
|
||||
gidList: make([]C.gid_t, len(gidlist)),
|
||||
}
|
||||
var cgids *C.gid_t
|
||||
if len(p.gidList) > 0 {
|
||||
for i, gid := range gidlist {
|
||||
p.gidList[i] = C.gid_t(gid)
|
||||
}
|
||||
cgids = (*C.gid_t)(unsafe.Pointer(&p.gidList[0]))
|
||||
}
|
||||
p.userPerm = C.ceph_userperm_new(
|
||||
p.uid, p.gid, C.int(len(p.gidList)), cgids)
|
||||
// if the go object is unreachable, we would like to free the c-memory
|
||||
// since this has no other resources than memory associated with it.
|
||||
// This is only valid for UserPerm objects created by new, and thus have
|
||||
// the managed var set.
|
||||
runtime.SetFinalizer(p, destroyUserPerm)
|
||||
return p
|
||||
}
|
||||
|
||||
// Destroy will explicitly free ceph resources associated with the UserPerm.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void ceph_userperm_destroy(UserPerm *perm);
|
||||
func (p *UserPerm) Destroy() {
|
||||
if p.userPerm == nil || !p.managed {
|
||||
return
|
||||
}
|
||||
C.ceph_userperm_destroy(p.userPerm)
|
||||
p.userPerm = nil
|
||||
p.gidList = nil
|
||||
}
|
||||
|
||||
func destroyUserPerm(p *UserPerm) {
|
||||
if p.userPerm != nil && p.managed {
|
||||
log.Warnf("unreachable UserPerm object has not been destroyed. Cleaning up.")
|
||||
}
|
||||
p.Destroy()
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
ccom "github.com/ceph/go-ceph/common/commands"
|
||||
)
|
||||
|
||||
// MgrAdmin is used to administrate ceph's manager (mgr).
|
||||
type MgrAdmin struct {
|
||||
conn ccom.RadosCommander
|
||||
}
|
||||
|
||||
// NewFromConn creates an new management object from a preexisting
|
||||
// rados connection. The existing connection can be rados.Conn or any
|
||||
// type implementing the RadosCommander interface.
|
||||
func NewFromConn(conn ccom.RadosCommander) *MgrAdmin {
|
||||
return &MgrAdmin{conn}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
Package manager from common/admin contains a set of APIs used to interact
|
||||
with and administer the Ceph manager (mgr).
|
||||
*/
|
||||
package manager
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"github.com/ceph/go-ceph/internal/commands"
|
||||
)
|
||||
|
||||
// EnableModule will enable the specified manager module.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph mgr module enable <module> [--force]
|
||||
func (fsa *MgrAdmin) EnableModule(module string, force bool) error {
|
||||
m := map[string]string{
|
||||
"prefix": "mgr module enable",
|
||||
"module": module,
|
||||
"format": "json",
|
||||
}
|
||||
if force {
|
||||
m["force"] = "--force"
|
||||
}
|
||||
// Why is this _only_ part of the mon command json? You'd think a mgr
|
||||
// command would be available as a MgrCommand but I couldn't figure it out.
|
||||
return commands.MarshalMonCommand(fsa.conn, m).NoData().End()
|
||||
}
|
||||
|
||||
// DisableModule will disable the specified manager module.
|
||||
//
|
||||
// Similar To:
|
||||
//
|
||||
// ceph mgr module disable <module>
|
||||
func (fsa *MgrAdmin) DisableModule(module string) error {
|
||||
m := map[string]string{
|
||||
"prefix": "mgr module disable",
|
||||
"module": module,
|
||||
"format": "json",
|
||||
}
|
||||
return commands.MarshalMonCommand(fsa.conn, m).NoData().End()
|
||||
}
|
||||
|
||||
// DisabledModule describes a disabled Ceph mgr module.
|
||||
// The Ceph JSON structure contains a complex module_options
|
||||
// substructure that go-ceph does not currently implement.
|
||||
type DisabledModule struct {
|
||||
Name string `json:"name"`
|
||||
CanRun bool `json:"can_run"`
|
||||
ErrorString string `json:"error_string"`
|
||||
}
|
||||
|
||||
// ModuleInfo contains fields that report the status of modules within the
|
||||
// ceph mgr.
|
||||
type ModuleInfo struct {
|
||||
// EnabledModules lists the names of the enabled modules.
|
||||
EnabledModules []string `json:"enabled_modules"`
|
||||
// AlwaysOnModules lists the names of the always-on modules.
|
||||
AlwaysOnModules []string `json:"always_on_modules"`
|
||||
// DisabledModules lists structures describing modules that are
|
||||
// not currently enabled.
|
||||
DisabledModules []DisabledModule `json:"disabled_modules"`
|
||||
}
|
||||
|
||||
func parseModuleInfo(res commands.Response) (*ModuleInfo, error) {
|
||||
m := &ModuleInfo{}
|
||||
if err := res.NoStatus().Unmarshal(m).End(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// ListModules returns a module info struct reporting the lists of
|
||||
// enabled, disabled, and always-on modules in the Ceph mgr.
|
||||
func (fsa *MgrAdmin) ListModules() (*ModuleInfo, error) {
|
||||
m := map[string]string{
|
||||
"prefix": "mgr module ls",
|
||||
"format": "json",
|
||||
}
|
||||
return parseModuleInfo(commands.MarshalMonCommand(fsa.conn, m))
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Package commands provides types and utility functions that are used for
|
||||
interfacing with the JSON based command infrastructure in Ceph.
|
||||
|
||||
The *rados.Conn type implements many of the interfaces found in this package.
|
||||
*/
|
||||
package commands
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package commands
|
||||
|
||||
// MgrCommander in an interface for the API needed to execute JSON formatted
|
||||
// commands on the ceph mgr.
|
||||
type MgrCommander interface {
|
||||
MgrCommand(buf [][]byte) ([]byte, string, error)
|
||||
}
|
||||
|
||||
// MgrBufferCommander is an interface for the API needed to execute JSON
|
||||
// formatted commands with an input buffer on the Ceph mgr.
|
||||
type MgrBufferCommander interface {
|
||||
MgrCommandWithInputBuffer([][]byte, []byte) ([]byte, string, error)
|
||||
}
|
||||
|
||||
// MonCommander is an interface for the API needed to execute JSON formatted
|
||||
// commands on the ceph mon(s).
|
||||
type MonCommander interface {
|
||||
MonCommand(buf []byte) ([]byte, string, error)
|
||||
}
|
||||
|
||||
// MonBufferCommander is an interface for the API needed to execute JSON
|
||||
// formatted commands with an input buffer on the Ceph mon(s).
|
||||
type MonBufferCommander interface {
|
||||
MonCommandWithInputBuffer([]byte, []byte) ([]byte, string, error)
|
||||
}
|
||||
|
||||
// RadosCommander provides an interface for APIs needed to execute JSON
|
||||
// formatted commands on the Ceph cluster.
|
||||
type RadosCommander interface {
|
||||
MgrCommander
|
||||
MonCommander
|
||||
}
|
||||
|
||||
// RadosBufferCommander provides an interface for APIs that need to execute
|
||||
// JSON formatted commands with an input buffer on the Ceph cluster.
|
||||
type RadosBufferCommander interface {
|
||||
RadosCommander
|
||||
MgrBufferCommander
|
||||
MonBufferCommander
|
||||
}
|
||||
+91
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
+35
@@ -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
@@ -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
@@ -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() {}
|
||||
+38
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <rados/librados.h>
|
||||
//
|
||||
import "C"
|
||||
|
||||
// AllocHintFlags control the behavior of read and write operations.
|
||||
type AllocHintFlags uint32
|
||||
|
||||
const (
|
||||
// AllocHintNoHint indicates no predefined behavior
|
||||
AllocHintNoHint = AllocHintFlags(0)
|
||||
// AllocHintSequentialWrite TODO
|
||||
AllocHintSequentialWrite = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_SEQUENTIAL_WRITE)
|
||||
// AllocHintRandomWrite TODO
|
||||
AllocHintRandomWrite = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_RANDOM_WRITE)
|
||||
// AllocHintSequentialRead TODO
|
||||
AllocHintSequentialRead = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_SEQUENTIAL_READ)
|
||||
// AllocHintRandomRead TODO
|
||||
AllocHintRandomRead = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_RANDOM_READ)
|
||||
// AllocHintAppendOnly TODO
|
||||
AllocHintAppendOnly = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_APPEND_ONLY)
|
||||
// AllocHintImmutable TODO
|
||||
AllocHintImmutable = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_IMMUTABLE)
|
||||
// AllocHintShortlived TODO
|
||||
AllocHintShortlived = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_SHORTLIVED)
|
||||
// AllocHintLonglived TODO
|
||||
AllocHintLonglived = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_LONGLIVED)
|
||||
// AllocHintCompressible TODO
|
||||
AllocHintCompressible = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_COMPRESSIBLE)
|
||||
// AllocHintIncompressible TODO
|
||||
AllocHintIncompressible = AllocHintFlags(C.LIBRADOS_ALLOC_HINT_FLAG_INCOMPRESSIBLE)
|
||||
)
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <stdlib.h>
|
||||
// #include <rados/librados.h>
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/ceph/go-ceph/internal/cutil"
|
||||
)
|
||||
|
||||
func radosBufferFree(p unsafe.Pointer) {
|
||||
C.rados_buffer_free((*C.char)(p))
|
||||
}
|
||||
|
||||
// MonCommand sends a command to one of the monitors
|
||||
func (c *Conn) MonCommand(args []byte) ([]byte, string, error) {
|
||||
return c.MonCommandWithInputBuffer(args, nil)
|
||||
}
|
||||
|
||||
// MonCommandWithInputBuffer sends a command to one of the monitors, with an input buffer
|
||||
func (c *Conn) MonCommandWithInputBuffer(args, inputBuffer []byte) ([]byte, string, error) {
|
||||
ci := cutil.NewCommandInput([][]byte{args}, inputBuffer)
|
||||
defer ci.Free()
|
||||
co := cutil.NewCommandOutput().SetFreeFunc(radosBufferFree)
|
||||
defer co.Free()
|
||||
|
||||
ret := C.rados_mon_command(
|
||||
c.cluster,
|
||||
(**C.char)(ci.Cmd()),
|
||||
C.size_t(ci.CmdLen()),
|
||||
(*C.char)(ci.InBuf()),
|
||||
C.size_t(ci.InBufLen()),
|
||||
(**C.char)(co.OutBuf()),
|
||||
(*C.size_t)(co.OutBufLen()),
|
||||
(**C.char)(co.Outs()),
|
||||
(*C.size_t)(co.OutsLen()))
|
||||
buf, status := co.GoValues()
|
||||
return buf, status, getError(ret)
|
||||
}
|
||||
|
||||
// PGCommand sends a command to one of the PGs
|
||||
//
|
||||
// The args parameter takes a slice of byte slices but typically a single
|
||||
// slice element is sufficient. The use of two slices exists to best match
|
||||
// the structure of the underlying C call which is often a legacy interface
|
||||
// in Ceph.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_pg_command(rados_t cluster, const char *pgstr,
|
||||
// const char **cmd, size_t cmdlen,
|
||||
// const char *inbuf, size_t inbuflen,
|
||||
// char **outbuf, size_t *outbuflen,
|
||||
// char **outs, size_t *outslen);
|
||||
func (c *Conn) PGCommand(pgid []byte, args [][]byte) ([]byte, string, error) {
|
||||
return c.PGCommandWithInputBuffer(pgid, args, nil)
|
||||
}
|
||||
|
||||
// PGCommandWithInputBuffer sends a command to one of the PGs, with an input buffer
|
||||
//
|
||||
// The args parameter takes a slice of byte slices but typically a single
|
||||
// slice element is sufficient. The use of two slices exists to best match
|
||||
// the structure of the underlying C call which is often a legacy interface
|
||||
// in Ceph.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_pg_command(rados_t cluster, const char *pgstr,
|
||||
// const char **cmd, size_t cmdlen,
|
||||
// const char *inbuf, size_t inbuflen,
|
||||
// char **outbuf, size_t *outbuflen,
|
||||
// char **outs, size_t *outslen);
|
||||
func (c *Conn) PGCommandWithInputBuffer(pgid []byte, args [][]byte, inputBuffer []byte) ([]byte, string, error) {
|
||||
name := C.CString(string(pgid))
|
||||
defer C.free(unsafe.Pointer(name))
|
||||
ci := cutil.NewCommandInput(args, inputBuffer)
|
||||
defer ci.Free()
|
||||
co := cutil.NewCommandOutput().SetFreeFunc(radosBufferFree)
|
||||
defer co.Free()
|
||||
|
||||
ret := C.rados_pg_command(
|
||||
c.cluster,
|
||||
name,
|
||||
(**C.char)(ci.Cmd()),
|
||||
C.size_t(ci.CmdLen()),
|
||||
(*C.char)(ci.InBuf()),
|
||||
C.size_t(ci.InBufLen()),
|
||||
(**C.char)(co.OutBuf()),
|
||||
(*C.size_t)(co.OutBufLen()),
|
||||
(**C.char)(co.Outs()),
|
||||
(*C.size_t)(co.OutsLen()))
|
||||
buf, status := co.GoValues()
|
||||
return buf, status, getError(ret)
|
||||
}
|
||||
|
||||
// MgrCommand sends a command to a ceph-mgr.
|
||||
//
|
||||
// The args parameter takes a slice of byte slices but typically a single
|
||||
// slice element is sufficient. The use of two slices exists to best match
|
||||
// the structure of the underlying C call which is often a legacy interface
|
||||
// in Ceph.
|
||||
func (c *Conn) MgrCommand(args [][]byte) ([]byte, string, error) {
|
||||
return c.MgrCommandWithInputBuffer(args, nil)
|
||||
}
|
||||
|
||||
// MgrCommandWithInputBuffer sends a command, with an input buffer, to a ceph-mgr.
|
||||
//
|
||||
// The args parameter takes a slice of byte slices but typically a single
|
||||
// slice element is sufficient. The use of two slices exists to best match
|
||||
// the structure of the underlying C call which is often a legacy interface
|
||||
// in Ceph.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_mgr_command(rados_t cluster, const char **cmd,
|
||||
// size_t cmdlen, const char *inbuf,
|
||||
// size_t inbuflen, char **outbuf,
|
||||
// size_t *outbuflen, char **outs,
|
||||
// size_t *outslen);
|
||||
func (c *Conn) MgrCommandWithInputBuffer(args [][]byte, inputBuffer []byte) ([]byte, string, error) {
|
||||
ci := cutil.NewCommandInput(args, inputBuffer)
|
||||
defer ci.Free()
|
||||
co := cutil.NewCommandOutput().SetFreeFunc(radosBufferFree)
|
||||
defer co.Free()
|
||||
|
||||
ret := C.rados_mgr_command(
|
||||
c.cluster,
|
||||
(**C.char)(ci.Cmd()),
|
||||
C.size_t(ci.CmdLen()),
|
||||
(*C.char)(ci.InBuf()),
|
||||
C.size_t(ci.InBufLen()),
|
||||
(**C.char)(co.OutBuf()),
|
||||
(*C.size_t)(co.OutBufLen()),
|
||||
(**C.char)(co.Outs()),
|
||||
(*C.size_t)(co.OutsLen()))
|
||||
buf, status := co.GoValues()
|
||||
return buf, status, getError(ret)
|
||||
}
|
||||
|
||||
// OsdCommand sends a command to the specified ceph OSD.
|
||||
//
|
||||
// The args parameter takes a slice of byte slices but typically a single
|
||||
// slice element is sufficient. The use of two slices exists to best match
|
||||
// the structure of the underlying C call which is often a legacy interface
|
||||
// in Ceph.
|
||||
func (c *Conn) OsdCommand(osd int, args [][]byte) ([]byte, string, error) {
|
||||
return c.OsdCommandWithInputBuffer(osd, args, nil)
|
||||
}
|
||||
|
||||
// OsdCommandWithInputBuffer sends a command, with an input buffer, to the
|
||||
// specified ceph OSD.
|
||||
//
|
||||
// The args parameter takes a slice of byte slices but typically a single
|
||||
// slice element is sufficient. The use of two slices exists to best match
|
||||
// the structure of the underlying C call which is often a legacy interface
|
||||
// in Ceph.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_osd_command(rados_t cluster, int osdid,
|
||||
// const char **cmd, size_t cmdlen,
|
||||
// const char *inbuf, size_t inbuflen,
|
||||
// char **outbuf, size_t *outbuflen,
|
||||
// char **outs, size_t *outslen);
|
||||
func (c *Conn) OsdCommandWithInputBuffer(
|
||||
osd int, args [][]byte, inputBuffer []byte) ([]byte, string, error) {
|
||||
|
||||
ci := cutil.NewCommandInput(args, inputBuffer)
|
||||
defer ci.Free()
|
||||
co := cutil.NewCommandOutput().SetFreeFunc(radosBufferFree)
|
||||
defer co.Free()
|
||||
|
||||
ret := C.rados_osd_command(
|
||||
c.cluster,
|
||||
C.int(osd),
|
||||
(**C.char)(ci.Cmd()),
|
||||
C.size_t(ci.CmdLen()),
|
||||
(*C.char)(ci.InBuf()),
|
||||
C.size_t(ci.InBufLen()),
|
||||
(**C.char)(co.OutBuf()),
|
||||
(*C.size_t)(co.OutBufLen()),
|
||||
(**C.char)(co.Outs()),
|
||||
(*C.size_t)(co.OutsLen()))
|
||||
buf, status := co.GoValues()
|
||||
return buf, status, getError(ret)
|
||||
}
|
||||
|
||||
// MonCommandTarget sends a command to a specified monitor.
|
||||
//
|
||||
// The args parameter takes a slice of byte slices but typically a single
|
||||
// slice element is sufficient. The use of two slices exists to best match
|
||||
// the structure of the underlying C call which is often a legacy interface
|
||||
// in Ceph.
|
||||
func (c *Conn) MonCommandTarget(name string, args [][]byte) ([]byte, string, error) {
|
||||
return c.MonCommandTargetWithInputBuffer(name, args, nil)
|
||||
}
|
||||
|
||||
// MonCommandTargetWithInputBuffer sends a command, with an input buffer, to a specified monitor.
|
||||
//
|
||||
// The args parameter takes a slice of byte slices but typically a single
|
||||
// slice element is sufficient. The use of two slices exists to best match
|
||||
// the structure of the underlying C call which is often a legacy interface
|
||||
// in Ceph.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_mon_command_target(rados_t cluster, const char *name,
|
||||
// const char **cmd, size_t cmdlen,
|
||||
// const char *inbuf, size_t inbuflen,
|
||||
// char **outbuf, size_t *outbuflen,
|
||||
// char **outs, size_t *outslen);
|
||||
func (c *Conn) MonCommandTargetWithInputBuffer(
|
||||
name string, args [][]byte, inputBuffer []byte) ([]byte, string, error) {
|
||||
|
||||
cName := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
ci := cutil.NewCommandInput(args, inputBuffer)
|
||||
defer ci.Free()
|
||||
co := cutil.NewCommandOutput().SetFreeFunc(radosBufferFree)
|
||||
defer co.Free()
|
||||
|
||||
ret := C.rados_mon_command_target(
|
||||
c.cluster,
|
||||
cName,
|
||||
(**C.char)(ci.Cmd()),
|
||||
C.size_t(ci.CmdLen()),
|
||||
(*C.char)(ci.InBuf()),
|
||||
C.size_t(ci.InBufLen()),
|
||||
(**C.char)(co.OutBuf()),
|
||||
(*C.size_t)(co.OutBufLen()),
|
||||
(**C.char)(co.Outs()),
|
||||
(*C.size_t)(co.OutsLen()))
|
||||
buf, status := co.GoValues()
|
||||
return buf, status, getError(ret)
|
||||
}
|
||||
+313
@@ -0,0 +1,313 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <stdlib.h>
|
||||
// #include <rados/librados.h>
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/ceph/go-ceph/internal/cutil"
|
||||
"github.com/ceph/go-ceph/internal/retry"
|
||||
)
|
||||
|
||||
var argvPlaceholder = "placeholder"
|
||||
|
||||
//revive:disable:var-naming old-yet-exported public api
|
||||
|
||||
// ClusterStat represents Ceph cluster statistics.
|
||||
type ClusterStat struct {
|
||||
Kb uint64
|
||||
Kb_used uint64
|
||||
Kb_avail uint64
|
||||
Num_objects uint64
|
||||
}
|
||||
|
||||
//revive:enable:var-naming
|
||||
|
||||
// Conn is a connection handle to a Ceph cluster.
|
||||
type Conn struct {
|
||||
cluster C.rados_t
|
||||
connected bool
|
||||
}
|
||||
|
||||
// ClusterRef represents a fundamental RADOS cluster connection.
|
||||
type ClusterRef C.rados_t
|
||||
|
||||
// Cluster returns the underlying RADOS cluster reference for this Conn.
|
||||
func (c *Conn) Cluster() ClusterRef {
|
||||
return ClusterRef(c.cluster)
|
||||
}
|
||||
|
||||
// PingMonitor sends a ping to a monitor and returns the reply.
|
||||
func (c *Conn) PingMonitor(id string) (string, error) {
|
||||
cid := C.CString(id)
|
||||
defer C.free(unsafe.Pointer(cid))
|
||||
|
||||
var strlen C.size_t
|
||||
var strout *C.char
|
||||
|
||||
ret := C.rados_ping_monitor(c.cluster, cid, &strout, &strlen)
|
||||
defer C.rados_buffer_free(strout)
|
||||
|
||||
if ret == 0 {
|
||||
reply := C.GoStringN(strout, (C.int)(strlen))
|
||||
return reply, nil
|
||||
}
|
||||
return "", getError(ret)
|
||||
}
|
||||
|
||||
// Connect establishes a connection to a RADOS cluster. It returns an error,
|
||||
// if any.
|
||||
func (c *Conn) Connect() error {
|
||||
ret := C.rados_connect(c.cluster)
|
||||
if ret != 0 {
|
||||
return getError(ret)
|
||||
}
|
||||
c.connected = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Shutdown disconnects from the cluster.
|
||||
func (c *Conn) Shutdown() {
|
||||
if err := c.ensureConnected(); err != nil {
|
||||
return
|
||||
}
|
||||
freeConn(c)
|
||||
}
|
||||
|
||||
// ReadConfigFile configures the connection using a Ceph configuration file.
|
||||
func (c *Conn) ReadConfigFile(path string) error {
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
ret := C.rados_conf_read_file(c.cluster, cPath)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// ReadDefaultConfigFile configures the connection using a Ceph configuration
|
||||
// file located at default locations.
|
||||
func (c *Conn) ReadDefaultConfigFile() error {
|
||||
ret := C.rados_conf_read_file(c.cluster, nil)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// OpenIOContext creates and returns a new IOContext for the given pool.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_ioctx_create(rados_t cluster, const char *pool_name,
|
||||
// rados_ioctx_t *ioctx);
|
||||
func (c *Conn) OpenIOContext(pool string) (*IOContext, error) {
|
||||
cPool := C.CString(pool)
|
||||
defer C.free(unsafe.Pointer(cPool))
|
||||
ioctx := &IOContext{conn: c}
|
||||
ret := C.rados_ioctx_create(c.cluster, cPool, &ioctx.ioctx)
|
||||
if ret == 0 {
|
||||
return ioctx, nil
|
||||
}
|
||||
return nil, getError(ret)
|
||||
}
|
||||
|
||||
// ListPools returns the names of all existing pools.
|
||||
func (c *Conn) ListPools() (names []string, err error) {
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
ret := C.rados_pool_list(c.cluster,
|
||||
(*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf)))
|
||||
if ret < 0 {
|
||||
return nil, getError(ret)
|
||||
}
|
||||
|
||||
if int(ret) > len(buf) {
|
||||
buf = make([]byte, ret)
|
||||
continue
|
||||
}
|
||||
|
||||
names = cutil.SplitSparseBuffer(buf[:ret])
|
||||
return names, nil
|
||||
}
|
||||
}
|
||||
|
||||
// SetConfigOption sets the value of the configuration option identified by
|
||||
// the given name.
|
||||
func (c *Conn) SetConfigOption(option, value string) error {
|
||||
cOpt, cVal := C.CString(option), C.CString(value)
|
||||
defer C.free(unsafe.Pointer(cOpt))
|
||||
defer C.free(unsafe.Pointer(cVal))
|
||||
ret := C.rados_conf_set(c.cluster, cOpt, cVal)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// GetConfigOption returns the value of the Ceph configuration option
|
||||
// identified by the given name.
|
||||
func (c *Conn) GetConfigOption(name string) (value string, err error) {
|
||||
cOption := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cOption))
|
||||
|
||||
var buf []byte
|
||||
// range from 4k to 256KiB
|
||||
retry.WithSizes(4096, 1<<18, func(size int) retry.Hint {
|
||||
buf = make([]byte, size)
|
||||
ret := C.rados_conf_get(
|
||||
c.cluster,
|
||||
cOption,
|
||||
(*C.char)(unsafe.Pointer(&buf[0])),
|
||||
C.size_t(len(buf)))
|
||||
err = getError(ret)
|
||||
return retry.DoubleSize.If(err == errNameTooLong)
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
value = C.GoString((*C.char)(unsafe.Pointer(&buf[0])))
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// WaitForLatestOSDMap blocks the caller until the latest OSD map has been
|
||||
// retrieved.
|
||||
func (c *Conn) WaitForLatestOSDMap() error {
|
||||
ret := C.rados_wait_for_latest_osdmap(c.cluster)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
func (c *Conn) ensureConnected() error {
|
||||
if c.connected {
|
||||
return nil
|
||||
}
|
||||
return ErrNotConnected
|
||||
}
|
||||
|
||||
// GetClusterStats returns statistics about the cluster associated with the
|
||||
// connection.
|
||||
func (c *Conn) GetClusterStats() (stat ClusterStat, err error) {
|
||||
if err := c.ensureConnected(); err != nil {
|
||||
return ClusterStat{}, err
|
||||
}
|
||||
cStat := C.struct_rados_cluster_stat_t{}
|
||||
ret := C.rados_cluster_stat(c.cluster, &cStat)
|
||||
if ret < 0 {
|
||||
return ClusterStat{}, getError(ret)
|
||||
}
|
||||
return ClusterStat{
|
||||
Kb: uint64(cStat.kb),
|
||||
Kb_used: uint64(cStat.kb_used),
|
||||
Kb_avail: uint64(cStat.kb_avail),
|
||||
Num_objects: uint64(cStat.num_objects),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParseConfigArgv configures the connection using a unix style command line
|
||||
// argument vector.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_conf_parse_argv(rados_t cluster, int argc,
|
||||
// const char **argv);
|
||||
func (c *Conn) ParseConfigArgv(argv []string) error {
|
||||
if c.cluster == nil {
|
||||
return ErrNotConnected
|
||||
}
|
||||
if len(argv) == 0 {
|
||||
return ErrEmptyArgument
|
||||
}
|
||||
cargv := make([]*C.char, len(argv))
|
||||
for i := range argv {
|
||||
cargv[i] = C.CString(argv[i])
|
||||
defer C.free(unsafe.Pointer(cargv[i]))
|
||||
}
|
||||
|
||||
ret := C.rados_conf_parse_argv(c.cluster, C.int(len(cargv)), &cargv[0])
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// ParseCmdLineArgs configures the connection from command line arguments.
|
||||
//
|
||||
// This function passes a placeholder value to Ceph as argv[0], see
|
||||
// ParseConfigArgv for a version of this function that allows the caller to
|
||||
// specify argv[0].
|
||||
func (c *Conn) ParseCmdLineArgs(args []string) error {
|
||||
argv := make([]string, len(args)+1)
|
||||
// Ceph expects a proper argv array as the actual contents with the
|
||||
// first element containing the executable name
|
||||
argv[0] = argvPlaceholder
|
||||
for i := range args {
|
||||
argv[i+1] = args[i]
|
||||
}
|
||||
return c.ParseConfigArgv(argv)
|
||||
}
|
||||
|
||||
// ParseDefaultConfigEnv configures the connection from the default Ceph
|
||||
// environment variable CEPH_ARGS.
|
||||
func (c *Conn) ParseDefaultConfigEnv() error {
|
||||
ret := C.rados_conf_parse_env(c.cluster, nil)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// GetFSID returns the fsid of the cluster as a hexadecimal string. The fsid
|
||||
// is a unique identifier of an entire Ceph cluster.
|
||||
func (c *Conn) GetFSID() (fsid string, err error) {
|
||||
buf := make([]byte, 37)
|
||||
ret := C.rados_cluster_fsid(c.cluster,
|
||||
(*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf)))
|
||||
// FIXME: the success case isn't documented correctly in librados.h
|
||||
if ret == 36 {
|
||||
fsid = C.GoString((*C.char)(unsafe.Pointer(&buf[0])))
|
||||
return fsid, nil
|
||||
}
|
||||
return "", getError(ret)
|
||||
}
|
||||
|
||||
// GetInstanceID returns a globally unique identifier for the cluster
|
||||
// connection instance.
|
||||
func (c *Conn) GetInstanceID() uint64 {
|
||||
// FIXME: are there any error cases for this?
|
||||
return uint64(C.rados_get_instance_id(c.cluster))
|
||||
}
|
||||
|
||||
// MakePool creates a new pool with default settings.
|
||||
func (c *Conn) MakePool(name string) error {
|
||||
cName := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
ret := C.rados_pool_create(c.cluster, cName)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// DeletePool deletes a pool and all the data inside the pool.
|
||||
func (c *Conn) DeletePool(name string) error {
|
||||
if err := c.ensureConnected(); err != nil {
|
||||
return err
|
||||
}
|
||||
cName := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
ret := C.rados_pool_delete(c.cluster, cName)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// GetPoolByName returns the ID of the pool with a given name.
|
||||
func (c *Conn) GetPoolByName(name string) (int64, error) {
|
||||
if err := c.ensureConnected(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
cName := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
ret := C.rados_pool_lookup(c.cluster, cName)
|
||||
if ret < 0 {
|
||||
return 0, getError(C.int(ret))
|
||||
}
|
||||
return int64(ret), nil
|
||||
}
|
||||
|
||||
// GetPoolByID returns the name of a pool by a given ID.
|
||||
func (c *Conn) GetPoolByID(id int64) (string, error) {
|
||||
buf := make([]byte, 4096)
|
||||
if err := c.ensureConnected(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
cid := C.int64_t(id)
|
||||
ret := C.rados_pool_reverse_lookup(c.cluster, cid, (*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf)))
|
||||
if ret < 0 {
|
||||
return "", getError(ret)
|
||||
}
|
||||
return C.GoString((*C.char)(unsafe.Pointer(&buf[0]))), nil
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
Package rados contains a set of wrappers around Ceph's librados API.
|
||||
*/
|
||||
package rados
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package rados
|
||||
|
||||
/*
|
||||
#include <errno.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ceph/go-ceph/internal/errutil"
|
||||
)
|
||||
|
||||
// Public go errors:
|
||||
|
||||
var (
|
||||
// ErrNotConnected is returned when functions are called
|
||||
// without a RADOS connection.
|
||||
ErrNotConnected = getError(-C.ENOTCONN)
|
||||
// ErrEmptyArgument may be returned if a function argument is passed
|
||||
// a zero-length slice or map.
|
||||
ErrEmptyArgument = errors.New("Argument must contain at least one item")
|
||||
// ErrInvalidIOContext may be returned if an api call requires an IOContext
|
||||
// but IOContext is not ready for use.
|
||||
ErrInvalidIOContext = errors.New("IOContext is not ready for use")
|
||||
// ErrOperationIncomplete is returned from write op or read op steps for
|
||||
// which the operation has not been performed yet.
|
||||
ErrOperationIncomplete = errors.New("Operation has not been performed yet")
|
||||
|
||||
// ErrNotFound indicates a missing resource.
|
||||
ErrNotFound = getError(-C.ENOENT)
|
||||
// ErrPermissionDenied indicates a permissions issue.
|
||||
ErrPermissionDenied = getError(-C.EPERM)
|
||||
// ErrObjectExists indicates that an exclusive object creation failed.
|
||||
ErrObjectExists = getError(-C.EEXIST)
|
||||
|
||||
// RadosErrorNotFound indicates a missing resource.
|
||||
//
|
||||
// Deprecated: use ErrNotFound instead
|
||||
RadosErrorNotFound = ErrNotFound
|
||||
// RadosErrorPermissionDenied indicates a permissions issue.
|
||||
//
|
||||
// Deprecated: use ErrPermissionDenied instead
|
||||
RadosErrorPermissionDenied = ErrPermissionDenied
|
||||
|
||||
// Private errors:
|
||||
|
||||
errNameTooLong = getError(-C.ENAMETOOLONG)
|
||||
errRange = getError(-C.ERANGE)
|
||||
)
|
||||
|
||||
func getError(errno C.int) error {
|
||||
return errutil.GetError("rados", int(errno))
|
||||
}
|
||||
|
||||
// getErrorIfNegative converts a ceph return code to error if negative.
|
||||
// This is useful for functions that return a usable positive value on
|
||||
// success but a negative error number on error.
|
||||
func getErrorIfNegative(ret C.int) error {
|
||||
if ret >= 0 {
|
||||
return nil
|
||||
}
|
||||
return getError(ret)
|
||||
}
|
||||
+725
@@ -0,0 +1,725 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <errno.h>
|
||||
// #include <stdlib.h>
|
||||
// #include <rados/librados.h>
|
||||
//
|
||||
// char* nextChunk(char **idx) {
|
||||
// char *copy;
|
||||
// copy = strdup(*idx);
|
||||
// *idx += strlen(*idx) + 1;
|
||||
// return copy;
|
||||
// }
|
||||
//
|
||||
// #if __APPLE__
|
||||
// #define ceph_time_t __darwin_time_t
|
||||
// #define ceph_suseconds_t __darwin_suseconds_t
|
||||
// #elif __GLIBC__
|
||||
// #define ceph_time_t __time_t
|
||||
// #define ceph_suseconds_t __suseconds_t
|
||||
// #else
|
||||
// #define ceph_time_t time_t
|
||||
// #define ceph_suseconds_t suseconds_t
|
||||
// #endif
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ceph/go-ceph/internal/retry"
|
||||
)
|
||||
|
||||
// CreateOption is passed to IOContext.Create() and should be one of
|
||||
// CreateExclusive or CreateIdempotent.
|
||||
type CreateOption int
|
||||
|
||||
const (
|
||||
// CreateExclusive if used with IOContext.Create() and the object
|
||||
// already exists, the function will return an error.
|
||||
CreateExclusive = C.LIBRADOS_CREATE_EXCLUSIVE
|
||||
// CreateIdempotent if used with IOContext.Create() and the object
|
||||
// already exists, the function will not return an error.
|
||||
CreateIdempotent = C.LIBRADOS_CREATE_IDEMPOTENT
|
||||
|
||||
defaultListObjectsResultSize = 1000
|
||||
// listEndSentinel is the value returned by rados_list_object_list_is_end
|
||||
// when a cursor has reached the end of a pool
|
||||
listEndSentinel = 1
|
||||
)
|
||||
|
||||
//revive:disable:var-naming old-yet-exported public api
|
||||
|
||||
// PoolStat represents Ceph pool statistics.
|
||||
type PoolStat struct {
|
||||
// space used in bytes
|
||||
Num_bytes uint64
|
||||
// space used in KB
|
||||
Num_kb uint64
|
||||
// number of objects in the pool
|
||||
Num_objects uint64
|
||||
// number of clones of objects
|
||||
Num_object_clones uint64
|
||||
// num_objects * num_replicas
|
||||
Num_object_copies uint64
|
||||
Num_objects_missing_on_primary uint64
|
||||
// number of objects found on no OSDs
|
||||
Num_objects_unfound uint64
|
||||
// number of objects replicated fewer times than they should be
|
||||
// (but found on at least one OSD)
|
||||
Num_objects_degraded uint64
|
||||
Num_rd uint64
|
||||
Num_rd_kb uint64
|
||||
Num_wr uint64
|
||||
Num_wr_kb uint64
|
||||
}
|
||||
|
||||
//revive:enable:var-naming
|
||||
|
||||
// ObjectStat represents an object stat information
|
||||
type ObjectStat struct {
|
||||
// current length in bytes
|
||||
Size uint64
|
||||
// last modification time
|
||||
ModTime time.Time
|
||||
}
|
||||
|
||||
// LockInfo represents information on a current Ceph lock
|
||||
type LockInfo struct {
|
||||
NumLockers int
|
||||
Exclusive bool
|
||||
Tag string
|
||||
Clients []string
|
||||
Cookies []string
|
||||
Addrs []string
|
||||
}
|
||||
|
||||
// IOContext represents a context for performing I/O within a pool.
|
||||
type IOContext struct {
|
||||
ioctx C.rados_ioctx_t
|
||||
|
||||
// Hold a reference back to the connection that the ioctx depends on so
|
||||
// that Go's GC doesn't trigger the Conn's finalizer before this
|
||||
// IOContext is destroyed.
|
||||
conn *Conn
|
||||
}
|
||||
|
||||
// validate returns an error if the ioctx is not ready to be used
|
||||
// with ceph C calls.
|
||||
func (ioctx *IOContext) validate() error {
|
||||
if ioctx.ioctx == nil {
|
||||
return ErrInvalidIOContext
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pointer returns a pointer reference to an internal structure.
|
||||
// This function should NOT be used outside of go-ceph itself.
|
||||
func (ioctx *IOContext) Pointer() unsafe.Pointer {
|
||||
return unsafe.Pointer(ioctx.ioctx)
|
||||
}
|
||||
|
||||
// SetNamespace sets the namespace for objects within this IO context (pool).
|
||||
// Setting namespace to a empty or zero length string sets the pool to the default namespace.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void rados_ioctx_set_namespace(rados_ioctx_t io,
|
||||
// const char *nspace);
|
||||
func (ioctx *IOContext) SetNamespace(namespace string) {
|
||||
var cns *C.char
|
||||
if len(namespace) > 0 {
|
||||
cns = C.CString(namespace)
|
||||
defer C.free(unsafe.Pointer(cns))
|
||||
}
|
||||
C.rados_ioctx_set_namespace(ioctx.ioctx, cns)
|
||||
}
|
||||
|
||||
// Create a new object with key oid.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void rados_write_op_create(rados_write_op_t write_op, int exclusive,
|
||||
// const char* category)
|
||||
func (ioctx *IOContext) Create(oid string, exclusive CreateOption) error {
|
||||
op := CreateWriteOp()
|
||||
defer op.Release()
|
||||
op.Create(exclusive)
|
||||
return op.operateCompat(ioctx, oid)
|
||||
}
|
||||
|
||||
// Write writes len(data) bytes to the object with key oid starting at byte
|
||||
// offset offset. It returns an error, if any.
|
||||
func (ioctx *IOContext) Write(oid string, data []byte, offset uint64) error {
|
||||
coid := C.CString(oid)
|
||||
defer C.free(unsafe.Pointer(coid))
|
||||
|
||||
dataPointer := unsafe.Pointer(nil)
|
||||
if len(data) > 0 {
|
||||
dataPointer = unsafe.Pointer(&data[0])
|
||||
}
|
||||
|
||||
ret := C.rados_write(ioctx.ioctx, coid,
|
||||
(*C.char)(dataPointer),
|
||||
(C.size_t)(len(data)),
|
||||
(C.uint64_t)(offset))
|
||||
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// WriteFull writes len(data) bytes to the object with key oid.
|
||||
// The object is filled with the provided data. If the object exists,
|
||||
// it is atomically truncated and then written. It returns an error, if any.
|
||||
func (ioctx *IOContext) WriteFull(oid string, data []byte) error {
|
||||
coid := C.CString(oid)
|
||||
defer C.free(unsafe.Pointer(coid))
|
||||
|
||||
ret := C.rados_write_full(ioctx.ioctx, coid,
|
||||
(*C.char)(unsafe.Pointer(&data[0])),
|
||||
(C.size_t)(len(data)))
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// Append appends len(data) bytes to the object with key oid.
|
||||
// The object is appended with the provided data. If the object exists,
|
||||
// it is atomically appended to. It returns an error, if any.
|
||||
func (ioctx *IOContext) Append(oid string, data []byte) error {
|
||||
coid := C.CString(oid)
|
||||
defer C.free(unsafe.Pointer(coid))
|
||||
|
||||
ret := C.rados_append(ioctx.ioctx, coid,
|
||||
(*C.char)(unsafe.Pointer(&data[0])),
|
||||
(C.size_t)(len(data)))
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// Read reads up to len(data) bytes from the object with key oid starting at byte
|
||||
// offset offset. It returns the number of bytes read and an error, if any.
|
||||
func (ioctx *IOContext) Read(oid string, data []byte, offset uint64) (int, error) {
|
||||
coid := C.CString(oid)
|
||||
defer C.free(unsafe.Pointer(coid))
|
||||
|
||||
var buf *C.char
|
||||
if len(data) > 0 {
|
||||
buf = (*C.char)(unsafe.Pointer(&data[0]))
|
||||
}
|
||||
|
||||
ret := C.rados_read(
|
||||
ioctx.ioctx,
|
||||
coid,
|
||||
buf,
|
||||
(C.size_t)(len(data)),
|
||||
(C.uint64_t)(offset))
|
||||
|
||||
if ret >= 0 {
|
||||
return int(ret), nil
|
||||
}
|
||||
return 0, getError(ret)
|
||||
}
|
||||
|
||||
// Delete deletes the object with key oid. It returns an error, if any.
|
||||
func (ioctx *IOContext) Delete(oid string) error {
|
||||
coid := C.CString(oid)
|
||||
defer C.free(unsafe.Pointer(coid))
|
||||
|
||||
return getError(C.rados_remove(ioctx.ioctx, coid))
|
||||
}
|
||||
|
||||
// Truncate resizes the object with key oid to size size. If the operation
|
||||
// enlarges the object, the new area is logically filled with zeroes. If the
|
||||
// operation shrinks the object, the excess data is removed. It returns an
|
||||
// error, if any.
|
||||
func (ioctx *IOContext) Truncate(oid string, size uint64) error {
|
||||
coid := C.CString(oid)
|
||||
defer C.free(unsafe.Pointer(coid))
|
||||
|
||||
return getError(C.rados_trunc(ioctx.ioctx, coid, (C.uint64_t)(size)))
|
||||
}
|
||||
|
||||
// Destroy informs librados that the I/O context is no longer in use.
|
||||
// Resources associated with the context may not be freed immediately, and the
|
||||
// context should not be used again after calling this method.
|
||||
func (ioctx *IOContext) Destroy() {
|
||||
C.rados_ioctx_destroy(ioctx.ioctx)
|
||||
}
|
||||
|
||||
// GetPoolStats returns a set of statistics about the pool associated with this I/O
|
||||
// context.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_ioctx_pool_stat(rados_ioctx_t io,
|
||||
// struct rados_pool_stat_t *stats);
|
||||
func (ioctx *IOContext) GetPoolStats() (stat PoolStat, err error) {
|
||||
cStat := C.struct_rados_pool_stat_t{}
|
||||
ret := C.rados_ioctx_pool_stat(ioctx.ioctx, &cStat)
|
||||
if ret < 0 {
|
||||
return PoolStat{}, getError(ret)
|
||||
}
|
||||
return PoolStat{
|
||||
Num_bytes: uint64(cStat.num_bytes),
|
||||
Num_kb: uint64(cStat.num_kb),
|
||||
Num_objects: uint64(cStat.num_objects),
|
||||
Num_object_clones: uint64(cStat.num_object_clones),
|
||||
Num_object_copies: uint64(cStat.num_object_copies),
|
||||
Num_objects_missing_on_primary: uint64(cStat.num_objects_missing_on_primary),
|
||||
Num_objects_unfound: uint64(cStat.num_objects_unfound),
|
||||
Num_objects_degraded: uint64(cStat.num_objects_degraded),
|
||||
Num_rd: uint64(cStat.num_rd),
|
||||
Num_rd_kb: uint64(cStat.num_rd_kb),
|
||||
Num_wr: uint64(cStat.num_wr),
|
||||
Num_wr_kb: uint64(cStat.num_wr_kb),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetPoolID returns the pool ID associated with the I/O context.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int64_t rados_ioctx_get_id(rados_ioctx_t io)
|
||||
func (ioctx *IOContext) GetPoolID() int64 {
|
||||
ret := C.rados_ioctx_get_id(ioctx.ioctx)
|
||||
return int64(ret)
|
||||
}
|
||||
|
||||
// GetPoolName returns the name of the pool associated with the I/O context.
|
||||
func (ioctx *IOContext) GetPoolName() (name string, err error) {
|
||||
var (
|
||||
buf []byte
|
||||
ret C.int
|
||||
)
|
||||
retry.WithSizes(128, 8192, func(size int) retry.Hint {
|
||||
buf = make([]byte, size)
|
||||
ret = C.rados_ioctx_get_pool_name(
|
||||
ioctx.ioctx,
|
||||
(*C.char)(unsafe.Pointer(&buf[0])),
|
||||
C.unsigned(len(buf)))
|
||||
err = getErrorIfNegative(ret)
|
||||
return retry.DoubleSize.If(err == errRange)
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name = C.GoStringN((*C.char)(unsafe.Pointer(&buf[0])), ret)
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// ObjectListFunc is the type of the function called for each object visited
|
||||
// by ListObjects.
|
||||
type ObjectListFunc func(oid string)
|
||||
|
||||
// ListObjects lists all of the objects in the pool associated with the I/O
|
||||
// context, and called the provided listFn function for each object, passing
|
||||
// to the function the name of the object. Call SetNamespace with
|
||||
// RadosAllNamespaces before calling this function to return objects from all
|
||||
// namespaces
|
||||
func (ioctx *IOContext) ListObjects(listFn ObjectListFunc) error {
|
||||
pageResults := C.size_t(defaultListObjectsResultSize)
|
||||
var filterLen C.size_t
|
||||
results := make([]C.rados_object_list_item, pageResults)
|
||||
|
||||
next := C.rados_object_list_begin(ioctx.ioctx)
|
||||
if next == nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
defer C.rados_object_list_cursor_free(ioctx.ioctx, next)
|
||||
finish := C.rados_object_list_end(ioctx.ioctx)
|
||||
if finish == nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
defer C.rados_object_list_cursor_free(ioctx.ioctx, finish)
|
||||
|
||||
for {
|
||||
res := (*C.rados_object_list_item)(unsafe.Pointer(&results[0]))
|
||||
ret := C.rados_object_list(ioctx.ioctx, next, finish, pageResults, nil, filterLen, res, &next)
|
||||
if ret < 0 {
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
numEntries := int(ret)
|
||||
for i := 0; i < numEntries; i++ {
|
||||
item := results[i]
|
||||
listFn(C.GoStringN(item.oid, (C.int)(item.oid_length)))
|
||||
}
|
||||
C.rados_object_list_free(C.size_t(ret), res)
|
||||
|
||||
if C.rados_object_list_is_end(ioctx.ioctx, next) == listEndSentinel {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stat returns the size of the object and its last modification time
|
||||
func (ioctx *IOContext) Stat(object string) (stat ObjectStat, err error) {
|
||||
var cPsize C.uint64_t
|
||||
var cPmtime C.time_t
|
||||
cObject := C.CString(object)
|
||||
defer C.free(unsafe.Pointer(cObject))
|
||||
|
||||
ret := C.rados_stat(
|
||||
ioctx.ioctx,
|
||||
cObject,
|
||||
&cPsize,
|
||||
&cPmtime)
|
||||
|
||||
if ret < 0 {
|
||||
return ObjectStat{}, getError(ret)
|
||||
}
|
||||
return ObjectStat{
|
||||
Size: uint64(cPsize),
|
||||
ModTime: time.Unix(int64(cPmtime), 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetXattr gets an xattr with key `name`, it returns the length of
|
||||
// the key read or an error if not successful
|
||||
func (ioctx *IOContext) GetXattr(object string, name string, data []byte) (int, error) {
|
||||
cObject := C.CString(object)
|
||||
cName := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cObject))
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
|
||||
ret := C.rados_getxattr(
|
||||
ioctx.ioctx,
|
||||
cObject,
|
||||
cName,
|
||||
(*C.char)(unsafe.Pointer(&data[0])),
|
||||
(C.size_t)(len(data)))
|
||||
|
||||
if ret >= 0 {
|
||||
return int(ret), nil
|
||||
}
|
||||
return 0, getError(ret)
|
||||
}
|
||||
|
||||
// SetXattr sets an xattr for an object with key `name` with value as `data`
|
||||
func (ioctx *IOContext) SetXattr(object string, name string, data []byte) error {
|
||||
cObject := C.CString(object)
|
||||
cName := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cObject))
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
|
||||
ret := C.rados_setxattr(
|
||||
ioctx.ioctx,
|
||||
cObject,
|
||||
cName,
|
||||
(*C.char)(unsafe.Pointer(&data[0])),
|
||||
(C.size_t)(len(data)))
|
||||
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// ListXattrs lists all the xattrs for an object. The xattrs are returned as a
|
||||
// mapping of string keys and byte-slice values.
|
||||
func (ioctx *IOContext) ListXattrs(oid string) (map[string][]byte, error) {
|
||||
coid := C.CString(oid)
|
||||
defer C.free(unsafe.Pointer(coid))
|
||||
|
||||
var it C.rados_xattrs_iter_t
|
||||
|
||||
ret := C.rados_getxattrs(ioctx.ioctx, coid, &it)
|
||||
if ret < 0 {
|
||||
return nil, getError(ret)
|
||||
}
|
||||
defer func() { C.rados_getxattrs_end(it) }()
|
||||
m := make(map[string][]byte)
|
||||
for {
|
||||
var cName, cVal *C.char
|
||||
var cLen C.size_t
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
defer C.free(unsafe.Pointer(cVal))
|
||||
|
||||
ret := C.rados_getxattrs_next(it, &cName, &cVal, &cLen)
|
||||
if ret < 0 {
|
||||
return nil, getError(ret)
|
||||
}
|
||||
// rados api returns a null name,val & 0-length upon
|
||||
// end of iteration
|
||||
if cName == nil {
|
||||
return m, nil // stop iteration
|
||||
}
|
||||
m[C.GoString(cName)] = C.GoBytes(unsafe.Pointer(cVal), (C.int)(cLen))
|
||||
}
|
||||
}
|
||||
|
||||
// RmXattr removes an xattr with key `name` from object `oid`
|
||||
func (ioctx *IOContext) RmXattr(oid string, name string) error {
|
||||
coid := C.CString(oid)
|
||||
cName := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(coid))
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
|
||||
ret := C.rados_rmxattr(
|
||||
ioctx.ioctx,
|
||||
coid,
|
||||
cName)
|
||||
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// LockExclusive takes an exclusive lock on an object.
|
||||
func (ioctx *IOContext) LockExclusive(oid, name, cookie, desc string, duration time.Duration, flags *byte) (int, error) {
|
||||
coid := C.CString(oid)
|
||||
cName := C.CString(name)
|
||||
cCookie := C.CString(cookie)
|
||||
cDesc := C.CString(desc)
|
||||
|
||||
var cDuration C.struct_timeval
|
||||
if duration != 0 {
|
||||
tv := syscall.NsecToTimeval(duration.Nanoseconds())
|
||||
cDuration = C.struct_timeval{tv_sec: C.ceph_time_t(tv.Sec), tv_usec: C.ceph_suseconds_t(tv.Usec)}
|
||||
}
|
||||
|
||||
var cFlags C.uint8_t
|
||||
if flags != nil {
|
||||
cFlags = C.uint8_t(*flags)
|
||||
}
|
||||
|
||||
defer C.free(unsafe.Pointer(coid))
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
defer C.free(unsafe.Pointer(cCookie))
|
||||
defer C.free(unsafe.Pointer(cDesc))
|
||||
|
||||
ret := C.rados_lock_exclusive(
|
||||
ioctx.ioctx,
|
||||
coid,
|
||||
cName,
|
||||
cCookie,
|
||||
cDesc,
|
||||
&cDuration,
|
||||
cFlags)
|
||||
|
||||
// 0 on success, negative error code on failure
|
||||
// -EBUSY if the lock is already held by another (client, cookie) pair
|
||||
// -EEXIST if the lock is already held by the same (client, cookie) pair
|
||||
|
||||
switch ret {
|
||||
case 0:
|
||||
return int(ret), nil
|
||||
case -C.EBUSY:
|
||||
return int(ret), nil
|
||||
case -C.EEXIST:
|
||||
return int(ret), nil
|
||||
default:
|
||||
return int(ret), getError(ret)
|
||||
}
|
||||
}
|
||||
|
||||
// LockShared takes a shared lock on an object.
|
||||
func (ioctx *IOContext) LockShared(oid, name, cookie, tag, desc string, duration time.Duration, flags *byte) (int, error) {
|
||||
coid := C.CString(oid)
|
||||
cName := C.CString(name)
|
||||
cCookie := C.CString(cookie)
|
||||
cTag := C.CString(tag)
|
||||
cDesc := C.CString(desc)
|
||||
|
||||
var cDuration C.struct_timeval
|
||||
if duration != 0 {
|
||||
tv := syscall.NsecToTimeval(duration.Nanoseconds())
|
||||
cDuration = C.struct_timeval{tv_sec: C.ceph_time_t(tv.Sec), tv_usec: C.ceph_suseconds_t(tv.Usec)}
|
||||
}
|
||||
|
||||
var cFlags C.uint8_t
|
||||
if flags != nil {
|
||||
cFlags = C.uint8_t(*flags)
|
||||
}
|
||||
|
||||
defer C.free(unsafe.Pointer(coid))
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
defer C.free(unsafe.Pointer(cCookie))
|
||||
defer C.free(unsafe.Pointer(cTag))
|
||||
defer C.free(unsafe.Pointer(cDesc))
|
||||
|
||||
ret := C.rados_lock_shared(
|
||||
ioctx.ioctx,
|
||||
coid,
|
||||
cName,
|
||||
cCookie,
|
||||
cTag,
|
||||
cDesc,
|
||||
&cDuration,
|
||||
cFlags)
|
||||
|
||||
// 0 on success, negative error code on failure
|
||||
// -EBUSY if the lock is already held by another (client, cookie) pair
|
||||
// -EEXIST if the lock is already held by the same (client, cookie) pair
|
||||
|
||||
switch ret {
|
||||
case 0:
|
||||
return int(ret), nil
|
||||
case -C.EBUSY:
|
||||
return int(ret), nil
|
||||
case -C.EEXIST:
|
||||
return int(ret), nil
|
||||
default:
|
||||
return int(ret), getError(ret)
|
||||
}
|
||||
}
|
||||
|
||||
// Unlock releases a shared or exclusive lock on an object.
|
||||
func (ioctx *IOContext) Unlock(oid, name, cookie string) (int, error) {
|
||||
coid := C.CString(oid)
|
||||
cName := C.CString(name)
|
||||
cCookie := C.CString(cookie)
|
||||
|
||||
defer C.free(unsafe.Pointer(coid))
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
defer C.free(unsafe.Pointer(cCookie))
|
||||
|
||||
// 0 on success, negative error code on failure
|
||||
// -ENOENT if the lock is not held by the specified (client, cookie) pair
|
||||
|
||||
ret := C.rados_unlock(
|
||||
ioctx.ioctx,
|
||||
coid,
|
||||
cName,
|
||||
cCookie)
|
||||
|
||||
switch ret {
|
||||
case 0:
|
||||
return int(ret), nil
|
||||
case -C.ENOENT:
|
||||
return int(ret), nil
|
||||
default:
|
||||
return int(ret), getError(ret)
|
||||
}
|
||||
}
|
||||
|
||||
// ListLockers lists clients that have locked the named object lock and
|
||||
// information about the lock.
|
||||
// The number of bytes required in each buffer is put in the corresponding size
|
||||
// out parameter. If any of the provided buffers are too short, -ERANGE is
|
||||
// returned after these sizes are filled in.
|
||||
func (ioctx *IOContext) ListLockers(oid, name string) (*LockInfo, error) {
|
||||
coid := C.CString(oid)
|
||||
cName := C.CString(name)
|
||||
|
||||
cTag := (*C.char)(C.malloc(C.size_t(1024)))
|
||||
cClients := (*C.char)(C.malloc(C.size_t(1024)))
|
||||
cCookies := (*C.char)(C.malloc(C.size_t(1024)))
|
||||
cAddrs := (*C.char)(C.malloc(C.size_t(1024)))
|
||||
|
||||
var cExclusive C.int
|
||||
cTagLen := C.size_t(1024)
|
||||
cClientsLen := C.size_t(1024)
|
||||
cCookiesLen := C.size_t(1024)
|
||||
cAddrsLen := C.size_t(1024)
|
||||
|
||||
defer C.free(unsafe.Pointer(coid))
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
defer C.free(unsafe.Pointer(cTag))
|
||||
defer C.free(unsafe.Pointer(cClients))
|
||||
defer C.free(unsafe.Pointer(cCookies))
|
||||
defer C.free(unsafe.Pointer(cAddrs))
|
||||
|
||||
ret := C.rados_list_lockers(
|
||||
ioctx.ioctx,
|
||||
coid,
|
||||
cName,
|
||||
&cExclusive,
|
||||
cTag,
|
||||
&cTagLen,
|
||||
cClients,
|
||||
&cClientsLen,
|
||||
cCookies,
|
||||
&cCookiesLen,
|
||||
cAddrs,
|
||||
&cAddrsLen)
|
||||
|
||||
splitCString := func(items *C.char, itemsLen C.size_t) []string {
|
||||
currLen := 0
|
||||
clients := []string{}
|
||||
for currLen < int(itemsLen) {
|
||||
client := C.GoString(C.nextChunk(&items))
|
||||
clients = append(clients, client)
|
||||
currLen += len(client) + 1
|
||||
}
|
||||
return clients
|
||||
}
|
||||
|
||||
if ret < 0 {
|
||||
return nil, getError(C.int(ret))
|
||||
}
|
||||
return &LockInfo{int(ret), cExclusive == 1, C.GoString(cTag), splitCString(cClients, cClientsLen), splitCString(cCookies, cCookiesLen), splitCString(cAddrs, cAddrsLen)}, nil
|
||||
}
|
||||
|
||||
// BreakLock releases a shared or exclusive lock on an object, which was taken by the specified client.
|
||||
func (ioctx *IOContext) BreakLock(oid, name, client, cookie string) (int, error) {
|
||||
coid := C.CString(oid)
|
||||
cName := C.CString(name)
|
||||
cClient := C.CString(client)
|
||||
cCookie := C.CString(cookie)
|
||||
|
||||
defer C.free(unsafe.Pointer(coid))
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
defer C.free(unsafe.Pointer(cClient))
|
||||
defer C.free(unsafe.Pointer(cCookie))
|
||||
|
||||
// 0 on success, negative error code on failure
|
||||
// -ENOENT if the lock is not held by the specified (client, cookie) pair
|
||||
// -EINVAL if the client cannot be parsed
|
||||
|
||||
ret := C.rados_break_lock(
|
||||
ioctx.ioctx,
|
||||
coid,
|
||||
cName,
|
||||
cClient,
|
||||
cCookie)
|
||||
|
||||
switch ret {
|
||||
case 0:
|
||||
return int(ret), nil
|
||||
case -C.ENOENT:
|
||||
return int(ret), nil
|
||||
case -C.EINVAL: // -EINVAL
|
||||
return int(ret), nil
|
||||
default:
|
||||
return int(ret), getError(ret)
|
||||
}
|
||||
}
|
||||
|
||||
// GetLastVersion will return the version number of the last object read or
|
||||
// written to.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// uint64_t rados_get_last_version(rados_ioctx_t io);
|
||||
func (ioctx *IOContext) GetLastVersion() (uint64, error) {
|
||||
if err := ioctx.validate(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
v := C.rados_get_last_version(ioctx.ioctx)
|
||||
return uint64(v), nil
|
||||
}
|
||||
|
||||
// GetNamespace gets the namespace used for objects within this IO context.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_ioctx_get_namespace(rados_ioctx_t io, char *buf,
|
||||
// unsigned maxlen);
|
||||
func (ioctx *IOContext) GetNamespace() (string, error) {
|
||||
if err := ioctx.validate(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
var (
|
||||
err error
|
||||
buf []byte
|
||||
ret C.int
|
||||
)
|
||||
retry.WithSizes(128, 8192, func(size int) retry.Hint {
|
||||
buf = make([]byte, size)
|
||||
ret = C.rados_ioctx_get_namespace(
|
||||
ioctx.ioctx,
|
||||
(*C.char)(unsafe.Pointer(&buf[0])),
|
||||
C.unsigned(len(buf)))
|
||||
err = getErrorIfNegative(ret)
|
||||
return retry.DoubleSize.If(err == errRange)
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(buf[:ret]), nil
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
//go:build nautilus
|
||||
// +build nautilus
|
||||
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <rados/librados.h>
|
||||
//
|
||||
import "C"
|
||||
|
||||
// SetPoolFullTry makes sure to send requests to the cluster despite
|
||||
// the cluster or pool being marked full; ops will either succeed(e.g., delete)
|
||||
// or return EDQUOT or ENOSPC.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void rados_set_osdmap_full_try(rados_ioctx_t io);
|
||||
func (ioctx *IOContext) SetPoolFullTry() error {
|
||||
if err := ioctx.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
C.rados_set_osdmap_full_try(ioctx.ioctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnsetPoolFullTry unsets the flag set by SetPoolFullTry()
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void rados_unset_osdmap_full_try(rados_ioctx_t io);
|
||||
func (ioctx *IOContext) UnsetPoolFullTry() error {
|
||||
if err := ioctx.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
C.rados_unset_osdmap_full_try(ioctx.ioctx)
|
||||
return nil
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
//go:build !nautilus
|
||||
// +build !nautilus
|
||||
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <rados/librados.h>
|
||||
//
|
||||
import "C"
|
||||
|
||||
// Ceph octopus deprecates rados_set_osdmap_full_try() and implements rados_set_pool_full_try()
|
||||
// Ceph octopus deprecates rados_unset_osdmap_full_try() and implements rados_unset_pool_full_try()
|
||||
|
||||
// SetPoolFullTry makes sure to send requests to the cluster despite
|
||||
// the cluster or pool being marked full; ops will either succeed(e.g., delete)
|
||||
// or return EDQUOT or ENOSPC.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void rados_set_pool_full_try(rados_ioctx_t io);
|
||||
func (ioctx *IOContext) SetPoolFullTry() error {
|
||||
if err := ioctx.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
C.rados_set_pool_full_try(ioctx.ioctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnsetPoolFullTry unsets the flag set by SetPoolFullTry()
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void rados_unset_pool_full_try(rados_ioctx_t io);
|
||||
func (ioctx *IOContext) UnsetPoolFullTry() error {
|
||||
if err := ioctx.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
C.rados_unset_pool_full_try(ioctx.ioctx)
|
||||
return nil
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <rados/librados.h>
|
||||
// #include <stdlib.h>
|
||||
//
|
||||
import "C"
|
||||
|
||||
// Alignment returns the required stripe size in bytes for pools supporting/requiring it, or an error if unsuccessful.
|
||||
// For an EC pool, a buffer size multiple of its stripe size is required to call Append. To know if the pool requires
|
||||
// alignment or not, use RequiresAlignment.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_ioctx_pool_required_alignment2(rados_ioctx_t io, uint64_t *alignment)
|
||||
func (ioctx *IOContext) Alignment() (uint64, error) {
|
||||
var alignSizeBytes C.uint64_t
|
||||
ret := C.rados_ioctx_pool_required_alignment2(
|
||||
ioctx.ioctx,
|
||||
&alignSizeBytes)
|
||||
if ret != 0 {
|
||||
return 0, getError(ret)
|
||||
}
|
||||
return uint64(alignSizeBytes), nil
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <rados/librados.h>
|
||||
// #include <stdlib.h>
|
||||
//
|
||||
import "C"
|
||||
|
||||
// RequiresAlignment returns true if the pool supports/requires alignment or an error if not successful.
|
||||
// For an EC pool, a buffer size multiple of its stripe size is required to call Append. See
|
||||
// Alignment to know how to get the stripe size for pools requiring it.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_ioctx_pool_requires_alignment2(rados_ioctx_t io, int *req)
|
||||
func (ioctx *IOContext) RequiresAlignment() (bool, error) {
|
||||
var alignRequired C.int
|
||||
ret := C.rados_ioctx_pool_requires_alignment2(
|
||||
ioctx.ioctx,
|
||||
&alignRequired)
|
||||
if ret != 0 {
|
||||
return false, getError(ret)
|
||||
}
|
||||
return (alignRequired != 0), nil
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <rados/librados.h>
|
||||
// #include <stdlib.h>
|
||||
//
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// SetAllocationHint sets allocation hint for an object. This is an advisory
|
||||
// operation, it will always succeed (as if it was submitted with a
|
||||
// LIBRADOS_OP_FLAG_FAILOK flag set) and is not guaranteed to do anything on
|
||||
// the backend.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_set_alloc_hint2(rados_ioctx_t io,
|
||||
// const char *o,
|
||||
// uint64_t expected_object_size,
|
||||
// uint64_t expected_write_size,
|
||||
// uint32_t flags);
|
||||
func (ioctx *IOContext) SetAllocationHint(oid string, expectedObjectSize uint64, expectedWriteSize uint64, flags AllocHintFlags) error {
|
||||
coid := C.CString(oid)
|
||||
defer C.free(unsafe.Pointer(coid))
|
||||
|
||||
return getError(C.rados_set_alloc_hint2(
|
||||
ioctx.ioctx,
|
||||
coid,
|
||||
(C.uint64_t)(expectedObjectSize),
|
||||
(C.uint64_t)(expectedWriteSize),
|
||||
(C.uint32_t)(flags),
|
||||
))
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <rados/librados.h>
|
||||
//
|
||||
import "C"
|
||||
|
||||
// Iter supports iterating over objects in the ioctx.
|
||||
type Iter struct {
|
||||
ctx C.rados_list_ctx_t
|
||||
err error
|
||||
entry string
|
||||
namespace string
|
||||
}
|
||||
|
||||
// IterToken supports reporting on and seeking to different positions.
|
||||
type IterToken uint32
|
||||
|
||||
// Iter returns a Iterator object that can be used to list the object names in the current pool
|
||||
func (ioctx *IOContext) Iter() (*Iter, error) {
|
||||
iter := Iter{}
|
||||
if cerr := C.rados_nobjects_list_open(ioctx.ioctx, &iter.ctx); cerr < 0 {
|
||||
return nil, getError(cerr)
|
||||
}
|
||||
return &iter, nil
|
||||
}
|
||||
|
||||
// Token returns a token marking the current position of the iterator. To be used in combination with Iter.Seek()
|
||||
func (iter *Iter) Token() IterToken {
|
||||
return IterToken(C.rados_nobjects_list_get_pg_hash_position(iter.ctx))
|
||||
}
|
||||
|
||||
// Seek moves the iterator to the position indicated by the token.
|
||||
func (iter *Iter) Seek(token IterToken) {
|
||||
C.rados_nobjects_list_seek(iter.ctx, C.uint32_t(token))
|
||||
}
|
||||
|
||||
// Next retrieves the next object name in the pool/namespace iterator.
|
||||
// Upon a successful invocation (return value of true), the Value method should
|
||||
// be used to obtain the name of the retrieved object name. When the iterator is
|
||||
// exhausted, Next returns false. The Err method should used to verify whether the
|
||||
// end of the iterator was reached, or the iterator received an error.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// iter := pool.Iter()
|
||||
// defer iter.Close()
|
||||
// for iter.Next() {
|
||||
// fmt.Printf("%v\n", iter.Value())
|
||||
// }
|
||||
// return iter.Err()
|
||||
func (iter *Iter) Next() bool {
|
||||
var cEntry *C.char
|
||||
var cNamespace *C.char
|
||||
if cerr := C.rados_nobjects_list_next(iter.ctx, &cEntry, nil, &cNamespace); cerr < 0 {
|
||||
iter.err = getError(cerr)
|
||||
return false
|
||||
}
|
||||
iter.entry = C.GoString(cEntry)
|
||||
iter.namespace = C.GoString(cNamespace)
|
||||
return true
|
||||
}
|
||||
|
||||
// Value returns the current value of the iterator (object name), after a successful call to Next.
|
||||
func (iter *Iter) Value() string {
|
||||
if iter.err != nil {
|
||||
return ""
|
||||
}
|
||||
return iter.entry
|
||||
}
|
||||
|
||||
// Namespace returns the namespace associated with the current value of the iterator (object name), after a successful call to Next.
|
||||
func (iter *Iter) Namespace() string {
|
||||
if iter.err != nil {
|
||||
return ""
|
||||
}
|
||||
return iter.namespace
|
||||
}
|
||||
|
||||
// Err checks whether the iterator has encountered an error.
|
||||
func (iter *Iter) Err() error {
|
||||
if iter.err == ErrNotFound {
|
||||
return nil
|
||||
}
|
||||
return iter.err
|
||||
}
|
||||
|
||||
// Close the iterator cursor on the server. Be aware that iterators are not closed automatically
|
||||
// at the end of iteration.
|
||||
func (iter *Iter) Close() {
|
||||
C.rados_nobjects_list_close(iter.ctx)
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
package rados
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lrados
|
||||
#include <stdlib.h>
|
||||
#include <rados/librados.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// OmapKeyValue items are returned by the GetOmapStep's Next call.
|
||||
type OmapKeyValue struct {
|
||||
Key string
|
||||
Value []byte
|
||||
}
|
||||
|
||||
// GetOmapStep values are used to get the results of an GetOmapValues call
|
||||
// on a WriteOp. Until the Operate method of the WriteOp is called the Next
|
||||
// call will return an error. After Operate is called, the Next call will
|
||||
// return valid results.
|
||||
//
|
||||
// The life cycle of the GetOmapStep is bound to the ReadOp, if the ReadOp
|
||||
// Release method is called the public methods of the step must no longer be
|
||||
// used and may return errors.
|
||||
type GetOmapStep struct {
|
||||
// C returned data:
|
||||
iter C.rados_omap_iter_t
|
||||
more *C.uchar
|
||||
rval *C.int
|
||||
|
||||
// internal state:
|
||||
|
||||
// canIterate is only set after the operation is performed and is
|
||||
// intended to prevent premature fetching of data
|
||||
canIterate bool
|
||||
}
|
||||
|
||||
func newGetOmapStep() *GetOmapStep {
|
||||
gos := &GetOmapStep{
|
||||
more: (*C.uchar)(C.malloc(C.sizeof_uchar)),
|
||||
rval: (*C.int)(C.malloc(C.sizeof_int)),
|
||||
}
|
||||
runtime.SetFinalizer(gos, opStepFinalizer)
|
||||
return gos
|
||||
}
|
||||
|
||||
func (gos *GetOmapStep) free() {
|
||||
gos.canIterate = false
|
||||
if gos.iter != nil {
|
||||
C.rados_omap_get_end(gos.iter)
|
||||
}
|
||||
gos.iter = nil
|
||||
C.free(unsafe.Pointer(gos.more))
|
||||
gos.more = nil
|
||||
C.free(unsafe.Pointer(gos.rval))
|
||||
gos.rval = nil
|
||||
}
|
||||
|
||||
func (gos *GetOmapStep) update() error {
|
||||
err := getError(*gos.rval)
|
||||
gos.canIterate = (err == nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// Next returns the next key value pair or nil if iteration is exhausted.
|
||||
func (gos *GetOmapStep) Next() (*OmapKeyValue, error) {
|
||||
if !gos.canIterate {
|
||||
return nil, ErrOperationIncomplete
|
||||
}
|
||||
var (
|
||||
cKey *C.char
|
||||
cVal *C.char
|
||||
cKeyLen C.size_t
|
||||
cValLen C.size_t
|
||||
)
|
||||
ret := C.rados_omap_get_next2(gos.iter, &cKey, &cVal, &cKeyLen, &cValLen)
|
||||
if ret != 0 {
|
||||
return nil, getError(ret)
|
||||
}
|
||||
if cKey == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return &OmapKeyValue{
|
||||
Key: string(C.GoBytes(unsafe.Pointer(cKey), C.int(cKeyLen))),
|
||||
Value: C.GoBytes(unsafe.Pointer(cVal), C.int(cValLen)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// More returns true if there are more matching keys available.
|
||||
func (gos *GetOmapStep) More() bool {
|
||||
// tad bit hacky, but go can't automatically convert from
|
||||
// unsigned char to bool
|
||||
return *gos.more != 0
|
||||
}
|
||||
|
||||
// SetOmap appends the map `pairs` to the omap `oid`
|
||||
func (ioctx *IOContext) SetOmap(oid string, pairs map[string][]byte) error {
|
||||
op := CreateWriteOp()
|
||||
defer op.Release()
|
||||
op.SetOmap(pairs)
|
||||
return op.operateCompat(ioctx, oid)
|
||||
}
|
||||
|
||||
// OmapListFunc is the type of the function called for each omap key
|
||||
// visited by ListOmapValues
|
||||
type OmapListFunc func(key string, value []byte)
|
||||
|
||||
// ListOmapValues iterates over the keys and values in an omap by way of
|
||||
// a callback function.
|
||||
//
|
||||
// `startAfter`: iterate only on the keys after this specified one
|
||||
// `filterPrefix`: iterate only on the keys beginning with this prefix
|
||||
// `maxReturn`: iterate no more than `maxReturn` key/value pairs
|
||||
// `listFn`: the function called at each iteration
|
||||
func (ioctx *IOContext) ListOmapValues(oid string, startAfter string, filterPrefix string, maxReturn int64, listFn OmapListFunc) error {
|
||||
|
||||
op := CreateReadOp()
|
||||
defer op.Release()
|
||||
gos := op.GetOmapValues(startAfter, filterPrefix, uint64(maxReturn))
|
||||
err := op.operateCompat(ioctx, oid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
kv, err := gos.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if kv == nil {
|
||||
break
|
||||
}
|
||||
listFn(kv.Key, kv.Value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOmapValues fetches a set of keys and their values from an omap and returns then as a map
|
||||
// `startAfter`: retrieve only the keys after this specified one
|
||||
// `filterPrefix`: retrieve only the keys beginning with this prefix
|
||||
// `maxReturn`: retrieve no more than `maxReturn` key/value pairs
|
||||
func (ioctx *IOContext) GetOmapValues(oid string, startAfter string, filterPrefix string, maxReturn int64) (map[string][]byte, error) {
|
||||
omap := map[string][]byte{}
|
||||
|
||||
err := ioctx.ListOmapValues(
|
||||
oid, startAfter, filterPrefix, maxReturn,
|
||||
func(key string, value []byte) {
|
||||
omap[key] = value
|
||||
},
|
||||
)
|
||||
|
||||
return omap, err
|
||||
}
|
||||
|
||||
// GetAllOmapValues fetches all the keys and their values from an omap and returns then as a map
|
||||
// `startAfter`: retrieve only the keys after this specified one
|
||||
// `filterPrefix`: retrieve only the keys beginning with this prefix
|
||||
// `iteratorSize`: internal number of keys to fetch during a read operation
|
||||
func (ioctx *IOContext) GetAllOmapValues(oid string, startAfter string, filterPrefix string, iteratorSize int64) (map[string][]byte, error) {
|
||||
omap := map[string][]byte{}
|
||||
omapSize := 0
|
||||
|
||||
for {
|
||||
err := ioctx.ListOmapValues(
|
||||
oid, startAfter, filterPrefix, iteratorSize,
|
||||
func(key string, value []byte) {
|
||||
omap[key] = value
|
||||
startAfter = key
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return omap, err
|
||||
}
|
||||
|
||||
// End of omap
|
||||
if len(omap) == omapSize {
|
||||
break
|
||||
}
|
||||
|
||||
omapSize = len(omap)
|
||||
}
|
||||
|
||||
return omap, nil
|
||||
}
|
||||
|
||||
// RmOmapKeys removes the specified `keys` from the omap `oid`
|
||||
func (ioctx *IOContext) RmOmapKeys(oid string, keys []string) error {
|
||||
op := CreateWriteOp()
|
||||
defer op.Release()
|
||||
op.RmOmapKeys(keys)
|
||||
return op.operateCompat(ioctx, oid)
|
||||
}
|
||||
|
||||
// CleanOmap clears the omap `oid`
|
||||
func (ioctx *IOContext) CleanOmap(oid string) error {
|
||||
op := CreateWriteOp()
|
||||
defer op.Release()
|
||||
op.CleanOmap()
|
||||
return op.operateCompat(ioctx, oid)
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
package rados
|
||||
|
||||
// #include <stdlib.h>
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ceph/go-ceph/internal/log"
|
||||
)
|
||||
|
||||
// The file operation.go exists to support both read op and write op types that
|
||||
// have some pretty common behaviors between them. In C/C++ its assumed that
|
||||
// the buffer types and other pointers will not be freed between passing them
|
||||
// to the action setup calls (things like rados_write_op_write or
|
||||
// rados_read_op_omap_get_vals2) and the call to Operate(...). Since there's
|
||||
// nothing stopping one from sleeping for hours between these calls, or passing
|
||||
// the op to other functions and calling Operate there, we want a mechanism
|
||||
// that's (fairly) simple to understand and won't run afoul of Go's garbage
|
||||
// collection. That's one reason the operation type tracks the steps (the
|
||||
// parts that track complex inputs and outputs) so that as long as the op
|
||||
// exists it will have a reference to the step, which will have references
|
||||
// to the C language types.
|
||||
|
||||
type opKind string
|
||||
|
||||
const (
|
||||
readOp opKind = "read"
|
||||
writeOp opKind = "write"
|
||||
)
|
||||
|
||||
// OperationError is an error type that may be returned by an Operate call.
|
||||
// It captures the error from the operate call itself and any errors from
|
||||
// steps that can return an error.
|
||||
type OperationError struct {
|
||||
kind opKind
|
||||
OpError error
|
||||
StepErrors map[int]error
|
||||
}
|
||||
|
||||
func (e OperationError) Error() string {
|
||||
subErrors := []string{}
|
||||
if e.OpError != nil {
|
||||
subErrors = append(subErrors,
|
||||
fmt.Sprintf("op=%s", e.OpError))
|
||||
}
|
||||
for idx, es := range e.StepErrors {
|
||||
subErrors = append(subErrors,
|
||||
fmt.Sprintf("Step#%d=%s", idx, es))
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"%s operation error: %s",
|
||||
e.kind,
|
||||
strings.Join(subErrors, ", "))
|
||||
}
|
||||
|
||||
func (e OperationError) Unwrap() []error {
|
||||
subErrors := make([]error, 0, len(e.StepErrors)+1)
|
||||
if e.OpError != nil {
|
||||
subErrors = append(subErrors, e.OpError)
|
||||
}
|
||||
for _, es := range e.StepErrors {
|
||||
subErrors = append(subErrors, es)
|
||||
}
|
||||
return subErrors
|
||||
}
|
||||
|
||||
// opStep provides an interface for types that are tied to the management of
|
||||
// data being input or output from write ops and read ops. The steps are
|
||||
// meant to simplify the internals of the ops themselves and be exportable when
|
||||
// appropriate. If a step is not being exported it should not be returned
|
||||
// from an ops action function. If the step is exported it should be
|
||||
// returned from an ops action function.
|
||||
//
|
||||
// Not all types implementing opStep are expected to need all the functions
|
||||
// in the interface. However, for the sake of simplicity on the op side, we use
|
||||
// the same interface for all cases and expect those implementing opStep
|
||||
// just embed the without* types that provide no-op implementation of
|
||||
// functions that make up this interface.
|
||||
type opStep interface {
|
||||
// update the state of the step after the call to Operate.
|
||||
// It can be used to convert values from C and cache them and/or
|
||||
// communicate a failure of the action associated with the step. The
|
||||
// update call will only be made once. Implementations are not required to
|
||||
// handle this call being made more than once.
|
||||
update() error
|
||||
// free will be called to free any resources, especially C memory, that
|
||||
// the step is managing. The behavior of free should be idempotent and
|
||||
// handle being called more than once.
|
||||
free()
|
||||
}
|
||||
|
||||
// operation represents some of the shared underlying mechanisms for
|
||||
// both read and write op types.
|
||||
type operation struct {
|
||||
steps []opStep
|
||||
}
|
||||
|
||||
// free will call the free method of all the steps this operation
|
||||
// contains.
|
||||
func (o *operation) free() {
|
||||
for i := range o.steps {
|
||||
o.steps[i].free()
|
||||
}
|
||||
}
|
||||
|
||||
// update the operation and the steps it contains. The top-level result
|
||||
// of the rados call is passed in as ret and used to construct errors.
|
||||
// The update call of each step is used to update the contents of each
|
||||
// step and gather any errors from those steps.
|
||||
func (o *operation) update(kind opKind, ret C.int) error {
|
||||
stepErrors := map[int]error{}
|
||||
for i := range o.steps {
|
||||
if err := o.steps[i].update(); err != nil {
|
||||
stepErrors[i] = err
|
||||
}
|
||||
}
|
||||
if ret == 0 && len(stepErrors) == 0 {
|
||||
return nil
|
||||
}
|
||||
return OperationError{
|
||||
kind: kind,
|
||||
OpError: getError(ret),
|
||||
StepErrors: stepErrors,
|
||||
}
|
||||
}
|
||||
|
||||
func opStepFinalizer(s opStep) {
|
||||
if s != nil {
|
||||
log.Warnf("unreachable opStep object found. Cleaning up.")
|
||||
s.free()
|
||||
}
|
||||
}
|
||||
|
||||
// withoutUpdate can be embedded in a struct to help indicate
|
||||
// the type implements the opStep interface but has a no-op
|
||||
// update function.
|
||||
type withoutUpdate struct{}
|
||||
|
||||
func (*withoutUpdate) update() error { return nil }
|
||||
|
||||
// withoutFree can be embedded in a struct to help indicate
|
||||
// the type implements the opStep interface but has a no-op
|
||||
// free function.
|
||||
type withoutFree struct{}
|
||||
|
||||
func (*withoutFree) free() {}
|
||||
|
||||
// withRefs is a embeddable type to help track and free C memory.
|
||||
type withRefs struct {
|
||||
refs []unsafe.Pointer
|
||||
}
|
||||
|
||||
func (w *withRefs) free() {
|
||||
for i := range w.refs {
|
||||
C.free(w.refs[i])
|
||||
}
|
||||
w.refs = nil
|
||||
}
|
||||
|
||||
func (w *withRefs) add(ptr unsafe.Pointer) {
|
||||
w.refs = append(w.refs, ptr)
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <errno.h>
|
||||
// #include <stdlib.h>
|
||||
// #include <rados/librados.h>
|
||||
//
|
||||
import "C"
|
||||
|
||||
// OperationFlags control the behavior of read and write operations.
|
||||
type OperationFlags int
|
||||
|
||||
const (
|
||||
// OperationNoFlag indicates no special behavior is requested.
|
||||
OperationNoFlag = OperationFlags(C.LIBRADOS_OPERATION_NOFLAG)
|
||||
// OperationBalanceReads TODO
|
||||
OperationBalanceReads = OperationFlags(C.LIBRADOS_OPERATION_BALANCE_READS)
|
||||
// OperationLocalizeReads TODO
|
||||
OperationLocalizeReads = OperationFlags(C.LIBRADOS_OPERATION_LOCALIZE_READS)
|
||||
// OperationOrderReadsWrites TODO
|
||||
OperationOrderReadsWrites = OperationFlags(C.LIBRADOS_OPERATION_ORDER_READS_WRITES)
|
||||
// OperationIgnoreCache TODO
|
||||
OperationIgnoreCache = OperationFlags(C.LIBRADOS_OPERATION_IGNORE_CACHE)
|
||||
// OperationSkipRWLocks TODO
|
||||
OperationSkipRWLocks = OperationFlags(C.LIBRADOS_OPERATION_SKIPRWLOCKS)
|
||||
// OperationIgnoreOverlay TODO
|
||||
OperationIgnoreOverlay = OperationFlags(C.LIBRADOS_OPERATION_IGNORE_OVERLAY)
|
||||
// OperationFullTry send request to a full cluster or pool, ops such as delete
|
||||
// can succeed while other ops will return out-of-space errors.
|
||||
OperationFullTry = OperationFlags(C.LIBRADOS_OPERATION_FULL_TRY)
|
||||
// OperationFullForce TODO
|
||||
OperationFullForce = OperationFlags(C.LIBRADOS_OPERATION_FULL_FORCE)
|
||||
// OperationIgnoreRedirect TODO
|
||||
OperationIgnoreRedirect = OperationFlags(C.LIBRADOS_OPERATION_IGNORE_REDIRECT)
|
||||
// OperationOrderSnap TODO
|
||||
OperationOrderSnap = OperationFlags(C.LIBRADOS_OPERATION_ORDERSNAP)
|
||||
)
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <errno.h>
|
||||
// #include <stdlib.h>
|
||||
// #include <rados/librados.h>
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ceph/go-ceph/internal/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// AllNamespaces is used to reset a selected namespace to all
|
||||
// namespaces. See the IOContext SetNamespace function.
|
||||
AllNamespaces = C.LIBRADOS_ALL_NSPACES
|
||||
|
||||
// FIXME: for backwards compatibility
|
||||
|
||||
// RadosAllNamespaces is used to reset a selected namespace to all
|
||||
// namespaces. See the IOContext SetNamespace function.
|
||||
//
|
||||
// Deprecated: use AllNamespaces instead
|
||||
RadosAllNamespaces = AllNamespaces
|
||||
)
|
||||
|
||||
// OpFlags are flags that can be set on a per-op basis.
|
||||
type OpFlags uint
|
||||
|
||||
const (
|
||||
// OpFlagNone can be use to not set any flags.
|
||||
OpFlagNone = OpFlags(0)
|
||||
// OpFlagExcl marks an op to fail a create operation if the object
|
||||
// already exists.
|
||||
OpFlagExcl = OpFlags(C.LIBRADOS_OP_FLAG_EXCL)
|
||||
// OpFlagFailOk allows the transaction to succeed even if the flagged
|
||||
// op fails.
|
||||
OpFlagFailOk = OpFlags(C.LIBRADOS_OP_FLAG_FAILOK)
|
||||
// OpFlagFAdviseRandom indicates read/write op random.
|
||||
OpFlagFAdviseRandom = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_RANDOM)
|
||||
// OpFlagFAdviseSequential indicates read/write op sequential.
|
||||
OpFlagFAdviseSequential = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL)
|
||||
// OpFlagFAdviseWillNeed indicates read/write data will be accessed in
|
||||
// the near future (by someone).
|
||||
OpFlagFAdviseWillNeed = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_WILLNEED)
|
||||
// OpFlagFAdviseDontNeed indicates read/write data will not accessed in
|
||||
// the near future (by anyone).
|
||||
OpFlagFAdviseDontNeed = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_DONTNEED)
|
||||
// OpFlagFAdviseNoCache indicates read/write data will not accessed
|
||||
// again (by *this* client).
|
||||
OpFlagFAdviseNoCache = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_NOCACHE)
|
||||
)
|
||||
|
||||
// Version returns the major, minor, and patch components of the version of
|
||||
// the RADOS library linked against.
|
||||
func Version() (int, int, int) {
|
||||
var cMajor, cMinor, cPatch C.int
|
||||
C.rados_version(&cMajor, &cMinor, &cPatch)
|
||||
return int(cMajor), int(cMinor), int(cPatch)
|
||||
}
|
||||
|
||||
func makeConn() *Conn {
|
||||
return &Conn{connected: false}
|
||||
}
|
||||
|
||||
func newConn(user *C.char) (*Conn, error) {
|
||||
conn := makeConn()
|
||||
ret := C.rados_create(&conn.cluster, user)
|
||||
|
||||
if ret != 0 {
|
||||
return nil, getError(ret)
|
||||
}
|
||||
|
||||
runtime.SetFinalizer(conn, freeConn)
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// NewConn creates a new connection object. It returns the connection and an
|
||||
// error, if any.
|
||||
func NewConn() (*Conn, error) {
|
||||
return newConn(nil)
|
||||
}
|
||||
|
||||
// NewConnWithUser creates a new connection object with a custom username.
|
||||
// The supplied username should not include a 'client.' prefix. It returns
|
||||
// the connection and an error, if any.
|
||||
func NewConnWithUser(user string) (*Conn, error) {
|
||||
cUser := C.CString(user)
|
||||
defer C.free(unsafe.Pointer(cUser))
|
||||
return newConn(cUser)
|
||||
}
|
||||
|
||||
// NewConnWithClusterAndUser creates a new connection object for a specific
|
||||
// cluster and username. The supplied username should be in the 'client.<user>'
|
||||
// format. It returns the connection and an error, if any.
|
||||
func NewConnWithClusterAndUser(clusterName string, userName string) (*Conn, error) {
|
||||
cClusterName := C.CString(clusterName)
|
||||
defer C.free(unsafe.Pointer(cClusterName))
|
||||
|
||||
cName := C.CString(userName)
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
|
||||
conn := makeConn()
|
||||
ret := C.rados_create2(&conn.cluster, cClusterName, cName, 0)
|
||||
if ret != 0 {
|
||||
return nil, getError(ret)
|
||||
}
|
||||
|
||||
runtime.SetFinalizer(conn, freeConn)
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// freeConn releases resources that are allocated while configuring the
|
||||
// connection to the cluster. rados_shutdown() should only be needed after a
|
||||
// successful call to rados_connect(), however if the connection has been
|
||||
// configured with non-default parameters, some of the parameters may be
|
||||
// allocated before connecting. rados_shutdown() will free the allocated
|
||||
// resources, even if there has not been a connection yet.
|
||||
//
|
||||
// This function is setup as a destructor/finalizer when rados_create() is
|
||||
// called.
|
||||
func freeConn(conn *Conn) {
|
||||
if conn.cluster != nil {
|
||||
log.Warnf("unreachable Conn object has not been shut down. Cleaning up.")
|
||||
C.rados_shutdown(conn.cluster)
|
||||
// prevent calling rados_shutdown() more than once
|
||||
conn.cluster = nil
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <stdlib.h>
|
||||
// #include <rados/librados.h>
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// GetAddrs returns the addresses of the RADOS session,
|
||||
// suitable for blocklisting.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_getaddrs(rados_t cluster, char **addrs)
|
||||
func (c *Conn) GetAddrs() (string, error) {
|
||||
var cAddrs *C.char
|
||||
defer C.free(unsafe.Pointer(cAddrs))
|
||||
|
||||
ret := C.rados_getaddrs(c.cluster, &cAddrs)
|
||||
if ret < 0 {
|
||||
return "", getError(ret)
|
||||
}
|
||||
|
||||
return C.GoString(cAddrs), nil
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
//go:build !mimic
|
||||
// +build !mimic
|
||||
|
||||
package rados
|
||||
|
||||
// #include <rados/librados.h>
|
||||
import "C"
|
||||
|
||||
const (
|
||||
// OpFlagFAdviseFUA optionally support FUA (force unit access) on write
|
||||
// requests.
|
||||
OpFlagFAdviseFUA = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_FUA)
|
||||
)
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <rados/librados.h>
|
||||
// #include <stdlib.h>
|
||||
//
|
||||
import "C"
|
||||
|
||||
// AssertVersion ensures that the object exists and that its internal version
|
||||
// number is equal to "ver" before reading. "ver" should be a version number
|
||||
// previously obtained with IOContext.GetLastVersion().
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void rados_read_op_assert_version(rados_read_op_t read_op,
|
||||
// uint64_t ver)
|
||||
func (r *ReadOp) AssertVersion(ver uint64) {
|
||||
C.rados_read_op_assert_version(r.op, C.uint64_t(ver))
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <rados/librados.h>
|
||||
// #include <stdlib.h>
|
||||
//
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// SetLocator sets the key for mapping objects to pgs within an io context.
|
||||
// Until a different locator key is set, all objects in this io context will be placed in the same pg.
|
||||
// To reset the locator, an empty string must be set.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void rados_ioctx_locator_set_key(rados_ioctx_t io, const char *key);
|
||||
func (ioctx *IOContext) SetLocator(locator string) {
|
||||
if locator == "" {
|
||||
C.rados_ioctx_locator_set_key(ioctx.ioctx, nil)
|
||||
} else {
|
||||
var cLoc *C.char = C.CString(locator)
|
||||
defer C.free(unsafe.Pointer(cLoc))
|
||||
C.rados_ioctx_locator_set_key(ioctx.ioctx, cLoc)
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <rados/librados.h>
|
||||
// #include <stdlib.h>
|
||||
//
|
||||
import "C"
|
||||
|
||||
// AssertVersion ensures that the object exists and that its internal version
|
||||
// number is equal to "ver" before writing. "ver" should be a version number
|
||||
// previously obtained with IOContext.GetLastVersion().
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void rados_read_op_assert_version(rados_read_op_t read_op,
|
||||
// uint64_t ver)
|
||||
func (w *WriteOp) AssertVersion(ver uint64) {
|
||||
C.rados_write_op_assert_version(w.op, C.uint64_t(ver))
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <rados/librados.h>
|
||||
// #include <stdlib.h>
|
||||
//
|
||||
import "C"
|
||||
|
||||
// Remove object.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void rados_write_op_remove(rados_write_op_t write_op)
|
||||
func (w *WriteOp) Remove() {
|
||||
C.rados_write_op_remove(w.op)
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <rados/librados.h>
|
||||
// #include <stdlib.h>
|
||||
//
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// SetXattr sets an xattr.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void rados_write_op_setxattr(rados_write_op_t write_op,
|
||||
// const char * name,
|
||||
// const char * value,
|
||||
// size_t value_len)
|
||||
func (w *WriteOp) SetXattr(name string, value []byte) {
|
||||
cName := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cName))
|
||||
|
||||
C.rados_write_op_setxattr(
|
||||
w.op,
|
||||
cName,
|
||||
(*C.char)(unsafe.Pointer(&value[0])),
|
||||
C.size_t(len(value)),
|
||||
)
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <errno.h>
|
||||
// #include <stdlib.h>
|
||||
// #include <rados/librados.h>
|
||||
//
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ReadOp manages a set of discrete object read actions that will be performed
|
||||
// together atomically.
|
||||
type ReadOp struct {
|
||||
operation
|
||||
op C.rados_read_op_t
|
||||
}
|
||||
|
||||
// CreateReadOp returns a newly constructed read operation.
|
||||
func CreateReadOp() *ReadOp {
|
||||
return &ReadOp{
|
||||
op: C.rados_create_read_op(),
|
||||
}
|
||||
}
|
||||
|
||||
// Release the resources associated with this read operation.
|
||||
func (r *ReadOp) Release() {
|
||||
C.rados_release_read_op(r.op)
|
||||
r.op = nil
|
||||
r.free()
|
||||
}
|
||||
|
||||
// Operate will perform the operation(s).
|
||||
func (r *ReadOp) Operate(ioctx *IOContext, oid string, flags OperationFlags) error {
|
||||
if err := ioctx.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cOid := C.CString(oid)
|
||||
defer C.free(unsafe.Pointer(cOid))
|
||||
|
||||
ret := C.rados_read_op_operate(r.op, ioctx.ioctx, cOid, C.int(flags))
|
||||
return r.update(readOp, ret)
|
||||
}
|
||||
|
||||
func (r *ReadOp) operateCompat(ioctx *IOContext, oid string) error {
|
||||
switch err := r.Operate(ioctx, oid, OperationNoFlag).(type) {
|
||||
case nil:
|
||||
return nil
|
||||
case OperationError:
|
||||
return err.OpError
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// AssertExists assures the object targeted by the read op exists.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void rados_read_op_assert_exists(rados_read_op_t read_op);
|
||||
func (r *ReadOp) AssertExists() {
|
||||
C.rados_read_op_assert_exists(r.op)
|
||||
}
|
||||
|
||||
// GetOmapValues is used to iterate over a set, or sub-set, of omap keys
|
||||
// as part of a read operation. An GetOmapStep is returned from this
|
||||
// function. The GetOmapStep may be used to iterate over the key-value
|
||||
// pairs after the Operate call has been performed.
|
||||
func (r *ReadOp) GetOmapValues(startAfter, filterPrefix string, maxReturn uint64) *GetOmapStep {
|
||||
gos := newGetOmapStep()
|
||||
r.steps = append(r.steps, gos)
|
||||
|
||||
cStartAfter := C.CString(startAfter)
|
||||
cFilterPrefix := C.CString(filterPrefix)
|
||||
defer C.free(unsafe.Pointer(cStartAfter))
|
||||
defer C.free(unsafe.Pointer(cFilterPrefix))
|
||||
|
||||
C.rados_read_op_omap_get_vals2(
|
||||
r.op,
|
||||
cStartAfter,
|
||||
cFilterPrefix,
|
||||
C.uint64_t(maxReturn),
|
||||
&gos.iter,
|
||||
gos.more,
|
||||
gos.rval,
|
||||
)
|
||||
return gos
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <stdlib.h>
|
||||
// #include <rados/librados.h>
|
||||
//
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ReadOpExecStep - step for exec operation code.
|
||||
type ReadOpExecStep struct {
|
||||
withoutFree
|
||||
|
||||
inBuffPtr *C.char
|
||||
inBuffLen C.size_t
|
||||
outBuffPtr *C.char
|
||||
outBuffLen C.size_t
|
||||
prval C.int
|
||||
canReadOutput bool
|
||||
}
|
||||
|
||||
// newExecStepOp - init new *execStepOp.
|
||||
func newReadOpExecStep(in []byte) *ReadOpExecStep {
|
||||
es := &ReadOpExecStep{
|
||||
outBuffPtr: nil,
|
||||
outBuffLen: 0,
|
||||
prval: 0,
|
||||
}
|
||||
|
||||
if len(in) > 0 {
|
||||
es.inBuffPtr = (*C.char)(unsafe.Pointer(&in[0]))
|
||||
es.inBuffLen = C.size_t(len(in))
|
||||
}
|
||||
|
||||
runtime.SetFinalizer(es, func(es *ReadOpExecStep) {
|
||||
if es != nil {
|
||||
es.freeBuffer()
|
||||
es = nil
|
||||
}
|
||||
})
|
||||
return es
|
||||
}
|
||||
|
||||
// freeBuffer - releases C allocated buffer. It is separated from es.free() because lifespan of C allocated buffer is
|
||||
// longer than lifespan of read operation.
|
||||
func (es *ReadOpExecStep) freeBuffer() {
|
||||
if es.outBuffPtr != nil {
|
||||
C.free(unsafe.Pointer(es.outBuffPtr))
|
||||
es.outBuffPtr = nil
|
||||
es.canReadOutput = false
|
||||
}
|
||||
}
|
||||
|
||||
// update - update state operation.
|
||||
func (es *ReadOpExecStep) update() error {
|
||||
err := getError(es.prval)
|
||||
es.canReadOutput = err == nil
|
||||
return err
|
||||
}
|
||||
|
||||
// Bytes returns the result of the executed command as a byte slice.
|
||||
func (es *ReadOpExecStep) Bytes() ([]byte, error) {
|
||||
if !es.canReadOutput {
|
||||
return nil, ErrOperationIncomplete
|
||||
}
|
||||
|
||||
return C.GoBytes(unsafe.Pointer(es.outBuffPtr), C.int(es.outBuffLen)), nil
|
||||
}
|
||||
|
||||
// Exec executes an OSD class method on an object.
|
||||
// See rados_exec() in the RADOS C api documentation for a general description.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void rados_read_op_exec(rados_read_op_t read_op,
|
||||
// const char *cls,
|
||||
// const char *method,
|
||||
// const char *in_buf,
|
||||
// size_t in_len,
|
||||
// char **out_buf,
|
||||
// size_t *out_len,
|
||||
// int *prval);
|
||||
func (r *ReadOp) Exec(clsName, method string, in []byte) *ReadOpExecStep {
|
||||
cClsName := C.CString(clsName)
|
||||
defer C.free(unsafe.Pointer(cClsName))
|
||||
|
||||
cMethod := C.CString(method)
|
||||
defer C.free(unsafe.Pointer(cMethod))
|
||||
|
||||
es := newReadOpExecStep(in)
|
||||
r.steps = append(r.steps, es)
|
||||
C.rados_read_op_exec(
|
||||
r.op,
|
||||
cClsName,
|
||||
cMethod,
|
||||
es.inBuffPtr,
|
||||
es.inBuffLen,
|
||||
&es.outBuffPtr,
|
||||
&es.outBuffLen,
|
||||
&es.prval,
|
||||
)
|
||||
|
||||
return es
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <rados/librados.h>
|
||||
// #include <stdlib.h>
|
||||
//
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/ceph/go-ceph/internal/cutil"
|
||||
)
|
||||
|
||||
// ReadOpOmapGetValsByKeysStep holds the result of the
|
||||
// GetOmapValuesByKeys read operation.
|
||||
// Result is valid only after Operate() was called.
|
||||
type ReadOpOmapGetValsByKeysStep struct {
|
||||
// C arguments
|
||||
|
||||
iter C.rados_omap_iter_t
|
||||
prval *C.int
|
||||
|
||||
// Internal state
|
||||
|
||||
// canIterate is only set after the operation is performed and is
|
||||
// intended to prevent premature fetching of data.
|
||||
canIterate bool
|
||||
}
|
||||
|
||||
func newReadOpOmapGetValsByKeysStep() *ReadOpOmapGetValsByKeysStep {
|
||||
s := &ReadOpOmapGetValsByKeysStep{
|
||||
prval: (*C.int)(C.malloc(C.sizeof_int)),
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *ReadOpOmapGetValsByKeysStep) free() {
|
||||
s.canIterate = false
|
||||
C.rados_omap_get_end(s.iter)
|
||||
|
||||
C.free(unsafe.Pointer(s.prval))
|
||||
s.prval = nil
|
||||
}
|
||||
|
||||
func (s *ReadOpOmapGetValsByKeysStep) update() error {
|
||||
err := getError(*s.prval)
|
||||
s.canIterate = (err == nil)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Next gets the next omap key/value pair referenced by
|
||||
// ReadOpOmapGetValsByKeysStep's internal iterator.
|
||||
// If there are no more elements to retrieve, (nil, nil) is returned.
|
||||
// May be called only after Operate() finished.
|
||||
func (s *ReadOpOmapGetValsByKeysStep) Next() (*OmapKeyValue, error) {
|
||||
if !s.canIterate {
|
||||
return nil, ErrOperationIncomplete
|
||||
}
|
||||
|
||||
var (
|
||||
cKey *C.char
|
||||
cVal *C.char
|
||||
cKeyLen C.size_t
|
||||
cValLen C.size_t
|
||||
)
|
||||
|
||||
ret := C.rados_omap_get_next2(s.iter, &cKey, &cVal, &cKeyLen, &cValLen)
|
||||
if ret != 0 {
|
||||
return nil, getError(ret)
|
||||
}
|
||||
|
||||
if cKey == nil {
|
||||
// Iterator has reached the end of the list.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &OmapKeyValue{
|
||||
Key: string(C.GoBytes(unsafe.Pointer(cKey), C.int(cKeyLen))),
|
||||
Value: C.GoBytes(unsafe.Pointer(cVal), C.int(cValLen)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetOmapValuesByKeys starts iterating over specific key/value pairs.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void rados_read_op_omap_get_vals_by_keys2(rados_read_op_t read_op,
|
||||
// char const * const * keys,
|
||||
// size_t num_keys,
|
||||
// const size_t * key_lens,
|
||||
// rados_omap_iter_t * iter,
|
||||
// int * prval)
|
||||
func (r *ReadOp) GetOmapValuesByKeys(keys []string) *ReadOpOmapGetValsByKeysStep {
|
||||
s := newReadOpOmapGetValsByKeysStep()
|
||||
r.steps = append(r.steps, s)
|
||||
|
||||
cKeys := cutil.NewBufferGroupStrings(keys)
|
||||
defer cKeys.Free()
|
||||
|
||||
C.rados_read_op_omap_get_vals_by_keys2(
|
||||
r.op,
|
||||
(**C.char)(cKeys.BuffersPtr()),
|
||||
C.size_t(len(keys)),
|
||||
(*C.size_t)(cKeys.LengthsPtr()),
|
||||
&s.iter,
|
||||
s.prval,
|
||||
)
|
||||
|
||||
return s
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <rados/librados.h>
|
||||
// #include <stdlib.h>
|
||||
//
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ReadOpReadStep holds the result of the Read read operation.
|
||||
// Result is valid only after Operate() was called.
|
||||
type ReadOpReadStep struct {
|
||||
// C returned data:
|
||||
bytesRead *C.size_t
|
||||
prval *C.int
|
||||
|
||||
BytesRead int64 // Bytes read by this action.
|
||||
Result int // Result of this action.
|
||||
}
|
||||
|
||||
func (s *ReadOpReadStep) update() error {
|
||||
s.BytesRead = (int64)(*s.bytesRead)
|
||||
s.Result = (int)(*s.prval)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ReadOpReadStep) free() {
|
||||
C.free(unsafe.Pointer(s.bytesRead))
|
||||
C.free(unsafe.Pointer(s.prval))
|
||||
|
||||
s.bytesRead = nil
|
||||
s.prval = nil
|
||||
}
|
||||
|
||||
func newReadOpReadStep() *ReadOpReadStep {
|
||||
return &ReadOpReadStep{
|
||||
bytesRead: (*C.size_t)(C.malloc(C.sizeof_size_t)),
|
||||
prval: (*C.int)(C.malloc(C.sizeof_int)),
|
||||
}
|
||||
}
|
||||
|
||||
// Read bytes from offset into buffer.
|
||||
// len(buffer) is the maximum number of bytes read from the object.
|
||||
// buffer[:ReadOpReadStep.BytesRead] then contains object data.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void rados_read_op_read(rados_read_op_t read_op,
|
||||
// uint64_t offset,
|
||||
// size_t len,
|
||||
// char * buffer,
|
||||
// size_t * bytes_read,
|
||||
// int * prval)
|
||||
func (r *ReadOp) Read(offset uint64, buffer []byte) *ReadOpReadStep {
|
||||
oe := newReadStep(buffer, offset)
|
||||
readStep := newReadOpReadStep()
|
||||
r.steps = append(r.steps, oe, readStep)
|
||||
C.rados_read_op_read(
|
||||
r.op,
|
||||
oe.cOffset,
|
||||
oe.cReadLen,
|
||||
oe.cBuffer,
|
||||
readStep.bytesRead,
|
||||
readStep.prval,
|
||||
)
|
||||
|
||||
return readStep
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package rados
|
||||
|
||||
// #include <stdint.h>
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type readStep struct {
|
||||
withoutUpdate
|
||||
withoutFree
|
||||
// the c pointer utilizes the Go byteslice data and no free is needed
|
||||
|
||||
// inputs:
|
||||
b []byte
|
||||
|
||||
// arguments:
|
||||
cBuffer *C.char
|
||||
cReadLen C.size_t
|
||||
cOffset C.uint64_t
|
||||
}
|
||||
|
||||
func newReadStep(b []byte, offset uint64) *readStep {
|
||||
return &readStep{
|
||||
b: b,
|
||||
cBuffer: (*C.char)(unsafe.Pointer(&b[0])), // TODO: must be pinned
|
||||
cReadLen: C.size_t(len(b)),
|
||||
cOffset: C.uint64_t(offset),
|
||||
}
|
||||
}
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <stdlib.h>
|
||||
// #include <rados/librados.h>
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ceph/go-ceph/internal/retry"
|
||||
)
|
||||
|
||||
// CreateSnap creates a pool-wide snapshot.
|
||||
//
|
||||
// Implements:
|
||||
// int rados_ioctx_snap_create(rados_ioctx_t io, const char *snapname)
|
||||
func (ioctx *IOContext) CreateSnap(snapName string) error {
|
||||
if err := ioctx.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cSnapName := C.CString(snapName)
|
||||
defer C.free(unsafe.Pointer(cSnapName))
|
||||
|
||||
ret := C.rados_ioctx_snap_create(ioctx.ioctx, cSnapName)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// RemoveSnap deletes the pool snapshot.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_ioctx_snap_remove(rados_ioctx_t io, const char *snapname)
|
||||
func (ioctx *IOContext) RemoveSnap(snapName string) error {
|
||||
if err := ioctx.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cSnapName := C.CString(snapName)
|
||||
defer C.free(unsafe.Pointer(cSnapName))
|
||||
|
||||
ret := C.rados_ioctx_snap_remove(ioctx.ioctx, cSnapName)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// SnapID represents the ID of a rados snapshot.
|
||||
type SnapID C.rados_snap_t
|
||||
|
||||
// LookupSnap returns the ID of a pool snapshot.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_ioctx_snap_lookup(rados_ioctx_t io, const char *name, rados_snap_t *id)
|
||||
func (ioctx *IOContext) LookupSnap(snapName string) (SnapID, error) {
|
||||
var snapID SnapID
|
||||
|
||||
if err := ioctx.validate(); err != nil {
|
||||
return snapID, err
|
||||
}
|
||||
|
||||
cSnapName := C.CString(snapName)
|
||||
defer C.free(unsafe.Pointer(cSnapName))
|
||||
|
||||
ret := C.rados_ioctx_snap_lookup(
|
||||
ioctx.ioctx,
|
||||
cSnapName,
|
||||
(*C.rados_snap_t)(&snapID))
|
||||
return snapID, getError(ret)
|
||||
}
|
||||
|
||||
// GetSnapName returns the name of a pool snapshot with the given snapshot ID.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_ioctx_snap_get_name(rados_ioctx_t io, rados_snap_t id, char *name, int maxlen)
|
||||
func (ioctx *IOContext) GetSnapName(snapID SnapID) (string, error) {
|
||||
if err := ioctx.validate(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var (
|
||||
buf []byte
|
||||
err error
|
||||
)
|
||||
// range from 1k to 64KiB
|
||||
retry.WithSizes(1024, 1<<16, func(length int) retry.Hint {
|
||||
cLen := C.int(length)
|
||||
buf = make([]byte, cLen)
|
||||
ret := C.rados_ioctx_snap_get_name(
|
||||
ioctx.ioctx,
|
||||
(C.rados_snap_t)(snapID),
|
||||
(*C.char)(unsafe.Pointer(&buf[0])),
|
||||
cLen)
|
||||
err = getError(ret)
|
||||
return retry.Size(int(cLen)).If(err == errRange)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return C.GoString((*C.char)(unsafe.Pointer(&buf[0]))), nil
|
||||
}
|
||||
|
||||
// GetSnapStamp returns the time of the pool snapshot creation.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_ioctx_snap_get_stamp(rados_ioctx_t io, rados_snap_t id, time_t *t)
|
||||
func (ioctx *IOContext) GetSnapStamp(snapID SnapID) (time.Time, error) {
|
||||
var cTime C.time_t
|
||||
|
||||
if err := ioctx.validate(); err != nil {
|
||||
return time.Unix(int64(cTime), 0), err
|
||||
}
|
||||
|
||||
ret := C.rados_ioctx_snap_get_stamp(
|
||||
ioctx.ioctx,
|
||||
(C.rados_snap_t)(snapID),
|
||||
&cTime)
|
||||
return time.Unix(int64(cTime), 0), getError(ret)
|
||||
}
|
||||
|
||||
// ListSnaps returns a slice containing the SnapIDs of existing pool snapshots.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_ioctx_snap_list(rados_ioctx_t io, rados_snap_t *snaps, int maxlen)
|
||||
func (ioctx *IOContext) ListSnaps() ([]SnapID, error) {
|
||||
if err := ioctx.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
snapList []SnapID
|
||||
cLen C.int
|
||||
err error
|
||||
ret C.int
|
||||
)
|
||||
retry.WithSizes(100, 1000, func(maxlen int) retry.Hint {
|
||||
cLen = C.int(maxlen)
|
||||
snapList = make([]SnapID, cLen)
|
||||
ret = C.rados_ioctx_snap_list(
|
||||
ioctx.ioctx,
|
||||
(*C.rados_snap_t)(unsafe.Pointer(&snapList[0])),
|
||||
cLen)
|
||||
err = getErrorIfNegative(ret)
|
||||
return retry.Size(int(cLen)).If(err == errRange)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return snapList[:ret], nil
|
||||
}
|
||||
|
||||
// RollbackSnap rollbacks the object with key oID to the pool snapshot.
|
||||
// The contents of the object will be the same as when the snapshot was taken.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_ioctx_snap_rollback(rados_ioctx_t io, const char *oid, const char *snapname);
|
||||
func (ioctx *IOContext) RollbackSnap(oid, snapName string) error {
|
||||
if err := ioctx.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
coid := C.CString(oid)
|
||||
defer C.free(unsafe.Pointer(coid))
|
||||
cSnapName := C.CString(snapName)
|
||||
defer C.free(unsafe.Pointer(cSnapName))
|
||||
|
||||
ret := C.rados_ioctx_snap_rollback(ioctx.ioctx, coid, cSnapName)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// SnapHead is the representation of LIBRADOS_SNAP_HEAD from librados.
|
||||
// SnapHead can be used to reset the IOContext to stop reading from a snapshot.
|
||||
const SnapHead = SnapID(C.LIBRADOS_SNAP_HEAD)
|
||||
|
||||
// SetReadSnap sets the snapshot from which reads are performed.
|
||||
// Subsequent reads will return data as it was at the time of that snapshot.
|
||||
// Pass SnapHead for no snapshot (i.e. normal operation).
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void rados_ioctx_snap_set_read(rados_ioctx_t io, rados_snap_t snap);
|
||||
func (ioctx *IOContext) SetReadSnap(snapID SnapID) error {
|
||||
if err := ioctx.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
C.rados_ioctx_snap_set_read(ioctx.ioctx, (C.rados_snap_t)(snapID))
|
||||
return nil
|
||||
}
|
||||
+375
@@ -0,0 +1,375 @@
|
||||
package rados
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lrados
|
||||
#include <stdlib.h>
|
||||
#include <rados/librados.h>
|
||||
extern void watchNotifyCb(void*, uint64_t, uint64_t, uint64_t, void*, size_t);
|
||||
extern void watchErrorCb(void*, uint64_t, int);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ceph/go-ceph/internal/log"
|
||||
)
|
||||
|
||||
type (
|
||||
// WatcherID is the unique id of a Watcher.
|
||||
WatcherID uint64
|
||||
// NotifyID is the unique id of a NotifyEvent.
|
||||
NotifyID uint64
|
||||
// NotifierID is the unique id of a notifying client.
|
||||
NotifierID uint64
|
||||
)
|
||||
|
||||
// NotifyEvent is received by a watcher for each notification.
|
||||
type NotifyEvent struct {
|
||||
ID NotifyID
|
||||
WatcherID WatcherID
|
||||
NotifierID NotifierID
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// NotifyAck represents an acknowleged notification.
|
||||
type NotifyAck struct {
|
||||
WatcherID WatcherID
|
||||
NotifierID NotifierID
|
||||
Response []byte
|
||||
}
|
||||
|
||||
// NotifyTimeout represents an unacknowleged notification.
|
||||
type NotifyTimeout struct {
|
||||
WatcherID WatcherID
|
||||
NotifierID NotifierID
|
||||
}
|
||||
|
||||
// Watcher receives all notifications for certain object.
|
||||
type Watcher struct {
|
||||
id WatcherID
|
||||
oid string
|
||||
ioctx *IOContext
|
||||
events chan NotifyEvent
|
||||
errors chan error
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
var (
|
||||
watchers = map[WatcherID]*Watcher{}
|
||||
watchersMtx sync.RWMutex
|
||||
)
|
||||
|
||||
// Watch creates a Watcher for the specified object.
|
||||
//
|
||||
// A Watcher receives all notifications that are sent to the object on which it
|
||||
// has been created. It exposes two read-only channels: Events() receives all
|
||||
// the NotifyEvents and Errors() receives all occuring errors. A typical code
|
||||
// creating a Watcher could look like this:
|
||||
//
|
||||
// watcher, err := ioctx.Watch(oid)
|
||||
// go func() { // event handler
|
||||
// for ne := range watcher.Events() {
|
||||
// ...
|
||||
// ne.Ack([]byte("response data..."))
|
||||
// ...
|
||||
// }
|
||||
// }()
|
||||
// go func() { // error handler
|
||||
// for err := range watcher.Errors() {
|
||||
// ... handle err ...
|
||||
// }
|
||||
// }()
|
||||
//
|
||||
// CAUTION: the Watcher references the IOContext in which it has been created.
|
||||
// Therefore all watchers must be deleted with the Delete() method before the
|
||||
// IOContext is being destroyed.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_watch2(rados_ioctx_t io, const char* o, uint64_t* cookie,
|
||||
// rados_watchcb2_t watchcb, rados_watcherrcb_t watcherrcb, void* arg)
|
||||
func (ioctx *IOContext) Watch(obj string) (*Watcher, error) {
|
||||
return ioctx.WatchWithTimeout(obj, 0)
|
||||
}
|
||||
|
||||
// WatchWithTimeout creates a watcher on an object. Same as Watcher(), but
|
||||
// different timeout than the default can be specified.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_watch3(rados_ioctx_t io, const char *o, uint64_t *cookie,
|
||||
// rados_watchcb2_t watchcb, rados_watcherrcb_t watcherrcb, uint32_t timeout,
|
||||
// void *arg);
|
||||
func (ioctx *IOContext) WatchWithTimeout(oid string, timeout time.Duration) (*Watcher, error) {
|
||||
cObj := C.CString(oid)
|
||||
defer C.free(unsafe.Pointer(cObj))
|
||||
var id C.uint64_t
|
||||
watchersMtx.Lock()
|
||||
defer watchersMtx.Unlock()
|
||||
ret := C.rados_watch3(
|
||||
ioctx.ioctx,
|
||||
cObj,
|
||||
&id,
|
||||
(C.rados_watchcb2_t)(C.watchNotifyCb),
|
||||
(C.rados_watcherrcb_t)(C.watchErrorCb),
|
||||
C.uint32_t(timeout.Milliseconds()/1000),
|
||||
nil,
|
||||
)
|
||||
if err := getError(ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
evCh := make(chan NotifyEvent)
|
||||
errCh := make(chan error)
|
||||
w := &Watcher{
|
||||
id: WatcherID(id),
|
||||
ioctx: ioctx,
|
||||
oid: oid,
|
||||
events: evCh,
|
||||
errors: errCh,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
watchers[WatcherID(id)] = w
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// ID returns the WatcherId of the Watcher
|
||||
func (w *Watcher) ID() WatcherID {
|
||||
return w.id
|
||||
}
|
||||
|
||||
// Events returns a read-only channel, that receives all notifications that are
|
||||
// sent to the object of the Watcher.
|
||||
func (w *Watcher) Events() <-chan NotifyEvent {
|
||||
return w.events
|
||||
}
|
||||
|
||||
// Errors returns a read-only channel, that receives all errors for the Watcher.
|
||||
func (w *Watcher) Errors() <-chan error {
|
||||
return w.errors
|
||||
}
|
||||
|
||||
// Check on the status of a Watcher.
|
||||
//
|
||||
// Returns the time since it was last confirmed. If there is an error, the
|
||||
// Watcher is no longer valid, and should be destroyed with the Delete() method.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_watch_check(rados_ioctx_t io, uint64_t cookie)
|
||||
func (w *Watcher) Check() (time.Duration, error) {
|
||||
ret := C.rados_watch_check(w.ioctx.ioctx, C.uint64_t(w.id))
|
||||
if ret < 0 {
|
||||
return 0, getError(ret)
|
||||
}
|
||||
return time.Millisecond * time.Duration(ret), nil
|
||||
}
|
||||
|
||||
// Delete the watcher. This closes both the event and error channel.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_unwatch2(rados_ioctx_t io, uint64_t cookie)
|
||||
func (w *Watcher) Delete() error {
|
||||
watchersMtx.Lock()
|
||||
_, ok := watchers[w.id]
|
||||
if ok {
|
||||
delete(watchers, w.id)
|
||||
}
|
||||
watchersMtx.Unlock()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
ret := C.rados_unwatch2(w.ioctx.ioctx, C.uint64_t(w.id))
|
||||
if ret != 0 {
|
||||
return getError(ret)
|
||||
}
|
||||
close(w.done) // unblock blocked callbacks
|
||||
close(w.events)
|
||||
close(w.errors)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Notify sends a notification with the provided data to all Watchers of the
|
||||
// specified object.
|
||||
//
|
||||
// CAUTION: even if the error is not nil. the returned slices
|
||||
// might still contain data.
|
||||
func (ioctx *IOContext) Notify(obj string, data []byte) ([]NotifyAck, []NotifyTimeout, error) {
|
||||
return ioctx.NotifyWithTimeout(obj, data, 0)
|
||||
}
|
||||
|
||||
// NotifyWithTimeout is like Notify() but with a different timeout than the
|
||||
// default.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_notify2(rados_ioctx_t io, const char* o, const char* buf, int buf_len,
|
||||
// uint64_t timeout_ms, char** reply_buffer, size_t* reply_buffer_len)
|
||||
func (ioctx *IOContext) NotifyWithTimeout(obj string, data []byte, timeout time.Duration) ([]NotifyAck,
|
||||
[]NotifyTimeout, error) {
|
||||
cObj := C.CString(obj)
|
||||
defer C.free(unsafe.Pointer(cObj))
|
||||
var cResponse *C.char
|
||||
defer C.rados_buffer_free(cResponse)
|
||||
var responseLen C.size_t
|
||||
var dataPtr *C.char
|
||||
if len(data) > 0 {
|
||||
dataPtr = (*C.char)(unsafe.Pointer(&data[0]))
|
||||
}
|
||||
ret := C.rados_notify2(
|
||||
ioctx.ioctx,
|
||||
cObj,
|
||||
dataPtr,
|
||||
C.int(len(data)),
|
||||
C.uint64_t(timeout.Milliseconds()),
|
||||
&cResponse,
|
||||
&responseLen,
|
||||
)
|
||||
// cResponse has been set even if an error is returned, so we decode it anyway
|
||||
acks, timeouts := decodeNotifyResponse(cResponse, responseLen)
|
||||
return acks, timeouts, getError(ret)
|
||||
}
|
||||
|
||||
// Ack sends an acknowledgement with the specified response data to the notfier
|
||||
// of the NotifyEvent. If a notify is not ack'ed, the originating Notify() call
|
||||
// blocks and eventiually times out.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_notify_ack(rados_ioctx_t io, const char *o, uint64_t notify_id,
|
||||
// uint64_t cookie, const char *buf, int buf_len)
|
||||
func (ne *NotifyEvent) Ack(response []byte) error {
|
||||
watchersMtx.RLock()
|
||||
w, ok := watchers[ne.WatcherID]
|
||||
watchersMtx.RUnlock()
|
||||
if !ok {
|
||||
return fmt.Errorf("can't ack on deleted watcher %v", ne.WatcherID)
|
||||
}
|
||||
cOID := C.CString(w.oid)
|
||||
defer C.free(unsafe.Pointer(cOID))
|
||||
var respPtr *C.char
|
||||
if len(response) > 0 {
|
||||
respPtr = (*C.char)(unsafe.Pointer(&response[0]))
|
||||
}
|
||||
ret := C.rados_notify_ack(
|
||||
w.ioctx.ioctx,
|
||||
cOID,
|
||||
C.uint64_t(ne.ID),
|
||||
C.uint64_t(ne.WatcherID),
|
||||
respPtr,
|
||||
C.int(len(response)),
|
||||
)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// WatcherFlush flushes all pending notifications of the cluster.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// int rados_watch_flush(rados_t cluster)
|
||||
func (c *Conn) WatcherFlush() error {
|
||||
if !c.connected {
|
||||
return ErrNotConnected
|
||||
}
|
||||
ret := C.rados_watch_flush(c.cluster)
|
||||
return getError(ret)
|
||||
}
|
||||
|
||||
// decoder for this notify response format:
|
||||
//
|
||||
// le32 num_acks
|
||||
// {
|
||||
// le64 gid global id for the client (for client.1234 that's 1234)
|
||||
// le64 cookie cookie for the client
|
||||
// le32 buflen length of reply message buffer
|
||||
// u8 buflen payload
|
||||
// } num_acks
|
||||
// le32 num_timeouts
|
||||
// {
|
||||
// le64 gid global id for the client
|
||||
// le64 cookie cookie for the client
|
||||
// } num_timeouts
|
||||
//
|
||||
// NOTE: starting with pacific this is implemented as a C function and this can
|
||||
// be replaced later
|
||||
func decodeNotifyResponse(response *C.char, length C.size_t) ([]NotifyAck, []NotifyTimeout) {
|
||||
if length == 0 || response == nil {
|
||||
return nil, nil
|
||||
}
|
||||
b := (*[math.MaxInt32]byte)(unsafe.Pointer(response))[:length:length]
|
||||
pos := 0
|
||||
|
||||
num := binary.LittleEndian.Uint32(b[pos:])
|
||||
pos += 4
|
||||
acks := make([]NotifyAck, num)
|
||||
for i := range acks {
|
||||
acks[i].NotifierID = NotifierID(binary.LittleEndian.Uint64(b[pos:]))
|
||||
pos += 8
|
||||
acks[i].WatcherID = WatcherID(binary.LittleEndian.Uint64(b[pos:]))
|
||||
pos += 8
|
||||
dataLen := binary.LittleEndian.Uint32(b[pos:])
|
||||
pos += 4
|
||||
if dataLen > 0 {
|
||||
acks[i].Response = C.GoBytes(unsafe.Pointer(&b[pos]), C.int(dataLen))
|
||||
pos += int(dataLen)
|
||||
}
|
||||
}
|
||||
|
||||
num = binary.LittleEndian.Uint32(b[pos:])
|
||||
pos += 4
|
||||
timeouts := make([]NotifyTimeout, num)
|
||||
for i := range timeouts {
|
||||
timeouts[i].NotifierID = NotifierID(binary.LittleEndian.Uint64(b[pos:]))
|
||||
pos += 8
|
||||
timeouts[i].WatcherID = WatcherID(binary.LittleEndian.Uint64(b[pos:]))
|
||||
pos += 8
|
||||
}
|
||||
return acks, timeouts
|
||||
}
|
||||
|
||||
//export watchNotifyCb
|
||||
func watchNotifyCb(_ unsafe.Pointer, notifyID C.uint64_t, id C.uint64_t,
|
||||
notifierID C.uint64_t, cData unsafe.Pointer, dataLen C.size_t) {
|
||||
ev := NotifyEvent{
|
||||
ID: NotifyID(notifyID),
|
||||
WatcherID: WatcherID(id),
|
||||
NotifierID: NotifierID(notifierID),
|
||||
}
|
||||
if dataLen > 0 {
|
||||
ev.Data = C.GoBytes(cData, C.int(dataLen))
|
||||
}
|
||||
watchersMtx.RLock()
|
||||
w, ok := watchers[WatcherID(id)]
|
||||
watchersMtx.RUnlock()
|
||||
if !ok {
|
||||
// usually this should not happen, but who knows
|
||||
log.Warnf("received notification for unknown watcher ID: %#v", ev)
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-w.done: // unblock when deleted
|
||||
case w.events <- ev:
|
||||
}
|
||||
}
|
||||
|
||||
//export watchErrorCb
|
||||
func watchErrorCb(_ unsafe.Pointer, id C.uint64_t, err C.int) {
|
||||
watchersMtx.RLock()
|
||||
w, ok := watchers[WatcherID(id)]
|
||||
watchersMtx.RUnlock()
|
||||
if !ok {
|
||||
// usually this should not happen, but who knows
|
||||
log.Warnf("received error for unknown watcher ID: id=%d err=%#v", id, err)
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-w.done: // unblock when deleted
|
||||
case w.errors <- getError(err):
|
||||
}
|
||||
}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <errno.h>
|
||||
// #include <stdlib.h>
|
||||
// #include <rados/librados.h>
|
||||
//
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/ceph/go-ceph/internal/cutil"
|
||||
ts "github.com/ceph/go-ceph/internal/timespec"
|
||||
)
|
||||
|
||||
// Timespec is a public type for the internal C 'struct timespec'
|
||||
type Timespec ts.Timespec
|
||||
|
||||
// WriteOp manages a set of discrete actions that will be performed together
|
||||
// atomically.
|
||||
type WriteOp struct {
|
||||
operation
|
||||
op C.rados_write_op_t
|
||||
}
|
||||
|
||||
// CreateWriteOp returns a newly constructed write operation.
|
||||
func CreateWriteOp() *WriteOp {
|
||||
return &WriteOp{
|
||||
op: C.rados_create_write_op(),
|
||||
}
|
||||
}
|
||||
|
||||
// Release the resources associated with this write operation.
|
||||
func (w *WriteOp) Release() {
|
||||
C.rados_release_write_op(w.op)
|
||||
w.op = nil
|
||||
w.free()
|
||||
}
|
||||
|
||||
func (w WriteOp) operate2(
|
||||
ioctx *IOContext, oid string, mtime *Timespec, flags OperationFlags) error {
|
||||
|
||||
if err := ioctx.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cOid := C.CString(oid)
|
||||
defer C.free(unsafe.Pointer(cOid))
|
||||
var cMtime *C.struct_timespec
|
||||
if mtime != nil {
|
||||
cMtime = &C.struct_timespec{}
|
||||
ts.CopyToCStruct(
|
||||
ts.Timespec(*mtime),
|
||||
ts.CTimespecPtr(cMtime))
|
||||
}
|
||||
|
||||
ret := C.rados_write_op_operate2(
|
||||
w.op, ioctx.ioctx, cOid, cMtime, C.int(flags))
|
||||
return w.update(writeOp, ret)
|
||||
}
|
||||
|
||||
// Operate will perform the operation(s).
|
||||
func (w *WriteOp) Operate(ioctx *IOContext, oid string, flags OperationFlags) error {
|
||||
return w.operate2(ioctx, oid, nil, flags)
|
||||
}
|
||||
|
||||
// OperateWithMtime will perform the operation while setting the modification
|
||||
// time stamp to the supplied value.
|
||||
func (w *WriteOp) OperateWithMtime(
|
||||
ioctx *IOContext, oid string, mtime Timespec, flags OperationFlags) error {
|
||||
|
||||
return w.operate2(ioctx, oid, &mtime, flags)
|
||||
}
|
||||
|
||||
func (w *WriteOp) operateCompat(ioctx *IOContext, oid string) error {
|
||||
switch err := w.Operate(ioctx, oid, OperationNoFlag).(type) {
|
||||
case nil:
|
||||
return nil
|
||||
case OperationError:
|
||||
return err.OpError
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Create a rados object.
|
||||
func (w *WriteOp) Create(exclusive CreateOption) {
|
||||
// category, the 3rd param, is deprecated and has no effect so we do not
|
||||
// implement it in go-ceph
|
||||
C.rados_write_op_create(w.op, C.int(exclusive), nil)
|
||||
}
|
||||
|
||||
// SetOmap appends the map `pairs` to the omap `oid`.
|
||||
func (w *WriteOp) SetOmap(pairs map[string][]byte) {
|
||||
keys := make([]string, len(pairs))
|
||||
values := make([][]byte, len(pairs))
|
||||
idx := 0
|
||||
for k, v := range pairs {
|
||||
keys[idx] = k
|
||||
values[idx] = v
|
||||
idx++
|
||||
}
|
||||
|
||||
cKeys := cutil.NewBufferGroupStrings(keys)
|
||||
cValues := cutil.NewBufferGroupBytes(values)
|
||||
defer cKeys.Free()
|
||||
defer cValues.Free()
|
||||
|
||||
C.rados_write_op_omap_set2(
|
||||
w.op,
|
||||
(**C.char)(cKeys.BuffersPtr()),
|
||||
(**C.char)(cValues.BuffersPtr()),
|
||||
(*C.size_t)(cKeys.LengthsPtr()),
|
||||
(*C.size_t)(cValues.LengthsPtr()),
|
||||
(C.size_t)(len(pairs)))
|
||||
}
|
||||
|
||||
// RmOmapKeys removes the specified `keys` from the omap `oid`.
|
||||
func (w *WriteOp) RmOmapKeys(keys []string) {
|
||||
cKeys := cutil.NewBufferGroupStrings(keys)
|
||||
defer cKeys.Free()
|
||||
|
||||
C.rados_write_op_omap_rm_keys2(
|
||||
w.op,
|
||||
(**C.char)(cKeys.BuffersPtr()),
|
||||
(*C.size_t)(cKeys.LengthsPtr()),
|
||||
(C.size_t)(len(keys)))
|
||||
}
|
||||
|
||||
// CleanOmap clears the omap `oid`.
|
||||
func (w *WriteOp) CleanOmap() {
|
||||
C.rados_write_op_omap_clear(w.op)
|
||||
}
|
||||
|
||||
// AssertExists assures the object targeted by the write op exists.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void rados_write_op_assert_exists(rados_write_op_t write_op);
|
||||
func (w *WriteOp) AssertExists() {
|
||||
C.rados_write_op_assert_exists(w.op)
|
||||
}
|
||||
|
||||
// Write a given byte slice at the supplied offset.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void rados_write_op_write(rados_write_op_t write_op,
|
||||
// const char *buffer,
|
||||
// size_t len,
|
||||
// uint64_t offset);
|
||||
func (w *WriteOp) Write(b []byte, offset uint64) {
|
||||
oe := newWriteStep(b, 0, offset)
|
||||
w.steps = append(w.steps, oe)
|
||||
C.rados_write_op_write(
|
||||
w.op,
|
||||
oe.cBuffer,
|
||||
oe.cDataLen,
|
||||
oe.cOffset)
|
||||
}
|
||||
|
||||
// WriteFull writes a given byte slice as the whole object,
|
||||
// atomically replacing it.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void rados_write_op_write_full(rados_write_op_t write_op,
|
||||
// const char *buffer,
|
||||
// size_t len);
|
||||
func (w *WriteOp) WriteFull(b []byte) {
|
||||
oe := newWriteStep(b, 0, 0)
|
||||
w.steps = append(w.steps, oe)
|
||||
C.rados_write_op_write_full(
|
||||
w.op,
|
||||
oe.cBuffer,
|
||||
oe.cDataLen)
|
||||
}
|
||||
|
||||
// WriteSame write a given byte slice to the object multiple times, until
|
||||
// writeLen is satisfied.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void rados_write_op_writesame(rados_write_op_t write_op,
|
||||
// const char *buffer,
|
||||
// size_t data_len,
|
||||
// size_t write_len,
|
||||
// uint64_t offset);
|
||||
func (w *WriteOp) WriteSame(b []byte, writeLen, offset uint64) {
|
||||
oe := newWriteStep(b, writeLen, offset)
|
||||
w.steps = append(w.steps, oe)
|
||||
C.rados_write_op_writesame(
|
||||
w.op,
|
||||
oe.cBuffer,
|
||||
oe.cDataLen,
|
||||
oe.cWriteLen,
|
||||
oe.cOffset)
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package rados
|
||||
|
||||
// #cgo LDFLAGS: -lrados
|
||||
// #include <rados/librados.h>
|
||||
// #include <stdlib.h>
|
||||
//
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// WriteOpCmpExtStep holds result of the CmpExt write operation.
|
||||
// Result is valid only after Operate() was called.
|
||||
type WriteOpCmpExtStep struct {
|
||||
// C returned data:
|
||||
prval *C.int
|
||||
|
||||
// Result of the CmpExt write operation.
|
||||
Result int
|
||||
}
|
||||
|
||||
func (s *WriteOpCmpExtStep) update() error {
|
||||
s.Result = int(*s.prval)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *WriteOpCmpExtStep) free() {
|
||||
C.free(unsafe.Pointer(s.prval))
|
||||
s.prval = nil
|
||||
}
|
||||
|
||||
func newWriteOpCmpExtStep() *WriteOpCmpExtStep {
|
||||
return &WriteOpCmpExtStep{
|
||||
prval: (*C.int)(C.malloc(C.sizeof_int)),
|
||||
}
|
||||
}
|
||||
|
||||
// CmpExt ensures that given object range (extent) satisfies comparison.
|
||||
//
|
||||
// Implements:
|
||||
//
|
||||
// void rados_write_op_cmpext(rados_write_op_t write_op,
|
||||
// const char * cmp_buf,
|
||||
// size_t cmp_len,
|
||||
// uint64_t off,
|
||||
// int * prval);
|
||||
func (w *WriteOp) CmpExt(b []byte, offset uint64) *WriteOpCmpExtStep {
|
||||
oe := newWriteStep(b, 0, offset)
|
||||
cmpExtStep := newWriteOpCmpExtStep()
|
||||
w.steps = append(w.steps, oe, cmpExtStep)
|
||||
C.rados_write_op_cmpext(
|
||||
w.op,
|
||||
oe.cBuffer,
|
||||
oe.cDataLen,
|
||||
oe.cOffset,
|
||||
cmpExtStep.prval)
|
||||
|
||||
return cmpExtStep
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user