pax_global_header00006660000000000000000000000064136417167120014522gustar00rootroot0000000000000052 comment=730713460d4fc41afdc2533bd37ff60c94c0c586 go-fuse-2.0.3/000077500000000000000000000000001364171671200130715ustar00rootroot00000000000000go-fuse-2.0.3/.gitignore000066400000000000000000000003151364171671200150600ustar00rootroot00000000000000*~ *.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 go-fuse-2.0.3/.travis.yml000066400000000000000000000017551364171671200152120ustar00rootroot00000000000000sudo: required # Ubuntu 18.04 "Bionic", https://docs.travis-ci.com/user/reference/bionic/ # Kernel 5.0.0-1026-gcp dist: bionic language: go go_import_path: github.com/hanwen/go-fuse go: - 1.10.x - 1.11.x - 1.12.x - 1.13.x - master matrix: fast_finish: true allow_failures: - go: master before_install: - sudo apt-get install -qq pkg-config fuse - sudo modprobe fuse - sudo chmod 666 /dev/fuse - sudo chown root:$USER /etc/fuse.conf install: - go get -t ./... - go get -t -race ./... # Travis CI has a no-output-timeout of 10 minutes. # Set "go test -timeout" lower so we get proper backtraces # on a hung test. # The tests sometimes hang in a way that "go test -timeout" # does not work anymore. Use the external "timeout" command # as backup, triggering 1 minute later. script: - set -e # fail fast - timeout -s QUIT -k 10s 90s go test -failfast -timeout 1m -p 1 -v ./fs - timeout -s QUIT -k 10s 6m go test -failfast -timeout 5m -p 1 -v ./... - set +e # restore go-fuse-2.0.3/AUTHORS000066400000000000000000000012311364171671200141360ustar00rootroot00000000000000Adam H. Leventhal Daniel Martí Fazlul Shahriar Frederick Akalin Google Inc. Haitao Li Jakob Unterwurzacher James D. Nurmi Jeff Kaoet Ibe Kirill Smelkov Logan Hanks Maria Shaldibina Nick Cooper Patrick Crosby Paul Jolly Paul Warren Shayan Pooya Valient Gough Yongwoo Park go-fuse-2.0.3/CONTRIBUTING000066400000000000000000000011241364171671200147210ustar00rootroot00000000000000 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 go-fuse-2.0.3/LICENSE000066400000000000000000000030631364171671200141000ustar00rootroot00000000000000// New BSD License // // Copyright (c) 2010 the Go-FUSE Authors. 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. // go-fuse-2.0.3/README.md000066400000000000000000000106551364171671200143570ustar00rootroot00000000000000# GO-FUSE [![Build Status](https://travis-ci.org/hanwen/go-fuse.svg?branch=master)](https://travis-ci.org/hanwen/go-fuse) [![GoDoc](https://godoc.org/github.com/hanwen/go-fuse?status.svg)](https://godoc.org/github.com/hanwen/go-fuse) Go native bindings for the FUSE kernel module. You should import and use [github.com/hanwen/go-fuse/fs](https://godoc.org/github.com/hanwen/go-fuse/fs) library. It follows the wire protocol closely, but provides convenient abstractions for building both node and path based file systems Older, deprecated APIs are available at [github.com/hanwen/go-fuse/fuse/pathfs](https://godoc.org/github.com/hanwen/go-fuse/fuse/pathfs) and [github.com/hanwen/go-fuse/fuse/nodefs](https://godoc.org/github.com/hanwen/go-fuse/fuse/nodefs). ## Comparison with other FUSE libraries The FUSE library gained a new, cleaned-up API during a rewrite completed in 2019. Find extensive documentation [here](https://godoc.org/github.com/hanwen/go-fuse/). Further highlights of this library is * Comprehensive and up to date protocol support (up to 7.12.28). * Performance that is competitive with libfuse. ## Examples * `example/hello/main.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, ```shell 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 ```shell mkdir /tmp/mountpoint example/loopback/loopback -debug /tmp/mountpoint /some/other/directory & ls /tmp/mountpoint fusermount -u /tmp/mountpoint ``` ## 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. * Tests are expected to pass; report any failure as a bug! ## 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: * Missing support for `CUSE`, `BMAP`, `IOCTL` ## License Like Go, this library is distributed under the new BSD license. See accompanying LICENSE file. -------- ## Appendix I. Go-FUSE log format To increase signal/noise ratio Go-FUSE uses abbreviations in its debug log output. Here is how to read it: - `iX` means `inode X`; - `gX` means `generation X`; - `tA` and `tE` means timeout for attributes and directory entry correspondingly; - `[ +)` means data range from `` inclusive till `+` exclusive; - `Xb` means `X bytes`. Every line is prefixed with either `rx ` or `tx ` to denote whether it was for kernel request, which Go-FUSE received, or reply, which Go-FUSE sent back to kernel. Example debug log output: ``` rx 2: LOOKUP i1 [".wcfs"] 6b tx 2: OK, {i3 g2 tE=1s tA=1s {M040755 SZ=0 L=0 1000:1000 B0*0 i0:3 A 0.000000 M 0.000000 C 0.000000}} rx 3: LOOKUP i3 ["zurl"] 5b tx 3: OK, {i4 g3 tE=1s tA=1s {M0100644 SZ=33 L=1 1000:1000 B0*0 i0:4 A 0.000000 M 0.000000 C 0.000000}} rx 4: OPEN i4 {O_RDONLY,0x8000} tx 4: 38=function not implemented, {Fh 0 } rx 5: READ i4 {Fh 0 [0 +4096) L 0 RDONLY,0x8000} tx 5: OK, 33b data "file:///"... rx 6: GETATTR i4 {Fh 0} tx 6: OK, {tA=1s {M0100644 SZ=33 L=1 1000:1000 B0*0 i0:4 A 0.000000 M 0.000000 C 0.000000}} rx 7: FLUSH i4 {Fh 0} tx 7: OK rx 8: LOOKUP i1 ["head"] 5b tx 8: OK, {i5 g4 tE=1s tA=1s {M040755 SZ=0 L=0 1000:1000 B0*0 i0:5 A 0.000000 M 0.000000 C 0.000000}} rx 9: LOOKUP i5 ["bigfile"] 8b tx 9: OK, {i6 g5 tE=1s tA=1s {M040755 SZ=0 L=0 1000:1000 B0*0 i0:6 A 0.000000 M 0.000000 C 0.000000}} rx 10: FLUSH i4 {Fh 0} tx 10: OK rx 11: GETATTR i1 {Fh 0} tx 11: OK, {tA=1s {M040755 SZ=0 L=1 1000:1000 B0*0 i0:1 A 0.000000 M 0.000000 C 0.000000}} ``` go-fuse-2.0.3/all.bash000077500000000000000000000024731364171671200145110ustar00rootroot00000000000000#!/bin/sh set -eu 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 example/statfs ; \ do go build -o /dev/null github.com/hanwen/go-fuse/${d} done for d in fuse zipfs unionfs fuse/test do ( cd $d # Make sure it compiles on all platforms. for GOOS in darwin linux ; do export GOOS go test -c -i github.com/hanwen/go-fuse/$d done 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 example/statfs ; \ 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 go-fuse-2.0.3/benchmark/000077500000000000000000000000001364171671200150235ustar00rootroot00000000000000go-fuse-2.0.3/benchmark/Makefile000066400000000000000000000003131364171671200164600ustar00rootroot00000000000000all: cstatfs bulkstat.bin cstatfs: statfs.cc g++ -O2 -Wall -std=c++0x $< `pkg-config fuse --cflags --libs` -o $@ bulkstat.bin: bulkstat/main.go (cd bulkstat && go build main.go) cp bulkstat/main $@ go-fuse-2.0.3/benchmark/benchmark.go000066400000000000000000000011571364171671200173100ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package benchmark // Routines for benchmarking fuse. import ( "bufio" "log" "os" ) 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") } return l } go-fuse-2.0.3/benchmark/bulkstat/000077500000000000000000000000001364171671200166545ustar00rootroot00000000000000go-fuse-2.0.3/benchmark/bulkstat/main.go000066400000000000000000000031651364171671200201340ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "bufio" "flag" "log" "os" "path/filepath" "sync" ) func BulkStat(parallelism int, files []string) { todo := make(chan string, len(files)) var wg sync.WaitGroup wg.Add(parallelism) for i := 0; i < parallelism; i++ { go func() { for { fn := <-todo if fn == "" { break } _, err := os.Lstat(fn) if err != nil { log.Fatal("All stats should succeed:", err) } } wg.Done() }() } for _, v := range files { todo <- v } close(todo) wg.Wait() } 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") } return l } func main() { N := flag.Int("N", 1000, "how many files to stat") cpu := flag.Int("cpu", 1, "how many threads to use") prefix := flag.String("prefix", "", "mount point") quiet := flag.Bool("quiet", false, "be quiet") flag.Parse() f := flag.Arg(0) files := ReadLines(f) for i, f := range files { files[i] = filepath.Join(*prefix, f) } if !*quiet { log.Printf("statting %d with %d threads; first file %s (%d names)", *N, *cpu, files[0], len(files)) } todo := *N for todo > 0 { if len(files) > todo { files = files[:todo] } BulkStat(*cpu, files) todo -= len(files) } } go-fuse-2.0.3/benchmark/latencymap.go000066400000000000000000000017761364171671200175220ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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() if l == nil { return 0, 0 } 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 } go-fuse-2.0.3/benchmark/latencymap_test.go000066400000000000000000000007141364171671200205500ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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) } } go-fuse-2.0.3/benchmark/stat_test.go000066400000000000000000000135361364171671200173740ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package benchmark import ( "fmt" "io/ioutil" "log" "os" "os/exec" "path/filepath" "runtime" "sort" "syscall" "testing" "time" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/internal/testutil" ) func setupFs(node fs.InodeEmbedder, N int) (string, func()) { opts := &fs.Options{} opts.Debug = testutil.VerboseTest() mountPoint := testutil.TempDir() server, err := fs.Mount(mountPoint, node, opts) if err != nil { log.Panicf("cannot mount %v", err) } lmap := NewLatencyMap() if testutil.VerboseTest() { server.RecordLatencies(lmap) } return mountPoint, func() { if testutil.VerboseTest() { var total time.Duration for _, n := range []string{"LOOKUP", "GETATTR", "OPENDIR", "READDIR", "READDIRPLUS", "RELEASEDIR", "FLUSH", } { if count, dt := lmap.Get(n); count > 0 { total += dt log.Printf("%s %v/call n=%d", n, dt/time.Duration(count), count) } } log.Printf("total %v, %v/bench op", total, total/time.Duration(N)) } err := server.Unmount() if err != nil { log.Println("error during unmount", err) } else { os.RemoveAll(mountPoint) } } } func TestNewStatFs(t *testing.T) { fs := &StatFS{} for _, n := range []string{ "file.txt", "sub/dir/foo.txt", "sub/dir/bar.txt", "sub/marine.txt"} { fs.AddFile(n, fuse.Attr{Mode: syscall.S_IFREG}) } wd, clean := setupFs(fs, 1) 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 BenchmarkGoFuseStat(b *testing.B) { b.StopTimer() fs := &StatFS{} wd, _ := os.Getwd() fileList := wd + "/testpaths.txt" files := ReadLines(fileList) for _, fn := range files { fs.AddFile(fn, fuse.Attr{Mode: syscall.S_IFREG}) } wd, clean := setupFs(fs, b.N) defer clean() for i, l := range files { files[i] = filepath.Join(wd, l) } threads := runtime.GOMAXPROCS(0) if err := TestingBOnePass(b, threads, fileList, wd); err != nil { log.Fatalf("TestingBOnePass %v8", err) } } func readdir(d string) error { f, err := os.Open(d) if err != nil { return err } if _, err := f.Readdirnames(-1); err != nil { return err } return f.Close() } func BenchmarkGoFuseReaddir(b *testing.B) { b.StopTimer() fs := &StatFS{} wd, _ := os.Getwd() dirSet := map[string]struct{}{} for _, fn := range ReadLines(wd + "/testpaths.txt") { fs.AddFile(fn, fuse.Attr{Mode: syscall.S_IFREG}) dirSet[filepath.Dir(fn)] = struct{}{} } wd, clean := setupFs(fs, b.N) defer clean() var dirs []string for dir := range dirSet { dirs = append(dirs, filepath.Join(wd, dir)) } b.StartTimer() todo := b.N for todo > 0 { if len(dirs) > todo { dirs = dirs[:todo] } for _, d := range dirs { if err := readdir(d); err != nil { b.Fatal(err) } } todo -= len(dirs) } b.StopTimer() } func TestingBOnePass(b *testing.B, threads int, filelist, mountPoint string) error { runtime.GC() var before, after runtime.MemStats runtime.ReadMemStats(&before) // We shell out to an external program, so the time spent by // the part stat'ing doesn't interfere with the time spent by // the FUSE server. cmd := exec.Command("./bulkstat.bin", fmt.Sprintf("-cpu=%d", threads), fmt.Sprintf("-prefix=%s", mountPoint), fmt.Sprintf("-N=%d", b.N), fmt.Sprintf("-quiet=%v", !testutil.VerboseTest()), filelist) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr b.StartTimer() err := cmd.Run() b.StopTimer() runtime.ReadMemStats(&after) if err != nil { return err } if testutil.VerboseTest() { fmt.Printf("GC count %d, total GC time: %d ns/file\n", after.NumGC-before.NumGC, (after.PauseTotalNs-before.PauseTotalNs)/uint64(b.N)) } return nil } // 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() fileList := wd + "/testpaths.txt" lines := ReadLines(fileList) 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 := testutil.TempDir() 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())) 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() time.Sleep(100 * time.Millisecond) os.Lstat(mountPoint) threads := runtime.GOMAXPROCS(0) if err := TestingBOnePass(b, threads, fileList, mountPoint); err != nil { log.Fatalf("TestingBOnePass %v", err) } } go-fuse-2.0.3/benchmark/statfs.cc000066400000000000000000000037411364171671200166430ustar00rootroot00000000000000// +build !cgo // g++ -Wall `pkg-config fuse --cflags --libs` statfs.cc -o statfs #include #include using std::string; using std::unordered_map; #define FUSE_USE_VERSION 26 extern "C" { #include } #include #include #include #include #include #include useconds_t delay_usec; class StatFs { public: void readFrom(const string& fn); unordered_map is_dir_; int GetAttr(const char *name, struct stat *statbuf) { if (strcmp(name, "/") == 0) { statbuf->st_mode = S_IFDIR | 0777; return 0; } unordered_map::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); } go-fuse-2.0.3/benchmark/statfs.go000066400000000000000000000025411364171671200166600ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package benchmark import ( "context" "path/filepath" "strings" "syscall" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" ) type StatFS struct { fs.Inode files map[string]fuse.Attr } var _ = (fs.NodeOnAdder)((*StatFS)(nil)) func (r *StatFS) OnAdd(ctx context.Context) { for nm, a := range r.files { r.addFile(nm, a) } r.files = nil } func (r *StatFS) AddFile(name string, a fuse.Attr) { if r.files == nil { r.files = map[string]fuse.Attr{} } r.files[name] = a } func (r *StatFS) addFile(name string, a fuse.Attr) { dir, base := filepath.Split(name) p := r.EmbeddedInode() // Add directories leading up to the file. for _, component := range strings.Split(dir, "/") { if len(component) == 0 { continue } ch := p.GetChild(component) if ch == nil { // Create a directory ch = p.NewPersistentInode(context.Background(), &fs.Inode{}, fs.StableAttr{Mode: syscall.S_IFDIR}) // Add it p.AddChild(component, ch, true) } p = ch } // Create the file child := p.NewPersistentInode(context.Background(), &fs.MemRegularFile{ Data: make([]byte, a.Size), Attr: a, }, fs.StableAttr{}) // And add it p.AddChild(base, child, true) } go-fuse-2.0.3/benchmark/testpaths.txt000066400000000000000000001233701364171671200176110ustar00rootroot00000000000000org/classpath/icedtea/pulseaudio/PulseAudioVolumeControl.java org/classpath/icedtea/pulseaudio/ContextEvent.java org/classpath/icedtea/pulseaudio/StreamSampleSpecification.java org/classpath/icedtea/pulseaudio/PulseAudioMixerProvider.java org/classpath/icedtea/pulseaudio/PulseAudioPlaybackLine.java org/classpath/icedtea/pulseaudio/PulseAudioMixerInfo.java org/classpath/icedtea/pulseaudio/PulseAudioLine.java org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java org/classpath/icedtea/pulseaudio/PulseAudioClip.java org/classpath/icedtea/pulseaudio/SecurityWrapper.java org/classpath/icedtea/pulseaudio/ContextListener.java org/classpath/icedtea/pulseaudio/PulseAudioPort.java org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java org/classpath/icedtea/pulseaudio/Debug.java org/classpath/icedtea/pulseaudio/Stream.java org/classpath/icedtea/pulseaudio/Operation.java org/classpath/icedtea/pulseaudio/PulseAudioSourcePort.java org/classpath/icedtea/pulseaudio/PulseAudioTargetPort.java org/classpath/icedtea/pulseaudio/PulseAudioMixer.java org/classpath/icedtea/pulseaudio/StreamBufferAttributes.java org/classpath/icedtea/pulseaudio/EventLoop.java org/w3c/dom/xpath/XPathEvaluator.java org/w3c/dom/xpath/XPathException.java org/w3c/dom/xpath/XPathNSResolver.java org/w3c/dom/xpath/XPathNamespace.java org/w3c/dom/xpath/XPathExpression.java org/w3c/dom/xpath/XPathResult.java org/w3c/dom/DOMConfiguration.java org/w3c/dom/DOMException.java org/w3c/dom/Node.java org/w3c/dom/Document.java org/w3c/dom/NamedNodeMap.java org/w3c/dom/TypeInfo.java org/w3c/dom/CharacterData.java org/w3c/dom/stylesheets/LinkStyle.java org/w3c/dom/stylesheets/StyleSheet.java org/w3c/dom/stylesheets/StyleSheetList.java org/w3c/dom/stylesheets/MediaList.java org/w3c/dom/stylesheets/DocumentStyle.java org/w3c/dom/html/HTMLMenuElement.java org/w3c/dom/html/HTMLDocument.java org/w3c/dom/html/HTMLTableColElement.java org/w3c/dom/html/HTMLLabelElement.java org/w3c/dom/html/HTMLOptionElement.java org/w3c/dom/html/HTMLLegendElement.java org/w3c/dom/html/HTMLHtmlElement.java org/w3c/dom/html/HTMLMapElement.java org/w3c/dom/html/HTMLParamElement.java org/w3c/dom/html/HTMLBRElement.java org/w3c/dom/html/HTMLLIElement.java org/w3c/dom/html/HTMLBaseFontElement.java org/w3c/dom/html/HTMLTableElement.java org/w3c/dom/html/HTMLAnchorElement.java org/w3c/dom/html/HTMLHRElement.java org/w3c/dom/html/HTMLUListElement.java org/w3c/dom/html/HTMLSelectElement.java org/w3c/dom/html/HTMLTableRowElement.java org/w3c/dom/html/HTMLDListElement.java org/w3c/dom/html/HTMLDirectoryElement.java org/w3c/dom/html/HTMLFrameElement.java org/w3c/dom/html/HTMLTableSectionElement.java org/w3c/dom/html/HTMLTableCellElement.java org/w3c/dom/html/HTMLFontElement.java org/w3c/dom/html/HTMLImageElement.java org/w3c/dom/html/HTMLOptGroupElement.java org/w3c/dom/html/HTMLBaseElement.java org/w3c/dom/html/HTMLIsIndexElement.java org/w3c/dom/html/HTMLFrameSetElement.java org/w3c/dom/html/HTMLStyleElement.java org/w3c/dom/html/HTMLTableCaptionElement.java org/w3c/dom/html/HTMLInputElement.java org/w3c/dom/html/HTMLFieldSetElement.java org/w3c/dom/html/HTMLOListElement.java org/w3c/dom/html/HTMLAreaElement.java org/w3c/dom/html/HTMLIFrameElement.java org/w3c/dom/html/HTMLQuoteElement.java org/w3c/dom/html/HTMLMetaElement.java org/w3c/dom/html/HTMLDOMImplementation.java org/w3c/dom/html/HTMLHeadElement.java org/w3c/dom/html/HTMLFormElement.java org/w3c/dom/html/HTMLModElement.java org/w3c/dom/html/HTMLPreElement.java org/w3c/dom/html/HTMLButtonElement.java org/w3c/dom/html/HTMLBodyElement.java org/w3c/dom/html/HTMLDivElement.java org/w3c/dom/html/HTMLCollection.java org/w3c/dom/html/HTMLScriptElement.java org/w3c/dom/html/HTMLParagraphElement.java org/w3c/dom/html/HTMLObjectElement.java org/w3c/dom/html/HTMLTitleElement.java org/w3c/dom/html/HTMLAppletElement.java org/w3c/dom/html/HTMLTextAreaElement.java org/w3c/dom/html/HTMLHeadingElement.java org/w3c/dom/html/HTMLLinkElement.java org/w3c/dom/html/HTMLElement.java org/w3c/dom/UserDataHandler.java org/w3c/dom/bootstrap/DOMImplementationRegistry.java org/w3c/dom/Comment.java org/w3c/dom/Element.java org/w3c/dom/traversal/TreeWalker.java org/w3c/dom/traversal/DocumentTraversal.java org/w3c/dom/traversal/NodeIterator.java org/w3c/dom/traversal/NodeFilter.java org/w3c/dom/DOMImplementationList.java org/w3c/dom/DOMImplementationSource.java org/w3c/dom/ProcessingInstruction.java org/w3c/dom/DocumentFragment.java org/w3c/dom/NodeList.java org/w3c/dom/DOMStringList.java org/w3c/dom/views/AbstractView.java org/w3c/dom/views/DocumentView.java org/w3c/dom/Entity.java org/w3c/dom/ls/LSProgressEvent.java org/w3c/dom/ls/LSSerializer.java org/w3c/dom/ls/LSOutput.java org/w3c/dom/ls/LSInput.java org/w3c/dom/ls/LSSerializerFilter.java org/w3c/dom/ls/LSParserFilter.java org/w3c/dom/ls/LSParser.java org/w3c/dom/ls/LSLoadEvent.java org/w3c/dom/ls/LSException.java org/w3c/dom/ls/DOMImplementationLS.java org/w3c/dom/ls/LSResourceResolver.java org/w3c/dom/CDATASection.java org/w3c/dom/DOMErrorHandler.java org/w3c/dom/ranges/RangeException.java org/w3c/dom/ranges/Range.java org/w3c/dom/ranges/DocumentRange.java org/w3c/dom/Text.java org/w3c/dom/DOMError.java org/w3c/dom/DocumentType.java org/w3c/dom/css/CSSFontFaceRule.java org/w3c/dom/css/CSSUnknownRule.java org/w3c/dom/css/CSSImportRule.java org/w3c/dom/css/CSSValue.java org/w3c/dom/css/CSSStyleDeclaration.java org/w3c/dom/css/CSS2Properties.java org/w3c/dom/css/CSSRule.java org/w3c/dom/css/CSSValueList.java org/w3c/dom/css/CSSPageRule.java org/w3c/dom/css/CSSCharsetRule.java org/w3c/dom/css/DocumentCSS.java org/w3c/dom/css/Rect.java org/w3c/dom/css/CSSStyleSheet.java org/w3c/dom/css/Counter.java org/w3c/dom/css/CSSRuleList.java org/w3c/dom/css/ViewCSS.java org/w3c/dom/css/ElementCSSInlineStyle.java org/w3c/dom/css/RGBColor.java org/w3c/dom/css/DOMImplementationCSS.java org/w3c/dom/css/CSSPrimitiveValue.java org/w3c/dom/css/CSSMediaRule.java org/w3c/dom/css/CSSStyleRule.java org/w3c/dom/Attr.java org/w3c/dom/NameList.java org/w3c/dom/events/EventListener.java org/w3c/dom/events/Event.java org/w3c/dom/events/MouseEvent.java org/w3c/dom/events/EventTarget.java org/w3c/dom/events/UIEvent.java org/w3c/dom/events/DocumentEvent.java org/w3c/dom/events/EventException.java org/w3c/dom/events/MutationEvent.java org/w3c/dom/EntityReference.java org/w3c/dom/DOMLocator.java org/w3c/dom/Notation.java org/w3c/dom/DOMImplementation.java org/xml/sax/ErrorHandler.java org/xml/sax/DTDHandler.java org/xml/sax/XMLReader.java org/xml/sax/AttributeList.java org/xml/sax/XMLFilter.java org/xml/sax/ext/Attributes2Impl.java org/xml/sax/ext/DeclHandler.java org/xml/sax/ext/Attributes2.java org/xml/sax/ext/DefaultHandler2.java org/xml/sax/ext/Locator2.java org/xml/sax/ext/LexicalHandler.java org/xml/sax/ext/EntityResolver2.java org/xml/sax/ext/Locator2Impl.java org/xml/sax/helpers/ParserAdapter.java org/xml/sax/helpers/LocatorImpl.java org/xml/sax/helpers/XMLReaderAdapter.java org/xml/sax/helpers/XMLFilterImpl.java org/xml/sax/helpers/XMLReaderFactory.java org/xml/sax/helpers/AttributeListImpl.java org/xml/sax/helpers/AttributesImpl.java org/xml/sax/helpers/NamespaceSupport.java org/xml/sax/helpers/DefaultHandler.java org/xml/sax/helpers/ParserFactory.java org/xml/sax/helpers/NewInstance.java org/xml/sax/SAXException.java org/xml/sax/Locator.java org/xml/sax/SAXParseException.java org/xml/sax/HandlerBase.java org/xml/sax/InputSource.java org/xml/sax/EntityResolver.java org/xml/sax/Parser.java org/xml/sax/SAXNotSupportedException.java org/xml/sax/SAXNotRecognizedException.java org/xml/sax/ContentHandler.java org/xml/sax/Attributes.java org/xml/sax/DocumentHandler.java org/relaxng/datatype/Datatype.java org/relaxng/datatype/ValidationContext.java org/relaxng/datatype/helpers/ParameterlessDatatypeBuilder.java org/relaxng/datatype/helpers/DatatypeLibraryLoader.java org/relaxng/datatype/helpers/StreamingValidatorImpl.java org/relaxng/datatype/DatatypeBuilder.java org/relaxng/datatype/DatatypeStreamingValidator.java org/relaxng/datatype/DatatypeLibraryFactory.java org/relaxng/datatype/DatatypeLibrary.java org/relaxng/datatype/DatatypeException.java org/ietf/jgss/Oid.java org/ietf/jgss/GSSCredential.java org/ietf/jgss/GSSException.java org/ietf/jgss/GSSName.java org/ietf/jgss/GSSContext.java org/ietf/jgss/MessageProp.java org/ietf/jgss/ChannelBinding.java org/ietf/jgss/GSSManager.java org/omg/PortableServer/ThreadPolicy.java org/omg/PortableServer/ServantLocatorPackage/CookieHolder.java org/omg/PortableServer/ForwardRequestHelper.java org/omg/PortableServer/CurrentHelper.java org/omg/PortableServer/ServantActivatorOperations.java org/omg/PortableServer/ServantLocator.java org/omg/PortableServer/POAHelper.java org/omg/PortableServer/_ServantActivatorStub.java org/omg/PortableServer/LifespanPolicy.java org/omg/PortableServer/ServantActivator.java org/omg/PortableServer/_ServantLocatorStub.java org/omg/PortableServer/ID_UNIQUENESS_POLICY_ID.java org/omg/PortableServer/DynamicImplementation.java org/omg/PortableServer/REQUEST_PROCESSING_POLICY_ID.java org/omg/PortableServer/ServantLocatorHelper.java org/omg/PortableServer/ThreadPolicyValue.java org/omg/PortableServer/ImplicitActivationPolicyValue.java org/omg/PortableServer/THREAD_POLICY_ID.java org/omg/PortableServer/IdUniquenessPolicyOperations.java org/omg/PortableServer/RequestProcessingPolicyValue.java org/omg/PortableServer/ServantRetentionPolicyOperations.java org/omg/PortableServer/Servant.java org/omg/PortableServer/ServantActivatorPOA.java org/omg/PortableServer/ImplicitActivationPolicy.java org/omg/PortableServer/Current.java org/omg/PortableServer/POAOperations.java org/omg/PortableServer/ForwardRequest.java org/omg/PortableServer/portable/Delegate.java org/omg/PortableServer/AdapterActivator.java org/omg/PortableServer/IdAssignmentPolicyOperations.java org/omg/PortableServer/LifespanPolicyOperations.java org/omg/PortableServer/ThreadPolicyOperations.java org/omg/PortableServer/POAManagerOperations.java org/omg/PortableServer/SERVANT_RETENTION_POLICY_ID.java org/omg/PortableServer/RequestProcessingPolicy.java org/omg/PortableServer/ImplicitActivationPolicyOperations.java org/omg/PortableServer/ServantLocatorPOA.java org/omg/PortableServer/POA.java org/omg/PortableServer/ServantActivatorHelper.java org/omg/PortableServer/ServantRetentionPolicy.java org/omg/PortableServer/POAManager.java org/omg/PortableServer/IdAssignmentPolicyValue.java org/omg/PortableServer/LifespanPolicyValue.java org/omg/PortableServer/ID_ASSIGNMENT_POLICY_ID.java org/omg/PortableServer/POAPackage/WrongAdapterHelper.java org/omg/PortableServer/POAPackage/ObjectAlreadyActiveHelper.java org/omg/PortableServer/POAPackage/WrongAdapter.java org/omg/PortableServer/POAPackage/WrongPolicy.java org/omg/PortableServer/POAPackage/InvalidPolicyHelper.java org/omg/PortableServer/POAPackage/AdapterNonExistentHelper.java org/omg/PortableServer/POAPackage/WrongPolicyHelper.java org/omg/PortableServer/POAPackage/InvalidPolicy.java org/omg/PortableServer/POAPackage/NoServant.java org/omg/PortableServer/POAPackage/ServantAlreadyActive.java org/omg/PortableServer/POAPackage/ServantNotActive.java org/omg/PortableServer/POAPackage/ObjectAlreadyActive.java org/omg/PortableServer/POAPackage/ObjectNotActiveHelper.java org/omg/PortableServer/POAPackage/ServantAlreadyActiveHelper.java org/omg/PortableServer/POAPackage/NoServantHelper.java org/omg/PortableServer/POAPackage/AdapterAlreadyExists.java org/omg/PortableServer/POAPackage/ObjectNotActive.java org/omg/PortableServer/POAPackage/AdapterAlreadyExistsHelper.java org/omg/PortableServer/POAPackage/ServantNotActiveHelper.java org/omg/PortableServer/POAPackage/AdapterNonExistent.java org/omg/PortableServer/IdUniquenessPolicy.java org/omg/PortableServer/RequestProcessingPolicyOperations.java org/omg/PortableServer/CurrentOperations.java org/omg/PortableServer/POAManagerPackage/AdapterInactive.java org/omg/PortableServer/POAManagerPackage/AdapterInactiveHelper.java org/omg/PortableServer/POAManagerPackage/State.java org/omg/PortableServer/AdapterActivatorOperations.java org/omg/PortableServer/ServantManagerOperations.java org/omg/PortableServer/CurrentPackage/NoContextHelper.java org/omg/PortableServer/CurrentPackage/NoContext.java org/omg/PortableServer/IdUniquenessPolicyValue.java org/omg/PortableServer/ServantRetentionPolicyValue.java org/omg/PortableServer/LIFESPAN_POLICY_ID.java org/omg/PortableServer/ServantManager.java org/omg/PortableServer/IdAssignmentPolicy.java org/omg/PortableServer/IMPLICIT_ACTIVATION_POLICY_ID.java org/omg/PortableServer/ServantLocatorOperations.java org/omg/Dynamic/Parameter.java org/omg/stub/java/rmi/_Remote_Stub.java org/omg/SendingContext/RunTime.java org/omg/SendingContext/RunTimeOperations.java org/omg/IOP/TAG_MULTIPLE_COMPONENTS.java org/omg/IOP/ExceptionDetailMessage.java org/omg/IOP/CodecFactoryHelper.java org/omg/IOP/MultipleComponentProfileHelper.java org/omg/IOP/ProfileIdHelper.java org/omg/IOP/TaggedProfileHolder.java org/omg/IOP/CodecOperations.java org/omg/IOP/CodecPackage/TypeMismatchHelper.java org/omg/IOP/CodecPackage/InvalidTypeForEncoding.java org/omg/IOP/CodecPackage/FormatMismatchHelper.java org/omg/IOP/CodecPackage/TypeMismatch.java org/omg/IOP/CodecPackage/InvalidTypeForEncodingHelper.java org/omg/IOP/CodecPackage/FormatMismatch.java org/omg/IOP/ServiceContextListHolder.java org/omg/IOP/IORHolder.java org/omg/IOP/CodecFactoryOperations.java org/omg/IOP/TAG_RMI_CUSTOM_MAX_STREAM_FORMAT.java org/omg/IOP/IOR.java org/omg/IOP/MultipleComponentProfileHolder.java org/omg/IOP/TransactionService.java org/omg/IOP/TAG_JAVA_CODEBASE.java org/omg/IOP/TaggedComponent.java org/omg/IOP/TAG_CODE_SETS.java org/omg/IOP/Encoding.java org/omg/IOP/TaggedComponentHelper.java org/omg/IOP/TaggedProfile.java org/omg/IOP/ServiceIdHelper.java org/omg/IOP/ServiceContextHolder.java org/omg/IOP/TaggedComponentHolder.java org/omg/IOP/ENCODING_CDR_ENCAPS.java org/omg/IOP/ComponentIdHelper.java org/omg/IOP/CodeSets.java org/omg/IOP/ServiceContext.java org/omg/IOP/IORHelper.java org/omg/IOP/TaggedProfileHelper.java org/omg/IOP/CodecFactory.java org/omg/IOP/RMICustomMaxStreamFormat.java org/omg/IOP/TAG_INTERNET_IOP.java org/omg/IOP/ServiceContextHelper.java org/omg/IOP/Codec.java org/omg/IOP/TAG_POLICIES.java org/omg/IOP/ServiceContextListHelper.java org/omg/IOP/CodecFactoryPackage/UnknownEncodingHelper.java org/omg/IOP/CodecFactoryPackage/UnknownEncoding.java org/omg/IOP/TAG_ALTERNATE_IIOP_ADDRESS.java org/omg/IOP/TAG_ORB_TYPE.java org/omg/Messaging/SyncScopeHelper.java org/omg/Messaging/SYNC_WITH_TRANSPORT.java org/omg/CORBA/StringHolder.java org/omg/CORBA/UnknownUserExceptionHolder.java org/omg/CORBA/DynUnion.java org/omg/CORBA/DynSequence.java org/omg/CORBA/ServiceInformationHelper.java org/omg/CORBA/TRANSIENT.java org/omg/CORBA/ACTIVITY_REQUIRED.java org/omg/CORBA/Principal.java org/omg/CORBA/ParameterModeHelper.java org/omg/CORBA/CurrentHelper.java org/omg/CORBA/PolicyTypeHelper.java org/omg/CORBA/PolicyError.java org/omg/CORBA/VM_TRUNCATABLE.java org/omg/CORBA/DoubleSeqHolder.java org/omg/CORBA/ULongSeqHelper.java org/omg/CORBA/INTERNAL.java org/omg/CORBA/ValueBaseHolder.java org/omg/CORBA/WCharSeqHelper.java org/omg/CORBA/BAD_CONTEXT.java org/omg/CORBA/UNKNOWN.java org/omg/CORBA/WStringSeqHelper.java org/omg/CORBA/BooleanSeqHolder.java org/omg/CORBA/ORB.java org/omg/CORBA/BAD_TYPECODE.java org/omg/CORBA/CharHolder.java org/omg/CORBA/CompletionStatusHelper.java org/omg/CORBA/ULongLongSeqHolder.java org/omg/CORBA/INV_POLICY.java org/omg/CORBA/DATA_CONVERSION.java org/omg/CORBA/VM_NONE.java org/omg/CORBA/COMM_FAILURE.java org/omg/CORBA/DataOutputStream.java org/omg/CORBA/BAD_INV_ORDER.java org/omg/CORBA/DomainManagerOperations.java org/omg/CORBA/TypeCodePackage/Bounds.java org/omg/CORBA/TypeCodePackage/BadKind.java org/omg/CORBA/ARG_OUT.java org/omg/CORBA/IntHolder.java org/omg/CORBA/PolicyOperations.java org/omg/CORBA/CTX_RESTRICT_SCOPE.java org/omg/CORBA/ValueMember.java org/omg/CORBA/PolicyListHelper.java org/omg/CORBA/StringSeqHolder.java org/omg/CORBA/ObjectHolder.java org/omg/CORBA/FloatSeqHolder.java org/omg/CORBA/NO_RESOURCES.java org/omg/CORBA/FREE_MEM.java org/omg/CORBA/NO_IMPLEMENT.java org/omg/CORBA/ULongSeqHolder.java org/omg/CORBA/TIMEOUT.java org/omg/CORBA/PolicyErrorCodeHelper.java org/omg/CORBA/BAD_POLICY.java org/omg/CORBA/AnyHolder.java org/omg/CORBA/LongSeqHolder.java org/omg/CORBA/NVList.java org/omg/CORBA/INV_IDENT.java org/omg/CORBA/VersionSpecHelper.java org/omg/CORBA/IRObject.java org/omg/CORBA/DynamicImplementation.java org/omg/CORBA/PolicyListHolder.java org/omg/CORBA/StringValueHelper.java org/omg/CORBA/SetOverrideType.java org/omg/CORBA/DynFixed.java org/omg/CORBA/CharSeqHolder.java org/omg/CORBA/OBJECT_NOT_EXIST.java org/omg/CORBA/INV_FLAG.java org/omg/CORBA/BAD_QOS.java org/omg/CORBA/SetOverrideTypeHelper.java org/omg/CORBA/DefinitionKind.java org/omg/CORBA/TCKind.java org/omg/CORBA/NameValuePairHelper.java org/omg/CORBA/Bounds.java org/omg/CORBA/UShortSeqHolder.java org/omg/CORBA/UnionMemberHelper.java org/omg/CORBA/WrongTransaction.java org/omg/CORBA/CurrentHolder.java org/omg/CORBA/VisibilityHelper.java org/omg/CORBA/LongLongSeqHelper.java org/omg/CORBA/OBJ_ADAPTER.java org/omg/CORBA/ExceptionList.java org/omg/CORBA/INTF_REPOS.java org/omg/CORBA/VM_ABSTRACT.java org/omg/CORBA/TRANSACTION_ROLLEDBACK.java org/omg/CORBA/AnySeqHolder.java org/omg/CORBA/Object.java org/omg/CORBA/ARG_IN.java org/omg/CORBA/Current.java org/omg/CORBA/Context.java org/omg/CORBA/_PolicyStub.java org/omg/CORBA/ValueMemberHelper.java org/omg/CORBA/PolicyErrorHelper.java org/omg/CORBA/TypeCodeHolder.java org/omg/CORBA/TypeCode.java org/omg/CORBA/UserException.java org/omg/CORBA/WrongTransactionHolder.java org/omg/CORBA/WStringValueHelper.java org/omg/CORBA/IdentifierHelper.java org/omg/CORBA/UnknownUserException.java org/omg/CORBA/portable/StreamableValue.java org/omg/CORBA/portable/Delegate.java org/omg/CORBA/portable/InputStream.java org/omg/CORBA/portable/IDLEntity.java org/omg/CORBA/portable/ApplicationException.java org/omg/CORBA/portable/ValueOutputStream.java org/omg/CORBA/portable/InvokeHandler.java org/omg/CORBA/portable/Streamable.java org/omg/CORBA/portable/ServantObject.java org/omg/CORBA/portable/RemarshalException.java org/omg/CORBA/portable/UnknownException.java org/omg/CORBA/portable/CustomValue.java org/omg/CORBA/portable/ObjectImpl.java org/omg/CORBA/portable/BoxedValueHelper.java org/omg/CORBA/portable/ValueInputStream.java org/omg/CORBA/portable/ValueFactory.java org/omg/CORBA/portable/OutputStream.java org/omg/CORBA/portable/ResponseHandler.java org/omg/CORBA/portable/IndirectionException.java org/omg/CORBA/portable/ValueBase.java org/omg/CORBA/StringSeqHelper.java org/omg/CORBA/CharSeqHelper.java org/omg/CORBA/LongLongSeqHolder.java org/omg/CORBA/ORBPackage/InvalidName.java org/omg/CORBA/ORBPackage/InconsistentTypeCode.java org/omg/CORBA/UNSUPPORTED_POLICY_VALUE.java org/omg/CORBA/UnknownUserExceptionHelper.java org/omg/CORBA/DynValue.java org/omg/CORBA/Request.java org/omg/CORBA/PRIVATE_MEMBER.java org/omg/CORBA/MARSHAL.java org/omg/CORBA/DoubleHolder.java org/omg/CORBA/SystemException.java org/omg/CORBA/LongSeqHelper.java org/omg/CORBA/BAD_POLICY_VALUE.java org/omg/CORBA/ServiceInformation.java org/omg/CORBA/BooleanHolder.java org/omg/CORBA/BAD_POLICY_TYPE.java org/omg/CORBA/DataInputStream.java org/omg/CORBA/_IDLTypeStub.java org/omg/CORBA/NamedValue.java org/omg/CORBA/ShortSeqHelper.java org/omg/CORBA/DynEnum.java org/omg/CORBA/OMGVMCID.java org/omg/CORBA/ACTIVITY_COMPLETED.java org/omg/CORBA/IDLType.java org/omg/CORBA/PERSIST_STORE.java org/omg/CORBA/DoubleSeqHelper.java org/omg/CORBA/UNSUPPORTED_POLICY.java org/omg/CORBA/ServiceDetailHelper.java org/omg/CORBA/Any.java org/omg/CORBA/DynAny.java org/omg/CORBA/PolicyHolder.java org/omg/CORBA/CompletionStatus.java org/omg/CORBA/NO_PERMISSION.java org/omg/CORBA/UShortSeqHelper.java org/omg/CORBA/FloatSeqHelper.java org/omg/CORBA/StructMember.java org/omg/CORBA/ServiceDetail.java org/omg/CORBA/CustomMarshal.java org/omg/CORBA/IDLTypeHelper.java org/omg/CORBA/UnionMember.java org/omg/CORBA/IRObjectOperations.java org/omg/CORBA/BAD_PARAM.java org/omg/CORBA/ParameterMode.java org/omg/CORBA/PolicyErrorHolder.java org/omg/CORBA/AnySeqHelper.java org/omg/CORBA/OctetSeqHelper.java org/omg/CORBA/BooleanSeqHelper.java org/omg/CORBA/REBIND.java org/omg/CORBA/DynArray.java org/omg/CORBA/WrongTransactionHelper.java org/omg/CORBA/PrincipalHolder.java org/omg/CORBA/FloatHolder.java org/omg/CORBA/DomainManager.java org/omg/CORBA/PolicyHelper.java org/omg/CORBA/CurrentOperations.java org/omg/CORBA/ServiceInformationHolder.java org/omg/CORBA/ULongLongSeqHelper.java org/omg/CORBA/FieldNameHelper.java org/omg/CORBA/OctetSeqHolder.java org/omg/CORBA/LongHolder.java org/omg/CORBA/WStringSeqHolder.java org/omg/CORBA/ParameterModeHolder.java org/omg/CORBA/TRANSACTION_REQUIRED.java org/omg/CORBA/IMP_LIMIT.java org/omg/CORBA/PUBLIC_MEMBER.java org/omg/CORBA/IDLTypeOperations.java org/omg/CORBA/NO_MEMORY.java org/omg/CORBA/TRANSACTION_UNAVAILABLE.java org/omg/CORBA/Environment.java org/omg/CORBA/ARG_INOUT.java org/omg/CORBA/FixedHolder.java org/omg/CORBA/WCharSeqHolder.java org/omg/CORBA/CODESET_INCOMPATIBLE.java org/omg/CORBA/INVALID_TRANSACTION.java org/omg/CORBA/DynAnyPackage/Invalid.java org/omg/CORBA/DynAnyPackage/InvalidSeq.java org/omg/CORBA/DynAnyPackage/InvalidValue.java org/omg/CORBA/DynAnyPackage/TypeMismatch.java org/omg/CORBA/RepositoryIdHelper.java org/omg/CORBA/VM_CUSTOM.java org/omg/CORBA/INVALID_ACTIVITY.java org/omg/CORBA/ValueBaseHelper.java org/omg/CORBA/ShortSeqHolder.java org/omg/CORBA/ContextList.java org/omg/CORBA/DefinitionKindHelper.java org/omg/CORBA/BAD_OPERATION.java org/omg/CORBA/ShortHolder.java org/omg/CORBA/DynStruct.java org/omg/CORBA/Policy.java org/omg/CORBA/LocalObject.java org/omg/CORBA/INV_OBJREF.java org/omg/CORBA/INITIALIZE.java org/omg/CORBA/ObjectHelper.java org/omg/CORBA/TRANSACTION_MODE.java org/omg/CORBA/ServerRequest.java org/omg/CORBA/StructMemberHelper.java org/omg/CORBA/ByteHolder.java org/omg/CORBA/NO_RESPONSE.java org/omg/CORBA/NameValuePair.java org/omg/CORBA_2_3/ORB.java org/omg/CORBA_2_3/portable/Delegate.java org/omg/CORBA_2_3/portable/InputStream.java org/omg/CORBA_2_3/portable/ObjectImpl.java org/omg/CORBA_2_3/portable/OutputStream.java org/omg/CosNaming/_BindingIteratorImplBase.java org/omg/CosNaming/NameComponentHolder.java org/omg/CosNaming/_NamingContextStub.java org/omg/CosNaming/NamingContextExtOperations.java org/omg/CosNaming/NameHolder.java org/omg/CosNaming/NamingContextExtPackage/InvalidAddressHelper.java org/omg/CosNaming/NamingContextExtPackage/URLStringHelper.java org/omg/CosNaming/NamingContextExtPackage/InvalidAddressHolder.java org/omg/CosNaming/NamingContextExtPackage/InvalidAddress.java org/omg/CosNaming/NamingContextExtPackage/AddressHelper.java org/omg/CosNaming/NamingContextExtPackage/StringNameHelper.java org/omg/CosNaming/NamingContextPackage/AlreadyBoundHelper.java org/omg/CosNaming/NamingContextPackage/NotEmptyHelper.java org/omg/CosNaming/NamingContextPackage/InvalidNameHolder.java org/omg/CosNaming/NamingContextPackage/NotFoundReasonHelper.java org/omg/CosNaming/NamingContextPackage/InvalidNameHelper.java org/omg/CosNaming/NamingContextPackage/CannotProceed.java org/omg/CosNaming/NamingContextPackage/AlreadyBound.java org/omg/CosNaming/NamingContextPackage/NotEmptyHolder.java org/omg/CosNaming/NamingContextPackage/NotFoundReason.java org/omg/CosNaming/NamingContextPackage/InvalidName.java org/omg/CosNaming/NamingContextPackage/AlreadyBoundHolder.java org/omg/CosNaming/NamingContextPackage/NotFoundReasonHolder.java org/omg/CosNaming/NamingContextPackage/CannotProceedHolder.java org/omg/CosNaming/NamingContextPackage/NotFoundHolder.java org/omg/CosNaming/NamingContextPackage/NotFoundHelper.java org/omg/CosNaming/NamingContextPackage/NotFound.java org/omg/CosNaming/NamingContextPackage/NotEmpty.java org/omg/CosNaming/NamingContextPackage/CannotProceedHelper.java org/omg/CosNaming/_NamingContextImplBase.java org/omg/CosNaming/NamingContextPOA.java org/omg/CosNaming/BindingIterator.java org/omg/CosNaming/BindingHelper.java org/omg/CosNaming/NamingContextHolder.java org/omg/CosNaming/BindingHolder.java org/omg/CosNaming/BindingType.java org/omg/CosNaming/BindingListHolder.java org/omg/CosNaming/BindingTypeHelper.java org/omg/CosNaming/NamingContextExt.java org/omg/CosNaming/NamingContext.java org/omg/CosNaming/NamingContextExtPOA.java org/omg/CosNaming/IstringHelper.java org/omg/CosNaming/NamingContextExtHelper.java org/omg/CosNaming/BindingTypeHolder.java org/omg/CosNaming/BindingIteratorPOA.java org/omg/CosNaming/_NamingContextExtStub.java org/omg/CosNaming/BindingListHelper.java org/omg/CosNaming/NameComponent.java org/omg/CosNaming/NameComponentHelper.java org/omg/CosNaming/NamingContextExtHolder.java org/omg/CosNaming/Binding.java org/omg/CosNaming/NameHelper.java org/omg/CosNaming/BindingIteratorHelper.java org/omg/CosNaming/BindingIteratorHolder.java org/omg/CosNaming/_BindingIteratorStub.java org/omg/CosNaming/NamingContextHelper.java org/omg/CosNaming/BindingIteratorOperations.java org/omg/CosNaming/NamingContextOperations.java org/omg/DynamicAny/_DynAnyStub.java org/omg/DynamicAny/DynUnion.java org/omg/DynamicAny/DynSequence.java org/omg/DynamicAny/DynValueBoxOperations.java org/omg/DynamicAny/DynAnySeqHelper.java org/omg/DynamicAny/DynEnumHelper.java org/omg/DynamicAny/NameDynAnyPair.java org/omg/DynamicAny/DynValueCommon.java org/omg/DynamicAny/_DynEnumStub.java org/omg/DynamicAny/DynStructOperations.java org/omg/DynamicAny/DynValueOperations.java org/omg/DynamicAny/_DynSequenceStub.java org/omg/DynamicAny/DynFixed.java org/omg/DynamicAny/_DynValueStub.java org/omg/DynamicAny/DynUnionHelper.java org/omg/DynamicAny/DynValueHelper.java org/omg/DynamicAny/NameValuePairHelper.java org/omg/DynamicAny/DynEnumOperations.java org/omg/DynamicAny/DynFixedOperations.java org/omg/DynamicAny/DynAnyFactoryOperations.java org/omg/DynamicAny/DynFixedHelper.java org/omg/DynamicAny/_DynAnyFactoryStub.java org/omg/DynamicAny/DynAnyOperations.java org/omg/DynamicAny/DynArrayHelper.java org/omg/DynamicAny/DynSequenceHelper.java org/omg/DynamicAny/DynAnyFactoryPackage/InconsistentTypeCode.java org/omg/DynamicAny/DynAnyFactoryPackage/InconsistentTypeCodeHelper.java org/omg/DynamicAny/NameDynAnyPairSeqHelper.java org/omg/DynamicAny/DynAnyHelper.java org/omg/DynamicAny/DynValue.java org/omg/DynamicAny/_DynUnionStub.java org/omg/DynamicAny/DynValueCommonOperations.java org/omg/DynamicAny/DynEnum.java org/omg/DynamicAny/DynValueBox.java org/omg/DynamicAny/DynAny.java org/omg/DynamicAny/_DynFixedStub.java org/omg/DynamicAny/DynSequenceOperations.java org/omg/DynamicAny/DynAnyFactoryHelper.java org/omg/DynamicAny/AnySeqHelper.java org/omg/DynamicAny/DynArray.java org/omg/DynamicAny/DynArrayOperations.java org/omg/DynamicAny/FieldNameHelper.java org/omg/DynamicAny/DynAnyPackage/TypeMismatchHelper.java org/omg/DynamicAny/DynAnyPackage/InvalidValue.java org/omg/DynamicAny/DynAnyPackage/TypeMismatch.java org/omg/DynamicAny/DynAnyPackage/InvalidValueHelper.java org/omg/DynamicAny/_DynStructStub.java org/omg/DynamicAny/DynStructHelper.java org/omg/DynamicAny/NameDynAnyPairHelper.java org/omg/DynamicAny/DynStruct.java org/omg/DynamicAny/DynUnionOperations.java org/omg/DynamicAny/_DynArrayStub.java org/omg/DynamicAny/NameValuePairSeqHelper.java org/omg/DynamicAny/NameValuePair.java org/omg/DynamicAny/DynAnyFactory.java org/omg/PortableInterceptor/ForwardRequestHelper.java org/omg/PortableInterceptor/CurrentHelper.java org/omg/PortableInterceptor/ORBInitializerOperations.java org/omg/PortableInterceptor/UNKNOWN.java org/omg/PortableInterceptor/IORInterceptor_3_0.java org/omg/PortableInterceptor/LOCATION_FORWARD.java org/omg/PortableInterceptor/ClientRequestInterceptorOperations.java org/omg/PortableInterceptor/ObjectReferenceTemplateSeqHelper.java org/omg/PortableInterceptor/IORInfo.java org/omg/PortableInterceptor/ObjectIdHelper.java org/omg/PortableInterceptor/ObjectReferenceTemplate.java org/omg/PortableInterceptor/InterceptorOperations.java org/omg/PortableInterceptor/PolicyFactory.java org/omg/PortableInterceptor/PolicyFactoryOperations.java org/omg/PortableInterceptor/ObjectReferenceTemplateSeqHolder.java org/omg/PortableInterceptor/IORInterceptor.java org/omg/PortableInterceptor/ServerRequestInfoOperations.java org/omg/PortableInterceptor/ServerRequestInterceptorOperations.java org/omg/PortableInterceptor/ObjectReferenceTemplateHolder.java org/omg/PortableInterceptor/ServerRequestInfo.java org/omg/PortableInterceptor/AdapterNameHelper.java org/omg/PortableInterceptor/INACTIVE.java org/omg/PortableInterceptor/ObjectReferenceFactoryHelper.java org/omg/PortableInterceptor/ORBInitInfoOperations.java org/omg/PortableInterceptor/Current.java org/omg/PortableInterceptor/USER_EXCEPTION.java org/omg/PortableInterceptor/HOLDING.java org/omg/PortableInterceptor/SUCCESSFUL.java org/omg/PortableInterceptor/ForwardRequest.java org/omg/PortableInterceptor/IORInterceptor_3_0Helper.java org/omg/PortableInterceptor/ObjectReferenceTemplateHelper.java org/omg/PortableInterceptor/ClientRequestInfo.java org/omg/PortableInterceptor/IORInfoOperations.java org/omg/PortableInterceptor/InvalidSlotHelper.java org/omg/PortableInterceptor/RequestInfoOperations.java org/omg/PortableInterceptor/IORInterceptor_3_0Operations.java org/omg/PortableInterceptor/ORBInitializer.java org/omg/PortableInterceptor/SYSTEM_EXCEPTION.java org/omg/PortableInterceptor/ObjectReferenceFactoryHolder.java org/omg/PortableInterceptor/Interceptor.java org/omg/PortableInterceptor/AdapterStateHelper.java org/omg/PortableInterceptor/AdapterManagerIdHelper.java org/omg/PortableInterceptor/ServerIdHelper.java org/omg/PortableInterceptor/NON_EXISTENT.java org/omg/PortableInterceptor/ACTIVE.java org/omg/PortableInterceptor/ORBIdHelper.java org/omg/PortableInterceptor/ORBInitInfo.java org/omg/PortableInterceptor/CurrentOperations.java org/omg/PortableInterceptor/ClientRequestInfoOperations.java org/omg/PortableInterceptor/IORInterceptor_3_0Holder.java org/omg/PortableInterceptor/DISCARDING.java org/omg/PortableInterceptor/InvalidSlot.java org/omg/PortableInterceptor/ObjectReferenceFactory.java org/omg/PortableInterceptor/RequestInfo.java org/omg/PortableInterceptor/TRANSPORT_RETRY.java org/omg/PortableInterceptor/ClientRequestInterceptor.java org/omg/PortableInterceptor/IORInterceptorOperations.java org/omg/PortableInterceptor/ORBInitInfoPackage/ObjectIdHelper.java org/omg/PortableInterceptor/ORBInitInfoPackage/DuplicateNameHelper.java org/omg/PortableInterceptor/ORBInitInfoPackage/InvalidNameHelper.java org/omg/PortableInterceptor/ORBInitInfoPackage/DuplicateName.java org/omg/PortableInterceptor/ORBInitInfoPackage/InvalidName.java org/omg/PortableInterceptor/ServerRequestInterceptor.java org/jcp/xml/dsig/internal/MacOutputStream.java org/jcp/xml/dsig/internal/SignerOutputStream.java org/jcp/xml/dsig/internal/DigesterOutputStream.java org/jcp/xml/dsig/internal/dom/DOMStructure.java org/jcp/xml/dsig/internal/dom/DOMReference.java org/jcp/xml/dsig/internal/dom/DOMKeyInfoFactory.java org/jcp/xml/dsig/internal/dom/ApacheTransform.java org/jcp/xml/dsig/internal/dom/DOMHMACSignatureMethod.java org/jcp/xml/dsig/internal/dom/Utils.java org/jcp/xml/dsig/internal/dom/DOMXMLSignatureFactory.java org/jcp/xml/dsig/internal/dom/DOMX509Data.java org/jcp/xml/dsig/internal/dom/ApacheData.java org/jcp/xml/dsig/internal/dom/DOMCanonicalizationMethod.java org/jcp/xml/dsig/internal/dom/DOMSignatureMethod.java org/jcp/xml/dsig/internal/dom/DOMXPathFilter2Transform.java org/jcp/xml/dsig/internal/dom/DOMSignatureProperties.java org/jcp/xml/dsig/internal/dom/DOMSignedInfo.java org/jcp/xml/dsig/internal/dom/DOMXPathTransform.java org/jcp/xml/dsig/internal/dom/DOMEnvelopedTransform.java org/jcp/xml/dsig/internal/dom/DOMTransform.java org/jcp/xml/dsig/internal/dom/DOMRetrievalMethod.java org/jcp/xml/dsig/internal/dom/DOMCanonicalXMLC14NMethod.java org/jcp/xml/dsig/internal/dom/DOMDigestMethod.java org/jcp/xml/dsig/internal/dom/DOMX509IssuerSerial.java org/jcp/xml/dsig/internal/dom/DOMPGPData.java org/jcp/xml/dsig/internal/dom/DOMURIDereferencer.java org/jcp/xml/dsig/internal/dom/DOMKeyValue.java org/jcp/xml/dsig/internal/dom/ApacheCanonicalizer.java org/jcp/xml/dsig/internal/dom/DOMXMLObject.java org/jcp/xml/dsig/internal/dom/DOMSubTreeData.java org/jcp/xml/dsig/internal/dom/ApacheOctetStreamData.java org/jcp/xml/dsig/internal/dom/DOMExcC14NMethod.java org/jcp/xml/dsig/internal/dom/DOMSignatureProperty.java org/jcp/xml/dsig/internal/dom/DOMKeyInfo.java org/jcp/xml/dsig/internal/dom/DOMManifest.java org/jcp/xml/dsig/internal/dom/DOMBase64Transform.java org/jcp/xml/dsig/internal/dom/ApacheNodeSetData.java org/jcp/xml/dsig/internal/dom/DOMXSLTTransform.java org/jcp/xml/dsig/internal/dom/DOMUtils.java org/jcp/xml/dsig/internal/dom/XMLDSigRI.java org/jcp/xml/dsig/internal/dom/DOMXMLSignature.java org/jcp/xml/dsig/internal/dom/DOMCryptoBinary.java org/jcp/xml/dsig/internal/dom/DOMKeyName.java sun/dc/DuctusRenderingEngine.java sun/applet/AppletPanel.java sun/applet/AppletViewerPanel.java sun/applet/AppletResourceLoader.java sun/applet/AppletMessageHandler.java sun/applet/AppletIOException.java sun/applet/AppletThreadGroup.java sun/applet/Main.java sun/applet/AppletProps.java sun/applet/AppletClassLoader.java sun/applet/AppletObjectInputStream.java sun/applet/AppletAudioClip.java sun/applet/AppletEvent.java sun/applet/AppletListener.java sun/applet/AppletViewer.java sun/applet/AppletSecurity.java sun/applet/AppletIllegalArgumentException.java sun/applet/AppletViewerFactory.java sun/applet/AppletImageRef.java sun/applet/resources/MsgAppletViewer.java sun/applet/resources/MsgAppletViewer_it.java sun/applet/resources/MsgAppletViewer_fr.java sun/applet/resources/MsgAppletViewer_ja.java sun/applet/resources/MsgAppletViewer_de.java sun/applet/resources/MsgAppletViewer_sv.java sun/applet/resources/MsgAppletViewer_es.java sun/applet/resources/MsgAppletViewer_zh_CN.java sun/applet/resources/MsgAppletViewer_zh_TW.java sun/applet/resources/MsgAppletViewer_ko.java sun/applet/AppletEventMulticaster.java sun/applet/AppletSecurityException.java sun/management/HotspotInternal.java sun/management/HotSpotDiagnostic.java sun/management/GcInfoBuilder.java sun/management/HotspotInternalMBean.java sun/management/ConnectorAddressLink.java sun/management/GarbageCollectorImpl.java sun/management/counter/Variability.java sun/management/counter/Units.java sun/management/counter/AbstractCounter.java sun/management/counter/LongCounter.java sun/management/counter/perf/PerfStringCounter.java sun/management/counter/perf/ByteArrayCounterSnapshot.java sun/management/counter/perf/LongArrayCounterSnapshot.java sun/management/counter/perf/PerfLongCounter.java sun/management/counter/perf/LongCounterSnapshot.java sun/management/counter/perf/PerfDataType.java sun/management/counter/perf/PerfInstrumentation.java sun/management/counter/perf/Prologue.java sun/management/counter/perf/StringCounterSnapshot.java sun/management/counter/perf/InstrumentationException.java sun/management/counter/perf/PerfByteArrayCounter.java sun/management/counter/perf/PerfDataEntry.java sun/management/counter/perf/PerfLongArrayCounter.java sun/management/counter/LongArrayCounter.java sun/management/counter/Counter.java sun/management/counter/ByteArrayCounter.java sun/management/counter/StringCounter.java sun/management/HotspotRuntimeMBean.java sun/management/CompilerThreadStat.java sun/management/MemoryManagerImpl.java sun/management/GcInfoCompositeData.java sun/management/MemoryNotifInfoCompositeData.java sun/management/VMManagement.java sun/management/StackTraceElementCompositeData.java sun/management/HotspotCompilationMBean.java sun/management/ClassLoadingImpl.java sun/management/HotspotThreadMBean.java sun/management/MemoryPoolImpl.java sun/management/MonitorInfoCompositeData.java sun/management/HotspotClassLoadingMBean.java sun/management/AgentConfigurationError.java sun/management/FileSystem.java sun/management/Agent.java sun/management/LockDataConverter.java sun/management/HotspotMemoryMBean.java sun/management/RuntimeImpl.java sun/management/MemoryImpl.java sun/management/HotspotCompilation.java sun/management/Sensor.java sun/management/HotspotMemory.java sun/management/MemoryUsageCompositeData.java sun/management/HotspotThread.java sun/management/HotspotClassLoading.java sun/management/VMOptionCompositeData.java sun/management/LazyCompositeData.java sun/management/OperatingSystemImpl.java sun/management/ManagementFactory.java sun/management/Flag.java sun/management/jmxremote/SingleEntryRegistry.java sun/management/jmxremote/SSLContextRMIServerSocketFactory.java sun/management/jmxremote/ConnectorBootstrap.java sun/management/jmxremote/LocalRMIServerSocketFactory.java sun/management/snmp/jvminstr/JvmMemPoolTableMetaImpl.java sun/management/snmp/jvminstr/JvmMemMgrPoolRelTableMetaImpl.java sun/management/snmp/jvminstr/JvmRTClassPathTableMetaImpl.java sun/management/snmp/jvminstr/JvmRTBootClassPathTableMetaImpl.java sun/management/snmp/jvminstr/JvmThreadingImpl.java sun/management/snmp/jvminstr/JVM_MANAGEMENT_MIB_IMPL.java sun/management/snmp/jvminstr/JvmMemManagerTableMetaImpl.java sun/management/snmp/jvminstr/JvmRuntimeMetaImpl.java sun/management/snmp/jvminstr/NotificationTargetImpl.java sun/management/snmp/jvminstr/JvmRTLibraryPathEntryImpl.java sun/management/snmp/jvminstr/JvmRTLibraryPathTableMetaImpl.java sun/management/snmp/jvminstr/JvmThreadInstanceTableMetaImpl.java sun/management/snmp/jvminstr/JvmMemManagerEntryImpl.java sun/management/snmp/jvminstr/JvmMemGCEntryImpl.java sun/management/snmp/jvminstr/NotificationTarget.java sun/management/snmp/jvminstr/JvmMemoryImpl.java sun/management/snmp/jvminstr/JvmThreadingMetaImpl.java sun/management/snmp/jvminstr/JvmRuntimeImpl.java sun/management/snmp/jvminstr/JvmThreadInstanceEntryImpl.java sun/management/snmp/jvminstr/JvmMemGCTableMetaImpl.java sun/management/snmp/jvminstr/JvmMemMgrPoolRelEntryImpl.java sun/management/snmp/jvminstr/JvmRTClassPathEntryImpl.java sun/management/snmp/jvminstr/JvmOSImpl.java sun/management/snmp/jvminstr/JvmRTInputArgsEntryImpl.java sun/management/snmp/jvminstr/JvmMemoryMetaImpl.java sun/management/snmp/jvminstr/JvmCompilationImpl.java sun/management/snmp/jvminstr/JvmMemPoolEntryImpl.java sun/management/snmp/jvminstr/JvmRTInputArgsTableMetaImpl.java sun/management/snmp/jvminstr/JvmClassLoadingImpl.java sun/management/snmp/jvminstr/JvmRTBootClassPathEntryImpl.java sun/management/snmp/jvmmib/JvmMemMgrPoolRelTableMeta.java sun/management/snmp/jvmmib/EnumJvmClassesVerboseLevel.java sun/management/snmp/jvmmib/JvmRTLibraryPathEntryMBean.java sun/management/snmp/jvmmib/JvmOSMeta.java sun/management/snmp/jvmmib/JvmRuntimeMeta.java sun/management/snmp/jvmmib/JvmMemManagerTableMeta.java sun/management/snmp/jvmmib/JvmMemGCEntryMBean.java sun/management/snmp/jvmmib/EnumJvmThreadCpuTimeMonitoring.java sun/management/snmp/jvmmib/JvmRTInputArgsEntryMBean.java sun/management/snmp/jvmmib/JvmClassLoadingMeta.java sun/management/snmp/jvmmib/JvmMemMgrPoolRelEntryMeta.java sun/management/snmp/jvmmib/JvmMemGCTableMeta.java sun/management/snmp/jvmmib/EnumJvmMemPoolThreshdSupport.java sun/management/snmp/jvmmib/JvmRTLibraryPathTableMeta.java sun/management/snmp/jvmmib/EnumJvmMemoryGCCall.java sun/management/snmp/jvmmib/JvmMemPoolTableMeta.java sun/management/snmp/jvmmib/JVM_MANAGEMENT_MIB.java sun/management/snmp/jvmmib/JvmClassLoadingMBean.java sun/management/snmp/jvmmib/JvmMemoryMBean.java sun/management/snmp/jvmmib/JvmCompilationMeta.java sun/management/snmp/jvmmib/JvmOSMBean.java sun/management/snmp/jvmmib/JvmMemMgrPoolRelEntryMBean.java sun/management/snmp/jvmmib/EnumJvmMemPoolState.java sun/management/snmp/jvmmib/JVM_MANAGEMENT_MIBOidTable.java sun/management/snmp/jvmmib/EnumJvmRTBootClassPathSupport.java sun/management/snmp/jvmmib/JvmRuntimeMBean.java sun/management/snmp/jvmmib/JvmThreadInstanceEntryMBean.java sun/management/snmp/jvmmib/JvmRTBootClassPathTableMeta.java sun/management/snmp/jvmmib/JvmRTBootClassPathEntryMBean.java sun/management/snmp/jvmmib/JvmThreadingMBean.java sun/management/snmp/jvmmib/EnumJvmThreadContentionMonitoring.java sun/management/snmp/jvmmib/JvmMemPoolEntryMeta.java sun/management/snmp/jvmmib/JvmMemPoolEntryMBean.java sun/management/snmp/jvmmib/JvmRTClassPathEntryMeta.java sun/management/snmp/jvmmib/JvmRTInputArgsEntryMeta.java sun/management/snmp/jvmmib/JvmMemoryMeta.java sun/management/snmp/jvmmib/JvmThreadInstanceTableMeta.java sun/management/snmp/jvmmib/EnumJvmMemManagerState.java sun/management/snmp/jvmmib/EnumJvmMemoryGCVerboseLevel.java sun/management/snmp/jvmmib/EnumJvmMemPoolCollectThreshdSupport.java sun/management/snmp/jvmmib/JvmMemGCEntryMeta.java sun/management/snmp/jvmmib/JvmRTClassPathTableMeta.java sun/management/snmp/jvmmib/EnumJvmJITCompilerTimeMonitoring.java sun/management/snmp/jvmmib/JvmThreadingMeta.java sun/management/snmp/jvmmib/JvmRTInputArgsTableMeta.java sun/management/snmp/jvmmib/JvmRTClassPathEntryMBean.java sun/management/snmp/jvmmib/JvmRTLibraryPathEntryMeta.java sun/management/snmp/jvmmib/EnumJvmMemPoolType.java sun/management/snmp/jvmmib/JvmMemManagerEntryMBean.java sun/management/snmp/jvmmib/JvmMemManagerEntryMeta.java sun/management/snmp/jvmmib/JvmThreadInstanceEntryMeta.java sun/management/snmp/jvmmib/JvmRTBootClassPathEntryMeta.java sun/management/snmp/jvmmib/JvmCompilationMBean.java sun/management/snmp/AdaptorBootstrap.java sun/management/snmp/util/SnmpTableCache.java sun/management/snmp/util/SnmpCachedData.java sun/management/snmp/util/MibLogger.java sun/management/snmp/util/JvmContextFactory.java sun/management/snmp/util/SnmpNamedListTableCache.java sun/management/snmp/util/SnmpTableHandler.java sun/management/snmp/util/SnmpListTableCache.java sun/management/snmp/util/SnmpLoadedClassData.java sun/management/Util.java sun/management/FileSystemImpl.java sun/management/NotificationEmitterSupport.java sun/management/CompilationImpl.java sun/management/MethodInfo.java sun/management/LockDataConverterMXBean.java sun/management/HotspotRuntime.java sun/management/MappedMXBeanType.java sun/management/resources/agent_zh_CN.java sun/management/resources/agent_ko.java sun/management/resources/agent_fr.java sun/management/resources/agent_ja.java sun/management/resources/agent_it.java sun/management/resources/agent_de.java sun/management/resources/agent_zh_HK.java sun/management/resources/agent_zh_TW.java sun/management/resources/agent_sv.java go-fuse-2.0.3/doc.go000066400000000000000000000010321364171671200141610ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // This is a repository containing Go bindings for writing FUSE file // systems. // // Go to https://godoc.org/github.com/hanwen/go-fuse/fs for the // in-depth documentation for this library. // // Older, deprecated APIs are available at // https://godoc.org/github.com/hanwen/go-fuse/fuse/pathfs and // https://godoc.org/github.com/hanwen/go-fuse/fuse/nodefs. package lib go-fuse-2.0.3/example/000077500000000000000000000000001364171671200145245ustar00rootroot00000000000000go-fuse-2.0.3/example/autounionfs/000077500000000000000000000000001364171671200170765ustar00rootroot00000000000000go-fuse-2.0.3/example/autounionfs/main.go000066400000000000000000000042431364171671200203540ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "flag" "fmt" "os" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/pathfs" "github.com/hanwen/go-fuse/v2/unionfs" ) func main() { debug := flag.Bool("debug", false, "debug on") hardlinks := flag.Bool("hardlinks", false, "support hardlinks") delcache_ttl := flag.Float64("deletion_cache_ttl", 5.0, "Deletion cache TTL in seconds.") branchcache_ttl := flag.Float64("branchcache_ttl", 5.0, "Branch cache TTL in seconds.") deldirname := flag.String( "deletion_dirname", "GOUNIONFS_DELETIONS", "Directory name to use for deletions.") hide_readonly_link := flag.Bool("hide_readonly_link", true, "Hides READONLY link from the top mountpoints. "+ "Enabled by default.") portableInodes := flag.Bool("portable-inodes", false, "Use sequential 32-bit inode numbers.") flag.Parse() if len(flag.Args()) < 2 { fmt.Println("Usage:\n main MOUNTPOINT BASEDIR") os.Exit(2) } ufsOptions := unionfs.UnionFsOptions{ DeletionCacheTTL: time.Duration(*delcache_ttl * float64(time.Second)), BranchCacheTTL: time.Duration(*branchcache_ttl * float64(time.Second)), DeletionDirName: *deldirname, } options := unionfs.AutoUnionFsOptions{ UnionFsOptions: ufsOptions, Options: nodefs.Options{ EntryTimeout: time.Second, AttrTimeout: time.Second, NegativeTimeout: time.Second, Owner: fuse.CurrentOwner(), Debug: *debug, }, UpdateOnMount: true, PathNodeFsOptions: pathfs.PathNodeFsOptions{ ClientInodes: *hardlinks, }, HideReadonly: *hide_readonly_link, } fsOpts := nodefs.Options{ PortableInodes: *portableInodes, Debug: *debug, } gofs := unionfs.NewAutoUnionFs(flag.Arg(1), options) pathfs := pathfs.NewPathNodeFs(gofs, &pathfs.PathNodeFsOptions{ Debug: *debug, }) state, _, err := nodefs.MountRoot(flag.Arg(0), pathfs.Root(), &fsOpts) if err != nil { fmt.Printf("Mount fail: %v\n", err) os.Exit(1) } state.Serve() time.Sleep(1 * time.Second) } go-fuse-2.0.3/example/benchmark-read-throughput/000077500000000000000000000000001364171671200215765ustar00rootroot00000000000000go-fuse-2.0.3/example/benchmark-read-throughput/readbench.go000066400000000000000000000023031364171671200240360ustar00rootroot00000000000000// Copyright 2017 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // readbench is a benchmark helper for measuring throughput on // single-file reads out of a FUSE filesystem. package main import ( "flag" "fmt" "log" "os" "time" ) func gulp(fn string, bs int) (int, error) { f, err := os.Open(fn) if err != nil { return 0, err } defer f.Close() var tot int buf := make([]byte, bs) for { n, _ := f.Read(buf[:]) tot += n if n < len(buf) { break } } return tot, nil } func main() { bs := flag.Int("bs", 32, "blocksize in kb") mbLimit := flag.Int("limit", 1000, "amount of data to read in mb") flag.Parse() if len(flag.Args()) < 1 { log.Fatal("readbench [-bs BLOCKSIZE -limit SIZE] file") } blocksize := *bs * 1024 totMB := 0.0 var totDT time.Duration for totMB < float64(*mbLimit) { t := time.Now() n, err := gulp(flag.Arg(0), blocksize) if err != nil { log.Fatal(err) } dt := time.Now().Sub(t) mb := float64(n) / (1 << 20) totMB += mb totDT += dt } fmt.Printf("block size %d kb: %.1f MB in %v: %.2f MBs/s\n", *bs, totMB, totDT, totMB/float64(totDT)*1e9) } go-fuse-2.0.3/example/benchmark.sh000066400000000000000000000025761364171671200170240ustar00rootroot00000000000000#!/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/* go-fuse-2.0.3/example/hello/000077500000000000000000000000001364171671200156275ustar00rootroot00000000000000go-fuse-2.0.3/example/hello/main.go000066400000000000000000000023621364171671200171050ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // This program is the analogon of libfuse's hello.c, a a program that // exposes a single file "file.txt" in the root directory. package main import ( "context" "flag" "log" "syscall" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" ) type HelloRoot struct { fs.Inode } func (r *HelloRoot) OnAdd(ctx context.Context) { ch := r.NewPersistentInode( ctx, &fs.MemRegularFile{ Data: []byte("file.txt"), Attr: fuse.Attr{ Mode: 0644, }, }, fs.StableAttr{Ino: 2}) r.AddChild("file.txt", ch, false) } func (r *HelloRoot) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno { out.Mode = 0755 return 0 } var _ = (fs.NodeGetattrer)((*HelloRoot)(nil)) var _ = (fs.NodeOnAdder)((*HelloRoot)(nil)) func main() { debug := flag.Bool("debug", false, "print debug data") flag.Parse() if len(flag.Args()) < 1 { log.Fatal("Usage:\n hello MOUNTPOINT") } opts := &fs.Options{} opts.Debug = *debug server, err := fs.Mount(flag.Arg(0), &HelloRoot{}, opts) if err != nil { log.Fatalf("Mount fail: %v\n", err) } server.Wait() } go-fuse-2.0.3/example/loopback/000077500000000000000000000000001364171671200163165ustar00rootroot00000000000000go-fuse-2.0.3/example/loopback/main.go000066400000000000000000000062251364171671200175760ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // This is main program driver for the loopback filesystem from // github.com/hanwen/go-fuse/fs/, a filesystem that shunts operations // to an underlying file system. package main import ( "flag" "fmt" "log" "os" "os/signal" "path" "runtime/pprof" "syscall" "time" "github.com/hanwen/go-fuse/v2/fs" ) 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.") quiet := flag.Bool("q", false, "quiet") 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 != "" { if !*quiet { 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 != "" { if !*quiet { 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 != "" { if !*quiet { fmt.Printf("Note: You must unmount gracefully, otherwise the profile file(s) will stay empty!\n") } } orig := flag.Arg(1) loopbackRoot, err := fs.NewLoopbackRoot(orig) if err != nil { log.Fatalf("NewLoopbackRoot(%s): %v\n", orig, err) } sec := time.Second opts := &fs.Options{ // These options are to be compatible with libfuse defaults, // making benchmarking easier. AttrTimeout: &sec, EntryTimeout: &sec, } opts.Debug = *debug opts.AllowOther = *other if opts.AllowOther { // Make the kernel check file permissions for us opts.MountOptions.Options = append(opts.MountOptions.Options, "default_permissions") } // First column in "df -T": original dir opts.MountOptions.Options = append(opts.MountOptions.Options, "fsname="+orig) // Second column in "df -T" will be shown as "fuse." + Name opts.MountOptions.Name = "loopback" // Leave file permissions on "000" files as-is opts.NullPermissions = true // Enable diagnostics logging if !*quiet { opts.Logger = log.New(os.Stderr, "", 0) } server, err := fs.Mount(flag.Arg(0), loopbackRoot, opts) if err != nil { log.Fatalf("Mount fail: %v\n", err) } if !*quiet { fmt.Println("Mounted!") } server.Wait() } go-fuse-2.0.3/example/memfs/000077500000000000000000000000001364171671200156335ustar00rootroot00000000000000go-fuse-2.0.3/example/memfs/main.go000066400000000000000000000016751364171671200171170ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Mounts MemNodeFs for testing purposes. package main import ( "flag" "fmt" "os" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/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() } go-fuse-2.0.3/example/multizip/000077500000000000000000000000001364171671200164015ustar00rootroot00000000000000go-fuse-2.0.3/example/multizip/main.go000066400000000000000000000020421364171671200176520ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // This is main program driver for MultiZipFs from // github.com/hanwen/go-fuse/zipfs, a filesystem for mounting multiple // read-only archives. It can be used by symlinking to an archive file // from the config/ subdirectory. package main import ( "flag" "fmt" "os" "path/filepath" "time" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/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) } root := &zipfs.MultiZipFs{} sec := time.Second opts := fs.Options{ EntryTimeout: &sec, AttrTimeout: &sec, } opts.Debug = *debug server, err := fs.Mount(flag.Arg(0), root, &opts) if err != nil { fmt.Printf("Mount fail: %v\n", err) os.Exit(1) } server.Serve() } go-fuse-2.0.3/example/statfs/000077500000000000000000000000001364171671200160305ustar00rootroot00000000000000go-fuse-2.0.3/example/statfs/main.go000066400000000000000000000040471364171671200173100ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // statfs is a main driver for the file system from // github.com/hanwen/go-fuse/benchmark, intended for benchmarking FUSE // libraries. package main import ( "flag" "fmt" "io" "log" "os" "os/exec" "runtime" "runtime/pprof" "strings" "syscall" "time" "github.com/hanwen/go-fuse/v2/benchmark" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" ) 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.Duration("ttl", time.Second, "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) } } root := &benchmark.StatFS{} lines := benchmark.ReadLines(flag.Arg(1)) for _, l := range lines { root.AddFile(strings.TrimSpace(l), fuse.Attr{Mode: syscall.S_IFREG}) } opts := &fs.Options{ AttrTimeout: ttl, EntryTimeout: ttl, } opts.Debug = *debug server, err := fs.Mount(flag.Arg(0), root, opts) if err != nil { log.Fatalf("Mount fail: %v\n", err) } 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() } server.Wait() if memProfFile != nil { pprof.WriteHeapProfile(memProfFile) } } go-fuse-2.0.3/example/unionfs/000077500000000000000000000000001364171671200162055ustar00rootroot00000000000000go-fuse-2.0.3/example/unionfs/main.go000066400000000000000000000036661364171671200174730ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package main import ( "flag" "fmt" "log" "os" "time" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/pathfs" "github.com/hanwen/go-fuse/v2/unionfs" ) func main() { debug := flag.Bool("debug", false, "debug on") portable := flag.Bool("portable", false, "use 32 bit inodes") entry_ttl := flag.Float64("entry_ttl", 1.0, "fuse entry cache TTL.") negative_ttl := flag.Float64("negative_ttl", 1.0, "fuse negative entry cache TTL.") delcache_ttl := flag.Float64("deletion_cache_ttl", 5.0, "Deletion cache TTL in seconds.") branchcache_ttl := flag.Float64("branchcache_ttl", 5.0, "Branch cache TTL in seconds.") deldirname := flag.String( "deletion_dirname", "GOUNIONFS_DELETIONS", "Directory name to use for deletions.") flag.Parse() if len(flag.Args()) < 2 { fmt.Println("Usage:\n unionfs MOUNTPOINT RW-DIRECTORY RO-DIRECTORY ...") os.Exit(2) } ufsOptions := unionfs.UnionFsOptions{ DeletionCacheTTL: time.Duration(*delcache_ttl * float64(time.Second)), BranchCacheTTL: time.Duration(*branchcache_ttl * float64(time.Second)), DeletionDirName: *deldirname, } ufs, err := unionfs.NewUnionFsFromRoots(flag.Args()[1:], &ufsOptions, true) if err != nil { log.Fatal("Cannot create UnionFs", err) os.Exit(1) } nodeFs := pathfs.NewPathNodeFs(ufs, &pathfs.PathNodeFsOptions{ClientInodes: true}) mOpts := nodefs.Options{ EntryTimeout: time.Duration(*entry_ttl * float64(time.Second)), AttrTimeout: time.Duration(*entry_ttl * float64(time.Second)), NegativeTimeout: time.Duration(*negative_ttl * float64(time.Second)), PortableInodes: *portable, Debug: *debug, } mountState, _, err := nodefs.MountRoot(flag.Arg(0), nodeFs.Root(), &mOpts) if err != nil { log.Fatal("Mount fail:", err) } mountState.Serve() } go-fuse-2.0.3/example/zipfs/000077500000000000000000000000001364171671200156575ustar00rootroot00000000000000go-fuse-2.0.3/example/zipfs/main.go000066400000000000000000000037211364171671200171350ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // This is main program driver for github.com/hanwen/go-fuse/zipfs, a // filesystem for mounting read-only archives. package main import ( "flag" "fmt" "io" "log" "os" "os/exec" "runtime" "runtime/pprof" "strings" "time" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/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.Duration("ttl", time.Second, "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 := &fs.Options{ AttrTimeout: ttl, EntryTimeout: ttl, } opts.Debug = *debug server, err := fs.Mount(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() } server.Wait() if memProfFile != nil { pprof.WriteHeapProfile(memProfFile) } } go-fuse-2.0.3/fs/000077500000000000000000000000001364171671200135015ustar00rootroot00000000000000go-fuse-2.0.3/fs/README.md000066400000000000000000000042141364171671200147610ustar00rootroot00000000000000 Objective ========= A high-performance FUSE API that minimizes pitfalls with writing correct filesystems. Decisions ========= * Nodes contain references to their children. This is useful because most filesystems will need to construct tree-like structures. * Nodes contain references to their parents. As a result, we can derive the path for each Inode, and there is no need for a separate PathFS. * Nodes can be "persistent", meaning their lifetime is not under control of the kernel. This is useful for constructing FS trees in advance, rather than driven by LOOKUP. * The NodeID for FS tree node must be defined on creation and are immutable. By contrast, reusing NodeIds (eg. rsc/bazil FUSE, as well as old go-fuse/fuse/nodefs) needs extra synchronization to avoid races with notify and FORGET, and makes handling the inode Generation more complicated. * The mode of an Inode is defined on creation. Files cannot change type during their lifetime. This also prevents the common error of forgetting to return the filetype in Lookup/GetAttr. * The NodeID (used for communicating with kernel) is equal to Attr.Ino (value shown in Stat and Lstat return values.). * No global treelock, to ensure scalability. * Support for hard links. libfuse doesn't support this in the high-level API. Extra care for race conditions is needed when looking up the same file through different paths. * do not issue Notify{Entry,Delete} as part of AddChild/RmChild/MvChild: because NodeIDs are unique and immutable, there is no confusion about which nodes are invalidated, and the notification doesn't have to happen under lock. * Directory reading uses the DirStream. Semantics for rewinding directory reads, and adding files after opening (but before reading) are handled automatically. No support for directory seeks. * Method names are based on syscall names. Where there is no syscall (eg. "open directory"), we bias towards writing everything together (Opendir) To do/To decide ========= * Symlink []byte vs string. go-fuse-2.0.3/fs/api.go000066400000000000000000000545411364171671200146120ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package fs provides infrastructure to build tree-organized filesystems. // // Structure of a file system implementation // // To create a file system, you should first define types for the // nodes of the file system tree. // // struct myNode { // fs.Inode // } // // // Node types must be InodeEmbedders // var _ = (fs.InodeEmbedder)((*myNode)(nil)) // // // Node types should implement some file system operations, eg. Lookup // var _ = (fs.NodeLookuper)((*myNode)(nil)) // // func (n *myNode) Lookup(ctx context.Context, name string, ... ) (*Inode, syscall.Errno) { // ops := myNode{} // return n.NewInode(ctx, &ops, fs.StableAttr{Mode: syscall.S_IFDIR}), 0 // } // // The method names are inspired on the system call names, so we have // Listxattr rather than ListXAttr. // // the file system is mounted by calling mount on the root of the tree, // // server, err := fs.Mount("/tmp/mnt", &myNode{}, &fs.Options{}) // .. // // start serving the file system // server.Wait() // // Error handling // // All error reporting must use the syscall.Errno type. This is an // integer with predefined error codes, where the value 0 (`OK`) // should be used to indicate success. // // File system concepts // // The FUSE API is very similar to Linux' internal VFS API for // defining file systems in the kernel. It is therefore useful to // understand some terminology. // // File content: the raw bytes that we store inside regular files. // // Path: a /-separated string path that describes location of a node // in the file system tree. For example // // dir1/file // // describes path root → dir1 → file. // // There can be several paths leading from tree root to a particular node, // known as hard-linking, for example // // root // / \ // dir1 dir2 // \ / // file // // Inode: ("index node") points to the file content, and stores // metadata (size, timestamps) about a file or directory. Each // inode has a type (directory, symlink, regular file, etc.) and // an identity (a 64-bit number, unique to the file // system). Directories can have children. // // The inode in the kernel is represented in Go-FUSE as the Inode // type. // // While common OS APIs are phrased in terms of paths (strings), the // precise semantics of a file system are better described in terms of // Inodes. This allows us to specify what happens in corner cases, // such as writing data to deleted files. // // File descriptor: a handle returned to opening a file. File // descriptors always refer to a single inode. // // Dirent: a dirent maps (parent inode number, name string) tuple to // child inode, thus representing a parent/child relation (or the // absense thereof). Dirents do not have an equivalent type inside // Go-FUSE, but the result of Lookup operation essentially is a // dirent, which the kernel puts in a cache. // // // Kernel caching // // The kernel caches several pieces of information from the FUSE process: // // 1. File contents: enabled with the fuse.FOPEN_KEEP_CACHE return flag // in Open, manipulated with ReadCache and WriteCache, and invalidated // with Inode.NotifyContent // // 2. File Attributes (size, mtime, etc.): controlled with the // attribute timeout fields in fuse.AttrOut and fuse.EntryOut, which // get be populated from Getattr and Lookup // // 3. Directory entries (parent/child relations in the FS tree): // controlled with the timeout fields in fuse.EntryOut, and // invalidated with Inode.NotifyEntry and Inode.NotifyDelete. // // Without Directory Entry timeouts, every operation on file "a/b/c" // must first do lookups for "a", "a/b" and "a/b/c", which is // expensive because of context switches between the kernel and the // FUSE process. // // Unsuccessful entry lookups can also be cached by setting an entry // timeout when Lookup returns ENOENT. // // The libfuse C library specifies 1 second timeouts for both // attribute and directory entries, but no timeout for negative // entries. by default. This can be achieve in go-fuse by setting // options on mount, eg. // // sec := time.Second // opts := fs.Options{ // EntryTimeout: &sec, // AttrTimeout: &sec, // } // // Locking // // Locks for networked filesystems are supported through the suite of // Getlk, Setlk and Setlkw methods. They alllow locks on regions of // regular files. // // Parallelism // // The VFS layer in the kernel is optimized to be highly parallel, and // this parallelism also affects FUSE file systems: many FUSE // operations can run in parallel, and this invites race // conditions. It is strongly recommended to test your FUSE file // system issuing file operations in parallel, and using the race // detector to weed out data races. // // Dynamically discovered file systems // // File system data usually cannot fit all in RAM, so the kernel must // discover the file system dynamically: as you are entering and list // directory contents, the kernel asks the FUSE server about the files // and directories you are busy reading/writing, and forgets parts of // your file system when it is low on memory. // // The two important operations for dynamic file systems are: // 1. Lookup, part of the NodeLookuper interface for discovering // individual children of directories, and 2. Readdir, part of the // NodeReaddirer interface for listing the contents of a directory. // // Static in-memory file systems // // For small, read-only file systems, getting the locking mechanics of // Lookup correct is tedious, so Go-FUSE provides a feature to // simplify building such file systems. // // Instead of discovering the FS tree on the fly, you can construct // the entire tree from an OnAdd method. Then, that in-memory tree // structure becomes the source of truth. This means you Go-FUSE must // remember Inodes even if the kernel is no longer interested in // them. This is done by instantiating "persistent" inodes from the // OnAdd method of the root node. See the ZipFS example for a // runnable example of how to do this. package fs import ( "context" "log" "syscall" "time" "github.com/hanwen/go-fuse/v2/fuse" ) // InodeEmbedder is an interface for structs that embed Inode. // // InodeEmbedder objects usually should implement some of the NodeXxxx // interfaces, to provide user-defined file system behaviors. // // In general, if an InodeEmbedder does not implement specific // filesystem methods, the filesystem will react as if it is a // read-only filesystem with a predefined tree structure. type InodeEmbedder interface { // populateInode and inode are used internally to link Inode // to a Node. // // See Inode() for the public API to retrieve an inode from Node. embed() *Inode // EmbeddedInode returns a pointer to the embedded inode. EmbeddedInode() *Inode } // Statfs implements statistics for the filesystem that holds this // Inode. If not defined, the `out` argument will zeroed with an OK // result. This is because OSX filesystems must Statfs, or the mount // will not work. type NodeStatfser interface { Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno } // Access should return if the caller can access the file with the // given mode. This is used for two purposes: to determine if a user // may enter a directory, and to answer to implement the access system // call. In the latter case, the context has data about the real // UID. For example, a root-SUID binary called by user susan gets the // UID and GID for susan here. // // If not defined, a default implementation will check traditional // unix permissions of the Getattr result agains the caller. If so, it // is necessary to either return permissions from GetAttr/Lookup or // set Options.DefaultPermissions in order to allow chdir into the // FUSE mount. type NodeAccesser interface { Access(ctx context.Context, mask uint32) syscall.Errno } // GetAttr reads attributes for an Inode. The library will ensure that // Mode and Ino are set correctly. For files that are not opened with // FOPEN_DIRECTIO, Size should be set so it can be read correctly. If // returning zeroed permissions, the default behavior is to change the // mode of 0755 (directory) or 0644 (files). This can be switched off // with the Options.NullPermissions setting. If blksize is unset, 4096 // is assumed, and the 'blocks' field is set accordingly. type NodeGetattrer interface { Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno } // SetAttr sets attributes for an Inode. type NodeSetattrer interface { Setattr(ctx context.Context, f FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno } // OnAdd is called when this InodeEmbedder is initialized. type NodeOnAdder interface { OnAdd(ctx context.Context) } // Getxattr should read data for the given attribute into // `dest` and return the number of bytes. If `dest` is too // small, it should return ERANGE and the size of the attribute. // If not defined, Getxattr will return ENOATTR. type NodeGetxattrer interface { Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) } // Setxattr should store data for the given attribute. See // setxattr(2) for information about flags. // If not defined, Setxattr will return ENOATTR. type NodeSetxattrer interface { Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno } // Removexattr should delete the given attribute. // If not defined, Removexattr will return ENOATTR. type NodeRemovexattrer interface { Removexattr(ctx context.Context, attr string) syscall.Errno } // Listxattr should read all attributes (null terminated) into // `dest`. If the `dest` buffer is too small, it should return ERANGE // and the correct size. If not defined, return an empty list and // success. type NodeListxattrer interface { Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) } // Readlink reads the content of a symlink. type NodeReadlinker interface { Readlink(ctx context.Context) ([]byte, syscall.Errno) } // Open opens an Inode (of regular file type) for reading. It // is optional but recommended to return a FileHandle. type NodeOpener interface { Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) } // Reads data from a file. The data should be returned as // ReadResult, which may be constructed from the incoming // `dest` buffer. If the file was opened without FileHandle, // the FileHandle argument here is nil. The default // implementation forwards to the FileHandle. type NodeReader interface { Read(ctx context.Context, f FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) } // Writes the data into the file handle at given offset. After // returning, the data will be reused and may not referenced. // The default implementation forwards to the FileHandle. type NodeWriter interface { Write(ctx context.Context, f FileHandle, data []byte, off int64) (written uint32, errno syscall.Errno) } // Fsync is a signal to ensure writes to the Inode are flushed // to stable storage. type NodeFsyncer interface { Fsync(ctx context.Context, f FileHandle, flags uint32) syscall.Errno } // Flush is called for the close(2) call on a file descriptor. In case // of a descriptor that was duplicated using dup(2), it may be called // more than once for the same FileHandle. The default implementation // forwards to the FileHandle, or if the handle does not support // FileFlusher, returns OK. type NodeFlusher interface { Flush(ctx context.Context, f FileHandle) syscall.Errno } // This is called to before a FileHandle is forgotten. The // kernel ignores the return value of this method, // so any cleanup that requires specific synchronization or // could fail with I/O errors should happen in Flush instead. // The default implementation forwards to the FileHandle. type NodeReleaser interface { Release(ctx context.Context, f FileHandle) syscall.Errno } // Allocate preallocates space for future writes, so they will // never encounter ESPACE. type NodeAllocater interface { Allocate(ctx context.Context, f FileHandle, off uint64, size uint64, mode uint32) syscall.Errno } // CopyFileRange copies data between sections of two files, // without the data having to pass through the calling process. type NodeCopyFileRanger interface { CopyFileRange(ctx context.Context, fhIn FileHandle, offIn uint64, out *Inode, fhOut FileHandle, offOut uint64, len uint64, flags uint64) (uint32, syscall.Errno) } // Lseek is used to implement holes: it should return the // first offset beyond `off` where there is data (SEEK_DATA) // or where there is a hole (SEEK_HOLE). type NodeLseeker interface { Lseek(ctx context.Context, f FileHandle, Off uint64, whence uint32) (uint64, syscall.Errno) } // Getlk returns locks that would conflict with the given input // lock. If no locks conflict, the output has type L_UNLCK. See // fcntl(2) for more information. // If not defined, returns ENOTSUP type NodeGetlker interface { Getlk(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) syscall.Errno } // Setlk obtains a lock on a file, or fail if the lock could not // obtained. See fcntl(2) for more information. If not defined, // returns ENOTSUP type NodeSetlker interface { Setlk(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno } // Setlkw obtains a lock on a file, waiting if necessary. See fcntl(2) // for more information. If not defined, returns ENOTSUP type NodeSetlkwer interface { Setlkw(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno } // DirStream lists directory entries. type DirStream interface { // HasNext indicates if there are further entries. HasNext // might be called on already closed streams. HasNext() bool // Next retrieves the next entry. It is only called if HasNext // has previously returned true. The Errno return may be used to // indicate I/O errors Next() (fuse.DirEntry, syscall.Errno) // Close releases resources related to this directory // stream. Close() } // Lookup should find a direct child of a directory by the child's name. If // the entry does not exist, it should return ENOENT and optionally // set a NegativeTimeout in `out`. If it does exist, it should return // attribute data in `out` and return the Inode for the child. A new // inode can be created using `Inode.NewInode`. The new Inode will be // added to the FS tree automatically if the return status is OK. // // If a directory does not implement NodeLookuper, the library looks // for an existing child with the given name. // // The input to a Lookup is {parent directory, name string}. // // Lookup, if successful, must return an *Inode. Once the Inode is // returned to the kernel, the kernel can issue further operations, // such as Open or Getxattr on that node. // // A successful Lookup also returns an EntryOut. Among others, this // contains file attributes (mode, size, mtime, etc.). // // FUSE supports other operations that modify the namespace. For // example, the Symlink, Create, Mknod, Link methods all create new // children in directories. Hence, they also return *Inode and must // populate their fuse.EntryOut arguments. // type NodeLookuper interface { Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) } // OpenDir opens a directory Inode for reading its // contents. The actual reading is driven from ReadDir, so // this method is just for performing sanity/permission // checks. The default is to return success. type NodeOpendirer interface { Opendir(ctx context.Context) syscall.Errno } // ReadDir opens a stream of directory entries. // // Readdir essentiallly returns a list of strings, and it is allowed // for Readdir to return different results from Lookup. For example, // you can return nothing for Readdir ("ls my-fuse-mount" is empty), // while still implementing Lookup ("ls my-fuse-mount/a-specific-file" // shows a single file). // // If a directory does not implement NodeReaddirer, a list of // currently known children from the tree is returned. This means that // static in-memory file systems need not implement NodeReaddirer. type NodeReaddirer interface { Readdir(ctx context.Context) (DirStream, syscall.Errno) } // Mkdir is similar to Lookup, but must create a directory entry and Inode. // Default is to return EROFS. type NodeMkdirer interface { Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) } // Mknod is similar to Lookup, but must create a device entry and Inode. // Default is to return EROFS. type NodeMknoder interface { Mknod(ctx context.Context, name string, mode uint32, dev uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) } // Link is similar to Lookup, but must create a new link to an existing Inode. // Default is to return EROFS. type NodeLinker interface { Link(ctx context.Context, target InodeEmbedder, name string, out *fuse.EntryOut) (node *Inode, errno syscall.Errno) } // Symlink is similar to Lookup, but must create a new symbolic link. // Default is to return EROFS. type NodeSymlinker interface { Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (node *Inode, errno syscall.Errno) } // Create is similar to Lookup, but should create a new // child. It typically also returns a FileHandle as a // reference for future reads/writes. // Default is to return EROFS. type NodeCreater interface { Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (node *Inode, fh FileHandle, fuseFlags uint32, errno syscall.Errno) } // Unlink should remove a child from this directory. If the // return status is OK, the Inode is removed as child in the // FS tree automatically. Default is to return EROFS. type NodeUnlinker interface { Unlink(ctx context.Context, name string) syscall.Errno } // Rmdir is like Unlink but for directories. // Default is to return EROFS. type NodeRmdirer interface { Rmdir(ctx context.Context, name string) syscall.Errno } // Rename should move a child from one directory to a different // one. The change is effected in the FS tree if the return status is // OK. Default is to return EROFS. type NodeRenamer interface { Rename(ctx context.Context, name string, newParent InodeEmbedder, newName string, flags uint32) syscall.Errno } // FileHandle is a resource identifier for opened files. Usually, a // FileHandle should implement some of the FileXxxx interfaces. // // All of the FileXxxx operations can also be implemented at the // InodeEmbedder level, for example, one can implement NodeReader // instead of FileReader. // // FileHandles are useful in two cases: First, if the underlying // storage systems needs a handle for reading/writing. This is the // case with Unix system calls, which need a file descriptor (See also // the function `NewLoopbackFile`). Second, it is useful for // implementing files whose contents are not tied to an inode. For // example, a file like `/proc/interrupts` has no fixed content, but // changes on each open call. This means that each file handle must // have its own view of the content; this view can be tied to a // FileHandle. Files that have such dynamic content should return the // FOPEN_DIRECT_IO flag from their `Open` method. See directio_test.go // for an example. type FileHandle interface { } // See NodeReleaser. type FileReleaser interface { Release(ctx context.Context) syscall.Errno } // See NodeGetattrer. type FileGetattrer interface { Getattr(ctx context.Context, out *fuse.AttrOut) syscall.Errno } // See NodeReader. type FileReader interface { Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) } // See NodeWriter. type FileWriter interface { Write(ctx context.Context, data []byte, off int64) (written uint32, errno syscall.Errno) } // See NodeGetlker. type FileGetlker interface { Getlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) syscall.Errno } // See NodeSetlker. type FileSetlker interface { Setlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno } // See NodeSetlkwer. type FileSetlkwer interface { Setlkw(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno } // See NodeLseeker. type FileLseeker interface { Lseek(ctx context.Context, off uint64, whence uint32) (uint64, syscall.Errno) } // See NodeFlusher. type FileFlusher interface { Flush(ctx context.Context) syscall.Errno } // See NodeFsync. type FileFsyncer interface { Fsync(ctx context.Context, flags uint32) syscall.Errno } // See NodeFsync. type FileSetattrer interface { Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno } // See NodeAllocater. type FileAllocater interface { Allocate(ctx context.Context, off uint64, size uint64, mode uint32) syscall.Errno } // Options sets options for the entire filesystem type Options struct { // MountOptions contain the options for mounting the fuse server fuse.MountOptions // If set to nonnil, this defines the overall entry timeout // for the file system. See fuse.EntryOut for more information. EntryTimeout *time.Duration // If set to nonnil, this defines the overall attribute // timeout for the file system. See fuse.EntryOut for more // information. AttrTimeout *time.Duration // If set to nonnil, this defines the overall entry timeout // for failed lookups (fuse.ENOENT). See fuse.EntryOut for // more information. NegativeTimeout *time.Duration // Automatic inode numbers are handed out sequentially // starting from this number. If unset, use 2^63. FirstAutomaticIno uint64 // OnAdd is an alternative way to specify the OnAdd // functionality of the root node. OnAdd func(ctx context.Context) // NullPermissions if set, leaves null file permissions // alone. Otherwise, they are set to 755 (dirs) or 644 (other // files.), which is necessary for doing a chdir into the FUSE // directories. NullPermissions bool // If nonzero, replace default (zero) UID with the given UID UID uint32 // If nonzero, replace default (zero) GID with the given GID GID uint32 // ServerCallbacks can be provided to stub out notification // functions for testing a filesystem without mounting it. ServerCallbacks ServerCallbacks // Logger is a sink for diagnostic messages. Diagnostic // messages are printed under conditions where we cannot // return error, but want to signal something seems off // anyway. If unset, no messages are printed. Logger *log.Logger } go-fuse-2.0.3/fs/bridge.go000066400000000000000000000672141364171671200152760ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "context" "log" "math/rand" "sync" "syscall" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/internal" ) func errnoToStatus(errno syscall.Errno) fuse.Status { return fuse.Status(errno) } type fileEntry struct { file FileHandle // index into Inode.openFiles nodeIndex int // Protects directory fields. Must be acquired before bridge.mu mu sync.Mutex // Directory dirStream DirStream hasOverflow bool overflow fuse.DirEntry wg sync.WaitGroup } // ServerCallbacks are calls into the kernel to manipulate the inode, // entry and page cache. They are stubbed so filesystems can be // unittested without mounting them. type ServerCallbacks interface { DeleteNotify(parent uint64, child uint64, name string) fuse.Status EntryNotify(parent uint64, name string) fuse.Status InodeNotify(node uint64, off int64, length int64) fuse.Status InodeRetrieveCache(node uint64, offset int64, dest []byte) (n int, st fuse.Status) InodeNotifyStoreCache(node uint64, offset int64, data []byte) fuse.Status } type rawBridge struct { options Options root *Inode server ServerCallbacks // mu protects the following data. Locks for inodes must be // taken before rawBridge.mu mu sync.Mutex nodes map[uint64]*Inode automaticIno uint64 files []*fileEntry freeFiles []uint32 } // newInode creates creates new inode pointing to ops. func (b *rawBridge) newInodeUnlocked(ops InodeEmbedder, id StableAttr, persistent bool) *Inode { b.mu.Lock() defer b.mu.Unlock() if id.Reserved() { log.Panicf("using reserved ID %d for inode number", id.Ino) } // This ops already was populated. Just return it. if ops.embed().bridge != nil { return ops.embed() } if id.Ino == 0 { for { id.Ino = b.automaticIno b.automaticIno++ _, ok := b.nodes[id.Ino] if !ok { break } } } // Only the file type bits matter id.Mode = id.Mode & syscall.S_IFMT if id.Mode == 0 { id.Mode = fuse.S_IFREG } // the same node can be looked up through 2 paths in parallel, eg. // // root // / \ // dir1 dir2 // \ / // file // // dir1.Lookup("file") and dir2.Lookup("file") are executed // simultaneously. The matching StableAttrs ensure that we return the // same node. var t time.Duration t0 := time.Now() for i := 1; true; i++ { old := b.nodes[id.Ino] if old == nil { break } if old.stableAttr == id { return old } b.mu.Unlock() t = expSleep(t) if i%5000 == 0 { b.logf("blocked for %.0f seconds waiting for FORGET on i%d", time.Since(t0).Seconds(), id.Ino) } b.mu.Lock() } b.nodes[id.Ino] = ops.embed() initInode(ops.embed(), ops, id, b, persistent) return ops.embed() } func (b *rawBridge) logf(format string, args ...interface{}) { if b.options.Logger != nil { b.options.Logger.Printf(format, args...) } } // expSleep sleeps for time `t` and returns an exponentially increasing value // for the next sleep time, capped at 1 ms. func expSleep(t time.Duration) time.Duration { if t == 0 { return time.Microsecond } time.Sleep(t) // Next sleep is between t and 2*t t += time.Duration(rand.Int63n(int64(t))) if t >= time.Millisecond { return time.Millisecond } return t } func (b *rawBridge) newInode(ctx context.Context, ops InodeEmbedder, id StableAttr, persistent bool) *Inode { ch := b.newInodeUnlocked(ops, id, persistent) if ch != ops.embed() { return ch } if oa, ok := ops.(NodeOnAdder); ok { oa.OnAdd(ctx) } return ch } // addNewChild inserts the child into the tree. Returns file handle if file != nil. func (b *rawBridge) addNewChild(parent *Inode, name string, child *Inode, file FileHandle, fileFlags uint32, out *fuse.EntryOut) uint32 { if name == "." || name == ".." { log.Panicf("BUG: tried to add virtual entry %q to the actual tree", name) } lockNodes(parent, child) parent.setEntry(name, child) b.mu.Lock() // Due to concurrent FORGETs, lookupCount may have dropped to zero. // This means it MAY have been deleted from nodes[] already. Add it back. if child.lookupCount == 0 { b.nodes[child.stableAttr.Ino] = child } child.lookupCount++ child.changeCounter++ var fh uint32 if file != nil { fh = b.registerFile(child, file, fileFlags) } out.NodeId = child.stableAttr.Ino out.Generation = child.stableAttr.Gen out.Attr.Ino = child.stableAttr.Ino b.mu.Unlock() unlockNodes(parent, child) return fh } func (b *rawBridge) setEntryOutTimeout(out *fuse.EntryOut) { b.setAttr(&out.Attr) if b.options.AttrTimeout != nil && out.AttrTimeout() == 0 { out.SetAttrTimeout(*b.options.AttrTimeout) } if b.options.EntryTimeout != nil && out.EntryTimeout() == 0 { out.SetEntryTimeout(*b.options.EntryTimeout) } } func (b *rawBridge) setAttr(out *fuse.Attr) { if !b.options.NullPermissions && out.Mode&07777 == 0 { out.Mode |= 0644 if out.Mode&syscall.S_IFDIR != 0 { out.Mode |= 0111 } } if b.options.UID != 0 && out.Uid == 0 { out.Uid = b.options.UID } if b.options.GID != 0 && out.Gid == 0 { out.Gid = b.options.GID } setBlocks(out) } func (b *rawBridge) setAttrTimeout(out *fuse.AttrOut) { if b.options.AttrTimeout != nil && out.Timeout() == 0 { out.SetTimeout(*b.options.AttrTimeout) } } // NewNodeFS creates a node based filesystem based on the // InodeEmbedder instance for the root of the tree. func NewNodeFS(root InodeEmbedder, opts *Options) fuse.RawFileSystem { bridge := &rawBridge{ automaticIno: opts.FirstAutomaticIno, server: opts.ServerCallbacks, } if bridge.automaticIno == 1 { bridge.automaticIno++ } if bridge.automaticIno == 0 { bridge.automaticIno = 1 << 63 } if opts != nil { bridge.options = *opts } else { oneSec := time.Second bridge.options.EntryTimeout = &oneSec bridge.options.AttrTimeout = &oneSec } initInode(root.embed(), root, StableAttr{ Ino: 1, Mode: fuse.S_IFDIR, }, bridge, false, ) bridge.root = root.embed() bridge.root.lookupCount = 1 bridge.nodes = map[uint64]*Inode{ 1: bridge.root, } // Fh 0 means no file handle. bridge.files = []*fileEntry{{}} if opts.OnAdd != nil { opts.OnAdd(context.Background()) } else if oa, ok := root.(NodeOnAdder); ok { oa.OnAdd(context.Background()) } return bridge } func (b *rawBridge) String() string { return "rawBridge" } func (b *rawBridge) inode(id uint64, fh uint64) (*Inode, *fileEntry) { b.mu.Lock() defer b.mu.Unlock() n, f := b.nodes[id], b.files[fh] if n == nil { log.Panicf("unknown node %d", id) } return n, f } func (b *rawBridge) Lookup(cancel <-chan struct{}, header *fuse.InHeader, name string, out *fuse.EntryOut) fuse.Status { parent, _ := b.inode(header.NodeId, 0) ctx := &fuse.Context{Caller: header.Caller, Cancel: cancel} child, errno := b.lookup(ctx, parent, name, out) if errno != 0 { if b.options.NegativeTimeout != nil && out.EntryTimeout() == 0 { out.SetEntryTimeout(*b.options.NegativeTimeout) } return errnoToStatus(errno) } child.setEntryOut(out) b.addNewChild(parent, name, child, nil, 0, out) b.setEntryOutTimeout(out) return fuse.OK } func (b *rawBridge) lookup(ctx *fuse.Context, parent *Inode, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) { if lu, ok := parent.ops.(NodeLookuper); ok { return lu.Lookup(ctx, name, out) } child := parent.GetChild(name) if child == nil { return nil, syscall.ENOENT } if ga, ok := child.ops.(NodeGetattrer); ok { var a fuse.AttrOut errno := ga.Getattr(ctx, nil, &a) if errno == 0 { out.Attr = a.Attr } } return child, OK } func (b *rawBridge) Rmdir(cancel <-chan struct{}, header *fuse.InHeader, name string) fuse.Status { parent, _ := b.inode(header.NodeId, 0) var errno syscall.Errno if mops, ok := parent.ops.(NodeRmdirer); ok { errno = mops.Rmdir(&fuse.Context{Caller: header.Caller, Cancel: cancel}, name) } if errno == 0 { parent.RmChild(name) } return errnoToStatus(errno) } func (b *rawBridge) Unlink(cancel <-chan struct{}, header *fuse.InHeader, name string) fuse.Status { parent, _ := b.inode(header.NodeId, 0) var errno syscall.Errno if mops, ok := parent.ops.(NodeUnlinker); ok { errno = mops.Unlink(&fuse.Context{Caller: header.Caller, Cancel: cancel}, name) } if errno == 0 { parent.RmChild(name) } return errnoToStatus(errno) } func (b *rawBridge) Mkdir(cancel <-chan struct{}, input *fuse.MkdirIn, name string, out *fuse.EntryOut) fuse.Status { parent, _ := b.inode(input.NodeId, 0) var child *Inode var errno syscall.Errno if mops, ok := parent.ops.(NodeMkdirer); ok { child, errno = mops.Mkdir(&fuse.Context{Caller: input.Caller, Cancel: cancel}, name, input.Mode, out) } else { return fuse.ENOTSUP } if errno != 0 { return errnoToStatus(errno) } if out.Attr.Mode&^07777 == 0 { out.Attr.Mode |= fuse.S_IFDIR } if out.Attr.Mode&^07777 != fuse.S_IFDIR { log.Panicf("Mkdir: mode must be S_IFDIR (%o), got %o", fuse.S_IFDIR, out.Attr.Mode) } child.setEntryOut(out) b.addNewChild(parent, name, child, nil, 0, out) b.setEntryOutTimeout(out) return fuse.OK } func (b *rawBridge) Mknod(cancel <-chan struct{}, input *fuse.MknodIn, name string, out *fuse.EntryOut) fuse.Status { parent, _ := b.inode(input.NodeId, 0) var child *Inode var errno syscall.Errno if mops, ok := parent.ops.(NodeMknoder); ok { child, errno = mops.Mknod(&fuse.Context{Caller: input.Caller, Cancel: cancel}, name, input.Mode, input.Rdev, out) } if errno != 0 { return errnoToStatus(errno) } child.setEntryOut(out) b.addNewChild(parent, name, child, nil, 0, out) b.setEntryOutTimeout(out) return fuse.OK } func (b *rawBridge) Create(cancel <-chan struct{}, input *fuse.CreateIn, name string, out *fuse.CreateOut) fuse.Status { ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel} parent, _ := b.inode(input.NodeId, 0) var child *Inode var errno syscall.Errno var f FileHandle var flags uint32 if mops, ok := parent.ops.(NodeCreater); ok { child, f, flags, errno = mops.Create(ctx, name, input.Flags, input.Mode, &out.EntryOut) } else { return fuse.EROFS } if errno != 0 { if b.options.NegativeTimeout != nil { out.SetEntryTimeout(*b.options.NegativeTimeout) } return errnoToStatus(errno) } out.Fh = uint64(b.addNewChild(parent, name, child, f, input.Flags|syscall.O_CREAT, &out.EntryOut)) out.OpenFlags = flags child.setEntryOut(&out.EntryOut) b.setEntryOutTimeout(&out.EntryOut) return fuse.OK } func (b *rawBridge) Forget(nodeid, nlookup uint64) { n, _ := b.inode(nodeid, 0) n.removeRef(nlookup, false) } func (b *rawBridge) SetDebug(debug bool) {} func (b *rawBridge) GetAttr(cancel <-chan struct{}, input *fuse.GetAttrIn, out *fuse.AttrOut) fuse.Status { n, fEntry := b.inode(input.NodeId, input.Fh()) f := fEntry.file if f == nil { // The linux kernel doesnt pass along the file // descriptor, so we have to fake it here. // See https://github.com/libfuse/libfuse/issues/62 b.mu.Lock() for _, fh := range n.openFiles { f = b.files[fh].file b.files[fh].wg.Add(1) defer b.files[fh].wg.Done() break } b.mu.Unlock() } ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel} return errnoToStatus(b.getattr(ctx, n, f, out)) } func (b *rawBridge) getattr(ctx context.Context, n *Inode, f FileHandle, out *fuse.AttrOut) syscall.Errno { var errno syscall.Errno var fg FileGetattrer if f != nil { fg, _ = f.(FileGetattrer) } if fops, ok := n.ops.(NodeGetattrer); ok { errno = fops.Getattr(ctx, f, out) } else if fg != nil { errno = fg.Getattr(ctx, out) } else { // We set Mode below, which is the minimum for success } if errno == 0 { if out.Ino != 0 && n.stableAttr.Ino > 1 && out.Ino != n.stableAttr.Ino { b.logf("warning: rawBridge.getattr: overriding ino %d with %d", out.Ino, n.stableAttr.Ino) } out.Ino = n.stableAttr.Ino out.Mode = (out.Attr.Mode & 07777) | n.stableAttr.Mode b.setAttr(&out.Attr) b.setAttrTimeout(out) } return errno } func (b *rawBridge) SetAttr(cancel <-chan struct{}, in *fuse.SetAttrIn, out *fuse.AttrOut) fuse.Status { ctx := &fuse.Context{Caller: in.Caller, Cancel: cancel} fh, _ := in.GetFh() n, fEntry := b.inode(in.NodeId, fh) f := fEntry.file var errno = syscall.ENOTSUP if fops, ok := n.ops.(NodeSetattrer); ok { errno = fops.Setattr(ctx, f, in, out) } else if fops, ok := f.(FileSetattrer); ok { errno = fops.Setattr(ctx, in, out) } out.Mode = n.stableAttr.Mode | (out.Mode & 07777) return errnoToStatus(errno) } func (b *rawBridge) Rename(cancel <-chan struct{}, input *fuse.RenameIn, oldName string, newName string) fuse.Status { p1, _ := b.inode(input.NodeId, 0) p2, _ := b.inode(input.Newdir, 0) if mops, ok := p1.ops.(NodeRenamer); ok { errno := mops.Rename(&fuse.Context{Caller: input.Caller, Cancel: cancel}, oldName, p2.ops, newName, input.Flags) if errno == 0 { if input.Flags&RENAME_EXCHANGE != 0 { p1.ExchangeChild(oldName, p2, newName) } else { // MvChild cannot fail with overwrite=true. _ = p1.MvChild(oldName, p2, newName, true) } } return errnoToStatus(errno) } return fuse.ENOTSUP } func (b *rawBridge) Link(cancel <-chan struct{}, input *fuse.LinkIn, name string, out *fuse.EntryOut) fuse.Status { parent, _ := b.inode(input.NodeId, 0) target, _ := b.inode(input.Oldnodeid, 0) if mops, ok := parent.ops.(NodeLinker); ok { child, errno := mops.Link(&fuse.Context{Caller: input.Caller, Cancel: cancel}, target.ops, name, out) if errno != 0 { return errnoToStatus(errno) } child.setEntryOut(out) b.addNewChild(parent, name, child, nil, 0, out) b.setEntryOutTimeout(out) return fuse.OK } return fuse.ENOTSUP } func (b *rawBridge) Symlink(cancel <-chan struct{}, header *fuse.InHeader, target string, name string, out *fuse.EntryOut) fuse.Status { parent, _ := b.inode(header.NodeId, 0) if mops, ok := parent.ops.(NodeSymlinker); ok { child, status := mops.Symlink(&fuse.Context{Caller: header.Caller, Cancel: cancel}, target, name, out) if status != 0 { return errnoToStatus(status) } b.addNewChild(parent, name, child, nil, 0, out) child.setEntryOut(out) b.setEntryOutTimeout(out) return fuse.OK } return fuse.ENOTSUP } func (b *rawBridge) Readlink(cancel <-chan struct{}, header *fuse.InHeader) (out []byte, status fuse.Status) { n, _ := b.inode(header.NodeId, 0) if linker, ok := n.ops.(NodeReadlinker); ok { result, errno := linker.Readlink(&fuse.Context{Caller: header.Caller, Cancel: cancel}) if errno != 0 { return nil, errnoToStatus(errno) } return result, fuse.OK } return nil, fuse.ENOTSUP } func (b *rawBridge) Access(cancel <-chan struct{}, input *fuse.AccessIn) fuse.Status { n, _ := b.inode(input.NodeId, 0) ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel} if a, ok := n.ops.(NodeAccesser); ok { return errnoToStatus(a.Access(ctx, input.Mask)) } // default: check attributes. caller := input.Caller var out fuse.AttrOut if s := b.getattr(ctx, n, nil, &out); s != 0 { return errnoToStatus(s) } if !internal.HasAccess(caller.Uid, caller.Gid, out.Uid, out.Gid, out.Mode, input.Mask) { return fuse.EACCES } return fuse.OK } // Extended attributes. func (b *rawBridge) GetXAttr(cancel <-chan struct{}, header *fuse.InHeader, attr string, data []byte) (uint32, fuse.Status) { n, _ := b.inode(header.NodeId, 0) if xops, ok := n.ops.(NodeGetxattrer); ok { nb, errno := xops.Getxattr(&fuse.Context{Caller: header.Caller, Cancel: cancel}, attr, data) return nb, errnoToStatus(errno) } return 0, fuse.ENOATTR } func (b *rawBridge) ListXAttr(cancel <-chan struct{}, header *fuse.InHeader, dest []byte) (sz uint32, status fuse.Status) { n, _ := b.inode(header.NodeId, 0) if xops, ok := n.ops.(NodeListxattrer); ok { sz, errno := xops.Listxattr(&fuse.Context{Caller: header.Caller, Cancel: cancel}, dest) return sz, errnoToStatus(errno) } return 0, fuse.OK } func (b *rawBridge) SetXAttr(cancel <-chan struct{}, input *fuse.SetXAttrIn, attr string, data []byte) fuse.Status { n, _ := b.inode(input.NodeId, 0) if xops, ok := n.ops.(NodeSetxattrer); ok { return errnoToStatus(xops.Setxattr(&fuse.Context{Caller: input.Caller, Cancel: cancel}, attr, data, input.Flags)) } return fuse.ENOATTR } func (b *rawBridge) RemoveXAttr(cancel <-chan struct{}, header *fuse.InHeader, attr string) fuse.Status { n, _ := b.inode(header.NodeId, 0) if xops, ok := n.ops.(NodeRemovexattrer); ok { return errnoToStatus(xops.Removexattr(&fuse.Context{Caller: header.Caller, Cancel: cancel}, attr)) } return fuse.ENOATTR } func (b *rawBridge) Open(cancel <-chan struct{}, input *fuse.OpenIn, out *fuse.OpenOut) fuse.Status { n, _ := b.inode(input.NodeId, 0) if op, ok := n.ops.(NodeOpener); ok { f, flags, errno := op.Open(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.Flags) if errno != 0 { return errnoToStatus(errno) } if f != nil { b.mu.Lock() defer b.mu.Unlock() out.Fh = uint64(b.registerFile(n, f, input.Flags)) } out.OpenFlags = flags return fuse.OK } return fuse.ENOTSUP } // registerFile hands out a file handle. Must have bridge.mu func (b *rawBridge) registerFile(n *Inode, f FileHandle, flags uint32) uint32 { var fh uint32 if len(b.freeFiles) > 0 { last := len(b.freeFiles) - 1 fh = b.freeFiles[last] b.freeFiles = b.freeFiles[:last] } else { fh = uint32(len(b.files)) b.files = append(b.files, &fileEntry{}) } fileEntry := b.files[fh] fileEntry.nodeIndex = len(n.openFiles) fileEntry.file = f n.openFiles = append(n.openFiles, fh) return fh } func (b *rawBridge) Read(cancel <-chan struct{}, input *fuse.ReadIn, buf []byte) (fuse.ReadResult, fuse.Status) { n, f := b.inode(input.NodeId, input.Fh) if fops, ok := n.ops.(NodeReader); ok { res, errno := fops.Read(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, buf, int64(input.Offset)) return res, errnoToStatus(errno) } if fr, ok := f.file.(FileReader); ok { res, errno := fr.Read(&fuse.Context{Caller: input.Caller, Cancel: cancel}, buf, int64(input.Offset)) return res, errnoToStatus(errno) } return nil, fuse.ENOTSUP } func (b *rawBridge) GetLk(cancel <-chan struct{}, input *fuse.LkIn, out *fuse.LkOut) fuse.Status { n, f := b.inode(input.NodeId, input.Fh) if lops, ok := n.ops.(NodeGetlker); ok { return errnoToStatus(lops.Getlk(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, input.Owner, &input.Lk, input.LkFlags, &out.Lk)) } if gl, ok := f.file.(FileGetlker); ok { return errnoToStatus(gl.Getlk(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.Owner, &input.Lk, input.LkFlags, &out.Lk)) } return fuse.ENOTSUP } func (b *rawBridge) SetLk(cancel <-chan struct{}, input *fuse.LkIn) fuse.Status { n, f := b.inode(input.NodeId, input.Fh) if lops, ok := n.ops.(NodeSetlker); ok { return errnoToStatus(lops.Setlk(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, input.Owner, &input.Lk, input.LkFlags)) } if sl, ok := n.ops.(FileSetlker); ok { return errnoToStatus(sl.Setlk(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.Owner, &input.Lk, input.LkFlags)) } return fuse.ENOTSUP } func (b *rawBridge) SetLkw(cancel <-chan struct{}, input *fuse.LkIn) fuse.Status { n, f := b.inode(input.NodeId, input.Fh) if lops, ok := n.ops.(NodeSetlkwer); ok { return errnoToStatus(lops.Setlkw(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, input.Owner, &input.Lk, input.LkFlags)) } if sl, ok := n.ops.(FileSetlkwer); ok { return errnoToStatus(sl.Setlkw(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.Owner, &input.Lk, input.LkFlags)) } return fuse.ENOTSUP } func (b *rawBridge) Release(cancel <-chan struct{}, input *fuse.ReleaseIn) { n, f := b.releaseFileEntry(input.NodeId, input.Fh) if f == nil { return } f.wg.Wait() if r, ok := n.ops.(NodeReleaser); ok { r.Release(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file) } else if r, ok := f.file.(FileReleaser); ok { r.Release(&fuse.Context{Caller: input.Caller, Cancel: cancel}) } b.mu.Lock() defer b.mu.Unlock() b.freeFiles = append(b.freeFiles, uint32(input.Fh)) } func (b *rawBridge) ReleaseDir(input *fuse.ReleaseIn) { _, f := b.releaseFileEntry(input.NodeId, input.Fh) f.wg.Wait() f.mu.Lock() if f.dirStream != nil { f.dirStream.Close() f.dirStream = nil } f.mu.Unlock() b.mu.Lock() defer b.mu.Unlock() b.freeFiles = append(b.freeFiles, uint32(input.Fh)) } func (b *rawBridge) releaseFileEntry(nid uint64, fh uint64) (*Inode, *fileEntry) { b.mu.Lock() defer b.mu.Unlock() n := b.nodes[nid] var entry *fileEntry if fh > 0 { last := len(n.openFiles) - 1 entry = b.files[fh] if last != entry.nodeIndex { n.openFiles[entry.nodeIndex] = n.openFiles[last] b.files[n.openFiles[entry.nodeIndex]].nodeIndex = entry.nodeIndex } n.openFiles = n.openFiles[:last] } return n, entry } func (b *rawBridge) Write(cancel <-chan struct{}, input *fuse.WriteIn, data []byte) (written uint32, status fuse.Status) { n, f := b.inode(input.NodeId, input.Fh) if wr, ok := n.ops.(NodeWriter); ok { w, errno := wr.Write(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, data, int64(input.Offset)) return w, errnoToStatus(errno) } if fr, ok := f.file.(FileWriter); ok { w, errno := fr.Write(&fuse.Context{Caller: input.Caller, Cancel: cancel}, data, int64(input.Offset)) return w, errnoToStatus(errno) } return 0, fuse.ENOTSUP } func (b *rawBridge) Flush(cancel <-chan struct{}, input *fuse.FlushIn) fuse.Status { n, f := b.inode(input.NodeId, input.Fh) if fl, ok := n.ops.(NodeFlusher); ok { return errnoToStatus(fl.Flush(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file)) } if fl, ok := f.file.(FileFlusher); ok { return errnoToStatus(fl.Flush(&fuse.Context{Caller: input.Caller, Cancel: cancel})) } return 0 } func (b *rawBridge) Fsync(cancel <-chan struct{}, input *fuse.FsyncIn) fuse.Status { n, f := b.inode(input.NodeId, input.Fh) if fs, ok := n.ops.(NodeFsyncer); ok { return errnoToStatus(fs.Fsync(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, input.FsyncFlags)) } if fs, ok := f.file.(FileFsyncer); ok { return errnoToStatus(fs.Fsync(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.FsyncFlags)) } return fuse.ENOTSUP } func (b *rawBridge) Fallocate(cancel <-chan struct{}, input *fuse.FallocateIn) fuse.Status { n, f := b.inode(input.NodeId, input.Fh) if a, ok := n.ops.(NodeAllocater); ok { return errnoToStatus(a.Allocate(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, input.Offset, input.Length, input.Mode)) } if a, ok := f.file.(FileAllocater); ok { return errnoToStatus(a.Allocate(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.Offset, input.Length, input.Mode)) } return fuse.ENOTSUP } func (b *rawBridge) OpenDir(cancel <-chan struct{}, input *fuse.OpenIn, out *fuse.OpenOut) fuse.Status { n, _ := b.inode(input.NodeId, 0) if od, ok := n.ops.(NodeOpendirer); ok { errno := od.Opendir(&fuse.Context{Caller: input.Caller, Cancel: cancel}) if errno != 0 { return errnoToStatus(errno) } } b.mu.Lock() defer b.mu.Unlock() out.Fh = uint64(b.registerFile(n, nil, 0)) return fuse.OK } // setStream sets the directory part of f. Must hold f.mu func (b *rawBridge) setStream(cancel <-chan struct{}, input *fuse.ReadIn, inode *Inode, f *fileEntry) syscall.Errno { if f.dirStream == nil || input.Offset == 0 { if f.dirStream != nil { f.dirStream.Close() f.dirStream = nil } str, errno := b.getStream(&fuse.Context{Caller: input.Caller, Cancel: cancel}, inode) if errno != 0 { return errno } f.hasOverflow = false f.dirStream = str } return 0 } func (b *rawBridge) getStream(ctx context.Context, inode *Inode) (DirStream, syscall.Errno) { if rd, ok := inode.ops.(NodeReaddirer); ok { return rd.Readdir(ctx) } r := []fuse.DirEntry{} for k, ch := range inode.Children() { r = append(r, fuse.DirEntry{Mode: ch.Mode(), Name: k, Ino: ch.StableAttr().Ino}) } return NewListDirStream(r), 0 } func (b *rawBridge) ReadDir(cancel <-chan struct{}, input *fuse.ReadIn, out *fuse.DirEntryList) fuse.Status { n, f := b.inode(input.NodeId, input.Fh) f.mu.Lock() defer f.mu.Unlock() if errno := b.setStream(cancel, input, n, f); errno != 0 { return errnoToStatus(errno) } if f.hasOverflow { // always succeeds. out.AddDirEntry(f.overflow) f.hasOverflow = false } for f.dirStream.HasNext() { e, errno := f.dirStream.Next() if errno != 0 { return errnoToStatus(errno) } if !out.AddDirEntry(e) { f.overflow = e f.hasOverflow = true return errnoToStatus(errno) } } return fuse.OK } func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out *fuse.DirEntryList) fuse.Status { n, f := b.inode(input.NodeId, input.Fh) f.mu.Lock() defer f.mu.Unlock() if errno := b.setStream(cancel, input, n, f); errno != 0 { return errnoToStatus(errno) } ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel} for f.dirStream.HasNext() || f.hasOverflow { var e fuse.DirEntry var errno syscall.Errno if f.hasOverflow { e = f.overflow f.hasOverflow = false } else { e, errno = f.dirStream.Next() } if errno != 0 { return errnoToStatus(errno) } entryOut := out.AddDirLookupEntry(e) if entryOut == nil { f.overflow = e f.hasOverflow = true return fuse.OK } // Virtual entries "." and ".." should be part of the // directory listing, but not part of the filesystem tree. // The values in EntryOut are ignored by Linux // (see fuse_direntplus_link() in linux/fs/fuse/readdir.c), so leave // them at zero-value. if e.Name == "." || e.Name == ".." { continue } child, errno := b.lookup(ctx, n, e.Name, entryOut) if errno != 0 { if b.options.NegativeTimeout != nil { entryOut.SetEntryTimeout(*b.options.NegativeTimeout) } } else { b.addNewChild(n, e.Name, child, nil, 0, entryOut) child.setEntryOut(entryOut) b.setEntryOutTimeout(entryOut) if e.Mode&syscall.S_IFMT != child.stableAttr.Mode&syscall.S_IFMT { // The file type has changed behind our back. Use the new value. out.FixMode(child.stableAttr.Mode) } entryOut.Mode = child.stableAttr.Mode | (entryOut.Mode & 07777) } } return fuse.OK } func (b *rawBridge) FsyncDir(cancel <-chan struct{}, input *fuse.FsyncIn) fuse.Status { n, _ := b.inode(input.NodeId, input.Fh) if fs, ok := n.ops.(NodeFsyncer); ok { return errnoToStatus(fs.Fsync(&fuse.Context{Caller: input.Caller, Cancel: cancel}, nil, input.FsyncFlags)) } return fuse.ENOTSUP } func (b *rawBridge) StatFs(cancel <-chan struct{}, input *fuse.InHeader, out *fuse.StatfsOut) fuse.Status { n, _ := b.inode(input.NodeId, 0) if sf, ok := n.ops.(NodeStatfser); ok { return errnoToStatus(sf.Statfs(&fuse.Context{Caller: input.Caller, Cancel: cancel}, out)) } // leave zeroed out return fuse.OK } func (b *rawBridge) Init(s *fuse.Server) { b.server = s } func (b *rawBridge) CopyFileRange(cancel <-chan struct{}, in *fuse.CopyFileRangeIn) (size uint32, status fuse.Status) { n1, f1 := b.inode(in.NodeId, in.FhIn) cfr, ok := n1.ops.(NodeCopyFileRanger) if !ok { return 0, fuse.ENOTSUP } n2, f2 := b.inode(in.NodeIdOut, in.FhOut) sz, errno := cfr.CopyFileRange(&fuse.Context{Caller: in.Caller, Cancel: cancel}, f1.file, in.OffIn, n2, f2.file, in.OffOut, in.Len, in.Flags) return sz, errnoToStatus(errno) } func (b *rawBridge) Lseek(cancel <-chan struct{}, in *fuse.LseekIn, out *fuse.LseekOut) fuse.Status { n, f := b.inode(in.NodeId, in.Fh) ls, ok := n.ops.(NodeLseeker) if ok { off, errno := ls.Lseek(&fuse.Context{Caller: in.Caller, Cancel: cancel}, f.file, in.Offset, in.Whence) out.Offset = off return errnoToStatus(errno) } if fs, ok := f.file.(FileLseeker); ok { off, errno := fs.Lseek(&fuse.Context{Caller: in.Caller, Cancel: cancel}, in.Offset, in.Whence) out.Offset = off return errnoToStatus(errno) } if in.Whence == _SEEK_DATA || in.Whence == _SEEK_HOLE { out.Offset = in.Offset return fuse.OK } return fuse.ENOTSUP } go-fuse-2.0.3/fs/bridge_test.go000066400000000000000000000125451364171671200163320ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "context" "log" "os" "strings" "sync/atomic" "syscall" "testing" "unsafe" "github.com/hanwen/go-fuse/v2/fuse" ) // TestBridgeReaddirPlusVirtualEntries looks at "." and ".." in the ReadDirPlus // output. They should exist, but the NodeId should be zero. func TestBridgeReaddirPlusVirtualEntries(t *testing.T) { // Set suppressDebug as we do our own logging tc := newTestCase(t, &testOptions{suppressDebug: true}) defer tc.Clean() rb := tc.rawFS.(*rawBridge) // We only populate what rawBridge.OpenDir() actually looks at. openIn := fuse.OpenIn{} openIn.NodeId = 1 // root node always has id 1 and always exists openOut := fuse.OpenOut{} status := rb.OpenDir(nil, &openIn, &openOut) if !status.Ok() { t.Fatal(status) } releaseIn := fuse.ReleaseIn{ Fh: openOut.Fh, } releaseIn.NodeId = 1 defer rb.ReleaseDir(&releaseIn) // We only populate what rawBridge.ReadDirPlus() actually looks at. readIn := fuse.ReadIn{} readIn.NodeId = 1 readIn.Fh = openOut.Fh buf := make([]byte, 400) dirents := fuse.NewDirEntryList(buf, 0) status = rb.ReadDirPlus(nil, &readIn, dirents) if !status.Ok() { t.Fatal(status) } // Parse the output buffer. Looks like this in memory: // 1) fuse.EntryOut // 2) fuse._Dirent // 3) Name (null-terminated) // 4) Padding to align to 8 bytes // [repeat] const entryOutSize = int(unsafe.Sizeof(fuse.EntryOut{})) // = unsafe.Sizeof(fuse._Dirent{}), see fuse/types.go const direntSize = 24 // Round up to 8. const entry2off = (entryOutSize + direntSize + len(".\x00") + 7) / 8 * 8 names := map[string]*fuse.EntryOut{} // 1st entry should be "." entry1 := (*fuse.EntryOut)(unsafe.Pointer(&buf[0])) name1 := string(buf[entryOutSize+direntSize : entryOutSize+direntSize+2]) names[name1] = entry1 // 2nd entry should be ".." entry2 := (*fuse.EntryOut)(unsafe.Pointer(&buf[entry2off])) name2 := string(buf[entry2off+entryOutSize+direntSize : entry2off+entryOutSize+direntSize+2]) names[name2] = entry2 if len(names) != 2 || names[".\000"] == nil || names[".."] == nil { t.Fatalf(`got %v, want {".\\0", ".."}`, names) } for k, v := range names { if v.NodeId != 0 { t.Errorf("entry %q NodeId should be 0, but is %d", k, v.NodeId) } } } // TestTypeChange simulates inode number reuse that happens on real // filesystems. For go-fuse, inode number reuse can look like a file changing // to a directory or vice versa. Acutally, the old inode does not exist anymore, // we just have not received the FORGET yet. func TestTypeChange(t *testing.T) { rootNode := testTypeChangeIno{} mnt, _, clean := testMount(t, &rootNode, nil) defer clean() for i := 0; i < 100; i++ { fi, _ := os.Stat(mnt + "/file") syscall.Unlink(mnt + "/file") fi, _ = os.Stat(mnt + "/dir") if !fi.IsDir() { t.Fatal("should be a dir now") } syscall.Rmdir(mnt + "/dir") fi, _ = os.Stat(mnt + "/file") if fi.IsDir() { t.Fatal("should be a file now") } } } type testTypeChangeIno struct { Inode } // Lookup function for TestTypeChange: // If name == "dir", returns a node of type dir, // if name == "file" of type file, // otherwise ENOENT. func (fn *testTypeChangeIno) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) { var mode uint32 switch name { case "file": mode = syscall.S_IFREG case "dir": mode = syscall.S_IFDIR default: return nil, syscall.ENOENT } stable := StableAttr{ Mode: mode, Ino: 1234, } childFN := &testTypeChangeIno{} child := fn.NewInode(ctx, childFN, stable) return child, syscall.F_OK } // TestDeletedInodePath checks that Inode.Path returns ".deleted" if an Inode is // disconnected from the hierarchy (=orphaned) func TestDeletedInodePath(t *testing.T) { rootNode := testDeletedIno{} mnt, _, clean := testMount(t, &rootNode, &Options{Logger: log.New(os.Stderr, "", 0)}) defer clean() // Open a file handle so the kernel cannot FORGET the inode fd, err := os.Open(mnt + "/dir") if err != nil { t.Fatal(err) } defer fd.Close() // Delete it so the inode does not have a path anymore err = syscall.Rmdir(mnt + "/dir") if err != nil { t.Fatal(err) } atomic.StoreInt32(&rootNode.deleted, 1) // Our Getattr implementation `testDeletedIno.Getattr` should return // ENFILE when everything looks ok, EILSEQ otherwise. var st syscall.Stat_t err = syscall.Fstat(int(fd.Fd()), &st) if err != syscall.ENFILE { t.Error(err) } } type testDeletedIno struct { Inode deleted int32 } func (n *testDeletedIno) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) { ino := n.Root().Operations().(*testDeletedIno) if atomic.LoadInt32(&ino.deleted) == 1 { return nil, syscall.ENOENT } if name != "dir" { return nil, syscall.ENOENT } childNode := &testDeletedIno{} stable := StableAttr{Mode: syscall.S_IFDIR, Ino: 999} child := n.NewInode(ctx, childNode, stable) return child, syscall.F_OK } func (n *testDeletedIno) Opendir(ctx context.Context) syscall.Errno { return OK } func (n *testDeletedIno) Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno { prefix := ".go-fuse" p := n.Path(n.Root()) if strings.HasPrefix(p, prefix) { // Return ENFILE when things look ok return syscall.ENFILE } // Otherwise EILSEQ return syscall.EILSEQ } go-fuse-2.0.3/fs/cache_test.go000066400000000000000000000056271364171671200161440ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "bytes" "context" "fmt" "io/ioutil" "sync" "syscall" "testing" "github.com/hanwen/go-fuse/v2/fuse" ) type keepCacheFile struct { Inode keepCache bool mu sync.Mutex content []byte count int } var _ = (NodeReader)((*keepCacheFile)(nil)) var _ = (NodeOpener)((*keepCacheFile)(nil)) var _ = (NodeGetattrer)((*keepCacheFile)(nil)) func (f *keepCacheFile) setContent(delta int) { f.mu.Lock() defer f.mu.Unlock() f.count += delta f.content = []byte(fmt.Sprintf("%010x", f.count)) } func (f *keepCacheFile) Open(ctx context.Context, flags uint32) (FileHandle, uint32, syscall.Errno) { var fl uint32 if f.keepCache { fl = fuse.FOPEN_KEEP_CACHE } f.setContent(0) return nil, fl, OK } func (f *keepCacheFile) Getattr(ctx context.Context, fh FileHandle, out *fuse.AttrOut) syscall.Errno { f.mu.Lock() defer f.mu.Unlock() out.Size = uint64(len(f.content)) return OK } func (f *keepCacheFile) Read(ctx context.Context, fh FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) { f.setContent(1) f.mu.Lock() defer f.mu.Unlock() return fuse.ReadResultData(f.content[off:]), OK } type keepCacheRoot struct { Inode keep, nokeep *keepCacheFile } var _ = (NodeOnAdder)((*keepCacheRoot)(nil)) func (r *keepCacheRoot) OnAdd(ctx context.Context) { i := &r.Inode r.keep = &keepCacheFile{ keepCache: true, } r.keep.setContent(0) i.AddChild("keep", i.NewInode(ctx, r.keep, StableAttr{}), true) r.nokeep = &keepCacheFile{ keepCache: false, } r.nokeep.setContent(0) i.AddChild("nokeep", i.NewInode(ctx, r.nokeep, StableAttr{}), true) } // Test FOPEN_KEEP_CACHE. This is a little subtle: the automatic cache // invalidation triggers if mtime or file size is changed, so only // change content but no metadata. func TestKeepCache(t *testing.T) { root := &keepCacheRoot{} mntDir, _, clean := testMount(t, root, nil) defer clean() c1, err := ioutil.ReadFile(mntDir + "/keep") if err != nil { t.Fatalf("read keep 1: %v", err) } c2, err := ioutil.ReadFile(mntDir + "/keep") if err != nil { t.Fatalf("read keep 2: %v", err) } if bytes.Compare(c1, c2) != 0 { t.Errorf("keep read 2 got %q want read 1 %q", c2, c1) } if s := root.keep.Inode.NotifyContent(0, 100); s != OK { t.Errorf("NotifyContent: %v", s) } c3, err := ioutil.ReadFile(mntDir + "/keep") if err != nil { t.Fatalf("read keep 3: %v", err) } if bytes.Compare(c2, c3) == 0 { t.Errorf("keep read 3 got %q want different", c3) } nc1, err := ioutil.ReadFile(mntDir + "/nokeep") if err != nil { t.Fatalf("read keep 1: %v", err) } nc2, err := ioutil.ReadFile(mntDir + "/nokeep") if err != nil { t.Fatalf("read keep 2: %v", err) } if bytes.Compare(nc1, nc2) == 0 { t.Errorf("nokeep read 2 got %q want read 1 %q", c2, c1) } } go-fuse-2.0.3/fs/constants.go000066400000000000000000000012331364171671200160430ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "syscall" "github.com/hanwen/go-fuse/v2/fuse" ) // OK is the Errno return value to indicate absense of errors. var OK = syscall.Errno(0) // ToErrno exhumes the syscall.Errno error from wrapped error values. func ToErrno(err error) syscall.Errno { s := fuse.ToStatus(err) return syscall.Errno(s) } // RENAME_EXCHANGE is a flag argument for renameat2() const RENAME_EXCHANGE = 0x2 // seek to the next data const _SEEK_DATA = 3 // seek to the next hole const _SEEK_HOLE = 4 go-fuse-2.0.3/fs/constants_darwin.go000066400000000000000000000004421364171671200174100ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import "syscall" // ENOATTR indicates that an extended attribute was not present. var ENOATTR = syscall.ENOATTR go-fuse-2.0.3/fs/constants_linux.go000066400000000000000000000004421364171671200172630ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import "syscall" // ENOATTR indicates that an extended attribute was not present. var ENOATTR = syscall.ENODATA go-fuse-2.0.3/fs/default.go000066400000000000000000000002601364171671200154520ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs go-fuse-2.0.3/fs/directio_example_test.go000066400000000000000000000043051364171671200204060ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs_test import ( "context" "fmt" "log" "syscall" "time" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" ) // bytesFileHandle is a file handle that carries separate content for // each Open call type bytesFileHandle struct { content []byte } // bytesFileHandle allows reads var _ = (fs.FileReader)((*bytesFileHandle)(nil)) func (fh *bytesFileHandle) Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) { end := off + int64(len(dest)) if end > int64(len(fh.content)) { end = int64(len(fh.content)) } // We could copy to the `dest` buffer, but since we have a // []byte already, return that. return fuse.ReadResultData(fh.content[off:end]), 0 } // timeFile is a file that contains the wall clock time as ASCII. type timeFile struct { fs.Inode } // timeFile implements Open var _ = (fs.NodeOpener)((*timeFile)(nil)) func (f *timeFile) Open(ctx context.Context, openFlags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) { // disallow writes if fuseFlags&(syscall.O_RDWR|syscall.O_WRONLY) != 0 { return nil, 0, syscall.EROFS } // capture open time now := time.Now().Format(time.StampNano) + "\n" fh = &bytesFileHandle{ content: []byte(now), } // Return FOPEN_DIRECT_IO so content is not cached. return fh, fuse.FOPEN_DIRECT_IO, 0 } // ExampleDirectIO shows how to create a file whose contents change on // every read. func Example_directIO() { mntDir := "/tmp/x" root := &fs.Inode{} // Mount the file system server, err := fs.Mount(mntDir, root, &fs.Options{ MountOptions: fuse.MountOptions{Debug: false}, // Setup the clock file. OnAdd: func(ctx context.Context) { ch := root.NewPersistentInode( ctx, &timeFile{}, fs.StableAttr{Mode: syscall.S_IFREG}) root.AddChild("clock", ch, true) }, }) if err != nil { log.Fatal(err) } fmt.Printf("cat %s/clock to see the time\n", mntDir) fmt.Printf("Unmount by calling 'fusermount -u %s'\n", mntDir) // Serve the file system, until unmounted by calling fusermount -u server.Wait() } go-fuse-2.0.3/fs/directio_test.go000066400000000000000000000043511364171671200166740ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "bytes" "context" "fmt" "os" "syscall" "testing" "github.com/hanwen/go-fuse/v2/fuse" ) type dioRoot struct { Inode } func (r *dioRoot) OnAdd(ctx context.Context) { r.Inode.AddChild("file", r.Inode.NewInode(ctx, &dioFile{}, StableAttr{}), false) } // A file handle that pretends that every hole/data starts at // multiples of 1024 type dioFH struct { } var _ = (FileLseeker)((*dioFH)(nil)) var _ = (FileReader)((*dioFH)(nil)) func (f *dioFH) Lseek(ctx context.Context, off uint64, whence uint32) (uint64, syscall.Errno) { next := (off + 1023) & (^uint64(1023)) return next, OK } func (fh *dioFH) Read(ctx context.Context, data []byte, off int64) (fuse.ReadResult, syscall.Errno) { r := bytes.Repeat([]byte(fmt.Sprintf("%010d", off)), 1+len(data)/10) return fuse.ReadResultData(r[:len(data)]), OK } // overrides Open so it can return a dioFH file handle type dioFile struct { Inode } var _ = (NodeOpener)((*dioFile)(nil)) func (f *dioFile) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) { return &dioFH{}, fuse.FOPEN_DIRECT_IO, OK } // this tests FOPEN_DIRECT_IO (as opposed to O_DIRECTIO) func TestFUSEDirectIO(t *testing.T) { root := &dioRoot{} mntDir, server, clean := testMount(t, root, nil) defer clean() f, err := os.Open(mntDir + "/file") if err != nil { t.Fatalf("Open %v", err) } defer f.Close() var buf [10]byte n, err := f.Read(buf[:]) if err != nil { t.Fatalf("Read %v", err) } want := bytes.Repeat([]byte{'0'}, 10) got := buf[:n] if bytes.Compare(got, want) != 0 { t.Errorf("got %q want %q", got, want) } if !server.KernelSettings().SupportsVersion(7, 24) { t.Skip("Kernel does not support lseek") } if n, err := syscall.Seek(int(f.Fd()), 512, _SEEK_DATA); err != nil { t.Errorf("Seek: %v", err) } else if n != 1024 { t.Errorf("seek: got %d, want %d", n, 1024) } n, err = f.Read(buf[:]) if err != nil { t.Fatalf("Read %v", err) } want = []byte(fmt.Sprintf("%010d", 1024)) got = buf[:n] if bytes.Compare(got, want) != 0 { t.Errorf("got %q want %q", got, want) } } go-fuse-2.0.3/fs/dirstream.go000066400000000000000000000012151364171671200160210ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "syscall" "github.com/hanwen/go-fuse/v2/fuse" ) type dirArray struct { entries []fuse.DirEntry } func (a *dirArray) HasNext() bool { return len(a.entries) > 0 } func (a *dirArray) Next() (fuse.DirEntry, syscall.Errno) { e := a.entries[0] a.entries = a.entries[1:] return e, 0 } func (a *dirArray) Close() { } // NewListDirStream wraps a slice of DirEntry as a DirStream. func NewListDirStream(list []fuse.DirEntry) DirStream { return &dirArray{list} } go-fuse-2.0.3/fs/dirstream_darwin.go000066400000000000000000000015131364171671200173660ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "io" "os" "syscall" "github.com/hanwen/go-fuse/v2/fuse" ) func NewLoopbackDirStream(nm string) (DirStream, syscall.Errno) { f, err := os.Open(nm) if err != nil { return nil, ToErrno(err) } defer f.Close() var entries []fuse.DirEntry for { want := 100 infos, err := f.Readdir(want) for _, info := range infos { s := fuse.ToStatT(info) if s == nil { continue } entries = append(entries, fuse.DirEntry{ Name: info.Name(), Mode: uint32(s.Mode), Ino: s.Ino, }) } if len(infos) < want || err == io.EOF { break } if err != nil { return nil, ToErrno(err) } } return &dirArray{entries}, OK } go-fuse-2.0.3/fs/dirstream_linux.go000066400000000000000000000034171364171671200172460ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "sync" "syscall" "unsafe" "github.com/hanwen/go-fuse/v2/fuse" ) type loopbackDirStream struct { buf []byte todo []byte // Protects fd so we can guard against double close mu sync.Mutex fd int } // NewLoopbackDirStream open a directory for reading as a DirStream func NewLoopbackDirStream(name string) (DirStream, syscall.Errno) { fd, err := syscall.Open(name, syscall.O_DIRECTORY, 0755) if err != nil { return nil, ToErrno(err) } ds := &loopbackDirStream{ buf: make([]byte, 4096), fd: fd, } if err := ds.load(); err != 0 { ds.Close() return nil, err } return ds, OK } func (ds *loopbackDirStream) Close() { ds.mu.Lock() defer ds.mu.Unlock() if ds.fd != -1 { syscall.Close(ds.fd) ds.fd = -1 } } func (ds *loopbackDirStream) HasNext() bool { ds.mu.Lock() defer ds.mu.Unlock() return len(ds.todo) > 0 } func (ds *loopbackDirStream) Next() (fuse.DirEntry, syscall.Errno) { ds.mu.Lock() defer ds.mu.Unlock() de := (*syscall.Dirent)(unsafe.Pointer(&ds.todo[0])) nameBytes := ds.todo[unsafe.Offsetof(syscall.Dirent{}.Name):de.Reclen] ds.todo = ds.todo[de.Reclen:] // After the loop, l contains the index of the first '\0'. l := 0 for l = range nameBytes { if nameBytes[l] == 0 { break } } nameBytes = nameBytes[:l] result := fuse.DirEntry{ Ino: de.Ino, Mode: (uint32(de.Type) << 12), Name: string(nameBytes), } return result, ds.load() } func (ds *loopbackDirStream) load() syscall.Errno { if len(ds.todo) > 0 { return OK } n, err := syscall.Getdents(ds.fd, ds.buf) if err != nil { return ToErrno(err) } ds.todo = ds.buf[:n] return OK } go-fuse-2.0.3/fs/dynamic_example_test.go000066400000000000000000000062521364171671200202330ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs_test import ( "context" "log" "os" "strconv" "syscall" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" ) // numberNode is a filesystem node representing an integer. Prime // numbers are regular files, while composite numbers are directories // containing all smaller numbers, eg. // // $ ls -F /tmp/x/6 // 2 3 4/ 5 // // the file system nodes are deduplicated using inode numbers. The // number 2 appears in many directories, but it is actually the represented // by the same numberNode{} object, with inode number 2. // // $ ls -i1 /tmp/x/2 /tmp/x/8/6/4/2 // 2 /tmp/x/2 // 2 /tmp/x/8/6/4/2 // type numberNode struct { // Must embed an Inode for the struct to work as a node. fs.Inode // num is the integer represented in this file/directory num int } // isPrime returns whether n is prime func isPrime(n int) bool { for i := 2; i*i <= n; i++ { if n%i == 0 { return false } } return true } func numberToMode(n int) uint32 { // prime numbers are files if isPrime(n) { return fuse.S_IFREG } // composite numbers are directories return fuse.S_IFDIR } // Ensure we are implementing the NodeReaddirer interface var _ = (fs.NodeReaddirer)((*numberNode)(nil)) // Readdir is part of the NodeReaddirer interface func (n *numberNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) { r := make([]fuse.DirEntry, 0, n.num) for i := 2; i < n.num; i++ { d := fuse.DirEntry{ Name: strconv.Itoa(i), Ino: uint64(i), Mode: numberToMode(i), } r = append(r, d) } return fs.NewListDirStream(r), 0 } // Ensure we are implementing the NodeLookuper interface var _ = (fs.NodeLookuper)((*numberNode)(nil)) // Lookup is part of the NodeLookuper interface func (n *numberNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) { i, err := strconv.Atoi(name) if err != nil { return nil, syscall.ENOENT } if i >= n.num || i <= 1 { return nil, syscall.ENOENT } stable := fs.StableAttr{ Mode: numberToMode(i), // The child inode is identified by its Inode number. // If multiple concurrent lookups try to find the same // inode, they are deduplicated on this key. Ino: uint64(i), } operations := &numberNode{num: i} // The NewInode call wraps the `operations` object into an Inode. child := n.NewInode(ctx, operations, stable) // In case of concurrent lookup requests, it can happen that operations != // child.Operations(). return child, 0 } // ExampleDynamic is a whimsical example of a dynamically discovered // file system. func Example_dynamic() { // This is where we'll mount the FS mntDir := "/tmp/x" os.Mkdir(mntDir, 0755) root := &numberNode{num: 10} server, err := fs.Mount(mntDir, root, &fs.Options{ MountOptions: fuse.MountOptions{ // Set to true to see how the file system works. Debug: true, }, }) if err != nil { log.Panic(err) } log.Printf("Mounted on %s", mntDir) log.Printf("Unmount by calling 'fusermount -u %s'", mntDir) // Wait until unmount before exiting server.Wait() } go-fuse-2.0.3/fs/example_test.go000066400000000000000000000021621364171671200165230ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs_test import ( "fmt" "io/ioutil" "log" "os" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" ) // ExampleMount shows how to create a loopback file system, and // mounting it onto a directory func Example_mount() { mntDir, _ := ioutil.TempDir("", "") home := os.Getenv("HOME") // Make $HOME available on a mount dir under /tmp/ . Caution: // write operations are also mirrored. root, err := fs.NewLoopbackRoot(home) if err != nil { log.Fatal(err) } // Mount the file system server, err := fs.Mount(mntDir, root, &fs.Options{ MountOptions: fuse.MountOptions{Debug: true}, }) if err != nil { log.Fatal(err) } fmt.Printf("Mounted %s as loopback on %s\n", home, mntDir) fmt.Printf("\n\nCAUTION:\nwrite operations on %s will also affect $HOME (%s)\n\n", mntDir, home) fmt.Printf("Unmount by calling 'fusermount -u %s'\n", mntDir) // Serve the file system, until unmounted by calling fusermount -u server.Wait() } go-fuse-2.0.3/fs/files.go000066400000000000000000000123341364171671200151350ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "context" "sync" // "time" "syscall" "github.com/hanwen/go-fuse/v2/fuse" "golang.org/x/sys/unix" ) // NewLoopbackFile creates a FileHandle out of a file descriptor. All // operations are implemented. func NewLoopbackFile(fd int) FileHandle { return &loopbackFile{fd: fd} } type loopbackFile struct { mu sync.Mutex fd int } var _ = (FileHandle)((*loopbackFile)(nil)) var _ = (FileReleaser)((*loopbackFile)(nil)) var _ = (FileGetattrer)((*loopbackFile)(nil)) var _ = (FileReader)((*loopbackFile)(nil)) var _ = (FileWriter)((*loopbackFile)(nil)) var _ = (FileGetlker)((*loopbackFile)(nil)) var _ = (FileSetlker)((*loopbackFile)(nil)) var _ = (FileSetlkwer)((*loopbackFile)(nil)) var _ = (FileLseeker)((*loopbackFile)(nil)) var _ = (FileFlusher)((*loopbackFile)(nil)) var _ = (FileFsyncer)((*loopbackFile)(nil)) var _ = (FileSetattrer)((*loopbackFile)(nil)) var _ = (FileAllocater)((*loopbackFile)(nil)) func (f *loopbackFile) Read(ctx context.Context, buf []byte, off int64) (res fuse.ReadResult, errno syscall.Errno) { f.mu.Lock() defer f.mu.Unlock() r := fuse.ReadResultFd(uintptr(f.fd), off, len(buf)) return r, OK } func (f *loopbackFile) Write(ctx context.Context, data []byte, off int64) (uint32, syscall.Errno) { f.mu.Lock() defer f.mu.Unlock() n, err := syscall.Pwrite(f.fd, data, off) return uint32(n), ToErrno(err) } func (f *loopbackFile) Release(ctx context.Context) syscall.Errno { f.mu.Lock() defer f.mu.Unlock() if f.fd != -1 { err := syscall.Close(f.fd) f.fd = -1 return ToErrno(err) } return syscall.EBADF } func (f *loopbackFile) Flush(ctx context.Context) syscall.Errno { f.mu.Lock() defer f.mu.Unlock() // Since Flush() may be called for each dup'd fd, we don't // want to really close the file, we just want to flush. This // is achieved by closing a dup'd fd. newFd, err := syscall.Dup(f.fd) if err != nil { return ToErrno(err) } err = syscall.Close(newFd) return ToErrno(err) } func (f *loopbackFile) Fsync(ctx context.Context, flags uint32) (errno syscall.Errno) { f.mu.Lock() defer f.mu.Unlock() r := ToErrno(syscall.Fsync(f.fd)) return r } const ( _OFD_GETLK = 36 _OFD_SETLK = 37 _OFD_SETLKW = 38 ) func (f *loopbackFile) Getlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) (errno syscall.Errno) { f.mu.Lock() defer f.mu.Unlock() flk := syscall.Flock_t{} lk.ToFlockT(&flk) errno = ToErrno(syscall.FcntlFlock(uintptr(f.fd), _OFD_GETLK, &flk)) out.FromFlockT(&flk) return } func (f *loopbackFile) Setlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) (errno syscall.Errno) { return f.setLock(ctx, owner, lk, flags, false) } func (f *loopbackFile) Setlkw(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) (errno syscall.Errno) { return f.setLock(ctx, owner, lk, flags, true) } func (f *loopbackFile) setLock(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32, blocking bool) (errno syscall.Errno) { f.mu.Lock() defer f.mu.Unlock() if (flags & fuse.FUSE_LK_FLOCK) != 0 { var op int switch lk.Typ { case syscall.F_RDLCK: op = syscall.LOCK_SH case syscall.F_WRLCK: op = syscall.LOCK_EX case syscall.F_UNLCK: op = syscall.LOCK_UN default: return syscall.EINVAL } if !blocking { op |= syscall.LOCK_NB } return ToErrno(syscall.Flock(f.fd, op)) } else { flk := syscall.Flock_t{} lk.ToFlockT(&flk) var op int if blocking { op = _OFD_SETLKW } else { op = _OFD_SETLK } return ToErrno(syscall.FcntlFlock(uintptr(f.fd), op, &flk)) } } func (f *loopbackFile) Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno { if errno := f.setAttr(ctx, in); errno != 0 { return errno } return f.Getattr(ctx, out) } func (f *loopbackFile) setAttr(ctx context.Context, in *fuse.SetAttrIn) syscall.Errno { f.mu.Lock() defer f.mu.Unlock() var errno syscall.Errno if mode, ok := in.GetMode(); ok { errno = ToErrno(syscall.Fchmod(f.fd, mode)) if errno != 0 { return errno } } uid32, uOk := in.GetUID() gid32, gOk := in.GetGID() if uOk || gOk { uid := -1 gid := -1 if uOk { uid = int(uid32) } if gOk { gid = int(gid32) } errno = ToErrno(syscall.Fchown(f.fd, uid, gid)) if errno != 0 { return errno } } mtime, mok := in.GetMTime() atime, aok := in.GetATime() if mok || aok { ap := &atime mp := &mtime if !aok { ap = nil } if !mok { mp = nil } errno = f.utimens(ap, mp) if errno != 0 { return errno } } if sz, ok := in.GetSize(); ok { errno = ToErrno(syscall.Ftruncate(f.fd, int64(sz))) if errno != 0 { return errno } } return OK } func (f *loopbackFile) Getattr(ctx context.Context, a *fuse.AttrOut) syscall.Errno { f.mu.Lock() defer f.mu.Unlock() st := syscall.Stat_t{} err := syscall.Fstat(f.fd, &st) if err != nil { return ToErrno(err) } a.FromStat(&st) return OK } func (f *loopbackFile) Lseek(ctx context.Context, off uint64, whence uint32) (uint64, syscall.Errno) { f.mu.Lock() defer f.mu.Unlock() n, err := unix.Seek(f.fd, int64(off), int(whence)) return uint64(n), ToErrno(err) } go-fuse-2.0.3/fs/files_darwin.go000066400000000000000000000004001364171671200164700ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import "github.com/hanwen/go-fuse/v2/fuse" func setBlocks(out *fuse.Attr) { } go-fuse-2.0.3/fs/files_linux.go000066400000000000000000000016771364171671200163640ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "context" "syscall" "time" "github.com/hanwen/go-fuse/v2/fuse" ) func (f *loopbackFile) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) syscall.Errno { f.mu.Lock() defer f.mu.Unlock() err := syscall.Fallocate(f.fd, mode, int64(off), int64(sz)) if err != nil { return ToErrno(err) } return OK } // Utimens - file handle based version of loopbackFileSystem.Utimens() func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno { var ts [2]syscall.Timespec ts[0] = fuse.UtimeToTimespec(a) ts[1] = fuse.UtimeToTimespec(m) err := futimens(int(f.fd), &ts) return ToErrno(err) } func setBlocks(out *fuse.Attr) { if out.Blksize > 0 { return } out.Blksize = 4096 pages := (out.Size + 4095) / 4096 out.Blocks = pages * 8 } go-fuse-2.0.3/fs/forget_test.go000066400000000000000000000045171364171671200163640ustar00rootroot00000000000000// Copyright 2020 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "context" "fmt" "io/ioutil" "log" "os" "os/user" "path/filepath" "syscall" "testing" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/internal/testutil" ) type allChildrenNode struct { Inode depth int } var _ = (NodeLookuper)((*allChildrenNode)(nil)) var _ = (NodeReaddirer)((*allChildrenNode)(nil)) func (n *allChildrenNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) { if n.depth == 0 { return nil, syscall.ENOENT } stable := StableAttr{ Mode: syscall.S_IFDIR, } if n.depth == 1 { stable.Mode = syscall.S_IFREG } childFN := &allChildrenNode{ depth: n.depth - 1, } child := n.NewInode(ctx, childFN, stable) return child, 0 } func (n *allChildrenNode) Readdir(ctx context.Context) (DirStream, syscall.Errno) { var list []fuse.DirEntry var m uint32 = syscall.S_IFDIR if n.depth == 1 { m = syscall.S_IFREG } for i := 0; i < 100; i++ { list = append(list, fuse.DirEntry{ Name: fmt.Sprintf("%d", i), Mode: m, }) } return NewListDirStream(list), 0 } func TestForget(t *testing.T) { u, err := user.Current() if err != nil { t.Fatal(err) } if u.Uid != "0" { t.Skip("must run test as root") } root := &allChildrenNode{ depth: 2, } sec := time.Second options := &Options{ FirstAutomaticIno: 1, EntryTimeout: &sec, } options.Debug = testutil.VerboseTest() dir, err := ioutil.TempDir("", "TestForget") if err != nil { t.Fatal(err) } defer os.RemoveAll(dir) rawFS := NewNodeFS(root, options) server, err := fuse.NewServer(rawFS, dir, &options.MountOptions) if err != nil { t.Fatal(err) } defer server.Unmount() go server.Serve() if err := server.WaitMount(); err != nil { t.Fatal(err) } nop := func(path string, info os.FileInfo, err error) error { return nil } if err := filepath.Walk(dir, nop); err != nil { t.Fatal(err) } log.Println("dropping cache") if err := ioutil.WriteFile("/proc/sys/vm/drop_caches", []byte("2"), 0644); err != nil { } time.Sleep(time.Second) bridge := rawFS.(*rawBridge) bridge.mu.Lock() l := len(bridge.nodes) bridge.mu.Unlock() if l != 1 { t.Fatalf("got %d live nodes, want 1", l) } } go-fuse-2.0.3/fs/inmemory_example_test.go000066400000000000000000000042701364171671200204440ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs_test import ( "context" "io/ioutil" "log" "path/filepath" "strings" "syscall" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" ) // files contains the files we will expose as a file system var files = map[string]string{ "file": "content", "subdir/other-file": "other-content", } // inMemoryFS is the root of the tree type inMemoryFS struct { fs.Inode } // Ensure that we implement NodeOnAdder var _ = (fs.NodeOnAdder)((*inMemoryFS)(nil)) // OnAdd is called on mounting the file system. Use it to populate // the file system tree. func (root *inMemoryFS) OnAdd(ctx context.Context) { for name, content := range files { dir, base := filepath.Split(name) p := &root.Inode // Add directories leading up to the file. for _, component := range strings.Split(dir, "/") { if len(component) == 0 { continue } ch := p.GetChild(component) if ch == nil { // Create a directory ch = p.NewPersistentInode(ctx, &fs.Inode{}, fs.StableAttr{Mode: syscall.S_IFDIR}) // Add it p.AddChild(component, ch, true) } p = ch } // Make a file out of the content bytes. This type // provides the open/read/flush methods. embedder := &fs.MemRegularFile{ Data: []byte(content), } // Create the file. The Inode must be persistent, // because its life time is not under control of the // kernel. child := p.NewPersistentInode(ctx, embedder, fs.StableAttr{}) // And add it p.AddChild(base, child, true) } } // This demonstrates how to build a file system in memory. The // read/write logic for the file is provided by the MemRegularFile type. func Example() { // This is where we'll mount the FS mntDir, _ := ioutil.TempDir("", "") root := &inMemoryFS{} server, err := fs.Mount(mntDir, root, &fs.Options{ MountOptions: fuse.MountOptions{Debug: true}, }) if err != nil { log.Panic(err) } log.Printf("Mounted on %s", mntDir) log.Printf("Unmount by calling 'fusermount -u %s'", mntDir) // Wait until unmount before exiting server.Wait() } go-fuse-2.0.3/fs/inode.go000066400000000000000000000470171364171671200151370ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "context" "fmt" "log" "math/rand" "sort" "strings" "sync" "syscall" "unsafe" "github.com/hanwen/go-fuse/v2/fuse" ) type parentData struct { name string parent *Inode } // StableAttr holds immutable attributes of a object in the filesystem. type StableAttr struct { // Each Inode has a type, which does not change over the // lifetime of the inode, for example fuse.S_IFDIR. The default (0) // is interpreted as S_IFREG (regular file). Mode uint32 // The inode number must be unique among the currently live // objects in the file system. It is used to communicate to // the kernel about this file object. The values uint64(-1), // and 1 are reserved. When using Ino==0, a unique, sequential // number is assigned (starting at 2^63 by default) on Inode creation. Ino uint64 // When reusing a previously used inode number for a new // object, the new object must have a different Gen // number. This is irrelevant if the FS is not exported over // NFS Gen uint64 } // Reserved returns if the StableAttr is using reserved Inode numbers. func (i *StableAttr) Reserved() bool { return i.Ino == 1 || i.Ino == ^uint64(0) } // Inode is a node in VFS tree. Inodes are one-to-one mapped to // Operations instances, which is the extension interface for file // systems. One can create fully-formed trees of Inodes ahead of time // by creating "persistent" Inodes. // // The Inode struct contains a lock, so it should not be // copied. Inodes should be obtained by calling Inode.NewInode() or // Inode.NewPersistentInode(). type Inode struct { stableAttr StableAttr ops InodeEmbedder bridge *rawBridge // Following data is mutable. // file handles. // protected by bridge.mu openFiles []uint32 // mu protects the following mutable fields. When locking // multiple Inodes, locks must be acquired using // lockNodes/unlockNodes mu sync.Mutex // persistent indicates that this node should not be removed // from the tree, even if there are no live references. This // must be set on creation, and can only be changed to false // by calling removeRef. // When you change this, you MUST increment changeCounter. persistent bool // changeCounter increments every time the mutable state // (lookupCount, persistent, children, parents) protected by // mu is modified. // // This is used in places where we have to relock inode into inode // group lock, and after locking the group we have to check if inode // did not changed, and if it changed - retry the operation. changeCounter uint32 // Number of kernel refs to this node. // When you change this, you MUST increment changeCounter. lookupCount uint64 // Children of this Inode. // When you change this, you MUST increment changeCounter. children map[string]*Inode // Parents of this Inode. Can be more than one due to hard links. // When you change this, you MUST increment changeCounter. parents map[parentData]struct{} } func (n *Inode) IsDir() bool { return n.stableAttr.Mode&syscall.S_IFDIR != 0 } func (n *Inode) embed() *Inode { return n } func (n *Inode) EmbeddedInode() *Inode { return n } func initInode(n *Inode, ops InodeEmbedder, attr StableAttr, bridge *rawBridge, persistent bool) { n.ops = ops n.stableAttr = attr n.bridge = bridge n.persistent = persistent n.parents = make(map[parentData]struct{}) if attr.Mode == fuse.S_IFDIR { n.children = make(map[string]*Inode) } } // Set node ID and mode in EntryOut func (n *Inode) setEntryOut(out *fuse.EntryOut) { out.NodeId = n.stableAttr.Ino out.Ino = n.stableAttr.Ino out.Mode = (out.Attr.Mode & 07777) | n.stableAttr.Mode } // StableAttr returns the (Ino, Gen) tuple for this node. func (n *Inode) StableAttr() StableAttr { return n.stableAttr } // Mode returns the filetype func (n *Inode) Mode() uint32 { return n.stableAttr.Mode } // Returns the root of the tree func (n *Inode) Root() *Inode { return n.bridge.root } // Returns whether this is the root of the tree func (n *Inode) IsRoot() bool { return n.bridge.root == n } func modeStr(m uint32) string { return map[uint32]string{ syscall.S_IFREG: "reg", syscall.S_IFLNK: "lnk", syscall.S_IFDIR: "dir", syscall.S_IFSOCK: "soc", syscall.S_IFIFO: "pip", syscall.S_IFCHR: "chr", syscall.S_IFBLK: "blk", }[m] } // debugString is used for debugging. Racy. func (n *Inode) String() string { n.mu.Lock() defer n.mu.Unlock() var ss []string for nm, ch := range n.children { ss = append(ss, fmt.Sprintf("%q=i%d[%s]", nm, ch.stableAttr.Ino, modeStr(ch.stableAttr.Mode))) } return fmt.Sprintf("i%d (%s): %s", n.stableAttr.Ino, modeStr(n.stableAttr.Mode), strings.Join(ss, ",")) } // sortNodes rearranges inode group in consistent order. // // The nodes are ordered by their in-RAM address, which gives consistency // property: for any A and B inodes, sortNodes will either always order A < B, // or always order A > B. // // See lockNodes where this property is used to avoid deadlock when taking // locks on inode group. func sortNodes(ns []*Inode) { sort.Slice(ns, func(i, j int) bool { return nodeLess(ns[i], ns[j]) }) } func nodeLess(a, b *Inode) bool { return uintptr(unsafe.Pointer(a)) < uintptr(unsafe.Pointer(b)) } // lockNodes locks group of inodes. // // It always lock the inodes in the same order - to avoid deadlocks. // It also avoids locking an inode more than once, if it was specified multiple times. // An example when an inode might be given multiple times is if dir/a and dir/b // are hardlinked to the same inode and the caller needs to take locks on dir children. func lockNodes(ns ...*Inode) { sortNodes(ns) // The default value nil prevents trying to lock nil nodes. var nprev *Inode for _, n := range ns { if n != nprev { n.mu.Lock() nprev = n } } } // lockNode2 locks a and b in order consistent with lockNodes. func lockNode2(a, b *Inode) { if a == b { a.mu.Lock() } else if nodeLess(a, b) { a.mu.Lock() b.mu.Lock() } else { b.mu.Lock() a.mu.Lock() } } // unlockNode2 unlocks a and b func unlockNode2(a, b *Inode) { if a == b { a.mu.Unlock() } else { a.mu.Unlock() b.mu.Unlock() } } // unlockNodes releases locks taken by lockNodes. func unlockNodes(ns ...*Inode) { // we don't need to unlock in the same order that was used in lockNodes. // however it still helps to have nodes sorted to avoid duplicates. sortNodes(ns) var nprev *Inode for _, n := range ns { if n != nprev { n.mu.Unlock() nprev = n } } } // Forgotten returns true if the kernel holds no references to this // inode. This can be used for background cleanup tasks, since the // kernel has no way of reviving forgotten nodes by its own // initiative. func (n *Inode) Forgotten() bool { n.mu.Lock() defer n.mu.Unlock() return n.lookupCount == 0 && len(n.parents) == 0 && !n.persistent } // Operations returns the object implementing the file system // operations. func (n *Inode) Operations() InodeEmbedder { return n.ops } // Path returns a path string to the inode relative to `root`. // Pass nil to walk the hierarchy as far up as possible. // // If you set `root`, Path() warns if it finds an orphaned Inode, i.e. // if it does not end up at `root` after walking the hierarchy. func (n *Inode) Path(root *Inode) string { var segments []string p := n for p != nil && p != root { var pd parentData // We don't try to take all locks at the same time, because // the caller won't use the "path" string under lock anyway. found := false p.mu.Lock() // Select an arbitrary parent for pd = range p.parents { found = true break } p.mu.Unlock() if found == false { p = nil break } if pd.parent == nil { break } segments = append(segments, pd.name) p = pd.parent } if root != nil && root != p { deletedPlaceholder := fmt.Sprintf(".go-fuse.%d/deleted", rand.Uint64()) n.bridge.logf("warning: Inode.Path: inode i%d is orphaned, replacing segment with %q", n.stableAttr.Ino, deletedPlaceholder) // NOSUBMIT - should replace rather than append? segments = append(segments, deletedPlaceholder) } i := 0 j := len(segments) - 1 for i < j { segments[i], segments[j] = segments[j], segments[i] i++ j-- } path := strings.Join(segments, "/") return path } // setEntry does `iparent[name] = ichild` linking. // // setEntry must not be called simultaneously for any of iparent or ichild. // This, for example could be satisfied if both iparent and ichild are locked, // but it could be also valid if only iparent is locked and ichild was just // created and only one goroutine keeps referencing it. func (iparent *Inode) setEntry(name string, ichild *Inode) { ichild.parents[parentData{name, iparent}] = struct{}{} iparent.children[name] = ichild ichild.changeCounter++ iparent.changeCounter++ } // NewPersistentInode returns an Inode whose lifetime is not in // control of the kernel. // // When the kernel is short on memory, it will forget cached file // system information (directory entries and inode metadata). This is // announced with FORGET messages. There are no guarantees if or when // this happens. When it happens, these are handled transparently by // go-fuse: all Inodes created with NewInode are released // automatically. NewPersistentInode creates inodes that go-fuse keeps // in memory, even if the kernel is not interested in them. This is // convenient for building static trees up-front. func (n *Inode) NewPersistentInode(ctx context.Context, node InodeEmbedder, id StableAttr) *Inode { return n.newInode(ctx, node, id, true) } // ForgetPersistent manually marks the node as no longer important. If // it has no children, and if the kernel as no references, the nodes // gets removed from the tree. func (n *Inode) ForgetPersistent() { n.removeRef(0, true) } // NewInode returns an inode for the given InodeEmbedder. The mode // should be standard mode argument (eg. S_IFDIR). The inode number in // id.Ino argument is used to implement hard-links. If it is given, // and another node with the same ID is known, that will node will be // returned, and the passed-in `node` is ignored. func (n *Inode) NewInode(ctx context.Context, node InodeEmbedder, id StableAttr) *Inode { return n.newInode(ctx, node, id, false) } func (n *Inode) newInode(ctx context.Context, ops InodeEmbedder, id StableAttr, persistent bool) *Inode { return n.bridge.newInode(ctx, ops, id, persistent) } // removeRef decreases references. Returns if this operation caused // the node to be forgotten (for kernel references), and whether it is // live (ie. was not dropped from the tree) func (n *Inode) removeRef(nlookup uint64, dropPersistence bool) (forgotten bool, live bool) { var lockme []*Inode var parents []parentData n.mu.Lock() if nlookup > 0 && dropPersistence { log.Panic("only one allowed") } else if nlookup > 0 { n.lookupCount -= nlookup n.changeCounter++ } else if dropPersistence && n.persistent { n.persistent = false n.changeCounter++ } retry: for { lockme = append(lockme[:0], n) parents = parents[:0] nChange := n.changeCounter live = n.lookupCount > 0 || len(n.children) > 0 || n.persistent forgotten = n.lookupCount == 0 for p := range n.parents { parents = append(parents, p) lockme = append(lockme, p.parent) } n.mu.Unlock() if live { return forgotten, live } lockNodes(lockme...) if n.changeCounter != nChange { unlockNodes(lockme...) // could avoid unlocking and relocking n here. n.mu.Lock() continue retry } for _, p := range parents { delete(p.parent.children, p.name) p.parent.changeCounter++ } n.parents = map[parentData]struct{}{} n.changeCounter++ if n.lookupCount != 0 { panic("lookupCount changed") } n.bridge.mu.Lock() delete(n.bridge.nodes, n.stableAttr.Ino) n.bridge.mu.Unlock() unlockNodes(lockme...) break } for _, p := range lockme { if p != n { p.removeRef(0, false) } } return forgotten, false } // GetChild returns a child node with the given name, or nil if the // directory has no child by that name. func (n *Inode) GetChild(name string) *Inode { n.mu.Lock() defer n.mu.Unlock() return n.children[name] } // AddChild adds a child to this node. If overwrite is false, fail if // the destination already exists. func (n *Inode) AddChild(name string, ch *Inode, overwrite bool) (success bool) { if len(name) == 0 { log.Panic("empty name for inode") } retry: for { lockNode2(n, ch) prev, ok := n.children[name] parentCounter := n.changeCounter if !ok { n.children[name] = ch ch.parents[parentData{name, n}] = struct{}{} n.changeCounter++ ch.changeCounter++ unlockNode2(n, ch) return true } unlockNode2(n, ch) if !overwrite { return false } lockme := [3]*Inode{n, ch, prev} lockNodes(lockme[:]...) if parentCounter != n.changeCounter { unlockNodes(lockme[:]...) continue retry } delete(prev.parents, parentData{name, n}) n.children[name] = ch ch.parents[parentData{name, n}] = struct{}{} n.changeCounter++ ch.changeCounter++ prev.changeCounter++ unlockNodes(lockme[:]...) return true } } // Children returns the list of children of this directory Inode. func (n *Inode) Children() map[string]*Inode { n.mu.Lock() defer n.mu.Unlock() r := make(map[string]*Inode, len(n.children)) for k, v := range n.children { r[k] = v } return r } // Parents returns a parent of this Inode, or nil if this Inode is // deleted or is the root func (n *Inode) Parent() (string, *Inode) { n.mu.Lock() defer n.mu.Unlock() for k := range n.parents { return k.name, k.parent } return "", nil } // RmAllChildren recursively drops a tree, forgetting all persistent // nodes. func (n *Inode) RmAllChildren() { for { chs := n.Children() if len(chs) == 0 { break } for nm, ch := range chs { ch.RmAllChildren() n.RmChild(nm) } } n.removeRef(0, true) } // RmChild removes multiple children. Returns whether the removal // succeeded and whether the node is still live afterward. The removal // is transactional: it only succeeds if all names are children, and // if they all were removed successfully. If the removal was // successful, and there are no children left, the node may be removed // from the FS tree. In that case, RmChild returns live==false. func (n *Inode) RmChild(names ...string) (success, live bool) { var lockme []*Inode retry: for { n.mu.Lock() lockme = append(lockme[:0], n) nChange := n.changeCounter for _, nm := range names { ch := n.children[nm] if ch == nil { n.mu.Unlock() return false, true } lockme = append(lockme, ch) } n.mu.Unlock() lockNodes(lockme...) if n.changeCounter != nChange { unlockNodes(lockme...) // could avoid unlocking and relocking n here. n.mu.Lock() continue retry } for _, nm := range names { ch := n.children[nm] delete(n.children, nm) delete(ch.parents, parentData{nm, n}) ch.changeCounter++ } n.changeCounter++ live = n.lookupCount > 0 || len(n.children) > 0 || n.persistent unlockNodes(lockme...) // removal successful break } if !live { _, live := n.removeRef(0, false) return true, live } return true, true } // MvChild executes a rename. If overwrite is set, a child at the // destination will be overwritten, should it exist. It returns false // if 'overwrite' is false, and the destination exists. func (n *Inode) MvChild(old string, newParent *Inode, newName string, overwrite bool) bool { if len(newName) == 0 { log.Panicf("empty newName for MvChild") } retry: for { lockNode2(n, newParent) counter1 := n.changeCounter counter2 := newParent.changeCounter oldChild := n.children[old] destChild := newParent.children[newName] unlockNode2(n, newParent) if destChild != nil && !overwrite { return false } lockNodes(n, newParent, oldChild, destChild) if counter2 != newParent.changeCounter || counter1 != n.changeCounter { unlockNodes(n, newParent, oldChild, destChild) continue retry } if oldChild != nil { delete(n.children, old) delete(oldChild.parents, parentData{old, n}) n.changeCounter++ oldChild.changeCounter++ } if destChild != nil { // This can cause the child to be slated for // removal; see below delete(newParent.children, newName) delete(destChild.parents, parentData{newName, newParent}) destChild.changeCounter++ newParent.changeCounter++ } if oldChild != nil { newParent.children[newName] = oldChild newParent.changeCounter++ oldChild.parents[parentData{newName, newParent}] = struct{}{} oldChild.changeCounter++ } unlockNodes(n, newParent, oldChild, destChild) if destChild != nil { destChild.removeRef(0, false) } return true } } // ExchangeChild swaps the entries at (n, oldName) and (newParent, // newName). func (n *Inode) ExchangeChild(oldName string, newParent *Inode, newName string) { oldParent := n retry: for { lockNode2(oldParent, newParent) counter1 := oldParent.changeCounter counter2 := newParent.changeCounter oldChild := oldParent.children[oldName] destChild := newParent.children[newName] unlockNode2(oldParent, newParent) if destChild == oldChild { return } lockNodes(oldParent, newParent, oldChild, destChild) if counter2 != newParent.changeCounter || counter1 != oldParent.changeCounter { unlockNodes(oldParent, newParent, oldChild, destChild) continue retry } // Detach if oldChild != nil { delete(oldParent.children, oldName) delete(oldChild.parents, parentData{oldName, oldParent}) oldParent.changeCounter++ oldChild.changeCounter++ } if destChild != nil { delete(newParent.children, newName) delete(destChild.parents, parentData{newName, newParent}) destChild.changeCounter++ newParent.changeCounter++ } // Attach if oldChild != nil { newParent.children[newName] = oldChild newParent.changeCounter++ oldChild.parents[parentData{newName, newParent}] = struct{}{} oldChild.changeCounter++ } if destChild != nil { oldParent.children[oldName] = oldChild oldParent.changeCounter++ destChild.parents[parentData{oldName, oldParent}] = struct{}{} destChild.changeCounter++ } unlockNodes(oldParent, newParent, oldChild, destChild) return } } // NotifyEntry notifies the kernel that data for a (directory, name) // tuple should be invalidated. On next access, a LOOKUP operation // will be started. func (n *Inode) NotifyEntry(name string) syscall.Errno { status := n.bridge.server.EntryNotify(n.stableAttr.Ino, name) return syscall.Errno(status) } // NotifyDelete notifies the kernel that the given inode was removed // from this directory as entry under the given name. It is equivalent // to NotifyEntry, but also sends an event to inotify watchers. func (n *Inode) NotifyDelete(name string, child *Inode) syscall.Errno { // XXX arg ordering? return syscall.Errno(n.bridge.server.DeleteNotify(n.stableAttr.Ino, child.stableAttr.Ino, name)) } // NotifyContent notifies the kernel that content under the given // inode should be flushed from buffers. func (n *Inode) NotifyContent(off, sz int64) syscall.Errno { // XXX how does this work for directories? return syscall.Errno(n.bridge.server.InodeNotify(n.stableAttr.Ino, off, sz)) } // WriteCache stores data in the kernel cache. func (n *Inode) WriteCache(offset int64, data []byte) syscall.Errno { return syscall.Errno(n.bridge.server.InodeNotifyStoreCache(n.stableAttr.Ino, offset, data)) } // ReadCache reads data from the kernel cache. func (n *Inode) ReadCache(offset int64, dest []byte) (count int, errno syscall.Errno) { c, s := n.bridge.server.InodeRetrieveCache(n.stableAttr.Ino, offset, dest) return c, syscall.Errno(s) } go-fuse-2.0.3/fs/interrupt_test.go000066400000000000000000000032411364171671200171230ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "context" "os/exec" "syscall" "testing" "time" "github.com/hanwen/go-fuse/v2/fuse" ) type interruptRoot struct { Inode child interruptOps } var _ = (NodeLookuper)((*interruptRoot)(nil)) func (r *interruptRoot) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) { if name != "file" { return nil, syscall.ENOENT } ch := r.Inode.NewInode(ctx, &r.child, StableAttr{ Ino: 2, Gen: 1}) return ch, OK } type interruptOps struct { Inode interrupted bool } var _ = (NodeOpener)((*interruptOps)(nil)) func (o *interruptOps) Open(ctx context.Context, flags uint32) (FileHandle, uint32, syscall.Errno) { select { case <-time.After(100 * time.Millisecond): return nil, 0, syscall.EIO case <-ctx.Done(): o.interrupted = true return nil, 0, syscall.EINTR } } // This currently doesn't test functionality, but is useful to investigate how // INTERRUPT opcodes are handled. func TestInterrupt(t *testing.T) { root := &interruptRoot{} oneSec := time.Second mntDir, _, clean := testMount(t, root, &Options{ EntryTimeout: &oneSec, AttrTimeout: &oneSec, }) defer func() { if clean != nil { clean() } }() cmd := exec.Command("cat", mntDir+"/file") if err := cmd.Start(); err != nil { t.Fatalf("run %v: %v", cmd, err) } time.Sleep(10 * time.Millisecond) if err := cmd.Process.Kill(); err != nil { t.Errorf("Kill: %v", err) } clean() clean = nil if !root.child.interrupted { t.Errorf("open request was not interrupted") } } go-fuse-2.0.3/fs/loopback.go000066400000000000000000000237321364171671200156310ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "context" "os" "path/filepath" "syscall" "github.com/hanwen/go-fuse/v2/fuse" ) type loopbackRoot struct { loopbackNode rootPath string rootDev uint64 } type loopbackNode struct { Inode } var _ = (NodeStatfser)((*loopbackNode)(nil)) var _ = (NodeStatfser)((*loopbackNode)(nil)) var _ = (NodeGetattrer)((*loopbackNode)(nil)) var _ = (NodeGetxattrer)((*loopbackNode)(nil)) var _ = (NodeSetxattrer)((*loopbackNode)(nil)) var _ = (NodeRemovexattrer)((*loopbackNode)(nil)) var _ = (NodeListxattrer)((*loopbackNode)(nil)) var _ = (NodeReadlinker)((*loopbackNode)(nil)) var _ = (NodeOpener)((*loopbackNode)(nil)) var _ = (NodeCopyFileRanger)((*loopbackNode)(nil)) var _ = (NodeLookuper)((*loopbackNode)(nil)) var _ = (NodeOpendirer)((*loopbackNode)(nil)) var _ = (NodeReaddirer)((*loopbackNode)(nil)) var _ = (NodeMkdirer)((*loopbackNode)(nil)) var _ = (NodeMknoder)((*loopbackNode)(nil)) var _ = (NodeLinker)((*loopbackNode)(nil)) var _ = (NodeSymlinker)((*loopbackNode)(nil)) var _ = (NodeUnlinker)((*loopbackNode)(nil)) var _ = (NodeRmdirer)((*loopbackNode)(nil)) var _ = (NodeRenamer)((*loopbackNode)(nil)) func (n *loopbackNode) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno { s := syscall.Statfs_t{} err := syscall.Statfs(n.path(), &s) if err != nil { return ToErrno(err) } out.FromStatfsT(&s) return OK } func (r *loopbackRoot) Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno { st := syscall.Stat_t{} err := syscall.Stat(r.rootPath, &st) if err != nil { return ToErrno(err) } out.FromStat(&st) return OK } func (n *loopbackNode) root() *loopbackRoot { return n.Root().Operations().(*loopbackRoot) } func (n *loopbackNode) path() string { path := n.Path(n.Root()) return filepath.Join(n.root().rootPath, path) } func (n *loopbackNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) { p := filepath.Join(n.path(), name) st := syscall.Stat_t{} err := syscall.Lstat(p, &st) if err != nil { return nil, ToErrno(err) } out.Attr.FromStat(&st) node := &loopbackNode{} ch := n.NewInode(ctx, node, n.root().idFromStat(&st)) return ch, 0 } // preserveOwner sets uid and gid of `path` according to the caller information // in `ctx`. func (n *loopbackNode) preserveOwner(ctx context.Context, path string) error { if os.Getuid() != 0 { return nil } caller, ok := fuse.FromContext(ctx) if !ok { return nil } return syscall.Lchown(path, int(caller.Uid), int(caller.Gid)) } func (n *loopbackNode) Mknod(ctx context.Context, name string, mode, rdev uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) { p := filepath.Join(n.path(), name) err := syscall.Mknod(p, mode, int(rdev)) if err != nil { return nil, ToErrno(err) } n.preserveOwner(ctx, p) st := syscall.Stat_t{} if err := syscall.Lstat(p, &st); err != nil { syscall.Rmdir(p) return nil, ToErrno(err) } out.Attr.FromStat(&st) node := &loopbackNode{} ch := n.NewInode(ctx, node, n.root().idFromStat(&st)) return ch, 0 } func (n *loopbackNode) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) { p := filepath.Join(n.path(), name) err := os.Mkdir(p, os.FileMode(mode)) if err != nil { return nil, ToErrno(err) } n.preserveOwner(ctx, p) st := syscall.Stat_t{} if err := syscall.Lstat(p, &st); err != nil { syscall.Rmdir(p) return nil, ToErrno(err) } out.Attr.FromStat(&st) node := &loopbackNode{} ch := n.NewInode(ctx, node, n.root().idFromStat(&st)) return ch, 0 } func (n *loopbackNode) Rmdir(ctx context.Context, name string) syscall.Errno { p := filepath.Join(n.path(), name) err := syscall.Rmdir(p) return ToErrno(err) } func (n *loopbackNode) Unlink(ctx context.Context, name string) syscall.Errno { p := filepath.Join(n.path(), name) err := syscall.Unlink(p) return ToErrno(err) } func toLoopbackNode(op InodeEmbedder) *loopbackNode { if r, ok := op.(*loopbackRoot); ok { return &r.loopbackNode } return op.(*loopbackNode) } func (n *loopbackNode) Rename(ctx context.Context, name string, newParent InodeEmbedder, newName string, flags uint32) syscall.Errno { newParentLoopback := toLoopbackNode(newParent) if flags&RENAME_EXCHANGE != 0 { return n.renameExchange(name, newParentLoopback, newName) } p1 := filepath.Join(n.path(), name) p2 := filepath.Join(newParentLoopback.path(), newName) err := syscall.Rename(p1, p2) return ToErrno(err) } func (r *loopbackRoot) idFromStat(st *syscall.Stat_t) StableAttr { // We compose an inode number by the underlying inode, and // mixing in the device number. In traditional filesystems, // the inode numbers are small. The device numbers are also // small (typically 16 bit). Finally, we mask out the root // device number of the root, so a loopback FS that does not // encompass multiple mounts will reflect the inode numbers of // the underlying filesystem swapped := (uint64(st.Dev) << 32) | (uint64(st.Dev) >> 32) swappedRootDev := (r.rootDev << 32) | (r.rootDev >> 32) return StableAttr{ Mode: uint32(st.Mode), Gen: 1, // This should work well for traditional backing FSes, // not so much for other go-fuse FS-es Ino: (swapped ^ swappedRootDev) ^ st.Ino, } } var _ = (NodeCreater)((*loopbackNode)(nil)) func (n *loopbackNode) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (inode *Inode, fh FileHandle, fuseFlags uint32, errno syscall.Errno) { p := filepath.Join(n.path(), name) flags = flags &^ syscall.O_APPEND fd, err := syscall.Open(p, int(flags)|os.O_CREATE, mode) if err != nil { return nil, nil, 0, ToErrno(err) } n.preserveOwner(ctx, p) st := syscall.Stat_t{} if err := syscall.Fstat(fd, &st); err != nil { syscall.Close(fd) return nil, nil, 0, ToErrno(err) } node := &loopbackNode{} ch := n.NewInode(ctx, node, n.root().idFromStat(&st)) lf := NewLoopbackFile(fd) out.FromStat(&st) return ch, lf, 0, 0 } func (n *loopbackNode) Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) { p := filepath.Join(n.path(), name) err := syscall.Symlink(target, p) if err != nil { return nil, ToErrno(err) } n.preserveOwner(ctx, p) st := syscall.Stat_t{} if err := syscall.Lstat(p, &st); err != nil { syscall.Unlink(p) return nil, ToErrno(err) } node := &loopbackNode{} ch := n.NewInode(ctx, node, n.root().idFromStat(&st)) out.Attr.FromStat(&st) return ch, 0 } func (n *loopbackNode) Link(ctx context.Context, target InodeEmbedder, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) { p := filepath.Join(n.path(), name) targetNode := toLoopbackNode(target) err := syscall.Link(targetNode.path(), p) if err != nil { return nil, ToErrno(err) } st := syscall.Stat_t{} if err := syscall.Lstat(p, &st); err != nil { syscall.Unlink(p) return nil, ToErrno(err) } node := &loopbackNode{} ch := n.NewInode(ctx, node, n.root().idFromStat(&st)) out.Attr.FromStat(&st) return ch, 0 } func (n *loopbackNode) Readlink(ctx context.Context) ([]byte, syscall.Errno) { p := n.path() for l := 256; ; l *= 2 { buf := make([]byte, l) sz, err := syscall.Readlink(p, buf) if err != nil { return nil, ToErrno(err) } if sz < len(buf) { return buf[:sz], 0 } } } func (n *loopbackNode) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) { flags = flags &^ syscall.O_APPEND p := n.path() f, err := syscall.Open(p, int(flags), 0) if err != nil { return nil, 0, ToErrno(err) } lf := NewLoopbackFile(f) return lf, 0, 0 } func (n *loopbackNode) Opendir(ctx context.Context) syscall.Errno { fd, err := syscall.Open(n.path(), syscall.O_DIRECTORY, 0755) if err != nil { return ToErrno(err) } syscall.Close(fd) return OK } func (n *loopbackNode) Readdir(ctx context.Context) (DirStream, syscall.Errno) { return NewLoopbackDirStream(n.path()) } func (n *loopbackNode) Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno { if f != nil { return f.(FileGetattrer).Getattr(ctx, out) } p := n.path() var err error st := syscall.Stat_t{} err = syscall.Lstat(p, &st) if err != nil { return ToErrno(err) } out.FromStat(&st) return OK } var _ = (NodeSetattrer)((*loopbackNode)(nil)) func (n *loopbackNode) Setattr(ctx context.Context, f FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno { p := n.path() fsa, ok := f.(FileSetattrer) if ok && fsa != nil { fsa.Setattr(ctx, in, out) } else { if m, ok := in.GetMode(); ok { if err := syscall.Chmod(p, m); err != nil { return ToErrno(err) } } uid, uok := in.GetUID() gid, gok := in.GetGID() if uok || gok { suid := -1 sgid := -1 if uok { suid = int(uid) } if gok { sgid = int(gid) } if err := syscall.Chown(p, suid, sgid); err != nil { return ToErrno(err) } } mtime, mok := in.GetMTime() atime, aok := in.GetATime() if mok || aok { ap := &atime mp := &mtime if !aok { ap = nil } if !mok { mp = nil } var ts [2]syscall.Timespec ts[0] = fuse.UtimeToTimespec(ap) ts[1] = fuse.UtimeToTimespec(mp) if err := syscall.UtimesNano(p, ts[:]); err != nil { return ToErrno(err) } } if sz, ok := in.GetSize(); ok { if err := syscall.Truncate(p, int64(sz)); err != nil { return ToErrno(err) } } } fga, ok := f.(FileGetattrer) if ok && fga != nil { fga.Getattr(ctx, out) } else { st := syscall.Stat_t{} err := syscall.Lstat(p, &st) if err != nil { return ToErrno(err) } out.FromStat(&st) } return OK } // NewLoopbackRoot returns a root node for a loopback file system whose // root is at the given root. This node implements all NodeXxxxer // operations available. func NewLoopbackRoot(root string) (InodeEmbedder, error) { var st syscall.Stat_t err := syscall.Stat(root, &st) if err != nil { return nil, err } n := &loopbackRoot{ rootPath: root, rootDev: uint64(st.Dev), } return n, nil } go-fuse-2.0.3/fs/loopback_darwin.go000066400000000000000000000071261364171671200171740ustar00rootroot00000000000000// +build darwin // Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "context" "syscall" "time" "unsafe" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/internal/utimens" ) func (n *loopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) { return 0, syscall.ENOSYS } func (n *loopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno { return syscall.ENOSYS } func (n *loopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno { return syscall.ENOSYS } func (n *loopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) { return 0, syscall.ENOSYS } func (n *loopbackNode) renameExchange(name string, newparent *loopbackNode, newName string) syscall.Errno { return syscall.ENOSYS } func (f *loopbackFile) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) syscall.Errno { // 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)) _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, uintptr(f.fd), uintptr(syscall.F_PREALLOCATE), uintptr(unsafe.Pointer(&k))) return errno } // 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 } // MacOS before High Sierra lacks utimensat() and UTIME_OMIT. // We emulate using utimes() and extra Getattr() calls. func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno { var attr fuse.AttrOut if a == nil || m == nil { errno := f.Getattr(context.Background(), &attr) if errno != 0 { return errno } } tv := utimens.Fill(a, m, &attr.Attr) err := syscall.Futimes(int(f.fd), tv) return ToErrno(err) } func (n *loopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle, offIn uint64, out *Inode, fhOut FileHandle, offOut uint64, len uint64, flags uint64) (uint32, syscall.Errno) { return 0, syscall.ENOSYS } go-fuse-2.0.3/fs/loopback_linux.go000066400000000000000000000046451364171671200170520ustar00rootroot00000000000000// +build linux // Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "context" "syscall" "golang.org/x/sys/unix" ) func (n *loopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) { sz, err := unix.Lgetxattr(n.path(), attr, dest) return uint32(sz), ToErrno(err) } func (n *loopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno { err := unix.Lsetxattr(n.path(), attr, data, int(flags)) return ToErrno(err) } func (n *loopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno { err := unix.Lremovexattr(n.path(), attr) return ToErrno(err) } func (n *loopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) { sz, err := unix.Llistxattr(n.path(), dest) return uint32(sz), ToErrno(err) } func (n *loopbackNode) renameExchange(name string, newparent *loopbackNode, newName string) syscall.Errno { fd1, err := syscall.Open(n.path(), syscall.O_DIRECTORY, 0) if err != nil { return ToErrno(err) } defer syscall.Close(fd1) fd2, err := syscall.Open(newparent.path(), syscall.O_DIRECTORY, 0) defer syscall.Close(fd2) if err != nil { return ToErrno(err) } var st syscall.Stat_t if err := syscall.Fstat(fd1, &st); err != nil { return ToErrno(err) } // Double check that nodes didn't change from under us. inode := &n.Inode if inode.Root() != inode && inode.StableAttr().Ino != n.root().idFromStat(&st).Ino { return syscall.EBUSY } if err := syscall.Fstat(fd2, &st); err != nil { return ToErrno(err) } newinode := &newparent.Inode if newinode.Root() != newinode && newinode.StableAttr().Ino != n.root().idFromStat(&st).Ino { return syscall.EBUSY } return ToErrno(unix.Renameat2(fd1, name, fd2, newName, unix.RENAME_EXCHANGE)) } func (n *loopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle, offIn uint64, out *Inode, fhOut FileHandle, offOut uint64, len uint64, flags uint64) (uint32, syscall.Errno) { lfIn, ok := fhIn.(*loopbackFile) if !ok { return 0, syscall.ENOTSUP } lfOut, ok := fhOut.(*loopbackFile) if !ok { return 0, syscall.ENOTSUP } signedOffIn := int64(offIn) signedOffOut := int64(offOut) count, err := unix.CopyFileRange(lfIn.fd, &signedOffIn, lfOut.fd, &signedOffOut, int(len), int(flags)) return uint32(count), ToErrno(err) } go-fuse-2.0.3/fs/loopback_linux_test.go000066400000000000000000000230221364171671200200770ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "bytes" "io/ioutil" "os" "reflect" "sync" "syscall" "testing" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/internal/testutil" "github.com/kylelemons/godebug/pretty" "golang.org/x/sys/unix" ) func TestRenameExchange(t *testing.T) { tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true}) defer tc.Clean() if err := os.Mkdir(tc.origDir+"/dir", 0755); err != nil { t.Fatalf("Mkdir: %v", err) } tc.writeOrig("file", "hello", 0644) tc.writeOrig("dir/file", "x", 0644) f1, err := syscall.Open(tc.mntDir+"/", syscall.O_DIRECTORY, 0) if err != nil { t.Fatalf("open 1: %v", err) } defer syscall.Close(f1) f2, err := syscall.Open(tc.mntDir+"/dir", syscall.O_DIRECTORY, 0) if err != nil { t.Fatalf("open 2: %v", err) } defer syscall.Close(f2) var before1, before2 unix.Stat_t if err := unix.Fstatat(f1, "file", &before1, 0); err != nil { t.Fatalf("Fstatat: %v", err) } if err := unix.Fstatat(f2, "file", &before2, 0); err != nil { t.Fatalf("Fstatat: %v", err) } if err := unix.Renameat2(f1, "file", f2, "file", unix.RENAME_EXCHANGE); err != nil { t.Errorf("rename EXCHANGE: %v", err) } var after1, after2 unix.Stat_t if err := unix.Fstatat(f1, "file", &after1, 0); err != nil { t.Fatalf("Fstatat: %v", err) } if err := unix.Fstatat(f2, "file", &after2, 0); err != nil { t.Fatalf("Fstatat: %v", err) } clearCtime := func(s *unix.Stat_t) { s.Ctim.Sec = 0 s.Ctim.Nsec = 0 } clearCtime(&after1) clearCtime(&after2) clearCtime(&before2) clearCtime(&before1) if diff := pretty.Compare(after1, before2); diff != "" { t.Errorf("after1, before2: %s", diff) } if !reflect.DeepEqual(after2, before1) { t.Errorf("after2, before1: %#v, %#v", after2, before1) } } func TestRenameNoOverwrite(t *testing.T) { tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true}) defer tc.Clean() if err := os.Mkdir(tc.origDir+"/dir", 0755); err != nil { t.Fatalf("Mkdir: %v", err) } tc.writeOrig("file", "hello", 0644) tc.writeOrig("dir/file", "x", 0644) f1, err := syscall.Open(tc.mntDir+"/", syscall.O_DIRECTORY, 0) if err != nil { t.Fatalf("open 1: %v", err) } defer syscall.Close(f1) f2, err := syscall.Open(tc.mntDir+"/dir", syscall.O_DIRECTORY, 0) if err != nil { t.Fatalf("open 2: %v", err) } defer syscall.Close(f2) if err := unix.Renameat2(f1, "file", f2, "file", unix.RENAME_NOREPLACE); err == nil { t.Errorf("rename NOREPLACE succeeded") } else if err != syscall.EEXIST { t.Errorf("got %v (%T) want EEXIST", err, err) } } func TestXAttr(t *testing.T) { tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true}) defer tc.Clean() tc.writeOrig("file", "", 0644) buf := make([]byte, 1024) attr := "user.xattrtest" if _, err := syscall.Getxattr(tc.mntDir+"/file", attr, buf); err == syscall.ENOTSUP { t.Skip("$TMP does not support xattrs. Rerun this test with a $TMPDIR override") } if _, err := syscall.Getxattr(tc.mntDir+"/file", attr, buf); err != syscall.ENODATA { t.Fatalf("got %v want ENOATTR", err) } value := []byte("value") if err := syscall.Setxattr(tc.mntDir+"/file", attr, value, 0); err != nil { t.Fatalf("Setxattr: %v", err) } sz, err := syscall.Listxattr(tc.mntDir+"/file", nil) if err != nil { t.Fatalf("Listxattr: %v", err) } buf = make([]byte, sz) if _, err := syscall.Listxattr(tc.mntDir+"/file", buf); err != nil { t.Fatalf("Listxattr: %v", err) } else { attributes := bytes.Split(buf[:sz], []byte{0}) found := false for _, a := range attributes { if string(a) == attr { found = true break } } if !found { t.Fatalf("Listxattr: %q (not found: %q", buf[:sz], attr) } } sz, err = syscall.Getxattr(tc.mntDir+"/file", attr, buf) if err != nil { t.Fatalf("Getxattr: %v", err) } if bytes.Compare(buf[:sz], value) != 0 { t.Fatalf("Getxattr got %q want %q", buf[:sz], value) } if err := syscall.Removexattr(tc.mntDir+"/file", attr); err != nil { t.Fatalf("Removexattr: %v", err) } if _, err := syscall.Getxattr(tc.mntDir+"/file", attr, buf); err != syscall.ENODATA { t.Fatalf("got %v want ENOATTR", err) } } // TestXAttrSymlink verifies that we did not forget to use Lgetxattr instead // of Getxattr. This test is Linux-specific because it depends on the behavoir // of the `security` namespace. // // On Linux, symlinks can not have xattrs in the `user` namespace, so we // try to read something from `security`. Writing would need root rights, // so don't even bother. See `man 7 xattr` for more info. func TestXAttrSymlink(t *testing.T) { tc := newTestCase(t, nil) defer tc.Clean() path := tc.mntDir + "/symlink" if err := syscall.Symlink("target/does/not/exist", path); err != nil { t.Fatal(err) } buf := make([]byte, 10) _, err := unix.Lgetxattr(path, "security.foo", buf) if err != unix.ENODATA { t.Errorf("want %d=ENODATA, got error %d=%q instead", unix.ENODATA, err, err) } } func TestCopyFileRange(t *testing.T) { tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true}) defer tc.Clean() if !tc.server.KernelSettings().SupportsVersion(7, 28) { t.Skip("need v7.28 for CopyFileRange") } tc.writeOrig("src", "01234567890123456789", 0644) tc.writeOrig("dst", "abcdefghijabcdefghij", 0644) f1, err := syscall.Open(tc.mntDir+"/src", syscall.O_RDONLY, 0) if err != nil { t.Fatalf("Open src: %v", err) } defer func() { // syscall.Close() is treacherous; because fds are // reused, a double close can cause serious havoc if f1 > 0 { syscall.Close(f1) } }() f2, err := syscall.Open(tc.mntDir+"/dst", syscall.O_RDWR, 0) if err != nil { t.Fatalf("Open dst: %v", err) } defer func() { if f2 > 0 { defer syscall.Close(f2) } }() srcOff := int64(5) dstOff := int64(7) if sz, err := unix.CopyFileRange(f1, &srcOff, f2, &dstOff, 3, 0); err != nil || sz != 3 { t.Fatalf("CopyFileRange: %d,%v", sz, err) } err = syscall.Close(f1) f1 = 0 if err != nil { t.Fatalf("Close src: %v", err) } err = syscall.Close(f2) f2 = 0 if err != nil { t.Fatalf("Close dst: %v", err) } c, err := ioutil.ReadFile(tc.mntDir + "/dst") if err != nil { t.Fatalf("ReadFile: %v", err) } want := "abcdefg567abcdefghij" got := string(c) if got != want { t.Errorf("got %q want %q", got, want) } } // Wait for a change in /proc/self/mounts. Efficient through the use of // unix.Poll(). func waitProcMountsChange() error { fd, err := syscall.Open("/proc/self/mounts", syscall.O_RDONLY, 0) defer syscall.Close(fd) if err != nil { return err } pollFds := []unix.PollFd{ { Fd: int32(fd), Events: unix.POLLPRI, }, } _, err = unix.Poll(pollFds, 1000) return err } // Wait until mountpoint "mnt" shows up /proc/self/mounts func waitMount(mnt string) error { for { err := waitProcMountsChange() if err != nil { return err } content, err := ioutil.ReadFile("/proc/self/mounts") if err != nil { return err } if bytes.Contains(content, []byte(mnt)) { return nil } } } // There is a hang that appears when enabling CAP_PARALLEL_DIROPS on Linux // 4.15.0: https://github.com/hanwen/go-fuse/issues/281 // The hang was originally triggered by gvfs-udisks2-volume-monitor. This // test emulates what gvfs-udisks2-volume-monitor does. func TestParallelDiropsHang(t *testing.T) { // We do NOT want to use newTestCase() here because we need to know the // mnt path before the filesystem is mounted dir := testutil.TempDir() orig := dir + "/orig" mnt := dir + "/mnt" if err := os.Mkdir(orig, 0755); err != nil { t.Fatal(err) } if err := os.Mkdir(mnt, 0755); err != nil { t.Fatal(err) } defer os.RemoveAll(dir) // Unblock the goroutines onces the mount shows up in /proc/self/mounts wait := make(chan struct{}) go func() { err := waitMount(mnt) if err != nil { t.Error(err) } // Unblock the goroutines regardless of an error. We don't want to hang // the test. close(wait) }() // gvfs-udisks2-volume-monitor hits the mount with three threads - we try to // emulate exactly what it does acc. to an strace log. var wg sync.WaitGroup wg.Add(3) // [pid 2117] lstat(".../mnt/autorun.inf", go func() { defer wg.Done() <-wait var st unix.Stat_t unix.Lstat(mnt+"/autorun.inf", &st) }() // [pid 2116] open(".../mnt/.xdg-volume-info", O_RDONLY go func() { defer wg.Done() <-wait syscall.Open(mnt+"/.xdg-volume-info", syscall.O_RDONLY, 0) }() // 25 times this: // [pid 1874] open(".../mnt", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC // [pid 1874] fstat(11, {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0 // [pid 1874] getdents(11, /* 2 entries */, 32768) = 48 // [pid 1874] close(11) = 0 go func() { defer wg.Done() <-wait for i := 1; i <= 25; i++ { f, err := os.Open(mnt) if err != nil { t.Error(err) return } _, err = f.Stat() if err != nil { t.Error(err) f.Close() return } _, err = f.Readdirnames(-1) if err != nil { t.Errorf("iteration %d: fd %d: %v", i, f.Fd(), err) return } f.Close() } }() loopbackRoot, err := NewLoopbackRoot(orig) if err != nil { t.Fatalf("NewLoopbackRoot(%s): %v\n", orig, err) } sec := time.Second opts := &Options{ AttrTimeout: &sec, EntryTimeout: &sec, } opts.Debug = testutil.VerboseTest() rawFS := NewNodeFS(loopbackRoot, opts) server, err := fuse.NewServer(rawFS, mnt, &opts.MountOptions) if err != nil { t.Fatal(err) } go server.Serve() wg.Wait() server.Unmount() } go-fuse-2.0.3/fs/mem.go000066400000000000000000000047531364171671200146170ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "context" "sync" "syscall" "github.com/hanwen/go-fuse/v2/fuse" ) // MemRegularFile is a filesystem node that holds a read-only data // slice in memory. type MemRegularFile struct { Inode mu sync.Mutex Data []byte Attr fuse.Attr } var _ = (NodeOpener)((*MemRegularFile)(nil)) var _ = (NodeReader)((*MemRegularFile)(nil)) var _ = (NodeWriter)((*MemRegularFile)(nil)) var _ = (NodeSetattrer)((*MemRegularFile)(nil)) var _ = (NodeFlusher)((*MemRegularFile)(nil)) func (f *MemRegularFile) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) { return nil, fuse.FOPEN_KEEP_CACHE, OK } func (f *MemRegularFile) Write(ctx context.Context, fh FileHandle, data []byte, off int64) (uint32, syscall.Errno) { f.mu.Lock() defer f.mu.Unlock() end := int64(len(data)) + off if int64(len(f.Data)) < end { n := make([]byte, end) copy(n, f.Data) f.Data = n } copy(f.Data[off:off+int64(len(data))], data) return uint32(len(data)), 0 } var _ = (NodeGetattrer)((*MemRegularFile)(nil)) func (f *MemRegularFile) Getattr(ctx context.Context, fh FileHandle, out *fuse.AttrOut) syscall.Errno { f.mu.Lock() defer f.mu.Unlock() out.Attr = f.Attr out.Attr.Size = uint64(len(f.Data)) return OK } func (f *MemRegularFile) Setattr(ctx context.Context, fh FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno { f.mu.Lock() defer f.mu.Unlock() if sz, ok := in.GetSize(); ok { f.Data = f.Data[:sz] } out.Attr = f.Attr out.Size = uint64(len(f.Data)) return OK } func (f *MemRegularFile) Flush(ctx context.Context, fh FileHandle) syscall.Errno { return 0 } func (f *MemRegularFile) Read(ctx context.Context, fh FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) { f.mu.Lock() defer f.mu.Unlock() end := int(off) + len(dest) if end > len(f.Data) { end = len(f.Data) } return fuse.ReadResultData(f.Data[off:end]), OK } // MemSymlink is an inode holding a symlink in memory. type MemSymlink struct { Inode Attr fuse.Attr Data []byte } var _ = (NodeReadlinker)((*MemSymlink)(nil)) func (l *MemSymlink) Readlink(ctx context.Context) ([]byte, syscall.Errno) { return l.Data, OK } var _ = (NodeGetattrer)((*MemSymlink)(nil)) func (l *MemSymlink) Getattr(ctx context.Context, fh FileHandle, out *fuse.AttrOut) syscall.Errno { out.Attr = l.Attr return OK } go-fuse-2.0.3/fs/mem_test.go000066400000000000000000000105131364171671200156450ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "bytes" "context" "io/ioutil" "math/rand" "os" "syscall" "testing" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/internal/testutil" ) func testMount(t *testing.T, root InodeEmbedder, opts *Options) (string, *fuse.Server, func()) { t.Helper() mntDir := testutil.TempDir() if opts == nil { opts = &Options{ FirstAutomaticIno: 1, } } opts.Debug = testutil.VerboseTest() server, err := Mount(mntDir, root, opts) if err != nil { t.Fatal(err) } return mntDir, server, func() { if err := server.Unmount(); err != nil { t.Fatalf("testMount: Unmount failed: %v", err) } if err := syscall.Rmdir(mntDir); err != nil { t.Errorf("testMount: Remove failed: %v", err) } } } func TestDefaultOwner(t *testing.T) { want := "hello" root := &Inode{} mntDir, _, clean := testMount(t, root, &Options{ FirstAutomaticIno: 1, OnAdd: func(ctx context.Context) { n := root.EmbeddedInode() ch := n.NewPersistentInode( ctx, &MemRegularFile{ Data: []byte(want), }, StableAttr{}) n.AddChild("file", ch, false) }, UID: 42, GID: 43, }) defer clean() var st syscall.Stat_t if err := syscall.Lstat(mntDir+"/file", &st); err != nil { t.Fatalf("Lstat: %v", err) } else if st.Uid != 42 || st.Gid != 43 { t.Fatalf("Got Lstat %d, %d want 42,43", st.Uid, st.Gid) } } func TestDataFile(t *testing.T) { want := "hello" root := &Inode{} mntDir, _, clean := testMount(t, root, &Options{ FirstAutomaticIno: 1, OnAdd: func(ctx context.Context) { n := root.EmbeddedInode() ch := n.NewPersistentInode( ctx, &MemRegularFile{ Data: []byte(want), Attr: fuse.Attr{ Mode: 0464, }, }, StableAttr{}) n.AddChild("file", ch, false) }, }) defer clean() var st syscall.Stat_t if err := syscall.Lstat(mntDir+"/file", &st); err != nil { t.Fatalf("Lstat: %v", err) } if want := uint32(syscall.S_IFREG | 0464); st.Mode != want { t.Errorf("got mode %o, want %o", st.Mode, want) } if st.Size != int64(len(want)) || st.Blocks != 8 || st.Blksize != 4096 { t.Errorf("got %#v, want sz = %d, 8 blocks, 4096 blocksize", st, len(want)) } fd, err := syscall.Open(mntDir+"/file", syscall.O_RDONLY, 0) if err != nil { t.Fatalf("Open: %v", err) } var buf [1024]byte n, err := syscall.Read(fd, buf[:]) if err != nil { t.Errorf("Read: %v", err) } if err := syscall.Close(fd); err != nil { t.Errorf("Close: %v", err) } got := string(buf[:n]) if got != want { t.Errorf("got %q want %q", got, want) } replace := []byte("replaced!") if err := ioutil.WriteFile(mntDir+"/file", replace, 0644); err != nil { t.Fatalf("WriteFile: %v", err) } if gotBytes, err := ioutil.ReadFile(mntDir + "/file"); err != nil { t.Fatalf("ReadFile: %v", err) } else if bytes.Compare(replace, gotBytes) != 0 { t.Fatalf("read: got %q want %q", gotBytes, replace) } } func TestDataFileLargeRead(t *testing.T) { root := &Inode{} data := make([]byte, 256*1024) rand.Read(data[:]) mntDir, _, clean := testMount(t, root, &Options{ FirstAutomaticIno: 1, OnAdd: func(ctx context.Context) { n := root.EmbeddedInode() ch := n.NewPersistentInode( ctx, &MemRegularFile{ Data: data, Attr: fuse.Attr{ Mode: 0464, }, }, StableAttr{}) n.AddChild("file", ch, false) }, }) defer clean() got, err := ioutil.ReadFile(mntDir + "/file") if err != nil { t.Fatalf("ReadFile: %v", err) } if !bytes.Equal(got, data) { t.Errorf("roundtrip read had change") } } type SymlinkerRoot struct { Inode } func (s *SymlinkerRoot) Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) { l := &MemSymlink{ Data: []byte(target), } ch := s.NewPersistentInode(ctx, l, StableAttr{Mode: syscall.S_IFLNK}) return ch, 0 } func TestDataSymlink(t *testing.T) { root := &SymlinkerRoot{} mntDir, _, clean := testMount(t, root, nil) defer clean() if err := syscall.Symlink("target", mntDir+"/link"); err != nil { t.Fatalf("Symlink: %v", err) } if got, err := os.Readlink(mntDir + "/link"); err != nil { t.Fatalf("Readlink: %v", err) } else if want := "target"; got != want { t.Errorf("Readlink: got %q want %q", got, want) } } go-fuse-2.0.3/fs/mount.go000066400000000000000000000020461364171671200151740ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "time" "github.com/hanwen/go-fuse/v2/fuse" ) // Mount mounts the given NodeFS on the directory, and starts serving // requests. This is a convenience wrapper around NewNodeFS and // fuse.NewServer. If nil is given as options, default settings are // applied, which are 1 second entry and attribute timeout. func Mount(dir string, root InodeEmbedder, options *Options) (*fuse.Server, error) { if options == nil { oneSec := time.Second options = &Options{ EntryTimeout: &oneSec, AttrTimeout: &oneSec, } } rawFS := NewNodeFS(root, options) server, err := fuse.NewServer(rawFS, dir, &options.MountOptions) if err != nil { return nil, err } go server.Serve() if err := server.WaitMount(); err != nil { // we don't shutdown the serve loop. If the mount does // not succeed, the loop won't work and exit. return nil, err } return server, nil } go-fuse-2.0.3/fs/randomtype_test.go000066400000000000000000000041561364171671200172570ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "context" "fmt" "hash/crc32" "os" "syscall" "testing" "github.com/hanwen/go-fuse/v2/fuse" ) type randomTypeTest struct { Inode } var _ = (NodeLookuper)((*randomTypeTest)(nil)) var _ = (NodeReaddirer)((*randomTypeTest)(nil)) // Lookup finds a dir. func (fn *randomTypeTest) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) { stable := StableAttr{ Mode: fuse.S_IFDIR, } // Override the file type on a pseudo-random subset of entries if crc32.ChecksumIEEE([]byte(name))%2 == 0 { stable.Mode = fuse.S_IFREG } childFN := &randomTypeTest{} child := fn.NewInode(ctx, childFN, stable) return child, syscall.F_OK } // Readdir will always return one child dir. func (fn *randomTypeTest) Readdir(ctx context.Context) (DirStream, syscall.Errno) { var entries []fuse.DirEntry for i := 0; i < 100; i++ { entries = append(entries, fuse.DirEntry{ Name: fmt.Sprintf("%d", i), Mode: fuse.S_IFDIR, }) } return NewListDirStream(entries), syscall.F_OK } // TestReaddirTypeFixup tests that DirEntryList.FixMode() works as expected. func TestReaddirTypeFixup(t *testing.T) { root := &randomTypeTest{} mntDir, _, clean := testMount(t, root, nil) defer clean() f, err := os.Open(mntDir) if err != nil { t.Fatalf("open: %v", err) } defer f.Close() // (Ab)use loopbackDirStream to call and parse getdents(2) on mntDir. // This makes the kernel call READDIRPLUS, which ultimately calls // randomTypeTest.Readdir() and randomTypeTest.Lookup() above. ds, errno := NewLoopbackDirStream(mntDir) if errno != 0 { t.Fatalf("readdir: %v", err) } defer ds.Close() for ds.HasNext() { e, err := ds.Next() if err != 0 { t.Errorf("Next: %d", err) } t.Logf("%q: mode=0x%x", e.Name, e.Mode) gotIsDir := (e.Mode & syscall.S_IFDIR) != 0 wantIsdir := (crc32.ChecksumIEEE([]byte(e.Name)) % 2) == 1 if gotIsDir != wantIsdir { t.Errorf("%q: isdir %v, want %v", e.Name, gotIsDir, wantIsdir) } } } go-fuse-2.0.3/fs/readonly_test.go000066400000000000000000000023521364171671200167060ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "context" "path/filepath" "syscall" "testing" "github.com/hanwen/go-fuse/v2/fuse" ) func TestReadonlyCreate(t *testing.T) { root := &Inode{} mntDir, _, clean := testMount(t, root, nil) defer clean() _, err := syscall.Creat(mntDir+"/test", 0644) if want := syscall.EROFS; want != err { t.Fatalf("got err %v, want %v", err, want) } } func TestDefaultPermissions(t *testing.T) { root := &Inode{} mntDir, _, clean := testMount(t, root, &Options{ OnAdd: func(ctx context.Context) { dir := root.NewPersistentInode(ctx, &Inode{}, StableAttr{Mode: syscall.S_IFDIR}) file := root.NewPersistentInode(ctx, &Inode{}, StableAttr{Mode: syscall.S_IFREG}) root.AddChild("dir", dir, false) root.AddChild("file", file, false) }, }) defer clean() for k, v := range map[string]uint32{ "dir": fuse.S_IFDIR | 0755, "file": fuse.S_IFREG | 0644, } { var st syscall.Stat_t if err := syscall.Lstat(filepath.Join(mntDir, k), &st); err != nil { t.Error("Lstat", err) } else if st.Mode != v { t.Errorf("got %o want %o", st.Mode, v) } } } go-fuse-2.0.3/fs/readwrite_handleless_example_test.go000066400000000000000000000066011364171671200227750ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs_test import ( "context" "fmt" "log" "sync" "syscall" "time" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" ) // bytesNode is a file that can be read and written type bytesNode struct { fs.Inode // When file systems are mutable, all access must use // synchronization. mu sync.Mutex content []byte mtime time.Time } // Implement GetAttr to provide size and mtime var _ = (fs.NodeGetattrer)((*bytesNode)(nil)) func (bn *bytesNode) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno { bn.mu.Lock() defer bn.mu.Unlock() bn.getattr(out) return 0 } func (bn *bytesNode) getattr(out *fuse.AttrOut) { out.Size = uint64(len(bn.content)) out.SetTimes(nil, &bn.mtime, nil) } func (bn *bytesNode) resize(sz uint64) { if sz > uint64(cap(bn.content)) { n := make([]byte, sz) copy(n, bn.content) bn.content = n } else { bn.content = bn.content[:sz] } bn.mtime = time.Now() } // Implement Setattr to support truncation var _ = (fs.NodeSetattrer)((*bytesNode)(nil)) func (bn *bytesNode) Setattr(ctx context.Context, fh fs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno { bn.mu.Lock() defer bn.mu.Unlock() if sz, ok := in.GetSize(); ok { bn.resize(sz) } bn.getattr(out) return 0 } // Implement handleless read. var _ = (fs.NodeReader)((*bytesNode)(nil)) func (bn *bytesNode) Read(ctx context.Context, fh fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) { bn.mu.Lock() defer bn.mu.Unlock() end := off + int64(len(dest)) if end > int64(len(bn.content)) { end = int64(len(bn.content)) } // We could copy to the `dest` buffer, but since we have a // []byte already, return that. return fuse.ReadResultData(bn.content[off:end]), 0 } // Implement handleless write. var _ = (fs.NodeWriter)((*bytesNode)(nil)) func (bn *bytesNode) Write(ctx context.Context, fh fs.FileHandle, buf []byte, off int64) (uint32, syscall.Errno) { bn.mu.Lock() defer bn.mu.Unlock() sz := int64(len(buf)) if off+sz > int64(len(bn.content)) { bn.resize(uint64(off + sz)) } copy(bn.content[off:], buf) bn.mtime = time.Now() return uint32(sz), 0 } // Implement (handleless) Open var _ = (fs.NodeOpener)((*bytesNode)(nil)) func (f *bytesNode) Open(ctx context.Context, openFlags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) { return nil, 0, 0 } // ExampleHandleLess shows how to create a file that can be read or written // by implementing Read/Write directly on the nodes. func Example_handleLess() { mntDir := "/tmp/x" root := &fs.Inode{} // Mount the file system server, err := fs.Mount(mntDir, root, &fs.Options{ MountOptions: fuse.MountOptions{Debug: false}, // Setup the file. OnAdd: func(ctx context.Context) { ch := root.NewPersistentInode( ctx, &bytesNode{}, fs.StableAttr{ Mode: syscall.S_IFREG, // Make debug output readable. Ino: 2, }) root.AddChild("bytes", ch, true) }, }) if err != nil { log.Fatal(err) } fmt.Printf(`Try: cd %s ls -l bytes echo hello > bytes ls -l bytes cat bytes cd - `, mntDir) fmt.Printf("Unmount by calling 'fusermount -u %s'\n", mntDir) // Serve the file system, until unmounted by calling fusermount -u server.Wait() } go-fuse-2.0.3/fs/simple_test.go000066400000000000000000000207211364171671200163620ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs import ( "fmt" "io/ioutil" "os" "path/filepath" "reflect" "sync" "syscall" "testing" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/internal/testutil" "github.com/hanwen/go-fuse/v2/posixtest" ) type testCase struct { *testing.T dir string origDir string mntDir string loopback InodeEmbedder rawFS fuse.RawFileSystem server *fuse.Server } // writeOrig writes a file into the backing directory of the loopback mount func (tc *testCase) writeOrig(path, content string, mode os.FileMode) { if err := ioutil.WriteFile(filepath.Join(tc.origDir, path), []byte(content), mode); err != nil { tc.Fatal(err) } } func (tc *testCase) Clean() { if err := tc.server.Unmount(); err != nil { tc.Fatal(err) } if err := os.RemoveAll(tc.dir); err != nil { tc.Fatal(err) } } type testOptions struct { entryCache bool attrCache bool suppressDebug bool testDir string } // newTestCase creates the directories `orig` and `mnt` inside a temporary // directory and mounts a loopback filesystem, backed by `orig`, on `mnt`. func newTestCase(t *testing.T, opts *testOptions) *testCase { if opts == nil { opts = &testOptions{} } if opts.testDir == "" { opts.testDir = testutil.TempDir() } tc := &testCase{ dir: opts.testDir, T: t, } tc.origDir = tc.dir + "/orig" tc.mntDir = tc.dir + "/mnt" if err := os.Mkdir(tc.origDir, 0755); err != nil { t.Fatal(err) } if err := os.Mkdir(tc.mntDir, 0755); err != nil { t.Fatal(err) } var err error tc.loopback, err = NewLoopbackRoot(tc.origDir) if err != nil { t.Fatalf("NewLoopback: %v", err) } oneSec := time.Second attrDT := &oneSec if !opts.attrCache { attrDT = nil } entryDT := &oneSec if !opts.entryCache { entryDT = nil } tc.rawFS = NewNodeFS(tc.loopback, &Options{ EntryTimeout: entryDT, AttrTimeout: attrDT, }) mOpts := &fuse.MountOptions{} if !opts.suppressDebug { mOpts.Debug = testutil.VerboseTest() } tc.server, err = fuse.NewServer(tc.rawFS, tc.mntDir, mOpts) if err != nil { t.Fatal(err) } go tc.server.Serve() if err := tc.server.WaitMount(); err != nil { t.Fatal(err) } return tc } func TestBasic(t *testing.T) { tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true}) defer tc.Clean() tc.writeOrig("file", "hello", 0644) fn := tc.mntDir + "/file" fi, err := os.Lstat(fn) if err != nil { t.Fatalf("Lstat: %v", err) } if fi.Size() != 5 { t.Errorf("got size %d want 5", fi.Size()) } stat := fuse.ToStatT(fi) if got, want := uint32(stat.Mode), uint32(fuse.S_IFREG|0644); got != want { t.Errorf("got mode %o, want %o", got, want) } if err := os.Remove(fn); err != nil { t.Errorf("Remove: %v", err) } if fi, err := os.Lstat(fn); err == nil { t.Errorf("Lstat after remove: got file %v", fi) } } func TestFileFdLeak(t *testing.T) { tc := newTestCase(t, &testOptions{ suppressDebug: true, attrCache: true, entryCache: true, }) defer func() { if tc != nil { tc.Clean() } }() posixtest.FdLeak(t, tc.mntDir) tc.Clean() bridge := tc.rawFS.(*rawBridge) tc = nil if got := len(bridge.files); got > 3 { t.Errorf("found %d used file handles, should be <= 3", got) } } func TestNotifyEntry(t *testing.T) { tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true}) defer tc.Clean() orig := tc.origDir + "/file" fn := tc.mntDir + "/file" tc.writeOrig("file", "hello", 0644) st := syscall.Stat_t{} if err := syscall.Lstat(fn, &st); err != nil { t.Fatalf("Lstat before: %v", err) } if err := os.Remove(orig); err != nil { t.Fatalf("Remove: %v", err) } after := syscall.Stat_t{} if err := syscall.Lstat(fn, &after); err != nil { t.Fatalf("Lstat after: %v", err) } else if !reflect.DeepEqual(st, after) { t.Fatalf("got after %#v, want %#v", after, st) } if errno := tc.loopback.EmbeddedInode().NotifyEntry("file"); errno != 0 { t.Errorf("notify failed: %v", errno) } if err := syscall.Lstat(fn, &after); err != syscall.ENOENT { t.Fatalf("Lstat after: got %v, want ENOENT", err) } } func TestReadDirStress(t *testing.T) { tc := newTestCase(t, &testOptions{suppressDebug: true, attrCache: true, entryCache: true}) defer tc.Clean() // Create 110 entries for i := 0; i < 110; i++ { name := fmt.Sprintf("file%036x", i) if err := ioutil.WriteFile(filepath.Join(tc.mntDir, name), []byte("hello"), 0644); err != nil { t.Fatalf("WriteFile %q: %v", name, err) } } var wg sync.WaitGroup stress := func(gr int) { defer wg.Done() for i := 1; i < 100; i++ { f, err := os.Open(tc.mntDir) if err != nil { t.Error(err) return } _, err = f.Readdirnames(-1) if err != nil { t.Errorf("goroutine %d iteration %d: %v", gr, i, err) f.Close() return } f.Close() } } n := 3 for i := 1; i <= n; i++ { wg.Add(1) go stress(i) } wg.Wait() } // This test is racy. If an external process consumes space while this // runs, we may see spurious differences between the two statfs() calls. func TestStatFs(t *testing.T) { tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true}) defer tc.Clean() empty := syscall.Statfs_t{} orig := empty if err := syscall.Statfs(tc.origDir, &orig); err != nil { t.Fatal("statfs orig", err) } mnt := syscall.Statfs_t{} if err := syscall.Statfs(tc.mntDir, &mnt); err != nil { t.Fatal("statfs mnt", err) } var mntFuse, origFuse fuse.StatfsOut mntFuse.FromStatfsT(&mnt) origFuse.FromStatfsT(&orig) if !reflect.DeepEqual(mntFuse, origFuse) { t.Errorf("Got %#v, want %#v", mntFuse, origFuse) } } func TestGetAttrParallel(t *testing.T) { // We grab a file-handle to provide to the API so rename+fstat // can be handled correctly. Here, test that closing and // (f)stat in parallel don't lead to fstat on closed files. // We can only test that if we switch off caching tc := newTestCase(t, &testOptions{suppressDebug: true}) defer tc.Clean() N := 100 var fds []int var fns []string for i := 0; i < N; i++ { fn := fmt.Sprintf("file%d", i) tc.writeOrig(fn, "ello", 0644) fn = filepath.Join(tc.mntDir, fn) fns = append(fns, fn) fd, err := syscall.Open(fn, syscall.O_RDONLY, 0) if err != nil { t.Fatalf("Open %d: %v", i, err) } fds = append(fds, fd) } var wg sync.WaitGroup wg.Add(2 * N) for i := 0; i < N; i++ { go func(i int) { if err := syscall.Close(fds[i]); err != nil { t.Errorf("close %d: %v", i, err) } wg.Done() }(i) go func(i int) { var st syscall.Stat_t if err := syscall.Lstat(fns[i], &st); err != nil { t.Errorf("lstat %d: %v", i, err) } wg.Done() }(i) } wg.Wait() } func TestMknod(t *testing.T) { tc := newTestCase(t, &testOptions{}) defer tc.Clean() modes := map[string]uint32{ "regular": syscall.S_IFREG, "socket": syscall.S_IFSOCK, "fifo": syscall.S_IFIFO, } for nm, mode := range modes { t.Run(nm, func(t *testing.T) { p := filepath.Join(tc.mntDir, nm) err := syscall.Mknod(p, mode|0755, (8<<8)|0) if err != nil { t.Fatalf("mknod(%s): %v", nm, err) } var st syscall.Stat_t if err := syscall.Stat(p, &st); err != nil { got := st.Mode &^ 07777 if want := mode; got != want { t.Fatalf("stat(%s): got %o want %o", nm, got, want) } } // We could test if the files can be // read/written but: The kernel handles FIFOs // without talking to FUSE at all. Presumably, // this also holds for sockets. Regular files // are tested extensively elsewhere. }) } } func TestPosix(t *testing.T) { noisy := map[string]bool{ "ParallelFileOpen": true, "ReadDir": true, } for nm, fn := range posixtest.All { t.Run(nm, func(t *testing.T) { tc := newTestCase(t, &testOptions{ suppressDebug: noisy[nm], attrCache: true, entryCache: true}) defer tc.Clean() fn(t, tc.mntDir) }) } } func TestOpenDirectIO(t *testing.T) { // Apparently, tmpfs does not allow O_DIRECT, so try to create // a test temp directory in /var/tmp. ext4Dir, err := ioutil.TempDir("/var/tmp", "go-fuse.TestOpenDirectIO") if err != nil { t.Fatalf("MkdirAll: %v", err) } defer os.RemoveAll(ext4Dir) posixtest.DirectIO(t, ext4Dir) if t.Failed() { t.Skip("DirectIO failed on underlying FS") } opts := testOptions{ testDir: ext4Dir, attrCache: true, entryCache: true, } tc := newTestCase(t, &opts) defer tc.Clean() posixtest.DirectIO(t, tc.mntDir) } func init() { syscall.Umask(0) } go-fuse-2.0.3/fs/syscall_linux.go000066400000000000000000000010421364171671200167160ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs 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 } go-fuse-2.0.3/fs/zip_test.go000066400000000000000000000050061364171671200156720ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs_test import ( "archive/zip" "bytes" "context" "io/ioutil" "path/filepath" "reflect" "syscall" "testing" "github.com/hanwen/go-fuse/v2/fs" ) var testData = map[string]string{ "file.txt": "content", "dir/subfile1": "content2", "dir/subdir/subfile": "content3", } func createZip(data map[string]string) []byte { buf := &bytes.Buffer{} zw := zip.NewWriter(buf) for k, v := range data { fw, _ := zw.Create(k) fw.Write([]byte(v)) } zw.Close() return buf.Bytes() } type byteReaderAt struct { b []byte } func (br *byteReaderAt) ReadAt(data []byte, off int64) (int, error) { end := int(off) + len(data) if end > len(br.b) { end = len(br.b) } copy(data, br.b[off:end]) return end - int(off), nil } func TestZipFS(t *testing.T) { zipBytes := createZip(testData) r, err := zip.NewReader(&byteReaderAt{zipBytes}, int64(len(zipBytes))) if err != nil { t.Fatal(err) } root := &zipRoot{zr: r} mntDir, err := ioutil.TempDir("", "ZipFS") if err != nil { t.Fatal(err) } server, err := fs.Mount(mntDir, root, nil) if err != nil { t.Fatal(err) } defer server.Unmount() for k, v := range testData { c, err := ioutil.ReadFile(filepath.Join(mntDir, k)) if err != nil { t.Fatal(err) } if string(c) != v { t.Errorf("got %q, want %q", c, v) } } entries, err := ioutil.ReadDir(mntDir) if err != nil { t.Fatal(err) } got := map[string]bool{} for _, e := range entries { got[e.Name()] = e.IsDir() } want := map[string]bool{ "dir": true, "file.txt": false, } if !reflect.DeepEqual(got, want) { t.Errorf("got %v want %v", got, want) } } func TestZipFSOnAdd(t *testing.T) { zipBytes := createZip(testData) r, err := zip.NewReader(&byteReaderAt{zipBytes}, int64(len(zipBytes))) if err != nil { t.Fatal(err) } zr := &zipRoot{zr: r} root := &fs.Inode{} mnt, err := ioutil.TempDir("", "ZipFS") if err != nil { t.Fatal(err) } server, err := fs.Mount(mnt, root, &fs.Options{ OnAdd: func(ctx context.Context) { root.AddChild("sub", root.NewPersistentInode(ctx, zr, fs.StableAttr{Mode: syscall.S_IFDIR}), false) }, }) if err != nil { t.Fatal(err) } defer server.Unmount() c, err := ioutil.ReadFile(mnt + "/sub/dir/subdir/subfile") if err != nil { t.Fatal("ReadFile", err) } if got, want := string(c), "content3"; got != want { t.Errorf("got %q, want %q", got, want) } } go-fuse-2.0.3/fs/zipfs_example_test.go000066400000000000000000000064041364171671200177410ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fs_test import ( "archive/zip" "context" "flag" "fmt" "io/ioutil" "log" "os" "path/filepath" "strings" "sync" "syscall" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" ) // zipFile is a file read from a zip archive. type zipFile struct { fs.Inode file *zip.File mu sync.Mutex data []byte } // We decompress the file on demand in Open var _ = (fs.NodeOpener)((*zipFile)(nil)) // Getattr sets the minimum, which is the size. A more full-featured // FS would also set timestamps and permissions. var _ = (fs.NodeGetattrer)((*zipFile)(nil)) func (zf *zipFile) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno { out.Size = zf.file.UncompressedSize64 return 0 } // Open lazily unpacks zip data func (zf *zipFile) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint32, syscall.Errno) { zf.mu.Lock() defer zf.mu.Unlock() if zf.data == nil { rc, err := zf.file.Open() if err != nil { return nil, 0, syscall.EIO } content, err := ioutil.ReadAll(rc) if err != nil { return nil, 0, syscall.EIO } zf.data = content } // We don't return a filehandle since we don't really need // one. The file content is immutable, so hint the kernel to // cache the data. return nil, fuse.FOPEN_KEEP_CACHE, fs.OK } // Read simply returns the data that was already unpacked in the Open call func (zf *zipFile) Read(ctx context.Context, f fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) { end := int(off) + len(dest) if end > len(zf.data) { end = len(zf.data) } return fuse.ReadResultData(zf.data[off:end]), fs.OK } // zipRoot is the root of the Zip filesystem. Its only functionality // is populating the filesystem. type zipRoot struct { fs.Inode zr *zip.Reader } // The root populates the tree in its OnAdd method var _ = (fs.NodeOnAdder)((*zipRoot)(nil)) func (zr *zipRoot) OnAdd(ctx context.Context) { // OnAdd is called once we are attached to an Inode. We can // then construct a tree. We construct the entire tree, and // we don't want parts of the tree to disappear when the // kernel is short on memory, so we use persistent inodes. for _, f := range zr.zr.File { dir, base := filepath.Split(f.Name) p := &zr.Inode for _, component := range strings.Split(dir, "/") { if len(component) == 0 { continue } ch := p.GetChild(component) if ch == nil { ch = p.NewPersistentInode(ctx, &fs.Inode{}, fs.StableAttr{Mode: fuse.S_IFDIR}) p.AddChild(component, ch, true) } p = ch } ch := p.NewPersistentInode(ctx, &zipFile{file: f}, fs.StableAttr{}) p.AddChild(base, ch, true) } } // ExampleZipFS shows an in-memory, static file system func Example_zipFS() { flag.Parse() if len(flag.Args()) != 1 { log.Fatal("usage: zipmount ZIP-FILE") } zfile, err := zip.OpenReader(flag.Arg(0)) if err != nil { log.Fatal(err) } root := &zipRoot{zr: &zfile.Reader} mnt := "/tmp/x" os.Mkdir(mnt, 0755) server, err := fs.Mount(mnt, root, nil) if err != nil { log.Fatal(err) } fmt.Println("zip file mounted") fmt.Printf("to unmount: fusermount -u %s\n", mnt) server.Wait() } go-fuse-2.0.3/fuse/000077500000000000000000000000001364171671200140335ustar00rootroot00000000000000go-fuse-2.0.3/fuse/.gitignore000066400000000000000000000000171364171671200160210ustar00rootroot00000000000000version.gen.go go-fuse-2.0.3/fuse/api.go000066400000000000000000000254021364171671200151360ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package fuse provides APIs to implement filesystems in // userspace in terms of raw FUSE protocol. // // A filesystem is implemented by implementing its server that provides a // RawFileSystem interface. Typically the server embeds // NewDefaultRawFileSystem() and implements only subset of filesystem methods: // // type MyFS struct { // fuse.RawFileSystem // ... // } // // func NewMyFS() *MyFS { // return &MyFS{ // RawFileSystem: fuse.NewDefaultRawFileSystem(), // ... // } // } // // // Mkdir implements "mkdir" request handler. // // // // For other requests - not explicitly implemented by MyFS - ENOSYS // // will be typically returned to client. // func (fs *MyFS) Mkdir(...) { // ... // } // // Then the filesystem can be mounted and served to a client (typically OS // kernel) by creating Server: // // fs := NewMyFS() // implements RawFileSystem // fssrv, err := fuse.NewServer(fs, mountpoint, &fuse.MountOptions{...}) // if err != nil { // ... // } // // and letting the server do its work: // // // either synchronously - .Serve() blocks until the filesystem is unmounted. // fssrv.Serve() // // // or in the background - .Serve() is spawned in another goroutine, but // // before interacting with fssrv from current context we have to wait // // until the filesystem mounting is complete. // go fssrv.Serve() // err = fssrv.WaitMount() // if err != nil { // ... // } // // The server will serve clients by dispatching their requests to the // filesystem implementation and conveying responses back. For example "mkdir" // FUSE request dispatches to call // // fs.Mkdir(*MkdirIn, ..., *EntryOut) // // "stat" to call // // fs.GetAttr(*GetAttrIn, *AttrOut) // // etc. Please refer to RawFileSystem documentation for details. // // Typically, each call of the API happens in its own // goroutine, so take care to make the file system thread-safe. // // // Higher level interfaces // // As said above this packages provides way to implement filesystems in terms of // raw FUSE protocol. Additionally packages nodefs and pathfs provide ways to // implement filesystem at higher levels: // // Package github.com/hanwen/go-fuse/fuse/nodefs provides way to implement // filesystems in terms of inodes. This resembles kernel's idea of what a // filesystem looks like. // // Package github.com/hanwen/go-fuse/fuse/pathfs provides way to implement // filesystems in terms of path names. Working with path names is somewhat // easier compared to inodes, however renames can be racy. Do not use pathfs if // you care about correctness. 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 // Max read ahead to use. If 0, use default. This number is // capped at the kernel maximum. MaxReadAhead 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 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 // If set, ask kernel to forward file locks to FUSE. If using, // you must implement the GetLk/SetLk/SetLkw methods. EnableLocks bool // If set, ask kernel not to do automatic data cache invalidation. // The filesystem is fully responsible for invalidating data cache. ExplicitDataCacheControl bool // If set, fuse will first attempt to use syscall.Mount instead of // fusermount to mount the filesystem. This will not update /etc/mtab // but might be needed if fusermount is not available. DirectMount bool // Options passed to syscall.Mount, the default value used by fusermount // is syscall.MS_NOSUID|syscall.MS_NODEV DirectMountFlags uintptr } // 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 nodefs.Node or pathfs.FileSystem interfaces; the // details of getting interactions with open files, renames, and threading // right etc. are somewhat tricky and not very interesting. // // Each FUSE request results in a corresponding method called by Server. // Several calls may be made simultaneously, because the server typically calls // each method in separate goroutine. // // A null implementation is provided by NewDefaultRawFileSystem. // // After a successful FUSE API call returns, you may not read input or // write output data: for performance reasons, memory is reused for // following requests, and reading/writing the request data will lead // to race conditions. If you spawn a background routine from a FUSE // API call, any incoming request data it wants to reference should be // copied over. // // If a FUSE API call is canceled (which is signaled by closing the // `cancel` channel), the API call should return EINTR. In this case, // the outstanding request data is not reused, so the API call may // return EINTR without ensuring that child contexts have successfully // completed. type RawFileSystem interface { String() string // If called, provide debug output through the log package. SetDebug(debug bool) // Lookup is called by the kernel when the VFS wants to know // about a file inside a directory. Many lookup calls can // occur in parallel, but only one call happens for each (dir, // name) pair. Lookup(cancel <-chan struct{}, header *InHeader, name string, out *EntryOut) (status Status) // Forget is called when the kernel discards entries from its // dentry cache. This happens on unmount, and when the kernel // is short on memory. Since it is not guaranteed to occur at // any moment, and since there is no return value, Forget // should not do I/O, as there is no channel to report back // I/O errors. Forget(nodeid, nlookup uint64) // Attributes. GetAttr(cancel <-chan struct{}, input *GetAttrIn, out *AttrOut) (code Status) SetAttr(cancel <-chan struct{}, input *SetAttrIn, out *AttrOut) (code Status) // Modifying structure. Mknod(cancel <-chan struct{}, input *MknodIn, name string, out *EntryOut) (code Status) Mkdir(cancel <-chan struct{}, input *MkdirIn, name string, out *EntryOut) (code Status) Unlink(cancel <-chan struct{}, header *InHeader, name string) (code Status) Rmdir(cancel <-chan struct{}, header *InHeader, name string) (code Status) Rename(cancel <-chan struct{}, input *RenameIn, oldName string, newName string) (code Status) Link(cancel <-chan struct{}, input *LinkIn, filename string, out *EntryOut) (code Status) Symlink(cancel <-chan struct{}, header *InHeader, pointedTo string, linkName string, out *EntryOut) (code Status) Readlink(cancel <-chan struct{}, header *InHeader) (out []byte, code Status) Access(cancel <-chan struct{}, input *AccessIn) (code Status) // Extended attributes. // GetXAttr reads an extended attribute, and should return the // number of bytes. If the buffer is too small, return ERANGE, // with the required buffer size. GetXAttr(cancel <-chan struct{}, header *InHeader, attr string, dest []byte) (sz uint32, code Status) // ListXAttr lists extended attributes as '\0' delimited byte // slice, and return the number of bytes. If the buffer is too // small, return ERANGE, with the required buffer size. ListXAttr(cancel <-chan struct{}, header *InHeader, dest []byte) (uint32, Status) // SetAttr writes an extended attribute. SetXAttr(cancel <-chan struct{}, input *SetXAttrIn, attr string, data []byte) Status // RemoveXAttr removes an extended attribute. RemoveXAttr(cancel <-chan struct{}, header *InHeader, attr string) (code Status) // File handling. Create(cancel <-chan struct{}, input *CreateIn, name string, out *CreateOut) (code Status) Open(cancel <-chan struct{}, input *OpenIn, out *OpenOut) (status Status) Read(cancel <-chan struct{}, input *ReadIn, buf []byte) (ReadResult, Status) Lseek(cancel <-chan struct{}, in *LseekIn, out *LseekOut) Status // File locking GetLk(cancel <-chan struct{}, input *LkIn, out *LkOut) (code Status) SetLk(cancel <-chan struct{}, input *LkIn) (code Status) SetLkw(cancel <-chan struct{}, input *LkIn) (code Status) Release(cancel <-chan struct{}, input *ReleaseIn) Write(cancel <-chan struct{}, input *WriteIn, data []byte) (written uint32, code Status) CopyFileRange(cancel <-chan struct{}, input *CopyFileRangeIn) (written uint32, code Status) Flush(cancel <-chan struct{}, input *FlushIn) Status Fsync(cancel <-chan struct{}, input *FsyncIn) (code Status) Fallocate(cancel <-chan struct{}, input *FallocateIn) (code Status) // Directory handling OpenDir(cancel <-chan struct{}, input *OpenIn, out *OpenOut) (status Status) ReadDir(cancel <-chan struct{}, input *ReadIn, out *DirEntryList) Status ReadDirPlus(cancel <-chan struct{}, input *ReadIn, out *DirEntryList) Status ReleaseDir(input *ReleaseIn) FsyncDir(cancel <-chan struct{}, input *FsyncIn) (code Status) StatFs(cancel <-chan struct{}, 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) } go-fuse-2.0.3/fuse/attr.go000066400000000000000000000043041364171671200153350ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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 } go-fuse-2.0.3/fuse/attr_darwin.go000066400000000000000000000012421364171671200166770ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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) } go-fuse-2.0.3/fuse/attr_linux.go000066400000000000000000000012331364171671200165520ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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) } go-fuse-2.0.3/fuse/bufferpool.go000066400000000000000000000031511364171671200165250ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fuse import ( "os" "sync" ) // bufferPool implements explicit memory management. It is used for // minimizing the GC overhead of communicating with the kernel. type bufferPool struct { lock sync.Mutex // For each page size multiple a list of slice pointers. buffersBySize []*sync.Pool } var pageSize = os.Getpagesize() func (p *bufferPool) 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 } // AllocBuffer creates a buffer of at least the given size. After use, // it should be deallocated with FreeBuffer(). func (p *bufferPool) 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] } // FreeBuffer takes back a buffer if it was allocated through // AllocBuffer. It is not an error to call FreeBuffer() on a slice // obtained elsewhere. func (p *bufferPool) 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) } go-fuse-2.0.3/fuse/bufferpool_test.go000066400000000000000000000010401364171671200175570ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fuse import ( "testing" ) func TestBufferPool(t *testing.T) { bp := bufferPool{} size := 1500 buf1 := bp.AllocBuffer(uint32(size)) if len(buf1) != size { t.Errorf("Expected buffer of %d bytes, got %d bytes", size, len(buf1)) } bp.FreeBuffer(buf1) // tried testing to see if we get buf1 back if we ask again, // but it's not guaranteed and sometimes fails } go-fuse-2.0.3/fuse/constants.go000066400000000000000000000012331364171671200163750ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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) logicalBlockSize = 512 ) go-fuse-2.0.3/fuse/constants_freebsd.go000066400000000000000000000004151364171671200200700ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fuse // arbitrary values const syscall_O_LARGEFILE = 1 << 29 const syscall_O_NOATIME = 1 << 30 go-fuse-2.0.3/fuse/constants_linux.go000066400000000000000000000004461364171671200176210ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fuse import ( "syscall" ) const syscall_O_LARGEFILE = syscall.O_LARGEFILE const syscall_O_NOATIME = syscall.O_NOATIME go-fuse-2.0.3/fuse/context.go000066400000000000000000000024211364171671200160450ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fuse import ( "context" "time" ) // Context passes along cancelation signal and request data (PID, GID, // UID). The name of this class predates the standard "context" // package from Go, but it does implement the context.Context // interface. // // When a FUSE request is canceled, the API routine should respond by // returning the EINTR status code. type Context struct { Caller Cancel <-chan struct{} } func (c *Context) Deadline() (time.Time, bool) { return time.Time{}, false } func (c *Context) Done() <-chan struct{} { return c.Cancel } func (c *Context) Err() error { select { case <-c.Cancel: return context.Canceled default: return nil } } type callerKeyType struct{} var callerKey callerKeyType func FromContext(ctx context.Context) (*Caller, bool) { v, ok := ctx.Value(callerKey).(*Caller) return v, ok } func NewContext(ctx context.Context, caller *Caller) context.Context { return context.WithValue(ctx, callerKey, caller) } func (c *Context) Value(key interface{}) interface{} { if key == callerKey { return &c.Caller } return nil } var _ = context.Context((*Context)(nil)) go-fuse-2.0.3/fuse/defaultraw.go000066400000000000000000000117421364171671200165250ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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(cancel <-chan struct{}, header *InHeader, out *StatfsOut) Status { return ENOSYS } func (fs *defaultRawFileSystem) Lookup(cancel <-chan struct{}, header *InHeader, name string, out *EntryOut) (code Status) { return ENOSYS } func (fs *defaultRawFileSystem) Forget(nodeID, nlookup uint64) { } func (fs *defaultRawFileSystem) GetAttr(cancel <-chan struct{}, input *GetAttrIn, out *AttrOut) (code Status) { return ENOSYS } func (fs *defaultRawFileSystem) Open(cancel <-chan struct{}, input *OpenIn, out *OpenOut) (status Status) { return OK } func (fs *defaultRawFileSystem) SetAttr(cancel <-chan struct{}, input *SetAttrIn, out *AttrOut) (code Status) { return ENOSYS } func (fs *defaultRawFileSystem) Readlink(cancel <-chan struct{}, header *InHeader) (out []byte, code Status) { return nil, ENOSYS } func (fs *defaultRawFileSystem) Mknod(cancel <-chan struct{}, input *MknodIn, name string, out *EntryOut) (code Status) { return ENOSYS } func (fs *defaultRawFileSystem) Mkdir(cancel <-chan struct{}, input *MkdirIn, name string, out *EntryOut) (code Status) { return ENOSYS } func (fs *defaultRawFileSystem) Unlink(cancel <-chan struct{}, header *InHeader, name string) (code Status) { return ENOSYS } func (fs *defaultRawFileSystem) Rmdir(cancel <-chan struct{}, header *InHeader, name string) (code Status) { return ENOSYS } func (fs *defaultRawFileSystem) Symlink(cancel <-chan struct{}, header *InHeader, pointedTo string, linkName string, out *EntryOut) (code Status) { return ENOSYS } func (fs *defaultRawFileSystem) Rename(cancel <-chan struct{}, input *RenameIn, oldName string, newName string) (code Status) { return ENOSYS } func (fs *defaultRawFileSystem) Link(cancel <-chan struct{}, input *LinkIn, name string, out *EntryOut) (code Status) { return ENOSYS } func (fs *defaultRawFileSystem) GetXAttr(cancel <-chan struct{}, header *InHeader, attr string, dest []byte) (size uint32, code Status) { return 0, ENOSYS } func (fs *defaultRawFileSystem) SetXAttr(cancel <-chan struct{}, input *SetXAttrIn, attr string, data []byte) Status { return ENOSYS } func (fs *defaultRawFileSystem) ListXAttr(cancel <-chan struct{}, header *InHeader, dest []byte) (n uint32, code Status) { return 0, ENOSYS } func (fs *defaultRawFileSystem) RemoveXAttr(cancel <-chan struct{}, header *InHeader, attr string) Status { return ENOSYS } func (fs *defaultRawFileSystem) Access(cancel <-chan struct{}, input *AccessIn) (code Status) { return ENOSYS } func (fs *defaultRawFileSystem) Create(cancel <-chan struct{}, input *CreateIn, name string, out *CreateOut) (code Status) { return ENOSYS } func (fs *defaultRawFileSystem) OpenDir(cancel <-chan struct{}, input *OpenIn, out *OpenOut) (status Status) { return ENOSYS } func (fs *defaultRawFileSystem) Read(cancel <-chan struct{}, input *ReadIn, buf []byte) (ReadResult, Status) { return nil, ENOSYS } func (fs *defaultRawFileSystem) GetLk(cancel <-chan struct{}, in *LkIn, out *LkOut) (code Status) { return ENOSYS } func (fs *defaultRawFileSystem) SetLk(cancel <-chan struct{}, in *LkIn) (code Status) { return ENOSYS } func (fs *defaultRawFileSystem) SetLkw(cancel <-chan struct{}, in *LkIn) (code Status) { return ENOSYS } func (fs *defaultRawFileSystem) Release(cancel <-chan struct{}, input *ReleaseIn) { } func (fs *defaultRawFileSystem) Write(cancel <-chan struct{}, input *WriteIn, data []byte) (written uint32, code Status) { return 0, ENOSYS } func (fs *defaultRawFileSystem) Flush(cancel <-chan struct{}, input *FlushIn) Status { return OK } func (fs *defaultRawFileSystem) Fsync(cancel <-chan struct{}, input *FsyncIn) (code Status) { return ENOSYS } func (fs *defaultRawFileSystem) ReadDir(cancel <-chan struct{}, input *ReadIn, l *DirEntryList) Status { return ENOSYS } func (fs *defaultRawFileSystem) ReadDirPlus(cancel <-chan struct{}, input *ReadIn, l *DirEntryList) Status { return ENOSYS } func (fs *defaultRawFileSystem) ReleaseDir(input *ReleaseIn) { } func (fs *defaultRawFileSystem) FsyncDir(cancel <-chan struct{}, input *FsyncIn) (code Status) { return ENOSYS } func (fs *defaultRawFileSystem) Fallocate(cancel <-chan struct{}, in *FallocateIn) (code Status) { return ENOSYS } func (fs *defaultRawFileSystem) CopyFileRange(cancel <-chan struct{}, input *CopyFileRangeIn) (written uint32, code Status) { return 0, ENOSYS } func (fs *defaultRawFileSystem) Lseek(cancel <-chan struct{}, in *LseekIn, out *LseekOut) Status { return ENOSYS } go-fuse-2.0.3/fuse/direntry.go000066400000000000000000000071471364171671200162330ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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 // Ino is the inode number. Ino uint64 } func (d DirEntry) String() string { return fmt.Sprintf("%o: %q ino=%d", d.Mode, d.Name, d.Ino) } // DirEntryList holds the return value for READDIR and READDIRPLUS // opcodes. type DirEntryList struct { buf []byte size int // capacity of the underlying buffer offset uint64 // entry count (NOT a byte offset) lastDirent *_Dirent // pointer to the last serialized _Dirent. Used by FixMode(). } // 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 { return l.Add(0, e.Name, e.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 { if inode == 0 { inode = FUSE_UNKNOWN_INO } 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.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 = modeToType(mode) 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 } // AddDirLookupEntry is used for ReadDirPlus. If reserves and zeroizes space // for an EntryOut struct and serializes a DirEntry. // On success, it returns pointers to both structs. // If not enough space was left, it returns two nil pointers. // // The resulting READDIRPLUS output buffer looks like this in memory: // 1) EntryOut{} // 2) _Dirent{} // 3) Name (null-terminated) // 4) Padding to align to 8 bytes // [repeat] func (l *DirEntryList) AddDirLookupEntry(e DirEntry) *EntryOut { const entryOutSize = int(unsafe.Sizeof(EntryOut{})) oldLen := len(l.buf) ok := l.Add(entryOutSize, e.Name, e.Ino, e.Mode) if !ok { return nil } l.lastDirent = (*_Dirent)(unsafe.Pointer(&l.buf[oldLen+entryOutSize])) entryOut := (*EntryOut)(unsafe.Pointer(&l.buf[oldLen])) *entryOut = EntryOut{} // zeroize return entryOut } // modeToType converts a file *mode* (as used in syscall.Stat_t.Mode) // to a file *type* (as used in _Dirent.Typ). // Equivalent to IFTODT() in libc (see man 5 dirent). func modeToType(mode uint32) uint32 { return (mode & 0170000) >> 12 } // FixMode overrides the file mode of the last direntry that was added. This can // be needed when a directory changes while READDIRPLUS is running. // Only the file type bits of mode are considered, the rest is masked out. func (l *DirEntryList) FixMode(mode uint32) { l.lastDirent.Typ = modeToType(mode) } func (l *DirEntryList) bytes() []byte { return l.buf } go-fuse-2.0.3/fuse/misc.go000066400000000000000000000040541364171671200153200ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Random odds and ends. package fuse import ( "fmt" "log" "os" "reflect" "syscall" "time" "unsafe" ) func (code Status) String() string { if code <= 0 { return []string{ "OK", "NOTIFY_POLL", "NOTIFY_INVAL_INODE", "NOTIFY_INVAL_ENTRY", "NOTIFY_STORE_CACHE", "NOTIFY_RETRIEVE_CACHE", "NOTIFY_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()), } } const _UTIME_OMIT = ((1 << 30) - 2) // UtimeToTimespec converts a "Time" pointer as passed to Utimens to a // "Timespec" that can be passed to the utimensat syscall. // A nil pointer is converted to the special UTIME_OMIT value. func UtimeToTimespec(t *time.Time) (ts syscall.Timespec) { if t == nil { ts.Nsec = _UTIME_OMIT } else { ts = syscall.NsecToTimespec(t.UnixNano()) // Go bug https://github.com/golang/go/issues/12777 if ts.Nsec < 0 { ts.Nsec = 0 } } return ts } go-fuse-2.0.3/fuse/misc_test.go000066400000000000000000000012711364171671200163550ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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) } } go-fuse-2.0.3/fuse/mount_darwin.go000066400000000000000000000044171364171671200170760ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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 { bin := oldLoadBin if _, err := os.Stat(newLoadBin); err == nil { bin = newLoadBin } cmd := exec.Command(bin) if err := cmd.Run(); err != nil { return nil, err } fs, err = filepath.Glob("/dev/osxfuse*") if err != nil { return nil, err } } 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 oldLoadBin = "/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs" const newLoadBin = "/Library/Filesystems/osxfuse.fs/Contents/Resources/load_osxfuse" const oldMountBin = "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs" const newMountBin = "/Library/Filesystems/osxfuse.fs/Contents/Resources/mount_osxfuse" func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) { f, err := openFUSEDevice() if err != nil { return 0, err } bin := oldMountBin if _, err := os.Stat(newMountBin); err == nil { bin = newMountBin } 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, opts *MountOptions) error { return syscall.Unmount(dir, 0) } go-fuse-2.0.3/fuse/mount_linux.go000066400000000000000000000111761364171671200167510ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fuse import ( "bytes" "fmt" "log" "os" "os/exec" "path" "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 without using // fusermount. func mountDirect(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) { fd, err = syscall.Open("/dev/fuse", os.O_RDWR, 0) // use syscall.Open since we want an int fd if err != nil { return } // managed to open dev/fuse, attempt to mount source := opts.FsName if source == "" { source = opts.Name } var flags uintptr flags |= syscall.MS_NOSUID | syscall.MS_NODEV // some values we need to pass to mount, but override possible since opts.Options comes after var r = []string{ fmt.Sprintf("fd=%d", fd), "rootmode=40000", "user_id=0", "group_id=0", } r = append(r, opts.Options...) if opts.AllowOther { r = append(r, "allow_other") } err = syscall.Mount(opts.FsName, mountPoint, "fuse."+opts.Name, opts.DirectMountFlags, strings.Join(r, ",")) if err != nil { syscall.Close(fd) return } // success close(ready) 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) { if opts.DirectMount { fd, err := mountDirect(mountPoint, opts, ready) if err == nil { return fd, nil } else if opts.Debug { log.Printf("mount: failed to do direct mount: %s", err) } } 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 } // golang sets CLOEXEC on file descriptors when they are // acquired through normal operations (e.g. open). // Buf for fd, we have to set CLOEXEC manually syscall.CloseOnExec(fd) close(ready) return fd, err } func unmount(mountPoint string, opts *MountOptions) (err error) { if opts.DirectMount { // Attempt to directly unmount, if fails fallback to fusermount method err := syscall.Unmount(mountPoint, 0) if err == nil { return nil } } 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") } go-fuse-2.0.3/fuse/nodefs/000077500000000000000000000000001364171671200153115ustar00rootroot00000000000000go-fuse-2.0.3/fuse/nodefs/api.go000066400000000000000000000206231364171671200164140ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // This package is deprecated. New projects should use the package // "github.com/hanwen/go-fuse/v2/fs" instead. // // 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/v2/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 may be called on nodes that are // already known. 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) // File locking // // GetLk returns existing lock information for file. GetLk(file File, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock, context *fuse.Context) (code fuse.Status) // Sets or clears the lock described by lk on file. SetLk(file File, owner uint64, lk *fuse.FileLock, flags uint32, context *fuse.Context) (code fuse.Status) // Sets or clears the lock described by lk. This call blocks until the operation can be completed. SetLkw(file File, owner uint64, lk *fuse.FileLock, flags uint32, context *fuse.Context) (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. This // is useful in that PathFS API, where Create/Open have no // access to the Inode at hand. 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) // File locking GetLk(owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) (code fuse.Status) SetLk(owner uint64, lk *fuse.FileLock, flags uint32) (code fuse.Status) SetLkw(owner uint64, lk *fuse.FileLock, flags 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 // If set, issue Lookup rather than GetAttr calls for known // children. This allows the filesystem to update its inode // hierarchy in response to kernel calls. LookupKnownChildren bool } go-fuse-2.0.3/fuse/nodefs/defaultfile.go000066400000000000000000000035471364171671200201350ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package nodefs import ( "time" "github.com/hanwen/go-fuse/v2/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) GetLk(owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) (code fuse.Status) { return fuse.ENOSYS } func (f *defaultFile) SetLk(owner uint64, lk *fuse.FileLock, flags uint32) (code fuse.Status) { return fuse.ENOSYS } func (f *defaultFile) SetLkw(owner uint64, lk *fuse.FileLock, flags uint32) (code fuse.Status) { return 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 } go-fuse-2.0.3/fuse/nodefs/defaultnode.go000066400000000000000000000121661364171671200201400ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package nodefs import ( "time" "github.com/hanwen/go-fuse/v2/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.ENOATTR } 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 file != nil { return file.GetAttr(out) } if n.Inode().IsDir() { out.Mode = fuse.S_IFDIR | 0755 } else { out.Mode = fuse.S_IFREG | 0644 } return fuse.OK } func (n *defaultNode) GetLk(file File, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock, context *fuse.Context) (code fuse.Status) { return fuse.ENOSYS } func (n *defaultNode) SetLk(file File, owner uint64, lk *fuse.FileLock, flags uint32, context *fuse.Context) (code fuse.Status) { return fuse.ENOSYS } func (n *defaultNode) SetLkw(file File, owner uint64, lk *fuse.FileLock, flags uint32, context *fuse.Context) (code fuse.Status) { return fuse.ENOSYS } 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 } go-fuse-2.0.3/fuse/nodefs/dir.go000066400000000000000000000057531364171671200164300ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package nodefs import ( "log" "sync" "github.com/hanwen/go-fuse/v2/fuse" ) type connectorDir struct { node Node inode *Inode 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 } func (d *connectorDir) ReadDir(cancel <-chan struct{}, input *fuse.ReadIn, out *fuse.DirEntryList) (code fuse.Status) { d.mu.Lock() defer d.mu.Unlock() // rewinddir() should be as if reopening directory. // TODO - test this. if d.stream == nil || input.Offset == 0 { d.stream, code = d.node.OpenDir(&fuse.Context{Caller: input.Caller, Cancel: cancel}) if !code.Ok() { return code } d.stream = append(d.stream, d.inode.getMountDirEntries()...) d.stream = append(d.stream, fuse.DirEntry{Mode: fuse.S_IFDIR, Name: "."}, fuse.DirEntry{Mode: fuse.S_IFDIR, Name: ".."}) } if input.Offset > uint64(len(d.stream)) { // See https://github.com/hanwen/go-fuse/issues/297 // This can happen for FUSE exported over NFS. This // seems incorrect, (maybe the kernel is using offsets // from other opendir/readdir calls), it is harmless to reinforce that // we have reached EOF. return fuse.OK } todo := d.stream[input.Offset:] for _, e := range todo { if e.Name == "" { log.Printf("got empty directory entry, mode %o.", e.Mode) continue } ok := out.AddDirEntry(e) if !ok { break } } return fuse.OK } func (d *connectorDir) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out *fuse.DirEntryList) (code fuse.Status) { d.mu.Lock() defer d.mu.Unlock() // rewinddir() should be as if reopening directory. if d.stream == nil || input.Offset == 0 { d.stream, code = d.node.OpenDir(&fuse.Context{Caller: input.Caller, Cancel: cancel}) if !code.Ok() { return code } d.stream = append(d.stream, d.inode.getMountDirEntries()...) d.stream = append(d.stream, fuse.DirEntry{Mode: fuse.S_IFDIR, Name: "."}, fuse.DirEntry{Mode: fuse.S_IFDIR, Name: ".."}) } if input.Offset > uint64(len(d.stream)) { // See comment in Readdir return fuse.OK } 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 := 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 } d.rawFS.Lookup(cancel, &input.InHeader, e.Name, entryDest) } 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 } go-fuse-2.0.3/fuse/nodefs/fileless_test.go000066400000000000000000000057131364171671200205130ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package nodefs import ( "io/ioutil" "testing" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/internal/testutil" ) 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: testutil.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 = testutil.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) } } go-fuse-2.0.3/fuse/nodefs/files.go000066400000000000000000000151721364171671200167500ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package nodefs import ( "fmt" "os" "sync" "syscall" "github.com/hanwen/go-fuse/v2/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 } const ( F_OFD_GETLK = 36 F_OFD_SETLK = 37 F_OFD_SETLKW = 38 ) func (f *loopbackFile) GetLk(owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) (code fuse.Status) { flk := syscall.Flock_t{} lk.ToFlockT(&flk) code = fuse.ToStatus(syscall.FcntlFlock(f.File.Fd(), F_OFD_GETLK, &flk)) out.FromFlockT(&flk) return } func (f *loopbackFile) SetLk(owner uint64, lk *fuse.FileLock, flags uint32) (code fuse.Status) { return f.setLock(owner, lk, flags, false) } func (f *loopbackFile) SetLkw(owner uint64, lk *fuse.FileLock, flags uint32) (code fuse.Status) { return f.setLock(owner, lk, flags, true) } func (f *loopbackFile) setLock(owner uint64, lk *fuse.FileLock, flags uint32, blocking bool) (code fuse.Status) { if (flags & fuse.FUSE_LK_FLOCK) != 0 { var op int switch lk.Typ { case syscall.F_RDLCK: op = syscall.LOCK_SH case syscall.F_WRLCK: op = syscall.LOCK_EX case syscall.F_UNLCK: op = syscall.LOCK_UN default: return fuse.EINVAL } if !blocking { op |= syscall.LOCK_NB } return fuse.ToStatus(syscall.Flock(int(f.File.Fd()), op)) } else { flk := syscall.Flock_t{} lk.ToFlockT(&flk) var op int if blocking { op = F_OFD_SETLKW } else { op = F_OFD_SETLK } return fuse.ToStatus(syscall.FcntlFlock(f.File.Fd(), op, &flk)) } } 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 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 } go-fuse-2.0.3/fuse/nodefs/files_darwin.go000066400000000000000000000055141364171671200203130ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package nodefs import ( "syscall" "time" "unsafe" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/internal/utimens" ) 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 } // 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 } // MacOS before High Sierra lacks utimensat() and UTIME_OMIT. // We emulate using utimes() and extra GetAttr() calls. func (f *loopbackFile) Utimens(a *time.Time, m *time.Time) fuse.Status { var attr fuse.Attr if a == nil || m == nil { var status fuse.Status status = f.GetAttr(&attr) if !status.Ok() { return status } } tv := utimens.Fill(a, m, &attr) f.lock.Lock() err := syscall.Futimes(int(f.File.Fd()), tv) f.lock.Unlock() return fuse.ToStatus(err) } go-fuse-2.0.3/fuse/nodefs/files_linux.go000066400000000000000000000015131364171671200201610ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package nodefs import ( "syscall" "time" "github.com/hanwen/go-fuse/v2/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 } // 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 ts[0] = fuse.UtimeToTimespec(a) ts[1] = fuse.UtimeToTimespec(m) f.lock.Lock() err := futimens(int(f.File.Fd()), &ts) f.lock.Unlock() return fuse.ToStatus(err) } go-fuse-2.0.3/fuse/nodefs/files_test.go000066400000000000000000000013651364171671200200060ustar00rootroot00000000000000// Copyright 2018 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package nodefs import ( "io/ioutil" "os" "testing" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/internal/testutil" ) // Check that loopbackFile.Utimens() works as expected func TestLoopbackFileUtimens(t *testing.T) { f2, err := ioutil.TempFile("", "TestLoopbackFileUtimens") if err != nil { t.Fatal(err) } path := f2.Name() defer os.Remove(path) defer f2.Close() f := NewLoopbackFile(f2) utimensFn := func(atime *time.Time, mtime *time.Time) fuse.Status { return f.Utimens(atime, mtime) } testutil.TestLoopbackUtimens(t, path, utimensFn) } go-fuse-2.0.3/fuse/nodefs/fsconnector.go000066400000000000000000000326531364171671200201740ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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" "sync" "time" "unsafe" "github.com/hanwen/go-fuse/v2/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 // This lock prevents Lookup() and Forget() from running concurrently. // Locking at this level is a big hammer, but makes sure we don't return // forgotten nodes to the kernel. Problems solved by this lock: // https://github.com/hanwen/go-fuse/issues/168 // https://github.com/rfjakob/gocryptfs/issues/322 // // The lock is shared: several concurrent Lookups are allowed to be // run simultaneously, while Forget is exclusive. lookupLock sync.RWMutex } // 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(&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 // necessary. 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(nil, &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 i%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) } // FileNotifyStoreCache notifies the kernel about changed data of the inode. // // This call is similar to FileNotify, but instead of only invalidating a data // region, it puts updated data directly to the kernel cache: // // After this call completes, the kernel has put updated data into the inode's cache, // and will use data from that cache for non direct-IO reads from the inode // in corresponding data region. After kernel's cache data is evicted, the kernel // will have to issue new Read calls on user request to get data content. // // ENOENT is returned if the kernel does not currently have entry for this // inode in its dentry cache. func (c *FileSystemConnector) FileNotifyStoreCache(node *Inode, off int64, data []byte) fuse.Status { var nID uint64 if node == c.rootNode { nID = fuse.FUSE_ROOT_ID } else { nID = c.inodeMap.Handle(&node.handled) } if nID == 0 { // the kernel does not currently know about this inode. return fuse.ENOENT } return c.server.InodeNotifyStoreCache(nID, off, data) } // FileRetrieveCache retrieves data from kernel's inode cache. // // This call retrieves data from kernel's inode cache @ offset and up to // len(dest) bytes. If kernel cache has fewer consecutive data starting at // offset, that fewer amount is returned. In particular if inode data at offset // is not cached (0, OK) is returned. // // If the kernel does not currently have entry for this inode in its dentry // cache (0, OK) is still returned, pretending that the inode could be known to // the kernel, but kernel's inode cache is empty. func (c *FileSystemConnector) FileRetrieveCache(node *Inode, off int64, dest []byte) (n int, st fuse.Status) { var nID uint64 if node == c.rootNode { nID = fuse.FUSE_ROOT_ID } else { nID = c.inodeMap.Handle(&node.handled) } if nID == 0 { // the kernel does not currently know about this inode. // -> we can pretend that its cache for the inode is empty. return 0, fuse.OK } return c.server.InodeRetrieveCache(nID, off, dest) } // 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) } go-fuse-2.0.3/fuse/nodefs/fsmount.go000066400000000000000000000104501364171671200173330ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package nodefs import ( "log" "sync" "unsafe" "github.com/hanwen/go-fuse/v2/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 = *m.options.Owner } } func (m *fileSystemMount) fillEntry(out *fuse.EntryOut) { out.SetEntryTimeout(m.options.EntryTimeout) out.SetAttrTimeout(m.options.AttrTimeout) 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) { out.SetTimeout(m.options.AttrTimeout) 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 } // registerFileHandle registers f or dir to have a handle. // // The handle is then used as file-handle in communications with kernel. // // If dir != nil the handle is registered for OpenDir and the inner file (see // below) must be nil. If dir = nil the handle is registered for regular open & // friends. // // f can be nil, or a WithFlags that leads to File=nil. For !OpenDir, if that // is the case, returned handle will be 0 to indicate a handleless open, and // the filesystem operations on the opened file will be routed to be served by // the node. // // other arguments: // // node - Inode for which f or dir were opened, // flags - file open flags, like O_RDWR. func (m *fileSystemMount) registerFileHandle(node *Inode, dir *connectorDir, f File, flags uint32) (handle uint64, opened *openedFile) { 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 } // don't allow both dir and file if dir != nil && b.WithFlags.File != nil { panic("registerFileHandle: both dir and file are set.") } if b.WithFlags.File == nil && dir == nil { // it was just WithFlags{...}, but the file itself is nil return 0, b } if b.WithFlags.File != nil { b.WithFlags.File.SetInode(node) } node.openFilesMutex.Lock() 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 out.SetEntryTimeout(m.options.NegativeTimeout) return true } return false } go-fuse-2.0.3/fuse/nodefs/fsops.go000066400000000000000000000371551364171671200170050ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package nodefs // This file contains FileSystemConnector's implementation of // RawFileSystem import ( "fmt" "log" "strings" "time" "github.com/hanwen/go-fuse/v2/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(cancel <-chan struct{}, 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(cancel <-chan struct{}, 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(cancel <-chan struct{}, 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.mount.options.LookupKnownChildren { code = child.fsInode.GetAttr(out, nil, &fuse.Context{Caller: header.Caller, Cancel: cancel}) } else { child, code = parent.fsInode.Lookup(out, name, &fuse.Context{Caller: header.Caller, Cancel: cancel}) } return child, code } func (c *rawBridge) Lookup(cancel <-chan struct{}, header *fuse.InHeader, name string, out *fuse.EntryOut) (code fuse.Status) { // Prevent Lookup() and Forget() from running concurrently. // Allow several Lookups to be run simultaneously. c.lookupLock.RLock() defer c.lookupLock.RUnlock() parent := c.toInode(header.NodeId) if !parent.IsDir() { log.Printf("Lookup %q called on non-Directory node %d", name, header.NodeId) return fuse.ENOTDIR } child, code := c.fsConn().internalLookup(cancel, &out.Attr, 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) { // Prevent Lookup() and Forget() from running concurrently. c.lookupLock.Lock() defer c.lookupLock.Unlock() c.fsConn().forgetUpdate(nodeID, int(nlookup)) } func (c *rawBridge) GetAttr(cancel <-chan struct{}, 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 := &out.Attr code = node.fsInode.GetAttr(dest, f, &fuse.Context{Caller: input.Caller, Cancel: cancel}) 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(cancel <-chan struct{}, input *fuse.OpenIn, out *fuse.OpenOut) (code fuse.Status) { node := c.toInode(input.NodeId) de := &connectorDir{ inode: node, node: node.Node(), 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(cancel <-chan struct{}, input *fuse.ReadIn, out *fuse.DirEntryList) fuse.Status { node := c.toInode(input.NodeId) opened := node.mount.getOpenedFile(input.Fh) return opened.dir.ReadDir(cancel, input, out) } func (c *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out *fuse.DirEntryList) fuse.Status { node := c.toInode(input.NodeId) opened := node.mount.getOpenedFile(input.Fh) return opened.dir.ReadDirPlus(cancel, input, out) } func (c *rawBridge) Open(cancel <-chan struct{}, input *fuse.OpenIn, out *fuse.OpenOut) (status fuse.Status) { node := c.toInode(input.NodeId) f, code := node.fsInode.Open(input.Flags, &fuse.Context{Caller: input.Caller, Cancel: cancel}) if !code.Ok() { 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(cancel <-chan struct{}, input *fuse.SetAttrIn, out *fuse.AttrOut) (code fuse.Status) { node := c.toInode(input.NodeId) var f File if fh, ok := input.GetFh(); ok { if opened := node.mount.getOpenedFile(fh); opened != nil { f = opened.WithFlags.File } } if permissions, ok := input.GetMode(); ok { code = node.fsInode.Chmod(f, permissions, &fuse.Context{Caller: input.Caller, Cancel: cancel}) } uid, uok := input.GetUID() gid, gok := input.GetGID() if code.Ok() && (uok || gok) { code = node.fsInode.Chown(f, uid, gid, &fuse.Context{Caller: input.Caller, Cancel: cancel}) } if sz, ok := input.GetSize(); code.Ok() && ok { code = node.fsInode.Truncate(f, sz, &fuse.Context{Caller: input.Caller, Cancel: cancel}) } atime, aok := input.GetATime() mtime, mok := input.GetMTime() if code.Ok() && (aok || mok) { var a, m *time.Time if aok { a = &atime } if mok { m = &mtime } code = node.fsInode.Utimens(f, a, m, &fuse.Context{Caller: input.Caller, Cancel: cancel}) } if !code.Ok() { return code } // Must call GetAttr(); the filesystem may override some of // the changes we effect here. attr := &out.Attr code = node.fsInode.GetAttr(attr, nil, &fuse.Context{Caller: input.Caller, Cancel: cancel}) if code.Ok() { node.mount.fillAttr(out, input.NodeId) } return code } func (c *rawBridge) Fallocate(cancel <-chan struct{}, 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, &fuse.Context{Caller: input.Caller, Cancel: cancel}) } func (c *rawBridge) Readlink(cancel <-chan struct{}, header *fuse.InHeader) (out []byte, code fuse.Status) { n := c.toInode(header.NodeId) return n.fsInode.Readlink(&fuse.Context{Caller: header.Caller, Cancel: cancel}) } func (c *rawBridge) Mknod(cancel <-chan struct{}, 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), &fuse.Context{Caller: input.Caller, Cancel: cancel}) if code.Ok() { c.childLookup(out, child, &fuse.Context{Caller: input.Caller, Cancel: cancel}) code = child.fsInode.GetAttr(&out.Attr, nil, &fuse.Context{Caller: input.Caller, Cancel: cancel}) } return code } func (c *rawBridge) Mkdir(cancel <-chan struct{}, input *fuse.MkdirIn, name string, out *fuse.EntryOut) (code fuse.Status) { parent := c.toInode(input.NodeId) child, code := parent.fsInode.Mkdir(name, input.Mode, &fuse.Context{Caller: input.Caller, Cancel: cancel}) if code.Ok() { c.childLookup(out, child, &fuse.Context{Caller: input.Caller, Cancel: cancel}) code = child.fsInode.GetAttr(&out.Attr, nil, &fuse.Context{Caller: input.Caller, Cancel: cancel}) } return code } func (c *rawBridge) Unlink(cancel <-chan struct{}, header *fuse.InHeader, name string) (code fuse.Status) { parent := c.toInode(header.NodeId) return parent.fsInode.Unlink(name, &fuse.Context{Caller: header.Caller, Cancel: cancel}) } func (c *rawBridge) Rmdir(cancel <-chan struct{}, header *fuse.InHeader, name string) (code fuse.Status) { parent := c.toInode(header.NodeId) return parent.fsInode.Rmdir(name, &fuse.Context{Caller: header.Caller, Cancel: cancel}) } func (c *rawBridge) Symlink(cancel <-chan struct{}, 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, &fuse.Context{Caller: header.Caller, Cancel: cancel}) if code.Ok() { c.childLookup(out, child, &fuse.Context{Caller: header.Caller, Cancel: cancel}) code = child.fsInode.GetAttr(&out.Attr, nil, &fuse.Context{Caller: header.Caller, Cancel: cancel}) } return code } func (c *rawBridge) Rename(cancel <-chan struct{}, input *fuse.RenameIn, oldName string, newName string) (code fuse.Status) { if input.Flags != 0 { return fuse.ENOSYS } 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, &fuse.Context{Caller: input.Caller, Cancel: cancel}) } func (c *rawBridge) Link(cancel <-chan struct{}, 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, &fuse.Context{Caller: input.Caller, Cancel: cancel}) if code.Ok() { c.childLookup(out, child, &fuse.Context{Caller: input.Caller, Cancel: cancel}) code = child.fsInode.GetAttr(&out.Attr, nil, &fuse.Context{Caller: input.Caller, Cancel: cancel}) } return code } func (c *rawBridge) Access(cancel <-chan struct{}, input *fuse.AccessIn) (code fuse.Status) { n := c.toInode(input.NodeId) return n.fsInode.Access(input.Mask, &fuse.Context{Caller: input.Caller, Cancel: cancel}) } func (c *rawBridge) Create(cancel <-chan struct{}, 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, &fuse.Context{Caller: input.Caller, Cancel: cancel}) if !code.Ok() { return code } c.childLookup(&out.EntryOut, child, &fuse.Context{Caller: input.Caller, Cancel: cancel}) 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(cancel <-chan struct{}, 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) GetXAttr(cancel <-chan struct{}, header *fuse.InHeader, attribute string, dest []byte) (sz uint32, code fuse.Status) { node := c.toInode(header.NodeId) data, errno := node.fsInode.GetXAttr(attribute, &fuse.Context{Caller: header.Caller, Cancel: cancel}) if len(data) > len(dest) { return uint32(len(data)), fuse.ERANGE } copy(dest, data) return uint32(len(data)), errno } func (c *rawBridge) GetXAttrData(cancel <-chan struct{}, header *fuse.InHeader, attribute string) (data []byte, code fuse.Status) { node := c.toInode(header.NodeId) return node.fsInode.GetXAttr(attribute, &fuse.Context{Caller: header.Caller, Cancel: cancel}) } func (c *rawBridge) RemoveXAttr(cancel <-chan struct{}, header *fuse.InHeader, attr string) fuse.Status { node := c.toInode(header.NodeId) return node.fsInode.RemoveXAttr(attr, &fuse.Context{Caller: header.Caller, Cancel: cancel}) } func (c *rawBridge) SetXAttr(cancel <-chan struct{}, input *fuse.SetXAttrIn, attr string, data []byte) fuse.Status { node := c.toInode(input.NodeId) return node.fsInode.SetXAttr(attr, data, int(input.Flags), &fuse.Context{Caller: input.Caller, Cancel: cancel}) } func (c *rawBridge) ListXAttr(cancel <-chan struct{}, header *fuse.InHeader, dest []byte) (uint32, fuse.Status) { node := c.toInode(header.NodeId) attrs, code := node.fsInode.ListXAttr(&fuse.Context{Caller: header.Caller, Cancel: cancel}) if code != fuse.OK { return 0, code } var sz uint32 for _, v := range attrs { sz += uint32(len(v)) + 1 } if int(sz) > len(dest) { return sz, fuse.ERANGE } dest = dest[:0] for _, v := range attrs { dest = append(dest, v...) dest = append(dest, 0) } return sz, code } //////////////// // files. func (c *rawBridge) Write(cancel <-chan struct{}, 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), &fuse.Context{Caller: input.Caller, Cancel: cancel}) } func (c *rawBridge) Read(cancel <-chan struct{}, 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), &fuse.Context{Caller: input.Caller, Cancel: cancel}) } func (c *rawBridge) GetLk(cancel <-chan struct{}, input *fuse.LkIn, out *fuse.LkOut) (code fuse.Status) { n := c.toInode(input.NodeId) opened := n.mount.getOpenedFile(input.Fh) return n.fsInode.GetLk(opened, input.Owner, &input.Lk, input.LkFlags, &out.Lk, &fuse.Context{Caller: input.Caller, Cancel: cancel}) } func (c *rawBridge) SetLk(cancel <-chan struct{}, input *fuse.LkIn) (code fuse.Status) { n := c.toInode(input.NodeId) opened := n.mount.getOpenedFile(input.Fh) return n.fsInode.SetLk(opened, input.Owner, &input.Lk, input.LkFlags, &fuse.Context{Caller: input.Caller, Cancel: cancel}) } func (c *rawBridge) SetLkw(cancel <-chan struct{}, input *fuse.LkIn) (code fuse.Status) { n := c.toInode(input.NodeId) opened := n.mount.getOpenedFile(input.Fh) return n.fsInode.SetLkw(opened, input.Owner, &input.Lk, input.LkFlags, &fuse.Context{Caller: input.Caller, Cancel: cancel}) } func (c *rawBridge) StatFs(cancel <-chan struct{}, 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(cancel <-chan struct{}, 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 } func (c *rawBridge) CopyFileRange(cancel <-chan struct{}, input *fuse.CopyFileRangeIn) (written uint32, code fuse.Status) { return 0, fuse.ENOSYS } func (fs *rawBridge) Lseek(cancel <-chan struct{}, in *fuse.LseekIn, out *fuse.LseekOut) fuse.Status { return fuse.ENOSYS } go-fuse-2.0.3/fuse/nodefs/fuse.go000066400000000000000000000017551364171671200166120ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package nodefs import ( "github.com/hanwen/go-fuse/v2/fuse" ) // Mount mounts a filesystem with the given root node on the given directory. // Convenience wrapper around fuse.NewServer func Mount(mountpoint string, root Node, mountOptions *fuse.MountOptions, nodefsOptions *Options) (*fuse.Server, *FileSystemConnector, error) { conn := NewFileSystemConnector(root, nodefsOptions) s, err := fuse.NewServer(conn.RawFS(), mountpoint, mountOptions) if err != nil { return nil, nil, err } return s, conn, nil } // MountRoot is like Mount but uses default fuse mount options. func MountRoot(mountpoint string, root Node, opts *Options) (*fuse.Server, *FileSystemConnector, error) { mountOpts := &fuse.MountOptions{} if opts != nil && opts.Debug { mountOpts.Debug = opts.Debug } return Mount(mountpoint, root, mountOpts, opts) } go-fuse-2.0.3/fuse/nodefs/handle.go000066400000000000000000000072151364171671200171000ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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 { handle uint64 generation 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() defer m.Unlock() // Reuse existing handle if obj.count != 0 { obj.count++ return obj.handle, obj.generation } // Create a new handle number or recycle one on from the free list if len(m.freeIds) == 0 { obj.handle = uint64(len(m.handles)) m.handles = append(m.handles, obj) } else { obj.handle = m.freeIds[len(m.freeIds)-1] m.freeIds = m.freeIds[:len(m.freeIds)-1] m.handles[obj.handle] = obj } // Increment generation number to guarantee the (handle, generation) tuple // is unique m.generation++ m.used++ obj.generation = m.generation obj.count++ return obj.handle, obj.generation } 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 } go-fuse-2.0.3/fuse/nodefs/handle_test.go000066400000000000000000000063371364171671200201430ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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") } } 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) } } func TestHandleMapGenerationKnown(t *testing.T) { hm := newPortableHandleMap() o1 := &handled{} h1, g1 := hm.Register(o1) o2 := &handled{} h2, _ := hm.Register(o2) h3, g3 := hm.Register(o1) if h1 != h3 { t.Fatalf("register known should reuse handle: got %d want %d.", h3, h1) } if g1 != g3 { t.Fatalf("register known should reuse generation: got %d want %d.", g3, g1) } hm.Forget(h1, 2) hm.Forget(h2, 1) h1, g1 = hm.Register(o1) h2, _ = hm.Register(o2) h3, g3 = hm.Register(o1) if h1 != h3 { t.Fatalf("register known should reuse handle: got %d want %d.", h3, h1) } if g1 != g3 { t.Fatalf("register known should reuse generation: got %d want %d.", g3, g1) } } go-fuse-2.0.3/fuse/nodefs/inode.go000066400000000000000000000161421364171671200167420ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package nodefs import ( "fmt" "log" "sync" "github.com/hanwen/go-fuse/v2/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 // 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. // Print the inode. The default print method may not be used for // debugging, as dumping the map requires synchronization. func (n *Inode) String() string { return fmt.Sprintf("node{%d}", n.handled.handle) } // 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, "" } n.mount.treeLock.RLock() defer n.mount.treeLock.RUnlock() 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) } } go-fuse-2.0.3/fuse/nodefs/lockingfile.go000066400000000000000000000051341364171671200201310ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package nodefs import ( "fmt" "sync" "time" "github.com/hanwen/go-fuse/v2/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) GetLk(owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) (code fuse.Status) { f.mu.Lock() defer f.mu.Unlock() return f.file.GetLk(owner, lk, flags, out) } func (f *lockingFile) SetLk(owner uint64, lk *fuse.FileLock, flags uint32) (code fuse.Status) { f.mu.Lock() defer f.mu.Unlock() return f.file.SetLk(owner, lk, flags) } func (f *lockingFile) SetLkw(owner uint64, lk *fuse.FileLock, flags uint32) (code fuse.Status) { f.mu.Lock() defer f.mu.Unlock() return f.file.SetLkw(owner, lk, flags) } 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) } go-fuse-2.0.3/fuse/nodefs/memnode.go000066400000000000000000000126521364171671200172720ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package nodefs import ( "fmt" "os" "sync" "syscall" "time" "github.com/hanwen/go-fuse/v2/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 mu sync.Mutex 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) { n.mu.Lock() defer n.mu.Unlock() 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.mu.Lock() defer n.node.mu.Unlock() 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) { n.mu.Lock() defer n.mu.Unlock() *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.mu.Lock() defer n.mu.Unlock() 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.mu.Lock() defer n.mu.Unlock() 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.mu.Lock() defer n.mu.Unlock() 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.mu.Lock() defer n.mu.Unlock() n.info.SetTimes(nil, nil, &now) return fuse.OK } go-fuse-2.0.3/fuse/nodefs/memnode_test.go000066400000000000000000000056301364171671200203270ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package nodefs import ( "io/ioutil" "os" "testing" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/internal/testutil" ) 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: testutil.VerboseTest(), LookupKnownChildren: true, }) state, err := fuse.NewServer(connector.RawFS(), mnt, &fuse.MountOptions{Debug: testutil.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()) } if err := f.Chown(21, 42); err != nil { t.Errorf("Chown: %v", err) } if fi, err := f.Stat(); err != nil { t.Fatalf("Stat failed: %v", err) } else { attr := fuse.ToStatT(fi) if attr.Gid != 42 || attr.Uid != 21 { t.Errorf("got (%d, %d), want 42,21", attr.Uid, attr.Gid) } } } go-fuse-2.0.3/fuse/nodefs/syscall_linux.go000066400000000000000000000010461364171671200205320ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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 } go-fuse-2.0.3/fuse/opcode.go000066400000000000000000000672171364171671200156500ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fuse import ( "bytes" "fmt" "log" "reflect" "runtime" "time" "unsafe" ) const ( _OP_LOOKUP = uint32(1) _OP_FORGET = uint32(2) _OP_GETATTR = uint32(3) _OP_SETATTR = uint32(4) _OP_READLINK = uint32(5) _OP_SYMLINK = uint32(6) _OP_MKNOD = uint32(8) _OP_MKDIR = uint32(9) _OP_UNLINK = uint32(10) _OP_RMDIR = uint32(11) _OP_RENAME = uint32(12) _OP_LINK = uint32(13) _OP_OPEN = uint32(14) _OP_READ = uint32(15) _OP_WRITE = uint32(16) _OP_STATFS = uint32(17) _OP_RELEASE = uint32(18) _OP_FSYNC = uint32(20) _OP_SETXATTR = uint32(21) _OP_GETXATTR = uint32(22) _OP_LISTXATTR = uint32(23) _OP_REMOVEXATTR = uint32(24) _OP_FLUSH = uint32(25) _OP_INIT = uint32(26) _OP_OPENDIR = uint32(27) _OP_READDIR = uint32(28) _OP_RELEASEDIR = uint32(29) _OP_FSYNCDIR = uint32(30) _OP_GETLK = uint32(31) _OP_SETLK = uint32(32) _OP_SETLKW = uint32(33) _OP_ACCESS = uint32(34) _OP_CREATE = uint32(35) _OP_INTERRUPT = uint32(36) _OP_BMAP = uint32(37) _OP_DESTROY = uint32(38) _OP_IOCTL = uint32(39) _OP_POLL = uint32(40) _OP_NOTIFY_REPLY = uint32(41) _OP_BATCH_FORGET = uint32(42) _OP_FALLOCATE = uint32(43) // protocol version 19. _OP_READDIRPLUS = uint32(44) // protocol version 21. _OP_RENAME2 = uint32(45) // protocol version 23. _OP_LSEEK = uint32(46) // protocol version 24 _OP_COPY_FILE_RANGE = uint32(47) // protocol version 28. // The following entries don't have to be compatible across Go-FUSE versions. _OP_NOTIFY_INVAL_ENTRY = uint32(100) _OP_NOTIFY_INVAL_INODE = uint32(101) _OP_NOTIFY_STORE_CACHE = uint32(102) _OP_NOTIFY_RETRIEVE_CACHE = uint32(103) _OP_NOTIFY_DELETE = uint32(104) // protocol version 18 _OPCODE_COUNT = uint32(105) ) //////////////////////////////////////////////////////////////// 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_READDIRPLUS | CAP_NO_OPEN_SUPPORT | CAP_PARALLEL_DIROPS) if server.opts.EnableLocks { server.kernelSettings.Flags |= CAP_FLOCK_LOCKS | CAP_POSIX_LOCKS } dataCacheMode := input.Flags & CAP_AUTO_INVAL_DATA if server.opts.ExplicitDataCacheControl { // we don't want CAP_AUTO_INVAL_DATA even if we cannot go into fully explicit mode dataCacheMode = 0 explicit := input.Flags & CAP_EXPLICIT_INVAL_DATA if explicit != 0 { dataCacheMode = explicit } } server.kernelSettings.Flags |= dataCacheMode if input.Minor >= 13 { server.setSplice() } server.reqMu.Unlock() out := (*InitOut)(req.outData()) *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 server.opts.MaxReadAhead != 0 && uint32(server.opts.MaxReadAhead) < out.MaxReadAhead { out.MaxReadAhead = uint32(server.opts.MaxReadAhead) } 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.status = OK } func doOpen(server *Server, req *request) { out := (*OpenOut)(req.outData()) status := server.fileSystem.Open(req.cancel, (*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(req.cancel, (*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(req.cancel, 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(req.cancel, in, out) req.flatData = out.bytes() req.status = code } func doOpenDir(server *Server, req *request) { out := (*OpenOut)(req.outData()) status := server.fileSystem.OpenDir(req.cancel, (*OpenIn)(req.inData), out) req.status = status } func doSetattr(server *Server, req *request) { out := (*AttrOut)(req.outData()) req.status = server.fileSystem.SetAttr(req.cancel, (*SetAttrIn)(req.inData), out) } func doWrite(server *Server, req *request) { n, status := server.fileSystem.Write(req.cancel, (*WriteIn)(req.inData), req.arg) o := (*WriteOut)(req.outData()) o.Size = n req.status = status } func doNotifyReply(server *Server, req *request) { reply := (*NotifyRetrieveIn)(req.inData) server.retrieveMu.Lock() reading := server.retrieveTab[reply.Unique] delete(server.retrieveTab, reply.Unique) server.retrieveMu.Unlock() badf := func(format string, argv ...interface{}) { log.Printf("notify reply: "+format, argv...) } if reading == nil { badf("unexpected unique - ignoring") return } reading.n = 0 reading.st = EIO defer close(reading.ready) if reading.nodeid != reply.NodeId { badf("inode mismatch: expected %s, got %s", reading.nodeid, reply.NodeId) return } if reading.offset != reply.Offset { badf("offset mismatch: expected @%d, got @%d", reading.offset, reply.Offset) return } if len(reading.dest) < len(req.arg) { badf("too much data: requested %db, got %db (will use only %db)", len(reading.dest), len(req.arg), len(reading.dest)) } reading.n = copy(reading.dest, req.arg) reading.st = OK } 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 = ENOATTR return } } input := (*GetXAttrIn)(req.inData) req.flatData = server.allocOut(req, input.Size) out := (*GetXAttrOut)(req.outData()) var n uint32 switch req.inHeader.Opcode { case _OP_GETXATTR: n, req.status = server.fileSystem.GetXAttr(req.cancel, req.inHeader, req.filenames[0], req.flatData) case _OP_LISTXATTR: n, req.status = server.fileSystem.ListXAttr(req.cancel, req.inHeader, req.flatData) default: req.status = ENOSYS } if input.Size == 0 && req.status == ERANGE { // For input.size==0, returning ERANGE is an error. req.status = OK out.Size = n } else if req.status.Ok() { // ListXAttr called with an empty buffer returns the current size of // the list but does not touch the buffer (see man 2 listxattr). if len(req.flatData) > 0 { req.flatData = req.flatData[:n] } out.Size = n } else { req.flatData = req.flatData[:0] } } func doGetAttr(server *Server, req *request) { out := (*AttrOut)(req.outData()) s := server.fileSystem.GetAttr(req.cancel, (*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: rx %d %d/%d: FORGET i%d {Nlookup=%d}", req.inHeader.Unique, i+1, len(forgets), f.NodeId, f.Nlookup) } if f.NodeId == pollHackInode { continue } server.fileSystem.Forget(f.NodeId, f.Nlookup) } } func doReadlink(server *Server, req *request) { req.flatData, req.status = server.fileSystem.Readlink(req.cancel, req.inHeader) } func doLookup(server *Server, req *request) { out := (*EntryOut)(req.outData()) s := server.fileSystem.Lookup(req.cancel, req.inHeader, req.filenames[0], out) req.status = s } func doMknod(server *Server, req *request) { out := (*EntryOut)(req.outData()) req.status = server.fileSystem.Mknod(req.cancel, (*MknodIn)(req.inData), req.filenames[0], out) } func doMkdir(server *Server, req *request) { out := (*EntryOut)(req.outData()) req.status = server.fileSystem.Mkdir(req.cancel, (*MkdirIn)(req.inData), req.filenames[0], out) } func doUnlink(server *Server, req *request) { req.status = server.fileSystem.Unlink(req.cancel, req.inHeader, req.filenames[0]) } func doRmdir(server *Server, req *request) { req.status = server.fileSystem.Rmdir(req.cancel, req.inHeader, req.filenames[0]) } func doLink(server *Server, req *request) { out := (*EntryOut)(req.outData()) req.status = server.fileSystem.Link(req.cancel, (*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(req.cancel, 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(req.cancel, (*FlushIn)(req.inData)) } func doRelease(server *Server, req *request) { server.fileSystem.Release(req.cancel, (*ReleaseIn)(req.inData)) } func doFsync(server *Server, req *request) { req.status = server.fileSystem.Fsync(req.cancel, (*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(req.cancel, (*FsyncIn)(req.inData)) } func doSetXAttr(server *Server, req *request) { splits := bytes.SplitN(req.arg, []byte{0}, 2) req.status = server.fileSystem.SetXAttr(req.cancel, (*SetXAttrIn)(req.inData), string(splits[0]), splits[1]) } func doRemoveXAttr(server *Server, req *request) { req.status = server.fileSystem.RemoveXAttr(req.cancel, req.inHeader, req.filenames[0]) } func doAccess(server *Server, req *request) { req.status = server.fileSystem.Access(req.cancel, (*AccessIn)(req.inData)) } func doSymlink(server *Server, req *request) { out := (*EntryOut)(req.outData()) req.status = server.fileSystem.Symlink(req.cancel, req.inHeader, req.filenames[1], req.filenames[0], out) } func doRename(server *Server, req *request) { in1 := (*Rename1In)(req.inData) in := RenameIn{ InHeader: in1.InHeader, Newdir: in1.Newdir, } req.status = server.fileSystem.Rename(req.cancel, &in, req.filenames[0], req.filenames[1]) } func doRename2(server *Server, req *request) { req.status = server.fileSystem.Rename(req.cancel, (*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.cancel, 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(req.cancel, (*FallocateIn)(req.inData)) } func doGetLk(server *Server, req *request) { req.status = server.fileSystem.GetLk(req.cancel, (*LkIn)(req.inData), (*LkOut)(req.outData())) } func doSetLk(server *Server, req *request) { req.status = server.fileSystem.SetLk(req.cancel, (*LkIn)(req.inData)) } func doSetLkw(server *Server, req *request) { req.status = server.fileSystem.SetLkw(req.cancel, (*LkIn)(req.inData)) } func doLseek(server *Server, req *request) { in := (*LseekIn)(req.inData) out := (*LseekOut)(req.outData()) req.status = server.fileSystem.Lseek(req.cancel, in, out) } func doCopyFileRange(server *Server, req *request) { in := (*CopyFileRangeIn)(req.inData) out := (*WriteOut)(req.outData()) out.Size, req.status = server.fileSystem.CopyFileRange(req.cancel, in) } func doInterrupt(server *Server, req *request) { input := (*InterruptIn)(req.inData) server.reqMu.Lock() defer server.reqMu.Unlock() // This is slow, but this operation is rare. for _, inflight := range server.reqInflight { if input.Unique == inflight.inHeader.Unique && !inflight.interrupted { close(inflight.cancel) inflight.interrupted = true req.status = OK return } } // not found; wait for a bit time.Sleep(10 * time.Microsecond) req.status = EAGAIN } //////////////////////////////////////////////////////////////// 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 uint32) string { h := getHandler(op) if h == nil { return "unknown" } return h.Name } func getHandler(o uint32) *operationHandler { if o >= _OPCODE_COUNT { return nil } return operationHandlers[o] } var maxInputSize uintptr func init() { operationHandlers = make([]*operationHandler, _OPCODE_COUNT) for i := range operationHandlers { operationHandlers[i] = &operationHandler{Name: fmt.Sprintf("OPCODE-%d", i)} } fileOps := []uint32{_OP_READLINK, _OP_NOTIFY_INVAL_ENTRY, _OP_NOTIFY_DELETE} for _, op := range fileOps { operationHandlers[op].FileNameOut = true } maxInputSize = 0 for op, sz := range map[uint32]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(Rename1In{}), _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_GETLK: unsafe.Sizeof(LkIn{}), _OP_SETLK: unsafe.Sizeof(LkIn{}), _OP_SETLKW: unsafe.Sizeof(LkIn{}), _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_NOTIFY_REPLY: unsafe.Sizeof(NotifyRetrieveIn{}), _OP_FALLOCATE: unsafe.Sizeof(FallocateIn{}), _OP_READDIRPLUS: unsafe.Sizeof(ReadIn{}), _OP_RENAME2: unsafe.Sizeof(RenameIn{}), _OP_LSEEK: unsafe.Sizeof(LseekIn{}), _OP_COPY_FILE_RANGE: unsafe.Sizeof(CopyFileRangeIn{}), } { operationHandlers[op].InputSize = sz if sz > maxInputSize { maxInputSize = sz } } for op, sz := range map[uint32]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_GETLK: unsafe.Sizeof(LkOut{}), _OP_CREATE: unsafe.Sizeof(CreateOut{}), _OP_BMAP: unsafe.Sizeof(_BmapOut{}), _OP_IOCTL: unsafe.Sizeof(_IoctlOut{}), _OP_POLL: unsafe.Sizeof(_PollOut{}), _OP_NOTIFY_INVAL_ENTRY: unsafe.Sizeof(NotifyInvalEntryOut{}), _OP_NOTIFY_INVAL_INODE: unsafe.Sizeof(NotifyInvalInodeOut{}), _OP_NOTIFY_STORE_CACHE: unsafe.Sizeof(NotifyStoreOut{}), _OP_NOTIFY_RETRIEVE_CACHE: unsafe.Sizeof(NotifyRetrieveOut{}), _OP_NOTIFY_DELETE: unsafe.Sizeof(NotifyInvalDeleteOut{}), _OP_LSEEK: unsafe.Sizeof(LseekOut{}), _OP_COPY_FILE_RANGE: unsafe.Sizeof(WriteOut{}), } { operationHandlers[op].OutputSize = sz } for op, v := range map[uint32]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_REPLY: "NOTIFY_REPLY", _OP_NOTIFY_INVAL_ENTRY: "NOTIFY_INVAL_ENTRY", _OP_NOTIFY_INVAL_INODE: "NOTIFY_INVAL_INODE", _OP_NOTIFY_STORE_CACHE: "NOTIFY_STORE", _OP_NOTIFY_RETRIEVE_CACHE: "NOTIFY_RETRIEVE", _OP_NOTIFY_DELETE: "NOTIFY_DELETE", _OP_FALLOCATE: "FALLOCATE", _OP_READDIRPLUS: "READDIRPLUS", _OP_RENAME2: "RENAME2", _OP_LSEEK: "LSEEK", _OP_COPY_FILE_RANGE: "COPY_FILE_RANGE", } { operationHandlers[op].Name = v } for op, v := range map[uint32]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_GETLK: doGetLk, _OP_SETLK: doSetLk, _OP_SETLKW: doSetLkw, _OP_ACCESS: doAccess, _OP_SYMLINK: doSymlink, _OP_RENAME: doRename, _OP_STATFS: doStatFs, _OP_IOCTL: doIoctl, _OP_DESTROY: doDestroy, _OP_NOTIFY_REPLY: doNotifyReply, _OP_FALLOCATE: doFallocate, _OP_READDIRPLUS: doReadDirPlus, _OP_RENAME2: doRename2, _OP_INTERRUPT: doInterrupt, _OP_COPY_FILE_RANGE: doCopyFileRange, _OP_LSEEK: doLseek, } { operationHandlers[op].Func = v } // Outputs. for op, f := range map[uint32]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_INVAL_ENTRY: func(ptr unsafe.Pointer) interface{} { return (*NotifyInvalEntryOut)(ptr) }, _OP_NOTIFY_INVAL_INODE: func(ptr unsafe.Pointer) interface{} { return (*NotifyInvalInodeOut)(ptr) }, _OP_NOTIFY_STORE_CACHE: func(ptr unsafe.Pointer) interface{} { return (*NotifyStoreOut)(ptr) }, _OP_NOTIFY_RETRIEVE_CACHE: func(ptr unsafe.Pointer) interface{} { return (*NotifyRetrieveOut)(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) }, _OP_GETLK: func(ptr unsafe.Pointer) interface{} { return (*LkOut)(ptr) }, _OP_LSEEK: func(ptr unsafe.Pointer) interface{} { return (*LseekOut)(ptr) }, _OP_COPY_FILE_RANGE: func(ptr unsafe.Pointer) interface{} { return (*WriteOut)(ptr) }, } { operationHandlers[op].DecodeOut = f } // Inputs. for op, f := range map[uint32]castPointerFunc{ _OP_FLUSH: func(ptr unsafe.Pointer) interface{} { return (*FlushIn)(ptr) }, _OP_GETATTR: func(ptr unsafe.Pointer) interface{} { return (*GetAttrIn)(ptr) }, _OP_SETXATTR: func(ptr unsafe.Pointer) interface{} { return (*SetXAttrIn)(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_WRITE: func(ptr unsafe.Pointer) interface{} { return (*WriteIn)(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_NOTIFY_REPLY: func(ptr unsafe.Pointer) interface{} { return (*NotifyRetrieveIn)(ptr) }, _OP_READDIRPLUS: func(ptr unsafe.Pointer) interface{} { return (*ReadIn)(ptr) }, _OP_RENAME: func(ptr unsafe.Pointer) interface{} { return (*Rename1In)(ptr) }, _OP_GETLK: func(ptr unsafe.Pointer) interface{} { return (*LkIn)(ptr) }, _OP_SETLK: func(ptr unsafe.Pointer) interface{} { return (*LkIn)(ptr) }, _OP_SETLKW: func(ptr unsafe.Pointer) interface{} { return (*LkIn)(ptr) }, _OP_RENAME2: func(ptr unsafe.Pointer) interface{} { return (*RenameIn)(ptr) }, _OP_INTERRUPT: func(ptr unsafe.Pointer) interface{} { return (*InterruptIn)(ptr) }, _OP_LSEEK: func(ptr unsafe.Pointer) interface{} { return (*LseekIn)(ptr) }, _OP_COPY_FILE_RANGE: func(ptr unsafe.Pointer) interface{} { return (*CopyFileRangeIn)(ptr) }, } { operationHandlers[op].DecodeIn = f } // File name args. for op, count := range map[uint32]int{ _OP_CREATE: 1, _OP_SETXATTR: 1, _OP_GETXATTR: 1, _OP_LINK: 1, _OP_LOOKUP: 1, _OP_MKDIR: 1, _OP_MKNOD: 1, _OP_REMOVEXATTR: 1, _OP_RENAME: 2, _OP_RENAME2: 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) } } } go-fuse-2.0.3/fuse/pathfs/000077500000000000000000000000001364171671200153205ustar00rootroot00000000000000go-fuse-2.0.3/fuse/pathfs/api.go000066400000000000000000000066371364171671200164340ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // This package is deprecated. New projects should use the package // "github.com/hanwen/go-fuse/v2/fs" instead. // // Package pathfs provides a file system API expressed in filenames. package pathfs import ( "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/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 } go-fuse-2.0.3/fuse/pathfs/copy.go000066400000000000000000000022331364171671200166210ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package pathfs import ( "os" "github.com/hanwen/go-fuse/v2/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 } go-fuse-2.0.3/fuse/pathfs/copy_test.go000066400000000000000000000023641364171671200176650ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package pathfs import ( "io/ioutil" "os" "testing" "github.com/hanwen/go-fuse/v2/internal/testutil" ) func TestCopyFile(t *testing.T) { d1 := testutil.TempDir() defer os.RemoveAll(d1) d2 := testutil.TempDir() 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)) } } go-fuse-2.0.3/fuse/pathfs/default.go000066400000000000000000000072341364171671200173010ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package pathfs import ( "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/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.ENOATTR } 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 } go-fuse-2.0.3/fuse/pathfs/locking.go000066400000000000000000000117451364171671200173050ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package pathfs import ( "sync" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/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) } go-fuse-2.0.3/fuse/pathfs/loopback.go000066400000000000000000000136171364171671200174510ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package pathfs import ( "io" "log" "os" "path/filepath" "syscall" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/internal" ) 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 { // Make sure the Root path is absolute to avoid problems when the // application changes working directory. root, err := filepath.Abs(root) if err != nil { panic(err) } return &loopbackFileSystem{ FileSystem: NewDefaultFileSystem(), Root: root, } } func (fs *loopbackFileSystem) StatFs(name string) *fuse.StatfsOut { s := syscall.Statfs_t{} err := syscall.Statfs(fs.GetPath(name), &s) if err == nil { out := &fuse.StatfsOut{} out.FromStatfsT(&s) return out } return nil } 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 for https://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) d.Ino = s.Ino } 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) { // filter out append. The kernel layer will translate the // offsets for us appropriately. flags = flags &^ syscall.O_APPEND 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) { oldPath = fs.GetPath(oldPath) newPath = fs.GetPath(newPath) err := os.Rename(oldPath, 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) { attr, status := fs.GetAttr(name, context) if !status.Ok() { return status } if !internal.HasAccess(context.Uid, context.Gid, attr.Uid, attr.Gid, attr.Mode, mode) { return fuse.EACCES } return fuse.OK } func (fs *loopbackFileSystem) Create(path string, flags uint32, mode uint32, context *fuse.Context) (fuseFile nodefs.File, code fuse.Status) { flags = flags &^ syscall.O_APPEND f, err := os.OpenFile(fs.GetPath(path), int(flags)|os.O_CREATE, os.FileMode(mode)) return nodefs.NewLoopbackFile(f), fuse.ToStatus(err) } go-fuse-2.0.3/fuse/pathfs/loopback_darwin.go000066400000000000000000000014411364171671200210050ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package pathfs import ( "syscall" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/internal/utimens" ) func (fs *loopbackFileSystem) Utimens(path string, a *time.Time, m *time.Time, context *fuse.Context) fuse.Status { // MacOS before High Sierra lacks utimensat() and UTIME_OMIT. // We emulate using utimes() and extra GetAttr() calls. var attr *fuse.Attr if a == nil || m == nil { var status fuse.Status attr, status = fs.GetAttr(path, context) if !status.Ok() { return status } } tv := utimens.Fill(a, m, attr) err := syscall.Utimes(fs.GetPath(path), tv) return fuse.ToStatus(err) } go-fuse-2.0.3/fuse/pathfs/loopback_linux.go000066400000000000000000000032331364171671200206610ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package pathfs import ( "fmt" "syscall" "time" "github.com/hanwen/go-fuse/v2/fuse" ) func (fs *loopbackFileSystem) ListXAttr(name string, context *fuse.Context) ([]string, fuse.Status) { attrs, err := listXAttr(fs.GetPath(name)) return attrs, fuse.ToStatus(err) } func (fs *loopbackFileSystem) RemoveXAttr(name string, attr string, context *fuse.Context) fuse.Status { err := syscall.Removexattr(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) { bufsz := 1024 for { data := make([]byte, bufsz) sz, err := syscall.Getxattr(fs.GetPath(name), attr, data) if err == nil { return data[:sz], fuse.OK } if err == syscall.ERANGE { bufsz = sz continue } return nil, 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) } // 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 ts[0] = fuse.UtimeToTimespec(a) ts[1] = fuse.UtimeToTimespec(m) err := sysUtimensat(0, fs.GetPath(path), &ts, _AT_SYMLINK_NOFOLLOW) return fuse.ToStatus(err) } go-fuse-2.0.3/fuse/pathfs/loopback_test.go000066400000000000000000000015311364171671200205000ustar00rootroot00000000000000// Copyright 2018 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package pathfs import ( "io/ioutil" "os" "path/filepath" "syscall" "testing" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/internal/testutil" ) // Check that loopbackFileSystem.Utimens() works as expected func TestLoopbackFileSystemUtimens(t *testing.T) { fs := NewLoopbackFileSystem(os.TempDir()) f, err := ioutil.TempFile("", "TestLoopbackFileSystemUtimens") if err != nil { t.Fatal(err) } path := f.Name() name := filepath.Base(path) f.Close() defer syscall.Unlink(path) utimensFn := func(atime *time.Time, mtime *time.Time) fuse.Status { return fs.Utimens(name, atime, mtime, nil) } testutil.TestLoopbackUtimens(t, path, utimensFn) } go-fuse-2.0.3/fuse/pathfs/owner_test.go000066400000000000000000000043271364171671200200460ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package pathfs import ( "os" "syscall" "testing" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/internal/testutil" ) 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 := testutil.TempDir() opts.Debug = testutil.VerboseTest() 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.Fatalf("WaitMount: %v", 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") } } go-fuse-2.0.3/fuse/pathfs/pathfs.go000066400000000000000000000515341364171671200171440ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package pathfs import ( "fmt" "log" "path/filepath" "strings" "sync" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/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. 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, '/') } } 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. return ".deleted." + n.inode.String() } 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) { n.pathFs.pathLock.Lock() defer n.pathFs.pathLock.Unlock() if ino == 0 || n.clientInode != 0 || !n.pathFs.options.ClientInodes || n.Inode().IsDir() { return } 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) (*nodefs.Inode, fuse.Status) { fullPath := filepath.Join(n.GetPath(), name) fi, code := n.fs.GetAttr(fullPath, context) node := n.Inode().GetChild(name) if node != nil && (!code.Ok() || node.IsDir() != fi.IsDir()) { n.Inode().RmChild(name) node = nil } if code.Ok() { if node == nil { 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 we have found an open file, try to fstat it. if file != nil { code = file.GetAttr(out) if code.Ok() { return code } // ENOSYS and EBADF are retried below. Error out for other codes. if code != fuse.ENOSYS && code != fuse.EBADF { return code } } // If we don't have an open file, or fstat on it failed due to an internal // error, stat by path. if file == nil || code == fuse.ENOSYS || code == fuse.EBADF { fi, code = n.fs.GetAttr(n.GetPath(), context) if !code.Ok() { return code } // This is a bug in the filesystem implementation, but let's not // crash. if fi == nil { log.Printf("Bug: fs.GetAttr returned OK with nil data") return fuse.EINVAL } } // Set inode number (unless already set or disabled). n.setClientInode(fi.Ino) // Help filesystems that forget to set Nlink. if !fi.IsDir() && fi.Nlink == 0 { fi.Nlink = 1 } *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 } func (n *pathInode) GetLk(file nodefs.File, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock, context *fuse.Context) (code fuse.Status) { if file != nil { return file.GetLk(owner, lk, flags, out) } return fuse.ENOSYS } func (n *pathInode) SetLk(file nodefs.File, owner uint64, lk *fuse.FileLock, flags uint32, context *fuse.Context) (code fuse.Status) { if file != nil { return file.SetLk(owner, lk, flags) } return fuse.ENOSYS } func (n *pathInode) SetLkw(file nodefs.File, owner uint64, lk *fuse.FileLock, flags uint32, context *fuse.Context) (code fuse.Status) { if file != nil { return file.SetLkw(owner, lk, flags) } return fuse.ENOSYS } go-fuse-2.0.3/fuse/pathfs/prefixfs.go000066400000000000000000000114171364171671200175010ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package pathfs import ( "fmt" "path/filepath" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/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)) } go-fuse-2.0.3/fuse/pathfs/readonlyfs.go000066400000000000000000000075031364171671200200220ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package pathfs import ( "fmt" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/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 } go-fuse-2.0.3/fuse/pathfs/syscall_linux.go000066400000000000000000000032731364171671200205450ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package pathfs import ( "bytes" "syscall" "unsafe" ) var _zero uintptr func getXAttr(path string, attr string, dest []byte) (value []byte, err error) { sz, err := syscall.Getxattr(path, attr, dest) for sz > cap(dest) && err == nil { dest = make([]byte, sz) sz, err = syscall.Getxattr(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 := syscall.Listxattr(path, dest) if err != nil { return nil, err } for sz > cap(dest) && err == nil { dest = make([]byte, sz) sz, err = syscall.Listxattr(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 } 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 } go-fuse-2.0.3/fuse/pathfs/syscall_test.go000066400000000000000000000016371364171671200203670ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // +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) } } go-fuse-2.0.3/fuse/pathfs/verbose_test.go000066400000000000000000000005711364171671200203560ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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" } go-fuse-2.0.3/fuse/pathfs/xattr_test.go000066400000000000000000000111201364171671200200430ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // +build linux package pathfs import ( "bytes" "os" "path/filepath" "syscall" "testing" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/internal/testutil" ) 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.ENOATTR } 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.ENOATTR } delete(fs.attrs, attr) return fuse.OK } func xattrTestCase(t *testing.T, nm string, m map[string][]byte) (mountPoint string, cleanup func()) { xfs := NewXAttrFs(nm, m) mountPoint = testutil.TempDir() nfs := NewPathNodeFs(xfs, nil) state, _, err := nodefs.MountRoot(mountPoint, nfs.Root(), &nodefs.Options{Debug: VerboseTest()}) if err != nil { t.Fatalf("MountRoot 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 readXAttr(p, a string) (val []byte, err error) { val = make([]byte, 1024) return getXAttr(p, a, val) } 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]) } } } if err = syscall.Setxattr(mounted, "third", []byte("value"), 0); 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)) } if err := syscall.Removexattr(mounted, "third"); err != nil { t.Errorf("Removexattr: %v", err) } val, err = readXAttr(mounted, "third") if fuse.ToStatus(err) != fuse.ENOATTR { t.Error("Data not removed?", err, val) } } go-fuse-2.0.3/fuse/poll.go000066400000000000000000000021541364171671200153320ustar00rootroot00000000000000package fuse // Go 1.9 introduces polling for file I/O. The implementation causes // the runtime's epoll to take up the last GOMAXPROCS slot, and if // that happens, we won't have any threads left to service FUSE's // _OP_POLL request. Prevent this by forcing _OP_POLL to happen, so we // can say ENOSYS and prevent further _OP_POLL requests. const pollHackName = ".go-fuse-epoll-hack" const pollHackInode = ^uint64(0) func doPollHackLookup(ms *Server, req *request) { attr := Attr{ Ino: pollHackInode, Mode: S_IFREG | 0644, Nlink: 1, } switch req.inHeader.Opcode { case _OP_CREATE: out := (*CreateOut)(req.outData()) out.EntryOut = EntryOut{ NodeId: pollHackInode, Attr: attr, } out.OpenOut = OpenOut{ Fh: pollHackInode, } req.status = OK case _OP_LOOKUP: out := (*EntryOut)(req.outData()) *out = EntryOut{} req.status = ENOENT case _OP_GETATTR: out := (*AttrOut)(req.outData()) out.Attr = attr req.status = OK case _OP_POLL: req.status = ENOSYS default: // We want to avoid switching off features through our // poll hack, so don't use ENOSYS req.status = ERANGE } } go-fuse-2.0.3/fuse/poll_darwin.go000066400000000000000000000016511364171671200166770ustar00rootroot00000000000000package fuse import ( "path/filepath" "syscall" "unsafe" ) type pollFd struct { Fd int32 Events int16 Revents int16 } func sysPoll(fds []pollFd, timeout int) (n int, err error) { r0, _, e1 := syscall.Syscall(syscall.SYS_POLL, uintptr(unsafe.Pointer(&fds[0])), uintptr(len(fds)), uintptr(timeout)) n = int(r0) if e1 != 0 { err = syscall.Errno(e1) } return n, err } func pollHack(mountPoint string) error { const ( POLLIN = 0x1 POLLPRI = 0x2 POLLOUT = 0x4 POLLRDHUP = 0x2000 POLLERR = 0x8 POLLHUP = 0x10 ) fd, err := syscall.Open(filepath.Join(mountPoint, pollHackName), syscall.O_CREAT|syscall.O_TRUNC|syscall.O_RDWR, 0644) if err != nil { return err } pollData := []pollFd{{ Fd: int32(fd), Events: POLLIN | POLLPRI | POLLOUT, }} // Trigger _OP_POLL, so we can say ENOSYS. We don't care about // the return value. sysPoll(pollData, 0) syscall.Close(fd) return nil } go-fuse-2.0.3/fuse/poll_linux.go000066400000000000000000000007511364171671200165520ustar00rootroot00000000000000package fuse import ( "path/filepath" "syscall" "golang.org/x/sys/unix" ) func pollHack(mountPoint string) error { fd, err := syscall.Creat(filepath.Join(mountPoint, pollHackName), syscall.O_CREAT) if err != nil { return err } pollData := []unix.PollFd{{ Fd: int32(fd), Events: unix.POLLIN | unix.POLLPRI | unix.POLLOUT, }} // Trigger _OP_POLL, so we can say ENOSYS. We don't care about // the return value. unix.Poll(pollData, 0) syscall.Close(fd) return nil } go-fuse-2.0.3/fuse/print.go000066400000000000000000000176321364171671200155270ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fuse import ( "fmt" "os" "strings" "syscall" ) var ( 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", CAP_PARALLEL_DIROPS: "PARALLEL_DIROPS", CAP_POSIX_ACL: "POSIX_ACL", CAP_HANDLE_KILLPRIV: "HANDLE_KILLPRIV", CAP_ABORT_ERROR: "ABORT_ERROR", CAP_MAX_PAGES: "MAX_PAGES", CAP_CACHE_SYMLINKS: "CACHE_SYMLINKS", CAP_NO_OPENDIR_SUPPORT: "NO_OPENDIR_SUPPORT", CAP_EXPLICIT_INVAL_DATA: "EXPLICIT_INVAL_DATA", } 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", FOPEN_CACHE_DIR: "CACHE_DIR", FOPEN_STREAM: "STREAM", } 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 (in *ForgetIn) string() string { return fmt.Sprintf("{Nlookup=%d}", in.Nlookup) } func (in *_BatchForgetIn) string() string { return fmt.Sprintf("{Count=%d}", in.Count) } func (in *MkdirIn) string() string { return fmt.Sprintf("{0%o (0%o)}", in.Mode, in.Umask) } func (in *Rename1In) string() string { return fmt.Sprintf("{i%d}", in.Newdir) } func (in *RenameIn) string() string { return fmt.Sprintf("{i%d %x}", in.Newdir, in.Flags) } func (in *SetAttrIn) string() string { s := []string{} if in.Valid&FATTR_MODE != 0 { s = append(s, fmt.Sprintf("mode 0%o", in.Mode)) } if in.Valid&FATTR_UID != 0 { s = append(s, fmt.Sprintf("uid %d", in.Uid)) } if in.Valid&FATTR_GID != 0 { s = append(s, fmt.Sprintf("gid %d", in.Gid)) } if in.Valid&FATTR_SIZE != 0 { s = append(s, fmt.Sprintf("size %d", in.Size)) } if in.Valid&FATTR_ATIME != 0 { s = append(s, fmt.Sprintf("atime %d.%09d", in.Atime, in.Atimensec)) } if in.Valid&FATTR_MTIME != 0 { s = append(s, fmt.Sprintf("mtime %d.%09d", in.Mtime, in.Mtimensec)) } if in.Valid&FATTR_FH != 0 { s = append(s, fmt.Sprintf("fh %d", in.Fh)) } // TODO - FATTR_ATIME_NOW = (1 << 7), FATTR_MTIME_NOW = (1 << 8), FATTR_LOCKOWNER = (1 << 9) return fmt.Sprintf("{%s}", strings.Join(s, ", ")) } func (in *ReleaseIn) string() string { return fmt.Sprintf("{Fh %d %s %s L%d}", in.Fh, flagString(openFlagNames, int64(in.Flags), ""), flagString(releaseFlagNames, int64(in.ReleaseFlags), ""), in.LockOwner) } func (in *OpenIn) string() string { return fmt.Sprintf("{%s}", flagString(openFlagNames, int64(in.Flags), "O_RDONLY")) } func (in *OpenOut) string() string { return fmt.Sprintf("{Fh %d %s}", in.Fh, flagString(fuseOpenFlagNames, int64(in.OpenFlags), "")) } func (in *InitIn) string() string { return fmt.Sprintf("{%d.%d Ra 0x%x %s}", in.Major, in.Minor, in.MaxReadAhead, flagString(initFlagNames, int64(in.Flags), "")) } func (o *InitOut) string() string { return fmt.Sprintf("{%d.%d Ra 0x%x %s %d/%d Wr 0x%x Tg 0x%x}", o.Major, o.Minor, o.MaxReadAhead, flagString(initFlagNames, int64(o.Flags), ""), o.CongestionThreshold, o.MaxBackground, o.MaxWrite, o.TimeGran) } func (s *FsyncIn) string() string { return fmt.Sprintf("{Fh %d Flags %x}", s.Fh, s.FsyncFlags) } func (in *SetXAttrIn) string() string { return fmt.Sprintf("{sz %d f%o}", in.Size, in.Flags) } func (in *GetXAttrIn) string() string { return fmt.Sprintf("{sz %d}", in.Size) } func (o *GetXAttrOut) string() string { return fmt.Sprintf("{sz %d}", o.Size) } func (in *AccessIn) string() string { return fmt.Sprintf("{u=%d g=%d %s}", in.Uid, in.Gid, flagString(accessFlagName, int64(in.Mask), "")) } func (in *FlushIn) string() string { return fmt.Sprintf("{Fh %d}", in.Fh) } func (o *AttrOut) string() string { return fmt.Sprintf( "{tA=%gs %v}", ft(o.AttrValid, o.AttrValidNsec), &o.Attr) } // ft converts (seconds , nanoseconds) -> float(seconds) func ft(tsec uint64, tnsec uint32) float64 { return float64(tsec) + float64(tnsec)*1E-9 } // Returned by LOOKUP func (o *EntryOut) string() string { return fmt.Sprintf("{i%d g%d tE=%gs tA=%gs %v}", o.NodeId, o.Generation, ft(o.EntryValid, o.EntryValidNsec), ft(o.AttrValid, o.AttrValidNsec), &o.Attr) } func (o *CreateOut) string() string { return fmt.Sprintf("{i%d g%d %v %v}", o.NodeId, o.Generation, &o.EntryOut, &o.OpenOut) } func (o *StatfsOut) string() string { return fmt.Sprintf( "{blocks (%d,%d)/%d files %d/%d bs%d nl%d frs%d}", o.Bfree, o.Bavail, o.Blocks, o.Ffree, o.Files, o.Bsize, o.NameLen, o.Frsize) } func (o *NotifyInvalEntryOut) string() string { return fmt.Sprintf("{parent i%d sz %d}", o.Parent, o.NameLen) } func (o *NotifyInvalInodeOut) string() string { return fmt.Sprintf("{i%d [%d +%d)}", o.Ino, o.Off, o.Length) } func (o *NotifyInvalDeleteOut) string() string { return fmt.Sprintf("{parent i%d ch i%d sz %d}", o.Parent, o.Child, o.NameLen) } func (o *NotifyStoreOut) string() string { return fmt.Sprintf("{i%d [%d +%d)}", o.Nodeid, o.Offset, o.Size) } func (o *NotifyRetrieveOut) string() string { return fmt.Sprintf("{> %d: i%d [%d +%d)}", o.NotifyUnique, o.Nodeid, o.Offset, o.Size) } func (i *NotifyRetrieveIn) string() string { return fmt.Sprintf("{[%d +%d)}", i.Offset, i.Size) } func (f *FallocateIn) string() string { return fmt.Sprintf("{Fh %d [%d +%d) mod 0%o}", f.Fh, f.Offset, f.Length, f.Mode) } func (f *LinkIn) string() string { return fmt.Sprintf("{Oldnodeid: %d}", f.Oldnodeid) } func (o *WriteOut) string() string { return fmt.Sprintf("{%db }", o.Size) } func (i *CopyFileRangeIn) string() string { return fmt.Sprintf("{Fh %d [%d +%d) => i%d Fh %d [%d, %d)}", i.FhIn, i.OffIn, i.Len, i.NodeIdOut, i.FhOut, i.OffOut, i.Len) } func (in *InterruptIn) string() string { return fmt.Sprintf("{ix %d}", in.Unique) } var seekNames = map[uint32]string{ 0: "SET", 1: "CUR", 2: "END", 3: "DATA", 4: "HOLE", } func (in *LseekIn) string() string { return fmt.Sprintf("{Fh %d [%s +%d)}", in.Fh, seekNames[in.Whence], in.Offset) } func (o *LseekOut) string() string { return fmt.Sprintf("{%d}", o.Offset) } // 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) } go-fuse-2.0.3/fuse/print_darwin.go000066400000000000000000000023721364171671200170660ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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 %f "+ "M %f "+ "C %f}", a.Mode, a.Size, a.Nlink, a.Uid, a.Gid, a.Blocks, a.Rdev, a.Ino, ft(a.Atime, a.Atimensec), ft(a.Mtime, a.Mtimensec), ft(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 [%d +%d) %s}", me.Fh, me.Offset, me.Size, flagString(readFlagNames, int64(me.ReadFlags), "")) } func (me *WriteIn) string() string { return fmt.Sprintf("{Fh %d [%d +%d) %s}", me.Fh, me.Offset, me.Size, flagString(writeFlagNames, int64(me.WriteFlags), "")) } go-fuse-2.0.3/fuse/print_linux.go000066400000000000000000000027601364171671200167420ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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 %f "+ "M %f "+ "C %f}", a.Mode, a.Size, a.Nlink, a.Uid, a.Gid, a.Blocks, a.Blksize, a.Rdev, a.Ino, ft(a.Atime, a.Atimensec), ft(a.Mtime, a.Mtimensec), ft(a.Ctime, a.Ctimensec)) } func (in *CreateIn) string() string { return fmt.Sprintf( "{0%o [%s] (0%o)}", in.Mode, flagString(openFlagNames, int64(in.Flags), "O_RDONLY"), in.Umask) } func (in *GetAttrIn) string() string { return fmt.Sprintf("{Fh %d}", in.Fh_) } func (in *MknodIn) string() string { return fmt.Sprintf("{0%o (0%o), %d}", in.Mode, in.Umask, in.Rdev) } func (in *ReadIn) string() string { return fmt.Sprintf("{Fh %d [%d +%d) %s L %d %s}", in.Fh, in.Offset, in.Size, flagString(readFlagNames, int64(in.ReadFlags), ""), in.LockOwner, flagString(openFlagNames, int64(in.Flags), "RDONLY")) } func (in *WriteIn) string() string { return fmt.Sprintf("{Fh %d [%d +%d) %s L %d %s}", in.Fh, in.Offset, in.Size, flagString(writeFlagNames, int64(in.WriteFlags), ""), in.LockOwner, flagString(openFlagNames, int64(in.Flags), "RDONLY")) } go-fuse-2.0.3/fuse/read.go000066400000000000000000000026301364171671200152760ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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() { } go-fuse-2.0.3/fuse/request.go000066400000000000000000000136351364171671200160620ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fuse import ( "bytes" "fmt" "log" "strings" "time" "unsafe" ) var sizeOfOutHeader = unsafe.Sizeof(OutHeader{}) var zeroOutBuf [outputHeaderSize]byte type request struct { inflightIndex int cancel chan struct{} // written under Server.reqMu interrupted bool inputBuf []byte // These split up inputBuf. inHeader *InHeader // generic header inData unsafe.Pointer // per op data arg []byte // flat data. filenames []string // filename arguments // Output data. 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 } func (r *request) clear() { r.inputBuf = nil r.inHeader = nil r.inData = nil r.arg = nil r.filenames = 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 != nil && r.handler.DecodeIn != nil { val = fmt.Sprintf("%v ", Print(r.handler.DecodeIn(r.inData))) } names := "" if r.filenames != nil { names = fmt.Sprintf("%q", r.filenames) } if len(r.arg) > 0 { names += fmt.Sprintf(" %db", len(r.arg)) } return fmt.Sprintf("rx %d: %s i%d %s%s", r.inHeader.Unique, operationName(r.inHeader.Opcode), r.inHeader.NodeId, val, names) } func (r *request) OutputDebug() string { var dataStr string if r.handler != nil && r.handler.DecodeOut != nil && r.handler.OutputSize > 0 { 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 != nil && r.handler.FileNameOut { s := strings.TrimRight(string(r.flatData), "\x00") flatStr = fmt.Sprintf(" %q", s) } else { spl := "" if r.fdData != nil { spl = " (fd data)" } else { l := len(r.flatData) s := "" if l > 8 { l = 8 s = "..." } spl = fmt.Sprintf(" %q%s", r.flatData[:l], s) } flatStr = fmt.Sprintf(" %db data%s", r.flatDataSize(), spl) } } extraStr := dataStr + flatStr if extraStr != "" { extraStr = ", " + extraStr } return fmt.Sprintf("tx %d: %v%s", r.inHeader.Unique, r.status, extraStr) } // 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) parseHeader() Status { if len(r.inputBuf) < int(unsafe.Sizeof(InHeader{})) { log.Printf("Short read for input header: %v", r.inputBuf) return EINVAL } r.inHeader = (*InHeader)(unsafe.Pointer(&r.inputBuf[0])) return OK } func (r *request) parse() { 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[unsafe.Sizeof(InHeader{}):] } count := r.handler.FileNames if count > 0 { if count == 1 && r.inHeader.Opcode == _OP_SETXATTR { // SETXATTR is special: the only opcode with a file name AND a // binary argument. splits := bytes.SplitN(r.arg, []byte{0}, 2) r.filenames = []string{string(splits[0])} } else 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]) } func (r *request) outData() unsafe.Pointer { return unsafe.Pointer(&r.outBuf[sizeOfOutHeader]) } // serializeHeader serializes the response header. The header points // to an internal buffer of the receiver. func (r *request) serializeHeader(flatDataSize int) (header []byte) { var dataLength uintptr if r.handler != nil { dataLength = r.handler.OutputSize } if r.status > OK { // only do this for positive status; negative status // is used for notification. dataLength = 0 } // [GET|LIST]XATTR is two opcodes in one: get/list xattr size (return // structured GetXAttrOut, no flat data) and get/list xattr data // (return no structured data, but only flat data) if r.inHeader.Opcode == _OP_GETXATTR || r.inHeader.Opcode == _OP_LISTXATTR { if (*GetXAttrIn)(r.inData).Size != 0 { dataLength = 0 } } 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) + flatDataSize) return header } func (r *request) flatDataSize() int { if r.fdData != nil { return r.fdData.Size() } return len(r.flatData) } go-fuse-2.0.3/fuse/request_darwin.go000066400000000000000000000004571364171671200174240ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fuse const outputHeaderSize = 200 const ( _FUSE_KERNEL_VERSION = 7 _MINIMUM_MINOR_VERSION = 8 _OUR_MINOR_VERSION = 8 ) go-fuse-2.0.3/fuse/request_linux.go000066400000000000000000000004611364171671200172720ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fuse const outputHeaderSize = 160 const ( _FUSE_KERNEL_VERSION = 7 _MINIMUM_MINOR_VERSION = 12 _OUR_MINOR_VERSION = 28 ) go-fuse-2.0.3/fuse/server.go000066400000000000000000000530051364171671200156730ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fuse import ( "fmt" "log" "math" "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 // Pools for []byte buffers bufferPool // Pool for request structs. reqPool sync.Pool // Pool for raw requests data readPool sync.Pool reqMu sync.Mutex reqReaders int reqInflight []*request kernelSettings InitIn // in-flight notify-retrieve queries retrieveMu sync.Mutex retrieveNext uint64 retrieveTab map[uint64]*retrieveCacheRequest // notifyUnique -> retrieve request singleReader bool canSplice bool loops sync.WaitGroup ready chan error // for implementing single threaded processing. requestProcessingMu sync.Mutex } // 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. The message should not be altered. 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, ms.opts) 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.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, retrieveTab: make(map[uint64]*retrieveCacheRequest), // 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", ready: make(chan error, 1), } ms.reqPool.New = func() interface{} { return &request{ cancel: make(chan struct{}), } } ms.readPool.New = func() interface{} { buf := make([]byte, o.MaxWrite+int(maxInputSize)+logicalBlockSize) buf = alignSlice(buf, unsafe.Sizeof(WriteIn{}), logicalBlockSize, uintptr(o.MaxWrite)+maxInputSize) return buf } 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)) } 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) } // This prepares for Serve being called somewhere, either // synchronously or asynchronously. ms.loops.Add(1) 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) { req = ms.reqPool.Get().(*request) dest := ms.readPool.Get().([]byte) ms.reqMu.Lock() if ms.reqReaders > _MAX_READERS { ms.reqMu.Unlock() return nil, OK } 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() defer ms.reqMu.Unlock() // Must parse request.Unique under lock if status := req.parseHeader(); !status.Ok() { return nil, status } req.inflightIndex = len(ms.reqInflight) ms.reqInflight = append(ms.reqInflight, req) if !gobbled { ms.readPool.Put(dest) dest = nil } ms.reqReaders-- if !ms.singleReader && ms.reqReaders <= 0 { ms.loops.Add(1) go ms.loop(true) } return req, OK } // returnRequest returns a request to the pool of unused requests. func (ms *Server) returnRequest(req *request) { ms.reqMu.Lock() this := req.inflightIndex last := len(ms.reqInflight) - 1 if last != this { ms.reqInflight[this] = ms.reqInflight[last] ms.reqInflight[this].inflightIndex = this } ms.reqInflight = ms.reqInflight[:last] interrupted := req.interrupted ms.reqMu.Unlock() ms.recordStats(req) if interrupted { // Don't reposses data, because someone might still // be looking at it return } if req.bufferPoolOutputBuf != nil { ms.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.loop(false) ms.loops.Wait() ms.writeMu.Lock() syscall.Close(ms.mountFd) ms.writeMu.Unlock() // shutdown in-flight cache retrieves. // // It is possible that umount comes in the middle - after retrieve // request was sent to kernel, but corresponding kernel reply has not // yet been read. We unblock all such readers and wake them up with ENODEV. ms.retrieveMu.Lock() rtab := ms.retrieveTab // retrieve attempts might be erroneously tried even after close // we have to keep retrieveTab !nil not to panic. ms.retrieveTab = make(map[uint64]*retrieveCacheRequest) ms.retrieveMu.Unlock() for _, reading := range rtab { reading.n = 0 reading.st = ENODEV close(reading.ready) } } // Wait waits for the serve loop to exit. This should only be called // after Serve has been called, or it will hang indefinitely. func (ms *Server) Wait() { ms.loops.Wait() } 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 if ms.opts.Debug { log.Printf("received ENODEV (unmount request), thread exiting") } 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 { if ms.opts.SingleThreaded { ms.requestProcessingMu.Lock() defer ms.requestProcessingMu.Unlock() } req.parse() if req.handler == nil { req.status = ENOSYS } if req.status.Ok() && ms.opts.Debug { log.Println(req.InputDebug()) } if req.inHeader.NodeId == pollHackInode || req.inHeader.NodeId == FUSE_ROOT_ID && len(req.filenames) > 0 && req.filenames[0] == pollHackName { doPollHackLookup(ms, req) } else if req.status.Ok() && req.handler.Func == nil { log.Printf("Unimplemented opcode %v", operationName(req.inHeader.Opcode)) req.status = ENOSYS } else 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) } // alignSlice ensures that the byte at alignedByte is aligned with the // given logical block size. The input slice should be at least (size // + blockSize) func alignSlice(buf []byte, alignedByte, blockSize, size uintptr) []byte { misaligned := uintptr(unsafe.Pointer(&buf[alignedByte])) & (blockSize - 1) buf = buf[blockSize-misaligned:] return buf[:size] } 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.buffers.FreeBuffer(req.bufferPoolOutputBuf) req.bufferPoolOutputBuf = nil } // As this allocated a multiple of the page size, very likely // this is aligned to logicalBlockSize too, which is smaller. req.bufferPoolOutputBuf = ms.buffers.AllocBuffer(size) return req.bufferPoolOutputBuf } func (ms *Server) write(req *request) Status { // Forget/NotifyReply do not wait for reply from filesystem server. switch req.inHeader.Opcode { case _OP_FORGET, _OP_BATCH_FORGET, _OP_NOTIFY_REPLY: return OK case _OP_INTERRUPT: if req.status.Ok() { 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 { if !ms.kernelSettings.SupportsNotify(NOTIFY_INVAL_INODE) { return ENOSYS } req := request{ inHeader: &InHeader{ Opcode: _OP_NOTIFY_INVAL_INODE, }, handler: operationHandlers[_OP_NOTIFY_INVAL_INODE], status: NOTIFY_INVAL_INODE, } entry := (*NotifyInvalInodeOut)(req.outData()) entry.Ino = node entry.Off = off entry.Length = length // 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 } // InodeNotifyStoreCache tells kernel to store data into inode's cache. // // This call is similar to InodeNotify, but instead of only invalidating a data // region, it gives updated data directly to the kernel. func (ms *Server) InodeNotifyStoreCache(node uint64, offset int64, data []byte) Status { if !ms.kernelSettings.SupportsNotify(NOTIFY_STORE_CACHE) { return ENOSYS } for len(data) > 0 { size := len(data) if size > math.MaxInt32 { // NotifyStoreOut has only uint32 for size. // we check for max(int32), not max(uint32), because on 32-bit // platforms int has only 31-bit for positive range. size = math.MaxInt32 } st := ms.inodeNotifyStoreCache32(node, offset, data[:size]) if st != OK { return st } data = data[size:] offset += int64(size) } return OK } // inodeNotifyStoreCache32 is internal worker for InodeNotifyStoreCache which // handles data chunks not larger than 2GB. func (ms *Server) inodeNotifyStoreCache32(node uint64, offset int64, data []byte) Status { req := request{ inHeader: &InHeader{ Opcode: _OP_NOTIFY_STORE_CACHE, }, handler: operationHandlers[_OP_NOTIFY_STORE_CACHE], status: NOTIFY_STORE_CACHE, } store := (*NotifyStoreOut)(req.outData()) store.Nodeid = node store.Offset = uint64(offset) // NOTE not int64, as it is e.g. in NotifyInvalInodeOut store.Size = uint32(len(data)) req.flatData = data // Protect against concurrent close. ms.writeMu.Lock() result := ms.write(&req) ms.writeMu.Unlock() if ms.opts.Debug { log.Printf("Response: INODE_NOTIFY_STORE_CACHE: %v", result) } return result } // InodeRetrieveCache retrieves data from kernel's inode cache. // // InodeRetrieveCache asks kernel to return data from its cache for inode at // [offset:offset+len(dest)) and waits for corresponding reply. If kernel cache // has fewer consecutive data starting at offset, that fewer amount is returned. // In particular if inode data at offset is not cached (0, OK) is returned. // // The kernel returns ENOENT if it does not currently have entry for this inode // in its dentry cache. func (ms *Server) InodeRetrieveCache(node uint64, offset int64, dest []byte) (n int, st Status) { // the kernel won't send us in one go more then what we negotiated as MaxWrite. // retrieve the data in chunks. // TODO spawn some number of readahead retrievers in parallel. ntotal := 0 for { chunkSize := len(dest) if chunkSize > ms.opts.MaxWrite { chunkSize = ms.opts.MaxWrite } n, st = ms.inodeRetrieveCache1(node, offset, dest[:chunkSize]) if st != OK || n == 0 { break } ntotal += n offset += int64(n) dest = dest[n:] } // if we could retrieve at least something - it is ok. // if ntotal=0 - st will be st returned from first inodeRetrieveCache1. if ntotal > 0 { st = OK } return ntotal, st } // inodeRetrieveCache1 is internal worker for InodeRetrieveCache which // actually talks to kernel and retrieves chunks not larger than ms.opts.MaxWrite. func (ms *Server) inodeRetrieveCache1(node uint64, offset int64, dest []byte) (n int, st Status) { if !ms.kernelSettings.SupportsNotify(NOTIFY_RETRIEVE_CACHE) { return 0, ENOSYS } req := request{ inHeader: &InHeader{ Opcode: _OP_NOTIFY_RETRIEVE_CACHE, }, handler: operationHandlers[_OP_NOTIFY_RETRIEVE_CACHE], status: NOTIFY_RETRIEVE_CACHE, } // retrieve up to 2GB not to overflow uint32 size in NotifyRetrieveOut. // see InodeNotifyStoreCache in similar place for why it is only 2GB, not 4GB. // // ( InodeRetrieveCache calls us with chunks not larger than // ms.opts.MaxWrite, but MaxWrite is int, so let's be extra cautious ) size := len(dest) if size > math.MaxInt32 { size = math.MaxInt32 } dest = dest[:size] q := (*NotifyRetrieveOut)(req.outData()) q.Nodeid = node q.Offset = uint64(offset) // not int64, as it is e.g. in NotifyInvalInodeOut q.Size = uint32(len(dest)) reading := &retrieveCacheRequest{ nodeid: q.Nodeid, offset: q.Offset, dest: dest, ready: make(chan struct{}), } ms.retrieveMu.Lock() q.NotifyUnique = ms.retrieveNext ms.retrieveNext++ ms.retrieveTab[q.NotifyUnique] = reading ms.retrieveMu.Unlock() // Protect against concurrent close. ms.writeMu.Lock() result := ms.write(&req) ms.writeMu.Unlock() if ms.opts.Debug { log.Printf("Response: NOTIFY_RETRIEVE_CACHE: %v", result) } if result != OK { ms.retrieveMu.Lock() r := ms.retrieveTab[q.NotifyUnique] if r == reading { delete(ms.retrieveTab, q.NotifyUnique) } else if r == nil { // ok - might be dequeued by umount } else { // although very unlikely, it is possible that kernel sends // unexpected NotifyReply with our notifyUnique, then // retrieveNext wraps, makes full cycle, and another // retrieve request is made with the same notifyUnique. log.Printf("W: INODE_RETRIEVE_CACHE: request with notifyUnique=%d mutated", q.NotifyUnique) } ms.retrieveMu.Unlock() return 0, result } // NotifyRetrieveOut sent to the kernel successfully. Now the kernel // have to return data in a separate write-style NotifyReply request. // Wait for the result. <-reading.ready return reading.n, reading.st } // retrieveCacheRequest represents in-flight cache retrieve request. type retrieveCacheRequest struct { nodeid uint64 offset uint64 dest []byte // reply status n int st Status ready chan struct{} } // 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_DELETE, } entry := (*NotifyInvalDeleteOut)(req.outData()) entry.Parent = parent entry.Child = child entry.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.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 { if !ms.kernelSettings.SupportsNotify(NOTIFY_INVAL_ENTRY) { return ENOSYS } req := request{ inHeader: &InHeader{ Opcode: _OP_NOTIFY_INVAL_ENTRY, }, handler: operationHandlers[_OP_NOTIFY_INVAL_ENTRY], status: NOTIFY_INVAL_ENTRY, } entry := (*NotifyInvalEntryOut)(req.outData()) entry.Parent = parent entry.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.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 } // SupportsVersion returns true if the kernel supports the given // protocol version or newer. func (in *InitIn) SupportsVersion(maj, min uint32) bool { return in.Major > maj || (in.Major == maj && in.Minor >= min) } // SupportsNotify returns whether a certain notification type is // supported. Pass any of the NOTIFY_* types as argument. func (in *InitIn) SupportsNotify(notifyType int) bool { switch notifyType { case NOTIFY_INVAL_ENTRY: return in.SupportsVersion(7, 12) case NOTIFY_INVAL_INODE: return in.SupportsVersion(7, 12) case NOTIFY_STORE_CACHE, NOTIFY_RETRIEVE_CACHE: return in.SupportsVersion(7, 15) case NOTIFY_DELETE: return in.SupportsVersion(7, 18) } return false } // 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. func (ms *Server) WaitMount() error { err := <-ms.ready if err != nil { return err } return pollHack(ms.mountPoint) } go-fuse-2.0.3/fuse/server_darwin.go000066400000000000000000000014011364171671200172300ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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) } go-fuse-2.0.3/fuse/server_linux.go000066400000000000000000000016561364171671200171170ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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) } go-fuse-2.0.3/fuse/splice_darwin.go000066400000000000000000000005721364171671200172110ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fuse import ( "fmt" ) func (s *Server) setSplice() { s.canSplice = false } func (ms *Server) trySplice(header []byte, req *request, fdData *readResultFd) error { return fmt.Errorf("unimplemented") } go-fuse-2.0.3/fuse/splice_linux.go000066400000000000000000000050071364171671200170620ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fuse import ( "fmt" "os" "github.com/hanwen/go-fuse/v2/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 } go-fuse-2.0.3/fuse/syscall_darwin.go000066400000000000000000000063051364171671200174040ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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) } go-fuse-2.0.3/fuse/syscall_linux.go000066400000000000000000000017631364171671200172620ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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 } go-fuse-2.0.3/fuse/test/000077500000000000000000000000001364171671200150125ustar00rootroot00000000000000go-fuse-2.0.3/fuse/test/cache_test.go000066400000000000000000000145571364171671200174570ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package test import ( "bytes" "fmt" "io/ioutil" "os" "runtime" "sync" "testing" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/pathfs" "github.com/hanwen/go-fuse/v2/internal/testutil" ) type 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 := testutil.TempDir() os.Mkdir(dir+"/mnt", 0755) os.Mkdir(dir+"/orig", 0755) fs := &cacheFs{ pathfs.NewLoopbackFileSystem(dir + "/orig"), } pfs := pathfs.NewPathNodeFs(fs, &pathfs.PathNodeFsOptions{Debug: testutil.VerboseTest()}) mntOpts := &fuse.MountOptions{ // ask kernel not to invalidate file data automatically ExplicitDataCacheControl: true, Debug: testutil.VerboseTest(), } opts := nodefs.NewOptions() opts.AttrTimeout = 10 * time.Millisecond opts.Debug = testutil.VerboseTest() state, _, err := nodefs.Mount(dir+"/mnt", pfs.Root(), mntOpts, 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() // x{read,write}File reads/writes file@path and fail on error xreadFile := func(path string) string { data, err := ioutil.ReadFile(path) if err != nil { t.Fatal(err) } return string(data) } xwriteFile := func(path, data string) { if err := ioutil.WriteFile(path, []byte(data), 0644); err != nil { t.Fatal(err) } } // xstat stats path and fails on error xstat := func(path string) os.FileInfo { st, err := os.Stat(path) if err != nil { t.Fatal(err) } return st } // XXX Linux FUSE client automatically invalidates cache of a file if it sees size change. // As workaround we keep len(before) == len(after) to avoid that codepath. // See https://github.com/hanwen/go-fuse/pull/273 for details. // // TODO use len(before) != len(after) if kernel supports precise control over data cache. before := "before" after := "afterX" if len(before) != len(after) { panic("len(before) != len(after)") } xwriteFile(wd+"/orig/file.txt", before) mtimeBefore := xstat(wd + "/orig/file.txt").ModTime() c := xreadFile(wd + "/mnt/file.txt") if c != before { t.Fatalf("ReadFile: got %q, want %q", c, before) } // sleep a bit to make sure mtime of file for before and after are different time.Sleep(20 * time.Millisecond) xwriteFile(wd+"/orig/file.txt", after) mtimeAfter := xstat(wd + "/orig/file.txt").ModTime() if δ := mtimeAfter.Sub(mtimeBefore); δ == 0 { panic(fmt.Sprintf("mtime(orig/before) == mtime(orig/after)")) } // sleep enough time for file attributes to expire; restat the file after. // this forces kernel client to relookup/regetattr the file and reread the attributes. // // this way we make sure the kernel knows updated size/mtime before we // try to read the file next time. time.Sleep(100 * time.Millisecond) _ = xstat(wd + "/mnt/file.txt") c = xreadFile(wd + "/mnt/file.txt") if c != before { t.Fatalf("ReadFile: got %q, want cached %q", c, before) } if minor := pathfs.Connector().Server().KernelSettings().Minor; minor < 12 { t.Skipf("protocol v%d has no notify support.", minor) } code := pathfs.EntryNotify("", "file.txt") if !code.Ok() { t.Errorf("EntryNotify: %v", code) } c = xreadFile(wd + "/mnt/file.txt") if 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 := testutil.TempDir() defer os.RemoveAll(dir) nfs := pathfs.NewPathNodeFs(fs, nil) opts := nodefs.NewOptions() opts.Debug = testutil.VerboseTest() state, _, err := nodefs.MountRoot(dir, nfs.Root(), opts) if err != nil { t.Fatalf("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 := testutil.TempDir() 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: testutil.VerboseTest()}) state, _, err := nodefs.MountRoot(dir+"/mnt", pfs.Root(), &nodefs.Options{Debug: testutil.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) } } go-fuse-2.0.3/fuse/test/cachecontrol_test.go000066400000000000000000000166741364171671200210620ustar00rootroot00000000000000// Copyright 2018 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package test // exercise functionality to store/retrieve kernel cache. import ( "bytes" "encoding/binary" "io/ioutil" "os" "testing" "golang.org/x/sys/unix" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/internal/testutil" ) // DataNode is a nodefs.Node that Reads static data. // // Since Read works without Open, the kernel does not invalidate DataNode cache // on user's open. type DataNode struct { nodefs.Node data []byte } func NewDataNode(data []byte) *DataNode { return &DataNode{Node: nodefs.NewDefaultNode(), data: data} } func (d *DataNode) GetAttr(out *fuse.Attr, _ nodefs.File, _ *fuse.Context) fuse.Status { out.Size = uint64(len(d.data)) out.Mode = fuse.S_IFREG | 0644 return fuse.OK } func (d *DataNode) Read(_ nodefs.File, dest []byte, off int64, _ *fuse.Context) (fuse.ReadResult, fuse.Status) { l := int64(len(d.data)) end := off + l if end > l { end = l } return fuse.ReadResultData(d.data[off:end]), fuse.OK } // TestCacheControl verifies that FUSE server process can store/retrieve kernel data cache. func TestCacheControl(t *testing.T) { dir := testutil.TempDir() defer func() { err := os.Remove(dir) if err != nil { t.Fatal(err) } }() // setup a filesystem with 1 file root := nodefs.NewDefaultNode() opts := nodefs.NewOptions() opts.Debug = testutil.VerboseTest() srv, fsconn, err := nodefs.MountRoot(dir, root, opts) if err != nil { t.Fatal(err) } data0 := "hello world" file := NewDataNode([]byte(data0)) root.Inode().NewChild("hello.txt", false, file) go srv.Serve() if err := srv.WaitMount(); err != nil { t.Fatal("WaitMount", err) } defer func() { err := srv.Unmount() if err != nil { t.Fatal(err) } }() // assertFileRead asserts that the file content reads as dataOK. assertFileRead := func(subj, dataOK string) { t.Helper() v, err := ioutil.ReadFile(dir + "/hello.txt") if err != nil { t.Fatalf("%s: file read: %s", subj, err) } if string(v) != dataOK { t.Fatalf("%s: file read: got %q ; want %q", subj, v, dataOK) } } // assertCacheRead asserts that file's kernel cache is retrieved as dataOK. assertCacheRead := func(subj, dataOK string) { t.Helper() assertCacheReadAt := func(offset int64, size int, dataOK string) { t.Helper() buf := make([]byte, size) n, st := fsconn.FileRetrieveCache(file.Inode(), offset, buf) if st != fuse.OK { t.Fatalf("%s: retrieve cache @%d [%d]: %s", subj, offset, size, st) } if got := buf[:n]; string(got) != dataOK { t.Fatalf("%s: retrieve cache @%d [%d]: have %q; want %q", subj, offset, size, got, dataOK) } } // retrieve [1:len - 1] (also verifying that offset/size are handled correctly) l := len(dataOK) if l >= 2 { assertCacheReadAt(1, l-2, dataOK[1:l-1]) } // retrieve [:∞] assertCacheReadAt(0, l+10000, dataOK) } // x is that t.Fatals on error xFileNotifyStoreCache := func(subj string, inode *nodefs.Inode, offset int64, data []byte) { t.Helper() st := fsconn.FileNotifyStoreCache(inode, offset, data) if st != fuse.OK { t.Fatalf("store cache (%s): %s", subj, st) } } xmmap := func(fd int, offset int64, size, prot, flags int) []byte { t.Helper() mem, err := unix.Mmap(fd, offset, size, prot, flags) if err != nil { t.Fatal(err) } return mem } xmunmap := func(mem []byte) { t.Helper() err := unix.Munmap(mem) if err != nil { t.Fatal(err) } } xmlock := func(mem []byte) { t.Helper() err := unix.Mlock(mem) if err != nil { t.Fatal(err) } } // before the kernel has entry for file in its dentry cache, the cache // should read as empty and cache store should fail with ENOENT. assertCacheRead("before lookup", "") st := fsconn.FileNotifyStoreCache(file.Inode(), 0, []byte("abc")) if st != fuse.ENOENT { t.Fatalf("%s: store cache -> %v; want %v", "before lookup", st, fuse.ENOENT) } // lookup on the file - forces to assign inode ID to it os.Stat(dir + "/hello.txt") // cache should be initially empty assertCacheRead("initial", "") // make sure the file reads correctly assertFileRead("original", data0) // pin file content into OS cache f, err := os.Open(dir + "/hello.txt") if err != nil { t.Fatal(err) } fmmap := xmmap(int(f.Fd()), 0, len(data0), unix.PROT_READ, unix.MAP_SHARED) defer func() { err := f.Close() if err != nil { t.Fatal(err) } }() defer func() { xmunmap(fmmap) }() xmlock(fmmap) // assertMmapRead asserts that file's mmaped memory reads as dataOK. assertMmapRead := func(subj, dataOK string) { t.Helper() if string(fmmap) != dataOK { t.Fatalf("%s: file mmap: got %q ; want %q", subj, fmmap, dataOK) } } // make sure the cache has original data assertMmapRead("original", data0) assertCacheRead("original", data0) // store changed data into OS cache xFileNotifyStoreCache("", file.Inode(), 7, []byte("123")) // make sure mmaped data and file read as updated data data1 := "hello w123d" assertMmapRead("after storecache", data1) assertFileRead("after storecache", data1) assertCacheRead("after storecache", data1) // invalidate cache st = fsconn.FileNotify(file.Inode(), 0, 0) if st != fuse.OK { t.Fatalf("invalidate cache: %s", st) } // make sure cache reads as empty right after invalidation assertCacheRead("after invalcache", "") // make sure mmapped data, file and cache read as original data assertMmapRead("after invalcache", data0) assertFileRead("after invalcache", data0) assertCacheRead("after invalcache + refill", data0) // make sure we can store/retrieve into/from OS cache big chunk of data. // we will need to mlock(1M), so skip this test if mlock limit is tight. const lbig = 1024 * 1024 // 1M const memlockNeed = lbig + /* we already used some small mlock above */ 64*1024 var lmemlock unix.Rlimit err = unix.Getrlimit(unix.RLIMIT_MEMLOCK, &lmemlock) if err != nil { t.Fatal(err) } t0 := t tp := &t t.Run("BigChunk", func(t *testing.T) { if lmemlock.Cur < memlockNeed { t.Skipf("skipping: too low memlock limit: %dK, need at least %dK", lmemlock.Cur/1024, memlockNeed/1024) } *tp = t // so that x defined above work with local - not parent - t defer func() { *tp = t0 }() // big chunk of data to be stored into OS cache, enlarging the file there. // // The values are unique uint32, so that if the kernel or our glue in // InodeRetrieveCache makes a mistake, we should notice by seeing // different data. // // Use 0x01020304 as of base offset so that there are no e.g. '00 00 00 01 // 00 00 00 02 ...' sequence - i.e. where we could not notice if e.g. a // single-byte offset bug is there somewhere. buf := &bytes.Buffer{} for i := uint32(0); i < lbig/4; i++ { err := binary.Write(buf, binary.BigEndian, i+0x01020304) if err != nil { panic(err) // Buffer.Write does not error } } dataBig := buf.String() // first store zeros and mlock, so that OS does not loose the cache xFileNotifyStoreCache("big, 0", file.Inode(), 0, make([]byte, lbig)) fmmapBig := xmmap(int(f.Fd()), 0, lbig, unix.PROT_READ, unix.MAP_SHARED) defer xmunmap(fmmapBig) xmlock(fmmapBig) // upload big data into pinned cache pages xFileNotifyStoreCache("big", file.Inode(), 0, []byte(dataBig)) // make sure we can retrieve a big chunk from the cache assertCacheRead("after storecache big", dataBig) }) } go-fuse-2.0.3/fuse/test/defaultnode_test.go000066400000000000000000000036631364171671200207020ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package test import ( "io/ioutil" "os" "path" "testing" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/internal/testutil" ) func TestDefaultNodeGetAttr(t *testing.T) { dir := testutil.TempDir() defer os.RemoveAll(dir) opts := &nodefs.Options{ // Note: defaultNode.GetAttr() calling file.GetAttr() is only useful if // AttrTimeout is zero. // See https://github.com/JonathonReinhart/gitlab-fuse/issues/2 Owner: fuse.CurrentOwner(), Debug: testutil.VerboseTest(), } root := nodefs.NewDefaultNode() s, _, err := nodefs.MountRoot(dir, root, opts) if err != nil { t.Fatalf("MountRoot: %v", err) } go s.Serve() if err := s.WaitMount(); err != nil { t.Fatal("WaitMount", err) } defer s.Unmount() // Attach another custom node type root.Inode().NewChild("foo", false, &myNode{ Node: nodefs.NewDefaultNode(), content: []byte("success"), }) filepath := path.Join(dir, "foo") // NewDefaultNode() should provide for stat that indicates 0-byte regular file fi, err := os.Stat(filepath) if err != nil { t.Fatalf("Stat: %v", err) } if mode := (fi.Mode() & os.ModeType); mode != 0 { // Mode() & ModeType should be zero for regular files t.Fatalf("Unexpected mode: %#o", mode) } if size := fi.Size(); size != 0 { t.Fatalf("Unexpected size: %d", size) } // But when we open the file, we should get the content content, err := ioutil.ReadFile(filepath) if err != nil { t.Fatalf("ReadFile: %v", err) } if string(content) != "success" { t.Fatalf("Unexpected content: %v", content) } } type myNode struct { nodefs.Node content []byte } func (n *myNode) Open(flags uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) { return nodefs.NewDataFile(n.content), fuse.OK } go-fuse-2.0.3/fuse/test/defaultread_test.go000066400000000000000000000032461364171671200206650ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package test import ( "io/ioutil" "os" "testing" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/pathfs" "github.com/hanwen/go-fuse/v2/internal/testutil" ) type 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 := testutil.TempDir() pathfs := pathfs.NewPathNodeFs(fs, nil) opts := nodefs.NewOptions() opts.Debug = testutil.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.") } } go-fuse-2.0.3/fuse/test/delete_linux_test.go000066400000000000000000000051461364171671200210670ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package test import ( "bytes" "io/ioutil" "os" "os/exec" "syscall" "testing" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/internal/testutil" ) 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 := testutil.TempDir() 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: testutil.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) } } go-fuse-2.0.3/fuse/test/file_lock_test.go000066400000000000000000000130611364171671200203300ustar00rootroot00000000000000// Copyright 2018 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // +build linux package test import ( "io/ioutil" "os" "os/exec" "path/filepath" "sync" "syscall" "testing" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/internal/testutil" ) func TestFlockExclusive(t *testing.T) { cmd, err := exec.LookPath("flock") if err != nil { t.Skip("flock command not found.") } 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 = syscall.Flock(int(f.Fd()), syscall.LOCK_EX); err != nil { t.Errorf("Flock returned: %v", err) return } _, err = runExternalFlock(cmd, tc.mountFile) if err == nil { t.Errorf("Expected flock to fail, but it did not") } } func runExternalFlock(flockPath, fname string) ([]byte, error) { f, err := os.OpenFile(fname, os.O_WRONLY, 0) if err != nil { return nil, err } defer f.Close() // in order to test the lock property we must use cmd.ExtraFiles (instead of passing the actual file) // if we were to pass the file then this flock command would fail to place the lock (returning a // 'file busy' error) as it is already opened and locked at this point (see above) cmd := exec.Command(flockPath, "--exclusive", "--nonblock", "3") cmd.Env = append(cmd.Env, "LC_ALL=C") // in case the user's shell language is different cmd.ExtraFiles = []*os.File{f} return cmd.CombinedOutput() } type lockingNode struct { nodefs.Node mu sync.Mutex getLkInvoked bool setLkInvoked bool setLkwInvoked bool } func (n *lockingNode) GetLkInvoked() bool { n.mu.Lock() defer n.mu.Unlock() return n.getLkInvoked } func (n *lockingNode) SetLkInvoked() bool { n.mu.Lock() defer n.mu.Unlock() return n.setLkInvoked } func (n *lockingNode) SetLkwInvoked() bool { n.mu.Lock() defer n.mu.Unlock() return n.setLkwInvoked } func (n *lockingNode) Open(flags uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) { return nodefs.NewDataFile([]byte("hello world")), fuse.OK } func (n *lockingNode) GetLk(file nodefs.File, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock, context *fuse.Context) (code fuse.Status) { n.mu.Lock() defer n.mu.Unlock() n.getLkInvoked = true return fuse.OK } func (n *lockingNode) SetLk(file nodefs.File, owner uint64, lk *fuse.FileLock, flags uint32, context *fuse.Context) (code fuse.Status) { n.mu.Lock() defer n.mu.Unlock() n.setLkInvoked = true return fuse.OK } func (n *lockingNode) SetLkw(file nodefs.File, owner uint64, lk *fuse.FileLock, flags uint32, context *fuse.Context) (code fuse.Status) { n.mu.Lock() defer n.mu.Unlock() n.setLkwInvoked = true return fuse.OK } func TestFlockInvoked(t *testing.T) { flock, err := exec.LookPath("flock") if err != nil { t.Skip("flock command not found.") } dir := testutil.TempDir() defer os.RemoveAll(dir) opts := &nodefs.Options{ Owner: fuse.CurrentOwner(), Debug: testutil.VerboseTest(), } root := nodefs.NewDefaultNode() conn := nodefs.NewFileSystemConnector(root, opts) mountOpts := fuse.MountOptions{ EnableLocks: true, } s, err := fuse.NewServer(conn.RawFS(), dir, &mountOpts) if err != nil { t.Fatal("NewServer", err) } go s.Serve() if err := s.WaitMount(); err != nil { t.Fatal("WaitMount", err) } defer s.Unmount() node := &lockingNode{ Node: nodefs.NewDefaultNode(), } root.Inode().NewChild("foo", false, node) realPath := filepath.Join(dir, "foo") if node.SetLkInvoked() { t.Fatalf("SetLk is invoked") } cmd := exec.Command(flock, "--nonblock", realPath, "echo", "locked") out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("flock %v: %v", err, string(out)) } if !node.SetLkInvoked() { t.Fatalf("SetLk is not invoked") } if node.SetLkwInvoked() { t.Fatalf("SetLkw is invoked") } cmd = exec.Command(flock, realPath, "echo", "locked") out, err = cmd.CombinedOutput() if err != nil { t.Fatalf("flock %v: %v", err, string(out)) } if !node.SetLkwInvoked() { t.Fatalf("SetLkw is not invoked") } } // Test that file system that don't implement locking are still // handled in the VFS layer. func TestNoLockSupport(t *testing.T) { flock, err := exec.LookPath("flock") if err != nil { t.Skip("flock command not found.") } tmp, err := ioutil.TempDir("", "TestNoLockSupport") if err != nil { t.Fatal(err) } mnt, err := ioutil.TempDir("", "TestNoLockSupport") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmp) defer os.RemoveAll(mnt) opts := &nodefs.Options{ Owner: fuse.CurrentOwner(), Debug: testutil.VerboseTest(), } root := nodefs.NewMemNodeFSRoot(tmp) lock := fuse.FileLock{} outLock := fuse.FileLock{} ctx := fuse.Context{} if status := root.GetLk(nil, uint64(1), &lock, uint32(0x0), &outLock, &ctx); status != fuse.ENOSYS { t.Fatalf("MemNodeFs should not implement locking") } s, _, err := nodefs.MountRoot(mnt, root, opts) if err != nil { t.Fatalf("MountRoot: %v", err) } go s.Serve() if err := s.WaitMount(); err != nil { t.Fatalf("WaitMount: %v", err) } defer s.Unmount() fn := mnt + "/file.txt" if err := ioutil.WriteFile(fn, []byte("content"), 0644); err != nil { t.Fatalf("WriteFile: %v", err) } cmd := exec.Command(flock, "--nonblock", fn, "echo", "locked") out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("flock %v: %v", err, string(out)) } } go-fuse-2.0.3/fuse/test/fsetattr_test.go000066400000000000000000000135431364171671200202420ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package test import ( "os" "syscall" "testing" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/pathfs" "github.com/hanwen/go-fuse/v2/internal/testutil" ) type MutableDataFile struct { nodefs.File data []byte fuse.Attr GetAttrCalled bool FsyncCalled bool } func (f *MutableDataFile) String() string { return "MutableDataFile" } func (f *MutableDataFile) Read(buf []byte, off int64) (fuse.ReadResult, fuse.Status) { end := int(off) + len(buf) if end > len(f.data) { end = len(f.data) } return fuse.ReadResultData(f.data[off:end]), fuse.OK } func (f *MutableDataFile) Write(d []byte, off int64) (uint32, fuse.Status) { end := int64(len(d)) + off if int(end) > len(f.data) { data := make([]byte, len(f.data), end) copy(data, f.data) f.data = data[:end] } copy(f.data[off:end], d) f.Attr.Size = uint64(len(f.data)) return uint32(end - off), fuse.OK } func (f *MutableDataFile) Flush() fuse.Status { return fuse.OK } func (f *MutableDataFile) Release() { } func (f *MutableDataFile) getAttr(out *fuse.Attr) { *out = f.Attr out.Size = uint64(len(f.data)) } func (f *MutableDataFile) GetAttr(out *fuse.Attr) fuse.Status { f.GetAttrCalled = true f.getAttr(out) return fuse.OK } func (f *MutableDataFile) Utimens(atime *time.Time, mtime *time.Time) fuse.Status { f.Attr.SetTimes(atime, mtime, nil) return fuse.OK } func (f *MutableDataFile) Truncate(size uint64) fuse.Status { f.data = f.data[:size] return fuse.OK } func (f *MutableDataFile) Chown(uid uint32, gid uint32) fuse.Status { f.Attr.Uid = uid f.Attr.Gid = gid return fuse.OK } func (f *MutableDataFile) Chmod(perms uint32) fuse.Status { f.Attr.Mode = (f.Attr.Mode &^ 07777) | perms return fuse.OK } func (f *MutableDataFile) Fsync(flags int) fuse.Status { f.FsyncCalled = true return fuse.OK } //////////////// // This FS only supports a single r/w file called "/file". type FSetAttrFs struct { pathfs.FileSystem file *MutableDataFile } func (fs *FSetAttrFs) GetXAttr(name string, attr string, context *fuse.Context) ([]byte, fuse.Status) { return nil, fuse.ENOATTR } func (fs *FSetAttrFs) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) { if name == "" { return &fuse.Attr{Mode: fuse.S_IFDIR | 0700}, fuse.OK } if name == "file" && fs.file != nil { var a fuse.Attr fs.file.getAttr(&a) a.Mode |= fuse.S_IFREG return &a, fuse.OK } return nil, fuse.ENOENT } func (fs *FSetAttrFs) Open(name string, flags uint32, context *fuse.Context) (nodefs.File, fuse.Status) { if name == "file" { return fs.file, fuse.OK } return nil, fuse.ENOENT } func (fs *FSetAttrFs) Create(name string, flags uint32, mode uint32, context *fuse.Context) (nodefs.File, fuse.Status) { if name == "file" { f := NewFile() fs.file = f fs.file.Attr.Mode = mode return f, fuse.OK } return nil, fuse.ENOENT } func NewFile() *MutableDataFile { return &MutableDataFile{File: nodefs.NewDefaultFile()} } func setupFAttrTest(t *testing.T, fs pathfs.FileSystem) (dir string, clean func()) { dir = testutil.TempDir() nfs := pathfs.NewPathNodeFs(fs, nil) opts := nodefs.NewOptions() opts.Debug = testutil.VerboseTest() state, _, err := nodefs.MountRoot(dir, nfs.Root(), opts) if err != nil { t.Fatalf("MountNodeFileSystem failed: %v", err) } go state.Serve() if err := state.WaitMount(); err != nil { t.Fatal("WaitMount", err) } clean = func() { if err := state.Unmount(); err != nil { t.Errorf("cleanup: Unmount: %v", err) } else { os.RemoveAll(dir) } } if state.KernelSettings().Flags&fuse.CAP_FILE_OPS == 0 { clean() t.Skip("Mount does not support file operations") } return dir, clean } func TestFSetAttr(t *testing.T) { fSetAttrFs := &FSetAttrFs{ FileSystem: pathfs.NewDefaultFileSystem(), } fs := pathfs.NewLockingFileSystem(fSetAttrFs) dir, clean := setupFAttrTest(t, fs) defer func() { if clean != nil { clean() } }() fn := dir + "/file" f, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY, 0755) if err != nil { t.Fatalf("OpenFile failed: %v", err) } defer f.Close() fi, err := f.Stat() if err != nil { t.Fatalf("Stat failed: %v", err) } _, err = f.WriteString("hello") if err != nil { t.Fatalf("WriteString failed: %v", err) } code := syscall.Ftruncate(int(f.Fd()), 3) if code != nil { t.Error("truncate retval", os.NewSyscallError("Ftruncate", code)) } if a, status := fs.GetAttr("file", nil); !status.Ok() { t.Fatalf("GetAttr: status %v", status) } else if a.Size != 3 { t.Errorf("truncate: size %d, status %v", a.Size, status) } if err := f.Chmod(024); err != nil { t.Fatalf("Chmod failed: %v", err) } if a, status := fs.GetAttr("file", nil); !status.Ok() { t.Errorf("chmod: %v", status) } else if a.Mode&07777 != 024 { t.Errorf("getattr after chmod: %o", a.Mode&0777) } if err := os.Chtimes(fn, time.Unix(0, 100e3), time.Unix(0, 101e3)); err != nil { t.Fatalf("Chtimes failed: %v", err) } if a, status := fs.GetAttr("file", nil); !status.Ok() { t.Errorf("GetAttr: %v", status) } else if a.Atimensec != 100e3 || a.Mtimensec != 101e3 { t.Errorf("Utimens: atime %d != 100e3 mtime %d != 101e3", a.Atimensec, a.Mtimensec) } newFi, err := f.Stat() if err != nil { t.Fatalf("Stat failed: %v", err) } i1 := fuse.ToStatT(fi).Ino i2 := fuse.ToStatT(newFi).Ino if i1 != i2 { t.Errorf("f.Lstat().Ino = %d. Returned %d before.", i2, i1) } if code := syscall.Fsync(int(f.Fd())); code != nil { t.Error("Fsync failed:", os.NewSyscallError("Fsync", code)) } // Close the file, otherwise we can't unmount. f.Close() // Shutdown the FUSE FS so we can safely look at fSetAttrFs clean() clean = nil if !fSetAttrFs.file.FsyncCalled { t.Error("Fsync was not called") } } go-fuse-2.0.3/fuse/test/loopback_darwin_test.go000066400000000000000000000010111364171671200215270ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package test import ( "syscall" ) func clearStatfs(s *syscall.Statfs_t) { empty := syscall.Statfs_t{} // FUSE can only set the following fields. empty.Blocks = s.Blocks empty.Bfree = s.Bfree empty.Bavail = s.Bavail empty.Files = s.Files empty.Ffree = s.Ffree empty.Iosize = s.Iosize empty.Bsize = s.Bsize // Clear out the rest. *s = empty } go-fuse-2.0.3/fuse/test/loopback_linux_test.go000066400000000000000000000115031364171671200214110ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package test import ( "io/ioutil" "os" "syscall" "testing" "time" "golang.org/x/sys/unix" "github.com/hanwen/go-fuse/v2/fuse" ) 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) } } // Setting nanoseconds should work for dates after 1970 func TestUtimesNano(t *testing.T) { tc := NewTestCase(t) defer tc.Cleanup() path := tc.mountFile err := ioutil.WriteFile(path, []byte("xyz"), 0600) if err != nil { t.Fatal(err) } ts := make([]syscall.Timespec, 2) // atime ts[0].Sec = 1 ts[0].Nsec = 2 // mtime ts[1].Sec = 3 ts[1].Nsec = 4 err = syscall.UtimesNano(path, ts) if err != nil { t.Fatal(err) } var st syscall.Stat_t err = syscall.Stat(path, &st) if err != nil { t.Fatal(err) } if st.Atim != ts[0] { t.Errorf("Wrong atime: %v, want: %v", st.Atim, ts[0]) } if st.Mtim != ts[1] { t.Errorf("Wrong mtime: %v, want: %v", st.Mtim, ts[1]) } } 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. unix.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 := unix.Getdents(int(d.Fd()), buf) if n == 0 { t.Errorf("directory is empty, entries '.' and '..' are missing") } } // Check that readdir(3) returns valid inode numbers in the directory entries func TestReaddirInodes(t *testing.T) { tc := NewTestCase(t) defer tc.Cleanup() // create "hello.txt" filename := "hello.txt" path := tc.orig + "/" + filename err := ioutil.WriteFile(path, []byte("xyz"), 0600) if err != nil { t.Fatal(err) } // open mountpoint dir d, err := os.Open(tc.mnt) if err != nil { t.Fatalf("Open failed: %v", err) } defer d.Close() buf := make([]byte, 100) // readdir(3) use getdents64(2) internally which returns linux_dirent64 // structures. We don't have readdir(3) so we call getdents64(2) directly. n, err := unix.Getdents(int(d.Fd()), buf) if n == 0 { t.Error("empty directory - we need at least one file") } buf = buf[:n] entries := parseDirents(buf) t.Logf("parseDirents returned %d entries", len(entries)) // Find "hello.txt" and check inode number. for _, entry := range entries { if entry.name != filename { continue } if entry.ino != 0 && entry.ino != fuse.FUSE_UNKNOWN_INO { // Inode number looks good, we are done. return } t.Errorf("got invalid inode number: %d = 0x%x", entry.ino, entry.ino) } t.Errorf("%q not found in directory listing", filename) } go-fuse-2.0.3/fuse/test/loopback_test.go000066400000000000000000000464211364171671200202010ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package test import ( "bytes" "fmt" "io" "io/ioutil" "math/rand" "os" "path/filepath" "reflect" "runtime" "syscall" "testing" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/pathfs" "github.com/hanwen/go-fuse/v2/internal/testutil" "github.com/hanwen/go-fuse/v2/posixtest" ) type testCase struct { // Per-testcase temporary directory, usually in /tmp, named something like // "$TESTNAME.123456". tmpDir string // Backing directory. Lives in tmpDir. orig string // Mountpoint. Lives in tmpDir. 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 = testutil.TempDir() 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: testutil.VerboseTest(), LookupKnownChildren: true, }) tc.state, err = fuse.NewServer( tc.connector.RawFS(), tc.mnt, &fuse.MountOptions{ SingleThreaded: true, Debug: testutil.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) var mode uint32 = 0757 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 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: %v", 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: %v", 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 TestPosix(t *testing.T) { tests := []string{ "SymlinkReadlink", "MkdirRmdir", "RenameOverwriteDestNoExist", "RenameOverwriteDestExist", "ReadDir", "ReadDirPicksUpCreate", "AppendWrite", } for _, k := range tests { f := posixtest.All[k] if f == nil { t.Fatalf("test %s missing", k) } t.Run(k, func(t *testing.T) { tc := NewTestCase(t) defer tc.Cleanup() f(t, tc.mnt) }) } } // 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 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()) } } // Test that READDIR works even if the directory is renamed after the OPENDIR. // This checks that the fix for https://github.com/hanwen/go-fuse/issues/252 // does not break this case. func TestReaddirRename(t *testing.T) { tc := NewTestCase(t) defer tc.Cleanup() tc.Mkdir(tc.origSubdir, 0777) tc.WriteFile(tc.origSubdir+"/file.txt", []byte("foo"), 0700) dir, err := os.Open(tc.mountSubdir) if err != nil { t.Fatalf("Open failed: %v", err) } defer dir.Close() err = os.Rename(tc.mountSubdir, tc.mountSubdir+".2") if err != nil { t.Fatalf("Rename failed: %v", err) } names, err := dir.Readdirnames(-1) if err != nil { t.Fatalf("Readdirnames failed: %v", err) } if len(names) != 1 || names[0] != "file.txt" { t.Fatalf("incorrect directory listing: %v", names) } } 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 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 TestNonVerboseStatFs(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 TestNonVerboseFStatFs(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 := testutil.TempDir() 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() } // 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) } } func TestLookupKnownChildrenAttrCopied(t *testing.T) { tc := NewTestCase(t) defer tc.Cleanup() if err := ioutil.WriteFile(tc.mountFile, []byte("hello"), 0644); err != nil { t.Fatalf("WriteFile: %v", err) } fi, err := os.Lstat(tc.mountFile) if err != nil { t.Fatalf("WriteFile: %v", err) } mode := fi.Mode() time.Sleep(2 * testTTL) if fi, err = os.Lstat(tc.mountFile); err != nil { t.Fatalf("Lstat: %v", err) } else if fi.Mode() != mode { t.Fatalf("got mode %o, want %o", fi.Mode(), mode) } } go-fuse-2.0.3/fuse/test/mount_test.go000066400000000000000000000125241364171671200175460ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package test import ( "io/ioutil" "os" "path/filepath" "runtime" "testing" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/pathfs" "github.com/hanwen/go-fuse/v2/internal/testutil" ) func TestMountOnExisting(t *testing.T) { ts := NewTestCase(t) defer ts.Cleanup() err := os.Mkdir(ts.mnt+"/mnt", 0777) if err != nil { t.Fatalf("Mkdir failed: %v", err) } nfs := nodefs.NewDefaultNode() code := ts.connector.Mount(ts.rootNode(), "mnt", nfs, nil) if code != fuse.EBUSY { t.Fatal("expect EBUSY:", code) } err = os.Remove(ts.mnt + "/mnt") if err != nil { t.Fatalf("Remove failed: %v", err) } code = ts.connector.Mount(ts.rootNode(), "mnt", nfs, nil) if !code.Ok() { t.Fatal("expect OK:", code) } code = ts.pathFs.Unmount("mnt") if !code.Ok() { t.Errorf("Unmount failed: %v", code) } } func TestMountRename(t *testing.T) { ts := NewTestCase(t) defer ts.Cleanup() fs := pathfs.NewPathNodeFs(pathfs.NewLoopbackFileSystem(ts.orig), nil) code := ts.connector.Mount(ts.rootNode(), "mnt", fs.Root(), nil) if !code.Ok() { t.Fatal("mount should succeed") } err := os.Rename(ts.mnt+"/mnt", ts.mnt+"/foobar") if fuse.ToStatus(err) != fuse.EBUSY { t.Fatal("rename mount point should fail with EBUSY:", err) } ts.pathFs.Unmount("mnt") } func TestMountReaddir(t *testing.T) { ts := NewTestCase(t) defer ts.Cleanup() fs := pathfs.NewPathNodeFs(pathfs.NewLoopbackFileSystem(ts.orig), nil) code := ts.connector.Mount(ts.rootNode(), "mnt", fs.Root(), nil) if !code.Ok() { t.Fatal("mount should succeed") } entries, err := ioutil.ReadDir(ts.mnt) if err != nil { t.Fatalf("ReadDir failed: %v", err) } if len(entries) != 1 || entries[0].Name() != "mnt" { t.Error("wrong readdir result", entries) } ts.pathFs.Unmount("mnt") } func TestRecursiveMount(t *testing.T) { ts := NewTestCase(t) defer ts.Cleanup() err := ioutil.WriteFile(ts.orig+"/hello.txt", []byte("blabla"), 0644) if err != nil { t.Fatalf("WriteFile failed: %v", err) } fs := pathfs.NewPathNodeFs(pathfs.NewLoopbackFileSystem(ts.orig), nil) code := ts.connector.Mount(ts.rootNode(), "mnt", fs.Root(), nil) if !code.Ok() { t.Fatal("mount should succeed") } submnt := ts.mnt + "/mnt" _, err = os.Lstat(submnt) if err != nil { t.Fatalf("Lstat failed: %v", err) } _, err = os.Lstat(filepath.Join(submnt, "hello.txt")) if err != nil { t.Fatalf("Lstat failed: %v", err) } f, err := os.Open(filepath.Join(submnt, "hello.txt")) if err != nil { t.Fatalf("Open failed: %v", err) } code = ts.pathFs.Unmount("mnt") if code != fuse.EBUSY { t.Error("expect EBUSY") } if err := f.Close(); err != nil { t.Errorf("close: %v", err) } // We can't avoid a sleep here: the file handle release is not // synchronized. t.Log("Waiting for kernel to flush file-close to fuse...") time.Sleep(testTTL) code = ts.pathFs.Unmount("mnt") if code != fuse.OK { t.Error("umount failed.", code) } } func TestDeletedUnmount(t *testing.T) { ts := NewTestCase(t) defer ts.Cleanup() submnt := filepath.Join(ts.mnt, "mnt") pfs2 := pathfs.NewPathNodeFs(pathfs.NewLoopbackFileSystem(ts.orig), nil) code := ts.connector.Mount(ts.rootNode(), "mnt", pfs2.Root(), nil) if !code.Ok() { t.Fatal("Mount error", code) } f, err := os.Create(filepath.Join(submnt, "hello.txt")) if err != nil { t.Fatalf("Create failed: %v", err) } err = os.Remove(filepath.Join(submnt, "hello.txt")) if err != nil { t.Fatalf("Remove failed: %v", err) } _, err = f.Write([]byte("bla")) if err != nil { t.Fatalf("Write failed: %v", err) } code = ts.pathFs.Unmount("mnt") if code != fuse.EBUSY { t.Error("expect EBUSY for unmount with open files", code) } f.Close() time.Sleep((3 * testTTL) / 2) code = ts.pathFs.Unmount("mnt") if !code.Ok() { t.Error("should succeed", code) } } func TestDefaultNodeMount(t *testing.T) { dir := testutil.TempDir() defer os.RemoveAll(dir) root := nodefs.NewDefaultNode() s, conn, err := nodefs.MountRoot(dir, root, nil) if err != nil { t.Fatalf("MountRoot: %v", err) } go s.Serve() if err := s.WaitMount(); err != nil { t.Fatal("WaitMount", err) } defer s.Unmount() if err := conn.Mount(root.Inode(), "sub", nodefs.NewDefaultNode(), nil); !err.Ok() { t.Fatalf("Mount: %v", err) } if entries, err := ioutil.ReadDir(dir); err != nil { t.Fatalf("ReadDir: %v", err) } else if len(entries) != 1 { t.Fatalf("got %d entries", len(entries)) } else if entries[0].Name() != "sub" { t.Fatalf("got %q, want %q", entries[0].Name(), "sub") } } func TestLiveness(t *testing.T) { dir := testutil.TempDir() defer os.RemoveAll(dir) root := nodefs.NewDefaultNode() s, _, err := nodefs.MountRoot(dir, root, nil) if err != nil { t.Fatalf("MountRoot: %v", err) } go s.Serve() if err := s.WaitMount(); err != nil { t.Fatal("WaitMount", err) } defer s.Unmount() if _, err := ioutil.ReadDir(dir); err != nil { t.Fatalf("ReadDir: %v", err) } // We previously encountered a sitation where a finalizer would close our fd out from under us. Try to force both finalizers to run and object destruction to complete. runtime.GC() runtime.GC() if _, err := ioutil.ReadDir(dir); err != nil { t.Fatalf("ReadDir: %v", err) } } go-fuse-2.0.3/fuse/test/nil_file_truncation_test.go000066400000000000000000000032701364171671200224310ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package test import ( "os" "testing" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/internal/testutil" ) type truncatableFile struct { nodefs.Node } func (d *truncatableFile) Open(flags uint32, context *fuse.Context) (nodefs.File, fuse.Status) { return nil, fuse.OK } func (d *truncatableFile) Truncate(file nodefs.File, size uint64, context *fuse.Context) fuse.Status { return fuse.OK } // TestNilFileTruncation verifies that the FUSE server process does not // crash when file truncation is performed on nil file handles. func TestNilFileTruncation(t *testing.T) { dir := testutil.TempDir() defer func() { err := os.Remove(dir) if err != nil { t.Fatal(err) } }() root := nodefs.NewDefaultNode() opts := nodefs.NewOptions() opts.Debug = testutil.VerboseTest() srv, _, err := nodefs.MountRoot(dir, root, opts) if err != nil { t.Fatal(err) } hello := &truncatableFile{ Node: nodefs.NewDefaultNode(), } root.Inode().NewChild("hello.txt", false, hello) go srv.Serve() if err := srv.WaitMount(); err != nil { t.Fatal("WaitMount", err) } defer func() { err := srv.Unmount() if err != nil { t.Fatal(err) } }() // truncate(). if err := os.Truncate(dir+"/hello.txt", 123); err != nil { t.Fatalf("truncate: %s", err) } // ftruncate(). f, err := os.OpenFile(dir+"/hello.txt", os.O_WRONLY, 0) if err != nil { t.Fatalf("open: %s", err) } defer f.Close() if err := f.Truncate(123); err != nil { t.Fatalf("ftruncate: %s", err) } } go-fuse-2.0.3/fuse/test/node_parallel_lookup_test.go000066400000000000000000000070751364171671200226030ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package test // verify that several lookup requests can be served in parallel without deadlock. import ( "context" "fmt" "io/ioutil" "log" "os" "testing" "golang.org/x/sync/errgroup" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/internal/testutil" ) // tRoot implements simple root node which Lookups children in predefined .nodes. // The Lookup is synchronized with main test driver on .lookupq and .lookupGo. type tRoot struct { nodefs.Node nodes map[string]nodefs.Node // name -> Node lookupq chan string // main <- fssrv: lookup(name) request received lookupGo chan struct{} // main -> fssrv: ok to further process lookup requests } func (r *tRoot) Lookup(out *fuse.Attr, name string, fctx *fuse.Context) (*nodefs.Inode, fuse.Status) { node, ok := r.nodes[name] if !ok { // e.g. it can be lookup for .Trash automatically issued by volume manager return nil, fuse.ENOENT } r.lookupq <- name // tell main driver that we received lookup(name) <-r.lookupGo // wait for main to allow us to continue st := node.GetAttr(out, nil, fctx) return node.Inode(), st } // verifyFileRead verifies that file @path has content == dataOK. func verifyFileRead(path string, dataOK string) error { v, err := ioutil.ReadFile(path) if err != nil { return err } if string(v) != dataOK { return fmt.Errorf("%s: file read: got %q ; want %q", path, v, dataOK) } return nil } func TestNodeParallelLookup(t *testing.T) { dir := testutil.TempDir() defer func() { err := os.Remove(dir) if err != nil { t.Fatal(err) } }() root := &tRoot{ Node: nodefs.NewDefaultNode(), nodes: make(map[string]nodefs.Node), lookupq: make(chan string), lookupGo: make(chan struct{}), } opts := nodefs.NewOptions() opts.LookupKnownChildren = true opts.Debug = testutil.VerboseTest() srv, _, err := nodefs.MountRoot(dir, root, opts) if err != nil { t.Fatal(err) } root.nodes["hello"] = NewDataNode([]byte("abc")) root.nodes["world"] = NewDataNode([]byte("def")) root.Inode().NewChild("hello", false, root.nodes["hello"]) root.Inode().NewChild("world", false, root.nodes["world"]) go srv.Serve() if err := srv.WaitMount(); err != nil { t.Fatal("WaitMount", err) } defer func() { err := srv.Unmount() if err != nil { t.Fatal(err) } }() // spawn 2 threads to access the files in parallel // this will deadlock if nodefs does not allow simultaneous Lookups to be handled. // see https://github.com/hanwen/go-fuse/commit/d0fca860 for context. ctx0, cancel := context.WithCancel(context.Background()) defer cancel() wg, ctx := errgroup.WithContext(ctx0) wg.Go(func() error { return verifyFileRead(dir + "/hello", "abc") }) wg.Go(func() error { return verifyFileRead(dir + "/world", "def") }) // wait till both threads queue into Lookup expect := map[string]struct{}{ // set of expected lookups "hello": struct{}{}, "world": struct{}{}, } loop: for len(expect) > 0 { var lookup string select { case <-ctx.Done(): break loop // wg.Wait will return the error case lookup = <-root.lookupq: // ok } if testutil.VerboseTest() { log.Printf("I: <- lookup %q", lookup) } _, ok := expect[lookup] if !ok { t.Fatalf("unexpected lookup: %q ; expect: %q", lookup, expect) } delete(expect, lookup) } // let both lookups continue close(root.lookupGo) err = wg.Wait() if err != nil { t.Fatal(err) } } go-fuse-2.0.3/fuse/test/node_test.go000066400000000000000000000041451364171671200173310ustar00rootroot00000000000000// Copyright 2018 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package test import ( "os" "sync" "testing" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/internal/testutil" ) type rootNode struct { nodefs.Node // represents backing store. mu sync.Mutex backing map[string]string } type blobNode struct { nodefs.Node content string } func (n *blobNode) GetAttr(out *fuse.Attr, file nodefs.File, context *fuse.Context) (code fuse.Status) { out.Mode = fuse.S_IFREG | 0777 out.Size = uint64(len(n.content)) return fuse.OK } func (n *rootNode) Lookup(out *fuse.Attr, name string, context *fuse.Context) (*nodefs.Inode, fuse.Status) { n.mu.Lock() defer n.mu.Unlock() want := n.backing[name] if want == "" { return nil, fuse.ENOENT } ch := n.Inode().GetChild(name) var blob *blobNode if ch != nil { blob = ch.Node().(*blobNode) if blob.content != want { n.Inode().RmChild(name) ch = nil } } if ch == nil { blob = &blobNode{nodefs.NewDefaultNode(), want} ch = n.Inode().NewChild(name, false, blob) } status := blob.GetAttr(out, nil, nil) return ch, status } func TestUpdateNode(t *testing.T) { dir := testutil.TempDir() root := &rootNode{ Node: nodefs.NewDefaultNode(), backing: map[string]string{"a": "aaa"}, } opts := nodefs.NewOptions() opts.Debug = testutil.VerboseTest() opts.EntryTimeout = 0 opts.LookupKnownChildren = true server, _, err := nodefs.MountRoot(dir, root, opts) if err != nil { t.Fatalf("MountRoot: %v", err) } go server.Serve() if err := server.WaitMount(); err != nil { t.Fatalf("WaitMount: %v", err) } defer server.Unmount() fi1, err := os.Lstat(dir + "/a") if err != nil { t.Fatal("Lstat", err) } if fi1.Size() != 3 { t.Fatalf("got %v, want sz 3", fi1.Size()) } root.mu.Lock() root.backing["a"] = "x" root.mu.Unlock() fi2, err := os.Lstat(dir + "/a") if err != nil { t.Fatal("Lstat", err) } if fi2.Size() != 1 { t.Fatalf("got %#v, want sz 1", fi2) } } go-fuse-2.0.3/fuse/test/nofile_test.go000066400000000000000000000057071364171671200176650ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package test import ( "io/ioutil" "os" "sync/atomic" "testing" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/internal/testutil" ) // exercise functionality when open returns 0 file handle. // NoFileNode is DataNode for which open returns File=nil. // // In other words all operations on a file opened from NoFileNode, are routed // to NoFileNode itself. type NoFileNode struct { *DataNode flags uint32 // FUSE flags for open, e.g. FOPEN_KEEP_CACHE nopen int32 // #Open called on us } // NewNoFileNode creates new file node for which Open will return File=nil, and // if flags !=0, will wrap it into WithFlags. func NewNoFileNode(data []byte, flags uint32) *NoFileNode { return &NoFileNode{ DataNode: NewDataNode(data), flags: flags, nopen: 0, } } func (d *NoFileNode) Open(flags uint32, context *fuse.Context) (nodefs.File, fuse.Status) { var f nodefs.File = nil if d.flags != 0 { f = &nodefs.WithFlags{ File: f, FuseFlags: d.flags, } } atomic.AddInt32(&d.nopen, +1) return f, fuse.OK } func TestNoFile(t *testing.T) { dir := testutil.TempDir() defer func() { err := os.Remove(dir) if err != nil { t.Fatal(err) } }() // setup a filesystem with 2 files: // // open(hello.txt) -> nil // open(world.txt) -> WithFlags(nil, FOPEN_KEEP_CACHE) root := nodefs.NewDefaultNode() opts := nodefs.NewOptions() opts.Debug = testutil.VerboseTest() srv, _, err := nodefs.MountRoot(dir, root, opts) if err != nil { t.Fatal(err) } hello := NewNoFileNode([]byte("hello"), 0) world := NewNoFileNode([]byte("world"), fuse.FOPEN_KEEP_CACHE) root.Inode().NewChild("hello.txt", false, hello) root.Inode().NewChild("world.txt", false, world) go srv.Serve() if err := srv.WaitMount(); err != nil { t.Fatal("WaitMount", err) } defer func() { err := srv.Unmount() if err != nil { t.Fatal(err) } }() // assertOpenRead asserts that file @ path reads as dataOK, and that // corresponding Open is called on the filesystem. assertOpenRead := func(path string, node *NoFileNode, dataOK string) { t.Helper() nopenPre := atomic.LoadInt32(&node.nopen) v, err := ioutil.ReadFile(dir + path) if err != nil { t.Fatalf("%s: read: %s", path, err) } if string(v) != dataOK { t.Fatalf("%s: read: got %q ; want %q", path, v, dataOK) } // make sure that path.Open() was called. // // this can be not the case if the filesystem has a node with // Open that returns ENOSYS - then the kernel won't ever call // open for all other nodes. nopen := atomic.LoadInt32(&node.nopen) if nopen == nopenPre { t.Fatalf("%s: read: open was not called", path) } } // make sure all nodes can be open/read. assertOpenRead("/hello.txt", hello, "hello") assertOpenRead("/world.txt", world, "world") } go-fuse-2.0.3/fuse/test/notify_linux_test.go000066400000000000000000000072001364171671200211260ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package test import ( "os" "testing" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/pathfs" "github.com/hanwen/go-fuse/v2/internal/testutil" ) type NotifyFs struct { pathfs.FileSystem size uint64 exist bool sizeChan chan uint64 existChan chan bool } func newNotifyFs() *NotifyFs { return &NotifyFs{ FileSystem: pathfs.NewDefaultFileSystem(), sizeChan: make(chan uint64, 1), existChan: make(chan bool, 1), } } func (fs *NotifyFs) Exists() bool { select { case s := <-fs.existChan: fs.exist = s default: } return fs.exist } func (fs *NotifyFs) Size() uint64 { select { case s := <-fs.sizeChan: fs.size = s default: } return fs.size } func (fs *NotifyFs) 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" || (name == "dir/file" && fs.Exists()) { return &fuse.Attr{Mode: fuse.S_IFREG | 0644, Size: fs.Size()}, fuse.OK } if name == "dir" { return &fuse.Attr{Mode: fuse.S_IFDIR | 0755}, fuse.OK } return nil, fuse.ENOENT } func (fs *NotifyFs) Open(name string, f uint32, context *fuse.Context) (nodefs.File, fuse.Status) { return nodefs.NewDataFile([]byte{42}), fuse.OK } type NotifyTest struct { fs *NotifyFs pathfs *pathfs.PathNodeFs connector *nodefs.FileSystemConnector dir string state *fuse.Server } func NewNotifyTest(t *testing.T) *NotifyTest { me := &NotifyTest{} me.fs = newNotifyFs() me.dir = testutil.TempDir() entryTTL := 100 * time.Millisecond opts := &nodefs.Options{ EntryTimeout: entryTTL, AttrTimeout: entryTTL, NegativeTimeout: entryTTL, Debug: testutil.VerboseTest(), } me.pathfs = pathfs.NewPathNodeFs(me.fs, nil) var err error me.state, me.connector, err = nodefs.MountRoot(me.dir, me.pathfs.Root(), opts) if err != nil { t.Fatalf("MountNodeFileSystem failed: %v", err) } go me.state.Serve() if err := me.state.WaitMount(); err != nil { t.Fatal("WaitMount", err) } return me } func (t *NotifyTest) Clean() { err := t.state.Unmount() if err == nil { os.RemoveAll(t.dir) } } func TestInodeNotify(t *testing.T) { test := NewNotifyTest(t) defer test.Clean() fs := test.fs dir := test.dir fs.sizeChan <- 42 fi, err := os.Lstat(dir + "/file") if err != nil { t.Fatalf("Lstat failed: %v", err) } if fi.Mode()&os.ModeType != 0 || fi.Size() != 42 { t.Error(fi) } fs.sizeChan <- 666 fi, err = os.Lstat(dir + "/file") if err != nil { t.Fatalf("Lstat failed: %v", err) } if fi.Mode()&os.ModeType != 0 || fi.Size() == 666 { t.Error(fi) } code := test.pathfs.FileNotify("file", -1, 0) if !code.Ok() { t.Error(code) } fi, err = os.Lstat(dir + "/file") if err != nil { t.Fatalf("Lstat failed: %v", err) } if fi.Mode()&os.ModeType != 0 || fi.Size() != 666 { t.Error(fi) } } func TestEntryNotify(t *testing.T) { test := NewNotifyTest(t) defer test.Clean() dir := test.dir test.fs.sizeChan <- 42 test.fs.existChan <- false fn := dir + "/dir/file" fi, _ := os.Lstat(fn) if fi != nil { t.Errorf("File should not exist, %#v", fi) } test.fs.existChan <- true fi, _ = os.Lstat(fn) if fi != nil { t.Errorf("negative entry should have been cached: %#v", fi) } code := test.pathfs.EntryNotify("dir", "file") if !code.Ok() { t.Errorf("EntryNotify returns error: %v", code) } fi, err := os.Lstat(fn) if err != nil { t.Fatalf("Lstat failed: %v", err) } } go-fuse-2.0.3/fuse/test/test.go000066400000000000000000000004061364171671200163200ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package test holds the tests for Go-FUSE and is not for end-user // consumption. package test go-fuse-2.0.3/fuse/test/umask_test.go000066400000000000000000000044651364171671200175310ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package test import ( "fmt" "os" "os/exec" "testing" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/pathfs" "github.com/hanwen/go-fuse/v2/internal/testutil" ) type umaskFS struct { pathfs.FileSystem mkdirMode uint32 createMode uint32 } func (fs *umaskFS) Create(name string, flags uint32, mode uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) { fs.createMode = mode return fs.FileSystem.Create(name, flags, mode, context) } func (fs *umaskFS) Mkdir(name string, mode uint32, context *fuse.Context) (code fuse.Status) { fs.mkdirMode = mode return fs.FileSystem.Mkdir(name, mode, context) } func TestUmask(t *testing.T) { tmpDir := testutil.TempDir() orig := tmpDir + "/orig" mnt := tmpDir + "/mnt" os.Mkdir(orig, 0700) os.Mkdir(mnt, 0700) var pfs pathfs.FileSystem pfs = pathfs.NewLoopbackFileSystem(orig) pfs = pathfs.NewLockingFileSystem(pfs) ufs := &umaskFS{FileSystem: pfs} pathFs := pathfs.NewPathNodeFs(ufs, &pathfs.PathNodeFsOptions{ ClientInodes: true}) connector := nodefs.NewFileSystemConnector(pathFs.Root(), &nodefs.Options{ EntryTimeout: testTTL, AttrTimeout: testTTL, NegativeTimeout: 0.0, Debug: testutil.VerboseTest(), LookupKnownChildren: true, }) server, err := fuse.NewServer( connector.RawFS(), mnt, &fuse.MountOptions{ SingleThreaded: true, Debug: testutil.VerboseTest(), }) if err != nil { t.Fatal("NewServer:", err) } go server.Serve() if err := server.WaitMount(); err != nil { t.Fatal("WaitMount", err) } // Make sure system setting does not affect test. mask := 020 cmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("umask %o && cd %s && mkdir x && touch y", mask, mnt)) if err := cmd.Run(); err != nil { t.Fatalf("cmd.Run: %v", err) } if err := server.Unmount(); err != nil { t.Fatalf("Unmount %v", err) } if got, want := ufs.mkdirMode&0777, uint32(0757); got != want { t.Errorf("got dirMode %o want %o", got, want) } if got, want := ufs.createMode&0666, uint32(0646); got != want { t.Errorf("got createMode %o want %o", got, want) } } go-fuse-2.0.3/fuse/test/xattr_test.go000066400000000000000000000042441364171671200175460ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // +build linux package test import ( "os" "path/filepath" "syscall" "testing" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/internal/testutil" ) // this file is linux-only, since it uses syscall.Getxattr. type xattrNode struct { nodefs.Node } func (n *xattrNode) OnMount(fsConn *nodefs.FileSystemConnector) { n.Inode().NewChild("child", false, &xattrChildNode{nodefs.NewDefaultNode()}) } type xattrChildNode struct { nodefs.Node } func (n *xattrChildNode) GetXAttr(attr string, context *fuse.Context) ([]byte, fuse.Status) { if attr == "attr" { return []byte("value"), fuse.OK } return []byte(""), fuse.OK } func TestDefaultXAttr(t *testing.T) { dir := testutil.TempDir() defer os.RemoveAll(dir) root := &xattrNode{ Node: nodefs.NewDefaultNode(), } opts := nodefs.NewOptions() opts.Debug = testutil.VerboseTest() s, _, err := nodefs.MountRoot(dir, root, opts) if err != nil { t.Fatalf("MountRoot: %v", err) } go s.Serve() if err := s.WaitMount(); err != nil { t.Fatal("WaitMount", err) } defer s.Unmount() var data [1024]byte sz, err := syscall.Getxattr(filepath.Join(dir, "child"), "attr", data[:]) if err != nil { t.Fatalf("Getxattr: %v", err) } else if got, want := string(data[:sz]), "value"; got != want { t.Fatalf("got %q, want %q", got, want) } } func TestEmptyXAttr(t *testing.T) { dir := testutil.TempDir() defer os.RemoveAll(dir) root := &xattrNode{ Node: nodefs.NewDefaultNode(), } opts := nodefs.NewOptions() opts.Debug = testutil.VerboseTest() s, _, err := nodefs.MountRoot(dir, root, opts) if err != nil { t.Fatalf("MountRoot: %v", err) } go s.Serve() if err := s.WaitMount(); err != nil { t.Fatal("WaitMount", err) } defer s.Unmount() var data [1024]byte sz, err := syscall.Getxattr(filepath.Join(dir, "child"), "attr2", data[:]) if err != nil { t.Fatalf("Getxattr: %v", err) } else if got, want := string(data[:sz]), ""; got != want { t.Fatalf("got %q, want %q", got, want) } } go-fuse-2.0.3/fuse/test/xfs_test.go000066400000000000000000000051411364171671200172010ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // +build linux package test import ( "fmt" "os" "path/filepath" "syscall" "testing" "unsafe" "golang.org/x/sys/unix" ) func clen(n []byte) int { for i := 0; i < len(n); i++ { if n[i] == 0 { return i } } return len(n) } type CDirent struct { ino uint64 ntype uint8 off int64 name string } func parseDirents(buf []byte) []CDirent { var result []CDirent for len(buf) > 0 { de := *(*unix.Dirent)(unsafe.Pointer(&buf[0])) buf = buf[de.Reclen:] bytes := (*[10000]byte)(unsafe.Pointer(&de.Name[0])) var name = string(bytes[0:clen(bytes[:])]) result = append(result, CDirent{ ino: de.Ino, ntype: de.Type, off: de.Off, name: name, }) } return result } // This test is inspired on xfstest, t_dir_offset2.c func TestReaddirPlusSeek(t *testing.T) { tc := NewTestCase(t) defer tc.Cleanup() var names []string for i := 0; i < 20; i++ { names = append(names, fmt.Sprintf("abcd%d", i)) } for _, n := range names { if err := os.MkdirAll(filepath.Join(tc.origSubdir, n), 0755); err != nil { t.Fatalf("Mkdir failed: %v", err) } } fd, err := syscall.Open(tc.mountSubdir, syscall.O_RDONLY, 0755) if err != nil { t.Fatalf("Open(%q): %v", tc.mountSubdir, err) } defer syscall.Close(int(fd)) // store offsets type entryOff struct { ino uint64 off int64 } previous := map[int]entryOff{} var bufdata [1024]byte for { buf := bufdata[:] n, err := syscall.ReadDirent(fd, buf) if err != nil { t.Fatalf("ReadDirent: %v", err) } if n == 0 { break } buf = buf[:n] for _, d := range parseDirents(buf) { if d.name == "." || d.name == ".." { continue } i := len(previous) previous[i] = entryOff{d.ino, d.off} } } for i := len(previous) - 1; i >= 0; i-- { var off int64 if i > 0 { off = previous[i-1].off } if _, err := syscall.Seek(fd, off, 0); err != nil { t.Fatalf("Seek %v", err) } buf := bufdata[:] n, err := syscall.ReadDirent(fd, buf) if err != nil { t.Fatalf("readdir after seek %d: %v", i, err) } if n == 0 { t.Fatalf("no dirent after seek to %d", i) } ds := parseDirents(buf[:n]) if ds[0].ino != previous[i].ino { t.Errorf("got ino %d, want %d", ds[0].ino, previous[i].ino) } } // Delete has a forget as side-effect: make sure we get the lookup counts correct. for _, n := range names { full := filepath.Join(tc.mountSubdir, n) if err := syscall.Rmdir(full); err != nil { t.Fatalf("Rmdir(%q): %v", n, err) } } } go-fuse-2.0.3/fuse/typeprint.go000066400000000000000000000003471364171671200164240ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fuse func (a *Attr) String() string { return Print(a) } go-fuse-2.0.3/fuse/types.go000066400000000000000000000301161364171671200155270ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fuse import ( "io" "syscall" "time" ) const ( _DEFAULT_BACKGROUND_TASKS = 12 ) // Status is the errno number that a FUSE call returns to the kernel. type Status int32 const ( OK = Status(0) // EACCESS Permission denied EACCES = Status(syscall.EACCES) // EBUSY Device or resource busy EBUSY = Status(syscall.EBUSY) // EAGAIN Resource temporarily unavailable EAGAIN = Status(syscall.EAGAIN) // EINTR Call was interrupted EINTR = Status(syscall.EINTR) // EINVAL Invalid argument EINVAL = Status(syscall.EINVAL) // EIO I/O error EIO = Status(syscall.EIO) // ENOENT No such file or directory ENOENT = Status(syscall.ENOENT) // ENOSYS Function not implemented ENOSYS = Status(syscall.ENOSYS) // ENODATA No data available ENODATA = Status(syscall.ENODATA) // ENOTDIR Not a directory ENOTDIR = Status(syscall.ENOTDIR) // ENOTSUP Not supported ENOTSUP = Status(syscall.ENOTSUP) // EISDIR Is a directory EISDIR = Status(syscall.EISDIR) // EPERM Operation not permitted EPERM = Status(syscall.EPERM) // ERANGE Math result not representable ERANGE = Status(syscall.ERANGE) // EXDEV Cross-device link EXDEV = Status(syscall.EXDEV) // EBADF Bad file number EBADF = Status(syscall.EBADF) // ENODEV No such device ENODEV = Status(syscall.ENODEV) // EROFS Read-only file system EROFS = Status(syscall.EROFS) ) type ForgetIn struct { InHeader Nlookup uint64 } // batch forget is handled internally. type _ForgetOne struct { NodeId uint64 Nlookup uint64 } // batch forget is handled internally. type _BatchForgetIn struct { InHeader Count uint32 Dummy uint32 } type MkdirIn struct { InHeader // The mode for the new directory. The calling process' umask // is already factored into the mode. Mode uint32 Umask uint32 } type Rename1In struct { InHeader Newdir uint64 } type RenameIn struct { InHeader Newdir uint64 Flags uint32 Padding uint32 } type LinkIn struct { InHeader Oldnodeid uint64 } type Owner struct { Uid uint32 Gid uint32 } const ( // SetAttrIn.Valid FATTR_MODE = (1 << 0) FATTR_UID = (1 << 1) FATTR_GID = (1 << 2) FATTR_SIZE = (1 << 3) FATTR_ATIME = (1 << 4) FATTR_MTIME = (1 << 5) FATTR_FH = (1 << 6) FATTR_ATIME_NOW = (1 << 7) FATTR_MTIME_NOW = (1 << 8) FATTR_LOCKOWNER = (1 << 9) FATTR_CTIME = (1 << 10) ) type SetAttrInCommon struct { InHeader Valid uint32 Padding uint32 Fh uint64 Size uint64 LockOwner uint64 Atime uint64 Mtime uint64 Ctime uint64 Atimensec uint32 Mtimensec uint32 Ctimensec uint32 Mode uint32 Unused4 uint32 Owner Unused5 uint32 } // GetFh returns the file handle if available, or 0 if undefined. func (s *SetAttrInCommon) GetFh() (uint64, bool) { if s.Valid&FATTR_FH != 0 { return s.Fh, true } return 0, false } func (s *SetAttrInCommon) GetMode() (uint32, bool) { if s.Valid&FATTR_MODE != 0 { return s.Mode & 07777, true } return 0, false } func (s *SetAttrInCommon) GetUID() (uint32, bool) { if s.Valid&FATTR_UID != 0 { return s.Uid, true } return ^uint32(0), false } func (s *SetAttrInCommon) GetGID() (uint32, bool) { if s.Valid&FATTR_GID != 0 { return s.Gid, true } return ^uint32(0), false } func (s *SetAttrInCommon) GetSize() (uint64, bool) { if s.Valid&FATTR_SIZE != 0 { return s.Size, true } return 0, false } func (s *SetAttrInCommon) GetMTime() (time.Time, bool) { var t time.Time if s.Valid&FATTR_MTIME != 0 { if s.Valid&FATTR_MTIME_NOW != 0 { t = time.Now() } else { t = time.Unix(int64(s.Mtime), int64(s.Mtimensec)) } return t, true } return t, false } func (s *SetAttrInCommon) GetATime() (time.Time, bool) { var t time.Time if s.Valid&FATTR_ATIME != 0 { if s.Valid&FATTR_ATIME_NOW != 0 { t = time.Now() } else { t = time.Unix(int64(s.Atime), int64(s.Atimensec)) } return t, true } return t, false } func (s *SetAttrInCommon) GetCTime() (time.Time, bool) { var t time.Time if s.Valid&FATTR_CTIME != 0 { t = time.Unix(int64(s.Ctime), int64(s.Ctimensec)) return t, true } return t, false } const RELEASE_FLUSH = (1 << 0) type ReleaseIn struct { InHeader Fh uint64 Flags uint32 ReleaseFlags uint32 LockOwner uint64 } type OpenIn struct { InHeader Flags uint32 Mode uint32 } const ( // OpenOut.Flags FOPEN_DIRECT_IO = (1 << 0) FOPEN_KEEP_CACHE = (1 << 1) FOPEN_NONSEEKABLE = (1 << 2) FOPEN_CACHE_DIR = (1 << 3) FOPEN_STREAM = (1 << 4) ) type OpenOut struct { Fh uint64 OpenFlags uint32 Padding uint32 } // To be set in InitIn/InitOut.Flags. const ( CAP_ASYNC_READ = (1 << 0) CAP_POSIX_LOCKS = (1 << 1) CAP_FILE_OPS = (1 << 2) CAP_ATOMIC_O_TRUNC = (1 << 3) CAP_EXPORT_SUPPORT = (1 << 4) CAP_BIG_WRITES = (1 << 5) CAP_DONT_MASK = (1 << 6) CAP_SPLICE_WRITE = (1 << 7) CAP_SPLICE_MOVE = (1 << 8) CAP_SPLICE_READ = (1 << 9) CAP_FLOCK_LOCKS = (1 << 10) CAP_IOCTL_DIR = (1 << 11) CAP_AUTO_INVAL_DATA = (1 << 12) CAP_READDIRPLUS = (1 << 13) CAP_READDIRPLUS_AUTO = (1 << 14) CAP_ASYNC_DIO = (1 << 15) CAP_WRITEBACK_CACHE = (1 << 16) CAP_NO_OPEN_SUPPORT = (1 << 17) CAP_PARALLEL_DIROPS = (1 << 18) CAP_HANDLE_KILLPRIV = (1 << 19) CAP_POSIX_ACL = (1 << 20) CAP_ABORT_ERROR = (1 << 21) CAP_MAX_PAGES = (1 << 22) CAP_CACHE_SYMLINKS = (1 << 23) CAP_NO_OPENDIR_SUPPORT = (1 << 24) CAP_EXPLICIT_INVAL_DATA = (1 << 25) ) type InitIn struct { InHeader Major uint32 Minor uint32 MaxReadAhead uint32 Flags uint32 } type InitOut struct { Major uint32 Minor uint32 MaxReadAhead uint32 Flags uint32 MaxBackground uint16 CongestionThreshold uint16 MaxWrite uint32 TimeGran uint32 MaxPages uint16 Padding uint16 Unused [8]uint32 } type _CuseInitIn struct { InHeader Major uint32 Minor uint32 Unused uint32 Flags uint32 } type _CuseInitOut struct { Major uint32 Minor uint32 Unused uint32 Flags uint32 MaxRead uint32 MaxWrite uint32 DevMajor uint32 DevMinor uint32 Spare [10]uint32 } type InterruptIn struct { InHeader Unique uint64 } type _BmapIn struct { InHeader Block uint64 Blocksize uint32 Padding uint32 } type _BmapOut struct { Block uint64 } const ( FUSE_IOCTL_COMPAT = (1 << 0) FUSE_IOCTL_UNRESTRICTED = (1 << 1) FUSE_IOCTL_RETRY = (1 << 2) ) type _IoctlIn struct { InHeader Fh uint64 Flags uint32 Cmd uint32 Arg uint64 InSize uint32 OutSize uint32 } type _IoctlOut struct { Result int32 Flags uint32 InIovs uint32 OutIovs uint32 } type _PollIn struct { InHeader Fh uint64 Kh uint64 Flags uint32 Padding uint32 } type _PollOut struct { Revents uint32 Padding uint32 } type _NotifyPollWakeupOut struct { Kh uint64 } type WriteOut struct { Size uint32 Padding uint32 } type GetXAttrOut struct { Size uint32 Padding uint32 } type FileLock struct { Start uint64 End uint64 Typ uint32 Pid uint32 } type LkIn struct { InHeader Fh uint64 Owner uint64 Lk FileLock LkFlags uint32 Padding uint32 } type LkOut struct { Lk FileLock } // For AccessIn.Mask. const ( X_OK = 1 W_OK = 2 R_OK = 4 F_OK = 0 ) type AccessIn struct { InHeader Mask uint32 Padding uint32 } type FsyncIn struct { InHeader Fh uint64 FsyncFlags uint32 Padding uint32 } type OutHeader struct { Length uint32 Status int32 Unique uint64 } type NotifyInvalInodeOut struct { Ino uint64 Off int64 Length int64 } type NotifyInvalEntryOut struct { Parent uint64 NameLen uint32 Padding uint32 } type NotifyInvalDeleteOut struct { Parent uint64 Child uint64 NameLen uint32 Padding uint32 } type NotifyStoreOut struct { Nodeid uint64 Offset uint64 Size uint32 Padding uint32 } type NotifyRetrieveOut struct { NotifyUnique uint64 Nodeid uint64 Offset uint64 Size uint32 Padding uint32 } type NotifyRetrieveIn struct { InHeader Dummy1 uint64 Offset uint64 Size uint32 Dummy2 uint32 Dummy3 uint64 Dummy4 uint64 } const ( // NOTIFY_POLL = -1 // notify kernel that a poll waiting for IO on a file handle should wake up NOTIFY_INVAL_INODE = -2 // notify kernel that an inode should be invalidated NOTIFY_INVAL_ENTRY = -3 // notify kernel that a directory entry should be invalidated NOTIFY_STORE_CACHE = -4 // store data into kernel cache of an inode NOTIFY_RETRIEVE_CACHE = -5 // retrieve data from kernel cache of an inode NOTIFY_DELETE = -6 // notify kernel that a directory entry has been deleted // NOTIFY_CODE_MAX = -6 ) type FlushIn struct { InHeader Fh uint64 Unused uint32 Padding uint32 LockOwner uint64 } type LseekIn struct { InHeader Fh uint64 Offset uint64 Whence uint32 Padding uint32 } type LseekOut struct { Offset uint64 } type CopyFileRangeIn struct { InHeader FhIn uint64 OffIn uint64 NodeIdOut uint64 FhOut uint64 OffOut uint64 Len uint64 Flags uint64 } // EntryOut holds the result of a (directory,name) lookup. It has two // TTLs, one for the (directory, name) lookup itself, and one for the // attributes (eg. size, mode). The entry TTL also applies if the // lookup result is ENOENT ("negative entry lookup") type EntryOut struct { NodeId uint64 Generation uint64 EntryValid uint64 AttrValid uint64 EntryValidNsec uint32 AttrValidNsec uint32 Attr } // EntryTimeout returns entry timeout currently func (o *EntryOut) EntryTimeout() time.Duration { return time.Duration(uint64(o.EntryValidNsec) + o.EntryValid*1e9) } func (o *EntryOut) AttrTimeout() time.Duration { return time.Duration(uint64(o.AttrValidNsec) + o.AttrValid*1e9) } func (o *EntryOut) SetEntryTimeout(dt time.Duration) { ns := int64(dt) o.EntryValidNsec = uint32(ns % 1e9) o.EntryValid = uint64(ns / 1e9) } func (o *EntryOut) SetAttrTimeout(dt time.Duration) { ns := int64(dt) o.AttrValidNsec = uint32(ns % 1e9) o.AttrValid = uint64(ns / 1e9) } type AttrOut struct { AttrValid uint64 AttrValidNsec uint32 Dummy uint32 Attr } func (o *AttrOut) Timeout() time.Duration { return time.Duration(uint64(o.AttrValidNsec) + o.AttrValid*1e9) } func (o *AttrOut) SetTimeout(dt time.Duration) { ns := int64(dt) o.AttrValidNsec = uint32(ns % 1e9) o.AttrValid = uint64(ns / 1e9) } type CreateOut struct { EntryOut OpenOut } // Caller has data on the process making the FS call. // // The UID and GID are effective UID/GID, except for the ACCESS // opcode, where UID and GID are the real UIDs type Caller struct { Owner Pid uint32 } type InHeader struct { Length uint32 Opcode uint32 Unique uint64 NodeId uint64 Caller Padding uint32 } type StatfsOut struct { Blocks uint64 Bfree uint64 Bavail uint64 Files uint64 Ffree uint64 Bsize uint32 NameLen uint32 Frsize uint32 Padding uint32 Spare [6]uint32 } // _Dirent is what we send to the kernel, but we offer DirEntry and // DirEntryList to the user. type _Dirent struct { Ino uint64 Off uint64 NameLen uint32 Typ uint32 } const ( READ_LOCKOWNER = (1 << 1) ) const ( WRITE_CACHE = (1 << 0) WRITE_LOCKOWNER = (1 << 1) ) type FallocateIn struct { InHeader Fh uint64 Offset uint64 Length uint64 Mode uint32 Padding uint32 } func (lk *FileLock) ToFlockT(flockT *syscall.Flock_t) { flockT.Start = int64(lk.Start) if lk.End == (1<<63)-1 { flockT.Len = 0 } else { flockT.Len = int64(lk.End - lk.Start + 1) } flockT.Whence = int16(io.SeekStart) flockT.Type = int16(lk.Typ) } func (lk *FileLock) FromFlockT(flockT *syscall.Flock_t) { lk.Typ = uint32(flockT.Type) if flockT.Type != syscall.F_UNLCK { lk.Start = uint64(flockT.Start) if flockT.Len == 0 { lk.End = (1 << 63) - 1 } else { lk.End = uint64(flockT.Start + flockT.Len - 1) } } lk.Pid = uint32(flockT.Pid) } go-fuse-2.0.3/fuse/types_darwin.go000066400000000000000000000056301364171671200170760ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fuse import ( "syscall" ) const ( ENOATTR = Status(syscall.ENOATTR) // ENOATTR is not defined for all GOOS. // EREMOTEIO is not supported on Darwin. EREMOTEIO = Status(syscall.EIO) ) type Attr struct { Ino uint64 Size uint64 Blocks uint64 Atime uint64 Mtime uint64 Ctime uint64 Crtime_ uint64 // OS X Atimensec uint32 Mtimensec uint32 Ctimensec uint32 Crtimensec_ uint32 // OS X Mode uint32 Nlink uint32 Owner Rdev uint32 Flags_ uint32 // OS X } const ( FATTR_CRTIME = (1 << 28) FATTR_CHGTIME = (1 << 29) FATTR_BKUPTIME = (1 << 30) FATTR_FLAGS = (1 << 31) ) type SetAttrIn struct { SetAttrInCommon // OS X only Bkuptime_ uint64 Chgtime_ uint64 Crtime uint64 BkuptimeNsec uint32 ChgtimeNsec uint32 CrtimeNsec uint32 Flags_ uint32 // see chflags(2) } const ( FOPEN_PURGE_ATTR = (1 << 30) FOPEN_PURGE_UBC = (1 << 31) ) // compat with linux. const ( // Mask for GetAttrIn.Flags. If set, GetAttrIn has a file handle set. FUSE_GETATTR_FH = (1 << 0) ) type GetAttrIn struct { InHeader } func (g *GetAttrIn) Flags() uint32 { return 0 } func (g *GetAttrIn) Fh() uint64 { return 0 } // Uses OpenIn struct for create. type CreateIn struct { InHeader Flags uint32 Mode uint32 } type MknodIn struct { InHeader Mode uint32 Rdev uint32 } type ReadIn struct { InHeader Fh uint64 Offset uint64 Size uint32 ReadFlags uint32 } type WriteIn struct { InHeader Fh uint64 Offset uint64 Size uint32 WriteFlags uint32 } type SetXAttrIn struct { InHeader Size uint32 Flags uint32 Position uint32 Padding uint32 } type GetXAttrIn struct { InHeader Size uint32 Padding uint32 Position uint32 Padding2 uint32 } const ( CAP_CASE_INSENSITIVE = (1 << 29) CAP_VOL_RENAME = (1 << 30) CAP_XTIMES = (1 << 31) ) type GetxtimesOut struct { Bkuptime uint64 Crtime uint64 Bkuptimensec uint32 Crtimensec uint32 } type ExchangeIn struct { InHeader Olddir uint64 Newdir uint64 Options uint64 } func (s *StatfsOut) FromStatfsT(statfs *syscall.Statfs_t) { s.Blocks = statfs.Blocks s.Bfree = statfs.Bfree s.Bavail = statfs.Bavail s.Files = statfs.Files s.Ffree = statfs.Ffree s.Bsize = uint32(statfs.Iosize) // Iosize translates to Bsize: the optimal transfer size. s.Frsize = s.Bsize // Bsize translates to Frsize: the minimum transfer size. // The block counts are in units of statfs.Bsize. // If s.Bsize != statfs.Bsize, we have to recalculate the block counts // accordingly (s.Bsize is usually 256*statfs.Bsize). if s.Bsize > statfs.Bsize { adj := uint64(s.Bsize / statfs.Bsize) s.Blocks /= adj s.Bfree /= adj s.Bavail /= adj } } go-fuse-2.0.3/fuse/types_linux.go000066400000000000000000000045371364171671200167560ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fuse import ( "syscall" ) const ( ENOATTR = Status(syscall.ENODATA) // On Linux, ENOATTR is an alias for ENODATA. // EREMOTEIO Remote I/O error EREMOTEIO = Status(syscall.EREMOTEIO) ) type Attr struct { Ino uint64 Size uint64 // Blocks is the number of 512-byte blocks that the file occupies on disk. Blocks uint64 Atime uint64 Mtime uint64 Ctime uint64 Atimensec uint32 Mtimensec uint32 Ctimensec uint32 Mode uint32 Nlink uint32 Owner Rdev uint32 // Blksize is the preferred size for file system operations. Blksize uint32 Padding uint32 } type SetAttrIn struct { SetAttrInCommon } const ( // Mask for GetAttrIn.Flags. If set, GetAttrIn has a file handle set. FUSE_GETATTR_FH = (1 << 0) ) type GetAttrIn struct { InHeader Flags_ uint32 Dummy uint32 Fh_ uint64 } // Flags accesses the flags. This is a method, because OSXFuse does not // have GetAttrIn flags. func (g *GetAttrIn) Flags() uint32 { return g.Flags_ } // Fh accesses the file handle. This is a method, because OSXFuse does not // have GetAttrIn flags. func (g *GetAttrIn) Fh() uint64 { return g.Fh_ } type CreateIn struct { InHeader Flags uint32 // Mode for the new file; already takes Umask into account. Mode uint32 // Umask used for this create call. Umask uint32 Padding uint32 } type MknodIn struct { InHeader // Mode to use, including the Umask value Mode uint32 Rdev uint32 Umask uint32 Padding uint32 } type ReadIn struct { InHeader Fh uint64 Offset uint64 Size uint32 ReadFlags uint32 LockOwner uint64 Flags uint32 Padding uint32 } type WriteIn struct { InHeader Fh uint64 Offset uint64 Size uint32 WriteFlags uint32 LockOwner uint64 Flags uint32 Padding uint32 } type SetXAttrIn struct { InHeader Size uint32 Flags uint32 } type GetXAttrIn struct { InHeader Size uint32 Padding uint32 } func (s *StatfsOut) FromStatfsT(statfs *syscall.Statfs_t) { s.Blocks = statfs.Blocks s.Bsize = uint32(statfs.Bsize) s.Bfree = statfs.Bfree s.Bavail = statfs.Bavail s.Files = statfs.Files s.Ffree = statfs.Ffree s.Frsize = uint32(statfs.Frsize) s.NameLen = uint32(statfs.Namelen) } go-fuse-2.0.3/go.mod000066400000000000000000000003231364171671200141750ustar00rootroot00000000000000module github.com/hanwen/go-fuse/v2 require ( github.com/hanwen/go-fuse v1.0.0 github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 ) go 1.13 go-fuse-2.0.3/go.sum000066400000000000000000000011411364171671200142210ustar00rootroot00000000000000github.com/hanwen/go-fuse v1.0.0 h1:GxS9Zrn6c35/BnfiVsZVWmsG803xwE7eVRDvcf/BEVc= github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 h1:Ve1ORMCxvRmSXBwJK+t3Oy+V2vRW2OetUQBq4rJIkZE= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= go-fuse-2.0.3/internal/000077500000000000000000000000001364171671200147055ustar00rootroot00000000000000go-fuse-2.0.3/internal/access.go000066400000000000000000000021421364171671200164740ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package internal import ( "os/user" "strconv" ) // HasAccess tests if a caller can access a file with permissions // `perm` in mode `mask` func HasAccess(callerUid, callerGid, fileUid, fileGid uint32, perm uint32, mask uint32) bool { if callerUid == 0 { // root can do anything. return true } mask = mask & 7 if mask == 0 { return true } if callerUid == fileUid { if perm&(mask<<6) != 0 { return true } } if callerGid == fileGid { if perm&(mask<<3) != 0 { return true } } if perm&mask != 0 { return true } // Check other groups. if perm&(mask<<3) == 0 { // avoid expensive lookup if it's not allowed anyway return false } u, err := user.LookupId(strconv.Itoa(int(callerUid))) if err != nil { return false } gs, err := u.GroupIds() if err != nil { return false } fileGidStr := strconv.Itoa(int(fileGid)) for _, gidStr := range gs { if gidStr == fileGidStr { return true } } return false } go-fuse-2.0.3/internal/access_test.go000066400000000000000000000033641364171671200175420ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package internal import ( "os/user" "strconv" "testing" ) func TestHasAccess(t *testing.T) { type testcase struct { uid, gid, fuid, fgid uint32 perm, mask uint32 want bool } u, err := user.Current() if err != nil { t.Fatalf("user.Current: %v", err) } myIntId, _ := strconv.Atoi(u.Uid) myUid := uint32(myIntId) myIntGid, _ := strconv.Atoi(u.Gid) myGid := uint32(myIntGid) gids, err := u.GroupIds() if err != nil { t.Fatalf("user.GroupIds: %v", err) } var myOtherGid, notMyGid uint32 mygids := map[uint32]bool{} for _, g := range gids { gnum, _ := strconv.Atoi(g) gnum32 := uint32(gnum) mygids[gnum32] = true if g != u.Gid { myOtherGid = uint32(gnum) } } for i := uint32(1); i < 1000; i++ { if !mygids[i] { notMyGid = i break } } _ = myOtherGid _ = notMyGid cases := []testcase{ {myUid, myGid, myUid, myGid, 0100, 01, true}, {myUid, myGid, myUid + 1, notMyGid, 0001, 0001, true}, {myUid, myGid, myUid + 1, notMyGid, 0000, 0001, false}, {myUid, myGid, myUid + 1, notMyGid, 0007, 0000, true}, {myUid, myGid, myUid + 1, notMyGid, 0020, 002, false}, {myUid, myGid, myUid, myGid, 0000, 01, false}, {myUid, myGid, myUid, myGid, 0200, 01, false}, {0, myGid, myUid + 1, notMyGid, 0700, 01, true}, } if myOtherGid != 0 { cases = append(cases, testcase{myUid, myGid, myUid + 1, myOtherGid, 0020, 002, true}) } for i, tc := range cases { got := HasAccess(tc.uid, tc.gid, tc.fuid, tc.fgid, tc.perm, tc.mask) if got != tc.want { t.Errorf("%d: accessCheck(%v): got %v, want %v", i, tc, got, tc.want) } } } go-fuse-2.0.3/internal/testutil/000077500000000000000000000000001364171671200165625ustar00rootroot00000000000000go-fuse-2.0.3/internal/testutil/helpers.go000066400000000000000000000044121364171671200205540ustar00rootroot00000000000000// Copyright 2018 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package testutil import ( "syscall" "testing" "time" "github.com/hanwen/go-fuse/v2/fuse" ) // Check that loopback Utimens() works as expected. // Called by TestLoopbackFileUtimens and TestLoopbackFileSystemUtimens. // // Parameters: // path ........ path to the backing file // utimensFn ... Utimens() function that acts on the backing file func TestLoopbackUtimens(t *testing.T, path string, utimensFn func(atime *time.Time, mtime *time.Time) fuse.Status) { // Arbitrary date: 05/02/2018 @ 7:57pm (UTC) t0sec := int64(1525291058) // Read original timestamp var st syscall.Stat_t err := syscall.Stat(path, &st) if err != nil { t.Fatal("Stat", err) } // FromStat handles the differently-named Stat_t fields on Linux and // Darwin var a1 fuse.Attr a1.FromStat(&st) // Change atime, keep mtime t0 := time.Unix(t0sec, 0) status := utimensFn(&t0, nil) if !status.Ok() { t.Fatal("utimensFn", status) } err = syscall.Stat(path, &st) if err != nil { t.Fatal(err) } var a2 fuse.Attr a2.FromStat(&st) if a1.Mtime != a2.Mtime { t.Errorf("mtime has changed: %v -> %v", a1.Mtime, a2.Mtime) } if a2.Atime != uint64(t0.Unix()) { t.Errorf("wrong atime: got %v want %v", a2.Atime, t0.Unix()) } // Change mtime, keep atime t1 := time.Unix(t0sec+123, 0) status = utimensFn(nil, &t1) if !status.Ok() { t.Fatal("utimensFn", status) } err = syscall.Stat(path, &st) if err != nil { t.Fatal("Stat", err) } var a3 fuse.Attr a3.FromStat(&st) if a2.Atime != a3.Atime { t.Errorf("atime has changed: %v -> %v", a2.Atime, a3.Atime) } if a3.Mtime != uint64(t1.Unix()) { t.Errorf("got mtime %v, want %v", a3.Mtime, t1.Unix()) } // Change both mtime and atime ta := time.Unix(t0sec+456, 0) tm := time.Unix(t0sec+789, 0) status = utimensFn(&ta, &tm) if !status.Ok() { t.Fatal("utimensFn", status) } err = syscall.Stat(path, &st) if err != nil { t.Fatal("Stat", err) } var a4 fuse.Attr a4.FromStat(&st) if a4.Atime != uint64(ta.Unix()) { t.Errorf("got atime %v, want %v", a4.Atime, ta.Unix()) } if a4.Mtime != uint64(tm.Unix()) { t.Errorf("got mtime %v, want %v", a4.Mtime, tm.Unix()) } } go-fuse-2.0.3/internal/testutil/log.go000066400000000000000000000004621364171671200176740ustar00rootroot00000000000000// Copyright 2018 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package testutil import "log" func init() { // For test, the date is irrelevant, but microseconds are. log.SetFlags(log.Lmicroseconds) } go-fuse-2.0.3/internal/testutil/tempdir.go000066400000000000000000000015311364171671200205550ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package testutil import ( "io/ioutil" "log" "runtime" "strings" ) // TempDir creates a temporary directory that includes the name of the // testcase. Panics if there was an I/O problem creating the directory. func TempDir() string { frames := make([]uintptr, 10) // at least 1 entry needed n := runtime.Callers(1, frames) lastName := "" for _, pc := range frames[:n] { f := runtime.FuncForPC(pc) name := f.Name() i := strings.LastIndex(name, ".") if i >= 0 { name = name[i+1:] } if strings.HasPrefix(name, "Test") { lastName = name } } dir, err := ioutil.TempDir("", lastName) if err != nil { log.Panicf("TempDir(%s): %v", lastName, err) } return dir } go-fuse-2.0.3/internal/testutil/verbose.go000066400000000000000000000010311364171671200205510ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package testutil import ( "bytes" "flag" "runtime" ) // VerboseTest returns true if the testing framework is run with -v. func VerboseTest() bool { var buf [2048]byte n := runtime.Stack(buf[:], false) if bytes.Index(buf[:n], []byte("TestNonVerbose")) != -1 { return false } flag := flag.Lookup("test.v") return flag != nil && flag.Value.String() == "true" } go-fuse-2.0.3/internal/utimens/000077500000000000000000000000001364171671200163715ustar00rootroot00000000000000go-fuse-2.0.3/internal/utimens/utimens_darwin.go000066400000000000000000000021211364171671200217440ustar00rootroot00000000000000// Copyright 2018 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package utimens import ( "syscall" "time" "github.com/hanwen/go-fuse/v2/fuse" ) // timeToTimeval converts time.Time to syscall.Timeval func timeToTimeval(t *time.Time) 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 var tv syscall.Timeval tv.Usec = int32(t.Nanosecond() / 1000) tv.Sec = t.Unix() return tv } // Fill converts a and m to a syscall.Timeval slice that can be passed // to syscall.Utimes. Missing values (if any) are taken from attr func Fill(a *time.Time, m *time.Time, attr *fuse.Attr) []syscall.Timeval { if a == nil { a2 := time.Unix(int64(attr.Atime), int64(attr.Atimensec)) a = &a2 } if m == nil { m2 := time.Unix(int64(attr.Mtime), int64(attr.Mtimensec)) m = &m2 } tv := make([]syscall.Timeval, 2) tv[0] = timeToTimeval(a) tv[1] = timeToTimeval(m) return tv } go-fuse-2.0.3/internal/utimens/utimens_linux.go000066400000000000000000000003631364171671200216250ustar00rootroot00000000000000// Copyright 2018 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package utimens // placeholder file so this package exists on all platforms. go-fuse-2.0.3/newunionfs/000077500000000000000000000000001364171671200152645ustar00rootroot00000000000000go-fuse-2.0.3/newunionfs/unionfs.go000066400000000000000000000256031364171671200173020ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package unionfs import ( "context" "crypto/md5" "fmt" "io/ioutil" "log" "path/filepath" "syscall" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" ) func filePathHash(path string) string { dir, base := filepath.Split(path) h := md5.New() h.Write([]byte(dir)) return fmt.Sprintf("%x-%s", h.Sum(nil)[:8], base) } type unionFSRoot struct { unionFSNode roots []string } type unionFSNode struct { fs.Inode } const delDir = "DELETIONS" var delDirHash = filePathHash(delDir) func (r *unionFSRoot) allMarkers(result map[string]struct{}) syscall.Errno { dir := filepath.Join(r.roots[0], delDir) ds, errno := fs.NewLoopbackDirStream(dir) if errno != 0 { return errno } defer ds.Close() for ds.HasNext() { e, errno := ds.Next() if errno != 0 { return errno } if e.Mode != syscall.S_IFREG { continue } result[e.Name] = struct{}{} } return 0 } func (r *unionFSRoot) rmMarker(name string) syscall.Errno { err := syscall.Unlink(r.markerPath(name)) if err != nil { return err.(syscall.Errno) } return 0 } func (r *unionFSRoot) writeMarker(name string) syscall.Errno { dir := filepath.Join(r.roots[0], delDir) var st syscall.Stat_t if err := syscall.Stat(dir, &st); err == syscall.ENOENT { if err := syscall.Mkdir(dir, 0755); err != nil { log.Printf("Mkdir %q: %v", dir, err) return syscall.EIO } } else if err != nil { return err.(syscall.Errno) } dest := r.markerPath(name) err := ioutil.WriteFile(dest, []byte(name), 0644) return fs.ToErrno(err) } func (r *unionFSRoot) markerPath(name string) string { return filepath.Join(r.roots[0], delDir, filePathHash(name)) } func (r *unionFSRoot) isDeleted(name string) bool { var st syscall.Stat_t err := syscall.Stat(r.markerPath(name), &st) return err == nil } func (n *unionFSNode) root() *unionFSRoot { return n.Root().Operations().(*unionFSRoot) } var _ = (fs.NodeSetattrer)((*unionFSNode)(nil)) func (n *unionFSNode) Setattr(ctx context.Context, fh fs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno { if errno := n.promote(); errno != 0 { return errno } if fh != nil { return fh.(fs.FileSetattrer).Setattr(ctx, in, out) } p := filepath.Join(n.root().roots[0], n.Path(nil)) fsa, ok := fh.(fs.FileSetattrer) if ok && fsa != nil { fsa.Setattr(ctx, in, out) } else { if m, ok := in.GetMode(); ok { if err := syscall.Chmod(p, m); err != nil { return fs.ToErrno(err) } } uid, uok := in.GetUID() gid, gok := in.GetGID() if uok || gok { suid := -1 sgid := -1 if uok { suid = int(uid) } if gok { sgid = int(gid) } if err := syscall.Chown(p, suid, sgid); err != nil { return fs.ToErrno(err) } } mtime, mok := in.GetMTime() atime, aok := in.GetATime() if mok || aok { ap := &atime mp := &mtime if !aok { ap = nil } if !mok { mp = nil } var ts [2]syscall.Timespec ts[0] = fuse.UtimeToTimespec(ap) ts[1] = fuse.UtimeToTimespec(mp) if err := syscall.UtimesNano(p, ts[:]); err != nil { return fs.ToErrno(err) } } if sz, ok := in.GetSize(); ok { if err := syscall.Truncate(p, int64(sz)); err != nil { return fs.ToErrno(err) } } } fga, ok := fh.(fs.FileGetattrer) if ok && fga != nil { fga.Getattr(ctx, out) } else { st := syscall.Stat_t{} err := syscall.Lstat(p, &st) if err != nil { return fs.ToErrno(err) } out.FromStat(&st) } return 0 } var _ = (fs.NodeCreater)((*unionFSNode)(nil)) func (n *unionFSNode) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (*fs.Inode, fs.FileHandle, uint32, syscall.Errno) { if n.IsRoot() && name == delDir { return nil, nil, 0, syscall.EPERM } var st syscall.Stat_t dirName, idx := n.getBranch(&st) if idx > 0 { if errno := n.promote(); errno != 0 { return nil, nil, 0, errno } idx = 0 } fullPath := filepath.Join(dirName, name) r := n.root() if errno := r.rmMarker(fullPath); errno != 0 && errno != syscall.ENOENT { return nil, nil, 0, errno } abs := filepath.Join(n.root().roots[0], fullPath) fd, err := syscall.Creat(abs, mode) if err != nil { return nil, nil, 0, err.(syscall.Errno) } if err := syscall.Fstat(fd, &st); err != nil { // now what? syscall.Close(fd) syscall.Unlink(abs) return nil, nil, 0, err.(syscall.Errno) } ch := n.NewInode(ctx, &unionFSNode{}, fs.StableAttr{Mode: st.Mode, Ino: st.Ino}) out.FromStat(&st) return ch, fs.NewLoopbackFile(fd), 0, 0 } var _ = (fs.NodeOpener)((*unionFSNode)(nil)) func (n *unionFSNode) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint32, syscall.Errno) { isWR := (flags&syscall.O_RDWR != 0) || (flags&syscall.O_WRONLY != 0) var st syscall.Stat_t nm, idx := n.getBranch(&st) if isWR && idx > 0 { if errno := n.promote(); errno != 0 { return nil, 0, errno } idx = 0 } fd, err := syscall.Open(filepath.Join(n.root().roots[idx], nm), int(flags), 0) if err != nil { return nil, 0, err.(syscall.Errno) } return fs.NewLoopbackFile(fd), 0, 0 } var _ = (fs.NodeGetattrer)((*unionFSNode)(nil)) func (n *unionFSNode) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno { var st syscall.Stat_t _, idx := n.getBranch(&st) if idx < 0 { return syscall.ENOENT } out.FromStat(&st) return 0 } var _ = (fs.NodeLookuper)((*unionFSNode)(nil)) func (n *unionFSNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) { if n.IsRoot() && name == delDir { return nil, syscall.ENOENT } var st syscall.Stat_t p := filepath.Join(n.Path(nil), name) idx := n.root().getBranch(p, &st) if idx >= 0 { // XXX use idx in Ino? ch := n.NewInode(ctx, &unionFSNode{}, fs.StableAttr{Mode: st.Mode, Ino: st.Ino}) out.FromStat(&st) out.Mode |= 0111 return ch, 0 } return nil, syscall.ENOENT } var _ = (fs.NodeUnlinker)((*unionFSNode)(nil)) func (n *unionFSNode) Unlink(ctx context.Context, name string) syscall.Errno { return n.root().delPath(filepath.Join(n.Path(nil), name)) } var _ = (fs.NodeRmdirer)((*unionFSNode)(nil)) func (n *unionFSNode) Rmdir(ctx context.Context, name string) syscall.Errno { return n.root().delPath(filepath.Join(n.Path(nil), name)) } var _ = (fs.NodeSymlinker)((*unionFSNode)(nil)) func (n *unionFSNode) Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) { n.promote() path := filepath.Join(n.root().roots[0], n.Path(nil), name) err := syscall.Symlink(target, path) if err != nil { return nil, err.(syscall.Errno) } var st syscall.Stat_t if err := syscall.Lstat(path, &st); err != nil { return nil, err.(syscall.Errno) } out.FromStat(&st) ch := n.NewInode(ctx, &unionFSNode{}, fs.StableAttr{ Mode: syscall.S_IFLNK, Ino: st.Ino, }) return ch, 0 } var _ = (fs.NodeReadlinker)((*unionFSNode)(nil)) func (n *unionFSNode) Readlink(ctx context.Context) ([]byte, syscall.Errno) { nm, idx := n.getBranch(nil) var buf [1024]byte count, err := syscall.Readlink(filepath.Join(n.root().roots[idx], nm), buf[:]) if err != nil { return nil, err.(syscall.Errno) } return buf[:count], 0 } var _ = (fs.NodeReaddirer)((*unionFSNode)(nil)) func (n *unionFSNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) { root := n.root() markers := map[string]struct{}{delDirHash: struct{}{}} // ignore error: assume no markers root.allMarkers(markers) dir := n.Path(nil) names := map[string]uint32{} for i := range root.roots { // deepest root first. readRoot(root.roots[len(root.roots)-i-1], dir, names) } result := make([]fuse.DirEntry, 0, len(names)) for nm, mode := range names { marker := filePathHash(filepath.Join(dir, nm)) if _, ok := markers[marker]; ok { continue } result = append(result, fuse.DirEntry{ Name: nm, Mode: mode, }) } return fs.NewListDirStream(result), 0 } func readRoot(root string, dir string, result map[string]uint32) { ds, errno := fs.NewLoopbackDirStream(filepath.Join(root, dir)) if errno != 0 { return } defer ds.Close() for ds.HasNext() { e, errno := ds.Next() if errno != 0 { return } result[e.Name] = e.Mode } } // getBranch returns the root where we can find the given file. It // will check the deletion markers in roots[0]. func (n *unionFSNode) getBranch(st *syscall.Stat_t) (string, int) { name := n.Path(nil) return name, n.root().getBranch(name, st) } func (r *unionFSRoot) getBranch(name string, st *syscall.Stat_t) int { if r.isDeleted(name) { return -1 } if st == nil { st = &syscall.Stat_t{} } for i, root := range r.roots { p := filepath.Join(root, name) err := syscall.Lstat(p, st) if err == nil { return i } } return -1 } func (n *unionFSRoot) delPath(p string) syscall.Errno { var st syscall.Stat_t r := n.root() idx := r.getBranch(p, &st) if idx < 0 { return 0 } if idx == 0 { err := syscall.Unlink(filepath.Join(r.roots[idx], p)) if err != nil { return fs.ToErrno(err) } idx = r.getBranch(p, &st) } if idx > 0 { return r.writeMarker(p) } return 0 } func (n *unionFSNode) promote() syscall.Errno { p := &n.Inode r := n.root() type tup struct { *unionFSNode name string idx int st syscall.Stat_t } var parents []tup for p != nil && p != &r.Inode { asUN := p.Operations().(*unionFSNode) var st syscall.Stat_t name, idx := asUN.getBranch(&st) if idx == 0 { break } if idx < 0 { log.Println("promote called on nonexistent file") return syscall.EIO } parents = append(parents, tup{asUN, name, idx, st}) _, p = p.Parent() } for i := len(parents) - 1; i >= 0; i-- { t := parents[i] path := t.Path(nil) if t.IsDir() { if err := syscall.Mkdir(filepath.Join(r.roots[0], path), t.st.Mode); err != nil { return err.(syscall.Errno) } } else if t.Mode()&syscall.S_IFREG != 0 { if errno := r.promoteRegularFile(path, t.idx, &t.st); errno != 0 { return errno } } else { log.Panicf("don't know how to handle mode %o", t.Mode()) } var ts [2]syscall.Timespec ts[0] = t.st.Atim ts[1] = t.st.Mtim // ignore error. syscall.UtimesNano(path, ts[:]) } return 0 } func (r *unionFSRoot) promoteRegularFile(p string, idx int, st *syscall.Stat_t) syscall.Errno { dest, err := syscall.Creat(filepath.Join(r.roots[0], p), st.Mode) if err != nil { return err.(syscall.Errno) } src, err := syscall.Open(filepath.Join(r.roots[idx], p), syscall.O_RDONLY, 0) if err != nil { syscall.Close(dest) return err.(syscall.Errno) } var ret syscall.Errno var buf [128 >> 10]byte for { n, err := syscall.Read(src, buf[:]) if n == 0 { break } if err != nil { ret = err.(syscall.Errno) break } if _, err := syscall.Write(dest, buf[:n]); err != nil { ret = err.(syscall.Errno) break } } syscall.Close(src) if err := syscall.Close(dest); err != nil && ret == 0 { ret = err.(syscall.Errno) } return ret } go-fuse-2.0.3/newunionfs/unionfs_test.go000066400000000000000000000141531364171671200203370ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package unionfs import ( "bytes" "io/ioutil" "os" "path/filepath" "reflect" "syscall" "testing" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/internal/testutil" "github.com/hanwen/go-fuse/v2/posixtest" ) type testCase struct { dir string mnt string server *fuse.Server rw string ro string root *unionFSRoot } func (tc *testCase) Clean() { if tc.server != nil { tc.server.Unmount() tc.server = nil } os.RemoveAll(tc.dir) } func newTestCase(t *testing.T, populate bool) *testCase { t.Helper() dir := testutil.TempDir() dirs := []string{"ro", "rw", "mnt"} if populate { dirs = append(dirs, "ro/dir") } for _, d := range dirs { if err := os.Mkdir(filepath.Join(dir, d), 0755); err != nil { t.Fatal("Mkdir", err) } } opts := fs.Options{} opts.Debug = testutil.VerboseTest() tc := &testCase{ dir: dir, mnt: dir + "/mnt", rw: dir + "/rw", ro: dir + "/ro", } tc.root = &unionFSRoot{ roots: []string{tc.rw, tc.ro}, } server, err := fs.Mount(tc.mnt, tc.root, &opts) if err != nil { t.Fatal("Mount", err) } tc.server = server if populate { if err := ioutil.WriteFile(tc.ro+"/dir/ro-file", []byte("bla"), 0644); err != nil { t.Fatal(err) } } return tc } func TestBasic(t *testing.T) { tc := newTestCase(t, true) defer tc.Clean() if fi, err := os.Lstat(tc.mnt + "/dir/ro-file"); err != nil { t.Fatal(err) } else if fi.Size() != 3 { t.Errorf("got size %d, want 3", fi.Size()) } } func TestDelete(t *testing.T) { tc := newTestCase(t, true) defer tc.Clean() if err := os.Remove(tc.mnt + "/dir/ro-file"); err != nil { t.Fatal(err) } if _, err := os.Lstat(tc.ro + "/dir/ro-file"); err != nil { t.Fatal(err) } c, err := ioutil.ReadFile(filepath.Join(tc.rw, delDir, filePathHash("dir/ro-file"))) if err != nil { t.Fatal(err) } if got, want := string(c), "dir/ro-file"; got != want { t.Errorf("got %q want %q", got, want) } } func TestDeleteMarker(t *testing.T) { tc := newTestCase(t, true) defer tc.Clean() path := "dir/ro-file" tc.root.delPath(path) var st syscall.Stat_t if err := syscall.Lstat(filepath.Join(tc.mnt, path), &st); err != syscall.ENOENT { t.Fatalf("Lstat before: %v", err) } if errno := tc.root.rmMarker(path); errno != 0 { t.Fatalf("rmMarker: %v", errno) } if err := syscall.Lstat(filepath.Join(tc.mnt, path), &st); err != nil { t.Fatalf("Lstat after: %v", err) } } func TestCreateDeletions(t *testing.T) { tc := newTestCase(t, true) defer tc.Clean() if _, err := syscall.Creat(filepath.Join(tc.mnt, delDir), 0644); err != syscall.EPERM { t.Fatalf("got err %v, want EPERM", err) } } func TestCreate(t *testing.T) { tc := newTestCase(t, true) defer tc.Clean() path := "dir/ro-file" if err := syscall.Unlink(filepath.Join(tc.mnt, path)); err != nil { t.Fatalf("Unlink: %v", err) } want := []byte{42} if err := ioutil.WriteFile(filepath.Join(tc.mnt, path), want, 0644); err != nil { t.Fatalf("WriteFile: %v", err) } if got, err := ioutil.ReadFile(filepath.Join(tc.mnt, path)); err != nil { t.Fatalf("WriteFile: %v", err) } else if !bytes.Equal(got, want) { t.Errorf("got %q, want %q", got, want) } } func TestPromote(t *testing.T) { tc := newTestCase(t, true) defer tc.Clean() path := "dir/ro-file" mPath := filepath.Join(tc.mnt, path) want := []byte{42} if err := ioutil.WriteFile(mPath, want, 0644); err != nil { t.Fatalf("WriteFile: %v", err) } if got, err := ioutil.ReadFile(mPath); err != nil { t.Fatalf("ReadFile: %v", err) } else if !bytes.Equal(got, want) { t.Errorf("got %q, want %q", got, want) } } func TestDeleteRevert(t *testing.T) { tc := newTestCase(t, true) defer tc.Clean() path := "dir/ro-file" mPath := filepath.Join(tc.mnt, path) if err := ioutil.WriteFile(mPath, []byte{42}, 0644); err != nil { t.Fatalf("WriteFile: %v", err) } var st syscall.Stat_t if err := syscall.Lstat(mPath, &st); err != nil { t.Fatalf("Lstat before: %v", err) } else if st.Size != 1 { t.Fatalf("Stat: want size 1, got %#v", st) } if err := syscall.Unlink(mPath); err != nil { t.Fatalf("Unlink: %v", err) } if err := syscall.Lstat(mPath, &st); err != syscall.ENOENT { t.Fatalf("Lstat after: got %v, want ENOENT", err) } } func TestReaddirRoot(t *testing.T) { tc := newTestCase(t, true) defer tc.Clean() if err := os.Remove(tc.mnt + "/dir/ro-file"); err != nil { t.Fatalf("Remove: %v", err) } f, err := os.Open(tc.mnt) if err != nil { t.Fatalf("Open: %v", err) } defer f.Close() names, err := f.Readdirnames(-1) if err != nil { t.Fatalf("Readdirnames: %v", err) } got := map[string]bool{} want := map[string]bool{"dir": true} for _, nm := range names { got[nm] = true } if !reflect.DeepEqual(want, got) { t.Errorf("got %v want %v", got, want) } } func TestReaddir(t *testing.T) { tc := newTestCase(t, true) defer tc.Clean() if err := ioutil.WriteFile(tc.ro+"/dir/file2", nil, 0644); err != nil { t.Fatalf("WriteFile: %v", err) } if err := os.Mkdir(tc.rw+"/dir", 0755); err != nil { t.Fatalf("Mkdir: %v", err) } if err := ioutil.WriteFile(tc.rw+"/dir/file3", nil, 0644); err != nil { t.Fatalf("WriteFile: %v", err) } if err := os.Remove(tc.mnt + "/dir/ro-file"); err != nil { t.Fatalf("Remove: %v", err) } res, err := ioutil.ReadDir(tc.mnt + "/dir") if err != nil { t.Fatalf("ReadDir: %v", err) } got := map[string]bool{} want := map[string]bool{ "file2": true, "file3": true, } for _, fi := range res { got[fi.Name()] = true } if !reflect.DeepEqual(want, got) { t.Errorf("got %v want %v", got, want) } } func TestPosix(t *testing.T) { cases := []string{ "SymlinkReadlink", "FileBasic", "TruncateFile", "TruncateNoFile", "FdLeak", // "MkdirRmdir", // "NlinkZero", "ParallelFileOpen", // "Link", "ReadDir", } for _, nm := range cases { f := posixtest.All[nm] t.Run(nm, func(t *testing.T) { tc := newTestCase(t, false) defer tc.Clean() f(t, tc.mnt) }) } } func init() { syscall.Umask(0) } go-fuse-2.0.3/posixtest/000077500000000000000000000000001364171671200151335ustar00rootroot00000000000000go-fuse-2.0.3/posixtest/listfds.go000066400000000000000000000037411364171671200171370ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package posixtest import ( "fmt" "log" "os" "runtime" "strings" ) // listFds lists the open file descriptors for process "pid". Pass pid=0 for // ourselves. Pass a prefix to ignore all paths that do not start with "prefix". // // Copied from https://github.com/rfjakob/gocryptfs/blob/master/tests/test_helpers/mount_unmount.go#L191 func listFds(pid int, prefix string) []string { // We need /proc to get the list of fds for other processes. Only exists // on Linux. if runtime.GOOS != "linux" && pid > 0 { return nil } // Both Linux and MacOS have /dev/fd dir := "/dev/fd" if pid > 0 { dir = fmt.Sprintf("/proc/%d/fd", pid) } f, err := os.Open(dir) if err != nil { fmt.Printf("ListFds: %v\n", err) return nil } defer f.Close() // Note: Readdirnames filters "." and ".." names, err := f.Readdirnames(0) if err != nil { log.Panic(err) } var out []string var filtered []string for _, n := range names { fdPath := dir + "/" + n fi, err := os.Lstat(fdPath) if err != nil { // fd was closed in the meantime continue } if fi.Mode()&0400 > 0 { n += "r" } if fi.Mode()&0200 > 0 { n += "w" } target, err := os.Readlink(fdPath) if err != nil { // fd was closed in the meantime continue } if strings.HasPrefix(target, "pipe:") || strings.HasPrefix(target, "anon_inode:[eventpoll]") { // go-fuse creates pipes on demand for splice(), which // creates spurious test failures. Ignore all pipes. // Also get rid of the "eventpoll" fd that is always there and not // interesting. filtered = append(filtered, target) continue } if prefix != "" && !strings.HasPrefix(target, prefix) { filtered = append(filtered, target) continue } out = append(out, n+"="+target) } out = append(out, fmt.Sprintf("(filtered: %s)", strings.Join(filtered, ", "))) return out } go-fuse-2.0.3/posixtest/test.go000066400000000000000000000357271364171671200164570ustar00rootroot00000000000000// Copyright 2019 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package posixtest file systems for generic posix conformance. package posixtest import ( "bytes" "fmt" "io/ioutil" "os" "path/filepath" "runtime" "syscall" "testing" "github.com/hanwen/go-fuse/v2/fuse" ) // All holds a map of all test functions var All = map[string]func(*testing.T, string){ "AppendWrite": AppendWrite, "SymlinkReadlink": SymlinkReadlink, "FileBasic": FileBasic, "TruncateFile": TruncateFile, "TruncateNoFile": TruncateNoFile, "FdLeak": FdLeak, "MkdirRmdir": MkdirRmdir, "NlinkZero": NlinkZero, "FstatDeleted": FstatDeleted, "ParallelFileOpen": ParallelFileOpen, "Link": Link, "LinkUnlinkRename": LinkUnlinkRename, "RenameOverwriteDestNoExist": RenameOverwriteDestNoExist, "RenameOverwriteDestExist": RenameOverwriteDestExist, "RenameOpenDir": RenameOpenDir, "ReadDir": ReadDir, "ReadDirPicksUpCreate": ReadDirPicksUpCreate, "DirectIO": DirectIO, "OpenAt": OpenAt, "Fallocate": Fallocate, } func DirectIO(t *testing.T, mnt string) { fn := mnt + "/file.txt" fd, err := syscall.Open(fn, syscall.O_TRUNC|syscall.O_CREAT|syscall.O_DIRECT|syscall.O_WRONLY, 0644) if err == syscall.EINVAL { t.Skip("FS does not support O_DIRECT") } if err != nil { t.Fatalf("Open: %v", err) } defer func() { if fd != 0 { syscall.Close(fd) } }() data := bytes.Repeat([]byte("bye"), 4096) if n, err := syscall.Write(fd, data); err != nil || n != len(data) { t.Fatalf("Write: %v (%d)", err, n) } err = syscall.Close(fd) fd = 0 if err != nil { t.Fatalf("Close: %v", err) } fd, err = syscall.Open(fn, syscall.O_DIRECT|syscall.O_RDONLY, 0644) if err != nil { t.Fatalf("Open 2: %v", err) } roundtrip := bytes.Repeat([]byte("xxx"), 4096) if n, err := syscall.Read(fd, roundtrip); err != nil || n != len(data) { t.Fatalf("ReadAt: %v (%d)", err, n) } if bytes.Compare(roundtrip, data) != 0 { t.Errorf("roundtrip made changes: got %q.., want %q..", roundtrip[:10], data[:10]) } } // SymlinkReadlink tests basic symlink functionality func SymlinkReadlink(t *testing.T, mnt string) { err := os.Symlink("/foobar", mnt+"/link") if err != nil { t.Fatalf("Symlink: %v", err) } val, err := os.Readlink(mnt + "/link") if err != nil { t.Fatalf("Readlink: %v", err) } if val != "/foobar" { t.Errorf("symlink mismatch: %v", val) } } func FileBasic(t *testing.T, mnt string) { content := []byte("hello world") fn := mnt + "/file" if err := ioutil.WriteFile(fn, content, 0755); err != nil { t.Fatalf("WriteFile: %v", err) } if got, err := ioutil.ReadFile(fn); err != nil { t.Fatalf("ReadFile: %v", err) } else if bytes.Compare(got, content) != 0 { t.Errorf("ReadFile: got %q, want %q", got, content) } f, err := os.Open(fn) if err != nil { t.Fatalf("Open: %v", err) } defer f.Close() fi, err := f.Stat() if err != nil { t.Fatalf("Fstat: %v", err) } else if int(fi.Size()) != len(content) { t.Errorf("got size %d want 5", fi.Size()) } stat := fuse.ToStatT(fi) if got, want := uint32(stat.Mode), uint32(fuse.S_IFREG|0755); got != want { t.Errorf("Fstat: got mode %o, want %o", got, want) } if err := f.Close(); err != nil { t.Errorf("Close: %v", err) } } func TruncateFile(t *testing.T, mnt string) { content := []byte("hello world") fn := mnt + "/file" if err := ioutil.WriteFile(fn, content, 0755); err != nil { t.Fatalf("WriteFile: %v", err) } f, err := os.OpenFile(fn, os.O_RDWR, 0644) if err != nil { t.Fatalf("Open: %v", err) } defer f.Close() const trunc = 5 if err := f.Truncate(5); err != nil { t.Errorf("Truncate: %v", err) } if err := f.Close(); err != nil { t.Errorf("Close: %v", err) } if got, err := ioutil.ReadFile(fn); err != nil { t.Fatalf("ReadFile: %v", err) } else if want := content[:trunc]; bytes.Compare(got, want) != 0 { t.Errorf("got %q, want %q", got, want) } } func TruncateNoFile(t *testing.T, mnt string) { fn := mnt + "/file" if err := ioutil.WriteFile(fn, []byte("hello"), 0644); err != nil { t.Errorf("WriteFile: %v", err) } if err := syscall.Truncate(fn, 1); err != nil { t.Fatalf("Truncate: %v", err) } var st syscall.Stat_t if err := syscall.Lstat(fn, &st); err != nil { t.Fatalf("Lstat: %v", err) } if st.Size != 1 { t.Errorf("got size %d, want 1", st.Size) } } func FdLeak(t *testing.T, mnt string) { fn := mnt + "/file" if err := ioutil.WriteFile(fn, []byte("hello world"), 0755); err != nil { t.Fatalf("WriteFile: %v", err) } for i := 0; i < 100; i++ { if _, err := ioutil.ReadFile(fn); err != nil { t.Fatalf("ReadFile: %v", err) } } if runtime.GOOS == "linux" { infos := listFds(0, "") if len(infos) > 15 { t.Errorf("found %d open file descriptors for 100x ReadFile: %v", len(infos), infos) } } } func MkdirRmdir(t *testing.T, mnt string) { fn := mnt + "/dir" if err := os.Mkdir(fn, 0755); err != nil { t.Fatalf("Mkdir: %v", err) } if fi, err := os.Lstat(fn); err != nil { t.Fatalf("Lstat %v", err) } else if !fi.IsDir() { t.Fatalf("is not a directory") } if err := os.Remove(fn); err != nil { t.Fatalf("Remove: %v", err) } } func NlinkZero(t *testing.T, mnt string) { src := mnt + "/src" dst := mnt + "/dst" if err := ioutil.WriteFile(src, []byte("source"), 0644); err != nil { t.Fatalf("WriteFile: %v", err) } if err := ioutil.WriteFile(dst, []byte("dst"), 0644); err != nil { t.Fatalf("WriteFile: %v", err) } f, err := syscall.Open(dst, 0, 0) if err != nil { t.Fatalf("Open: %v", err) } defer syscall.Close(f) var st syscall.Stat_t if err := syscall.Fstat(f, &st); err != nil { t.Errorf("Fstat before: %v", err) } else if st.Nlink != 1 { t.Errorf("Nlink of file: got %d, want 1", st.Nlink) } if err := os.Rename(src, dst); err != nil { t.Fatalf("Rename: %v", err) } if err := syscall.Fstat(f, &st); err != nil { t.Errorf("Fstat after: %v", err) } else if st.Nlink != 0 { t.Errorf("Nlink of overwritten file: got %d, want 0", st.Nlink) } } // FstatDeleted is similar to NlinkZero, but Fstat()s multiple deleted files // in random order and checks that the results match an earlier Stat(). // // Excercises the fd-finding logic in rawBridge.GetAttr. func FstatDeleted(t *testing.T, mnt string) { const iMax = 9 type file struct { fd int st syscall.Stat_t } files := make(map[int]file) for i := 0; i <= iMax; i++ { // Create files with different sizes path := fmt.Sprintf("%s/%d", mnt, i) content := make([]byte, i) err := ioutil.WriteFile(path, content, 0644) if err != nil { t.Fatalf("WriteFile: %v", err) } var st syscall.Stat_t err = syscall.Stat(path, &st) if err != nil { t.Fatal(err) } // Open fd, err := syscall.Open(path, syscall.O_RDONLY, 0) if err != nil { t.Fatal(err) } files[i] = file{fd, st} defer syscall.Close(fd) // Delete err = syscall.Unlink(path) if err != nil { t.Fatal(err) } } // Fstat in random order for _, v := range files { var st syscall.Stat_t err := syscall.Fstat(v.fd, &st) if err != nil { t.Fatal(err) } // Ignore ctime, changes on unlink v.st.Ctim = syscall.Timespec{} st.Ctim = syscall.Timespec{} // Nlink value should have dropped to zero v.st.Nlink = 0 // Rest should stay the same if v.st != st { t.Errorf("stat mismatch: want=%v\n have=%v", v.st, st) } } } func ParallelFileOpen(t *testing.T, mnt string) { fn := mnt + "/file" if err := ioutil.WriteFile(fn, []byte("content"), 0644); err != nil { t.Fatalf("WriteFile: %v", err) } N := 10 errs := make(chan error, N) one := func(b byte) { f, err := os.OpenFile(fn, os.O_RDWR, 0644) if err != nil { errs <- err return } var buf [10]byte f.Read(buf[:]) buf[0] = b f.WriteAt(buf[0:1], 2) f.Close() errs <- nil } for i := 0; i < N; i++ { go one(byte(i)) } for i := 0; i < N; i++ { if e := <-errs; e != nil { t.Error(e) } } } func Link(t *testing.T, mnt string) { link := mnt + "/link" target := mnt + "/target" if err := ioutil.WriteFile(target, []byte("hello"), 0644); err != nil { t.Fatalf("WriteFile: %v", err) } st := syscall.Stat_t{} if err := syscall.Lstat(target, &st); err != nil { t.Fatalf("Lstat before: %v", err) } beforeIno := st.Ino if err := os.Link(target, link); err != nil { t.Errorf("Link: %v", err) } if err := syscall.Lstat(link, &st); err != nil { t.Fatalf("Lstat after: %v", err) } if st.Ino != beforeIno { t.Errorf("Lstat after: got %d, want %d", st.Ino, beforeIno) } if st.Nlink != 2 { t.Errorf("Expect 2 links, got %d", st.Nlink) } } func RenameOverwriteDestNoExist(t *testing.T, mnt string) { RenameOverwrite(t, mnt, false) } func RenameOverwriteDestExist(t *testing.T, mnt string) { RenameOverwrite(t, mnt, true) } func RenameOverwrite(t *testing.T, mnt string, destExists bool) { if err := os.Mkdir(mnt+"/dir", 0755); err != nil { t.Fatalf("Mkdir: %v", err) } if err := ioutil.WriteFile(mnt+"/file", []byte("hello"), 0644); err != nil { t.Fatalf("WriteFile: %v", err) } if destExists { if err := ioutil.WriteFile(mnt+"/dir/renamed", []byte("xx"), 0644); err != nil { t.Fatalf("WriteFile dest: %v", err) } } st := syscall.Stat_t{} if err := syscall.Lstat(mnt+"/file", &st); err != nil { t.Fatalf("Lstat before: %v", err) } beforeIno := st.Ino if err := os.Rename(mnt+"/file", mnt+"/dir/renamed"); err != nil { t.Errorf("Rename: %v", err) } if fi, err := os.Lstat(mnt + "/file"); err == nil { t.Fatalf("Lstat old: %v", fi) } if err := syscall.Lstat(mnt+"/dir/renamed", &st); err != nil { t.Fatalf("Lstat after: %v", err) } if got := st.Ino; got != beforeIno { t.Errorf("got ino %d, want %d", got, beforeIno) } } func RenameOpenDir(t *testing.T, mnt string) { if err := os.Mkdir(mnt+"/dir1", 0755); err != nil { t.Fatalf("Mkdir: %v", err) } // Different permissions so directories are easier to tell apart if err := os.Mkdir(mnt+"/dir2", 0700); err != nil { t.Fatalf("Mkdir: %v", err) } var st1 syscall.Stat_t if err := syscall.Stat(mnt+"/dir2", &st1); err != nil { t.Fatalf("Stat: %v", err) } fd, err := syscall.Open(mnt+"/dir2", syscall.O_RDONLY, 0) if err != nil { t.Fatalf("Open: %v", err) } defer syscall.Close(fd) if err := syscall.Rename(mnt+"/dir1", mnt+"/dir2"); err != nil { t.Fatalf("Rename: %v", err) } var st2 syscall.Stat_t if err := syscall.Fstat(fd, &st2); err != nil { t.Skipf("Fstat failed: %v. Known limitation - see https://github.com/hanwen/go-fuse/issues/55", err) } if st2.Mode&syscall.S_IFMT != syscall.S_IFDIR { t.Errorf("got mode %o, want %o", st2.Mode, syscall.S_IFDIR) } if st2.Ino != st1.Ino { t.Errorf("got ino %d, want %d", st2.Ino, st1.Ino) } if st2.Mode&0777 != st1.Mode&0777 { t.Skipf("got permissions %#o, want %#o. Known limitation - see https://github.com/hanwen/go-fuse/issues/55", st2.Mode&0777, st1.Mode&0777) } } // ReadDir creates 110 files one by one, checking that we get the expected // entries after each file creation. func ReadDir(t *testing.T, mnt string) { want := map[string]bool{} // 40 bytes of filename, so 110 entries overflows a // 4096 page. for i := 0; i < 110; i++ { nm := fmt.Sprintf("file%036x", i) want[nm] = true if err := ioutil.WriteFile(filepath.Join(mnt, nm), []byte("hello"), 0644); err != nil { t.Fatalf("WriteFile %q: %v", nm, err) } // Verify that we get the expected entries f, err := os.Open(mnt) if err != nil { t.Fatalf("Open: %v", err) } names, err := f.Readdirnames(-1) if err != nil { t.Fatalf("ReadDir: %v", err) } f.Close() got := map[string]bool{} for _, e := range names { got[e] = true } if len(got) != len(want) { t.Errorf("got %d entries, want %d", len(got), len(want)) } for k := range got { if !want[k] { t.Errorf("got unknown name %q", k) } } } } // Readdir should pick file created after open, but before readdir. func ReadDirPicksUpCreate(t *testing.T, mnt string) { f, err := os.Open(mnt) if err != nil { t.Fatalf("Open: %v", err) } if err := ioutil.WriteFile(mnt+"/file", []byte{42}, 0644); err != nil { t.Fatalf("WriteFile: %v", err) } names, err := f.Readdirnames(-1) if err != nil { t.Fatalf("ReadDir: %v", err) } f.Close() if len(names) != 1 || names[0] != "file" { t.Errorf("missing file created after opendir") } } // LinkUnlinkRename implements rename with a link/unlink sequence func LinkUnlinkRename(t *testing.T, mnt string) { content := []byte("hello") tmp := mnt + "/tmpfile" if err := ioutil.WriteFile(tmp, content, 0644); err != nil { t.Fatalf("WriteFile %q: %v", tmp, err) } dest := mnt + "/file" if err := syscall.Link(tmp, dest); err != nil { t.Fatalf("Link %q %q: %v", tmp, dest, err) } if err := syscall.Unlink(tmp); err != nil { t.Fatalf("Unlink %q: %v", tmp, err) } if back, err := ioutil.ReadFile(dest); err != nil { t.Fatalf("Read %q: %v", dest, err) } else if bytes.Compare(back, content) != 0 { t.Fatalf("Read got %q want %q", back, content) } } // test open with O_APPEND func AppendWrite(t *testing.T, mnt string) { fd, err := syscall.Open(mnt+"/file", syscall.O_WRONLY|syscall.O_APPEND|syscall.O_CREAT, 0644) if err != nil { t.Fatalf("Open: %v", err) } defer func() { if fd != 0 { syscall.Close(fd) } }() if _, err := syscall.Write(fd, []byte("hello")); err != nil { t.Fatalf("Write 1: %v", err) } if _, err := syscall.Write(fd, []byte("world")); err != nil { t.Fatalf("Write 2: %v", err) } if err := syscall.Close(fd); err != nil { t.Fatalf("Open: %v", err) } fd = 0 want := []byte("helloworld") got, err := ioutil.ReadFile(mnt + "/file") if err != nil { t.Fatalf("ReadFile: %v", err) } if bytes.Compare(got, want) != 0 { t.Errorf("got %q want %q", got, want) } } // OpenAt tests syscall.Openat(). // // Hint: // $ go test ./fs -run TestPosix/OpenAt -v func OpenAt(t *testing.T, mnt string) { dir1 := mnt + "/dir1" err := os.Mkdir(dir1, 0777) if err != nil { t.Fatal(err) } dirfd, err := syscall.Open(dir1, syscall.O_RDONLY, 0) if err != nil { t.Fatal(err) } defer syscall.Close(dirfd) dir2 := mnt + "/dir2" err = os.Rename(dir1, dir2) if err != nil { t.Fatal(err) } fd, err := syscall.Openat(dirfd, "file1", syscall.O_CREAT, 0700) if err != nil { t.Fatal(err) } defer syscall.Close(fd) _, err = os.Stat(dir2 + "/file1") if err != nil { t.Error(err) } } func Fallocate(t *testing.T, mnt string) { rwFile, err := os.OpenFile(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("Fallocate failed: %v", err) } fi, err := os.Lstat(mnt + "/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()) } } go-fuse-2.0.3/splice/000077500000000000000000000000001364171671200143505ustar00rootroot00000000000000go-fuse-2.0.3/splice/copy.go000066400000000000000000000023371364171671200156560ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package splice import ( "io" "os" ) func SpliceCopy(dst *os.File, src *os.File, p *Pair) (int64, error) { total := int64(0) for { n, err := p.LoadFrom(src.Fd(), p.size) if err != nil { return total, err } if n == 0 { break } m, err := p.WriteTo(dst.Fd(), n) total += int64(m) if err != nil { return total, err } if m < n { return total, err } if int(n) < p.size { break } } return total, nil } // Argument ordering follows io.Copy. func CopyFile(dstName string, srcName string, mode int) error { src, err := os.Open(srcName) if err != nil { return err } defer src.Close() dst, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(mode)) if err != nil { return err } defer dst.Close() return CopyFds(dst, src) } func CopyFds(dst *os.File, src *os.File) (err error) { p, err := splicePool.get() if p != nil { p.Grow(256 * 1024) _, err := SpliceCopy(dst, src, p) splicePool.done(p) return err } else { _, err = io.Copy(dst, src) } if err == io.EOF { err = nil } return err } go-fuse-2.0.3/splice/copy_test.go000066400000000000000000000026071364171671200167150ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package splice import ( "io/ioutil" "os" "testing" ) func check(err error) { if err != nil { panic(err) } } func TestCopyFile(t *testing.T) { src, _ := ioutil.TempFile("", "termite") err := ioutil.WriteFile(src.Name(), []byte("hello"), 0644) if err != nil { t.Error(err) } dst, _ := ioutil.TempFile("", "termite") err = CopyFile(dst.Name(), src.Name(), 0755) if err != nil { t.Error(err) } c, err := ioutil.ReadFile(dst.Name()) if err != nil { t.Error(err) } if string(c) != "hello" { t.Error("mismatch", string(c)) } } func TestSpliceCopy(t *testing.T) { src, err := ioutil.TempFile("", "termite") check(err) bs := make([]byte, 2*1024*1024) for i := range bs { bs[i] = byte(i % 256) } _, err = src.Write(bs) check(err) err = src.Close() check(err) src, err = os.Open(src.Name()) check(err) dst, err := ioutil.TempFile("", "termite") check(err) if maxPipeSize%4096 != 0 || maxPipeSize < 4096 { t.Error("pipe size should be page size multiple", maxPipeSize) } pool := newSplicePairPool() p, err := pool.get() if p != nil { p.MaxGrow() t.Logf("Splice size %d", p.size) SpliceCopy(dst, src, p) dst.Close() src.Close() p.Close() } else { t.Error("Could not open splice: ", err) } } go-fuse-2.0.3/splice/pair.go000066400000000000000000000022721364171671200156350ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package splice import ( "fmt" "syscall" ) type Pair struct { r, w int size int } func (p *Pair) MaxGrow() { for p.Grow(2*p.size) == nil { } } func (p *Pair) Grow(n int) error { if n <= p.size { return nil } if !resizable { return fmt.Errorf("splice: want %d bytes, but not resizable", n) } if n > maxPipeSize { return fmt.Errorf("splice: want %d bytes, max pipe size %d", n, maxPipeSize) } newsize, errNo := fcntl(uintptr(p.r), F_SETPIPE_SZ, n) if errNo != 0 { return fmt.Errorf("splice: fcntl returned %v", errNo) } p.size = newsize return nil } func (p *Pair) Cap() int { return p.size } func (p *Pair) Close() error { err1 := syscall.Close(p.r) err2 := syscall.Close(p.w) if err1 != nil { return err1 } return err2 } func (p *Pair) Read(d []byte) (n int, err error) { return syscall.Read(p.r, d) } func (p *Pair) Write(d []byte) (n int, err error) { return syscall.Write(p.w, d) } func (p *Pair) ReadFd() uintptr { return uintptr(p.r) } func (p *Pair) WriteFd() uintptr { return uintptr(p.w) } go-fuse-2.0.3/splice/pair_darwin.go000066400000000000000000000007771364171671200172110ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package splice import () func (p *Pair) LoadFromAt(fd uintptr, sz int, off int64) (int, error) { panic("not implemented") return 0, nil } func (p *Pair) LoadFrom(fd uintptr, sz int) (int, error) { panic("not implemented") return 0, nil } func (p *Pair) WriteTo(fd uintptr, n int) (int, error) { panic("not implemented") return 0, nil } go-fuse-2.0.3/splice/pair_linux.go000066400000000000000000000025261364171671200170560ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package splice import ( "fmt" "log" "os" "syscall" ) func (p *Pair) LoadFromAt(fd uintptr, sz int, off int64) (int, error) { n, err := syscall.Splice(int(fd), &off, p.w, nil, sz, 0) return int(n), err } func (p *Pair) LoadFrom(fd uintptr, sz int) (int, error) { if sz > p.size { return 0, fmt.Errorf("LoadFrom: not enough space %d, %d", sz, p.size) } n, err := syscall.Splice(int(fd), nil, p.w, nil, sz, 0) if err != nil { err = os.NewSyscallError("Splice load from", err) } return int(n), err } func (p *Pair) WriteTo(fd uintptr, n int) (int, error) { m, err := syscall.Splice(p.r, nil, int(fd), nil, int(n), 0) if err != nil { err = os.NewSyscallError("Splice write", err) } return int(m), err } const _SPLICE_F_NONBLOCK = 0x2 func (p *Pair) discard() { _, err := syscall.Splice(p.r, nil, int(devNullFD), nil, int(p.size), _SPLICE_F_NONBLOCK) if err == syscall.EAGAIN { // all good. } else if err != nil { errR := syscall.Close(p.r) errW := syscall.Close(p.w) // This can happen if something closed our fd // inadvertently (eg. double close) log.Panicf("splicing into /dev/null: %v (close R %d '%v', close W %d '%v')", err, p.r, errR, p.w, errW) } } go-fuse-2.0.3/splice/pool.go000066400000000000000000000030101364171671200156420ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package splice import ( "sync" ) var splicePool *pairPool type pairPool struct { sync.Mutex unused []*Pair usedCount int } func ClearSplicePool() { splicePool.clear() } func Get() (*Pair, error) { return splicePool.get() } func Total() int { return splicePool.total() } func Used() int { return splicePool.used() } // Done returns the pipe pair to pool. func Done(p *Pair) { splicePool.done(p) } // Closes and discards pipe pair. func Drop(p *Pair) { splicePool.drop(p) } func newSplicePairPool() *pairPool { return &pairPool{} } func (pp *pairPool) clear() { pp.Lock() for _, p := range pp.unused { p.Close() } pp.unused = pp.unused[:0] pp.Unlock() } func (pp *pairPool) used() (n int) { pp.Lock() n = pp.usedCount pp.Unlock() return n } func (pp *pairPool) total() int { pp.Lock() n := pp.usedCount + len(pp.unused) pp.Unlock() return n } func (pp *pairPool) drop(p *Pair) { p.Close() pp.Lock() pp.usedCount-- pp.Unlock() } func (pp *pairPool) get() (p *Pair, err error) { pp.Lock() defer pp.Unlock() pp.usedCount++ l := len(pp.unused) if l > 0 { p := pp.unused[l-1] pp.unused = pp.unused[:l-1] return p, nil } return newSplicePair() } func (pp *pairPool) done(p *Pair) { p.discard() pp.Lock() pp.usedCount-- pp.unused = append(pp.unused, p) pp.Unlock() } func init() { splicePool = newSplicePairPool() } go-fuse-2.0.3/splice/splice.go000066400000000000000000000037151364171671200161640ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package splice // Routines for efficient file to file copying. import ( "fmt" "io/ioutil" "log" "os" "syscall" ) var maxPipeSize int var resizable bool func Resizable() bool { return resizable } func MaxPipeSize() int { return maxPipeSize } // From manpage on ubuntu Lucid: // // Since Linux 2.6.11, the pipe capacity is 65536 bytes. const DefaultPipeSize = 16 * 4096 // We empty pipes by splicing to /dev/null. var devNullFD uintptr func init() { content, err := ioutil.ReadFile("/proc/sys/fs/pipe-max-size") if err != nil { maxPipeSize = DefaultPipeSize } else { fmt.Sscan(string(content), &maxPipeSize) } r, w, err := os.Pipe() if err != nil { log.Panicf("cannot create pipe: %v", err) } sz, errNo := fcntl(r.Fd(), F_GETPIPE_SZ, 0) resizable = (errNo == 0) _, errNo = fcntl(r.Fd(), F_SETPIPE_SZ, 2*sz) resizable = resizable && (errNo == 0) r.Close() w.Close() fd, err := syscall.Open("/dev/null", os.O_WRONLY, 0) if err != nil { log.Panicf("splice: %v", err) } devNullFD = uintptr(fd) } // copy & paste from syscall. func fcntl(fd uintptr, cmd int, arg int) (val int, errno syscall.Errno) { r0, _, e1 := syscall.Syscall(syscall.SYS_FCNTL, fd, uintptr(cmd), uintptr(arg)) val = int(r0) errno = syscall.Errno(e1) return } const F_SETPIPE_SZ = 1031 const F_GETPIPE_SZ = 1032 func osPipe() (int, int, error) { var fds [2]int err := syscall.Pipe2(fds[:], syscall.O_NONBLOCK) return fds[0], fds[1], err } func newSplicePair() (p *Pair, err error) { p = &Pair{} p.r, p.w, err = osPipe() if err != nil { return nil, err } var errNo syscall.Errno p.size, errNo = fcntl(uintptr(p.r), F_GETPIPE_SZ, 0) if err == syscall.EINVAL { p.size = DefaultPipeSize return p, nil } if errNo != 0 { p.Close() return nil, fmt.Errorf("fcntl getsize: %v", errNo) } return p, nil } go-fuse-2.0.3/splice/splice_test.go000066400000000000000000000016051364171671200172170ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package splice import ( "io/ioutil" "testing" ) func TestPairSize(t *testing.T) { p, _ := Get() defer Done(p) p.MaxGrow() b := make([]byte, p.Cap()+100) for i := range b { b[i] = byte(i) } f, _ := ioutil.TempFile("", "splice") err := ioutil.WriteFile(f.Name(), b, 0644) if err != nil { t.Fatalf("WriteFile: %v", err) } _, err = p.LoadFrom(f.Fd(), len(b)) if err == nil { t.Fatalf("should give error on exceeding capacity") } } func TestDiscard(t *testing.T) { p, _ := Get() defer Done(p) if _, err := p.Write([]byte("hello")); err != nil { t.Fatalf("Write: %v", err) } p.discard() var b [1]byte n, err := p.Read(b[:]) if n != -1 { t.Fatalf("Read: got (%d, %v) want (-1, EAGAIN)", n, err) } } go-fuse-2.0.3/unionfs/000077500000000000000000000000001364171671200145525ustar00rootroot00000000000000go-fuse-2.0.3/unionfs/autounion.go000066400000000000000000000246511364171671200171320ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package unionfs import ( "fmt" "io/ioutil" "log" "os" "path/filepath" "strings" "sync" "syscall" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/pathfs" ) type knownFs struct { unionFS pathfs.FileSystem nodeFS *pathfs.PathNodeFs } // Creates unions for all files under a given directory, // walking the tree and looking for directories D which have a // D/READONLY symlink. // // A union for A/B/C will placed under directory A-B-C. type autoUnionFs struct { pathfs.FileSystem debug bool lock sync.RWMutex zombies map[string]bool knownFileSystems map[string]knownFs nameRootMap map[string]string root string nodeFs *pathfs.PathNodeFs options *AutoUnionFsOptions } type AutoUnionFsOptions struct { UnionFsOptions nodefs.Options pathfs.PathNodeFsOptions // If set, run updateKnownFses() after mounting. UpdateOnMount bool // If set hides the _READONLY file. HideReadonly bool // Expose this version in /status/gounionfs_version Version string } const ( _READONLY = "READONLY" _STATUS = "status" _CONFIG = "config" _DEBUG = "debug" _ROOT = "root" _VERSION = "gounionfs_version" _SCAN_CONFIG = ".scan_config" ) func NewAutoUnionFs(directory string, options AutoUnionFsOptions) pathfs.FileSystem { if options.HideReadonly { options.HiddenFiles = append(options.HiddenFiles, _READONLY) } a := &autoUnionFs{ knownFileSystems: make(map[string]knownFs), nameRootMap: make(map[string]string), zombies: make(map[string]bool), options: &options, FileSystem: pathfs.NewDefaultFileSystem(), } directory, err := filepath.Abs(directory) if err != nil { panic("filepath.Abs returned err") } a.root = directory return a } func (fs *autoUnionFs) String() string { return fmt.Sprintf("autoUnionFs(%s)", fs.root) } func (fs *autoUnionFs) OnMount(nodeFs *pathfs.PathNodeFs) { fs.nodeFs = nodeFs if fs.options.UpdateOnMount { time.AfterFunc(100*time.Millisecond, func() { fs.updateKnownFses() }) } } func (fs *autoUnionFs) addAutomaticFs(roots []string) { relative := strings.TrimLeft(strings.Replace(roots[0], fs.root, "", -1), "/") name := strings.Replace(relative, "/", "-", -1) if fs.getUnionFs(name) == nil { fs.addFs(name, roots) } } func (fs *autoUnionFs) createFs(name string, roots []string) fuse.Status { fs.lock.Lock() defer fs.lock.Unlock() if fs.zombies[name] { log.Printf("filesystem named %q is being removed", name) return fuse.EBUSY } for workspace, root := range fs.nameRootMap { if root == roots[0] && workspace != name { log.Printf("Already have a union FS for directory %s in workspace %s", roots[0], workspace) return fuse.EBUSY } } known := fs.knownFileSystems[name] if known.unionFS != nil { log.Println("Already have a workspace:", name) return fuse.EBUSY } ufs, err := NewUnionFsFromRoots(roots, &fs.options.UnionFsOptions, true) if err != nil { log.Println("Could not create UnionFs:", err) return fuse.EPERM } log.Printf("Adding workspace %v for roots %v", name, ufs.String()) nfs := pathfs.NewPathNodeFs(ufs, &fs.options.PathNodeFsOptions) code := fs.nodeFs.Mount(name, nfs.Root(), &fs.options.Options) if code.Ok() { fs.knownFileSystems[name] = knownFs{ ufs, nfs, } fs.nameRootMap[name] = roots[0] } return code } func (fs *autoUnionFs) rmFs(name string) (code fuse.Status) { fs.lock.Lock() defer fs.lock.Unlock() if fs.zombies[name] { return fuse.ENOENT } known := fs.knownFileSystems[name] if known.unionFS == nil { return fuse.ENOENT } root := fs.nameRootMap[name] delete(fs.knownFileSystems, name) delete(fs.nameRootMap, name) fs.zombies[name] = true fs.lock.Unlock() code = fs.nodeFs.Unmount(name) fs.lock.Lock() delete(fs.zombies, name) if !code.Ok() { // Reinstate. log.Printf("Unmount failed for %s. Code %v", name, code) fs.knownFileSystems[name] = known fs.nameRootMap[name] = root } return code } func (fs *autoUnionFs) addFs(name string, roots []string) (code fuse.Status) { if name == _CONFIG || name == _STATUS || name == _SCAN_CONFIG { return fuse.EINVAL } return fs.createFs(name, roots) } func (fs *autoUnionFs) getRoots(path string) []string { ro := filepath.Join(path, _READONLY) fi, err := os.Lstat(ro) fiDir, errDir := os.Stat(ro) if err != nil || errDir != nil { return nil } if fi.Mode()&os.ModeSymlink != 0 && fiDir.IsDir() { // TODO - should recurse and chain all READONLYs // together. return []string{path, ro} } return nil } func (fs *autoUnionFs) visit(path string, fi os.FileInfo, err error) error { if fi != nil && fi.IsDir() { roots := fs.getRoots(path) if roots != nil { fs.addAutomaticFs(roots) } } return nil } func (fs *autoUnionFs) updateKnownFses() { // We unroll the first level of entries in the root manually in order // to allow symbolic links on that level. directoryEntries, err := ioutil.ReadDir(fs.root) if err == nil { for _, dir := range directoryEntries { if dir.IsDir() || dir.Mode()&os.ModeSymlink != 0 { path := filepath.Join(fs.root, dir.Name()) dir, _ = os.Stat(path) fs.visit(path, dir, nil) filepath.Walk(path, func(path string, fi os.FileInfo, err error) error { return fs.visit(path, fi, err) }) } } } } func (fs *autoUnionFs) Readlink(path string, context *fuse.Context) (out string, code fuse.Status) { comps := strings.Split(path, string(filepath.Separator)) if comps[0] == _STATUS && comps[1] == _ROOT { return fs.root, fuse.OK } if comps[0] != _CONFIG { return "", fuse.ENOENT } name := comps[1] fs.lock.RLock() defer fs.lock.RUnlock() root, ok := fs.nameRootMap[name] if ok { return root, fuse.OK } return "", fuse.ENOENT } func (fs *autoUnionFs) getUnionFs(name string) pathfs.FileSystem { fs.lock.RLock() defer fs.lock.RUnlock() return fs.knownFileSystems[name].unionFS } func (fs *autoUnionFs) Symlink(pointedTo string, linkName string, context *fuse.Context) (code fuse.Status) { comps := strings.Split(linkName, "/") if len(comps) != 2 { return fuse.EPERM } if comps[0] == _CONFIG { roots := fs.getRoots(pointedTo) if roots == nil { return fuse.Status(syscall.ENOTDIR) } name := comps[1] return fs.addFs(name, roots) } return fuse.EPERM } func (fs *autoUnionFs) Unlink(path string, context *fuse.Context) (code fuse.Status) { comps := strings.Split(path, "/") if len(comps) != 2 { return fuse.EPERM } if comps[0] == _CONFIG && comps[1] != _SCAN_CONFIG { code = fs.rmFs(comps[1]) } else { code = fuse.ENOENT } return code } // Must define this, because ENOSYS will suspend all GetXAttr calls. func (fs *autoUnionFs) GetXAttr(name string, attr string, context *fuse.Context) ([]byte, fuse.Status) { return nil, fuse.ENOATTR } func (fs *autoUnionFs) GetAttr(path string, context *fuse.Context) (*fuse.Attr, fuse.Status) { a := &fuse.Attr{ Owner: *fuse.CurrentOwner(), } if path == "" || path == _CONFIG || path == _STATUS { a.Mode = fuse.S_IFDIR | 0755 return a, fuse.OK } if path == filepath.Join(_STATUS, _VERSION) { a.Mode = fuse.S_IFREG | 0644 a.Size = uint64(len(fs.options.Version)) return a, fuse.OK } if path == filepath.Join(_STATUS, _DEBUG) { a.Mode = fuse.S_IFREG | 0644 a.Size = uint64(len(fs.DebugData())) return a, fuse.OK } if path == filepath.Join(_STATUS, _ROOT) { a.Mode = syscall.S_IFLNK | 0644 return a, fuse.OK } if path == filepath.Join(_CONFIG, _SCAN_CONFIG) { a.Mode = fuse.S_IFREG | 0644 return a, fuse.OK } comps := strings.Split(path, string(filepath.Separator)) if len(comps) > 1 && comps[0] == _CONFIG { fs := fs.getUnionFs(comps[1]) if fs == nil { return nil, fuse.ENOENT } a.Mode = syscall.S_IFLNK | 0644 return a, fuse.OK } return nil, fuse.ENOENT } func (fs *autoUnionFs) StatusDir() (stream []fuse.DirEntry, status fuse.Status) { stream = make([]fuse.DirEntry, 0, 10) stream = []fuse.DirEntry{ {Name: _VERSION, Mode: fuse.S_IFREG | 0644}, {Name: _DEBUG, Mode: fuse.S_IFREG | 0644}, {Name: _ROOT, Mode: syscall.S_IFLNK | 0644}, } return stream, fuse.OK } func (fs *autoUnionFs) DebugData() string { conn := fs.nodeFs.Connector() if conn.Server() == nil { return "autoUnionFs.mountState not set" } setting := conn.Server().KernelSettings() msg := fmt.Sprintf( "Version: %v\n"+ "Bufferpool: %v\n"+ "Kernel: %v\n", fs.options.Version, conn.Server().DebugData(), &setting) if conn != nil { msg += fmt.Sprintf("Live inodes: %d\n", conn.InodeHandleCount()) } return msg } func (fs *autoUnionFs) Open(path string, flags uint32, context *fuse.Context) (nodefs.File, fuse.Status) { if path == filepath.Join(_STATUS, _DEBUG) { if flags&fuse.O_ANYWRITE != 0 { return nil, fuse.EPERM } return nodefs.NewDataFile([]byte(fs.DebugData())), fuse.OK } if path == filepath.Join(_STATUS, _VERSION) { if flags&fuse.O_ANYWRITE != 0 { return nil, fuse.EPERM } return nodefs.NewDataFile([]byte(fs.options.Version)), fuse.OK } if path == filepath.Join(_CONFIG, _SCAN_CONFIG) { if flags&fuse.O_ANYWRITE != 0 { fs.updateKnownFses() } return nodefs.NewDevNullFile(), fuse.OK } return nil, fuse.ENOENT } func (fs *autoUnionFs) Truncate(name string, offset uint64, context *fuse.Context) (code fuse.Status) { if name != filepath.Join(_CONFIG, _SCAN_CONFIG) { log.Println("Huh? Truncating unsupported write file", name) return fuse.EPERM } return fuse.OK } func (fs *autoUnionFs) OpenDir(name string, context *fuse.Context) (stream []fuse.DirEntry, status fuse.Status) { switch name { case _STATUS: return fs.StatusDir() case _CONFIG: case "/": name = "" case "": default: log.Printf("Argh! Don't know how to list dir %v", name) return nil, fuse.ENOSYS } fs.lock.RLock() defer fs.lock.RUnlock() stream = make([]fuse.DirEntry, 0, len(fs.knownFileSystems)+5) if name == _CONFIG { for k := range fs.knownFileSystems { stream = append(stream, fuse.DirEntry{ Name: k, Mode: syscall.S_IFLNK | 0644, }) } } if name == "" { stream = append(stream, fuse.DirEntry{ Name: _CONFIG, Mode: uint32(fuse.S_IFDIR | 0755), }, fuse.DirEntry{ Name: _STATUS, Mode: uint32(fuse.S_IFDIR | 0755), }) } return stream, status } func (fs *autoUnionFs) StatFs(name string) *fuse.StatfsOut { return &fuse.StatfsOut{} } go-fuse-2.0.3/unionfs/autounion_test.go000066400000000000000000000141301364171671200201600ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package unionfs import ( "io/ioutil" "os" "testing" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/pathfs" "github.com/hanwen/go-fuse/v2/internal/testutil" ) const entryTTL = 100 * time.Millisecond var testAOpts = AutoUnionFsOptions{ UnionFsOptions: testOpts, Options: nodefs.Options{ EntryTimeout: entryTTL, AttrTimeout: entryTTL, NegativeTimeout: 0, Debug: testutil.VerboseTest(), LookupKnownChildren: true, }, HideReadonly: true, Version: "version", } func init() { testAOpts.Options.Debug = testutil.VerboseTest() } func WriteFile(t *testing.T, name string, contents string) { err := ioutil.WriteFile(name, []byte(contents), 0644) if err != nil { t.Fatalf("WriteFile failed: %v", err) } } func setup(t *testing.T) (workdir string, server *fuse.Server, cleanup func()) { wd := testutil.TempDir() err := os.Mkdir(wd+"/mnt", 0700) if err != nil { t.Fatalf("Mkdir failed: %v", err) } err = os.Mkdir(wd+"/store", 0700) if err != nil { t.Fatalf("Mkdir failed: %v", err) } os.Mkdir(wd+"/ro", 0700) if err != nil { t.Fatalf("Mkdir failed: %v", err) } WriteFile(t, wd+"/ro/file1", "file1") WriteFile(t, wd+"/ro/file2", "file2") fs := NewAutoUnionFs(wd+"/store", testAOpts) nfs := pathfs.NewPathNodeFs(fs, nil) state, _, err := nodefs.MountRoot(wd+"/mnt", nfs.Root(), &testAOpts.Options) if err != nil { t.Fatalf("MountNodeFileSystem failed: %v", err) } go state.Serve() state.WaitMount() return wd, state, func() { state.Unmount() os.RemoveAll(wd) } } func TestDebug(t *testing.T) { wd, _, clean := setup(t) defer clean() c, err := ioutil.ReadFile(wd + "/mnt/status/debug") if err != nil { t.Fatalf("ReadFile failed: %v", err) } if len(c) == 0 { t.Fatal("No debug found.") } } func TestVersion(t *testing.T) { wd, _, clean := setup(t) defer clean() c, err := ioutil.ReadFile(wd + "/mnt/status/gounionfs_version") if err != nil { t.Fatalf("ReadFile failed: %v", err) } if len(c) == 0 { t.Fatal("No version found.") } } func TestAutoFsSymlink(t *testing.T) { wd, server, clean := setup(t) defer clean() err := os.Mkdir(wd+"/store/backing1", 0755) if err != nil { t.Fatalf("Mkdir failed: %v", err) } err = os.Symlink(wd+"/ro", wd+"/store/backing1/READONLY") if err != nil { t.Fatalf("Symlink failed: %v", err) } err = os.Symlink(wd+"/store/backing1", wd+"/mnt/config/manual1") if err != nil { t.Fatalf("Symlink failed: %v", err) } fi, err := os.Lstat(wd + "/mnt/manual1/file1") if err != nil { t.Fatalf("Lstat failed: %v", err) } entries, err := ioutil.ReadDir(wd + "/mnt") if err != nil { t.Fatalf("ReadDir failed: %v", err) } if len(entries) != 3 { t.Error("readdir mismatch", entries) } err = os.Remove(wd + "/mnt/config/manual1") if err != nil { t.Fatalf("Remove failed: %v", err) } scan := wd + "/mnt/config/" + _SCAN_CONFIG err = ioutil.WriteFile(scan, []byte("something"), 0644) if err != nil { t.Error("error writing:", err) } // If FUSE supports invalid inode notifications we expect this node to be gone. Otherwise we'll just make sure that it's not reachable. if server.KernelSettings().SupportsNotify(fuse.NOTIFY_INVAL_INODE) { fi, _ = os.Lstat(wd + "/mnt/manual1") if fi != nil { t.Error("Should not have file:", fi) } } else { entries, err = ioutil.ReadDir(wd + "/mnt") if err != nil { t.Fatalf("ReadDir failed: %v", err) } for _, e := range entries { if e.Name() == "manual1" { t.Error("Should not have entry: ", e) } } } _, err = os.Lstat(wd + "/mnt/backing1/file1") if err != nil { t.Fatalf("Lstat failed: %v", err) } } func TestDetectSymlinkedDirectories(t *testing.T) { wd, _, clean := setup(t) defer clean() err := os.Mkdir(wd+"/backing1", 0755) if err != nil { t.Fatalf("Mkdir failed: %v", err) } err = os.Symlink(wd+"/ro", wd+"/backing1/READONLY") if err != nil { t.Fatalf("Symlink failed: %v", err) } err = os.Symlink(wd+"/backing1", wd+"/store/backing1") if err != nil { t.Fatalf("Symlink failed: %v", err) } scan := wd + "/mnt/config/" + _SCAN_CONFIG err = ioutil.WriteFile(scan, []byte("something"), 0644) if err != nil { t.Error("error writing:", err) } _, err = os.Lstat(wd + "/mnt/backing1") if err != nil { t.Fatalf("Lstat failed: %v", err) } } func TestExplicitScan(t *testing.T) { wd, _, clean := setup(t) defer clean() err := os.Mkdir(wd+"/store/backing1", 0755) if err != nil { t.Fatalf("Mkdir failed: %v", err) } os.Symlink(wd+"/ro", wd+"/store/backing1/READONLY") if err != nil { t.Fatalf("Symlink failed: %v", err) } fi, _ := os.Lstat(wd + "/mnt/backing1") if fi != nil { t.Error("Should not have file:", fi) } scan := wd + "/mnt/config/" + _SCAN_CONFIG _, err = os.Lstat(scan) if err != nil { t.Error(".scan_config missing:", err) } err = ioutil.WriteFile(scan, []byte("something"), 0644) if err != nil { t.Error("error writing:", err) } _, err = os.Lstat(wd + "/mnt/backing1") if err != nil { t.Error("Should have workspace backing1:", err) } } func TestCreationChecks(t *testing.T) { wd, _, clean := setup(t) defer clean() err := os.Mkdir(wd+"/store/foo", 0755) if err != nil { t.Fatalf("Mkdir failed: %v", err) } os.Symlink(wd+"/ro", wd+"/store/foo/READONLY") if err != nil { t.Fatalf("Symlink failed: %v", err) } err = os.Mkdir(wd+"/store/ws2", 0755) if err != nil { t.Fatalf("Mkdir failed: %v", err) } os.Symlink(wd+"/ro", wd+"/store/ws2/READONLY") if err != nil { t.Fatalf("Symlink failed: %v", err) } err = os.Symlink(wd+"/store/foo", wd+"/mnt/config/bar") if err != nil { t.Fatalf("Symlink failed: %v", err) } err = os.Symlink(wd+"/store/foo", wd+"/mnt/config/foo") code := fuse.ToStatus(err) if code != fuse.EBUSY { t.Error("Should return EBUSY", err) } err = os.Symlink(wd+"/store/ws2", wd+"/mnt/config/config") code = fuse.ToStatus(err) if code != fuse.EINVAL { t.Error("Should return EINVAL", err) } } go-fuse-2.0.3/unionfs/cachingfs.go000066400000000000000000000067301364171671200170340ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package unionfs import ( "fmt" "log" "strings" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/pathfs" ) const _XATTRSEP = "@XATTR@" type attrResponse struct { *fuse.Attr fuse.Status } type xattrResponse struct { data []byte fuse.Status } type dirResponse struct { entries []fuse.DirEntry fuse.Status } type linkResponse struct { linkContent string fuse.Status } // Caches filesystem metadata. type cachingFileSystem struct { pathfs.FileSystem attributes *TimedCache dirs *TimedCache links *TimedCache xattr *TimedCache } func readDir(fs pathfs.FileSystem, name string) *dirResponse { origStream, code := fs.OpenDir(name, nil) r := &dirResponse{nil, code} if !code.Ok() { return r } r.entries = origStream return r } func getAttr(fs pathfs.FileSystem, name string) *attrResponse { a, code := fs.GetAttr(name, nil) return &attrResponse{ Attr: a, Status: code, } } func getXAttr(fs pathfs.FileSystem, nameAttr string) *xattrResponse { ns := strings.SplitN(nameAttr, _XATTRSEP, 2) a, code := fs.GetXAttr(ns[0], ns[1], nil) return &xattrResponse{ data: a, Status: code, } } func readLink(fs pathfs.FileSystem, name string) *linkResponse { a, code := fs.Readlink(name, nil) return &linkResponse{ linkContent: a, Status: code, } } func NewCachingFileSystem(fs pathfs.FileSystem, ttl time.Duration) pathfs.FileSystem { c := new(cachingFileSystem) c.FileSystem = fs c.attributes = NewTimedCache(func(n string) (interface{}, bool) { a := getAttr(fs, n) return a, a.Ok() }, ttl) c.dirs = NewTimedCache(func(n string) (interface{}, bool) { d := readDir(fs, n) return d, d.Ok() }, ttl) c.links = NewTimedCache(func(n string) (interface{}, bool) { l := readLink(fs, n) return l, l.Ok() }, ttl) c.xattr = NewTimedCache(func(n string) (interface{}, bool) { l := getXAttr(fs, n) return l, l.Ok() }, ttl) return c } func (fs *cachingFileSystem) DropCache() { for _, c := range []*TimedCache{fs.attributes, fs.dirs, fs.links, fs.xattr} { c.DropAll(nil) } } func (fs *cachingFileSystem) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) { if name == _DROP_CACHE { return &fuse.Attr{ Mode: fuse.S_IFREG | 0777, }, fuse.OK } r := fs.attributes.Get(name).(*attrResponse) return r.Attr, r.Status } func (fs *cachingFileSystem) GetXAttr(name string, attr string, context *fuse.Context) ([]byte, fuse.Status) { key := name + _XATTRSEP + attr r := fs.xattr.Get(key).(*xattrResponse) return r.data, r.Status } func (fs *cachingFileSystem) Readlink(name string, context *fuse.Context) (string, fuse.Status) { r := fs.links.Get(name).(*linkResponse) return r.linkContent, r.Status } func (fs *cachingFileSystem) OpenDir(name string, context *fuse.Context) (stream []fuse.DirEntry, status fuse.Status) { r := fs.dirs.Get(name).(*dirResponse) return r.entries, r.Status } func (fs *cachingFileSystem) String() string { return fmt.Sprintf("cachingFileSystem(%v)", fs.FileSystem) } func (fs *cachingFileSystem) Open(name string, flags uint32, context *fuse.Context) (f nodefs.File, status fuse.Status) { if flags&fuse.O_ANYWRITE != 0 && name == _DROP_CACHE { log.Println("Dropping cache for", fs) fs.DropCache() } return fs.FileSystem.Open(name, flags, context) } go-fuse-2.0.3/unionfs/cachingfs_test.go000066400000000000000000000026711364171671200200730ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package unionfs import ( "os" "syscall" "testing" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/pathfs" "github.com/hanwen/go-fuse/v2/internal/testutil" ) func modeMapEq(m1, m2 map[string]uint32) bool { if len(m1) != len(m2) { return false } for k, v := range m1 { val, ok := m2[k] if !ok || val != v { return false } } return true } func TestCachingFs(t *testing.T) { wd := testutil.TempDir() defer os.RemoveAll(wd) fs := pathfs.NewLoopbackFileSystem(wd) cfs := NewCachingFileSystem(fs, 0) os.Mkdir(wd+"/orig", 0755) fi, code := cfs.GetAttr("orig", nil) if !code.Ok() { t.Fatal("GetAttr failure", code) } if !fi.IsDir() { t.Error("unexpected attr", fi) } os.Symlink("orig", wd+"/symlink") val, code := cfs.Readlink("symlink", nil) if val != "orig" { t.Error("unexpected readlink", val) } if !code.Ok() { t.Error("code !ok ", code) } stream, code := cfs.OpenDir("", nil) if !code.Ok() { t.Fatal("Readdir fail", code) } results := make(map[string]uint32) for _, v := range stream { results[v.Name] = v.Mode &^ 07777 } expected := map[string]uint32{ "symlink": syscall.S_IFLNK, "orig": fuse.S_IFDIR, } if !modeMapEq(results, expected) { t.Error("Unexpected readdir result", results, expected) } } go-fuse-2.0.3/unionfs/create.go000066400000000000000000000013501364171671200163430ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package unionfs import ( "os" "github.com/hanwen/go-fuse/v2/fuse/pathfs" ) func NewUnionFsFromRoots(roots []string, opts *UnionFsOptions, roCaching bool) (pathfs.FileSystem, error) { fses := make([]pathfs.FileSystem, 0) for i, r := range roots { var fs pathfs.FileSystem fi, err := os.Stat(r) if err != nil { return nil, err } if fi.IsDir() { fs = pathfs.NewLoopbackFileSystem(r) } if fs == nil { return nil, err } if i > 0 && roCaching { fs = NewCachingFileSystem(fs, 0) } fses = append(fses, fs) } return NewUnionFs(fses, *opts) } go-fuse-2.0.3/unionfs/dircache.go000066400000000000000000000050061364171671200166440ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package unionfs import ( "sync" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/pathfs" ) // newDirnameMap reads the contents of the given directory. On error, // returns a nil map. This forces reloads in the dirCache until we // succeed. func newDirnameMap(fs pathfs.FileSystem, dir string) map[string]struct{} { stream, code := fs.OpenDir(dir, nil) if code == fuse.ENOENT { // The directory not existing is not an error. return map[string]struct{}{} } if !code.Ok() { return nil } result := make(map[string]struct{}) for _, e := range stream { if e.Mode&fuse.S_IFREG != 0 { result[e.Name] = struct{}{} } } return result } // dirCache caches names in a directory for some time. // // If called when the cache is expired, the filenames are read afresh in // the background. type dirCache struct { dir string ttl time.Duration fs pathfs.FileSystem // Protects data below. lock sync.RWMutex // If nil, you may call refresh() to schedule a new one. names map[string]struct{} updateRunning bool } func (c *dirCache) setMap(newMap map[string]struct{}) { c.lock.Lock() defer c.lock.Unlock() c.names = newMap c.updateRunning = false _ = time.AfterFunc(c.ttl, func() { c.DropCache() }) } func (c *dirCache) DropCache() { c.lock.Lock() defer c.lock.Unlock() c.names = nil } // Try to refresh: if another update is already running, do nothing, // otherwise, read the directory and set it. func (c *dirCache) maybeRefresh() { c.lock.Lock() defer c.lock.Unlock() if c.updateRunning { return } c.updateRunning = true go func() { newmap := newDirnameMap(c.fs, c.dir) c.setMap(newmap) }() } func (c *dirCache) RemoveEntry(name string) { c.lock.Lock() defer c.lock.Unlock() if c.names == nil { go c.maybeRefresh() return } delete(c.names, name) } func (c *dirCache) AddEntry(name string) { c.lock.Lock() defer c.lock.Unlock() if c.names == nil { go c.maybeRefresh() return } c.names[name] = struct{}{} } func newDirCache(fs pathfs.FileSystem, dir string, ttl time.Duration) *dirCache { dc := new(dirCache) dc.dir = dir dc.fs = fs dc.ttl = ttl return dc } func (c *dirCache) HasEntry(name string) (mapPresent bool, found bool) { c.lock.RLock() defer c.lock.RUnlock() if c.names == nil { go c.maybeRefresh() return false, false } _, ok := c.names[name] return true, ok } go-fuse-2.0.3/unionfs/timedcache.go000066400000000000000000000046251364171671200171760ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package unionfs import ( "sync" "time" ) type cacheEntry struct { data interface{} // expiry is the absolute timestamp of the expiry. expiry time.Time } // TimedCache caches the result of fetch() for some time. It is // thread-safe. Calls of fetch() do no happen inside a critical // section, so when multiple concurrent Get()s happen for the same // key, multiple fetch() calls may be issued for the same key. type TimedCacheFetcher func(name string) (value interface{}, cacheable bool) type TimedCache struct { fetch TimedCacheFetcher // ttl is the duration of the cache. ttl time.Duration cacheMapMutex sync.RWMutex cacheMap map[string]*cacheEntry PurgeTimer *time.Timer } // Creates a new cache with the given TTL. If TTL <= 0, the caching is // indefinite. func NewTimedCache(fetcher TimedCacheFetcher, ttl time.Duration) *TimedCache { l := new(TimedCache) l.ttl = ttl l.fetch = fetcher l.cacheMap = make(map[string]*cacheEntry) return l } func (c *TimedCache) Get(name string) interface{} { c.cacheMapMutex.RLock() info, ok := c.cacheMap[name] c.cacheMapMutex.RUnlock() valid := ok && (c.ttl <= 0 || info.expiry.After(time.Now())) if valid { return info.data } return c.GetFresh(name) } func (c *TimedCache) Set(name string, val interface{}) { c.cacheMapMutex.Lock() defer c.cacheMapMutex.Unlock() c.cacheMap[name] = &cacheEntry{ data: val, expiry: time.Now().Add(c.ttl), } } func (c *TimedCache) DropEntry(name string) { c.cacheMapMutex.Lock() defer c.cacheMapMutex.Unlock() delete(c.cacheMap, name) } func (c *TimedCache) GetFresh(name string) interface{} { data, ok := c.fetch(name) if ok { c.Set(name, data) } return data } // Drop all expired entries. func (c *TimedCache) Purge() { keys := make([]string, 0, len(c.cacheMap)) now := time.Now() c.cacheMapMutex.Lock() defer c.cacheMapMutex.Unlock() for k, v := range c.cacheMap { if now.After(v.expiry) { keys = append(keys, k) } } for _, k := range keys { delete(c.cacheMap, k) } } func (c *TimedCache) DropAll(names []string) { c.cacheMapMutex.Lock() defer c.cacheMapMutex.Unlock() if names == nil { c.cacheMap = make(map[string]*cacheEntry, len(c.cacheMap)) } else { for _, nm := range names { delete(c.cacheMap, nm) } } } go-fuse-2.0.3/unionfs/timedcache_test.go000066400000000000000000000027611364171671200202340ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package unionfs import ( "testing" "time" ) func TestTimedCacheUncacheable(t *testing.T) { fetchCount := 0 fetch := func(n string) (interface{}, bool) { fetchCount++ i := int(n[0]) return &i, false } cache := NewTimedCache(fetch, 0) v := cache.Get("n").(*int) w := cache.Get("n").(*int) if *v != int('n') || *w != *v { t.Errorf("value mismatch: got %d, %d want %d", *v, *w, int('n')) } if fetchCount != 2 { t.Fatalf("Should have fetched twice: %d", fetchCount) } } func TestTimedCache(t *testing.T) { fetchCount := 0 fetch := func(n string) (interface{}, bool) { fetchCount++ i := int(n[0]) return &i, true } // This fails with 1e6 on some Opteron CPUs. ttl := 100 * time.Millisecond cache := NewTimedCache(fetch, ttl) v := cache.Get("n").(*int) if *v != int('n') { t.Errorf("value mismatch: got %d, want %d", *v, int('n')) } if fetchCount != 1 { t.Errorf("fetch count mismatch: got %d want 1", fetchCount) } // The cache update is async. time.Sleep(time.Duration(ttl / 10)) w := cache.Get("n") if v != w { t.Errorf("Huh, inconsistent: 1st = %v != 2nd = %v", v, w) } if fetchCount > 1 { t.Errorf("fetch count fail: %d > 1", fetchCount) } time.Sleep(time.Duration(ttl * 2)) cache.Purge() w = cache.Get("n") if fetchCount == 1 { t.Error("Did not fetch again. Purge unsuccessful?") } } go-fuse-2.0.3/unionfs/unionfs.go000066400000000000000000000601771364171671200165750ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package unionfs import ( "crypto/md5" "fmt" "log" "os" "path" "path/filepath" "strings" "sync" "syscall" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/pathfs" ) func filePathHash(path string) string { dir, base := filepath.Split(path) h := md5.New() h.Write([]byte(dir)) return fmt.Sprintf("%x-%s", h.Sum(nil)[:8], base) } /* UnionFs implements a user-space union file system, which is stateless but efficient even if the writable branch is on NFS. Assumptions: * It uses a list of branches, the first of which (index 0) is thought to be writable, and the rest read-only. * It assumes that the number of deleted files is small relative to the total tree size. Implementation notes. * It overlays arbitrary writable FileSystems with any number of readonly FileSystems. * Deleting a file will put a file named /DELETIONS/HASH-OF-FULL-FILENAME into the writable overlay, containing the full filename itself. This is optimized for NFS usage: we want to minimize the number of NFS operations, which are slow. By putting all whiteouts in one place, we can cheaply fetch the list of all deleted files. Even without caching on our side, the kernel's negative dentry cache can answer is-deleted queries quickly. */ type unionFS struct { pathfs.FileSystem // The same, but as interfaces. fileSystems []pathfs.FileSystem // A file-existence cache. deletionCache *dirCache // A file -> branch cache. branchCache *TimedCache // Map of files to hide. hiddenFiles map[string]bool options *UnionFsOptions nodeFs *pathfs.PathNodeFs } type UnionFsOptions struct { BranchCacheTTL time.Duration DeletionCacheTTL time.Duration DeletionDirName string HiddenFiles []string } const ( _DROP_CACHE = ".drop_cache" ) func NewUnionFs(fileSystems []pathfs.FileSystem, options UnionFsOptions) (pathfs.FileSystem, error) { g := &unionFS{ options: &options, fileSystems: fileSystems, FileSystem: pathfs.NewDefaultFileSystem(), } writable := g.fileSystems[0] code := g.createDeletionStore() if !code.Ok() { return nil, fmt.Errorf("could not create deletion path %v: %v", options.DeletionDirName, code) } g.deletionCache = newDirCache(writable, options.DeletionDirName, options.DeletionCacheTTL) g.branchCache = NewTimedCache( func(n string) (interface{}, bool) { return g.getBranchAttrNoCache(n), true }, options.BranchCacheTTL) g.hiddenFiles = make(map[string]bool) for _, name := range options.HiddenFiles { g.hiddenFiles[name] = true } return g, nil } func (fs *unionFS) OnMount(nodeFs *pathfs.PathNodeFs) { fs.nodeFs = nodeFs } //////////////// // Deal with all the caches. // The isDeleted() method tells us if a path has a marker in the deletion store. // It may return an error code if the store could not be accessed. func (fs *unionFS) isDeleted(name string) (deleted bool, code fuse.Status) { marker := fs.deletionPath(name) haveCache, found := fs.deletionCache.HasEntry(filepath.Base(marker)) if haveCache { return found, fuse.OK } _, code = fs.fileSystems[0].GetAttr(marker, nil) if code == fuse.OK { return true, code } if code == fuse.ENOENT { return false, fuse.OK } log.Printf("error accessing deletion marker %s: %v", marker, code) return false, fuse.Status(syscall.EROFS) } func (fs *unionFS) createDeletionStore() (code fuse.Status) { writable := fs.fileSystems[0] fi, code := writable.GetAttr(fs.options.DeletionDirName, nil) if code == fuse.ENOENT { code = writable.Mkdir(fs.options.DeletionDirName, 0755, nil) if code.Ok() { fi, code = writable.GetAttr(fs.options.DeletionDirName, nil) } } if !code.Ok() || !fi.IsDir() { code = fuse.Status(syscall.EROFS) } return code } func (fs *unionFS) getBranch(name string) branchResult { name = stripSlash(name) r := fs.branchCache.Get(name) return r.(branchResult) } func (fs *unionFS) setBranch(name string, r branchResult) { if !r.valid() { log.Panicf("entry %q setting illegal branchResult %v", name, r) } fs.branchCache.Set(name, r) } type branchResult struct { attr *fuse.Attr code fuse.Status branch int } func (r *branchResult) valid() bool { return (r.branch >= 0 && r.attr != nil && r.code.Ok()) || (r.branch < 0 && r.attr == nil && !r.code.Ok()) } func (fs branchResult) String() string { return fmt.Sprintf("{%v %v branch %d}", fs.attr, fs.code, fs.branch) } func (fs *unionFS) getBranchAttrNoCache(name string) branchResult { name = stripSlash(name) parent, base := path.Split(name) parent = stripSlash(parent) parentBranch := 0 if base != "" { parentBranch = fs.getBranch(parent).branch } for i, fs := range fs.fileSystems { if i < parentBranch { continue } a, s := fs.GetAttr(name, nil) if s.Ok() { if i > 0 { // Needed to make hardlinks work. a.Ino = 0 } return branchResult{ attr: a, code: s, branch: i, } } else { if s != fuse.ENOENT { log.Printf("getattr: %v: Got error %v from branch %v", name, s, i) } } } return branchResult{nil, fuse.ENOENT, -1} } //////////////// // Deletion. func (fs *unionFS) deletionPath(name string) string { return filepath.Join(fs.options.DeletionDirName, filePathHash(name)) } func (fs *unionFS) removeDeletion(name string) { marker := fs.deletionPath(name) // os.Remove tries to be smart and issues a Remove() and // Rmdir() sequentially. We want to skip the 2nd system call, // so use syscall.Unlink() directly. code := fs.fileSystems[0].Unlink(marker, nil) if !code.Ok() && code != fuse.ENOENT { log.Printf("error unlinking %s: %v", marker, code) } // Update in-memory cache as last step, so we avoid caching a // state from before the storage update. fs.deletionCache.RemoveEntry(path.Base(marker)) } func (fs *unionFS) putDeletion(name string) (code fuse.Status) { code = fs.createDeletionStore() if !code.Ok() { return code } marker := fs.deletionPath(name) // Is there a WriteStringToFileOrDie ? writable := fs.fileSystems[0] fi, code := writable.GetAttr(marker, nil) if code.Ok() && fi.Size == uint64(len(name)) { return fuse.OK } var f nodefs.File if code == fuse.ENOENT { f, code = writable.Create(marker, uint32(os.O_TRUNC|os.O_WRONLY), 0644, nil) } else { writable.Chmod(marker, 0644, nil) f, code = writable.Open(marker, uint32(os.O_TRUNC|os.O_WRONLY), nil) } if !code.Ok() { log.Printf("could not create deletion file %v: %v", marker, code) return fuse.EPERM } defer f.Release() defer f.Flush() n, code := f.Write([]byte(name), 0) if int(n) != len(name) || !code.Ok() { panic(fmt.Sprintf("Error for writing %v: %v, %v (exp %v) %v", name, marker, n, len(name), code)) } // Update the in-memory deletion cache as the last step, // to ensure that the new state stays in memory fs.deletionCache.AddEntry(path.Base(marker)) return fuse.OK } //////////////// // Promotion. func (fs *unionFS) Promote(name string, srcResult branchResult, context *fuse.Context) (code fuse.Status) { writable := fs.fileSystems[0] sourceFs := fs.fileSystems[srcResult.branch] // Promote directories. fs.promoteDirsTo(name) if srcResult.attr.IsRegular() { code = pathfs.CopyFile(sourceFs, writable, name, name, context) if code.Ok() { code = writable.Chmod(name, srcResult.attr.Mode&07777|0200, context) } if code.Ok() { aTime := srcResult.attr.AccessTime() mTime := srcResult.attr.ModTime() code = writable.Utimens(name, &aTime, &mTime, context) } files := fs.nodeFs.AllFiles(name, 0) for _, fileWrapper := range files { if !code.Ok() { break } var uf *unionFsFile f := fileWrapper.File for f != nil { ok := false uf, ok = f.(*unionFsFile) if ok { break } f = f.InnerFile() } if uf == nil { panic("no unionFsFile found inside") } if uf.layer > 0 { uf.layer = 0 f := uf.File uf.File, code = fs.fileSystems[0].Open(name, fileWrapper.OpenFlags, context) f.Flush() f.Release() } } } else if srcResult.attr.IsSymlink() { link := "" link, code = sourceFs.Readlink(name, context) if !code.Ok() { log.Println("can't read link in source fs", name) } else { code = writable.Symlink(link, name, context) } } else if srcResult.attr.IsDir() { code = writable.Mkdir(name, srcResult.attr.Mode&07777|0200, context) } else { log.Println("Unknown file type:", srcResult.attr) return fuse.ENOSYS } if !code.Ok() { fs.branchCache.GetFresh(name) return code } else { r := fs.getBranch(name) r.branch = 0 fs.setBranch(name, r) } return fuse.OK } //////////////////////////////////////////////////////////////// // Below: implement interface for a FileSystem. func (fs *unionFS) Link(orig string, newName string, context *fuse.Context) (code fuse.Status) { origResult := fs.getBranch(orig) code = origResult.code if code.Ok() && origResult.branch > 0 { code = fs.Promote(orig, origResult, context) } if code.Ok() && origResult.branch > 0 { // Hairy: for the link to be hooked up to the existing // inode, PathNodeFs must see a client inode for the // original. We force a refresh of the attribute (so // the Ino is filled in.), and then force PathNodeFs // to see the Inode number. fs.branchCache.GetFresh(orig) inode := fs.nodeFs.Node(orig) var a fuse.Attr inode.Node().GetAttr(&a, nil, nil) } if code.Ok() { code = fs.promoteDirsTo(newName) } if code.Ok() { code = fs.fileSystems[0].Link(orig, newName, context) } if code.Ok() { fs.removeDeletion(newName) fs.branchCache.GetFresh(newName) } return code } func (fs *unionFS) Rmdir(path string, context *fuse.Context) (code fuse.Status) { r := fs.getBranch(path) if r.code != fuse.OK { return r.code } if !r.attr.IsDir() { return fuse.Status(syscall.ENOTDIR) } stream, code := fs.OpenDir(path, context) found := false for _ = range stream { found = true } if found { return fuse.Status(syscall.ENOTEMPTY) } if r.branch > 0 { code = fs.putDeletion(path) return code } code = fs.fileSystems[0].Rmdir(path, context) if code != fuse.OK { return code } r = fs.branchCache.GetFresh(path).(branchResult) if r.branch > 0 { code = fs.putDeletion(path) } return code } func (fs *unionFS) Mkdir(path string, mode uint32, context *fuse.Context) (code fuse.Status) { deleted, code := fs.isDeleted(path) if !code.Ok() { return code } if !deleted { r := fs.getBranch(path) if r.code != fuse.ENOENT { return fuse.Status(syscall.EEXIST) } } code = fs.promoteDirsTo(path) if code.Ok() { code = fs.fileSystems[0].Mkdir(path, mode, context) } if code.Ok() { fs.removeDeletion(path) attr := &fuse.Attr{ Mode: fuse.S_IFDIR | mode, } fs.setBranch(path, branchResult{attr, fuse.OK, 0}) } var stream []fuse.DirEntry stream, code = fs.OpenDir(path, context) if code.Ok() { // This shouldn't happen, but let's be safe. for _, entry := range stream { fs.putDeletion(filepath.Join(path, entry.Name)) } } return code } func (fs *unionFS) Symlink(pointedTo string, linkName string, context *fuse.Context) (code fuse.Status) { code = fs.promoteDirsTo(linkName) if code.Ok() { code = fs.fileSystems[0].Symlink(pointedTo, linkName, context) } if code.Ok() { fs.removeDeletion(linkName) fs.branchCache.GetFresh(linkName) } return code } func (fs *unionFS) Truncate(path string, size uint64, context *fuse.Context) (code fuse.Status) { if path == _DROP_CACHE { return fuse.OK } r := fs.getBranch(path) if r.branch > 0 { code = fs.Promote(path, r, context) r.branch = 0 } if code.Ok() { code = fs.fileSystems[0].Truncate(path, size, context) } if code.Ok() { newAttr := *r.attr r.attr = &newAttr r.attr.Size = size now := time.Now() r.attr.SetTimes(nil, &now, &now) fs.setBranch(path, r) } return code } func (fs *unionFS) Utimens(name string, atime *time.Time, mtime *time.Time, context *fuse.Context) (code fuse.Status) { name = stripSlash(name) r := fs.getBranch(name) code = r.code if code.Ok() && r.branch > 0 { code = fs.Promote(name, r, context) r.branch = 0 } if code.Ok() { code = fs.fileSystems[0].Utimens(name, atime, mtime, context) } if code.Ok() { now := time.Now() newAttr := *r.attr r.attr = &newAttr r.attr.SetTimes(atime, mtime, &now) fs.setBranch(name, r) } return code } func (fs *unionFS) Chown(name string, uid uint32, gid uint32, context *fuse.Context) (code fuse.Status) { name = stripSlash(name) r := fs.getBranch(name) if r.attr == nil || r.code != fuse.OK { return r.code } newAttr := *r.attr r.attr = &newAttr if os.Geteuid() != 0 { return fuse.EPERM } if r.attr.Uid != uid || r.attr.Gid != gid { if r.branch > 0 { code := fs.Promote(name, r, context) if code != fuse.OK { return code } r.branch = 0 } fs.fileSystems[0].Chown(name, uid, gid, context) } r.attr.Uid = uid r.attr.Gid = gid now := time.Now() r.attr.SetTimes(nil, nil, &now) fs.setBranch(name, r) return fuse.OK } func (fs *unionFS) Chmod(name string, mode uint32, context *fuse.Context) (code fuse.Status) { name = stripSlash(name) r := fs.getBranch(name) if r.attr == nil { return r.code } newAttr := *r.attr r.attr = &newAttr if r.code != fuse.OK { return r.code } permMask := uint32(07777) // Always be writable. oldMode := r.attr.Mode & permMask if oldMode != mode { if r.branch > 0 { code := fs.Promote(name, r, context) if code != fuse.OK { return code } r.branch = 0 } fs.fileSystems[0].Chmod(name, mode, context) } r.attr.Mode = (r.attr.Mode &^ permMask) | mode now := time.Now() r.attr.SetTimes(nil, nil, &now) fs.setBranch(name, r) return fuse.OK } func (fs *unionFS) Access(name string, mode uint32, context *fuse.Context) (code fuse.Status) { // We always allow writing. mode = mode &^ fuse.W_OK if name == "" || name == _DROP_CACHE { return fuse.OK } r := fs.getBranch(name) if r.branch >= 0 { return fs.fileSystems[r.branch].Access(name, mode, context) } return fuse.ENOENT } func (fs *unionFS) Unlink(name string, context *fuse.Context) (code fuse.Status) { r := fs.getBranch(name) if r.branch == 0 { code = fs.fileSystems[0].Unlink(name, context) if code != fuse.OK { return code } r = fs.branchCache.GetFresh(name).(branchResult) } if r.branch > 0 { // It would be nice to do the putDeletion async. code = fs.putDeletion(name) } return code } func (fs *unionFS) Readlink(name string, context *fuse.Context) (out string, code fuse.Status) { r := fs.getBranch(name) if r.branch >= 0 { return fs.fileSystems[r.branch].Readlink(name, context) } return "", fuse.ENOENT } func stripSlash(fn string) string { return strings.TrimRight(fn, string(filepath.Separator)) } func (fs *unionFS) promoteDirsTo(filename string) fuse.Status { dirName, _ := filepath.Split(filename) dirName = stripSlash(dirName) var todo []string var results []branchResult for dirName != "" { r := fs.getBranch(dirName) if !r.code.Ok() { log.Println("path component does not exist", filename, dirName) } if !r.attr.IsDir() { log.Println("path component is not a directory.", dirName, r) return fuse.EPERM } if r.branch == 0 { break } todo = append(todo, dirName) results = append(results, r) dirName, _ = filepath.Split(dirName) dirName = stripSlash(dirName) } for i := range todo { j := len(todo) - i - 1 d := todo[j] r := results[j] code := fs.fileSystems[0].Mkdir(d, r.attr.Mode&07777|0200, nil) if code != fuse.OK { log.Println("Error creating dir leading to path", d, code, fs.fileSystems[0]) return fuse.EPERM } aTime := r.attr.AccessTime() mTime := r.attr.ModTime() fs.fileSystems[0].Utimens(d, &aTime, &mTime, nil) r.branch = 0 fs.setBranch(d, r) } return fuse.OK } func (fs *unionFS) Create(name string, flags uint32, mode uint32, context *fuse.Context) (fuseFile nodefs.File, code fuse.Status) { writable := fs.fileSystems[0] code = fs.promoteDirsTo(name) if code != fuse.OK { return nil, code } fuseFile, code = writable.Create(name, flags, mode, context) if code.Ok() { fuseFile = fs.newUnionFsFile(fuseFile, 0) fs.removeDeletion(name) now := time.Now() a := fuse.Attr{ Mode: fuse.S_IFREG | mode, } a.SetTimes(nil, &now, &now) fs.setBranch(name, branchResult{&a, fuse.OK, 0}) } return fuseFile, code } func (fs *unionFS) GetAttr(name string, context *fuse.Context) (a *fuse.Attr, s fuse.Status) { _, hidden := fs.hiddenFiles[name] if hidden { return nil, fuse.ENOENT } if name == _DROP_CACHE { return &fuse.Attr{ Mode: fuse.S_IFREG | 0777, }, fuse.OK } if name == fs.options.DeletionDirName { return nil, fuse.ENOENT } isDel, s := fs.isDeleted(name) if !s.Ok() { return nil, s } if isDel { return nil, fuse.ENOENT } r := fs.getBranch(name) if r.branch < 0 { return nil, fuse.ENOENT } fi := *r.attr // Make everything appear writable. fi.Mode |= 0200 return &fi, r.code } func (fs *unionFS) GetXAttr(name string, attr string, context *fuse.Context) ([]byte, fuse.Status) { if name == _DROP_CACHE { return nil, fuse.ENOATTR } r := fs.getBranch(name) if r.branch >= 0 { return fs.fileSystems[r.branch].GetXAttr(name, attr, context) } return nil, fuse.ENOENT } func (fs *unionFS) OpenDir(directory string, context *fuse.Context) (stream []fuse.DirEntry, status fuse.Status) { dirBranch := fs.getBranch(directory) if dirBranch.branch < 0 { return nil, fuse.ENOENT } // We could try to use the cache, but we have a delay, so // might as well get the fresh results async. var wg sync.WaitGroup var deletions map[string]struct{} wg.Add(1) go func() { deletions = newDirnameMap(fs.fileSystems[0], fs.options.DeletionDirName) wg.Done() }() entries := make([]map[string]uint32, len(fs.fileSystems)) for i := range fs.fileSystems { entries[i] = make(map[string]uint32) } statuses := make([]fuse.Status, len(fs.fileSystems)) for i, l := range fs.fileSystems { if i >= dirBranch.branch { wg.Add(1) go func(j int, pfs pathfs.FileSystem) { ch, s := pfs.OpenDir(directory, context) statuses[j] = s for _, v := range ch { entries[j][v.Name] = v.Mode } wg.Done() }(i, l) } } wg.Wait() if deletions == nil { _, code := fs.fileSystems[0].GetAttr(fs.options.DeletionDirName, context) if code == fuse.ENOENT { deletions = map[string]struct{}{} } else { return nil, fuse.Status(syscall.EROFS) } } results := entries[0] // TODO(hanwen): should we do anything with the return // statuses? for i, m := range entries { if statuses[i] != fuse.OK { continue } if i == 0 { // We don't need to further process the first // branch: it has no deleted files. continue } for k, v := range m { _, ok := results[k] if ok { continue } _, deleted := deletions[filePathHash(filepath.Join(directory, k))] if !deleted { results[k] = v } } } if directory == "" { delete(results, fs.options.DeletionDirName) for name, _ := range fs.hiddenFiles { delete(results, name) } } stream = make([]fuse.DirEntry, 0, len(results)) for k, v := range results { stream = append(stream, fuse.DirEntry{ Name: k, Mode: v, }) } return stream, fuse.OK } // recursivePromote promotes path, and if a directory, everything // below that directory. It returns a list of all promoted paths, in // full, including the path itself. func (fs *unionFS) recursivePromote(path string, pathResult branchResult, context *fuse.Context) (names []string, code fuse.Status) { names = []string{} if pathResult.branch > 0 { code = fs.Promote(path, pathResult, context) } if code.Ok() { names = append(names, path) } if code.Ok() && pathResult.attr != nil && pathResult.attr.IsDir() { var stream []fuse.DirEntry stream, code = fs.OpenDir(path, context) for _, e := range stream { if !code.Ok() { break } subnames := []string{} p := filepath.Join(path, e.Name) r := fs.getBranch(p) subnames, code = fs.recursivePromote(p, r, context) names = append(names, subnames...) } } if !code.Ok() { names = nil } return names, code } func (fs *unionFS) renameDirectory(srcResult branchResult, srcDir string, dstDir string, context *fuse.Context) (code fuse.Status) { names := []string{} if code.Ok() { names, code = fs.recursivePromote(srcDir, srcResult, context) } if code.Ok() { code = fs.promoteDirsTo(dstDir) } if code.Ok() { writable := fs.fileSystems[0] code = writable.Rename(srcDir, dstDir, context) } if code.Ok() { for _, srcName := range names { relative := strings.TrimLeft(srcName[len(srcDir):], string(filepath.Separator)) dst := filepath.Join(dstDir, relative) fs.removeDeletion(dst) srcResult := fs.getBranch(srcName) srcResult.branch = 0 fs.setBranch(dst, srcResult) srcResult = fs.branchCache.GetFresh(srcName).(branchResult) if srcResult.branch > 0 { code = fs.putDeletion(srcName) } } } return code } func (fs *unionFS) Rename(src string, dst string, context *fuse.Context) fuse.Status { srcResult := fs.getBranch(src) if !srcResult.code.Ok() { return srcResult.code } if srcResult.attr.IsDir() { return fs.renameDirectory(srcResult, src, dst, context) } if srcResult.branch > 0 { if code := fs.Promote(src, srcResult, context); !code.Ok() { return code } } if code := fs.promoteDirsTo(dst); !code.Ok() { return code } if code := fs.fileSystems[0].Rename(src, dst, context); !code.Ok() { return code } fs.removeDeletion(dst) // Rename is racy; avoid racing with unionFsFile.Release(). fs.branchCache.DropEntry(dst) srcResult = fs.branchCache.GetFresh(src).(branchResult) if srcResult.branch > 0 { return fs.putDeletion(src) } return fuse.OK } func (fs *unionFS) DropBranchCache(names []string) { fs.branchCache.DropAll(names) } func (fs *unionFS) DropDeletionCache() { fs.deletionCache.DropCache() } func (fs *unionFS) DropSubFsCaches() { for _, fs := range fs.fileSystems { a, code := fs.GetAttr(_DROP_CACHE, nil) if code.Ok() && a.IsRegular() { f, _ := fs.Open(_DROP_CACHE, uint32(os.O_WRONLY), nil) if f != nil { f.Flush() f.Release() } } } } func (fs *unionFS) Open(name string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) { if name == _DROP_CACHE { if flags&fuse.O_ANYWRITE != 0 { log.Println("Forced cache drop on", fs) fs.DropBranchCache(nil) fs.DropDeletionCache() fs.DropSubFsCaches() fs.nodeFs.ForgetClientInodes() } return nodefs.NewDevNullFile(), fuse.OK } r := fs.getBranch(name) if r.branch < 0 { // This should not happen, as a GetAttr() should have // already verified existence. log.Println("UnionFs: open of non-existent file:", name) return nil, fuse.ENOENT } if flags&fuse.O_ANYWRITE != 0 && r.branch > 0 { code := fs.Promote(name, r, context) if code != fuse.OK { return nil, code } r.branch = 0 now := time.Now() r.attr.SetTimes(nil, &now, nil) fs.setBranch(name, r) } fuseFile, status = fs.fileSystems[r.branch].Open(name, uint32(flags), context) if fuseFile != nil { fuseFile = fs.newUnionFsFile(fuseFile, r.branch) } return fuseFile, status } func (fs *unionFS) String() string { names := []string{} for _, fs := range fs.fileSystems { names = append(names, fs.String()) } return fmt.Sprintf("UnionFs(%v)", names) } func (fs *unionFS) StatFs(name string) *fuse.StatfsOut { return fs.fileSystems[0].StatFs("") } type unionFsFile struct { nodefs.File ufs *unionFS node *nodefs.Inode layer int } func (fs *unionFsFile) String() string { return fmt.Sprintf("unionFsFile(%s)", fs.File.String()) } func (fs *unionFS) newUnionFsFile(f nodefs.File, branch int) *unionFsFile { return &unionFsFile{ File: f, ufs: fs, layer: branch, } } func (fs *unionFsFile) InnerFile() (file nodefs.File) { return fs.File } // We can't hook on Release. Release has no response, so it is not // ordered wrt any following calls. func (fs *unionFsFile) Flush() (code fuse.Status) { code = fs.File.Flush() path := fs.ufs.nodeFs.Path(fs.node) fs.ufs.branchCache.GetFresh(path) return code } func (fs *unionFsFile) SetInode(node *nodefs.Inode) { fs.node = node } func (fs *unionFsFile) GetAttr(out *fuse.Attr) fuse.Status { code := fs.File.GetAttr(out) if code.Ok() { out.Mode |= 0200 } return code } go-fuse-2.0.3/unionfs/unionfs_test.go000066400000000000000000001004201364171671200176160ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package unionfs import ( "bytes" "fmt" "io/ioutil" "os" "os/exec" "path/filepath" "regexp" "strconv" "strings" "syscall" "testing" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/pathfs" "github.com/hanwen/go-fuse/v2/internal/testutil" "github.com/hanwen/go-fuse/v2/posixtest" ) func TestFilePathHash(t *testing.T) { got := filePathHash("xyz/abc") want := "34d52a6371ee5c79-abc" if got != want { t.Errorf("got %q want %q", got, want) } } var testOpts = UnionFsOptions{ DeletionCacheTTL: entryTTL, DeletionDirName: "DELETIONS", BranchCacheTTL: entryTTL, HiddenFiles: []string{"hidden"}, } func setRecursiveWritable(t *testing.T, dir string, writable bool) { err := filepath.Walk( dir, func(path string, fi os.FileInfo, err error) error { var newMode uint32 if writable { newMode = uint32(fi.Mode().Perm()) | 0200 } else { newMode = uint32(fi.Mode().Perm()) &^ 0222 } if fi.Mode()|os.ModeSymlink != 0 { return nil } return os.Chmod(path, os.FileMode(newMode)) }) if err != nil { t.Fatalf("Walk: %v", err) } } // Creates a temporary dir "wd" with 3 directories: // mnt ... overlayed (unionfs) mount // rw .... modifiable data // ro .... read-only data func setupUfs(t *testing.T) (wd string, cleanup func()) { // Make sure system setting does not affect test. syscall.Umask(0) wd = testutil.TempDir() err := os.Mkdir(wd+"/mnt", 0700) if err != nil { t.Fatalf("Mkdir: %v", err) } err = os.Mkdir(wd+"/rw", 0700) if err != nil { t.Fatalf("Mkdir: %v", err) } os.Mkdir(wd+"/ro", 0700) if err != nil { t.Fatalf("Mkdir: %v", err) } fses := []pathfs.FileSystem{ pathfs.NewLoopbackFileSystem(wd + "/rw"), NewCachingFileSystem(pathfs.NewLoopbackFileSystem(wd+"/ro"), 0), } ufs, err := NewUnionFs(fses, testOpts) if err != nil { t.Fatalf("NewUnionFs: %v", err) } // We configure timeouts are smaller, so we can check for // UnionFs's cache consistency. opts := &nodefs.Options{ EntryTimeout: entryTTL / 2, AttrTimeout: entryTTL / 2, NegativeTimeout: entryTTL / 2, PortableInodes: true, Debug: testutil.VerboseTest(), LookupKnownChildren: true, } pathfs := pathfs.NewPathNodeFs(ufs, &pathfs.PathNodeFsOptions{ClientInodes: true, Debug: opts.Debug, }) state, _, err := nodefs.MountRoot(wd+"/mnt", pathfs.Root(), opts) if err != nil { t.Fatalf("MountNodeFileSystem: %v", err) } go state.Serve() state.WaitMount() return wd, func() { err := state.Unmount() if err != nil { return } setRecursiveWritable(t, wd, true) os.RemoveAll(wd) } } func readFromFile(t *testing.T, path string) string { b, err := ioutil.ReadFile(path) if err != nil { t.Fatalf("ReadFile: %v", err) } return string(b) } func dirNames(t *testing.T, path string) map[string]bool { f, err := os.Open(path) if err != nil { t.Fatalf("Open: %v", err) } result := make(map[string]bool) names, err := f.Readdirnames(-1) if err != nil { t.Fatalf("Readdirnames: %v", err) } err = f.Close() if err != nil { t.Fatalf("Close: %v", err) } for _, nm := range names { result[nm] = true } return result } func checkMapEq(t *testing.T, m1, m2 map[string]bool) { if !mapEq(m1, m2) { msg := fmt.Sprintf("mismatch: got %v != expect %v", m1, m2) panic(msg) } } func mapEq(m1, m2 map[string]bool) bool { if len(m1) != len(m2) { return false } for k, v := range m1 { val, ok := m2[k] if !ok || val != v { return false } } return true } func fileExists(path string) bool { f, err := os.Lstat(path) return err == nil && f != nil } func TestUnionFsAutocreateDeletionDir(t *testing.T) { wd, clean := setupUfs(t) defer clean() err := os.Remove(wd + "/rw/DELETIONS") if err != nil { t.Fatalf("Remove: %v", err) } err = os.Mkdir(wd+"/mnt/dir", 0755) if err != nil { t.Fatalf("Mkdir: %v", err) } _, err = ioutil.ReadDir(wd + "/mnt/dir") if err != nil { t.Fatalf("ReadDir: %v", err) } } func TestUnionFsSymlink(t *testing.T) { wd, clean := setupUfs(t) defer clean() posixtest.SymlinkReadlink(t, wd+"/mnt") } func TestUnionFsSymlinkPromote(t *testing.T) { wd, clean := setupUfs(t) defer clean() err := os.Mkdir(wd+"/ro/subdir", 0755) if err != nil { t.Fatalf("Mkdir: %v", err) } err = os.Symlink("/foobar", wd+"/mnt/subdir/link") if err != nil { t.Fatalf("Symlink: %v", err) } } func TestUnionFsChtimes(t *testing.T) { wd, clean := setupUfs(t) defer clean() WriteFile(t, wd+"/ro/file", "a") err := os.Chtimes(wd+"/ro/file", time.Unix(42, 0), time.Unix(43, 0)) if err != nil { t.Fatalf("Chtimes: %v", err) } err = os.Chtimes(wd+"/mnt/file", time.Unix(82, 0), time.Unix(83, 0)) if err != nil { t.Fatalf("Chtimes: %v", err) } fi, err := os.Lstat(wd + "/mnt/file") attr := &fuse.Attr{} attr.FromStat(fuse.ToStatT(fi)) if attr.Atime != 82 || attr.Mtime != 83 { t.Error("Incorrect timestamp", fi) } } func TestUnionFsChmod(t *testing.T) { wd, clean := setupUfs(t) defer clean() ro_fn := wd + "/ro/file" m_fn := wd + "/mnt/file" WriteFile(t, ro_fn, "a") err := os.Chmod(m_fn, 00070) if err != nil { t.Fatalf("Chmod: %v", err) } fi, err := os.Lstat(m_fn) if err != nil { t.Fatalf("Lstat: %v", err) } if fi.Mode()&07777 != 00270 { t.Errorf("Unexpected mode found: %o", uint32(fi.Mode().Perm())) } _, err = os.Lstat(wd + "/rw/file") if err != nil { t.Errorf("File not promoted") } } func TestUnionFsChown(t *testing.T) { wd, clean := setupUfs(t) defer clean() ro_fn := wd + "/ro/file" m_fn := wd + "/mnt/file" WriteFile(t, ro_fn, "a") err := os.Chown(m_fn, 0, 0) code := fuse.ToStatus(err) if code != fuse.EPERM { t.Error("Unexpected error code", code, err) } } func TestUnionFsDelete(t *testing.T) { wd, clean := setupUfs(t) defer clean() WriteFile(t, wd+"/ro/file", "a") _, err := os.Lstat(wd + "/mnt/file") if err != nil { t.Fatalf("Lstat: %v", err) } err = os.Remove(wd + "/mnt/file") if err != nil { t.Fatalf("Remove: %v", err) } _, err = os.Lstat(wd + "/mnt/file") if err == nil { t.Fatal("should have disappeared.") } delPath := wd + "/rw/" + testOpts.DeletionDirName names := dirNames(t, delPath) if len(names) != 1 { t.Fatal("Should have 1 deletion", names) } for k := range names { c, err := ioutil.ReadFile(delPath + "/" + k) if err != nil { t.Fatalf("ReadFile: %v", err) } if string(c) != "file" { t.Fatal("content mismatch", string(c)) } } } func TestUnionFsBasic(t *testing.T) { wd, clean := setupUfs(t) defer clean() WriteFile(t, wd+"/rw/rw", "a") WriteFile(t, wd+"/ro/ro1", "a") WriteFile(t, wd+"/ro/ro2", "b") names := dirNames(t, wd+"/mnt") expected := map[string]bool{ "rw": true, "ro1": true, "ro2": true, } checkMapEq(t, names, expected) WriteFile(t, wd+"/mnt/new", "new contents") if !fileExists(wd + "/rw/new") { t.Errorf("missing file in rw layer: %s", wd+"/rw/new") } contents := readFromFile(t, wd+"/mnt/new") if contents != "new contents" { t.Errorf("read mismatch: '%v'", contents) } WriteFile(t, wd+"/mnt/ro1", "promote me") if !fileExists(wd + "/rw/ro1") { t.Errorf("missing file in rw layer: %s", wd+"/mnt/ro1") } err := os.Remove(wd + "/mnt/new") if err != nil { t.Fatalf("Remove: %v", err) } names = dirNames(t, wd+"/mnt") checkMapEq(t, names, map[string]bool{ "rw": true, "ro1": true, "ro2": true, }) names = dirNames(t, wd+"/rw") checkMapEq(t, names, map[string]bool{ testOpts.DeletionDirName: true, "rw": true, "ro1": true, }) names = dirNames(t, wd+"/rw/"+testOpts.DeletionDirName) if len(names) != 0 { t.Errorf("Expected 0 entry in %v", names) } err = os.Remove(wd + "/mnt/ro1") if err != nil { t.Fatalf("Remove: %v", err) } names = dirNames(t, wd+"/mnt") checkMapEq(t, names, map[string]bool{ "rw": true, "ro2": true, }) names = dirNames(t, wd+"/rw") checkMapEq(t, names, map[string]bool{ "rw": true, testOpts.DeletionDirName: true, }) names = dirNames(t, wd+"/rw/"+testOpts.DeletionDirName) if len(names) != 1 { t.Errorf("Expected 1 entry in %v", names) } } func TestUnionFsPromote(t *testing.T) { wd, clean := setupUfs(t) defer clean() err := os.Mkdir(wd+"/ro/subdir", 0755) if err != nil { t.Fatalf("Mkdir: %v", err) } WriteFile(t, wd+"/ro/subdir/file", "content") WriteFile(t, wd+"/mnt/subdir/file", "other-content") } func TestUnionFsCreate(t *testing.T) { wd, clean := setupUfs(t) defer clean() err := os.MkdirAll(wd+"/ro/subdir/sub2", 0755) if err != nil { t.Fatalf("MkdirAll: %v", err) } WriteFile(t, wd+"/mnt/subdir/sub2/file", "other-content") _, err = os.Lstat(wd + "/mnt/subdir/sub2/file") if err != nil { t.Fatalf("Lstat: %v", err) } } func TestUnionFsOpenUndeletes(t *testing.T) { wd, clean := setupUfs(t) defer clean() WriteFile(t, wd+"/ro/file", "X") err := os.Remove(wd + "/mnt/file") if err != nil { t.Fatalf("Remove: %v", err) } WriteFile(t, wd+"/mnt/file", "X") _, err = os.Lstat(wd + "/mnt/file") if err != nil { t.Fatalf("Lstat: %v", err) } } func TestUnionFsMkdir(t *testing.T) { wd, clean := setupUfs(t) defer clean() posixtest.MkdirRmdir(t, wd+"/mnt") } func TestUnionFsMkdirPromote(t *testing.T) { wd, clean := setupUfs(t) defer clean() dirname := wd + "/ro/subdir/subdir2" err := os.MkdirAll(dirname, 0755) if err != nil { t.Fatalf("MkdirAll: %v", err) } err = os.Mkdir(wd+"/mnt/subdir/subdir2/dir3", 0755) if err != nil { t.Fatalf("Mkdir: %v", err) } fi, _ := os.Lstat(wd + "/rw/subdir/subdir2/dir3") if err != nil { t.Fatalf("Lstat: %v", err) } if fi == nil || !fi.IsDir() { t.Error("is not a directory: ", fi) } } func TestUnionFsRmdirMkdir(t *testing.T) { wd, clean := setupUfs(t) defer clean() err := os.Mkdir(wd+"/ro/subdir", 0755) if err != nil { t.Fatalf("Mkdir: %v", err) } dirname := wd + "/mnt/subdir" err = os.Remove(dirname) if err != nil { t.Fatalf("Remove: %v", err) } err = os.Mkdir(dirname, 0755) if err != nil { t.Fatalf("Mkdir: %v", err) } } func TestUnionFsRename(t *testing.T) { type Config struct { f1_ro bool f1_rw bool f2_ro bool f2_rw bool } configs := make([]Config, 0) for i := 0; i < 16; i++ { c := Config{i&0x1 != 0, i&0x2 != 0, i&0x4 != 0, i&0x8 != 0} if !(c.f1_ro || c.f1_rw) { continue } configs = append(configs, c) } for i, c := range configs { t.Run(fmt.Sprintf("config %d", i), func(t *testing.T) { wd, clean := setupUfs(t) defer clean() if c.f1_ro { WriteFile(t, wd+"/ro/file1", "c1") } if c.f1_rw { WriteFile(t, wd+"/rw/file1", "c2") } if c.f2_ro { WriteFile(t, wd+"/ro/file2", "c3") } if c.f2_rw { WriteFile(t, wd+"/rw/file2", "c4") } err := os.Rename(wd+"/mnt/file1", wd+"/mnt/file2") if err != nil { t.Fatalf("Rename: %v", err) } _, err = os.Lstat(wd + "/mnt/file1") if err == nil { t.Errorf("Should have lost file1") } _, err = os.Lstat(wd + "/mnt/file2") if err != nil { t.Errorf("Should have gotten file2: %v", err) } err = os.Rename(wd+"/mnt/file2", wd+"/mnt/file1") if err != nil { t.Fatalf("Rename: %v", err) } _, err = os.Lstat(wd + "/mnt/file2") if err == nil { t.Errorf("Should have lost file2") } _, err = os.Lstat(wd + "/mnt/file1") if err != nil { t.Errorf("Should have gotten file1: %v", err) } }) } } func TestUnionFsRenameDirBasic(t *testing.T) { wd, clean := setupUfs(t) defer clean() err := os.MkdirAll(wd+"/ro/dir/subdir", 0755) if err != nil { t.Fatalf("MkdirAll: %v", err) } err = os.Rename(wd+"/mnt/dir", wd+"/mnt/renamed") if err != nil { t.Fatalf("Rename: %v", err) } if fi, _ := os.Lstat(wd + "/mnt/dir"); fi != nil { t.Fatalf("%s/mnt/dir should have disappeared: %v", wd, fi) } if fi, _ := os.Lstat(wd + "/mnt/renamed"); fi == nil || !fi.IsDir() { t.Fatalf("%s/mnt/renamed should be directory: %v", wd, fi) } entries, err := ioutil.ReadDir(wd + "/mnt/renamed") if err != nil || len(entries) != 1 || entries[0].Name() != "subdir" { t.Errorf("readdir(%s/mnt/renamed) should have one entry: %v, err %v", wd, entries, err) } if err = os.Mkdir(wd+"/mnt/dir", 0755); err != nil { t.Errorf("mkdir should succeed %v", err) } } func TestUnionFsRenameDirAllSourcesGone(t *testing.T) { wd, clean := setupUfs(t) defer clean() err := os.MkdirAll(wd+"/ro/dir", 0755) if err != nil { t.Fatalf("MkdirAll: %v", err) } err = ioutil.WriteFile(wd+"/ro/dir/file.txt", []byte{42}, 0644) if err != nil { t.Fatalf("WriteFile: %v", err) } setRecursiveWritable(t, wd+"/ro", false) err = os.Rename(wd+"/mnt/dir", wd+"/mnt/renamed") if err != nil { t.Fatalf("Rename: %v", err) } names := dirNames(t, wd+"/rw/"+testOpts.DeletionDirName) if len(names) != 2 { t.Errorf("Expected 2 entries in %v", names) } } func TestUnionFsRenameDirWithDeletions(t *testing.T) { wd, clean := setupUfs(t) defer clean() err := os.MkdirAll(wd+"/ro/dir/subdir", 0755) if err != nil { t.Fatalf("MkdirAll: %v", err) } err = ioutil.WriteFile(wd+"/ro/dir/file.txt", []byte{42}, 0644) if err != nil { t.Fatalf("WriteFile: %v", err) } err = ioutil.WriteFile(wd+"/ro/dir/subdir/file.txt", []byte{42}, 0644) if err != nil { t.Fatalf("WriteFile: %v", err) } setRecursiveWritable(t, wd+"/ro", false) if fi, _ := os.Lstat(wd + "/mnt/dir/subdir/file.txt"); fi == nil || fi.Mode()&os.ModeType != 0 { t.Fatalf("%s/mnt/dir/subdir/file.txt should be file: %v", wd, fi) } err = os.Remove(wd + "/mnt/dir/file.txt") if err != nil { t.Fatalf("Remove: %v", err) } err = os.Rename(wd+"/mnt/dir", wd+"/mnt/renamed") if err != nil { t.Fatalf("Rename: %v", err) } if fi, _ := os.Lstat(wd + "/mnt/dir/subdir/file.txt"); fi != nil { t.Fatalf("%s/mnt/dir/subdir/file.txt should have disappeared: %v", wd, fi) } if fi, _ := os.Lstat(wd + "/mnt/dir"); fi != nil { t.Fatalf("%s/mnt/dir should have disappeared: %v", wd, fi) } if fi, _ := os.Lstat(wd + "/mnt/renamed"); fi == nil || !fi.IsDir() { t.Fatalf("%s/mnt/renamed should be directory: %v", wd, fi) } if fi, _ := os.Lstat(wd + "/mnt/renamed/file.txt"); fi != nil { t.Fatalf("%s/mnt/renamed/file.txt should have disappeared %#v", wd, fi) } if err = os.Mkdir(wd+"/mnt/dir", 0755); err != nil { t.Errorf("mkdir should succeed %v", err) } if fi, _ := os.Lstat(wd + "/mnt/dir/subdir"); fi != nil { t.Fatalf("%s/mnt/dir/subdir should have disappeared %#v", wd, fi) } } func TestUnionFsRenameSymlink(t *testing.T) { wd, clean := setupUfs(t) defer clean() err := os.Symlink("linktarget", wd+"/ro/link") if err != nil { t.Fatalf("Symlink: %v", err) } err = os.Rename(wd+"/mnt/link", wd+"/mnt/renamed") if err != nil { t.Fatalf("Rename: %v", err) } if fi, _ := os.Lstat(wd + "/mnt/link"); fi != nil { t.Fatalf("%s/mnt/link should have disappeared: %v", wd, fi) } if fi, _ := os.Lstat(wd + "/mnt/renamed"); fi == nil || fi.Mode()&os.ModeSymlink == 0 { t.Fatalf("%s/mnt/renamed should be link: %v", wd, fi) } if link, err := os.Readlink(wd + "/mnt/renamed"); err != nil || link != "linktarget" { t.Fatalf("readlink(%s/mnt/renamed) should point to 'linktarget': %v, err %v", wd, link, err) } } func TestUnionFsWritableDir(t *testing.T) { wd, clean := setupUfs(t) defer clean() dirname := wd + "/ro/subdir" err := os.Mkdir(dirname, 0555) if err != nil { t.Fatalf("Mkdir: %v", err) } setRecursiveWritable(t, wd+"/ro", false) fi, err := os.Lstat(wd + "/mnt/subdir") if err != nil { t.Fatalf("Lstat: %v", err) } if fi.Mode().Perm()&0222 == 0 { t.Errorf("unexpected permission %o", fi.Mode().Perm()) } } func TestUnionFsWriteAccess(t *testing.T) { wd, clean := setupUfs(t) defer clean() fn := wd + "/ro/file" // No write perms. err := ioutil.WriteFile(fn, []byte("foo"), 0444) if err != nil { t.Fatalf("WriteFile: %v", err) } setRecursiveWritable(t, wd+"/ro", false) err = syscall.Access(wd+"/mnt/file", fuse.W_OK) if err != nil { if err != nil { t.Fatalf("Access: %v", err) } } } func TestUnionFsLink(t *testing.T) { wd, clean := setupUfs(t) defer clean() content := "blabla" fn := wd + "/ro/file" err := ioutil.WriteFile(fn, []byte(content), 0666) if err != nil { t.Fatalf("WriteFile: %v", err) } setRecursiveWritable(t, wd+"/ro", false) err = os.Link(wd+"/mnt/file", wd+"/mnt/linked") if err != nil { t.Fatalf("Link: %v", err) } fi2, err := os.Lstat(wd + "/mnt/linked") if err != nil { t.Fatalf("Lstat: %v", err) } fi1, err := os.Lstat(wd + "/mnt/file") if err != nil { t.Fatalf("Lstat: %v", err) } s1 := fuse.ToStatT(fi1) s2 := fuse.ToStatT(fi2) if s1.Ino != s2.Ino { t.Errorf("inode numbers should be equal for linked files %v, %v", s1.Ino, s2.Ino) } c, err := ioutil.ReadFile(wd + "/mnt/linked") if string(c) != content { t.Errorf("content mismatch got %q want %q", string(c), content) } } func TestUnionFsTruncate(t *testing.T) { wd, clean := setupUfs(t) defer clean() WriteFile(t, wd+"/ro/file", "hello") setRecursiveWritable(t, wd+"/ro", false) os.Truncate(wd+"/mnt/file", 2) content := readFromFile(t, wd+"/mnt/file") if content != "he" { t.Errorf("unexpected content %v", content) } content2 := readFromFile(t, wd+"/rw/file") if content2 != content { t.Errorf("unexpected rw content %v", content2) } } func TestUnionFsCopyChmod(t *testing.T) { wd, clean := setupUfs(t) defer clean() contents := "hello" fn := wd + "/mnt/y" err := ioutil.WriteFile(fn, []byte(contents), 0644) if err != nil { t.Fatalf("WriteFile(%v): %v", fn, err) } err = os.Chmod(fn, 0755) if err != nil { t.Fatalf("Chmod(%v): %v", fn, err) } fi, err := os.Lstat(fn) if err != nil { t.Fatalf("Lstat(%v): %v", fn, err) } if fi.Mode()&0111 == 0 { t.Errorf("Lstat(%v): got mode %o, want some +x bit", fn, fi.Mode()) } time.Sleep(entryTTL) fi, err = os.Lstat(fn) if err != nil { t.Fatalf("Lstat(%v) after sleep: %v", fn, err) } if fi.Mode()&0111 == 0 { t.Errorf("Lstat(%v) after sleep: mode %o", fn, fi.Mode()) } } func abs(dt int64) int64 { if dt >= 0 { return dt } return -dt } func TestUnionFsTruncateTimestamp(t *testing.T) { wd, clean := setupUfs(t) defer clean() contents := "hello" fn := wd + "/mnt/y" err := ioutil.WriteFile(fn, []byte(contents), 0644) if err != nil { t.Fatalf("WriteFile(%v): %v", fn, err) } time.Sleep(200 * time.Millisecond) truncTs := time.Now() err = os.Truncate(fn, 3) if err != nil { t.Fatalf("Truncate(%v): %v", fn, err) } fi, err := os.Lstat(fn) if err != nil { t.Fatalf("Lstat(%v): %v", fn, err) } if truncTs.Sub(fi.ModTime()) > 100*time.Millisecond { t.Errorf("after Truncate: got TS %v, want %v", fi.ModTime(), truncTs) } } func TestUnionFsRemoveAll(t *testing.T) { wd, clean := setupUfs(t) defer clean() err := os.MkdirAll(wd+"/ro/dir/subdir", 0755) if err != nil { t.Fatalf("MkdirAll: %v", err) } contents := "hello" fn := wd + "/ro/dir/subdir/y" err = ioutil.WriteFile(fn, []byte(contents), 0644) if err != nil { t.Fatalf("WriteFile: %v", err) } setRecursiveWritable(t, wd+"/ro", false) err = os.RemoveAll(wd + "/mnt/dir") if err != nil { t.Error("Should delete all") } for _, f := range []string{"dir/subdir/y", "dir/subdir", "dir"} { if fi, _ := os.Lstat(filepath.Join(wd, "mount", f)); fi != nil { t.Errorf("file %s should have disappeared: %v", f, fi) } } names, err := Readdirnames(wd + "/rw/DELETIONS") if err != nil { t.Fatalf("Readdirnames: %v", err) } if len(names) != 3 { t.Fatal("unexpected names", names) } } func ProgramVersion(bin string) (major, minor int64, err error) { cmd := exec.Command(bin, "--version") buf := &bytes.Buffer{} cmd.Stdout = buf if err := cmd.Run(); err != nil { return 0, 0, err } lines := strings.Split(buf.String(), "\n") if len(lines) < 1 { return 0, 0, fmt.Errorf("no output") } matches := regexp.MustCompile(".* ([0-9]+)\\.([0-9]+)").FindStringSubmatch(lines[0]) if matches == nil { return 0, 0, fmt.Errorf("no match for %q", lines[0]) } major, err = strconv.ParseInt(matches[1], 10, 64) if err != nil { return 0, 0, err } minor, err = strconv.ParseInt(matches[2], 10, 64) if err != nil { return 0, 0, err } return major, minor, nil } func TestUnionFsRmRf(t *testing.T) { wd, clean := setupUfs(t) defer clean() err := os.MkdirAll(wd+"/ro/dir/subdir", 0755) if err != nil { t.Fatalf("MkdirAll: %v", err) } contents := "hello" fn := wd + "/ro/dir/subdir/y" err = ioutil.WriteFile(fn, []byte(contents), 0644) if err != nil { t.Fatalf("WriteFile: %v", err) } setRecursiveWritable(t, wd+"/ro", false) bin, err := exec.LookPath("rm") if err != nil { t.Fatalf("LookPath: %v", err) } maj, min, err := ProgramVersion(bin) if err != nil { t.Logf("ProgramVersion: %v", err) } if maj < 8 { // assuming GNU coreutils. t.Skipf("Skipping test; GNU rm %d.%d is not POSIX compliant.", maj, min) } names, _ := Readdirnames(wd + "/mnt/dir") t.Logf("Contents of %s/mnt/dir: %s", wd, strings.Join(names, ", ")) cmd := exec.Command(bin, "-rf", wd+"/mnt/dir") err = cmd.Run() if err != nil { t.Fatal("rm -rf returned error:", err) } for _, f := range []string{"dir/subdir/y", "dir/subdir", "dir"} { if fi, _ := os.Lstat(filepath.Join(wd, "mount", f)); fi != nil { t.Errorf("file %s should have disappeared: %v", f, fi) } } names, err = Readdirnames(wd + "/rw/DELETIONS") if err != nil { t.Fatalf("Readdirnames: %v", err) } if len(names) != 3 { t.Fatal("unexpected names", names) } } func Readdirnames(dir string) ([]string, error) { f, err := os.Open(dir) if err != nil { return nil, err } defer f.Close() return f.Readdirnames(-1) } func TestUnionFsDropDeletionCache(t *testing.T) { wd, clean := setupUfs(t) defer clean() err := ioutil.WriteFile(wd+"/ro/file", []byte("bla"), 0644) if err != nil { t.Fatalf("WriteFile: %v", err) } setRecursiveWritable(t, wd+"/ro", false) _, err = os.Lstat(wd + "/mnt/file") if err != nil { t.Fatalf("Lstat: %v", err) } err = os.Remove(wd + "/mnt/file") if err != nil { t.Fatalf("Remove: %v", err) } fi, _ := os.Lstat(wd + "/mnt/file") if fi != nil { t.Fatal("Lstat() should have failed", fi) } names, err := Readdirnames(wd + "/rw/DELETIONS") if err != nil { t.Fatalf("Readdirnames: %v", err) } if len(names) != 1 { t.Fatal("unexpected names", names) } os.Remove(wd + "/rw/DELETIONS/" + names[0]) fi, _ = os.Lstat(wd + "/mnt/file") if fi != nil { t.Fatal("Lstat() should have failed", fi) } // Expire kernel entry. time.Sleep((6 * entryTTL) / 10) err = ioutil.WriteFile(wd+"/mnt/.drop_cache", []byte(""), 0644) if err != nil { t.Fatalf("WriteFile: %v", err) } _, err = os.Lstat(wd + "/mnt/file") if err != nil { t.Fatal("Lstat() should have succeeded", err) } } func TestUnionFsDropCache(t *testing.T) { wd, clean := setupUfs(t) defer clean() err := ioutil.WriteFile(wd+"/ro/file", []byte("bla"), 0644) if err != nil { t.Fatalf("WriteFile: %v", err) } _, err = os.Lstat(wd + "/mnt/.drop_cache") if err != nil { t.Fatalf("Lstat: %v", err) } names, err := Readdirnames(wd + "/mnt") if err != nil { t.Fatalf("Readdirnames: %v", err) } if len(names) != 1 || names[0] != "file" { t.Fatal("unexpected names", names) } err = ioutil.WriteFile(wd+"/ro/file2", []byte("blabla"), 0644) names2, err := Readdirnames(wd + "/mnt") if err != nil { t.Fatalf("Readdirnames: %v", err) } if len(names2) != len(names) { t.Fatal("mismatch", names2) } err = ioutil.WriteFile(wd+"/mnt/.drop_cache", []byte("does not matter"), 0644) if err != nil { t.Fatalf("WriteFile: %v", err) } names2, err = Readdirnames(wd + "/mnt") if len(names2) != 2 { t.Fatal("mismatch 2", names2) } } type disappearingFS struct { pathfs.FileSystem normal pathfs.FileSystem nop pathfs.FileSystem visible bool visibleChan chan bool } func (d *disappearingFS) fs() pathfs.FileSystem { select { case v := <-d.visibleChan: d.visible = v if v { d.FileSystem = d.normal } else { d.FileSystem = d.nop } default: } return d.FileSystem } func (d *disappearingFS) GetAttr(name string, context *fuse.Context) (a *fuse.Attr, s fuse.Status) { return d.fs().GetAttr(name, context) } func (d *disappearingFS) OpenDir(name string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) { return d.fs().OpenDir(name, context) } func newDisappearingFS(fs, nop pathfs.FileSystem) *disappearingFS { return &disappearingFS{ visibleChan: make(chan bool, 1), visible: true, normal: fs, nop: nop, FileSystem: fs, } } func TestUnionFsDisappearing(t *testing.T) { // This init is like setupUfs, but we want access to the // writable Fs. wd := testutil.TempDir() defer os.RemoveAll(wd) err := os.Mkdir(wd+"/mnt", 0700) if err != nil { t.Fatalf("Mkdir: %v", err) } err = os.Mkdir(wd+"/rw", 0700) if err != nil { t.Fatalf("Mkdir: %v", err) } os.Mkdir(wd+"/ro", 0700) if err != nil { t.Fatalf("Mkdir: %v", err) } wrFs := newDisappearingFS(pathfs.NewLoopbackFileSystem(wd+"/rw"), pathfs.NewLoopbackFileSystem("/dev/null")) var fses []pathfs.FileSystem fses = append(fses, pathfs.NewLockingFileSystem(wrFs)) fses = append(fses, pathfs.NewLoopbackFileSystem(wd+"/ro")) ufs, err := NewUnionFs(fses, testOpts) if err != nil { t.Fatalf("NewUnionFs: %v", err) } opts := &nodefs.Options{ EntryTimeout: entryTTL, AttrTimeout: entryTTL, NegativeTimeout: entryTTL, Debug: testutil.VerboseTest(), LookupKnownChildren: true, } nfs := pathfs.NewPathNodeFs(ufs, nil) state, _, err := nodefs.MountRoot(wd+"/mnt", nfs.Root(), opts) if err != nil { t.Fatalf("MountNodeFileSystem: %v", err) } defer state.Unmount() go state.Serve() state.WaitMount() err = ioutil.WriteFile(wd+"/ro/file", []byte("blabla"), 0644) if err != nil { t.Fatalf("WriteFile: %v", err) } setRecursiveWritable(t, wd+"/ro", false) err = os.Remove(wd + "/mnt/file") if err != nil { t.Fatalf("Remove: %v", err) } wrFs.visibleChan <- false time.Sleep((3 * entryTTL) / 2) _, err = ioutil.ReadDir(wd + "/mnt") if err == nil { t.Fatal("Readdir should have failed") } err = ioutil.WriteFile(wd+"/mnt/file2", []byte("blabla"), 0644) if err == nil { t.Fatal("write should have failed") } // Wait for the caches to purge, and then restore. time.Sleep((3 * entryTTL) / 2) wrFs.visibleChan <- true _, err = ioutil.ReadDir(wd + "/mnt") if err != nil { t.Fatal("Readdir should succeed", err) } err = ioutil.WriteFile(wd+"/mnt/file2", []byte("blabla"), 0644) if err != nil { t.Fatal("write should succeed", err) } } func TestUnionFsDeletedGetAttr(t *testing.T) { wd, clean := setupUfs(t) defer clean() err := ioutil.WriteFile(wd+"/ro/file", []byte("blabla"), 0644) if err != nil { t.Fatalf("WriteFile: %v", err) } setRecursiveWritable(t, wd+"/ro", false) f, err := os.Open(wd + "/mnt/file") if err != nil { t.Fatalf("Open: %v", err) } defer f.Close() err = os.Remove(wd + "/mnt/file") if err != nil { t.Fatalf("Remove: %v", err) } if fi, err := f.Stat(); err != nil || fi.Mode()&os.ModeType != 0 { t.Fatalf("stat returned error or non-file: %v %v", err, fi) } } func TestUnionFsDoubleOpen(t *testing.T) { wd, clean := setupUfs(t) defer clean() err := ioutil.WriteFile(wd+"/ro/file", []byte("blablabla"), 0644) if err != nil { t.Fatalf("WriteFile: %v", err) } setRecursiveWritable(t, wd+"/ro", false) roFile, err := os.Open(wd + "/mnt/file") if err != nil { t.Fatalf("Open: %v", err) } defer roFile.Close() rwFile, err := os.OpenFile(wd+"/mnt/file", os.O_WRONLY|os.O_TRUNC, 0666) if err != nil { t.Fatalf("OpenFile: %v", err) } defer rwFile.Close() output, err := ioutil.ReadAll(roFile) if err != nil { t.Fatalf("ReadAll: %v", err) } if len(output) != 0 { t.Errorf("After r/w truncation, r/o file should be empty too: %q", string(output)) } want := "hello" _, err = rwFile.Write([]byte(want)) if err != nil { t.Fatalf("Write: %v", err) } b := make([]byte, 100) roFile.Seek(0, 0) n, err := roFile.Read(b) if err != nil { t.Fatalf("Read: %v", err) } b = b[:n] if string(b) != "hello" { t.Errorf("r/w and r/o file are not synchronized: got %q want %q", string(b), want) } } func TestUnionFsStatFs(t *testing.T) { wd, clean := setupUfs(t) defer clean() s1 := syscall.Statfs_t{} err := syscall.Statfs(wd+"/mnt", &s1) if err != nil { t.Fatal("statfs mnt", err) } if s1.Bsize == 0 { t.Fatal("Expect blocksize > 0") } } func TestUnionFsFlushSize(t *testing.T) { wd, clean := setupUfs(t) defer clean() fn := wd + "/mnt/file" f, err := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE, 0644) if err != nil { t.Fatalf("OpenFile: %v", err) } fi, err := f.Stat() if err != nil { t.Fatalf("Stat: %v", err) } n, err := f.Write([]byte("hello")) if err != nil { t.Fatalf("Write: %v", err) } f.Close() fi, err = os.Lstat(fn) if err != nil { t.Fatalf("Lstat: %v", err) } if fi.Size() != int64(n) { t.Errorf("got %d from Stat().Size, want %d", fi.Size(), n) } } func TestUnionFsFlushRename(t *testing.T) { wd, clean := setupUfs(t) defer clean() err := ioutil.WriteFile(wd+"/mnt/file", []byte("x"), 0644) fn := wd + "/mnt/tmp" f, err := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE, 0644) if err != nil { t.Fatalf("OpenFile: %v", err) } fi, err := f.Stat() if err != nil { t.Fatalf("Stat: %v", err) } n, err := f.Write([]byte("hello")) if err != nil { t.Fatalf("Write: %v", err) } f.Close() dst := wd + "/mnt/file" err = os.Rename(fn, dst) if err != nil { t.Fatalf("Rename: %v", err) } fi, err = os.Lstat(dst) if err != nil { t.Fatalf("Lstat: %v", err) } if fi.Size() != int64(n) { t.Errorf("got %d from Stat().Size, want %d", fi.Size(), n) } } func TestUnionFsTruncGetAttr(t *testing.T) { wd, clean := setupUfs(t) defer clean() c := []byte("hello") f, err := os.OpenFile(wd+"/mnt/file", os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644) if err != nil { t.Fatalf("OpenFile: %v", err) } _, err = f.Write(c) if err != nil { t.Fatalf("Write: %v", err) } err = f.Close() if err != nil { t.Fatalf("Close: %v", err) } fi, err := os.Lstat(wd + "/mnt/file") if fi.Size() != int64(len(c)) { t.Fatalf("Length mismatch got %d want %d", fi.Size(), len(c)) } } func TestUnionFsPromoteDirTimeStamp(t *testing.T) { wd, clean := setupUfs(t) defer clean() err := os.Mkdir(wd+"/ro/subdir", 0750) if err != nil { t.Fatalf("Mkdir: %v", err) } err = ioutil.WriteFile(wd+"/ro/subdir/file", []byte("hello"), 0644) if err != nil { t.Fatalf("WriteFile: %v", err) } setRecursiveWritable(t, wd+"/ro", false) err = os.Chmod(wd+"/mnt/subdir/file", 0060) if err != nil { t.Fatalf("Chmod: %v", err) } fRo, err := os.Lstat(wd + "/ro/subdir") if err != nil { t.Fatalf("Lstat: %v", err) } fRw, err := os.Lstat(wd + "/rw/subdir") if err != nil { t.Fatalf("Lstat: %v", err) } // TODO - need to update timestamps after promoteDirsTo calls, // not during. if false && fRo.ModTime().Equal(fRw.ModTime()) { t.Errorf("Changed timestamps on promoted subdir: ro %v rw %v", fRo.ModTime(), fRw.ModTime()) } if fRo.Mode().Perm()|0200 != fRw.Mode().Perm() { t.Errorf("Changed mode ro: %v, rw: %v", fRo.Mode(), fRw.Mode()) } } func TestUnionFsCheckHiddenFiles(t *testing.T) { wd, clean := setupUfs(t) defer clean() err := ioutil.WriteFile(wd+"/ro/hidden", []byte("bla"), 0644) if err != nil { t.Fatalf("WriteFile: %v", err) } err = ioutil.WriteFile(wd+"/ro/not_hidden", []byte("bla"), 0644) if err != nil { t.Fatalf("WriteFile: %v", err) } setRecursiveWritable(t, wd+"/ro", false) fi, _ := os.Lstat(wd + "/mnt/hidden") if fi != nil { t.Fatal("Lstat() should have failed", fi) } _, err = os.Lstat(wd + "/mnt/not_hidden") if err != nil { t.Fatalf("Lstat: %v", err) } names, err := Readdirnames(wd + "/mnt") if err != nil { t.Fatalf("Readdirnames: %v", err) } if len(names) != 1 || names[0] != "not_hidden" { t.Fatal("unexpected names", names) } } func TestUnionFSBarf(t *testing.T) { wd, clean := setupUfs(t) defer clean() if err := os.Mkdir(wd+"/mnt/dir", 0755); err != nil { t.Fatalf("os.Mkdir: %v", err) } if err := os.Mkdir(wd+"/mnt/dir2", 0755); err != nil { t.Fatalf("os.Mkdir: %v", err) } if err := ioutil.WriteFile(wd+"/rw/dir/file", []byte("bla"), 0644); err != nil { t.Fatalf("WriteFile: %v", err) } if _, err := os.Lstat(wd + "/mnt/dir/file"); err != nil { t.Fatalf("Lstat: %v", err) } if err := os.Rename(wd+"/rw/dir/file", wd+"/rw/file"); err != nil { t.Fatalf("os.Rename: %v", err) } if err := os.Rename(wd+"/mnt/file", wd+"/mnt/dir2/file"); err != nil { t.Fatalf("os.Rename: %v", err) } } go-fuse-2.0.3/unionfs/unionfs_xattr_test.go000066400000000000000000000062251364171671200210500ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package unionfs import ( "os" "sync/atomic" "testing" "time" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/pathfs" "github.com/hanwen/go-fuse/v2/internal/testutil" ) type TestFS struct { pathfs.FileSystem xattrRead int64 } func (fs *TestFS) GetAttr(path string, context *fuse.Context) (*fuse.Attr, fuse.Status) { switch path { case "": return &fuse.Attr{Mode: fuse.S_IFDIR | 0755}, fuse.OK case "file": return &fuse.Attr{Mode: fuse.S_IFREG | 0755}, fuse.OK } return nil, fuse.ENOENT } func (fs *TestFS) GetXAttr(path string, name string, context *fuse.Context) ([]byte, fuse.Status) { if path == "file" && name == "user.attr" { atomic.AddInt64(&fs.xattrRead, 1) return []byte{42}, fuse.OK } return nil, fuse.ENOATTR } func TestXAttrCaching(t *testing.T) { wd := testutil.TempDir() defer os.RemoveAll(wd) os.Mkdir(wd+"/mnt", 0700) err := os.Mkdir(wd+"/rw", 0700) if err != nil { t.Fatalf("Mkdir failed: %v", err) } rwFS := pathfs.NewLoopbackFileSystem(wd + "/rw") roFS := &TestFS{ FileSystem: pathfs.NewDefaultFileSystem(), } ufs, err := NewUnionFs([]pathfs.FileSystem{rwFS, NewCachingFileSystem(roFS, entryTTL)}, testOpts) if err != nil { t.Fatalf("NewUnionFs: %v", err) } opts := &nodefs.Options{ EntryTimeout: entryTTL / 2, AttrTimeout: entryTTL / 2, NegativeTimeout: entryTTL / 2, Debug: testutil.VerboseTest(), LookupKnownChildren: true, } pathfs := pathfs.NewPathNodeFs(ufs, &pathfs.PathNodeFsOptions{ClientInodes: true, Debug: testutil.VerboseTest()}) server, _, err := nodefs.MountRoot(wd+"/mnt", pathfs.Root(), opts) if err != nil { t.Fatalf("MountNodeFileSystem failed: %v", err) } defer server.Unmount() go server.Serve() server.WaitMount() start := time.Now() if fi, err := os.Lstat(wd + "/mnt"); err != nil || !fi.IsDir() { t.Fatalf("root not readable: %v, %v", err, fi) } buf := make([]byte, 1024) n, err := Getxattr(wd+"/mnt/file", "user.attr", buf) if err != nil { t.Fatalf("Getxattr: %v", err) } want := "\x2a" got := string(buf[:n]) if got != want { t.Fatalf("Got %q want %q", got, err) } time.Sleep(entryTTL / 3) n, err = Getxattr(wd+"/mnt/file", "user.attr", buf) if err != nil { t.Fatalf("Getxattr: %v", err) } got = string(buf[:n]) if got != want { t.Fatalf("Got %q want %q", got, err) } time.Sleep(entryTTL / 3) // Make sure that an interceding Getxattr() to a filesystem that doesn't implement GetXAttr() doesn't affect future calls. Getxattr(wd, "whatever", buf) n, err = Getxattr(wd+"/mnt/file", "user.attr", buf) if err != nil { t.Fatalf("Getxattr: %v", err) } got = string(buf[:n]) if got != want { t.Fatalf("Got %q want %q", got, err) } if time.Now().Sub(start) >= entryTTL { // If we run really slowly, this test will spuriously // fail. t.Skip("test took too long.") } actual := atomic.LoadInt64(&roFS.xattrRead) if actual != 1 { t.Errorf("got xattrRead=%d, want 1", actual) } } go-fuse-2.0.3/unionfs/unionfs_xattr_test_darwin.go000066400000000000000000000016461364171671200224160ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package unionfs import ( "syscall" "unsafe" ) // Darwin doesn't have support for syscall.Getxattr() so we pull it into its own file and implement it by hand on Darwin. func Getxattr(path string, attr string, dest []byte) (sz int, err error) { var _p0 *byte _p0, err = syscall.BytePtrFromString(path) if err != nil { return } var _p1 *byte _p1, err = syscall.BytePtrFromString(attr) if err != nil { return } var _p2 unsafe.Pointer if len(dest) > 0 { _p2 = unsafe.Pointer(&dest[0]) } else { var _zero uintptr _p2 = unsafe.Pointer(&_zero) } r0, _, e1 := syscall.Syscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(dest)), 0, 0) sz = int(r0) if e1 != 0 { err = e1 } return } go-fuse-2.0.3/unionfs/unionfs_xattr_test_linux.go000066400000000000000000000006771364171671200222740ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package unionfs import ( "syscall" ) // Darwin doesn't have support for syscall.Getxattr() so we pull it into its own file and implement it by hand on Darwin. func Getxattr(path string, attr string, dest []byte) (sz int, err error) { return syscall.Getxattr(path, attr, dest) } go-fuse-2.0.3/zipfs/000077500000000000000000000000001364171671200142245ustar00rootroot00000000000000go-fuse-2.0.3/zipfs/multizip.go000066400000000000000000000036321364171671200164340ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package zipfs /* This provides a practical example of mounting Go-fuse path filesystems on top of each other. It is a file system that configures a Zip filesystem at /zipmount when symlinking path/to/zipfile to /config/zipmount */ import ( "context" "log" "syscall" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" ) // MultiZipFs is a filesystem that mounts zipfiles. type MultiZipFs struct { fs.Inode } func (root *MultiZipFs) OnAdd(ctx context.Context) { n := root.NewPersistentInode(ctx, &configRoot{}, fs.StableAttr{Mode: syscall.S_IFDIR}) root.AddChild("config", n, false) } type configRoot struct { fs.Inode } var _ = (fs.NodeUnlinker)((*configRoot)(nil)) var _ = (fs.NodeSymlinker)((*configRoot)(nil)) func (r *configRoot) Unlink(ctx context.Context, basename string) syscall.Errno { if r.GetChild(basename) == nil { return syscall.ENOENT } // XXX RmChild should return Inode? _, parent := r.Parent() ch := parent.GetChild(basename) if ch == nil { return syscall.ENOENT } success, _ := parent.RmChild(basename) if !success { return syscall.EIO } ch.RmAllChildren() parent.RmChild(basename) parent.NotifyEntry(basename) return 0 } func (r *configRoot) Symlink(ctx context.Context, target string, base string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) { root, err := NewArchiveFileSystem(target) if err != nil { log.Println("NewZipArchiveFileSystem failed.", err) return nil, syscall.EINVAL } _, parent := r.Parent() ch := r.NewPersistentInode(ctx, root, fs.StableAttr{Mode: syscall.S_IFDIR}) parent.AddChild(base, ch, false) link := r.NewPersistentInode(ctx, &fs.MemSymlink{ Data: []byte(target), }, fs.StableAttr{Mode: syscall.S_IFLNK}) r.AddChild(base, link, false) return link, 0 } go-fuse-2.0.3/zipfs/multizip_test.go000066400000000000000000000062251364171671200174740ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package zipfs import ( "io/ioutil" "os" "testing" "time" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/internal/testutil" ) const testTtl = 100 * time.Millisecond func setupMzfs(t *testing.T) (mountPoint string, state *fuse.Server, cleanup func()) { root := &MultiZipFs{} mountPoint = testutil.TempDir() dt := testTtl opts := &fs.Options{ EntryTimeout: &dt, AttrTimeout: &dt, } opts.Debug = testutil.VerboseTest() server, err := fs.Mount(mountPoint, root, opts) if err != nil { t.Fatalf("MountNodeFileSystem failed: %v", err) } return mountPoint, server, func() { server.Unmount() os.RemoveAll(mountPoint) } } func TestMultiZipReadonly(t *testing.T) { mountPoint, _, cleanup := setupMzfs(t) defer cleanup() _, err := os.Create(mountPoint + "/random") if err == nil { t.Error("Must fail writing in root.") } _, err = os.OpenFile(mountPoint+"/config/zipmount", os.O_WRONLY, 0) if err == nil { t.Error("Must fail without O_CREATE") } } func TestMultiZipFs(t *testing.T) { mountPoint, server, cleanup := setupMzfs(t) defer cleanup() zipFile := testZipFile() entries, err := ioutil.ReadDir(mountPoint) if err != nil { t.Fatalf("ReadDir failed: %v", err) } if len(entries) != 1 || string(entries[0].Name()) != "config" { t.Errorf("wrong names return. %v", entries) } err = os.Symlink(zipFile, mountPoint+"/config/zipmount") if err != nil { t.Fatalf("Symlink failed: %v", err) } fi, err := os.Lstat(mountPoint + "/zipmount") if !fi.IsDir() { t.Errorf("Expect directory at /zipmount") } entries, err = ioutil.ReadDir(mountPoint) if err != nil { t.Fatalf("ReadDir failed: %v", err) } if len(entries) != 2 { t.Error("Expect 2 entries", entries) } val, err := os.Readlink(mountPoint + "/config/zipmount") if err != nil { t.Fatalf("Readlink failed: %v", err) } if val != zipFile { t.Errorf("expected %v got %v", zipFile, val) } fi, err = os.Lstat(mountPoint + "/zipmount") if err != nil { t.Fatalf("Lstat failed: %v", err) } if !fi.IsDir() { t.Fatalf("expect directory for /zipmount, got %v", fi) } // Check that zipfs itself works. fi, err = os.Stat(mountPoint + "/zipmount/subdir") if err != nil { t.Fatalf("Stat failed: %v", err) } if !fi.IsDir() { t.Error("directory type", fi) } // Removing the config dir unmount err = os.Remove(mountPoint + "/config/zipmount") if err != nil { t.Fatalf("Remove failed: %v", err) } // If FUSE supports invalid inode notifications we expect this node to be gone. Otherwise we'll just make sure that it's not reachable. if server.KernelSettings().SupportsNotify(fuse.NOTIFY_INVAL_INODE) { fi, err = os.Stat(mountPoint + "/zipmount") if err == nil { t.Errorf("stat should fail after unmount, got %#v", fi) } } else { entries, err = ioutil.ReadDir(mountPoint) if err != nil { t.Fatalf("ReadDir failed: %v", err) } for _, e := range entries { if e.Name() == "zipmount" { t.Error("Should not have entry: ", e) } } } } go-fuse-2.0.3/zipfs/tarfs.go000066400000000000000000000074001364171671200156730ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package zipfs import ( "archive/tar" "bytes" "compress/bzip2" "compress/gzip" "context" "io" "log" "os" "path/filepath" "strings" "syscall" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" ) // TODO - handle symlinks. // HeaderToFileInfo fills a fuse.Attr struct from a tar.Header. func HeaderToFileInfo(out *fuse.Attr, h *tar.Header) { out.Mode = uint32(h.Mode) out.Size = uint64(h.Size) out.Uid = uint32(h.Uid) out.Gid = uint32(h.Gid) out.SetTimes(&h.AccessTime, &h.ModTime, &h.ChangeTime) } type tarRoot struct { fs.Inode rc io.ReadCloser } // tarRoot implements NodeOnAdder var _ = (fs.NodeOnAdder)((*tarRoot)(nil)) func (r *tarRoot) OnAdd(ctx context.Context) { tr := tar.NewReader(r.rc) defer r.rc.Close() var longName *string for { hdr, err := tr.Next() if err == io.EOF { // end of tar archive break } if err != nil { log.Printf("Add: %v", err) // XXX handle error break } if hdr.Typeflag == 'L' { buf := bytes.NewBuffer(make([]byte, 0, hdr.Size)) io.Copy(buf, tr) s := buf.String() longName = &s continue } if longName != nil { hdr.Name = *longName longName = nil } buf := bytes.NewBuffer(make([]byte, 0, hdr.Size)) io.Copy(buf, tr) dir, base := filepath.Split(filepath.Clean(hdr.Name)) p := r.EmbeddedInode() for _, comp := range strings.Split(dir, "/") { if len(comp) == 0 { continue } ch := p.GetChild(comp) if ch == nil { ch = p.NewPersistentInode(ctx, &fs.Inode{}, fs.StableAttr{Mode: syscall.S_IFDIR}) p.AddChild(comp, ch, false) } p = ch } var attr fuse.Attr HeaderToFileInfo(&attr, hdr) switch hdr.Typeflag { case tar.TypeSymlink: l := &fs.MemSymlink{ Data: []byte(hdr.Linkname), } l.Attr = attr p.AddChild(base, r.NewPersistentInode(ctx, l, fs.StableAttr{Mode: syscall.S_IFLNK}), false) case tar.TypeLink: log.Println("don't know how to handle Typelink") case tar.TypeChar: rf := &fs.MemRegularFile{} rf.Attr = attr p.AddChild(base, r.NewPersistentInode(ctx, rf, fs.StableAttr{Mode: syscall.S_IFCHR}), false) case tar.TypeBlock: rf := &fs.MemRegularFile{} rf.Attr = attr p.AddChild(base, r.NewPersistentInode(ctx, rf, fs.StableAttr{Mode: syscall.S_IFBLK}), false) case tar.TypeDir: rf := &fs.MemRegularFile{} rf.Attr = attr p.AddChild(base, r.NewPersistentInode(ctx, rf, fs.StableAttr{Mode: syscall.S_IFDIR}), false) case tar.TypeFifo: rf := &fs.MemRegularFile{} rf.Attr = attr p.AddChild(base, r.NewPersistentInode(ctx, rf, fs.StableAttr{Mode: syscall.S_IFIFO}), false) case tar.TypeReg, tar.TypeRegA: df := &fs.MemRegularFile{ Data: buf.Bytes(), } df.Attr = attr p.AddChild(base, r.NewPersistentInode(ctx, df, fs.StableAttr{}), false) default: log.Printf("entry %q: unsupported type '%c'", hdr.Name, hdr.Typeflag) } } } type readCloser struct { io.Reader close func() error } func (rc *readCloser) Close() error { return rc.close() } // NewTarCompressedTree creates the tree of a tar file as a FUSE // InodeEmbedder. The inode can either be mounted as the root of a // FUSE mount, or added as a child to some other FUSE tree. func NewTarCompressedTree(name string, format string) (fs.InodeEmbedder, error) { f, err := os.Open(name) if err != nil { return nil, err } var stream io.ReadCloser switch format { case "gz": unzip, err := gzip.NewReader(f) if err != nil { return nil, err } stream = &readCloser{ unzip, f.Close, } case "bz2": unzip := bzip2.NewReader(f) stream = &readCloser{ unzip, f.Close, } } return &tarRoot{rc: stream}, nil } go-fuse-2.0.3/zipfs/tarfs_test.go000066400000000000000000000044071364171671200167360ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package zipfs import ( "archive/tar" "bytes" "io" "io/ioutil" "os" "path/filepath" "strings" "syscall" "testing" "time" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/internal/testutil" ) var tarContents = map[string]string{ "emptydir/": "", "file.txt": "content", "dir/subfile.txt": "other content", } type addClose struct { io.Reader } func (c *addClose) Close() error { return nil } func TestTar(t *testing.T) { buf := &bytes.Buffer{} w := tar.NewWriter(buf) now := time.Now() for k, v := range tarContents { h := &tar.Header{ Name: k, Size: int64(len(v)), Mode: 0464, Uid: 42, Gid: 42, ModTime: now, } isLink := filepath.Base(k) == "link" isDir := strings.HasSuffix(k, "/") if isLink { h.Typeflag = tar.TypeSymlink h.Linkname = v } else if isDir { h.Typeflag = tar.TypeDir } w.WriteHeader(h) if !isLink && !isDir { w.Write([]byte(v)) } } w.Close() root := &tarRoot{rc: &addClose{buf}} mnt := testutil.TempDir() defer os.Remove(mnt) opts := &fs.Options{} opts.Debug = testutil.VerboseTest() s, err := fs.Mount(mnt, root, opts) if err != nil { t.Errorf("Mount: %v", err) } defer s.Unmount() for k, want := range tarContents { p := filepath.Join(mnt, k) var st syscall.Stat_t if err := syscall.Lstat(p, &st); err != nil { t.Fatalf("Stat %q: %v", p, err) } if filepath.Base(k) == "link" { got, err := os.Readlink(p) if err != nil { t.Fatalf("Readlink: %v", err) } if got != want { t.Errorf("Readlink: got %q want %q", got, want) } } else if strings.HasSuffix(k, "/") { if got, want := st.Mode, uint32(syscall.S_IFDIR|0464); got != want { t.Errorf("dir %q: got mode %o, want %o", k, got, want) } } else { if got, want := st.Mode, uint32(syscall.S_IFREG|0464); got != want { t.Errorf("entry %q, got mode %o, want %o", k, got, want) } c, err := ioutil.ReadFile(p) if err != nil { t.Errorf("read %q: %v", k, err) got := string(c) if got != want { t.Errorf("file %q: got %q, want %q", k, got, want) } } } } } go-fuse-2.0.3/zipfs/test.zip000066400000000000000000000007451364171671200157350ustar00rootroot00000000000000PK gV>subdir/UT cMcMux PK gV>subdir/subfile.txtUT cMcMux hello2 PK gV> 0:6file.txtUT cMcMux hello PK gV>Asubdir/UTcMux PK gV>Asubdir/subfile.txtUTcMux PK gV> 0:6file.txtUTcMux PKgo-fuse-2.0.3/zipfs/zipfs.go000066400000000000000000000067111364171671200157130ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package zipfs import ( "archive/zip" "context" "fmt" "io/ioutil" "os" "path/filepath" "strings" "sync" "syscall" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" ) type zipRoot struct { fs.Inode zr *zip.ReadCloser } var _ = (fs.NodeOnAdder)((*zipRoot)(nil)) func (zr *zipRoot) OnAdd(ctx context.Context) { for _, f := range zr.zr.File { if f.FileInfo().IsDir() { continue } dir, base := filepath.Split(filepath.Clean(f.Name)) p := &zr.Inode for _, component := range strings.Split(dir, "/") { if len(component) == 0 { continue } ch := p.GetChild(component) if ch == nil { ch = p.NewPersistentInode(ctx, &fs.Inode{}, fs.StableAttr{Mode: fuse.S_IFDIR}) p.AddChild(component, ch, true) } p = ch } ch := p.NewPersistentInode(ctx, &zipFile{file: f}, fs.StableAttr{}) p.AddChild(base, ch, true) } } // NewZipTree creates a new file-system for the zip file named name. func NewZipTree(name string) (fs.InodeEmbedder, error) { r, err := zip.OpenReader(name) if err != nil { return nil, err } return &zipRoot{zr: r}, nil } // zipFile is a file read from a zip archive. type zipFile struct { fs.Inode file *zip.File mu sync.Mutex data []byte } var _ = (fs.NodeOpener)((*zipFile)(nil)) var _ = (fs.NodeGetattrer)((*zipFile)(nil)) // Getattr sets the minimum, which is the size. A more full-featured // FS would also set timestamps and permissions. func (zf *zipFile) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno { out.Mode = uint32(zf.file.Mode()) & 07777 out.Nlink = 1 out.Mtime = uint64(zf.file.ModTime().Unix()) out.Atime = out.Mtime out.Ctime = out.Mtime out.Size = zf.file.UncompressedSize64 const bs = 512 out.Blksize = bs out.Blocks = (out.Size + bs - 1) / bs return 0 } // Open lazily unpacks zip data func (zf *zipFile) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint32, syscall.Errno) { zf.mu.Lock() defer zf.mu.Unlock() if zf.data == nil { rc, err := zf.file.Open() if err != nil { return nil, 0, syscall.EIO } content, err := ioutil.ReadAll(rc) if err != nil { return nil, 0, syscall.EIO } zf.data = content } // We don't return a filehandle since we don't really need // one. The file content is immutable, so hint the kernel to // cache the data. return nil, fuse.FOPEN_KEEP_CACHE, 0 } // Read simply returns the data that was already unpacked in the Open call func (zf *zipFile) Read(ctx context.Context, f fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) { end := int(off) + len(dest) if end > len(zf.data) { end = len(zf.data) } return fuse.ReadResultData(zf.data[off:end]), 0 } var _ = (fs.NodeOnAdder)((*zipRoot)(nil)) func NewArchiveFileSystem(name string) (root fs.InodeEmbedder, err error) { switch { case strings.HasSuffix(name, ".zip"): root, err = NewZipTree(name) case strings.HasSuffix(name, ".tar.gz"): root, err = NewTarCompressedTree(name, "gz") case strings.HasSuffix(name, ".tar.bz2"): root, err = NewTarCompressedTree(name, "bz2") case strings.HasSuffix(name, ".tar"): f, err := os.Open(name) if err != nil { return nil, err } root = &tarRoot{rc: f} default: return nil, fmt.Errorf("unknown archive format %q", name) } if err != nil { return nil, err } return root, nil } go-fuse-2.0.3/zipfs/zipfs_test.go000066400000000000000000000046741364171671200167600ustar00rootroot00000000000000// Copyright 2016 the Go-FUSE Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package zipfs import ( "io/ioutil" "os" "path/filepath" "runtime" "syscall" "testing" "time" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/internal/testutil" ) func testZipFile() string { _, file, _, ok := runtime.Caller(0) if !ok { panic("need runtime.Caller()'s file name to discover testdata") } dir, _ := filepath.Split(file) return filepath.Join(dir, "test.zip") } func setupZipfs(t *testing.T) (mountPoint string, cleanup func()) { root, err := NewArchiveFileSystem(testZipFile()) if err != nil { t.Fatalf("NewArchiveFileSystem failed: %v", err) } mountPoint = testutil.TempDir() opts := &fs.Options{} opts.Debug = testutil.VerboseTest() server, err := fs.Mount(mountPoint, root, opts) return mountPoint, func() { server.Unmount() os.RemoveAll(mountPoint) } } func TestZipFs(t *testing.T) { mountPoint, clean := setupZipfs(t) defer clean() entries, err := ioutil.ReadDir(mountPoint) if err != nil { t.Fatalf("ReadDir failed: %v", err) } if len(entries) != 2 { t.Error("wrong length", entries) } fi, err := os.Stat(mountPoint + "/subdir") if err != nil { t.Fatalf("Stat failed: %v", err) } if !fi.IsDir() { t.Error("directory type", fi) } fi, err = os.Stat(mountPoint + "/file.txt") if err != nil { t.Fatalf("Stat failed: %v", err) } if got, want := fi.Mode(), 0664; int(got) != want { t.Fatalf("File mode: got 0%o want 0%o", got, want) } if st := fi.Sys().(*syscall.Stat_t); st.Blocks != 1 { t.Errorf("got block count %d, want 1", st.Blocks) } if want, err := time.Parse(time.RFC3339, "2011-02-22T12:56:12Z"); err != nil { panic(err) } else if !fi.ModTime().Equal(want) { t.Fatalf("File mtime got %v, want %v", fi.ModTime(), want) } if fi.IsDir() { t.Error("file type", fi) } f, err := os.Open(mountPoint + "/file.txt") if err != nil { t.Fatalf("Open failed: %v", err) } b := make([]byte, 1024) n, err := f.Read(b) b = b[:n] if string(b) != "hello\n" { t.Error("content fail", b[:n]) } f.Close() } func TestLinkCount(t *testing.T) { mp, clean := setupZipfs(t) defer clean() fi, err := os.Stat(mp + "/file.txt") if err != nil { t.Fatalf("Stat failed: %v", err) } if fuse.ToStatT(fi).Nlink != 1 { t.Fatal("wrong link count", fuse.ToStatT(fi).Nlink) } }