2017-04-14 19:26:32 +02:00
|
|
|
package zfs
|
|
|
|
|
2017-04-26 17:39:16 +02:00
|
|
|
import (
|
|
|
|
"bufio"
|
2017-05-01 20:35:04 +02:00
|
|
|
"bytes"
|
2017-04-26 17:39:16 +02:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2017-04-26 20:25:53 +02:00
|
|
|
"io"
|
|
|
|
"os/exec"
|
|
|
|
"strings"
|
2017-04-26 17:39:16 +02:00
|
|
|
)
|
2017-04-14 19:26:32 +02:00
|
|
|
|
2017-04-26 17:39:16 +02:00
|
|
|
func InitialSend(snapshot string) (io.Reader, error) {
|
|
|
|
return nil, nil
|
2017-04-14 19:26:32 +02:00
|
|
|
}
|
|
|
|
|
2017-04-26 17:39:16 +02:00
|
|
|
func IncrementalSend(from, to string) (io.Reader, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type DatasetPath []string
|
|
|
|
|
|
|
|
func (p DatasetPath) ToString() string {
|
|
|
|
return strings.Join(p, "/")
|
|
|
|
}
|
|
|
|
|
2017-04-30 16:13:05 +02:00
|
|
|
func (p DatasetPath) Empty() bool {
|
|
|
|
return len(p) == 0
|
|
|
|
}
|
|
|
|
|
|
|
|
var EmptyDatasetPath DatasetPath = []string{}
|
|
|
|
|
2017-04-26 18:36:01 +02:00
|
|
|
func NewDatasetPath(s string) (p DatasetPath, err error) {
|
2017-04-30 16:13:05 +02:00
|
|
|
if s == "" {
|
|
|
|
return EmptyDatasetPath, nil // the empty dataset path
|
|
|
|
}
|
2017-05-01 20:09:17 +02:00
|
|
|
const FORBIDDEN = "@#|\t "
|
|
|
|
if strings.ContainsAny(s, FORBIDDEN) { // TODO space may be a bit too restrictive...
|
|
|
|
return nil, errors.New(fmt.Sprintf("path '%s' contains forbidden characters (any of '%s')", s, FORBIDDEN))
|
|
|
|
}
|
2017-05-01 20:35:04 +02:00
|
|
|
return strings.Split(s, "/"), nil
|
2017-04-26 18:36:01 +02:00
|
|
|
}
|
|
|
|
|
2017-04-26 17:39:16 +02:00
|
|
|
func toDatasetPath(s string) DatasetPath {
|
2017-05-01 20:35:04 +02:00
|
|
|
p, err := NewDatasetPath(s)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return p
|
2017-04-26 17:39:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type DatasetFilter func(path DatasetPath) bool
|
|
|
|
|
|
|
|
type ZFSError struct {
|
|
|
|
Stderr []byte
|
|
|
|
WaitErr error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e ZFSError) Error() string {
|
|
|
|
return fmt.Sprintf("zfs exited with error: %s", e.WaitErr.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
var ZFS_BINARY string = "zfs"
|
|
|
|
|
2017-05-01 20:35:04 +02:00
|
|
|
func ZFSList(properties []string, zfsArgs ...string) (res [][]string, err error) {
|
2017-04-14 19:26:32 +02:00
|
|
|
|
2017-05-01 20:35:04 +02:00
|
|
|
args := make([]string, 0, 4+len(zfsArgs))
|
2017-04-30 16:13:05 +02:00
|
|
|
args = append(args,
|
2017-05-06 10:57:43 +02:00
|
|
|
"list", "-H", "-p",
|
2017-05-01 20:35:04 +02:00
|
|
|
"-o", strings.Join(properties, ","))
|
|
|
|
args = append(args, zfsArgs...)
|
2017-04-30 16:13:05 +02:00
|
|
|
|
|
|
|
cmd := exec.Command(ZFS_BINARY, args...)
|
2017-04-26 17:39:16 +02:00
|
|
|
|
|
|
|
var stdout io.Reader
|
2017-05-01 20:35:04 +02:00
|
|
|
stderr := bytes.NewBuffer(make([]byte, 0, 1024))
|
|
|
|
cmd.Stderr = stderr
|
2017-04-26 17:39:16 +02:00
|
|
|
|
|
|
|
if stdout, err = cmd.StdoutPipe(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = cmd.Start(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
s := bufio.NewScanner(stdout)
|
|
|
|
buf := make([]byte, 1024)
|
|
|
|
s.Buffer(buf, 0)
|
|
|
|
|
2017-05-01 20:35:04 +02:00
|
|
|
res = make([][]string, 0)
|
2017-04-26 17:39:16 +02:00
|
|
|
|
|
|
|
for s.Scan() {
|
2017-05-01 20:35:04 +02:00
|
|
|
fields := strings.SplitN(s.Text(), "\t", len(properties))
|
|
|
|
|
|
|
|
if len(fields) != len(properties) {
|
2017-04-26 17:39:16 +02:00
|
|
|
err = errors.New("unexpected output")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-05-01 20:35:04 +02:00
|
|
|
res = append(res, fields)
|
2017-04-26 17:39:16 +02:00
|
|
|
}
|
|
|
|
|
2017-04-26 20:25:53 +02:00
|
|
|
if waitErr := cmd.Wait(); waitErr != nil {
|
2017-04-26 17:39:16 +02:00
|
|
|
err := ZFSError{
|
2017-05-01 20:35:04 +02:00
|
|
|
Stderr: stderr.Bytes(),
|
2017-04-26 17:39:16 +02:00
|
|
|
WaitErr: waitErr,
|
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|