Commit 55370734 authored by Kirill Smelkov's avatar Kirill Smelkov

Merge branch 'master' into t

* master:
  fixup! rename NodeAttr to StableAttr
  rename NodeAttr to StableAttr
  internal: fix access for root user
  tarfs: handle empty directories, handle more file types
  nodefs: add Attr to MemSymlink
  nodefs: protect dirsteam overflow with lock
  nodefs: add TestReadDirStress
  nodefs: protect against double close
  newunionfs: run PosixTest tests.
  newunionfs: symlink/readlink
  posixtest: export list of tests
  nodefs: handle unimplemented Mkdir
  newunionfs: a new unionfs implementation
  fuse: fix SupportsVersion
  nodefs: skip tests if kernel doesn't support lseek/copy_file_range
  posixtest: new package for generic file system tests
  update go modules.
  nodefs: add EntryOut to Create signature
  nodefs: add Inode.IsDir
  nodefs: implement Setattr in loopback
parents 56dcd64b 315ce885
......@@ -51,7 +51,7 @@ func (r *StatFS) addFile(name string, a fuse.Attr) {
if ch == nil {
// Create a directory
ch = p.NewPersistentInode(context.Background(), &nodefs.Inode{},
nodefs.NodeAttr{Mode: syscall.S_IFDIR})
nodefs.StableAttr{Mode: syscall.S_IFDIR})
// Add it
p.AddChild(component, ch, true)
}
......@@ -63,7 +63,7 @@ func (r *StatFS) addFile(name string, a fuse.Attr) {
child := p.NewPersistentInode(context.Background(), &nodefs.MemRegularFile{
Data: make([]byte, a.Size),
Attr: a,
}, nodefs.NodeAttr{})
}, nodefs.StableAttr{})
// And add it
p.AddChild(base, child, true)
......
......@@ -27,7 +27,7 @@ func (r *HelloRoot) OnAdd(ctx context.Context) {
Attr: fuse.Attr{
Mode: 0644,
},
}, nodefs.NodeAttr{Ino: 2})
}, nodefs.StableAttr{Ino: 2})
r.AddChild("file.txt", ch, false)
}
......
......@@ -58,8 +58,8 @@ const (
_OP_FALLOCATE = uint32(43) // protocol version 19.
_OP_READDIRPLUS = uint32(44) // protocol version 21.
_OP_RENAME2 = uint32(45) // protocol version 23.
_OP_LSEEK = uint32(46)
_OP_COPY_FILE_RANGE = uint32(47)
_OP_LSEEK = uint32(46) // protocol version 24
_OP_COPY_FILE_RANGE = uint32(47) // protocol version 28.
// The following entries don't have to be compatible across Go-FUSE versions.
_OP_NOTIFY_INVAL_ENTRY = uint32(100)
......
......@@ -9,5 +9,5 @@ const outputHeaderSize = 160
const (
_FUSE_KERNEL_VERSION = 7
_MINIMUM_MINOR_VERSION = 12
_OUR_MINOR_VERSION = 23
_OUR_MINOR_VERSION = 28
)
......@@ -817,7 +817,7 @@ func (ms *Server) EntryNotify(parent uint64, name string) Status {
// SupportsVersion returns true if the kernel supports the given
// protocol version or newer.
func (in *InitIn) SupportsVersion(maj, min uint32) bool {
return in.Major >= maj || (in.Major == maj && in.Minor >= min)
return in.Major > maj || (in.Major == maj && in.Minor >= min)
}
// SupportsNotify returns whether a certain notification type is
......
module github.com/hanwen/go-fuse
require golang.org/x/sys v0.0.0-20180830151530-49385e6e1522
require (
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522
)
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 h1:Ve1ORMCxvRmSXBwJK+t3Oy+V2vRW2OetUQBq4rJIkZE=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
......@@ -12,6 +12,10 @@ import (
// HasAccess tests if a caller can access a file with permissions
// `perm` in mode `mask`
func HasAccess(callerUid, callerGid, fileUid, fileGid uint32, perm uint32, mask uint32) bool {
if callerUid == 0 {
// root can do anything.
return true
}
mask = mask & 7
if mask == 0 {
return true
......
......@@ -63,6 +63,7 @@ func TestHasAccess(t *testing.T) {
{myUid, myGid, myUid + 1, notMyGid, 0020, 002, false},
{myUid, myGid, myUid, myGid, 0000, 01, false},
{myUid, myGid, myUid, myGid, 0200, 01, false},
{0, myGid, myUid + 1, notMyGid, 0700, 01, true},
} {
got := HasAccess(tc.uid, tc.gid, tc.fuid, tc.fgid, tc.perm, tc.mask)
if got != tc.want {
......
// 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 unionfs
import (
"context"
"crypto/md5"
"fmt"
"io/ioutil"
"log"
"path/filepath"
"syscall"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/nodefs"
)
func filePathHash(path string) string {
dir, base := filepath.Split(path)
h := md5.New()
h.Write([]byte(dir))
return fmt.Sprintf("%x-%s", h.Sum(nil)[:8], base)
}
type unionFSRoot struct {
unionFSNode
roots []string
}
type unionFSNode struct {
nodefs.Inode
}
const delDir = "DELETIONS"
func (r *unionFSRoot) rmMarker(name string) syscall.Errno {
err := syscall.Unlink(r.markerPath(name))
if err != nil {
return err.(syscall.Errno)
}
return 0
}
func (r *unionFSRoot) writeMarker(name string) syscall.Errno {
dir := filepath.Join(r.roots[0], delDir)
var st syscall.Stat_t
if err := syscall.Stat(dir, &st); err == syscall.ENOENT {
if err := syscall.Mkdir(dir, 0755); err != nil {
log.Printf("Mkdir %q: %v", dir, err)
return syscall.EIO
}
} else if err != nil {
return err.(syscall.Errno)
}
dest := r.markerPath(name)
err := ioutil.WriteFile(dest, []byte(name), 0644)
return nodefs.ToErrno(err)
}
func (r *unionFSRoot) markerPath(name string) string {
return filepath.Join(r.roots[0], delDir, filePathHash(name))
}
func (r *unionFSRoot) isDeleted(name string) bool {
var st syscall.Stat_t
err := syscall.Stat(r.markerPath(name), &st)
return err == nil
}
func (n *unionFSNode) root() *unionFSRoot {
return n.Root().Operations().(*unionFSRoot)
}
var _ = (nodefs.Setattrer)((*unionFSNode)(nil))
func (n *unionFSNode) Setattr(ctx context.Context, fh nodefs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno {
if errno := n.promote(); errno != 0 {
return errno
}
if fh != nil {
return fh.(nodefs.FileSetattrer).Setattr(ctx, in, out)
}
p := filepath.Join(n.root().roots[0], n.Path(nil))
fsa, ok := fh.(nodefs.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 nodefs.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 nodefs.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 nodefs.ToErrno(err)
}
}
if sz, ok := in.GetSize(); ok {
if err := syscall.Truncate(p, int64(sz)); err != nil {
return nodefs.ToErrno(err)
}
}
}
fga, ok := fh.(nodefs.FileGetattrer)
if ok && fga != nil {
fga.Getattr(ctx, out)
} else {
st := syscall.Stat_t{}
err := syscall.Lstat(p, &st)
if err != nil {
return nodefs.ToErrno(err)
}
out.FromStat(&st)
}
return 0
}
var _ = (nodefs.Creater)((*unionFSNode)(nil))
func (n *unionFSNode) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (*nodefs.Inode, nodefs.FileHandle, uint32, syscall.Errno) {
var st syscall.Stat_t
dirName, idx := n.getBranch(&st)
if idx > 0 {
if errno := n.promote(); errno != 0 {
return nil, nil, 0, errno
}
idx = 0
}
fullPath := filepath.Join(dirName, name)
r := n.root()
if errno := r.rmMarker(fullPath); errno != 0 && errno != syscall.ENOENT {
return nil, nil, 0, errno
}
abs := filepath.Join(n.root().roots[0], fullPath)
fd, err := syscall.Creat(abs, mode)
if err != nil {
return nil, nil, 0, err.(syscall.Errno)
}
if err := syscall.Fstat(fd, &st); err != nil {
// now what?
syscall.Close(fd)
syscall.Unlink(abs)
return nil, nil, 0, err.(syscall.Errno)
}
ch := n.NewInode(ctx, &unionFSNode{}, nodefs.StableAttr{Mode: st.Mode, Ino: st.Ino})
out.FromStat(&st)
return ch, nodefs.NewLoopbackFile(fd), 0, 0
}
var _ = (nodefs.Opener)((*unionFSNode)(nil))
func (n *unionFSNode) Open(ctx context.Context, flags uint32) (nodefs.FileHandle, uint32, syscall.Errno) {
isWR := (flags&syscall.O_RDWR != 0) || (flags&syscall.O_WRONLY != 0)
var st syscall.Stat_t
nm, idx := n.getBranch(&st)
if isWR && idx > 0 {
if errno := n.promote(); errno != 0 {
return nil, 0, errno
}
idx = 0
}
fd, err := syscall.Open(filepath.Join(n.root().roots[idx], nm), int(flags), 0)
if err != nil {
return nil, 0, err.(syscall.Errno)
}
return nodefs.NewLoopbackFile(fd), 0, 0
}
var _ = (nodefs.Getattrer)((*unionFSNode)(nil))
func (n *unionFSNode) Getattr(ctx context.Context, fh nodefs.FileHandle, out *fuse.AttrOut) syscall.Errno {
var st syscall.Stat_t
_, idx := n.getBranch(&st)
if idx < 0 {
return syscall.ENOENT
}
out.FromStat(&st)
return 0
}
var _ = (nodefs.Lookuper)((*unionFSNode)(nil))
func (n *unionFSNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*nodefs.Inode, syscall.Errno) {
var st syscall.Stat_t
p := filepath.Join(n.Path(nil), name)
idx := n.root().getBranch(p, &st)
if idx >= 0 {
// XXX use idx in Ino?
ch := n.NewInode(ctx, &unionFSNode{}, nodefs.StableAttr{Mode: st.Mode, Ino: st.Ino})
out.FromStat(&st)
out.Mode |= 0111
return ch, 0
}
return nil, syscall.ENOENT
}
var _ = (nodefs.Unlinker)((*unionFSNode)(nil))
func (n *unionFSNode) Unlink(ctx context.Context, name string) syscall.Errno {
return n.root().delPath(filepath.Join(n.Path(nil), name))
}
var _ = (nodefs.Rmdirer)((*unionFSNode)(nil))
func (n *unionFSNode) Rmdir(ctx context.Context, name string) syscall.Errno {
return n.root().delPath(filepath.Join(n.Path(nil), name))
}
var _ = (nodefs.Symlinker)((*unionFSNode)(nil))
func (n *unionFSNode) Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (*nodefs.Inode, syscall.Errno) {
n.promote()
path := filepath.Join(n.root().roots[0], n.Path(nil), name)
err := syscall.Symlink(target, path)
if err != nil {
return nil, err.(syscall.Errno)
}
var st syscall.Stat_t
if err := syscall.Lstat(path, &st); err != nil {
return nil, err.(syscall.Errno)
}
out.FromStat(&st)
ch := n.NewInode(ctx, &unionFSNode{}, nodefs.StableAttr{
Mode: syscall.S_IFLNK,
Ino: st.Ino,
})
return ch, 0
}
var _ = (nodefs.Readlinker)((*unionFSNode)(nil))
func (n *unionFSNode) Readlink(ctx context.Context) ([]byte, syscall.Errno) {
nm, idx := n.getBranch(nil)
var buf [1024]byte
count, err := syscall.Readlink(filepath.Join(n.root().roots[idx], nm), buf[:])
if err != nil {
return nil, err.(syscall.Errno)
}
return buf[:count], 0
}
// getBranch returns the root where we can find the given file. It
// will check the deletion markers in roots[0].
func (n *unionFSNode) getBranch(st *syscall.Stat_t) (string, int) {
name := n.Path(nil)
return name, n.root().getBranch(name, st)
}
func (r *unionFSRoot) getBranch(name string, st *syscall.Stat_t) int {
if r.isDeleted(name) {
return -1
}
if st == nil {
st = &syscall.Stat_t{}
}
for i, root := range r.roots {
p := filepath.Join(root, name)
err := syscall.Lstat(p, st)
if err == nil {
return i
}
}
return -1
}
func (n *unionFSRoot) delPath(p string) syscall.Errno {
var st syscall.Stat_t
r := n.root()
idx := r.getBranch(p, &st)
if idx < 0 {
return 0
}
if idx == 0 {
err := syscall.Unlink(filepath.Join(r.roots[idx], p))
if err != nil {
return nodefs.ToErrno(err)
}
idx = r.getBranch(p, &st)
}
if idx > 0 {
return r.writeMarker(p)
}
return 0
}
func (n *unionFSNode) promote() syscall.Errno {
p := &n.Inode
r := n.root()
type tup struct {
*unionFSNode
name string
idx int
st syscall.Stat_t
}
var parents []tup
for p != nil && p != &r.Inode {
asUN := p.Operations().(*unionFSNode)
var st syscall.Stat_t
name, idx := asUN.getBranch(&st)
if idx == 0 {
break
}
if idx < 0 {
log.Println("promote called on nonexistent file")
return syscall.EIO
}
parents = append(parents, tup{asUN, name, idx, st})
_, p = p.Parent()
}
for i := len(parents) - 1; i >= 0; i-- {
t := parents[i]
path := t.Path(nil)
if t.IsDir() {
if err := syscall.Mkdir(filepath.Join(r.roots[0], path), t.st.Mode); err != nil {
return err.(syscall.Errno)
}
} else if t.Mode()&syscall.S_IFREG != 0 {
if errno := r.promoteRegularFile(path, t.idx, &t.st); errno != 0 {
return errno
}
} else {
log.Panicf("don't know how to handle mode %o", t.Mode())
}
var ts [2]syscall.Timespec
ts[0] = t.st.Atim
ts[1] = t.st.Mtim
// ignore error.
syscall.UtimesNano(path, ts[:])
}
return 0
}
func (r *unionFSRoot) promoteRegularFile(p string, idx int, st *syscall.Stat_t) syscall.Errno {
dest, err := syscall.Creat(filepath.Join(r.roots[0], p), st.Mode)
if err != nil {
return err.(syscall.Errno)
}
src, err := syscall.Open(filepath.Join(r.roots[idx], p), syscall.O_RDONLY, 0)
if err != nil {
syscall.Close(dest)
return err.(syscall.Errno)
}
var ret syscall.Errno
var buf [128 >> 10]byte
for {
n, err := syscall.Read(src, buf[:])
if n == 0 {
break
}
if err != nil {
ret = err.(syscall.Errno)
break
}
if _, err := syscall.Write(dest, buf[:n]); err != nil {
ret = err.(syscall.Errno)
break
}
}
syscall.Close(src)
if err := syscall.Close(dest); err != nil && ret == 0 {
ret = err.(syscall.Errno)
}
return ret
}
// 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 unionfs
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"syscall"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/nodefs"
"github.com/hanwen/go-fuse/posixtest"
)
type testCase struct {
dir string
mnt string
server *fuse.Server
rw string
ro string
root *unionFSRoot
}
func (tc *testCase) Clean() {
if tc.server != nil {
tc.server.Unmount()
tc.server = nil
}
os.RemoveAll(tc.dir)
}
func newTestCase(t *testing.T, populate bool) *testCase {
t.Helper()
dir := testutil.TempDir()
dirs := []string{"ro", "rw", "mnt"}
if populate {
dirs = append(dirs, "ro/dir")
}
for _, d := range dirs {
if err := os.Mkdir(filepath.Join(dir, d), 0755); err != nil {
t.Fatal("Mkdir", err)
}
}
opts := nodefs.Options{}
opts.Debug = testutil.VerboseTest()
tc := &testCase{
dir: dir,
mnt: dir + "/mnt",
rw: dir + "/rw",
ro: dir + "/ro",
}
tc.root = &unionFSRoot{
roots: []string{tc.rw, tc.ro},
}
server, err := nodefs.Mount(tc.mnt, tc.root, &opts)
if err != nil {
t.Fatal("Mount", err)
}
tc.server = server
if populate {
if err := ioutil.WriteFile(tc.ro+"/dir/ro-file", []byte("bla"), 0644); err != nil {
t.Fatal(err)
}
}
return tc
}
func TestBasic(t *testing.T) {
tc := newTestCase(t, true)
defer tc.Clean()
if fi, err := os.Lstat(tc.mnt + "/dir/ro-file"); err != nil {
t.Fatal(err)
} else if fi.Size() != 3 {
t.Errorf("got size %d, want 3", fi.Size())
}
}
func TestDelete(t *testing.T) {
tc := newTestCase(t, true)
defer tc.Clean()
if err := os.Remove(tc.mnt + "/dir/ro-file"); err != nil {
t.Fatal(err)
}
if _, err := os.Lstat(tc.ro + "/dir/ro-file"); err != nil {
t.Fatal(err)
}
c, err := ioutil.ReadFile(filepath.Join(tc.rw, delDir, filePathHash("dir/ro-file")))
if err != nil {
t.Fatal(err)
}
if got, want := string(c), "dir/ro-file"; got != want {
t.Errorf("got %q want %q", got, want)
}
}
func TestDeleteMarker(t *testing.T) {
tc := newTestCase(t, true)
defer tc.Clean()
path := "dir/ro-file"
tc.root.delPath(path)
var st syscall.Stat_t
if err := syscall.Lstat(filepath.Join(tc.mnt, path), &st); err != syscall.ENOENT {
t.Fatalf("Lstat before: %v", err)
}
if errno := tc.root.rmMarker(path); errno != 0 {
t.Fatalf("rmMarker: %v", errno)
}
if err := syscall.Lstat(filepath.Join(tc.mnt, path), &st); err != nil {
t.Fatalf("Lstat after: %v", err)
}
}
func TestCreate(t *testing.T) {
tc := newTestCase(t, true)
defer tc.Clean()
path := "dir/ro-file"
if err := syscall.Unlink(filepath.Join(tc.mnt, path)); err != nil {
t.Fatalf("Unlink: %v", err)
}
want := []byte{42}
if err := ioutil.WriteFile(filepath.Join(tc.mnt, path), want, 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
if got, err := ioutil.ReadFile(filepath.Join(tc.mnt, path)); err != nil {
t.Fatalf("WriteFile: %v", err)
} else if !bytes.Equal(got, want) {
t.Errorf("got %q, want %q", got, want)
}
}
func TestPromote(t *testing.T) {
tc := newTestCase(t, true)
defer tc.Clean()
path := "dir/ro-file"
mPath := filepath.Join(tc.mnt, path)
want := []byte{42}
if err := ioutil.WriteFile(mPath, want, 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
if got, err := ioutil.ReadFile(mPath); err != nil {
t.Fatalf("ReadFile: %v", err)
} else if !bytes.Equal(got, want) {
t.Errorf("got %q, want %q", got, want)
}
}
func TestDeleteRevert(t *testing.T) {
tc := newTestCase(t, true)
defer tc.Clean()
path := "dir/ro-file"
mPath := filepath.Join(tc.mnt, path)
if err := ioutil.WriteFile(mPath, []byte{42}, 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
var st syscall.Stat_t
if err := syscall.Lstat(mPath, &st); err != nil {
t.Fatalf("Lstat before: %v", err)
} else if st.Size != 1 {
t.Fatalf("Stat: want size 1, got %#v", st)
}
if err := syscall.Unlink(mPath); err != nil {
t.Fatalf("Unlink: %v", err)
}
if err := syscall.Lstat(mPath, &st); err != syscall.ENOENT {
t.Fatalf("Lstat after: got %v, want ENOENT", err)
}
}
func TestPosix(t *testing.T) {
cases := []string{
"SymlinkReadlink",
"FileBasic",
"TruncateFile",
"TruncateNoFile",
"FdLeak",
// "MkdirRmdir",
// "NlinkZero",
"ParallelFileOpen",
// "Link",
// "ReadDir",
}
for _, nm := range cases {
f := posixtest.All[nm]
t.Run(nm, func(t *testing.T) {
tc := newTestCase(t, false)
defer tc.Clean()
f(t, tc.mnt)
})
}
}
......@@ -40,7 +40,7 @@
//
// func (n *myNode) Lookup(ctx context.Context, name string, ... ) (*Inode, syscall.Errno) {
// child := myNode{}
// return n.NewInode(ctx, &myNode{}, NodeAttr{Mode: syscall.S_IFDIR}), 0
// return n.NewInode(ctx, &myNode{}, StableAttr{Mode: syscall.S_IFDIR}), 0
// }
//
// On mounting, the root InodeEmbedder is associated with root of the
......@@ -334,7 +334,7 @@ type Symlinker interface {
// reference for future reads/writes.
// Default is to return EROFS.
type Creater interface {
Create(ctx context.Context, name string, flags uint32, mode uint32) (node *Inode, fh FileHandle, fuseFlags uint32, errno syscall.Errno)
Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (node *Inode, fh FileHandle, fuseFlags uint32, errno syscall.Errno)
}
// Unlink should remove a child from this directory. If the
......
......@@ -26,7 +26,9 @@ type fileEntry struct {
nodeIndex int
// Directory
dirStream DirStream
dirStream DirStream
mu sync.Mutex
hasOverflow bool
overflow fuse.DirEntry
......@@ -49,7 +51,7 @@ type rawBridge struct {
}
// newInode creates creates new inode pointing to ops.
func (b *rawBridge) newInodeUnlocked(ops InodeEmbedder, id NodeAttr, persistent bool) *Inode {
func (b *rawBridge) newInodeUnlocked(ops InodeEmbedder, id StableAttr, persistent bool) *Inode {
b.mu.Lock()
defer b.mu.Unlock()
......@@ -82,7 +84,7 @@ func (b *rawBridge) newInodeUnlocked(ops InodeEmbedder, id NodeAttr, persistent
// file
//
// dir1.Lookup("file") and dir2.Lookup("file") are executed
// simultaneously. The matching NodeAttrs ensure that we return the
// simultaneously. The matching StableAttrs ensure that we return the
// same node.
old := b.nodes[id.Ino]
if old != nil {
......@@ -99,7 +101,7 @@ func (b *rawBridge) newInodeUnlocked(ops InodeEmbedder, id NodeAttr, persistent
return ops.embed()
}
func (b *rawBridge) newInode(ctx context.Context, ops InodeEmbedder, id NodeAttr, persistent bool) *Inode {
func (b *rawBridge) newInode(ctx context.Context, ops InodeEmbedder, id StableAttr, persistent bool) *Inode {
ch := b.newInodeUnlocked(ops, id, persistent)
if ch != ops.embed() {
return ch
......@@ -181,7 +183,7 @@ func NewNodeFS(root InodeEmbedder, opts *Options) fuse.RawFileSystem {
}
initInode(root.embed(), root,
NodeAttr{
StableAttr{
Ino: 1,
Mode: fuse.S_IFDIR,
},
......@@ -293,6 +295,8 @@ func (b *rawBridge) Mkdir(cancel <-chan struct{}, input *fuse.MkdirIn, name stri
var errno syscall.Errno
if mops, ok := parent.ops.(Mkdirer); ok {
child, errno = mops.Mkdir(&fuse.Context{Caller: input.Caller, Cancel: cancel}, name, input.Mode, out)
} else {
return fuse.ENOTSUP
}
if errno != 0 {
......@@ -337,7 +341,7 @@ func (b *rawBridge) Create(cancel <-chan struct{}, input *fuse.CreateIn, name st
var f FileHandle
var flags uint32
if mops, ok := parent.ops.(Creater); ok {
child, f, flags, errno = mops.Create(ctx, name, input.Flags, input.Mode)
child, f, flags, errno = mops.Create(ctx, name, input.Flags, input.Mode, &out.EntryOut)
} else {
return fuse.EROFS
}
......@@ -353,15 +357,8 @@ func (b *rawBridge) Create(cancel <-chan struct{}, input *fuse.CreateIn, name st
out.OpenFlags = flags
var temp fuse.AttrOut
b.getattr(ctx, child, f, &temp)
out.Attr = temp.Attr
out.AttrValid = temp.AttrValid
out.AttrValidNsec = temp.AttrValidNsec
child.setEntryOut(&out.EntryOut)
b.setEntryOutTimeout(&out.EntryOut)
return fuse.OK
}
......@@ -795,7 +792,7 @@ func (b *rawBridge) getStream(ctx context.Context, inode *Inode) (DirStream, sys
for k, ch := range inode.Children() {
r = append(r, fuse.DirEntry{Mode: ch.Mode(),
Name: k,
Ino: ch.NodeAttr().Ino})
Ino: ch.StableAttr().Ino})
}
return NewListDirStream(r), 0
}
......@@ -838,16 +835,20 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out
if errno := b.setStream(cancel, input, n, f); errno != 0 {
return errnoToStatus(errno)
}
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
for f.dirStream.HasNext() {
var e fuse.DirEntry
var errno syscall.Errno
f.mu.Lock()
if f.hasOverflow {
e = f.overflow
f.hasOverflow = false
} else {
e, errno = f.dirStream.Next()
}
f.mu.Unlock()
if errno != 0 {
return errnoToStatus(errno)
......@@ -855,8 +856,10 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out
entryOut := out.AddDirLookupEntry(e)
if entryOut == nil {
f.mu.Lock()
f.overflow = e
f.hasOverflow = true
f.mu.Unlock()
return fuse.OK
}
......
......@@ -78,13 +78,13 @@ func (r *keepCacheRoot) OnAdd(ctx context.Context) {
keepCache: true,
}
r.keep.setContent(0)
i.AddChild("keep", i.NewInode(ctx, r.keep, NodeAttr{}), true)
i.AddChild("keep", i.NewInode(ctx, r.keep, StableAttr{}), true)
r.nokeep = &keepCacheFile{
keepCache: false,
}
r.nokeep.setContent(0)
i.AddChild("nokeep", i.NewInode(ctx, r.nokeep, NodeAttr{}), true)
i.AddChild("nokeep", i.NewInode(ctx, r.nokeep, StableAttr{}), true)
}
// Test FOPEN_KEEP_CACHE. This is a little subtle: the automatic cache
......@@ -92,7 +92,7 @@ func (r *keepCacheRoot) OnAdd(ctx context.Context) {
// change content but no metadata.
func TestKeepCache(t *testing.T) {
root := &keepCacheRoot{}
mntDir, clean := testMount(t, root, nil)
mntDir, _, clean := testMount(t, root, nil)
defer clean()
c1, err := ioutil.ReadFile(mntDir + "/keep")
if err != nil {
......
......@@ -20,7 +20,7 @@ type dioRoot struct {
}
func (r *dioRoot) OnAdd(ctx context.Context) {
r.Inode.AddChild("file", r.Inode.NewInode(ctx, &dioFile{}, NodeAttr{}), false)
r.Inode.AddChild("file", r.Inode.NewInode(ctx, &dioFile{}, StableAttr{}), false)
}
// A file handle that pretends that every hole/data starts at
......@@ -54,7 +54,7 @@ func (f *dioFile) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFl
func TestDirectIO(t *testing.T) {
root := &dioRoot{}
mntDir, clean := testMount(t, root, nil)
mntDir, server, clean := testMount(t, root, nil)
defer clean()
f, err := os.Open(mntDir + "/file")
......@@ -74,6 +74,9 @@ func TestDirectIO(t *testing.T) {
t.Errorf("got %q want %q", got, want)
}
if !server.KernelSettings().SupportsVersion(7, 24) {
t.Skip("Kernel does not support lseek")
}
if n, err := syscall.Seek(int(f.Fd()), 512, _SEEK_DATA); err != nil {
t.Errorf("Seek: %v", err)
} else if n != 1024 {
......
......@@ -5,6 +5,7 @@
package nodefs
import (
"sync"
"syscall"
"unsafe"
......@@ -14,7 +15,10 @@ import (
type loopbackDirStream struct {
buf []byte
todo []byte
fd int
// Protects fd so we can guard against double close
mu sync.Mutex
fd int
}
// NewLoopbackDirStream open a directory for reading as a DirStream
......@@ -37,14 +41,23 @@ func NewLoopbackDirStream(name string) (DirStream, syscall.Errno) {
}
func (ds *loopbackDirStream) Close() {
syscall.Close(ds.fd)
ds.mu.Lock()
defer ds.mu.Unlock()
if ds.fd != -1 {
syscall.Close(ds.fd)
ds.fd = -1
}
}
func (ds *loopbackDirStream) HasNext() bool {
ds.mu.Lock()
defer ds.mu.Unlock()
return len(ds.todo) > 0
}
func (ds *loopbackDirStream) Next() (fuse.DirEntry, syscall.Errno) {
ds.mu.Lock()
defer ds.mu.Unlock()
de := (*syscall.Dirent)(unsafe.Pointer(&ds.todo[0]))
nameBytes := ds.todo[unsafe.Offsetof(syscall.Dirent{}.Name):de.Reclen]
......
......@@ -6,6 +6,8 @@ package nodefs
import (
"context"
"sync"
// "time"
"syscall"
......@@ -21,6 +23,7 @@ func NewLoopbackFile(fd int) FileHandle {
}
type loopbackFile struct {
mu sync.Mutex
fd int
}
......@@ -39,21 +42,33 @@ var _ = (FileSetattrer)((*loopbackFile)(nil))
var _ = (FileAllocater)((*loopbackFile)(nil))
func (f *loopbackFile) Read(ctx context.Context, buf []byte, off int64) (res fuse.ReadResult, errno syscall.Errno) {
f.mu.Lock()
defer f.mu.Unlock()
r := fuse.ReadResultFd(uintptr(f.fd), off, len(buf))
return r, OK
}
func (f *loopbackFile) Write(ctx context.Context, data []byte, off int64) (uint32, syscall.Errno) {
f.mu.Lock()
defer f.mu.Unlock()
n, err := syscall.Pwrite(f.fd, data, off)
return uint32(n), ToErrno(err)
}
func (f *loopbackFile) Release(ctx context.Context) syscall.Errno {
err := syscall.Close(f.fd)
return ToErrno(err)
f.mu.Lock()
defer f.mu.Unlock()
if f.fd != -1 {
err := syscall.Close(f.fd)
f.fd = -1
return ToErrno(err)
}
return syscall.EBADF
}
func (f *loopbackFile) Flush(ctx context.Context) syscall.Errno {
f.mu.Lock()
defer f.mu.Unlock()
// Since Flush() may be called for each dup'd fd, we don't
// want to really close the file, we just want to flush. This
// is achieved by closing a dup'd fd.
......@@ -67,6 +82,8 @@ func (f *loopbackFile) Flush(ctx context.Context) syscall.Errno {
}
func (f *loopbackFile) Fsync(ctx context.Context, flags uint32) (errno syscall.Errno) {
f.mu.Lock()
defer f.mu.Unlock()
r := ToErrno(syscall.Fsync(f.fd))
return r
......@@ -79,6 +96,8 @@ const (
)
func (f *loopbackFile) Getlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) (errno syscall.Errno) {
f.mu.Lock()
defer f.mu.Unlock()
flk := syscall.Flock_t{}
lk.ToFlockT(&flk)
errno = ToErrno(syscall.FcntlFlock(uintptr(f.fd), _OFD_GETLK, &flk))
......@@ -95,6 +114,8 @@ func (f *loopbackFile) Setlkw(ctx context.Context, owner uint64, lk *fuse.FileLo
}
func (f *loopbackFile) setLock(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32, blocking bool) (errno syscall.Errno) {
f.mu.Lock()
defer f.mu.Unlock()
if (flags & fuse.FUSE_LK_FLOCK) != 0 {
var op int
switch lk.Typ {
......@@ -133,6 +154,8 @@ func (f *loopbackFile) Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fus
}
func (f *loopbackFile) setAttr(ctx context.Context, in *fuse.SetAttrIn) syscall.Errno {
f.mu.Lock()
defer f.mu.Unlock()
var errno syscall.Errno
if mode, ok := in.GetMode(); ok {
errno = ToErrno(syscall.Fchmod(f.fd, mode))
......@@ -187,6 +210,8 @@ func (f *loopbackFile) setAttr(ctx context.Context, in *fuse.SetAttrIn) syscall.
}
func (f *loopbackFile) Getattr(ctx context.Context, a *fuse.AttrOut) syscall.Errno {
f.mu.Lock()
defer f.mu.Unlock()
st := syscall.Stat_t{}
err := syscall.Fstat(f.fd, &st)
if err != nil {
......@@ -198,6 +223,8 @@ func (f *loopbackFile) Getattr(ctx context.Context, a *fuse.AttrOut) syscall.Err
}
func (f *loopbackFile) Lseek(ctx context.Context, off uint64, whence uint32) (uint64, syscall.Errno) {
f.mu.Lock()
defer f.mu.Unlock()
n, err := unix.Seek(f.fd, int64(off), int(whence))
return uint64(n), ToErrno(err)
}
......@@ -13,6 +13,8 @@ import (
)
func (f *loopbackFile) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) syscall.Errno {
f.mu.Lock()
defer f.mu.Unlock()
err := syscall.Fallocate(f.fd, mode, int64(off), int64(sz))
if err != nil {
return ToErrno(err)
......
......@@ -22,8 +22,8 @@ type parentData struct {
parent *Inode
}
// NodeAttr holds immutable attributes of a object in the filesystem.
type NodeAttr struct {
// StableAttr holds immutable attributes of a object in the filesystem.
type StableAttr struct {
// Each Inode has a type, which does not change over the
// lifetime of the inode, for example fuse.S_IFDIR. The default (0)
// is interpreted as S_IFREG (regular file).
......@@ -43,8 +43,8 @@ type NodeAttr struct {
Gen uint64
}
// Reserved returns if the NodeAttr is using reserved Inode numbers.
func (i *NodeAttr) Reserved() bool {
// Reserved returns if the StableAttr is using reserved Inode numbers.
func (i *StableAttr) Reserved() bool {
return i.Ino == 1 || i.Ino == ^uint64(0)
}
......@@ -57,7 +57,7 @@ func (i *NodeAttr) Reserved() bool {
// copied. Inodes should be obtained by calling Inode.NewInode() or
// Inode.NewPersistentInode().
type Inode struct {
nodeAttr NodeAttr
nodeAttr StableAttr
ops InodeEmbedder
bridge *rawBridge
......@@ -96,6 +96,10 @@ type Inode struct {
parents map[parentData]struct{}
}
func (n *Inode) IsDir() bool {
return n.nodeAttr.Mode&syscall.S_IFDIR != 0
}
func (n *Inode) embed() *Inode {
return n
}
......@@ -104,7 +108,7 @@ func (n *Inode) EmbeddedInode() *Inode {
return n
}
func initInode(n *Inode, ops InodeEmbedder, attr NodeAttr, bridge *rawBridge, persistent bool) {
func initInode(n *Inode, ops InodeEmbedder, attr StableAttr, bridge *rawBridge, persistent bool) {
n.ops = ops
n.nodeAttr = attr
n.bridge = bridge
......@@ -122,8 +126,8 @@ func (n *Inode) setEntryOut(out *fuse.EntryOut) {
out.Mode = (out.Attr.Mode & 07777) | n.nodeAttr.Mode
}
// NodeAttr returns the (Ino, Gen) tuple for this node.
func (n *Inode) NodeAttr() NodeAttr {
// StableAttr returns the (Ino, Gen) tuple for this node.
func (n *Inode) StableAttr() StableAttr {
return n.nodeAttr
}
......@@ -307,7 +311,7 @@ func (iparent *Inode) setEntry(name string, ichild *Inode) {
// NewPersistentInode returns an Inode whose lifetime is not in
// control of the kernel.
func (n *Inode) NewPersistentInode(ctx context.Context, node InodeEmbedder, id NodeAttr) *Inode {
func (n *Inode) NewPersistentInode(ctx context.Context, node InodeEmbedder, id StableAttr) *Inode {
return n.newInode(ctx, node, id, true)
}
......@@ -323,11 +327,11 @@ func (n *Inode) ForgetPersistent() {
// id.Ino argument is used to implement hard-links. If it is given,
// and another node with the same ID is known, that will node will be
// returned, and the passed-in `node` is ignored.
func (n *Inode) NewInode(ctx context.Context, node InodeEmbedder, id NodeAttr) *Inode {
func (n *Inode) NewInode(ctx context.Context, node InodeEmbedder, id StableAttr) *Inode {
return n.newInode(ctx, node, id, false)
}
func (n *Inode) newInode(ctx context.Context, ops InodeEmbedder, id NodeAttr, persistent bool) *Inode {
func (n *Inode) newInode(ctx context.Context, ops InodeEmbedder, id StableAttr, persistent bool) *Inode {
return n.bridge.newInode(ctx, ops, id, persistent)
}
......
......@@ -25,7 +25,7 @@ func (r *interruptRoot) Lookup(ctx context.Context, name string, out *fuse.Entry
if name != "file" {
return nil, syscall.ENOENT
}
ch := r.Inode.NewInode(ctx, &r.child, NodeAttr{
ch := r.Inode.NewInode(ctx, &r.child, StableAttr{
Ino: 2,
Gen: 1})
......@@ -55,7 +55,7 @@ func TestInterrupt(t *testing.T) {
root := &interruptRoot{}
oneSec := time.Second
mntDir, clean := testMount(t, root, &Options{
mntDir, _, clean := testMount(t, root, &Options{
EntryTimeout: &oneSec,
AttrTimeout: &oneSec,
})
......
......@@ -41,7 +41,6 @@ var _ = (Mkdirer)((*loopbackNode)(nil))
var _ = (Mknoder)((*loopbackNode)(nil))
var _ = (Linker)((*loopbackNode)(nil))
var _ = (Symlinker)((*loopbackNode)(nil))
var _ = (Creater)((*loopbackNode)(nil))
var _ = (Unlinker)((*loopbackNode)(nil))
var _ = (Rmdirer)((*loopbackNode)(nil))
var _ = (Renamer)((*loopbackNode)(nil))
......@@ -163,7 +162,7 @@ func (n *loopbackNode) Rename(ctx context.Context, name string, newParent InodeE
return ToErrno(err)
}
func (r *loopbackRoot) idFromStat(st *syscall.Stat_t) NodeAttr {
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
......@@ -173,7 +172,7 @@ func (r *loopbackRoot) idFromStat(st *syscall.Stat_t) NodeAttr {
// the underlying filesystem
swapped := (uint64(st.Dev) << 32) | (uint64(st.Dev) >> 32)
swappedRootDev := (r.rootDev << 32) | (r.rootDev >> 32)
return NodeAttr{
return StableAttr{
Mode: uint32(st.Mode),
Gen: 1,
// This should work well for traditional backing FSes,
......@@ -182,7 +181,9 @@ func (r *loopbackRoot) idFromStat(st *syscall.Stat_t) NodeAttr {
}
}
func (n *loopbackNode) Create(ctx context.Context, name string, flags uint32, mode uint32) (inode *Inode, fh FileHandle, fuseFlags uint32, errno syscall.Errno) {
var _ = (Creater)((*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)
fd, err := syscall.Open(p, int(flags)|os.O_CREATE, mode)
......@@ -199,6 +200,8 @@ func (n *loopbackNode) Create(ctx context.Context, name string, flags uint32, mo
node := &loopbackNode{}
ch := n.NewInode(ctx, node, n.root().idFromStat(&st))
lf := NewLoopbackFile(fd)
out.FromStat(&st)
return ch, lf, 0, 0
}
......@@ -295,6 +298,79 @@ func (n *loopbackNode) Getattr(ctx context.Context, f FileHandle, out *fuse.Attr
return OK
}
var _ = (Setattrer)((*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
}
// NewLoopback returns a root node for a loopback file system whose
// root is at the given root.
func NewLoopbackRoot(root string) (InodeEmbedder, error) {
......
......@@ -52,7 +52,7 @@ func (n *loopbackNode) renameExchange(name string, newparent *loopbackNode, newN
// Double check that nodes didn't change from under us.
inode := &n.Inode
if inode.Root() != inode && inode.NodeAttr().Ino != n.root().idFromStat(&st).Ino {
if inode.Root() != inode && inode.StableAttr().Ino != n.root().idFromStat(&st).Ino {
return syscall.EBUSY
}
if err := syscall.Fstat(fd2, &st); err != nil {
......@@ -60,7 +60,7 @@ func (n *loopbackNode) renameExchange(name string, newparent *loopbackNode, newN
}
newinode := &newparent.Inode
if newinode.Root() != newinode && newinode.NodeAttr().Ino != n.root().idFromStat(&st).Ino {
if newinode.Root() != newinode && newinode.StableAttr().Ino != n.root().idFromStat(&st).Ino {
return syscall.EBUSY
}
......
......@@ -140,6 +140,10 @@ func TestCopyFileRange(t *testing.T) {
tc := newTestCase(t, true, true)
defer tc.Clean()
if !tc.server.KernelSettings().SupportsVersion(7, 28) {
t.Skip("need v7.28 for CopyFileRange")
}
tc.writeOrig("src", "01234567890123456789", 0644)
tc.writeOrig("dst", "abcdefghijabcdefghij", 0644)
......
......@@ -13,6 +13,7 @@ import (
// MemRegularFile is a filesystem node that holds a read-only data
// slice in memory.
type MemRegularFile struct {
Inode
Data []byte
......@@ -20,7 +21,6 @@ type MemRegularFile struct {
}
var _ = (Opener)((*MemRegularFile)(nil))
var _ = (Getattrer)((*MemRegularFile)(nil))
var _ = (Reader)((*MemRegularFile)(nil))
var _ = (Flusher)((*MemRegularFile)(nil))
......@@ -32,6 +32,8 @@ func (f *MemRegularFile) Open(ctx context.Context, flags uint32) (fh FileHandle,
return nil, fuse.FOPEN_KEEP_CACHE, OK
}
var _ = (Getattrer)((*MemRegularFile)(nil))
func (f *MemRegularFile) Getattr(ctx context.Context, fh FileHandle, out *fuse.AttrOut) syscall.Errno {
out.Attr = f.Attr
out.Attr.Size = uint64(len(f.Data))
......@@ -53,6 +55,7 @@ func (f *MemRegularFile) Read(ctx context.Context, fh FileHandle, dest []byte, o
// MemSymlink is an inode holding a symlink in memory.
type MemSymlink struct {
Inode
Attr fuse.Attr
Data []byte
}
......@@ -61,3 +64,10 @@ var _ = (Readlinker)((*MemSymlink)(nil))
func (l *MemSymlink) Readlink(ctx context.Context) ([]byte, syscall.Errno) {
return l.Data, OK
}
var _ = (Getattrer)((*MemSymlink)(nil))
func (l *MemSymlink) Getattr(ctx context.Context, fh FileHandle, out *fuse.AttrOut) syscall.Errno {
out.Attr = l.Attr
return OK
}
......@@ -17,7 +17,7 @@ import (
"github.com/hanwen/go-fuse/internal/testutil"
)
func testMount(t *testing.T, root InodeEmbedder, opts *Options) (string, func()) {
func testMount(t *testing.T, root InodeEmbedder, opts *Options) (string, *fuse.Server, func()) {
t.Helper()
mntDir := testutil.TempDir()
......@@ -32,7 +32,7 @@ func testMount(t *testing.T, root InodeEmbedder, opts *Options) (string, func())
if err != nil {
t.Fatal(err)
}
return mntDir, func() {
return mntDir, server, func() {
server.Unmount()
os.Remove(mntDir)
}
......@@ -41,7 +41,7 @@ func testMount(t *testing.T, root InodeEmbedder, opts *Options) (string, func())
func TestDataFile(t *testing.T) {
want := "hello"
root := &Inode{}
mntDir, clean := testMount(t, root, &Options{
mntDir, _, clean := testMount(t, root, &Options{
FirstAutomaticIno: 1,
OnAdd: func(ctx context.Context) {
n := root.EmbeddedInode()
......@@ -53,7 +53,7 @@ func TestDataFile(t *testing.T) {
Mode: 0464,
},
},
NodeAttr{})
StableAttr{})
n.AddChild("file", ch, false)
},
})
......@@ -94,7 +94,7 @@ func TestDataFileLargeRead(t *testing.T) {
data := make([]byte, 256*1024)
rand.Read(data[:])
mntDir, clean := testMount(t, root, &Options{
mntDir, _, clean := testMount(t, root, &Options{
FirstAutomaticIno: 1,
OnAdd: func(ctx context.Context) {
n := root.EmbeddedInode()
......@@ -106,7 +106,7 @@ func TestDataFileLargeRead(t *testing.T) {
Mode: 0464,
},
},
NodeAttr{})
StableAttr{})
n.AddChild("file", ch, false)
},
})
......@@ -130,14 +130,14 @@ func (s *SymlinkerRoot) Symlink(ctx context.Context, target, name string, out *f
Data: []byte(target),
}
ch := s.NewPersistentInode(ctx, l, NodeAttr{Mode: syscall.S_IFLNK})
ch := s.NewPersistentInode(ctx, l, StableAttr{Mode: syscall.S_IFLNK})
return ch, 0
}
func TestDataSymlink(t *testing.T) {
root := &SymlinkerRoot{}
mntDir, clean := testMount(t, root, nil)
mntDir, _, clean := testMount(t, root, nil)
defer clean()
if err := syscall.Symlink("target", mntDir+"/link"); err != nil {
......
......@@ -16,7 +16,7 @@ import (
func TestReadonlyCreate(t *testing.T) {
root := &Inode{}
mntDir, clean := testMount(t, root, nil)
mntDir, _, clean := testMount(t, root, nil)
defer clean()
_, err := syscall.Creat(mntDir+"/test", 0644)
......@@ -28,11 +28,11 @@ func TestReadonlyCreate(t *testing.T) {
func TestDefaultPermissions(t *testing.T) {
root := &Inode{}
mntDir, clean := testMount(t, root, &Options{
mntDir, _, clean := testMount(t, root, &Options{
DefaultPermissions: true,
OnAdd: func(ctx context.Context) {
dir := root.NewPersistentInode(ctx, &Inode{}, NodeAttr{Mode: syscall.S_IFDIR})
file := root.NewPersistentInode(ctx, &Inode{}, NodeAttr{Mode: syscall.S_IFREG})
dir := root.NewPersistentInode(ctx, &Inode{}, StableAttr{Mode: syscall.S_IFDIR})
file := root.NewPersistentInode(ctx, &Inode{}, StableAttr{Mode: syscall.S_IFREG})
root.AddChild("dir", dir, false)
root.AddChild("file", file, false)
......
......@@ -5,13 +5,11 @@
package nodefs
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"runtime"
"sync"
"syscall"
"testing"
......@@ -19,6 +17,7 @@ import (
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/posixtest"
)
type testCase struct {
......@@ -132,72 +131,14 @@ func TestFileBasic(t *testing.T) {
tc := newTestCase(t, true, true)
defer tc.Clean()
content := []byte("hello world")
fn := tc.mntDir + "/file"
if err := ioutil.WriteFile(fn, content, 0755); err != nil {
t.Fatalf("WriteFile: %v", err)
}
if got, err := ioutil.ReadFile(fn); err != nil {
t.Fatalf("ReadFile: %v", err)
} else if bytes.Compare(got, content) != 0 {
t.Errorf("ReadFile: got %q, want %q", got, content)
}
f, err := os.Open(fn)
if err != nil {
t.Fatalf("Open: %v", err)
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
t.Fatalf("Fstat: %v", err)
} else if int(fi.Size()) != len(content) {
t.Errorf("got size %d want 5", fi.Size())
}
stat := fuse.ToStatT(fi)
if got, want := uint32(stat.Mode), uint32(fuse.S_IFREG|0755); got != want {
t.Errorf("Fstat: got mode %o, want %o", got, want)
}
if err := f.Close(); err != nil {
t.Errorf("Close: %v", err)
}
posixtest.FileBasic(t, tc.mntDir)
}
func TestFileTruncate(t *testing.T) {
tc := newTestCase(t, true, true)
defer tc.Clean()
content := []byte("hello world")
tc.writeOrig("file", string(content), 0755)
f, err := os.OpenFile(tc.mntDir+"/file", os.O_RDWR, 0644)
if err != nil {
t.Fatalf("Open: %v", err)
}
defer f.Close()
const trunc = 5
if err := f.Truncate(5); err != nil {
t.Errorf("Truncate: %v", err)
}
if err := f.Close(); err != nil {
t.Errorf("Close: %v", err)
}
if got, err := ioutil.ReadFile(tc.origDir + "/file"); err != nil {
t.Fatalf("ReadFile: %v", err)
} else if want := content[:trunc]; bytes.Compare(got, want) != 0 {
t.Errorf("got %q, want %q", got, want)
}
posixtest.TruncateFile(t, tc.mntDir)
}
func TestFileFdLeak(t *testing.T) {
......@@ -208,24 +149,7 @@ func TestFileFdLeak(t *testing.T) {
}
}()
tc.writeOrig("file", "hello world", 0755)
for i := 0; i < 100; i++ {
if _, err := ioutil.ReadFile(tc.mntDir + "/file"); err != nil {
t.Fatalf("ReadFile: %v", err)
}
}
if runtime.GOOS == "linux" {
infos, err := ioutil.ReadDir("/proc/self/fd")
if err != nil {
t.Errorf("ReadDir %v", err)
}
if len(infos) > 15 {
t.Errorf("found %d open file descriptors for 100x ReadFile", len(infos))
}
}
posixtest.FdLeak(t, tc.mntDir)
tc.Clean()
bridge := tc.rawFS.(*rawBridge)
......@@ -240,54 +164,13 @@ func TestMkdir(t *testing.T) {
tc := newTestCase(t, true, true)
defer tc.Clean()
if err := os.Mkdir(tc.mntDir+"/dir", 0755); err != nil {
t.Fatalf("Mkdir: %v", err)
}
if fi, err := os.Lstat(tc.mntDir + "/dir"); err != nil {
t.Fatalf("Lstat %v", err)
} else if !fi.IsDir() {
t.Fatalf("is not a directory")
}
if err := os.Remove(tc.mntDir + "/dir"); err != nil {
t.Fatalf("Remove: %v", err)
}
posixtest.MkdirRmdir(t, tc.mntDir)
}
func testRenameOverwrite(t *testing.T, destExists bool) {
tc := newTestCase(t, true, true)
defer tc.Clean()
if err := os.Mkdir(tc.origDir+"/dir", 0755); err != nil {
t.Fatalf("Mkdir: %v", err)
}
tc.writeOrig("file", "hello", 0644)
if destExists {
tc.writeOrig("/dir/renamed", "xx", 0644)
}
st := syscall.Stat_t{}
if err := syscall.Lstat(tc.mntDir+"/file", &st); err != nil {
t.Fatalf("Lstat before: %v", err)
}
beforeIno := st.Ino
if err := os.Rename(tc.mntDir+"/file", tc.mntDir+"/dir/renamed"); err != nil {
t.Errorf("Rename: %v", err)
}
if fi, err := os.Lstat(tc.mntDir + "/file"); err == nil {
t.Fatalf("Lstat old: %v", fi)
}
if err := syscall.Lstat(tc.mntDir+"/dir/renamed", &st); err != nil {
t.Fatalf("Lstat after: %v", err)
}
if got := st.Ino; got != beforeIno {
t.Errorf("got ino %d, want %d", got, beforeIno)
}
posixtest.RenameOverwrite(t, tc.mntDir, destExists)
}
func TestRenameDestExist(t *testing.T) {
......@@ -303,114 +186,28 @@ func TestNlinkZero(t *testing.T) {
tc := newTestCase(t, true, true)
defer tc.Clean()
src := tc.mntDir + "/src"
dst := tc.mntDir + "/dst"
if err := ioutil.WriteFile(src, []byte("source"), 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
if err := ioutil.WriteFile(dst, []byte("dst"), 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
f, err := syscall.Open(dst, 0, 0)
if err != nil {
t.Fatalf("Open: %v", err)
}
defer syscall.Close(f)
var st syscall.Stat_t
if err := syscall.Fstat(f, &st); err != nil {
t.Errorf("Fstat before: %v", err)
} else if st.Nlink != 1 {
t.Errorf("Nlink of file: got %d, want 1", st.Nlink)
}
if err := os.Rename(src, dst); err != nil {
t.Fatalf("Rename: %v", err)
}
if err := syscall.Fstat(f, &st); err != nil {
t.Errorf("Fstat after: %v", err)
} else if st.Nlink != 0 {
t.Errorf("Nlink of overwritten file: got %d, want 0", st.Nlink)
}
posixtest.NlinkZero(t, tc.mntDir)
}
func TestParallelFileOpen(t *testing.T) {
tc := newTestCase(t, true, true)
defer tc.Clean()
fn := tc.mntDir + "/file"
if err := ioutil.WriteFile(fn, []byte("content"), 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
var wg sync.WaitGroup
one := func(b byte) {
f, err := os.OpenFile(fn, os.O_RDWR, 0644)
if err != nil {
t.Fatalf("OpenFile: %v", err)
}
var buf [10]byte
f.Read(buf[:])
buf[0] = b
f.WriteAt(buf[0:1], 2)
f.Close()
wg.Done()
}
for i := 0; i < 10; i++ {
wg.Add(1)
go one(byte(i))
}
wg.Wait()
posixtest.ParallelFileOpen(t, tc.mntDir)
}
func TestSymlink(t *testing.T) {
tc := newTestCase(t, true, true)
defer tc.Clean()
fn := tc.mntDir + "/link"
target := "target"
if err := os.Symlink(target, fn); err != nil {
t.Fatalf("Symlink: %v", err)
}
if got, err := os.Readlink(fn); err != nil {
t.Fatalf("Readlink: %v", err)
} else if got != target {
t.Errorf("Readlink: got %q, want %q", got, target)
}
posixtest.SymlinkReadlink(t, tc.mntDir)
}
func TestLink(t *testing.T) {
tc := newTestCase(t, true, true)
defer tc.Clean()
link := tc.mntDir + "/link"
target := tc.mntDir + "/target"
if err := ioutil.WriteFile(target, []byte("hello"), 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
st := syscall.Stat_t{}
if err := syscall.Lstat(target, &st); err != nil {
t.Fatalf("Lstat before: %v", err)
}
beforeIno := st.Ino
if err := os.Link(target, link); err != nil {
t.Errorf("Link: %v", err)
}
if err := syscall.Lstat(link, &st); err != nil {
t.Fatalf("Lstat after: %v", err)
}
if st.Ino != beforeIno {
t.Errorf("Lstat after: got %d, want %d", st.Ino, beforeIno)
}
posixtest.Link(t, tc.mntDir)
}
func TestNotifyEntry(t *testing.T) {
......@@ -450,38 +247,40 @@ func TestReadDir(t *testing.T) {
tc := newTestCase(t, true, true)
defer tc.Clean()
f, err := os.Open(tc.mntDir)
if err != nil {
t.Fatalf("Open: %v", err)
}
defer f.Close()
// add entries after opening the directory
want := map[string]bool{}
for i := 0; i < 110; i++ {
// 40 bytes of filename, so 110 entries overflows a
// 4096 page.
nm := fmt.Sprintf("file%036x", i)
want[nm] = true
tc.writeOrig(nm, "hello", 0644)
}
posixtest.ReadDir(t, tc.mntDir)
}
names, err := f.Readdirnames(-1)
if err != nil {
t.Fatalf("ReadDir: %v", err)
}
got := map[string]bool{}
for _, e := range names {
got[e] = true
}
if len(got) != len(want) {
t.Errorf("got %d entries, want %d", len(got), len(want))
}
for k := range got {
if !want[k] {
t.Errorf("got unknown name %q", k)
func TestReadDirStress(t *testing.T) {
tc := newTestCase(t, true, true)
defer tc.Clean()
// (ab)use posixtest.ReadDir to create 110 test files
posixtest.ReadDir(t, tc.mntDir)
var wg sync.WaitGroup
stress := func(gr int) {
defer wg.Done()
for i := 1; i < 100; i++ {
f, err := os.Open(tc.mntDir)
if err != nil {
t.Error(err)
return
}
_, err = f.Readdirnames(-1)
if err != nil {
t.Errorf("goroutine %d iteration %d: %v", gr, i, err)
f.Close()
return
}
f.Close()
}
}
n := 3
for i := 1; i <= n; i++ {
wg.Add(1)
go stress(i)
}
wg.Wait()
}
// This test is racy. If an external process consumes space while this
......@@ -589,3 +388,10 @@ func TestMknod(t *testing.T) {
})
}
}
func TestTruncate(t *testing.T) {
tc := newTestCase(t, false, false)
defer tc.Clean()
posixtest.TruncateNoFile(t, tc.mntDir)
}
......@@ -62,7 +62,7 @@ func TestZipFS(t *testing.T) {
}
root := &zipRoot{zr: r}
mntDir, clean := testMount(t, root, nil)
mntDir, _, clean := testMount(t, root, nil)
defer clean()
for k, v := range testData {
......@@ -104,10 +104,10 @@ func TestZipFSOnAdd(t *testing.T) {
zr := &zipRoot{zr: r}
root := &Inode{}
mnt, clean := testMount(t, root, &Options{
mnt, _, clean := testMount(t, root, &Options{
OnAdd: func(ctx context.Context) {
root.AddChild("sub",
root.NewPersistentInode(ctx, zr, NodeAttr{Mode: syscall.S_IFDIR}), false)
root.NewPersistentInode(ctx, zr, StableAttr{Mode: syscall.S_IFDIR}), false)
},
})
defer clean()
......@@ -197,13 +197,13 @@ func (zr *zipRoot) OnAdd(ctx context.Context) {
ch := p.GetChild(component)
if ch == nil {
ch = p.NewPersistentInode(ctx, &Inode{},
NodeAttr{Mode: fuse.S_IFDIR})
StableAttr{Mode: fuse.S_IFDIR})
p.AddChild(component, ch, true)
}
p = ch
}
ch := p.NewPersistentInode(ctx, &zipFile{file: f}, NodeAttr{})
ch := p.NewPersistentInode(ctx, &zipFile{file: f}, StableAttr{})
p.AddChild(base, ch, true)
}
}
......@@ -235,7 +235,7 @@ func ExampleInode_NewPersistentInode() {
if ch == nil {
// Create a directory
ch = p.NewPersistentInode(ctx, &Inode{},
NodeAttr{Mode: syscall.S_IFDIR})
StableAttr{Mode: syscall.S_IFDIR})
// Add it
p.AddChild(component, ch, true)
}
......@@ -246,7 +246,7 @@ func ExampleInode_NewPersistentInode() {
// Create the file
child := p.NewPersistentInode(ctx, &MemRegularFile{
Data: []byte(content),
}, NodeAttr{})
}, StableAttr{})
// And add it
p.AddChild(base, child, true)
......
// 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 posixtest file systems for generic posix conformance.
package posixtest
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"sync"
"syscall"
"testing"
"github.com/hanwen/go-fuse/fuse"
)
var All = map[string]func(*testing.T, string){
"SymlinkReadlink": SymlinkReadlink,
"FileBasic": FileBasic,
"TruncateFile": TruncateFile,
"TruncateNoFile": TruncateNoFile,
"FdLeak": FdLeak,
"MkdirRmdir": MkdirRmdir,
"NlinkZero": NlinkZero,
"ParallelFileOpen": ParallelFileOpen,
"Link": Link,
"RenameOverwriteDestNoExist": RenameOverwriteDestNoExist,
"RenameOverwriteDestExist": RenameOverwriteDestExist,
"ReadDir": ReadDir,
}
// SymlinkReadlink tests basic symlink functionality
func SymlinkReadlink(t *testing.T, mnt string) {
err := os.Symlink("/foobar", mnt+"/link")
if err != nil {
t.Fatalf("Symlink: %v", err)
}
val, err := os.Readlink(mnt + "/link")
if err != nil {
t.Fatalf("Readlink: %v", err)
}
if val != "/foobar" {
t.Errorf("symlink mismatch: %v", val)
}
}
func FileBasic(t *testing.T, mnt string) {
content := []byte("hello world")
fn := mnt + "/file"
if err := ioutil.WriteFile(fn, content, 0755); err != nil {
t.Fatalf("WriteFile: %v", err)
}
if got, err := ioutil.ReadFile(fn); err != nil {
t.Fatalf("ReadFile: %v", err)
} else if bytes.Compare(got, content) != 0 {
t.Errorf("ReadFile: got %q, want %q", got, content)
}
f, err := os.Open(fn)
if err != nil {
t.Fatalf("Open: %v", err)
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
t.Fatalf("Fstat: %v", err)
} else if int(fi.Size()) != len(content) {
t.Errorf("got size %d want 5", fi.Size())
}
stat := fuse.ToStatT(fi)
if got, want := uint32(stat.Mode), uint32(fuse.S_IFREG|0755); got != want {
t.Errorf("Fstat: got mode %o, want %o", got, want)
}
if err := f.Close(); err != nil {
t.Errorf("Close: %v", err)
}
}
func TruncateFile(t *testing.T, mnt string) {
content := []byte("hello world")
fn := mnt + "/file"
if err := ioutil.WriteFile(fn, content, 0755); err != nil {
t.Fatalf("WriteFile: %v", err)
}
f, err := os.OpenFile(fn, os.O_RDWR, 0644)
if err != nil {
t.Fatalf("Open: %v", err)
}
defer f.Close()
const trunc = 5
if err := f.Truncate(5); err != nil {
t.Errorf("Truncate: %v", err)
}
if err := f.Close(); err != nil {
t.Errorf("Close: %v", err)
}
if got, err := ioutil.ReadFile(fn); err != nil {
t.Fatalf("ReadFile: %v", err)
} else if want := content[:trunc]; bytes.Compare(got, want) != 0 {
t.Errorf("got %q, want %q", got, want)
}
}
func TruncateNoFile(t *testing.T, mnt string) {
fn := mnt + "/file"
if err := ioutil.WriteFile(fn, []byte("hello"), 0644); err != nil {
t.Errorf("WriteFile: %v", err)
}
if err := syscall.Truncate(fn, 1); err != nil {
t.Fatalf("Truncate: %v", err)
}
var st syscall.Stat_t
if err := syscall.Lstat(fn, &st); err != nil {
t.Fatalf("Lstat: %v", err)
}
if st.Size != 1 {
t.Errorf("got size %d, want 1", st.Size)
}
}
func FdLeak(t *testing.T, mnt string) {
fn := mnt + "/file"
if err := ioutil.WriteFile(fn, []byte("hello world"), 0755); err != nil {
t.Fatalf("WriteFile: %v", err)
}
for i := 0; i < 100; i++ {
if _, err := ioutil.ReadFile(fn); err != nil {
t.Fatalf("ReadFile: %v", err)
}
}
if runtime.GOOS == "linux" {
infos, err := ioutil.ReadDir("/proc/self/fd")
if err != nil {
t.Errorf("ReadDir %v", err)
}
if len(infos) > 15 {
t.Errorf("found %d open file descriptors for 100x ReadFile", len(infos))
}
}
}
func MkdirRmdir(t *testing.T, mnt string) {
fn := mnt + "/dir"
if err := os.Mkdir(fn, 0755); err != nil {
t.Fatalf("Mkdir: %v", err)
}
if fi, err := os.Lstat(fn); err != nil {
t.Fatalf("Lstat %v", err)
} else if !fi.IsDir() {
t.Fatalf("is not a directory")
}
if err := os.Remove(fn); err != nil {
t.Fatalf("Remove: %v", err)
}
}
func NlinkZero(t *testing.T, mnt string) {
src := mnt + "/src"
dst := mnt + "/dst"
if err := ioutil.WriteFile(src, []byte("source"), 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
if err := ioutil.WriteFile(dst, []byte("dst"), 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
f, err := syscall.Open(dst, 0, 0)
if err != nil {
t.Fatalf("Open: %v", err)
}
defer syscall.Close(f)
var st syscall.Stat_t
if err := syscall.Fstat(f, &st); err != nil {
t.Errorf("Fstat before: %v", err)
} else if st.Nlink != 1 {
t.Errorf("Nlink of file: got %d, want 1", st.Nlink)
}
if err := os.Rename(src, dst); err != nil {
t.Fatalf("Rename: %v", err)
}
if err := syscall.Fstat(f, &st); err != nil {
t.Errorf("Fstat after: %v", err)
} else if st.Nlink != 0 {
t.Errorf("Nlink of overwritten file: got %d, want 0", st.Nlink)
}
}
func ParallelFileOpen(t *testing.T, mnt string) {
fn := mnt + "/file"
if err := ioutil.WriteFile(fn, []byte("content"), 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
var wg sync.WaitGroup
one := func(b byte) {
f, err := os.OpenFile(fn, os.O_RDWR, 0644)
if err != nil {
t.Fatalf("OpenFile: %v", err)
}
var buf [10]byte
f.Read(buf[:])
buf[0] = b
f.WriteAt(buf[0:1], 2)
f.Close()
wg.Done()
}
for i := 0; i < 10; i++ {
wg.Add(1)
go one(byte(i))
}
wg.Wait()
}
func Link(t *testing.T, mnt string) {
link := mnt + "/link"
target := mnt + "/target"
if err := ioutil.WriteFile(target, []byte("hello"), 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
st := syscall.Stat_t{}
if err := syscall.Lstat(target, &st); err != nil {
t.Fatalf("Lstat before: %v", err)
}
beforeIno := st.Ino
if err := os.Link(target, link); err != nil {
t.Errorf("Link: %v", err)
}
if err := syscall.Lstat(link, &st); err != nil {
t.Fatalf("Lstat after: %v", err)
}
if st.Ino != beforeIno {
t.Errorf("Lstat after: got %d, want %d", st.Ino, beforeIno)
}
}
func RenameOverwriteDestNoExist(t *testing.T, mnt string) {
RenameOverwrite(t, mnt, false)
}
func RenameOverwriteDestExist(t *testing.T, mnt string) {
RenameOverwrite(t, mnt, true)
}
func RenameOverwrite(t *testing.T, mnt string, destExists bool) {
if err := os.Mkdir(mnt+"/dir", 0755); err != nil {
t.Fatalf("Mkdir: %v", err)
}
if err := ioutil.WriteFile(mnt+"/file", []byte("hello"), 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
if destExists {
if err := ioutil.WriteFile(mnt+"/dir/renamed", []byte("xx"), 0644); err != nil {
t.Fatalf("WriteFile dest: %v", err)
}
}
st := syscall.Stat_t{}
if err := syscall.Lstat(mnt+"/file", &st); err != nil {
t.Fatalf("Lstat before: %v", err)
}
beforeIno := st.Ino
if err := os.Rename(mnt+"/file", mnt+"/dir/renamed"); err != nil {
t.Errorf("Rename: %v", err)
}
if fi, err := os.Lstat(mnt + "/file"); err == nil {
t.Fatalf("Lstat old: %v", fi)
}
if err := syscall.Lstat(mnt+"/dir/renamed", &st); err != nil {
t.Fatalf("Lstat after: %v", err)
}
if got := st.Ino; got != beforeIno {
t.Errorf("got ino %d, want %d", got, beforeIno)
}
}
func ReadDir(t *testing.T, mnt string) {
f, err := os.Open(mnt)
if err != nil {
t.Fatalf("Open: %v", err)
}
defer f.Close()
// add entries after opening the directory
want := map[string]bool{}
for i := 0; i < 110; i++ {
// 40 bytes of filename, so 110 entries overflows a
// 4096 page.
nm := fmt.Sprintf("file%036x", i)
want[nm] = true
if err := ioutil.WriteFile(filepath.Join(mnt, nm), []byte("hello"), 0644); err != nil {
t.Fatalf("WriteFile %q: %v", nm, err)
}
}
names, err := f.Readdirnames(-1)
if err != nil {
t.Fatalf("ReadDir: %v", err)
}
got := map[string]bool{}
for _, e := range names {
got[e] = true
}
if len(got) != len(want) {
t.Errorf("got %d entries, want %d", len(got), len(want))
}
for k := range got {
if !want[k] {
t.Errorf("got unknown name %q", k)
}
}
}
......@@ -22,6 +22,7 @@ import (
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/posixtest"
)
func TestFilePathHash(t *testing.T) {
......@@ -203,19 +204,7 @@ func TestUnionFsSymlink(t *testing.T) {
wd, clean := setupUfs(t)
defer clean()
err := os.Symlink("/foobar", wd+"/mnt/link")
if err != nil {
t.Fatalf("Symlink: %v", err)
}
val, err := os.Readlink(wd + "/mnt/link")
if err != nil {
t.Fatalf("Readlink: %v", err)
}
if val != "/foobar" {
t.Errorf("symlink mismatch: %v", val)
}
posixtest.SymlinkReadlink(t, wd+"/mnt")
}
func TestUnionFsSymlinkPromote(t *testing.T) {
......@@ -447,16 +436,7 @@ func TestUnionFsMkdir(t *testing.T) {
wd, clean := setupUfs(t)
defer clean()
dirname := wd + "/mnt/subdir"
err := os.Mkdir(dirname, 0755)
if err != nil {
t.Fatalf("Mkdir: %v", err)
}
err = os.Remove(dirname)
if err != nil {
t.Fatalf("Remove: %v", err)
}
posixtest.MkdirRmdir(t, wd+"/mnt")
}
func TestUnionFsMkdirPromote(t *testing.T) {
......
......@@ -29,7 +29,7 @@ type MultiZipFs struct {
}
func (fs *MultiZipFs) OnAdd(ctx context.Context) {
n := fs.NewPersistentInode(ctx, &configRoot{}, nodefs.NodeAttr{Mode: syscall.S_IFDIR})
n := fs.NewPersistentInode(ctx, &configRoot{}, nodefs.StableAttr{Mode: syscall.S_IFDIR})
fs.AddChild("config", n, false)
}
......@@ -72,12 +72,12 @@ func (r *configRoot) Symlink(ctx context.Context, target string, base string, ou
}
_, parent := r.Parent()
ch := r.NewPersistentInode(ctx, root, nodefs.NodeAttr{Mode: syscall.S_IFDIR})
ch := r.NewPersistentInode(ctx, root, nodefs.StableAttr{Mode: syscall.S_IFDIR})
parent.AddChild(base, ch, false)
link := r.NewPersistentInode(ctx, &nodefs.MemSymlink{
Data: []byte(target),
}, nodefs.NodeAttr{Mode: syscall.S_IFLNK})
}, nodefs.StableAttr{Mode: syscall.S_IFLNK})
r.AddChild(base, link, false)
return link, 0
}
......@@ -52,7 +52,6 @@ func (r *tarRoot) OnAdd(ctx context.Context) {
// XXX handle error
break
}
if hdr.Typeflag == 'L' {
buf := bytes.NewBuffer(make([]byte, 0, hdr.Size))
io.Copy(buf, tr)
......@@ -66,10 +65,6 @@ func (r *tarRoot) OnAdd(ctx context.Context) {
longName = nil
}
if strings.HasSuffix(hdr.Name, "/") {
continue
}
buf := bytes.NewBuffer(make([]byte, 0, hdr.Size))
io.Copy(buf, tr)
dir, base := filepath.Split(filepath.Clean(hdr.Name))
......@@ -83,22 +78,49 @@ func (r *tarRoot) OnAdd(ctx context.Context) {
if ch == nil {
ch = p.NewPersistentInode(ctx,
&nodefs.Inode{},
nodefs.NodeAttr{Mode: syscall.S_IFDIR})
nodefs.StableAttr{Mode: syscall.S_IFDIR})
p.AddChild(comp, ch, false)
}
p = ch
}
if hdr.Typeflag == tar.TypeSymlink {
p.AddChild(base, r.NewPersistentInode(ctx, &nodefs.MemSymlink{
var attr fuse.Attr
HeaderToFileInfo(&attr, hdr)
switch hdr.Typeflag {
case tar.TypeSymlink:
l := &nodefs.MemSymlink{
Data: []byte(hdr.Linkname),
}, nodefs.NodeAttr{Mode: syscall.S_IFLNK}), false)
} else {
}
l.Attr = attr
p.AddChild(base, r.NewPersistentInode(ctx, l, nodefs.StableAttr{Mode: syscall.S_IFLNK}), false)
case tar.TypeLink:
log.Println("don't know how to handle Typelink")
case tar.TypeChar:
rf := &nodefs.MemRegularFile{}
rf.Attr = attr
p.AddChild(base, r.NewPersistentInode(ctx, rf, nodefs.StableAttr{Mode: syscall.S_IFCHR}), false)
case tar.TypeBlock:
rf := &nodefs.MemRegularFile{}
rf.Attr = attr
p.AddChild(base, r.NewPersistentInode(ctx, rf, nodefs.StableAttr{Mode: syscall.S_IFBLK}), false)
case tar.TypeDir:
rf := &nodefs.MemRegularFile{}
rf.Attr = attr
p.AddChild(base, r.NewPersistentInode(ctx, rf, nodefs.StableAttr{Mode: syscall.S_IFDIR}), false)
case tar.TypeFifo:
rf := &nodefs.MemRegularFile{}
rf.Attr = attr
p.AddChild(base, r.NewPersistentInode(ctx, rf, nodefs.StableAttr{Mode: syscall.S_IFIFO}), false)
case tar.TypeReg, tar.TypeRegA:
df := &nodefs.MemRegularFile{
Data: buf.Bytes(),
}
HeaderToFileInfo(&df.Attr, hdr)
p.AddChild(base, r.NewPersistentInode(ctx, df, nodefs.NodeAttr{}), false)
df.Attr = attr
p.AddChild(base, r.NewPersistentInode(ctx, df, nodefs.StableAttr{}), false)
default:
log.Printf("entry %q: unsupported type '%c'", hdr.Name, hdr.Typeflag)
}
}
}
......
......@@ -11,6 +11,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"syscall"
"testing"
"time"
......@@ -20,6 +21,7 @@ import (
)
var tarContents = map[string]string{
"emptydir/": "",
"file.txt": "content",
"dir/subfile.txt": "other content",
}
......@@ -48,14 +50,16 @@ func TestTar(t *testing.T) {
}
isLink := filepath.Base(k) == "link"
isDir := strings.HasSuffix(k, "/")
if isLink {
h.Typeflag = tar.TypeSymlink
h.Linkname = v
} else if isDir {
h.Typeflag = tar.TypeDir
}
w.WriteHeader(h)
if !isLink {
if !isLink && !isDir {
w.Write([]byte(v))
}
}
......@@ -89,9 +93,15 @@ func TestTar(t *testing.T) {
if got != want {
t.Errorf("Readlink: got %q want %q", got, want)
}
} else if strings.HasSuffix(k, "/") {
if got, want := st.Mode, uint32(syscall.S_IFDIR|0464); got != want {
t.Errorf("dir %q: got mode %o, want %o", k, got, want)
}
} else {
if got, want := st.Mode, uint32(syscall.S_IFREG|0464); got != want {
t.Errorf("got mode %o, want %o", got, want)
t.Errorf("entry %q, got mode %o, want %o", k, got, want)
}
c, err := ioutil.ReadFile(p)
......
......@@ -66,13 +66,13 @@ func (zr *zipRoot) OnAdd(ctx context.Context) {
ch := p.GetChild(component)
if ch == nil {
ch = p.NewPersistentInode(ctx, &nodefs.Inode{},
nodefs.NodeAttr{Mode: fuse.S_IFDIR})
nodefs.StableAttr{Mode: fuse.S_IFDIR})
p.AddChild(component, ch, true)
}
p = ch
}
ch := p.NewPersistentInode(ctx, &zipFile{file: f}, nodefs.NodeAttr{})
ch := p.NewPersistentInode(ctx, &zipFile{file: f}, nodefs.StableAttr{})
p.AddChild(base, ch, true)
}
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment