mtimefs.go 3.1 KB

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