filesystem.go 8.7 KB

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