mirror of https://github.com/restic/restic.git
fuse: added symlink 'latest' to snapshots-dir
This commit is contained in:
parent
81473f4538
commit
1a83a739dc
|
@ -82,7 +82,7 @@ func listSnapshots(t testing.TB, dir string) []string {
|
||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkSnapshots(t testing.TB, global GlobalOptions, repo *repository.Repository, mountpoint, repodir string, snapshotIDs restic.IDs) {
|
func checkSnapshots(t testing.TB, global GlobalOptions, repo *repository.Repository, mountpoint, repodir string, snapshotIDs restic.IDs, expectedSnapshotsInFuseDir int) {
|
||||||
t.Logf("checking for %d snapshots: %v", len(snapshotIDs), snapshotIDs)
|
t.Logf("checking for %d snapshots: %v", len(snapshotIDs), snapshotIDs)
|
||||||
|
|
||||||
go testRunMount(t, global, mountpoint)
|
go testRunMount(t, global, mountpoint)
|
||||||
|
@ -99,14 +99,24 @@ func checkSnapshots(t testing.TB, global GlobalOptions, repo *repository.Reposit
|
||||||
namesInSnapshots := listSnapshots(t, mountpoint)
|
namesInSnapshots := listSnapshots(t, mountpoint)
|
||||||
t.Logf("found %v snapshots in fuse mount: %v", len(namesInSnapshots), namesInSnapshots)
|
t.Logf("found %v snapshots in fuse mount: %v", len(namesInSnapshots), namesInSnapshots)
|
||||||
Assert(t,
|
Assert(t,
|
||||||
len(namesInSnapshots) == len(snapshotIDs),
|
expectedSnapshotsInFuseDir == len(namesInSnapshots),
|
||||||
"Invalid number of snapshots: expected %d, got %d", len(snapshotIDs), len(namesInSnapshots))
|
"Invalid number of snapshots: expected %d, got %d", expectedSnapshotsInFuseDir, len(namesInSnapshots))
|
||||||
|
|
||||||
namesMap := make(map[string]bool)
|
namesMap := make(map[string]bool)
|
||||||
for _, name := range namesInSnapshots {
|
for _, name := range namesInSnapshots {
|
||||||
namesMap[name] = false
|
namesMap[name] = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Is "latest" present?
|
||||||
|
if len(namesMap) != 0 {
|
||||||
|
_, ok := namesMap["latest"]
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("Symlink latest isn't present in fuse dir")
|
||||||
|
} else {
|
||||||
|
namesMap["latest"] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, id := range snapshotIDs {
|
for _, id := range snapshotIDs {
|
||||||
snapshot, err := restic.LoadSnapshot(context.TODO(), repo, id)
|
snapshot, err := restic.LoadSnapshot(context.TODO(), repo, id)
|
||||||
OK(t, err)
|
OK(t, err)
|
||||||
|
@ -153,7 +163,7 @@ func TestMount(t *testing.T) {
|
||||||
// We remove the mountpoint now to check that cmdMount creates it
|
// We remove the mountpoint now to check that cmdMount creates it
|
||||||
RemoveAll(t, env.mountpoint)
|
RemoveAll(t, env.mountpoint)
|
||||||
|
|
||||||
checkSnapshots(t, env.gopts, repo, env.mountpoint, env.repo, []restic.ID{})
|
checkSnapshots(t, env.gopts, repo, env.mountpoint, env.repo, []restic.ID{}, 0)
|
||||||
|
|
||||||
SetupTarTestFixture(t, env.testdata, filepath.Join("testdata", "backup-data.tar.gz"))
|
SetupTarTestFixture(t, env.testdata, filepath.Join("testdata", "backup-data.tar.gz"))
|
||||||
|
|
||||||
|
@ -163,7 +173,7 @@ func TestMount(t *testing.T) {
|
||||||
Assert(t, len(snapshotIDs) == 1,
|
Assert(t, len(snapshotIDs) == 1,
|
||||||
"expected one snapshot, got %v", snapshotIDs)
|
"expected one snapshot, got %v", snapshotIDs)
|
||||||
|
|
||||||
checkSnapshots(t, env.gopts, repo, env.mountpoint, env.repo, snapshotIDs)
|
checkSnapshots(t, env.gopts, repo, env.mountpoint, env.repo, snapshotIDs, 2)
|
||||||
|
|
||||||
// second backup, implicit incremental
|
// second backup, implicit incremental
|
||||||
testRunBackup(t, []string{env.testdata}, BackupOptions{}, env.gopts)
|
testRunBackup(t, []string{env.testdata}, BackupOptions{}, env.gopts)
|
||||||
|
@ -171,7 +181,7 @@ func TestMount(t *testing.T) {
|
||||||
Assert(t, len(snapshotIDs) == 2,
|
Assert(t, len(snapshotIDs) == 2,
|
||||||
"expected two snapshots, got %v", snapshotIDs)
|
"expected two snapshots, got %v", snapshotIDs)
|
||||||
|
|
||||||
checkSnapshots(t, env.gopts, repo, env.mountpoint, env.repo, snapshotIDs)
|
checkSnapshots(t, env.gopts, repo, env.mountpoint, env.repo, snapshotIDs, 3)
|
||||||
|
|
||||||
// third backup, explicit incremental
|
// third backup, explicit incremental
|
||||||
bopts := BackupOptions{Parent: snapshotIDs[0].String()}
|
bopts := BackupOptions{Parent: snapshotIDs[0].String()}
|
||||||
|
@ -180,7 +190,7 @@ func TestMount(t *testing.T) {
|
||||||
Assert(t, len(snapshotIDs) == 3,
|
Assert(t, len(snapshotIDs) == 3,
|
||||||
"expected three snapshots, got %v", snapshotIDs)
|
"expected three snapshots, got %v", snapshotIDs)
|
||||||
|
|
||||||
checkSnapshots(t, env.gopts, repo, env.mountpoint, env.repo, snapshotIDs)
|
checkSnapshots(t, env.gopts, repo, env.mountpoint, env.repo, snapshotIDs, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMountSameTimestamps(t *testing.T) {
|
func TestMountSameTimestamps(t *testing.T) {
|
||||||
|
@ -202,5 +212,5 @@ func TestMountSameTimestamps(t *testing.T) {
|
||||||
restic.TestParseID("5fd0d8b2ef0fa5d23e58f1e460188abb0f525c0f0c4af8365a1280c807a80a1b"),
|
restic.TestParseID("5fd0d8b2ef0fa5d23e58f1e460188abb0f525c0f0c4af8365a1280c807a80a1b"),
|
||||||
}
|
}
|
||||||
|
|
||||||
checkSnapshots(t, env.gopts, repo, env.mountpoint, env.repo, ids)
|
checkSnapshots(t, env.gopts, repo, env.mountpoint, env.repo, ids, 4)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,11 +23,13 @@ type SnapshotsDir struct {
|
||||||
root *Root
|
root *Root
|
||||||
snapshots restic.Snapshots
|
snapshots restic.Snapshots
|
||||||
names map[string]*restic.Snapshot
|
names map[string]*restic.Snapshot
|
||||||
|
latest string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure that *SnapshotsDir implements these interfaces
|
// ensure that *SnapshotsDir implements these interfaces
|
||||||
var _ = fs.HandleReadDirAller(&SnapshotsDir{})
|
var _ = fs.HandleReadDirAller(&SnapshotsDir{})
|
||||||
var _ = fs.NodeStringLookuper(&SnapshotsDir{})
|
var _ = fs.NodeStringLookuper(&SnapshotsDir{})
|
||||||
|
var _ = fs.NodeReadlinker(&snapshotLink{})
|
||||||
|
|
||||||
// NewSnapshotsDir returns a new directory containing snapshots.
|
// NewSnapshotsDir returns a new directory containing snapshots.
|
||||||
func NewSnapshotsDir(root *Root, inode uint64, snapshots restic.Snapshots) *SnapshotsDir {
|
func NewSnapshotsDir(root *Root, inode uint64, snapshots restic.Snapshots) *SnapshotsDir {
|
||||||
|
@ -39,8 +41,16 @@ func NewSnapshotsDir(root *Root, inode uint64, snapshots restic.Snapshots) *Snap
|
||||||
names: make(map[string]*restic.Snapshot, len(snapshots)),
|
names: make(map[string]*restic.Snapshot, len(snapshots)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Track latest Snapshot
|
||||||
|
var latestTime time.Time
|
||||||
|
d.latest = ""
|
||||||
|
|
||||||
for _, sn := range snapshots {
|
for _, sn := range snapshots {
|
||||||
name := sn.Time.Format(time.RFC3339)
|
name := sn.Time.Format(time.RFC3339)
|
||||||
|
if d.latest == "" || !sn.Time.Before(latestTime) {
|
||||||
|
latestTime = sn.Time
|
||||||
|
d.latest = name
|
||||||
|
}
|
||||||
for i := 1; ; i++ {
|
for i := 1; ; i++ {
|
||||||
if _, ok := d.names[name]; !ok {
|
if _, ok := d.names[name]; !ok {
|
||||||
break
|
break
|
||||||
|
@ -93,15 +103,65 @@ func (d *SnapshotsDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Latest
|
||||||
|
if d.latest != "" {
|
||||||
|
items = append(items, fuse.Dirent{
|
||||||
|
Inode: fs.GenerateDynamicInode(d.inode, "latest"),
|
||||||
|
Name: "latest",
|
||||||
|
Type: fuse.DT_Link,
|
||||||
|
})
|
||||||
|
}
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type snapshotLink struct {
|
||||||
|
root *Root
|
||||||
|
inode uint64
|
||||||
|
target string
|
||||||
|
snapshot *restic.Snapshot
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSnapshotLink(ctx context.Context, root *Root, inode uint64, target string, snapshot *restic.Snapshot) (*snapshotLink, error) {
|
||||||
|
return &snapshotLink{root: root, inode: inode, target: target, snapshot: snapshot}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *snapshotLink) Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error) {
|
||||||
|
return l.target, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *snapshotLink) Attr(ctx context.Context, a *fuse.Attr) error {
|
||||||
|
a.Inode = l.inode
|
||||||
|
a.Mode = os.ModeSymlink | 0777
|
||||||
|
|
||||||
|
if !l.root.cfg.OwnerIsRoot {
|
||||||
|
a.Uid = uint32(os.Getuid())
|
||||||
|
a.Gid = uint32(os.Getgid())
|
||||||
|
}
|
||||||
|
a.Atime = l.snapshot.Time
|
||||||
|
a.Ctime = l.snapshot.Time
|
||||||
|
a.Mtime = l.snapshot.Time
|
||||||
|
|
||||||
|
a.Nlink = 1
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Lookup returns a specific entry from the root node.
|
// Lookup returns a specific entry from the root node.
|
||||||
func (d *SnapshotsDir) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
func (d *SnapshotsDir) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
||||||
debug.Log("Lookup(%s)", name)
|
debug.Log("Lookup(%s)", name)
|
||||||
|
|
||||||
sn, ok := d.names[name]
|
sn, ok := d.names[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
if name == "latest" && d.latest != "" {
|
||||||
|
sn2, ok2 := d.names[d.latest]
|
||||||
|
|
||||||
|
// internal error
|
||||||
|
if !ok2 {
|
||||||
|
return nil, fuse.ENOENT
|
||||||
|
}
|
||||||
|
|
||||||
|
return newSnapshotLink(ctx, d.root, fs.GenerateDynamicInode(d.inode, name), d.latest, sn2)
|
||||||
|
}
|
||||||
return nil, fuse.ENOENT
|
return nil, fuse.ENOENT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue