pax_global_header00006660000000000000000000000064144423201210014503gustar00rootroot0000000000000052 comment=068be28218e76b2ed5a03b16cf897a3c34f47df7 golang-github-anacrolix-fuse-0.2.0/000077500000000000000000000000001444232012100171475ustar00rootroot00000000000000golang-github-anacrolix-fuse-0.2.0/.gitattributes000066400000000000000000000000451444232012100220410ustar00rootroot00000000000000*.go filter=gofmt *.cgo filter=gofmt golang-github-anacrolix-fuse-0.2.0/.gitignore000066400000000000000000000002021444232012100211310ustar00rootroot00000000000000*~ .#* ## the next line needs to start with a backslash to avoid looking like ## a comment \#*# .*.swp *.test /clockfs /hellofs golang-github-anacrolix-fuse-0.2.0/LICENSE000066400000000000000000000101041444232012100201500ustar00rootroot00000000000000Copyright (c) 2013-2019 Tommi Virtanen. Copyright (c) 2009, 2011, 2012 The Go 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 Google Inc. 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. The following included software components have additional copyright notices and license terms that may differ from the above. File fuse.go: // Adapted from Plan 9 from User Space's src/cmd/9pfuse/fuse.c, // which carries this notice: // // The files in this directory are subject to the following license. // // The author of this software is Russ Cox. // // Copyright (c) 2006 Russ Cox // // Permission to use, copy, modify, and distribute this software for any // purpose without fee is hereby granted, provided that this entire notice // is included in all copies of any software which is or includes a copy // or modification of this software and in all copies of the supporting // documentation for such software. // // THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED // WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION OR WARRANTY // OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS SOFTWARE OR ITS // FITNESS FOR ANY PARTICULAR PURPOSE. File fuse_kernel.go: // Derived from FUSE's fuse_kernel.h /* This file defines the kernel interface of FUSE Copyright (C) 2001-2007 Miklos Szeredi This -- and only this -- header file may also be distributed under the terms of the BSD Licence as follows: Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. 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. THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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. */ golang-github-anacrolix-fuse-0.2.0/README.md000066400000000000000000000026641444232012100204360ustar00rootroot00000000000000bazil.org/fuse -- Filesystems in Go =================================== This fork has **support for FUSE on Mac**. MacFUSE 4.0.0 and 3.3 (or newer) are supported. The original project **dropped support** for FUSE on Mac when OSXFUSE stopped being an open source project [bazil/fuse#224](https://github.com/bazil/fuse/issues/224). I respect the maintainers decisions of both projects. In this fork, the following patches to _remove_ support for OSXFUSE have been dropped: * [60eaf8](https://github.com/bazil/fuse/commit/60eaf8f021ce00e5c52529cdcba1067e13c1c2c2) - Remove macOS support * [eca21f](https://github.com/bazil/fuse/commit/eca21f36f00e04957de26b2e64e21544fa0e0372) - Comment cleanup after macOS support removal After forking, a patch to introduce support for macFUSE 4 has been made [#1](https://github.com/zegl/fuse/pull/1). To use this fork in your project: `go get github.com/zegl/fuse` --- `bazil.org/fuse` is a Go library for writing FUSE userspace filesystems. It is a from-scratch implementation of the kernel-userspace communication protocol, and does not use the C library from the project called FUSE. `bazil.org/fuse` embraces Go fully for safety and ease of programming. Here’s how to get going: go get bazil.org/fuse Website: http://bazil.org/fuse/ Github repository: https://github.com/bazil/fuse API docs: http://godoc.org/bazil.org/fuse Our thanks to Russ Cox for his fuse library, which this project is based on. golang-github-anacrolix-fuse-0.2.0/buffer.go000066400000000000000000000013341444232012100207500ustar00rootroot00000000000000package fuse import "unsafe" // buffer provides a mechanism for constructing a message from // multiple segments. type buffer []byte // alloc allocates size bytes and returns a pointer to the new // segment. func (w *buffer) alloc(size uintptr) unsafe.Pointer { s := int(size) if len(*w)+s > cap(*w) { old := *w *w = make([]byte, len(*w), 2*cap(*w)+s) copy(*w, old) } l := len(*w) *w = (*w)[:l+s] return unsafe.Pointer(&(*w)[l]) } // reset clears out the contents of the buffer. func (w *buffer) reset() { for i := range (*w)[:cap(*w)] { (*w)[i] = 0 } *w = (*w)[:0] } func newBuffer(extra uintptr) buffer { const hdrSize = unsafe.Sizeof(outHeader{}) buf := make(buffer, hdrSize, hdrSize+extra) return buf } golang-github-anacrolix-fuse-0.2.0/cmd/000077500000000000000000000000001444232012100177125ustar00rootroot00000000000000golang-github-anacrolix-fuse-0.2.0/cmd/fuse-abort/000077500000000000000000000000001444232012100217615ustar00rootroot00000000000000golang-github-anacrolix-fuse-0.2.0/cmd/fuse-abort/.gitignore000066400000000000000000000000141444232012100237440ustar00rootroot00000000000000/fuse-abort golang-github-anacrolix-fuse-0.2.0/cmd/fuse-abort/internal/000077500000000000000000000000001444232012100235755ustar00rootroot00000000000000golang-github-anacrolix-fuse-0.2.0/cmd/fuse-abort/internal/mountinfo/000077500000000000000000000000001444232012100256135ustar00rootroot00000000000000golang-github-anacrolix-fuse-0.2.0/cmd/fuse-abort/internal/mountinfo/.gitignore000066400000000000000000000002051444232012100276000ustar00rootroot00000000000000/mountinfo-fuzz.zip /testdata/fuzz/crashers/ /testdata/fuzz/suppressions/ /testdata/fuzz/corpus/* !/testdata/fuzz/corpus/*.mountinfo golang-github-anacrolix-fuse-0.2.0/cmd/fuse-abort/internal/mountinfo/fuzz000077500000000000000000000002101444232012100265300ustar00rootroot00000000000000#!/bin/sh set -e go run github.com/dvyukov/go-fuzz/go-fuzz-build exec go run github.com/dvyukov/go-fuzz/go-fuzz -workdir=testdata/fuzz golang-github-anacrolix-fuse-0.2.0/cmd/fuse-abort/internal/mountinfo/fuzz.go000066400000000000000000000002041444232012100271340ustar00rootroot00000000000000// +build gofuzz package mountinfo func Fuzz(data []byte) int { if _, err := parse(data); err != nil { return 0 } return 1 } golang-github-anacrolix-fuse-0.2.0/cmd/fuse-abort/internal/mountinfo/mountinfo.go000066400000000000000000000054601444232012100301650ustar00rootroot00000000000000package mountinfo import ( "bufio" "bytes" "fmt" "io" "os" "strconv" ) const DefaultPath = "/proc/self/mountinfo" func Open(p string) (*Reader, error) { f, err := os.Open(p) if err != nil { return nil, err } rr := &Reader{ closer: f, scanner: bufio.NewScanner(f), } return rr, nil } type Reader struct { closer io.Closer scanner *bufio.Scanner } var _ io.Closer = (*Reader)(nil) func (r *Reader) Close() error { return r.closer.Close() } // unescape backslash-prefixed octal func unescape(in []byte) (string, error) { buf := make([]byte, 0, len(in)) for len(in) > 0 { if in[0] == '\\' { if len(in) < 4 { return "", fmt.Errorf("truncated octal sequence: %q", in[1:]) } octal := string(in[1:4]) in = in[4:] num, err := strconv.ParseUint(octal, 8, 8) if err != nil { return "", err } buf = append(buf, byte(num)) continue } buf = append(buf, in[0]) in = in[1:] } return string(buf), nil } func (r *Reader) Next() (*Mount, error) { if !r.scanner.Scan() { err := r.scanner.Err() if err != nil { return nil, err } return nil, io.EOF } info, err := parse(r.scanner.Bytes()) if err != nil { return nil, err } return info, nil } func parse(line []byte) (*Mount, error) { // https://www.kernel.org/doc/Documentation/filesystems/proc.txt // // mountinfo fields are space-separated, with octal escapes // // id parentId maj:min path_inside mountpoint mount_options [optional fields...] - fstype /dev/sdblah super_block_options // // we care about maj:min, mountpoint, fstype fields := bytes.Split(line, []byte{' '}) const minFields = 7 if len(fields) < minFields { return nil, fmt.Errorf("cannot parse mountinfo entry: %q", line) } majmin := fields[2] idx := bytes.IndexByte(majmin, ':') if idx == -1 { return nil, fmt.Errorf("cannot parse mountinfo entry, weird major:minor: %q", line) } major := string(majmin[:idx]) minor := string(majmin[idx+1:]) mountpoint, err := unescape(fields[4]) if err != nil { return nil, fmt.Errorf("cannot parse mountinfo entry, invalid escape in mountpoint: %q", line) } rest := fields[6:] // skip optional fields for { if len(rest) == 0 { return nil, fmt.Errorf("cannot parse mountinfo entry, no optional field terminator: %q", line) } cur := rest[0] rest = rest[1:] if bytes.Equal(cur, []byte{'-'}) { break } } if len(rest) == 0 { return nil, fmt.Errorf("cannot parse mountinfo entry, no fstype: %q", line) } fstype, err := unescape(rest[0]) if err != nil { return nil, fmt.Errorf("cannot parse mountinfo entry, invalid escape in fstype: %q", line) } i := &Mount{ Major: major, Minor: minor, Mountpoint: mountpoint, FSType: fstype, } return i, nil } type Mount struct { Major string Minor string Mountpoint string FSType string } golang-github-anacrolix-fuse-0.2.0/cmd/fuse-abort/internal/mountinfo/mountinfo_test.go000066400000000000000000000042241444232012100312210ustar00rootroot00000000000000package mountinfo_test import ( "io" "os" "testing" "github.com/anacrolix/fuse/cmd/fuse-abort/internal/mountinfo" ) func TestOpenError(t *testing.T) { r, err := mountinfo.Open("testdata/does-not-exist") if err == nil { r.Close() t.Fatal("expected an error") } if !os.IsNotExist(err) { t.Fatalf("expected a not-exists error: %v", err) } } func TestReal(t *testing.T) { r, err := mountinfo.Open("testdata/fuzz/corpus/real.mountinfo") if err != nil { t.Fatalf("cannot open mountinfo: %v", err) } defer func() { if err := r.Close(); err != nil { t.Errorf("close: %v", err) } }() success := false for { info, err := r.Next() if err == io.EOF { break } if err != nil { t.Fatalf("reading mountinfo: %v", err) } if info.Mountpoint != "/home/tv/tmp/fusetest128387706" { t.Logf("skip %#v\n", info) continue } t.Logf("found %#v\n", info) success = true if g, e := info.Major, "0"; g != e { t.Errorf("wrong major number: %q != %q", g, e) } if g, e := info.Minor, "139"; g != e { t.Errorf("wrong minor number: %q != %q", g, e) } if g, e := info.FSType, "fuse"; g != e { t.Errorf("wrong FS type: %q != %q", g, e) } } if !success { t.Error("did not see mount") } } func TestEscape(t *testing.T) { r, err := mountinfo.Open("testdata/fuzz/corpus/escaped.mountinfo") if err != nil { t.Fatalf("cannot open mountinfo: %v", err) } defer func() { if err := r.Close(); err != nil { t.Errorf("close: %v", err) } }() info, err := r.Next() if err != nil { t.Fatalf("reading mountinfo: %v", err) } t.Logf("got %#v\n", info) if g, e := info.Mountpoint, "/xyz\x53zy"; g != e { t.Errorf("wrong mountpoint: %q != %q", g, e) } if g, e := info.FSType, "foo\x01bar"; g != e { t.Errorf("wrong FS type: %q != %q", g, e) } } func TestCrashers(t *testing.T) { r, err := mountinfo.Open("testdata/fuzz/corpus/crashers.mountinfo") if err != nil { t.Fatalf("cannot open mountinfo: %v", err) } defer func() { if err := r.Close(); err != nil { t.Errorf("close: %v", err) } }() for { _, err := r.Next() if err == io.EOF { break } // we don't care about errors, just that we don't crash } } golang-github-anacrolix-fuse-0.2.0/cmd/fuse-abort/internal/mountinfo/testdata/000077500000000000000000000000001444232012100274245ustar00rootroot00000000000000golang-github-anacrolix-fuse-0.2.0/cmd/fuse-abort/internal/mountinfo/testdata/fuzz/000077500000000000000000000000001444232012100304225ustar00rootroot00000000000000golang-github-anacrolix-fuse-0.2.0/cmd/fuse-abort/internal/mountinfo/testdata/fuzz/corpus/000077500000000000000000000000001444232012100317355ustar00rootroot00000000000000crashers.mountinfo000066400000000000000000000000111444232012100354200ustar00rootroot00000000000000golang-github-anacrolix-fuse-0.2.0/cmd/fuse-abort/internal/mountinfo/testdata/fuzz/corpus : - escaped.mountinfo000066400000000000000000000001621444232012100352210ustar00rootroot00000000000000golang-github-anacrolix-fuse-0.2.0/cmd/fuse-abort/internal/mountinfo/testdata/fuzz/corpus268 48 0:139 / /xyz\123zy rw,nosuid,nodev,relatime shared:94 - foo\001bar /dev/fuse rw,user_id=1000,group_id=1000 real.mountinfo000066400000000000000000000066171444232012100345530ustar00rootroot00000000000000golang-github-anacrolix-fuse-0.2.0/cmd/fuse-abort/internal/mountinfo/testdata/fuzz/corpus19 24 0:18 / /sys rw,nosuid,nodev,noexec,relatime shared:7 - sysfs sysfs rw 20 24 0:4 / /proc rw,nosuid,nodev,noexec,relatime shared:12 - proc proc rw 21 24 0:6 / /dev rw,nosuid,relatime shared:2 - devtmpfs udev rw,size=32673056k,nr_inodes=8168264,mode=755 22 21 0:14 / /dev/pts rw,nosuid,noexec,relatime shared:3 - devpts devpts rw,gid=5,mode=620,ptmxmode=000 23 24 0:19 / /run rw,nosuid,noexec,relatime shared:5 - tmpfs tmpfs rw,size=6538356k,mode=755 24 0 0:20 /@ / rw,relatime shared:1 - btrfs /dev/mapper/sda5_crypt rw,ssd,space_cache,metadata_ratio=4,subvolid=256,subvol=/@ 25 19 0:12 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:8 - securityfs securityfs rw 26 21 0:23 / /dev/shm rw,nosuid,nodev shared:4 - tmpfs tmpfs rw 27 23 0:24 / /run/lock rw,nosuid,nodev,noexec,relatime shared:6 - tmpfs tmpfs rw,size=5120k 28 19 0:25 / /sys/fs/cgroup rw shared:9 - tmpfs tmpfs rw,mode=755 29 28 0:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:10 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd 30 19 0:27 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:11 - pstore pstore rw 31 28 0:28 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:13 - cgroup cgroup rw,blkio 32 28 0:29 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:14 - cgroup cgroup rw,net_cls,net_prio 33 28 0:30 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:15 - cgroup cgroup rw,cpuset,clone_children 34 28 0:31 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:16 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb 35 28 0:32 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:17 - cgroup cgroup rw,freezer 36 28 0:33 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:18 - cgroup cgroup rw,memory 37 28 0:34 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:19 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event 38 28 0:35 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:20 - cgroup cgroup rw,cpu,cpuacct 39 28 0:36 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:21 - cgroup cgroup rw,devices 40 20 0:37 / /proc/sys/fs/binfmt_misc rw,relatime shared:22 - autofs systemd-1 rw,fd=31,pgrp=1,timeout=0,minproto=5,maxproto=5,direct 41 19 0:7 / /sys/kernel/debug rw,relatime shared:23 - debugfs debugfs rw 42 21 0:38 / /dev/hugepages rw,relatime shared:24 - hugetlbfs hugetlbfs rw 43 21 0:17 / /dev/mqueue rw,relatime shared:25 - mqueue mqueue rw 44 19 0:39 / /sys/fs/fuse/connections rw,relatime shared:26 - fusectl fusectl rw 50 24 8:1 / /boot rw,relatime shared:27 - ext4 /dev/sda1 rw,data=ordered 48 24 0:20 /@home /home rw,relatime shared:29 - btrfs /dev/mapper/sda5_crypt rw,ssd,space_cache,metadata_ratio=4,subvolid=258,subvol=/@home 95 23 0:45 / /run/cgmanager/fs rw,relatime shared:74 - tmpfs cgmfs rw,size=100k,mode=755 173 40 0:46 / /proc/sys/fs/binfmt_misc rw,relatime shared:76 - binfmt_misc binfmt_misc rw 243 23 0:51 / /run/user/1000 rw,nosuid,nodev,relatime shared:160 - tmpfs tmpfs rw,size=6538356k,mode=700,uid=1000,gid=1000 203 243 0:50 / /run/user/1000/gvfs rw,nosuid,nodev,relatime shared:199 - fuse.gvfsd-fuse gvfsd-fuse rw,user_id=1000,group_id=1000 268 48 0:139 / /home/tv/tmp/fusetest128387706 rw,nosuid,nodev,relatime shared:94 - fuse /dev/fuse rw,user_id=1000,group_id=1000 golang-github-anacrolix-fuse-0.2.0/cmd/fuse-abort/internal/mountinfo/tools.go000066400000000000000000000002011444232012100272730ustar00rootroot00000000000000// +build tools package tools import ( _ "github.com/dvyukov/go-fuzz/go-fuzz" _ "github.com/dvyukov/go-fuzz/go-fuzz-build" ) golang-github-anacrolix-fuse-0.2.0/cmd/fuse-abort/main.go000066400000000000000000000122251444232012100232360ustar00rootroot00000000000000// +build linux // Forcibly abort a FUSE filesystem mounted at the given path. // // This is only supported on Linux. package main import ( "errors" "flag" "fmt" "io" "log" "os" "path/filepath" "strings" "syscall" "github.com/anacrolix/fuse" "github.com/anacrolix/fuse/cmd/fuse-abort/internal/mountinfo" ) // When developing a FUSE filesystem, it's pretty common to end up // with broken mount points, where the FUSE server process is either // no longer running, or is not responsive. // // The usual `fusermount -u` / `umount` commands do things like stat // the mountpoint, causing filesystem requests. A hung filesystem // won't answer them. // // The way out of this conundrum is to sever the kernel FUSE // connection. This process is woefully underdocumented, but basically // we need to find a "connection identifier" and then use `sysfs` to // tell the FUSE kernelspace to abort the connection. // // The special sauce is knowing that the minor number of a device node // for the mountpoint is this identifier. That and some careful // parsing of a file listing all the mounts. // // https://www.kernel.org/doc/Documentation/filesystems/fuse.txt // https://sourceforge.net/p/fuse/mailman/message/31426925/ // findFUSEMounts returns a mapping of all the known mounts in the // current namespace. For FUSE mounts, the value will be the // connection ID. Non-FUSE mounts store an empty string, to // differentiate error messages. func findFUSEMounts() (map[string]string, error) { r := map[string]string{} mounts, err := mountinfo.Open(mountinfo.DefaultPath) if err != nil { return nil, fmt.Errorf("cannot open mountinfo: %v", err) } defer mounts.Close() for { info, err := mounts.Next() if err == io.EOF { break } if err != nil { return nil, fmt.Errorf("parsing mountinfo: %v", err) } if info.FSType != "fuse" && !strings.HasPrefix(info.FSType, "fuse.") { r[info.Mountpoint] = "" continue } if info.Major != "0" { return nil, fmt.Errorf("FUSE mount has weird device major number: %v:%v: %v", info.Major, info.Minor, info.Mountpoint) } if _, ok := r[info.Mountpoint]; ok { return nil, fmt.Errorf("mountpoint seen seen twice in mountinfo: %v", info.Mountpoint) } r[info.Mountpoint] = info.Minor } return r, nil } func abort(id string) error { p := filepath.Join("/sys/fs/fuse/connections", id, "abort") f, err := os.OpenFile(p, os.O_WRONLY, 0600) if errors.Is(err, os.ErrNotExist) { // nothing to abort, consider that a success because we might // have just raced against an unmount return nil } if err != nil { return err } defer f.Close() if _, err := f.WriteString("1\n"); err != nil { return err } if err := f.Close(); err != nil { return err } f = nil return nil } func pruneEmptyDir(p string) error { // we want an rmdir and not a generic delete like // os.Remove; the node underlying the mountpoint might not // be a directory, and we really want to only prune // directories if err := syscall.Rmdir(p); err != nil { switch err { case syscall.ENOTEMPTY, syscall.ENOTDIR: // underlying node wasn't an empty dir; ignore case syscall.ENOENT: // someone else removed it for us; ignore default: err = &os.PathError{ Op: "rmdir", Path: p, Err: err, } return err } } return nil } var errWarnings = errors.New("encountered warnings") func run(prune bool, mountpoints []string) error { success := true // make an explicit effort to process mountpoints in command line // order, even if mountinfo is not in that order mounts, err := findFUSEMounts() if err != nil { return err } for _, mountpoint := range mountpoints { p, err := filepath.Abs(mountpoint) if err != nil { log.Printf("cannot make path absolute: %s: %v", mountpoint, err) success = false continue } id, ok := mounts[p] if !ok { log.Printf("mountpoint not found: %v", p) success = false continue } if id == "" { log.Printf("not a FUSE mount: %v", p) success = false continue } if err := abort(id); err != nil { return fmt.Errorf("cannot abort: %v is connection %v: %v", p, id, err) } if err := fuse.Unmount(p); err != nil { log.Printf("cannot unmount: %v", err) success = false continue } if prune { if err := pruneEmptyDir(p); err != nil { log.Printf("cannot prune mountpoint: %v", err) success = false } } } if !success { return errWarnings } return nil } var prog = filepath.Base(os.Args[0]) func usage() { fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s:\n", prog) fmt.Fprintf(flag.CommandLine.Output(), " %s MOUNTPOINT..\n", prog) fmt.Fprintf(flag.CommandLine.Output(), "\n") fmt.Fprintf(flag.CommandLine.Output(), "Forcibly aborts a FUSE filesystem mounted at the given path.\n") fmt.Fprintf(flag.CommandLine.Output(), "\n") flag.PrintDefaults() } func main() { log.SetFlags(0) log.SetPrefix(prog + ": ") var prune bool flag.BoolVar(&prune, "p", false, "prune empty mountpoints after unmounting") flag.Usage = usage flag.Parse() if flag.NArg() == 0 { flag.Usage() os.Exit(2) } if err := run(prune, flag.Args()); err != nil { if err == errWarnings { // they've already been logged os.Exit(1) } log.Fatal(err) } } golang-github-anacrolix-fuse-0.2.0/debug.go000066400000000000000000000007141444232012100205660ustar00rootroot00000000000000package fuse import ( "runtime" ) func stack() string { buf := make([]byte, 1024) return string(buf[:runtime.Stack(buf, false)]) } func nop(msg interface{}) {} // Debug is called to output debug messages, including protocol // traces. The default behavior is to do nothing. // // The messages have human-friendly string representations and are // safe to marshal to JSON. // // Implementations must not retain msg. var Debug func(msg interface{}) = nop golang-github-anacrolix-fuse-0.2.0/doc/000077500000000000000000000000001444232012100177145ustar00rootroot00000000000000golang-github-anacrolix-fuse-0.2.0/doc/.gitignore000066400000000000000000000001441444232012100217030ustar00rootroot00000000000000/*.seq.svg # not ignoring *.seq.png; we want those committed to the repo # for embedding on Github golang-github-anacrolix-fuse-0.2.0/doc/README.md000066400000000000000000000002531444232012100211730ustar00rootroot00000000000000# bazil.org/fuse documentation See also API docs at http://godoc.org/bazil.org/fuse - [The mount sequence](mount-sequence.md) - [Writing documentation](writing-docs.md) golang-github-anacrolix-fuse-0.2.0/doc/mount-linux-error-init.seq000066400000000000000000000021301444232012100250110ustar00rootroot00000000000000seqdiag { app; fuse [label="bazil.org/fuse"]; fusermount; kernel; mounts; app; fuse [label="bazil.org/fuse"]; fusermount; kernel; mounts; app -> fuse [label="Mount"]; fuse -> fusermount [label="spawn, pass socketpair fd"]; fusermount -> kernel [label="open /dev/fuse"]; fusermount -> kernel [label="mount(2)"]; kernel ->> mounts [label="mount is visible"]; fusermount <-- kernel [label="mount(2) returns"]; fuse <<-- fusermount [diagonal, label="exit, receive /dev/fuse fd", leftnote="on Linux, successful exit here\nmeans the mount has happened,\nthough InitRequest might not have yet"]; app <-- fuse [label="Mount returns\nConn.Ready is already closed"]; app -> fuse [label="fs.Serve"]; fuse => kernel [label="read /dev/fuse fd", note="starts with InitRequest"]; fuse -> app [label="Init"]; fuse <-- app [color=red]; fuse -> kernel [label="write /dev/fuse fd", color=red]; kernel -> kernel [label="set connection\nstate to error", color=red]; fuse <-- kernel; ... conn.MountError == nil, so it is still mounted ... ... call conn.Close to clean up ... } golang-github-anacrolix-fuse-0.2.0/doc/mount-linux-error-init.seq.png000066400000000000000000000707531444232012100256140ustar00rootroot00000000000000PNG  IHDRh=ͭqIDATx͏#M'v;""y뭩zv",f뺒T ~6g, {A@\6TI(hO: @Ue'3#tCҟb23 }>@"#Hw#ik10hű<>d@@d@@d@@d@@d@@d@@d@@d@@d@@d@݀c16nǕR6əOrs>!ұCJ)R}n|?g0.@6r3 {Ţ16EQc@3,oٻu=Z,e42o?L'g9g6݃nWWWx>hBEQ4!O?yəOr!dX۷ofgB1S10OK'g?9Ym? CWecl} A'o?9Y?($S!TUUF۪eY.RJM 3eCy!d}w_Ftzuvvv4M`OɛOrq*@B]__I]ףL'g?9˵iJ _i]R qR CCJ'g?9YnU@~QO]u@iAq?L'g9VE)%ÒL'g9k!)"!/?9əOr #o?9əOr@u PuZb{]C_@b98Vz.;>쭿 m &wC0p#J]o\n*jF9ˇm]?_ˮY Cf?yqڱo8 X7ruϱM}c]]I{k[O~)?,LW 64N1}`  Ȁ2  Ȁ2  ȀB1tv1L'g?9˵ ^QM16!cyəOrs݀`%!CeYeY.hbYAϝLs9˹gWWWWWWo޼~ooo緷'+a˿ſ˱VeYWUu;L/../yUUweYEQ,B#07__*SeclS1Eʲ\TUUlr>OCa4u=j//^b(dr}~~r67UUI``?|_>SYvGQl6{~~vBf>ORH) j/nc1{k}svvv?Y(E1 ??nYO ࢪt:x{ww7 !drs}}}^xXTM ? O(f9 hxqqv:^UU5o_݆D OEX@4˩?l*~...޴b(CG?_݆Ltvۋ)༪M pKI?sNIeY.F|6!tz5'u]Wm7?G۱TbJ Xtz5fhv`O_σlv!f4C0Ed4ݞ]_4J7C~>vzEhx>hȠD,KML) R)4M4MX,ʺiS,H!TE*b<krHLϋ1?/Dzd/!>cjW5ӿ\y?I@^j/|8ﵿ)./ BY,K%?KM qu{YS>O?C@hz}]rewKM.?C'1r?9#C1r?9#C1-r#  Ȁ2 Ĕk@@@bͱǢ3Lϛ2  Ȁ2  1q@@bͱǢ3Lϛ2  Ȁ2  1X^4$8{iM4'ϩ" Al&G@fVϧVDqbͮ2~ů )cL!(xvT La}'g?9&KLObS76AiB1cJ))8 HJ'꺞EQ,hQ+x9M`RlX)UTRJE_Y䕜Lϛ|x|RZޔHF@ɁP 2\ԬW]_O}?K?fS䣕ͷyW)(,yY()&~~rj'Kw}YE=hT.Vpf@>quunX,e{& ?C%HUonnf|Ru\=Xr'wwwGŢl `L Ou]6MS, EJg3?o^|>\LJ'L`|Х)L`cs6Lϛ3!⇜ 2  Ȁ2 | [?9  Ȁ2cl8y@d@@d@J) @r͋@d( p,?9&  Ȁ2  Ȁ2RlLϛ2  P9vXr:vdEڟ;FצrP>xl<8Tsx@CisF{W cz[6h_kt{wgk۶>]UqڳO(@OEQ,گۑ"p*6]ܧXܷ=Y}Lnd~g}l{ێץ=<c!ic@VwߏϨt~g߫"`Q=s~cFצQ>!>ۯn-(:F)W@OtTXn*HWs} >նwk1%uam q=5MSӦJ|H{z]x=8ci}vO =@.,I 6xO^|drYUռ(ŶE)$'7!q. GubJ0,CLl !c1D'qu=1.XE,@Sbnoo/ʲWU5!E0 :t)E0 ~f\^^~5ޅ*1>\"1 <2 G+oݻRJEQuY,EQ,RJMP :9&5tݻ/Gx<~?BdXT!{|>u]5MSb<>BnRhXm`bͱǢMBlH)mwA! KCyg? 2 >a& I@V,xIy Ȁ2 +1mc&  Ȁ2  Ȁ|&[޼d@cs6y@d@@d@@VRJ>-?o^|Ȁ2 J9vX Ȁ2  d^cl8&pRJY-SJcJuau Ax(~LO0"')C)Ƙb)$`7'鐅L]ד(EQr!0(N>J),yUU]QA ]]]}>SJW˛RQI AZ.kW]2c\,%d/I^ G+oݻRJEQuY,EQ,RJMP <Ckݻw_EQFx~4`@^ܜME,yWI]U4eJ)OE@&u]E ``di"TN-C& {N.y `?0tȀV  ]9v8d@@d@@H)3  Ȁ 1mxd@@d@d"̘2  Lc@d@@d@0c^|Ȁ2 cclߵ}F8eً16筶E~{jџR*C:v) (wۺ murM[[w뛊mo@7o@/m\m?&]=9MS}uٶ3cvj (uvݿ~B9~NBbOQ$'M I`C1ҁlXīW~}6}Z"~'g?߿y懏4xO^|drYUռ(rtn()鷿ؼ` CߜR)x}1Y/#oN#E4 $=FZ,bQ-C"').c6ϊ˲lI1ƴ\\0X8UXsss2E .V'Kk޿E)˲.(zĔR/ꦽfJ3c@Xsyy۳ǫW8vKk^ܜMګ?h|y`t>W.n} `ݤb(MBk.)RJE[d? `MJ)01mxBiLFfC!  Ȁ .q7/>d@@16n#  Ȁ2  Ȁ )%5`Ƽd@d" 2  Ȁ2  LԀ@d@cs6p<Ȁ2 Tn<}~y6nzϯ:x0]8C,NO{(bj>ҽۺ >Foj6oc3``~"TvmU]m֋mu>{]#ۂ~bmd}~W-8KXϹ=^?t1_=p#?d_]8}FO]w5}`MӔ!tMC{^mkK.:όUTț'd%8A%-~m>) !-~>d2j^"Ƙ.Xm@/6߄Rԧ 8QSS/ .9yK[c1`'1zc\E(16Ba} REY!(`|<_7(x\ԯ ˯fٻ>bc\,%F8*4E~WmN@׫mY6-<ص]ktgI Ez*oMsw}_>oy#zq~i9DQ;# ^ N}8c@:v[E|իW޵ͶGO7[ﯪjQE$N|?wA8%}&?7u]WEQlv4VUuW(E<&:y懓r:^-񼪪(l6{?Ln,˺ n+} E(bqc71TUٻt~4ݶ!@08qvڵtzUd2>;;{sqql6{7L; ׍gt:f]\\ٛd~C'J@L&Ͽ=??tv<_p5TI^QxӃGfW >EMӎ}ڵɶ"kߛFraS{ljӞm3 =[ oٿV(EQB"5*v7}}6Wgi?ڼ}]ۯ?}n_mSvkS;7=ϛ>>o~?tm0竺iʦib@౛g^Ǿڴ]F kv!Z G4|MUU!Ƹp:t!!TU l)縛똻n1k]Ǯve֦>i\^^\M1RQ)2 ԋc?TŇtfq0`U9{ߟu߶x} j߻C_i)S/˲˲cMg8 kvME\)DWv[2z\ljOc۞w=碫=כG]qn=k?0 r/޿Ţ\GvQ㮯J{>v Uo!k4~}?7zkXe(g{v:q}}}qss3瓺G1۬#NX-^~i}F7˾q:FWط=Ml U՞Mk۩}ݶ>0OnX,ivsk}W3}}}}gCȓ[ܩ]>$|uj[czX,FŢ\\OO86#0\}9eC-qvm>bQ.GB{ @ny `XZq lh#]ێ=էݵm]شmy=.[No ScLK F6hMm훾^]]iO}b#۞<}^uǮw#XTUUt:Gt:ǷUUm; V̮Ec= 1vMS3R})pnߴ5V_9RQ,htBgggo/..޼xۋ7gggoht[e]"az!uwnߵ}gcǻ>O}sEc}N:?L&W!ŋnXʲfM&Na1l}xv#_1C)^Cŋ/7/^ٛd~ gc;n8G1CJium۶?u=I)1Ʀx|=]jpp j4!Bb1nTE]弪yYwK@'>GQSJ= #MӔvEQe,s(k!@Z>:C1_DbۥױVs=CϾv.SC8l*Om !91z+)`X[_QW{׷e{>_C9v±*s*WE}i\_O%XۮM߯(hh|vݷ׫4}ڻi?]?~.m>}>tM?w~`!,}ȋGKH>)N OѴivSOۻDz3۶wg=st]y]Ӟe[wcu~!|(׋>6S6rkD! rwSͦ]pN`׈Ь?ޮSa۬QMsym˂~nb|NwF yr>j>7n:!SWuKk3 ͫ_oG󰺟WkԺM}^CgMgƶ}G;k 1 BJ)6MSFs)8Z:?|?iW_w?яGןߍyQ"giy/6o¿~ꫯ/_a2\VU?Vx9!hO8v;1'G?]D>F.>?i<JNKe'(&9榨KVKeYe(1 :lvj7o_?ۿۯpyybyIiX__{vYLEU !$ǰ2?eYWUu;L/../yUUweYEQ,J_]9GUbmb(T墪z<fggg|B趮Q4 ^l6\SuYxk$L&o.gx|SUU; l ?*AvZpQl6{~~vBf>O-M=}/ŋoj^]8616ᛳ˗/_2ꮽT͐FMŸf,OӫwwwBL&7u]E4?TǾ_}ߎFx|3n˲l*8(i6xqqv:^]d~) l6{_u‡iobQN}g|;NO&j +c>5xx...^\\|0~Sw )ʲ\Fl6{BUUj>O꺮~|<;;{{vvf6ק:ʰYxC28-b}#Uh?ѻFX?>&i}׹WMc=DjӦ5v՞M> ~m?]i!ۧP~>C3d@@dW}i/X2z~Tݦ)ضCqq~:^xvmMcUdڽ]B}}F7=þߧ=L`RJz_ߧ ]_՞min~m݄f9i >Oa}z߂>w m*hsqn~6~c>۞f dmǶiiMwoܾwkm>u~0`p+mu7qȂá{v*`;w<`b @㱛,ȏ7 {{ux:do~?C$n<616>O%ƘcIV^mVGLGGSKWU(Y S[~'BmE׮ޞޞoOǿ߼ygwwwht3n+rQŢ 9}[_#`}۬4MSۊ}w+R@|hN^u' w`0A|lի_ p_CvYOׯ?L&˪EQ,c5߹8oy_RJQ'.ȏ61?B !F4)C)Ƙb)$`;T溮'1EQ(:BN8Y)gۋ,UUCwEQ!CH&0|WWWTERp-5l.#1>\"1 FࣕyW)(,yY()&~$ \ݗEQԣz<Fr?'?+^ܜME,y<WI]U4eJ)+&u]E @i"Tᔤ|xX_V`|Xc?C$ cxZ `H 2  Ȁ2 |x  Ȁ2cli Ȁ2  Ȁ2 |x  Ȁ2cli Ȁ2  ȀBHnc@RJ>@f@d@16nc7 W> ﻟcvtX8u>8ɡ,b;T:x$. =EC> h}_o_n[V M_tg}w]ݴ3S4e~_ۊX?ӎoz]`?fC[w~CZ[j϶z.1~XdH1>,c|lc౤_M`4?iHU u*bGṅ15B.lbدcbxޞՙ mSWT>3"=})KՇ R)Y1ic;nǾ!}S`ׯ_˗L&UU͋XlZNͮ ]WӷoyA{WY_oߴM=vN?C=mġzt߷~; !TUb AxPkyɵGFwMks܇olp6oѶMM+1Ԯ B$Rq?T4MS.vm5%՟[G}[kG6et}l{\۞V_|]k`~?˛|>,iRk b'1OcY,bQEQ/޻wd! =t?]<nH{kM!@c>/˲˲cͲB'38)1n>u=mf P,Oy{vݸii޳Uȏe;\tٵ 9g۷ofd2F,)%}NX/bMYuQw1T˫l\]]>瓺GVmݵ\^^~1'd2y7,C!͛7_?~o~?vX,Fu]WasDzoBa9Ƿggg/^/z¿-:|9Zʰ,[OeY.t:>;;BFu]츏Scl4UUNӫtz5o+1l$5˩bQUb1,EUUwv6???s{{; !x|3ϧm.NS˲\ey7Ln^\\f](v?!c u]UJ0NWo&!0LnngY1E(˲F  "]S;`Ⱥ]ciB+d[w( eYmcL]m,E[+yL!TX X ^ pu[a?Sy|iyJJZmϝ6vr!@Xyu5i dp+EGſbǤq_8 \I Ȁ2cli Ȁ2  T@f@d@16nd@@d@@RJ>@1c7`B`(clbͱ}Un)Q?04 pH1:"}^=+-"իW޵h_湵}įzUU( 9%8u ^~]!>Ǽ6nwIGǯ ?M]UQi6FUUݕe(bц9. x't: `Xt:f]\\ٛd~C'JbǪL&Ͽ=??tv<_p5Ty {EQz2fodr9˲[^?pd/b,oGx<(v FbQRQMQ,V.Ar% b-"b)جNu]i-?0R*WLjVJ@@l=A@RJaY+@d@Zvdě?d@@cs63&  Ȁ2  Ȁ2R22  P9v%  Ȁ2  Ȁ2R22  P9v%  Ȁ2  Ȁ891f]|TnR:vz޷ QѤ6h7ݷ뷯 Zw]m fG Shߵl[C-e@Fž6swm~ș3po !1cL!~?d.~'g?߿yEQ,vS@3b]@@Hi]! ͟RJq()T4MSb!.ӡxcb1Z,2(J XYQuYM4)ƘbMJZ@dwmȈnnn^(RY2@fSJ) ;%8} ;z1 C1m p8]Ȁ2  Ȁ2R22  P9v%  Ȁ2  Ȁ2R22  P9v%  z1 ܓN8]16BfЋ^ `';T'C )bM1-Ћ vJ) /~R!Wo!Ԟb}\/C޿K?%\/>>~Mo꺮H_~??~X,gBHMSnrr.OyUUuQl~2܌yYu n+tR'|b<SUU󳳳wggghtۆa9?clytzUd2>;;{sqql6{7Lk)b`'Wj6]wqqgggo&eP/ cR@o>QUd2yw~~wNӷz}@{57g|(z<_O&wt:};L.GuYwEQ48->R 1EYz<_UUuSE;߄B!ŢL)((eYޭ\Pp|bXERJ1clWJ)UM)W[H'RJa?02cRX@Wu2"  Ȁ2  c7@RJ>@f@d@16nd@@d@@RJ>@f@d@16nd@@d@@RJ>@f@d@16nd:vjzٵqc8}ucD۱єXy?F>r&)"UE6}Gίo߯{u]_յ窫=}d$CT|R߯޷3}~E"~}j}}7:T=>;[`ծBA}=p @OiXxSAX1R2X`_Ή_^k}.]ڵmf.tcZ?ֶS<} GGWi$u>m{C>i pʆx@_}gKB@!"`C1ҁ[lX`_%`DkK|r~7o~-8*}'/_d2j^b9:'/ظ 'kWxգob;!0^b`L1Ocp/)ſ b-jA 58-O~|>?+.˲i&r`=T Cnnn^(RYZ,y1Ʀ,˺((eSJ)FgGk.//,궪r)&~Ngu]Wpܗ2clxή^ܜMګ?pv:+c nRhXC!5u]M)-Sp&VALF@Lo›?lD@cs6OK@d@@d(3dƛ?d@@cs6OK@d@@d(3dƛ?d@@cs6OK@d@@d(3<csf۶cUnpBp>G7EHxm׻tݾ>_m_O;K[~qj2iqUT.u_mXi'pTPAz{y,GO`p_ 7o~x{ss S޷, mkE -VOsi}~۶`>ó'-^7?0L.E흻 6muz}>m'p4~ Ȁ2  Cɂ\op_; +@us~ )_׬u[79~vs1ns)Hp4~ CaՏE]SӔ! ٔ·orj}K[o~~]V<b-+vxR~`ova# ]N5`S:^_]!AWM컏{Ֆ큣|gL&UU͋X 6MߤLJ>ضC=&Q  Ȁ5 Cɹ["!؄c7 Nz*j~6ܗvxg?vJ16~^ǿ޹S -^7?Ԁ͛\!u=NMS!}Tk} d@@iod@@bͱ<-d@@d@@od@@bͱ<-d@@d@@od@@bͱ<-d@@d@@CM8o =csv@_ձpJ0 nRl&Hg}cclOJn 6xկwm:B>>z~?׷ynsro_Uբ(f%HN N`ׯ_dWCw1ߵͱ~?]ooSuUEfoGmUUweY.X!C pl-޼ydr9N/xX,FŢH)c16k+' {u]W)i"*0@ 쥔1UencRX`d@@@d( c7 Ȁ2  Ȁ2 |x  Ȁ2cli Ȁ2  Ȁ2 |x  Ȁ2  Ȁ2  Ȁ2  Ȁ2cli ձ@oFl9Re` 2  Ȁ2*c쳶\߷su|~۾>p<ئ" C7vXi>J)c`3m[}p&vmѿ^@l:nMum߶M!F=iv>mwb[?[oc>ϛc}v0,MnCgS1?R,wnMڶqys4|9|l^ǾM6m="<=~g:x3]9w}T7sDzqvݾ) Oߧljg;S>}}W{}7ݾoyߗױkϡpXgz\]_~ӹmϮ}ݮ}wh}_>s\Cs}mþٌN589mSw߯Zo;zPyLn73S'भOY)ğ阅>)NڮOqܧ6=>k}| 89mh:G}]ou]G>6~' vC1ZwS; 8I]ESf}ﺭcl!^|\۞}>๲f)1` d@@,s"x$>h@d@@d@;i0<3;5NrSOTdCs9/9Yख़"3qԵ˿Zd 4/ \xs^H"3 f7?}竫߯y7MӶ}ߏ֩})e\k~'2) K{ZιR94MM4}yo6:c+Od柧xZ)euRJck|_k}vmcus)e@΃'2@,aR:n4 ]ˀ8 &? 򣥔2mJ)]^^nYZJ7ŧan `>$%Kd sjxo߾yhRk7f@y?@L?EtnsYr+]=Zo "3ʭ5I';ʲ,eY-P/?Ǚ"3:YRN_/ӷps6?@L?sk_7psn?@L?RHϖAJEO &\P>       pK@IENDB`golang-github-anacrolix-fuse-0.2.0/doc/mount-linux.seq000066400000000000000000000032241444232012100227260ustar00rootroot00000000000000seqdiag { // seqdiag -T svg -o doc/mount-osx.svg doc/mount-osx.seq app; fuse [label="bazil.org/fuse"]; fusermount; kernel; mounts; app -> fuse [label="Mount"]; fuse -> fusermount [label="spawn, pass socketpair fd"]; fusermount -> kernel [label="open /dev/fuse"]; fusermount -> kernel [label="mount(2)"]; kernel ->> mounts [label="mount is visible"]; fusermount <-- kernel [label="mount(2) returns"]; fuse <<-- fusermount [diagonal, label="exit, receive /dev/fuse fd", leftnote="on Linux, successful exit here\nmeans the mount has happened,\nthough InitRequest might not have yet"]; app <-- fuse [label="Mount returns\nConn.Ready is already closed", rightnote="InitRequest and StatfsRequest\nmay or may not be seen\nbefore Conn.Ready,\ndepending on platform"]; fuse => kernel [label="read /dev/fuse fd: initRequest"]; fuse => kernel [label="write /dev/fuse fd: initResponse"]; app <-- fuse [label="Mount returns"]; app -> fuse [label="fs.Serve"]; fuse => kernel [label="read /dev/fuse fd", note="starts with InitRequest"]; fuse => app [label="FS/Node/Handle methods"]; fuse => kernel [label="write /dev/fuse fd"]; ... repeat ... ... shutting down ... app -> fuse [label="Unmount"]; fuse -> fusermount [label="fusermount -u"]; fusermount -> kernel; kernel <<-- mounts; fusermount <-- kernel; fuse <<-- fusermount [diagonal]; app <-- fuse [label="Unmount returns"]; // actually triggers before above fuse <<-- kernel [diagonal, label="/dev/fuse EOF"]; app <-- fuse [label="fs.Serve returns"]; app -> fuse [label="conn.Close"]; fuse -> kernel [label="close /dev/fuse fd"]; fuse <-- kernel; app <-- fuse; } golang-github-anacrolix-fuse-0.2.0/doc/mount-linux.seq.png000066400000000000000000001271071444232012100235200ustar00rootroot00000000000000PNG  IHDR<!IDATx͏$x>[Ddb7EXhy]iĕ Hn}$` 4=i=Ҁ a΄̎&]afҭe/|>@"w"1R8kű2  Ȁ2  Ȁ2  Ȁ2  Ȁ2  Ȁ2  Ȁ2  Ȁ2  Ȁ2  Ȁ2 TncJ)eL'g?9˹gBJMHb!C?_3_ϙ,mm"? L'g93^4U-"88'L'g93Q4e۶"<~?yəOrYm\h4Fy!EцR"xvBɛOrk!?}l~6!}1SJ,O əOrcd 䃯꺮BeY~R7,B@B_}.G}UU,R?QB?yəO2!29}FmrD gOrWB߿|>u={8vr3C4Xzc%/jq)|)sCL'g?9YnY@NjVmۖagϋL'g?9˱w 4M)%g%+?9əOr׶mB(RJE.C^r3Fr3 O}DvX'k1~k:Ȱէ^Ox |`l/s1g?krxUEͪQmS|Vs4GYgϪvضa]ۦY09ت__mS6_֖^\6)\mۡϪ۫t=Y;e<~0n}pġ/ͺ#ˡ&>XwWwueS.v!RNCض> ]GШ}0O.8C^x}t 6ryl˾9m 9t;j:cv=]oOp  Ȁ2  Ȁ2  ȀB1tv1L'g?9˵ ^Qm1!cyəOrs݀`)!CeYeY6EQ1ƬNs9 _˛7o|׿;w|W_Ogu]OJ<ȟٟ??ݎCZ_e]Ud2O?UU=eYEфRG\?_\?,c b(TeSUU=f|> !ht_m\~zo݆c EQUU=L&7׳x|>u]u߹M|_݆clRz<Oӛl~6Fq@gK@<o6N#ЎFyF.k۶\J}{_ 䛓zEtI`UUx<Fh4_\OY? OL)3 SJmۢmۢimr/ku'H!TE*Y͵@?9$^oߖ}^${qA\3.;v;ٯ|_M@{+?O< kN/yK„?'?K%AR_d8s?C@EўM71o/3|K*ʡyFϑɡoyFϑɡoY-r#  Ȁ2 Ĕk@32  cE'g?9ə7d@@d@@bJ)a@@bǢ3Lϛ2  Ȁ2  1X]4$;Lձ1)b]п˺u{*WO)P\eSML'g?9&`gݨs[N^ M_ulc9 ;c=fh٦aK3p* !acUEQԊ(NYԟ8' _lCJ)S!^MLNrM*1?!N?Βm[R1SJIIGp|I))]GT6s6_=<<̚m[.ṤmI^əOGnnn>Ƿ)ŢTE2JΕ m5y}}l.o,)T;9֙ywSJEQuY,EQ4)6~~rj'VC޽(z4ݎѨ\dI뻻ˇI4h=s%ȶg1I],w6¡NG&u])(%?JG.۶-XaEJw3?o|>\LΕHəELy&g?H1nO XH(98')~ș@d@@dK'g޼d@@bǢ3?oȀ2  Ȁ2  C)%ȖOy Ȁ2 cE'gd@@d@@RJ-y@d@1nOUntrq8Ѿ5\^P}#'3vʹu˅a]^j}ooUe4ZZyٺg~7}{j?3d`KEQ4r/VCEuS,ڞ~!m{۴s~9*ڳŗ-m[v?MB~U}hی/?囶Ǯ+=5GN/0;cFkhT|`[6Sݯr[Z>`r ]>6ߵpݦ=ڶjẐn낒`h[xr(D?Hq[j۶1w_5|msu| @xJѷݏT_66%Έ3|zclhhlj LR6_e9jBx("|B777۔bQ*")<-N fw!|@YӇK }{)(,yY(R?Tfp\yjݻwEQFx~4( HB777.&MTgBgn۫m2n#px äQ4e38C1mc&B!u]m['BX*3#B. = S| #fߢ$ +NxIy Ȁ2 +1mc&  Ȁ2  Ȁ|&[޼d@c{6y@d@@d@@VRJ-?o|Ȁ2 J=vX Ȁ2  d^cl}8&pRJ.)RiXO)Fŗ ?ou^cg;V-$ r͛lTE·/bwUq_mqU(0tHs&>_=LEӱ!l>Pg!q 8 Oŏ)HImB1cJ) 8KwɛBI))]G@'e?Y,yUUCQAٻd<ߦnRQIgiqRf\__w6 Ub>\"MgӿL&ypufu޽{ݔRQE],yQMJ3'm5tݻϊGx<~?0ܼ|xx4MS-. h?pVmpww7瓺m˔RE@&u]) ٫l۶H)] N-L@\\y@ Xb&pc@d@@d@0c|Ȁ2 2clG@d@@@&RJjy Ȁ2 D=v8d@@d@@H)3  E11v_PmS&^c|jWw˖R!ұǼUnE,^Uw7V[!rCYW*ևUmj>,tEK)   Gʿnku73Iڶ-CX]o:h٦˗)P79SJqۄcr 8i*mE/΂t¿4ͨijAOxRw<z&u])M΅z.۶-RJEWi@OJ),p6bljҀs`?pna&p.0@d@d%.  Ȁ 1mxd@@d@d"̘72  Lc@d@@d@0c|Ȁ2 2clG@ ;N{k?1lǝ?&upp]8!O;(Y\G/[Uo3n}vy7sfKO u: RZ_<Φv4.G;Z.vݖ!M b,~>y{Jqw1}/C OlkvgQ n?<?A۶etвM+?p"3ds*  #8Av%5ۿ-(sg87`n<7pJwmc1vHn |ܴκGO(v)H Nȗ_~M!cO~Mү~(4ގFʲlh)86қ7o7L6UUEQ4d2,.%֪z\ESEscj~qqt:}? 8nڵsMYWd2j0n % {MӔ)TE[ż,ˇK* {Mӌ)RJ)Ƙbm?pd*Tm[9" PgA@RJYs% { vCRXl Ȁ2  Ȁ2 phȀ2  Ȁ2  Ȁ2  VIn%  Ȁ2  Ȁ2 #1clc):vK=v^}Fca%?5)"SJ1%G.ׅ?M@; !#{BX-i}gy۾> 0IQmQ)RJa^@JY*mOv Muž=} Yv8 !4EQB]Gf mu M;viאuEm\?emjS_vΫ_Ijjv>YGV+fs?aꫯ!Bq8CBGU~׋1B*"¹8bXO)E>+YC>_yxx5MSm[t3^mʶvcMmxckw>_ O9Sc.Ixuj[cz4ͨirQSW?5xSv࡟)nci\w' !!Y?NH ] /x^uYmڹn۫z^nLuy籯6abt_/81ƴ8@~dHр*lW-y1WM{vg5UwS7m}MY6-guK[ǃB(v)H !X] ێGx5ʻv=ߗbuɮ1<_}_$??n4WUPeSEӅ2pEncyw{u_j1ؑuS^m_˱RnoFETUUt:}xx~xxGt:UU] U#acS;541# Uy=}_w׍/T(,z4݇۫7^t~4ݗeYE3l/Vju7mcvm]Zikk;v.?m)#[XO&BxTU4ͨ,ˇld2j08/f@ϺU??ϦcO`y7c1EQԋ}!zw|_}'իW~wqqf2_!=?cXmR1gwm]]דRclǷO0TT !<4M3n۶TE]弪yYK3!1Rw@Fmۖz(fQupdOߗ^ϻN)B //3"XRl8_|af[ޗCnn9>J 6BRZ?)H?Ae.?ŗb$'BfUM44j;TUu "w۳K;7Mt6cڳ}Ylu|J\n_yA ~‡1pJ|abb~_S7?e?ƒl;_Xg+!6]u@|JX*P_{RNq8Xw3Jȓc/WKv?4:tS/[u,f~?Fgay;4j1>ڳiSYwX>w}z?F7M?Fr=K)RmۖhR s-NyzKx~{ޗ|__^^(Ib۶eUU o??~~'uUUcI;nR=rCɺcHKw!l{ΟɹJN_KeΛ#NZQmcMQ۞ /Y.M,˺,˦(6(Xah /gtSonn.onn.߼y__o~W_}?u=i%%m: ȿWb+R!)8,˺dr{uu~WUUͫz(˲. K}u9r>:1v|1(RYMUUx]]]N7k՞L?*M`rR]Lfu]W!|uuo !g8e8n;>t:}?LO&EP/*8]suu,yUUsUOBWO9Te3}!TUUOӛ|>FK~ofٻx|{#c4 f6fGѼw#cxI>Nh4Thtqqqݍm[. -6޾zo^zzs~EQ4Lx>ht劽_`q+xI>!|s](8mſێEPnx<~fwWWWo...ގFacY/RQ(fqn mst:!m:FSE#]qЖet۪H)"TU,Ηp{qq~4VUt8_vy_|Bj޿_)&X>!آH)6g[!)*˲LUUѨeY~kz5Yw]3ه/_|/u]}m^'O߄ai ]f74M3Z!UcW7ma(9?޼ydrP4p BV܏m߶p_!|St#>_w' A2X܋aq!,\u S+N8@+,~G du!_^ Ȁ2  Ȁ2 `xpX)% 3> Ȁ2 cx^Ȁ2  Ȁ2  C)% 3> Ȁ2 cx^Ȁ2  Ȁ2  C)% 3> Ȁұ cx^Ȁ2  Ȁ!|8#! ~  g4 clSJź06 e6 mȑNXP ~*,?~uɰj{ڿ*txfb_NNK,8{g;w $\A@/c7b00M!B1# Dz'b>z¾n2{a _|jw1SJnNX~HKsl:g 8.(nXx'_/CmѼn]ۥPjG? !|U^hggv[9_]~_MhH1ƐRJ E)raE6O]Wtn-Z ?(Z!<m;yP튽C-?|K'f1C3 Vo]VmgcYuϪnsYUwi;C߯!K!?eY6EQ4EQ9ie+T V`ˆY*wN=hEjj纰f;kߪk?u]m[mÇ>i~v pm,?v?C/j7g_Ǫ}.Xe/wr9ڜVO)úY EPA;ԎukӦo?w]p׺s{{{c 1c Bae䞲}Cm~Sl*یf:zl{u >m>yoc1=4ۿu9_۸~}ww7瓦iʶmcJigaO/}IUSR*Q4w=taavC8qP154ԑmfOm.쯳Kk;c)_xUUeYeY1ƶ(G GbO[ h?Yu oZڵn{ǬK:m۹>G1wqϮswww0^:!)3Y.̗0{ϻa~Yy ݷ(ڿn˅:Mo3#)_o߾t6]fv4˲lI/k z_xvvc=vU?==$bȇWn@JN 793Tvgm zO9_o:[cO^^۫$u]rn!<ɪ)?F!Xnc[n۶qr(ж uz߇^m߷* Xu|>èimbip$lZ| M_Mmslc1^}1T쟗m/Kv_]iFKv3vO)M 3k©:p /S4b\;,83G]?r6_3~>۹-_Luw!0 mgzXLx|x&]A/ ~8X$nS*WvlǮ~[غvCuwθ<8 :>+EmSKm# m_ pNjRԩl y=uDy]ΡF7Mo ujDնXLϹ;mozH m?f6f/UU5?vЎpJ;%Ƙv1k}ܴiT9_%~WUE.P'$`/BuEצ?}߿쫯?͛70Fw辪,(.e? ͛7ߛL&mZLO1TESUUBa4Mx|_UUݿc?x [z\ESEs#(,htBoSJE]㪪oht_e*{z'{zvMv9@N|2܄«WiFeY>fw&MUU> ౡ9>r| v=(ڢ(E>^zdrӶmUE=ogٻd~ԋǙFn~C!õwڽ]Lx_p?oBw_wzo~7ތ㛪g,. xF_C%xid!ƘRJaq@!˔Rٶm٭o'l8U=XՆu*rcjUUݏFht7n_7*EHvoy믺yhC]yU_ء}o&sg11|K'{J$xϽ:3?4Jn~M_M߬ ' ;E" Ӵipl[f;B{UϩcC431֝lZ645}lg`ﲭ lg~ySac{6t^(ss/u8f/qmzc~Ll{f{UEBhY61F!fu=J))Rr^>/c7]]\\\~wo!}UUEQ41ƤuEQ]'t:j@0b|6 ô74ep/ɿm,Fa0a0WWWo^WUUw3FFl6{BUUf>O꺮S9W1vi@=l6{?ޏFy<vԝph‡Уo۶\w4曓Et3h4FrNG&x)]ܾn۶\!R)%UgiqrBHEQ(Ź1+]`V}슛,˦+d߿_Mw@x|BESJm0)pxMvKݥ2COO߄aiәyɊh;^,9*?debN.5d@@@RJ-2   [1mc ?Ȁ2  Ȁ2 l $[?LJ?d@@@bǢ@~d@@d@J)HȏȀ2Pcl)'`g]9D`W1V#W?GNhA.Ǯ]~?嫶K.;E;i۶~N)Cw]*W~l#{\wEY7l8/[7mȝ `]|w_}sg -sm2Ƙ׫} o_W m;gc r@~|gnjs_|crmB+k}?x_O&몪EQ4Nǰ_yr_lx{8>9g牣1U pG1ӶmBH1cL)%!gA}bu=16EQ4EQQR!'O] Rf,UUCEQ!@x|RY,JEQ$?NIp}}l.'I16cpC6F[g_ݻwM)EQeY˲EѤ7$rw>+Fh4*Pp\`뻻ˇI4Ⲁsvy 4Uf|RuնmRݿCIaRi ˶mR۞@^2t”]=v%Xh+_tzL fpN+(87Ȁ2 %LJ?d@@c{6K@d@@d(;dƇ?d@@c{6K@d@@d(;dƇ?d@!c7d( @d:vr| .d1?v;.Xcp$*^YwvJ8Fj~@\8{fl(&hzǞ}|u[.UYzo*Wwhl( ڶ-C.J]X~t#/7>wv؍ˣFcFw-7gcz-1{sI?E{dH1,pJ/\CC/Q穈1e2_ԝ Յm7/Ǵo{}C'[~{g.l~/kݡ̈u,`L)Ŕ1`Kmۖ1ƕF׍x >ۿ9AIQQEMp/D >ϳ\i/e;}l۞Byl۶H)aF33EW9AWSfUm95,pBhhc!d&[p0]FZWnyuE,/_EQ9Uwhڽm{~ }{vV=Xzn?(˲))BV_ pH6u鲦'?|͛7[mf  7m|(-fWmUcc7: ~{vm&K*l$_=LE Nͦ ^M6};g_twm3 `ۦClm)G`_u] ][~i᮪cl&!B%RUe^5*im-%_oЌ}yhDזUmZoY0^._1a͡,MIK).SJE۶ҙbǮ:3~UgV红mCmukhm_o{)_imۘR| N!cif4MUEծ#O9!SЂagZg(fOmc}Ӫ,,c诰!s17/꺞m;^(9SLx ޽{i7 aܶmٶֳUȏswwM]tugo~:ͮgdr7eY6)%}OcmYuQ1PWŸyu{{{9'u][q2 (ꪪ+rB8)✷|>><|¿+:@ͫlvYRJeXU뭧,x|7No/..4F}]ף8;(mUUf:ތhcii}?гZ=nꎫN)cXMUU~6|s? !x|7ϧ].t'eٔe0L...^]]f;_Q$q]ףq:R0N7WWWo&!0Lnoo/?Oʲl3N_]]}חדni6@rBR EQB?fhtuuoOEQUU=L&oB7f1 ZNJ/of4gBN7˱U迩r}Rt~6fa>ꤓ$U{7+F<?_\\\pՇOhʲG| "VO "~Q۶-b!.] lߥ("hʲv@1{" O#]ՖetŸCZWr0e؟AX?? èsxiqHJ6NCfMF^{͡_X#-]*cc C1y0gK2  Ȁ2  PJwȌX_祵ۈxUU5EQK!@rHpl }?9vܿIϾu;~ӟu]Efht_UCYMQMzc WVInc Vm_4o*6Wz~mkq OyXC%,Ԯ}^^l}  m3Ԟ!8eںi;C_\a 2g)O Vv?3V 1mg1)Mu> LUq_S׍ZwCO{e]ϥ:<>:v#ҫ¾F7m})嘳nПINZm?ԞUm].v} fah߻iӔ]o< m^Mmܶ {xOv[TIz3!$GGb!ƘBi~?d.~㏖'?W˷߼y( ڶ-7 .GRJqP6 ?86J)mۖ)B t(بiQ4"(R 8(,۶mS1۔Rwc^2ݽ1EQc۲,(A@L)pT6(EѤ7s6]__z{{6#`WwwwjApҜß|t);v؎&u])M8M6l۶H)]R1nlDQOaVL8mȀ plMpd@rWȏȀ2  C1m2  Ȁ2  PJwȌȀ2  C1m2  Ȁ2 `k1c7G!i'+غ92Noʲ|L&of&MUUapl LGt:fח__]]śd~ԋ =n2˯x|۟] )Ƿl6{3NN&ht[CQI)cSh47UUEэD9ß4MSRQmQ,.8A>4ͨi"b)WJ)Um)o'HGRJ,iqHYwt>R _p&d@@d(;dƇ?d@* `d( @d@@d@J)@d( @d@@d@J)@d( @d@@c7ug[O)c?zc~S!8c)Pz;DYHn TEMhW./h;ntP{oU?ukhv̒xR/B[Ԯ+L/|}X͗%tP\?ulۛ w=pڶ-hݡP1R(1۫Fݗ)_ePmhªoZw(6w^#ȑ;XU(`ot~xL>_9 ppmm;[ | #,<%1c !=7 p_|/8$#M/e?O7o|M8*/_dr]Uռ(f1:'oS/٦j~΁pP+SJQ ?d?$TOm˔R !'͇?'iQ4"(8< |~QE]e۶m1Ź3S%1ƶ(]^p8YȐz߿4ؖeYEPEbpVc7`g蹾(789蹾E]+m%WwwwjA8OxRw<z&u])M΅z.۶-RJEWi@OJ),gAJ)kΑ?l)@@b@d@@d@@d@@b)o#~WUE.! /)XsRwa$gߺ?qOo뺮Hh4,˦( _c͛M&tz}p8x^UU]E3O&x,. h8iR 1Ʀ,ht;o+oÇ!pdi2het @?pdiQ4EJ)SNu]m7g[,^JX\3:?pE,,;v{?K)EΖ2  C@d@@ 84d@@RJ@f|@d@1nd@@d@@RJ@f|@d@1nd@@d@@RJ@f|@d@@d@@b Ȁ2  `+1c4 Ȁ2  (ϋ3coo\0s)O 8Q]4/GӇumj˺UWws 8~/p]ap>4 ALSs9הR}f^[}}+<ܶ?@__xpO>}W_ӶmJ)(]7(lj֚`*?0 ZsJ) G(7VdZ$!`櫫y)ofzsd9M2PJ WpP8;;4M_JYRu]D-NOOWJ۶]m{fu_38/..>,y;m[|\.$b]]]Mn7_n<~Z8d[} PjeSj?_g?FI l`?c rþ.= !܃C'@@Z}`  rþ. @@ Z  Pyw%@@ Tk? 9n}7mRcXmz綟7=`|xaֲEu]?_wM1w͟R=foAphw64}`|x=ZKR׭ſMSo1zH049zݱ=ϟwy0k~sj7to_l|||h^ w_}ߺ;mZ5[?o>58gf0Zs2 CSk)/Ap^XVjծCXAyռ7M3 Psu`8TqyyUy(+%gggsK)RJr+ྛ`qzzRJ߶m0kCR X,}O<v|\.8pرX,fWWWӭ[׾Xر\.'ժ1 Z˦վaZk*FA;j>'0 [ nD  @@  @@  @@@9 0)%! rþphSZ q (RR3f!{f)4 Csn}ජ뮂q_]_7ߧ}߷zttd2YmlfUJYmB?'l2,6!@28p^6FvGgfy4ˮ.?t]w޶e1}1Nݻwxqug_o8P m/;y_gbwpM@x~:^t]wrttt<~t2\4M\?pWkM9U4dr1N۶,lFe_,VZK)C)i-(oZMVU֜s9;^mZguFA@xֲe?0V[yZӺW3j.sG  @ jj@ rþ  @@DU 7@@Dyw ~%&/@GaK c'RJ)ky9}x~<(~7![l۶RʰTKC#p[RJbxwvvϟ?oǟ~9>>rM&dhv4ͪڄB#>пmDg=k)eնm?Nb\.~2\fth۶z]j)e4M?Ll>\k-}_m{5fgd4M_JY%3`4nk2G;_۶]VI4ˣ|~uy۶WOy@\xwA51RupRJ>|aRJ?N/N;[zCp ^a Su{bӖ}Wk-9mt:Nm.g>&A8L}& d ? 1 &T`$* 5<' &o539O)ҭ% 辅)J)۾3V8L h60 Mιn~o ?o?RJ۶=-a|/3t3?WG~`"3`3ugZ7@@}%@L  @@Z}?6o> 9n?6 @@ Z@@9am&@@ Tk ؼ@(< >@@  RJ_ }Sk }7"µֲ] /EoHyv`7@mγc7>wokm_w^a Fl}mS^WTtc*ow}۞?[wC?p 6E[+nks}n{ loކw?<ϑs~6E9slMkϱ"\צkπs='!&}]!c7s>~?om)?~磣۶ C%Ѭbl6;?:::}?޿|~u:$qx\cli뺓w}~?޽{y6@@ky  l|@ s^l@  @@Vr͛  rþsM  @3@@y  Pywx} :_ZK֎7oufsۛ w o>wW۶ikܧPxZl?2q-m1vg&\C<QY02(wCNɾ_^ ntۿyoʿ\-km\7} B҇r90\k-}_m{5fgd4M_JY%3F_Uu)Çm\ViGGG'm,H028BRjb(,>|Su0~:^t]w) QAF_L9Zg,6?}ZKyhv1N/y۶]`H)-7ZJ雦jiFQ@@ ȧoq#'\ki bxWkmah6[J)u?{k<ח;ZsJ)_ [)#`?Zٟ#f]ԯÀ9,m>> G3BCzmvoO#wG{d =Z=0B>B퇧LMA3Bp?ޤ#Moq#K([yOdl^zEHo%A !x6{ٰ O!R*Fx wf)4 Csno;nC) >}l <8>>Cum^dTЗL'2?6o>=}?]0C`lR׺ <#(84>@@Odl@  @@VK'2?6o> 9nOdl@  @@VK'2?6o> 9nOdl@  @@VK'2?6o> 9nOdl@  @@VK'2?6o> ^Ay|-[G"Lw`6٧n{ xAEn?Zk.u+\|[{ojmםW@2"Zn*[n_Wc^C֞'DC2} Fow Ǭ ۜ=}}@D\7}S9v۔lK}䜟i&/hwvq;R]^Wo~w}|nu<5pu=7=S3x~nzcS{<Ռ"Odlm۫}^ px-9O-Ǐ?6^i:|mWa+0nӧ !㎿]bxwvvϟ?o~ߎ\.gr2,ڶ]6M*6!CK܎Odlt||Ng_sεj۶O)lv\.Oe?L.gt:]mo׾_<{~ZJYRVn mj4M<:::']םm{ec!ѬPJYJ)}᧮·ahK)t:8:::l SpSu{b`Zk9m.t:=ov=8d u0 ~r-M\m{4r}K@#<BιZz@J]םl_7{] OԾ)\k)~)ogBd? PB͚\k>fB]ԯÀXDaz?I'2@@  ªOd?y  @X9am}L'2?6 @@jOdl|@ Vyw`_"LM  @ Zk aD7@@s"c@@ v Ĥ*q])f p;]lo~}snh0JEn!s}۰=ڳ< 0 AD7GO!-XYw[{޻f0]D7LDp ?c#rþ@@  PgL͛  rþ@@  PgL͛  rþ@@  PgL͛n9?+Y9ϽݮhK!yCZ[,8_Mb_m x(}F=vsϻϽ}?s\wx=Mn}"LpOUJ/:o*RwO-jo+wf\<\χ%GxݑC Fo~<%Qff"@@  j>7@@圇}@@`-&lc%x6:v 8$~Ƣjv'жվn_9 >~]1ڝR;;N[m* [!@$ >}]!m:@<~c>]bxwvvϟ?o~ߎ\.gr2,ڶ]6M*6!CAy@\[lvp5\K)mRft\r6]LE۶&|5<=}?-J)}U4d2Y|>Zhj><&ɢi~}W"x͔9ESJÇovZ&M,NIum^Y851RupRJ>|aRJ?N/N;[zCHsnXl}Wk-9mt:Nm.g0Fm )w0_\K)}4Wm^5M\?0*F+\ki tNah6[^d " jӔRRN۷S3J `?Zٟ#!l0c$ >z3I'2?6 @@ Z7@@圇}E'2?6 @@ Z7@@圇}E'2?6 @@pJp!cVk"ch9!<ܭw8<~?c$ڶw +w{mגs-ums%*Ǐ?݋h㎿ox۶RʰTKww]wd;w-w>o?~;>>rM&dhv4ͪڄyx]םf}k=k)eնmRJb\.~2\fth۶}x *xZJY5MO&EJ)kmϳl2,]`ivKl_u]wRJ>|{۶j5ifyttt2O;o2B=RC)_g)Ç;-뺳uЯ7LIsNMj%k)om۫i[gt"2@9ZkMe)}_m{5 CyzrXu.<NP}_k)~)o`@([!fMnOik??c!9afws?E?@J'2?6b($?@@ {Od?y  @X9am}L'2?6 @@jOdl|@ Vyw`_"LM  (&LJ0@A5"L͛o䜇}%7r5<h^_[m* [!@$ Y,Ż}~v||r9L&dѶiU)e vxn9:8L'2?6@ 5\K)mRft\r6]LE۶&|58xj)e4M?L)4/ڶ?fdh/MUu)Çm\ViGGG'm,8|8jb(,>|Su0~:^t]w)  snXl6xO}w֒sڶ]LӋtz޶b{&@0`H)-7ZJ雦jiF s2; fuse [label="Mount"]; fuse -> kernel [label="open /dev/osxfuseN"]; fuse -> mount_osxfusefs [label="spawn, pass fd"]; fuse -> wait [label="goroutine", note="blocks on cmd.Wait"]; app <-- fuse [label="Mount returns"]; mount_osxfusefs -> kernel [label="mount(2)"]; app -> fuse [label="fs.Serve"]; fuse => kernel [label="read /dev/osxfuseN fd", note="starts with InitRequest,\nalso seen before mount exits:\ntwo StatfsRequest calls"]; fuse -> app [label="Init"]; fuse <-- app [color=red]; fuse -> kernel [label="write /dev/osxfuseN fd", color=red]; fuse <-- kernel; mount_osxfusefs <-- kernel [label="mount(2) returns", color=red]; wait <<-- mount_osxfusefs [diagonal, label="exit", color=red]; app <<-- wait [diagonal, label="mount has failed,\nclose Conn.Ready", color=red]; // actually triggers before above fuse <<-- kernel [diagonal, label="/dev/osxfuseN EOF"]; app <-- fuse [label="fs.Serve returns"]; ... conn.MountError != nil, so it was was never mounted ... ... call conn.Close to clean up ... } golang-github-anacrolix-fuse-0.2.0/doc/mount-osx-error-init.seq.png000066400000000000000000000775521444232012100252720ustar00rootroot00000000000000PNG  IHDRf C1IDATx͏#M'v;"ɪzUS3<ZH.R=9+vYu`}ܫZA9tX\4|Ȉ 鶇$0Adnd/fs1Rt` @!B3f "(D0P` @!B3f "(D0P` @!B3f "(D0P` @!B3f "(D0P` @!B3f Rn@i1ƶt(+ـRGL˺9MfB!\ c 9B]wwww)ymYjgo?]e?]sjTy鴎1)c}tOtO3tx<4MӛNU۶i{WLLtbF!pwww'^o&!RjCa!A8?!tOtYW? {ݗh4B1q1s"'2..N[g$|wI4u򪪚c?ؿ?!tOtYW?LBo޼B뺞TU59c\~E'm?]e.3!w}^7wWWWmV ]OtOus:}U&9!Ç_O&A4K|F?o1l[m:?]e]N3 /QޔMԳKK]"WZ*09XL˺9} fBb㏚7MӛNu۶U8ߔ/q?tynqmn//[NOtY? MyRf2ZAƺey1m??tO ׶m !s et-dNtO 64- 0a1p&.2Se9/X57-۴|q1}}Bm~R'~p,(O*K80OiKJ}~o]z.d-8ϡ:)~K#p¬zCݴcU /'7wX_ߪo{,۪~|s M՛>{~ktֵivi<. llL/g ,璿.$`%7mlz˰o~V.׺s>lzf.7UNͦoKsΦ}55 }CK޻|K|!!>Zg(T%V\msNz]7s׵VC} ^`օ2>tĘ6UYW"ru!Ӫ{ mҜ}+8gĦߚ˷w{g]Y,>{/(VBXwc.k8 )mշm.w7f]م2˱nV3/l[]>)K̪o~^g[(W޵ZlujOy>j+ ]c{P&8Cs'`8嫰,*dU*nڳi9Ƕm~1{.xC_bJ@!/qE]@!/bN9f "(D0P` ]NK.2.9%:/ԆcB1FotOtOtNBZJUUTU5M)1ν1ݦ]]]l033~?~͛p{{x4MS|<-NU!]b)~t^,oge՛w777RI]F^o4b  ?砳|ٛs&!|,uz㫫yR޶m_ßK|+4'u]OMzdvټK#_;AHN9 1鞗s9ضmj6MӪimjysu fzBN)t6x>#{;cBbw}ߧw}ο̒gşo ?|q݅\&#n?Iyә?7>>7!\`"|s]:1.;I;I;I;I$ fGz3w݊4cJ?w9;ݡs3[\rJm"gƛv/˥s:{U]uۅtot14]]x3 ݠ% f. ݠfbm6@)?]̰LM0ZNJ2x B3f "(D0s2N%@!B3fbm6@)?]"(D0P` @!B3fr>Y?](D0P` tP-(E8=B3f "(D0P` tPg:K8=^f "1tG0pf?P1(n'\txs:c޾C_ bq/?:9aS|˿suߵiڵ==}1)`G)i `UذgSO㚯_,_=-s$8CouuǾ_`Ub`UlY .bU}T,onm^~ |_ m*]pJ3;j۶1u[5'̦s"JCn8$sWpI1x]Ou]N@^=W\3Rz]sA H _oK ,jy窾L fx_z`p[$44/Wm WoB9z ac"R-%9Dn۶ !c1朳p!,2`lfc)v6O8 f8+29vI]דcJ)gx :~>|7[SJY( f\.MSG>>bٿ1d1 @ fT߻&߿q9&UUMRJӜs?2*g8 lRRjz}4BJ 3wwwq0N峍_3\]0L&imst`{||4MӛN< *fx:im۔sNPf׉93tlR2t &9 &( !@ B3@ /I0clKNP&(D0P` @!B3f @}8>"(D0P`:(ؖnb3f "(D0P` tPgC@!B3fbm6 (F0P` @!B3f @}8>"(D0pbmwp3g.¾y.s2?9Uov-tfU;r|(QJi)7$cVUʬxb,)2Gݓ̗zp`3j> aPS __ߴOB!c`GmV!|^)mtu֬;*p%'`_׿t~s1\ip3{XwU fM۴yh6"\y8L4 f4 2h>`SܡmÝ2jq>؏eú]ÙƾU9_sڿ*8Y6ٶUϺ`q+giJiRR9{/_on8u=I)M'϶!9O̺ W.R-~~VmPdqW{q١/kn۶jV A _W~? gmAI}MX䰵;^", ?|ʒdVY~\~~=uo޼1i4 D0CliioPx9Y~]ʱ#ZuVU<%ٵT_C>ƘSJ,h28ĤԳa(l?qݐ}*fV YdՐu?ei?۬|,[}iΗM09])md2LӪmۘs{^"3p9o4|6?Us˰gUSjn[~~ w릪&ئF:kf :Yxp2 s4 6_^w?zŊ7]]}wo?yp<ii:̮<BI)5~|uuu_/`@f|!^ WWWtZm[P')X1RjZud2B7MӛyTlh[[UU[x8 û~?qv5@L |~PmE}BWJiZUUSu&? g`= .C']cmj3 w|1!RʳfZUU3w̸2l&"*&'mUU+9 d.׭k.۴MmZu_M8N\ XYpӶPc^c/AB\8y3rC!ȦCme Uȱ<7Vsdx=FB0glݐUsؕ`޾}`p;oKj6D)sJiZuBznc.`5MO)MSJm')iUUMջsj澮ջp덫jf](`͇z gfZf2 B/C]׏WUh4zuu~0u=1 8& OPP)f|om:h~0|3l"3 fc 9Ŋpc,_t੶3eg.~O4` ,x_z`p[$4U@q۪bv)_%8sǨ9ǜsLq)8S9tP&ڶr1gG": su@fn:i= gRQp&^*TUնmcy6p0 O0'bmJ)/=](G >(VUդSJ,lE0'_u]?/snTF G_㫦iWh*6D>q0:Sfd<'IqSmp3@g&it: csR'MTmۦs29E0'9G&ýeyclK/G0P`T fP^` @!B3f9{rRP` @!bm6&(D0P` @!B3f s @!B3@gmM0P` @!B3f ":+=(I @!B3fΊ1t`t8OlO#aoP湁0]#a/)fe}WU,nj^)9Ye/mVsi]kclcf?p*3bH2[m|xu. e`CJeu&Mp3e3•]YZ^<r̺ea}(M0^ڶbyyUS;Jq2]58Ebhs#.ή(3l_t੖'^,/3_ 3l럿zථIJij_(a[I}M9G}x)<%\FH`ԶmB1c9g p4r^i1iJiRjgggk@&wn<TU5zBxL) p,www?9٢RB3śMSG>^)8K6@f.ޕ6ys)TU5jR1Q9`芝J]޿eJz~C׫fe;ؕL.ݫt:g6~ 8 ^a4LMmV989v,x||4MӛN< *f#,hj6[urv>׺Z;ܬr~91/% !w}y{{D[_?*f`1XX,;Tƾ˶lYgc^|<5شUk]B>V̌Fha0zIUU*f^r5H ]SUsBu_\Sq{{û/h^1s_6{0@jv4c&sxv9X5gjMRiY5homMC=׻5gf>o4tZcan&CvԶm qU )$8u}-Os߶KVL&d2x||MӺm۴P1` ×wp^-?dKs꾧j_/q]4MӟN٥C?UCg3g\ud>g׿y9 s6NYL5 e|(ӦP& ;d@c OF i勁ɾɦJumYϦCUm.êvy\ks.ܥ=5.5\sl۶zW߄˿~۟'իۺ')K}yj'l&ȦP&,~CJi:w օgqwϾx7g9(mk>'wϪj:9j讦imSyIwo>v[r긋[w{ymY^}*ٴ]]v.o>o]%`s9s|~V {e~۾5f[jhϦUyUϱ7>.2z"},nɶ-繏~v C=۾aZcrλo0ʺu(!dվA3 mxUs`1׵}>]t۶ Zlm* {<(gj]۸mfIq7=c2 ^жJE [3T\?͏׋E 3BclC!Ƹs8bzjRjc&Άho?_yp<LO盆Aq)ǃGI]׏UU5)i!Ù"`c9ǜs1RjZUUFчd2 !^7n׶m-CJq0___FC]ʙy05U(UU=h4p}}v<C?L&a4s9M10~puuuի~; gu]?1ƼZ& ̪ee~R:ﮯ>>>Ca0<_7MӟNu۶.RJ gssn8u=3a+4 f&/T LSJM۶i1^7y;NU!ĥUUUMU3ͻfU3yMfXkV-B4ƴ줳zp8L&iy!L\cP5p8FFч^7Ygf# <'UU\3WWWJmJly4U U2t ×vZ0.0qDθ]bY&`os̗碁8DpB؟"(D0P` @!B3f I,dB3f "(D0P` @!B3f Ut" f؉pO016ؖnp 8'TfvPtJ``+baܹt[ fmori:/~YM?mVls2K^^u]OSJB8 mo g6}Az_b}9m[-{/(_z`pj(ߩ<>ߴ^C4UUUp8|u]?VU5M)M!]&۷? t[^Bߟuj> ~?3mK A444-cz)MFCÙbF0yj8u=軛7`0zb8` =Th՛?\]] fL38 <`O۫?\__~3Wg/^w? gwWU8tJ8 CaZC]~뇔ҼZ e3p>Nu9۪&UU=.\*[(&izmVʙcl fimۺm۔s4oQ(G ssN!Kp|>9sn\: , "(D0P` @!B3fXK7D0P` @!B3f "(D0P` @!B3f "(D0P` @!B3f "(D0P` @!B3f "N1ۖt8It %SXvN0sNpf,^n˗pʜ'e1lY֬ \9y(D0\B0jnm/g5o1aqB̾9cmzS# dx 1c!52p_}ٲ??oo}SJӭpB;2 !떃mma؍hvs$|srV9Զms!87hyTt:MzΤ΄ 2 [M&RSUU۶m1cs_)`nؗ`^۔R1 6 [}G1ƶ&RjfM9̰)q]u]ORJӜs?2*g V?b<_5M_BS9̰׏ՙPM0Vx8L ̎ms&aA4tZ#aimS9C %asXḛUY?#pN ]"a/*gp3L(%(D0P` @!B3f "(D0P` @!B3f "(D0P` @!B3f "ag1M"a/8 pblCmf<` h3%a脗s!Ό`b!sN4K7W߬]CooBm)Moz~۽3nǯ 7MSk4z_OO?OR*gr!䶭6Vl;)F(8 t4} ~RUUft[a% p&3|?s]דWWW^73aV1'E  wUU=777oF`pW$- g2~7Fw777z; >̂fv3$Μ`u0 ___p8|+fWgf>Rj`0x?wWURjM '39cVU5z~뇔ҼZ e3 >3NsN))IUU ft:MӔs1clfL4uιn6?; i2p >sNe.f,K%390 d2pDB3f "(D0P` ."(D0P` @!B3f "(D0P` @!B3f "(D0P` @!B3f "(D0P` @!B3ԥpblC!6.;qVPYϵ=N.`J眾&cT{PeS{ȥ]"QLi|I6AS26U,ok5ζdsj٪,wkPfuQ  O-g?5 ,)*ڹ*Yn}W='xv!`BEˆcY-u̺>7?_~*^8Q2(mS|낒c9WU,#ϙDx[b\jnnRY\"dU!ǺvmJ]mJsMC=w6@ >VNyic cywվUp*}Nj'vNJ@gO0pC1!lf5ߖn|c[UURzL)5&sF5sfX˔RSesmʨg0/kx|4M M@0Zwww_<<<\?>>Wg B8 kd2/\2;J .`Mie`ڶM94er6/`ra!L0Z9gpD^`ld` ;Q9'`+ ` @!B3f "(D0P` @!B3f "(D0P` @!B3f "(D0P`bI0N3px'11ƶt;Vn9]RG0'P/3?]6cΥH0_m,~sNYo}WlZYo۶:f_zRj™lh``ׯ_|[8жJ[sαmjy8^W~CׯիUCNYo]\U??j^7iJi:g2 o߾`0XUUsuua0>>>>6^a8q]<!2 e] t94zBzsNMu=z7?zqUUMJiT3mCȄqyd0܅_|񇺮ih`0zb8/E0alI)f|!/]۶uJ`00 fD!g8" Os9`)r΋3]C ϳcce*KvYj}W6gRk;8{ub](jUʾ*ڳ_מu߷2f[;U\xyXpʡ.,1OJYR VyjxdyN91qY,?g_OjUBU_w}-??fy_< ϳsWצi^ϳy</FѪu ܌`) Vޢ[e:~z||g4z=Fˇt:4͋Lp[la;ӯںW`湪t:ҶmSj2|}xxp;ݰeNnK0Y &a@RJrEH=O? `` O0pa"Xrfep3+ p);vgI7<1 `PM? `PN!h@$ wP !{RLf^_l 0O0pUu!G`O0pl6mQJ)N7x- @?ӟ?Vs-\B~?Ni楪r. Vdsv0pLS2ã #a@A3P Jd꟒ @ @ @ @ @ @ @ (w)qP@RJmt dG0D0D0D0D0D0D0D09bJaxA3A3A3PR)@r~X꟒%@ @ @ (FJax3A3A3A3A3A3A3A3P(dA D0n n.v@+o %RJmZyJax+Ƿcd?C͡ӝ-XԷ}=ڿgp\uÁna[f_skCuwP{vDsv{zrHw5][8Ծ8%d9w Ld_b;ФK9o]))K5 3pEV=;¾n/˾.}9uNYΡ{}kOB8\3׷oNʵgҞkg{"l{ p+N4KtBO3J)po~nJJ)oW~lC7\b>98iu]m'Ɇ6"᠟~O™tӯ=}bnX|O>?~-x<6M,GѺ69g=2~۷퀷 Q)\i՟f_/d2Y4M3~JLS2ã '[Vu]^Fx<^TUUsj4|>y6=h4Zm](\`mH۹32/KUU?Yh4Z><<|矧闦i^ gI0ëyw$_u]6sUUtmۦd2y:>oFU%.L0{)*1>|Z9:6ML&_'ɗi3\`(&iZnߛj۶5Ih4zie4-7S PR9WLU4NivoCfu2W!䪪ͰTUUt-F0MOsNM~2WnN?%S0<8ݰeN& P4! I76 X'.A?%SLffffffffb圝)d꟒%@ @ @ PR)dG0D0D0D0D0D0D0D0+H?%SL8(fffb6@O?%S0<       X9g@)dA D0D0D0D0D0D0D0D0D0D0D0+FJ)i%N+s*p|tAP0<J     Mmw_l_o e|kn 8`/}FlE_"x]v_@->Wp3@RJmyC:~7@8>aVz[owSڷou>sszھ[[92~enC9T9w۞SxJ5dC?5$] {ܟ/՞ C~&79vzs_k}m7v^(cc߼!-xq7ׇڳo>NO=/ź]"~~Pq]h7@ݞ Chk_(^S埲|Kנax e[5zߐkXO;KҎ}g}thھG-s?wOuTUý)5R:wk8sCr|ʐSz͞&8^[Cmgb_/S߷Cok[9T o(3ulHO{|ʷزN{p-N.^sκONsβ.nsc.Qz5$p3p2O  X)6 ܞ?%S0<  +N a?  ` ` ` ` ` ` ` ` `(VJnDQLS2#"""""""svX꟒JaxA3A3A3@RJmt d꟒ @ @ @ @ @ @ @ Ps RLS2"""RjQ?%SLffffffffb圝)d꟒%@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ PR)dG0n@pzxFrUeߒ̀8S2""""L{)zDEd:{8`wXk=uJ)9gC:Tۣ[ۣ䔟OYg3hgw9;6/5z?Jaxܝ0PHp=e^圻ߺr.59m{_O3@}p>% &)2p8g4Ckdž bE69۵;-wKpw}7}[c2wZǩ۵2Kp]{{߲k%knס|]9/q[1C/""Rj9S2#@rWO?  ` ` ` ` ` ` ` ` `(VJnDQLS2#"""""""svX꟒JaxA3A3A3@RJmt d꟒ @ @ @ @ @ @ @ Ps RLS2"""RjQ?%SLffffffRnkfb圝)d꟒%{)'?6@ 3RN))xϚp{98iu]m'Ɇ6Z)d꟒Law~?~ǿ>==n\xhf9u]Ltd3D)r]iVUUUre\lu2,YmmD0S\z4񢪪j>sWצi^ϳy</FѪu \`,2/KUU?Yh4Z><<|矧闦i^ g̔#_u]6sUUtmۦd2y:>oFU%"T圻=fۻjR4b2|L&_Yt{x:\`0`j}o>m&庮Wih<:[Os ;mmG9Q۶<2뺮כP6k9?u™T;>"[(W )rw{_ \`ݐ*a ܂` HR ffffffffffffff/i~^_#IENDB`golang-github-anacrolix-fuse-0.2.0/doc/mount-osx.seq000066400000000000000000000032701444232012100224010ustar00rootroot00000000000000seqdiag { // seqdiag -T svg -o doc/mount-osx.svg doc/mount-osx.seq app; fuse [label="bazil.org/fuse"]; wait [label="callMount\nhelper goroutine"]; mount_osxfusefs; kernel; mounts; app -> fuse [label="Mount"]; fuse -> kernel [label="open /dev/osxfuseN"]; fuse -> mount_osxfusefs [label="spawn, pass fd"]; fuse -> wait [label="goroutine", note="blocks on cmd.Wait"]; app <-- fuse [label="Mount returns"]; mount_osxfusefs -> kernel [label="mount(2)"]; app -> fuse [label="fs.Serve"]; fuse => kernel [label="read /dev/osxfuseN fd", note="starts with InitRequest,\nalso seen before mount exits:\ntwo StatfsRequest calls"]; fuse => app [label="FS/Node/Handle methods"]; fuse => kernel [label="write /dev/osxfuseN fd"]; ... repeat ... kernel ->> mounts [label="mount is visible"]; mount_osxfusefs <-- kernel [label="mount(2) returns"]; wait <<-- mount_osxfusefs [diagonal, label="exit", leftnote="on OS X, successful exit\nhere means we finally know\nthe mount has happened\n(can't trust InitRequest,\nkernel might have timed out\nwaiting for InitResponse)"]; app <<-- wait [diagonal, label="mount is ready,\nclose Conn.Ready", rightnote="InitRequest and StatfsRequest\nmay or may not be seen\nbefore Conn.Ready,\ndepending on platform"]; ... shutting down ... app -> fuse [label="Unmount"]; fuse -> kernel [label="umount(2)"]; kernel <<-- mounts; fuse <-- kernel; app <-- fuse [label="Unmount returns"]; // actually triggers before above fuse <<-- kernel [diagonal, label="/dev/osxfuseN EOF"]; app <-- fuse [label="fs.Serve returns"]; app -> fuse [label="conn.Close"]; fuse -> kernel [label="close /dev/osxfuseN"]; fuse <-- kernel; app <-- fuse; } golang-github-anacrolix-fuse-0.2.0/doc/mount-osx.seq.png000066400000000000000000001443201444232012100231660ustar00rootroot00000000000000PNG  IHDRsM09ȗIDATxM$I暙{efWt nws#+]mȁ 9r3Z|- f@.b@..֙9 %.*&**^"yBe{[D32f1*`M@`M@`M@`M@`M@`M@`M@`M@`M@`M@`M@`M@`M@`M@`M@`M@`M@`M@`M@cwbݱq?C2ϐl?,rGc 9BCK!SԿ2l?z]E /0!S g?C67k۶1v)c3L!S g?C674M3j۶.-R!M3d!S ِ#6܏.|4G<RJ]!,%Q6ϐL3dCYl6{3ބB6Ƙc9\€꟞g?C2ϐ { Bw}GM!Mjb]|?æ2ϐl°(',nFm]ڜs%'6ϐL3d°}w_Ftzuvvvu]`?A?æ2ϐl?@J !7o|z}}}>'Mӌ~i1o٧S2ϐl@'4ޡ?Ӧiť``K{!P,S-9Z}]0u*ASQ g?C6_6,T4͸iQ۶uuUx;?RSϦ[6V{?C2ϐ { ׶m|Ũ ^hSX)ϻ кcVoDL3d!r R!S.̰k9@z빏X?C2ϐ `̋g:0=S2ϐl_[9Bu ٯz۶mwwuc8{u~}mScuͮc>kX?}<5+ xV =B&@yNs|p­Uq/m!ֽ_}!5Z~}5Hl{>d}MηOnO_7m~q.jfmFs kr9,_]g;V߫{W?Nɶ]Smqak>;huVצv5~ּ|5^"+_^<7ri >m!Z}<cڻ0~1ځZ91~xM.)6aSM:F&C~O{];!C_7zjjD摓~NNl6_@%m_lƼMs/<~m ֵ>,Iv}#kʷwkmrX\-a}Schv}plOkrY`}n?u|@x9Oᡋ&8pF#Z|%]W[wMljcWmߴ&nӺb܇M0Ǿ־H]!ؾu^M!#W' H>F%kP8EP4EP4b.L3d!S P_॔BcB!8M3d!S gȆ\;, PUUMUUmJ1 ?t_WC?dC`W_꯾ogi&m۾{r.Fſ~~<o'w?y]wUU5)6R9ԿW_RԿWhdcs1rUUm]xcH)5u]M&Wgggxտ/_P°~gFl&n|>4MSwiC?{?}XbR ތtz5f7h2X-0?PoAKCp/xF?,}X v\z!]'_>P0000B8,~Cx؄POs\\8!Q ?HA_P4-4Ϲ+_ ݚt{sQá?CH!?-𧔺늼.Ca_./MK˥wSR 來P$CjcBm >r?C!Gg1 6pYo'!h0&h0&h10&h0&cw>L3d!S 6EP4EP4EP4E9xNS1 ( .p,!S g?CM@`M@`M@`M@b9,؅BY@ <Ԧs_w~1FAXdm .9'P…FS):f?C2ϐaq~TcH!Qlʸnmp ]niۮ}s?@Y\]]9m[w]W=8u<*v777|>i^\/T?p ݤiQ۶U#`Ph.Sԩ"=2ϐatdC#RqL=2!SecE3d!S&c+#a2Pϐr( ( ( (=2ϐaP4EP4 P;vX?C2?l0&h0&h0&h0&9{`L3d<M@`Mc8ϐL ( ( ( `r0X!S 6O>EP4EcE3d!S&81c]),O}sN=TWY߫=.w!vmo;sl׶k]o}>RarвҬ u6}}l ޯ~_]@m `{̵- WM; k[_w;)OXFPkC `O]U)vߩ} ͺ0i]AԺnZ#kM=ܴ٦[w_>Q^F-Zo׶ftCyȱsszf'Lszeeϐa<>SfNˮQ0aCE`|W>vza}B1\'˗?y&e] @LsvZk/`/6!J'8q ucȿa@`'1F3v]Wr1sYGpbso4cRjSJb ( D|>^TU5zBK)!P]]]}6sWM9_@I`Y,j/f^e2.%" 1׎Sׯ9RSUռyJ9wme$E?l025tןht=ߌFj~ (`^ܜMڶžh߫@i*OGEc8ﻻ4M3j۶`?l0k.S~> bc2( `r>B0p<P4!#r ~EY!S<M@`Mc80L^M@`M@`M@`M@`0@9g,0L^͓@`M@`0@1}cQ`M@`M@`M@`MsR<M@`Mc80L^M@`M@`M@cw,_]'vmxj>h+VC.p,F`y&F=-D1nyۺcZ]o]^mھW8Fu]cvTۂuSgTڴHަO m$ڴ 8Eu6LBu9S Ϯ۾|ZoJqy20L^Mc޴^ukvm!:1FxLM3γx5`  a.$P#M૯OIP@VmF@`|'/^d2zRj-U8eu:䜣x( "<0ԍ9!@Y`0@FQǨB1c9 ϰ 8IAiIM))n1MXpR rqnoo/u=!ܥ !`𮮮>9ŦR/( U Ţ{b6G]KD>eW6zʎۼ~sJ)5UUͫڜsߦ_F Pn~R3hT-~@ `𮮮^ܜMڶž (־WI4uuU90xwwwiFmV}biR9׾ ϟ [LwL<@`0@9g` y6O>Lyr (  c#@6EP4EP4EP4E圽aP4EP4 P;v>&M@`M@`M@`M@`0@9gAg<M@`McaP4EP4EP4EP4 P{`P6O>EP4 1v1}N^9YπC Mgcw4si=}or8`O)6A}F-[n׺6m@CcO>?ePGÓ?S$ N̺0ǒcw$!!W] !B1g!`O]U!|8eTկ7vtupHPz.^Ob9朏5`t~6յkmm}19>K5mۦE&=S / ۶mҢ.~،#鮯c!xc)n~BpyqqjN4`譮CnacޗvɵmDצv=ݧ|qss3瓶mbu`pMLbB⯤Fd3]!Y7m]?ňu]7UU5UU51. 8*C}j^9ŦR.>flf2܌FyUUmι~&2bl:.c;\]]}r}}}>'MӌSClq_SJj^U<ԏa%U)t>OES9<0(^ׯ?O)5z<F#x%{d䫫󫫫W^}7o~_ogmGMaqiNyB&Ԍ۳O>o+.>z~EBz1LޜMڶRxc|`sJ)WUu݌tz}vvv9ϧ!0nqi1%v:^Mӫx|[yxc^=#{țb_<-`m]wv69??u{{; !x|3ϧ}s!jL&7ggg_\\f_>-n.NB4M3ZߴͧwwwBL&7Mӌ#_YUUo...w痓fi4+@ς !4{7 V8fo|ht{qqնmBH*G&ɻt:ZkRJ9!S%¯Fl&nU?ecY(f<N7j6A`<1!h4!׷ggg+()G)f4͗-//MT>91.Byu_?2?c1+rJ)/_\ x`}>說j)*_p-RJy9 ;vBk Oo1Ƙs~ GW1i/9䊍os. K#~\|k, Y ; yN xtB;8u9gȀy( ( (^;v8EP4ExSP4EP4P d<M@`M/ ( ( ( e h0&h0x1}x`M@`M·9O>l" `;'Ӷ0lu㗷j6emk[6D8a났MϺcI9+$ ySihhc5ɇ.hz* vzj|=GY 4p[mkMkz-Ӧ5v?B02X{'(q[kjmͦmӟwHl`95(_Pc0V}"@y`駟}%ƘW_}]l[x|u4/޺6-`ٔH&c/_dWv==~/իWtww7F7趮뻪ڔRۇ`EN^d2N bjc1R[uBF7z<uXi)6/pO9VUՌFB8;;>眚z~vvt:}3nj.8a0O;su]ߵm;n6>;;{=L꺞 p`ۡ/VRj؛o]__2LSJxsJj^Rg|&!Ƙsa1 2#ں]Uq_b;W8a0`0VB<nc!?]99P1:`%e)!s^b/'g%`q{@Y` g+EP4EP40X9g0' ( 1vqx?l0&h0&h0&h0&+5aP4EP40X1}>M@`M@`M@`M@``圽@x?l|&h0&+ ( ( ( ( @͓Gcbݶ_LX[Ї\+眄`C>vZ}sRj_FwQX;& _` 9XVm z}u`3/KءK$ Ⱦ )h`p2n[m}#}-BXJZkӶ]W/+~W ܾaPd8L8_'J{Hc 1Bȏ-g``&:_}կ]/`aW^ G `˗/ŋM&˺)v1:n(}>o~5@ `')Fc9 prïs꺮9B\p``bp"m;j۶^`)(f $xRSUUu]1tHT xEK)c 8Y0yb]UUMJ.,߮.L;o뺾zRjs]|~ ?=kf|Ec `suuݤd~'N;|>ïso%c\FMm[HI6O>4MSu]rΩr=p`sK@`cP"/l`c92y?l0&`-#R ( ( ( @͓@`M@``c86EP4EP4EP4Esy( ( cw>&h0&h0&h0&h0`r^` 6O>EP4Ec`>v8Mɹ+iϙCᧂ4)8HJ^  v^޾M Pt]W_Ӧi)uA׮"Q,Q},vm iS y4aȮUmSOp 8Ȧ5Xm[]#luئmc5 8HuU1nBncp늤͇F%!`P}0a f 0ꫯ~}>}^a/m(^|/^n2\u=O)cGsj,08sC% 8}01}*c9ƘsB0'Nc4$ئڔRXGLp`:$9}j^i9Sx:1}x`K&Mӌڶ,p`KK9ԇ_.$XLy( `I"@a|Xa#@Y`M`e!Xw<M@`M/ ( ( ( e h0&h0x1}x`M@`M@`,cP4E9@̓@`M@`@bݱ( !?0 [寎~~/B!{![_mmsN!s9cw= vC]ӶmZ`a $O.!O|v=˾yNC_jRRJ9rٴKN9|'郐Co(qRJ,`sBhSJy!ӻօ<I|̦i[;۷nbl<Ǻ2k\?_o6۷u]=O /ϪjSJmJILY ar豼}[]n_{?6on?tDmgզu[vvVsh?OMMv4Mu]u] o?vXa,Tv̾{Z=wǺF{6ҫ?io!C5I^\Jy-vmOڶ9iRۀduСv4lzܶ-@m]ןumƮ_;r!?e;֩y ܶmHmw1^D`ךdFm릪&إ)3{꺮 ashk[zkW}u}c+Zןu`KX,% !OonnK y&g]0FdϮ_I_w#*) lv9L&h4],/{n:}F[=u!C¨M/3R 򳫫Onoogs\r)g=k~mM BhڷڟumWFkoT嶷MXڟ]E~4ms ¯gH=u]Wm.x< ܇ܯյCux^{9|>ݨmۺ뺴4g@pM7pZN- d>zr窿OOy֝cwMӌ۶m[-¯Bx~ DJև CuK`scֶmU-¯O~=0AX1,}ܭCG7mn_ Lԗl;~5l[-qX}>l }sF+m:̶v6O>-ۿ}uW5rϻ!8Q)J9*/6Ǻ6_\g=m׍T{ȕ׍2Z۬i]jyɷ}]޶]L01ض(T=Pᐶv-n_vK:v4||ok7"83cco c%` g16 ϙ .//?O)UU5?v_KF7ͱp_06lg? !|zmixqY>Ǧvzww7jY|~|eiOїfW1Y<ݍu]v I~mjBu6e[;_oyo]>?$3:b 4Mu]9n_ݿM 6Y ֝wM߯¯mms|8q$`s9s|H;›j뱦歞sMacM 6j]c]ԏ|L06g& !}fh];N>vhh O`lsإ6[itHP浩ֵi>tm֭յ婙sn6W]utױt۴m]$Q 9֭SCݶ߷ܶc]~cO?vOpLh*NLd;#DayxRBb]!`ό`͔=PUUMUUmJ1 !ԮE]׷׳o//__}noogMLڶ}yINPUUSd2?_u=&Ԇr-ߖQ1c9sJ)WUVUՌlr>OCa46M3꺮|QcH)5u]M&Wgggx< `BgBNRjǷi!|>mfsN9s4E1/Ev[]J1f!`؏?OӫWwwwBL&7Mӌ۶|??NJ[L!g}...NWu],"!`4M)5]׽i6h4xՏj۶ !$S yn~~1Gf7_\\|6G!س `»ڪŇn45j>O_>?cF5v:^f7h43 `cCX^UU;;;G~u]W-ʏ~lݢ}\]]x<Fh4O)uh}XL 1\UUcl꺎hu]Ca>|g{hs!rJM)KWC.}#_RBu]7K x_?V[}=ZUv.׻/1n}Hx5k{Yp5mZau]W&UCգ( (⪥ ( ( &%h0x9gȀy( ( (^;v8EP4EP4ES]7{Jp1.~r2S"/ P}8J O?pL1~|ܗ `׻Y-c_z)gOg6-`ٔHNQp 6xOv`~jc9v]WNW?|'/^d2\7?߶_?Z''K4UUUt:~4u}WUURj1`[zGr:^/x;;{=NߌF> F'Fp~ځ!Nu]Nlŷd2zn$p``LTj69;;{}vv^M&7Y, 80ީv2\ozuAމ16zNO&ht]U]J>pj`s!뛺U]7)~WގqR,c2l|i۶9cWUռ/T xiQub$X1v+~ #iR*1Fp`sk%EWy$) wr)cN9,/P EP4EP4E9@̓@`,cP4Pcw>p<0&h0&h0&h0&s ' ( cP4EP4EP4EP4P d<M@`M/ ( ( <'1.|`x"۟ի<9w?}N>vkem.۷ [օLzyϦX= "FF-߶ny62MmfS@loතY޶tuۍ 0l|c6\n)H[l;~lkgsZgSj㼩}͇D~.`Zm >H Aӱ֎z舤ucS,ic!E61G m:lQPk]-o_~mϺ>!s(Cmy\ 1GsSOaӹӮ)csomsHlp(v)A|0ԃ/``| b97|'C6p˯?XիW?N);j }YuծL<' sI !;SuU9 T:l|vjvԶmRX Bϛ5i>r1s4x`tss"إr[:5q ͛7?1vUU5)Rb9w7Gd@ RSm]7u]SJmιo/#gKN?i!7]`tuuݤd~'BN|>ïson4ͨmGةi뺔sN}u csX Nryً)P41 850&TcP4EP4EP4EP4P d<M@`M/ ( ( ( e h0&h0x1}x`-x.LA`'+ .0bϙ m`$XTXGN1>X C_oO?_7BDcs!?]bݡ<_??SJy6}Rj#r!t]Wm+|E|>6󺮛R;L&x<߱ 271\tf4!XXD:0>0NL&ggg...f'U]ipl/x|5Nfw?;;{5L,fq` ؗd u}3L^{~~x|:$s%)f<_O&׳t:~2\F몪RJS!9clF뛔R? oG gOڶr9ԥUUW~Fڶmr9Ƙcʺ_/d@4uι.߭rÄ_IsZ\6#p`|`߱@y@9,/p`M@`@bݱ( ( X'E@`M/,0O>EP4Eŋ1v#h0&h0&h0&h0x9gȀy( ( (^;v8EP4EP4EP4E9@̓@`M@`@bݱ@_ˋnYw:jP OĎs+Sm^m)<H;`{J)}. 1}Gmzܦ.z}GzL6=F붭Ϻg>כ S8[B04Y޷z}ne[}F^mj˷]8P=o.Y.99b zm54J쾏ͶvFp 몔Rn SnTjw:{HrpQ^֎QW֍pZg]_4rmw*>6mo ';?#.( ]v!>xpC]samJ??78a<]}G6'J{ ,b9=`|W>v1/޺GCgZիW?z=j>/_ɋ/~7L.뺞_CdcuU]/6:ٟ???Ks?%5>w|@p>ƨs9G0p 8Q91 u]s!Nzmێڶ!X B0 p"|>?K)5UUu]c^,&LN nnn^R1vK_ !`7o UUդRJ""N06+`ltww7ifԶme#, K9ԇ_9[8-069 $ (s#p)P4{1 8U0v~L͓@`M@`@bݱ( ( ( ( (^Y2`|&h0&cG@`M@`M@`M@`c{~<5^d2N 0u]Ǫ7f<ϫjwKi)6/Oi4ݤh4f^O7`vjH@u]׷z6}wqql6{=L꺞 "=<xht^M'h4.Y8509B[M]뛔R? oG !ms1vUUͫ(N wu]W-FcJ&iR*1Fp`sWˋB;9sc1x' ( ( -n@y( ( ecG@`M@`M@`M@`@r2P4EP4Pcw>p<0&h0&h0&h0&s ' ( ( ( ( ( ( ( cPxa`r~' ( ( N@1 $h;p*X9Fc~yj[l_޶`1.l @yHm~9Z=nw-8 j}n86S ɡ#V4ri~iZ\!?rkW}_w[Ӷ}n;d?63 |@`M/ Yx9g̓@`M@`M@`M@`M@`@bݱ( ( ( ( (^Y2`|&h0&cG@`M@`M@`M@`@r2P4EP4Pcw>p<0&h0&h0&h0&s ' ( cP4EP4EP4EP^ !6sn 8iOl!غ6 ՑN۾_ݶ.gնTM}s2l1p~>Ydm m.9]ϦcVãmwHk׈m p?0/ m |=.=,!h?s<N5x~T:n;>VnwMm\]:`x`* wiۮ# gp``Iq(k}>>aJn ?O7L.뺞c>v`ٶQ`_lݗ_sT N#M9!K\*c9ƘsBB g)ofclSJmJ1vB2 g+9y]]J)$x|sZl),* (bQF]^^~1^ve]KD>eW`@I޻6_"RJMUU)6ŷ闑`kׯ?O)5z<Fb0Wa ݤmۺ*qZ`@Q l>Orα}tww7ifԶm`" Aj.S~>E bc2|0`rrDe0( <#&MU>EP4EP4EP4EP4EP4EP4EP4EP4EP4EP4EP4EP4EP4EP4EP4EP4EP4pRb]۶?U`?01.眖!~r sN*aҺ0߾+Z޶.ڶoyqu?6s} H!ͺjS`uP9!6cvu8n/HcK(kպS)MYB}>UPkSw|}c6pT0m"mζ]'l}Y 6-]kϦ=&!P {*]VèյfӶ]W/+g}R?5p`}Fu=?vc%O?K_c,/EK_:f㫣gcGnSJRMN |'B.nO/>~u?i:g趮뻪ڔRۇ`9/ `ūW~4L.Om<nRJl6{3Ln񼪪+} `i)6/TM1u=?;;{}vvz:F}N v觝:%NWUUM&볳Wfד䪮i@ `xK5mۦE&3ɒ^Gk00#p{A ]@m W3\a~20RHal4ƽ3w0=uN[8תJ1+̈HLשּׁ'0N_wsgmۺ o~7 SJ)%"`?ҫ&BSB͐孄./z`ߎZ[!&qtj=96|2h׷=žsۮî청mC<1uv[?߂U6z?ϛmmCio 0 'o"Fx^tjҮmWյ,=?Rei_~êB !`;_^m{kBmUKm~]c><<\C!$c*6zNشS6eoTc9ձmyhCkz=s[eڮ5V^gЫ !R1UUU?YX 5Sxj9e}8{>>iy]!vU;0 oas{)\>O{޴iY]׋mۺSJI, $&b>q<ŢN)c %d2fmjB0Xzxx~||M|>-+B%Nt:fbѴm*% eUU:ZUmzήOm|>/b'0m_`P൹Զ-zYZZMtH3PmͶ6[A-d9tm۷eή=sj)?GEשz=_rc=g 0Xf`εymu{7ڷ-9T!y?k:d9}eWL)S՗ l8&Wu϶j hOn{vyyy_N wٟs>Tg$jl>쏟x}}h4ML+\ . `}[rL(YǺ]aֹo*%>]/ju]zCUUx.!/55߬`NW1S%WQ@Vס .v*vj0`08 njv8!d"۾y/YtmmmǮnl{JvwZ綯̀)@)UUU5M3z^=4M3YV~ C 6+9hS=j^S8;m{Sc[nnmX,RJ01.RJjm{䥘/=0.ߩi:[vh\*NBXJkbi *h]Q/9mq3*1: "xvMUpFktu C`@qV :N^~mZjϗ|]9c.ŗ/W~ Ǵw\[~]-nn_]eԫ-5*mVlZ?f[׶VmgWwwկ]69B=u۶8>umFe !;(_@v2_߶_sCS?&@[o*c12}}vs[.{j(u}LV]* (ھwyJ|o949T+`:sO؀KS m)aݬm:=P.O}};4׵kP ,ZPڜʸY_9&f[OFS\:_ߜ:`zl7λoMcrsgU٥ Rh[mlP?wwcv-~j)W|Jtl޾)}9ymN[T>0L:$\*S8@&^\]׮0dp xIG,ktb:~ Dp"kmmv̾s%qS_K~.𶪪m:Vۭ%% z5clߒe}嗿z||nf_%{vY{WWǯ۶}W6o{pey7mn6mþ=Y^vo[pێ?vU}7/?oys/p-X߶~̶lkyS.BmcןlUZ . (Үcs[nC M\Uӡsǚ`Zp%™3p}[Kv:+UJ) K#js`k5udžgei]Slylͩ_j( º"@_O 3v}kzCu]?)c,5K$ؾ5nW%ketLr:ٟV7SKb //P1ƶ!` `@\u=zclC0Adc5/???[~X P!UU tamzcUi`0xӏ4ʹiY]Z ,RU-cz4ͼ?FcF0a^".Kt$PMx<Fw~iU%*.[ !Md4]]]w޽of4ʹ*TёUBhcq<~g^]],CI4"T ܤl:ﯮn~ y__K{^oR*TPFbrtG?p8of *G$Oh4fͱS]~p8 w~͗`N,v9w{w}}O*i4sa- _^o:RJU]1x<0oF~ ЮUd8ޏFhtK0 !Xm4cڸݻ{ݷwMUUb)ƸXud T ewvi]{hftfpiv0 ; VGUB:5Mzyߟueб!jmڣ G8(@>`@Bi&Xc<ꏝbk۶N)SІVe5U(plpնmݶmlUacm% aF&TW¯PUUX,?XR ))P5Y5Y5Y5;J)  Ț (J5Y5Y+K5YPPȚ Ț ڮ.Y2.Sppa`. e~*wm?몪RUUU+8R>a" )Rk0BhOǼUROV!X".0Aa@@UUJ7#K ¯~YUWnF?WxyNџ{}7˾~n{^Czn~=<͸Eqc{PUp֦۬:e:緫9SqkAŶPl[m1?¥S9yjH6羰׿m}k!~mۺmP}Z YmfsxB򫊲ڏU[ױ!qo])RǴYY쪖:Py՟Cϥ_7LlgT@&l2msfmj: 6tu6kO[m?}UiǮ@gW?M;Uhz´K}U !<jM`!`fԶvP[s)cz ~nTl[c9v]Ssٱծv6ﯷo}}krk{T]sSSy/:Ň~4ͼy]Bc\#7PkvU,VѭK|>|dz٬ LP\ÇFht7 z޴EJ)$``p&?7LF J]@J'3yxx^-?{ŢN)J2W{UU98 >Эt:Nl[,M۶q W m6۶sbgĵr5ŢX,e`UU}Z?`1/&Ю~s}:f[ΩwY?׶m~ zEW  8B}O5Ω}וꤪ~XYt1X^=u>mV: ]ٮvvo)W?Ue/x: @UOVmzn^î ]2/ykl#W~m ?sscVm0|K[֧?;-o3@۬ ;5`b`kjkw8Dd3ԩ Fr pfevsPS xSVж)wUwkS)}./H)oiQN%lƾv6<~[;5\cNl`Z=fq0/m*y9Szb[" u޿*K'?$BZ3^&]J?!\S_zicZ ^ ޠ鈛שWL&W'߿W_}ۯoonn~:^Mu1.V!ٞtD/hWPu(:SS!Mzgl6zp'MWs[$'xnֹ?B1.꺞zp8RJq>M꺞+V Ɵn W^5 c۶M4bѫz6>㏃ikO|jhv7wsnk|" KXRR1Η]۶M m61yF2/į3VmVZ߶}o?ۿiWec¯~}_ g| Sݵ}R\ٵV« I߿/o??~>7M3Y{ 7C]z0~zؾkOn߼J Nz]j6ԶmR 󺮧ML뺞ۗZVzӥLU1mZR xc]/l BH)j9 m۶e"ƸX_m;w^)wٶ)ovwP\չ2[.X5M3z^=4M3YV~-~mJS+6;ej5Ž)]S(cw>N@^B˳V SWJi}U-v"}>Jk_֡ۯ* ( ۵)_m4 .ֱkhm[m[oXb-]mu֟OU}ϵ+0s/VJ);x97Axy/5Y奆!^Bh_]afѡjw[ձz9d[5m~CA0 xq Bh7ìϮ/o;m1˾7o;on;S«c;nkkuC?4-'P~(d:50lwΩǞk/ x5DȲݱS.ծַ~m5?}J;ܠimi]]ơ. 0xA.k - Cڶkj1iOi멁)گo7 0xҶF9 E{ -{AO}zOBXL]3ϲZ-L187x1ƶ ! $:ʮkW?r,?TE !q㢪NTha5/?/޿d2bFJ.ƪy4`p}}ii4Mi?x]02oڶ%S@9B+R1uhfGxRJ1woO?!vx<ϾY`if1E! [0o~ y&l:ﯯ?fAUU`0x|xxbѴm+wF]NBG^__MLWX劐)l06M3*U]Kv9q:ySU=^__߬E]UU4//?y]N___Ӳ l4|U1V 306|+@6讪ip8N|ެ-˿s!kU`~?n4z:`@G`l ƸXU5M3^7zc*=O&(c?YԶm UURH)7G?ѿ>cL1rm U %"-+_Vܷu]/VZ\k?jڣm'[_{G~H)է27OdZB֦= @5S JW~Z^le/xc`xM0UȚ BhtȚ Ț Ț Ț RbPȚ Ț BhtȚ Ț Ț Ț RbPȚ Ț BhtȚ Ț Ț Ț RbPȚ Ț Ț Ț Ț Ț Ț BhtZuחRSv\8<_dM@`dM@`dU bR:qٵоsxs( 0^|B/ڔR^%UU+6v;ue_ym۷s-E)3,m=*e}z;sU~y@`P_6+Jx6öҕ0Kõ+L{![ISvYZRdžRۦnV~s:SwsW7'Jd| Kֿv̱k}Wb@`wJC!˳Y5(v5@RJ`wހ @`dM@`dM@`dM@`dM@`PBu+?G@`dM@`dM@`dM@`P@`dM@`PBu+?G@`dM@`dM@`dM@`P@`dM@`PBu+?G@`dM@`dM@`dM@`P@`dM@`PBu+?G@`dM@`dM@`dM@`\BBh%I)@(Ou,1EU}:"IFR`E]6l3@ws۪c6T)zUH-\j} $,ŭB)aDJf@ydł&'i۶篷k*㶵@UY'0mŵy{4Ck~țcx Ȇ m`@vB*[t`_>S}냅/~m7?߿ t W$co}ٿۦi1Ų::w~w[2ȅGP2#ؤBJ).RRJm: pA`{keXE b%8@5pN㼮mBH+!d.93<0=? !1Bhn . `ں1Yq B*~]?1Λ4M44ƸH)S ?~xxx7L~EȮq`{߿{||f +E1L鴿 RJau8 `l6bQp`{mۘR+%sRJP,?)j- P%pap)M !]b@y`GR p`G~\.Y5Y5Y@RJP,?Y5Y@Bm}PY5Y5Y5Y@RJP,?Y5Y@Bm}PY5Y5Y5Y@RJP,?Y5Y@Bm}P !ܷ:~#[!L]*Zm[RJQK]w5]wRUu8DFm{ܶvַm v]6C3cHPj[X+tvW =x魄M/dp.Ni/40'j۶㯤k*6ǮS<öh3\vٿ}}CLwJfs,k4>42tb_2Y4P0.sBBHUU3w Ͱ]`C{/~m7?߿ t S0#?/> MLc忖qJfsUy3~w[2.K)R ȁP)x+۶SJ/E\VEoX4,VB0 @\Xn:c۶mS!-ΫL. <>>~Bhc)Ю~Kw~Bh뺞g12 Ӫ]wd0(kLnooc7M3iiiqRjçK%pq;?~xxx7L~EȮT0sj6 VW_d2NUR T0(Pf|[,@.`|g>mƔR\_)Yl0R/,NJ߉@v|=<5[r!_@N`P S2?Y5Y@Bm}PY5Y5Y5Y@RJP,?Y5Y@Bm}PY5Y5Y5Y@RJP,M! (,o0$vc4]w˲ L)&* _)Oc\ڔR~ʡ mUUU۶u!u8B !u`lWW߷zf*~_UUifclBdJd`lX`4Hrd2L&Wwww?yϿ~{ssl6z^o4ͬroJ5X`tss`p4ʹ,|eXNmL!c\4M3v6{p8|ils`4EJEH1E]^7RJq>?4M3]כu=1.*`YpU%[w[4bѫz6>㏃iip2oX 12zݷm&8`0[`U%ˊ Jf-P+&)t:AJ)ڦi&~7M3Ys5ȼ,iI۶ͧ|,㼮i4Ӻg1yY !RYU*ML۶W-+12j 2#e5O)B+wӎ"`PBk$Jec-Ku]/Wb¯| lmZ?X^UPaW~5Y5Y5(;2l|& k0&ڮ]1l0& k0& k0& k0&|X?@|͛@`dM@`PBu+?@|M@`dM@`dM@`dM@`P(yȚ !t ȅ Bmp,㕒e_\2Dr#xi]%> G0|u^RaC/"8__1bRo7oKgܿM{7M1k!X2%t0#?/`tBj??ƿ%_e>714>zI41*;<o 777? Cߟ6M31.F`0xӺlu_Jc\]9c!5M3px&R\83Z)3o8u= h48 I.|Mp~ht{uu7n͗ W P05M8 >^]]p?lV$pT,,8h4m{zcl-\:ȺuK)U!E]ד^y1Sl0-:bmqZluG @EoXĔR !B x0&ԴmSJ߭r Ä_@`K)ŔR*#+@XXt (XJZ_/ [0&-z"e_6o>Y5Y@Bm}e_6Y-t& k0& k0& k0(PJweټdM@`dM !]b`dM@`dM@`dM@`dMJ)@2/7 Ț @!>@W2/ Ț Ț Ț @)%(P&e5PUUUB{.MR\ !f@mhon[~]<|ק}[+LV7]`ټFEn[=ۂ]_߷}®sl{on~p!V[ wk}|7m1ֽ?!ҡ*6wsH7El[{}ڵz!ڶ>Z#Кa|$Lɞޔn붹Vm uL:S2~>Y_Ҩl04ʹ>k\>}u+!c9:LɌ2/:f3M,bZL,0o~q({I_e>714>zI41*{yx]0`0]^Zߟ6M31.F`0xӺlu_8~qc\tx)~1p8zUV#!:( 8p8z6 hq07M35 x299~4^]]`0[`U%05M<WWWﯮtuu~8~` pT1`08n`p꺞[ \RBXu=z~i>U . `bSJ)u]VW~\&b[,1B)n%005I)5mƔw/0p%_JfټkRJq1#@`kXXtO&T-/@&`PUMH?@|M@`dM@`dM@`dM}p^0(PJweټdM@`dM !]b`dM@`dM@`dM@`dMJ)@2/7 Ț @!>@W2/ Ț Ț Ț @)%(P&e5Y5(vP&e5Y϶9(Ю~^s9w?x;'z_J鬅{uv]}m;Bh×]͡`hW }w]ןmocW}|εٷ1m;|XgsLfW(-c׷ϮdOy~a&q`d:TfеJS_ǝP {<@Lcz[Ufʳ]o;7^t|M<ɶ0i}vUfZ#M=\+k[p~}o{Wu l߹=sz;rpV{?1{9=ǩҩS4O}PU襃AQ/(+Q2X/ `!\g,ub 0 m{7M1k!X2%ȍ  ?u]dr5L~W槳l{ޤiY]׋b4Uuj@ /6B1.WUU Çlv;^q8>I4U9W_&܀jWSw u]{ޤj<H)|4t<w^oR<ƸToFVo0z`0zݿ5M3[,gx<8  H,)K68_`wUUU޽v0ܷm~a4} wl\`@&`P @Rǿ)e!T) IJ)~=NG|R!iIML+\ ) .HV,jhfҶm) )8z4ʹY>SZ!4ȪTѵhfڶm:nY1.W7_o 0=nnn~6 nV5D8|>/k7m`5Qƥ3~)OɌJfH'0r Ț Ț Ț RbdټdM@`dM !]bS2㟒e5Y5Y5Y5(;2)OɌyȚ Ț Bhtd?%3& k0& k0& k0& k0(PJweS2㟒e5Y5(vOɌJfM@`dM@`dM@`dM@`Pd?%3 k0& k0(P㟒/ 5]wJ/ ^yd ^عr]AG/h= N =v[u湯opmWokW@?0)+l:6 !!v[uS}o>}m'eG"t`ri_ӾoOoEO*ʪ}{c¬S_Ob $td[Ӯgoߔs\{c^c5: ^Wfz-Z߾Yu޾5UljuL;:m:sș”d?%3˦'; s9f:yN^⼻n&'SGkfu x#?BZ' \ 㟒/:f`ؿM{7M1k!X2% oš oK4L&O޿_t6 {cכ4M3zc\BS"㠛 pxu_੖SS!M̫l6f^8i*[txG"Ƹ/D)Ƹz&UUUCJ)ip8z.`0Nvjy\`0zݿ5M3[,gx<8  p`RJ16M3~izA\&PeVU5bm|B1뺞6M3zcl-OTR2㟒/ (B!4ȪTѵhfڶm:nYX^]u*Lc#KpkR UUAW !|vY`@QB՚^!~\>_⬪bjkKEΖP$㟒/ 0hB. k0& k0& k0&RHJfS2㟒e5Y5PBu+?%3)OɌ Ț Ț Ț Ț (VJg 2)OɌJf͛@`dM@`@Bm}d?%3& k0& k0& k0& k0X)%d?%3)_6o>Y5Y !]bS2㟒/ Ț Ț Zu96/cR#bl V>8d?%3&dacosǜv\6s㢪~mY!akm<϶ 2eOvLlVm]/E-K8fp*҂=FIɌJfS2㟒e'i۶1.6 :vM}]\:`' m۾b_x޶sj:.AdC60 ;0YwxB늟YO ?0o~g`0mfd_PU/cB}*q<+KFT;R2㟒/ 9~m[WUB)RJB0 '.9%g>B".\0pN RJ&u]ӦiUUb ?R1 UR$?%3)_6GUh4XU~A/U^ (Ǐ"c󺮧u]OcR>_*. (Q[?~Iq]׫ .k?{||fbѴm[WGgM0^q4Ni۶N)K!X3X,UVh05n6*:v}&th#K)@JfS2l|5~)zLyȋ vP FBm}d`[! k0& k0& k0&|X?%3)_6o>Y5Y@Bm}d`dM@`dM@`dM@`dMJ)@JfS2l|& k0&ڮ]1)OɌ Ț Ț Ț Ț RbdټdM@`dML!v~K.o+כdٚ;pVi_)iտs/H7_@y`'85Huf0-D;% !՟]7ϽCk9w[}s#Uu|׮f[XS 6J t_?/!,bc&&"_)pq亮i4Ӫf1JFPrS1&@fUv{{cU}J^aoH.]x0|ʎ|/RJ18zZ4ƸH)S x0(9OKcSJ>~=^W/~eǫl6X,M۶uudxp)`:*t:M۶uJ)~^EPl6bQJ(v7mcJ)¯cЇKl0-;FS x@ |ΔG w0&*`@` Ț _(yȚ Ț Bht( Ț Ț Ț Ț Rbe_6o>Y5Y@Bm}e_6Y5Y5Y5U.(;2a7!9_6o>Bu?8DH;v>K!T?|_!-wH]^ v/1eog/?m޻iE] )@n`;|78y w1]~_rc?gdr5L~W槳l{ޤiY]׋brNp8/XNmL!c\4M3v6{p8|ils8y?Ƹ1. RqQMRMLpx&u]ϗw_@V`'XM;p$WM}UUջwibu=F`p44H G0'8u W'mq ޽{`0o۶1h48 | ~U L2BRJ`?]__;)Bh~4d \@)OIX[UլŢ߶m) )8z4ʹY>9_6@B)T-AVէE4Ӷmqˊ?rSW"F< ՟BN;pf0`5BJi}U-v/r$Z`P*BB۶mЋ/ .DO& k0& k0& k0(PJweS2㟒e5Y5(vOɌJfM@`dM@`dM@`dM@`Pd?%3 k0& k0(P㟒/ Ț Ț Ț @)%(OɌJf͛E.p&0`yd!6v}`LET~Qd?%3t.G4Ӯ//E8?%Up0/nVok&|Y۵, p`7P/t8HoK4L&O޿_t6 {cכ4M3zc\BS"㠛 pxu_੖SS!M̫l6f^8i*[txG"Ƹ/D)Ƹz&UUUCJ)ip8z.`0Nvjy\`0zݿ5M3[,gx<8  p`<٩/AR1Ηû`p߶mchq0-rAd?%3&BRJ`/|R!iIML+\ 2 b,iI۶ͧ|,㼮i4Ӻg1M!RJrdU}Z4M3m۶^Z,/.׺sH &^ ՟Bn{BhAJfS2l0(k!XO_)Uؽ lZ?X^P4a@} k0& k0&RHJfS2㟒e5Y5PBu+?%3)OɌ Ț Ț Ț Ț (VJg 2)OɌJf͛@`dM@`@Bm}d?%3& k0& k0& k0&cB]x6{ YJg 2)OɌJf͛@`l ȅ!]\`@!B/𒄽d?%3)_޻iE] )f2\M&_}o뿽l6z^7ifV"ƸX`])`YNmL!c\4M3v6{p8|ils8<)Ƹz&UUUCJ)ip8z1E PWM}UUջwibu=F`p44H ʑ'mq ޽{`0o۶1h48 | ~U #+DJ)WMRJ|>H)B4ͤ?i&` \Rhb^Rl۶^,|_,mOXH1y]Ӧiu]ϖX[-Pv<N!+F۶SJu۶eൈ1.b d`avUUsЕB߻eJV=|ks{p`laUU |Z`dM@`dM@`dM@`dM@`dM@`dM@`dM@&Г)IENDB`golang-github-anacrolix-fuse-0.2.0/doc/mount-sequence.md000066400000000000000000000020141444232012100232030ustar00rootroot00000000000000# The mount sequence FUSE mounting is a little bit tricky. There's a userspace helper tool that performs the handshake with the kernel, and then steps out of the way. This helper behaves differently on different platforms, forcing a more complex API on us. ## Successful runs On Linux, the mount is immediate and file system accesses wait until the requests are served. ![Diagram of Linux FUSE mount sequence](mount-linux.seq.png) On OS X, the mount becomes visible only after `InitRequest` (and maybe more) have been served. ![Diagram of OSXFUSE mount sequence](mount-osx.seq.png) ## Errors Let's see what happens if `InitRequest` gets an error response. On Linux, the mountpoint is there but all operations will fail: ![Diagram of Linux error handling](mount-linux-error-init.seq.png) On OS X, the mount never happened: ======= Let's see what happens if `initRequest` gets an error response. The mountpoint is temporarily there but all operations will fail: ![Diagram of OS X error handling](mount-osx-error-init.seq.png) golang-github-anacrolix-fuse-0.2.0/doc/writing-docs.md000066400000000000000000000010021444232012100226400ustar00rootroot00000000000000# Writing documentation ## Sequence diagrams The sequence diagrams are generated with `seqdiag`: http://blockdiag.com/en/seqdiag/index.html An easy way to work on them is to automatically update the generated files with https://github.com/cespare/reflex : reflex -g 'doc/[^.]*.seq' -- seqdiag -T svg -o '{}.svg' '{}' & reflex -g 'doc/[^.]*.seq' -- seqdiag -T png -o '{}.png' '{}' & The markdown files refer to PNG images because of Github limitations, but the SVG is generally more pleasant to view. golang-github-anacrolix-fuse-0.2.0/error_darwin.go000066400000000000000000000002471444232012100221760ustar00rootroot00000000000000package fuse import ( "syscall" ) const ( ENOATTR = Errno(syscall.ENOATTR) ) const ( errNoXattr = ENOATTR ) func init() { errnoNames[errNoXattr] = "ENOATTR" } golang-github-anacrolix-fuse-0.2.0/error_freebsd.go000066400000000000000000000002421444232012100223170ustar00rootroot00000000000000package fuse import "syscall" const ( ENOATTR = Errno(syscall.ENOATTR) ) const ( errNoXattr = ENOATTR ) func init() { errnoNames[errNoXattr] = "ENOATTR" } golang-github-anacrolix-fuse-0.2.0/error_linux.go000066400000000000000000000002471444232012100220510ustar00rootroot00000000000000package fuse import ( "syscall" ) const ( ENODATA = Errno(syscall.ENODATA) ) const ( errNoXattr = ENODATA ) func init() { errnoNames[errNoXattr] = "ENODATA" } golang-github-anacrolix-fuse-0.2.0/error_std.go000066400000000000000000000025351444232012100215060ustar00rootroot00000000000000package fuse // There is very little commonality in extended attribute errors // across platforms. // // getxattr return value for "extended attribute does not exist" is // ENOATTR on OS X, and ENODATA on Linux and apparently at least // NetBSD. There may be a #define ENOATTR on Linux too, but the value // is ENODATA in the actual syscalls. FreeBSD and OpenBSD have no // ENODATA, only ENOATTR. ENOATTR is not in any of the standards, // ENODATA exists but is only used for STREAMs. // // Each platform will define it a errNoXattr constant, and this file // will enforce that it implements the right interfaces and hide the // implementation. // // https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/getxattr.2.html // http://mail-index.netbsd.org/tech-kern/2012/04/30/msg013090.html // http://mail-index.netbsd.org/tech-kern/2012/04/30/msg013097.html // http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html // http://www.freebsd.org/cgi/man.cgi?query=extattr_get_file&sektion=2 // http://nixdoc.net/man-pages/openbsd/man2/extattr_get_file.2.html // ErrNoXattr is a platform-independent error value meaning the // extended attribute was not found. It can be used to respond to // GetxattrRequest and such. const ErrNoXattr = errNoXattr var _ error = ErrNoXattr var _ Errno = ErrNoXattr var _ ErrorNumber = ErrNoXattr golang-github-anacrolix-fuse-0.2.0/examples/000077500000000000000000000000001444232012100207655ustar00rootroot00000000000000golang-github-anacrolix-fuse-0.2.0/examples/clockfs/000077500000000000000000000000001444232012100224115ustar00rootroot00000000000000golang-github-anacrolix-fuse-0.2.0/examples/clockfs/clockfs.go000066400000000000000000000073761444232012100244010ustar00rootroot00000000000000// Clockfs implements a file system with the current time in a file. // It was written to demonstrate kernel cache invalidation. package main import ( "context" "flag" "fmt" "log" "os" "sync/atomic" "syscall" "time" "github.com/anacrolix/fuse" "github.com/anacrolix/fuse/fs" _ "github.com/anacrolix/fuse/fs/fstestutil" "github.com/anacrolix/fuse/fuseutil" ) func usage() { fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) fmt.Fprintf(os.Stderr, " %s MOUNTPOINT\n", os.Args[0]) flag.PrintDefaults() } func run(mountpoint string) error { c, err := fuse.Mount( mountpoint, fuse.FSName("clock"), fuse.Subtype("clockfsfs"), fuse.LocalVolume(), fuse.VolumeName("Clock filesystem"), ) if err != nil { return err } defer c.Close() srv := fs.New(c, nil) filesys := &FS{ // We pre-create the clock node so that it's always the same // object returned from all the Lookups. You could carefully // track its lifetime between Lookup&Forget, and have the // ticking & invalidation happen only when active, but let's // keep this example simple. clockFile: &File{ fuse: srv, }, } filesys.clockFile.tick() // This goroutine never exits. That's fine for this example. go filesys.clockFile.update() if err := srv.Serve(filesys); err != nil { return err } // Check if the mount process has an error to report. <-c.Ready if err := c.MountError; err != nil { return err } return nil } func main() { flag.Usage = usage flag.Parse() if flag.NArg() != 1 { usage() os.Exit(2) } mountpoint := flag.Arg(0) if err := run(mountpoint); err != nil { log.Fatal(err) } } type FS struct { clockFile *File } var _ fs.FS = (*FS)(nil) func (f *FS) Root() (fs.Node, error) { return &Dir{fs: f}, nil } // Dir implements both Node and Handle for the root directory. type Dir struct { fs *FS } var _ fs.Node = (*Dir)(nil) func (d *Dir) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 1 a.Mode = os.ModeDir | 0o555 return nil } var _ fs.NodeStringLookuper = (*Dir)(nil) func (d *Dir) Lookup(ctx context.Context, name string) (fs.Node, error) { if name == "clock" { return d.fs.clockFile, nil } return nil, syscall.ENOENT } var dirDirs = []fuse.Dirent{ {Inode: 2, Name: "clock", Type: fuse.DT_File}, } var _ fs.HandleReadDirAller = (*Dir)(nil) func (d *Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { return dirDirs, nil } type File struct { fuse *fs.Server content atomic.Value count uint64 } var _ fs.Node = (*File)(nil) func (f *File) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 2 a.Mode = 0o444 t := f.content.Load().(string) a.Size = uint64(len(t)) return nil } var _ fs.NodeOpener = (*File)(nil) func (f *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { if !req.Flags.IsReadOnly() { return nil, fuse.Errno(syscall.EACCES) } resp.Flags |= fuse.OpenKeepCache return f, nil } var _ fs.Handle = (*File)(nil) var _ fs.HandleReader = (*File)(nil) func (f *File) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { t := f.content.Load().(string) fuseutil.HandleRead(req, resp, []byte(t)) return nil } func (f *File) tick() { // Intentionally a variable-length format, to demonstrate size changes. f.count++ s := fmt.Sprintf("%d\t%s\n", f.count, time.Now()) f.content.Store(s) // For simplicity, this example tries to send invalidate // notifications even when the kernel does not hold a reference to // the node, so be extra sure to ignore ErrNotCached. if err := f.fuse.InvalidateNodeData(f); err != nil && err != fuse.ErrNotCached { log.Printf("invalidate error: %v", err) } } func (f *File) update() { tick := time.NewTicker(1 * time.Second) defer tick.Stop() for range tick.C { f.tick() } } golang-github-anacrolix-fuse-0.2.0/examples/hellofs/000077500000000000000000000000001444232012100224215ustar00rootroot00000000000000golang-github-anacrolix-fuse-0.2.0/examples/hellofs/hello.go000066400000000000000000000035511444232012100240570ustar00rootroot00000000000000// Hellofs implements a simple "hello world" file system. package main import ( "context" "flag" "fmt" "log" "os" "syscall" "github.com/anacrolix/fuse" "github.com/anacrolix/fuse/fs" _ "github.com/anacrolix/fuse/fs/fstestutil" ) func usage() { fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) fmt.Fprintf(os.Stderr, " %s MOUNTPOINT\n", os.Args[0]) flag.PrintDefaults() } func main() { flag.Usage = usage flag.Parse() if flag.NArg() != 1 { usage() os.Exit(2) } mountpoint := flag.Arg(0) c, err := fuse.Mount( mountpoint, fuse.FSName("helloworld"), fuse.Subtype("hellofs"), fuse.LocalVolume(), fuse.VolumeName("Hello world!"), ) if err != nil { log.Fatal(err) } defer c.Close() err = fs.Serve(c, FS{}) if err != nil { log.Fatal(err) } // check if the mount process has an error to report <-c.Ready if err := c.MountError; err != nil { log.Fatal(err) } } // FS implements the hello world file system. type FS struct{} func (FS) Root() (fs.Node, error) { return Dir{}, nil } // Dir implements both Node and Handle for the root directory. type Dir struct{} func (Dir) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 1 a.Mode = os.ModeDir | 0o555 return nil } func (Dir) Lookup(ctx context.Context, name string) (fs.Node, error) { if name == "hello" { return File{}, nil } return nil, syscall.ENOENT } var dirDirs = []fuse.Dirent{ {Inode: 2, Name: "hello", Type: fuse.DT_File}, } func (Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { return dirDirs, nil } // File implements both Node and Handle for the hello file. type File struct{} const greeting = "hello, world\n" func (File) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 2 a.Mode = 0o444 a.Size = uint64(len(greeting)) return nil } func (File) ReadAll(ctx context.Context) ([]byte, error) { return []byte(greeting), nil } golang-github-anacrolix-fuse-0.2.0/fs/000077500000000000000000000000001444232012100175575ustar00rootroot00000000000000golang-github-anacrolix-fuse-0.2.0/fs/bench/000077500000000000000000000000001444232012100206365ustar00rootroot00000000000000golang-github-anacrolix-fuse-0.2.0/fs/bench/bench_create_test.go000066400000000000000000000045561444232012100246400ustar00rootroot00000000000000package bench_test import ( "context" "fmt" "log" "net/http" "os" "sync" "testing" "github.com/anacrolix/fuse" "github.com/anacrolix/fuse/fs" "github.com/anacrolix/fuse/fs/fstestutil" "github.com/anacrolix/fuse/fs/fstestutil/spawntest/httpjson" ) type dummyFile struct { fstestutil.File } type benchCreateDir struct { fstestutil.Dir } var _ fs.NodeCreater = (*benchCreateDir)(nil) func (f *benchCreateDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) { child := &dummyFile{} return child, child, nil } type benchCreateHelp struct { mu sync.Mutex n int names []string } func (b *benchCreateHelp) ServeHTTP(w http.ResponseWriter, req *http.Request) { switch req.URL.Path { case "/init": httpjson.ServePOST(b.doInit).ServeHTTP(w, req) case "/bench": httpjson.ServePOST(b.doBench).ServeHTTP(w, req) default: http.NotFound(w, req) } } type benchCreateInitRequest struct { Dir string N int } func (b *benchCreateHelp) doInit(ctx context.Context, req benchCreateInitRequest) (*struct{}, error) { b.mu.Lock() defer b.mu.Unlock() // prepare file names to decrease test overhead names := make([]string, 0, req.N) for i := 0; i < req.N; i++ { // zero-padded so cost stays the same on every iteration names = append(names, req.Dir+"/"+fmt.Sprintf("%08x", i)) } b.n = req.N b.names = names return &struct{}{}, nil } func (b *benchCreateHelp) doBench(ctx context.Context, _ struct{}) (*struct{}, error) { for i := 0; i < b.n; i++ { f, err := os.Create(b.names[i]) if err != nil { log.Fatalf("Create: %v", err) } f.Close() } return &struct{}{}, nil } var benchCreateHelper = helpers.Register("benchCreate", &benchCreateHelp{}) func BenchmarkCreate(b *testing.B) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &benchCreateDir{} mnt, err := fstestutil.MountedT(b, fstestutil.SimpleFS{f}, nil) if err != nil { b.Fatal(err) } defer mnt.Close() control := benchCreateHelper.Spawn(ctx, b) defer control.Close() req := benchCreateInitRequest{ Dir: mnt.Dir, N: b.N, } var nothing struct{} if err := control.JSON("/init").Call(ctx, req, ¬hing); err != nil { b.Fatalf("calling helper: %v", err) } b.ResetTimer() if err := control.JSON("/bench").Call(ctx, struct{}{}, ¬hing); err != nil { b.Fatalf("calling helper: %v", err) } } golang-github-anacrolix-fuse-0.2.0/fs/bench/bench_lookup_test.go000066400000000000000000000027121444232012100246760ustar00rootroot00000000000000package bench_test import ( "context" "fmt" "os" "syscall" "testing" "github.com/anacrolix/fuse" "github.com/anacrolix/fuse/fs" "github.com/anacrolix/fuse/fs/fstestutil" "github.com/anacrolix/fuse/fs/fstestutil/spawntest/httpjson" ) type benchLookupDir struct { fstestutil.Dir } var _ fs.NodeRequestLookuper = (*benchLookupDir)(nil) func (f *benchLookupDir) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (fs.Node, error) { return nil, syscall.ENOENT } type benchLookupRequest struct { Path string N int } func doBenchLookup(ctx context.Context, req benchLookupRequest) (*struct{}, error) { for i := 0; i < req.N; i++ { if _, err := os.Stat(req.Path); !os.IsNotExist(err) { return nil, fmt.Errorf("Stat: wrong error: %v", err) } } return &struct{}{}, nil } var benchLookupHelper = helpers.Register("benchLookup", httpjson.ServePOST(doBenchLookup)) func BenchmarkLookup(b *testing.B) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &benchLookupDir{} mnt, err := fstestutil.MountedT(b, fstestutil.SimpleFS{f}, nil) if err != nil { b.Fatal(err) } defer mnt.Close() control := benchLookupHelper.Spawn(ctx, b) defer control.Close() name := mnt.Dir + "/does-not-exist" req := benchLookupRequest{ Path: name, N: b.N, } var nothing struct{} b.ResetTimer() if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { b.Fatalf("calling helper: %v", err) } } golang-github-anacrolix-fuse-0.2.0/fs/bench/bench_readwrite_test.go000066400000000000000000000124501444232012100253530ustar00rootroot00000000000000package bench_test import ( "context" "fmt" "io" "io/ioutil" "os" "syscall" "testing" "github.com/anacrolix/fuse" "github.com/anacrolix/fuse/fs" "github.com/anacrolix/fuse/fs/fstestutil" "github.com/anacrolix/fuse/fs/fstestutil/spawntest" "github.com/anacrolix/fuse/fs/fstestutil/spawntest/httpjson" ) type benchConfig struct { directIO bool } type benchFS struct { conf *benchConfig } var _ fs.FS = (*benchFS)(nil) func (f *benchFS) Root() (fs.Node, error) { return benchDir{fs: f}, nil } type benchDir struct { fs *benchFS } var _ fs.Node = benchDir{} var _ fs.NodeStringLookuper = benchDir{} var _ fs.Handle = benchDir{} var _ fs.HandleReadDirAller = benchDir{} func (benchDir) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 1 a.Mode = os.ModeDir | 0o555 return nil } func (d benchDir) Lookup(ctx context.Context, name string) (fs.Node, error) { if name == "bench" { return benchFile{conf: d.fs.conf}, nil } return nil, syscall.ENOENT } func (benchDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { l := []fuse.Dirent{ {Inode: 2, Name: "bench", Type: fuse.DT_File}, } return l, nil } type benchFile struct { conf *benchConfig } var _ fs.Node = benchFile{} var _ fs.NodeOpener = benchFile{} var _ fs.NodeFsyncer = benchFile{} var _ fs.Handle = benchFile{} var _ fs.HandleReader = benchFile{} var _ fs.HandleWriter = benchFile{} func (benchFile) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 2 a.Mode = 0o644 a.Size = 9999999999999999 return nil } func (f benchFile) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { if f.conf.directIO { resp.Flags |= fuse.OpenDirectIO } // TODO configurable? resp.Flags |= fuse.OpenKeepCache return f, nil } func (benchFile) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { resp.Data = resp.Data[:cap(resp.Data)] return nil } func (benchFile) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error { resp.Size = len(req.Data) return nil } func (benchFile) Fsync(ctx context.Context, req *fuse.FsyncRequest) error { return nil } type benchReadWriteRequest struct { Path string Size int64 N int } func benchmark(helper *spawntest.Helper, conf *benchConfig, size int64) func(*testing.B) { fn := func(b *testing.B) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() filesys := &benchFS{ conf: conf, } mnt, err := fstestutil.Mounted(filesys, nil, fuse.MaxReadahead(64*1024*1024), fuse.AsyncRead(), fuse.WritebackCache(), ) if err != nil { b.Fatal(err) } defer mnt.Close() control := helper.Spawn(ctx, b) defer control.Close() name := mnt.Dir + "/bench" req := benchReadWriteRequest{ Path: name, Size: size, N: b.N, } var nothing struct{} b.SetBytes(size) b.ResetTimer() if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { b.Fatalf("calling helper: %v", err) } } return fn } func benchmarkSizes(helper *spawntest.Helper, conf *benchConfig) func(*testing.B) { fn := func(b *testing.B) { b.Run("100", benchmark(helper, conf, 100)) b.Run("10MB", benchmark(helper, conf, 10*1024*1024)) b.Run("100MB", benchmark(helper, conf, 100*1024*1024)) } return fn } type zero struct{} func (zero) Read(p []byte) (n int, err error) { return len(p), nil } var Zero io.Reader = zero{} func doBenchWrite(ctx context.Context, req benchReadWriteRequest) (*struct{}, error) { f, err := os.Create(req.Path) if err != nil { return nil, err } defer f.Close() for i := 0; i < req.N; i++ { if _, err := io.CopyN(f, Zero, req.Size); err != nil { return nil, err } } return &struct{}{}, nil } var benchWriteHelper = helpers.Register("benchWrite", httpjson.ServePOST(doBenchWrite)) func BenchmarkWrite(b *testing.B) { b.Run("pagecache", benchmarkSizes(benchWriteHelper, &benchConfig{})) b.Run("direct", benchmarkSizes(benchWriteHelper, &benchConfig{ directIO: true, })) } func doBenchWriteSync(ctx context.Context, req benchReadWriteRequest) (*struct{}, error) { f, err := os.Create(req.Path) if err != nil { return nil, err } defer f.Close() for i := 0; i < req.N; i++ { if _, err := io.CopyN(f, Zero, req.Size); err != nil { return nil, err } if err := f.Sync(); err != nil { return nil, err } } return &struct{}{}, nil } var benchWriteSyncHelper = helpers.Register("benchWriteSync", httpjson.ServePOST(doBenchWriteSync)) func BenchmarkWriteSync(b *testing.B) { b.Run("pagecache", benchmarkSizes(benchWriteSyncHelper, &benchConfig{})) b.Run("direct", benchmarkSizes(benchWriteSyncHelper, &benchConfig{ directIO: true, })) } func doBenchRead(ctx context.Context, req benchReadWriteRequest) (*struct{}, error) { f, err := os.Create(req.Path) if err != nil { return nil, err } defer f.Close() for i := 0; i < req.N; i++ { n, err := io.CopyN(ioutil.Discard, f, req.Size) if err != nil { return nil, err } if n != req.Size { return nil, fmt.Errorf("unexpected size: %d != %d", n, req.Size) } } return &struct{}{}, nil } var benchReadHelper = helpers.Register("benchRead", httpjson.ServePOST(doBenchRead)) func BenchmarkRead(b *testing.B) { b.Run("pagecache", benchmarkSizes(benchReadHelper, &benchConfig{})) b.Run("direct", benchmarkSizes(benchReadHelper, &benchConfig{ directIO: true, })) } golang-github-anacrolix-fuse-0.2.0/fs/bench/doc.go000066400000000000000000000002471444232012100217350ustar00rootroot00000000000000// Package bench contains benchmarks. // // It is kept in a separate package to avoid conflicting with the // debug-heavy defaults for the actual tests. package bench golang-github-anacrolix-fuse-0.2.0/fs/bench/helpers_test.go000066400000000000000000000004111444232012100236620ustar00rootroot00000000000000package bench_test import ( "flag" "os" "testing" "github.com/anacrolix/fuse/fs/fstestutil/spawntest" ) var helpers spawntest.Registry func TestMain(m *testing.M) { helpers.AddFlag(flag.CommandLine) flag.Parse() helpers.RunIfNeeded() os.Exit(m.Run()) } golang-github-anacrolix-fuse-0.2.0/fs/fstestutil/000077500000000000000000000000001444232012100217655ustar00rootroot00000000000000golang-github-anacrolix-fuse-0.2.0/fs/fstestutil/checkdir.go000066400000000000000000000031531444232012100240720ustar00rootroot00000000000000package fstestutil import ( "fmt" "io/ioutil" "os" ) // FileInfoCheck is a function that validates an os.FileInfo according // to some criteria. type FileInfoCheck func(fi os.FileInfo) error type checkDirError struct { missing map[string]struct{} extra map[string]os.FileMode } func (e *checkDirError) Error() string { return fmt.Sprintf("wrong directory contents: missing %v, extra %v", e.missing, e.extra) } // CheckDir checks the contents of the directory at path, making sure // every directory entry listed in want is present. If the check is // not nil, it must also pass. // // If want contains the impossible filename "", unexpected files are // checked with that. If the key is not in want, unexpected files are // an error. // // Missing entries, that are listed in want but not seen, are an // error. func CheckDir(path string, want map[string]FileInfoCheck) error { problems := &checkDirError{ missing: make(map[string]struct{}, len(want)), extra: make(map[string]os.FileMode), } for k := range want { if k == "" { continue } problems.missing[k] = struct{}{} } fis, err := ioutil.ReadDir(path) if err != nil { return fmt.Errorf("cannot read directory: %v", err) } for _, fi := range fis { check, ok := want[fi.Name()] if !ok { check, ok = want[""] } if !ok { problems.extra[fi.Name()] = fi.Mode() continue } delete(problems.missing, fi.Name()) if check != nil { if err := check(fi); err != nil { return fmt.Errorf("check failed: %v: %v", fi.Name(), err) } } } if len(problems.missing) > 0 || len(problems.extra) > 0 { return problems } return nil } golang-github-anacrolix-fuse-0.2.0/fs/fstestutil/debug.go000066400000000000000000000023211444232012100234000ustar00rootroot00000000000000package fstestutil import ( "flag" "log" "strconv" "github.com/anacrolix/fuse" ) type flagDebug bool var debug flagDebug var _ flag.Value = &debug func (f *flagDebug) IsBoolFlag() bool { return true } func nop(msg interface{}) {} func (f *flagDebug) Set(s string) error { v, err := strconv.ParseBool(s) if err != nil { return err } *f = flagDebug(v) if v { fuse.Debug = logMsg } else { fuse.Debug = nop } return nil } func (f *flagDebug) String() string { return strconv.FormatBool(bool(*f)) } func logMsg(msg interface{}) { log.Printf("FUSE: %s\n", msg) } func init() { flag.Var(&debug, "fuse.debug", "log FUSE processing details") } // DebugByDefault changes the default of the `-fuse.debug` flag to // true. // // This package registers a command line flag `-fuse.debug` and when // run with that flag (and activated inside the tests), logs FUSE // debug messages. // // This is disabled by default, as most callers probably won't care // about FUSE details. Use DebugByDefault for tests where you'd // normally be passing `-fuse.debug` all the time anyway. // // Call from an init function. func DebugByDefault() { f := flag.Lookup("fuse.debug") f.DefValue = "true" f.Value.Set(f.DefValue) } golang-github-anacrolix-fuse-0.2.0/fs/fstestutil/doc.go000066400000000000000000000001071444232012100230570ustar00rootroot00000000000000package fstestutil // import "github.com/anacrolix/fuse/fs/fstestutil" golang-github-anacrolix-fuse-0.2.0/fs/fstestutil/mounted.go000066400000000000000000000076261444232012100240020ustar00rootroot00000000000000package fstestutil import ( "errors" "io/ioutil" "log" "os" "strings" "testing" "time" "github.com/anacrolix/fuse" "github.com/anacrolix/fuse/fs" ) // Mount contains information about the mount for the test to use. type Mount struct { // Dir is the temporary directory where the filesystem is mounted. Dir string Conn *fuse.Conn Server *fs.Server // Error will receive the return value of Serve. Error <-chan error done <-chan struct{} closed bool } // Close unmounts the filesystem and waits for fs.Serve to return. Any // returned error will be stored in Err. It is safe to call Close // multiple times. func (mnt *Mount) Close() { if mnt.closed { return } mnt.closed = true prev := "" for tries := 0; tries < 1000; tries++ { err := fuse.Unmount(mnt.Dir) if err != nil { msg := err.Error() // hide repeating errors if msg != prev { // TODO do more than log? // silence a very common message we can't do anything // about, for the first few tries. it'll still show if // the condition persists. if !strings.HasSuffix(err.Error(), ": Device or resource busy") || tries > 10 { log.Printf("unmount error: %v", err) prev = msg } } time.Sleep(100 * time.Millisecond) continue } break } <-mnt.done mnt.Conn.Close() os.Remove(mnt.Dir) } // MountedFunc mounts a filesystem at a temporary directory. The // filesystem used is constructed by calling a function, to allow // storing fuse.Conn and fs.Server in the FS. // // It also waits until the filesystem is known to be visible (OS X // workaround). // // After successful return, caller must clean up by calling Close. func MountedFunc(fn func(*Mount) fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) { dir, err := ioutil.TempDir("", "fusetest") if err != nil { return nil, err } defer func() { _ = os.Remove(dir) }() c, err := fuse.Mount(dir, options...) if err != nil { return nil, err } server := fs.New(c, conf) done := make(chan struct{}) serveErr := make(chan error, 1) mnt := &Mount{ Dir: dir, Conn: c, Server: server, Error: serveErr, done: done, } filesys := fn(mnt) go func() { defer close(done) serveErr <- server.Serve(filesys) }() select { case <-mnt.Conn.Ready: if err := mnt.Conn.MountError; err != nil { return nil, err } return mnt, nil case err = <-mnt.Error: // Serve quit early if err != nil { return nil, err } //lint:ignore ST1005 uppercase because it's an idenfier return nil, errors.New("Serve exited early") } } // Mounted mounts the fuse.Server at a temporary directory. // // It also waits until the filesystem is known to be visible (OS X // workaround). // // After successful return, caller must clean up by calling Close. func Mounted(filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) { fn := func(*Mount) fs.FS { return filesys } return MountedFunc(fn, conf, options...) } // MountedFuncT mounts a filesystem at a temporary directory, // directing it's debug log to the testing logger. // // See MountedFunc for usage. // // The debug log is not enabled by default. Use `-fuse.debug` or call // DebugByDefault to enable. func MountedFuncT(t testing.TB, fn func(*Mount) fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) { if conf == nil { conf = &fs.Config{} } if debug && conf.Debug == nil { conf.Debug = func(msg interface{}) { t.Helper() t.Logf("FUSE: %s", msg) } } return MountedFunc(fn, conf, options...) } // MountedT mounts the filesystem at a temporary directory, // directing it's debug log to the testing logger. // // See Mounted for usage. // // The debug log is not enabled by default. Use `-fuse.debug` or call // DebugByDefault to enable. func MountedT(t testing.TB, filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) { fn := func(*Mount) fs.FS { return filesys } return MountedFuncT(t, fn, conf, options...) } golang-github-anacrolix-fuse-0.2.0/fs/fstestutil/mountinfo.go000066400000000000000000000005551444232012100243370ustar00rootroot00000000000000package fstestutil // MountInfo describes a mounted file system. type MountInfo struct { FSName string Type string } // GetMountInfo finds information about the mount at mnt. It is // intended for use by tests only, and only fetches information // relevant to the current tests. func GetMountInfo(mnt string) (*MountInfo, error) { return getMountInfo(mnt) } golang-github-anacrolix-fuse-0.2.0/fs/fstestutil/mountinfo_darwin.go000066400000000000000000000016471444232012100257060ustar00rootroot00000000000000package fstestutil import ( "regexp" "syscall" ) var re = regexp.MustCompile(`\\(.)`) // unescape removes backslash-escaping. The escaped characters are not // mapped in any way; that is, unescape(`\n` ) == `n`. func unescape(s string) string { return re.ReplaceAllString(s, `$1`) } // cstr converts a nil-terminated C string into a Go string func cstr(ca []int8) string { s := make([]byte, 0, len(ca)) for _, c := range ca { if c == 0x00 { break } s = append(s, byte(c)) } return string(s) } func getMountInfo(mnt string) (*MountInfo, error) { var st syscall.Statfs_t err := syscall.Statfs(mnt, &st) if err != nil { return nil, err } i := &MountInfo{ // osx getmntent(3) fails to un-escape the data, so we do it.. // this might lead to double-unescaping in the future. fun. // TestMountOptionFSNameEvilBackslashDouble checks for that. FSName: unescape(cstr(st.Mntfromname[:])), } return i, nil } golang-github-anacrolix-fuse-0.2.0/fs/fstestutil/mountinfo_freebsd.go000066400000000000000000000002361444232012100260250ustar00rootroot00000000000000package fstestutil import "errors" func getMountInfo(mnt string) (*MountInfo, error) { return nil, errors.New("FreeBSD has no useful mount information") } golang-github-anacrolix-fuse-0.2.0/fs/fstestutil/mountinfo_linux.go000066400000000000000000000026241444232012100255550ustar00rootroot00000000000000package fstestutil import ( "errors" "io/ioutil" "strings" "time" ) // Linux /proc/mounts shows current mounts. // Same format as /etc/fstab. Quoting getmntent(3): // // Since fields in the mtab and fstab files are separated by whitespace, // octal escapes are used to represent the four characters space (\040), // tab (\011), newline (\012) and backslash (\134) in those files when // they occur in one of the four strings in a mntent structure. // // http://linux.die.net/man/3/getmntent var fstabUnescape = strings.NewReplacer( `\040`, "\040", `\011`, "\011", `\012`, "\012", `\134`, "\134", ) var errNotFound = errors.New("mount not found") func getMountInfo(mnt string) (*MountInfo, error) { // TODO delay a little to minimize an undiagnosed race between // fuse.Conn.Ready and /proc/mounts // https://github.com/bazil/fuse/issues/228 time.Sleep(10 * time.Millisecond) data, err := ioutil.ReadFile("/proc/mounts") if err != nil { return nil, err } for _, line := range strings.Split(string(data), "\n") { fields := strings.Fields(line) if len(fields) < 3 { continue } // Fields are: fsname dir type opts freq passno fsname := fstabUnescape.Replace(fields[0]) dir := fstabUnescape.Replace(fields[1]) fstype := fstabUnescape.Replace(fields[2]) if mnt == dir { info := &MountInfo{ FSName: fsname, Type: fstype, } return info, nil } } return nil, errNotFound } golang-github-anacrolix-fuse-0.2.0/fs/fstestutil/record/000077500000000000000000000000001444232012100232435ustar00rootroot00000000000000golang-github-anacrolix-fuse-0.2.0/fs/fstestutil/record/buffer.go000066400000000000000000000006541444232012100250500ustar00rootroot00000000000000package record import ( "bytes" "io" "sync" ) // Buffer is like bytes.Buffer but safe to access from multiple // goroutines. type Buffer struct { mu sync.Mutex buf bytes.Buffer } var _ io.Writer = (*Buffer)(nil) func (b *Buffer) Write(p []byte) (n int, err error) { b.mu.Lock() defer b.mu.Unlock() return b.buf.Write(p) } func (b *Buffer) Bytes() []byte { b.mu.Lock() defer b.mu.Unlock() return b.buf.Bytes() } golang-github-anacrolix-fuse-0.2.0/fs/fstestutil/record/record.go000066400000000000000000000245241444232012100250570ustar00rootroot00000000000000package record // import "github.com/anacrolix/fuse/fs/fstestutil/record" import ( "context" "sync" "sync/atomic" "syscall" "github.com/anacrolix/fuse" "github.com/anacrolix/fuse/fs" ) // Writes gathers data from FUSE Write calls. type Writes struct { buf Buffer } var _ fs.HandleWriter = (*Writes)(nil) func (w *Writes) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error { n, err := w.buf.Write(req.Data) resp.Size = n if err != nil { return err } return nil } func (w *Writes) RecordedWriteData() []byte { return w.buf.Bytes() } // Counter records number of times a thing has occurred. type Counter struct { count uint32 } func (r *Counter) Inc() { atomic.AddUint32(&r.count, 1) } func (r *Counter) Count() uint32 { return atomic.LoadUint32(&r.count) } // MarkRecorder records whether a thing has occurred. type MarkRecorder struct { count Counter } func (r *MarkRecorder) Mark() { r.count.Inc() } func (r *MarkRecorder) Recorded() bool { return r.count.Count() > 0 } // Flushes notes whether a FUSE Flush call has been seen. type Flushes struct { rec MarkRecorder } var _ fs.HandleFlusher = (*Flushes)(nil) func (r *Flushes) Flush(ctx context.Context, req *fuse.FlushRequest) error { r.rec.Mark() return nil } func (r *Flushes) RecordedFlush() bool { return r.rec.Recorded() } type Recorder struct { mu sync.Mutex val interface{} } // Record that we've seen value. A nil value is indistinguishable from // no value recorded. func (r *Recorder) Record(value interface{}) { r.mu.Lock() r.val = value r.mu.Unlock() } func (r *Recorder) Recorded() interface{} { r.mu.Lock() val := r.val r.mu.Unlock() return val } type RequestRecorder struct { rec Recorder } // Record a fuse.Request, after zeroing header fields that are hard to // reproduce. // // Make sure to record a copy, not the original request. func (r *RequestRecorder) RecordRequest(req fuse.Request) { hdr := req.Hdr() *hdr = fuse.Header{} r.rec.Record(req) } func (r *RequestRecorder) Recorded() fuse.Request { val := r.rec.Recorded() if val == nil { return nil } return val.(fuse.Request) } // Setattrs records a Setattr request and its fields. type Setattrs struct { rec RequestRecorder } var _ fs.NodeSetattrer = (*Setattrs)(nil) func (r *Setattrs) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error { tmp := *req r.rec.RecordRequest(&tmp) return nil } func (r *Setattrs) RecordedSetattr() fuse.SetattrRequest { val := r.rec.Recorded() if val == nil { return fuse.SetattrRequest{} } return *(val.(*fuse.SetattrRequest)) } // Fsyncs records an Fsync request and its fields. type Fsyncs struct { rec RequestRecorder } var _ fs.NodeFsyncer = (*Fsyncs)(nil) func (r *Fsyncs) Fsync(ctx context.Context, req *fuse.FsyncRequest) error { tmp := *req r.rec.RecordRequest(&tmp) return nil } func (r *Fsyncs) RecordedFsync() fuse.FsyncRequest { val := r.rec.Recorded() if val == nil { return fuse.FsyncRequest{} } return *(val.(*fuse.FsyncRequest)) } // Mkdirs records a Mkdir request and its fields. type Mkdirs struct { rec RequestRecorder } var _ fs.NodeMkdirer = (*Mkdirs)(nil) // Mkdir records the request and returns an error. Most callers should // wrap this call in a function that returns a more useful result. func (r *Mkdirs) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) { tmp := *req r.rec.RecordRequest(&tmp) return nil, syscall.EIO } // RecordedMkdir returns information about the Mkdir request. // If no request was seen, returns a zero value. func (r *Mkdirs) RecordedMkdir() fuse.MkdirRequest { val := r.rec.Recorded() if val == nil { return fuse.MkdirRequest{} } return *(val.(*fuse.MkdirRequest)) } // Symlinks records a Symlink request and its fields. type Symlinks struct { rec RequestRecorder } var _ fs.NodeSymlinker = (*Symlinks)(nil) // Symlink records the request and returns an error. Most callers should // wrap this call in a function that returns a more useful result. func (r *Symlinks) Symlink(ctx context.Context, req *fuse.SymlinkRequest) (fs.Node, error) { tmp := *req r.rec.RecordRequest(&tmp) return nil, syscall.EIO } // RecordedSymlink returns information about the Symlink request. // If no request was seen, returns a zero value. func (r *Symlinks) RecordedSymlink() fuse.SymlinkRequest { val := r.rec.Recorded() if val == nil { return fuse.SymlinkRequest{} } return *(val.(*fuse.SymlinkRequest)) } // Links records a Link request and its fields. type Links struct { rec RequestRecorder } var _ fs.NodeLinker = (*Links)(nil) // Link records the request and returns an error. Most callers should // wrap this call in a function that returns a more useful result. func (r *Links) Link(ctx context.Context, req *fuse.LinkRequest, old fs.Node) (fs.Node, error) { tmp := *req r.rec.RecordRequest(&tmp) return nil, syscall.EIO } // RecordedLink returns information about the Link request. // If no request was seen, returns a zero value. func (r *Links) RecordedLink() fuse.LinkRequest { val := r.rec.Recorded() if val == nil { return fuse.LinkRequest{} } return *(val.(*fuse.LinkRequest)) } // Mknods records a Mknod request and its fields. type Mknods struct { rec RequestRecorder } var _ fs.NodeMknoder = (*Mknods)(nil) // Mknod records the request and returns an error. Most callers should // wrap this call in a function that returns a more useful result. func (r *Mknods) Mknod(ctx context.Context, req *fuse.MknodRequest) (fs.Node, error) { tmp := *req r.rec.RecordRequest(&tmp) return nil, syscall.EIO } // RecordedMknod returns information about the Mknod request. // If no request was seen, returns a zero value. func (r *Mknods) RecordedMknod() fuse.MknodRequest { val := r.rec.Recorded() if val == nil { return fuse.MknodRequest{} } return *(val.(*fuse.MknodRequest)) } // Opens records a Open request and its fields. type Opens struct { rec RequestRecorder } var _ fs.NodeOpener = (*Opens)(nil) // Open records the request and returns an error. Most callers should // wrap this call in a function that returns a more useful result. func (r *Opens) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { tmp := *req r.rec.RecordRequest(&tmp) return nil, syscall.EIO } // RecordedOpen returns information about the Open request. // If no request was seen, returns a zero value. func (r *Opens) RecordedOpen() fuse.OpenRequest { val := r.rec.Recorded() if val == nil { return fuse.OpenRequest{} } return *(val.(*fuse.OpenRequest)) } // Getxattrs records a Getxattr request and its fields. type Getxattrs struct { rec RequestRecorder } var _ fs.NodeGetxattrer = (*Getxattrs)(nil) // Getxattr records the request and returns an error. Most callers should // wrap this call in a function that returns a more useful result. func (r *Getxattrs) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error { tmp := *req r.rec.RecordRequest(&tmp) return fuse.ErrNoXattr } // RecordedGetxattr returns information about the Getxattr request. // If no request was seen, returns a zero value. func (r *Getxattrs) RecordedGetxattr() fuse.GetxattrRequest { val := r.rec.Recorded() if val == nil { return fuse.GetxattrRequest{} } return *(val.(*fuse.GetxattrRequest)) } // Listxattrs records a Listxattr request and its fields. type Listxattrs struct { rec RequestRecorder } var _ fs.NodeListxattrer = (*Listxattrs)(nil) // Listxattr records the request and returns an error. Most callers should // wrap this call in a function that returns a more useful result. func (r *Listxattrs) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error { tmp := *req r.rec.RecordRequest(&tmp) return fuse.ErrNoXattr } // RecordedListxattr returns information about the Listxattr request. // If no request was seen, returns a zero value. func (r *Listxattrs) RecordedListxattr() fuse.ListxattrRequest { val := r.rec.Recorded() if val == nil { return fuse.ListxattrRequest{} } return *(val.(*fuse.ListxattrRequest)) } // Setxattrs records a Setxattr request and its fields. type Setxattrs struct { rec RequestRecorder } var _ fs.NodeSetxattrer = (*Setxattrs)(nil) // Setxattr records the request and returns an error. Most callers should // wrap this call in a function that returns a more useful result. func (r *Setxattrs) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error { tmp := *req // The byte slice points to memory that will be reused, so make a // deep copy. tmp.Xattr = append([]byte(nil), req.Xattr...) r.rec.RecordRequest(&tmp) return nil } // RecordedSetxattr returns information about the Setxattr request. // If no request was seen, returns a zero value. func (r *Setxattrs) RecordedSetxattr() fuse.SetxattrRequest { val := r.rec.Recorded() if val == nil { return fuse.SetxattrRequest{} } return *(val.(*fuse.SetxattrRequest)) } // Removexattrs records a Removexattr request and its fields. type Removexattrs struct { rec RequestRecorder } var _ fs.NodeRemovexattrer = (*Removexattrs)(nil) // Removexattr records the request and returns an error. Most callers should // wrap this call in a function that returns a more useful result. func (r *Removexattrs) Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) error { tmp := *req r.rec.RecordRequest(&tmp) return nil } // RecordedRemovexattr returns information about the Removexattr request. // If no request was seen, returns a zero value. func (r *Removexattrs) RecordedRemovexattr() fuse.RemovexattrRequest { val := r.rec.Recorded() if val == nil { return fuse.RemovexattrRequest{} } return *(val.(*fuse.RemovexattrRequest)) } // Creates records a Create request and its fields. type Creates struct { rec RequestRecorder } var _ fs.NodeCreater = (*Creates)(nil) // Create records the request and returns an error. Most callers should // wrap this call in a function that returns a more useful result. func (r *Creates) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) { tmp := *req r.rec.RecordRequest(&tmp) return nil, nil, syscall.EIO } // RecordedCreate returns information about the Create request. // If no request was seen, returns a zero value. func (r *Creates) RecordedCreate() fuse.CreateRequest { val := r.rec.Recorded() if val == nil { return fuse.CreateRequest{} } return *(val.(*fuse.CreateRequest)) } golang-github-anacrolix-fuse-0.2.0/fs/fstestutil/record/wait.go000066400000000000000000000023501444232012100245360ustar00rootroot00000000000000package record import ( "context" "sync" "time" "github.com/anacrolix/fuse" "github.com/anacrolix/fuse/fs" ) // ReleaseWaiter notes whether a FUSE Release call has been seen. // // Releases are not guaranteed to happen synchronously with any client // call, so they must be waited for. type ReleaseWaiter struct { once sync.Once seen chan *fuse.ReleaseRequest } var _ fs.HandleReleaser = (*ReleaseWaiter)(nil) func (r *ReleaseWaiter) init() { r.once.Do(func() { r.seen = make(chan *fuse.ReleaseRequest, 1) }) } func (r *ReleaseWaiter) Release(ctx context.Context, req *fuse.ReleaseRequest) error { r.init() tmp := *req hdr := tmp.Hdr() *hdr = fuse.Header{} r.seen <- &tmp close(r.seen) return nil } // WaitForRelease waits for Release to be called. // // With zero duration, wait forever. Otherwise, timeout early // in a more controlled way than `-test.timeout`. // // Returns a sanitized ReleaseRequest and whether a Release was seen. // Always true if dur==0. func (r *ReleaseWaiter) WaitForRelease(dur time.Duration) (*fuse.ReleaseRequest, bool) { r.init() var timeout <-chan time.Time if dur > 0 { timeout = time.After(dur) } select { case req := <-r.seen: return req, true case <-timeout: return nil, false } } golang-github-anacrolix-fuse-0.2.0/fs/fstestutil/spawntest/000077500000000000000000000000001444232012100240155ustar00rootroot00000000000000golang-github-anacrolix-fuse-0.2.0/fs/fstestutil/spawntest/example_test.go000066400000000000000000000031441444232012100270400ustar00rootroot00000000000000package spawntest_test import ( "context" "errors" "flag" "os" "testing" "github.com/anacrolix/fuse/fs/fstestutil/spawntest" "github.com/anacrolix/fuse/fs/fstestutil/spawntest/httpjson" ) var helpers spawntest.Registry type addRequest struct { A uint64 B uint64 } type addResult struct { X uint64 } func add(ctx context.Context, req addRequest) (*addResult, error) { // In real tests, you'd instruct the helper to interact with the // system-under-test on behalf of the unit test process. For // brevity, we'll just do the action directly in this example. x := req.A + req.B if x < req.A { return nil, errors.New("overflow") } r := &addResult{ X: x, } return r, nil } // The second argument to Register can be any http.Handler. To keep // state in the helper between calls, you can create a custom type and // delegate to methods based on http.Request.URL.Path. var addHelper = helpers.Register("add", httpjson.ServePOST(add)) func name_me_TestAdd(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() control := addHelper.Spawn(ctx, t) defer control.Close() var got addResult if err := control.JSON("/").Call(ctx, addRequest{A: 42, B: 13}, &got); err != nil { t.Fatalf("calling helper: %v", err) } if g, e := got.X, uint64(55); g != e { t.Errorf("wrong add result: %v != %v", g, e) } } func name_me_TestMain(m *testing.M) { helpers.AddFlag(flag.CommandLine) flag.Parse() helpers.RunIfNeeded() os.Exit(m.Run()) } func Example() {} // Quiet linters. See https://github.com/dominikh/go-tools/issues/675 var _ = name_me_TestAdd var _ = name_me_TestMain golang-github-anacrolix-fuse-0.2.0/fs/fstestutil/spawntest/httpjson/000077500000000000000000000000001444232012100256665ustar00rootroot00000000000000golang-github-anacrolix-fuse-0.2.0/fs/fstestutil/spawntest/httpjson/client.go000066400000000000000000000036561444232012100275050ustar00rootroot00000000000000package httpjson import ( "bytes" "context" "encoding/json" "fmt" "io" "io/ioutil" "net/http" ) // JSON helps make a HTTP request to the resource tree at url with // JSON request and response bodies. // // If client is nil http.DefaultClient will be used. func JSON(client *http.Client, url string) *Resource { if client == nil { client = http.DefaultClient } return &Resource{ http: client, url: url, } } // Resource represents a JSON-speaking remote HTTP resource. type Resource struct { http *http.Client url string } // Call a HTTP resource that is expected to return JSON data. // // If data is not nil, method is POST and the request body is data // marshaled into JSON. If data is nil, method is GET. // // The response JSON is unmarshaled into dst. func (c *Resource) Call(ctx context.Context, data interface{}, dst interface{}) error { method := "GET" var body io.Reader if data != nil { buf, err := json.Marshal(data) if err != nil { return err } method = "POST" body = bytes.NewReader(buf) } req, err := http.NewRequestWithContext(ctx, method, c.url, body) if err != nil { return err } if method != "GET" { req.Header.Set("Content-Type", "application/json") } req.Header.Set("Accept", "application/json") resp, err := c.http.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { buf, err := ioutil.ReadAll(resp.Body) if err != nil { buf = []byte("(cannot read error body: " + err.Error() + ")") } return fmt.Errorf("http error: %v: %q", resp.Status, bytes.TrimSpace(buf)) } dec := json.NewDecoder(resp.Body) // add options to func JSON to disable this, if need is strong // enough; at that point might change api to just take nothing but // options, use default for client etc. dec.DisallowUnknownFields() if err := dec.Decode(dst); err != nil { return err } if err := mustEOF(dec); err != nil { return err } return nil } golang-github-anacrolix-fuse-0.2.0/fs/fstestutil/spawntest/httpjson/doc.go000066400000000000000000000002421444232012100267600ustar00rootroot00000000000000// Package httpjson helps transporting JSON over HTTP. // // This might get extracted to a standalone repository, if it proves // useful enough. package httpjson golang-github-anacrolix-fuse-0.2.0/fs/fstestutil/spawntest/httpjson/musteof.go000066400000000000000000000010341444232012100276750ustar00rootroot00000000000000package httpjson import ( "encoding/json" "fmt" "io" ) // TrailingDataError is an error that is returned if there is trailing // data after a JSON message. type TrailingDataError struct { Token json.Token } func (t *TrailingDataError) Error() string { return fmt.Sprintf("invalid character %q after top-level value", t.Token) } func mustEOF(dec *json.Decoder) error { token, err := dec.Token() switch err { case io.EOF: // expected return nil case nil: return &TrailingDataError{Token: token} default: return err } } golang-github-anacrolix-fuse-0.2.0/fs/fstestutil/spawntest/httpjson/server.go000066400000000000000000000047251444232012100275330ustar00rootroot00000000000000package httpjson import ( "context" "encoding/json" "fmt" "net/http" "reflect" ) // TODO ServeGET // ServePOST adapts a function to a http.Handler with easy JSON // unmarshal & marshal and error reporting. // // fn is expected to be of the form // // func(context.Context, T1) (T2, error) // // or similar with pointers to T1 or T2. func ServePOST(fn interface{}) http.Handler { val := reflect.ValueOf(fn) if val.Kind() != reflect.Func { panic("JSONHandler was passed a value that is not a function") } typ := val.Type() if typ.NumIn() != 2 { panic("JSONHandler function must take two arguments") } if typ.In(0) != reflect.TypeOf((*context.Context)(nil)).Elem() { panic("JSONHandler function must take context") } if typ.NumOut() != 2 { panic("JSONHandler function must return two values") } if typ.Out(1) != reflect.TypeOf((*error)(nil)).Elem() { panic("JSONHandler function must return error") } return jsonPOST{ fnVal: val, argType: typ.In(1), retType: typ.Out(0), } } type jsonPOST struct { fnVal reflect.Value argType reflect.Type retType reflect.Type } var _ http.Handler = jsonPOST{} func (j jsonPOST) ServeHTTP(w http.ResponseWriter, req *http.Request) { if req.Method != "POST" { w.Header().Set("Allow", "POST") const code = http.StatusMethodNotAllowed http.Error(w, http.StatusText(code), code) return } // TODO do we want to enforce request Content-Type // TODO do we want to enforce request Accept argVal := reflect.New(j.argType) arg := argVal.Interface() dec := json.NewDecoder(req.Body) dec.DisallowUnknownFields() if err := dec.Decode(arg); err != nil { msg := fmt.Sprintf("cannot unmarshal request body as json: %v", err) http.Error(w, msg, http.StatusBadRequest) return } if err := mustEOF(dec); err != nil { msg := fmt.Sprintf("cannot unmarshal request body as json: %v", err) http.Error(w, msg, http.StatusBadRequest) return } ret := j.fnVal.Call([]reflect.Value{ reflect.ValueOf(req.Context()), argVal.Elem(), }) // TODO allow bad request etc status codes errI := ret[1].Interface() if errI != nil { if err := errI.(error); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } data := ret[0].Interface() buf, err := json.Marshal(data) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") if _, err := w.Write(buf); err != nil { panic(http.ErrAbortHandler) } } golang-github-anacrolix-fuse-0.2.0/fs/fstestutil/spawntest/spawntest.go000066400000000000000000000140431444232012100263760ustar00rootroot00000000000000// Package spawntest helps write tests that use subprocesses. // // The subprocess runs a HTTP server on a UNIX domain socket, and the // test can make HTTP requests to control the behavior of the helper // subprocess. // // Helpers are identified by names they pass to Registry.Register. // This call should be placed in an init function. The test spawns the // subprocess by executing the same test binary in a subprocess, // passing it a special flag that is recognized by TestMain. // // This might get extracted to a standalone repository, if it proves // useful enough. package spawntest import ( "context" "errors" "flag" "io/ioutil" "log" "net" "net/http" "net/url" "os" "os/exec" "path/filepath" "sync" "testing" "github.com/anacrolix/fuse/fs/fstestutil/spawntest/httpjson" "github.com/tv42/httpunix" ) // Registry keeps track of helpers. // // The zero value is ready to use. type Registry struct { mu sync.Mutex helpers map[string]http.Handler runName string runHandler http.Handler } // Register a helper in the registry. // // This should be called from a top-level variable assignment. // // Register will panic if the name is already registered. func (r *Registry) Register(name string, h http.Handler) *Helper { r.mu.Lock() defer r.mu.Unlock() if r.helpers == nil { r.helpers = make(map[string]http.Handler) } if _, seen := r.helpers[name]; seen { panic("spawntest: helper already registered: " + name) } r.helpers[name] = h hh := &Helper{ name: name, } return hh } type helperFlag struct { r *Registry } var _ flag.Value = helperFlag{} func (hf helperFlag) String() string { if hf.r == nil { return "" } return hf.r.runName } func (hf helperFlag) Set(s string) error { h, ok := hf.r.helpers[s] if !ok { return errors.New("helper not found") } hf.r.runName = s hf.r.runHandler = h return nil } const flagName = "spawntest.internal.helper" // AddFlag adds the command-line flag used to communicate between // Control and the helper to the flag set. Typically flag.CommandLine // is used, and this should be called from TestMain before flag.Parse. func (r *Registry) AddFlag(f *flag.FlagSet) { v := helperFlag{r: r} f.Var(v, flagName, "internal use only") } // RunIfNeeded passes execution to the helper if the right // command-line flag was seen. This should be called from TestMain // after flag.Parse. If running as the helper, the call will not // return. func (r *Registry) RunIfNeeded() { h := r.runHandler if h == nil { return } f := os.NewFile(3, "") l, err := net.FileListener(f) if err != nil { log.Fatalf("cannot listen: %v", err) } if err := http.Serve(l, h); err != nil { log.Fatalf("http server error: %v", err) } os.Exit(0) } // Helper is the result of registering a helper. It can be used by // tests to spawn the helper. type Helper struct { name string } type transportWithBase struct { Base *url.URL Transport http.RoundTripper } var _ http.RoundTripper = (*transportWithBase)(nil) func (t *transportWithBase) RoundTrip(req *http.Request) (*http.Response, error) { ctx := req.Context() req = req.Clone(ctx) req.URL = t.Base.ResolveReference(req.URL) return t.Transport.RoundTrip(req) } func makeHTTPClient(path string) *http.Client { u := &httpunix.Transport{} const loc = "helper" u.RegisterLocation(loc, path) t := &transportWithBase{ Base: &url.URL{ Scheme: httpunix.Scheme, Host: loc, }, Transport: u, } client := &http.Client{ Transport: t, } return client } // Spawn the helper. All errors will be reported via t.Logf and fatal // errors result in t.FailNow. The helper is killed after context // cancels. func (h *Helper) Spawn(ctx context.Context, t testing.TB) *Control { executable, err := os.Executable() if err != nil { t.Fatalf("spawntest: cannot find our executable: %v", err) } // could use TB.TempDir() // https://github.com/golang/go/issues/35998 dir, err := ioutil.TempDir("", "spawntest") if err != nil { t.Fatalf("spawnmount.Spawn: cannot make temp dir: %v", err) } defer func() { if dir != "" { if err := os.RemoveAll(dir); err != nil { t.Logf("error cleaning temp dir: %v", err) } } }() controlPath := filepath.Join(dir, "control") l, err := net.ListenUnix("unix", &net.UnixAddr{Name: controlPath, Net: "unix"}) if err != nil { t.Fatalf("cannot open listener: %v", err) } l.SetUnlinkOnClose(false) defer l.Close() lf, err := l.File() if err != nil { t.Fatalf("cannot get FD from listener: %v", err) } defer lf.Close() cmd := exec.Command(executable, "-"+flagName+"="+h.name) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.ExtraFiles = []*os.File{lf} if err := cmd.Start(); err != nil { t.Fatalf("spawntest: cannot start helper: %v", err) } defer func() { if cmd != nil { if err := cmd.Process.Kill(); err != nil { t.Logf("error killing spawned helper: %v", err) } } }() c := &Control{ t: t, dir: dir, cmd: cmd, http: makeHTTPClient(controlPath), } dir = "" cmd = nil return c } // Control an instance of a helper running as a subprocess. type Control struct { t testing.TB dir string cmd *exec.Cmd http *http.Client } // Close kills the helper and frees resources. func (c *Control) Close() { if c.cmd.ProcessState == nil { // not yet Waited on c.cmd.Process.Kill() _ = c.cmd.Wait() } if c.dir != "" { if err := os.RemoveAll(c.dir); err != nil { c.t.Logf("error cleaning temp dir: %v", err) } c.dir = "" } } // Signal send a signal to the helper process. func (c *Control) Signal(sig os.Signal) error { return c.cmd.Process.Signal(sig) } // HTTP returns a HTTP client that can be used to communicate with the // helper. URLs passed to this helper should not include scheme or // host. func (c *Control) HTTP() *http.Client { return c.http } // JSON returns a helper to make HTTP requests that pass data as JSON // to the resource identified by path. Path should not include scheme // or host. Path can be empty to communicate with the root resource. func (c *Control) JSON(path string) *httpjson.Resource { return httpjson.JSON(c.http, path) } golang-github-anacrolix-fuse-0.2.0/fs/fstestutil/testfs.go000066400000000000000000000021771444232012100236330ustar00rootroot00000000000000package fstestutil import ( "context" "os" "syscall" "github.com/anacrolix/fuse" "github.com/anacrolix/fuse/fs" ) // SimpleFS is a trivial FS that just implements the Root method. type SimpleFS struct { Node fs.Node } var _ fs.FS = SimpleFS{} func (f SimpleFS) Root() (fs.Node, error) { return f.Node, nil } // File can be embedded in a struct to make it look like a file. type File struct{} func (f File) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0o666 return nil } // Dir can be embedded in a struct to make it look like a directory. type Dir struct{} func (f Dir) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = os.ModeDir | 0o777 return nil } // ChildMap is a directory with child nodes looked up from a map. type ChildMap map[string]fs.Node var _ fs.Node = (*ChildMap)(nil) var _ fs.NodeStringLookuper = (*ChildMap)(nil) func (f *ChildMap) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = os.ModeDir | 0o777 return nil } func (f *ChildMap) Lookup(ctx context.Context, name string) (fs.Node, error) { child, ok := (*f)[name] if !ok { return nil, syscall.ENOENT } return child, nil } golang-github-anacrolix-fuse-0.2.0/fs/helpers_test.go000066400000000000000000000002601444232012100226050ustar00rootroot00000000000000package fs_test import ( "flag" "os" "testing" ) func TestMain(m *testing.M) { helpers.AddFlag(flag.CommandLine) flag.Parse() helpers.RunIfNeeded() os.Exit(m.Run()) } golang-github-anacrolix-fuse-0.2.0/fs/serve.go000066400000000000000000001400451444232012100212360ustar00rootroot00000000000000// FUSE service loop, for servers that wish to use it. package fs // import "github.com/anacrolix/fuse/fs" import ( "bytes" "context" "encoding/binary" "errors" "fmt" "hash/fnv" "io" "log" "reflect" "runtime" "strings" "sync" "syscall" "time" "github.com/anacrolix/fuse" "github.com/anacrolix/fuse/fuseutil" "golang.org/x/sys/unix" ) const ( attrValidTime = 1 * time.Minute entryValidTime = 1 * time.Minute ) // TODO: FINISH DOCS // An FS is the interface required of a file system. // // Other FUSE requests can be handled by implementing methods from the // FS* interfaces, for example FSStatfser. type FS interface { // Root is called to obtain the Node for the file system root. Root() (Node, error) } type FSStatfser interface { // Statfs is called to obtain file system metadata. // It should write that data to resp. Statfs(ctx context.Context, req *fuse.StatfsRequest, resp *fuse.StatfsResponse) error } type FSDestroyer interface { // Destroy is called when the file system is shutting down. // // Linux only sends this request for block device backed (fuseblk) // filesystems, to allow them to flush writes to disk before the // unmount completes. Destroy() } type FSInodeGenerator interface { // GenerateInode is called to pick a dynamic inode number when it // would otherwise be 0. // // Not all filesystems bother tracking inodes, but FUSE requires // the inode to be set, and fewer duplicates in general makes UNIX // tools work better. // // Operations where the nodes may return 0 inodes include Getattr, // Setattr and ReadDir. // // If FS does not implement FSInodeGenerator, GenerateDynamicInode // is used. // // Implementing this is useful to e.g. constrain the range of // inode values used for dynamic inodes. // // Non-zero return values should be greater than 1, as that is // always used for the root inode. GenerateInode(parentInode uint64, name string) uint64 } // A Node is the interface required of a file or directory. // See the documentation for type FS for general information // pertaining to all methods. // // A Node must be usable as a map key, that is, it cannot be a // function, map or slice. // // Other FUSE requests can be handled by implementing methods from the // Node* interfaces, for example NodeOpener. // // Methods returning Node should take care to return the same Node // when the result is logically the same instance. Without this, each // Node will get a new NodeID, causing spurious cache invalidations, // extra lookups and aliasing anomalies. This may not matter for a // simple, read-only filesystem. type Node interface { // Attr fills attr with the standard metadata for the node. // // Fields with reasonable defaults are prepopulated. For example, // all times are set to a fixed moment when the program started. // // If Inode is left as 0, a dynamic inode number is chosen. // // The result may be cached for the duration set in Valid. Attr(ctx context.Context, attr *fuse.Attr) error } type NodeGetattrer interface { // Getattr obtains the standard metadata for the receiver. // It should store that metadata in resp. // // If this method is not implemented, the attributes will be // generated based on Attr(), with zero values filled in. Getattr(ctx context.Context, req *fuse.GetattrRequest, resp *fuse.GetattrResponse) error } type NodeSetattrer interface { // Setattr sets the standard metadata for the receiver. // // Note, this is also used to communicate changes in the size of // the file, outside of Writes. // // req.Valid is a bitmask of what fields are actually being set. // For example, the method should not change the mode of the file // unless req.Valid.Mode() is true. Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error } type NodeSymlinker interface { // Symlink creates a new symbolic link in the receiver, which must be a directory. // // TODO is the above true about directories? Symlink(ctx context.Context, req *fuse.SymlinkRequest) (Node, error) } // This optional request will be called only for symbolic link nodes. type NodeReadlinker interface { // Readlink reads a symbolic link. Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error) } type NodeLinker interface { // Link creates a new directory entry in the receiver based on an // existing Node. Receiver must be a directory. Link(ctx context.Context, req *fuse.LinkRequest, old Node) (Node, error) } type NodeRemover interface { // Remove removes the entry with the given name from // the receiver, which must be a directory. The entry to be removed // may correspond to a file (unlink) or to a directory (rmdir). Remove(ctx context.Context, req *fuse.RemoveRequest) error } type NodeAccesser interface { // Access checks whether the calling context has permission for // the given operations on the receiver. If so, Access should // return nil. If not, Access should return EPERM. // // Note that this call affects the result of the access(2) system // call but not the open(2) system call. If Access is not // implemented, the Node behaves as if it always returns nil // (permission granted), relying on checks in Open instead. Access(ctx context.Context, req *fuse.AccessRequest) error } type NodeStringLookuper interface { // Lookup looks up a specific entry in the receiver, // which must be a directory. Lookup should return a Node // corresponding to the entry. If the name does not exist in // the directory, Lookup should return ENOENT. // // Lookup need not to handle the names "." and "..". Lookup(ctx context.Context, name string) (Node, error) } type NodeRequestLookuper interface { // Lookup looks up a specific entry in the receiver. // See NodeStringLookuper for more. Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (Node, error) } type NodeMkdirer interface { Mkdir(ctx context.Context, req *fuse.MkdirRequest) (Node, error) } type NodeOpener interface { // Open opens the receiver. After a successful open, a client // process has a file descriptor referring to this Handle. // // Open can also be also called on non-files. For example, // directories are Opened for ReadDir or fchdir(2). // // If this method is not implemented, the open will always // succeed, and the Node itself will be used as the Handle. // // XXX note about access. XXX OpenFlags. Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (Handle, error) } type NodeCreater interface { // Create creates a new directory entry in the receiver, which // must be a directory. Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (Node, Handle, error) } type NodeForgetter interface { // Forget about this node. This node will not receive further // method calls. // // Forget is not necessarily seen on unmount, as all nodes are // implicitly forgotten as part of the unmount. Forget() } type NodeRenamer interface { Rename(ctx context.Context, req *fuse.RenameRequest, newDir Node) error } type NodeMknoder interface { Mknod(ctx context.Context, req *fuse.MknodRequest) (Node, error) } // TODO this should be on Handle not Node type NodeFsyncer interface { Fsync(ctx context.Context, req *fuse.FsyncRequest) error } type NodeGetxattrer interface { // Getxattr gets an extended attribute by the given name from the // node. // // If there is no xattr by that name, returns fuse.ErrNoXattr. Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error } type NodeListxattrer interface { // Listxattr lists the extended attributes recorded for the node. Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error } type NodeSetxattrer interface { // Setxattr sets an extended attribute with the given name and // value for the node. Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error } type NodeRemovexattrer interface { // Removexattr removes an extended attribute for the name. // // If there is no xattr by that name, returns fuse.ErrNoXattr. Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) error } var startTime = time.Now() func nodeAttr(ctx context.Context, n Node, attr *fuse.Attr) error { attr.Valid = attrValidTime attr.Nlink = 1 attr.Atime = startTime attr.Mtime = startTime attr.Ctime = startTime attr.Crtime = startTime if err := n.Attr(ctx, attr); err != nil { return err } return nil } // A Handle is the interface required of an opened file or directory. // See the documentation for type FS for general information // pertaining to all methods. // // Other FUSE requests can be handled by implementing methods from the // Handle* interfaces. The most common to implement are HandleReader, // HandleReadDirer, and HandleWriter. // // TODO implement methods: Getlk, Setlk, Setlkw type Handle interface { } type HandleFlusher interface { // Flush is called each time the file or directory is closed. // Because there can be multiple file descriptors referring to a // single opened file, Flush can be called multiple times. Flush(ctx context.Context, req *fuse.FlushRequest) error } type HandleReadAller interface { ReadAll(ctx context.Context) ([]byte, error) } type HandleReadDirAller interface { ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) } type HandleReader interface { // Read requests to read data from the handle. // // There is a page cache in the kernel that normally submits only // page-aligned reads spanning one or more pages. However, you // should not rely on this. To see individual requests as // submitted by the file system clients, set OpenDirectIO. // // Note that reads beyond the size of the file as reported by Attr // are not even attempted (except in OpenDirectIO mode). Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error } type HandleWriter interface { // Write requests to write data into the handle at the given offset. // Store the amount of data written in resp.Size. // // There is a writeback page cache in the kernel that normally submits // only page-aligned writes spanning one or more pages. However, // you should not rely on this. To see individual requests as // submitted by the file system clients, set OpenDirectIO. // // Writes that grow the file are expected to update the file size // (as seen through Attr). Note that file size changes are // communicated also through Setattr. Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error } type HandleReleaser interface { Release(ctx context.Context, req *fuse.ReleaseRequest) error } type HandlePoller interface { // Poll checks whether the handle is currently ready for I/O, and // may request a wakeup when it is. // // Poll should always return quickly. Clients waiting for // readiness can be woken up by passing the return value of // PollRequest.Wakeup to fs.Server.NotifyPollWakeup or // fuse.Conn.NotifyPollWakeup. // // To allow supporting poll for only some of your Nodes/Handles, // the default behavior is to report immediate readiness. If your // FS does not support polling and you want to minimize needless // requests and log noise, implement NodePoller and return // syscall.ENOSYS. // // The Go runtime uses epoll-based I/O whenever possible, even for // regular files. Poll(ctx context.Context, req *fuse.PollRequest, resp *fuse.PollResponse) error } type NodePoller interface { // Poll checks whether the node is currently ready for I/O, and // may request a wakeup when it is. See HandlePoller. Poll(ctx context.Context, req *fuse.PollRequest, resp *fuse.PollResponse) error } // HandleLocker contains the common operations for all kinds of file // locks. See also lock family specific interfaces: HandleFlockLocker, // HandlePOSIXLocker. type HandleLocker interface { // Lock tries to acquire a lock on a byte range of the node. If a // conflicting lock is already held, returns syscall.EAGAIN. // // LockRequest.LockOwner is a file-unique identifier for this // lock, and will be seen in calls releasing this lock // (UnlockRequest, ReleaseRequest, FlushRequest) and also // in e.g. ReadRequest, WriteRequest. Lock(ctx context.Context, req *fuse.LockRequest) error // LockWait acquires a lock on a byte range of the node, waiting // until the lock can be obtained (or context is canceled). LockWait(ctx context.Context, req *fuse.LockWaitRequest) error // Unlock releases the lock on a byte range of the node. Locks can // be released also implicitly, see HandleFlockLocker and // HandlePOSIXLocker. Unlock(ctx context.Context, req *fuse.UnlockRequest) error // QueryLock returns the current state of locks held for the byte // range of the node. // // See QueryLockRequest for details on how to respond. // // To simplify implementing this method, resp.Lock is prefilled to // have Lock.Type F_UNLCK, and the whole struct should be // overwritten for in case of conflicting locks. QueryLock(ctx context.Context, req *fuse.QueryLockRequest, resp *fuse.QueryLockResponse) error } // HandleFlockLocker describes locking behavior unique to flock (BSD) // locks. See HandleLocker. type HandleFlockLocker interface { HandleLocker // Flock unlocking can also happen implicitly as part of Release, // in which case Unlock is not called, and Release will have // ReleaseFlags bit ReleaseFlockUnlock set. HandleReleaser } // HandlePOSIXLocker describes locking behavior unique to POSIX (fcntl // F_SETLK) locks. See HandleLocker. type HandlePOSIXLocker interface { HandleLocker // POSIX unlocking can also happen implicitly as part of Flush, // in which case Unlock is not called. HandleFlusher } type Config struct { // Function to send debug log messages to. If nil, use fuse.Debug. // Note that changing this or fuse.Debug may not affect existing // calls to Serve. // // See fuse.Debug for the rules that log functions must follow. Debug func(msg interface{}) // Function to put things into context for processing the request. // The returned context must have ctx as its parent. // // Note that changing this may not affect existing calls to Serve. // // Must not retain req. WithContext func(ctx context.Context, req fuse.Request) context.Context } // New returns a new FUSE server ready to serve this kernel FUSE // connection. // // Config may be nil. func New(conn *fuse.Conn, config *Config) *Server { s := &Server{ conn: conn, req: map[fuse.RequestID]*serveRequest{}, nodeRef: map[Node]fuse.NodeID{}, notifyWait: map[fuse.RequestID]chan<- *fuse.NotifyReply{}, dynamicInode: GenerateDynamicInode, } if config != nil { s.debug = config.Debug s.context = config.WithContext } if s.debug == nil { s.debug = fuse.Debug } return s } type Server struct { // set in New conn *fuse.Conn debug func(msg interface{}) context func(ctx context.Context, req fuse.Request) context.Context // set once at Serve time fs FS dynamicInode func(parent uint64, name string) uint64 // state, protected by meta meta sync.Mutex req map[fuse.RequestID]*serveRequest node []*serveNode nodeRef map[Node]fuse.NodeID handle []*serveHandle freeNode []fuse.NodeID freeHandle []fuse.HandleID nodeGen uint64 // pending notify upcalls to kernel notifyMu sync.Mutex notifySeq fuse.RequestID notifyWait map[fuse.RequestID]chan<- *fuse.NotifyReply // Used to ensure worker goroutines finish before Serve returns wg sync.WaitGroup } // Serve serves the FUSE connection by making calls to the methods // of fs and the Nodes and Handles it makes available. It returns only // when the connection has been closed or an unexpected error occurs. func (s *Server) Serve(fs FS) error { defer s.wg.Wait() // Wait for worker goroutines to complete before return s.fs = fs if dyn, ok := fs.(FSInodeGenerator); ok { s.dynamicInode = dyn.GenerateInode } root, err := fs.Root() if err != nil { return fmt.Errorf("cannot obtain root node: %v", err) } // Recognize the root node if it's ever returned from Lookup, // passed to Invalidate, etc. s.nodeRef[root] = 1 s.node = append(s.node, nil, &serveNode{ inode: 1, generation: s.nodeGen, node: root, refs: 1, }) s.handle = append(s.handle, nil) for { req, err := s.conn.ReadRequest() if err != nil { if err == io.EOF { break } return err } s.wg.Add(1) go func() { defer s.wg.Done() s.serve(req) }() } return nil } // Serve serves a FUSE connection with the default settings. See // Server.Serve. func Serve(c *fuse.Conn, fs FS) error { server := New(c, nil) return server.Serve(fs) } type serveRequest struct { Request fuse.Request cancel func() } type serveNode struct { inode uint64 generation uint64 node Node refs uint64 // Delay freeing the NodeID until waitgroup is done. This allows // using the NodeID for short periods of time without holding the // Server.meta lock. // // Rules: // // - hold Server.meta while calling wg.Add, then unlock // - do NOT try to reacquire Server.meta wg sync.WaitGroup } func (sn *serveNode) attr(ctx context.Context, attr *fuse.Attr) error { err := nodeAttr(ctx, sn.node, attr) if attr.Inode == 0 { attr.Inode = sn.inode } return err } type serveHandle struct { handle Handle readData []byte } func (c *Server) saveNode(inode uint64, node Node) (id fuse.NodeID, gen uint64) { c.meta.Lock() defer c.meta.Unlock() if id, ok := c.nodeRef[node]; ok { sn := c.node[id] sn.refs++ return id, sn.generation } sn := &serveNode{inode: inode, node: node, refs: 1} if n := len(c.freeNode); n > 0 { id = c.freeNode[n-1] c.freeNode = c.freeNode[:n-1] c.node[id] = sn c.nodeGen++ } else { id = fuse.NodeID(len(c.node)) c.node = append(c.node, sn) } sn.generation = c.nodeGen c.nodeRef[node] = id return id, sn.generation } func (c *Server) saveHandle(handle Handle) (id fuse.HandleID) { c.meta.Lock() shandle := &serveHandle{handle: handle} if n := len(c.freeHandle); n > 0 { id = c.freeHandle[n-1] c.freeHandle = c.freeHandle[:n-1] c.handle[id] = shandle } else { id = fuse.HandleID(len(c.handle)) c.handle = append(c.handle, shandle) } c.meta.Unlock() return } type nodeRefcountDropBug struct { N uint64 Refs uint64 Node fuse.NodeID } func (n nodeRefcountDropBug) String() string { return fmt.Sprintf("bug: trying to drop %d of %d references to %v", n.N, n.Refs, n.Node) } // dropNode decreases reference count for node with id by n. // If reference count dropped to zero, returns true. // Note that node is not guaranteed to be non-nil. func (c *Server) dropNode(id fuse.NodeID, n uint64) (node Node, forget bool) { c.meta.Lock() defer c.meta.Unlock() snode := c.node[id] if snode == nil { // this should only happen if refcounts kernel<->us disagree // *and* two ForgetRequests for the same node race each other; // this indicates a bug somewhere c.debug(nodeRefcountDropBug{N: n, Node: id}) // we may end up triggering Forget twice, but that's better // than not even once, and that's the best we can do return nil, true } if n > snode.refs { c.debug(nodeRefcountDropBug{N: n, Refs: snode.refs, Node: id}) n = snode.refs } snode.refs -= n if snode.refs == 0 { snode.wg.Wait() c.node[id] = nil delete(c.nodeRef, snode.node) c.freeNode = append(c.freeNode, id) return snode.node, true } return nil, false } func (c *Server) dropHandle(id fuse.HandleID) { c.meta.Lock() c.handle[id] = nil c.freeHandle = append(c.freeHandle, id) c.meta.Unlock() } type missingHandle struct { Handle fuse.HandleID MaxHandle fuse.HandleID } func (m missingHandle) String() string { return fmt.Sprint("missing handle: ", m.Handle, m.MaxHandle) } // Returns nil for invalid handles. func (c *Server) getHandle(id fuse.HandleID) (shandle *serveHandle) { c.meta.Lock() defer c.meta.Unlock() if id < fuse.HandleID(len(c.handle)) { shandle = c.handle[uint(id)] } if shandle == nil { c.debug(missingHandle{ Handle: id, MaxHandle: fuse.HandleID(len(c.handle)), }) } return } type request struct { In interface{} `json:",omitempty"` } func (r request) String() string { return fmt.Sprintf("<- %s", r.In) } type logResponseHeader struct { ID fuse.RequestID } func (m logResponseHeader) String() string { return fmt.Sprintf("ID=%v", m.ID) } type response struct { Op string Request logResponseHeader Out interface{} `json:",omitempty"` // Errno contains the errno value as a string, for example "EPERM". Errno string `json:",omitempty"` // Error may contain a free form error message. Error string `json:",omitempty"` } func (r response) errstr() string { s := r.Errno if r.Error != "" { // prefix the errno constant to the long form message s = s + ": " + r.Error } return s } func (r response) String() string { switch { case r.Errno != "" && r.Out != nil: return fmt.Sprintf("-> [%v] %v error=%s", r.Request, r.Out, r.errstr()) case r.Errno != "": return fmt.Sprintf("-> [%v] %s error=%s", r.Request, r.Op, r.errstr()) case r.Out != nil: // make sure (seemingly) empty values are readable switch r.Out.(type) { case string: return fmt.Sprintf("-> [%v] %s %q", r.Request, r.Op, r.Out) case []byte: return fmt.Sprintf("-> [%v] %s [% x]", r.Request, r.Op, r.Out) default: return fmt.Sprintf("-> [%v] %v", r.Request, r.Out) } default: return fmt.Sprintf("-> [%v] %s", r.Request, r.Op) } } type notification struct { Op string Node fuse.NodeID Out interface{} `json:",omitempty"` Err string `json:",omitempty"` } func (n notification) String() string { var buf bytes.Buffer fmt.Fprintf(&buf, "=> %s %v", n.Op, n.Node) if n.Out != nil { // make sure (seemingly) empty values are readable switch n.Out.(type) { case string: fmt.Fprintf(&buf, " %q", n.Out) case []byte: fmt.Fprintf(&buf, " [% x]", n.Out) default: fmt.Fprintf(&buf, " %s", n.Out) } } if n.Err != "" { fmt.Fprintf(&buf, " Err:%v", n.Err) } return buf.String() } type notificationRequest struct { ID fuse.RequestID Op string Node fuse.NodeID Out interface{} `json:",omitempty"` } func (n notificationRequest) String() string { var buf bytes.Buffer fmt.Fprintf(&buf, ">> %s [ID=%d] %v", n.Op, n.ID, n.Node) if n.Out != nil { // make sure (seemingly) empty values are readable switch n.Out.(type) { case string: fmt.Fprintf(&buf, " %q", n.Out) case []byte: fmt.Fprintf(&buf, " [% x]", n.Out) default: fmt.Fprintf(&buf, " %s", n.Out) } } return buf.String() } type notificationResponse struct { ID fuse.RequestID Op string In interface{} `json:",omitempty"` Err string `json:",omitempty"` } func (n notificationResponse) String() string { var buf bytes.Buffer fmt.Fprintf(&buf, "<< [ID=%d] %s", n.ID, n.Op) if n.In != nil { // make sure (seemingly) empty values are readable switch n.In.(type) { case string: fmt.Fprintf(&buf, " %q", n.In) case []byte: fmt.Fprintf(&buf, " [% x]", n.In) default: fmt.Fprintf(&buf, " %s", n.In) } } if n.Err != "" { fmt.Fprintf(&buf, " Err:%v", n.Err) } return buf.String() } type logMissingNode struct { MaxNode fuse.NodeID } func opName(req fuse.Request) string { t := reflect.Indirect(reflect.ValueOf(req)).Type() s := t.Name() s = strings.TrimSuffix(s, "Request") return s } type logLinkRequestOldNodeNotFound struct { Request *fuse.Header In *fuse.LinkRequest } func (m *logLinkRequestOldNodeNotFound) String() string { return fmt.Sprintf("In LinkRequest (request %v), node %d not found", m.Request.Hdr().ID, m.In.OldNode) } type renameNewDirNodeNotFound struct { Request *fuse.Header In *fuse.RenameRequest } func (m *renameNewDirNodeNotFound) String() string { return fmt.Sprintf("In RenameRequest (request %v), node %d not found", m.Request.Hdr().ID, m.In.NewDir) } type handlerPanickedError struct { Request interface{} Err interface{} } var _ error = handlerPanickedError{} func (h handlerPanickedError) Error() string { return fmt.Sprintf("handler panicked: %v", h.Err) } var _ fuse.ErrorNumber = handlerPanickedError{} func (h handlerPanickedError) Errno() fuse.Errno { if err, ok := h.Err.(fuse.ErrorNumber); ok { return err.Errno() } return fuse.DefaultErrno } // handlerTerminatedError happens when a handler terminates itself // with runtime.Goexit. This is most commonly because of incorrect use // of testing.TB.FailNow, typically via t.Fatal. type handlerTerminatedError struct { Request interface{} } var _ error = handlerTerminatedError{} func (h handlerTerminatedError) Error() string { return fmt.Sprintf("handler terminated (called runtime.Goexit)") } var _ fuse.ErrorNumber = handlerTerminatedError{} func (h handlerTerminatedError) Errno() fuse.Errno { return fuse.DefaultErrno } type handleNotReaderError struct { handle Handle } var _ error = handleNotReaderError{} func (e handleNotReaderError) Error() string { return fmt.Sprintf("handle has no Read: %T", e.handle) } var _ fuse.ErrorNumber = handleNotReaderError{} func (e handleNotReaderError) Errno() fuse.Errno { return fuse.Errno(syscall.ENOTSUP) } func initLookupResponse(s *fuse.LookupResponse) { s.EntryValid = entryValidTime } func (c *Server) serve(r fuse.Request) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() parentCtx := ctx if c.context != nil { ctx = c.context(ctx, r) } req := &serveRequest{Request: r, cancel: cancel} switch r.(type) { case *fuse.NotifyReply: // don't log NotifyReply here, they're logged by the recipient // as soon as we have decoded them to the right types default: c.debug(request{ In: r, }) } var node Node var snode *serveNode c.meta.Lock() hdr := r.Hdr() if id := hdr.Node; id != 0 { if id < fuse.NodeID(len(c.node)) { snode = c.node[uint(id)] } if snode == nil { c.meta.Unlock() err := syscall.ESTALE c.debug(response{ Op: opName(r), Request: logResponseHeader{ID: hdr.ID}, Error: fuse.Errno(err).ErrnoName(), // this is the only place that sets both Error and // Out; not sure if i want to do that; might get rid // of len(c.node) things altogether Out: logMissingNode{ MaxNode: fuse.NodeID(len(c.node)), }, }) r.RespondError(err) return } node = snode.node } if c.req[hdr.ID] != nil { // This happens with OSXFUSE. Assume it's okay and // that we'll never see an interrupt for this one. // Otherwise everything wedges. TODO: Report to OSXFUSE? // // TODO this might have been because of missing done() calls } else { c.req[hdr.ID] = req } c.meta.Unlock() // Call this before responding. // After responding is too late: we might get another request // with the same ID and be very confused. done := func(resp interface{}) { msg := response{ Op: opName(r), Request: logResponseHeader{ID: hdr.ID}, } if err, ok := resp.(error); ok { errno := fuse.ToErrno(err) msg.Errno = errno.ErrnoName() if errno != err && syscall.Errno(errno) != err { // if it's more than just a fuse.Errno or a // syscall.Errno, log extra detail msg.Error = err.Error() } } else { msg.Out = resp } c.debug(msg) c.meta.Lock() delete(c.req, hdr.ID) c.meta.Unlock() } var responded bool defer func() { if rec := recover(); rec != nil { const size = 1 << 16 buf := make([]byte, size) n := runtime.Stack(buf, false) buf = buf[:n] log.Printf("fuse: panic in handler for %v: %v\n%s", r, rec, buf) err := handlerPanickedError{ Request: r, Err: rec, } done(err) r.RespondError(err) return } if !responded { err := handlerTerminatedError{ Request: r, } done(err) r.RespondError(err) } }() if err := c.handleRequest(ctx, node, snode, r, done); err != nil { if err == context.Canceled { select { case <-parentCtx.Done(): // We canceled the parent context because of an // incoming interrupt request, so return EINTR // to trigger the right behavior in the client app. // // Only do this when it's the parent context that was // canceled, not a context controlled by the program // using this library, so we don't return EINTR too // eagerly -- it might cause busy loops. // // Decent write-up on role of EINTR: // http://250bpm.com/blog:12 err = syscall.EINTR default: // nothing } } done(err) r.RespondError(err) } // disarm runtime.Goexit protection responded = true } // handleRequest will either a) call done(s) and r.Respond(s) OR b) return an error. func (c *Server) handleRequest(ctx context.Context, node Node, snode *serveNode, r fuse.Request, done func(resp interface{})) error { switch r := r.(type) { default: // Note: To FUSE, ENOSYS means "this server never implements this request." // It would be inappropriate to return ENOSYS for other operations in this // switch that might only be unavailable in some contexts, not all. return syscall.ENOSYS case *fuse.StatfsRequest: s := &fuse.StatfsResponse{} if fs, ok := c.fs.(FSStatfser); ok { if err := fs.Statfs(ctx, r, s); err != nil { return err } } done(s) r.Respond(s) return nil // Node operations. case *fuse.GetattrRequest: s := &fuse.GetattrResponse{} if n, ok := node.(NodeGetattrer); ok { if err := n.Getattr(ctx, r, s); err != nil { return err } } else { if err := snode.attr(ctx, &s.Attr); err != nil { return err } } done(s) r.Respond(s) return nil case *fuse.SetattrRequest: s := &fuse.SetattrResponse{} if n, ok := node.(NodeSetattrer); ok { if err := n.Setattr(ctx, r, s); err != nil { return err } } if err := snode.attr(ctx, &s.Attr); err != nil { return err } done(s) r.Respond(s) return nil case *fuse.SymlinkRequest: s := &fuse.SymlinkResponse{} initLookupResponse(&s.LookupResponse) n, ok := node.(NodeSymlinker) if !ok { return syscall.EIO // XXX or EPERM like Mkdir? } n2, err := n.Symlink(ctx, r) if err != nil { return err } if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.NewName, n2); err != nil { return err } done(s) r.Respond(s) return nil case *fuse.ReadlinkRequest: n, ok := node.(NodeReadlinker) if !ok { return syscall.EIO /// XXX or EPERM? } target, err := n.Readlink(ctx, r) if err != nil { return err } done(target) r.Respond(target) return nil case *fuse.LinkRequest: n, ok := node.(NodeLinker) if !ok { return syscall.EIO /// XXX or EPERM? } c.meta.Lock() var oldNode *serveNode if int(r.OldNode) < len(c.node) { oldNode = c.node[r.OldNode] } c.meta.Unlock() if oldNode == nil { c.debug(logLinkRequestOldNodeNotFound{ Request: r.Hdr(), In: r, }) return syscall.EIO } n2, err := n.Link(ctx, r, oldNode.node) if err != nil { return err } s := &fuse.LookupResponse{} initLookupResponse(s) if err := c.saveLookup(ctx, s, snode, r.NewName, n2); err != nil { return err } done(s) r.Respond(s) return nil case *fuse.RemoveRequest: n, ok := node.(NodeRemover) if !ok { return syscall.EIO /// XXX or EPERM? } err := n.Remove(ctx, r) if err != nil { return err } done(nil) r.Respond() return nil case *fuse.AccessRequest: if n, ok := node.(NodeAccesser); ok { if err := n.Access(ctx, r); err != nil { return err } } done(nil) r.Respond() return nil case *fuse.LookupRequest: var n2 Node var err error s := &fuse.LookupResponse{} initLookupResponse(s) if n, ok := node.(NodeStringLookuper); ok { n2, err = n.Lookup(ctx, r.Name) } else if n, ok := node.(NodeRequestLookuper); ok { n2, err = n.Lookup(ctx, r, s) } else { return syscall.ENOENT } if err != nil { return err } if err := c.saveLookup(ctx, s, snode, r.Name, n2); err != nil { return err } done(s) r.Respond(s) return nil case *fuse.MkdirRequest: s := &fuse.MkdirResponse{} initLookupResponse(&s.LookupResponse) n, ok := node.(NodeMkdirer) if !ok { return syscall.EPERM } n2, err := n.Mkdir(ctx, r) if err != nil { return err } if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.Name, n2); err != nil { return err } done(s) r.Respond(s) return nil case *fuse.OpenRequest: s := &fuse.OpenResponse{} var h2 Handle if n, ok := node.(NodeOpener); ok { hh, err := n.Open(ctx, r, s) if err != nil { return err } h2 = hh } else { h2 = node } s.Handle = c.saveHandle(h2) done(s) r.Respond(s) return nil case *fuse.CreateRequest: n, ok := node.(NodeCreater) if !ok { // If we send back ENOSYS, FUSE will try mknod+open. return syscall.EPERM } s := &fuse.CreateResponse{OpenResponse: fuse.OpenResponse{}} initLookupResponse(&s.LookupResponse) n2, h2, err := n.Create(ctx, r, s) if err != nil { return err } if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.Name, n2); err != nil { return err } s.Handle = c.saveHandle(h2) done(s) r.Respond(s) return nil case *fuse.GetxattrRequest: n, ok := node.(NodeGetxattrer) if !ok { return syscall.ENOTSUP } s := &fuse.GetxattrResponse{} err := n.Getxattr(ctx, r, s) if err != nil { return err } if r.Size != 0 && uint64(len(s.Xattr)) > uint64(r.Size) { return syscall.ERANGE } done(s) r.Respond(s) return nil case *fuse.ListxattrRequest: n, ok := node.(NodeListxattrer) if !ok { return syscall.ENOTSUP } s := &fuse.ListxattrResponse{} err := n.Listxattr(ctx, r, s) if err != nil { return err } if r.Size != 0 && uint64(len(s.Xattr)) > uint64(r.Size) { return syscall.ERANGE } done(s) r.Respond(s) return nil case *fuse.SetxattrRequest: n, ok := node.(NodeSetxattrer) if !ok { return syscall.ENOTSUP } err := n.Setxattr(ctx, r) if err != nil { return err } done(nil) r.Respond() return nil case *fuse.RemovexattrRequest: n, ok := node.(NodeRemovexattrer) if !ok { return syscall.ENOTSUP } err := n.Removexattr(ctx, r) if err != nil { return err } done(nil) r.Respond() return nil case *fuse.ForgetRequest: _, forget := c.dropNode(r.Hdr().Node, r.N) if forget { n, ok := node.(NodeForgetter) if ok { n.Forget() } } done(nil) r.Respond() return nil case *fuse.BatchForgetRequest: // BatchForgetRequest is hard to unit test, as it // fundamentally relies on something unprivileged userspace // has little control over. A root-only, Linux-only test could // be written with `echo 2 >/proc/sys/vm/drop_caches`, but // that would still rely on timing, the number of batches and // operation spread over them could vary, it wouldn't run in a // typical container regardless of privileges, and it would // degrade performance for the rest of the machine. It would // still probably be worth doing, just not the most fun. // node is nil here because BatchForget as a message is not // aimed at a any one node for _, item := range r.Forget { node, forget := c.dropNode(item.NodeID, item.N) // node can be nil here if kernel vs our refcount were out // of sync and multiple Forgets raced each other if node == nil { // nothing we can do about that continue } if forget { n, ok := node.(NodeForgetter) if ok { n.Forget() } } } done(nil) r.Respond() return nil // Handle operations. case *fuse.ReadRequest: shandle := c.getHandle(r.Handle) if shandle == nil { return syscall.ESTALE } handle := shandle.handle s := &fuse.ReadResponse{Data: make([]byte, 0, r.Size)} if r.Dir { if h, ok := handle.(HandleReadDirAller); ok { // detect rewinddir(3) or similar seek and refresh // contents if r.Offset == 0 { shandle.readData = nil } if shandle.readData == nil { dirs, err := h.ReadDirAll(ctx) if err != nil { return err } var data []byte for _, dir := range dirs { if dir.Inode == 0 { dir.Inode = c.dynamicInode(snode.inode, dir.Name) } data = fuse.AppendDirent(data, dir) } shandle.readData = data } fuseutil.HandleRead(r, s, shandle.readData) done(s) r.Respond(s) return nil } } else { if h, ok := handle.(HandleReadAller); ok { if shandle.readData == nil { data, err := h.ReadAll(ctx) if err != nil { return err } if data == nil { data = []byte{} } shandle.readData = data } fuseutil.HandleRead(r, s, shandle.readData) done(s) r.Respond(s) return nil } h, ok := handle.(HandleReader) if !ok { err := handleNotReaderError{handle: handle} return err } if err := h.Read(ctx, r, s); err != nil { return err } } done(s) r.Respond(s) return nil case *fuse.WriteRequest: shandle := c.getHandle(r.Handle) if shandle == nil { return syscall.ESTALE } s := &fuse.WriteResponse{} if h, ok := shandle.handle.(HandleWriter); ok { if err := h.Write(ctx, r, s); err != nil { return err } done(s) r.Respond(s) return nil } return syscall.EIO case *fuse.FlushRequest: shandle := c.getHandle(r.Handle) if shandle == nil { return syscall.ESTALE } handle := shandle.handle if h, ok := handle.(HandleFlusher); ok { if err := h.Flush(ctx, r); err != nil { return err } } done(nil) r.Respond() return nil case *fuse.ReleaseRequest: shandle := c.getHandle(r.Handle) if shandle == nil { return syscall.ESTALE } handle := shandle.handle // No matter what, release the handle. c.dropHandle(r.Handle) if h, ok := handle.(HandleReleaser); ok { if err := h.Release(ctx, r); err != nil { return err } } done(nil) r.Respond() return nil case *fuse.DestroyRequest: if fs, ok := c.fs.(FSDestroyer); ok { fs.Destroy() } done(nil) r.Respond() return nil case *fuse.RenameRequest: c.meta.Lock() var newDirNode *serveNode if int(r.NewDir) < len(c.node) { newDirNode = c.node[r.NewDir] } c.meta.Unlock() if newDirNode == nil { c.debug(renameNewDirNodeNotFound{ Request: r.Hdr(), In: r, }) return syscall.EIO } n, ok := node.(NodeRenamer) if !ok { return syscall.EIO // XXX or EPERM like Mkdir? } err := n.Rename(ctx, r, newDirNode.node) if err != nil { return err } done(nil) r.Respond() return nil case *fuse.MknodRequest: n, ok := node.(NodeMknoder) if !ok { return syscall.EIO } n2, err := n.Mknod(ctx, r) if err != nil { return err } s := &fuse.LookupResponse{} initLookupResponse(s) if err := c.saveLookup(ctx, s, snode, r.Name, n2); err != nil { return err } done(s) r.Respond(s) return nil case *fuse.FsyncRequest: n, ok := node.(NodeFsyncer) if !ok { return syscall.EIO } err := n.Fsync(ctx, r) if err != nil { return err } done(nil) r.Respond() return nil case *fuse.InterruptRequest: c.meta.Lock() ireq := c.req[r.IntrID] if ireq != nil && ireq.cancel != nil { ireq.cancel() ireq.cancel = nil } c.meta.Unlock() done(nil) r.Respond() return nil case *fuse.PollRequest: shandle := c.getHandle(r.Handle) if shandle == nil { return syscall.ESTALE } s := &fuse.PollResponse{} if h, ok := shandle.handle.(HandlePoller); ok { if err := h.Poll(ctx, r, s); err != nil { return err } done(s) r.Respond(s) return nil } if n, ok := node.(NodePoller); ok { if err := n.Poll(ctx, r, s); err != nil { return err } done(s) r.Respond(s) return nil } // fallback to always claim ready s.REvents = fuse.DefaultPollMask done(s) r.Respond(s) return nil case *fuse.NotifyReply: c.notifyMu.Lock() w, ok := c.notifyWait[r.Hdr().ID] if ok { delete(c.notifyWait, r.Hdr().ID) } c.notifyMu.Unlock() if !ok { c.debug(notificationResponse{ ID: r.Hdr().ID, Op: "NotifyReply", Err: "unknown ID", }) return nil } w <- r return nil case *fuse.LockRequest: shandle := c.getHandle(r.Handle) if shandle == nil { return syscall.ESTALE } h, ok := shandle.handle.(HandleLocker) if !ok { return syscall.ENOTSUP } if err := h.Lock(ctx, r); err != nil { return err } done(nil) r.Respond() return nil case *fuse.LockWaitRequest: shandle := c.getHandle(r.Handle) if shandle == nil { return syscall.ESTALE } h, ok := shandle.handle.(HandleLocker) if !ok { return syscall.ENOTSUP } if err := h.LockWait(ctx, r); err != nil { return err } done(nil) r.Respond() return nil case *fuse.UnlockRequest: shandle := c.getHandle(r.Handle) if shandle == nil { return syscall.ESTALE } h, ok := shandle.handle.(HandleLocker) if !ok { return syscall.ENOTSUP } if err := h.Unlock(ctx, r); err != nil { return err } done(nil) r.Respond() return nil case *fuse.QueryLockRequest: shandle := c.getHandle(r.Handle) if shandle == nil { return syscall.ESTALE } h, ok := shandle.handle.(HandleLocker) if !ok { return syscall.ENOTSUP } s := &fuse.QueryLockResponse{ Lock: fuse.FileLock{ Type: unix.F_UNLCK, }, } if err := h.QueryLock(ctx, r, s); err != nil { return err } done(s) r.Respond(s) return nil /* case *FsyncdirRequest: return ENOSYS case *BmapRequest: return ENOSYS case *SetvolnameRequest, *GetxtimesRequest, *ExchangeRequest: return ENOSYS */ } panic("not reached") } func (c *Server) saveLookup(ctx context.Context, s *fuse.LookupResponse, snode *serveNode, elem string, n2 Node) error { if err := nodeAttr(ctx, n2, &s.Attr); err != nil { return err } if s.Attr.Inode == 0 { s.Attr.Inode = c.dynamicInode(snode.inode, elem) } s.Node, s.Generation = c.saveNode(s.Attr.Inode, n2) return nil } type invalidateNodeDetail struct { Off int64 Size int64 } func (i invalidateNodeDetail) String() string { return fmt.Sprintf("Off:%d Size:%d", i.Off, i.Size) } func errstr(err error) string { if err == nil { return "" } return err.Error() } func (s *Server) invalidateNode(node Node, off int64, size int64) error { s.meta.Lock() id, ok := s.nodeRef[node] if ok { snode := s.node[id] snode.wg.Add(1) defer snode.wg.Done() } s.meta.Unlock() if !ok { // This is what the kernel would have said, if we had been // able to send this message; it's not cached. return fuse.ErrNotCached } // Delay logging until after we can record the error too. We // consider a /dev/fuse write to be instantaneous enough to not // need separate before and after messages. err := s.conn.InvalidateNode(id, off, size) s.debug(notification{ Op: "InvalidateNode", Node: id, Out: invalidateNodeDetail{ Off: off, Size: size, }, Err: errstr(err), }) return err } // InvalidateNodeAttr invalidates the kernel cache of the attributes // of node. // // Returns fuse.ErrNotCached if the kernel is not currently caching // the node. func (s *Server) InvalidateNodeAttr(node Node) error { return s.invalidateNode(node, 0, 0) } // InvalidateNodeData invalidates the kernel cache of the attributes // and data of node. // // Returns fuse.ErrNotCached if the kernel is not currently caching // the node. func (s *Server) InvalidateNodeData(node Node) error { return s.invalidateNode(node, 0, -1) } // InvalidateNodeDataRange invalidates the kernel cache of the // attributes and a range of the data of node. // // Returns fuse.ErrNotCached if the kernel is not currently caching // the node. func (s *Server) InvalidateNodeDataRange(node Node, off int64, size int64) error { return s.invalidateNode(node, off, size) } type invalidateEntryDetail struct { Name string } func (i invalidateEntryDetail) String() string { return fmt.Sprintf("%q", i.Name) } // InvalidateEntry invalidates the kernel cache of the directory entry // identified by parent node and entry basename. // // Kernel may or may not cache directory listings. To invalidate // those, use InvalidateNode to invalidate all of the data for a // directory. (As of 2015-06, Linux FUSE does not cache directory // listings.) // // Returns ErrNotCached if the kernel is not currently caching the // node. func (s *Server) InvalidateEntry(parent Node, name string) error { s.meta.Lock() id, ok := s.nodeRef[parent] if ok { snode := s.node[id] snode.wg.Add(1) defer snode.wg.Done() } s.meta.Unlock() if !ok { // This is what the kernel would have said, if we had been // able to send this message; it's not cached. return fuse.ErrNotCached } err := s.conn.InvalidateEntry(id, name) s.debug(notification{ Op: "InvalidateEntry", Node: id, Out: invalidateEntryDetail{ Name: name, }, Err: errstr(err), }) return err } type notifyStoreRetrieveDetail struct { Off uint64 Size uint64 } func (i notifyStoreRetrieveDetail) String() string { return fmt.Sprintf("Off:%d Size:%d", i.Off, i.Size) } type notifyRetrieveReplyDetail struct { Size uint64 } func (i notifyRetrieveReplyDetail) String() string { return fmt.Sprintf("Size:%d", i.Size) } // NotifyStore puts data into the kernel page cache. // // Returns fuse.ErrNotCached if the kernel is not currently caching // the node. func (s *Server) NotifyStore(node Node, offset uint64, data []byte) error { s.meta.Lock() id, ok := s.nodeRef[node] if ok { snode := s.node[id] snode.wg.Add(1) defer snode.wg.Done() } s.meta.Unlock() if !ok { // This is what the kernel would have said, if we had been // able to send this message; it's not cached. return fuse.ErrNotCached } // Delay logging until after we can record the error too. We // consider a /dev/fuse write to be instantaneous enough to not // need separate before and after messages. err := s.conn.NotifyStore(id, offset, data) s.debug(notification{ Op: "NotifyStore", Node: id, Out: notifyStoreRetrieveDetail{ Off: offset, Size: uint64(len(data)), }, Err: errstr(err), }) return err } // NotifyRetrieve gets data from the kernel page cache. // // Returns fuse.ErrNotCached if the kernel is not currently caching // the node. func (s *Server) NotifyRetrieve(node Node, offset uint64, size uint32) ([]byte, error) { s.meta.Lock() id, ok := s.nodeRef[node] if ok { snode := s.node[id] snode.wg.Add(1) defer snode.wg.Done() } s.meta.Unlock() if !ok { // This is what the kernel would have said, if we had been // able to send this message; it's not cached. return nil, fuse.ErrNotCached } ch := make(chan *fuse.NotifyReply, 1) s.notifyMu.Lock() const wraparoundThreshold = 1 << 63 if s.notifySeq > wraparoundThreshold { s.notifyMu.Unlock() return nil, errors.New("running out of notify sequence numbers") } s.notifySeq++ seq := s.notifySeq s.notifyWait[seq] = ch s.notifyMu.Unlock() s.debug(notificationRequest{ ID: seq, Op: "NotifyRetrieve", Node: id, Out: notifyStoreRetrieveDetail{ Off: offset, Size: uint64(size), }, }) retrieval, err := s.conn.NotifyRetrieve(seq, id, offset, size) if err != nil { s.debug(notificationResponse{ ID: seq, Op: "NotifyRetrieve", Err: errstr(err), }) return nil, err } reply := <-ch data := retrieval.Finish(reply) s.debug(notificationResponse{ ID: seq, Op: "NotifyRetrieve", In: notifyRetrieveReplyDetail{ Size: uint64(len(data)), }, }) return data, nil } func (s *Server) NotifyPollWakeup(wakeup fuse.PollWakeup) error { // Delay logging until after we can record the error too. We // consider a /dev/fuse write to be instantaneous enough to not // need separate before and after messages. err := s.conn.NotifyPollWakeup(wakeup) s.debug(notification{ Op: "NotifyPollWakeup", Out: wakeup, Err: errstr(err), }) return err } // DataHandle returns a read-only Handle that satisfies reads // using the given data. func DataHandle(data []byte) Handle { return &dataHandle{data} } type dataHandle struct { data []byte } func (d *dataHandle) ReadAll(ctx context.Context) ([]byte, error) { return d.data, nil } // GenerateDynamicInode returns a dynamic inode. // // The parent inode and current entry name are used as the criteria // for choosing a pseudorandom inode. This makes it likely the same // entry will get the same inode on multiple runs. func GenerateDynamicInode(parent uint64, name string) uint64 { h := fnv.New64a() var buf [8]byte binary.LittleEndian.PutUint64(buf[:], parent) _, _ = h.Write(buf[:]) _, _ = h.Write([]byte(name)) var inode uint64 for { inode = h.Sum64() if inode > 1 { break } // there's a tiny probability that result is zero or the // hardcoded root inode 1; change the input a little and try // again _, _ = h.Write([]byte{'x'}) } return inode } golang-github-anacrolix-fuse-0.2.0/fs/serve_darwin_test.go000066400000000000000000000037041444232012100236410ustar00rootroot00000000000000package fs_test import ( "context" "errors" "fmt" "os" "syscall" "testing" "github.com/anacrolix/fuse/fs/fstestutil" "github.com/anacrolix/fuse/fs/fstestutil/spawntest/httpjson" "golang.org/x/sys/unix" ) func platformStatfs(st *syscall.Statfs_t) *statfsResult { return &statfsResult{ Blocks: st.Blocks, Bfree: st.Bfree, Bavail: st.Bavail, Files: st.Files, Ffree: st.Ffree, Bsize: int64(st.Iosize), Namelen: 0, Frsize: 0, } } func platformStat(fi os.FileInfo) *statResult { r := &statResult{ Mode: fi.Mode(), } st := fi.Sys().(*syscall.Stat_t) r.Ino = st.Ino r.Nlink = uint64(st.Nlink) r.UID = st.Uid r.GID = st.Gid r.Blksize = int64(st.Blksize) return r } type exchangeData struct { fstestutil.File // this struct cannot be zero size or multiple instances may look identical _ int } type exchangedataRequest struct { Path1 string Path2 string Options int WantErrno syscall.Errno } func doExchange(ctx context.Context, req exchangedataRequest) (*struct{}, error) { if err := unix.Exchangedata(req.Path1, req.Path2, req.Options); !errors.Is(err, req.WantErrno) { return nil, fmt.Errorf("from error from exchangedata: %v", err) } return &struct{}{}, nil } var exchangeHelper = helpers.Register("exchange", httpjson.ServePOST(doExchange)) func TestExchangeDataNotSupported(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{ "one": &exchangeData{}, "two": &exchangeData{}, }}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := exchangeHelper.Spawn(ctx, t) defer control.Close() req := exchangedataRequest{ Path1: mnt.Dir + "/one", Path2: mnt.Dir + "/two", Options: 0, WantErrno: syscall.ENOTSUP, } var nothing struct{} if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } } golang-github-anacrolix-fuse-0.2.0/fs/serve_freebsd_test.go000066400000000000000000000011011444232012100237540ustar00rootroot00000000000000package fs_test import ( "os" "syscall" ) func platformStatfs(st *syscall.Statfs_t) *statfsResult { return &statfsResult{ Blocks: st.Blocks, Bfree: st.Bfree, Bavail: uint64(st.Bavail), Files: st.Files, Ffree: uint64(st.Ffree), Bsize: int64(st.Iosize), Namelen: int64(st.Namemax), Frsize: int64(st.Bsize), } } func platformStat(fi os.FileInfo) *statResult { r := &statResult{ Mode: fi.Mode(), } st := fi.Sys().(*syscall.Stat_t) r.Ino = st.Ino r.Nlink = st.Nlink r.UID = st.Uid r.GID = st.Gid r.Blksize = int64(st.Blksize) return r } golang-github-anacrolix-fuse-0.2.0/fs/serve_linux_test.go000066400000000000000000000025441444232012100235150ustar00rootroot00000000000000package fs_test import ( "io" "os" "syscall" "golang.org/x/sys/unix" ) func platformStatfs(st *syscall.Statfs_t) *statfsResult { return &statfsResult{ Blocks: st.Blocks, Bfree: st.Bfree, Bavail: st.Bavail, Files: st.Files, Ffree: st.Ffree, Bsize: st.Bsize, Namelen: st.Namelen, Frsize: st.Frsize, } } func platformStat(fi os.FileInfo) *statResult { r := &statResult{ Mode: fi.Mode(), } st := fi.Sys().(*syscall.Stat_t) r.Ino = st.Ino r.Nlink = st.Nlink r.UID = st.Uid r.GID = st.Gid r.Blksize = st.Blksize return r } var _lockOFDHelper = helpers.Register("lock-ofd", &lockHelp{ lockFn: func(fd uintptr, req *lockReq) error { lk := unix.Flock_t{ Type: unix.F_WRLCK, Whence: int16(io.SeekStart), Start: req.Start, Len: req.Len, } cmd := unix.F_OFD_SETLK if req.Wait { cmd = unix.F_OFD_SETLKW } return unix.FcntlFlock(fd, cmd, &lk) }, unlockFn: func(fd uintptr, req *lockReq) error { lk := unix.Flock_t{ Type: unix.F_UNLCK, Whence: int16(io.SeekStart), Start: req.Start, Len: req.Len, } cmd := unix.F_OFD_SETLK if req.Wait { cmd = unix.F_OFD_SETLKW } return unix.FcntlFlock(fd, cmd, &lk) }, queryFn: func(fd uintptr, lk *unix.Flock_t) error { cmd := unix.F_OFD_GETLK return unix.FcntlFlock(fd, cmd, lk) }, }) func init() { lockOFDHelper = _lockOFDHelper } golang-github-anacrolix-fuse-0.2.0/fs/serve_test.go000066400000000000000000003540121444232012100222760ustar00rootroot00000000000000package fs_test import ( "bytes" "context" "errors" "fmt" "golang.org/x/sys/unix" "io" "io/ioutil" "log" "net/http" "os" "path" "path/filepath" "runtime" "strings" "sync" "sync/atomic" "syscall" "testing" "time" "github.com/anacrolix/fuse" "github.com/anacrolix/fuse/fs" "github.com/anacrolix/fuse/fs/fstestutil" "github.com/anacrolix/fuse/fs/fstestutil/record" "github.com/anacrolix/fuse/fs/fstestutil/spawntest" "github.com/anacrolix/fuse/fs/fstestutil/spawntest/httpjson" "github.com/anacrolix/fuse/fuseutil" "github.com/anacrolix/fuse/syscallx" ) func maybeParallel(t *testing.T) { // t.Parallel() } var helpers spawntest.Registry // TO TEST: // Lookup(*LookupRequest, *LookupResponse) // Getattr(*GetattrRequest, *GetattrResponse) // Attr with explicit inode // Setattr(*SetattrRequest, *SetattrResponse) // Access(*AccessRequest) // Open(*OpenRequest, *OpenResponse) // Write(*WriteRequest, *WriteResponse) // Flush(*FlushRequest, *FlushResponse) func init() { fstestutil.DebugByDefault() } // symlink can be embedded in a struct to make it look like a symlink. type symlink struct { } func (f symlink) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = os.ModeSymlink | 0o666 return nil } // fifo can be embedded in a struct to make it look like a named pipe. type fifo struct{} func (f fifo) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = os.ModeNamedPipe | 0o666 return nil } func TestMountpointDoesNotExist(t *testing.T) { maybeParallel(t) tmp, err := ioutil.TempDir("", "fusetest") if err != nil { t.Fatal(err) } defer os.Remove(tmp) mountpoint := path.Join(tmp, "does-not-exist") conn, err := fuse.Mount(mountpoint) if err == nil { conn.Close() t.Fatalf("expected error with non-existent mountpoint") } if _, ok := err.(*fuse.MountpointDoesNotExistError); !ok { t.Fatalf("wrong error from mount: %T: %v", err, err) } } type badRootFS struct{} func (badRootFS) Root() (fs.Node, error) { // pick a really distinct error, to identify it later return nil, fuse.Errno(syscall.ENAMETOOLONG) } func TestRootErr(t *testing.T) { maybeParallel(t) mnt, err := fstestutil.MountedT(t, badRootFS{}, nil) if err == nil { // path for synchronous mounts (linux): started out fine, now // wait for Serve to cycle through err = <-mnt.Error // without this, unmount will keep failing with EBUSY; nudge // kernel into realizing InitResponse will not happen mnt.Conn.Close() mnt.Close() } if err == nil { t.Fatal("expected an error") } // TODO this should not be a textual comparison, Serve hides // details if err.Error() != "cannot obtain root node: file name too long" { t.Errorf("Unexpected error: %v", err) } } type testPanic struct{} type panicSentinel struct{} var _ error = panicSentinel{} func (panicSentinel) Error() string { return "just a test" } var _ fuse.ErrorNumber = panicSentinel{} func (panicSentinel) Errno() fuse.Errno { return fuse.Errno(syscall.ENAMETOOLONG) } func (f testPanic) Root() (fs.Node, error) { return f, nil } func (f testPanic) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 1 a.Mode = os.ModeDir | 0o777 return nil } func (f testPanic) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) { panic(panicSentinel{}) } func doPanic(ctx context.Context, dir string) (*struct{}, error) { err := os.Mkdir(dir+"/trigger-a-panic", 0o700) if nerr, ok := err.(*os.PathError); !ok || nerr.Err != syscall.ENAMETOOLONG { return nil, fmt.Errorf("wrong error from panicking handler: %T: %v", err, err) } return &struct{}{}, nil } var panicHelper = helpers.Register("panic", httpjson.ServePOST(doPanic)) func TestPanic(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() mnt, err := fstestutil.MountedT(t, testPanic{}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := panicHelper.Spawn(ctx, t) defer control.Close() var nothing struct{} if err := control.JSON("/").Call(ctx, mnt.Dir, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } } type testStatFS struct{} func (f testStatFS) Root() (fs.Node, error) { return f, nil } func (f testStatFS) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 1 a.Mode = os.ModeDir | 0o777 return nil } func (f testStatFS) Statfs(ctx context.Context, req *fuse.StatfsRequest, resp *fuse.StatfsResponse) error { resp.Blocks = 42 resp.Bfree = 10 resp.Bavail = 3 resp.Files = 13 resp.Ffree = 11 resp.Bsize = 1000 resp.Namelen = 34 resp.Frsize = 7 return nil } type statfsResult struct { Blocks uint64 Bfree uint64 Bavail uint64 Files uint64 Ffree uint64 Bsize int64 Namelen int64 Frsize int64 } func prepStatfs(dir string) error { if runtime.GOOS == "darwin" { // Perform an operation that forces the OS X mount to be ready, so // we know the Statfs handler will really be called. OS X insists // on volumes answering Statfs calls very early (before FUSE // handshake), so OSXFUSE gives made-up answers for a few brief moments // during the mount process. if _, err := os.Stat(dir + "/does-not-exist"); !os.IsNotExist(err) { return err } } return nil } func doStatfs(ctx context.Context, dir string) (*statfsResult, error) { if err := prepStatfs(dir); err != nil { return nil, err } var st syscall.Statfs_t if err := syscall.Statfs(dir, &st); err != nil { return nil, fmt.Errorf("Statfs failed: %v", err) } log.Printf("Statfs got: %#v", st) r := platformStatfs(&st) return r, nil } var statfsHelper = helpers.Register("statfs", httpjson.ServePOST(doStatfs)) func testStatfs(t *testing.T, helper *spawntest.Helper) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() mnt, err := fstestutil.MountedT(t, testStatFS{}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := helper.Spawn(ctx, t) defer control.Close() var got statfsResult if err := control.JSON("/").Call(ctx, mnt.Dir, &got); err != nil { t.Fatalf("calling helper: %v", err) } if g, e := got.Blocks, uint64(42); g != e { t.Errorf("got Blocks = %d; want %d", g, e) } if g, e := got.Bfree, uint64(10); g != e { t.Errorf("got Bfree = %d; want %d", g, e) } if g, e := got.Bavail, uint64(3); g != e { t.Errorf("got Bavail = %d; want %d", g, e) } if g, e := got.Files, uint64(13); g != e { t.Errorf("got Files = %d; want %d", g, e) } if g, e := got.Ffree, uint64(11); g != e { t.Errorf("got Ffree = %d; want %d", g, e) } switch runtime.GOOS { case "freebsd": // freebsd gives 65536 here regardless of the fuse fs if got.Bsize != 65536 { t.Errorf("freebsd now implements statfs Bsize, please fix tests") } case "darwin": // darwin gives 4096 here regardless of the fuse fs if got.Bsize != 4096 { t.Errorf("darwin now implements statfs Bsize, please fix tests") } default: if g, e := got.Bsize, int64(1000); g != e { t.Errorf("got Bsize = %d; want %d", g, e) } } switch runtime.GOOS { case "darwin": if g, e := got.Namelen, int64(0); g != e { t.Errorf("darwin now implements Namelen, please fix tests") } default: if g, e := got.Namelen, int64(34); g != e { t.Errorf("got Namelen = %d; want %d", g, e) } } switch runtime.GOOS { case "darwin": if g, e := got.Frsize, int64(0); g != e { t.Errorf("darwin now implements Frsize, please fix tests") } default: if g, e := got.Frsize, int64(7); g != e { t.Errorf("got Frsize = %d; want %d", g, e) } } } func TestStatfs(t *testing.T) { testStatfs(t, statfsHelper) } func doFstatfs(ctx context.Context, dir string) (*statfsResult, error) { if err := prepStatfs(dir); err != nil { return nil, err } f, err := os.Open(dir) if err != nil { return nil, fmt.Errorf("Open for fstatfs failed: %v", err) } defer f.Close() var st syscall.Statfs_t err = syscall.Fstatfs(int(f.Fd()), &st) if err != nil { return nil, fmt.Errorf("Fstatfs failed: %v", err) } log.Printf("Fstatfs got: %#v", st) r := platformStatfs(&st) return r, nil } var fstatfsHelper = helpers.Register("fstatfs", httpjson.ServePOST(doFstatfs)) func TestFstatfs(t *testing.T) { testStatfs(t, fstatfsHelper) } // Test Stat of root. type root struct{} func (f root) Root() (fs.Node, error) { return f, nil } func (root) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 1 a.Mode = os.ModeDir | 0o555 // This has to be a power of two, but try to pick something that's an unlikely default. a.BlockSize = 65536 return nil } type statResult struct { Mode os.FileMode Ino uint64 Nlink uint64 UID uint32 GID uint32 Blksize int64 } func doStat(ctx context.Context, path string) (*statResult, error) { fi, err := os.Stat(path) if err != nil { return nil, err } r := platformStat(fi) return r, nil } var statHelper = helpers.Register("stat", httpjson.ServePOST(doStat)) func TestStatRoot(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() mnt, err := fstestutil.MountedT(t, root{}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := statHelper.Spawn(ctx, t) defer control.Close() var got statResult if err := control.JSON("/").Call(ctx, mnt.Dir, &got); err != nil { t.Fatalf("calling helper: %v", err) } if (got.Mode & os.ModeType) != os.ModeDir { t.Errorf("root is not a directory: %v", got.Mode) } if p := got.Mode.Perm(); p != 0o555 { t.Errorf("root has weird access mode: %v", p) } if got.Ino != 1 { t.Errorf("root has wrong inode: %v", got.Ino) } if got.Nlink != 1 { t.Errorf("root has wrong link count: %v", got.Nlink) } if got.UID != 0 { t.Errorf("root has wrong uid: %d", got.UID) } if got.GID != 0 { t.Errorf("root has wrong gid: %d", got.GID) } if g, e := got.Blksize, int64(65536); g != e { // convert got.Blksize too because it's int64 on Linux but // int32 on Darwin. if g, e := int64(got.Blksize), int64(65536); g != e { t.Errorf("root has wrong blocksize: %d != %d", g, e) } } } // Test Read calling ReadAll. type readAll struct { fstestutil.File } const hi = "hello, world" func (readAll) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0o666 a.Size = uint64(len(hi)) return nil } func (readAll) ReadAll(ctx context.Context) ([]byte, error) { return []byte(hi), nil } type readResult struct { Data []byte } func doRead(ctx context.Context, path string) (*readResult, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() data := make([]byte, 4096) n, err := f.Read(data) if err != nil { return nil, err } r := &readResult{Data: data[:n]} return r, nil } var readHelper = helpers.Register("read", httpjson.ServePOST(doRead)) func TestReadAll(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": readAll{}}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := readHelper.Spawn(ctx, t) defer control.Close() var got readResult if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil { t.Fatalf("calling helper: %v", err) } if g, e := string(got.Data), hi; g != e { t.Errorf("readAll = %q, want %q", g, e) } } // Test Read. type readWithHandleRead struct { fstestutil.File } func (readWithHandleRead) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0o666 a.Size = uint64(len(hi)) return nil } func (readWithHandleRead) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { fuseutil.HandleRead(req, resp, []byte(hi)) return nil } func TestReadAllWithHandleRead(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": readWithHandleRead{}}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := readHelper.Spawn(ctx, t) defer control.Close() var got readResult if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil { t.Fatalf("calling helper: %v", err) } if g, e := string(got.Data), hi; g != e { t.Errorf("readAll = %q, want %q", g, e) } } type readFlags struct { fstestutil.File fileFlags record.Recorder } func (r *readFlags) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0o666 a.Size = uint64(len(hi)) return nil } func (r *readFlags) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { r.fileFlags.Record(req.FileFlags) fuseutil.HandleRead(req, resp, []byte(hi)) return nil } func doReadFileFlags(ctx context.Context, path string) (*struct{}, error) { f, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND, 0o666) if err != nil { return nil, err } defer f.Close() if _, err := f.Read(make([]byte, 4096)); err != nil { return nil, err } _ = f.Close() return &struct{}{}, nil } var readFileFlagsHelper = helpers.Register("readFileFlags", httpjson.ServePOST(doReadFileFlags)) func TestReadFileFlags(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() r := &readFlags{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": r}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := readFileFlagsHelper.Spawn(ctx, t) defer control.Close() var nothing struct{} if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } got := r.fileFlags.Recorded().(fuse.OpenFlags) got &^= fuse.OpenNonblock want := fuse.OpenReadWrite | fuse.OpenAppend if runtime.GOOS == "darwin" { // OSXFUSE shares one read and one write handle for all // clients, so it uses a OpenReadOnly handle for performing // our read. // // If this test starts failing in the future, that probably // means they added the feature, and we want to notice that! want = fuse.OpenReadOnly } if runtime.GOOS == "freebsd" { // FreeBSD doesn't pass append to FUSE? want ^= fuse.OpenAppend } if g, e := got, want; g != e { t.Errorf("read saw file flags %+v, want %+v", g, e) } } type writeFlags struct { fstestutil.File fileFlags record.Recorder } func (r *writeFlags) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0o666 // do not set Size here or FreeBSD will do a read-modify-write, // even if the write replaces whole page contents return nil } func (r *writeFlags) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { // OSXFUSE 3.0.4 does a read-modify-write cycle even when the // write was for 4096 bytes. fuseutil.HandleRead(req, resp, []byte(hi)) return nil } func (r *writeFlags) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error { r.fileFlags.Record(req.FileFlags) resp.Size = len(req.Data) return nil } func doWriteFileFlags(ctx context.Context, path string) (*struct{}, error) { f, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND, 0o666) if err != nil { return nil, err } defer f.Close() if _, err := f.Write(make([]byte, 4096)); err != nil { return nil, err } _ = f.Close() return &struct{}{}, nil } var writeFileFlagsHelper = helpers.Register("writeFileFlags", httpjson.ServePOST(doWriteFileFlags)) func TestWriteFileFlags(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() r := &writeFlags{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": r}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := writeFileFlagsHelper.Spawn(ctx, t) defer control.Close() var nothing struct{} if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } got := r.fileFlags.Recorded().(fuse.OpenFlags) got &^= fuse.OpenNonblock want := fuse.OpenReadWrite | fuse.OpenAppend if runtime.GOOS == "darwin" { // OSXFUSE shares one read and one write handle for all // clients, so it uses a OpenWriteOnly handle for performing // our read. // // If this test starts failing in the future, that probably // means they added the feature, and we want to notice that! want = fuse.OpenWriteOnly } if runtime.GOOS == "freebsd" { // FreeBSD doesn't pass append to FUSE? want &^= fuse.OpenAppend } if g, e := got, want; g != e { t.Errorf("write saw file flags %+v, want %+v", g, e) } } // Test Release. type release struct { fstestutil.File record.ReleaseWaiter } func doOpen(ctx context.Context, path string) (*struct{}, error) { f, err := os.Open(path) if err != nil { return nil, err } f.Close() return &struct{}{}, nil } var openHelper = helpers.Register("open", httpjson.ServePOST(doOpen)) func TestRelease(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() r := &release{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": r}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := openHelper.Spawn(ctx, t) defer control.Close() var nothing struct{} if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } got, ok := r.WaitForRelease(1 * time.Second) if !ok { t.Error("Close did not Release in time") } // dynamic values that are too hard to control if got.Handle == 0 { t.Errorf("got ReleaseRequest with no Handle") } got.Handle = 0 want := &fuse.ReleaseRequest{ Flags: fuse.OpenReadOnly | fuse.OpenNonblock, } if runtime.GOOS == "freebsd" { // Go on FreeBSD isn't using the netpoller for os.File? want.Flags &^= fuse.OpenNonblock // no locking used but FreeBSD sets LockOwner? got.LockOwner = 0 } if g, e := got, want; *g != *e { t.Errorf("bad release:\ngot\t%v\nwant\t%v", g, e) } } // Test Write calling basic Write, with an fsync thrown in too. type write struct { fstestutil.File record.Writes record.Fsyncs } type createWriteFsyncHelp struct { mu sync.Mutex file *os.File } func (cwf *createWriteFsyncHelp) ServeHTTP(w http.ResponseWriter, req *http.Request) { switch req.URL.Path { case "/createWrite": httpjson.ServePOST(cwf.doCreateWrite).ServeHTTP(w, req) case "/fsync": httpjson.ServePOST(cwf.doFsync).ServeHTTP(w, req) case "/close": httpjson.ServePOST(cwf.doClose).ServeHTTP(w, req) default: http.NotFound(w, req) } } func (cwf *createWriteFsyncHelp) doCreateWrite(ctx context.Context, path string) (*struct{}, error) { cwf.mu.Lock() defer cwf.mu.Unlock() f, err := os.Create(path) if err != nil { return nil, fmt.Errorf("Create: %v", err) } cwf.file = f n, err := f.Write([]byte(hi)) if err != nil { return nil, fmt.Errorf("Write: %v", err) } if n != len(hi) { return nil, fmt.Errorf("short write; n=%d; hi=%d", n, len(hi)) } return &struct{}{}, nil } func (cwf *createWriteFsyncHelp) doFsync(ctx context.Context, _ struct{}) (*struct{}, error) { cwf.mu.Lock() defer cwf.mu.Unlock() if err := cwf.file.Sync(); err != nil { return nil, fmt.Errorf("Fsync = %v", err) } return &struct{}{}, nil } func (cwf *createWriteFsyncHelp) doClose(ctx context.Context, _ struct{}) (*struct{}, error) { cwf.mu.Lock() defer cwf.mu.Unlock() if err := cwf.file.Close(); err != nil { return nil, fmt.Errorf("Close: %v", err) } return &struct{}{}, nil } var createWriteFsyncHelper = helpers.Register("createWriteFsync", &createWriteFsyncHelp{}) func TestWrite(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() w := &write{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": w}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := createWriteFsyncHelper.Spawn(ctx, t) defer control.Close() var nothing struct{} if err := control.JSON("/createWrite").Call(ctx, mnt.Dir+"/child", ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } if err := control.JSON("/fsync").Call(ctx, struct{}{}, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } if w.RecordedFsync() == (fuse.FsyncRequest{}) { t.Errorf("never received expected fsync call") } if got := string(w.RecordedWriteData()); got != hi { t.Errorf("write = %q, want %q", got, hi) } if err := control.JSON("/close").Call(ctx, struct{}{}, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } } // Test Write of a larger buffer. func makeLargeData() (one, large []byte) { o := []byte("xyzzyfoo") l := bytes.Repeat(o, 8192) return o, l } func doWriteLarge(ctx context.Context, path string) (*struct{}, error) { f, err := os.Create(path) if err != nil { return nil, fmt.Errorf("Create: %v", err) } defer f.Close() _, large := makeLargeData() n, err := f.Write(large) if err != nil { return nil, fmt.Errorf("Write: %v", err) } if g, e := n, len(large); g != e { return nil, fmt.Errorf("short write: %d != %d", g, e) } err = f.Close() if err != nil { return nil, fmt.Errorf("Close: %v", err) } return &struct{}{}, nil } var writeLargeHelper = helpers.Register("writeLarge", httpjson.ServePOST(doWriteLarge)) func TestWriteLarge(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() w := &write{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": w}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := writeLargeHelper.Spawn(ctx, t) defer control.Close() var nothing struct{} if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } got := w.RecordedWriteData() one, large := makeLargeData() if g, e := len(got), len(large); g != e { t.Errorf("write wrong length: %d != %d", g, e) } if g := bytes.Replace(got, one, nil, -1); len(g) > 0 { t.Errorf("write wrong data: expected repeats of %q, also got %q", one, g) } } // Test Write calling Setattr+Write+Flush. type writeTruncateFlush struct { fstestutil.File record.Writes record.Setattrs record.Flushes } type writeFileRequest struct { Path string Data []byte } func doWriteFile(ctx context.Context, req writeFileRequest) (*struct{}, error) { if err := ioutil.WriteFile(req.Path, req.Data, 0o666); err != nil { return nil, fmt.Errorf("WriteFile: %v", err) } return &struct{}{}, nil } var writeFileHelper = helpers.Register("writeFile", httpjson.ServePOST(doWriteFile)) func TestWriteTruncateFlush(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() w := &writeTruncateFlush{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": w}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := writeFileHelper.Spawn(ctx, t) defer control.Close() var nothing struct{} req := writeFileRequest{ Path: mnt.Dir + "/child", Data: []byte(hi), } if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } if w.RecordedSetattr() == (fuse.SetattrRequest{}) { t.Errorf("writeTruncateFlush expected Setattr") } if !w.RecordedFlush() { t.Errorf("writeTruncateFlush expected Setattr") } if got := string(w.RecordedWriteData()); got != hi { t.Errorf("writeTruncateFlush = %q, want %q", got, hi) } } // Test Mkdir. type mkdir1 struct { fstestutil.Dir record.Mkdirs } func (f *mkdir1) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) { f.Mkdirs.Mkdir(ctx, req) return &mkdir1{}, nil } func doMkdir(ctx context.Context, path string) (*struct{}, error) { // uniform umask needed to make os.Mkdir's mode into something // reproducible syscall.Umask(0o022) if err := os.Mkdir(path, 0o771); err != nil { return nil, fmt.Errorf("mkdir: %v", err) } return &struct{}{}, nil } var mkdirHelper = helpers.Register("mkdir", httpjson.ServePOST(doMkdir)) func TestMkdir(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &mkdir1{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := mkdirHelper.Spawn(ctx, t) defer control.Close() var nothing struct{} if err := control.JSON("/").Call(ctx, mnt.Dir+"/foo", ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } want := fuse.MkdirRequest{ Name: "foo", Mode: os.ModeDir | 0o751, Umask: 0o022, } if runtime.GOOS == "darwin" { // https://github.com/osxfuse/osxfuse/issues/225 want.Umask = 0 } if g, e := f.RecordedMkdir(), want; g != e { t.Errorf("mkdir saw %+v, want %+v", g, e) } } // Test Create type create1file struct { fstestutil.File record.Creates } type create1 struct { fstestutil.Dir f create1file } func (f *create1) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) { if req.Name != "foo" { log.Printf("ERROR create1.Create unexpected name: %q\n", req.Name) return nil, nil, syscall.EPERM } _, _, _ = f.f.Creates.Create(ctx, req, resp) return &f.f, &f.f, nil } func doCreate(ctx context.Context, path string) (*struct{}, error) { // uniform umask needed to make os.Mkdir's mode into something // reproducible syscall.Umask(0o022) f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o640) if err != nil { return nil, fmt.Errorf("create1 WriteFile: %v", err) } _ = f.Close() return &struct{}{}, nil } var createHelper = helpers.Register("create", httpjson.ServePOST(doCreate)) func TestCreate(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &create1{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := createHelper.Spawn(ctx, t) defer control.Close() var nothing struct{} if err := control.JSON("/").Call(ctx, mnt.Dir+"/foo", ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } want := fuse.CreateRequest{ Name: "foo", Flags: fuse.OpenReadWrite | fuse.OpenCreate | fuse.OpenTruncate, Mode: 0o640, Umask: 0o022, } if runtime.GOOS == "darwin" { // OS X does not pass O_TRUNC here, Linux does; as this is a // Create, that's acceptable want.Flags &^= fuse.OpenTruncate // https://github.com/osxfuse/osxfuse/issues/225 want.Umask = 0 } if runtime.GOOS == "freebsd" { // FreeBSD doesn't pass truncate to FUSE?; as this is a // Create, that's acceptable want.Flags &^= fuse.OpenTruncate } got := f.f.RecordedCreate() if runtime.GOOS == "linux" { // Linux <3.7 accidentally leaks O_CLOEXEC through to FUSE; // avoid spurious test failures got.Flags &^= fuse.OpenFlags(syscall.O_CLOEXEC) } if g, e := got, want; g != e { t.Fatalf("create saw %+v, want %+v", g, e) } } // Test Create + Write + Remove type create3file struct { fstestutil.File record.Writes } type create3 struct { fstestutil.Dir f create3file fooCreated record.MarkRecorder fooRemoved record.MarkRecorder } func (f *create3) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) { if req.Name != "foo" { log.Printf("ERROR create3.Create unexpected name: %q\n", req.Name) return nil, nil, syscall.EPERM } f.fooCreated.Mark() return &f.f, &f.f, nil } func (f *create3) Lookup(ctx context.Context, name string) (fs.Node, error) { if f.fooCreated.Recorded() && !f.fooRemoved.Recorded() && name == "foo" { return &f.f, nil } return nil, syscall.ENOENT } func (f *create3) Remove(ctx context.Context, r *fuse.RemoveRequest) error { if f.fooCreated.Recorded() && !f.fooRemoved.Recorded() && r.Name == "foo" && !r.Dir { f.fooRemoved.Mark() return nil } return syscall.ENOENT } func doCreateWriteRemove(ctx context.Context, path string) (*struct{}, error) { if err := ioutil.WriteFile(path, []byte(hi), 0o666); err != nil { return nil, fmt.Errorf("WriteFile: %v", err) } if err := os.Remove(path); err != nil { return nil, fmt.Errorf("Remove: %v", err) } if err := os.Remove(path); !errors.Is(err, syscall.ENOENT) { return nil, fmt.Errorf("second Remove: wrong error: %v", err) } return &struct{}{}, nil } var createWriteRemoveHelper = helpers.Register("createWriteRemove", httpjson.ServePOST(doCreateWriteRemove)) func TestCreateWriteRemove(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &create3{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := createWriteRemoveHelper.Spawn(ctx, t) defer control.Close() var nothing struct{} if err := control.JSON("/").Call(ctx, mnt.Dir+"/foo", ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } } // Test symlink + readlink // is a Node that is a symlink to target type symlink1link struct { symlink fs *symlink1 } func (f symlink1link) Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error) { return f.fs.RecordedSymlink().Target, nil } type symlink1 struct { fstestutil.Dir record.Symlinks } var _ fs.NodeStringLookuper = (*symlink1)(nil) func (f *symlink1) Lookup(ctx context.Context, name string) (fs.Node, error) { if name != "symlink.file" { return nil, syscall.ENOENT } if f.RecordedSymlink() == (fuse.SymlinkRequest{}) { return nil, syscall.ENOENT } return symlink1link{fs: f}, nil } func (f *symlink1) Symlink(ctx context.Context, req *fuse.SymlinkRequest) (fs.Node, error) { if f.RecordedSymlink() != (fuse.SymlinkRequest{}) { log.Print("this test is not prepared to handle multiple symlinks") return nil, fuse.Errno(syscall.ENAMETOOLONG) } f.Symlinks.Symlink(ctx, req) return symlink1link{fs: f}, nil } type symlinkHelp struct{} func (i *symlinkHelp) ServeHTTP(w http.ResponseWriter, req *http.Request) { switch req.URL.Path { case "/symlink": httpjson.ServePOST(i.doSymlink).ServeHTTP(w, req) case "/readlink": httpjson.ServePOST(i.doReadlink).ServeHTTP(w, req) default: http.NotFound(w, req) } } type symlinkRequest struct { Target string Path string } func (i *symlinkHelp) doSymlink(ctx context.Context, req symlinkRequest) (*struct{}, error) { if err := os.Symlink(req.Target, req.Path); err != nil { return nil, err } return &struct{}{}, nil } func (i *symlinkHelp) doReadlink(ctx context.Context, path string) (string, error) { return os.Readlink(path) } var symlinkHelper = helpers.Register("symlink", &symlinkHelp{}) func TestSymlink(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &symlink1{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := symlinkHelper.Spawn(ctx, t) defer control.Close() const target = "/some-target" path := mnt.Dir + "/symlink.file" req := symlinkRequest{ Target: target, Path: path, } var nothing struct{} if err := control.JSON("/symlink").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } want := fuse.SymlinkRequest{NewName: "symlink.file", Target: target} if g, e := f.RecordedSymlink(), want; g != e { t.Errorf("symlink saw %+v, want %+v", g, e) } var gotName string if err := control.JSON("/readlink").Call(ctx, path, &gotName); err != nil { t.Fatalf("calling helper: %v", err) } if gotName != target { t.Errorf("os.Readlink = %q; want %q", gotName, target) } } // Test link type link1 struct { fstestutil.Dir record.Links } func (f *link1) Lookup(ctx context.Context, name string) (fs.Node, error) { if name == "old" { return fstestutil.File{}, nil } return nil, syscall.ENOENT } func (f *link1) Link(ctx context.Context, r *fuse.LinkRequest, old fs.Node) (fs.Node, error) { f.Links.Link(ctx, r, old) return fstestutil.File{}, nil } type linkRequest struct { OldName string NewName string } func doLink(ctx context.Context, req linkRequest) (*struct{}, error) { if err := os.Link(req.OldName, req.NewName); err != nil { return nil, err } return &struct{}{}, nil } var linkHelper = helpers.Register("link", httpjson.ServePOST(doLink)) func TestLink(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &link1{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := linkHelper.Spawn(ctx, t) defer control.Close() req := linkRequest{ OldName: mnt.Dir + "/old", NewName: mnt.Dir + "/new", } var nothing struct{} if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } got := f.RecordedLink() want := fuse.LinkRequest{ NewName: "new", // unpredictable OldNode: got.OldNode, } if g, e := got, want; g != e { t.Fatalf("link saw %+v, want %+v", g, e) } } // Test Rename type rename1 struct { fstestutil.Dir renamed record.Counter } func (f *rename1) Lookup(ctx context.Context, name string) (fs.Node, error) { if name == "old" { return fstestutil.File{}, nil } return nil, syscall.ENOENT } func (f *rename1) Rename(ctx context.Context, r *fuse.RenameRequest, newDir fs.Node) error { if r.OldName == "old" && r.NewName == "new" && newDir == f { f.renamed.Inc() return nil } return syscall.EIO } type renameRequest struct { OldName string NewName string WantErrno syscall.Errno } func doRename(ctx context.Context, req renameRequest) (*struct{}, error) { var want error if req.WantErrno > 0 { want = req.WantErrno } if err := os.Rename(req.OldName, req.NewName); !errors.Is(err, want) { return nil, fmt.Errorf("wrong error: %v", err) } return &struct{}{}, nil } var renameHelper = helpers.Register("rename", httpjson.ServePOST(doRename)) func TestRename(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &rename1{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := renameHelper.Spawn(ctx, t) defer control.Close() { req := renameRequest{ OldName: mnt.Dir + "/old", NewName: mnt.Dir + "/new", } var nothing struct{} if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } } if g, e := f.renamed.Count(), uint32(1); g != e { t.Fatalf("expected rename didn't happen: %d != %d", g, e) } { req := renameRequest{ OldName: mnt.Dir + "/old2", NewName: mnt.Dir + "/new2", WantErrno: syscall.ENOENT, } var nothing struct{} if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } } } // Test mknod type mknod1 struct { fstestutil.Dir record.Mknods } func (f *mknod1) Mknod(ctx context.Context, r *fuse.MknodRequest) (fs.Node, error) { f.Mknods.Mknod(ctx, r) return fifo{}, nil } func doMknod(ctx context.Context, path string) (*struct{}, error) { // uniform umask needed to make mknod's mode into something // reproducible syscall.Umask(0o022) if err := syscall.Mknod(path, syscall.S_IFIFO|0o660, 123); err != nil { return nil, err } return &struct{}{}, nil } var mknodHelper = helpers.Register("mknod", httpjson.ServePOST(doMknod)) func TestMknod(t *testing.T) { if os.Getuid() != 0 { t.Skip("skipping unless root") } maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &mknod1{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := mknodHelper.Spawn(ctx, t) defer control.Close() var nothing struct{} if err := control.JSON("/").Call(ctx, mnt.Dir+"/node", ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } want := fuse.MknodRequest{ Name: "node", Mode: os.FileMode(os.ModeNamedPipe | 0o640), Rdev: uint32(123), Umask: 0o022, } if runtime.GOOS == "linux" { // Linux fuse doesn't echo back the rdev if the node // isn't a device (we're using a FIFO here, as that // bit is portable.) want.Rdev = 0 } if mnt.Conn.Protocol().HasUmask() { want.Umask = 0022 } if runtime.GOOS == "darwin" { // https://github.com/osxfuse/osxfuse/issues/225 want.Umask = 0 } if g, e := f.RecordedMknod(), want; g != e { t.Fatalf("mknod saw %+v, want %+v", g, e) } } // Test Read served with DataHandle. type dataHandleTest struct { fstestutil.File } func (dataHandleTest) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0o666 a.Size = uint64(len(hi)) return nil } func (dataHandleTest) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { return fs.DataHandle([]byte(hi)), nil } func TestDataHandle(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &dataHandleTest{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := readHelper.Spawn(ctx, t) defer control.Close() var got readResult if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil { t.Fatalf("calling helper: %v", err) } if g, e := string(got.Data), hi; g != e { t.Errorf("readAll = %q, want %q", g, e) } } // Test interrupt type interrupt struct { fstestutil.File // strobes to signal we have a read hanging hanging chan struct{} // strobes to signal kernel asked us to interrupt read interrupted chan struct{} } func (interrupt) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0o666 a.Size = 1 return nil } func (it *interrupt) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { select { case it.hanging <- struct{}{}: default: } log.Printf("reading...") <-ctx.Done() log.Printf("read done") select { case it.interrupted <- struct{}{}: default: } return ctx.Err() } type interruptHelp struct { mu sync.Mutex result *interruptResult } type interruptResult struct { OK bool Read []byte Error string } func (i *interruptHelp) ServeHTTP(w http.ResponseWriter, req *http.Request) { switch req.URL.Path { case "/read": httpjson.ServePOST(i.doRead).ServeHTTP(w, req) case "/report": httpjson.ServePOST(i.doReport).ServeHTTP(w, req) default: http.NotFound(w, req) } } func (i *interruptHelp) doRead(ctx context.Context, dir string) (struct{}, error) { log.SetPrefix("interrupt child: ") log.SetFlags(0) log.Printf("starting...") f, err := os.Open(filepath.Join(dir, "child")) if err != nil { log.Fatalf("cannot open file: %v", err) } i.mu.Lock() // background this so we can return a response to the test go func() { defer i.mu.Unlock() defer f.Close() log.Printf("reading...") buf := make([]byte, 4096) n, err := syscall.Read(int(f.Fd()), buf) var r interruptResult switch err { case nil: buf = buf[:n] log.Printf("read: expected error, got data: %q", buf) r.Read = buf case syscall.EINTR: log.Printf("read: saw EINTR, all good") r.OK = true default: msg := err.Error() log.Printf("read: wrong error: %s", msg) r.Error = msg } i.result = &r log.Printf("read done...") }() return struct{}{}, nil } func (i *interruptHelp) doReport(ctx context.Context, _ struct{}) (*interruptResult, error) { i.mu.Lock() defer i.mu.Unlock() if i.result == nil { return nil, errors.New("no result yet") } return i.result, nil } var interruptHelper = helpers.Register("interrupt", &interruptHelp{}) func TestInterrupt(t *testing.T) { if runtime.GOOS == "freebsd" || runtime.GOOS == "darwin" { t.Skip("don't know how to trigger EINTR from read syscall on FreeBSD or macOS") } maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &interrupt{ hanging: make(chan struct{}, 1), interrupted: make(chan struct{}, 1), } mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() // start a subprocess that can hang until signaled control := interruptHelper.Spawn(ctx, t) defer control.Close() var nothing struct{} if err := control.JSON("/read").Call(ctx, mnt.Dir, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } // wait till we're sure it's hanging in read <-f.hanging if err := control.Signal(syscall.SIGSTOP); err != nil { t.Errorf("cannot send SIGSTOP: %v", err) return } // give the process enough time to receive SIGSTOP, otherwise it // won't interrupt the syscall. <-f.interrupted if err := control.Signal(syscall.SIGCONT); err != nil { t.Errorf("cannot send SIGCONT: %v", err) return } var result interruptResult if err := control.JSON("/report").Call(ctx, struct{}{}, &result); err != nil { t.Fatalf("calling helper: %v", err) } if !result.OK { if msg := result.Error; msg != "" { t.Errorf("unexpected error from read: %v", msg) } if data := result.Read; len(data) > 0 { t.Errorf("unexpected successful read: %q", data) } } } // Test deadline type deadline struct { fstestutil.File } var _ fs.NodeOpener = (*deadline)(nil) func (it *deadline) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { <-ctx.Done() return nil, ctx.Err() } type openRequest struct { Path string Flags int Perm os.FileMode WantErrno syscall.Errno } func doOpenErr(ctx context.Context, req openRequest) (*struct{}, error) { f, err := os.OpenFile(req.Path, req.Flags, req.Perm) if err == nil { f.Close() } if !errors.Is(err, req.WantErrno) { return nil, fmt.Errorf("wrong error: %v", err) } return &struct{}{}, nil } var openErrHelper = helpers.Register("openErr", httpjson.ServePOST(doOpenErr)) func TestDeadline(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() child := &deadline{} config := &fs.Config{ WithContext: func(ctx context.Context, req fuse.Request) context.Context { // return a context that has already deadlined // Server.serve will cancel the parent context, which will // cancel this one, so discarding cancel here should be // safe. ctx, _ = context.WithDeadline(ctx, time.Unix(0, 0)) return ctx }, } mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": child}}, config) if err != nil { t.Fatal(err) } defer mnt.Close() control := openErrHelper.Spawn(ctx, t) defer control.Close() req := openRequest{ Path: mnt.Dir + "/child", Flags: os.O_RDONLY, Perm: 0, // not caused by signal -> should not get EINTR; // context.DeadlineExceeded will be translated into EIO WantErrno: syscall.EIO, } var nothing struct{} if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } } // Test truncate type truncate struct { fstestutil.File record.Setattrs } type truncateRequest struct { Path string ToSize int64 } func doTruncate(ctx context.Context, req truncateRequest) (*struct{}, error) { if err := os.Truncate(req.Path, req.ToSize); err != nil { return nil, err } return &struct{}{}, nil } var truncateHelper = helpers.Register("truncate", httpjson.ServePOST(doTruncate)) func testTruncate(t *testing.T, toSize int64) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &truncate{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := truncateHelper.Spawn(ctx, t) defer control.Close() req := truncateRequest{ Path: mnt.Dir + "/child", ToSize: toSize, } var nothing struct{} if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } gotr := f.RecordedSetattr() if gotr == (fuse.SetattrRequest{}) { t.Fatalf("no recorded SetattrRequest") } if g, e := gotr.Size, uint64(toSize); g != e { t.Errorf("got Size = %q; want %q", g, e) } if g, e := gotr.Valid&^fuse.SetattrLockOwner, fuse.SetattrSize; g != e { t.Errorf("got Valid = %q; want %q", g, e) } t.Logf("Got request: %#v", gotr) } func TestTruncate(t *testing.T) { t.Run("42", func(t *testing.T) { testTruncate(t, 42) }) t.Run("0", func(t *testing.T) { testTruncate(t, 0) }) } // Test ftruncate type ftruncate struct { fstestutil.File record.Setattrs } func doFtruncate(ctx context.Context, req truncateRequest) (*struct{}, error) { f, err := os.OpenFile(req.Path, os.O_WRONLY, 0o666) if err != nil { return nil, err } defer f.Close() if err := f.Truncate(req.ToSize); err != nil { return nil, err } return &struct{}{}, nil } var ftruncateHelper = helpers.Register("ftruncate", httpjson.ServePOST(doFtruncate)) func testFtruncate(t *testing.T, toSize int64) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &ftruncate{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := ftruncateHelper.Spawn(ctx, t) defer control.Close() req := truncateRequest{ Path: mnt.Dir + "/child", ToSize: toSize, } var nothing struct{} if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } gotr := f.RecordedSetattr() if gotr == (fuse.SetattrRequest{}) { t.Fatalf("no recorded SetattrRequest") } if g, e := gotr.Size, uint64(toSize); g != e { t.Errorf("got Size = %q; want %q", g, e) } if g, e := gotr.Valid&^fuse.SetattrLockOwner, fuse.SetattrHandle|fuse.SetattrSize; g != e { t.Errorf("got Valid = %q; want %q", g, e) } t.Logf("Got request: %#v", gotr) } func TestFtruncate(t *testing.T) { t.Run("42", func(t *testing.T) { testFtruncate(t, 42) }) t.Run("0", func(t *testing.T) { testFtruncate(t, 0) }) } // Test opening existing file truncates type truncateWithOpen struct { fstestutil.File record.Setattrs } func doTruncateWithOpen(ctx context.Context, path string) (*struct{}, error) { fil, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0o666) if err != nil { return nil, err } _ = fil.Close() return &struct{}{}, nil } var truncateWithOpenHelper = helpers.Register("truncateWithOpen", httpjson.ServePOST(doTruncateWithOpen)) func TestTruncateWithOpen(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &truncateWithOpen{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := truncateWithOpenHelper.Spawn(ctx, t) defer control.Close() var nothing struct{} if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } gotr := f.RecordedSetattr() if gotr == (fuse.SetattrRequest{}) { t.Fatalf("no recorded SetattrRequest") } if g, e := gotr.Size, uint64(0); g != e { t.Errorf("got Size = %q; want %q", g, e) } // osxfuse sets SetattrHandle here, linux does not if g, e := gotr.Valid&^(fuse.SetattrLockOwner|fuse.SetattrHandle), fuse.SetattrSize; g != e { t.Errorf("got Valid = %q; want %q", g, e) } got := gotr.Valid if runtime.GOOS == "freebsd" { // FreeBSD seems to set this but Linux doesn't??? Want to // detect if Linux starts adding it. I assume the logic is // something like the truncate happens before the open; or it // just slipped by. got &^= fuse.SetattrHandle } if g, e := got&^fuse.SetattrLockOwner, fuse.SetattrSize; g != e { t.Errorf("got Valid = %q; want %q", g, e) } t.Logf("Got request: %#v", gotr) } // Test readdir calling ReadDirAll type readDirAll struct { fstestutil.Dir } func (d *readDirAll) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { return []fuse.Dirent{ {Name: "one", Inode: 11, Type: fuse.DT_Dir}, {Name: "three", Inode: 13}, {Name: "two", Inode: 12, Type: fuse.DT_File}, }, nil } func doReaddir(ctx context.Context, path string) ([]string, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() // go Readdir is just Readdirnames + Lstat, there's no point in // testing that here; we have no consumption API for the real // dirent data names, err := f.Readdirnames(-1) if err != nil { return nil, err } return names, nil } var readdirHelper = helpers.Register("readdir", httpjson.ServePOST(doReaddir)) func TestReadDirAll(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &readDirAll{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := readdirHelper.Spawn(ctx, t) defer control.Close() var names []string if err := control.JSON("/").Call(ctx, mnt.Dir, &names); err != nil { t.Fatalf("calling helper: %v", err) } t.Logf("Got readdir: %q", names) if len(names) != 3 || names[0] != "one" || names[1] != "three" || names[2] != "two" { t.Errorf(`expected 3 entries of "one", "three", "two", got: %q`, names) return } } type readDirAllBad struct { fstestutil.Dir } func (d *readDirAllBad) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { r := []fuse.Dirent{ {Name: "one", Inode: 11, Type: fuse.DT_Dir}, {Name: "three", Inode: 13}, {Name: "two", Inode: 12, Type: fuse.DT_File}, } // pick a really distinct error, to identify it later return r, fuse.Errno(syscall.ENAMETOOLONG) } func doReaddirBad(ctx context.Context, path string) (*struct{}, error) { fil, err := os.Open(path) if err != nil { return nil, err } defer fil.Close() var names []string for { n, err := fil.Readdirnames(1) if err != nil { if !errors.Is(err, syscall.ENAMETOOLONG) { return nil, fmt.Errorf("wrong error: %v", err) } break } names = append(names, n...) } log.Printf("Got readdir: %q", names) // TODO could serve partial results from ReadDirAll but the // shandle.readData mechanism makes that awkward. if len(names) != 0 { return nil, fmt.Errorf("expected 0 entries, got: %q", names) } return &struct{}{}, nil } var readdirBadHelper = helpers.Register("readdirBad", httpjson.ServePOST(doReaddirBad)) func TestReadDirAllBad(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &readDirAllBad{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := readdirBadHelper.Spawn(ctx, t) defer control.Close() var nothing struct{} if err := control.JSON("/").Call(ctx, mnt.Dir, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } } // Test readdir without any ReadDir methods implemented. type readDirNotImplemented struct { fstestutil.Dir } func TestReadDirNotImplemented(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &readDirNotImplemented{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := readdirHelper.Spawn(ctx, t) defer control.Close() var names []string if err := control.JSON("/").Call(ctx, mnt.Dir, &names); err != nil { t.Fatalf("calling helper: %v", err) } t.Logf("Got readdir: %q", names) if len(names) != 0 { t.Errorf(`expected 0 entries, got: %q`, names) } } type readDirAllRewind struct { fstestutil.Dir entries atomic.Value } func (d *readDirAllRewind) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { entries := d.entries.Load().([]fuse.Dirent) return entries, nil } type readdirRewindHelp struct { mu sync.Mutex file *os.File } func (r *readdirRewindHelp) ServeHTTP(w http.ResponseWriter, req *http.Request) { switch req.URL.Path { case "/openReaddir": httpjson.ServePOST(r.doOpenReaddir).ServeHTTP(w, req) case "/rewindReaddirClose": httpjson.ServePOST(r.doRewindReaddirClose).ServeHTTP(w, req) default: http.NotFound(w, req) } } func (r *readdirRewindHelp) doOpenReaddir(ctx context.Context, path string) ([]string, error) { r.mu.Lock() defer r.mu.Unlock() f, err := os.Open(path) if err != nil { return nil, err } r.file = f names, err := f.Readdirnames(100) if err != nil { return nil, err } return names, nil } func (r *readdirRewindHelp) doRewindReaddirClose(ctx context.Context, _ struct{}) ([]string, error) { r.mu.Lock() defer r.mu.Unlock() defer r.file.Close() if _, err := r.file.Seek(0, io.SeekStart); err != nil { return nil, err } names, err := r.file.Readdirnames(100) if err != nil { return nil, err } return names, nil } var readdirRewindHelper = helpers.Register("readdirRewind", &readdirRewindHelp{}) func TestReadDirAllRewind(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &readDirAllRewind{} f.entries.Store([]fuse.Dirent{ {Name: "one", Inode: 11, Type: fuse.DT_Dir}, }) mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := readdirRewindHelper.Spawn(ctx, t) defer control.Close() { var names []string if err := control.JSON("/openReaddir").Call(ctx, mnt.Dir, &names); err != nil { t.Fatalf("calling helper: %v", err) } t.Logf("Got readdir: %q", names) if len(names) != 1 || names[0] != "one" { t.Errorf(`expected entry of "one", got: %q`, names) return } } f.entries.Store([]fuse.Dirent{ {Name: "two", Inode: 12, Type: fuse.DT_File}, {Name: "one", Inode: 11, Type: fuse.DT_Dir}, }) { var names []string if err := control.JSON("/rewindReaddirClose").Call(ctx, struct{}{}, &names); err != nil { t.Fatalf("calling helper: %v", err) } t.Logf("Got readdir: %q", names) if len(names) != 2 || names[0] != "two" || names[1] != "one" { t.Errorf(`expected 2 entries of "two", "one", got: %q`, names) return } } } // Test Chmod. type chmod struct { fstestutil.File record.Setattrs } func (f *chmod) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error { if !req.Valid.Mode() { log.Printf("setattr not a chmod: %v", req.Valid) return syscall.EIO } f.Setattrs.Setattr(ctx, req, resp) return nil } type chmodRequest struct { Path string Mode os.FileMode } func doChmod(ctx context.Context, req chmodRequest) (*struct{}, error) { if err := os.Chmod(req.Path, req.Mode); err != nil { return nil, err } return &struct{}{}, nil } var chmodHelper = helpers.Register("chmod", httpjson.ServePOST(doChmod)) func TestChmod(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &chmod{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := chmodHelper.Spawn(ctx, t) defer control.Close() req := chmodRequest{ Path: mnt.Dir + "/child", Mode: 0o764, } var nothing struct{} if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } got := f.RecordedSetattr() if g, e := got.Mode.Perm(), os.FileMode(0o764); g != e { t.Errorf("wrong mode: %o %v != %o %v", g, g, e, e) } ftype := got.Mode & os.ModeType switch { case runtime.GOOS == "freebsd" && ftype == os.ModeIrregular: // acceptable but unfortunate default: if !ftype.IsRegular() { t.Errorf("mode is not regular: %o %v", got.Mode, got.Mode) } } } // Test open type open struct { fstestutil.File record.Opens } func (f *open) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { f.Opens.Open(ctx, req, resp) // pick a really distinct error, to identify it later return nil, fuse.Errno(syscall.ENAMETOOLONG) } func TestOpen(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &open{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := openErrHelper.Spawn(ctx, t) defer control.Close() req := openRequest{ Path: mnt.Dir + "/child", Flags: os.O_WRONLY | os.O_APPEND, // note: mode only matters with O_CREATE Perm: 0, WantErrno: syscall.ENAMETOOLONG, } var nothing struct{} if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } want := fuse.OpenRequest{Dir: false, Flags: fuse.OpenWriteOnly | fuse.OpenAppend} if runtime.GOOS == "darwin" { // osxfuse does not let O_APPEND through at all // // https://code.google.com/p/macfuse/issues/detail?id=233 // https://code.google.com/p/macfuse/issues/detail?id=132 // https://code.google.com/p/macfuse/issues/detail?id=133 want.Flags &^= fuse.OpenAppend } got := f.RecordedOpen() if runtime.GOOS == "linux" { // Linux <3.7 accidentally leaks O_CLOEXEC through to FUSE; // avoid spurious test failures got.Flags &^= fuse.OpenFlags(syscall.O_CLOEXEC) } if runtime.GOOS == "freebsd" { // FreeBSD doesn't pass append to FUSE? want.Flags &^= fuse.OpenAppend } if g, e := got, want; g != e { t.Errorf("open saw %+v, want %+v", g, e) return } } type openNonSeekable struct { fstestutil.File } func (f *openNonSeekable) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { resp.Flags |= fuse.OpenNonSeekable return f, nil } func doOpenNonseekable(ctx context.Context, path string) (*struct{}, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() if _, err := f.Seek(0, io.SeekStart); !errors.Is(err, syscall.ESPIPE) { return nil, fmt.Errorf("wrong error: %v", err) } return &struct{}{}, nil } var openNonseekableHelper = helpers.Register("openNonseekable", httpjson.ServePOST(doOpenNonseekable)) func TestOpenNonSeekable(t *testing.T) { if runtime.GOOS == "darwin" { t.Skip("OSXFUSE shares one read and one write handle for all clients, does not support open modes") } if runtime.GOOS == "freebsd" { // behavior observed: seek calls succeed, but file offset does // not change t.Skip("FreeBSD seems to ignore OpenNonSeekable") } maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &openNonSeekable{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := openNonseekableHelper.Spawn(ctx, t) defer control.Close() var nothing struct{} if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } } // Test Fsync on a dir type fsyncDir struct { fstestutil.Dir record.Fsyncs } func doOpenFsyncClose(ctx context.Context, path string) (*struct{}, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() err = f.Sync() if err != nil { return nil, err } return &struct{}{}, nil } var openFsyncCloseHelper = helpers.Register("openFsyncClose", httpjson.ServePOST(doOpenFsyncClose)) func TestFsyncDir(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &fsyncDir{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := openFsyncCloseHelper.Spawn(ctx, t) defer control.Close() var nothing struct{} if err := control.JSON("/").Call(ctx, mnt.Dir, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } got := f.RecordedFsync() want := fuse.FsyncRequest{ Flags: 0, Dir: true, // unpredictable Handle: got.Handle, } if runtime.GOOS == "darwin" { // TODO document the meaning of these flags, figure out why // they differ want.Flags = 1 } if g, e := got, want; g != e { t.Fatalf("fsyncDir saw %+v, want %+v", g, e) } } // Test Getxattr type getxattr struct { fstestutil.File record.Getxattrs } func (f *getxattr) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error { f.Getxattrs.Getxattr(ctx, req, resp) resp.Xattr = []byte("hello, world") return nil } type getxattrRequest struct { Path string Name string Size int WantErrno syscall.Errno } type getxattrResult struct { // only one of Data and Size is set Data []byte Size int } func doGetxattr(ctx context.Context, req getxattrRequest) (*getxattrResult, error) { buf := make([]byte, req.Size) n, err := syscallx.Getxattr(req.Path, req.Name, buf) if req.WantErrno != 0 { if !errors.Is(err, req.WantErrno) { return nil, fmt.Errorf("wrong error: %v", err) } return nil, nil } if err != nil { return nil, fmt.Errorf("unexpected error: %v", err) } if req.Size == 0 { r := &getxattrResult{ Size: n, } return r, nil } r := &getxattrResult{ Data: buf[:n], } return r, nil } var getxattrHelper = helpers.Register("getxattr", httpjson.ServePOST(doGetxattr)) func TestGetxattr(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &getxattr{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := getxattrHelper.Spawn(ctx, t) defer control.Close() req := getxattrRequest{ Path: mnt.Dir + "/child", Name: "user.dummyxattr", Size: 8192, } var res getxattrResult if err := control.JSON("/").Call(ctx, req, &res); err != nil { t.Fatalf("calling helper: %v", err) } if g, e := string(res.Data), "hello, world"; g != e { t.Errorf("wrong getxattr content: %#v != %#v", g, e) } seen := f.RecordedGetxattr() if g, e := seen.Name, "user.dummyxattr"; g != e { t.Errorf("wrong getxattr name: %#v != %#v", g, e) } } // Test Getxattr that has no space to return value type getxattrTooSmall struct { fstestutil.File } func (f *getxattrTooSmall) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error { resp.Xattr = []byte("hello, world") return nil } func TestGetxattrTooSmall(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &getxattrTooSmall{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := getxattrHelper.Spawn(ctx, t) defer control.Close() req := getxattrRequest{ Path: mnt.Dir + "/child", Name: "user.dummyxattr", Size: 3, WantErrno: syscall.ERANGE, } var res getxattrResult if err := control.JSON("/").Call(ctx, req, &res); err != nil { t.Fatalf("calling helper: %v", err) } } // Test Getxattr used to probe result size type getxattrSize struct { fstestutil.File } func (f *getxattrSize) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error { resp.Xattr = []byte("hello, world") return nil } func TestGetxattrSize(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &getxattrSize{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := getxattrHelper.Spawn(ctx, t) defer control.Close() req := getxattrRequest{ Path: mnt.Dir + "/child", Name: "user.dummyxattr", Size: 0, } var res getxattrResult if err := control.JSON("/").Call(ctx, req, &res); err != nil { t.Fatalf("calling helper: %v", err) } if g, e := res.Size, len("hello, world"); g != e { t.Errorf("Getxattr incorrect size: %d != %d", g, e) } } // Test Listxattr type listxattr struct { fstestutil.File record.Listxattrs } func (f *listxattr) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error { f.Listxattrs.Listxattr(ctx, req, resp) resp.Append("user.one", "user.two") return nil } type listxattrRequest struct { Path string Size int WantErrno syscall.Errno } type listxattrResult struct { // only one of Data and Size is set Data []byte Size int } func doListxattr(ctx context.Context, req listxattrRequest) (*listxattrResult, error) { buf := make([]byte, req.Size) n, err := syscallx.Listxattr(req.Path, buf) if req.WantErrno != 0 { if !errors.Is(err, req.WantErrno) { return nil, fmt.Errorf("wrong error: %v", err) } return nil, nil } if err != nil { return nil, fmt.Errorf("unexpected error: %v", err) } if req.Size == 0 { r := &listxattrResult{ Size: n, } return r, nil } buf = buf[:n] if runtime.GOOS == "freebsd" { // Normalize FreeBSD listxattr syscall response to the same // zero-terminated format as others. This is just the // client-side syscall; the FUSE interaction still uses the // nil-terminated strings with namespace prefixes. // // Length-prefixed, no namespace (you have to query per // namespace on FreeBSD). var out []byte for len(buf) > 0 { size := int(buf[0]) out = append(out, []byte("user.")...) out = append(out, buf[1:1+size]...) out = append(out, '\x00') buf = buf[1+size:] } buf = out } r := &listxattrResult{ Data: buf, } return r, nil } var listxattrHelper = helpers.Register("listxattr", httpjson.ServePOST(doListxattr)) func TestListxattr(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &listxattr{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := listxattrHelper.Spawn(ctx, t) defer control.Close() req := listxattrRequest{ Path: mnt.Dir + "/child", Size: 8192, } var res listxattrResult if err := control.JSON("/").Call(ctx, req, &res); err != nil { t.Fatalf("calling helper: %v", err) } if g, e := string(res.Data), "user.one\x00user.two\x00"; g != e { t.Errorf("wrong listxattr content: %#v != %#v", g, e) } want := fuse.ListxattrRequest{ Size: 8192, } if runtime.GOOS == "freebsd" { // FreeBSD seems to always probe the size for you, even when // userspace passed a large enough buffer. This means two (or // more, if the size keeps growing!) Listxattr FUSE requests, // with the last one likely having the perfect size (except // when the size changed downward between the calls). Blargh. want.Size = uint32(len("user.one\x00user.two\x00")) } if g, e := f.RecordedListxattr(), want; g != e { t.Fatalf("listxattr saw %+v, want %+v", g, e) } } // Test Listxattr that has no space to return value type listxattrTooSmall struct { fstestutil.File } func (f *listxattrTooSmall) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error { resp.Xattr = []byte("one\x00two\x00") return nil } func TestListxattrTooSmall(t *testing.T) { if runtime.GOOS == "freebsd" { t.Skip("FreeBSD xattr list format is different and the kernel has intermediate buffer; can't drive FUSE requests directly from userspace") } maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &listxattrTooSmall{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := listxattrHelper.Spawn(ctx, t) defer control.Close() req := listxattrRequest{ Path: mnt.Dir + "/child", Size: 3, WantErrno: syscall.ERANGE, } var res listxattrResult if err := control.JSON("/").Call(ctx, req, &res); err != nil { t.Fatalf("calling helper: %v", err) } } // Test Listxattr used to probe result size type listxattrSize struct { fstestutil.File } func (f *listxattrSize) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error { resp.Xattr = []byte("one\x00two\x00") return nil } func TestListxattrSize(t *testing.T) { if runtime.GOOS == "freebsd" { t.Skip("FreeBSD xattr list format is different and the kernel has intermediate buffer; can't drive FUSE requests directly from userspace") } maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &listxattrSize{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := listxattrHelper.Spawn(ctx, t) defer control.Close() req := listxattrRequest{ Path: mnt.Dir + "/child", Size: 0, } var res listxattrResult if err := control.JSON("/").Call(ctx, req, &res); err != nil { t.Fatalf("calling helper: %v", err) } if g, e := res.Size, len("one\x00two\x00"); g != e { t.Errorf("Listxattr incorrect size: %d != %d", g, e) } } // Test Setxattr type setxattr struct { fstestutil.File record.Setxattrs } type setxattrRequest struct { Path string Name string Data []byte Flags int } func doSetxattr(ctx context.Context, req setxattrRequest) (*struct{}, error) { if err := syscallx.Setxattr(req.Path, req.Name, req.Data, req.Flags); err != nil { return nil, err } return &struct{}{}, nil } var setxattrHelper = helpers.Register("setxattr", httpjson.ServePOST(doSetxattr)) func testSetxattr(t *testing.T, size int) { const linux_XATTR_NAME_MAX = 64 * 1024 if size > linux_XATTR_NAME_MAX && runtime.GOOS == "linux" { t.Skip("large xattrs are not supported by linux") } if runtime.GOOS == "freebsd" && size > 135106 { // no idea what that magic number is but it seems like a very // repeatable exact cutoff for me t.Skip("FreeBSD setxattr seems to hang on large values") } maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &setxattr{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := setxattrHelper.Spawn(ctx, t) defer control.Close() const g = "hello, world" greeting := strings.Repeat(g, size/len(g)+1)[:size] req := setxattrRequest{ Path: mnt.Dir + "/child", Name: "user.greeting", Data: []byte(greeting), Flags: 0, } var nothing struct{} if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } // fuse.SetxattrRequest contains a byte slice and thus cannot be // directly compared got := f.RecordedSetxattr() if g, e := got.Name, "user.greeting"; g != e { t.Errorf("Setxattr incorrect name: %q != %q", g, e) } if g, e := got.Flags, uint32(0); g != e { t.Errorf("Setxattr incorrect flags: %d != %d", g, e) } if g, e := string(got.Xattr), greeting; g != e { t.Errorf("Setxattr incorrect data: %q != %q", g, e) } } func TestSetxattr(t *testing.T) { t.Run("20", func(t *testing.T) { testSetxattr(t, 20) }) t.Run("64kB", func(t *testing.T) { testSetxattr(t, 64*1024) }) t.Run("16MB", func(t *testing.T) { testSetxattr(t, 16*1024*1024) }) } // Test Removexattr type removexattr struct { fstestutil.File record.Removexattrs } type removexattrRequest struct { Path string Name string } func doRemovexattr(ctx context.Context, req removexattrRequest) (*struct{}, error) { if err := syscallx.Removexattr(req.Path, req.Name); err != nil { return nil, err } return &struct{}{}, nil } var removexattrHelper = helpers.Register("removexattr", httpjson.ServePOST(doRemovexattr)) func TestRemovexattr(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() f := &removexattr{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := removexattrHelper.Spawn(ctx, t) defer control.Close() req := removexattrRequest{ Path: mnt.Dir + "/child", Name: "user.greeting", } var nothing struct{} if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } want := fuse.RemovexattrRequest{Name: "user.greeting"} if g, e := f.RecordedRemovexattr(), want; g != e { t.Errorf("removexattr saw %v, want %v", g, e) } } // Test default error. type defaultErrno struct { fstestutil.Dir } func (f defaultErrno) Lookup(ctx context.Context, name string) (fs.Node, error) { return nil, errors.New("bork") } type statErrRequest struct { Path string WantErrno syscall.Errno } func doStatErr(ctx context.Context, req statErrRequest) (*struct{}, error) { if _, err := os.Stat(req.Path); !errors.Is(err, req.WantErrno) { return nil, fmt.Errorf("wrong error: %v", err) } return &struct{}{}, nil } var statErrHelper = helpers.Register("statErr", httpjson.ServePOST(doStatErr)) func TestDefaultErrno(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{defaultErrno{}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := statErrHelper.Spawn(ctx, t) defer control.Close() req := statErrRequest{ Path: mnt.Dir + "/child", WantErrno: syscall.EIO, } var nothing struct{} if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } } // Test custom error. type customErrNode struct { fstestutil.Dir } type myCustomError struct { fuse.ErrorNumber } var _ fuse.ErrorNumber = myCustomError{} func (myCustomError) Error() string { return "bork" } func (f customErrNode) Lookup(ctx context.Context, name string) (fs.Node, error) { return nil, myCustomError{ ErrorNumber: fuse.Errno(syscall.ENAMETOOLONG), } } func TestCustomErrno(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{customErrNode{}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := statErrHelper.Spawn(ctx, t) defer control.Close() req := statErrRequest{ Path: mnt.Dir + "/child", WantErrno: syscall.ENAMETOOLONG, } var nothing struct{} if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } } // Test returning syscall.Errno type syscallErrNode struct { fstestutil.Dir } func (f syscallErrNode) Lookup(ctx context.Context, name string) (fs.Node, error) { return nil, syscall.ENAMETOOLONG } func TestSyscallErrno(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{syscallErrNode{}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := statErrHelper.Spawn(ctx, t) defer control.Close() req := statErrRequest{ Path: mnt.Dir + "/child", WantErrno: syscall.ENAMETOOLONG, } var nothing struct{} if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } } // Test Mmap writing type inMemoryFile struct { mu sync.Mutex data []byte } func (f *inMemoryFile) bytes() []byte { f.mu.Lock() defer f.mu.Unlock() return f.data } func (f *inMemoryFile) Attr(ctx context.Context, a *fuse.Attr) error { f.mu.Lock() defer f.mu.Unlock() a.Mode = 0o666 a.Size = uint64(len(f.data)) return nil } func (f *inMemoryFile) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { f.mu.Lock() defer f.mu.Unlock() fuseutil.HandleRead(req, resp, f.data) return nil } func (f *inMemoryFile) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error { f.mu.Lock() defer f.mu.Unlock() resp.Size = copy(f.data[req.Offset:], req.Data) return nil } const mmapSize = 16 * 4096 var mmapWrites = map[int]byte{ 10: 'a', 4096: 'b', 4097: 'c', mmapSize - 4096: 'd', mmapSize - 1: 'z', } func doMmap(ctx context.Context, dir string) (*struct{}, error) { f, err := os.Create(filepath.Join(dir, "child")) if err != nil { return nil, fmt.Errorf("Create: %v", err) } defer f.Close() data, err := syscall.Mmap(int(f.Fd()), 0, mmapSize, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) if err != nil { return nil, fmt.Errorf("Mmap: %v", err) } for i, b := range mmapWrites { data[i] = b } if err := syscallx.Msync(data, syscall.MS_SYNC); err != nil { return nil, fmt.Errorf("Msync: %v", err) } if err := syscall.Munmap(data); err != nil { return nil, fmt.Errorf("Munmap: %v", err) } if err := f.Sync(); err != nil { return nil, fmt.Errorf("Fsync = %v", err) } if err := f.Close(); err != nil { return nil, fmt.Errorf("Close: %v", err) } return &struct{}{}, nil } var mmapHelper = helpers.Register("mmap", httpjson.ServePOST(doMmap)) type mmap struct { inMemoryFile // We don't actually care about whether the fsync happened or not; // this just lets us force the page cache to send the writes to // FUSE, so we can reliably verify they came through. record.Fsyncs } func TestMmap(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() w := &mmap{} w.data = make([]byte, mmapSize) mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": w}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() // Run the mmap-using parts of the test in a subprocess, to avoid // an intentional page fault hanging the whole process (because it // would need to be served by the same process, and there might // not be a thread free to do that). Merely bumping GOMAXPROCS is // not enough to prevent the hangs reliably. control := mmapHelper.Spawn(ctx, t) defer control.Close() var nothing struct{} if err := control.JSON("/").Call(ctx, mnt.Dir, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } got := w.bytes() if g, e := len(got), mmapSize; g != e { t.Fatalf("bad write length: %d != %d", g, e) } for i, g := range got { // default '\x00' for writes[i] is good here if e := mmapWrites[i]; g != e { t.Errorf("wrong byte at offset %d: %q != %q", i, g, e) } } } // Test direct Read. type directRead struct { fstestutil.File } // explicitly not defining Attr and setting Size func (f directRead) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { // do not allow the kernel to use page cache resp.Flags |= fuse.OpenDirectIO return f, nil } func (directRead) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { fuseutil.HandleRead(req, resp, []byte(hi)) return nil } func TestDirectRead(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": directRead{}}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := readHelper.Spawn(ctx, t) defer control.Close() var got readResult if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil { t.Fatalf("calling helper: %v", err) } if g, e := string(got.Data), hi; g != e { t.Errorf("readAll = %q, want %q", g, e) } } // Test direct Write. type directWrite struct { fstestutil.File record.Writes } // explicitly not defining Attr / Setattr and managing Size func (f *directWrite) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { // do not allow the kernel to use page cache resp.Flags |= fuse.OpenDirectIO return f, nil } func TestDirectWrite(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() w := &directWrite{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": w}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := writeFileHelper.Spawn(ctx, t) defer control.Close() var nothing struct{} req := writeFileRequest{ Path: mnt.Dir + "/child", Data: []byte(hi), } if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } if got := string(w.RecordedWriteData()); got != hi { t.Errorf("write = %q, want %q", got, hi) } } // Test Attr // attrUnlinked is a file that is unlinked (Nlink==0). type attrUnlinked struct { fstestutil.File } var _ fs.Node = attrUnlinked{} func (f attrUnlinked) Attr(ctx context.Context, a *fuse.Attr) error { if err := f.File.Attr(ctx, a); err != nil { return err } a.Nlink = 0 return nil } func TestAttrUnlinked(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": attrUnlinked{}}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := statHelper.Spawn(ctx, t) defer control.Close() var got statResult if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil { t.Fatalf("calling helper: %v", err) } if g, e := got.Nlink, uint64(0); g != e { t.Errorf("wrong link count: %v != %v", g, e) } } // Test behavior when Attr method fails type attrBad struct { } var _ fs.Node = attrBad{} func (attrBad) Attr(ctx context.Context, attr *fuse.Attr) error { return fuse.Errno(syscall.ENAMETOOLONG) } func TestAttrBad(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": attrBad{}}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := statErrHelper.Spawn(ctx, t) defer control.Close() req := statErrRequest{ Path: mnt.Dir + "/child", WantErrno: syscall.ENAMETOOLONG, } var nothing struct{} if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } } // Test kernel cache invalidation type invalidateAttr struct { t testing.TB attr record.Counter } var _ fs.Node = (*invalidateAttr)(nil) func (i *invalidateAttr) Attr(ctx context.Context, a *fuse.Attr) error { i.attr.Inc() i.t.Logf("Attr called, #%d", i.attr.Count()) a.Mode = 0o600 return nil } func TestInvalidateNodeAttr(t *testing.T) { // This test may see false positive failures when run under // extreme memory pressure. maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() a := &invalidateAttr{ t: t, } mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": a}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := statHelper.Spawn(ctx, t) defer control.Close() for i := 0; i < 10; i++ { var got statResult if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil { t.Fatalf("calling helper: %v", err) } } // With OSXFUSE 3.0.4, we seem to see typically two Attr calls by // this point; something not populating the in-kernel cache // properly? Cope with it; we care more about seeing a new Attr // call after the invalidation. // // We still enforce a max number here so that we know that the // invalidate actually did something, and it's not just that every // Stat results in an Attr. before := a.attr.Count() if before == 0 { t.Error("no Attr call seen") } if g, e := before, uint32(3); g > e { t.Errorf("too many Attr calls seen: %d > %d", g, e) } t.Logf("invalidating...") if err := mnt.Server.InvalidateNodeAttr(a); err != nil { t.Fatalf("invalidate error: %v", err) } for i := 0; i < 10; i++ { var got statResult if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil { t.Fatalf("calling helper: %v", err) } } if g, e := a.attr.Count(), before+1; g != e { t.Errorf("wrong Attr call count: %d != %d", g, e) } } type invalidateData struct { t testing.TB attr record.Counter read record.Counter data atomic.Value } const ( invalidateDataContent1 = "hello, world\n" invalidateDataContent2 = "so long!\n" ) var _ fs.Node = (*invalidateData)(nil) func (i *invalidateData) Attr(ctx context.Context, a *fuse.Attr) error { i.attr.Inc() i.t.Logf("Attr called, #%d", i.attr.Count()) a.Mode = 0o600 a.Size = uint64(len(i.data.Load().(string))) return nil } var _ fs.HandleReader = (*invalidateData)(nil) func (i *invalidateData) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { i.read.Inc() i.t.Logf("Read called, #%d", i.read.Count()) fuseutil.HandleRead(req, resp, []byte(i.data.Load().(string))) return nil } type fstatHelp struct { mu sync.Mutex file *os.File } func (f *fstatHelp) ServeHTTP(w http.ResponseWriter, req *http.Request) { switch req.URL.Path { case "/open": httpjson.ServePOST(f.doOpen).ServeHTTP(w, req) case "/fstat": httpjson.ServePOST(f.doFstat).ServeHTTP(w, req) case "/close": httpjson.ServePOST(f.doClose).ServeHTTP(w, req) default: http.NotFound(w, req) } } func (f *fstatHelp) doOpen(ctx context.Context, path string) (*struct{}, error) { f.mu.Lock() defer f.mu.Unlock() fil, err := os.Open(path) if err != nil { return nil, err } f.file = fil return &struct{}{}, nil } func (f *fstatHelp) doFstat(ctx context.Context, _ struct{}) (*statResult, error) { f.mu.Lock() defer f.mu.Unlock() fi, err := f.file.Stat() if err != nil { return nil, err } r := platformStat(fi) return r, nil } func (f *fstatHelp) doClose(ctx context.Context, _ struct{}) (*struct{}, error) { f.mu.Lock() defer f.mu.Unlock() f.file.Close() return &struct{}{}, nil } var fstatHelper = helpers.Register("fstat", &fstatHelp{}) func TestInvalidateNodeDataInvalidatesAttr(t *testing.T) { // This test may see false positive failures when run under // extreme memory pressure. maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() a := &invalidateData{ t: t, } a.data.Store(invalidateDataContent1) mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": a}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := fstatHelper.Spawn(ctx, t) defer control.Close() var nothing struct{} if err := control.JSON("/open").Call(ctx, mnt.Dir+"/child", ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } attrBefore := a.attr.Count() if g, min := attrBefore, uint32(1); g < min { t.Errorf("wrong Attr call count: %d < %d", g, min) } t.Logf("invalidating...") a.data.Store(invalidateDataContent2) if err := mnt.Server.InvalidateNodeData(a); err != nil { t.Fatalf("invalidate error: %v", err) } // on OSXFUSE 3.0.6, the Attr has already triggered here, so don't // check the count at this point var got statResult if err := control.JSON("/fstat").Call(ctx, struct{}{}, &got); err != nil { t.Fatalf("calling helper: %v", err) } if g, prev := a.attr.Count(), attrBefore; g <= prev { t.Errorf("did not see Attr call after invalidate: %d <= %d", g, prev) } } type manyReadsHelp struct { mu sync.Mutex file *os.File } func (m *manyReadsHelp) ServeHTTP(w http.ResponseWriter, req *http.Request) { switch req.URL.Path { case "/open": httpjson.ServePOST(m.doOpen).ServeHTTP(w, req) case "/readAt": httpjson.ServePOST(m.doReadAt).ServeHTTP(w, req) case "/close": httpjson.ServePOST(m.doClose).ServeHTTP(w, req) default: http.NotFound(w, req) } } func (m *manyReadsHelp) doOpen(ctx context.Context, path string) (*struct{}, error) { m.mu.Lock() defer m.mu.Unlock() fil, err := os.Open(path) if err != nil { return nil, err } m.file = fil return &struct{}{}, nil } type readAtRequest struct { Offset int64 Length int } func (m *manyReadsHelp) doReadAt(ctx context.Context, req readAtRequest) ([]byte, error) { m.mu.Lock() defer m.mu.Unlock() buf := make([]byte, req.Length) n, err := m.file.ReadAt(buf, req.Offset) if err != nil && err != io.EOF { return nil, err } buf = buf[:n] return buf, nil } func (m *manyReadsHelp) doClose(ctx context.Context, _ struct{}) (*struct{}, error) { m.mu.Lock() defer m.mu.Unlock() m.file.Close() return &struct{}{}, nil } var manyReadsHelper = helpers.Register("manyReads", &manyReadsHelp{}) func TestInvalidateNodeDataInvalidatesData(t *testing.T) { // This test may see false positive failures when run under // extreme memory pressure. maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() a := &invalidateData{ t: t, } a.data.Store(invalidateDataContent1) mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": a}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := manyReadsHelper.Spawn(ctx, t) defer control.Close() var nothing struct{} if err := control.JSON("/open").Call(ctx, mnt.Dir+"/child", ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } { for i := 0; i < 10; i++ { req := readAtRequest{ Offset: 0, Length: 100, } var got []byte if err := control.JSON("/readAt").Call(ctx, req, &got); err != nil { t.Fatalf("calling helper: %v", err) } if g, e := string(got), invalidateDataContent1; g != e { t.Errorf("wrong content: %q != %q", g, e) } } } if g, e := a.read.Count(), uint32(1); g != e { t.Errorf("wrong Read call count: %d != %d", g, e) } t.Logf("invalidating...") a.data.Store(invalidateDataContent2) if err := mnt.Server.InvalidateNodeData(a); err != nil { t.Fatalf("invalidate error: %v", err) } if g, e := a.read.Count(), uint32(1); g != e { t.Errorf("wrong Read call count: %d != %d", g, e) } { // explicitly don't cross the EOF, to trigger more edge cases // (Linux will always do Getattr if you cross what it believes // the EOF to be) const bufSize = len(invalidateDataContent2) - 3 for i := 0; i < 10; i++ { req := readAtRequest{ Offset: 0, Length: bufSize, } var got []byte if err := control.JSON("/readAt").Call(ctx, req, &got); err != nil { t.Fatalf("calling helper: %v", err) } if g, e := string(got), invalidateDataContent2[:bufSize]; g != e { t.Errorf("wrong content: %q != %q", g, e) } } } if g, e := a.read.Count(), uint32(2); g != e { t.Errorf("wrong Read call count: %d != %d", g, e) } } type invalidateDataPartial struct { t testing.TB attr record.Counter read record.Counter } var invalidateDataPartialContent = strings.Repeat("hello, world\n", 1000) var _ fs.Node = (*invalidateDataPartial)(nil) func (i *invalidateDataPartial) Attr(ctx context.Context, a *fuse.Attr) error { i.attr.Inc() i.t.Logf("Attr called, #%d", i.attr.Count()) a.Mode = 0o600 a.Size = uint64(len(invalidateDataPartialContent)) return nil } var _ fs.HandleReader = (*invalidateDataPartial)(nil) func (i *invalidateDataPartial) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { i.read.Inc() i.t.Logf("Read called, #%d", i.read.Count()) fuseutil.HandleRead(req, resp, []byte(invalidateDataPartialContent)) return nil } func TestInvalidateNodeDataRangeMiss(t *testing.T) { if runtime.GOOS == "freebsd" { t.Skip("FreeBSD seems to always invalidate whole file") } // This test may see false positive failures when run under // extreme memory pressure. maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() a := &invalidateDataPartial{ t: t, } mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": a}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := manyReadsHelper.Spawn(ctx, t) defer control.Close() var nothing struct{} if err := control.JSON("/open").Call(ctx, mnt.Dir+"/child", ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } for i := 0; i < 10; i++ { req := readAtRequest{ Offset: 0, Length: 4, } var got []byte if err := control.JSON("/readAt").Call(ctx, req, &got); err != nil { t.Fatalf("calling helper: %v", err) } } if g, e := a.read.Count(), uint32(1); g != e { t.Errorf("wrong Read call count: %d != %d", g, e) } t.Logf("invalidating an uninteresting block...") if err := mnt.Server.InvalidateNodeDataRange(a, 4096, 4096); err != nil { t.Fatalf("invalidate error: %v", err) } for i := 0; i < 10; i++ { req := readAtRequest{ Offset: 0, Length: 4, } var got []byte if err := control.JSON("/readAt").Call(ctx, req, &got); err != nil { t.Fatalf("calling helper: %v", err) } } // The page invalidated is not the page we're reading, so it // should stay in cache. if g, e := a.read.Count(), uint32(1); g != e { t.Errorf("wrong Read call count: %d != %d", g, e) } } func TestInvalidateNodeDataRangeHit(t *testing.T) { // This test may see false positive failures when run under // extreme memory pressure. maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() a := &invalidateDataPartial{ t: t, } mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": a}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := manyReadsHelper.Spawn(ctx, t) defer control.Close() var nothing struct{} if err := control.JSON("/open").Call(ctx, mnt.Dir+"/child", ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } const offset = 4096 for i := 0; i < 10; i++ { req := readAtRequest{ Offset: offset, Length: 4, } var got []byte if err := control.JSON("/readAt").Call(ctx, req, &got); err != nil { t.Fatalf("calling helper: %v", err) } } if g, e := a.read.Count(), uint32(1); g != e { t.Errorf("wrong Read call count: %d != %d", g, e) } t.Logf("invalidating where the reads are...") if err := mnt.Server.InvalidateNodeDataRange(a, offset, 4096); err != nil { t.Fatalf("invalidate error: %v", err) } for i := 0; i < 10; i++ { req := readAtRequest{ Offset: offset, Length: 4, } var got []byte if err := control.JSON("/readAt").Call(ctx, req, &got); err != nil { t.Fatalf("calling helper: %v", err) } } // One new read if g, e := a.read.Count(), uint32(2); g != e { t.Errorf("wrong Read call count: %d != %d", g, e) } } type invalidateEntryRoot struct { t testing.TB lookup record.Counter } var _ fs.Node = (*invalidateEntryRoot)(nil) func (i *invalidateEntryRoot) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0o600 | os.ModeDir return nil } var _ fs.NodeStringLookuper = (*invalidateEntryRoot)(nil) func (i *invalidateEntryRoot) Lookup(ctx context.Context, name string) (fs.Node, error) { if name != "child" { return nil, syscall.ENOENT } i.lookup.Inc() i.t.Logf("Lookup called, #%d", i.lookup.Count()) return fstestutil.File{}, nil } func TestInvalidateEntry(t *testing.T) { // This test may see false positive failures when run under // extreme memory pressure. maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() a := &invalidateEntryRoot{ t: t, } mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{a}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := statHelper.Spawn(ctx, t) defer control.Close() for i := 0; i < 10; i++ { var got statResult if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil { t.Fatalf("calling helper: %v", err) } } if g, e := a.lookup.Count(), uint32(1); g != e { t.Errorf("wrong Lookup call count: %d != %d", g, e) } t.Logf("invalidating...") if err := mnt.Server.InvalidateEntry(a, "child"); err != nil { t.Fatalf("invalidate error: %v", err) } for i := 0; i < 10; i++ { var got statResult if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil { t.Fatalf("calling helper: %v", err) } } if g, e := a.lookup.Count(), uint32(2); g != e { t.Errorf("wrong Lookup call count: %d != %d", g, e) } } type cachedFile struct { } var _ fs.Node = cachedFile{} func (f cachedFile) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0o666 // FreeBSD won't issue reads if the file is empty. a.Size = 4096 return nil } var _ fs.NodeOpener = cachedFile{} func (f cachedFile) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { resp.Flags |= fuse.OpenKeepCache return f, nil } type readErrRequest struct { Path string WantErrno syscall.Errno } func doReadErr(ctx context.Context, req readErrRequest) (*struct{}, error) { f, err := os.Open(req.Path) if err != nil { return nil, err } defer f.Close() data := make([]byte, 4096) if _, err := f.Read(data); !errors.Is(err, req.WantErrno) { return nil, fmt.Errorf("wrong error: %v", err) } return &struct{}{}, nil } var readErrHelper = helpers.Register("readErr", httpjson.ServePOST(doReadErr)) func TestNotifyStore(t *testing.T) { // This test may see false positive failures when run under // extreme memory pressure. maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() child := cachedFile{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": child}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := readHelper.Spawn(ctx, t) defer control.Close() // prove that read doesn't work, and make sure node is cached { control := readErrHelper.Spawn(ctx, t) defer control.Close() req := readErrRequest{ Path: mnt.Dir + "/child", WantErrno: syscall.EOPNOTSUPP, } var nothing struct{} if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } } greeting := strings.Repeat("testing store\n", 500) if l := len(greeting); l < syscall.Getpagesize() { t.Fatalf("must fill at least one page to avoid second Read: len=%d", l) } t.Logf("storing...") if err := mnt.Server.NotifyStore(child, 0, []byte(greeting)); err != nil { if runtime.GOOS == "freebsd" && errors.Is(err, syscall.ENOSYS) { t.Skip("FreeBSD does not support NotifyStore") } t.Fatalf("store error: %v", err) } if runtime.GOOS == "freebsd" { t.Errorf("FreeBSD started supporting NotifyStore, update code") } var got readResult if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil { t.Fatalf("calling helper: %v", err) } // we have just read data without implementing Read! // doRead caps result at 4 kiB if g, e := string(got.Data), greeting[:4096]; g != e { t.Errorf("readAll = %q, want %q", g, e) } } func TestNotifyRetrieve(t *testing.T) { // This test may see false positive failures when run under // extreme memory pressure. maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() child := readAll{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": child}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := readHelper.Spawn(ctx, t) defer control.Close() // read to fill page cache var got readResult if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil { t.Fatalf("calling helper: %v", err) } t.Logf("retrieving...") data, err := mnt.Server.NotifyRetrieve(child, 0, 5) if err != nil { if runtime.GOOS == "freebsd" && errors.Is(err, syscall.ENOSYS) { t.Skip("FreeBSD does not support NotifyRetrieve") } t.Fatalf("retrieve error: %v", err) } if runtime.GOOS == "freebsd" { t.Errorf("FreeBSD started supporting NotifyRetrieve, update code") } if g, e := string(data), hi[:5]; g != e { t.Errorf("retrieve = %q, want %q", g, e) } } type contextFile struct { fstestutil.File } var contextFileSentinel int func (contextFile) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { v := ctx.Value(&contextFileSentinel) if v == nil { return nil, syscall.ESTALE } data, ok := v.(string) if !ok { return nil, syscall.EIO } resp.Flags |= fuse.OpenDirectIO return fs.DataHandle([]byte(data)), nil } func TestContext(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() const input = "kilroy was here" mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": contextFile{}}}, &fs.Config{ WithContext: func(ctx context.Context, req fuse.Request) context.Context { return context.WithValue(ctx, &contextFileSentinel, input) }, }) if err != nil { t.Fatal(err) } defer mnt.Close() control := readHelper.Spawn(ctx, t) defer control.Close() var got readResult if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil { t.Fatalf("calling helper: %v", err) } if g, e := string(got.Data), input; g != e { t.Errorf("read wrong data: %q != %q", g, e) } } type goexitFile struct { fstestutil.File } func (goexitFile) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { log.Println("calling runtime.Goexit...") runtime.Goexit() panic("not reached") } func TestGoexit(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": goexitFile{}}}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := openErrHelper.Spawn(ctx, t) defer control.Close() req := openRequest{ Path: mnt.Dir + "/child", Flags: os.O_RDONLY, Perm: 0, WantErrno: syscall.EIO, } var nothing struct{} if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } } // Test Poll: NodePoller and HandlePoller // pollDelayRead is a HandleReader that only lets a read succeed after // one round of polling. type pollDelayRead struct { t testing.TB server *fs.Server mu sync.Mutex seen []string wakeup atomic.Value ready uint64 } // Can be used as either Handle or Node. If these interfaces diverge, // change this to a common core with two wrappers. var _ fs.HandlePoller = (*pollDelayRead)(nil) var _ fs.NodePoller = (*pollDelayRead)(nil) func (r *pollDelayRead) saw(s string) { r.mu.Lock() defer r.mu.Unlock() r.t.Logf("saw %s", s) r.seen = append(r.seen, s) } func (n *pollDelayRead) Poll(ctx context.Context, req *fuse.PollRequest, resp *fuse.PollResponse) error { if w, ok := req.Wakeup(); ok { n.wakeup.Store(w) } resp.REvents = fuse.PollOut if atomic.LoadUint64(&n.ready) == 1 { resp.REvents |= fuse.PollIn return nil } return nil } func (n *pollDelayRead) doWakeup() { n.saw("wakeup") atomic.StoreUint64(&n.ready, 1) if w, ok := n.wakeup.Load().(fuse.PollWakeup); ok { if err := n.server.NotifyPollWakeup(w); err != nil { n.t.Errorf("wakeup error: %v", err) } } } var _ fs.HandleReader = (*pollDelayRead)(nil) func (n *pollDelayRead) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { if req.FileFlags&fuse.OpenNonblock == 0 { n.t.Errorf("expected a non-blocking read") return syscall.ENAMETOOLONG } if atomic.LoadUint64(&n.ready) == 0 { n.saw("read-eagain") time.AfterFunc(1*time.Millisecond, n.doWakeup) return syscall.EAGAIN } n.saw("read-ready") fuseutil.HandleRead(req, resp, []byte(hi)) return nil } // Test NodePoller type readPolledNode struct { pollDelayRead } var _ fs.Node = (*readPolledNode)(nil) func (readPolledNode) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0o666 a.Size = uint64(len(hi)) return nil } func TestReadPollNode(t *testing.T) { if runtime.GOOS == "freebsd" { t.Skip("no poll on FreeBSD") } maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() child := &readPolledNode{ pollDelayRead: pollDelayRead{ t: t, }, } filesys := fstestutil.SimpleFS{&fstestutil.ChildMap{"child": child}} setup := func(mnt *fstestutil.Mount) fs.FS { child.server = mnt.Server return filesys } mnt, err := fstestutil.MountedFuncT(t, setup, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := readHelper.Spawn(ctx, t) defer control.Close() var got readResult if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil { t.Fatalf("calling helper: %v", err) } if g, e := string(got.Data), hi; g != e { t.Errorf("readAll = %q, want %q", g, e) } if g, e := strings.Join(child.seen, " "), "read-eagain wakeup read-ready"; g != e { t.Errorf("wrong events: %q != %q", g, e) } } // Test HandlePoller type readPolledNodeWithHandle struct { handle pollDelayRead } var _ fs.Node = (*readPolledNodeWithHandle)(nil) func (readPolledNodeWithHandle) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0o666 a.Size = uint64(len(hi)) return nil } var _ fs.NodeOpener = (*readPolledNodeWithHandle)(nil) func (f *readPolledNodeWithHandle) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { return &f.handle, nil } func TestReadPollHandle(t *testing.T) { if runtime.GOOS == "freebsd" { t.Skip("no poll on FreeBSD") } maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() child := &readPolledNodeWithHandle{ handle: pollDelayRead{ t: t, }, } filesys := fstestutil.SimpleFS{&fstestutil.ChildMap{"child": child}} setup := func(mnt *fstestutil.Mount) fs.FS { child.handle.server = mnt.Server return filesys } mnt, err := fstestutil.MountedFuncT(t, setup, nil) if err != nil { t.Fatal(err) } defer mnt.Close() control := readHelper.Spawn(ctx, t) defer control.Close() var got readResult if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil { t.Fatalf("calling helper: %v", err) } if g, e := string(got.Data), hi; g != e { t.Errorf("readAll = %q, want %q", g, e) } if g, e := strings.Join(child.handle.seen, " "), "read-eagain wakeup read-ready"; g != e { t.Errorf("wrong events: %q != %q", g, e) } } // Test flock // Go syscall & golang.org/x/sys/unix do a horrible thing where they // muddle the difference between fcntl and flock by naming the syscall // "FcntlFlock" and the result type "Flock_t". Make no mistake that is // fcntl and has nothing to do with flock. type lockFile struct { fstestutil.File lock record.RequestRecorder unlock record.RequestRecorder release record.ReleaseWaiter flush record.RequestRecorder } var _ fs.NodeOpener = (*lockFile)(nil) func (f *lockFile) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { h := &lockHandle{ f: f, } return h, nil } type lockHandle struct { f *lockFile } var _ fs.Handle = (*lockHandle)(nil) var _ fs.HandleLocker = (*lockHandle)(nil) func (h *lockHandle) Lock(ctx context.Context, req *fuse.LockRequest) error { tmp := *req h.f.lock.RecordRequest(&tmp) return nil } func (h *lockHandle) LockWait(ctx context.Context, req *fuse.LockWaitRequest) error { tmp := *req h.f.lock.RecordRequest(&tmp) return nil } func (h *lockHandle) Unlock(ctx context.Context, req *fuse.UnlockRequest) error { tmp := *req h.f.unlock.RecordRequest(&tmp) return nil } func (h *lockHandle) QueryLock(ctx context.Context, req *fuse.QueryLockRequest, resp *fuse.QueryLockResponse) error { return nil } var _ fs.HandleFlockLocker = (*lockHandle)(nil) var _ fs.HandleReleaser = (*lockHandle)(nil) func (h *lockHandle) Release(ctx context.Context, req *fuse.ReleaseRequest) error { return h.f.release.Release(ctx, req) } var _ fs.HandlePOSIXLocker = (*lockHandle)(nil) var _ fs.HandleFlusher = (*lockHandle)(nil) func (h *lockHandle) Flush(ctx context.Context, req *fuse.FlushRequest) error { tmp := *req h.f.flush.RecordRequest(&tmp) return nil } type lockHelp struct { lockFn func(fd uintptr, req *lockReq) error unlockFn func(fd uintptr, req *lockReq) error queryFn func(fd uintptr, lk *unix.Flock_t) error mu sync.Mutex file *os.File } func (lh *lockHelp) ServeHTTP(w http.ResponseWriter, req *http.Request) { switch req.URL.Path { case "/lock": httpjson.ServePOST(lh.doLock).ServeHTTP(w, req) case "/unlock": httpjson.ServePOST(lh.doUnlock).ServeHTTP(w, req) case "/close": httpjson.ServePOST(lh.doClose).ServeHTTP(w, req) case "/query": httpjson.ServePOST(lh.doQuery).ServeHTTP(w, req) default: http.NotFound(w, req) } } type lockReq struct { Path string Wait bool Start int64 Len int64 } func (lh *lockHelp) doLock(ctx context.Context, req *lockReq) (*struct{}, error) { lh.mu.Lock() defer lh.mu.Unlock() f, err := os.OpenFile(req.Path, os.O_RDWR, 0o644) if err != nil { return nil, fmt.Errorf("open: %v", err) } lh.file = f c, err := lh.file.SyscallConn() if err != nil { return nil, fmt.Errorf("syscallconn: %v", err) } var outerr error lockFn := func(fd uintptr) { outerr = lh.lockFn(fd, req) } if err := c.Control(lockFn); err != nil { return nil, fmt.Errorf("error calling lock: %v", err) } if err := outerr; err != nil { return nil, fmt.Errorf("lock error: %v", err) } return &struct{}{}, nil } func (lh *lockHelp) doUnlock(ctx context.Context, req *lockReq) (*struct{}, error) { lh.mu.Lock() defer lh.mu.Unlock() if lh.file == nil { return nil, errors.New("file not open") } c, err := lh.file.SyscallConn() if err != nil { return nil, fmt.Errorf("syscallconn: %v", err) } var outerr error unlockFn := func(fd uintptr) { outerr = lh.unlockFn(fd, req) } if err := c.Control(unlockFn); err != nil { return nil, fmt.Errorf("error calling unlock: %v", err) } if err := outerr; err != nil { return nil, fmt.Errorf("unlock error: %v", err) } return &struct{}{}, nil } func (lh *lockHelp) doClose(ctx context.Context, _ struct{}) (*struct{}, error) { lh.mu.Lock() defer lh.mu.Unlock() if err := lh.file.Close(); err != nil { return nil, fmt.Errorf("Close: %v", err) } return &struct{}{}, nil } func (lh *lockHelp) doQuery(ctx context.Context, req *lockReq) (*unix.Flock_t, error) { lh.mu.Lock() defer lh.mu.Unlock() if lh.file == nil { return nil, errors.New("file not open") } c, err := lh.file.SyscallConn() if err != nil { return nil, fmt.Errorf("syscallconn: %v", err) } var outerr error lk := unix.Flock_t{ Type: unix.F_WRLCK, Whence: int16(io.SeekStart), Start: req.Start, Len: req.Len, } queryFn := func(fd uintptr) { outerr = lh.queryFn(fd, &lk) } if err := c.Control(queryFn); err != nil { return nil, fmt.Errorf("error calling getlk: %v", err) } if err := outerr; err != nil { return nil, fmt.Errorf("query lock error: %v", err) } return &lk, nil } var lockFlockHelper = helpers.Register("lock-flock", &lockHelp{ lockFn: func(fd uintptr, req *lockReq) error { flags := unix.LOCK_EX if !req.Wait { flags |= unix.LOCK_NB } return unix.Flock(int(fd), flags) }, unlockFn: func(fd uintptr, req *lockReq) error { return unix.Flock(int(fd), unix.LOCK_UN) }, queryFn: func(fd uintptr, lk *unix.Flock_t) error { return errors.New("no query in flock api") }, }) var lockPOSIXHelper = helpers.Register("lock-posix", &lockHelp{ lockFn: func(fd uintptr, req *lockReq) error { lk := unix.Flock_t{ Type: unix.F_WRLCK, Whence: int16(io.SeekStart), Start: req.Start, Len: req.Len, } cmd := unix.F_SETLK if req.Wait { cmd = unix.F_SETLKW } // return unix.FcntlFlock(fd, cmd, &lk) err := unix.FcntlFlock(fd, cmd, &lk) log.Printf("WTF L %d %v %#v: %v", fd, cmd, lk, err) return err }, unlockFn: func(fd uintptr, req *lockReq) error { lk := unix.Flock_t{ Type: unix.F_UNLCK, Whence: int16(io.SeekStart), Start: req.Start, Len: req.Len, } cmd := unix.F_SETLK // return unix.FcntlFlock(fd, cmd, &lk) err := unix.FcntlFlock(fd, cmd, &lk) log.Printf("WTF U %d %v %#v: %v", fd, cmd, lk, err) return err }, queryFn: func(fd uintptr, lk *unix.Flock_t) error { cmd := unix.F_GETLK return unix.FcntlFlock(fd, cmd, lk) }, }) // ugly kludge to have platform-specific subtests. filled in // elsewhere, when on linux. var lockOFDHelper *spawntest.Helper type lockTest struct { *testing.T ctx context.Context kind string child *lockFile mnt *fstestutil.Mount control *spawntest.Control } func (t *lockTest) recordedLockRequest() (_ *fuse.LockRequest, waited bool) { switch req := t.child.lock.Recorded().(type) { case *fuse.LockRequest: return req, false case *fuse.LockWaitRequest: return (*fuse.LockRequest)(req), true default: t.Fatalf("bad lock request: %#v", req) } panic("not reached") } func (t *lockTest) callLock(req *lockReq) { t.Logf("calling lock") if req.Path == "" { req.Path = "child" } req.Path = filepath.Join(t.mnt.Dir, req.Path) var nothing struct{} if err := t.control.JSON("/lock").Call(t.ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } want := &fuse.LockRequest{ LockFlags: 0, Lock: fuse.FileLock{ Start: uint64(req.Start), End: uint64(req.Start) + uint64(req.Len) - 1, Type: fuse.LockWrite, PID: 0, }, } if t.kind == "flock" { want.LockFlags |= fuse.LockFlock want.Lock.Start = 0 want.Lock.End = 0x7fff_ffff_ffff_ffff } got, waited := t.recordedLockRequest() if g, e := waited, req.Wait; g != e { t.Errorf("lock non-blocking field is bad: %v != %v", g, e) } // dynamic values that are too hard to control if got.Handle == 0 { t.Errorf("got LockRequest with no Handle") } want.Handle = got.Handle if got.LockOwner == 0 { t.Errorf("got LockRequest with no LockOwner") } want.LockOwner = got.LockOwner if got.Lock.PID == 0 { t.Errorf("got LockRequest with no PID") } want.Lock.PID = got.Lock.PID if g, e := got, want; *g != *e { t.Errorf("lock bad request\ngot\t%v\nwant\t%v", g, e) } } func (t *lockTest) callUnlock(req *lockReq) { t.Logf("calling unlock") var nothing struct{} if err := t.control.JSON("/unlock").Call(t.ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } lockReq, _ := t.recordedLockRequest() t.Logf("previous lock request: %v", lockReq) want := &fuse.UnlockRequest{ LockOwner: lockReq.LockOwner, LockFlags: 0, Lock: fuse.FileLock{ Start: uint64(req.Start), End: uint64(req.Start) + uint64(req.Len) - 1, Type: fuse.LockUnlock, PID: 0, }, } if t.kind == "flock" { want.LockFlags |= fuse.LockFlock want.Lock.Start = 0 want.Lock.End = 0x7fff_ffff_ffff_ffff } got := t.child.unlock.Recorded().(*fuse.UnlockRequest) // dynamic values that are too hard to control if got.Handle == 0 { t.Errorf("got UnlockRequest with no Handle") } want.Handle = got.Handle if g, e := got, want; *g != *e { t.Errorf("unlock bad request\ngot\t%v\nwant\t%v", g, e) } } func (t *lockTest) callCloseToUnlock() { t.Logf("calling close with automatic unlock") var nothing struct{} if err := t.control.JSON("/close").Call(t.ctx, struct{}{}, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } if t.kind == "posix" { lockReq, _ := t.recordedLockRequest() want := &fuse.FlushRequest{ LockOwner: lockReq.LockOwner, } got := t.child.flush.Recorded().(*fuse.FlushRequest) // dynamic values that are too hard to control if got.Handle == 0 { t.Errorf("got FlushRequest with no Handle") } want.Handle = got.Handle if g, e := got, want; *g != *e { t.Errorf("close to unlock bad flush request\ngot\t%v\nwant\t%v", g, e) } } if t.kind == "flock" || t.kind == "ofd" { lockReq, _ := t.recordedLockRequest() want := &fuse.ReleaseRequest{ Flags: fuse.OpenReadWrite | fuse.OpenNonblock, LockOwner: lockReq.LockOwner, } if t.kind == "ofd" { // TODO linux kernel ofd FUSE support is very partial; // disable parts that don't work want.LockOwner = 0 } if t.kind == "flock" { want.ReleaseFlags |= fuse.ReleaseFlockUnlock } got, ok := t.child.release.WaitForRelease(1 * time.Second) if !ok { t.Fatalf("Close did not Release in time") } // dynamic values that are too hard to control if got.Handle == 0 { t.Errorf("got ReleaseRequest with no Handle") } want.Handle = got.Handle if g, e := got, want; *g != *e { t.Errorf("bad release:\ngot\t%v\nwant\t%v", g, e) } } } func (t *lockTest) callQueryLock(req *lockReq) *unix.Flock_t { t.Logf("calling queryLock") var resp unix.Flock_t if err := t.control.JSON("/query").Call(t.ctx, req, &resp); err != nil { t.Fatalf("calling helper: %v", err) } return &resp } type lockFamily struct { name string mountOptions []fuse.MountOption helper *spawntest.Helper } func (family lockFamily) run(t *testing.T, name string, fn func(t *lockTest)) { t.Helper() t.Run(name, func(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() child := &lockFile{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": child}}, nil, family.mountOptions...) if err != nil { t.Fatal(err) } defer mnt.Close() control := family.helper.Spawn(ctx, t) defer control.Close() lt := &lockTest{ T: t, ctx: ctx, kind: family.name, child: child, mnt: mnt, control: control, } fn(lt) }) } func TestLocking(t *testing.T) { if runtime.GOOS == "freebsd" { // Non-exhaustive list of issues encountered, too many to add // workarounds or kludge tests: // // - flock is not implemented // - F_SETLKW comes through as non-blocking // - fcntl F_UNLCK calls give EINVAL for some reason // - LockRequest.LockOwner == 0 // - LockRequest.Lock.PID == 0, while Linux fills it t.Skip("FreeBSD locking support does not work") } t.Run("Flock", func(t *testing.T) { run := lockFamily{ name: "flock", mountOptions: []fuse.MountOption{fuse.LockingFlock()}, helper: lockFlockHelper, }.run run(t, "Nonblock", func(t *lockTest) { t.callLock(&lockReq{}) t.callUnlock(&lockReq{}) }) run(t, "Wait", func(t *lockTest) { t.callLock(&lockReq{ Wait: true, }) t.callUnlock(&lockReq{}) }) run(t, "CloseUnlocks", func(t *lockTest) { t.callLock(&lockReq{}) t.callCloseToUnlock() }) }) t.Run("POSIX", func(t *testing.T) { run := lockFamily{ name: "posix", mountOptions: []fuse.MountOption{fuse.LockingPOSIX()}, helper: lockPOSIXHelper, }.run run(t, "Nonblock", func(t *lockTest) { lr := &lockReq{ Start: 42, Len: 13, } t.callLock(lr) t.callUnlock(lr) }) run(t, "Wait", func(t *lockTest) { lr := &lockReq{ Wait: true, Start: 42, Len: 13, } t.callLock(lr) t.callUnlock(lr) }) run(t, "CloseUnlocks", func(t *lockTest) { t.callLock(&lockReq{ Wait: true, Start: 42, Len: 13, }) t.callCloseToUnlock() }) run(t, "QueryLock", func(t *lockTest) { t.callLock(&lockReq{ Wait: true, Start: 42, Len: 13, }) got := t.callQueryLock(&lockReq{ Start: 40, Len: 10, }) want := &unix.Flock_t{ Type: unix.F_UNLCK, Whence: io.SeekStart, Start: 40, Len: 10, Pid: 0, } if g, e := got, want; *g != *e { t.Errorf("bad query lock\ngot\t%#v\nwant\t%#v", g, e) } }) }) t.Run("OpenFileDescription", func(t *testing.T) { if runtime.GOOS != "linux" { t.Skip("Open File Descriptor locks are Linux-only") } run := lockFamily{ name: "ofd", mountOptions: []fuse.MountOption{fuse.LockingPOSIX()}, helper: lockOFDHelper, }.run run(t, "Nonblock", func(t *lockTest) { lr := &lockReq{ Start: 42, Len: 13, } t.callLock(lr) t.callUnlock(lr) }) run(t, "Wait", func(t *lockTest) { lr := &lockReq{ Wait: true, Start: 42, Len: 13, } t.callLock(lr) t.callUnlock(lr) }) run(t, "CloseUnlocks", func(t *lockTest) { t.callLock(&lockReq{ Wait: true, Start: 42, Len: 13, }) t.callCloseToUnlock() }) run(t, "QueryLock", func(t *lockTest) { t.callLock(&lockReq{ Wait: true, Start: 42, Len: 13, }) got := t.callQueryLock(&lockReq{ Start: 40, Len: 10, }) want := &unix.Flock_t{ Type: unix.F_UNLCK, Whence: io.SeekStart, Start: 40, Len: 10, Pid: 0, } if g, e := got, want; *g != *e { t.Errorf("bad query lock\ngot\t%#v\nwant\t%#v", g, e) } }) }) } golang-github-anacrolix-fuse-0.2.0/fs/tree.go000066400000000000000000000036111444232012100210460ustar00rootroot00000000000000// FUSE directory tree, for servers that wish to use it with the service loop. package fs import ( "context" "os" pathpkg "path" "strings" "syscall" "github.com/anacrolix/fuse" ) // A Tree implements a basic read-only directory tree for FUSE. // The Nodes contained in it may still be writable. type Tree struct { tree } func (t *Tree) Root() (Node, error) { return &t.tree, nil } // Add adds the path to the tree, resolving to the given node. // If path or a prefix of path has already been added to the tree, // Add panics. // // Add is only safe to call before starting to serve requests. func (t *Tree) Add(path string, node Node) { path = pathpkg.Clean("/" + path)[1:] elems := strings.Split(path, "/") dir := Node(&t.tree) for i, elem := range elems { dt, ok := dir.(*tree) if !ok { panic("fuse: Tree.Add for " + strings.Join(elems[:i], "/") + " and " + path) } n := dt.lookup(elem) if n != nil { if i+1 == len(elems) { panic("fuse: Tree.Add for " + path + " conflicts with " + elem) } dir = n } else { if i+1 == len(elems) { dt.add(elem, node) } else { dir = &tree{} dt.add(elem, dir) } } } } type treeDir struct { name string node Node } type tree struct { dir []treeDir } func (t *tree) lookup(name string) Node { for _, d := range t.dir { if d.name == name { return d.node } } return nil } func (t *tree) add(name string, n Node) { t.dir = append(t.dir, treeDir{name, n}) } func (t *tree) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = os.ModeDir | 0o555 return nil } func (t *tree) Lookup(ctx context.Context, name string) (Node, error) { n := t.lookup(name) if n != nil { return n, nil } return nil, syscall.ENOENT } func (t *tree) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { var out []fuse.Dirent for _, d := range t.dir { out = append(out, fuse.Dirent{Name: d.name}) } return out, nil } golang-github-anacrolix-fuse-0.2.0/fuse.go000066400000000000000000002175331444232012100204530ustar00rootroot00000000000000// See the file LICENSE for copyright and licensing information. // Adapted from Plan 9 from User Space's src/cmd/9pfuse/fuse.c, // which carries this notice: // // The files in this directory are subject to the following license. // // The author of this software is Russ Cox. // // Copyright (c) 2006 Russ Cox // // Permission to use, copy, modify, and distribute this software for any // purpose without fee is hereby granted, provided that this entire notice // is included in all copies of any software which is or includes a copy // or modification of this software and in all copies of the supporting // documentation for such software. // // THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED // WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION OR WARRANTY // OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS SOFTWARE OR ITS // FITNESS FOR ANY PARTICULAR PURPOSE. // Package fuse enables writing FUSE file systems on Linux, OS X, and FreeBSD. // // On OS X, it requires OSXFUSE (http://osxfuse.github.com/). // // There are two approaches to writing a FUSE file system. The first is to speak // the low-level message protocol, reading from a Conn using ReadRequest and // writing using the various Respond methods. This approach is closest to // the actual interaction with the kernel and can be the simplest one in contexts // such as protocol translators. // // Servers of synthesized file systems tend to share common // bookkeeping abstracted away by the second approach, which is to // call fs.Serve to serve the FUSE protocol using an implementation of // the service methods in the interfaces FS* (file system), Node* (file // or directory), and Handle* (opened file or directory). // There are a daunting number of such methods that can be written, // but few are required. // The specific methods are described in the documentation for those interfaces. // // The hellofs subdirectory contains a simple illustration of the fs.Serve approach. // // Service Methods // // The required and optional methods for the FS, Node, and Handle interfaces // have the general form // // Op(ctx context.Context, req *OpRequest, resp *OpResponse) error // // where Op is the name of a FUSE operation. Op reads request // parameters from req and writes results to resp. An operation whose // only result is the error result omits the resp parameter. // // Multiple goroutines may call service methods simultaneously; the // methods being called are responsible for appropriate // synchronization. // // The operation must not hold on to the request or response, // including any []byte fields such as WriteRequest.Data or // SetxattrRequest.Xattr. // // Errors // // Operations can return errors. The FUSE interface can only // communicate POSIX errno error numbers to file system clients, the // message is not visible to file system clients. The returned error // can implement ErrorNumber to control the errno returned. Without // ErrorNumber, a generic errno (EIO) is returned. // // Error messages will be visible in the debug log as part of the // response. // // Interrupted Operations // // In some file systems, some operations // may take an undetermined amount of time. For example, a Read waiting for // a network message or a matching Write might wait indefinitely. If the request // is cancelled and no longer needed, the context will be cancelled. // Blocking operations should select on a receive from ctx.Done() and attempt to // abort the operation early if the receive succeeds (meaning the channel is closed). // To indicate that the operation failed because it was aborted, return syscall.EINTR. // // If an operation does not block for an indefinite amount of time, supporting // cancellation is not necessary. // // Authentication // // All requests types embed a Header, meaning that the method can // inspect req.Pid, req.Uid, and req.Gid as necessary to implement // permission checking. The kernel FUSE layer normally prevents other // users from accessing the FUSE file system (to change this, see // AllowOther), but does not enforce access modes (to change this, see // DefaultPermissions). // // Mount Options // // Behavior and metadata of the mounted file system can be changed by // passing MountOption values to Mount. // package fuse // import "github.com/anacrolix/fuse" import ( "bytes" "encoding/json" "errors" "fmt" "io" "os" "strings" "sync" "syscall" "time" "unsafe" ) // A Conn represents a connection to a mounted FUSE file system. type Conn struct { // Ready is closed when the mount is complete or has failed. Ready <-chan struct{} // MountError stores any error from the mount process. Only valid // after Ready is closed. MountError error // File handle for kernel communication. Only safe to access if // rio or wio is held. dev *os.File wio sync.RWMutex rio sync.RWMutex // Protocol version negotiated with initRequest/initResponse. proto Protocol // Feature flags negotiated with initRequest/initResponse. flags InitFlags } // MountpointDoesNotExistError is an error returned when the // mountpoint does not exist. type MountpointDoesNotExistError struct { Path string } var _ error = (*MountpointDoesNotExistError)(nil) func (e *MountpointDoesNotExistError) Error() string { return fmt.Sprintf("mountpoint does not exist: %v", e.Path) } // Mount mounts a new FUSE connection on the named directory // and returns a connection for reading and writing FUSE messages. // // After a successful return, caller must call Close to free // resources. // // Even on successful return, the new mount is not guaranteed to be // visible until after Conn.Ready is closed. See Conn.MountError for // possible errors. Incoming requests on Conn must be served to make // progress. func Mount(dir string, options ...MountOption) (*Conn, error) { conf := mountConfig{ options: make(map[string]string), } for _, option := range options { if err := option(&conf); err != nil { return nil, err } } ready := make(chan struct{}, 1) c := &Conn{ Ready: ready, } f, err := mount(dir, &conf, ready, &c.MountError) if err != nil { return nil, err } c.dev = f if err := initMount(c, &conf); err != nil { c.Close() if err == ErrClosedWithoutInit { // see if we can provide a better error <-c.Ready if err := c.MountError; err != nil { return nil, err } } return nil, err } return c, nil } type OldVersionError struct { Kernel Protocol LibraryMin Protocol } func (e *OldVersionError) Error() string { return fmt.Sprintf("kernel FUSE version is too old: %v < %v", e.Kernel, e.LibraryMin) } var ( ErrClosedWithoutInit = errors.New("fuse connection closed without init") ) func initMount(c *Conn, conf *mountConfig) error { req, err := c.ReadRequest() if err != nil { if err == io.EOF { return ErrClosedWithoutInit } return err } r, ok := req.(*initRequest) if !ok { return fmt.Errorf("missing init, got: %T", req) } min := Protocol{protoVersionMinMajor, protoVersionMinMinor} if r.Kernel.LT(min) { req.RespondError(Errno(syscall.EPROTO)) c.Close() return &OldVersionError{ Kernel: r.Kernel, LibraryMin: min, } } proto := Protocol{protoVersionMaxMajor, protoVersionMaxMinor} if r.Kernel.LT(proto) { // Kernel doesn't support the latest version we have. proto = r.Kernel } c.proto = proto c.flags = r.Flags & (InitBigWrites | conf.initFlags) s := &initResponse{ Library: proto, MaxReadahead: conf.maxReadahead, Flags: c.flags, MaxBackground: conf.maxBackground, CongestionThreshold: conf.congestionThreshold, MaxWrite: maxWrite, } r.Respond(s) return nil } // A Request represents a single FUSE request received from the kernel. // Use a type switch to determine the specific kind. // A request of unrecognized type will have concrete type *Header. type Request interface { // Hdr returns the Header associated with this request. Hdr() *Header // RespondError responds to the request with the given error. RespondError(error) String() string } // A RequestID identifies an active FUSE request. type RequestID uint64 func (r RequestID) String() string { return fmt.Sprintf("%#x", uint64(r)) } // A NodeID is a number identifying a directory or file. // It must be unique among IDs returned in LookupResponses // that have not yet been forgotten by ForgetRequests. type NodeID uint64 func (n NodeID) String() string { return fmt.Sprintf("%#x", uint64(n)) } // A HandleID is a number identifying an open directory or file. // It only needs to be unique while the directory or file is open. type HandleID uint64 func (h HandleID) String() string { return fmt.Sprintf("%#x", uint64(h)) } // The RootID identifies the root directory of a FUSE file system. const RootID NodeID = rootID // A Header describes the basic information sent in every request. type Header struct { Conn *Conn `json:"-"` // connection this request was received on ID RequestID // unique ID for request Node NodeID // file or directory the request is about Uid uint32 // user ID of process making request Gid uint32 // group ID of process making request Pid uint32 // process ID of process making request // for returning to reqPool msg *message } func (h *Header) String() string { return fmt.Sprintf("ID=%v Node=%v Uid=%d Gid=%d Pid=%d", h.ID, h.Node, h.Uid, h.Gid, h.Pid) } func (h *Header) Hdr() *Header { return h } func (h *Header) noResponse() { putMessage(h.msg) } func (h *Header) respond(msg []byte) { out := (*outHeader)(unsafe.Pointer(&msg[0])) out.Unique = uint64(h.ID) h.Conn.respond(msg) putMessage(h.msg) } // An ErrorNumber is an error with a specific error number. // // Operations may return an error value that implements ErrorNumber to // control what specific error number (errno) to return. type ErrorNumber interface { // Errno returns the the error number (errno) for this error. Errno() Errno } // Deprecated: Return a syscall.Errno directly. See ToErrno for exact // rules. const ( // ENOSYS indicates that the call is not supported. ENOSYS = Errno(syscall.ENOSYS) // ESTALE is used by Serve to respond to violations of the FUSE protocol. ESTALE = Errno(syscall.ESTALE) ENOENT = Errno(syscall.ENOENT) EIO = Errno(syscall.EIO) EPERM = Errno(syscall.EPERM) // EINTR indicates request was interrupted by an InterruptRequest. // See also fs.Intr. EINTR = Errno(syscall.EINTR) ERANGE = Errno(syscall.ERANGE) ENOTSUP = Errno(syscall.ENOTSUP) EEXIST = Errno(syscall.EEXIST) ) // DefaultErrno is the errno used when error returned does not // implement ErrorNumber. const DefaultErrno = EIO var errnoNames = map[Errno]string{ ENOSYS: "ENOSYS", ESTALE: "ESTALE", ENOENT: "ENOENT", EIO: "EIO", EPERM: "EPERM", EINTR: "EINTR", EEXIST: "EEXIST", Errno(syscall.ENAMETOOLONG): "ENAMETOOLONG", } // Errno implements Error and ErrorNumber using a syscall.Errno. type Errno syscall.Errno var _ ErrorNumber = Errno(0) var _ error = Errno(0) func (e Errno) Errno() Errno { return e } func (e Errno) String() string { return syscall.Errno(e).Error() } func (e Errno) Error() string { return syscall.Errno(e).Error() } // ErrnoName returns the short non-numeric identifier for this errno. // For example, "EIO". func (e Errno) ErrnoName() string { s := errnoNames[e] if s == "" { s = fmt.Sprint(e.Errno()) } return s } func (e Errno) MarshalText() ([]byte, error) { s := e.ErrnoName() return []byte(s), nil } // ToErrno converts arbitrary errors to Errno. // // If the underlying type of err is syscall.Errno, it is used // directly. No unwrapping is done, to prevent wrong errors from // leaking via e.g. *os.PathError. // // If err unwraps to implement ErrorNumber, that is used. // // Finally, returns DefaultErrno. func ToErrno(err error) Errno { if err, ok := err.(syscall.Errno); ok { return Errno(err) } var errnum ErrorNumber if errors.As(err, &errnum) { return Errno(errnum.Errno()) } return DefaultErrno } func (h *Header) RespondError(err error) { errno := ToErrno(err) // FUSE uses negative errors! // TODO: File bug report against OSXFUSE: positive error causes kernel panic. buf := newBuffer(0) hOut := (*outHeader)(unsafe.Pointer(&buf[0])) hOut.Error = -int32(errno) h.respond(buf) } // All requests read from the kernel, without data, are shorter than // this. var maxRequestSize = syscall.Getpagesize() var bufSize = maxRequestSize + maxWrite // reqPool is a pool of messages. // // Lifetime of a logical message is from getMessage to putMessage. // getMessage is called by ReadRequest. putMessage is called by // Conn.ReadRequest, Request.Respond, or Request.RespondError. // // Messages in the pool are guaranteed to have conn and off zeroed, // buf allocated and len==bufSize, and hdr set. var reqPool = sync.Pool{ New: allocMessage, } func allocMessage() interface{} { m := &message{buf: make([]byte, bufSize)} m.hdr = (*inHeader)(unsafe.Pointer(&m.buf[0])) return m } func getMessage(c *Conn) *message { m := reqPool.Get().(*message) m.conn = c return m } func putMessage(m *message) { m.buf = m.buf[:bufSize] m.conn = nil m.off = 0 reqPool.Put(m) } // a message represents the bytes of a single FUSE message type message struct { conn *Conn buf []byte // all bytes hdr *inHeader // header off int // offset for reading additional fields } func (m *message) len() uintptr { return uintptr(len(m.buf) - m.off) } func (m *message) data() unsafe.Pointer { var p unsafe.Pointer if m.off < len(m.buf) { p = unsafe.Pointer(&m.buf[m.off]) } return p } func (m *message) bytes() []byte { return m.buf[m.off:] } func (m *message) Header() Header { h := m.hdr return Header{ Conn: m.conn, ID: RequestID(h.Unique), Node: NodeID(h.Nodeid), Uid: h.Uid, Gid: h.Gid, Pid: h.Pid, msg: m, } } // fileMode returns a Go os.FileMode from a Unix mode. func fileMode(unixMode uint32) os.FileMode { mode := os.FileMode(unixMode & 0o777) switch unixMode & syscall.S_IFMT { case syscall.S_IFREG: // nothing case syscall.S_IFDIR: mode |= os.ModeDir case syscall.S_IFCHR: mode |= os.ModeCharDevice | os.ModeDevice case syscall.S_IFBLK: mode |= os.ModeDevice case syscall.S_IFIFO: mode |= os.ModeNamedPipe case syscall.S_IFLNK: mode |= os.ModeSymlink case syscall.S_IFSOCK: mode |= os.ModeSocket case 0: // apparently there's plenty of times when the FUSE request // does not contain the file type mode |= os.ModeIrregular default: // not just unavailable in the kernel codepath; known to // kernel but unrecognized by us Debug(fmt.Sprintf("unrecognized file mode type: %04o", unixMode)) mode |= os.ModeIrregular } if unixMode&syscall.S_ISUID != 0 { mode |= os.ModeSetuid } if unixMode&syscall.S_ISGID != 0 { mode |= os.ModeSetgid } return mode } type noOpcode struct { Opcode uint32 } func (m noOpcode) String() string { return fmt.Sprintf("No opcode %v", m.Opcode) } type malformedMessage struct { } func (malformedMessage) String() string { return "malformed message" } // Close closes the FUSE connection. func (c *Conn) Close() error { c.wio.Lock() defer c.wio.Unlock() c.rio.Lock() defer c.rio.Unlock() return c.dev.Close() } // caller must hold wio or rio func (c *Conn) fd() int { return int(c.dev.Fd()) } func (c *Conn) Protocol() Protocol { return c.proto } // Features reports the feature flags negotiated between the kernel and // the FUSE library. See MountOption for how to influence features // activated. func (c *Conn) Features() InitFlags { return c.flags } // ReadRequest returns the next FUSE request from the kernel. // // Caller must call either Request.Respond or Request.RespondError in // a reasonable time. Caller must not retain Request after that call. func (c *Conn) ReadRequest() (Request, error) { m := getMessage(c) loop: c.rio.RLock() n, err := syscall.Read(c.fd(), m.buf) c.rio.RUnlock() if err == syscall.EINTR { // OSXFUSE sends EINTR to userspace when a request interrupt // completed before it got sent to userspace? goto loop } if err != nil && err != syscall.ENODEV { putMessage(m) return nil, err } if n <= 0 { putMessage(m) return nil, io.EOF } m.buf = m.buf[:n] if n < inHeaderSize { putMessage(m) return nil, errors.New("fuse: message too short") } // FreeBSD FUSE sends a short length in the header // for FUSE_INIT even though the actual read length is correct. if n == inHeaderSize+initInSize && m.hdr.Opcode == opInit && m.hdr.Len < uint32(n) { m.hdr.Len = uint32(n) } // OSXFUSE sometimes sends the wrong m.hdr.Len in a FUSE_WRITE message. if m.hdr.Len < uint32(n) && m.hdr.Len >= uint32(unsafe.Sizeof(writeIn{})) && m.hdr.Opcode == opWrite { m.hdr.Len = uint32(n) } if m.hdr.Len != uint32(n) { // prepare error message before returning m to pool err := fmt.Errorf("fuse: read %d opcode %d but expected %d", n, m.hdr.Opcode, m.hdr.Len) putMessage(m) return nil, err } m.off = inHeaderSize // Convert to data structures. // Do not trust kernel to hand us well-formed data. var req Request switch m.hdr.Opcode { default: Debug(noOpcode{Opcode: m.hdr.Opcode}) goto unrecognized case opLookup: buf := m.bytes() n := len(buf) if n == 0 || buf[n-1] != '\x00' { goto corrupt } req = &LookupRequest{ Header: m.Header(), Name: string(buf[:n-1]), } case opForget: in := (*forgetIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } req = &ForgetRequest{ Header: m.Header(), N: in.Nlookup, } case opGetattr: switch { case c.proto.LT(Protocol{7, 9}): req = &GetattrRequest{ Header: m.Header(), } default: in := (*getattrIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } req = &GetattrRequest{ Header: m.Header(), Flags: GetattrFlags(in.GetattrFlags), Handle: HandleID(in.Fh), } } case opSetattr: in := (*setattrIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } req = &SetattrRequest{ Header: m.Header(), Valid: SetattrValid(in.Valid), Handle: HandleID(in.Fh), Size: in.Size, Atime: time.Unix(int64(in.Atime), int64(in.AtimeNsec)), Mtime: time.Unix(int64(in.Mtime), int64(in.MtimeNsec)), Mode: fileMode(in.Mode), Uid: in.Uid, Gid: in.Gid, Bkuptime: in.BkupTime(), Chgtime: in.Chgtime(), Flags: in.Flags(), } case opReadlink: if len(m.bytes()) > 0 { goto corrupt } req = &ReadlinkRequest{ Header: m.Header(), } case opSymlink: // m.bytes() is "newName\0target\0" names := m.bytes() if len(names) == 0 || names[len(names)-1] != 0 { goto corrupt } i := bytes.IndexByte(names, '\x00') if i < 0 { goto corrupt } newName, target := names[0:i], names[i+1:len(names)-1] req = &SymlinkRequest{ Header: m.Header(), NewName: string(newName), Target: string(target), } case opLink: in := (*linkIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } newName := m.bytes()[unsafe.Sizeof(*in):] if len(newName) < 2 || newName[len(newName)-1] != 0 { goto corrupt } newName = newName[:len(newName)-1] req = &LinkRequest{ Header: m.Header(), OldNode: NodeID(in.Oldnodeid), NewName: string(newName), } case opMknod: size := mknodInSize(c.proto) if m.len() < size { goto corrupt } in := (*mknodIn)(m.data()) name := m.bytes()[size:] if len(name) < 2 || name[len(name)-1] != '\x00' { goto corrupt } name = name[:len(name)-1] r := &MknodRequest{ Header: m.Header(), Mode: fileMode(in.Mode), Rdev: in.Rdev, Name: string(name), } if c.proto.GE(Protocol{7, 12}) { r.Umask = fileMode(in.Umask) & os.ModePerm } req = r case opMkdir: size := mkdirInSize(c.proto) if m.len() < size { goto corrupt } in := (*mkdirIn)(m.data()) name := m.bytes()[size:] i := bytes.IndexByte(name, '\x00') if i < 0 { goto corrupt } r := &MkdirRequest{ Header: m.Header(), Name: string(name[:i]), // observed on Linux: mkdirIn.Mode & syscall.S_IFMT == 0, // and this causes fileMode to go into it's "no idea" // code branch; enforce type to directory Mode: fileMode((in.Mode &^ syscall.S_IFMT) | syscall.S_IFDIR), } if c.proto.GE(Protocol{7, 12}) { r.Umask = fileMode(in.Umask) & os.ModePerm } req = r case opUnlink, opRmdir: buf := m.bytes() n := len(buf) if n == 0 || buf[n-1] != '\x00' { goto corrupt } req = &RemoveRequest{ Header: m.Header(), Name: string(buf[:n-1]), Dir: m.hdr.Opcode == opRmdir, } case opRename: in := (*renameIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } newDirNodeID := NodeID(in.Newdir) oldNew := m.bytes()[unsafe.Sizeof(*in):] // oldNew should be "old\x00new\x00" if len(oldNew) < 4 { goto corrupt } if oldNew[len(oldNew)-1] != '\x00' { goto corrupt } i := bytes.IndexByte(oldNew, '\x00') if i < 0 { goto corrupt } oldName, newName := string(oldNew[:i]), string(oldNew[i+1:len(oldNew)-1]) req = &RenameRequest{ Header: m.Header(), NewDir: newDirNodeID, OldName: oldName, NewName: newName, } case opOpendir, opOpen: in := (*openIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } req = &OpenRequest{ Header: m.Header(), Dir: m.hdr.Opcode == opOpendir, Flags: openFlags(in.Flags), } case opRead, opReaddir: in := (*readIn)(m.data()) if m.len() < readInSize(c.proto) { goto corrupt } r := &ReadRequest{ Header: m.Header(), Dir: m.hdr.Opcode == opReaddir, Handle: HandleID(in.Fh), Offset: int64(in.Offset), Size: int(in.Size), } if c.proto.GE(Protocol{7, 9}) { r.Flags = ReadFlags(in.ReadFlags) r.LockOwner = LockOwner(in.LockOwner) r.FileFlags = openFlags(in.Flags) } req = r case opWrite: in := (*writeIn)(m.data()) if m.len() < writeInSize(c.proto) { goto corrupt } r := &WriteRequest{ Header: m.Header(), Handle: HandleID(in.Fh), Offset: int64(in.Offset), Flags: WriteFlags(in.WriteFlags), } if c.proto.GE(Protocol{7, 9}) { r.LockOwner = LockOwner(in.LockOwner) r.FileFlags = openFlags(in.Flags) } buf := m.bytes()[writeInSize(c.proto):] if uint32(len(buf)) < in.Size { goto corrupt } r.Data = buf req = r case opStatfs: req = &StatfsRequest{ Header: m.Header(), } case opRelease, opReleasedir: in := (*releaseIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } req = &ReleaseRequest{ Header: m.Header(), Dir: m.hdr.Opcode == opReleasedir, Handle: HandleID(in.Fh), Flags: openFlags(in.Flags), ReleaseFlags: ReleaseFlags(in.ReleaseFlags), LockOwner: LockOwner(in.LockOwner), } case opFsync, opFsyncdir: in := (*fsyncIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } req = &FsyncRequest{ Dir: m.hdr.Opcode == opFsyncdir, Header: m.Header(), Handle: HandleID(in.Fh), Flags: in.FsyncFlags, } case opSetxattr: in := (*setxattrIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } m.off += int(unsafe.Sizeof(*in)) name := m.bytes() i := bytes.IndexByte(name, '\x00') if i < 0 { goto corrupt } xattr := name[i+1:] if uint32(len(xattr)) < in.Size { goto corrupt } xattr = xattr[:in.Size] req = &SetxattrRequest{ Header: m.Header(), Flags: in.Flags, Position: in.position(), Name: string(name[:i]), Xattr: xattr, } case opGetxattr: in := (*getxattrIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } name := m.bytes()[unsafe.Sizeof(*in):] i := bytes.IndexByte(name, '\x00') if i < 0 { goto corrupt } req = &GetxattrRequest{ Header: m.Header(), Name: string(name[:i]), Size: in.Size, Position: in.position(), } case opListxattr: in := (*getxattrIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } req = &ListxattrRequest{ Header: m.Header(), Size: in.Size, Position: in.position(), } case opRemovexattr: buf := m.bytes() n := len(buf) if n == 0 || buf[n-1] != '\x00' { goto corrupt } req = &RemovexattrRequest{ Header: m.Header(), Name: string(buf[:n-1]), } case opFlush: in := (*flushIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } req = &FlushRequest{ Header: m.Header(), Handle: HandleID(in.Fh), LockOwner: LockOwner(in.LockOwner), } case opInit: in := (*initIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } req = &initRequest{ Header: m.Header(), Kernel: Protocol{in.Major, in.Minor}, MaxReadahead: in.MaxReadahead, Flags: InitFlags(in.Flags), } case opAccess: in := (*accessIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } req = &AccessRequest{ Header: m.Header(), Mask: in.Mask, } case opCreate: size := createInSize(c.proto) if m.len() < size { goto corrupt } in := (*createIn)(m.data()) name := m.bytes()[size:] i := bytes.IndexByte(name, '\x00') if i < 0 { goto corrupt } r := &CreateRequest{ Header: m.Header(), Flags: openFlags(in.Flags), Mode: fileMode(in.Mode), Name: string(name[:i]), } if c.proto.GE(Protocol{7, 12}) { r.Umask = fileMode(in.Umask) & os.ModePerm } req = r case opInterrupt: in := (*interruptIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } req = &InterruptRequest{ Header: m.Header(), IntrID: RequestID(in.Unique), } case opBmap: // bmap asks to map a byte offset within a file to a single // uint64. On Linux, it triggers only with blkdev fuse mounts, // that claim to be backed by an actual block device. FreeBSD // seems to send it for just any fuse mount, whether there's a // block device involved or not. goto unrecognized case opDestroy: req = &DestroyRequest{ Header: m.Header(), } // OS X case opSetvolname: panic("opSetvolname") case opGetxtimes: panic("opGetxtimes") case opExchange: in := (*exchangeIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } oldDirNodeID := NodeID(in.Olddir) newDirNodeID := NodeID(in.Newdir) oldNew := m.bytes()[unsafe.Sizeof(*in):] // oldNew should be "oldname\x00newname\x00" if len(oldNew) < 4 { goto corrupt } if oldNew[len(oldNew)-1] != '\x00' { goto corrupt } i := bytes.IndexByte(oldNew, '\x00') if i < 0 { goto corrupt } oldName, newName := string(oldNew[:i]), string(oldNew[i+1:len(oldNew)-1]) req = &ExchangeDataRequest{ Header: m.Header(), OldDir: oldDirNodeID, NewDir: newDirNodeID, OldName: oldName, NewName: newName, // TODO options } case opNotifyReply: req = &NotifyReply{ Header: m.Header(), msg: m, } case opPoll: in := (*pollIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } req = &PollRequest{ Header: m.Header(), Handle: HandleID(in.Fh), kh: in.Kh, Flags: PollFlags(in.Flags), Events: PollEvents(in.Events), } case opBatchForget: in := (*batchForgetIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } m.off += int(unsafe.Sizeof(*in)) items := make([]BatchForgetItem, 0, in.Count) for count := in.Count; count > 0; count-- { one := (*forgetOne)(m.data()) if m.len() < unsafe.Sizeof(*one) { goto corrupt } m.off += int(unsafe.Sizeof(*one)) items = append(items, BatchForgetItem{ NodeID: NodeID(one.NodeID), N: one.Nlookup, }) } req = &BatchForgetRequest{ Header: m.Header(), Forget: items, } case opSetlk, opSetlkw: in := (*lkIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } tmp := &LockRequest{ Header: m.Header(), Handle: HandleID(in.Fh), LockOwner: LockOwner(in.Owner), Lock: FileLock{ Start: in.Lk.Start, End: in.Lk.End, Type: LockType(in.Lk.Type), PID: int32(in.Lk.PID), }, LockFlags: LockFlags(in.LkFlags), } switch { case tmp.Lock.Type == LockUnlock: req = (*UnlockRequest)(tmp) case m.hdr.Opcode == opSetlkw: req = (*LockWaitRequest)(tmp) default: req = tmp } case opGetlk: in := (*lkIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } req = &QueryLockRequest{ Header: m.Header(), Handle: HandleID(in.Fh), LockOwner: LockOwner(in.Owner), Lock: FileLock{ Start: in.Lk.Start, End: in.Lk.End, Type: LockType(in.Lk.Type), // fuse.h claims this field is a uint32, but then the // spec talks about -1 as a value, and using int as // the C definition is pretty common. Make our API use // a signed integer. PID: int32(in.Lk.PID), }, LockFlags: LockFlags(in.LkFlags), } } return req, nil corrupt: Debug(malformedMessage{}) putMessage(m) return nil, fmt.Errorf("fuse: malformed message") unrecognized: // Unrecognized message. // Assume higher-level code will send a "no idea what you mean" error. req = &UnrecognizedRequest{ Header: m.Header(), Opcode: m.hdr.Opcode, } return req, nil } type bugShortKernelWrite struct { Written int64 Length int64 Error string Stack string } func (b bugShortKernelWrite) String() string { return fmt.Sprintf("short kernel write: written=%d/%d error=%q stack=\n%s", b.Written, b.Length, b.Error, b.Stack) } type bugKernelWriteError struct { Error string Stack string } func (b bugKernelWriteError) String() string { return fmt.Sprintf("kernel write error: error=%q stack=\n%s", b.Error, b.Stack) } // safe to call even with nil error func errorString(err error) string { if err == nil { return "" } return err.Error() } func (c *Conn) writeToKernel(msg []byte) error { out := (*outHeader)(unsafe.Pointer(&msg[0])) out.Len = uint32(len(msg)) c.wio.RLock() defer c.wio.RUnlock() nn, err := syscall.Write(c.fd(), msg) if err == nil && nn != len(msg) { Debug(bugShortKernelWrite{ Written: int64(nn), Length: int64(len(msg)), Error: errorString(err), Stack: stack(), }) } return err } func (c *Conn) respond(msg []byte) { if err := c.writeToKernel(msg); err != nil { Debug(bugKernelWriteError{ Error: errorString(err), Stack: stack(), }) } } type notCachedError struct{} func (notCachedError) Error() string { return "node not cached" } var _ ErrorNumber = notCachedError{} func (notCachedError) Errno() Errno { // Behave just like if the original syscall.ENOENT had been passed // straight through. return ENOENT } var ( ErrNotCached = notCachedError{} ) // sendNotify sends a notification to kernel. // // A returned ENOENT is translated to a friendlier error. func (c *Conn) sendNotify(msg []byte) error { switch err := c.writeToKernel(msg); err { case syscall.ENOENT: return ErrNotCached default: return err } } // InvalidateNode invalidates the kernel cache of the attributes and a // range of the data of a node. // // Giving offset 0 and size -1 means all data. To invalidate just the // attributes, give offset 0 and size 0. // // Returns ErrNotCached if the kernel is not currently caching the // node. func (c *Conn) InvalidateNode(nodeID NodeID, off int64, size int64) error { buf := newBuffer(unsafe.Sizeof(notifyInvalInodeOut{})) h := (*outHeader)(unsafe.Pointer(&buf[0])) // h.Unique is 0 h.Error = notifyCodeInvalInode out := (*notifyInvalInodeOut)(buf.alloc(unsafe.Sizeof(notifyInvalInodeOut{}))) out.Ino = uint64(nodeID) out.Off = off out.Len = size return c.sendNotify(buf) } // InvalidateEntry invalidates the kernel cache of the directory entry // identified by parent directory node ID and entry basename. // // Kernel may or may not cache directory listings. To invalidate // those, use InvalidateNode to invalidate all of the data for a // directory. (As of 2015-06, Linux FUSE does not cache directory // listings.) // // Returns ErrNotCached if the kernel is not currently caching the // node. func (c *Conn) InvalidateEntry(parent NodeID, name string) error { const maxUint32 = ^uint32(0) if uint64(len(name)) > uint64(maxUint32) { // very unlikely, but we don't want to silently truncate return syscall.ENAMETOOLONG } buf := newBuffer(unsafe.Sizeof(notifyInvalEntryOut{}) + uintptr(len(name)) + 1) h := (*outHeader)(unsafe.Pointer(&buf[0])) // h.Unique is 0 h.Error = notifyCodeInvalEntry out := (*notifyInvalEntryOut)(buf.alloc(unsafe.Sizeof(notifyInvalEntryOut{}))) out.Parent = uint64(parent) out.Namelen = uint32(len(name)) buf = append(buf, name...) buf = append(buf, '\x00') return c.sendNotify(buf) } func (c *Conn) NotifyStore(nodeID NodeID, offset uint64, data []byte) error { buf := newBuffer(unsafe.Sizeof(notifyStoreOut{}) + uintptr(len(data))) h := (*outHeader)(unsafe.Pointer(&buf[0])) // h.Unique is 0 h.Error = notifyCodeStore out := (*notifyStoreOut)(buf.alloc(unsafe.Sizeof(notifyStoreOut{}))) out.Nodeid = uint64(nodeID) out.Offset = offset out.Size = uint32(len(data)) buf = append(buf, data...) return c.sendNotify(buf) } type NotifyRetrieval struct { // we may want fields later, so don't let callers know it's the // empty struct _ struct{} } func (n *NotifyRetrieval) Finish(r *NotifyReply) []byte { m := r.msg defer putMessage(m) in := (*notifyRetrieveIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { Debug(malformedMessage{}) return nil } m.off += int(unsafe.Sizeof(*in)) buf := m.bytes() if uint32(len(buf)) < in.Size { Debug(malformedMessage{}) return nil } data := make([]byte, in.Size) copy(data, buf) return data } func (c *Conn) NotifyRetrieve(notificationID RequestID, nodeID NodeID, offset uint64, size uint32) (*NotifyRetrieval, error) { // notificationID may collide with kernel-chosen requestIDs, it's // up to the caller to branch based on the opCode. buf := newBuffer(unsafe.Sizeof(notifyRetrieveOut{})) h := (*outHeader)(unsafe.Pointer(&buf[0])) // h.Unique is 0 h.Error = notifyCodeRetrieve out := (*notifyRetrieveOut)(buf.alloc(unsafe.Sizeof(notifyRetrieveOut{}))) out.NotifyUnique = uint64(notificationID) out.Nodeid = uint64(nodeID) out.Offset = offset // kernel constrains size to maxWrite for us out.Size = size if err := c.sendNotify(buf); err != nil { return nil, err } r := &NotifyRetrieval{} return r, nil } // NotifyPollWakeup sends a notification to the kernel to wake up all // clients waiting on this node. Wakeup is a value from a PollRequest // for a Handle or a Node currently alive (Forget has not been called // on it). func (c *Conn) NotifyPollWakeup(wakeup PollWakeup) error { if wakeup.kh == 0 { // likely somebody ignored the comma-ok return return nil } buf := newBuffer(unsafe.Sizeof(notifyPollWakeupOut{})) h := (*outHeader)(unsafe.Pointer(&buf[0])) // h.Unique is 0 h.Error = notifyCodePoll out := (*notifyPollWakeupOut)(buf.alloc(unsafe.Sizeof(notifyPollWakeupOut{}))) out.Kh = wakeup.kh return c.sendNotify(buf) } // LockOwner is a file-local opaque identifier assigned by the kernel // to identify the owner of a particular lock. type LockOwner uint64 func (o LockOwner) String() string { if o == 0 { return "0" } return fmt.Sprintf("%016x", uint64(o)) } // An initRequest is the first request sent on a FUSE file system. type initRequest struct { Header `json:"-"` Kernel Protocol // Maximum readahead in bytes that the kernel plans to use. MaxReadahead uint32 Flags InitFlags } var _ Request = (*initRequest)(nil) func (r *initRequest) String() string { return fmt.Sprintf("Init [%v] %v ra=%d fl=%v", &r.Header, r.Kernel, r.MaxReadahead, r.Flags) } type UnrecognizedRequest struct { Header `json:"-"` Opcode uint32 } var _ Request = (*UnrecognizedRequest)(nil) func (r *UnrecognizedRequest) String() string { return fmt.Sprintf("Unrecognized [%v] opcode=%d", &r.Header, r.Opcode) } // An initResponse is the response to an initRequest. type initResponse struct { Library Protocol // Maximum readahead in bytes that the kernel can use. Ignored if // greater than initRequest.MaxReadahead. MaxReadahead uint32 Flags InitFlags // Maximum number of outstanding background requests MaxBackground uint16 // Number of background requests at which congestion starts CongestionThreshold uint16 // Maximum size of a single write operation. // Linux enforces a minimum of 4 KiB. MaxWrite uint32 } func (r *initResponse) String() string { return fmt.Sprintf("Init %v ra=%d fl=%v maxbg=%d cg=%d w=%d", r.Library, r.MaxReadahead, r.Flags, r.MaxBackground, r.CongestionThreshold, r.MaxWrite) } // Respond replies to the request with the given response. func (r *initRequest) Respond(resp *initResponse) { buf := newBuffer(unsafe.Sizeof(initOut{})) out := (*initOut)(buf.alloc(unsafe.Sizeof(initOut{}))) out.Major = resp.Library.Major out.Minor = resp.Library.Minor out.MaxReadahead = resp.MaxReadahead out.Flags = uint32(resp.Flags) out.MaxBackground = resp.MaxBackground out.CongestionThreshold = resp.CongestionThreshold out.MaxWrite = resp.MaxWrite // MaxWrite larger than our receive buffer would just lead to // errors on large writes. if out.MaxWrite > maxWrite { out.MaxWrite = maxWrite } r.respond(buf) } // A StatfsRequest requests information about the mounted file system. type StatfsRequest struct { Header `json:"-"` } var _ Request = (*StatfsRequest)(nil) func (r *StatfsRequest) String() string { return fmt.Sprintf("Statfs [%s]", &r.Header) } // Respond replies to the request with the given response. func (r *StatfsRequest) Respond(resp *StatfsResponse) { buf := newBuffer(unsafe.Sizeof(statfsOut{})) out := (*statfsOut)(buf.alloc(unsafe.Sizeof(statfsOut{}))) out.St = kstatfs{ Blocks: resp.Blocks, Bfree: resp.Bfree, Bavail: resp.Bavail, Files: resp.Files, Ffree: resp.Ffree, Bsize: resp.Bsize, Namelen: resp.Namelen, Frsize: resp.Frsize, } r.respond(buf) } // A StatfsResponse is the response to a StatfsRequest. type StatfsResponse struct { Blocks uint64 // Total data blocks in file system. Bfree uint64 // Free blocks in file system. Bavail uint64 // Free blocks in file system if you're not root. Files uint64 // Total files in file system. Ffree uint64 // Free files in file system. Bsize uint32 // Block size Namelen uint32 // Maximum file name length? Frsize uint32 // Fragment size, smallest addressable data size in the file system. } func (r *StatfsResponse) String() string { return fmt.Sprintf("Statfs blocks=%d/%d/%d files=%d/%d bsize=%d frsize=%d namelen=%d", r.Bavail, r.Bfree, r.Blocks, r.Ffree, r.Files, r.Bsize, r.Frsize, r.Namelen, ) } // An AccessRequest asks whether the file can be accessed // for the purpose specified by the mask. type AccessRequest struct { Header `json:"-"` Mask uint32 } var _ Request = (*AccessRequest)(nil) func (r *AccessRequest) String() string { return fmt.Sprintf("Access [%s] mask=%#x", &r.Header, r.Mask) } // Respond replies to the request indicating that access is allowed. // To deny access, use RespondError. func (r *AccessRequest) Respond() { buf := newBuffer(0) r.respond(buf) } // An Attr is the metadata for a single file or directory. type Attr struct { Valid time.Duration // how long Attr can be cached Inode uint64 // inode number Size uint64 // size in bytes Blocks uint64 // size in 512-byte units Atime time.Time // time of last access Mtime time.Time // time of last modification Ctime time.Time // time of last inode change Crtime time.Time // time of creation (OS X only) Mode os.FileMode // file mode Nlink uint32 // number of links (usually 1) Uid uint32 // owner uid Gid uint32 // group gid Rdev uint32 // device numbers Flags uint32 // chflags(2) flags (OS X only) BlockSize uint32 // preferred blocksize for filesystem I/O } func (a Attr) String() string { return fmt.Sprintf("valid=%v ino=%v size=%d mode=%v", a.Valid, a.Inode, a.Size, a.Mode) } func unixTime(t time.Time) (sec uint64, nsec uint32) { nano := t.UnixNano() sec = uint64(nano / 1e9) nsec = uint32(nano % 1e9) return } func (a *Attr) attr(out *attr, proto Protocol) { out.Ino = a.Inode out.Size = a.Size out.Blocks = a.Blocks out.Atime, out.AtimeNsec = unixTime(a.Atime) out.Mtime, out.MtimeNsec = unixTime(a.Mtime) out.Ctime, out.CtimeNsec = unixTime(a.Ctime) out.SetCrtime(unixTime(a.Crtime)) out.Mode = uint32(a.Mode) & 0o777 switch { default: out.Mode |= syscall.S_IFREG case a.Mode&os.ModeDir != 0: out.Mode |= syscall.S_IFDIR case a.Mode&os.ModeDevice != 0: if a.Mode&os.ModeCharDevice != 0 { out.Mode |= syscall.S_IFCHR } else { out.Mode |= syscall.S_IFBLK } case a.Mode&os.ModeNamedPipe != 0: out.Mode |= syscall.S_IFIFO case a.Mode&os.ModeSymlink != 0: out.Mode |= syscall.S_IFLNK case a.Mode&os.ModeSocket != 0: out.Mode |= syscall.S_IFSOCK } if a.Mode&os.ModeSetuid != 0 { out.Mode |= syscall.S_ISUID } if a.Mode&os.ModeSetgid != 0 { out.Mode |= syscall.S_ISGID } out.Nlink = a.Nlink out.Uid = a.Uid out.Gid = a.Gid out.Rdev = a.Rdev out.SetFlags(a.Flags) if proto.GE(Protocol{7, 9}) { out.Blksize = a.BlockSize } } // A GetattrRequest asks for the metadata for the file denoted by r.Node. type GetattrRequest struct { Header `json:"-"` Flags GetattrFlags Handle HandleID } var _ Request = (*GetattrRequest)(nil) func (r *GetattrRequest) String() string { return fmt.Sprintf("Getattr [%s] %v fl=%v", &r.Header, r.Handle, r.Flags) } // Respond replies to the request with the given response. func (r *GetattrRequest) Respond(resp *GetattrResponse) { size := attrOutSize(r.Header.Conn.proto) buf := newBuffer(size) out := (*attrOut)(buf.alloc(size)) out.AttrValid = uint64(resp.Attr.Valid / time.Second) out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) resp.Attr.attr(&out.Attr, r.Header.Conn.proto) r.respond(buf) } // A GetattrResponse is the response to a GetattrRequest. type GetattrResponse struct { Attr Attr // file attributes } func (r *GetattrResponse) String() string { return fmt.Sprintf("Getattr %v", r.Attr) } // A GetxattrRequest asks for the extended attributes associated with r.Node. type GetxattrRequest struct { Header `json:"-"` // Maximum size to return. Size uint32 // Name of the attribute requested. Name string // Offset within extended attributes. // // Only valid for OS X, and then only with the resource fork // attribute. Position uint32 } var _ Request = (*GetxattrRequest)(nil) func (r *GetxattrRequest) String() string { return fmt.Sprintf("Getxattr [%s] %q %d @%d", &r.Header, r.Name, r.Size, r.Position) } // Respond replies to the request with the given response. func (r *GetxattrRequest) Respond(resp *GetxattrResponse) { if r.Size == 0 { buf := newBuffer(unsafe.Sizeof(getxattrOut{})) out := (*getxattrOut)(buf.alloc(unsafe.Sizeof(getxattrOut{}))) out.Size = uint32(len(resp.Xattr)) r.respond(buf) } else { buf := newBuffer(uintptr(len(resp.Xattr))) buf = append(buf, resp.Xattr...) r.respond(buf) } } // A GetxattrResponse is the response to a GetxattrRequest. type GetxattrResponse struct { Xattr []byte } func (r *GetxattrResponse) String() string { return fmt.Sprintf("Getxattr %q", r.Xattr) } // A ListxattrRequest asks to list the extended attributes associated with r.Node. type ListxattrRequest struct { Header `json:"-"` Size uint32 // maximum size to return Position uint32 // offset within attribute list } var _ Request = (*ListxattrRequest)(nil) func (r *ListxattrRequest) String() string { return fmt.Sprintf("Listxattr [%s] %d @%d", &r.Header, r.Size, r.Position) } // Respond replies to the request with the given response. func (r *ListxattrRequest) Respond(resp *ListxattrResponse) { if r.Size == 0 { buf := newBuffer(unsafe.Sizeof(getxattrOut{})) out := (*getxattrOut)(buf.alloc(unsafe.Sizeof(getxattrOut{}))) out.Size = uint32(len(resp.Xattr)) r.respond(buf) } else { buf := newBuffer(uintptr(len(resp.Xattr))) buf = append(buf, resp.Xattr...) r.respond(buf) } } // A ListxattrResponse is the response to a ListxattrRequest. type ListxattrResponse struct { Xattr []byte } func (r *ListxattrResponse) String() string { return fmt.Sprintf("Listxattr %q", r.Xattr) } // Append adds an extended attribute name to the response. func (r *ListxattrResponse) Append(names ...string) { for _, name := range names { r.Xattr = append(r.Xattr, name...) r.Xattr = append(r.Xattr, '\x00') } } // A RemovexattrRequest asks to remove an extended attribute associated with r.Node. type RemovexattrRequest struct { Header `json:"-"` Name string // name of extended attribute } var _ Request = (*RemovexattrRequest)(nil) func (r *RemovexattrRequest) String() string { return fmt.Sprintf("Removexattr [%s] %q", &r.Header, r.Name) } // Respond replies to the request, indicating that the attribute was removed. func (r *RemovexattrRequest) Respond() { buf := newBuffer(0) r.respond(buf) } // A SetxattrRequest asks to set an extended attribute associated with a file. type SetxattrRequest struct { Header `json:"-"` // Flags can make the request fail if attribute does/not already // exist. Unfortunately, the constants are platform-specific and // not exposed by Go1.2. Look for XATTR_CREATE, XATTR_REPLACE. // // TODO improve this later // // TODO XATTR_CREATE and exist -> EEXIST // // TODO XATTR_REPLACE and not exist -> ENODATA Flags uint32 // Offset within extended attributes. // // Only valid for OS X, and then only with the resource fork // attribute. Position uint32 Name string Xattr []byte } var _ Request = (*SetxattrRequest)(nil) func trunc(b []byte, max int) ([]byte, string) { if len(b) > max { return b[:max], "..." } return b, "" } func (r *SetxattrRequest) String() string { xattr, tail := trunc(r.Xattr, 16) return fmt.Sprintf("Setxattr [%s] %q %q%s fl=%v @%#x", &r.Header, r.Name, xattr, tail, r.Flags, r.Position) } // Respond replies to the request, indicating that the extended attribute was set. func (r *SetxattrRequest) Respond() { buf := newBuffer(0) r.respond(buf) } // A LookupRequest asks to look up the given name in the directory named by r.Node. type LookupRequest struct { Header `json:"-"` Name string } var _ Request = (*LookupRequest)(nil) func (r *LookupRequest) String() string { return fmt.Sprintf("Lookup [%s] %q", &r.Header, r.Name) } // Respond replies to the request with the given response. func (r *LookupRequest) Respond(resp *LookupResponse) { size := entryOutSize(r.Header.Conn.proto) buf := newBuffer(size) out := (*entryOut)(buf.alloc(size)) out.Nodeid = uint64(resp.Node) out.Generation = resp.Generation out.EntryValid = uint64(resp.EntryValid / time.Second) out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond) out.AttrValid = uint64(resp.Attr.Valid / time.Second) out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) resp.Attr.attr(&out.Attr, r.Header.Conn.proto) r.respond(buf) } // A LookupResponse is the response to a LookupRequest. type LookupResponse struct { Node NodeID Generation uint64 EntryValid time.Duration Attr Attr } func (r *LookupResponse) string() string { return fmt.Sprintf("%v gen=%d valid=%v attr={%v}", r.Node, r.Generation, r.EntryValid, r.Attr) } func (r *LookupResponse) String() string { return fmt.Sprintf("Lookup %s", r.string()) } // An OpenRequest asks to open a file or directory type OpenRequest struct { Header `json:"-"` Dir bool // is this Opendir? Flags OpenFlags } var _ Request = (*OpenRequest)(nil) func (r *OpenRequest) String() string { return fmt.Sprintf("Open [%s] dir=%v fl=%v", &r.Header, r.Dir, r.Flags) } // Respond replies to the request with the given response. func (r *OpenRequest) Respond(resp *OpenResponse) { buf := newBuffer(unsafe.Sizeof(openOut{})) out := (*openOut)(buf.alloc(unsafe.Sizeof(openOut{}))) out.Fh = uint64(resp.Handle) out.OpenFlags = uint32(resp.Flags) r.respond(buf) } // A OpenResponse is the response to a OpenRequest. type OpenResponse struct { Handle HandleID Flags OpenResponseFlags } func (r *OpenResponse) string() string { return fmt.Sprintf("%v fl=%v", r.Handle, r.Flags) } func (r *OpenResponse) String() string { return fmt.Sprintf("Open %s", r.string()) } // A CreateRequest asks to create and open a file (not a directory). type CreateRequest struct { Header `json:"-"` Name string Flags OpenFlags Mode os.FileMode // Umask of the request. Not supported on OS X. Umask os.FileMode } var _ Request = (*CreateRequest)(nil) func (r *CreateRequest) String() string { return fmt.Sprintf("Create [%s] %q fl=%v mode=%v umask=%v", &r.Header, r.Name, r.Flags, r.Mode, r.Umask) } // Respond replies to the request with the given response. func (r *CreateRequest) Respond(resp *CreateResponse) { eSize := entryOutSize(r.Header.Conn.proto) buf := newBuffer(eSize + unsafe.Sizeof(openOut{})) e := (*entryOut)(buf.alloc(eSize)) e.Nodeid = uint64(resp.Node) e.Generation = resp.Generation e.EntryValid = uint64(resp.EntryValid / time.Second) e.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond) e.AttrValid = uint64(resp.Attr.Valid / time.Second) e.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) resp.Attr.attr(&e.Attr, r.Header.Conn.proto) o := (*openOut)(buf.alloc(unsafe.Sizeof(openOut{}))) o.Fh = uint64(resp.Handle) o.OpenFlags = uint32(resp.Flags) r.respond(buf) } // A CreateResponse is the response to a CreateRequest. // It describes the created node and opened handle. type CreateResponse struct { LookupResponse OpenResponse } func (r *CreateResponse) String() string { return fmt.Sprintf("Create {%s} {%s}", r.LookupResponse.string(), r.OpenResponse.string()) } // A MkdirRequest asks to create (but not open) a directory. type MkdirRequest struct { Header `json:"-"` Name string Mode os.FileMode // Umask of the request. Not supported on OS X. Umask os.FileMode } var _ Request = (*MkdirRequest)(nil) func (r *MkdirRequest) String() string { return fmt.Sprintf("Mkdir [%s] %q mode=%v umask=%v", &r.Header, r.Name, r.Mode, r.Umask) } // Respond replies to the request with the given response. func (r *MkdirRequest) Respond(resp *MkdirResponse) { size := entryOutSize(r.Header.Conn.proto) buf := newBuffer(size) out := (*entryOut)(buf.alloc(size)) out.Nodeid = uint64(resp.Node) out.Generation = resp.Generation out.EntryValid = uint64(resp.EntryValid / time.Second) out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond) out.AttrValid = uint64(resp.Attr.Valid / time.Second) out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) resp.Attr.attr(&out.Attr, r.Header.Conn.proto) r.respond(buf) } // A MkdirResponse is the response to a MkdirRequest. type MkdirResponse struct { LookupResponse } func (r *MkdirResponse) String() string { return fmt.Sprintf("Mkdir %v", r.LookupResponse.string()) } // A ReadRequest asks to read from an open file. type ReadRequest struct { Header `json:"-"` Dir bool // is this Readdir? Handle HandleID Offset int64 Size int Flags ReadFlags LockOwner LockOwner FileFlags OpenFlags } var _ Request = (*ReadRequest)(nil) func (r *ReadRequest) String() string { return fmt.Sprintf("Read [%s] %v %d @%#x dir=%v fl=%v owner=%v ffl=%v", &r.Header, r.Handle, r.Size, r.Offset, r.Dir, r.Flags, r.LockOwner, r.FileFlags) } // Respond replies to the request with the given response. func (r *ReadRequest) Respond(resp *ReadResponse) { buf := newBuffer(uintptr(len(resp.Data))) buf = append(buf, resp.Data...) r.respond(buf) } // A ReadResponse is the response to a ReadRequest. type ReadResponse struct { Data []byte } func (r *ReadResponse) String() string { return fmt.Sprintf("Read %d", len(r.Data)) } type jsonReadResponse struct { Len uint64 } func (r *ReadResponse) MarshalJSON() ([]byte, error) { j := jsonReadResponse{ Len: uint64(len(r.Data)), } return json.Marshal(j) } // A ReleaseRequest asks to release (close) an open file handle. type ReleaseRequest struct { Header `json:"-"` Dir bool // is this Releasedir? Handle HandleID Flags OpenFlags // flags from OpenRequest ReleaseFlags ReleaseFlags LockOwner LockOwner } var _ Request = (*ReleaseRequest)(nil) func (r *ReleaseRequest) String() string { return fmt.Sprintf("Release [%s] %v fl=%v rfl=%v owner=%v", &r.Header, r.Handle, r.Flags, r.ReleaseFlags, r.LockOwner) } // Respond replies to the request, indicating that the handle has been released. func (r *ReleaseRequest) Respond() { buf := newBuffer(0) r.respond(buf) } // A DestroyRequest is sent by the kernel when unmounting the file system. // No more requests will be received after this one, but it should still be // responded to. type DestroyRequest struct { Header `json:"-"` } var _ Request = (*DestroyRequest)(nil) func (r *DestroyRequest) String() string { return fmt.Sprintf("Destroy [%s]", &r.Header) } // Respond replies to the request. func (r *DestroyRequest) Respond() { buf := newBuffer(0) r.respond(buf) } // A ForgetRequest is sent by the kernel when forgetting about r.Node // as returned by r.N lookup requests. type ForgetRequest struct { Header `json:"-"` N uint64 } var _ Request = (*ForgetRequest)(nil) func (r *ForgetRequest) String() string { return fmt.Sprintf("Forget [%s] %d", &r.Header, r.N) } // Respond replies to the request, indicating that the forgetfulness has been recorded. func (r *ForgetRequest) Respond() { // Don't reply to forget messages. r.noResponse() } type BatchForgetItem struct { NodeID NodeID N uint64 } type BatchForgetRequest struct { Header `json:"-"` Forget []BatchForgetItem } var _ Request = (*BatchForgetRequest)(nil) func (r *BatchForgetRequest) String() string { b := new(strings.Builder) fmt.Fprintf(b, "BatchForget [%s]", &r.Header) if len(r.Forget) == 0 { b.WriteString(" empty") } else { for _, item := range r.Forget { fmt.Fprintf(b, " %dx%d", item.NodeID, item.N) } } return b.String() } // Respond replies to the request, indicating that the forgetfulness has been recorded. func (r *BatchForgetRequest) Respond() { // Don't reply to forget messages. r.noResponse() } // A Dirent represents a single directory entry. type Dirent struct { // Inode this entry names. Inode uint64 // Type of the entry, for example DT_File. // // Setting this is optional. The zero value (DT_Unknown) means // callers will just need to do a Getattr when the type is // needed. Providing a type can speed up operations // significantly. Type DirentType // Name of the entry Name string } // Type of an entry in a directory listing. type DirentType uint32 const ( // These don't quite match os.FileMode; especially there's an // explicit unknown, instead of zero value meaning file. They // are also not quite syscall.DT_*; nothing says the FUSE // protocol follows those, and even if they were, we don't // want each fs to fiddle with syscall. // The shift by 12 is hardcoded in the FUSE userspace // low-level C library, so it's safe here. DT_Unknown DirentType = 0 DT_Socket DirentType = syscall.S_IFSOCK >> 12 DT_Link DirentType = syscall.S_IFLNK >> 12 DT_File DirentType = syscall.S_IFREG >> 12 DT_Block DirentType = syscall.S_IFBLK >> 12 DT_Dir DirentType = syscall.S_IFDIR >> 12 DT_Char DirentType = syscall.S_IFCHR >> 12 DT_FIFO DirentType = syscall.S_IFIFO >> 12 ) func (t DirentType) String() string { switch t { case DT_Unknown: return "unknown" case DT_Socket: return "socket" case DT_Link: return "link" case DT_File: return "file" case DT_Block: return "block" case DT_Dir: return "dir" case DT_Char: return "char" case DT_FIFO: return "fifo" } return "invalid" } // AppendDirent appends the encoded form of a directory entry to data // and returns the resulting slice. func AppendDirent(data []byte, dir Dirent) []byte { de := dirent{ Ino: dir.Inode, Namelen: uint32(len(dir.Name)), Type: uint32(dir.Type), } de.Off = uint64(len(data) + direntSize + (len(dir.Name)+7)&^7) data = append(data, (*[direntSize]byte)(unsafe.Pointer(&de))[:]...) data = append(data, dir.Name...) n := direntSize + uintptr(len(dir.Name)) if n%8 != 0 { var pad [8]byte data = append(data, pad[:8-n%8]...) } return data } // A WriteRequest asks to write to an open file. type WriteRequest struct { Header Handle HandleID Offset int64 Data []byte Flags WriteFlags LockOwner LockOwner FileFlags OpenFlags } var _ Request = (*WriteRequest)(nil) func (r *WriteRequest) String() string { return fmt.Sprintf("Write [%s] %v %d @%d fl=%v owner=%v ffl=%v", &r.Header, r.Handle, len(r.Data), r.Offset, r.Flags, r.LockOwner, r.FileFlags) } type jsonWriteRequest struct { Handle HandleID Offset int64 Len uint64 Flags WriteFlags } func (r *WriteRequest) MarshalJSON() ([]byte, error) { j := jsonWriteRequest{ Handle: r.Handle, Offset: r.Offset, Len: uint64(len(r.Data)), Flags: r.Flags, } return json.Marshal(j) } // Respond replies to the request with the given response. func (r *WriteRequest) Respond(resp *WriteResponse) { buf := newBuffer(unsafe.Sizeof(writeOut{})) out := (*writeOut)(buf.alloc(unsafe.Sizeof(writeOut{}))) out.Size = uint32(resp.Size) r.respond(buf) } // A WriteResponse replies to a write indicating how many bytes were written. type WriteResponse struct { Size int } func (r *WriteResponse) String() string { return fmt.Sprintf("Write %d", r.Size) } // A SetattrRequest asks to change one or more attributes associated with a file, // as indicated by Valid. type SetattrRequest struct { Header `json:"-"` Valid SetattrValid Handle HandleID Size uint64 Atime time.Time Mtime time.Time // Mode is the file mode to set (when valid). // // The type of the node (as in os.ModeType, os.ModeDir etc) is not // guaranteed to be sent by the kernel, in which case // os.ModeIrregular will be set. Mode os.FileMode Uid uint32 Gid uint32 // OS X only Bkuptime time.Time Chgtime time.Time Crtime time.Time Flags uint32 // see chflags(2) } var _ Request = (*SetattrRequest)(nil) func (r *SetattrRequest) String() string { var buf bytes.Buffer fmt.Fprintf(&buf, "Setattr [%s]", &r.Header) if r.Valid.Mode() { fmt.Fprintf(&buf, " mode=%v", r.Mode) } if r.Valid.Uid() { fmt.Fprintf(&buf, " uid=%d", r.Uid) } if r.Valid.Gid() { fmt.Fprintf(&buf, " gid=%d", r.Gid) } if r.Valid.Size() { fmt.Fprintf(&buf, " size=%d", r.Size) } if r.Valid.Atime() { fmt.Fprintf(&buf, " atime=%v", r.Atime) } if r.Valid.AtimeNow() { fmt.Fprintf(&buf, " atime=now") } if r.Valid.Mtime() { fmt.Fprintf(&buf, " mtime=%v", r.Mtime) } if r.Valid.MtimeNow() { fmt.Fprintf(&buf, " mtime=now") } if r.Valid.Handle() { fmt.Fprintf(&buf, " handle=%v", r.Handle) } else { fmt.Fprintf(&buf, " handle=INVALID-%v", r.Handle) } if r.Valid.LockOwner() { fmt.Fprintf(&buf, " lockowner") } if r.Valid.Crtime() { fmt.Fprintf(&buf, " crtime=%v", r.Crtime) } if r.Valid.Chgtime() { fmt.Fprintf(&buf, " chgtime=%v", r.Chgtime) } if r.Valid.Bkuptime() { fmt.Fprintf(&buf, " bkuptime=%v", r.Bkuptime) } if r.Valid.Flags() { fmt.Fprintf(&buf, " flags=%v", r.Flags) } return buf.String() } // Respond replies to the request with the given response, // giving the updated attributes. func (r *SetattrRequest) Respond(resp *SetattrResponse) { size := attrOutSize(r.Header.Conn.proto) buf := newBuffer(size) out := (*attrOut)(buf.alloc(size)) out.AttrValid = uint64(resp.Attr.Valid / time.Second) out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) resp.Attr.attr(&out.Attr, r.Header.Conn.proto) r.respond(buf) } // A SetattrResponse is the response to a SetattrRequest. type SetattrResponse struct { Attr Attr // file attributes } func (r *SetattrResponse) String() string { return fmt.Sprintf("Setattr %v", r.Attr) } // A FlushRequest asks for the current state of an open file to be flushed // to storage, as when a file descriptor is being closed. A single opened Handle // may receive multiple FlushRequests over its lifetime. type FlushRequest struct { Header `json:"-"` Handle HandleID // Deprecated: Unused since 2006. Flags uint32 LockOwner LockOwner } var _ Request = (*FlushRequest)(nil) func (r *FlushRequest) String() string { return fmt.Sprintf("Flush [%s] %v fl=%#x owner=%v", &r.Header, r.Handle, r.Flags, r.LockOwner) } // Respond replies to the request, indicating that the flush succeeded. func (r *FlushRequest) Respond() { buf := newBuffer(0) r.respond(buf) } // A RemoveRequest asks to remove a file or directory from the // directory r.Node. type RemoveRequest struct { Header `json:"-"` Name string // name of the entry to remove Dir bool // is this rmdir? } var _ Request = (*RemoveRequest)(nil) func (r *RemoveRequest) String() string { return fmt.Sprintf("Remove [%s] %q dir=%v", &r.Header, r.Name, r.Dir) } // Respond replies to the request, indicating that the file was removed. func (r *RemoveRequest) Respond() { buf := newBuffer(0) r.respond(buf) } // A SymlinkRequest is a request to create a symlink making NewName point to Target. type SymlinkRequest struct { Header `json:"-"` NewName, Target string } var _ Request = (*SymlinkRequest)(nil) func (r *SymlinkRequest) String() string { return fmt.Sprintf("Symlink [%s] from %q to target %q", &r.Header, r.NewName, r.Target) } // Respond replies to the request, indicating that the symlink was created. func (r *SymlinkRequest) Respond(resp *SymlinkResponse) { size := entryOutSize(r.Header.Conn.proto) buf := newBuffer(size) out := (*entryOut)(buf.alloc(size)) out.Nodeid = uint64(resp.Node) out.Generation = resp.Generation out.EntryValid = uint64(resp.EntryValid / time.Second) out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond) out.AttrValid = uint64(resp.Attr.Valid / time.Second) out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) resp.Attr.attr(&out.Attr, r.Header.Conn.proto) r.respond(buf) } // A SymlinkResponse is the response to a SymlinkRequest. type SymlinkResponse struct { LookupResponse } func (r *SymlinkResponse) String() string { return fmt.Sprintf("Symlink %v", r.LookupResponse.string()) } // A ReadlinkRequest is a request to read a symlink's target. type ReadlinkRequest struct { Header `json:"-"` } var _ Request = (*ReadlinkRequest)(nil) func (r *ReadlinkRequest) String() string { return fmt.Sprintf("Readlink [%s]", &r.Header) } func (r *ReadlinkRequest) Respond(target string) { buf := newBuffer(uintptr(len(target))) buf = append(buf, target...) r.respond(buf) } // A LinkRequest is a request to create a hard link. type LinkRequest struct { Header `json:"-"` OldNode NodeID NewName string } var _ Request = (*LinkRequest)(nil) func (r *LinkRequest) String() string { return fmt.Sprintf("Link [%s] node %d to %q", &r.Header, r.OldNode, r.NewName) } func (r *LinkRequest) Respond(resp *LookupResponse) { size := entryOutSize(r.Header.Conn.proto) buf := newBuffer(size) out := (*entryOut)(buf.alloc(size)) out.Nodeid = uint64(resp.Node) out.Generation = resp.Generation out.EntryValid = uint64(resp.EntryValid / time.Second) out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond) out.AttrValid = uint64(resp.Attr.Valid / time.Second) out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) resp.Attr.attr(&out.Attr, r.Header.Conn.proto) r.respond(buf) } // A RenameRequest is a request to rename a file. type RenameRequest struct { Header `json:"-"` NewDir NodeID OldName, NewName string } var _ Request = (*RenameRequest)(nil) func (r *RenameRequest) String() string { return fmt.Sprintf("Rename [%s] from %q to dirnode %v %q", &r.Header, r.OldName, r.NewDir, r.NewName) } func (r *RenameRequest) Respond() { buf := newBuffer(0) r.respond(buf) } type MknodRequest struct { Header `json:"-"` Name string Mode os.FileMode Rdev uint32 // Umask of the request. Not supported on OS X. Umask os.FileMode } var _ Request = (*MknodRequest)(nil) func (r *MknodRequest) String() string { return fmt.Sprintf("Mknod [%s] Name %q mode=%v umask=%v rdev=%d", &r.Header, r.Name, r.Mode, r.Umask, r.Rdev) } func (r *MknodRequest) Respond(resp *LookupResponse) { size := entryOutSize(r.Header.Conn.proto) buf := newBuffer(size) out := (*entryOut)(buf.alloc(size)) out.Nodeid = uint64(resp.Node) out.Generation = resp.Generation out.EntryValid = uint64(resp.EntryValid / time.Second) out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond) out.AttrValid = uint64(resp.Attr.Valid / time.Second) out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) resp.Attr.attr(&out.Attr, r.Header.Conn.proto) r.respond(buf) } type FsyncRequest struct { Header `json:"-"` Handle HandleID // TODO bit 1 is datasync, not well documented upstream Flags uint32 Dir bool } var _ Request = (*FsyncRequest)(nil) func (r *FsyncRequest) String() string { return fmt.Sprintf("Fsync [%s] Handle %v Flags %v", &r.Header, r.Handle, r.Flags) } func (r *FsyncRequest) Respond() { buf := newBuffer(0) r.respond(buf) } // An InterruptRequest is a request to interrupt another pending request. The // response to that request should return an error status of EINTR. type InterruptRequest struct { Header `json:"-"` IntrID RequestID // ID of the request to be interrupt. } var _ Request = (*InterruptRequest)(nil) func (r *InterruptRequest) Respond() { // nothing to do here r.noResponse() } func (r *InterruptRequest) String() string { return fmt.Sprintf("Interrupt [%s] ID %v", &r.Header, r.IntrID) } // An ExchangeDataRequest is a request to exchange the contents of two // files, while leaving most metadata untouched. // // This request comes from OS X exchangedata(2) and represents its // specific semantics. Crucially, it is very different from Linux // renameat(2) RENAME_EXCHANGE. // // https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/exchangedata.2.html type ExchangeDataRequest struct { Header `json:"-"` OldDir, NewDir NodeID OldName, NewName string // TODO options } var _ Request = (*ExchangeDataRequest)(nil) func (r *ExchangeDataRequest) String() string { // TODO options return fmt.Sprintf("ExchangeData [%s] %v %q and %v %q", &r.Header, r.OldDir, r.OldName, r.NewDir, r.NewName) } func (r *ExchangeDataRequest) Respond() { buf := newBuffer(0) r.respond(buf) } // NotifyReply is a response to an earlier notification. It behaves // like a Request, but is not really a request expecting a response. type NotifyReply struct { Header `json:"-"` msg *message } var _ Request = (*NotifyReply)(nil) func (r *NotifyReply) String() string { return fmt.Sprintf("NotifyReply [%s]", &r.Header) } type PollRequest struct { Header `json:"-"` Handle HandleID kh uint64 Flags PollFlags // Events is a bitmap of events of interest. // // This field is only set for FUSE protocol 7.21 and later. Events PollEvents } var _ Request = (*PollRequest)(nil) func (r *PollRequest) String() string { return fmt.Sprintf("Poll [%s] %v kh=%v fl=%v ev=%v", &r.Header, r.Handle, r.kh, r.Flags, r.Events) } type PollWakeup struct { kh uint64 } func (p PollWakeup) String() string { return fmt.Sprintf("PollWakeup{kh=%d}", p.kh) } // Wakeup returns information that can be used later to wake up file // system clients polling a Handle or a Node. // // ok is false if wakeups are not requested for this poll. // // Do not retain PollWakeup past the lifetime of the Handle or Node. func (r *PollRequest) Wakeup() (_ PollWakeup, ok bool) { if r.Flags&PollScheduleNotify == 0 { return PollWakeup{}, false } p := PollWakeup{ kh: r.kh, } return p, true } func (r *PollRequest) Respond(resp *PollResponse) { buf := newBuffer(unsafe.Sizeof(pollOut{})) out := (*pollOut)(buf.alloc(unsafe.Sizeof(pollOut{}))) out.REvents = uint32(resp.REvents) r.respond(buf) } type PollResponse struct { REvents PollEvents } func (r *PollResponse) String() string { return fmt.Sprintf("Poll revents=%v", r.REvents) } type FileLock struct { Start uint64 End uint64 Type LockType PID int32 } // LockRequest asks to try acquire a byte range lock on a node. The // response should be immediate, do not wait to obtain lock. // // Unlocking can be // // - explicit with UnlockRequest // - for flock: implicit on final close (ReleaseRequest.ReleaseFlags // has ReleaseFlockUnlock set) // - for POSIX locks: implicit on any close (FlushRequest) // - for Open File Description locks: implicit on final close // (no LockOwner observed as of 2020-04) // // See LockFlags to know which kind of a lock is being requested. (As // of 2020-04, Open File Descriptor locks are indistinguishable from // POSIX. This means programs using those locks will likely misbehave // when closing FDs on FUSE-based distributed filesystems, as the // filesystem has no better knowledge than to follow POSIX locking // rules and release the global lock too early.) // // Most of the other differences between flock (BSD) and POSIX (fcntl // F_SETLK) locks are relevant only to the caller, not the filesystem. // FUSE always sees ranges, and treats flock whole-file locks as // requests for the maximum byte range. Filesystems should do the // same, as this provides a forwards compatibility path to // Linux-native Open file description locks. // // To enable locking events in FUSE, pass LockingFlock() and/or // LockingPOSIX() to Mount. // // See also LockWaitRequest. type LockRequest struct { Header Handle HandleID // LockOwner is a unique identifier for the originating client, to // identify locks. LockOwner LockOwner Lock FileLock LockFlags LockFlags } var _ Request = (*LockRequest)(nil) func (r *LockRequest) String() string { return fmt.Sprintf("Lock [%s] %v owner=%v range=%d..%d type=%v pid=%v fl=%v", &r.Header, r.Handle, r.LockOwner, r.Lock.Start, r.Lock.End, r.Lock.Type, r.Lock.PID, r.LockFlags) } func (r *LockRequest) Respond() { buf := newBuffer(0) r.respond(buf) } // LockWaitRequest asks to acquire a byte range lock on a node, // delaying response until lock can be obtained (or the request is // interrupted). // // See LockRequest. LockWaitRequest can be converted to a LockRequest. type LockWaitRequest LockRequest var _ LockRequest = LockRequest(LockWaitRequest{}) var _ Request = (*LockWaitRequest)(nil) func (r *LockWaitRequest) String() string { return fmt.Sprintf("LockWait [%s] %v owner=%v range=%d..%d type=%v pid=%v fl=%v", &r.Header, r.Handle, r.LockOwner, r.Lock.Start, r.Lock.End, r.Lock.Type, r.Lock.PID, r.LockFlags) } func (r *LockWaitRequest) Respond() { buf := newBuffer(0) r.respond(buf) } // UnlockRequest asks to release a lock on a byte range on a node. // // UnlockRequests always have Lock.Type == LockUnlock. // // See LockRequest. UnlockRequest can be converted to a LockRequest. type UnlockRequest LockRequest var _ LockRequest = LockRequest(UnlockRequest{}) var _ Request = (*UnlockRequest)(nil) func (r *UnlockRequest) String() string { return fmt.Sprintf("Unlock [%s] %v owner=%v range=%d..%d type=%v pid=%v fl=%v", &r.Header, r.Handle, r.LockOwner, r.Lock.Start, r.Lock.End, r.Lock.Type, r.Lock.PID, r.LockFlags) } func (r *UnlockRequest) Respond() { buf := newBuffer(0) r.respond(buf) } // QueryLockRequest queries the lock status. // // If the lock could be placed, set response Lock.Type to // unix.F_UNLCK. // // If there are conflicting locks, the response should describe one of // them. For Open File Description locks, set PID to -1. (This is // probably also the sane behavior for locks held by remote parties.) type QueryLockRequest struct { Header Handle HandleID LockOwner LockOwner Lock FileLock LockFlags LockFlags } var _ Request = (*QueryLockRequest)(nil) func (r *QueryLockRequest) String() string { return fmt.Sprintf("QueryLock [%s] %v owner=%v range=%d..%d type=%v pid=%v fl=%v", &r.Header, r.Handle, r.LockOwner, r.Lock.Start, r.Lock.End, r.Lock.Type, r.Lock.PID, r.LockFlags) } // Respond replies to the request with the given response. func (r *QueryLockRequest) Respond(resp *QueryLockResponse) { buf := newBuffer(unsafe.Sizeof(lkOut{})) out := (*lkOut)(buf.alloc(unsafe.Sizeof(lkOut{}))) out.Lk = fileLock{ Start: resp.Lock.Start, End: resp.Lock.End, Type: uint32(resp.Lock.Type), PID: uint32(resp.Lock.PID), } r.respond(buf) } type QueryLockResponse struct { Lock FileLock } func (r *QueryLockResponse) String() string { return fmt.Sprintf("QueryLock range=%d..%d type=%v pid=%v", r.Lock.Start, r.Lock.End, r.Lock.Type, r.Lock.PID) } golang-github-anacrolix-fuse-0.2.0/fuse_darwin.go000066400000000000000000000005221444232012100220030ustar00rootroot00000000000000package fuse // Maximum file write size we are prepared to receive from the kernel. // // This value has to be >=16MB or OSXFUSE (3.4.0 observed) will // forcibly close the /dev/fuse file descriptor on a Setxattr with a // 16MB value. See TestSetxattr16MB and // https://github.com/bazil/fuse/issues/42 const maxWrite = 16 * 1024 * 1024 golang-github-anacrolix-fuse-0.2.0/fuse_freebsd.go000066400000000000000000000002241444232012100221300ustar00rootroot00000000000000package fuse // Maximum file write size we are prepared to receive from the kernel. // // This number is just a guess. const maxWrite = 128 * 1024 golang-github-anacrolix-fuse-0.2.0/fuse_kernel.go000066400000000000000000000514031444232012100220030ustar00rootroot00000000000000// See the file LICENSE for copyright and licensing information. // Derived from FUSE's fuse_kernel.h, which carries this notice: /* This file defines the kernel interface of FUSE Copyright (C) 2001-2007 Miklos Szeredi This -- and only this -- header file may also be distributed under the terms of the BSD Licence as follows: Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. 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. THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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. */ package fuse import ( "fmt" "syscall" "unsafe" "golang.org/x/sys/unix" ) // The FUSE version implemented by the package. const ( protoVersionMinMajor = 7 protoVersionMinMinor = 17 protoVersionMaxMajor = 7 protoVersionMaxMinor = 17 ) const ( rootID = 1 ) type kstatfs struct { Blocks uint64 Bfree uint64 Bavail uint64 Files uint64 Ffree uint64 Bsize uint32 Namelen uint32 Frsize uint32 _ uint32 Spare [6]uint32 } // GetattrFlags are bit flags that can be seen in GetattrRequest. type GetattrFlags uint32 const ( // Indicates the handle is valid. GetattrFh GetattrFlags = 1 << 0 ) var getattrFlagsNames = []flagName{ {uint32(GetattrFh), "GetattrFh"}, } func (fl GetattrFlags) String() string { return flagString(uint32(fl), getattrFlagsNames) } // The SetattrValid are bit flags describing which fields in the SetattrRequest // are included in the change. type SetattrValid uint32 const ( SetattrMode SetattrValid = 1 << 0 SetattrUid SetattrValid = 1 << 1 SetattrGid SetattrValid = 1 << 2 SetattrSize SetattrValid = 1 << 3 SetattrAtime SetattrValid = 1 << 4 SetattrMtime SetattrValid = 1 << 5 SetattrHandle SetattrValid = 1 << 6 SetattrAtimeNow SetattrValid = 1 << 7 SetattrMtimeNow SetattrValid = 1 << 8 SetattrLockOwner SetattrValid = 1 << 9 // http://www.mail-archive.com/git-commits-head@vger.kernel.org/msg27852.html // OS X only SetattrCrtime SetattrValid = 1 << 28 SetattrChgtime SetattrValid = 1 << 29 SetattrBkuptime SetattrValid = 1 << 30 SetattrFlags SetattrValid = 1 << 31 ) func (fl SetattrValid) Mode() bool { return fl&SetattrMode != 0 } func (fl SetattrValid) Uid() bool { return fl&SetattrUid != 0 } func (fl SetattrValid) Gid() bool { return fl&SetattrGid != 0 } func (fl SetattrValid) Size() bool { return fl&SetattrSize != 0 } func (fl SetattrValid) Atime() bool { return fl&SetattrAtime != 0 } func (fl SetattrValid) Mtime() bool { return fl&SetattrMtime != 0 } func (fl SetattrValid) Handle() bool { return fl&SetattrHandle != 0 } func (fl SetattrValid) AtimeNow() bool { return fl&SetattrAtimeNow != 0 } func (fl SetattrValid) MtimeNow() bool { return fl&SetattrMtimeNow != 0 } func (fl SetattrValid) LockOwner() bool { return fl&SetattrLockOwner != 0 } func (fl SetattrValid) Crtime() bool { return fl&SetattrCrtime != 0 } func (fl SetattrValid) Chgtime() bool { return fl&SetattrChgtime != 0 } func (fl SetattrValid) Bkuptime() bool { return fl&SetattrBkuptime != 0 } func (fl SetattrValid) Flags() bool { return fl&SetattrFlags != 0 } func (fl SetattrValid) String() string { return flagString(uint32(fl), setattrValidNames) } var setattrValidNames = []flagName{ {uint32(SetattrMode), "SetattrMode"}, {uint32(SetattrUid), "SetattrUid"}, {uint32(SetattrGid), "SetattrGid"}, {uint32(SetattrSize), "SetattrSize"}, {uint32(SetattrAtime), "SetattrAtime"}, {uint32(SetattrMtime), "SetattrMtime"}, {uint32(SetattrHandle), "SetattrHandle"}, {uint32(SetattrAtimeNow), "SetattrAtimeNow"}, {uint32(SetattrMtimeNow), "SetattrMtimeNow"}, {uint32(SetattrLockOwner), "SetattrLockOwner"}, {uint32(SetattrCrtime), "SetattrCrtime"}, {uint32(SetattrChgtime), "SetattrChgtime"}, {uint32(SetattrBkuptime), "SetattrBkuptime"}, {uint32(SetattrFlags), "SetattrFlags"}, } // Flags that can be seen in OpenRequest.Flags. const ( // Access modes. These are not 1-bit flags, but alternatives where // only one can be chosen. See the IsReadOnly etc convenience // methods. OpenReadOnly OpenFlags = syscall.O_RDONLY OpenWriteOnly OpenFlags = syscall.O_WRONLY OpenReadWrite OpenFlags = syscall.O_RDWR // File was opened in append-only mode, all writes will go to end // of file. OS X does not provide this information. OpenAppend OpenFlags = syscall.O_APPEND OpenCreate OpenFlags = syscall.O_CREAT OpenDirectory OpenFlags = syscall.O_DIRECTORY OpenExclusive OpenFlags = syscall.O_EXCL OpenNonblock OpenFlags = syscall.O_NONBLOCK OpenSync OpenFlags = syscall.O_SYNC OpenTruncate OpenFlags = syscall.O_TRUNC ) // OpenAccessModeMask is a bitmask that separates the access mode // from the other flags in OpenFlags. const OpenAccessModeMask OpenFlags = syscall.O_ACCMODE // OpenFlags are the O_FOO flags passed to open/create/etc calls. For // example, os.O_WRONLY | os.O_APPEND. type OpenFlags uint32 func (fl OpenFlags) String() string { // O_RDONLY, O_RWONLY, O_RDWR are not flags s := accModeName(fl & OpenAccessModeMask) flags := uint32(fl &^ OpenAccessModeMask) if flags != 0 { s = s + "+" + flagString(flags, openFlagNames) } return s } // Return true if OpenReadOnly is set. func (fl OpenFlags) IsReadOnly() bool { return fl&OpenAccessModeMask == OpenReadOnly } // Return true if OpenWriteOnly is set. func (fl OpenFlags) IsWriteOnly() bool { return fl&OpenAccessModeMask == OpenWriteOnly } // Return true if OpenReadWrite is set. func (fl OpenFlags) IsReadWrite() bool { return fl&OpenAccessModeMask == OpenReadWrite } func accModeName(flags OpenFlags) string { switch flags { case OpenReadOnly: return "OpenReadOnly" case OpenWriteOnly: return "OpenWriteOnly" case OpenReadWrite: return "OpenReadWrite" default: return "" } } var openFlagNames = []flagName{ {uint32(OpenAppend), "OpenAppend"}, {uint32(OpenCreate), "OpenCreate"}, {uint32(OpenDirectory), "OpenDirectory"}, {uint32(OpenExclusive), "OpenExclusive"}, {uint32(OpenNonblock), "OpenNonblock"}, {uint32(OpenSync), "OpenSync"}, {uint32(OpenTruncate), "OpenTruncate"}, } // The OpenResponseFlags are returned in the OpenResponse. type OpenResponseFlags uint32 const ( OpenDirectIO OpenResponseFlags = 1 << 0 // bypass page cache for this open file OpenKeepCache OpenResponseFlags = 1 << 1 // don't invalidate the data cache on open OpenNonSeekable OpenResponseFlags = 1 << 2 // mark the file as non-seekable (not supported on OS X or FreeBSD) OpenPurgeAttr OpenResponseFlags = 1 << 30 // OS X OpenPurgeUBC OpenResponseFlags = 1 << 31 // OS X ) func (fl OpenResponseFlags) String() string { return flagString(uint32(fl), openResponseFlagNames) } var openResponseFlagNames = []flagName{ {uint32(OpenDirectIO), "OpenDirectIO"}, {uint32(OpenKeepCache), "OpenKeepCache"}, {uint32(OpenNonSeekable), "OpenNonSeekable"}, {uint32(OpenPurgeAttr), "OpenPurgeAttr"}, {uint32(OpenPurgeUBC), "OpenPurgeUBC"}, } // The InitFlags are used in the Init exchange. type InitFlags uint32 const ( InitAsyncRead InitFlags = 1 << 0 InitPOSIXLocks InitFlags = 1 << 1 InitFileOps InitFlags = 1 << 2 InitAtomicTrunc InitFlags = 1 << 3 InitExportSupport InitFlags = 1 << 4 InitBigWrites InitFlags = 1 << 5 // Do not mask file access modes with umask. Not supported on OS X. InitDontMask InitFlags = 1 << 6 InitSpliceWrite InitFlags = 1 << 7 InitSpliceMove InitFlags = 1 << 8 InitSpliceRead InitFlags = 1 << 9 InitFlockLocks InitFlags = 1 << 10 InitHasIoctlDir InitFlags = 1 << 11 InitAutoInvalData InitFlags = 1 << 12 InitDoReaddirplus InitFlags = 1 << 13 InitReaddirplusAuto InitFlags = 1 << 14 InitAsyncDIO InitFlags = 1 << 15 InitWritebackCache InitFlags = 1 << 16 InitNoOpenSupport InitFlags = 1 << 17 InitCaseSensitive InitFlags = 1 << 29 // OS X only InitVolRename InitFlags = 1 << 30 // OS X only InitXtimes InitFlags = 1 << 31 // OS X only ) type flagName struct { bit uint32 name string } var initFlagNames = []flagName{ {uint32(InitAsyncRead), "InitAsyncRead"}, {uint32(InitPOSIXLocks), "InitPOSIXLocks"}, {uint32(InitFileOps), "InitFileOps"}, {uint32(InitAtomicTrunc), "InitAtomicTrunc"}, {uint32(InitExportSupport), "InitExportSupport"}, {uint32(InitBigWrites), "InitBigWrites"}, {uint32(InitDontMask), "InitDontMask"}, {uint32(InitSpliceWrite), "InitSpliceWrite"}, {uint32(InitSpliceMove), "InitSpliceMove"}, {uint32(InitSpliceRead), "InitSpliceRead"}, {uint32(InitFlockLocks), "InitFlockLocks"}, {uint32(InitHasIoctlDir), "InitHasIoctlDir"}, {uint32(InitAutoInvalData), "InitAutoInvalData"}, {uint32(InitDoReaddirplus), "InitDoReaddirplus"}, {uint32(InitReaddirplusAuto), "InitReaddirplusAuto"}, {uint32(InitAsyncDIO), "InitAsyncDIO"}, {uint32(InitWritebackCache), "InitWritebackCache"}, {uint32(InitNoOpenSupport), "InitNoOpenSupport"}, {uint32(InitCaseSensitive), "InitCaseSensitive"}, {uint32(InitVolRename), "InitVolRename"}, {uint32(InitXtimes), "InitXtimes"}, } func (fl InitFlags) String() string { return flagString(uint32(fl), initFlagNames) } func flagString(f uint32, names []flagName) string { var s string if f == 0 { return "0" } for _, n := range names { if f&n.bit != 0 { s += "+" + n.name f &^= n.bit } } if f != 0 { s += fmt.Sprintf("%+#x", f) } return s[1:] } // The ReleaseFlags are used in the Release exchange. type ReleaseFlags uint32 const ( ReleaseFlush ReleaseFlags = 1 << 0 ReleaseFlockUnlock ReleaseFlags = 1 << 1 ) func (fl ReleaseFlags) String() string { return flagString(uint32(fl), releaseFlagNames) } var releaseFlagNames = []flagName{ {uint32(ReleaseFlush), "ReleaseFlush"}, {uint32(ReleaseFlockUnlock), "ReleaseFlockUnlock"}, } // Opcodes const ( opLookup = 1 opForget = 2 // no reply opGetattr = 3 opSetattr = 4 opReadlink = 5 opSymlink = 6 opMknod = 8 opMkdir = 9 opUnlink = 10 opRmdir = 11 opRename = 12 opLink = 13 opOpen = 14 opRead = 15 opWrite = 16 opStatfs = 17 opRelease = 18 opFsync = 20 opSetxattr = 21 opGetxattr = 22 opListxattr = 23 opRemovexattr = 24 opFlush = 25 opInit = 26 opOpendir = 27 opReaddir = 28 opReleasedir = 29 opFsyncdir = 30 opGetlk = 31 opSetlk = 32 opSetlkw = 33 opAccess = 34 opCreate = 35 opInterrupt = 36 opBmap = 37 opDestroy = 38 opIoctl = 39 opPoll = 40 opNotifyReply = 41 opBatchForget = 42 // OS X opSetvolname = 61 opGetxtimes = 62 opExchange = 63 ) type entryOut struct { Nodeid uint64 // Inode ID Generation uint64 // Inode generation EntryValid uint64 // Cache timeout for the name AttrValid uint64 // Cache timeout for the attributes EntryValidNsec uint32 AttrValidNsec uint32 Attr attr } func entryOutSize(p Protocol) uintptr { switch { case p.LT(Protocol{7, 9}): return unsafe.Offsetof(entryOut{}.Attr) + unsafe.Offsetof(entryOut{}.Attr.Blksize) default: return unsafe.Sizeof(entryOut{}) } } type forgetIn struct { Nlookup uint64 } type forgetOne struct { NodeID uint64 Nlookup uint64 } type batchForgetIn struct { Count uint32 _ uint32 } type getattrIn struct { GetattrFlags uint32 _ uint32 Fh uint64 } type attrOut struct { AttrValid uint64 // Cache timeout for the attributes AttrValidNsec uint32 _ uint32 Attr attr } func attrOutSize(p Protocol) uintptr { switch { case p.LT(Protocol{7, 9}): return unsafe.Offsetof(attrOut{}.Attr) + unsafe.Offsetof(attrOut{}.Attr.Blksize) default: return unsafe.Sizeof(attrOut{}) } } // OS X type getxtimesOut struct { Bkuptime uint64 Crtime uint64 BkuptimeNsec uint32 CrtimeNsec uint32 } type mknodIn struct { Mode uint32 Rdev uint32 Umask uint32 _ uint32 // "filename\x00" follows. } func mknodInSize(p Protocol) uintptr { switch { case p.LT(Protocol{7, 12}): return unsafe.Offsetof(mknodIn{}.Umask) default: return unsafe.Sizeof(mknodIn{}) } } type mkdirIn struct { Mode uint32 Umask uint32 // filename follows } func mkdirInSize(p Protocol) uintptr { switch { case p.LT(Protocol{7, 12}): return unsafe.Offsetof(mkdirIn{}.Umask) + 4 default: return unsafe.Sizeof(mkdirIn{}) } } type renameIn struct { Newdir uint64 // "oldname\x00newname\x00" follows } // OS X type exchangeIn struct { Olddir uint64 Newdir uint64 Options uint64 // "oldname\x00newname\x00" follows } type linkIn struct { Oldnodeid uint64 } type setattrInCommon struct { Valid uint32 _ uint32 Fh uint64 Size uint64 LockOwner uint64 // unused on OS X? Atime uint64 Mtime uint64 Unused2 uint64 AtimeNsec uint32 MtimeNsec uint32 Unused3 uint32 Mode uint32 Unused4 uint32 Uid uint32 Gid uint32 Unused5 uint32 } type openIn struct { Flags uint32 Unused uint32 } type openOut struct { Fh uint64 OpenFlags uint32 _ uint32 } type createIn struct { Flags uint32 Mode uint32 Umask uint32 _ uint32 } func createInSize(p Protocol) uintptr { switch { case p.LT(Protocol{7, 12}): return unsafe.Offsetof(createIn{}.Umask) default: return unsafe.Sizeof(createIn{}) } } type releaseIn struct { Fh uint64 Flags uint32 ReleaseFlags uint32 LockOwner uint64 } type flushIn struct { Fh uint64 _ uint32 _ uint32 LockOwner uint64 } type readIn struct { Fh uint64 Offset uint64 Size uint32 ReadFlags uint32 LockOwner uint64 Flags uint32 _ uint32 } func readInSize(p Protocol) uintptr { switch { case p.LT(Protocol{7, 9}): return unsafe.Offsetof(readIn{}.ReadFlags) + 4 default: return unsafe.Sizeof(readIn{}) } } // The ReadFlags are passed in ReadRequest. type ReadFlags uint32 const ( // LockOwner field is valid. ReadLockOwner ReadFlags = 1 << 1 ) var readFlagNames = []flagName{ {uint32(ReadLockOwner), "ReadLockOwner"}, } func (fl ReadFlags) String() string { return flagString(uint32(fl), readFlagNames) } type writeIn struct { Fh uint64 Offset uint64 Size uint32 WriteFlags uint32 LockOwner uint64 Flags uint32 _ uint32 } func writeInSize(p Protocol) uintptr { switch { case p.LT(Protocol{7, 9}): return unsafe.Offsetof(writeIn{}.LockOwner) default: return unsafe.Sizeof(writeIn{}) } } type writeOut struct { Size uint32 _ uint32 } // The WriteFlags are passed in WriteRequest. type WriteFlags uint32 const ( WriteCache WriteFlags = 1 << 0 // LockOwner field is valid. WriteLockOwner WriteFlags = 1 << 1 ) var writeFlagNames = []flagName{ {uint32(WriteCache), "WriteCache"}, {uint32(WriteLockOwner), "WriteLockOwner"}, } func (fl WriteFlags) String() string { return flagString(uint32(fl), writeFlagNames) } type statfsOut struct { St kstatfs } type fsyncIn struct { Fh uint64 FsyncFlags uint32 _ uint32 } type setxattrInCommon struct { Size uint32 Flags uint32 } func (setxattrInCommon) position() uint32 { return 0 } type getxattrInCommon struct { Size uint32 _ uint32 } func (getxattrInCommon) position() uint32 { return 0 } type getxattrOut struct { Size uint32 _ uint32 } // The LockFlags are passed in LockRequest or LockWaitRequest. type LockFlags uint32 const ( // BSD-style flock lock (not POSIX lock) LockFlock LockFlags = 1 << 0 ) var lockFlagNames = []flagName{ {uint32(LockFlock), "LockFlock"}, } func (fl LockFlags) String() string { return flagString(uint32(fl), lockFlagNames) } type LockType uint32 const ( // It seems FreeBSD FUSE passes these through using its local // values, not whatever Linux enshrined into the protocol. It's // unclear what the intended behavior is. LockRead LockType = unix.F_RDLCK LockWrite LockType = unix.F_WRLCK LockUnlock LockType = unix.F_UNLCK ) var lockTypeNames = map[LockType]string{ LockRead: "LockRead", LockWrite: "LockWrite", LockUnlock: "LockUnlock", } func (l LockType) String() string { s, ok := lockTypeNames[l] if ok { return s } return fmt.Sprintf("LockType(%d)", l) } type fileLock struct { Start uint64 End uint64 Type uint32 PID uint32 } type lkIn struct { Fh uint64 Owner uint64 Lk fileLock LkFlags uint32 _ uint32 } type lkOut struct { Lk fileLock } type accessIn struct { Mask uint32 _ uint32 } type initIn struct { Major uint32 Minor uint32 MaxReadahead uint32 Flags uint32 } const initInSize = int(unsafe.Sizeof(initIn{})) type initOut struct { Major uint32 Minor uint32 MaxReadahead uint32 Flags uint32 MaxBackground uint16 CongestionThreshold uint16 MaxWrite uint32 } type interruptIn struct { Unique uint64 } type bmapIn struct { Block uint64 BlockSize uint32 _ uint32 } type bmapOut struct { Block uint64 } type inHeader struct { Len uint32 Opcode uint32 Unique uint64 Nodeid uint64 Uid uint32 Gid uint32 Pid uint32 _ uint32 } const inHeaderSize = int(unsafe.Sizeof(inHeader{})) type outHeader struct { Len uint32 Error int32 Unique uint64 } type dirent struct { Ino uint64 Off uint64 Namelen uint32 Type uint32 } const direntSize = 8 + 8 + 4 + 4 const ( notifyCodePoll int32 = 1 notifyCodeInvalInode int32 = 2 notifyCodeInvalEntry int32 = 3 notifyCodeStore int32 = 4 notifyCodeRetrieve int32 = 5 ) type notifyInvalInodeOut struct { Ino uint64 Off int64 Len int64 } type notifyInvalEntryOut struct { Parent uint64 Namelen uint32 _ uint32 } type notifyStoreOut struct { Nodeid uint64 Offset uint64 Size uint32 _ uint32 } type notifyRetrieveOut struct { NotifyUnique uint64 Nodeid uint64 Offset uint64 Size uint32 _ uint32 } type notifyRetrieveIn struct { // matches writeIn _ uint64 Offset uint64 Size uint32 _ uint32 _ uint64 _ uint64 } // PollFlags are passed in PollRequest.Flags type PollFlags uint32 const ( // PollScheduleNotify requests that a poll notification is done // once the node is ready. PollScheduleNotify PollFlags = 1 << 0 ) var pollFlagNames = []flagName{ {uint32(PollScheduleNotify), "PollScheduleNotify"}, } func (fl PollFlags) String() string { return flagString(uint32(fl), pollFlagNames) } type PollEvents uint32 const ( PollIn PollEvents = 0x0000_0001 PollPriority PollEvents = 0x0000_0002 PollOut PollEvents = 0x0000_0004 PollError PollEvents = 0x0000_0008 PollHangup PollEvents = 0x0000_0010 // PollInvalid doesn't seem to be used in the FUSE protocol. PollInvalid PollEvents = 0x0000_0020 PollReadNormal PollEvents = 0x0000_0040 PollReadOutOfBand PollEvents = 0x0000_0080 PollWriteNormal PollEvents = 0x0000_0100 PollWriteOutOfBand PollEvents = 0x0000_0200 PollMessage PollEvents = 0x0000_0400 PollReadHangup PollEvents = 0x0000_2000 DefaultPollMask = PollIn | PollOut | PollReadNormal | PollWriteNormal ) var pollEventNames = []flagName{ {uint32(PollIn), "PollIn"}, {uint32(PollPriority), "PollPriority"}, {uint32(PollOut), "PollOut"}, {uint32(PollError), "PollError"}, {uint32(PollHangup), "PollHangup"}, {uint32(PollInvalid), "PollInvalid"}, {uint32(PollReadNormal), "PollReadNormal"}, {uint32(PollReadOutOfBand), "PollReadOutOfBand"}, {uint32(PollWriteNormal), "PollWriteNormal"}, {uint32(PollWriteOutOfBand), "PollWriteOutOfBand"}, {uint32(PollMessage), "PollMessage"}, {uint32(PollReadHangup), "PollReadHangup"}, } func (fl PollEvents) String() string { return flagString(uint32(fl), pollEventNames) } type pollIn struct { Fh uint64 Kh uint64 Flags uint32 Events uint32 } type pollOut struct { REvents uint32 _ uint32 } type notifyPollWakeupOut struct { Kh uint64 } golang-github-anacrolix-fuse-0.2.0/fuse_kernel_darwin.go000066400000000000000000000027251444232012100233520ustar00rootroot00000000000000package fuse import ( "time" ) type attr struct { Ino uint64 Size uint64 Blocks uint64 Atime uint64 Mtime uint64 Ctime uint64 Crtime_ uint64 // OS X only AtimeNsec uint32 MtimeNsec uint32 CtimeNsec uint32 CrtimeNsec uint32 // OS X only Mode uint32 Nlink uint32 Uid uint32 Gid uint32 Rdev uint32 Flags_ uint32 // OS X only; see chflags(2) Blksize uint32 padding uint32 } func (a *attr) SetCrtime(s uint64, ns uint32) { a.Crtime_, a.CrtimeNsec = s, ns } func (a *attr) SetFlags(f uint32) { a.Flags_ = f } type setattrIn struct { setattrInCommon // OS X only Bkuptime_ uint64 Chgtime_ uint64 Crtime uint64 BkuptimeNsec uint32 ChgtimeNsec uint32 CrtimeNsec uint32 Flags_ uint32 // see chflags(2) } func (in *setattrIn) BkupTime() time.Time { return time.Unix(int64(in.Bkuptime_), int64(in.BkuptimeNsec)) } func (in *setattrIn) Chgtime() time.Time { return time.Unix(int64(in.Chgtime_), int64(in.ChgtimeNsec)) } func (in *setattrIn) Flags() uint32 { return in.Flags_ } func openFlags(flags uint32) OpenFlags { return OpenFlags(flags) } type getxattrIn struct { getxattrInCommon // OS X only Position uint32 Padding uint32 } func (g *getxattrIn) position() uint32 { return g.Position } type setxattrIn struct { setxattrInCommon // OS X only Position uint32 Padding uint32 } func (s *setxattrIn) position() uint32 { return s.Position } golang-github-anacrolix-fuse-0.2.0/fuse_kernel_freebsd.go000066400000000000000000000016321444232012100234740ustar00rootroot00000000000000package fuse import "time" type attr struct { Ino uint64 Size uint64 Blocks uint64 Atime uint64 Mtime uint64 Ctime uint64 AtimeNsec uint32 MtimeNsec uint32 CtimeNsec uint32 Mode uint32 Nlink uint32 Uid uint32 Gid uint32 Rdev uint32 Blksize uint32 padding uint32 } func (a *attr) Crtime() time.Time { return time.Time{} } func (a *attr) SetCrtime(s uint64, ns uint32) { // ignored on freebsd } func (a *attr) SetFlags(f uint32) { // ignored on freebsd } type setattrIn struct { setattrInCommon } func (in *setattrIn) BkupTime() time.Time { return time.Time{} } func (in *setattrIn) Chgtime() time.Time { return time.Time{} } func (in *setattrIn) Flags() uint32 { return 0 } func openFlags(flags uint32) OpenFlags { return OpenFlags(flags) } type getxattrIn struct { getxattrInCommon } type setxattrIn struct { setxattrInCommon } golang-github-anacrolix-fuse-0.2.0/fuse_kernel_linux.go000066400000000000000000000023101444232012100232130ustar00rootroot00000000000000package fuse import "time" type attr struct { Ino uint64 Size uint64 Blocks uint64 Atime uint64 Mtime uint64 Ctime uint64 AtimeNsec uint32 MtimeNsec uint32 CtimeNsec uint32 Mode uint32 Nlink uint32 Uid uint32 Gid uint32 Rdev uint32 Blksize uint32 _ uint32 } func (a *attr) Crtime() time.Time { return time.Time{} } func (a *attr) SetCrtime(s uint64, ns uint32) { // Ignored on Linux. } func (a *attr) SetFlags(f uint32) { // Ignored on Linux. } type setattrIn struct { setattrInCommon } func (in *setattrIn) BkupTime() time.Time { return time.Time{} } func (in *setattrIn) Chgtime() time.Time { return time.Time{} } func (in *setattrIn) Flags() uint32 { return 0 } func openFlags(flags uint32) OpenFlags { // on amd64, the 32-bit O_LARGEFILE flag is always seen; // on i386, the flag probably depends on the app // requesting, but in any case should be utterly // uninteresting to us here; our kernel protocol messages // are not directly related to the client app's kernel // API/ABI flags &^= 0x8000 return OpenFlags(flags) } type getxattrIn struct { getxattrInCommon } type setxattrIn struct { setxattrInCommon } golang-github-anacrolix-fuse-0.2.0/fuse_kernel_std.go000066400000000000000000000000151444232012100226460ustar00rootroot00000000000000package fuse golang-github-anacrolix-fuse-0.2.0/fuse_kernel_test.go000066400000000000000000000031521444232012100230400ustar00rootroot00000000000000package fuse_test import ( "os" "testing" "github.com/anacrolix/fuse" ) func TestOpenFlagsAccmodeMaskReadWrite(t *testing.T) { var f = fuse.OpenFlags(os.O_RDWR | os.O_SYNC) if g, e := f&fuse.OpenAccessModeMask, fuse.OpenReadWrite; g != e { t.Fatalf("OpenAccessModeMask behaves wrong: %v: %o != %o", f, g, e) } if f.IsReadOnly() { t.Fatalf("IsReadOnly is wrong: %v", f) } if f.IsWriteOnly() { t.Fatalf("IsWriteOnly is wrong: %v", f) } if !f.IsReadWrite() { t.Fatalf("IsReadWrite is wrong: %v", f) } } func TestOpenFlagsAccmodeMaskReadOnly(t *testing.T) { var f = fuse.OpenFlags(os.O_RDONLY | os.O_SYNC) if g, e := f&fuse.OpenAccessModeMask, fuse.OpenReadOnly; g != e { t.Fatalf("OpenAccessModeMask behaves wrong: %v: %o != %o", f, g, e) } if !f.IsReadOnly() { t.Fatalf("IsReadOnly is wrong: %v", f) } if f.IsWriteOnly() { t.Fatalf("IsWriteOnly is wrong: %v", f) } if f.IsReadWrite() { t.Fatalf("IsReadWrite is wrong: %v", f) } } func TestOpenFlagsAccmodeMaskWriteOnly(t *testing.T) { var f = fuse.OpenFlags(os.O_WRONLY | os.O_SYNC) if g, e := f&fuse.OpenAccessModeMask, fuse.OpenWriteOnly; g != e { t.Fatalf("OpenAccessModeMask behaves wrong: %v: %o != %o", f, g, e) } if f.IsReadOnly() { t.Fatalf("IsReadOnly is wrong: %v", f) } if !f.IsWriteOnly() { t.Fatalf("IsWriteOnly is wrong: %v", f) } if f.IsReadWrite() { t.Fatalf("IsReadWrite is wrong: %v", f) } } func TestOpenFlagsString(t *testing.T) { var f = fuse.OpenFlags(os.O_RDWR | os.O_SYNC | os.O_APPEND) if g, e := f.String(), "OpenReadWrite+OpenAppend+OpenSync"; g != e { t.Fatalf("OpenFlags.String: %q != %q", g, e) } } golang-github-anacrolix-fuse-0.2.0/fuse_linux.go000066400000000000000000000003331444232012100216560ustar00rootroot00000000000000package fuse // Maximum file write size we are prepared to receive from the kernel. // // Linux 4.2.0 has been observed to cap this value at 128kB // (FUSE_MAX_PAGES_PER_REQ=32, 4kB pages). const maxWrite = 128 * 1024 golang-github-anacrolix-fuse-0.2.0/fuse_test.go000066400000000000000000000031031444232012100214740ustar00rootroot00000000000000package fuse_test import ( "io/ioutil" "os" "runtime" "testing" "github.com/anacrolix/fuse" ) func getFeatures(t *testing.T, opts ...fuse.MountOption) fuse.InitFlags { tmp, err := ioutil.TempDir("", "fusetest") if err != nil { t.Fatalf("error creating temp dir: %v", err) } defer func() { if err := os.RemoveAll(tmp); err != nil { t.Errorf("error cleaning temp dir: %v", err) } }() conn, err := fuse.Mount(tmp, opts...) if err != nil { t.Fatalf("error mounting: %v", err) } defer func() { if err := conn.Close(); err != nil { t.Errorf("error closing FUSE connection: %v", err) } if err := fuse.Unmount(tmp); err != nil { t.Errorf("error unmounting: %v", err) } }() return conn.Features() } func TestFeatures(t *testing.T) { run := func(name string, want, notwant fuse.InitFlags, opts ...fuse.MountOption) { t.Run(name, func(t *testing.T) { if runtime.GOOS == "freebsd" { if want&fuse.InitFlockLocks != 0 { t.Skip("FreeBSD FUSE does not implement Flock locks") } } got := getFeatures(t, opts...) t.Logf("features: %v", got) missing := want &^ got if missing != 0 { t.Errorf("missing: %v", missing) } extra := got & notwant if extra != 0 { t.Errorf("extra: %v", extra) } }) } run("bare", 0, fuse.InitPOSIXLocks|fuse.InitFlockLocks) run("LockingFlock", fuse.InitFlockLocks, 0, fuse.LockingFlock()) run("LockingPOSIX", fuse.InitPOSIXLocks, 0, fuse.LockingPOSIX()) run("AsyncRead", fuse.InitAsyncRead, 0, fuse.AsyncRead()) run("WritebackCache", fuse.InitWritebackCache, 0, fuse.WritebackCache()) } golang-github-anacrolix-fuse-0.2.0/fuseutil/000077500000000000000000000000001444232012100210075ustar00rootroot00000000000000golang-github-anacrolix-fuse-0.2.0/fuseutil/fuseutil.go000066400000000000000000000010621444232012100231750ustar00rootroot00000000000000package fuseutil // import "github.com/anacrolix/fuse/fuseutil" import ( "github.com/anacrolix/fuse" ) // HandleRead handles a read request assuming that data is the entire file content. // It adjusts the amount returned in resp according to req.Offset and req.Size. func HandleRead(req *fuse.ReadRequest, resp *fuse.ReadResponse, data []byte) { if req.Offset >= int64(len(data)) { data = nil } else { data = data[req.Offset:] } if len(data) > req.Size { data = data[:req.Size] } n := copy(resp.Data[:req.Size], data) resp.Data = resp.Data[:n] } golang-github-anacrolix-fuse-0.2.0/go.mod000066400000000000000000000006331444232012100202570ustar00rootroot00000000000000module github.com/anacrolix/fuse go 1.13 require ( github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813 github.com/elazarl/go-bindata-assetfs v1.0.0 // indirect github.com/stephens2424/writerset v1.0.2 // indirect github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 golang.org/x/tools v0.0.0-20200423201157-2723c5de0d66 // indirect ) golang-github-anacrolix-fuse-0.2.0/go.sum000066400000000000000000000075361444232012100203150ustar00rootroot00000000000000github.com/Julusian/godocdown v0.0.0-20170816220326-6d19f8ff2df8/go.mod h1:INZr5t32rG59/5xeltqoCJoNY7e5x/3xoY9WSWVWg74= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813 h1:NgO45/5mBLRVfiXerEFzH6ikcZ7DNRPS639xFg3ENzU= github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk= github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/robertkrimen/godocdown v0.0.0-20130622164427-0bfa04905481/go.mod h1:C9WhFzY47SzYBIvzFqSvHIR6ROgDo4TtdTuRaOMjF/s= github.com/stephens2424/writerset v1.0.2 h1:znRLgU6g8RS5euYRcy004XeE4W+Tu44kALzy7ghPif8= github.com/stephens2424/writerset v1.0.2/go.mod h1:aS2JhsMn6eA7e82oNmW4rfsgAOp9COBTTl8mzkwADnc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c h1:u6SKchux2yDvFQnDHS3lPnIRmfVJ5Sxy3ao2SIdysLQ= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ= golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200423201157-2723c5de0d66 h1:EqVh9e7SxFnv93tWN3j33DpFAMKlFhaqI736Yp4kynk= golang.org/x/tools v0.0.0-20200423201157-2723c5de0d66/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang-github-anacrolix-fuse-0.2.0/mount.go000066400000000000000000000013761444232012100206470ustar00rootroot00000000000000package fuse import ( "bufio" "errors" "io" "log" "sync" ) var ( // ErrOSXFUSENotFound is returned from Mount when the OSXFUSE // installation is not detected. // // Only happens on OS X. Make sure OSXFUSE is installed, or see // OSXFUSELocations for customization. ErrOSXFUSENotFound = errors.New("cannot locate OSXFUSE") ) func neverIgnoreLine(line string) bool { return false } func lineLogger(wg *sync.WaitGroup, prefix string, ignore func(line string) bool, r io.ReadCloser) { defer wg.Done() scanner := bufio.NewScanner(r) for scanner.Scan() { line := scanner.Text() if ignore(line) { continue } log.Printf("%s: %s", prefix, line) } if err := scanner.Err(); err != nil { log.Printf("%s, error reading: %v", prefix, err) } } golang-github-anacrolix-fuse-0.2.0/mount_darwin.go000066400000000000000000000061561444232012100222140ustar00rootroot00000000000000package fuse import ( "bytes" "fmt" "os" "os/exec" "strconv" "syscall" "unsafe" ) func mount(mountPoint string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) { locations := conf.osxfuseLocations if locations == nil { locations = []OSXFUSEPaths{ OSXFUSELocationV4, OSXFUSELocationV3, } } var binLocation string for _, loc := range locations { if _, err := os.Stat(loc.Mount); os.IsNotExist(err) { // try the other locations continue } binLocation = loc.Mount break } if binLocation == "" { return nil, ErrOSXFUSENotFound } local, remote, err := unixgramSocketpair() if err != nil { return nil, err } defer local.Close() defer remote.Close() cmd := exec.Command(binLocation, "-o", conf.getOptions(), // Tell osxfuse-kext how large our buffer is. It must split // writes larger than this into multiple writes. // // OSXFUSE seems to ignore InitResponse.MaxWrite, and uses // this instead. "-o", "iosize="+strconv.FormatUint(maxWrite, 10), mountPoint) cmd.ExtraFiles = []*os.File{remote} // fd would be (index + 3) cmd.Env = append(os.Environ(), "_FUSE_CALL_BY_LIB=", "_FUSE_DAEMON_PATH="+os.Args[0], "_FUSE_COMMFD=3", "_FUSE_COMMVERS=2", "MOUNT_OSXFUSE_CALL_BY_LIB=", "MOUNT_OSXFUSE_DAEMON_PATH="+os.Args[0]) var out, errOut bytes.Buffer cmd.Stdout = &out cmd.Stderr = &errOut if err = cmd.Start(); err != nil { return nil, err } fd, err := getConnection(local) if err != nil { return nil, err } go func() { // wait inside a goroutine or otherwise it would block forever for unknown reasons if err := cmd.Wait(); err != nil { err = fmt.Errorf("mount_osxfusefs failed: %v. Stderr: %s, Stdout: %s", err, errOut.String(), out.String()) *errp = err } close(ready) }() dup, err := syscall.Dup(int(fd.Fd())) if err != nil { return nil, err } syscall.CloseOnExec(int(fd.Fd())) syscall.CloseOnExec(dup) return os.NewFile(uintptr(dup), "macfuse"), err } func unixgramSocketpair() (l, r *os.File, err error) { fd, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 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 } func getConnection(local *os.File) (*os.File, 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 nil, err } message := *(*syscall.Cmsghdr)(unsafe.Pointer(&control[0])) fd := *(*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(&control[0])) + syscall.SizeofCmsghdr)) if message.Type != syscall.SCM_RIGHTS { return nil, fmt.Errorf("getConnection: recvmsg returned wrong control type: %d", message.Type) } if oobn <= syscall.SizeofCmsghdr { return nil, fmt.Errorf("getConnection: too short control message. Length: %d", oobn) } if fd < 0 { return nil, fmt.Errorf("getConnection: fd < 0: %d", fd) } return os.NewFile(uintptr(fd), "macfuse"), nil } golang-github-anacrolix-fuse-0.2.0/mount_freebsd.go000066400000000000000000000054671444232012100223460ustar00rootroot00000000000000package fuse import ( "fmt" "log" "os" "os/exec" "strings" "sync" "syscall" ) func handleMountFusefsStderr(errCh chan<- error) func(line string) (ignore bool) { return func(line string) (ignore bool) { const ( noMountpointPrefix = `mount_fusefs: ` noMountpointSuffix = `: No such file or directory` ) if strings.HasPrefix(line, noMountpointPrefix) && strings.HasSuffix(line, noMountpointSuffix) { // re-extract it from the error message in case some layer // changed the path mountpoint := line[len(noMountpointPrefix) : len(line)-len(noMountpointSuffix)] err := &MountpointDoesNotExistError{ Path: mountpoint, } select { case errCh <- err: return true default: // not the first error; fall back to logging it return false } } return false } } // isBoringMountFusefsError returns whether the Wait error is // uninteresting; exit status 1 is. func isBoringMountFusefsError(err error) bool { if err, ok := err.(*exec.ExitError); ok && err.Exited() { if status, ok := err.Sys().(syscall.WaitStatus); ok && status.ExitStatus() == 1 { return true } } return false } func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) { for k, v := range conf.options { if strings.Contains(k, ",") || strings.Contains(v, ",") { // Silly limitation but the mount helper does not // understand any escaping. See TestMountOptionCommaError. return nil, fmt.Errorf("mount options cannot contain commas on FreeBSD: %q=%q", k, v) } } f, err := os.OpenFile("/dev/fuse", os.O_RDWR, 0o000) if err != nil { *errp = err return nil, err } cmd := exec.Command( "/sbin/mount_fusefs", "--safe", "-o", conf.getOptions(), "3", dir, ) cmd.ExtraFiles = []*os.File{f} stdout, err := cmd.StdoutPipe() if err != nil { return nil, fmt.Errorf("setting up mount_fusefs stderr: %v", err) } stderr, err := cmd.StderrPipe() if err != nil { return nil, fmt.Errorf("setting up mount_fusefs stderr: %v", err) } if err := cmd.Start(); err != nil { return nil, fmt.Errorf("mount_fusefs: %v", err) } helperErrCh := make(chan error, 1) var wg sync.WaitGroup wg.Add(2) go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout) go lineLogger(&wg, "mount helper error", handleMountFusefsStderr(helperErrCh), stderr) wg.Wait() if err := cmd.Wait(); err != nil { // see if we have a better error to report select { case helperErr := <-helperErrCh: // log the Wait error if it's not what we expected if !isBoringMountFusefsError(err) { log.Printf("mount helper failed: %v", err) } // and now return what we grabbed from stderr as the real // error return nil, helperErr default: // nope, fall back to generic message } return nil, fmt.Errorf("mount_fusefs: %v", err) } close(ready) return f, nil } golang-github-anacrolix-fuse-0.2.0/mount_linux.go000066400000000000000000000077401444232012100220670ustar00rootroot00000000000000package fuse import ( "fmt" "log" "net" "os" "os/exec" "strings" "sync" "syscall" ) func handleFusermountStderr(errCh chan<- error) func(line string) (ignore bool) { return func(line string) (ignore bool) { if line == `fusermount: failed to open /etc/fuse.conf: Permission denied` { // Silence this particular message, it occurs way too // commonly and isn't very relevant to whether the mount // succeeds or not. return true } const ( noMountpointPrefix = `fusermount: failed to access mountpoint ` noMountpointSuffix = `: No such file or directory` ) if strings.HasPrefix(line, noMountpointPrefix) && strings.HasSuffix(line, noMountpointSuffix) { // re-extract it from the error message in case some layer // changed the path mountpoint := line[len(noMountpointPrefix) : len(line)-len(noMountpointSuffix)] err := &MountpointDoesNotExistError{ Path: mountpoint, } select { case errCh <- err: return true default: // not the first error; fall back to logging it return false } } return false } } // isBoringFusermountError returns whether the Wait error is // uninteresting; exit status 1 is. func isBoringFusermountError(err error) bool { if err, ok := err.(*exec.ExitError); ok && err.Exited() { if status, ok := err.Sys().(syscall.WaitStatus); ok && status.ExitStatus() == 1 { return true } } return false } func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (fusefd *os.File, err error) { // linux mount is never delayed close(ready) fds, err := syscall.Socketpair(syscall.AF_FILE, syscall.SOCK_STREAM, 0) if err != nil { return nil, fmt.Errorf("socketpair error: %v", err) } writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes") defer writeFile.Close() readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads") defer readFile.Close() cmd := exec.Command( "fusermount", "-o", conf.getOptions(), "--", dir, ) cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3") cmd.ExtraFiles = []*os.File{writeFile} var wg sync.WaitGroup stdout, err := cmd.StdoutPipe() if err != nil { return nil, fmt.Errorf("setting up fusermount stderr: %v", err) } stderr, err := cmd.StderrPipe() if err != nil { return nil, fmt.Errorf("setting up fusermount stderr: %v", err) } if err := cmd.Start(); err != nil { return nil, fmt.Errorf("fusermount: %v", err) } helperErrCh := make(chan error, 1) wg.Add(2) go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout) go lineLogger(&wg, "mount helper error", handleFusermountStderr(helperErrCh), stderr) wg.Wait() if err := cmd.Wait(); err != nil { // see if we have a better error to report select { case helperErr := <-helperErrCh: // log the Wait error if it's not what we expected if !isBoringFusermountError(err) { log.Printf("mount helper failed: %v", err) } // and now return what we grabbed from stderr as the real // error return nil, helperErr default: // nope, fall back to generic message } return nil, fmt.Errorf("fusermount: %v", err) } c, err := net.FileConn(readFile) if err != nil { return nil, fmt.Errorf("FileConn from fusermount socket: %v", err) } defer c.Close() uc, ok := c.(*net.UnixConn) if !ok { return nil, fmt.Errorf("unexpected FileConn type; expected UnixConn, got %T", c) } buf := make([]byte, 32) // expect 1 byte oob := make([]byte, 32) // expect 24 bytes _, oobn, _, _, err := uc.ReadMsgUnix(buf, oob) scms, err := syscall.ParseSocketControlMessage(oob[:oobn]) if err != nil { return nil, fmt.Errorf("ParseSocketControlMessage: %v", err) } if len(scms) != 1 { return nil, fmt.Errorf("expected 1 SocketControlMessage; got scms = %#v", scms) } scm := scms[0] gotFds, err := syscall.ParseUnixRights(&scm) if err != nil { return nil, fmt.Errorf("syscall.ParseUnixRights: %v", err) } if len(gotFds) != 1 { return nil, fmt.Errorf("wanted 1 fd; got %#v", gotFds) } f := os.NewFile(uintptr(gotFds[0]), "/dev/fuse") return f, nil } golang-github-anacrolix-fuse-0.2.0/options.go000066400000000000000000000227561444232012100212050ustar00rootroot00000000000000package fuse import ( "errors" "strings" ) func dummyOption(conf *mountConfig) error { return nil } // mountConfig holds the configuration for a mount operation. // Use it by passing MountOption values to Mount. type mountConfig struct { options map[string]string maxReadahead uint32 initFlags InitFlags maxBackground uint16 congestionThreshold uint16 osxfuseLocations []OSXFUSEPaths } func escapeComma(s string) string { s = strings.Replace(s, `\`, `\\`, -1) s = strings.Replace(s, `,`, `\,`, -1) return s } // getOptions makes a string of options suitable for passing to FUSE // mount flag `-o`. Returns an empty string if no options were set. // Any platform specific adjustments should happen before the call. func (m *mountConfig) getOptions() string { var opts []string for k, v := range m.options { k = escapeComma(k) if v != "" { k += "=" + escapeComma(v) } opts = append(opts, k) } return strings.Join(opts, ",") } type mountOption func(*mountConfig) error // MountOption is passed to Mount to change the behavior of the mount. type MountOption mountOption // FSName sets the file system name (also called source) that is // visible in the list of mounted file systems. // // FreeBSD ignores this option. func FSName(name string) MountOption { return func(conf *mountConfig) error { conf.options["fsname"] = name return nil } } // Subtype sets the subtype of the mount. The main type is always // `fuse`. The type in a list of mounted file systems will look like // `fuse.foo`. // // OS X ignores this option. // FreeBSD ignores this option. func Subtype(fstype string) MountOption { return func(conf *mountConfig) error { conf.options["subtype"] = fstype return nil } } // LocalVolume sets the volume to be local (instead of network), // changing the behavior of Finder, Spotlight, and such. // // OS X only. Others ignore this option. func LocalVolume() MountOption { return localVolume } // VolumeName sets the volume name shown in Finder. // // OS X only. Others ignore this option. func VolumeName(name string) MountOption { return volumeName(name) } // NoAppleDouble makes OSXFUSE disallow files with names used by OS X // to store extended attributes on file systems that do not support // them natively. // // Such file names are: // // ._* // .DS_Store // // OS X only. Others ignore this option. func NoAppleDouble() MountOption { return noAppleDouble } // NoAppleXattr makes OSXFUSE disallow extended attributes with the // prefix "com.apple.". This disables persistent Finder state and // other such information. // // OS X only. Others ignore this option. func NoAppleXattr() MountOption { return noAppleXattr } // NoBrowse makes OSXFUSE mark the volume as non-browsable, so that // Finder won't automatically browse it. // // OS X only. Others ignore this option. func NoBrowse() MountOption { return noBrowse } // ExclCreate causes O_EXCL flag to be set for only "truly" exclusive creates, // i.e. create calls for which the initiator explicitly set the O_EXCL flag. // // OSXFUSE expects all create calls to return EEXIST in case the file // already exists, regardless of whether O_EXCL was specified or not. // To ensure this behavior, it normally sets OpenExclusive for all // Create calls, regardless of whether the original call had it set. // For distributed filesystems, that may force every file create to be // a distributed consensus action, causing undesirable delays. // // This option makes the FUSE filesystem see the original flag value, // and better decide when to ensure global consensus. // // Note that returning EEXIST on existing file create is still // expected with OSXFUSE, regardless of the presence of the // OpenExclusive flag. // // For more information, see // https://github.com/osxfuse/osxfuse/issues/209 // // OS X only. Others ignore this options. // Requires OSXFUSE 3.4.1 or newer. func ExclCreate() MountOption { return exclCreate } // DaemonTimeout sets the time in seconds between a request and a reply before // the FUSE mount is declared dead. // // OS X and FreeBSD only. Others ignore this option. func DaemonTimeout(name string) MountOption { return daemonTimeout(name) } // AllowOther allows other users to access the file system. func AllowOther() MountOption { return func(conf *mountConfig) error { conf.options["allow_other"] = "" return nil } } // AllowDev enables interpreting character or block special devices on the // filesystem. func AllowDev() MountOption { return func(conf *mountConfig) error { conf.options["dev"] = "" return nil } } // AllowSUID allows set-user-identifier or set-group-identifier bits to take // effect. func AllowSUID() MountOption { return func(conf *mountConfig) error { conf.options["suid"] = "" return nil } } // DefaultPermissions makes the kernel enforce access control based on // the file mode (as in chmod). // // Without this option, the Node itself decides what is and is not // allowed. This is normally ok because FUSE file systems cannot be // accessed by other users without AllowOther. // // FreeBSD ignores this option. func DefaultPermissions() MountOption { return func(conf *mountConfig) error { conf.options["default_permissions"] = "" return nil } } // ReadOnly makes the mount read-only. func ReadOnly() MountOption { return func(conf *mountConfig) error { conf.options["ro"] = "" return nil } } // MaxReadahead sets the number of bytes that can be prefetched for // sequential reads. The kernel can enforce a maximum value lower than // this. // // This setting makes the kernel perform speculative reads that do not // originate from any client process. This usually tremendously // improves read performance. func MaxReadahead(n uint32) MountOption { return func(conf *mountConfig) error { conf.maxReadahead = n return nil } } // AsyncRead enables multiple outstanding read requests for the same // handle. Without this, there is at most one request in flight at a // time. func AsyncRead() MountOption { return func(conf *mountConfig) error { conf.initFlags |= InitAsyncRead return nil } } // WritebackCache enables the kernel to buffer writes before sending // them to the FUSE server. Without this, writethrough caching is // used. func WritebackCache() MountOption { return func(conf *mountConfig) error { conf.initFlags |= InitWritebackCache return nil } } // OSXFUSEPaths describes the path used by an installed OSXFUSE // version. See OSXFUSELocationV4 for typical values. type OSXFUSEPaths struct { // Path of the mount helper, used for the mount operation Mount string } // Default paths for OSXFUSE. See OSXFUSELocations. var ( OSXFUSELocationV4 = OSXFUSEPaths{ Mount: "/Library/Filesystems/macfuse.fs/Contents/Resources/mount_macfuse", } OSXFUSELocationV3 = OSXFUSEPaths{ Mount: "/Library/Filesystems/osxfuse.fs/Contents/Resources/mount_osxfuse", } ) // OSXFUSELocations sets where to look for OSXFUSE files. The // arguments are all the possible locations. The previous locations // are replaced. // // Without this option, OSXFUSELocationV3 and OSXFUSELocationV2 are // used. // // OS X only. Others ignore this option. func OSXFUSELocations(paths ...OSXFUSEPaths) MountOption { return func(conf *mountConfig) error { if len(paths) == 0 { return errors.New("must specify at least one location for OSXFUSELocations") } // replace previous values, but make a copy so there's no // worries about caller mutating their slice conf.osxfuseLocations = append(conf.osxfuseLocations[:0], paths...) return nil } } // AllowNonEmptyMount allows the mounting over a non-empty directory. // // The files in it will be shadowed by the freshly created mount. By // default these mounts are rejected to prevent accidental covering up // of data, which could for example prevent automatic backup. func AllowNonEmptyMount() MountOption { return func(conf *mountConfig) error { conf.options["nonempty"] = "" return nil } } // MaxBackground sets the maximum number of FUSE requests the kernel // will submit in the background. Background requests are used when an // immediate answer is not needed. This may help with request latency. // // On Linux, this can be adjusted on the fly with // /sys/fs/fuse/connections/CONN/max_background func MaxBackground(n uint16) MountOption { return func(conf *mountConfig) error { conf.maxBackground = n return nil } } // CongestionThreshold sets the number of outstanding background FUSE // requests beyond which the kernel considers the filesystem // congested. This may help with request latency. // // On Linux, this can be adjusted on the fly with // /sys/fs/fuse/connections/CONN/congestion_threshold func CongestionThreshold(n uint16) MountOption { // TODO to test this, we'd have to figure out our connection id // and read /sys return func(conf *mountConfig) error { conf.congestionThreshold = n return nil } } // LockingFlock enables flock-based (BSD) locking. This is mostly // useful for distributed filesystems with global locking. Without // this, kernel manages local locking automatically. func LockingFlock() MountOption { return func(conf *mountConfig) error { conf.initFlags |= InitFlockLocks return nil } } // LockingPOSIX enables flock-based (BSD) locking. This is mostly // useful for distributed filesystems with global locking. Without // this, kernel manages local locking automatically. // // Beware POSIX locks are a broken API with unintuitive behavior for // callers. func LockingPOSIX() MountOption { return func(conf *mountConfig) error { conf.initFlags |= InitPOSIXLocks return nil } } golang-github-anacrolix-fuse-0.2.0/options_daemon_timeout_test.go000066400000000000000000000027411444232012100253250ustar00rootroot00000000000000// Test for adjustable timeout between a FUSE request and the daemon's response. // //go:build darwin // +build darwin package fuse_test import ( "context" "os" "runtime" "syscall" "testing" "time" "github.com/anacrolix/fuse" "github.com/anacrolix/fuse/fs" "github.com/anacrolix/fuse/fs/fstestutil" ) type slowCreaterDir struct { fstestutil.Dir } var _ fs.NodeCreater = slowCreaterDir{} func (c slowCreaterDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) { time.Sleep(10 * time.Second) // pick a really distinct error, to identify it later return nil, nil, fuse.Errno(syscall.ENAMETOOLONG) } func TestMountOptionDaemonTimeout(t *testing.T) { if runtime.GOOS != "darwin" { return } if testing.Short() { t.Skip("skipping time-based test in short mode") } maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{slowCreaterDir{}}, nil, fuse.DaemonTimeout("2"), ) if err != nil { t.Fatal(err) } defer mnt.Close() control := openErrHelper.Spawn(ctx, t) defer control.Close() // This should fail by the kernel timing out the request. req := openRequest{ Path: mnt.Dir + "/child", Flags: os.O_WRONLY | os.O_CREATE, Perm: 0, WantErrno: syscall.ENOTCONN, } var nothing struct{} if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } } golang-github-anacrolix-fuse-0.2.0/options_darwin.go000066400000000000000000000013571444232012100225430ustar00rootroot00000000000000package fuse func localVolume(conf *mountConfig) error { conf.options["local"] = "" return nil } func volumeName(name string) MountOption { return func(conf *mountConfig) error { conf.options["volname"] = name return nil } } func daemonTimeout(name string) MountOption { return func(conf *mountConfig) error { conf.options["daemon_timeout"] = name return nil } } func noAppleXattr(conf *mountConfig) error { conf.options["noapplexattr"] = "" return nil } func noAppleDouble(conf *mountConfig) error { conf.options["noappledouble"] = "" return nil } func exclCreate(conf *mountConfig) error { conf.options["excl_create"] = "" return nil } func noBrowse(conf *mountConfig) error { conf.options["nobrowse"] = "" return nil } golang-github-anacrolix-fuse-0.2.0/options_freebsd.go000066400000000000000000000007771444232012100226760ustar00rootroot00000000000000package fuse func localVolume(conf *mountConfig) error { return nil } func volumeName(name string) MountOption { return dummyOption } func daemonTimeout(name string) MountOption { return func(conf *mountConfig) error { conf.options["timeout"] = name return nil } } func noAppleXattr(conf *mountConfig) error { return nil } func noAppleDouble(conf *mountConfig) error { return nil } func exclCreate(conf *mountConfig) error { return nil } func noBrowse(conf *mountConfig) error { return nil } golang-github-anacrolix-fuse-0.2.0/options_helper_test.go000066400000000000000000000004111444232012100235630ustar00rootroot00000000000000package fuse //lint:ignore U1000 Used by TestMountOptionCommaError to bypass safe API // for TestMountOptionCommaError func ForTestSetMountOption(k, v string) MountOption { fn := func(conf *mountConfig) error { conf.options[k] = v return nil } return fn } golang-github-anacrolix-fuse-0.2.0/options_linux.go000066400000000000000000000006721444232012100224150ustar00rootroot00000000000000package fuse func localVolume(conf *mountConfig) error { return nil } func volumeName(name string) MountOption { return dummyOption } func daemonTimeout(name string) MountOption { return dummyOption } func noAppleXattr(conf *mountConfig) error { return nil } func noAppleDouble(conf *mountConfig) error { return nil } func exclCreate(conf *mountConfig) error { return nil } func noBrowse(conf *mountConfig) error { return nil } golang-github-anacrolix-fuse-0.2.0/options_nocomma_test.go000066400000000000000000000015111444232012100237370ustar00rootroot00000000000000// This file contains tests for platforms that have no escape // mechanism for including commas in mount options. // //go:build darwin // +build darwin package fuse_test import ( "runtime" "testing" "github.com/anacrolix/fuse" "github.com/anacrolix/fuse/fs/fstestutil" ) func TestMountOptionCommaError(t *testing.T) { maybeParallel(t) // this test is not tied to any specific option, it just needs // some string content var evil = "FuseTest,Marker" mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, fuse.ForTestSetMountOption("fusetest", evil), ) if err == nil { mnt.Close() t.Fatal("expected an error about commas") } if g, e := err.Error(), `mount options cannot contain commas on `+runtime.GOOS+`: "fusetest"="FuseTest,Marker"`; g != e { t.Fatalf("wrong error: %q != %q", g, e) } } golang-github-anacrolix-fuse-0.2.0/options_test.go000066400000000000000000000163031444232012100222330ustar00rootroot00000000000000package fuse_test import ( "bufio" "context" "errors" "flag" "fmt" "os" "runtime" "syscall" "testing" "github.com/anacrolix/fuse" "github.com/anacrolix/fuse/fs" "github.com/anacrolix/fuse/fs/fstestutil" "github.com/anacrolix/fuse/fs/fstestutil/spawntest" "github.com/anacrolix/fuse/fs/fstestutil/spawntest/httpjson" ) func init() { fstestutil.DebugByDefault() } func maybeParallel(t *testing.T) { // t.Parallel() } var helpers spawntest.Registry func TestMain(m *testing.M) { helpers.AddFlag(flag.CommandLine) flag.Parse() helpers.RunIfNeeded() os.Exit(m.Run()) } func TestMountOptionFSName(t *testing.T) { if runtime.GOOS == "freebsd" { t.Skip("FreeBSD does not support FSName") } maybeParallel(t) const name = "FuseTestMarker" mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, fuse.FSName(name), ) if err != nil { t.Fatal(err) } defer mnt.Close() info, err := fstestutil.GetMountInfo(mnt.Dir) if err != nil { t.Fatal(err) } if g, e := info.FSName, name; g != e { t.Errorf("wrong FSName: %q != %q", g, e) } } func testMountOptionFSNameEvil(t *testing.T, evil string) { if runtime.GOOS == "freebsd" { t.Skip("FreeBSD does not support FSName") } maybeParallel(t) var name = "FuseTest" + evil + "Marker" mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, fuse.FSName(name), ) if err != nil { t.Fatal(err) } defer mnt.Close() info, err := fstestutil.GetMountInfo(mnt.Dir) if err != nil { t.Fatal(err) } if g, e := info.FSName, name; g != e { t.Errorf("wrong FSName: %q != %q", g, e) } } func TestMountOptionFSNameEvilComma(t *testing.T) { if runtime.GOOS == "darwin" { // see TestMountOptionCommaError for a test that enforces we // at least give a nice error, instead of corrupting the mount // options t.Skip("TODO: OS X gets this wrong, commas in mount options cannot be escaped at all") } testMountOptionFSNameEvil(t, ",") } func TestMountOptionFSNameEvilSpace(t *testing.T) { testMountOptionFSNameEvil(t, " ") } func TestMountOptionFSNameEvilTab(t *testing.T) { testMountOptionFSNameEvil(t, "\t") } func TestMountOptionFSNameEvilNewline(t *testing.T) { testMountOptionFSNameEvil(t, "\n") } func TestMountOptionFSNameEvilBackslash(t *testing.T) { testMountOptionFSNameEvil(t, `\`) } func TestMountOptionFSNameEvilBackslashDouble(t *testing.T) { // catch double-unescaping, if it were to happen testMountOptionFSNameEvil(t, `\\`) } func TestMountOptionSubtype(t *testing.T) { if runtime.GOOS == "darwin" { t.Skip("OS X does not support Subtype") } if runtime.GOOS == "freebsd" { t.Skip("FreeBSD does not support Subtype") } maybeParallel(t) const name = "FuseTestMarker" mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, fuse.Subtype(name), ) if err != nil { t.Fatal(err) } defer mnt.Close() info, err := fstestutil.GetMountInfo(mnt.Dir) if err != nil { t.Fatal(err) } if g, e := info.Type, "fuse."+name; g != e { t.Errorf("wrong Subtype: %q != %q", g, e) } } func etcFuseHasAllowOther(t testing.TB) bool { // sucks to go poking around in other programs' config files. f, err := os.Open("/etc/fuse.conf") if errors.Is(err, os.ErrNotExist) { return false } if err != nil { t.Fatal(err) } defer f.Close() scanner := bufio.NewScanner(f) for scanner.Scan() { if scanner.Text() == "user_allow_other" { return true } } if err := scanner.Err(); err != nil { t.Fatalf("reading /etc/fuse.conf: %v", err) } return false } func TestMountOptionAllowOther(t *testing.T) { if !etcFuseHasAllowOther(t) { t.Skip("need user_allow_other in /etc/fuse.conf") } maybeParallel(t) mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, fuse.AllowOther(), ) if err != nil { t.Fatalf("mount error: %v", err) } defer mnt.Close() // we're not going to bother testing that other users actually can // access the fs, that's quite fiddly and system specific -- we'd // need to run as root and switch to some other account for the // client. } type unwritableFile struct{} func (f unwritableFile) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0o000 return nil } type openRequest struct { Path string Flags int Perm os.FileMode WantErrno syscall.Errno } func doOpenErr(ctx context.Context, req openRequest) (*struct{}, error) { f, err := os.OpenFile(req.Path, req.Flags, req.Perm) if err == nil { f.Close() } if !errors.Is(err, req.WantErrno) { return nil, fmt.Errorf("wrong error: %v", err) } return &struct{}{}, nil } var openErrHelper = helpers.Register("openErr", httpjson.ServePOST(doOpenErr)) func TestMountOptionDefaultPermissions(t *testing.T) { if runtime.GOOS == "freebsd" { t.Skip("FreeBSD does not support DefaultPermissions") } if os.Getuid() == 0 { t.Skip("root cannot be denied by DefaultPermissions") } maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{ &fstestutil.ChildMap{"child": unwritableFile{}}, }, nil, fuse.DefaultPermissions(), ) if err != nil { t.Fatal(err) } defer mnt.Close() control := openErrHelper.Spawn(ctx, t) defer control.Close() // This will be prevented by kernel-level access checking when // DefaultPermissions is used. req := openRequest{ Path: mnt.Dir + "/child", Flags: os.O_WRONLY, Perm: 0, WantErrno: syscall.EACCES, } var nothing struct{} if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } } type createrDir struct { fstestutil.Dir } var _ fs.NodeCreater = createrDir{} func (createrDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) { // pick a really distinct error, to identify it later return nil, nil, fuse.Errno(syscall.ENAMETOOLONG) } func TestMountOptionReadOnly(t *testing.T) { maybeParallel(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{createrDir{}}, nil, fuse.ReadOnly(), ) if err != nil { t.Fatal(err) } defer mnt.Close() control := openErrHelper.Spawn(ctx, t) defer control.Close() // This will be prevented by kernel-level access checking when // ReadOnly is used. req := openRequest{ Path: mnt.Dir + "/child", Flags: os.O_WRONLY | os.O_CREATE, Perm: 0, WantErrno: syscall.EROFS, } var nothing struct{} if err := control.JSON("/").Call(ctx, req, ¬hing); err != nil { t.Fatalf("calling helper: %v", err) } } func TestMountOptionMaxBackground(t *testing.T) { maybeParallel(t) mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, fuse.MaxBackground(4), ) if err != nil { t.Fatal(err) } defer mnt.Close() // TODO figure out our connection id and read // /sys/fs/fuse/connections/NUM/max_background } func TestMountOptionCongestionThreshold(t *testing.T) { maybeParallel(t) mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, fuse.CongestionThreshold(3), ) if err != nil { t.Fatal(err) } defer mnt.Close() // TODO figure out our connection id and read // /sys/fs/fuse/connections/NUM/congestion_threshold } golang-github-anacrolix-fuse-0.2.0/protocol.go000066400000000000000000000036521444232012100213450ustar00rootroot00000000000000package fuse import ( "fmt" ) // Protocol is a FUSE protocol version number. type Protocol struct { Major uint32 Minor uint32 } func (p Protocol) String() string { return fmt.Sprintf("%d.%d", p.Major, p.Minor) } // LT returns whether a is less than b. func (a Protocol) LT(b Protocol) bool { return a.Major < b.Major || (a.Major == b.Major && a.Minor < b.Minor) } // GE returns whether a is greater than or equal to b. func (a Protocol) GE(b Protocol) bool { return a.Major > b.Major || (a.Major == b.Major && a.Minor >= b.Minor) } // HasAttrBlockSize returns whether Attr.BlockSize is respected by the // kernel. // // Deprecated: Guaranteed to be true with our minimum supported // protocol version. func (a Protocol) HasAttrBlockSize() bool { return true } // HasReadWriteFlags returns whether ReadRequest/WriteRequest // fields Flags and FileFlags are valid. // // Deprecated: Guaranteed to be true with our minimum supported // protocol version. func (a Protocol) HasReadWriteFlags() bool { return true } // HasGetattrFlags returns whether GetattrRequest field Flags is // valid. // // Deprecated: Guaranteed to be true with our minimum supported // protocol version. func (a Protocol) HasGetattrFlags() bool { return true } // HasOpenNonSeekable returns whether OpenResponse field Flags flag // OpenNonSeekable is supported. // // Deprecated: Guaranteed to be true with our minimum supported // protocol version. func (a Protocol) HasOpenNonSeekable() bool { return true } // HasUmask returns whether CreateRequest/MkdirRequest/MknodRequest // field Umask is valid. // // Deprecated: Guaranteed to be true with our minimum supported // protocol version. func (a Protocol) HasUmask() bool { return true } // HasInvalidate returns whether InvalidateNode/InvalidateEntry are // supported. // // Deprecated: Guaranteed to be true with our minimum supported // protocol version. func (a Protocol) HasInvalidate() bool { return true } golang-github-anacrolix-fuse-0.2.0/syscallx/000077500000000000000000000000001444232012100210115ustar00rootroot00000000000000golang-github-anacrolix-fuse-0.2.0/syscallx/doc.go000066400000000000000000000011071444232012100221040ustar00rootroot00000000000000// Package syscallx provides wrappers that make syscalls on various // platforms more interoperable. // // The API intentionally omits the OS X-specific position and option // arguments for extended attribute calls. // // Not having position means it might not be useful for accessing the // resource fork. If that's needed by code inside fuse, a function // with a different name may be added on the side. // // Options can be implemented with separate wrappers, in the style of // Linux getxattr/lgetxattr/fgetxattr. package syscallx // import "github.com/anacrolix/fuse/syscallx" golang-github-anacrolix-fuse-0.2.0/syscallx/generate000077500000000000000000000014551444232012100225360ustar00rootroot00000000000000#!/bin/sh set -e mksys="$(go env GOROOT)/src/pkg/syscall/mksyscall.pl" fix() { sed 's,^package syscall$,&x\nimport "syscall",' \ | gofmt -r='BytePtrFromString -> syscall.BytePtrFromString' \ | gofmt -r='Syscall6 -> syscall.Syscall6' \ | gofmt -r='Syscall -> syscall.Syscall' \ | gofmt -r='SYS_GETXATTR -> syscall.SYS_GETXATTR' \ | gofmt -r='SYS_LISTXATTR -> syscall.SYS_LISTXATTR' \ | gofmt -r='SYS_SETXATTR -> syscall.SYS_SETXATTR' \ | gofmt -r='SYS_REMOVEXATTR -> syscall.SYS_REMOVEXATTR' \ | gofmt -r='SYS_MSYNC -> syscall.SYS_MSYNC' } cd "$(dirname "$0")" $mksys xattr_darwin.go \ | fix \ >xattr_darwin_amd64.go $mksys -l32 xattr_darwin.go \ | fix \ >xattr_darwin_386.go $mksys msync.go \ | fix \ >msync_amd64.go $mksys -l32 msync.go \ | fix \ >msync_386.go golang-github-anacrolix-fuse-0.2.0/syscallx/msync.go000066400000000000000000000002201444232012100224630ustar00rootroot00000000000000package syscallx /* This is the source file for msync_*.go, to regenerate run ./generate */ //sys Msync(b []byte, flags int) (err error) golang-github-anacrolix-fuse-0.2.0/syscallx/msync_386.go000066400000000000000000000007631444232012100230770ustar00rootroot00000000000000// mksyscall.pl -l32 msync.go // MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT package syscallx import "syscall" import "unsafe" // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT func Msync(b []byte, flags int) (err error) { var _p0 unsafe.Pointer if len(b) > 0 { _p0 = unsafe.Pointer(&b[0]) } else { _p0 = unsafe.Pointer(&_zero) } _, _, e1 := syscall.Syscall(syscall.SYS_MSYNC, uintptr(_p0), uintptr(len(b)), uintptr(flags)) if e1 != 0 { err = e1 } return } golang-github-anacrolix-fuse-0.2.0/syscallx/msync_amd64.go000066400000000000000000000007561444232012100234740ustar00rootroot00000000000000// mksyscall.pl msync.go // MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT package syscallx import "syscall" import "unsafe" // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT func Msync(b []byte, flags int) (err error) { var _p0 unsafe.Pointer if len(b) > 0 { _p0 = unsafe.Pointer(&b[0]) } else { _p0 = unsafe.Pointer(&_zero) } _, _, e1 := syscall.Syscall(syscall.SYS_MSYNC, uintptr(_p0), uintptr(len(b)), uintptr(flags)) if e1 != 0 { err = e1 } return } golang-github-anacrolix-fuse-0.2.0/syscallx/syscallx.go000066400000000000000000000001541444232012100232020ustar00rootroot00000000000000package syscallx // make us look more like package syscall, so mksyscall.pl output works var _zero uintptr golang-github-anacrolix-fuse-0.2.0/syscallx/syscallx_std.go000066400000000000000000000011671444232012100240610ustar00rootroot00000000000000// +build !darwin package syscallx // This file just contains wrappers for platforms that already have // the right stuff in golang.org/x/sys/unix. import ( "golang.org/x/sys/unix" ) func Getxattr(path string, attr string, dest []byte) (sz int, err error) { return unix.Getxattr(path, attr, dest) } func Listxattr(path string, dest []byte) (sz int, err error) { return unix.Listxattr(path, dest) } func Setxattr(path string, attr string, data []byte, flags int) (err error) { return unix.Setxattr(path, attr, data, flags) } func Removexattr(path string, attr string) (err error) { return unix.Removexattr(path, attr) } golang-github-anacrolix-fuse-0.2.0/syscallx/xattr_darwin.go000066400000000000000000000021131444232012100240430ustar00rootroot00000000000000package syscallx /* This is the source file for syscallx_darwin_*.go, to regenerate run ./generate */ // cannot use dest []byte here because OS X getxattr really wants a // NULL to trigger size probing, size==0 is not enough // //sys getxattr(path string, attr string, dest *byte, size int, position uint32, options int) (sz int, err error) func Getxattr(path string, attr string, dest []byte) (sz int, err error) { var destp *byte if len(dest) > 0 { destp = &dest[0] } return getxattr(path, attr, destp, len(dest), 0, 0) } //sys listxattr(path string, dest []byte, options int) (sz int, err error) func Listxattr(path string, dest []byte) (sz int, err error) { return listxattr(path, dest, 0) } //sys setxattr(path string, attr string, data []byte, position uint32, flags int) (err error) func Setxattr(path string, attr string, data []byte, flags int) (err error) { return setxattr(path, attr, data, 0, flags) } //sys removexattr(path string, attr string, options int) (err error) func Removexattr(path string, attr string) (err error) { return removexattr(path, attr, 0) } golang-github-anacrolix-fuse-0.2.0/syscallx/xattr_darwin_386.go000066400000000000000000000046311444232012100244520ustar00rootroot00000000000000// mksyscall.pl -l32 xattr_darwin.go // MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT package syscallx import "syscall" import "unsafe" // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT func getxattr(path string, attr string, dest *byte, size int, position uint32, options int) (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 } r0, _, e1 := syscall.Syscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(unsafe.Pointer(dest)), uintptr(size), uintptr(position), uintptr(options)) sz = int(r0) if e1 != 0 { err = e1 } return } // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT func listxattr(path string, dest []byte, options int) (sz int, err error) { var _p0 *byte _p0, err = syscall.BytePtrFromString(path) if err != nil { return } var _p1 unsafe.Pointer if len(dest) > 0 { _p1 = unsafe.Pointer(&dest[0]) } else { _p1 = unsafe.Pointer(&_zero) } r0, _, e1 := syscall.Syscall6(syscall.SYS_LISTXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(dest)), uintptr(options), 0, 0) sz = int(r0) if e1 != 0 { err = e1 } return } // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT func setxattr(path string, attr string, data []byte, position uint32, flags 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(data) > 0 { _p2 = unsafe.Pointer(&data[0]) } else { _p2 = unsafe.Pointer(&_zero) } _, _, e1 := syscall.Syscall6(syscall.SYS_SETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(data)), uintptr(position), uintptr(flags)) if e1 != 0 { err = e1 } return } // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT func removexattr(path string, attr string, options 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 } _, _, e1 := syscall.Syscall(syscall.SYS_REMOVEXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(options)) if e1 != 0 { err = e1 } return } golang-github-anacrolix-fuse-0.2.0/syscallx/xattr_darwin_amd64.go000066400000000000000000000046241444232012100250470ustar00rootroot00000000000000// mksyscall.pl xattr_darwin.go // MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT package syscallx import "syscall" import "unsafe" // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT func getxattr(path string, attr string, dest *byte, size int, position uint32, options int) (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 } r0, _, e1 := syscall.Syscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(unsafe.Pointer(dest)), uintptr(size), uintptr(position), uintptr(options)) sz = int(r0) if e1 != 0 { err = e1 } return } // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT func listxattr(path string, dest []byte, options int) (sz int, err error) { var _p0 *byte _p0, err = syscall.BytePtrFromString(path) if err != nil { return } var _p1 unsafe.Pointer if len(dest) > 0 { _p1 = unsafe.Pointer(&dest[0]) } else { _p1 = unsafe.Pointer(&_zero) } r0, _, e1 := syscall.Syscall6(syscall.SYS_LISTXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(dest)), uintptr(options), 0, 0) sz = int(r0) if e1 != 0 { err = e1 } return } // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT func setxattr(path string, attr string, data []byte, position uint32, flags 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(data) > 0 { _p2 = unsafe.Pointer(&data[0]) } else { _p2 = unsafe.Pointer(&_zero) } _, _, e1 := syscall.Syscall6(syscall.SYS_SETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(data)), uintptr(position), uintptr(flags)) if e1 != 0 { err = e1 } return } // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT func removexattr(path string, attr string, options 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 } _, _, e1 := syscall.Syscall(syscall.SYS_REMOVEXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(options)) if e1 != 0 { err = e1 } return } golang-github-anacrolix-fuse-0.2.0/unmount.go000066400000000000000000000002011444232012100211740ustar00rootroot00000000000000package fuse // Unmount tries to unmount the filesystem mounted at dir. func Unmount(dir string) error { return unmount(dir) } golang-github-anacrolix-fuse-0.2.0/unmount_linux.go000066400000000000000000000005471444232012100224300ustar00rootroot00000000000000package fuse import ( "bytes" "errors" "os/exec" ) func unmount(dir string) error { cmd := exec.Command("fusermount", "-u", dir) output, err := cmd.CombinedOutput() if err != nil { if len(output) > 0 { output = bytes.TrimRight(output, "\n") msg := err.Error() + ": " + string(output) err = errors.New(msg) } return err } return nil } golang-github-anacrolix-fuse-0.2.0/unmount_std.go000066400000000000000000000003471444232012100220610ustar00rootroot00000000000000// +build !linux package fuse import ( "os" "syscall" ) func unmount(dir string) error { err := syscall.Unmount(dir, 0) if err != nil { err = &os.PathError{Op: "unmount", Path: dir, Err: err} return err } return nil }