2017-06-15 14:12:39 +02:00
|
|
|
package zfs
|
|
|
|
|
|
|
|
// #include <stdlib.h>
|
|
|
|
// #include <libzfs.h>
|
|
|
|
// #include "common.h"
|
|
|
|
// #include "zpool.h"
|
|
|
|
// #include "zfs.h"
|
2018-07-11 16:20:37 +02:00
|
|
|
// #include <memory.h>
|
|
|
|
// #include <string.h>
|
2017-06-15 14:12:39 +02:00
|
|
|
import "C"
|
|
|
|
import (
|
|
|
|
"fmt"
|
2018-07-11 16:20:37 +02:00
|
|
|
"io/ioutil"
|
2017-06-15 14:12:39 +02:00
|
|
|
"os"
|
2018-07-11 16:20:37 +02:00
|
|
|
"regexp"
|
|
|
|
"strconv"
|
2017-06-15 14:12:39 +02:00
|
|
|
"strings"
|
2018-07-11 16:20:37 +02:00
|
|
|
"time"
|
2017-06-15 14:12:39 +02:00
|
|
|
"unsafe"
|
|
|
|
)
|
|
|
|
|
2020-07-16 13:28:47 +02:00
|
|
|
// SendFlags send flags
|
2017-06-15 14:12:39 +02:00
|
|
|
type SendFlags struct {
|
2019-12-10 15:37:02 +01:00
|
|
|
Verbose bool // -v
|
|
|
|
Replicate bool // -R
|
|
|
|
DoAll bool // -I
|
|
|
|
FromOrigin bool // -o
|
|
|
|
Dedup bool // -D
|
|
|
|
Props bool // -p
|
|
|
|
DryRun bool // -n
|
|
|
|
Parsable bool // -P
|
2020-07-16 13:28:47 +02:00
|
|
|
Progress bool // show progress (ie. -v)
|
2019-12-10 15:37:02 +01:00
|
|
|
LargeBlock bool // -L
|
|
|
|
EmbedData bool // -e
|
|
|
|
Compress bool // -c
|
2020-07-16 13:28:47 +02:00
|
|
|
Raw bool // raw encrypted records are permitted
|
|
|
|
Backup bool // only send received properties (ie. -b)
|
|
|
|
Holds bool // include snapshot holds in send stream
|
2017-06-15 14:12:39 +02:00
|
|
|
}
|
|
|
|
|
2020-07-16 13:28:47 +02:00
|
|
|
// RecvFlags receive flags
|
2017-06-15 14:12:39 +02:00
|
|
|
type RecvFlags struct {
|
2019-12-10 15:37:02 +01:00
|
|
|
Verbose bool // -v
|
|
|
|
IsPrefix bool // -d
|
|
|
|
IsTail bool // -e
|
|
|
|
DryRun bool // -n
|
|
|
|
Force bool // -r
|
|
|
|
Resumable bool // -s
|
|
|
|
NoMount bool // -u
|
2017-06-15 14:12:39 +02:00
|
|
|
CanmountOff bool
|
|
|
|
ByteSwap bool
|
|
|
|
}
|
|
|
|
|
2020-07-16 13:29:40 +02:00
|
|
|
// ResumeToken - informations extracted from resume token
|
|
|
|
type ResumeToken struct {
|
|
|
|
ToName string
|
|
|
|
FromName string
|
|
|
|
Object uint64
|
|
|
|
Offset uint64
|
|
|
|
ToGUID uint64
|
|
|
|
FromGUID uint64
|
|
|
|
Bytes uint64
|
|
|
|
LargeBlock bool
|
|
|
|
EmbedOk bool
|
|
|
|
CompressOk bool
|
|
|
|
RawOk bool
|
|
|
|
}
|
|
|
|
|
2017-06-15 14:12:39 +02:00
|
|
|
func to_boolean_t(a bool) C.boolean_t {
|
|
|
|
if a {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func to_sendflags_t(flags *SendFlags) (cflags *C.sendflags_t) {
|
|
|
|
cflags = C.alloc_sendflags()
|
2020-07-16 13:28:47 +02:00
|
|
|
cflags.verbose = to_boolean_t(flags.Verbose)
|
2017-06-15 14:12:39 +02:00
|
|
|
cflags.replicate = to_boolean_t(flags.Replicate)
|
|
|
|
cflags.doall = to_boolean_t(flags.DoAll)
|
|
|
|
cflags.fromorigin = to_boolean_t(flags.FromOrigin)
|
|
|
|
cflags.dedup = to_boolean_t(flags.Dedup)
|
|
|
|
cflags.props = to_boolean_t(flags.Props)
|
|
|
|
cflags.dryrun = to_boolean_t(flags.DryRun)
|
2018-07-11 16:20:37 +02:00
|
|
|
cflags.parsable = to_boolean_t(flags.Parsable)
|
|
|
|
cflags.progress = to_boolean_t(flags.Progress)
|
2017-06-15 14:12:39 +02:00
|
|
|
cflags.largeblock = to_boolean_t(flags.LargeBlock)
|
|
|
|
cflags.embed_data = to_boolean_t(flags.EmbedData)
|
2018-07-11 16:20:37 +02:00
|
|
|
cflags.compress = to_boolean_t(flags.Compress)
|
2020-07-16 13:28:47 +02:00
|
|
|
cflags.raw = to_boolean_t(flags.Raw)
|
|
|
|
cflags.backup = to_boolean_t(flags.Backup)
|
|
|
|
cflags.holds = to_boolean_t(flags.Holds)
|
2017-06-15 14:12:39 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func to_recvflags_t(flags *RecvFlags) (cflags *C.recvflags_t) {
|
|
|
|
cflags = C.alloc_recvflags()
|
|
|
|
cflags.verbose = to_boolean_t(flags.Verbose)
|
|
|
|
cflags.isprefix = to_boolean_t(flags.IsPrefix)
|
|
|
|
cflags.istail = to_boolean_t(flags.IsTail)
|
|
|
|
cflags.dryrun = to_boolean_t(flags.DryRun)
|
|
|
|
cflags.force = to_boolean_t(flags.Force)
|
|
|
|
cflags.canmountoff = to_boolean_t(flags.CanmountOff)
|
2020-01-17 14:00:56 +01:00
|
|
|
cflags.resumable = to_boolean_t(flags.Resumable)
|
2017-06-15 14:12:39 +02:00
|
|
|
cflags.byteswap = to_boolean_t(flags.ByteSwap)
|
|
|
|
cflags.nomount = to_boolean_t(flags.NoMount)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Dataset) send(FromName string, outf *os.File, flags *SendFlags) (err error) {
|
|
|
|
var cfromname, ctoname *C.char
|
|
|
|
var dpath string
|
|
|
|
var pd Dataset
|
|
|
|
|
|
|
|
if d.Type != DatasetTypeSnapshot || (len(FromName) > 0 && strings.Contains(FromName, "#")) {
|
|
|
|
err = fmt.Errorf(
|
|
|
|
"Unsupported method on filesystem or bookmark. Use func SendOne() for that purpose.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
cflags := to_sendflags_t(flags)
|
|
|
|
defer C.free(unsafe.Pointer(cflags))
|
|
|
|
if dpath, err = d.Path(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
2018-08-15 20:23:12 +02:00
|
|
|
sendparams := strings.Split(dpath, "@")
|
|
|
|
parent := sendparams[0]
|
2017-06-15 14:12:39 +02:00
|
|
|
if len(FromName) > 0 {
|
2018-08-15 20:23:12 +02:00
|
|
|
if FromName[0] == '@' {
|
|
|
|
FromName = FromName[1:]
|
|
|
|
} else if strings.Contains(FromName, "/") {
|
|
|
|
from := strings.Split(FromName, "@")
|
|
|
|
if len(from) > 0 {
|
|
|
|
FromName = from[1]
|
|
|
|
}
|
2017-06-15 14:12:39 +02:00
|
|
|
}
|
|
|
|
cfromname = C.CString(FromName)
|
|
|
|
defer C.free(unsafe.Pointer(cfromname))
|
|
|
|
}
|
|
|
|
ctoname = C.CString(sendparams[1])
|
|
|
|
defer C.free(unsafe.Pointer(ctoname))
|
|
|
|
if pd, err = DatasetOpen(parent); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer pd.Close()
|
|
|
|
cerr := C.zfs_send(pd.list.zh, cfromname, ctoname, cflags, C.int(outf.Fd()), nil, nil, nil)
|
|
|
|
if cerr != 0 {
|
|
|
|
err = LastError()
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-12-10 15:37:02 +01:00
|
|
|
func (d *Dataset) SendResume(outf *os.File, flags *SendFlags, receiveResumeToken string) (err error) {
|
|
|
|
if d.Type != DatasetTypeSnapshot {
|
|
|
|
err = fmt.Errorf("Unsupported method on filesystem or bookmark. Use func SendOne() for that purpose.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var dpath string
|
|
|
|
var pd Dataset
|
|
|
|
|
|
|
|
cflags := to_sendflags_t(flags)
|
|
|
|
defer C.free(unsafe.Pointer(cflags))
|
|
|
|
if dpath, err = d.Path(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
sendparams := strings.Split(dpath, "@")
|
|
|
|
parent := sendparams[0]
|
|
|
|
|
|
|
|
if pd, err = DatasetOpen(parent); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer pd.Close()
|
|
|
|
|
|
|
|
cReceiveResumeToken := C.CString(receiveResumeToken)
|
|
|
|
defer C.free(unsafe.Pointer(cReceiveResumeToken))
|
|
|
|
|
|
|
|
clerr := C.zfs_send_resume(C.libzfsHandle, cflags, C.int(outf.Fd()), cReceiveResumeToken)
|
|
|
|
if clerr != 0 {
|
|
|
|
err = LastError()
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-06-15 14:12:39 +02:00
|
|
|
func (d *Dataset) Send(outf *os.File, flags SendFlags) (err error) {
|
|
|
|
if flags.Replicate {
|
|
|
|
flags.DoAll = true
|
|
|
|
}
|
|
|
|
err = d.send("", outf, &flags)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Dataset) SendFrom(FromName string, outf *os.File, flags SendFlags) (err error) {
|
|
|
|
var porigin Property
|
|
|
|
var from, dest []string
|
|
|
|
if err = d.ReloadProperties(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
porigin, _ = d.GetProperty(DatasetPropOrigin)
|
|
|
|
if len(porigin.Value) > 0 && porigin.Value == FromName {
|
|
|
|
FromName = ""
|
|
|
|
flags.FromOrigin = true
|
|
|
|
} else {
|
|
|
|
var dpath string
|
|
|
|
if dpath, err = d.Path(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
dest = strings.Split(dpath, "@")
|
|
|
|
from = strings.Split(FromName, "@")
|
|
|
|
|
|
|
|
if len(from[0]) > 0 && from[0] != dest[0] {
|
|
|
|
err = fmt.Errorf("Incremental source must be in same filesystem.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if len(from) < 2 || strings.Contains(from[1], "@") || strings.Contains(from[1], "/") {
|
|
|
|
err = fmt.Errorf("Invalid incremental source.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2018-08-15 20:23:12 +02:00
|
|
|
err = d.send("@"+from[1], outf, &flags)
|
2017-06-15 14:12:39 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-07-11 16:20:37 +02:00
|
|
|
// 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 {
|
2017-06-15 14:12:39 +02:00
|
|
|
return
|
|
|
|
}
|
2018-07-11 16:20:37 +02:00
|
|
|
defer r.Close()
|
|
|
|
go func() {
|
|
|
|
var tmpe error
|
2018-11-15 21:58:33 +01:00
|
|
|
saveOut := C.redirect_libzfs_stdout(C.int(w.Fd()))
|
|
|
|
if saveOut < 0 {
|
|
|
|
tmpe = fmt.Errorf("Redirection of zfslib stdout failed %d", saveOut)
|
2018-07-11 16:20:37 +02:00
|
|
|
} else {
|
|
|
|
tmpe = d.send(FromName, w, &flags)
|
2018-11-15 21:58:33 +01:00
|
|
|
C.restore_libzfs_stdout(saveOut)
|
2017-06-15 14:12:39 +02:00
|
|
|
}
|
2018-07-11 16:20:37 +02:00
|
|
|
w.Close()
|
|
|
|
errch <- tmpe
|
|
|
|
}()
|
|
|
|
|
2019-05-31 10:40:03 +02:00
|
|
|
r.SetReadDeadline(time.Now().Add(60 * time.Second))
|
2018-07-11 16:20:37 +02:00
|
|
|
var data []byte
|
|
|
|
if data, err = ioutil.ReadAll(r); err != nil {
|
|
|
|
return
|
2017-06-15 14:12:39 +02:00
|
|
|
}
|
2018-07-11 16:20:37 +02:00
|
|
|
// parse size
|
|
|
|
var sizeRe *regexp.Regexp
|
|
|
|
if sizeRe, err = regexp.Compile("size[ \t]*([0-9]+)"); err != nil {
|
|
|
|
return
|
2017-06-15 14:12:39 +02:00
|
|
|
}
|
2018-07-11 16:20:37 +02:00
|
|
|
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
|
|
|
|
}
|
2017-06-15 14:12:39 +02:00
|
|
|
}
|
2018-07-11 16:20:37 +02:00
|
|
|
err = <-errch
|
2017-06-15 14:12:39 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-07-11 16:20:37 +02:00
|
|
|
// Receive - receive snapshot stream
|
2017-08-04 13:12:41 +02:00
|
|
|
func (d *Dataset) Receive(inf *os.File, flags RecvFlags) (err error) {
|
2017-06-15 14:12:39 +02:00
|
|
|
var dpath string
|
|
|
|
if dpath, err = d.Path(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
props := C.new_property_nvlist()
|
|
|
|
if props == nil {
|
|
|
|
err = fmt.Errorf("Out of memory func (d *Dataset) Recv()")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer C.nvlist_free(props)
|
|
|
|
cflags := to_recvflags_t(&flags)
|
|
|
|
defer C.free(unsafe.Pointer(cflags))
|
2017-08-04 13:12:41 +02:00
|
|
|
dest := C.CString(dpath)
|
2017-06-15 14:12:39 +02:00
|
|
|
defer C.free(unsafe.Pointer(dest))
|
2018-07-02 14:37:54 +02:00
|
|
|
ec := C.zfs_receive(C.libzfsHandle, dest, nil, cflags, C.int(inf.Fd()), nil)
|
2017-06-15 14:12:39 +02:00
|
|
|
if ec != 0 {
|
|
|
|
err = fmt.Errorf("ZFS receive of %s failed. %s", C.GoString(dest), LastError().Error())
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2020-07-16 13:29:40 +02:00
|
|
|
|
|
|
|
// Unpack unpack resume token
|
|
|
|
func (rt *ResumeToken) Unpack(token string) (err error) {
|
|
|
|
ctoken := C.CString(token)
|
|
|
|
defer C.free(unsafe.Pointer(ctoken))
|
|
|
|
resume_nvl := C.zfs_send_resume_token_to_nvlist(C.libzfsHandle, ctoken)
|
|
|
|
defer C.nvlist_free(resume_nvl)
|
|
|
|
if resume_nvl == nil {
|
|
|
|
err = fmt.Errorf("Failed to unpack resume token: %s", LastError().Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if rt.ToName, err = rt.lookupString(resume_nvl, "toname"); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
rt.FromName, _ = rt.lookupString(resume_nvl, "fromname")
|
|
|
|
|
|
|
|
if rt.Object, err = rt.lookupUnit64(resume_nvl, "object"); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if rt.Offset, err = rt.lookupUnit64(resume_nvl, "offset"); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if rt.Bytes, err = rt.lookupUnit64(resume_nvl, "bytes"); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if rt.ToGUID, err = rt.lookupUnit64(resume_nvl, "toguid"); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
rt.FromGUID, _ = rt.lookupUnit64(resume_nvl, "fromguid")
|
|
|
|
|
|
|
|
rt.LargeBlock = rt.exist(resume_nvl, "largeblockok")
|
|
|
|
rt.EmbedOk = rt.exist(resume_nvl, "embedok")
|
|
|
|
rt.CompressOk = rt.exist(resume_nvl, "compressok")
|
|
|
|
rt.RawOk = rt.exist(resume_nvl, "rawok")
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rt *ResumeToken) lookupString(nvl *C.nvlist_t, key string) (val string, err error) {
|
|
|
|
var cstr *C.char
|
|
|
|
ckey := C.CString(key)
|
|
|
|
defer C.free(unsafe.Pointer(ckey))
|
|
|
|
defer C.free(unsafe.Pointer(cstr))
|
|
|
|
rc := C.nvlist_lookup_string(nvl, ckey, &cstr)
|
|
|
|
if rc != 0 {
|
|
|
|
err = fmt.Errorf("resume token is corrupt")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
val = C.GoString(cstr)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rt *ResumeToken) lookupUnit64(nvl *C.nvlist_t, key string) (val uint64, err error) {
|
|
|
|
var num C.uint64_t
|
|
|
|
ckey := C.CString(key)
|
|
|
|
defer C.free(unsafe.Pointer(ckey))
|
|
|
|
rc := C.nvlist_lookup_uint64(nvl, ckey, &num)
|
|
|
|
if rc != 0 {
|
|
|
|
err = fmt.Errorf("resume token is corrupt")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
val = uint64(num)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rt *ResumeToken) exist(nvl *C.nvlist_t, key string) (val bool) {
|
|
|
|
ckey := C.CString(key)
|
|
|
|
defer C.free(unsafe.Pointer(ckey))
|
|
|
|
rc := C.nvlist_exists(nvl, ckey)
|
|
|
|
val = (rc != 0)
|
|
|
|
return
|
|
|
|
}
|