From c88f3a05b897bca1672fe15b851c787054bb2256 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Fri, 7 Mar 2025 20:44:43 +0100 Subject: [PATCH 1/2] add go-libzfs because it is unmaintained source: https://github.com/lf-edge/eve/tree/master/pkg/pillar/vendor/github.com/andrewd-zededa/go-libzfs --- go-libzfs/.gitignore | 3 + go-libzfs/LICENSE.md | 27 + go-libzfs/README.md | 77 +++ go-libzfs/common.c | 77 +++ go-libzfs/common.go | 519 +++++++++++++++++ go-libzfs/common.h | 43 ++ go-libzfs/sendrecv.go | 370 ++++++++++++ go-libzfs/sort.go | 50 ++ go-libzfs/zfs.c | 266 +++++++++ go-libzfs/zfs.go | 875 ++++++++++++++++++++++++++++ go-libzfs/zfs.h | 146 +++++ go-libzfs/zpool.c | 563 ++++++++++++++++++ go-libzfs/zpool.go | 1197 +++++++++++++++++++++++++++++++++++++++ go-libzfs/zpool.h | 165 ++++++ go-libzfs/zpool_vdev.c | 37 ++ go-libzfs/zpool_vdev.go | 144 +++++ go.mod | 1 - main.go | 2 +- 18 files changed, 4560 insertions(+), 2 deletions(-) create mode 100644 go-libzfs/.gitignore create mode 100644 go-libzfs/LICENSE.md create mode 100644 go-libzfs/README.md create mode 100644 go-libzfs/common.c create mode 100644 go-libzfs/common.go create mode 100644 go-libzfs/common.h create mode 100644 go-libzfs/sendrecv.go create mode 100644 go-libzfs/sort.go create mode 100644 go-libzfs/zfs.c create mode 100644 go-libzfs/zfs.go create mode 100644 go-libzfs/zfs.h create mode 100644 go-libzfs/zpool.c create mode 100644 go-libzfs/zpool.go create mode 100644 go-libzfs/zpool.h create mode 100644 go-libzfs/zpool_vdev.c create mode 100644 go-libzfs/zpool_vdev.go diff --git a/go-libzfs/.gitignore b/go-libzfs/.gitignore new file mode 100644 index 0000000..1fcc717 --- /dev/null +++ b/go-libzfs/.gitignore @@ -0,0 +1,3 @@ +.gitconfig +*.sublime-* +go-libzfs.test diff --git a/go-libzfs/LICENSE.md b/go-libzfs/LICENSE.md new file mode 100644 index 0000000..c1aaecb --- /dev/null +++ b/go-libzfs/LICENSE.md @@ -0,0 +1,27 @@ +Copyright (c) 2015, Faruk Kasumovic +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of go-libzfs nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/go-libzfs/README.md b/go-libzfs/README.md new file mode 100644 index 0000000..d224f7e --- /dev/null +++ b/go-libzfs/README.md @@ -0,0 +1,77 @@ +# Introduction + +**go-libzfs** currently implements basic manipulation of ZFS pools and data sets. Plan is to add more in further development, improve documentation with more examples, and add more tests. _go-libzfs_ use libzfs C library and does not wrap OpenZFS CLI tools. Goal is to let easy using and manipulating OpenZFS form with in go, and tries to map libzfs C library in to go style package respecting golang common practice. + +## Note +This golang package is only used and tested on Linux. + +- Version tagged as v0.1 is latest used and compatible with ZFS On Linux version 0.6.5.x +- Version tagged as v0.2 is latest used and compatible with ZFS On Linux version 0.7.x + +[![GoDoc](https://godoc.org/github.com/bicomsystems/go-libzfs?status.svg)](https://godoc.org/github.com/bicomsystems/go-libzfs) + +## Main features + +- Creating, destroying, importing and exporting pools. +- Reading and modifying pool properties. +- Creating, destroying and renaming of filesystem datasets and volumes. +- Creating, destroying and rollback of snapshots. +- Cloning datasets and volumes. +- Reading and modifying dataset and volume properties. +- Send and receive snapshot streams + + +## Requirements: + +- OpenZFS on Linux and libzfs with development headers installed. +- Developed using go1.9 + +## Installing + +```sh +go get github.com/bicomsystems/go-libzfs +``` + +## Testing + +```sh +# On command line shell run +cd $GOPATH/src/github.com/bicomsystems/go-libzfs +go test +``` + +## Usage example + +```go +// Create map to represent ZFS dataset properties. This is equivalent to +// list of properties you can get from ZFS CLI tool, and some more +// internally used by libzfs. +props := make(map[ZFSProp]Property) + +// 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 +// specify size as base 10 number in string, or use strconv package or +// similar to convert in to string (base 10) from numeric type. +strSize := "1073741824" + +props[DatasetPropVolsize] = Property{Value: strSize} +// In addition I explicitly choose some more properties to be set. +props[DatasetPropVolblocksize] = Property{Value: "4096"} +props[DatasetPropReservation] = Property{Value: strSize} + +// Lets create desired volume +d, err := DatasetCreate("TESTPOOL/VOLUME1", DatasetTypeVolume, props) +if err != nil { + println(err.Error()) + return +} +// Dataset have to be closed for memory cleanup +defer d.Close() + +println("Created zfs volume TESTPOOL/VOLUME1") +``` + +## Special thanks to + +- [Bicom Systems](http://www.bicomsystems.com) for supporting this little project and that way making it possible. +- [OpenZFS](http://open-zfs.org) as the main ZFS software collective. \ No newline at end of file diff --git a/go-libzfs/common.c b/go-libzfs/common.c new file mode 100644 index 0000000..6409769 --- /dev/null +++ b/go-libzfs/common.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include + +#include "common.h" + +libzfs_handle_ptr libzfsHandle; + +int go_libzfs_init() { + libzfsHandle = libzfs_init(); + return 0; +} + +int libzfs_last_error() { + return libzfs_errno(libzfsHandle); +} + +const char *libzfs_last_error_str() { + return libzfs_error_description(libzfsHandle); +} + +int libzfs_clear_last_error() { + zfs_standard_error(libzfsHandle, EZFS_SUCCESS, "success"); + return 0; +} + +property_list_t *new_property_list() { + property_list_t *r = malloc(sizeof(property_list_t)); + memset(r, 0, sizeof(property_list_t)); + return r; +} + +void free_properties(property_list_t *root) { + property_list_t *tmp = 0; + while(root) { + tmp = root->pnext; + free(root); + root = tmp; + } +} + +nvlist_ptr new_property_nvlist() { + nvlist_ptr props = NULL; + int r = nvlist_alloc(&props, NV_UNIQUE_NAME, 0); + if ( r != 0 ) { + return NULL; + } + return props; +} + +int property_nvlist_add(nvlist_ptr list, const char *prop, const char *value) { + return nvlist_add_string(list, prop, value); +} + +int redirect_libzfs_stdout(int to) { + int save, res; + save = dup(STDOUT_FILENO); + if (save < 0) { + return save; + } + res = dup2(to, STDOUT_FILENO); + if (res < 0) { + return res; + } + return save; +} + +int restore_libzfs_stdout(int saved) { + int res; + fflush(stdout); + res = dup2(saved, STDOUT_FILENO); + if (res < 0) { + return res; + } + close(saved); +} diff --git a/go-libzfs/common.go b/go-libzfs/common.go new file mode 100644 index 0000000..f881007 --- /dev/null +++ b/go-libzfs/common.go @@ -0,0 +1,519 @@ +// Package zfs implements basic manipulation of ZFS pools and data sets. +// Use libzfs C library instead CLI zfs tools, with goal +// to let using and manipulating OpenZFS form with in go project. +// +// TODO: Adding to the pool. (Add the given vdevs to the pool) +// TODO: Scan for pools. +package zfs + +/* +#cgo CFLAGS: -I /usr/include/libzfs -I /usr/include/libspl -DHAVE_IOCTL_IN_SYS_IOCTL_H -D_GNU_SOURCE +#cgo LDFLAGS: -lzfs -lzpool -lnvpair -lzfs_core + +#include +#include +#include "common.h" +#include "zpool.h" +#include "zfs.h" +*/ +import "C" + +import ( + "errors" + "sync" +) + +// VDevType type of device in the pool +type VDevType string + +func init() { + C.go_libzfs_init() + return +} + +// Types of Virtual Devices +const ( + VDevTypeRoot VDevType = "root" // VDevTypeRoot root device in ZFS pool + VDevTypeMirror = "mirror" // VDevTypeMirror mirror device in ZFS pool + VDevTypeReplacing = "replacing" // VDevTypeReplacing replacing + VDevTypeRaidz = "raidz" // VDevTypeRaidz RAIDZ device + VDevTypeDisk = "disk" // VDevTypeDisk device is disk + VDevTypeFile = "file" // VDevTypeFile device is file + VDevTypeMissing = "missing" // VDevTypeMissing missing device + VDevTypeHole = "hole" // VDevTypeHole hole + VDevTypeSpare = "spare" // VDevTypeSpare spare device + VDevTypeLog = "log" // VDevTypeLog ZIL device + VDevTypeL2cache = "l2cache" // VDevTypeL2cache cache device (disk) +) + +// Prop type to enumerate all different properties suppoerted by ZFS +type Prop int + +// PoolStatus type representing status of the pool +type PoolStatus int + +// PoolState type representing pool state +type PoolState uint64 + +// VDevState - vdev states tye +type VDevState uint64 + +// VDevAux - vdev aux states +type VDevAux uint64 + +// Property ZFS pool or dataset property value +type Property struct { + Value string + Source string +} + +// Global - global objects +var Global struct { + Mtx sync.Mutex +} + +// Pool status +const ( + /* + * The following correspond to faults as defined in the (fault.fs.zfs.*) + * event namespace. Each is associated with a corresponding message ID. + */ + PoolStatusCorruptCache PoolStatus = iota /* corrupt /kernel/drv/zpool.cache */ + PoolStatusMissingDevR /* missing device with replicas */ + PoolStatusMissingDevNr /* missing device with no replicas */ + PoolStatusCorruptLabelR /* bad device label with replicas */ + PoolStatusCorruptLabelNr /* bad device label with no replicas */ + PoolStatusBadGUIDSum /* sum of device guids didn't match */ + PoolStatusCorruptPool /* pool metadata is corrupted */ + PoolStatusCorruptData /* data errors in user (meta)data */ + PoolStatusFailingDev /* device experiencing errors */ + PoolStatusVersionNewer /* newer on-disk version */ + PoolStatusHostidMismatch /* last accessed by another system */ + PoolStatusHosidActive /* currently active on another system */ + PoolStatusHostidRequired /* multihost=on and hostid=0 */ + PoolStatusIoFailureWait /* failed I/O, failmode 'wait' */ + PoolStatusIoFailureContinue /* failed I/O, failmode 'continue' */ + PoolStatusIOFailureMMP /* ailed MMP, failmode not 'panic' */ + PoolStatusBadLog /* cannot read log chain(s) */ + PoolStatusErrata /* informational errata available */ + + /* + * 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. + */ + PoolStatusUnsupFeatRead /* unsupported features for read */ + PoolStatusUnsupFeatWrite /* unsupported features for 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. + */ + PoolStatusFaultedDevR /* faulted device with replicas */ + PoolStatusFaultedDevNr /* faulted device with no replicas */ + + /* + * The following are not faults per se, but still an error possibly + * requiring administrative attention. There is no corresponding + * message ID. + */ + PoolStatusVersionOlder /* older legacy on-disk version */ + PoolStatusFeatDisabled /* supported features are disabled */ + PoolStatusResilvering /* device being resilvered */ + PoolStatusOfflineDev /* device offline */ + PoolStatusRemovedDev /* removed device */ + PoolStatusRebuilding /* device being rebuilt */ + PoolStatusRebuildScrub /* recommend scrubbing the pool */ + PoolStatusNonNativeAshift /* (e.g. 512e dev with ashift of 9) */ + PoolStatusCompatibilityErr /* bad 'compatibility' property */ + PoolStatusIncompatibleFeat /* feature set outside compatibility */ + + /* + * Finally, the following indicates a healthy pool. + */ + PoolStatusOk +) + +// Possible ZFS pool states +const ( + PoolStateActive PoolState = iota /* In active use */ + PoolStateExported /* Explicitly exported */ + PoolStateDestroyed /* Explicitly destroyed */ + PoolStateSpare /* Reserved for hot spare use */ + PoolStateL2cache /* Level 2 ARC device */ + PoolStateUninitialized /* Internal spa_t state */ + PoolStateUnavail /* Internal libzfs state */ + PoolStatePotentiallyActive /* Internal libzfs state */ +) + +// Pool properties. Enumerates available ZFS pool properties. Use it to access +// pool properties either to read or set soecific property. +const ( + PoolPropCont Prop = iota - 2 + PoolPropInval + PoolPropName + PoolPropSize + PoolPropCapacity + PoolPropAltroot + PoolPropHealth + PoolPropGUID + PoolPropVersion + PoolPropBootfs + PoolPropDelegation + PoolPropAutoreplace + PoolPropCachefile + PoolPropFailuremode + PoolPropListsnaps + PoolPropAutoexpand + PoolPropDedupditto + PoolPropDedupratio + PoolPropFree + PoolPropAllocated + PoolPropReadonly + PoolPropAshift + PoolPropComment + PoolPropExpandsz + PoolPropFreeing + PoolPropFragmentaion + PoolPropLeaked + PoolPropMaxBlockSize + PoolPropTName + PoolPropMaxDNodeSize + PoolPropMultiHost + PoolPropCheckpoint + PoolPropLoadGUID + PoolPropAutotrim + PoolPropCompatibility + PoolPropBcloneUsed + PoolPropBcloneSaved + PoolPropBcloneRatio + PoolNumProps +) + +/* + * Dataset properties are identified by these constants and must be added to + * the end of this list to ensure that external consumers are not affected + * by the change. If you make any changes to this list, be sure to update + * the property table in module/zcommon/zfs_prop.c. + */ +const ( + DatasetPropCont Prop = iota - 2 + DatasetPropBad + DatasetPropType + DatasetPropCreation + DatasetPropUsed + DatasetPropAvailable + DatasetPropReferenced + DatasetPropCompressratio + DatasetPropMounted + DatasetPropOrigin + DatasetPropQuota + DatasetPropReservation + DatasetPropVolsize + DatasetPropVolblocksize + DatasetPropRecordsize + DatasetPropMountpoint + DatasetPropSharenfs + DatasetPropChecksum + DatasetPropCompression + DatasetPropAtime + DatasetPropDevices + DatasetPropExec + DatasetPropSetuid + DatasetPropReadonly + DatasetPropZoned + DatasetPropSnapdir + DatasetPropPrivate /* not exposed to user, temporary */ + DatasetPropAclinherit + DatasetPropCreateTXG /* not exposed to the user */ + DatasetPropName /* not exposed to the user */ + DatasetPropCanmount + DatasetPropIscsioptions /* not exposed to the user */ + DatasetPropXattr + DatasetPropNumclones /* not exposed to the user */ + DatasetPropCopies + DatasetPropVersion + DatasetPropUtf8only + DatasetPropNormalize + DatasetPropCase + DatasetPropVscan + DatasetPropNbmand + DatasetPropSharesmb + DatasetPropRefquota + DatasetPropRefreservation + DatasetPropGUID + DatasetPropPrimarycache + DatasetPropSecondarycache + DatasetPropUsedsnap + DatasetPropUsedds + DatasetPropUsedchild + DatasetPropUsedrefreserv + DatasetPropUseraccounting /* not exposed to the user */ + DatasetPropStmfShareinfo /* not exposed to the user */ + DatasetPropDeferDestroy + DatasetPropUserrefs + DatasetPropLogbias + DatasetPropUnique /* not exposed to the user */ + DatasetPropObjsetid /* not exposed to the user */ + DatasetPropDedup + DatasetPropMlslabel + DatasetPropSync + DatasetPropDnodeSize + DatasetPropRefratio + DatasetPropWritten + DatasetPropClones + DatasetPropLogicalused + DatasetPropLogicalreferenced + DatasetPropInconsistent /* not exposed to the user */ + DatasetPropVolmode + DatasetPropFilesystemLimit + DatasetPropSnapshotLimit + DatasetPropFilesystemCount + DatasetPropSnapshotCount + DatasetPropSnapdev + DatasetPropAcltype + DatasetPropSelinuxContext + DatasetPropSelinuxFsContext + DatasetPropSelinuxDefContext + DatasetPropSelinuxRootContext + DatasetPropRelatime + DatasetPropRedundantMetadata + DatasetPropOverlay + DatasetPropPrevSnap + DatasetPropReceiveResumeToken + DatasetPropEncryption + DatasetPropKeyLocation + DatasetPropKeyFormat + DatasetPropPBKDF2Salt + DatasetPropPBKDF2Iters + DatasetPropEncryptionRoot + DatasetPropKeyGUID + DatasetPropKeyStatus + DatasetPropRemapTXG /* not exposed to the user */ + DatasetPropSpecialSmallBlocks + DatasetPropIVSetGuid /* not exposed to the user */ + DatasetPropRedacted + DatasetPropRedactSnaps + DatasetPropSnapshotsChanged + DatasetNumProps +) + +// LastError get last underlying libzfs error description if any +func LastError() (err error) { + return errors.New(C.GoString(C.libzfs_last_error_str())) +} + +// ClearLastError force clear of any last error set by undeliying libzfs +func ClearLastError() (err error) { + err = LastError() + C.libzfs_clear_last_error() + return +} + +func booleanT(b bool) (r C.boolean_t) { + if b { + return 1 + } + return 0 +} + +// ZFS errors +const ( + ESuccess = 0 /* no error -- success */ + ENomem = 2000 + iota - 1 /* 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 */ + EScrubpaused /* scrub currently paused */ + EActivepool /* pool is imported on a different system */ + ECryptofailed /* failed to setup encryption */ + ENopending /* cannot cancel, no operation is pending */ + ECheckpointExists /* checkpoint exists */ + EDiscardingCheckpoint /* currently discarding a checkpoint */ + ENoCheckpoint /* pool has no checkpoint */ + EDevrmInProgress /* a device is currently being removed */ + EVdevTooBig /* a device is too big to be used */ + EIocNotsupported /* operation not supported by zfs module */ + EToomany /* argument list too long */ + EInitializing /* currently initializing */ + ENoInitialize /* no active initialize */ + EWrongParent /* invalid parent dataset (e.g ZVOL) */ + ETrimming /* currently trimming */ + ENoTrim /* no active trim */ + ETrimNotsup /* device does not support trim */ + ENoResilverDefer /* pool doesn't support resilver_defer */ + EExportInProgress /* currently exporting the pool */ + ERebuilding /* resilvering (sequential reconstrution) */ + EVdevNotSup /* ops not supported for this type of vdev */ + ENotUserNamespace /* a file is not a user namespace */ + ECksum /* insufficient replicas */ + EResumeExists /* resume on existing dataset without force */ + EShareFailed /* filesystem share failed */ + EUnknown +) + +// vdev states are ordered from least to most healthy. +// A vdev that's VDevStateCantOpen or below is considered unusable. +const ( + VDevStateUnknown VDevState = iota // Uninitialized vdev + VDevStateClosed // Not currently open + VDevStateOffline // Not allowed to open + VDevStateRemoved // Explicitly removed from system + VDevStateCantOpen // Tried to open, but failed + VDevStateFaulted // External request to fault device + VDevStateDegraded // Replicated vdev with unhealthy kids + VDevStateHealthy // Presumed good +) + +// vdev aux states. When a vdev is in the VDevStateCantOpen state, the aux field +// of the vdev stats structure uses these constants to distinguish why. +const ( + VDevAuxNone VDevAux = iota // no error + VDevAuxOpenFailed // ldi_open_*() or vn_open() failed + VDevAuxCorruptData // bad label or disk contents + VDevAuxNoReplicas // insufficient number of replicas + VDevAuxBadGUIDSum // vdev guid sum doesn't match + VDevAuxTooSmall // vdev size is too small + VDevAuxBadLabel // the label is OK but invalid + VDevAuxVersionNewer // on-disk version is too new + VDevAuxVersionOlder // on-disk version is too old + VDevAuxUnsupFeat // unsupported features + VDevAuxSpared // hot spare used in another pool + VDevAuxErrExceeded // too many errors + VDevAuxIOFailure // experienced I/O failure + VDevAuxBadLog // cannot read log chain(s) + VDevAuxExternal // external diagnosis + VDevAuxSplitPool // vdev was split off into another pool + VdevAuxBadAshift // vdev ashift is invalid + VdevAuxExternalPersist // persistent forced fault + VdevAuxActive // vdev active on a different host + VdevAuxChildrenOffline // all children are offline + VdevAuxAshiftTooBig // vdev's min block size is too large +) + +var PoolStatusStrings = map[PoolStatus]string{ + PoolStatusCorruptCache: "CORRUPT_CACHE", + PoolStatusMissingDevR: "MISSING_DEV_R", /* missing device with replicas */ + PoolStatusMissingDevNr: "MISSING_DEV_NR", + PoolStatusCorruptLabelR: "CORRUPT_LABEL_R", + PoolStatusCorruptLabelNr: "CORRUPT_LABEL_NR", + PoolStatusBadGUIDSum: "BAD_GUID_SUM", + PoolStatusCorruptPool: "CORRUPT_POOL", + PoolStatusCorruptData: "CORRUPT_DATA", + PoolStatusFailingDev: "FAILLING_DEV", + PoolStatusVersionNewer: "VERSION_NEWER", + PoolStatusHostidMismatch: "HOSTID_MISMATCH", + PoolStatusHosidActive: "HOSTID_ACTIVE", + PoolStatusHostidRequired: "HOSTID_REQUIRED", + PoolStatusIoFailureWait: "FAILURE_WAIT", + PoolStatusIoFailureContinue: "FAILURE_CONTINUE", + PoolStatusIOFailureMMP: "HOSTID_FAILURE_MMP", + PoolStatusBadLog: "BAD_LOG", + PoolStatusErrata: "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. + */ + PoolStatusUnsupFeatRead: "UNSUP_FEAT_READ", + PoolStatusUnsupFeatWrite: "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. + */ + PoolStatusFaultedDevR: "FAULTED_DEV_R", + PoolStatusFaultedDevNr: "FAULTED_DEV_NR", + + /* + * The following are not faults per se, but still an error possibly + * requiring administrative attention. There is no corresponding + * message ID. + */ + PoolStatusVersionOlder: "VERSION_OLDER", + PoolStatusFeatDisabled: "FEAT_DISABLED", + PoolStatusResilvering: "RESILVERIN", + PoolStatusOfflineDev: "OFFLINE_DEV", + PoolStatusRemovedDev: "REMOVED_DEV", + PoolStatusRebuilding: "REBUILDING", + PoolStatusRebuildScrub: "REBUILD_SCRUB", + PoolStatusNonNativeAshift: "NON_NATIVE_ASHIFT", + PoolStatusCompatibilityErr: "COMPATIBILITY_ERR", + PoolStatusIncompatibleFeat: "INCOMPATIBLE_FEAT", + + /* + * Finally, the following indicates a healthy pool. + */ + PoolStatusOk: "OK", +} diff --git a/go-libzfs/common.h b/go-libzfs/common.h new file mode 100644 index 0000000..f4d33dd --- /dev/null +++ b/go-libzfs/common.h @@ -0,0 +1,43 @@ +/* C wrappers around some zfs calls and C in general that should simplify + * using libzfs from go language, make go code shorter and more readable. + */ + +#ifndef loff_t + #define loff_t off_t +#endif +#define INT_MAX_NAME 256 +#define INT_MAX_VALUE 1024 +#define ZAP_OLDMAXVALUELEN 1024 +#define ZFS_MAX_DATASET_NAME_LEN 256 + +typedef struct property_list { + char value[INT_MAX_VALUE]; + char source[ZFS_MAX_DATASET_NAME_LEN]; + int property; + void *pnext; +} property_list_t; + +typedef struct libzfs_handle* libzfs_handle_ptr; +typedef struct nvlist* nvlist_ptr; +typedef struct property_list *property_list_ptr; +typedef struct nvpair* nvpair_ptr; +typedef struct vdev_stat* vdev_stat_ptr; +typedef char* char_ptr; + +extern libzfs_handle_ptr libzfsHandle; + +int go_libzfs_init(); + +int libzfs_last_error(); +const char *libzfs_last_error_str(); +int libzfs_clear_last_error(); + +property_list_t *new_property_list(); +void free_properties(property_list_t *root); + +nvlist_ptr new_property_nvlist(); +int property_nvlist_add(nvlist_ptr ptr, const char* prop, const char *value); + +int redirect_libzfs_stdout(int to); +int restore_libzfs_stdout(int saved); + diff --git a/go-libzfs/sendrecv.go b/go-libzfs/sendrecv.go new file mode 100644 index 0000000..fa79d80 --- /dev/null +++ b/go-libzfs/sendrecv.go @@ -0,0 +1,370 @@ +package zfs + +// #include +// #include +// #include "common.h" +// #include "zpool.h" +// #include "zfs.h" +// #include +// #include +import "C" +import ( + "fmt" + "io/ioutil" + "os" + "regexp" + "strconv" + "strings" + "time" + "unsafe" +) + +// SendFlags send flags +type SendFlags struct { + Verbosity int // -v + Replicate bool // -R + DoAll bool // -I + FromOrigin bool // -o + Props bool // -p + DryRun bool // -n + Parsable bool // -P + Progress bool // show progress (ie. -v) + LargeBlock bool // -L + EmbedData bool // -e + Compress bool // -c + Raw bool // raw encrypted records are permitted + Backup bool // only send received properties (ie. -b) + Holds bool // include snapshot holds in send stream +} + +// RecvFlags receive flags +type RecvFlags struct { + Verbose bool // -v + IsPrefix bool // -d + IsTail bool // -e + DryRun bool // -n + Force bool // -r + Resumable bool // -s + NoMount bool // -u + CanmountOff bool + ByteSwap bool +} + +// ResumeToken - informations extracted from resume token +type ResumeToken struct { + ToName string + FromName string + Object uint64 + Offset uint64 + ToGUID uint64 + FromGUID uint64 + Bytes uint64 + LargeBlock bool + EmbedOk bool + CompressOk bool + RawOk bool +} + +func to_boolean_t(a bool) C.boolean_t { + if a { + return 1 + } + return 0 +} + +func to_sendflags_t(flags *SendFlags) (cflags *C.sendflags_t) { + cflags = C.alloc_sendflags() + cflags.verbosity = C.int(flags.Verbosity) + cflags.replicate = to_boolean_t(flags.Replicate) + cflags.doall = to_boolean_t(flags.DoAll) + cflags.fromorigin = to_boolean_t(flags.FromOrigin) + cflags.props = to_boolean_t(flags.Props) + cflags.dryrun = to_boolean_t(flags.DryRun) + cflags.parsable = to_boolean_t(flags.Parsable) + cflags.progress = to_boolean_t(flags.Progress) + cflags.largeblock = to_boolean_t(flags.LargeBlock) + cflags.embed_data = to_boolean_t(flags.EmbedData) + cflags.compress = to_boolean_t(flags.Compress) + cflags.raw = to_boolean_t(flags.Raw) + cflags.backup = to_boolean_t(flags.Backup) + cflags.holds = to_boolean_t(flags.Holds) + return +} + +func to_recvflags_t(flags *RecvFlags) (cflags *C.recvflags_t) { + cflags = C.alloc_recvflags() + cflags.verbose = to_boolean_t(flags.Verbose) + cflags.isprefix = to_boolean_t(flags.IsPrefix) + cflags.istail = to_boolean_t(flags.IsTail) + cflags.dryrun = to_boolean_t(flags.DryRun) + cflags.force = to_boolean_t(flags.Force) + cflags.canmountoff = to_boolean_t(flags.CanmountOff) + cflags.resumable = to_boolean_t(flags.Resumable) + cflags.byteswap = to_boolean_t(flags.ByteSwap) + cflags.nomount = to_boolean_t(flags.NoMount) + return +} + +func (d *Dataset) send(FromName string, outf *os.File, flags *SendFlags) (err error) { + var cfromname, ctoname *C.char + var dpath string + var pd Dataset + + if d.Type != DatasetTypeSnapshot || (len(FromName) > 0 && strings.Contains(FromName, "#")) { + err = fmt.Errorf( + "Unsupported method on filesystem or bookmark. Use func SendOne() for that purpose.") + return + } + + cflags := to_sendflags_t(flags) + defer C.free(unsafe.Pointer(cflags)) + if dpath, err = d.Path(); err != nil { + return + } + sendparams := strings.Split(dpath, "@") + parent := sendparams[0] + if len(FromName) > 0 { + if FromName[0] == '@' { + FromName = FromName[1:] + } else if strings.Contains(FromName, "/") { + from := strings.Split(FromName, "@") + if len(from) > 0 { + FromName = from[1] + } + } + cfromname = C.CString(FromName) + defer C.free(unsafe.Pointer(cfromname)) + } + ctoname = C.CString(sendparams[1]) + defer C.free(unsafe.Pointer(ctoname)) + if pd, err = DatasetOpen(parent); err != nil { + return + } + defer pd.Close() + cerr := C.zfs_send(pd.list.zh, cfromname, ctoname, cflags, C.int(outf.Fd()), nil, nil, nil) + if cerr != 0 { + err = LastError() + } + return +} + +func (d *Dataset) SendResume(outf *os.File, flags *SendFlags, receiveResumeToken string) (err error) { + if d.Type != DatasetTypeSnapshot { + err = fmt.Errorf("Unsupported method on filesystem or bookmark. Use func SendOne() for that purpose.") + return + } + + var dpath string + var pd Dataset + + cflags := to_sendflags_t(flags) + defer C.free(unsafe.Pointer(cflags)) + if dpath, err = d.Path(); err != nil { + return + } + sendparams := strings.Split(dpath, "@") + parent := sendparams[0] + + if pd, err = DatasetOpen(parent); err != nil { + return + } + defer pd.Close() + + cReceiveResumeToken := C.CString(receiveResumeToken) + defer C.free(unsafe.Pointer(cReceiveResumeToken)) + + clerr := C.zfs_send_resume(C.libzfsHandle, cflags, C.int(outf.Fd()), cReceiveResumeToken) + if clerr != 0 { + err = LastError() + } + + return +} + +func (d *Dataset) Send(outf *os.File, flags SendFlags) (err error) { + if flags.Replicate { + flags.DoAll = true + } + err = d.send("", outf, &flags) + return +} + +func (d *Dataset) SendFrom(FromName string, outf *os.File, flags SendFlags) (err error) { + var porigin Property + var from, dest []string + if err = d.ReloadProperties(); err != nil { + return + } + porigin, _ = d.GetProperty(DatasetPropOrigin) + if len(porigin.Value) > 0 && porigin.Value == FromName { + FromName = "" + flags.FromOrigin = true + } else { + var dpath string + if dpath, err = d.Path(); err != nil { + return + } + dest = strings.Split(dpath, "@") + from = strings.Split(FromName, "@") + + if len(from[0]) > 0 && from[0] != dest[0] { + err = fmt.Errorf("Incremental source must be in same filesystem.") + return + } + if len(from) < 2 || strings.Contains(from[1], "@") || strings.Contains(from[1], "/") { + err = fmt.Errorf("Invalid incremental source.") + return + } + } + err = d.send("@"+from[1], outf, &flags) + return +} + +// SendSize - estimate snapshot size to transfer +func (d *Dataset) SendSize(FromName string, flags SendFlags) (size int64, err error) { + var r, w *os.File + errch := make(chan error) + defer func() { + select { + case <-errch: + default: + } + close(errch) + }() + flags.DryRun = true + flags.Verbosity = 1 + flags.Progress = true + flags.Parsable = true + if r, w, err = os.Pipe(); err != nil { + return + } + defer r.Close() + go func() { + var tmpe error + saveOut := C.redirect_libzfs_stdout(C.int(w.Fd())) + if saveOut < 0 { + tmpe = fmt.Errorf("Redirection of zfslib stdout failed %d", saveOut) + } else { + tmpe = d.send(FromName, w, &flags) + C.restore_libzfs_stdout(saveOut) + } + w.Close() + errch <- tmpe + }() + + r.SetReadDeadline(time.Now().Add(60 * time.Second)) + var data []byte + if data, err = ioutil.ReadAll(r); err != nil { + return + } + // parse size + var sizeRe *regexp.Regexp + if sizeRe, err = regexp.Compile("size[ \t]*([0-9]+)"); err != nil { + return + } + matches := sizeRe.FindAllSubmatch(data, 3) + if len(matches) > 0 && len(matches[0]) > 1 { + if size, err = strconv.ParseInt( + string(matches[0][1]), 10, 64); err != nil { + return + } + } + err = <-errch + return +} + +// Receive - receive snapshot stream +func (d *Dataset) Receive(inf *os.File, flags RecvFlags) (err error) { + var dpath string + if dpath, err = d.Path(); err != nil { + return + } + props := C.new_property_nvlist() + if props == nil { + err = fmt.Errorf("Out of memory func (d *Dataset) Recv()") + return + } + defer C.nvlist_free(props) + cflags := to_recvflags_t(&flags) + defer C.free(unsafe.Pointer(cflags)) + dest := C.CString(dpath) + defer C.free(unsafe.Pointer(dest)) + ec := C.zfs_receive(C.libzfsHandle, dest, nil, cflags, C.int(inf.Fd()), nil) + if ec != 0 { + err = fmt.Errorf("ZFS receive of %s failed. %s", C.GoString(dest), LastError().Error()) + } + return +} + +// Unpack unpack resume token +func (rt *ResumeToken) Unpack(token string) (err error) { + ctoken := C.CString(token) + defer C.free(unsafe.Pointer(ctoken)) + resume_nvl := C.zfs_send_resume_token_to_nvlist(C.libzfsHandle, ctoken) + defer C.nvlist_free(resume_nvl) + if resume_nvl == nil { + err = fmt.Errorf("Failed to unpack resume token: %s", LastError().Error()) + return + } + if rt.ToName, err = rt.lookupString(resume_nvl, "toname"); err != nil { + return + } + rt.FromName, _ = rt.lookupString(resume_nvl, "fromname") + + if rt.Object, err = rt.lookupUnit64(resume_nvl, "object"); err != nil { + return + } + if rt.Offset, err = rt.lookupUnit64(resume_nvl, "offset"); err != nil { + return + } + if rt.Bytes, err = rt.lookupUnit64(resume_nvl, "bytes"); err != nil { + return + } + if rt.ToGUID, err = rt.lookupUnit64(resume_nvl, "toguid"); err != nil { + return + } + + rt.FromGUID, _ = rt.lookupUnit64(resume_nvl, "fromguid") + + rt.LargeBlock = rt.exist(resume_nvl, "largeblockok") + rt.EmbedOk = rt.exist(resume_nvl, "embedok") + rt.CompressOk = rt.exist(resume_nvl, "compressok") + rt.RawOk = rt.exist(resume_nvl, "rawok") + + return +} + +func (rt *ResumeToken) lookupString(nvl *C.nvlist_t, key string) (val string, err error) { + var cstr *C.char + ckey := C.CString(key) + defer C.free(unsafe.Pointer(ckey)) + defer C.free(unsafe.Pointer(cstr)) + rc := C.nvlist_lookup_string(nvl, ckey, &cstr) + if rc != 0 { + err = fmt.Errorf("resume token is corrupt") + return + } + val = C.GoString(cstr) + return +} + +func (rt *ResumeToken) lookupUnit64(nvl *C.nvlist_t, key string) (val uint64, err error) { + var num C.uint64_t + ckey := C.CString(key) + defer C.free(unsafe.Pointer(ckey)) + rc := C.nvlist_lookup_uint64(nvl, ckey, &num) + if rc != 0 { + err = fmt.Errorf("resume token is corrupt") + return + } + val = uint64(num) + return +} + +func (rt *ResumeToken) exist(nvl *C.nvlist_t, key string) (val bool) { + ckey := C.CString(key) + defer C.free(unsafe.Pointer(ckey)) + rc := C.nvlist_exists(nvl, ckey) + val = (rc != 0) + return +} diff --git a/go-libzfs/sort.go b/go-libzfs/sort.go new file mode 100644 index 0000000..b5c70f7 --- /dev/null +++ b/go-libzfs/sort.go @@ -0,0 +1,50 @@ +package zfs + +import ( + "strconv" +) + +type clonesCreateDesc []Dataset + +func (list clonesCreateDesc) Less(i, j int) bool { + _, oki := list[i].Properties[DatasetNumProps+1000] + _, okj := list[i].Properties[DatasetNumProps+1000] + if oki && okj { + unixti, err := strconv.ParseInt( + list[i].Properties[DatasetNumProps+1000].Value, 10, 64) + if err != nil { + panic(err) + } + unixtj, err := strconv.ParseInt( + list[j].Properties[DatasetNumProps+1000].Value, 10, 64) + if err != nil { + panic(err) + } + if unixti != unixtj { + return unixti > unixtj + } + } + + // if we have two datasets created from same snapshot + // any of them will do, but we will go for most recent + unixti, err := strconv.ParseInt( + list[i].Properties[DatasetPropCreateTXG].Value, 10, 64) + if err != nil { + panic(err) + } + unixtj, err := strconv.ParseInt( + list[j].Properties[DatasetPropCreateTXG].Value, 10, 64) + if err != nil { + panic(err) + } + + return unixti > unixtj +} + +func (list clonesCreateDesc) Swap(i, j int) { + list[i], list[j] = list[j], list[i] +} + +func (list clonesCreateDesc) Len() int { + return len(list) +} diff --git a/go-libzfs/zfs.c b/go-libzfs/zfs.c new file mode 100644 index 0000000..c829780 --- /dev/null +++ b/go-libzfs/zfs.c @@ -0,0 +1,266 @@ +/* C wrappers around some zfs calls and C in general that should simplify + * using libzfs from go language, make go code shorter and more readable. + */ + +#include +#include +#include +#include + +#include "common.h" +#include "zpool.h" +#include "zfs.h" + + +dataset_list_t *create_dataset_list_item() { + dataset_list_t *zlist = malloc(sizeof(dataset_list_t)); + memset(zlist, 0, sizeof(dataset_list_t)); + return zlist; +} + +void dataset_list_close(dataset_list_t *list) { + if (list != NULL) { + if (list->zh != NULL) { + zfs_close(list->zh); + list->zh = NULL; + } + free(list); + } + // dataset_list_free(list); +} + +void dataset_list_free(dataset_list_t *list) { + dataset_list_t *next; + while(list) { + next = list->pnext; + free(list); + list = next; + } +} + +int dataset_list_callb(zfs_handle_t *dataset, void *data) { + dataset_list_t **lroot = (dataset_list_t**)data; + + if ( !((*lroot)->zh) ) { + (*lroot)->zh = dataset; + } else { + dataset_list_t *nroot = create_dataset_list_item(); + nroot->zh = dataset; + nroot->pnext = (void*)*lroot; + *lroot = nroot; + } + return 0; +} + +dataset_list_ptr dataset_list_root() { + int err = 0; + dataset_list_t *zlist = create_dataset_list_item(); + err = zfs_iter_root(libzfsHandle, dataset_list_callb, &zlist); + if ( err != 0 || zlist->zh == NULL) { + dataset_list_free(zlist); + return NULL; + } + return zlist; +} + +dataset_list_ptr dataset_next(dataset_list_t *dataset) { + return dataset->pnext; +} + +int dataset_type(dataset_list_ptr dataset) { + return zfs_get_type(dataset->zh); +} + +dataset_list_ptr dataset_open(const char *path) { + dataset_list_ptr list = create_dataset_list_item(); + list->zh = zfs_open(libzfsHandle, path, 0xF); + if (list->zh == NULL) { + dataset_list_free(list); + list = NULL; + } + return list; +} + +int dataset_create(const char *path, zfs_type_t type, nvlist_ptr props) { + return zfs_create(libzfsHandle, path, type, props); +} + +int dataset_destroy(dataset_list_ptr dataset, boolean_t defer) { + return zfs_destroy(dataset->zh, defer); +} + +dataset_list_t *dataset_list_children(dataset_list_t *dataset) { + int err = 0; + dataset_list_t *zlist = create_dataset_list_item(); + err = zfs_iter_children(dataset->zh, dataset_list_callb, &zlist); + if ( err != 0 || zlist->zh == NULL) { + dataset_list_free(zlist); + return NULL; + } + return zlist; +} + +zpool_list_ptr dataset_get_pool(dataset_list_ptr dataset) { + zpool_list_ptr pool = create_zpool_list_item(); + if(pool != NULL) { + pool->zph = zfs_get_pool_handle(dataset->zh); + } + return pool; +} + +int dataset_prop_set(dataset_list_ptr dataset, zfs_prop_t prop, const char *value) { + return zfs_prop_set(dataset->zh, zfs_prop_to_name(prop), value); +} + +int dataset_user_prop_set(dataset_list_ptr dataset, const char *prop, const char *value) { + return zfs_prop_set(dataset->zh, prop, value); +} + +int dataset_clone(dataset_list_ptr dataset, const char *target, nvlist_ptr props) { + return zfs_clone(dataset->zh, target, props); +} + +int dataset_snapshot(const char *path, boolean_t recur, nvlist_ptr props) { + return zfs_snapshot(libzfsHandle, path, recur, props); +} + +int dataset_rollback(dataset_list_ptr dataset, dataset_list_ptr snapshot, boolean_t force) { + return zfs_rollback(dataset->zh, snapshot->zh, force); +} + +int dataset_promote(dataset_list_ptr dataset) { + return zfs_promote(dataset->zh); +} + +int dataset_rename(dataset_list_ptr dataset, const char* new_name, boolean_t recur, boolean_t nounmount, boolean_t force_unm) { + renameflags_t flags = {recur,nounmount,force_unm}; + return zfs_rename(dataset->zh, new_name, flags); +} + +const char *dataset_is_mounted(dataset_list_ptr dataset){ + char *mp = NULL; + // zfs_is_mounted returns B_TRUE or B_FALSE + if (0 != zfs_is_mounted(dataset->zh, &mp)) { + return mp; + } + return NULL; +} + +int dataset_mount(dataset_list_ptr dataset, const char *options, int flags) { + if ( 0 < strlen(options)) { + return zfs_mount(dataset->zh, options, flags); + } else { + return zfs_mount(dataset->zh, NULL, flags); + } +} + +int dataset_unmount(dataset_list_ptr dataset, int flags) { + return zfs_unmount(dataset->zh, NULL, flags); +} + +int dataset_unmountall(dataset_list_ptr dataset, int flags) { + return zfs_unmountall(dataset->zh, flags); +} + +const char *dataset_get_name(dataset_list_ptr ds) { + return zfs_get_name(ds->zh); +} + +//int read_dataset_property(zfs_handle_t *zh, property_list_t *list, int prop) { +property_list_t *read_dataset_property(dataset_list_t *dataset, int prop) { + int r = 0; + zprop_source_t source; + char statbuf[INT_MAX_VALUE]; + property_list_ptr list = NULL; + list = new_property_list(); + + r = zfs_prop_get(dataset->zh, prop, + list->value, INT_MAX_VALUE, &source, statbuf, INT_MAX_VALUE, 1); + if (r == 0 && list != NULL) { + // strcpy(list->name, zpool_prop_to_name(prop)); + zprop_source_tostr(list->source, source); + list->property = (int)prop; + } else if (list != NULL) { + free_properties(list); + list = NULL; + } + return list; +} + +// int read_user_property(zfs_handle_t *zh, property_list_t *list, const char *prop) { +property_list_t *read_user_property(dataset_list_t *dataset, const char* prop) { + nvlist_t *user_props = zfs_get_user_props(dataset->zh); + nvlist_t *propval; + zprop_source_t sourcetype; + const char *strval; + const char *sourceval; + // char source[ZFS_MAX_DATASET_NAME_LEN]; + property_list_ptr list = new_property_list(); + + if (nvlist_lookup_nvlist(user_props, + prop, &propval) != 0) { + sourcetype = ZPROP_SRC_NONE; + (void) strncpy(list->source, + "none", sizeof (list->source)); + strval = "-"; + } else { + verify(nvlist_lookup_string(propval, + ZPROP_VALUE, &strval) == 0); + verify(nvlist_lookup_string(propval, + ZPROP_SOURCE, &sourceval) == 0); + + if (strcmp(sourceval, + zfs_get_name(dataset->zh)) == 0) { + sourcetype = ZPROP_SRC_LOCAL; + (void) strncpy(list->source, + "local", sizeof (list->source)); + } else if (strcmp(sourceval, + ZPROP_SOURCE_VAL_RECVD) == 0) { + sourcetype = ZPROP_SRC_RECEIVED; + (void) strncpy(list->source, + "received", sizeof (list->source)); + } else { + sourcetype = ZPROP_SRC_INHERITED; + (void) strncpy(list->source, + sourceval, sizeof (list->source)); + } + } + (void) strncpy(list->value, + strval, sizeof (list->value)); + return list; +} + +char** alloc_cstrings(int size) { + return malloc(size*sizeof(char*)); +} + +void strings_setat(char **a, int at, char *v) { + a[at] = v; +} + + +sendflags_t *alloc_sendflags() { + sendflags_t *r = malloc(sizeof(sendflags_t)); + memset(r, 0, sizeof(sendflags_t)); + return r; +} +recvflags_t *alloc_recvflags() { + recvflags_t *r = malloc(sizeof(recvflags_t)); + memset(r, 0, sizeof(recvflags_t)); + return r; +} + +struct zfs_cmd *new_zfs_cmd(){ + struct zfs_cmd *cmd = malloc(sizeof(struct zfs_cmd)); + memset(cmd, 0, sizeof(struct zfs_cmd)); + return cmd; +} + +int estimate_send_size(struct zfs_cmd *zc) { + int rc = zfs_ioctl(libzfsHandle, ZFS_IOC_SEND, zc); + if (rc != 0) { + rc = errno; + } + return rc; +} + diff --git a/go-libzfs/zfs.go b/go-libzfs/zfs.go new file mode 100644 index 0000000..748e460 --- /dev/null +++ b/go-libzfs/zfs.go @@ -0,0 +1,875 @@ +package zfs + +// #include +// #include +// #include "common.h" +// #include "zpool.h" +// #include "zfs.h" +import "C" + +import ( + "errors" + "fmt" + "path" + "sort" + "strings" + "sync" + "time" + "unsafe" +) + +const ( + msgDatasetIsNil = "Dataset handle not initialized or its closed" +) + +// DatasetProperties type is map of dataset or volume properties prop -> value +type DatasetProperties map[Prop]string + +// DatasetType defines enum of dataset types +type DatasetType int32 + +const ( + // DatasetTypeFilesystem - file system dataset + DatasetTypeFilesystem DatasetType = (1 << 0) + // DatasetTypeSnapshot - snapshot of dataset + DatasetTypeSnapshot = (1 << 1) + // DatasetTypeVolume - volume (virtual block device) dataset + DatasetTypeVolume = (1 << 2) + // DatasetTypePool - pool dataset + DatasetTypePool = (1 << 3) + // DatasetTypeBookmark - bookmark dataset + DatasetTypeBookmark = (1 << 4) +) + +// HoldTag - user holds tags +type HoldTag struct { + Name string + Timestamp time.Time +} + +// Dataset - ZFS dataset object +type Dataset struct { + list C.dataset_list_ptr + closeOnce *sync.Once + Type DatasetType + Properties map[Prop]Property + Children []Dataset +} + +// RenameFlags structure contains information for ZFS 2.0.x Rename Dataset feature +type RenameFlags struct { + // Recursive rename + Recursive bool + // Do not unmount file systems + Nounmount bool + // Force unmount file systems + Forceunmount bool +} + +func (d *Dataset) openChildren() (err error) { + d.Children = make([]Dataset, 0, 5) + list := C.dataset_list_children(d.list) + for list != nil { + dataset := Dataset{list: list, closeOnce: new(sync.Once)} + dataset.Type = DatasetType(C.dataset_type(list)) + dataset.Properties = make(map[Prop]Property) + err = dataset.ReloadProperties() + if err != nil { + return + } + d.Children = append(d.Children, dataset) + list = C.dataset_next(list) + } + for ci := range d.Children { + if err = d.Children[ci].openChildren(); err != nil { + return + } + } + return +} + +// DatasetOpenAll recursive get handles to all available datasets on system +// (file-systems, volumes or snapshots). +func DatasetOpenAll() (datasets []Dataset, err error) { + list := C.dataset_list_root() + for list != nil { + dataset := Dataset{ + list: list, + closeOnce: new(sync.Once), + Type: DatasetType(C.dataset_type(list)), + } + dataset.Type = DatasetType(C.dataset_type(list)) + err = dataset.ReloadProperties() + if err != nil { + return + } + datasets = append(datasets, dataset) + list = C.dataset_next(list) + } + for ci := range datasets { + if err = datasets[ci].openChildren(); err != nil { + return + } + } + return +} + +// DatasetCloseAll close all datasets in slice and all of its recursive +// children datasets +func DatasetCloseAll(datasets []Dataset) { + for _, d := range datasets { + d.Close() + } +} + +// DatasetOpen open dataset and all of its recursive children datasets +func DatasetOpen(path string) (d Dataset, err error) { + if d, err = DatasetOpenSingle(path); err != nil { + return + } + err = d.openChildren() + return +} + +// DatasetOpenSingle open dataset without opening all of its recursive +// children datasets +func DatasetOpenSingle(path string) (d Dataset, err error) { + csPath := C.CString(path) + d.list = C.dataset_open(csPath) + C.free(unsafe.Pointer(csPath)) + + if d.list == nil || d.list.zh == nil { + err = LastError() + if err == nil { + err = fmt.Errorf("dataset not found") + } + err = fmt.Errorf("%s - %s", err.Error(), path) + return + } + d.closeOnce = new(sync.Once) + d.Type = DatasetType(C.dataset_type(d.list)) + err = d.ReloadProperties() + if err != nil { + return + } + return +} + +func datasetPropertiesTonvlist(props map[Prop]Property) ( + cprops C.nvlist_ptr, err error) { + // convert properties to nvlist C type + cprops = C.new_property_nvlist() + if cprops == nil { + err = errors.New("Failed to allocate properties") + return + } + for prop, value := range props { + csValue := C.CString(value.Value) + r := C.property_nvlist_add( + cprops, C.zfs_prop_to_name(C.zfs_prop_t(prop)), csValue) + C.free(unsafe.Pointer(csValue)) + if r != 0 { + err = errors.New("Failed to convert property") + return + } + } + return +} + +// DatasetCreate create a new filesystem or volume on path representing +// pool/dataset or pool/parent/dataset +func DatasetCreate(path string, dtype DatasetType, + props map[Prop]Property) (d Dataset, err error) { + var cprops C.nvlist_ptr + if cprops, err = datasetPropertiesTonvlist(props); err != nil { + return + } + defer C.nvlist_free(cprops) + + csPath := C.CString(path) + errcode := C.dataset_create(csPath, C.zfs_type_t(dtype), cprops) + C.free(unsafe.Pointer(csPath)) + if errcode != 0 { + err = LastError() + return + } + return DatasetOpen(path) +} + +// Close close dataset and all its recursive children datasets (close handle +// and cleanup dataset object/s from memory) +func (d *Dataset) Close() { + // if dataset was ever open + if d.closeOnce != nil { + d.closeOnce.Do(func() { + C.dataset_list_close(d.list) + }) + } + d.list = nil + for _, cd := range d.Children { + cd.Close() + } +} + +// reOpen - close and open dataset. Not thread safe! +func (d *Dataset) reOpen() (err error) { + d.Close() + *d, err = DatasetOpen(d.Properties[DatasetPropName].Value) + return +} + +// Destroy destroys the dataset. The caller must make sure that the filesystem +// isn't mounted, and that there are no active dependents. Set Defer argument +// to true to defer destruction for when dataset is not in use. Call Close() to +// cleanup memory. +func (d *Dataset) Destroy(Defer bool) (err error) { + if len(d.Children) > 0 { + path, e := d.Path() + if e != nil { + return + } + dsType, e := d.GetProperty(DatasetPropType) + if e != nil { + dsType.Value = err.Error() // just put error (why it didn't fetch property type) + } + err = errors.New("Cannot destroy dataset " + path + + ": " + dsType.Value + " has children") + return + } + if d.list != nil { + if ec := C.dataset_destroy(d.list, booleanT(Defer)); ec != 0 { + err = LastError() + } + } else { + err = errors.New(msgDatasetIsNil) + } + return +} + +// IsSnapshot - retrun true if datset is snapshot +func (d *Dataset) IsSnapshot() (ok bool) { + path := d.Properties[DatasetPropName].Value + ok = (d.Type == DatasetTypeSnapshot || strings.Contains(path, "@")) + return +} + +// DestroyRecursive recursively destroy children of dataset and dataset. +func (d *Dataset) DestroyRecursive() (err error) { + var path string + if path, err = d.Path(); err != nil { + return + } + if !strings.Contains(path, "@") { // not snapshot + if len(d.Children) > 0 { + for _, c := range d.Children { + if err = c.DestroyRecursive(); err != nil { + return + } + // close handle to destroyed child dataset + c.Close() + } + // clear closed children array + d.Children = make([]Dataset, 0) + } + err = d.Destroy(false) + } else { + var parent Dataset + tmp := strings.Split(path, "@") + ppath, snapname := tmp[0], tmp[1] + if parent, err = DatasetOpen(ppath); err != nil { + return + } + defer parent.Close() + if len(parent.Children) > 0 { + for _, c := range parent.Children { + if path, err = c.Path(); err != nil { + return + } + if strings.Contains(path, "@") { + continue // skip other snapshots + } + if c, err = DatasetOpen(path + "@" + snapname); err != nil { + continue + } + if err = c.DestroyRecursive(); err != nil { + c.Close() + return + } + c.Close() + } + } + err = d.Destroy(false) + } + return +} + +// Pool returns pool dataset belongs to +func (d *Dataset) Pool() (p Pool, err error) { + if d.list == nil { + err = errors.New(msgDatasetIsNil) + return + } + p.list = C.dataset_get_pool(d.list) + if p.list != nil && p.list.zph != nil { + err = p.ReloadProperties() + return + } + err = LastError() + return +} + +// PoolName - return name of the pool +func (d *Dataset) PoolName() string { + path := d.Properties[DatasetPropName].Value + i := strings.Index(path, "/") + if i < 0 { + return path + } + return path[0:i] +} + +// ReloadProperties re-read dataset's properties +func (d *Dataset) ReloadProperties() (err error) { + Global.Mtx.Lock() + defer Global.Mtx.Unlock() + if d.list == nil { + err = errors.New(msgDatasetIsNil) + return + } + d.Properties = make(map[Prop]Property) + C.zfs_refresh_properties(d.list.zh) + for prop := DatasetPropType; prop < DatasetNumProps; prop++ { + plist := C.read_dataset_property(d.list, C.int(prop)) + if plist == nil { + continue + } + d.Properties[prop] = Property{Value: C.GoString(&(*plist).value[0]), + Source: C.GoString(&(*plist).source[0])} + C.free_properties(plist) + } + return +} + +// GetProperty reload and return single specified property. This also reloads requested +// property in Properties map. +func (d *Dataset) GetProperty(p Prop) (prop Property, err error) { + Global.Mtx.Lock() + defer Global.Mtx.Unlock() + if d.list == nil { + err = errors.New(msgDatasetIsNil) + return + } + plist := C.read_dataset_property(d.list, C.int(p)) + if plist == nil { + err = LastError() + return + } + defer C.free_properties(plist) + prop = Property{Value: C.GoString(&(*plist).value[0]), + Source: C.GoString(&(*plist).source[0])} + d.Properties[p] = prop + return +} + +// GetUserProperty - lookup and return user propery +func (d *Dataset) GetUserProperty(p string) (prop Property, err error) { + Global.Mtx.Lock() + defer Global.Mtx.Unlock() + if d.list == nil { + err = errors.New(msgDatasetIsNil) + return + } + csp := C.CString(p) + defer C.free(unsafe.Pointer(csp)) + plist := C.read_user_property(d.list, csp) + if plist == nil { + err = LastError() + return + } + defer C.free_properties(plist) + prop = Property{Value: C.GoString(&(*plist).value[0]), + Source: C.GoString(&(*plist).source[0])} + return +} + +// SetProperty set ZFS dataset property to value. Not all properties can be set, +// some can be set only at creation time and some are read only. +// Always check if returned error and its description. +func (d *Dataset) SetProperty(p Prop, value string) (err error) { + Global.Mtx.Lock() + defer Global.Mtx.Unlock() + if d.list == nil { + err = errors.New(msgDatasetIsNil) + return + } + csValue := C.CString(value) + errcode := C.dataset_prop_set(d.list, C.zfs_prop_t(p), csValue) + C.free(unsafe.Pointer(csValue)) + if errcode != 0 { + err = LastError() + return + } + // Update Properties member with change made + plist := C.read_dataset_property(d.list, C.int(p)) + if plist == nil { + err = LastError() + return + } + defer C.free_properties(plist) + d.Properties[p] = Property{Value: C.GoString(&(*plist).value[0]), + Source: C.GoString(&(*plist).source[0])} + return +} + +// SetUserProperty - +func (d *Dataset) SetUserProperty(prop, value string) (err error) { + Global.Mtx.Lock() + defer Global.Mtx.Unlock() + if d.list == nil { + err = errors.New(msgDatasetIsNil) + return + } + csValue := C.CString(value) + csProp := C.CString(prop) + errcode := C.dataset_user_prop_set(d.list, csProp, csValue) + C.free(unsafe.Pointer(csValue)) + C.free(unsafe.Pointer(csProp)) + if errcode != 0 { + err = LastError() + } + return +} + +// Clone - clones the dataset. The target must be of the same type as +// the source. +func (d *Dataset) Clone(target string, props map[Prop]Property) (rd Dataset, err error) { + var cprops C.nvlist_ptr + if d.list == nil { + err = errors.New(msgDatasetIsNil) + return + } + if cprops, err = datasetPropertiesTonvlist(props); err != nil { + return + } + defer C.nvlist_free(cprops) + csTarget := C.CString(target) + defer C.free(unsafe.Pointer(csTarget)) + if errc := C.dataset_clone(d.list, csTarget, cprops); errc != 0 { + err = LastError() + return + } + rd, err = DatasetOpen(target) + return +} + +// DatasetSnapshot create dataset snapshot. Set recur to true to snapshot child datasets. +func DatasetSnapshot(path string, recur bool, props map[Prop]Property) (rd Dataset, err error) { + var cprops C.nvlist_ptr + if cprops, err = datasetPropertiesTonvlist(props); err != nil { + return + } + defer C.nvlist_free(cprops) + csPath := C.CString(path) + defer C.free(unsafe.Pointer(csPath)) + if errc := C.dataset_snapshot(csPath, booleanT(recur), cprops); errc != 0 { + err = LastError() + return + } + rd, err = DatasetOpen(path) + return +} + +// Path return zfs dataset path/name +func (d *Dataset) Path() (path string, err error) { + if d.list == nil { + err = errors.New(msgDatasetIsNil) + return + } + name := C.dataset_get_name(d.list) + path = C.GoString(name) + return +} + +// Rollback rollabck's dataset snapshot +func (d *Dataset) Rollback(snap *Dataset, force bool) (err error) { + if d.list == nil { + err = errors.New(msgDatasetIsNil) + return + } + if errc := C.dataset_rollback(d.list, snap.list, booleanT(force)); errc != 0 { + err = LastError() + return + } + d.ReloadProperties() + return +} + +// Promote promotes dataset clone +func (d *Dataset) Promote() (err error) { + if d.list == nil { + err = errors.New(msgDatasetIsNil) + return + } + if errc := C.dataset_promote(d.list); errc != 0 { + err = LastError() + return + } + d.ReloadProperties() + return +} + +// Rename dataset +func (d *Dataset) Rename(newName string, recur, + forceUnmount bool) (err error) { + if d.list == nil { + err = errors.New(msgDatasetIsNil) + return + } + csNewName := C.CString(newName) + defer C.free(unsafe.Pointer(csNewName)) + if errc := C.dataset_rename(d.list, csNewName, + booleanT(recur), booleanT(false), booleanT(forceUnmount)); errc != 0 { + err = LastError() + return + } + d.ReloadProperties() + return +} + +// Rename2 dataset for ZFS 2.0.x with an option to rename a filesystem without needing to remount +func (d *Dataset) Rename2(newName string, flags RenameFlags) (err error) { + if d.list == nil { + err = errors.New(msgDatasetIsNil) + return + } + csNewName := C.CString(newName) + defer C.free(unsafe.Pointer(csNewName)) + if errc := C.dataset_rename(d.list, csNewName, + booleanT(flags.Recursive), booleanT(flags.Nounmount), booleanT(flags.Forceunmount)); errc != 0 { + err = LastError() + return + } + d.ReloadProperties() + return +} + +// IsMounted checks to see if the mount is active. If the filesystem is mounted, +// sets in 'where' argument the current mountpoint, and returns true. Otherwise, +// returns false. +func (d *Dataset) IsMounted() (mounted bool, where string) { + if d.list == nil { + return + } + Global.Mtx.Lock() + defer Global.Mtx.Unlock() + mp := C.dataset_is_mounted(d.list) + // defer C.free(mp) + if mounted = (mp != nil); mounted { + where = C.GoString(mp) + C.free(unsafe.Pointer(mp)) + } + return +} + +// Mount the given filesystem. +func (d *Dataset) Mount(options string, flags int) (err error) { + Global.Mtx.Lock() + defer Global.Mtx.Unlock() + if d.list == nil { + err = errors.New(msgDatasetIsNil) + return + } + csOptions := C.CString(options) + defer C.free(unsafe.Pointer(csOptions)) + if ec := C.dataset_mount(d.list, csOptions, C.int(flags)); ec != 0 { + err = LastError() + } + return +} + +// Unmount the given filesystem. +func (d *Dataset) Unmount(flags int) (err error) { + if d.list == nil { + err = errors.New(msgDatasetIsNil) + return + } + if ec := C.dataset_unmount(d.list, C.int(flags)); ec != 0 { + err = LastError() + } + return +} + +// UnmountAll unmount this filesystem and any children inheriting the +// mountpoint property. +func (d *Dataset) UnmountAll(flags int) (err error) { + if d.list == nil { + err = errors.New(msgDatasetIsNil) + return + } + // This is implemented recursive because zfs_unmountall() didn't work + if len(d.Children) > 0 { + for _, c := range d.Children { + if err = c.UnmountAll(flags); err != nil { + return + } + } + } + return d.Unmount(flags) +} + +// Hold - Adds a single reference, named with the tag argument, to the snapshot. +// Each snapshot has its own tag namespace, and tags must be unique within that space. +func (d *Dataset) Hold(flag string) (err error) { + var path string + var pd Dataset + if path, err = d.Path(); err != nil { + return + } + if !strings.Contains(path, "@") { + err = fmt.Errorf("'%s' is not a snapshot", path) + return + } + pd, err = DatasetOpenSingle(path[:strings.Index(path, "@")]) + if err != nil { + return + } + defer pd.Close() + csSnapName := C.CString(path[strings.Index(path, "@")+1:]) + defer C.free(unsafe.Pointer(csSnapName)) + csFlag := C.CString(flag) + defer C.free(unsafe.Pointer(csFlag)) + if 0 != C.zfs_hold(pd.list.zh, csSnapName, csFlag, booleanT(false), -1) { + err = LastError() + } + return +} + +// Release - Removes a single reference, named with the tag argument, from the specified snapshot. +// The tag must already exist for each snapshot. If a hold exists on a snapshot, attempts to destroy +// that snapshot by using the zfs destroy command return EBUSY. +func (d *Dataset) Release(flag string) (err error) { + var path string + var pd Dataset + if path, err = d.Path(); err != nil { + return + } + if !strings.Contains(path, "@") { + err = fmt.Errorf("'%s' is not a snapshot", path) + return + } + pd, err = DatasetOpenSingle(path[:strings.Index(path, "@")]) + if err != nil { + return + } + defer pd.Close() + csSnapName := C.CString(path[strings.Index(path, "@")+1:]) + defer C.free(unsafe.Pointer(csSnapName)) + csFlag := C.CString(flag) + defer C.free(unsafe.Pointer(csFlag)) + if 0 != C.zfs_release(pd.list.zh, csSnapName, csFlag, booleanT(false)) { + err = LastError() + } + return +} + +// Holds - Lists all existing user references for the given snapshot +func (d *Dataset) Holds() (tags []HoldTag, err error) { + var nvl *C.nvlist_t + var nvp *C.nvpair_t + var tu64 C.uint64_t + var path string + if path, err = d.Path(); err != nil { + return + } + if !strings.Contains(path, "@") { + err = fmt.Errorf("'%s' is not a snapshot", path) + return + } + if 0 != C.zfs_get_holds(d.list.zh, &nvl) { + err = LastError() + return + } + defer C.nvlist_free(nvl) + tags = make([]HoldTag, 0, 5) + for nvp = C.nvlist_next_nvpair(nvl, nvp); nvp != nil; { + tag := C.nvpair_name(nvp) + C.nvpair_value_uint64(nvp, &tu64) + tags = append(tags, HoldTag{ + Name: C.GoString(tag), + Timestamp: time.Unix(int64(tu64), 0), + }) + + nvp = C.nvlist_next_nvpair(nvl, nvp) + } + return +} + +// DatasetPropertyToName convert property to name +// ( returns built in string representation of property name). +// This is optional, you can represent each property with string +// name of choice. +func DatasetPropertyToName(p Prop) (name string) { + if p == DatasetNumProps { + return "numofprops" + } + prop := C.zfs_prop_t(p) + name = C.GoString(C.zfs_prop_to_name(prop)) + return +} + +// DestroyPromote - Same as DestroyRecursive() except it will not destroy +// any dependent clones, but promote them first. +// This function will navigate any dependency chain +// of cloned datasets using breadth first search to promote according and let +// you remove dataset regardless of its cloned dependencies. +// Note: that this function wan't work when you want to destroy snapshot this way. +// However it will destroy all snaphsot of destroyed dataset without dependencies, +// otherwise snapshot will move to promoted clone +func (d *Dataset) DestroyPromote() (err error) { + var snaps []Dataset + var clones []string + // We need to save list of child snapshots, to destroy them latter + // since they will be moved to promoted clone + var psnaps []string + if clones, err = d.Clones(); err != nil { + return + } + if len(clones) > 0 { + var cds Dataset + // For this to always work we need to promote youngest clone + // in terms of most recent origin snapshot or creation time if + // cloned from same snapshot + if cds, err = DatasetOpen(clones[0]); err != nil { + return + } + defer cds.Close() + // since promote will move the snapshots to promoted dataset + // we need to check and resolve possible name conflicts + if snaps, err = d.Snapshots(); err != nil { + return + } + for _, s := range snaps { + spath := s.Properties[DatasetPropName].Value + sname := spath[strings.Index(spath, "@"):] + // conflict and resolve + if ok, _ := cds.FindSnapshotName(sname); ok { + // snapshot with the same name already exist + volname := path.Base(spath[:strings.Index(spath, "@")]) + sname = sname + "." + volname + if err = s.Rename(spath+"."+volname, false, true); err != nil { + return + } + } + psnaps = append(psnaps, sname) + } + if err = cds.Promote(); err != nil { + return + } + } + // destroy child datasets, since this works recursive + for _, cd := range d.Children { + if err = cd.DestroyPromote(); err != nil { + return + } + } + d.Children = make([]Dataset, 0) + if err = d.Destroy(false); err != nil { + return + } + // Load with new promoted snapshots + if len(clones) > 0 && len(psnaps) > 0 { + var cds Dataset + if cds, err = DatasetOpen(clones[0]); err != nil { + return + } + defer cds.Close() + // try to destroy (promoted) snapshots now + for _, sname := range psnaps { + if ok, snap := cds.FindSnapshotName(sname); ok { + snap.Destroy(false) + } + } + } + return +} + +// Snapshots - filter and return all snapshots of dataset +func (d *Dataset) Snapshots() (snaps []Dataset, err error) { + for _, ch := range d.Children { + if !ch.IsSnapshot() { + continue + } + snaps = append(snaps, ch) + } + return +} + +// FindSnapshot - returns true if given path is one of dataset snaphsots +func (d *Dataset) FindSnapshot(path string) (ok bool, snap Dataset) { + for _, ch := range d.Children { + if !ch.IsSnapshot() { + continue + } + if ok = (path == ch.Properties[DatasetPropName].Value); ok { + snap = ch + break + } + } + return +} + +// FindSnapshotName - returns true and snapshot if given snapshot +// name eg. '@snap1' is one of dataset snaphsots +func (d *Dataset) FindSnapshotName(name string) (ok bool, snap Dataset) { + return d.FindSnapshot(d.Properties[DatasetPropName].Value + name) +} + +// Clones - get list of all dataset paths cloned from this +// dataset or this snapshot +// List is sorted descedent by origin snapshot order +func (d *Dataset) Clones() (clones []string, err error) { + // Clones can only live on same pool + var root Dataset + var sortDesc []Dataset + if root, err = DatasetOpen(d.PoolName()); err != nil { + return + } + defer root.Close() + dIsSnapshot := d.IsSnapshot() + // USe breadth first search to find all clones + queue := make(chan Dataset, 1024) + defer close(queue) // This will close and cleanup all + queue <- root // start from the root element + for { + select { + case ds := <-queue: // pull from queue (breadth first search) + for _, ch := range ds.Children { + origin := ch.Properties[DatasetPropOrigin].Value + if len(origin) > 0 { + if dIsSnapshot && origin == d.Properties[DatasetPropName].Value { + // if this dataset is snaphot + ch.Properties[DatasetNumProps+1000] = d.Properties[DatasetPropCreateTXG] + sortDesc = append(sortDesc, ch) + } else { + // Check if origin of this dataset is one of snapshots + ok, snap := d.FindSnapshot(origin) + if !ok { + continue + } + ch.Properties[DatasetNumProps+1000] = snap.Properties[DatasetPropCreateTXG] + sortDesc = append(sortDesc, ch) + } + } + queue <- ch + } + default: + sort.Sort(clonesCreateDesc(sortDesc)) + // This way we get clones ordered from most recent sanpshots first + for _, c := range sortDesc { + clones = append(clones, c.Properties[DatasetPropName].Value) + } + return + } + } + return +} diff --git a/go-libzfs/zfs.h b/go-libzfs/zfs.h new file mode 100644 index 0000000..aa1176d --- /dev/null +++ b/go-libzfs/zfs.h @@ -0,0 +1,146 @@ +/* C wrappers around some zfs calls and C in general that should simplify + * using libzfs from go language, make go code shorter and more readable. + */ + +#ifndef SERVERWARE_ZFS_H +#define SERVERWARE_ZFS_H + +struct dataset_list { + zfs_handle_t *zh; + void *pnext; +}; + +typedef struct zfs_share { + uint64_t z_exportdata; + uint64_t z_sharedata; + uint64_t z_sharetype; /* 0 = share, 1 = unshare */ + uint64_t z_sharemax; /* max length of share string */ +} zfs_share_t; + +/* + * A limited number of zpl level stats are retrievable + * with an ioctl. zfs diff is the current consumer. + */ +typedef struct zfs_stat { + uint64_t zs_gen; + uint64_t zs_mode; + uint64_t zs_links; + uint64_t zs_ctime[2]; +} zfs_stat_t; + +typedef struct zinject_record { + uint64_t zi_objset; + uint64_t zi_object; + uint64_t zi_start; + uint64_t zi_end; + uint64_t zi_guid; + uint32_t zi_level; + uint32_t zi_error; + uint64_t zi_type; + uint32_t zi_freq; + uint32_t zi_failfast; + char zi_func[MAXNAMELEN]; + uint32_t zi_iotype; + int32_t zi_duration; + uint64_t zi_timer; + uint64_t zi_nlanes; + uint32_t zi_cmd; + uint32_t zi_pad; +} zinject_record_t; + +typedef struct dmu_objset_stats { + uint64_t dds_num_clones; /* number of clones of this */ + uint64_t dds_creation_txg; + uint64_t dds_guid; + dmu_objset_type_t dds_type; + uint8_t dds_is_snapshot; + uint8_t dds_inconsistent; + char dds_origin[ZFS_MAX_DATASET_NAME_LEN]; +} dmu_objset_stats_t; + +typedef struct zfs_cmd { + char zc_name[MAXPATHLEN]; /* name of pool or dataset */ + uint64_t zc_nvlist_src; /* really (char *) */ + uint64_t zc_nvlist_src_size; + uint64_t zc_nvlist_dst; /* really (char *) */ + uint64_t zc_nvlist_dst_size; + boolean_t zc_nvlist_dst_filled; /* put an nvlist in dst? */ + int zc_pad2; + + /* + * The following members are for legacy ioctls which haven't been + * converted to the new method. + */ + uint64_t zc_history; /* really (char *) */ + char zc_value[MAXPATHLEN * 2]; + char zc_string[MAXNAMELEN]; + uint64_t zc_guid; + uint64_t zc_nvlist_conf; /* really (char *) */ + uint64_t zc_nvlist_conf_size; + uint64_t zc_cookie; + uint64_t zc_objset_type; + uint64_t zc_perm_action; + uint64_t zc_history_len; + uint64_t zc_history_offset; + uint64_t zc_obj; + uint64_t zc_iflags; /* internal to zfs(7fs) */ + zfs_share_t zc_share; + dmu_objset_stats_t zc_objset_stats; + zinject_record_t zc_inject_record; + uint32_t zc_defer_destroy; + uint32_t zc_flags; + uint64_t zc_action_handle; + int zc_cleanup_fd; + uint8_t zc_simple; + uint8_t zc_pad[3]; /* alignment */ + uint64_t zc_sendobj; + uint64_t zc_fromobj; + uint64_t zc_createtxg; + zfs_stat_t zc_stat; +} zfs_cmd_t; + +typedef struct dataset_list dataset_list_t; +typedef struct dataset_list* dataset_list_ptr; + + +dataset_list_t *create_dataset_list_item(); +void dataset_list_close(dataset_list_t *list); +void dataset_list_free(dataset_list_t *list); + +dataset_list_t* dataset_list_root(); +dataset_list_t* dataset_list_children(dataset_list_t *dataset); +dataset_list_t *dataset_next(dataset_list_t *dataset); +int dataset_type(dataset_list_ptr dataset); + +dataset_list_ptr dataset_open(const char *path); +int dataset_create(const char *path, zfs_type_t type, nvlist_ptr props); +int dataset_destroy(dataset_list_ptr dataset, boolean_t defer); +zpool_list_ptr dataset_get_pool(dataset_list_ptr dataset); +int dataset_prop_set(dataset_list_ptr dataset, zfs_prop_t prop, const char *value); +int dataset_user_prop_set(dataset_list_ptr dataset, const char *prop, const char *value); +int dataset_clone(dataset_list_ptr dataset, const char *target, nvlist_ptr props); +int dataset_snapshot(const char *path, boolean_t recur, nvlist_ptr props); +int dataset_rollback(dataset_list_ptr dataset, dataset_list_ptr snapshot, boolean_t force); +int dataset_promote(dataset_list_ptr dataset); +int dataset_rename(dataset_list_ptr dataset, const char* new_name, boolean_t recur, boolean_t nounmount, boolean_t force_unm); +const char* dataset_is_mounted(dataset_list_ptr dataset); +int dataset_mount(dataset_list_ptr dataset, const char *options, int flags); +int dataset_unmount(dataset_list_ptr dataset, int flags); +int dataset_unmountall(dataset_list_ptr dataset, int flags); +const char *dataset_get_name(dataset_list_ptr ds); + +property_list_t *read_dataset_property(dataset_list_t *dataset, int prop); +property_list_t *read_user_property(dataset_list_t *dataset, const char* prop); + +char** alloc_cstrings(int size); +void strings_setat(char **a, int at, char *v); + +sendflags_t *alloc_sendflags(); +recvflags_t *alloc_recvflags(); + + +struct zfs_cmd *new_zfs_cmd(); +int estimate_send_size(struct zfs_cmd *zc); + +#endif +/* SERVERWARE_ZFS_H */ diff --git a/go-libzfs/zpool.c b/go-libzfs/zpool.c new file mode 100644 index 0000000..97783ea --- /dev/null +++ b/go-libzfs/zpool.c @@ -0,0 +1,563 @@ +/* C wrappers around some zfs calls and C in general that should simplify + * using libzfs from go language, and make go code shorter and more readable. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include "common.h" +#include "zpool.h" + +char *sZPOOL_CONFIG_VERSION = ZPOOL_CONFIG_VERSION; +char *sZPOOL_CONFIG_POOL_NAME = ZPOOL_CONFIG_POOL_NAME; +char *sZPOOL_CONFIG_POOL_STATE = ZPOOL_CONFIG_POOL_STATE; +char *sZPOOL_CONFIG_POOL_TXG = ZPOOL_CONFIG_POOL_TXG; +char *sZPOOL_CONFIG_POOL_GUID = ZPOOL_CONFIG_POOL_GUID; +char *sZPOOL_CONFIG_CREATE_TXG = ZPOOL_CONFIG_CREATE_TXG; +char *sZPOOL_CONFIG_TOP_GUID = ZPOOL_CONFIG_TOP_GUID; +char *sZPOOL_CONFIG_VDEV_TREE = ZPOOL_CONFIG_VDEV_TREE; +char *sZPOOL_CONFIG_TYPE = ZPOOL_CONFIG_TYPE; +char *sZPOOL_CONFIG_CHILDREN = ZPOOL_CONFIG_CHILDREN; +char *sZPOOL_CONFIG_ID = ZPOOL_CONFIG_ID; +char *sZPOOL_CONFIG_GUID = ZPOOL_CONFIG_GUID; +char *sZPOOL_CONFIG_PATH = ZPOOL_CONFIG_PATH; +char *sZPOOL_CONFIG_DEVID = ZPOOL_CONFIG_DEVID; +char *sZPOOL_CONFIG_METASLAB_ARRAY = ZPOOL_CONFIG_METASLAB_ARRAY; +char *sZPOOL_CONFIG_METASLAB_SHIFT = ZPOOL_CONFIG_METASLAB_SHIFT; +char *sZPOOL_CONFIG_ASHIFT = ZPOOL_CONFIG_ASHIFT; +char *sZPOOL_CONFIG_ASIZE = ZPOOL_CONFIG_ASIZE; +char *sZPOOL_CONFIG_DTL = ZPOOL_CONFIG_DTL; +char *sZPOOL_CONFIG_SCAN_STATS = ZPOOL_CONFIG_SCAN_STATS; +char *sZPOOL_CONFIG_VDEV_STATS = ZPOOL_CONFIG_VDEV_STATS; +char *sZPOOL_CONFIG_WHOLE_DISK = ZPOOL_CONFIG_WHOLE_DISK; +char *sZPOOL_CONFIG_ERRCOUNT = ZPOOL_CONFIG_ERRCOUNT; +char *sZPOOL_CONFIG_NOT_PRESENT = ZPOOL_CONFIG_NOT_PRESENT; +char *sZPOOL_CONFIG_SPARES = ZPOOL_CONFIG_SPARES; +char *sZPOOL_CONFIG_IS_SPARE = ZPOOL_CONFIG_IS_SPARE; +char *sZPOOL_CONFIG_NPARITY = ZPOOL_CONFIG_NPARITY; +char *sZPOOL_CONFIG_HOSTID = ZPOOL_CONFIG_HOSTID; +char *sZPOOL_CONFIG_HOSTNAME = ZPOOL_CONFIG_HOSTNAME; +char *sZPOOL_CONFIG_LOADED_TIME = ZPOOL_CONFIG_LOADED_TIME; +char *sZPOOL_CONFIG_UNSPARE = ZPOOL_CONFIG_UNSPARE; +char *sZPOOL_CONFIG_PHYS_PATH = ZPOOL_CONFIG_PHYS_PATH; +char *sZPOOL_CONFIG_IS_LOG = ZPOOL_CONFIG_IS_LOG; +char *sZPOOL_CONFIG_L2CACHE = ZPOOL_CONFIG_L2CACHE; +char *sZPOOL_CONFIG_HOLE_ARRAY = ZPOOL_CONFIG_HOLE_ARRAY; +char *sZPOOL_CONFIG_VDEV_CHILDREN = ZPOOL_CONFIG_VDEV_CHILDREN; +char *sZPOOL_CONFIG_IS_HOLE = ZPOOL_CONFIG_IS_HOLE; +char *sZPOOL_CONFIG_DDT_HISTOGRAM = ZPOOL_CONFIG_DDT_HISTOGRAM; +char *sZPOOL_CONFIG_DDT_OBJ_STATS = ZPOOL_CONFIG_DDT_OBJ_STATS; +char *sZPOOL_CONFIG_DDT_STATS = ZPOOL_CONFIG_DDT_STATS; +char *sZPOOL_CONFIG_SPLIT = ZPOOL_CONFIG_SPLIT; +char *sZPOOL_CONFIG_ORIG_GUID = ZPOOL_CONFIG_ORIG_GUID; +char *sZPOOL_CONFIG_SPLIT_GUID = ZPOOL_CONFIG_SPLIT_GUID; +char *sZPOOL_CONFIG_SPLIT_LIST = ZPOOL_CONFIG_SPLIT_LIST; +char *sZPOOL_CONFIG_REMOVING = ZPOOL_CONFIG_REMOVING; +char *sZPOOL_CONFIG_RESILVER_TXG = ZPOOL_CONFIG_RESILVER_TXG; +char *sZPOOL_CONFIG_COMMENT = ZPOOL_CONFIG_COMMENT; +char *sZPOOL_CONFIG_SUSPENDED = ZPOOL_CONFIG_SUSPENDED; +char *sZPOOL_CONFIG_TIMESTAMP = ZPOOL_CONFIG_TIMESTAMP; +char *sZPOOL_CONFIG_BOOTFS = ZPOOL_CONFIG_BOOTFS; +char *sZPOOL_CONFIG_MISSING_DEVICES = ZPOOL_CONFIG_MISSING_DEVICES; +char *sZPOOL_CONFIG_LOAD_INFO = ZPOOL_CONFIG_LOAD_INFO; +char *sZPOOL_CONFIG_REWIND_INFO = ZPOOL_CONFIG_REWIND_INFO; +char *sZPOOL_CONFIG_UNSUP_FEAT = ZPOOL_CONFIG_UNSUP_FEAT; +char *sZPOOL_CONFIG_ENABLED_FEAT = ZPOOL_CONFIG_ENABLED_FEAT; +char *sZPOOL_CONFIG_CAN_RDONLY = ZPOOL_CONFIG_CAN_RDONLY; +char *sZPOOL_CONFIG_FEATURES_FOR_READ = ZPOOL_CONFIG_FEATURES_FOR_READ; +char *sZPOOL_CONFIG_FEATURE_STATS = ZPOOL_CONFIG_FEATURE_STATS; +char *sZPOOL_CONFIG_ERRATA = ZPOOL_CONFIG_ERRATA; +char *sZPOOL_CONFIG_OFFLINE = ZPOOL_CONFIG_OFFLINE; +char *sZPOOL_CONFIG_FAULTED = ZPOOL_CONFIG_FAULTED; +char *sZPOOL_CONFIG_DEGRADED = ZPOOL_CONFIG_DEGRADED; +char *sZPOOL_CONFIG_REMOVED = ZPOOL_CONFIG_REMOVED; +char *sZPOOL_CONFIG_FRU = ZPOOL_CONFIG_FRU; +char *sZPOOL_CONFIG_AUX_STATE = ZPOOL_CONFIG_AUX_STATE; +char *sZPOOL_LOAD_POLICY = ZPOOL_LOAD_POLICY; +char *sZPOOL_LOAD_REWIND_POLICY = ZPOOL_LOAD_REWIND_POLICY; +char *sZPOOL_LOAD_REQUEST_TXG = ZPOOL_LOAD_REQUEST_TXG; +char *sZPOOL_LOAD_META_THRESH = ZPOOL_LOAD_META_THRESH; +char *sZPOOL_LOAD_DATA_THRESH = ZPOOL_LOAD_DATA_THRESH; +char *sZPOOL_CONFIG_LOAD_TIME = ZPOOL_CONFIG_LOAD_TIME; +char *sZPOOL_CONFIG_LOAD_DATA_ERRORS = ZPOOL_CONFIG_LOAD_DATA_ERRORS; +char *sZPOOL_CONFIG_REWIND_TIME = ZPOOL_CONFIG_REWIND_TIME; + +static char _lasterr_[1024]; + +const char *lasterr(void) { + return _lasterr_; +} + +zpool_list_t *create_zpool_list_item() { + zpool_list_t *zlist = malloc(sizeof(zpool_list_t)); + memset(zlist, 0, sizeof(zpool_list_t)); + return zlist; +} + +int zpool_list_callb(zpool_handle_t *pool, void *data) { + zpool_list_t **lroot = (zpool_list_t**)data; + zpool_list_t *nroot = create_zpool_list_item(); + + if ( !((*lroot)->zph) ) { + (*lroot)->zph = pool; + } else { + nroot->zph = pool; + nroot->pnext = (void*)*lroot; + *lroot = nroot; + } + return 0; +} + +zpool_list_ptr zpool_list_openall() { + int err = 0; + zpool_list_t *zlist = create_zpool_list_item(); + err = zpool_iter(libzfsHandle, zpool_list_callb, &zlist); + if ( err != 0 || zlist->zph == NULL ) { + zpool_list_free(zlist); + zlist = NULL; + } + return zlist; +} + +zpool_list_t* zpool_list_open(const char *name) { + zpool_list_t *zlist = create_zpool_list_item(); + zlist->zph = zpool_open(libzfsHandle, name); + if ( zlist->zph ) { + return zlist; + } else { + zpool_list_free(zlist); + } + return 0; +} + +zpool_list_t *zpool_next(zpool_list_t *pool) { + return pool->pnext; +} + +void zpool_list_free(zpool_list_t *list) { + zpool_list_ptr next; + while(list) { + next = list->pnext; + free(list); + list = next; + } +} + +void zpool_list_close(zpool_list_t *pool) { + zpool_close(pool->zph); + zpool_list_free(pool); +} + +property_list_t *next_property(property_list_t *list) { + if (list != 0) { + return list->pnext; + } + return list; +} + + +void zprop_source_tostr(char *dst, zprop_source_t source) { + switch (source) { + case ZPROP_SRC_NONE: + strcpy(dst, "none"); + break; + case ZPROP_SRC_TEMPORARY: + strcpy(dst, "temporary"); + break; + case ZPROP_SRC_LOCAL: + strcpy(dst, "local"); + break; + case ZPROP_SRC_INHERITED: + strcpy(dst, "inherited"); + break; + case ZPROP_SRC_RECEIVED: + strcpy(dst, "received"); + break; + default: + strcpy(dst, "default"); + break; + } +} + + +property_list_ptr read_zpool_property(zpool_list_ptr pool, int prop) { + + int r = 0; + zprop_source_t source; + property_list_ptr list = new_property_list(); + + r = zpool_get_prop(pool->zph, prop, + list->value, INT_MAX_VALUE, &source, B_TRUE); + if (r == 0) { + // strcpy(list->name, zpool_prop_to_name(prop)); + zprop_source_tostr(list->source, source); + } else { + free_properties(list); + return NULL; + } + list->property = (int)prop; + return list; +} + +property_list_ptr read_append_zpool_property(zpool_list_ptr pool, property_list_ptr proot, zpool_prop_t prop) { + int r = 0; + property_list_t *newitem = NULL; + + newitem = read_zpool_property(pool, prop); + if (newitem == NULL) { + return proot; + } + // printf("p: %s %s %s\n", newitem->name, newitem->value, newitem->source); + newitem->pnext = proot; + proot = newitem; + + return proot; +} + +property_list_t *read_zpool_properties(zpool_list_ptr pool) { + // read pool name as first property + property_list_t *root = NULL, *list = NULL; + + root = read_append_zpool_property(pool, root, ZPOOL_PROP_NAME); + root = read_append_zpool_property(pool, root, ZPOOL_PROP_SIZE); + root = read_append_zpool_property(pool, root, ZPOOL_PROP_CAPACITY); + root = read_append_zpool_property(pool, root, ZPOOL_PROP_ALTROOT); + root = read_append_zpool_property(pool, root, ZPOOL_PROP_HEALTH); + root = read_append_zpool_property(pool, root, ZPOOL_PROP_GUID); + root = read_append_zpool_property(pool, root, ZPOOL_PROP_VERSION); + root = read_append_zpool_property(pool, root, ZPOOL_PROP_BOOTFS); + root = read_append_zpool_property(pool, root, ZPOOL_PROP_DELEGATION); + root = read_append_zpool_property(pool, root, ZPOOL_PROP_AUTOREPLACE); + root = read_append_zpool_property(pool, root, ZPOOL_PROP_CACHEFILE); + root = read_append_zpool_property(pool, root, ZPOOL_PROP_FAILUREMODE); + root = read_append_zpool_property(pool, root, ZPOOL_PROP_LISTSNAPS); + root = read_append_zpool_property(pool, root, ZPOOL_PROP_AUTOEXPAND); + root = read_append_zpool_property(pool, root, ZPOOL_PROP_DEDUPDITTO); + root = read_append_zpool_property(pool, root, ZPOOL_PROP_DEDUPRATIO); + root = read_append_zpool_property(pool, root, ZPOOL_PROP_FREE); + root = read_append_zpool_property(pool, root, ZPOOL_PROP_ALLOCATED); + root = read_append_zpool_property(pool, root, ZPOOL_PROP_READONLY); + root = read_append_zpool_property(pool, root, ZPOOL_PROP_ASHIFT); + root = read_append_zpool_property(pool, root, ZPOOL_PROP_COMMENT); + root = read_append_zpool_property(pool, root, ZPOOL_PROP_EXPANDSZ); + root = read_append_zpool_property(pool, root, ZPOOL_PROP_FREEING); + + list = new_property_list(); + list->property = ZPOOL_NUM_PROPS; + sprintf(list->value, "%d", ZPOOL_NUM_PROPS); + list->pnext = root; + zprop_source_tostr(list->source, ZPROP_SRC_NONE); + root = list; + + // printf("Finished properties reading.\n"); + return root; +} + +pool_state_t zpool_read_state(zpool_handle_t *zh) { + return zpool_get_state(zh); +} + + +const char *gettext(const char *txt) { + return txt; +} +/* + * Add a property pair (name, string-value) into a property nvlist. + */ +// int +// add_prop_list(const char *propname, char *propval, nvlist_t **props, +// boolean_t poolprop) { +// zpool_prop_t prop = ZPROP_INVAL; +// zfs_prop_t fprop; +// nvlist_t *proplist; +// const char *normnm; +// char *strval; + +// if (*props == NULL && +// nvlist_alloc(props, NV_UNIQUE_NAME, 0) != 0) { +// (void) snprintf(_lasterr_, 1024, "internal error: out of memory"); +// return (1); +// } + +// proplist = *props; + +// if (poolprop) { +// const char *vname = zpool_prop_to_name(ZPOOL_PROP_VERSION); + +// if ((prop = zpool_name_to_prop(propname)) == ZPROP_INVAL && +// !zpool_prop_feature(propname)) { +// (void) snprintf(_lasterr_, 1024, "property '%s' is " +// "not a valid pool property", propname); +// return (2); +// } + +// /* +// * feature@ properties and version should not be specified +// * at the same time. +// */ +// // if ((prop == ZPROP_INVAL && zpool_prop_feature(propname) && +// // nvlist_exists(proplist, vname)) || +// // (prop == ZPOOL_PROP_VERSION && +// // prop_list_contains_feature(proplist))) { +// // (void) fprintf(stderr, gettext("'feature@' and " +// // "'version' properties cannot be specified " +// // "together\n")); +// // return (2); +// // } + + +// if (zpool_prop_feature(propname)) +// normnm = propname; +// else +// normnm = zpool_prop_to_name(prop); +// } else { +// if ((fprop = zfs_name_to_prop(propname)) != ZPROP_INVAL) { +// normnm = zfs_prop_to_name(fprop); +// } else { +// normnm = propname; +// } +// } + +// if (nvlist_lookup_string(proplist, normnm, &strval) == 0 && +// prop != ZPOOL_PROP_CACHEFILE) { +// (void) snprintf(_lasterr_, 1024, "property '%s' " +// "specified multiple times", propname); +// return (2); +// } + +// if (nvlist_add_string(proplist, normnm, propval) != 0) { +// (void) snprintf(_lasterr_, 1024, "internal " +// "error: out of memory\n"); +// return (1); +// } + +// return (0); +// } + +nvlist_t** nvlist_alloc_array(int count) { + return malloc(count*sizeof(nvlist_t*)); +} + +void nvlist_array_set(nvlist_t** a, int i, nvlist_t *item) { + a[i] = item; +} + +void nvlist_free_array(nvlist_t **a) { + free(a); +} + +nvlist_t *nvlist_array_at(nvlist_t **a, uint_t i) { + return a[i]; +} + +int refresh_stats(zpool_list_t *pool) +{ + boolean_t missing; + int err = zpool_refresh_stats(pool->zph, &missing); + if ( err != 0 ) { + return err; + } + if ( missing == B_TRUE ) { + return -1; + } + return 0; +} + +const char *get_vdev_type(nvlist_ptr nv) { + const char *value = NULL; + int r = nvlist_lookup_string(nv, ZPOOL_CONFIG_TYPE, &value); + if(r != 0) { + return NULL; + } + return value; +} + +uint64_t get_vdev_guid(nvlist_ptr nv) { + uint64_t value = 0; + nvlist_lookup_uint64(nv, ZPOOL_CONFIG_GUID, &value); + return value; +} + +const vdev_stat_ptr get_vdev_stats(nvlist_ptr nv) { + vdev_stat_ptr vs = NULL; + uint_t count; + int r = nvlist_lookup_uint64_array(nv, ZPOOL_CONFIG_VDEV_STATS, (uint64_t**)&vs, &count); + if(r != 0) { + return NULL; + } + return vs; +} + +pool_scan_stat_ptr get_vdev_scan_stats(nvlist_t *nv) { + pool_scan_stat_ptr vds = NULL; + uint_t c; + int r = nvlist_lookup_uint64_array(nv, ZPOOL_CONFIG_SCAN_STATS, (uint64_t**)&vds, &c); + if(r != 0) { + return NULL; + } + return vds; +} + +vdev_children_ptr get_vdev_children(nvlist_t *nv) { + int r; + vdev_children_ptr children = malloc(sizeof(vdev_children_t)); + memset(children, 0, sizeof(vdev_children_t)); + r = nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN, &(children->first), &(children->count)); + if (r != 0) { + free(children); + return NULL; + } + return children; +} + +vdev_children_ptr get_vdev_spares(nvlist_t *nv) { + int r; + vdev_children_ptr children = malloc(sizeof(vdev_children_t)); + memset(children, 0, sizeof(vdev_children_t)); + r = nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_SPARES, &(children->first), &(children->count)); + if (r != 0) { + free(children); + return NULL; + } + return children; +} + +vdev_children_ptr get_vdev_l2cache(nvlist_t *nv) { + int r; + vdev_children_ptr children = malloc(sizeof(vdev_children_t)); + memset(children, 0, sizeof(vdev_children_t)); + r = nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_L2CACHE, &(children->first), &(children->count)); + if (r != 0) { + free(children); + return NULL; + } + return children; +} + +const char *get_vdev_path(nvlist_ptr nv) { + const char *path = NULL; + uint64_t notpresent = 0; + int r = nvlist_lookup_uint64(nv, ZPOOL_CONFIG_NOT_PRESENT, ¬present); + if (r == 0 || notpresent != 0) { + if ( 0 != nvlist_lookup_string(nv, ZPOOL_CONFIG_PATH, &path) ) { + return NULL; + } + } + return path; +} + +uint64_t get_vdev_is_log(nvlist_ptr nv) { + uint64_t islog = B_FALSE; + nvlist_lookup_uint64(nv, ZPOOL_CONFIG_IS_LOG, &islog); + return islog; +} + + +// return +uint64_t get_zpool_state(nvlist_ptr nv) { + uint64_t state = 0; + nvlist_lookup_uint64(nv, ZPOOL_CONFIG_POOL_STATE, &state); + return state; +} + +uint64_t get_zpool_guid(nvlist_ptr nv) { + uint64_t guid = 0; + nvlist_lookup_uint64(nv, ZPOOL_CONFIG_POOL_GUID, &guid); + return guid; +} + +const char *get_zpool_name(nvlist_ptr nv) { + const char *name = NULL; + if (0 != nvlist_lookup_string(nv, ZPOOL_CONFIG_POOL_NAME, &name)) { + return NULL; + } + return name; +} + +const char *get_zpool_comment(nvlist_ptr nv) { + const char *comment = NULL; + if (0 != nvlist_lookup_string(nv, ZPOOL_CONFIG_COMMENT, &comment)) { + return NULL; + } + return comment; +} + +nvlist_ptr get_zpool_vdev_tree(nvlist_ptr nv) { + nvlist_ptr vdev_tree = NULL; + if ( 0 != nvlist_lookup_nvlist(nv, ZPOOL_CONFIG_VDEV_TREE, &vdev_tree) ) { + return NULL; + } + return vdev_tree; +} + + +nvlist_ptr go_zpool_search_import(libzfs_handle_ptr zfsh, int paths, char **path, boolean_t do_scan) { + importargs_t idata; + memset(&idata, 0, sizeof(importargs_t)); + nvlist_ptr pools = NULL; + idata.path = path; + idata.paths = paths; + // idata.scan = 0; + + tpool_t *t; + t = tpool_create(1, 5 * sysconf(_SC_NPROCESSORS_ONLN), 0, NULL); + if (t == NULL) + return NULL; + + libpc_handle_t lpch = { + .lpc_lib_handle = zfsh, + .lpc_ops = &libzfs_config_ops, + .lpc_printerr = B_TRUE + }; + pools = zpool_search_import(&lpch, &idata); + + tpool_wait(t); + tpool_destroy(t); + return pools; +} + + +int do_zpool_clear(zpool_list_t *pool, const char *device, u_int32_t load_policy) { + nvlist_t *policy = NULL; + int ret = 0; + if (nvlist_alloc(&policy, NV_UNIQUE_NAME, 0) != 0 || + nvlist_add_uint32(policy, ZPOOL_LOAD_POLICY, load_policy) != 0) + return (1); + + if (zpool_clear(pool->zph, device, policy) != 0) + ret = 1; + + nvlist_free(policy); + + return (ret); +} + +void collect_zpool_leaves(zpool_handle_t *zhp, nvlist_t *nvroot, nvlist_t *nv){ + uint_t children = 0; + nvlist_t **child; + uint_t i; + + (void) nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_CHILDREN, + &child, &children); + + if (children == 0) { + char *path = zpool_vdev_name(libzfsHandle, zhp, nvroot, + VDEV_NAME_PATH); + + if (strcmp(path, VDEV_TYPE_INDIRECT) != 0) + fnvlist_add_boolean(nv, path); + + free(path); + return; + } + + for (i = 0; i < children; i++) { + collect_zpool_leaves(zhp, child[i], nv); + } +} \ No newline at end of file diff --git a/go-libzfs/zpool.go b/go-libzfs/zpool.go new file mode 100644 index 0000000..f295637 --- /dev/null +++ b/go-libzfs/zpool.go @@ -0,0 +1,1197 @@ +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 +) + +// PoolInitializeAction type representing pool initialize action +type PoolInitializeAction int + +// Initialize actions +const ( + PoolInitializeStart PoolInitializeAction = iota // start initialization + PoolInitializeCancel // cancel initialization + PoolInitializeSuspend // suspend initialization +) + +// 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 + 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 + GUID uint64 + 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 + } + + vdevs.GUID = uint64(C.get_vdev_guid(nv)) + + // 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.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 i := 0; next != nil && i < int(PoolNumProps); i++ { + 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 + + // Enable 2.1.x features per default + features["encryption"] = FENABLED + features["project_quota"] = FENABLED + features["device_removal"] = FENABLED + features["obsolete_counts"] = FENABLED + features["zpool_checkpoint"] = FENABLED + features["spacemap_v2"] = FENABLED + features["allocation_classes"] = FENABLED + features["resilver_defer"] = FENABLED + features["bookmark_v2"] = FENABLED + features["redaction_bookmarks"] = FENABLED + features["redacted_datasets"] = FENABLED + features["bookmark_written"] = FENABLED + features["log_spacemap"] = FENABLED + features["livelist"] = FENABLED + features["device_rebuild"] = FENABLED + features["zstd_compress"] = FENABLED + features["draid"] = 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 +} + +// Initialize - initializes pool +func (pool *Pool) Initialize() (err error) { + return pool.initialize(PoolInitializeStart) +} + +// CancelInitialization - cancels ongoing initialization +func (pool *Pool) CancelInitialization() (err error) { + return pool.initialize(PoolInitializeCancel) +} + +// SuspendInitialization - suspends ongoing initialization +func (pool *Pool) SuspendInitialization() (err error) { + return pool.initialize(PoolInitializeSuspend) +} + +func (pool *Pool) initialize(action PoolInitializeAction) (err error) { + var nvroot *C.struct_nvlist + + 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 + } + + var vds *C.nvlist_t + if r := C.nvlist_alloc(&vds, C.NV_UNIQUE_NAME, 0); r != 0 { + err = errors.New("Failed to allocate vdev") + return + } + defer C.nvlist_free(vds) + + C.collect_zpool_leaves(pool.list.zph, nvroot, vds) + + if C.zpool_initialize(pool.list.zph, C.pool_initialize_func_t(action), vds) != 0 { + err = fmt.Errorf("Initialization action %s failed. (%s)", action.String(), LastError()) + return + } + 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 { + str, known := PoolStatusStrings[s] + if !known { + return "UNKNOWN" + } + return str +} + +func (s PoolInitializeAction) String() string { + switch s { + case PoolInitializeStart: + return "START" + case PoolInitializeCancel: + return "CANCEL" + case PoolInitializeSuspend: + return "SUSPEND" + default: + return "UNKNOWN" + } +} diff --git a/go-libzfs/zpool.h b/go-libzfs/zpool.h new file mode 100644 index 0000000..3758e72 --- /dev/null +++ b/go-libzfs/zpool.h @@ -0,0 +1,165 @@ +/* C wrappers around some zfs calls and C in general that should simplify + * using libzfs from go language, make go code shorter and more readable. + */ + +#ifndef SERVERWARE_ZPOOL_H +#define SERVERWARE_ZPOOL_H + +/* Rewind request information */ +#define ZPOOL_NO_REWIND 1 /* No policy - default behavior */ +#define ZPOOL_NEVER_REWIND 2 /* Do not search for best txg or rewind */ +#define ZPOOL_TRY_REWIND 4 /* Search for best txg, but do not rewind */ +#define ZPOOL_DO_REWIND 8 /* Rewind to best txg w/in deferred frees */ +#define ZPOOL_EXTREME_REWIND 16 /* Allow extreme measures to find best txg */ +#define ZPOOL_REWIND_MASK 28 /* All the possible rewind bits */ +#define ZPOOL_REWIND_POLICIES 31 /* All the possible policy bits */ + +struct zpool_list { + zpool_handle_t *zph; + void *pnext; +}; + +struct vdev_children { + nvlist_t **first; + uint_t count; +}; + +typedef struct zpool_list zpool_list_t; +typedef struct zpool_list* zpool_list_ptr; +typedef struct vdev_children vdev_children_t; +typedef struct vdev_children* vdev_children_ptr; + +typedef struct pool_scan_stat* pool_scan_stat_ptr; + +zpool_list_t *create_zpool_list_item(); +void zprop_source_tostr(char *dst, zprop_source_t source); + +zpool_list_t* zpool_list_open(const char *name); +zpool_list_ptr zpool_list_openall(); +zpool_list_t *zpool_next(zpool_list_t *pool); + +void zpool_list_free(zpool_list_t *list); +void zpool_list_close(zpool_list_t *pool); + +property_list_ptr read_zpool_property(zpool_list_ptr pool, int prop); +property_list_t *read_zpool_properties(zpool_list_ptr pool); +property_list_t *next_property(property_list_t *list); + +pool_state_t zpool_read_state(zpool_handle_t *zh); + + +const char *lasterr(void); + +// int +// add_prop_list(const char *propname, char *propval, nvlist_t **props, +// boolean_t poolprop); + +nvlist_t** nvlist_alloc_array(int count); +void nvlist_array_set(nvlist_t** a, int i, nvlist_t *item); +void nvlist_free_array(nvlist_t **a); +nvlist_t *nvlist_array_at(nvlist_t **a, uint_t i); + +int refresh_stats(zpool_list_t *pool); + +const char *get_vdev_type(nvlist_ptr nv); +uint64_t get_vdev_guid(nvlist_ptr nv); +const vdev_stat_ptr get_vdev_stats(nvlist_ptr nv); +pool_scan_stat_ptr get_vdev_scan_stats(nvlist_t *nv); +vdev_children_ptr get_vdev_children(nvlist_t *nv); +vdev_children_ptr get_vdev_spares(nvlist_t *nv); +vdev_children_ptr get_vdev_l2cache(nvlist_t *nv); +const char *get_vdev_path(nvlist_ptr nv); +uint64_t get_vdev_is_log(nvlist_ptr nv); + +uint64_t get_zpool_state(nvlist_ptr nv); +uint64_t get_zpool_guid(nvlist_ptr nv); +const char *get_zpool_name(nvlist_ptr nv); +const char *get_zpool_comment(nvlist_ptr nv); + +nvlist_ptr get_zpool_vdev_tree(nvlist_ptr nv); + +nvlist_ptr go_zpool_search_import(libzfs_handle_ptr zfsh, int paths, char **path, boolean_t do_scan); + +uint64_t set_zpool_vdev_online(zpool_list_t *pool, const char *path, int flags); +int set_zpool_vdev_offline(zpool_list_t *pool, const char *path, boolean_t istmp, boolean_t force); +int do_zpool_clear(zpool_list_t *pool, const char *device, u_int32_t rewind_policy); +void collect_zpool_leaves(zpool_handle_t *zhp, nvlist_t *nvroot, nvlist_t *nv); + + +extern char *sZPOOL_CONFIG_VERSION; +extern char *sZPOOL_CONFIG_POOL_NAME; +extern char *sZPOOL_CONFIG_POOL_STATE; +extern char *sZPOOL_CONFIG_POOL_TXG; +extern char *sZPOOL_CONFIG_POOL_GUID; +extern char *sZPOOL_CONFIG_CREATE_TXG; +extern char *sZPOOL_CONFIG_TOP_GUID; +extern char *sZPOOL_CONFIG_VDEV_TREE; +extern char *sZPOOL_CONFIG_TYPE; +extern char *sZPOOL_CONFIG_CHILDREN; +extern char *sZPOOL_CONFIG_ID; +extern char *sZPOOL_CONFIG_GUID; +extern char *sZPOOL_CONFIG_PATH; +extern char *sZPOOL_CONFIG_DEVID; +extern char *sZPOOL_CONFIG_METASLAB_ARRAY; +extern char *sZPOOL_CONFIG_METASLAB_SHIFT; +extern char *sZPOOL_CONFIG_ASHIFT; +extern char *sZPOOL_CONFIG_ASIZE; +extern char *sZPOOL_CONFIG_DTL; +extern char *sZPOOL_CONFIG_SCAN_STATS; +extern char *sZPOOL_CONFIG_VDEV_STATS; +extern char *sZPOOL_CONFIG_WHOLE_DISK; +extern char *sZPOOL_CONFIG_ERRCOUNT; +extern char *sZPOOL_CONFIG_NOT_PRESENT; +extern char *sZPOOL_CONFIG_SPARES; +extern char *sZPOOL_CONFIG_IS_SPARE; +extern char *sZPOOL_CONFIG_NPARITY; +extern char *sZPOOL_CONFIG_HOSTID; +extern char *sZPOOL_CONFIG_HOSTNAME; +extern char *sZPOOL_CONFIG_LOADED_TIME; +extern char *sZPOOL_CONFIG_UNSPARE; +extern char *sZPOOL_CONFIG_PHYS_PATH; +extern char *sZPOOL_CONFIG_IS_LOG; +extern char *sZPOOL_CONFIG_L2CACHE; +extern char *sZPOOL_CONFIG_HOLE_ARRAY; +extern char *sZPOOL_CONFIG_VDEV_CHILDREN; +extern char *sZPOOL_CONFIG_IS_HOLE; +extern char *sZPOOL_CONFIG_DDT_HISTOGRAM; +extern char *sZPOOL_CONFIG_DDT_OBJ_STATS; +extern char *sZPOOL_CONFIG_DDT_STATS; +extern char *sZPOOL_CONFIG_SPLIT; +extern char *sZPOOL_CONFIG_ORIG_GUID; +extern char *sZPOOL_CONFIG_SPLIT_GUID; +extern char *sZPOOL_CONFIG_SPLIT_LIST; +extern char *sZPOOL_CONFIG_REMOVING; +extern char *sZPOOL_CONFIG_RESILVER_TXG; +extern char *sZPOOL_CONFIG_COMMENT; +extern char *sZPOOL_CONFIG_SUSPENDED; +extern char *sZPOOL_CONFIG_TIMESTAMP; +extern char *sZPOOL_CONFIG_BOOTFS; +extern char *sZPOOL_CONFIG_MISSING_DEVICES; +extern char *sZPOOL_CONFIG_LOAD_INFO; +extern char *sZPOOL_CONFIG_REWIND_INFO; +extern char *sZPOOL_CONFIG_UNSUP_FEAT; +extern char *sZPOOL_CONFIG_ENABLED_FEAT; +extern char *sZPOOL_CONFIG_CAN_RDONLY; +extern char *sZPOOL_CONFIG_FEATURES_FOR_READ; +extern char *sZPOOL_CONFIG_FEATURE_STATS; +extern char *sZPOOL_CONFIG_ERRATA; +extern char *sZPOOL_CONFIG_OFFLINE; +extern char *sZPOOL_CONFIG_FAULTED; +extern char *sZPOOL_CONFIG_DEGRADED; +extern char *sZPOOL_CONFIG_REMOVED; +extern char *sZPOOL_CONFIG_FRU; +extern char *sZPOOL_CONFIG_AUX_STATE; +extern char *sZPOOL_LOAD_POLICY; +extern char *sZPOOL_LOAD_REWIND_POLICY; +extern char *sZPOOL_LOAD_REQUEST_TXG; +extern char *sZPOOL_LOAD_META_THRESH; +extern char *sZPOOL_LOAD_DATA_THRESH; +extern char *sZPOOL_CONFIG_LOAD_TIME; +extern char *sZPOOL_CONFIG_LOAD_DATA_ERRORS; +extern char *sZPOOL_CONFIG_REWIND_TIME; + + +#endif +/* SERVERWARE_ZPOOL_H */ diff --git a/go-libzfs/zpool_vdev.c b/go-libzfs/zpool_vdev.c new file mode 100644 index 0000000..3d4427a --- /dev/null +++ b/go-libzfs/zpool_vdev.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include + +#include "common.h" +#include "zpool.h" + + +uint64_t set_zpool_vdev_online(zpool_list_t *pool, const char *path, int flags) { + vdev_state_t newstate = VDEV_STATE_UNKNOWN; + zpool_vdev_online(pool->zph, path, flags, &newstate); + return newstate; +} + +int set_zpool_vdev_offline(zpool_list_t *pool, const char *path, boolean_t istmp, boolean_t force) { + int ret = 0; + // if (force) { + // uint64_t guid = zpool_vdev_path_to_guid(pool->zph, path); + // vdev_aux_t aux; + // if (istmp == B_FALSE) { + // /* Force the fault to persist across imports */ + // aux = VDEV_AUX_EXTERNAL_PERSIST; + // } else { + // aux = VDEV_AUX_EXTERNAL; + // } + + // if (guid == 0 || zpool_vdev_fault(pool->zph, guid, aux) != 0) + // ret = 1; + // } else { + if (zpool_vdev_offline(pool->zph, path, istmp) != 0) + ret = 1; + // } + return ret; +} + diff --git a/go-libzfs/zpool_vdev.go b/go-libzfs/zpool_vdev.go new file mode 100644 index 0000000..903735e --- /dev/null +++ b/go-libzfs/zpool_vdev.go @@ -0,0 +1,144 @@ +package zfs + +// #include +// #include +// #include "common.h" +// #include "zpool.h" +// #include "zfs.h" +import "C" +import ( + "fmt" + "unsafe" +) + +// Online try to set dev online +// expand - expand storage +func (pool *Pool) Online(expand bool, devs ...string) (err error) { + cflags := C.int(0) + if expand { + cflags = C.ZFS_ONLINE_EXPAND + } + for _, dev := range devs { + csdev := C.CString(dev) + var newstate VDevState + if newstate = VDevState(C.set_zpool_vdev_online(pool.list, csdev, cflags)); newstate != VDevStateUnknown { + if newstate != VDevStateHealthy { + err = fmt.Errorf( + "Device '%s' onlined, but remains in faulted state", + dev) + } + } else { + err = LastError() + } + C.free(unsafe.Pointer(csdev)) + } + return +} + +// Offline Take the device/s in offline state +func (pool *Pool) Offline(force bool, devs ...string) (err error) { + return pool.offline(false, force, devs...) +} + +// OfflineTemp Take the device/s in offline state temporary, +// upon reboot, the specified physical device reverts to its previous state. +// force - Force the device into a faulted state. +func (pool *Pool) OfflineTemp(force bool, devs ...string) (err error) { + return pool.offline(true, force, devs...) +} + +// temp - Upon reboot, the specified physical device reverts to its previous state. +// force - Force the device into a faulted state. +func (pool *Pool) offline(temp, force bool, devs ...string) (err error) { + for _, dev := range devs { + csdev := C.CString(dev) + var newstate VDevState + if newstate = VDevState(C.set_zpool_vdev_offline(pool.list, csdev, booleanT(temp), booleanT(force))); newstate != VDevStateUnknown { + if newstate != VDevStateHealthy { + err = fmt.Errorf( + "Device '%s' offlined, but remains in faulted state", + dev) + } + } else { + err = LastError() + } + C.free(unsafe.Pointer(csdev)) + } + return +} + +// Clear - Clear all errors associated with a pool or a particular device. +func (pool *Pool) Clear(device string) (err error) { + csdev := C.CString(device) + if len(device) == 0 { + csdev = nil + } + if sc := C.do_zpool_clear(pool.list, csdev, C.ZPOOL_NO_REWIND); sc != 0 { + err = fmt.Errorf("Pool clear failed") + } + C.free(unsafe.Pointer(csdev)) + return +} + +// Attach test +// func (pool *Pool) attach(props PoolProperties, devs ...string) (err error) { +// cprops := toCPoolProperties(props) +// if cprops != nil { +// defer C.nvlist_free(cprops) +// } else { +// return fmt.Errorf("Out of memory [Pool Attach properties]") +// } +// cdevs := C.alloc_cstrings(C.int(len(devs))) +// if cdevs != nil { +// defer C.free(unsafe.Pointer(cdevs)) +// } else { +// return fmt.Errorf("Out of memory [Pool Attach args]") +// } +// for i, dp := range devs { +// tmp := C.CString(dp) +// if tmp != nil { +// defer C.free(unsafe.Pointer(tmp)) +// } else { +// return fmt.Errorf("Out of memory [Pool Attach dev]") +// } +// C.strings_setat(cdevs, C.int(i), tmp) +// } +// // vroot := C.make_root_vdev(pool.list.zph, cprops, 0, 0, 0, 0, len(devs), cdevs) +// 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 +// } + +// return +// } + +// func (pool *Pool) AttachForce(devs ...string) (err error) { +// return +// } + +// func (pool *Pool) Detach(devs ...string) (err error) { +// return +// } + +// func (pool *Pool) DetachForce(devs ...string) (err error) { +// return +// } + +// func (pool *Pool) Replace(devs ...string) (err error) { +// return +// } diff --git a/go.mod b/go.mod index feeb469..1595ff0 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.15 require ( code.cloudfoundry.org/bytefmt v0.0.0-20211005130812-5bb3c17173e5 - github.com/bicomsystems/go-libzfs v0.4.0 github.com/davidscholberg/go-durationfmt v0.0.0-20170122144659-64843a2083d3 github.com/go-yaml/yaml v2.1.0+incompatible github.com/kr/pretty v0.1.0 // indirect diff --git a/main.go b/main.go index 7ebd528..172388a 100644 --- a/main.go +++ b/main.go @@ -12,7 +12,7 @@ import ( "time" "code.cloudfoundry.org/bytefmt" - zfs "github.com/bicomsystems/go-libzfs" + zfs "code.thetadev.de/ThetaDev/zfsmon/go-libzfs" "github.com/davidscholberg/go-durationfmt" "gopkg.in/yaml.v2" ) From 1fc167037567c075f71a52b36f1ad286847021cd Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sat, 16 Aug 2025 03:48:47 +0200 Subject: [PATCH 2/2] feat: add support for ZFS 2.3.2, add info flag --- go-libzfs/common.go | 4 ++++ go.mod | 2 +- go.sum | 5 +---- main.go | 11 +++++++++++ 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/go-libzfs/common.go b/go-libzfs/common.go index f881007..24475f1 100644 --- a/go-libzfs/common.go +++ b/go-libzfs/common.go @@ -189,6 +189,10 @@ const ( PoolPropBcloneUsed PoolPropBcloneSaved PoolPropBcloneRatio + PoolPropX1 + PoolPropX2 + PoolPropX3 + PoolPropX4 PoolNumProps ) diff --git a/go.mod b/go.mod index 1595ff0..9dee39d 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.15 require ( code.cloudfoundry.org/bytefmt v0.0.0-20211005130812-5bb3c17173e5 + github.com/davecgh/go-spew v1.1.1 github.com/davidscholberg/go-durationfmt v0.0.0-20170122144659-64843a2083d3 - github.com/go-yaml/yaml v2.1.0+incompatible github.com/kr/pretty v0.1.0 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index 87f65fa..82303c5 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,7 @@ code.cloudfoundry.org/bytefmt v0.0.0-20211005130812-5bb3c17173e5 h1:tM5+dn2C9xZw1RzgI6WTQW1rGqdUimKB3RFbyu4h6Hc= code.cloudfoundry.org/bytefmt v0.0.0-20211005130812-5bb3c17173e5/go.mod h1:v4VVB6oBMz/c9fRY6vZrwr5xKRWOH5NPDjQZlPk0Gbs= -github.com/bicomsystems/go-libzfs v0.4.0 h1:rezv5ZTVe31o2MbACEDrTYAeRO4rSHm70DHOTTas/yU= -github.com/bicomsystems/go-libzfs v0.4.0/go.mod h1:/ABUjxseIy72AxJV8ROgSfeZ5YA8/ZSp1mMzfDKi0Mw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidscholberg/go-durationfmt v0.0.0-20170122144659-64843a2083d3 h1:qshMBFxVjYjzI+kwvWvgoByF3uMCvnJiaK8KslWAbr8= github.com/davidscholberg/go-durationfmt v0.0.0-20170122144659-64843a2083d3/go.mod h1:M9fx6rAdHSYLKxXPgUXGgblb586CA7ceNrpu4DEc2No= @@ -10,8 +9,6 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= -github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= diff --git a/main.go b/main.go index 172388a..556d606 100644 --- a/main.go +++ b/main.go @@ -235,8 +235,19 @@ func main() { lastScanFile := flag.String("f", filepath.Join(workDir, LAST_SCAN_FILE), "File to store last scan results") logFile := flag.String("log", filepath.Join(workDir, LOG_FILE), "Log file") spaceWarn := flag.Int("space", POOL_SPACE_WARN, "Pool fill level in percent to warn at") + infoFlag := flag.Bool("info", false, "Print info and exit") flag.Parse() + if *infoFlag { + poolState, err := getPoolState(*poolName) + if err != nil { + log.Fatalf("Could not open %s. Error: %s", *poolName, err.Error()) + } + data, _ := yaml.Marshal(poolState) + fmt.Print(string(data)) + return + } + // Log file if *logFile != "" { f, err := os.OpenFile(*logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)