2
0

btrfs_snapshot_hook.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. //go:build linux
  2. // +build linux
  3. package worker
  4. import (
  5. "fmt"
  6. "os"
  7. "path/filepath"
  8. "github.com/dennwc/btrfs"
  9. )
  10. type btrfsSnapshotHook struct {
  11. provider mirrorProvider
  12. mirrorSnapshotPath string
  13. }
  14. // the user who runs the jobs (typically `tunasync`) should be granted the permission to run btrfs commands
  15. // TODO: check if the filesystem is Btrfs
  16. func newBtrfsSnapshotHook(provider mirrorProvider, snapshotPath string, mirror mirrorConfig) *btrfsSnapshotHook {
  17. mirrorSnapshotPath := mirror.SnapshotPath
  18. if mirrorSnapshotPath == "" {
  19. mirrorSnapshotPath = filepath.Join(snapshotPath, provider.Name())
  20. }
  21. return &btrfsSnapshotHook{
  22. provider: provider,
  23. mirrorSnapshotPath: mirrorSnapshotPath,
  24. }
  25. }
  26. // check if path `snapshotPath/providerName` exists
  27. // Case 1: Not exists => create a new subvolume
  28. // Case 2: Exists as a subvolume => nothing to do
  29. // Case 3: Exists as a directory => error detected
  30. func (h *btrfsSnapshotHook) preJob() error {
  31. path := h.provider.WorkingDir()
  32. if _, err := os.Stat(path); os.IsNotExist(err) {
  33. // create subvolume
  34. err := btrfs.CreateSubVolume(path)
  35. if err != nil {
  36. logger.Errorf("failed to create Btrfs subvolume %s: %s", path, err.Error())
  37. return err
  38. }
  39. logger.Noticef("created new Btrfs subvolume %s", path)
  40. } else {
  41. if is, err := btrfs.IsSubVolume(path); err != nil {
  42. return err
  43. } else if !is {
  44. return fmt.Errorf("path %s exists but isn't a Btrfs subvolume", path)
  45. }
  46. }
  47. return nil
  48. }
  49. func (h *btrfsSnapshotHook) preExec() error {
  50. return nil
  51. }
  52. func (h *btrfsSnapshotHook) postExec() error {
  53. return nil
  54. }
  55. // delete old snapshot if exists, then create a new snapshot
  56. func (h *btrfsSnapshotHook) postSuccess() error {
  57. if _, err := os.Stat(h.mirrorSnapshotPath); !os.IsNotExist(err) {
  58. isSubVol, err := btrfs.IsSubVolume(h.mirrorSnapshotPath)
  59. if err != nil {
  60. return err
  61. } else if !isSubVol {
  62. return fmt.Errorf("path %s exists and isn't a Btrfs snapshot", h.mirrorSnapshotPath)
  63. }
  64. // is old snapshot => delete it
  65. if err := btrfs.DeleteSubVolume(h.mirrorSnapshotPath); err != nil {
  66. logger.Errorf("failed to delete old Btrfs snapshot %s", h.mirrorSnapshotPath)
  67. return err
  68. }
  69. logger.Noticef("deleted old snapshot %s", h.mirrorSnapshotPath)
  70. }
  71. // create a new writable snapshot
  72. // (the snapshot is writable so that it can be deleted easily)
  73. if err := btrfs.SnapshotSubVolume(h.provider.WorkingDir(), h.mirrorSnapshotPath, false); err != nil {
  74. logger.Errorf("failed to create new Btrfs snapshot %s", h.mirrorSnapshotPath)
  75. return err
  76. }
  77. logger.Noticef("created new Btrfs snapshot %s", h.mirrorSnapshotPath)
  78. return nil
  79. }
  80. // keep the old snapshot => nothing to do
  81. func (h *btrfsSnapshotHook) postFail() error {
  82. return nil
  83. }