mtimefs.go 2.9 KB

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