filesystem.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. // Copyright (C) 2016 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 fs
  7. import (
  8. "context"
  9. "errors"
  10. "io"
  11. "os"
  12. "path/filepath"
  13. "strings"
  14. "time"
  15. )
  16. // The Filesystem interface abstracts access to the file system.
  17. type Filesystem interface {
  18. Chmod(name string, mode FileMode) error
  19. Chtimes(name string, atime time.Time, mtime time.Time) error
  20. Create(name string) (File, error)
  21. CreateSymlink(target, name string) error
  22. DirNames(name string) ([]string, error)
  23. Lstat(name string) (FileInfo, error)
  24. Mkdir(name string, perm FileMode) error
  25. MkdirAll(name string, perm FileMode) error
  26. Open(name string) (File, error)
  27. OpenFile(name string, flags int, mode FileMode) (File, error)
  28. ReadSymlink(name string) (string, error)
  29. Remove(name string) error
  30. RemoveAll(name string) error
  31. Rename(oldname, newname string) error
  32. Stat(name string) (FileInfo, error)
  33. SymlinksSupported() bool
  34. Walk(name string, walkFn WalkFunc) error
  35. Watch(path string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, error)
  36. Hide(name string) error
  37. Unhide(name string) error
  38. Glob(pattern string) ([]string, error)
  39. Roots() ([]string, error)
  40. Usage(name string) (Usage, error)
  41. Type() FilesystemType
  42. URI() string
  43. SameFile(fi1, fi2 FileInfo) bool
  44. }
  45. // The File interface abstracts access to a regular file, being a somewhat
  46. // smaller interface than os.File
  47. type File interface {
  48. io.Closer
  49. io.Reader
  50. io.ReaderAt
  51. io.Seeker
  52. io.Writer
  53. io.WriterAt
  54. Name() string
  55. Truncate(size int64) error
  56. Stat() (FileInfo, error)
  57. Sync() error
  58. }
  59. // The FileInfo interface is almost the same as os.FileInfo, but with the
  60. // Sys method removed (as we don't want to expose whatever is underlying)
  61. // and with a couple of convenience methods added.
  62. type FileInfo interface {
  63. // Standard things present in os.FileInfo
  64. Name() string
  65. Mode() FileMode
  66. Size() int64
  67. ModTime() time.Time
  68. IsDir() bool
  69. // Extensions
  70. IsRegular() bool
  71. IsSymlink() bool
  72. }
  73. // FileMode is similar to os.FileMode
  74. type FileMode uint32
  75. func (fm FileMode) String() string {
  76. return os.FileMode(fm).String()
  77. }
  78. // Usage represents filesystem space usage
  79. type Usage struct {
  80. Free int64
  81. Total int64
  82. }
  83. type Matcher interface {
  84. ShouldIgnore(name string) bool
  85. SkipIgnoredDirs() bool
  86. }
  87. type MatchResult interface {
  88. IsIgnored() bool
  89. }
  90. type Event struct {
  91. Name string
  92. Type EventType
  93. }
  94. type EventType int
  95. const (
  96. NonRemove EventType = 1 + iota
  97. Remove
  98. Mixed // Should probably not be necessary to be used in filesystem interface implementation
  99. )
  100. // Merge returns Mixed, except if evType and other are the same and not Mixed.
  101. func (evType EventType) Merge(other EventType) EventType {
  102. return evType | other
  103. }
  104. func (evType EventType) String() string {
  105. switch {
  106. case evType == NonRemove:
  107. return "non-remove"
  108. case evType == Remove:
  109. return "remove"
  110. case evType == Mixed:
  111. return "mixed"
  112. default:
  113. panic("bug: Unknown event type")
  114. }
  115. }
  116. var ErrWatchNotSupported = errors.New("watching is not supported")
  117. // Equivalents from os package.
  118. const ModePerm = FileMode(os.ModePerm)
  119. const ModeSetgid = FileMode(os.ModeSetgid)
  120. const ModeSetuid = FileMode(os.ModeSetuid)
  121. const ModeSticky = FileMode(os.ModeSticky)
  122. const ModeSymlink = FileMode(os.ModeSymlink)
  123. const ModeType = FileMode(os.ModeType)
  124. const PathSeparator = os.PathSeparator
  125. const OptAppend = os.O_APPEND
  126. const OptCreate = os.O_CREATE
  127. const OptExclusive = os.O_EXCL
  128. const OptReadOnly = os.O_RDONLY
  129. const OptReadWrite = os.O_RDWR
  130. const OptSync = os.O_SYNC
  131. const OptTruncate = os.O_TRUNC
  132. const OptWriteOnly = os.O_WRONLY
  133. // SkipDir is used as a return value from WalkFuncs to indicate that
  134. // the directory named in the call is to be skipped. It is not returned
  135. // as an error by any function.
  136. var SkipDir = filepath.SkipDir
  137. // IsExist is the equivalent of os.IsExist
  138. var IsExist = os.IsExist
  139. // IsNotExist is the equivalent of os.IsNotExist
  140. var IsNotExist = os.IsNotExist
  141. // IsPermission is the equivalent of os.IsPermission
  142. var IsPermission = os.IsPermission
  143. // IsPathSeparator is the equivalent of os.IsPathSeparator
  144. var IsPathSeparator = os.IsPathSeparator
  145. func NewFilesystem(fsType FilesystemType, uri string) Filesystem {
  146. var fs Filesystem
  147. switch fsType {
  148. case FilesystemTypeBasic:
  149. fs = newBasicFilesystem(uri)
  150. case FilesystemTypeFake:
  151. fs = newFakeFilesystem(uri)
  152. default:
  153. l.Debugln("Unknown filesystem", fsType, uri)
  154. fs = &errorFilesystem{
  155. fsType: fsType,
  156. uri: uri,
  157. err: errors.New("filesystem with type " + fsType.String() + " does not exist."),
  158. }
  159. }
  160. if l.ShouldDebug("walkfs") {
  161. return NewWalkFilesystem(&logFilesystem{fs})
  162. }
  163. if l.ShouldDebug("fs") {
  164. return &logFilesystem{NewWalkFilesystem(fs)}
  165. }
  166. return NewWalkFilesystem(fs)
  167. }
  168. // IsInternal returns true if the file, as a path relative to the folder
  169. // root, represents an internal file that should always be ignored. The file
  170. // path must be clean (i.e., in canonical shortest form).
  171. func IsInternal(file string) bool {
  172. // fs cannot import config, so we hard code .stfolder here (config.DefaultMarkerName)
  173. internals := []string{".stfolder", ".stignore", ".stversions"}
  174. pathSep := string(PathSeparator)
  175. for _, internal := range internals {
  176. if file == internal {
  177. return true
  178. }
  179. if strings.HasPrefix(file, internal+pathSep) {
  180. return true
  181. }
  182. }
  183. return false
  184. }
  185. // Canonicalize checks that the file path is valid and returns it in the "canonical" form:
  186. // - /foo/bar -> foo/bar
  187. // - / -> "."
  188. func Canonicalize(file string) (string, error) {
  189. pathSep := string(PathSeparator)
  190. if strings.HasPrefix(file, pathSep+pathSep) {
  191. // The relative path may pretend to be an absolute path within
  192. // the root, but the double path separator on Windows implies
  193. // something else and is out of spec.
  194. return "", ErrNotRelative
  195. }
  196. // The relative path should be clean from internal dotdots and similar
  197. // funkyness.
  198. file = filepath.Clean(file)
  199. // It is not acceptable to attempt to traverse upwards.
  200. switch file {
  201. case "..":
  202. return "", ErrNotRelative
  203. }
  204. if strings.HasPrefix(file, ".."+pathSep) {
  205. return "", ErrNotRelative
  206. }
  207. if strings.HasPrefix(file, pathSep) {
  208. if file == pathSep {
  209. return ".", nil
  210. }
  211. return file[1:], nil
  212. }
  213. return file, nil
  214. }