external.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. // Copyright (C) 2015 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package versioner
  7. import (
  8. "context"
  9. "errors"
  10. "fmt"
  11. "os"
  12. "os/exec"
  13. "strings"
  14. "time"
  15. "github.com/syncthing/syncthing/lib/build"
  16. "github.com/syncthing/syncthing/lib/config"
  17. "github.com/syncthing/syncthing/lib/fs"
  18. "github.com/kballard/go-shellquote"
  19. )
  20. func init() {
  21. // Register the constructor for this type of versioner with the name "external"
  22. factories["external"] = newExternal
  23. }
  24. type external struct {
  25. command string
  26. filesystem fs.Filesystem
  27. }
  28. func newExternal(cfg config.FolderConfiguration) Versioner {
  29. command := cfg.Versioning.Params["command"]
  30. if build.IsWindows {
  31. command = strings.ReplaceAll(command, `\`, `\\`)
  32. }
  33. s := external{
  34. command: command,
  35. filesystem: cfg.Filesystem(nil),
  36. }
  37. l.Debugf("instantiated %#v", s)
  38. return s
  39. }
  40. // Archive moves the named file away to a version archive. If this function
  41. // returns nil, the named file does not exist any more (has been archived).
  42. func (v external) Archive(filePath string) error {
  43. info, err := v.filesystem.Lstat(filePath)
  44. if fs.IsNotExist(err) {
  45. l.Debugln("not archiving nonexistent file", filePath)
  46. return nil
  47. } else if err != nil {
  48. return err
  49. }
  50. if info.IsSymlink() {
  51. panic("bug: attempting to version a symlink")
  52. }
  53. l.Debugln("archiving", filePath)
  54. if v.command == "" {
  55. return errors.New("command is empty, please enter a valid command")
  56. }
  57. words, err := shellquote.Split(v.command)
  58. if err != nil {
  59. return fmt.Errorf("command is invalid: %w", err)
  60. }
  61. context := map[string]string{
  62. "%FOLDER_FILESYSTEM%": v.filesystem.Type().String(),
  63. "%FOLDER_PATH%": v.filesystem.URI(),
  64. "%FILE_PATH%": filePath,
  65. }
  66. for i, word := range words {
  67. for key, val := range context {
  68. word = strings.ReplaceAll(word, key, val)
  69. }
  70. words[i] = word
  71. }
  72. cmd := exec.Command(words[0], words[1:]...)
  73. env := os.Environ()
  74. // filter STGUIAUTH and STGUIAPIKEY from environment variables
  75. var filteredEnv []string
  76. for _, x := range env {
  77. if !strings.HasPrefix(x, "STGUIAUTH=") && !strings.HasPrefix(x, "STGUIAPIKEY=") {
  78. filteredEnv = append(filteredEnv, x)
  79. }
  80. }
  81. cmd.Env = filteredEnv
  82. combinedOutput, err := cmd.CombinedOutput()
  83. l.Debugln("external command output:", string(combinedOutput))
  84. if err != nil {
  85. if eerr, ok := err.(*exec.ExitError); ok && len(eerr.Stderr) > 0 {
  86. return fmt.Errorf("%v: %v", err, string(eerr.Stderr))
  87. }
  88. return err
  89. }
  90. // return error if the file was not removed
  91. if _, err = v.filesystem.Lstat(filePath); fs.IsNotExist(err) {
  92. return nil
  93. }
  94. return errors.New("file was not removed by external script")
  95. }
  96. func (external) GetVersions() (map[string][]FileVersion, error) {
  97. return nil, ErrRestorationNotSupported
  98. }
  99. func (external) Restore(_ string, _ time.Time) error {
  100. return ErrRestorationNotSupported
  101. }
  102. func (external) Clean(_ context.Context) error {
  103. return nil
  104. }