Initial QSfera import
This commit is contained in:
+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()
|
||||
}
|
||||
Reference in New Issue
Block a user