package zfs // #include // #include // #include "common.h" // #include "zpool.h" // #include "zfs.h" // #include // #include import "C" import ( "fmt" "io/ioutil" "os" "regexp" "strconv" "strings" "time" "unsafe" ) // SendFlags send flags type SendFlags struct { Verbose bool // -v Replicate bool // -R DoAll bool // -I FromOrigin bool // -o Dedup bool // -D Props bool // -p DryRun bool // -n Parsable bool // -P Progress bool // show progress (ie. -v) LargeBlock bool // -L EmbedData bool // -e Compress bool // -c Raw bool // raw encrypted records are permitted Backup bool // only send received properties (ie. -b) Holds bool // include snapshot holds in send stream } // RecvFlags receive flags type RecvFlags struct { Verbose bool // -v IsPrefix bool // -d IsTail bool // -e DryRun bool // -n Force bool // -r Resumable bool // -s NoMount bool // -u CanmountOff bool ByteSwap bool } // 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 } 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() cflags.verbose = to_boolean_t(flags.Verbose) 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) 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.raw = to_boolean_t(flags.Raw) cflags.backup = to_boolean_t(flags.Backup) cflags.holds = to_boolean_t(flags.Holds) 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) cflags.resumable = to_boolean_t(flags.Resumable) 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 } sendparams := strings.Split(dpath, "@") parent := sendparams[0] if len(FromName) > 0 { 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)) } 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 } 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 } 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 } } err = d.send("@"+from[1], outf, &flags) return } // 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 } defer r.Close() go func() { var tmpe error saveOut := C.redirect_libzfs_stdout(C.int(w.Fd())) if saveOut < 0 { tmpe = fmt.Errorf("Redirection of zfslib stdout failed %d", saveOut) } else { tmpe = d.send(FromName, w, &flags) C.restore_libzfs_stdout(saveOut) } w.Close() errch <- tmpe }() r.SetReadDeadline(time.Now().Add(60 * 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 } } 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 { 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)) dest := C.CString(dpath) defer C.free(unsafe.Pointer(dest)) 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()) } return } // 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 }