basicfs.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  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. "errors"
  9. "fmt"
  10. "os"
  11. "path/filepath"
  12. "runtime"
  13. "strings"
  14. "time"
  15. "github.com/shirou/gopsutil/disk"
  16. )
  17. var (
  18. ErrInvalidFilename = errors.New("filename is invalid")
  19. ErrNotRelative = errors.New("not a relative path")
  20. )
  21. // The BasicFilesystem implements all aspects by delegating to package os.
  22. // All paths are relative to the root and cannot (should not) escape the root directory.
  23. type BasicFilesystem struct {
  24. root string
  25. }
  26. func newBasicFilesystem(root string) *BasicFilesystem {
  27. // The reason it's done like this:
  28. // C: -> C:\ -> C:\ (issue that this is trying to fix)
  29. // C:\somedir -> C:\somedir\ -> C:\somedir
  30. // C:\somedir\ -> C:\somedir\\ -> C:\somedir
  31. // This way in the tests, we get away without OS specific separators
  32. // in the test configs.
  33. sep := string(filepath.Separator)
  34. root = filepath.Dir(root + sep)
  35. // Attempt tilde expansion; leave unchanged in case of error
  36. if path, err := ExpandTilde(root); err == nil {
  37. root = path
  38. }
  39. // Attempt absolutification; leave unchanged in case of error
  40. if !filepath.IsAbs(root) {
  41. // Abs() looks like a fairly expensive syscall on Windows, while
  42. // IsAbs() is a whole bunch of string mangling. I think IsAbs() may be
  43. // somewhat faster in the general case, hence the outer if...
  44. if path, err := filepath.Abs(root); err == nil {
  45. root = path
  46. }
  47. }
  48. // Attempt to enable long filename support on Windows. We may still not
  49. // have an absolute path here if the previous steps failed.
  50. if runtime.GOOS == "windows" {
  51. root = longFilenameSupport(root)
  52. }
  53. return &BasicFilesystem{root}
  54. }
  55. // rooted expands the relative path to the full path that is then used with os
  56. // package. If the relative path somehow causes the final path to escape the root
  57. // directory, this returns an error, to prevent accessing files that are not in the
  58. // shared directory.
  59. func (f *BasicFilesystem) rooted(rel string) (string, error) {
  60. return rooted(rel, f.root)
  61. }
  62. func rooted(rel, root string) (string, error) {
  63. // The root must not be empty.
  64. if root == "" {
  65. return "", ErrInvalidFilename
  66. }
  67. var err error
  68. // Takes care that rel does not try to escape
  69. rel, err = Canonicalize(rel)
  70. if err != nil {
  71. return "", err
  72. }
  73. return filepath.Join(root, rel), nil
  74. }
  75. func (f *BasicFilesystem) unrooted(path string) string {
  76. return rel(path, f.root)
  77. }
  78. func (f *BasicFilesystem) Chmod(name string, mode FileMode) error {
  79. name, err := f.rooted(name)
  80. if err != nil {
  81. return err
  82. }
  83. return os.Chmod(name, os.FileMode(mode))
  84. }
  85. func (f *BasicFilesystem) Lchown(name string, uid, gid int) error {
  86. name, err := f.rooted(name)
  87. if err != nil {
  88. return err
  89. }
  90. return os.Lchown(name, uid, gid)
  91. }
  92. func (f *BasicFilesystem) Chtimes(name string, atime time.Time, mtime time.Time) error {
  93. name, err := f.rooted(name)
  94. if err != nil {
  95. return err
  96. }
  97. return os.Chtimes(name, atime, mtime)
  98. }
  99. func (f *BasicFilesystem) Mkdir(name string, perm FileMode) error {
  100. name, err := f.rooted(name)
  101. if err != nil {
  102. return err
  103. }
  104. return os.Mkdir(name, os.FileMode(perm))
  105. }
  106. // MkdirAll creates a directory named path, along with any necessary parents,
  107. // and returns nil, or else returns an error.
  108. // The permission bits perm are used for all directories that MkdirAll creates.
  109. // If path is already a directory, MkdirAll does nothing and returns nil.
  110. func (f *BasicFilesystem) MkdirAll(path string, perm FileMode) error {
  111. path, err := f.rooted(path)
  112. if err != nil {
  113. return err
  114. }
  115. return f.mkdirAll(path, os.FileMode(perm))
  116. }
  117. func (f *BasicFilesystem) Lstat(name string) (FileInfo, error) {
  118. name, err := f.rooted(name)
  119. if err != nil {
  120. return nil, err
  121. }
  122. fi, err := underlyingLstat(name)
  123. if err != nil {
  124. return nil, err
  125. }
  126. return basicFileInfo{fi}, err
  127. }
  128. func (f *BasicFilesystem) Remove(name string) error {
  129. name, err := f.rooted(name)
  130. if err != nil {
  131. return err
  132. }
  133. return os.Remove(name)
  134. }
  135. func (f *BasicFilesystem) RemoveAll(name string) error {
  136. name, err := f.rooted(name)
  137. if err != nil {
  138. return err
  139. }
  140. return os.RemoveAll(name)
  141. }
  142. func (f *BasicFilesystem) Rename(oldpath, newpath string) error {
  143. oldpath, err := f.rooted(oldpath)
  144. if err != nil {
  145. return err
  146. }
  147. newpath, err = f.rooted(newpath)
  148. if err != nil {
  149. return err
  150. }
  151. return os.Rename(oldpath, newpath)
  152. }
  153. func (f *BasicFilesystem) Stat(name string) (FileInfo, error) {
  154. name, err := f.rooted(name)
  155. if err != nil {
  156. return nil, err
  157. }
  158. fi, err := os.Stat(name)
  159. if err != nil {
  160. return nil, err
  161. }
  162. return basicFileInfo{fi}, err
  163. }
  164. func (f *BasicFilesystem) DirNames(name string) ([]string, error) {
  165. name, err := f.rooted(name)
  166. if err != nil {
  167. return nil, err
  168. }
  169. fd, err := os.OpenFile(name, OptReadOnly, 0777)
  170. if err != nil {
  171. return nil, err
  172. }
  173. defer fd.Close()
  174. names, err := fd.Readdirnames(-1)
  175. if err != nil {
  176. return nil, err
  177. }
  178. return names, nil
  179. }
  180. func (f *BasicFilesystem) Open(name string) (File, error) {
  181. rootedName, err := f.rooted(name)
  182. if err != nil {
  183. return nil, err
  184. }
  185. fd, err := os.Open(rootedName)
  186. if err != nil {
  187. return nil, err
  188. }
  189. return basicFile{fd, name}, err
  190. }
  191. func (f *BasicFilesystem) OpenFile(name string, flags int, mode FileMode) (File, error) {
  192. rootedName, err := f.rooted(name)
  193. if err != nil {
  194. return nil, err
  195. }
  196. fd, err := os.OpenFile(rootedName, flags, os.FileMode(mode))
  197. if err != nil {
  198. return nil, err
  199. }
  200. return basicFile{fd, name}, err
  201. }
  202. func (f *BasicFilesystem) Create(name string) (File, error) {
  203. rootedName, err := f.rooted(name)
  204. if err != nil {
  205. return nil, err
  206. }
  207. fd, err := os.Create(rootedName)
  208. if err != nil {
  209. return nil, err
  210. }
  211. return basicFile{fd, name}, err
  212. }
  213. func (f *BasicFilesystem) Walk(root string, walkFn WalkFunc) error {
  214. // implemented in WalkFilesystem
  215. return errors.New("not implemented")
  216. }
  217. func (f *BasicFilesystem) Glob(pattern string) ([]string, error) {
  218. pattern, err := f.rooted(pattern)
  219. if err != nil {
  220. return nil, err
  221. }
  222. files, err := filepath.Glob(pattern)
  223. unrooted := make([]string, len(files))
  224. for i := range files {
  225. unrooted[i] = f.unrooted(files[i])
  226. }
  227. return unrooted, err
  228. }
  229. func (f *BasicFilesystem) Usage(name string) (Usage, error) {
  230. name, err := f.rooted(name)
  231. if err != nil {
  232. return Usage{}, err
  233. }
  234. u, err := disk.Usage(name)
  235. if err != nil {
  236. return Usage{}, err
  237. }
  238. return Usage{
  239. Free: int64(u.Free),
  240. Total: int64(u.Total),
  241. }, nil
  242. }
  243. func (f *BasicFilesystem) Type() FilesystemType {
  244. return FilesystemTypeBasic
  245. }
  246. func (f *BasicFilesystem) URI() string {
  247. return strings.TrimPrefix(f.root, `\\?\`)
  248. }
  249. func (f *BasicFilesystem) SameFile(fi1, fi2 FileInfo) bool {
  250. // Like os.SameFile, we always return false unless fi1 and fi2 were created
  251. // by this package's Stat/Lstat method.
  252. f1, ok1 := fi1.(basicFileInfo)
  253. f2, ok2 := fi2.(basicFileInfo)
  254. if !ok1 || !ok2 {
  255. return false
  256. }
  257. return os.SameFile(f1.FileInfo, f2.FileInfo)
  258. }
  259. // basicFile implements the fs.File interface on top of an os.File
  260. type basicFile struct {
  261. *os.File
  262. name string
  263. }
  264. func (f basicFile) Name() string {
  265. return f.name
  266. }
  267. func (f basicFile) Stat() (FileInfo, error) {
  268. info, err := f.File.Stat()
  269. if err != nil {
  270. return nil, err
  271. }
  272. return basicFileInfo{info}, nil
  273. }
  274. // basicFileInfo implements the fs.FileInfo interface on top of an os.FileInfo.
  275. type basicFileInfo struct {
  276. os.FileInfo
  277. }
  278. func (e basicFileInfo) IsSymlink() bool {
  279. // Must use basicFileInfo.Mode() because it may apply magic.
  280. return e.Mode()&ModeSymlink != 0
  281. }
  282. func (e basicFileInfo) IsRegular() bool {
  283. // Must use basicFileInfo.Mode() because it may apply magic.
  284. return e.Mode()&ModeType == 0
  285. }
  286. // longFilenameSupport adds the necessary prefix to the path to enable long
  287. // filename support on windows if necessary.
  288. // This does NOT check the current system, i.e. will also take effect on unix paths.
  289. func longFilenameSupport(path string) string {
  290. if filepath.IsAbs(path) && !strings.HasPrefix(path, `\\`) {
  291. return `\\?\` + path
  292. }
  293. return path
  294. }
  295. type ErrWatchEventOutsideRoot struct{ msg string }
  296. func (e *ErrWatchEventOutsideRoot) Error() string {
  297. return e.msg
  298. }
  299. func (f *BasicFilesystem) newErrWatchEventOutsideRoot(absPath string, roots []string) *ErrWatchEventOutsideRoot {
  300. return &ErrWatchEventOutsideRoot{fmt.Sprintf("Watching for changes encountered an event outside of the filesystem root: f.root==%v, roots==%v, path==%v. This should never happen, please report this message to forum.syncthing.net.", f.root, roots, absPath)}
  301. }