filesystem.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  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. "fmt"
  11. "io"
  12. "io/fs"
  13. "os"
  14. "path/filepath"
  15. "strings"
  16. "time"
  17. "github.com/syncthing/syncthing/lib/ignore/ignoreresult"
  18. "github.com/syncthing/syncthing/lib/protocol"
  19. )
  20. type filesystemWrapperType int32
  21. const (
  22. filesystemWrapperTypeNone filesystemWrapperType = iota
  23. filesystemWrapperTypeMtime
  24. filesystemWrapperTypeCase
  25. filesystemWrapperTypeError
  26. filesystemWrapperTypeWalk
  27. filesystemWrapperTypeLog
  28. filesystemWrapperTypeMetrics
  29. )
  30. type XattrFilter interface {
  31. Permit(string) bool
  32. GetMaxSingleEntrySize() int
  33. GetMaxTotalSize() int
  34. }
  35. // The Filesystem interface abstracts access to the file system.
  36. type Filesystem interface {
  37. Chmod(name string, mode FileMode) error
  38. Lchown(name string, uid, gid string) error // uid/gid as strings; numeric on POSIX, SID on Windows, like in os/user package
  39. Chtimes(name string, atime time.Time, mtime time.Time) error
  40. Create(name string) (File, error)
  41. CreateSymlink(target, name string) error
  42. DirNames(name string) ([]string, error)
  43. Lstat(name string) (FileInfo, error)
  44. Mkdir(name string, perm FileMode) error
  45. MkdirAll(name string, perm FileMode) error
  46. Open(name string) (File, error)
  47. OpenFile(name string, flags int, mode FileMode) (File, error)
  48. ReadSymlink(name string) (string, error)
  49. Remove(name string) error
  50. RemoveAll(name string) error
  51. Rename(oldname, newname string) error
  52. Stat(name string) (FileInfo, error)
  53. SymlinksSupported() bool
  54. Walk(name string, walkFn WalkFunc) error
  55. // If setup fails, returns non-nil error, and if afterwards a fatal (!)
  56. // error occurs, sends that error on the channel. Afterwards this watch
  57. // can be considered stopped.
  58. Watch(path string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, <-chan error, error)
  59. Hide(name string) error
  60. Unhide(name string) error
  61. Glob(pattern string) ([]string, error)
  62. Roots() ([]string, error)
  63. Usage(name string) (Usage, error)
  64. Type() FilesystemType
  65. URI() string
  66. Options() []Option
  67. SameFile(fi1, fi2 FileInfo) bool
  68. PlatformData(name string, withOwnership, withXattrs bool, xattrFilter XattrFilter) (protocol.PlatformData, error)
  69. GetXattr(name string, xattrFilter XattrFilter) ([]protocol.Xattr, error)
  70. SetXattr(path string, xattrs []protocol.Xattr, xattrFilter XattrFilter) error
  71. // Used for unwrapping things
  72. underlying() (Filesystem, bool)
  73. wrapperType() filesystemWrapperType
  74. }
  75. // The File interface abstracts access to a regular file, being a somewhat
  76. // smaller interface than os.File
  77. type File interface {
  78. io.Closer
  79. io.Reader
  80. io.ReaderAt
  81. io.Seeker
  82. io.Writer
  83. io.WriterAt
  84. Name() string
  85. Truncate(size int64) error
  86. Stat() (FileInfo, error)
  87. Sync() error
  88. }
  89. // The FileInfo interface is almost the same as os.FileInfo, but with the
  90. // Sys method removed (as we don't want to expose whatever is underlying)
  91. // and with a couple of convenience methods added.
  92. type FileInfo interface {
  93. // Standard things present in os.FileInfo
  94. Name() string
  95. Mode() FileMode
  96. Size() int64
  97. ModTime() time.Time
  98. IsDir() bool
  99. Sys() interface{}
  100. // Extensions
  101. IsRegular() bool
  102. IsSymlink() bool
  103. Owner() int
  104. Group() int
  105. InodeChangeTime() time.Time // may be zero if not supported
  106. }
  107. // FileMode is similar to os.FileMode
  108. type FileMode uint32
  109. func (fm FileMode) String() string {
  110. return os.FileMode(fm).String()
  111. }
  112. // Usage represents filesystem space usage
  113. type Usage struct {
  114. Free uint64
  115. Total uint64
  116. }
  117. type Matcher interface {
  118. Match(name string) ignoreresult.R
  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 (
  152. ModePerm = FileMode(os.ModePerm)
  153. ModeSetgid = FileMode(os.ModeSetgid)
  154. ModeSetuid = FileMode(os.ModeSetuid)
  155. ModeSticky = FileMode(os.ModeSticky)
  156. ModeSymlink = FileMode(os.ModeSymlink)
  157. ModeType = FileMode(os.ModeType)
  158. PathSeparator = os.PathSeparator
  159. OptAppend = os.O_APPEND
  160. OptCreate = os.O_CREATE
  161. OptExclusive = os.O_EXCL
  162. OptReadOnly = os.O_RDONLY
  163. OptReadWrite = os.O_RDWR
  164. OptSync = os.O_SYNC
  165. OptTruncate = os.O_TRUNC
  166. OptWriteOnly = os.O_WRONLY
  167. )
  168. // SkipDir is used as a return value from WalkFuncs to indicate that
  169. // the directory named in the call is to be skipped. It is not returned
  170. // as an error by any function.
  171. var SkipDir = filepath.SkipDir
  172. func IsExist(err error) bool {
  173. return errors.Is(err, ErrExist)
  174. }
  175. // ErrExist is the equivalent of os.ErrExist
  176. var ErrExist = fs.ErrExist
  177. // IsNotExist is the equivalent of os.IsNotExist
  178. func IsNotExist(err error) bool {
  179. return errors.Is(err, ErrNotExist)
  180. }
  181. // ErrNotExist is the equivalent of os.ErrNotExist
  182. var ErrNotExist = fs.ErrNotExist
  183. // IsPermission is the equivalent of os.IsPermission
  184. func IsPermission(err error) bool {
  185. return errors.Is(err, fs.ErrPermission)
  186. }
  187. // IsPathSeparator is the equivalent of os.IsPathSeparator
  188. var IsPathSeparator = os.IsPathSeparator
  189. func NewFilesystem(fsType FilesystemType, uri string, opts ...Option) Filesystem {
  190. var caseOpt Option
  191. var mtimeOpt Option
  192. i := 0
  193. for _, opt := range opts {
  194. if caseOpt != nil && mtimeOpt != nil {
  195. break
  196. }
  197. switch opt.(type) {
  198. case *OptionDetectCaseConflicts:
  199. caseOpt = opt
  200. case *optionMtime:
  201. mtimeOpt = opt
  202. default:
  203. opts[i] = opt
  204. i++
  205. }
  206. }
  207. opts = opts[:i]
  208. // Construct file system using the registered factory function
  209. var fs Filesystem
  210. var err error
  211. filesystemFactoriesMutex.Lock()
  212. fsFactory, factoryFound := filesystemFactories[fsType]
  213. filesystemFactoriesMutex.Unlock()
  214. if factoryFound {
  215. fs, err = fsFactory(uri, opts...)
  216. } else {
  217. err = fmt.Errorf("File system type '%s' not recognized", fsType)
  218. }
  219. if err != nil {
  220. fs = &errorFilesystem{
  221. fsType: fsType,
  222. uri: uri,
  223. err: err,
  224. }
  225. }
  226. // mtime handling should happen inside walking, as filesystem calls while
  227. // walking should be mtime-resolved too
  228. if mtimeOpt != nil {
  229. fs = mtimeOpt.apply(fs)
  230. }
  231. fs = &metricsFS{next: fs}
  232. layersAboveWalkFilesystem := 0
  233. if caseOpt != nil {
  234. // DirNames calls made to check the case of a name will also be
  235. // attributed to the calling function.
  236. layersAboveWalkFilesystem++
  237. }
  238. if l.ShouldDebug("walkfs") {
  239. // A walkFilesystem is not a layer to skip, it embeds the underlying
  240. // filesystem, passing calls directly trough. Except for calls made
  241. // during walking, however those are truly originating in the walk
  242. // filesystem.
  243. fs = NewWalkFilesystem(newLogFilesystem(fs, layersAboveWalkFilesystem))
  244. } else if l.ShouldDebug("fs") {
  245. fs = newLogFilesystem(NewWalkFilesystem(fs), layersAboveWalkFilesystem)
  246. } else {
  247. fs = NewWalkFilesystem(fs)
  248. }
  249. // Case handling is at the outermost layer to resolve all input names.
  250. // Reason being is that the only names/paths that are potentially "wrong"
  251. // come from outside the fs package. Any paths that result from filesystem
  252. // operations itself already have the correct case. Thus there's e.g. no
  253. // point to check the case on all the stating the walk filesystem does, it
  254. // just adds overhead.
  255. if caseOpt != nil {
  256. fs = caseOpt.apply(fs)
  257. }
  258. return fs
  259. }
  260. // IsInternal returns true if the file, as a path relative to the folder
  261. // root, represents an internal file that should always be ignored. The file
  262. // path must be clean (i.e., in canonical shortest form).
  263. func IsInternal(file string) bool {
  264. // fs cannot import config or versioner, so we hard code .stfolder
  265. // (config.DefaultMarkerName) and .stversions (versioner.DefaultPath)
  266. internals := []string{".stfolder", ".stignore", ".stversions"}
  267. for _, internal := range internals {
  268. if file == internal {
  269. return true
  270. }
  271. if IsParent(file, internal) {
  272. return true
  273. }
  274. }
  275. return false
  276. }
  277. var (
  278. errPathInvalid = errors.New("path is invalid")
  279. errPathTraversingUpwards = errors.New("relative path traversing upwards (starting with ..)")
  280. )
  281. // Canonicalize checks that the file path is valid and returns it in the "canonical" form:
  282. // - /foo/bar -> foo/bar
  283. // - / -> "."
  284. func Canonicalize(file string) (string, error) {
  285. const pathSep = string(PathSeparator)
  286. if strings.HasPrefix(file, pathSep+pathSep) {
  287. // The relative path may pretend to be an absolute path within
  288. // the root, but the double path separator on Windows implies
  289. // something else and is out of spec.
  290. return "", errPathInvalid
  291. }
  292. // The relative path should be clean from internal dotdots and similar
  293. // funkyness.
  294. file = filepath.Clean(file)
  295. // It is not acceptable to attempt to traverse upwards.
  296. if file == ".." {
  297. return "", errPathTraversingUpwards
  298. }
  299. if strings.HasPrefix(file, ".."+pathSep) {
  300. return "", errPathTraversingUpwards
  301. }
  302. if strings.HasPrefix(file, pathSep) {
  303. if file == pathSep {
  304. return ".", nil
  305. }
  306. return file[1:], nil
  307. }
  308. return file, nil
  309. }
  310. // unwrapFilesystem removes "wrapping" filesystems to expose the filesystem of the requested wrapperType, if it exists.
  311. func unwrapFilesystem(fs Filesystem, wrapperType filesystemWrapperType) (Filesystem, bool) {
  312. var ok bool
  313. for {
  314. if fs.wrapperType() == wrapperType {
  315. return fs, true
  316. }
  317. fs, ok = fs.underlying()
  318. if !ok {
  319. return nil, false
  320. }
  321. }
  322. }
  323. // WriteFile writes data to the named file, creating it if necessary.
  324. // If the file does not exist, WriteFile creates it with permissions perm (before umask);
  325. // otherwise WriteFile truncates it before writing, without changing permissions.
  326. // Since Writefile requires multiple system calls to complete, a failure mid-operation
  327. // can leave the file in a partially written state.
  328. func WriteFile(fs Filesystem, name string, data []byte, perm FileMode) error {
  329. f, err := fs.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
  330. if err != nil {
  331. return err
  332. }
  333. _, err = f.Write(data)
  334. if err1 := f.Close(); err1 != nil && err == nil {
  335. err = err1
  336. }
  337. return err
  338. }