diff --git a/cmd/mount/dir.go b/cmd/mount/dir.go index 6885feccc..0860e4fa9 100644 --- a/cmd/mount/dir.go +++ b/cmd/mount/dir.go @@ -169,7 +169,7 @@ func (d *Dir) isEmpty() (bool, error) { // Check interface satsified var _ fusefs.Node = (*Dir)(nil) -// Attr updates the attribes of a directory +// Attr updates the attributes of a directory func (d *Dir) Attr(ctx context.Context, a *fuse.Attr) error { a.Gid = gid a.Uid = uid @@ -183,6 +183,27 @@ func (d *Dir) Attr(ctx context.Context, a *fuse.Attr) error { return nil } +// Check interface satisfied +var _ fusefs.NodeSetattrer = (*Dir)(nil) + +// Setattr handles attribute changes from FUSE. Currently supports ModTime only. +func (d *Dir) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error { + if noModTime { + return nil + } + + d.mu.Lock() + defer d.mu.Unlock() + + if req.Valid.MtimeNow() { + d.modTime = time.Now() + } else if req.Valid.Mtime() { + d.modTime = req.Mtime + } + + return nil +} + // lookupNode calls lookup then makes sure the node is not nil in the DirEntry func (d *Dir) lookupNode(leaf string) (item *DirEntry, err error) { item, err = d.lookup(leaf) diff --git a/cmd/mount/dir_test.go b/cmd/mount/dir_test.go index 45987b600..3833438e3 100644 --- a/cmd/mount/dir_test.go +++ b/cmd/mount/dir_test.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -133,3 +134,20 @@ func TestDirRenameFullDir(t *testing.T) { run.rmdir(t, "dir") run.checkDir(t, "") } + +func TestDirModTime(t *testing.T) { + run.skipIfNoFUSE(t) + + run.mkdir(t, "dir") + mtime := time.Date(2012, 11, 18, 17, 32, 31, 0, time.UTC) + err := os.Chtimes(run.path("dir"), mtime, mtime) + require.NoError(t, err) + + info, err := os.Stat(run.path("dir")) + require.NoError(t, err) + + // avoid errors because of timezone differences + assert.Equal(t, info.ModTime().Unix(), mtime.Unix()) + + run.rmdir(t, "dir") +} diff --git a/cmd/mount/file.go b/cmd/mount/file.go index f23033a88..2b937bc7d 100644 --- a/cmd/mount/file.go +++ b/cmd/mount/file.go @@ -74,6 +74,47 @@ func (f *File) Attr(ctx context.Context, a *fuse.Attr) error { return nil } +// Check interface satisfied +var _ fusefs.NodeSetattrer = (*File)(nil) + +// Setattr handles attribute changes from FUSE. Currently supports ModTime only. +func (f *File) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error { + if noModTime { + return nil + } + + // if o is nil it isn't valid yet + o, err := f.waitForValidObject() + if err != nil { + return err + } + + f.mu.Lock() + defer f.mu.Unlock() + + var newTime time.Time + if req.Valid.MtimeNow() { + newTime = time.Now() + } else if req.Valid.Mtime() { + newTime = req.Mtime + } + + if !newTime.IsZero() { + err := o.SetModTime(newTime) + switch err { + case nil: + fs.Debugf(o, "File.Setattr ModTime OK") + case fs.ErrorCantSetModTime: + // do nothing, in order to not break "touch somefile" if it exists already + default: + fs.Errorf(o, "File.Setattr ModTime error: %v", err) + return err + } + } + + return nil +} + // Update the size while writing func (f *File) written(n int64) { atomic.AddInt64(&f.size, n) diff --git a/cmd/mount/file_test.go b/cmd/mount/file_test.go new file mode 100644 index 000000000..9dde3ff2c --- /dev/null +++ b/cmd/mount/file_test.go @@ -0,0 +1,30 @@ +// +build linux darwin freebsd + +package mount + +import ( + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestFileModTime(t *testing.T) { + run.skipIfNoFUSE(t) + + run.createFile(t, "file", "123") + + mtime := time.Date(2012, 11, 18, 17, 32, 31, 0, time.UTC) + err := os.Chtimes(run.path("file"), mtime, mtime) + require.NoError(t, err) + + info, err := os.Stat(run.path("file")) + require.NoError(t, err) + + // avoid errors because of timezone differences + assert.Equal(t, info.ModTime().Unix(), mtime.Unix()) + + run.rm(t, "file") +} diff --git a/cmd/mount/mount.go b/cmd/mount/mount.go index 40788687a..be3984341 100644 --- a/cmd/mount/mount.go +++ b/cmd/mount/mount.go @@ -46,7 +46,7 @@ func init() { umask = unix.Umask(0) // read the umask unix.Umask(umask) // set it back to what it was cmd.Root.AddCommand(commandDefintion) - commandDefintion.Flags().BoolVarP(&noModTime, "no-modtime", "", noModTime, "Don't read the modification time (can speed things up).") + commandDefintion.Flags().BoolVarP(&noModTime, "no-modtime", "", noModTime, "Don't read/write the modification time (can speed things up).") commandDefintion.Flags().BoolVarP(&debugFUSE, "debug-fuse", "", debugFUSE, "Debug the FUSE internals - needs -v.") commandDefintion.Flags().BoolVarP(&noSeek, "no-seek", "", noSeek, "Don't allow seeking in files.") commandDefintion.Flags().DurationVarP(&dirCacheTime, "dir-cache-time", "", dirCacheTime, "Time to cache directory entries for.") @@ -130,8 +130,6 @@ files to be visible in the mount. ### TODO ### * Check hashes on upload/download - * Preserve timestamps - * Move directories `, Run: func(command *cobra.Command, args []string) { cmd.CheckArgs(2, 2, command, args)