| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143 |
- // 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 (
- "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. A nil MtimeFS
- // just does the underlying operations with no additions.
- type MtimeFS struct {
- Filesystem
- db database
- }
- func NewMtimeFS(underlying Filesystem, db database) *MtimeFS {
- return &MtimeFS{
- Filesystem: underlying,
- db: db,
- }
- }
- func (f *MtimeFS) Chtimes(name string, atime, mtime time.Time) error {
- if f == nil {
- return osChtimes(name, atime, mtime)
- }
- // 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) (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 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 {
- 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
- }
|