filesystem.go 8.1 KB

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