|
@@ -0,0 +1,90 @@
|
|
|
+package worker
|
|
|
+
|
|
|
+import (
|
|
|
+ "fmt"
|
|
|
+ "os"
|
|
|
+ "path/filepath"
|
|
|
+
|
|
|
+ "github.com/dennwc/btrfs"
|
|
|
+)
|
|
|
+
|
|
|
+type btrfsSnapshotHook struct {
|
|
|
+ provider mirrorProvider
|
|
|
+ mirrorSnapshotPath string
|
|
|
+}
|
|
|
+
|
|
|
+// the user who runs the jobs (typically `tunasync`) should be granted the permission to run btrfs commands
|
|
|
+// TODO: check if the filesystem is Btrfs
|
|
|
+func newBtrfsSnapshotHook(provider mirrorProvider, snapshotPath string, mirror mirrorConfig) *btrfsSnapshotHook {
|
|
|
+ mirrorSnapshotPath := mirror.SnapshotPath
|
|
|
+ if mirrorSnapshotPath == "" {
|
|
|
+ mirrorSnapshotPath = filepath.Join(snapshotPath, provider.Name())
|
|
|
+ }
|
|
|
+ return &btrfsSnapshotHook{
|
|
|
+ provider: provider,
|
|
|
+ mirrorSnapshotPath: mirrorSnapshotPath,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// check if path `snapshotPath/providerName` exists
|
|
|
+// Case 1: Not exists => create a new subvolume
|
|
|
+// Case 2: Exists as a subvolume => nothing to do
|
|
|
+// Case 3: Exists as a directory => error detected
|
|
|
+func (h *btrfsSnapshotHook) preJob() error {
|
|
|
+ path := h.provider.WorkingDir()
|
|
|
+ if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
|
+ // create subvolume
|
|
|
+ err := btrfs.CreateSubVolume(path)
|
|
|
+ if err != nil {
|
|
|
+ logger.Errorf("failed to create Btrfs subvolume %s: %s", path, err.Error())
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ logger.Noticef("created new Btrfs subvolume %s", path)
|
|
|
+ } else {
|
|
|
+ if is, err := btrfs.IsSubVolume(path); err != nil {
|
|
|
+ return err
|
|
|
+ } else if !is {
|
|
|
+ return fmt.Errorf("path %s exists but isn't a Btrfs subvolume", path)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func (h *btrfsSnapshotHook) preExec() error {
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func (h *btrfsSnapshotHook) postExec() error {
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// delete old snapshot if exists, then create a new snapshot
|
|
|
+func (h *btrfsSnapshotHook) postSuccess() error {
|
|
|
+ if _, err := os.Stat(h.mirrorSnapshotPath); !os.IsNotExist(err) {
|
|
|
+ isSubVol, err := btrfs.IsSubVolume(h.mirrorSnapshotPath)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ } else if !isSubVol {
|
|
|
+ return fmt.Errorf("path %s exists and isn't a Btrfs snapshot", h.mirrorSnapshotPath)
|
|
|
+ }
|
|
|
+ // is old snapshot => delete it
|
|
|
+ if err := btrfs.DeleteSubVolume(h.mirrorSnapshotPath); err != nil {
|
|
|
+ logger.Errorf("failed to delete old Btrfs snapshot %s", h.mirrorSnapshotPath)
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ logger.Noticef("deleted old snapshot %s", h.mirrorSnapshotPath)
|
|
|
+ }
|
|
|
+ // create a new writable snapshot
|
|
|
+ // (the snapshot is writable so that it can be deleted easily)
|
|
|
+ if err := btrfs.SnapshotSubVolume(h.provider.WorkingDir(), h.mirrorSnapshotPath, false); err != nil {
|
|
|
+ logger.Errorf("failed to create new Btrfs snapshot %s", h.mirrorSnapshotPath)
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ logger.Noticef("created new Btrfs snapshot %s", h.mirrorSnapshotPath)
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// keep the old snapshot => nothing to do
|
|
|
+func (h *btrfsSnapshotHook) postFail() error {
|
|
|
+ return nil
|
|
|
+}
|