829 lines
21 KiB
Go
829 lines
21 KiB
Go
package zfs
|
|
|
|
// #include <stdlib.h>
|
|
// #include <libzfs.h>
|
|
// #include "common.h"
|
|
// #include "zpool.h"
|
|
// #include "zfs.h"
|
|
import "C"
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"path"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
"unsafe"
|
|
)
|
|
|
|
const (
|
|
msgDatasetIsNil = "Dataset handle not initialized or its closed"
|
|
)
|
|
|
|
// DatasetProperties type is map of dataset or volume properties prop -> value
|
|
type DatasetProperties map[Prop]string
|
|
|
|
// DatasetType defines enum of dataset types
|
|
type DatasetType int32
|
|
|
|
const (
|
|
// DatasetTypeFilesystem - file system dataset
|
|
DatasetTypeFilesystem DatasetType = (1 << 0)
|
|
// DatasetTypeSnapshot - snapshot of dataset
|
|
DatasetTypeSnapshot = (1 << 1)
|
|
// DatasetTypeVolume - volume (virtual block device) dataset
|
|
DatasetTypeVolume = (1 << 2)
|
|
// DatasetTypePool - pool dataset
|
|
DatasetTypePool = (1 << 3)
|
|
// DatasetTypeBookmark - bookmark dataset
|
|
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
|
|
Type DatasetType
|
|
Properties map[Prop]Property
|
|
Children []Dataset
|
|
}
|
|
|
|
func (d *Dataset) openChildren() (err error) {
|
|
d.Children = make([]Dataset, 0, 5)
|
|
list := C.dataset_list_children(d.list)
|
|
for list != nil {
|
|
dataset := Dataset{list: list}
|
|
dataset.Type = DatasetType(C.dataset_type(d.list))
|
|
dataset.Properties = make(map[Prop]Property)
|
|
err = dataset.ReloadProperties()
|
|
if err != nil {
|
|
return
|
|
}
|
|
d.Children = append(d.Children, dataset)
|
|
list = C.dataset_next(list)
|
|
}
|
|
for ci := range d.Children {
|
|
if err = d.Children[ci].openChildren(); err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// DatasetOpenAll recursive get handles to all available datasets on system
|
|
// (file-systems, volumes or snapshots).
|
|
func DatasetOpenAll() (datasets []Dataset, err error) {
|
|
var dataset Dataset
|
|
dataset.list = C.dataset_list_root()
|
|
for dataset.list != nil {
|
|
dataset.Type = DatasetType(C.dataset_type(dataset.list))
|
|
err = dataset.ReloadProperties()
|
|
if err != nil {
|
|
return
|
|
}
|
|
datasets = append(datasets, dataset)
|
|
dataset.list = C.dataset_next(dataset.list)
|
|
}
|
|
for ci := range datasets {
|
|
if err = datasets[ci].openChildren(); err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// DatasetCloseAll close all datasets in slice and all of its recursive
|
|
// children datasets
|
|
func DatasetCloseAll(datasets []Dataset) {
|
|
for _, d := range datasets {
|
|
d.Close()
|
|
}
|
|
}
|
|
|
|
// 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))
|
|
|
|
if d.list == nil || d.list.zh == nil {
|
|
err = LastError()
|
|
if err == nil {
|
|
err = fmt.Errorf("dataset not found.")
|
|
}
|
|
err = fmt.Errorf("%s - %s", err.Error(), path)
|
|
return
|
|
}
|
|
d.Type = DatasetType(C.dataset_type(d.list))
|
|
d.Properties = make(map[Prop]Property)
|
|
err = d.ReloadProperties()
|
|
if err != nil {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
func datasetPropertiesTonvlist(props map[Prop]Property) (
|
|
cprops C.nvlist_ptr, err error) {
|
|
// convert properties to nvlist C type
|
|
cprops = C.new_property_nvlist()
|
|
if cprops == nil {
|
|
err = errors.New("Failed to allocate properties")
|
|
return
|
|
}
|
|
for prop, value := range props {
|
|
csValue := C.CString(value.Value)
|
|
r := C.property_nvlist_add(
|
|
cprops, C.zfs_prop_to_name(C.zfs_prop_t(prop)), csValue)
|
|
C.free(unsafe.Pointer(csValue))
|
|
if r != 0 {
|
|
err = errors.New("Failed to convert property")
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// DatasetCreate create a new filesystem or volume on path representing
|
|
// pool/dataset or pool/parent/dataset
|
|
func DatasetCreate(path string, dtype DatasetType,
|
|
props map[Prop]Property) (d Dataset, err error) {
|
|
var cprops C.nvlist_ptr
|
|
if cprops, err = datasetPropertiesTonvlist(props); err != nil {
|
|
return
|
|
}
|
|
defer C.nvlist_free(cprops)
|
|
|
|
csPath := C.CString(path)
|
|
errcode := C.dataset_create(csPath, C.zfs_type_t(dtype), cprops)
|
|
C.free(unsafe.Pointer(csPath))
|
|
if errcode != 0 {
|
|
err = LastError()
|
|
return
|
|
}
|
|
return DatasetOpen(path)
|
|
}
|
|
|
|
// Close close dataset and all its recursive children datasets (close handle
|
|
// 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()
|
|
}
|
|
}
|
|
|
|
// reOpen - close and open dataset. Not thread safe!
|
|
func (d *Dataset) reOpen() (err error) {
|
|
d.Close()
|
|
*d, err = DatasetOpen(d.Properties[DatasetPropName].Value)
|
|
return
|
|
}
|
|
|
|
// Destroy destroys the dataset. The caller must make sure that the filesystem
|
|
// isn't mounted, and that there are no active dependents. Set Defer argument
|
|
// to true to defer destruction for when dataset is not in use. Call Close() to
|
|
// cleanup memory.
|
|
func (d *Dataset) Destroy(Defer bool) (err error) {
|
|
if len(d.Children) > 0 {
|
|
path, e := d.Path()
|
|
if e != nil {
|
|
return
|
|
}
|
|
dsType, e := d.GetProperty(DatasetPropType)
|
|
if e != nil {
|
|
dsType.Value = err.Error() // just put error (why it didn't fetch property type)
|
|
}
|
|
err = errors.New("Cannot destroy dataset " + path +
|
|
": " + dsType.Value + " has children")
|
|
return
|
|
}
|
|
if d.list != nil {
|
|
if ec := C.dataset_destroy(d.list, booleanT(Defer)); ec != 0 {
|
|
err = LastError()
|
|
}
|
|
} else {
|
|
err = errors.New(msgDatasetIsNil)
|
|
}
|
|
return
|
|
}
|
|
|
|
// IsSnapshot - retrun true if datset is snapshot
|
|
func (d *Dataset) IsSnapshot() (ok bool) {
|
|
path := d.Properties[DatasetPropName].Value
|
|
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
|
|
if path, err = d.Path(); err != nil {
|
|
return
|
|
}
|
|
if !strings.Contains(path, "@") { // not snapshot
|
|
if len(d.Children) > 0 {
|
|
for _, c := range d.Children {
|
|
if err = c.DestroyRecursive(); err != nil {
|
|
return
|
|
}
|
|
// close handle to destroyed child dataset
|
|
c.Close()
|
|
}
|
|
// clear closed children array
|
|
d.Children = make([]Dataset, 0)
|
|
}
|
|
err = d.Destroy(false)
|
|
} else {
|
|
var parent Dataset
|
|
tmp := strings.Split(path, "@")
|
|
ppath, snapname := tmp[0], tmp[1]
|
|
if parent, err = DatasetOpen(ppath); err != nil {
|
|
return
|
|
}
|
|
defer parent.Close()
|
|
if len(parent.Children) > 0 {
|
|
for _, c := range parent.Children {
|
|
if path, err = c.Path(); err != nil {
|
|
return
|
|
}
|
|
if strings.Contains(path, "@") {
|
|
continue // skip other snapshots
|
|
}
|
|
if c, err = DatasetOpen(path + "@" + snapname); err != nil {
|
|
continue
|
|
}
|
|
if err = c.DestroyRecursive(); err != nil {
|
|
c.Close()
|
|
return
|
|
}
|
|
c.Close()
|
|
}
|
|
}
|
|
err = d.Destroy(false)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Pool returns pool dataset belongs to
|
|
func (d *Dataset) Pool() (p Pool, err error) {
|
|
if d.list == nil {
|
|
err = errors.New(msgDatasetIsNil)
|
|
return
|
|
}
|
|
p.list = C.dataset_get_pool(d.list)
|
|
if p.list != nil && p.list.zph != nil {
|
|
err = p.ReloadProperties()
|
|
return
|
|
}
|
|
err = LastError()
|
|
return
|
|
}
|
|
|
|
// PoolName - return name of the pool
|
|
func (d *Dataset) PoolName() string {
|
|
path := d.Properties[DatasetPropName].Value
|
|
i := strings.Index(path, "/")
|
|
if i < 0 {
|
|
return path
|
|
}
|
|
return path[0:i]
|
|
}
|
|
|
|
// ReloadProperties re-read dataset's properties
|
|
func (d *Dataset) ReloadProperties() (err error) {
|
|
if d.list == nil {
|
|
err = errors.New(msgDatasetIsNil)
|
|
return
|
|
}
|
|
d.Properties = make(map[Prop]Property)
|
|
Global.Mtx.Lock()
|
|
defer Global.Mtx.Unlock()
|
|
C.zfs_refresh_properties(d.list.zh)
|
|
for prop := DatasetPropType; prop < DatasetNumProps; prop++ {
|
|
plist := C.read_dataset_property(d.list, C.int(prop))
|
|
if plist == nil {
|
|
continue
|
|
}
|
|
d.Properties[prop] = Property{Value: C.GoString(&(*plist).value[0]),
|
|
Source: C.GoString(&(*plist).source[0])}
|
|
C.free_properties(plist)
|
|
}
|
|
return
|
|
}
|
|
|
|
// GetProperty reload and return single specified property. This also reloads requested
|
|
// property in Properties map.
|
|
func (d *Dataset) GetProperty(p Prop) (prop Property, err error) {
|
|
if d.list == nil {
|
|
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()
|
|
return
|
|
}
|
|
defer C.free_properties(plist)
|
|
prop = Property{Value: C.GoString(&(*plist).value[0]),
|
|
Source: C.GoString(&(*plist).source[0])}
|
|
d.Properties[p] = prop
|
|
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)
|
|
return
|
|
}
|
|
csp := C.CString(p)
|
|
defer C.free(unsafe.Pointer(csp))
|
|
plist := C.read_user_property(d.list, csp)
|
|
if plist == nil {
|
|
err = LastError()
|
|
return
|
|
}
|
|
defer C.free_properties(plist)
|
|
prop = Property{Value: C.GoString(&(*plist).value[0]),
|
|
Source: C.GoString(&(*plist).source[0])}
|
|
return
|
|
}
|
|
|
|
// SetProperty set ZFS dataset property to value. Not all properties can be set,
|
|
// some can be set only at creation time and some are read only.
|
|
// Always check if returned error and its description.
|
|
func (d *Dataset) SetProperty(p Prop, value string) (err error) {
|
|
if d.list == nil {
|
|
err = errors.New(msgDatasetIsNil)
|
|
return
|
|
}
|
|
csValue := C.CString(value)
|
|
errcode := C.dataset_prop_set(d.list, C.zfs_prop_t(p), csValue)
|
|
C.free(unsafe.Pointer(csValue))
|
|
if errcode != 0 {
|
|
err = LastError()
|
|
}
|
|
// Update Properties member with change made
|
|
if _, err = d.GetProperty(p); err != nil {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// SetUserProperty -
|
|
func (d *Dataset) SetUserProperty(prop, value string) (err error) {
|
|
if d.list == nil {
|
|
err = errors.New(msgDatasetIsNil)
|
|
return
|
|
}
|
|
csValue := C.CString(value)
|
|
csProp := C.CString(prop)
|
|
errcode := C.dataset_user_prop_set(d.list, csProp, csValue)
|
|
C.free(unsafe.Pointer(csValue))
|
|
C.free(unsafe.Pointer(csProp))
|
|
if errcode != 0 {
|
|
err = LastError()
|
|
}
|
|
return
|
|
}
|
|
|
|
// Clone - clones the dataset. The target must be of the same type as
|
|
// the source.
|
|
func (d *Dataset) Clone(target string, props map[Prop]Property) (rd Dataset, err error) {
|
|
var cprops C.nvlist_ptr
|
|
if d.list == nil {
|
|
err = errors.New(msgDatasetIsNil)
|
|
return
|
|
}
|
|
if cprops, err = datasetPropertiesTonvlist(props); err != nil {
|
|
return
|
|
}
|
|
defer C.nvlist_free(cprops)
|
|
csTarget := C.CString(target)
|
|
defer C.free(unsafe.Pointer(csTarget))
|
|
if errc := C.dataset_clone(d.list, csTarget, cprops); errc != 0 {
|
|
err = LastError()
|
|
return
|
|
}
|
|
rd, err = DatasetOpen(target)
|
|
return
|
|
}
|
|
|
|
// DatasetSnapshot create dataset snapshot. Set recur to true to snapshot child datasets.
|
|
func DatasetSnapshot(path string, recur bool, props map[Prop]Property) (rd Dataset, err error) {
|
|
var cprops C.nvlist_ptr
|
|
if cprops, err = datasetPropertiesTonvlist(props); err != nil {
|
|
return
|
|
}
|
|
defer C.nvlist_free(cprops)
|
|
csPath := C.CString(path)
|
|
defer C.free(unsafe.Pointer(csPath))
|
|
if errc := C.dataset_snapshot(csPath, booleanT(recur), cprops); errc != 0 {
|
|
err = LastError()
|
|
return
|
|
}
|
|
rd, err = DatasetOpen(path)
|
|
return
|
|
}
|
|
|
|
// Path return zfs dataset path/name
|
|
func (d *Dataset) Path() (path string, err error) {
|
|
if d.list == nil {
|
|
err = errors.New(msgDatasetIsNil)
|
|
return
|
|
}
|
|
name := C.dataset_get_name(d.list)
|
|
path = C.GoString(name)
|
|
return
|
|
}
|
|
|
|
// Rollback rollabck's dataset snapshot
|
|
func (d *Dataset) Rollback(snap *Dataset, force bool) (err error) {
|
|
if d.list == nil {
|
|
err = errors.New(msgDatasetIsNil)
|
|
return
|
|
}
|
|
if errc := C.dataset_rollback(d.list, snap.list, booleanT(force)); errc != 0 {
|
|
err = LastError()
|
|
return
|
|
}
|
|
d.ReloadProperties()
|
|
return
|
|
}
|
|
|
|
// Promote promotes dataset clone
|
|
func (d *Dataset) Promote() (err error) {
|
|
if d.list == nil {
|
|
err = errors.New(msgDatasetIsNil)
|
|
return
|
|
}
|
|
if errc := C.dataset_promote(d.list); errc != 0 {
|
|
err = LastError()
|
|
return
|
|
}
|
|
d.ReloadProperties()
|
|
return
|
|
}
|
|
|
|
// Rename dataset
|
|
func (d *Dataset) Rename(newName string, recur,
|
|
forceUnmount bool) (err error) {
|
|
if d.list == nil {
|
|
err = errors.New(msgDatasetIsNil)
|
|
return
|
|
}
|
|
csNewName := C.CString(newName)
|
|
defer C.free(unsafe.Pointer(csNewName))
|
|
if errc := C.dataset_rename(d.list, csNewName,
|
|
booleanT(recur), booleanT(forceUnmount)); errc != 0 {
|
|
err = LastError()
|
|
return
|
|
}
|
|
d.ReloadProperties()
|
|
return
|
|
}
|
|
|
|
// IsMounted checks to see if the mount is active. If the filesystem is mounted,
|
|
// sets in 'where' argument the current mountpoint, and returns true. Otherwise,
|
|
// returns false.
|
|
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 {
|
|
where = C.GoString(mp)
|
|
C.free(unsafe.Pointer(mp))
|
|
}
|
|
return
|
|
}
|
|
|
|
// 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
|
|
}
|
|
csOptions := C.CString(options)
|
|
defer C.free(unsafe.Pointer(csOptions))
|
|
if ec := C.dataset_mount(d.list, csOptions, C.int(flags)); ec != 0 {
|
|
err = LastError()
|
|
}
|
|
return
|
|
}
|
|
|
|
// Unmount the given filesystem.
|
|
func (d *Dataset) Unmount(flags int) (err error) {
|
|
if d.list == nil {
|
|
err = errors.New(msgDatasetIsNil)
|
|
return
|
|
}
|
|
if ec := C.dataset_unmount(d.list, C.int(flags)); ec != 0 {
|
|
err = LastError()
|
|
}
|
|
return
|
|
}
|
|
|
|
// UnmountAll unmount this filesystem and any children inheriting the
|
|
// mountpoint property.
|
|
func (d *Dataset) UnmountAll(flags int) (err error) {
|
|
if d.list == nil {
|
|
err = errors.New(msgDatasetIsNil)
|
|
return
|
|
}
|
|
// This is implemented recursive because zfs_unmountall() didn't work
|
|
if len(d.Children) > 0 {
|
|
for _, c := range d.Children {
|
|
if err = c.UnmountAll(flags); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
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
|
|
// name of choice.
|
|
func DatasetPropertyToName(p Prop) (name string) {
|
|
if p == DatasetNumProps {
|
|
return "numofprops"
|
|
}
|
|
prop := C.zfs_prop_t(p)
|
|
name = C.GoString(C.zfs_prop_to_name(prop))
|
|
return
|
|
}
|
|
|
|
// DestroyPromote - Same as DestroyRecursive() except it will not destroy
|
|
// any dependent clones, but promote them first.
|
|
// This function will navigate any dependency chain
|
|
// of cloned datasets using breadth first search to promote according and let
|
|
// you remove dataset regardless of its cloned dependencies.
|
|
// Note: that this function wan't work when you want to destroy snapshot this way.
|
|
// However it will destroy all snaphsot of destroyed dataset without dependencies,
|
|
// otherwise snapshot will move to promoted clone
|
|
func (d *Dataset) DestroyPromote() (err error) {
|
|
var snaps []Dataset
|
|
var clones []string
|
|
// We need to save list of child snapshots, to destroy them latter
|
|
// since they will be moved to promoted clone
|
|
var psnaps []string
|
|
if clones, err = d.Clones(); err != nil {
|
|
return
|
|
}
|
|
if len(clones) > 0 {
|
|
var cds Dataset
|
|
// For this to always work we need to promote youngest clone
|
|
// in terms of most recent origin snapshot or creation time if
|
|
// cloned from same snapshot
|
|
if cds, err = DatasetOpen(clones[0]); err != nil {
|
|
return
|
|
}
|
|
defer cds.Close()
|
|
// since promote will move the snapshots to promoted dataset
|
|
// we need to check and resolve possible name conflicts
|
|
if snaps, err = d.Snapshots(); err != nil {
|
|
return
|
|
}
|
|
for _, s := range snaps {
|
|
spath := s.Properties[DatasetPropName].Value
|
|
sname := spath[strings.Index(spath, "@"):]
|
|
// conflict and resolve
|
|
if ok, _ := cds.FindSnapshotName(sname); ok {
|
|
// snapshot with the same name already exist
|
|
volname := path.Base(spath[:strings.Index(spath, "@")])
|
|
sname = sname + "." + volname
|
|
if err = s.Rename(spath+"."+volname, false, true); err != nil {
|
|
return
|
|
}
|
|
}
|
|
psnaps = append(psnaps, sname)
|
|
}
|
|
if err = cds.Promote(); err != nil {
|
|
return
|
|
}
|
|
}
|
|
// destroy child datasets, since this works recursive
|
|
for _, cd := range d.Children {
|
|
if err = cd.DestroyPromote(); err != nil {
|
|
return
|
|
}
|
|
}
|
|
d.Children = make([]Dataset, 0)
|
|
if err = d.Destroy(false); err != nil {
|
|
return
|
|
}
|
|
// Load with new promoted snapshots
|
|
if len(clones) > 0 && len(psnaps) > 0 {
|
|
var cds Dataset
|
|
if cds, err = DatasetOpen(clones[0]); err != nil {
|
|
return
|
|
}
|
|
defer cds.Close()
|
|
// try to destroy (promoted) snapshots now
|
|
for _, sname := range psnaps {
|
|
if ok, snap := cds.FindSnapshotName(sname); ok {
|
|
snap.Destroy(false)
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Snapshots - filter and return all snapshots of dataset
|
|
func (d *Dataset) Snapshots() (snaps []Dataset, err error) {
|
|
for _, ch := range d.Children {
|
|
if !ch.IsSnapshot() {
|
|
continue
|
|
}
|
|
snaps = append(snaps, ch)
|
|
}
|
|
return
|
|
}
|
|
|
|
// FindSnapshot - returns true if given path is one of dataset snaphsots
|
|
func (d *Dataset) FindSnapshot(path string) (ok bool, snap Dataset) {
|
|
for _, ch := range d.Children {
|
|
if !ch.IsSnapshot() {
|
|
continue
|
|
}
|
|
if ok = (path == ch.Properties[DatasetPropName].Value); ok {
|
|
snap = ch
|
|
break
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// FindSnapshotName - returns true and snapshot if given snapshot
|
|
// name eg. '@snap1' is one of dataset snaphsots
|
|
func (d *Dataset) FindSnapshotName(name string) (ok bool, snap Dataset) {
|
|
return d.FindSnapshot(d.Properties[DatasetPropName].Value + name)
|
|
}
|
|
|
|
// Clones - get list of all dataset paths cloned from this
|
|
// dataset or this snapshot
|
|
// List is sorted descedent by origin snapshot order
|
|
func (d *Dataset) Clones() (clones []string, err error) {
|
|
// Clones can only live on same pool
|
|
var root Dataset
|
|
var sortDesc []Dataset
|
|
if root, err = DatasetOpen(d.PoolName()); err != nil {
|
|
return
|
|
}
|
|
defer root.Close()
|
|
dIsSnapshot := d.IsSnapshot()
|
|
// USe breadth first search to find all clones
|
|
queue := make(chan Dataset, 1024)
|
|
defer close(queue) // This will close and cleanup all
|
|
queue <- root // start from the root element
|
|
for {
|
|
select {
|
|
case ds := <-queue: // pull from queue (breadth first search)
|
|
for _, ch := range ds.Children {
|
|
origin := ch.Properties[DatasetPropOrigin].Value
|
|
if len(origin) > 0 {
|
|
if dIsSnapshot && origin == d.Properties[DatasetPropName].Value {
|
|
// if this dataset is snaphot
|
|
ch.Properties[DatasetNumProps+1000] = d.Properties[DatasetPropCreateTXG]
|
|
sortDesc = append(sortDesc, ch)
|
|
} else {
|
|
// Check if origin of this dataset is one of snapshots
|
|
ok, snap := d.FindSnapshot(origin)
|
|
if !ok {
|
|
continue
|
|
}
|
|
ch.Properties[DatasetNumProps+1000] = snap.Properties[DatasetPropCreateTXG]
|
|
sortDesc = append(sortDesc, ch)
|
|
}
|
|
}
|
|
queue <- ch
|
|
}
|
|
default:
|
|
sort.Sort(clonesCreateDesc(sortDesc))
|
|
// This way we get clones ordered from most recent sanpshots first
|
|
for _, c := range sortDesc {
|
|
clones = append(clones, c.Properties[DatasetPropName].Value)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|