- Changes to make library interface more clear and to better suit go package standards

This commit is contained in:
Faruk Kasumovic 2015-12-04 23:05:19 +01:00
parent db4703b708
commit bc19737222
7 changed files with 420 additions and 299 deletions

View File

@ -46,10 +46,10 @@ props := make(map[ZFSProp]Property)
// similar to convert in to string (base 10) from numeric type. // similar to convert in to string (base 10) from numeric type.
strSize := "1073741824" strSize := "1073741824"
props[ZFSPropVolsize] = Property{Value: strSize} props[DatasetPropVolsize] = Property{Value: strSize}
// In addition I explicitly choose some more properties to be set. // In addition I explicitly choose some more properties to be set.
props[ZFSPropVolblocksize] = Property{Value: "4096"} props[DatasetPropVolblocksize] = Property{Value: "4096"}
props[ZFSPropReservation] = Property{Value: strSize} props[DatasetPropReservation] = Property{Value: strSize}
// Lets create desired volume // Lets create desired volume
d, err := DatasetCreate("TESTPOOL/VOLUME1", DatasetTypeVolume, props) d, err := DatasetCreate("TESTPOOL/VOLUME1", DatasetTypeVolume, props)

325
common.go
View File

@ -1,4 +1,4 @@
// Implements basic manipulation of ZFS pools and data sets. // Package zfs implements basic manipulation of ZFS pools and data sets.
// Use libzfs C library instead CLI zfs tools, with goal // Use libzfs C library instead CLI zfs tools, with goal
// to let using and manipulating OpenZFS form with in go project. // to let using and manipulating OpenZFS form with in go project.
// //
@ -23,36 +23,41 @@ import (
"errors" "errors"
) )
// VDevType type of device in the pool
type VDevType string type VDevType string
var libzfs_handle *C.struct_libzfs_handle var libzfsHandle *C.struct_libzfs_handle
func init() { func init() {
libzfs_handle = C.libzfs_init() libzfsHandle = C.libzfs_init()
return return
} }
// Types of Virtual Devices // Types of Virtual Devices
const ( const (
VDevTypeRoot VDevType = "root" VDevTypeRoot VDevType = "root" // VDevTypeRoot root device in ZFS pool
VDevTypeMirror = "mirror" VDevTypeMirror = "mirror" // VDevTypeMirror mirror device in ZFS pool
VDevTypeReplacing = "replacing" VDevTypeReplacing = "replacing" // VDevTypeReplacing replacing
VDevTypeRaidz = "raidz" VDevTypeRaidz = "raidz" // VDevTypeRaidz RAIDZ device
VDevTypeDisk = "disk" VDevTypeDisk = "disk" // VDevTypeDisk device is disk
VDevTypeFile = "file" VDevTypeFile = "file" // VDevTypeFile device is file
VDevTypeMissing = "missing" VDevTypeMissing = "missing" // VDevTypeMissing missing device
VDevTypeHole = "hole" VDevTypeHole = "hole" // VDevTypeHole hole
VDevTypeSpare = "spare" VDevTypeSpare = "spare" // VDevTypeSpare spare device
VDevTypeLog = "log" VDevTypeLog = "log" // VDevTypeLog ZIL device
VDevTypeL2cache = "l2cache" VDevTypeL2cache = "l2cache" // VDevTypeL2cache cache device (disk)
) )
type PoolProp int // Prop type to enumerate all different properties suppoerted by ZFS
type ZFSProp int type Prop int
// PoolStatus type representing status of the pool
type PoolStatus int type PoolStatus int
// PoolState type representing pool state
type PoolState uint64 type PoolState uint64
// Zfs pool or dataset property // Property ZFS pool or dataset property value
type Property struct { type Property struct {
Value string Value string
Source string Source string
@ -64,20 +69,20 @@ const (
* The following correspond to faults as defined in the (fault.fs.zfs.*) * The following correspond to faults as defined in the (fault.fs.zfs.*)
* event namespace. Each is associated with a corresponding message ID. * event namespace. Each is associated with a corresponding message ID.
*/ */
PoolStatusCorrupt_cache PoolStatus = iota /* corrupt /kernel/drv/zpool.cache */ PoolStatusCorruptCache PoolStatus = iota /* corrupt /kernel/drv/zpool.cache */
PoolStatusMissing_dev_r /* missing device with replicas */ PoolStatusMissingDevR /* missing device with replicas */
PoolStatusMissing_dev_nr /* missing device with no replicas */ PoolStatusMissingDevNr /* missing device with no replicas */
PoolStatusCorrupt_label_r /* bad device label with replicas */ PoolStatusCorruptLabelR /* bad device label with replicas */
PoolStatusCorrupt_label_nr /* bad device label with no replicas */ PoolStatusCorruptLabelNr /* bad device label with no replicas */
PoolStatusBad_guid_sum /* sum of device guids didn't match */ PoolStatusBadGUIDSum /* sum of device guids didn't match */
PoolStatusCorrupt_pool /* pool metadata is corrupted */ PoolStatusCorruptPool /* pool metadata is corrupted */
PoolStatusCorrupt_data /* data errors in user (meta)data */ PoolStatusCorruptData /* data errors in user (meta)data */
PoolStatusFailing_dev /* device experiencing errors */ PoolStatusFailingDev /* device experiencing errors */
PoolStatusVersion_newer /* newer on-disk version */ PoolStatusVersionNewer /* newer on-disk version */
PoolStatusHostid_mismatch /* last accessed by another system */ PoolStatusHostidMismatch /* last accessed by another system */
PoolStatusIo_failure_wait /* failed I/O, failmode 'wait' */ PoolStatusIoFailureWait /* failed I/O, failmode 'wait' */
PoolStatusIo_failure_continue /* failed I/O, failmode 'continue' */ PoolStatusIoFailureContinue /* failed I/O, failmode 'continue' */
PoolStatusBad_log /* cannot read log chain(s) */ PoolStatusBadLog /* cannot read log chain(s) */
PoolStatusErrata /* informational errata available */ PoolStatusErrata /* informational errata available */
/* /*
@ -86,27 +91,27 @@ const (
* pool has unsupported features but cannot be opened at all, its * pool has unsupported features but cannot be opened at all, its
* status is ZPOOL_STATUS_UNSUP_FEAT_READ. * status is ZPOOL_STATUS_UNSUP_FEAT_READ.
*/ */
PoolStatusUnsup_feat_read /* unsupported features for read */ PoolStatusUnsupFeatRead /* unsupported features for read */
PoolStatusUnsup_feat_write /* unsupported features for write */ PoolStatusUnsupFeatWrite /* unsupported features for write */
/* /*
* These faults have no corresponding message ID. At the time we are * 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 * checking the status, the original reason for the FMA fault (I/O or
* checksum errors) has been lost. * checksum errors) has been lost.
*/ */
PoolStatusFaulted_dev_r /* faulted device with replicas */ PoolStatusFaultedDevR /* faulted device with replicas */
PoolStatusFaulted_dev_nr /* faulted device with no replicas */ PoolStatusFaultedDevNr /* faulted device with no replicas */
/* /*
* The following are not faults per se, but still an error possibly * The following are not faults per se, but still an error possibly
* requiring administrative attention. There is no corresponding * requiring administrative attention. There is no corresponding
* message ID. * message ID.
*/ */
PoolStatusVersion_older /* older legacy on-disk version */ PoolStatusVersionOlder /* older legacy on-disk version */
PoolStatusFeat_disabled /* supported features are disabled */ PoolStatusFeatDisabled /* supported features are disabled */
PoolStatusResilvering /* device being resilvered */ PoolStatusResilvering /* device being resilvered */
PoolStatusOffline_dev /* device online */ PoolStatusOfflineDev /* device online */
PoolStatusRemoved_dev /* removed device */ PoolStatusRemovedDev /* removed device */
/* /*
* Finally, the following indicates a healthy pool. * Finally, the following indicates a healthy pool.
@ -129,12 +134,12 @@ const (
// Pool properties. Enumerates available ZFS pool properties. Use it to access // Pool properties. Enumerates available ZFS pool properties. Use it to access
// pool properties either to read or set soecific property. // pool properties either to read or set soecific property.
const ( const (
PoolPropName PoolProp = iota PoolPropName Prop = iota
PoolPropSize PoolPropSize
PoolPropCapacity PoolPropCapacity
PoolPropAltroot PoolPropAltroot
PoolPropHealth PoolPropHealth
PoolPropGuid PoolPropGUID
PoolPropVersion PoolPropVersion
PoolPropBootfs PoolPropBootfs
PoolPropDelegation PoolPropDelegation
@ -166,102 +171,178 @@ const (
* the property table in module/zcommon/zfs_prop.c. * the property table in module/zcommon/zfs_prop.c.
*/ */
const ( const (
ZFSPropType ZFSProp = iota DatasetPropType Prop = iota
ZFSPropCreation DatasetPropCreation
ZFSPropUsed DatasetPropUsed
ZFSPropAvailable DatasetPropAvailable
ZFSPropReferenced DatasetPropReferenced
ZFSPropCompressratio DatasetPropCompressratio
ZFSPropMounted DatasetPropMounted
ZFSPropOrigin DatasetPropOrigin
ZFSPropQuota DatasetPropQuota
ZFSPropReservation DatasetPropReservation
ZFSPropVolsize DatasetPropVolsize
ZFSPropVolblocksize DatasetPropVolblocksize
ZFSPropRecordsize DatasetPropRecordsize
ZFSPropMountpoint DatasetPropMountpoint
ZFSPropSharenfs DatasetPropSharenfs
ZFSPropChecksum DatasetPropChecksum
ZFSPropCompression DatasetPropCompression
ZFSPropAtime DatasetPropAtime
ZFSPropDevices DatasetPropDevices
ZFSPropExec DatasetPropExec
ZFSPropSetuid DatasetPropSetuid
ZFSPropReadonly DatasetPropReadonly
ZFSPropZoned DatasetPropZoned
ZFSPropSnapdir DatasetPropSnapdir
ZFSPropPrivate /* not exposed to user, temporary */ DatasetPropPrivate /* not exposed to user, temporary */
ZFSPropAclinherit DatasetPropAclinherit
ZFSPropCreatetxg /* not exposed to the user */ DatasetPropCreatetxg /* not exposed to the user */
ZFSPropName /* not exposed to the user */ DatasetPropName /* not exposed to the user */
ZFSPropCanmount DatasetPropCanmount
ZFSPropIscsioptions /* not exposed to the user */ DatasetPropIscsioptions /* not exposed to the user */
ZFSPropXattr DatasetPropXattr
ZFSPropNumclones /* not exposed to the user */ DatasetPropNumclones /* not exposed to the user */
ZFSPropCopies DatasetPropCopies
ZFSPropVersion DatasetPropVersion
ZFSPropUtf8only DatasetPropUtf8only
ZFSPropNormalize DatasetPropNormalize
ZFSPropCase DatasetPropCase
ZFSPropVscan DatasetPropVscan
ZFSPropNbmand DatasetPropNbmand
ZFSPropSharesmb DatasetPropSharesmb
ZFSPropRefquota DatasetPropRefquota
ZFSPropRefreservation DatasetPropRefreservation
ZFSPropGuid DatasetPropGUID
ZFSPropPrimarycache DatasetPropPrimarycache
ZFSPropSecondarycache DatasetPropSecondarycache
ZFSPropUsedsnap DatasetPropUsedsnap
ZFSPropUsedds DatasetPropUsedds
ZFSPropUsedchild DatasetPropUsedchild
ZFSPropUsedrefreserv DatasetPropUsedrefreserv
ZFSPropUseraccounting /* not exposed to the user */ DatasetPropUseraccounting /* not exposed to the user */
ZFSPropStmf_shareinfo /* not exposed to the user */ DatasetPropStmfShareinfo /* not exposed to the user */
ZFSPropDefer_destroy DatasetPropDeferDestroy
ZFSPropUserrefs DatasetPropUserrefs
ZFSPropLogbias DatasetPropLogbias
ZFSPropUnique /* not exposed to the user */ DatasetPropUnique /* not exposed to the user */
ZFSPropObjsetid /* not exposed to the user */ DatasetPropObjsetid /* not exposed to the user */
ZFSPropDedup DatasetPropDedup
ZFSPropMlslabel DatasetPropMlslabel
ZFSPropSync DatasetPropSync
ZFSPropRefratio DatasetPropRefratio
ZFSPropWritten DatasetPropWritten
ZFSPropClones DatasetPropClones
ZFSPropLogicalused DatasetPropLogicalused
ZFSPropLogicalreferenced DatasetPropLogicalreferenced
ZFSPropInconsistent /* not exposed to the user */ DatasetPropInconsistent /* not exposed to the user */
ZFSPropSnapdev DatasetPropSnapdev
ZFSPropAcltype DatasetPropAcltype
ZFSPropSelinux_context DatasetPropSelinuxContext
ZFSPropSelinux_fscontext DatasetPropSelinuxFsContext
ZFSPropSelinux_defcontext DatasetPropSelinuxDefContext
ZFSPropSelinux_rootcontext DatasetPropSelinuxRootContext
ZFSPropRelatime DatasetPropRelatime
ZFSPropRedundant_metadata DatasetPropRedundantMetadata
ZFSPropOverlay DatasetPropOverlay
ZFSNumProps DatasetNumProps
) )
// Get last underlying libzfs error description if any // LastError get last underlying libzfs error description if any
func LastError() (err error) { func LastError() (err error) {
errno := C.libzfs_errno(libzfs_handle) errno := C.libzfs_errno(libzfsHandle)
if errno == 0 { if errno == 0 {
return nil return nil
} }
return errors.New(C.GoString(C.libzfs_error_description(libzfs_handle))) return errors.New(C.GoString(C.libzfs_error_description(libzfsHandle)))
} }
// Force clear of any last error set by undeliying libzfs // ClearLastError force clear of any last error set by undeliying libzfs
func ClearLastError() (err error) { func ClearLastError() (err error) {
err = LastError() err = LastError()
C.clear_last_error(libzfs_handle) C.clear_last_error(libzfsHandle)
return return
} }
func boolean_t(b bool) (r C.boolean_t) { func booleanT(b bool) (r C.boolean_t) {
if b { if b {
return 1 return 1
} }
return 0 return 0
} }
// ZFS errors
const (
ESuccess = 0 /* no error -- success */
ENomem = 2000 << iota /* out of memory */
EBadprop /* invalid property value */
EPropreadonly /* cannot set readonly property */
EProptype /* property does not apply to dataset type */
EPropnoninherit /* property is not inheritable */
EPropspace /* bad quota or reservation */
EBadtype /* dataset is not of appropriate type */
EBusy /* pool or dataset is busy */
EExists /* pool or dataset already exists */
ENoent /* no such pool or dataset */
EBadstream /* bad backup stream */
EDsreadonly /* dataset is readonly */
EVoltoobig /* volume is too large for 32-bit system */
EInvalidname /* invalid dataset name */
EBadrestore /* unable to restore to destination */
EBadbackup /* backup failed */
EBadtarget /* bad attach/detach/replace target */
ENodevice /* no such device in pool */
EBaddev /* invalid device to add */
ENoreplicas /* no valid replicas */
EResilvering /* currently resilvering */
EBadversion /* unsupported version */
EPoolunavail /* pool is currently unavailable */
EDevoverflow /* too many devices in one vdev */
EBadpath /* must be an absolute path */
ECrosstarget /* rename or clone across pool or dataset */
EZoned /* used improperly in local zone */
EMountfailed /* failed to mount dataset */
EUmountfailed /* failed to unmount dataset */
EUnsharenfsfailed /* unshare(1M) failed */
ESharenfsfailed /* share(1M) failed */
EPerm /* permission denied */
ENospc /* out of space */
EFault /* bad address */
EIo /* I/O error */
EIntr /* signal received */
EIsspare /* device is a hot spare */
EInvalconfig /* invalid vdev configuration */
ERecursive /* recursive dependency */
ENohistory /* no history object */
EPoolprops /* couldn't retrieve pool props */
EPoolNotsup /* ops not supported for this type of pool */
EPoolInvalarg /* invalid argument for this pool operation */
ENametoolong /* dataset name is too long */
EOpenfailed /* open of device failed */
ENocap /* couldn't get capacity */
ELabelfailed /* write of label failed */
EBadwho /* invalid permission who */
EBadperm /* invalid permission */
EBadpermset /* invalid permission set name */
ENodelegation /* delegated administration is disabled */
EUnsharesmbfailed /* failed to unshare over smb */
ESharesmbfailed /* failed to share over smb */
EBadcache /* bad cache file */
EIsl2CACHE /* device is for the level 2 ARC */
EVdevnotsup /* unsupported vdev type */
ENotsup /* ops not supported on this dataset */
EActiveSpare /* pool has active shared spare devices */
EUnplayedLogs /* log device has unplayed logs */
EReftagRele /* snapshot release: tag not found */
EReftagHold /* snapshot hold: tag already exists */
ETagtoolong /* snapshot hold/rele: tag too long */
EPipefailed /* pipe create failed */
EThreadcreatefailed /* thread create failed */
EPostsplitOnline /* onlining a disk after splitting it */
EScrubbing /* currently scrubbing */
ENoScrub /* no active scrub */
EDiff /* general failure of zfs diff */
EDiffdata /* bad zfs diff data */
EPoolreadonly /* pool is in read-only mode */
EUnknown
)

104
zfs.go
View File

@ -14,20 +14,30 @@ const (
msgDatasetIsNil = "Dataset handle not initialized or its closed" 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 type DatasetType int32
const ( const (
// DatasetTypeFilesystem - file system dataset
DatasetTypeFilesystem DatasetType = (1 << 0) DatasetTypeFilesystem DatasetType = (1 << 0)
// DatasetTypeSnapshot - snapshot of dataset
DatasetTypeSnapshot = (1 << 1) DatasetTypeSnapshot = (1 << 1)
// DatasetTypeVolume - volume (virtual block device) dataset
DatasetTypeVolume = (1 << 2) DatasetTypeVolume = (1 << 2)
// DatasetTypePool - pool dataset
DatasetTypePool = (1 << 3) DatasetTypePool = (1 << 3)
// DatasetTypeBookmark - bookmark dataset
DatasetTypeBookmark = (1 << 4) DatasetTypeBookmark = (1 << 4)
) )
// Dataset - ZFS dataset object
type Dataset struct { type Dataset struct {
list *C.dataset_list_t list *C.dataset_list_t
Type DatasetType Type DatasetType
Properties map[ZFSProp]Property Properties map[Prop]Property
Children []Dataset Children []Dataset
} }
@ -37,7 +47,7 @@ func (d *Dataset) openChildren() (err error) {
errcode := C.dataset_list_children(d.list.zh, &(dataset.list)) errcode := C.dataset_list_children(d.list.zh, &(dataset.list))
for dataset.list != nil { for dataset.list != nil {
dataset.Type = DatasetType(C.zfs_get_type(dataset.list.zh)) dataset.Type = DatasetType(C.zfs_get_type(dataset.list.zh))
dataset.Properties = make(map[ZFSProp]Property) dataset.Properties = make(map[Prop]Property)
err = dataset.ReloadProperties() err = dataset.ReloadProperties()
if err != nil { if err != nil {
return return
@ -49,7 +59,7 @@ func (d *Dataset) openChildren() (err error) {
err = LastError() err = LastError()
return return
} }
for ci, _ := range d.Children { for ci := range d.Children {
if err = d.Children[ci].openChildren(); err != nil { if err = d.Children[ci].openChildren(); err != nil {
return return
} }
@ -57,11 +67,11 @@ func (d *Dataset) openChildren() (err error) {
return return
} }
// Recursive get handles to all available datasets on system // DatasetOpenAll recursive get handles to all available datasets on system
// (file-systems, volumes or snapshots). // (file-systems, volumes or snapshots).
func DatasetOpenAll() (datasets []Dataset, err error) { func DatasetOpenAll() (datasets []Dataset, err error) {
var dataset Dataset var dataset Dataset
errcode := C.dataset_list_root(libzfs_handle, &dataset.list) errcode := C.dataset_list_root(libzfsHandle, &dataset.list)
for dataset.list != nil { for dataset.list != nil {
dataset.Type = DatasetType(C.zfs_get_type(dataset.list.zh)) dataset.Type = DatasetType(C.zfs_get_type(dataset.list.zh))
err = dataset.ReloadProperties() err = dataset.ReloadProperties()
@ -75,7 +85,7 @@ func DatasetOpenAll() (datasets []Dataset, err error) {
err = LastError() err = LastError()
return return
} }
for ci, _ := range datasets { for ci := range datasets {
if err = datasets[ci].openChildren(); err != nil { if err = datasets[ci].openChildren(); err != nil {
return return
} }
@ -83,24 +93,25 @@ func DatasetOpenAll() (datasets []Dataset, err error) {
return return
} }
// Close all datasets in slice and all of its recursive children datasets // DatasetCloseAll close all datasets in slice and all of its recursive
// children datasets
func DatasetCloseAll(datasets []Dataset) { func DatasetCloseAll(datasets []Dataset) {
for _, d := range datasets { for _, d := range datasets {
d.Close() d.Close()
} }
} }
// Open dataset and all of its recursive children datasets // DatasetOpen open dataset and all of its recursive children datasets
func DatasetOpen(path string) (d Dataset, err error) { func DatasetOpen(path string) (d Dataset, err error) {
d.list = C.create_dataset_list_item() d.list = C.create_dataset_list_item()
d.list.zh = C.zfs_open(libzfs_handle, C.CString(path), 0xF) d.list.zh = C.zfs_open(libzfsHandle, C.CString(path), 0xF)
if d.list.zh == nil { if d.list.zh == nil {
err = LastError() err = LastError()
return return
} }
d.Type = DatasetType(C.zfs_get_type(d.list.zh)) d.Type = DatasetType(C.zfs_get_type(d.list.zh))
d.Properties = make(map[ZFSProp]Property) d.Properties = make(map[Prop]Property)
err = d.ReloadProperties() err = d.ReloadProperties()
if err != nil { if err != nil {
return return
@ -109,7 +120,7 @@ func DatasetOpen(path string) (d Dataset, err error) {
return return
} }
func datasetPropertiesTo_nvlist(props map[ZFSProp]Property) ( func datasetPropertiesTonvlist(props map[Prop]Property) (
cprops *C.nvlist_t, err error) { cprops *C.nvlist_t, err error) {
// convert properties to nvlist C type // convert properties to nvlist C type
r := C.nvlist_alloc(&cprops, C.NV_UNIQUE_NAME, 0) r := C.nvlist_alloc(&cprops, C.NV_UNIQUE_NAME, 0)
@ -129,16 +140,17 @@ func datasetPropertiesTo_nvlist(props map[ZFSProp]Property) (
return return
} }
// Create a new filesystem or volume on path representing pool/dataset or pool/parent/dataset // DatasetCreate create a new filesystem or volume on path representing
// pool/dataset or pool/parent/dataset
func DatasetCreate(path string, dtype DatasetType, func DatasetCreate(path string, dtype DatasetType,
props map[ZFSProp]Property) (d Dataset, err error) { props map[Prop]Property) (d Dataset, err error) {
var cprops *C.nvlist_t var cprops *C.nvlist_t
if cprops, err = datasetPropertiesTo_nvlist(props); err != nil { if cprops, err = datasetPropertiesTonvlist(props); err != nil {
return return
} }
defer C.nvlist_free(cprops) defer C.nvlist_free(cprops)
errcode := C.zfs_create(libzfs_handle, C.CString(path), errcode := C.zfs_create(libzfsHandle, C.CString(path),
C.zfs_type_t(dtype), cprops) C.zfs_type_t(dtype), cprops)
if errcode != 0 { if errcode != 0 {
err = LastError() err = LastError()
@ -146,7 +158,8 @@ func DatasetCreate(path string, dtype DatasetType,
return return
} }
// Close dataset and all its recursive children datasets (close handle and cleanup dataset object/s from memory) // Close close dataset and all its recursive children datasets (close handle
// and cleanup dataset object/s from memory)
func (d *Dataset) Close() { func (d *Dataset) Close() {
if d.list != nil && d.list.zh != nil { if d.list != nil && d.list.zh != nil {
C.dataset_list_close(d.list) C.dataset_list_close(d.list)
@ -156,7 +169,7 @@ func (d *Dataset) Close() {
} }
} }
// Destroys the dataset. The caller must make sure that the filesystem // 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 // 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. // to true to defer destruction for when dataset is not in use.
func (d *Dataset) Destroy(Defer bool) (err error) { func (d *Dataset) Destroy(Defer bool) (err error) {
@ -165,13 +178,13 @@ func (d *Dataset) Destroy(Defer bool) (err error) {
if e != nil { if e != nil {
return return
} }
dsType, e := d.GetProperty(ZFSPropType) dsType, e := d.GetProperty(DatasetPropType)
err = errors.New("Cannot destroy dataset " + path + err = errors.New("Cannot destroy dataset " + path +
": " + dsType.Value + " has children") ": " + dsType.Value + " has children")
return return
} }
if d.list != nil { if d.list != nil {
if ec := C.zfs_destroy(d.list.zh, boolean_t(Defer)); ec != 0 { if ec := C.zfs_destroy(d.list.zh, booleanT(Defer)); ec != 0 {
err = LastError() err = LastError()
} }
} else { } else {
@ -180,7 +193,7 @@ func (d *Dataset) Destroy(Defer bool) (err error) {
return return
} }
// Recursively destroy children of dataset and dataset. // DestroyRecursive recursively destroy children of dataset and dataset.
func (d *Dataset) DestroyRecursive() (err error) { func (d *Dataset) DestroyRecursive() (err error) {
if len(d.Children) > 0 { if len(d.Children) > 0 {
for _, c := range d.Children { for _, c := range d.Children {
@ -197,6 +210,7 @@ func (d *Dataset) DestroyRecursive() (err error) {
return return
} }
// Pool returns pool dataset belongs to
func (d *Dataset) Pool() (p Pool, err error) { func (d *Dataset) Pool() (p Pool, err error) {
if d.list == nil { if d.list == nil {
err = errors.New(msgDatasetIsNil) err = errors.New(msgDatasetIsNil)
@ -212,6 +226,7 @@ func (d *Dataset) Pool() (p Pool, err error) {
return return
} }
// ReloadProperties re-read dataset's properties
func (d *Dataset) ReloadProperties() (err error) { func (d *Dataset) ReloadProperties() (err error) {
if d.list == nil { if d.list == nil {
err = errors.New(msgDatasetIsNil) err = errors.New(msgDatasetIsNil)
@ -220,8 +235,8 @@ func (d *Dataset) ReloadProperties() (err error) {
var plist *C.property_list_t var plist *C.property_list_t
plist = C.new_property_list() plist = C.new_property_list()
defer C.free_properties(plist) defer C.free_properties(plist)
d.Properties = make(map[ZFSProp]Property) d.Properties = make(map[Prop]Property)
for prop := ZFSPropType; prop < ZFSNumProps; prop++ { for prop := DatasetPropType; prop < DatasetNumProps; prop++ {
errcode := C.read_dataset_property(d.list.zh, plist, C.int(prop)) errcode := C.read_dataset_property(d.list.zh, plist, C.int(prop))
if errcode != 0 { if errcode != 0 {
continue continue
@ -232,9 +247,9 @@ func (d *Dataset) ReloadProperties() (err error) {
return return
} }
// Reload and return single specified property. This also reloads requested // GetProperty reload and return single specified property. This also reloads requested
// property in Properties map. // property in Properties map.
func (d *Dataset) GetProperty(p ZFSProp) (prop Property, err error) { func (d *Dataset) GetProperty(p Prop) (prop Property, err error) {
if d.list == nil { if d.list == nil {
err = errors.New(msgDatasetIsNil) err = errors.New(msgDatasetIsNil)
return return
@ -253,10 +268,10 @@ func (d *Dataset) GetProperty(p ZFSProp) (prop Property, err error) {
return return
} }
// Set ZFS dataset property to value. Not all properties can be set, // 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. // some can be set only at creation time and some are read only.
// Always check if returned error and its description. // Always check if returned error and its description.
func (d *Dataset) SetProperty(p ZFSProp, value string) (err error) { func (d *Dataset) SetProperty(p Prop, value string) (err error) {
if d.list == nil { if d.list == nil {
err = errors.New(msgDatasetIsNil) err = errors.New(msgDatasetIsNil)
return return
@ -273,15 +288,15 @@ func (d *Dataset) SetProperty(p ZFSProp, value string) (err error) {
return return
} }
// Clones the dataset. The target must be of the same type as // Clone - clones the dataset. The target must be of the same type as
// the source. // the source.
func (d *Dataset) Clone(target string, props map[ZFSProp]Property) (rd Dataset, err error) { func (d *Dataset) Clone(target string, props map[Prop]Property) (rd Dataset, err error) {
var cprops *C.nvlist_t var cprops *C.nvlist_t
if d.list == nil { if d.list == nil {
err = errors.New(msgDatasetIsNil) err = errors.New(msgDatasetIsNil)
return return
} }
if cprops, err = datasetPropertiesTo_nvlist(props); err != nil { if cprops, err = datasetPropertiesTonvlist(props); err != nil {
return return
} }
defer C.nvlist_free(cprops) defer C.nvlist_free(cprops)
@ -293,14 +308,14 @@ func (d *Dataset) Clone(target string, props map[ZFSProp]Property) (rd Dataset,
return return
} }
// Create dataset snapshot. Set recur to true to snapshot child datasets. // DatasetSnapshot create dataset snapshot. Set recur to true to snapshot child datasets.
func DatasetSnapshot(path string, recur bool, props map[ZFSProp]Property) (rd Dataset, err error) { func DatasetSnapshot(path string, recur bool, props map[Prop]Property) (rd Dataset, err error) {
var cprops *C.nvlist_t var cprops *C.nvlist_t
if cprops, err = datasetPropertiesTo_nvlist(props); err != nil { if cprops, err = datasetPropertiesTonvlist(props); err != nil {
return return
} }
defer C.nvlist_free(cprops) defer C.nvlist_free(cprops)
if errc := C.zfs_snapshot(libzfs_handle, C.CString(path), boolean_t(recur), cprops); errc != 0 { if errc := C.zfs_snapshot(libzfsHandle, C.CString(path), booleanT(recur), cprops); errc != 0 {
err = LastError() err = LastError()
return return
} }
@ -308,7 +323,7 @@ func DatasetSnapshot(path string, recur bool, props map[ZFSProp]Property) (rd Da
return return
} }
// Return zfs dataset path/name // Path return zfs dataset path/name
func (d *Dataset) Path() (path string, err error) { func (d *Dataset) Path() (path string, err error) {
if d.list == nil { if d.list == nil {
err = errors.New(msgDatasetIsNil) err = errors.New(msgDatasetIsNil)
@ -319,14 +334,14 @@ func (d *Dataset) Path() (path string, err error) {
return return
} }
// Rollabck dataset snapshot // Rollback rollabck's dataset snapshot
func (d *Dataset) Rollback(snap *Dataset, force bool) (err error) { func (d *Dataset) Rollback(snap *Dataset, force bool) (err error) {
if d.list == nil { if d.list == nil {
err = errors.New(msgDatasetIsNil) err = errors.New(msgDatasetIsNil)
return return
} }
if errc := C.zfs_rollback(d.list.zh, if errc := C.zfs_rollback(d.list.zh,
snap.list.zh, boolean_t(force)); errc != 0 { snap.list.zh, booleanT(force)); errc != 0 {
err = LastError() err = LastError()
} }
return return
@ -334,20 +349,20 @@ func (d *Dataset) Rollback(snap *Dataset, force bool) (err error) {
// Rename dataset // Rename dataset
func (d *Dataset) Rename(newname string, recur, func (d *Dataset) Rename(newname string, recur,
force_umount bool) (err error) { forceUnmount bool) (err error) {
if d.list == nil { if d.list == nil {
err = errors.New(msgDatasetIsNil) err = errors.New(msgDatasetIsNil)
return return
} }
if errc := C.zfs_rename(d.list.zh, C.CString(newname), if errc := C.zfs_rename(d.list.zh, C.CString(newname),
boolean_t(recur), boolean_t(force_umount)); errc != 0 { booleanT(recur), booleanT(forceUnmount)); errc != 0 {
err = LastError() err = LastError()
} }
return return
} }
// Checks to see if the mount is active. If the filesystem is mounted, fills // IsMounted checks to see if the mount is active. If the filesystem is mounted,
// in 'where' with the current mountpoint, and returns true. Otherwise, // sets in 'where' argument the current mountpoint, and returns true. Otherwise,
// returns false. // returns false.
func (d *Dataset) IsMounted() (mounted bool, where string) { func (d *Dataset) IsMounted() (mounted bool, where string) {
var cw *C.char var cw *C.char
@ -386,7 +401,8 @@ func (d *Dataset) Unmount(flags int) (err error) {
return return
} }
// Unmount this filesystem and any children inheriting the mountpoint property. // UnmountAll unmount this filesystem and any children inheriting the
// mountpoint property.
func (d *Dataset) UnmountAll(flags int) (err error) { func (d *Dataset) UnmountAll(flags int) (err error) {
if d.list == nil { if d.list == nil {
err = errors.New(msgDatasetIsNil) err = errors.New(msgDatasetIsNil)
@ -398,12 +414,12 @@ func (d *Dataset) UnmountAll(flags int) (err error) {
return return
} }
// Convert property to name // DatasetPropertyToName convert property to name
// ( returns built in string representation of property name). // ( returns built in string representation of property name).
// This is optional, you can represent each property with string // This is optional, you can represent each property with string
// name of choice. // name of choice.
func DatasetPropertyToName(p ZFSProp) (name string) { func DatasetPropertyToName(p Prop) (name string) {
if p == ZFSNumProps { if p == DatasetNumProps {
return "numofprops" return "numofprops"
} }
prop := C.zfs_prop_t(p) prop := C.zfs_prop_t(p)

View File

@ -2,15 +2,16 @@ package zfs_test
import ( import (
"fmt" "fmt"
"github.com/bicomsystems/go-libzfs"
"testing" "testing"
"github.com/bicomsystems/go-libzfs"
) )
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
// HELPERS: // HELPERS:
var TST_DATASET_PATH = TST_POOL_NAME + "/DATASET" var TSTDatasetPath = TSTPoolName + "/DATASET"
var TST_VOLUME_PATH = TST_DATASET_PATH + "/VOLUME" var TSTVolumePath = TSTDatasetPath + "/VOLUME"
var TST_DATASET_PATH_SNAP = TST_DATASET_PATH + "@test" var TSTDatasetPathSnap = TSTDatasetPath + "@test"
func printDatasets(ds []zfs.Dataset) error { func printDatasets(ds []zfs.Dataset) error {
for _, d := range ds { for _, d := range ds {
@ -19,7 +20,7 @@ func printDatasets(ds []zfs.Dataset) error {
if err != nil { if err != nil {
return err return err
} }
p, err := d.GetProperty(zfs.ZFSPropType) p, err := d.GetProperty(zfs.DatasetPropType)
if err != nil { if err != nil {
return err return err
} }
@ -36,45 +37,45 @@ func printDatasets(ds []zfs.Dataset) error {
func zfsTestDatasetCreate(t *testing.T) { func zfsTestDatasetCreate(t *testing.T) {
// reinit names used in case TESTPOOL was in conflict // reinit names used in case TESTPOOL was in conflict
TST_DATASET_PATH = TST_POOL_NAME + "/DATASET" TSTDatasetPath = TSTPoolName + "/DATASET"
TST_VOLUME_PATH = TST_DATASET_PATH + "/VOLUME" TSTVolumePath = TSTDatasetPath + "/VOLUME"
TST_DATASET_PATH_SNAP = TST_DATASET_PATH + "@test" TSTDatasetPathSnap = TSTDatasetPath + "@test"
println("TEST DatasetCreate(", TST_DATASET_PATH, ") (filesystem) ... ") println("TEST DatasetCreate(", TSTDatasetPath, ") (filesystem) ... ")
props := make(map[zfs.ZFSProp]zfs.Property) props := make(map[zfs.Prop]zfs.Property)
d, err := zfs.DatasetCreate(TST_DATASET_PATH, zfs.DatasetTypeFilesystem, props) d, err := zfs.DatasetCreate(TSTDatasetPath, zfs.DatasetTypeFilesystem, props)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
} }
d.Close() d.Close()
println("PASS\n") print("PASS\n\n")
strSize := "536870912" // 512M strSize := "536870912" // 512M
println("TEST DatasetCreate(", TST_VOLUME_PATH, ") (volume) ... ") println("TEST DatasetCreate(", TSTVolumePath, ") (volume) ... ")
props[zfs.ZFSPropVolsize] = zfs.Property{Value: strSize} props[zfs.DatasetPropVolsize] = zfs.Property{Value: strSize}
// In addition I explicitly choose some more properties to be set. // In addition I explicitly choose some more properties to be set.
props[zfs.ZFSPropVolblocksize] = zfs.Property{Value: "4096"} props[zfs.DatasetPropVolblocksize] = zfs.Property{Value: "4096"}
props[zfs.ZFSPropReservation] = zfs.Property{Value: strSize} props[zfs.DatasetPropReservation] = zfs.Property{Value: strSize}
d, err = zfs.DatasetCreate(TST_VOLUME_PATH, zfs.DatasetTypeVolume, props) d, err = zfs.DatasetCreate(TSTVolumePath, zfs.DatasetTypeVolume, props)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
} }
d.Close() d.Close()
println("PASS\n") print("PASS\n\n")
} }
func zfsTestDatasetOpen(t *testing.T) { func zfsTestDatasetOpen(t *testing.T) {
println("TEST DatasetOpen(", TST_DATASET_PATH, ") ... ") println("TEST DatasetOpen(", TSTDatasetPath, ") ... ")
d, err := zfs.DatasetOpen(TST_DATASET_PATH) d, err := zfs.DatasetOpen(TSTDatasetPath)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
} }
d.Close() d.Close()
println("PASS\n") print("PASS\n\n")
} }
func zfsTestDatasetOpenAll(t *testing.T) { func zfsTestDatasetOpenAll(t *testing.T) {
@ -90,24 +91,24 @@ func zfsTestDatasetOpenAll(t *testing.T) {
return return
} }
zfs.DatasetCloseAll(ds) zfs.DatasetCloseAll(ds)
println("PASS\n") print("PASS\n\n")
} }
func zfsTestDatasetSnapshot(t *testing.T) { func zfsTestDatasetSnapshot(t *testing.T) {
println("TEST DatasetSnapshot(", TST_DATASET_PATH, ", true, ...) ... ") println("TEST DatasetSnapshot(", TSTDatasetPath, ", true, ...) ... ")
props := make(map[zfs.ZFSProp]zfs.Property) props := make(map[zfs.Prop]zfs.Property)
d, err := zfs.DatasetSnapshot(TST_DATASET_PATH_SNAP, true, props) d, err := zfs.DatasetSnapshot(TSTDatasetPathSnap, true, props)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
} }
defer d.Close() defer d.Close()
println("PASS\n") print("PASS\n\n")
} }
func zfsTestDatasetDestroy(t *testing.T) { func zfsTestDatasetDestroy(t *testing.T) {
println("TEST DATASET Destroy( ", TST_DATASET_PATH, " ) ... ") println("TEST DATASET Destroy( ", TSTDatasetPath, " ) ... ")
d, err := zfs.DatasetOpen(TST_DATASET_PATH) d, err := zfs.DatasetOpen(TSTDatasetPath)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
@ -117,7 +118,7 @@ func zfsTestDatasetDestroy(t *testing.T) {
t.Error(err) t.Error(err)
return return
} }
println("PASS\n") print("PASS\n\n")
} }
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
@ -128,7 +129,7 @@ func ExampleDatasetCreate() {
// Create map to represent ZFS dataset properties. This is equivalent to // Create map to represent ZFS dataset properties. This is equivalent to
// list of properties you can get from ZFS CLI tool, and some more // list of properties you can get from ZFS CLI tool, and some more
// internally used by libzfs. // internally used by libzfs.
props := make(map[zfs.ZFSProp]zfs.Property) props := make(map[zfs.Prop]zfs.Property)
// I choose to create (block) volume 1GiB in size. Size is just ZFS dataset // I choose to create (block) volume 1GiB in size. Size is just ZFS dataset
// property and this is done as map of strings. So, You have to either // property and this is done as map of strings. So, You have to either
@ -136,10 +137,10 @@ func ExampleDatasetCreate() {
// similar to convert in to string (base 10) from numeric type. // similar to convert in to string (base 10) from numeric type.
strSize := "1073741824" strSize := "1073741824"
props[zfs.ZFSPropVolsize] = zfs.Property{Value: strSize} props[zfs.DatasetPropVolsize] = zfs.Property{Value: strSize}
// In addition I explicitly choose some more properties to be set. // In addition I explicitly choose some more properties to be set.
props[zfs.ZFSPropVolblocksize] = zfs.Property{Value: "4096"} props[zfs.DatasetPropVolblocksize] = zfs.Property{Value: "4096"}
props[zfs.ZFSPropReservation] = zfs.Property{Value: strSize} props[zfs.DatasetPropReservation] = zfs.Property{Value: strSize}
// Lets create desired volume // Lets create desired volume
d, err := zfs.DatasetCreate("TESTPOOL/VOLUME1", zfs.DatasetTypeVolume, props) d, err := zfs.DatasetCreate("TESTPOOL/VOLUME1", zfs.DatasetTypeVolume, props)
@ -161,10 +162,11 @@ func ExampleDatasetOpen() {
} }
defer d.Close() defer d.Close()
var p zfs.Property var p zfs.Property
if p, err = d.GetProperty(zfs.ZFSPropAvailable); err != nil { if p, err = d.GetProperty(zfs.DatasetPropAvailable); err != nil {
panic(err.Error()) panic(err.Error())
} }
println(zfs.DatasetPropertyToName(zfs.ZFSPropAvailable), " = ", p.Value) println(zfs.DatasetPropertyToName(zfs.DatasetPropAvailable), " = ",
p.Value)
} }
func ExampleDatasetOpenAll() { func ExampleDatasetOpenAll() {
@ -180,7 +182,7 @@ func ExampleDatasetOpenAll() {
if err != nil { if err != nil {
panic(err.Error()) panic(err.Error())
} }
p, err := d.GetProperty(zfs.ZFSPropType) p, err := d.GetProperty(zfs.DatasetPropType)
if err != nil { if err != nil {
panic(err.Error()) panic(err.Error())
} }

127
zpool.go
View File

@ -10,16 +10,17 @@ import (
"errors" "errors"
"fmt" "fmt"
"strconv" "strconv"
"time"
) )
const ( const (
msgPoolIsNil = "Pool handle not initialized or its closed" msgPoolIsNil = "Pool handle not initialized or its closed"
) )
type PoolProperties map[PoolProp]string // PoolProperties type is map of pool properties name -> value
type ZFSProperties map[ZFSProp]string type PoolProperties map[Prop]string
// Object represents handler to single ZFS pool // Pool object represents handler to single ZFS pool
// //
/* Pool.Properties map[string]Property /* Pool.Properties map[string]Property
*/ */
@ -34,11 +35,11 @@ type Pool struct {
Features map[string]string Features map[string]string
} }
// Open ZFS pool handler by name. // PoolOpen open ZFS pool handler by name.
// Returns Pool object, requires Pool.Close() to be called explicitly // Returns Pool object, requires Pool.Close() to be called explicitly
// for memory cleanup after object is not needed anymore. // for memory cleanup after object is not needed anymore.
func PoolOpen(name string) (pool Pool, err error) { func PoolOpen(name string) (pool Pool, err error) {
pool.list = C.zpool_list_open(libzfs_handle, C.CString(name)) pool.list = C.zpool_list_open(libzfsHandle, C.CString(name))
if pool.list != nil { if pool.list != nil {
err = pool.ReloadProperties() err = pool.ReloadProperties()
return return
@ -47,7 +48,7 @@ func PoolOpen(name string) (pool Pool, err error) {
return return
} }
// Given a list of directories to search, find and import pool with matching // PoolImport given a list of directories to search, find and import pool with matching
// name stored on disk. // name stored on disk.
func PoolImport(name string, searchpaths []string) (pool Pool, err error) { func PoolImport(name string, searchpaths []string) (pool Pool, err error) {
errPoolList := errors.New("Failed to list pools") errPoolList := errors.New("Failed to list pools")
@ -59,7 +60,7 @@ func PoolImport(name string, searchpaths []string) (pool Pool, err error) {
C.strings_setat(cpaths, C.int(i), C.CString(path)) C.strings_setat(cpaths, C.int(i), C.CString(path))
} }
pools := C.zpool_find_import(libzfs_handle, C.int(numofp), cpaths) pools := C.zpool_find_import(libzfsHandle, C.int(numofp), cpaths)
defer C.nvlist_free(pools) defer C.nvlist_free(pools)
elem = C.nvlist_next_nvpair(pools, elem) elem = C.nvlist_next_nvpair(pools, elem)
@ -88,7 +89,7 @@ func PoolImport(name string, searchpaths []string) (pool Pool, err error) {
return return
} }
retcode := C.zpool_import(libzfs_handle, config, C.CString(name), nil) retcode := C.zpool_import(libzfsHandle, config, C.CString(name), nil)
if retcode != 0 { if retcode != 0 {
err = LastError() err = LastError()
return return
@ -97,12 +98,12 @@ func PoolImport(name string, searchpaths []string) (pool Pool, err error) {
return return
} }
// Open all active ZFS pools on current system. // PoolOpenAll open all active ZFS pools on current system.
// Returns array of Pool handlers, each have to be closed after not needed // Returns array of Pool handlers, each have to be closed after not needed
// anymore. Call Pool.Close() method. // anymore. Call Pool.Close() method.
func PoolOpenAll() (pools []Pool, err error) { func PoolOpenAll() (pools []Pool, err error) {
var pool Pool var pool Pool
errcode := C.zpool_list(libzfs_handle, &pool.list) errcode := C.zpool_list(libzfsHandle, &pool.list)
for pool.list != nil { for pool.list != nil {
err = pool.ReloadProperties() err = pool.ReloadProperties()
if err != nil { if err != nil {
@ -117,17 +118,18 @@ func PoolOpenAll() (pools []Pool, err error) {
return return
} }
// PoolCloseAll close all pools in given slice
func PoolCloseAll(pools []Pool) { func PoolCloseAll(pools []Pool) {
for _, p := range pools { for _, p := range pools {
p.Close() p.Close()
} }
} }
// Convert property to name // PoolPropertyToName convert property to name
// ( returns built in string representation of property name). // ( returns built in string representation of property name).
// This is optional, you can represent each property with string // This is optional, you can represent each property with string
// name of choice. // name of choice.
func PoolPropertyToName(p PoolProp) (name string) { func PoolPropertyToName(p Prop) (name string) {
if p == PoolNumProps { if p == PoolNumProps {
return "numofprops" return "numofprops"
} }
@ -136,15 +138,15 @@ func PoolPropertyToName(p PoolProp) (name string) {
return return
} }
// Map POOL STATE to string. // PoolStateToName maps POOL STATE to string.
func PoolStateToName(state PoolState) (name string) { func PoolStateToName(state PoolState) (name string) {
ps := C.pool_state_t(state) ps := C.pool_state_t(state)
name = C.GoString(C.zpool_pool_state_to_name(ps)) name = C.GoString(C.zpool_pool_state_to_name(ps))
return return
} }
// Re-read ZFS pool properties and features, refresh Pool.Properties and // ReloadProperties re-read ZFS pool properties and features, refresh
// Pool.Features map // Pool.Properties and Pool.Features map
func (pool *Pool) ReloadProperties() (err error) { func (pool *Pool) ReloadProperties() (err error) {
propList := C.read_zpool_properties(pool.list.zph) propList := C.read_zpool_properties(pool.list.zph)
if propList == nil { if propList == nil {
@ -165,15 +167,15 @@ func (pool *Pool) ReloadProperties() (err error) {
"async_destroy": "disabled", "async_destroy": "disabled",
"empty_bpobj": "disabled", "empty_bpobj": "disabled",
"lz4_compress": "disabled"} "lz4_compress": "disabled"}
for name, _ := range pool.Features { for name := range pool.Features {
pool.GetFeature(name) pool.GetFeature(name)
} }
return return
} }
// Reload and return single specified property. This also reloads requested // GetProperty reload and return single specified property. This also reloads requested
// property in Properties map. // property in Properties map.
func (pool *Pool) GetProperty(p PoolProp) (prop Property, err error) { func (pool *Pool) GetProperty(p Prop) (prop Property, err error) {
if pool.list != nil { if pool.list != nil {
// First check if property exist at all // First check if property exist at all
if p < PoolPropName || p > PoolNumProps { if p < PoolPropName || p > PoolNumProps {
@ -194,7 +196,7 @@ func (pool *Pool) GetProperty(p PoolProp) (prop Property, err error) {
return prop, errors.New(msgPoolIsNil) return prop, errors.New(msgPoolIsNil)
} }
// Reload and return single specified feature. This also reloads requested // GetFeature reload and return single specified feature. This also reloads requested
// feature in Features map. // feature in Features map.
func (pool *Pool) GetFeature(name string) (value string, err error) { func (pool *Pool) GetFeature(name string) (value string, err error) {
var fvalue [512]C.char var fvalue [512]C.char
@ -209,10 +211,10 @@ func (pool *Pool) GetFeature(name string) (value string, err error) {
return return
} }
// Set ZFS pool property to value. Not all properties can be set, // 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. // some can be set only at creation time and some are read only.
// Always check if returned error and its description. // Always check if returned error and its description.
func (pool *Pool) SetProperty(p PoolProp, value string) (err error) { func (pool *Pool) SetProperty(p Prop, value string) (err error) {
if pool.list != nil { if pool.list != nil {
// First check if property exist at all // First check if property exist at all
if p < PoolPropName || p > PoolNumProps { if p < PoolPropName || p > PoolNumProps {
@ -241,7 +243,7 @@ func (pool *Pool) Close() {
pool.list = nil pool.list = nil
} }
// Get (re-read) ZFS pool name property // Name get (re-read) ZFS pool name property
func (pool *Pool) Name() (name string, err error) { func (pool *Pool) Name() (name string, err error) {
if pool.list == nil { if pool.list == nil {
err = errors.New(msgPoolIsNil) err = errors.New(msgPoolIsNil)
@ -252,7 +254,7 @@ func (pool *Pool) Name() (name string, err error) {
return return
} }
// Get ZFS pool state // State get ZFS pool state
// Return the state of the pool (ACTIVE or UNAVAILABLE) // Return the state of the pool (ACTIVE or UNAVAILABLE)
func (pool *Pool) State() (state PoolState, err error) { func (pool *Pool) State() (state PoolState, err error) {
if pool.list == nil { if pool.list == nil {
@ -263,7 +265,7 @@ func (pool *Pool) State() (state PoolState, err error) {
return return
} }
// ZFS virtual device specification // VDevSpec ZFS virtual device specification
type VDevSpec struct { type VDevSpec struct {
Type VDevType Type VDevType
Devices []VDevSpec // groups other devices (e.g. mirror) Devices []VDevSpec // groups other devices (e.g. mirror)
@ -271,31 +273,31 @@ type VDevSpec struct {
Path string Path string
} }
func (self *VDevSpec) isGrouping() (grouping bool, mindevs, maxdevs int) { func (vdev *VDevSpec) isGrouping() (grouping bool, mindevs, maxdevs int) {
maxdevs = int(^uint(0) >> 1) maxdevs = int(^uint(0) >> 1)
if self.Type == VDevTypeRaidz { if vdev.Type == VDevTypeRaidz {
grouping = true grouping = true
if self.Parity == 0 { if vdev.Parity == 0 {
self.Parity = 1 vdev.Parity = 1
} }
if self.Parity > 254 { if vdev.Parity > 254 {
self.Parity = 254 vdev.Parity = 254
} }
mindevs = int(self.Parity) + 1 mindevs = int(vdev.Parity) + 1
maxdevs = 255 maxdevs = 255
} else if self.Type == VDevTypeMirror { } else if vdev.Type == VDevTypeMirror {
grouping = true grouping = true
mindevs = 2 mindevs = 2
} else if self.Type == VDevTypeLog || self.Type == VDevTypeSpare || self.Type == VDevTypeL2cache { } else if vdev.Type == VDevTypeLog || vdev.Type == VDevTypeSpare || vdev.Type == VDevTypeL2cache {
grouping = true grouping = true
mindevs = 1 mindevs = 1
} }
return return
} }
func (self *VDevSpec) isLog() (r C.uint64_t) { func (vdev *VDevSpec) isLog() (r C.uint64_t) {
r = 0 r = 0
if self.Type == VDevTypeLog { if vdev.Type == VDevTypeLog {
r = 1 r = 1
} }
return return
@ -317,7 +319,7 @@ func toCPoolProperties(props PoolProperties) (cprops *C.nvlist_t) {
return return
} }
func toCZFSProperties(props ZFSProperties) (cprops *C.nvlist_t) { func toCDatasetProperties(props DatasetProperties) (cprops *C.nvlist_t) {
cprops = nil cprops = nil
for prop, value := range props { for prop, value := range props {
name := C.zfs_prop_to_name(C.zfs_prop_t(prop)) name := C.zfs_prop_to_name(C.zfs_prop_t(prop))
@ -361,7 +363,7 @@ func buildVDevSpec(root *C.nvlist_t, rtype VDevType, vdevs []VDevSpec,
defer C.nvlist_free_array(l2cache) defer C.nvlist_free_array(l2cache)
for i, vdev := range vdevs { for i, vdev := range vdevs {
grouping, mindevs, maxdevs := vdev.isGrouping() grouping, mindevs, maxdevs := vdev.isGrouping()
var child *C.nvlist_t = nil var child *C.nvlist_t
// fmt.Println(vdev.Type) // fmt.Println(vdev.Type)
if r := C.nvlist_alloc(&child, C.NV_UNIQUE_NAME, 0); r != 0 { if r := C.nvlist_alloc(&child, C.NV_UNIQUE_NAME, 0); r != 0 {
err = errors.New("Failed to allocate vdev") err = errors.New("Failed to allocate vdev")
@ -369,8 +371,9 @@ func buildVDevSpec(root *C.nvlist_t, rtype VDevType, vdevs []VDevSpec,
} }
vcount := len(vdev.Devices) vcount := len(vdev.Devices)
if vcount < mindevs || vcount > maxdevs { if vcount < mindevs || vcount > maxdevs {
err = errors.New(fmt.Sprintf( err = fmt.Errorf(
"Invalid vdev specification: %s supports no less than %d or more than %d devices", vdev.Type, mindevs, maxdevs)) "Invalid vdev specification: %s supports no less than %d or more than %d devices",
vdev.Type, mindevs, maxdevs)
return return
} }
if r := C.nvlist_add_string(child, C.CString(C.ZPOOL_CONFIG_TYPE), if r := C.nvlist_add_string(child, C.CString(C.ZPOOL_CONFIG_TYPE),
@ -466,11 +469,11 @@ func buildVDevSpec(root *C.nvlist_t, rtype VDevType, vdevs []VDevSpec,
return return
} }
// Create ZFS pool per specs, features and properties of pool and root dataset // 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 []VDevSpec, features map[string]string,
props PoolProperties, fsprops ZFSProperties) (pool Pool, err error) { props PoolProperties, fsprops DatasetProperties) (pool Pool, err error) {
// create root vdev nvroot // create root vdev nvroot
var nvroot *C.nvlist_t = nil var nvroot *C.nvlist_t
if r := C.nvlist_alloc(&nvroot, C.NV_UNIQUE_NAME, 0); r != 0 { if r := C.nvlist_alloc(&nvroot, C.NV_UNIQUE_NAME, 0); r != 0 {
err = errors.New("Failed to allocate root vdev") err = errors.New("Failed to allocate root vdev")
return return
@ -495,7 +498,7 @@ func PoolCreate(name string, vdevs []VDevSpec, features map[string]string,
err = errors.New("Failed to allocate pool properties") err = errors.New("Failed to allocate pool properties")
return return
} }
cfsprops := toCZFSProperties(fsprops) cfsprops := toCDatasetProperties(fsprops)
if cfsprops != nil { if cfsprops != nil {
defer C.nvlist_free(cfsprops) defer C.nvlist_free(cfsprops)
} else if len(fsprops) > 0 { } else if len(fsprops) > 0 {
@ -516,16 +519,34 @@ func PoolCreate(name string, vdevs []VDevSpec, features map[string]string,
} }
// Create actual pool then open // Create actual pool then open
if r := C.zpool_create(libzfs_handle, C.CString(name), nvroot, if r := C.zpool_create(libzfsHandle, C.CString(name), nvroot,
cprops, cfsprops); r != 0 { cprops, cfsprops); r != 0 {
err = LastError() err = LastError()
return err = errors.New(err.Error() + " (zpool_create)")
}
pool, err = PoolOpen(name)
return return
} }
// Get pool status. Let you check if pool healthy. // It can happen that pool is not immediately available,
// we know we just created it with success so lets wait and retry
// but only in case EZFS_NOENT error
retr := 0
for pool, err = PoolOpen(name); err != nil && retr < 3; retr++ {
errno := C.libzfs_errno(libzfsHandle)
if errno == ENoent {
time.Sleep(500 * time.Millisecond)
} else {
err = errors.New(err.Error() + " (PoolOpen)")
return
}
pool, err = PoolOpen(name)
}
if err != nil {
err = errors.New(err.Error() + " (PoolOpen)")
}
return
}
// Status get pool status. Let you check if pool healthy.
func (pool *Pool) Status() (status PoolStatus, err error) { func (pool *Pool) Status() (status PoolStatus, err error) {
var msgid *C.char var msgid *C.char
var reason C.zpool_status_t var reason C.zpool_status_t
@ -554,22 +575,22 @@ func (pool *Pool) Destroy(logStr string) (err error) {
return return
} }
// Exports the pool from the system. // Export exports the pool from the system.
// Before exporting the pool, all datasets within the pool are unmounted. // 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 // A pool can not be exported if it has a shared spare that is currently
// being used. // being used.
func (pool *Pool) Export(force bool, log string) (err error) { func (pool *Pool) Export(force bool, log string) (err error) {
var force_t C.boolean_t = 0 var forcet C.boolean_t
if force { if force {
force_t = 1 forcet = 1
} }
if rc := C.zpool_export(pool.list.zph, force_t, C.CString(log)); rc != 0 { if rc := C.zpool_export(pool.list.zph, forcet, C.CString(log)); rc != 0 {
err = LastError() err = LastError()
} }
return return
} }
// Hard force // ExportForce hard force export of the pool from the system.
func (pool *Pool) ExportForce(log string) (err error) { func (pool *Pool) ExportForce(log string) (err error) {
if rc := C.zpool_export_force(pool.list.zph, C.CString(log)); rc != 0 { if rc := C.zpool_export_force(pool.list.zph, C.CString(log)); rc != 0 {
err = LastError() err = LastError()

View File

@ -2,16 +2,17 @@ package zfs_test
import ( import (
"fmt" "fmt"
"github.com/bicomsystems/go-libzfs"
"io/ioutil" "io/ioutil"
"os" "os"
"testing" "testing"
"github.com/bicomsystems/go-libzfs"
) )
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
// HELPERS: // HELPERS:
var TST_POOL_NAME = "TESTPOOL" var TSTPoolName = "TESTPOOL"
func CreateTmpSparse(prefix string, size int64) (path string, err error) { func CreateTmpSparse(prefix string, size int64) (path string, err error) {
sf, err := ioutil.TempFile("/tmp", prefix) sf, err := ioutil.TempFile("/tmp", prefix)
@ -66,12 +67,12 @@ func zpoolTestPoolCreate(t *testing.T) {
// first check if pool with same name already exist // first check if pool with same name already exist
// we don't want conflict // we don't want conflict
for { for {
p, err := zfs.PoolOpen(TST_POOL_NAME) p, err := zfs.PoolOpen(TSTPoolName)
if err != nil { if err != nil {
break break
} }
p.Close() p.Close()
TST_POOL_NAME += "0" TSTPoolName += "0"
} }
var err error var err error
@ -94,15 +95,15 @@ func zpoolTestPoolCreate(t *testing.T) {
zfs.VDevSpec{Type: zfs.VDevTypeSpare, Devices: sdevs}, zfs.VDevSpec{Type: zfs.VDevTypeSpare, Devices: sdevs},
} }
props := make(map[zfs.PoolProp]string) props := make(map[zfs.Prop]string)
fsprops := make(map[zfs.ZFSProp]string) fsprops := make(map[zfs.Prop]string)
features := make(map[string]string) features := make(map[string]string)
fsprops[zfs.ZFSPropMountpoint] = "none" fsprops[zfs.DatasetPropMountpoint] = "none"
features["async_destroy"] = "enabled" features["async_destroy"] = "enabled"
features["empty_bpobj"] = "enabled" features["empty_bpobj"] = "enabled"
features["lz4_compress"] = "enabled" features["lz4_compress"] = "enabled"
pool, err := zfs.PoolCreate(TST_POOL_NAME, vdevs, features, props, fsprops) pool, err := zfs.PoolCreate(TSTPoolName, vdevs, features, props, fsprops)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
// try cleanup // try cleanup
@ -113,7 +114,7 @@ func zpoolTestPoolCreate(t *testing.T) {
} }
defer pool.Close() defer pool.Close()
println("PASS\n") print("PASS\n\n")
} }
// Open and list all pools and them state on the system // Open and list all pools and them state on the system
@ -143,22 +144,22 @@ func zpoolTestPoolOpenAll(t *testing.T) {
println("\tPool: ", pname, " state: ", pstate) println("\tPool: ", pname, " state: ", pstate)
p.Close() p.Close()
} }
println("PASS\n") print("PASS\n\n")
} }
func zpoolTestPoolDestroy(t *testing.T) { func zpoolTestPoolDestroy(t *testing.T) {
println("TEST POOL Destroy( ", TST_POOL_NAME, " ) ... ") println("TEST POOL Destroy( ", TSTPoolName, " ) ... ")
p, err := zfs.PoolOpen(TST_POOL_NAME) p, err := zfs.PoolOpen(TSTPoolName)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
} }
defer p.Close() defer p.Close()
if err = p.Destroy("Test of pool destroy (" + TST_POOL_NAME + ")"); err != nil { if err = p.Destroy("Test of pool destroy (" + TSTPoolName + ")"); err != nil {
t.Error(err.Error()) t.Error(err.Error())
return return
} }
println("PASS\n") print("PASS\n\n")
} }
func zpoolTestFailPoolOpen(t *testing.T) { func zpoolTestFailPoolOpen(t *testing.T) {
@ -166,7 +167,7 @@ func zpoolTestFailPoolOpen(t *testing.T) {
pname := "fail to open this pool" pname := "fail to open this pool"
p, err := zfs.PoolOpen(pname) p, err := zfs.PoolOpen(pname)
if err != nil { if err != nil {
println("PASS\n") print("PASS\n\n")
return return
} }
t.Error("PoolOpen pass when it should fail") t.Error("PoolOpen pass when it should fail")
@ -174,43 +175,43 @@ func zpoolTestFailPoolOpen(t *testing.T) {
} }
func zpoolTestExport(t *testing.T) { func zpoolTestExport(t *testing.T) {
println("TEST POOL Export( ", TST_POOL_NAME, " ) ... ") println("TEST POOL Export( ", TSTPoolName, " ) ... ")
p, err := zfs.PoolOpen(TST_POOL_NAME) p, err := zfs.PoolOpen(TSTPoolName)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
} }
p.Export(false, "Test exporting pool") p.Export(false, "Test exporting pool")
defer p.Close() defer p.Close()
println("PASS\n") print("PASS\n\n")
} }
func zpoolTestExportForce(t *testing.T) { func zpoolTestExportForce(t *testing.T) {
println("TEST POOL ExportForce( ", TST_POOL_NAME, " ) ... ") println("TEST POOL ExportForce( ", TSTPoolName, " ) ... ")
p, err := zfs.PoolOpen(TST_POOL_NAME) p, err := zfs.PoolOpen(TSTPoolName)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
} }
p.ExportForce("Test force exporting pool") p.ExportForce("Test force exporting pool")
defer p.Close() defer p.Close()
println("PASS\n") print("PASS\n\n")
} }
func zpoolTestImport(t *testing.T) { func zpoolTestImport(t *testing.T) {
println("TEST POOL Import( ", TST_POOL_NAME, " ) ... ") println("TEST POOL Import( ", TSTPoolName, " ) ... ")
p, err := zfs.PoolImport(TST_POOL_NAME, []string{"/tmp"}) p, err := zfs.PoolImport(TSTPoolName, []string{"/tmp"})
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
} }
defer p.Close() defer p.Close()
println("PASS\n") print("PASS\n\n")
} }
func zpoolTestPoolProp(t *testing.T) { func zpoolTestPoolProp(t *testing.T) {
println("TEST PoolProp on ", TST_POOL_NAME, " ... ") println("TEST PoolProp on ", TSTPoolName, " ... ")
if pool, err := zfs.PoolOpen(TST_POOL_NAME); err == nil { if pool, err := zfs.PoolOpen(TSTPoolName); err == nil {
defer pool.Close() defer pool.Close()
// Turn on snapshot listing for pool // Turn on snapshot listing for pool
pool.SetProperty(zfs.PoolPropListsnaps, "on") pool.SetProperty(zfs.PoolPropListsnaps, "on")
@ -247,12 +248,12 @@ func zpoolTestPoolProp(t *testing.T) {
t.Error(err) t.Error(err)
return return
} }
println("PASS\n") print("PASS\n\n")
} }
func zpoolTestPoolStatusAndState(t *testing.T) { func zpoolTestPoolStatusAndState(t *testing.T) {
println("TEST pool Status/State ( ", TST_POOL_NAME, " ) ... ") println("TEST pool Status/State ( ", TSTPoolName, " ) ... ")
pool, err := zfs.PoolOpen(TST_POOL_NAME) pool, err := zfs.PoolOpen(TSTPoolName)
if err != nil { if err != nil {
t.Error(err.Error()) t.Error(err.Error())
return return
@ -269,9 +270,9 @@ func zpoolTestPoolStatusAndState(t *testing.T) {
t.Error(err.Error()) t.Error(err.Error())
return return
} }
println("POOL", TST_POOL_NAME, "state:", zfs.PoolStateToName(pstate)) println("POOL", TSTPoolName, "state:", zfs.PoolStateToName(pstate))
println("PASS\n") print("PASS\n\n")
} }
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
@ -315,7 +316,7 @@ func ExamplePoolOpenAll() {
// Iterate pool properties and print name, value and source // Iterate pool properties and print name, value and source
for key, prop := range p.Properties { for key, prop := range p.Properties {
pkey := zfs.PoolProp(key) pkey := zfs.Prop(key)
if pkey == zfs.PoolPropName { if pkey == zfs.PoolPropName {
continue // Skip name its already printed above continue // Skip name its already printed above
} }
@ -353,14 +354,14 @@ func ExamplePoolCreate() {
} }
// pool properties // pool properties
props := make(map[zfs.PoolProp]string) props := make(map[zfs.Prop]string)
// root dataset filesystem properties // root dataset filesystem properties
fsprops := make(map[zfs.ZFSProp]string) fsprops := make(map[zfs.Prop]string)
// pool features // pool features
features := make(map[string]string) features := make(map[string]string)
// Turn off auto mounting by ZFS // Turn off auto mounting by ZFS
fsprops[zfs.ZFSPropMountpoint] = "none" fsprops[zfs.DatasetPropMountpoint] = "none"
// Enable some features // Enable some features
features["async_destroy"] = "enabled" features["async_destroy"] = "enabled"