external.go 2.8 KB

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