vfs.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. // Package vfs provides local and remote filesystems support
  2. package vfs
  3. import (
  4. "errors"
  5. "fmt"
  6. "os"
  7. "path"
  8. "runtime"
  9. "strings"
  10. "time"
  11. "github.com/drakkan/sftpgo/logger"
  12. "github.com/eikenb/pipeat"
  13. "github.com/pkg/sftp"
  14. )
  15. // Fs defines the interface for filesystem backends
  16. type Fs interface {
  17. Name() string
  18. ConnectionID() string
  19. Stat(name string) (os.FileInfo, error)
  20. Lstat(name string) (os.FileInfo, error)
  21. Open(name string) (*os.File, *pipeat.PipeReaderAt, func(), error)
  22. Create(name string, flag int) (*os.File, *pipeat.PipeWriterAt, func(), error)
  23. Rename(source, target string) error
  24. Remove(name string, isDir bool) error
  25. Mkdir(name string) error
  26. Symlink(source, target string) error
  27. Chown(name string, uid int, gid int) error
  28. Chmod(name string, mode os.FileMode) error
  29. Chtimes(name string, atime, mtime time.Time) error
  30. ReadDir(dirname string) ([]os.FileInfo, error)
  31. IsUploadResumeSupported() bool
  32. IsAtomicUploadSupported() bool
  33. CheckRootPath(username string, uid int, gid int) bool
  34. ResolvePath(sftpPath string) (string, error)
  35. IsNotExist(err error) bool
  36. IsPermission(err error) bool
  37. ScanRootDirContents() (int, int64, error)
  38. GetAtomicUploadPath(name string) string
  39. GetRelativePath(name string) string
  40. Join(elem ...string) string
  41. }
  42. // VirtualFolder defines a mapping between a SFTP/SCP virtual path and a
  43. // filesystem path outside the user home directory.
  44. // The specified paths must be absolute and the virtual path cannot be "/",
  45. // it must be a sub directory. The parent directory for the specified virtual
  46. // path must exist. SFTPGo will try to automatically create any missing
  47. // parent directory for the configured virtual folders at user login.
  48. type VirtualFolder struct {
  49. VirtualPath string `json:"virtual_path"`
  50. MappedPath string `json:"mapped_path"`
  51. }
  52. // IsDirectory checks if a path exists and is a directory
  53. func IsDirectory(fs Fs, path string) (bool, error) {
  54. fileInfo, err := fs.Stat(path)
  55. if err != nil {
  56. return false, err
  57. }
  58. return fileInfo.IsDir(), err
  59. }
  60. // GetSFTPError returns an sftp error from a filesystem error
  61. func GetSFTPError(fs Fs, err error) error {
  62. if fs.IsNotExist(err) {
  63. return sftp.ErrSSHFxNoSuchFile
  64. } else if fs.IsPermission(err) {
  65. return sftp.ErrSSHFxPermissionDenied
  66. } else if err != nil {
  67. return sftp.ErrSSHFxFailure
  68. }
  69. return nil
  70. }
  71. // IsLocalOsFs returns true if fs is the local filesystem implementation
  72. func IsLocalOsFs(fs Fs) bool {
  73. return fs.Name() == osFsName
  74. }
  75. // ValidateS3FsConfig returns nil if the specified s3 config is valid, otherwise an error
  76. func ValidateS3FsConfig(config *S3FsConfig) error {
  77. if len(config.Bucket) == 0 {
  78. return errors.New("bucket cannot be empty")
  79. }
  80. if len(config.Region) == 0 {
  81. return errors.New("region cannot be empty")
  82. }
  83. if len(config.AccessKey) == 0 && len(config.AccessSecret) > 0 {
  84. return errors.New("access_key cannot be empty with access_secret not empty")
  85. }
  86. if len(config.AccessSecret) == 0 && len(config.AccessKey) > 0 {
  87. return errors.New("access_secret cannot be empty with access_key not empty")
  88. }
  89. if len(config.KeyPrefix) > 0 {
  90. if strings.HasPrefix(config.KeyPrefix, "/") {
  91. return errors.New("key_prefix cannot start with /")
  92. }
  93. config.KeyPrefix = path.Clean(config.KeyPrefix)
  94. if !strings.HasSuffix(config.KeyPrefix, "/") {
  95. config.KeyPrefix += "/"
  96. }
  97. }
  98. if config.UploadPartSize != 0 && config.UploadPartSize < 5 {
  99. return errors.New("upload_part_size cannot be != 0 and lower than 5 (MB)")
  100. }
  101. if config.UploadConcurrency < 0 {
  102. return fmt.Errorf("invalid upload concurrency: %v", config.UploadConcurrency)
  103. }
  104. return nil
  105. }
  106. // ValidateGCSFsConfig returns nil if the specified GCS config is valid, otherwise an error
  107. func ValidateGCSFsConfig(config *GCSFsConfig, credentialsFilePath string) error {
  108. if len(config.Bucket) == 0 {
  109. return errors.New("bucket cannot be empty")
  110. }
  111. if len(config.KeyPrefix) > 0 {
  112. if strings.HasPrefix(config.KeyPrefix, "/") {
  113. return errors.New("key_prefix cannot start with /")
  114. }
  115. config.KeyPrefix = path.Clean(config.KeyPrefix)
  116. if !strings.HasSuffix(config.KeyPrefix, "/") {
  117. config.KeyPrefix += "/"
  118. }
  119. }
  120. if len(config.Credentials) == 0 && config.AutomaticCredentials == 0 {
  121. fi, err := os.Stat(credentialsFilePath)
  122. if err != nil {
  123. return fmt.Errorf("invalid credentials %v", err)
  124. }
  125. if fi.Size() == 0 {
  126. return errors.New("credentials cannot be empty")
  127. }
  128. }
  129. return nil
  130. }
  131. // SetPathPermissions calls fs.Chown.
  132. // It does nothing for local filesystem on windows
  133. func SetPathPermissions(fs Fs, path string, uid int, gid int) {
  134. if IsLocalOsFs(fs) {
  135. if runtime.GOOS == "windows" {
  136. return
  137. }
  138. }
  139. if err := fs.Chown(path, uid, gid); err != nil {
  140. fsLog(fs, logger.LevelWarn, "error chowning path %v: %v", path, err)
  141. }
  142. }
  143. func fsLog(fs Fs, level logger.LogLevel, format string, v ...interface{}) {
  144. logger.Log(level, fs.Name(), fs.ConnectionID(), format, v...)
  145. }