diff --git a/a_test.go b/a_test.go index d5b8204..568c34b 100644 --- a/a_test.go +++ b/a_test.go @@ -12,7 +12,7 @@ func Test(t *testing.T) { zpoolTestExport(t) zpoolTestImport(t) zpoolTestExportForce(t) - zpoolTestImport(t) + zpoolTestImportByGUID(t) zpoolTestPoolProp(t) zpoolTestPoolStatusAndState(t) zpoolTestPoolOpenAll(t) diff --git a/zpool.go b/zpool.go index 3f3fe52..c63cc62 100644 --- a/zpool.go +++ b/zpool.go @@ -20,6 +20,14 @@ const ( // PoolProperties type is map of pool properties name -> value type PoolProperties map[Prop]string +// VDevTree ZFS virtual device tree +type VDevTree struct { + Type VDevType + Devices []VDevTree // groups other devices (e.g. mirror) + Parity uint + Path string +} + // Pool object represents handler to single ZFS pool // /* Pool.Properties map[string]Property @@ -48,12 +56,13 @@ func PoolOpen(name string) (pool Pool, err error) { return } -// PoolImport given a list of directories to search, find and import pool with matching -// name stored on disk. -func PoolImport(name string, searchpaths []string) (pool Pool, err error) { +func poolSearchImport(q string, searchpaths []string, guid bool) (name string, + err error) { + var config *C.nvlist_t + var cname *C.char + config = nil errPoolList := errors.New("Failed to list pools") var elem *C.nvpair_t - var config *C.nvlist_t numofp := len(searchpaths) cpaths := C.alloc_strings(C.int(numofp)) for i, path := range searchpaths { @@ -65,39 +74,87 @@ func PoolImport(name string, searchpaths []string) (pool Pool, err error) { elem = C.nvlist_next_nvpair(pools, elem) for ; elem != nil; elem = C.nvlist_next_nvpair(pools, elem) { - var cname *C.char + var cq *C.char var tconfig *C.nvlist_t retcode := C.nvpair_value_nvlist(elem, &tconfig) if retcode != 0 { err = errPoolList return } - retcode = C.nvlist_lookup_string(tconfig, - C.CString(C.ZPOOL_CONFIG_POOL_NAME), &cname) - if retcode != 0 { - err = errPoolList - return - } - oname := C.GoString(cname) - if name == oname { - config = tconfig - break + if guid { + var iguid C.uint64_t + if retcode = C.nvlist_lookup_uint64(tconfig, + C.CString(C.ZPOOL_CONFIG_POOL_GUID), &iguid); retcode != 0 { + err = errPoolList + return + } + sguid := fmt.Sprint(iguid) + if q == sguid { + config = tconfig + break + } + } else { + if retcode = C.nvlist_lookup_string(tconfig, + C.CString(C.ZPOOL_CONFIG_POOL_NAME), &cq); retcode != 0 { + err = errPoolList + return + } + cname = cq + name = C.GoString(cq) + if q == name { + config = tconfig + break + } } } if config == nil { - err = errors.New("No pools to import found with name " + name) + err = fmt.Errorf("No pool found %s", q) return } - - retcode := C.zpool_import(libzfsHandle, config, C.CString(name), nil) - if retcode != 0 { + if guid { + // We need to get name so we can open pool by name + if retcode := C.nvlist_lookup_string(config, + C.CString(C.ZPOOL_CONFIG_POOL_NAME), &cname); retcode != 0 { + err = errPoolList + return + } + name = C.GoString(cname) + } + if retcode := C.zpool_import(libzfsHandle, config, cname, + nil); retcode != 0 { err = LastError() return } + return +} + +// PoolImport given a list of directories to search, find and import pool with matching +// name stored on disk. +func PoolImport(name string, searchpaths []string) (pool Pool, err error) { + _, err = poolSearchImport(name, searchpaths, false) + if err != nil { + return + } pool, err = PoolOpen(name) return } +// PoolImportByGUID given a list of directories to search, find and import pool +// with matching GUID stored on disk. +func PoolImportByGUID(guid string, searchpaths []string) (pool Pool, err error) { + var name string + name, err = poolSearchImport(guid, searchpaths, true) + if err != nil { + return + } + pool, err = PoolOpen(name) + return +} + +// func PoolList(paths []string, cache string) (pools []Pool, err error) { +// +// } + // PoolOpenAll open all active ZFS pools on current system. // Returns array of Pool handlers, each have to be closed after not needed // anymore. Call Pool.Close() method. @@ -164,9 +221,17 @@ func (pool *Pool) ReloadProperties() (err error) { // read features pool.Features = map[string]string{ - "async_destroy": "disabled", - "empty_bpobj": "disabled", - "lz4_compress": "disabled"} + "async_destroy": "disabled", + "empty_bpobj": "disabled", + "lz4_compress": "disabled", + "spacemap_histogram": "disabled", + "enabled_txg": "disabled", + "hole_birth": "disabled", + "extensible_dataset": "disabled", + "embedded_data": "disabled", + "bookmarks": "disabled", + "filesystem_limits": "disabled", + "large_blocks": "disabled"} for name := range pool.Features { pool.GetFeature(name) } @@ -265,15 +330,7 @@ func (pool *Pool) State() (state PoolState, err error) { return } -// VDevSpec ZFS virtual device specification -type VDevSpec struct { - Type VDevType - Devices []VDevSpec // groups other devices (e.g. mirror) - Parity uint - Path string -} - -func (vdev *VDevSpec) isGrouping() (grouping bool, mindevs, maxdevs int) { +func (vdev *VDevTree) isGrouping() (grouping bool, mindevs, maxdevs int) { maxdevs = int(^uint(0) >> 1) if vdev.Type == VDevTypeRaidz { grouping = true @@ -295,7 +352,7 @@ func (vdev *VDevSpec) isGrouping() (grouping bool, mindevs, maxdevs int) { return } -func (vdev *VDevSpec) isLog() (r C.uint64_t) { +func (vdev *VDevTree) isLog() (r C.uint64_t) { r = 0 if vdev.Type == VDevTypeLog { r = 1 @@ -335,7 +392,7 @@ func toCDatasetProperties(props DatasetProperties) (cprops *C.nvlist_t) { return } -func buildVDevSpec(root *C.nvlist_t, rtype VDevType, vdevs []VDevSpec, +func buildVDevTree(root *C.nvlist_t, rtype VDevType, vdevs []VDevTree, props PoolProperties) (err error) { count := len(vdevs) if count == 0 { @@ -396,7 +453,7 @@ func buildVDevSpec(root *C.nvlist_t, rtype VDevType, vdevs []VDevSpec, return } } - if err = buildVDevSpec(child, vdev.Type, vdev.Devices, + if err = buildVDevTree(child, vdev.Type, vdev.Devices, props); err != nil { return } @@ -470,7 +527,7 @@ func buildVDevSpec(root *C.nvlist_t, rtype VDevType, vdevs []VDevSpec, } // PoolCreate create ZFS pool per specs, features and properties of pool and root dataset -func PoolCreate(name string, vdevs []VDevSpec, features map[string]string, +func PoolCreate(name string, vdevs []VDevTree, features map[string]string, props PoolProperties, fsprops DatasetProperties) (pool Pool, err error) { // create root vdev nvroot var nvroot *C.nvlist_t @@ -486,7 +543,7 @@ func PoolCreate(name string, vdevs []VDevSpec, features map[string]string, defer C.nvlist_free(nvroot) // Now we need to build specs (vdev hierarchy) - if err = buildVDevSpec(nvroot, VDevTypeRoot, vdevs, props); err != nil { + if err = buildVDevTree(nvroot, VDevTypeRoot, vdevs, props); err != nil { return } diff --git a/zpool_test.go b/zpool_test.go index cc27780..f3406f5 100644 --- a/zpool_test.go +++ b/zpool_test.go @@ -13,6 +13,7 @@ import ( // HELPERS: var TSTPoolName = "TESTPOOL" +var TSTPoolGUID string func CreateTmpSparse(prefix string, size int64) (path string, err error) { sf, err := ioutil.TempFile("/tmp", prefix) @@ -83,16 +84,16 @@ func zpoolTestPoolCreate(t *testing.T) { disks := [2]string{s1path, s2path} - var vdevs, mdevs, sdevs []zfs.VDevSpec + var vdevs, mdevs, sdevs []zfs.VDevTree for _, d := range disks { mdevs = append(mdevs, - zfs.VDevSpec{Type: zfs.VDevTypeFile, Path: d}) + zfs.VDevTree{Type: zfs.VDevTypeFile, Path: d}) } - sdevs = []zfs.VDevSpec{ + sdevs = []zfs.VDevTree{ {Type: zfs.VDevTypeFile, Path: s3path}} - vdevs = []zfs.VDevSpec{ - zfs.VDevSpec{Type: zfs.VDevTypeMirror, Devices: mdevs}, - zfs.VDevSpec{Type: zfs.VDevTypeSpare, Devices: sdevs}, + vdevs = []zfs.VDevTree{ + zfs.VDevTree{Type: zfs.VDevTypeMirror, Devices: mdevs}, + zfs.VDevTree{Type: zfs.VDevTypeSpare, Devices: sdevs}, } props := make(map[zfs.Prop]string) @@ -114,6 +115,9 @@ func zpoolTestPoolCreate(t *testing.T) { } defer pool.Close() + pguid, _ := pool.GetProperty(zfs.PoolPropGUID) + TSTPoolGUID = pguid.Value + print("PASS\n\n") } @@ -209,6 +213,17 @@ func zpoolTestImport(t *testing.T) { print("PASS\n\n") } +func zpoolTestImportByGUID(t *testing.T) { + println("TEST POOL ImportByGUID( ", TSTPoolGUID, " ) ... ") + p, err := zfs.PoolImportByGUID(TSTPoolGUID, []string{"/tmp"}) + if err != nil { + t.Error(err) + return + } + defer p.Close() + print("PASS\n\n") +} + func zpoolTestPoolProp(t *testing.T) { println("TEST PoolProp on ", TSTPoolName, " ... ") if pool, err := zfs.PoolOpen(TSTPoolName); err == nil { @@ -335,22 +350,22 @@ func ExamplePoolOpenAll() { func ExamplePoolCreate() { disks := [2]string{"/dev/disk/by-id/ATA-123", "/dev/disk/by-id/ATA-456"} - var vdevs, mdevs, sdevs []zfs.VDevSpec + var vdevs, mdevs, sdevs []zfs.VDevTree // build mirror devices specs for _, d := range disks { mdevs = append(mdevs, - zfs.VDevSpec{Type: zfs.VDevTypeDisk, Path: d}) + zfs.VDevTree{Type: zfs.VDevTypeDisk, Path: d}) } // spare device specs - sdevs = []zfs.VDevSpec{ + sdevs = []zfs.VDevTree{ {Type: zfs.VDevTypeDisk, Path: "/dev/disk/by-id/ATA-789"}} // pool specs - vdevs = []zfs.VDevSpec{ - zfs.VDevSpec{Type: zfs.VDevTypeMirror, Devices: mdevs}, - zfs.VDevSpec{Type: zfs.VDevTypeSpare, Devices: sdevs}, + vdevs = []zfs.VDevTree{ + zfs.VDevTree{Type: zfs.VDevTypeMirror, Devices: mdevs}, + zfs.VDevTree{Type: zfs.VDevTypeSpare, Devices: sdevs}, } // pool properties