diff --git a/zfs.go b/zfs.go index 4e5087f..5f434f5 100644 --- a/zfs.go +++ b/zfs.go @@ -68,7 +68,7 @@ const ( ) // DestroyFlag is the options flag passed to Destroy -type DestroyFlag int +type DestroyFlag int64 // Valid destroy options const ( @@ -79,6 +79,17 @@ const ( DestroyForceUmount = 1 << iota ) +// SendFlag is the options flags passed to SendSnapshot +type SendFlag int64 + +// Valid send options +const ( + SendDefault SendFlag = 1 << iota + IncrementalStream = 1 << iota + IncrementalPackage = 1 << iota + ReplicationStream = 1 << iota +) + // InodeChange represents a change as reported by Diff type InodeChange struct { Change ChangeType @@ -233,16 +244,60 @@ func ReceiveSnapshot(input io.Reader, name string) (*Dataset, error) { return GetDataset(name) } +// ReceiveSnapshotRollback forces a rollback of the file system to the most recent +// snapshot before the receive is initiated. After, receives a ZFS stream from the +// input io.Reader like ReceiveSnapshot does. +func ReceiveSnapshotRollback(input io.Reader, name string, overwrite bool) (*Dataset, error) { + c := command{Command: "zfs", Stdin: input} + _, err := c.Run("receive", "-F", name) + if err != nil { + return nil, err + } + return GetDataset(name) +} + // SendSnapshot sends a ZFS stream of a snapshot to the input io.Writer. // An error will be returned if the input dataset is not of snapshot type. -func (d *Dataset) SendSnapshot(output io.Writer) error { +func (d *Dataset) SendSnapshot(output io.Writer, flags SendFlag) error { if d.Type != DatasetSnapshot { return errors.New("can only send snapshots") } + c := command{Command: "zfs", Stdout: output} + + // Flags for SendSnapshot + if flags&ReplicationStream !=0 { + _, err := c.Run("send", "-R", d.Name) + return err + } else { + _, err := c.Run("send", d.Name) + return err + } +} + +// SendSnapshotIncremental sends a ZFS incremental stream to the input io.Writer. +// Includes options -i and -I to send an incremental stream or a stream package respectively. +func SendSnapshotIncremental(output io.Writer, d1 *Dataset, d2 *Dataset, replication bool, flags SendFlag) error { + if d1.Type != DatasetSnapshot || d2.Type != DatasetSnapshot { + return errors.New("can only send snapshots") + } + // Flags for SendSnapshot + option := "" + if flags&IncrementalStream !=0 { + option = "-i" + } + if flags&IncrementalPackage !=0 { + option = "-I" + } c := command{Command: "zfs", Stdout: output} - _, err := c.Run("send", d.Name) - return err + if replication == true { + stream := "-R" + _, err := c.Run("send", stream, option, d1.Name, d2.Name) + return err + } else { + _, err := c.Run("send", option, d1.Name, d2.Name) + return err + } } // CreateVolume creates a new ZFS volume with the specified name, size, and diff --git a/zfs_test.go b/zfs_test.go index 1a5acc4..615920f 100644 --- a/zfs_test.go +++ b/zfs_test.go @@ -255,7 +255,7 @@ func TestSendSnapshot(t *testing.T) { ok(t, err) defer os.Remove(file.Name()) - err = s.SendSnapshot(file) + err = s.SendSnapshot(file, zfs.SendDefault) ok(t, err) ok(t, s.Destroy(zfs.DestroyDefault)) @@ -264,6 +264,39 @@ func TestSendSnapshot(t *testing.T) { }) } +func TestSendSnapshotIncremental(t *testing.T) { + zpoolTest(t, func() { + f, err := zfs.CreateFilesystem("test/snapshot-test", nil) + ok(t, err) + + filesystems, err := zfs.Filesystems("") + ok(t, err) + + for _, filesystem := range filesystems { + equals(t, zfs.DatasetFilesystem, filesystem.Type) + } + + s1, err := f.Snapshot("snap1", false) + ok(t, err) + s2, err := f.Snapshot("snap2", false) + ok(t, err) + + file, _ := ioutil.TempFile("/tmp/", "zfs-") + defer file.Close() + err = file.Truncate(pow2(30)) + ok(t, err) + defer os.Remove(file.Name()) + + err = zfs.SendSnapshotIncremental(file, s1, s2, true, zfs.IncrementalStream) + ok(t, err) + + ok(t, s2.Destroy(zfs.DestroyDefault)) + ok(t, s1.Destroy(zfs.DestroyDefault)) + + ok(t, f.Destroy(zfs.DestroyDefault)) + }) +} + func TestChildren(t *testing.T) { zpoolTest(t, func() { f, err := zfs.CreateFilesystem("test/snapshot-test", nil) @@ -350,7 +383,7 @@ func TestDiff(t *testing.T) { snapshot, err := fs.Snapshot("snapshot", false) ok(t, err) - unicodeFile, err := os.Create(filepath.Join(fs.Mountpoint, "i ❤ unicode")) + unicodeFile, err := os.Create(filepath.Join(fs.Mountpoint, "i_love_unicode")) ok(t, err) err = os.Rename(movedFile.Name(), movedFile.Name()+"-new") @@ -377,7 +410,7 @@ func TestDiff(t *testing.T) { equals(t, zfs.File, inodeChanges[2].Type) equals(t, zfs.Renamed, inodeChanges[2].Change) - equals(t, "/test/origin/i ❤ unicode", inodeChanges[3].Path) + equals(t, "/test/origin/i_love_unicode", inodeChanges[3].Path) equals(t, zfs.File, inodeChanges[3].Type) equals(t, zfs.Created, inodeChanges[3].Change)