external.go 2.6 KB

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