vendor github.com/hanwen/go-fuse (#2245)

This commit is contained in:
Adam Leventhal
2016-08-02 17:14:10 -07:00
committed by GitHub
parent 117eafebbc
commit cb10a6ae7f
137 changed files with 19728 additions and 0 deletions

14
vendor/github.com/hanwen/go-fuse/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,14 @@
*~
*.6
8.out
.nfs*
_*
6.out
#*
example/hello/hello
example/unionfs/unionfs
example/autounionfs/autounionfs
example/loopback/loopback
example/multizip/multizip
example/bulkstat/bulkstat
example/zipfs/zipfs

2
vendor/github.com/hanwen/go-fuse/.version generated vendored Normal file
View File

@@ -0,0 +1,2 @@
https://github.com/hanwen/go-fuse
ac3dcb75c6f00809911236629fe92af66b02dd35

22
vendor/github.com/hanwen/go-fuse/CONTRIBUTING generated vendored Normal file
View File

@@ -0,0 +1,22 @@
Before sending a patch or Pull request, please fill out a Google CLA, using the following form:
https://cla.developers.google.com/clas
For complex changes, please use Gerrit:
* Connect your github account with gerrithub, at
https://review.gerrithub.io/static/intro.html
* The signup will setup your SSH keys from Github.
* Create your change as a commit
* Add an ID to the commit message:
echo "Change-Id: I"$(head -c 20 /dev/urandom | sha1sum | awk '{print $1}')
* Push the change for review,
git push ssh://$USER@review.gerrithub.io:29418/hanwen/go-fuse HEAD:refs/for/master

30
vendor/github.com/hanwen/go-fuse/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,30 @@
// New BSD License
//
// Copyright (c) 2010 Ivan Krasin (imkrasin@gmail.com). All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Ivan Krasin nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//

150
vendor/github.com/hanwen/go-fuse/README generated vendored Normal file
View File

@@ -0,0 +1,150 @@
GO-FUSE: native bindings for the FUSE kernel module.
HIGHLIGHTS
* High speed: as fast as libfuse using the gc compiler for single
threaded loads.
* Supports in-process mounting of different FileSystems onto
subdirectories of the FUSE mount.
* Supports 3 interfaces for writing filesystems:
- PathFileSystem: define filesystems in terms path names.
- NodeFileSystem: define filesystems in terms of inodes.
- RawFileSystem: define filesystems in terms of FUSE's raw
wire protocol.
* Both NodeFileSystem and PathFileSystem support manipulation of true
hardlinks.
* Includes two fleshed out examples, zipfs and unionfs.
EXAMPLES
* examples/hello/hello.go contains a 60-line "hello world" filesystem
* zipfs/zipfs.go contains a small and simple read-only filesystem for
zip and tar files. The corresponding command is in example/zipfs/
For example,
mkdir /tmp/mountpoint
example/zipfs/zipfs /tmp/mountpoint file.zip &
ls /tmp/mountpoint
fusermount -u /tmp/mountpoint
* zipfs/multizipfs.go shows how to use in-process mounts to
combine multiple Go-FUSE filesystems into a larger filesystem.
* fuse/loopback.go mounts another piece of the filesystem.
Functionally, it is similar to a symlink. A binary to run is in
example/loopback/ . For example
mkdir /tmp/mountpoint
example/loopback/loopback -debug /tmp/mountpoint /some/other/directory &
ls /tmp/mountpoint
fusermount -u /tmp/mountpoint
* unionfs/unionfs.go: implements a union mount using 1 R/W branch, and
multiple R/O branches.
mkdir -p /tmp/mountpoint /tmp/writable
example/unionfs/unionfs /tmp/mountpoint /tmp/writable /usr &
ls /tmp/mountpoint
ls -l /tmp/mountpoint/bin/vi
rm /tmp/mountpoint/bin/vi
ls -l /tmp/mountpoint/bin/vi
cat /tmp/writable/*DELETION*/*
* union/autounionfs.go: creates UnionFs mounts automatically based on
existence of READONLY symlinks.
Tested on:
- x86 32bits (Fedora 14).
- x86 64bits (Ubuntu Lucid).
BENCHMARKS
We use threaded stats over a read-only filesystem for benchmarking.
Automated code is under benchmark/ directory. A simple C version of
the same FS gives a FUSE baseline
Data points (Go-FUSE version May 2012), 1000 files, high level
interface, all kernel caching turned off, median stat time:
platform libfuse Go-FUSE difference (%)
Lenovo T60/Fedora16 (1cpu) 349us 355us 2% slower
Lenovo T400/Lucid (1cpu) 138us 140us 5% slower
Dell T3500/Lucid (1cpu) 72us 76us 5% slower
On T60, for each file we have
- Client side latency is 360us
- 106us of this is server side latency (4.5x lookup 23us, 1x getattr 4us)
- 16.5us is due to latency measurements.
- 3us is due to garbage collection.
MACOS SUPPORT
go-fuse works somewhat on OSX. Known limitations:
* All of the limitations of OSXFUSE, including lack of support for
NOTIFY.
* OSX issues STATFS calls continuously (leading to performance
concerns).
* OSX has trouble with concurrent reads from the FUSE device, leading
to performance concerns.
* A bunch of tests under fuse/test/ fail, and the functionality they
test for OSX is likely broken.
CREDITS
* Inspired by Taru Karttunen's package, https://bitbucket.org/taruti/go-extra.
* Originally based on Ivan Krasin's https://github.com/krasin/go-fuse-zip
BUGS
Yes, probably. Report them through
https://github.com/hanwen/go-fuse/issues
DISCLAIMER
This is not an official Google product.
KNOWN PROBLEMS
Grep source code for TODO. Major topics:
* Support for umask in Create
* Missing support for network FS file locking: FUSE_GETLK, FUSE_SETLK,
FUSE_SETLKW
* Missing support for FUSE_INTERRUPT, CUSE, BMAP, POLL, IOCTL
* In the path API, renames are racy; See also:
http://sourceforge.net/mailarchive/message.php?msg_id=27550667
Don't use the path API if you care about correctness.
LICENSE
Like Go, this library is distributed under the new BSD license. See
accompanying LICENSE file.

35
vendor/github.com/hanwen/go-fuse/all.bash generated vendored Executable file
View File

@@ -0,0 +1,35 @@
#!/bin/sh
set -eu
for d in fuse zipfs unionfs fuse/test
do
(
cd $d
echo "go test github.com/hanwen/go-fuse/$d"
go test github.com/hanwen/go-fuse/$d
echo "go test -race github.com/hanwen/go-fuse/$d"
go test -race github.com/hanwen/go-fuse/$d
)
done
for target in "clean" "install" ; do
for d in fuse fuse/nodefs fuse/pathfs fuse/test zipfs unionfs \
example/hello example/loopback example/zipfs \
example/multizip example/unionfs example/memfs \
example/autounionfs ; \
do
if test "${target}" = "install" && test "${d}" = "fuse/test"; then
continue
fi
echo "go ${target} github.com/hanwen/go-fuse/${d}"
go ${target} github.com/hanwen/go-fuse/${d}
done
done
make -C benchmark
for d in benchmark
do
go test github.com/hanwen/go-fuse/benchmark -test.bench '.*' -test.cpu 1,2
done

2
vendor/github.com/hanwen/go-fuse/benchmark/Makefile generated vendored Normal file
View File

@@ -0,0 +1,2 @@
cstatfs: statfs.cc
g++ -O2 -Wall -std=c++0x $< `pkg-config fuse --cflags --libs` -o $@

136
vendor/github.com/hanwen/go-fuse/benchmark/benchmark.go generated vendored Normal file
View File

@@ -0,0 +1,136 @@
package benchmark
// Routines for benchmarking fuse.
import (
"bufio"
"fmt"
"log"
"math"
"os"
"regexp"
"sort"
"time"
)
func ReadLines(name string) []string {
f, err := os.Open(name)
if err != nil {
log.Fatal("ReadLines: ", err)
}
defer f.Close()
r := bufio.NewReader(f)
l := []string{}
for {
line, _, err := r.ReadLine()
if line == nil || err != nil {
break
}
fn := string(line)
l = append(l, fn)
}
if len(l) == 0 {
log.Fatal("no files added")
}
log.Printf("Read %d file names", len(l))
return l
}
// Used for benchmarking. Returns milliseconds.
func BulkStat(parallelism int, files []string) float64 {
todo := make(chan string, len(files))
dts := make(chan time.Duration, parallelism)
for i := 0; i < parallelism; i++ {
go func() {
t := time.Now()
for {
fn := <-todo
if fn == "" {
break
}
_, err := os.Lstat(fn)
if err != nil {
log.Fatal("All stats should succeed:", err)
}
}
dts <- time.Now().Sub(t)
}()
}
for _, v := range files {
todo <- v
}
close(todo)
total := 0.0
for i := 0; i < parallelism; i++ {
total += float64(<-dts) / float64(time.Millisecond)
}
avg := total / float64(len(files))
return avg
}
func AnalyzeBenchmarkRuns(label string, times []float64) {
sorted := times
sort.Float64s(sorted)
tot := 0.0
for _, v := range times {
tot += v
}
n := float64(len(times))
avg := tot / n
variance := 0.0
for _, v := range times {
variance += (v - avg) * (v - avg)
}
variance /= n
stddev := math.Sqrt(variance)
median := sorted[len(times)/2]
perc90 := sorted[int(n*0.9)]
perc10 := sorted[int(n*0.1)]
fmt.Printf(
"%s: %d samples\n"+
"avg %.3fms +/- %.0f%% "+
"median %.3fms, 10%%tiles: [-%.0f%%, +%.0f%%]\n",
label,
len(times), avg, 100.0*2*stddev/avg,
median, 100*(median-perc10)/median, 100*(perc90-median)/median)
}
func RunBulkStat(runs int, threads int, sleepTime time.Duration, files []string) (results []float64) {
for j := 0; j < runs; j++ {
result := BulkStat(threads, files)
results = append(results, result)
if j < runs-1 {
fmt.Printf("Sleeping %d seconds\n", sleepTime)
time.Sleep(sleepTime)
}
}
return results
}
func CountCpus() int {
var contents [10240]byte
f, err := os.Open("/proc/stat")
defer f.Close()
if err != nil {
return 1
}
n, _ := f.Read(contents[:])
re, _ := regexp.Compile("\ncpu[0-9]")
return len(re.FindAllString(string(contents[:n]), 100))
}

View File

@@ -0,0 +1,52 @@
package benchmark
import (
"sync"
"time"
)
type latencyMapEntry struct {
count int
dur time.Duration
}
type LatencyMap struct {
sync.Mutex
stats map[string]*latencyMapEntry
}
func NewLatencyMap() *LatencyMap {
m := &LatencyMap{}
m.stats = make(map[string]*latencyMapEntry)
return m
}
func (m *LatencyMap) Get(name string) (count int, dt time.Duration) {
m.Mutex.Lock()
l := m.stats[name]
m.Mutex.Unlock()
return l.count, l.dur
}
func (m *LatencyMap) Add(name string, dt time.Duration) {
m.Mutex.Lock()
e := m.stats[name]
if e == nil {
e = new(latencyMapEntry)
m.stats[name] = e
}
e.count++
e.dur += dt
m.Mutex.Unlock()
}
func (m *LatencyMap) Counts() map[string]int {
r := make(map[string]int)
m.Mutex.Lock()
for k, v := range m.stats {
r[k] = v.count
}
m.Mutex.Unlock()
return r
}

View File

@@ -0,0 +1,16 @@
package benchmark
import (
"testing"
"time"
)
func TestLatencyMap(t *testing.T) {
m := NewLatencyMap()
m.Add("foo", 100*time.Millisecond)
m.Add("foo", 200*time.Millisecond)
c, d := m.Get("foo")
if c != 2 || d != 300*time.Millisecond {
t.Errorf("got %v, %d, want 2, 150ms", c, d)
}
}

215
vendor/github.com/hanwen/go-fuse/benchmark/stat_test.go generated vendored Normal file
View File

@@ -0,0 +1,215 @@
package benchmark
import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"sort"
"testing"
"time"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
)
func setupFs(fs pathfs.FileSystem) (string, func()) {
opts := &nodefs.Options{
EntryTimeout: 0.0,
AttrTimeout: 0.0,
NegativeTimeout: 0.0,
}
mountPoint, _ := ioutil.TempDir("", "stat_test")
nfs := pathfs.NewPathNodeFs(fs, nil)
state, _, err := nodefs.MountRoot(mountPoint, nfs.Root(), opts)
if err != nil {
panic(fmt.Sprintf("cannot mount %v", err)) // ugh - benchmark has no error methods.
}
lmap := NewLatencyMap()
state.RecordLatencies(lmap)
go state.Serve()
return mountPoint, func() {
lc, lns := lmap.Get("LOOKUP")
gc, gns := lmap.Get("GETATTR")
fmt.Printf("GETATTR %v/call n=%d, LOOKUP %v/call n=%d\n",
gns/time.Duration(gc), gc,
lns/time.Duration(lc), lc)
err := state.Unmount()
if err != nil {
log.Println("error during unmount", err)
} else {
os.RemoveAll(mountPoint)
}
}
}
func TestNewStatFs(t *testing.T) {
fs := NewStatFs()
for _, n := range []string{
"file.txt", "sub/dir/foo.txt",
"sub/dir/bar.txt", "sub/marine.txt"} {
fs.AddFile(n)
}
wd, clean := setupFs(fs)
defer clean()
names, err := ioutil.ReadDir(wd)
if err != nil {
t.Fatalf("failed: %v", err)
}
if len(names) != 2 {
t.Error("readdir /", names)
}
fi, err := os.Lstat(wd + "/sub")
if err != nil {
t.Fatalf("failed: %v", err)
}
if !fi.IsDir() {
t.Error("mode", fi)
}
names, err = ioutil.ReadDir(wd + "/sub")
if err != nil {
t.Fatalf("failed: %v", err)
}
if len(names) != 2 {
t.Error("readdir /sub", names)
}
names, err = ioutil.ReadDir(wd + "/sub/dir")
if err != nil {
t.Fatalf("failed: %v", err)
}
if len(names) != 2 {
t.Error("readdir /sub/dir", names)
}
fi, err = os.Lstat(wd + "/sub/marine.txt")
if err != nil {
t.Fatalf("failed: %v", err)
}
if fi.Mode()&os.ModeType != 0 {
t.Error("mode", fi)
}
}
func BenchmarkGoFuseThreadedStat(b *testing.B) {
b.StopTimer()
fs := NewStatFs()
fs.delay = delay
wd, _ := os.Getwd()
files := ReadLines(wd + "/testpaths.txt")
for _, fn := range files {
fs.AddFile(fn)
}
wd, clean := setupFs(fs)
defer clean()
for i, l := range files {
files[i] = filepath.Join(wd, l)
}
threads := runtime.GOMAXPROCS(0)
results := TestingBOnePass(b, threads, files)
AnalyzeBenchmarkRuns(fmt.Sprintf("Go-FUSE %d CPUs", threads), results)
}
func TestingBOnePass(b *testing.B, threads int, files []string) (results []float64) {
runtime.GC()
var before, after runtime.MemStats
runtime.ReadMemStats(&before)
todo := b.N
for todo > 0 {
if len(files) > todo {
files = files[:todo]
}
b.StartTimer()
result := BulkStat(threads, files)
todo -= len(files)
b.StopTimer()
results = append(results, result)
}
runtime.ReadMemStats(&after)
fmt.Printf("GC count %d, total GC time: %d ns/file\n",
after.NumGC-before.NumGC, (after.PauseTotalNs-before.PauseTotalNs)/uint64(b.N))
return results
}
// Add this so we can estimate impact on latency numbers.
func BenchmarkTimeNow(b *testing.B) {
for i := 0; i < b.N; i++ {
time.Now()
}
}
func BenchmarkCFuseThreadedStat(b *testing.B) {
b.StopTimer()
wd, _ := os.Getwd()
lines := ReadLines(wd + "/testpaths.txt")
unique := map[string]int{}
for _, l := range lines {
unique[l] = 1
dir, _ := filepath.Split(l)
for dir != "/" && dir != "" {
unique[dir] = 1
dir = filepath.Clean(dir)
dir, _ = filepath.Split(dir)
}
}
out := []string{}
for k := range unique {
out = append(out, k)
}
f, err := ioutil.TempFile("", "")
if err != nil {
b.Fatalf("failed: %v", err)
}
sort.Strings(out)
for _, k := range out {
f.Write([]byte(fmt.Sprintf("/%s\n", k)))
}
f.Close()
mountPoint, _ := ioutil.TempDir("", "stat_test")
cmd := exec.Command(wd+"/cstatfs",
"-o",
"entry_timeout=0.0,attr_timeout=0.0,ac_attr_timeout=0.0,negative_timeout=0.0",
mountPoint)
cmd.Env = append(os.Environ(),
fmt.Sprintf("STATFS_INPUT=%s", f.Name()),
fmt.Sprintf("STATFS_DELAY_USEC=%d", delay/time.Microsecond))
cmd.Start()
bin, err := exec.LookPath("fusermount")
if err != nil {
b.Fatalf("failed: %v", err)
}
stop := exec.Command(bin, "-u", mountPoint)
if err != nil {
b.Fatalf("failed: %v", err)
}
defer stop.Run()
for i, l := range lines {
lines[i] = filepath.Join(mountPoint, l)
}
time.Sleep(100 * time.Millisecond)
os.Lstat(mountPoint)
threads := runtime.GOMAXPROCS(0)
results := TestingBOnePass(b, threads, lines)
AnalyzeBenchmarkRuns(fmt.Sprintf("CFuse on %d CPUS", threads), results)
}

99
vendor/github.com/hanwen/go-fuse/benchmark/statfs.cc generated vendored Normal file
View File

@@ -0,0 +1,99 @@
// +build !cgo
// g++ -Wall `pkg-config fuse --cflags --libs` statfs.cc -o statfs
#include <unordered_map>
#include <string>
using std::string;
using std::unordered_map;
#define FUSE_USE_VERSION 26
extern "C" {
#include <fuse.h>
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
useconds_t delay_usec;
class StatFs {
public:
void readFrom(const string& fn);
unordered_map<string, bool> is_dir_;
int GetAttr(const char *name, struct stat *statbuf) {
if (strcmp(name, "/") == 0) {
statbuf->st_mode = S_IFDIR | 0777;
return 0;
}
unordered_map<string, bool>::const_iterator it(is_dir_.find(name));
if (it == is_dir_.end()) {
return -ENOENT;
}
if (it->second) {
statbuf->st_mode = S_IFDIR | 0777;
} else {
statbuf->st_nlink = 1;
statbuf->st_mode = S_IFREG | 0666;
}
if (delay_usec > 0) {
usleep(delay_usec);
}
return 0;
}
};
StatFs *global;
int global_getattr(const char *name, struct stat *statbuf) {
return global->GetAttr(name, statbuf);
}
void StatFs::readFrom(const string& fn) {
FILE *f = fopen(fn.c_str(), "r");
char line[1024];
while (char *s = fgets(line, sizeof(line), f)) {
int l = strlen(s);
if (line[l-1] == '\n') {
line[l-1] = '\0';
l--;
}
bool is_dir = line[l-1] == '/';
if (is_dir) {
line[l-1] = '\0';
}
is_dir_[line] = is_dir;
}
fclose(f);
}
int main(int argc, char *argv[])
{
global = new StatFs;
// don't want to know about fuselib's option handling
char *in = getenv("STATFS_INPUT");
if (!in || !*in) {
fprintf(stderr, "pass file in $STATFS_INPUT\n");
exit(2);
}
global->readFrom(in);
in = getenv("STATFS_DELAY_USEC");
if (in != NULL) {
delay_usec = atoi(in);
}
struct fuse_operations statfs_oper = {0};
statfs_oper.getattr = &global_getattr;
return fuse_main(argc, argv, &statfs_oper, NULL);
}

69
vendor/github.com/hanwen/go-fuse/benchmark/statfs.go generated vendored Normal file
View File

@@ -0,0 +1,69 @@
package benchmark
import (
"path/filepath"
"strings"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/pathfs"
)
var delay = 0 * time.Microsecond
type StatFs struct {
pathfs.FileSystem
entries map[string]*fuse.Attr
dirs map[string][]fuse.DirEntry
delay time.Duration
}
func (me *StatFs) Add(name string, a *fuse.Attr) {
name = strings.TrimRight(name, "/")
_, ok := me.entries[name]
if ok {
return
}
me.entries[name] = a
if name == "/" || name == "" {
return
}
dir, base := filepath.Split(name)
dir = strings.TrimRight(dir, "/")
me.dirs[dir] = append(me.dirs[dir], fuse.DirEntry{Name: base, Mode: a.Mode})
me.Add(dir, &fuse.Attr{Mode: fuse.S_IFDIR | 0755})
}
func (me *StatFs) AddFile(name string) {
me.Add(name, &fuse.Attr{Mode: fuse.S_IFREG | 0644})
}
func (me *StatFs) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
e := me.entries[name]
if e == nil {
return nil, fuse.ENOENT
}
if me.delay > 0 {
time.Sleep(me.delay)
}
return e, fuse.OK
}
func (me *StatFs) OpenDir(name string, context *fuse.Context) (stream []fuse.DirEntry, status fuse.Status) {
entries := me.dirs[name]
if entries == nil {
return nil, fuse.ENOENT
}
return entries, fuse.OK
}
func NewStatFs() *StatFs {
return &StatFs{
FileSystem: pathfs.NewDefaultFileSystem(),
entries: make(map[string]*fuse.Attr),
dirs: make(map[string][]fuse.DirEntry),
}
}

1000
vendor/github.com/hanwen/go-fuse/benchmark/testpaths.txt generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,70 @@
package main
import (
"flag"
"fmt"
"os"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/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)
}

76
vendor/github.com/hanwen/go-fuse/example/benchmark.sh generated vendored Normal file
View File

@@ -0,0 +1,76 @@
#!/bin/sh
# Runtime is typically dominated by the costs of GetAttr (ie. Stat),
# so let's time that. We use zipfs which runs from memory to minimize
# noise due to the filesystem itself.
if [ "$1" == "" ] ; then
echo "Usage: benchmark.sh ZIPFILE"
echo "The zipfile should be large (> 10000 files)."
exit 2
fi
set -eux
ZIPFILE=$1
shift
CPU_COUNT=$(grep '^processor' /proc/cpuinfo | wc -l)
export GOMAXPROCS=${CPU_COUNT}
DELAY=5
gomake -C zipfs
gomake -C bulkstat
MP=/tmp/zipbench
fusermount -u ${MP} || true
mkdir -p ${MP}
ZIPFS=$PWD/zipfs/zipfs
BULKSTAT="$PWD/bulkstat/bulkstat -threads ${CPU_COUNT}"
cd /tmp
${ZIPFS} ${MP} ${ZIPFILE} >& zipfs.log &
# Wait for FS to mount.
sleep ${DELAY}
find ${MP} > /tmp/zipfiles.txt
fusermount -u ${MP}
# Run vanilla: block box measurement.
${ZIPFS} ${MP} ${ZIPFILE} >& zipfs.log &
# Wait for zipfs to unpack and serve the file.
sleep ${DELAY}
# Performance number without 6prof running
echo -e "\n\n"
${BULKSTAT} -runs 5 /tmp/zipfiles.txt
echo -e "\n\n"
# Run 6prof
6prof -p $! -d 20 -t 3 -hs -l -h -f >& /tmp/zipfs.6prof &
sleep 0.1
# Feed data to 6prof
${BULKSTAT} -runs 3 /tmp/zipfiles.txt
echo -e "\n\n"
fusermount -u ${MP}
# Now run with internal monitoring.
${ZIPFS} -latencies ${MP} ${ZIPFILE} >& zipfs.log &
sleep ${DELAY}
# Measurements.
${BULKSTAT} -runs 5 /tmp/zipfiles.txt
# Dump internal measurements.
cat ${MP}/.debug/*

61
vendor/github.com/hanwen/go-fuse/example/hello/main.go generated vendored Normal file
View File

@@ -0,0 +1,61 @@
// A Go mirror of libfuse's hello.c
package main
import (
"flag"
"log"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
)
type HelloFs struct {
pathfs.FileSystem
}
func (me *HelloFs) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
switch name {
case "file.txt":
return &fuse.Attr{
Mode: fuse.S_IFREG | 0644, Size: uint64(len(name)),
}, fuse.OK
case "":
return &fuse.Attr{
Mode: fuse.S_IFDIR | 0755,
}, fuse.OK
}
return nil, fuse.ENOENT
}
func (me *HelloFs) OpenDir(name string, context *fuse.Context) (c []fuse.DirEntry, code fuse.Status) {
if name == "" {
c = []fuse.DirEntry{{Name: "file.txt", Mode: fuse.S_IFREG}}
return c, fuse.OK
}
return nil, fuse.ENOENT
}
func (me *HelloFs) Open(name string, flags uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) {
if name != "file.txt" {
return nil, fuse.ENOENT
}
if flags&fuse.O_ANYWRITE != 0 {
return nil, fuse.EPERM
}
return nodefs.NewDataFile([]byte(name)), fuse.OK
}
func main() {
flag.Parse()
if len(flag.Args()) < 1 {
log.Fatal("Usage:\n hello MOUNTPOINT")
}
nfs := pathfs.NewPathNodeFs(&HelloFs{FileSystem: pathfs.NewDefaultFileSystem()}, nil)
server, _, err := nodefs.MountRoot(flag.Arg(0), nfs.Root(), nil)
if err != nil {
log.Fatalf("Mount fail: %v\n", err)
}
server.Serve()
}

View File

@@ -0,0 +1,109 @@
// Mounts another directory as loopback for testing and benchmarking
// purposes.
package main
import (
"flag"
"fmt"
"log"
"os"
"os/signal"
"path"
"path/filepath"
"runtime/pprof"
"syscall"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
)
func writeMemProfile(fn string, sigs <-chan os.Signal) {
i := 0
for range sigs {
fn := fmt.Sprintf("%s-%d.memprof", fn, i)
i++
log.Printf("Writing mem profile to %s\n", fn)
f, err := os.Create(fn)
if err != nil {
log.Printf("Create: %v", err)
continue
}
pprof.WriteHeapProfile(f)
if err := f.Close(); err != nil {
log.Printf("close %v", err)
}
}
}
func main() {
log.SetFlags(log.Lmicroseconds)
// Scans the arg list and sets up flags
debug := flag.Bool("debug", false, "print debugging messages.")
other := flag.Bool("allow-other", false, "mount with -o allowother.")
enableLinks := flag.Bool("l", false, "Enable hard link support")
cpuprofile := flag.String("cpuprofile", "", "write cpu profile to this file")
memprofile := flag.String("memprofile", "", "write memory profile to this file")
flag.Parse()
if flag.NArg() < 2 {
fmt.Printf("usage: %s MOUNTPOINT ORIGINAL\n", path.Base(os.Args[0]))
fmt.Printf("\noptions:\n")
flag.PrintDefaults()
os.Exit(2)
}
if *cpuprofile != "" {
fmt.Printf("Writing cpu profile to %s\n", *cpuprofile)
f, err := os.Create(*cpuprofile)
if err != nil {
fmt.Println(err)
os.Exit(3)
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
if *memprofile != "" {
log.Printf("send SIGUSR1 to %d to dump memory profile", os.Getpid())
profSig := make(chan os.Signal, 1)
signal.Notify(profSig, syscall.SIGUSR1)
go writeMemProfile(*memprofile, profSig)
}
if *cpuprofile != "" || *memprofile != "" {
fmt.Printf("Note: You must unmount gracefully, otherwise the profile file(s) will stay empty!\n")
}
var finalFs pathfs.FileSystem
orig := flag.Arg(1)
loopbackfs := pathfs.NewLoopbackFileSystem(orig)
finalFs = loopbackfs
opts := &nodefs.Options{
// These options are to be compatible with libfuse defaults,
// making benchmarking easier.
NegativeTimeout: time.Second,
AttrTimeout: time.Second,
EntryTimeout: time.Second,
}
// Enable ClientInodes so hard links work
pathFsOpts := &pathfs.PathNodeFsOptions{ClientInodes: *enableLinks}
pathFs := pathfs.NewPathNodeFs(finalFs, pathFsOpts)
conn := nodefs.NewFileSystemConnector(pathFs.Root(), opts)
mountPoint := flag.Arg(0)
origAbs, _ := filepath.Abs(orig)
mOpts := &fuse.MountOptions{
AllowOther: *other,
Name: "loopbackfs",
FsName: origAbs,
Debug: *debug,
}
state, err := fuse.NewServer(conn.RawFS(), mountPoint, mOpts)
if err != nil {
fmt.Printf("Mount fail: %v\n", err)
os.Exit(1)
}
fmt.Println("Mounted!")
state.Serve()
}

37
vendor/github.com/hanwen/go-fuse/example/memfs/main.go generated vendored Normal file
View File

@@ -0,0 +1,37 @@
// Mounts MemNodeFs for testing purposes.
package main
import (
"flag"
"fmt"
"os"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
)
func main() {
// Scans the arg list and sets up flags
debug := flag.Bool("debug", false, "print debugging messages.")
flag.Parse()
if flag.NArg() < 2 {
// TODO - where to get program name?
fmt.Println("usage: main MOUNTPOINT BACKING-PREFIX")
os.Exit(2)
}
mountPoint := flag.Arg(0)
prefix := flag.Arg(1)
root := nodefs.NewMemNodeFSRoot(prefix)
conn := nodefs.NewFileSystemConnector(root, nil)
server, err := fuse.NewServer(conn.RawFS(), mountPoint, &fuse.MountOptions{
Debug: *debug,
})
if err != nil {
fmt.Printf("Mount fail: %v\n", err)
os.Exit(1)
}
fmt.Println("Mounted!")
server.Serve()
}

View File

@@ -0,0 +1,35 @@
package main
import (
"flag"
"fmt"
"os"
"path/filepath"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/zipfs"
)
func main() {
// Scans the arg list and sets up flags
debug := flag.Bool("debug", false, "debug on")
flag.Parse()
if flag.NArg() < 1 {
_, prog := filepath.Split(os.Args[0])
fmt.Printf("usage: %s MOUNTPOINT\n", prog)
os.Exit(2)
}
fs := zipfs.NewMultiZipFs()
nfs := pathfs.NewPathNodeFs(fs, nil)
opts := nodefs.NewOptions()
opts.Debug = *debug
state, _, err := nodefs.MountRoot(flag.Arg(0), nfs.Root(), opts)
if err != nil {
fmt.Printf("Mount fail: %v\n", err)
os.Exit(1)
}
state.Serve()
}

View File

@@ -0,0 +1,81 @@
package main
import (
"flag"
"fmt"
"io"
"log"
"os"
"os/exec"
"runtime"
"runtime/pprof"
"strings"
"time"
"github.com/hanwen/go-fuse/benchmark"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
)
func main() {
// Scans the arg list and sets up flags
debug := flag.Bool("debug", false, "print debugging messages.")
profile := flag.String("profile", "", "record cpu profile.")
mem_profile := flag.String("mem-profile", "", "record memory profile.")
command := flag.String("run", "", "run this command after mounting.")
ttl := flag.Float64("ttl", 1.0, "attribute/entry cache TTL.")
flag.Parse()
if flag.NArg() < 2 {
fmt.Fprintf(os.Stderr, "usage: %s MOUNTPOINT FILENAMES-FILE\n", os.Args[0])
os.Exit(2)
}
var profFile, memProfFile io.Writer
var err error
if *profile != "" {
profFile, err = os.Create(*profile)
if err != nil {
log.Fatalf("os.Create: %v", err)
}
}
if *mem_profile != "" {
memProfFile, err = os.Create(*mem_profile)
if err != nil {
log.Fatalf("os.Create: %v", err)
}
}
fs := benchmark.NewStatFs()
lines := benchmark.ReadLines(flag.Arg(1))
for _, l := range lines {
fs.AddFile(l)
}
nfs := pathfs.NewPathNodeFs(fs, nil)
opts := &nodefs.Options{
AttrTimeout: time.Duration(*ttl * float64(time.Second)),
EntryTimeout: time.Duration(*ttl * float64(time.Second)),
Debug: *debug,
}
state, _, err := nodefs.MountRoot(flag.Arg(0), nfs.Root(), opts)
if err != nil {
fmt.Printf("Mount fail: %v\n", err)
os.Exit(1)
}
runtime.GC()
if profFile != nil {
pprof.StartCPUProfile(profFile)
defer pprof.StopCPUProfile()
}
if *command != "" {
args := strings.Split(*command, " ")
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdout = os.Stdout
cmd.Start()
}
state.Serve()
if memProfFile != nil {
pprof.WriteHeapProfile(memProfFile)
}
}

View File

@@ -0,0 +1,58 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"time"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/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()
}

81
vendor/github.com/hanwen/go-fuse/example/zipfs/main.go generated vendored Normal file
View File

@@ -0,0 +1,81 @@
package main
import (
"flag"
"fmt"
"io"
"log"
"os"
"os/exec"
"runtime"
"runtime/pprof"
"strings"
"time"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/zipfs"
)
func main() {
// Scans the arg list and sets up flags
debug := flag.Bool("debug", false, "print debugging messages.")
profile := flag.String("profile", "", "record cpu profile.")
mem_profile := flag.String("mem-profile", "", "record memory profile.")
command := flag.String("run", "", "run this command after mounting.")
ttl := flag.Float64("ttl", 1.0, "attribute/entry cache TTL.")
flag.Parse()
if flag.NArg() < 2 {
fmt.Fprintf(os.Stderr, "usage: %s MOUNTPOINT ZIP-FILE\n", os.Args[0])
os.Exit(2)
}
var profFile, memProfFile io.Writer
var err error
if *profile != "" {
profFile, err = os.Create(*profile)
if err != nil {
log.Fatalf("os.Create: %v", err)
}
}
if *mem_profile != "" {
memProfFile, err = os.Create(*mem_profile)
if err != nil {
log.Fatalf("os.Create: %v", err)
}
}
root, err := zipfs.NewArchiveFileSystem(flag.Arg(1))
if err != nil {
fmt.Fprintf(os.Stderr, "NewArchiveFileSystem failed: %v\n", err)
os.Exit(1)
}
opts := &nodefs.Options{
AttrTimeout: time.Duration(*ttl * float64(time.Second)),
EntryTimeout: time.Duration(*ttl * float64(time.Second)),
Debug: *debug,
}
state, _, err := nodefs.MountRoot(flag.Arg(0), root, opts)
if err != nil {
fmt.Printf("Mount fail: %v\n", err)
os.Exit(1)
}
runtime.GC()
if profFile != nil {
pprof.StartCPUProfile(profFile)
defer pprof.StopCPUProfile()
}
if *command != "" {
args := strings.Split(*command, " ")
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdout = os.Stdout
cmd.Start()
}
state.Serve()
if memProfFile != nil {
pprof.WriteHeapProfile(memProfFile)
}
}

1
vendor/github.com/hanwen/go-fuse/fuse/.gitignore generated vendored Normal file
View File

@@ -0,0 +1 @@
version.gen.go

138
vendor/github.com/hanwen/go-fuse/fuse/api.go generated vendored Normal file
View File

@@ -0,0 +1,138 @@
// The fuse package provides APIs to implement filesystems in
// userspace. Typically, each call of the API happens in its own
// goroutine, so take care to make the file system thread-safe.
package fuse
// Types for users to implement.
// The result of Read is an array of bytes, but for performance
// reasons, we can also return data as a file-descriptor/offset/size
// tuple. If the backing store for a file is another filesystem, this
// reduces the amount of copying between the kernel and the FUSE
// server. The ReadResult interface captures both cases.
type ReadResult interface {
// Returns the raw bytes for the read, possibly using the
// passed buffer. The buffer should be larger than the return
// value from Size.
Bytes(buf []byte) ([]byte, Status)
// Size returns how many bytes this return value takes at most.
Size() int
// Done() is called after sending the data to the kernel.
Done()
}
type MountOptions struct {
AllowOther bool
// Options are passed as -o string to fusermount.
Options []string
// Default is _DEFAULT_BACKGROUND_TASKS, 12. This numbers
// controls the allowed number of requests that relate to
// async I/O. Concurrency for synchronous I/O is not limited.
MaxBackground int
// Write size to use. If 0, use default. This number is
// capped at the kernel maximum.
MaxWrite int
// If IgnoreSecurityLabels is set, all security related xattr
// requests will return NO_DATA without passing through the
// user defined filesystem. You should only set this if you
// file system implements extended attributes, and you are not
// interested in security labels.
IgnoreSecurityLabels bool // ignoring labels should be provided as a fusermount mount option.
// If given, use this buffer pool instead of the global one.
Buffers BufferPool
// If RememberInodes is set, we will never forget inodes.
// This may be useful for NFS.
RememberInodes bool
// Values shown in "df -T" and friends
// First column, "Filesystem"
FsName string
// Second column, "Type", will be shown as "fuse." + Name
Name string
// If set, wrap the file system in a single-threaded locking wrapper.
SingleThreaded bool
// If set, return ENOSYS for Getxattr calls, so the kernel does not issue any
// Xattr operations at all.
DisableXAttrs bool
// If set, print debugging information.
Debug bool
}
// RawFileSystem is an interface close to the FUSE wire protocol.
//
// Unless you really know what you are doing, you should not implement
// this, but rather the FileSystem interface; the details of getting
// interactions with open files, renames, and threading right etc. are
// somewhat tricky and not very interesting.
//
// A null implementation is provided by NewDefaultRawFileSystem.
type RawFileSystem interface {
String() string
// If called, provide debug output through the log package.
SetDebug(debug bool)
Lookup(header *InHeader, name string, out *EntryOut) (status Status)
Forget(nodeid, nlookup uint64)
// Attributes.
GetAttr(input *GetAttrIn, out *AttrOut) (code Status)
SetAttr(input *SetAttrIn, out *AttrOut) (code Status)
// Modifying structure.
Mknod(input *MknodIn, name string, out *EntryOut) (code Status)
Mkdir(input *MkdirIn, name string, out *EntryOut) (code Status)
Unlink(header *InHeader, name string) (code Status)
Rmdir(header *InHeader, name string) (code Status)
Rename(input *RenameIn, oldName string, newName string) (code Status)
Link(input *LinkIn, filename string, out *EntryOut) (code Status)
Symlink(header *InHeader, pointedTo string, linkName string, out *EntryOut) (code Status)
Readlink(header *InHeader) (out []byte, code Status)
Access(input *AccessIn) (code Status)
// Extended attributes.
GetXAttrSize(header *InHeader, attr string) (sz int, code Status)
GetXAttrData(header *InHeader, attr string) (data []byte, code Status)
ListXAttr(header *InHeader) (attributes []byte, code Status)
SetXAttr(input *SetXAttrIn, attr string, data []byte) Status
RemoveXAttr(header *InHeader, attr string) (code Status)
// File handling.
Create(input *CreateIn, name string, out *CreateOut) (code Status)
Open(input *OpenIn, out *OpenOut) (status Status)
Read(input *ReadIn, buf []byte) (ReadResult, Status)
Release(input *ReleaseIn)
Write(input *WriteIn, data []byte) (written uint32, code Status)
Flush(input *FlushIn) Status
Fsync(input *FsyncIn) (code Status)
Fallocate(input *FallocateIn) (code Status)
// Directory handling
OpenDir(input *OpenIn, out *OpenOut) (status Status)
ReadDir(input *ReadIn, out *DirEntryList) Status
ReadDirPlus(input *ReadIn, out *DirEntryList) Status
ReleaseDir(input *ReleaseIn)
FsyncDir(input *FsyncIn) (code Status)
//
StatFs(input *InHeader, out *StatfsOut) (code Status)
// This is called on processing the first request. The
// filesystem implementation can use the server argument to
// talk back to the kernel (through notify methods).
Init(*Server)
}

75
vendor/github.com/hanwen/go-fuse/fuse/attr.go generated vendored Normal file
View File

@@ -0,0 +1,75 @@
package fuse
import (
"os"
"syscall"
"time"
)
func (a *Attr) IsFifo() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFIFO }
// IsChar reports whether the FileInfo describes a character special file.
func (a *Attr) IsChar() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFCHR }
// IsDir reports whether the FileInfo describes a directory.
func (a *Attr) IsDir() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFDIR }
// IsBlock reports whether the FileInfo describes a block special file.
func (a *Attr) IsBlock() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFBLK }
// IsRegular reports whether the FileInfo describes a regular file.
func (a *Attr) IsRegular() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFREG }
// IsSymlink reports whether the FileInfo describes a symbolic link.
func (a *Attr) IsSymlink() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFLNK }
// IsSocket reports whether the FileInfo describes a socket.
func (a *Attr) IsSocket() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFSOCK }
func (a *Attr) SetTimes(access *time.Time, mod *time.Time, chstatus *time.Time) {
if access != nil {
a.Atime = uint64(access.Unix())
a.Atimensec = uint32(access.Nanosecond())
}
if mod != nil {
a.Mtime = uint64(mod.Unix())
a.Mtimensec = uint32(mod.Nanosecond())
}
if chstatus != nil {
a.Ctime = uint64(chstatus.Unix())
a.Ctimensec = uint32(chstatus.Nanosecond())
}
}
func (a *Attr) ChangeTime() time.Time {
return time.Unix(int64(a.Ctime), int64(a.Ctimensec))
}
func (a *Attr) AccessTime() time.Time {
return time.Unix(int64(a.Atime), int64(a.Atimensec))
}
func (a *Attr) ModTime() time.Time {
return time.Unix(int64(a.Mtime), int64(a.Mtimensec))
}
func ToStatT(f os.FileInfo) *syscall.Stat_t {
s, _ := f.Sys().(*syscall.Stat_t)
if s != nil {
return s
}
return nil
}
func ToAttr(f os.FileInfo) *Attr {
if f == nil {
return nil
}
s := ToStatT(f)
if s != nil {
a := &Attr{}
a.FromStat(s)
return a
}
return nil
}

22
vendor/github.com/hanwen/go-fuse/fuse/attr_darwin.go generated vendored Normal file
View File

@@ -0,0 +1,22 @@
package fuse
import (
"syscall"
)
func (a *Attr) FromStat(s *syscall.Stat_t) {
a.Ino = uint64(s.Ino)
a.Size = uint64(s.Size)
a.Blocks = uint64(s.Blocks)
a.Atime = uint64(s.Atimespec.Sec)
a.Atimensec = uint32(s.Atimespec.Nsec)
a.Mtime = uint64(s.Mtimespec.Sec)
a.Mtimensec = uint32(s.Mtimespec.Nsec)
a.Ctime = uint64(s.Ctimespec.Sec)
a.Ctimensec = uint32(s.Ctimespec.Nsec)
a.Mode = uint32(s.Mode)
a.Nlink = uint32(s.Nlink)
a.Uid = uint32(s.Uid)
a.Gid = uint32(s.Gid)
a.Rdev = uint32(s.Rdev)
}

23
vendor/github.com/hanwen/go-fuse/fuse/attr_linux.go generated vendored Normal file
View File

@@ -0,0 +1,23 @@
package fuse
import (
"syscall"
)
func (a *Attr) FromStat(s *syscall.Stat_t) {
a.Ino = uint64(s.Ino)
a.Size = uint64(s.Size)
a.Blocks = uint64(s.Blocks)
a.Atime = uint64(s.Atim.Sec)
a.Atimensec = uint32(s.Atim.Nsec)
a.Mtime = uint64(s.Mtim.Sec)
a.Mtimensec = uint32(s.Mtim.Nsec)
a.Ctime = uint64(s.Ctim.Sec)
a.Ctimensec = uint32(s.Ctim.Nsec)
a.Mode = s.Mode
a.Nlink = uint32(s.Nlink)
a.Uid = uint32(s.Uid)
a.Gid = uint32(s.Gid)
a.Rdev = uint32(s.Rdev)
a.Blksize = uint32(s.Blksize)
}

95
vendor/github.com/hanwen/go-fuse/fuse/bufferpool.go generated vendored Normal file
View File

@@ -0,0 +1,95 @@
package fuse
import (
"sync"
)
var paranoia bool
// BufferPool implements explicit memory management. It is used for
// minimizing the GC overhead of communicating with the kernel.
type BufferPool interface {
// AllocBuffer creates a buffer of at least the given size. After use,
// it should be deallocated with FreeBuffer().
AllocBuffer(size uint32) []byte
// FreeBuffer takes back a buffer if it was allocated through
// AllocBuffer. It is not an error to call FreeBuffer() on a slice
// obtained elsewhere.
FreeBuffer(slice []byte)
}
type gcBufferPool struct {
}
// NewGcBufferPool is a fallback to the standard allocation routines.
func NewGcBufferPool() BufferPool {
return &gcBufferPool{}
}
func (p *gcBufferPool) AllocBuffer(size uint32) []byte {
return make([]byte, size)
}
func (p *gcBufferPool) FreeBuffer(slice []byte) {
}
type bufferPoolImpl struct {
lock sync.Mutex
// For each page size multiple a list of slice pointers.
buffersBySize []*sync.Pool
}
// NewBufferPool returns a BufferPool implementation that that returns
// slices with capacity of a multiple of PAGESIZE, which have possibly
// been used, and may contain random contents. When using
// NewBufferPool, file system handlers may not hang on to passed-in
// buffers beyond the handler's return.
func NewBufferPool() BufferPool {
bp := new(bufferPoolImpl)
return bp
}
func (p *bufferPoolImpl) getPool(pageCount int) *sync.Pool {
p.lock.Lock()
for len(p.buffersBySize) < pageCount+1 {
p.buffersBySize = append(p.buffersBySize, nil)
}
if p.buffersBySize[pageCount] == nil {
p.buffersBySize[pageCount] = &sync.Pool{
New: func() interface{} { return make([]byte, PAGESIZE*pageCount) },
}
}
pool := p.buffersBySize[pageCount]
p.lock.Unlock()
return pool
}
func (p *bufferPoolImpl) AllocBuffer(size uint32) []byte {
sz := int(size)
if sz < PAGESIZE {
sz = PAGESIZE
}
if sz%PAGESIZE != 0 {
sz += PAGESIZE
}
pages := sz / PAGESIZE
b := p.getPool(pages).Get().([]byte)
return b[:size]
}
func (p *bufferPoolImpl) FreeBuffer(slice []byte) {
if slice == nil {
return
}
if cap(slice)%PAGESIZE != 0 || cap(slice) == 0 {
return
}
pages := cap(slice) / PAGESIZE
slice = slice[:cap(slice)]
p.getPool(pages).Put(slice)
}

View File

@@ -0,0 +1,15 @@
package fuse
import (
"testing"
)
func TestBufferPool(t *testing.T) {
bp := NewBufferPool()
size := 1500
buf := bp.AllocBuffer(uint32(size))
if len(buf) != size {
t.Errorf("Expected buffer of %d bytes, got %d bytes", size, len(buf))
}
bp.FreeBuffer(buf)
}

31
vendor/github.com/hanwen/go-fuse/fuse/constants.go generated vendored Normal file
View File

@@ -0,0 +1,31 @@
package fuse
import (
"os"
"syscall"
)
const (
FUSE_ROOT_ID = 1
FUSE_UNKNOWN_INO = 0xffffffff
CUSE_UNRESTRICTED_IOCTL = (1 << 0)
FUSE_LK_FLOCK = (1 << 0)
FUSE_IOCTL_MAX_IOV = 256
FUSE_POLL_SCHEDULE_NOTIFY = (1 << 0)
CUSE_INIT_INFO_MAX = 4096
S_IFDIR = syscall.S_IFDIR
S_IFREG = syscall.S_IFREG
S_IFLNK = syscall.S_IFLNK
S_IFIFO = syscall.S_IFIFO
CUSE_INIT = 4096
O_ANYWRITE = uint32(os.O_WRONLY | os.O_RDWR | os.O_APPEND | os.O_CREATE | os.O_TRUNC)
)

View File

@@ -0,0 +1,5 @@
package fuse
// arbitrary values
const syscall_O_LARGEFILE = 1 << 29
const syscall_O_NOATIME = 1 << 30

View File

@@ -0,0 +1,8 @@
package fuse
import (
"syscall"
)
const syscall_O_LARGEFILE = syscall.O_LARGEFILE
const syscall_O_NOATIME = syscall.O_NOATIME

148
vendor/github.com/hanwen/go-fuse/fuse/defaultraw.go generated vendored Normal file
View File

@@ -0,0 +1,148 @@
package fuse
import (
"os"
)
// NewDefaultRawFileSystem returns ENOSYS (not implemented) for all
// operations.
func NewDefaultRawFileSystem() RawFileSystem {
return (*defaultRawFileSystem)(nil)
}
type defaultRawFileSystem struct{}
func (fs *defaultRawFileSystem) Init(*Server) {
}
func (fs *defaultRawFileSystem) String() string {
return os.Args[0]
}
func (fs *defaultRawFileSystem) SetDebug(dbg bool) {
}
func (fs *defaultRawFileSystem) StatFs(header *InHeader, out *StatfsOut) Status {
return ENOSYS
}
func (fs *defaultRawFileSystem) Lookup(header *InHeader, name string, out *EntryOut) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Forget(nodeID, nlookup uint64) {
}
func (fs *defaultRawFileSystem) GetAttr(input *GetAttrIn, out *AttrOut) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Open(input *OpenIn, out *OpenOut) (status Status) {
return OK
}
func (fs *defaultRawFileSystem) SetAttr(input *SetAttrIn, out *AttrOut) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Readlink(header *InHeader) (out []byte, code Status) {
return nil, ENOSYS
}
func (fs *defaultRawFileSystem) Mknod(input *MknodIn, name string, out *EntryOut) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Mkdir(input *MkdirIn, name string, out *EntryOut) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Unlink(header *InHeader, name string) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Rmdir(header *InHeader, name string) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Symlink(header *InHeader, pointedTo string, linkName string, out *EntryOut) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Rename(input *RenameIn, oldName string, newName string) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Link(input *LinkIn, name string, out *EntryOut) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) GetXAttrSize(header *InHeader, attr string) (size int, code Status) {
return 0, ENOSYS
}
func (fs *defaultRawFileSystem) GetXAttrData(header *InHeader, attr string) (data []byte, code Status) {
return nil, ENODATA
}
func (fs *defaultRawFileSystem) SetXAttr(input *SetXAttrIn, attr string, data []byte) Status {
return ENOSYS
}
func (fs *defaultRawFileSystem) ListXAttr(header *InHeader) (data []byte, code Status) {
return nil, ENOSYS
}
func (fs *defaultRawFileSystem) RemoveXAttr(header *InHeader, attr string) Status {
return ENOSYS
}
func (fs *defaultRawFileSystem) Access(input *AccessIn) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Create(input *CreateIn, name string, out *CreateOut) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) OpenDir(input *OpenIn, out *OpenOut) (status Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Read(input *ReadIn, buf []byte) (ReadResult, Status) {
return nil, ENOSYS
}
func (fs *defaultRawFileSystem) Release(input *ReleaseIn) {
}
func (fs *defaultRawFileSystem) Write(input *WriteIn, data []byte) (written uint32, code Status) {
return 0, ENOSYS
}
func (fs *defaultRawFileSystem) Flush(input *FlushIn) Status {
return OK
}
func (fs *defaultRawFileSystem) Fsync(input *FsyncIn) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) ReadDir(input *ReadIn, l *DirEntryList) Status {
return ENOSYS
}
func (fs *defaultRawFileSystem) ReadDirPlus(input *ReadIn, l *DirEntryList) Status {
return ENOSYS
}
func (fs *defaultRawFileSystem) ReleaseDir(input *ReleaseIn) {
}
func (fs *defaultRawFileSystem) FsyncDir(input *FsyncIn) (code Status) {
return ENOSYS
}
func (fs *defaultRawFileSystem) Fallocate(in *FallocateIn) (code Status) {
return ENOSYS
}

98
vendor/github.com/hanwen/go-fuse/fuse/direntry.go generated vendored Normal file
View File

@@ -0,0 +1,98 @@
package fuse
// all of the code for DirEntryList.
import (
"fmt"
"unsafe"
)
var eightPadding [8]byte
const direntSize = int(unsafe.Sizeof(_Dirent{}))
// DirEntry is a type for PathFileSystem and NodeFileSystem to return
// directory contents in.
type DirEntry struct {
// Mode is the file's mode. Only the high bits (eg. S_IFDIR)
// are considered.
Mode uint32
// Name is the basename of the file in the directory.
Name string
}
func (d DirEntry) String() string {
return fmt.Sprintf("%o: %q", d.Mode, d.Name)
}
// DirEntryList holds the return value for READDIR and READDIRPLUS
// opcodes.
type DirEntryList struct {
buf []byte
size int
offset uint64
}
// NewDirEntryList creates a DirEntryList with the given data buffer
// and offset.
func NewDirEntryList(data []byte, off uint64) *DirEntryList {
return &DirEntryList{
buf: data[:0],
size: len(data),
offset: off,
}
}
// AddDirEntry tries to add an entry, and reports whether it
// succeeded.
func (l *DirEntryList) AddDirEntry(e DirEntry) (bool, uint64) {
return l.Add(0, e.Name, uint64(FUSE_UNKNOWN_INO), e.Mode)
}
// Add adds a direntry to the DirEntryList, returning whether it
// succeeded.
func (l *DirEntryList) Add(prefix int, name string, inode uint64, mode uint32) (bool, uint64) {
padding := (8 - len(name)&7) & 7
delta := padding + direntSize + len(name) + prefix
oldLen := len(l.buf)
newLen := delta + oldLen
if newLen > l.size {
return false, l.offset
}
l.buf = l.buf[:newLen]
oldLen += prefix
dirent := (*_Dirent)(unsafe.Pointer(&l.buf[oldLen]))
dirent.Off = l.offset + 1
dirent.Ino = inode
dirent.NameLen = uint32(len(name))
dirent.Typ = (mode & 0170000) >> 12
oldLen += direntSize
copy(l.buf[oldLen:], name)
oldLen += len(name)
if padding > 0 {
copy(l.buf[oldLen:], eightPadding[:padding])
}
l.offset = dirent.Off
return true, l.offset
}
// AddDirLookupEntry is used for ReadDirPlus. It serializes a DirEntry
// and returns the space for entry. If no space is left, returns a nil
// pointer.
func (l *DirEntryList) AddDirLookupEntry(e DirEntry) (*EntryOut, uint64) {
lastStart := len(l.buf)
ok, off := l.Add(int(unsafe.Sizeof(EntryOut{})), e.Name,
uint64(FUSE_UNKNOWN_INO), e.Mode)
if !ok {
return nil, off
}
return (*EntryOut)(unsafe.Pointer(&l.buf[lastStart])), off
}
func (l *DirEntryList) bytes() []byte {
return l.buf
}

206
vendor/github.com/hanwen/go-fuse/fuse/lockingfs.go generated vendored Normal file
View File

@@ -0,0 +1,206 @@
package fuse
import (
"fmt"
"sync"
)
////////////////////////////////////////////////////////////////
// Locking raw FS.
type lockingRawFileSystem struct {
RawFS RawFileSystem
lock sync.Mutex
}
// Returns a Wrap
func NewLockingRawFileSystem(fs RawFileSystem) RawFileSystem {
return &lockingRawFileSystem{
RawFS: fs,
}
}
func (fs *lockingRawFileSystem) FS() RawFileSystem {
return fs.RawFS
}
func (fs *lockingRawFileSystem) locked() func() {
fs.lock.Lock()
return func() { fs.lock.Unlock() }
}
func (fs *lockingRawFileSystem) Lookup(header *InHeader, name string, out *EntryOut) (code Status) {
defer fs.locked()()
return fs.RawFS.Lookup(header, name, out)
}
func (fs *lockingRawFileSystem) SetDebug(dbg bool) {
defer fs.locked()()
fs.RawFS.SetDebug(dbg)
}
func (fs *lockingRawFileSystem) Forget(nodeID uint64, nlookup uint64) {
defer fs.locked()()
fs.RawFS.Forget(nodeID, nlookup)
}
func (fs *lockingRawFileSystem) GetAttr(input *GetAttrIn, out *AttrOut) (code Status) {
defer fs.locked()()
return fs.RawFS.GetAttr(input, out)
}
func (fs *lockingRawFileSystem) Open(input *OpenIn, out *OpenOut) (status Status) {
defer fs.locked()()
return fs.RawFS.Open(input, out)
}
func (fs *lockingRawFileSystem) SetAttr(input *SetAttrIn, out *AttrOut) (code Status) {
defer fs.locked()()
return fs.RawFS.SetAttr(input, out)
}
func (fs *lockingRawFileSystem) Readlink(header *InHeader) (out []byte, code Status) {
defer fs.locked()()
return fs.RawFS.Readlink(header)
}
func (fs *lockingRawFileSystem) Mknod(input *MknodIn, name string, out *EntryOut) (code Status) {
defer fs.locked()()
return fs.RawFS.Mknod(input, name, out)
}
func (fs *lockingRawFileSystem) Mkdir(input *MkdirIn, name string, out *EntryOut) (code Status) {
defer fs.locked()()
return fs.RawFS.Mkdir(input, name, out)
}
func (fs *lockingRawFileSystem) Unlink(header *InHeader, name string) (code Status) {
defer fs.locked()()
return fs.RawFS.Unlink(header, name)
}
func (fs *lockingRawFileSystem) Rmdir(header *InHeader, name string) (code Status) {
defer fs.locked()()
return fs.RawFS.Rmdir(header, name)
}
func (fs *lockingRawFileSystem) Symlink(header *InHeader, pointedTo string, linkName string, out *EntryOut) (code Status) {
defer fs.locked()()
return fs.RawFS.Symlink(header, pointedTo, linkName, out)
}
func (fs *lockingRawFileSystem) Rename(input *RenameIn, oldName string, newName string) (code Status) {
defer fs.locked()()
return fs.RawFS.Rename(input, oldName, newName)
}
func (fs *lockingRawFileSystem) Link(input *LinkIn, name string, out *EntryOut) (code Status) {
defer fs.locked()()
return fs.RawFS.Link(input, name, out)
}
func (fs *lockingRawFileSystem) SetXAttr(input *SetXAttrIn, attr string, data []byte) Status {
defer fs.locked()()
return fs.RawFS.SetXAttr(input, attr, data)
}
func (fs *lockingRawFileSystem) GetXAttrData(header *InHeader, attr string) (data []byte, code Status) {
defer fs.locked()()
return fs.RawFS.GetXAttrData(header, attr)
}
func (fs *lockingRawFileSystem) GetXAttrSize(header *InHeader, attr string) (sz int, code Status) {
defer fs.locked()()
return fs.RawFS.GetXAttrSize(header, attr)
}
func (fs *lockingRawFileSystem) ListXAttr(header *InHeader) (data []byte, code Status) {
defer fs.locked()()
return fs.RawFS.ListXAttr(header)
}
func (fs *lockingRawFileSystem) RemoveXAttr(header *InHeader, attr string) Status {
defer fs.locked()()
return fs.RawFS.RemoveXAttr(header, attr)
}
func (fs *lockingRawFileSystem) Access(input *AccessIn) (code Status) {
defer fs.locked()()
return fs.RawFS.Access(input)
}
func (fs *lockingRawFileSystem) Create(input *CreateIn, name string, out *CreateOut) (code Status) {
defer fs.locked()()
return fs.RawFS.Create(input, name, out)
}
func (fs *lockingRawFileSystem) OpenDir(input *OpenIn, out *OpenOut) (status Status) {
defer fs.locked()()
return fs.RawFS.OpenDir(input, out)
}
func (fs *lockingRawFileSystem) Release(input *ReleaseIn) {
defer fs.locked()()
fs.RawFS.Release(input)
}
func (fs *lockingRawFileSystem) ReleaseDir(input *ReleaseIn) {
defer fs.locked()()
fs.RawFS.ReleaseDir(input)
}
func (fs *lockingRawFileSystem) Read(input *ReadIn, buf []byte) (ReadResult, Status) {
defer fs.locked()()
return fs.RawFS.Read(input, buf)
}
func (fs *lockingRawFileSystem) Write(input *WriteIn, data []byte) (written uint32, code Status) {
defer fs.locked()()
return fs.RawFS.Write(input, data)
}
func (fs *lockingRawFileSystem) Flush(input *FlushIn) Status {
defer fs.locked()()
return fs.RawFS.Flush(input)
}
func (fs *lockingRawFileSystem) Fsync(input *FsyncIn) (code Status) {
defer fs.locked()()
return fs.RawFS.Fsync(input)
}
func (fs *lockingRawFileSystem) ReadDir(input *ReadIn, out *DirEntryList) Status {
defer fs.locked()()
return fs.RawFS.ReadDir(input, out)
}
func (fs *lockingRawFileSystem) ReadDirPlus(input *ReadIn, out *DirEntryList) Status {
defer fs.locked()()
return fs.RawFS.ReadDirPlus(input, out)
}
func (fs *lockingRawFileSystem) FsyncDir(input *FsyncIn) (code Status) {
defer fs.locked()()
return fs.RawFS.FsyncDir(input)
}
func (fs *lockingRawFileSystem) Init(s *Server) {
defer fs.locked()()
fs.RawFS.Init(s)
}
func (fs *lockingRawFileSystem) StatFs(header *InHeader, out *StatfsOut) (code Status) {
defer fs.locked()()
return fs.RawFS.StatFs(header, out)
}
func (fs *lockingRawFileSystem) Fallocate(in *FallocateIn) (code Status) {
defer fs.locked()()
return fs.RawFS.Fallocate(in)
}
func (fs *lockingRawFileSystem) String() string {
defer fs.locked()()
return fmt.Sprintf("Locked(%s)", fs.RawFS.String())
}

84
vendor/github.com/hanwen/go-fuse/fuse/misc.go generated vendored Normal file
View File

@@ -0,0 +1,84 @@
// Random odds and ends.
package fuse
import (
"fmt"
"log"
"os"
"reflect"
"syscall"
"unsafe"
)
func (code Status) String() string {
if code <= 0 {
return []string{
"OK",
"NOTIFY_POLL",
"NOTIFY_INVAL_INODE",
"NOTIFY_INVAL_ENTRY",
"NOTIFY_INVAL_STORE",
"NOTIFY_INVAL_RETRIEVE",
"NOTIFY_INVAL_DELETE",
}[-code]
}
return fmt.Sprintf("%d=%v", int(code), syscall.Errno(code))
}
func (code Status) Ok() bool {
return code == OK
}
// ToStatus extracts an errno number from Go error objects. If it
// fails, it logs an error and returns ENOSYS.
func ToStatus(err error) Status {
switch err {
case nil:
return OK
case os.ErrPermission:
return EPERM
case os.ErrExist:
return Status(syscall.EEXIST)
case os.ErrNotExist:
return ENOENT
case os.ErrInvalid:
return EINVAL
}
switch t := err.(type) {
case syscall.Errno:
return Status(t)
case *os.SyscallError:
return Status(t.Err.(syscall.Errno))
case *os.PathError:
return ToStatus(t.Err)
case *os.LinkError:
return ToStatus(t.Err)
}
log.Println("can't convert error type:", err)
return ENOSYS
}
func toSlice(dest *[]byte, ptr unsafe.Pointer, byteCount uintptr) {
h := (*reflect.SliceHeader)(unsafe.Pointer(dest))
*h = reflect.SliceHeader{
Data: uintptr(ptr),
Len: int(byteCount),
Cap: int(byteCount),
}
}
func CurrentOwner() *Owner {
return &Owner{
Uid: uint32(os.Getuid()),
Gid: uint32(os.Getgid()),
}
}
func init() {
p := syscall.Getpagesize()
if p != PAGESIZE {
log.Panicf("page size incorrect: %d", p)
}
}

26
vendor/github.com/hanwen/go-fuse/fuse/misc_test.go generated vendored Normal file
View File

@@ -0,0 +1,26 @@
package fuse
import (
"os"
"syscall"
"testing"
)
func TestToStatus(t *testing.T) {
errNo := ToStatus(os.ErrPermission)
if errNo != EPERM {
t.Errorf("Wrong conversion %v != %v", errNo, syscall.EPERM)
}
e := os.NewSyscallError("syscall", syscall.EPERM)
errNo = ToStatus(e)
if errNo != EPERM {
t.Errorf("Wrong conversion %v != %v", errNo, syscall.EPERM)
}
e = os.Remove("this-file-surely-does-not-exist")
errNo = ToStatus(e)
if errNo != ENOENT {
t.Errorf("Wrong conversion %v != %v", errNo, syscall.ENOENT)
}
}

72
vendor/github.com/hanwen/go-fuse/fuse/mount_darwin.go generated vendored Normal file
View File

@@ -0,0 +1,72 @@
package fuse
import (
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
)
func openFUSEDevice() (*os.File, error) {
fs, err := filepath.Glob("/dev/osxfuse*")
if err != nil {
return nil, err
}
if len(fs) == 0 {
// TODO(hanwen): run the load_osxfuse command.
return nil, fmt.Errorf("no FUSE devices found")
}
for _, fn := range fs {
f, err := os.OpenFile(fn, os.O_RDWR, 0)
if err != nil {
continue
}
return f, nil
}
return nil, fmt.Errorf("all FUSE devices busy")
}
const bin = "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs"
func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) {
f, err := openFUSEDevice()
if err != nil {
return 0, err
}
cmd := exec.Command(bin, "-o", strings.Join(opts.optionsStrings(), ","), "-o", fmt.Sprintf("iosize=%d", opts.MaxWrite), "3", mountPoint)
cmd.ExtraFiles = []*os.File{f}
cmd.Env = append(os.Environ(), "MOUNT_FUSEFS_CALL_BY_LIB=", "MOUNT_OSXFUSE_CALL_BY_LIB=",
"MOUNT_OSXFUSE_DAEMON_PATH="+os.Args[0],
"MOUNT_FUSEFS_DAEMON_PATH="+os.Args[0])
var out, errOut bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &errOut
if err := cmd.Start(); err != nil {
f.Close()
return 0, err
}
go func() {
err := cmd.Wait()
if err != nil {
err = fmt.Errorf("mount_osxfusefs failed: %v. Stderr: %s, Stdout: %s", err, errOut.String(), out.String())
}
ready <- err
close(ready)
}()
// The finalizer for f will close its fd so we return a dup.
defer f.Close()
return syscall.Dup(int(f.Fd()))
}
func unmount(dir string) error {
return syscall.Unmount(dir, 0)
}

158
vendor/github.com/hanwen/go-fuse/fuse/mount_linux.go generated vendored Normal file
View File

@@ -0,0 +1,158 @@
package fuse
import (
"bytes"
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"syscall"
"unsafe"
)
func unixgramSocketpair() (l, r *os.File, err error) {
fd, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_SEQPACKET, 0)
if err != nil {
return nil, nil, os.NewSyscallError("socketpair",
err.(syscall.Errno))
}
l = os.NewFile(uintptr(fd[0]), "socketpair-half1")
r = os.NewFile(uintptr(fd[1]), "socketpair-half2")
return
}
// Create a FUSE FS on the specified mount point. The returned
// mount point is always absolute.
func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) {
local, remote, err := unixgramSocketpair()
if err != nil {
return
}
defer local.Close()
defer remote.Close()
bin, err := fusermountBinary()
if err != nil {
return 0, err
}
cmd := []string{bin, mountPoint}
if s := opts.optionsStrings(); len(s) > 0 {
cmd = append(cmd, "-o", strings.Join(s, ","))
}
proc, err := os.StartProcess(bin,
cmd,
&os.ProcAttr{
Env: []string{"_FUSE_COMMFD=3"},
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr, remote}})
if err != nil {
return
}
w, err := proc.Wait()
if err != nil {
return
}
if !w.Success() {
err = fmt.Errorf("fusermount exited with code %v\n", w.Sys())
return
}
fd, err = getConnection(local)
if err != nil {
return -1, err
}
close(ready)
return fd, err
}
func privilegedUnmount(mountPoint string) error {
dir, _ := filepath.Split(mountPoint)
bin, err := umountBinary()
if err != nil {
return err
}
proc, err := os.StartProcess(bin,
[]string{bin, mountPoint},
&os.ProcAttr{Dir: dir, Files: []*os.File{nil, nil, os.Stderr}})
if err != nil {
return err
}
w, err := proc.Wait()
if !w.Success() {
return fmt.Errorf("umount exited with code %v\n", w.Sys())
}
return err
}
func unmount(mountPoint string) (err error) {
if os.Geteuid() == 0 {
return privilegedUnmount(mountPoint)
}
bin, err := fusermountBinary()
if err != nil {
return err
}
errBuf := bytes.Buffer{}
cmd := exec.Command(bin, "-u", mountPoint)
cmd.Stderr = &errBuf
err = cmd.Run()
if errBuf.Len() > 0 {
return fmt.Errorf("%s (code %v)\n",
errBuf.String(), err)
}
return err
}
func getConnection(local *os.File) (int, error) {
var data [4]byte
control := make([]byte, 4*256)
// n, oobn, recvflags, from, errno - todo: error checking.
_, oobn, _, _,
err := syscall.Recvmsg(
int(local.Fd()), data[:], control[:], 0)
if err != nil {
return 0, err
}
message := *(*syscall.Cmsghdr)(unsafe.Pointer(&control[0]))
fd := *(*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(&control[0])) + syscall.SizeofCmsghdr))
if message.Type != 1 {
return 0, fmt.Errorf("getConnection: recvmsg returned wrong control type: %d", message.Type)
}
if oobn <= syscall.SizeofCmsghdr {
return 0, fmt.Errorf("getConnection: too short control message. Length: %d", oobn)
}
if fd < 0 {
return 0, fmt.Errorf("getConnection: fd < 0: %d", fd)
}
return int(fd), nil
}
// lookPathFallback - search binary in PATH and, if that fails,
// in fallbackDir. This is useful if PATH is possible empty.
func lookPathFallback(file string, fallbackDir string) (string, error) {
binPath, err := exec.LookPath(file)
if err == nil {
return binPath, nil
}
abs := path.Join(fallbackDir, file)
return exec.LookPath(abs)
}
func fusermountBinary() (string, error) {
return lookPathFallback("fusermount", "/bin")
}
func umountBinary() (string, error) {
return lookPathFallback("umount", "/bin")
}

184
vendor/github.com/hanwen/go-fuse/fuse/nodefs/api.go generated vendored Normal file
View File

@@ -0,0 +1,184 @@
// The nodefs package offers a high level API that resembles the
// kernel's idea of what an FS looks like. File systems can have
// multiple hard-links to one file, for example. It is also suited if
// the data to represent fits in memory: you can construct the
// complete file system tree at mount time
package nodefs
import (
"time"
"github.com/hanwen/go-fuse/fuse"
)
// The Node interface implements the user-defined file system
// functionality
type Node interface {
// Inode and SetInode are basic getter/setters. They are
// called by the FileSystemConnector. You get them for free by
// embedding the result of NewDefaultNode() in your node
// struct.
Inode() *Inode
SetInode(node *Inode)
// OnMount is called on the root node just after a mount is
// executed, either when the actual root is mounted, or when a
// filesystem is mounted in-process. The passed-in
// FileSystemConnector gives access to Notify methods and
// Debug settings.
OnMount(conn *FileSystemConnector)
// OnUnmount is executed just before a submount is removed,
// and when the process receives a forget for the FUSE root
// node.
OnUnmount()
// Lookup finds a child node to this node; it is only called
// for directory Nodes.
Lookup(out *fuse.Attr, name string, context *fuse.Context) (*Inode, fuse.Status)
// Deletable() should return true if this node may be discarded once
// the kernel forgets its reference.
// If it returns false, OnForget will never get called for this node. This
// is appropriate if the filesystem has no persistent backing store
// (in-memory filesystems) where discarding the node loses the stored data.
// Deletable will be called from within the treeLock critical section, so you
// cannot look at other nodes.
Deletable() bool
// OnForget is called when the kernel forgets its reference to this node and
// sends a FORGET request. It should perform cleanup and free memory as
// appropriate for the filesystem.
// OnForget is not called if the node is a directory and has children.
// This is called from within a treeLock critical section.
OnForget()
// Misc.
Access(mode uint32, context *fuse.Context) (code fuse.Status)
Readlink(c *fuse.Context) ([]byte, fuse.Status)
// Namespace operations; these are only called on directory Nodes.
// Mknod should create the node, add it to the receiver's
// inode, and return it
Mknod(name string, mode uint32, dev uint32, context *fuse.Context) (newNode *Inode, code fuse.Status)
// Mkdir should create the directory Inode, add it to the
// receiver's Inode, and return it
Mkdir(name string, mode uint32, context *fuse.Context) (newNode *Inode, code fuse.Status)
Unlink(name string, context *fuse.Context) (code fuse.Status)
Rmdir(name string, context *fuse.Context) (code fuse.Status)
// Symlink should create a child inode to the receiver, and
// return it.
Symlink(name string, content string, context *fuse.Context) (*Inode, fuse.Status)
Rename(oldName string, newParent Node, newName string, context *fuse.Context) (code fuse.Status)
// Link should return the Inode of the resulting link. In
// a POSIX conformant file system, this should add 'existing'
// to the receiver, and return the Inode corresponding to
// 'existing'.
Link(name string, existing Node, context *fuse.Context) (newNode *Inode, code fuse.Status)
// Create should return an open file, and the Inode for that file.
Create(name string, flags uint32, mode uint32, context *fuse.Context) (file File, child *Inode, code fuse.Status)
// Open opens a file, and returns a File which is associated
// with a file handle. It is OK to return (nil, OK) here. In
// that case, the Node should implement Read or Write
// directly.
Open(flags uint32, context *fuse.Context) (file File, code fuse.Status)
OpenDir(context *fuse.Context) ([]fuse.DirEntry, fuse.Status)
Read(file File, dest []byte, off int64, context *fuse.Context) (fuse.ReadResult, fuse.Status)
Write(file File, data []byte, off int64, context *fuse.Context) (written uint32, code fuse.Status)
// XAttrs
GetXAttr(attribute string, context *fuse.Context) (data []byte, code fuse.Status)
RemoveXAttr(attr string, context *fuse.Context) fuse.Status
SetXAttr(attr string, data []byte, flags int, context *fuse.Context) fuse.Status
ListXAttr(context *fuse.Context) (attrs []string, code fuse.Status)
// Attributes
GetAttr(out *fuse.Attr, file File, context *fuse.Context) (code fuse.Status)
Chmod(file File, perms uint32, context *fuse.Context) (code fuse.Status)
Chown(file File, uid uint32, gid uint32, context *fuse.Context) (code fuse.Status)
Truncate(file File, size uint64, context *fuse.Context) (code fuse.Status)
Utimens(file File, atime *time.Time, mtime *time.Time, context *fuse.Context) (code fuse.Status)
Fallocate(file File, off uint64, size uint64, mode uint32, context *fuse.Context) (code fuse.Status)
StatFs() *fuse.StatfsOut
}
// A File object is returned from FileSystem.Open and
// FileSystem.Create. Include the NewDefaultFile return value into
// the struct to inherit a null implementation.
type File interface {
// Called upon registering the filehandle in the inode.
SetInode(*Inode)
// The String method is for debug printing.
String() string
// Wrappers around other File implementations, should return
// the inner file here.
InnerFile() File
Read(dest []byte, off int64) (fuse.ReadResult, fuse.Status)
Write(data []byte, off int64) (written uint32, code fuse.Status)
// Flush is called for close() call on a file descriptor. In
// case of duplicated descriptor, it may be called more than
// once for a file.
Flush() fuse.Status
// This is called to before the file handle is forgotten. This
// method has no return value, so nothing can synchronizes on
// the call. Any cleanup that requires specific synchronization or
// could fail with I/O errors should happen in Flush instead.
Release()
Fsync(flags int) (code fuse.Status)
// The methods below may be called on closed files, due to
// concurrency. In that case, you should return EBADF.
Truncate(size uint64) fuse.Status
GetAttr(out *fuse.Attr) fuse.Status
Chown(uid uint32, gid uint32) fuse.Status
Chmod(perms uint32) fuse.Status
Utimens(atime *time.Time, mtime *time.Time) fuse.Status
Allocate(off uint64, size uint64, mode uint32) (code fuse.Status)
}
// Wrap a File return in this to set FUSE flags. Also used internally
// to store open file data.
type WithFlags struct {
File
// For debugging.
Description string
// Put FOPEN_* flags here.
FuseFlags uint32
// O_RDWR, O_TRUNCATE, etc.
OpenFlags uint32
}
// Options contains time out options for a node FileSystem. The
// default copied from libfuse and set in NewMountOptions() is
// (1s,1s,0s).
type Options struct {
EntryTimeout time.Duration
AttrTimeout time.Duration
NegativeTimeout time.Duration
// If set, replace all uids with given UID.
// NewOptions() will set this to the daemon's
// uid/gid.
*fuse.Owner
// This option exists for compatibility and is ignored.
PortableInodes bool
// If set, print debug information.
Debug bool
}

View File

@@ -0,0 +1,70 @@
package nodefs
import (
"time"
"github.com/hanwen/go-fuse/fuse"
)
type defaultFile struct{}
// NewDefaultFile returns a File instance that returns ENOSYS for
// every operation.
func NewDefaultFile() File {
return (*defaultFile)(nil)
}
func (f *defaultFile) SetInode(*Inode) {
}
func (f *defaultFile) InnerFile() File {
return nil
}
func (f *defaultFile) String() string {
return "defaultFile"
}
func (f *defaultFile) Read(buf []byte, off int64) (fuse.ReadResult, fuse.Status) {
return nil, fuse.ENOSYS
}
func (f *defaultFile) Write(data []byte, off int64) (uint32, fuse.Status) {
return 0, fuse.ENOSYS
}
func (f *defaultFile) Flush() fuse.Status {
return fuse.OK
}
func (f *defaultFile) Release() {
}
func (f *defaultFile) GetAttr(*fuse.Attr) fuse.Status {
return fuse.ENOSYS
}
func (f *defaultFile) Fsync(flags int) (code fuse.Status) {
return fuse.ENOSYS
}
func (f *defaultFile) Utimens(atime *time.Time, mtime *time.Time) fuse.Status {
return fuse.ENOSYS
}
func (f *defaultFile) Truncate(size uint64) fuse.Status {
return fuse.ENOSYS
}
func (f *defaultFile) Chown(uid uint32, gid uint32) fuse.Status {
return fuse.ENOSYS
}
func (f *defaultFile) Chmod(perms uint32) fuse.Status {
return fuse.ENOSYS
}
func (f *defaultFile) Allocate(off uint64, size uint64, mode uint32) (code fuse.Status) {
return fuse.ENOSYS
}

View File

@@ -0,0 +1,165 @@
package nodefs
import (
"time"
"github.com/hanwen/go-fuse/fuse"
)
// NewDefaultNode returns an implementation of Node that returns
// ENOSYS for all operations.
func NewDefaultNode() Node {
return &defaultNode{}
}
type defaultNode struct {
inode *Inode
}
func (fs *defaultNode) OnUnmount() {
}
func (fs *defaultNode) OnMount(conn *FileSystemConnector) {
}
func (n *defaultNode) StatFs() *fuse.StatfsOut {
return nil
}
func (n *defaultNode) SetInode(node *Inode) {
n.inode = node
}
func (n *defaultNode) Deletable() bool {
return true
}
func (n *defaultNode) Inode() *Inode {
return n.inode
}
func (n *defaultNode) OnForget() {
}
func (n *defaultNode) Lookup(out *fuse.Attr, name string, context *fuse.Context) (node *Inode, code fuse.Status) {
return nil, fuse.ENOENT
}
func (n *defaultNode) Access(mode uint32, context *fuse.Context) (code fuse.Status) {
return fuse.ENOSYS
}
func (n *defaultNode) Readlink(c *fuse.Context) ([]byte, fuse.Status) {
return nil, fuse.ENOSYS
}
func (n *defaultNode) Mknod(name string, mode uint32, dev uint32, context *fuse.Context) (newNode *Inode, code fuse.Status) {
return nil, fuse.ENOSYS
}
func (n *defaultNode) Mkdir(name string, mode uint32, context *fuse.Context) (newNode *Inode, code fuse.Status) {
return nil, fuse.ENOSYS
}
func (n *defaultNode) Unlink(name string, context *fuse.Context) (code fuse.Status) {
return fuse.ENOSYS
}
func (n *defaultNode) Rmdir(name string, context *fuse.Context) (code fuse.Status) {
return fuse.ENOSYS
}
func (n *defaultNode) Symlink(name string, content string, context *fuse.Context) (newNode *Inode, code fuse.Status) {
return nil, fuse.ENOSYS
}
func (n *defaultNode) Rename(oldName string, newParent Node, newName string, context *fuse.Context) (code fuse.Status) {
return fuse.ENOSYS
}
func (n *defaultNode) Link(name string, existing Node, context *fuse.Context) (newNode *Inode, code fuse.Status) {
return nil, fuse.ENOSYS
}
func (n *defaultNode) Create(name string, flags uint32, mode uint32, context *fuse.Context) (file File, newNode *Inode, code fuse.Status) {
return nil, nil, fuse.ENOSYS
}
func (n *defaultNode) Open(flags uint32, context *fuse.Context) (file File, code fuse.Status) {
return nil, fuse.ENOSYS
}
func (n *defaultNode) Flush(file File, openFlags uint32, context *fuse.Context) (code fuse.Status) {
return fuse.ENOSYS
}
func (n *defaultNode) OpenDir(context *fuse.Context) ([]fuse.DirEntry, fuse.Status) {
ch := n.Inode().Children()
s := make([]fuse.DirEntry, 0, len(ch))
for name, child := range ch {
if child.mountPoint != nil {
continue
}
var a fuse.Attr
code := child.Node().GetAttr(&a, nil, context)
if code.Ok() {
s = append(s, fuse.DirEntry{Name: name, Mode: a.Mode})
}
}
return s, fuse.OK
}
func (n *defaultNode) GetXAttr(attribute string, context *fuse.Context) (data []byte, code fuse.Status) {
return nil, fuse.ENODATA
}
func (n *defaultNode) RemoveXAttr(attr string, context *fuse.Context) fuse.Status {
return fuse.ENOSYS
}
func (n *defaultNode) SetXAttr(attr string, data []byte, flags int, context *fuse.Context) fuse.Status {
return fuse.ENOSYS
}
func (n *defaultNode) ListXAttr(context *fuse.Context) (attrs []string, code fuse.Status) {
return nil, fuse.ENOSYS
}
func (n *defaultNode) GetAttr(out *fuse.Attr, file File, context *fuse.Context) (code fuse.Status) {
if n.Inode().IsDir() {
out.Mode = fuse.S_IFDIR | 0755
} else {
out.Mode = fuse.S_IFREG | 0644
}
return fuse.OK
}
func (n *defaultNode) Chmod(file File, perms uint32, context *fuse.Context) (code fuse.Status) {
return fuse.ENOSYS
}
func (n *defaultNode) Chown(file File, uid uint32, gid uint32, context *fuse.Context) (code fuse.Status) {
return fuse.ENOSYS
}
func (n *defaultNode) Truncate(file File, size uint64, context *fuse.Context) (code fuse.Status) {
return fuse.ENOSYS
}
func (n *defaultNode) Utimens(file File, atime *time.Time, mtime *time.Time, context *fuse.Context) (code fuse.Status) {
return fuse.ENOSYS
}
func (n *defaultNode) Fallocate(file File, off uint64, size uint64, mode uint32, context *fuse.Context) (code fuse.Status) {
return fuse.ENOSYS
}
func (n *defaultNode) Read(file File, dest []byte, off int64, context *fuse.Context) (fuse.ReadResult, fuse.Status) {
if file != nil {
return file.Read(dest, off)
}
return nil, fuse.ENOSYS
}
func (n *defaultNode) Write(file File, data []byte, off int64, context *fuse.Context) (written uint32, code fuse.Status) {
if file != nil {
return file.Write(data, off)
}
return 0, fuse.ENOSYS
}

114
vendor/github.com/hanwen/go-fuse/fuse/nodefs/dir.go generated vendored Normal file
View File

@@ -0,0 +1,114 @@
package nodefs
import (
"log"
"sync"
"github.com/hanwen/go-fuse/fuse"
)
type connectorDir struct {
node Node
rawFS fuse.RawFileSystem
// Protect stream and lastOffset. These are written in case
// there is a seek on the directory.
mu sync.Mutex
stream []fuse.DirEntry
// lastOffset stores the last offset for a readdir. This lets
// readdir pick up changes to the directory made after opening
// it.
lastOffset uint64
}
func (d *connectorDir) ReadDir(input *fuse.ReadIn, out *fuse.DirEntryList) (code fuse.Status) {
d.mu.Lock()
defer d.mu.Unlock()
if d.stream == nil {
return fuse.OK
}
// rewinddir() should be as if reopening directory.
// TODO - test this.
if d.lastOffset > 0 && input.Offset == 0 {
d.stream, code = d.node.OpenDir((*fuse.Context)(&input.Context))
if !code.Ok() {
return code
}
}
if input.Offset > uint64(len(d.stream)) {
// This shouldn't happen, but let's not crash.
return fuse.EINVAL
}
todo := d.stream[input.Offset:]
for _, e := range todo {
if e.Name == "" {
log.Printf("got empty directory entry, mode %o.", e.Mode)
continue
}
ok, off := out.AddDirEntry(e)
d.lastOffset = off
if !ok {
break
}
}
return fuse.OK
}
func (d *connectorDir) ReadDirPlus(input *fuse.ReadIn, out *fuse.DirEntryList) (code fuse.Status) {
d.mu.Lock()
defer d.mu.Unlock()
if d.stream == nil {
return fuse.OK
}
// rewinddir() should be as if reopening directory.
if d.lastOffset > 0 && input.Offset == 0 {
d.stream, code = d.node.OpenDir((*fuse.Context)(&input.Context))
if !code.Ok() {
return code
}
}
if input.Offset > uint64(len(d.stream)) {
// This shouldn't happen, but let's not crash.
return fuse.EINVAL
}
todo := d.stream[input.Offset:]
for _, e := range todo {
if e.Name == "" {
log.Printf("got empty directory entry, mode %o.", e.Mode)
continue
}
// we have to be sure entry will fit if we try to add
// it, or we'll mess up the lookup counts.
entryDest, off := out.AddDirLookupEntry(e)
if entryDest == nil {
break
}
entryDest.Ino = uint64(fuse.FUSE_UNKNOWN_INO)
// No need to fill attributes for . and ..
if e.Name == "." || e.Name == ".." {
continue
}
// Clear entryDest before use it, some fields can be corrupted if does not set all fields in rawFS.Lookup
*entryDest = fuse.EntryOut{}
d.rawFS.Lookup(&input.InHeader, e.Name, entryDest)
d.lastOffset = off
}
return fuse.OK
}
type rawDir interface {
ReadDir(out *fuse.DirEntryList, input *fuse.ReadIn, c *fuse.Context) fuse.Status
ReadDirPlus(out *fuse.DirEntryList, input *fuse.ReadIn, c *fuse.Context) fuse.Status
}

View File

@@ -0,0 +1,125 @@
package nodefs
import (
"io/ioutil"
"testing"
"github.com/hanwen/go-fuse/fuse"
)
type nodeReadNode struct {
Node
dir bool
noOpen bool
data []byte
}
func newNodeReadNode(noOpen, dir bool, d []byte) *nodeReadNode {
return &nodeReadNode{
Node: NewDefaultNode(),
noOpen: noOpen,
dir: dir,
data: d,
}
}
func (n *nodeReadNode) Open(flags uint32, context *fuse.Context) (file File, code fuse.Status) {
if n.noOpen {
return nil, fuse.ENOSYS
}
return nil, fuse.OK
}
func (n *nodeReadNode) Read(file File, dest []byte, off int64, context *fuse.Context) (fuse.ReadResult, fuse.Status) {
e := off + int64(len(dest))
if int(e) > len(n.data) {
e = int64(len(n.data))
}
return fuse.ReadResultData(n.data[off:int(e)]), fuse.OK
}
func (n *nodeReadNode) GetAttr(out *fuse.Attr, file File, context *fuse.Context) (code fuse.Status) {
if n.dir {
out.Mode = fuse.S_IFDIR | 0755
} else {
out.Mode = fuse.S_IFREG | 0644
}
out.Size = uint64(len(n.data))
return fuse.OK
}
func (n *nodeReadNode) Lookup(out *fuse.Attr, name string, context *fuse.Context) (*Inode, fuse.Status) {
ch := n.Inode().NewChild(name, false, newNodeReadNode(n.noOpen, false, []byte(name)))
return ch, ch.Node().GetAttr(out, nil, context)
}
func TestNoOpen(t *testing.T) {
dir, err := ioutil.TempDir("", "nodefs")
if err != nil {
t.Fatalf("TempDir: %v", err)
}
root := newNodeReadNode(true, true, nil)
root.noOpen = true
s, _, err := MountRoot(dir, root, &Options{Debug: VerboseTest()})
if err != nil {
t.Fatalf("MountRoot: %v", err)
}
defer s.Unmount()
go s.Serve()
if err := s.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
if s.KernelSettings().Minor < 23 {
t.Skip("Kernel does not support open-less read/writes. Skipping test.")
}
content, err := ioutil.ReadFile(dir + "/file")
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
want := "file"
if string(content) != want {
t.Fatalf("got %q, want %q", content, want)
}
content, err = ioutil.ReadFile(dir + "/file2")
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
want = "file2"
if string(content) != want {
t.Fatalf("got %q, want %q", content, want)
}
}
func TestNodeRead(t *testing.T) {
dir, err := ioutil.TempDir("", "nodefs")
if err != nil {
t.Fatalf("TempDir: %v", err)
}
root := newNodeReadNode(false, true, nil)
opts := NewOptions()
opts.Debug = VerboseTest()
s, _, err := MountRoot(dir, root, opts)
if err != nil {
t.Fatalf("MountRoot: %v", err)
}
defer s.Unmount()
go s.Serve()
if err := s.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
content, err := ioutil.ReadFile(dir + "/file")
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
want := "file"
if string(content) != want {
t.Fatalf("got %q, want %q", content, want)
}
}

249
vendor/github.com/hanwen/go-fuse/fuse/nodefs/files.go generated vendored Normal file
View File

@@ -0,0 +1,249 @@
package nodefs
import (
"fmt"
"os"
"sync"
"syscall"
"github.com/hanwen/go-fuse/fuse"
)
// DataFile is for implementing read-only filesystems. This
// assumes we already have the data in memory.
type dataFile struct {
data []byte
File
}
func (f *dataFile) String() string {
l := len(f.data)
if l > 10 {
l = 10
}
return fmt.Sprintf("dataFile(%x)", f.data[:l])
}
func (f *dataFile) GetAttr(out *fuse.Attr) fuse.Status {
out.Mode = fuse.S_IFREG | 0644
out.Size = uint64(len(f.data))
return fuse.OK
}
func NewDataFile(data []byte) File {
f := new(dataFile)
f.data = data
f.File = NewDefaultFile()
return f
}
func (f *dataFile) Read(buf []byte, off int64) (res fuse.ReadResult, code fuse.Status) {
end := int(off) + int(len(buf))
if end > len(f.data) {
end = len(f.data)
}
return fuse.ReadResultData(f.data[off:end]), fuse.OK
}
type devNullFile struct {
File
}
// NewDevNullFile returns a file that accepts any write, and always
// returns EOF for reads.
func NewDevNullFile() File {
return &devNullFile{
File: NewDefaultFile(),
}
}
func (f *devNullFile) Allocate(off uint64, size uint64, mode uint32) (code fuse.Status) {
return fuse.OK
}
func (f *devNullFile) String() string {
return "devNullFile"
}
func (f *devNullFile) Read(buf []byte, off int64) (fuse.ReadResult, fuse.Status) {
return fuse.ReadResultData(nil), fuse.OK
}
func (f *devNullFile) Write(content []byte, off int64) (uint32, fuse.Status) {
return uint32(len(content)), fuse.OK
}
func (f *devNullFile) Flush() fuse.Status {
return fuse.OK
}
func (f *devNullFile) Fsync(flags int) (code fuse.Status) {
return fuse.OK
}
func (f *devNullFile) Truncate(size uint64) (code fuse.Status) {
return fuse.OK
}
////////////////
// LoopbackFile delegates all operations back to an underlying os.File.
func NewLoopbackFile(f *os.File) File {
return &loopbackFile{File: f}
}
type loopbackFile struct {
File *os.File
// os.File is not threadsafe. Although fd themselves are
// constant during the lifetime of an open file, the OS may
// reuse the fd number after it is closed. When open races
// with another close, they may lead to confusion as which
// file gets written in the end.
lock sync.Mutex
}
func (f *loopbackFile) InnerFile() File {
return nil
}
func (f *loopbackFile) SetInode(n *Inode) {
}
func (f *loopbackFile) String() string {
return fmt.Sprintf("loopbackFile(%s)", f.File.Name())
}
func (f *loopbackFile) Read(buf []byte, off int64) (res fuse.ReadResult, code fuse.Status) {
f.lock.Lock()
// This is not racy by virtue of the kernel properly
// synchronizing the open/write/close.
r := fuse.ReadResultFd(f.File.Fd(), off, len(buf))
f.lock.Unlock()
return r, fuse.OK
}
func (f *loopbackFile) Write(data []byte, off int64) (uint32, fuse.Status) {
f.lock.Lock()
n, err := f.File.WriteAt(data, off)
f.lock.Unlock()
return uint32(n), fuse.ToStatus(err)
}
func (f *loopbackFile) Release() {
f.lock.Lock()
f.File.Close()
f.lock.Unlock()
}
func (f *loopbackFile) Flush() fuse.Status {
f.lock.Lock()
// 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.
newFd, err := syscall.Dup(int(f.File.Fd()))
f.lock.Unlock()
if err != nil {
return fuse.ToStatus(err)
}
err = syscall.Close(newFd)
return fuse.ToStatus(err)
}
func (f *loopbackFile) Fsync(flags int) (code fuse.Status) {
f.lock.Lock()
r := fuse.ToStatus(syscall.Fsync(int(f.File.Fd())))
f.lock.Unlock()
return r
}
func (f *loopbackFile) Truncate(size uint64) fuse.Status {
f.lock.Lock()
r := fuse.ToStatus(syscall.Ftruncate(int(f.File.Fd()), int64(size)))
f.lock.Unlock()
return r
}
func (f *loopbackFile) Chmod(mode uint32) fuse.Status {
f.lock.Lock()
r := fuse.ToStatus(f.File.Chmod(os.FileMode(mode)))
f.lock.Unlock()
return r
}
func (f *loopbackFile) Chown(uid uint32, gid uint32) fuse.Status {
f.lock.Lock()
r := fuse.ToStatus(f.File.Chown(int(uid), int(gid)))
f.lock.Unlock()
return r
}
func (f *loopbackFile) GetAttr(a *fuse.Attr) fuse.Status {
st := syscall.Stat_t{}
f.lock.Lock()
err := syscall.Fstat(int(f.File.Fd()), &st)
f.lock.Unlock()
if err != nil {
return fuse.ToStatus(err)
}
a.FromStat(&st)
return fuse.OK
}
// Utimens implemented in files_linux.go
// Allocate implemented in files_linux.go
////////////////////////////////////////////////////////////////
// NewReadOnlyFile wraps a File so all read/write operations are
// denied.
func NewReadOnlyFile(f File) File {
return &readOnlyFile{File: f}
}
type readOnlyFile struct {
File
}
func (f *readOnlyFile) InnerFile() File {
return f.File
}
func (f *readOnlyFile) String() string {
return fmt.Sprintf("readOnlyFile(%s)", f.File.String())
}
func (f *readOnlyFile) Write(data []byte, off int64) (uint32, fuse.Status) {
return 0, fuse.EPERM
}
func (f *readOnlyFile) Fsync(flag int) (code fuse.Status) {
return fuse.OK
}
func (f *readOnlyFile) Truncate(size uint64) fuse.Status {
return fuse.EPERM
}
func (f *readOnlyFile) Chmod(mode uint32) fuse.Status {
return fuse.EPERM
}
func (f *readOnlyFile) Chown(uid uint32, gid uint32) fuse.Status {
return fuse.EPERM
}
func (f *readOnlyFile) Allocate(off uint64, sz uint64, mode uint32) fuse.Status {
return fuse.EPERM
}

View File

@@ -0,0 +1,99 @@
package nodefs
import (
"syscall"
"time"
"unsafe"
"github.com/hanwen/go-fuse/fuse"
)
func (f *loopbackFile) Allocate(off uint64, sz uint64, mode uint32) fuse.Status {
// TODO: Handle `mode` parameter.
// From `man fcntl` on OSX:
// The F_PREALLOCATE command operates on the following structure:
//
// typedef struct fstore {
// u_int32_t fst_flags; /* IN: flags word */
// int fst_posmode; /* IN: indicates offset field */
// off_t fst_offset; /* IN: start of the region */
// off_t fst_length; /* IN: size of the region */
// off_t fst_bytesalloc; /* OUT: number of bytes allocated */
// } fstore_t;
//
// The flags (fst_flags) for the F_PREALLOCATE command are as follows:
//
// F_ALLOCATECONTIG Allocate contiguous space.
//
// F_ALLOCATEALL Allocate all requested space or no space at all.
//
// The position modes (fst_posmode) for the F_PREALLOCATE command indicate how to use the offset field. The modes are as fol-
// lows:
//
// F_PEOFPOSMODE Allocate from the physical end of file.
//
// F_VOLPOSMODE Allocate from the volume offset.
k := struct {
Flags uint32 // u_int32_t
Posmode int64 // int
Offset int64 // off_t
Length int64 // off_t
Bytesalloc int64 // off_t
}{
0,
0,
int64(off),
int64(sz),
0,
}
// Linux version for reference:
// err := syscall.Fallocate(int(f.File.Fd()), mode, int64(off), int64(sz))
f.lock.Lock()
_, _, errno := syscall.Syscall(syscall.SYS_FCNTL, f.File.Fd(), uintptr(syscall.F_PREALLOCATE), uintptr(unsafe.Pointer(&k)))
f.lock.Unlock()
if errno != 0 {
return fuse.ToStatus(errno)
}
return fuse.OK
}
const _UTIME_NOW = ((1 << 30) - 1)
const _UTIME_OMIT = ((1 << 30) - 2)
// timeToTimeval - Convert time.Time to syscall.Timeval
//
// Note: This does not use syscall.NsecToTimespec because
// that does not work properly for times before 1970,
// see https://github.com/golang/go/issues/12777
func timeToTimeval(t *time.Time) syscall.Timeval {
var tv syscall.Timeval
tv.Usec = int32(t.Nanosecond() / 1000)
tv.Sec = t.Unix()
return tv
}
// OSX does not have the utimensat syscall neded to implement this properly.
// We do our best to emulate it using futimes.
func (f *loopbackFile) Utimens(a *time.Time, m *time.Time) fuse.Status {
tv := make([]syscall.Timeval, 2)
if a == nil {
tv[0].Usec = _UTIME_OMIT
} else {
tv[0] = timeToTimeval(a)
}
if m == nil {
tv[1].Usec = _UTIME_OMIT
} else {
tv[1] = timeToTimeval(m)
}
f.lock.Lock()
err := syscall.Futimes(int(f.File.Fd()), tv)
f.lock.Unlock()
return fuse.ToStatus(err)
}

View File

@@ -0,0 +1,45 @@
package nodefs
import (
"syscall"
"time"
"github.com/hanwen/go-fuse/fuse"
)
func (f *loopbackFile) Allocate(off uint64, sz uint64, mode uint32) fuse.Status {
f.lock.Lock()
err := syscall.Fallocate(int(f.File.Fd()), mode, int64(off), int64(sz))
f.lock.Unlock()
if err != nil {
return fuse.ToStatus(err)
}
return fuse.OK
}
const _UTIME_NOW = ((1 << 30) - 1)
const _UTIME_OMIT = ((1 << 30) - 2)
// Utimens - file handle based version of loopbackFileSystem.Utimens()
func (f *loopbackFile) Utimens(a *time.Time, m *time.Time) fuse.Status {
var ts [2]syscall.Timespec
if a == nil {
ts[0].Nsec = _UTIME_OMIT
} else {
ts[0] = syscall.NsecToTimespec(a.UnixNano())
ts[0].Nsec = 0
}
if m == nil {
ts[1].Nsec = _UTIME_OMIT
} else {
ts[1] = syscall.NsecToTimespec(a.UnixNano())
ts[1].Nsec = 0
}
f.lock.Lock()
err := futimens(int(f.File.Fd()), &ts)
f.lock.Unlock()
return fuse.ToStatus(err)
}

View File

@@ -0,0 +1,420 @@
package nodefs
// This file contains the internal logic of the
// FileSystemConnector. The functions for satisfying the raw interface
// are in fsops.go
import (
"log"
"path/filepath"
"strings"
"time"
"unsafe"
"github.com/hanwen/go-fuse/fuse"
)
// Tests should set to true.
var paranoia = false
// FilesystemConnector translates the raw FUSE protocol (serialized
// structs of uint32/uint64) to operations on Go objects representing
// files and directories.
type FileSystemConnector struct {
debug bool
// Callbacks for talking back to the kernel.
server *fuse.Server
// Translate between uint64 handles and *Inode.
inodeMap handleMap
// The root of the FUSE file system.
rootNode *Inode
}
// NewOptions generates FUSE options that correspond to libfuse's
// defaults.
func NewOptions() *Options {
return &Options{
NegativeTimeout: 0,
AttrTimeout: time.Second,
EntryTimeout: time.Second,
Owner: fuse.CurrentOwner(),
}
}
// NewFileSystemConnector creates a FileSystemConnector with the given
// options.
func NewFileSystemConnector(root Node, opts *Options) (c *FileSystemConnector) {
c = new(FileSystemConnector)
if opts == nil {
opts = NewOptions()
}
c.inodeMap = newPortableHandleMap()
c.rootNode = newInode(true, root)
c.verify()
c.mountRoot(opts)
// FUSE does not issue a LOOKUP for 1 (obviously), but it does
// issue a forget. This lookupUpdate is to make the counts match.
c.lookupUpdate(c.rootNode)
c.debug = opts.Debug
return c
}
// Server returns the fuse.Server that talking to the kernel.
func (c *FileSystemConnector) Server() *fuse.Server {
return c.server
}
// SetDebug toggles printing of debug information. This function is
// deprecated. Set the Debug option in the Options struct instead.
func (c *FileSystemConnector) SetDebug(debug bool) {
c.debug = debug
}
// This verifies invariants of the data structure. This routine
// acquires tree locks as it walks the inode tree.
func (c *FileSystemConnector) verify() {
if !paranoia {
return
}
root := c.rootNode
root.verify(c.rootNode.mountPoint)
}
// childLookup fills entry information for a newly created child inode
func (c *rawBridge) childLookup(out *fuse.EntryOut, n *Inode, context *fuse.Context) {
n.Node().GetAttr((*fuse.Attr)(&out.Attr), nil, context)
n.mount.fillEntry(out)
out.NodeId, out.Generation = c.fsConn().lookupUpdate(n)
if out.Ino == 0 {
out.Ino = out.NodeId
}
if out.Nlink == 0 {
// With Nlink == 0, newer kernels will refuse link
// operations.
out.Nlink = 1
}
}
func (c *rawBridge) toInode(nodeid uint64) *Inode {
if nodeid == fuse.FUSE_ROOT_ID {
return c.rootNode
}
i := (*Inode)(unsafe.Pointer(c.inodeMap.Decode(nodeid)))
return i
}
// Must run outside treeLock. Returns the nodeId and generation.
func (c *FileSystemConnector) lookupUpdate(node *Inode) (id, generation uint64) {
id, generation = c.inodeMap.Register(&node.handled)
c.verify()
return
}
// forgetUpdate decrements the reference counter for "nodeID" by "forgetCount".
// Must run outside treeLock.
func (c *FileSystemConnector) forgetUpdate(nodeID uint64, forgetCount int) {
if nodeID == fuse.FUSE_ROOT_ID {
c.rootNode.Node().OnUnmount()
// We never got a lookup for root, so don't try to
// forget root.
return
}
// Prevent concurrent modification of the tree while we are processing
// the FORGET
node := (*Inode)(unsafe.Pointer(c.inodeMap.Decode(nodeID)))
node.mount.treeLock.Lock()
defer node.mount.treeLock.Unlock()
if forgotten, _ := c.inodeMap.Forget(nodeID, forgetCount); forgotten {
if len(node.children) > 0 || !node.Node().Deletable() ||
node == c.rootNode || node.mountPoint != nil {
// We cannot forget a directory that still has children as these
// would become unreachable.
return
}
// We have to remove ourself from all parents.
// Create a copy of node.parents so we can safely iterate over it
// while modifying the original.
parents := make(map[parentData]struct{}, len(node.parents))
for k, v := range node.parents {
parents[k] = v
}
for p := range parents {
// This also modifies node.parents
p.parent.rmChild(p.name)
}
node.fsInode.OnForget()
}
// TODO - try to drop children even forget was not successful.
c.verify()
}
// InodeCount returns the number of inodes registered with the kernel.
func (c *FileSystemConnector) InodeHandleCount() int {
return c.inodeMap.Count()
}
// Finds a node within the currently known inodes, returns the last
// known node and the remaining unknown path components. If parent is
// nil, start from FUSE mountpoint.
func (c *FileSystemConnector) Node(parent *Inode, fullPath string) (*Inode, []string) {
if parent == nil {
parent = c.rootNode
}
if fullPath == "" {
return parent, nil
}
sep := string(filepath.Separator)
fullPath = strings.TrimLeft(filepath.Clean(fullPath), sep)
comps := strings.Split(fullPath, sep)
node := parent
if node.mountPoint == nil {
node.mount.treeLock.RLock()
defer node.mount.treeLock.RUnlock()
}
for i, component := range comps {
if len(component) == 0 {
continue
}
if node.mountPoint != nil {
node.mount.treeLock.RLock()
defer node.mount.treeLock.RUnlock()
}
next := node.children[component]
if next == nil {
return node, comps[i:]
}
node = next
}
return node, nil
}
// Follows the path from the given parent, doing lookups as
// necesary. The path should be '/' separated without leading slash.
func (c *FileSystemConnector) LookupNode(parent *Inode, path string) *Inode {
if path == "" {
return parent
}
components := strings.Split(path, "/")
for _, r := range components {
var a fuse.Attr
// This will not affect inode ID lookup counts, which
// are only update in response to kernel requests.
var dummy fuse.InHeader
child, _ := c.internalLookup(&a, parent, r, &dummy)
if child == nil {
return nil
}
parent = child
}
return parent
}
func (c *FileSystemConnector) mountRoot(opts *Options) {
c.rootNode.mountFs(opts)
c.rootNode.mount.connector = c
c.verify()
}
// Mount() generates a synthetic directory node, and mounts the file
// system there. If opts is nil, the mount options of the root file
// system are inherited. The encompassing filesystem should pretend
// the mount point does not exist.
//
// It returns ENOENT if the directory containing the mount point does
// not exist, and EBUSY if the intended mount point already exists.
func (c *FileSystemConnector) Mount(parent *Inode, name string, root Node, opts *Options) fuse.Status {
node, code := c.lockMount(parent, name, root, opts)
if !code.Ok() {
return code
}
node.Node().OnMount(c)
return code
}
func (c *FileSystemConnector) lockMount(parent *Inode, name string, root Node, opts *Options) (*Inode, fuse.Status) {
defer c.verify()
parent.mount.treeLock.Lock()
defer parent.mount.treeLock.Unlock()
node := parent.children[name]
if node != nil {
return nil, fuse.EBUSY
}
node = newInode(true, root)
if opts == nil {
opts = c.rootNode.mountPoint.options
}
node.mountFs(opts)
node.mount.connector = c
parent.addChild(name, node)
node.mountPoint.parentInode = parent
if c.debug {
log.Printf("Mount %T on subdir %s, parent %d", node,
name, c.inodeMap.Handle(&parent.handled))
}
return node, fuse.OK
}
// Unmount() tries to unmount the given inode. It returns EINVAL if the
// path does not exist, or is not a mount point, and EBUSY if there
// are open files or submounts below this node.
func (c *FileSystemConnector) Unmount(node *Inode) fuse.Status {
// TODO - racy.
if node.mountPoint == nil {
log.Println("not a mountpoint:", c.inodeMap.Handle(&node.handled))
return fuse.EINVAL
}
nodeID := c.inodeMap.Handle(&node.handled)
// Must lock parent to update tree structure.
parentNode := node.mountPoint.parentInode
parentNode.mount.treeLock.Lock()
defer parentNode.mount.treeLock.Unlock()
mount := node.mountPoint
name := node.mountPoint.mountName()
if mount.openFiles.Count() > 0 {
return fuse.EBUSY
}
node.mount.treeLock.Lock()
defer node.mount.treeLock.Unlock()
if mount.mountInode != node {
log.Panicf("got two different mount inodes %v vs %v",
c.inodeMap.Handle(&mount.mountInode.handled),
c.inodeMap.Handle(&node.handled))
}
if !node.canUnmount() {
return fuse.EBUSY
}
delete(parentNode.children, name)
node.Node().OnUnmount()
parentId := c.inodeMap.Handle(&parentNode.handled)
if parentNode == c.rootNode {
// TODO - test coverage. Currently covered by zipfs/multizip_test.go
parentId = fuse.FUSE_ROOT_ID
}
// We have to wait until the kernel has forgotten the
// mountpoint, so the write to node.mountPoint is no longer
// racy.
mount.treeLock.Unlock()
parentNode.mount.treeLock.Unlock()
code := c.server.DeleteNotify(parentId, nodeID, name)
if code.Ok() {
delay := 100 * time.Microsecond
for {
// This operation is rare, so we kludge it to avoid
// contention.
time.Sleep(delay)
delay = delay * 2
if !c.inodeMap.Has(nodeID) {
break
}
if delay >= time.Second {
// We limit the wait at one second. If
// it takes longer, something else is
// amiss, and we would be waiting forever.
log.Println("kernel did not issue FORGET for node on Unmount.")
break
}
}
}
parentNode.mount.treeLock.Lock()
mount.treeLock.Lock()
mount.mountInode = nil
node.mountPoint = nil
return fuse.OK
}
// FileNotify notifies the kernel that data and metadata of this inode
// has changed. After this call completes, the kernel will issue a
// new GetAttr requests for metadata and new Read calls for content.
// Use negative offset for metadata-only invalidation, and zero-length
// for invalidating all content.
func (c *FileSystemConnector) FileNotify(node *Inode, off int64, length int64) fuse.Status {
var nId uint64
if node == c.rootNode {
nId = fuse.FUSE_ROOT_ID
} else {
nId = c.inodeMap.Handle(&node.handled)
}
if nId == 0 {
return fuse.OK
}
return c.server.InodeNotify(nId, off, length)
}
// EntryNotify makes the kernel forget the entry data from the given
// name from a directory. After this call, the kernel will issue a
// new lookup request for the given name when necessary. No filesystem
// related locks should be held when calling this.
func (c *FileSystemConnector) EntryNotify(node *Inode, name string) fuse.Status {
var nId uint64
if node == c.rootNode {
nId = fuse.FUSE_ROOT_ID
} else {
nId = c.inodeMap.Handle(&node.handled)
}
if nId == 0 {
return fuse.OK
}
return c.server.EntryNotify(nId, name)
}
// DeleteNotify signals to the kernel that the named entry in dir for
// the child disappeared. No filesystem related locks should be held
// when calling this.
func (c *FileSystemConnector) DeleteNotify(dir *Inode, child *Inode, name string) fuse.Status {
var nId uint64
if dir == c.rootNode {
nId = fuse.FUSE_ROOT_ID
} else {
nId = c.inodeMap.Handle(&dir.handled)
}
if nId == 0 {
return fuse.OK
}
chId := c.inodeMap.Handle(&child.handled)
return c.server.DeleteNotify(nId, chId, name)
}

154
vendor/github.com/hanwen/go-fuse/fuse/nodefs/fsmount.go generated vendored Normal file
View File

@@ -0,0 +1,154 @@
package nodefs
import (
"log"
"sync"
"unsafe"
"github.com/hanwen/go-fuse/fuse"
)
// openedFile stores either an open dir or an open file.
type openedFile struct {
handled
WithFlags
dir *connectorDir
}
type fileSystemMount struct {
// Node that we were mounted on.
mountInode *Inode
// Parent to the mountInode.
parentInode *Inode
// Options for the mount.
options *Options
// Protects the "children" and "parents" hashmaps of the inodes
// within the mount.
// treeLock should be acquired before openFilesLock.
//
// If multiple treeLocks must be acquired, the treeLocks
// closer to the root must be acquired first.
treeLock sync.RWMutex
// Manage filehandles of open files.
openFiles handleMap
Debug bool
connector *FileSystemConnector
}
// Must called with lock for parent held.
func (m *fileSystemMount) mountName() string {
for k, v := range m.parentInode.children {
if m.mountInode == v {
return k
}
}
panic("not found")
}
func (m *fileSystemMount) setOwner(attr *fuse.Attr) {
if m.options.Owner != nil {
attr.Owner = *(*fuse.Owner)(m.options.Owner)
}
}
func (m *fileSystemMount) fillEntry(out *fuse.EntryOut) {
splitDuration(m.options.EntryTimeout, &out.EntryValid, &out.EntryValidNsec)
splitDuration(m.options.AttrTimeout, &out.AttrValid, &out.AttrValidNsec)
m.setOwner(&out.Attr)
if out.Mode&fuse.S_IFDIR == 0 && out.Nlink == 0 {
out.Nlink = 1
}
}
func (m *fileSystemMount) fillAttr(out *fuse.AttrOut, nodeId uint64) {
splitDuration(m.options.AttrTimeout, &out.AttrValid, &out.AttrValidNsec)
m.setOwner(&out.Attr)
if out.Ino == 0 {
out.Ino = nodeId
}
}
func (m *fileSystemMount) getOpenedFile(h uint64) *openedFile {
var b *openedFile
if h != 0 {
b = (*openedFile)(unsafe.Pointer(m.openFiles.Decode(h)))
}
if b != nil && m.connector.debug && b.WithFlags.Description != "" {
log.Printf("File %d = %q", h, b.WithFlags.Description)
}
return b
}
func (m *fileSystemMount) unregisterFileHandle(handle uint64, node *Inode) *openedFile {
_, obj := m.openFiles.Forget(handle, 1)
opened := (*openedFile)(unsafe.Pointer(obj))
node.openFilesMutex.Lock()
idx := -1
for i, v := range node.openFiles {
if v == opened {
idx = i
break
}
}
l := len(node.openFiles)
if idx == l-1 {
node.openFiles[idx] = nil
} else {
node.openFiles[idx] = node.openFiles[l-1]
}
node.openFiles = node.openFiles[:l-1]
node.openFilesMutex.Unlock()
return opened
}
func (m *fileSystemMount) registerFileHandle(node *Inode, dir *connectorDir, f File, flags uint32) (uint64, *openedFile) {
node.openFilesMutex.Lock()
b := &openedFile{
dir: dir,
WithFlags: WithFlags{
File: f,
OpenFlags: flags,
},
}
for {
withFlags, ok := f.(*WithFlags)
if !ok {
break
}
b.WithFlags.File = withFlags.File
b.WithFlags.FuseFlags |= withFlags.FuseFlags
b.WithFlags.Description += withFlags.Description
f = withFlags.File
}
if b.WithFlags.File != nil {
b.WithFlags.File.SetInode(node)
}
node.openFiles = append(node.openFiles, b)
handle, _ := m.openFiles.Register(&b.handled)
node.openFilesMutex.Unlock()
return handle, b
}
// Creates a return entry for a non-existent path.
func (m *fileSystemMount) negativeEntry(out *fuse.EntryOut) bool {
if m.options.NegativeTimeout > 0.0 {
out.NodeId = 0
splitDuration(m.options.NegativeTimeout, &out.EntryValid, &out.EntryValidNsec)
return true
}
return false
}

472
vendor/github.com/hanwen/go-fuse/fuse/nodefs/fsops.go generated vendored Normal file
View File

@@ -0,0 +1,472 @@
package nodefs
// This file contains FileSystemConnector's implementation of
// RawFileSystem
import (
"bytes"
"fmt"
"log"
"strings"
"time"
"github.com/hanwen/go-fuse/fuse"
)
// Returns the RawFileSystem so it can be mounted.
func (c *FileSystemConnector) RawFS() fuse.RawFileSystem {
return (*rawBridge)(c)
}
type rawBridge FileSystemConnector
func (c *rawBridge) Fsync(input *fuse.FsyncIn) fuse.Status {
node := c.toInode(input.NodeId)
opened := node.mount.getOpenedFile(input.Fh)
if opened != nil {
return opened.WithFlags.File.Fsync(int(input.FsyncFlags))
}
return fuse.ENOSYS
}
func (c *rawBridge) SetDebug(debug bool) {
c.fsConn().SetDebug(debug)
}
func (c *rawBridge) FsyncDir(input *fuse.FsyncIn) fuse.Status {
return fuse.ENOSYS
}
func (c *rawBridge) fsConn() *FileSystemConnector {
return (*FileSystemConnector)(c)
}
func (c *rawBridge) String() string {
if c.rootNode == nil || c.rootNode.mount == nil {
return "go-fuse:unmounted"
}
name := fmt.Sprintf("%T", c.rootNode.Node())
name = strings.TrimLeft(name, "*")
return name
}
func (c *rawBridge) Init(s *fuse.Server) {
c.server = s
c.rootNode.Node().OnMount((*FileSystemConnector)(c))
}
func (c *FileSystemConnector) lookupMountUpdate(out *fuse.Attr, mount *fileSystemMount) (node *Inode, code fuse.Status) {
code = mount.mountInode.Node().GetAttr(out, nil, nil)
if !code.Ok() {
log.Println("Root getattr should not return error", code)
out.Mode = fuse.S_IFDIR | 0755
return mount.mountInode, fuse.OK
}
return mount.mountInode, fuse.OK
}
// internalLookup executes a lookup without affecting NodeId reference counts.
func (c *FileSystemConnector) internalLookup(out *fuse.Attr, parent *Inode, name string, header *fuse.InHeader) (node *Inode, code fuse.Status) {
// We may already know the child because it was created using Create or Mkdir,
// from an earlier lookup, or because the nodes were created in advance
// (in-memory filesystems).
child := parent.GetChild(name)
if child != nil && child.mountPoint != nil {
return c.lookupMountUpdate(out, child.mountPoint)
}
if child != nil {
parent = nil
}
if child != nil {
code = child.fsInode.GetAttr(out, nil, &header.Context)
} else {
child, code = parent.fsInode.Lookup(out, name, &header.Context)
}
return child, code
}
func (c *rawBridge) Lookup(header *fuse.InHeader, name string, out *fuse.EntryOut) (code fuse.Status) {
parent := c.toInode(header.NodeId)
if !parent.IsDir() {
log.Printf("Lookup %q called on non-Directory node %d", name, header.NodeId)
return fuse.ENOTDIR
}
outAttr := (*fuse.Attr)(&out.Attr)
child, code := c.fsConn().internalLookup(outAttr, parent, name, header)
if code == fuse.ENOENT && parent.mount.negativeEntry(out) {
return fuse.OK
}
if !code.Ok() {
return code
}
if child == nil {
log.Println("Lookup returned fuse.OK with nil child", name)
}
child.mount.fillEntry(out)
out.NodeId, out.Generation = c.fsConn().lookupUpdate(child)
if out.Ino == 0 {
out.Ino = out.NodeId
}
return fuse.OK
}
func (c *rawBridge) Forget(nodeID, nlookup uint64) {
c.fsConn().forgetUpdate(nodeID, int(nlookup))
}
func (c *rawBridge) GetAttr(input *fuse.GetAttrIn, out *fuse.AttrOut) (code fuse.Status) {
node := c.toInode(input.NodeId)
var f File
if input.Flags()&fuse.FUSE_GETATTR_FH != 0 {
if opened := node.mount.getOpenedFile(input.Fh()); opened != nil {
f = opened.WithFlags.File
}
}
dest := (*fuse.Attr)(&out.Attr)
code = node.fsInode.GetAttr(dest, f, &input.Context)
if !code.Ok() {
return code
}
if out.Nlink == 0 {
// With Nlink == 0, newer kernels will refuse link
// operations.
out.Nlink = 1
}
node.mount.fillAttr(out, input.NodeId)
return fuse.OK
}
func (c *rawBridge) OpenDir(input *fuse.OpenIn, out *fuse.OpenOut) (code fuse.Status) {
node := c.toInode(input.NodeId)
stream, err := node.fsInode.OpenDir(&input.Context)
if err != fuse.OK {
return err
}
stream = append(stream, node.getMountDirEntries()...)
de := &connectorDir{
node: node.Node(),
stream: append(stream,
fuse.DirEntry{Mode: fuse.S_IFDIR, Name: "."},
fuse.DirEntry{Mode: fuse.S_IFDIR, Name: ".."}),
rawFS: c,
}
h, opened := node.mount.registerFileHandle(node, de, nil, input.Flags)
out.OpenFlags = opened.FuseFlags
out.Fh = h
return fuse.OK
}
func (c *rawBridge) ReadDir(input *fuse.ReadIn, out *fuse.DirEntryList) fuse.Status {
node := c.toInode(input.NodeId)
opened := node.mount.getOpenedFile(input.Fh)
return opened.dir.ReadDir(input, out)
}
func (c *rawBridge) ReadDirPlus(input *fuse.ReadIn, out *fuse.DirEntryList) fuse.Status {
node := c.toInode(input.NodeId)
opened := node.mount.getOpenedFile(input.Fh)
return opened.dir.ReadDirPlus(input, out)
}
func (c *rawBridge) Open(input *fuse.OpenIn, out *fuse.OpenOut) (status fuse.Status) {
node := c.toInode(input.NodeId)
f, code := node.fsInode.Open(input.Flags, &input.Context)
if !code.Ok() || f == nil {
return code
}
h, opened := node.mount.registerFileHandle(node, nil, f, input.Flags)
out.OpenFlags = opened.FuseFlags
out.Fh = h
return fuse.OK
}
func (c *rawBridge) SetAttr(input *fuse.SetAttrIn, out *fuse.AttrOut) (code fuse.Status) {
node := c.toInode(input.NodeId)
var f File
if input.Valid&fuse.FATTR_FH != 0 {
opened := node.mount.getOpenedFile(input.Fh)
f = opened.WithFlags.File
}
if code.Ok() && input.Valid&fuse.FATTR_MODE != 0 {
permissions := uint32(07777) & input.Mode
code = node.fsInode.Chmod(f, permissions, &input.Context)
}
if code.Ok() && (input.Valid&(fuse.FATTR_UID|fuse.FATTR_GID) != 0) {
var uid uint32 = ^uint32(0) // means "do not change" in chown(2)
var gid uint32 = ^uint32(0)
if input.Valid&fuse.FATTR_UID != 0 {
uid = input.Uid
}
if input.Valid&fuse.FATTR_GID != 0 {
gid = input.Gid
}
code = node.fsInode.Chown(f, uid, gid, &input.Context)
}
if code.Ok() && input.Valid&fuse.FATTR_SIZE != 0 {
code = node.fsInode.Truncate(f, input.Size, &input.Context)
}
if code.Ok() && (input.Valid&(fuse.FATTR_ATIME|fuse.FATTR_MTIME|fuse.FATTR_ATIME_NOW|fuse.FATTR_MTIME_NOW) != 0) {
now := time.Now()
var atime *time.Time
var mtime *time.Time
if input.Valid&fuse.FATTR_ATIME != 0 {
if input.Valid&fuse.FATTR_ATIME_NOW != 0 {
atime = &now
} else {
t := time.Unix(int64(input.Atime), int64(input.Atimensec))
atime = &t
}
}
if input.Valid&fuse.FATTR_MTIME != 0 {
if input.Valid&fuse.FATTR_MTIME_NOW != 0 {
mtime = &now
} else {
t := time.Unix(int64(input.Mtime), int64(input.Mtimensec))
mtime = &t
}
}
code = node.fsInode.Utimens(f, atime, mtime, &input.Context)
}
if !code.Ok() {
return code
}
// Must call GetAttr(); the filesystem may override some of
// the changes we effect here.
attr := (*fuse.Attr)(&out.Attr)
code = node.fsInode.GetAttr(attr, nil, &input.Context)
if code.Ok() {
node.mount.fillAttr(out, input.NodeId)
}
return code
}
func (c *rawBridge) Fallocate(input *fuse.FallocateIn) (code fuse.Status) {
n := c.toInode(input.NodeId)
opened := n.mount.getOpenedFile(input.Fh)
return n.fsInode.Fallocate(opened, input.Offset, input.Length, input.Mode, &input.Context)
}
func (c *rawBridge) Readlink(header *fuse.InHeader) (out []byte, code fuse.Status) {
n := c.toInode(header.NodeId)
return n.fsInode.Readlink(&header.Context)
}
func (c *rawBridge) Mknod(input *fuse.MknodIn, name string, out *fuse.EntryOut) (code fuse.Status) {
parent := c.toInode(input.NodeId)
child, code := parent.fsInode.Mknod(name, input.Mode, uint32(input.Rdev), &input.Context)
if code.Ok() {
c.childLookup(out, child, &input.Context)
code = child.fsInode.GetAttr((*fuse.Attr)(&out.Attr), nil, &input.Context)
}
return code
}
func (c *rawBridge) Mkdir(input *fuse.MkdirIn, name string, out *fuse.EntryOut) (code fuse.Status) {
parent := c.toInode(input.NodeId)
child, code := parent.fsInode.Mkdir(name, input.Mode, &input.Context)
if code.Ok() {
c.childLookup(out, child, &input.Context)
code = child.fsInode.GetAttr((*fuse.Attr)(&out.Attr), nil, &input.Context)
}
return code
}
func (c *rawBridge) Unlink(header *fuse.InHeader, name string) (code fuse.Status) {
parent := c.toInode(header.NodeId)
return parent.fsInode.Unlink(name, &header.Context)
}
func (c *rawBridge) Rmdir(header *fuse.InHeader, name string) (code fuse.Status) {
parent := c.toInode(header.NodeId)
return parent.fsInode.Rmdir(name, &header.Context)
}
func (c *rawBridge) Symlink(header *fuse.InHeader, pointedTo string, linkName string, out *fuse.EntryOut) (code fuse.Status) {
parent := c.toInode(header.NodeId)
child, code := parent.fsInode.Symlink(linkName, pointedTo, &header.Context)
if code.Ok() {
c.childLookup(out, child, &header.Context)
code = child.fsInode.GetAttr((*fuse.Attr)(&out.Attr), nil, &header.Context)
}
return code
}
func (c *rawBridge) Rename(input *fuse.RenameIn, oldName string, newName string) (code fuse.Status) {
oldParent := c.toInode(input.NodeId)
child := oldParent.GetChild(oldName)
if child == nil {
return fuse.ENOENT
}
if child.mountPoint != nil {
return fuse.EBUSY
}
newParent := c.toInode(input.Newdir)
if oldParent.mount != newParent.mount {
return fuse.EXDEV
}
return oldParent.fsInode.Rename(oldName, newParent.fsInode, newName, &input.Context)
}
func (c *rawBridge) Link(input *fuse.LinkIn, name string, out *fuse.EntryOut) (code fuse.Status) {
existing := c.toInode(input.Oldnodeid)
parent := c.toInode(input.NodeId)
if existing.mount != parent.mount {
return fuse.EXDEV
}
child, code := parent.fsInode.Link(name, existing.fsInode, &input.Context)
if code.Ok() {
c.childLookup(out, child, &input.Context)
code = child.fsInode.GetAttr((*fuse.Attr)(&out.Attr), nil, &input.Context)
}
return code
}
func (c *rawBridge) Access(input *fuse.AccessIn) (code fuse.Status) {
n := c.toInode(input.NodeId)
return n.fsInode.Access(input.Mask, &input.Context)
}
func (c *rawBridge) Create(input *fuse.CreateIn, name string, out *fuse.CreateOut) (code fuse.Status) {
parent := c.toInode(input.NodeId)
f, child, code := parent.fsInode.Create(name, uint32(input.Flags), input.Mode, &input.Context)
if !code.Ok() {
return code
}
c.childLookup(&out.EntryOut, child, &input.Context)
handle, opened := parent.mount.registerFileHandle(child, nil, f, input.Flags)
out.OpenOut.OpenFlags = opened.FuseFlags
out.OpenOut.Fh = handle
return code
}
func (c *rawBridge) Release(input *fuse.ReleaseIn) {
if input.Fh != 0 {
node := c.toInode(input.NodeId)
opened := node.mount.unregisterFileHandle(input.Fh, node)
opened.WithFlags.File.Release()
}
}
func (c *rawBridge) ReleaseDir(input *fuse.ReleaseIn) {
if input.Fh != 0 {
node := c.toInode(input.NodeId)
node.mount.unregisterFileHandle(input.Fh, node)
}
}
func (c *rawBridge) GetXAttrSize(header *fuse.InHeader, attribute string) (sz int, code fuse.Status) {
node := c.toInode(header.NodeId)
data, errno := node.fsInode.GetXAttr(attribute, &header.Context)
return len(data), errno
}
func (c *rawBridge) GetXAttrData(header *fuse.InHeader, attribute string) (data []byte, code fuse.Status) {
node := c.toInode(header.NodeId)
return node.fsInode.GetXAttr(attribute, &header.Context)
}
func (c *rawBridge) RemoveXAttr(header *fuse.InHeader, attr string) fuse.Status {
node := c.toInode(header.NodeId)
return node.fsInode.RemoveXAttr(attr, &header.Context)
}
func (c *rawBridge) SetXAttr(input *fuse.SetXAttrIn, attr string, data []byte) fuse.Status {
node := c.toInode(input.NodeId)
return node.fsInode.SetXAttr(attr, data, int(input.Flags), &input.Context)
}
func (c *rawBridge) ListXAttr(header *fuse.InHeader) (data []byte, code fuse.Status) {
node := c.toInode(header.NodeId)
attrs, code := node.fsInode.ListXAttr(&header.Context)
if code != fuse.OK {
return nil, code
}
b := bytes.NewBuffer([]byte{})
for _, v := range attrs {
b.Write([]byte(v))
b.WriteByte(0)
}
return b.Bytes(), code
}
////////////////
// files.
func (c *rawBridge) Write(input *fuse.WriteIn, data []byte) (written uint32, code fuse.Status) {
node := c.toInode(input.NodeId)
opened := node.mount.getOpenedFile(input.Fh)
var f File
if opened != nil {
f = opened.WithFlags.File
}
return node.Node().Write(f, data, int64(input.Offset), &input.Context)
}
func (c *rawBridge) Read(input *fuse.ReadIn, buf []byte) (fuse.ReadResult, fuse.Status) {
node := c.toInode(input.NodeId)
opened := node.mount.getOpenedFile(input.Fh)
var f File
if opened != nil {
f = opened.WithFlags.File
}
return node.Node().Read(f, buf, int64(input.Offset), &input.Context)
}
func (c *rawBridge) StatFs(header *fuse.InHeader, out *fuse.StatfsOut) fuse.Status {
node := c.toInode(header.NodeId)
s := node.Node().StatFs()
if s == nil {
return fuse.ENOSYS
}
*out = *s
return fuse.OK
}
func (c *rawBridge) Flush(input *fuse.FlushIn) fuse.Status {
node := c.toInode(input.NodeId)
opened := node.mount.getOpenedFile(input.Fh)
if opened != nil {
return opened.WithFlags.File.Flush()
}
return fuse.OK
}

20
vendor/github.com/hanwen/go-fuse/fuse/nodefs/fuse.go generated vendored Normal file
View File

@@ -0,0 +1,20 @@
package nodefs
import (
"github.com/hanwen/go-fuse/fuse"
)
// Mounts a filesystem with the given root node on the given directory
func MountRoot(mountpoint string, root Node, opts *Options) (*fuse.Server, *FileSystemConnector, error) {
conn := NewFileSystemConnector(root, opts)
mountOpts := fuse.MountOptions{}
if opts != nil && opts.Debug {
mountOpts.Debug = opts.Debug
}
s, err := fuse.NewServer(conn.RawFS(), mountpoint, &mountOpts)
if err != nil {
return nil, nil, err
}
return s, conn, nil
}

147
vendor/github.com/hanwen/go-fuse/fuse/nodefs/handle.go generated vendored Normal file
View File

@@ -0,0 +1,147 @@
package nodefs
import (
"log"
"sync"
)
// HandleMap translates objects in Go space to 64-bit handles that can
// be given out to -say- the linux kernel as NodeIds.
//
// The 32 bits version of this is a threadsafe wrapper around a map.
//
// To use it, include "handled" as first member of the structure
// you wish to export.
//
// This structure is thread-safe.
type handleMap interface {
// Register stores "obj" and returns a unique (NodeId, generation) tuple.
Register(obj *handled) (handle, generation uint64)
Count() int
// Decode retrieves a stored object from its 64-bit handle.
Decode(uint64) *handled
// Forget decrements the reference counter for "handle" by "count" and drops
// the object if the refcount reaches zero.
// Returns a boolean whether the object was dropped and the object itself.
Forget(handle uint64, count int) (bool, *handled)
// Handle gets the object's NodeId.
Handle(obj *handled) uint64
// Has checks if NodeId is stored.
Has(uint64) bool
}
type handled struct {
check uint32
handle uint64
count int
}
func (h *handled) verify() {
if h.count < 0 {
log.Panicf("negative lookup count %d", h.count)
}
if (h.count == 0) != (h.handle == 0) {
log.Panicf("registration mismatch: lookup %d id %d", h.count, h.handle)
}
}
const _ALREADY_MSG = "Object already has a handle"
////////////////////////////////////////////////////////////////
// portable version using 32 bit integers.
type portableHandleMap struct {
sync.RWMutex
// The generation counter is incremented each time a NodeId is reused,
// hence the (NodeId, Generation) tuple is always unique.
generation uint64
// Number of currently used handles
used int
// Array of Go objects indexed by NodeId
handles []*handled
// Free slots in the "handles" array
freeIds []uint64
}
func newPortableHandleMap() *portableHandleMap {
return &portableHandleMap{
// Avoid handing out ID 0 and 1.
handles: []*handled{nil, nil},
}
}
func (m *portableHandleMap) Register(obj *handled) (handle, generation uint64) {
m.Lock()
if obj.count == 0 {
if obj.check != 0 {
panic(_ALREADY_MSG)
}
if len(m.freeIds) == 0 {
handle = uint64(len(m.handles))
m.handles = append(m.handles, obj)
} else {
handle = m.freeIds[len(m.freeIds)-1]
m.freeIds = m.freeIds[:len(m.freeIds)-1]
m.generation++
m.handles[handle] = obj
}
m.used++
obj.handle = handle
} else {
handle = obj.handle
}
obj.count++
generation = m.generation
m.Unlock()
return
}
func (m *portableHandleMap) Handle(obj *handled) (h uint64) {
m.RLock()
if obj.count == 0 {
h = 0
} else {
h = obj.handle
}
m.RUnlock()
return h
}
func (m *portableHandleMap) Count() int {
m.RLock()
c := m.used
m.RUnlock()
return c
}
func (m *portableHandleMap) Decode(h uint64) *handled {
m.RLock()
v := m.handles[h]
m.RUnlock()
return v
}
func (m *portableHandleMap) Forget(h uint64, count int) (forgotten bool, obj *handled) {
m.Lock()
obj = m.handles[h]
obj.count -= count
if obj.count < 0 {
log.Panicf("underflow: handle %d, count %d, object %d", h, count, obj.count)
} else if obj.count == 0 {
m.handles[h] = nil
m.freeIds = append(m.freeIds, h)
m.used--
forgotten = true
obj.handle = 0
}
m.Unlock()
return forgotten, obj
}
func (m *portableHandleMap) Has(h uint64) bool {
m.RLock()
ok := m.handles[h] != nil
m.RUnlock()
return ok
}

View File

@@ -0,0 +1,127 @@
package nodefs
import (
"strings"
"testing"
)
func markSeen(t *testing.T, substr string) {
if r := recover(); r != nil {
s := r.(string)
if strings.Contains(s, substr) {
t.Log("expected recovery from: ", r)
} else {
panic(s)
}
}
}
func TestHandleMapLookupCount(t *testing.T) {
for _, portable := range []bool{true, false} {
t.Log("portable:", portable)
v := new(handled)
hm := newPortableHandleMap()
h1, g1 := hm.Register(v)
h2, g2 := hm.Register(v)
if h1 != h2 {
t.Fatalf("double register should reuse handle: got %d want %d.", h2, h1)
}
if g1 != g2 {
t.Fatalf("double register should reuse generation: got %d want %d.", g2, g1)
}
hm.Register(v)
forgotten, obj := hm.Forget(h1, 1)
if forgotten {
t.Fatalf("single forget unref forget object.")
}
if obj != v {
t.Fatalf("should return input object.")
}
if !hm.Has(h1) {
t.Fatalf("handlemap.Has() returned false for live object.")
}
forgotten, obj = hm.Forget(h1, 2)
if !forgotten {
t.Fatalf("unref did not forget object.")
}
if obj != v {
t.Fatalf("should return input object.")
}
if hm.Has(h1) {
t.Fatalf("handlemap.Has() returned false for live object.")
}
}
}
func TestHandleMapBasic(t *testing.T) {
v := new(handled)
hm := newPortableHandleMap()
h, _ := hm.Register(v)
t.Logf("Got handle 0x%x", h)
if !hm.Has(h) {
t.Fatal("Does not have handle")
}
if hm.Handle(v) != h {
t.Fatalf("handle mismatch, got %x want %x", hm.Handle(v), h)
}
if hm.Decode(h) != v {
t.Fatal("address mismatch")
}
if hm.Count() != 1 {
t.Fatal("count error")
}
hm.Forget(h, 1)
if hm.Count() != 0 {
t.Fatal("count error")
}
if hm.Has(h) {
t.Fatal("Still has handle")
}
if v.check != 0 {
t.Errorf("forgotten object still has a check.")
}
}
func TestHandleMapMultiple(t *testing.T) {
hm := newPortableHandleMap()
for i := 0; i < 10; i++ {
v := &handled{}
h, _ := hm.Register(v)
if hm.Decode(h) != v {
t.Fatal("address mismatch")
}
if hm.Count() != i+1 {
t.Fatal("count error")
}
}
}
func TestHandleMapGeneration(t *testing.T) {
hm := newPortableHandleMap()
h1, g1 := hm.Register(&handled{})
forgotten, _ := hm.Forget(h1, 1)
if !forgotten {
t.Fatalf("unref did not forget object.")
}
h2, g2 := hm.Register(&handled{})
if h1 != h2 {
t.Fatalf("register should reuse handle: got %d want %d.", h2, h1)
}
if g1 >= g2 {
t.Fatalf("register should increase generation: got %d want greater than %d.", g2, g1)
}
}

285
vendor/github.com/hanwen/go-fuse/fuse/nodefs/inode.go generated vendored Normal file
View File

@@ -0,0 +1,285 @@
package nodefs
import (
"log"
"sync"
"github.com/hanwen/go-fuse/fuse"
)
type parentData struct {
parent *Inode
name string
}
// An Inode reflects the kernel's idea of the inode. Inodes have IDs
// that are communicated to the kernel, and they have a tree
// structure: a directory Inode may contain named children. Each
// Inode object is paired with a Node object, which file system
// implementers should supply.
type Inode struct {
handled handled
// Generation number of the inode. Each (re)use of an inode
// should have a unique generation number.
generation uint64
// Number of open files and its protection.
openFilesMutex sync.Mutex
openFiles []*openedFile
fsInode Node
// Each inode belongs to exactly one fileSystemMount. This
// pointer is constant during the lifetime, except upon
// Unmount() when it is set to nil.
mount *fileSystemMount
// All data below is protected by treeLock.
children map[string]*Inode
// Due to hard links, an Inode can have many parents.
parents map[parentData]struct{}
// Non-nil if this inode is a mountpoint, ie. the Root of a
// NodeFileSystem.
mountPoint *fileSystemMount
}
func newInode(isDir bool, fsNode Node) *Inode {
me := new(Inode)
me.parents = map[parentData]struct{}{}
if isDir {
me.children = make(map[string]*Inode, initDirSize)
}
me.fsInode = fsNode
me.fsInode.SetInode(me)
return me
}
// public methods.
// Returns any open file, preferably a r/w one.
func (n *Inode) AnyFile() (file File) {
n.openFilesMutex.Lock()
for _, f := range n.openFiles {
if file == nil || f.WithFlags.OpenFlags&fuse.O_ANYWRITE != 0 {
file = f.WithFlags.File
}
}
n.openFilesMutex.Unlock()
return file
}
// Children returns all children of this inode.
func (n *Inode) Children() (out map[string]*Inode) {
n.mount.treeLock.RLock()
out = make(map[string]*Inode, len(n.children))
for k, v := range n.children {
out[k] = v
}
n.mount.treeLock.RUnlock()
return out
}
// Parent returns a random parent and the name this inode has under this parent.
// This function can be used to walk up the directory tree. It will not cross
// sub-mounts.
func (n *Inode) Parent() (parent *Inode, name string) {
if n.mountPoint != nil {
return nil, ""
}
for k := range n.parents {
return k.parent, k.name
}
return nil, ""
}
// FsChildren returns all the children from the same filesystem. It
// will skip mountpoints.
func (n *Inode) FsChildren() (out map[string]*Inode) {
n.mount.treeLock.RLock()
out = map[string]*Inode{}
for k, v := range n.children {
if v.mount == n.mount {
out[k] = v
}
}
n.mount.treeLock.RUnlock()
return out
}
// Node returns the file-system specific node.
func (n *Inode) Node() Node {
return n.fsInode
}
// Files() returns an opens file that have bits in common with the
// give mask. Use mask==0 to return all files.
func (n *Inode) Files(mask uint32) (files []WithFlags) {
n.openFilesMutex.Lock()
for _, f := range n.openFiles {
if mask == 0 || f.WithFlags.OpenFlags&mask != 0 {
files = append(files, f.WithFlags)
}
}
n.openFilesMutex.Unlock()
return files
}
// IsDir returns true if this is a directory.
func (n *Inode) IsDir() bool {
return n.children != nil
}
// NewChild adds a new child inode to this inode.
func (n *Inode) NewChild(name string, isDir bool, fsi Node) *Inode {
ch := newInode(isDir, fsi)
ch.mount = n.mount
n.AddChild(name, ch)
return ch
}
// GetChild returns a child inode with the given name, or nil if it
// does not exist.
func (n *Inode) GetChild(name string) (child *Inode) {
n.mount.treeLock.RLock()
child = n.children[name]
n.mount.treeLock.RUnlock()
return child
}
// AddChild adds a child inode. The parent inode must be a directory
// node.
func (n *Inode) AddChild(name string, child *Inode) {
if child == nil {
log.Panicf("adding nil child as %q", name)
}
n.mount.treeLock.Lock()
n.addChild(name, child)
n.mount.treeLock.Unlock()
}
// TreeWatcher is an additional interface that Nodes can implement.
// If they do, the OnAdd and OnRemove are called for operations on the
// file system tree. These functions run under a lock, so they should
// not do blocking operations.
type TreeWatcher interface {
OnAdd(parent *Inode, name string)
OnRemove(parent *Inode, name string)
}
// RmChild removes an inode by name, and returns it. It returns nil if
// child does not exist.
func (n *Inode) RmChild(name string) (ch *Inode) {
n.mount.treeLock.Lock()
ch = n.rmChild(name)
n.mount.treeLock.Unlock()
return
}
//////////////////////////////////////////////////////////////
// private
// addChild adds "child" to our children under name "name".
// Must be called with treeLock for the mount held.
func (n *Inode) addChild(name string, child *Inode) {
if paranoia {
ch := n.children[name]
if ch != nil {
log.Panicf("Already have an Inode with same name: %v: %v", name, ch)
}
}
n.children[name] = child
child.parents[parentData{n, name}] = struct{}{}
if w, ok := child.Node().(TreeWatcher); ok && child.mountPoint == nil {
w.OnAdd(n, name)
}
}
// rmChild throws out child "name". This means (1) deleting "name" from our
// "children" map and (2) deleting ourself from the child's "parents" map.
// Must be called with treeLock for the mount held.
func (n *Inode) rmChild(name string) *Inode {
ch := n.children[name]
if ch != nil {
delete(n.children, name)
delete(ch.parents, parentData{n, name})
if w, ok := ch.Node().(TreeWatcher); ok && ch.mountPoint == nil {
w.OnRemove(n, name)
}
}
return ch
}
// Can only be called on untouched root inodes.
func (n *Inode) mountFs(opts *Options) {
n.mountPoint = &fileSystemMount{
openFiles: newPortableHandleMap(),
mountInode: n,
options: opts,
}
n.mount = n.mountPoint
}
// Must be called with treeLock held.
func (n *Inode) canUnmount() bool {
for _, v := range n.children {
if v.mountPoint != nil {
// This access may be out of date, but it is no
// problem to err on the safe side.
return false
}
if !v.canUnmount() {
return false
}
}
n.openFilesMutex.Lock()
ok := len(n.openFiles) == 0
n.openFilesMutex.Unlock()
return ok
}
func (n *Inode) getMountDirEntries() (out []fuse.DirEntry) {
n.mount.treeLock.RLock()
for k, v := range n.children {
if v.mountPoint != nil {
out = append(out, fuse.DirEntry{
Name: k,
Mode: fuse.S_IFDIR,
})
}
}
n.mount.treeLock.RUnlock()
return out
}
const initDirSize = 20
func (n *Inode) verify(cur *fileSystemMount) {
n.handled.verify()
if n.mountPoint != nil {
if n != n.mountPoint.mountInode {
log.Panicf("mountpoint mismatch %v %v", n, n.mountPoint.mountInode)
}
cur = n.mountPoint
cur.treeLock.Lock()
defer cur.treeLock.Unlock()
}
if n.mount != cur {
log.Panicf("n.mount not set correctly %v %v", n.mount, cur)
}
for nm, ch := range n.children {
if ch == nil {
log.Panicf("Found nil child: %q", nm)
}
ch.verify(cur)
}
}

View File

@@ -0,0 +1,99 @@
package nodefs
import (
"fmt"
"sync"
"time"
"github.com/hanwen/go-fuse/fuse"
)
type lockingFile struct {
mu *sync.Mutex
file File
}
// NewLockingFile serializes operations an existing File.
func NewLockingFile(mu *sync.Mutex, f File) File {
return &lockingFile{
mu: mu,
file: f,
}
}
func (f *lockingFile) SetInode(*Inode) {
}
func (f *lockingFile) InnerFile() File {
return f.file
}
func (f *lockingFile) String() string {
return fmt.Sprintf("lockingFile(%s)", f.file.String())
}
func (f *lockingFile) Read(buf []byte, off int64) (fuse.ReadResult, fuse.Status) {
f.mu.Lock()
defer f.mu.Unlock()
return f.file.Read(buf, off)
}
func (f *lockingFile) Write(data []byte, off int64) (uint32, fuse.Status) {
f.mu.Lock()
defer f.mu.Unlock()
return f.file.Write(data, off)
}
func (f *lockingFile) Flush() fuse.Status {
f.mu.Lock()
defer f.mu.Unlock()
return f.file.Flush()
}
func (f *lockingFile) Release() {
f.mu.Lock()
defer f.mu.Unlock()
f.file.Release()
}
func (f *lockingFile) GetAttr(a *fuse.Attr) fuse.Status {
f.mu.Lock()
defer f.mu.Unlock()
return f.file.GetAttr(a)
}
func (f *lockingFile) Fsync(flags int) (code fuse.Status) {
f.mu.Lock()
defer f.mu.Unlock()
return f.file.Fsync(flags)
}
func (f *lockingFile) Utimens(atime *time.Time, mtime *time.Time) fuse.Status {
f.mu.Lock()
defer f.mu.Unlock()
return f.file.Utimens(atime, mtime)
}
func (f *lockingFile) Truncate(size uint64) fuse.Status {
f.mu.Lock()
defer f.mu.Unlock()
return f.file.Truncate(size)
}
func (f *lockingFile) Chown(uid uint32, gid uint32) fuse.Status {
f.mu.Lock()
defer f.mu.Unlock()
return f.file.Chown(uid, gid)
}
func (f *lockingFile) Chmod(perms uint32) fuse.Status {
f.mu.Lock()
defer f.mu.Unlock()
return f.file.Chmod(perms)
}
func (f *lockingFile) Allocate(off uint64, size uint64, mode uint32) (code fuse.Status) {
f.mu.Lock()
defer f.mu.Unlock()
return f.file.Allocate(off, size, mode)
}

228
vendor/github.com/hanwen/go-fuse/fuse/nodefs/memnode.go generated vendored Normal file
View File

@@ -0,0 +1,228 @@
package nodefs
import (
"fmt"
"os"
"sync"
"syscall"
"time"
"github.com/hanwen/go-fuse/fuse"
)
// NewMemNodeFSRoot creates an in-memory node-based filesystem. Files
// are written into a backing store under the given prefix.
func NewMemNodeFSRoot(prefix string) Node {
fs := &memNodeFs{
backingStorePrefix: prefix,
}
fs.root = fs.newNode()
return fs.root
}
type memNodeFs struct {
backingStorePrefix string
root *memNode
mutex sync.Mutex
nextFree int
}
func (fs *memNodeFs) String() string {
return fmt.Sprintf("MemNodeFs(%s)", fs.backingStorePrefix)
}
func (fs *memNodeFs) Root() Node {
return fs.root
}
func (fs *memNodeFs) SetDebug(bool) {
}
func (fs *memNodeFs) OnMount(*FileSystemConnector) {
}
func (fs *memNodeFs) OnUnmount() {
}
func (fs *memNodeFs) newNode() *memNode {
fs.mutex.Lock()
id := fs.nextFree
fs.nextFree++
fs.mutex.Unlock()
n := &memNode{
Node: NewDefaultNode(),
fs: fs,
id: id,
}
now := time.Now()
n.info.SetTimes(&now, &now, &now)
n.info.Mode = fuse.S_IFDIR | 0777
return n
}
func (fs *memNodeFs) Filename(n *Inode) string {
mn := n.Node().(*memNode)
return mn.filename()
}
type memNode struct {
Node
fs *memNodeFs
id int
link string
info fuse.Attr
}
func (n *memNode) filename() string {
return fmt.Sprintf("%s%d", n.fs.backingStorePrefix, n.id)
}
func (n *memNode) Deletable() bool {
return false
}
func (n *memNode) Readlink(c *fuse.Context) ([]byte, fuse.Status) {
return []byte(n.link), fuse.OK
}
func (n *memNode) StatFs() *fuse.StatfsOut {
return &fuse.StatfsOut{}
}
func (n *memNode) Mkdir(name string, mode uint32, context *fuse.Context) (newNode *Inode, code fuse.Status) {
ch := n.fs.newNode()
ch.info.Mode = mode | fuse.S_IFDIR
n.Inode().NewChild(name, true, ch)
return ch.Inode(), fuse.OK
}
func (n *memNode) Unlink(name string, context *fuse.Context) (code fuse.Status) {
ch := n.Inode().RmChild(name)
if ch == nil {
return fuse.ENOENT
}
return fuse.OK
}
func (n *memNode) Rmdir(name string, context *fuse.Context) (code fuse.Status) {
return n.Unlink(name, context)
}
func (n *memNode) Symlink(name string, content string, context *fuse.Context) (newNode *Inode, code fuse.Status) {
ch := n.fs.newNode()
ch.info.Mode = fuse.S_IFLNK | 0777
ch.link = content
n.Inode().NewChild(name, false, ch)
return ch.Inode(), fuse.OK
}
func (n *memNode) Rename(oldName string, newParent Node, newName string, context *fuse.Context) (code fuse.Status) {
ch := n.Inode().RmChild(oldName)
newParent.Inode().RmChild(newName)
newParent.Inode().AddChild(newName, ch)
return fuse.OK
}
func (n *memNode) Link(name string, existing Node, context *fuse.Context) (*Inode, fuse.Status) {
n.Inode().AddChild(name, existing.Inode())
return existing.Inode(), fuse.OK
}
func (n *memNode) Create(name string, flags uint32, mode uint32, context *fuse.Context) (file File, node *Inode, code fuse.Status) {
ch := n.fs.newNode()
ch.info.Mode = mode | fuse.S_IFREG
f, err := os.Create(ch.filename())
if err != nil {
return nil, nil, fuse.ToStatus(err)
}
n.Inode().NewChild(name, false, ch)
return ch.newFile(f), ch.Inode(), fuse.OK
}
type memNodeFile struct {
File
node *memNode
}
func (n *memNodeFile) String() string {
return fmt.Sprintf("memNodeFile(%s)", n.File.String())
}
func (n *memNodeFile) InnerFile() File {
return n.File
}
func (n *memNodeFile) Flush() fuse.Status {
code := n.File.Flush()
if !code.Ok() {
return code
}
st := syscall.Stat_t{}
err := syscall.Stat(n.node.filename(), &st)
n.node.info.Size = uint64(st.Size)
n.node.info.Blocks = uint64(st.Blocks)
return fuse.ToStatus(err)
}
func (n *memNode) newFile(f *os.File) File {
return &memNodeFile{
File: NewLoopbackFile(f),
node: n,
}
}
func (n *memNode) Open(flags uint32, context *fuse.Context) (file File, code fuse.Status) {
f, err := os.OpenFile(n.filename(), int(flags), 0666)
if err != nil {
return nil, fuse.ToStatus(err)
}
return n.newFile(f), fuse.OK
}
func (n *memNode) GetAttr(fi *fuse.Attr, file File, context *fuse.Context) (code fuse.Status) {
*fi = n.info
return fuse.OK
}
func (n *memNode) Truncate(file File, size uint64, context *fuse.Context) (code fuse.Status) {
if file != nil {
code = file.Truncate(size)
} else {
err := os.Truncate(n.filename(), int64(size))
code = fuse.ToStatus(err)
}
if code.Ok() {
now := time.Now()
n.info.SetTimes(nil, nil, &now)
// TODO - should update mtime too?
n.info.Size = size
}
return code
}
func (n *memNode) Utimens(file File, atime *time.Time, mtime *time.Time, context *fuse.Context) (code fuse.Status) {
c := time.Now()
n.info.SetTimes(atime, mtime, &c)
return fuse.OK
}
func (n *memNode) Chmod(file File, perms uint32, context *fuse.Context) (code fuse.Status) {
n.info.Mode = (n.info.Mode &^ 07777) | perms
now := time.Now()
n.info.SetTimes(nil, nil, &now)
return fuse.OK
}
func (n *memNode) Chown(file File, uid uint32, gid uint32, context *fuse.Context) (code fuse.Status) {
n.info.Uid = uid
n.info.Gid = gid
now := time.Now()
n.info.SetTimes(nil, nil, &now)
return fuse.OK
}

View File

@@ -0,0 +1,110 @@
package nodefs
import (
"io/ioutil"
"os"
"testing"
"time"
"github.com/hanwen/go-fuse/fuse"
)
const testTtl = 100 * time.Millisecond
func setupMemNodeTest(t *testing.T) (wd string, root Node, clean func()) {
tmp, err := ioutil.TempDir("", "go-fuse-memnode_test")
if err != nil {
t.Fatalf("TempDir failed: %v", err)
}
back := tmp + "/backing"
os.Mkdir(back, 0700)
root = NewMemNodeFSRoot(back)
mnt := tmp + "/mnt"
os.Mkdir(mnt, 0700)
connector := NewFileSystemConnector(root,
&Options{
EntryTimeout: testTtl,
AttrTimeout: testTtl,
NegativeTimeout: 0.0,
Debug: VerboseTest(),
})
state, err := fuse.NewServer(connector.RawFS(), mnt, &fuse.MountOptions{Debug: VerboseTest()})
if err != nil {
t.Fatal("NewServer", err)
}
// Unthreaded, but in background.
go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
return mnt, root, func() {
state.Unmount()
os.RemoveAll(tmp)
}
}
func TestMemNodeFsWrite(t *testing.T) {
wd, _, clean := setupMemNodeTest(t)
defer clean()
want := "hello"
err := ioutil.WriteFile(wd+"/test", []byte(want), 0644)
if err != nil {
t.Fatalf("WriteFile failed: %v", err)
}
content, err := ioutil.ReadFile(wd + "/test")
if string(content) != want {
t.Fatalf("content mismatch: got %q, want %q", content, want)
}
}
func TestMemNodeFsBasic(t *testing.T) {
wd, _, clean := setupMemNodeTest(t)
defer clean()
err := ioutil.WriteFile(wd+"/test", []byte{42}, 0644)
if err != nil {
t.Fatalf("WriteFile failed: %v", err)
}
fi, err := os.Lstat(wd + "/test")
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
if fi.Size() != 1 {
t.Errorf("Size after write incorrect: got %d want 1", fi.Size())
}
entries, err := ioutil.ReadDir(wd)
if len(entries) != 1 || entries[0].Name() != "test" {
t.Fatalf("Readdir got %v, expected 1 file named 'test'", entries)
}
}
func TestMemNodeSetattr(t *testing.T) {
wd, _, clean := setupMemNodeTest(t)
defer clean()
f, err := os.OpenFile(wd+"/test", os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
t.Fatalf("OpenFile failed: %v", err)
}
defer f.Close()
err = f.Truncate(4096)
if err != nil {
t.Fatalf("Truncate failed: %v", err)
}
fi, err := f.Stat()
if err != nil {
t.Fatalf("Stat failed: %v", err)
}
if fi.Size() != 4096 {
t.Errorf("Size should be 4096 after Truncate: %d", fi.Size())
}
}

View File

@@ -0,0 +1,9 @@
package nodefs
import "time"
func splitDuration(dt time.Duration, secs *uint64, nsecs *uint32) {
ns := int64(dt)
*nsecs = uint32(ns % 1e9)
*secs = uint64(ns / 1e9)
}

14
vendor/github.com/hanwen/go-fuse/fuse/nodefs/print.go generated vendored Normal file
View File

@@ -0,0 +1,14 @@
package nodefs
import (
"fmt"
"github.com/hanwen/go-fuse/fuse"
)
// String provides a debug string for the given file.
func (f *WithFlags) String() string {
return fmt.Sprintf("File %s (%s) %s %s",
f.File, f.Description, fuse.FlagString(fuse.OpenFlagNames, int64(f.OpenFlags), "O_RDONLY"),
fuse.FlagString(fuse.FuseOpenFlagNames, int64(f.FuseFlags), ""))
}

View File

@@ -0,0 +1,16 @@
package nodefs
import (
"syscall"
"unsafe"
)
// futimens - futimens(3) calls utimensat(2) with "pathname" set to null and
// "flags" set to zero
func futimens(fd int, times *[2]syscall.Timespec) (err error) {
_, _, e1 := syscall.Syscall6(syscall.SYS_UTIMENSAT, uintptr(fd), 0, uintptr(unsafe.Pointer(times)), uintptr(0), 0, 0)
if e1 != 0 {
err = syscall.Errno(e1)
}
return
}

View File

@@ -0,0 +1,9 @@
package nodefs
import "flag"
// VerboseTest returns true if the testing framework is run with -v.
func VerboseTest() bool {
flag := flag.Lookup("test.v")
return flag != nil && flag.Value.String() == "true"
}

651
vendor/github.com/hanwen/go-fuse/fuse/opcode.go generated vendored Normal file
View File

@@ -0,0 +1,651 @@
package fuse
import (
"bytes"
"log"
"reflect"
"runtime"
"unsafe"
)
const (
_OP_LOOKUP = int32(1)
_OP_FORGET = int32(2)
_OP_GETATTR = int32(3)
_OP_SETATTR = int32(4)
_OP_READLINK = int32(5)
_OP_SYMLINK = int32(6)
_OP_MKNOD = int32(8)
_OP_MKDIR = int32(9)
_OP_UNLINK = int32(10)
_OP_RMDIR = int32(11)
_OP_RENAME = int32(12)
_OP_LINK = int32(13)
_OP_OPEN = int32(14)
_OP_READ = int32(15)
_OP_WRITE = int32(16)
_OP_STATFS = int32(17)
_OP_RELEASE = int32(18)
_OP_FSYNC = int32(20)
_OP_SETXATTR = int32(21)
_OP_GETXATTR = int32(22)
_OP_LISTXATTR = int32(23)
_OP_REMOVEXATTR = int32(24)
_OP_FLUSH = int32(25)
_OP_INIT = int32(26)
_OP_OPENDIR = int32(27)
_OP_READDIR = int32(28)
_OP_RELEASEDIR = int32(29)
_OP_FSYNCDIR = int32(30)
_OP_GETLK = int32(31)
_OP_SETLK = int32(32)
_OP_SETLKW = int32(33)
_OP_ACCESS = int32(34)
_OP_CREATE = int32(35)
_OP_INTERRUPT = int32(36)
_OP_BMAP = int32(37)
_OP_DESTROY = int32(38)
_OP_IOCTL = int32(39)
_OP_POLL = int32(40)
_OP_NOTIFY_REPLY = int32(41)
_OP_BATCH_FORGET = int32(42)
_OP_FALLOCATE = int32(43) // protocol version 19.
_OP_READDIRPLUS = int32(44) // protocol version 21.
_OP_FUSE_RENAME2 = int32(45) // protocol version 23.
// The following entries don't have to be compatible across Go-FUSE versions.
_OP_NOTIFY_ENTRY = int32(100)
_OP_NOTIFY_INODE = int32(101)
_OP_NOTIFY_DELETE = int32(102) // protocol version 18
_OPCODE_COUNT = int32(103)
)
////////////////////////////////////////////////////////////////
func doInit(server *Server, req *request) {
input := (*InitIn)(req.inData)
if input.Major != _FUSE_KERNEL_VERSION {
log.Printf("Major versions does not match. Given %d, want %d\n", input.Major, _FUSE_KERNEL_VERSION)
req.status = EIO
return
}
if input.Minor < _MINIMUM_MINOR_VERSION {
log.Printf("Minor version is less than we support. Given %d, want at least %d\n", input.Minor, _MINIMUM_MINOR_VERSION)
req.status = EIO
return
}
server.reqMu.Lock()
server.kernelSettings = *input
server.kernelSettings.Flags = input.Flags & (CAP_ASYNC_READ | CAP_BIG_WRITES | CAP_FILE_OPS |
CAP_AUTO_INVAL_DATA | CAP_READDIRPLUS | CAP_NO_OPEN_SUPPORT)
if input.Minor >= 13 {
server.setSplice()
}
server.reqMu.Unlock()
out := &InitOut{
Major: _FUSE_KERNEL_VERSION,
Minor: _OUR_MINOR_VERSION,
MaxReadAhead: input.MaxReadAhead,
Flags: server.kernelSettings.Flags,
MaxWrite: uint32(server.opts.MaxWrite),
CongestionThreshold: uint16(server.opts.MaxBackground * 3 / 4),
MaxBackground: uint16(server.opts.MaxBackground),
}
if out.Minor > input.Minor {
out.Minor = input.Minor
}
if out.Minor <= 22 {
tweaked := *req.handler
// v8-v22 don't have TimeGran and further fields.
tweaked.OutputSize = 24
req.handler = &tweaked
}
req.outData = unsafe.Pointer(out)
req.status = OK
}
func doOpen(server *Server, req *request) {
out := (*OpenOut)(req.outData)
status := server.fileSystem.Open((*OpenIn)(req.inData), out)
req.status = status
if status != OK {
return
}
}
func doCreate(server *Server, req *request) {
out := (*CreateOut)(req.outData)
status := server.fileSystem.Create((*CreateIn)(req.inData), req.filenames[0], out)
req.status = status
}
func doReadDir(server *Server, req *request) {
in := (*ReadIn)(req.inData)
buf := server.allocOut(req, in.Size)
out := NewDirEntryList(buf, uint64(in.Offset))
code := server.fileSystem.ReadDir(in, out)
req.flatData = out.bytes()
req.status = code
}
func doReadDirPlus(server *Server, req *request) {
in := (*ReadIn)(req.inData)
buf := server.allocOut(req, in.Size)
out := NewDirEntryList(buf, uint64(in.Offset))
code := server.fileSystem.ReadDirPlus(in, out)
req.flatData = out.bytes()
req.status = code
}
func doOpenDir(server *Server, req *request) {
out := (*OpenOut)(req.outData)
status := server.fileSystem.OpenDir((*OpenIn)(req.inData), out)
req.status = status
}
func doSetattr(server *Server, req *request) {
out := (*AttrOut)(req.outData)
req.status = server.fileSystem.SetAttr((*SetAttrIn)(req.inData), out)
}
func doWrite(server *Server, req *request) {
n, status := server.fileSystem.Write((*WriteIn)(req.inData), req.arg)
o := (*WriteOut)(req.outData)
o.Size = n
req.status = status
}
const _SECURITY_CAPABILITY = "security.capability"
const _SECURITY_ACL = "system.posix_acl_access"
const _SECURITY_ACL_DEFAULT = "system.posix_acl_default"
func doGetXAttr(server *Server, req *request) {
if server.opts.DisableXAttrs {
req.status = ENOSYS
return
}
if server.opts.IgnoreSecurityLabels && req.inHeader.Opcode == _OP_GETXATTR {
fn := req.filenames[0]
if fn == _SECURITY_CAPABILITY || fn == _SECURITY_ACL_DEFAULT ||
fn == _SECURITY_ACL {
req.status = ENODATA
return
}
}
input := (*GetXAttrIn)(req.inData)
if input.Size == 0 {
out := (*GetXAttrOut)(req.outData)
switch req.inHeader.Opcode {
case _OP_GETXATTR:
// TODO(hanwen): double check this. For getxattr, input.Size
// field refers to the size of the attribute, so it usually
// is not 0.
sz, code := server.fileSystem.GetXAttrSize(req.inHeader, req.filenames[0])
if code.Ok() {
out.Size = uint32(sz)
}
req.status = code
return
case _OP_LISTXATTR:
data, code := server.fileSystem.ListXAttr(req.inHeader)
if code.Ok() {
out.Size = uint32(len(data))
}
req.status = code
return
}
}
req.outData = nil
var data []byte
switch req.inHeader.Opcode {
case _OP_GETXATTR:
data, req.status = server.fileSystem.GetXAttrData(req.inHeader, req.filenames[0])
case _OP_LISTXATTR:
data, req.status = server.fileSystem.ListXAttr(req.inHeader)
default:
log.Panicf("xattr opcode %v", req.inHeader.Opcode)
req.status = ENOSYS
}
if len(data) > int(input.Size) {
req.status = ERANGE
}
if !req.status.Ok() {
return
}
req.flatData = data
}
func doGetAttr(server *Server, req *request) {
out := (*AttrOut)(req.outData)
s := server.fileSystem.GetAttr((*GetAttrIn)(req.inData), out)
req.status = s
}
// doForget - forget one NodeId
func doForget(server *Server, req *request) {
if !server.opts.RememberInodes {
server.fileSystem.Forget(req.inHeader.NodeId, (*ForgetIn)(req.inData).Nlookup)
}
}
// doBatchForget - forget a list of NodeIds
func doBatchForget(server *Server, req *request) {
in := (*_BatchForgetIn)(req.inData)
wantBytes := uintptr(in.Count) * unsafe.Sizeof(_ForgetOne{})
if uintptr(len(req.arg)) < wantBytes {
// We have no return value to complain, so log an error.
log.Printf("Too few bytes for batch forget. Got %d bytes, want %d (%d entries)",
len(req.arg), wantBytes, in.Count)
}
h := &reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(&req.arg[0])),
Len: int(in.Count),
Cap: int(in.Count),
}
forgets := *(*[]_ForgetOne)(unsafe.Pointer(h))
for i, f := range forgets {
if server.opts.Debug {
log.Printf("doBatchForget: forgetting %d of %d: NodeId: %d, Nlookup: %d", i+1, len(forgets), f.NodeId, f.Nlookup)
}
server.fileSystem.Forget(f.NodeId, f.Nlookup)
}
}
func doReadlink(server *Server, req *request) {
req.flatData, req.status = server.fileSystem.Readlink(req.inHeader)
}
func doLookup(server *Server, req *request) {
out := (*EntryOut)(req.outData)
s := server.fileSystem.Lookup(req.inHeader, req.filenames[0], out)
req.status = s
req.outData = unsafe.Pointer(out)
}
func doMknod(server *Server, req *request) {
out := (*EntryOut)(req.outData)
req.status = server.fileSystem.Mknod((*MknodIn)(req.inData), req.filenames[0], out)
}
func doMkdir(server *Server, req *request) {
out := (*EntryOut)(req.outData)
req.status = server.fileSystem.Mkdir((*MkdirIn)(req.inData), req.filenames[0], out)
}
func doUnlink(server *Server, req *request) {
req.status = server.fileSystem.Unlink(req.inHeader, req.filenames[0])
}
func doRmdir(server *Server, req *request) {
req.status = server.fileSystem.Rmdir(req.inHeader, req.filenames[0])
}
func doLink(server *Server, req *request) {
out := (*EntryOut)(req.outData)
req.status = server.fileSystem.Link((*LinkIn)(req.inData), req.filenames[0], out)
}
func doRead(server *Server, req *request) {
in := (*ReadIn)(req.inData)
buf := server.allocOut(req, in.Size)
req.readResult, req.status = server.fileSystem.Read(in, buf)
if fd, ok := req.readResult.(*readResultFd); ok {
req.fdData = fd
req.flatData = nil
} else if req.readResult != nil && req.status.Ok() {
req.flatData, req.status = req.readResult.Bytes(buf)
}
}
func doFlush(server *Server, req *request) {
req.status = server.fileSystem.Flush((*FlushIn)(req.inData))
}
func doRelease(server *Server, req *request) {
server.fileSystem.Release((*ReleaseIn)(req.inData))
}
func doFsync(server *Server, req *request) {
req.status = server.fileSystem.Fsync((*FsyncIn)(req.inData))
}
func doReleaseDir(server *Server, req *request) {
server.fileSystem.ReleaseDir((*ReleaseIn)(req.inData))
}
func doFsyncDir(server *Server, req *request) {
req.status = server.fileSystem.FsyncDir((*FsyncIn)(req.inData))
}
func doSetXAttr(server *Server, req *request) {
splits := bytes.SplitN(req.arg, []byte{0}, 2)
req.status = server.fileSystem.SetXAttr((*SetXAttrIn)(req.inData), string(splits[0]), splits[1])
}
func doRemoveXAttr(server *Server, req *request) {
req.status = server.fileSystem.RemoveXAttr(req.inHeader, req.filenames[0])
}
func doAccess(server *Server, req *request) {
req.status = server.fileSystem.Access((*AccessIn)(req.inData))
}
func doSymlink(server *Server, req *request) {
out := (*EntryOut)(req.outData)
req.status = server.fileSystem.Symlink(req.inHeader, req.filenames[1], req.filenames[0], out)
}
func doRename(server *Server, req *request) {
req.status = server.fileSystem.Rename((*RenameIn)(req.inData), req.filenames[0], req.filenames[1])
}
func doStatFs(server *Server, req *request) {
out := (*StatfsOut)(req.outData)
req.status = server.fileSystem.StatFs(req.inHeader, out)
if req.status == ENOSYS && runtime.GOOS == "darwin" {
// OSX FUSE requires Statfs to be implemented for the
// mount to succeed.
*out = StatfsOut{}
req.status = OK
}
}
func doIoctl(server *Server, req *request) {
req.status = ENOSYS
}
func doDestroy(server *Server, req *request) {
req.status = OK
}
func doFallocate(server *Server, req *request) {
req.status = server.fileSystem.Fallocate((*FallocateIn)(req.inData))
}
////////////////////////////////////////////////////////////////
type operationFunc func(*Server, *request)
type castPointerFunc func(unsafe.Pointer) interface{}
type operationHandler struct {
Name string
Func operationFunc
InputSize uintptr
OutputSize uintptr
DecodeIn castPointerFunc
DecodeOut castPointerFunc
FileNames int
FileNameOut bool
}
var operationHandlers []*operationHandler
func operationName(op int32) string {
h := getHandler(op)
if h == nil {
return "unknown"
}
return h.Name
}
func getHandler(o int32) *operationHandler {
if o >= _OPCODE_COUNT {
return nil
}
return operationHandlers[o]
}
func init() {
operationHandlers = make([]*operationHandler, _OPCODE_COUNT)
for i := range operationHandlers {
operationHandlers[i] = &operationHandler{Name: "UNKNOWN"}
}
fileOps := []int32{_OP_READLINK, _OP_NOTIFY_ENTRY, _OP_NOTIFY_DELETE}
for _, op := range fileOps {
operationHandlers[op].FileNameOut = true
}
for op, sz := range map[int32]uintptr{
_OP_FORGET: unsafe.Sizeof(ForgetIn{}),
_OP_BATCH_FORGET: unsafe.Sizeof(_BatchForgetIn{}),
_OP_GETATTR: unsafe.Sizeof(GetAttrIn{}),
_OP_SETATTR: unsafe.Sizeof(SetAttrIn{}),
_OP_MKNOD: unsafe.Sizeof(MknodIn{}),
_OP_MKDIR: unsafe.Sizeof(MkdirIn{}),
_OP_RENAME: unsafe.Sizeof(RenameIn{}),
_OP_LINK: unsafe.Sizeof(LinkIn{}),
_OP_OPEN: unsafe.Sizeof(OpenIn{}),
_OP_READ: unsafe.Sizeof(ReadIn{}),
_OP_WRITE: unsafe.Sizeof(WriteIn{}),
_OP_RELEASE: unsafe.Sizeof(ReleaseIn{}),
_OP_FSYNC: unsafe.Sizeof(FsyncIn{}),
_OP_SETXATTR: unsafe.Sizeof(SetXAttrIn{}),
_OP_GETXATTR: unsafe.Sizeof(GetXAttrIn{}),
_OP_LISTXATTR: unsafe.Sizeof(GetXAttrIn{}),
_OP_FLUSH: unsafe.Sizeof(FlushIn{}),
_OP_INIT: unsafe.Sizeof(InitIn{}),
_OP_OPENDIR: unsafe.Sizeof(OpenIn{}),
_OP_READDIR: unsafe.Sizeof(ReadIn{}),
_OP_RELEASEDIR: unsafe.Sizeof(ReleaseIn{}),
_OP_FSYNCDIR: unsafe.Sizeof(FsyncIn{}),
_OP_ACCESS: unsafe.Sizeof(AccessIn{}),
_OP_CREATE: unsafe.Sizeof(CreateIn{}),
_OP_INTERRUPT: unsafe.Sizeof(InterruptIn{}),
_OP_BMAP: unsafe.Sizeof(_BmapIn{}),
_OP_IOCTL: unsafe.Sizeof(_IoctlIn{}),
_OP_POLL: unsafe.Sizeof(_PollIn{}),
_OP_FALLOCATE: unsafe.Sizeof(FallocateIn{}),
_OP_READDIRPLUS: unsafe.Sizeof(ReadIn{}),
} {
operationHandlers[op].InputSize = sz
}
for op, sz := range map[int32]uintptr{
_OP_LOOKUP: unsafe.Sizeof(EntryOut{}),
_OP_GETATTR: unsafe.Sizeof(AttrOut{}),
_OP_SETATTR: unsafe.Sizeof(AttrOut{}),
_OP_SYMLINK: unsafe.Sizeof(EntryOut{}),
_OP_MKNOD: unsafe.Sizeof(EntryOut{}),
_OP_MKDIR: unsafe.Sizeof(EntryOut{}),
_OP_LINK: unsafe.Sizeof(EntryOut{}),
_OP_OPEN: unsafe.Sizeof(OpenOut{}),
_OP_WRITE: unsafe.Sizeof(WriteOut{}),
_OP_STATFS: unsafe.Sizeof(StatfsOut{}),
_OP_GETXATTR: unsafe.Sizeof(GetXAttrOut{}),
_OP_LISTXATTR: unsafe.Sizeof(GetXAttrOut{}),
_OP_INIT: unsafe.Sizeof(InitOut{}),
_OP_OPENDIR: unsafe.Sizeof(OpenOut{}),
_OP_CREATE: unsafe.Sizeof(CreateOut{}),
_OP_BMAP: unsafe.Sizeof(_BmapOut{}),
_OP_IOCTL: unsafe.Sizeof(_IoctlOut{}),
_OP_POLL: unsafe.Sizeof(_PollOut{}),
_OP_NOTIFY_ENTRY: unsafe.Sizeof(NotifyInvalEntryOut{}),
_OP_NOTIFY_INODE: unsafe.Sizeof(NotifyInvalInodeOut{}),
_OP_NOTIFY_DELETE: unsafe.Sizeof(NotifyInvalDeleteOut{}),
} {
operationHandlers[op].OutputSize = sz
}
for op, v := range map[int32]string{
_OP_LOOKUP: "LOOKUP",
_OP_FORGET: "FORGET",
_OP_BATCH_FORGET: "BATCH_FORGET",
_OP_GETATTR: "GETATTR",
_OP_SETATTR: "SETATTR",
_OP_READLINK: "READLINK",
_OP_SYMLINK: "SYMLINK",
_OP_MKNOD: "MKNOD",
_OP_MKDIR: "MKDIR",
_OP_UNLINK: "UNLINK",
_OP_RMDIR: "RMDIR",
_OP_RENAME: "RENAME",
_OP_LINK: "LINK",
_OP_OPEN: "OPEN",
_OP_READ: "READ",
_OP_WRITE: "WRITE",
_OP_STATFS: "STATFS",
_OP_RELEASE: "RELEASE",
_OP_FSYNC: "FSYNC",
_OP_SETXATTR: "SETXATTR",
_OP_GETXATTR: "GETXATTR",
_OP_LISTXATTR: "LISTXATTR",
_OP_REMOVEXATTR: "REMOVEXATTR",
_OP_FLUSH: "FLUSH",
_OP_INIT: "INIT",
_OP_OPENDIR: "OPENDIR",
_OP_READDIR: "READDIR",
_OP_RELEASEDIR: "RELEASEDIR",
_OP_FSYNCDIR: "FSYNCDIR",
_OP_GETLK: "GETLK",
_OP_SETLK: "SETLK",
_OP_SETLKW: "SETLKW",
_OP_ACCESS: "ACCESS",
_OP_CREATE: "CREATE",
_OP_INTERRUPT: "INTERRUPT",
_OP_BMAP: "BMAP",
_OP_DESTROY: "DESTROY",
_OP_IOCTL: "IOCTL",
_OP_POLL: "POLL",
_OP_NOTIFY_ENTRY: "NOTIFY_ENTRY",
_OP_NOTIFY_INODE: "NOTIFY_INODE",
_OP_NOTIFY_DELETE: "NOTIFY_DELETE",
_OP_FALLOCATE: "FALLOCATE",
_OP_READDIRPLUS: "READDIRPLUS",
} {
operationHandlers[op].Name = v
}
for op, v := range map[int32]operationFunc{
_OP_OPEN: doOpen,
_OP_READDIR: doReadDir,
_OP_WRITE: doWrite,
_OP_OPENDIR: doOpenDir,
_OP_CREATE: doCreate,
_OP_SETATTR: doSetattr,
_OP_GETXATTR: doGetXAttr,
_OP_LISTXATTR: doGetXAttr,
_OP_GETATTR: doGetAttr,
_OP_FORGET: doForget,
_OP_BATCH_FORGET: doBatchForget,
_OP_READLINK: doReadlink,
_OP_INIT: doInit,
_OP_LOOKUP: doLookup,
_OP_MKNOD: doMknod,
_OP_MKDIR: doMkdir,
_OP_UNLINK: doUnlink,
_OP_RMDIR: doRmdir,
_OP_LINK: doLink,
_OP_READ: doRead,
_OP_FLUSH: doFlush,
_OP_RELEASE: doRelease,
_OP_FSYNC: doFsync,
_OP_RELEASEDIR: doReleaseDir,
_OP_FSYNCDIR: doFsyncDir,
_OP_SETXATTR: doSetXAttr,
_OP_REMOVEXATTR: doRemoveXAttr,
_OP_ACCESS: doAccess,
_OP_SYMLINK: doSymlink,
_OP_RENAME: doRename,
_OP_STATFS: doStatFs,
_OP_IOCTL: doIoctl,
_OP_DESTROY: doDestroy,
_OP_FALLOCATE: doFallocate,
_OP_READDIRPLUS: doReadDirPlus,
} {
operationHandlers[op].Func = v
}
// Outputs.
for op, f := range map[int32]castPointerFunc{
_OP_LOOKUP: func(ptr unsafe.Pointer) interface{} { return (*EntryOut)(ptr) },
_OP_OPEN: func(ptr unsafe.Pointer) interface{} { return (*OpenOut)(ptr) },
_OP_OPENDIR: func(ptr unsafe.Pointer) interface{} { return (*OpenOut)(ptr) },
_OP_GETATTR: func(ptr unsafe.Pointer) interface{} { return (*AttrOut)(ptr) },
_OP_CREATE: func(ptr unsafe.Pointer) interface{} { return (*CreateOut)(ptr) },
_OP_LINK: func(ptr unsafe.Pointer) interface{} { return (*EntryOut)(ptr) },
_OP_SETATTR: func(ptr unsafe.Pointer) interface{} { return (*AttrOut)(ptr) },
_OP_INIT: func(ptr unsafe.Pointer) interface{} { return (*InitOut)(ptr) },
_OP_MKDIR: func(ptr unsafe.Pointer) interface{} { return (*EntryOut)(ptr) },
_OP_NOTIFY_ENTRY: func(ptr unsafe.Pointer) interface{} { return (*NotifyInvalEntryOut)(ptr) },
_OP_NOTIFY_INODE: func(ptr unsafe.Pointer) interface{} { return (*NotifyInvalInodeOut)(ptr) },
_OP_NOTIFY_DELETE: func(ptr unsafe.Pointer) interface{} { return (*NotifyInvalDeleteOut)(ptr) },
_OP_STATFS: func(ptr unsafe.Pointer) interface{} { return (*StatfsOut)(ptr) },
_OP_SYMLINK: func(ptr unsafe.Pointer) interface{} { return (*EntryOut)(ptr) },
} {
operationHandlers[op].DecodeOut = f
}
// Inputs.
for op, f := range map[int32]castPointerFunc{
_OP_FLUSH: func(ptr unsafe.Pointer) interface{} { return (*FlushIn)(ptr) },
_OP_GETATTR: func(ptr unsafe.Pointer) interface{} { return (*GetAttrIn)(ptr) },
_OP_GETXATTR: func(ptr unsafe.Pointer) interface{} { return (*GetXAttrIn)(ptr) },
_OP_LISTXATTR: func(ptr unsafe.Pointer) interface{} { return (*GetXAttrIn)(ptr) },
_OP_SETATTR: func(ptr unsafe.Pointer) interface{} { return (*SetAttrIn)(ptr) },
_OP_INIT: func(ptr unsafe.Pointer) interface{} { return (*InitIn)(ptr) },
_OP_IOCTL: func(ptr unsafe.Pointer) interface{} { return (*_IoctlIn)(ptr) },
_OP_OPEN: func(ptr unsafe.Pointer) interface{} { return (*OpenIn)(ptr) },
_OP_MKNOD: func(ptr unsafe.Pointer) interface{} { return (*MknodIn)(ptr) },
_OP_CREATE: func(ptr unsafe.Pointer) interface{} { return (*CreateIn)(ptr) },
_OP_READ: func(ptr unsafe.Pointer) interface{} { return (*ReadIn)(ptr) },
_OP_READDIR: func(ptr unsafe.Pointer) interface{} { return (*ReadIn)(ptr) },
_OP_ACCESS: func(ptr unsafe.Pointer) interface{} { return (*AccessIn)(ptr) },
_OP_FORGET: func(ptr unsafe.Pointer) interface{} { return (*ForgetIn)(ptr) },
_OP_BATCH_FORGET: func(ptr unsafe.Pointer) interface{} { return (*_BatchForgetIn)(ptr) },
_OP_LINK: func(ptr unsafe.Pointer) interface{} { return (*LinkIn)(ptr) },
_OP_MKDIR: func(ptr unsafe.Pointer) interface{} { return (*MkdirIn)(ptr) },
_OP_RELEASE: func(ptr unsafe.Pointer) interface{} { return (*ReleaseIn)(ptr) },
_OP_RELEASEDIR: func(ptr unsafe.Pointer) interface{} { return (*ReleaseIn)(ptr) },
_OP_FALLOCATE: func(ptr unsafe.Pointer) interface{} { return (*FallocateIn)(ptr) },
_OP_READDIRPLUS: func(ptr unsafe.Pointer) interface{} { return (*ReadIn)(ptr) },
_OP_RENAME: func(ptr unsafe.Pointer) interface{} { return (*RenameIn)(ptr) },
} {
operationHandlers[op].DecodeIn = f
}
// File name args.
for op, count := range map[int32]int{
_OP_CREATE: 1,
_OP_GETXATTR: 1,
_OP_LINK: 1,
_OP_LOOKUP: 1,
_OP_MKDIR: 1,
_OP_MKNOD: 1,
_OP_REMOVEXATTR: 1,
_OP_RENAME: 2,
_OP_RMDIR: 1,
_OP_SYMLINK: 2,
_OP_UNLINK: 1,
} {
operationHandlers[op].FileNames = count
}
var r request
sizeOfOutHeader := unsafe.Sizeof(OutHeader{})
for code, h := range operationHandlers {
if h.OutputSize+sizeOfOutHeader > unsafe.Sizeof(r.outBuf) {
log.Panicf("request output buffer too small: code %v, sz %d + %d %v", code, h.OutputSize, sizeOfOutHeader, h)
}
}
}

81
vendor/github.com/hanwen/go-fuse/fuse/pathfs/api.go generated vendored Normal file
View File

@@ -0,0 +1,81 @@
package pathfs
import (
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
)
// A filesystem API that uses paths rather than inodes. A minimal
// file system should have at least a functional GetAttr method.
// Typically, each call happens in its own goroutine, so take care to
// make the file system thread-safe.
//
// NewDefaultFileSystem provides a null implementation of required
// methods.
type FileSystem interface {
// Used for pretty printing.
String() string
// If called, provide debug output through the log package.
SetDebug(debug bool)
// Attributes. This function is the main entry point, through
// which FUSE discovers which files and directories exist.
//
// If the filesystem wants to implement hard-links, it should
// return consistent non-zero FileInfo.Ino data. Using
// hardlinks incurs a performance hit.
GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status)
// These should update the file's ctime too.
Chmod(name string, mode uint32, context *fuse.Context) (code fuse.Status)
Chown(name string, uid uint32, gid uint32, context *fuse.Context) (code fuse.Status)
Utimens(name string, Atime *time.Time, Mtime *time.Time, context *fuse.Context) (code fuse.Status)
Truncate(name string, size uint64, context *fuse.Context) (code fuse.Status)
Access(name string, mode uint32, context *fuse.Context) (code fuse.Status)
// Tree structure
Link(oldName string, newName string, context *fuse.Context) (code fuse.Status)
Mkdir(name string, mode uint32, context *fuse.Context) fuse.Status
Mknod(name string, mode uint32, dev uint32, context *fuse.Context) fuse.Status
Rename(oldName string, newName string, context *fuse.Context) (code fuse.Status)
Rmdir(name string, context *fuse.Context) (code fuse.Status)
Unlink(name string, context *fuse.Context) (code fuse.Status)
// Extended attributes.
GetXAttr(name string, attribute string, context *fuse.Context) (data []byte, code fuse.Status)
ListXAttr(name string, context *fuse.Context) (attributes []string, code fuse.Status)
RemoveXAttr(name string, attr string, context *fuse.Context) fuse.Status
SetXAttr(name string, attr string, data []byte, flags int, context *fuse.Context) fuse.Status
// Called after mount.
OnMount(nodeFs *PathNodeFs)
OnUnmount()
// File handling. If opening for writing, the file's mtime
// should be updated too.
Open(name string, flags uint32, context *fuse.Context) (file nodefs.File, code fuse.Status)
Create(name string, flags uint32, mode uint32, context *fuse.Context) (file nodefs.File, code fuse.Status)
// Directory handling
OpenDir(name string, context *fuse.Context) (stream []fuse.DirEntry, code fuse.Status)
// Symlinks.
Symlink(value string, linkName string, context *fuse.Context) (code fuse.Status)
Readlink(name string, context *fuse.Context) (string, fuse.Status)
StatFs(name string) *fuse.StatfsOut
}
type PathNodeFsOptions struct {
// If ClientInodes is set, use Inode returned from GetAttr to
// find hard-linked files.
ClientInodes bool
// Debug controls printing of debug information.
Debug bool
}

57
vendor/github.com/hanwen/go-fuse/fuse/pathfs/copy.go generated vendored Normal file
View File

@@ -0,0 +1,57 @@
package pathfs
import (
"os"
"github.com/hanwen/go-fuse/fuse"
)
func CopyFile(srcFs, destFs FileSystem, srcFile, destFile string, context *fuse.Context) fuse.Status {
src, code := srcFs.Open(srcFile, uint32(os.O_RDONLY), context)
if !code.Ok() {
return code
}
defer src.Release()
defer src.Flush()
attr, code := srcFs.GetAttr(srcFile, context)
if !code.Ok() {
return code
}
dst, code := destFs.Create(destFile, uint32(os.O_WRONLY|os.O_CREATE|os.O_TRUNC), attr.Mode, context)
if !code.Ok() {
return code
}
defer dst.Release()
defer dst.Flush()
buf := make([]byte, 128*(1<<10))
off := int64(0)
for {
res, code := src.Read(buf, off)
if !code.Ok() {
return code
}
data, code := res.Bytes(buf)
if !code.Ok() {
return code
}
if len(data) == 0 {
break
}
n, code := dst.Write(data, off)
if !code.Ok() {
return code
}
if int(n) < len(data) {
return fuse.EIO
}
if len(data) < len(buf) {
break
}
off += int64(len(data))
}
return fuse.OK
}

View File

@@ -0,0 +1,59 @@
package pathfs
import (
"io/ioutil"
"os"
"testing"
)
func TestCopyFile(t *testing.T) {
d1, err := ioutil.TempDir("", "go-fuse")
if err != nil {
t.Fatalf("TempDir failed: %v", err)
}
defer os.RemoveAll(d1)
d2, err := ioutil.TempDir("", "go-fuse")
if err != nil {
t.Fatalf("TempDir failed: %v", err)
}
defer os.RemoveAll(d2)
fs1 := NewLoopbackFileSystem(d1)
fs2 := NewLoopbackFileSystem(d2)
content1 := "blabla"
err = ioutil.WriteFile(d1+"/file", []byte(content1), 0644)
if err != nil {
t.Fatalf("WriteFile failed: %v", err)
}
code := CopyFile(fs1, fs2, "file", "file", nil)
if !code.Ok() {
t.Fatal("Unexpected ret code", code)
}
data, err := ioutil.ReadFile(d2 + "/file")
if content1 != string(data) {
t.Fatal("Unexpected content", string(data))
}
content2 := "foobar"
err = ioutil.WriteFile(d2+"/file", []byte(content2), 0644)
if err != nil {
t.Fatalf("WriteFile failed: %v", err)
}
// Copy back: should overwrite.
code = CopyFile(fs2, fs1, "file", "file", nil)
if !code.Ok() {
t.Fatal("Unexpected ret code", code)
}
data, err = ioutil.ReadFile(d1 + "/file")
if content2 != string(data) {
t.Fatal("Unexpected content", string(data))
}
}

118
vendor/github.com/hanwen/go-fuse/fuse/pathfs/default.go generated vendored Normal file
View File

@@ -0,0 +1,118 @@
package pathfs
import (
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
)
// NewDefaultFileSystem creates a filesystem that responds ENOSYS for
// all methods
func NewDefaultFileSystem() FileSystem {
return (*defaultFileSystem)(nil)
}
// defaultFileSystem implements a FileSystem that returns ENOSYS for every operation.
type defaultFileSystem struct{}
func (fs *defaultFileSystem) SetDebug(debug bool) {}
// defaultFileSystem
func (fs *defaultFileSystem) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
return nil, fuse.ENOSYS
}
func (fs *defaultFileSystem) GetXAttr(name string, attr string, context *fuse.Context) ([]byte, fuse.Status) {
return nil, fuse.ENOSYS
}
func (fs *defaultFileSystem) SetXAttr(name string, attr string, data []byte, flags int, context *fuse.Context) fuse.Status {
return fuse.ENOSYS
}
func (fs *defaultFileSystem) ListXAttr(name string, context *fuse.Context) ([]string, fuse.Status) {
return nil, fuse.ENOSYS
}
func (fs *defaultFileSystem) RemoveXAttr(name string, attr string, context *fuse.Context) fuse.Status {
return fuse.ENOSYS
}
func (fs *defaultFileSystem) Readlink(name string, context *fuse.Context) (string, fuse.Status) {
return "", fuse.ENOSYS
}
func (fs *defaultFileSystem) Mknod(name string, mode uint32, dev uint32, context *fuse.Context) fuse.Status {
return fuse.ENOSYS
}
func (fs *defaultFileSystem) Mkdir(name string, mode uint32, context *fuse.Context) fuse.Status {
return fuse.ENOSYS
}
func (fs *defaultFileSystem) Unlink(name string, context *fuse.Context) (code fuse.Status) {
return fuse.ENOSYS
}
func (fs *defaultFileSystem) Rmdir(name string, context *fuse.Context) (code fuse.Status) {
return fuse.ENOSYS
}
func (fs *defaultFileSystem) Symlink(value string, linkName string, context *fuse.Context) (code fuse.Status) {
return fuse.ENOSYS
}
func (fs *defaultFileSystem) Rename(oldName string, newName string, context *fuse.Context) (code fuse.Status) {
return fuse.ENOSYS
}
func (fs *defaultFileSystem) Link(oldName string, newName string, context *fuse.Context) (code fuse.Status) {
return fuse.ENOSYS
}
func (fs *defaultFileSystem) Chmod(name string, mode uint32, context *fuse.Context) (code fuse.Status) {
return fuse.ENOSYS
}
func (fs *defaultFileSystem) Chown(name string, uid uint32, gid uint32, context *fuse.Context) (code fuse.Status) {
return fuse.ENOSYS
}
func (fs *defaultFileSystem) Truncate(name string, offset uint64, context *fuse.Context) (code fuse.Status) {
return fuse.ENOSYS
}
func (fs *defaultFileSystem) Open(name string, flags uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) {
return nil, fuse.ENOSYS
}
func (fs *defaultFileSystem) OpenDir(name string, context *fuse.Context) (stream []fuse.DirEntry, status fuse.Status) {
return nil, fuse.ENOSYS
}
func (fs *defaultFileSystem) OnMount(nodeFs *PathNodeFs) {
}
func (fs *defaultFileSystem) OnUnmount() {
}
func (fs *defaultFileSystem) Access(name string, mode uint32, context *fuse.Context) (code fuse.Status) {
return fuse.ENOSYS
}
func (fs *defaultFileSystem) Create(name string, flags uint32, mode uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) {
return nil, fuse.ENOSYS
}
func (fs *defaultFileSystem) Utimens(name string, Atime *time.Time, Mtime *time.Time, context *fuse.Context) (code fuse.Status) {
return fuse.ENOSYS
}
func (fs *defaultFileSystem) String() string {
return "defaultFileSystem"
}
func (fs *defaultFileSystem) StatFs(name string) *fuse.StatfsOut {
return nil
}

163
vendor/github.com/hanwen/go-fuse/fuse/pathfs/locking.go generated vendored Normal file
View File

@@ -0,0 +1,163 @@
package pathfs
import (
"sync"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
)
type lockingFileSystem struct {
// Should be public so people reusing can access the wrapped
// FS.
FS FileSystem
lock sync.Mutex
}
// NewLockingFileSystem is a wrapper that makes a FileSystem
// threadsafe by serializing each operation.
func NewLockingFileSystem(pfs FileSystem) FileSystem {
l := new(lockingFileSystem)
l.FS = pfs
return l
}
func (fs *lockingFileSystem) String() string {
defer fs.locked()()
return fs.FS.String()
}
func (fs *lockingFileSystem) SetDebug(debug bool) {
defer fs.locked()()
fs.FS.SetDebug(debug)
}
func (fs *lockingFileSystem) StatFs(name string) *fuse.StatfsOut {
defer fs.locked()()
return fs.FS.StatFs(name)
}
func (fs *lockingFileSystem) locked() func() {
fs.lock.Lock()
return func() { fs.lock.Unlock() }
}
func (fs *lockingFileSystem) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
defer fs.locked()()
return fs.FS.GetAttr(name, context)
}
func (fs *lockingFileSystem) Readlink(name string, context *fuse.Context) (string, fuse.Status) {
defer fs.locked()()
return fs.FS.Readlink(name, context)
}
func (fs *lockingFileSystem) Mknod(name string, mode uint32, dev uint32, context *fuse.Context) fuse.Status {
defer fs.locked()()
return fs.FS.Mknod(name, mode, dev, context)
}
func (fs *lockingFileSystem) Mkdir(name string, mode uint32, context *fuse.Context) fuse.Status {
defer fs.locked()()
return fs.FS.Mkdir(name, mode, context)
}
func (fs *lockingFileSystem) Unlink(name string, context *fuse.Context) (code fuse.Status) {
defer fs.locked()()
return fs.FS.Unlink(name, context)
}
func (fs *lockingFileSystem) Rmdir(name string, context *fuse.Context) (code fuse.Status) {
defer fs.locked()()
return fs.FS.Rmdir(name, context)
}
func (fs *lockingFileSystem) Symlink(value string, linkName string, context *fuse.Context) (code fuse.Status) {
defer fs.locked()()
return fs.FS.Symlink(value, linkName, context)
}
func (fs *lockingFileSystem) Rename(oldName string, newName string, context *fuse.Context) (code fuse.Status) {
defer fs.locked()()
return fs.FS.Rename(oldName, newName, context)
}
func (fs *lockingFileSystem) Link(oldName string, newName string, context *fuse.Context) (code fuse.Status) {
defer fs.locked()()
return fs.FS.Link(oldName, newName, context)
}
func (fs *lockingFileSystem) Chmod(name string, mode uint32, context *fuse.Context) (code fuse.Status) {
defer fs.locked()()
return fs.FS.Chmod(name, mode, context)
}
func (fs *lockingFileSystem) Chown(name string, uid uint32, gid uint32, context *fuse.Context) (code fuse.Status) {
defer fs.locked()()
return fs.FS.Chown(name, uid, gid, context)
}
func (fs *lockingFileSystem) Truncate(name string, offset uint64, context *fuse.Context) (code fuse.Status) {
defer fs.locked()()
return fs.FS.Truncate(name, offset, context)
}
func (fs *lockingFileSystem) Open(name string, flags uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) {
file, code = fs.FS.Open(name, flags, context)
file = nodefs.NewLockingFile(&fs.lock, file)
return
}
func (fs *lockingFileSystem) OpenDir(name string, context *fuse.Context) (stream []fuse.DirEntry, status fuse.Status) {
defer fs.locked()()
return fs.FS.OpenDir(name, context)
}
func (fs *lockingFileSystem) OnMount(nodeFs *PathNodeFs) {
defer fs.locked()()
fs.FS.OnMount(nodeFs)
}
func (fs *lockingFileSystem) OnUnmount() {
defer fs.locked()()
fs.FS.OnUnmount()
}
func (fs *lockingFileSystem) Access(name string, mode uint32, context *fuse.Context) (code fuse.Status) {
defer fs.locked()()
return fs.FS.Access(name, mode, context)
}
func (fs *lockingFileSystem) Create(name string, flags uint32, mode uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) {
defer fs.locked()()
file, code = fs.FS.Create(name, flags, mode, context)
file = nodefs.NewLockingFile(&fs.lock, file)
return file, code
}
func (fs *lockingFileSystem) Utimens(name string, Atime *time.Time, Mtime *time.Time, context *fuse.Context) (code fuse.Status) {
defer fs.locked()()
return fs.FS.Utimens(name, Atime, Mtime, context)
}
func (fs *lockingFileSystem) GetXAttr(name string, attr string, context *fuse.Context) ([]byte, fuse.Status) {
defer fs.locked()()
return fs.FS.GetXAttr(name, attr, context)
}
func (fs *lockingFileSystem) SetXAttr(name string, attr string, data []byte, flags int, context *fuse.Context) fuse.Status {
defer fs.locked()()
return fs.FS.SetXAttr(name, attr, data, flags, context)
}
func (fs *lockingFileSystem) ListXAttr(name string, context *fuse.Context) ([]string, fuse.Status) {
defer fs.locked()()
return fs.FS.ListXAttr(name, context)
}
func (fs *lockingFileSystem) RemoveXAttr(name string, attr string, context *fuse.Context) fuse.Status {
defer fs.locked()()
return fs.FS.RemoveXAttr(name, attr, context)
}

View File

@@ -0,0 +1,162 @@
package pathfs
import (
"io"
"log"
"os"
"path/filepath"
"syscall"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
)
type loopbackFileSystem struct {
// TODO - this should need default fill in.
FileSystem
Root string
}
// A FUSE filesystem that shunts all request to an underlying file
// system. Its main purpose is to provide test coverage without
// having to build a synthetic filesystem.
func NewLoopbackFileSystem(root string) FileSystem {
return &loopbackFileSystem{
FileSystem: NewDefaultFileSystem(),
Root: root,
}
}
func (fs *loopbackFileSystem) OnMount(nodeFs *PathNodeFs) {
}
func (fs *loopbackFileSystem) OnUnmount() {}
func (fs *loopbackFileSystem) GetPath(relPath string) string {
return filepath.Join(fs.Root, relPath)
}
func (fs *loopbackFileSystem) GetAttr(name string, context *fuse.Context) (a *fuse.Attr, code fuse.Status) {
fullPath := fs.GetPath(name)
var err error = nil
st := syscall.Stat_t{}
if name == "" {
// When GetAttr is called for the toplevel directory, we always want
// to look through symlinks.
err = syscall.Stat(fullPath, &st)
} else {
err = syscall.Lstat(fullPath, &st)
}
if err != nil {
return nil, fuse.ToStatus(err)
}
a = &fuse.Attr{}
a.FromStat(&st)
return a, fuse.OK
}
func (fs *loopbackFileSystem) OpenDir(name string, context *fuse.Context) (stream []fuse.DirEntry, status fuse.Status) {
// What other ways beyond O_RDONLY are there to open
// directories?
f, err := os.Open(fs.GetPath(name))
if err != nil {
return nil, fuse.ToStatus(err)
}
want := 500
output := make([]fuse.DirEntry, 0, want)
for {
infos, err := f.Readdir(want)
for i := range infos {
// workaround forhttps://code.google.com/p/go/issues/detail?id=5960
if infos[i] == nil {
continue
}
n := infos[i].Name()
d := fuse.DirEntry{
Name: n,
}
if s := fuse.ToStatT(infos[i]); s != nil {
d.Mode = uint32(s.Mode)
} else {
log.Printf("ReadDir entry %q for %q has no stat info", n, name)
}
output = append(output, d)
}
if len(infos) < want || err == io.EOF {
break
}
if err != nil {
log.Println("Readdir() returned err:", err)
break
}
}
f.Close()
return output, fuse.OK
}
func (fs *loopbackFileSystem) Open(name string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) {
f, err := os.OpenFile(fs.GetPath(name), int(flags), 0)
if err != nil {
return nil, fuse.ToStatus(err)
}
return nodefs.NewLoopbackFile(f), fuse.OK
}
func (fs *loopbackFileSystem) Chmod(path string, mode uint32, context *fuse.Context) (code fuse.Status) {
err := os.Chmod(fs.GetPath(path), os.FileMode(mode))
return fuse.ToStatus(err)
}
func (fs *loopbackFileSystem) Chown(path string, uid uint32, gid uint32, context *fuse.Context) (code fuse.Status) {
return fuse.ToStatus(os.Chown(fs.GetPath(path), int(uid), int(gid)))
}
func (fs *loopbackFileSystem) Truncate(path string, offset uint64, context *fuse.Context) (code fuse.Status) {
return fuse.ToStatus(os.Truncate(fs.GetPath(path), int64(offset)))
}
func (fs *loopbackFileSystem) Readlink(name string, context *fuse.Context) (out string, code fuse.Status) {
f, err := os.Readlink(fs.GetPath(name))
return f, fuse.ToStatus(err)
}
func (fs *loopbackFileSystem) Mknod(name string, mode uint32, dev uint32, context *fuse.Context) (code fuse.Status) {
return fuse.ToStatus(syscall.Mknod(fs.GetPath(name), mode, int(dev)))
}
func (fs *loopbackFileSystem) Mkdir(path string, mode uint32, context *fuse.Context) (code fuse.Status) {
return fuse.ToStatus(os.Mkdir(fs.GetPath(path), os.FileMode(mode)))
}
// Don't use os.Remove, it removes twice (unlink followed by rmdir).
func (fs *loopbackFileSystem) Unlink(name string, context *fuse.Context) (code fuse.Status) {
return fuse.ToStatus(syscall.Unlink(fs.GetPath(name)))
}
func (fs *loopbackFileSystem) Rmdir(name string, context *fuse.Context) (code fuse.Status) {
return fuse.ToStatus(syscall.Rmdir(fs.GetPath(name)))
}
func (fs *loopbackFileSystem) Symlink(pointedTo string, linkName string, context *fuse.Context) (code fuse.Status) {
return fuse.ToStatus(os.Symlink(pointedTo, fs.GetPath(linkName)))
}
func (fs *loopbackFileSystem) Rename(oldPath string, newPath string, context *fuse.Context) (codee fuse.Status) {
err := os.Rename(fs.GetPath(oldPath), fs.GetPath(newPath))
return fuse.ToStatus(err)
}
func (fs *loopbackFileSystem) Link(orig string, newName string, context *fuse.Context) (code fuse.Status) {
return fuse.ToStatus(os.Link(fs.GetPath(orig), fs.GetPath(newName)))
}
func (fs *loopbackFileSystem) Access(name string, mode uint32, context *fuse.Context) (code fuse.Status) {
return fuse.ToStatus(syscall.Access(fs.GetPath(name), mode))
}
func (fs *loopbackFileSystem) Create(path string, flags uint32, mode uint32, context *fuse.Context) (fuseFile nodefs.File, code fuse.Status) {
f, err := os.OpenFile(fs.GetPath(path), int(flags)|os.O_CREATE, os.FileMode(mode))
return nodefs.NewLoopbackFile(f), fuse.ToStatus(err)
}

View File

@@ -0,0 +1,59 @@
package pathfs
import (
"syscall"
"time"
"github.com/hanwen/go-fuse/fuse"
)
func (fs *loopbackFileSystem) StatFs(name string) *fuse.StatfsOut {
s := syscall.Statfs_t{}
err := syscall.Statfs(fs.GetPath(name), &s)
if err == nil {
return &fuse.StatfsOut{
Blocks: s.Blocks,
Bsize: uint32(s.Bsize),
Bfree: s.Bfree,
Bavail: s.Bavail,
Files: s.Files,
Ffree: s.Ffree,
}
}
return nil
}
const _UTIME_NOW = ((1 << 30) - 1)
const _UTIME_OMIT = ((1 << 30) - 2)
// timeToTimeval - Convert time.Time to syscall.Timeval
//
// Note: This does not use syscall.NsecToTimespec because
// that does not work properly for times before 1970,
// see https://github.com/golang/go/issues/12777
func timeToTimeval(t *time.Time) syscall.Timeval {
var tv syscall.Timeval
tv.Usec = int32(t.Nanosecond() / 1000)
tv.Sec = t.Unix()
return tv
}
// OSX does not have the utimensat syscall neded to implement this properly.
// We do our best to emulate it using futimes.
func (fs *loopbackFileSystem) Utimens(path string, a *time.Time, m *time.Time, context *fuse.Context) fuse.Status {
tv := make([]syscall.Timeval, 2)
if a == nil {
tv[0].Usec = _UTIME_OMIT
} else {
tv[0] = timeToTimeval(a)
}
if m == nil {
tv[1].Usec = _UTIME_OMIT
} else {
tv[1] = timeToTimeval(m)
}
err := syscall.Utimes(path, tv)
return fuse.ToStatus(err)
}

View File

@@ -0,0 +1,79 @@
package pathfs
import (
"fmt"
"syscall"
"time"
"github.com/hanwen/go-fuse/fuse"
)
func (fs *loopbackFileSystem) StatFs(name string) *fuse.StatfsOut {
s := syscall.Statfs_t{}
err := syscall.Statfs(fs.GetPath(name), &s)
if err == nil {
return &fuse.StatfsOut{
Blocks: s.Blocks,
Bsize: uint32(s.Bsize),
Bfree: s.Bfree,
Bavail: s.Bavail,
Files: s.Files,
Ffree: s.Ffree,
Frsize: uint32(s.Frsize),
NameLen: uint32(s.Namelen),
}
}
return nil
}
func (fs *loopbackFileSystem) ListXAttr(name string, context *fuse.Context) ([]string, fuse.Status) {
data, err := listXAttr(fs.GetPath(name))
return data, fuse.ToStatus(err)
}
func (fs *loopbackFileSystem) RemoveXAttr(name string, attr string, context *fuse.Context) fuse.Status {
err := sysRemovexattr(fs.GetPath(name), attr)
return fuse.ToStatus(err)
}
func (fs *loopbackFileSystem) String() string {
return fmt.Sprintf("LoopbackFs(%s)", fs.Root)
}
func (fs *loopbackFileSystem) GetXAttr(name string, attr string, context *fuse.Context) ([]byte, fuse.Status) {
data := make([]byte, 1024)
data, err := getXAttr(fs.GetPath(name), attr, data)
return data, fuse.ToStatus(err)
}
func (fs *loopbackFileSystem) SetXAttr(name string, attr string, data []byte, flags int, context *fuse.Context) fuse.Status {
err := syscall.Setxattr(fs.GetPath(name), attr, data, flags)
return fuse.ToStatus(err)
}
const _UTIME_NOW = ((1 << 30) - 1)
const _UTIME_OMIT = ((1 << 30) - 2)
// Utimens - path based version of loopbackFile.Utimens()
func (fs *loopbackFileSystem) Utimens(path string, a *time.Time, m *time.Time, context *fuse.Context) (code fuse.Status) {
var ts [2]syscall.Timespec
if a == nil {
ts[0].Nsec = _UTIME_OMIT
} else {
ts[0] = syscall.NsecToTimespec(a.UnixNano())
ts[0].Nsec = 0
}
if m == nil {
ts[1].Nsec = _UTIME_OMIT
} else {
ts[1] = syscall.NsecToTimespec(m.UnixNano())
ts[1].Nsec = 0
}
err := sysUtimensat(0, fs.GetPath(path), &ts, _AT_SYMLINK_NOFOLLOW)
return fuse.ToStatus(err)
}

View File

@@ -0,0 +1,95 @@
package pathfs
import (
"io/ioutil"
"os"
"syscall"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
)
type ownerFs struct {
FileSystem
}
const _RANDOM_OWNER = 31415265
func (fs *ownerFs) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
if name == "" {
return &fuse.Attr{
Mode: fuse.S_IFDIR | 0755,
}, fuse.OK
}
a := &fuse.Attr{
Mode: fuse.S_IFREG | 0644,
}
a.Uid = _RANDOM_OWNER
a.Gid = _RANDOM_OWNER
return a, fuse.OK
}
func setupOwnerTest(t *testing.T, opts *nodefs.Options) (workdir string, cleanup func()) {
wd, err := ioutil.TempDir("", "go-fuse-owner_test")
fs := &ownerFs{NewDefaultFileSystem()}
nfs := NewPathNodeFs(fs, nil)
state, _, err := nodefs.MountRoot(wd, 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)
}
return wd, func() {
state.Unmount()
os.RemoveAll(wd)
}
}
func TestOwnerDefault(t *testing.T) {
wd, cleanup := setupOwnerTest(t, nodefs.NewOptions())
defer cleanup()
var stat syscall.Stat_t
err := syscall.Lstat(wd+"/foo", &stat)
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
if int(stat.Uid) != os.Getuid() || int(stat.Gid) != os.Getgid() {
t.Fatal("Should use current uid for mount")
}
}
func TestOwnerRoot(t *testing.T) {
wd, cleanup := setupOwnerTest(t, &nodefs.Options{})
defer cleanup()
var st syscall.Stat_t
err := syscall.Lstat(wd+"/foo", &st)
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
if st.Uid != _RANDOM_OWNER || st.Gid != _RANDOM_OWNER {
t.Fatal("Should use FS owner uid")
}
}
func TestOwnerOverride(t *testing.T) {
wd, cleanup := setupOwnerTest(t, &nodefs.Options{Owner: &fuse.Owner{Uid: 42, Gid: 43}})
defer cleanup()
var stat syscall.Stat_t
err := syscall.Lstat(wd+"/foo", &stat)
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
if stat.Uid != 42 || stat.Gid != 43 {
t.Fatal("Should use current uid for mount")
}
}

737
vendor/github.com/hanwen/go-fuse/fuse/pathfs/pathfs.go generated vendored Normal file
View File

@@ -0,0 +1,737 @@
package pathfs
import (
"fmt"
"log"
"path/filepath"
"strings"
"sync"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
)
// refCountedInode is used in clientInodeMap. The reference count is used to decide
// if the entry in clientInodeMap can be dropped.
type refCountedInode struct {
node *pathInode
refCount int
}
// PathNodeFs is the file system that can translate an inode back to a
// path. The path name is then used to call into an object that has
// the FileSystem interface.
//
// Lookups (ie. FileSystem.GetAttr) may return a inode number in its
// return value. The inode number ("clientInode") is used to indicate
// linked files.
type PathNodeFs struct {
debug bool
fs FileSystem
root *pathInode
connector *nodefs.FileSystemConnector
// protects clientInodeMap
pathLock sync.RWMutex
// This map lists all the parent links known for a given inode number.
clientInodeMap map[uint64]*refCountedInode
options *PathNodeFsOptions
}
// SetDebug toggles debug information: it will log path names for
// each operation processed.
func (fs *PathNodeFs) SetDebug(dbg bool) {
fs.debug = dbg
}
// Mount mounts a another node filesystem with the given root on the
// path. The last component of the path should not exist yet.
func (fs *PathNodeFs) Mount(path string, root nodefs.Node, opts *nodefs.Options) fuse.Status {
dir, name := filepath.Split(path)
if dir != "" {
dir = filepath.Clean(dir)
}
parent := fs.LookupNode(dir)
if parent == nil {
return fuse.ENOENT
}
return fs.connector.Mount(parent, name, root, opts)
}
// ForgetClientInodes forgets all known information on client inodes.
func (fs *PathNodeFs) ForgetClientInodes() {
if !fs.options.ClientInodes {
return
}
fs.pathLock.Lock()
fs.clientInodeMap = map[uint64]*refCountedInode{}
fs.root.forgetClientInodes()
fs.pathLock.Unlock()
}
// Rereads all inode numbers for all known files.
func (fs *PathNodeFs) RereadClientInodes() {
if !fs.options.ClientInodes {
return
}
fs.ForgetClientInodes()
fs.root.updateClientInodes()
}
// UnmountNode unmounts the node filesystem with the given root.
func (fs *PathNodeFs) UnmountNode(node *nodefs.Inode) fuse.Status {
return fs.connector.Unmount(node)
}
// UnmountNode unmounts the node filesystem with the given root.
func (fs *PathNodeFs) Unmount(path string) fuse.Status {
node := fs.Node(path)
if node == nil {
return fuse.ENOENT
}
return fs.connector.Unmount(node)
}
// String returns a name for this file system
func (fs *PathNodeFs) String() string {
name := fs.fs.String()
if name == "defaultFileSystem" {
name = fmt.Sprintf("%T", fs.fs)
name = strings.TrimLeft(name, "*")
}
return name
}
// Connector returns the FileSystemConnector (the bridge to the raw
// protocol) for this PathNodeFs.
func (fs *PathNodeFs) Connector() *nodefs.FileSystemConnector {
return fs.connector
}
// Node looks up the Inode that corresponds to the given path name, or
// returns nil if not found.
func (fs *PathNodeFs) Node(name string) *nodefs.Inode {
n, rest := fs.LastNode(name)
if len(rest) > 0 {
return nil
}
return n
}
// Like Node, but use Lookup to discover inodes we may not have yet.
func (fs *PathNodeFs) LookupNode(name string) *nodefs.Inode {
return fs.connector.LookupNode(fs.Root().Inode(), name)
}
// Path constructs a path for the given Inode. If the file system
// implements hard links through client-inode numbers, the path may
// not be unique.
func (fs *PathNodeFs) Path(node *nodefs.Inode) string {
pNode := node.Node().(*pathInode)
return pNode.GetPath()
}
// LastNode finds the deepest inode known corresponding to a path. The
// unknown part of the filename is also returned.
func (fs *PathNodeFs) LastNode(name string) (*nodefs.Inode, []string) {
return fs.connector.Node(fs.Root().Inode(), name)
}
// FileNotify notifies that file contents were changed within the
// given range. Use negative offset for metadata-only invalidation,
// and zero-length for invalidating all content.
func (fs *PathNodeFs) FileNotify(path string, off int64, length int64) fuse.Status {
node, r := fs.connector.Node(fs.root.Inode(), path)
if len(r) > 0 {
return fuse.ENOENT
}
return fs.connector.FileNotify(node, off, length)
}
// EntryNotify makes the kernel forget the entry data from the given
// name from a directory. After this call, the kernel will issue a
// new lookup request for the given name when necessary.
func (fs *PathNodeFs) EntryNotify(dir string, name string) fuse.Status {
node, rest := fs.connector.Node(fs.root.Inode(), dir)
if len(rest) > 0 {
return fuse.ENOENT
}
return fs.connector.EntryNotify(node, name)
}
// Notify ensures that the path name is invalidates: if the inode is
// known, it issues an file content Notify, if not, an entry notify
// for the path is issued. The latter will clear out non-existence
// cache entries.
func (fs *PathNodeFs) Notify(path string) fuse.Status {
node, rest := fs.connector.Node(fs.root.Inode(), path)
if len(rest) > 0 {
return fs.connector.EntryNotify(node, rest[0])
}
return fs.connector.FileNotify(node, 0, 0)
}
// AllFiles returns all open files for the inode corresponding with
// the given mask.
func (fs *PathNodeFs) AllFiles(name string, mask uint32) []nodefs.WithFlags {
n := fs.Node(name)
if n == nil {
return nil
}
return n.Files(mask)
}
// NewPathNodeFs returns a file system that translates from inodes to
// path names.
func NewPathNodeFs(fs FileSystem, opts *PathNodeFsOptions) *PathNodeFs {
root := &pathInode{}
root.fs = fs
if opts == nil {
opts = &PathNodeFsOptions{}
}
pfs := &PathNodeFs{
fs: fs,
root: root,
clientInodeMap: map[uint64]*refCountedInode{},
options: opts,
}
root.pathFs = pfs
return pfs
}
// Root returns the root node for the path filesystem.
func (fs *PathNodeFs) Root() nodefs.Node {
return fs.root
}
// This is a combination of dentry (entry in the file/directory and
// the inode). This structure is used to implement glue for FSes where
// there is a one-to-one mapping of paths and inodes.
type pathInode struct {
pathFs *PathNodeFs
fs FileSystem
// This is to correctly resolve hardlinks of the underlying
// real filesystem.
clientInode uint64
inode *nodefs.Inode
}
func (n *pathInode) OnMount(conn *nodefs.FileSystemConnector) {
n.pathFs.connector = conn
n.pathFs.fs.OnMount(n.pathFs)
}
func (n *pathInode) OnUnmount() {
}
// Drop all known client inodes. Must have the treeLock.
func (n *pathInode) forgetClientInodes() {
n.clientInode = 0
for _, ch := range n.Inode().FsChildren() {
ch.Node().(*pathInode).forgetClientInodes()
}
}
func (fs *pathInode) Deletable() bool {
return true
}
func (n *pathInode) Inode() *nodefs.Inode {
return n.inode
}
func (n *pathInode) SetInode(node *nodefs.Inode) {
n.inode = node
}
// Reread all client nodes below this node. Must run outside the treeLock.
func (n *pathInode) updateClientInodes() {
n.GetAttr(&fuse.Attr{}, nil, nil)
for _, ch := range n.Inode().FsChildren() {
ch.Node().(*pathInode).updateClientInodes()
}
}
// GetPath returns the path relative to the mount governing this
// inode. It returns nil for mount if the file was deleted or the
// filesystem unmounted.
func (n *pathInode) GetPath() string {
if n == n.pathFs.root {
return ""
}
pathLen := 1
// The simple solution is to collect names, and reverse join
// them, them, but since this is a hot path, we take some
// effort to avoid allocations.
n.pathFs.pathLock.RLock()
walkUp := n.Inode()
// TODO - guess depth?
segments := make([]string, 0, 10)
for {
parent, name := walkUp.Parent()
if parent == nil {
break
}
segments = append(segments, name)
pathLen += len(name) + 1
walkUp = parent
}
pathLen--
pathBytes := make([]byte, 0, pathLen)
for i := len(segments) - 1; i >= 0; i-- {
pathBytes = append(pathBytes, segments[i]...)
if i > 0 {
pathBytes = append(pathBytes, '/')
}
}
n.pathFs.pathLock.RUnlock()
path := string(pathBytes)
if n.pathFs.debug {
log.Printf("Inode = %q (%s)", path, n.fs.String())
}
if walkUp != n.pathFs.root.Inode() {
// This might happen if the node has been removed from
// the tree using unlink, but we are forced to run
// some file system operation, because the file is
// still opened.
// TODO - add a deterministic disambiguating suffix.
return ".deleted"
}
return path
}
func (n *pathInode) OnAdd(parent *nodefs.Inode, name string) {
// TODO it would be logical to increment the clientInodeMap reference count
// here. However, as the inode number is loaded lazily, we cannot do it
// yet.
}
func (n *pathInode) rmChild(name string) *pathInode {
childInode := n.Inode().RmChild(name)
if childInode == nil {
return nil
}
return childInode.Node().(*pathInode)
}
func (n *pathInode) OnRemove(parent *nodefs.Inode, name string) {
if n.clientInode == 0 || !n.pathFs.options.ClientInodes || n.Inode().IsDir() {
return
}
n.pathFs.pathLock.Lock()
r := n.pathFs.clientInodeMap[n.clientInode]
if r != nil {
r.refCount--
if r.refCount == 0 {
delete(n.pathFs.clientInodeMap, n.clientInode)
}
}
n.pathFs.pathLock.Unlock()
}
// setClientInode sets the inode number if has not been set yet.
// This function exists to allow lazy-loading of the inode number.
func (n *pathInode) setClientInode(ino uint64) {
if ino == 0 || n.clientInode != 0 || !n.pathFs.options.ClientInodes || n.Inode().IsDir() {
return
}
n.pathFs.pathLock.Lock()
defer n.pathFs.pathLock.Unlock()
n.clientInode = ino
n.pathFs.clientInodeMap[ino] = &refCountedInode{node: n, refCount: 1}
}
func (n *pathInode) OnForget() {
if n.clientInode == 0 || !n.pathFs.options.ClientInodes || n.Inode().IsDir() {
return
}
n.pathFs.pathLock.Lock()
delete(n.pathFs.clientInodeMap, n.clientInode)
n.pathFs.pathLock.Unlock()
}
////////////////////////////////////////////////////////////////
// FS operations
func (n *pathInode) StatFs() *fuse.StatfsOut {
return n.fs.StatFs(n.GetPath())
}
func (n *pathInode) Readlink(c *fuse.Context) ([]byte, fuse.Status) {
path := n.GetPath()
val, err := n.fs.Readlink(path, c)
return []byte(val), err
}
func (n *pathInode) Access(mode uint32, context *fuse.Context) (code fuse.Status) {
p := n.GetPath()
return n.fs.Access(p, mode, context)
}
func (n *pathInode) GetXAttr(attribute string, context *fuse.Context) (data []byte, code fuse.Status) {
return n.fs.GetXAttr(n.GetPath(), attribute, context)
}
func (n *pathInode) RemoveXAttr(attr string, context *fuse.Context) fuse.Status {
p := n.GetPath()
return n.fs.RemoveXAttr(p, attr, context)
}
func (n *pathInode) SetXAttr(attr string, data []byte, flags int, context *fuse.Context) fuse.Status {
return n.fs.SetXAttr(n.GetPath(), attr, data, flags, context)
}
func (n *pathInode) ListXAttr(context *fuse.Context) (attrs []string, code fuse.Status) {
return n.fs.ListXAttr(n.GetPath(), context)
}
func (n *pathInode) Flush(file nodefs.File, openFlags uint32, context *fuse.Context) (code fuse.Status) {
return file.Flush()
}
func (n *pathInode) OpenDir(context *fuse.Context) ([]fuse.DirEntry, fuse.Status) {
return n.fs.OpenDir(n.GetPath(), context)
}
func (n *pathInode) Mknod(name string, mode uint32, dev uint32, context *fuse.Context) (*nodefs.Inode, fuse.Status) {
fullPath := filepath.Join(n.GetPath(), name)
code := n.fs.Mknod(fullPath, mode, dev, context)
var child *nodefs.Inode
if code.Ok() {
pNode := n.createChild(name, false)
child = pNode.Inode()
}
return child, code
}
func (n *pathInode) Mkdir(name string, mode uint32, context *fuse.Context) (*nodefs.Inode, fuse.Status) {
fullPath := filepath.Join(n.GetPath(), name)
code := n.fs.Mkdir(fullPath, mode, context)
var child *nodefs.Inode
if code.Ok() {
pNode := n.createChild(name, true)
child = pNode.Inode()
}
return child, code
}
func (n *pathInode) Unlink(name string, context *fuse.Context) (code fuse.Status) {
code = n.fs.Unlink(filepath.Join(n.GetPath(), name), context)
if code.Ok() {
n.Inode().RmChild(name)
}
return code
}
func (n *pathInode) Rmdir(name string, context *fuse.Context) (code fuse.Status) {
code = n.fs.Rmdir(filepath.Join(n.GetPath(), name), context)
if code.Ok() {
n.Inode().RmChild(name)
}
return code
}
func (n *pathInode) Symlink(name string, content string, context *fuse.Context) (*nodefs.Inode, fuse.Status) {
fullPath := filepath.Join(n.GetPath(), name)
code := n.fs.Symlink(content, fullPath, context)
var child *nodefs.Inode
if code.Ok() {
pNode := n.createChild(name, false)
child = pNode.Inode()
}
return child, code
}
func (n *pathInode) Rename(oldName string, newParent nodefs.Node, newName string, context *fuse.Context) (code fuse.Status) {
p := newParent.(*pathInode)
oldPath := filepath.Join(n.GetPath(), oldName)
newPath := filepath.Join(p.GetPath(), newName)
code = n.fs.Rename(oldPath, newPath, context)
if code.Ok() {
// The rename may have overwritten another file, remove it from the tree
p.Inode().RmChild(newName)
ch := n.Inode().RmChild(oldName)
if ch != nil {
// oldName may have been forgotten in the meantime.
p.Inode().AddChild(newName, ch)
}
}
return code
}
func (n *pathInode) Link(name string, existingFsnode nodefs.Node, context *fuse.Context) (*nodefs.Inode, fuse.Status) {
if !n.pathFs.options.ClientInodes {
return nil, fuse.ENOSYS
}
newPath := filepath.Join(n.GetPath(), name)
existing := existingFsnode.(*pathInode)
oldPath := existing.GetPath()
code := n.fs.Link(oldPath, newPath, context)
var a *fuse.Attr
if code.Ok() {
a, code = n.fs.GetAttr(newPath, context)
}
var child *nodefs.Inode
if code.Ok() {
if existing.clientInode != 0 && existing.clientInode == a.Ino {
child = existing.Inode()
n.Inode().AddChild(name, existing.Inode())
} else {
pNode := n.createChild(name, false)
child = pNode.Inode()
pNode.clientInode = a.Ino
}
}
return child, code
}
func (n *pathInode) Create(name string, flags uint32, mode uint32, context *fuse.Context) (nodefs.File, *nodefs.Inode, fuse.Status) {
var child *nodefs.Inode
fullPath := filepath.Join(n.GetPath(), name)
file, code := n.fs.Create(fullPath, flags, mode, context)
if code.Ok() {
pNode := n.createChild(name, false)
child = pNode.Inode()
}
return file, child, code
}
func (n *pathInode) createChild(name string, isDir bool) *pathInode {
i := &pathInode{
fs: n.fs,
pathFs: n.pathFs,
}
n.Inode().NewChild(name, isDir, i)
return i
}
func (n *pathInode) Open(flags uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) {
p := n.GetPath()
file, code = n.fs.Open(p, flags, context)
if n.pathFs.debug {
file = &nodefs.WithFlags{
File: file,
Description: n.GetPath(),
}
}
return
}
func (n *pathInode) Lookup(out *fuse.Attr, name string, context *fuse.Context) (node *nodefs.Inode, code fuse.Status) {
fullPath := filepath.Join(n.GetPath(), name)
fi, code := n.fs.GetAttr(fullPath, context)
if code.Ok() {
node = n.findChild(fi, name, fullPath).Inode()
*out = *fi
}
return node, code
}
func (n *pathInode) findChild(fi *fuse.Attr, name string, fullPath string) (out *pathInode) {
if fi.Ino > 0 {
n.pathFs.pathLock.RLock()
r := n.pathFs.clientInodeMap[fi.Ino]
if r != nil {
out = r.node
r.refCount++
if fi.Nlink == 1 {
log.Printf("Found linked inode, but Nlink == 1, ino=%d, fullPath=%q", fi.Ino, fullPath)
}
}
n.pathFs.pathLock.RUnlock()
}
if out == nil {
out = n.createChild(name, fi.IsDir())
out.setClientInode(fi.Ino)
} else {
n.Inode().AddChild(name, out.Inode())
}
return out
}
func (n *pathInode) GetAttr(out *fuse.Attr, file nodefs.File, context *fuse.Context) (code fuse.Status) {
var fi *fuse.Attr
if file == nil {
// Linux currently (tested on v4.4) does not pass a file descriptor for
// fstat. To be able to stat a deleted file we have to find ourselves
// an open fd.
file = n.Inode().AnyFile()
}
if file != nil {
code = file.GetAttr(out)
}
if file == nil || code == fuse.ENOSYS || code == fuse.EBADF {
fi, code = n.fs.GetAttr(n.GetPath(), context)
}
if fi != nil {
n.setClientInode(fi.Ino)
}
if fi != nil && !fi.IsDir() && fi.Nlink == 0 {
fi.Nlink = 1
}
if fi != nil {
*out = *fi
}
return code
}
func (n *pathInode) Chmod(file nodefs.File, perms uint32, context *fuse.Context) (code fuse.Status) {
// Note that Linux currently (Linux 4.4) DOES NOT pass a file descriptor
// to FUSE for fchmod. We still check because that may change in the future.
if file != nil {
code = file.Chmod(perms)
if code != fuse.ENOSYS {
return code
}
}
files := n.Inode().Files(fuse.O_ANYWRITE)
for _, f := range files {
// TODO - pass context
code = f.Chmod(perms)
if code.Ok() {
return
}
}
if len(files) == 0 || code == fuse.ENOSYS || code == fuse.EBADF {
code = n.fs.Chmod(n.GetPath(), perms, context)
}
return code
}
func (n *pathInode) Chown(file nodefs.File, uid uint32, gid uint32, context *fuse.Context) (code fuse.Status) {
// Note that Linux currently (Linux 4.4) DOES NOT pass a file descriptor
// to FUSE for fchown. We still check because it may change in the future.
if file != nil {
code = file.Chown(uid, gid)
if code != fuse.ENOSYS {
return code
}
}
files := n.Inode().Files(fuse.O_ANYWRITE)
for _, f := range files {
// TODO - pass context
code = f.Chown(uid, gid)
if code.Ok() {
return code
}
}
if len(files) == 0 || code == fuse.ENOSYS || code == fuse.EBADF {
// TODO - can we get just FATTR_GID but not FATTR_UID ?
code = n.fs.Chown(n.GetPath(), uid, gid, context)
}
return code
}
func (n *pathInode) Truncate(file nodefs.File, size uint64, context *fuse.Context) (code fuse.Status) {
// A file descriptor was passed in AND the filesystem implements the
// operation on the file handle. This the common case for ftruncate.
if file != nil {
code = file.Truncate(size)
if code != fuse.ENOSYS {
return code
}
}
files := n.Inode().Files(fuse.O_ANYWRITE)
for _, f := range files {
// TODO - pass context
code = f.Truncate(size)
if code.Ok() {
return code
}
}
if len(files) == 0 || code == fuse.ENOSYS || code == fuse.EBADF {
code = n.fs.Truncate(n.GetPath(), size, context)
}
return code
}
func (n *pathInode) Utimens(file nodefs.File, atime *time.Time, mtime *time.Time, context *fuse.Context) (code fuse.Status) {
// Note that Linux currently (Linux 4.4) DOES NOT pass a file descriptor
// to FUSE for futimens. We still check because it may change in the future.
if file != nil {
code = file.Utimens(atime, mtime)
if code != fuse.ENOSYS {
return code
}
}
files := n.Inode().Files(fuse.O_ANYWRITE)
for _, f := range files {
// TODO - pass context
code = f.Utimens(atime, mtime)
if code.Ok() {
return code
}
}
if len(files) == 0 || code == fuse.ENOSYS || code == fuse.EBADF {
code = n.fs.Utimens(n.GetPath(), atime, mtime, context)
}
return code
}
func (n *pathInode) Fallocate(file nodefs.File, off uint64, size uint64, mode uint32, context *fuse.Context) (code fuse.Status) {
if file != nil {
code = file.Allocate(off, size, mode)
if code.Ok() {
return code
}
}
files := n.Inode().Files(fuse.O_ANYWRITE)
for _, f := range files {
// TODO - pass context
code = f.Allocate(off, size, mode)
if code.Ok() {
return code
}
}
return code
}
func (n *pathInode) Read(file nodefs.File, dest []byte, off int64, context *fuse.Context) (fuse.ReadResult, fuse.Status) {
if file != nil {
return file.Read(dest, off)
}
return nil, fuse.ENOSYS
}
func (n *pathInode) Write(file nodefs.File, data []byte, off int64, context *fuse.Context) (written uint32, code fuse.Status) {
if file != nil {
return file.Write(data, off)
}
return 0, fuse.ENOSYS
}

View File

@@ -0,0 +1,128 @@
package pathfs
import (
"fmt"
"path/filepath"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
)
// PrefixFileSystem adds a path prefix to incoming calls.
type prefixFileSystem struct {
FileSystem FileSystem
Prefix string
}
func NewPrefixFileSystem(fs FileSystem, prefix string) FileSystem {
return &prefixFileSystem{fs, prefix}
}
func (fs *prefixFileSystem) SetDebug(debug bool) {
fs.FileSystem.SetDebug(debug)
}
func (fs *prefixFileSystem) prefixed(n string) string {
return filepath.Join(fs.Prefix, n)
}
func (fs *prefixFileSystem) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
return fs.FileSystem.GetAttr(fs.prefixed(name), context)
}
func (fs *prefixFileSystem) Readlink(name string, context *fuse.Context) (string, fuse.Status) {
return fs.FileSystem.Readlink(fs.prefixed(name), context)
}
func (fs *prefixFileSystem) Mknod(name string, mode uint32, dev uint32, context *fuse.Context) fuse.Status {
return fs.FileSystem.Mknod(fs.prefixed(name), mode, dev, context)
}
func (fs *prefixFileSystem) Mkdir(name string, mode uint32, context *fuse.Context) fuse.Status {
return fs.FileSystem.Mkdir(fs.prefixed(name), mode, context)
}
func (fs *prefixFileSystem) Unlink(name string, context *fuse.Context) (code fuse.Status) {
return fs.FileSystem.Unlink(fs.prefixed(name), context)
}
func (fs *prefixFileSystem) Rmdir(name string, context *fuse.Context) (code fuse.Status) {
return fs.FileSystem.Rmdir(fs.prefixed(name), context)
}
func (fs *prefixFileSystem) Symlink(value string, linkName string, context *fuse.Context) (code fuse.Status) {
return fs.FileSystem.Symlink(value, fs.prefixed(linkName), context)
}
func (fs *prefixFileSystem) Rename(oldName string, newName string, context *fuse.Context) (code fuse.Status) {
return fs.FileSystem.Rename(fs.prefixed(oldName), fs.prefixed(newName), context)
}
func (fs *prefixFileSystem) Link(oldName string, newName string, context *fuse.Context) (code fuse.Status) {
return fs.FileSystem.Link(fs.prefixed(oldName), fs.prefixed(newName), context)
}
func (fs *prefixFileSystem) Chmod(name string, mode uint32, context *fuse.Context) (code fuse.Status) {
return fs.FileSystem.Chmod(fs.prefixed(name), mode, context)
}
func (fs *prefixFileSystem) Chown(name string, uid uint32, gid uint32, context *fuse.Context) (code fuse.Status) {
return fs.FileSystem.Chown(fs.prefixed(name), uid, gid, context)
}
func (fs *prefixFileSystem) Truncate(name string, offset uint64, context *fuse.Context) (code fuse.Status) {
return fs.FileSystem.Truncate(fs.prefixed(name), offset, context)
}
func (fs *prefixFileSystem) Open(name string, flags uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) {
return fs.FileSystem.Open(fs.prefixed(name), flags, context)
}
func (fs *prefixFileSystem) OpenDir(name string, context *fuse.Context) (stream []fuse.DirEntry, status fuse.Status) {
return fs.FileSystem.OpenDir(fs.prefixed(name), context)
}
func (fs *prefixFileSystem) OnMount(nodeFs *PathNodeFs) {
fs.FileSystem.OnMount(nodeFs)
}
func (fs *prefixFileSystem) OnUnmount() {
fs.FileSystem.OnUnmount()
}
func (fs *prefixFileSystem) Access(name string, mode uint32, context *fuse.Context) (code fuse.Status) {
return fs.FileSystem.Access(fs.prefixed(name), mode, context)
}
func (fs *prefixFileSystem) Create(name string, flags uint32, mode uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) {
return fs.FileSystem.Create(fs.prefixed(name), flags, mode, context)
}
func (fs *prefixFileSystem) Utimens(name string, Atime *time.Time, Mtime *time.Time, context *fuse.Context) (code fuse.Status) {
return fs.FileSystem.Utimens(fs.prefixed(name), Atime, Mtime, context)
}
func (fs *prefixFileSystem) GetXAttr(name string, attr string, context *fuse.Context) ([]byte, fuse.Status) {
return fs.FileSystem.GetXAttr(fs.prefixed(name), attr, context)
}
func (fs *prefixFileSystem) SetXAttr(name string, attr string, data []byte, flags int, context *fuse.Context) fuse.Status {
return fs.FileSystem.SetXAttr(fs.prefixed(name), attr, data, flags, context)
}
func (fs *prefixFileSystem) ListXAttr(name string, context *fuse.Context) ([]string, fuse.Status) {
return fs.FileSystem.ListXAttr(fs.prefixed(name), context)
}
func (fs *prefixFileSystem) RemoveXAttr(name string, attr string, context *fuse.Context) fuse.Status {
return fs.FileSystem.RemoveXAttr(fs.prefixed(name), attr, context)
}
func (fs *prefixFileSystem) String() string {
return fmt.Sprintf("prefixFileSystem(%s,%s)", fs.FileSystem.String(), fs.Prefix)
}
func (fs *prefixFileSystem) StatFs(name string) *fuse.StatfsOut {
return fs.FileSystem.StatFs(fs.prefixed(name))
}

View File

@@ -0,0 +1,119 @@
package pathfs
import (
"fmt"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
)
// NewReadonlyFileSystem returns a wrapper that only exposes read-only
// operations.
func NewReadonlyFileSystem(fs FileSystem) FileSystem {
return &readonlyFileSystem{fs}
}
type readonlyFileSystem struct {
FileSystem
}
func (fs *readonlyFileSystem) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
return fs.FileSystem.GetAttr(name, context)
}
func (fs *readonlyFileSystem) Readlink(name string, context *fuse.Context) (string, fuse.Status) {
return fs.FileSystem.Readlink(name, context)
}
func (fs *readonlyFileSystem) Mknod(name string, mode uint32, dev uint32, context *fuse.Context) fuse.Status {
return fuse.EPERM
}
func (fs *readonlyFileSystem) Mkdir(name string, mode uint32, context *fuse.Context) fuse.Status {
return fuse.EPERM
}
func (fs *readonlyFileSystem) Unlink(name string, context *fuse.Context) (code fuse.Status) {
return fuse.EPERM
}
func (fs *readonlyFileSystem) Rmdir(name string, context *fuse.Context) (code fuse.Status) {
return fuse.EPERM
}
func (fs *readonlyFileSystem) Symlink(value string, linkName string, context *fuse.Context) (code fuse.Status) {
return fuse.EPERM
}
func (fs *readonlyFileSystem) Rename(oldName string, newName string, context *fuse.Context) (code fuse.Status) {
return fuse.EPERM
}
func (fs *readonlyFileSystem) Link(oldName string, newName string, context *fuse.Context) (code fuse.Status) {
return fuse.EPERM
}
func (fs *readonlyFileSystem) Chmod(name string, mode uint32, context *fuse.Context) (code fuse.Status) {
return fuse.EPERM
}
func (fs *readonlyFileSystem) Chown(name string, uid uint32, gid uint32, context *fuse.Context) (code fuse.Status) {
return fuse.EPERM
}
func (fs *readonlyFileSystem) Truncate(name string, offset uint64, context *fuse.Context) (code fuse.Status) {
return fuse.EPERM
}
func (fs *readonlyFileSystem) Open(name string, flags uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) {
if flags&fuse.O_ANYWRITE != 0 {
return nil, fuse.EPERM
}
file, code = fs.FileSystem.Open(name, flags, context)
return nodefs.NewReadOnlyFile(file), code
}
func (fs *readonlyFileSystem) OpenDir(name string, context *fuse.Context) (stream []fuse.DirEntry, status fuse.Status) {
return fs.FileSystem.OpenDir(name, context)
}
func (fs *readonlyFileSystem) OnMount(nodeFs *PathNodeFs) {
fs.FileSystem.OnMount(nodeFs)
}
func (fs *readonlyFileSystem) OnUnmount() {
fs.FileSystem.OnUnmount()
}
func (fs *readonlyFileSystem) String() string {
return fmt.Sprintf("readonlyFileSystem(%v)", fs.FileSystem)
}
func (fs *readonlyFileSystem) Access(name string, mode uint32, context *fuse.Context) (code fuse.Status) {
return fs.FileSystem.Access(name, mode, context)
}
func (fs *readonlyFileSystem) Create(name string, flags uint32, mode uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) {
return nil, fuse.EPERM
}
func (fs *readonlyFileSystem) Utimens(name string, atime *time.Time, ctime *time.Time, context *fuse.Context) (code fuse.Status) {
return fuse.EPERM
}
func (fs *readonlyFileSystem) GetXAttr(name string, attr string, context *fuse.Context) ([]byte, fuse.Status) {
return fs.FileSystem.GetXAttr(name, attr, context)
}
func (fs *readonlyFileSystem) SetXAttr(name string, attr string, data []byte, flags int, context *fuse.Context) fuse.Status {
return fuse.EPERM
}
func (fs *readonlyFileSystem) ListXAttr(name string, context *fuse.Context) ([]string, fuse.Status) {
return fs.FileSystem.ListXAttr(name, context)
}
func (fs *readonlyFileSystem) RemoveXAttr(name string, attr string, context *fuse.Context) fuse.Status {
return fuse.EPERM
}

View File

@@ -0,0 +1,140 @@
package pathfs
import (
"bytes"
"syscall"
"unsafe"
)
var _zero uintptr
func getXAttr(path string, attr string, dest []byte) (value []byte, err error) {
sz, err := sysGetxattr(path, attr, dest)
for sz > cap(dest) && err == nil {
dest = make([]byte, sz)
sz, err = sysGetxattr(path, attr, dest)
}
if err != nil {
return nil, err
}
return dest[:sz], err
}
func listXAttr(path string) (attributes []string, err error) {
dest := make([]byte, 0)
sz, err := sysListxattr(path, dest)
if err != nil {
return nil, err
}
for sz > cap(dest) && err == nil {
dest = make([]byte, sz)
sz, err = sysListxattr(path, dest)
}
if sz == 0 {
return nil, err
}
// -1 to drop the final empty slice.
dest = dest[:sz-1]
attributesBytes := bytes.Split(dest, []byte{0})
attributes = make([]string, len(attributesBytes))
for i, v := range attributesBytes {
attributes[i] = string(v)
}
return attributes, err
}
// Below is cut & paste from std lib syscall, so gccgo 4.8.1 can
// compile this too.
func sysGetxattr(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 {
_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
}
func sysRemovexattr(path string, attr string) (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
}
_, _, e1 := syscall.Syscall(syscall.SYS_REMOVEXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), 0)
if e1 != 0 {
err = e1
}
return
}
func sysListxattr(path string, dest []byte) (sz int, err error) {
var _p0 *byte
_p0, err = syscall.BytePtrFromString(path)
if err != nil {
return
}
var _p1 unsafe.Pointer
if len(dest) > 0 {
_p1 = unsafe.Pointer(&dest[0])
} else {
_p1 = unsafe.Pointer(&_zero)
}
r0, _, e1 := syscall.Syscall(syscall.SYS_LISTXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(dest)))
sz = int(r0)
if e1 != 0 {
err = e1
}
return
}
func sysSetxattr(path string, attr string, val []byte, flag int) error {
return syscall.Setxattr(path, attr, val, flag)
}
const _AT_SYMLINK_NOFOLLOW = 0x100
// Linux kernel syscall utimensat(2)
//
// Needed to implement SetAttr on symlinks correctly as only utimensat provides
// AT_SYMLINK_NOFOLLOW.
func sysUtimensat(dirfd int, pathname string, times *[2]syscall.Timespec, flags int) (err error) {
// Null-terminated version of pathname
p0, err := syscall.BytePtrFromString(pathname)
if err != nil {
return err
}
_, _, e1 := syscall.Syscall6(syscall.SYS_UTIMENSAT,
uintptr(dirfd), uintptr(unsafe.Pointer(p0)), uintptr(unsafe.Pointer(times)), uintptr(flags), 0, 0)
if e1 != 0 {
err = syscall.Errno(e1)
}
return
}

View File

@@ -0,0 +1,45 @@
// +build linux
package pathfs
import (
"os"
"reflect"
"syscall"
"testing"
)
func TestSysUtimensat(t *testing.T) {
symlink := "/tmp/TestSysUtimensat"
os.Remove(symlink)
err := os.Symlink("/nonexisting/file", symlink)
if err != nil {
t.Fatal(err)
}
var ts [2]syscall.Timespec
// Atime
ts[0].Nsec = 1111
ts[0].Sec = 2222
// Mtime
ts[1].Nsec = 3333
ts[1].Sec = 4444
// Linux specific.
err = sysUtimensat(0, symlink, &ts, _AT_SYMLINK_NOFOLLOW)
if err != nil {
t.Fatal(err)
}
var st syscall.Stat_t
err = syscall.Lstat(symlink, &st)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(st.Atim, ts[0]) {
t.Errorf("Wrong atime: %v", st.Atim)
}
if !reflect.DeepEqual(st.Mtim, ts[1]) {
t.Errorf("Wrong mtime: %v", st.Mtim)
}
}

View File

@@ -0,0 +1,9 @@
package pathfs
import "flag"
// VerboseTest returns true if the testing framework is run with -v.
func VerboseTest() bool {
flag := flag.Lookup("test.v")
return flag != nil && flag.Value.String() == "true"
}

View File

@@ -0,0 +1,202 @@
// +build linux
package pathfs
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"syscall"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
)
var xattrGolden = map[string][]byte{
"user.attr1": []byte("val1"),
"user.attr2": []byte("val2")}
var xattrFilename = "filename"
type XAttrTestFs struct {
filename string
attrs map[string][]byte
FileSystem
}
func NewXAttrFs(nm string, m map[string][]byte) *XAttrTestFs {
x := &XAttrTestFs{
filename: nm,
attrs: make(map[string][]byte, len(m)),
FileSystem: NewDefaultFileSystem(),
}
for k, v := range m {
x.attrs[k] = v
}
return x
}
func (fs *XAttrTestFs) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
a := &fuse.Attr{}
if name == "" || name == "/" {
a.Mode = fuse.S_IFDIR | 0700
return a, fuse.OK
}
if name == fs.filename {
a.Mode = fuse.S_IFREG | 0600
return a, fuse.OK
}
return nil, fuse.ENOENT
}
func (fs *XAttrTestFs) SetXAttr(name string, attr string, data []byte, flags int, context *fuse.Context) fuse.Status {
if name != fs.filename {
return fuse.ENOENT
}
dest := make([]byte, len(data))
copy(dest, data)
fs.attrs[attr] = dest
return fuse.OK
}
func (fs *XAttrTestFs) GetXAttr(name string, attr string, context *fuse.Context) ([]byte, fuse.Status) {
if name != fs.filename {
return nil, fuse.ENOENT
}
v, ok := fs.attrs[attr]
if !ok {
return nil, fuse.ENODATA
}
return v, fuse.OK
}
func (fs *XAttrTestFs) ListXAttr(name string, context *fuse.Context) (data []string, code fuse.Status) {
if name != fs.filename {
return nil, fuse.ENOENT
}
for k := range fs.attrs {
data = append(data, k)
}
return data, fuse.OK
}
func (fs *XAttrTestFs) RemoveXAttr(name string, attr string, context *fuse.Context) fuse.Status {
if name != fs.filename {
return fuse.ENOENT
}
_, ok := fs.attrs[attr]
if !ok {
return fuse.ENODATA
}
delete(fs.attrs, attr)
return fuse.OK
}
func readXAttr(p, a string) (val []byte, err error) {
val = make([]byte, 1024)
return getXAttr(p, a, val)
}
func xattrTestCase(t *testing.T, nm string, m map[string][]byte) (mountPoint string, cleanup func()) {
xfs := NewXAttrFs(nm, m)
mountPoint, err := ioutil.TempDir("", "go-fuse-xattr_test")
if err != nil {
t.Fatalf("TempDir failed: %v", err)
}
nfs := NewPathNodeFs(xfs, nil)
state, _, err := nodefs.MountRoot(mountPoint, nfs.Root(),
&nodefs.Options{Debug: VerboseTest()})
if err != nil {
t.Fatalf("TempDir failed: %v", err)
}
go state.Serve()
return mountPoint, func() {
state.Unmount()
os.RemoveAll(mountPoint)
}
}
func TestXAttrNoAttrs(t *testing.T) {
nm := xattrFilename
mountPoint, clean := xattrTestCase(t, nm, make(map[string][]byte))
defer clean()
mounted := filepath.Join(mountPoint, nm)
attrs, err := listXAttr(mounted)
if err != nil {
t.Error("Unexpected ListXAttr error", err)
}
if len(attrs) > 0 {
t.Errorf("ListXAttr(%s) = %s, want empty slice", mounted, attrs)
}
}
func TestXAttrNoExist(t *testing.T) {
nm := xattrFilename
mountPoint, clean := xattrTestCase(t, nm, xattrGolden)
defer clean()
mounted := filepath.Join(mountPoint, nm)
_, err := os.Lstat(mounted)
if err != nil {
t.Error("Unexpected stat error", err)
}
val, err := readXAttr(mounted, "noexist")
if err == nil {
t.Error("Expected GetXAttr error", val)
}
}
func TestXAttrRead(t *testing.T) {
nm := xattrFilename
mountPoint, clean := xattrTestCase(t, nm, xattrGolden)
defer clean()
mounted := filepath.Join(mountPoint, nm)
attrs, err := listXAttr(mounted)
readback := make(map[string][]byte)
if err != nil {
t.Error("Unexpected ListXAttr error", err)
} else {
for _, a := range attrs {
val, err := readXAttr(mounted, a)
if err != nil {
t.Errorf("GetXAttr(%q) failed: %v", a, err)
}
readback[a] = val
}
}
if len(readback) != len(xattrGolden) {
t.Error("length mismatch", xattrGolden, readback)
} else {
for k, v := range readback {
if bytes.Compare(xattrGolden[k], v) != 0 {
t.Error("val mismatch", k, v, xattrGolden[k])
}
}
}
err = sysSetxattr(mounted, "third", []byte("value"), 0)
if err != nil {
t.Error("Setxattr error", err)
}
val, err := readXAttr(mounted, "third")
if err != nil || string(val) != "value" {
t.Error("Read back set xattr:", err, string(val))
}
sysRemovexattr(mounted, "third")
val, err = readXAttr(mounted, "third")
if err != syscall.ENODATA {
t.Error("Data not removed?", err, val)
}
}

246
vendor/github.com/hanwen/go-fuse/fuse/print.go generated vendored Normal file
View File

@@ -0,0 +1,246 @@
package fuse
import (
"fmt"
"os"
"strings"
"syscall"
)
var initFlagNames map[int64]string
var releaseFlagNames map[int64]string
var OpenFlagNames map[int64]string
var FuseOpenFlagNames map[int64]string
var accessFlagName map[int64]string
var writeFlagNames map[int64]string
var readFlagNames map[int64]string
func init() {
writeFlagNames = map[int64]string{
WRITE_CACHE: "CACHE",
WRITE_LOCKOWNER: "LOCKOWNER",
}
readFlagNames = map[int64]string{
READ_LOCKOWNER: "LOCKOWNER",
}
initFlagNames = map[int64]string{
CAP_ASYNC_READ: "ASYNC_READ",
CAP_POSIX_LOCKS: "POSIX_LOCKS",
CAP_FILE_OPS: "FILE_OPS",
CAP_ATOMIC_O_TRUNC: "ATOMIC_O_TRUNC",
CAP_EXPORT_SUPPORT: "EXPORT_SUPPORT",
CAP_BIG_WRITES: "BIG_WRITES",
CAP_DONT_MASK: "DONT_MASK",
CAP_SPLICE_WRITE: "SPLICE_WRITE",
CAP_SPLICE_MOVE: "SPLICE_MOVE",
CAP_SPLICE_READ: "SPLICE_READ",
CAP_FLOCK_LOCKS: "FLOCK_LOCKS",
CAP_IOCTL_DIR: "IOCTL_DIR",
CAP_AUTO_INVAL_DATA: "AUTO_INVAL_DATA",
CAP_READDIRPLUS: "READDIRPLUS",
CAP_READDIRPLUS_AUTO: "READDIRPLUS_AUTO",
CAP_ASYNC_DIO: "ASYNC_DIO",
CAP_WRITEBACK_CACHE: "WRITEBACK_CACHE",
CAP_NO_OPEN_SUPPORT: "NO_OPEN_SUPPORT",
}
releaseFlagNames = map[int64]string{
RELEASE_FLUSH: "FLUSH",
}
OpenFlagNames = map[int64]string{
int64(os.O_WRONLY): "WRONLY",
int64(os.O_RDWR): "RDWR",
int64(os.O_APPEND): "APPEND",
int64(syscall.O_ASYNC): "ASYNC",
int64(os.O_CREATE): "CREAT",
int64(os.O_EXCL): "EXCL",
int64(syscall.O_NOCTTY): "NOCTTY",
int64(syscall.O_NONBLOCK): "NONBLOCK",
int64(os.O_SYNC): "SYNC",
int64(os.O_TRUNC): "TRUNC",
int64(syscall.O_CLOEXEC): "CLOEXEC",
int64(syscall.O_DIRECTORY): "DIRECTORY",
}
FuseOpenFlagNames = map[int64]string{
FOPEN_DIRECT_IO: "DIRECT",
FOPEN_KEEP_CACHE: "CACHE",
FOPEN_NONSEEKABLE: "NONSEEK",
}
accessFlagName = map[int64]string{
X_OK: "x",
W_OK: "w",
R_OK: "r",
}
}
func FlagString(names map[int64]string, fl int64, def string) string {
s := []string{}
for k, v := range names {
if fl&k != 0 {
s = append(s, v)
fl ^= k
}
}
if len(s) == 0 && def != "" {
s = []string{def}
}
if fl != 0 {
s = append(s, fmt.Sprintf("0x%x", fl))
}
return strings.Join(s, ",")
}
func (me *ForgetIn) string() string {
return fmt.Sprintf("{Nlookup=%d}", me.Nlookup)
}
func (me *_BatchForgetIn) string() string {
return fmt.Sprintf("{Count=%d}", me.Count)
}
func (me *MkdirIn) string() string {
return fmt.Sprintf("{0%o (0%o)}", me.Mode, me.Umask)
}
func (me *RenameIn) string() string {
return fmt.Sprintf("{%d}", me.Newdir)
}
func (me *SetAttrIn) string() string {
s := []string{}
if me.Valid&FATTR_MODE != 0 {
s = append(s, fmt.Sprintf("mode 0%o", me.Mode))
}
if me.Valid&FATTR_UID != 0 {
s = append(s, fmt.Sprintf("uid %d", me.Uid))
}
if me.Valid&FATTR_GID != 0 {
s = append(s, fmt.Sprintf("uid %d", me.Gid))
}
if me.Valid&FATTR_SIZE != 0 {
s = append(s, fmt.Sprintf("size %d", me.Size))
}
if me.Valid&FATTR_ATIME != 0 {
s = append(s, fmt.Sprintf("atime %d.%09d", me.Atime, me.Atimensec))
}
if me.Valid&FATTR_MTIME != 0 {
s = append(s, fmt.Sprintf("mtime %d.%09d", me.Mtime, me.Mtimensec))
}
if me.Valid&FATTR_FH != 0 {
s = append(s, fmt.Sprintf("fh %d", me.Fh))
}
// TODO - FATTR_ATIME_NOW = (1 << 7), FATTR_MTIME_NOW = (1 << 8), FATTR_LOCKOWNER = (1 << 9)
return fmt.Sprintf("{%s}", strings.Join(s, ", "))
}
func (me *ReleaseIn) string() string {
return fmt.Sprintf("{Fh %d %s %s L%d}",
me.Fh, FlagString(OpenFlagNames, int64(me.Flags), ""),
FlagString(releaseFlagNames, int64(me.ReleaseFlags), ""),
me.LockOwner)
}
func (me *OpenIn) string() string {
return fmt.Sprintf("{%s}", FlagString(OpenFlagNames, int64(me.Flags), "O_RDONLY"))
}
func (me *OpenOut) string() string {
return fmt.Sprintf("{Fh %d %s}", me.Fh,
FlagString(FuseOpenFlagNames, int64(me.OpenFlags), ""))
}
func (me *InitIn) string() string {
return fmt.Sprintf("{%d.%d Ra 0x%x %s}",
me.Major, me.Minor, me.MaxReadAhead,
FlagString(initFlagNames, int64(me.Flags), ""))
}
func (me *InitOut) string() string {
return fmt.Sprintf("{%d.%d Ra 0x%x %s %d/%d Wr 0x%x Tg 0x%x}",
me.Major, me.Minor, me.MaxReadAhead,
FlagString(initFlagNames, int64(me.Flags), ""),
me.CongestionThreshold, me.MaxBackground, me.MaxWrite,
me.TimeGran)
}
func (s *FsyncIn) string() string {
return fmt.Sprintf("{Fh %d Flags %x}", s.Fh, s.FsyncFlags)
}
func (me *SetXAttrIn) string() string {
return fmt.Sprintf("{sz %d f%o}", me.Size, me.Flags)
}
func (me *GetXAttrIn) string() string {
return fmt.Sprintf("{sz %d}", me.Size)
}
func (me *GetXAttrOut) string() string {
return fmt.Sprintf("{sz %d}", me.Size)
}
func (me *AccessIn) string() string {
return fmt.Sprintf("{%s}", FlagString(accessFlagName, int64(me.Mask), ""))
}
func (me *FlushIn) string() string {
return fmt.Sprintf("{Fh %d}", me.Fh)
}
func (me *AttrOut) string() string {
return fmt.Sprintf(
"{A%d.%09d %v}",
me.AttrValid, me.AttrValidNsec, &me.Attr)
}
// Returned by LOOKUP
func (me *EntryOut) string() string {
return fmt.Sprintf("{NodeId: %d Generation=%d EntryValid=%d.%03d AttrValid=%d.%03d Attr=%v}",
me.NodeId, me.Generation, me.EntryValid, me.EntryValidNsec/1000000,
me.AttrValid, me.AttrValidNsec/1000000, &me.Attr)
}
func (me *CreateOut) string() string {
return fmt.Sprintf("{NodeId: %d Generation=%d %v %v}", me.NodeId, me.Generation, &me.EntryOut, &me.OpenOut)
}
func (me *StatfsOut) string() string {
return fmt.Sprintf(
"{blocks (%d,%d)/%d files %d/%d bs%d nl%d frs%d}",
me.Bfree, me.Bavail, me.Blocks, me.Ffree, me.Files,
me.Bsize, me.NameLen, me.Frsize)
}
func (o *NotifyInvalEntryOut) string() string {
return fmt.Sprintf("{parent %d sz %d}", o.Parent, o.NameLen)
}
func (o *NotifyInvalInodeOut) string() string {
return fmt.Sprintf("{ino %d off %d sz %d}", o.Ino, o.Off, o.Length)
}
func (o *NotifyInvalDeleteOut) string() string {
return fmt.Sprintf("{parent %d ch %d sz %d}", o.Parent, o.Child, o.NameLen)
}
func (f *FallocateIn) string() string {
return fmt.Sprintf("{Fh %d off %d sz %d mod 0%o}",
f.Fh, f.Offset, f.Length, f.Mode)
}
func (f *LinkIn) string() string {
return fmt.Sprintf("{Oldnodeid: %d}", f.Oldnodeid)
}
// Print pretty prints FUSE data types for kernel communication
func Print(obj interface{}) string {
t, ok := obj.(interface {
string() string
})
if ok {
return t.string()
}
return fmt.Sprintf("%T: %v", obj, obj)
}

50
vendor/github.com/hanwen/go-fuse/fuse/print_darwin.go generated vendored Normal file
View File

@@ -0,0 +1,50 @@
package fuse
import (
"fmt"
)
func init() {
initFlagNames[CAP_XTIMES] = "XTIMES"
initFlagNames[CAP_VOL_RENAME] = "VOL_RENAME"
initFlagNames[CAP_CASE_INSENSITIVE] = "CASE_INSENSITIVE"
}
func (a *Attr) string() string {
return fmt.Sprintf(
"{M0%o SZ=%d L=%d "+
"%d:%d "+
"%d %d:%d "+
"A %d.%09d "+
"M %d.%09d "+
"C %d.%09d}",
a.Mode, a.Size, a.Nlink,
a.Uid, a.Gid,
a.Blocks,
a.Rdev, a.Ino, a.Atime, a.Atimensec, a.Mtime, a.Mtimensec,
a.Ctime, a.Ctimensec)
}
func (me *CreateIn) string() string {
return fmt.Sprintf(
"{0%o [%s]}", me.Mode,
FlagString(OpenFlagNames, int64(me.Flags), "O_RDONLY"))
}
func (me *GetAttrIn) string() string { return "" }
func (me *MknodIn) string() string {
return fmt.Sprintf("{0%o, %d}", me.Mode, me.Rdev)
}
func (me *ReadIn) string() string {
return fmt.Sprintf("{Fh %d off %d sz %d %s}",
me.Fh, me.Offset, me.Size,
FlagString(readFlagNames, int64(me.ReadFlags), ""))
}
func (me *WriteIn) string() string {
return fmt.Sprintf("{Fh %d off %d sz %d %s}",
me.Fh, me.Offset, me.Size,
FlagString(writeFlagNames, int64(me.WriteFlags), ""))
}

58
vendor/github.com/hanwen/go-fuse/fuse/print_linux.go generated vendored Normal file
View File

@@ -0,0 +1,58 @@
package fuse
import (
"fmt"
"syscall"
)
func init() {
OpenFlagNames[syscall.O_DIRECT] = "DIRECT"
OpenFlagNames[syscall.O_LARGEFILE] = "LARGEFILE"
OpenFlagNames[syscall_O_NOATIME] = "NOATIME"
}
func (a *Attr) string() string {
return fmt.Sprintf(
"{M0%o SZ=%d L=%d "+
"%d:%d "+
"B%d*%d i%d:%d "+
"A %d.%09d "+
"M %d.%09d "+
"C %d.%09d}",
a.Mode, a.Size, a.Nlink,
a.Uid, a.Gid,
a.Blocks, a.Blksize,
a.Rdev, a.Ino, a.Atime, a.Atimensec, a.Mtime, a.Mtimensec,
a.Ctime, a.Ctimensec)
}
func (me *CreateIn) string() string {
return fmt.Sprintf(
"{0%o [%s] (0%o)}", me.Mode,
FlagString(OpenFlagNames, int64(me.Flags), "O_RDONLY"), me.Umask)
}
func (me *GetAttrIn) string() string {
return fmt.Sprintf("{Fh %d}", me.Fh_)
}
func (me *MknodIn) string() string {
return fmt.Sprintf("{0%o (0%o), %d}", me.Mode, me.Umask, me.Rdev)
}
func (me *ReadIn) string() string {
return fmt.Sprintf("{Fh %d off %d sz %d %s L %d %s}",
me.Fh, me.Offset, me.Size,
FlagString(readFlagNames, int64(me.ReadFlags), ""),
me.LockOwner,
FlagString(OpenFlagNames, int64(me.Flags), "RDONLY"))
}
func (me *WriteIn) string() string {
return fmt.Sprintf("{Fh %d off %d sz %d %s L %d %s}",
me.Fh, me.Offset, me.Size,
FlagString(writeFlagNames, int64(me.WriteFlags), ""),
me.LockOwner,
FlagString(OpenFlagNames, int64(me.Flags), "RDONLY"))
}

71
vendor/github.com/hanwen/go-fuse/fuse/read.go generated vendored Normal file
View File

@@ -0,0 +1,71 @@
package fuse
import (
"io"
"syscall"
)
// ReadResultData is the read return for returning bytes directly.
type readResultData struct {
// Raw bytes for the read.
Data []byte
}
func (r *readResultData) Size() int {
return len(r.Data)
}
func (r *readResultData) Done() {
}
func (r *readResultData) Bytes(buf []byte) ([]byte, Status) {
return r.Data, OK
}
func ReadResultData(b []byte) ReadResult {
return &readResultData{b}
}
func ReadResultFd(fd uintptr, off int64, sz int) ReadResult {
return &readResultFd{fd, off, sz}
}
// ReadResultFd is the read return for zero-copy file data.
type readResultFd struct {
// Splice from the following file.
Fd uintptr
// Offset within Fd, or -1 to use current offset.
Off int64
// Size of data to be loaded. Actual data available may be
// less at the EOF.
Sz int
}
// Reads raw bytes from file descriptor if necessary, using the passed
// buffer as storage.
func (r *readResultFd) Bytes(buf []byte) ([]byte, Status) {
sz := r.Sz
if len(buf) < sz {
sz = len(buf)
}
n, err := syscall.Pread(int(r.Fd), buf[:sz], r.Off)
if err == io.EOF {
err = nil
}
if n < 0 {
n = 0
}
return buf[:n], ToStatus(err)
}
func (r *readResultFd) Size() int {
return r.Sz
}
func (r *readResultFd) Done() {
}

213
vendor/github.com/hanwen/go-fuse/fuse/request.go generated vendored Normal file
View File

@@ -0,0 +1,213 @@
package fuse
import (
"bytes"
"fmt"
"log"
"strings"
"time"
"unsafe"
)
var sizeOfOutHeader = unsafe.Sizeof(OutHeader{})
var zeroOutBuf [outputHeaderSize]byte
type request struct {
inputBuf []byte
// These split up inputBuf.
inHeader *InHeader // generic header
inData unsafe.Pointer // per op data
arg []byte // flat data.
filenames []string // filename arguments
// Unstructured data, a pointer to the relevant XxxxOut struct.
outData unsafe.Pointer
status Status
flatData []byte
fdData *readResultFd
// In case of read, keep read result here so we can call
// Done() on it.
readResult ReadResult
// Start timestamp for timing info.
startTime time.Time
// All information pertaining to opcode of this request.
handler *operationHandler
// Request storage. For large inputs and outputs, use data
// obtained through bufferpool.
bufferPoolInputBuf []byte
bufferPoolOutputBuf []byte
// For small pieces of data, we use the following inlines
// arrays:
//
// Output header and structured data.
outBuf [outputHeaderSize]byte
// Input, if small enough to fit here.
smallInputBuf [128]byte
context Context
}
func (r *request) clear() {
r.inputBuf = nil
r.inHeader = nil
r.inData = nil
r.arg = nil
r.filenames = nil
r.outData = nil
r.status = OK
r.flatData = nil
r.fdData = nil
r.startTime = time.Time{}
r.handler = nil
r.readResult = nil
}
func (r *request) InputDebug() string {
val := " "
if r.handler.DecodeIn != nil {
val = fmt.Sprintf(" data: %v ", Print(r.handler.DecodeIn(r.inData)))
}
names := ""
if r.filenames != nil {
names = fmt.Sprintf("names: %v", r.filenames)
}
if len(r.arg) > 0 {
names += fmt.Sprintf(" %d bytes", len(r.arg))
}
return fmt.Sprintf("Dispatch %d: %s, NodeId: %v.%v%v",
r.inHeader.Unique, operationName(r.inHeader.Opcode),
r.inHeader.NodeId, val, names)
}
func (r *request) OutputDebug() string {
var dataStr string
if r.handler.DecodeOut != nil && r.outData != nil {
dataStr = Print(r.handler.DecodeOut(r.outData))
}
max := 1024
if len(dataStr) > max {
dataStr = dataStr[:max] + fmt.Sprintf(" ...trimmed")
}
flatStr := ""
if r.flatDataSize() > 0 {
if r.handler.FileNameOut {
s := strings.TrimRight(string(r.flatData), "\x00")
flatStr = fmt.Sprintf(" %q", s)
} else {
spl := ""
if r.fdData != nil {
spl = " (fd data)"
}
flatStr = fmt.Sprintf(" %d bytes data%s\n", r.flatDataSize(), spl)
}
}
return fmt.Sprintf("Serialize %d: %s code: %v value: %v%v",
r.inHeader.Unique, operationName(r.inHeader.Opcode), r.status,
dataStr, flatStr)
}
// setInput returns true if it takes ownership of the argument, false if not.
func (r *request) setInput(input []byte) bool {
if len(input) < len(r.smallInputBuf) {
copy(r.smallInputBuf[:], input)
r.inputBuf = r.smallInputBuf[:len(input)]
return false
}
r.inputBuf = input
r.bufferPoolInputBuf = input[:cap(input)]
return true
}
func (r *request) parse() {
inHSize := int(unsafe.Sizeof(InHeader{}))
if len(r.inputBuf) < inHSize {
log.Printf("Short read for input header: %v", r.inputBuf)
return
}
r.inHeader = (*InHeader)(unsafe.Pointer(&r.inputBuf[0]))
r.arg = r.inputBuf[:]
r.handler = getHandler(r.inHeader.Opcode)
if r.handler == nil {
log.Printf("Unknown opcode %d", r.inHeader.Opcode)
r.status = ENOSYS
return
}
if len(r.arg) < int(r.handler.InputSize) {
log.Printf("Short read for %v: %v", operationName(r.inHeader.Opcode), r.arg)
r.status = EIO
return
}
if r.handler.InputSize > 0 {
r.inData = unsafe.Pointer(&r.arg[0])
r.arg = r.arg[r.handler.InputSize:]
} else {
r.arg = r.arg[inHSize:]
}
count := r.handler.FileNames
if count > 0 {
if count == 1 {
r.filenames = []string{string(r.arg[:len(r.arg)-1])}
} else {
names := bytes.SplitN(r.arg[:len(r.arg)-1], []byte{0}, count)
r.filenames = make([]string, len(names))
for i, n := range names {
r.filenames[i] = string(n)
}
if len(names) != count {
log.Println("filename argument mismatch", names, count)
r.status = EIO
}
}
}
copy(r.outBuf[:r.handler.OutputSize+sizeOfOutHeader],
zeroOutBuf[:r.handler.OutputSize+sizeOfOutHeader])
r.outData = unsafe.Pointer(&r.outBuf[sizeOfOutHeader])
}
func (r *request) serializeHeader(dataSize int) (header []byte) {
dataLength := r.handler.OutputSize
if r.outData == nil || r.status > OK {
dataLength = 0
}
sizeOfOutHeader := unsafe.Sizeof(OutHeader{})
header = r.outBuf[:sizeOfOutHeader+dataLength]
o := (*OutHeader)(unsafe.Pointer(&header[0]))
o.Unique = r.inHeader.Unique
o.Status = int32(-r.status)
o.Length = uint32(
int(sizeOfOutHeader) + int(dataLength) + dataSize)
var asSlice []byte
toSlice(&asSlice, r.outData, dataLength)
copy(header[sizeOfOutHeader:], asSlice)
return header
}
func (r *request) flatDataSize() int {
if r.fdData != nil {
return r.fdData.Size()
}
return len(r.flatData)
}

View File

@@ -0,0 +1,9 @@
package fuse
const outputHeaderSize = 200
const (
_FUSE_KERNEL_VERSION = 7
_MINIMUM_MINOR_VERSION = 8
_OUR_MINOR_VERSION = 8
)

View File

@@ -0,0 +1,9 @@
package fuse
const outputHeaderSize = 160
const (
_FUSE_KERNEL_VERSION = 7
_MINIMUM_MINOR_VERSION = 12
_OUR_MINOR_VERSION = 23
)

555
vendor/github.com/hanwen/go-fuse/fuse/server.go generated vendored Normal file
View File

@@ -0,0 +1,555 @@
package fuse
import (
"fmt"
"log"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"syscall"
"time"
"unsafe"
)
const (
// The kernel caps writes at 128k.
MAX_KERNEL_WRITE = 128 * 1024
)
// Server contains the logic for reading from the FUSE device and
// translating it to RawFileSystem interface calls.
type Server struct {
// Empty if unmounted.
mountPoint string
fileSystem RawFileSystem
// writeMu serializes close and notify writes
writeMu sync.Mutex
// I/O with kernel and daemon.
mountFd int
latencies LatencyMap
opts *MountOptions
// Pool for request structs.
reqPool sync.Pool
// Pool for raw requests data
readPool sync.Pool
reqMu sync.Mutex
reqReaders int
kernelSettings InitIn
singleReader bool
canSplice bool
loops sync.WaitGroup
ready chan error
}
// SetDebug is deprecated. Use MountOptions.Debug instead.
func (ms *Server) SetDebug(dbg bool) {
// This will typically trigger the race detector.
ms.opts.Debug = dbg
}
// KernelSettings returns the Init message from the kernel, so
// filesystems can adapt to availability of features of the kernel
// driver.
func (ms *Server) KernelSettings() InitIn {
ms.reqMu.Lock()
s := ms.kernelSettings
ms.reqMu.Unlock()
return s
}
const _MAX_NAME_LEN = 20
// This type may be provided for recording latencies of each FUSE
// operation.
type LatencyMap interface {
Add(name string, dt time.Duration)
}
// RecordLatencies switches on collection of timing for each request
// coming from the kernel.P assing a nil argument switches off the
func (ms *Server) RecordLatencies(l LatencyMap) {
ms.latencies = l
}
// Unmount calls fusermount -u on the mount. This has the effect of
// shutting down the filesystem. After the Server is unmounted, it
// should be discarded.
func (ms *Server) Unmount() (err error) {
if ms.mountPoint == "" {
return nil
}
delay := time.Duration(0)
for try := 0; try < 5; try++ {
err = unmount(ms.mountPoint)
if err == nil {
break
}
// Sleep for a bit. This is not pretty, but there is
// no way we can be certain that the kernel thinks all
// open files have already been closed.
delay = 2*delay + 5*time.Millisecond
time.Sleep(delay)
}
if err != nil {
return
}
// Wait for event loops to exit.
ms.loops.Wait()
ms.mountPoint = ""
return err
}
// NewServer creates a server and attaches it to the given directory.
func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server, error) {
if opts == nil {
opts = &MountOptions{
MaxBackground: _DEFAULT_BACKGROUND_TASKS,
}
}
o := *opts
if o.SingleThreaded {
fs = NewLockingRawFileSystem(fs)
}
if o.Buffers == nil {
o.Buffers = defaultBufferPool
}
if o.MaxWrite < 0 {
o.MaxWrite = 0
}
if o.MaxWrite == 0 {
o.MaxWrite = 1 << 16
}
if o.MaxWrite > MAX_KERNEL_WRITE {
o.MaxWrite = MAX_KERNEL_WRITE
}
if o.Name == "" {
name := fs.String()
l := len(name)
if l > _MAX_NAME_LEN {
l = _MAX_NAME_LEN
}
o.Name = strings.Replace(name[:l], ",", ";", -1)
}
for _, s := range o.optionsStrings() {
if strings.Contains(s, ",") {
return nil, fmt.Errorf("found ',' in option string %q", s)
}
}
ms := &Server{
fileSystem: fs,
opts: &o,
// OSX has races when multiple routines read from the
// FUSE device: on unmount, sometime some reads do not
// error-out, meaning that unmount will hang.
singleReader: runtime.GOOS == "darwin",
}
ms.reqPool.New = func() interface{} { return new(request) }
ms.readPool.New = func() interface{} { return make([]byte, o.MaxWrite+PAGESIZE) }
mountPoint = filepath.Clean(mountPoint)
if !filepath.IsAbs(mountPoint) {
cwd, err := os.Getwd()
if err != nil {
return nil, err
}
mountPoint = filepath.Clean(filepath.Join(cwd, mountPoint))
}
ms.ready = make(chan error, 1)
fd, err := mount(mountPoint, &o, ms.ready)
if err != nil {
return nil, err
}
ms.mountPoint = mountPoint
ms.mountFd = fd
if code := ms.handleInit(); !code.Ok() {
syscall.Close(fd)
// TODO - unmount as well?
return nil, fmt.Errorf("init: %s", code)
}
return ms, nil
}
func (o *MountOptions) optionsStrings() []string {
var r []string
r = append(r, o.Options...)
if o.AllowOther {
r = append(r, "allow_other")
}
if o.FsName != "" {
r = append(r, "fsname="+o.FsName)
}
if o.Name != "" {
r = append(r, "subtype="+o.Name)
}
return r
}
// DebugData returns internal status information for debugging
// purposes.
func (ms *Server) DebugData() string {
var r int
ms.reqMu.Lock()
r = ms.reqReaders
ms.reqMu.Unlock()
return fmt.Sprintf("readers: %d", r)
}
// What is a good number? Maybe the number of CPUs?
const _MAX_READERS = 2
// handleEINTR retries the given function until it doesn't return syscall.EINTR.
// This is similar to the HANDLE_EINTR() macro from Chromium ( see
// https://code.google.com/p/chromium/codesearch#chromium/src/base/posix/eintr_wrapper.h
// ) and the TEMP_FAILURE_RETRY() from glibc (see
// https://www.gnu.org/software/libc/manual/html_node/Interrupted-Primitives.html
// ).
//
// Don't use handleEINTR() with syscall.Close(); see
// https://code.google.com/p/chromium/issues/detail?id=269623 .
func handleEINTR(fn func() error) (err error) {
for {
err = fn()
if err != syscall.EINTR {
break
}
}
return
}
// 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) {
ms.reqMu.Lock()
if ms.reqReaders > _MAX_READERS {
ms.reqMu.Unlock()
return nil, OK
}
req = ms.reqPool.Get().(*request)
dest := ms.readPool.Get().([]byte)
ms.reqReaders++
ms.reqMu.Unlock()
var n int
err := handleEINTR(func() error {
var err error
n, err = syscall.Read(ms.mountFd, dest)
return err
})
if err != nil {
code = ToStatus(err)
ms.reqPool.Put(req)
ms.reqMu.Lock()
ms.reqReaders--
ms.reqMu.Unlock()
return nil, code
}
if ms.latencies != nil {
req.startTime = time.Now()
}
gobbled := req.setInput(dest[:n])
ms.reqMu.Lock()
if !gobbled {
ms.readPool.Put(dest)
dest = nil
}
ms.reqReaders--
if !ms.singleReader && ms.reqReaders <= 0 {
ms.loops.Add(1)
go ms.loop(true)
}
ms.reqMu.Unlock()
return req, OK
}
// returnRequest returns a request to the pool of unused requests.
func (ms *Server) returnRequest(req *request) {
ms.recordStats(req)
if req.bufferPoolOutputBuf != nil {
ms.opts.Buffers.FreeBuffer(req.bufferPoolOutputBuf)
req.bufferPoolOutputBuf = nil
}
req.clear()
if p := req.bufferPoolInputBuf; p != nil {
req.bufferPoolInputBuf = nil
ms.readPool.Put(p)
}
ms.reqPool.Put(req)
}
func (ms *Server) recordStats(req *request) {
if ms.latencies != nil {
dt := time.Now().Sub(req.startTime)
opname := operationName(req.inHeader.Opcode)
ms.latencies.Add(opname, dt)
}
}
// Serve initiates the FUSE loop. Normally, callers should run Serve()
// and wait for it to exit, but tests will want to run this in a
// goroutine.
//
// Each filesystem operation executes in a separate goroutine.
func (ms *Server) Serve() {
ms.loops.Add(1)
ms.loop(false)
ms.loops.Wait()
ms.writeMu.Lock()
syscall.Close(ms.mountFd)
ms.writeMu.Unlock()
}
func (ms *Server) handleInit() Status {
// The first request should be INIT; read it synchronously,
// and don't spawn new readers.
orig := ms.singleReader
ms.singleReader = true
req, errNo := ms.readRequest(false)
ms.singleReader = orig
if errNo != OK || req == nil {
return errNo
}
if code := ms.handleRequest(req); !code.Ok() {
return code
}
// INIT is handled. Init the file system, but don't accept
// incoming requests, so the file system can setup itself.
ms.fileSystem.Init(ms)
return OK
}
func (ms *Server) loop(exitIdle bool) {
defer ms.loops.Done()
exit:
for {
req, errNo := ms.readRequest(exitIdle)
switch errNo {
case OK:
if req == nil {
break exit
}
case ENOENT:
continue
case ENODEV:
// unmount
break exit
default: // some other error?
log.Printf("Failed to read from fuse conn: %v", errNo)
break exit
}
if ms.singleReader {
go ms.handleRequest(req)
} else {
ms.handleRequest(req)
}
}
}
func (ms *Server) handleRequest(req *request) Status {
req.parse()
if req.handler == nil {
req.status = ENOSYS
}
if req.status.Ok() && ms.opts.Debug {
log.Println(req.InputDebug())
}
if req.status.Ok() && req.handler.Func == nil {
log.Printf("Unimplemented opcode %v", operationName(req.inHeader.Opcode))
req.status = ENOSYS
}
if req.status.Ok() {
req.handler.Func(ms, req)
}
errNo := ms.write(req)
if errNo != 0 {
log.Printf("writer: Write/Writev failed, err: %v. opcode: %v",
errNo, operationName(req.inHeader.Opcode))
}
ms.returnRequest(req)
return Status(errNo)
}
func (ms *Server) allocOut(req *request, size uint32) []byte {
if cap(req.bufferPoolOutputBuf) >= int(size) {
req.bufferPoolOutputBuf = req.bufferPoolOutputBuf[:size]
return req.bufferPoolOutputBuf
}
if req.bufferPoolOutputBuf != nil {
ms.opts.Buffers.FreeBuffer(req.bufferPoolOutputBuf)
}
req.bufferPoolOutputBuf = ms.opts.Buffers.AllocBuffer(size)
return req.bufferPoolOutputBuf
}
func (ms *Server) write(req *request) Status {
// Forget does not wait for reply.
if req.inHeader.Opcode == _OP_FORGET || req.inHeader.Opcode == _OP_BATCH_FORGET {
return OK
}
header := req.serializeHeader(req.flatDataSize())
if ms.opts.Debug {
log.Println(req.OutputDebug())
}
if header == nil {
return OK
}
s := ms.systemWrite(req, header)
return s
}
// InodeNotify invalidates the information associated with the inode
// (ie. data cache, attributes, etc.)
func (ms *Server) InodeNotify(node uint64, off int64, length int64) Status {
entry := &NotifyInvalInodeOut{
Ino: node,
Off: off,
Length: length,
}
req := request{
inHeader: &InHeader{
Opcode: _OP_NOTIFY_INODE,
},
handler: operationHandlers[_OP_NOTIFY_INODE],
status: NOTIFY_INVAL_INODE,
}
req.outData = unsafe.Pointer(entry)
// Protect against concurrent close.
ms.writeMu.Lock()
result := ms.write(&req)
ms.writeMu.Unlock()
if ms.opts.Debug {
log.Println("Response: INODE_NOTIFY", result)
}
return result
}
// DeleteNotify notifies the kernel that an entry is removed from a
// directory. In many cases, this is equivalent to EntryNotify,
// except when the directory is in use, eg. as working directory of
// some process. You should not hold any FUSE filesystem locks, as that
// can lead to deadlock.
func (ms *Server) DeleteNotify(parent uint64, child uint64, name string) Status {
if ms.kernelSettings.Minor < 18 {
return ms.EntryNotify(parent, name)
}
req := request{
inHeader: &InHeader{
Opcode: _OP_NOTIFY_DELETE,
},
handler: operationHandlers[_OP_NOTIFY_DELETE],
status: NOTIFY_INVAL_DELETE,
}
entry := &NotifyInvalDeleteOut{
Parent: parent,
Child: child,
NameLen: uint32(len(name)),
}
// Many versions of FUSE generate stacktraces if the
// terminating null byte is missing.
nameBytes := make([]byte, len(name)+1)
copy(nameBytes, name)
nameBytes[len(nameBytes)-1] = '\000'
req.outData = unsafe.Pointer(entry)
req.flatData = nameBytes
// Protect against concurrent close.
ms.writeMu.Lock()
result := ms.write(&req)
ms.writeMu.Unlock()
if ms.opts.Debug {
log.Printf("Response: DELETE_NOTIFY: %v", result)
}
return result
}
// EntryNotify should be used if the existence status of an entry
// within a directory changes. You should not hold any FUSE filesystem
// locks, as that can lead to deadlock.
func (ms *Server) EntryNotify(parent uint64, name string) Status {
req := request{
inHeader: &InHeader{
Opcode: _OP_NOTIFY_ENTRY,
},
handler: operationHandlers[_OP_NOTIFY_ENTRY],
status: NOTIFY_INVAL_ENTRY,
}
entry := &NotifyInvalEntryOut{
Parent: parent,
NameLen: uint32(len(name)),
}
// Many versions of FUSE generate stacktraces if the
// terminating null byte is missing.
nameBytes := make([]byte, len(name)+1)
copy(nameBytes, name)
nameBytes[len(nameBytes)-1] = '\000'
req.outData = unsafe.Pointer(entry)
req.flatData = nameBytes
// Protect against concurrent close.
ms.writeMu.Lock()
result := ms.write(&req)
ms.writeMu.Unlock()
if ms.opts.Debug {
log.Printf("Response: ENTRY_NOTIFY: %v", result)
}
return result
}
var defaultBufferPool BufferPool
func init() {
defaultBufferPool = NewBufferPool()
}
// WaitMount waits for the first request to be served. Use this to
// avoid racing between accessing the (empty or not yet mounted)
// mountpoint, and the OS trying to setup the user-space mount.
// Currently, this call only necessary on OSX.
func (ms *Server) WaitMount() error {
err := <-ms.ready
return err
}

28
vendor/github.com/hanwen/go-fuse/fuse/server_darwin.go generated vendored Normal file
View File

@@ -0,0 +1,28 @@
package fuse
import (
"syscall"
)
func (ms *Server) systemWrite(req *request, header []byte) Status {
if req.flatDataSize() == 0 {
err := handleEINTR(func() error {
_, err := syscall.Write(ms.mountFd, header)
return err
})
return ToStatus(err)
}
if req.fdData != nil {
sz := req.flatDataSize()
buf := ms.allocOut(req, uint32(sz))
req.flatData, req.status = req.fdData.Bytes(buf)
header = req.serializeHeader(len(req.flatData))
}
_, err := writev(int(ms.mountFd), [][]byte{header, req.flatData})
if req.readResult != nil {
req.readResult.Done()
}
return ToStatus(err)
}

38
vendor/github.com/hanwen/go-fuse/fuse/server_linux.go generated vendored Normal file
View File

@@ -0,0 +1,38 @@
package fuse
import (
"log"
"syscall"
)
func (ms *Server) systemWrite(req *request, header []byte) Status {
if req.flatDataSize() == 0 {
err := handleEINTR(func() error {
_, err := syscall.Write(ms.mountFd, header)
return err
})
return ToStatus(err)
}
if req.fdData != nil {
if ms.canSplice {
err := ms.trySplice(header, req, req.fdData)
if err == nil {
req.readResult.Done()
return OK
}
log.Println("trySplice:", err)
}
sz := req.flatDataSize()
buf := ms.allocOut(req, uint32(sz))
req.flatData, req.status = req.fdData.Bytes(buf)
header = req.serializeHeader(len(req.flatData))
}
_, err := writev(ms.mountFd, [][]byte{header, req.flatData})
if req.readResult != nil {
req.readResult.Done()
}
return ToStatus(err)
}

13
vendor/github.com/hanwen/go-fuse/fuse/splice_darwin.go generated vendored Normal file
View File

@@ -0,0 +1,13 @@
package fuse
import (
"fmt"
)
func (s *Server) setSplice() {
panic("darwin has no splice.")
}
func (ms *Server) trySplice(header []byte, req *request, fdData *readResultFd) error {
return fmt.Errorf("unimplemented")
}

93
vendor/github.com/hanwen/go-fuse/fuse/splice_linux.go generated vendored Normal file
View File

@@ -0,0 +1,93 @@
package fuse
import (
"fmt"
"os"
"github.com/hanwen/go-fuse/splice"
)
func (s *Server) setSplice() {
s.canSplice = splice.Resizable()
}
// trySplice: Zero-copy read from fdData.Fd into /dev/fuse
//
// This is a four-step process:
//
// 1) Splice data form fdData.Fd into the "pair1" pipe buffer --> pair1: [payload]
// Now we know the actual payload length and can
// construct the reply header
// 2) Write header into the "pair2" pipe buffer --> pair2: [header]
// 4) Splice data from "pair1" into "pair2" --> pair2: [header][payload]
// 3) Splice the data from "pair2" into /dev/fuse
//
// This dance is neccessary because header and payload cannot be split across
// two splices and we cannot seek in a pipe buffer.
func (ms *Server) trySplice(header []byte, req *request, fdData *readResultFd) error {
var err error
// Get a pair of connected pipes
pair1, err := splice.Get()
if err != nil {
return err
}
defer splice.Done(pair1)
// Grow buffer pipe to requested size + one extra page
// Without the extra page the kernel will block once the pipe is almost full
pair1Sz := fdData.Size() + os.Getpagesize()
if err := pair1.Grow(pair1Sz); err != nil {
return err
}
// Read data from file
payloadLen, err := pair1.LoadFromAt(fdData.Fd, fdData.Size(), fdData.Off)
if err != nil {
// TODO - extract the data from splice.
return err
}
// Get another pair of connected pipes
pair2, err := splice.Get()
if err != nil {
return err
}
defer splice.Done(pair2)
// Grow pipe to header + actually read size + one extra page
// Without the extra page the kernel will block once the pipe is almost full
header = req.serializeHeader(payloadLen)
total := len(header) + payloadLen
pair2Sz := total + os.Getpagesize()
if err := pair2.Grow(pair2Sz); err != nil {
return err
}
// Write header into pair2
n, err := pair2.Write(header)
if err != nil {
return err
}
if n != len(header) {
return fmt.Errorf("Short write into splice: wrote %d, want %d", n, len(header))
}
// Write data into pair2
n, err = pair2.LoadFrom(pair1.ReadFd(), payloadLen)
if err != nil {
return err
}
if n != payloadLen {
return fmt.Errorf("Short splice: wrote %d, want %d", n, payloadLen)
}
// Write header + data to /dev/fuse
_, err = pair2.WriteTo(uintptr(ms.mountFd), total)
if err != nil {
return err
}
return nil
}

135
vendor/github.com/hanwen/go-fuse/fuse/syscall_darwin.go generated vendored Normal file
View File

@@ -0,0 +1,135 @@
package fuse
import (
"bytes"
"os"
"syscall"
"unsafe"
)
// TODO - move these into Go's syscall package.
func sys_writev(fd int, iovecs *syscall.Iovec, cnt int) (n int, err error) {
n1, _, e1 := syscall.Syscall(
syscall.SYS_WRITEV,
uintptr(fd), uintptr(unsafe.Pointer(iovecs)), uintptr(cnt))
n = int(n1)
if e1 != 0 {
err = syscall.Errno(e1)
}
return
}
func writev(fd int, packet [][]byte) (n int, err error) {
iovecs := make([]syscall.Iovec, 0, len(packet))
for _, v := range packet {
if len(v) == 0 {
continue
}
vec := syscall.Iovec{
Base: &v[0],
}
vec.SetLen(len(v))
iovecs = append(iovecs, vec)
}
sysErr := handleEINTR(func() error {
var err error
n, err = sys_writev(fd, &iovecs[0], len(iovecs))
return err
})
if sysErr != nil {
err = os.NewSyscallError("writev", sysErr)
}
return n, err
}
func getxattr(path string, attr string, dest []byte) (sz int, errno int) {
pathBs := syscall.StringBytePtr(path)
attrBs := syscall.StringBytePtr(attr)
size, _, errNo := syscall.Syscall6(
syscall.SYS_GETXATTR,
uintptr(unsafe.Pointer(pathBs)),
uintptr(unsafe.Pointer(attrBs)),
uintptr(unsafe.Pointer(&dest[0])),
uintptr(len(dest)),
0, 0)
return int(size), int(errNo)
}
func GetXAttr(path string, attr string, dest []byte) (value []byte, errno int) {
sz, errno := getxattr(path, attr, dest)
for sz > cap(dest) && errno == 0 {
dest = make([]byte, sz)
sz, errno = getxattr(path, attr, dest)
}
if errno != 0 {
return nil, errno
}
return dest[:sz], errno
}
func listxattr(path string, dest []byte) (sz int, errno int) {
pathbs := syscall.StringBytePtr(path)
var destPointer unsafe.Pointer
if len(dest) > 0 {
destPointer = unsafe.Pointer(&dest[0])
}
size, _, errNo := syscall.Syscall(
syscall.SYS_LISTXATTR,
uintptr(unsafe.Pointer(pathbs)),
uintptr(destPointer),
uintptr(len(dest)))
return int(size), int(errNo)
}
func ListXAttr(path string) (attributes []string, errno int) {
dest := make([]byte, 0)
sz, errno := listxattr(path, dest)
if errno != 0 {
return nil, errno
}
for sz > cap(dest) && errno == 0 {
dest = make([]byte, sz)
sz, errno = listxattr(path, dest)
}
// -1 to drop the final empty slice.
dest = dest[:sz-1]
attributesBytes := bytes.Split(dest, []byte{0})
attributes = make([]string, len(attributesBytes))
for i, v := range attributesBytes {
attributes[i] = string(v)
}
return attributes, errno
}
func Setxattr(path string, attr string, data []byte, flags int) (errno int) {
pathbs := syscall.StringBytePtr(path)
attrbs := syscall.StringBytePtr(attr)
_, _, errNo := syscall.Syscall6(
syscall.SYS_SETXATTR,
uintptr(unsafe.Pointer(pathbs)),
uintptr(unsafe.Pointer(attrbs)),
uintptr(unsafe.Pointer(&data[0])),
uintptr(len(data)),
uintptr(flags), 0)
return int(errNo)
}
func Removexattr(path string, attr string) (errno int) {
pathbs := syscall.StringBytePtr(path)
attrbs := syscall.StringBytePtr(attr)
_, _, errNo := syscall.Syscall(
syscall.SYS_REMOVEXATTR,
uintptr(unsafe.Pointer(pathbs)),
uintptr(unsafe.Pointer(attrbs)), 0)
return int(errNo)
}

45
vendor/github.com/hanwen/go-fuse/fuse/syscall_linux.go generated vendored Normal file
View File

@@ -0,0 +1,45 @@
package fuse
import (
"os"
"syscall"
"unsafe"
)
// TODO - move these into Go's syscall package.
func sys_writev(fd int, iovecs *syscall.Iovec, cnt int) (n int, err error) {
n1, _, e1 := syscall.Syscall(
syscall.SYS_WRITEV,
uintptr(fd), uintptr(unsafe.Pointer(iovecs)), uintptr(cnt))
n = int(n1)
if e1 != 0 {
err = syscall.Errno(e1)
}
return n, err
}
func writev(fd int, packet [][]byte) (n int, err error) {
iovecs := make([]syscall.Iovec, 0, len(packet))
for _, v := range packet {
if len(v) == 0 {
continue
}
vec := syscall.Iovec{
Base: &v[0],
}
vec.SetLen(len(v))
iovecs = append(iovecs, vec)
}
sysErr := handleEINTR(func() error {
var err error
n, err = sys_writev(fd, &iovecs[0], len(iovecs))
return err
})
if sysErr != nil {
err = os.NewSyscallError("writev", sysErr)
}
return n, err
}

View File

@@ -0,0 +1,220 @@
package test
import (
"bytes"
"io/ioutil"
"os"
"runtime"
"sync"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
)
type cacheFs struct {
pathfs.FileSystem
}
func (fs *cacheFs) Open(name string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) {
f, c := fs.FileSystem.Open(name, flags, context)
if !c.Ok() {
return f, c
}
return &nodefs.WithFlags{
File: f,
FuseFlags: fuse.FOPEN_KEEP_CACHE,
}, c
}
func setupCacheTest(t *testing.T) (string, *pathfs.PathNodeFs, func()) {
dir, err := ioutil.TempDir("", "go-fuse-cachetest")
if err != nil {
t.Fatalf("TempDir failed: %v", err)
}
os.Mkdir(dir+"/mnt", 0755)
os.Mkdir(dir+"/orig", 0755)
fs := &cacheFs{
pathfs.NewLoopbackFileSystem(dir + "/orig"),
}
pfs := pathfs.NewPathNodeFs(fs, &pathfs.PathNodeFsOptions{Debug: VerboseTest()})
opts := nodefs.NewOptions()
opts.Debug = VerboseTest()
state, _, err := nodefs.MountRoot(dir+"/mnt", pfs.Root(), opts)
if err != nil {
t.Fatalf("MountNodeFileSystem failed: %v", err)
}
go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
return dir, pfs, func() {
err := state.Unmount()
if err == nil {
os.RemoveAll(dir)
}
}
}
func TestFopenKeepCache(t *testing.T) {
if runtime.GOOS == "darwin" {
t.Skip("FOPEN_KEEP_CACHE is broken on Darwin.")
}
wd, pathfs, clean := setupCacheTest(t)
defer clean()
before := "before"
after := "after"
if err := ioutil.WriteFile(wd+"/orig/file.txt", []byte(before), 0644); err != nil {
t.Fatalf("WriteFile failed: %v", err)
}
c, err := ioutil.ReadFile(wd + "/mnt/file.txt")
if err != nil {
t.Fatalf("ReadFile: %v", err)
} else if string(c) != before {
t.Fatalf("ReadFile: got %q, want %q", c, before)
}
if err := ioutil.WriteFile(wd+"/orig/file.txt", []byte(after), 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
c, err = ioutil.ReadFile(wd + "/mnt/file.txt")
if err != nil {
t.Fatalf("ReadFile: %v", err)
} else if string(c) != before {
t.Fatalf("ReadFile: got %q, want cached %q", c, before)
}
if minor := pathfs.Connector().Server().KernelSettings().Minor; minor < 12 {
t.Skip("protocol v%d has no notify support.", minor)
}
code := pathfs.EntryNotify("", "file.txt")
if !code.Ok() {
t.Errorf("EntryNotify: %v", code)
}
c, err = ioutil.ReadFile(wd + "/mnt/file.txt")
if err != nil {
t.Fatalf("ReadFile: %v", err)
} else if string(c) != after {
t.Fatalf("ReadFile: got %q after notify, want %q", c, after)
}
}
type nonseekFs struct {
pathfs.FileSystem
Length int
}
func (fs *nonseekFs) GetAttr(name string, context *fuse.Context) (fi *fuse.Attr, status fuse.Status) {
if name == "file" {
return &fuse.Attr{Mode: fuse.S_IFREG | 0644}, fuse.OK
}
return nil, fuse.ENOENT
}
func (fs *nonseekFs) Open(name string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) {
if name != "file" {
return nil, fuse.ENOENT
}
data := bytes.Repeat([]byte{42}, fs.Length)
f := nodefs.NewDataFile(data)
return &nodefs.WithFlags{
File: f,
FuseFlags: fuse.FOPEN_NONSEEKABLE,
}, fuse.OK
}
func TestNonseekable(t *testing.T) {
fs := &nonseekFs{FileSystem: pathfs.NewDefaultFileSystem()}
fs.Length = 200 * 1024
dir, err := ioutil.TempDir("", "go-fuse-cache_test")
if err != nil {
t.Fatalf("failed: %v", err)
}
defer os.RemoveAll(dir)
nfs := pathfs.NewPathNodeFs(fs, nil)
opts := nodefs.NewOptions()
opts.Debug = VerboseTest()
state, _, err := nodefs.MountRoot(dir, nfs.Root(), opts)
if err != nil {
t.Fatalf("failed: %v", err)
}
defer state.Unmount()
go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
f, err := os.Open(dir + "/file")
if err != nil {
t.Fatalf("failed: %v", err)
}
defer f.Close()
b := make([]byte, 200)
n, err := f.ReadAt(b, 20)
if err == nil || n > 0 {
t.Errorf("file was opened nonseekable, but seek successful")
}
}
func TestGetAttrRace(t *testing.T) {
dir, err := ioutil.TempDir("", "go-fuse-cache_test")
if err != nil {
t.Fatalf("failed: %v", err)
}
defer os.RemoveAll(dir)
os.Mkdir(dir+"/mnt", 0755)
os.Mkdir(dir+"/orig", 0755)
fs := pathfs.NewLoopbackFileSystem(dir + "/orig")
pfs := pathfs.NewPathNodeFs(fs, &pathfs.PathNodeFsOptions{Debug: VerboseTest()})
state, _, err := nodefs.MountRoot(dir+"/mnt", pfs.Root(),
&nodefs.Options{Debug: VerboseTest()})
if err != nil {
t.Fatalf("MountNodeFileSystem failed: %v", err)
}
go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
defer state.Unmount()
var wg sync.WaitGroup
n := 100
wg.Add(n)
var statErr error
for i := 0; i < n; i++ {
go func() {
defer wg.Done()
fn := dir + "/mnt/file"
err := ioutil.WriteFile(fn, []byte{42}, 0644)
if err != nil {
statErr = err
return
}
_, err = os.Lstat(fn)
if err != nil {
statErr = err
}
}()
}
wg.Wait()
if statErr != nil {
t.Error(statErr)
}
}

View File

@@ -0,0 +1,70 @@
package test
import (
"io/ioutil"
"os"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
)
type DefaultReadFS struct {
pathfs.FileSystem
size uint64
exist bool
}
func (fs *DefaultReadFS) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
if name == "" {
return &fuse.Attr{Mode: fuse.S_IFDIR | 0755}, fuse.OK
}
if name == "file" {
return &fuse.Attr{Mode: fuse.S_IFREG | 0644, Size: fs.size}, fuse.OK
}
return nil, fuse.ENOENT
}
func (fs *DefaultReadFS) Open(name string, f uint32, context *fuse.Context) (nodefs.File, fuse.Status) {
return nodefs.NewDefaultFile(), fuse.OK
}
func defaultReadTest(t *testing.T) (root string, cleanup func()) {
fs := &DefaultReadFS{
FileSystem: pathfs.NewDefaultFileSystem(),
size: 22,
}
var err error
dir, err := ioutil.TempDir("", "go-fuse")
if err != nil {
t.Fatalf("TempDir failed: %v", err)
}
pathfs := pathfs.NewPathNodeFs(fs, nil)
opts := nodefs.NewOptions()
opts.Debug = VerboseTest()
state, _, err := nodefs.MountRoot(dir, pathfs.Root(), opts)
if err != nil {
t.Fatalf("MountNodeFileSystem failed: %v", err)
}
go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
return dir, func() {
state.Unmount()
os.Remove(dir)
}
}
func TestDefaultRead(t *testing.T) {
root, clean := defaultReadTest(t)
defer clean()
_, err := ioutil.ReadFile(root + "/file")
if err == nil {
t.Fatal("should have failed read.")
}
}

View File

@@ -0,0 +1,117 @@
package test
import (
"bytes"
"io/ioutil"
"os"
"os/exec"
"syscall"
"testing"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
)
type flipNode struct {
nodefs.Node
ok chan int
}
func (f *flipNode) GetAttr(out *fuse.Attr, file nodefs.File, c *fuse.Context) fuse.Status {
select {
case <-f.ok:
// use a status that is easily recognizable.
return fuse.Status(syscall.EXDEV)
default:
}
return f.Node.GetAttr(out, file, c)
}
func TestDeleteNotify(t *testing.T) {
dir, err := ioutil.TempDir("", "go-fuse-delete_test")
if err != nil {
t.Fatalf("TempDir failed %v", err)
}
defer os.RemoveAll(dir)
root := nodefs.NewMemNodeFSRoot(dir + "/backing")
conn := nodefs.NewFileSystemConnector(root,
&nodefs.Options{PortableInodes: true})
mnt := dir + "/mnt"
err = os.Mkdir(mnt, 0755)
if err != nil {
t.Fatal(err)
}
state, err := fuse.NewServer(conn.RawFS(), mnt, &fuse.MountOptions{
Debug: VerboseTest(),
})
if err != nil {
t.Fatal(err)
}
go state.Serve()
defer state.Unmount()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
_, code := root.Mkdir("testdir", 0755, nil)
if !code.Ok() {
t.Fatal(code)
}
ch := root.Inode().RmChild("testdir")
ch.Node().SetInode(nil)
flip := flipNode{
Node: ch.Node(),
ok: make(chan int),
}
root.Inode().NewChild("testdir", true, &flip)
err = ioutil.WriteFile(mnt+"/testdir/testfile", []byte{42}, 0644)
if err != nil {
t.Fatal(err)
}
// Do the test here, so we surely have state.KernelSettings()
if state.KernelSettings().Minor < 18 {
t.Log("Kernel does not support deletion notify; aborting test.")
return
}
buf := bytes.Buffer{}
cmd := exec.Command("/usr/bin/tail", "-f", "testfile")
cmd.Dir = mnt + "/testdir"
cmd.Stdin = &buf
cmd.Stdout = &bytes.Buffer{}
cmd.Stderr = os.Stderr
err = cmd.Start()
if err != nil {
t.Fatal(err)
}
defer func() {
cmd.Process.Kill()
time.Sleep(100 * time.Millisecond)
}()
// Wait until tail opened the file.
time.Sleep(100 * time.Millisecond)
err = os.Remove(mnt + "/testdir/testfile")
if err != nil {
t.Fatal(err)
}
// Simulate deletion+mkdir coming from the network
close(flip.ok)
oldCh := root.Inode().RmChild("testdir")
_, code = root.Inode().Node().Mkdir("testdir", 0755, nil)
if !code.Ok() {
t.Fatal("mkdir status", code)
}
conn.DeleteNotify(root.Inode(), oldCh, "testdir")
_, err = os.Lstat(mnt + "/testdir")
if err != nil {
t.Fatalf("lstat after del + mkdir failed: %v", err)
}
}

View File

@@ -0,0 +1,257 @@
package test
import (
"io/ioutil"
"os"
"syscall"
"testing"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
)
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.ENODATA
}
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, err := ioutil.TempDir("", "go-fuse-fsetattr_test")
if err != nil {
t.Fatalf("TempDir failed: %v", err)
}
nfs := pathfs.NewPathNodeFs(fs, nil)
opts := nodefs.NewOptions()
opts.Debug = 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)
}
if state.KernelSettings().Flags&fuse.CAP_FILE_OPS == 0 {
t.Skip("Mount does not support file operations")
}
return dir, func() {
if err := state.Unmount(); err != nil {
t.Errorf("cleanup: Unmount: %v", err)
} else {
os.RemoveAll(dir)
}
}
}
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")
}
}

View File

@@ -0,0 +1,14 @@
package test
import (
"syscall"
)
func clearStatfs(s *syscall.Statfs_t) {
empty := syscall.Statfs_t{}
s.Type = 0
s.Fsid = empty.Fsid
// s.Spare = empty.Spare
// TODO - figure out what this is for.
s.Flags = 0
}

View File

@@ -0,0 +1,120 @@
package test
import (
"io/ioutil"
"os"
"syscall"
"testing"
"time"
)
func TestTouch(t *testing.T) {
ts := NewTestCase(t)
defer ts.Cleanup()
contents := []byte{1, 2, 3}
err := ioutil.WriteFile(ts.origFile, []byte(contents), 0700)
if err != nil {
t.Fatalf("WriteFile failed: %v", err)
}
err = os.Chtimes(ts.mountFile, time.Unix(42, 0), time.Unix(43, 0))
if err != nil {
t.Fatalf("Chtimes failed: %v", err)
}
var stat syscall.Stat_t
err = syscall.Lstat(ts.mountFile, &stat)
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
if stat.Atim.Sec != 42 {
t.Errorf("Got atime.sec %d, want 42. Stat_t was %#v", stat.Atim.Sec, stat)
}
if stat.Mtim.Sec != 43 {
t.Errorf("Got mtime.sec %d, want 43. Stat_t was %#v", stat.Mtim.Sec, stat)
}
}
func TestNegativeTime(t *testing.T) {
ts := NewTestCase(t)
defer ts.Cleanup()
_, err := os.Create(ts.origFile)
if err != nil {
t.Fatalf("Create failed: %v", err)
}
var stat syscall.Stat_t
// set negative nanosecond will occur errors on UtimesNano as invalid argument
ut := time.Date(1960, time.January, 10, 23, 0, 0, 0, time.UTC)
tim := []syscall.Timespec{
syscall.NsecToTimespec(ut.UnixNano()),
syscall.NsecToTimespec(ut.UnixNano()),
}
err = syscall.UtimesNano(ts.mountFile, tim)
if err != nil {
t.Fatalf("UtimesNano failed: %v", err)
}
err = syscall.Lstat(ts.mountFile, &stat)
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
if stat.Atim.Sec >= 0 || stat.Mtim.Sec >= 0 {
t.Errorf("Got wrong timestamps %v", stat)
}
}
func clearStatfs(s *syscall.Statfs_t) {
empty := syscall.Statfs_t{}
s.Type = 0
s.Fsid = empty.Fsid
s.Spare = empty.Spare
// TODO - figure out what this is for.
s.Flags = 0
}
func TestFallocate(t *testing.T) {
ts := NewTestCase(t)
defer ts.Cleanup()
if ts.state.KernelSettings().Minor < 19 {
t.Log("FUSE does not support Fallocate.")
return
}
rwFile, err := os.OpenFile(ts.mnt+"/file", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
if err != nil {
t.Fatalf("OpenFile failed: %v", err)
}
defer rwFile.Close()
err = syscall.Fallocate(int(rwFile.Fd()), 0, 1024, 4096)
if err != nil {
t.Fatalf("FUSE Fallocate failed: %v", err)
}
fi, err := os.Lstat(ts.orig + "/file")
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
if fi.Size() < (1024 + 4096) {
t.Fatalf("fallocate should have changed file size. Got %d bytes",
fi.Size())
}
}
// Check that "." and ".." exists. syscall.Getdents is linux specific.
func TestSpecialEntries(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
d, err := os.Open(tc.mnt)
if err != nil {
t.Fatalf("Open failed: %v", err)
}
defer d.Close()
buf := make([]byte, 100)
n, err := syscall.Getdents(int(d.Fd()), buf)
if n == 0 {
t.Errorf("directory is empty, entries '.' and '..' are missing")
}
}

View File

@@ -0,0 +1,960 @@
package test
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"math/rand"
"os"
"os/exec"
"path/filepath"
"reflect"
"runtime"
"syscall"
"testing"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
)
const mode uint32 = 0757
type testCase struct {
tmpDir string
orig string
mnt string
mountFile string
mountSubdir string
origFile string
origSubdir string
tester *testing.T
state *fuse.Server
pathFs *pathfs.PathNodeFs
connector *nodefs.FileSystemConnector
}
const testTtl = 100 * time.Millisecond
// Mkdir is a utility wrapper for os.Mkdir, aborting the test if it fails.
func (tc *testCase) Mkdir(name string, mode os.FileMode) {
if err := os.Mkdir(name, mode); err != nil {
tc.tester.Fatalf("Mkdir(%q,%v): %v", name, mode, err)
}
}
// WriteFile is a utility wrapper for ioutil.WriteFile, aborting the
// test if it fails.
func (tc *testCase) WriteFile(name string, content []byte, mode os.FileMode) {
if err := ioutil.WriteFile(name, content, mode); err != nil {
if len(content) > 50 {
content = append(content[:50], '.', '.', '.')
}
tc.tester.Fatalf("WriteFile(%q, %q, %o): %v", name, content, mode, err)
}
}
// Create and mount filesystem.
func NewTestCase(t *testing.T) *testCase {
tc := &testCase{}
tc.tester = t
// Make sure system setting does not affect test.
syscall.Umask(0)
const name string = "hello.txt"
const subdir string = "subdir"
var err error
tc.tmpDir, err = ioutil.TempDir("", "go-fuse")
if err != nil {
t.Fatalf("TempDir failed: %v", err)
}
tc.orig = tc.tmpDir + "/orig"
tc.mnt = tc.tmpDir + "/mnt"
tc.Mkdir(tc.orig, 0700)
tc.Mkdir(tc.mnt, 0700)
tc.mountFile = filepath.Join(tc.mnt, name)
tc.mountSubdir = filepath.Join(tc.mnt, subdir)
tc.origFile = filepath.Join(tc.orig, name)
tc.origSubdir = filepath.Join(tc.orig, subdir)
var pfs pathfs.FileSystem
pfs = pathfs.NewLoopbackFileSystem(tc.orig)
pfs = pathfs.NewLockingFileSystem(pfs)
tc.pathFs = pathfs.NewPathNodeFs(pfs, &pathfs.PathNodeFsOptions{
ClientInodes: true})
tc.connector = nodefs.NewFileSystemConnector(tc.pathFs.Root(),
&nodefs.Options{
EntryTimeout: testTtl,
AttrTimeout: testTtl,
NegativeTimeout: 0.0,
Debug: VerboseTest(),
})
tc.state, err = fuse.NewServer(
fuse.NewRawFileSystem(tc.connector.RawFS()), tc.mnt, &fuse.MountOptions{
SingleThreaded: true,
Debug: VerboseTest(),
})
if err != nil {
t.Fatal("NewServer:", err)
}
go tc.state.Serve()
if err := tc.state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
return tc
}
// Unmount and del.
func (tc *testCase) Cleanup() {
err := tc.state.Unmount()
if err != nil {
tc.tester.Fatalf("Unmount failed: %v", err)
}
os.RemoveAll(tc.tmpDir)
}
func (tc *testCase) rootNode() *nodefs.Inode {
return tc.pathFs.Root().Inode()
}
////////////////
// Tests.
func TestOpenUnreadable(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
_, err := os.Open(tc.mnt + "/doesnotexist")
if err == nil {
t.Errorf("open non-existent should raise error")
}
}
func TestReadThrough(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
content := randomData(125)
tc.WriteFile(tc.origFile, content, 0700)
err := os.Chmod(tc.mountFile, os.FileMode(mode))
if err != nil {
t.Fatalf("Chmod failed: %v", err)
}
fi, err := os.Lstat(tc.mountFile)
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
if uint32(fi.Mode().Perm()) != mode {
t.Errorf("Wrong mode %o != %o", int(fi.Mode().Perm()), mode)
}
// Open (for read), read.
f, err := os.Open(tc.mountFile)
if err != nil {
t.Fatalf("Open failed: %v", err)
}
defer f.Close()
var buf [1024]byte
slice := buf[:]
n, err := f.Read(slice)
CompareSlices(t, slice[:n], content)
}
func TestRemove(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
contents := []byte{1, 2, 3}
tc.WriteFile(tc.origFile, []byte(contents), 0700)
err := os.Remove(tc.mountFile)
if err != nil {
t.Fatalf("Remove failed: %v", err)
}
_, err = os.Lstat(tc.origFile)
if err == nil {
t.Errorf("Lstat() after delete should have generated error.")
}
}
func TestWriteThrough(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
// Create (for write), write.
f, err := os.OpenFile(tc.mountFile, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
t.Fatalf("OpenFile failed: %v", err)
}
defer f.Close()
content := randomData(125)
n, err := f.Write(content)
if err != nil {
t.Fatalf("Write failed: %v", err)
}
if n != len(content) {
t.Errorf("Write mismatch: %v of %v", n, len(content))
}
fi, err := os.Lstat(tc.origFile)
if err != nil {
t.Fatalf("Lstat(%q): %v", tc.origFile, err)
}
if fi.Mode().Perm() != 0644 {
t.Errorf("create mode error %o", fi.Mode()&0777)
}
f, err = os.Open(tc.origFile)
if err != nil {
t.Fatalf("Open failed: %v", err)
}
defer f.Close()
var buf [1024]byte
slice := buf[:]
n, err = f.Read(slice)
if err != nil {
t.Fatalf("Read failed: %v", err)
}
CompareSlices(t, slice[:n], content)
}
func TestMkdirRmdir(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
// Mkdir/Rmdir.
if err := os.Mkdir(tc.mountSubdir, 0777); err != nil {
t.Fatalf("Mkdir failed: %v", err)
}
if fi, err := os.Lstat(tc.origSubdir); err != nil {
t.Fatalf("Lstat(%q): %v", tc.origSubdir, err)
} else if !fi.IsDir() {
t.Errorf("Not a directory: %v", fi)
}
if err := os.Remove(tc.mountSubdir); err != nil {
t.Fatalf("Remove failed: %v", err)
}
}
func TestLinkCreate(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
content := randomData(125)
tc.WriteFile(tc.origFile, content, 0700)
tc.Mkdir(tc.origSubdir, 0777)
// Link.
mountSubfile := filepath.Join(tc.mountSubdir, "subfile")
err := os.Link(tc.mountFile, mountSubfile)
if err != nil {
t.Fatalf("Link failed: %v", err)
}
var subStat, stat syscall.Stat_t
err = syscall.Lstat(mountSubfile, &subStat)
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
err = syscall.Lstat(tc.mountFile, &stat)
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
if stat.Nlink != 2 {
t.Errorf("Expect 2 links: %v", stat)
}
if stat.Ino != subStat.Ino {
t.Errorf("Link succeeded, but inode numbers different: %v %v", stat.Ino, subStat.Ino)
}
readback, err := ioutil.ReadFile(mountSubfile)
if err != nil {
t.Fatalf("ReadFile failed: %v", err)
}
CompareSlices(t, readback, content)
err = os.Remove(tc.mountFile)
if err != nil {
t.Fatalf("Remove failed: %v", err)
}
_, err = ioutil.ReadFile(mountSubfile)
if err != nil {
t.Fatalf("ReadFile failed: %v", err)
}
}
func randomData(size int) []byte {
return bytes.Repeat([]byte{'x'}, size)
}
// Deal correctly with hard links implied by matching client inode
// numbers.
func TestLinkExisting(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
c := randomData(5)
tc.WriteFile(tc.orig+"/file1", c, 0644)
err := os.Link(tc.orig+"/file1", tc.orig+"/file2")
if err != nil {
t.Fatalf("Link failed: %v", err)
}
var s1, s2 syscall.Stat_t
err = syscall.Lstat(tc.mnt+"/file1", &s1)
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
err = syscall.Lstat(tc.mnt+"/file2", &s2)
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
if s1.Ino != s2.Ino {
t.Errorf("linked files should have identical inodes %v %v", s1.Ino, s2.Ino)
}
back, err := ioutil.ReadFile(tc.mnt + "/file1")
if err != nil {
t.Fatalf("ReadFile failed: %v", err)
}
CompareSlices(t, back, c)
}
// Deal correctly with hard links implied by matching client inode
// numbers.
func TestLinkForget(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
c := "hello"
tc.WriteFile(tc.orig+"/file1", []byte(c), 0644)
err := os.Link(tc.orig+"/file1", tc.orig+"/file2")
if err != nil {
t.Fatalf("Link failed: %v", err)
}
for _, fn := range []string{"file1", "file2"} {
var s syscall.Stat_t
err = syscall.Lstat(tc.mnt+"/"+fn, &s)
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
tc.pathFs.ForgetClientInodes()
}
// Now, the backing files are still hardlinked, but go-fuse's
// view of them should not be because of the
// ForgetClientInodes call. To prove this, we swap out the
// files in the backing store, and prove that they are
// distinct by truncating to different lengths.
for _, fn := range []string{"file1", "file2"} {
fn = tc.orig + "/" + fn
if err := os.Remove(fn); err != nil {
t.Fatalf("Remove", err)
}
tc.WriteFile(fn, []byte(c), 0644)
}
for i, fn := range []string{"file1", "file2"} {
fn = tc.mnt + "/" + fn
if err := os.Truncate(fn, int64(i)); err != nil {
t.Fatalf("Truncate", err)
}
}
for i, fn := range []string{"file1", "file2"} {
var s syscall.Stat_t
err = syscall.Lstat(tc.mnt+"/"+fn, &s)
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
if s.Size != int64(i) {
t.Errorf("Lstat(%q): got size %d, want %d", fn, s.Size, i)
}
}
}
func TestSymlink(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
contents := []byte{1, 2, 3}
tc.WriteFile(tc.origFile, []byte(contents), 0700)
linkFile := "symlink-file"
orig := "hello.txt"
err := os.Symlink(orig, filepath.Join(tc.mnt, linkFile))
if err != nil {
t.Fatalf("Symlink failed: %v", err)
}
origLink := filepath.Join(tc.orig, linkFile)
fi, err := os.Lstat(origLink)
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
if fi.Mode()&os.ModeSymlink == 0 {
t.Errorf("not a symlink: %v", fi)
return
}
read, err := os.Readlink(filepath.Join(tc.mnt, linkFile))
if err != nil {
t.Fatalf("Readlink failed: %v", err)
}
if read != orig {
t.Errorf("unexpected symlink value '%v'", read)
}
}
func TestRename(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
contents := []byte{1, 2, 3}
tc.WriteFile(tc.origFile, []byte(contents), 0700)
sd := tc.mnt + "/testRename"
tc.Mkdir(sd, 0777)
subFile := sd + "/subfile"
if err := os.Rename(tc.mountFile, subFile); err != nil {
t.Fatalf("Rename failed: %v", err)
}
f, _ := os.Lstat(tc.origFile)
if f != nil {
t.Errorf("original %v still exists.", tc.origFile)
}
if _, err := os.Lstat(subFile); err != nil {
t.Errorf("destination %q does not exist: %v", subFile, err)
}
}
// Flaky test, due to rename race condition.
func TestDelRename(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
sd := tc.mnt + "/testDelRename"
tc.Mkdir(sd, 0755)
d := sd + "/dest"
tc.WriteFile(d, []byte("blabla"), 0644)
f, err := os.Open(d)
if err != nil {
t.Fatalf("Open failed: %v", err)
}
defer f.Close()
if err := os.Remove(d); err != nil {
t.Fatalf("Remove failed: %v", err)
}
s := sd + "/src"
tc.WriteFile(s, []byte("blabla"), 0644)
if err := os.Rename(s, d); err != nil {
t.Fatalf("Rename failed: %v", err)
}
}
func TestOverwriteRename(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
sd := tc.mnt + "/testOverwriteRename"
tc.Mkdir(sd, 0755)
d := sd + "/dest"
tc.WriteFile(d, []byte("blabla"), 0644)
s := sd + "/src"
tc.WriteFile(s, []byte("blabla"), 0644)
if err := os.Rename(s, d); err != nil {
t.Fatalf("Rename failed: %v", err)
}
}
func TestAccess(t *testing.T) {
if os.Geteuid() == 0 {
t.Log("Skipping TestAccess() as root.")
return
}
tc := NewTestCase(t)
defer tc.Cleanup()
contents := []byte{1, 2, 3}
tc.WriteFile(tc.origFile, []byte(contents), 0700)
if err := os.Chmod(tc.origFile, 0); err != nil {
t.Fatalf("Chmod failed: %v", err)
}
// Ugh - copied from unistd.h
const W_OK uint32 = 2
if errCode := syscall.Access(tc.mountFile, W_OK); errCode != syscall.EACCES {
t.Errorf("Expected EACCES for non-writable, %v %v", errCode, syscall.EACCES)
}
if err := os.Chmod(tc.origFile, 0222); err != nil {
t.Fatalf("Chmod failed: %v", err)
}
if errCode := syscall.Access(tc.mountFile, W_OK); errCode != nil {
t.Errorf("Expected no error code for writable. %v", errCode)
}
}
func TestMknod(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
if errNo := syscall.Mknod(tc.mountFile, syscall.S_IFIFO|0777, 0); errNo != nil {
t.Errorf("Mknod %v", errNo)
}
if fi, err := os.Lstat(tc.origFile); err != nil {
t.Errorf("Lstat(%q): %v", tc.origFile, err)
} else if fi.Mode()&os.ModeNamedPipe == 0 {
t.Errorf("Expected FIFO filetype, got %x", fi.Mode())
}
}
func TestReaddir(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
contents := []byte{1, 2, 3}
tc.WriteFile(tc.origFile, []byte(contents), 0700)
tc.Mkdir(tc.origSubdir, 0777)
dir, err := os.Open(tc.mnt)
if err != nil {
t.Fatalf("Open failed: %v", err)
}
defer dir.Close()
infos, err := dir.Readdir(10)
if err != nil {
t.Fatalf("Readdir failed: %v", err)
}
wanted := map[string]bool{
"hello.txt": true,
"subdir": true,
}
if len(wanted) != len(infos) {
t.Errorf("Length mismatch %v", infos)
} else {
for _, v := range infos {
_, ok := wanted[v.Name()]
if !ok {
t.Errorf("Unexpected name %v", v.Name())
}
}
}
}
func TestFSync(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
contents := []byte{1, 2, 3}
tc.WriteFile(tc.origFile, []byte(contents), 0700)
f, err := os.OpenFile(tc.mountFile, os.O_WRONLY, 0)
if err != nil {
t.Fatalf("OpenFile(%q): %v", tc.mountFile, err)
}
defer f.Close()
if _, err := f.WriteString("hello there"); err != nil {
t.Fatalf("WriteString failed: %v", err)
}
// How to really test fsync ?
err = syscall.Fsync(int(f.Fd()))
if err != nil {
t.Errorf("fsync returned: %v", err)
}
}
func TestReadZero(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
tc.WriteFile(tc.origFile, []byte{}, 0644)
back, err := ioutil.ReadFile(tc.mountFile)
if err != nil {
t.Fatalf("ReadFile(%q): %v", tc.mountFile, err)
} else if len(back) != 0 {
t.Errorf("content length: got %d want %d", len(back), 0)
}
}
func CompareSlices(t *testing.T, got, want []byte) {
if len(got) != len(want) {
t.Errorf("content length: got %d want %d", len(got), len(want))
return
}
for i := range want {
if want[i] != got[i] {
t.Errorf("content mismatch byte %d, got %d want %d.", i, got[i], want[i])
break
}
}
}
// Check that reading large files doesn't lead to large allocations.
func TestReadLargeMemCheck(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
content := randomData(385 * 1023)
tc.WriteFile(tc.origFile, []byte(content), 0644)
f, err := os.Open(tc.mountFile)
if err != nil {
t.Fatalf("Open failed: %v", err)
}
defer f.Close()
buf := make([]byte, len(content)+1024)
f.Read(buf)
if err != nil {
t.Fatalf("Read failed: %v", err)
}
f.Close()
runtime.GC()
var before, after runtime.MemStats
N := 100
runtime.ReadMemStats(&before)
for i := 0; i < N; i++ {
f, _ := os.Open(tc.mountFile)
f.Read(buf)
f.Close()
}
runtime.ReadMemStats(&after)
delta := int((after.TotalAlloc - before.TotalAlloc))
delta = (delta - 40000) / N
t.Logf("bytes per read loop: %d", delta)
}
func TestReadLarge(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
content := randomData(385 * 1023)
tc.WriteFile(tc.origFile, []byte(content), 0644)
back, err := ioutil.ReadFile(tc.mountFile)
if err != nil {
t.Fatalf("ReadFile failed: %v", err)
}
CompareSlices(t, back, content)
}
func TestWriteLarge(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
content := randomData(385 * 1023)
tc.WriteFile(tc.mountFile, []byte(content), 0644)
back, err := ioutil.ReadFile(tc.origFile)
if err != nil {
t.Fatalf("ReadFile failed: %v", err)
}
CompareSlices(t, back, content)
}
func randomLengthString(length int) string {
r := rand.Intn(length)
b := make([]byte, r)
for i := 0; i < r; i++ {
b[i] = byte(i%10) + byte('0')
}
return string(b)
}
func TestLargeDirRead(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
created := 100
names := make([]string, created)
subdir := filepath.Join(tc.orig, "readdirSubdir")
tc.Mkdir(subdir, 0700)
longname := "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
nameSet := make(map[string]bool)
for i := 0; i < created; i++ {
// Should vary file name length.
base := fmt.Sprintf("file%d%s", i,
randomLengthString(len(longname)))
name := filepath.Join(subdir, base)
nameSet[base] = true
tc.WriteFile(name, []byte("bla"), 0777)
names[i] = name
}
dir, err := os.Open(filepath.Join(tc.mnt, "readdirSubdir"))
if err != nil {
t.Fatalf("Open failed: %v", err)
}
defer dir.Close()
// Chunked read.
total := 0
readSet := make(map[string]bool)
for {
namesRead, err := dir.Readdirnames(200)
if len(namesRead) == 0 || err == io.EOF {
break
}
if err != nil {
t.Fatalf("Readdirnames failed: %v", err)
}
for _, v := range namesRead {
readSet[v] = true
}
total += len(namesRead)
}
if total != created {
t.Errorf("readdir mismatch got %v wanted %v", total, created)
}
for k := range nameSet {
_, ok := readSet[k]
if !ok {
t.Errorf("Name %v not found in output", k)
}
}
}
func TestRootDir(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
d, err := os.Open(tc.mnt)
if err != nil {
t.Fatalf("Open failed: %v", err)
}
if _, err := d.Readdirnames(-1); err != nil {
t.Fatalf("Readdirnames failed: %v", err)
}
if err := d.Close(); err != nil {
t.Fatalf("Close failed: %v", err)
}
}
func ioctl(fd int, cmd int, arg uintptr) (int, int) {
r0, _, e1 := syscall.Syscall(
syscall.SYS_IOCTL, uintptr(fd), uintptr(cmd), uintptr(arg))
val := int(r0)
errno := int(e1)
return val, errno
}
func TestIoctl(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
f, err := os.OpenFile(filepath.Join(tc.mnt, "hello.txt"),
os.O_WRONLY|os.O_CREATE, 0777)
if err != nil {
t.Fatalf("OpenFile failed: %v", err)
}
defer f.Close()
ioctl(int(f.Fd()), 0x5401, 42)
}
// This test is racy. If an external process consumes space while this
// runs, we may see spurious differences between the two statfs() calls.
func TestStatFs(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
empty := syscall.Statfs_t{}
s1 := empty
if err := syscall.Statfs(tc.orig, &s1); err != nil {
t.Fatal("statfs orig", err)
}
s2 := syscall.Statfs_t{}
if err := syscall.Statfs(tc.mnt, &s2); err != nil {
t.Fatal("statfs mnt", err)
}
clearStatfs(&s1)
clearStatfs(&s2)
if !reflect.DeepEqual(s1, s2) {
t.Errorf("statfs mismatch %#v != %#v", s1, s2)
}
}
func TestFStatFs(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
fOrig, err := os.OpenFile(tc.orig+"/file", os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0644)
if err != nil {
t.Fatalf("OpenFile failed: %v", err)
}
defer fOrig.Close()
empty := syscall.Statfs_t{}
s1 := empty
if errno := syscall.Fstatfs(int(fOrig.Fd()), &s1); errno != nil {
t.Fatal("statfs orig", err)
}
fMnt, err := os.OpenFile(tc.mnt+"/file", os.O_RDWR, 0644)
if err != nil {
t.Fatalf("OpenFile failed: %v", err)
}
defer fMnt.Close()
s2 := empty
if errno := syscall.Fstatfs(int(fMnt.Fd()), &s2); errno != nil {
t.Fatal("statfs mnt", err)
}
clearStatfs(&s1)
clearStatfs(&s2)
if !reflect.DeepEqual(s1, s2) {
t.Errorf("statfs mismatch: %#v != %#v", s1, s2)
}
}
func TestOriginalIsSymlink(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "go-fuse-loopback_test")
if err != nil {
t.Fatalf("TempDir failed: %v", err)
}
defer os.RemoveAll(tmpDir)
orig := tmpDir + "/orig"
err = os.Mkdir(orig, 0755)
if err != nil {
t.Fatalf("Mkdir failed: %v", err)
}
link := tmpDir + "/link"
mnt := tmpDir + "/mnt"
if err := os.Mkdir(mnt, 0755); err != nil {
t.Fatalf("Mkdir failed: %v", err)
}
if err := os.Symlink("orig", link); err != nil {
t.Fatalf("Symlink failed: %v", err)
}
fs := pathfs.NewLoopbackFileSystem(link)
nfs := pathfs.NewPathNodeFs(fs, nil)
state, _, err := nodefs.MountRoot(mnt, nfs.Root(), nil)
if err != nil {
t.Fatalf("MountNodeFileSystem failed: %v", err)
}
defer state.Unmount()
go state.Serve()
if err := state.WaitMount(); err != nil {
t.Fatal("WaitMount", err)
}
if _, err := os.Lstat(mnt); err != nil {
t.Fatalf("Lstat failed: %v", err)
}
}
func TestDoubleOpen(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
tc.WriteFile(tc.orig+"/file", []byte("blabla"), 0644)
roFile, err := os.Open(tc.mnt + "/file")
if err != nil {
t.Fatalf(" failed: %v", err)
}
defer roFile.Close()
rwFile, err := os.OpenFile(tc.mnt+"/file", os.O_WRONLY|os.O_TRUNC, 0666)
if err != nil {
t.Fatalf("OpenFile failed: %v", err)
}
defer rwFile.Close()
}
func TestUmask(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
// Make sure system setting does not affect test.
fn := tc.mnt + "/file"
mask := 020
cmd := exec.Command("/bin/sh", "-c",
fmt.Sprintf("umask %o && mkdir %s", mask, fn))
if err := cmd.Run(); err != nil {
t.Fatalf("cmd.Run: %v", err)
}
fi, err := os.Lstat(fn)
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
expect := mask ^ 0777
got := int(fi.Mode().Perm())
if got != expect {
t.Errorf("got %o, expect mode %o for file %s", got, expect, fn)
}
}
// Check that chgrp(1) works
func TestChgrp(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
f, err := os.Create(tc.mnt + "/file")
if err != nil {
t.Fatalf("Create failed: %v", err)
}
defer f.Close()
err = f.Chown(-1, os.Getgid())
if err != nil {
t.Errorf("Chown failed: %v", err)
}
}

Some files were not shown because too many files have changed in this diff Show More