mirror of
https://github.com/zrepl/zrepl.git
synced 2024-11-22 16:34:32 +01:00
d50e553ebb
We assumed that `zfs recv -F FS` would basically replace FS inplace, leaving its children untouched. That is in fact not the case, it only works if `zfs send -R` is set, which we don't do. Thus, implement the required functionality manually. This solves a `zfs recv` error that would occur when a filesystem previously created as placeholder on the receiving side becomes a non-placeholder filesystem (likely due to config change on the sending side): zfs send pool1/foo@1 | zfs recv -F pool1/bar cannot receive new filesystem stream: destination has snapshots (eg. pool1/bar) must destroy them to overwrite it
113 lines
3.1 KiB
Go
113 lines
3.1 KiB
Go
package zfs
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha512"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"os/exec"
|
|
)
|
|
|
|
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 ZFSListFilesystemState() (localState map[string]FilesystemState, err error) {
|
|
|
|
var actual [][]string
|
|
if actual, err = ZFSList([]string{"name", ZREPL_PLACEHOLDER_PROPERTY_NAME}, "-t", "filesystem,volume"); err != nil {
|
|
return
|
|
}
|
|
|
|
localState = make(map[string]FilesystemState, len(actual))
|
|
for _, e := range actual {
|
|
dp, err := NewDatasetPath(e[0])
|
|
if err != nil {
|
|
return 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
|
|
}
|
|
|
|
// for nonexistent FS, isPlaceholder == false && err == nil
|
|
func ZFSIsPlaceholderFilesystem(p *DatasetPath) (isPlaceholder bool, err error) {
|
|
props, err := zfsGet(p.ToString(), []string{ZREPL_PLACEHOLDER_PROPERTY_NAME}, sourceAny)
|
|
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",
|
|
"-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
|
|
}
|
|
|
|
func ZFSSetNoPlaceholder(p *DatasetPath) error {
|
|
props := NewZFSProperties()
|
|
props.Set(ZREPL_PLACEHOLDER_PROPERTY_NAME, "off")
|
|
return zfsSet(p.ToString(), props)
|
|
} |