Commit 643585d7 authored by Kirill Smelkov's avatar Kirill Smelkov

Merge branch 'master' into y/nodefs-cancel

* master:
  Make ReadDirPlus configurable
  all.bash: use /bin/bash explicitly
  fuse: Add release flag constants
  Fix sync.Pool leak
  README: make godoc links point to v2 instead of ancient v1.0.0
  Ports commit fee50bf0 to poll_darwin.go in order to allow read-only mounts on Darwin as well.
  Make OSXFUSE handle timeouts the same way as Linux
  Update to fuse 7.12
  example: remove unionfs examples
  unionfs: remove library
  posixtest: provide more detail for failing ReadDir test
parents 464ade3c 915cf541
# GO-FUSE
[![CI](https://github.com/hanwen/go-fuse/actions/workflows/ci.yml/badge.svg)](https://github.com/hanwen/go-fuse/actions/workflows/ci.yml)
[![GoDoc](https://godoc.org/github.com/hanwen/go-fuse?status.svg)](https://godoc.org/github.com/hanwen/go-fuse)
[![GoDoc](https://godoc.org/github.com/hanwen/go-fuse?status.svg)](https://godoc.org/github.com/hanwen/go-fuse/v2)
Go native bindings for the FUSE kernel module.
You should import and use
[github.com/hanwen/go-fuse/fs](https://godoc.org/github.com/hanwen/go-fuse/fs)
[github.com/hanwen/go-fuse/v2/fs](https://godoc.org/github.com/hanwen/go-fuse/v2/fs)
library. It follows the wire protocol closely, but provides
convenient abstractions for building both node and path based file
systems
Older, deprecated APIs are available at
[github.com/hanwen/go-fuse/fuse/pathfs](https://godoc.org/github.com/hanwen/go-fuse/fuse/pathfs)
[github.com/hanwen/go-fuse/fuse/pathfs](https://godoc.org/github.com/hanwen/go-fuse/v2/fuse/pathfs)
and
[github.com/hanwen/go-fuse/fuse/nodefs](https://godoc.org/github.com/hanwen/go-fuse/fuse/nodefs).
[github.com/hanwen/go-fuse/fuse/nodefs](https://godoc.org/github.com/hanwen/go-fuse/v2/fuse/nodefs).
## Comparison with other FUSE libraries
The FUSE library gained a new, cleaned-up API during a rewrite
completed in 2019. Find extensive documentation
[here](https://godoc.org/github.com/hanwen/go-fuse/).
[here](https://godoc.org/github.com/hanwen/go-fuse/v2).
Further highlights of this library is
......
#!/bin/sh
#!/bin/bash
set -eux
# Everything must compile on Linux
......
// 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 main
import (
"flag"
"fmt"
"os"
"time"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/v2/unionfs"
)
func main() {
debug := flag.Bool("debug", false, "debug on")
hardlinks := flag.Bool("hardlinks", false, "support hardlinks")
delcache_ttl := flag.Float64("deletion_cache_ttl", 5.0, "Deletion cache TTL in seconds.")
branchcache_ttl := flag.Float64("branchcache_ttl", 5.0, "Branch cache TTL in seconds.")
deldirname := flag.String(
"deletion_dirname", "GOUNIONFS_DELETIONS", "Directory name to use for deletions.")
hide_readonly_link := flag.Bool("hide_readonly_link", true,
"Hides READONLY link from the top mountpoints. "+
"Enabled by default.")
portableInodes := flag.Bool("portable-inodes", false,
"Use sequential 32-bit inode numbers.")
flag.Parse()
if len(flag.Args()) < 2 {
fmt.Println("Usage:\n main MOUNTPOINT BASEDIR")
os.Exit(2)
}
ufsOptions := unionfs.UnionFsOptions{
DeletionCacheTTL: time.Duration(*delcache_ttl * float64(time.Second)),
BranchCacheTTL: time.Duration(*branchcache_ttl * float64(time.Second)),
DeletionDirName: *deldirname,
}
options := unionfs.AutoUnionFsOptions{
UnionFsOptions: ufsOptions,
Options: nodefs.Options{
EntryTimeout: time.Second,
AttrTimeout: time.Second,
NegativeTimeout: time.Second,
Owner: fuse.CurrentOwner(),
Debug: *debug,
},
UpdateOnMount: true,
PathNodeFsOptions: pathfs.PathNodeFsOptions{
ClientInodes: *hardlinks,
},
HideReadonly: *hide_readonly_link,
}
fsOpts := nodefs.Options{
PortableInodes: *portableInodes,
Debug: *debug,
}
gofs := unionfs.NewAutoUnionFs(flag.Arg(1), options)
pathfs := pathfs.NewPathNodeFs(gofs, &pathfs.PathNodeFsOptions{
Debug: *debug,
})
state, _, err := nodefs.MountRoot(flag.Arg(0), pathfs.Root(), &fsOpts)
if err != nil {
fmt.Printf("Mount fail: %v\n", err)
os.Exit(1)
}
state.Serve()
time.Sleep(1 * time.Second)
}
// 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 main
import (
"flag"
"fmt"
"log"
"os"
"time"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/v2/unionfs"
)
func main() {
debug := flag.Bool("debug", false, "debug on")
portable := flag.Bool("portable", false, "use 32 bit inodes")
entry_ttl := flag.Float64("entry_ttl", 1.0, "fuse entry cache TTL.")
negative_ttl := flag.Float64("negative_ttl", 1.0, "fuse negative entry cache TTL.")
delcache_ttl := flag.Float64("deletion_cache_ttl", 5.0, "Deletion cache TTL in seconds.")
branchcache_ttl := flag.Float64("branchcache_ttl", 5.0, "Branch cache TTL in seconds.")
deldirname := flag.String(
"deletion_dirname", "GOUNIONFS_DELETIONS", "Directory name to use for deletions.")
flag.Parse()
if len(flag.Args()) < 2 {
fmt.Println("Usage:\n unionfs MOUNTPOINT RW-DIRECTORY RO-DIRECTORY ...")
os.Exit(2)
}
ufsOptions := unionfs.UnionFsOptions{
DeletionCacheTTL: time.Duration(*delcache_ttl * float64(time.Second)),
BranchCacheTTL: time.Duration(*branchcache_ttl * float64(time.Second)),
DeletionDirName: *deldirname,
}
ufs, err := unionfs.NewUnionFsFromRoots(flag.Args()[1:], &ufsOptions, true)
if err != nil {
log.Fatal("Cannot create UnionFs", err)
os.Exit(1)
}
nodeFs := pathfs.NewPathNodeFs(ufs, &pathfs.PathNodeFsOptions{ClientInodes: true})
mOpts := nodefs.Options{
EntryTimeout: time.Duration(*entry_ttl * float64(time.Second)),
AttrTimeout: time.Duration(*entry_ttl * float64(time.Second)),
NegativeTimeout: time.Duration(*negative_ttl * float64(time.Second)),
PortableInodes: *portable,
Debug: *debug,
}
mountState, _, err := nodefs.MountRoot(flag.Arg(0), nodeFs.Root(), &mOpts)
if err != nil {
log.Fatal("Mount fail:", err)
}
mountState.Serve()
}
......@@ -229,6 +229,11 @@ type MountOptions struct {
// in https://github.com/libfuse/libfuse/blob/master/include/fuse_common.h
// for details.
EnableAcl bool
// Disable ReadDirPlus capability so ReadDir is used instead. Simple
// directory queries (i.e. 'ls' without '-l') can be faster with
// ReadDir, as no per-file stat calls are needed
DisableReadDirPlus bool
}
// RawFileSystem is an interface close to the FUSE wire protocol.
......
......@@ -23,4 +23,5 @@ func (a *Attr) FromStat(s *syscall.Stat_t) {
a.Uid = uint32(s.Uid)
a.Gid = uint32(s.Gid)
a.Rdev = uint32(s.Rdev)
a.Blksize = uint32(s.Blksize)
}
......@@ -18,6 +18,9 @@ const (
FUSE_LK_FLOCK = (1 << 0)
FUSE_RELEASE_FLUSH = (1 << 0)
FUSE_RELEASE_FLOCK_UNLOCK = (1 << 1)
FUSE_IOCTL_MAX_IOV = 256
FUSE_POLL_SCHEDULE_NOTIFY = (1 << 0)
......
......@@ -103,6 +103,10 @@ func doInit(server *Server, req *request) {
// Clear CAP_ASYNC_READ
server.kernelSettings.Flags &= ^uint32(CAP_ASYNC_READ)
}
if server.opts.DisableReadDirPlus {
// Clear CAP_READDIRPLUS
server.kernelSettings.Flags &= ^uint32(CAP_READDIRPLUS)
}
dataCacheMode := input.Flags & CAP_AUTO_INVAL_DATA
if server.opts.ExplicitDataCacheControl {
......
......@@ -32,7 +32,7 @@ func pollHack(mountPoint string) error {
POLLHUP = 0x10
)
fd, err := syscall.Open(filepath.Join(mountPoint, pollHackName), syscall.O_CREAT|syscall.O_TRUNC|syscall.O_RDWR, 0644)
fd, err := syscall.Open(filepath.Join(mountPoint, pollHackName), syscall.O_RDONLY, 0)
if err != nil {
return err
}
......
......@@ -18,13 +18,13 @@ func (a *Attr) string() string {
return fmt.Sprintf(
"{M0%o SZ=%d L=%d "+
"%d:%d "+
"%d %d:%d "+
"B%d*%d i%d:%d "+
"A %f "+
"M %f "+
"C %f}",
a.Mode, a.Size, a.Nlink,
a.Uid, a.Gid,
a.Blocks,
a.Blocks, a.Blksize,
a.Rdev, a.Ino, ft(a.Atime, a.Atimensec), ft(a.Mtime, a.Mtimensec),
ft(a.Ctime, a.Ctimensec))
}
......
......@@ -8,6 +8,6 @@ const outputHeaderSize = 200
const (
_FUSE_KERNEL_VERSION = 7
_MINIMUM_MINOR_VERSION = 8
_OUR_MINOR_VERSION = 8
_MINIMUM_MINOR_VERSION = 12
_OUR_MINOR_VERSION = 12
)
......@@ -262,6 +262,13 @@ func (o *MountOptions) optionsStrings() []string {
r = append(r, "subtype="+o.Name)
}
// OSXFUSE applies a 60-second timeout for file operations. This
// is inconsistent with how FUSE works on Linux, where operations
// last as long as the daemon is willing to let them run.
if runtime.GOOS == "darwin" {
r = append(r, "daemon_timeout=0")
}
return r
}
......@@ -298,9 +305,6 @@ func handleEINTR(fn func() error) (err error) {
// Returns a new request, or error. In case exitIdle is given, returns
// nil, OK if we have too many readers already.
func (ms *Server) readRequest(exitIdle bool) (req *request, code Status) {
req = ms.reqPool.Get().(*request)
dest := ms.readPool.Get().([]byte)
ms.reqMu.Lock()
if ms.reqReaders > ms.maxReaders {
ms.reqMu.Unlock()
......@@ -309,6 +313,9 @@ func (ms *Server) readRequest(exitIdle bool) (req *request, code Status) {
ms.reqReaders++
ms.reqMu.Unlock()
req = ms.reqPool.Get().(*request)
dest := ms.readPool.Get().([]byte)
var n int
err := handleEINTR(func() error {
var err error
......
......@@ -32,6 +32,8 @@ type Attr struct {
Owner
Rdev uint32
Flags_ uint32 // OS X
Blksize uint32
Padding uint32
}
const (
......@@ -67,38 +69,52 @@ const (
type GetAttrIn struct {
InHeader
Flags_ uint32
Dummy uint32
Fh_ uint64
}
func (g *GetAttrIn) Flags() uint32 {
return 0
return g.Flags_
}
func (g *GetAttrIn) Fh() uint64 {
return 0
return g.Fh_
}
// Uses OpenIn struct for create.
type CreateIn struct {
InHeader
Flags uint32
// Mode for the new file; already takes Umask into account.
Mode uint32
// Umask used for this create call.
Umask uint32
Padding uint32
}
type MknodIn struct {
InHeader
// Mode to use, including the Umask value
Mode uint32
Rdev uint32
Umask uint32
Padding uint32
}
type ReadIn struct {
InHeader
Fh uint64
Offset uint64
Size uint32
ReadFlags uint32
LockOwner uint64
Flags uint32
Padding uint32
}
type WriteIn struct {
......@@ -107,6 +123,9 @@ type WriteIn struct {
Offset uint64
Size uint32
WriteFlags uint32
LockOwner uint64
Flags uint32
Padding uint32
}
type SetXAttrIn struct {
......
......@@ -487,11 +487,16 @@ func ReadDir(t *testing.T, mnt string) {
got[e] = true
}
if len(got) != len(want) {
t.Errorf("got %d entries, want %d", len(got), len(want))
t.Errorf("mismatch got %d want %d", len(got), len(want))
}
for k := range got {
if !want[k] {
t.Errorf("got unknown name %q", k)
t.Errorf("got extra entry %q", k)
}
}
for k := range want {
if !got[k] {
t.Errorf("missing entry %q", k)
}
}
}
......
This diff is collapsed.
// 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 unionfs
import (
"io/ioutil"
"os"
"testing"
"time"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
const entryTTL = 100 * time.Millisecond
var testAOpts = AutoUnionFsOptions{
UnionFsOptions: testOpts,
Options: nodefs.Options{
EntryTimeout: entryTTL,
AttrTimeout: entryTTL,
NegativeTimeout: 0,
Debug: testutil.VerboseTest(),
LookupKnownChildren: true,
},
HideReadonly: true,
Version: "version",
}
func init() {
testAOpts.Options.Debug = testutil.VerboseTest()
}
func WriteFile(t *testing.T, name string, contents string) {
err := ioutil.WriteFile(name, []byte(contents), 0644)
if err != nil {
t.Fatalf("WriteFile failed: %v", err)
}
}
func setup(t *testing.T) (workdir string, server *fuse.Server, cleanup func()) {
wd := testutil.TempDir()
err := os.Mkdir(wd+"/mnt", 0700)
if err != nil {
t.Fatalf("Mkdir failed: %v", err)
}
err = os.Mkdir(wd+"/store", 0700)
if err != nil {
t.Fatalf("Mkdir failed: %v", err)
}
os.Mkdir(wd+"/ro", 0700)
if err != nil {
t.Fatalf("Mkdir failed: %v", err)
}
WriteFile(t, wd+"/ro/file1", "file1")
WriteFile(t, wd+"/ro/file2", "file2")
fs := NewAutoUnionFs(wd+"/store", testAOpts)
nfs := pathfs.NewPathNodeFs(fs, nil)
state, _, err := nodefs.MountRoot(wd+"/mnt", nfs.Root(), &testAOpts.Options)
if err != nil {
t.Fatalf("MountNodeFileSystem failed: %v", err)
}
go state.Serve()
state.WaitMount()
return wd, state, func() {
state.Unmount()
os.RemoveAll(wd)
}
}
func TestDebug(t *testing.T) {
wd, _, clean := setup(t)
defer clean()
c, err := ioutil.ReadFile(wd + "/mnt/status/debug")
if err != nil {
t.Fatalf("ReadFile failed: %v", err)
}
if len(c) == 0 {
t.Fatal("No debug found.")
}
}
func TestVersion(t *testing.T) {
wd, _, clean := setup(t)
defer clean()
c, err := ioutil.ReadFile(wd + "/mnt/status/gounionfs_version")
if err != nil {
t.Fatalf("ReadFile failed: %v", err)
}
if len(c) == 0 {
t.Fatal("No version found.")
}
}
func TestAutoFsSymlink(t *testing.T) {
wd, server, clean := setup(t)
defer clean()
err := os.Mkdir(wd+"/store/backing1", 0755)
if err != nil {
t.Fatalf("Mkdir failed: %v", err)
}
err = os.Symlink(wd+"/ro", wd+"/store/backing1/READONLY")
if err != nil {
t.Fatalf("Symlink failed: %v", err)
}
err = os.Symlink(wd+"/store/backing1", wd+"/mnt/config/manual1")
if err != nil {
t.Fatalf("Symlink failed: %v", err)
}
fi, err := os.Lstat(wd + "/mnt/manual1/file1")
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
entries, err := ioutil.ReadDir(wd + "/mnt")
if err != nil {
t.Fatalf("ReadDir failed: %v", err)
}
if len(entries) != 3 {
t.Error("readdir mismatch", entries)
}
err = os.Remove(wd + "/mnt/config/manual1")
if err != nil {
t.Fatalf("Remove failed: %v", err)
}
scan := wd + "/mnt/config/" + _SCAN_CONFIG
err = ioutil.WriteFile(scan, []byte("something"), 0644)
if err != nil {
t.Error("error writing:", err)
}
// If FUSE supports invalid inode notifications we expect this node to be gone. Otherwise we'll just make sure that it's not reachable.
if server.KernelSettings().SupportsNotify(fuse.NOTIFY_INVAL_INODE) {
fi, _ = os.Lstat(wd + "/mnt/manual1")
if fi != nil {
t.Error("Should not have file:", fi)
}
} else {
entries, err = ioutil.ReadDir(wd + "/mnt")
if err != nil {
t.Fatalf("ReadDir failed: %v", err)
}
for _, e := range entries {
if e.Name() == "manual1" {
t.Error("Should not have entry: ", e)
}
}
}
_, err = os.Lstat(wd + "/mnt/backing1/file1")
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
}
func TestDetectSymlinkedDirectories(t *testing.T) {
wd, _, clean := setup(t)
defer clean()
err := os.Mkdir(wd+"/backing1", 0755)
if err != nil {
t.Fatalf("Mkdir failed: %v", err)
}
err = os.Symlink(wd+"/ro", wd+"/backing1/READONLY")
if err != nil {
t.Fatalf("Symlink failed: %v", err)
}
err = os.Symlink(wd+"/backing1", wd+"/store/backing1")
if err != nil {
t.Fatalf("Symlink failed: %v", err)
}
scan := wd + "/mnt/config/" + _SCAN_CONFIG
err = ioutil.WriteFile(scan, []byte("something"), 0644)
if err != nil {
t.Error("error writing:", err)
}
_, err = os.Lstat(wd + "/mnt/backing1")
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
}
func TestExplicitScan(t *testing.T) {
wd, _, clean := setup(t)
defer clean()
err := os.Mkdir(wd+"/store/backing1", 0755)
if err != nil {
t.Fatalf("Mkdir failed: %v", err)
}
os.Symlink(wd+"/ro", wd+"/store/backing1/READONLY")
if err != nil {
t.Fatalf("Symlink failed: %v", err)
}
fi, _ := os.Lstat(wd + "/mnt/backing1")
if fi != nil {
t.Error("Should not have file:", fi)
}
scan := wd + "/mnt/config/" + _SCAN_CONFIG
_, err = os.Lstat(scan)
if err != nil {
t.Error(".scan_config missing:", err)
}
err = ioutil.WriteFile(scan, []byte("something"), 0644)
if err != nil {
t.Error("error writing:", err)
}
_, err = os.Lstat(wd + "/mnt/backing1")
if err != nil {
t.Error("Should have workspace backing1:", err)
}
}
func TestCreationChecks(t *testing.T) {
wd, _, clean := setup(t)
defer clean()
err := os.Mkdir(wd+"/store/foo", 0755)
if err != nil {
t.Fatalf("Mkdir failed: %v", err)
}
os.Symlink(wd+"/ro", wd+"/store/foo/READONLY")
if err != nil {
t.Fatalf("Symlink failed: %v", err)
}
err = os.Mkdir(wd+"/store/ws2", 0755)
if err != nil {
t.Fatalf("Mkdir failed: %v", err)
}
os.Symlink(wd+"/ro", wd+"/store/ws2/READONLY")
if err != nil {
t.Fatalf("Symlink failed: %v", err)
}
err = os.Symlink(wd+"/store/foo", wd+"/mnt/config/bar")
if err != nil {
t.Fatalf("Symlink failed: %v", err)
}
err = os.Symlink(wd+"/store/foo", wd+"/mnt/config/foo")
code := fuse.ToStatus(err)
if code != fuse.EBUSY {
t.Error("Should return EBUSY", err)
}
err = os.Symlink(wd+"/store/ws2", wd+"/mnt/config/config")
code = fuse.ToStatus(err)
if code != fuse.EINVAL {
t.Error("Should return EINVAL", err)
}
}
// 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 unionfs
import (
"fmt"
"log"
"strings"
"time"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse/pathfs"
)
const _XATTRSEP = "@XATTR@"
type attrResponse struct {
*fuse.Attr
fuse.Status
}
type xattrResponse struct {
data []byte
fuse.Status
}
type dirResponse struct {
entries []fuse.DirEntry
fuse.Status
}
type linkResponse struct {
linkContent string
fuse.Status
}
// Caches filesystem metadata.
type cachingFileSystem struct {
pathfs.FileSystem
attributes *TimedCache
dirs *TimedCache
links *TimedCache
xattr *TimedCache
}
func readDir(fs pathfs.FileSystem, name string) *dirResponse {
origStream, code := fs.OpenDir(name, nil)
r := &dirResponse{nil, code}
if !code.Ok() {
return r
}
r.entries = origStream
return r
}
func getAttr(fs pathfs.FileSystem, name string) *attrResponse {
a, code := fs.GetAttr(name, nil)
return &attrResponse{
Attr: a,
Status: code,
}
}
func getXAttr(fs pathfs.FileSystem, nameAttr string) *xattrResponse {
ns := strings.SplitN(nameAttr, _XATTRSEP, 2)
a, code := fs.GetXAttr(ns[0], ns[1], nil)
return &xattrResponse{
data: a,
Status: code,
}
}
func readLink(fs pathfs.FileSystem, name string) *linkResponse {
a, code := fs.Readlink(name, nil)
return &linkResponse{
linkContent: a,
Status: code,
}
}
func NewCachingFileSystem(fs pathfs.FileSystem, ttl time.Duration) pathfs.FileSystem {
c := new(cachingFileSystem)
c.FileSystem = fs
c.attributes = NewTimedCache(func(n string) (interface{}, bool) {
a := getAttr(fs, n)
return a, a.Ok()
}, ttl)
c.dirs = NewTimedCache(func(n string) (interface{}, bool) {
d := readDir(fs, n)
return d, d.Ok()
}, ttl)
c.links = NewTimedCache(func(n string) (interface{}, bool) {
l := readLink(fs, n)
return l, l.Ok()
}, ttl)
c.xattr = NewTimedCache(func(n string) (interface{}, bool) {
l := getXAttr(fs, n)
return l, l.Ok()
}, ttl)
return c
}
func (fs *cachingFileSystem) DropCache() {
for _, c := range []*TimedCache{fs.attributes, fs.dirs, fs.links, fs.xattr} {
c.DropAll(nil)
}
}
func (fs *cachingFileSystem) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
if name == _DROP_CACHE {
return &fuse.Attr{
Mode: fuse.S_IFREG | 0777,
}, fuse.OK
}
r := fs.attributes.Get(name).(*attrResponse)
return r.Attr, r.Status
}
func (fs *cachingFileSystem) GetXAttr(name string, attr string, context *fuse.Context) ([]byte, fuse.Status) {
key := name + _XATTRSEP + attr
r := fs.xattr.Get(key).(*xattrResponse)
return r.data, r.Status
}
func (fs *cachingFileSystem) Readlink(name string, context *fuse.Context) (string, fuse.Status) {
r := fs.links.Get(name).(*linkResponse)
return r.linkContent, r.Status
}
func (fs *cachingFileSystem) OpenDir(name string, context *fuse.Context) (stream []fuse.DirEntry, status fuse.Status) {
r := fs.dirs.Get(name).(*dirResponse)
return r.entries, r.Status
}
func (fs *cachingFileSystem) String() string {
return fmt.Sprintf("cachingFileSystem(%v)", fs.FileSystem)
}
func (fs *cachingFileSystem) Open(name string, flags uint32, context *fuse.Context) (f nodefs.File, status fuse.Status) {
if flags&fuse.O_ANYWRITE != 0 && name == _DROP_CACHE {
log.Println("Dropping cache for", fs)
fs.DropCache()
}
return fs.FileSystem.Open(name, flags, context)
}
// 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 unionfs
import (
"os"
"syscall"
"testing"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
func modeMapEq(m1, m2 map[string]uint32) bool {
if len(m1) != len(m2) {
return false
}
for k, v := range m1 {
val, ok := m2[k]
if !ok || val != v {
return false
}
}
return true
}
func TestCachingFs(t *testing.T) {
wd := testutil.TempDir()
defer os.RemoveAll(wd)
fs := pathfs.NewLoopbackFileSystem(wd)
cfs := NewCachingFileSystem(fs, 0)
os.Mkdir(wd+"/orig", 0755)
fi, code := cfs.GetAttr("orig", nil)
if !code.Ok() {
t.Fatal("GetAttr failure", code)
}
if !fi.IsDir() {
t.Error("unexpected attr", fi)
}
os.Symlink("orig", wd+"/symlink")
val, code := cfs.Readlink("symlink", nil)
if val != "orig" {
t.Error("unexpected readlink", val)
}
if !code.Ok() {
t.Error("code !ok ", code)
}
stream, code := cfs.OpenDir("", nil)
if !code.Ok() {
t.Fatal("Readdir fail", code)
}
results := make(map[string]uint32)
for _, v := range stream {
results[v.Name] = v.Mode &^ 07777
}
expected := map[string]uint32{
"symlink": syscall.S_IFLNK,
"orig": fuse.S_IFDIR,
}
if !modeMapEq(results, expected) {
t.Error("Unexpected readdir result", results, expected)
}
}
// 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 unionfs
import (
"os"
"github.com/hanwen/go-fuse/v2/fuse/pathfs"
)
func NewUnionFsFromRoots(roots []string, opts *UnionFsOptions, roCaching bool) (pathfs.FileSystem, error) {
fses := make([]pathfs.FileSystem, 0)
for i, r := range roots {
var fs pathfs.FileSystem
fi, err := os.Stat(r)
if err != nil {
return nil, err
}
if fi.IsDir() {
fs = pathfs.NewLoopbackFileSystem(r)
}
if fs == nil {
return nil, err
}
if i > 0 && roCaching {
fs = NewCachingFileSystem(fs, 0)
}
fses = append(fses, fs)
}
return NewUnionFs(fses, *opts)
}
// 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 unionfs
import (
"sync"
"time"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/pathfs"
)
// newDirnameMap reads the contents of the given directory. On error,
// returns a nil map. This forces reloads in the dirCache until we
// succeed.
func newDirnameMap(fs pathfs.FileSystem, dir string) map[string]struct{} {
stream, code := fs.OpenDir(dir, nil)
if code == fuse.ENOENT {
// The directory not existing is not an error.
return map[string]struct{}{}
}
if !code.Ok() {
return nil
}
result := make(map[string]struct{})
for _, e := range stream {
if e.Mode&fuse.S_IFREG != 0 {
result[e.Name] = struct{}{}
}
}
return result
}
// dirCache caches names in a directory for some time.
//
// If called when the cache is expired, the filenames are read afresh in
// the background.
type dirCache struct {
dir string
ttl time.Duration
fs pathfs.FileSystem
// Protects data below.
lock sync.RWMutex
// If nil, you may call refresh() to schedule a new one.
names map[string]struct{}
updateRunning bool
}
func (c *dirCache) setMap(newMap map[string]struct{}) {
c.lock.Lock()
defer c.lock.Unlock()
c.names = newMap
c.updateRunning = false
_ = time.AfterFunc(c.ttl,
func() { c.DropCache() })
}
func (c *dirCache) DropCache() {
c.lock.Lock()
defer c.lock.Unlock()
c.names = nil
}
// Try to refresh: if another update is already running, do nothing,
// otherwise, read the directory and set it.
func (c *dirCache) maybeRefresh() {
c.lock.Lock()
defer c.lock.Unlock()
if c.updateRunning {
return
}
c.updateRunning = true
go func() {
newmap := newDirnameMap(c.fs, c.dir)
c.setMap(newmap)
}()
}
func (c *dirCache) RemoveEntry(name string) {
c.lock.Lock()
defer c.lock.Unlock()
if c.names == nil {
go c.maybeRefresh()
return
}
delete(c.names, name)
}
func (c *dirCache) AddEntry(name string) {
c.lock.Lock()
defer c.lock.Unlock()
if c.names == nil {
go c.maybeRefresh()
return
}
c.names[name] = struct{}{}
}
func newDirCache(fs pathfs.FileSystem, dir string, ttl time.Duration) *dirCache {
dc := new(dirCache)
dc.dir = dir
dc.fs = fs
dc.ttl = ttl
return dc
}
func (c *dirCache) HasEntry(name string) (mapPresent bool, found bool) {
c.lock.RLock()
defer c.lock.RUnlock()
if c.names == nil {
go c.maybeRefresh()
return false, false
}
_, ok := c.names[name]
return true, ok
}
// 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 unionfs
import (
"sync"
"time"
)
type cacheEntry struct {
data interface{}
// expiry is the absolute timestamp of the expiry.
expiry time.Time
}
// TimedCache caches the result of fetch() for some time. It is
// thread-safe. Calls of fetch() do no happen inside a critical
// section, so when multiple concurrent Get()s happen for the same
// key, multiple fetch() calls may be issued for the same key.
type TimedCacheFetcher func(name string) (value interface{}, cacheable bool)
type TimedCache struct {
fetch TimedCacheFetcher
// ttl is the duration of the cache.
ttl time.Duration
cacheMapMutex sync.RWMutex
cacheMap map[string]*cacheEntry
PurgeTimer *time.Timer
}
// Creates a new cache with the given TTL. If TTL <= 0, the caching is
// indefinite.
func NewTimedCache(fetcher TimedCacheFetcher, ttl time.Duration) *TimedCache {
l := new(TimedCache)
l.ttl = ttl
l.fetch = fetcher
l.cacheMap = make(map[string]*cacheEntry)
return l
}
func (c *TimedCache) Get(name string) interface{} {
c.cacheMapMutex.RLock()
info, ok := c.cacheMap[name]
c.cacheMapMutex.RUnlock()
valid := ok && (c.ttl <= 0 || info.expiry.After(time.Now()))
if valid {
return info.data
}
return c.GetFresh(name)
}
func (c *TimedCache) Set(name string, val interface{}) {
c.cacheMapMutex.Lock()
defer c.cacheMapMutex.Unlock()
c.cacheMap[name] = &cacheEntry{
data: val,
expiry: time.Now().Add(c.ttl),
}
}
func (c *TimedCache) DropEntry(name string) {
c.cacheMapMutex.Lock()
defer c.cacheMapMutex.Unlock()
delete(c.cacheMap, name)
}
func (c *TimedCache) GetFresh(name string) interface{} {
data, ok := c.fetch(name)
if ok {
c.Set(name, data)
}
return data
}
// Drop all expired entries.
func (c *TimedCache) Purge() {
keys := make([]string, 0, len(c.cacheMap))
now := time.Now()
c.cacheMapMutex.Lock()
defer c.cacheMapMutex.Unlock()
for k, v := range c.cacheMap {
if now.After(v.expiry) {
keys = append(keys, k)
}
}
for _, k := range keys {
delete(c.cacheMap, k)
}
}
func (c *TimedCache) DropAll(names []string) {
c.cacheMapMutex.Lock()
defer c.cacheMapMutex.Unlock()
if names == nil {
c.cacheMap = make(map[string]*cacheEntry, len(c.cacheMap))
} else {
for _, nm := range names {
delete(c.cacheMap, nm)
}
}
}
// 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 unionfs
import (
"testing"
"time"
)
func TestTimedCacheUncacheable(t *testing.T) {
fetchCount := 0
fetch := func(n string) (interface{}, bool) {
fetchCount++
i := int(n[0])
return &i, false
}
cache := NewTimedCache(fetch, 0)
v := cache.Get("n").(*int)
w := cache.Get("n").(*int)
if *v != int('n') || *w != *v {
t.Errorf("value mismatch: got %d, %d want %d", *v, *w, int('n'))
}
if fetchCount != 2 {
t.Fatalf("Should have fetched twice: %d", fetchCount)
}
}
func TestTimedCache(t *testing.T) {
fetchCount := 0
fetch := func(n string) (interface{}, bool) {
fetchCount++
i := int(n[0])
return &i, true
}
// This fails with 1e6 on some Opteron CPUs.
ttl := 100 * time.Millisecond
cache := NewTimedCache(fetch, ttl)
v := cache.Get("n").(*int)
if *v != int('n') {
t.Errorf("value mismatch: got %d, want %d", *v, int('n'))
}
if fetchCount != 1 {
t.Errorf("fetch count mismatch: got %d want 1", fetchCount)
}
// The cache update is async.
time.Sleep(time.Duration(ttl / 10))
w := cache.Get("n")
if v != w {
t.Errorf("Huh, inconsistent: 1st = %v != 2nd = %v", v, w)
}
if fetchCount > 1 {
t.Errorf("fetch count fail: %d > 1", fetchCount)
}
time.Sleep(time.Duration(ttl * 2))
cache.Purge()
w = cache.Get("n")
if fetchCount == 1 {
t.Error("Did not fetch again. Purge unsuccessful?")
}
}
This diff is collapsed.
This diff is collapsed.
// 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 unionfs
import (
"os"
"sync/atomic"
"testing"
"time"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
type TestFS struct {
pathfs.FileSystem
xattrRead int64
}
func (fs *TestFS) GetAttr(path string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
switch path {
case "":
return &fuse.Attr{Mode: fuse.S_IFDIR | 0755}, fuse.OK
case "file":
return &fuse.Attr{Mode: fuse.S_IFREG | 0755}, fuse.OK
}
return nil, fuse.ENOENT
}
func (fs *TestFS) GetXAttr(path string, name string, context *fuse.Context) ([]byte, fuse.Status) {
if path == "file" && name == "user.attr" {
atomic.AddInt64(&fs.xattrRead, 1)
return []byte{42}, fuse.OK
}
return nil, fuse.ENOATTR
}
func TestXAttrCaching(t *testing.T) {
wd := testutil.TempDir()
defer os.RemoveAll(wd)
os.Mkdir(wd+"/mnt", 0700)
err := os.Mkdir(wd+"/rw", 0700)
if err != nil {
t.Fatalf("Mkdir failed: %v", err)
}
rwFS := pathfs.NewLoopbackFileSystem(wd + "/rw")
roFS := &TestFS{
FileSystem: pathfs.NewDefaultFileSystem(),
}
ufs, err := NewUnionFs([]pathfs.FileSystem{rwFS,
NewCachingFileSystem(roFS, entryTTL)}, testOpts)
if err != nil {
t.Fatalf("NewUnionFs: %v", err)
}
opts := &nodefs.Options{
EntryTimeout: entryTTL / 2,
AttrTimeout: entryTTL / 2,
NegativeTimeout: entryTTL / 2,
Debug: testutil.VerboseTest(),
LookupKnownChildren: true,
}
pathfs := pathfs.NewPathNodeFs(ufs,
&pathfs.PathNodeFsOptions{ClientInodes: true,
Debug: testutil.VerboseTest()})
server, _, err := nodefs.MountRoot(wd+"/mnt", pathfs.Root(), opts)
if err != nil {
t.Fatalf("MountNodeFileSystem failed: %v", err)
}
defer server.Unmount()
go server.Serve()
server.WaitMount()
start := time.Now()
if fi, err := os.Lstat(wd + "/mnt"); err != nil || !fi.IsDir() {
t.Fatalf("root not readable: %v, %v", err, fi)
}
buf := make([]byte, 1024)
n, err := Getxattr(wd+"/mnt/file", "user.attr", buf)
if err != nil {
t.Fatalf("Getxattr: %v", err)
}
want := "\x2a"
got := string(buf[:n])
if got != want {
t.Fatalf("Got %q want %q", got, err)
}
time.Sleep(entryTTL / 3)
n, err = Getxattr(wd+"/mnt/file", "user.attr", buf)
if err != nil {
t.Fatalf("Getxattr: %v", err)
}
got = string(buf[:n])
if got != want {
t.Fatalf("Got %q want %q", got, err)
}
time.Sleep(entryTTL / 3)
// Make sure that an interceding Getxattr() to a filesystem that doesn't implement GetXAttr() doesn't affect future calls.
Getxattr(wd, "whatever", buf)
n, err = Getxattr(wd+"/mnt/file", "user.attr", buf)
if err != nil {
t.Fatalf("Getxattr: %v", err)
}
got = string(buf[:n])
if got != want {
t.Fatalf("Got %q want %q", got, err)
}
if time.Now().Sub(start) >= entryTTL {
// If we run really slowly, this test will spuriously
// fail.
t.Skip("test took too long.")
}
actual := atomic.LoadInt64(&roFS.xattrRead)
if actual != 1 {
t.Errorf("got xattrRead=%d, want 1", actual)
}
}
// 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 unionfs
import (
"syscall"
"unsafe"
)
// Darwin doesn't have support for syscall.Getxattr() so we pull it into its own file and implement it by hand on Darwin.
func Getxattr(path string, attr string, dest []byte) (sz int, err error) {
var _p0 *byte
_p0, err = syscall.BytePtrFromString(path)
if err != nil {
return
}
var _p1 *byte
_p1, err = syscall.BytePtrFromString(attr)
if err != nil {
return
}
var _p2 unsafe.Pointer
if len(dest) > 0 {
_p2 = unsafe.Pointer(&dest[0])
} else {
var _zero uintptr
_p2 = unsafe.Pointer(&_zero)
}
r0, _, e1 := syscall.Syscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(dest)), 0, 0)
sz = int(r0)
if e1 != 0 {
err = e1
}
return
}
// 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 unionfs
import (
"syscall"
)
// Darwin doesn't have support for syscall.Getxattr() so we pull it into its own file and implement it by hand on Darwin.
func Getxattr(path string, attr string, dest []byte) (sz int, err error) {
return syscall.Getxattr(path, attr, dest)
}
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