package zfs // #cgo CFLAGS: -D__USE_LARGEFILE64=1 // #include // #include // #include "common.h" // #include "zpool.h" // #include "zfs.h" import "C" import ( "errors" "fmt" "strconv" "time" "unsafe" ) const ( msgPoolIsNil = "Pool handle not initialized or its closed" ) // Enable or disable pool feature with this constants const ( FENABLED = "enabled" FDISABLED = "disabled" ) // PoolProperties type is map of pool properties name -> value type PoolProperties map[Prop]string /* * ZIO types. Needed to interpret vdev statistics below. */ const ( ZIOTypeNull = iota ZIOTypeRead ZIOTypeWrite ZIOTypeFree ZIOTypeClaim ZIOTypeIOCtl ZIOTypes ) // Scan states const ( DSSNone = iota // No scan DSSScanning // Scanning DSSFinished // Scan finished DSSCanceled // Scan canceled DSSNumStates // Total number of scan states ) // Scan functions const ( PoolScanNone = iota // No scan function PoolScanScrub // Pools is checked against errors PoolScanResilver // Pool is resilvering PoolScanFuncs // Number of scan functions ) // VDevStat - Vdev statistics. Note: all fields should be 64-bit because this // is passed between kernel and userland as an nvlist uint64 array. type VDevStat struct { Timestamp time.Duration /* time since vdev load (nanoseconds)*/ State VDevState /* vdev state */ Aux VDevAux /* see vdev_aux_t */ Alloc uint64 /* space allocated */ Space uint64 /* total capacity */ DSpace uint64 /* deflated capacity */ RSize uint64 /* replaceable dev size */ ESize uint64 /* expandable dev size */ Ops [ZIOTypes]uint64 /* operation count */ Bytes [ZIOTypes]uint64 /* bytes read/written */ ReadErrors uint64 /* read errors */ WriteErrors uint64 /* write errors */ ChecksumErrors uint64 /* checksum errors */ SelfHealed uint64 /* self-healed bytes */ ScanRemoving uint64 /* removing? */ ScanProcessed uint64 /* scan processed bytes */ Fragmentation uint64 /* device fragmentation */ } // PoolScanStat - Pool scan statistics type PoolScanStat struct { // Values stored on disk Func uint64 // Current scan function e.g. none, scrub ... State uint64 // Current scan state e.g. scanning, finished ... StartTime uint64 // Scan start time EndTime uint64 // Scan end time ToExamine uint64 // Total bytes to scan Examined uint64 // Total bytes scaned ToProcess uint64 // Total bytes to processed Processed uint64 // Total bytes processed Errors uint64 // Scan errors // Values not stored on disk PassExam uint64 // Examined bytes per scan pass PassStart uint64 // Start time of scan pass } // VDevTree ZFS virtual device tree type VDevTree struct { Type VDevType Devices []VDevTree // groups other devices (e.g. mirror) Spares []VDevTree L2Cache []VDevTree Logs *VDevTree Parity uint Path string Name string Stat VDevStat ScanStat PoolScanStat } // ExportedPool is type representing ZFS pool available for import type ExportedPool struct { VDevs VDevTree Name string Comment string GUID uint64 State PoolState Status PoolStatus } // Pool object represents handler to single ZFS pool // /* Pool.Properties map[string]Property */ // Map of all ZFS pool properties, changing any of this will not affect ZFS // pool, for that use SetProperty( name, value string) method of the pool // object. This map is initial loaded when ever you open or create pool to // give easy access to listing all available properties. It can be refreshed // with up to date values with call to (*Pool) ReloadProperties type Pool struct { list C.zpool_list_ptr Properties []Property Features map[string]string } // PoolOpen open ZFS pool handler by name. // Returns Pool object, requires Pool.Close() to be called explicitly // for memory cleanup after object is not needed anymore. func PoolOpen(name string) (pool Pool, err error) { csName := C.CString(name) defer C.free(unsafe.Pointer(csName)) pool.list = C.zpool_list_open(csName) if pool.list != nil { err = pool.ReloadProperties() return } err = LastError() return } func poolGetConfig(name string, nv C.nvlist_ptr) (vdevs VDevTree, err error) { var dtype C.char_ptr var vs C.vdev_stat_ptr var ps C.pool_scan_stat_ptr var children C.vdev_children_ptr if dtype = C.get_vdev_type(nv); dtype == nil { err = fmt.Errorf("Failed to fetch %s", C.ZPOOL_CONFIG_TYPE) return } vdevs.Name = name vdevs.Type = VDevType(C.GoString(dtype)) if vdevs.Type == VDevTypeMissing || vdevs.Type == VDevTypeHole { return } // Fetch vdev state if vs = C.get_vdev_stats(nv); vs == nil { err = fmt.Errorf("Failed to fetch %s", C.ZPOOL_CONFIG_VDEV_STATS) return } vdevs.Stat.Timestamp = time.Duration(vs.vs_timestamp) vdevs.Stat.State = VDevState(vs.vs_state) vdevs.Stat.Aux = VDevAux(vs.vs_aux) vdevs.Stat.Alloc = uint64(vs.vs_alloc) vdevs.Stat.Space = uint64(vs.vs_space) vdevs.Stat.DSpace = uint64(vs.vs_dspace) vdevs.Stat.RSize = uint64(vs.vs_rsize) vdevs.Stat.ESize = uint64(vs.vs_esize) for z := 0; z < ZIOTypes; z++ { vdevs.Stat.Ops[z] = uint64(vs.vs_ops[z]) vdevs.Stat.Bytes[z] = uint64(vs.vs_bytes[z]) } vdevs.Stat.ReadErrors = uint64(vs.vs_read_errors) vdevs.Stat.WriteErrors = uint64(vs.vs_write_errors) vdevs.Stat.ChecksumErrors = uint64(vs.vs_checksum_errors) vdevs.Stat.SelfHealed = uint64(vs.vs_self_healed) vdevs.Stat.ScanRemoving = uint64(vs.vs_scan_removing) vdevs.Stat.ScanProcessed = uint64(vs.vs_scan_processed) vdevs.Stat.Fragmentation = uint64(vs.vs_fragmentation) // Fetch vdev scan stats if ps = C.get_vdev_scan_stats(nv); ps != nil { vdevs.ScanStat.Func = uint64(ps.pss_func) vdevs.ScanStat.State = uint64(ps.pss_state) vdevs.ScanStat.StartTime = uint64(ps.pss_start_time) vdevs.ScanStat.EndTime = uint64(ps.pss_end_time) vdevs.ScanStat.ToExamine = uint64(ps.pss_to_examine) vdevs.ScanStat.Examined = uint64(ps.pss_examined) vdevs.ScanStat.ToProcess = uint64(ps.pss_to_process) vdevs.ScanStat.Processed = uint64(ps.pss_processed) vdevs.ScanStat.Errors = uint64(ps.pss_errors) vdevs.ScanStat.PassExam = uint64(ps.pss_pass_exam) vdevs.ScanStat.PassStart = uint64(ps.pss_pass_start) } // Fetch the children children = C.get_vdev_children(nv) if children != nil { // this object that reference childrens and count should be deallocated from memory defer C.free(unsafe.Pointer(children)) vdevs.Devices = make([]VDevTree, 0, children.count) } path := C.get_vdev_path(nv) if path != nil { vdevs.Path = C.GoString(path) } for c := C.uint_t(0); children != nil && c < children.count; c++ { var islog = C.uint64_t(C.B_FALSE) islog = C.get_vdev_is_log(C.nvlist_array_at(children.first, c)) vname := C.zpool_vdev_name(C.libzfsHandle, nil, C.nvlist_array_at(children.first, c), C.B_TRUE) var vdev VDevTree vdev, err = poolGetConfig(C.GoString(vname), C.nvlist_array_at(children.first, c)) C.free(unsafe.Pointer(vname)) if err != nil { return } if islog != C.B_FALSE { vdevs.Logs = &vdev } else { vdevs.Devices = append(vdevs.Devices, vdev) } } return } func poolGetSpares(name string, nv C.nvlist_ptr) (vdevs []VDevTree, err error) { // Fetch the spares var spares C.vdev_children_ptr spares = C.get_vdev_spares(nv) if spares != nil { // this object that reference spares and count should be deallocated from memory defer C.free(unsafe.Pointer(spares)) vdevs = make([]VDevTree, 0, spares.count) } for c := C.uint_t(0); spares != nil && c < spares.count; c++ { vname := C.zpool_vdev_name(C.libzfsHandle, nil, C.nvlist_array_at(spares.first, c), C.B_TRUE) var vdev VDevTree vdev, err = poolGetConfig(C.GoString(vname), C.nvlist_array_at(spares.first, c)) C.free(unsafe.Pointer(vname)) if err != nil { return } vdevs = append(vdevs, vdev) } return } func poolGetL2Cache(name string, nv C.nvlist_ptr) (vdevs []VDevTree, err error) { // Fetch the spares var l2cache C.vdev_children_ptr l2cache = C.get_vdev_l2cache(nv) if l2cache != nil { // this object that reference l2cache and count should be deallocated from memory defer C.free(unsafe.Pointer(l2cache)) vdevs = make([]VDevTree, 0, l2cache.count) } for c := C.uint_t(0); l2cache != nil && c < l2cache.count; c++ { vname := C.zpool_vdev_name(C.libzfsHandle, nil, C.nvlist_array_at(l2cache.first, c), C.B_TRUE) var vdev VDevTree vdev, err = poolGetConfig(C.GoString(vname), C.nvlist_array_at(l2cache.first, c)) C.free(unsafe.Pointer(vname)) if err != nil { return } vdevs = append(vdevs, vdev) } return } // PoolImportSearch - Search pools available to import but not imported. // Returns array of found pools. func PoolImportSearch(searchpaths []string) (epools []ExportedPool, err error) { var config, nvroot C.nvlist_ptr var cname, msgid, comment C.char_ptr var reason C.zpool_status_t var errata C.zpool_errata_t config = nil var elem C.nvpair_ptr numofp := len(searchpaths) cpaths := C.alloc_cstrings(C.int(numofp)) defer C.free(unsafe.Pointer(cpaths)) for i, path := range searchpaths { csPath := C.CString(path) defer C.free(unsafe.Pointer(csPath)) C.strings_setat(cpaths, C.int(i), csPath) } pools := C.go_zpool_search_import(C.libzfsHandle, C.int(numofp), cpaths, C.B_FALSE) defer C.nvlist_free(pools) elem = C.nvlist_next_nvpair(pools, elem) epools = make([]ExportedPool, 0, 1) for ; elem != nil; elem = C.nvlist_next_nvpair(pools, elem) { ep := ExportedPool{} if C.nvpair_value_nvlist(elem, (**C.struct_nvlist)(&config)) != 0 { err = LastError() return } ep.State = PoolState(C.get_zpool_state(config)) if ep.State == PoolStateDestroyed { continue // skip destroyed pools } if cname = C.get_zpool_name(config); cname == nil { err = fmt.Errorf("Failed to fetch %s", C.ZPOOL_CONFIG_POOL_NAME) return } ep.Name = C.GoString(cname) ep.GUID = uint64(C.get_zpool_guid(config)) reason = C.zpool_import_status(config, (**C.char)(&msgid), &errata) ep.Status = PoolStatus(reason) if comment = C.get_zpool_comment(config); comment != nil { ep.Comment = C.GoString(comment) } if nvroot = C.get_zpool_vdev_tree(config); nvroot == nil { err = fmt.Errorf("Failed to fetch %s", C.ZPOOL_CONFIG_VDEV_TREE) return } ep.VDevs, err = poolGetConfig(ep.Name, nvroot) epools = append(epools, ep) } return } func poolSearchImport(q string, searchpaths []string, guid bool) (name string, err error) { var config C.nvlist_ptr var cname C.char_ptr config = nil errPoolList := errors.New("Failed to list pools") var elem *C.nvpair_t numofp := len(searchpaths) cpaths := C.alloc_cstrings(C.int(numofp)) defer C.free(unsafe.Pointer(cpaths)) for i, path := range searchpaths { csPath := C.CString(path) defer C.free(unsafe.Pointer(csPath)) C.strings_setat(cpaths, C.int(i), csPath) } pools := C.go_zpool_search_import(C.libzfsHandle, C.int(numofp), cpaths, C.B_FALSE) defer C.nvlist_free(pools) elem = C.nvlist_next_nvpair(pools, elem) for ; elem != nil; elem = C.nvlist_next_nvpair(pools, elem) { var cq *C.char var tconfig *C.nvlist_t retcode := C.nvpair_value_nvlist(elem, (**C.struct_nvlist)(&tconfig)) if retcode != 0 { err = errPoolList return } if PoolState(C.get_zpool_state(tconfig)) == PoolStateDestroyed { continue // skip destroyed pools } if guid { sguid := fmt.Sprint(C.get_zpool_guid(tconfig)) if q == sguid { config = tconfig break } } else { if cq = C.get_zpool_name(tconfig); cq == nil { err = errPoolList return } cname = cq name = C.GoString(cq) if q == name { config = tconfig break } } } if config == nil { err = fmt.Errorf("No pool found %s", q) return } if guid { // We need to get name so we can open pool by name if cname = C.get_zpool_name(config); cname == nil { err = errPoolList return } name = C.GoString(cname) } if retcode := C.zpool_import_props(C.libzfsHandle, config, cname, nil, C.ZFS_IMPORT_NORMAL|C.ZFS_IMPORT_ANY_HOST); retcode != 0 { err = fmt.Errorf("Import pool properties failed: %s", LastError().Error()) 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. func PoolOpenAll() (pools []Pool, err error) { var pool Pool if pool.list = C.zpool_list_openall(); pool.list == nil { err = LastError() return } for pool.list != nil { err = pool.ReloadProperties() if err != nil { return } next := C.zpool_next(pool.list) pool.list.pnext = nil pools = append(pools, pool) pool.list = next } return } // PoolCloseAll close all pools in given slice func PoolCloseAll(pools []Pool) { for _, p := range pools { p.Close() } } // PoolPropertyToName 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 PoolPropertyToName(p Prop) (name string) { if p == PoolNumProps { return "numofprops" } prop := C.zpool_prop_t(p) name = C.GoString(C.zpool_prop_to_name(prop)) return } // PoolStateToName maps POOL STATE to string. func PoolStateToName(state PoolState) (name string) { ps := C.pool_state_t(state) name = C.GoString(C.zpool_pool_state_to_name(ps)) return } // RefreshStats the pool's vdev statistics, e.g. bytes read/written. func (pool *Pool) RefreshStats() (err error) { if 0 != C.refresh_stats(pool.list) { return errors.New("error refreshing stats") } return nil } // ReloadProperties re-read ZFS pool properties and features, refresh // Pool.Properties and Pool.Features map func (pool *Pool) ReloadProperties() (err error) { propList := C.read_zpool_properties(pool.list) if propList == nil { err = LastError() return } pool.Properties = make([]Property, PoolNumProps+1) next := propList for next != nil { pool.Properties[next.property] = Property{Value: C.GoString(&(next.value[0])), Source: C.GoString(&(next.source[0]))} next = C.next_property(next) } C.free_properties(propList) // read features pool.Features = map[string]string{ "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 { _, ferr := pool.GetFeature(name) if ferr != nil { // tolerate it } } return } // GetProperty reload and return single specified property. This also reloads requested // property in Properties map. func (pool *Pool) GetProperty(p Prop) (prop Property, err error) { if pool.list != nil { // First check if property exist at all if p < PoolPropName || p > PoolNumProps { err = errors.New(fmt.Sprint("Unknown zpool property: ", PoolPropertyToName(p))) return } list := C.read_zpool_property(pool.list, C.int(p)) if list == nil { err = LastError() return } defer C.free_properties(list) prop.Value = C.GoString(&(list.value[0])) prop.Source = C.GoString(&(list.source[0])) pool.Properties[p] = prop return } return prop, errors.New(msgPoolIsNil) } // GetFeature reload and return single specified feature. This also reloads requested // feature in Features map. func (pool *Pool) GetFeature(name string) (value string, err error) { var fvalue [512]C.char csName := C.CString(fmt.Sprint("feature@", name)) r := C.zpool_prop_get_feature(pool.list.zph, csName, &(fvalue[0]), 512) C.free(unsafe.Pointer(csName)) if r != 0 { err = errors.New(fmt.Sprint("Unknown zpool feature: ", name)) return } value = C.GoString(&(fvalue[0])) pool.Features[name] = value return } // SetProperty set ZFS pool 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 (pool *Pool) SetProperty(p Prop, value string) (err error) { if pool.list != nil { // First check if property exist at all if p < PoolPropName || p > PoolNumProps { err = errors.New(fmt.Sprint("Unknown zpool property: ", PoolPropertyToName(p))) return } csPropName := C.CString(PoolPropertyToName(p)) csPropValue := C.CString(value) r := C.zpool_set_prop(pool.list.zph, csPropName, csPropValue) C.free(unsafe.Pointer(csPropName)) C.free(unsafe.Pointer(csPropValue)) if r != 0 { err = LastError() } else { // Update Properties member with change made if _, err = pool.GetProperty(p); err != nil { return } } return } return errors.New(msgPoolIsNil) } // Close ZFS pool handler and release associated memory. // Do not use Pool object after this. func (pool *Pool) Close() { if pool.list != nil { C.zpool_list_close(pool.list) pool.list = nil } } // Name get (re-read) ZFS pool name property func (pool *Pool) Name() (name string, err error) { if pool.list == nil { err = errors.New(msgPoolIsNil) } else { name = C.GoString(C.zpool_get_name(pool.list.zph)) pool.Properties[PoolPropName] = Property{Value: name, Source: "none"} } return } // State get ZFS pool state // Return the state of the pool (ACTIVE or UNAVAILABLE) func (pool *Pool) State() (state PoolState, err error) { if pool.list == nil { err = errors.New(msgPoolIsNil) } else { state = PoolState(C.zpool_read_state(pool.list.zph)) } return } func (vdev *VDevTree) isGrouping() (grouping bool, mindevs, maxdevs int) { maxdevs = int(^uint(0) >> 1) if vdev.Type == VDevTypeRaidz { grouping = true if vdev.Parity == 0 { vdev.Parity = 1 } if vdev.Parity > 254 { vdev.Parity = 254 } mindevs = int(vdev.Parity) + 1 maxdevs = 255 } else if vdev.Type == VDevTypeMirror { grouping = true mindevs = 2 } else if vdev.Type == VDevTypeLog || vdev.Type == VDevTypeSpare || vdev.Type == VDevTypeL2cache { grouping = true mindevs = 1 } return } func (vdev *VDevTree) isLog() (r C.uint64_t) { r = 0 if vdev.Type == VDevTypeLog { r = 1 } return } func toCPoolProperties(props PoolProperties) (cprops C.nvlist_ptr) { cprops = C.new_property_nvlist() for prop, value := range props { name := C.zpool_prop_to_name(C.zpool_prop_t(prop)) csPropValue := C.CString(value) r := C.property_nvlist_add(cprops, name, csPropValue) C.free(unsafe.Pointer(csPropValue)) if r != 0 { if cprops != nil { C.nvlist_free(cprops) cprops = nil } return } } return } func toCDatasetProperties(props DatasetProperties) (cprops C.nvlist_ptr) { cprops = C.new_property_nvlist() for prop, value := range props { name := C.zfs_prop_to_name(C.zfs_prop_t(prop)) csPropValue := C.CString(value) r := C.property_nvlist_add(cprops, name, csPropValue) C.free(unsafe.Pointer(csPropValue)) if r != 0 { if cprops != nil { C.nvlist_free(cprops) cprops = nil } return } } return } func buildVdev(vdev VDevTree, ashift int) (nvvdev *C.struct_nvlist, err error) { if r := C.nvlist_alloc(&nvvdev, C.NV_UNIQUE_NAME, 0); r != 0 { err = errors.New("Failed to allocate vdev") return } csType := C.CString(string(vdev.Type)) r := C.nvlist_add_string(nvvdev, C.sZPOOL_CONFIG_TYPE, csType) C.free(unsafe.Pointer(csType)) if r != 0 { err = errors.New("Failed to set vdev type") return } if r := C.nvlist_add_uint64(nvvdev, C.sZPOOL_CONFIG_IS_LOG, vdev.isLog()); r != 0 { err = errors.New("Failed to allocate vdev (is_log)") return } if r := C.nvlist_add_uint64(nvvdev, C.sZPOOL_CONFIG_WHOLE_DISK, 1); r != 0 { err = errors.New("Failed to allocate vdev nvvdev (whdisk)") return } if len(vdev.Path) > 0 { csPath := C.CString(vdev.Path) r := C.nvlist_add_string( nvvdev, C.sZPOOL_CONFIG_PATH, csPath) C.free(unsafe.Pointer(csPath)) if r != 0 { err = errors.New("Failed to allocate vdev nvvdev (type)") return } if ashift > 0 { if r := C.nvlist_add_uint64(nvvdev, C.sZPOOL_CONFIG_ASHIFT, C.uint64_t(ashift)); r != 0 { err = errors.New("Failed to allocate vdev nvvdev (ashift)") return } } } return } func buildVDevTree(root *C.nvlist_t, rtype VDevType, vdevs, spares, l2cache []VDevTree, props PoolProperties) (err error) { count := len(vdevs) if count == 0 { return } childrens := C.nvlist_alloc_array(C.int(count)) if childrens == nil { err = errors.New("No enough memory") return } defer C.nvlist_free_array(childrens) for i, vdev := range vdevs { grouping, mindevs, maxdevs := vdev.isGrouping() var child *C.struct_nvlist vcount := len(vdev.Devices) if vcount < mindevs || vcount > maxdevs { err = fmt.Errorf( "Invalid vdev specification: %s supports no less than %d or more than %d devices", vdev.Type, mindevs, maxdevs) return } if grouping { if r := C.nvlist_alloc(&child, C.NV_UNIQUE_NAME, 0); r != 0 { err = errors.New("Failed to allocate vdev") return } csType := C.CString(string(vdev.Type)) r := C.nvlist_add_string(child, C.sZPOOL_CONFIG_TYPE, csType) C.free(unsafe.Pointer(csType)) if r != 0 { err = errors.New("Failed to set vdev type") return } if vdev.Type == VDevTypeRaidz { r := C.nvlist_add_uint64(child, C.sZPOOL_CONFIG_NPARITY, C.uint64_t(mindevs-1)) if r != 0 { err = errors.New("Failed to allocate vdev (parity)") return } } if err = buildVDevTree(child, vdev.Type, vdev.Devices, nil, nil, props); err != nil { return } } else { ashift, _ := strconv.Atoi(props[PoolPropAshift]) if child, err = buildVdev(vdev, ashift); err != nil { return } } C.nvlist_array_set(childrens, C.int(i), child) } if count > 0 { if r := C.nvlist_add_nvlist_array(root, C.sZPOOL_CONFIG_CHILDREN, childrens, C.uint_t(count)); r != 0 { err = errors.New("Failed to allocate vdev children") return } } if len(spares) > 0 { ashift, _ := strconv.Atoi(props[PoolPropAshift]) if err = buildVdevSpares(root, VDevTypeRoot, spares, ashift); err != nil { return } } if len(l2cache) > 0 { ashift, _ := strconv.Atoi(props[PoolPropAshift]) if err = buildVdevL2Cache(root, VDevTypeRoot, l2cache, ashift); err != nil { return } } return } func buildVdevSpares(root *C.nvlist_t, rtype VDevType, vdevs []VDevTree, ashift int) (err error) { count := len(vdevs) if count == 0 { return } spares := C.nvlist_alloc_array(C.int(count)) if spares == nil { err = errors.New("No enough memory buildVdevSpares") return } defer C.nvlist_free_array(spares) for i, vdev := range vdevs { var child *C.struct_nvlist if child, err = buildVdev(vdev, ashift); err != nil { return } C.nvlist_array_set(spares, C.int(i), child) } if r := C.nvlist_add_nvlist_array(root, C.sZPOOL_CONFIG_SPARES, spares, C.uint_t(len(vdevs))); r != 0 { err = errors.New("Failed to allocate vdev spare") } return } func buildVdevL2Cache(root *C.nvlist_t, rtype VDevType, vdevs []VDevTree, ashift int) (err error) { count := len(vdevs) if count == 0 { return } l2cache := C.nvlist_alloc_array(C.int(count)) if l2cache == nil { err = errors.New("No enough memory buildVdevL2Cache") return } defer C.nvlist_free_array(l2cache) for i, vdev := range vdevs { var child *C.struct_nvlist if child, err = buildVdev(vdev, ashift); err != nil { return } C.nvlist_array_set(l2cache, C.int(i), child) } if r := C.nvlist_add_nvlist_array(root, C.sZPOOL_CONFIG_SPARES, l2cache, C.uint_t(len(vdevs))); r != 0 { err = errors.New("Failed to allocate vdev l2cache") } return } // PoolCreate create ZFS pool per specs, features and properties of pool and root dataset func PoolCreate(name string, vdev VDevTree, features map[string]string, props PoolProperties, fsprops DatasetProperties) (pool Pool, err error) { // create root vdev nvroot var nvroot *C.struct_nvlist if r := C.nvlist_alloc(&nvroot, C.NV_UNIQUE_NAME, 0); r != 0 { err = errors.New("Failed to allocate root vdev") return } csTypeRoot := C.CString(string(VDevTypeRoot)) r := C.nvlist_add_string(nvroot, C.sZPOOL_CONFIG_TYPE, csTypeRoot) C.free(unsafe.Pointer(csTypeRoot)) if r != 0 { err = errors.New("Failed to allocate root vdev") return } defer C.nvlist_free(nvroot) // Now we need to build specs (vdev hierarchy) if err = buildVDevTree(nvroot, VDevTypeRoot, vdev.Devices, vdev.Spares, vdev.L2Cache, props); err != nil { return } // Enable 0.6.5 features per default features["spacemap_histogram"] = FENABLED features["enabled_txg"] = FENABLED features["hole_birth"] = FENABLED features["extensible_dataset"] = FENABLED features["embedded_data"] = FENABLED features["bookmarks"] = FENABLED features["filesystem_limits"] = FENABLED features["large_blocks"] = FENABLED // Enable 0.7.x features per default features["multi_vdev_crash_dump"] = FENABLED features["large_dnode"] = FENABLED features["sha512"] = FENABLED features["skein"] = FENABLED features["edonr"] = FENABLED features["userobj_accounting"] = FENABLED // convert properties cprops := toCPoolProperties(props) if cprops != nil { defer C.nvlist_free(cprops) } else if len(props) > 0 { err = errors.New("Failed to allocate pool properties") return } cfsprops := toCDatasetProperties(fsprops) if cfsprops != nil { defer C.nvlist_free(cfsprops) } else if len(fsprops) > 0 { err = errors.New("Failed to allocate FS properties") return } for fname, fval := range features { csName := C.CString(fmt.Sprintf("feature@%s", fname)) csVal := C.CString(fval) r := C.property_nvlist_add(cprops, csName, csVal) C.free(unsafe.Pointer(csName)) C.free(unsafe.Pointer(csVal)) if r != 0 { if cprops != nil { C.nvlist_free(cprops) cprops = nil } return } } // Create actual pool then open csName := C.CString(name) defer C.free(unsafe.Pointer(csName)) if r := C.zpool_create(C.libzfsHandle, csName, nvroot, cprops, cfsprops); r != 0 { err = LastError() err = errors.New(err.Error() + " (zpool_create)") return } // Open created pool and return handle pool, err = PoolOpen(name) return } // Status get pool status. Let you check if pool healthy. func (pool *Pool) Status() (status PoolStatus, err error) { var msgid *C.char var reason C.zpool_status_t var errata C.zpool_errata_t if pool.list == nil { err = errors.New(msgPoolIsNil) return } reason = C.zpool_get_status(pool.list.zph, &msgid, &errata) status = PoolStatus(reason) return } // Destroy the pool. It is up to the caller to ensure that there are no // datasets left in the pool. logStr is optional if specified it is // appended to ZFS history func (pool *Pool) Destroy(logStr string) (err error) { if pool.list == nil { err = errors.New(msgPoolIsNil) return } csLog := C.CString(logStr) defer C.free(unsafe.Pointer(csLog)) retcode := C.zpool_destroy(pool.list.zph, csLog) if retcode != 0 { err = LastError() } return } // Export exports the pool from the system. // Before exporting the pool, all datasets within the pool are unmounted. // A pool can not be exported if it has a shared spare that is currently // being used. func (pool *Pool) Export(force bool, log string) (err error) { var forcet C.boolean_t if force { forcet = 1 } csLog := C.CString(log) defer C.free(unsafe.Pointer(csLog)) if rc := C.zpool_disable_datasets(pool.list.zph, forcet); rc != 0 { err = LastError() return } if rc := C.zpool_export(pool.list.zph, forcet, csLog); rc != 0 { err = LastError() } return } // ExportForce hard force export of the pool from the system. func (pool *Pool) ExportForce(log string) (err error) { csLog := C.CString(log) defer C.free(unsafe.Pointer(csLog)) if rc := C.zpool_export_force(pool.list.zph, csLog); rc != 0 { err = LastError() } return } // VDevTree - Fetch pool's current vdev tree configuration, state and stats func (pool *Pool) VDevTree() (vdevs VDevTree, err error) { var nvroot *C.struct_nvlist var poolName string config := C.zpool_get_config(pool.list.zph, nil) if config == nil { err = fmt.Errorf("Failed zpool_get_config") return } if C.nvlist_lookup_nvlist(config, C.sZPOOL_CONFIG_VDEV_TREE, &nvroot) != 0 { err = fmt.Errorf("Failed to fetch %s", C.ZPOOL_CONFIG_VDEV_TREE) return } if poolName, err = pool.Name(); err != nil { return } if vdevs, err = poolGetConfig(poolName, nvroot); err != nil { return } vdevs.Spares, err = poolGetSpares(poolName, nvroot) vdevs.L2Cache, err = poolGetL2Cache(poolName, nvroot) return } func (s PoolState) String() string { switch s { case PoolStateActive: return "ACTIVE" case PoolStateExported: return "EXPORTED" case PoolStateDestroyed: return "DESTROYED" case PoolStateSpare: return "SPARE" case PoolStateL2cache: return "L2CACHE" case PoolStateUninitialized: return "UNINITIALIZED" case PoolStateUnavail: return "UNAVAILABLE" case PoolStatePotentiallyActive: return "POTENTIALLYACTIVE" default: return "UNKNOWN" } } func (s VDevState) String() string { switch s { case VDevStateUnknown: return "UNINITIALIZED" case VDevStateClosed: return "CLOSED" case VDevStateOffline: return "OFFLINE" case VDevStateRemoved: return "REMOVED" case VDevStateCantOpen: return "CANT_OPEN" case VDevStateFaulted: return "FAULTED" case VDevStateDegraded: return "DEGRADED" case VDevStateHealthy: return "ONLINE" default: return "UNKNOWN" } } func (s PoolStatus) String() string { switch s { case PoolStatusCorruptCache: return "CORRUPT_CACHE" case PoolStatusMissingDevR: return "MISSING_DEV_R" /* missing device with replicas */ case PoolStatusMissingDevNr: /* missing device with no replicas */ return "MISSING_DEV_NR" case PoolStatusCorruptLabelR: /* bad device label with replicas */ return "CORRUPT_LABEL_R" case PoolStatusCorruptLabelNr: /* bad device label with no replicas */ return "CORRUPT_LABEL_NR" case PoolStatusBadGUIDSum: /* sum of device guids didn't match */ return "BAD_GUID_SUM" case PoolStatusCorruptPool: /* pool metadata is corrupted */ return "CORRUPT_POOL" case PoolStatusCorruptData: /* data errors in user (meta)data */ return "CORRUPT_DATA" case PoolStatusFailingDev: /* device experiencing errors */ return "FAILLING_DEV" case PoolStatusVersionNewer: /* newer on-disk version */ return "VERSION_NEWER" case PoolStatusHostidMismatch: /* last accessed by another system */ return "HOSTID_MISMATCH" case PoolStatusHosidActive: /* currently active on another system */ return "HOSTID_ACTIVE" case PoolStatusHostidRequired: /* multihost=on and hostid=0 */ return "HOSTID_REQUIRED" case PoolStatusIoFailureWait: /* failed I/O, failmode 'wait' */ return "FAILURE_WAIT" case PoolStatusIoFailureContinue: /* failed I/O, failmode 'continue' */ return "FAILURE_CONTINUE" case PoolStatusIOFailureMap: /* ailed MMP, failmode not 'panic' */ return "HOSTID_FAILURE_MAP" case PoolStatusBadLog: /* cannot read log chain(s) */ return "BAD_LOG" case PoolStatusErrata: /* informational errata available */ return "ERRATA" /* * If the pool has unsupported features but can still be opened in * read-only mode, its status is ZPOOL_STATUS_UNSUP_FEAT_WRITE. If the * pool has unsupported features but cannot be opened at all, its * status is ZPOOL_STATUS_UNSUP_FEAT_READ. */ case PoolStatusUnsupFeatRead: /* unsupported features for read */ return "UNSUP_FEAT_READ" case PoolStatusUnsupFeatWrite: /* unsupported features for write */ return "UNSUP_FEAT_WRITE" /* * These faults have no corresponding message ID. At the time we are * checking the status, the original reason for the FMA fault (I/O or * checksum errors) has been lost. */ case PoolStatusFaultedDevR: /* faulted device with replicas */ return "FAULTED_DEV_R" case PoolStatusFaultedDevNr: /* faulted device with no replicas */ return "FAULTED_DEV_NR" /* * The following are not faults per se, but still an error possibly * requiring administrative attention. There is no corresponding * message ID. */ case PoolStatusVersionOlder: /* older legacy on-disk version */ return "VERSION_OLDER" case PoolStatusFeatDisabled: /* supported features are disabled */ return "FEAT_DISABLED" case PoolStatusResilvering: /* device being resilvered */ return "RESILVERIN" case PoolStatusOfflineDev: /* device online */ return "OFFLINE_DEV" case PoolStatusRemovedDev: /* removed device */ return "REMOVED_DEV" /* * Finally, the following indicates a healthy pool. */ case PoolStatusOk: return "OK" default: return "OK" } }