filesystem.go 9.2 KB

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