// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "context" "os" "path/filepath" "syscall" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/internal/renameat" "golang.org/x/sys/unix" ) // LoopbackRoot holds the parameters for creating a new loopback // filesystem. Loopback filesystem delegate their operations to an // underlying POSIX file system. type LoopbackRoot struct { // The path to the root of the underlying file system. Path string // The device on which the Path resides. This must be set if // the underlying filesystem crosses file systems. Dev uint64 // NewNode returns a new InodeEmbedder to be used to respond // to a LOOKUP/CREATE/MKDIR/MKNOD opcode. If not set, use a // LoopbackNode. NewNode func(rootData *LoopbackRoot, parent *Inode, name string, st *syscall.Stat_t) InodeEmbedder } func (r *LoopbackRoot) newNode(parent *Inode, name string, st *syscall.Stat_t) InodeEmbedder { if r.NewNode != nil { return r.NewNode(r, parent, name, st) } return &LoopbackNode{ RootData: r, } } func (r *LoopbackRoot) idFromStat(st *syscall.Stat_t) StableAttr { // We compose an inode number by the underlying inode, and // mixing in the device number. In traditional filesystems, // the inode numbers are small. The device numbers are also // small (typically 16 bit). Finally, we mask out the root // device number of the root, so a loopback FS that does not // encompass multiple mounts will reflect the inode numbers of // the underlying filesystem swapped := (uint64(st.Dev) << 32) | (uint64(st.Dev) >> 32) swappedRootDev := (r.Dev << 32) | (r.Dev >> 32) return StableAttr{ Mode: uint32(st.Mode), Gen: 1, // This should work well for traditional backing FSes, // not so much for other go-fuse FS-es Ino: (swapped ^ swappedRootDev) ^ st.Ino, } } // LoopbackNode is a filesystem node in a loopback file system. It is // public so it can be used as a basis for other loopback based // filesystems. See NewLoopbackFile or LoopbackRoot for more // information. type LoopbackNode struct { Inode // RootData points back to the root of the loopback filesystem. RootData *LoopbackRoot } var _ = (NodeStatfser)((*LoopbackNode)(nil)) func (n *LoopbackNode) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno { s := syscall.Statfs_t{} err := syscall.Statfs(n.path(), &s) if err != nil { return ToErrno(err) } out.FromStatfsT(&s) return OK } // path returns the full path to the file in the underlying file // system. func (n *LoopbackNode) path() string { path := n.Path(n.Root()) return filepath.Join(n.RootData.Path, path) } var _ = (NodeLookuper)((*LoopbackNode)(nil)) func (n *LoopbackNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) { p := filepath.Join(n.path(), name) st := syscall.Stat_t{} err := syscall.Lstat(p, &st) if err != nil { return nil, ToErrno(err) } out.Attr.FromStat(&st) node := n.RootData.newNode(n.EmbeddedInode(), name, &st) ch := n.NewInode(ctx, node, n.RootData.idFromStat(&st)) return ch, 0 } // preserveOwner sets uid and gid of `path` according to the caller information // in `ctx`. func (n *LoopbackNode) preserveOwner(ctx context.Context, path string) error { if os.Getuid() != 0 { return nil } caller, ok := fuse.FromContext(ctx) if !ok { return nil } return syscall.Lchown(path, int(caller.Uid), int(caller.Gid)) } var _ = (NodeMknoder)((*LoopbackNode)(nil)) func (n *LoopbackNode) Mknod(ctx context.Context, name string, mode, rdev uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) { p := filepath.Join(n.path(), name) err := syscall.Mknod(p, mode, intDev(rdev)) if err != nil { return nil, ToErrno(err) } n.preserveOwner(ctx, p) st := syscall.Stat_t{} if err := syscall.Lstat(p, &st); err != nil { syscall.Rmdir(p) return nil, ToErrno(err) } out.Attr.FromStat(&st) node := n.RootData.newNode(n.EmbeddedInode(), name, &st) ch := n.NewInode(ctx, node, n.RootData.idFromStat(&st)) return ch, 0 } var _ = (NodeMkdirer)((*LoopbackNode)(nil)) func (n *LoopbackNode) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) { p := filepath.Join(n.path(), name) err := os.Mkdir(p, os.FileMode(mode)) if err != nil { return nil, ToErrno(err) } n.preserveOwner(ctx, p) st := syscall.Stat_t{} if err := syscall.Lstat(p, &st); err != nil { syscall.Rmdir(p) return nil, ToErrno(err) } out.Attr.FromStat(&st) node := n.RootData.newNode(n.EmbeddedInode(), name, &st) ch := n.NewInode(ctx, node, n.RootData.idFromStat(&st)) return ch, 0 } var _ = (NodeRmdirer)((*LoopbackNode)(nil)) func (n *LoopbackNode) Rmdir(ctx context.Context, name string) syscall.Errno { p := filepath.Join(n.path(), name) err := syscall.Rmdir(p) return ToErrno(err) } var _ = (NodeUnlinker)((*LoopbackNode)(nil)) func (n *LoopbackNode) Unlink(ctx context.Context, name string) syscall.Errno { p := filepath.Join(n.path(), name) err := syscall.Unlink(p) return ToErrno(err) } var _ = (NodeRenamer)((*LoopbackNode)(nil)) func (n *LoopbackNode) Rename(ctx context.Context, name string, newParent InodeEmbedder, newName string, flags uint32) syscall.Errno { if flags&RENAME_EXCHANGE != 0 { return n.renameExchange(name, newParent, newName) } p1 := filepath.Join(n.path(), name) p2 := filepath.Join(n.RootData.Path, newParent.EmbeddedInode().Path(nil), newName) err := syscall.Rename(p1, p2) return ToErrno(err) } var _ = (NodeCreater)((*LoopbackNode)(nil)) func (n *LoopbackNode) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (inode *Inode, fh FileHandle, fuseFlags uint32, errno syscall.Errno) { p := filepath.Join(n.path(), name) flags = flags &^ syscall.O_APPEND fd, err := syscall.Open(p, int(flags)|os.O_CREATE, mode) if err != nil { return nil, nil, 0, ToErrno(err) } n.preserveOwner(ctx, p) st := syscall.Stat_t{} if err := syscall.Fstat(fd, &st); err != nil { syscall.Close(fd) return nil, nil, 0, ToErrno(err) } node := n.RootData.newNode(n.EmbeddedInode(), name, &st) ch := n.NewInode(ctx, node, n.RootData.idFromStat(&st)) lf := NewLoopbackFile(fd) out.FromStat(&st) return ch, lf, 0, 0 } func (n *LoopbackNode) renameExchange(name string, newparent InodeEmbedder, newName string) syscall.Errno { fd1, err := syscall.Open(n.path(), syscall.O_DIRECTORY, 0) if err != nil { return ToErrno(err) } defer syscall.Close(fd1) p2 := filepath.Join(n.RootData.Path, newparent.EmbeddedInode().Path(nil)) fd2, err := syscall.Open(p2, syscall.O_DIRECTORY, 0) defer syscall.Close(fd2) if err != nil { return ToErrno(err) } var st syscall.Stat_t if err := syscall.Fstat(fd1, &st); err != nil { return ToErrno(err) } // Double check that nodes didn't change from under us. inode := &n.Inode if inode.Root() != inode && inode.StableAttr().Ino != n.RootData.idFromStat(&st).Ino { return syscall.EBUSY } if err := syscall.Fstat(fd2, &st); err != nil { return ToErrno(err) } newinode := newparent.EmbeddedInode() if newinode.Root() != newinode && newinode.StableAttr().Ino != n.RootData.idFromStat(&st).Ino { return syscall.EBUSY } return ToErrno(renameat.Renameat(fd1, name, fd2, newName, renameat.RENAME_EXCHANGE)) } var _ = (NodeSymlinker)((*LoopbackNode)(nil)) func (n *LoopbackNode) Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) { p := filepath.Join(n.path(), name) err := syscall.Symlink(target, p) if err != nil { return nil, ToErrno(err) } n.preserveOwner(ctx, p) st := syscall.Stat_t{} if err := syscall.Lstat(p, &st); err != nil { syscall.Unlink(p) return nil, ToErrno(err) } node := n.RootData.newNode(n.EmbeddedInode(), name, &st) ch := n.NewInode(ctx, node, n.RootData.idFromStat(&st)) out.Attr.FromStat(&st) return ch, 0 } var _ = (NodeLinker)((*LoopbackNode)(nil)) func (n *LoopbackNode) Link(ctx context.Context, target InodeEmbedder, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) { p := filepath.Join(n.path(), name) err := syscall.Link(filepath.Join(n.RootData.Path, target.EmbeddedInode().Path(nil)), p) if err != nil { return nil, ToErrno(err) } st := syscall.Stat_t{} if err := syscall.Lstat(p, &st); err != nil { syscall.Unlink(p) return nil, ToErrno(err) } node := n.RootData.newNode(n.EmbeddedInode(), name, &st) ch := n.NewInode(ctx, node, n.RootData.idFromStat(&st)) out.Attr.FromStat(&st) return ch, 0 } var _ = (NodeReadlinker)((*LoopbackNode)(nil)) func (n *LoopbackNode) Readlink(ctx context.Context) ([]byte, syscall.Errno) { p := n.path() for l := 256; ; l *= 2 { buf := make([]byte, l) sz, err := syscall.Readlink(p, buf) if err != nil { return nil, ToErrno(err) } if sz < len(buf) { return buf[:sz], 0 } } } var _ = (NodeOpener)((*LoopbackNode)(nil)) func (n *LoopbackNode) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) { flags = flags &^ syscall.O_APPEND p := n.path() f, err := syscall.Open(p, int(flags), 0) if err != nil { return nil, 0, ToErrno(err) } lf := NewLoopbackFile(f) return lf, 0, 0 } var _ = (NodeOpendirer)((*LoopbackNode)(nil)) func (n *LoopbackNode) Opendir(ctx context.Context) syscall.Errno { fd, err := syscall.Open(n.path(), syscall.O_DIRECTORY, 0755) if err != nil { return ToErrno(err) } syscall.Close(fd) return OK } var _ = (NodeReaddirer)((*LoopbackNode)(nil)) func (n *LoopbackNode) Readdir(ctx context.Context) (DirStream, syscall.Errno) { return NewLoopbackDirStream(n.path()) } var _ = (NodeGetattrer)((*LoopbackNode)(nil)) func (n *LoopbackNode) Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno { if f != nil { return f.(FileGetattrer).Getattr(ctx, out) } p := n.path() var err error st := syscall.Stat_t{} if &n.Inode == n.Root() { err = syscall.Stat(p, &st) } else { err = syscall.Lstat(p, &st) } if err != nil { return ToErrno(err) } out.FromStat(&st) return OK } var _ = (NodeSetattrer)((*LoopbackNode)(nil)) func (n *LoopbackNode) Setattr(ctx context.Context, f FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno { p := n.path() fsa, ok := f.(FileSetattrer) if ok && fsa != nil { fsa.Setattr(ctx, in, out) } else { if m, ok := in.GetMode(); ok { if err := syscall.Chmod(p, m); err != nil { return ToErrno(err) } } uid, uok := in.GetUID() gid, gok := in.GetGID() if uok || gok { suid := -1 sgid := -1 if uok { suid = int(uid) } if gok { sgid = int(gid) } if err := syscall.Chown(p, suid, sgid); err != nil { return ToErrno(err) } } mtime, mok := in.GetMTime() atime, aok := in.GetATime() if mok || aok { ap := &atime mp := &mtime if !aok { ap = nil } if !mok { mp = nil } var ts [2]syscall.Timespec ts[0] = fuse.UtimeToTimespec(ap) ts[1] = fuse.UtimeToTimespec(mp) if err := syscall.UtimesNano(p, ts[:]); err != nil { return ToErrno(err) } } if sz, ok := in.GetSize(); ok { if err := syscall.Truncate(p, int64(sz)); err != nil { return ToErrno(err) } } } fga, ok := f.(FileGetattrer) if ok && fga != nil { fga.Getattr(ctx, out) } else { st := syscall.Stat_t{} err := syscall.Lstat(p, &st) if err != nil { return ToErrno(err) } out.FromStat(&st) } return OK } var _ = (NodeGetxattrer)((*LoopbackNode)(nil)) func (n *LoopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) { sz, err := unix.Lgetxattr(n.path(), attr, dest) return uint32(sz), ToErrno(err) } var _ = (NodeSetxattrer)((*LoopbackNode)(nil)) func (n *LoopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno { err := unix.Lsetxattr(n.path(), attr, data, int(flags)) return ToErrno(err) } var _ = (NodeRemovexattrer)((*LoopbackNode)(nil)) func (n *LoopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno { err := unix.Lremovexattr(n.path(), attr) return ToErrno(err) } var _ = (NodeCopyFileRanger)((*LoopbackNode)(nil)) func (n *LoopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle, offIn uint64, out *Inode, fhOut FileHandle, offOut uint64, len uint64, flags uint64) (uint32, syscall.Errno) { lfIn, ok := fhIn.(*loopbackFile) if !ok { return 0, unix.ENOTSUP } lfOut, ok := fhOut.(*loopbackFile) if !ok { return 0, unix.ENOTSUP } signedOffIn := int64(offIn) signedOffOut := int64(offOut) doCopyFileRange(lfIn.fd, signedOffIn, lfOut.fd, signedOffOut, int(len), int(flags)) return 0, syscall.ENOSYS } // NewLoopbackRoot returns a root node for a loopback file system whose // root is at the given root. This node implements all NodeXxxxer // operations available. func NewLoopbackRoot(rootPath string) (InodeEmbedder, error) { var st syscall.Stat_t err := syscall.Stat(rootPath, &st) if err != nil { return nil, err } root := &LoopbackRoot{ Path: rootPath, Dev: uint64(st.Dev), } return root.newNode(nil, "", &st), nil }