From c8b67555935002a3cb832c7cb737a9866b57857f Mon Sep 17 00:00:00 2001 From: Faruk Kasumovic Date: Mon, 2 Jul 2018 14:37:54 +0200 Subject: [PATCH 01/10] Port to zfs-0.7.x Compatibility changes to work with libzfs 0.7.x, changes are not bacward compatible --- common.go | 25 ++++++++++++++++++++++--- sendrecv.go | 2 +- zpool.c | 12 ++++++++++-- zpool.go | 3 ++- zpool_test.go | 2 +- 5 files changed, 36 insertions(+), 8 deletions(-) diff --git a/common.go b/common.go index 81a4e6d..f23aac7 100644 --- a/common.go +++ b/common.go @@ -144,7 +144,9 @@ const ( // Pool properties. Enumerates available ZFS pool properties. Use it to access // pool properties either to read or set soecific property. const ( - PoolPropName Prop = iota + PoolPropCont Prop = iota - 2 + PoolPropInval + PoolPropName PoolPropSize PoolPropCapacity PoolPropAltroot @@ -171,6 +173,8 @@ const ( PoolPropLeaked PoolPropMaxBlockSize PoolPropTName + PoolPropMaxNodeSize + PoolPropMultiHost PoolNumProps ) @@ -181,7 +185,9 @@ const ( * the property table in module/zcommon/zfs_prop.c. */ const ( - DatasetPropType Prop = iota + DatasetPropCont Prop = iota - 2 + DatasetPropBad + DatasetPropType DatasetPropCreation DatasetPropUsed DatasetPropAvailable @@ -207,7 +213,7 @@ const ( DatasetPropSnapdir DatasetPropPrivate /* not exposed to user, temporary */ DatasetPropAclinherit - DatasetPropCreatetxg /* not exposed to the user */ + DatasetPropCreateTXG /* not exposed to the user */ DatasetPropName /* not exposed to the user */ DatasetPropCanmount DatasetPropIscsioptions /* not exposed to the user */ @@ -240,12 +246,14 @@ const ( DatasetPropDedup DatasetPropMlslabel DatasetPropSync + DatasetPropDnodeSize DatasetPropRefratio DatasetPropWritten DatasetPropClones DatasetPropLogicalused DatasetPropLogicalreferenced DatasetPropInconsistent /* not exposed to the user */ + DatasetPropVolmode DatasetPropFilesystemLimit DatasetPropSnapshotLimit DatasetPropFilesystemCount @@ -259,6 +267,17 @@ const ( DatasetPropRelatime DatasetPropRedundantMetadata DatasetPropOverlay + DatasetPropPrevSnap + DatasetPropReceiveResumeToken + DatasetPropEncryption + DatasetPropKeyLocation + DatasetPropKeyFormat + DatasetPropPBKDF2Salt + DatasetPropPBKDF2Iters + DatasetPropEncryptionRoot + DatasetPropKeyGUID + DatasetPropKeyStatus + DatasetPropRemapTXG /* not exposed to the user */ DatasetNumProps ) diff --git a/sendrecv.go b/sendrecv.go index 89937de..bd118e1 100644 --- a/sendrecv.go +++ b/sendrecv.go @@ -261,7 +261,7 @@ func (d *Dataset) Receive(inf *os.File, flags RecvFlags) (err error) { defer C.free(unsafe.Pointer(cflags)) dest := C.CString(dpath) 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 { err = fmt.Errorf("ZFS receive of %s failed. %s", C.GoString(dest), LastError().Error()) } diff --git a/zpool.c b/zpool.c index d592be4..e23f126 100644 --- a/zpool.c +++ b/zpool.c @@ -2,7 +2,11 @@ * using libzfs from go language, and make go code shorter and more readable. */ +typedef unsigned long int rlim64_t; + #include +#include + #include #include #include @@ -189,7 +193,7 @@ property_list_ptr read_zpool_property(zpool_list_ptr pool, int prop) { property_list_ptr list = new_property_list(); r = zpool_get_prop(pool->zph, prop, - list->value, INT_MAX_VALUE, &source); + list->value, INT_MAX_VALUE, &source, B_FALSE); if (r == 0) { // strcpy(list->name, zpool_prop_to_name(prop)); zprop_source_tostr(list->source, source); @@ -491,11 +495,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) { importargs_t idata = { 0 }; + nvlist_ptr pools = NULL; idata.path = path; idata.paths = paths; // idata.scan = 0; - return zpool_search_import(zfsh, &idata); + thread_init(); + pools = zpool_search_import(zfsh, &idata); + thread_fini(); + return pools; } diff --git a/zpool.go b/zpool.go index 6d00056..a7b3f30 100644 --- a/zpool.go +++ b/zpool.go @@ -1,5 +1,6 @@ package zfs +// #cgo CFLAGS: -D__USE_LARGEFILE64=1 // #include // #include // #include "common.h" @@ -361,7 +362,7 @@ func poolSearchImport(q string, searchpaths []string, guid bool) (name string, C.strings_setat(cpaths, C.int(i), csPath) } - pools := C.zpool_find_import(C.libzfsHandle, C.int(numofp), cpaths) + pools := C.go_zpool_search_import(C.libzfsHandle, C.int(numofp), cpaths, C.B_FALSE) defer C.nvlist_free(pools) elem = C.nvlist_next_nvpair(pools, elem) diff --git a/zpool_test.go b/zpool_test.go index 72e9aff..05dc12f 100644 --- a/zpool_test.go +++ b/zpool_test.go @@ -543,7 +543,7 @@ func TestPool_VDevTree(t *testing.T) { // TODO: Add test cases. { name: "test1", - fields: fields{"NETSTOR"}, + fields: fields{"TESTPOOL"}, wantErr: false, }, } From 5d716ef4ec67ccc84c85c88a33de098298861981 Mon Sep 17 00:00:00 2001 From: Faruk Kasumovic Date: Fri, 6 Jul 2018 13:35:47 +0200 Subject: [PATCH 02/10] Additional compatibility changes to work with libzfs 0.7.x --- common.go | 3 +++ zpool.go | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/common.go b/common.go index f23aac7..02678d8 100644 --- a/common.go +++ b/common.go @@ -90,8 +90,11 @@ const ( PoolStatusFailingDev /* device experiencing errors */ PoolStatusVersionNewer /* newer on-disk version */ PoolStatusHostidMismatch /* last accessed by another system */ + PoolStatusHosidActive /* currently active on another system */ + PoolStatusHostidRequired /* multihost=on and hostid=0 */ PoolStatusIoFailureWait /* failed I/O, failmode 'wait' */ PoolStatusIoFailureContinue /* failed I/O, failmode 'continue' */ + PoolStatusIOFailureMap /* ailed MMP, failmode not 'panic' */ PoolStatusBadLog /* cannot read log chain(s) */ PoolStatusErrata /* informational errata available */ diff --git a/zpool.go b/zpool.go index a7b3f30..d45e526 100644 --- a/zpool.go +++ b/zpool.go @@ -1111,10 +1111,16 @@ func (s PoolStatus) String() string { return "VERSION_NEWER" case PoolStatusHostidMismatch: /* last accessed by another system */ 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' */ return "FAILURE_WAIT" case PoolStatusIoFailureContinue: /* failed I/O, failmode 'continue' */ return "FAILURE_CONTINUE" + case PoolStatusIOFailureMap: /* ailed MMP, failmode not 'panic' */ + return "HOSTID_FAILURE_MAP" case PoolStatusBadLog: /* cannot read log chain(s) */ return "BAD_LOG" case PoolStatusErrata: /* informational errata available */ From 46ec88c0a85d09e2b290d5c78ae8c0fb3e2b7deb Mon Sep 17 00:00:00 2001 From: Faruk Kasumovic Date: Wed, 11 Jul 2018 16:20:37 +0200 Subject: [PATCH 03/10] Estimate snapshot send size --- sendrecv.go | 103 +++++++++++++++++++++++++++++----------------------- zfs.c | 6 ++- 2 files changed, 63 insertions(+), 46 deletions(-) diff --git a/sendrecv.go b/sendrecv.go index bd118e1..ed6963b 100644 --- a/sendrecv.go +++ b/sendrecv.go @@ -5,13 +5,18 @@ package zfs // #include "common.h" // #include "zpool.h" // #include "zfs.h" +// #include +// #include import "C" import ( "fmt" + "io/ioutil" "os" "path" + "regexp" + "strconv" "strings" - "syscall" + "time" "unsafe" ) @@ -23,11 +28,11 @@ type SendFlags struct { Dedup bool Props bool DryRun bool - // Parsable bool - // Progress bool + Parsable bool + Progress bool LargeBlock bool EmbedData bool - // Compress bool + Compress bool } 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.props = to_boolean_t(flags.Props) cflags.dryrun = to_boolean_t(flags.DryRun) - // cflags.parsable = to_boolean_t(flags.Parsable) - // cflags.progress = to_boolean_t(flags.Progress) + cflags.parsable = to_boolean_t(flags.Parsable) + cflags.progress = to_boolean_t(flags.Progress) cflags.largeblock = to_boolean_t(flags.LargeBlock) cflags.embed_data = to_boolean_t(flags.EmbedData) - // cflags.compress = to_boolean_t(flags.Compress) + cflags.compress = to_boolean_t(flags.Compress) return } @@ -199,53 +204,61 @@ func (d *Dataset) SendFrom(FromName string, outf *os.File, flags SendFlags) (err return } -func (d *Dataset) SendSize(FromName string, flags SendFlags) (size uint64, err error) { - var porigin Property - var from Dataset - var dpath string - if dpath, err = d.Path(); err != nil { +// SendSize - estimate snapshot size to transfer +func (d *Dataset) SendSize(FromName string, flags SendFlags) (size int64, err error) { + var r, w *os.File + errch := make(chan error) + 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 } - zc := C.new_zfs_cmd() - defer C.free(unsafe.Pointer(zc)) - dpath = strings.Split(dpath, "@")[0] - if len(FromName) > 0 { + defer r.Close() + go func() { + var tmpe error + 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] == '@' { - FromName = dpath + FromName - } - porigin, _ = d.GetProperty(DatasetPropOrigin) - if len(porigin.Value) > 0 && porigin.Value == FromName { - FromName = "" - flags.FromOrigin = true - } - if from, err = DatasetOpen(FromName); err != nil { + r.SetReadDeadline(time.Now().Add(15 * time.Second)) + var data []byte + if data, err = ioutil.ReadAll(r); err != nil { + return + } + // parse size + var sizeRe *regexp.Regexp + if sizeRe, err = regexp.Compile("size[ \t]*([0-9]+)"); 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 } - 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)) - 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) + err = <-errch return } +// Receive - receive snapshot stream func (d *Dataset) Receive(inf *os.File, flags RecvFlags) (err error) { var dpath string if dpath, err = d.Path(); err != nil { diff --git a/zfs.c b/zfs.c index 09ba095..99e620b 100644 --- a/zfs.c +++ b/zfs.c @@ -256,6 +256,10 @@ struct zfs_cmd *new_zfs_cmd(){ } 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; } From c0f5b857fcb2d8cd3ec2b13e24b9eb60c0b1a491 Mon Sep 17 00:00:00 2001 From: Faruk Kasumovic Date: Mon, 2 Jul 2018 14:37:54 +0200 Subject: [PATCH 04/10] Port to zfs-0.7.x Compatibility changes to work with libzfs 0.7.x, changes are not bacward compatible --- common.go | 25 ++++++++++++++++++++++--- sendrecv.go | 2 +- zpool.c | 12 ++++++++++-- zpool.go | 3 ++- zpool_test.go | 2 +- 5 files changed, 36 insertions(+), 8 deletions(-) diff --git a/common.go b/common.go index 81a4e6d..f23aac7 100644 --- a/common.go +++ b/common.go @@ -144,7 +144,9 @@ const ( // Pool properties. Enumerates available ZFS pool properties. Use it to access // pool properties either to read or set soecific property. const ( - PoolPropName Prop = iota + PoolPropCont Prop = iota - 2 + PoolPropInval + PoolPropName PoolPropSize PoolPropCapacity PoolPropAltroot @@ -171,6 +173,8 @@ const ( PoolPropLeaked PoolPropMaxBlockSize PoolPropTName + PoolPropMaxNodeSize + PoolPropMultiHost PoolNumProps ) @@ -181,7 +185,9 @@ const ( * the property table in module/zcommon/zfs_prop.c. */ const ( - DatasetPropType Prop = iota + DatasetPropCont Prop = iota - 2 + DatasetPropBad + DatasetPropType DatasetPropCreation DatasetPropUsed DatasetPropAvailable @@ -207,7 +213,7 @@ const ( DatasetPropSnapdir DatasetPropPrivate /* not exposed to user, temporary */ DatasetPropAclinherit - DatasetPropCreatetxg /* not exposed to the user */ + DatasetPropCreateTXG /* not exposed to the user */ DatasetPropName /* not exposed to the user */ DatasetPropCanmount DatasetPropIscsioptions /* not exposed to the user */ @@ -240,12 +246,14 @@ const ( DatasetPropDedup DatasetPropMlslabel DatasetPropSync + DatasetPropDnodeSize DatasetPropRefratio DatasetPropWritten DatasetPropClones DatasetPropLogicalused DatasetPropLogicalreferenced DatasetPropInconsistent /* not exposed to the user */ + DatasetPropVolmode DatasetPropFilesystemLimit DatasetPropSnapshotLimit DatasetPropFilesystemCount @@ -259,6 +267,17 @@ const ( DatasetPropRelatime DatasetPropRedundantMetadata DatasetPropOverlay + DatasetPropPrevSnap + DatasetPropReceiveResumeToken + DatasetPropEncryption + DatasetPropKeyLocation + DatasetPropKeyFormat + DatasetPropPBKDF2Salt + DatasetPropPBKDF2Iters + DatasetPropEncryptionRoot + DatasetPropKeyGUID + DatasetPropKeyStatus + DatasetPropRemapTXG /* not exposed to the user */ DatasetNumProps ) diff --git a/sendrecv.go b/sendrecv.go index 89937de..bd118e1 100644 --- a/sendrecv.go +++ b/sendrecv.go @@ -261,7 +261,7 @@ func (d *Dataset) Receive(inf *os.File, flags RecvFlags) (err error) { defer C.free(unsafe.Pointer(cflags)) dest := C.CString(dpath) 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 { err = fmt.Errorf("ZFS receive of %s failed. %s", C.GoString(dest), LastError().Error()) } diff --git a/zpool.c b/zpool.c index d592be4..e23f126 100644 --- a/zpool.c +++ b/zpool.c @@ -2,7 +2,11 @@ * using libzfs from go language, and make go code shorter and more readable. */ +typedef unsigned long int rlim64_t; + #include +#include + #include #include #include @@ -189,7 +193,7 @@ property_list_ptr read_zpool_property(zpool_list_ptr pool, int prop) { property_list_ptr list = new_property_list(); r = zpool_get_prop(pool->zph, prop, - list->value, INT_MAX_VALUE, &source); + list->value, INT_MAX_VALUE, &source, B_FALSE); if (r == 0) { // strcpy(list->name, zpool_prop_to_name(prop)); zprop_source_tostr(list->source, source); @@ -491,11 +495,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) { importargs_t idata = { 0 }; + nvlist_ptr pools = NULL; idata.path = path; idata.paths = paths; // idata.scan = 0; - return zpool_search_import(zfsh, &idata); + thread_init(); + pools = zpool_search_import(zfsh, &idata); + thread_fini(); + return pools; } diff --git a/zpool.go b/zpool.go index 153cc97..959c4d6 100644 --- a/zpool.go +++ b/zpool.go @@ -1,5 +1,6 @@ package zfs +// #cgo CFLAGS: -D__USE_LARGEFILE64=1 // #include // #include // #include "common.h" @@ -364,7 +365,7 @@ func poolSearchImport(q string, searchpaths []string, guid bool) (name string, C.strings_setat(cpaths, C.int(i), csPath) } - pools := C.zpool_find_import(C.libzfsHandle, C.int(numofp), cpaths) + pools := C.go_zpool_search_import(C.libzfsHandle, C.int(numofp), cpaths, C.B_FALSE) defer C.nvlist_free(pools) elem = C.nvlist_next_nvpair(pools, elem) diff --git a/zpool_test.go b/zpool_test.go index 72e9aff..05dc12f 100644 --- a/zpool_test.go +++ b/zpool_test.go @@ -543,7 +543,7 @@ func TestPool_VDevTree(t *testing.T) { // TODO: Add test cases. { name: "test1", - fields: fields{"NETSTOR"}, + fields: fields{"TESTPOOL"}, wantErr: false, }, } From 6b010566916e9d430a3817105a3a1b8b5879d93e Mon Sep 17 00:00:00 2001 From: Faruk Kasumovic Date: Fri, 6 Jul 2018 13:35:47 +0200 Subject: [PATCH 05/10] Additional compatibility changes to work with libzfs 0.7.x --- common.go | 3 +++ zpool.go | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/common.go b/common.go index f23aac7..02678d8 100644 --- a/common.go +++ b/common.go @@ -90,8 +90,11 @@ const ( PoolStatusFailingDev /* device experiencing errors */ PoolStatusVersionNewer /* newer on-disk version */ PoolStatusHostidMismatch /* last accessed by another system */ + PoolStatusHosidActive /* currently active on another system */ + PoolStatusHostidRequired /* multihost=on and hostid=0 */ PoolStatusIoFailureWait /* failed I/O, failmode 'wait' */ PoolStatusIoFailureContinue /* failed I/O, failmode 'continue' */ + PoolStatusIOFailureMap /* ailed MMP, failmode not 'panic' */ PoolStatusBadLog /* cannot read log chain(s) */ PoolStatusErrata /* informational errata available */ diff --git a/zpool.go b/zpool.go index 959c4d6..f0d69be 100644 --- a/zpool.go +++ b/zpool.go @@ -1114,10 +1114,16 @@ func (s PoolStatus) String() string { return "VERSION_NEWER" case PoolStatusHostidMismatch: /* last accessed by another system */ 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' */ return "FAILURE_WAIT" case PoolStatusIoFailureContinue: /* failed I/O, failmode 'continue' */ return "FAILURE_CONTINUE" + case PoolStatusIOFailureMap: /* ailed MMP, failmode not 'panic' */ + return "HOSTID_FAILURE_MAP" case PoolStatusBadLog: /* cannot read log chain(s) */ return "BAD_LOG" case PoolStatusErrata: /* informational errata available */ From 6d3dff30aaa6abaa02cdb13d07be645588b089be Mon Sep 17 00:00:00 2001 From: Faruk Kasumovic Date: Wed, 11 Jul 2018 16:20:37 +0200 Subject: [PATCH 06/10] Estimate snapshot send size --- sendrecv.go | 103 +++++++++++++++++++++++++++++----------------------- zfs.c | 6 ++- 2 files changed, 63 insertions(+), 46 deletions(-) diff --git a/sendrecv.go b/sendrecv.go index bd118e1..ed6963b 100644 --- a/sendrecv.go +++ b/sendrecv.go @@ -5,13 +5,18 @@ package zfs // #include "common.h" // #include "zpool.h" // #include "zfs.h" +// #include +// #include import "C" import ( "fmt" + "io/ioutil" "os" "path" + "regexp" + "strconv" "strings" - "syscall" + "time" "unsafe" ) @@ -23,11 +28,11 @@ type SendFlags struct { Dedup bool Props bool DryRun bool - // Parsable bool - // Progress bool + Parsable bool + Progress bool LargeBlock bool EmbedData bool - // Compress bool + Compress bool } 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.props = to_boolean_t(flags.Props) cflags.dryrun = to_boolean_t(flags.DryRun) - // cflags.parsable = to_boolean_t(flags.Parsable) - // cflags.progress = to_boolean_t(flags.Progress) + cflags.parsable = to_boolean_t(flags.Parsable) + cflags.progress = to_boolean_t(flags.Progress) cflags.largeblock = to_boolean_t(flags.LargeBlock) cflags.embed_data = to_boolean_t(flags.EmbedData) - // cflags.compress = to_boolean_t(flags.Compress) + cflags.compress = to_boolean_t(flags.Compress) return } @@ -199,53 +204,61 @@ func (d *Dataset) SendFrom(FromName string, outf *os.File, flags SendFlags) (err return } -func (d *Dataset) SendSize(FromName string, flags SendFlags) (size uint64, err error) { - var porigin Property - var from Dataset - var dpath string - if dpath, err = d.Path(); err != nil { +// SendSize - estimate snapshot size to transfer +func (d *Dataset) SendSize(FromName string, flags SendFlags) (size int64, err error) { + var r, w *os.File + errch := make(chan error) + 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 } - zc := C.new_zfs_cmd() - defer C.free(unsafe.Pointer(zc)) - dpath = strings.Split(dpath, "@")[0] - if len(FromName) > 0 { + defer r.Close() + go func() { + var tmpe error + 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] == '@' { - FromName = dpath + FromName - } - porigin, _ = d.GetProperty(DatasetPropOrigin) - if len(porigin.Value) > 0 && porigin.Value == FromName { - FromName = "" - flags.FromOrigin = true - } - if from, err = DatasetOpen(FromName); err != nil { + r.SetReadDeadline(time.Now().Add(15 * time.Second)) + var data []byte + if data, err = ioutil.ReadAll(r); err != nil { + return + } + // parse size + var sizeRe *regexp.Regexp + if sizeRe, err = regexp.Compile("size[ \t]*([0-9]+)"); 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 } - 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)) - 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) + err = <-errch return } +// Receive - receive snapshot stream func (d *Dataset) Receive(inf *os.File, flags RecvFlags) (err error) { var dpath string if dpath, err = d.Path(); err != nil { diff --git a/zfs.c b/zfs.c index 09ba095..99e620b 100644 --- a/zfs.c +++ b/zfs.c @@ -256,6 +256,10 @@ struct zfs_cmd *new_zfs_cmd(){ } 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; } From 21c2f90b27e4e19b33f21919654fdd848c8dcfe1 Mon Sep 17 00:00:00 2001 From: Faruk Kasumovic Date: Mon, 6 Aug 2018 14:21:34 +0200 Subject: [PATCH 07/10] Enable 0.7.x features per default --- zpool.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/zpool.go b/zpool.go index f0d69be..a3f9d50 100644 --- a/zpool.go +++ b/zpool.go @@ -913,6 +913,14 @@ func PoolCreate(name string, vdev VDevTree, features map[string]string, features["filesystem_limits"] = 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 cprops := toCPoolProperties(props) if cprops != nil { From bcd0988597ca262a56ea0459cdcbf79647662731 Mon Sep 17 00:00:00 2001 From: Faruk Kasumovic Date: Wed, 15 Aug 2018 20:23:12 +0200 Subject: [PATCH 08/10] Fix problem with from snapshot name interpretation on incremental syncs --- sendrecv.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/sendrecv.go b/sendrecv.go index ed6963b..0bc5cf0 100644 --- a/sendrecv.go +++ b/sendrecv.go @@ -101,15 +101,20 @@ func (d *Dataset) send(FromName string, outf *os.File, flags *SendFlags) (err er if dpath, err = d.Path(); err != nil { return } + sendparams := strings.Split(dpath, "@") + parent := sendparams[0] if len(FromName) > 0 { - if FromName[0] == '#' || FromName[0] == '@' { - FromName = dpath + FromName + if FromName[0] == '@' { + FromName = FromName[1:] + } else if strings.Contains(FromName, "/") { + from := strings.Split(FromName, "@") + if len(from) > 0 { + FromName = from[1] + } } cfromname = C.CString(FromName) defer C.free(unsafe.Pointer(cfromname)) } - sendparams := strings.Split(dpath, "@") - parent := sendparams[0] ctoname = C.CString(sendparams[1]) defer C.free(unsafe.Pointer(ctoname)) if pd, err = DatasetOpen(parent); err != nil { @@ -200,7 +205,7 @@ func (d *Dataset) SendFrom(FromName string, outf *os.File, flags SendFlags) (err return } } - err = d.send(from[1], outf, &flags) + err = d.send("@"+from[1], outf, &flags) return } From 0d2d2cf113d1cb1de33886c9c99394a9944e6fd8 Mon Sep 17 00:00:00 2001 From: Faruk Kasumovic Date: Fri, 2 Nov 2018 11:15:55 +0100 Subject: [PATCH 09/10] Functions to set hold, release and list user references on snapshots --- a_test.go | 1 + zfs.go | 117 +++++++++++++++++++++++++++++++++++++++++++++++++- zfs_test.go | 41 ++++++++++++++++++ zpool_test.go | 61 +++++++++++++------------- 4 files changed, 188 insertions(+), 32 deletions(-) diff --git a/a_test.go b/a_test.go index da96b49..5a663ec 100644 --- a/a_test.go +++ b/a_test.go @@ -25,6 +25,7 @@ func Test(t *testing.T) { zfsTestDatasetSnapshot(t) zfsTestDatasetOpenAll(t) zfsTestDatasetSetProperty(t) + zfsTestDatasetHoldRelease(t) zfsTestDatasetDestroy(t) diff --git a/zfs.go b/zfs.go index b76b0d8..4956c05 100644 --- a/zfs.go +++ b/zfs.go @@ -11,6 +11,7 @@ import ( "errors" "fmt" "strings" + "time" "unsafe" ) @@ -37,6 +38,12 @@ const ( DatasetTypeBookmark = (1 << 4) ) +// HoldTag - user holds tags +type HoldTag struct { + Name string + Timestamp time.Time +} + // Dataset - ZFS dataset object type Dataset struct { list C.dataset_list_ptr @@ -99,6 +106,16 @@ func DatasetCloseAll(datasets []Dataset) { // DatasetOpen open dataset and all of its recursive children datasets 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) d.list = C.dataset_open(csPath) C.free(unsafe.Pointer(csPath)) @@ -117,7 +134,6 @@ func DatasetOpen(path string) (d Dataset, err error) { if err != nil { return } - err = d.openChildren() return } @@ -201,6 +217,16 @@ func (d *Dataset) Destroy(Defer bool) (err error) { 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. func (d *Dataset) DestroyRecursive() (err error) { var path string @@ -306,6 +332,7 @@ func (d *Dataset) GetProperty(p Prop) (prop Property, err error) { return } +// GetUserProperty - lookup and return user propery func (d *Dataset) GetUserProperty(p string) (prop Property, err error) { if d.list == nil { err = errors.New(msgDatasetIsNil) @@ -345,6 +372,7 @@ func (d *Dataset) SetProperty(p Prop, value string) (err error) { return } +// SetUserProperty - func (d *Dataset) SetUserProperty(prop, value string) (err error) { if d.list == nil { err = errors.New(msgDatasetIsNil) @@ -517,6 +545,93 @@ func (d *Dataset) UnmountAll(flags int) (err error) { 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 // ( returns built in string representation of property name). // This is optional, you can represent each property with string diff --git a/zfs_test.go b/zfs_test.go index 22628ac..dbd9ea0 100644 --- a/zfs_test.go +++ b/zfs_test.go @@ -147,6 +147,47 @@ func zfsTestDatasetSnapshot(t *testing.T) { 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) { println("TEST DATASET Destroy( ", TSTDatasetPath, " ) ... ") d, err := zfs.DatasetOpen(TSTDatasetPath) diff --git a/zpool_test.go b/zpool_test.go index 05dc12f..56fda54 100644 --- a/zpool_test.go +++ b/zpool_test.go @@ -1,7 +1,6 @@ package zfs_test import ( - "encoding/json" "fmt" "io/ioutil" "os" @@ -531,33 +530,33 @@ func ExamplePool_State() { println("POOL TESTPOOL state:", zfs.PoolStateToName(pstate)) } -func TestPool_VDevTree(t *testing.T) { - type fields struct { - poolName string - } - tests := []struct { - name string - fields fields - wantErr bool - }{ - // TODO: Add test cases. - { - name: "test1", - fields: fields{"TESTPOOL"}, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - pool, _ := zfs.PoolOpen(tt.fields.poolName) - defer pool.Close() - gotVdevs, err := pool.VDevTree() - if (err != nil) != tt.wantErr { - t.Errorf("Pool.VDevTree() error = %v, wantErr %v", err, tt.wantErr) - return - } - jsonData, _ := json.MarshalIndent(gotVdevs, "", "\t") - t.Logf("gotVdevs: %s", string(jsonData)) - }) - } -} +// func TestPool_VDevTree(t *testing.T) { +// type fields struct { +// poolName string +// } +// tests := []struct { +// name string +// fields fields +// wantErr bool +// }{ +// // TODO: Add test cases. +// { +// name: "test1", +// fields: fields{"TESTPOOL"}, +// wantErr: false, +// }, +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// pool, _ := zfs.PoolOpen(tt.fields.poolName) +// defer pool.Close() +// gotVdevs, err := pool.VDevTree() +// if (err != nil) != tt.wantErr { +// t.Errorf("Pool.VDevTree() error = %v, wantErr %v", err, tt.wantErr) +// return +// } +// jsonData, _ := json.MarshalIndent(gotVdevs, "", "\t") +// t.Logf("gotVdevs: %s", string(jsonData)) +// }) +// } +// } From 6d3c5e3bd47dd18f3f6ca212138c8dec7757578b Mon Sep 17 00:00:00 2001 From: Faruk Kasumovic Date: Fri, 9 Nov 2018 16:08:20 +0100 Subject: [PATCH 10/10] Prevent race condition on zfs-linux internal implementation of access to mnttab --- zfs.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/zfs.go b/zfs.go index 4956c05..17f2456 100644 --- a/zfs.go +++ b/zfs.go @@ -182,8 +182,10 @@ func DatasetCreate(path string, dtype DatasetType, // and cleanup dataset object/s from memory) func (d *Dataset) Close() { // path, _ := d.Path() + Global.Mtx.Lock() C.dataset_list_close(d.list) d.list = nil + Global.Mtx.Unlock() for _, cd := range d.Children { cd.Close() } @@ -320,6 +322,8 @@ func (d *Dataset) GetProperty(p Prop) (prop Property, err error) { err = errors.New(msgDatasetIsNil) return } + Global.Mtx.Lock() + defer Global.Mtx.Unlock() plist := C.read_dataset_property(d.list, C.int(p)) if plist == nil { err = LastError() @@ -492,6 +496,8 @@ func (d *Dataset) IsMounted() (mounted bool, where string) { if d.list == nil { return } + Global.Mtx.Lock() + defer Global.Mtx.Unlock() mp := C.dataset_is_mounted(d.list) // defer C.free(mp) if mounted = (mp != nil); mounted { @@ -503,6 +509,8 @@ func (d *Dataset) IsMounted() (mounted bool, where string) { // Mount the given filesystem. func (d *Dataset) Mount(options string, flags int) (err error) { + Global.Mtx.Lock() + defer Global.Mtx.Unlock() if d.list == nil { err = errors.New(msgDatasetIsNil) return