Implement placeholder filesystems.

Note the docs on the placeholder user property introduced with this
commit. The solution is not really satisfying but couldn't think of a
better one OTOMH
This commit is contained in:
Christian Schwarz
2017-07-30 14:56:16 +02:00
parent 8eb4a2ba44
commit 4732fdd4cc
2 changed files with 117 additions and 24 deletions

View File

@ -1,6 +1,11 @@
package zfs
import (
"bytes"
"crypto/sha512"
"encoding/hex"
"fmt"
"os/exec"
"sort"
)
@ -158,23 +163,86 @@ outer:
return
}
const ZREPL_PLACEHOLDER_PROPERTY_NAME string = "zrepl:placeholder"
type FilesystemState struct {
Placeholder bool
// TODO extend with resume token when that feature is finally added
}
// A somewhat efficient way to determine if a filesystem exists on this host.
// Particularly useful if exists is called more than once (will only fork exec once and cache the result)
func ZFSListFilesystemExists() (exists func(p DatasetPath) bool, err error) {
func ZFSListFilesystemState() (localState map[string]FilesystemState, err error) {
var actual [][]string
if actual, err = ZFSList([]string{"name"}, "-t", "filesystem,volume"); err != nil {
if actual, err = ZFSList([]string{"name", ZREPL_PLACEHOLDER_PROPERTY_NAME}, "-t", "filesystem,volume"); err != nil {
return
}
filesystems := make(map[string]bool, len(actual))
localState = make(map[string]FilesystemState, len(actual))
for _, e := range actual {
filesystems[e[0]] = true
}
exists = func(p DatasetPath) bool {
return filesystems[p.ToString()]
dp, err := NewDatasetPath(e[0])
if err != nil {
fmt.Errorf("ZFS does not return parseable dataset path: %s", e[0])
}
placeholder, _ := IsPlaceholder(dp, e[1])
localState[e[0]] = FilesystemState{
placeholder,
}
}
return
}
// Computes the value for the ZREPL_PLACEHOLDER_PROPERTY_NAME ZFS user property
// to mark the given DatasetPath p as a placeholder
//
// We cannot simply use booleans here since user properties are always
// inherited.
//
// We hash the DatasetPath and use it to check for a given path if it is the
// one originally marked as placeholder.
//
// However, this prohibits moving datasets around via `zfs rename`. The
// placeholder attribute must be re-computed for the dataset path after the
// move.
//
// TODO better solution available?
func PlaceholderPropertyValue(p DatasetPath) string {
ps := []byte(p.ToString())
sum := sha512.Sum512_256(ps)
return hex.EncodeToString(sum[:])
}
func IsPlaceholder(p DatasetPath, placeholderPropertyValue string) (isPlaceholder bool, err error) {
expected := PlaceholderPropertyValue(p)
isPlaceholder = expected == placeholderPropertyValue
if !isPlaceholder {
err = fmt.Errorf("expected %s, has %s", expected, placeholderPropertyValue)
}
return
}
func ZFSCreatePlaceholderFilesystem(p DatasetPath) (err error) {
v := PlaceholderPropertyValue(p)
cmd := exec.Command(ZFS_BINARY, "create",
"-o", fmt.Sprintf("%s=%s", ZREPL_PLACEHOLDER_PROPERTY_NAME, v),
"-o", "mountpoint=none",
p.ToString())
stderr := bytes.NewBuffer(make([]byte, 0, 1024))
cmd.Stderr = stderr
if err = cmd.Start(); err != nil {
return err
}
if err = cmd.Wait(); err != nil {
err = ZFSError{
Stderr: stderr.Bytes(),
WaitErr: err,
}
}
return
}