filesystem.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  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. Lchown(name string, uid, gid int) error
  20. Chtimes(name string, atime time.Time, mtime time.Time) error
  21. Create(name string) (File, error)
  22. CreateSymlink(target, name string) error
  23. DirNames(name string) ([]string, error)
  24. Lstat(name string) (FileInfo, error)
  25. Mkdir(name string, perm FileMode) error
  26. MkdirAll(name string, perm FileMode) error
  27. Open(name string) (File, error)
  28. OpenFile(name string, flags int, mode FileMode) (File, error)
  29. ReadSymlink(name string) (string, error)
  30. Remove(name string) error
  31. RemoveAll(name string) error
  32. Rename(oldname, newname string) error
  33. Stat(name string) (FileInfo, error)
  34. SymlinksSupported() bool
  35. Walk(name string, walkFn WalkFunc) error
  36. // If setup fails, returns non-nil error, and if afterwards a fatal (!)
  37. // error occurs, sends that error on the channel. Afterwards this watch
  38. // can be considered stopped.
  39. Watch(path string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, <-chan error, error)
  40. Hide(name string) error
  41. Unhide(name string) error
  42. Glob(pattern string) ([]string, error)
  43. Roots() ([]string, error)
  44. Usage(name string) (Usage, error)
  45. Type() FilesystemType
  46. URI() string
  47. Options() []Option
  48. SameFile(fi1, fi2 FileInfo) bool
  49. }
  50. // The File interface abstracts access to a regular file, being a somewhat
  51. // smaller interface than os.File
  52. type File interface {
  53. io.Closer
  54. io.Reader
  55. io.ReaderAt
  56. io.Seeker
  57. io.Writer
  58. io.WriterAt
  59. Name() string
  60. Truncate(size int64) error
  61. Stat() (FileInfo, error)
  62. Sync() error
  63. }
  64. // The FileInfo interface is almost the same as os.FileInfo, but with the
  65. // Sys method removed (as we don't want to expose whatever is underlying)
  66. // and with a couple of convenience methods added.
  67. type FileInfo interface {
  68. // Standard things present in os.FileInfo
  69. Name() string
  70. Mode() FileMode
  71. Size() int64
  72. ModTime() time.Time
  73. IsDir() bool
  74. // Extensions
  75. IsRegular() bool
  76. IsSymlink() bool
  77. Owner() int
  78. Group() int
  79. }
  80. // FileMode is similar to os.FileMode
  81. type FileMode uint32
  82. func (fm FileMode) String() string {
  83. return os.FileMode(fm).String()
  84. }
  85. // Usage represents filesystem space usage
  86. type Usage struct {
  87. Free uint64
  88. Total uint64
  89. }
  90. type Matcher interface {
  91. ShouldIgnore(name string) bool
  92. SkipIgnoredDirs() bool
  93. }
  94. type MatchResult interface {
  95. IsIgnored() bool
  96. }
  97. type Event struct {
  98. Name string
  99. Type EventType
  100. }
  101. type EventType int
  102. const (
  103. NonRemove EventType = 1 + iota
  104. Remove
  105. Mixed // Should probably not be necessary to be used in filesystem interface implementation
  106. )
  107. // Merge returns Mixed, except if evType and other are the same and not Mixed.
  108. func (evType EventType) Merge(other EventType) EventType {
  109. return evType | other
  110. }
  111. func (evType EventType) String() string {
  112. switch {
  113. case evType == NonRemove:
  114. return "non-remove"
  115. case evType == Remove:
  116. return "remove"
  117. case evType == Mixed:
  118. return "mixed"
  119. default:
  120. panic("bug: Unknown event type")
  121. }
  122. }
  123. var ErrWatchNotSupported = errors.New("watching is not supported")
  124. // Equivalents from os package.
  125. const ModePerm = FileMode(os.ModePerm)
  126. const ModeSetgid = FileMode(os.ModeSetgid)
  127. const ModeSetuid = FileMode(os.ModeSetuid)
  128. const ModeSticky = FileMode(os.ModeSticky)
  129. const ModeSymlink = FileMode(os.ModeSymlink)
  130. const ModeType = FileMode(os.ModeType)
  131. const PathSeparator = os.PathSeparator
  132. const OptAppend = os.O_APPEND
  133. const OptCreate = os.O_CREATE
  134. const OptExclusive = os.O_EXCL
  135. const OptReadOnly = os.O_RDONLY
  136. const OptReadWrite = os.O_RDWR
  137. const OptSync = os.O_SYNC
  138. const OptTruncate = os.O_TRUNC
  139. const OptWriteOnly = os.O_WRONLY
  140. // SkipDir is used as a return value from WalkFuncs to indicate that
  141. // the directory named in the call is to be skipped. It is not returned
  142. // as an error by any function.
  143. var SkipDir = filepath.SkipDir
  144. // IsExist is the equivalent of os.IsExist
  145. var IsExist = os.IsExist
  146. // IsExist is the equivalent of os.ErrExist
  147. var ErrExist = os.ErrExist
  148. // IsNotExist is the equivalent of os.IsNotExist
  149. var IsNotExist = os.IsNotExist
  150. // ErrNotExist is the equivalent of os.ErrNotExist
  151. var ErrNotExist = os.ErrNotExist
  152. // IsPermission is the equivalent of os.IsPermission
  153. var IsPermission = os.IsPermission
  154. // IsPathSeparator is the equivalent of os.IsPathSeparator
  155. var IsPathSeparator = os.IsPathSeparator
  156. // Option modifies a filesystem at creation. An option might be specific
  157. // to a filesystem-type.
  158. //
  159. // String is used to detect options with the same effect, i.e. must be different
  160. // for options with different effects. Meaning if an option has parameters, a
  161. // representation of those must be part of the returned string.
  162. type Option interface {
  163. String() string
  164. apply(Filesystem)
  165. }
  166. func NewFilesystem(fsType FilesystemType, uri string, opts ...Option) Filesystem {
  167. var fs Filesystem
  168. switch fsType {
  169. case FilesystemTypeBasic:
  170. fs = newBasicFilesystem(uri, opts...)
  171. case FilesystemTypeFake:
  172. fs = newFakeFilesystem(uri, opts...)
  173. default:
  174. l.Debugln("Unknown filesystem", fsType, uri)
  175. fs = &errorFilesystem{
  176. fsType: fsType,
  177. uri: uri,
  178. err: errors.New("filesystem with type " + fsType.String() + " does not exist."),
  179. }
  180. }
  181. if l.ShouldDebug("walkfs") {
  182. return NewWalkFilesystem(&logFilesystem{fs})
  183. }
  184. if l.ShouldDebug("fs") {
  185. return &logFilesystem{NewWalkFilesystem(fs)}
  186. }
  187. return NewWalkFilesystem(fs)
  188. }
  189. // IsInternal returns true if the file, as a path relative to the folder
  190. // root, represents an internal file that should always be ignored. The file
  191. // path must be clean (i.e., in canonical shortest form).
  192. func IsInternal(file string) bool {
  193. // fs cannot import config, so we hard code .stfolder here (config.DefaultMarkerName)
  194. internals := []string{".stfolder", ".stignore", ".stversions"}
  195. for _, internal := range internals {
  196. if file == internal {
  197. return true
  198. }
  199. if IsParent(file, internal) {
  200. return true
  201. }
  202. }
  203. return false
  204. }
  205. // Canonicalize checks that the file path is valid and returns it in the "canonical" form:
  206. // - /foo/bar -> foo/bar
  207. // - / -> "."
  208. func Canonicalize(file string) (string, error) {
  209. pathSep := string(PathSeparator)
  210. if strings.HasPrefix(file, pathSep+pathSep) {
  211. // The relative path may pretend to be an absolute path within
  212. // the root, but the double path separator on Windows implies
  213. // something else and is out of spec.
  214. return "", errNotRelative
  215. }
  216. // The relative path should be clean from internal dotdots and similar
  217. // funkyness.
  218. file = filepath.Clean(file)
  219. // It is not acceptable to attempt to traverse upwards.
  220. switch file {
  221. case "..":
  222. return "", errNotRelative
  223. }
  224. if strings.HasPrefix(file, ".."+pathSep) {
  225. return "", errNotRelative
  226. }
  227. if strings.HasPrefix(file, pathSep) {
  228. if file == pathSep {
  229. return ".", nil
  230. }
  231. return file[1:], nil
  232. }
  233. return file, nil
  234. }
  235. // wrapFilesystem should always be used when wrapping a Filesystem.
  236. // It ensures proper wrapping order, which right now means:
  237. // `logFilesystem` needs to be the outermost wrapper for caller lookup.
  238. func wrapFilesystem(fs Filesystem, wrapFn func(Filesystem) Filesystem) Filesystem {
  239. logFs, ok := fs.(*logFilesystem)
  240. if ok {
  241. fs = logFs.Filesystem
  242. }
  243. fs = wrapFn(fs)
  244. if ok {
  245. fs = &logFilesystem{fs}
  246. }
  247. return fs
  248. }
  249. // unwrapFilesystem removes "wrapping" filesystems to expose the underlying filesystem.
  250. func unwrapFilesystem(fs Filesystem) Filesystem {
  251. for {
  252. switch sfs := fs.(type) {
  253. case *logFilesystem:
  254. fs = sfs.Filesystem
  255. case *walkFilesystem:
  256. fs = sfs.Filesystem
  257. case *mtimeFS:
  258. fs = sfs.Filesystem
  259. default:
  260. return sfs
  261. }
  262. }
  263. }