From 92918ef1b65706c30a1272fb05d0f4101f85974e Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 14 Jan 2018 14:22:08 +0100 Subject: [PATCH] fuse/mount: Add option for snapshot template --- cmd/restic/cmd_mount.go | 45 ++++++++++++++++++++++++++-------- internal/fuse/root.go | 9 ++++--- internal/fuse/snapshots_dir.go | 25 ++++++++++--------- 3 files changed, 54 insertions(+), 25 deletions(-) diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 5f57efa9c..5e7b003a5 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,10 @@ func umount(mountpoint string) error { } func runMount(opts MountOptions, gopts GlobalOptions, args []string) error { + 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/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 {