mirror of https://github.com/restic/restic.git
Merge pull request #1554 from restic/fix-988
fuse: Correct behavior for reading after EOF, add snapshot template string
This commit is contained in:
commit
35528506a6
|
@ -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
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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}))
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue