commit
d7d3b1e9b7
|
@ -25,6 +25,7 @@ func Test(t *testing.T) {
|
||||||
zfsTestDatasetSnapshot(t)
|
zfsTestDatasetSnapshot(t)
|
||||||
zfsTestDatasetOpenAll(t)
|
zfsTestDatasetOpenAll(t)
|
||||||
zfsTestDatasetSetProperty(t)
|
zfsTestDatasetSetProperty(t)
|
||||||
|
zfsTestDatasetHoldRelease(t)
|
||||||
|
|
||||||
zfsTestDatasetDestroy(t)
|
zfsTestDatasetDestroy(t)
|
||||||
|
|
||||||
|
|
28
common.go
28
common.go
|
@ -90,8 +90,11 @@ const (
|
||||||
PoolStatusFailingDev /* device experiencing errors */
|
PoolStatusFailingDev /* device experiencing errors */
|
||||||
PoolStatusVersionNewer /* newer on-disk version */
|
PoolStatusVersionNewer /* newer on-disk version */
|
||||||
PoolStatusHostidMismatch /* last accessed by another system */
|
PoolStatusHostidMismatch /* last accessed by another system */
|
||||||
|
PoolStatusHosidActive /* currently active on another system */
|
||||||
|
PoolStatusHostidRequired /* multihost=on and hostid=0 */
|
||||||
PoolStatusIoFailureWait /* failed I/O, failmode 'wait' */
|
PoolStatusIoFailureWait /* failed I/O, failmode 'wait' */
|
||||||
PoolStatusIoFailureContinue /* failed I/O, failmode 'continue' */
|
PoolStatusIoFailureContinue /* failed I/O, failmode 'continue' */
|
||||||
|
PoolStatusIOFailureMap /* ailed MMP, failmode not 'panic' */
|
||||||
PoolStatusBadLog /* cannot read log chain(s) */
|
PoolStatusBadLog /* cannot read log chain(s) */
|
||||||
PoolStatusErrata /* informational errata available */
|
PoolStatusErrata /* informational errata available */
|
||||||
|
|
||||||
|
@ -144,7 +147,9 @@ const (
|
||||||
// Pool properties. Enumerates available ZFS pool properties. Use it to access
|
// Pool properties. Enumerates available ZFS pool properties. Use it to access
|
||||||
// pool properties either to read or set soecific property.
|
// pool properties either to read or set soecific property.
|
||||||
const (
|
const (
|
||||||
PoolPropName Prop = iota
|
PoolPropCont Prop = iota - 2
|
||||||
|
PoolPropInval
|
||||||
|
PoolPropName
|
||||||
PoolPropSize
|
PoolPropSize
|
||||||
PoolPropCapacity
|
PoolPropCapacity
|
||||||
PoolPropAltroot
|
PoolPropAltroot
|
||||||
|
@ -171,6 +176,8 @@ const (
|
||||||
PoolPropLeaked
|
PoolPropLeaked
|
||||||
PoolPropMaxBlockSize
|
PoolPropMaxBlockSize
|
||||||
PoolPropTName
|
PoolPropTName
|
||||||
|
PoolPropMaxNodeSize
|
||||||
|
PoolPropMultiHost
|
||||||
PoolNumProps
|
PoolNumProps
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -181,7 +188,9 @@ const (
|
||||||
* the property table in module/zcommon/zfs_prop.c.
|
* the property table in module/zcommon/zfs_prop.c.
|
||||||
*/
|
*/
|
||||||
const (
|
const (
|
||||||
DatasetPropType Prop = iota
|
DatasetPropCont Prop = iota - 2
|
||||||
|
DatasetPropBad
|
||||||
|
DatasetPropType
|
||||||
DatasetPropCreation
|
DatasetPropCreation
|
||||||
DatasetPropUsed
|
DatasetPropUsed
|
||||||
DatasetPropAvailable
|
DatasetPropAvailable
|
||||||
|
@ -207,7 +216,7 @@ const (
|
||||||
DatasetPropSnapdir
|
DatasetPropSnapdir
|
||||||
DatasetPropPrivate /* not exposed to user, temporary */
|
DatasetPropPrivate /* not exposed to user, temporary */
|
||||||
DatasetPropAclinherit
|
DatasetPropAclinherit
|
||||||
DatasetPropCreatetxg /* not exposed to the user */
|
DatasetPropCreateTXG /* not exposed to the user */
|
||||||
DatasetPropName /* not exposed to the user */
|
DatasetPropName /* not exposed to the user */
|
||||||
DatasetPropCanmount
|
DatasetPropCanmount
|
||||||
DatasetPropIscsioptions /* not exposed to the user */
|
DatasetPropIscsioptions /* not exposed to the user */
|
||||||
|
@ -240,12 +249,14 @@ const (
|
||||||
DatasetPropDedup
|
DatasetPropDedup
|
||||||
DatasetPropMlslabel
|
DatasetPropMlslabel
|
||||||
DatasetPropSync
|
DatasetPropSync
|
||||||
|
DatasetPropDnodeSize
|
||||||
DatasetPropRefratio
|
DatasetPropRefratio
|
||||||
DatasetPropWritten
|
DatasetPropWritten
|
||||||
DatasetPropClones
|
DatasetPropClones
|
||||||
DatasetPropLogicalused
|
DatasetPropLogicalused
|
||||||
DatasetPropLogicalreferenced
|
DatasetPropLogicalreferenced
|
||||||
DatasetPropInconsistent /* not exposed to the user */
|
DatasetPropInconsistent /* not exposed to the user */
|
||||||
|
DatasetPropVolmode
|
||||||
DatasetPropFilesystemLimit
|
DatasetPropFilesystemLimit
|
||||||
DatasetPropSnapshotLimit
|
DatasetPropSnapshotLimit
|
||||||
DatasetPropFilesystemCount
|
DatasetPropFilesystemCount
|
||||||
|
@ -259,6 +270,17 @@ const (
|
||||||
DatasetPropRelatime
|
DatasetPropRelatime
|
||||||
DatasetPropRedundantMetadata
|
DatasetPropRedundantMetadata
|
||||||
DatasetPropOverlay
|
DatasetPropOverlay
|
||||||
|
DatasetPropPrevSnap
|
||||||
|
DatasetPropReceiveResumeToken
|
||||||
|
DatasetPropEncryption
|
||||||
|
DatasetPropKeyLocation
|
||||||
|
DatasetPropKeyFormat
|
||||||
|
DatasetPropPBKDF2Salt
|
||||||
|
DatasetPropPBKDF2Iters
|
||||||
|
DatasetPropEncryptionRoot
|
||||||
|
DatasetPropKeyGUID
|
||||||
|
DatasetPropKeyStatus
|
||||||
|
DatasetPropRemapTXG /* not exposed to the user */
|
||||||
DatasetNumProps
|
DatasetNumProps
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
120
sendrecv.go
120
sendrecv.go
|
@ -5,13 +5,18 @@ package zfs
|
||||||
// #include "common.h"
|
// #include "common.h"
|
||||||
// #include "zpool.h"
|
// #include "zpool.h"
|
||||||
// #include "zfs.h"
|
// #include "zfs.h"
|
||||||
|
// #include <memory.h>
|
||||||
|
// #include <string.h>
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,11 +28,11 @@ type SendFlags struct {
|
||||||
Dedup bool
|
Dedup bool
|
||||||
Props bool
|
Props bool
|
||||||
DryRun bool
|
DryRun bool
|
||||||
// Parsable bool
|
Parsable bool
|
||||||
// Progress bool
|
Progress bool
|
||||||
LargeBlock bool
|
LargeBlock bool
|
||||||
EmbedData bool
|
EmbedData bool
|
||||||
// Compress bool
|
Compress bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type RecvFlags struct {
|
type RecvFlags struct {
|
||||||
|
@ -58,11 +63,11 @@ func to_sendflags_t(flags *SendFlags) (cflags *C.sendflags_t) {
|
||||||
cflags.dedup = to_boolean_t(flags.Dedup)
|
cflags.dedup = to_boolean_t(flags.Dedup)
|
||||||
cflags.props = to_boolean_t(flags.Props)
|
cflags.props = to_boolean_t(flags.Props)
|
||||||
cflags.dryrun = to_boolean_t(flags.DryRun)
|
cflags.dryrun = to_boolean_t(flags.DryRun)
|
||||||
// cflags.parsable = to_boolean_t(flags.Parsable)
|
cflags.parsable = to_boolean_t(flags.Parsable)
|
||||||
// cflags.progress = to_boolean_t(flags.Progress)
|
cflags.progress = to_boolean_t(flags.Progress)
|
||||||
cflags.largeblock = to_boolean_t(flags.LargeBlock)
|
cflags.largeblock = to_boolean_t(flags.LargeBlock)
|
||||||
cflags.embed_data = to_boolean_t(flags.EmbedData)
|
cflags.embed_data = to_boolean_t(flags.EmbedData)
|
||||||
// cflags.compress = to_boolean_t(flags.Compress)
|
cflags.compress = to_boolean_t(flags.Compress)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,15 +101,20 @@ func (d *Dataset) send(FromName string, outf *os.File, flags *SendFlags) (err er
|
||||||
if dpath, err = d.Path(); err != nil {
|
if dpath, err = d.Path(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
sendparams := strings.Split(dpath, "@")
|
||||||
|
parent := sendparams[0]
|
||||||
if len(FromName) > 0 {
|
if len(FromName) > 0 {
|
||||||
if FromName[0] == '#' || FromName[0] == '@' {
|
if FromName[0] == '@' {
|
||||||
FromName = dpath + FromName
|
FromName = FromName[1:]
|
||||||
|
} else if strings.Contains(FromName, "/") {
|
||||||
|
from := strings.Split(FromName, "@")
|
||||||
|
if len(from) > 0 {
|
||||||
|
FromName = from[1]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cfromname = C.CString(FromName)
|
cfromname = C.CString(FromName)
|
||||||
defer C.free(unsafe.Pointer(cfromname))
|
defer C.free(unsafe.Pointer(cfromname))
|
||||||
}
|
}
|
||||||
sendparams := strings.Split(dpath, "@")
|
|
||||||
parent := sendparams[0]
|
|
||||||
ctoname = C.CString(sendparams[1])
|
ctoname = C.CString(sendparams[1])
|
||||||
defer C.free(unsafe.Pointer(ctoname))
|
defer C.free(unsafe.Pointer(ctoname))
|
||||||
if pd, err = DatasetOpen(parent); err != nil {
|
if pd, err = DatasetOpen(parent); err != nil {
|
||||||
|
@ -195,57 +205,65 @@ func (d *Dataset) SendFrom(FromName string, outf *os.File, flags SendFlags) (err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = d.send(from[1], outf, &flags)
|
err = d.send("@"+from[1], outf, &flags)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dataset) SendSize(FromName string, flags SendFlags) (size uint64, err error) {
|
// SendSize - estimate snapshot size to transfer
|
||||||
var porigin Property
|
func (d *Dataset) SendSize(FromName string, flags SendFlags) (size int64, err error) {
|
||||||
var from Dataset
|
var r, w *os.File
|
||||||
var dpath string
|
errch := make(chan error)
|
||||||
if dpath, err = d.Path(); err != nil {
|
defer func() {
|
||||||
|
select {
|
||||||
|
case <-errch:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
close(errch)
|
||||||
|
}()
|
||||||
|
flags.DryRun = true
|
||||||
|
flags.Verbose = true
|
||||||
|
flags.Progress = true
|
||||||
|
flags.Parsable = true
|
||||||
|
if r, w, err = os.Pipe(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
zc := C.new_zfs_cmd()
|
defer r.Close()
|
||||||
defer C.free(unsafe.Pointer(zc))
|
go func() {
|
||||||
dpath = strings.Split(dpath, "@")[0]
|
var tmpe error
|
||||||
if len(FromName) > 0 {
|
saveOut := C.dup(C.fileno(C.stdout))
|
||||||
|
if res := C.dup2(C.int(w.Fd()), C.fileno(C.stdout)); res < 0 {
|
||||||
|
tmpe = fmt.Errorf("Redirection of zfslib stdout failed %d", res)
|
||||||
|
} else {
|
||||||
|
tmpe = d.send(FromName, w, &flags)
|
||||||
|
C.fflush(C.stdout)
|
||||||
|
C.dup2(saveOut, C.fileno(C.stdout))
|
||||||
|
}
|
||||||
|
w.Close()
|
||||||
|
errch <- tmpe
|
||||||
|
}()
|
||||||
|
|
||||||
if FromName[0] == '#' || FromName[0] == '@' {
|
r.SetReadDeadline(time.Now().Add(15 * time.Second))
|
||||||
FromName = dpath + FromName
|
var data []byte
|
||||||
}
|
if data, err = ioutil.ReadAll(r); err != nil {
|
||||||
porigin, _ = d.GetProperty(DatasetPropOrigin)
|
return
|
||||||
if len(porigin.Value) > 0 && porigin.Value == FromName {
|
}
|
||||||
FromName = ""
|
// parse size
|
||||||
flags.FromOrigin = true
|
var sizeRe *regexp.Regexp
|
||||||
}
|
if sizeRe, err = regexp.Compile("size[ \t]*([0-9]+)"); err != nil {
|
||||||
if from, err = DatasetOpen(FromName); err != nil {
|
return
|
||||||
|
}
|
||||||
|
matches := sizeRe.FindAllSubmatch(data, 3)
|
||||||
|
if len(matches) > 0 && len(matches[0]) > 1 {
|
||||||
|
if size, err = strconv.ParseInt(
|
||||||
|
string(matches[0][1]), 10, 64); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
zc.zc_fromobj = C.zfs_prop_get_int(from.list.zh, C.ZFS_PROP_OBJSETID)
|
|
||||||
from.Close()
|
|
||||||
} else {
|
|
||||||
zc.zc_fromobj = 0
|
|
||||||
}
|
}
|
||||||
zc.zc_obj = C.uint64_t(to_boolean_t(flags.FromOrigin))
|
err = <-errch
|
||||||
zc.zc_sendobj = C.zfs_prop_get_int(d.list.zh, C.ZFS_PROP_OBJSETID)
|
|
||||||
zc.zc_guid = 1
|
|
||||||
zc.zc_flags = 0
|
|
||||||
if flags.LargeBlock {
|
|
||||||
zc.zc_flags |= C.LZC_SEND_FLAG_LARGE_BLOCK
|
|
||||||
}
|
|
||||||
if flags.EmbedData {
|
|
||||||
zc.zc_flags |= C.LZC_SEND_FLAG_EMBED_DATA
|
|
||||||
}
|
|
||||||
|
|
||||||
// C.estimate_ioctl(d.list.zhp, prevsnap_obj, to_boolean_t(flags.FromOrigin), lzc_send_flags, unsafe.Pointer(&size))
|
|
||||||
if ec, e := C.estimate_send_size(zc); ec != 0 {
|
|
||||||
err = fmt.Errorf("Failed to estimate send size. %s %d", e.Error(), e.(syscall.Errno))
|
|
||||||
}
|
|
||||||
size = uint64(zc.zc_objset_type)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Receive - receive snapshot stream
|
||||||
func (d *Dataset) Receive(inf *os.File, flags RecvFlags) (err error) {
|
func (d *Dataset) Receive(inf *os.File, flags RecvFlags) (err error) {
|
||||||
var dpath string
|
var dpath string
|
||||||
if dpath, err = d.Path(); err != nil {
|
if dpath, err = d.Path(); err != nil {
|
||||||
|
@ -261,7 +279,7 @@ func (d *Dataset) Receive(inf *os.File, flags RecvFlags) (err error) {
|
||||||
defer C.free(unsafe.Pointer(cflags))
|
defer C.free(unsafe.Pointer(cflags))
|
||||||
dest := C.CString(dpath)
|
dest := C.CString(dpath)
|
||||||
defer C.free(unsafe.Pointer(dest))
|
defer C.free(unsafe.Pointer(dest))
|
||||||
ec := C.zfs_receive(C.libzfsHandle, dest, cflags, C.int(inf.Fd()), nil)
|
ec := C.zfs_receive(C.libzfsHandle, dest, nil, cflags, C.int(inf.Fd()), nil)
|
||||||
if ec != 0 {
|
if ec != 0 {
|
||||||
err = fmt.Errorf("ZFS receive of %s failed. %s", C.GoString(dest), LastError().Error())
|
err = fmt.Errorf("ZFS receive of %s failed. %s", C.GoString(dest), LastError().Error())
|
||||||
}
|
}
|
||||||
|
|
6
zfs.c
6
zfs.c
|
@ -256,6 +256,10 @@ struct zfs_cmd *new_zfs_cmd(){
|
||||||
}
|
}
|
||||||
|
|
||||||
int estimate_send_size(struct zfs_cmd *zc) {
|
int estimate_send_size(struct zfs_cmd *zc) {
|
||||||
return zfs_ioctl(libzfsHandle, ZFS_IOC_SEND, zc);
|
int rc = zfs_ioctl(libzfsHandle, ZFS_IOC_SEND, zc);
|
||||||
|
if (rc != 0) {
|
||||||
|
rc = errno;
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
125
zfs.go
125
zfs.go
|
@ -11,6 +11,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,6 +38,12 @@ const (
|
||||||
DatasetTypeBookmark = (1 << 4)
|
DatasetTypeBookmark = (1 << 4)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// HoldTag - user holds tags
|
||||||
|
type HoldTag struct {
|
||||||
|
Name string
|
||||||
|
Timestamp time.Time
|
||||||
|
}
|
||||||
|
|
||||||
// Dataset - ZFS dataset object
|
// Dataset - ZFS dataset object
|
||||||
type Dataset struct {
|
type Dataset struct {
|
||||||
list C.dataset_list_ptr
|
list C.dataset_list_ptr
|
||||||
|
@ -99,6 +106,16 @@ func DatasetCloseAll(datasets []Dataset) {
|
||||||
|
|
||||||
// DatasetOpen open dataset and all of its recursive children datasets
|
// DatasetOpen open dataset and all of its recursive children datasets
|
||||||
func DatasetOpen(path string) (d Dataset, err error) {
|
func DatasetOpen(path string) (d Dataset, err error) {
|
||||||
|
if d, err = DatasetOpenSingle(path); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = d.openChildren()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatasetOpenSingle open dataset without opening all of its recursive
|
||||||
|
// children datasets
|
||||||
|
func DatasetOpenSingle(path string) (d Dataset, err error) {
|
||||||
csPath := C.CString(path)
|
csPath := C.CString(path)
|
||||||
d.list = C.dataset_open(csPath)
|
d.list = C.dataset_open(csPath)
|
||||||
C.free(unsafe.Pointer(csPath))
|
C.free(unsafe.Pointer(csPath))
|
||||||
|
@ -117,7 +134,6 @@ func DatasetOpen(path string) (d Dataset, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = d.openChildren()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,8 +182,10 @@ func DatasetCreate(path string, dtype DatasetType,
|
||||||
// and cleanup dataset object/s from memory)
|
// and cleanup dataset object/s from memory)
|
||||||
func (d *Dataset) Close() {
|
func (d *Dataset) Close() {
|
||||||
// path, _ := d.Path()
|
// path, _ := d.Path()
|
||||||
|
Global.Mtx.Lock()
|
||||||
C.dataset_list_close(d.list)
|
C.dataset_list_close(d.list)
|
||||||
d.list = nil
|
d.list = nil
|
||||||
|
Global.Mtx.Unlock()
|
||||||
for _, cd := range d.Children {
|
for _, cd := range d.Children {
|
||||||
cd.Close()
|
cd.Close()
|
||||||
}
|
}
|
||||||
|
@ -201,6 +219,16 @@ func (d *Dataset) Destroy(Defer bool) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsSnapshot - retrun true if datset is snapshot
|
||||||
|
func (d *Dataset) IsSnapshot() (ok bool, err error) {
|
||||||
|
var path string
|
||||||
|
if path, err = d.Path(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ok = d.Type == DatasetTypeSnapshot || strings.Contains(path, "@")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// DestroyRecursive recursively destroy children of dataset and dataset.
|
// DestroyRecursive recursively destroy children of dataset and dataset.
|
||||||
func (d *Dataset) DestroyRecursive() (err error) {
|
func (d *Dataset) DestroyRecursive() (err error) {
|
||||||
var path string
|
var path string
|
||||||
|
@ -294,6 +322,8 @@ func (d *Dataset) GetProperty(p Prop) (prop Property, err error) {
|
||||||
err = errors.New(msgDatasetIsNil)
|
err = errors.New(msgDatasetIsNil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Global.Mtx.Lock()
|
||||||
|
defer Global.Mtx.Unlock()
|
||||||
plist := C.read_dataset_property(d.list, C.int(p))
|
plist := C.read_dataset_property(d.list, C.int(p))
|
||||||
if plist == nil {
|
if plist == nil {
|
||||||
err = LastError()
|
err = LastError()
|
||||||
|
@ -306,6 +336,7 @@ func (d *Dataset) GetProperty(p Prop) (prop Property, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserProperty - lookup and return user propery
|
||||||
func (d *Dataset) GetUserProperty(p string) (prop Property, err error) {
|
func (d *Dataset) GetUserProperty(p string) (prop Property, err error) {
|
||||||
if d.list == nil {
|
if d.list == nil {
|
||||||
err = errors.New(msgDatasetIsNil)
|
err = errors.New(msgDatasetIsNil)
|
||||||
|
@ -345,6 +376,7 @@ func (d *Dataset) SetProperty(p Prop, value string) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetUserProperty -
|
||||||
func (d *Dataset) SetUserProperty(prop, value string) (err error) {
|
func (d *Dataset) SetUserProperty(prop, value string) (err error) {
|
||||||
if d.list == nil {
|
if d.list == nil {
|
||||||
err = errors.New(msgDatasetIsNil)
|
err = errors.New(msgDatasetIsNil)
|
||||||
|
@ -464,6 +496,8 @@ func (d *Dataset) IsMounted() (mounted bool, where string) {
|
||||||
if d.list == nil {
|
if d.list == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Global.Mtx.Lock()
|
||||||
|
defer Global.Mtx.Unlock()
|
||||||
mp := C.dataset_is_mounted(d.list)
|
mp := C.dataset_is_mounted(d.list)
|
||||||
// defer C.free(mp)
|
// defer C.free(mp)
|
||||||
if mounted = (mp != nil); mounted {
|
if mounted = (mp != nil); mounted {
|
||||||
|
@ -475,6 +509,8 @@ func (d *Dataset) IsMounted() (mounted bool, where string) {
|
||||||
|
|
||||||
// Mount the given filesystem.
|
// Mount the given filesystem.
|
||||||
func (d *Dataset) Mount(options string, flags int) (err error) {
|
func (d *Dataset) Mount(options string, flags int) (err error) {
|
||||||
|
Global.Mtx.Lock()
|
||||||
|
defer Global.Mtx.Unlock()
|
||||||
if d.list == nil {
|
if d.list == nil {
|
||||||
err = errors.New(msgDatasetIsNil)
|
err = errors.New(msgDatasetIsNil)
|
||||||
return
|
return
|
||||||
|
@ -517,6 +553,93 @@ func (d *Dataset) UnmountAll(flags int) (err error) {
|
||||||
return d.Unmount(flags)
|
return d.Unmount(flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hold - Adds a single reference, named with the tag argument, to the snapshot.
|
||||||
|
// Each snapshot has its own tag namespace, and tags must be unique within that space.
|
||||||
|
func (d *Dataset) Hold(flag string) (err error) {
|
||||||
|
var path string
|
||||||
|
var pd Dataset
|
||||||
|
if path, err = d.Path(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !strings.Contains(path, "@") {
|
||||||
|
err = fmt.Errorf("'%s' is not a snapshot", path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pd, err = DatasetOpenSingle(path[:strings.Index(path, "@")])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer pd.Close()
|
||||||
|
csSnapName := C.CString(path[strings.Index(path, "@")+1:])
|
||||||
|
defer C.free(unsafe.Pointer(csSnapName))
|
||||||
|
csFlag := C.CString(flag)
|
||||||
|
defer C.free(unsafe.Pointer(csFlag))
|
||||||
|
if 0 != C.zfs_hold(pd.list.zh, csSnapName, csFlag, booleanT(false), -1) {
|
||||||
|
err = LastError()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release - Removes a single reference, named with the tag argument, from the specified snapshot.
|
||||||
|
// The tag must already exist for each snapshot. If a hold exists on a snapshot, attempts to destroy
|
||||||
|
// that snapshot by using the zfs destroy command return EBUSY.
|
||||||
|
func (d *Dataset) Release(flag string) (err error) {
|
||||||
|
var path string
|
||||||
|
var pd Dataset
|
||||||
|
if path, err = d.Path(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !strings.Contains(path, "@") {
|
||||||
|
err = fmt.Errorf("'%s' is not a snapshot", path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pd, err = DatasetOpenSingle(path[:strings.Index(path, "@")])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer pd.Close()
|
||||||
|
csSnapName := C.CString(path[strings.Index(path, "@")+1:])
|
||||||
|
defer C.free(unsafe.Pointer(csSnapName))
|
||||||
|
csFlag := C.CString(flag)
|
||||||
|
defer C.free(unsafe.Pointer(csFlag))
|
||||||
|
if 0 != C.zfs_release(pd.list.zh, csSnapName, csFlag, booleanT(false)) {
|
||||||
|
err = LastError()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Holds - Lists all existing user references for the given snapshot
|
||||||
|
func (d *Dataset) Holds() (tags []HoldTag, err error) {
|
||||||
|
var nvl *C.nvlist_t
|
||||||
|
var nvp *C.nvpair_t
|
||||||
|
var tu64 C.uint64_t
|
||||||
|
var path string
|
||||||
|
if path, err = d.Path(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !strings.Contains(path, "@") {
|
||||||
|
err = fmt.Errorf("'%s' is not a snapshot", path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if 0 != C.zfs_get_holds(d.list.zh, &nvl) {
|
||||||
|
err = LastError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer C.nvlist_free(nvl)
|
||||||
|
tags = make([]HoldTag, 0, 5)
|
||||||
|
for nvp = C.nvlist_next_nvpair(nvl, nvp); nvp != nil; {
|
||||||
|
tag := C.nvpair_name(nvp)
|
||||||
|
C.nvpair_value_uint64(nvp, &tu64)
|
||||||
|
tags = append(tags, HoldTag{
|
||||||
|
Name: C.GoString(tag),
|
||||||
|
Timestamp: time.Unix(int64(tu64), 0),
|
||||||
|
})
|
||||||
|
|
||||||
|
nvp = C.nvlist_next_nvpair(nvl, nvp)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// DatasetPropertyToName convert property to name
|
// DatasetPropertyToName convert property to name
|
||||||
// ( returns built in string representation of property name).
|
// ( returns built in string representation of property name).
|
||||||
// This is optional, you can represent each property with string
|
// This is optional, you can represent each property with string
|
||||||
|
|
41
zfs_test.go
41
zfs_test.go
|
@ -147,6 +147,47 @@ func zfsTestDatasetSnapshot(t *testing.T) {
|
||||||
print("PASS\n\n")
|
print("PASS\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func zfsTestDatasetHoldRelease(t *testing.T) {
|
||||||
|
println("TEST Hold/Release(", TSTDatasetPathSnap, ", true, ...) ... ")
|
||||||
|
d, err := zfs.DatasetOpen(TSTDatasetPathSnap)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer d.Close()
|
||||||
|
err = d.Hold("keep")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var tags []zfs.HoldTag
|
||||||
|
tags, err = d.Holds()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, tag := range tags {
|
||||||
|
println("tag:", tag.Name, "timestamp:", tag.Timestamp.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.Release("keep")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tags, err = d.Holds()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, tag := range tags {
|
||||||
|
println("* tag:", tag.Name, "timestamp:", tag.Timestamp.String())
|
||||||
|
}
|
||||||
|
print("PASS\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
func zfsTestDatasetDestroy(t *testing.T) {
|
func zfsTestDatasetDestroy(t *testing.T) {
|
||||||
println("TEST DATASET Destroy( ", TSTDatasetPath, " ) ... ")
|
println("TEST DATASET Destroy( ", TSTDatasetPath, " ) ... ")
|
||||||
d, err := zfs.DatasetOpen(TSTDatasetPath)
|
d, err := zfs.DatasetOpen(TSTDatasetPath)
|
||||||
|
|
12
zpool.c
12
zpool.c
|
@ -2,7 +2,11 @@
|
||||||
* using libzfs from go language, and make go code shorter and more readable.
|
* using libzfs from go language, and make go code shorter and more readable.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
typedef unsigned long int rlim64_t;
|
||||||
|
|
||||||
#include <libzfs.h>
|
#include <libzfs.h>
|
||||||
|
#include <libzfs/sys/zfs_context.h>
|
||||||
|
|
||||||
#include <memory.h>
|
#include <memory.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
@ -189,7 +193,7 @@ property_list_ptr read_zpool_property(zpool_list_ptr pool, int prop) {
|
||||||
property_list_ptr list = new_property_list();
|
property_list_ptr list = new_property_list();
|
||||||
|
|
||||||
r = zpool_get_prop(pool->zph, prop,
|
r = zpool_get_prop(pool->zph, prop,
|
||||||
list->value, INT_MAX_VALUE, &source);
|
list->value, INT_MAX_VALUE, &source, B_FALSE);
|
||||||
if (r == 0) {
|
if (r == 0) {
|
||||||
// strcpy(list->name, zpool_prop_to_name(prop));
|
// strcpy(list->name, zpool_prop_to_name(prop));
|
||||||
zprop_source_tostr(list->source, source);
|
zprop_source_tostr(list->source, source);
|
||||||
|
@ -492,11 +496,15 @@ nvlist_ptr get_zpool_vdev_tree(nvlist_ptr nv) {
|
||||||
nvlist_ptr go_zpool_search_import(libzfs_handle_ptr zfsh, int paths, char **path, boolean_t do_scan) {
|
nvlist_ptr go_zpool_search_import(libzfs_handle_ptr zfsh, int paths, char **path, boolean_t do_scan) {
|
||||||
importargs_t idata;
|
importargs_t idata;
|
||||||
memset(&idata, 0, sizeof(importargs_t));
|
memset(&idata, 0, sizeof(importargs_t));
|
||||||
|
nvlist_ptr pools = NULL;
|
||||||
idata.path = path;
|
idata.path = path;
|
||||||
idata.paths = paths;
|
idata.paths = paths;
|
||||||
// idata.scan = 0;
|
// idata.scan = 0;
|
||||||
|
|
||||||
return zpool_search_import(zfsh, &idata);
|
thread_init();
|
||||||
|
pools = zpool_search_import(zfsh, &idata);
|
||||||
|
thread_fini();
|
||||||
|
return pools;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
15
zpool.go
15
zpool.go
|
@ -1,5 +1,6 @@
|
||||||
package zfs
|
package zfs
|
||||||
|
|
||||||
|
// #cgo CFLAGS: -D__USE_LARGEFILE64=1
|
||||||
// #include <stdlib.h>
|
// #include <stdlib.h>
|
||||||
// #include <libzfs.h>
|
// #include <libzfs.h>
|
||||||
// #include "common.h"
|
// #include "common.h"
|
||||||
|
@ -915,6 +916,14 @@ func PoolCreate(name string, vdev VDevTree, features map[string]string,
|
||||||
features["filesystem_limits"] = FENABLED
|
features["filesystem_limits"] = FENABLED
|
||||||
features["large_blocks"] = FENABLED
|
features["large_blocks"] = FENABLED
|
||||||
|
|
||||||
|
// Enable 0.7.x features per default
|
||||||
|
features["multi_vdev_crash_dump"] = FENABLED
|
||||||
|
features["large_dnode"] = FENABLED
|
||||||
|
features["sha512"] = FENABLED
|
||||||
|
features["skein"] = FENABLED
|
||||||
|
features["edonr"] = FENABLED
|
||||||
|
features["userobj_accounting"] = FENABLED
|
||||||
|
|
||||||
// convert properties
|
// convert properties
|
||||||
cprops := toCPoolProperties(props)
|
cprops := toCPoolProperties(props)
|
||||||
if cprops != nil {
|
if cprops != nil {
|
||||||
|
@ -1116,10 +1125,16 @@ func (s PoolStatus) String() string {
|
||||||
return "VERSION_NEWER"
|
return "VERSION_NEWER"
|
||||||
case PoolStatusHostidMismatch: /* last accessed by another system */
|
case PoolStatusHostidMismatch: /* last accessed by another system */
|
||||||
return "HOSTID_MISMATCH"
|
return "HOSTID_MISMATCH"
|
||||||
|
case PoolStatusHosidActive: /* currently active on another system */
|
||||||
|
return "HOSTID_ACTIVE"
|
||||||
|
case PoolStatusHostidRequired: /* multihost=on and hostid=0 */
|
||||||
|
return "HOSTID_REQUIRED"
|
||||||
case PoolStatusIoFailureWait: /* failed I/O, failmode 'wait' */
|
case PoolStatusIoFailureWait: /* failed I/O, failmode 'wait' */
|
||||||
return "FAILURE_WAIT"
|
return "FAILURE_WAIT"
|
||||||
case PoolStatusIoFailureContinue: /* failed I/O, failmode 'continue' */
|
case PoolStatusIoFailureContinue: /* failed I/O, failmode 'continue' */
|
||||||
return "FAILURE_CONTINUE"
|
return "FAILURE_CONTINUE"
|
||||||
|
case PoolStatusIOFailureMap: /* ailed MMP, failmode not 'panic' */
|
||||||
|
return "HOSTID_FAILURE_MAP"
|
||||||
case PoolStatusBadLog: /* cannot read log chain(s) */
|
case PoolStatusBadLog: /* cannot read log chain(s) */
|
||||||
return "BAD_LOG"
|
return "BAD_LOG"
|
||||||
case PoolStatusErrata: /* informational errata available */
|
case PoolStatusErrata: /* informational errata available */
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package zfs_test
|
package zfs_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -531,33 +530,33 @@ func ExamplePool_State() {
|
||||||
println("POOL TESTPOOL state:", zfs.PoolStateToName(pstate))
|
println("POOL TESTPOOL state:", zfs.PoolStateToName(pstate))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPool_VDevTree(t *testing.T) {
|
// func TestPool_VDevTree(t *testing.T) {
|
||||||
type fields struct {
|
// type fields struct {
|
||||||
poolName string
|
// poolName string
|
||||||
}
|
// }
|
||||||
tests := []struct {
|
// tests := []struct {
|
||||||
name string
|
// name string
|
||||||
fields fields
|
// fields fields
|
||||||
wantErr bool
|
// wantErr bool
|
||||||
}{
|
// }{
|
||||||
// TODO: Add test cases.
|
// // TODO: Add test cases.
|
||||||
{
|
// {
|
||||||
name: "test1",
|
// name: "test1",
|
||||||
fields: fields{"NETSTOR"},
|
// fields: fields{"TESTPOOL"},
|
||||||
wantErr: false,
|
// wantErr: false,
|
||||||
},
|
// },
|
||||||
}
|
// }
|
||||||
for _, tt := range tests {
|
// for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
// t.Run(tt.name, func(t *testing.T) {
|
||||||
pool, _ := zfs.PoolOpen(tt.fields.poolName)
|
// pool, _ := zfs.PoolOpen(tt.fields.poolName)
|
||||||
defer pool.Close()
|
// defer pool.Close()
|
||||||
gotVdevs, err := pool.VDevTree()
|
// gotVdevs, err := pool.VDevTree()
|
||||||
if (err != nil) != tt.wantErr {
|
// if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("Pool.VDevTree() error = %v, wantErr %v", err, tt.wantErr)
|
// t.Errorf("Pool.VDevTree() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
jsonData, _ := json.MarshalIndent(gotVdevs, "", "\t")
|
// jsonData, _ := json.MarshalIndent(gotVdevs, "", "\t")
|
||||||
t.Logf("gotVdevs: %s", string(jsonData))
|
// t.Logf("gotVdevs: %s", string(jsonData))
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
Loading…
Reference in New Issue