diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 290ae4056..dc2ad673d 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -13,6 +13,8 @@ import ( ) type CmdMount struct { + Root bool `long:"owner-root" description:"use 'root' as the owner of files and dirs" default:"false"` + global *GlobalOptions ready chan struct{} done chan struct{} @@ -69,7 +71,7 @@ func (cmd CmdMount) Execute(args []string) error { } root := fs.Tree{} - root.Add("snapshots", fuse.NewSnapshotsDir(repo)) + root.Add("snapshots", fuse.NewSnapshotsDir(repo, cmd.Root)) cmd.global.Printf("Now serving %s at %s\n", repo.Backend().Location(), mountpoint) cmd.global.Printf("Don't forget to umount after quitting!\n") diff --git a/fuse/dir.go b/fuse/dir.go index 59bf8263b..7588b31e6 100644 --- a/fuse/dir.go +++ b/fuse/dir.go @@ -16,12 +16,14 @@ var _ = fs.HandleReadDirAller(&dir{}) var _ = fs.NodeStringLookuper(&dir{}) type dir struct { - repo *repository.Repository - items map[string]*restic.Node - inode uint64 + repo *repository.Repository + items map[string]*restic.Node + inode uint64 + node *restic.Node + ownerIsRoot bool } -func newDir(repo *repository.Repository, node *restic.Node) (*dir, error) { +func newDir(repo *repository.Repository, node *restic.Node, ownerIsRoot bool) (*dir, error) { tree, err := restic.LoadTree(repo, *node.Subtree) if err != nil { return nil, err @@ -32,13 +34,15 @@ func newDir(repo *repository.Repository, node *restic.Node) (*dir, error) { } return &dir{ - repo: repo, - items: items, - inode: node.Inode, + repo: repo, + node: node, + items: items, + inode: node.Inode, + ownerIsRoot: ownerIsRoot, }, nil } -func newDirFromSnapshot(repo *repository.Repository, snapshot SnapshotWithId) (*dir, error) { +func newDirFromSnapshot(repo *repository.Repository, snapshot SnapshotWithId, ownerIsRoot bool) (*dir, error) { tree, err := restic.LoadTree(repo, *snapshot.Tree) if err != nil { return nil, err @@ -49,15 +53,32 @@ func newDirFromSnapshot(repo *repository.Repository, snapshot SnapshotWithId) (* } return &dir{ - repo: repo, - items: items, - inode: inodeFromBackendId(snapshot.ID), + repo: repo, + node: &restic.Node{ + UID: uint32(os.Getuid()), + GID: uint32(os.Getgid()), + AccessTime: snapshot.Time, + ModTime: snapshot.Time, + ChangeTime: snapshot.Time, + Mode: os.ModeDir | 0555, + }, + items: items, + inode: inodeFromBackendId(snapshot.ID), + ownerIsRoot: ownerIsRoot, }, nil } func (d *dir) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = d.inode - a.Mode = os.ModeDir | 0555 + a.Mode = os.ModeDir | d.node.Mode + + if !d.ownerIsRoot { + a.Uid = d.node.UID + a.Gid = d.node.GID + } + a.Atime = d.node.AccessTime + a.Ctime = d.node.ChangeTime + a.Mtime = d.node.ModTime return nil } @@ -92,11 +113,11 @@ func (d *dir) Lookup(ctx context.Context, name string) (fs.Node, error) { } switch node.Type { case "dir": - return newDir(d.repo, node) + return newDir(d.repo, node, d.ownerIsRoot) case "file": - return newFile(d.repo, node) + return newFile(d.repo, node, d.ownerIsRoot) case "symlink": - return newLink(d.repo, node) + return newLink(d.repo, node, d.ownerIsRoot) default: return nil, fuse.ENOENT } diff --git a/fuse/file.go b/fuse/file.go index 97e46543b..20e6fdd16 100644 --- a/fuse/file.go +++ b/fuse/file.go @@ -24,8 +24,9 @@ type BlobLoader interface { } type file struct { - repo BlobLoader - node *restic.Node + repo BlobLoader + node *restic.Node + ownerIsRoot bool sizes []uint blobs [][]byte @@ -39,7 +40,7 @@ var blobPool = sync.Pool{ }, } -func newFile(repo BlobLoader, node *restic.Node) (*file, error) { +func newFile(repo BlobLoader, node *restic.Node, ownerIsRoot bool) (*file, error) { sizes := make([]uint, len(node.Content)) for i, id := range node.Content { size, err := repo.LookupBlobSize(id) @@ -51,10 +52,11 @@ func newFile(repo BlobLoader, node *restic.Node) (*file, error) { } return &file{ - repo: repo, - node: node, - sizes: sizes, - blobs: make([][]byte, len(node.Content)), + repo: repo, + node: node, + sizes: sizes, + blobs: make([][]byte, len(node.Content)), + ownerIsRoot: ownerIsRoot, }, nil } @@ -62,6 +64,14 @@ func (f *file) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = f.node.Inode a.Mode = f.node.Mode a.Size = f.node.Size + + if !f.ownerIsRoot { + a.Uid = f.node.UID + a.Gid = f.node.GID + } + a.Atime = f.node.AccessTime + a.Ctime = f.node.ChangeTime + a.Mtime = f.node.ModTime return nil } diff --git a/fuse/file_test.go b/fuse/file_test.go index 25ea574c3..db48f1e9d 100644 --- a/fuse/file_test.go +++ b/fuse/file_test.go @@ -3,7 +3,6 @@ package fuse import ( "bytes" "errors" - "fmt" "math/rand" "testing" "time" @@ -121,7 +120,7 @@ func TestFuseFile(t *testing.T) { Size: 42, Content: ids, } - f, err := newFile(repo, node) + f, err := newFile(repo, node, false) OK(t, err) attr := fuse.Attr{} @@ -148,7 +147,6 @@ func TestFuseFile(t *testing.T) { } b := memfile[offset : offset+length] - fmt.Printf("test offset %d, length %d\n", offset, length) res := testRead(t, f, offset, length, b) if !bytes.Equal(b, res) { t.Errorf("test %d failed (offset %d, length %d), wrong data returned", i, offset, length) diff --git a/fuse/link.go b/fuse/link.go index c5a17811b..61e05b3fd 100644 --- a/fuse/link.go +++ b/fuse/link.go @@ -12,11 +12,12 @@ import ( var _ = fs.NodeReadlinker(&link{}) type link struct { - node *restic.Node + node *restic.Node + ownerIsRoot bool } -func newLink(repo *repository.Repository, node *restic.Node) (*link, error) { - return &link{node: node}, nil +func newLink(repo *repository.Repository, node *restic.Node, ownerIsRoot bool) (*link, error) { + return &link{node: node, ownerIsRoot: ownerIsRoot}, nil } func (l *link) Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error) { @@ -26,5 +27,13 @@ func (l *link) Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, func (l *link) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = l.node.Inode a.Mode = l.node.Mode + + if !l.ownerIsRoot { + a.Uid = l.node.UID + a.Gid = l.node.GID + } + a.Atime = l.node.AccessTime + a.Ctime = l.node.ChangeTime + a.Mtime = l.node.ModTime return nil } diff --git a/fuse/snapshot.go b/fuse/snapshot.go index 6f82998c4..99f84e806 100644 --- a/fuse/snapshot.go +++ b/fuse/snapshot.go @@ -27,23 +27,30 @@ var _ = fs.HandleReadDirAller(&SnapshotsDir{}) var _ = fs.NodeStringLookuper(&SnapshotsDir{}) type SnapshotsDir struct { - repo *repository.Repository + repo *repository.Repository + ownerIsRoot bool // knownSnapshots maps snapshot timestamp to the snapshot sync.RWMutex knownSnapshots map[string]SnapshotWithId } -func NewSnapshotsDir(repo *repository.Repository) *SnapshotsDir { +func NewSnapshotsDir(repo *repository.Repository, ownerIsRoot bool) *SnapshotsDir { return &SnapshotsDir{ repo: repo, knownSnapshots: make(map[string]SnapshotWithId), + ownerIsRoot: ownerIsRoot, } } func (sn *SnapshotsDir) Attr(ctx context.Context, attr *fuse.Attr) error { attr.Inode = 0 attr.Mode = os.ModeDir | 0555 + + if !sn.ownerIsRoot { + attr.Uid = uint32(os.Getuid()) + attr.Gid = uint32(os.Getgid()) + } return nil } @@ -105,5 +112,5 @@ func (sn *SnapshotsDir) Lookup(ctx context.Context, name string) (fs.Node, error } } - return newDirFromSnapshot(sn.repo, snapshot) + return newDirFromSnapshot(sn.repo, snapshot, sn.ownerIsRoot) }