external.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  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(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. for key, val := range context {
  65. word = strings.Replace(word, key, val, -1)
  66. }
  67. words[i] = word
  68. }
  69. cmd := exec.Command(words[0], words[1:]...)
  70. env := os.Environ()
  71. // filter STGUIAUTH and STGUIAPIKEY from environment variables
  72. filteredEnv := []string{}
  73. for _, x := range env {
  74. if !strings.HasPrefix(x, "STGUIAUTH=") && !strings.HasPrefix(x, "STGUIAPIKEY=") {
  75. filteredEnv = append(filteredEnv, x)
  76. }
  77. }
  78. cmd.Env = filteredEnv
  79. combinedOutput, err := cmd.CombinedOutput()
  80. l.Debugln("external command output:", string(combinedOutput))
  81. if err != nil {
  82. return err
  83. }
  84. // return error if the file was not removed
  85. if _, err = v.filesystem.Lstat(filePath); fs.IsNotExist(err) {
  86. return nil
  87. }
  88. return errors.New("Versioner: file was not removed by external script")
  89. }
  90. func (v external) GetVersions() (map[string][]FileVersion, error) {
  91. return nil, ErrRestorationNotSupported
  92. }
  93. func (v external) Restore(filePath string, versionTime time.Time) error {
  94. return ErrRestorationNotSupported
  95. }