filesystem.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  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 XattrFilter interface {
  21. Permit(string) bool
  22. GetMaxSingleEntrySize() int
  23. GetMaxTotalSize() int
  24. }
  25. // The Filesystem interface abstracts access to the file system.
  26. type Filesystem interface {
  27. Chmod(name string, mode FileMode) error
  28. Lchown(name string, uid, gid string) error // uid/gid as strings; numeric on POSIX, SID on Windows, like in os/user package
  29. Chtimes(name string, atime time.Time, mtime time.Time) error
  30. Create(name string) (File, error)
  31. CreateSymlink(target, name string) error
  32. DirNames(name string) ([]string, error)
  33. Lstat(name string) (FileInfo, error)
  34. Mkdir(name string, perm FileMode) error
  35. MkdirAll(name string, perm FileMode) error
  36. Open(name string) (File, error)
  37. OpenFile(name string, flags int, mode FileMode) (File, error)
  38. ReadSymlink(name string) (string, error)
  39. Remove(name string) error
  40. RemoveAll(name string) error
  41. Rename(oldname, newname string) error
  42. Stat(name string) (FileInfo, error)
  43. SymlinksSupported() bool
  44. Walk(name string, walkFn WalkFunc) error
  45. // If setup fails, returns non-nil error, and if afterwards a fatal (!)
  46. // error occurs, sends that error on the channel. Afterwards this watch
  47. // can be considered stopped.
  48. Watch(path string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, <-chan error, error)
  49. Hide(name string) error
  50. Unhide(name string) error
  51. Glob(pattern string) ([]string, error)
  52. Roots() ([]string, error)
  53. Usage(name string) (Usage, error)
  54. Type() FilesystemType
  55. URI() string
  56. Options() []Option
  57. SameFile(fi1, fi2 FileInfo) bool
  58. PlatformData(name string, withOwnership, withXattrs bool, xattrFilter XattrFilter) (protocol.PlatformData, error)
  59. GetXattr(name string, xattrFilter XattrFilter) ([]protocol.Xattr, error)
  60. SetXattr(path string, xattrs []protocol.Xattr, xattrFilter XattrFilter) error
  61. }
  62. type wrappingFilesystem interface {
  63. // Used for unwrapping things
  64. underlying() (Filesystem, bool)
  65. }
  66. // The File interface abstracts access to a regular file, being a somewhat
  67. // smaller interface than os.File
  68. type File interface {
  69. io.Closer
  70. io.Reader
  71. io.ReaderAt
  72. io.Seeker
  73. io.Writer
  74. io.WriterAt
  75. Name() string
  76. Truncate(size int64) error
  77. Stat() (FileInfo, error)
  78. Sync() error
  79. }
  80. // The FileInfo interface is almost the same as os.FileInfo, but with the
  81. // Sys method removed (as we don't want to expose whatever is underlying)
  82. // and with a couple of convenience methods added.
  83. type FileInfo interface {
  84. // Standard things present in os.FileInfo
  85. Name() string
  86. Mode() FileMode
  87. Size() int64
  88. ModTime() time.Time
  89. IsDir() bool
  90. Sys() interface{}
  91. // Extensions
  92. IsRegular() bool
  93. IsSymlink() bool
  94. Owner() int
  95. Group() int
  96. InodeChangeTime() time.Time // may be zero if not supported
  97. }
  98. // FileMode is similar to os.FileMode
  99. type FileMode uint32
  100. func (fm FileMode) String() string {
  101. return os.FileMode(fm).String()
  102. }
  103. // Usage represents filesystem space usage
  104. type Usage struct {
  105. Free uint64
  106. Total uint64
  107. }
  108. type Matcher interface {
  109. Match(name string) ignoreresult.R
  110. }
  111. type Event struct {
  112. Name string
  113. Type EventType
  114. }
  115. type EventType int
  116. const (
  117. NonRemove EventType = 1 + iota
  118. Remove
  119. Mixed // Should probably not be necessary to be used in filesystem interface implementation
  120. )
  121. // Merge returns Mixed, except if evType and other are the same and not Mixed.
  122. func (evType EventType) Merge(other EventType) EventType {
  123. return evType | other
  124. }
  125. func (evType EventType) String() string {
  126. switch {
  127. case evType == NonRemove:
  128. return "non-remove"
  129. case evType == Remove:
  130. return "remove"
  131. case evType == Mixed:
  132. return "mixed"
  133. default:
  134. panic("bug: Unknown event type")
  135. }
  136. }
  137. var (
  138. ErrWatchNotSupported = errors.New("watching is not supported")
  139. ErrXattrsNotSupported = errors.New("extended attributes are not supported on this platform")
  140. )
  141. // Equivalents from os package.
  142. const (
  143. ModePerm = FileMode(os.ModePerm)
  144. ModeSetgid = FileMode(os.ModeSetgid)
  145. ModeSetuid = FileMode(os.ModeSetuid)
  146. ModeSticky = FileMode(os.ModeSticky)
  147. ModeSymlink = FileMode(os.ModeSymlink)
  148. ModeType = FileMode(os.ModeType)
  149. PathSeparator = os.PathSeparator
  150. OptAppend = os.O_APPEND
  151. OptCreate = os.O_CREATE
  152. OptExclusive = os.O_EXCL
  153. OptReadOnly = os.O_RDONLY
  154. OptReadWrite = os.O_RDWR
  155. OptSync = os.O_SYNC
  156. OptTruncate = os.O_TRUNC
  157. OptWriteOnly = os.O_WRONLY
  158. )
  159. // SkipDir is used as a return value from WalkFuncs to indicate that
  160. // the directory named in the call is to be skipped. It is not returned
  161. // as an error by any function.
  162. var SkipDir = filepath.SkipDir
  163. func IsExist(err error) bool {
  164. return errors.Is(err, ErrExist)
  165. }
  166. // ErrExist is the equivalent of os.ErrExist
  167. var ErrExist = fs.ErrExist
  168. // IsNotExist is the equivalent of os.IsNotExist
  169. func IsNotExist(err error) bool {
  170. return errors.Is(err, ErrNotExist)
  171. }
  172. // ErrNotExist is the equivalent of os.ErrNotExist
  173. var ErrNotExist = fs.ErrNotExist
  174. // IsPermission is the equivalent of os.IsPermission
  175. func IsPermission(err error) bool {
  176. return errors.Is(err, fs.ErrPermission)
  177. }
  178. // IsPathSeparator is the equivalent of os.IsPathSeparator
  179. var IsPathSeparator = os.IsPathSeparator
  180. func NewFilesystem(fsType FilesystemType, uri string, opts ...Option) Filesystem {
  181. var caseOpt Option
  182. var mtimeOpt Option
  183. i := 0
  184. for _, opt := range opts {
  185. if caseOpt != nil && mtimeOpt != nil {
  186. break
  187. }
  188. switch opt.(type) {
  189. case *OptionDetectCaseConflicts:
  190. caseOpt = opt
  191. case *optionMtime:
  192. mtimeOpt = opt
  193. default:
  194. opts[i] = opt
  195. i++
  196. }
  197. }
  198. opts = opts[:i]
  199. // Construct file system using the registered factory function
  200. var fs Filesystem
  201. var err error
  202. filesystemFactoriesMutex.Lock()
  203. fsFactory, factoryFound := filesystemFactories[fsType]
  204. filesystemFactoriesMutex.Unlock()
  205. if factoryFound {
  206. fs, err = fsFactory(uri, opts...)
  207. } else {
  208. err = fmt.Errorf("File system type '%s' not recognized", fsType)
  209. }
  210. if err != nil {
  211. fs = &errorFilesystem{
  212. fsType: fsType,
  213. uri: uri,
  214. err: err,
  215. }
  216. }
  217. // mtime handling should happen inside walking, as filesystem calls while
  218. // walking should be mtime-resolved too
  219. if mtimeOpt != nil {
  220. fs = mtimeOpt.apply(fs)
  221. }
  222. fs = &metricsFS{next: fs}
  223. layersAboveWalkFilesystem := 0
  224. if caseOpt != nil {
  225. // DirNames calls made to check the case of a name will also be
  226. // attributed to the calling function.
  227. layersAboveWalkFilesystem++
  228. }
  229. if l.ShouldDebug("walkfs") {
  230. // A walkFilesystem is not a layer to skip, it embeds the underlying
  231. // filesystem, passing calls directly trough. Except for calls made
  232. // during walking, however those are truly originating in the walk
  233. // filesystem.
  234. fs = NewWalkFilesystem(newLogFilesystem(fs, layersAboveWalkFilesystem))
  235. } else if l.ShouldDebug("fs") {
  236. fs = newLogFilesystem(NewWalkFilesystem(fs), layersAboveWalkFilesystem)
  237. } else {
  238. fs = NewWalkFilesystem(fs)
  239. }
  240. // Case handling is at the outermost layer to resolve all input names.
  241. // Reason being is that the only names/paths that are potentially "wrong"
  242. // come from outside the fs package. Any paths that result from filesystem
  243. // operations itself already have the correct case. Thus there's e.g. no
  244. // point to check the case on all the stating the walk filesystem does, it
  245. // just adds overhead.
  246. if caseOpt != nil {
  247. fs = caseOpt.apply(fs)
  248. }
  249. return fs
  250. }
  251. // IsInternal returns true if the file, as a path relative to the folder
  252. // root, represents an internal file that should always be ignored. The file
  253. // path must be clean (i.e., in canonical shortest form).
  254. func IsInternal(file string) bool {
  255. // fs cannot import config or versioner, so we hard code .stfolder
  256. // (config.DefaultMarkerName) and .stversions (versioner.DefaultPath)
  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 wrapper type T, if it exists.
  302. func unwrapFilesystem[T Filesystem](fs Filesystem) (T, bool) {
  303. for {
  304. if unwrapped, ok := fs.(T); ok {
  305. return unwrapped, true
  306. }
  307. wrappingFs, ok := fs.(wrappingFilesystem)
  308. if !ok {
  309. var x T
  310. return x, false
  311. }
  312. fs, ok = wrappingFs.underlying()
  313. if !ok {
  314. var x T
  315. return x, false
  316. }
  317. }
  318. }
  319. // WriteFile writes data to the named file, creating it if necessary.
  320. // If the file does not exist, WriteFile creates it with permissions perm (before umask);
  321. // otherwise WriteFile truncates it before writing, without changing permissions.
  322. // Since Writefile requires multiple system calls to complete, a failure mid-operation
  323. // can leave the file in a partially written state.
  324. func WriteFile(fs Filesystem, name string, data []byte, perm FileMode) error {
  325. f, err := fs.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
  326. if err != nil {
  327. return err
  328. }
  329. _, err = f.Write(data)
  330. if err1 := f.Close(); err1 != nil && err == nil {
  331. err = err1
  332. }
  333. return err
  334. }