filesystem.go 10.0 KB

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