filesystem.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  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/ignore/ignoreresult"
  17. "github.com/syncthing/syncthing/lib/protocol"
  18. )
  19. type filesystemWrapperType int32
  20. const (
  21. filesystemWrapperTypeNone filesystemWrapperType = iota
  22. filesystemWrapperTypeMtime
  23. filesystemWrapperTypeCase
  24. filesystemWrapperTypeError
  25. filesystemWrapperTypeWalk
  26. filesystemWrapperTypeLog
  27. filesystemWrapperTypeMetrics
  28. )
  29. type XattrFilter interface {
  30. Permit(string) bool
  31. GetMaxSingleEntrySize() int
  32. GetMaxTotalSize() int
  33. }
  34. // The Filesystem interface abstracts access to the file system.
  35. type Filesystem interface {
  36. Chmod(name string, mode FileMode) error
  37. Lchown(name string, uid, gid string) error // uid/gid as strings; numeric on POSIX, SID on Windows, like in os/user package
  38. Chtimes(name string, atime time.Time, mtime time.Time) error
  39. Create(name string) (File, error)
  40. CreateSymlink(target, name string) error
  41. DirNames(name string) ([]string, error)
  42. Lstat(name string) (FileInfo, error)
  43. Mkdir(name string, perm FileMode) error
  44. MkdirAll(name string, perm FileMode) error
  45. Open(name string) (File, error)
  46. OpenFile(name string, flags int, mode FileMode) (File, error)
  47. ReadSymlink(name string) (string, error)
  48. Remove(name string) error
  49. RemoveAll(name string) error
  50. Rename(oldname, newname string) error
  51. Stat(name string) (FileInfo, error)
  52. SymlinksSupported() bool
  53. Walk(name string, walkFn WalkFunc) error
  54. // If setup fails, returns non-nil error, and if afterwards a fatal (!)
  55. // error occurs, sends that error on the channel. Afterwards this watch
  56. // can be considered stopped.
  57. Watch(path string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, <-chan error, error)
  58. Hide(name string) error
  59. Unhide(name string) error
  60. Glob(pattern string) ([]string, error)
  61. Roots() ([]string, error)
  62. Usage(name string) (Usage, error)
  63. Type() FilesystemType
  64. URI() string
  65. Options() []Option
  66. SameFile(fi1, fi2 FileInfo) bool
  67. PlatformData(name string, withOwnership, withXattrs bool, xattrFilter XattrFilter) (protocol.PlatformData, error)
  68. GetXattr(name string, xattrFilter XattrFilter) ([]protocol.Xattr, error)
  69. SetXattr(path string, xattrs []protocol.Xattr, xattrFilter XattrFilter) error
  70. // Used for unwrapping things
  71. underlying() (Filesystem, bool)
  72. wrapperType() filesystemWrapperType
  73. }
  74. // The File interface abstracts access to a regular file, being a somewhat
  75. // smaller interface than os.File
  76. type File interface {
  77. io.Closer
  78. io.Reader
  79. io.ReaderAt
  80. io.Seeker
  81. io.Writer
  82. io.WriterAt
  83. Name() string
  84. Truncate(size int64) error
  85. Stat() (FileInfo, error)
  86. Sync() error
  87. }
  88. // The FileInfo interface is almost the same as os.FileInfo, but with the
  89. // Sys method removed (as we don't want to expose whatever is underlying)
  90. // and with a couple of convenience methods added.
  91. type FileInfo interface {
  92. // Standard things present in os.FileInfo
  93. Name() string
  94. Mode() FileMode
  95. Size() int64
  96. ModTime() time.Time
  97. IsDir() bool
  98. Sys() interface{}
  99. // Extensions
  100. IsRegular() bool
  101. IsSymlink() bool
  102. Owner() int
  103. Group() int
  104. InodeChangeTime() time.Time // may be zero if not supported
  105. }
  106. // FileMode is similar to os.FileMode
  107. type FileMode uint32
  108. func (fm FileMode) String() string {
  109. return os.FileMode(fm).String()
  110. }
  111. // Usage represents filesystem space usage
  112. type Usage struct {
  113. Free uint64
  114. Total uint64
  115. }
  116. type Matcher interface {
  117. Match(name string) ignoreresult.R
  118. }
  119. type Event struct {
  120. Name string
  121. Type EventType
  122. }
  123. type EventType int
  124. const (
  125. NonRemove EventType = 1 + iota
  126. Remove
  127. Mixed // Should probably not be necessary to be used in filesystem interface implementation
  128. )
  129. // Merge returns Mixed, except if evType and other are the same and not Mixed.
  130. func (evType EventType) Merge(other EventType) EventType {
  131. return evType | other
  132. }
  133. func (evType EventType) String() string {
  134. switch {
  135. case evType == NonRemove:
  136. return "non-remove"
  137. case evType == Remove:
  138. return "remove"
  139. case evType == Mixed:
  140. return "mixed"
  141. default:
  142. panic("bug: Unknown event type")
  143. }
  144. }
  145. var (
  146. ErrWatchNotSupported = errors.New("watching is not supported")
  147. ErrXattrsNotSupported = errors.New("extended attributes are not supported on this platform")
  148. )
  149. // Equivalents from os package.
  150. const (
  151. ModePerm = FileMode(os.ModePerm)
  152. ModeSetgid = FileMode(os.ModeSetgid)
  153. ModeSetuid = FileMode(os.ModeSetuid)
  154. ModeSticky = FileMode(os.ModeSticky)
  155. ModeSymlink = FileMode(os.ModeSymlink)
  156. ModeType = FileMode(os.ModeType)
  157. PathSeparator = os.PathSeparator
  158. OptAppend = os.O_APPEND
  159. OptCreate = os.O_CREATE
  160. OptExclusive = os.O_EXCL
  161. OptReadOnly = os.O_RDONLY
  162. OptReadWrite = os.O_RDWR
  163. OptSync = os.O_SYNC
  164. OptTruncate = os.O_TRUNC
  165. OptWriteOnly = os.O_WRONLY
  166. )
  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. // mtime handling should happen inside walking, as filesystem calls while
  232. // walking should be mtime-resolved too
  233. if mtimeOpt != nil {
  234. fs = mtimeOpt.apply(fs)
  235. }
  236. fs = &metricsFS{next: fs}
  237. layersAboveWalkFilesystem := 0
  238. if caseOpt != nil {
  239. // DirNames calls made to check the case of a name will also be
  240. // attributed to the calling function.
  241. layersAboveWalkFilesystem++
  242. }
  243. if l.ShouldDebug("walkfs") {
  244. // A walkFilesystem is not a layer to skip, it embeds the underlying
  245. // filesystem, passing calls directly trough. Except for calls made
  246. // during walking, however those are truly originating in the walk
  247. // filesystem.
  248. fs = NewWalkFilesystem(newLogFilesystem(fs, layersAboveWalkFilesystem))
  249. } else if l.ShouldDebug("fs") {
  250. fs = newLogFilesystem(NewWalkFilesystem(fs), layersAboveWalkFilesystem)
  251. } else {
  252. fs = NewWalkFilesystem(fs)
  253. }
  254. // Case handling is at the outermost layer to resolve all input names.
  255. // Reason being is that the only names/paths that are potentially "wrong"
  256. // come from outside the fs package. Any paths that result from filesystem
  257. // operations itself already have the correct case. Thus there's e.g. no
  258. // point to check the case on all the stating the walk filesystem does, it
  259. // just adds overhead.
  260. if caseOpt != nil {
  261. fs = caseOpt.apply(fs)
  262. }
  263. return fs
  264. }
  265. // IsInternal returns true if the file, as a path relative to the folder
  266. // root, represents an internal file that should always be ignored. The file
  267. // path must be clean (i.e., in canonical shortest form).
  268. func IsInternal(file string) bool {
  269. // fs cannot import config or versioner, so we hard code .stfolder
  270. // (config.DefaultMarkerName) and .stversions (versioner.DefaultPath)
  271. internals := []string{".stfolder", ".stignore", ".stversions"}
  272. for _, internal := range internals {
  273. if file == internal {
  274. return true
  275. }
  276. if IsParent(file, internal) {
  277. return true
  278. }
  279. }
  280. return false
  281. }
  282. var (
  283. errPathInvalid = errors.New("path is invalid")
  284. errPathTraversingUpwards = errors.New("relative path traversing upwards (starting with ..)")
  285. )
  286. // Canonicalize checks that the file path is valid and returns it in the "canonical" form:
  287. // - /foo/bar -> foo/bar
  288. // - / -> "."
  289. func Canonicalize(file string) (string, error) {
  290. const pathSep = string(PathSeparator)
  291. if strings.HasPrefix(file, pathSep+pathSep) {
  292. // The relative path may pretend to be an absolute path within
  293. // the root, but the double path separator on Windows implies
  294. // something else and is out of spec.
  295. return "", errPathInvalid
  296. }
  297. // The relative path should be clean from internal dotdots and similar
  298. // funkyness.
  299. file = filepath.Clean(file)
  300. // It is not acceptable to attempt to traverse upwards.
  301. if file == ".." {
  302. return "", errPathTraversingUpwards
  303. }
  304. if strings.HasPrefix(file, ".."+pathSep) {
  305. return "", errPathTraversingUpwards
  306. }
  307. if strings.HasPrefix(file, pathSep) {
  308. if file == pathSep {
  309. return ".", nil
  310. }
  311. return file[1:], nil
  312. }
  313. return file, nil
  314. }
  315. // unwrapFilesystem removes "wrapping" filesystems to expose the filesystem of the requested wrapperType, if it exists.
  316. func unwrapFilesystem(fs Filesystem, wrapperType filesystemWrapperType) (Filesystem, bool) {
  317. var ok bool
  318. for {
  319. if fs.wrapperType() == wrapperType {
  320. return fs, true
  321. }
  322. fs, ok = fs.underlying()
  323. if !ok {
  324. return nil, false
  325. }
  326. }
  327. }
  328. // WriteFile writes data to the named file, creating it if necessary.
  329. // If the file does not exist, WriteFile creates it with permissions perm (before umask);
  330. // otherwise WriteFile truncates it before writing, without changing permissions.
  331. // Since Writefile requires multiple system calls to complete, a failure mid-operation
  332. // can leave the file in a partially written state.
  333. func WriteFile(fs Filesystem, name string, data []byte, perm FileMode) error {
  334. f, err := fs.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
  335. if err != nil {
  336. return err
  337. }
  338. _, err = f.Write(data)
  339. if err1 := f.Close(); err1 != nil && err == nil {
  340. err = err1
  341. }
  342. return err
  343. }