1
0

filesystem.go 9.3 KB

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