From 1b47551b8717193bb27f9f49501de5fb34c8064c Mon Sep 17 00:00:00 2001 From: Faruk Kasumovic Date: Thu, 15 Jun 2017 14:12:39 +0200 Subject: [PATCH] - ZFS send/receive --- common.go | 5 + sendrecv.go | 269 ++++++++++++++++++++++++++++++++++++++++++++++++++++ zfs.c | 29 +++++- zfs.go | 2 +- zfs.h | 108 +++++++++++++++++++++ 5 files changed, 409 insertions(+), 4 deletions(-) create mode 100644 sendrecv.go diff --git a/common.go b/common.go index 0b0ab88..b7949d0 100644 --- a/common.go +++ b/common.go @@ -22,6 +22,7 @@ import "C" import ( "errors" + "sync" ) // VDevType type of device in the pool @@ -68,6 +69,10 @@ type Property struct { Source string } +var Global struct { + Mtx sync.Mutex +} + // Pool status const ( /* diff --git a/sendrecv.go b/sendrecv.go new file mode 100644 index 0000000..d78eb5b --- /dev/null +++ b/sendrecv.go @@ -0,0 +1,269 @@ +package zfs + +// #include +// #include +// #include "common.h" +// #include "zpool.h" +// #include "zfs.h" +import "C" +import ( + "fmt" + "os" + "path" + "strings" + "syscall" + "unsafe" +) + +type SendFlags struct { + Verbose bool + Replicate bool + DoAll bool + FromOrigin bool + Dedup bool + Props bool + DryRun bool + // Parsable bool + // Progress bool + LargeBlock bool + EmbedData bool + // Compress bool +} + +type RecvFlags struct { + Verbose bool + IsPrefix bool + IsTail bool + DryRun bool + Force bool + CanmountOff bool + Resumable bool + ByteSwap bool + NoMount 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) + 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 + } + if len(FromName) > 0 { + if FromName[0] == '#' || FromName[0] == '@' { + FromName = dpath + FromName + } + 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 { + 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) SendOne(FromName string, outf *os.File, flags *SendFlags) (err error) { + var cfromname, ctoname *C.char + var dpath string + var lzc_send_flags uint32 + + if d.Type == DatasetTypeSnapshot || (len(FromName) > 0 && !strings.Contains(FromName, "#")) { + err = fmt.Errorf( + "Unsupported with snapshot. Use func Send() for that purpose.") + return + } + if flags.Replicate || flags.DoAll || flags.Props || flags.Dedup || flags.DryRun { + err = fmt.Errorf("Unsupported flag with filesystem or bookmark.") + return + } + + if flags.LargeBlock { + lzc_send_flags |= C.LZC_SEND_FLAG_LARGE_BLOCK + } + if flags.EmbedData { + lzc_send_flags |= C.LZC_SEND_FLAG_EMBED_DATA + } + // if (flags.Compress) + // lzc_send_flags |= LZC_SEND_FLAG_COMPRESS; + if dpath, err = d.Path(); err != nil { + return + } + if len(FromName) > 0 { + if FromName[0] == '#' || FromName[0] == '@' { + FromName = dpath + FromName + } + cfromname = C.CString(FromName) + defer C.free(unsafe.Pointer(cfromname)) + } + ctoname = C.CString(path.Base(dpath)) + defer C.free(unsafe.Pointer(ctoname)) + cerr := C.zfs_send_one(d.list.zh, cfromname, C.int(outf.Fd()), lzc_send_flags) + if cerr != 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 +} + +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 { + return + } + zc := C.new_zfs_cmd() + defer C.free(unsafe.Pointer(zc)) + dpath = strings.Split(dpath, "@")[0] + if len(FromName) > 0 { + + 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 { + 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) + return +} + +func (d *Dataset) Receive(name string, 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 + "/" + name) + defer C.free(unsafe.Pointer(dest)) + ec := C.zfs_receive(C.libzfsHandle, dest, cflags, C.int(inf.Fd()), nil) + if ec != 0 { + err = fmt.Errorf("ZFS receive of %s failed. %s", C.GoString(dest), LastError().Error()) + } + return +} diff --git a/zfs.c b/zfs.c index a54affc..96bd849 100644 --- a/zfs.c +++ b/zfs.c @@ -160,16 +160,16 @@ property_list_t *read_dataset_property(dataset_list_t *dataset, int prop) { int r = 0; zprop_source_t source; char statbuf[INT_MAX_VALUE]; - property_list_ptr list; + property_list_ptr list = NULL; list = new_property_list(); r = zfs_prop_get(dataset->zh, prop, list->value, INT_MAX_VALUE, &source, statbuf, INT_MAX_VALUE, 1); - if (r == 0) { + if (r == 0 && list != NULL) { // strcpy(list->name, zpool_prop_to_name(prop)); zprop_source_tostr(list->source, source); list->property = (int)prop; - } else { + } else if (list != NULL) { free_properties(list); list = NULL; } @@ -226,3 +226,26 @@ char** alloc_cstrings(int size) { void strings_setat(char **a, int at, char *v) { a[at] = v; } + + +sendflags_t *alloc_sendflags() { + sendflags_t *r = malloc(sizeof(sendflags_t)); + memset(r, 0, sizeof(sendflags_t)); + return r; +} +recvflags_t *alloc_recvflags() { + recvflags_t *r = malloc(sizeof(recvflags_t)); + memset(r, 0, sizeof(recvflags_t)); + return r; +} + +struct zfs_cmd *new_zfs_cmd(){ + struct zfs_cmd *cmd = malloc(sizeof(struct zfs_cmd)); + memset(cmd, 0, sizeof(struct zfs_cmd)); + return cmd; +} + +int estimate_send_size(struct zfs_cmd *zc) { + return zfs_ioctl(libzfsHandle, ZFS_IOC_SEND, zc); +} + diff --git a/zfs.go b/zfs.go index e4ec4f3..18caade 100644 --- a/zfs.go +++ b/zfs.go @@ -107,7 +107,7 @@ func DatasetOpen(path string) (d Dataset, err error) { if err == nil { err = fmt.Errorf("dataset not found.") } - println("open failed") + err = fmt.Errorf("%s - %s", err.Error(), path) return } d.Type = DatasetType(C.dataset_type(d.list)) diff --git a/zfs.h b/zfs.h index d70566e..3a43c82 100644 --- a/zfs.h +++ b/zfs.h @@ -10,6 +10,107 @@ struct dataset_list { void *pnext; }; +typedef struct zfs_share { + uint64_t z_exportdata; + uint64_t z_sharedata; + uint64_t z_sharetype; /* 0 = share, 1 = unshare */ + uint64_t z_sharemax; /* max length of share string */ +} zfs_share_t; + +struct drr_begin { + uint64_t drr_magic; + uint64_t drr_versioninfo; /* was drr_version */ + uint64_t drr_creation_time; + dmu_objset_type_t drr_type; + uint32_t drr_flags; + uint64_t drr_toguid; + uint64_t drr_fromguid; + char drr_toname[MAXNAMELEN]; +} drr_begin; + +/* + * A limited number of zpl level stats are retrievable + * with an ioctl. zfs diff is the current consumer. + */ +typedef struct zfs_stat { + uint64_t zs_gen; + uint64_t zs_mode; + uint64_t zs_links; + uint64_t zs_ctime[2]; +} zfs_stat_t; + +typedef struct zinject_record { + uint64_t zi_objset; + uint64_t zi_object; + uint64_t zi_start; + uint64_t zi_end; + uint64_t zi_guid; + uint32_t zi_level; + uint32_t zi_error; + uint64_t zi_type; + uint32_t zi_freq; + uint32_t zi_failfast; + char zi_func[MAXNAMELEN]; + uint32_t zi_iotype; + int32_t zi_duration; + uint64_t zi_timer; + uint64_t zi_nlanes; + uint32_t zi_cmd; + uint32_t zi_pad; +} zinject_record_t; + +typedef struct dmu_objset_stats { + uint64_t dds_num_clones; /* number of clones of this */ + uint64_t dds_creation_txg; + uint64_t dds_guid; + dmu_objset_type_t dds_type; + uint8_t dds_is_snapshot; + uint8_t dds_inconsistent; + char dds_origin[ZFS_MAX_DATASET_NAME_LEN]; +} dmu_objset_stats_t; + +typedef struct zfs_cmd { + char zc_name[MAXPATHLEN]; /* name of pool or dataset */ + uint64_t zc_nvlist_src; /* really (char *) */ + uint64_t zc_nvlist_src_size; + uint64_t zc_nvlist_dst; /* really (char *) */ + uint64_t zc_nvlist_dst_size; + boolean_t zc_nvlist_dst_filled; /* put an nvlist in dst? */ + int zc_pad2; + + /* + * The following members are for legacy ioctls which haven't been + * converted to the new method. + */ + uint64_t zc_history; /* really (char *) */ + char zc_value[MAXPATHLEN * 2]; + char zc_string[MAXNAMELEN]; + uint64_t zc_guid; + uint64_t zc_nvlist_conf; /* really (char *) */ + uint64_t zc_nvlist_conf_size; + uint64_t zc_cookie; + uint64_t zc_objset_type; + uint64_t zc_perm_action; + uint64_t zc_history_len; + uint64_t zc_history_offset; + uint64_t zc_obj; + uint64_t zc_iflags; /* internal to zfs(7fs) */ + zfs_share_t zc_share; + dmu_objset_stats_t zc_objset_stats; + struct drr_begin zc_begin_record; + zinject_record_t zc_inject_record; + uint32_t zc_defer_destroy; + uint32_t zc_flags; + uint64_t zc_action_handle; + int zc_cleanup_fd; + uint8_t zc_simple; + uint8_t zc_pad[3]; /* alignment */ + uint64_t zc_sendobj; + uint64_t zc_fromobj; + uint64_t zc_createtxg; + zfs_stat_t zc_stat; +} zfs_cmd_t; + typedef struct dataset_list dataset_list_t; typedef struct dataset_list* dataset_list_ptr; @@ -46,5 +147,12 @@ property_list_t *read_user_property(dataset_list_t *dataset, const char* prop); char** alloc_cstrings(int size); void strings_setat(char **a, int at, char *v); +sendflags_t *alloc_sendflags(); +recvflags_t *alloc_recvflags(); + + +struct zfs_cmd *new_zfs_cmd(); +int estimate_send_size(struct zfs_cmd *zc); + #endif /* SERVERWARE_ZFS_H */