Initial working version
Summary: * Logging is still bad * test output in a lot of placed * FIXMEs every where Test Plan: None, just review Differential Revision: https://phabricator.cschwarz.com/D2
This commit is contained in:
+14
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"io"
|
||||
)
|
||||
|
||||
type fsbyCreateTXG []FilesystemVersion
|
||||
@@ -245,6 +246,19 @@ func IsPlaceholder(p *DatasetPath, placeholderPropertyValue string) (isPlacehold
|
||||
return
|
||||
}
|
||||
|
||||
// for nonexistent FS, isPlaceholder == false && err == nil
|
||||
func ZFSIsPlaceholderFilesystem(p *DatasetPath) (isPlaceholder bool, err error) {
|
||||
props, err := ZFSGet(p, []string{ZREPL_PLACEHOLDER_PROPERTY_NAME})
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
// interpret this as an early exit of the zfs binary due to the fs not existing
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
return false, err
|
||||
}
|
||||
isPlaceholder, _ = IsPlaceholder(p, props.Get(ZREPL_PLACEHOLDER_PROPERTY_NAME))
|
||||
return
|
||||
}
|
||||
|
||||
func ZFSCreatePlaceholderFilesystem(p *DatasetPath) (err error) {
|
||||
v := PlaceholderPropertyValue(p)
|
||||
cmd := exec.Command(ZFS_BINARY, "create",
|
||||
|
||||
+41
-6
@@ -10,26 +10,56 @@ type DatasetFilter interface {
|
||||
}
|
||||
|
||||
func ZFSListMapping(filter DatasetFilter) (datasets []*DatasetPath, err error) {
|
||||
res, err := ZFSListMappingProperties(filter, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
datasets = make([]*DatasetPath, len(res))
|
||||
for i, r := range res {
|
||||
datasets[i] = r.Path
|
||||
}
|
||||
return datasets, nil
|
||||
}
|
||||
|
||||
type ZFSListMappingPropertiesResult struct {
|
||||
Path *DatasetPath
|
||||
// Guaranteed to have the same length as properties in the originating call
|
||||
Fields []string
|
||||
}
|
||||
|
||||
// properties must not contain 'name'
|
||||
func ZFSListMappingProperties(filter DatasetFilter, properties []string) (datasets []ZFSListMappingPropertiesResult, err error) {
|
||||
|
||||
if filter == nil {
|
||||
panic("filter must not be nil")
|
||||
}
|
||||
|
||||
for _, p := range properties {
|
||||
if p == "name" {
|
||||
panic("properties must not contain 'name'")
|
||||
}
|
||||
}
|
||||
newProps := make([]string, len(properties) + 1)
|
||||
newProps[0] = "name"
|
||||
copy(newProps[1:], properties)
|
||||
properties = newProps
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
rchan := make(chan ZFSListResult)
|
||||
go ZFSListChan(ctx, rchan, []string{"name"}, "-r", "-t", "filesystem,volume")
|
||||
|
||||
datasets = make([]*DatasetPath, 0)
|
||||
go ZFSListChan(ctx, rchan, properties, "-r", "-t", "filesystem,volume")
|
||||
|
||||
datasets = make([]ZFSListMappingPropertiesResult, 0)
|
||||
for r := range rchan {
|
||||
|
||||
if r.err != nil {
|
||||
err = r.err
|
||||
if r.Err != nil {
|
||||
err = r.Err
|
||||
return
|
||||
}
|
||||
|
||||
var path *DatasetPath
|
||||
if path, err = NewDatasetPath(r.fields[0]); err != nil {
|
||||
if path, err = NewDatasetPath(r.Fields[0]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -38,10 +68,15 @@ func ZFSListMapping(filter DatasetFilter) (datasets []*DatasetPath, err error) {
|
||||
return nil, fmt.Errorf("error calling filter: %s", filterErr)
|
||||
}
|
||||
if pass {
|
||||
datasets = append(datasets, path)
|
||||
datasets = append(datasets, ZFSListMappingPropertiesResult{
|
||||
Path: path,
|
||||
Fields: r.Fields[1:],
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -103,3 +103,18 @@ func ParseResumeToken(ctx context.Context, token string) (*ResumeToken, error) {
|
||||
return rt, nil
|
||||
|
||||
}
|
||||
|
||||
func ZFSGetReceiveResumeToken(fs *DatasetPath) (string, error) {
|
||||
const prop_receive_resume_token = "receive_resume_token"
|
||||
props, err := ZFSGet(fs, []string{prop_receive_resume_token})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res := props.m[prop_receive_resume_token]
|
||||
if res == "-" {
|
||||
return "", nil
|
||||
} else {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+34
-23
@@ -9,6 +9,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"io"
|
||||
)
|
||||
|
||||
type VersionType string
|
||||
@@ -33,6 +34,26 @@ func (t VersionType) String() string {
|
||||
return string(t)
|
||||
}
|
||||
|
||||
func DecomposeVersionString(v string) (fs string, versionType VersionType, name string, err error) {
|
||||
if len(v) < 3 {
|
||||
err = errors.New(fmt.Sprintf("snapshot or bookmark name implausibly short: %s", v))
|
||||
return
|
||||
}
|
||||
|
||||
snapSplit := strings.SplitN(v, "@", 2)
|
||||
bookmarkSplit := strings.SplitN(v, "#", 2)
|
||||
if len(snapSplit)*len(bookmarkSplit) != 2 {
|
||||
err = errors.New(fmt.Sprintf("dataset cannot be snapshot and bookmark at the same time: %s", v))
|
||||
return
|
||||
}
|
||||
|
||||
if len(snapSplit) == 2 {
|
||||
return snapSplit[0], Snapshot, snapSplit[1], nil
|
||||
} else {
|
||||
return bookmarkSplit[0], Bookmark, bookmarkSplit[1], nil
|
||||
}
|
||||
}
|
||||
|
||||
type FilesystemVersion struct {
|
||||
Type VersionType
|
||||
|
||||
@@ -63,7 +84,7 @@ func (v FilesystemVersion) ToAbsPath(p *DatasetPath) string {
|
||||
}
|
||||
|
||||
type FilesystemVersionFilter interface {
|
||||
Filter(fsv FilesystemVersion) (accept bool, err error)
|
||||
Filter(t VersionType, name string) (accept bool, err error)
|
||||
}
|
||||
|
||||
func ZFSListFilesystemVersions(fs *DatasetPath, filter FilesystemVersionFilter) (res []FilesystemVersion, err error) {
|
||||
@@ -82,31 +103,21 @@ func ZFSListFilesystemVersions(fs *DatasetPath, filter FilesystemVersionFilter)
|
||||
|
||||
res = make([]FilesystemVersion, 0)
|
||||
for listResult := range listResults {
|
||||
if listResult.err != nil {
|
||||
return nil, listResult.err
|
||||
if listResult.Err != nil {
|
||||
if listResult.Err == io.ErrUnexpectedEOF {
|
||||
// Since we specified the fs on the command line, we'll treat this like the filesystem doesn't exist
|
||||
return []FilesystemVersion{}, nil
|
||||
}
|
||||
return nil, listResult.Err
|
||||
}
|
||||
|
||||
line := listResult.fields
|
||||
|
||||
if len(line[0]) < 3 {
|
||||
err = errors.New(fmt.Sprintf("snapshot or bookmark name implausibly short: %s", line[0]))
|
||||
return
|
||||
}
|
||||
|
||||
snapSplit := strings.SplitN(line[0], "@", 2)
|
||||
bookmarkSplit := strings.SplitN(line[0], "#", 2)
|
||||
if len(snapSplit)*len(bookmarkSplit) != 2 {
|
||||
err = errors.New(fmt.Sprintf("dataset cannot be snapshot and bookmark at the same time: %s", line[0]))
|
||||
return
|
||||
}
|
||||
line := listResult.Fields
|
||||
|
||||
var v FilesystemVersion
|
||||
if len(snapSplit) == 2 {
|
||||
v.Name = snapSplit[1]
|
||||
v.Type = Snapshot
|
||||
} else {
|
||||
v.Name = bookmarkSplit[1]
|
||||
v.Type = Bookmark
|
||||
|
||||
_, v.Type, v.Name, err = DecomposeVersionString(line[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if v.Guid, err = strconv.ParseUint(line[1], 10, 64); err != nil {
|
||||
@@ -129,7 +140,7 @@ func ZFSListFilesystemVersions(fs *DatasetPath, filter FilesystemVersionFilter)
|
||||
|
||||
accept := true
|
||||
if filter != nil {
|
||||
accept, err = filter.Filter(v)
|
||||
accept, err = filter.Filter(v.Type, v.Name)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error executing filter: %s", err)
|
||||
return nil, err
|
||||
|
||||
+106
-9
@@ -189,13 +189,13 @@ func ZFSList(properties []string, zfsArgs ...string) (res [][]string, err error)
|
||||
}
|
||||
|
||||
type ZFSListResult struct {
|
||||
fields []string
|
||||
err error
|
||||
Fields []string
|
||||
Err error
|
||||
}
|
||||
|
||||
// ZFSListChan executes `zfs list` and sends the results to the `out` channel.
|
||||
// The `out` channel is always closed by ZFSListChan:
|
||||
// If an error occurs, it is closed after sending a result with the err field set.
|
||||
// If an error occurs, it is closed after sending a result with the Err field set.
|
||||
// If no error occurs, it is just closed.
|
||||
// If the operation is cancelled via context, the channel is just closed.
|
||||
//
|
||||
@@ -250,15 +250,56 @@ func ZFSListChan(ctx context.Context, out chan ZFSListResult, properties []strin
|
||||
return
|
||||
}
|
||||
|
||||
func ZFSSend(fs *DatasetPath, from, to *FilesystemVersion) (stream io.Reader, err error) {
|
||||
func validateRelativeZFSVersion(s string) error {
|
||||
if len(s) <= 1 {
|
||||
return errors.New("version must start with a delimiter char followed by at least one character")
|
||||
}
|
||||
if !(s[0] == '#' || s[0] == '@') {
|
||||
return errors.New("version name starts with invalid delimiter char")
|
||||
}
|
||||
// FIXME whitespace check...
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateZFSFilesystem(fs string) error {
|
||||
if len(fs) < 1 {
|
||||
return errors.New("filesystem path must have length > 0")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func absVersion(fs, v string) (full string, err error) {
|
||||
if err := validateZFSFilesystem(fs); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := validateRelativeZFSVersion(v); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s%s", fs, v), nil
|
||||
}
|
||||
|
||||
func ZFSSend(fs string, from, to string) (stream io.Reader, err error) {
|
||||
|
||||
fromV, err := absVersion(fs, from)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
toV := ""
|
||||
if to != "" {
|
||||
toV, err = absVersion(fs, to)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
args := make([]string, 0)
|
||||
args = append(args, "send")
|
||||
|
||||
if to == nil { // Initial
|
||||
args = append(args, from.ToAbsPath(fs))
|
||||
if toV == "" { // Initial
|
||||
args = append(args, fromV)
|
||||
} else {
|
||||
args = append(args, "-i", from.ToAbsPath(fs), to.ToAbsPath(fs))
|
||||
args = append(args, "-i", fromV, toV)
|
||||
}
|
||||
|
||||
stream, err = util.RunIOCommand(ZFS_BINARY, args...)
|
||||
@@ -266,14 +307,18 @@ func ZFSSend(fs *DatasetPath, from, to *FilesystemVersion) (stream io.Reader, er
|
||||
return
|
||||
}
|
||||
|
||||
func ZFSRecv(fs *DatasetPath, stream io.Reader, additionalArgs ...string) (err error) {
|
||||
func ZFSRecv(fs string, stream io.Reader, additionalArgs ...string) (err error) {
|
||||
|
||||
if err := validateZFSFilesystem(fs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := make([]string, 0)
|
||||
args = append(args, "recv")
|
||||
if len(args) > 0 {
|
||||
args = append(args, additionalArgs...)
|
||||
}
|
||||
args = append(args, fs.ToString())
|
||||
args = append(args, fs)
|
||||
|
||||
cmd := exec.Command(ZFS_BINARY, args...)
|
||||
|
||||
@@ -304,6 +349,27 @@ func ZFSRecv(fs *DatasetPath, stream io.Reader, additionalArgs ...string) (err e
|
||||
return nil
|
||||
}
|
||||
|
||||
func ZFSRecvWriter(fs *DatasetPath, additionalArgs ...string) (io.WriteCloser, error) {
|
||||
|
||||
args := make([]string, 0)
|
||||
args = append(args, "recv")
|
||||
if len(args) > 0 {
|
||||
args = append(args, additionalArgs...)
|
||||
}
|
||||
args = append(args, fs.ToString())
|
||||
|
||||
cmd, err := util.NewIOCommand(ZFS_BINARY, args, 1024)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = cmd.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cmd.Stdin, nil
|
||||
}
|
||||
|
||||
type ZFSProperties struct {
|
||||
m map[string]string
|
||||
}
|
||||
@@ -316,6 +382,10 @@ func (p *ZFSProperties) Set(key, val string) {
|
||||
p.m[key] = val
|
||||
}
|
||||
|
||||
func (p *ZFSProperties) Get(key string) string {
|
||||
return p.m[key]
|
||||
}
|
||||
|
||||
func (p *ZFSProperties) appendArgs(args *[]string) (err error) {
|
||||
for prop, val := range p.m {
|
||||
if strings.Contains(prop, "=") {
|
||||
@@ -355,6 +425,33 @@ func ZFSSet(fs *DatasetPath, props *ZFSProperties) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func ZFSGet(fs *DatasetPath, props []string) (*ZFSProperties, error) {
|
||||
args := []string{"get", "-Hp", "-o", "property,value", strings.Join(props, ","), fs.ToString()}
|
||||
|
||||
cmd := exec.Command(ZFS_BINARY, args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o := string(output)
|
||||
lines := strings.Split(o, "\n")
|
||||
if len(lines) < 1 || // account for newlines
|
||||
len(lines)-1 != len(props) {
|
||||
return nil, fmt.Errorf("zfs get did not return the number of expected property values")
|
||||
}
|
||||
res := &ZFSProperties{
|
||||
make(map[string]string, len(lines)),
|
||||
}
|
||||
for _, line := range lines[:len(lines)-1] {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) != 2 {
|
||||
return nil, fmt.Errorf("zfs get did not return property value pairs")
|
||||
}
|
||||
res.m[fields[0]] = fields[1]
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func ZFSDestroy(dataset string) (err error) {
|
||||
|
||||
cmd := exec.Command(ZFS_BINARY, "destroy", dataset)
|
||||
|
||||
Reference in New Issue
Block a user