mirror of
https://github.com/kopia/kopia.git
synced 2026-01-26 23:38:04 -05:00
218 lines
5.0 KiB
Go
218 lines
5.0 KiB
Go
//go:build !windows && !openbsd && !freebsd
|
|
// +build !windows,!openbsd,!freebsd
|
|
|
|
// Package fusemount implements FUSE filesystem nodes for mounting contents of filesystem stored in repository.
|
|
//
|
|
// The FUSE implementation used is from github.com/hanwen/go-fuse/v2
|
|
package fusemount
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"sync"
|
|
"syscall"
|
|
|
|
gofusefs "github.com/hanwen/go-fuse/v2/fs"
|
|
"github.com/hanwen/go-fuse/v2/fuse"
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/net/context"
|
|
|
|
"github.com/kopia/kopia/fs"
|
|
"github.com/kopia/kopia/repo/logging"
|
|
)
|
|
|
|
var log = logging.Module("fuse")
|
|
|
|
const fakeBlockSize = 4096
|
|
|
|
type fuseNode struct {
|
|
gofusefs.Inode
|
|
entry fs.Entry
|
|
}
|
|
|
|
func populateAttributes(a *fuse.Attr, e fs.Entry) {
|
|
a.Mode = uint32(e.Mode()) & uint32(os.ModePerm)
|
|
a.Size = uint64(e.Size())
|
|
a.Mtime = uint64(e.ModTime().Unix())
|
|
a.Ctime = a.Mtime
|
|
a.Atime = a.Mtime
|
|
a.Nlink = 1
|
|
a.Uid = e.Owner().UserID
|
|
a.Gid = e.Owner().GroupID
|
|
a.Blocks = (a.Size + fakeBlockSize - 1) / fakeBlockSize
|
|
}
|
|
|
|
func (n *fuseNode) Getattr(ctx context.Context, fh gofusefs.FileHandle, a *fuse.AttrOut) syscall.Errno {
|
|
populateAttributes(&a.Attr, n.entry)
|
|
|
|
a.Ino = n.StableAttr().Ino
|
|
|
|
return gofusefs.OK
|
|
}
|
|
|
|
type fuseFileNode struct {
|
|
fuseNode
|
|
}
|
|
|
|
func (f *fuseFileNode) Open(ctx context.Context, flags uint32) (gofusefs.FileHandle, uint32, syscall.Errno) {
|
|
reader, err := f.entry.(fs.File).Open(ctx)
|
|
if err != nil {
|
|
log(ctx).Errorf("error opening %v: %v", f.entry.Name(), err)
|
|
|
|
return nil, 0, syscall.EIO
|
|
}
|
|
|
|
return &fuseFileHandle{reader: reader, file: f.entry.(fs.File)}, 0, gofusefs.OK
|
|
}
|
|
|
|
type fuseFileHandle struct {
|
|
mu sync.Mutex
|
|
reader fs.Reader
|
|
file fs.File
|
|
}
|
|
|
|
func (f *fuseFileHandle) Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
|
|
_, err := f.reader.Seek(off, io.SeekStart)
|
|
if err != nil {
|
|
log(ctx).Errorf("seek error: %v %v: %v", f.file.Name(), off, err)
|
|
|
|
return nil, syscall.EIO
|
|
}
|
|
|
|
n, err := f.reader.Read(dest)
|
|
|
|
if err != nil && !errors.Is(err, io.EOF) {
|
|
log(ctx).Errorf("read error: %v: %v", f.file.Name(), err)
|
|
return nil, syscall.EIO
|
|
}
|
|
|
|
return fuse.ReadResultData(dest[0:n]), gofusefs.OK
|
|
}
|
|
|
|
func (f *fuseFileHandle) Release(ctx context.Context) syscall.Errno {
|
|
f.reader.Close() //nolint:errcheck
|
|
|
|
return gofusefs.OK
|
|
}
|
|
|
|
type fuseDirectoryNode struct {
|
|
fuseNode
|
|
}
|
|
|
|
func (dir *fuseDirectoryNode) directory() fs.Directory {
|
|
return dir.entry.(fs.Directory)
|
|
}
|
|
|
|
func (dir *fuseDirectoryNode) Lookup(ctx context.Context, fileName string, out *fuse.EntryOut) (*gofusefs.Inode, syscall.Errno) {
|
|
entries, err := dir.directory().Readdir(ctx)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, syscall.ENOENT
|
|
}
|
|
|
|
log(ctx).Errorf("lookup error %v in %v: %v", fileName, dir.entry.Name(), err)
|
|
|
|
return nil, syscall.EIO
|
|
}
|
|
|
|
e := entries.FindByName(fileName)
|
|
if e == nil {
|
|
return nil, syscall.ENOENT
|
|
}
|
|
|
|
stable := gofusefs.StableAttr{
|
|
Mode: entryToFuseMode(e),
|
|
}
|
|
|
|
n, err := newFuseNode(e)
|
|
if err != nil {
|
|
return nil, syscall.EIO
|
|
}
|
|
|
|
child := dir.NewInode(ctx, n, stable)
|
|
|
|
populateAttributes(&out.Attr, e)
|
|
|
|
return child, gofusefs.OK
|
|
}
|
|
|
|
func (dir *fuseDirectoryNode) Readdir(ctx context.Context) (gofusefs.DirStream, syscall.Errno) {
|
|
entries, err := dir.directory().Readdir(ctx)
|
|
if err != nil {
|
|
log(ctx).Errorf("error reading directory %v: %v", dir.entry.Name(), err)
|
|
return nil, syscall.EIO
|
|
}
|
|
|
|
result := []fuse.DirEntry{}
|
|
for _, e := range entries {
|
|
result = append(result, fuse.DirEntry{
|
|
Name: e.Name(),
|
|
Mode: entryToFuseMode(e),
|
|
})
|
|
}
|
|
|
|
return gofusefs.NewListDirStream(result), gofusefs.OK
|
|
}
|
|
|
|
type fuseSymlinkNode struct {
|
|
fuseNode
|
|
}
|
|
|
|
func (sl *fuseSymlinkNode) Readlink(ctx context.Context) ([]byte, syscall.Errno) {
|
|
v, err := sl.entry.(fs.Symlink).Readlink(ctx)
|
|
if err != nil {
|
|
log(ctx).Errorf("error reading symlink %v: %v", sl.entry.Name(), err)
|
|
return nil, syscall.EIO
|
|
}
|
|
|
|
return []byte(v), gofusefs.OK
|
|
}
|
|
|
|
func entryToFuseMode(e fs.Entry) uint32 {
|
|
switch e.(type) {
|
|
case fs.File:
|
|
return fuse.S_IFREG
|
|
case fs.Directory:
|
|
return fuse.S_IFDIR
|
|
case fs.Symlink:
|
|
return fuse.S_IFLNK
|
|
default:
|
|
return fuse.S_IFREG
|
|
}
|
|
}
|
|
|
|
func newFuseNode(e fs.Entry) (gofusefs.InodeEmbedder, error) {
|
|
switch e := e.(type) {
|
|
case fs.Directory:
|
|
return newDirectoryNode(e), nil
|
|
case fs.File:
|
|
return &fuseFileNode{fuseNode{entry: e}}, nil
|
|
case fs.Symlink:
|
|
return &fuseSymlinkNode{fuseNode{entry: e}}, nil
|
|
default:
|
|
return nil, errors.Errorf("entry type not supported: %v", e.Mode())
|
|
}
|
|
}
|
|
|
|
func newDirectoryNode(dir fs.Directory) gofusefs.InodeEmbedder {
|
|
return &fuseDirectoryNode{fuseNode{entry: dir}}
|
|
}
|
|
|
|
// NewDirectoryNode returns FUSE Node for a given fs.Directory.
|
|
func NewDirectoryNode(dir fs.Directory) gofusefs.InodeEmbedder {
|
|
return newDirectoryNode(dir)
|
|
}
|
|
|
|
var (
|
|
_ gofusefs.NodeGetattrer = (*fuseNode)(nil)
|
|
_ gofusefs.NodeOpener = (*fuseFileNode)(nil)
|
|
_ gofusefs.NodeLookuper = (*fuseDirectoryNode)(nil)
|
|
_ gofusefs.NodeReaddirer = (*fuseDirectoryNode)(nil)
|
|
_ gofusefs.NodeReadlinker = (*fuseSymlinkNode)(nil)
|
|
_ gofusefs.FileReleaser = (*fuseFileHandle)(nil)
|
|
_ gofusefs.FileReader = (*fuseFileHandle)(nil)
|
|
)
|