external.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  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. "os"
  11. "os/exec"
  12. "runtime"
  13. "strings"
  14. "time"
  15. "github.com/syncthing/syncthing/lib/config"
  16. "github.com/syncthing/syncthing/lib/fs"
  17. "github.com/kballard/go-shellquote"
  18. )
  19. func init() {
  20. // Register the constructor for this type of versioner with the name "external"
  21. factories["external"] = newExternal
  22. }
  23. type external struct {
  24. command string
  25. filesystem fs.Filesystem
  26. }
  27. func newExternal(cfg config.FolderConfiguration) Versioner {
  28. command := cfg.Versioning.Params["command"]
  29. if runtime.GOOS == "windows" {
  30. command = strings.Replace(command, `\`, `\\`, -1)
  31. }
  32. s := external{
  33. command: command,
  34. filesystem: cfg.Filesystem(),
  35. }
  36. l.Debugf("instantiated %#v", s)
  37. return s
  38. }
  39. // Archive moves the named file away to a version archive. If this function
  40. // returns nil, the named file does not exist any more (has been archived).
  41. func (v external) Archive(filePath string) error {
  42. info, err := v.filesystem.Lstat(filePath)
  43. if fs.IsNotExist(err) {
  44. l.Debugln("not archiving nonexistent file", filePath)
  45. return nil
  46. } else if err != nil {
  47. return err
  48. }
  49. if info.IsSymlink() {
  50. panic("bug: attempting to version a symlink")
  51. }
  52. l.Debugln("archiving", filePath)
  53. if v.command == "" {
  54. return errors.New("Versioner: command is empty, please enter a valid command")
  55. }
  56. words, err := shellquote.Split(v.command)
  57. if err != nil {
  58. return errors.New("Versioner: command is invalid: " + err.Error())
  59. }
  60. context := map[string]string{
  61. "%FOLDER_FILESYSTEM%": v.filesystem.Type().String(),
  62. "%FOLDER_PATH%": v.filesystem.URI(),
  63. "%FILE_PATH%": filePath,
  64. }
  65. for i, word := range words {
  66. for key, val := range context {
  67. word = strings.Replace(word, key, val, -1)
  68. }
  69. words[i] = word
  70. }
  71. cmd := exec.Command(words[0], words[1:]...)
  72. env := os.Environ()
  73. // filter STGUIAUTH and STGUIAPIKEY from environment variables
  74. filteredEnv := []string{}
  75. for _, x := range env {
  76. if !strings.HasPrefix(x, "STGUIAUTH=") && !strings.HasPrefix(x, "STGUIAPIKEY=") {
  77. filteredEnv = append(filteredEnv, x)
  78. }
  79. }
  80. cmd.Env = filteredEnv
  81. combinedOutput, err := cmd.CombinedOutput()
  82. l.Debugln("external command output:", string(combinedOutput))
  83. if err != nil {
  84. return err
  85. }
  86. // return error if the file was not removed
  87. if _, err = v.filesystem.Lstat(filePath); fs.IsNotExist(err) {
  88. return nil
  89. }
  90. return errors.New("Versioner: file was not removed by external script")
  91. }
  92. func (v external) GetVersions() (map[string][]FileVersion, error) {
  93. return nil, ErrRestorationNotSupported
  94. }
  95. func (v external) Restore(filePath string, versionTime time.Time) error {
  96. return ErrRestorationNotSupported
  97. }
  98. func (v external) Clean(_ context.Context) error {
  99. return nil
  100. }