~tsileo/blobstash

7d90c6d372655d1d963494906e9646d95074754a — Thomas Sileo 3 months ago 757285a
fs: move fs to its own repo
209 files changed, 30330 insertions(+), 20769 deletions(-)

D cmd/blobstash-fs/blobstash-fs.go
M go.mod
M go.sum
D pkg/filetree/fs/asof.go
D pkg/filetree/fs/cache.go
D pkg/filetree/fs/config.go
D pkg/filetree/fs/debug.go
D pkg/filetree/fs/fs.go
D pkg/filetree/fs/node.go
D pkg/filetree/fs/recent.go
D pkg/filetree/fs/versions.go
D vendor/bazil.org/fuse/.gitattributes
D vendor/bazil.org/fuse/.gitignore
D vendor/bazil.org/fuse/LICENSE
D vendor/bazil.org/fuse/README.md
D vendor/bazil.org/fuse/buffer.go
D vendor/bazil.org/fuse/debug.go
D vendor/bazil.org/fuse/error_darwin.go
D vendor/bazil.org/fuse/error_freebsd.go
D vendor/bazil.org/fuse/error_linux.go
D vendor/bazil.org/fuse/error_std.go
D vendor/bazil.org/fuse/fs/serve.go
D vendor/bazil.org/fuse/fs/tree.go
D vendor/bazil.org/fuse/fuse.go
D vendor/bazil.org/fuse/fuse_darwin.go
D vendor/bazil.org/fuse/fuse_freebsd.go
D vendor/bazil.org/fuse/fuse_kernel.go
D vendor/bazil.org/fuse/fuse_kernel_darwin.go
D vendor/bazil.org/fuse/fuse_kernel_freebsd.go
D vendor/bazil.org/fuse/fuse_kernel_linux.go
D vendor/bazil.org/fuse/fuse_kernel_std.go
D vendor/bazil.org/fuse/fuse_linux.go
D vendor/bazil.org/fuse/fuseutil/fuseutil.go
D vendor/bazil.org/fuse/go.mod
D vendor/bazil.org/fuse/go.sum
D vendor/bazil.org/fuse/mount.go
D vendor/bazil.org/fuse/mount_darwin.go
D vendor/bazil.org/fuse/mount_freebsd.go
D vendor/bazil.org/fuse/mount_linux.go
D vendor/bazil.org/fuse/options.go
D vendor/bazil.org/fuse/options_darwin.go
D vendor/bazil.org/fuse/options_freebsd.go
D vendor/bazil.org/fuse/options_linux.go
D vendor/bazil.org/fuse/protocol.go
D vendor/bazil.org/fuse/unmount.go
D vendor/bazil.org/fuse/unmount_linux.go
D vendor/bazil.org/fuse/unmount_std.go
A vendor/github.com/golang/protobuf/proto/buffer.go
D vendor/github.com/golang/protobuf/proto/clone.go
D vendor/github.com/golang/protobuf/proto/decode.go
A vendor/github.com/golang/protobuf/proto/defaults.go
M vendor/github.com/golang/protobuf/proto/deprecated.go
M vendor/github.com/golang/protobuf/proto/discard.go
D vendor/github.com/golang/protobuf/proto/encode.go
D vendor/github.com/golang/protobuf/proto/equal.go
M vendor/github.com/golang/protobuf/proto/extensions.go
D vendor/github.com/golang/protobuf/proto/lib.go
D vendor/github.com/golang/protobuf/proto/message_set.go
D vendor/github.com/golang/protobuf/proto/pointer_reflect.go
D vendor/github.com/golang/protobuf/proto/pointer_unsafe.go
M vendor/github.com/golang/protobuf/proto/properties.go
A vendor/github.com/golang/protobuf/proto/proto.go
A vendor/github.com/golang/protobuf/proto/registry.go
D vendor/github.com/golang/protobuf/proto/table_marshal.go
D vendor/github.com/golang/protobuf/proto/table_merge.go
D vendor/github.com/golang/protobuf/proto/table_unmarshal.go
D vendor/github.com/golang/protobuf/proto/text.go
R vendor/github.com/golang/protobuf/proto/{text_parser.go => text_decode.go}
A vendor/github.com/golang/protobuf/proto/text_encode.go
A vendor/github.com/golang/protobuf/proto/wire.go
A vendor/github.com/golang/protobuf/proto/wrappers.go
D vendor/github.com/mitchellh/go-ps/.gitignore
D vendor/github.com/mitchellh/go-ps/.travis.yml
D vendor/github.com/mitchellh/go-ps/LICENSE.md
D vendor/github.com/mitchellh/go-ps/README.md
D vendor/github.com/mitchellh/go-ps/Vagrantfile
D vendor/github.com/mitchellh/go-ps/go.mod
D vendor/github.com/mitchellh/go-ps/process.go
D vendor/github.com/mitchellh/go-ps/process_darwin.go
D vendor/github.com/mitchellh/go-ps/process_freebsd.go
D vendor/github.com/mitchellh/go-ps/process_linux.go
D vendor/github.com/mitchellh/go-ps/process_solaris.go
D vendor/github.com/mitchellh/go-ps/process_unix.go
D vendor/github.com/mitchellh/go-ps/process_windows.go
M vendor/github.com/yuin/goldmark/extension/typographer.go
M vendor/golang.org/x/sys/unix/syscall_linux.go
M vendor/golang.org/x/sys/unix/zsyscall_linux.go
M vendor/golang.org/x/sys/unix/ztypes_linux.go
A vendor/google.golang.org/protobuf/AUTHORS
A vendor/google.golang.org/protobuf/CONTRIBUTORS
A vendor/google.golang.org/protobuf/LICENSE
A vendor/google.golang.org/protobuf/PATENTS
A vendor/google.golang.org/protobuf/encoding/prototext/decode.go
A vendor/google.golang.org/protobuf/encoding/prototext/doc.go
A vendor/google.golang.org/protobuf/encoding/prototext/encode.go
A vendor/google.golang.org/protobuf/encoding/protowire/wire.go
A vendor/google.golang.org/protobuf/internal/descfmt/stringer.go
A vendor/google.golang.org/protobuf/internal/descopts/options.go
A vendor/google.golang.org/protobuf/internal/detrand/rand.go
A vendor/google.golang.org/protobuf/internal/encoding/defval/default.go
A vendor/google.golang.org/protobuf/internal/encoding/messageset/messageset.go
A vendor/google.golang.org/protobuf/internal/encoding/tag/tag.go
A vendor/google.golang.org/protobuf/internal/encoding/text/decode.go
A vendor/google.golang.org/protobuf/internal/encoding/text/decode_number.go
A vendor/google.golang.org/protobuf/internal/encoding/text/decode_string.go
A vendor/google.golang.org/protobuf/internal/encoding/text/decode_token.go
A vendor/google.golang.org/protobuf/internal/encoding/text/doc.go
A vendor/google.golang.org/protobuf/internal/encoding/text/encode.go
A vendor/google.golang.org/protobuf/internal/errors/errors.go
A vendor/google.golang.org/protobuf/internal/errors/is_go112.go
A vendor/google.golang.org/protobuf/internal/errors/is_go113.go
A vendor/google.golang.org/protobuf/internal/fieldnum/any_gen.go
A vendor/google.golang.org/protobuf/internal/fieldnum/api_gen.go
A vendor/google.golang.org/protobuf/internal/fieldnum/descriptor_gen.go
A vendor/google.golang.org/protobuf/internal/fieldnum/doc.go
A vendor/google.golang.org/protobuf/internal/fieldnum/duration_gen.go
A vendor/google.golang.org/protobuf/internal/fieldnum/empty_gen.go
A vendor/google.golang.org/protobuf/internal/fieldnum/field_mask_gen.go
A vendor/google.golang.org/protobuf/internal/fieldnum/source_context_gen.go
A vendor/google.golang.org/protobuf/internal/fieldnum/struct_gen.go
A vendor/google.golang.org/protobuf/internal/fieldnum/timestamp_gen.go
A vendor/google.golang.org/protobuf/internal/fieldnum/type_gen.go
A vendor/google.golang.org/protobuf/internal/fieldnum/wrappers_gen.go
A vendor/google.golang.org/protobuf/internal/fieldsort/fieldsort.go
A vendor/google.golang.org/protobuf/internal/filedesc/build.go
A vendor/google.golang.org/protobuf/internal/filedesc/desc.go
A vendor/google.golang.org/protobuf/internal/filedesc/desc_init.go
A vendor/google.golang.org/protobuf/internal/filedesc/desc_lazy.go
A vendor/google.golang.org/protobuf/internal/filedesc/desc_list.go
A vendor/google.golang.org/protobuf/internal/filedesc/desc_list_gen.go
A vendor/google.golang.org/protobuf/internal/filedesc/placeholder.go
A vendor/google.golang.org/protobuf/internal/filetype/build.go
A vendor/google.golang.org/protobuf/internal/flags/flags.go
A vendor/google.golang.org/protobuf/internal/flags/proto_legacy_disable.go
A vendor/google.golang.org/protobuf/internal/flags/proto_legacy_enable.go
A vendor/google.golang.org/protobuf/internal/genname/name.go
A vendor/google.golang.org/protobuf/internal/impl/api_export.go
A vendor/google.golang.org/protobuf/internal/impl/checkinit.go
A vendor/google.golang.org/protobuf/internal/impl/codec_extension.go
A vendor/google.golang.org/protobuf/internal/impl/codec_field.go
A vendor/google.golang.org/protobuf/internal/impl/codec_gen.go
A vendor/google.golang.org/protobuf/internal/impl/codec_map.go
A vendor/google.golang.org/protobuf/internal/impl/codec_map_go111.go
A vendor/google.golang.org/protobuf/internal/impl/codec_map_go112.go
A vendor/google.golang.org/protobuf/internal/impl/codec_message.go
A vendor/google.golang.org/protobuf/internal/impl/codec_messageset.go
A vendor/google.golang.org/protobuf/internal/impl/codec_reflect.go
A vendor/google.golang.org/protobuf/internal/impl/codec_tables.go
A vendor/google.golang.org/protobuf/internal/impl/codec_unsafe.go
A vendor/google.golang.org/protobuf/internal/impl/convert.go
A vendor/google.golang.org/protobuf/internal/impl/convert_list.go
A vendor/google.golang.org/protobuf/internal/impl/convert_map.go
A vendor/google.golang.org/protobuf/internal/impl/decode.go
A vendor/google.golang.org/protobuf/internal/impl/encode.go
A vendor/google.golang.org/protobuf/internal/impl/enum.go
A vendor/google.golang.org/protobuf/internal/impl/extension.go
A vendor/google.golang.org/protobuf/internal/impl/legacy_enum.go
A vendor/google.golang.org/protobuf/internal/impl/legacy_export.go
A vendor/google.golang.org/protobuf/internal/impl/legacy_extension.go
A vendor/google.golang.org/protobuf/internal/impl/legacy_file.go
A vendor/google.golang.org/protobuf/internal/impl/legacy_message.go
A vendor/google.golang.org/protobuf/internal/impl/merge.go
A vendor/google.golang.org/protobuf/internal/impl/merge_gen.go
A vendor/google.golang.org/protobuf/internal/impl/message.go
A vendor/google.golang.org/protobuf/internal/impl/message_reflect.go
A vendor/google.golang.org/protobuf/internal/impl/message_reflect_field.go
A vendor/google.golang.org/protobuf/internal/impl/message_reflect_gen.go
A vendor/google.golang.org/protobuf/internal/impl/pointer_reflect.go
A vendor/google.golang.org/protobuf/internal/impl/pointer_unsafe.go
A vendor/google.golang.org/protobuf/internal/impl/validate.go
A vendor/google.golang.org/protobuf/internal/impl/weak.go
A vendor/google.golang.org/protobuf/internal/mapsort/mapsort.go
A vendor/google.golang.org/protobuf/internal/pragma/pragma.go
A vendor/google.golang.org/protobuf/internal/set/ints.go
A vendor/google.golang.org/protobuf/internal/strs/strings.go
A vendor/google.golang.org/protobuf/internal/strs/strings_pure.go
A vendor/google.golang.org/protobuf/internal/strs/strings_unsafe.go
A vendor/google.golang.org/protobuf/internal/version/version.go
A vendor/google.golang.org/protobuf/proto/checkinit.go
A vendor/google.golang.org/protobuf/proto/decode.go
A vendor/google.golang.org/protobuf/proto/decode_gen.go
A vendor/google.golang.org/protobuf/proto/doc.go
A vendor/google.golang.org/protobuf/proto/encode.go
A vendor/google.golang.org/protobuf/proto/encode_gen.go
A vendor/google.golang.org/protobuf/proto/equal.go
A vendor/google.golang.org/protobuf/proto/extension.go
A vendor/google.golang.org/protobuf/proto/merge.go
A vendor/google.golang.org/protobuf/proto/messageset.go
A vendor/google.golang.org/protobuf/proto/proto.go
A vendor/google.golang.org/protobuf/proto/proto_methods.go
A vendor/google.golang.org/protobuf/proto/proto_reflect.go
A vendor/google.golang.org/protobuf/proto/reset.go
A vendor/google.golang.org/protobuf/proto/size.go
A vendor/google.golang.org/protobuf/proto/size_gen.go
A vendor/google.golang.org/protobuf/proto/wrappers.go
A vendor/google.golang.org/protobuf/reflect/protoreflect/methods.go
A vendor/google.golang.org/protobuf/reflect/protoreflect/proto.go
A vendor/google.golang.org/protobuf/reflect/protoreflect/source.go
A vendor/google.golang.org/protobuf/reflect/protoreflect/type.go
A vendor/google.golang.org/protobuf/reflect/protoreflect/value.go
A vendor/google.golang.org/protobuf/reflect/protoreflect/value_pure.go
A vendor/google.golang.org/protobuf/reflect/protoreflect/value_union.go
A vendor/google.golang.org/protobuf/reflect/protoreflect/value_unsafe.go
A vendor/google.golang.org/protobuf/reflect/protoregistry/registry.go
A vendor/google.golang.org/protobuf/runtime/protoiface/legacy.go
A vendor/google.golang.org/protobuf/runtime/protoiface/methods.go
A vendor/google.golang.org/protobuf/runtime/protoimpl/impl.go
A vendor/google.golang.org/protobuf/runtime/protoimpl/version.go
M vendor/modules.txt
D cmd/blobstash-fs/blobstash-fs.go => cmd/blobstash-fs/blobstash-fs.go +0 -7
@@ 1,7 0,0 @@
package main

import "a4.io/blobstash/pkg/filetree/fs"

func main() {
	fs.Main()
}

M go.mod => go.mod +7 -7
@@ 6,7 6,7 @@ require (
	a4.io/gluarequire2 v0.0.0-20200222094423-7528d5a10bc1
	a4.io/go/indieauth v1.0.0
	a4.io/ssse v0.0.0-20181202155639-1949828a8689
	bazil.org/fuse v0.0.0-20200117225306-7b5117fecadc
	bazil.org/fuse v0.0.0-20200415052832-70bd89b671a2 // indirect
	github.com/alecthomas/chroma v0.7.2 // indirect
	github.com/aws/aws-sdk-go v1.30.7
	github.com/blevesearch/segment v0.9.0


@@ 16,7 16,7 @@ require (
	github.com/e3b0c442/warp v0.6.1
	github.com/evanphx/json-patch v4.5.0+incompatible
	github.com/fxamacker/cbor v1.5.1 // indirect
	github.com/golang/protobuf v1.3.5 // indirect
	github.com/golang/protobuf v1.4.0 // indirect
	github.com/golang/snappy v0.0.1
	github.com/gorilla/context v1.1.1
	github.com/gorilla/handlers v1.4.2


@@ 24,7 24,7 @@ require (
	github.com/gorilla/sessions v1.2.0
	github.com/hashicorp/golang-lru v0.5.4
	github.com/inconshreveable/log15 v0.0.0-20200109203555-b30bc20e4fd1
	github.com/mitchellh/go-ps v1.0.0
	github.com/mitchellh/go-ps v1.0.0 // indirect
	github.com/mvdan/xurls v1.1.0 // indirect
	github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
	github.com/reiver/go-porterstemmer v1.0.1


@@ 36,12 36,12 @@ require (
	github.com/unrolled/secure v1.0.7
	github.com/vmihailenco/msgpack v4.0.4+incompatible
	github.com/xeonx/timeago v1.0.0-rc4
	github.com/yuin/goldmark v1.1.28
	github.com/yuin/goldmark v1.1.29
	github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
	github.com/zpatrick/rbac v0.0.0-20180829190353-d2c4f050cf28
	golang.org/x/crypto v0.0.0-20200406173513-056763e48d71
	golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
	golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa // indirect
	golang.org/x/crypto v0.0.0-20200414173820-0848c9571904
	golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect
	golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 // indirect
	gopkg.in/inconshreveable/log15.v2 v2.0.0-20200109203555-b30bc20e4fd1
	gopkg.in/src-d/enry.v1 v1.7.3
	gopkg.in/src-d/go-git.v4 v4.13.1

M go.sum => go.sum +22 -0
@@ 40,6 40,8 @@ bazil.org/fuse v0.0.0-20180421153158-65cc252bf669 h1:FNCRpXiquG1aoyqcIWVFmpTSKVc
bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
bazil.org/fuse v0.0.0-20200117225306-7b5117fecadc h1:utDghgcjE8u+EBjHOgYT+dJPcnDF05KqWMBcjuJy510=
bazil.org/fuse v0.0.0-20200117225306-7b5117fecadc/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM=
bazil.org/fuse v0.0.0-20200415052832-70bd89b671a2 h1:BSFd7txKmf/El2sF/nCZvaNNm8/6FRF/tZtoTXWmv3Q=
bazil.org/fuse v0.0.0-20200415052832-70bd89b671a2/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM=
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=


@@ 177,6 179,12 @@ github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=


@@ 192,6 200,7 @@ github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=


@@ 428,6 437,8 @@ github.com/yuin/goldmark v1.1.25 h1:isv+Q6HQAmmL2Ofcmg8QauBmDPlUUnSoNhEcC940Rds=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.28 h1:3Ksz4BbKZVlaGbkXzHxoazZzASQKsfUuOZPr5CNxnC4=
github.com/yuin/goldmark v1.1.28/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.29 h1:NgInEI7XcFG1LV4mebFHBvGfgQs5Na9wrEkP6cBaAfc=
github.com/yuin/goldmark v1.1.29/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark-highlighting v0.0.0-20191202084645-78f32c8dd6d5 h1:QbH7ca1qtgZHrzvcVAEoiJIwBqrXxMOfHYfwZIniIK0=
github.com/yuin/goldmark-highlighting v0.0.0-20191202084645-78f32c8dd6d5/go.mod h1:4QGn5rJFOASBa2uK4Q2h3BRTyJqRfsAucPFIipSTcaM=
github.com/yuin/goldmark-highlighting v0.0.0-20200218065240-d1af22c1126f h1:5295skDVJn90SXIYI22jOMeR9XbnuN76y/V1m9N8ITQ=


@@ 461,6 472,8 @@ golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZ
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200406173513-056763e48d71 h1:DOmugCavvUtnUD114C1Wh+UgTgQZ4pMLzXxi1pSt+/Y=
golang.org/x/crypto v0.0.0-20200406173513-056763e48d71/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8=
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181213202711-891ebc4b82d6 h1:gT0Y6H7hbVPUtvtk0YGxMXPgN+p8fYlqWkgJeUCZcaQ=


@@ 530,16 543,25 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa h1:mQTN3ECqfsViCNBgq+A40vdwhkGykrrQlYe3mPj6BoU=
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

D pkg/filetree/fs/asof.go => pkg/filetree/fs/asof.go +0 -71
@@ 1,71 0,0 @@
package fs

import (
	"context"
	"fmt"
	"os"

	"a4.io/blobstash/pkg/asof"
	"bazil.org/fuse"
	"bazil.org/fuse/fs"
)

// atDir holds a magic "time travel" directory, by parsingd directory name as "asOf" on the fly
type atDir struct {
	fs *FS
}

var _ fs.Node = (*atDir)(nil)
var _ fs.HandleReadDirAller = (*atDir)(nil)
var _ fs.NodeStringLookuper = (*atDir)(nil)

// Attr implements the fs.Node
func (*atDir) Attr(ctx context.Context, a *fuse.Attr) error {
	a.Mode = os.ModeDir | 0555
	return nil
}

// Lookup implements the fs.NodeStringLookuper
func (a *atDir) Lookup(ctx context.Context, name string) (fs.Node, error) {
	if asof.IsValid(name) {
		asOf, err := asof.ParseAsOf(name)
		if err != nil {
			// TODO(tsileo): log the error
			return nil, fuse.ENOENT
		}

		fmt.Printf("asof=%v\n", asOf)
		cachedRoot, ok := a.fs.atCache.Get(asOf)
		if ok {
			return cachedRoot.(*dir), nil
		}

		root := &dir{
			path: "/",
			fs:   a.fs,
			node: nil,
			ro:   true,
			asOf: asOf,
		}

		// Actually loads it
		if err := root.preloadFTRoot(); err != nil {
			return nil, err
		}

		if root.node == nil {
			return nil, fuse.ENOENT
		}

		a.fs.atCache.Add(asOf, root)

		return root, nil
	}

	return nil, fuse.ENOENT
}

// ReadDirAll implements the fs.HandleReadDirAller interface
func (a *atDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
	return []fuse.Dirent{}, nil
}

D pkg/filetree/fs/cache.go => pkg/filetree/fs/cache.go +0 -92
@@ 1,92 0,0 @@
package fs

import (
	"fmt"
	"sync"

	bcache "a4.io/blobstash/pkg/cache"
	"a4.io/blobstash/pkg/client/blobstore"
	"golang.org/x/net/context"
)

// cache implements the blobStore interface with a local disk-backed LRU cache
type cache struct {
	fs *FS
	bs *blobstore.BlobStore
	mu sync.Mutex

	blobsCache *bcache.Cache
}

// newCache initializes a new cache instance
func newCache(fs *FS, bs *blobstore.BlobStore, path string) (*cache, error) {
	blobsCache, err := bcache.New(path, "blobs.cache", (5*1024)<<20) // 5GB on-disk LRU cache TODO(tsileo): make it configurable
	if err != nil {
		return nil, err
	}

	return &cache{
		fs:         fs,
		bs:         bs,
		blobsCache: blobsCache,
	}, nil
}

// Close implements the io.Closer interface
func (c *cache) Close() error {
	return c.blobsCache.Close()
}

// Stat implements the blobStore interface
func (c *cache) Stat(ctx context.Context, hash string) (bool, error) {
	c.mu.Lock()
	defer c.mu.Unlock()

	stat, err := c.bs.Stat(context.TODO(), hash)
	if err != nil {
		return false, err
	}

	return stat, nil
}

// Put implements the blobStore interface for filereader.File
func (c *cache) Put(ctx context.Context, hash string, data []byte) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	if err := c.bs.Put(ctx, hash, data); err != nil {
		return err
	}

	if err := c.blobsCache.Add(hash, data); err != nil {
		return err
	}
	return nil
}

// Get implements the blobStore interface for filereader.File
func (c *cache) Get(ctx context.Context, hash string) ([]byte, error) {
	logger.Printf("Cache.Get(%q)\n", hash)
	var err error
	cachedBlob, ok, err := c.blobsCache.Get(hash)
	if err != nil {
		return nil, fmt.Errorf("cache failed: %v", err)
	}
	var data []byte
	if ok {
		data = cachedBlob
	} else {
		c.mu.Lock()
		defer c.mu.Unlock()

		data, err = c.bs.Get(ctx, hash)
		if err != nil {
			return nil, fmt.Errorf("failed to call blobstore: %v", err)
		}
		if err := c.blobsCache.Add(hash, data); err != nil {
			return nil, fmt.Errorf("failed to add to cache: %v", err)
		}
	}
	return data, nil
}

D pkg/filetree/fs/config.go => pkg/filetree/fs/config.go +0 -52
@@ 1,52 0,0 @@
package fs

import (
	"fmt"
	"io/ioutil"
	"os"

	yaml "gopkg.in/yaml.v2"
)

// remoteConfig holds the "remote endpoint" configuration
type remoteConfig struct {
	Endpoint        string `yaml:"endpoint"`
	Region          string `yaml:"region"`
	Bucket          string `yaml:"bucket"`
	AccessKeyID     string `yaml:"access_key_id"`
	SecretAccessKey string `yaml:"secret_access_key"`
	KeyFile         string `yaml:"key_file"`
}

// profile holds a profile configuration
type profile struct {
	RemoteConfig *remoteConfig `yaml:"remote_config"`
	Endpoint     string        `yaml:"endpoint"`
	APIKey       string        `yaml:"api_key"`
}

// config holds config profiles
type config map[string]*profile

// loadProfile loads the config file and the given profile within it
func loadProfile(configFile, name string) (*profile, error) {
	dat, err := ioutil.ReadFile(configFile)
	switch {
	case err == nil:
	case os.IsNotExist(err):
		return nil, nil
	default:
		return nil, err
	}
	out := config{}
	if err := yaml.Unmarshal(dat, out); err != nil {
		return nil, err
	}

	prof, ok := out[name]
	if !ok {
		return nil, fmt.Errorf("profile %s not found", name)
	}

	return prof, nil
}

D pkg/filetree/fs/debug.go => pkg/filetree/fs/debug.go +0 -187
@@ 1,187 0,0 @@
package fs

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"strconv"
	"sync"
	"text/tabwriter"
	"time"

	"bazil.org/fuse"
	"bazil.org/fuse/fs"
	ps "github.com/mitchellh/go-ps"
	"golang.org/x/net/context"
)

var mu sync.Mutex
var pidCache = map[uint32]string{}

type fdDebug struct {
	FSType   string `json:"fs_type"`
	Path     string `json:"path"`
	PName    string `json:"pname"`
	PID      uint32 `json:"pid"`
	RW       bool   `json:"rw"`
	openedAt time.Time
	OpenedAt string `json:"opened_at"`
}

type dataNode struct {
	data []byte
	f    func() ([]byte, error)
}

func (d *dataNode) Attr(ctx context.Context, a *fuse.Attr) error {
	if d.f != nil {
		var err error
		d.data, err = d.f()
		if err != nil {
			return err
		}
	}
	a.Uid = uint32(os.Getuid())
	a.Gid = uint32(os.Getgid())
	a.Mode = 0644
	a.Size = uint64(len(d.data))
	return nil
}

func (d *dataNode) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
	if d.f != nil {
		var err error
		d.data, err = d.f()
		if err != nil {
			return nil, err
		}
	}

	resp.Flags |= fuse.OpenDirectIO
	return fs.DataHandle(d.data), nil
}

type counterNode struct {
	val int
}

func (c *counterNode) Attr(ctx context.Context, a *fuse.Attr) error {
	a.Uid = uint32(os.Getuid())
	a.Gid = uint32(os.Getgid())
	a.Mode = 0644
	a.Size = uint64(len(strconv.Itoa(c.val)))
	return nil
}

func (c *counterNode) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
	resp.Flags |= fuse.OpenDirectIO
	return fs.DataHandle([]byte(strconv.Itoa(c.val))), nil
}

type counters struct {
	Tree  *fs.Tree
	index map[string]*counterNode
	mu    sync.Mutex
}

func newCounters() *counters {
	return &counters{index: map[string]*counterNode{}, Tree: &fs.Tree{}}
}

func (c *counters) register(name string) {
	c.index[name] = &counterNode{}
	c.Tree.Add(name, c.index[name])
}

func (c *counters) incr(name string) {
	c.mu.Lock()
	c.index[name].val++
	c.mu.Unlock()
}

// Fetch the process name for the given PID
func getProcName(pid uint32) string {
	mu.Lock()
	defer mu.Unlock()

	if pname, ok := pidCache[pid]; ok {
		return pname
	}

	p, err := ps.FindProcess(int(pid))
	if err != nil {
		panic(err)
	}
	if p == nil {
		return "<unk>"
	}

	exec := p.Executable()
	pidCache[pid] = exec

	return exec
}

// build the magic .stats dir
func statsTree(cfs *FS) *fs.Tree {
	// Setup the counters
	cfs.counters.register("open")
	cfs.counters.register("open-ro")
	cfs.counters.register("open-ro-error")
	cfs.counters.register("open-rw")
	cfs.counters.register("open-rw-error")

	// Debug VFS mounted a /.stats
	statsTree := &fs.Tree{}
	statsTree.Add("started_at", &dataNode{data: []byte(startedAt.Format(time.RFC3339))})
	statsTree.Add("last_revision", &dataNode{f: func() ([]byte, error) {
		cfs.muLastRev.Lock()
		defer cfs.muLastRev.Unlock()

		return []byte(strconv.FormatInt(cfs.lastRevision, 10)), nil
	}})
	statsTree.Add("last_sync_revision", &dataNode{f: func() ([]byte, error) {
		cfs.muLastRev.Lock()
		defer cfs.muLastRev.Unlock()

		return []byte(strconv.FormatInt(cfs.lastSyncRev, 10)), nil
	}})

	statsTree.Add("fds.json", &dataNode{f: func() ([]byte, error) {
		for _, d := range cfs.openedFds {
			if d.OpenedAt == "" {
				d.OpenedAt = d.openedAt.Format(time.RFC3339)
			}
		}
		return json.Marshal(cfs.openedFds)
	}})
	statsTree.Add("fds", &dataNode{f: func() ([]byte, error) {
		var buf bytes.Buffer
		w := tabwriter.NewWriter(&buf, 0, 0, 1, ' ', tabwriter.TabIndent)
		for _, d := range cfs.openedFds {
			fmt.Fprintln(w, fmt.Sprintf("%s\t%d\t%s\t%v\t%s", d.Path, d.PID, d.PName, d.RW, d.openedAt.Format(time.RFC3339)))
		}
		w.Flush()
		return buf.Bytes(), nil
	}})
	statsTree.Add("open_logs", &dataNode{f: func() ([]byte, error) {
		var buf bytes.Buffer
		w := tabwriter.NewWriter(&buf, 0, 0, 1, ' ', tabwriter.TabIndent)
		for _, d := range cfs.openLogs {
			fmt.Fprintln(w, fmt.Sprintf("%s\t%d\t%s\t%v\t%s", d.Path, d.PID, d.PName, d.RW, d.openedAt.Format(time.RFC3339)))
		}
		w.Flush()
		return buf.Bytes(), nil
	}})
	statsTree.Add("open_logs.json", &dataNode{f: func() ([]byte, error) {
		for _, d := range cfs.openLogs {
			if d.OpenedAt == "" {
				d.OpenedAt = d.openedAt.Format(time.RFC3339)
			}
		}
		return json.Marshal(cfs.openLogs)
	}})
	statsTree.Add("counters", cfs.counters.Tree)
	return statsTree
}

D pkg/filetree/fs/fs.go => pkg/filetree/fs/fs.go +0 -1478
@@ 1,1478 0,0 @@
package fs // import "a4.io/blobstash/pkg/filetree/fs"

import (
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"

	"a4.io/blobstash/pkg/client/blobstore"
	"a4.io/blobstash/pkg/client/clientutil"
	"a4.io/blobstash/pkg/client/kvstore"
	"a4.io/blobstash/pkg/config/pathutil"
	"a4.io/blobstash/pkg/ctxutil"
	rnode "a4.io/blobstash/pkg/filetree/filetreeutil/node"
	"a4.io/blobstash/pkg/filetree/reader/filereader"
	"a4.io/blobstash/pkg/filetree/writer"
	"a4.io/blobstash/pkg/iputil"
	"github.com/aws/aws-sdk-go/service/s3"
	lru "github.com/hashicorp/golang-lru"

	"bazil.org/fuse"
	"bazil.org/fuse/fs"
	"golang.org/x/net/context"
)

var logger = log.New(os.Stderr, "BlobFS: ", log.LstdFlags)

const revisionHeader = "BlobStash-Filetree-FS-Revision"

var startedAt = time.Now()

func usage() {
	fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
	fmt.Fprintf(os.Stderr, "  %s MOUNTPOINT FSNAME\n", os.Args[0])
	flag.PrintDefaults()
}

// Permissions bits for mode manipulation (borrowed from https://github.com/phayes/permbits/blob/master/permbits.go#L10)
const (
	setuid uint32 = 1 << (12 - 1 - iota)
	setgid
	sticky
	userRead
	userWrite
	userExecute
	groupRead
	groupWrite
	groupExecute
	otherRead
	otherWrite
	otherExecute
)

func Main() {
	// Scans the arg list and sets up flags
	//debug := flag.Bool("debug", false, "print debugging messages.")
	resetCache := flag.Bool("reset-cache", false, "remove the local cache before starting.")
	syncDelay := flag.Duration("sync-delay", 5*time.Minute, "delay to wait after the last modification to initate a sync")
	configFile := flag.String("config-file", filepath.Join(pathutil.ConfigDir(), "fs_client.yaml"), "confg file path")
	configProfile := flag.String("config-profile", "default", "config profile name")

	flag.Usage = usage
	flag.Parse()

	if flag.NArg() != 2 {
		usage()
		os.Exit(2)
	}
	mountpoint := flag.Arg(0)
	ref := flag.Arg(1)

	profile, err := loadProfile(*configFile, *configProfile)
	if err != nil {
		fmt.Printf("failed to load config profile %s at %s: %v\n", *configProfile, *configFile, err)
		os.Exit(1)
	}

	if profile == nil {
		fmt.Printf("please setup a config file at %s\n", *configFile)
		os.Exit(1)
	}

	// Cache setup, follow XDG spec
	cacheDir := filepath.Join(pathutil.CacheDir(), "fs", fmt.Sprintf("%s_%s", mountpoint, ref))
	logger.Printf("cacheDir=%s\n", cacheDir)

	if _, err := os.Stat(cacheDir); err != nil {
		if os.IsNotExist(err) {
			if err := os.MkdirAll(cacheDir, 0700); err != nil {
				fmt.Printf("failed to create cache dir: %v\n", err)
				os.Exit(1)
			}
		}

	} else {
		if *resetCache {
			if err := os.RemoveAll(cacheDir); err != nil {
				fmt.Printf("failed to reset cache: %v\n", err)
				os.Exit(1)
			}
			if err := os.MkdirAll(cacheDir, 0700); err != nil {
				fmt.Printf("failed to re-create cache dir: %v\n", err)
				os.Exit(1)
			}
		}
	}

	// Setup the clients for BlobStash
	hostname, err := os.Hostname()
	if err != nil {
		fmt.Printf("failed to get hostname: %v\n", err)
		os.Exit(1)
	}

	clientUtil := clientutil.NewClientUtil(profile.Endpoint,
		clientutil.WithAPIKey(profile.APIKey),
		clientutil.WithHeader(ctxutil.FileTreeHostnameHeader, hostname),
		clientutil.WithHeader(ctxutil.NamespaceHeader, "rwfs-"+ref),
		clientutil.EnableMsgpack(),
		clientutil.EnableSnappyEncoding(),
	)

	bs := blobstore.New(clientUtil)
	kvs := kvstore.New(clientUtil)

	authOk, err := clientUtil.CheckAuth()
	if err != nil {
		fmt.Printf("failed to contact BlobStash: %v\n", err)
		os.Exit(1)
	}

	if !authOk {
		fmt.Printf("bad API key\n")
		os.Exit(1)
	}

	// Discover server capabilities (for the remote/replication stuff)
	caps, err := clientUtil.Capabilities()
	if err != nil {
		log.Fatal(err)
	}

	isHostLocal, err := iputil.IsPrivate(profile.Endpoint)
	if err != nil {
		fmt.Printf("invalid BLOBS_API_HOST")
		os.Exit(1)
	}
	fmt.Printf("isHostLocal=%v\n", isHostLocal)

	c, err := fuse.Mount(
		mountpoint,
		fuse.VolumeName(filepath.Base(mountpoint)),
		fuse.NoAppleDouble(),
		fuse.NoAppleXattr(),
		fuse.MaxReadahead(32*1024*1024),
	)
	if err != nil {
		log.Fatal(err)
	}
	defer c.Close()

	// LRU cache for "data blobs" when reading a file
	freaderCache, err := lru.New(512)
	if err != nil {
		log.Fatal(err)
	}

	atCache, err := lru.NewARC(32)
	if err != nil {
		log.Fatal(err)
	}

	blobfs := &FS{
		profile:      profile,
		up:           writer.NewUploader(bs),
		clientUtil:   clientUtil,
		kvs:          kvs,
		ref:          ref,
		counters:     newCounters(),
		openedFds:    map[fuse.NodeID]*fdDebug{},
		openLogs:     []*fdDebug{},
		freaderCache: freaderCache,
		atCache:      atCache,
		caps:         caps,
	}
	blobfs.bs, err = newCache(blobfs, bs, cacheDir)
	if err != nil {
		log.Fatal(err)
	}
	defer blobfs.bs.(*cache).Close()

	logger.Printf("caps=%+v\n", caps)

	go func() {
		ticker := time.NewTicker(45 * time.Second)
		for _ = range ticker.C {
			blobfs.muLastRev.Lock()
			currentRev := blobfs.lastRevision
			blobfs.muLastRev.Unlock()

			if currentRev > 0 && currentRev > blobfs.lastSyncRev {
				logger.Printf("sync ticker, current_rev=%d last_sync_rev=%d\n", currentRev, blobfs.lastSyncRev)
				if time.Now().UTC().Sub(time.Unix(0, currentRev)) > *syncDelay {
					if err := blobfs.GC(); err != nil {
						panic(err)
					}
				}
			}
		}
	}()

	go func() {
		err = fs.Serve(c, blobfs)
		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)
		}
	}()

	cs := make(chan os.Signal, 1)
	signal.Notify(cs, os.Interrupt,
		syscall.SIGINT,
		syscall.SIGTERM,
		syscall.SIGQUIT)
	<-cs
	logger.Printf("GC...")
	if err := blobfs.GC(); err != nil {
		fmt.Printf("\nGC failed err=%v\n", err)
	} else {
		fmt.Printf("done\n")
	}
	logger.Printf("Unmounting...\n")
	fuse.Unmount(mountpoint)
}

// blobStore is the blobstore client interface
type blobStore interface {
	Stat(ctx context.Context, hash string) (bool, error)
	Get(ctx context.Context, hash string) ([]byte, error)
	Put(ctx context.Context, hash string, data []byte) error
}

// FS implements the BlobStash FileTree filesystem
type FS struct {
	// BlobStash clients (and cache)
	up         *writer.Uploader
	kvs        *kvstore.KvStore
	bs         blobStore
	clientUtil *clientutil.ClientUtil
	caps       *clientutil.Caps

	// config profile
	profile *profile

	// S3 client and key
	s3  *s3.S3
	key *[32]byte

	// cached root dir
	ref    string
	ftRoot *dir

	// "magic" root
	root *fs.Tree

	// in-mem blobs cache
	freaderCache *lru.Cache

	// in-mem cache for RO snapshots of older versions
	atCache *lru.ARCCache

	// debug info
	counters  *counters
	openedFds map[fuse.NodeID]*fdDebug
	openLogs  []*fdDebug
	mu        sync.Mutex

	// current revision
	lastRevision int64
	lastSyncRev  int64
	muLastRev    sync.Mutex
}

// FIXME(tsileo): use it, and a ticker with a debounce
func (fs *FS) updateLastRevision(resp *http.Response) {
	fs.muLastRev.Lock()
	defer fs.muLastRev.Unlock()
	rev := resp.Header.Get(revisionHeader)
	if rev == "" {
		panic("missing FS revision in response")
	}
	var err error
	fs.lastRevision, err = strconv.ParseInt(rev, 10, 0)
	if err != nil {
		panic("invalid FS revision")
	}
}

// Save the current tree and reset the stash
func (fs *FS) GC() error {
	fs.muLastRev.Lock()
	defer fs.muLastRev.Unlock()

	if fs.lastRevision == 0 {
		return nil
	}

	gcScript := fmt.Sprintf(`
local kvstore = require('kvstore')
-- FS vkv ref
local key = "_filetree:fs:%s"

-- Do the "premark" step, to tell the GC which blobs are already in the root blobstore
local last_sync_version = "%d"
if last_sync_version ~= "0" then
  local _, last_ref, _ = kvstore.get(key, last_sync_version)
  premark_kv(key, last_sync_version)
  premark_filetree_node(last_ref)
end

-- Now, do the actual GC mark (and premarked blobs will be skipped)
local version = "%d"
local _, ref, _ = kvstore.get(key, version)

-- mark the actual KV entry
mark_kv(key, version)

-- mark the whole tree
mark_filetree_node(ref)
`, fs.ref, fs.lastSyncRev, fs.lastRevision)

	// FIXME(tsileo): make the stash name configurable
	resp, err := fs.clientUtil.PostMsgpack(
		fmt.Sprintf("/api/stash/rwfs-%s/_gc", fs.ref),
		map[string]interface{}{
			"script": gcScript,
		},
	)
	if err != nil {
		// FIXME(tsileo): find a better way to handle this?
		return err
	}
	defer resp.Body.Close()

	if err := clientutil.ExpectStatusCode(resp, http.StatusNoContent); err != nil {
		if err.IsNotFound() {
			// The stash does not exists, nothing to GC
			return nil
		}
		// FIXME(tsileo): find a better way to handle this?
		return err
	}

	fs.lastSyncRev = fs.lastRevision

	return nil
}

// remotePath the API path for the FileTree API
func (fs *FS) remotePath(path string) string {
	return fmt.Sprintf("/api/filetree/fs/fs/%s/%s", fs.ref, path[1:])
}

// getNode fetches the node at path from BlobStash, like a "remote stat".
func (fs *FS) getNode(path string, depth int, asOf int64) (*node, error) {
	return fs.getNodeAsOf(path, depth, asOf)
}

// getNode fetches the node at path from BlobStash, like a "remote stat".
func (fs *FS) getNodeAsOf(path string, depth int, asOf int64) (*node, error) {
	// Fetch the node via the FileTree FS API
	resp, err := fs.clientUtil.Get(
		fs.remotePath(path)+fmt.Sprintf("?depth=%d", depth),
		clientutil.WithQueryArg("as_of", strconv.FormatInt(asOf, 10)),
	)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if err := clientutil.ExpectStatusCode(resp, http.StatusOK); err != nil {
		if err.IsNotFound() {
			// Return nil as ENOENT
			return nil, nil
		}
		return nil, err
	}

	node := &node{}
	if err := clientutil.Unmarshal(resp, node); err != nil {
		return nil, err
	}

	node.AsOf = asOf
	node.Revision, err = strconv.ParseInt(resp.Header.Get("BlobStash-FileTree-Revision"), 10, 0)
	if err != nil {
		return nil, err
	}
	// fmt.Printf("getNode(%s) = %v\n", fs.remotePath(path), node)

	return node, nil
}

// Root returns the root node of the FS
func (cfs *FS) Root() (fs.Node, error) {
	// Check if there's a cached root
	if cfs.root != nil {
		return cfs.root, nil
	}

	// Create a dummy dir that will be our root ref
	cfs.root = &fs.Tree{}

	// the read-write root will be mounted a /current
	ftRoot, err := cfs.FTRoot()
	if err != nil {
		return nil, err
	}
	cfs.root.Add("current", ftRoot)
	fmt.Printf("cfs=%+v\n", cfs)

	// magic dir that list all versions, YYYY-MM-DDTHH:MM:SS
	cfs.root.Add("versions", &versionsDir{cfs})

	// Time travel magic dir: "at/2018/myfile", "at/-40h/myfile"
	cfs.root.Add("at", &atDir{cfs})

	// Last 100 opened files (locally tracked only)
	cfs.root.Add("recent", &recentDir{cfs, &cfs.openLogs})

	// Debug VFS mounted a /.stats
	cfs.root.Add(".stats", statsTree(cfs))

	return cfs.root, nil
}

// FTRoot returns the FileTree node root
func (fs *FS) FTRoot() (fs.Node, error) {
	// Check if there's a cached root
	if fs.ftRoot != nil {
		return fs.ftRoot, nil
	}

	// Create a dummy dir that will be our root ref
	fs.ftRoot = &dir{
		path: "/",
		fs:   fs,
		node: nil,
	}

	// Actually loads it
	if err := fs.ftRoot.preloadFTRoot(); err != nil {
		return nil, err
	}
	return fs.ftRoot, nil
}

// dir implements fs.Node and represents a FileTree directory
type dir struct {
	path string
	fs   *FS
	node *node

	ro   bool
	asOf int64

	mu       sync.Mutex
	children map[string]fs.Node
	parent   *dir
}

var _ fs.Node = (*dir)(nil)
var _ fs.NodeMkdirer = (*dir)(nil)
var _ fs.NodeCreater = (*dir)(nil)
var _ fs.NodeRemover = (*dir)(nil)
var _ fs.HandleReadDirAller = (*dir)(nil)
var _ fs.NodeStringLookuper = (*dir)(nil)
var _ fs.NodeListxattrer = (*dir)(nil)
var _ fs.NodeGetxattrer = (*dir)(nil)

// FTNode lazy-loads the node from BlobStash FileTree API
func (d *dir) FTNode() (*node, error) {
	d.mu.Lock()
	defer d.mu.Unlock()
	//	if d.node != nil {
	//		return d.node, nil
	//	}
	n, err := d.fs.getNode(d.path, 1, d.asOf)
	if err != nil {
		return nil, err
	}
	fmt.Printf("fetch dir node=%+v\n", n)
	d.node = n
	return n, nil
}

// Listxattr implements the fs.NodeListxattrer interface
func (d *dir) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error {
	logger.Printf("Listxattr %s", d.path)
	// TODO(tsileo): node info + metadata support
	resp.Append([]string{"debug.ref"}...)
	return nil
}

// Getxattr implements the fs.NodeGetxattrer interface
func (d *dir) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error {
	logger.Printf("Getxattr %s", d.path)
	switch req.Name {
	case "debug.ref":
		resp.Xattr = []byte(d.node.Ref)
	default:
		return fuse.ErrNoXattr
	}
	return nil
}

// Attr implements the fs.Node interface
func (d *dir) Attr(ctx context.Context, a *fuse.Attr) error {
	logger.Printf("Attr %s", d.path)
	n, err := d.FTNode()
	if err != nil {
		return err
	}
	a.Valid = 0 * time.Second
	a.Uid = uint32(os.Getuid())
	a.Gid = uint32(os.Getgid())

	if d.path == "/" {
		a.Inode = 1
	}
	if n != nil {
		a.Mode = os.ModeDir | os.FileMode(n.mode())
	} else {
		a.Mode = os.ModeDir | 0755
	}

	if d.ro || d.asOf > 0 {
		a.Mode &^= os.FileMode(userWrite | groupWrite | otherWrite)
	}

	return nil
}

// Special preloading for the root that fetch the root tree with a depth of 2
// (meaning we fetch the directories of the directories inside the root).
func (d *dir) preloadFTRoot() error {
	d.mu.Lock()
	defer d.mu.Unlock()

	// Fetch the root node with a depth=2
	n, err := d.fs.getNodeAsOf(d.path, 2, d.asOf)
	if err != nil {
		return err
	}
	// Cache the node
	d.node = n
	if d.node == nil {
		return nil
	}

	d.children = map[string]fs.Node{}
	for _, child := range d.node.Children {
		child.AsOf = d.asOf
		// We can set the node directly, and directories will contains children because we asked
		// for a depth=2 when requesting the root dir
		if child.isFile() {
			d.children[child.Name] = &file{
				path:   filepath.Join(d.path, child.Name),
				fs:     d.fs,
				node:   child,
				parent: d,
				ro:     d.ro,
			}
		} else {
			d.children[child.Name] = &dir{
				path:   filepath.Join(d.path, child.Name),
				fs:     d.fs,
				node:   child,
				parent: d,
				ro:     d.ro,
			}
			// "load"/setup the children index, as we already have the children within the node
			d.children[child.Name].(*dir).loadChildren()
		}
	}

	d.fs.lastSyncRev = n.Revision
	return nil
}

// Load the children from the FileTree node to the fs.Node children index used for lookups and readdiralls
func (d *dir) loadChildren() {
	d.children = map[string]fs.Node{}
	for _, child := range d.node.Children {
		child.AsOf = d.asOf
		if child.isFile() {
			d.children[child.Name] = &file{
				path:   filepath.Join(d.path, child.Name),
				fs:     d.fs,
				node:   child,
				parent: d,
				ro:     d.ro,
			}
		} else {
			// The node is set to nil for directories because we haven't fetched to children
			d.children[child.Name] = &dir{
				path:   filepath.Join(d.path, child.Name),
				fs:     d.fs,
				node:   nil,
				parent: d,
				ro:     d.ro,
			}
		}
	}

}

// Lookup implements the fs.NodeRequestLookuper interface
func (d *dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
	// lazy load the remote node
	n, err := d.FTNode()
	if err != nil {
		return nil, err
	}
	if n == nil {
		return nil, fuse.ENOENT
	}

	// fetch the children (local index)
	if d.children == nil {
		d.loadChildren()
	}

	// update the index
	d.mu.Lock()
	defer d.mu.Unlock()
	if node, ok := d.children[name]; ok {
		return node, nil
	}

	return nil, fuse.ENOENT
}

// ReadDirAll implements the fs.HandleReadDirAller interface
func (d *dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
	// lazy loads the remote node
	n, err := d.FTNode()
	if err != nil {
		return nil, err
	}
	if n == nil {
		return nil, fuse.ENOENT
	}
	// load the children (local index)
	if d.children == nil {
		d.loadChildren()
	}

	// Build the response
	d.mu.Lock()
	defer d.mu.Unlock()
	out := []fuse.Dirent{}
	for _, child := range d.children {
		if f, ok := child.(*file); ok {
			out = append(out, fuse.Dirent{Name: filepath.Base(f.path), Type: fuse.DT_File})
		} else {
			d := child.(*dir)
			out = append(out, fuse.Dirent{Name: filepath.Base(d.path), Type: fuse.DT_Dir})

		}
	}

	return out, nil
}

// Mkdir implements the fs.NodeMkdirer interface
func (d *dir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) {
	if d.ro || d.asOf > 0 {
		return nil, fuse.EPERM
	}

	// new mtime for the parent dir
	mtime := time.Now().Unix()

	// initialize an empty dir node
	node := &rnode.RawNode{
		Version: rnode.V1,
		Type:    rnode.Dir,
		Name:    req.Name,
		ModTime: mtime,
	}

	// patch dir to insert the new empty dir
	resp, err := d.fs.clientUtil.PatchMsgpack(
		d.fs.remotePath(d.path),
		node,
		clientutil.WithQueryArg("mtime", strconv.FormatInt(mtime, 10)),
	)
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	if err := clientutil.ExpectStatusCode(resp, http.StatusOK); err != nil {
		return nil, err
	}

	// Update the FS "revision" (the kv entry version) for later GC
	d.fs.updateLastRevision(resp)

	// initialize the FS node and update the local dir
	newDir := &dir{path: filepath.Join(d.path, req.Name), fs: d.fs, node: nil, parent: d}
	d.mu.Lock()
	if d.children == nil {
		d.children = map[string]fs.Node{}
	}
	d.children[req.Name] = newDir
	d.mu.Unlock()

	return newDir, nil
}

// Rename implements the fs.NodeRenamer interface
func (d *dir) Rename(ctx context.Context, req *fuse.RenameRequest, newDir fs.Node) error {
	if d.ro || d.asOf > 0 {
		return fuse.EPERM
	}

	// mtime for the modifications
	mtime := time.Now().Unix()

	d.mu.Lock()
	n := d.children[req.OldName]
	d.mu.Unlock()

	// First, we remove the old path
	resp, err := d.fs.clientUtil.Delete(
		d.fs.remotePath(filepath.Join(d.path, req.OldName)),
		clientutil.WithQueryArg("mtime", strconv.FormatInt(mtime, 10)),
	)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if err := clientutil.ExpectStatusCode(resp, http.StatusNoContent); err != nil {
		return err
	}

	newPath := filepath.Join(newDir.(*dir).path, req.NewName)
	fmt.Printf("NewName=%s\n", newPath)

	var ref string
	if d, ok := n.(*dir); ok {
		ref = d.node.Ref

	} else {
		f := n.(*file)
		ref = f.node.Ref
	}

	// Next, we re-add it to its dest
	resp, err = d.fs.clientUtil.PatchMsgpack(
		d.fs.remotePath(newDir.(*dir).path),
		nil,
		clientutil.WithHeaders(map[string]string{
			"BlobStash-Filetree-Patch-Ref":  ref,
			"BlobStash-Filetree-Patch-Name": filepath.Base(newPath),
		}),
		clientutil.WithQueryArgs(map[string]string{
			// FIXME(tsileo): s/rename/change/ ?
			"rename": strconv.FormatBool(true),
			"mtime":  strconv.Itoa(int(mtime)),
		}),
	)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if err := clientutil.ExpectStatusCode(resp, http.StatusOK); err != nil {
		return err
	}

	// Update the FS "revision" (the kv entry version) for later GC
	d.fs.updateLastRevision(resp)

	d.mu.Lock()
	delete(d.children, req.OldName)
	d.mu.Unlock()
	if d, ok := n.(*dir); ok {
		d.path = newPath
		d.node = nil
		if _, err := d.FTNode(); err != nil {
			return err
		}
	} else {
		f := n.(*file)
		f.path = newPath
		f.node = nil
		if _, err := f.FTNode(); err != nil {
			return err
		}
	}
	d2 := newDir.(*dir)
	d2.mu.Lock()
	d2.children[req.NewName] = n
	d2.mu.Unlock()

	fmt.Printf("Rename done, new node=%+v\n", n)
	return nil
}

// Create implements the fs.NodeCreater interface
func (d *dir) Create(ctx context.Context, req *fuse.CreateRequest, res *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
	if d.ro || d.asOf > 0 {
		return nil, nil, fuse.EPERM
	}

	d.fs.counters.incr("open")
	d.fs.counters.incr("open-rw")

	// mtime for the parent dir
	mtime := time.Now().Unix()

	// Initialize an empty file node
	node := &rnode.RawNode{
		Type:    rnode.File,
		Name:    req.Name,
		Version: rnode.V1,
		ModTime: mtime,
		Mode:    uint32(req.Mode),
	}

	// Patch the parent dir
	resp, err := d.fs.clientUtil.PatchMsgpack(
		d.fs.remotePath(d.path),
		node,
		clientutil.WithQueryArg("mtime", strconv.FormatInt(mtime, 10)),
	)
	if err != nil {
		d.fs.counters.incr("open-rw-error")
		return nil, nil, err
	}
	defer resp.Body.Close()

	if err := clientutil.ExpectStatusCode(resp, http.StatusOK); err != nil {
		d.fs.counters.incr("open-rw-error")
		return nil, nil, err
	}

	// Update the FS "revision" (the kv entry version) for later GC
	d.fs.updateLastRevision(resp)

	// Initialize the file node
	f := &file{
		path:   filepath.Join(d.path, req.Name),
		fs:     d.fs,
		node:   nil,
		parent: d,
	}

	// Update the local dir
	d.mu.Lock()
	if d.children == nil {
		d.children = map[string]fs.Node{}
	}
	d.children[req.Name] = f
	f.fds++

	d.fs.openedFds[req.Node] = &fdDebug{
		Path:     d.path,
		PID:      req.Pid,
		PName:    getProcName(req.Pid),
		RW:       true,
		openedAt: time.Now(),
	}
	d.fs.openLogs = append(d.fs.openLogs, d.fs.openedFds[req.Node])
	if len(d.fs.openLogs) > 100 {
		d.fs.openLogs = d.fs.openLogs[:100]
	}
	d.mu.Unlock()

	// Initialize a temporary file for the RW handle
	tmp, err := ioutil.TempFile("", fmt.Sprintf("blobfs-%s-", req.Name))
	if err != nil {
		d.fs.counters.incr("open-rw-error")
		return nil, nil, err
	}

	// Initialize the RW handle
	fh := &rwFileHandle{
		f:   f,
		tmp: tmp,
	}
	f.h = fh

	return f, fh, nil
}

// Remove implements the fs.NodeRemover interface
func (d *dir) Remove(ctx context.Context, req *fuse.RemoveRequest) error {
	if d.ro || d.asOf > 0 {
		return fuse.EPERM
	}

	// mtime for the parent dir
	mtime := time.Now().Unix()

	// Remove the node from the dir in the index server/BlobStash
	resp, err := d.fs.clientUtil.Delete(
		d.fs.remotePath(filepath.Join(d.path, req.Name)),
		clientutil.WithQueryArg("mtime", strconv.FormatInt(mtime, 10)),
	)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if err := clientutil.ExpectStatusCode(resp, http.StatusNoContent); err != nil {
		fmt.Printf("err=%+v\n", err)
		return err
	}

	// Update the FS "revision" (the kv entry version) for later GC
	d.fs.updateLastRevision(resp)

	// Update the local node
	d.mu.Lock()
	delete(d.children, req.Name)
	d.mu.Unlock()

	return nil
}

// file implements both Node and Handle for the hello file.
type file struct {
	// absolute path
	path string

	// read-only mode
	ro   bool
	asOf int64

	// FS ref
	fs *FS

	// FileTree node
	node *node

	// Node parent
	parent *dir

	// Guard the rw handle and the file descriptor count
	mu sync.Mutex

	// Keep track of the opened file descriptors
	fds int
	h   *rwFileHandle
}

var _ fs.Node = (*file)(nil)
var _ fs.NodeAccesser = (*file)(nil)
var _ fs.NodeSetattrer = (*file)(nil)
var _ fs.NodeOpener = (*file)(nil)
var _ fs.NodeFsyncer = (*file)(nil)
var _ fs.NodeListxattrer = (*file)(nil)
var _ fs.NodeGetxattrer = (*file)(nil)

// Fsync implements the fs.NodeFsyncer interface
func (f *file) Fsync(ctx context.Context, req *fuse.FsyncRequest) error {
	return nil
}

// FTNode lazy-loads the node from BlobStash FileTree API
func (f *file) FTNode() (*node, error) {
	f.mu.Lock()
	defer f.mu.Unlock()

	// Returns the cached node if it's already there
	if f.node != nil {
		return f.node, nil
	}

	// Loads it from BlobStash
	n, err := f.fs.getNode(f.path, 1, f.asOf)
	if err != nil {
		return nil, err
	}

	// Cache it
	f.node = n
	return n, nil
}

// Attr implements the fs.Node interface
func (f *file) Attr(ctx context.Context, a *fuse.Attr) error {
	n, err := f.FTNode()
	if err != nil {
		return err
	}
	a.Valid = 0 * time.Second
	a.Uid = uint32(os.Getuid())
	a.Gid = uint32(os.Getgid())
	a.BlockSize = 4096

	if f.h != nil {
		fi, err := f.h.tmp.Stat()
		if err != nil {
			return err
		}
		a.Mode = fi.Mode()
		a.Size = uint64(fi.Size())
		a.Mtime = fi.ModTime()
		a.Ctime = fi.ModTime()
	} else {

		// a.Inode = 2
		if n != nil {
			a.Mode = os.FileMode(n.mode()) | 0644
			a.Size = uint64(n.Size)
			a.Mtime = time.Unix(int64(n.mtime()), 0)
			a.Ctime = time.Unix(int64(n.ctime()), 0)
		} else {
			a.Mode = 0644
			a.Size = 0
		}
	}
	if a.Size > 0 {
		a.Blocks = a.Size/512 + 1
	}

	// the file is read-only, remove the write bits
	if f.ro || f.asOf > 0 {
		a.Mode &^= os.FileMode(userWrite | groupWrite | otherWrite)
	}

	return nil
}

// Listxattr implements the fs.NodeListxattrer interface
func (f *file) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error {
	// Node metadata (stored in the node/meta itself)
	for k, _ := range f.node.Metadata {
		resp.Append(fmt.Sprintf("metadata.%s", k))
	}
	// Node info (video)
	if v, vok := f.node.Info["video"]; vok {
		for k, _ := range v.(map[string]interface{}) {
			resp.Append(fmt.Sprintf("info.video.%s", k))
		}
	}
	// Node info (image)
	if v, vok := f.node.Info["image"]; vok {
		for k, _ := range v.(map[string]interface{}) {
			resp.Append(fmt.Sprintf("info.image.%s", k))
		}
	}
	// Node debug
	resp.Append([]string{"debug.ref"}...)
	return nil
}

// Getxattr implements the fs.NodeGetxattrer interface
func (f *file) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error {
	switch {
	case req.Name == "debug.ref":
		resp.Xattr = []byte(f.node.Ref)
	case strings.HasPrefix(req.Name, "metadata."):
		resp.Xattr = []byte(fmt.Sprintf("%v", f.node.Metadata[req.Name[9:]]))
	case strings.HasPrefix(req.Name, "info.video."):
		resp.Xattr = []byte(fmt.Sprintf("%v", f.node.Info["video"].(map[string]interface{})[req.Name[11:]]))
	case strings.HasPrefix(req.Name, "info.image."):
		resp.Xattr = []byte(fmt.Sprintf("%v", f.node.Info["image"].(map[string]interface{})[req.Name[11:]]))
	default:
		return fuse.ErrNoXattr
	}
	return nil
}

// Access implements the fs.NodeAccesser interface
func (f *file) Access(ctx context.Context, req *fuse.AccessRequest) error {
	return nil
}

// Setattr implements the fs.NodeSetattrer
func (f *file) Setattr(ctx context.Context, req *fuse.SetattrRequest, res *fuse.SetattrResponse) error {
	if f.ro || f.asOf > 0 {
		return fuse.EPERM
	}

	n, err := f.FTNode()
	if err != nil {
		return err
	}
	if n == nil {

	} else {

		mtime := time.Now().Unix()
		headers := map[string]string{
			"BlobStash-Filetree-Patch-Ref": n.Ref,
		}
		if req.Valid&fuse.SetattrMtime != 0 {
			mtime = req.Mtime.Unix()
		}
		//if req.Valid&fuse.SetattrAtime != 0 {
		//	n.atime = req.Atime
		//}
		if req.Valid&fuse.SetattrMode != 0 {
			headers["BlobStash-Filetree-Patch-Mode"] = strconv.Itoa(int(req.Mode))
		}

		resp, err := f.fs.clientUtil.PatchMsgpack(
			f.fs.remotePath(filepath.Dir(f.path)),
			nil,
			clientutil.WithQueryArgs(map[string]string{
				"mtime": strconv.Itoa(int(mtime)),
			}),
			clientutil.WithHeaders(headers),
		)
		if err != nil {
			return err
		}

		defer resp.Body.Close()

		if err := clientutil.ExpectStatusCode(resp, http.StatusOK); err != nil {
			return err
		}

		// Update the FS "revision" (the kv entry version) for later GC
		f.fs.updateLastRevision(resp)

	}
	// TODO(tsileo): apply the attrs to the temp file
	f.Attr(ctx, &res.Attr)
	return nil
}

// Open implements the fs.HandleOpener interface
func (f *file) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
	fmt.Printf("Open %v %+v %s write=%v\n", f, f.node, f.path, req.Flags&fuse.OpenFlags(os.O_WRONLY|os.O_RDWR|os.O_APPEND|os.O_CREATE) != 0)
	fmt.Printf("current handler=%+v\n", f.h)

	isRW := req.Flags&fuse.OpenFlags(os.O_WRONLY|os.O_RDWR|os.O_APPEND|os.O_CREATE) != 0
	if (f.ro || f.asOf > 0) && isRW {
		return nil, fuse.EPERM
	}

	f.fs.counters.incr("open")
	if isRW {
		f.fs.counters.incr("open-rw")
	} else {
		f.fs.counters.incr("open-ro")
	}

	// Update the opened file descriptor counter
	f.fds++
	f.fs.mu.Lock()
	f.fs.openedFds[req.Node] = &fdDebug{
		Path:     f.path,
		PID:      req.Pid,
		PName:    getProcName(req.Pid),
		RW:       isRW,
		openedAt: time.Now(),
	}
	f.fs.openLogs = append([]*fdDebug{f.fs.openedFds[req.Node]}, f.fs.openLogs...)
	if len(f.fs.openLogs) > 100 {
		f.fs.openLogs = f.fs.openLogs[:100]
	}
	f.fs.mu.Unlock()

	// Short circuit the open if this file is already open for write
	if f.h != nil {
		fmt.Printf("Returning already openfile\n")
		return f.h, nil
	}

	// Lazy loads the remote node if needed
	if _, err := f.FTNode(); err != nil {
		if isRW {
			f.fs.counters.incr("open-rw-error")
		} else {
			f.fs.counters.incr("open-ro-error")
		}

		return nil, err
	}

	// Open RW
	if req.Flags&fuse.OpenFlags(os.O_WRONLY|os.O_RDWR|os.O_APPEND|os.O_CREATE) != 0 {
		// Create a temporary file
		tmp, err := ioutil.TempFile("", fmt.Sprintf("blobfs-%s-", filepath.Base(f.path)))
		if err != nil {
			f.fs.counters.incr("open-rw-error")
			return nil, err
		}

		// Initialize a reader for initializing/loading the node content into the temp file
		r, err := f.Reader()
		if err != nil {
			f.fs.counters.incr("open-rw-error")
			return nil, err
		}

		// Copy the reader into the temp file if needed
		f.mu.Lock()
		defer f.mu.Unlock()

		if r != nil {
			defer r.Close()

			if _, err := io.Copy(tmp, r); err != nil {
				f.fs.counters.incr("open-rw-error")
				return nil, err
			}
		}

		// Initialize the RW handler
		rwHandle := &rwFileHandle{
			f:   f,
			tmp: tmp,
		}
		f.h = rwHandle
		return rwHandle, nil
	}

	f.mu.Lock()
	defer f.mu.Unlock()

	if f.node == nil {
		return nil, fuse.ENOENT
	}

	var r fileReader
	if f.h != nil {
		r = f.h.tmp
	}

	// Initialize a RO handle
	fh := &fileHandle{
		f: f,
		r: r,
	}
	resp.Flags |= fuse.OpenKeepCache
	return fh, nil
}

// fileReader is the minimal interface for the file hander
type fileReader interface {
	io.Reader
	io.ReaderAt
	io.Closer
}

type preloadableFileReader interface {
	fileReader
	PreloadChunks()
}

// fileHandle implements a RO file handler
type fileHandle struct {
	f *file
	r fileReader
}

var _ fs.HandleReader = (*fileHandle)(nil)
var _ fs.HandleReleaser = (*fileHandle)(nil)

// Reader returns a fileReader for the remote node
func (f *file) Reader() (fileReader, error) {
	// Fetch the remote node
	n, err := f.FTNode()
	if err != nil {
		return nil, err
	}
	if n == nil {
		return nil, nil
	}

	// Fetch the reference blob to decode the "raw meta"
	blob, err := f.fs.bs.Get(context.Background(), n.Ref)
	if err != nil {
		return nil, err
	}
	meta, err := rnode.NewNodeFromBlob(n.Ref, blob)
	if err != nil {
		return nil, fmt.Errorf("failed to build node from blob \"%s\": %v", blob, err)
	}

	// Instanciate the filereader
	fr := filereader.NewFile(context.Background(), f.fs.bs, meta, f.fs.freaderCache)

	// FIXME(tsileo): test if preloading is worth it
	// fr.PreloadChunks()

	return fr, nil
}

// Release implements the fs.HandleReleaser interface
func (fh *fileHandle) Release(ctx context.Context, req *fuse.ReleaseRequest) error {
	fh.f.mu.Lock()
	defer fh.f.mu.Unlock()

	// Close the reader if it was opened
	if fh.r != nil {
		fh.r.Close()
		fh.r = nil
	}

	// Update the opened file descriptor counter
	// TODO(tsileo): release the rwFileHandler here too if it was used?
	fh.f.fds--
	fh.f.fs.mu.Lock()
	if _, ok := fh.f.fs.openedFds[req.Node]; ok {
		delete(fh.f.fs.openedFds, req.Node)
	}
	fh.f.fs.mu.Unlock()

	return nil
}

// Read implements the fs.HandleReader interface
func (fh *fileHandle) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
	logger.Printf("Read [ro] %s: %#v", fh.f.path, req)
	var err error
	var r fileReader
	if fh.f.h != nil {
		// Short circuit the read operation to the RW handle
		r = fh.f.h.tmp
	} else {
		// Shortcut for empty file
		if fh.f.node.Size == 0 {
			return nil
		}

		// Lazy-loads the reader
		if fh.r != nil {
			r = fh.r
		} else {
			r, err = fh.f.Reader()
			if err != nil {
				return err
			}
			fh.r = r
		}
	}

	// No reader, the file was just created
	if r == nil {
		return nil
	}

	// Perform the read operation on the fileReader
	buf := make([]byte, req.Size)
	n, err := r.ReadAt(buf, req.Offset)
	if err != nil {
		return err
	}
	resp.Data = buf[:n]
	return nil
}

// rwFileHandle implements a RW file handler
type rwFileHandle struct {
	f *file

	tmp *os.File
}

var _ fs.HandleFlusher = (*rwFileHandle)(nil)
var _ fs.HandleReader = (*rwFileHandle)(nil)
var _ fs.HandleWriter = (*rwFileHandle)(nil)
var _ fs.HandleReleaser = (*rwFileHandle)(nil)

// Read implements the fs.HandleReader interface
func (f *rwFileHandle) Read(ctx context.Context, req *fuse.ReadRequest, res *fuse.ReadResponse) error {
	buf := make([]byte, req.Size)
	n, err := f.tmp.ReadAt(buf, req.Offset)

	switch err {
	case nil:
	case io.EOF:
		err = nil
	default:
		return err
	}

	res.Data = buf[:n]
	return nil
}

// Write implements the fs.HandleWriter interface
func (f *rwFileHandle) Write(ctx context.Context, req *fuse.WriteRequest, res *fuse.WriteResponse) error {
	n, err := f.tmp.WriteAt(req.Data, req.Offset)
	if err != nil {
		return err
	}
	res.Size = n
	return nil
}

// Flush implements the fs.HandleFlusher interface
func (f *rwFileHandle) Flush(ctx context.Context, req *fuse.FlushRequest) error {
	// Upload the file
	f.f.mu.Lock()
	rawNode, err := f.f.fs.up.PutFileRename(f.tmp.Name(), filepath.Base(f.f.path), true)
	if err != nil {
		return nil
	}
	f.f.mu.Unlock()

	// Patch the parent dir
	resp, err := f.f.fs.clientUtil.PatchMsgpack(
		f.f.fs.remotePath(filepath.Dir(f.f.path)),
		rawNode,
		clientutil.WithQueryArgs(map[string]string{
			"mtime": strconv.Itoa(int(rawNode.ModTime)),
		}))
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if err := clientutil.ExpectStatusCode(resp, http.StatusOK); err != nil {
		return err
	}

	// Update the FS "revision" (the kv entry version) for later GC
	f.f.fs.updateLastRevision(resp)

	// Reset the cached FileTree node
	f.f.node = nil
	if _, err := f.f.FTNode(); err != nil {
		return err
	}

	return nil
}

// Release implements the fuse.HandleReleaser interface
func (f *rwFileHandle) Release(ctx context.Context, req *fuse.ReleaseRequest) error {
	f.f.mu.Lock()
	defer f.f.mu.Unlock()

	f.f.fds--
	f.f.fs.mu.Lock()
	if _, ok := f.f.fs.openedFds[req.Node]; ok {
		delete(f.f.fs.openedFds, req.Node)
	}
	f.f.fs.mu.Unlock()

	if f.f.fds == 0 {
		f.tmp.Close()
		if err := os.Remove(f.tmp.Name()); err != nil {
			return err
		}
	}
	f.f.h = nil
	return nil
}

D pkg/filetree/fs/node.go => pkg/filetree/fs/node.go +0 -82
@@ 1,82 0,0 @@
package fs

import (
	"time"

	rnode "a4.io/blobstash/pkg/filetree/filetreeutil/node"
)

// node represents a FileTree node
type node struct {
	Name       string                 `json:"name" msgpack:"n"`
	Ref        string                 `json:"ref" msgpack:"r"`
	Size       int                    `json:"size" msgpack:"s,omitempty"`
	Type       string                 `json:"type" msgpack:"t"`
	Children   []*node                `json:"children" msgpack:"c,omitempty"`
	Metadata   map[string]interface{} `json:"metadata" msgpack:"md,omitempty"`
	ModTime    string                 `json:"mtime" msgpack:"mt"`
	ChangeTime string                 `json:"ctime" msgpack:"ct"`
	RawMode    int                    `json:"mode" msgpack:"mo"`
	RemoteRefs []*rnode.IndexValue    `json:"remote_refs,omitempty" msgpack:"rrfs,omitempty"`
	Info       map[string]interface{} `json:"info,omitempty" msgpack:"i,omitempty"`

	// Set by the FS
	AsOf     int64 `json:"-" msgpack:"-"`
	Revision int64 `json:"-" msgpack:"-"`
}

// mode returns the node file mode
func (n *node) mode() uint32 {
	// TODO(tsileo): handle asOf
	if n.RawMode > 0 {
		return uint32(n.RawMode)
	}
	if n.Type == rnode.File {
		return 0644
	} else {
		return 0755
	}
}

// hash returns the file content hash (blake2b)
func (n *node) hash() string {
	if len(n.Metadata) == 0 {
		// It happens for empty file
		return "69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9"
	}
	return n.Metadata["blake2b-hash"].(string)
}

// isDir returns true if the node is a dir
func (n *node) isDir() bool {
	return n.Type == rnode.Dir
}

// is File returns true if the node is a file
func (n *node) isFile() bool {
	return n.Type == rnode.File
}

// mtime returns the node mtime timestsamp
func (n *node) mtime() uint64 {
	if n.ModTime != "" {
		t, err := time.Parse(time.RFC3339, n.ModTime)
		if err != nil {
			panic(err)
		}
		return uint64(t.Unix())
	}
	return 0
}

// ctime returns the node ctime timestamp
func (n *node) ctime() uint64 {
	if n.ChangeTime != "" {
		t, err := time.Parse(time.RFC3339, n.ChangeTime)
		if err != nil {
			panic(err)
		}
		return uint64(t.Unix())
	}
	return 0
}

D pkg/filetree/fs/recent.go => pkg/filetree/fs/recent.go +0 -52
@@ 1,52 0,0 @@
package fs

import (
	"context"
	"os"
	"path/filepath"

	"bazil.org/fuse"
	"bazil.org/fuse/fs"
)

// recentDir implements a magic dir that list the last 100 files recently opened
type recentDir struct {
	fs  *FS
	dat *[]*fdDebug
}

var _ fs.Node = (*recentDir)(nil)
var _ fs.HandleReadDirAller = (*recentDir)(nil)
var _ fs.NodeStringLookuper = (*recentDir)(nil)

// Attr implements the fs.Node interface
func (r *recentDir) Attr(ctx context.Context, a *fuse.Attr) error {
	a.Mode = os.ModeDir | 0555
	return nil
}

// Lookup implements the fs.NodeStringLookuper interface
func (r *recentDir) Lookup(ctx context.Context, name string) (fs.Node, error) {
	for _, d := range *r.dat {
		if filepath.Base(d.Path) == name {
			return &file{
				path:   d.Path,
				fs:     r.fs,
				node:   nil,
				parent: nil,
				ro:     true,
			}, nil
		}
	}

	return nil, fuse.ENOENT
}

// ReadDirAll implements the fs.HandleReadDirAller
func (r *recentDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
	var out []fuse.Dirent
	for _, d := range *r.dat {
		out = append(out, fuse.Dirent{Type: fuse.DT_File, Name: filepath.Base(d.Path)})
	}
	return out, nil
}

D pkg/filetree/fs/versions.go => pkg/filetree/fs/versions.go +0 -117
@@ 1,117 0,0 @@
package fs

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"time"

	"a4.io/blobstash/pkg/asof"
	"a4.io/blobstash/pkg/client/clientutil"
	"a4.io/blobstash/pkg/ctxutil"
	"bazil.org/fuse"
	"bazil.org/fuse/fs"
)

const vfmt = "2006-01-02T15:04:05"

// versionsDir holds a magic dir that list versions/snapshots
type versionsDir struct {
	fs *FS
}

var _ fs.Node = (*versionsDir)(nil)
var _ fs.HandleReadDirAller = (*versionsDir)(nil)
var _ fs.NodeStringLookuper = (*versionsDir)(nil)

// Attr implements the fs.Node interface
func (*versionsDir) Attr(ctx context.Context, a *fuse.Attr) error {
	a.Mode = os.ModeDir | 0555
	return nil
}

// Lookup implements the fs.NodeStringLookuper interface
func (a *versionsDir) Lookup(ctx context.Context, name string) (fs.Node, error) {
	if asof.IsValid(name) {
		asOf, err := asof.ParseAsOf(name)
		if err != nil {
			return nil, err
		}

		cachedRoot, ok := a.fs.atCache.Get(asOf)
		if ok {
			return cachedRoot.(*dir), nil
		}

		root := &dir{
			path: "/",
			fs:   a.fs,
			node: nil,
			ro:   true,
			asOf: asOf,
		}

		// Actually loads it
		if err := root.preloadFTRoot(); err != nil {
			return nil, err
		}

		if root.node == nil {
			return nil, fuse.ENOENT
		}

		a.fs.atCache.Add(asOf, root)

		return root, nil
	}

	return nil, fuse.ENOENT
}

type version struct {
	Ref       string `json:"ref"`
	CreatedAt int64  `json:"created_at"`
}

type versionsResp struct {
	Versions []*version `json:"versions"`
}

func (v *versionsDir) load() (*versionsResp, error) {
	resp, err := v.fs.clientUtil.Get(
		fmt.Sprintf("/api/filetree/versions/fs/%s", v.fs.ref),
		clientutil.EnableJSON(),                            // XXX  this endpoint does not support msgpack
		clientutil.WithHeader(ctxutil.NamespaceHeader, ""), // Disable the stash/namespace
	)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if err := clientutil.ExpectStatusCode(resp, http.StatusOK); err != nil {
		return nil, err
	}

	res := &versionsResp{}
	if err := clientutil.Unmarshal(resp, res); err != nil {
		return nil, err
	}

	return res, nil
}

// ReadDirAll implements the fs.HandleReadDirAller interface
func (v *versionsDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
	out := []fuse.Dirent{}
	resp, err := v.load()
	if err != nil {
		return nil, err
	}

	for _, v := range resp.Versions {
		out = append(out, fuse.Dirent{Type: fuse.DT_Dir, Name: time.Unix(0, v.CreatedAt).Format(vfmt)})
	}

	return out, nil
}

D vendor/bazil.org/fuse/.gitattributes => vendor/bazil.org/fuse/.gitattributes +0 -2
@@ 1,2 0,0 @@
*.go filter=gofmt
*.cgo filter=gofmt

D vendor/bazil.org/fuse/.gitignore => vendor/bazil.org/fuse/.gitignore +0 -11
@@ 1,11 0,0 @@
*~
.#*
## the next line needs to start with a backslash to avoid looking like
## a comment
\#*#
.*.swp

*.test

/clockfs
/hellofs

D vendor/bazil.org/fuse/LICENSE => vendor/bazil.org/fuse/LICENSE +0 -93
@@ 1,93 0,0 @@
Copyright (c) 2013-2019 Tommi Virtanen.
Copyright (c) 2009, 2011, 2012 The Go Authors.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

   * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
   * Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
   * Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.



The following included software components have additional copyright
notices and license terms that may differ from the above.


File fuse.go:

// Adapted from Plan 9 from User Space's src/cmd/9pfuse/fuse.c,
// which carries this notice:
//
// The files in this directory are subject to the following license.
//
// The author of this software is Russ Cox.
//
//         Copyright (c) 2006 Russ Cox
//
// Permission to use, copy, modify, and distribute this software for any
// purpose without fee is hereby granted, provided that this entire notice
// is included in all copies of any software which is or includes a copy
// or modification of this software and in all copies of the supporting
// documentation for such software.
//
// THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
// WARRANTY.  IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION OR WARRANTY
// OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS SOFTWARE OR ITS
// FITNESS FOR ANY PARTICULAR PURPOSE.


File fuse_kernel.go:

// Derived from FUSE's fuse_kernel.h
/*
   This file defines the kernel interface of FUSE
   Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>


   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.
*/

D vendor/bazil.org/fuse/README.md => vendor/bazil.org/fuse/README.md +0 -23
@@ 1,23 0,0 @@
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/bazil/fuse

API docs: http://godoc.org/bazil.org/fuse

Our thanks to Russ Cox for his fuse library, which this project is
based on.

D vendor/bazil.org/fuse/buffer.go => vendor/bazil.org/fuse/buffer.go +0 -35
@@ 1,35 0,0 @@
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
}

D vendor/bazil.org/fuse/debug.go => vendor/bazil.org/fuse/debug.go +0 -21
@@ 1,21 0,0 @@
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

D vendor/bazil.org/fuse/error_darwin.go => vendor/bazil.org/fuse/error_darwin.go +0 -17
@@ 1,17 0,0 @@
package fuse

import (
	"syscall"
)

const (
	ENOATTR = Errno(syscall.ENOATTR)
)

const (
	errNoXattr = ENOATTR
)

func init() {
	errnoNames[errNoXattr] = "ENOATTR"
}

D vendor/bazil.org/fuse/error_freebsd.go => vendor/bazil.org/fuse/error_freebsd.go +0 -15
@@ 1,15 0,0 @@
package fuse

import "syscall"

const (
	ENOATTR = Errno(syscall.ENOATTR)
)

const (
	errNoXattr = ENOATTR
)

func init() {
	errnoNames[errNoXattr] = "ENOATTR"
}

D vendor/bazil.org/fuse/error_linux.go => vendor/bazil.org/fuse/error_linux.go +0 -17
@@ 1,17 0,0 @@
package fuse

import (
	"syscall"
)

const (
	ENODATA = Errno(syscall.ENODATA)
)

const (
	errNoXattr = ENODATA
)

func init() {
	errnoNames[errNoXattr] = "ENODATA"
}

D vendor/bazil.org/fuse/error_std.go => vendor/bazil.org/fuse/error_std.go +0 -31
@@ 1,31 0,0 @@
package fuse

// There is very little commonality in extended attribute errors
// across platforms.
//
// getxattr return value for "extended attribute does not exist" is
// ENOATTR on OS X, and ENODATA on Linux and apparently at least
// NetBSD. There may be a #define ENOATTR on Linux too, but the value
// is ENODATA in the actual syscalls. FreeBSD and OpenBSD have no
// ENODATA, only ENOATTR. ENOATTR is not in any of the standards,
// ENODATA exists but is only used for STREAMs.
//
// Each platform will define it a errNoXattr constant, and this file
// will enforce that it implements the right interfaces and hide the
// implementation.
//
// https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/getxattr.2.html
// http://mail-index.netbsd.org/tech-kern/2012/04/30/msg013090.html
// http://mail-index.netbsd.org/tech-kern/2012/04/30/msg013097.html
// http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html
// http://www.freebsd.org/cgi/man.cgi?query=extattr_get_file&sektion=2
// http://nixdoc.net/man-pages/openbsd/man2/extattr_get_file.2.html

// ErrNoXattr is a platform-independent error value meaning the
// extended attribute was not found. It can be used to respond to
// GetxattrRequest and such.
const ErrNoXattr = errNoXattr

var _ error = ErrNoXattr
var _ Errno = ErrNoXattr
var _ ErrorNumber = ErrNoXattr

D vendor/bazil.org/fuse/fs/serve.go => vendor/bazil.org/fuse/fs/serve.go +0 -1562
@@ 1,1562 0,0 @@
// FUSE service loop, for servers that wish to use it.

package fs // import "bazil.org/fuse/fs"

import (
	"bytes"
	"context"
	"encoding/binary"
	"fmt"
	"hash/fnv"
	"io"
	"log"
	"reflect"
	"runtime"
	"strings"
	"sync"
	"syscall"
	"time"

	"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 FSStatfser.
type FS interface {
	// Root is called to obtain the Node for the file system root.
	Root() (Node, error)
}

type FSStatfser interface {
	// Statfs is called to obtain file system metadata.
	// It should write that data to resp.
	Statfs(ctx context.Context, req *fuse.StatfsRequest, resp *fuse.StatfsResponse) error
}

type FSDestroyer interface {
	// Destroy is called when the file system is shutting down.
	//
	// Linux only sends this request for block device backed (fuseblk)
	// filesystems, to allow them to flush writes to disk before the
	// unmount completes.
	Destroy()
}

type FSInodeGenerator interface {
	// GenerateInode is called to pick a dynamic inode number when it
	// would otherwise be 0.
	//
	// Not all filesystems bother tracking inodes, but FUSE requires
	// the inode to be set, and fewer duplicates in general makes UNIX
	// tools work better.
	//
	// Operations where the nodes may return 0 inodes include Getattr,
	// Setattr and ReadDir.
	//
	// If FS does not implement FSInodeGenerator, GenerateDynamicInode
	// is used.
	//
	// Implementing this is useful to e.g. constrain the range of
	// inode values used for dynamic inodes.
	//
	// Non-zero return values should be greater than 1, as that is
	// always used for the root inode.
	GenerateInode(parentInode uint64, name string) uint64
}

// A Node is the interface required of a file or directory.
// See the documentation for type FS for general information
// pertaining to all methods.
//
// A Node must be usable as a map key, that is, it cannot be a
// function, map or slice.
//
// Other FUSE requests can be handled by implementing methods from the
// Node* interfaces, for example NodeOpener.
//
// Methods returning Node should take care to return the same Node
// when the result is logically the same instance. Without this, each
// Node will get a new NodeID, causing spurious cache invalidations,
// extra lookups and aliasing anomalies. This may not matter for a
// simple, read-only filesystem.
type Node interface {
	// Attr fills attr with the standard metadata for the node.
	//
	// Fields with reasonable defaults are prepopulated. For example,
	// all times are set to a fixed moment when the program started.
	//
	// If Inode is left as 0, a dynamic inode number is chosen.
	//
	// The result may be cached for the duration set in Valid.
	Attr(ctx context.Context, attr *fuse.Attr) error
}

type NodeGetattrer interface {
	// Getattr obtains the standard metadata for the receiver.
	// It should store that metadata in resp.
	//
	// If this method is not implemented, the attributes will be
	// generated based on Attr(), with zero values filled in.
	Getattr(ctx context.Context, req *fuse.GetattrRequest, resp *fuse.GetattrResponse) error
}

type NodeSetattrer interface {
	// Setattr sets the standard metadata for the receiver.
	//
	// Note, this is also used to communicate changes in the size of
	// the file, outside of Writes.
	//
	// req.Valid is a bitmask of what fields are actually being set.
	// For example, the method should not change the mode of the file
	// unless req.Valid.Mode() is true.
	Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error
}

type NodeSymlinker interface {
	// Symlink creates a new symbolic link in the receiver, which must be a directory.
	//
	// TODO is the above true about directories?
	Symlink(ctx context.Context, req *fuse.SymlinkRequest) (Node, error)
}

// This optional request will be called only for symbolic link nodes.
type NodeReadlinker interface {
	// Readlink reads a symbolic link.
	Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error)
}

type NodeLinker interface {
	// Link creates a new directory entry in the receiver based on an
	// existing Node. Receiver must be a directory.
	Link(ctx context.Context, req *fuse.LinkRequest, old Node) (Node, error)
}

type NodeRemover interface {
	// Remove removes the entry with the given name from
	// the receiver, which must be a directory.  The entry to be removed
	// may correspond to a file (unlink) or to a directory (rmdir).
	Remove(ctx context.Context, req *fuse.RemoveRequest) error
}

type NodeAccesser interface {
	// Access checks whether the calling context has permission for
	// the given operations on the receiver. If so, Access should
	// return nil. If not, Access should return EPERM.
	//
	// Note that this call affects the result of the access(2) system
	// call but not the open(2) system call. If Access is not
	// implemented, the Node behaves as if it always returns nil
	// (permission granted), relying on checks in Open instead.
	Access(ctx context.Context, req *fuse.AccessRequest) error
}

type NodeStringLookuper interface {
	// Lookup looks up a specific entry in the receiver,
	// which must be a directory.  Lookup should return a Node
	// corresponding to the entry.  If the name does not exist in
	// the directory, Lookup should return ENOENT.
	//
	// Lookup need not to handle the names "." and "..".
	Lookup(ctx context.Context, name string) (Node, error)
}

type NodeRequestLookuper interface {
	// Lookup looks up a specific entry in the receiver.
	// See NodeStringLookuper for more.
	Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (Node, error)
}

type NodeMkdirer interface {
	Mkdir(ctx context.Context, req *fuse.MkdirRequest) (Node, error)
}

type NodeOpener interface {
	// Open opens the receiver. After a successful open, a client
	// process has a file descriptor referring to this Handle.
	//
	// Open can also be also called on non-files. For example,
	// directories are Opened for ReadDir or fchdir(2).
	//
	// If this method is not implemented, the open will always
	// succeed, and the Node itself will be used as the Handle.
	//
	// XXX note about access.  XXX OpenFlags.
	Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (Handle, error)
}

type NodeCreater interface {
	// Create creates a new directory entry in the receiver, which
	// must be a directory.
	Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (Node, Handle, error)
}

type NodeForgetter interface {
	// Forget about this node. This node will not receive further
	// method calls.
	//
	// Forget is not necessarily seen on unmount, as all nodes are
	// implicitly forgotten as part part of the unmount.
	Forget()
}

type NodeRenamer interface {
	Rename(ctx context.Context, req *fuse.RenameRequest, newDir Node) error
}

type NodeMknoder interface {
	Mknod(ctx context.Context, req *fuse.MknodRequest) (Node, error)
}

// TODO this should be on Handle not Node
type NodeFsyncer interface {
	Fsync(ctx context.Context, req *fuse.FsyncRequest) error
}

type NodeGetxattrer interface {
	// Getxattr gets an extended attribute by the given name from the
	// node.
	//
	// If there is no xattr by that name, returns fuse.ErrNoXattr.
	Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error
}

type NodeListxattrer interface {
	// Listxattr lists the extended attributes recorded for the node.
	Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error
}

type NodeSetxattrer interface {
	// Setxattr sets an extended attribute with the given name and
	// value for the node.
	Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error
}

type NodeRemovexattrer interface {
	// Removexattr removes an extended attribute for the name.
	//
	// If there is no xattr by that name, returns fuse.ErrNoXattr.
	Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) error
}

var startTime = time.Now()

func nodeAttr(ctx context.Context, n Node, attr *fuse.Attr) error {
	attr.Valid = attrValidTime
	attr.Nlink = 1
	attr.Atime = startTime
	attr.Mtime = startTime
	attr.Ctime = startTime
	attr.Crtime = startTime
	if err := n.Attr(ctx, attr); err != nil {
		return err
	}
	return nil
}

// A Handle is the interface required of an opened file or directory.
// See the documentation for type FS for general information
// pertaining to all methods.
//
// Other FUSE requests can be handled by implementing methods from the
// Handle* interfaces. The most common to implement are HandleReader,
// HandleReadDirer, and HandleWriter.
//
// TODO implement methods: Getlk, Setlk, Setlkw
type Handle interface {
}

type HandleFlusher interface {
	// Flush is called each time the file or directory is closed.
	// Because there can be multiple file descriptors referring to a
	// single opened file, Flush can be called multiple times.
	Flush(ctx context.Context, req *fuse.FlushRequest) error
}

type HandleReadAller interface {
	ReadAll(ctx context.Context) ([]byte, error)
}

type HandleReadDirAller interface {
	ReadDirAll(ctx context.Context) ([]fuse.Dirent, error)
}

type HandleReader interface {
	// Read requests to read data from the handle.
	//
	// There is a page cache in the kernel that normally submits only
	// page-aligned reads spanning one or more pages. However, you
	// should not rely on this. To see individual requests as
	// submitted by the file system clients, set OpenDirectIO.
	//
	// Note that reads beyond the size of the file as reported by Attr
	// are not even attempted (except in OpenDirectIO mode).
	Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error
}

type HandleWriter interface {
	// Write requests to write data into the handle at the given offset.
	// Store the amount of data written in resp.Size.
	//
	// There is a writeback page cache in the kernel that normally submits
	// only page-aligned writes spanning one or more pages. However,
	// you should not rely on this. To see individual requests as
	// submitted by the file system clients, set OpenDirectIO.
	//
	// Writes that grow the file are expected to update the file size
	// (as seen through Attr). Note that file size changes are
	// communicated also through Setattr.
	Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error
}

type HandleReleaser interface {
	Release(ctx context.Context, req *fuse.ReleaseRequest) error
}

type Config struct {
	// Function to send debug log messages to. If nil, use fuse.Debug.
	// Note that changing this or fuse.Debug may not affect existing
	// calls to Serve.
	//
	// See fuse.Debug for the rules that log functions must follow.
	Debug func(msg interface{})

	// Function to put things into context for processing the request.
	// The returned context must have ctx as its parent.
	//
	// Note that changing this may not affect existing calls to Serve.
	//
	// Must not retain req.
	WithContext func(ctx context.Context, req fuse.Request) context.Context
}

// New returns a new FUSE server ready to serve this kernel FUSE
// connection.
//
// Config may be nil.
func New(conn *fuse.Conn, config *Config) *Server {
	s := &Server{
		conn:         conn,
		req:          map[fuse.RequestID]*serveRequest{},
		nodeRef:      map[Node]fuse.NodeID{},
		dynamicInode: GenerateDynamicInode,
	}
	if config != nil {
		s.debug = config.Debug
		s.context = config.WithContext
	}
	if s.debug == nil {
		s.debug = fuse.Debug
	}
	return s
}

type Server struct {
	// set in New
	conn    *fuse.Conn
	debug   func(msg interface{})
	context func(ctx context.Context, req fuse.Request) context.Context

	// set once at Serve time
	fs           FS
	dynamicInode func(parent uint64, name string) uint64

	// state, protected by meta
	meta       sync.Mutex
	req        map[fuse.RequestID]*serveRequest
	node       []*serveNode
	nodeRef    map[Node]fuse.NodeID
	handle     []*serveHandle
	freeNode   []fuse.NodeID
	freeHandle []fuse.HandleID
	nodeGen    uint64

	// Used to ensure worker goroutines finish before Serve returns
	wg sync.WaitGroup
}

// Serve serves the FUSE connection by making calls to the methods
// of fs and the Nodes and Handles it makes available.  It returns only
// when the connection has been closed or an unexpected error occurs.
func (s *Server) Serve(fs FS) error {
	defer s.wg.Wait() // Wait for worker goroutines to complete before return

	s.fs = fs
	if dyn, ok := fs.(FSInodeGenerator); ok {
		s.dynamicInode = dyn.GenerateInode
	}

	root, err := fs.Root()
	if err != nil {
		return fmt.Errorf("cannot obtain root node: %v", err)
	}
	// Recognize the root node if it's ever returned from Lookup,
	// passed to Invalidate, etc.
	s.nodeRef[root] = 1
	s.node = append(s.node, nil, &serveNode{
		inode:      1,
		generation: s.nodeGen,
		node:       root,
		refs:       1,
	})
	s.handle = append(s.handle, nil)

	for {
		req, err := s.conn.ReadRequest()
		if err != nil {
			if err == io.EOF {
				break
			}
			return err
		}

		s.wg.Add(1)
		go func() {
			defer s.wg.Done()
			s.serve(req)
		}()
	}
	return nil
}

// Serve serves a FUSE connection with the default settings. See
// Server.Serve.
func Serve(c *fuse.Conn, fs FS) error {
	server := New(c, nil)
	return server.Serve(fs)
}

type serveRequest struct {
	Request fuse.Request
	cancel  func()
}

type serveNode struct {
	inode      uint64
	generation uint64
	node       Node
	refs       uint64

	// Delay freeing the NodeID until waitgroup is done. This allows
	// using the NodeID for short periods of time without holding the
	// Server.meta lock.
	//
	// Rules:
	//
	//     - hold Server.meta while calling wg.Add, then unlock
	//     - do NOT try to reacquire Server.meta
	wg sync.WaitGroup
}

func (sn *serveNode) attr(ctx context.Context, attr *fuse.Attr) error {
	err := nodeAttr(ctx, sn.node, attr)
	if attr.Inode == 0 {
		attr.Inode = sn.inode
	}
	return err
}

type serveHandle struct {
	handle   Handle
	readData []byte
}

// 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{}

func (c *Server) saveNode(inode uint64, node Node) (id fuse.NodeID, gen uint64) {
	c.meta.Lock()
	defer c.meta.Unlock()

	if id, ok := c.nodeRef[node]; ok {
		sn := c.node[id]
		sn.refs++
		return id, sn.generation
	}

	sn := &serveNode{inode: inode, node: node, refs: 1}
	if n := len(c.freeNode); n > 0 {
		id = c.freeNode[n-1]
		c.freeNode = c.freeNode[:n-1]
		c.node[id] = sn
		c.nodeGen++
	} else {
		id = fuse.NodeID(len(c.node))
		c.node = append(c.node, sn)
	}
	sn.generation = c.nodeGen
	c.nodeRef[node] = id
	return id, sn.generation
}

func (c *Server) saveHandle(handle Handle) (id fuse.HandleID) {
	c.meta.Lock()
	shandle := &serveHandle{handle: handle}
	if n := len(c.freeHandle); n > 0 {
		id = c.freeHandle[n-1]
		c.freeHandle = c.freeHandle[:n-1]
		c.handle[id] = shandle
	} else {
		id = fuse.HandleID(len(c.handle))
		c.handle = append(c.handle, shandle)
	}
	c.meta.Unlock()
	return
}

type nodeRefcountDropBug struct {
	N    uint64
	Refs uint64
	Node fuse.NodeID
}

func (n *nodeRefcountDropBug) String() string {
	return fmt.Sprintf("bug: trying to drop %d of %d references to %v", n.N, n.Refs, n.Node)
}

func (c *Server) 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 {
		snode.wg.Wait()
		c.node[id] = nil
		delete(c.nodeRef, snode.node)
		c.freeNode = append(c.freeNode, id)
		return true
	}
	return false
}

func (c *Server) dropHandle(id fuse.HandleID) {
	c.meta.Lock()
	c.handle[id] = nil
	c.freeHandle = append(c.freeHandle, id)
	c.meta.Unlock()
}

type missingHandle struct {
	Handle    fuse.HandleID
	MaxHandle fuse.HandleID
}

func (m missingHandle) String() string {
	return fmt.Sprint("missing handle: ", m.Handle, m.MaxHandle)
}

// Returns nil for invalid handles.
func (c *Server) getHandle(id fuse.HandleID) (shandle *serveHandle) {
	c.meta.Lock()
	defer c.meta.Unlock()
	if id < fuse.HandleID(len(c.handle)) {
		shandle = c.handle[uint(id)]
	}
	if shandle == nil {
		c.debug(missingHandle{
			Handle:    id,
			MaxHandle: fuse.HandleID(len(c.handle)),
		})
	}
	return
}

type request struct {
	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=%v", m.ID)
}

type response struct {
	Op      string
	Request logResponseHeader
	Out     interface{} `json:",omitempty"`
	// Errno contains the errno value as a string, for example "EPERM".
	Errno string `json:",omitempty"`
	// Error may contain a free form error message.
	Error string `json:",omitempty"`
}

func (r response) errstr() string {
	s := r.Errno
	if r.Error != "" {
		// prefix the errno constant to the long form message
		s = s + ": " + r.Error
	}
	return s
}

func (r response) String() string {
	switch {
	case r.Errno != "" && r.Out != nil:
		return fmt.Sprintf("-> [%v] %v error=%s", r.Request, r.Out, r.errstr())
	case r.Errno != "":
		return fmt.Sprintf("-> [%v] %s error=%s", r.Request, r.Op, r.errstr())
	case r.Out != nil:
		// make sure (seemingly) empty values are readable
		switch r.Out.(type) {
		case string:
			return fmt.Sprintf("-> [%v] %s %q", r.Request, r.Op, r.Out)
		case []byte:
			return fmt.Sprintf("-> [%v] %s [% x]", r.Request, r.Op, r.Out)
		default:
			return fmt.Sprintf("-> [%v] %v", r.Request, r.Out)
		}
	default:
		return fmt.Sprintf("-> [%v] %s", r.Request, r.Op)
	}
}

type notification struct {
	Op   string
	Node fuse.NodeID
	Out  interface{} `json:",omitempty"`
	Err  string      `json:",omitempty"`
}

func (n notification) String() string {
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "=> %s %v", n.Op, n.Node)
	if n.Out != nil {
		// make sure (seemingly) empty values are readable
		switch n.Out.(type) {
		case string:
			fmt.Fprintf(&buf, " %q", n.Out)
		case []byte:
			fmt.Fprintf(&buf, " [% x]", n.Out)
		default:
			fmt.Fprintf(&buf, " %s", n.Out)
		}
	}
	if n.Err != "" {
		fmt.Fprintf(&buf, " Err:%v", n.Err)
	}
	return buf.String()
}

type logMissingNode struct {
	MaxNode fuse.NodeID
}

func opName(req fuse.Request) string {
	t := reflect.Indirect(reflect.ValueOf(req)).Type()
	s := t.Name()
	s = strings.TrimSuffix(s, "Request")
	return s
}

type logLinkRequestOldNodeNotFound struct {
	Request *fuse.Header
	In      *fuse.LinkRequest
}

func (m *logLinkRequestOldNodeNotFound) String() string {
	return fmt.Sprintf("In LinkRequest (request %v), node %d not found", m.Request.Hdr().ID, m.In.OldNode)
}

type renameNewDirNodeNotFound struct {
	Request *fuse.Header
	In      *fuse.RenameRequest
}

func (m *renameNewDirNodeNotFound) String() string {
	return fmt.Sprintf("In RenameRequest (request %v), node %d not found", m.Request.Hdr().ID, m.In.NewDir)
}

type handlerPanickedError struct {
	Request interface{}
	Err     interface{}
}

var _ error = handlerPanickedError{}

func (h handlerPanickedError) Error() string {
	return fmt.Sprintf("handler panicked: %v", h.Err)
}

var _ fuse.ErrorNumber = handlerPanickedError{}

func (h handlerPanickedError) Errno() fuse.Errno {
	if err, ok := h.Err.(fuse.ErrorNumber); ok {
		return err.Errno()
	}
	return fuse.DefaultErrno
}

// handlerTerminatedError happens when a handler terminates itself
// with runtime.Goexit. This is most commonly because of incorrect use
// of testing.TB.FailNow, typically via t.Fatal.
type handlerTerminatedError struct {
	Request interface{}
}

var _ error = handlerTerminatedError{}

func (h handlerTerminatedError) Error() string {
	return fmt.Sprintf("handler terminated (called runtime.Goexit)")
}

var _ fuse.ErrorNumber = handlerTerminatedError{}

func (h handlerTerminatedError) Errno() fuse.Errno {
	return fuse.DefaultErrno
}

type handleNotReaderError struct {
	handle Handle
}

var _ error = handleNotReaderError{}

func (e handleNotReaderError) Error() string {
	return fmt.Sprintf("handle has no Read: %T", e.handle)
}

var _ fuse.ErrorNumber = handleNotReaderError{}

func (e handleNotReaderError) Errno() fuse.Errno {
	return fuse.Errno(syscall.ENOTSUP)
}

func initLookupResponse(s *fuse.LookupResponse) {
	s.EntryValid = entryValidTime
}

func (c *Server) serve(r fuse.Request) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	parentCtx := ctx
	if c.context != nil {
		ctx = c.context(ctx, r)
	}

	req := &serveRequest{Request: r, cancel: cancel}

	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()
			err := syscall.ESTALE
			c.debug(response{
				Op:      opName(r),
				Request: logResponseHeader{ID: hdr.ID},
				Error:   fuse.Errno(err).ErrnoName(),
				// this is the only place that sets both Error and
				// Out; not sure if i want to do that; might get rid
				// of len(c.node) things altogether
				Out: logMissingNode{
					MaxNode: fuse.NodeID(len(c.node)),
				},
			})
			r.RespondError(err)
			return
		}
		node = snode.node
	}
	if c.req[hdr.ID] != nil {
		// This happens with OSXFUSE.  Assume it's okay and
		// that we'll never see an interrupt for this one.
		// Otherwise everything wedges.  TODO: Report to OSXFUSE?
		//
		// TODO this might have been because of missing done() calls
	} else {
		c.req[hdr.ID] = req
	}
	c.meta.Unlock()

	// Call this before responding.
	// After responding is too late: we might get another request
	// with the same ID and be very confused.
	done := func(resp interface{}) {
		msg := response{
			Op:      opName(r),
			Request: logResponseHeader{ID: hdr.ID},
		}
		if err, ok := resp.(error); ok {
			errno := fuse.ToErrno(err)
			msg.Errno = errno.ErrnoName()
			if errno != err && syscall.Errno(errno) != err {
				// if it's more than just a fuse.Errno or a
				// syscall.Errno, log extra detail
				msg.Error = err.Error()
			}
		} else {
			msg.Out = resp
		}
		c.debug(msg)

		c.meta.Lock()
		delete(c.req, hdr.ID)
		c.meta.Unlock()
	}

	var responded bool
	defer func() {
		if rec := recover(); rec != nil {
			const size = 1 << 16
			buf := make([]byte, size)
			n := runtime.Stack(buf, false)
			buf = buf[:n]
			log.Printf("fuse: panic in handler for %v: %v\n%s", r, rec, buf)
			err := handlerPanickedError{
				Request: r,
				Err:     rec,
			}
			done(err)
			r.RespondError(err)
			return
		}

		if !responded {
			err := handlerTerminatedError{
				Request: r,
			}
			done(err)
			r.RespondError(err)
		}
	}()

	if err := c.handleRequest(ctx, node, snode, r, done); err != nil {
		if err == context.Canceled {
			select {
			case <-parentCtx.Done():
				// We canceled the parent context because of an
				// incoming interrupt request, so return EINTR
				// to trigger the right behavior in the client app.
				//
				// Only do this when it's the parent context that was
				// canceled, not a context controlled by the program
				// using this library, so we don't return EINTR too
				// eagerly -- it might cause busy loops.
				//
				// Decent write-up on role of EINTR:
				// http://250bpm.com/blog:12
				err = syscall.EINTR
			default:
				// nothing
			}
		}
		done(err)
		r.RespondError(err)
	}

	// disarm runtime.Goexit protection
	responded = true
}

// handleRequest will either a) call done(s) and r.Respond(s) OR b) return an error.
func (c *Server) handleRequest(ctx context.Context, node Node, snode *serveNode, r fuse.Request, done func(resp interface{})) error {
	switch r := r.(type) {
	default:
		// Note: To FUSE, ENOSYS means "this server never implements this request."
		// It would be inappropriate to return ENOSYS for other operations in this
		// switch that might only be unavailable in some contexts, not all.
		return syscall.ENOSYS

	case *fuse.StatfsRequest:
		s := &fuse.StatfsResponse{}
		if fs, ok := c.fs.(FSStatfser); ok {
			if err := fs.Statfs(ctx, r, s); err != nil {
				return err
			}
		}
		done(s)
		r.Respond(s)
		return nil

	// Node operations.
	case *fuse.GetattrRequest:
		s := &fuse.GetattrResponse{}
		if n, ok := node.(NodeGetattrer); ok {
			if err := n.Getattr(ctx, r, s); err != nil {
				return err
			}
		} else {
			if err := snode.attr(ctx, &s.Attr); err != nil {
				return err
			}
		}
		done(s)
		r.Respond(s)
		return nil

	case *fuse.SetattrRequest:
		s := &fuse.SetattrResponse{}
		if n, ok := node.(NodeSetattrer); ok {
			if err := n.Setattr(ctx, r, s); err != nil {
				return err
			}
		}

		if err := snode.attr(ctx, &s.Attr); err != nil {
			return err
		}
		done(s)
		r.Respond(s)
		return nil

	case *fuse.SymlinkRequest:
		s := &fuse.SymlinkResponse{}
		initLookupResponse(&s.LookupResponse)
		n, ok := node.(NodeSymlinker)
		if !ok {
			return syscall.EIO // XXX or EPERM like Mkdir?
		}
		n2, err := n.Symlink(ctx, r)
		if err != nil {
			return err
		}
		if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.NewName, n2); err != nil {
			return err
		}
		done(s)
		r.Respond(s)
		return nil

	case *fuse.ReadlinkRequest:
		n, ok := node.(NodeReadlinker)
		if !ok {
			return syscall.EIO /// XXX or EPERM?
		}
		target, err := n.Readlink(ctx, r)
		if err != nil {
			return err
		}
		done(target)
		r.Respond(target)
		return nil

	case *fuse.LinkRequest:
		n, ok := node.(NodeLinker)
		if !ok {
			return syscall.EIO /// XXX or EPERM?
		}
		c.meta.Lock()
		var oldNode *serveNode
		if int(r.OldNode) < len(c.node) {
			oldNode = c.node[r.OldNode]
		}
		c.meta.Unlock()
		if oldNode == nil {
			c.debug(logLinkRequestOldNodeNotFound{
				Request: r.Hdr(),
				In:      r,
			})
			return syscall.EIO
		}
		n2, err := n.Link(ctx, r, oldNode.node)
		if err != nil {
			return err
		}
		s := &fuse.LookupResponse{}
		initLookupResponse(s)
		if err := c.saveLookup(ctx, s, snode, r.NewName, n2); err != nil {
			return err
		}
		done(s)
		r.Respond(s)
		return nil

	case *fuse.RemoveRequest:
		n, ok := node.(NodeRemover)
		if !ok {
			return syscall.EIO /// XXX or EPERM?
		}
		err := n.Remove(ctx, r)
		if err != nil {
			return err
		}
		done(nil)
		r.Respond()
		return nil

	case *fuse.AccessRequest:
		if n, ok := node.(NodeAccesser); ok {
			if err := n.Access(ctx, r); err != nil {
				return err
			}
		}
		done(nil)
		r.Respond()
		return nil

	case *fuse.LookupRequest:
		var n2 Node
		var err error
		s := &fuse.LookupResponse{}
		initLookupResponse(s)
		if n, ok := node.(NodeStringLookuper); ok {
			n2, err = n.Lookup(ctx, r.Name)
		} else if n, ok := node.(NodeRequestLookuper); ok {
			n2, err = n.Lookup(ctx, r, s)
		} else {
			return syscall.ENOENT
		}
		if err != nil {
			return err
		}
		if err := c.saveLookup(ctx, s, snode, r.Name, n2); err != nil {
			return err
		}
		done(s)
		r.Respond(s)
		return nil

	case *fuse.MkdirRequest:
		s := &fuse.MkdirResponse{}
		initLookupResponse(&s.LookupResponse)
		n, ok := node.(NodeMkdirer)
		if !ok {
			return syscall.EPERM
		}
		n2, err := n.Mkdir(ctx, r)
		if err != nil {
			return err
		}
		if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.Name, n2); err != nil {
			return err
		}
		done(s)
		r.Respond(s)
		return nil

	case *fuse.OpenRequest:
		s := &fuse.OpenResponse{}
		var h2 Handle
		if n, ok := node.(NodeOpener); ok {
			hh, err := n.Open(ctx, r, s)
			if err != nil {
				return err
			}
			h2 = hh
		} else {
			h2 = node
		}
		s.Handle = c.saveHandle(h2)
		done(s)
		r.Respond(s)
		return nil

	case *fuse.CreateRequest:
		n, ok := node.(NodeCreater)
		if !ok {
			// If we send back ENOSYS, FUSE will try mknod+open.
			return syscall.EPERM
		}
		s := &fuse.CreateResponse{OpenResponse: fuse.OpenResponse{}}
		initLookupResponse(&s.LookupResponse)
		n2, h2, err := n.Create(ctx, r, s)
		if err != nil {
			return err
		}
		if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.Name, n2); err != nil {
			return err
		}
		s.Handle = c.saveHandle(h2)
		done(s)
		r.Respond(s)
		return nil

	case *fuse.GetxattrRequest:
		n, ok := node.(NodeGetxattrer)
		if !ok {
			return syscall.ENOTSUP
		}
		s := &fuse.GetxattrResponse{}
		err := n.Getxattr(ctx, r, s)
		if err != nil {
			return err
		}
		if r.Size != 0 && uint64(len(s.Xattr)) > uint64(r.Size) {
			return syscall.ERANGE
		}
		done(s)
		r.Respond(s)
		return nil

	case *fuse.ListxattrRequest:
		n, ok := node.(NodeListxattrer)
		if !ok {
			return syscall.ENOTSUP
		}
		s := &fuse.ListxattrResponse{}
		err := n.Listxattr(ctx, r, s)
		if err != nil {
			return err
		}
		if r.Size != 0 && uint64(len(s.Xattr)) > uint64(r.Size) {
			return syscall.ERANGE
		}
		done(s)
		r.Respond(s)
		return nil

	case *fuse.SetxattrRequest:
		n, ok := node.(NodeSetxattrer)
		if !ok {
			return syscall.ENOTSUP
		}
		err := n.Setxattr(ctx, r)
		if err != nil {
			return err
		}
		done(nil)
		r.Respond()
		return nil

	case *fuse.RemovexattrRequest:
		n, ok := node.(NodeRemovexattrer)
		if !ok {
			return syscall.ENOTSUP
		}
		err := n.Removexattr(ctx, r)
		if err != nil {
			return err
		}
		done(nil)
		r.Respond()
		return nil

	case *fuse.ForgetRequest:
		forget := c.dropNode(r.Hdr().Node, r.N)
		if forget {
			n, ok := node.(NodeForgetter)
			if ok {
				n.Forget()
			}
		}
		done(nil)
		r.Respond()
		return nil

	// Handle operations.
	case *fuse.ReadRequest:
		shandle := c.getHandle(r.Handle)
		if shandle == nil {
			return syscall.ESTALE
		}
		handle := shandle.handle

		s := &fuse.ReadResponse{Data: make([]byte, 0, r.Size)}
		if r.Dir {
			if h, ok := handle.(HandleReadDirAller); ok {
				// detect rewinddir(3) or similar seek and refresh
				// contents
				if r.Offset == 0 {
					shandle.readData = nil
				}

				if shandle.readData == nil {
					dirs, err := h.ReadDirAll(ctx)
					if err != nil {
						return err
					}
					var data []byte
					for _, dir := range dirs {
						if dir.Inode == 0 {
							dir.Inode = c.dynamicInode(snode.inode, dir.Name)
						}
						data = fuse.AppendDirent(data, dir)
					}
					shandle.readData = data
				}
				fuseutil.HandleRead(r, s, shandle.readData)
				done(s)
				r.Respond(s)
				return nil
			}
		} else {
			if h, ok := handle.(HandleReadAller); ok {
				if shandle.readData == nil {
					data, err := h.ReadAll(ctx)
					if err != nil {
						return err
					}
					if data == nil {
						data = []byte{}
					}
					shandle.readData = data
				}
				fuseutil.HandleRead(r, s, shandle.readData)
				done(s)
				r.Respond(s)
				return nil
			}
			h, ok := handle.(HandleReader)
			if !ok {
				err := handleNotReaderError{handle: handle}
				return err
			}
			if err := h.Read(ctx, r, s); err != nil {
				return err
			}
		}
		done(s)
		r.Respond(s)
		return nil

	case *fuse.WriteRequest:
		shandle := c.getHandle(r.Handle)
		if shandle == nil {
			return syscall.ESTALE
		}

		s := &fuse.WriteResponse{}
		if h, ok := shandle.handle.(HandleWriter); ok {
			if err := h.Write(ctx, r, s); err != nil {
				return err
			}
			done(s)
			r.Respond(s)
			return nil
		}
		return syscall.EIO

	case *fuse.FlushRequest:
		shandle := c.getHandle(r.Handle)
		if shandle == nil {
			return syscall.ESTALE
		}
		handle := shandle.handle

		if h, ok := handle.(HandleFlusher); ok {
			if err := h.Flush(ctx, r); err != nil {
				return err
			}
		}
		done(nil)
		r.Respond()
		return nil

	case *fuse.ReleaseRequest:
		shandle := c.getHandle(r.Handle)
		if shandle == nil {
			return syscall.ESTALE
		}
		handle := shandle.handle

		// No matter what, release the handle.
		c.dropHandle(r.Handle)

		if h, ok := handle.(HandleReleaser); ok {
			if err := h.Release(ctx, r); err != nil {
				return err
			}
		}
		done(nil)
		r.Respond()
		return nil

	case *fuse.DestroyRequest:
		if fs, ok := c.fs.(FSDestroyer); ok {
			fs.Destroy()
		}
		done(nil)
		r.Respond()
		return nil

	case *fuse.RenameRequest:
		c.meta.Lock()
		var newDirNode *serveNode
		if int(r.NewDir) < len(c.node) {
			newDirNode = c.node[r.NewDir]
		}
		c.meta.Unlock()
		if newDirNode == nil {
			c.debug(renameNewDirNodeNotFound{
				Request: r.Hdr(),
				In:      r,
			})
			return syscall.EIO
		}
		n, ok := node.(NodeRenamer)
		if !ok {
			return syscall.EIO // XXX or EPERM like Mkdir?
		}
		err := n.Rename(ctx, r, newDirNode.node)
		if err != nil {
			return err
		}
		done(nil)
		r.Respond()
		return nil

	case *fuse.MknodRequest:
		n, ok := node.(NodeMknoder)
		if !ok {
			return syscall.EIO
		}
		n2, err := n.Mknod(ctx, r)
		if err != nil {
			return err
		}
		s := &fuse.LookupResponse{}
		initLookupResponse(s)
		if err := c.saveLookup(ctx, s, snode, r.Name, n2); err != nil {
			return err
		}
		done(s)
		r.Respond(s)
		return nil

	case *fuse.FsyncRequest:
		n, ok := node.(NodeFsyncer)
		if !ok {
			return syscall.EIO
		}
		err := n.Fsync(ctx, r)
		if err != nil {
			return err
		}
		done(nil)
		r.Respond()
		return nil

	case *fuse.InterruptRequest:
		c.meta.Lock()
		ireq := c.req[r.IntrID]
		if ireq != nil && ireq.cancel != nil {
			ireq.cancel()
			ireq.cancel = nil
		}
		c.meta.Unlock()
		done(nil)
		r.Respond()
		return nil

		/*	case *FsyncdirRequest:
				return ENOSYS

			case *GetlkRequest, *SetlkRequest, *SetlkwRequest:
				return ENOSYS

			case *BmapRequest:
				return ENOSYS

			case *SetvolnameRequest, *GetxtimesRequest, *ExchangeRequest:
				return ENOSYS
		*/
	}

	panic("not reached")
}

func (c *Server) saveLookup(ctx context.Context, s *fuse.LookupResponse, snode *serveNode, elem string, n2 Node) error {
	if err := nodeAttr(ctx, n2, &s.Attr); err != nil {
		return err
	}
	if s.Attr.Inode == 0 {
		s.Attr.Inode = c.dynamicInode(snode.inode, elem)
	}

	s.Node, s.Generation = c.saveNode(s.Attr.Inode, n2)
	return nil
}

type invalidateNodeDetail struct {
	Off  int64
	Size int64
}

func (i invalidateNodeDetail) String() string {
	return fmt.Sprintf("Off:%d Size:%d", i.Off, i.Size)
}

func errstr(err error) string {
	if err == nil {
		return ""
	}
	return err.Error()
}

func (s *Server) invalidateNode(node Node, off int64, size int64) error {
	s.meta.Lock()
	id, ok := s.nodeRef[node]
	if ok {
		snode := s.node[id]
		snode.wg.Add(1)
		defer snode.wg.Done()
	}
	s.meta.Unlock()
	if !ok {
		// This is what the kernel would have said, if we had been
		// able to send this message; it's not cached.
		return fuse.ErrNotCached
	}
	// Delay logging until after we can record the error too. We
	// consider a /dev/fuse write to be instantaneous enough to not
	// need separate before and after messages.
	err := s.conn.InvalidateNode(id, off, size)
	s.debug(notification{
		Op:   "InvalidateNode",
		Node: id,
		Out: invalidateNodeDetail{
			Off:  off,
			Size: size,
		},
		Err: errstr(err),
	})
	return err
}

// InvalidateNodeAttr invalidates the kernel cache of the attributes
// of node.
//
// Returns fuse.ErrNotCached if the kernel is not currently caching
// the node.
func (s *Server) InvalidateNodeAttr(node Node) error {
	return s.invalidateNode(node, 0, 0)
}

// InvalidateNodeData invalidates the kernel cache of the attributes
// and data of node.
//
// Returns fuse.ErrNotCached if the kernel is not currently caching
// the node.
func (s *Server) InvalidateNodeData(node Node) error {
	return s.invalidateNode(node, 0, -1)
}

// InvalidateNodeDataRange invalidates the kernel cache of the
// attributes and a range of the data of node.
//
// Returns fuse.ErrNotCached if the kernel is not currently caching
// the node.
func (s *Server) InvalidateNodeDataRange(node Node, off int64, size int64) error {
	return s.invalidateNode(node, off, size)
}

type invalidateEntryDetail struct {
	Name string
}

func (i invalidateEntryDetail) String() string {
	return fmt.Sprintf("%q", i.Name)
}

// InvalidateEntry invalidates the kernel cache of the directory entry
// identified by parent node and entry basename.
//
// Kernel may or may not cache directory listings. To invalidate
// those, use InvalidateNode to invalidate all of the data for a
// directory. (As of 2015-06, Linux FUSE does not cache directory
// listings.)
//
// Returns ErrNotCached if the kernel is not currently caching the
// node.
func (s *Server) InvalidateEntry(parent Node, name string) error {
	s.meta.Lock()
	id, ok := s.nodeRef[parent]
	if ok {
		snode := s.node[id]
		snode.wg.Add(1)
		defer snode.wg.Done()
	}
	s.meta.Unlock()
	if !ok {
		// This is what the kernel would have said, if we had been
		// able to send this message; it's not cached.
		return fuse.ErrNotCached
	}
	err := s.conn.InvalidateEntry(id, name)
	s.debug(notification{
		Op:   "InvalidateEntry",
		Node: id,
		Out: invalidateEntryDetail{
			Name: name,
		},
		Err: errstr(err),
	})
	return err
}

// DataHandle returns a read-only Handle that satisfies reads
// using the given data.
func DataHandle(data []byte) Handle {
	return &dataHandle{data}
}

type dataHandle struct {
	data []byte
}

func (d *dataHandle) ReadAll(ctx context.Context) ([]byte, error) {
	return d.data, nil
}

// GenerateDynamicInode returns a dynamic inode.
//
// The parent inode and current entry name are used as the criteria
// for choosing a pseudorandom inode. This makes it likely the same
// entry will get the same inode on multiple runs.
func GenerateDynamicInode(parent uint64, name string) uint64 {
	h := fnv.New64a()
	var buf [8]byte
	binary.LittleEndian.PutUint64(buf[:], parent)
	_, _ = h.Write(buf[:])
	_, _ = h.Write([]byte(name))
	var inode uint64
	for {
		inode = h.Sum64()
		if inode > 1 {
			break
		}
		// there's a tiny probability that result is zero or the
		// hardcoded root inode 1; change the input a little and try
		// again
		_, _ = h.Write([]byte{'x'})
	}
	return inode
}

D vendor/bazil.org/fuse/fs/tree.go => vendor/bazil.org/fuse/fs/tree.go +0 -97
@@ 1,97 0,0 @@
// FUSE directory tree, for servers that wish to use it with the service loop.

package fs

import (
	"context"
	"os"
	pathpkg "path"
	"strings"
	"syscall"

	"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(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) {
	n := t.lookup(name)
	if n != nil {
		return n, nil
	}
	return nil, syscall.ENOENT
}

func (t *tree) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
	var out []fuse.Dirent
	for _, d := range t.dir {
		out = append(out, fuse.Dirent{Name: d.name})
	}
	return out, nil
}

D vendor/bazil.org/fuse/fuse.go => vendor/bazil.org/fuse/fuse.go +0 -2338
@@ 1,2338 0,0 @@
// See the file LICENSE for copyright and licensing information.

// Adapted from Plan 9 from User Space's src/cmd/9pfuse/fuse.c,
// which carries this notice:
//
// The files in this directory are subject to the following license.
//
// The author of this software is Russ Cox.
//
//         Copyright (c) 2006 Russ Cox
//
// Permission to use, copy, modify, and distribute this software for any
// purpose without fee is hereby granted, provided that this entire notice
// is included in all copies of any software which is or includes a copy
// or modification of this software and in all copies of the supporting
// documentation for such software.
//
// THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
// WARRANTY.  IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION OR WARRANTY
// OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS SOFTWARE OR ITS
// FITNESS FOR ANY PARTICULAR PURPOSE.

// Package fuse enables writing FUSE file systems on Linux, OS X, and FreeBSD.
//
// On OS X, it requires OSXFUSE (http://osxfuse.github.com/).
//
// There are two approaches to writing a FUSE file system.  The first is to speak
// the low-level message protocol, reading from a Conn using ReadRequest and
// writing using the various Respond methods.  This approach is closest to
// the actual interaction with the kernel and can be the simplest one in contexts
// such as protocol translators.
//
// Servers of synthesized file systems tend to share common
// bookkeeping abstracted away by the second approach, which is to
// call fs.Serve to serve the FUSE protocol using an implementation of
// the service methods in the interfaces FS* (file system), Node* (file
// or directory), and Handle* (opened file or directory).
// There are a daunting number of such methods that can be written,
// but few are required.
// The specific methods are described in the documentation for those interfaces.
//
// The hellofs subdirectory contains a simple illustration of the fs.Serve approach.
//
// Service Methods
//
// The required and optional methods for the FS, Node, and Handle interfaces
// have the general form
//
//	Op(ctx context.Context, req *OpRequest, resp *OpResponse) error
//
// where Op is the name of a FUSE operation. Op reads request
// parameters from req and writes results to resp. An operation whose
// only result is the error result omits the resp parameter.
//
// Multiple goroutines may call service methods simultaneously; the
// methods being called are responsible for appropriate
// synchronization.
//
// The operation must not hold on to the request or response,
// including any []byte fields such as WriteRequest.Data or
// SetxattrRequest.Xattr.
//
// Errors
//
// Operations can return errors. The FUSE interface can only
// communicate POSIX errno error numbers to file system clients, the
// message is not visible to file system clients. The returned error
// can implement ErrorNumber to control the errno returned. Without
// ErrorNumber, a generic errno (EIO) is returned.
//
// Error messages will be visible in the debug log as part of the
// response.
//
// Interrupted Operations
//
// In some file systems, some operations
// may take an undetermined amount of time.  For example, a Read waiting for
// a network message or a matching Write might wait indefinitely.  If the request
// is cancelled and no longer needed, the context will be cancelled.
// Blocking operations should select on a receive from ctx.Done() and attempt to
// abort the operation early if the receive succeeds (meaning the channel is closed).
// To indicate that the operation failed because it was aborted, return syscall.EINTR.
//
// If an operation does not block for an indefinite amount of time, supporting
// cancellation is not necessary.
//
// Authentication
//
// All requests types embed a Header, meaning that the method can
// inspect req.Pid, req.Uid, and req.Gid as necessary to implement
// permission checking. The kernel FUSE layer normally prevents other
// users from accessing the FUSE file system (to change this, see
// AllowOther), but does not enforce access modes (to change this, see
// DefaultPermissions).
//
// Mount Options
//
// Behavior and metadata of the mounted file system can be changed by
// passing MountOption values to Mount.
//
package fuse // import "bazil.org/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
	wio sync.RWMutex
	rio sync.RWMutex

	// Protocol version negotiated with InitRequest/InitResponse.
	proto Protocol
}

// MountpointDoesNotExistError is an error returned when the
// mountpoint does not exist.
type MountpointDoesNotExistError struct {
	Path string
}

var _ error = (*MountpointDoesNotExistError)(nil)

func (e *MountpointDoesNotExistError) Error() string {
	return fmt.Sprintf("mountpoint does not exist: %v", e.Path)
}

// Mount mounts a new FUSE connection on the named directory
// and returns a connection for reading and writing FUSE messages.
//
// After a successful return, caller must call Close to free
// resources.
//
// Even on successful return, the new mount is not guaranteed to be
// visible until after Conn.Ready is closed. See Conn.MountError for
// possible errors. Incoming requests on Conn must be served to make
// progress.
func Mount(dir string, options ...MountOption) (*Conn, error) {
	conf := mountConfig{
		options: make(map[string]string),
	}
	for _, option := range options {
		if err := option(&conf); err != nil {
			return nil, err
		}
	}

	ready := make(chan struct{}, 1)
	c := &Conn{
		Ready: ready,
	}
	f, err := mount(dir, &conf, ready, &c.MountError)
	if err != nil {
		return nil, err
	}
	c.dev = f

	if err := initMount(c, &conf); err != nil {
		c.Close()
		if err == ErrClosedWithoutInit {
			// see if we can provide a better error
			<-c.Ready
			if err := c.MountError; err != nil {
				return nil, err
			}
		}
		return nil, err
	}

	return c, nil
}

type OldVersionError struct {
	Kernel     Protocol
	LibraryMin Protocol
}

func (e *OldVersionError) Error() string {
	return fmt.Sprintf("kernel FUSE version is too old: %v < %v", e.Kernel, e.LibraryMin)
}

var (
	ErrClosedWithoutInit = errors.New("fuse connection closed without init")
)

func initMount(c *Conn, conf *mountConfig) error {
	req, err := c.ReadRequest()
	if err != nil {
		if err == io.EOF {
			return ErrClosedWithoutInit
		}
		return err
	}
	r, ok := req.(*InitRequest)
	if !ok {
		return fmt.Errorf("missing init, got: %T", req)
	}

	min := Protocol{protoVersionMinMajor, protoVersionMinMinor}
	if r.Kernel.LT(min) {
		req.RespondError(Errno(syscall.EPROTO))
		c.Close()
		return &OldVersionError{
			Kernel:     r.Kernel,
			LibraryMin: min,
		}
	}

	proto := Protocol{protoVersionMaxMajor, protoVersionMaxMinor}
	if r.Kernel.LT(proto) {
		// Kernel doesn't support the lat