Compare commits

...

2 commits

19 changed files with 4577 additions and 7 deletions

3
go-libzfs/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.gitconfig
*.sublime-*
go-libzfs.test

27
go-libzfs/LICENSE.md Normal file
View file

@ -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.

77
go-libzfs/README.md Normal file
View file

@ -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.

77
go-libzfs/common.c Normal file
View file

@ -0,0 +1,77 @@
#include <libzfs.h>
#include <memory.h>
#include <string.h>
#include <stdio.h>
#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);
}

523
go-libzfs/common.go Normal file
View file

@ -0,0 +1,523 @@
// 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 <stdlib.h>
#include <libzfs.h>
#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
PoolPropX1
PoolPropX2
PoolPropX3
PoolPropX4
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",
}

43
go-libzfs/common.h Normal file
View file

@ -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);

370
go-libzfs/sendrecv.go Normal file
View file

@ -0,0 +1,370 @@
package zfs
// #include <stdlib.h>
// #include <libzfs.h>
// #include "common.h"
// #include "zpool.h"
// #include "zfs.h"
// #include <memory.h>
// #include <string.h>
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
}

50
go-libzfs/sort.go Normal file
View file

@ -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)
}

266
go-libzfs/zfs.c Normal file
View file

@ -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 <libzfs.h>
#include <memory.h>
#include <string.h>
#include <stdio.h>
#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;
}

875
go-libzfs/zfs.go Normal file
View file

@ -0,0 +1,875 @@
package zfs
// #include <stdlib.h>
// #include <libzfs.h>
// #include "common.h"
// #include "zpool.h"
// #include "zfs.h"
import "C"
import (
"errors"
"fmt"
"path"
"sort"
"strings"
"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
}

146
go-libzfs/zfs.h Normal file
View file

@ -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 */

563
go-libzfs/zpool.c Normal file
View file

@ -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 <libzfs.h>
#include <libzfs/sys/zfs_context.h>
#include <libzutil.h>
#include <thread_pool.h>
#include <memory.h>
#include <string.h>
#include <stdio.h>
#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, &notpresent);
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);
}
}

1197
go-libzfs/zpool.go Normal file

File diff suppressed because it is too large Load diff

165
go-libzfs/zpool.h Normal file
View file

@ -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 */

37
go-libzfs/zpool_vdev.c Normal file
View file

@ -0,0 +1,37 @@
#include <libzfs.h>
#include <memory.h>
#include <string.h>
#include <stdio.h>
#include <sys/fs/zfs.h>
#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;
}

144
go-libzfs/zpool_vdev.go Normal file
View file

@ -0,0 +1,144 @@
package zfs
// #include <stdlib.h>
// #include <libzfs.h>
// #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
// }

3
go.mod
View file

@ -4,9 +4,8 @@ go 1.15
require ( require (
code.cloudfoundry.org/bytefmt v0.0.0-20211005130812-5bb3c17173e5 code.cloudfoundry.org/bytefmt v0.0.0-20211005130812-5bb3c17173e5
github.com/bicomsystems/go-libzfs v0.4.0 github.com/davecgh/go-spew v1.1.1
github.com/davidscholberg/go-durationfmt v0.0.0-20170122144659-64843a2083d3 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 github.com/kr/pretty v0.1.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0

5
go.sum
View file

@ -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 h1:tM5+dn2C9xZw1RzgI6WTQW1rGqdUimKB3RFbyu4h6Hc=
code.cloudfoundry.org/bytefmt v0.0.0-20211005130812-5bb3c17173e5/go.mod h1:v4VVB6oBMz/c9fRY6vZrwr5xKRWOH5NPDjQZlPk0Gbs= 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.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/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 h1:qshMBFxVjYjzI+kwvWvgoByF3uMCvnJiaK8KslWAbr8=
github.com/davidscholberg/go-durationfmt v0.0.0-20170122144659-64843a2083d3/go.mod h1:M9fx6rAdHSYLKxXPgUXGgblb586CA7ceNrpu4DEc2No= 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 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 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-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.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/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=

13
main.go
View file

@ -12,7 +12,7 @@ import (
"time" "time"
"code.cloudfoundry.org/bytefmt" "code.cloudfoundry.org/bytefmt"
zfs "github.com/bicomsystems/go-libzfs" zfs "code.thetadev.de/ThetaDev/zfsmon/go-libzfs"
"github.com/davidscholberg/go-durationfmt" "github.com/davidscholberg/go-durationfmt"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
@ -235,8 +235,19 @@ func main() {
lastScanFile := flag.String("f", filepath.Join(workDir, LAST_SCAN_FILE), "File to store last scan results") 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") 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") 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() 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 // Log file
if *logFile != "" { if *logFile != "" {
f, err := os.OpenFile(*logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) f, err := os.OpenFile(*logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)