mtimefs.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  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. "time"
  9. )
  10. // The database is where we store the virtual mtimes
  11. type database interface {
  12. Bytes(key string) (data []byte, ok bool)
  13. PutBytes(key string, data []byte)
  14. Delete(key string)
  15. }
  16. // The MtimeFS is a filesystem with nanosecond mtime precision, regardless
  17. // of what shenanigans the underlying filesystem gets up to. A nil MtimeFS
  18. // just does the underlying operations with no additions.
  19. type MtimeFS struct {
  20. Filesystem
  21. chtimes func(string, time.Time, time.Time) error
  22. db database
  23. caseInsensitive bool
  24. }
  25. type MtimeFSOption func(*MtimeFS)
  26. func WithCaseInsensitivity(v bool) MtimeFSOption {
  27. return func(f *MtimeFS) {
  28. f.caseInsensitive = v
  29. }
  30. }
  31. func NewMtimeFS(underlying Filesystem, db database, options ...MtimeFSOption) *MtimeFS {
  32. f := &MtimeFS{
  33. Filesystem: underlying,
  34. chtimes: underlying.Chtimes, // for mocking it out in the tests
  35. db: db,
  36. }
  37. for _, opt := range options {
  38. opt(f)
  39. }
  40. return f
  41. }
  42. func (f *MtimeFS) Chtimes(name string, atime, mtime time.Time) error {
  43. if f == nil {
  44. return f.chtimes(name, atime, mtime)
  45. }
  46. // Do a normal Chtimes call, don't care if it succeeds or not.
  47. f.chtimes(name, atime, mtime)
  48. // Stat the file to see what happened. Here we *do* return an error,
  49. // because it might be "does not exist" or similar.
  50. info, err := f.Filesystem.Lstat(name)
  51. if err != nil {
  52. return err
  53. }
  54. f.save(name, info.ModTime(), mtime)
  55. return nil
  56. }
  57. func (f *MtimeFS) Lstat(name string) (FileInfo, error) {
  58. info, err := f.Filesystem.Lstat(name)
  59. if err != nil {
  60. return nil, err
  61. }
  62. real, virtual := f.load(name)
  63. if real == info.ModTime() {
  64. info = mtimeFileInfo{
  65. FileInfo: info,
  66. mtime: virtual,
  67. }
  68. }
  69. return info, nil
  70. }
  71. // "real" is the on disk timestamp
  72. // "virtual" is what want the timestamp to be
  73. func (f *MtimeFS) save(name string, real, virtual time.Time) {
  74. if f.caseInsensitive {
  75. name = UnicodeLowercase(name)
  76. }
  77. if real.Equal(virtual) {
  78. // If the virtual time and the real on disk time are equal we don't
  79. // need to store anything.
  80. f.db.Delete(name)
  81. return
  82. }
  83. mtime := dbMtime{
  84. real: real,
  85. virtual: virtual,
  86. }
  87. bs, _ := mtime.Marshal() // Can't fail
  88. f.db.PutBytes(name, bs)
  89. }
  90. func (f *MtimeFS) load(name string) (real, virtual time.Time) {
  91. if f.caseInsensitive {
  92. name = UnicodeLowercase(name)
  93. }
  94. data, exists := f.db.Bytes(name)
  95. if !exists {
  96. return
  97. }
  98. var mtime dbMtime
  99. if err := mtime.Unmarshal(data); err != nil {
  100. return
  101. }
  102. return mtime.real, mtime.virtual
  103. }
  104. // The mtimeFileInfo is an os.FileInfo that lies about the ModTime().
  105. type mtimeFileInfo struct {
  106. FileInfo
  107. mtime time.Time
  108. }
  109. func (m mtimeFileInfo) ModTime() time.Time {
  110. return m.mtime
  111. }
  112. // The dbMtime is our database representation
  113. type dbMtime struct {
  114. real time.Time
  115. virtual time.Time
  116. }
  117. func (t *dbMtime) Marshal() ([]byte, error) {
  118. bs0, _ := t.real.MarshalBinary()
  119. bs1, _ := t.virtual.MarshalBinary()
  120. return append(bs0, bs1...), nil
  121. }
  122. func (t *dbMtime) Unmarshal(bs []byte) error {
  123. if err := t.real.UnmarshalBinary(bs[:len(bs)/2]); err != nil {
  124. return err
  125. }
  126. if err := t.virtual.UnmarshalBinary(bs[len(bs)/2:]); err != nil {
  127. return err
  128. }
  129. return nil
  130. }