From 32a321e51e0ba84a6d374b2cd0219fdf777e5567 Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Tue, 7 Apr 2015 21:10:53 +0200 Subject: [PATCH 01/24] Add simple mount command --- Godeps/Godeps.json | 4 + .../src/bazil.org/fuse/.gitattributes | 2 + .../_workspace/src/bazil.org/fuse/.gitignore | 8 + Godeps/_workspace/src/bazil.org/fuse/LICENSE | 93 + .../_workspace/src/bazil.org/fuse/README.md | 23 + Godeps/_workspace/src/bazil.org/fuse/debug.go | 21 + .../src/bazil.org/fuse/doc/.gitignore | 4 + .../src/bazil.org/fuse/doc/README.md | 6 + .../fuse/doc/mount-linux-error-init.seq | 32 + .../fuse/doc/mount-linux-error-init.seq.png | Bin 0 -> 29163 bytes .../src/bazil.org/fuse/doc/mount-linux.seq | 41 + .../bazil.org/fuse/doc/mount-linux.seq.png | Bin 0 -> 44615 bytes .../fuse/doc/mount-osx-error-init.seq | 32 + .../fuse/doc/mount-osx-error-init.seq.png | Bin 0 -> 32618 bytes .../src/bazil.org/fuse/doc/mount-osx.seq | 45 + .../src/bazil.org/fuse/doc/mount-osx.seq.png | Bin 0 -> 51408 bytes .../src/bazil.org/fuse/doc/mount-sequence.md | 30 + .../src/bazil.org/fuse/doc/writing-docs.md | 16 + .../src/bazil.org/fuse/error_darwin.go | 17 + .../src/bazil.org/fuse/error_freebsd.go | 15 + .../src/bazil.org/fuse/error_linux.go | 17 + .../src/bazil.org/fuse/error_std.go | 31 + .../src/bazil.org/fuse/fs/bench/bench_test.go | 271 +++ .../src/bazil.org/fuse/fs/bench/doc.go | 5 + .../src/bazil.org/fuse/fs/fstestutil/debug.go | 65 + .../src/bazil.org/fuse/fs/fstestutil/doc.go | 1 + .../bazil.org/fuse/fs/fstestutil/mounted.go | 113 + .../bazil.org/fuse/fs/fstestutil/mountinfo.go | 26 + .../fuse/fs/fstestutil/mountinfo_darwin.go | 29 + .../fuse/fs/fstestutil/mountinfo_freebsd.go | 7 + .../fuse/fs/fstestutil/mountinfo_linux.go | 51 + .../fuse/fs/fstestutil/record/buffer.go | 28 + .../fuse/fs/fstestutil/record/record.go | 381 ++++ .../fuse/fs/fstestutil/record/wait.go | 55 + .../bazil.org/fuse/fs/fstestutil/testfs.go | 52 + .../src/bazil.org/fuse/fs/helpers_test.go | 67 + .../_workspace/src/bazil.org/fuse/fs/serve.go | 1336 +++++++++++ .../src/bazil.org/fuse/fs/serve_test.go | 1843 +++++++++++++++ .../_workspace/src/bazil.org/fuse/fs/tree.go | 98 + Godeps/_workspace/src/bazil.org/fuse/fuse.go | 2029 +++++++++++++++++ .../src/bazil.org/fuse/fuse_kernel.go | 639 ++++++ .../src/bazil.org/fuse/fuse_kernel_darwin.go | 86 + .../src/bazil.org/fuse/fuse_kernel_freebsd.go | 60 + .../src/bazil.org/fuse/fuse_kernel_linux.go | 70 + .../src/bazil.org/fuse/fuse_kernel_std.go | 1 + .../src/bazil.org/fuse/fuse_kernel_test.go | 31 + .../src/bazil.org/fuse/fuseutil/fuseutil.go | 20 + .../src/bazil.org/fuse/hellofs/hello.go | 99 + .../src/bazil.org/fuse/mount_darwin.go | 126 + .../src/bazil.org/fuse/mount_freebsd.go | 41 + .../src/bazil.org/fuse/mount_linux.go | 72 + .../_workspace/src/bazil.org/fuse/options.go | 132 ++ .../src/bazil.org/fuse/options_darwin.go | 13 + .../src/bazil.org/fuse/options_freebsd.go | 9 + .../src/bazil.org/fuse/options_helper_test.go | 6 + .../src/bazil.org/fuse/options_linux.go | 9 + .../bazil.org/fuse/options_nocomma_test.go | 34 + .../src/bazil.org/fuse/options_test.go | 228 ++ .../src/bazil.org/fuse/syscallx/doc.go | 13 + .../src/bazil.org/fuse/syscallx/generate | 34 + .../src/bazil.org/fuse/syscallx/msync.go | 9 + .../src/bazil.org/fuse/syscallx/msync_386.go | 24 + .../bazil.org/fuse/syscallx/msync_amd64.go | 24 + .../src/bazil.org/fuse/syscallx/syscallx.go | 4 + .../bazil.org/fuse/syscallx/syscallx_std.go | 26 + .../bazil.org/fuse/syscallx/xattr_darwin.go | 38 + .../fuse/syscallx/xattr_darwin_386.go | 97 + .../fuse/syscallx/xattr_darwin_amd64.go | 97 + .../_workspace/src/bazil.org/fuse/unmount.go | 6 + .../src/bazil.org/fuse/unmount_linux.go | 21 + .../src/bazil.org/fuse/unmount_std.go | 17 + .../src/golang.org/x/net/context/context.go | 447 ++++ .../golang.org/x/net/context/context_test.go | 575 +++++ .../x/net/context/withtimeout_test.go | 26 + cmd/restic/cmd_mount.go | 272 +++ 75 files changed, 10300 insertions(+) create mode 100644 Godeps/_workspace/src/bazil.org/fuse/.gitattributes create mode 100644 Godeps/_workspace/src/bazil.org/fuse/.gitignore create mode 100644 Godeps/_workspace/src/bazil.org/fuse/LICENSE create mode 100644 Godeps/_workspace/src/bazil.org/fuse/README.md create mode 100644 Godeps/_workspace/src/bazil.org/fuse/debug.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/doc/.gitignore create mode 100644 Godeps/_workspace/src/bazil.org/fuse/doc/README.md create mode 100644 Godeps/_workspace/src/bazil.org/fuse/doc/mount-linux-error-init.seq create mode 100644 Godeps/_workspace/src/bazil.org/fuse/doc/mount-linux-error-init.seq.png create mode 100644 Godeps/_workspace/src/bazil.org/fuse/doc/mount-linux.seq create mode 100644 Godeps/_workspace/src/bazil.org/fuse/doc/mount-linux.seq.png create mode 100644 Godeps/_workspace/src/bazil.org/fuse/doc/mount-osx-error-init.seq create mode 100644 Godeps/_workspace/src/bazil.org/fuse/doc/mount-osx-error-init.seq.png create mode 100644 Godeps/_workspace/src/bazil.org/fuse/doc/mount-osx.seq create mode 100644 Godeps/_workspace/src/bazil.org/fuse/doc/mount-osx.seq.png create mode 100644 Godeps/_workspace/src/bazil.org/fuse/doc/mount-sequence.md create mode 100644 Godeps/_workspace/src/bazil.org/fuse/doc/writing-docs.md create mode 100644 Godeps/_workspace/src/bazil.org/fuse/error_darwin.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/error_freebsd.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/error_linux.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/error_std.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fs/bench/bench_test.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fs/bench/doc.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/debug.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/doc.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mounted.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mountinfo.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mountinfo_darwin.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mountinfo_freebsd.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mountinfo_linux.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/record/buffer.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/record/record.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/record/wait.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/testfs.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fs/helpers_test.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fs/serve.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fs/serve_test.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fs/tree.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fuse.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fuse_kernel.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_darwin.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_freebsd.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_linux.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_std.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_test.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/fuseutil/fuseutil.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/hellofs/hello.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/mount_darwin.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/mount_freebsd.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/mount_linux.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/options.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/options_darwin.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/options_freebsd.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/options_helper_test.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/options_linux.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/options_nocomma_test.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/options_test.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/syscallx/doc.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/syscallx/generate create mode 100644 Godeps/_workspace/src/bazil.org/fuse/syscallx/msync.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/syscallx/msync_386.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/syscallx/msync_amd64.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/syscallx/syscallx.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/syscallx/syscallx_std.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/syscallx/xattr_darwin.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/syscallx/xattr_darwin_386.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/syscallx/xattr_darwin_amd64.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/unmount.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/unmount_linux.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/unmount_std.go create mode 100644 Godeps/_workspace/src/golang.org/x/net/context/context.go create mode 100644 Godeps/_workspace/src/golang.org/x/net/context/context_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/net/context/withtimeout_test.go create mode 100644 cmd/restic/cmd_mount.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 3440bba1a..b5d73b44d 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -5,6 +5,10 @@ "./..." ], "Deps": [ + { + "ImportPath": "bazil.org/fuse", + "Rev": "6312e7c7c12b9337021a37aff2b0f655f4709688" + }, { "ImportPath": "github.com/jessevdk/go-flags", "Comment": "v1-297-g1b89bf7", diff --git a/Godeps/_workspace/src/bazil.org/fuse/.gitattributes b/Godeps/_workspace/src/bazil.org/fuse/.gitattributes new file mode 100644 index 000000000..b65f2a9ff --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/.gitattributes @@ -0,0 +1,2 @@ +*.go filter=gofmt +*.cgo filter=gofmt diff --git a/Godeps/_workspace/src/bazil.org/fuse/.gitignore b/Godeps/_workspace/src/bazil.org/fuse/.gitignore new file mode 100644 index 000000000..2b286ca94 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/.gitignore @@ -0,0 +1,8 @@ +*~ +.#* +## the next line needs to start with a backslash to avoid looking like +## a comment +\#*# +.*.swp + +*.test diff --git a/Godeps/_workspace/src/bazil.org/fuse/LICENSE b/Godeps/_workspace/src/bazil.org/fuse/LICENSE new file mode 100644 index 000000000..4ac7cd838 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/LICENSE @@ -0,0 +1,93 @@ +Copyright (c) 2013-2015 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. +*/ diff --git a/Godeps/_workspace/src/bazil.org/fuse/README.md b/Godeps/_workspace/src/bazil.org/fuse/README.md new file mode 100644 index 000000000..471b2b258 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/README.md @@ -0,0 +1,23 @@ +bazil.org/fuse -- Filesystems in Go +=================================== + +`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/bazillion/fuse + +API docs: http://godoc.org/bazil.org/fuse + +Our thanks to Russ Cox for his fuse library, which this project is +based on. diff --git a/Godeps/_workspace/src/bazil.org/fuse/debug.go b/Godeps/_workspace/src/bazil.org/fuse/debug.go new file mode 100644 index 000000000..be9f900d5 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/debug.go @@ -0,0 +1,21 @@ +package 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 diff --git a/Godeps/_workspace/src/bazil.org/fuse/doc/.gitignore b/Godeps/_workspace/src/bazil.org/fuse/doc/.gitignore new file mode 100644 index 000000000..6ebe2d170 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/doc/.gitignore @@ -0,0 +1,4 @@ +/*.seq.svg + +# not ignoring *.seq.png; we want those committed to the repo +# for embedding on Github diff --git a/Godeps/_workspace/src/bazil.org/fuse/doc/README.md b/Godeps/_workspace/src/bazil.org/fuse/doc/README.md new file mode 100644 index 000000000..54ed0e590 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/doc/README.md @@ -0,0 +1,6 @@ +# 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) diff --git a/Godeps/_workspace/src/bazil.org/fuse/doc/mount-linux-error-init.seq b/Godeps/_workspace/src/bazil.org/fuse/doc/mount-linux-error-init.seq new file mode 100644 index 000000000..89cf15158 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/doc/mount-linux-error-init.seq @@ -0,0 +1,32 @@ +seqdiag { + 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 ... +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/doc/mount-linux-error-init.seq.png b/Godeps/_workspace/src/bazil.org/fuse/doc/mount-linux-error-init.seq.png new file mode 100644 index 0000000000000000000000000000000000000000..fea214f71f210fe41eb204cf925a9b8b81305c5d GIT binary patch literal 29163 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⋙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 literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/bazil.org/fuse/doc/mount-linux.seq b/Godeps/_workspace/src/bazil.org/fuse/doc/mount-linux.seq new file mode 100644 index 000000000..a1cafc7a6 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/doc/mount-linux.seq @@ -0,0 +1,41 @@ +seqdiag { + // 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"]; + + 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; +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/doc/mount-linux.seq.png b/Godeps/_workspace/src/bazil.org/fuse/doc/mount-linux.seq.png new file mode 100644 index 0000000000000000000000000000000000000000..af373dd284c52c60e65a9514bfc077785feef99f GIT binary patch literal 44615 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 literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/bazil.org/fuse/doc/mount-osx-error-init.seq b/Godeps/_workspace/src/bazil.org/fuse/doc/mount-osx-error-init.seq new file mode 100644 index 000000000..3bb2b39a0 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/doc/mount-osx-error-init.seq @@ -0,0 +1,32 @@ +seqdiag { + app; + fuse [label="bazil.org/fuse"]; + wait [label="callMount\nhelper goroutine"]; + mount_osxfusefs; + kernel; + + 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="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 ... +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/doc/mount-osx-error-init.seq.png b/Godeps/_workspace/src/bazil.org/fuse/doc/mount-osx-error-init.seq.png new file mode 100644 index 0000000000000000000000000000000000000000..e96589c13452cba6d6e0d821ab0b487dbb4656a6 GIT binary patch literal 32618 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 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; +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/doc/mount-osx.seq.png b/Godeps/_workspace/src/bazil.org/fuse/doc/mount-osx.seq.png new file mode 100644 index 0000000000000000000000000000000000000000..7e310f9144ba42896efc4ad1b124b823bb5cf15b GIT binary patch literal 51408 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

c-|0Yh5nbnWe>Fq#RUjzyPdpQrTT8 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 0 +} + +// Flushes notes whether a FUSE Flush call has been seen. +type Flushes struct { + rec MarkRecorder +} + +var _ = fs.HandleFlusher(&Flushes{}) + +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{}) + +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{}) + +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{}) + +// 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, fuse.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{}) + +// 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, fuse.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{}) + +// 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, fuse.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{}) + +// 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, fuse.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{}) + +// 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, fuse.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{}) + +// 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{}) + +// 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{}) + +// 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 + 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{}) + +// 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)) +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/record/wait.go b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/record/wait.go new file mode 100644 index 000000000..fb151297b --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/record/wait.go @@ -0,0 +1,55 @@ +package record + +import ( + "sync" + "time" + + "bazil.org/fuse" + "bazil.org/fuse/fs" + "golang.org/x/net/context" +) + +type nothing struct{} + +// 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 nothing +} + +var _ = fs.HandleReleaser(&ReleaseWaiter{}) + +func (r *ReleaseWaiter) init() { + r.once.Do(func() { + r.seen = make(chan nothing, 1) + }) +} + +func (r *ReleaseWaiter) Release(ctx context.Context, req *fuse.ReleaseRequest) error { + r.init() + close(r.seen) + return nil +} + +// WaitForRelease waits for Release to be called. +// +// With zero duration, wait forever. Otherwise, timeout early +// in a more controller way than `-test.timeout`. +// +// Returns whether a Release was seen. Always true if dur==0. +func (r *ReleaseWaiter) WaitForRelease(dur time.Duration) bool { + r.init() + var timeout <-chan time.Time + if dur > 0 { + timeout = time.After(dur) + } + select { + case <-r.seen: + return true + case <-timeout: + return false + } +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/testfs.go b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/testfs.go new file mode 100644 index 000000000..63be5b1ca --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/testfs.go @@ -0,0 +1,52 @@ +package fstestutil + +import ( + "os" + + "bazil.org/fuse" + "bazil.org/fuse/fs" + "golang.org/x/net/context" +) + +// 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(a *fuse.Attr) { + a.Mode = 0666 +} + +// Dir can be embedded in a struct to make it look like a directory. +type Dir struct{} + +func (f Dir) Attr(a *fuse.Attr) { + a.Mode = os.ModeDir | 0777 +} + +// ChildMap is a directory with child nodes looked up from a map. +type ChildMap map[string]fs.Node + +var _ = fs.Node(ChildMap{}) +var _ = fs.NodeStringLookuper(ChildMap{}) + +func (f ChildMap) Attr(a *fuse.Attr) { + a.Mode = os.ModeDir | 0777 +} + +func (f ChildMap) Lookup(ctx context.Context, name string) (fs.Node, error) { + child, ok := f[name] + if !ok { + return nil, fuse.ENOENT + } + return child, nil +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/helpers_test.go b/Godeps/_workspace/src/bazil.org/fuse/fs/helpers_test.go new file mode 100644 index 000000000..e37877c17 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/helpers_test.go @@ -0,0 +1,67 @@ +package fs_test + +import ( + "errors" + "flag" + "os" + "os/exec" + "path/filepath" + "testing" +) + +var childHelpers = map[string]func(){} + +type childProcess struct { + name string + fn func() +} + +var _ flag.Value = (*childProcess)(nil) + +func (c *childProcess) String() string { + return c.name +} + +func (c *childProcess) Set(s string) error { + fn, ok := childHelpers[s] + if !ok { + return errors.New("helper not found") + } + c.name = s + c.fn = fn + return nil +} + +var childMode childProcess + +func init() { + flag.Var(&childMode, "fuse.internal.child", "internal use only") +} + +// childCmd prepares a test function to be run in a subprocess, with +// childMode set to true. Caller must still call Run or Start. +// +// Re-using the test executable as the subprocess is useful because +// now test executables can e.g. be cross-compiled, transferred +// between hosts, and run in settings where the whole Go development +// environment is not installed. +func childCmd(childName string) (*exec.Cmd, error) { + // caller may set cwd, so we can't rely on relative paths + executable, err := filepath.Abs(os.Args[0]) + if err != nil { + return nil, err + } + cmd := exec.Command(executable, "-fuse.internal.child="+childName) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd, nil +} + +func TestMain(m *testing.M) { + flag.Parse() + if childMode.fn != nil { + childMode.fn() + os.Exit(0) + } + os.Exit(m.Run()) +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/serve.go b/Godeps/_workspace/src/bazil.org/fuse/fs/serve.go new file mode 100644 index 000000000..bbc4f6140 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/serve.go @@ -0,0 +1,1336 @@ +// FUSE service loop, for servers that wish to use it. + +package fs + +import ( + "encoding/binary" + "fmt" + "hash/fnv" + "io" + "reflect" + "strings" + "sync" + "time" + + "golang.org/x/net/context" +) + +import ( + "bazil.org/fuse" + "bazil.org/fuse/fuseutil" +) + +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 FSIniter. +type FS interface { + // Root is called to obtain the Node for the file system root. + Root() (Node, error) +} + +type FSIniter interface { + // Init is called to initialize the FUSE connection. + // It can inspect the request and adjust the response as desired. + // Init must return promptly. + Init(ctx context.Context, req *fuse.InitRequest, resp *fuse.InitResponse) 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. + 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. +// +// Other FUSE requests can be handled by implementing methods from the +// Node* interfaces, for example NodeOpener. +type Node interface { + Attr(*fuse.Attr) +} + +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. Not implementing Setattr causes writes to be unable + // to grow the file (except with OpenDirectIO, which bypasses that + // mechanism). + // + // 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 nil, err. + // + // 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 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(n Node) (attr fuse.Attr) { + attr.Nlink = 1 + attr.Atime = startTime + attr.Mtime = startTime + attr.Ctime = startTime + attr.Crtime = startTime + n.Attr(&attr) + return +} + +// 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. + // + // 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. + // + // Note that file size changes are communicated through Setattr. + // Writes beyond the size of the file as reported by Attr are not + // even attempted (except in OpenDirectIO mode). + Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error +} + +type HandleReleaser interface { + Release(ctx context.Context, req *fuse.ReleaseRequest) error +} + +type Server struct { + FS FS + + // 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{}) +} + +// 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(c *fuse.Conn) error { + sc := serveConn{ + fs: s.FS, + debug: s.Debug, + req: map[fuse.RequestID]*serveRequest{}, + dynamicInode: GenerateDynamicInode, + } + if sc.debug == nil { + sc.debug = fuse.Debug + } + if dyn, ok := sc.fs.(FSInodeGenerator); ok { + sc.dynamicInode = dyn.GenerateInode + } + + root, err := sc.fs.Root() + if err != nil { + return fmt.Errorf("cannot obtain root node: %v", err) + } + sc.node = append(sc.node, nil, &serveNode{inode: 1, node: root, refs: 1}) + sc.handle = append(sc.handle, nil) + + for { + req, err := c.ReadRequest() + if err != nil { + if err == io.EOF { + break + } + return err + } + + go sc.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 := Server{ + FS: fs, + } + return server.Serve(c) +} + +type nothing struct{} + +type serveConn struct { + meta sync.Mutex + fs FS + req map[fuse.RequestID]*serveRequest + node []*serveNode + handle []*serveHandle + freeNode []fuse.NodeID + freeHandle []fuse.HandleID + nodeGen uint64 + debug func(msg interface{}) + dynamicInode func(parent uint64, name string) uint64 +} + +type serveRequest struct { + Request fuse.Request + cancel func() +} + +type serveNode struct { + inode uint64 + node Node + refs uint64 +} + +func (sn *serveNode) attr() (attr fuse.Attr) { + attr = nodeAttr(sn.node) + if attr.Inode == 0 { + attr.Inode = sn.inode + } + return +} + +type serveHandle struct { + handle Handle + readData []byte + nodeID fuse.NodeID +} + +// NodeRef can be embedded in a Node to recognize the same Node being +// returned from multiple Lookup, Create etc calls. +// +// 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 NodeRef struct { + id fuse.NodeID + generation uint64 +} + +// nodeRef is only ever accessed while holding serveConn.meta +func (n *NodeRef) nodeRef() *NodeRef { + return n +} + +type nodeRef interface { + nodeRef() *NodeRef +} + +func (c *serveConn) saveNode(inode uint64, node Node) (id fuse.NodeID, gen uint64) { + c.meta.Lock() + defer c.meta.Unlock() + + var ref *NodeRef + if nodeRef, ok := node.(nodeRef); ok { + ref = nodeRef.nodeRef() + + if ref.id != 0 { + // dropNode guarantees that NodeRef is zeroed at the same + // time as the NodeID is removed from serveConn.node, as + // guarded by c.meta; this means sn cannot be nil here + sn := c.node[ref.id] + sn.refs++ + return ref.id, ref.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) + } + gen = c.nodeGen + if ref != nil { + ref.id = id + ref.generation = gen + } + return +} + +func (c *serveConn) saveHandle(handle Handle, nodeID fuse.NodeID) (id fuse.HandleID) { + c.meta.Lock() + shandle := &serveHandle{handle: handle, nodeID: nodeID} + 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) +} + +func (c *serveConn) dropNode(id fuse.NodeID, n uint64) (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 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 { + c.node[id] = nil + if nodeRef, ok := snode.node.(nodeRef); ok { + ref := nodeRef.nodeRef() + *ref = NodeRef{} + } + c.freeNode = append(c.freeNode, id) + return true + } + return false +} + +func (c *serveConn) 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 *serveConn) 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 { + Op string + Request *fuse.Header + 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=%#x", 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("-> %s error=%s %s", r.Request, r.errstr(), r.Out) + case r.Errno != "": + return fmt.Sprintf("-> %s error=%s", r.Request, r.errstr()) + case r.Out != nil: + // make sure (seemingly) empty values are readable + switch r.Out.(type) { + case string: + return fmt.Sprintf("-> %s %q", r.Request, r.Out) + case []byte: + return fmt.Sprintf("-> %s [% x]", r.Request, r.Out) + default: + return fmt.Sprintf("-> %s %s", r.Request, r.Out) + } + default: + return fmt.Sprintf("-> %s", r.Request) + } +} + +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 %#x), 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 %#x), node %d not found", m.Request.Hdr().ID, m.In.NewDir) +} + +func (c *serveConn) serve(r fuse.Request) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + req := &serveRequest{Request: r, cancel: cancel} + + c.debug(request{ + Op: opName(r), + Request: r.Hdr(), + 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() + c.debug(response{ + Op: opName(r), + Request: logResponseHeader{ID: hdr.ID}, + Error: fuse.ESTALE.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(fuse.ESTALE) + 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 { + msg.Error = err.Error() + if ferr, ok := err.(fuse.ErrorNumber); ok { + errno := ferr.Errno() + msg.Errno = errno.ErrnoName() + if errno == err { + // it's just a fuse.Errno with no extra detail; + // skip the textual message for log readability + msg.Error = "" + } + } else { + msg.Errno = fuse.DefaultErrno.ErrnoName() + } + } else { + msg.Out = resp + } + c.debug(msg) + + c.meta.Lock() + delete(c.req, hdr.ID) + c.meta.Unlock() + } + + 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. + done(fuse.ENOSYS) + r.RespondError(fuse.ENOSYS) + + // FS operations. + case *fuse.InitRequest: + s := &fuse.InitResponse{ + MaxWrite: 128 * 1024, + Flags: fuse.InitBigWrites, + } + if fs, ok := c.fs.(FSIniter); ok { + if err := fs.Init(ctx, r, s); err != nil { + done(err) + r.RespondError(err) + break + } + } + done(s) + r.Respond(s) + + case *fuse.StatfsRequest: + s := &fuse.StatfsResponse{} + if fs, ok := c.fs.(FSStatfser); ok { + if err := fs.Statfs(ctx, r, s); err != nil { + done(err) + r.RespondError(err) + break + } + } + done(s) + r.Respond(s) + + // Node operations. + case *fuse.GetattrRequest: + s := &fuse.GetattrResponse{} + if n, ok := node.(NodeGetattrer); ok { + if err := n.Getattr(ctx, r, s); err != nil { + done(err) + r.RespondError(err) + break + } + } else { + s.AttrValid = attrValidTime + s.Attr = snode.attr() + } + done(s) + r.Respond(s) + + case *fuse.SetattrRequest: + s := &fuse.SetattrResponse{} + if n, ok := node.(NodeSetattrer); ok { + if err := n.Setattr(ctx, r, s); err != nil { + done(err) + r.RespondError(err) + break + } + done(s) + r.Respond(s) + break + } + + if s.AttrValid == 0 { + s.AttrValid = attrValidTime + } + s.Attr = snode.attr() + done(s) + r.Respond(s) + + case *fuse.SymlinkRequest: + s := &fuse.SymlinkResponse{} + n, ok := node.(NodeSymlinker) + if !ok { + done(fuse.EIO) // XXX or EPERM like Mkdir? + r.RespondError(fuse.EIO) + break + } + n2, err := n.Symlink(ctx, r) + if err != nil { + done(err) + r.RespondError(err) + break + } + c.saveLookup(&s.LookupResponse, snode, r.NewName, n2) + done(s) + r.Respond(s) + + case *fuse.ReadlinkRequest: + n, ok := node.(NodeReadlinker) + if !ok { + done(fuse.EIO) /// XXX or EPERM? + r.RespondError(fuse.EIO) + break + } + target, err := n.Readlink(ctx, r) + if err != nil { + done(err) + r.RespondError(err) + break + } + done(target) + r.Respond(target) + + case *fuse.LinkRequest: + n, ok := node.(NodeLinker) + if !ok { + done(fuse.EIO) /// XXX or EPERM? + r.RespondError(fuse.EIO) + break + } + 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, + }) + done(fuse.EIO) + r.RespondError(fuse.EIO) + break + } + n2, err := n.Link(ctx, r, oldNode.node) + if err != nil { + done(err) + r.RespondError(err) + break + } + s := &fuse.LookupResponse{} + c.saveLookup(s, snode, r.NewName, n2) + done(s) + r.Respond(s) + + case *fuse.RemoveRequest: + n, ok := node.(NodeRemover) + if !ok { + done(fuse.EIO) /// XXX or EPERM? + r.RespondError(fuse.EIO) + break + } + err := n.Remove(ctx, r) + if err != nil { + done(err) + r.RespondError(err) + break + } + done(nil) + r.Respond() + + case *fuse.AccessRequest: + if n, ok := node.(NodeAccesser); ok { + if err := n.Access(ctx, r); err != nil { + done(err) + r.RespondError(err) + break + } + } + done(nil) + r.Respond() + + case *fuse.LookupRequest: + var n2 Node + var err error + s := &fuse.LookupResponse{} + 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 { + done(fuse.ENOENT) + r.RespondError(fuse.ENOENT) + break + } + if err != nil { + done(err) + r.RespondError(err) + break + } + c.saveLookup(s, snode, r.Name, n2) + done(s) + r.Respond(s) + + case *fuse.MkdirRequest: + s := &fuse.MkdirResponse{} + n, ok := node.(NodeMkdirer) + if !ok { + done(fuse.EPERM) + r.RespondError(fuse.EPERM) + break + } + n2, err := n.Mkdir(ctx, r) + if err != nil { + done(err) + r.RespondError(err) + break + } + c.saveLookup(&s.LookupResponse, snode, r.Name, n2) + done(s) + r.Respond(s) + + 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 { + done(err) + r.RespondError(err) + break + } + h2 = hh + } else { + h2 = node + } + s.Handle = c.saveHandle(h2, hdr.Node) + done(s) + r.Respond(s) + + case *fuse.CreateRequest: + n, ok := node.(NodeCreater) + if !ok { + // If we send back ENOSYS, FUSE will try mknod+open. + done(fuse.EPERM) + r.RespondError(fuse.EPERM) + break + } + s := &fuse.CreateResponse{OpenResponse: fuse.OpenResponse{}} + n2, h2, err := n.Create(ctx, r, s) + if err != nil { + done(err) + r.RespondError(err) + break + } + c.saveLookup(&s.LookupResponse, snode, r.Name, n2) + s.Handle = c.saveHandle(h2, hdr.Node) + done(s) + r.Respond(s) + + case *fuse.GetxattrRequest: + n, ok := node.(NodeGetxattrer) + if !ok { + done(fuse.ENOTSUP) + r.RespondError(fuse.ENOTSUP) + break + } + s := &fuse.GetxattrResponse{} + err := n.Getxattr(ctx, r, s) + if err != nil { + done(err) + r.RespondError(err) + break + } + if r.Size != 0 && uint64(len(s.Xattr)) > uint64(r.Size) { + done(fuse.ERANGE) + r.RespondError(fuse.ERANGE) + break + } + done(s) + r.Respond(s) + + case *fuse.ListxattrRequest: + n, ok := node.(NodeListxattrer) + if !ok { + done(fuse.ENOTSUP) + r.RespondError(fuse.ENOTSUP) + break + } + s := &fuse.ListxattrResponse{} + err := n.Listxattr(ctx, r, s) + if err != nil { + done(err) + r.RespondError(err) + break + } + if r.Size != 0 && uint64(len(s.Xattr)) > uint64(r.Size) { + done(fuse.ERANGE) + r.RespondError(fuse.ERANGE) + break + } + done(s) + r.Respond(s) + + case *fuse.SetxattrRequest: + n, ok := node.(NodeSetxattrer) + if !ok { + done(fuse.ENOTSUP) + r.RespondError(fuse.ENOTSUP) + break + } + err := n.Setxattr(ctx, r) + if err != nil { + done(err) + r.RespondError(err) + break + } + done(nil) + r.Respond() + + case *fuse.RemovexattrRequest: + n, ok := node.(NodeRemovexattrer) + if !ok { + done(fuse.ENOTSUP) + r.RespondError(fuse.ENOTSUP) + break + } + err := n.Removexattr(ctx, r) + if err != nil { + done(err) + r.RespondError(err) + break + } + done(nil) + r.Respond() + + case *fuse.ForgetRequest: + forget := c.dropNode(hdr.Node, r.N) + if forget { + n, ok := node.(NodeForgetter) + if ok { + n.Forget() + } + } + done(nil) + r.Respond() + + // Handle operations. + case *fuse.ReadRequest: + shandle := c.getHandle(r.Handle) + if shandle == nil { + done(fuse.ESTALE) + r.RespondError(fuse.ESTALE) + return + } + handle := shandle.handle + + s := &fuse.ReadResponse{Data: make([]byte, 0, r.Size)} + if r.Dir { + if h, ok := handle.(HandleReadDirAller); ok { + if shandle.readData == nil { + dirs, err := h.ReadDirAll(ctx) + if err != nil { + done(err) + r.RespondError(err) + break + } + 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) + break + } + } else { + if h, ok := handle.(HandleReadAller); ok { + if shandle.readData == nil { + data, err := h.ReadAll(ctx) + if err != nil { + done(err) + r.RespondError(err) + break + } + if data == nil { + data = []byte{} + } + shandle.readData = data + } + fuseutil.HandleRead(r, s, shandle.readData) + done(s) + r.Respond(s) + break + } + h, ok := handle.(HandleReader) + if !ok { + fmt.Printf("NO READ FOR %T\n", handle) + done(fuse.EIO) + r.RespondError(fuse.EIO) + break + } + if err := h.Read(ctx, r, s); err != nil { + done(err) + r.RespondError(err) + break + } + } + done(s) + r.Respond(s) + + case *fuse.WriteRequest: + shandle := c.getHandle(r.Handle) + if shandle == nil { + done(fuse.ESTALE) + r.RespondError(fuse.ESTALE) + return + } + + s := &fuse.WriteResponse{} + if h, ok := shandle.handle.(HandleWriter); ok { + if err := h.Write(ctx, r, s); err != nil { + done(err) + r.RespondError(err) + break + } + done(s) + r.Respond(s) + break + } + done(fuse.EIO) + r.RespondError(fuse.EIO) + + case *fuse.FlushRequest: + shandle := c.getHandle(r.Handle) + if shandle == nil { + done(fuse.ESTALE) + r.RespondError(fuse.ESTALE) + return + } + handle := shandle.handle + + if h, ok := handle.(HandleFlusher); ok { + if err := h.Flush(ctx, r); err != nil { + done(err) + r.RespondError(err) + break + } + } + done(nil) + r.Respond() + + case *fuse.ReleaseRequest: + shandle := c.getHandle(r.Handle) + if shandle == nil { + done(fuse.ESTALE) + r.RespondError(fuse.ESTALE) + return + } + 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 { + done(err) + r.RespondError(err) + break + } + } + done(nil) + r.Respond() + + case *fuse.DestroyRequest: + if fs, ok := c.fs.(FSDestroyer); ok { + fs.Destroy() + } + done(nil) + r.Respond() + + 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, + }) + done(fuse.EIO) + r.RespondError(fuse.EIO) + break + } + n, ok := node.(NodeRenamer) + if !ok { + done(fuse.EIO) // XXX or EPERM like Mkdir? + r.RespondError(fuse.EIO) + break + } + err := n.Rename(ctx, r, newDirNode.node) + if err != nil { + done(err) + r.RespondError(err) + break + } + done(nil) + r.Respond() + + case *fuse.MknodRequest: + n, ok := node.(NodeMknoder) + if !ok { + done(fuse.EIO) + r.RespondError(fuse.EIO) + break + } + n2, err := n.Mknod(ctx, r) + if err != nil { + done(err) + r.RespondError(err) + break + } + s := &fuse.LookupResponse{} + c.saveLookup(s, snode, r.Name, n2) + done(s) + r.Respond(s) + + case *fuse.FsyncRequest: + n, ok := node.(NodeFsyncer) + if !ok { + done(fuse.EIO) + r.RespondError(fuse.EIO) + break + } + err := n.Fsync(ctx, r) + if err != nil { + done(err) + r.RespondError(err) + break + } + done(nil) + r.Respond() + + 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() + + /* case *FsyncdirRequest: + done(ENOSYS) + r.RespondError(ENOSYS) + + case *GetlkRequest, *SetlkRequest, *SetlkwRequest: + done(ENOSYS) + r.RespondError(ENOSYS) + + case *BmapRequest: + done(ENOSYS) + r.RespondError(ENOSYS) + + case *SetvolnameRequest, *GetxtimesRequest, *ExchangeRequest: + done(ENOSYS) + r.RespondError(ENOSYS) + */ + } +} + +func (c *serveConn) saveLookup(s *fuse.LookupResponse, snode *serveNode, elem string, n2 Node) { + s.Attr = nodeAttr(n2) + if s.Attr.Inode == 0 { + s.Attr.Inode = c.dynamicInode(snode.inode, elem) + } + + s.Node, s.Generation = c.saveNode(s.Attr.Inode, n2) + if s.EntryValid == 0 { + s.EntryValid = entryValidTime + } + if s.AttrValid == 0 { + s.AttrValid = attrValidTime + } +} + +// 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 != 0 { + break + } + // there's a tiny probability that result is zero; change the + // input a little and try again + _, _ = h.Write([]byte{'x'}) + } + return inode +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/serve_test.go b/Godeps/_workspace/src/bazil.org/fuse/fs/serve_test.go new file mode 100644 index 000000000..6d614ae78 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/serve_test.go @@ -0,0 +1,1843 @@ +package fs_test + +import ( + "bytes" + "errors" + "io" + "io/ioutil" + "log" + "os" + "os/exec" + "runtime" + "strings" + "sync" + "syscall" + "testing" + "time" + + "bazil.org/fuse" + "bazil.org/fuse/fs" + "bazil.org/fuse/fs/fstestutil" + "bazil.org/fuse/fs/fstestutil/record" + "bazil.org/fuse/fuseutil" + "bazil.org/fuse/syscallx" + "golang.org/x/net/context" +) + +// 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 { + target string +} + +func (f symlink) Attr(a *fuse.Attr) { + a.Mode = os.ModeSymlink | 0666 +} + +// fifo can be embedded in a struct to make it look like a named pipe. +type fifo struct{} + +func (f fifo) Attr(a *fuse.Attr) { + a.Mode = os.ModeNamedPipe | 0666 +} + +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) { + t.Parallel() + mnt, err := fstestutil.MountedT(t, badRootFS{}) + 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 testStatFS struct{} + +func (f testStatFS) Root() (fs.Node, error) { + return f, nil +} + +func (f testStatFS) Attr(a *fuse.Attr) { + a.Inode = 1 + a.Mode = os.ModeDir | 0777 +} + +func (f testStatFS) Statfs(ctx context.Context, req *fuse.StatfsRequest, resp *fuse.StatfsResponse) error { + resp.Blocks = 42 + resp.Files = 13 + return nil +} + +func TestStatfs(t *testing.T) { + t.Parallel() + mnt, err := fstestutil.MountedT(t, testStatFS{}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + { + var st syscall.Statfs_t + err = syscall.Statfs(mnt.Dir, &st) + if err != nil { + t.Errorf("Statfs failed: %v", err) + } + t.Logf("Statfs got: %#v", st) + if g, e := st.Blocks, uint64(42); g != e { + t.Errorf("got Blocks = %d; want %d", g, e) + } + if g, e := st.Files, uint64(13); g != e { + t.Errorf("got Files = %d; want %d", g, e) + } + } + + { + var st syscall.Statfs_t + f, err := os.Open(mnt.Dir) + if err != nil { + t.Errorf("Open for fstatfs failed: %v", err) + } + defer f.Close() + err = syscall.Fstatfs(int(f.Fd()), &st) + if err != nil { + t.Errorf("Fstatfs failed: %v", err) + } + t.Logf("Fstatfs got: %#v", st) + if g, e := st.Blocks, uint64(42); g != e { + t.Errorf("got Blocks = %d; want %d", g, e) + } + if g, e := st.Files, uint64(13); g != e { + t.Errorf("got Files = %d; want %d", g, e) + } + } + +} + +// Test Stat of root. + +type root struct{} + +func (f root) Root() (fs.Node, error) { + return f, nil +} + +func (root) Attr(a *fuse.Attr) { + a.Inode = 1 + a.Mode = os.ModeDir | 0555 +} + +func TestStatRoot(t *testing.T) { + t.Parallel() + mnt, err := fstestutil.MountedT(t, root{}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + fi, err := os.Stat(mnt.Dir) + if err != nil { + t.Fatalf("root getattr failed with %v", err) + } + mode := fi.Mode() + if (mode & os.ModeType) != os.ModeDir { + t.Errorf("root is not a directory: %#v", fi) + } + if mode.Perm() != 0555 { + t.Errorf("root has weird access mode: %v", mode.Perm()) + } + switch stat := fi.Sys().(type) { + case *syscall.Stat_t: + if stat.Ino != 1 { + t.Errorf("root has wrong inode: %v", stat.Ino) + } + if stat.Nlink != 1 { + t.Errorf("root has wrong link count: %v", stat.Nlink) + } + if stat.Uid != 0 { + t.Errorf("root has wrong uid: %d", stat.Uid) + } + if stat.Gid != 0 { + t.Errorf("root has wrong gid: %d", stat.Gid) + } + } +} + +// Test Read calling ReadAll. + +type readAll struct { + fstestutil.File +} + +const hi = "hello, world" + +func (readAll) Attr(a *fuse.Attr) { + a.Mode = 0666 + a.Size = uint64(len(hi)) +} + +func (readAll) ReadAll(ctx context.Context) ([]byte, error) { + return []byte(hi), nil +} + +func testReadAll(t *testing.T, path string) { + data, err := ioutil.ReadFile(path) + if err != nil { + t.Fatalf("readAll: %v", err) + } + if string(data) != hi { + t.Errorf("readAll = %q, want %q", data, hi) + } +} + +func TestReadAll(t *testing.T) { + t.Parallel() + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": readAll{}}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + testReadAll(t, mnt.Dir+"/child") +} + +// Test Read. + +type readWithHandleRead struct { + fstestutil.File +} + +func (readWithHandleRead) Attr(a *fuse.Attr) { + a.Mode = 0666 + a.Size = uint64(len(hi)) +} + +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) { + t.Parallel() + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": readWithHandleRead{}}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + testReadAll(t, mnt.Dir+"/child") +} + +// Test Release. + +type release struct { + fstestutil.File + record.ReleaseWaiter +} + +func TestRelease(t *testing.T) { + t.Parallel() + r := &release{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": r}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + f, err := os.Open(mnt.Dir + "/child") + if err != nil { + t.Fatal(err) + } + f.Close() + if !r.WaitForRelease(1 * time.Second) { + t.Error("Close did not Release in time") + } +} + +// Test Write calling basic Write, with an fsync thrown in too. + +type write struct { + fstestutil.File + record.Writes + record.Fsyncs +} + +func TestWrite(t *testing.T) { + t.Parallel() + w := &write{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": w}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + f, err := os.Create(mnt.Dir + "/child") + if err != nil { + t.Fatalf("Create: %v", err) + } + defer f.Close() + n, err := f.Write([]byte(hi)) + if err != nil { + t.Fatalf("Write: %v", err) + } + if n != len(hi) { + t.Fatalf("short write; n=%d; hi=%d", n, len(hi)) + } + + err = syscall.Fsync(int(f.Fd())) + if err != nil { + t.Fatalf("Fsync = %v", err) + } + if w.RecordedFsync() == (fuse.FsyncRequest{}) { + t.Errorf("never received expected fsync call") + } + + err = f.Close() + if err != nil { + t.Fatalf("Close: %v", err) + } + + if got := string(w.RecordedWriteData()); got != hi { + t.Errorf("write = %q, want %q", got, hi) + } +} + +// Test Write of a larger buffer. + +type writeLarge struct { + fstestutil.File + record.Writes +} + +func TestWriteLarge(t *testing.T) { + t.Parallel() + w := &write{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": w}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + f, err := os.Create(mnt.Dir + "/child") + if err != nil { + t.Fatalf("Create: %v", err) + } + defer f.Close() + const one = "xyzzyfoo" + large := bytes.Repeat([]byte(one), 8192) + n, err := f.Write(large) + if err != nil { + t.Fatalf("Write: %v", err) + } + if g, e := n, len(large); g != e { + t.Fatalf("short write: %d != %d", g, e) + } + + err = f.Close() + if err != nil { + t.Fatalf("Close: %v", err) + } + + got := w.RecordedWriteData() + if g, e := len(got), len(large); g != e { + t.Errorf("write wrong length: %d != %d", g, e) + } + if g := strings.Replace(string(got), one, "", -1); g != "" { + 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 +} + +func TestWriteTruncateFlush(t *testing.T) { + t.Parallel() + w := &writeTruncateFlush{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": w}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + err = ioutil.WriteFile(mnt.Dir+"/child", []byte(hi), 0666) + if err != nil { + t.Fatalf("WriteFile: %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 TestMkdir(t *testing.T) { + t.Parallel() + f := &mkdir1{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + // uniform umask needed to make os.Mkdir's mode into something + // reproducible + defer syscall.Umask(syscall.Umask(0022)) + err = os.Mkdir(mnt.Dir+"/foo", 0771) + if err != nil { + t.Fatalf("mkdir: %v", err) + } + want := fuse.MkdirRequest{Name: "foo", Mode: os.ModeDir | 0751} + if g, e := f.RecordedMkdir(), want; g != e { + t.Errorf("mkdir saw %+v, want %+v", g, e) + } +} + +// Test Create (and fsync) + +type create1file struct { + fstestutil.File + record.Fsyncs +} + +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, fuse.EPERM + } + flags := req.Flags + + // OS X does not pass O_TRUNC here, Linux does; as this is a + // Create, that's acceptable + flags &^= fuse.OpenTruncate + + if runtime.GOOS == "linux" { + // Linux <3.7 accidentally leaks O_CLOEXEC through to FUSE; + // avoid spurious test failures + flags &^= fuse.OpenFlags(syscall.O_CLOEXEC) + } + + if g, e := flags, fuse.OpenReadWrite|fuse.OpenCreate; g != e { + log.Printf("ERROR create1.Create unexpected flags: %v != %v\n", g, e) + return nil, nil, fuse.EPERM + } + if g, e := req.Mode, os.FileMode(0644); g != e { + log.Printf("ERROR create1.Create unexpected mode: %v != %v\n", g, e) + return nil, nil, fuse.EPERM + } + return &f.f, &f.f, nil +} + +func TestCreate(t *testing.T) { + t.Parallel() + f := &create1{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + // uniform umask needed to make os.Create's 0666 into something + // reproducible + defer syscall.Umask(syscall.Umask(0022)) + ff, err := os.Create(mnt.Dir + "/foo") + if err != nil { + t.Fatalf("create1 WriteFile: %v", err) + } + defer ff.Close() + + err = syscall.Fsync(int(ff.Fd())) + if err != nil { + t.Fatalf("Fsync = %v", err) + } + + if f.f.RecordedFsync() == (fuse.FsyncRequest{}) { + t.Errorf("never received expected fsync call") + } + + ff.Close() +} + +// 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, fuse.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, fuse.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 fuse.ENOENT +} + +func TestCreateWriteRemove(t *testing.T) { + t.Parallel() + f := &create3{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + err = ioutil.WriteFile(mnt.Dir+"/foo", []byte(hi), 0666) + if err != nil { + t.Fatalf("create3 WriteFile: %v", err) + } + if got := string(f.f.RecordedWriteData()); got != hi { + t.Fatalf("create3 write = %q, want %q", got, hi) + } + + err = os.Remove(mnt.Dir + "/foo") + if err != nil { + t.Fatalf("Remove: %v", err) + } + err = os.Remove(mnt.Dir + "/foo") + if err == nil { + t.Fatalf("second Remove = nil; want some error") + } +} + +// Test symlink + readlink + +// is a Node that is a symlink to target +type symlink1link struct { + symlink + target string +} + +func (f symlink1link) Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error) { + return f.target, nil +} + +type symlink1 struct { + fstestutil.Dir + record.Symlinks +} + +func (f *symlink1) Symlink(ctx context.Context, req *fuse.SymlinkRequest) (fs.Node, error) { + f.Symlinks.Symlink(ctx, req) + return symlink1link{target: req.Target}, nil +} + +func TestSymlink(t *testing.T) { + t.Parallel() + f := &symlink1{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + const target = "/some-target" + + err = os.Symlink(target, mnt.Dir+"/symlink.file") + if err != nil { + t.Fatalf("os.Symlink: %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) + } + + gotName, err := os.Readlink(mnt.Dir + "/symlink.file") + if err != nil { + t.Fatalf("os.Readlink: %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, fuse.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 +} + +func TestLink(t *testing.T) { + t.Parallel() + f := &link1{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + err = os.Link(mnt.Dir+"/old", mnt.Dir+"/new") + if err != nil { + t.Fatalf("Link: %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, fuse.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 fuse.EIO +} + +func TestRename(t *testing.T) { + t.Parallel() + f := &rename1{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + err = os.Rename(mnt.Dir+"/old", mnt.Dir+"/new") + if err != nil { + t.Fatalf("Rename: %v", err) + } + if g, e := f.renamed.Count(), uint32(1); g != e { + t.Fatalf("expected rename didn't happen: %d != %d", g, e) + } + err = os.Rename(mnt.Dir+"/old2", mnt.Dir+"/new2") + if err == nil { + t.Fatal("expected error on second Rename; got nil") + } +} + +// 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 TestMknod(t *testing.T) { + t.Parallel() + if os.Getuid() != 0 { + t.Skip("skipping unless root") + } + + f := &mknod1{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + defer syscall.Umask(syscall.Umask(0)) + err = syscall.Mknod(mnt.Dir+"/node", syscall.S_IFIFO|0666, 123) + if err != nil { + t.Fatalf("Mknod: %v", err) + } + + want := fuse.MknodRequest{ + Name: "node", + Mode: os.FileMode(os.ModeNamedPipe | 0666), + Rdev: uint32(123), + } + 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 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(a *fuse.Attr) { + a.Mode = 0666 + a.Size = uint64(len(hi)) +} + +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) { + t.Parallel() + f := &dataHandleTest{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + data, err := ioutil.ReadFile(mnt.Dir + "/child") + if err != nil { + t.Errorf("readAll: %v", err) + return + } + if string(data) != hi { + t.Errorf("readAll = %q, want %q", data, hi) + } +} + +// Test interrupt + +type interrupt struct { + fstestutil.File + + // strobes to signal we have a read hanging + hanging chan struct{} +} + +func (interrupt) Attr(a *fuse.Attr) { + a.Mode = 0666 + a.Size = 1 +} + +func (it *interrupt) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { + select { + case it.hanging <- struct{}{}: + default: + } + <-ctx.Done() + return fuse.EINTR +} + +func TestInterrupt(t *testing.T) { + t.Parallel() + f := &interrupt{} + f.hanging = make(chan struct{}, 1) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + // start a subprocess that can hang until signaled + cmd := exec.Command("cat", mnt.Dir+"/child") + + err = cmd.Start() + if err != nil { + t.Errorf("interrupt: cannot start cat: %v", err) + return + } + + // try to clean up if child is still alive when returning + defer cmd.Process.Kill() + + // wait till we're sure it's hanging in read + <-f.hanging + + err = cmd.Process.Signal(os.Interrupt) + if err != nil { + t.Errorf("interrupt: cannot interrupt cat: %v", err) + return + } + + p, err := cmd.Process.Wait() + if err != nil { + t.Errorf("interrupt: cat bork: %v", err) + return + } + switch ws := p.Sys().(type) { + case syscall.WaitStatus: + if ws.CoreDump() { + t.Errorf("interrupt: didn't expect cat to dump core: %v", ws) + } + + if ws.Exited() { + t.Errorf("interrupt: didn't expect cat to exit normally: %v", ws) + } + + if !ws.Signaled() { + t.Errorf("interrupt: expected cat to get a signal: %v", ws) + } else { + if ws.Signal() != os.Interrupt { + t.Errorf("interrupt: cat got wrong signal: %v", ws) + } + } + default: + t.Logf("interrupt: this platform has no test coverage") + } +} + +// Test truncate + +type truncate struct { + fstestutil.File + record.Setattrs +} + +func testTruncate(t *testing.T, toSize int64) { + t.Parallel() + f := &truncate{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + err = os.Truncate(mnt.Dir+"/child", toSize) + if err != nil { + t.Fatalf("Truncate: %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 TestTruncate42(t *testing.T) { + testTruncate(t, 42) +} + +func TestTruncate0(t *testing.T) { + testTruncate(t, 0) +} + +// Test ftruncate + +type ftruncate struct { + fstestutil.File + record.Setattrs +} + +func testFtruncate(t *testing.T, toSize int64) { + t.Parallel() + f := &ftruncate{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + { + fil, err := os.OpenFile(mnt.Dir+"/child", os.O_WRONLY, 0666) + if err != nil { + t.Error(err) + return + } + defer fil.Close() + + err = fil.Truncate(toSize) + if err != nil { + t.Fatalf("Ftruncate: %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 TestFtruncate42(t *testing.T) { + testFtruncate(t, 42) +} + +func TestFtruncate0(t *testing.T) { + testFtruncate(t, 0) +} + +// Test opening existing file truncates + +type truncateWithOpen struct { + fstestutil.File + record.Setattrs +} + +func TestTruncateWithOpen(t *testing.T) { + t.Parallel() + f := &truncateWithOpen{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + fil, err := os.OpenFile(mnt.Dir+"/child", os.O_WRONLY|os.O_TRUNC, 0666) + if err != nil { + t.Error(err) + return + } + fil.Close() + + 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) + } + 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 TestReadDirAll(t *testing.T) { + t.Parallel() + f := &readDirAll{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + fil, err := os.Open(mnt.Dir) + if err != nil { + t.Error(err) + return + } + defer fil.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 := fil.Readdirnames(100) + if err != nil { + t.Error(err) + return + } + + 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 + } +} + +// Test readdir without any ReadDir methods implemented. + +type readDirNotImplemented struct { + fstestutil.Dir +} + +func TestReadDirNotImplemented(t *testing.T) { + t.Parallel() + f := &readDirNotImplemented{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + fil, err := os.Open(mnt.Dir) + if err != nil { + t.Error(err) + return + } + defer fil.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 := fil.Readdirnames(100) + if len(names) > 0 || err != io.EOF { + t.Fatalf("expected EOF got names=%v err=%v", names, err) + } +} + +// 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 fuse.EIO + } + f.Setattrs.Setattr(ctx, req, resp) + return nil +} + +func TestChmod(t *testing.T) { + t.Parallel() + f := &chmod{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + err = os.Chmod(mnt.Dir+"/child", 0764) + if err != nil { + t.Errorf("chmod: %v", err) + return + } + got := f.RecordedSetattr() + if g, e := got.Mode, os.FileMode(0764); g != e { + t.Errorf("wrong mode: %v != %v", g, e) + } +} + +// 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) { + t.Parallel() + f := &open{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + // node: mode only matters with O_CREATE + fil, err := os.OpenFile(mnt.Dir+"/child", os.O_WRONLY|os.O_APPEND, 0) + if err == nil { + t.Error("Open err == nil, expected ENAMETOOLONG") + fil.Close() + return + } + + switch err2 := err.(type) { + case *os.PathError: + if err2.Err == syscall.ENAMETOOLONG { + break + } + t.Errorf("unexpected inner error: %#v", err2) + default: + t.Errorf("unexpected error: %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 g, e := got, want; g != e { + t.Errorf("open saw %v, want %v", g, e) + return + } +} + +// Test Fsync on a dir + +type fsyncDir struct { + fstestutil.Dir + record.Fsyncs +} + +func TestFsyncDir(t *testing.T) { + t.Parallel() + f := &fsyncDir{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + fil, err := os.Open(mnt.Dir) + if err != nil { + t.Errorf("fsyncDir open: %v", err) + return + } + defer fil.Close() + err = fil.Sync() + if err != nil { + t.Errorf("fsyncDir sync: %v", err) + return + } + + 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 +} + +func TestGetxattr(t *testing.T) { + t.Parallel() + f := &getxattr{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + buf := make([]byte, 8192) + n, err := syscallx.Getxattr(mnt.Dir+"/child", "not-there", buf) + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + buf = buf[:n] + if g, e := string(buf), "hello, world"; g != e { + t.Errorf("wrong getxattr content: %#v != %#v", g, e) + } + seen := f.RecordedGetxattr() + if g, e := seen.Name, "not-there"; 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) { + t.Parallel() + f := &getxattrTooSmall{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + buf := make([]byte, 3) + _, err = syscallx.Getxattr(mnt.Dir+"/child", "whatever", buf) + if err == nil { + t.Error("Getxattr = nil; want some error") + } + if err != syscall.ERANGE { + t.Errorf("unexpected error: %v", err) + return + } +} + +// 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) { + t.Parallel() + f := &getxattrSize{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + n, err := syscallx.Getxattr(mnt.Dir+"/child", "whatever", nil) + if err != nil { + t.Errorf("Getxattr unexpected error: %v", err) + return + } + if g, e := n, 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("one", "two") + return nil +} + +func TestListxattr(t *testing.T) { + t.Parallel() + f := &listxattr{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + buf := make([]byte, 8192) + n, err := syscallx.Listxattr(mnt.Dir+"/child", buf) + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + buf = buf[:n] + if g, e := string(buf), "one\x00two\x00"; g != e { + t.Errorf("wrong listxattr content: %#v != %#v", g, e) + } + + want := fuse.ListxattrRequest{ + Size: 8192, + } + 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) { + t.Parallel() + f := &listxattrTooSmall{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + buf := make([]byte, 3) + _, err = syscallx.Listxattr(mnt.Dir+"/child", buf) + if err == nil { + t.Error("Listxattr = nil; want some error") + } + if err != syscall.ERANGE { + t.Errorf("unexpected error: %v", err) + return + } +} + +// 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) { + t.Parallel() + f := &listxattrSize{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + n, err := syscallx.Listxattr(mnt.Dir+"/child", nil) + if err != nil { + t.Errorf("Listxattr unexpected error: %v", err) + return + } + if g, e := n, len("one\x00two\x00"); g != e { + t.Errorf("Getxattr incorrect size: %d != %d", g, e) + } +} + +// Test Setxattr + +type setxattr struct { + fstestutil.File + record.Setxattrs +} + +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") + } + + t.Parallel() + f := &setxattr{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + const g = "hello, world" + greeting := strings.Repeat(g, size/len(g)+1)[:size] + err = syscallx.Setxattr(mnt.Dir+"/child", "greeting", []byte(greeting), 0) + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + // fuse.SetxattrRequest contains a byte slice and thus cannot be + // directly compared + got := f.RecordedSetxattr() + + if g, e := got.Name, "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) { + testSetxattr(t, 20) +} + +func TestSetxattr64kB(t *testing.T) { + testSetxattr(t, 64*1024) +} + +func TestSetxattr16MB(t *testing.T) { + testSetxattr(t, 16*1024*1024) +} + +// Test Removexattr + +type removexattr struct { + fstestutil.File + record.Removexattrs +} + +func TestRemovexattr(t *testing.T) { + t.Parallel() + f := &removexattr{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + err = syscallx.Removexattr(mnt.Dir+"/child", "greeting") + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + want := fuse.RemovexattrRequest{Name: "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") +} + +func TestDefaultErrno(t *testing.T) { + t.Parallel() + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{defaultErrno{}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + _, err = os.Stat(mnt.Dir + "/trigger") + if err == nil { + t.Fatalf("expected error") + } + + switch err2 := err.(type) { + case *os.PathError: + if err2.Err == syscall.EIO { + break + } + t.Errorf("unexpected inner error: Err=%v %#v", err2.Err, err2) + default: + t.Errorf("unexpected error: %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) { + t.Parallel() + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{customErrNode{}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + _, err = os.Stat(mnt.Dir + "/trigger") + if err == nil { + t.Fatalf("expected error") + } + + switch err2 := err.(type) { + case *os.PathError: + if err2.Err == syscall.ENAMETOOLONG { + break + } + t.Errorf("unexpected inner error: %#v", err2) + default: + t.Errorf("unexpected error: %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(a *fuse.Attr) { + f.mu.Lock() + defer f.mu.Unlock() + + a.Mode = 0666 + a.Size = uint64(len(f.data)) +} + +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 helperMmap() { + f, err := os.Create("child") + if err != nil { + log.Fatalf("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 { + log.Fatalf("Mmap: %v", err) + } + + for i, b := range mmapWrites { + data[i] = b + } + + if err := syscallx.Msync(data, syscall.MS_SYNC); err != nil { + log.Fatalf("Msync: %v", err) + } + + if err := syscall.Munmap(data); err != nil { + log.Fatalf("Munmap: %v", err) + } + + if err := f.Sync(); err != nil { + log.Fatalf("Fsync = %v", err) + } + + err = f.Close() + if err != nil { + log.Fatalf("Close: %v", err) + } +} + +func init() { + childHelpers["mmap"] = helperMmap +} + +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) { + + w := &mmap{} + w.data = make([]byte, mmapSize) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": w}}) + 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. + child, err := childCmd("mmap") + if err != nil { + t.Fatal(err) + } + child.Dir = mnt.Dir + if err := child.Run(); err != nil { + t.Fatal(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) { + t.Parallel() + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": directRead{}}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + testReadAll(t, mnt.Dir+"/child") +} + +// 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) { + t.Parallel() + w := &directWrite{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": w}}) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + f, err := os.OpenFile(mnt.Dir+"/child", os.O_RDWR, 0666) + if err != nil { + t.Fatalf("Create: %v", err) + } + defer f.Close() + n, err := f.Write([]byte(hi)) + if err != nil { + t.Fatalf("Write: %v", err) + } + if n != len(hi) { + t.Fatalf("short write; n=%d; hi=%d", n, len(hi)) + } + + err = f.Close() + if err != nil { + t.Fatalf("Close: %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(a *fuse.Attr) { + f.File.Attr(a) + a.Nlink = 0 +} + +func TestAttrUnlinked(t *testing.T) { + t.Parallel() + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": attrUnlinked{}}}) + + fi, err := os.Stat(mnt.Dir + "/child") + if err != nil { + t.Fatalf("Stat failed with %v", err) + } + switch stat := fi.Sys().(type) { + case *syscall.Stat_t: + if stat.Nlink != 0 { + t.Errorf("wrong link count: %v", stat.Nlink) + } + } +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/tree.go b/Godeps/_workspace/src/bazil.org/fuse/fs/tree.go new file mode 100644 index 000000000..07135ce2f --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/tree.go @@ -0,0 +1,98 @@ +// FUSE directory tree, for servers that wish to use it with the service loop. + +package fs + +import ( + "os" + pathpkg "path" + "strings" + + "golang.org/x/net/context" +) + +import ( + "bazil.org/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(a *fuse.Attr) { + a.Mode = os.ModeDir | 0555 +} + +func (t *tree) Lookup(ctx context.Context, name string) (Node, error) { + n := t.lookup(name) + if n != nil { + return n, nil + } + return nil, fuse.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 +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/fuse.go b/Godeps/_workspace/src/bazil.org/fuse/fuse.go new file mode 100644 index 000000000..8ef28cb60 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/fuse.go @@ -0,0 +1,2029 @@ +// 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. +// +// Errors 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 fuse.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, AllowRoot), 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 ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "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 + buf []byte + wio sync.Mutex + rio sync.RWMutex +} + +// 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 + return c, 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 + +// 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 + +// 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 + +// 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=%#x Node=%#x 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(out *outHeader, n uintptr) { + h.Conn.respond(out, n) + putMessage(h.msg) +} + +func (h *Header) respondData(out *outHeader, n uintptr, data []byte) { + h.Conn.respondData(out, n, data) + 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 +} + +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 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 +} + +func (h *Header) RespondError(err error) { + errno := DefaultErrno + if ferr, ok := err.(ErrorNumber); ok { + errno = ferr.Errno() + } + // FUSE uses negative errors! + // TODO: File bug report against OSXFUSE: positive error causes kernel panic. + out := &outHeader{Error: -int32(errno), Unique: uint64(h.ID)} + h.respond(out, unsafe.Sizeof(*out)) +} + +// Maximum file write size we are prepared to receive from the kernel. +const maxWrite = 16 * 1024 * 1024 + +// 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 & 0777) + 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 + default: + // no idea + mode |= os.ModeDevice + } + 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()) +} + +// 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: + req = &GetattrRequest{ + Header: m.Header(), + } + + 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: + in := (*mknodIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + name := m.bytes()[unsafe.Sizeof(*in):] + if len(name) < 2 || name[len(name)-1] != '\x00' { + goto corrupt + } + name = name[:len(name)-1] + req = &MknodRequest{ + Header: m.Header(), + Mode: fileMode(in.Mode), + Rdev: in.Rdev, + Name: string(name), + } + + case opMkdir: + in := (*mkdirIn)(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 = &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), + } + + 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() < unsafe.Sizeof(*in) { + goto corrupt + } + req = &ReadRequest{ + Header: m.Header(), + Dir: m.hdr.Opcode == opReaddir, + Handle: HandleID(in.Fh), + Offset: int64(in.Offset), + Size: int(in.Size), + } + + case opWrite: + in := (*writeIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + r := &WriteRequest{ + Header: m.Header(), + Handle: HandleID(in.Fh), + Offset: int64(in.Offset), + Flags: WriteFlags(in.WriteFlags), + } + buf := m.bytes()[unsafe.Sizeof(*in):] + 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: 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), + Flags: in.FlushFlags, + LockOwner: in.LockOwner, + } + + case opInit: + in := (*initIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + req = &InitRequest{ + Header: m.Header(), + Major: in.Major, + Minor: in.Minor, + MaxReadahead: in.MaxReadahead, + Flags: InitFlags(in.Flags), + } + + case opGetlk: + panic("opGetlk") + case opSetlk: + panic("opSetlk") + case opSetlkw: + panic("opSetlkw") + + case opAccess: + in := (*accessIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + req = &AccessRequest{ + Header: m.Header(), + Mask: in.Mask, + } + + case opCreate: + in := (*createIn)(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 = &CreateRequest{ + Header: m.Header(), + Flags: openFlags(in.Flags), + Mode: fileMode(in.Mode), + Name: string(name[:i]), + } + + 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: + panic("opBmap") + + case opDestroy: + req = &DestroyRequest{ + Header: m.Header(), + } + + // OS X + case opSetvolname: + panic("opSetvolname") + case opGetxtimes: + panic("opGetxtimes") + case opExchange: + panic("opExchange") + } + + 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. + h := m.Header() + return &h, 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) +} + +// safe to call even with nil error +func errorString(err error) string { + if err == nil { + return "" + } + return err.Error() +} + +func (c *Conn) respond(out *outHeader, n uintptr) { + c.wio.Lock() + defer c.wio.Unlock() + out.Len = uint32(n) + msg := (*[1 << 30]byte)(unsafe.Pointer(out))[:n] + nn, err := syscall.Write(c.fd(), msg) + if nn != len(msg) || err != nil { + Debug(bugShortKernelWrite{ + Written: int64(nn), + Length: int64(len(msg)), + Error: errorString(err), + Stack: stack(), + }) + } +} + +func (c *Conn) respondData(out *outHeader, n uintptr, data []byte) { + c.wio.Lock() + defer c.wio.Unlock() + // TODO: use writev + out.Len = uint32(n + uintptr(len(data))) + msg := make([]byte, out.Len) + copy(msg, (*[1 << 30]byte)(unsafe.Pointer(out))[:n]) + copy(msg[n:], data) + syscall.Write(c.fd(), msg) +} + +// An InitRequest is the first request sent on a FUSE file system. +type InitRequest struct { + Header `json:"-"` + Major uint32 + Minor uint32 + // Maximum readahead in bytes that the kernel plans to use. + MaxReadahead uint32 + Flags InitFlags +} + +var _ = Request(&InitRequest{}) + +func (r *InitRequest) String() string { + return fmt.Sprintf("Init [%s] %d.%d ra=%d fl=%v", &r.Header, r.Major, r.Minor, r.MaxReadahead, r.Flags) +} + +// An InitResponse is the response to an InitRequest. +type InitResponse struct { + // Maximum readahead in bytes that the kernel can use. Ignored if + // greater than InitRequest.MaxReadahead. + MaxReadahead uint32 + Flags InitFlags + // 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", *r) +} + +// Respond replies to the request with the given response. +func (r *InitRequest) Respond(resp *InitResponse) { + out := &initOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + Major: kernelVersion, + Minor: kernelMinorVersion, + MaxReadahead: resp.MaxReadahead, + Flags: uint32(resp.Flags), + 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(&out.outHeader, unsafe.Sizeof(*out)) +} + +// A StatfsRequest requests information about the mounted file system. +type StatfsRequest struct { + Header `json:"-"` +} + +var _ = Request(&StatfsRequest{}) + +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) { + out := &statfsOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + St: kstatfs{ + Blocks: resp.Blocks, + Bfree: resp.Bfree, + Bavail: resp.Bavail, + Files: resp.Files, + Bsize: resp.Bsize, + Namelen: resp.Namelen, + Frsize: resp.Frsize, + }, + } + r.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +// 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 %+v", *r) +} + +// 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{}) + +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() { + out := &outHeader{Unique: uint64(r.ID)} + r.respond(out, unsafe.Sizeof(*out)) +} + +// An Attr is the metadata for a single file or directory. +type Attr struct { + Inode uint64 // inode number + Size uint64 // size in bytes + Blocks uint64 // size in blocks + 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 + Uid uint32 // owner uid + Gid uint32 // group gid + Rdev uint32 // device numbers + Flags uint32 // chflags(2) flags (OS X only) +} + +func unix(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) { + out.Ino = a.Inode + out.Size = a.Size + out.Blocks = a.Blocks + out.Atime, out.AtimeNsec = unix(a.Atime) + out.Mtime, out.MtimeNsec = unix(a.Mtime) + out.Ctime, out.CtimeNsec = unix(a.Ctime) + out.SetCrtime(unix(a.Crtime)) + out.Mode = uint32(a.Mode) & 0777 + 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) + + return +} + +// A GetattrRequest asks for the metadata for the file denoted by r.Node. +type GetattrRequest struct { + Header `json:"-"` +} + +var _ = Request(&GetattrRequest{}) + +func (r *GetattrRequest) String() string { + return fmt.Sprintf("Getattr [%s]", &r.Header) +} + +// Respond replies to the request with the given response. +func (r *GetattrRequest) Respond(resp *GetattrResponse) { + out := &attrOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + AttrValid: uint64(resp.AttrValid / time.Second), + AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), + Attr: resp.Attr.attr(), + } + r.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +// A GetattrResponse is the response to a GetattrRequest. +type GetattrResponse struct { + AttrValid time.Duration // how long Attr can be cached + Attr Attr // file attributes +} + +func (r *GetattrResponse) String() string { + return fmt.Sprintf("Getattr %+v", *r) +} + +// 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{}) + +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 { + out := &getxattrOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + Size: uint32(len(resp.Xattr)), + } + r.respond(&out.outHeader, unsafe.Sizeof(*out)) + } else { + out := &outHeader{Unique: uint64(r.ID)} + r.respondData(out, unsafe.Sizeof(*out), resp.Xattr) + } +} + +// A GetxattrResponse is the response to a GetxattrRequest. +type GetxattrResponse struct { + Xattr []byte +} + +func (r *GetxattrResponse) String() string { + return fmt.Sprintf("Getxattr %x", 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{}) + +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 { + out := &getxattrOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + Size: uint32(len(resp.Xattr)), + } + r.respond(&out.outHeader, unsafe.Sizeof(*out)) + } else { + out := &outHeader{Unique: uint64(r.ID)} + r.respondData(out, unsafe.Sizeof(*out), resp.Xattr) + } +} + +// A ListxattrResponse is the response to a ListxattrRequest. +type ListxattrResponse struct { + Xattr []byte +} + +func (r *ListxattrResponse) String() string { + return fmt.Sprintf("Listxattr %x", 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{}) + +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() { + out := &outHeader{Unique: uint64(r.ID)} + r.respond(out, unsafe.Sizeof(*out)) +} + +// 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{}) + +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 %x%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() { + out := &outHeader{Unique: uint64(r.ID)} + r.respond(out, unsafe.Sizeof(*out)) +} + +// 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{}) + +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) { + out := &entryOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + Nodeid: uint64(resp.Node), + Generation: resp.Generation, + EntryValid: uint64(resp.EntryValid / time.Second), + EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), + AttrValid: uint64(resp.AttrValid / time.Second), + AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), + Attr: resp.Attr.attr(), + } + r.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +// A LookupResponse is the response to a LookupRequest. +type LookupResponse struct { + Node NodeID + Generation uint64 + EntryValid time.Duration + AttrValid time.Duration + Attr Attr +} + +func (r *LookupResponse) String() string { + return fmt.Sprintf("Lookup %+v", *r) +} + +// An OpenRequest asks to open a file or directory +type OpenRequest struct { + Header `json:"-"` + Dir bool // is this Opendir? + Flags OpenFlags +} + +var _ = Request(&OpenRequest{}) + +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) { + out := &openOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + Fh: uint64(resp.Handle), + OpenFlags: uint32(resp.Flags), + } + r.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +// A OpenResponse is the response to a OpenRequest. +type OpenResponse struct { + Handle HandleID + Flags OpenResponseFlags +} + +func (r *OpenResponse) String() string { + return fmt.Sprintf("Open %+v", *r) +} + +// A CreateRequest asks to create and open a file (not a directory). +type CreateRequest struct { + Header `json:"-"` + Name string + Flags OpenFlags + Mode os.FileMode +} + +var _ = Request(&CreateRequest{}) + +func (r *CreateRequest) String() string { + return fmt.Sprintf("Create [%s] %q fl=%v mode=%v", &r.Header, r.Name, r.Flags, r.Mode) +} + +// Respond replies to the request with the given response. +func (r *CreateRequest) Respond(resp *CreateResponse) { + out := &createOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + + Nodeid: uint64(resp.Node), + Generation: resp.Generation, + EntryValid: uint64(resp.EntryValid / time.Second), + EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), + AttrValid: uint64(resp.AttrValid / time.Second), + AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), + Attr: resp.Attr.attr(), + + Fh: uint64(resp.Handle), + OpenFlags: uint32(resp.Flags), + } + r.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +// 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 %+v", *r) +} + +// A MkdirRequest asks to create (but not open) a directory. +type MkdirRequest struct { + Header `json:"-"` + Name string + Mode os.FileMode +} + +var _ = Request(&MkdirRequest{}) + +func (r *MkdirRequest) String() string { + return fmt.Sprintf("Mkdir [%s] %q mode=%v", &r.Header, r.Name, r.Mode) +} + +// Respond replies to the request with the given response. +func (r *MkdirRequest) Respond(resp *MkdirResponse) { + out := &entryOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + Nodeid: uint64(resp.Node), + Generation: resp.Generation, + EntryValid: uint64(resp.EntryValid / time.Second), + EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), + AttrValid: uint64(resp.AttrValid / time.Second), + AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), + Attr: resp.Attr.attr(), + } + r.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +// A MkdirResponse is the response to a MkdirRequest. +type MkdirResponse struct { + LookupResponse +} + +func (r *MkdirResponse) String() string { + return fmt.Sprintf("Mkdir %+v", *r) +} + +// 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 +} + +var _ = Request(&ReadRequest{}) + +func (r *ReadRequest) String() string { + return fmt.Sprintf("Read [%s] %#x %d @%#x dir=%v", &r.Header, r.Handle, r.Size, r.Offset, r.Dir) +} + +// Respond replies to the request with the given response. +func (r *ReadRequest) Respond(resp *ReadResponse) { + out := &outHeader{Unique: uint64(r.ID)} + r.respondData(out, unsafe.Sizeof(*out), resp.Data) +} + +// 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 uint32 +} + +var _ = Request(&ReleaseRequest{}) + +func (r *ReleaseRequest) String() string { + return fmt.Sprintf("Release [%s] %#x fl=%v rfl=%v owner=%#x", &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() { + out := &outHeader{Unique: uint64(r.ID)} + r.respond(out, unsafe.Sizeof(*out)) +} + +// 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{}) + +func (r *DestroyRequest) String() string { + return fmt.Sprintf("Destroy [%s]", &r.Header) +} + +// Respond replies to the request. +func (r *DestroyRequest) Respond() { + out := &outHeader{Unique: uint64(r.ID)} + r.respond(out, unsafe.Sizeof(*out)) +} + +// 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{}) + +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() +} + +// 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 +} + +var _ = Request(&WriteRequest{}) + +func (r *WriteRequest) String() string { + return fmt.Sprintf("Write [%s] %#x %d @%d fl=%v", &r.Header, r.Handle, len(r.Data), r.Offset, r.Flags) +} + +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) { + out := &writeOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + Size: uint32(resp.Size), + } + r.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +// 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 %+v", *r) +} + +// 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 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{}) + +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=%#x", r.Handle) + } else { + fmt.Fprintf(&buf, " handle=INVALID-%#x", 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=%#x", r.Flags) + } + return buf.String() +} + +// Respond replies to the request with the given response, +// giving the updated attributes. +func (r *SetattrRequest) Respond(resp *SetattrResponse) { + out := &attrOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + AttrValid: uint64(resp.AttrValid / time.Second), + AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), + Attr: resp.Attr.attr(), + } + r.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +// A SetattrResponse is the response to a SetattrRequest. +type SetattrResponse struct { + AttrValid time.Duration // how long Attr can be cached + Attr Attr // file attributes +} + +func (r *SetattrResponse) String() string { + return fmt.Sprintf("Setattr %+v", *r) +} + +// 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 + Flags uint32 + LockOwner uint64 +} + +var _ = Request(&FlushRequest{}) + +func (r *FlushRequest) String() string { + return fmt.Sprintf("Flush [%s] %#x fl=%#x lk=%#x", &r.Header, r.Handle, r.Flags, r.LockOwner) +} + +// Respond replies to the request, indicating that the flush succeeded. +func (r *FlushRequest) Respond() { + out := &outHeader{Unique: uint64(r.ID)} + r.respond(out, unsafe.Sizeof(*out)) +} + +// 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{}) + +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() { + out := &outHeader{Unique: uint64(r.ID)} + r.respond(out, unsafe.Sizeof(*out)) +} + +// 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{}) + +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) { + out := &entryOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + Nodeid: uint64(resp.Node), + Generation: resp.Generation, + EntryValid: uint64(resp.EntryValid / time.Second), + EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), + AttrValid: uint64(resp.AttrValid / time.Second), + AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), + Attr: resp.Attr.attr(), + } + r.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +// A SymlinkResponse is the response to a SymlinkRequest. +type SymlinkResponse struct { + LookupResponse +} + +// A ReadlinkRequest is a request to read a symlink's target. +type ReadlinkRequest struct { + Header `json:"-"` +} + +var _ = Request(&ReadlinkRequest{}) + +func (r *ReadlinkRequest) String() string { + return fmt.Sprintf("Readlink [%s]", &r.Header) +} + +func (r *ReadlinkRequest) Respond(target string) { + out := &outHeader{Unique: uint64(r.ID)} + r.respondData(out, unsafe.Sizeof(*out), []byte(target)) +} + +// A LinkRequest is a request to create a hard link. +type LinkRequest struct { + Header `json:"-"` + OldNode NodeID + NewName string +} + +var _ = Request(&LinkRequest{}) + +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) { + out := &entryOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + Nodeid: uint64(resp.Node), + Generation: resp.Generation, + EntryValid: uint64(resp.EntryValid / time.Second), + EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), + AttrValid: uint64(resp.AttrValid / time.Second), + AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), + Attr: resp.Attr.attr(), + } + r.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +// A RenameRequest is a request to rename a file. +type RenameRequest struct { + Header `json:"-"` + NewDir NodeID + OldName, NewName string +} + +var _ = Request(&RenameRequest{}) + +func (r *RenameRequest) String() string { + return fmt.Sprintf("Rename [%s] from %q to dirnode %d %q", &r.Header, r.OldName, r.NewDir, r.NewName) +} + +func (r *RenameRequest) Respond() { + out := &outHeader{Unique: uint64(r.ID)} + r.respond(out, unsafe.Sizeof(*out)) +} + +type MknodRequest struct { + Header `json:"-"` + Name string + Mode os.FileMode + Rdev uint32 +} + +var _ = Request(&MknodRequest{}) + +func (r *MknodRequest) String() string { + return fmt.Sprintf("Mknod [%s] Name %q mode %v rdev %d", &r.Header, r.Name, r.Mode, r.Rdev) +} + +func (r *MknodRequest) Respond(resp *LookupResponse) { + out := &entryOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + Nodeid: uint64(resp.Node), + Generation: resp.Generation, + EntryValid: uint64(resp.EntryValid / time.Second), + EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), + AttrValid: uint64(resp.AttrValid / time.Second), + AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), + Attr: resp.Attr.attr(), + } + r.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +type FsyncRequest struct { + Header `json:"-"` + Handle HandleID + // TODO bit 1 is datasync, not well documented upstream + Flags uint32 + Dir bool +} + +var _ = Request(&FsyncRequest{}) + +func (r *FsyncRequest) String() string { + return fmt.Sprintf("Fsync [%s] Handle %v Flags %v", &r.Header, r.Handle, r.Flags) +} + +func (r *FsyncRequest) Respond() { + out := &outHeader{Unique: uint64(r.ID)} + r.respond(out, unsafe.Sizeof(*out)) +} + +// 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{}) + +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) +} + +/*{ + +// A XXXRequest xxx. +type XXXRequest struct { + Header `json:"-"` + xxx +} + +var _ = Request(&XXXRequest{}) + +func (r *XXXRequest) String() string { + return fmt.Sprintf("XXX [%s] xxx", &r.Header) +} + +// Respond replies to the request with the given response. +func (r *XXXRequest) Respond(resp *XXXResponse) { + out := &xxxOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + xxx, + } + r.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +// A XXXResponse is the response to a XXXRequest. +type XXXResponse struct { + xxx +} + +func (r *XXXResponse) String() string { + return fmt.Sprintf("XXX %+v", *r) +} + + } +*/ diff --git a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel.go b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel.go new file mode 100644 index 000000000..b04c89a9e --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel.go @@ -0,0 +1,639 @@ +// 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" +) + +// Version is the FUSE version implemented by the package. +const Version = "7.8" + +const ( + kernelVersion = 7 + kernelMinorVersion = 8 + rootID = 1 +) + +type kstatfs struct { + Blocks uint64 + Bfree uint64 + Bavail uint64 + Files uint64 + Ffree uint64 + Bsize uint32 + Namelen uint32 + Frsize uint32 + Padding uint32 + Spare [6]uint32 +} + +type fileLock struct { + Start uint64 + End uint64 + Type uint32 + Pid uint32 +} + +// 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 + + // Linux only(?) + 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 + + OpenAppend OpenFlags = syscall.O_APPEND + OpenCreate OpenFlags = syscall.O_CREAT + OpenExclusive OpenFlags = syscall.O_EXCL + 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(OpenCreate), "OpenCreate"}, + {uint32(OpenExclusive), "OpenExclusive"}, + {uint32(OpenTruncate), "OpenTruncate"}, + {uint32(OpenAppend), "OpenAppend"}, + {uint32(OpenSync), "OpenSync"}, +} + +// 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 // (Linux?) + + 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(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 + 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 +) + +func (fl ReleaseFlags) String() string { + return flagString(uint32(fl), releaseFlagNames) +} + +var releaseFlagNames = []flagName{ + {uint32(ReleaseFlush), "ReleaseFlush"}, +} + +// 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 // Linux? + opPoll = 40 // Linux? + + // OS X + opSetvolname = 61 + opGetxtimes = 62 + opExchange = 63 +) + +type entryOut struct { + outHeader + 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 +} + +type forgetIn struct { + Nlookup uint64 +} + +type attrOut struct { + outHeader + AttrValid uint64 // Cache timeout for the attributes + AttrValidNsec uint32 + Dummy uint32 + Attr attr +} + +// OS X +type getxtimesOut struct { + outHeader + Bkuptime uint64 + Crtime uint64 + BkuptimeNsec uint32 + CrtimeNsec uint32 +} + +type mknodIn struct { + Mode uint32 + Rdev uint32 + // "filename\x00" follows. +} + +type mkdirIn struct { + Mode uint32 + Padding uint32 + // filename follows +} + +type renameIn struct { + Newdir uint64 + // "oldname\x00newname\x00" follows +} + +// OS X +type exchangeIn struct { + Olddir uint64 + Newdir uint64 + Options uint64 +} + +type linkIn struct { + Oldnodeid uint64 +} + +type setattrInCommon struct { + Valid uint32 + Padding 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 { + outHeader + Fh uint64 + OpenFlags uint32 + Padding uint32 +} + +type createIn struct { + Flags uint32 + Mode uint32 +} + +type createOut struct { + outHeader + + 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 + + Fh uint64 + OpenFlags uint32 + Padding uint32 +} + +type releaseIn struct { + Fh uint64 + Flags uint32 + ReleaseFlags uint32 + LockOwner uint32 +} + +type flushIn struct { + Fh uint64 + FlushFlags uint32 + Padding uint32 + LockOwner uint64 +} + +type readIn struct { + Fh uint64 + Offset uint64 + Size uint32 + Padding uint32 +} + +type writeIn struct { + Fh uint64 + Offset uint64 + Size uint32 + WriteFlags uint32 +} + +type writeOut struct { + outHeader + Size uint32 + Padding uint32 +} + +// The WriteFlags are passed in WriteRequest. +type WriteFlags uint32 + +func (fl WriteFlags) String() string { + return flagString(uint32(fl), writeFlagNames) +} + +var writeFlagNames = []flagName{} + +const compatStatfsSize = 48 + +type statfsOut struct { + outHeader + St kstatfs +} + +type fsyncIn struct { + Fh uint64 + FsyncFlags uint32 + Padding uint32 +} + +type setxattrInCommon struct { + Size uint32 + Flags uint32 +} + +func (setxattrInCommon) position() uint32 { + return 0 +} + +type getxattrInCommon struct { + Size uint32 + Padding uint32 +} + +func (getxattrInCommon) position() uint32 { + return 0 +} + +type getxattrOut struct { + outHeader + Size uint32 + Padding uint32 +} + +type lkIn struct { + Fh uint64 + Owner uint64 + Lk fileLock +} + +type lkOut struct { + outHeader + Lk fileLock +} + +type accessIn struct { + Mask uint32 + Padding uint32 +} + +type initIn struct { + Major uint32 + Minor uint32 + MaxReadahead uint32 + Flags uint32 +} + +const initInSize = int(unsafe.Sizeof(initIn{})) + +type initOut struct { + outHeader + Major uint32 + Minor uint32 + MaxReadahead uint32 + Flags uint32 + Unused uint32 + MaxWrite uint32 +} + +type interruptIn struct { + Unique uint64 +} + +type bmapIn struct { + Block uint64 + BlockSize uint32 + Padding uint32 +} + +type bmapOut struct { + outHeader + Block uint64 +} + +type inHeader struct { + Len uint32 + Opcode uint32 + Unique uint64 + Nodeid uint64 + Uid uint32 + Gid uint32 + Pid uint32 + Padding 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 + Name [0]byte +} + +const direntSize = 8 + 8 + 4 + 4 diff --git a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_darwin.go b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_darwin.go new file mode 100644 index 000000000..4f9347d03 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_darwin.go @@ -0,0 +1,86 @@ +package 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) +} + +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 +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_freebsd.go b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_freebsd.go new file mode 100644 index 000000000..7636878c3 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_freebsd.go @@ -0,0 +1,60 @@ +package 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 +} + +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 +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_linux.go b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_linux.go new file mode 100644 index 000000000..6a752457a --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_linux.go @@ -0,0 +1,70 @@ +package 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 // Only in protocol 7.9 + // padding_ uint32 // Only in protocol 7.9 +} + +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 +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_std.go b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_std.go new file mode 100644 index 000000000..074cfd322 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_std.go @@ -0,0 +1 @@ +package fuse diff --git a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_test.go b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_test.go new file mode 100644 index 000000000..bee2e63c5 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_test.go @@ -0,0 +1,31 @@ +package fuse_test + +import ( + "os" + "testing" + + "bazil.org/fuse" +) + +func TestOpenFlagsAccmodeMask(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 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) + } +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/fuseutil/fuseutil.go b/Godeps/_workspace/src/bazil.org/fuse/fuseutil/fuseutil.go new file mode 100644 index 000000000..c9e44de70 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/fuseutil/fuseutil.go @@ -0,0 +1,20 @@ +package fuseutil + +import ( + "bazil.org/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] +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/hellofs/hello.go b/Godeps/_workspace/src/bazil.org/fuse/hellofs/hello.go new file mode 100644 index 000000000..5d9febe75 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/hellofs/hello.go @@ -0,0 +1,99 @@ +// Hellofs implements a simple "hello world" file system. +package main + +import ( + "flag" + "fmt" + "log" + "os" + + "bazil.org/fuse" + "bazil.org/fuse/fs" + _ "bazil.org/fuse/fs/fstestutil" + "golang.org/x/net/context" +) + +var Usage = func() { + 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(a *fuse.Attr) { + a.Inode = 1 + a.Mode = os.ModeDir | 0555 +} + +func (Dir) Lookup(ctx context.Context, name string) (fs.Node, error) { + if name == "hello" { + return File{}, nil + } + return nil, fuse.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(a *fuse.Attr) { + a.Inode = 2 + a.Mode = 0444 + a.Size = uint64(len(greeting)) +} + +func (File) ReadAll(ctx context.Context) ([]byte, error) { + return []byte(greeting), nil +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/mount_darwin.go b/Godeps/_workspace/src/bazil.org/fuse/mount_darwin.go new file mode 100644 index 000000000..742b084c2 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/mount_darwin.go @@ -0,0 +1,126 @@ +package fuse + +import ( + "bytes" + "errors" + "fmt" + "os" + "os/exec" + "strconv" + "strings" + "syscall" +) + +var errNoAvail = errors.New("no available fuse devices") + +var errNotLoaded = errors.New("osxfusefs is not loaded") + +func loadOSXFUSE() error { + cmd := exec.Command("/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs") + cmd.Dir = "/" + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + return err +} + +func openOSXFUSEDev() (*os.File, error) { + var f *os.File + var err error + for i := uint64(0); ; i++ { + path := "/dev/osxfuse" + strconv.FormatUint(i, 10) + f, err = os.OpenFile(path, os.O_RDWR, 0000) + if os.IsNotExist(err) { + if i == 0 { + // not even the first device was found -> fuse is not loaded + return nil, errNotLoaded + } + + // we've run out of kernel-provided devices + return nil, errNoAvail + } + + if err2, ok := err.(*os.PathError); ok && err2.Err == syscall.EBUSY { + // try the next one + continue + } + + if err != nil { + return nil, err + } + return f, nil + } +} + +func callMount(dir string, conf *MountConfig, f *os.File, ready chan<- struct{}, errp *error) error { + bin := "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs" + + 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 fmt.Errorf("mount options cannot contain commas on darwin: %q=%q", k, v) + } + } + cmd := exec.Command( + bin, + "-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), + // refers to fd passed in cmd.ExtraFiles + "3", + dir, + ) + cmd.ExtraFiles = []*os.File{f} + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_CALL_BY_LIB=") + // TODO this is used for fs typenames etc, let app influence it + cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_DAEMON_PATH="+bin) + var buf bytes.Buffer + cmd.Stdout = &buf + cmd.Stderr = &buf + + err := cmd.Start() + if err != nil { + return err + } + go func() { + err := cmd.Wait() + if err != nil { + if buf.Len() > 0 { + output := buf.Bytes() + output = bytes.TrimRight(output, "\n") + msg := err.Error() + ": " + string(output) + err = errors.New(msg) + } + } + *errp = err + close(ready) + }() + return err +} + +func mount(dir string, conf *MountConfig, ready chan<- struct{}, errp *error) (*os.File, error) { + f, err := openOSXFUSEDev() + if err == errNotLoaded { + err = loadOSXFUSE() + if err != nil { + return nil, err + } + // try again + f, err = openOSXFUSEDev() + } + if err != nil { + return nil, err + } + err = callMount(dir, conf, f, ready, errp) + if err != nil { + f.Close() + return nil, err + } + return f, nil +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/mount_freebsd.go b/Godeps/_workspace/src/bazil.org/fuse/mount_freebsd.go new file mode 100644 index 000000000..951dcf10c --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/mount_freebsd.go @@ -0,0 +1,41 @@ +package fuse + +import ( + "fmt" + "os" + "os/exec" + "strings" +) + +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, 0000) + 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} + + out, err := cmd.CombinedOutput() + if err != nil { + return nil, fmt.Errorf("mount_fusefs: %q, %v", out, err) + } + + close(ready) + return f, nil +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/mount_linux.go b/Godeps/_workspace/src/bazil.org/fuse/mount_linux.go new file mode 100644 index 000000000..0748c0a5d --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/mount_linux.go @@ -0,0 +1,72 @@ +package fuse + +import ( + "fmt" + "net" + "os" + "os/exec" + "syscall" +) + +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) + } + defer syscall.Close(fds[0]) + defer syscall.Close(fds[1]) + + cmd := exec.Command( + "fusermount", + "-o", conf.getOptions(), + "--", + dir, + ) + cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3") + + writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes") + defer writeFile.Close() + cmd.ExtraFiles = []*os.File{writeFile} + + out, err := cmd.CombinedOutput() + if len(out) > 0 || err != nil { + return nil, fmt.Errorf("fusermount: %q, %v", out, err) + } + + readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads") + defer readFile.Close() + 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 +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/options.go b/Godeps/_workspace/src/bazil.org/fuse/options.go new file mode 100644 index 000000000..5a0b7f180 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/options.go @@ -0,0 +1,132 @@ +package 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 +} + +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, ",") +} + +// MountOption is passed to Mount to change the behavior of the mount. +type MountOption func(*MountConfig) error + +// 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) +} + +var ErrCannotCombineAllowOtherAndAllowRoot = errors.New("cannot combine AllowOther and AllowRoot") + +// AllowOther allows other users to access the file system. +// +// Only one of AllowOther or AllowRoot can be used. +func AllowOther() MountOption { + return func(conf *MountConfig) error { + if _, ok := conf.options["allow_root"]; ok { + return ErrCannotCombineAllowOtherAndAllowRoot + } + conf.options["allow_other"] = "" + return nil + } +} + +// AllowRoot allows other users to access the file system. +// +// Only one of AllowOther or AllowRoot can be used. +// +// FreeBSD ignores this option. +func AllowRoot() MountOption { + return func(conf *MountConfig) error { + if _, ok := conf.options["allow_other"]; ok { + return ErrCannotCombineAllowOtherAndAllowRoot + } + conf.options["allow_root"] = "" + 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/AllowRoot. +// +// 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 + } +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/options_darwin.go b/Godeps/_workspace/src/bazil.org/fuse/options_darwin.go new file mode 100644 index 000000000..15aedbcfc --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/options_darwin.go @@ -0,0 +1,13 @@ +package 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 + } +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/options_freebsd.go b/Godeps/_workspace/src/bazil.org/fuse/options_freebsd.go new file mode 100644 index 000000000..8eb07e009 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/options_freebsd.go @@ -0,0 +1,9 @@ +package fuse + +func localVolume(conf *MountConfig) error { + return nil +} + +func volumeName(name string) MountOption { + return dummyOption +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/options_helper_test.go b/Godeps/_workspace/src/bazil.org/fuse/options_helper_test.go new file mode 100644 index 000000000..f9c90e8bf --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/options_helper_test.go @@ -0,0 +1,6 @@ +package fuse + +// for TestMountOptionCommaError +func ForTestSetMountOption(conf *MountConfig, k, v string) { + conf.options[k] = v +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/options_linux.go b/Godeps/_workspace/src/bazil.org/fuse/options_linux.go new file mode 100644 index 000000000..8eb07e009 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/options_linux.go @@ -0,0 +1,9 @@ +package fuse + +func localVolume(conf *MountConfig) error { + return nil +} + +func volumeName(name string) MountOption { + return dummyOption +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/options_nocomma_test.go b/Godeps/_workspace/src/bazil.org/fuse/options_nocomma_test.go new file mode 100644 index 000000000..53f3c6fa3 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/options_nocomma_test.go @@ -0,0 +1,34 @@ +// This file contains tests for platforms that have no escape +// mechanism for including commas in mount options. +// +// +build darwin + +package fuse_test + +import ( + "runtime" + "testing" + + "bazil.org/fuse" + "bazil.org/fuse/fs/fstestutil" +) + +func TestMountOptionCommaError(t *testing.T) { + t.Parallel() + // 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{}}, + func(conf *fuse.MountConfig) error { + fuse.ForTestSetMountOption(conf, "fusetest", evil) + return nil + }, + ) + 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) + } +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/options_test.go b/Godeps/_workspace/src/bazil.org/fuse/options_test.go new file mode 100644 index 000000000..8d337aaf6 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/options_test.go @@ -0,0 +1,228 @@ +package fuse_test + +import ( + "os" + "runtime" + "syscall" + "testing" + + "bazil.org/fuse" + "bazil.org/fuse/fs" + "bazil.org/fuse/fs/fstestutil" + "golang.org/x/net/context" +) + +func init() { + fstestutil.DebugByDefault() +} + +func TestMountOptionFSName(t *testing.T) { + if runtime.GOOS == "freebsd" { + t.Skip("FreeBSD does not support FSName") + } + t.Parallel() + const name = "FuseTestMarker" + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, + 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") + } + t.Parallel() + var name = "FuseTest" + evil + "Marker" + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, + 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") + } + t.Parallel() + const name = "FuseTestMarker" + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, + 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) + } +} + +// TODO test LocalVolume + +// TODO test AllowOther; hard because needs system-level authorization + +func TestMountOptionAllowOtherThenAllowRoot(t *testing.T) { + t.Parallel() + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, + fuse.AllowOther(), + fuse.AllowRoot(), + ) + if err == nil { + mnt.Close() + } + if g, e := err, fuse.ErrCannotCombineAllowOtherAndAllowRoot; g != e { + t.Fatalf("wrong error: %v != %v", g, e) + } +} + +// TODO test AllowRoot; hard because needs system-level authorization + +func TestMountOptionAllowRootThenAllowOther(t *testing.T) { + t.Parallel() + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, + fuse.AllowRoot(), + fuse.AllowOther(), + ) + if err == nil { + mnt.Close() + } + if g, e := err, fuse.ErrCannotCombineAllowOtherAndAllowRoot; g != e { + t.Fatalf("wrong error: %v != %v", g, e) + } +} + +type unwritableFile struct{} + +func (f unwritableFile) Attr(a *fuse.Attr) { + a.Mode = 0000 +} + +func TestMountOptionDefaultPermissions(t *testing.T) { + if runtime.GOOS == "freebsd" { + t.Skip("FreeBSD does not support DefaultPermissions") + } + t.Parallel() + mnt, err := fstestutil.MountedT(t, + fstestutil.SimpleFS{ + fstestutil.ChildMap{"child": unwritableFile{}}, + }, + fuse.DefaultPermissions(), + ) + + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + // This will be prevented by kernel-level access checking when + // DefaultPermissions is used. + f, err := os.OpenFile(mnt.Dir+"/child", os.O_WRONLY, 0000) + if err == nil { + f.Close() + t.Fatal("expected an error") + } + if !os.IsPermission(err) { + t.Fatalf("expected a permission error, got %T: %v", err, 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) { + t.Parallel() + mnt, err := fstestutil.MountedT(t, + fstestutil.SimpleFS{createrDir{}}, + fuse.ReadOnly(), + ) + + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + // This will be prevented by kernel-level access checking when + // ReadOnly is used. + f, err := os.Create(mnt.Dir + "/child") + if err == nil { + f.Close() + t.Fatal("expected an error") + } + perr, ok := err.(*os.PathError) + if !ok { + t.Fatalf("expected PathError, got %T: %v", err, err) + } + if perr.Err != syscall.EROFS { + t.Fatalf("expected EROFS, got %T: %v", err, err) + } +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/syscallx/doc.go b/Godeps/_workspace/src/bazil.org/fuse/syscallx/doc.go new file mode 100644 index 000000000..8ceee43b0 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/syscallx/doc.go @@ -0,0 +1,13 @@ +// 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 diff --git a/Godeps/_workspace/src/bazil.org/fuse/syscallx/generate b/Godeps/_workspace/src/bazil.org/fuse/syscallx/generate new file mode 100644 index 000000000..476a282b1 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/syscallx/generate @@ -0,0 +1,34 @@ +#!/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 diff --git a/Godeps/_workspace/src/bazil.org/fuse/syscallx/msync.go b/Godeps/_workspace/src/bazil.org/fuse/syscallx/msync.go new file mode 100644 index 000000000..30737e6d4 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/syscallx/msync.go @@ -0,0 +1,9 @@ +package syscallx + +/* This is the source file for msync_*.go, to regenerate run + + ./generate + +*/ + +//sys Msync(b []byte, flags int) (err error) diff --git a/Godeps/_workspace/src/bazil.org/fuse/syscallx/msync_386.go b/Godeps/_workspace/src/bazil.org/fuse/syscallx/msync_386.go new file mode 100644 index 000000000..672599423 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/syscallx/msync_386.go @@ -0,0 +1,24 @@ +// 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 +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/syscallx/msync_amd64.go b/Godeps/_workspace/src/bazil.org/fuse/syscallx/msync_amd64.go new file mode 100644 index 000000000..0bbe1ab85 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/syscallx/msync_amd64.go @@ -0,0 +1,24 @@ +// 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 +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/syscallx/syscallx.go b/Godeps/_workspace/src/bazil.org/fuse/syscallx/syscallx.go new file mode 100644 index 000000000..eb099129e --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/syscallx/syscallx.go @@ -0,0 +1,4 @@ +package syscallx + +// make us look more like package syscall, so mksyscall.pl output works +var _zero uintptr diff --git a/Godeps/_workspace/src/bazil.org/fuse/syscallx/syscallx_std.go b/Godeps/_workspace/src/bazil.org/fuse/syscallx/syscallx_std.go new file mode 100644 index 000000000..c0187a6b8 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/syscallx/syscallx_std.go @@ -0,0 +1,26 @@ +// +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) +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/syscallx/xattr_darwin.go b/Godeps/_workspace/src/bazil.org/fuse/syscallx/xattr_darwin.go new file mode 100644 index 000000000..b00f90203 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/syscallx/xattr_darwin.go @@ -0,0 +1,38 @@ +package 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) +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/syscallx/xattr_darwin_386.go b/Godeps/_workspace/src/bazil.org/fuse/syscallx/xattr_darwin_386.go new file mode 100644 index 000000000..ffc357aef --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/syscallx/xattr_darwin_386.go @@ -0,0 +1,97 @@ +// 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 +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/syscallx/xattr_darwin_amd64.go b/Godeps/_workspace/src/bazil.org/fuse/syscallx/xattr_darwin_amd64.go new file mode 100644 index 000000000..864c4c1d4 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/syscallx/xattr_darwin_amd64.go @@ -0,0 +1,97 @@ +// 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 +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/unmount.go b/Godeps/_workspace/src/bazil.org/fuse/unmount.go new file mode 100644 index 000000000..ffe3f155c --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/unmount.go @@ -0,0 +1,6 @@ +package fuse + +// Unmount tries to unmount the filesystem mounted at dir. +func Unmount(dir string) error { + return unmount(dir) +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/unmount_linux.go b/Godeps/_workspace/src/bazil.org/fuse/unmount_linux.go new file mode 100644 index 000000000..088f0cfee --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/unmount_linux.go @@ -0,0 +1,21 @@ +package 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 +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/unmount_std.go b/Godeps/_workspace/src/bazil.org/fuse/unmount_std.go new file mode 100644 index 000000000..d6efe276f --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/unmount_std.go @@ -0,0 +1,17 @@ +// +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 +} diff --git a/Godeps/_workspace/src/golang.org/x/net/context/context.go b/Godeps/_workspace/src/golang.org/x/net/context/context.go new file mode 100644 index 000000000..ef2f3e86f --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/context/context.go @@ -0,0 +1,447 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package context defines the Context type, which carries deadlines, +// cancelation signals, and other request-scoped values across API boundaries +// and between processes. +// +// Incoming requests to a server should create a Context, and outgoing calls to +// servers should accept a Context. The chain of function calls between must +// propagate the Context, optionally replacing it with a modified copy created +// using WithDeadline, WithTimeout, WithCancel, or WithValue. +// +// Programs that use Contexts should follow these rules to keep interfaces +// consistent across packages and enable static analysis tools to check context +// propagation: +// +// Do not store Contexts inside a struct type; instead, pass a Context +// explicitly to each function that needs it. The Context should be the first +// parameter, typically named ctx: +// +// func DoSomething(ctx context.Context, arg Arg) error { +// // ... use ctx ... +// } +// +// Do not pass a nil Context, even if a function permits it. Pass context.TODO +// if you are unsure about which Context to use. +// +// Use context Values only for request-scoped data that transits processes and +// APIs, not for passing optional parameters to functions. +// +// The same Context may be passed to functions running in different goroutines; +// Contexts are safe for simultaneous use by multiple goroutines. +// +// See http://blog.golang.org/context for example code for a server that uses +// Contexts. +package context + +import ( + "errors" + "fmt" + "sync" + "time" +) + +// A Context carries a deadline, a cancelation signal, and other values across +// API boundaries. +// +// Context's methods may be called by multiple goroutines simultaneously. +type Context interface { + // Deadline returns the time when work done on behalf of this context + // should be canceled. Deadline returns ok==false when no deadline is + // set. Successive calls to Deadline return the same results. + Deadline() (deadline time.Time, ok bool) + + // Done returns a channel that's closed when work done on behalf of this + // context should be canceled. Done may return nil if this context can + // never be canceled. Successive calls to Done return the same value. + // + // WithCancel arranges for Done to be closed when cancel is called; + // WithDeadline arranges for Done to be closed when the deadline + // expires; WithTimeout arranges for Done to be closed when the timeout + // elapses. + // + // Done is provided for use in select statements: + // + // // Stream generates values with DoSomething and sends them to out + // // until DoSomething returns an error or ctx.Done is closed. + // func Stream(ctx context.Context, out <-chan Value) error { + // for { + // v, err := DoSomething(ctx) + // if err != nil { + // return err + // } + // select { + // case <-ctx.Done(): + // return ctx.Err() + // case out <- v: + // } + // } + // } + // + // See http://blog.golang.org/pipelines for more examples of how to use + // a Done channel for cancelation. + Done() <-chan struct{} + + // Err returns a non-nil error value after Done is closed. Err returns + // Canceled if the context was canceled or DeadlineExceeded if the + // context's deadline passed. No other values for Err are defined. + // After Done is closed, successive calls to Err return the same value. + Err() error + + // Value returns the value associated with this context for key, or nil + // if no value is associated with key. Successive calls to Value with + // the same key returns the same result. + // + // Use context values only for request-scoped data that transits + // processes and API boundaries, not for passing optional parameters to + // functions. + // + // A key identifies a specific value in a Context. Functions that wish + // to store values in Context typically allocate a key in a global + // variable then use that key as the argument to context.WithValue and + // Context.Value. A key can be any type that supports equality; + // packages should define keys as an unexported type to avoid + // collisions. + // + // Packages that define a Context key should provide type-safe accessors + // for the values stores using that key: + // + // // Package user defines a User type that's stored in Contexts. + // package user + // + // import "golang.org/x/net/context" + // + // // User is the type of value stored in the Contexts. + // type User struct {...} + // + // // key is an unexported type for keys defined in this package. + // // This prevents collisions with keys defined in other packages. + // type key int + // + // // userKey is the key for user.User values in Contexts. It is + // // unexported; clients use user.NewContext and user.FromContext + // // instead of using this key directly. + // var userKey key = 0 + // + // // NewContext returns a new Context that carries value u. + // func NewContext(ctx context.Context, u *User) context.Context { + // return context.WithValue(ctx, userKey, u) + // } + // + // // FromContext returns the User value stored in ctx, if any. + // func FromContext(ctx context.Context) (*User, bool) { + // u, ok := ctx.Value(userKey).(*User) + // return u, ok + // } + Value(key interface{}) interface{} +} + +// Canceled is the error returned by Context.Err when the context is canceled. +var Canceled = errors.New("context canceled") + +// DeadlineExceeded is the error returned by Context.Err when the context's +// deadline passes. +var DeadlineExceeded = errors.New("context deadline exceeded") + +// An emptyCtx is never canceled, has no values, and has no deadline. It is not +// struct{}, since vars of this type must have distinct addresses. +type emptyCtx int + +func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { + return +} + +func (*emptyCtx) Done() <-chan struct{} { + return nil +} + +func (*emptyCtx) Err() error { + return nil +} + +func (*emptyCtx) Value(key interface{}) interface{} { + return nil +} + +func (e *emptyCtx) String() string { + switch e { + case background: + return "context.Background" + case todo: + return "context.TODO" + } + return "unknown empty Context" +} + +var ( + background = new(emptyCtx) + todo = new(emptyCtx) +) + +// Background returns a non-nil, empty Context. It is never canceled, has no +// values, and has no deadline. It is typically used by the main function, +// initialization, and tests, and as the top-level Context for incoming +// requests. +func Background() Context { + return background +} + +// TODO returns a non-nil, empty Context. Code should use context.TODO when +// it's unclear which Context to use or it's is not yet available (because the +// surrounding function has not yet been extended to accept a Context +// parameter). TODO is recognized by static analysis tools that determine +// whether Contexts are propagated correctly in a program. +func TODO() Context { + return todo +} + +// A CancelFunc tells an operation to abandon its work. +// A CancelFunc does not wait for the work to stop. +// After the first call, subsequent calls to a CancelFunc do nothing. +type CancelFunc func() + +// WithCancel returns a copy of parent with a new Done channel. The returned +// context's Done channel is closed when the returned cancel function is called +// or when the parent context's Done channel is closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { + c := newCancelCtx(parent) + propagateCancel(parent, &c) + return &c, func() { c.cancel(true, Canceled) } +} + +// newCancelCtx returns an initialized cancelCtx. +func newCancelCtx(parent Context) cancelCtx { + return cancelCtx{ + Context: parent, + done: make(chan struct{}), + } +} + +// propagateCancel arranges for child to be canceled when parent is. +func propagateCancel(parent Context, child canceler) { + if parent.Done() == nil { + return // parent is never canceled + } + if p, ok := parentCancelCtx(parent); ok { + p.mu.Lock() + if p.err != nil { + // parent has already been canceled + child.cancel(false, p.err) + } else { + if p.children == nil { + p.children = make(map[canceler]bool) + } + p.children[child] = true + } + p.mu.Unlock() + } else { + go func() { + select { + case <-parent.Done(): + child.cancel(false, parent.Err()) + case <-child.Done(): + } + }() + } +} + +// parentCancelCtx follows a chain of parent references until it finds a +// *cancelCtx. This function understands how each of the concrete types in this +// package represents its parent. +func parentCancelCtx(parent Context) (*cancelCtx, bool) { + for { + switch c := parent.(type) { + case *cancelCtx: + return c, true + case *timerCtx: + return &c.cancelCtx, true + case *valueCtx: + parent = c.Context + default: + return nil, false + } + } +} + +// removeChild removes a context from its parent. +func removeChild(parent Context, child canceler) { + p, ok := parentCancelCtx(parent) + if !ok { + return + } + p.mu.Lock() + if p.children != nil { + delete(p.children, child) + } + p.mu.Unlock() +} + +// A canceler is a context type that can be canceled directly. The +// implementations are *cancelCtx and *timerCtx. +type canceler interface { + cancel(removeFromParent bool, err error) + Done() <-chan struct{} +} + +// A cancelCtx can be canceled. When canceled, it also cancels any children +// that implement canceler. +type cancelCtx struct { + Context + + done chan struct{} // closed by the first cancel call. + + mu sync.Mutex + children map[canceler]bool // set to nil by the first cancel call + err error // set to non-nil by the first cancel call +} + +func (c *cancelCtx) Done() <-chan struct{} { + return c.done +} + +func (c *cancelCtx) Err() error { + c.mu.Lock() + defer c.mu.Unlock() + return c.err +} + +func (c *cancelCtx) String() string { + return fmt.Sprintf("%v.WithCancel", c.Context) +} + +// cancel closes c.done, cancels each of c's children, and, if +// removeFromParent is true, removes c from its parent's children. +func (c *cancelCtx) cancel(removeFromParent bool, err error) { + if err == nil { + panic("context: internal error: missing cancel error") + } + c.mu.Lock() + if c.err != nil { + c.mu.Unlock() + return // already canceled + } + c.err = err + close(c.done) + for child := range c.children { + // NOTE: acquiring the child's lock while holding parent's lock. + child.cancel(false, err) + } + c.children = nil + c.mu.Unlock() + + if removeFromParent { + removeChild(c.Context, c) + } +} + +// WithDeadline returns a copy of the parent context with the deadline adjusted +// to be no later than d. If the parent's deadline is already earlier than d, +// WithDeadline(parent, d) is semantically equivalent to parent. The returned +// context's Done channel is closed when the deadline expires, when the returned +// cancel function is called, or when the parent context's Done channel is +// closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { + if cur, ok := parent.Deadline(); ok && cur.Before(deadline) { + // The current deadline is already sooner than the new one. + return WithCancel(parent) + } + c := &timerCtx{ + cancelCtx: newCancelCtx(parent), + deadline: deadline, + } + propagateCancel(parent, c) + d := deadline.Sub(time.Now()) + if d <= 0 { + c.cancel(true, DeadlineExceeded) // deadline has already passed + return c, func() { c.cancel(true, Canceled) } + } + c.mu.Lock() + defer c.mu.Unlock() + if c.err == nil { + c.timer = time.AfterFunc(d, func() { + c.cancel(true, DeadlineExceeded) + }) + } + return c, func() { c.cancel(true, Canceled) } +} + +// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to +// implement Done and Err. It implements cancel by stopping its timer then +// delegating to cancelCtx.cancel. +type timerCtx struct { + cancelCtx + timer *time.Timer // Under cancelCtx.mu. + + deadline time.Time +} + +func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { + return c.deadline, true +} + +func (c *timerCtx) String() string { + return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now())) +} + +func (c *timerCtx) cancel(removeFromParent bool, err error) { + c.cancelCtx.cancel(false, err) + if removeFromParent { + // Remove this timerCtx from its parent cancelCtx's children. + removeChild(c.cancelCtx.Context, c) + } + c.mu.Lock() + if c.timer != nil { + c.timer.Stop() + c.timer = nil + } + c.mu.Unlock() +} + +// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete: +// +// func slowOperationWithTimeout(ctx context.Context) (Result, error) { +// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) +// defer cancel() // releases resources if slowOperation completes before timeout elapses +// return slowOperation(ctx) +// } +func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { + return WithDeadline(parent, time.Now().Add(timeout)) +} + +// WithValue returns a copy of parent in which the value associated with key is +// val. +// +// Use context Values only for request-scoped data that transits processes and +// APIs, not for passing optional parameters to functions. +func WithValue(parent Context, key interface{}, val interface{}) Context { + return &valueCtx{parent, key, val} +} + +// A valueCtx carries a key-value pair. It implements Value for that key and +// delegates all other calls to the embedded Context. +type valueCtx struct { + Context + key, val interface{} +} + +func (c *valueCtx) String() string { + return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val) +} + +func (c *valueCtx) Value(key interface{}) interface{} { + if c.key == key { + return c.val + } + return c.Context.Value(key) +} diff --git a/Godeps/_workspace/src/golang.org/x/net/context/context_test.go b/Godeps/_workspace/src/golang.org/x/net/context/context_test.go new file mode 100644 index 000000000..faf67722a --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/context/context_test.go @@ -0,0 +1,575 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package context + +import ( + "fmt" + "math/rand" + "runtime" + "strings" + "sync" + "testing" + "time" +) + +// otherContext is a Context that's not one of the types defined in context.go. +// This lets us test code paths that differ based on the underlying type of the +// Context. +type otherContext struct { + Context +} + +func TestBackground(t *testing.T) { + c := Background() + if c == nil { + t.Fatalf("Background returned nil") + } + select { + case x := <-c.Done(): + t.Errorf("<-c.Done() == %v want nothing (it should block)", x) + default: + } + if got, want := fmt.Sprint(c), "context.Background"; got != want { + t.Errorf("Background().String() = %q want %q", got, want) + } +} + +func TestTODO(t *testing.T) { + c := TODO() + if c == nil { + t.Fatalf("TODO returned nil") + } + select { + case x := <-c.Done(): + t.Errorf("<-c.Done() == %v want nothing (it should block)", x) + default: + } + if got, want := fmt.Sprint(c), "context.TODO"; got != want { + t.Errorf("TODO().String() = %q want %q", got, want) + } +} + +func TestWithCancel(t *testing.T) { + c1, cancel := WithCancel(Background()) + + if got, want := fmt.Sprint(c1), "context.Background.WithCancel"; got != want { + t.Errorf("c1.String() = %q want %q", got, want) + } + + o := otherContext{c1} + c2, _ := WithCancel(o) + contexts := []Context{c1, o, c2} + + for i, c := range contexts { + if d := c.Done(); d == nil { + t.Errorf("c[%d].Done() == %v want non-nil", i, d) + } + if e := c.Err(); e != nil { + t.Errorf("c[%d].Err() == %v want nil", i, e) + } + + select { + case x := <-c.Done(): + t.Errorf("<-c.Done() == %v want nothing (it should block)", x) + default: + } + } + + cancel() + time.Sleep(100 * time.Millisecond) // let cancelation propagate + + for i, c := range contexts { + select { + case <-c.Done(): + default: + t.Errorf("<-c[%d].Done() blocked, but shouldn't have", i) + } + if e := c.Err(); e != Canceled { + t.Errorf("c[%d].Err() == %v want %v", i, e, Canceled) + } + } +} + +func TestParentFinishesChild(t *testing.T) { + // Context tree: + // parent -> cancelChild + // parent -> valueChild -> timerChild + parent, cancel := WithCancel(Background()) + cancelChild, stop := WithCancel(parent) + defer stop() + valueChild := WithValue(parent, "key", "value") + timerChild, stop := WithTimeout(valueChild, 10000*time.Hour) + defer stop() + + select { + case x := <-parent.Done(): + t.Errorf("<-parent.Done() == %v want nothing (it should block)", x) + case x := <-cancelChild.Done(): + t.Errorf("<-cancelChild.Done() == %v want nothing (it should block)", x) + case x := <-timerChild.Done(): + t.Errorf("<-timerChild.Done() == %v want nothing (it should block)", x) + case x := <-valueChild.Done(): + t.Errorf("<-valueChild.Done() == %v want nothing (it should block)", x) + default: + } + + // The parent's children should contain the two cancelable children. + pc := parent.(*cancelCtx) + cc := cancelChild.(*cancelCtx) + tc := timerChild.(*timerCtx) + pc.mu.Lock() + if len(pc.children) != 2 || !pc.children[cc] || !pc.children[tc] { + t.Errorf("bad linkage: pc.children = %v, want %v and %v", + pc.children, cc, tc) + } + pc.mu.Unlock() + + if p, ok := parentCancelCtx(cc.Context); !ok || p != pc { + t.Errorf("bad linkage: parentCancelCtx(cancelChild.Context) = %v, %v want %v, true", p, ok, pc) + } + if p, ok := parentCancelCtx(tc.Context); !ok || p != pc { + t.Errorf("bad linkage: parentCancelCtx(timerChild.Context) = %v, %v want %v, true", p, ok, pc) + } + + cancel() + + pc.mu.Lock() + if len(pc.children) != 0 { + t.Errorf("pc.cancel didn't clear pc.children = %v", pc.children) + } + pc.mu.Unlock() + + // parent and children should all be finished. + check := func(ctx Context, name string) { + select { + case <-ctx.Done(): + default: + t.Errorf("<-%s.Done() blocked, but shouldn't have", name) + } + if e := ctx.Err(); e != Canceled { + t.Errorf("%s.Err() == %v want %v", name, e, Canceled) + } + } + check(parent, "parent") + check(cancelChild, "cancelChild") + check(valueChild, "valueChild") + check(timerChild, "timerChild") + + // WithCancel should return a canceled context on a canceled parent. + precanceledChild := WithValue(parent, "key", "value") + select { + case <-precanceledChild.Done(): + default: + t.Errorf("<-precanceledChild.Done() blocked, but shouldn't have") + } + if e := precanceledChild.Err(); e != Canceled { + t.Errorf("precanceledChild.Err() == %v want %v", e, Canceled) + } +} + +func TestChildFinishesFirst(t *testing.T) { + cancelable, stop := WithCancel(Background()) + defer stop() + for _, parent := range []Context{Background(), cancelable} { + child, cancel := WithCancel(parent) + + select { + case x := <-parent.Done(): + t.Errorf("<-parent.Done() == %v want nothing (it should block)", x) + case x := <-child.Done(): + t.Errorf("<-child.Done() == %v want nothing (it should block)", x) + default: + } + + cc := child.(*cancelCtx) + pc, pcok := parent.(*cancelCtx) // pcok == false when parent == Background() + if p, ok := parentCancelCtx(cc.Context); ok != pcok || (ok && pc != p) { + t.Errorf("bad linkage: parentCancelCtx(cc.Context) = %v, %v want %v, %v", p, ok, pc, pcok) + } + + if pcok { + pc.mu.Lock() + if len(pc.children) != 1 || !pc.children[cc] { + t.Errorf("bad linkage: pc.children = %v, cc = %v", pc.children, cc) + } + pc.mu.Unlock() + } + + cancel() + + if pcok { + pc.mu.Lock() + if len(pc.children) != 0 { + t.Errorf("child's cancel didn't remove self from pc.children = %v", pc.children) + } + pc.mu.Unlock() + } + + // child should be finished. + select { + case <-child.Done(): + default: + t.Errorf("<-child.Done() blocked, but shouldn't have") + } + if e := child.Err(); e != Canceled { + t.Errorf("child.Err() == %v want %v", e, Canceled) + } + + // parent should not be finished. + select { + case x := <-parent.Done(): + t.Errorf("<-parent.Done() == %v want nothing (it should block)", x) + default: + } + if e := parent.Err(); e != nil { + t.Errorf("parent.Err() == %v want nil", e) + } + } +} + +func testDeadline(c Context, wait time.Duration, t *testing.T) { + select { + case <-time.After(wait): + t.Fatalf("context should have timed out") + case <-c.Done(): + } + if e := c.Err(); e != DeadlineExceeded { + t.Errorf("c.Err() == %v want %v", e, DeadlineExceeded) + } +} + +func TestDeadline(t *testing.T) { + c, _ := WithDeadline(Background(), time.Now().Add(100*time.Millisecond)) + if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) { + t.Errorf("c.String() = %q want prefix %q", got, prefix) + } + testDeadline(c, 200*time.Millisecond, t) + + c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond)) + o := otherContext{c} + testDeadline(o, 200*time.Millisecond, t) + + c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond)) + o = otherContext{c} + c, _ = WithDeadline(o, time.Now().Add(300*time.Millisecond)) + testDeadline(c, 200*time.Millisecond, t) +} + +func TestTimeout(t *testing.T) { + c, _ := WithTimeout(Background(), 100*time.Millisecond) + if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) { + t.Errorf("c.String() = %q want prefix %q", got, prefix) + } + testDeadline(c, 200*time.Millisecond, t) + + c, _ = WithTimeout(Background(), 100*time.Millisecond) + o := otherContext{c} + testDeadline(o, 200*time.Millisecond, t) + + c, _ = WithTimeout(Background(), 100*time.Millisecond) + o = otherContext{c} + c, _ = WithTimeout(o, 300*time.Millisecond) + testDeadline(c, 200*time.Millisecond, t) +} + +func TestCanceledTimeout(t *testing.T) { + c, _ := WithTimeout(Background(), 200*time.Millisecond) + o := otherContext{c} + c, cancel := WithTimeout(o, 400*time.Millisecond) + cancel() + time.Sleep(100 * time.Millisecond) // let cancelation propagate + select { + case <-c.Done(): + default: + t.Errorf("<-c.Done() blocked, but shouldn't have") + } + if e := c.Err(); e != Canceled { + t.Errorf("c.Err() == %v want %v", e, Canceled) + } +} + +type key1 int +type key2 int + +var k1 = key1(1) +var k2 = key2(1) // same int as k1, different type +var k3 = key2(3) // same type as k2, different int + +func TestValues(t *testing.T) { + check := func(c Context, nm, v1, v2, v3 string) { + if v, ok := c.Value(k1).(string); ok == (len(v1) == 0) || v != v1 { + t.Errorf(`%s.Value(k1).(string) = %q, %t want %q, %t`, nm, v, ok, v1, len(v1) != 0) + } + if v, ok := c.Value(k2).(string); ok == (len(v2) == 0) || v != v2 { + t.Errorf(`%s.Value(k2).(string) = %q, %t want %q, %t`, nm, v, ok, v2, len(v2) != 0) + } + if v, ok := c.Value(k3).(string); ok == (len(v3) == 0) || v != v3 { + t.Errorf(`%s.Value(k3).(string) = %q, %t want %q, %t`, nm, v, ok, v3, len(v3) != 0) + } + } + + c0 := Background() + check(c0, "c0", "", "", "") + + c1 := WithValue(Background(), k1, "c1k1") + check(c1, "c1", "c1k1", "", "") + + if got, want := fmt.Sprint(c1), `context.Background.WithValue(1, "c1k1")`; got != want { + t.Errorf("c.String() = %q want %q", got, want) + } + + c2 := WithValue(c1, k2, "c2k2") + check(c2, "c2", "c1k1", "c2k2", "") + + c3 := WithValue(c2, k3, "c3k3") + check(c3, "c2", "c1k1", "c2k2", "c3k3") + + c4 := WithValue(c3, k1, nil) + check(c4, "c4", "", "c2k2", "c3k3") + + o0 := otherContext{Background()} + check(o0, "o0", "", "", "") + + o1 := otherContext{WithValue(Background(), k1, "c1k1")} + check(o1, "o1", "c1k1", "", "") + + o2 := WithValue(o1, k2, "o2k2") + check(o2, "o2", "c1k1", "o2k2", "") + + o3 := otherContext{c4} + check(o3, "o3", "", "c2k2", "c3k3") + + o4 := WithValue(o3, k3, nil) + check(o4, "o4", "", "c2k2", "") +} + +func TestAllocs(t *testing.T) { + bg := Background() + for _, test := range []struct { + desc string + f func() + limit float64 + gccgoLimit float64 + }{ + { + desc: "Background()", + f: func() { Background() }, + limit: 0, + gccgoLimit: 0, + }, + { + desc: fmt.Sprintf("WithValue(bg, %v, nil)", k1), + f: func() { + c := WithValue(bg, k1, nil) + c.Value(k1) + }, + limit: 3, + gccgoLimit: 3, + }, + { + desc: "WithTimeout(bg, 15*time.Millisecond)", + f: func() { + c, _ := WithTimeout(bg, 15*time.Millisecond) + <-c.Done() + }, + limit: 8, + gccgoLimit: 13, + }, + { + desc: "WithCancel(bg)", + f: func() { + c, cancel := WithCancel(bg) + cancel() + <-c.Done() + }, + limit: 5, + gccgoLimit: 8, + }, + { + desc: "WithTimeout(bg, 100*time.Millisecond)", + f: func() { + c, cancel := WithTimeout(bg, 100*time.Millisecond) + cancel() + <-c.Done() + }, + limit: 8, + gccgoLimit: 25, + }, + } { + limit := test.limit + if runtime.Compiler == "gccgo" { + // gccgo does not yet do escape analysis. + // TOOD(iant): Remove this when gccgo does do escape analysis. + limit = test.gccgoLimit + } + if n := testing.AllocsPerRun(100, test.f); n > limit { + t.Errorf("%s allocs = %f want %d", test.desc, n, int(limit)) + } + } +} + +func TestSimultaneousCancels(t *testing.T) { + root, cancel := WithCancel(Background()) + m := map[Context]CancelFunc{root: cancel} + q := []Context{root} + // Create a tree of contexts. + for len(q) != 0 && len(m) < 100 { + parent := q[0] + q = q[1:] + for i := 0; i < 4; i++ { + ctx, cancel := WithCancel(parent) + m[ctx] = cancel + q = append(q, ctx) + } + } + // Start all the cancels in a random order. + var wg sync.WaitGroup + wg.Add(len(m)) + for _, cancel := range m { + go func(cancel CancelFunc) { + cancel() + wg.Done() + }(cancel) + } + // Wait on all the contexts in a random order. + for ctx := range m { + select { + case <-ctx.Done(): + case <-time.After(1 * time.Second): + buf := make([]byte, 10<<10) + n := runtime.Stack(buf, true) + t.Fatalf("timed out waiting for <-ctx.Done(); stacks:\n%s", buf[:n]) + } + } + // Wait for all the cancel functions to return. + done := make(chan struct{}) + go func() { + wg.Wait() + close(done) + }() + select { + case <-done: + case <-time.After(1 * time.Second): + buf := make([]byte, 10<<10) + n := runtime.Stack(buf, true) + t.Fatalf("timed out waiting for cancel functions; stacks:\n%s", buf[:n]) + } +} + +func TestInterlockedCancels(t *testing.T) { + parent, cancelParent := WithCancel(Background()) + child, cancelChild := WithCancel(parent) + go func() { + parent.Done() + cancelChild() + }() + cancelParent() + select { + case <-child.Done(): + case <-time.After(1 * time.Second): + buf := make([]byte, 10<<10) + n := runtime.Stack(buf, true) + t.Fatalf("timed out waiting for child.Done(); stacks:\n%s", buf[:n]) + } +} + +func TestLayersCancel(t *testing.T) { + testLayers(t, time.Now().UnixNano(), false) +} + +func TestLayersTimeout(t *testing.T) { + testLayers(t, time.Now().UnixNano(), true) +} + +func testLayers(t *testing.T, seed int64, testTimeout bool) { + rand.Seed(seed) + errorf := func(format string, a ...interface{}) { + t.Errorf(fmt.Sprintf("seed=%d: %s", seed, format), a...) + } + const ( + timeout = 200 * time.Millisecond + minLayers = 30 + ) + type value int + var ( + vals []*value + cancels []CancelFunc + numTimers int + ctx = Background() + ) + for i := 0; i < minLayers || numTimers == 0 || len(cancels) == 0 || len(vals) == 0; i++ { + switch rand.Intn(3) { + case 0: + v := new(value) + ctx = WithValue(ctx, v, v) + vals = append(vals, v) + case 1: + var cancel CancelFunc + ctx, cancel = WithCancel(ctx) + cancels = append(cancels, cancel) + case 2: + var cancel CancelFunc + ctx, cancel = WithTimeout(ctx, timeout) + cancels = append(cancels, cancel) + numTimers++ + } + } + checkValues := func(when string) { + for _, key := range vals { + if val := ctx.Value(key).(*value); key != val { + errorf("%s: ctx.Value(%p) = %p want %p", when, key, val, key) + } + } + } + select { + case <-ctx.Done(): + errorf("ctx should not be canceled yet") + default: + } + if s, prefix := fmt.Sprint(ctx), "context.Background."; !strings.HasPrefix(s, prefix) { + t.Errorf("ctx.String() = %q want prefix %q", s, prefix) + } + t.Log(ctx) + checkValues("before cancel") + if testTimeout { + select { + case <-ctx.Done(): + case <-time.After(timeout + timeout/10): + errorf("ctx should have timed out") + } + checkValues("after timeout") + } else { + cancel := cancels[rand.Intn(len(cancels))] + cancel() + select { + case <-ctx.Done(): + default: + errorf("ctx should be canceled") + } + checkValues("after cancel") + } +} + +func TestCancelRemoves(t *testing.T) { + checkChildren := func(when string, ctx Context, want int) { + if got := len(ctx.(*cancelCtx).children); got != want { + t.Errorf("%s: context has %d children, want %d", when, got, want) + } + } + + ctx, _ := WithCancel(Background()) + checkChildren("after creation", ctx, 0) + _, cancel := WithCancel(ctx) + checkChildren("with WithCancel child ", ctx, 1) + cancel() + checkChildren("after cancelling WithCancel child", ctx, 0) + + ctx, _ = WithCancel(Background()) + checkChildren("after creation", ctx, 0) + _, cancel = WithTimeout(ctx, 60*time.Minute) + checkChildren("with WithTimeout child ", ctx, 1) + cancel() + checkChildren("after cancelling WithTimeout child", ctx, 0) +} diff --git a/Godeps/_workspace/src/golang.org/x/net/context/withtimeout_test.go b/Godeps/_workspace/src/golang.org/x/net/context/withtimeout_test.go new file mode 100644 index 000000000..a6754dc36 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/context/withtimeout_test.go @@ -0,0 +1,26 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package context_test + +import ( + "fmt" + "time" + + "golang.org/x/net/context" +) + +func ExampleWithTimeout() { + // Pass a context with a timeout to tell a blocking function that it + // should abandon its work after the timeout elapses. + ctx, _ := context.WithTimeout(context.Background(), 100*time.Millisecond) + select { + case <-time.After(200 * time.Millisecond): + fmt.Println("overslept") + case <-ctx.Done(): + fmt.Println(ctx.Err()) // prints "context deadline exceeded" + } + // Output: + // context deadline exceeded +} diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go new file mode 100644 index 000000000..67e9f4d73 --- /dev/null +++ b/cmd/restic/cmd_mount.go @@ -0,0 +1,272 @@ +package main + +import ( + "encoding/binary" + "fmt" + "os" + "time" + + "golang.org/x/net/context" + + "github.com/restic/restic" + "github.com/restic/restic/backend" + "github.com/restic/restic/crypto" + "github.com/restic/restic/pack" + "github.com/restic/restic/repository" + + "bazil.org/fuse" + "bazil.org/fuse/fs" +) + +type CmdMount struct { + global *GlobalOptions +} + +func init() { + _, err := parser.AddCommand("mount", + "mount a repository", + "The mount command mounts a repository read-only to a given directory", + &CmdMount{global: &globalOpts}) + if err != nil { + panic(err) + } +} + +func (cmd CmdMount) Usage() string { + return "MOUNTPOINT" +} + +func (cmd CmdMount) Execute(args []string) error { + if len(args) == 0 { + return fmt.Errorf("wrong number of parameters, Usage: %s", cmd.Usage()) + } + + repo, err := cmd.global.OpenRepository() + if err != nil { + return err + } + + err = repo.LoadIndex() + if err != nil { + return err + } + + mountpoint := args[0] + if _, err := os.Stat(mountpoint); err != nil { + if os.IsNotExist(err) { + err = os.Mkdir(mountpoint, os.ModeDir|0755) + if err != nil { + return err + } + } + } + c, err := fuse.Mount( + mountpoint, + fuse.ReadOnly(), + fuse.FSName("restic"), + ) + if err != nil { + return err + } + + root := fs.Tree{} + root.Add("snapshots", &snapshots{repo}) + + fmt.Printf("Now serving %s under %s\n", repo.Backend().Location(), mountpoint) + fmt.Println("Don't forget to umount after quitting !") + + err = fs.Serve(c, &root) + if err != nil { + return err + } + + <-c.Ready + return c.MountError +} + +var _ = fs.HandleReadDirAller(&snapshots{}) +var _ = fs.NodeStringLookuper(&snapshots{}) + +type snapshots struct { + repo *repository.Repository +} + +func (sn *snapshots) Attr(a *fuse.Attr) { + a.Inode = 0 + a.Mode = os.ModeDir | 0555 +} + +func (sn *snapshots) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { + ret := make([]fuse.Dirent, 0) + for id := range sn.repo.List(backend.Snapshot, make(chan struct{})) { + snapshot, err := restic.LoadSnapshot(sn.repo, id) + if err != nil { + return nil, err + } + ret = append(ret, fuse.Dirent{ + Inode: binary.BigEndian.Uint64(id[:8]), + Type: fuse.DT_Dir, + Name: snapshot.Time.Format(time.RFC3339), + }) + } + + return ret, nil +} + +func (sn *snapshots) Lookup(ctx context.Context, name string) (fs.Node, error) { + // This is kind of lame: we reload each snapshot and check the name + // (which is the timestamp) + for id := range sn.repo.List(backend.Snapshot, make(chan struct{})) { + snapshot, err := restic.LoadSnapshot(sn.repo, id) + if err != nil { + return nil, err + } + if snapshot.Time.Format(time.RFC3339) == name { + tree, err := restic.LoadTree(sn.repo, snapshot.Tree) + if err != nil { + return nil, err + } + return &dir{ + repo: sn.repo, + tree: tree, + }, nil + } + } + return nil, fuse.ENOENT +} + +var _ = fs.HandleReadDirAller(&dir{}) +var _ = fs.NodeStringLookuper(&dir{}) + +type dir struct { + repo *repository.Repository + tree *restic.Tree + inode uint64 +} + +func (d *dir) Attr(a *fuse.Attr) { + a.Inode = d.inode + a.Mode = os.ModeDir | 0555 +} + +func (d *dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { + ret := make([]fuse.Dirent, 0, len(d.tree.Nodes)) + + for _, node := range d.tree.Nodes { + var typ fuse.DirentType + switch { + case node.Mode.IsDir(): + typ = fuse.DT_Dir + case node.Mode.IsRegular(): + typ = fuse.DT_File + } + + ret = append(ret, fuse.Dirent{ + Inode: node.Inode, + Type: typ, + Name: node.Name, + }) + } + + return ret, nil +} + +func (d *dir) Lookup(ctx context.Context, name string) (fs.Node, error) { + for _, node := range d.tree.Nodes { + if node.Name == name { + switch { + case node.Mode.IsDir(): + subtree, err := restic.LoadTree(d.repo, node.Subtree) + if err != nil { + return nil, err + } + return &dir{ + repo: d.repo, + tree: subtree, + inode: binary.BigEndian.Uint64(node.Subtree[:8]), + }, nil + case node.Mode.IsRegular(): + return makeFile(d.repo, node) + } + } + } + + return nil, fuse.ENOENT +} + +var _ = fs.HandleReader(&file{}) + +type file struct { + repo *repository.Repository + node *restic.Node + + sizes []uint32 + + // cleartext contents + clearContent [][]byte +} + +func makeFile(repo *repository.Repository, node *restic.Node) (*file, error) { + sizes := make([]uint32, len(node.Content)) + for i, bid := range node.Content { + _, _, _, length, err := repo.Index().Lookup(bid) + if err != nil { + return nil, err + } + sizes[i] = uint32(length) - crypto.Extension + } + + return &file{ + repo: repo, + node: node, + sizes: sizes, + clearContent: make([][]byte, len(node.Content)), + }, nil +} + +func (f *file) Attr(a *fuse.Attr) { + a.Inode = f.node.Inode + a.Mode = f.node.Mode + a.Size = f.node.Size +} + +func (f *file) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { + off := req.Offset + content := make([]byte, req.Size) + allContent := content + + for i := range f.node.Content { + if off >= int64(f.sizes[i]) { + off -= int64(f.sizes[i]) + continue + } + + var subContent []byte + if f.clearContent[i] != nil { + subContent = f.clearContent[i] + } else { + var err error + subContent, err = f.repo.LoadBlob(pack.Data, f.node.Content[i]) + if err != nil { + return err + } + f.clearContent[i] = subContent + } + + subContent = subContent[off:] + off = 0 + + var copied int + if len(subContent) > len(content) { + copied = copy(content[0:], subContent[:len(content)]) + } else { + copied = copy(content[0:], subContent) + } + content = content[copied:] + if len(content) == 0 { + break + } + } + resp.Data = allContent + return nil +} From 3a8261224449601c5af0eae3b6f53f74d121da1b Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sat, 18 Jul 2015 19:07:59 +0200 Subject: [PATCH 02/24] Update bazil.org/fuse version --- Godeps/Godeps.json | 2 +- .../_workspace/src/bazil.org/fuse/.gitignore | 3 + .../_workspace/src/bazil.org/fuse/README.md | 2 +- .../_workspace/src/bazil.org/fuse/buffer.go | 35 + .../fuse/examples/clockfs/clockfs.go | 173 +++++ .../fuse/{ => examples}/hellofs/hello.go | 6 +- .../src/bazil.org/fuse/fs/bench/bench_test.go | 25 +- .../bazil.org/fuse/fs/fstestutil/mounted.go | 34 +- .../fuse/fs/fstestutil/record/record.go | 5 +- .../bazil.org/fuse/fs/fstestutil/testfs.go | 17 +- .../_workspace/src/bazil.org/fuse/fs/serve.go | 505 +++++++++---- .../src/bazil.org/fuse/fs/serve_test.go | 666 +++++++++++++++-- .../_workspace/src/bazil.org/fuse/fs/tree.go | 3 +- Godeps/_workspace/src/bazil.org/fuse/fuse.go | 706 +++++++++++------- .../src/bazil.org/fuse/fuse_kernel.go | 217 ++++-- .../src/bazil.org/fuse/fuse_kernel_darwin.go | 2 + .../src/bazil.org/fuse/fuse_kernel_freebsd.go | 2 + .../src/bazil.org/fuse/fuse_kernel_linux.go | 4 +- .../src/bazil.org/fuse/mount_darwin.go | 4 +- .../src/bazil.org/fuse/mount_freebsd.go | 2 +- .../src/bazil.org/fuse/mount_linux.go | 60 +- .../_workspace/src/bazil.org/fuse/options.go | 62 +- .../src/bazil.org/fuse/options_darwin.go | 4 +- .../src/bazil.org/fuse/options_freebsd.go | 2 +- .../src/bazil.org/fuse/options_helper_test.go | 8 +- .../src/bazil.org/fuse/options_linux.go | 2 +- .../bazil.org/fuse/options_nocomma_test.go | 7 +- .../src/bazil.org/fuse/options_test.go | 17 +- .../_workspace/src/bazil.org/fuse/protocol.go | 75 ++ cmd/restic/cmd_mount.go | 9 +- 30 files changed, 2062 insertions(+), 597 deletions(-) create mode 100644 Godeps/_workspace/src/bazil.org/fuse/buffer.go create mode 100644 Godeps/_workspace/src/bazil.org/fuse/examples/clockfs/clockfs.go rename Godeps/_workspace/src/bazil.org/fuse/{ => examples}/hellofs/hello.go (92%) create mode 100644 Godeps/_workspace/src/bazil.org/fuse/protocol.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index b5d73b44d..8e0f27b54 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -7,7 +7,7 @@ "Deps": [ { "ImportPath": "bazil.org/fuse", - "Rev": "6312e7c7c12b9337021a37aff2b0f655f4709688" + "Rev": "18419ee53958df28fcfc9490fe6123bd59e237bb" }, { "ImportPath": "github.com/jessevdk/go-flags", diff --git a/Godeps/_workspace/src/bazil.org/fuse/.gitignore b/Godeps/_workspace/src/bazil.org/fuse/.gitignore index 2b286ca94..53589948c 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/.gitignore +++ b/Godeps/_workspace/src/bazil.org/fuse/.gitignore @@ -6,3 +6,6 @@ .*.swp *.test + +/clockfs +/hellofs diff --git a/Godeps/_workspace/src/bazil.org/fuse/README.md b/Godeps/_workspace/src/bazil.org/fuse/README.md index 471b2b258..8c6d556ee 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/README.md +++ b/Godeps/_workspace/src/bazil.org/fuse/README.md @@ -15,7 +15,7 @@ Here’s how to get going: Website: http://bazil.org/fuse/ -Github repository: https://github.com/bazillion/fuse +Github repository: https://github.com/bazil/fuse API docs: http://godoc.org/bazil.org/fuse diff --git a/Godeps/_workspace/src/bazil.org/fuse/buffer.go b/Godeps/_workspace/src/bazil.org/fuse/buffer.go new file mode 100644 index 000000000..bb1d2b776 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/buffer.go @@ -0,0 +1,35 @@ +package 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 +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/examples/clockfs/clockfs.go b/Godeps/_workspace/src/bazil.org/fuse/examples/clockfs/clockfs.go new file mode 100644 index 000000000..178fda943 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/examples/clockfs/clockfs.go @@ -0,0 +1,173 @@ +// Clockfs implements a file system with the current time in a file. +// It was written to demonstrate kernel cache invalidation. +package main + +import ( + "flag" + "fmt" + "log" + "os" + "sync/atomic" + "syscall" + "time" + + "bazil.org/fuse" + "bazil.org/fuse/fs" + _ "bazil.org/fuse/fs/fstestutil" + "bazil.org/fuse/fuseutil" + "golang.org/x/net/context" +) + +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("clock"), + fuse.Subtype("clockfsfs"), + fuse.LocalVolume(), + fuse.VolumeName("Clock filesystem"), + ) + if err != nil { + log.Fatal(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 { + log.Fatal(err) + } + + // Check if the mount process has an error to report. + <-c.Ready + if err := c.MountError; 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 | 0555 + 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, fuse.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 { + fs.NodeRef + 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 = 0444 + 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() + } +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/hellofs/hello.go b/Godeps/_workspace/src/bazil.org/fuse/examples/hellofs/hello.go similarity index 92% rename from Godeps/_workspace/src/bazil.org/fuse/hellofs/hello.go rename to Godeps/_workspace/src/bazil.org/fuse/examples/hellofs/hello.go index 5d9febe75..5ec5ce8ee 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/hellofs/hello.go +++ b/Godeps/_workspace/src/bazil.org/fuse/examples/hellofs/hello.go @@ -63,9 +63,10 @@ func (FS) Root() (fs.Node, error) { // Dir implements both Node and Handle for the root directory. type Dir struct{} -func (Dir) Attr(a *fuse.Attr) { +func (Dir) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 1 a.Mode = os.ModeDir | 0555 + return nil } func (Dir) Lookup(ctx context.Context, name string) (fs.Node, error) { @@ -88,10 +89,11 @@ type File struct{} const greeting = "hello, world\n" -func (File) Attr(a *fuse.Attr) { +func (File) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 2 a.Mode = 0444 a.Size = uint64(len(greeting)) + return nil } func (File) ReadAll(ctx context.Context) ([]byte, error) { diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/bench/bench_test.go b/Godeps/_workspace/src/bazil.org/fuse/fs/bench/bench_test.go index c1177671a..d26f82c17 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fs/bench/bench_test.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/bench/bench_test.go @@ -22,13 +22,6 @@ type benchFS struct { } var _ = fs.FS(benchFS{}) -var _ = fs.FSIniter(benchFS{}) - -func (benchFS) Init(ctx context.Context, req *fuse.InitRequest, resp *fuse.InitResponse) error { - resp.MaxReadahead = 64 * 1024 * 1024 - resp.Flags |= fuse.InitAsyncRead - return nil -} func (f benchFS) Root() (fs.Node, error) { return benchDir{conf: f.conf}, nil @@ -43,9 +36,10 @@ var _ = fs.NodeStringLookuper(benchDir{}) var _ = fs.Handle(benchDir{}) var _ = fs.HandleReadDirAller(benchDir{}) -func (benchDir) Attr(a *fuse.Attr) { +func (benchDir) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 1 a.Mode = os.ModeDir | 0555 + return nil } func (d benchDir) Lookup(ctx context.Context, name string) (fs.Node, error) { @@ -73,10 +67,11 @@ var _ = fs.Handle(benchFile{}) var _ = fs.HandleReader(benchFile{}) var _ = fs.HandleWriter(benchFile{}) -func (benchFile) Attr(a *fuse.Attr) { +func (benchFile) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 2 a.Mode = 0644 a.Size = 9999999999999999 + return nil } func (f benchFile) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { @@ -103,12 +98,14 @@ func (benchFile) Fsync(ctx context.Context, req *fuse.FsyncRequest) error { } func benchmark(b *testing.B, fn func(b *testing.B, mnt string), conf *benchConfig) { - srv := &fs.Server{ - FS: benchFS{ - conf: conf, - }, + filesys := benchFS{ + conf: conf, } - mnt, err := fstestutil.Mounted(srv) + mnt, err := fstestutil.Mounted(filesys, nil, + fuse.MaxReadahead(64*1024*1024), + fuse.AsyncRead(), + fuse.WritebackCache(), + ) if err != nil { b.Fatal(err) } diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mounted.go b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mounted.go index 5c30011df..1209d2572 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mounted.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mounted.go @@ -17,7 +17,8 @@ type Mount struct { // Dir is the temporary directory where the filesystem is mounted. Dir string - Conn *fuse.Conn + Conn *fuse.Conn + Server *fs.Server // Error will receive the return value of Serve. Error <-chan error @@ -55,7 +56,7 @@ func (mnt *Mount) Close() { // workaround). // // After successful return, caller must clean up by calling Close. -func Mounted(srv *fs.Server, options ...fuse.MountOption) (*Mount, error) { +func Mounted(filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) { dir, err := ioutil.TempDir("", "fusetest") if err != nil { return nil, err @@ -64,26 +65,27 @@ func Mounted(srv *fs.Server, options ...fuse.MountOption) (*Mount, error) { 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, - Error: serveErr, - done: done, + Dir: dir, + Conn: c, + Server: server, + Error: serveErr, + done: done, } go func() { defer close(done) - serveErr <- srv.Serve(c) + serveErr <- server.Serve(filesys) }() select { case <-mnt.Conn.Ready: - if mnt.Conn.MountError != nil { + if err := mnt.Conn.MountError; err != nil { return nil, err } - return mnt, err + return mnt, nil case err = <-mnt.Error: // Serve quit early if err != nil { @@ -100,14 +102,14 @@ func Mounted(srv *fs.Server, options ...fuse.MountOption) (*Mount, error) { // // The debug log is not enabled by default. Use `-fuse.debug` or call // DebugByDefault to enable. -func MountedT(t testing.TB, filesys fs.FS, options ...fuse.MountOption) (*Mount, error) { - srv := &fs.Server{ - FS: filesys, +func MountedT(t testing.TB, filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) { + if conf == nil { + conf = &fs.Config{} } - if debug { - srv.Debug = func(msg interface{}) { + if debug && conf.Debug == nil { + conf.Debug = func(msg interface{}) { t.Logf("FUSE: %s", msg) } } - return Mounted(srv, options...) + return Mounted(filesys, conf, options...) } diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/record/record.go b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/record/record.go index fff6fdbd2..2bf2e9f8b 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/record/record.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/record/record.go @@ -35,7 +35,7 @@ type Counter struct { } func (r *Counter) Inc() { - atomic.StoreUint32(&r.count, 1) + atomic.AddUint32(&r.count, 1) } func (r *Counter) Count() uint32 { @@ -341,6 +341,9 @@ var _ = fs.NodeSetxattrer(&Setxattrs{}) // 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 } diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/testfs.go b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/testfs.go index 63be5b1ca..c1988bf70 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/testfs.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/testfs.go @@ -22,29 +22,32 @@ func (f SimpleFS) Root() (fs.Node, error) { // File can be embedded in a struct to make it look like a file. type File struct{} -func (f File) Attr(a *fuse.Attr) { +func (f File) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0666 + return nil } // Dir can be embedded in a struct to make it look like a directory. type Dir struct{} -func (f Dir) Attr(a *fuse.Attr) { +func (f Dir) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = os.ModeDir | 0777 + return nil } // ChildMap is a directory with child nodes looked up from a map. type ChildMap map[string]fs.Node -var _ = fs.Node(ChildMap{}) -var _ = fs.NodeStringLookuper(ChildMap{}) +var _ = fs.Node(&ChildMap{}) +var _ = fs.NodeStringLookuper(&ChildMap{}) -func (f ChildMap) Attr(a *fuse.Attr) { +func (f *ChildMap) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = os.ModeDir | 0777 + return nil } -func (f ChildMap) Lookup(ctx context.Context, name string) (fs.Node, error) { - child, ok := f[name] +func (f *ChildMap) Lookup(ctx context.Context, name string) (fs.Node, error) { + child, ok := (*f)[name] if !ok { return nil, fuse.ENOENT } diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/serve.go b/Godeps/_workspace/src/bazil.org/fuse/fs/serve.go index bbc4f6140..e5c3de865 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fs/serve.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/serve.go @@ -7,7 +7,9 @@ import ( "fmt" "hash/fnv" "io" + "log" "reflect" + "runtime" "strings" "sync" "time" @@ -30,19 +32,12 @@ const ( // 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 FSIniter. +// FS* interfaces, for example FSStatfser. type FS interface { // Root is called to obtain the Node for the file system root. Root() (Node, error) } -type FSIniter interface { - // Init is called to initialize the FUSE connection. - // It can inspect the request and adjust the response as desired. - // Init must return promptly. - Init(ctx context.Context, req *fuse.InitRequest, resp *fuse.InitResponse) error -} - type FSStatfser interface { // Statfs is called to obtain file system metadata. // It should write that data to resp. @@ -81,10 +76,20 @@ type FSInodeGenerator interface { // 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(*fuse.Attr) + // Attr fills attr with the standard metadata for the node. + Attr(ctx context.Context, attr *fuse.Attr) error } type NodeGetattrer interface { @@ -152,7 +157,7 @@ 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 nil, err. + // the directory, Lookup should return ENOENT. // // Lookup need not to handle the names "." and "..". Lookup(ctx context.Context, name string) (Node, error) @@ -238,14 +243,17 @@ type NodeRemovexattrer interface { var startTime = time.Now() -func nodeAttr(n Node) (attr fuse.Attr) { +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 - n.Attr(&attr) - return + if err := n.Attr(ctx, attr); err != nil { + return err + } + return nil } // A Handle is the interface required of an opened file or directory. @@ -306,43 +314,97 @@ type HandleReleaser interface { Release(ctx context.Context, req *fuse.ReleaseRequest) error } -type Server struct { - FS FS - +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 create new contexts. If nil, use + // context.Background. + // + // Note that changing this may not affect existing calls to Serve. + GetContext func() 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{}, + dynamicInode: GenerateDynamicInode, + } + if config != nil { + s.debug = config.Debug + s.context = config.GetContext + } + if s.debug == nil { + s.debug = fuse.Debug + } + if s.context == nil { + s.context = context.Background + } + return s +} + +type Server struct { + // set in New + conn *fuse.Conn + debug func(msg interface{}) + context func() 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 + + // 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(c *fuse.Conn) error { - sc := serveConn{ - fs: s.FS, - debug: s.Debug, - req: map[fuse.RequestID]*serveRequest{}, - dynamicInode: GenerateDynamicInode, - } - if sc.debug == nil { - sc.debug = fuse.Debug - } - if dyn, ok := sc.fs.(FSInodeGenerator); ok { - sc.dynamicInode = dyn.GenerateInode +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 := sc.fs.Root() + root, err := fs.Root() if err != nil { return fmt.Errorf("cannot obtain root node: %v", err) } - sc.node = append(sc.node, nil, &serveNode{inode: 1, node: root, refs: 1}) - sc.handle = append(sc.handle, nil) + // 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 := c.ReadRequest() + req, err := s.conn.ReadRequest() if err != nil { if err == io.EOF { break @@ -350,7 +412,11 @@ func (s *Server) Serve(c *fuse.Conn) error { return err } - go sc.serve(req) + s.wg.Add(1) + go func() { + defer s.wg.Done() + s.serve(req) + }() } return nil } @@ -358,44 +424,40 @@ func (s *Server) Serve(c *fuse.Conn) error { // Serve serves a FUSE connection with the default settings. See // Server.Serve. func Serve(c *fuse.Conn, fs FS) error { - server := Server{ - FS: fs, - } - return server.Serve(c) + server := New(c, nil) + return server.Serve(fs) } type nothing struct{} -type serveConn struct { - meta sync.Mutex - fs FS - req map[fuse.RequestID]*serveRequest - node []*serveNode - handle []*serveHandle - freeNode []fuse.NodeID - freeHandle []fuse.HandleID - nodeGen uint64 - debug func(msg interface{}) - dynamicInode func(parent uint64, name string) uint64 -} - type serveRequest struct { Request fuse.Request cancel func() } type serveNode struct { - inode uint64 - node Node - refs uint64 + 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() (attr fuse.Attr) { - attr = nodeAttr(sn.node) +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 + return err } type serveHandle struct { @@ -404,42 +466,20 @@ type serveHandle struct { nodeID fuse.NodeID } -// NodeRef can be embedded in a Node to recognize the same Node being -// returned from multiple Lookup, Create etc calls. -// -// 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 NodeRef struct { - id fuse.NodeID - generation uint64 -} +// NodeRef is deprecated. It remains here to decrease code churn on +// FUSE library users. You may remove it from your program now; +// returning the same Node values are now recognized automatically, +// without needing NodeRef. +type NodeRef struct{} -// nodeRef is only ever accessed while holding serveConn.meta -func (n *NodeRef) nodeRef() *NodeRef { - return n -} - -type nodeRef interface { - nodeRef() *NodeRef -} - -func (c *serveConn) saveNode(inode uint64, node Node) (id fuse.NodeID, gen uint64) { +func (c *Server) saveNode(inode uint64, node Node) (id fuse.NodeID, gen uint64) { c.meta.Lock() defer c.meta.Unlock() - var ref *NodeRef - if nodeRef, ok := node.(nodeRef); ok { - ref = nodeRef.nodeRef() - - if ref.id != 0 { - // dropNode guarantees that NodeRef is zeroed at the same - // time as the NodeID is removed from serveConn.node, as - // guarded by c.meta; this means sn cannot be nil here - sn := c.node[ref.id] - sn.refs++ - return ref.id, ref.generation - } + 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} @@ -452,15 +492,12 @@ func (c *serveConn) saveNode(inode uint64, node Node) (id fuse.NodeID, gen uint6 id = fuse.NodeID(len(c.node)) c.node = append(c.node, sn) } - gen = c.nodeGen - if ref != nil { - ref.id = id - ref.generation = gen - } + sn.generation = c.nodeGen + c.nodeRef[node] = id return } -func (c *serveConn) saveHandle(handle Handle, nodeID fuse.NodeID) (id fuse.HandleID) { +func (c *Server) saveHandle(handle Handle, nodeID fuse.NodeID) (id fuse.HandleID) { c.meta.Lock() shandle := &serveHandle{handle: handle, nodeID: nodeID} if n := len(c.freeHandle); n > 0 { @@ -485,7 +522,7 @@ func (n *nodeRefcountDropBug) String() string { return fmt.Sprintf("bug: trying to drop %d of %d references to %v", n.N, n.Refs, n.Node) } -func (c *serveConn) dropNode(id fuse.NodeID, n uint64) (forget bool) { +func (c *Server) dropNode(id fuse.NodeID, n uint64) (forget bool) { c.meta.Lock() defer c.meta.Unlock() snode := c.node[id] @@ -508,18 +545,16 @@ func (c *serveConn) dropNode(id fuse.NodeID, n uint64) (forget bool) { snode.refs -= n if snode.refs == 0 { + snode.wg.Wait() c.node[id] = nil - if nodeRef, ok := snode.node.(nodeRef); ok { - ref := nodeRef.nodeRef() - *ref = NodeRef{} - } + delete(c.nodeRef, snode.node) c.freeNode = append(c.freeNode, id) return true } return false } -func (c *serveConn) dropHandle(id fuse.HandleID) { +func (c *Server) dropHandle(id fuse.HandleID) { c.meta.Lock() c.handle[id] = nil c.freeHandle = append(c.freeHandle, id) @@ -532,11 +567,11 @@ type missingHandle struct { } func (m missingHandle) String() string { - return fmt.Sprint("missing handle", m.Handle, m.MaxHandle) + return fmt.Sprint("missing handle: ", m.Handle, m.MaxHandle) } // Returns nil for invalid handles. -func (c *serveConn) getHandle(id fuse.HandleID) (shandle *serveHandle) { +func (c *Server) getHandle(id fuse.HandleID) (shandle *serveHandle) { c.meta.Lock() defer c.meta.Unlock() if id < fuse.HandleID(len(c.handle)) { @@ -609,6 +644,30 @@ func (r response) String() string { } } +type notification struct { + Op string + Node fuse.NodeID + Out interface{} `json:",omitempty"` + Err string `json:",omitempty"` +} + +func (n notification) String() string { + switch { + case n.Out != nil: + // make sure (seemingly) empty values are readable + switch n.Out.(type) { + case string: + return fmt.Sprintf("=> %s %d %q Err:%v", n.Op, n.Node, n.Out, n.Err) + case []byte: + return fmt.Sprintf("=> %s %d [% x] Err:%v", n.Op, n.Node, n.Out, n.Err) + default: + return fmt.Sprintf("=> %s %d %s Err:%v", n.Op, n.Node, n.Out, n.Err) + } + default: + return fmt.Sprintf("=> %s %d Err:%v", n.Op, n.Node, n.Err) + } +} + type logMissingNode struct { MaxNode fuse.NodeID } @@ -638,8 +697,32 @@ func (m *renameNewDirNodeNotFound) String() string { return fmt.Sprintf("In RenameRequest (request %#x), node %d not found", m.Request.Hdr().ID, m.In.NewDir) } -func (c *serveConn) serve(r fuse.Request) { - ctx, cancel := context.WithCancel(context.Background()) +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 +} + +func initLookupResponse(s *fuse.LookupResponse) { + s.EntryValid = entryValidTime +} + +func (c *Server) serve(r fuse.Request) { + ctx, cancel := context.WithCancel(c.context()) defer cancel() req := &serveRequest{Request: r, cancel: cancel} @@ -717,6 +800,22 @@ func (c *serveConn) serve(r fuse.Request) { c.meta.Unlock() } + 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) + } + }() + switch r := r.(type) { default: // Note: To FUSE, ENOSYS means "this server never implements this request." @@ -725,22 +824,6 @@ func (c *serveConn) serve(r fuse.Request) { done(fuse.ENOSYS) r.RespondError(fuse.ENOSYS) - // FS operations. - case *fuse.InitRequest: - s := &fuse.InitResponse{ - MaxWrite: 128 * 1024, - Flags: fuse.InitBigWrites, - } - if fs, ok := c.fs.(FSIniter); ok { - if err := fs.Init(ctx, r, s); err != nil { - done(err) - r.RespondError(err) - break - } - } - done(s) - r.Respond(s) - case *fuse.StatfsRequest: s := &fuse.StatfsResponse{} if fs, ok := c.fs.(FSStatfser); ok { @@ -763,8 +846,11 @@ func (c *serveConn) serve(r fuse.Request) { break } } else { - s.AttrValid = attrValidTime - s.Attr = snode.attr() + if err := snode.attr(ctx, &s.Attr); err != nil { + done(err) + r.RespondError(err) + break + } } done(s) r.Respond(s) @@ -782,15 +868,17 @@ func (c *serveConn) serve(r fuse.Request) { break } - if s.AttrValid == 0 { - s.AttrValid = attrValidTime + if err := snode.attr(ctx, &s.Attr); err != nil { + done(err) + r.RespondError(err) + break } - s.Attr = snode.attr() done(s) r.Respond(s) case *fuse.SymlinkRequest: s := &fuse.SymlinkResponse{} + initLookupResponse(&s.LookupResponse) n, ok := node.(NodeSymlinker) if !ok { done(fuse.EIO) // XXX or EPERM like Mkdir? @@ -803,7 +891,11 @@ func (c *serveConn) serve(r fuse.Request) { r.RespondError(err) break } - c.saveLookup(&s.LookupResponse, snode, r.NewName, n2) + if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.NewName, n2); err != nil { + done(err) + r.RespondError(err) + break + } done(s) r.Respond(s) @@ -852,7 +944,12 @@ func (c *serveConn) serve(r fuse.Request) { break } s := &fuse.LookupResponse{} - c.saveLookup(s, snode, r.NewName, n2) + initLookupResponse(s) + if err := c.saveLookup(ctx, s, snode, r.NewName, n2); err != nil { + done(err) + r.RespondError(err) + break + } done(s) r.Respond(s) @@ -887,6 +984,7 @@ func (c *serveConn) serve(r fuse.Request) { 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 { @@ -901,12 +999,17 @@ func (c *serveConn) serve(r fuse.Request) { r.RespondError(err) break } - c.saveLookup(s, snode, r.Name, n2) + if err := c.saveLookup(ctx, s, snode, r.Name, n2); err != nil { + done(err) + r.RespondError(err) + break + } done(s) r.Respond(s) case *fuse.MkdirRequest: s := &fuse.MkdirResponse{} + initLookupResponse(&s.LookupResponse) n, ok := node.(NodeMkdirer) if !ok { done(fuse.EPERM) @@ -919,7 +1022,11 @@ func (c *serveConn) serve(r fuse.Request) { r.RespondError(err) break } - c.saveLookup(&s.LookupResponse, snode, r.Name, n2) + if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.Name, n2); err != nil { + done(err) + r.RespondError(err) + break + } done(s) r.Respond(s) @@ -950,13 +1057,18 @@ func (c *serveConn) serve(r fuse.Request) { break } s := &fuse.CreateResponse{OpenResponse: fuse.OpenResponse{}} + initLookupResponse(&s.LookupResponse) n2, h2, err := n.Create(ctx, r, s) if err != nil { done(err) r.RespondError(err) break } - c.saveLookup(&s.LookupResponse, snode, r.Name, n2) + if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.Name, n2); err != nil { + done(err) + r.RespondError(err) + break + } s.Handle = c.saveHandle(h2, hdr.Node) done(s) r.Respond(s) @@ -1232,7 +1344,12 @@ func (c *serveConn) serve(r fuse.Request) { break } s := &fuse.LookupResponse{} - c.saveLookup(s, snode, r.Name, n2) + initLookupResponse(s) + if err := c.saveLookup(ctx, s, snode, r.Name, n2); err != nil { + done(err) + r.RespondError(err) + break + } done(s) r.Respond(s) @@ -1282,19 +1399,133 @@ func (c *serveConn) serve(r fuse.Request) { } } -func (c *serveConn) saveLookup(s *fuse.LookupResponse, snode *serveNode, elem string, n2 Node) { - s.Attr = nodeAttr(n2) +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) - if s.EntryValid == 0 { - s.EntryValid = entryValidTime + 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 "" } - if s.AttrValid == 0 { - s.AttrValid = attrValidTime + 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 } // DataHandle returns a read-only Handle that satisfies reads diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/serve_test.go b/Godeps/_workspace/src/bazil.org/fuse/fs/serve_test.go index 6d614ae78..762e0a963 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fs/serve_test.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/serve_test.go @@ -43,15 +43,17 @@ type symlink struct { target string } -func (f symlink) Attr(a *fuse.Attr) { +func (f symlink) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = os.ModeSymlink | 0666 + return nil } // fifo can be embedded in a struct to make it look like a named pipe. type fifo struct{} -func (f fifo) Attr(a *fuse.Attr) { +func (f fifo) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = os.ModeNamedPipe | 0666 + return nil } type badRootFS struct{} @@ -63,7 +65,7 @@ func (badRootFS) Root() (fs.Node, error) { func TestRootErr(t *testing.T) { t.Parallel() - mnt, err := fstestutil.MountedT(t, badRootFS{}) + 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 @@ -84,15 +86,58 @@ func TestRootErr(t *testing.T) { } } +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 | 0777 + return nil +} + +func (f testPanic) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) { + panic(panicSentinel{}) +} + +func TestPanic(t *testing.T) { + t.Parallel() + mnt, err := fstestutil.MountedT(t, testPanic{}, nil) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + err = os.Mkdir(mnt.Dir+"/trigger-a-panic", 0700) + if nerr, ok := err.(*os.PathError); !ok || nerr.Err != syscall.ENAMETOOLONG { + t.Fatalf("wrong error from panicking handler: %T: %v", err, err) + } +} + type testStatFS struct{} func (f testStatFS) Root() (fs.Node, error) { return f, nil } -func (f testStatFS) Attr(a *fuse.Attr) { +func (f testStatFS) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 1 a.Mode = os.ModeDir | 0777 + return nil } func (f testStatFS) Statfs(ctx context.Context, req *fuse.StatfsRequest, resp *fuse.StatfsResponse) error { @@ -103,12 +148,21 @@ func (f testStatFS) Statfs(ctx context.Context, req *fuse.StatfsRequest, resp *f func TestStatfs(t *testing.T) { t.Parallel() - mnt, err := fstestutil.MountedT(t, testStatFS{}) + mnt, err := fstestutil.MountedT(t, testStatFS{}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() + // 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(mnt.Dir + "/does-not-exist"); !os.IsNotExist(err) { + t.Fatal(err) + } + { var st syscall.Statfs_t err = syscall.Statfs(mnt.Dir, &st) @@ -143,7 +197,6 @@ func TestStatfs(t *testing.T) { t.Errorf("got Files = %d; want %d", g, e) } } - } // Test Stat of root. @@ -154,14 +207,17 @@ func (f root) Root() (fs.Node, error) { return f, nil } -func (root) Attr(a *fuse.Attr) { +func (root) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 1 a.Mode = os.ModeDir | 0555 + // This has to be a power of two, but try to pick something that's an unlikely default. + a.BlockSize = 65536 + return nil } func TestStatRoot(t *testing.T) { t.Parallel() - mnt, err := fstestutil.MountedT(t, root{}) + mnt, err := fstestutil.MountedT(t, root{}, nil) if err != nil { t.Fatal(err) } @@ -192,6 +248,13 @@ func TestStatRoot(t *testing.T) { if stat.Gid != 0 { t.Errorf("root has wrong gid: %d", stat.Gid) } + if mnt.Conn.Protocol().HasAttrBlockSize() { + // convert stat.Blksize too because it's int64 on Linux but + // int32 on Darwin. + if g, e := int64(stat.Blksize), int64(65536); g != e { + t.Errorf("root has wrong blocksize: %d != %d", g, e) + } + } } } @@ -203,9 +266,10 @@ type readAll struct { const hi = "hello, world" -func (readAll) Attr(a *fuse.Attr) { +func (readAll) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0666 a.Size = uint64(len(hi)) + return nil } func (readAll) ReadAll(ctx context.Context) ([]byte, error) { @@ -224,7 +288,7 @@ func testReadAll(t *testing.T, path string) { func TestReadAll(t *testing.T) { t.Parallel() - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": readAll{}}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": readAll{}}}, nil) if err != nil { t.Fatal(err) } @@ -239,9 +303,10 @@ type readWithHandleRead struct { fstestutil.File } -func (readWithHandleRead) Attr(a *fuse.Attr) { +func (readWithHandleRead) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0666 a.Size = uint64(len(hi)) + return nil } func (readWithHandleRead) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { @@ -251,7 +316,7 @@ func (readWithHandleRead) Read(ctx context.Context, req *fuse.ReadRequest, resp func TestReadAllWithHandleRead(t *testing.T) { t.Parallel() - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": readWithHandleRead{}}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": readWithHandleRead{}}}, nil) if err != nil { t.Fatal(err) } @@ -260,6 +325,98 @@ func TestReadAllWithHandleRead(t *testing.T) { testReadAll(t, mnt.Dir+"/child") } +type readFlags struct { + fstestutil.File + fileFlags record.Recorder +} + +func (r *readFlags) Attr(ctx context.Context, a *fuse.Attr) error { + a.Mode = 0666 + 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 TestReadFileFlags(t *testing.T) { + t.Parallel() + r := &readFlags{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": r}}, nil) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + if !mnt.Conn.Protocol().HasReadWriteFlags() { + t.Skip("Old FUSE protocol") + } + + f, err := os.OpenFile(mnt.Dir+"/child", os.O_RDWR|os.O_APPEND, 0666) + if err != nil { + t.Fatal(err) + } + defer f.Close() + if _, err := f.Read(make([]byte, 4096)); err != nil { + t.Fatal(err) + } + _ = f.Close() + + want := fuse.OpenReadWrite | fuse.OpenAppend + if g, e := r.fileFlags.Recorded().(fuse.OpenFlags), 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 = 0666 + a.Size = uint64(len(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 TestWriteFileFlags(t *testing.T) { + t.Parallel() + r := &writeFlags{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": r}}, nil) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + if !mnt.Conn.Protocol().HasReadWriteFlags() { + t.Skip("Old FUSE protocol") + } + + f, err := os.OpenFile(mnt.Dir+"/child", os.O_RDWR|os.O_APPEND, 0666) + if err != nil { + t.Fatal(err) + } + defer f.Close() + if _, err := f.Write(make([]byte, 4096)); err != nil { + t.Fatal(err) + } + _ = f.Close() + + want := fuse.OpenReadWrite | fuse.OpenAppend + if g, e := r.fileFlags.Recorded().(fuse.OpenFlags), want; g != e { + t.Errorf("write saw file flags %+v, want %+v", g, e) + } +} + // Test Release. type release struct { @@ -270,7 +427,7 @@ type release struct { func TestRelease(t *testing.T) { t.Parallel() r := &release{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": r}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": r}}, nil) if err != nil { t.Fatal(err) } @@ -297,7 +454,7 @@ type write struct { func TestWrite(t *testing.T) { t.Parallel() w := &write{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": w}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": w}}, nil) if err != nil { t.Fatal(err) } @@ -344,7 +501,7 @@ type writeLarge struct { func TestWriteLarge(t *testing.T) { t.Parallel() w := &write{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": w}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": w}}, nil) if err != nil { t.Fatal(err) } @@ -391,7 +548,7 @@ type writeTruncateFlush struct { func TestWriteTruncateFlush(t *testing.T) { t.Parallel() w := &writeTruncateFlush{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": w}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": w}}, nil) if err != nil { t.Fatal(err) } @@ -427,7 +584,7 @@ func (f *mkdir1) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, er func TestMkdir(t *testing.T) { t.Parallel() f := &mkdir1{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } @@ -441,6 +598,9 @@ func TestMkdir(t *testing.T) { t.Fatalf("mkdir: %v", err) } want := fuse.MkdirRequest{Name: "foo", Mode: os.ModeDir | 0751} + if mnt.Conn.Protocol().HasUmask() { + want.Umask = 0022 + } if g, e := f.RecordedMkdir(), want; g != e { t.Errorf("mkdir saw %+v, want %+v", g, e) } @@ -489,7 +649,7 @@ func (f *create1) Create(ctx context.Context, req *fuse.CreateRequest, resp *fus func TestCreate(t *testing.T) { t.Parallel() f := &create1{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } @@ -558,7 +718,7 @@ func (f *create3) Remove(ctx context.Context, r *fuse.RemoveRequest) error { func TestCreateWriteRemove(t *testing.T) { t.Parallel() f := &create3{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } @@ -607,7 +767,7 @@ func (f *symlink1) Symlink(ctx context.Context, req *fuse.SymlinkRequest) (fs.No func TestSymlink(t *testing.T) { t.Parallel() f := &symlink1{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } @@ -656,7 +816,7 @@ func (f *link1) Link(ctx context.Context, r *fuse.LinkRequest, old fs.Node) (fs. func TestLink(t *testing.T) { t.Parallel() f := &link1{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } @@ -703,7 +863,7 @@ func (f *rename1) Rename(ctx context.Context, r *fuse.RenameRequest, newDir fs.N func TestRename(t *testing.T) { t.Parallel() f := &rename1{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } @@ -741,7 +901,7 @@ func TestMknod(t *testing.T) { } f := &mknod1{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } @@ -775,9 +935,10 @@ type dataHandleTest struct { fstestutil.File } -func (dataHandleTest) Attr(a *fuse.Attr) { +func (dataHandleTest) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0666 a.Size = uint64(len(hi)) + return nil } func (dataHandleTest) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { @@ -787,7 +948,7 @@ func (dataHandleTest) Open(ctx context.Context, req *fuse.OpenRequest, resp *fus func TestDataHandle(t *testing.T) { t.Parallel() f := &dataHandleTest{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -812,9 +973,10 @@ type interrupt struct { hanging chan struct{} } -func (interrupt) Attr(a *fuse.Attr) { +func (interrupt) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0666 a.Size = 1 + return nil } func (it *interrupt) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { @@ -830,7 +992,7 @@ func TestInterrupt(t *testing.T) { t.Parallel() f := &interrupt{} f.hanging = make(chan struct{}, 1) - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -894,7 +1056,7 @@ type truncate struct { func testTruncate(t *testing.T, toSize int64) { t.Parallel() f := &truncate{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -935,7 +1097,7 @@ type ftruncate struct { func testFtruncate(t *testing.T, toSize int64) { t.Parallel() f := &ftruncate{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -985,7 +1147,7 @@ type truncateWithOpen struct { func TestTruncateWithOpen(t *testing.T) { t.Parallel() f := &truncateWithOpen{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -1029,7 +1191,7 @@ func (d *readDirAll) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { func TestReadDirAll(t *testing.T) { t.Parallel() f := &readDirAll{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } @@ -1071,7 +1233,7 @@ type readDirNotImplemented struct { func TestReadDirNotImplemented(t *testing.T) { t.Parallel() f := &readDirNotImplemented{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } @@ -1112,7 +1274,7 @@ func (f *chmod) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fus func TestChmod(t *testing.T) { t.Parallel() f := &chmod{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -1140,13 +1302,12 @@ func (f *open) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenR 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) { t.Parallel() f := &open{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -1193,6 +1354,40 @@ func TestOpen(t *testing.T) { } } +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 TestOpenNonSeekable(t *testing.T) { + t.Parallel() + f := &openNonSeekable{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + if !mnt.Conn.Protocol().HasOpenNonSeekable() { + t.Skip("Old FUSE protocol") + } + + fil, err := os.Open(mnt.Dir + "/child") + if err != nil { + t.Fatal(err) + } + defer fil.Close() + + _, err = fil.Seek(0, os.SEEK_SET) + if nerr, ok := err.(*os.PathError); !ok || nerr.Err != syscall.ESPIPE { + t.Fatalf("wrong error: %v", err) + } +} + // Test Fsync on a dir type fsyncDir struct { @@ -1203,7 +1398,7 @@ type fsyncDir struct { func TestFsyncDir(t *testing.T) { t.Parallel() f := &fsyncDir{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } @@ -1254,7 +1449,7 @@ func (f *getxattr) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp func TestGetxattr(t *testing.T) { t.Parallel() f := &getxattr{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -1290,7 +1485,7 @@ func (f *getxattrTooSmall) Getxattr(ctx context.Context, req *fuse.GetxattrReque func TestGetxattrTooSmall(t *testing.T) { t.Parallel() f := &getxattrTooSmall{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -1321,7 +1516,7 @@ func (f *getxattrSize) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, func TestGetxattrSize(t *testing.T) { t.Parallel() f := &getxattrSize{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -1353,7 +1548,7 @@ func (f *listxattr) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, r func TestListxattr(t *testing.T) { t.Parallel() f := &listxattr{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -1392,7 +1587,7 @@ func (f *listxattrTooSmall) Listxattr(ctx context.Context, req *fuse.ListxattrRe func TestListxattrTooSmall(t *testing.T) { t.Parallel() f := &listxattrTooSmall{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -1423,7 +1618,7 @@ func (f *listxattrSize) Listxattr(ctx context.Context, req *fuse.ListxattrReques func TestListxattrSize(t *testing.T) { t.Parallel() f := &listxattrSize{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -1454,7 +1649,7 @@ func testSetxattr(t *testing.T, size int) { t.Parallel() f := &setxattr{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -1507,7 +1702,7 @@ type removexattr struct { func TestRemovexattr(t *testing.T) { t.Parallel() f := &removexattr{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -1537,7 +1732,7 @@ func (f defaultErrno) Lookup(ctx context.Context, name string) (fs.Node, error) func TestDefaultErrno(t *testing.T) { t.Parallel() - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{defaultErrno{}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{defaultErrno{}}, nil) if err != nil { t.Fatal(err) } @@ -1583,7 +1778,7 @@ func (f customErrNode) Lookup(ctx context.Context, name string) (fs.Node, error) func TestCustomErrno(t *testing.T) { t.Parallel() - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{customErrNode{}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{customErrNode{}}, nil) if err != nil { t.Fatal(err) } @@ -1619,12 +1814,13 @@ func (f *inMemoryFile) bytes() []byte { return f.data } -func (f *inMemoryFile) Attr(a *fuse.Attr) { +func (f *inMemoryFile) Attr(ctx context.Context, a *fuse.Attr) error { f.mu.Lock() defer f.mu.Unlock() a.Mode = 0666 a.Size = uint64(len(f.data)) + return nil } func (f *inMemoryFile) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { @@ -1703,7 +1899,7 @@ func TestMmap(t *testing.T) { w := &mmap{} w.data = make([]byte, mmapSize) - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": w}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": w}}, nil) if err != nil { t.Fatal(err) } @@ -1756,7 +1952,7 @@ func (directRead) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.Re func TestDirectRead(t *testing.T) { t.Parallel() - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": directRead{}}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": directRead{}}}, nil) if err != nil { t.Fatal(err) } @@ -1783,7 +1979,7 @@ func (f *directWrite) Open(ctx context.Context, req *fuse.OpenRequest, resp *fus func TestDirectWrite(t *testing.T) { t.Parallel() w := &directWrite{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": w}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": w}}, nil) if err != nil { t.Fatal(err) } @@ -1821,14 +2017,21 @@ type attrUnlinked struct { var _ fs.Node = attrUnlinked{} -func (f attrUnlinked) Attr(a *fuse.Attr) { - f.File.Attr(a) +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) { t.Parallel() - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": attrUnlinked{}}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": attrUnlinked{}}}, nil) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() fi, err := os.Stat(mnt.Dir + "/child") if err != nil { @@ -1841,3 +2044,360 @@ func TestAttrUnlinked(t *testing.T) { } } } + +// 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) { + t.Parallel() + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": attrBad{}}}, nil) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + _, err = os.Stat(mnt.Dir + "/child") + if nerr, ok := err.(*os.PathError); !ok || nerr.Err != syscall.ENAMETOOLONG { + t.Fatalf("wrong error: %v", err) + } +} + +// Test kernel cache invalidation + +type invalidateAttr struct { + fs.NodeRef + 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 = 0600 + return nil +} + +func TestInvalidateNodeAttr(t *testing.T) { + // This test may see false positive failures when run under + // extreme memory pressure. + t.Parallel() + 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() + + if !mnt.Conn.Protocol().HasInvalidate() { + t.Skip("Old FUSE protocol") + } + + for i := 0; i < 10; i++ { + if _, err := os.Stat(mnt.Dir + "/child"); err != nil { + t.Fatalf("stat error: %v", err) + } + } + if g, e := a.attr.Count(), uint32(1); g != e { + t.Errorf("wrong Attr call count: %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++ { + if _, err := os.Stat(mnt.Dir + "/child"); err != nil { + t.Fatalf("stat error: %v", err) + } + } + if g, e := a.attr.Count(), uint32(2); g != e { + t.Errorf("wrong Attr call count: %d != %d", g, e) + } +} + +type invalidateData struct { + fs.NodeRef + t testing.TB + attr record.Counter + read record.Counter +} + +const invalidateDataContent = "hello, world\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 = 0600 + a.Size = uint64(len(invalidateDataContent)) + 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(invalidateDataContent)) + return nil +} + +func TestInvalidateNodeData(t *testing.T) { + // This test may see false positive failures when run under + // extreme memory pressure. + t.Parallel() + a := &invalidateData{ + t: t, + } + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": a}}, nil) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + if !mnt.Conn.Protocol().HasInvalidate() { + t.Skip("Old FUSE protocol") + } + + f, err := os.Open(mnt.Dir + "/child") + if err != nil { + t.Fatal(err) + } + defer f.Close() + + buf := make([]byte, 4) + for i := 0; i < 10; i++ { + if _, err := f.ReadAt(buf, 0); err != nil { + t.Fatalf("readat error: %v", err) + } + } + if g, e := a.attr.Count(), uint32(1); g != e { + t.Errorf("wrong Attr call count: %d != %d", 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...") + if err := mnt.Server.InvalidateNodeData(a); err != nil { + t.Fatalf("invalidate error: %v", err) + } + + for i := 0; i < 10; i++ { + if _, err := f.ReadAt(buf, 0); err != nil { + t.Fatalf("readat error: %v", err) + } + } + if g, e := a.attr.Count(), uint32(1); g != e { + t.Errorf("wrong Attr call count: %d != %d", 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 { + fs.NodeRef + 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 = 0600 + 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 TestInvalidateNodeDataRange(t *testing.T) { + // This test may see false positive failures when run under + // extreme memory pressure. + t.Parallel() + 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() + + if !mnt.Conn.Protocol().HasInvalidate() { + t.Skip("Old FUSE protocol") + } + + f, err := os.Open(mnt.Dir + "/child") + if err != nil { + t.Fatal(err) + } + defer f.Close() + + buf := make([]byte, 4) + for i := 0; i < 10; i++ { + if _, err := f.ReadAt(buf, 0); err != nil { + t.Fatalf("readat error: %v", err) + } + } + if g, e := a.attr.Count(), uint32(1); g != e { + t.Errorf("wrong Attr call count: %d != %d", 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...") + if err := mnt.Server.InvalidateNodeDataRange(a, 4096, 4096); err != nil { + t.Fatalf("invalidate error: %v", err) + } + + for i := 0; i < 10; i++ { + if _, err := f.ReadAt(buf, 0); err != nil { + t.Fatalf("readat error: %v", err) + } + } + if g, e := a.attr.Count(), uint32(1); g != e { + t.Errorf("wrong Attr call count: %d != %d", g, e) + } + // 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) + } +} + +type invalidateEntryRoot struct { + fs.NodeRef + t testing.TB + lookup record.Counter +} + +var _ fs.Node = (*invalidateEntryRoot)(nil) + +func (i *invalidateEntryRoot) Attr(ctx context.Context, a *fuse.Attr) error { + a.Mode = 0600 | 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, fuse.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. + t.Parallel() + a := &invalidateEntryRoot{ + t: t, + } + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{a}, nil) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + if !mnt.Conn.Protocol().HasInvalidate() { + t.Skip("Old FUSE protocol") + } + + for i := 0; i < 10; i++ { + if _, err := os.Stat(mnt.Dir + "/child"); err != nil { + t.Fatalf("stat error: %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++ { + if _, err := os.Stat(mnt.Dir + "/child"); err != nil { + t.Fatalf("stat error: %v", err) + } + } + if g, e := a.lookup.Count(), uint32(2); g != e { + t.Errorf("wrong Lookup call count: %d != %d", 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, fuse.ESTALE + } + data, ok := v.(string) + if !ok { + return nil, fuse.EIO + } + resp.Flags |= fuse.OpenDirectIO + return fs.DataHandle([]byte(data)), nil +} + +func TestContext(t *testing.T) { + t.Parallel() + ctx := context.Background() + const input = "kilroy was here" + ctx = context.WithValue(ctx, &contextFileSentinel, input) + mnt, err := fstestutil.MountedT(t, + fstestutil.SimpleFS{&fstestutil.ChildMap{"child": contextFile{}}}, + &fs.Config{ + GetContext: func() context.Context { return ctx }, + }) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + data, err := ioutil.ReadFile(mnt.Dir + "/child") + if err != nil { + t.Fatalf("cannot read context file: %v", err) + } + if g, e := string(data), input; g != e { + t.Errorf("read wrong data: %q != %q", g, e) + } +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/tree.go b/Godeps/_workspace/src/bazil.org/fuse/fs/tree.go index 07135ce2f..7e078045a 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fs/tree.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/tree.go @@ -77,8 +77,9 @@ func (t *tree) add(name string, n Node) { t.dir = append(t.dir, treeDir{name, n}) } -func (t *tree) Attr(a *fuse.Attr) { +func (t *tree) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = os.ModeDir | 0555 + return nil } func (t *tree) Lookup(ctx context.Context, name string) (Node, error) { diff --git a/Godeps/_workspace/src/bazil.org/fuse/fuse.go b/Godeps/_workspace/src/bazil.org/fuse/fuse.go index 8ef28cb60..c34d05981 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fuse.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fuse.go @@ -46,7 +46,7 @@ // 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 +// 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 @@ -68,7 +68,7 @@ // can implement ErrorNumber to control the errno returned. Without // ErrorNumber, a generic errno (EIO) is returned. // -// Errors messages will be visible in the debug log as part of the +// Error messages will be visible in the debug log as part of the // response. // // Interrupted Operations @@ -125,9 +125,11 @@ type Conn struct { // File handle for kernel communication. Only safe to access if // rio or wio is held. dev *os.File - buf []byte - wio sync.Mutex + wio sync.RWMutex rio sync.RWMutex + + // Protocol version negotiated with InitRequest/InitResponse. + proto Protocol } // Mount mounts a new FUSE connection on the named directory @@ -141,7 +143,7 @@ type Conn struct { // possible errors. Incoming requests on Conn must be served to make // progress. func Mount(dir string, options ...MountOption) (*Conn, error) { - conf := MountConfig{ + conf := mountConfig{ options: make(map[string]string), } for _, option := range options { @@ -159,9 +161,64 @@ func Mount(dir string, options ...MountOption) (*Conn, error) { return nil, err } c.dev = f + + if err := initMount(c, &conf); err != nil { + c.Close() + 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) +} + +func initMount(c *Conn, conf *mountConfig) error { + req, err := c.ReadRequest() + if err != nil { + if err == io.EOF { + return fmt.Errorf("missing init, got EOF") + } + 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 + + s := &InitResponse{ + Library: proto, + MaxReadahead: conf.maxReadahead, + MaxWrite: 128 * 1024, + Flags: InitBigWrites | conf.initFlags, + } + 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. @@ -215,13 +272,10 @@ func (h *Header) noResponse() { putMessage(h.msg) } -func (h *Header) respond(out *outHeader, n uintptr) { - h.Conn.respond(out, n) - putMessage(h.msg) -} - -func (h *Header) respondData(out *outHeader, n uintptr, data []byte) { - h.Conn.respondData(out, n, data) +func (h *Header) respond(msg []byte) { + out := (*outHeader)(unsafe.Pointer(&msg[0])) + out.Unique = uint64(h.ID) + h.Conn.respond(msg) putMessage(h.msg) } @@ -308,8 +362,10 @@ func (h *Header) RespondError(err error) { } // FUSE uses negative errors! // TODO: File bug report against OSXFUSE: positive error causes kernel panic. - out := &outHeader{Error: -int32(errno), Unique: uint64(h.ID)} - h.respond(out, unsafe.Sizeof(*out)) + buf := newBuffer(0) + hOut := (*outHeader)(unsafe.Pointer(&buf[0])) + hOut.Error = -int32(errno) + h.respond(buf) } // Maximum file write size we are prepared to receive from the kernel. @@ -449,6 +505,10 @@ func (c *Conn) fd() int { return int(c.dev.Fd()) } +func (c *Conn) Protocol() Protocol { + return c.proto +} + // ReadRequest returns the next FUSE request from the kernel. // // Caller must call either Request.Respond or Request.RespondError in @@ -529,8 +589,22 @@ loop: } case opGetattr: - req = &GetattrRequest{ - Header: m.Header(), + 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: @@ -595,33 +669,39 @@ loop: } case opMknod: - in := (*mknodIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + size := mknodInSize(c.proto) + if m.len() < size { goto corrupt } - name := m.bytes()[unsafe.Sizeof(*in):] + in := (*mknodIn)(m.data()) + name := m.bytes()[size:] if len(name) < 2 || name[len(name)-1] != '\x00' { goto corrupt } name = name[:len(name)-1] - req = &MknodRequest{ + 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: - in := (*mkdirIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + size := mkdirInSize(c.proto) + if m.len() < size { goto corrupt } - name := m.bytes()[unsafe.Sizeof(*in):] + in := (*mkdirIn)(m.data()) + name := m.bytes()[size:] i := bytes.IndexByte(name, '\x00') if i < 0 { goto corrupt } - req = &MkdirRequest{ + r := &MkdirRequest{ Header: m.Header(), Name: string(name[:i]), // observed on Linux: mkdirIn.Mode & syscall.S_IFMT == 0, @@ -629,6 +709,10 @@ loop: // 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() @@ -681,20 +765,26 @@ loop: case opRead, opReaddir: in := (*readIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + if m.len() < readInSize(c.proto) { goto corrupt } - req = &ReadRequest{ + 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 = in.LockOwner + r.FileFlags = openFlags(in.Flags) + } + req = r case opWrite: in := (*writeIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + if m.len() < writeInSize(c.proto) { goto corrupt } r := &WriteRequest{ @@ -703,7 +793,11 @@ loop: Offset: int64(in.Offset), Flags: WriteFlags(in.WriteFlags), } - buf := m.bytes()[unsafe.Sizeof(*in):] + if c.proto.GE(Protocol{7, 9}) { + r.LockOwner = in.LockOwner + r.FileFlags = openFlags(in.Flags) + } + buf := m.bytes()[writeInSize(c.proto):] if uint32(len(buf)) < in.Size { goto corrupt } @@ -823,8 +917,7 @@ loop: } req = &InitRequest{ Header: m.Header(), - Major: in.Major, - Minor: in.Minor, + Kernel: Protocol{in.Major, in.Minor}, MaxReadahead: in.MaxReadahead, Flags: InitFlags(in.Flags), } @@ -847,21 +940,26 @@ loop: } case opCreate: - in := (*createIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + size := createInSize(c.proto) + if m.len() < size { goto corrupt } - name := m.bytes()[unsafe.Sizeof(*in):] + in := (*createIn)(m.data()) + name := m.bytes()[size:] i := bytes.IndexByte(name, '\x00') if i < 0 { goto corrupt } - req = &CreateRequest{ + 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()) @@ -915,6 +1013,15 @@ 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 { @@ -923,13 +1030,14 @@ func errorString(err error) string { return err.Error() } -func (c *Conn) respond(out *outHeader, n uintptr) { - c.wio.Lock() - defer c.wio.Unlock() - out.Len = uint32(n) - msg := (*[1 << 30]byte)(unsafe.Pointer(out))[:n] +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 nn != len(msg) || err != nil { + if err == nil && nn != len(msg) { Debug(bugShortKernelWrite{ Written: int64(nn), Length: int64(len(msg)), @@ -937,24 +1045,100 @@ func (c *Conn) respond(out *outHeader, n uintptr) { Stack: stack(), }) } + return err } -func (c *Conn) respondData(out *outHeader, n uintptr, data []byte) { - c.wio.Lock() - defer c.wio.Unlock() - // TODO: use writev - out.Len = uint32(n + uintptr(len(data))) - msg := make([]byte, out.Len) - copy(msg, (*[1 << 30]byte)(unsafe.Pointer(out))[:n]) - copy(msg[n:], data) - syscall.Write(c.fd(), msg) +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{} +) + +// sendInvalidate sends an invalidate notification to kernel. +// +// A returned ENOENT is translated to a friendlier error. +func (c *Conn) sendInvalidate(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.sendInvalidate(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.sendInvalidate(buf) } // An InitRequest is the first request sent on a FUSE file system. type InitRequest struct { Header `json:"-"` - Major uint32 - Minor uint32 + Kernel Protocol // Maximum readahead in bytes that the kernel plans to use. MaxReadahead uint32 Flags InitFlags @@ -963,11 +1147,12 @@ type InitRequest struct { var _ = Request(&InitRequest{}) func (r *InitRequest) String() string { - return fmt.Sprintf("Init [%s] %d.%d ra=%d fl=%v", &r.Header, r.Major, r.Minor, r.MaxReadahead, r.Flags) + return fmt.Sprintf("Init [%s] %v ra=%d fl=%v", &r.Header, r.Kernel, r.MaxReadahead, r.Flags) } // 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 @@ -983,20 +1168,20 @@ func (r *InitResponse) String() string { // Respond replies to the request with the given response. func (r *InitRequest) Respond(resp *InitResponse) { - out := &initOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - Major: kernelVersion, - Minor: kernelMinorVersion, - MaxReadahead: resp.MaxReadahead, - Flags: uint32(resp.Flags), - MaxWrite: resp.MaxWrite, - } + 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.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(&out.outHeader, unsafe.Sizeof(*out)) + r.respond(buf) } // A StatfsRequest requests information about the mounted file system. @@ -1012,19 +1197,18 @@ func (r *StatfsRequest) String() string { // Respond replies to the request with the given response. func (r *StatfsRequest) Respond(resp *StatfsResponse) { - out := &statfsOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - St: kstatfs{ - Blocks: resp.Blocks, - Bfree: resp.Bfree, - Bavail: resp.Bavail, - Files: resp.Files, - Bsize: resp.Bsize, - Namelen: resp.Namelen, - Frsize: resp.Frsize, - }, + 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, + Bsize: resp.Bsize, + Namelen: resp.Namelen, + Frsize: resp.Frsize, } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + r.respond(buf) } // A StatfsResponse is the response to a StatfsRequest. @@ -1059,25 +1243,28 @@ func (r *AccessRequest) String() string { // Respond replies to the request indicating that access is allowed. // To deny access, use RespondError. func (r *AccessRequest) Respond() { - out := &outHeader{Unique: uint64(r.ID)} - r.respond(out, unsafe.Sizeof(*out)) + buf := newBuffer(0) + r.respond(buf) } // An Attr is the metadata for a single file or directory. type Attr struct { - Inode uint64 // inode number - Size uint64 // size in bytes - Blocks uint64 // size in blocks - 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 - Uid uint32 // owner uid - Gid uint32 // group gid - Rdev uint32 // device numbers - Flags uint32 // chflags(2) flags (OS X only) + 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 + 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 unix(t time.Time) (sec uint64, nsec uint32) { @@ -1087,7 +1274,7 @@ func unix(t time.Time) (sec uint64, nsec uint32) { return } -func (a *Attr) attr() (out attr) { +func (a *Attr) attr(out *attr, proto Protocol) { out.Ino = a.Inode out.Size = a.Size out.Blocks = a.Blocks @@ -1125,6 +1312,9 @@ func (a *Attr) attr() (out attr) { out.Gid = a.Gid out.Rdev = a.Rdev out.SetFlags(a.Flags) + if proto.GE(Protocol{7, 9}) { + out.Blksize = a.BlockSize + } return } @@ -1132,29 +1322,30 @@ func (a *Attr) attr() (out attr) { // 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{}) func (r *GetattrRequest) String() string { - return fmt.Sprintf("Getattr [%s]", &r.Header) + return fmt.Sprintf("Getattr [%s] %#x fl=%v", &r.Header, r.Handle, r.Flags) } // Respond replies to the request with the given response. func (r *GetattrRequest) Respond(resp *GetattrResponse) { - out := &attrOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - AttrValid: uint64(resp.AttrValid / time.Second), - AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), - Attr: resp.Attr.attr(), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + 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 { - AttrValid time.Duration // how long Attr can be cached - Attr Attr // file attributes + Attr Attr // file attributes } func (r *GetattrResponse) String() string { @@ -1187,14 +1378,14 @@ func (r *GetxattrRequest) String() string { // Respond replies to the request with the given response. func (r *GetxattrRequest) Respond(resp *GetxattrResponse) { if r.Size == 0 { - out := &getxattrOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - Size: uint32(len(resp.Xattr)), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + buf := newBuffer(unsafe.Sizeof(getxattrOut{})) + out := (*getxattrOut)(buf.alloc(unsafe.Sizeof(getxattrOut{}))) + out.Size = uint32(len(resp.Xattr)) + r.respond(buf) } else { - out := &outHeader{Unique: uint64(r.ID)} - r.respondData(out, unsafe.Sizeof(*out), resp.Xattr) + buf := newBuffer(uintptr(len(resp.Xattr))) + buf = append(buf, resp.Xattr...) + r.respond(buf) } } @@ -1223,14 +1414,14 @@ func (r *ListxattrRequest) String() string { // Respond replies to the request with the given response. func (r *ListxattrRequest) Respond(resp *ListxattrResponse) { if r.Size == 0 { - out := &getxattrOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - Size: uint32(len(resp.Xattr)), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + buf := newBuffer(unsafe.Sizeof(getxattrOut{})) + out := (*getxattrOut)(buf.alloc(unsafe.Sizeof(getxattrOut{}))) + out.Size = uint32(len(resp.Xattr)) + r.respond(buf) } else { - out := &outHeader{Unique: uint64(r.ID)} - r.respondData(out, unsafe.Sizeof(*out), resp.Xattr) + buf := newBuffer(uintptr(len(resp.Xattr))) + buf = append(buf, resp.Xattr...) + r.respond(buf) } } @@ -1265,8 +1456,8 @@ func (r *RemovexattrRequest) String() string { // Respond replies to the request, indicating that the attribute was removed. func (r *RemovexattrRequest) Respond() { - out := &outHeader{Unique: uint64(r.ID)} - r.respond(out, unsafe.Sizeof(*out)) + buf := newBuffer(0) + r.respond(buf) } // A SetxattrRequest asks to set an extended attribute associated with a file. @@ -1310,8 +1501,8 @@ func (r *SetxattrRequest) String() string { // Respond replies to the request, indicating that the extended attribute was set. func (r *SetxattrRequest) Respond() { - out := &outHeader{Unique: uint64(r.ID)} - r.respond(out, unsafe.Sizeof(*out)) + buf := newBuffer(0) + r.respond(buf) } // A LookupRequest asks to look up the given name in the directory named by r.Node. @@ -1328,17 +1519,17 @@ func (r *LookupRequest) String() string { // Respond replies to the request with the given response. func (r *LookupRequest) Respond(resp *LookupResponse) { - out := &entryOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - Nodeid: uint64(resp.Node), - Generation: resp.Generation, - EntryValid: uint64(resp.EntryValid / time.Second), - EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), - AttrValid: uint64(resp.AttrValid / time.Second), - AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), - Attr: resp.Attr.attr(), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + 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. @@ -1346,7 +1537,6 @@ type LookupResponse struct { Node NodeID Generation uint64 EntryValid time.Duration - AttrValid time.Duration Attr Attr } @@ -1369,12 +1559,11 @@ func (r *OpenRequest) String() string { // Respond replies to the request with the given response. func (r *OpenRequest) Respond(resp *OpenResponse) { - out := &openOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - Fh: uint64(resp.Handle), - OpenFlags: uint32(resp.Flags), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + 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. @@ -1393,31 +1582,34 @@ type CreateRequest struct { Name string Flags OpenFlags Mode os.FileMode + Umask os.FileMode } var _ = Request(&CreateRequest{}) func (r *CreateRequest) String() string { - return fmt.Sprintf("Create [%s] %q fl=%v mode=%v", &r.Header, r.Name, r.Flags, r.Mode) + 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) { - out := &createOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, + eSize := entryOutSize(r.Header.Conn.proto) + buf := newBuffer(eSize + unsafe.Sizeof(openOut{})) - Nodeid: uint64(resp.Node), - Generation: resp.Generation, - EntryValid: uint64(resp.EntryValid / time.Second), - EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), - AttrValid: uint64(resp.AttrValid / time.Second), - AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), - Attr: resp.Attr.attr(), + 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) - Fh: uint64(resp.Handle), - OpenFlags: uint32(resp.Flags), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + 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. @@ -1436,27 +1628,28 @@ type MkdirRequest struct { Header `json:"-"` Name string Mode os.FileMode + Umask os.FileMode } var _ = Request(&MkdirRequest{}) func (r *MkdirRequest) String() string { - return fmt.Sprintf("Mkdir [%s] %q mode=%v", &r.Header, r.Name, r.Mode) + 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) { - out := &entryOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - Nodeid: uint64(resp.Node), - Generation: resp.Generation, - EntryValid: uint64(resp.EntryValid / time.Second), - EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), - AttrValid: uint64(resp.AttrValid / time.Second), - AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), - Attr: resp.Attr.attr(), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + 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. @@ -1470,23 +1663,27 @@ func (r *MkdirResponse) String() 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 + Header `json:"-"` + Dir bool // is this Readdir? + Handle HandleID + Offset int64 + Size int + Flags ReadFlags + LockOwner uint64 + FileFlags OpenFlags } var _ = Request(&ReadRequest{}) func (r *ReadRequest) String() string { - return fmt.Sprintf("Read [%s] %#x %d @%#x dir=%v", &r.Header, r.Handle, r.Size, r.Offset, r.Dir) + return fmt.Sprintf("Read [%s] %#x %d @%#x dir=%v fl=%v lock=%d 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) { - out := &outHeader{Unique: uint64(r.ID)} - r.respondData(out, unsafe.Sizeof(*out), resp.Data) + buf := newBuffer(uintptr(len(resp.Data))) + buf = append(buf, resp.Data...) + r.respond(buf) } // A ReadResponse is the response to a ReadRequest. @@ -1527,8 +1724,8 @@ func (r *ReleaseRequest) String() string { // Respond replies to the request, indicating that the handle has been released. func (r *ReleaseRequest) Respond() { - out := &outHeader{Unique: uint64(r.ID)} - r.respond(out, unsafe.Sizeof(*out)) + buf := newBuffer(0) + r.respond(buf) } // A DestroyRequest is sent by the kernel when unmounting the file system. @@ -1546,8 +1743,8 @@ func (r *DestroyRequest) String() string { // Respond replies to the request. func (r *DestroyRequest) Respond() { - out := &outHeader{Unique: uint64(r.ID)} - r.respond(out, unsafe.Sizeof(*out)) + buf := newBuffer(0) + r.respond(buf) } // A ForgetRequest is sent by the kernel when forgetting about r.Node @@ -1653,16 +1850,18 @@ func AppendDirent(data []byte, dir Dirent) []byte { // A WriteRequest asks to write to an open file. type WriteRequest struct { Header - Handle HandleID - Offset int64 - Data []byte - Flags WriteFlags + Handle HandleID + Offset int64 + Data []byte + Flags WriteFlags + LockOwner uint64 + FileFlags OpenFlags } var _ = Request(&WriteRequest{}) func (r *WriteRequest) String() string { - return fmt.Sprintf("Write [%s] %#x %d @%d fl=%v", &r.Header, r.Handle, len(r.Data), r.Offset, r.Flags) + return fmt.Sprintf("Write [%s] %#x %d @%d fl=%v lock=%d ffl=%v", &r.Header, r.Handle, len(r.Data), r.Offset, r.Flags, r.LockOwner, r.FileFlags) } type jsonWriteRequest struct { @@ -1684,11 +1883,10 @@ func (r *WriteRequest) MarshalJSON() ([]byte, error) { // Respond replies to the request with the given response. func (r *WriteRequest) Respond(resp *WriteResponse) { - out := &writeOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - Size: uint32(resp.Size), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + 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. @@ -1775,19 +1973,18 @@ func (r *SetattrRequest) String() string { // Respond replies to the request with the given response, // giving the updated attributes. func (r *SetattrRequest) Respond(resp *SetattrResponse) { - out := &attrOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - AttrValid: uint64(resp.AttrValid / time.Second), - AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), - Attr: resp.Attr.attr(), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + 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 { - AttrValid time.Duration // how long Attr can be cached - Attr Attr // file attributes + Attr Attr // file attributes } func (r *SetattrResponse) String() string { @@ -1812,8 +2009,8 @@ func (r *FlushRequest) String() string { // Respond replies to the request, indicating that the flush succeeded. func (r *FlushRequest) Respond() { - out := &outHeader{Unique: uint64(r.ID)} - r.respond(out, unsafe.Sizeof(*out)) + buf := newBuffer(0) + r.respond(buf) } // A RemoveRequest asks to remove a file or directory from the @@ -1832,8 +2029,8 @@ func (r *RemoveRequest) String() string { // Respond replies to the request, indicating that the file was removed. func (r *RemoveRequest) Respond() { - out := &outHeader{Unique: uint64(r.ID)} - r.respond(out, unsafe.Sizeof(*out)) + buf := newBuffer(0) + r.respond(buf) } // A SymlinkRequest is a request to create a symlink making NewName point to Target. @@ -1850,17 +2047,17 @@ func (r *SymlinkRequest) String() string { // Respond replies to the request, indicating that the symlink was created. func (r *SymlinkRequest) Respond(resp *SymlinkResponse) { - out := &entryOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - Nodeid: uint64(resp.Node), - Generation: resp.Generation, - EntryValid: uint64(resp.EntryValid / time.Second), - EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), - AttrValid: uint64(resp.AttrValid / time.Second), - AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), - Attr: resp.Attr.attr(), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + 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. @@ -1880,8 +2077,9 @@ func (r *ReadlinkRequest) String() string { } func (r *ReadlinkRequest) Respond(target string) { - out := &outHeader{Unique: uint64(r.ID)} - r.respondData(out, unsafe.Sizeof(*out), []byte(target)) + buf := newBuffer(uintptr(len(target))) + buf = append(buf, target...) + r.respond(buf) } // A LinkRequest is a request to create a hard link. @@ -1898,17 +2096,17 @@ func (r *LinkRequest) String() string { } func (r *LinkRequest) Respond(resp *LookupResponse) { - out := &entryOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - Nodeid: uint64(resp.Node), - Generation: resp.Generation, - EntryValid: uint64(resp.EntryValid / time.Second), - EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), - AttrValid: uint64(resp.AttrValid / time.Second), - AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), - Attr: resp.Attr.attr(), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + 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. @@ -1925,8 +2123,8 @@ func (r *RenameRequest) String() string { } func (r *RenameRequest) Respond() { - out := &outHeader{Unique: uint64(r.ID)} - r.respond(out, unsafe.Sizeof(*out)) + buf := newBuffer(0) + r.respond(buf) } type MknodRequest struct { @@ -1934,26 +2132,27 @@ type MknodRequest struct { Name string Mode os.FileMode Rdev uint32 + Umask os.FileMode } var _ = Request(&MknodRequest{}) func (r *MknodRequest) String() string { - return fmt.Sprintf("Mknod [%s] Name %q mode %v rdev %d", &r.Header, r.Name, r.Mode, r.Rdev) + 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) { - out := &entryOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - Nodeid: uint64(resp.Node), - Generation: resp.Generation, - EntryValid: uint64(resp.EntryValid / time.Second), - EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), - AttrValid: uint64(resp.AttrValid / time.Second), - AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), - Attr: resp.Attr.attr(), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + 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 { @@ -1971,8 +2170,8 @@ func (r *FsyncRequest) String() string { } func (r *FsyncRequest) Respond() { - out := &outHeader{Unique: uint64(r.ID)} - r.respond(out, unsafe.Sizeof(*out)) + buf := newBuffer(0) + r.respond(buf) } // An InterruptRequest is a request to interrupt another pending request. The @@ -1992,38 +2191,3 @@ func (r *InterruptRequest) Respond() { func (r *InterruptRequest) String() string { return fmt.Sprintf("Interrupt [%s] ID %v", &r.Header, r.IntrID) } - -/*{ - -// A XXXRequest xxx. -type XXXRequest struct { - Header `json:"-"` - xxx -} - -var _ = Request(&XXXRequest{}) - -func (r *XXXRequest) String() string { - return fmt.Sprintf("XXX [%s] xxx", &r.Header) -} - -// Respond replies to the request with the given response. -func (r *XXXRequest) Respond(resp *XXXResponse) { - out := &xxxOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - xxx, - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) -} - -// A XXXResponse is the response to a XXXRequest. -type XXXResponse struct { - xxx -} - -func (r *XXXResponse) String() string { - return fmt.Sprintf("XXX %+v", *r) -} - - } -*/ diff --git a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel.go b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel.go index b04c89a9e..c08798dfd 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel.go @@ -41,13 +41,16 @@ import ( "unsafe" ) -// Version is the FUSE version implemented by the package. -const Version = "7.8" +// The FUSE version implemented by the package. +const ( + protoVersionMinMajor = 7 + protoVersionMinMinor = 8 + protoVersionMaxMajor = 7 + protoVersionMaxMinor = 12 +) const ( - kernelVersion = 7 - kernelMinorVersion = 8 - rootID = 1 + rootID = 1 ) type kstatfs struct { @@ -70,6 +73,22 @@ type fileLock struct { Pid 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 @@ -207,7 +226,7 @@ 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 // (Linux?) + OpenNonSeekable OpenResponseFlags = 1 << 2 // mark the file as non-seekable (not supported on OS X) OpenPurgeAttr OpenResponseFlags = 1 << 30 // OS X OpenPurgeUBC OpenResponseFlags = 1 << 31 // OS X @@ -220,6 +239,7 @@ func (fl OpenResponseFlags) String() string { var openResponseFlagNames = []flagName{ {uint32(OpenDirectIO), "OpenDirectIO"}, {uint32(OpenKeepCache), "OpenKeepCache"}, + {uint32(OpenNonSeekable), "OpenNonSeekable"}, {uint32(OpenPurgeAttr), "OpenPurgeAttr"}, {uint32(OpenPurgeUBC), "OpenPurgeUBC"}, } @@ -368,7 +388,6 @@ const ( ) type entryOut struct { - outHeader Nodeid uint64 // Inode ID Generation uint64 // Inode generation EntryValid uint64 // Cache timeout for the name @@ -378,21 +397,43 @@ type entryOut struct { 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 getattrIn struct { + GetattrFlags uint32 + dummy uint32 + Fh uint64 +} + type attrOut struct { - outHeader AttrValid uint64 // Cache timeout for the attributes AttrValidNsec uint32 Dummy 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 { - outHeader Bkuptime uint64 Crtime uint64 BkuptimeNsec uint32 @@ -400,17 +441,37 @@ type getxtimesOut struct { } type mknodIn struct { - Mode uint32 - Rdev uint32 + Mode uint32 + Rdev uint32 + Umask uint32 + padding 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 - Padding uint32 + 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 @@ -452,31 +513,25 @@ type openIn struct { } type openOut struct { - outHeader Fh uint64 OpenFlags uint32 Padding uint32 } type createIn struct { - Flags uint32 - Mode uint32 + Flags uint32 + Mode uint32 + Umask uint32 + padding uint32 } -type createOut struct { - outHeader - - 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 - - Fh uint64 - OpenFlags uint32 - Padding 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 { @@ -494,10 +549,38 @@ type flushIn struct { } type readIn struct { - Fh uint64 - Offset uint64 - Size uint32 - Padding uint32 + Fh uint64 + Offset uint64 + Size uint32 + ReadFlags uint32 + LockOwner uint64 + Flags uint32 + padding 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 { @@ -505,10 +588,21 @@ type writeIn struct { Offset uint64 Size uint32 WriteFlags uint32 + LockOwner uint64 + Flags uint32 + padding 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 { - outHeader Size uint32 Padding uint32 } @@ -516,16 +610,24 @@ type writeOut struct { // 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) } -var writeFlagNames = []flagName{} - const compatStatfsSize = 48 type statfsOut struct { - outHeader St kstatfs } @@ -554,19 +656,28 @@ func (getxattrInCommon) position() uint32 { } type getxattrOut struct { - outHeader Size uint32 Padding uint32 } type lkIn struct { - Fh uint64 - Owner uint64 - Lk fileLock + Fh uint64 + Owner uint64 + Lk fileLock + LkFlags uint32 + padding uint32 +} + +func lkInSize(p Protocol) uintptr { + switch { + case p.LT(Protocol{7, 9}): + return unsafe.Offsetof(lkIn{}.LkFlags) + default: + return unsafe.Sizeof(lkIn{}) + } } type lkOut struct { - outHeader Lk fileLock } @@ -585,7 +696,6 @@ type initIn struct { const initInSize = int(unsafe.Sizeof(initIn{})) type initOut struct { - outHeader Major uint32 Minor uint32 MaxReadahead uint32 @@ -605,7 +715,6 @@ type bmapIn struct { } type bmapOut struct { - outHeader Block uint64 } @@ -637,3 +746,21 @@ type dirent struct { } const direntSize = 8 + 8 + 4 + 4 + +const ( + notifyCodePoll int32 = 1 + notifyCodeInvalInode int32 = 2 + notifyCodeInvalEntry int32 = 3 +) + +type notifyInvalInodeOut struct { + Ino uint64 + Off int64 + Len int64 +} + +type notifyInvalEntryOut struct { + Parent uint64 + Namelen uint32 + padding uint32 +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_darwin.go b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_darwin.go index 4f9347d03..b9873fdf3 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_darwin.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_darwin.go @@ -22,6 +22,8 @@ type attr struct { Gid uint32 Rdev uint32 Flags_ uint32 // OS X only; see chflags(2) + Blksize uint32 + padding uint32 } func (a *attr) SetCrtime(s uint64, ns uint32) { diff --git a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_freebsd.go b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_freebsd.go index 7636878c3..b1141e41d 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_freebsd.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_freebsd.go @@ -17,6 +17,8 @@ type attr struct { Uid uint32 Gid uint32 Rdev uint32 + Blksize uint32 + padding uint32 } func (a *attr) Crtime() time.Time { diff --git a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_linux.go b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_linux.go index 6a752457a..d3ba86617 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_linux.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_linux.go @@ -17,8 +17,8 @@ type attr struct { Uid uint32 Gid uint32 Rdev uint32 - // Blksize uint32 // Only in protocol 7.9 - // padding_ uint32 // Only in protocol 7.9 + Blksize uint32 + padding uint32 } func (a *attr) Crtime() time.Time { diff --git a/Godeps/_workspace/src/bazil.org/fuse/mount_darwin.go b/Godeps/_workspace/src/bazil.org/fuse/mount_darwin.go index 742b084c2..86e977ea2 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/mount_darwin.go +++ b/Godeps/_workspace/src/bazil.org/fuse/mount_darwin.go @@ -52,7 +52,7 @@ func openOSXFUSEDev() (*os.File, error) { } } -func callMount(dir string, conf *MountConfig, f *os.File, ready chan<- struct{}, errp *error) error { +func callMount(dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, errp *error) error { bin := "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs" for k, v := range conf.options { @@ -104,7 +104,7 @@ func callMount(dir string, conf *MountConfig, f *os.File, ready chan<- struct{}, return err } -func mount(dir string, conf *MountConfig, ready chan<- struct{}, errp *error) (*os.File, error) { +func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) { f, err := openOSXFUSEDev() if err == errNotLoaded { err = loadOSXFUSE() diff --git a/Godeps/_workspace/src/bazil.org/fuse/mount_freebsd.go b/Godeps/_workspace/src/bazil.org/fuse/mount_freebsd.go index 951dcf10c..2914ce9e1 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/mount_freebsd.go +++ b/Godeps/_workspace/src/bazil.org/fuse/mount_freebsd.go @@ -7,7 +7,7 @@ import ( "strings" ) -func mount(dir string, conf *MountConfig, ready chan<- struct{}, errp *error) (*os.File, error) { +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 diff --git a/Godeps/_workspace/src/bazil.org/fuse/mount_linux.go b/Godeps/_workspace/src/bazil.org/fuse/mount_linux.go index 0748c0a5d..de7a06003 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/mount_linux.go +++ b/Godeps/_workspace/src/bazil.org/fuse/mount_linux.go @@ -1,14 +1,38 @@ package fuse import ( + "bufio" "fmt" + "io" + "log" "net" "os" "os/exec" + "sync" "syscall" ) -func mount(dir string, conf *MountConfig, ready chan<- struct{}, errp *error) (fusefd *os.File, err error) { +func lineLogger(wg *sync.WaitGroup, prefix string, r io.ReadCloser) { + defer wg.Done() + + scanner := bufio.NewScanner(r) + for scanner.Scan() { + switch line := scanner.Text(); line { + case `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. + continue + default: + log.Printf("%s: %s", prefix, line) + } + } + if err := scanner.Err(); err != nil { + log.Printf("%s, error reading: %v", prefix, err) + } +} + +func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (fusefd *os.File, err error) { // linux mount is never delayed close(ready) @@ -16,8 +40,12 @@ func mount(dir string, conf *MountConfig, ready chan<- struct{}, errp *error) (f if err != nil { return nil, fmt.Errorf("socketpair error: %v", err) } - defer syscall.Close(fds[0]) - defer syscall.Close(fds[1]) + + 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", @@ -27,17 +55,29 @@ func mount(dir string, conf *MountConfig, ready chan<- struct{}, errp *error) (f ) cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3") - writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes") - defer writeFile.Close() cmd.ExtraFiles = []*os.File{writeFile} - out, err := cmd.CombinedOutput() - if len(out) > 0 || err != nil { - return nil, fmt.Errorf("fusermount: %q, %v", out, err) + 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) + } + wg.Add(2) + go lineLogger(&wg, "mount helper output", stdout) + go lineLogger(&wg, "mount helper error", stderr) + wg.Wait() + if err := cmd.Wait(); err != nil { + return nil, fmt.Errorf("fusermount: %v", err) } - readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads") - defer readFile.Close() c, err := net.FileConn(readFile) if err != nil { return nil, fmt.Errorf("FileConn from fusermount socket: %v", err) diff --git a/Godeps/_workspace/src/bazil.org/fuse/options.go b/Godeps/_workspace/src/bazil.org/fuse/options.go index 5a0b7f180..98c418f1d 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/options.go +++ b/Godeps/_workspace/src/bazil.org/fuse/options.go @@ -5,14 +5,16 @@ import ( "strings" ) -func dummyOption(conf *MountConfig) error { +func dummyOption(conf *mountConfig) error { return nil } -// MountConfig holds the configuration for a mount operation. +// mountConfig holds the configuration for a mount operation. // Use it by passing MountOption values to Mount. -type MountConfig struct { - options map[string]string +type mountConfig struct { + options map[string]string + maxReadahead uint32 + initFlags InitFlags } func escapeComma(s string) string { @@ -24,7 +26,7 @@ func escapeComma(s string) string { // 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 { +func (m *mountConfig) getOptions() string { var opts []string for k, v := range m.options { k = escapeComma(k) @@ -36,15 +38,17 @@ func (m *MountConfig) getOptions() string { return strings.Join(opts, ",") } +type mountOption func(*mountConfig) error + // MountOption is passed to Mount to change the behavior of the mount. -type MountOption func(*MountConfig) error +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 { + return func(conf *mountConfig) error { conf.options["fsname"] = name return nil } @@ -57,7 +61,7 @@ func FSName(name string) MountOption { // OS X ignores this option. // FreeBSD ignores this option. func Subtype(fstype string) MountOption { - return func(conf *MountConfig) error { + return func(conf *mountConfig) error { conf.options["subtype"] = fstype return nil } @@ -84,7 +88,7 @@ var ErrCannotCombineAllowOtherAndAllowRoot = errors.New("cannot combine AllowOth // // Only one of AllowOther or AllowRoot can be used. func AllowOther() MountOption { - return func(conf *MountConfig) error { + return func(conf *mountConfig) error { if _, ok := conf.options["allow_root"]; ok { return ErrCannotCombineAllowOtherAndAllowRoot } @@ -99,7 +103,7 @@ func AllowOther() MountOption { // // FreeBSD ignores this option. func AllowRoot() MountOption { - return func(conf *MountConfig) error { + return func(conf *mountConfig) error { if _, ok := conf.options["allow_other"]; ok { return ErrCannotCombineAllowOtherAndAllowRoot } @@ -117,7 +121,7 @@ func AllowRoot() MountOption { // // FreeBSD ignores this option. func DefaultPermissions() MountOption { - return func(conf *MountConfig) error { + return func(conf *mountConfig) error { conf.options["default_permissions"] = "" return nil } @@ -125,8 +129,42 @@ func DefaultPermissions() MountOption { // ReadOnly makes the mount read-only. func ReadOnly() MountOption { - return func(conf *MountConfig) error { + 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 + } +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/options_darwin.go b/Godeps/_workspace/src/bazil.org/fuse/options_darwin.go index 15aedbcfc..f71fa97eb 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/options_darwin.go +++ b/Godeps/_workspace/src/bazil.org/fuse/options_darwin.go @@ -1,12 +1,12 @@ package fuse -func localVolume(conf *MountConfig) error { +func localVolume(conf *mountConfig) error { conf.options["local"] = "" return nil } func volumeName(name string) MountOption { - return func(conf *MountConfig) error { + return func(conf *mountConfig) error { conf.options["volname"] = name return nil } diff --git a/Godeps/_workspace/src/bazil.org/fuse/options_freebsd.go b/Godeps/_workspace/src/bazil.org/fuse/options_freebsd.go index 8eb07e009..5a0b84806 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/options_freebsd.go +++ b/Godeps/_workspace/src/bazil.org/fuse/options_freebsd.go @@ -1,6 +1,6 @@ package fuse -func localVolume(conf *MountConfig) error { +func localVolume(conf *mountConfig) error { return nil } diff --git a/Godeps/_workspace/src/bazil.org/fuse/options_helper_test.go b/Godeps/_workspace/src/bazil.org/fuse/options_helper_test.go index f9c90e8bf..76b13e2ac 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/options_helper_test.go +++ b/Godeps/_workspace/src/bazil.org/fuse/options_helper_test.go @@ -1,6 +1,10 @@ package fuse // for TestMountOptionCommaError -func ForTestSetMountOption(conf *MountConfig, k, v string) { - conf.options[k] = v +func ForTestSetMountOption(k, v string) MountOption { + fn := func(conf *mountConfig) error { + conf.options[k] = v + return nil + } + return fn } diff --git a/Godeps/_workspace/src/bazil.org/fuse/options_linux.go b/Godeps/_workspace/src/bazil.org/fuse/options_linux.go index 8eb07e009..5a0b84806 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/options_linux.go +++ b/Godeps/_workspace/src/bazil.org/fuse/options_linux.go @@ -1,6 +1,6 @@ package fuse -func localVolume(conf *MountConfig) error { +func localVolume(conf *mountConfig) error { return nil } diff --git a/Godeps/_workspace/src/bazil.org/fuse/options_nocomma_test.go b/Godeps/_workspace/src/bazil.org/fuse/options_nocomma_test.go index 53f3c6fa3..968628abf 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/options_nocomma_test.go +++ b/Godeps/_workspace/src/bazil.org/fuse/options_nocomma_test.go @@ -18,11 +18,8 @@ func TestMountOptionCommaError(t *testing.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{}}, - func(conf *fuse.MountConfig) error { - fuse.ForTestSetMountOption(conf, "fusetest", evil) - return nil - }, + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, + fuse.ForTestSetMountOption("fusetest", evil), ) if err == nil { mnt.Close() diff --git a/Godeps/_workspace/src/bazil.org/fuse/options_test.go b/Godeps/_workspace/src/bazil.org/fuse/options_test.go index 8d337aaf6..2578e1f47 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/options_test.go +++ b/Godeps/_workspace/src/bazil.org/fuse/options_test.go @@ -22,7 +22,7 @@ func TestMountOptionFSName(t *testing.T) { } t.Parallel() const name = "FuseTestMarker" - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, fuse.FSName(name), ) if err != nil { @@ -45,7 +45,7 @@ func testMountOptionFSNameEvil(t *testing.T, evil string) { } t.Parallel() var name = "FuseTest" + evil + "Marker" - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, fuse.FSName(name), ) if err != nil { @@ -102,7 +102,7 @@ func TestMountOptionSubtype(t *testing.T) { } t.Parallel() const name = "FuseTestMarker" - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, fuse.Subtype(name), ) if err != nil { @@ -125,7 +125,7 @@ func TestMountOptionSubtype(t *testing.T) { func TestMountOptionAllowOtherThenAllowRoot(t *testing.T) { t.Parallel() - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, fuse.AllowOther(), fuse.AllowRoot(), ) @@ -141,7 +141,7 @@ func TestMountOptionAllowOtherThenAllowRoot(t *testing.T) { func TestMountOptionAllowRootThenAllowOther(t *testing.T) { t.Parallel() - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, fuse.AllowRoot(), fuse.AllowOther(), ) @@ -155,8 +155,9 @@ func TestMountOptionAllowRootThenAllowOther(t *testing.T) { type unwritableFile struct{} -func (f unwritableFile) Attr(a *fuse.Attr) { +func (f unwritableFile) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0000 + return nil } func TestMountOptionDefaultPermissions(t *testing.T) { @@ -166,8 +167,9 @@ func TestMountOptionDefaultPermissions(t *testing.T) { t.Parallel() mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{ - fstestutil.ChildMap{"child": unwritableFile{}}, + &fstestutil.ChildMap{"child": unwritableFile{}}, }, + nil, fuse.DefaultPermissions(), ) @@ -203,6 +205,7 @@ func TestMountOptionReadOnly(t *testing.T) { t.Parallel() mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{createrDir{}}, + nil, fuse.ReadOnly(), ) diff --git a/Godeps/_workspace/src/bazil.org/fuse/protocol.go b/Godeps/_workspace/src/bazil.org/fuse/protocol.go new file mode 100644 index 000000000..a77bbf72f --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/protocol.go @@ -0,0 +1,75 @@ +package 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) +} + +func (a Protocol) is79() bool { + return a.GE(Protocol{7, 9}) +} + +// HasAttrBlockSize returns whether Attr.BlockSize is respected by the +// kernel. +func (a Protocol) HasAttrBlockSize() bool { + return a.is79() +} + +// HasReadWriteFlags returns whether ReadRequest/WriteRequest +// fields Flags and FileFlags are valid. +func (a Protocol) HasReadWriteFlags() bool { + return a.is79() +} + +// HasGetattrFlags returns whether GetattrRequest field Flags is +// valid. +func (a Protocol) HasGetattrFlags() bool { + return a.is79() +} + +func (a Protocol) is710() bool { + return a.GE(Protocol{7, 10}) +} + +// HasOpenNonSeekable returns whether OpenResponse field Flags flag +// OpenNonSeekable is supported. +func (a Protocol) HasOpenNonSeekable() bool { + return a.is710() +} + +func (a Protocol) is712() bool { + return a.GE(Protocol{7, 12}) +} + +// HasUmask returns whether CreateRequest/MkdirRequest/MknodRequest +// field Umask is valid. +func (a Protocol) HasUmask() bool { + return a.is712() +} + +// HasInvalidate returns whether InvalidateNode/InvalidateEntry are +// supported. +func (a Protocol) HasInvalidate() bool { + return a.is712() +} diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 67e9f4d73..d5fd79d68 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -91,9 +91,10 @@ type snapshots struct { repo *repository.Repository } -func (sn *snapshots) Attr(a *fuse.Attr) { +func (sn *snapshots) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 0 a.Mode = os.ModeDir | 0555 + return nil } func (sn *snapshots) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { @@ -144,9 +145,10 @@ type dir struct { inode uint64 } -func (d *dir) Attr(a *fuse.Attr) { +func (d *dir) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = d.inode a.Mode = os.ModeDir | 0555 + return nil } func (d *dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { @@ -224,10 +226,11 @@ func makeFile(repo *repository.Repository, node *restic.Node) (*file, error) { }, nil } -func (f *file) Attr(a *fuse.Attr) { +func (f *file) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = f.node.Inode a.Mode = f.node.Mode a.Size = f.node.Size + return nil } func (f *file) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { From a6ce7d984558d8e1b8fc131b264baec6fc1b48ce Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sat, 18 Jul 2015 21:31:11 +0200 Subject: [PATCH 03/24] Use more restic-y idioms --- cmd/restic/cmd_mount.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index d5fd79d68..6505ce967 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -54,6 +54,7 @@ func (cmd CmdMount) Execute(args []string) error { mountpoint := args[0] if _, err := os.Stat(mountpoint); err != nil { if os.IsNotExist(err) { + cmd.global.Verbosef("Mountpoint [%s] doesn't exist, creating it\n", mountpoint) err = os.Mkdir(mountpoint, os.ModeDir|0755) if err != nil { return err @@ -72,8 +73,8 @@ func (cmd CmdMount) Execute(args []string) error { root := fs.Tree{} root.Add("snapshots", &snapshots{repo}) - fmt.Printf("Now serving %s under %s\n", repo.Backend().Location(), mountpoint) - fmt.Println("Don't forget to umount after quitting !") + cmd.global.Printf("Now serving %s under %s\n", repo.Backend().Location(), mountpoint) + cmd.global.Printf("Don't forget to umount after quitting !\n") err = fs.Serve(c, &root) if err != nil { @@ -99,7 +100,7 @@ func (sn *snapshots) Attr(ctx context.Context, a *fuse.Attr) error { func (sn *snapshots) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { ret := make([]fuse.Dirent, 0) - for id := range sn.repo.List(backend.Snapshot, make(chan struct{})) { + for id := range sn.repo.List(backend.Snapshot, ctx.Done()) { snapshot, err := restic.LoadSnapshot(sn.repo, id) if err != nil { return nil, err @@ -117,7 +118,7 @@ func (sn *snapshots) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { func (sn *snapshots) Lookup(ctx context.Context, name string) (fs.Node, error) { // This is kind of lame: we reload each snapshot and check the name // (which is the timestamp) - for id := range sn.repo.List(backend.Snapshot, make(chan struct{})) { + for id := range sn.repo.List(backend.Snapshot, ctx.Done()) { snapshot, err := restic.LoadSnapshot(sn.repo, id) if err != nil { return nil, err From 414ade5b3f047aea8f3e49cd64b12848610d5d35 Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sat, 18 Jul 2015 21:31:34 +0200 Subject: [PATCH 04/24] Signal readiness of mount to potential callers --- cmd/restic/cmd_mount.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 6505ce967..28a4dd18d 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -20,13 +20,17 @@ import ( type CmdMount struct { global *GlobalOptions + ready chan struct{} } func init() { _, err := parser.AddCommand("mount", "mount a repository", "The mount command mounts a repository read-only to a given directory", - &CmdMount{global: &globalOpts}) + &CmdMount{ + global: &globalOpts, + ready: make(chan struct{}, 1), + }) if err != nil { panic(err) } @@ -76,6 +80,8 @@ func (cmd CmdMount) Execute(args []string) error { cmd.global.Printf("Now serving %s under %s\n", repo.Backend().Location(), mountpoint) cmd.global.Printf("Don't forget to umount after quitting !\n") + cmd.ready <- struct{}{} + err = fs.Serve(c, &root) if err != nil { return err From 25b090a074cc09d7827fbb0a09b91e9714a8e87a Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sat, 18 Jul 2015 21:31:51 +0200 Subject: [PATCH 05/24] Add integration tests for mount command --- cmd/restic/integration_test.go | 87 ++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/cmd/restic/integration_test.go b/cmd/restic/integration_test.go index b06b85745..b6e814009 100644 --- a/cmd/restic/integration_test.go +++ b/cmd/restic/integration_test.go @@ -14,8 +14,10 @@ import ( "testing" "time" + "github.com/restic/restic" "github.com/restic/restic/backend" "github.com/restic/restic/debug" + "github.com/restic/restic/repository" . "github.com/restic/restic/test" ) @@ -73,6 +75,11 @@ func cmdCheck(t testing.TB, global GlobalOptions) { OK(t, cmd.Execute(nil)) } +func cmdMount(t testing.TB, global GlobalOptions, dir string, ready chan struct{}) { + cmd := &CmdMount{global: &global, ready: ready} + OK(t, cmd.Execute([]string{dir})) +} + func TestBackup(t *testing.T) { withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) { datafile := filepath.Join("testdata", "backup-data.tar.gz") @@ -483,3 +490,83 @@ func TestRestoreNoMetadataOnIgnoredIntermediateDirs(t *testing.T) { "meta data of intermediate directory hasn't been restore") }) } + +func TestMount(t *testing.T) { + + checkSnapshots := func(repo *repository.Repository, mountpoint string, snapshotIDs []backend.ID) { + stSnapshots, err := os.Open(filepath.Join(mountpoint, "snapshots")) + OK(t, err) + namesInSnapshots, err := stSnapshots.Readdirnames(-1) + OK(t, err) + Assert(t, + len(namesInSnapshots) == len(snapshotIDs), + "Invalid number of snapshots: expected %d, got %d", len(snapshotIDs), len(namesInSnapshots)) + + for i, id := range snapshotIDs { + snapshot, err := restic.LoadSnapshot(repo, id) + OK(t, err) + Assert(t, + namesInSnapshots[i] == snapshot.Time.Format(time.RFC3339), + "Invalid snapshot folder name: expected %s, got %s", snapshot.Time.Format(time.RFC3339), namesInSnapshots[i]) + } + OK(t, stSnapshots.Close()) + } + + withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) { + cmdInit(t, global) + repo, err := global.OpenRepository() + OK(t, err) + + mountpoint, err := ioutil.TempDir(TestTempDir, "restic-test-mount-") + OK(t, err) + defer OK(t, os.RemoveAll(mountpoint)) + + ready := make(chan struct{}, 1) + go cmdMount(t, global, mountpoint, ready) + <-ready + + stMountPoint, err := os.Open(mountpoint) + OK(t, err) + names, err := stMountPoint.Readdirnames(-1) + OK(t, err) + Assert(t, len(names) == 1 && names[0] == "snapshots", "expected the snapshots directory to exist") + OK(t, stMountPoint.Close()) + + checkSnapshots(repo, mountpoint, []backend.ID{}) + + datafile := filepath.Join("testdata", "backup-data.tar.gz") + fd, err := os.Open(datafile) + if os.IsNotExist(err) { + t.Skipf("unable to find data file %q, skipping", datafile) + return + } + OK(t, err) + OK(t, fd.Close()) + + SetupTarTestFixture(t, env.testdata, datafile) + + // first backup + cmdBackup(t, global, []string{env.testdata}, nil) + snapshotIDs := cmdList(t, global, "snapshots") + Assert(t, len(snapshotIDs) == 1, + "expected one snapshot, got %v", snapshotIDs) + + checkSnapshots(repo, mountpoint, snapshotIDs) + + // second backup, implicit incremental + cmdBackup(t, global, []string{env.testdata}, nil) + snapshotIDs = cmdList(t, global, "snapshots") + Assert(t, len(snapshotIDs) == 2, + "expected two snapshots, got %v", snapshotIDs) + + checkSnapshots(repo, mountpoint, snapshotIDs) + + // third backup, explicit incremental + cmdBackup(t, global, []string{env.testdata}, snapshotIDs[0]) + snapshotIDs = cmdList(t, global, "snapshots") + Assert(t, len(snapshotIDs) == 3, + "expected three snapshots, got %v", snapshotIDs) + + checkSnapshots(repo, mountpoint, snapshotIDs) + }) +} From c9b3eebc09d136c693fea267ca3490e74c13ae79 Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sat, 18 Jul 2015 21:48:53 +0200 Subject: [PATCH 06/24] Check that mountpoints are created --- cmd/restic/cmd_mount.go | 2 +- cmd/restic/integration_test.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 28a4dd18d..269386523 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -58,7 +58,7 @@ func (cmd CmdMount) Execute(args []string) error { mountpoint := args[0] if _, err := os.Stat(mountpoint); err != nil { if os.IsNotExist(err) { - cmd.global.Verbosef("Mountpoint [%s] doesn't exist, creating it\n", mountpoint) + cmd.global.Verbosef("Mountpoint %s doesn't exist, creating it\n", mountpoint) err = os.Mkdir(mountpoint, os.ModeDir|0755) if err != nil { return err diff --git a/cmd/restic/integration_test.go b/cmd/restic/integration_test.go index b6e814009..9a08cd932 100644 --- a/cmd/restic/integration_test.go +++ b/cmd/restic/integration_test.go @@ -519,7 +519,9 @@ func TestMount(t *testing.T) { mountpoint, err := ioutil.TempDir(TestTempDir, "restic-test-mount-") OK(t, err) - defer OK(t, os.RemoveAll(mountpoint)) + + // We remove the mountpoint now to check that cmdMount creates it + OK(t, os.RemoveAll(mountpoint)) ready := make(chan struct{}, 1) go cmdMount(t, global, mountpoint, ready) From a8cd74ba7ec112136f0680d585e7bb8a50c29fa0 Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sat, 18 Jul 2015 21:49:08 +0200 Subject: [PATCH 07/24] Cache known snapshots instead of re-traversing the repository every time --- cmd/restic/cmd_mount.go | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 269386523..1f3b54ad6 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -75,7 +75,10 @@ func (cmd CmdMount) Execute(args []string) error { } root := fs.Tree{} - root.Add("snapshots", &snapshots{repo}) + root.Add("snapshots", &snapshots{ + repo: repo, + knownSnapshots: make(map[string]*restic.Snapshot), + }) cmd.global.Printf("Now serving %s under %s\n", repo.Backend().Location(), mountpoint) cmd.global.Printf("Don't forget to umount after quitting !\n") @@ -96,6 +99,9 @@ var _ = fs.NodeStringLookuper(&snapshots{}) type snapshots struct { repo *repository.Repository + + // snapshot timestamp -> snapshot + knownSnapshots map[string]*restic.Snapshot } func (sn *snapshots) Attr(ctx context.Context, a *fuse.Attr) error { @@ -111,6 +117,7 @@ func (sn *snapshots) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { if err != nil { return nil, err } + sn.knownSnapshots[snapshot.Time.Format(time.RFC3339)] = snapshot ret = append(ret, fuse.Dirent{ Inode: binary.BigEndian.Uint64(id[:8]), Type: fuse.DT_Dir, @@ -122,24 +129,29 @@ func (sn *snapshots) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { } func (sn *snapshots) Lookup(ctx context.Context, name string) (fs.Node, error) { - // This is kind of lame: we reload each snapshot and check the name - // (which is the timestamp) - for id := range sn.repo.List(backend.Snapshot, ctx.Done()) { - snapshot, err := restic.LoadSnapshot(sn.repo, id) - if err != nil { - return nil, err - } - if snapshot.Time.Format(time.RFC3339) == name { - tree, err := restic.LoadTree(sn.repo, snapshot.Tree) + if _, ok := sn.knownSnapshots[name]; !ok { + // At least this snapshot is not cached. We use this opportunity to + // load all missing snapshots + for id := range sn.repo.List(backend.Snapshot, ctx.Done()) { + snapshot, err := restic.LoadSnapshot(sn.repo, id) if err != nil { return nil, err } - return &dir{ - repo: sn.repo, - tree: tree, - }, nil + snapshotName := snapshot.Time.Format(time.RFC3339) + sn.knownSnapshots[snapshotName] = snapshot + break } } + + snapshot := sn.knownSnapshots[name] + tree, err := restic.LoadTree(sn.repo, snapshot.Tree) + if err != nil { + return nil, err + } + return &dir{ + repo: sn.repo, + tree: tree, + }, nil return nil, fuse.ENOENT } From 1f79a19293749025c133b5ca6365cbfe9d6363e3 Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sat, 18 Jul 2015 21:52:10 +0200 Subject: [PATCH 08/24] Comment the go trick about static verification of a struct implementing an interface --- cmd/restic/cmd_mount.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 1f3b54ad6..96241be2d 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -94,6 +94,9 @@ func (cmd CmdMount) Execute(args []string) error { return c.MountError } +// These lines statically ensure that a *snapshots implement the given +// interfaces; a misplaced refactoring of the implementation that breaks +// the interface will be catched by the compiler var _ = fs.HandleReadDirAller(&snapshots{}) var _ = fs.NodeStringLookuper(&snapshots{}) @@ -155,6 +158,7 @@ func (sn *snapshots) Lookup(ctx context.Context, name string) (fs.Node, error) { return nil, fuse.ENOENT } +// Statically ensure that *dir implement those interface var _ = fs.HandleReadDirAller(&dir{}) var _ = fs.NodeStringLookuper(&dir{}) @@ -215,6 +219,7 @@ func (d *dir) Lookup(ctx context.Context, name string) (fs.Node, error) { return nil, fuse.ENOENT } +// Statically ensure that *file implements the given interface var _ = fs.HandleReader(&file{}) type file struct { From 9ff98d34ef82cc3a823fbf92a5fdfd4e6bd44e22 Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sat, 18 Jul 2015 23:19:50 +0200 Subject: [PATCH 09/24] Add build and test instructions * Don't build on openbsd * Don't test fuse on travis --- .travis.yml | 2 +- cmd/restic/cmd_mount.go | 2 + cmd/restic/integration_fuse_test.go | 101 ++++++++++++++++++++++++++++ cmd/restic/integration_test.go | 84 ----------------------- test/backend.go | 1 + 5 files changed, 105 insertions(+), 85 deletions(-) create mode 100644 cmd/restic/integration_fuse_test.go diff --git a/.travis.yml b/.travis.yml index c675033a9..642f0ec5d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ os: - linux - osx -env: GOX_OS="linux darwin openbsd freebsd" GOX_ARCH="386 amd64 arm" +env: GOX_OS="linux darwin openbsd freebsd" GOX_ARCH="386 amd64 arm" RESTIC_TEST_FUSE="0" notifications: irc: diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 96241be2d..62f8a5efa 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -1,3 +1,5 @@ +// +build !openbsd + package main import ( diff --git a/cmd/restic/integration_fuse_test.go b/cmd/restic/integration_fuse_test.go new file mode 100644 index 000000000..f12b714e7 --- /dev/null +++ b/cmd/restic/integration_fuse_test.go @@ -0,0 +1,101 @@ +// +build !openbsd + +package main + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + "time" + + "github.com/restic/restic" + "github.com/restic/restic/backend" + "github.com/restic/restic/repository" + . "github.com/restic/restic/test" +) + +func TestMount(t *testing.T) { + if !RunFuseTest { + t.Skip("Skipping fuse tests") + } + + checkSnapshots := func(repo *repository.Repository, mountpoint string, snapshotIDs []backend.ID) { + stSnapshots, err := os.Open(filepath.Join(mountpoint, "snapshots")) + OK(t, err) + namesInSnapshots, err := stSnapshots.Readdirnames(-1) + OK(t, err) + Assert(t, + len(namesInSnapshots) == len(snapshotIDs), + "Invalid number of snapshots: expected %d, got %d", len(snapshotIDs), len(namesInSnapshots)) + + for i, id := range snapshotIDs { + snapshot, err := restic.LoadSnapshot(repo, id) + OK(t, err) + Assert(t, + namesInSnapshots[i] == snapshot.Time.Format(time.RFC3339), + "Invalid snapshot folder name: expected %s, got %s", snapshot.Time.Format(time.RFC3339), namesInSnapshots[i]) + } + OK(t, stSnapshots.Close()) + } + + withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) { + cmdInit(t, global) + repo, err := global.OpenRepository() + OK(t, err) + + mountpoint, err := ioutil.TempDir(TestTempDir, "restic-test-mount-") + OK(t, err) + + // We remove the mountpoint now to check that cmdMount creates it + OK(t, os.RemoveAll(mountpoint)) + + ready := make(chan struct{}, 1) + go cmdMount(t, global, mountpoint, ready) + <-ready + + stMountPoint, err := os.Open(mountpoint) + OK(t, err) + names, err := stMountPoint.Readdirnames(-1) + OK(t, err) + Assert(t, len(names) == 1 && names[0] == "snapshots", "expected the snapshots directory to exist") + OK(t, stMountPoint.Close()) + + checkSnapshots(repo, mountpoint, []backend.ID{}) + + datafile := filepath.Join("testdata", "backup-data.tar.gz") + fd, err := os.Open(datafile) + if os.IsNotExist(err) { + t.Skipf("unable to find data file %q, skipping", datafile) + return + } + OK(t, err) + OK(t, fd.Close()) + + SetupTarTestFixture(t, env.testdata, datafile) + + // first backup + cmdBackup(t, global, []string{env.testdata}, nil) + snapshotIDs := cmdList(t, global, "snapshots") + Assert(t, len(snapshotIDs) == 1, + "expected one snapshot, got %v", snapshotIDs) + + checkSnapshots(repo, mountpoint, snapshotIDs) + + // second backup, implicit incremental + cmdBackup(t, global, []string{env.testdata}, nil) + snapshotIDs = cmdList(t, global, "snapshots") + Assert(t, len(snapshotIDs) == 2, + "expected two snapshots, got %v", snapshotIDs) + + checkSnapshots(repo, mountpoint, snapshotIDs) + + // third backup, explicit incremental + cmdBackup(t, global, []string{env.testdata}, snapshotIDs[0]) + snapshotIDs = cmdList(t, global, "snapshots") + Assert(t, len(snapshotIDs) == 3, + "expected three snapshots, got %v", snapshotIDs) + + checkSnapshots(repo, mountpoint, snapshotIDs) + }) +} diff --git a/cmd/restic/integration_test.go b/cmd/restic/integration_test.go index 9a08cd932..4bbad4861 100644 --- a/cmd/restic/integration_test.go +++ b/cmd/restic/integration_test.go @@ -14,10 +14,8 @@ import ( "testing" "time" - "github.com/restic/restic" "github.com/restic/restic/backend" "github.com/restic/restic/debug" - "github.com/restic/restic/repository" . "github.com/restic/restic/test" ) @@ -490,85 +488,3 @@ func TestRestoreNoMetadataOnIgnoredIntermediateDirs(t *testing.T) { "meta data of intermediate directory hasn't been restore") }) } - -func TestMount(t *testing.T) { - - checkSnapshots := func(repo *repository.Repository, mountpoint string, snapshotIDs []backend.ID) { - stSnapshots, err := os.Open(filepath.Join(mountpoint, "snapshots")) - OK(t, err) - namesInSnapshots, err := stSnapshots.Readdirnames(-1) - OK(t, err) - Assert(t, - len(namesInSnapshots) == len(snapshotIDs), - "Invalid number of snapshots: expected %d, got %d", len(snapshotIDs), len(namesInSnapshots)) - - for i, id := range snapshotIDs { - snapshot, err := restic.LoadSnapshot(repo, id) - OK(t, err) - Assert(t, - namesInSnapshots[i] == snapshot.Time.Format(time.RFC3339), - "Invalid snapshot folder name: expected %s, got %s", snapshot.Time.Format(time.RFC3339), namesInSnapshots[i]) - } - OK(t, stSnapshots.Close()) - } - - withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) { - cmdInit(t, global) - repo, err := global.OpenRepository() - OK(t, err) - - mountpoint, err := ioutil.TempDir(TestTempDir, "restic-test-mount-") - OK(t, err) - - // We remove the mountpoint now to check that cmdMount creates it - OK(t, os.RemoveAll(mountpoint)) - - ready := make(chan struct{}, 1) - go cmdMount(t, global, mountpoint, ready) - <-ready - - stMountPoint, err := os.Open(mountpoint) - OK(t, err) - names, err := stMountPoint.Readdirnames(-1) - OK(t, err) - Assert(t, len(names) == 1 && names[0] == "snapshots", "expected the snapshots directory to exist") - OK(t, stMountPoint.Close()) - - checkSnapshots(repo, mountpoint, []backend.ID{}) - - datafile := filepath.Join("testdata", "backup-data.tar.gz") - fd, err := os.Open(datafile) - if os.IsNotExist(err) { - t.Skipf("unable to find data file %q, skipping", datafile) - return - } - OK(t, err) - OK(t, fd.Close()) - - SetupTarTestFixture(t, env.testdata, datafile) - - // first backup - cmdBackup(t, global, []string{env.testdata}, nil) - snapshotIDs := cmdList(t, global, "snapshots") - Assert(t, len(snapshotIDs) == 1, - "expected one snapshot, got %v", snapshotIDs) - - checkSnapshots(repo, mountpoint, snapshotIDs) - - // second backup, implicit incremental - cmdBackup(t, global, []string{env.testdata}, nil) - snapshotIDs = cmdList(t, global, "snapshots") - Assert(t, len(snapshotIDs) == 2, - "expected two snapshots, got %v", snapshotIDs) - - checkSnapshots(repo, mountpoint, snapshotIDs) - - // third backup, explicit incremental - cmdBackup(t, global, []string{env.testdata}, snapshotIDs[0]) - snapshotIDs = cmdList(t, global, "snapshots") - Assert(t, len(snapshotIDs) == 3, - "expected three snapshots, got %v", snapshotIDs) - - checkSnapshots(repo, mountpoint, snapshotIDs) - }) -} diff --git a/test/backend.go b/test/backend.go index c2d1fe285..8ea7ccdfd 100644 --- a/test/backend.go +++ b/test/backend.go @@ -18,6 +18,7 @@ var ( TestCleanup = getBoolVar("RESTIC_TEST_CLEANUP", true) TestTempDir = getStringVar("RESTIC_TEST_TMPDIR", "") RunIntegrationTest = getBoolVar("RESTIC_TEST_INTEGRATION", true) + RunFuseTest = getBoolVar("RESTIC_TEST_FUSE", true) TestSFTPPath = getStringVar("RESTIC_TEST_SFTPPATH", "/usr/lib/ssh:/usr/lib/openssh") TestWalkerPath = getStringVar("RESTIC_TEST_PATH", ".") BenchArchiveDirectory = getStringVar("RESTIC_BENCH_DIR", ".") From 0e7c1668d5694a98c54122920357b8e7320ec035 Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sat, 18 Jul 2015 23:22:13 +0200 Subject: [PATCH 10/24] Make mountpoint readable by owner only --- cmd/restic/cmd_mount.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 62f8a5efa..906f450ed 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -61,7 +61,7 @@ func (cmd CmdMount) Execute(args []string) error { if _, err := os.Stat(mountpoint); err != nil { if os.IsNotExist(err) { cmd.global.Verbosef("Mountpoint %s doesn't exist, creating it\n", mountpoint) - err = os.Mkdir(mountpoint, os.ModeDir|0755) + err = os.Mkdir(mountpoint, os.ModeDir|0700) if err != nil { return err } From e654a9659cad29e3c206a04f6543292efbc7ea13 Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sun, 19 Jul 2015 15:14:47 +0200 Subject: [PATCH 11/24] Protect the snapshots cache with a RWMutex --- cmd/restic/cmd_mount.go | 71 ++++++++++++++++------- cmd/restic/fuse/snapshot.go | 109 ++++++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+), 21 deletions(-) create mode 100644 cmd/restic/fuse/snapshot.go diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 906f450ed..d26634ad4 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "fmt" "os" + "sync" "time" "golang.org/x/net/context" @@ -79,7 +80,7 @@ func (cmd CmdMount) Execute(args []string) error { root := fs.Tree{} root.Add("snapshots", &snapshots{ repo: repo, - knownSnapshots: make(map[string]*restic.Snapshot), + knownSnapshots: make(map[string]snapshotWithId), }) cmd.global.Printf("Now serving %s under %s\n", repo.Backend().Location(), mountpoint) @@ -96,6 +97,11 @@ func (cmd CmdMount) Execute(args []string) error { return c.MountError } +type snapshotWithId struct { + *restic.Snapshot + backend.ID +} + // These lines statically ensure that a *snapshots implement the given // interfaces; a misplaced refactoring of the implementation that breaks // the interface will be catched by the compiler @@ -105,8 +111,9 @@ var _ = fs.NodeStringLookuper(&snapshots{}) type snapshots struct { repo *repository.Repository - // snapshot timestamp -> snapshot - knownSnapshots map[string]*restic.Snapshot + // knownSnapshots maps snapshot timestamp to the snapshot + sync.RWMutex + knownSnapshots map[string]snapshotWithId } func (sn *snapshots) Attr(ctx context.Context, a *fuse.Attr) error { @@ -115,16 +122,39 @@ func (sn *snapshots) Attr(ctx context.Context, a *fuse.Attr) error { return nil } -func (sn *snapshots) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { - ret := make([]fuse.Dirent, 0) +func (sn *snapshots) updateCache(ctx context.Context) error { + sn.Lock() + defer sn.Unlock() + for id := range sn.repo.List(backend.Snapshot, ctx.Done()) { snapshot, err := restic.LoadSnapshot(sn.repo, id) if err != nil { - return nil, err + return err } - sn.knownSnapshots[snapshot.Time.Format(time.RFC3339)] = snapshot + sn.knownSnapshots[snapshot.Time.Format(time.RFC3339)] = snapshotWithId{snapshot, id} + } + return nil +} +func (sn *snapshots) get(name string) (snapshot snapshotWithId, ok bool) { + sn.Lock() + snapshot, ok = sn.knownSnapshots[name] + sn.Unlock() + return snapshot, ok +} + +func (sn *snapshots) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { + err := sn.updateCache(ctx) + if err != nil { + return nil, err + } + + sn.RLock() + defer sn.RUnlock() + + ret := make([]fuse.Dirent, 0) + for _, snapshot := range sn.knownSnapshots { ret = append(ret, fuse.Dirent{ - Inode: binary.BigEndian.Uint64(id[:8]), + Inode: binary.BigEndian.Uint64(snapshot.ID[:8]), Type: fuse.DT_Dir, Name: snapshot.Time.Format(time.RFC3339), }) @@ -134,21 +164,21 @@ func (sn *snapshots) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { } func (sn *snapshots) Lookup(ctx context.Context, name string) (fs.Node, error) { - if _, ok := sn.knownSnapshots[name]; !ok { - // At least this snapshot is not cached. We use this opportunity to - // load all missing snapshots - for id := range sn.repo.List(backend.Snapshot, ctx.Done()) { - snapshot, err := restic.LoadSnapshot(sn.repo, id) - if err != nil { - return nil, err - } - snapshotName := snapshot.Time.Format(time.RFC3339) - sn.knownSnapshots[snapshotName] = snapshot - break + snapshot, ok := sn.get(name) + + if !ok { + // We don't know about it, update the cache + err := sn.updateCache(ctx) + if err != nil { + return nil, err + } + snapshot, ok = sn.get(name) + if !ok { + // We still don't know about it, this time it really doesn't exist + return nil, fuse.ENOENT } } - snapshot := sn.knownSnapshots[name] tree, err := restic.LoadTree(sn.repo, snapshot.Tree) if err != nil { return nil, err @@ -157,7 +187,6 @@ func (sn *snapshots) Lookup(ctx context.Context, name string) (fs.Node, error) { repo: sn.repo, tree: tree, }, nil - return nil, fuse.ENOENT } // Statically ensure that *dir implement those interface diff --git a/cmd/restic/fuse/snapshot.go b/cmd/restic/fuse/snapshot.go new file mode 100644 index 000000000..f341073a4 --- /dev/null +++ b/cmd/restic/fuse/snapshot.go @@ -0,0 +1,109 @@ +package fuse + +import ( + "encoding/binary" + "os" + "sync" + "time" + + "bazil.org/fuse" + "bazil.org/fuse/fs" + + "github.com/restic/restic" + "github.com/restic/restic/backend" + "github.com/restic/restic/repository" + + "golang.org/x/net/context" +) + +type SnapshotWithId struct { + *restic.Snapshot + backend.ID +} + +// These lines statically ensure that a *SnapshotsDir implement the given +// interfaces; a misplaced refactoring of the implementation that breaks +// the interface will be catched by the compiler +var _ = fs.HandleReadDirAller(&SnapshotsDir{}) +var _ = fs.NodeStringLookuper(&SnapshotsDir{}) + +type SnapshotsDir struct { + repo *repository.Repository + + // knownSnapshots maps snapshot timestamp to the snapshot + sync.RWMutex + knownSnapshots map[string]SnapshotWithId +} + +func NewSnapshotsDir(repo *repository.Repository) *SnapshotsDir { + return &SnapshotsDir{ + repo: repo, + knownSnapshots: make(map[string]SnapshotWithId), + } +} + +func (sn *SnapshotsDir) Attr(ctx context.Context, attr *fuse.Attr) error { + attr.Inode = 0 + attr.Mode = os.ModeDir | 0555 + return nil +} + +func (sn *SnapshotsDir) updateCache(ctx context.Context) error { + sn.Lock() + defer sn.Unlock() + + for id := range sn.repo.List(backend.Snapshot, ctx.Done()) { + snapshot, err := restic.LoadSnapshot(sn.repo, id) + if err != nil { + return err + } + sn.knownSnapshots[snapshot.Time.Format(time.RFC3339)] = SnapshotWithId{snapshot, id} + } + return nil +} +func (sn *SnapshotsDir) get(name string) (snapshot SnapshotWithId, ok bool) { + sn.RLock() + snapshot, ok = sn.knownSnapshots[name] + sn.RUnlock() + return snapshot, ok +} + +func (sn *SnapshotsDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { + err := sn.updateCache(ctx) + if err != nil { + return nil, err + } + + sn.RLock() + defer sn.RUnlock() + + ret := make([]fuse.Dirent, 0) + for _, snapshot := range sn.knownSnapshots { + ret = append(ret, fuse.Dirent{ + Inode: binary.BigEndian.Uint64(snapshot.ID[:8]), + Type: fuse.DT_Dir, + Name: snapshot.Time.Format(time.RFC3339), + }) + } + + return ret, nil +} + +func (sn *SnapshotsDir) Lookup(ctx context.Context, name string) (fs.Node, error) { + snapshot, ok := sn.get(name) + + if !ok { + // We don't know about it, update the cache + err := sn.updateCache(ctx) + if err != nil { + return nil, err + } + snapshot, ok = sn.get(name) + if !ok { + // We still don't know about it, this time it really doesn't exist + return nil, fuse.ENOENT + } + } + + return newDirFromSnapshot(sn.repo, snapshot) +} From a016f8205168a489dcc214458d256e119751e513 Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sun, 19 Jul 2015 14:08:34 +0200 Subject: [PATCH 12/24] Fix coding style --- cmd/restic/cmd_mount.go | 27 ++++++++++++--------------- cmd/restic/integration_fuse_test.go | 2 +- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index d26634ad4..442637180 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -59,13 +59,11 @@ func (cmd CmdMount) Execute(args []string) error { } mountpoint := args[0] - if _, err := os.Stat(mountpoint); err != nil { - if os.IsNotExist(err) { - cmd.global.Verbosef("Mountpoint %s doesn't exist, creating it\n", mountpoint) - err = os.Mkdir(mountpoint, os.ModeDir|0700) - if err != nil { - return err - } + if _, err := os.Stat(mountpoint); os.IsNotExist(err) { + cmd.global.Verbosef("Mountpoint %s doesn't exist, creating it\n", mountpoint) + err = os.Mkdir(mountpoint, os.ModeDir|0700) + if err != nil { + return err } } c, err := fuse.Mount( @@ -83,8 +81,8 @@ func (cmd CmdMount) Execute(args []string) error { knownSnapshots: make(map[string]snapshotWithId), }) - cmd.global.Printf("Now serving %s under %s\n", repo.Backend().Location(), mountpoint) - cmd.global.Printf("Don't forget to umount after quitting !\n") + cmd.global.Printf("Now serving %s at %s\n", repo.Backend().Location(), mountpoint) + cmd.global.Printf("Don't forget to umount after quitting!\n") cmd.ready <- struct{}{} @@ -116,9 +114,9 @@ type snapshots struct { knownSnapshots map[string]snapshotWithId } -func (sn *snapshots) Attr(ctx context.Context, a *fuse.Attr) error { - a.Inode = 0 - a.Mode = os.ModeDir | 0555 +func (sn *snapshots) Attr(ctx context.Context, attr *fuse.Attr) error { + attr.Inode = 0 + attr.Mode = os.ModeDir | 0555 return nil } @@ -247,7 +245,6 @@ func (d *dir) Lookup(ctx context.Context, name string) (fs.Node, error) { } } - return nil, fuse.ENOENT } // Statically ensure that *file implements the given interface @@ -265,8 +262,8 @@ type file struct { func makeFile(repo *repository.Repository, node *restic.Node) (*file, error) { sizes := make([]uint32, len(node.Content)) - for i, bid := range node.Content { - _, _, _, length, err := repo.Index().Lookup(bid) + for i, blobId := range node.Content { + _, _, _, length, err := repo.Index().Lookup(blobId) if err != nil { return nil, err } diff --git a/cmd/restic/integration_fuse_test.go b/cmd/restic/integration_fuse_test.go index f12b714e7..0754c0e72 100644 --- a/cmd/restic/integration_fuse_test.go +++ b/cmd/restic/integration_fuse_test.go @@ -34,7 +34,7 @@ func TestMount(t *testing.T) { OK(t, err) Assert(t, namesInSnapshots[i] == snapshot.Time.Format(time.RFC3339), - "Invalid snapshot folder name: expected %s, got %s", snapshot.Time.Format(time.RFC3339), namesInSnapshots[i]) + "Invalid snapshot directory name: expected %s, got %s", snapshot.Time.Format(time.RFC3339), namesInSnapshots[i]) } OK(t, stSnapshots.Close()) } From 3731a9436792e57561fb6ee5f0986f4027d98975 Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sun, 19 Jul 2015 14:10:19 +0200 Subject: [PATCH 13/24] Use functions to create names --- cmd/restic/cmd_mount.go | 84 +++++++++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 32 deletions(-) diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 442637180..dcc4b19a5 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -177,14 +177,7 @@ func (sn *snapshots) Lookup(ctx context.Context, name string) (fs.Node, error) { } } - tree, err := restic.LoadTree(sn.repo, snapshot.Tree) - if err != nil { - return nil, err - } - return &dir{ - repo: sn.repo, - tree: tree, - }, nil + return newDirFromSnapshot(sn.repo, snapshot) } // Statically ensure that *dir implement those interface @@ -192,9 +185,43 @@ var _ = fs.HandleReadDirAller(&dir{}) var _ = fs.NodeStringLookuper(&dir{}) type dir struct { - repo *repository.Repository - tree *restic.Tree - inode uint64 + repo *repository.Repository + children map[string]*restic.Node + inode uint64 +} + +func newDir(repo *repository.Repository, node *restic.Node) (*dir, error) { + tree, err := restic.LoadTree(repo, node.Subtree) + if err != nil { + return nil, err + } + children := make(map[string]*restic.Node) + for _, child := range tree.Nodes { + children[child.Name] = child + } + + return &dir{ + repo: repo, + children: children, + inode: node.Inode, + }, nil +} + +func newDirFromSnapshot(repo *repository.Repository, snapshot snapshotWithId) (*dir, error) { + tree, err := restic.LoadTree(repo, snapshot.Tree) + if err != nil { + return nil, err + } + children := make(map[string]*restic.Node) + for _, node := range tree.Nodes { + children[node.Name] = node + } + + return &dir{ + repo: repo, + children: children, + inode: binary.BigEndian.Uint64(snapshot.ID), + }, nil } func (d *dir) Attr(ctx context.Context, a *fuse.Attr) error { @@ -204,9 +231,9 @@ func (d *dir) Attr(ctx context.Context, a *fuse.Attr) error { } func (d *dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { - ret := make([]fuse.Dirent, 0, len(d.tree.Nodes)) + ret := make([]fuse.Dirent, 0, len(d.children)) - for _, node := range d.tree.Nodes { + for _, node := range d.children { var typ fuse.DirentType switch { case node.Mode.IsDir(): @@ -226,25 +253,18 @@ func (d *dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { } func (d *dir) Lookup(ctx context.Context, name string) (fs.Node, error) { - for _, node := range d.tree.Nodes { - if node.Name == name { - switch { - case node.Mode.IsDir(): - subtree, err := restic.LoadTree(d.repo, node.Subtree) - if err != nil { - return nil, err - } - return &dir{ - repo: d.repo, - tree: subtree, - inode: binary.BigEndian.Uint64(node.Subtree[:8]), - }, nil - case node.Mode.IsRegular(): - return makeFile(d.repo, node) - } - } + child, ok := d.children[name] + if !ok { + return nil, fuse.ENOENT + } + switch { + case child.Mode.IsDir(): + return newDir(d.repo, child) + case child.Mode.IsRegular(): + return newFile(d.repo, child) + default: + return nil, fuse.ENOENT } - } // Statically ensure that *file implements the given interface @@ -260,7 +280,7 @@ type file struct { clearContent [][]byte } -func makeFile(repo *repository.Repository, node *restic.Node) (*file, error) { +func newFile(repo *repository.Repository, node *restic.Node) (*file, error) { sizes := make([]uint32, len(node.Content)) for i, blobId := range node.Content { _, _, _, length, err := repo.Index().Lookup(blobId) From 0606b9884e3edc5e78217c7505570945a15fbf27 Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sun, 19 Jul 2015 14:11:33 +0200 Subject: [PATCH 14/24] Make file.Read more intelligible --- cmd/restic/cmd_mount.go | 63 +++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index dcc4b19a5..033c98850 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -275,9 +275,7 @@ type file struct { node *restic.Node sizes []uint32 - - // cleartext contents - clearContent [][]byte + blobs [][]byte } func newFile(repo *repository.Repository, node *restic.Node) (*file, error) { @@ -291,10 +289,10 @@ func newFile(repo *repository.Repository, node *restic.Node) (*file, error) { } return &file{ - repo: repo, - node: node, - sizes: sizes, - clearContent: make([][]byte, len(node.Content)), + repo: repo, + node: node, + sizes: sizes, + blobs: make([][]byte, len(node.Content)), }, nil } @@ -305,37 +303,46 @@ func (f *file) Attr(ctx context.Context, a *fuse.Attr) error { return nil } +func (f *file) getBlobAt(i int) (blob []byte, err error) { + if f.blobs[i] != nil { + blob = f.blobs[i] + } else { + blob, err = f.repo.LoadBlob(pack.Data, f.node.Content[i]) + if err != nil { + return nil, err + } + f.blobs[i] = blob + } + + return blob, nil +} + func (f *file) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { off := req.Offset + + // Skip blobs before the offset + startContent := 0 + for off > int64(f.sizes[startContent]) { + off -= int64(f.sizes[startContent]) + startContent++ + } + content := make([]byte, req.Size) allContent := content - - for i := range f.node.Content { - if off >= int64(f.sizes[i]) { - off -= int64(f.sizes[i]) - continue + for i := startContent; i < len(f.sizes); i++ { + blob, err := f.getBlobAt(i) + if err != nil { + return err } - var subContent []byte - if f.clearContent[i] != nil { - subContent = f.clearContent[i] - } else { - var err error - subContent, err = f.repo.LoadBlob(pack.Data, f.node.Content[i]) - if err != nil { - return err - } - f.clearContent[i] = subContent - } - - subContent = subContent[off:] + blob = blob[off:] off = 0 var copied int - if len(subContent) > len(content) { - copied = copy(content[0:], subContent[:len(content)]) + if len(blob) > len(content) { + copied = copy(content[0:], blob[:len(content)]) } else { - copied = copy(content[0:], subContent) + copied = copy(content[0:], blob) } content = content[copied:] if len(content) == 0 { From b1426826ccb279fa04607d337c4dc1a299457f26 Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sun, 19 Jul 2015 14:28:11 +0200 Subject: [PATCH 15/24] Extract fuse structs --- cmd/restic/cmd_mount.go | 282 +--------------------------------------- cmd/restic/fuse/dir.go | 100 ++++++++++++++ cmd/restic/fuse/file.go | 98 ++++++++++++++ 3 files changed, 204 insertions(+), 276 deletions(-) create mode 100644 cmd/restic/fuse/dir.go create mode 100644 cmd/restic/fuse/file.go diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 033c98850..d06274d00 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -3,21 +3,12 @@ package main import ( - "encoding/binary" "fmt" "os" - "sync" - "time" - "golang.org/x/net/context" + "github.com/restic/restic/cmd/restic/fuse" - "github.com/restic/restic" - "github.com/restic/restic/backend" - "github.com/restic/restic/crypto" - "github.com/restic/restic/pack" - "github.com/restic/restic/repository" - - "bazil.org/fuse" + systemFuse "bazil.org/fuse" "bazil.org/fuse/fs" ) @@ -66,20 +57,17 @@ func (cmd CmdMount) Execute(args []string) error { return err } } - c, err := fuse.Mount( + c, err := systemFuse.Mount( mountpoint, - fuse.ReadOnly(), - fuse.FSName("restic"), + systemFuse.ReadOnly(), + systemFuse.FSName("restic"), ) if err != nil { return err } root := fs.Tree{} - root.Add("snapshots", &snapshots{ - repo: repo, - knownSnapshots: make(map[string]snapshotWithId), - }) + root.Add("snapshots", fuse.NewSnapshotsDir(repo)) cmd.global.Printf("Now serving %s at %s\n", repo.Backend().Location(), mountpoint) cmd.global.Printf("Don't forget to umount after quitting!\n") @@ -94,261 +82,3 @@ func (cmd CmdMount) Execute(args []string) error { <-c.Ready return c.MountError } - -type snapshotWithId struct { - *restic.Snapshot - backend.ID -} - -// These lines statically ensure that a *snapshots implement the given -// interfaces; a misplaced refactoring of the implementation that breaks -// the interface will be catched by the compiler -var _ = fs.HandleReadDirAller(&snapshots{}) -var _ = fs.NodeStringLookuper(&snapshots{}) - -type snapshots struct { - repo *repository.Repository - - // knownSnapshots maps snapshot timestamp to the snapshot - sync.RWMutex - knownSnapshots map[string]snapshotWithId -} - -func (sn *snapshots) Attr(ctx context.Context, attr *fuse.Attr) error { - attr.Inode = 0 - attr.Mode = os.ModeDir | 0555 - return nil -} - -func (sn *snapshots) updateCache(ctx context.Context) error { - sn.Lock() - defer sn.Unlock() - - for id := range sn.repo.List(backend.Snapshot, ctx.Done()) { - snapshot, err := restic.LoadSnapshot(sn.repo, id) - if err != nil { - return err - } - sn.knownSnapshots[snapshot.Time.Format(time.RFC3339)] = snapshotWithId{snapshot, id} - } - return nil -} -func (sn *snapshots) get(name string) (snapshot snapshotWithId, ok bool) { - sn.Lock() - snapshot, ok = sn.knownSnapshots[name] - sn.Unlock() - return snapshot, ok -} - -func (sn *snapshots) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { - err := sn.updateCache(ctx) - if err != nil { - return nil, err - } - - sn.RLock() - defer sn.RUnlock() - - ret := make([]fuse.Dirent, 0) - for _, snapshot := range sn.knownSnapshots { - ret = append(ret, fuse.Dirent{ - Inode: binary.BigEndian.Uint64(snapshot.ID[:8]), - Type: fuse.DT_Dir, - Name: snapshot.Time.Format(time.RFC3339), - }) - } - - return ret, nil -} - -func (sn *snapshots) Lookup(ctx context.Context, name string) (fs.Node, error) { - snapshot, ok := sn.get(name) - - if !ok { - // We don't know about it, update the cache - err := sn.updateCache(ctx) - if err != nil { - return nil, err - } - snapshot, ok = sn.get(name) - if !ok { - // We still don't know about it, this time it really doesn't exist - return nil, fuse.ENOENT - } - } - - return newDirFromSnapshot(sn.repo, snapshot) -} - -// Statically ensure that *dir implement those interface -var _ = fs.HandleReadDirAller(&dir{}) -var _ = fs.NodeStringLookuper(&dir{}) - -type dir struct { - repo *repository.Repository - children map[string]*restic.Node - inode uint64 -} - -func newDir(repo *repository.Repository, node *restic.Node) (*dir, error) { - tree, err := restic.LoadTree(repo, node.Subtree) - if err != nil { - return nil, err - } - children := make(map[string]*restic.Node) - for _, child := range tree.Nodes { - children[child.Name] = child - } - - return &dir{ - repo: repo, - children: children, - inode: node.Inode, - }, nil -} - -func newDirFromSnapshot(repo *repository.Repository, snapshot snapshotWithId) (*dir, error) { - tree, err := restic.LoadTree(repo, snapshot.Tree) - if err != nil { - return nil, err - } - children := make(map[string]*restic.Node) - for _, node := range tree.Nodes { - children[node.Name] = node - } - - return &dir{ - repo: repo, - children: children, - inode: binary.BigEndian.Uint64(snapshot.ID), - }, nil -} - -func (d *dir) Attr(ctx context.Context, a *fuse.Attr) error { - a.Inode = d.inode - a.Mode = os.ModeDir | 0555 - return nil -} - -func (d *dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { - ret := make([]fuse.Dirent, 0, len(d.children)) - - for _, node := range d.children { - var typ fuse.DirentType - switch { - case node.Mode.IsDir(): - typ = fuse.DT_Dir - case node.Mode.IsRegular(): - typ = fuse.DT_File - } - - ret = append(ret, fuse.Dirent{ - Inode: node.Inode, - Type: typ, - Name: node.Name, - }) - } - - return ret, nil -} - -func (d *dir) Lookup(ctx context.Context, name string) (fs.Node, error) { - child, ok := d.children[name] - if !ok { - return nil, fuse.ENOENT - } - switch { - case child.Mode.IsDir(): - return newDir(d.repo, child) - case child.Mode.IsRegular(): - return newFile(d.repo, child) - default: - return nil, fuse.ENOENT - } -} - -// Statically ensure that *file implements the given interface -var _ = fs.HandleReader(&file{}) - -type file struct { - repo *repository.Repository - node *restic.Node - - sizes []uint32 - blobs [][]byte -} - -func newFile(repo *repository.Repository, node *restic.Node) (*file, error) { - sizes := make([]uint32, len(node.Content)) - for i, blobId := range node.Content { - _, _, _, length, err := repo.Index().Lookup(blobId) - if err != nil { - return nil, err - } - sizes[i] = uint32(length) - crypto.Extension - } - - return &file{ - repo: repo, - node: node, - sizes: sizes, - blobs: make([][]byte, len(node.Content)), - }, nil -} - -func (f *file) Attr(ctx context.Context, a *fuse.Attr) error { - a.Inode = f.node.Inode - a.Mode = f.node.Mode - a.Size = f.node.Size - return nil -} - -func (f *file) getBlobAt(i int) (blob []byte, err error) { - if f.blobs[i] != nil { - blob = f.blobs[i] - } else { - blob, err = f.repo.LoadBlob(pack.Data, f.node.Content[i]) - if err != nil { - return nil, err - } - f.blobs[i] = blob - } - - return blob, nil -} - -func (f *file) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { - off := req.Offset - - // Skip blobs before the offset - startContent := 0 - for off > int64(f.sizes[startContent]) { - off -= int64(f.sizes[startContent]) - startContent++ - } - - content := make([]byte, req.Size) - allContent := content - for i := startContent; i < len(f.sizes); i++ { - blob, err := f.getBlobAt(i) - if err != nil { - return err - } - - blob = blob[off:] - off = 0 - - var copied int - if len(blob) > len(content) { - copied = copy(content[0:], blob[:len(content)]) - } else { - copied = copy(content[0:], blob) - } - content = content[copied:] - if len(content) == 0 { - break - } - } - resp.Data = allContent - return nil -} diff --git a/cmd/restic/fuse/dir.go b/cmd/restic/fuse/dir.go new file mode 100644 index 000000000..ef9ad9a73 --- /dev/null +++ b/cmd/restic/fuse/dir.go @@ -0,0 +1,100 @@ +package fuse + +import ( + "encoding/binary" + "os" + + "bazil.org/fuse" + "bazil.org/fuse/fs" + "golang.org/x/net/context" + + "github.com/restic/restic" + "github.com/restic/restic/repository" +) + +// Statically ensure that *dir implement those interface +var _ = fs.HandleReadDirAller(&dir{}) +var _ = fs.NodeStringLookuper(&dir{}) + +type dir struct { + repo *repository.Repository + children map[string]*restic.Node + inode uint64 +} + +func newDir(repo *repository.Repository, node *restic.Node) (*dir, error) { + tree, err := restic.LoadTree(repo, node.Subtree) + if err != nil { + return nil, err + } + children := make(map[string]*restic.Node) + for _, child := range tree.Nodes { + children[child.Name] = child + } + + return &dir{ + repo: repo, + children: children, + inode: node.Inode, + }, nil +} + +func newDirFromSnapshot(repo *repository.Repository, snapshot SnapshotWithId) (*dir, error) { + tree, err := restic.LoadTree(repo, snapshot.Tree) + if err != nil { + return nil, err + } + children := make(map[string]*restic.Node) + for _, node := range tree.Nodes { + children[node.Name] = node + } + + return &dir{ + repo: repo, + children: children, + inode: binary.BigEndian.Uint64(snapshot.ID), + }, nil +} + +func (d *dir) Attr(ctx context.Context, a *fuse.Attr) error { + a.Inode = d.inode + a.Mode = os.ModeDir | 0555 + return nil +} + +func (d *dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { + ret := make([]fuse.Dirent, 0, len(d.children)) + + for _, node := range d.children { + var typ fuse.DirentType + switch { + case node.Mode.IsDir(): + typ = fuse.DT_Dir + case node.Mode.IsRegular(): + typ = fuse.DT_File + } + + ret = append(ret, fuse.Dirent{ + Inode: node.Inode, + Type: typ, + Name: node.Name, + }) + } + + return ret, nil +} + +func (d *dir) Lookup(ctx context.Context, name string) (fs.Node, error) { + child, ok := d.children[name] + if !ok { + return nil, fuse.ENOENT + } + switch { + case child.Mode.IsDir(): + return newDir(d.repo, child) + case child.Mode.IsRegular(): + return newFile(d.repo, child) + default: + return nil, fuse.ENOENT + } +} diff --git a/cmd/restic/fuse/file.go b/cmd/restic/fuse/file.go new file mode 100644 index 000000000..ff28ebf85 --- /dev/null +++ b/cmd/restic/fuse/file.go @@ -0,0 +1,98 @@ +package fuse + +import ( + "github.com/restic/restic" + "github.com/restic/restic/crypto" + "github.com/restic/restic/pack" + "github.com/restic/restic/repository" + + "bazil.org/fuse" + "bazil.org/fuse/fs" + "golang.org/x/net/context" +) + +// Statically ensure that *file implements the given interface +var _ = fs.HandleReader(&file{}) + +type file struct { + repo *repository.Repository + node *restic.Node + + sizes []uint32 + blobs [][]byte +} + +func newFile(repo *repository.Repository, node *restic.Node) (*file, error) { + sizes := make([]uint32, len(node.Content)) + for i, blobId := range node.Content { + _, _, _, length, err := repo.Index().Lookup(blobId) + if err != nil { + return nil, err + } + sizes[i] = uint32(length) - crypto.Extension + } + + return &file{ + repo: repo, + node: node, + sizes: sizes, + blobs: make([][]byte, len(node.Content)), + }, nil +} + +func (f *file) Attr(ctx context.Context, a *fuse.Attr) error { + a.Inode = f.node.Inode + a.Mode = f.node.Mode + a.Size = f.node.Size + return nil +} + +func (f *file) getBlobAt(i int) (blob []byte, err error) { + if f.blobs[i] != nil { + blob = f.blobs[i] + } else { + blob, err = f.repo.LoadBlob(pack.Data, f.node.Content[i]) + if err != nil { + return nil, err + } + f.blobs[i] = blob + } + + return blob, nil +} + +func (f *file) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { + off := req.Offset + + // Skip blobs before the offset + startContent := 0 + for off > int64(f.sizes[startContent]) { + off -= int64(f.sizes[startContent]) + startContent++ + } + + content := make([]byte, req.Size) + allContent := content + for i := startContent; i < len(f.sizes); i++ { + blob, err := f.getBlobAt(i) + if err != nil { + return err + } + + blob = blob[off:] + off = 0 + + var copied int + if len(blob) > len(content) { + copied = copy(content[0:], blob[:len(content)]) + } else { + copied = copy(content[0:], blob) + } + content = content[copied:] + if len(content) == 0 { + break + } + } + resp.Data = allContent + return nil +} From c9d8ab9be59141db697b0361f64d514a0bcb83b1 Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sun, 19 Jul 2015 14:29:43 +0200 Subject: [PATCH 16/24] Skip fuse tests for non-darwin --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 642f0ec5d..c675033a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ os: - linux - osx -env: GOX_OS="linux darwin openbsd freebsd" GOX_ARCH="386 amd64 arm" RESTIC_TEST_FUSE="0" +env: GOX_OS="linux darwin openbsd freebsd" GOX_ARCH="386 amd64 arm" notifications: irc: From ca6b7ec5330b33450c56dbe58be0032fa7cd4132 Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sun, 19 Jul 2015 15:16:16 +0200 Subject: [PATCH 17/24] Add Index.LookupSize --- cmd/restic/fuse/file.go | 5 ++--- repository/index.go | 11 +++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/cmd/restic/fuse/file.go b/cmd/restic/fuse/file.go index ff28ebf85..b207d939c 100644 --- a/cmd/restic/fuse/file.go +++ b/cmd/restic/fuse/file.go @@ -2,7 +2,6 @@ package fuse import ( "github.com/restic/restic" - "github.com/restic/restic/crypto" "github.com/restic/restic/pack" "github.com/restic/restic/repository" @@ -25,11 +24,11 @@ type file struct { func newFile(repo *repository.Repository, node *restic.Node) (*file, error) { sizes := make([]uint32, len(node.Content)) for i, blobId := range node.Content { - _, _, _, length, err := repo.Index().Lookup(blobId) + length, err := repo.Index().LookupSize(blobId) if err != nil { return nil, err } - sizes[i] = uint32(length) - crypto.Extension + sizes[i] = uint32(length) } return &file{ diff --git a/repository/index.go b/repository/index.go index d7bd4bf16..2fcc67cbc 100644 --- a/repository/index.go +++ b/repository/index.go @@ -7,6 +7,7 @@ import ( "sync" "github.com/restic/restic/backend" + "github.com/restic/restic/crypto" "github.com/restic/restic/debug" "github.com/restic/restic/pack" ) @@ -91,6 +92,16 @@ func (idx *Index) Has(id backend.ID) bool { return false } +// LookupSize returns the length of the cleartext content behind the +// given id +func (idx *Index) LookupSize(id backend.ID) (cleartextLength uint, err error) { + _, _, _, encryptedLength, err := idx.Lookup(id) + if err != nil { + return 0, err + } + return encryptedLength - crypto.Extension, nil +} + // Merge loads all items from other into idx. func (idx *Index) Merge(other *Index) { debug.Log("Index.Merge", "Merge index with %p", other) From d7888d4dd5c42e2f719046a18d50e774be55b630 Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sun, 19 Jul 2015 15:21:21 +0200 Subject: [PATCH 18/24] Fix checks in fuse tests --- cmd/restic/integration_fuse_test.go | 30 ++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/cmd/restic/integration_fuse_test.go b/cmd/restic/integration_fuse_test.go index 0754c0e72..4dc725b16 100644 --- a/cmd/restic/integration_fuse_test.go +++ b/cmd/restic/integration_fuse_test.go @@ -21,22 +21,30 @@ func TestMount(t *testing.T) { } checkSnapshots := func(repo *repository.Repository, mountpoint string, snapshotIDs []backend.ID) { - stSnapshots, err := os.Open(filepath.Join(mountpoint, "snapshots")) + snapshotsDir, err := os.Open(filepath.Join(mountpoint, "snapshots")) OK(t, err) - namesInSnapshots, err := stSnapshots.Readdirnames(-1) + namesInSnapshots, err := snapshotsDir.Readdirnames(-1) OK(t, err) Assert(t, len(namesInSnapshots) == len(snapshotIDs), "Invalid number of snapshots: expected %d, got %d", len(snapshotIDs), len(namesInSnapshots)) - for i, id := range snapshotIDs { + namesMap := make(map[string]bool) + for _, name := range namesInSnapshots { + namesMap[name] = false + } + + for _, id := range snapshotIDs { snapshot, err := restic.LoadSnapshot(repo, id) OK(t, err) - Assert(t, - namesInSnapshots[i] == snapshot.Time.Format(time.RFC3339), - "Invalid snapshot directory name: expected %s, got %s", snapshot.Time.Format(time.RFC3339), namesInSnapshots[i]) + _, ok := namesMap[snapshot.Time.Format(time.RFC3339)] + Assert(t, ok, "Snapshot %s isn't present in fuse dir", snapshot.Time.Format(time.RFC3339)) + namesMap[snapshot.Time.Format(time.RFC3339)] = true } - OK(t, stSnapshots.Close()) + for name, present := range namesMap { + Assert(t, present, "Directory %s is present in fuse dir but is not a snapshot", name) + } + OK(t, snapshotsDir.Close()) } withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) { @@ -54,12 +62,12 @@ func TestMount(t *testing.T) { go cmdMount(t, global, mountpoint, ready) <-ready - stMountPoint, err := os.Open(mountpoint) + mountpointDir, err := os.Open(mountpoint) OK(t, err) - names, err := stMountPoint.Readdirnames(-1) + names, err := mountpointDir.Readdirnames(-1) OK(t, err) - Assert(t, len(names) == 1 && names[0] == "snapshots", "expected the snapshots directory to exist") - OK(t, stMountPoint.Close()) + Assert(t, len(names) == 1 && names[0] == "snapshots", `The fuse virtual directory "snapshots" doesn't exist`) + OK(t, mountpointDir.Close()) checkSnapshots(repo, mountpoint, []backend.ID{}) From a4d122e5ae6c3f7dab95ff6437055c58de5db60d Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sun, 19 Jul 2015 16:33:37 +0200 Subject: [PATCH 19/24] Cleanup mount after test --- cmd/restic/integration_fuse_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cmd/restic/integration_fuse_test.go b/cmd/restic/integration_fuse_test.go index 4dc725b16..107b1e831 100644 --- a/cmd/restic/integration_fuse_test.go +++ b/cmd/restic/integration_fuse_test.go @@ -9,6 +9,8 @@ import ( "testing" "time" + "bazil.org/fuse" + "github.com/restic/restic" "github.com/restic/restic/backend" "github.com/restic/restic/repository" @@ -62,6 +64,15 @@ func TestMount(t *testing.T) { go cmdMount(t, global, mountpoint, ready) <-ready + defer func() { + err := fuse.Unmount(mountpoint) + OK(t, err) + if TestCleanup { + err = os.RemoveAll(mountpoint) + OK(t, err) + } + }() + mountpointDir, err := os.Open(mountpoint) OK(t, err) names, err := mountpointDir.Readdirnames(-1) From fe6f1c01f356399c1d6d5d57dcf36b9b9adc294d Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sun, 19 Jul 2015 16:39:17 +0200 Subject: [PATCH 20/24] Make inodeFromBackendId more explicit --- cmd/restic/fuse/dir.go | 3 +-- cmd/restic/fuse/fuse.go | 14 ++++++++++++++ cmd/restic/fuse/snapshot.go | 3 +-- 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 cmd/restic/fuse/fuse.go diff --git a/cmd/restic/fuse/dir.go b/cmd/restic/fuse/dir.go index ef9ad9a73..c8114bbca 100644 --- a/cmd/restic/fuse/dir.go +++ b/cmd/restic/fuse/dir.go @@ -1,7 +1,6 @@ package fuse import ( - "encoding/binary" "os" "bazil.org/fuse" @@ -52,7 +51,7 @@ func newDirFromSnapshot(repo *repository.Repository, snapshot SnapshotWithId) (* return &dir{ repo: repo, children: children, - inode: binary.BigEndian.Uint64(snapshot.ID), + inode: inodeFromBackendId(snapshot.ID), }, nil } diff --git a/cmd/restic/fuse/fuse.go b/cmd/restic/fuse/fuse.go new file mode 100644 index 000000000..487d23f10 --- /dev/null +++ b/cmd/restic/fuse/fuse.go @@ -0,0 +1,14 @@ +package fuse + +import ( + "encoding/binary" + + "github.com/restic/restic/backend" +) + +// inodeFromBackendId returns a unique uint64 from a backend id. +// Endianness has no specific meaning, it is just the simplest way to +// transform a []byte to an uint64 +func inodeFromBackendId(id backend.ID) uint64 { + return binary.BigEndian.Uint64(id[:8]) +} diff --git a/cmd/restic/fuse/snapshot.go b/cmd/restic/fuse/snapshot.go index f341073a4..f288615d1 100644 --- a/cmd/restic/fuse/snapshot.go +++ b/cmd/restic/fuse/snapshot.go @@ -1,7 +1,6 @@ package fuse import ( - "encoding/binary" "os" "sync" "time" @@ -80,7 +79,7 @@ func (sn *SnapshotsDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { ret := make([]fuse.Dirent, 0) for _, snapshot := range sn.knownSnapshots { ret = append(ret, fuse.Dirent{ - Inode: binary.BigEndian.Uint64(snapshot.ID[:8]), + Inode: inodeFromBackendId(snapshot.ID), Type: fuse.DT_Dir, Name: snapshot.Time.Format(time.RFC3339), }) From eadfcd3f9eedfd130d42078823bb03e096ed06ec Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sun, 19 Jul 2015 22:53:10 +0200 Subject: [PATCH 21/24] Add waitForMount for OSX --- cmd/restic/integration_fuse_test.go | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/cmd/restic/integration_fuse_test.go b/cmd/restic/integration_fuse_test.go index 107b1e831..b09e61eb0 100644 --- a/cmd/restic/integration_fuse_test.go +++ b/cmd/restic/integration_fuse_test.go @@ -3,6 +3,7 @@ package main import ( + "fmt" "io/ioutil" "os" "path/filepath" @@ -17,6 +18,41 @@ import ( . "github.com/restic/restic/test" ) +const ( + mountWait = 20 + mountSleep = 100 * time.Millisecond + mountTestSubdir = "snapshots" +) + +// waitForMount blocks (max mountWait * mountSleep) until the subdir +// "snapshots" appears in the dir. +func waitForMount(dir string) error { + for i := 0; i < mountWait; i++ { + f, err := os.Open(dir) + if err != nil { + return err + } + + names, err := f.Readdirnames(-1) + if err != nil { + return err + } + + if err = f.Close(); err != nil { + return err + } + + for _, name := range names { + if name == mountTestSubdir { + return nil + } + } + + time.Sleep(mountSleep) + } + + return fmt.Errorf("subdir %q of dir %s never appeared", mountTestSubdir, dir) +} func TestMount(t *testing.T) { if !RunFuseTest { t.Skip("Skipping fuse tests") @@ -72,6 +108,7 @@ func TestMount(t *testing.T) { OK(t, err) } }() + OK(t, waitForMount(mountpoint)) mountpointDir, err := os.Open(mountpoint) OK(t, err) From e44716381c9ac56a9eb65b062e29ae10d9c96be4 Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sun, 19 Jul 2015 23:02:02 +0200 Subject: [PATCH 22/24] Unmount when closing application from cli --- cmd/restic/cmd_mount.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index d06274d00..01ebe7313 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -72,6 +72,10 @@ func (cmd CmdMount) Execute(args []string) error { cmd.global.Printf("Now serving %s at %s\n", repo.Backend().Location(), mountpoint) cmd.global.Printf("Don't forget to umount after quitting!\n") + AddCleanupHandler(func() error { + return systemFuse.Unmount(mountpoint) + }) + cmd.ready <- struct{}{} err = fs.Serve(c, &root) From 3767eb2675a12f0d3631610307da7c7721ca0a0c Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sun, 19 Jul 2015 23:02:48 +0200 Subject: [PATCH 23/24] Unmount and remove directory for mount in tests --- cmd/restic/cmd_mount.go | 28 ++++++++++++++++++++++------ cmd/restic/integration_fuse_test.go | 24 ++++++++++++------------ cmd/restic/integration_test.go | 5 ----- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 01ebe7313..d685af98a 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -15,6 +15,7 @@ import ( type CmdMount struct { global *GlobalOptions ready chan struct{} + done chan struct{} } func init() { @@ -24,6 +25,7 @@ func init() { &CmdMount{ global: &globalOpts, ready: make(chan struct{}, 1), + done: make(chan struct{}), }) if err != nil { panic(err) @@ -78,11 +80,25 @@ func (cmd CmdMount) Execute(args []string) error { cmd.ready <- struct{}{} - err = fs.Serve(c, &root) - if err != nil { - return err - } + errServe := make(chan error) + go func() { + err = fs.Serve(c, &root) + if err != nil { + errServe <- err + } - <-c.Ready - return c.MountError + <-c.Ready + errServe <- c.MountError + }() + + select { + case err := <-errServe: + return err + case <-cmd.done: + err := c.Close() + if err != nil { + cmd.global.Printf("Error closing fuse connection: %s\n", err) + } + return systemFuse.Unmount(mountpoint) + } } diff --git a/cmd/restic/integration_fuse_test.go b/cmd/restic/integration_fuse_test.go index b09e61eb0..1c4cce39f 100644 --- a/cmd/restic/integration_fuse_test.go +++ b/cmd/restic/integration_fuse_test.go @@ -10,8 +10,6 @@ import ( "testing" "time" - "bazil.org/fuse" - "github.com/restic/restic" "github.com/restic/restic/backend" "github.com/restic/restic/repository" @@ -53,6 +51,15 @@ func waitForMount(dir string) error { return fmt.Errorf("subdir %q of dir %s never appeared", mountTestSubdir, dir) } + +func cmdMount(t testing.TB, global GlobalOptions, dir string, ready, done chan struct{}) { + cmd := &CmdMount{global: &global, ready: ready, done: done} + OK(t, cmd.Execute([]string{dir})) + if TestCleanup { + OK(t, os.RemoveAll(dir)) + } +} + func TestMount(t *testing.T) { if !RunFuseTest { t.Skip("Skipping fuse tests") @@ -97,17 +104,10 @@ func TestMount(t *testing.T) { OK(t, os.RemoveAll(mountpoint)) ready := make(chan struct{}, 1) - go cmdMount(t, global, mountpoint, ready) + done := make(chan struct{}) + go cmdMount(t, global, mountpoint, ready, done) <-ready - - defer func() { - err := fuse.Unmount(mountpoint) - OK(t, err) - if TestCleanup { - err = os.RemoveAll(mountpoint) - OK(t, err) - } - }() + defer close(done) OK(t, waitForMount(mountpoint)) mountpointDir, err := os.Open(mountpoint) diff --git a/cmd/restic/integration_test.go b/cmd/restic/integration_test.go index 4bbad4861..b06b85745 100644 --- a/cmd/restic/integration_test.go +++ b/cmd/restic/integration_test.go @@ -73,11 +73,6 @@ func cmdCheck(t testing.TB, global GlobalOptions) { OK(t, cmd.Execute(nil)) } -func cmdMount(t testing.TB, global GlobalOptions, dir string, ready chan struct{}) { - cmd := &CmdMount{global: &global, ready: ready} - OK(t, cmd.Execute([]string{dir})) -} - func TestBackup(t *testing.T) { withTestEnvironment(t, func(env *testEnvironment, global GlobalOptions) { datafile := filepath.Join("testdata", "backup-data.tar.gz") From 77c0f69dd04665809f47db2c820566ca74a1fc77 Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sun, 19 Jul 2015 23:24:37 +0200 Subject: [PATCH 24/24] Don't test fuse on non-darwin --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index c675033a9..199559449 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,7 @@ install: - go version | grep -q "darwin" && export GOX_OS="darwin" || true - uname -s | grep -qi darwin && brew install caskroom/cask/brew-cask || true - uname -s | grep -qi darwin && brew cask install osxfuse || true + - uname -s | grep -vqi darwin && export RESTIC_TEST_FUSE="0" || true - echo "cross-compile for \"$GOX_OS\" on \"$GOX_ARCH\"" - gox -build-toolchain -os "$GOX_OS" -arch "$GOX_ARCH"