Compare commits
2 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
1fc1670375 |
|||
|
c88f3a05b8 |
19 changed files with 4577 additions and 7 deletions
3
go-libzfs/.gitignore
vendored
Normal file
3
go-libzfs/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
.gitconfig
|
||||||
|
*.sublime-*
|
||||||
|
go-libzfs.test
|
||||||
27
go-libzfs/LICENSE.md
Normal file
27
go-libzfs/LICENSE.md
Normal 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
77
go-libzfs/README.md
Normal 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
|
||||||
|
|
||||||
|
[](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
77
go-libzfs/common.c
Normal 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
523
go-libzfs/common.go
Normal 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
43
go-libzfs/common.h
Normal 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
370
go-libzfs/sendrecv.go
Normal 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
50
go-libzfs/sort.go
Normal 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
266
go-libzfs/zfs.c
Normal 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
875
go-libzfs/zfs.go
Normal 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
146
go-libzfs/zfs.h
Normal 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
563
go-libzfs/zpool.c
Normal 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, ¬present);
|
||||||
|
if (r == 0 || notpresent != 0) {
|
||||||
|
if ( 0 != nvlist_lookup_string(nv, ZPOOL_CONFIG_PATH, &path) ) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t get_vdev_is_log(nvlist_ptr nv) {
|
||||||
|
uint64_t islog = B_FALSE;
|
||||||
|
nvlist_lookup_uint64(nv, ZPOOL_CONFIG_IS_LOG, &islog);
|
||||||
|
return islog;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// return
|
||||||
|
uint64_t get_zpool_state(nvlist_ptr nv) {
|
||||||
|
uint64_t state = 0;
|
||||||
|
nvlist_lookup_uint64(nv, ZPOOL_CONFIG_POOL_STATE, &state);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t get_zpool_guid(nvlist_ptr nv) {
|
||||||
|
uint64_t guid = 0;
|
||||||
|
nvlist_lookup_uint64(nv, ZPOOL_CONFIG_POOL_GUID, &guid);
|
||||||
|
return guid;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *get_zpool_name(nvlist_ptr nv) {
|
||||||
|
const char *name = NULL;
|
||||||
|
if (0 != nvlist_lookup_string(nv, ZPOOL_CONFIG_POOL_NAME, &name)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *get_zpool_comment(nvlist_ptr nv) {
|
||||||
|
const char *comment = NULL;
|
||||||
|
if (0 != nvlist_lookup_string(nv, ZPOOL_CONFIG_COMMENT, &comment)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
nvlist_ptr get_zpool_vdev_tree(nvlist_ptr nv) {
|
||||||
|
nvlist_ptr vdev_tree = NULL;
|
||||||
|
if ( 0 != nvlist_lookup_nvlist(nv, ZPOOL_CONFIG_VDEV_TREE, &vdev_tree) ) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return vdev_tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
nvlist_ptr go_zpool_search_import(libzfs_handle_ptr zfsh, int paths, char **path, boolean_t do_scan) {
|
||||||
|
importargs_t idata;
|
||||||
|
memset(&idata, 0, sizeof(importargs_t));
|
||||||
|
nvlist_ptr pools = NULL;
|
||||||
|
idata.path = path;
|
||||||
|
idata.paths = paths;
|
||||||
|
// idata.scan = 0;
|
||||||
|
|
||||||
|
tpool_t *t;
|
||||||
|
t = tpool_create(1, 5 * sysconf(_SC_NPROCESSORS_ONLN), 0, NULL);
|
||||||
|
if (t == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
libpc_handle_t lpch = {
|
||||||
|
.lpc_lib_handle = zfsh,
|
||||||
|
.lpc_ops = &libzfs_config_ops,
|
||||||
|
.lpc_printerr = B_TRUE
|
||||||
|
};
|
||||||
|
pools = zpool_search_import(&lpch, &idata);
|
||||||
|
|
||||||
|
tpool_wait(t);
|
||||||
|
tpool_destroy(t);
|
||||||
|
return pools;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int do_zpool_clear(zpool_list_t *pool, const char *device, u_int32_t load_policy) {
|
||||||
|
nvlist_t *policy = NULL;
|
||||||
|
int ret = 0;
|
||||||
|
if (nvlist_alloc(&policy, NV_UNIQUE_NAME, 0) != 0 ||
|
||||||
|
nvlist_add_uint32(policy, ZPOOL_LOAD_POLICY, load_policy) != 0)
|
||||||
|
return (1);
|
||||||
|
|
||||||
|
if (zpool_clear(pool->zph, device, policy) != 0)
|
||||||
|
ret = 1;
|
||||||
|
|
||||||
|
nvlist_free(policy);
|
||||||
|
|
||||||
|
return (ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
void collect_zpool_leaves(zpool_handle_t *zhp, nvlist_t *nvroot, nvlist_t *nv){
|
||||||
|
uint_t children = 0;
|
||||||
|
nvlist_t **child;
|
||||||
|
uint_t i;
|
||||||
|
|
||||||
|
(void) nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_CHILDREN,
|
||||||
|
&child, &children);
|
||||||
|
|
||||||
|
if (children == 0) {
|
||||||
|
char *path = zpool_vdev_name(libzfsHandle, zhp, nvroot,
|
||||||
|
VDEV_NAME_PATH);
|
||||||
|
|
||||||
|
if (strcmp(path, VDEV_TYPE_INDIRECT) != 0)
|
||||||
|
fnvlist_add_boolean(nv, path);
|
||||||
|
|
||||||
|
free(path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < children; i++) {
|
||||||
|
collect_zpool_leaves(zhp, child[i], nv);
|
||||||
|
}
|
||||||
|
}
|
||||||
1197
go-libzfs/zpool.go
Normal file
1197
go-libzfs/zpool.go
Normal file
File diff suppressed because it is too large
Load diff
165
go-libzfs/zpool.h
Normal file
165
go-libzfs/zpool.h
Normal 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
37
go-libzfs/zpool_vdev.c
Normal 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
144
go-libzfs/zpool_vdev.go
Normal 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
3
go.mod
|
|
@ -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
5
go.sum
|
|
@ -1,8 +1,7 @@
|
||||||
code.cloudfoundry.org/bytefmt v0.0.0-20211005130812-5bb3c17173e5 h1:tM5+dn2C9xZw1RzgI6WTQW1rGqdUimKB3RFbyu4h6Hc=
|
code.cloudfoundry.org/bytefmt v0.0.0-20211005130812-5bb3c17173e5 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
13
main.go
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue