fakefs.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029
  1. // Copyright (C) 2018 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. "hash/fnv"
  12. "io"
  13. "math/rand"
  14. "net/url"
  15. "os"
  16. "os/user"
  17. "path/filepath"
  18. "strconv"
  19. "strings"
  20. "sync"
  21. "testing"
  22. "time"
  23. "github.com/syncthing/syncthing/lib/protocol"
  24. )
  25. const FilesystemTypeFake FilesystemType = "fake"
  26. func init() {
  27. RegisterFilesystemType(FilesystemTypeFake, func(root string, opts ...Option) (Filesystem, error) {
  28. return newFakeFilesystem(root, opts...), nil
  29. })
  30. }
  31. // see readShortAt()
  32. const randomBlockShift = 14 // 128k
  33. // fakeFS is a fake filesystem for testing and benchmarking. It has the
  34. // following properties:
  35. //
  36. // - File metadata is kept in RAM. Specifically, we remember which files and
  37. // directories exist, their dates, permissions and sizes. Symlinks are
  38. // not supported.
  39. //
  40. // - File contents are generated pseudorandomly with just the file name as
  41. // seed. Writes are discarded, other than having the effect of increasing
  42. // the file size. If you only write data that you've read from a file with
  43. // the same name on a different fakeFS, you'll never know the difference...
  44. //
  45. // - We totally ignore permissions - pretend you are root.
  46. //
  47. // - The root path can contain URL query-style parameters that pre populate
  48. // the filesystem at creation with a certain amount of random data:
  49. //
  50. // files=n to generate n random files (default 0)
  51. // maxsize=n to generate files up to a total of n MiB (default 0)
  52. // sizeavg=n to set the average size of random files, in bytes (default 1<<20)
  53. // seed=n to set the initial random seed (default 0)
  54. // insens=b "true" makes filesystem case-insensitive Windows- or OSX-style (default false)
  55. // latency=d to set the amount of time each "disk" operation takes, where d is time.ParseDuration format
  56. // content=true to save actual file contents instead of generating pseudorandomly; n.b. memory usage
  57. // nostfolder=true skip the creation of .stfolder
  58. // timeprecisionsecond=true Modification times are stored with only second precision
  59. //
  60. // - Two fakeFS:s pointing at the same root path see the same files.
  61. type fakeFS struct {
  62. counters fakeFSCounters
  63. uri string
  64. mut sync.Mutex
  65. root *fakeEntry
  66. insens bool
  67. withContent bool
  68. timePrecisionSecond bool
  69. latency time.Duration
  70. userCache *userCache
  71. groupCache *groupCache
  72. }
  73. type fakeFSCounters struct {
  74. Chmod int64
  75. Lchown int64
  76. Chtimes int64
  77. Create int64
  78. DirNames int64
  79. Lstat int64
  80. Mkdir int64
  81. MkdirAll int64
  82. Open int64
  83. OpenFile int64
  84. ReadSymlink int64
  85. Remove int64
  86. RemoveAll int64
  87. Rename int64
  88. }
  89. var (
  90. fakeFSMut sync.Mutex
  91. fakeFSCache = make(map[string]*fakeFS)
  92. )
  93. func newFakeFilesystem(rootURI string, _ ...Option) *fakeFS {
  94. fakeFSMut.Lock()
  95. defer fakeFSMut.Unlock()
  96. var params url.Values
  97. uri, err := url.Parse(rootURI)
  98. if err == nil {
  99. params = uri.Query()
  100. }
  101. if fs, ok := fakeFSCache[rootURI]; ok {
  102. // Already have an fs at this path
  103. return fs
  104. }
  105. fs := &fakeFS{
  106. uri: "fake://" + rootURI,
  107. root: &fakeEntry{
  108. name: "/",
  109. entryType: fakeEntryTypeDir,
  110. mode: 0o700,
  111. mtime: time.Now(),
  112. children: make(map[string]*fakeEntry),
  113. },
  114. userCache: newValueCache(time.Hour, user.LookupId),
  115. groupCache: newValueCache(time.Hour, user.LookupGroupId),
  116. }
  117. files, _ := strconv.Atoi(params.Get("files"))
  118. maxsize, _ := strconv.Atoi(params.Get("maxsize"))
  119. sizeavg, _ := strconv.Atoi(params.Get("sizeavg"))
  120. seed, _ := strconv.Atoi(params.Get("seed"))
  121. fs.insens = params.Get("insens") == "true"
  122. fs.withContent = params.Get("content") == "true"
  123. nostfolder := params.Get("nostfolder") == "true"
  124. fs.timePrecisionSecond = params.Get("timeprecisionsecond") == "true"
  125. if sizeavg == 0 {
  126. sizeavg = 1 << 20
  127. }
  128. if files > 0 || maxsize > 0 {
  129. // Generate initial data according to specs. Operations in here
  130. // *look* like file I/O, but they are not. Do not worry that they
  131. // might fail.
  132. rng := rand.New(rand.NewSource(int64(seed))) //nolint:gosec
  133. var createdFiles int
  134. var writtenData int64
  135. for (files == 0 || createdFiles < files) && (maxsize == 0 || writtenData>>20 < int64(maxsize)) {
  136. dir := filepath.Join(fmt.Sprintf("%02x", rng.Intn(255)), fmt.Sprintf("%02x", rng.Intn(255)))
  137. file := fmt.Sprintf("%016x", rng.Int63())
  138. _ = fs.MkdirAll(dir, 0o755)
  139. fd, _ := fs.Create(filepath.Join(dir, file))
  140. createdFiles++
  141. fsize := int64(sizeavg/2 + rng.Intn(sizeavg))
  142. _ = fd.Truncate(fsize)
  143. writtenData += fsize
  144. ftime := time.Unix(1000000000+rng.Int63n(10*365*86400), 0)
  145. _ = fs.Chtimes(filepath.Join(dir, file), ftime, ftime)
  146. }
  147. }
  148. if !nostfolder {
  149. // Also create a default folder marker for good measure
  150. _ = fs.Mkdir(".stfolder", 0o700)
  151. }
  152. // We only set the latency after doing the operations required to create
  153. // the filesystem initially.
  154. fs.latency, _ = time.ParseDuration(params.Get("latency"))
  155. fakeFSCache[rootURI] = fs
  156. return fs
  157. }
  158. type fakeEntryType int
  159. const (
  160. fakeEntryTypeFile fakeEntryType = iota
  161. fakeEntryTypeDir
  162. fakeEntryTypeSymlink
  163. )
  164. // fakeEntry is an entry (file or directory) in the fake filesystem
  165. type fakeEntry struct {
  166. name string
  167. entryType fakeEntryType
  168. dest string // for symlinks
  169. size int64
  170. mode FileMode
  171. uid int
  172. gid int
  173. mtime time.Time
  174. children map[string]*fakeEntry
  175. content []byte
  176. }
  177. func (fs *fakeFS) entryForName(name string) *fakeEntry {
  178. if fs.insens {
  179. name = UnicodeLowercaseNormalized(name)
  180. }
  181. name = filepath.ToSlash(name)
  182. if name == "." || name == "/" {
  183. return fs.root
  184. }
  185. name = strings.Trim(name, "/")
  186. comps := strings.Split(name, "/")
  187. entry := fs.root
  188. for i, comp := range comps {
  189. if entry.entryType != fakeEntryTypeDir {
  190. return nil
  191. }
  192. var ok bool
  193. entry, ok = entry.children[comp]
  194. if !ok {
  195. return nil
  196. }
  197. if i < len(comps)-1 && entry.entryType == fakeEntryTypeSymlink {
  198. // only absolute link targets are supported, and we assume
  199. // lookup is Lstat-kind so we only resolve symlinks when they
  200. // are not the last path component.
  201. return fs.entryForName(entry.dest)
  202. }
  203. }
  204. return entry
  205. }
  206. func (fs *fakeFS) Chmod(name string, mode FileMode) error {
  207. fs.mut.Lock()
  208. defer fs.mut.Unlock()
  209. fs.counters.Chmod++
  210. time.Sleep(fs.latency)
  211. entry := fs.entryForName(name)
  212. if entry == nil {
  213. return os.ErrNotExist
  214. }
  215. entry.mode = mode
  216. return nil
  217. }
  218. func (fs *fakeFS) Lchown(name, uid, gid string) error {
  219. fs.mut.Lock()
  220. defer fs.mut.Unlock()
  221. fs.counters.Lchown++
  222. time.Sleep(fs.latency)
  223. entry := fs.entryForName(name)
  224. if entry == nil {
  225. return os.ErrNotExist
  226. }
  227. entry.uid, _ = strconv.Atoi(uid)
  228. entry.gid, _ = strconv.Atoi(gid)
  229. return nil
  230. }
  231. func (fs *fakeFS) Chtimes(name string, _ time.Time, mtime time.Time) error {
  232. fs.mut.Lock()
  233. defer fs.mut.Unlock()
  234. fs.counters.Chtimes++
  235. time.Sleep(fs.latency)
  236. entry := fs.entryForName(name)
  237. if entry == nil {
  238. return os.ErrNotExist
  239. }
  240. if fs.timePrecisionSecond {
  241. mtime = mtime.Truncate(time.Second)
  242. }
  243. entry.mtime = mtime
  244. return nil
  245. }
  246. func (fs *fakeFS) create(name string) (*fakeEntry, error) {
  247. fs.mut.Lock()
  248. defer fs.mut.Unlock()
  249. fs.counters.Create++
  250. time.Sleep(fs.latency)
  251. if entry := fs.entryForName(name); entry != nil {
  252. switch entry.entryType {
  253. case fakeEntryTypeDir:
  254. return nil, os.ErrExist
  255. case fakeEntryTypeSymlink:
  256. return nil, errors.New("following symlink not supported")
  257. }
  258. entry.size = 0
  259. entry.mtime = time.Now()
  260. entry.mode = 0o666
  261. entry.content = nil
  262. if fs.withContent {
  263. entry.content = make([]byte, 0)
  264. }
  265. return entry, nil
  266. }
  267. dir := filepath.Dir(name)
  268. base := filepath.Base(name)
  269. entry := fs.entryForName(dir)
  270. if entry == nil {
  271. return nil, os.ErrNotExist
  272. }
  273. new := &fakeEntry{
  274. name: base,
  275. mode: 0o666,
  276. mtime: time.Now(),
  277. }
  278. if fs.insens {
  279. base = UnicodeLowercaseNormalized(base)
  280. }
  281. if fs.withContent {
  282. new.content = make([]byte, 0)
  283. }
  284. entry.children[base] = new
  285. return new, nil
  286. }
  287. func (fs *fakeFS) Create(name string) (File, error) {
  288. entry, err := fs.create(name)
  289. if err != nil {
  290. return nil, err
  291. }
  292. if fs.insens {
  293. return &fakeFile{fakeEntry: entry, presentedName: filepath.Base(name), mut: &fs.mut}, nil
  294. }
  295. return &fakeFile{fakeEntry: entry, mut: &fs.mut}, nil
  296. }
  297. func (fs *fakeFS) CreateSymlink(target, name string) error {
  298. entry, err := fs.create(name)
  299. if err != nil {
  300. return err
  301. }
  302. entry.entryType = fakeEntryTypeSymlink
  303. entry.dest = target
  304. return nil
  305. }
  306. func (fs *fakeFS) DirNames(name string) ([]string, error) {
  307. fs.mut.Lock()
  308. defer fs.mut.Unlock()
  309. fs.counters.DirNames++
  310. time.Sleep(fs.latency)
  311. entry := fs.entryForName(name)
  312. if entry == nil {
  313. return nil, os.ErrNotExist
  314. }
  315. names := make([]string, 0, len(entry.children))
  316. for _, child := range entry.children {
  317. names = append(names, child.name)
  318. }
  319. return names, nil
  320. }
  321. func (fs *fakeFS) Lstat(name string) (FileInfo, error) {
  322. fs.mut.Lock()
  323. defer fs.mut.Unlock()
  324. fs.counters.Lstat++
  325. time.Sleep(fs.latency)
  326. entry := fs.entryForName(name)
  327. if entry == nil {
  328. return nil, os.ErrNotExist
  329. }
  330. info := &fakeFileInfo{*entry}
  331. info.content = nil
  332. info.children = nil
  333. if fs.insens {
  334. info.name = filepath.Base(name)
  335. }
  336. return info, nil
  337. }
  338. func (fs *fakeFS) Mkdir(name string, perm FileMode) error {
  339. fs.mut.Lock()
  340. defer fs.mut.Unlock()
  341. fs.counters.Mkdir++
  342. time.Sleep(fs.latency)
  343. dir := filepath.Dir(name)
  344. base := filepath.Base(name)
  345. entry := fs.entryForName(dir)
  346. key := base
  347. if entry == nil {
  348. return os.ErrNotExist
  349. }
  350. if entry.entryType != fakeEntryTypeDir {
  351. return os.ErrExist
  352. }
  353. if fs.insens {
  354. key = UnicodeLowercaseNormalized(key)
  355. }
  356. if _, ok := entry.children[key]; ok {
  357. return os.ErrExist
  358. }
  359. entry.children[key] = &fakeEntry{
  360. name: base,
  361. entryType: fakeEntryTypeDir,
  362. mode: perm,
  363. mtime: time.Now(),
  364. children: make(map[string]*fakeEntry),
  365. }
  366. return nil
  367. }
  368. func (fs *fakeFS) MkdirAll(name string, perm FileMode) error {
  369. fs.mut.Lock()
  370. defer fs.mut.Unlock()
  371. fs.counters.MkdirAll++
  372. time.Sleep(fs.latency)
  373. name = filepath.ToSlash(name)
  374. name = strings.Trim(name, "/")
  375. comps := strings.Split(name, "/")
  376. entry := fs.root
  377. for _, comp := range comps {
  378. key := comp
  379. if fs.insens {
  380. key = UnicodeLowercaseNormalized(key)
  381. }
  382. next, ok := entry.children[key]
  383. if !ok {
  384. new := &fakeEntry{
  385. name: comp,
  386. entryType: fakeEntryTypeDir,
  387. mode: perm,
  388. mtime: time.Now(),
  389. children: make(map[string]*fakeEntry),
  390. }
  391. entry.children[key] = new
  392. next = new
  393. } else if next.entryType != fakeEntryTypeDir {
  394. return errors.New("not a directory")
  395. }
  396. entry = next
  397. }
  398. return nil
  399. }
  400. func (fs *fakeFS) Open(name string) (File, error) {
  401. fs.mut.Lock()
  402. defer fs.mut.Unlock()
  403. fs.counters.Open++
  404. time.Sleep(fs.latency)
  405. entry := fs.entryForName(name)
  406. if entry == nil || entry.entryType != fakeEntryTypeFile {
  407. return nil, os.ErrNotExist
  408. }
  409. if fs.insens {
  410. return &fakeFile{fakeEntry: entry, presentedName: filepath.Base(name), mut: &fs.mut}, nil
  411. }
  412. return &fakeFile{fakeEntry: entry, mut: &fs.mut}, nil
  413. }
  414. func (fs *fakeFS) OpenFile(name string, flags int, mode FileMode) (File, error) {
  415. if flags&os.O_CREATE == 0 {
  416. return fs.Open(name)
  417. }
  418. fs.mut.Lock()
  419. defer fs.mut.Unlock()
  420. fs.counters.OpenFile++
  421. time.Sleep(fs.latency)
  422. dir := filepath.Dir(name)
  423. base := filepath.Base(name)
  424. entry := fs.entryForName(dir)
  425. key := base
  426. if entry == nil {
  427. return nil, os.ErrNotExist
  428. } else if entry.entryType != fakeEntryTypeDir {
  429. return nil, errors.New("not a directory")
  430. }
  431. if fs.insens {
  432. key = UnicodeLowercaseNormalized(key)
  433. }
  434. if flags&os.O_EXCL != 0 {
  435. if _, ok := entry.children[key]; ok {
  436. return nil, os.ErrExist
  437. }
  438. }
  439. newEntry := &fakeEntry{
  440. name: base,
  441. mode: mode,
  442. mtime: time.Now(),
  443. }
  444. if fs.withContent {
  445. newEntry.content = make([]byte, 0)
  446. }
  447. entry.children[key] = newEntry
  448. return &fakeFile{fakeEntry: newEntry, mut: &fs.mut}, nil
  449. }
  450. func (fs *fakeFS) ReadSymlink(name string) (string, error) {
  451. fs.mut.Lock()
  452. defer fs.mut.Unlock()
  453. fs.counters.ReadSymlink++
  454. time.Sleep(fs.latency)
  455. entry := fs.entryForName(name)
  456. if entry == nil {
  457. return "", os.ErrNotExist
  458. } else if entry.entryType != fakeEntryTypeSymlink {
  459. return "", errors.New("not a symlink")
  460. }
  461. return entry.dest, nil
  462. }
  463. func (fs *fakeFS) Remove(name string) error {
  464. fs.mut.Lock()
  465. defer fs.mut.Unlock()
  466. fs.counters.Remove++
  467. time.Sleep(fs.latency)
  468. if fs.insens {
  469. name = UnicodeLowercaseNormalized(name)
  470. }
  471. entry := fs.entryForName(name)
  472. if entry == nil {
  473. return os.ErrNotExist
  474. }
  475. if len(entry.children) != 0 {
  476. return errors.New("not empty")
  477. }
  478. entry = fs.entryForName(filepath.Dir(name))
  479. delete(entry.children, filepath.Base(name))
  480. return nil
  481. }
  482. func (fs *fakeFS) RemoveAll(name string) error {
  483. fs.mut.Lock()
  484. defer fs.mut.Unlock()
  485. fs.counters.RemoveAll++
  486. time.Sleep(fs.latency)
  487. if fs.insens {
  488. name = UnicodeLowercaseNormalized(name)
  489. }
  490. entry := fs.entryForName(filepath.Dir(name))
  491. if entry == nil {
  492. return nil // all tested real systems exhibit this behaviour
  493. }
  494. // RemoveAll is easy when the file system uses garbage collection under
  495. // the hood... We even get the correct semantics for open fd:s for free.
  496. delete(entry.children, filepath.Base(name))
  497. return nil
  498. }
  499. func (fs *fakeFS) Rename(oldname, newname string) error {
  500. fs.mut.Lock()
  501. defer fs.mut.Unlock()
  502. fs.counters.Rename++
  503. time.Sleep(fs.latency)
  504. oldKey := filepath.Base(oldname)
  505. newKey := filepath.Base(newname)
  506. if fs.insens {
  507. oldKey = UnicodeLowercaseNormalized(oldKey)
  508. newKey = UnicodeLowercaseNormalized(newKey)
  509. }
  510. p0 := fs.entryForName(filepath.Dir(oldname))
  511. if p0 == nil {
  512. return os.ErrNotExist
  513. }
  514. entry := p0.children[oldKey]
  515. if entry == nil {
  516. return os.ErrNotExist
  517. }
  518. p1 := fs.entryForName(filepath.Dir(newname))
  519. if p1 == nil {
  520. return os.ErrNotExist
  521. }
  522. dst, ok := p1.children[newKey]
  523. if ok {
  524. if fs.insens && newKey == oldKey {
  525. // case-only in-place rename
  526. entry.name = filepath.Base(newname)
  527. return nil
  528. }
  529. if dst.entryType == fakeEntryTypeDir {
  530. return errors.New("is a directory")
  531. }
  532. }
  533. p1.children[newKey] = entry
  534. entry.name = filepath.Base(newname)
  535. delete(p0.children, oldKey)
  536. return nil
  537. }
  538. func (fs *fakeFS) Stat(name string) (FileInfo, error) {
  539. return fs.Lstat(name)
  540. }
  541. func (*fakeFS) SymlinksSupported() bool {
  542. return false
  543. }
  544. func (*fakeFS) Walk(_ string, _ WalkFunc) error {
  545. return errors.New("not implemented")
  546. }
  547. func (*fakeFS) Watch(_ string, _ Matcher, _ context.Context, _ bool) (<-chan Event, <-chan error, error) {
  548. return nil, nil, ErrWatchNotSupported
  549. }
  550. func (*fakeFS) Hide(_ string) error {
  551. return nil
  552. }
  553. func (*fakeFS) Unhide(_ string) error {
  554. return nil
  555. }
  556. func (*fakeFS) GetXattr(_ string, _ XattrFilter) ([]protocol.Xattr, error) {
  557. return nil, nil
  558. }
  559. func (*fakeFS) SetXattr(_ string, _ []protocol.Xattr, _ XattrFilter) error {
  560. return nil
  561. }
  562. // A basic glob-implementation that should be able to handle
  563. // simple test cases.
  564. func (fs *fakeFS) Glob(pattern string) ([]string, error) {
  565. dir := filepath.Dir(pattern)
  566. file := filepath.Base(pattern)
  567. if _, err := fs.Lstat(dir); err != nil {
  568. return nil, errPathInvalid
  569. }
  570. var matches []string
  571. names, err := fs.DirNames(dir)
  572. if err != nil {
  573. return nil, err
  574. }
  575. for _, n := range names {
  576. matched, err := filepath.Match(file, n)
  577. if err != nil {
  578. return nil, err
  579. }
  580. if matched {
  581. matches = append(matches, filepath.Join(dir, n))
  582. }
  583. }
  584. return matches, err
  585. }
  586. func (*fakeFS) Roots() ([]string, error) {
  587. return []string{"/"}, nil
  588. }
  589. func (*fakeFS) Usage(_ string) (Usage, error) {
  590. return Usage{}, errors.New("not implemented")
  591. }
  592. func (*fakeFS) Type() FilesystemType {
  593. return FilesystemTypeFake
  594. }
  595. func (fs *fakeFS) URI() string {
  596. return fs.uri
  597. }
  598. func (*fakeFS) Options() []Option {
  599. return nil
  600. }
  601. func (fs *fakeFS) SameFile(fi1, fi2 FileInfo) bool {
  602. // BUG: real systems base file sameness on path, inodes, etc
  603. // we try our best, but FileInfo just doesn't have enough data
  604. // so there be false positives, especially on Windows
  605. // where ModTime is not that precise
  606. var ok bool
  607. if fs.insens {
  608. ok = UnicodeLowercaseNormalized(fi1.Name()) == UnicodeLowercaseNormalized(fi2.Name())
  609. } else {
  610. ok = fi1.Name() == fi2.Name()
  611. }
  612. return ok && fi1.ModTime().Equal(fi2.ModTime()) && fi1.Mode() == fi2.Mode() && fi1.IsDir() == fi2.IsDir() && fi1.IsRegular() == fi2.IsRegular() && fi1.IsSymlink() == fi2.IsSymlink() && fi1.Owner() == fi2.Owner() && fi1.Group() == fi2.Group()
  613. }
  614. func (fs *fakeFS) PlatformData(name string, scanOwnership, scanXattrs bool, xattrFilter XattrFilter) (protocol.PlatformData, error) {
  615. return unixPlatformData(fs, name, fs.userCache, fs.groupCache, scanOwnership, scanXattrs, xattrFilter)
  616. }
  617. func (*fakeFS) underlying() (Filesystem, bool) {
  618. return nil, false
  619. }
  620. func (fs *fakeFS) resetCounters() {
  621. fs.mut.Lock()
  622. fs.counters = fakeFSCounters{}
  623. fs.mut.Unlock()
  624. }
  625. func (fs *fakeFS) reportMetricsPerOp(b *testing.B) {
  626. b.Helper()
  627. fs.reportMetricsPer(b, 1, "op")
  628. }
  629. func (fs *fakeFS) reportMetricsPer(b *testing.B, divisor float64, unit string) {
  630. fs.mut.Lock()
  631. defer fs.mut.Unlock()
  632. b.ReportMetric(float64(fs.counters.Lstat)/divisor/float64(b.N), "Lstat/"+unit)
  633. b.ReportMetric(float64(fs.counters.DirNames)/divisor/float64(b.N), "DirNames/"+unit)
  634. }
  635. // fakeFile is the representation of an open file. We don't care if it's
  636. // opened for reading or writing, it's all good.
  637. type fakeFile struct {
  638. *fakeEntry
  639. mut *sync.Mutex
  640. rng io.Reader
  641. seed int64
  642. offset int64
  643. seedOffs int64
  644. presentedName string // present (i.e. != "") on insensitive fs only
  645. }
  646. func (*fakeFile) Close() error {
  647. return nil
  648. }
  649. func (f *fakeFile) Read(p []byte) (int, error) {
  650. f.mut.Lock()
  651. defer f.mut.Unlock()
  652. return f.readShortAt(p, f.offset)
  653. }
  654. func (f *fakeFile) ReadAt(p []byte, offs int64) (int, error) {
  655. f.mut.Lock()
  656. defer f.mut.Unlock()
  657. // ReadAt is spec:ed to always read a full block unless EOF or failure,
  658. // so we must loop. It's also not supposed to affect the seek position,
  659. // but that would make things annoying or inefficient in terms of
  660. // generating the appropriate RNG etc so I ignore that. In practice we
  661. // currently don't depend on that aspect of it...
  662. var read int
  663. for {
  664. n, err := f.readShortAt(p[read:], offs+int64(read))
  665. read += n
  666. if err != nil {
  667. return read, err
  668. }
  669. if read == len(p) {
  670. return read, nil
  671. }
  672. }
  673. }
  674. func (f *fakeFile) readShortAt(p []byte, offs int64) (int, error) {
  675. // Here be a certain amount of magic... We want to return pseudorandom,
  676. // predictable data so that a read from the same offset in the same file
  677. // always returns the same data. But the RNG is a stream, and reads can
  678. // be random.
  679. //
  680. // We split the file into "blocks" numbered by "seedNo", where each
  681. // block becomes an instantiation of the RNG, seeded with the hash of
  682. // the file number plus the seedNo (block number). We keep the RNG
  683. // around in the hope that the next read will be sequential to this one
  684. // and we can continue reading from the same RNG.
  685. //
  686. // When that's not the case we create a new RNG for the block we are in,
  687. // read as many bytes from it as necessary to get to the right offset,
  688. // and then serve the read from there. We limit the length of the read
  689. // to the end of the block, as another RNG needs to be created to serve
  690. // the next block.
  691. //
  692. // The size of the blocks are a matter of taste... Larger blocks give
  693. // better performance for sequential reads, but worse for random reads
  694. // as we often need to generate and throw away a lot of data at the
  695. // start of the block to serve a given read. 128 KiB blocks fit
  696. // reasonably well with the type of IO Syncthing tends to do.
  697. if f.entryType == fakeEntryTypeDir {
  698. return 0, errors.New("is a directory")
  699. }
  700. if offs >= f.size {
  701. return 0, io.EOF
  702. }
  703. if f.content != nil {
  704. n := copy(p, f.content[int(offs):])
  705. f.offset = offs + int64(n)
  706. return n, nil
  707. }
  708. // Lazily calculate our main seed, a simple 64 bit FNV hash our file
  709. // name.
  710. if f.seed == 0 {
  711. hf := fnv.New64()
  712. hf.Write([]byte(f.name))
  713. f.seed = int64(hf.Sum64()) //nolint:gosec
  714. }
  715. // Check whether the read is a continuation of an RNG we already have or
  716. // we need to set up a new one.
  717. seedNo := offs >> randomBlockShift
  718. minOffs := seedNo << randomBlockShift
  719. nextBlockOffs := (seedNo + 1) << randomBlockShift
  720. if f.rng == nil || f.offset != offs || seedNo != f.seedOffs {
  721. // This is not a straight read continuing from a previous one
  722. f.rng = rand.New(rand.NewSource(f.seed + seedNo)) //nolint:gosec
  723. // If the read is not at the start of the block, discard data
  724. // accordingly.
  725. diff := offs - minOffs
  726. if diff > 0 {
  727. lr := io.LimitReader(f.rng, diff)
  728. _, _ = io.Copy(io.Discard, lr)
  729. }
  730. f.offset = offs
  731. f.seedOffs = seedNo
  732. }
  733. size := len(p)
  734. // Don't read past the end of the file
  735. if offs+int64(size) > f.size {
  736. size = int(f.size - offs)
  737. }
  738. // Don't read across the block boundary
  739. if offs+int64(size) > nextBlockOffs {
  740. size = int(nextBlockOffs - offs)
  741. }
  742. f.offset += int64(size)
  743. return f.rng.Read(p[:size])
  744. }
  745. func (f *fakeFile) Seek(offset int64, whence int) (int64, error) {
  746. f.mut.Lock()
  747. defer f.mut.Unlock()
  748. if f.entryType == fakeEntryTypeDir {
  749. return 0, errors.New("is a directory")
  750. }
  751. f.rng = nil
  752. switch whence {
  753. case io.SeekCurrent:
  754. f.offset += offset
  755. case io.SeekEnd:
  756. f.offset = f.size - offset
  757. case io.SeekStart:
  758. f.offset = offset
  759. }
  760. if f.offset < 0 {
  761. f.offset = 0
  762. return f.offset, errors.New("seek before start")
  763. }
  764. if f.offset > f.size {
  765. f.offset = f.size
  766. return f.offset, io.EOF
  767. }
  768. return f.offset, nil
  769. }
  770. func (f *fakeFile) Write(p []byte) (int, error) {
  771. f.mut.Lock()
  772. offs := f.offset
  773. f.mut.Unlock()
  774. return f.WriteAt(p, offs)
  775. }
  776. func (f *fakeFile) WriteAt(p []byte, off int64) (int, error) {
  777. f.mut.Lock()
  778. defer f.mut.Unlock()
  779. if f.entryType == fakeEntryTypeDir {
  780. return 0, errors.New("is a directory")
  781. }
  782. if f.content != nil {
  783. if len(f.content) < int(off)+len(p) {
  784. newc := make([]byte, int(off)+len(p))
  785. copy(newc, f.content)
  786. f.content = newc
  787. }
  788. copy(f.content[int(off):], p)
  789. }
  790. f.rng = nil
  791. f.offset = off + int64(len(p))
  792. if f.offset > f.size {
  793. f.size = f.offset
  794. }
  795. return len(p), nil
  796. }
  797. func (f *fakeFile) Name() string {
  798. if f.presentedName != "" {
  799. return f.presentedName
  800. }
  801. f.mut.Lock()
  802. defer f.mut.Unlock()
  803. return f.name
  804. }
  805. func (f *fakeFile) Truncate(size int64) error {
  806. f.mut.Lock()
  807. defer f.mut.Unlock()
  808. if f.content != nil {
  809. if int64(cap(f.content)) < size {
  810. c := make([]byte, size)
  811. copy(c[:len(f.content)], f.content)
  812. f.content = c
  813. } else {
  814. f.content = f.content[:int(size)]
  815. }
  816. }
  817. f.rng = nil
  818. f.size = size
  819. if f.offset > size {
  820. f.offset = size
  821. }
  822. return nil
  823. }
  824. func (f *fakeFile) Stat() (FileInfo, error) {
  825. f.mut.Lock()
  826. info := &fakeFileInfo{*f.fakeEntry}
  827. f.mut.Unlock()
  828. if f.presentedName != "" {
  829. info.name = f.presentedName
  830. }
  831. return info, nil
  832. }
  833. func (*fakeFile) Sync() error {
  834. return nil
  835. }
  836. // fakeFileInfo is the stat result.
  837. type fakeFileInfo struct {
  838. fakeEntry // intentionally a copy of the struct
  839. }
  840. func (f *fakeFileInfo) Name() string {
  841. return f.name
  842. }
  843. func (f *fakeFileInfo) Mode() FileMode {
  844. return f.mode
  845. }
  846. func (f *fakeFileInfo) Size() int64 {
  847. return f.size
  848. }
  849. func (f *fakeFileInfo) ModTime() time.Time {
  850. return f.mtime
  851. }
  852. func (f *fakeFileInfo) IsDir() bool {
  853. return f.entryType == fakeEntryTypeDir
  854. }
  855. func (f *fakeFileInfo) IsRegular() bool {
  856. return f.entryType == fakeEntryTypeFile
  857. }
  858. func (f *fakeFileInfo) IsSymlink() bool {
  859. return f.entryType == fakeEntryTypeSymlink
  860. }
  861. func (f *fakeFileInfo) Owner() int {
  862. return f.uid
  863. }
  864. func (f *fakeFileInfo) Group() int {
  865. return f.gid
  866. }
  867. func (*fakeFileInfo) Sys() interface{} {
  868. return nil
  869. }
  870. func (*fakeFileInfo) InodeChangeTime() time.Time {
  871. return time.Time{}
  872. }