| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- // 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 https://mozilla.org/MPL/2.0/.
- package fs
- import (
- "time"
- )
- // 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)
- }
- // The MtimeFS is a filesystem with nanosecond mtime precision, regardless
- // of what shenanigans the underlying filesystem gets up to. A nil MtimeFS
- // just does the underlying operations with no additions.
- type MtimeFS struct {
- Filesystem
- chtimes func(string, time.Time, time.Time) error
- db database
- caseInsensitive bool
- }
- type MtimeFSOption func(*MtimeFS)
- func WithCaseInsensitivity(v bool) MtimeFSOption {
- return func(f *MtimeFS) {
- f.caseInsensitive = v
- }
- }
- func NewMtimeFS(underlying Filesystem, db database, options ...MtimeFSOption) *MtimeFS {
- f := &MtimeFS{
- Filesystem: underlying,
- chtimes: underlying.Chtimes, // for mocking it out in the tests
- db: db,
- }
- for _, opt := range options {
- opt(f)
- }
- return f
- }
- func (f *MtimeFS) Chtimes(name string, atime, mtime time.Time) error {
- if f == nil {
- return f.chtimes(name, atime, mtime)
- }
- // Do a normal Chtimes call, don't care if it succeeds or not.
- f.chtimes(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.
- info, err := f.Filesystem.Lstat(name)
- if err != nil {
- return err
- }
- f.save(name, info.ModTime(), mtime)
- return nil
- }
- func (f *MtimeFS) Lstat(name string) (FileInfo, error) {
- info, err := f.Filesystem.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 f.caseInsensitive {
- name = UnicodeLowercase(name)
- }
- 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) {
- if f.caseInsensitive {
- name = UnicodeLowercase(name)
- }
- 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 {
- 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
- }
|