From d89f2e5226d16dd9866bf60893645c14dda86662 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sat, 13 Jan 2018 10:10:07 +0100 Subject: [PATCH 1/4] fuse: Remove debug log message --- internal/fuse/dir.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/fuse/dir.go b/internal/fuse/dir.go index dfbff7d4f..703b27f2b 100644 --- a/internal/fuse/dir.go +++ b/internal/fuse/dir.go @@ -182,7 +182,6 @@ func (d *dir) Lookup(ctx context.Context, name string) (fs.Node, error) { node, ok := d.items[name] if !ok { debug.Log(" Lookup(%v) -> not found", name) - debug.Log(" items: %v\n", d.items) return nil, fuse.ENOENT } switch node.Type { From f49f5c5903a37e77f248c447e565bc77579e3945 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sat, 13 Jan 2018 10:14:10 +0100 Subject: [PATCH 2/4] fuse: Don't return an error for EOF --- internal/fuse/file.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/fuse/file.go b/internal/fuse/file.go index 642245cbc..233875993 100644 --- a/internal/fuse/file.go +++ b/internal/fuse/file.go @@ -4,7 +4,6 @@ package fuse import ( - "github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/debug" @@ -111,7 +110,10 @@ func (f *file) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadR if uint64(offset) > f.node.Size { debug.Log("Read(%v): offset is greater than file size: %v > %v", f.node.Name, req.Offset, f.node.Size) - return errors.New("offset greater than files size") + + // return no data + resp.Data = resp.Data[:0] + return nil } // handle special case: file is empty From 97565d68a237f2a858d612cb4c0e6619f1b3ffff Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 14 Jan 2018 14:22:08 +0100 Subject: [PATCH 3/4] fuse/mount: Add option for snapshot template --- cmd/restic/cmd_mount.go | 49 +++++++++++++++++++++++------ cmd/restic/integration_fuse_test.go | 4 ++- internal/fuse/root.go | 9 +++--- internal/fuse/snapshots_dir.go | 25 ++++++++------- 4 files changed, 61 insertions(+), 26 deletions(-) diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 5f57efa9c..8bcc2d8bc 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -5,6 +5,8 @@ package main import ( "os" + "strings" + "time" "github.com/spf13/cobra" @@ -25,6 +27,21 @@ var cmdMount = &cobra.Command{ Long: ` The "mount" command mounts the repository via fuse to a directory. This is a read-only mount. + +Snapshot Directories +==================== + +If you need a different template for all directories that contain snapshots, +you can pass a template via --snapshot-template. Example without colons: + + --snapshot-template "2006-01-02_15-04-05" + +You need to specify a sample format for exactly the following timestamp: + + Mon Jan 2 15:04:05 -0700 MST 2006 + +For details please see the documentation for time.Format() at: + https://godoc.org/time#Time.Format `, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { @@ -34,12 +51,13 @@ read-only mount. // MountOptions collects all options for the mount command. type MountOptions struct { - OwnerRoot bool - AllowRoot bool - AllowOther bool - Host string - Tags restic.TagLists - Paths []string + OwnerRoot bool + AllowRoot bool + AllowOther bool + Host string + Tags restic.TagLists + Paths []string + SnapshotTemplate string } var mountOptions MountOptions @@ -55,6 +73,8 @@ func init() { mountFlags.StringVarP(&mountOptions.Host, "host", "H", "", `only consider snapshots for this host`) mountFlags.Var(&mountOptions.Tags, "tag", "only consider snapshots which include this `taglist`") mountFlags.StringArrayVar(&mountOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`") + + mountFlags.StringVar(&mountOptions.SnapshotTemplate, "snapshot-template", time.RFC3339, "set `template` to use for snapshot dirs") } func mount(opts MountOptions, gopts GlobalOptions, mountpoint string) error { @@ -108,10 +128,11 @@ func mount(opts MountOptions, gopts GlobalOptions, mountpoint string) error { } cfg := fuse.Config{ - OwnerIsRoot: opts.OwnerRoot, - Host: opts.Host, - Tags: opts.Tags, - Paths: opts.Paths, + OwnerIsRoot: opts.OwnerRoot, + Host: opts.Host, + Tags: opts.Tags, + Paths: opts.Paths, + SnapshotTemplate: opts.SnapshotTemplate, } root, err := fuse.NewRoot(gopts.ctx, repo, cfg) if err != nil { @@ -136,6 +157,14 @@ func umount(mountpoint string) error { } func runMount(opts MountOptions, gopts GlobalOptions, args []string) error { + if opts.SnapshotTemplate == "" { + return errors.Fatal("snapshot template string cannot be empty") + } + + if strings.ContainsAny(opts.SnapshotTemplate, `\/`) { + return errors.Fatal("snapshot template string contains a slash (/) or backslash (\\) character") + } + if len(args) == 0 { return errors.Fatal("wrong number of parameters") } diff --git a/cmd/restic/integration_fuse_test.go b/cmd/restic/integration_fuse_test.go index d4922b8b9..d680dd2af 100644 --- a/cmd/restic/integration_fuse_test.go +++ b/cmd/restic/integration_fuse_test.go @@ -55,7 +55,9 @@ func waitForMount(t testing.TB, dir string) { } func testRunMount(t testing.TB, gopts GlobalOptions, dir string) { - opts := MountOptions{} + opts := MountOptions{ + SnapshotTemplate: time.RFC3339, + } rtest.OK(t, runMount(opts, gopts, []string{dir})) } diff --git a/internal/fuse/root.go b/internal/fuse/root.go index c6abc3bf2..98b04fa5d 100644 --- a/internal/fuse/root.go +++ b/internal/fuse/root.go @@ -16,10 +16,11 @@ import ( // Config holds settings for the fuse mount. type Config struct { - OwnerIsRoot bool - Host string - Tags []restic.TagList - Paths []string + OwnerIsRoot bool + Host string + Tags []restic.TagList + Paths []string + SnapshotTemplate string } // Root is the root node of the fuse mount of a repository. diff --git a/internal/fuse/snapshots_dir.go b/internal/fuse/snapshots_dir.go index 19c785224..762056611 100644 --- a/internal/fuse/snapshots_dir.go +++ b/internal/fuse/snapshots_dir.go @@ -26,6 +26,8 @@ type SnapshotsDir struct { tag string host string snCount int + + template string } // SnapshotsIDSDir is a fuse directory which contains snapshots named by ids. @@ -112,12 +114,13 @@ func updateSnapshotIDSNames(d *SnapshotsIDSDir) { func NewSnapshotsDir(root *Root, inode uint64, tag string, host string) *SnapshotsDir { debug.Log("create snapshots dir, inode %d", inode) d := &SnapshotsDir{ - root: root, - inode: inode, - names: make(map[string]*restic.Snapshot), - latest: "", - tag: tag, - host: host, + root: root, + inode: inode, + names: make(map[string]*restic.Snapshot), + latest: "", + tag: tag, + host: host, + template: root.cfg.SnapshotTemplate, } return d @@ -239,7 +242,7 @@ func updateSnapshots(ctx context.Context, root *Root) { } // read snapshot timestamps from the current repository-state. -func updateSnapshotNames(d *SnapshotsDir) { +func updateSnapshotNames(d *SnapshotsDir, template string) { if d.snCount != d.root.snCount { d.snCount = d.root.snCount var latestTime time.Time @@ -248,7 +251,7 @@ func updateSnapshotNames(d *SnapshotsDir) { for _, sn := range d.root.snapshots { if d.tag == "" || isElem(d.tag, sn.Tags) { if d.host == "" || d.host == sn.Hostname { - name := sn.Time.Format(time.RFC3339) + name := sn.Time.Format(template) if d.latest == "" || !sn.Time.Before(latestTime) { latestTime = sn.Time d.latest = name @@ -258,7 +261,7 @@ func updateSnapshotNames(d *SnapshotsDir) { break } - name = fmt.Sprintf("%s-%d", sn.Time.Format(time.RFC3339), i) + name = fmt.Sprintf("%s-%d", sn.Time.Format(template), i) } d.names[name] = sn @@ -276,7 +279,7 @@ func (d *SnapshotsDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { updateSnapshots(ctx, d.root) // update snapshot names - updateSnapshotNames(d) + updateSnapshotNames(d, d.root.cfg.SnapshotTemplate) items := []fuse.Dirent{ { @@ -450,7 +453,7 @@ func (d *SnapshotsDir) Lookup(ctx context.Context, name string) (fs.Node, error) updateSnapshots(ctx, d.root) // update snapshot names - updateSnapshotNames(d) + updateSnapshotNames(d, d.root.cfg.SnapshotTemplate) sn, ok := d.names[name] if ok { From 2544978aaf315e3186c41359c2243b71d4511a51 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 14 Jan 2018 14:24:14 +0100 Subject: [PATCH 4/4] Add entry to changelog --- changelog/0.8.2/pull-1554 | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 changelog/0.8.2/pull-1554 diff --git a/changelog/0.8.2/pull-1554 b/changelog/0.8.2/pull-1554 new file mode 100644 index 000000000..9a528084a --- /dev/null +++ b/changelog/0.8.2/pull-1554 @@ -0,0 +1,7 @@ +Enhancement: fuse/mount: Correctly handle EOF, add template option + +We've added the `--snapshot-template` string, which can be used to specify a +template for a snapshot directory. In addition, accessing data after the end of +a file via the fuse mount is now handled correctly. + +https://github.com/restic/restic/pull/1554