mirror of
https://github.com/restic/restic.git
synced 2026-05-15 14:45:24 +00:00
461bddb0e8
When using `restic mount` to serve a repository via a POSIX host's file system, all files backed up from Windows systems will show up on the mounting host with a (hard)link count of 0. While this is not a problem in general and most programs do not even register this strange value, some others (such as Samba's smbd(8)) will go the extra mile and check a file's stat() results for various properties (such as a positive non-zero link count), and refuse to operate if any of the reported values appear off. Since other inode properties absent from non-POSIX backup sources are also "faked up" (e.g., the inode number) during mount and work fine in general, the chances of this change to be in any way harmful are probably rather slim.
183 lines
4.6 KiB
Go
183 lines
4.6 KiB
Go
//go:build darwin || freebsd || linux
|
|
|
|
package fuse
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
|
|
"github.com/restic/restic/internal/data"
|
|
"github.com/restic/restic/internal/debug"
|
|
"github.com/restic/restic/internal/errors"
|
|
"github.com/restic/restic/internal/restic"
|
|
|
|
"github.com/anacrolix/fuse"
|
|
"github.com/anacrolix/fuse/fs"
|
|
)
|
|
|
|
// The default block size to report in stat
|
|
const blockSize = 512
|
|
|
|
// Statically ensure that *file and *openFile implement the given interfaces
|
|
var _ = fs.HandleReader(&openFile{})
|
|
var _ = fs.NodeForgetter(&file{})
|
|
var _ = fs.NodeGetxattrer(&file{})
|
|
var _ = fs.NodeListxattrer(&file{})
|
|
var _ = fs.NodeOpener(&file{})
|
|
|
|
type file struct {
|
|
root *Root
|
|
forget forgetFn
|
|
node *data.Node
|
|
inode uint64
|
|
}
|
|
|
|
type openFile struct {
|
|
file
|
|
// cumsize[i] holds the cumulative size of blobs[:i].
|
|
cumsize []uint64
|
|
}
|
|
|
|
func newFile(root *Root, forget forgetFn, inode uint64, node *data.Node) (fusefile *file, err error) {
|
|
debug.Log("create new file for %v with %d blobs", node.Name, len(node.Content))
|
|
return &file{
|
|
inode: inode,
|
|
forget: forget,
|
|
root: root,
|
|
node: node,
|
|
}, nil
|
|
}
|
|
|
|
func (f *file) Attr(_ context.Context, a *fuse.Attr) error {
|
|
debug.Log("Attr(%v)", f.node.Name)
|
|
a.Inode = f.inode
|
|
a.Mode = f.node.Mode
|
|
a.Size = f.node.Size
|
|
a.Blocks = (f.node.Size + blockSize - 1) / blockSize
|
|
a.BlockSize = blockSize
|
|
// present a link count > 0 to keep over-eager stat() .st_nlink checks (e.g., from Samba's smbd) happy
|
|
a.Nlink = max(uint32(1), uint32(f.node.Links))
|
|
|
|
if !f.root.cfg.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
|
|
|
|
}
|
|
|
|
func (f *file) Open(ctx context.Context, _ *fuse.OpenRequest, _ *fuse.OpenResponse) (fs.Handle, error) {
|
|
debug.Log("open file %v with %d blobs", f.node.Name, len(f.node.Content))
|
|
|
|
var bytes uint64
|
|
cumsize := make([]uint64, 1+len(f.node.Content))
|
|
for i, id := range f.node.Content {
|
|
if ctx.Err() != nil {
|
|
return nil, ctx.Err()
|
|
}
|
|
|
|
size, found := f.root.repo.LookupBlobSize(restic.DataBlob, id)
|
|
if !found {
|
|
return nil, errors.Errorf("id %v not found in repository", id)
|
|
}
|
|
|
|
bytes += uint64(size)
|
|
cumsize[i+1] = bytes
|
|
}
|
|
|
|
var of = openFile{file: *f}
|
|
|
|
if bytes != f.node.Size {
|
|
debug.Log("sizes do not match: node.Size %v != size %v, using real size", f.node.Size, bytes)
|
|
// Make a copy of the node with correct size
|
|
nodenew := *f.node
|
|
nodenew.Size = bytes
|
|
of.file.node = &nodenew
|
|
}
|
|
of.cumsize = cumsize
|
|
|
|
return &of, nil
|
|
}
|
|
|
|
func (f *openFile) getBlobAt(ctx context.Context, i int) (blob []byte, err error) {
|
|
blob, err = f.root.blobCache.GetOrCompute(f.node.Content[i], func() ([]byte, error) {
|
|
return f.root.repo.LoadBlob(ctx, restic.DataBlob, f.node.Content[i], nil)
|
|
})
|
|
if err != nil {
|
|
debug.Log("LoadBlob(%v, %v) failed: %v", f.node.Name, f.node.Content[i], err)
|
|
return nil, unwrapCtxCanceled(err)
|
|
}
|
|
|
|
return blob, nil
|
|
}
|
|
|
|
func (f *openFile) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
|
|
debug.Log("Read(%v, %v, %v), file size %v", f.node.Name, req.Size, req.Offset, f.node.Size)
|
|
offset := uint64(req.Offset)
|
|
|
|
// as stated in https://godoc.org/bazil.org/fuse/fs#HandleReader there
|
|
// is no need to check if offset > size
|
|
|
|
// handle special case: file is empty
|
|
if f.node.Size == 0 {
|
|
resp.Data = resp.Data[:0]
|
|
return nil
|
|
}
|
|
|
|
// Skip blobs before the offset
|
|
startContent := -1 + sort.Search(len(f.cumsize), func(i int) bool {
|
|
return f.cumsize[i] > offset
|
|
})
|
|
offset -= f.cumsize[startContent]
|
|
|
|
dst := resp.Data[0:req.Size]
|
|
readBytes := 0
|
|
remainingBytes := req.Size
|
|
|
|
// The documentation of bazil/fuse actually says that synchronization is
|
|
// required (see https://godoc.org/bazil.org/fuse#hdr-Service_Methods):
|
|
//
|
|
// Multiple goroutines may call service methods simultaneously;
|
|
// the methods being called are responsible for appropriate synchronization.
|
|
//
|
|
// However, no lock needed here as getBlobAt can be called concurrently
|
|
// (blobCache has its own locking)
|
|
for i := startContent; remainingBytes > 0 && i < len(f.cumsize)-1; i++ {
|
|
blob, err := f.getBlobAt(ctx, i)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if offset > 0 {
|
|
blob = blob[offset:]
|
|
offset = 0
|
|
}
|
|
|
|
copied := copy(dst, blob)
|
|
remainingBytes -= copied
|
|
readBytes += copied
|
|
|
|
dst = dst[copied:]
|
|
}
|
|
resp.Data = resp.Data[:readBytes]
|
|
|
|
return nil
|
|
}
|
|
|
|
func (f *file) Listxattr(_ context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error {
|
|
nodeToXattrList(f.node, req, resp)
|
|
return nil
|
|
}
|
|
|
|
func (f *file) Getxattr(_ context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error {
|
|
return nodeGetXattr(f.node, req, resp)
|
|
}
|
|
|
|
func (f *file) Forget() {
|
|
f.forget()
|
|
}
|