-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
/
mtimefs.go
136 lines (110 loc) · 2.89 KB
/
mtimefs.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package fs
import (
"os"
"time"
"github.com/syncthing/syncthing/lib/osutil"
)
// The database is where we store the virtual mtimes
type database interface {
Bytes(key string) (data []byte, ok bool)
PutBytes(key string, data []byte)
Delete(key string)
}
// variable so that we can mock it for testing
var osChtimes = os.Chtimes
// The MtimeFS is a filesystem with nanosecond mtime precision, regardless
// of what shenanigans the underlying filesystem gets up to.
type MtimeFS struct {
db database
}
func NewMtimeFS(db database) *MtimeFS {
return &MtimeFS{
db: db,
}
}
func (f *MtimeFS) Chtimes(name string, atime, mtime time.Time) error {
// Do a normal Chtimes call, don't care if it succeeds or not.
osChtimes(name, atime, mtime)
// Stat the file to see what happened. Here we *do* return an error,
// because it might be "does not exist" or similar. osutil.Lstat is the
// souped up version to account for Android breakage.
info, err := osutil.Lstat(name)
if err != nil {
return err
}
f.save(name, info.ModTime(), mtime)
return nil
}
func (f *MtimeFS) Lstat(name string) (os.FileInfo, error) {
info, err := osutil.Lstat(name)
if err != nil {
return nil, err
}
real, virtual := f.load(name)
if real == info.ModTime() {
info = mtimeFileInfo{
FileInfo: info,
mtime: virtual,
}
}
return info, nil
}
// "real" is the on disk timestamp
// "virtual" is what want the timestamp to be
func (f *MtimeFS) save(name string, real, virtual time.Time) {
if real.Equal(virtual) {
// If the virtual time and the real on disk time are equal we don't
// need to store anything.
f.db.Delete(name)
return
}
mtime := dbMtime{
real: real,
virtual: virtual,
}
bs, _ := mtime.Marshal() // Can't fail
f.db.PutBytes(name, bs)
}
func (f *MtimeFS) load(name string) (real, virtual time.Time) {
data, exists := f.db.Bytes(name)
if !exists {
return
}
var mtime dbMtime
if err := mtime.Unmarshal(data); err != nil {
return
}
return mtime.real, mtime.virtual
}
// The mtimeFileInfo is an os.FileInfo that lies about the ModTime().
type mtimeFileInfo struct {
os.FileInfo
mtime time.Time
}
func (m mtimeFileInfo) ModTime() time.Time {
return m.mtime
}
// The dbMtime is our database representation
type dbMtime struct {
real time.Time
virtual time.Time
}
func (t *dbMtime) Marshal() ([]byte, error) {
bs0, _ := t.real.MarshalBinary()
bs1, _ := t.virtual.MarshalBinary()
return append(bs0, bs1...), nil
}
func (t *dbMtime) Unmarshal(bs []byte) error {
if err := t.real.UnmarshalBinary(bs[:len(bs)/2]); err != nil {
return err
}
if err := t.virtual.UnmarshalBinary(bs[len(bs)/2:]); err != nil {
return err
}
return nil
}