Files
go-fuse/fuse/test/fsetattr_test.go

260 lines
5.8 KiB
Go

// Copyright 2016 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 test
import (
"os"
"syscall"
"testing"
"time"
"github.com/hanzoai/go-fuse/v2/fuse"
"github.com/hanzoai/go-fuse/v2/fuse/nodefs"
"github.com/hanzoai/go-fuse/v2/fuse/pathfs"
"github.com/hanzoai/go-fuse/v2/internal/testutil"
)
type MutableDataFile struct {
nodefs.File
data []byte
fuse.Attr
GetAttrCalled bool
FsyncCalled bool
}
func (f *MutableDataFile) String() string {
return "MutableDataFile"
}
func (f *MutableDataFile) Read(buf []byte, off int64) (fuse.ReadResult, fuse.Status) {
end := int(off) + len(buf)
if end > len(f.data) {
end = len(f.data)
}
return fuse.ReadResultData(f.data[off:end]), fuse.OK
}
func (f *MutableDataFile) Write(d []byte, off int64) (uint32, fuse.Status) {
end := int64(len(d)) + off
if int(end) > len(f.data) {
data := make([]byte, len(f.data), end)
copy(data, f.data)
f.data = data[:end]
}
copy(f.data[off:end], d)
f.Attr.Size = uint64(len(f.data))
return uint32(end - off), fuse.OK
}
func (f *MutableDataFile) Flush() fuse.Status {
return fuse.OK
}
func (f *MutableDataFile) Release() {
}
func (f *MutableDataFile) getAttr(out *fuse.Attr) {
*out = f.Attr
out.Size = uint64(len(f.data))
}
func (f *MutableDataFile) GetAttr(out *fuse.Attr) fuse.Status {
f.GetAttrCalled = true
f.getAttr(out)
return fuse.OK
}
func (f *MutableDataFile) Utimens(atime *time.Time, mtime *time.Time) fuse.Status {
f.Attr.SetTimes(atime, mtime, nil)
return fuse.OK
}
func (f *MutableDataFile) Truncate(size uint64) fuse.Status {
f.data = f.data[:size]
return fuse.OK
}
func (f *MutableDataFile) Chown(uid uint32, gid uint32) fuse.Status {
f.Attr.Uid = uid
f.Attr.Gid = gid
return fuse.OK
}
func (f *MutableDataFile) Chmod(perms uint32) fuse.Status {
f.Attr.Mode = (f.Attr.Mode &^ 07777) | perms
return fuse.OK
}
func (f *MutableDataFile) Fsync(flags int) fuse.Status {
f.FsyncCalled = true
return fuse.OK
}
////////////////
// This FS only supports a single r/w file called "/file".
type FSetAttrFs struct {
pathfs.FileSystem
file *MutableDataFile
}
func (fs *FSetAttrFs) GetXAttr(name string, attr string, context *fuse.Context) ([]byte, fuse.Status) {
return nil, fuse.ENOATTR
}
func (fs *FSetAttrFs) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
if name == "" {
return &fuse.Attr{Mode: fuse.S_IFDIR | 0700}, fuse.OK
}
if name == "file" && fs.file != nil {
var a fuse.Attr
fs.file.getAttr(&a)
a.Mode |= fuse.S_IFREG
return &a, fuse.OK
}
return nil, fuse.ENOENT
}
func (fs *FSetAttrFs) Open(name string, flags uint32, context *fuse.Context) (nodefs.File, fuse.Status) {
if name == "file" {
return fs.file, fuse.OK
}
return nil, fuse.ENOENT
}
func (fs *FSetAttrFs) Create(name string, flags uint32, mode uint32, context *fuse.Context) (nodefs.File, fuse.Status) {
if name == "file" {
f := NewFile()
fs.file = f
fs.file.Attr.Mode = mode
return f, fuse.OK
}
return nil, fuse.ENOENT
}
func NewFile() *MutableDataFile {
return &MutableDataFile{File: nodefs.NewDefaultFile()}
}
func setupFAttrTest(t *testing.T, fs pathfs.FileSystem) (dir string, clean func()) {
dir = t.TempDir()
nfs := pathfs.NewPathNodeFs(fs, nil)
opts := nodefs.NewOptions()
opts.Debug = testutil.VerboseTest()
state, _, err := nodefs.MountRoot(dir, nfs.Root(), opts)
if err != nil {
t.Fatalf("MountNodeFileSystem failed: %v", err)
}
go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
clean = func() {
if err := state.Unmount(); err != nil {
t.Errorf("cleanup: Unmount: %v", err)
}
}
if state.KernelSettings().Flags&fuse.CAP_FILE_OPS == 0 {
clean()
t.Skip("Mount does not support file operations")
}
return dir, clean
}
func TestFSetAttr(t *testing.T) {
fSetAttrFs := &FSetAttrFs{
FileSystem: pathfs.NewDefaultFileSystem(),
}
fs := pathfs.NewLockingFileSystem(fSetAttrFs)
dir, clean := setupFAttrTest(t, fs)
defer func() {
if clean != nil {
clean()
}
}()
fn := dir + "/file"
f, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY, 0755)
if err != nil {
t.Fatalf("OpenFile failed: %v", err)
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
t.Fatalf("Stat failed: %v", err)
}
_, err = f.WriteString("hello")
if err != nil {
t.Fatalf("WriteString failed: %v", err)
}
code := syscall.Ftruncate(int(f.Fd()), 3)
if code != nil {
t.Error("truncate retval", os.NewSyscallError("Ftruncate", code))
}
if a, status := fs.GetAttr("file", nil); !status.Ok() {
t.Fatalf("GetAttr: status %v", status)
} else if a.Size != 3 {
t.Errorf("truncate: size %d, status %v", a.Size, status)
}
if err := f.Chmod(024); err != nil {
t.Fatalf("Chmod failed: %v", err)
}
if a, status := fs.GetAttr("file", nil); !status.Ok() {
t.Errorf("chmod: %v", status)
} else if a.Mode&07777 != 024 {
t.Errorf("getattr after chmod: %o", a.Mode&0777)
}
if err := os.Chtimes(fn, time.Unix(0, 100e3), time.Unix(0, 101e3)); err != nil {
t.Fatalf("Chtimes failed: %v", err)
}
if a, status := fs.GetAttr("file", nil); !status.Ok() {
t.Errorf("GetAttr: %v", status)
} else if a.Atimensec != 100e3 || a.Mtimensec != 101e3 {
t.Errorf("Utimens: atime %d != 100e3 mtime %d != 101e3",
a.Atimensec, a.Mtimensec)
}
newFi, err := f.Stat()
if err != nil {
t.Fatalf("Stat failed: %v", err)
}
i1 := fuse.ToStatT(fi).Ino
i2 := fuse.ToStatT(newFi).Ino
if i1 != i2 {
t.Errorf("f.Lstat().Ino = %d. Returned %d before.", i2, i1)
}
if code := syscall.Fsync(int(f.Fd())); code != nil {
t.Error("Fsync failed:", os.NewSyscallError("Fsync", code))
}
// Close the file, otherwise we can't unmount.
f.Close()
// Shutdown the FUSE FS so we can safely look at fSetAttrFs
clean()
clean = nil
if !fSetAttrFs.file.FsyncCalled {
t.Error("Fsync was not called")
}
}