mtimefs.go 3.1 KB

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