mtimefs.go 3.1 KB

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