From c38d7be373f0019113f791d4d45034e571f3dcb8 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Mon, 4 Nov 2019 17:12:50 +0000 Subject: [PATCH] vendor: add github.com/hanwen/go-fuse/v2@master for mount2 --- go.mod | 1 + go.sum | 6 + vendor/github.com/hanwen/go-fuse/v2/AUTHORS | 20 + vendor/github.com/hanwen/go-fuse/v2/LICENSE | 30 + .../github.com/hanwen/go-fuse/v2/fs/README.md | 60 ++ vendor/github.com/hanwen/go-fuse/v2/fs/api.go | 602 +++++++++++ .../github.com/hanwen/go-fuse/v2/fs/bridge.go | 972 ++++++++++++++++++ .../hanwen/go-fuse/v2/fs/constants.go | 29 + .../hanwen/go-fuse/v2/fs/constants_darwin.go | 10 + .../hanwen/go-fuse/v2/fs/constants_linux.go | 10 + .../hanwen/go-fuse/v2/fs/default.go | 5 + .../hanwen/go-fuse/v2/fs/dirstream.go | 34 + .../hanwen/go-fuse/v2/fs/dirstream_darwin.go | 48 + .../hanwen/go-fuse/v2/fs/dirstream_linux.go | 93 ++ .../github.com/hanwen/go-fuse/v2/fs/files.go | 230 +++++ .../hanwen/go-fuse/v2/fs/files_darwin.go | 10 + .../hanwen/go-fuse/v2/fs/files_linux.go | 42 + .../github.com/hanwen/go-fuse/v2/fs/inode.go | 723 +++++++++++++ .../hanwen/go-fuse/v2/fs/loopback.go | 389 +++++++ .../hanwen/go-fuse/v2/fs/loopback_darwin.go | 118 +++ .../hanwen/go-fuse/v2/fs/loopback_linux.go | 86 ++ vendor/github.com/hanwen/go-fuse/v2/fs/mem.go | 103 ++ .../github.com/hanwen/go-fuse/v2/fs/mount.go | 40 + .../hanwen/go-fuse/v2/fs/syscall_linux.go | 20 + .../hanwen/go-fuse/v2/fuse/.gitignore | 1 + .../github.com/hanwen/go-fuse/v2/fuse/api.go | 274 +++++ .../github.com/hanwen/go-fuse/v2/fuse/attr.go | 79 ++ .../hanwen/go-fuse/v2/fuse/attr_darwin.go | 26 + .../hanwen/go-fuse/v2/fuse/attr_linux.go | 27 + .../hanwen/go-fuse/v2/fuse/bufferpool.go | 69 ++ .../hanwen/go-fuse/v2/fuse/constants.go | 37 + .../go-fuse/v2/fuse/constants_freebsd.go | 9 + .../hanwen/go-fuse/v2/fuse/constants_linux.go | 12 + .../hanwen/go-fuse/v2/fuse/context.go | 61 ++ .../hanwen/go-fuse/v2/fuse/defaultraw.go | 168 +++ .../hanwen/go-fuse/v2/fuse/direntry.go | 118 +++ .../github.com/hanwen/go-fuse/v2/fuse/misc.go | 100 ++ .../hanwen/go-fuse/v2/fuse/mount_darwin.go | 97 ++ .../hanwen/go-fuse/v2/fuse/mount_linux.go | 143 +++ .../hanwen/go-fuse/v2/fuse/opcode.go | 804 +++++++++++++++ .../github.com/hanwen/go-fuse/v2/fuse/poll.go | 34 + .../hanwen/go-fuse/v2/fuse/poll_darwin.go | 49 + .../hanwen/go-fuse/v2/fuse/poll_linux.go | 25 + .../hanwen/go-fuse/v2/fuse/print.go | 305 ++++++ .../hanwen/go-fuse/v2/fuse/print_darwin.go | 54 + .../hanwen/go-fuse/v2/fuse/print_linux.go | 61 ++ .../github.com/hanwen/go-fuse/v2/fuse/read.go | 75 ++ .../hanwen/go-fuse/v2/fuse/request.go | 254 +++++ .../hanwen/go-fuse/v2/fuse/request_darwin.go | 13 + .../hanwen/go-fuse/v2/fuse/request_linux.go | 13 + .../hanwen/go-fuse/v2/fuse/server.go | 858 ++++++++++++++++ .../hanwen/go-fuse/v2/fuse/server_darwin.go | 32 + .../hanwen/go-fuse/v2/fuse/server_linux.go | 42 + .../hanwen/go-fuse/v2/fuse/splice_darwin.go | 17 + .../hanwen/go-fuse/v2/fuse/splice_linux.go | 97 ++ .../hanwen/go-fuse/v2/fuse/syscall_darwin.go | 139 +++ .../hanwen/go-fuse/v2/fuse/syscall_linux.go | 49 + .../hanwen/go-fuse/v2/fuse/typeprint.go | 9 + .../hanwen/go-fuse/v2/fuse/types.go | 676 ++++++++++++ .../hanwen/go-fuse/v2/fuse/types_darwin.go | 166 +++ .../hanwen/go-fuse/v2/fuse/types_linux.go | 134 +++ .../hanwen/go-fuse/v2/internal/access.go | 60 ++ .../v2/internal/utimens/utimens_darwin.go | 40 + .../v2/internal/utimens/utimens_linux.go | 7 + .../hanwen/go-fuse/v2/splice/copy.go | 70 ++ .../hanwen/go-fuse/v2/splice/pair.go | 68 ++ .../hanwen/go-fuse/v2/splice/pair_darwin.go | 22 + .../hanwen/go-fuse/v2/splice/pair_linux.go | 54 + .../hanwen/go-fuse/v2/splice/pool.go | 105 ++ .../hanwen/go-fuse/v2/splice/splice.go | 97 ++ vendor/modules.txt | 6 + 71 files changed, 9337 insertions(+) create mode 100644 vendor/github.com/hanwen/go-fuse/v2/AUTHORS create mode 100644 vendor/github.com/hanwen/go-fuse/v2/LICENSE create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fs/README.md create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fs/api.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fs/bridge.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fs/constants.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fs/constants_darwin.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fs/constants_linux.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fs/default.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fs/dirstream.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fs/dirstream_darwin.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fs/dirstream_linux.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fs/files.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fs/files_darwin.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fs/files_linux.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fs/inode.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fs/loopback.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fs/loopback_darwin.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fs/loopback_linux.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fs/mem.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fs/mount.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fs/syscall_linux.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/.gitignore create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/api.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/attr.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/attr_darwin.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/attr_linux.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/bufferpool.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/constants.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/constants_freebsd.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/constants_linux.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/context.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/defaultraw.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/direntry.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/misc.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/mount_darwin.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/mount_linux.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/opcode.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/poll.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/poll_darwin.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/poll_linux.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/print.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/print_darwin.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/print_linux.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/read.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/request.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/request_darwin.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/request_linux.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/server.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/server_darwin.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/server_linux.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/splice_darwin.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/splice_linux.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/syscall_darwin.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/syscall_linux.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/typeprint.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/types.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/types_darwin.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/fuse/types_linux.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/internal/access.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/internal/utimens/utimens_darwin.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/internal/utimens/utimens_linux.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/splice/copy.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/splice/pair.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/splice/pair_darwin.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/splice/pair_linux.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/splice/pool.go create mode 100644 vendor/github.com/hanwen/go-fuse/v2/splice/splice.go diff --git a/go.mod b/go.mod index 6844699d9..3c8082346 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/google/go-cmp v0.3.1 // indirect github.com/google/go-querystring v1.0.0 // indirect github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f // indirect + github.com/hanwen/go-fuse/v2 v2.0.3-0.20191108143333-152e6ac32d54 github.com/jlaffaye/ftp v0.0.0-20191025175106-a59fe673c9b2 github.com/jzelinskie/whirlpool v0.0.0-20170603002051-c19460b8caa6 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect diff --git a/go.sum b/go.sum index 3c74d9cac..971a3a1ea 100644 --- a/go.sum +++ b/go.sum @@ -129,6 +129,10 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f h1:KMlcu9X58lhTA/KrfX8Bi1LQSO4pzoVjTiL3h4Jk+Zk= github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/hanwen/go-fuse v1.0.0 h1:GxS9Zrn6c35/BnfiVsZVWmsG803xwE7eVRDvcf/BEVc= +github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok= +github.com/hanwen/go-fuse/v2 v2.0.3-0.20191108143333-152e6ac32d54 h1:0JL4/kY3QKTRevfl0IbEncTzA+jczGba+swfDBBluuU= +github.com/hanwen/go-fuse/v2 v2.0.3-0.20191108143333-152e6ac32d54/go.mod h1:HH3ygZOoyRbP9y2q7y3+JM6hPL+Epe29IbWaS0UA81o= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -167,6 +171,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= +github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= diff --git a/vendor/github.com/hanwen/go-fuse/v2/AUTHORS b/vendor/github.com/hanwen/go-fuse/v2/AUTHORS new file mode 100644 index 000000000..052128d30 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/AUTHORS @@ -0,0 +1,20 @@ +Adam H. Leventhal +Daniel Martí +Fazlul Shahriar +Frederick Akalin +Google Inc. +Haitao Li +Jakob Unterwurzacher +James D. Nurmi +Jeff +Kaoet Ibe +Kirill Smelkov +Logan Hanks +Maria Shaldibina +Nick Cooper +Patrick Crosby +Paul Jolly +Paul Warren +Shayan Pooya +Valient Gough +Yongwoo Park diff --git a/vendor/github.com/hanwen/go-fuse/v2/LICENSE b/vendor/github.com/hanwen/go-fuse/v2/LICENSE new file mode 100644 index 000000000..962771ccf --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/LICENSE @@ -0,0 +1,30 @@ +// New BSD License +// +// Copyright (c) 2010 the Go-FUSE Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Ivan Krasin nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/README.md b/vendor/github.com/hanwen/go-fuse/v2/fs/README.md new file mode 100644 index 000000000..62936acfd --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/README.md @@ -0,0 +1,60 @@ + +Objective +========= + +A high-performance FUSE API that minimizes pitfalls with writing +correct filesystems. + +Decisions +========= + + * Nodes contain references to their children. This is useful + because most filesystems will need to construct tree-like + structures. + + * Nodes contain references to their parents. As a result, we can + derive the path for each Inode, and there is no need for a + separate PathFS. + + * Nodes can be "persistent", meaning their lifetime is not under + control of the kernel. This is useful for constructing FS trees + in advance, rather than driven by LOOKUP. + + * The NodeID for FS tree node must be defined on creation and are + immutable. By contrast, reusing NodeIds (eg. rsc/bazil FUSE, as + well as old go-fuse/fuse/nodefs) needs extra synchronization to + avoid races with notify and FORGET, and makes handling the inode + Generation more complicated. + + * The mode of an Inode is defined on creation. Files cannot change + type during their lifetime. This also prevents the common error + of forgetting to return the filetype in Lookup/GetAttr. + + * The NodeID (used for communicating with kernel) is equal to + Attr.Ino (value shown in Stat and Lstat return values.). + + * No global treelock, to ensure scalability. + + * Support for hard links. libfuse doesn't support this in the + high-level API. Extra care for race conditions is needed when + looking up the same file through different paths. + + * do not issue Notify{Entry,Delete} as part of + AddChild/RmChild/MvChild: because NodeIDs are unique and + immutable, there is no confusion about which nodes are + invalidated, and the notification doesn't have to happen under + lock. + + * Directory reading uses the DirStream. Semantics for rewinding + directory reads, and adding files after opening (but before + reading) are handled automatically. No support for directory + seeks. + + * Method names are based on syscall names. Where there is no + syscall (eg. "open directory"), we bias towards writing + everything together (Opendir) + +To do/To decide +========= + + * Symlink []byte vs string. diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/api.go b/vendor/github.com/hanwen/go-fuse/v2/fs/api.go new file mode 100644 index 000000000..644b91872 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/api.go @@ -0,0 +1,602 @@ +// Copyright 2019 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package fs provides infrastructure to build tree-organized filesystems. +// +// Structure of a file system implementation +// +// To create a file system, you should first define types for the +// nodes of the file system tree. +// +// struct myNode { +// fs.Inode +// } +// +// // Node types must be InodeEmbedders +// var _ = (fs.InodeEmbedder)((*myNode)(nil)) +// +// // Node types should implement some file system operations, eg. Lookup +// var _ = (fs.NodeLookuper)((*myNode)(nil)) +// +// func (n *myNode) Lookup(ctx context.Context, name string, ... ) (*Inode, syscall.Errno) { +// ops := myNode{} +// return n.NewInode(ctx, &ops, fs.StableAttr{Mode: syscall.S_IFDIR}), 0 +// } +// +// The method names are inspired on the system call names, so we have +// Listxattr rather than ListXAttr. +// +// the file system is mounted by calling mount on the root of the tree, +// +// server, err := fs.Mount("/tmp/mnt", &myNode{}, &fs.Options{}) +// .. +// // start serving the file system +// server.Wait() +// +// Error handling +// +// All error reporting must use the syscall.Errno type. This is an +// integer with predefined error codes, where the value 0 (`OK`) +// should be used to indicate success. +// +// File system concepts +// +// The FUSE API is very similar to Linux' internal VFS API for +// defining file systems in the kernel. It is therefore useful to +// understand some terminology. +// +// File content: the raw bytes that we store inside regular files. +// +// Path: a /-separated string path that describes location of a node +// in the file system tree. For example +// +// dir1/file +// +// describes path root → dir1 → file. +// +// There can be several paths leading from tree root to a particular node, +// known as hard-linking, for example +// +// root +// / \ +// dir1 dir2 +// \ / +// file +// +// Inode: ("index node") points to the file content, and stores +// metadata (size, timestamps) about a file or directory. Each +// inode has a type (directory, symlink, regular file, etc.) and +// an identity (a 64-bit number, unique to the file +// system). Directories can have children. +// +// The inode in the kernel is represented in Go-FUSE as the Inode +// type. +// +// While common OS APIs are phrased in terms of paths (strings), the +// precise semantics of a file system are better described in terms of +// Inodes. This allows us to specify what happens in corner cases, +// such as writing data to deleted files. +// +// File descriptor: a handle returned to opening a file. File +// descriptors always refer to a single inode. +// +// Dirent: a dirent maps (parent inode number, name string) tuple to +// child inode, thus representing a parent/child relation (or the +// absense thereof). Dirents do not have an equivalent type inside +// Go-FUSE, but the result of Lookup operation essentially is a +// dirent, which the kernel puts in a cache. +// +// +// Kernel caching +// +// The kernel caches several pieces of information from the FUSE process: +// +// 1. File contents: enabled with the fuse.FOPEN_KEEP_CACHE return flag +// in Open, manipulated with ReadCache and WriteCache, and invalidated +// with Inode.NotifyContent +// +// 2. File Attributes (size, mtime, etc.): controlled with the +// attribute timeout fields in fuse.AttrOut and fuse.EntryOut, which +// get be populated from Getattr and Lookup +// +// 3. Directory entries (parent/child relations in the FS tree): +// controlled with the timeout fields in fuse.EntryOut, and +// invalidated with Inode.NotifyEntry and Inode.NotifyDelete. +// +// Without Directory Entry timeouts, every operation on file "a/b/c" +// must first do lookups for "a", "a/b" and "a/b/c", which is +// expensive because of context switches between the kernel and the +// FUSE process. +// +// Unsuccessful entry lookups can also be cached by setting an entry +// timeout when Lookup returns ENOENT. +// +// The libfuse C library specifies 1 second timeouts for both +// attribute and directory entries, but no timeout for negative +// entries. by default. This can be achieve in go-fuse by setting +// options on mount, eg. +// +// sec := time.Second +// opts := fs.Options{ +// EntryTimeout: &sec, +// AttrTimeout: &sec, +// } +// +// Locking +// +// Locks for networked filesystems are supported through the suite of +// Getlk, Setlk and Setlkw methods. They alllow locks on regions of +// regular files. +// +// Parallelism +// +// The VFS layer in the kernel is optimized to be highly parallel, and +// this parallelism also affects FUSE file systems: many FUSE +// operations can run in parallel, and this invites race +// conditions. It is strongly recommended to test your FUSE file +// system issuing file operations in parallel, and using the race +// detector to weed out data races. +// +// Dynamically discovered file systems +// +// File system data usually cannot fit all in RAM, so the kernel must +// discover the file system dynamically: as you are entering and list +// directory contents, the kernel asks the FUSE server about the files +// and directories you are busy reading/writing, and forgets parts of +// your file system when it is low on memory. +// +// The two important operations for dynamic file systems are: +// 1. Lookup, part of the NodeLookuper interface for discovering +// individual children of directories, and 2. Readdir, part of the +// NodeReaddirer interface for listing the contents of a directory. +// +// Static in-memory file systems +// +// For small, read-only file systems, getting the locking mechanics of +// Lookup correct is tedious, so Go-FUSE provides a feature to +// simplify building such file systems. +// +// Instead of discovering the FS tree on the fly, you can construct +// the entire tree from an OnAdd method. Then, that in-memory tree +// structure becomes the source of truth. This means you Go-FUSE must +// remember Inodes even if the kernel is no longer interested in +// them. This is done by instantiating "persistent" inodes from the +// OnAdd method of the root node. See the ZipFS example for a +// runnable example of how to do this. +package fs + +import ( + "context" + "syscall" + "time" + + "github.com/hanwen/go-fuse/v2/fuse" +) + +// InodeEmbedder is an interface for structs that embed Inode. +// +// InodeEmbedder objects usually should implement some of the NodeXxxx +// interfaces, to provide user-defined file system behaviors. +// +// In general, if an InodeEmbedder does not implement specific +// filesystem methods, the filesystem will react as if it is a +// read-only filesystem with a predefined tree structure. +type InodeEmbedder interface { + // populateInode and inode are used internally to link Inode + // to a Node. + // + // See Inode() for the public API to retrieve an inode from Node. + embed() *Inode + + // EmbeddedInode returns a pointer to the embedded inode. + EmbeddedInode() *Inode +} + +// Statfs implements statistics for the filesystem that holds this +// Inode. If not defined, the `out` argument will zeroed with an OK +// result. This is because OSX filesystems must Statfs, or the mount +// will not work. +type NodeStatfser interface { + Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno +} + +// Access should return if the caller can access the file with the +// given mode. This is used for two purposes: to determine if a user +// may enter a directory, and to answer to implement the access system +// call. In the latter case, the context has data about the real +// UID. For example, a root-SUID binary called by user susan gets the +// UID and GID for susan here. +// +// If not defined, a default implementation will check traditional +// unix permissions of the Getattr result agains the caller. If so, it +// is necessary to either return permissions from GetAttr/Lookup or +// set Options.DefaultPermissions in order to allow chdir into the +// FUSE mount. +type NodeAccesser interface { + Access(ctx context.Context, mask uint32) syscall.Errno +} + +// GetAttr reads attributes for an Inode. The library will ensure that +// Mode and Ino are set correctly. For files that are not opened with +// FOPEN_DIRECTIO, Size should be set so it can be read correctly. If +// returning zeroed permissions, the default behavior is to change the +// mode of 0755 (directory) or 0644 (files). This can be switched off +// with the Options.NullPermissions setting. If blksize is unset, 4096 +// is assumed, and the 'blocks' field is set accordingly. +type NodeGetattrer interface { + Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno +} + +// SetAttr sets attributes for an Inode. +type NodeSetattrer interface { + Setattr(ctx context.Context, f FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno +} + +// OnAdd is called when this InodeEmbedder is initialized. +type NodeOnAdder interface { + OnAdd(ctx context.Context) +} + +// Getxattr should read data for the given attribute into +// `dest` and return the number of bytes. If `dest` is too +// small, it should return ERANGE and the size of the attribute. +// If not defined, Getxattr will return ENOATTR. +type NodeGetxattrer interface { + Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) +} + +// Setxattr should store data for the given attribute. See +// setxattr(2) for information about flags. +// If not defined, Setxattr will return ENOATTR. +type NodeSetxattrer interface { + Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno +} + +// Removexattr should delete the given attribute. +// If not defined, Removexattr will return ENOATTR. +type NodeRemovexattrer interface { + Removexattr(ctx context.Context, attr string) syscall.Errno +} + +// Listxattr should read all attributes (null terminated) into +// `dest`. If the `dest` buffer is too small, it should return ERANGE +// and the correct size. If not defined, return an empty list and +// success. +type NodeListxattrer interface { + Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) +} + +// Readlink reads the content of a symlink. +type NodeReadlinker interface { + Readlink(ctx context.Context) ([]byte, syscall.Errno) +} + +// Open opens an Inode (of regular file type) for reading. It +// is optional but recommended to return a FileHandle. +type NodeOpener interface { + Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) +} + +// Reads data from a file. The data should be returned as +// ReadResult, which may be constructed from the incoming +// `dest` buffer. If the file was opened without FileHandle, +// the FileHandle argument here is nil. The default +// implementation forwards to the FileHandle. +type NodeReader interface { + Read(ctx context.Context, f FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) +} + +// Writes the data into the file handle at given offset. After +// returning, the data will be reused and may not referenced. +// The default implementation forwards to the FileHandle. +type NodeWriter interface { + Write(ctx context.Context, f FileHandle, data []byte, off int64) (written uint32, errno syscall.Errno) +} + +// Fsync is a signal to ensure writes to the Inode are flushed +// to stable storage. +type NodeFsyncer interface { + Fsync(ctx context.Context, f FileHandle, flags uint32) syscall.Errno +} + +// Flush is called for the close(2) call on a file descriptor. In case +// of a descriptor that was duplicated using dup(2), it may be called +// more than once for the same FileHandle. The default implementation +// forwards to the FileHandle, or if the handle does not support +// FileFlusher, returns OK. +type NodeFlusher interface { + Flush(ctx context.Context, f FileHandle) syscall.Errno +} + +// This is called to before a FileHandle is forgotten. The +// kernel ignores the return value of this method, +// so any cleanup that requires specific synchronization or +// could fail with I/O errors should happen in Flush instead. +// The default implementation forwards to the FileHandle. +type NodeReleaser interface { + Release(ctx context.Context, f FileHandle) syscall.Errno +} + +// Allocate preallocates space for future writes, so they will +// never encounter ESPACE. +type NodeAllocater interface { + Allocate(ctx context.Context, f FileHandle, off uint64, size uint64, mode uint32) syscall.Errno +} + +// CopyFileRange copies data between sections of two files, +// without the data having to pass through the calling process. +type NodeCopyFileRanger interface { + CopyFileRange(ctx context.Context, fhIn FileHandle, + offIn uint64, out *Inode, fhOut FileHandle, offOut uint64, + len uint64, flags uint64) (uint32, syscall.Errno) +} + +// Lseek is used to implement holes: it should return the +// first offset beyond `off` where there is data (SEEK_DATA) +// or where there is a hole (SEEK_HOLE). +type NodeLseeker interface { + Lseek(ctx context.Context, f FileHandle, Off uint64, whence uint32) (uint64, syscall.Errno) +} + +// Getlk returns locks that would conflict with the given input +// lock. If no locks conflict, the output has type L_UNLCK. See +// fcntl(2) for more information. +// If not defined, returns ENOTSUP +type NodeGetlker interface { + Getlk(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) syscall.Errno +} + +// Setlk obtains a lock on a file, or fail if the lock could not +// obtained. See fcntl(2) for more information. If not defined, +// returns ENOTSUP +type NodeSetlker interface { + Setlk(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno +} + +// Setlkw obtains a lock on a file, waiting if necessary. See fcntl(2) +// for more information. If not defined, returns ENOTSUP +type NodeSetlkwer interface { + Setlkw(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno +} + +// DirStream lists directory entries. +type DirStream interface { + // HasNext indicates if there are further entries. HasNext + // might be called on already closed streams. + HasNext() bool + + // Next retrieves the next entry. It is only called if HasNext + // has previously returned true. The Errno return may be used to + // indicate I/O errors + Next() (fuse.DirEntry, syscall.Errno) + + // Close releases resources related to this directory + // stream. + Close() +} + +// Lookup should find a direct child of a directory by the child's name. If +// the entry does not exist, it should return ENOENT and optionally +// set a NegativeTimeout in `out`. If it does exist, it should return +// attribute data in `out` and return the Inode for the child. A new +// inode can be created using `Inode.NewInode`. The new Inode will be +// added to the FS tree automatically if the return status is OK. +// +// If a directory does not implement NodeLookuper, the library looks +// for an existing child with the given name. +// +// The input to a Lookup is {parent directory, name string}. +// +// Lookup, if successful, must return an *Inode. Once the Inode is +// returned to the kernel, the kernel can issue further operations, +// such as Open or Getxattr on that node. +// +// A successful Lookup also returns an EntryOut. Among others, this +// contains file attributes (mode, size, mtime, etc.). +// +// FUSE supports other operations that modify the namespace. For +// example, the Symlink, Create, Mknod, Link methods all create new +// children in directories. Hence, they also return *Inode and must +// populate their fuse.EntryOut arguments. + +// +type NodeLookuper interface { + Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) +} + +// OpenDir opens a directory Inode for reading its +// contents. The actual reading is driven from ReadDir, so +// this method is just for performing sanity/permission +// checks. The default is to return success. +type NodeOpendirer interface { + Opendir(ctx context.Context) syscall.Errno +} + +// ReadDir opens a stream of directory entries. +// +// Readdir essentiallly returns a list of strings, and it is allowed +// for Readdir to return different results from Lookup. For example, +// you can return nothing for Readdir ("ls my-fuse-mount" is empty), +// while still implementing Lookup ("ls my-fuse-mount/a-specific-file" +// shows a single file). +// +// If a directory does not implement NodeReaddirer, a list of +// currently known children from the tree is returned. This means that +// static in-memory file systems need not implement NodeReaddirer. +type NodeReaddirer interface { + Readdir(ctx context.Context) (DirStream, syscall.Errno) +} + +// Mkdir is similar to Lookup, but must create a directory entry and Inode. +// Default is to return EROFS. +type NodeMkdirer interface { + Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) +} + +// Mknod is similar to Lookup, but must create a device entry and Inode. +// Default is to return EROFS. +type NodeMknoder interface { + Mknod(ctx context.Context, name string, mode uint32, dev uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) +} + +// Link is similar to Lookup, but must create a new link to an existing Inode. +// Default is to return EROFS. +type NodeLinker interface { + Link(ctx context.Context, target InodeEmbedder, name string, out *fuse.EntryOut) (node *Inode, errno syscall.Errno) +} + +// Symlink is similar to Lookup, but must create a new symbolic link. +// Default is to return EROFS. +type NodeSymlinker interface { + Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (node *Inode, errno syscall.Errno) +} + +// Create is similar to Lookup, but should create a new +// child. It typically also returns a FileHandle as a +// reference for future reads/writes. +// Default is to return EROFS. +type NodeCreater interface { + Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (node *Inode, fh FileHandle, fuseFlags uint32, errno syscall.Errno) +} + +// Unlink should remove a child from this directory. If the +// return status is OK, the Inode is removed as child in the +// FS tree automatically. Default is to return EROFS. +type NodeUnlinker interface { + Unlink(ctx context.Context, name string) syscall.Errno +} + +// Rmdir is like Unlink but for directories. +// Default is to return EROFS. +type NodeRmdirer interface { + Rmdir(ctx context.Context, name string) syscall.Errno +} + +// Rename should move a child from one directory to a different +// one. The change is effected in the FS tree if the return status is +// OK. Default is to return EROFS. +type NodeRenamer interface { + Rename(ctx context.Context, name string, newParent InodeEmbedder, newName string, flags uint32) syscall.Errno +} + +// FileHandle is a resource identifier for opened files. Usually, a +// FileHandle should implement some of the FileXxxx interfaces. +// +// All of the FileXxxx operations can also be implemented at the +// InodeEmbedder level, for example, one can implement NodeReader +// instead of FileReader. +// +// FileHandles are useful in two cases: First, if the underlying +// storage systems needs a handle for reading/writing. This is the +// case with Unix system calls, which need a file descriptor (See also +// the function `NewLoopbackFile`). Second, it is useful for +// implementing files whose contents are not tied to an inode. For +// example, a file like `/proc/interrupts` has no fixed content, but +// changes on each open call. This means that each file handle must +// have its own view of the content; this view can be tied to a +// FileHandle. Files that have such dynamic content should return the +// FOPEN_DIRECT_IO flag from their `Open` method. See directio_test.go +// for an example. +type FileHandle interface { +} + +// See NodeReleaser. +type FileReleaser interface { + Release(ctx context.Context) syscall.Errno +} + +// See NodeGetattrer. +type FileGetattrer interface { + Getattr(ctx context.Context, out *fuse.AttrOut) syscall.Errno +} + +// See NodeReader. +type FileReader interface { + Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) +} + +// See NodeWriter. +type FileWriter interface { + Write(ctx context.Context, data []byte, off int64) (written uint32, errno syscall.Errno) +} + +// See NodeGetlker. +type FileGetlker interface { + Getlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) syscall.Errno +} + +// See NodeSetlker. +type FileSetlker interface { + Setlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno +} + +// See NodeSetlkwer. +type FileSetlkwer interface { + Setlkw(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno +} + +// See NodeLseeker. +type FileLseeker interface { + Lseek(ctx context.Context, off uint64, whence uint32) (uint64, syscall.Errno) +} + +// See NodeFlusher. +type FileFlusher interface { + Flush(ctx context.Context) syscall.Errno +} + +// See NodeFsync. +type FileFsyncer interface { + Fsync(ctx context.Context, flags uint32) syscall.Errno +} + +// See NodeFsync. +type FileSetattrer interface { + Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno +} + +// See NodeAllocater. +type FileAllocater interface { + Allocate(ctx context.Context, off uint64, size uint64, mode uint32) syscall.Errno +} + +// Options sets options for the entire filesystem +type Options struct { + // MountOptions contain the options for mounting the fuse server + fuse.MountOptions + + // If set to nonnil, this defines the overall entry timeout + // for the file system. See fuse.EntryOut for more information. + EntryTimeout *time.Duration + + // If set to nonnil, this defines the overall attribute + // timeout for the file system. See fuse.EntryOut for more + // information. + AttrTimeout *time.Duration + + // If set to nonnil, this defines the overall entry timeout + // for failed lookups (fuse.ENOENT). See fuse.EntryOut for + // more information. + NegativeTimeout *time.Duration + + // Automatic inode numbers are handed out sequentially + // starting from this number. If unset, use 2^63. + FirstAutomaticIno uint64 + + // OnAdd is an alternative way to specify the OnAdd + // functionality of the root node. + OnAdd func(ctx context.Context) + + // NullPermissions if set, leaves null file permissions + // alone. Otherwise, they are set to 755 (dirs) or 644 (other + // files.), which is necessary for doing a chdir into the FUSE + // directories. + NullPermissions bool + + // If nonzero, replace default (zero) UID with the given UID + UID uint32 + + // If nonzero, replace default (zero) GID with the given GID + GID uint32 +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/bridge.go b/vendor/github.com/hanwen/go-fuse/v2/fs/bridge.go new file mode 100644 index 000000000..4c4991127 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/bridge.go @@ -0,0 +1,972 @@ +// Copyright 2019 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs + +import ( + "context" + "log" + "sync" + "syscall" + "time" + + "github.com/hanwen/go-fuse/v2/fuse" + "github.com/hanwen/go-fuse/v2/internal" +) + +func errnoToStatus(errno syscall.Errno) fuse.Status { + return fuse.Status(errno) +} + +type fileEntry struct { + file FileHandle + + // index into Inode.openFiles + nodeIndex int + + // Protects directory fields. Must be acquired before bridge.mu + mu sync.Mutex + + // Directory + dirStream DirStream + hasOverflow bool + overflow fuse.DirEntry + + wg sync.WaitGroup +} + +type rawBridge struct { + options Options + root *Inode + server *fuse.Server + + // mu protects the following data. Locks for inodes must be + // taken before rawBridge.mu + mu sync.Mutex + nodes map[uint64]*Inode + automaticIno uint64 + + files []*fileEntry + freeFiles []uint32 +} + +// newInode creates creates new inode pointing to ops. +func (b *rawBridge) newInodeUnlocked(ops InodeEmbedder, id StableAttr, persistent bool) *Inode { + b.mu.Lock() + defer b.mu.Unlock() + + if id.Reserved() { + log.Panicf("using reserved ID %d for inode number", id.Ino) + } + + // This ops already was populated. Just return it. + if ops.embed().bridge != nil { + return ops.embed() + } + + if id.Ino == 0 { + for { + id.Ino = b.automaticIno + b.automaticIno++ + _, ok := b.nodes[id.Ino] + if !ok { + break + } + } + } + + // the same node can be looked up through 2 paths in parallel, eg. + // + // root + // / \ + // dir1 dir2 + // \ / + // file + // + // dir1.Lookup("file") and dir2.Lookup("file") are executed + // simultaneously. The matching StableAttrs ensure that we return the + // same node. + old := b.nodes[id.Ino] + if old != nil { + return old + } + + id.Mode = id.Mode &^ 07777 + if id.Mode == 0 { + id.Mode = fuse.S_IFREG + } + + b.nodes[id.Ino] = ops.embed() + initInode(ops.embed(), ops, id, b, persistent) + return ops.embed() +} + +func (b *rawBridge) newInode(ctx context.Context, ops InodeEmbedder, id StableAttr, persistent bool) *Inode { + ch := b.newInodeUnlocked(ops, id, persistent) + if ch != ops.embed() { + return ch + } + + if oa, ok := ops.(NodeOnAdder); ok { + oa.OnAdd(ctx) + } + return ch +} + +// addNewChild inserts the child into the tree. Returns file handle if file != nil. +func (b *rawBridge) addNewChild(parent *Inode, name string, child *Inode, file FileHandle, fileFlags uint32, out *fuse.EntryOut) uint32 { + if name == "." || name == ".." { + log.Panicf("BUG: tried to add virtual entry %q to the actual tree", name) + } + lockNodes(parent, child) + parent.setEntry(name, child) + b.mu.Lock() + + child.lookupCount++ + + var fh uint32 + if file != nil { + fh = b.registerFile(child, file, fileFlags) + } + + out.NodeId = child.stableAttr.Ino + out.Generation = child.stableAttr.Gen + out.Attr.Ino = child.stableAttr.Ino + + b.mu.Unlock() + unlockNodes(parent, child) + return fh +} + +func (b *rawBridge) setEntryOutTimeout(out *fuse.EntryOut) { + b.setAttr(&out.Attr) + if b.options.AttrTimeout != nil && out.AttrTimeout() == 0 { + out.SetAttrTimeout(*b.options.AttrTimeout) + } + if b.options.EntryTimeout != nil && out.EntryTimeout() == 0 { + out.SetEntryTimeout(*b.options.EntryTimeout) + } +} + +func (b *rawBridge) setAttr(out *fuse.Attr) { + if !b.options.NullPermissions && out.Mode&07777 == 0 { + out.Mode |= 0644 + if out.Mode&syscall.S_IFDIR != 0 { + out.Mode |= 0111 + } + } + if b.options.UID != 0 && out.Uid == 0 { + out.Uid = b.options.UID + } + if b.options.GID != 0 && out.Gid == 0 { + out.Gid = b.options.GID + } + setBlocks(out) +} + +func (b *rawBridge) setAttrTimeout(out *fuse.AttrOut) { + if b.options.AttrTimeout != nil && out.Timeout() == 0 { + out.SetTimeout(*b.options.AttrTimeout) + } +} + +// NewNodeFS creates a node based filesystem based on the +// InodeEmbedder instance for the root of the tree. +func NewNodeFS(root InodeEmbedder, opts *Options) fuse.RawFileSystem { + bridge := &rawBridge{ + automaticIno: opts.FirstAutomaticIno, + } + if bridge.automaticIno == 1 { + bridge.automaticIno++ + } + + if bridge.automaticIno == 0 { + bridge.automaticIno = 1 << 63 + } + + if opts != nil { + bridge.options = *opts + } else { + oneSec := time.Second + bridge.options.EntryTimeout = &oneSec + bridge.options.AttrTimeout = &oneSec + } + + initInode(root.embed(), root, + StableAttr{ + Ino: 1, + Mode: fuse.S_IFDIR, + }, + bridge, + false, + ) + bridge.root = root.embed() + bridge.root.lookupCount = 1 + bridge.nodes = map[uint64]*Inode{ + 1: bridge.root, + } + + // Fh 0 means no file handle. + bridge.files = []*fileEntry{{}} + + if opts.OnAdd != nil { + opts.OnAdd(context.Background()) + } else if oa, ok := root.(NodeOnAdder); ok { + oa.OnAdd(context.Background()) + } + + return bridge +} + +func (b *rawBridge) String() string { + return "rawBridge" +} + +func (b *rawBridge) inode(id uint64, fh uint64) (*Inode, *fileEntry) { + b.mu.Lock() + defer b.mu.Unlock() + n, f := b.nodes[id], b.files[fh] + if n == nil { + log.Panicf("unknown node %d", id) + } + return n, f +} + +func (b *rawBridge) Lookup(cancel <-chan struct{}, header *fuse.InHeader, name string, out *fuse.EntryOut) fuse.Status { + parent, _ := b.inode(header.NodeId, 0) + ctx := &fuse.Context{Caller: header.Caller, Cancel: cancel} + child, errno := b.lookup(ctx, parent, name, out) + + if errno != 0 { + if b.options.NegativeTimeout != nil && out.EntryTimeout() == 0 { + out.SetEntryTimeout(*b.options.NegativeTimeout) + } + return errnoToStatus(errno) + } + + child.setEntryOut(out) + b.addNewChild(parent, name, child, nil, 0, out) + b.setEntryOutTimeout(out) + return fuse.OK +} + +func (b *rawBridge) lookup(ctx *fuse.Context, parent *Inode, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) { + if lu, ok := parent.ops.(NodeLookuper); ok { + return lu.Lookup(ctx, name, out) + } + + child := parent.GetChild(name) + if child == nil { + return nil, syscall.ENOENT + } + + if ga, ok := child.ops.(NodeGetattrer); ok { + var a fuse.AttrOut + errno := ga.Getattr(ctx, nil, &a) + if errno == 0 { + out.Attr = a.Attr + } + } + + return child, OK +} + +func (b *rawBridge) Rmdir(cancel <-chan struct{}, header *fuse.InHeader, name string) fuse.Status { + parent, _ := b.inode(header.NodeId, 0) + var errno syscall.Errno + if mops, ok := parent.ops.(NodeRmdirer); ok { + errno = mops.Rmdir(&fuse.Context{Caller: header.Caller, Cancel: cancel}, name) + } + + if errno == 0 { + parent.RmChild(name) + } + return errnoToStatus(errno) +} + +func (b *rawBridge) Unlink(cancel <-chan struct{}, header *fuse.InHeader, name string) fuse.Status { + parent, _ := b.inode(header.NodeId, 0) + var errno syscall.Errno + if mops, ok := parent.ops.(NodeUnlinker); ok { + errno = mops.Unlink(&fuse.Context{Caller: header.Caller, Cancel: cancel}, name) + } + + if errno == 0 { + parent.RmChild(name) + } + return errnoToStatus(errno) +} + +func (b *rawBridge) Mkdir(cancel <-chan struct{}, input *fuse.MkdirIn, name string, out *fuse.EntryOut) fuse.Status { + parent, _ := b.inode(input.NodeId, 0) + + var child *Inode + var errno syscall.Errno + if mops, ok := parent.ops.(NodeMkdirer); ok { + child, errno = mops.Mkdir(&fuse.Context{Caller: input.Caller, Cancel: cancel}, name, input.Mode, out) + } else { + return fuse.ENOTSUP + } + + if errno != 0 { + return errnoToStatus(errno) + } + + if out.Attr.Mode&^07777 == 0 { + out.Attr.Mode |= fuse.S_IFDIR + } + + if out.Attr.Mode&^07777 != fuse.S_IFDIR { + log.Panicf("Mkdir: mode must be S_IFDIR (%o), got %o", fuse.S_IFDIR, out.Attr.Mode) + } + + child.setEntryOut(out) + b.addNewChild(parent, name, child, nil, 0, out) + b.setEntryOutTimeout(out) + return fuse.OK +} + +func (b *rawBridge) Mknod(cancel <-chan struct{}, input *fuse.MknodIn, name string, out *fuse.EntryOut) fuse.Status { + parent, _ := b.inode(input.NodeId, 0) + + var child *Inode + var errno syscall.Errno + if mops, ok := parent.ops.(NodeMknoder); ok { + child, errno = mops.Mknod(&fuse.Context{Caller: input.Caller, Cancel: cancel}, name, input.Mode, input.Rdev, out) + } + + if errno != 0 { + return errnoToStatus(errno) + } + + child.setEntryOut(out) + b.addNewChild(parent, name, child, nil, 0, out) + b.setEntryOutTimeout(out) + return fuse.OK +} + +func (b *rawBridge) Create(cancel <-chan struct{}, input *fuse.CreateIn, name string, out *fuse.CreateOut) fuse.Status { + ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel} + parent, _ := b.inode(input.NodeId, 0) + + var child *Inode + var errno syscall.Errno + var f FileHandle + var flags uint32 + if mops, ok := parent.ops.(NodeCreater); ok { + child, f, flags, errno = mops.Create(ctx, name, input.Flags, input.Mode, &out.EntryOut) + } else { + return fuse.EROFS + } + + if errno != 0 { + if b.options.NegativeTimeout != nil { + out.SetEntryTimeout(*b.options.NegativeTimeout) + } + return errnoToStatus(errno) + } + + out.Fh = uint64(b.addNewChild(parent, name, child, f, input.Flags|syscall.O_CREAT, &out.EntryOut)) + + out.OpenFlags = flags + + child.setEntryOut(&out.EntryOut) + b.setEntryOutTimeout(&out.EntryOut) + return fuse.OK +} + +func (b *rawBridge) Forget(nodeid, nlookup uint64) { + n, _ := b.inode(nodeid, 0) + n.removeRef(nlookup, false) +} + +func (b *rawBridge) SetDebug(debug bool) {} + +func (b *rawBridge) GetAttr(cancel <-chan struct{}, input *fuse.GetAttrIn, out *fuse.AttrOut) fuse.Status { + n, fEntry := b.inode(input.NodeId, input.Fh()) + f := fEntry.file + if f == nil { + // The linux kernel doesnt pass along the file + // descriptor, so we have to fake it here. + // See https://github.com/libfuse/libfuse/issues/62 + b.mu.Lock() + for _, fh := range n.openFiles { + f = b.files[fh].file + b.files[fh].wg.Add(1) + defer b.files[fh].wg.Done() + break + } + b.mu.Unlock() + } + ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel} + return errnoToStatus(b.getattr(ctx, n, f, out)) +} + +func (b *rawBridge) getattr(ctx context.Context, n *Inode, f FileHandle, out *fuse.AttrOut) syscall.Errno { + var errno syscall.Errno + + var fg FileGetattrer + if f != nil { + fg, _ = f.(FileGetattrer) + } + + if fops, ok := n.ops.(NodeGetattrer); ok { + errno = fops.Getattr(ctx, f, out) + } else if fg != nil { + errno = fg.Getattr(ctx, out) + } else { + // We set Mode below, which is the minimum for success + } + + if errno == 0 { + out.Ino = n.stableAttr.Ino + out.Mode = (out.Attr.Mode & 07777) | n.stableAttr.Mode + b.setAttr(&out.Attr) + b.setAttrTimeout(out) + } + return errno +} + +func (b *rawBridge) SetAttr(cancel <-chan struct{}, in *fuse.SetAttrIn, out *fuse.AttrOut) fuse.Status { + ctx := &fuse.Context{Caller: in.Caller, Cancel: cancel} + + fh, _ := in.GetFh() + + n, fEntry := b.inode(in.NodeId, fh) + f := fEntry.file + + var errno = syscall.ENOTSUP + if fops, ok := n.ops.(NodeSetattrer); ok { + errno = fops.Setattr(ctx, f, in, out) + } else if fops, ok := f.(FileSetattrer); ok { + errno = fops.Setattr(ctx, in, out) + } + + out.Mode = n.stableAttr.Mode | (out.Mode & 07777) + return errnoToStatus(errno) +} + +func (b *rawBridge) Rename(cancel <-chan struct{}, input *fuse.RenameIn, oldName string, newName string) fuse.Status { + p1, _ := b.inode(input.NodeId, 0) + p2, _ := b.inode(input.Newdir, 0) + + if mops, ok := p1.ops.(NodeRenamer); ok { + errno := mops.Rename(&fuse.Context{Caller: input.Caller, Cancel: cancel}, oldName, p2.ops, newName, input.Flags) + if errno == 0 { + if input.Flags&RENAME_EXCHANGE != 0 { + p1.ExchangeChild(oldName, p2, newName) + } else { + if ok := p1.MvChild(oldName, p2, newName, true); !ok { + log.Println("MvChild failed") + } + } + } + return errnoToStatus(errno) + } + return fuse.ENOTSUP +} + +func (b *rawBridge) Link(cancel <-chan struct{}, input *fuse.LinkIn, name string, out *fuse.EntryOut) fuse.Status { + parent, _ := b.inode(input.NodeId, 0) + target, _ := b.inode(input.Oldnodeid, 0) + + if mops, ok := parent.ops.(NodeLinker); ok { + child, errno := mops.Link(&fuse.Context{Caller: input.Caller, Cancel: cancel}, target.ops, name, out) + if errno != 0 { + return errnoToStatus(errno) + } + + child.setEntryOut(out) + b.addNewChild(parent, name, child, nil, 0, out) + b.setEntryOutTimeout(out) + return fuse.OK + } + return fuse.ENOTSUP +} + +func (b *rawBridge) Symlink(cancel <-chan struct{}, header *fuse.InHeader, target string, name string, out *fuse.EntryOut) fuse.Status { + parent, _ := b.inode(header.NodeId, 0) + + if mops, ok := parent.ops.(NodeSymlinker); ok { + child, status := mops.Symlink(&fuse.Context{Caller: header.Caller, Cancel: cancel}, target, name, out) + if status != 0 { + return errnoToStatus(status) + } + + b.addNewChild(parent, name, child, nil, 0, out) + child.setEntryOut(out) + b.setEntryOutTimeout(out) + return fuse.OK + } + return fuse.ENOTSUP +} + +func (b *rawBridge) Readlink(cancel <-chan struct{}, header *fuse.InHeader) (out []byte, status fuse.Status) { + n, _ := b.inode(header.NodeId, 0) + + if linker, ok := n.ops.(NodeReadlinker); ok { + result, errno := linker.Readlink(&fuse.Context{Caller: header.Caller, Cancel: cancel}) + if errno != 0 { + return nil, errnoToStatus(errno) + } + + return result, fuse.OK + + } + + return nil, fuse.ENOTSUP +} + +func (b *rawBridge) Access(cancel <-chan struct{}, input *fuse.AccessIn) fuse.Status { + n, _ := b.inode(input.NodeId, 0) + + ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel} + if a, ok := n.ops.(NodeAccesser); ok { + return errnoToStatus(a.Access(ctx, input.Mask)) + } + + // default: check attributes. + caller := input.Caller + + var out fuse.AttrOut + if s := b.getattr(ctx, n, nil, &out); s != 0 { + return errnoToStatus(s) + } + + if !internal.HasAccess(caller.Uid, caller.Gid, out.Uid, out.Gid, out.Mode, input.Mask) { + return fuse.EACCES + } + return fuse.OK +} + +// Extended attributes. + +func (b *rawBridge) GetXAttr(cancel <-chan struct{}, header *fuse.InHeader, attr string, data []byte) (uint32, fuse.Status) { + n, _ := b.inode(header.NodeId, 0) + + if xops, ok := n.ops.(NodeGetxattrer); ok { + nb, errno := xops.Getxattr(&fuse.Context{Caller: header.Caller, Cancel: cancel}, attr, data) + return nb, errnoToStatus(errno) + } + + return 0, fuse.ENOATTR +} + +func (b *rawBridge) ListXAttr(cancel <-chan struct{}, header *fuse.InHeader, dest []byte) (sz uint32, status fuse.Status) { + n, _ := b.inode(header.NodeId, 0) + if xops, ok := n.ops.(NodeListxattrer); ok { + sz, errno := xops.Listxattr(&fuse.Context{Caller: header.Caller, Cancel: cancel}, dest) + return sz, errnoToStatus(errno) + } + return 0, fuse.OK +} + +func (b *rawBridge) SetXAttr(cancel <-chan struct{}, input *fuse.SetXAttrIn, attr string, data []byte) fuse.Status { + n, _ := b.inode(input.NodeId, 0) + if xops, ok := n.ops.(NodeSetxattrer); ok { + return errnoToStatus(xops.Setxattr(&fuse.Context{Caller: input.Caller, Cancel: cancel}, attr, data, input.Flags)) + } + return fuse.ENOATTR +} + +func (b *rawBridge) RemoveXAttr(cancel <-chan struct{}, header *fuse.InHeader, attr string) fuse.Status { + n, _ := b.inode(header.NodeId, 0) + if xops, ok := n.ops.(NodeRemovexattrer); ok { + return errnoToStatus(xops.Removexattr(&fuse.Context{Caller: header.Caller, Cancel: cancel}, attr)) + } + return fuse.ENOATTR +} + +func (b *rawBridge) Open(cancel <-chan struct{}, input *fuse.OpenIn, out *fuse.OpenOut) fuse.Status { + n, _ := b.inode(input.NodeId, 0) + + if op, ok := n.ops.(NodeOpener); ok { + f, flags, errno := op.Open(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.Flags) + if errno != 0 { + return errnoToStatus(errno) + } + + if f != nil { + b.mu.Lock() + defer b.mu.Unlock() + out.Fh = uint64(b.registerFile(n, f, input.Flags)) + } + out.OpenFlags = flags + return fuse.OK + } + + return fuse.ENOTSUP +} + +// registerFile hands out a file handle. Must have bridge.mu +func (b *rawBridge) registerFile(n *Inode, f FileHandle, flags uint32) uint32 { + var fh uint32 + if len(b.freeFiles) > 0 { + last := len(b.freeFiles) - 1 + fh = b.freeFiles[last] + b.freeFiles = b.freeFiles[:last] + } else { + fh = uint32(len(b.files)) + b.files = append(b.files, &fileEntry{}) + } + + fileEntry := b.files[fh] + fileEntry.nodeIndex = len(n.openFiles) + fileEntry.file = f + + n.openFiles = append(n.openFiles, fh) + return fh +} + +func (b *rawBridge) Read(cancel <-chan struct{}, input *fuse.ReadIn, buf []byte) (fuse.ReadResult, fuse.Status) { + n, f := b.inode(input.NodeId, input.Fh) + + if fops, ok := n.ops.(NodeReader); ok { + res, errno := fops.Read(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, buf, int64(input.Offset)) + return res, errnoToStatus(errno) + } + if fr, ok := f.file.(FileReader); ok { + res, errno := fr.Read(&fuse.Context{Caller: input.Caller, Cancel: cancel}, buf, int64(input.Offset)) + return res, errnoToStatus(errno) + } + + return nil, fuse.ENOTSUP +} + +func (b *rawBridge) GetLk(cancel <-chan struct{}, input *fuse.LkIn, out *fuse.LkOut) fuse.Status { + n, f := b.inode(input.NodeId, input.Fh) + + if lops, ok := n.ops.(NodeGetlker); ok { + return errnoToStatus(lops.Getlk(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, input.Owner, &input.Lk, input.LkFlags, &out.Lk)) + } + if gl, ok := f.file.(FileGetlker); ok { + return errnoToStatus(gl.Getlk(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.Owner, &input.Lk, input.LkFlags, &out.Lk)) + } + return fuse.ENOTSUP +} + +func (b *rawBridge) SetLk(cancel <-chan struct{}, input *fuse.LkIn) fuse.Status { + n, f := b.inode(input.NodeId, input.Fh) + if lops, ok := n.ops.(NodeSetlker); ok { + return errnoToStatus(lops.Setlk(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, input.Owner, &input.Lk, input.LkFlags)) + } + if sl, ok := n.ops.(FileSetlker); ok { + return errnoToStatus(sl.Setlk(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.Owner, &input.Lk, input.LkFlags)) + } + return fuse.ENOTSUP +} +func (b *rawBridge) SetLkw(cancel <-chan struct{}, input *fuse.LkIn) fuse.Status { + n, f := b.inode(input.NodeId, input.Fh) + if lops, ok := n.ops.(NodeSetlkwer); ok { + return errnoToStatus(lops.Setlkw(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, input.Owner, &input.Lk, input.LkFlags)) + } + if sl, ok := n.ops.(FileSetlkwer); ok { + return errnoToStatus(sl.Setlkw(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.Owner, &input.Lk, input.LkFlags)) + } + return fuse.ENOTSUP +} + +func (b *rawBridge) Release(cancel <-chan struct{}, input *fuse.ReleaseIn) { + n, f := b.releaseFileEntry(input.NodeId, input.Fh) + if f == nil { + return + } + + f.wg.Wait() + + if r, ok := n.ops.(NodeReleaser); ok { + r.Release(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file) + } else if r, ok := f.file.(FileReleaser); ok { + r.Release(&fuse.Context{Caller: input.Caller, Cancel: cancel}) + } + + b.mu.Lock() + defer b.mu.Unlock() + b.freeFiles = append(b.freeFiles, uint32(input.Fh)) +} + +func (b *rawBridge) ReleaseDir(input *fuse.ReleaseIn) { + _, f := b.releaseFileEntry(input.NodeId, input.Fh) + f.wg.Wait() + + f.mu.Lock() + if f.dirStream != nil { + f.dirStream.Close() + f.dirStream = nil + } + f.mu.Unlock() + + b.mu.Lock() + defer b.mu.Unlock() + b.freeFiles = append(b.freeFiles, uint32(input.Fh)) +} + +func (b *rawBridge) releaseFileEntry(nid uint64, fh uint64) (*Inode, *fileEntry) { + b.mu.Lock() + defer b.mu.Unlock() + + n := b.nodes[nid] + var entry *fileEntry + if fh > 0 { + last := len(n.openFiles) - 1 + entry = b.files[fh] + if last != entry.nodeIndex { + n.openFiles[entry.nodeIndex] = n.openFiles[last] + + b.files[n.openFiles[entry.nodeIndex]].nodeIndex = entry.nodeIndex + } + n.openFiles = n.openFiles[:last] + } + return n, entry +} + +func (b *rawBridge) Write(cancel <-chan struct{}, input *fuse.WriteIn, data []byte) (written uint32, status fuse.Status) { + n, f := b.inode(input.NodeId, input.Fh) + + if wr, ok := n.ops.(NodeWriter); ok { + w, errno := wr.Write(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, data, int64(input.Offset)) + return w, errnoToStatus(errno) + } + if fr, ok := f.file.(FileWriter); ok { + w, errno := fr.Write(&fuse.Context{Caller: input.Caller, Cancel: cancel}, data, int64(input.Offset)) + return w, errnoToStatus(errno) + } + + return 0, fuse.ENOTSUP +} + +func (b *rawBridge) Flush(cancel <-chan struct{}, input *fuse.FlushIn) fuse.Status { + n, f := b.inode(input.NodeId, input.Fh) + if fl, ok := n.ops.(NodeFlusher); ok { + return errnoToStatus(fl.Flush(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file)) + } + if fl, ok := f.file.(FileFlusher); ok { + return errnoToStatus(fl.Flush(&fuse.Context{Caller: input.Caller, Cancel: cancel})) + } + return 0 +} + +func (b *rawBridge) Fsync(cancel <-chan struct{}, input *fuse.FsyncIn) fuse.Status { + n, f := b.inode(input.NodeId, input.Fh) + if fs, ok := n.ops.(NodeFsyncer); ok { + return errnoToStatus(fs.Fsync(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, input.FsyncFlags)) + } + if fs, ok := f.file.(FileFsyncer); ok { + return errnoToStatus(fs.Fsync(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.FsyncFlags)) + } + return fuse.ENOTSUP +} + +func (b *rawBridge) Fallocate(cancel <-chan struct{}, input *fuse.FallocateIn) fuse.Status { + n, f := b.inode(input.NodeId, input.Fh) + if a, ok := n.ops.(NodeAllocater); ok { + return errnoToStatus(a.Allocate(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, input.Offset, input.Length, input.Mode)) + } + if a, ok := n.ops.(FileAllocater); ok { + return errnoToStatus(a.Allocate(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.Offset, input.Length, input.Mode)) + } + return fuse.ENOTSUP +} + +func (b *rawBridge) OpenDir(cancel <-chan struct{}, input *fuse.OpenIn, out *fuse.OpenOut) fuse.Status { + n, _ := b.inode(input.NodeId, 0) + + if od, ok := n.ops.(NodeOpendirer); ok { + errno := od.Opendir(&fuse.Context{Caller: input.Caller, Cancel: cancel}) + if errno != 0 { + return errnoToStatus(errno) + } + } + + b.mu.Lock() + defer b.mu.Unlock() + out.Fh = uint64(b.registerFile(n, nil, 0)) + return fuse.OK +} + +// setStream sets the directory part of f. Must hold f.mu +func (b *rawBridge) setStream(cancel <-chan struct{}, input *fuse.ReadIn, inode *Inode, f *fileEntry) syscall.Errno { + if f.dirStream == nil || input.Offset == 0 { + if f.dirStream != nil { + f.dirStream.Close() + f.dirStream = nil + } + str, errno := b.getStream(&fuse.Context{Caller: input.Caller, Cancel: cancel}, inode) + if errno != 0 { + return errno + } + + f.hasOverflow = false + f.dirStream = str + } + + return 0 +} + +func (b *rawBridge) getStream(ctx context.Context, inode *Inode) (DirStream, syscall.Errno) { + if rd, ok := inode.ops.(NodeReaddirer); ok { + return rd.Readdir(ctx) + } + + r := []fuse.DirEntry{} + for k, ch := range inode.Children() { + r = append(r, fuse.DirEntry{Mode: ch.Mode(), + Name: k, + Ino: ch.StableAttr().Ino}) + } + return NewListDirStream(r), 0 +} + +func (b *rawBridge) ReadDir(cancel <-chan struct{}, input *fuse.ReadIn, out *fuse.DirEntryList) fuse.Status { + n, f := b.inode(input.NodeId, input.Fh) + + f.mu.Lock() + defer f.mu.Unlock() + if errno := b.setStream(cancel, input, n, f); errno != 0 { + return errnoToStatus(errno) + } + + if f.hasOverflow { + // always succeeds. + out.AddDirEntry(f.overflow) + f.hasOverflow = false + } + + for f.dirStream.HasNext() { + e, errno := f.dirStream.Next() + + if errno != 0 { + return errnoToStatus(errno) + } + if !out.AddDirEntry(e) { + f.overflow = e + f.hasOverflow = true + return errnoToStatus(errno) + } + } + + return fuse.OK +} + +func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out *fuse.DirEntryList) fuse.Status { + n, f := b.inode(input.NodeId, input.Fh) + + f.mu.Lock() + defer f.mu.Unlock() + if errno := b.setStream(cancel, input, n, f); errno != 0 { + return errnoToStatus(errno) + } + + ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel} + for f.dirStream.HasNext() || f.hasOverflow { + var e fuse.DirEntry + var errno syscall.Errno + + if f.hasOverflow { + e = f.overflow + f.hasOverflow = false + } else { + e, errno = f.dirStream.Next() + } + + if errno != 0 { + return errnoToStatus(errno) + } + + entryOut := out.AddDirLookupEntry(e) + if entryOut == nil { + f.overflow = e + f.hasOverflow = true + return fuse.OK + } + + // Virtual entries "." and ".." should be part of the + // directory listing, but not part of the filesystem tree. + // The values in EntryOut are ignored by Linux + // (see fuse_direntplus_link() in linux/fs/fuse/readdir.c), so leave + // them at zero-value. + if e.Name == "." || e.Name == ".." { + continue + } + + child, errno := b.lookup(ctx, n, e.Name, entryOut) + if errno != 0 { + if b.options.NegativeTimeout != nil { + entryOut.SetEntryTimeout(*b.options.NegativeTimeout) + } + } else { + b.addNewChild(n, e.Name, child, nil, 0, entryOut) + child.setEntryOut(entryOut) + b.setEntryOutTimeout(entryOut) + if (e.Mode &^ 07777) != (child.stableAttr.Mode &^ 07777) { + // The file type has changed behind our back. Use the new value. + out.FixMode(child.stableAttr.Mode) + } + entryOut.Mode = child.stableAttr.Mode | (entryOut.Mode & 07777) + } + } + + return fuse.OK +} + +func (b *rawBridge) FsyncDir(cancel <-chan struct{}, input *fuse.FsyncIn) fuse.Status { + n, _ := b.inode(input.NodeId, input.Fh) + if fs, ok := n.ops.(NodeFsyncer); ok { + return errnoToStatus(fs.Fsync(&fuse.Context{Caller: input.Caller, Cancel: cancel}, nil, input.FsyncFlags)) + } + + return fuse.ENOTSUP +} + +func (b *rawBridge) StatFs(cancel <-chan struct{}, input *fuse.InHeader, out *fuse.StatfsOut) fuse.Status { + n, _ := b.inode(input.NodeId, 0) + if sf, ok := n.ops.(NodeStatfser); ok { + return errnoToStatus(sf.Statfs(&fuse.Context{Caller: input.Caller, Cancel: cancel}, out)) + } + + // leave zeroed out + return fuse.OK +} + +func (b *rawBridge) Init(s *fuse.Server) { + b.server = s +} + +func (b *rawBridge) CopyFileRange(cancel <-chan struct{}, in *fuse.CopyFileRangeIn) (size uint32, status fuse.Status) { + n1, f1 := b.inode(in.NodeId, in.FhIn) + cfr, ok := n1.ops.(NodeCopyFileRanger) + if !ok { + return 0, fuse.ENOTSUP + } + + n2, f2 := b.inode(in.NodeIdOut, in.FhOut) + + sz, errno := cfr.CopyFileRange(&fuse.Context{Caller: in.Caller, Cancel: cancel}, + f1.file, in.OffIn, n2, f2.file, in.OffOut, in.Len, in.Flags) + return sz, errnoToStatus(errno) +} + +func (b *rawBridge) Lseek(cancel <-chan struct{}, in *fuse.LseekIn, out *fuse.LseekOut) fuse.Status { + n, f := b.inode(in.NodeId, in.Fh) + + ls, ok := n.ops.(NodeLseeker) + if ok { + off, errno := ls.Lseek(&fuse.Context{Caller: in.Caller, Cancel: cancel}, + f.file, in.Offset, in.Whence) + out.Offset = off + return errnoToStatus(errno) + } + if fs, ok := f.file.(FileLseeker); ok { + off, errno := fs.Lseek(&fuse.Context{Caller: in.Caller, Cancel: cancel}, in.Offset, in.Whence) + out.Offset = off + return errnoToStatus(errno) + } + + if in.Whence == _SEEK_DATA || in.Whence == _SEEK_HOLE { + out.Offset = in.Offset + return fuse.OK + } + + return fuse.ENOTSUP +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/constants.go b/vendor/github.com/hanwen/go-fuse/v2/fs/constants.go new file mode 100644 index 000000000..a382f2134 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/constants.go @@ -0,0 +1,29 @@ +// Copyright 2019 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs + +import ( + "syscall" + + "github.com/hanwen/go-fuse/v2/fuse" +) + +// OK is the Errno return value to indicate absense of errors. +var OK = syscall.Errno(0) + +// ToErrno exhumes the syscall.Errno error from wrapped error values. +func ToErrno(err error) syscall.Errno { + s := fuse.ToStatus(err) + return syscall.Errno(s) +} + +// RENAME_EXCHANGE is a flag argument for renameat2() +const RENAME_EXCHANGE = 0x2 + +// seek to the next data +const _SEEK_DATA = 3 + +// seek to the next hole +const _SEEK_HOLE = 4 diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/constants_darwin.go b/vendor/github.com/hanwen/go-fuse/v2/fs/constants_darwin.go new file mode 100644 index 000000000..ece0eeb0d --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/constants_darwin.go @@ -0,0 +1,10 @@ +// Copyright 2019 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs + +import "syscall" + +// ENOATTR indicates that an extended attribute was not present. +var ENOATTR = syscall.ENOATTR diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/constants_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fs/constants_linux.go new file mode 100644 index 000000000..15fecc4a2 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/constants_linux.go @@ -0,0 +1,10 @@ +// Copyright 2019 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs + +import "syscall" + +// ENOATTR indicates that an extended attribute was not present. +var ENOATTR = syscall.ENODATA diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/default.go b/vendor/github.com/hanwen/go-fuse/v2/fs/default.go new file mode 100644 index 000000000..e2f7a67c2 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/default.go @@ -0,0 +1,5 @@ +// Copyright 2019 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/dirstream.go b/vendor/github.com/hanwen/go-fuse/v2/fs/dirstream.go new file mode 100644 index 000000000..363aef6ab --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/dirstream.go @@ -0,0 +1,34 @@ +// Copyright 2019 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs + +import ( + "syscall" + + "github.com/hanwen/go-fuse/v2/fuse" +) + +type dirArray struct { + entries []fuse.DirEntry +} + +func (a *dirArray) HasNext() bool { + return len(a.entries) > 0 +} + +func (a *dirArray) Next() (fuse.DirEntry, syscall.Errno) { + e := a.entries[0] + a.entries = a.entries[1:] + return e, 0 +} + +func (a *dirArray) Close() { + +} + +// NewListDirStream wraps a slice of DirEntry as a DirStream. +func NewListDirStream(list []fuse.DirEntry) DirStream { + return &dirArray{list} +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/dirstream_darwin.go b/vendor/github.com/hanwen/go-fuse/v2/fs/dirstream_darwin.go new file mode 100644 index 000000000..c71029474 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/dirstream_darwin.go @@ -0,0 +1,48 @@ +// Copyright 2019 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs + +import ( + "io" + "os" + "syscall" + + "github.com/hanwen/go-fuse/v2/fuse" +) + +func NewLoopbackDirStream(nm string) (DirStream, syscall.Errno) { + f, err := os.Open(nm) + if err != nil { + return nil, ToErrno(err) + } + defer f.Close() + + var entries []fuse.DirEntry + for { + want := 100 + infos, err := f.Readdir(want) + for _, info := range infos { + s := fuse.ToStatT(info) + if s == nil { + continue + } + + entries = append(entries, fuse.DirEntry{ + Name: info.Name(), + Mode: uint32(s.Mode), + Ino: s.Ino, + }) + } + if len(infos) < want || err == io.EOF { + break + } + + if err != nil { + return nil, ToErrno(err) + } + } + + return &dirArray{entries}, OK +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/dirstream_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fs/dirstream_linux.go new file mode 100644 index 000000000..236a780ad --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/dirstream_linux.go @@ -0,0 +1,93 @@ +// Copyright 2019 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs + +import ( + "sync" + "syscall" + "unsafe" + + "github.com/hanwen/go-fuse/v2/fuse" +) + +type loopbackDirStream struct { + buf []byte + todo []byte + + // Protects fd so we can guard against double close + mu sync.Mutex + fd int +} + +// NewLoopbackDirStream open a directory for reading as a DirStream +func NewLoopbackDirStream(name string) (DirStream, syscall.Errno) { + fd, err := syscall.Open(name, syscall.O_DIRECTORY, 0755) + if err != nil { + return nil, ToErrno(err) + } + + ds := &loopbackDirStream{ + buf: make([]byte, 4096), + fd: fd, + } + + if err := ds.load(); err != 0 { + ds.Close() + return nil, err + } + return ds, OK +} + +func (ds *loopbackDirStream) Close() { + ds.mu.Lock() + defer ds.mu.Unlock() + if ds.fd != -1 { + syscall.Close(ds.fd) + ds.fd = -1 + } +} + +func (ds *loopbackDirStream) HasNext() bool { + ds.mu.Lock() + defer ds.mu.Unlock() + return len(ds.todo) > 0 +} + +func (ds *loopbackDirStream) Next() (fuse.DirEntry, syscall.Errno) { + ds.mu.Lock() + defer ds.mu.Unlock() + de := (*syscall.Dirent)(unsafe.Pointer(&ds.todo[0])) + + nameBytes := ds.todo[unsafe.Offsetof(syscall.Dirent{}.Name):de.Reclen] + ds.todo = ds.todo[de.Reclen:] + + // After the loop, l contains the index of the first '\0'. + l := 0 + for l = range nameBytes { + if nameBytes[l] == 0 { + break + } + } + nameBytes = nameBytes[:l] + result := fuse.DirEntry{ + Ino: de.Ino, + Mode: (uint32(de.Type) << 12), + Name: string(nameBytes), + } + return result, ds.load() +} + +func (ds *loopbackDirStream) load() syscall.Errno { + if len(ds.todo) > 0 { + return OK + } + + n, err := syscall.Getdents(ds.fd, ds.buf) + if err != nil { + return ToErrno(err) + } + ds.todo = ds.buf[:n] + return OK +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/files.go b/vendor/github.com/hanwen/go-fuse/v2/fs/files.go new file mode 100644 index 000000000..67304a9e2 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/files.go @@ -0,0 +1,230 @@ +// Copyright 2019 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs + +import ( + "context" + "sync" + + // "time" + + "syscall" + + "github.com/hanwen/go-fuse/v2/fuse" + "golang.org/x/sys/unix" +) + +// NewLoopbackFile creates a FileHandle out of a file descriptor. All +// operations are implemented. +func NewLoopbackFile(fd int) FileHandle { + return &loopbackFile{fd: fd} +} + +type loopbackFile struct { + mu sync.Mutex + fd int +} + +var _ = (FileHandle)((*loopbackFile)(nil)) +var _ = (FileReleaser)((*loopbackFile)(nil)) +var _ = (FileGetattrer)((*loopbackFile)(nil)) +var _ = (FileReader)((*loopbackFile)(nil)) +var _ = (FileWriter)((*loopbackFile)(nil)) +var _ = (FileGetlker)((*loopbackFile)(nil)) +var _ = (FileSetlker)((*loopbackFile)(nil)) +var _ = (FileSetlkwer)((*loopbackFile)(nil)) +var _ = (FileLseeker)((*loopbackFile)(nil)) +var _ = (FileFlusher)((*loopbackFile)(nil)) +var _ = (FileFsyncer)((*loopbackFile)(nil)) +var _ = (FileSetattrer)((*loopbackFile)(nil)) +var _ = (FileAllocater)((*loopbackFile)(nil)) + +func (f *loopbackFile) Read(ctx context.Context, buf []byte, off int64) (res fuse.ReadResult, errno syscall.Errno) { + f.mu.Lock() + defer f.mu.Unlock() + r := fuse.ReadResultFd(uintptr(f.fd), off, len(buf)) + return r, OK +} + +func (f *loopbackFile) Write(ctx context.Context, data []byte, off int64) (uint32, syscall.Errno) { + f.mu.Lock() + defer f.mu.Unlock() + n, err := syscall.Pwrite(f.fd, data, off) + return uint32(n), ToErrno(err) +} + +func (f *loopbackFile) Release(ctx context.Context) syscall.Errno { + f.mu.Lock() + defer f.mu.Unlock() + if f.fd != -1 { + err := syscall.Close(f.fd) + f.fd = -1 + return ToErrno(err) + } + return syscall.EBADF +} + +func (f *loopbackFile) Flush(ctx context.Context) syscall.Errno { + f.mu.Lock() + defer f.mu.Unlock() + // Since Flush() may be called for each dup'd fd, we don't + // want to really close the file, we just want to flush. This + // is achieved by closing a dup'd fd. + newFd, err := syscall.Dup(f.fd) + + if err != nil { + return ToErrno(err) + } + err = syscall.Close(newFd) + return ToErrno(err) +} + +func (f *loopbackFile) Fsync(ctx context.Context, flags uint32) (errno syscall.Errno) { + f.mu.Lock() + defer f.mu.Unlock() + r := ToErrno(syscall.Fsync(f.fd)) + + return r +} + +const ( + _OFD_GETLK = 36 + _OFD_SETLK = 37 + _OFD_SETLKW = 38 +) + +func (f *loopbackFile) Getlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) (errno syscall.Errno) { + f.mu.Lock() + defer f.mu.Unlock() + flk := syscall.Flock_t{} + lk.ToFlockT(&flk) + errno = ToErrno(syscall.FcntlFlock(uintptr(f.fd), _OFD_GETLK, &flk)) + out.FromFlockT(&flk) + return +} + +func (f *loopbackFile) Setlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) (errno syscall.Errno) { + return f.setLock(ctx, owner, lk, flags, false) +} + +func (f *loopbackFile) Setlkw(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) (errno syscall.Errno) { + return f.setLock(ctx, owner, lk, flags, true) +} + +func (f *loopbackFile) setLock(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32, blocking bool) (errno syscall.Errno) { + f.mu.Lock() + defer f.mu.Unlock() + if (flags & fuse.FUSE_LK_FLOCK) != 0 { + var op int + switch lk.Typ { + case syscall.F_RDLCK: + op = syscall.LOCK_SH + case syscall.F_WRLCK: + op = syscall.LOCK_EX + case syscall.F_UNLCK: + op = syscall.LOCK_UN + default: + return syscall.EINVAL + } + if !blocking { + op |= syscall.LOCK_NB + } + return ToErrno(syscall.Flock(f.fd, op)) + } else { + flk := syscall.Flock_t{} + lk.ToFlockT(&flk) + var op int + if blocking { + op = _OFD_SETLKW + } else { + op = _OFD_SETLK + } + return ToErrno(syscall.FcntlFlock(uintptr(f.fd), op, &flk)) + } +} + +func (f *loopbackFile) Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno { + if errno := f.setAttr(ctx, in); errno != 0 { + return errno + } + + return f.Getattr(ctx, out) +} + +func (f *loopbackFile) setAttr(ctx context.Context, in *fuse.SetAttrIn) syscall.Errno { + f.mu.Lock() + defer f.mu.Unlock() + var errno syscall.Errno + if mode, ok := in.GetMode(); ok { + errno = ToErrno(syscall.Fchmod(f.fd, mode)) + if errno != 0 { + return errno + } + } + + uid32, uOk := in.GetUID() + gid32, gOk := in.GetGID() + if uOk || gOk { + uid := -1 + gid := -1 + + if uOk { + uid = int(uid32) + } + if gOk { + gid = int(gid32) + } + errno = ToErrno(syscall.Fchown(f.fd, uid, gid)) + if errno != 0 { + return errno + } + } + + mtime, mok := in.GetMTime() + atime, aok := in.GetATime() + + if mok || aok { + ap := &atime + mp := &mtime + if !aok { + ap = nil + } + if !mok { + mp = nil + } + errno = f.utimens(ap, mp) + if errno != 0 { + return errno + } + } + + if sz, ok := in.GetSize(); ok { + errno = ToErrno(syscall.Ftruncate(f.fd, int64(sz))) + if errno != 0 { + return errno + } + } + return OK +} + +func (f *loopbackFile) Getattr(ctx context.Context, a *fuse.AttrOut) syscall.Errno { + f.mu.Lock() + defer f.mu.Unlock() + st := syscall.Stat_t{} + err := syscall.Fstat(f.fd, &st) + if err != nil { + return ToErrno(err) + } + a.FromStat(&st) + + return OK +} + +func (f *loopbackFile) Lseek(ctx context.Context, off uint64, whence uint32) (uint64, syscall.Errno) { + f.mu.Lock() + defer f.mu.Unlock() + n, err := unix.Seek(f.fd, int64(off), int(whence)) + return uint64(n), ToErrno(err) +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/files_darwin.go b/vendor/github.com/hanwen/go-fuse/v2/fs/files_darwin.go new file mode 100644 index 000000000..bc232efc8 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/files_darwin.go @@ -0,0 +1,10 @@ +// Copyright 2019 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs + +import "github.com/hanwen/go-fuse/v2/fuse" + +func setBlocks(out *fuse.Attr) { +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/files_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fs/files_linux.go new file mode 100644 index 000000000..a52379f9d --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/files_linux.go @@ -0,0 +1,42 @@ +// Copyright 2019 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs + +import ( + "context" + "syscall" + "time" + + "github.com/hanwen/go-fuse/v2/fuse" +) + +func (f *loopbackFile) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) syscall.Errno { + f.mu.Lock() + defer f.mu.Unlock() + err := syscall.Fallocate(f.fd, mode, int64(off), int64(sz)) + if err != nil { + return ToErrno(err) + } + return OK +} + +// Utimens - file handle based version of loopbackFileSystem.Utimens() +func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno { + var ts [2]syscall.Timespec + ts[0] = fuse.UtimeToTimespec(a) + ts[1] = fuse.UtimeToTimespec(m) + err := futimens(int(f.fd), &ts) + return ToErrno(err) +} + +func setBlocks(out *fuse.Attr) { + if out.Blksize > 0 { + return + } + + out.Blksize = 4096 + pages := (out.Size + 4095) / 4096 + out.Blocks = pages * 8 +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/inode.go b/vendor/github.com/hanwen/go-fuse/v2/fs/inode.go new file mode 100644 index 000000000..f3e735084 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/inode.go @@ -0,0 +1,723 @@ +// Copyright 2019 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs + +import ( + "context" + "fmt" + "log" + "sort" + "strings" + "sync" + "syscall" + "unsafe" + + "github.com/hanwen/go-fuse/v2/fuse" +) + +type parentData struct { + name string + parent *Inode +} + +// StableAttr holds immutable attributes of a object in the filesystem. +type StableAttr struct { + // Each Inode has a type, which does not change over the + // lifetime of the inode, for example fuse.S_IFDIR. The default (0) + // is interpreted as S_IFREG (regular file). + Mode uint32 + + // The inode number must be unique among the currently live + // objects in the file system. It is used to communicate to + // the kernel about this file object. The values uint64(-1), + // and 1 are reserved. When using Ino==0, a unique, sequential + // number is assigned (starting at 2^63 by default) on Inode creation. + Ino uint64 + + // When reusing a previously used inode number for a new + // object, the new object must have a different Gen + // number. This is irrelevant if the FS is not exported over + // NFS + Gen uint64 +} + +// Reserved returns if the StableAttr is using reserved Inode numbers. +func (i *StableAttr) Reserved() bool { + return i.Ino == 1 || i.Ino == ^uint64(0) +} + +// Inode is a node in VFS tree. Inodes are one-to-one mapped to +// Operations instances, which is the extension interface for file +// systems. One can create fully-formed trees of Inodes ahead of time +// by creating "persistent" Inodes. +// +// The Inode struct contains a lock, so it should not be +// copied. Inodes should be obtained by calling Inode.NewInode() or +// Inode.NewPersistentInode(). +type Inode struct { + stableAttr StableAttr + + ops InodeEmbedder + bridge *rawBridge + + // Following data is mutable. + + // protected by bridge.mu + + // file handles. + openFiles []uint32 + + // mu protects the following mutable fields. When locking + // multiple Inodes, locks must be acquired using + // lockNodes/unlockNodes + mu sync.Mutex + + // persistent indicates that this node should not be removed + // from the tree, even if there are no live references. This + // must be set on creation, and can only be changed to false + // by calling removeRef. + persistent bool + + // changeCounter increments every time the mutable state + // (lookupCount, persistent, children, parents) protected by + // mu is modified. + // + // This is used in places where we have to relock inode into inode + // group lock, and after locking the group we have to check if inode + // did not changed, and if it changed - retry the operation. + changeCounter uint32 + + // Number of kernel refs to this node. + lookupCount uint64 + + children map[string]*Inode + parents map[parentData]struct{} +} + +func (n *Inode) IsDir() bool { + return n.stableAttr.Mode&syscall.S_IFDIR != 0 +} + +func (n *Inode) embed() *Inode { + return n +} + +func (n *Inode) EmbeddedInode() *Inode { + return n +} + +func initInode(n *Inode, ops InodeEmbedder, attr StableAttr, bridge *rawBridge, persistent bool) { + n.ops = ops + n.stableAttr = attr + n.bridge = bridge + n.persistent = persistent + n.parents = make(map[parentData]struct{}) + if attr.Mode == fuse.S_IFDIR { + n.children = make(map[string]*Inode) + } +} + +// Set node ID and mode in EntryOut +func (n *Inode) setEntryOut(out *fuse.EntryOut) { + out.NodeId = n.stableAttr.Ino + out.Ino = n.stableAttr.Ino + out.Mode = (out.Attr.Mode & 07777) | n.stableAttr.Mode +} + +// StableAttr returns the (Ino, Gen) tuple for this node. +func (n *Inode) StableAttr() StableAttr { + return n.stableAttr +} + +// Mode returns the filetype +func (n *Inode) Mode() uint32 { + return n.stableAttr.Mode +} + +// Returns the root of the tree +func (n *Inode) Root() *Inode { + return n.bridge.root +} + +// Returns whether this is the root of the tree +func (n *Inode) IsRoot() bool { + return n.bridge.root == n +} + +func modeStr(m uint32) string { + return map[uint32]string{ + syscall.S_IFREG: "reg", + syscall.S_IFLNK: "lnk", + syscall.S_IFDIR: "dir", + syscall.S_IFSOCK: "soc", + syscall.S_IFIFO: "pip", + syscall.S_IFCHR: "chr", + syscall.S_IFBLK: "blk", + }[m] +} + +// debugString is used for debugging. Racy. +func (n *Inode) String() string { + n.mu.Lock() + defer n.mu.Unlock() + var ss []string + for nm, ch := range n.children { + ss = append(ss, fmt.Sprintf("%q=i%d[%s]", nm, ch.stableAttr.Ino, modeStr(ch.stableAttr.Mode))) + } + + return fmt.Sprintf("i%d (%s): %s", n.stableAttr.Ino, modeStr(n.stableAttr.Mode), strings.Join(ss, ",")) +} + +// sortNodes rearranges inode group in consistent order. +// +// The nodes are ordered by their in-RAM address, which gives consistency +// property: for any A and B inodes, sortNodes will either always order A < B, +// or always order A > B. +// +// See lockNodes where this property is used to avoid deadlock when taking +// locks on inode group. +func sortNodes(ns []*Inode) { + sort.Slice(ns, func(i, j int) bool { + return nodeLess(ns[i], ns[j]) + }) +} + +func nodeLess(a, b *Inode) bool { + return uintptr(unsafe.Pointer(a)) < uintptr(unsafe.Pointer(b)) +} + +// lockNodes locks group of inodes. +// +// It always lock the inodes in the same order - to avoid deadlocks. +// It also avoids locking an inode more than once, if it was specified multiple times. +// An example when an inode might be given multiple times is if dir/a and dir/b +// are hardlinked to the same inode and the caller needs to take locks on dir children. +func lockNodes(ns ...*Inode) { + sortNodes(ns) + + // The default value nil prevents trying to lock nil nodes. + var nprev *Inode + for _, n := range ns { + if n != nprev { + n.mu.Lock() + nprev = n + } + } +} + +// lockNode2 locks a and b in order consistent with lockNodes. +func lockNode2(a, b *Inode) { + if a == b { + a.mu.Lock() + } else if nodeLess(a, b) { + a.mu.Lock() + b.mu.Lock() + } else { + b.mu.Lock() + a.mu.Lock() + } +} + +// unlockNode2 unlocks a and b +func unlockNode2(a, b *Inode) { + if a == b { + a.mu.Unlock() + } else { + a.mu.Unlock() + b.mu.Unlock() + } +} + +// unlockNodes releases locks taken by lockNodes. +func unlockNodes(ns ...*Inode) { + // we don't need to unlock in the same order that was used in lockNodes. + // however it still helps to have nodes sorted to avoid duplicates. + sortNodes(ns) + + var nprev *Inode + for _, n := range ns { + if n != nprev { + n.mu.Unlock() + nprev = n + } + } +} + +// Forgotten returns true if the kernel holds no references to this +// inode. This can be used for background cleanup tasks, since the +// kernel has no way of reviving forgotten nodes by its own +// initiative. +func (n *Inode) Forgotten() bool { + n.mu.Lock() + defer n.mu.Unlock() + return n.lookupCount == 0 && len(n.parents) == 0 && !n.persistent +} + +// Operations returns the object implementing the file system +// operations. +func (n *Inode) Operations() InodeEmbedder { + return n.ops +} + +// Path returns a path string to the inode relative to the root. +func (n *Inode) Path(root *Inode) string { + var segments []string + p := n + for p != nil && p != root { + var pd parentData + + // We don't try to take all locks at the same time, because + // the caller won't use the "path" string under lock anyway. + p.mu.Lock() + // Select an arbitrary parent + for pd = range p.parents { + break + } + p.mu.Unlock() + if pd.parent == nil { + break + } + + segments = append(segments, pd.name) + p = pd.parent + } + + if p == nil { + // NOSUBMIT - should replace rather than append? + segments = append(segments, ".deleted") + } + + i := 0 + j := len(segments) - 1 + + for i < j { + segments[i], segments[j] = segments[j], segments[i] + i++ + j-- + } + + path := strings.Join(segments, "/") + return path +} + +// setEntry does `iparent[name] = ichild` linking. +// +// setEntry must not be called simultaneously for any of iparent or ichild. +// This, for example could be satisfied if both iparent and ichild are locked, +// but it could be also valid if only iparent is locked and ichild was just +// created and only one goroutine keeps referencing it. +func (iparent *Inode) setEntry(name string, ichild *Inode) { + ichild.parents[parentData{name, iparent}] = struct{}{} + iparent.children[name] = ichild + ichild.changeCounter++ + iparent.changeCounter++ +} + +// NewPersistentInode returns an Inode whose lifetime is not in +// control of the kernel. +// +// When the kernel is short on memory, it will forget cached file +// system information (directory entries and inode metadata). This is +// announced with FORGET messages. There are no guarantees if or when +// this happens. When it happens, these are handled transparently by +// go-fuse: all Inodes created with NewInode are released +// automatically. NewPersistentInode creates inodes that go-fuse keeps +// in memory, even if the kernel is not interested in them. This is +// convenient for building static trees up-front. +func (n *Inode) NewPersistentInode(ctx context.Context, node InodeEmbedder, id StableAttr) *Inode { + return n.newInode(ctx, node, id, true) +} + +// ForgetPersistent manually marks the node as no longer important. If +// it has no children, and if the kernel as no references, the nodes +// gets removed from the tree. +func (n *Inode) ForgetPersistent() { + n.removeRef(0, true) +} + +// NewInode returns an inode for the given InodeEmbedder. The mode +// should be standard mode argument (eg. S_IFDIR). The inode number in +// id.Ino argument is used to implement hard-links. If it is given, +// and another node with the same ID is known, that will node will be +// returned, and the passed-in `node` is ignored. +func (n *Inode) NewInode(ctx context.Context, node InodeEmbedder, id StableAttr) *Inode { + return n.newInode(ctx, node, id, false) +} + +func (n *Inode) newInode(ctx context.Context, ops InodeEmbedder, id StableAttr, persistent bool) *Inode { + return n.bridge.newInode(ctx, ops, id, persistent) +} + +// removeRef decreases references. Returns if this operation caused +// the node to be forgotten (for kernel references), and whether it is +// live (ie. was not dropped from the tree) +func (n *Inode) removeRef(nlookup uint64, dropPersistence bool) (forgotten bool, live bool) { + var lockme []*Inode + var parents []parentData + + n.mu.Lock() + if nlookup > 0 && dropPersistence { + log.Panic("only one allowed") + } else if nlookup > 0 { + + n.lookupCount -= nlookup + n.changeCounter++ + } else if dropPersistence && n.persistent { + n.persistent = false + n.changeCounter++ + } + +retry: + for { + lockme = append(lockme[:0], n) + parents = parents[:0] + nChange := n.changeCounter + live = n.lookupCount > 0 || len(n.children) > 0 || n.persistent + forgotten = n.lookupCount == 0 + for p := range n.parents { + parents = append(parents, p) + lockme = append(lockme, p.parent) + } + n.mu.Unlock() + + if live { + return forgotten, live + } + + lockNodes(lockme...) + if n.changeCounter != nChange { + unlockNodes(lockme...) + // could avoid unlocking and relocking n here. + n.mu.Lock() + continue retry + } + + for _, p := range parents { + delete(p.parent.children, p.name) + p.parent.changeCounter++ + } + n.parents = map[parentData]struct{}{} + n.changeCounter++ + + if n.lookupCount != 0 { + panic("lookupCount changed") + } + + n.bridge.mu.Lock() + delete(n.bridge.nodes, n.stableAttr.Ino) + n.bridge.mu.Unlock() + + unlockNodes(lockme...) + break + } + + for _, p := range lockme { + if p != n { + p.removeRef(0, false) + } + } + return forgotten, false +} + +// GetChild returns a child node with the given name, or nil if the +// directory has no child by that name. +func (n *Inode) GetChild(name string) *Inode { + n.mu.Lock() + defer n.mu.Unlock() + return n.children[name] +} + +// AddChild adds a child to this node. If overwrite is false, fail if +// the destination already exists. +func (n *Inode) AddChild(name string, ch *Inode, overwrite bool) (success bool) { + if len(name) == 0 { + log.Panic("empty name for inode") + } + +retry: + for { + lockNode2(n, ch) + prev, ok := n.children[name] + parentCounter := n.changeCounter + if !ok { + n.children[name] = ch + ch.parents[parentData{name, n}] = struct{}{} + n.changeCounter++ + ch.changeCounter++ + unlockNode2(n, ch) + return true + } + unlockNode2(n, ch) + if !overwrite { + return false + } + lockme := [3]*Inode{n, ch, prev} + + lockNodes(lockme[:]...) + if parentCounter != n.changeCounter { + unlockNodes(lockme[:]...) + continue retry + } + + delete(prev.parents, parentData{name, n}) + n.children[name] = ch + ch.parents[parentData{name, n}] = struct{}{} + n.changeCounter++ + ch.changeCounter++ + prev.changeCounter++ + unlockNodes(lockme[:]...) + + return true + } +} + +// Children returns the list of children of this directory Inode. +func (n *Inode) Children() map[string]*Inode { + n.mu.Lock() + defer n.mu.Unlock() + r := make(map[string]*Inode, len(n.children)) + for k, v := range n.children { + r[k] = v + } + return r +} + +// Parents returns a parent of this Inode, or nil if this Inode is +// deleted or is the root +func (n *Inode) Parent() (string, *Inode) { + n.mu.Lock() + defer n.mu.Unlock() + for k := range n.parents { + return k.name, k.parent + } + return "", nil +} + +// RmAllChildren recursively drops a tree, forgetting all persistent +// nodes. +func (n *Inode) RmAllChildren() { + for { + chs := n.Children() + if len(chs) == 0 { + break + } + for nm, ch := range chs { + ch.RmAllChildren() + n.RmChild(nm) + } + } + n.removeRef(0, true) +} + +// RmChild removes multiple children. Returns whether the removal +// succeeded and whether the node is still live afterward. The removal +// is transactional: it only succeeds if all names are children, and +// if they all were removed successfully. If the removal was +// successful, and there are no children left, the node may be removed +// from the FS tree. In that case, RmChild returns live==false. +func (n *Inode) RmChild(names ...string) (success, live bool) { + var lockme []*Inode + +retry: + for { + n.mu.Lock() + lockme = append(lockme[:0], n) + nChange := n.changeCounter + for _, nm := range names { + ch := n.children[nm] + if ch == nil { + n.mu.Unlock() + return false, true + } + lockme = append(lockme, ch) + } + n.mu.Unlock() + + lockNodes(lockme...) + if n.changeCounter != nChange { + unlockNodes(lockme...) + // could avoid unlocking and relocking n here. + n.mu.Lock() + continue retry + } + + for _, nm := range names { + ch := n.children[nm] + delete(n.children, nm) + delete(ch.parents, parentData{nm, n}) + + ch.changeCounter++ + } + n.changeCounter++ + + live = n.lookupCount > 0 || len(n.children) > 0 || n.persistent + unlockNodes(lockme...) + + // removal successful + break + } + + if !live { + _, live := n.removeRef(0, false) + return true, live + } + + return true, true +} + +// MvChild executes a rename. If overwrite is set, a child at the +// destination will be overwritten, should it exist. +func (n *Inode) MvChild(old string, newParent *Inode, newName string, overwrite bool) bool { + if len(newName) == 0 { + log.Panicf("empty newName for MvChild") + } + +retry: + for { + lockNode2(n, newParent) + counter1 := n.changeCounter + counter2 := newParent.changeCounter + + oldChild := n.children[old] + destChild := newParent.children[newName] + unlockNode2(n, newParent) + + if destChild != nil && !overwrite { + return false + } + + lockNodes(n, newParent, oldChild, destChild) + if counter2 != newParent.changeCounter || counter1 != n.changeCounter { + unlockNodes(n, newParent, oldChild, destChild) + continue retry + } + + if oldChild != nil { + delete(n.children, old) + delete(oldChild.parents, parentData{old, n}) + n.changeCounter++ + oldChild.changeCounter++ + } + + if destChild != nil { + // This can cause the child to be slated for + // removal; see below + delete(newParent.children, newName) + delete(destChild.parents, parentData{newName, newParent}) + destChild.changeCounter++ + newParent.changeCounter++ + } + + if oldChild != nil { + newParent.children[newName] = oldChild + newParent.changeCounter++ + + oldChild.parents[parentData{newName, newParent}] = struct{}{} + oldChild.changeCounter++ + } + + unlockNodes(n, newParent, oldChild, destChild) + + if destChild != nil { + destChild.removeRef(0, false) + } + return true + } +} + +// ExchangeChild swaps the entries at (n, oldName) and (newParent, +// newName). +func (n *Inode) ExchangeChild(oldName string, newParent *Inode, newName string) { + oldParent := n +retry: + for { + lockNode2(oldParent, newParent) + counter1 := oldParent.changeCounter + counter2 := newParent.changeCounter + + oldChild := oldParent.children[oldName] + destChild := newParent.children[newName] + unlockNode2(oldParent, newParent) + + if destChild == oldChild { + return + } + + lockNodes(oldParent, newParent, oldChild, destChild) + if counter2 != newParent.changeCounter || counter1 != oldParent.changeCounter { + unlockNodes(oldParent, newParent, oldChild, destChild) + continue retry + } + + // Detach + if oldChild != nil { + delete(oldParent.children, oldName) + delete(oldChild.parents, parentData{oldName, oldParent}) + oldParent.changeCounter++ + oldChild.changeCounter++ + } + + if destChild != nil { + delete(newParent.children, newName) + delete(destChild.parents, parentData{newName, newParent}) + destChild.changeCounter++ + newParent.changeCounter++ + } + + // Attach + if oldChild != nil { + newParent.children[newName] = oldChild + newParent.changeCounter++ + + oldChild.parents[parentData{newName, newParent}] = struct{}{} + oldChild.changeCounter++ + } + + if destChild != nil { + oldParent.children[oldName] = oldChild + oldParent.changeCounter++ + + destChild.parents[parentData{oldName, oldParent}] = struct{}{} + destChild.changeCounter++ + } + unlockNodes(oldParent, newParent, oldChild, destChild) + return + } +} + +// NotifyEntry notifies the kernel that data for a (directory, name) +// tuple should be invalidated. On next access, a LOOKUP operation +// will be started. +func (n *Inode) NotifyEntry(name string) syscall.Errno { + status := n.bridge.server.EntryNotify(n.stableAttr.Ino, name) + return syscall.Errno(status) +} + +// NotifyDelete notifies the kernel that the given inode was removed +// from this directory as entry under the given name. It is equivalent +// to NotifyEntry, but also sends an event to inotify watchers. +func (n *Inode) NotifyDelete(name string, child *Inode) syscall.Errno { + // XXX arg ordering? + return syscall.Errno(n.bridge.server.DeleteNotify(n.stableAttr.Ino, child.stableAttr.Ino, name)) + +} + +// NotifyContent notifies the kernel that content under the given +// inode should be flushed from buffers. +func (n *Inode) NotifyContent(off, sz int64) syscall.Errno { + // XXX how does this work for directories? + return syscall.Errno(n.bridge.server.InodeNotify(n.stableAttr.Ino, off, sz)) +} + +// WriteCache stores data in the kernel cache. +func (n *Inode) WriteCache(offset int64, data []byte) syscall.Errno { + return syscall.Errno(n.bridge.server.InodeNotifyStoreCache(n.stableAttr.Ino, offset, data)) +} + +// ReadCache reads data from the kernel cache. +func (n *Inode) ReadCache(offset int64, dest []byte) (count int, errno syscall.Errno) { + c, s := n.bridge.server.InodeRetrieveCache(n.stableAttr.Ino, offset, dest) + return c, syscall.Errno(s) +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/loopback.go b/vendor/github.com/hanwen/go-fuse/v2/fs/loopback.go new file mode 100644 index 000000000..5cb6acc5c --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/loopback.go @@ -0,0 +1,389 @@ +// Copyright 2019 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs + +import ( + "context" + "os" + "path/filepath" + "syscall" + + "github.com/hanwen/go-fuse/v2/fuse" +) + +type loopbackRoot struct { + loopbackNode + + rootPath string + rootDev uint64 +} + +type loopbackNode struct { + Inode +} + +var _ = (NodeStatfser)((*loopbackNode)(nil)) +var _ = (NodeStatfser)((*loopbackNode)(nil)) +var _ = (NodeGetattrer)((*loopbackNode)(nil)) +var _ = (NodeGetxattrer)((*loopbackNode)(nil)) +var _ = (NodeSetxattrer)((*loopbackNode)(nil)) +var _ = (NodeRemovexattrer)((*loopbackNode)(nil)) +var _ = (NodeListxattrer)((*loopbackNode)(nil)) +var _ = (NodeReadlinker)((*loopbackNode)(nil)) +var _ = (NodeOpener)((*loopbackNode)(nil)) +var _ = (NodeCopyFileRanger)((*loopbackNode)(nil)) +var _ = (NodeLookuper)((*loopbackNode)(nil)) +var _ = (NodeOpendirer)((*loopbackNode)(nil)) +var _ = (NodeReaddirer)((*loopbackNode)(nil)) +var _ = (NodeMkdirer)((*loopbackNode)(nil)) +var _ = (NodeMknoder)((*loopbackNode)(nil)) +var _ = (NodeLinker)((*loopbackNode)(nil)) +var _ = (NodeSymlinker)((*loopbackNode)(nil)) +var _ = (NodeUnlinker)((*loopbackNode)(nil)) +var _ = (NodeRmdirer)((*loopbackNode)(nil)) +var _ = (NodeRenamer)((*loopbackNode)(nil)) + +func (n *loopbackNode) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno { + s := syscall.Statfs_t{} + err := syscall.Statfs(n.path(), &s) + if err != nil { + return ToErrno(err) + } + out.FromStatfsT(&s) + return OK +} + +func (n *loopbackRoot) Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno { + st := syscall.Stat_t{} + err := syscall.Stat(n.rootPath, &st) + if err != nil { + return ToErrno(err) + } + out.FromStat(&st) + return OK +} + +func (n *loopbackNode) root() *loopbackRoot { + return n.Root().Operations().(*loopbackRoot) +} + +func (n *loopbackNode) path() string { + path := n.Path(nil) + return filepath.Join(n.root().rootPath, path) +} + +func (n *loopbackNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) { + p := filepath.Join(n.path(), name) + + st := syscall.Stat_t{} + err := syscall.Lstat(p, &st) + if err != nil { + return nil, ToErrno(err) + } + + out.Attr.FromStat(&st) + node := &loopbackNode{} + ch := n.NewInode(ctx, node, n.root().idFromStat(&st)) + return ch, 0 +} + +func (n *loopbackNode) Mknod(ctx context.Context, name string, mode, rdev uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) { + p := filepath.Join(n.path(), name) + err := syscall.Mknod(p, mode, int(rdev)) + if err != nil { + return nil, ToErrno(err) + } + st := syscall.Stat_t{} + if err := syscall.Lstat(p, &st); err != nil { + syscall.Rmdir(p) + return nil, ToErrno(err) + } + + out.Attr.FromStat(&st) + + node := &loopbackNode{} + ch := n.NewInode(ctx, node, n.root().idFromStat(&st)) + + return ch, 0 +} + +func (n *loopbackNode) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) { + p := filepath.Join(n.path(), name) + err := os.Mkdir(p, os.FileMode(mode)) + if err != nil { + return nil, ToErrno(err) + } + st := syscall.Stat_t{} + if err := syscall.Lstat(p, &st); err != nil { + syscall.Rmdir(p) + return nil, ToErrno(err) + } + + out.Attr.FromStat(&st) + + node := &loopbackNode{} + ch := n.NewInode(ctx, node, n.root().idFromStat(&st)) + + return ch, 0 +} + +func (n *loopbackNode) Rmdir(ctx context.Context, name string) syscall.Errno { + p := filepath.Join(n.path(), name) + err := syscall.Rmdir(p) + return ToErrno(err) +} + +func (n *loopbackNode) Unlink(ctx context.Context, name string) syscall.Errno { + p := filepath.Join(n.path(), name) + err := syscall.Unlink(p) + return ToErrno(err) +} + +func toLoopbackNode(op InodeEmbedder) *loopbackNode { + if r, ok := op.(*loopbackRoot); ok { + return &r.loopbackNode + } + return op.(*loopbackNode) +} + +func (n *loopbackNode) Rename(ctx context.Context, name string, newParent InodeEmbedder, newName string, flags uint32) syscall.Errno { + newParentLoopback := toLoopbackNode(newParent) + if flags&RENAME_EXCHANGE != 0 { + return n.renameExchange(name, newParentLoopback, newName) + } + + p1 := filepath.Join(n.path(), name) + + p2 := filepath.Join(newParentLoopback.path(), newName) + err := os.Rename(p1, p2) + return ToErrno(err) +} + +func (r *loopbackRoot) idFromStat(st *syscall.Stat_t) StableAttr { + // We compose an inode number by the underlying inode, and + // mixing in the device number. In traditional filesystems, + // the inode numbers are small. The device numbers are also + // small (typically 16 bit). Finally, we mask out the root + // device number of the root, so a loopback FS that does not + // encompass multiple mounts will reflect the inode numbers of + // the underlying filesystem + swapped := (uint64(st.Dev) << 32) | (uint64(st.Dev) >> 32) + swappedRootDev := (r.rootDev << 32) | (r.rootDev >> 32) + return StableAttr{ + Mode: uint32(st.Mode), + Gen: 1, + // This should work well for traditional backing FSes, + // not so much for other go-fuse FS-es + Ino: (swapped ^ swappedRootDev) ^ st.Ino, + } +} + +var _ = (NodeCreater)((*loopbackNode)(nil)) + +func (n *loopbackNode) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (inode *Inode, fh FileHandle, fuseFlags uint32, errno syscall.Errno) { + p := filepath.Join(n.path(), name) + flags = flags &^ syscall.O_APPEND + fd, err := syscall.Open(p, int(flags)|os.O_CREATE, mode) + if err != nil { + return nil, nil, 0, ToErrno(err) + } + + st := syscall.Stat_t{} + if err := syscall.Fstat(fd, &st); err != nil { + syscall.Close(fd) + return nil, nil, 0, ToErrno(err) + } + + node := &loopbackNode{} + ch := n.NewInode(ctx, node, n.root().idFromStat(&st)) + lf := NewLoopbackFile(fd) + + out.FromStat(&st) + return ch, lf, 0, 0 +} + +func (n *loopbackNode) Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) { + p := filepath.Join(n.path(), name) + err := syscall.Symlink(target, p) + if err != nil { + return nil, ToErrno(err) + } + st := syscall.Stat_t{} + if syscall.Lstat(p, &st); err != nil { + syscall.Unlink(p) + return nil, ToErrno(err) + } + node := &loopbackNode{} + ch := n.NewInode(ctx, node, n.root().idFromStat(&st)) + + out.Attr.FromStat(&st) + return ch, 0 +} + +func (n *loopbackNode) Link(ctx context.Context, target InodeEmbedder, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) { + + p := filepath.Join(n.path(), name) + targetNode := toLoopbackNode(target) + err := syscall.Link(targetNode.path(), p) + if err != nil { + return nil, ToErrno(err) + } + st := syscall.Stat_t{} + if syscall.Lstat(p, &st); err != nil { + syscall.Unlink(p) + return nil, ToErrno(err) + } + node := &loopbackNode{} + ch := n.NewInode(ctx, node, n.root().idFromStat(&st)) + + out.Attr.FromStat(&st) + return ch, 0 +} + +func (n *loopbackNode) Readlink(ctx context.Context) ([]byte, syscall.Errno) { + p := n.path() + + for l := 256; ; l *= 2 { + buf := make([]byte, l) + sz, err := syscall.Readlink(p, buf) + if err != nil { + return nil, ToErrno(err) + } + + if sz < len(buf) { + return buf[:sz], 0 + } + } +} + +func (n *loopbackNode) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) { + flags = flags &^ syscall.O_APPEND + p := n.path() + f, err := syscall.Open(p, int(flags), 0) + if err != nil { + return nil, 0, ToErrno(err) + } + lf := NewLoopbackFile(f) + return lf, 0, 0 +} + +func (n *loopbackNode) Opendir(ctx context.Context) syscall.Errno { + fd, err := syscall.Open(n.path(), syscall.O_DIRECTORY, 0755) + if err != nil { + return ToErrno(err) + } + syscall.Close(fd) + return OK +} + +func (n *loopbackNode) Readdir(ctx context.Context) (DirStream, syscall.Errno) { + return NewLoopbackDirStream(n.path()) +} + +func (n *loopbackNode) Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno { + if f != nil { + return f.(FileGetattrer).Getattr(ctx, out) + } + p := n.path() + + var err error = nil + st := syscall.Stat_t{} + err = syscall.Lstat(p, &st) + if err != nil { + return ToErrno(err) + } + out.FromStat(&st) + return OK +} + +var _ = (NodeSetattrer)((*loopbackNode)(nil)) + +func (n *loopbackNode) Setattr(ctx context.Context, f FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno { + p := n.path() + fsa, ok := f.(FileSetattrer) + if ok && fsa != nil { + fsa.Setattr(ctx, in, out) + } else { + if m, ok := in.GetMode(); ok { + if err := syscall.Chmod(p, m); err != nil { + return ToErrno(err) + } + } + + uid, uok := in.GetUID() + gid, gok := in.GetGID() + if uok || gok { + suid := -1 + sgid := -1 + if uok { + suid = int(uid) + } + if gok { + sgid = int(gid) + } + if err := syscall.Chown(p, suid, sgid); err != nil { + return ToErrno(err) + } + } + + mtime, mok := in.GetMTime() + atime, aok := in.GetATime() + + if mok || aok { + + ap := &atime + mp := &mtime + if !aok { + ap = nil + } + if !mok { + mp = nil + } + var ts [2]syscall.Timespec + ts[0] = fuse.UtimeToTimespec(ap) + ts[1] = fuse.UtimeToTimespec(mp) + + if err := syscall.UtimesNano(p, ts[:]); err != nil { + return ToErrno(err) + } + } + + if sz, ok := in.GetSize(); ok { + if err := syscall.Truncate(p, int64(sz)); err != nil { + return ToErrno(err) + } + } + } + + fga, ok := f.(FileGetattrer) + if ok && fga != nil { + fga.Getattr(ctx, out) + } else { + st := syscall.Stat_t{} + err := syscall.Lstat(p, &st) + if err != nil { + return ToErrno(err) + } + out.FromStat(&st) + } + return OK +} + +// NewLoopback returns a root node for a loopback file system whose +// root is at the given root. This node implements all NodeXxxxer +// operations available. +func NewLoopbackRoot(root string) (InodeEmbedder, error) { + var st syscall.Stat_t + err := syscall.Stat(root, &st) + if err != nil { + return nil, err + } + + n := &loopbackRoot{ + rootPath: root, + rootDev: uint64(st.Dev), + } + return n, nil +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/loopback_darwin.go b/vendor/github.com/hanwen/go-fuse/v2/fs/loopback_darwin.go new file mode 100644 index 000000000..40eabee09 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/loopback_darwin.go @@ -0,0 +1,118 @@ +// +build darwin + +// Copyright 2019 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs + +import ( + "context" + "syscall" + "time" + "unsafe" + + "github.com/hanwen/go-fuse/v2/fuse" + "github.com/hanwen/go-fuse/v2/internal/utimens" +) + +func (n *loopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) { + return 0, syscall.ENOSYS +} + +func (n *loopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno { + return syscall.ENOSYS +} + +func (n *loopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno { + return syscall.ENOSYS +} + +func (n *loopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) { + return 0, syscall.ENOSYS +} + +func (n *loopbackNode) renameExchange(name string, newparent *loopbackNode, newName string) syscall.Errno { + return syscall.ENOSYS +} + +func (f *loopbackFile) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) syscall.Errno { + // TODO: Handle `mode` parameter. + + // From `man fcntl` on OSX: + // The F_PREALLOCATE command operates on the following structure: + // + // typedef struct fstore { + // u_int32_t fst_flags; /* IN: flags word */ + // int fst_posmode; /* IN: indicates offset field */ + // off_t fst_offset; /* IN: start of the region */ + // off_t fst_length; /* IN: size of the region */ + // off_t fst_bytesalloc; /* OUT: number of bytes allocated */ + // } fstore_t; + // + // The flags (fst_flags) for the F_PREALLOCATE command are as follows: + // + // F_ALLOCATECONTIG Allocate contiguous space. + // + // F_ALLOCATEALL Allocate all requested space or no space at all. + // + // The position modes (fst_posmode) for the F_PREALLOCATE command indicate how to use the offset field. The modes are as fol- + // lows: + // + // F_PEOFPOSMODE Allocate from the physical end of file. + // + // F_VOLPOSMODE Allocate from the volume offset. + + k := struct { + Flags uint32 // u_int32_t + Posmode int64 // int + Offset int64 // off_t + Length int64 // off_t + Bytesalloc int64 // off_t + }{ + 0, + 0, + int64(off), + int64(sz), + 0, + } + + // Linux version for reference: + // err := syscall.Fallocate(int(f.File.Fd()), mode, int64(off), int64(sz)) + _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, uintptr(f.fd), uintptr(syscall.F_PREALLOCATE), uintptr(unsafe.Pointer(&k))) + + return errno +} + +// timeToTimeval - Convert time.Time to syscall.Timeval +// +// Note: This does not use syscall.NsecToTimespec because +// that does not work properly for times before 1970, +// see https://github.com/golang/go/issues/12777 +func timeToTimeval(t *time.Time) syscall.Timeval { + var tv syscall.Timeval + tv.Usec = int32(t.Nanosecond() / 1000) + tv.Sec = t.Unix() + return tv +} + +// MacOS before High Sierra lacks utimensat() and UTIME_OMIT. +// We emulate using utimes() and extra Getattr() calls. +func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno { + var attr fuse.AttrOut + if a == nil || m == nil { + errno := f.Getattr(context.Background(), &attr) + if errno != 0 { + return errno + } + } + tv := utimens.Fill(a, m, &attr.Attr) + err := syscall.Futimes(int(f.fd), tv) + return ToErrno(err) +} + +func (n *loopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle, + offIn uint64, out *Inode, fhOut FileHandle, offOut uint64, + len uint64, flags uint64) (uint32, syscall.Errno) { + return 0, syscall.ENOSYS +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/loopback_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fs/loopback_linux.go new file mode 100644 index 000000000..89359ce3e --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/loopback_linux.go @@ -0,0 +1,86 @@ +// +build linux + +// Copyright 2019 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs + +import ( + "context" + "syscall" + + "golang.org/x/sys/unix" +) + +func (n *loopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) { + sz, err := syscall.Getxattr(n.path(), attr, dest) + return uint32(sz), ToErrno(err) +} + +func (n *loopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno { + err := syscall.Setxattr(n.path(), attr, data, int(flags)) + return ToErrno(err) +} + +func (n *loopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno { + err := syscall.Removexattr(n.path(), attr) + return ToErrno(err) +} + +func (n *loopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) { + sz, err := syscall.Listxattr(n.path(), dest) + return uint32(sz), ToErrno(err) +} + +func (n *loopbackNode) renameExchange(name string, newparent *loopbackNode, newName string) syscall.Errno { + fd1, err := syscall.Open(n.path(), syscall.O_DIRECTORY, 0) + if err != nil { + return ToErrno(err) + } + defer syscall.Close(fd1) + fd2, err := syscall.Open(newparent.path(), syscall.O_DIRECTORY, 0) + defer syscall.Close(fd2) + if err != nil { + return ToErrno(err) + } + + var st syscall.Stat_t + if err := syscall.Fstat(fd1, &st); err != nil { + return ToErrno(err) + } + + // Double check that nodes didn't change from under us. + inode := &n.Inode + if inode.Root() != inode && inode.StableAttr().Ino != n.root().idFromStat(&st).Ino { + return syscall.EBUSY + } + if err := syscall.Fstat(fd2, &st); err != nil { + return ToErrno(err) + } + + newinode := &newparent.Inode + if newinode.Root() != newinode && newinode.StableAttr().Ino != n.root().idFromStat(&st).Ino { + return syscall.EBUSY + } + + return ToErrno(unix.Renameat2(fd1, name, fd2, newName, unix.RENAME_EXCHANGE)) +} + +func (n *loopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle, + offIn uint64, out *Inode, fhOut FileHandle, offOut uint64, + len uint64, flags uint64) (uint32, syscall.Errno) { + lfIn, ok := fhIn.(*loopbackFile) + if !ok { + return 0, syscall.ENOTSUP + } + lfOut, ok := fhOut.(*loopbackFile) + if !ok { + return 0, syscall.ENOTSUP + } + + signedOffIn := int64(offIn) + signedOffOut := int64(offOut) + count, err := unix.CopyFileRange(lfIn.fd, &signedOffIn, lfOut.fd, &signedOffOut, int(len), int(flags)) + return uint32(count), ToErrno(err) +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/mem.go b/vendor/github.com/hanwen/go-fuse/v2/fs/mem.go new file mode 100644 index 000000000..dd1e46978 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/mem.go @@ -0,0 +1,103 @@ +// Copyright 2019 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs + +import ( + "context" + "sync" + "syscall" + + "github.com/hanwen/go-fuse/v2/fuse" +) + +// MemRegularFile is a filesystem node that holds a read-only data +// slice in memory. +type MemRegularFile struct { + Inode + + mu sync.Mutex + Data []byte + Attr fuse.Attr +} + +var _ = (NodeOpener)((*MemRegularFile)(nil)) +var _ = (NodeReader)((*MemRegularFile)(nil)) +var _ = (NodeWriter)((*MemRegularFile)(nil)) +var _ = (NodeSetattrer)((*MemRegularFile)(nil)) +var _ = (NodeFlusher)((*MemRegularFile)(nil)) + +func (f *MemRegularFile) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) { + return nil, fuse.FOPEN_KEEP_CACHE, OK +} + +func (f *MemRegularFile) Write(ctx context.Context, fh FileHandle, data []byte, off int64) (uint32, syscall.Errno) { + f.mu.Lock() + defer f.mu.Unlock() + end := int64(len(data)) + off + if int64(len(f.Data)) < end { + n := make([]byte, end) + copy(n, f.Data) + f.Data = n + } + + copy(f.Data[off:off+int64(len(data))], data) + + return uint32(len(data)), 0 +} + +var _ = (NodeGetattrer)((*MemRegularFile)(nil)) + +func (f *MemRegularFile) Getattr(ctx context.Context, fh FileHandle, out *fuse.AttrOut) syscall.Errno { + f.mu.Lock() + defer f.mu.Unlock() + out.Attr = f.Attr + out.Attr.Size = uint64(len(f.Data)) + return OK +} + +func (f *MemRegularFile) Setattr(ctx context.Context, fh FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno { + f.mu.Lock() + defer f.mu.Unlock() + if sz, ok := in.GetSize(); ok { + f.Data = f.Data[:sz] + } + out.Attr = f.Attr + out.Size = uint64(len(f.Data)) + return OK +} + +func (f *MemRegularFile) Flush(ctx context.Context, fh FileHandle) syscall.Errno { + return 0 +} + +func (f *MemRegularFile) Read(ctx context.Context, fh FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) { + f.mu.Lock() + defer f.mu.Unlock() + end := int(off) + len(dest) + if end > len(f.Data) { + end = len(f.Data) + } + return fuse.ReadResultData(f.Data[off:end]), OK +} + +// MemSymlink is an inode holding a symlink in memory. +type MemSymlink struct { + Inode + Attr fuse.Attr + Data []byte +} + +var _ = (NodeReadlinker)((*MemSymlink)(nil)) + +func (l *MemSymlink) Readlink(ctx context.Context) ([]byte, syscall.Errno) { + return l.Data, OK +} + +var _ = (NodeGetattrer)((*MemSymlink)(nil)) + +func (l *MemSymlink) Getattr(ctx context.Context, fh FileHandle, out *fuse.AttrOut) syscall.Errno { + out.Attr = l.Attr + return OK +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/mount.go b/vendor/github.com/hanwen/go-fuse/v2/fs/mount.go new file mode 100644 index 000000000..187d6e28a --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/mount.go @@ -0,0 +1,40 @@ +// Copyright 2019 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs + +import ( + "time" + + "github.com/hanwen/go-fuse/v2/fuse" +) + +// Mount mounts the given NodeFS on the directory, and starts serving +// requests. This is a convenience wrapper around NewNodeFS and +// fuse.NewServer. If nil is given as options, default settings are +// applied, which are 1 second entry and attribute timeout. +func Mount(dir string, root InodeEmbedder, options *Options) (*fuse.Server, error) { + if options == nil { + oneSec := time.Second + options = &Options{ + EntryTimeout: &oneSec, + AttrTimeout: &oneSec, + } + } + + rawFS := NewNodeFS(root, options) + server, err := fuse.NewServer(rawFS, dir, &options.MountOptions) + if err != nil { + return nil, err + } + + go server.Serve() + if err := server.WaitMount(); err != nil { + // we don't shutdown the serve loop. If the mount does + // not succeed, the loop won't work and exit. + return nil, err + } + + return server, nil +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fs/syscall_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fs/syscall_linux.go new file mode 100644 index 000000000..3fed2630e --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fs/syscall_linux.go @@ -0,0 +1,20 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs + +import ( + "syscall" + "unsafe" +) + +// futimens - futimens(3) calls utimensat(2) with "pathname" set to null and +// "flags" set to zero +func futimens(fd int, times *[2]syscall.Timespec) (err error) { + _, _, e1 := syscall.Syscall6(syscall.SYS_UTIMENSAT, uintptr(fd), 0, uintptr(unsafe.Pointer(times)), uintptr(0), 0, 0) + if e1 != 0 { + err = syscall.Errno(e1) + } + return +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/.gitignore b/vendor/github.com/hanwen/go-fuse/v2/fuse/.gitignore new file mode 100644 index 000000000..13df91618 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/.gitignore @@ -0,0 +1 @@ +version.gen.go diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/api.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/api.go new file mode 100644 index 000000000..f07eb5949 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/api.go @@ -0,0 +1,274 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package fuse provides APIs to implement filesystems in +// userspace in terms of raw FUSE protocol. +// +// A filesystem is implemented by implementing its server that provides a +// RawFileSystem interface. Typically the server embeds +// NewDefaultRawFileSystem() and implements only subset of filesystem methods: +// +// type MyFS struct { +// fuse.RawFileSystem +// ... +// } +// +// func NewMyFS() *MyFS { +// return &MyFS{ +// RawFileSystem: fuse.NewDefaultRawFileSystem(), +// ... +// } +// } +// +// // Mkdir implements "mkdir" request handler. +// // +// // For other requests - not explicitly implemented by MyFS - ENOSYS +// // will be typically returned to client. +// func (fs *MyFS) Mkdir(...) { +// ... +// } +// +// Then the filesystem can be mounted and served to a client (typically OS +// kernel) by creating Server: +// +// fs := NewMyFS() // implements RawFileSystem +// fssrv, err := fuse.NewServer(fs, mountpoint, &fuse.MountOptions{...}) +// if err != nil { +// ... +// } +// +// and letting the server do its work: +// +// // either synchronously - .Serve() blocks until the filesystem is unmounted. +// fssrv.Serve() +// +// // or in the background - .Serve() is spawned in another goroutine, but +// // before interacting with fssrv from current context we have to wait +// // until the filesystem mounting is complete. +// go fssrv.Serve() +// err = fssrv.WaitMount() +// if err != nil { +// ... +// } +// +// The server will serve clients by dispatching their requests to the +// filesystem implementation and conveying responses back. For example "mkdir" +// FUSE request dispatches to call +// +// fs.Mkdir(*MkdirIn, ..., *EntryOut) +// +// "stat" to call +// +// fs.GetAttr(*GetAttrIn, *AttrOut) +// +// etc. Please refer to RawFileSystem documentation for details. +// +// Typically, each call of the API happens in its own +// goroutine, so take care to make the file system thread-safe. +// +// +// Higher level interfaces +// +// As said above this packages provides way to implement filesystems in terms of +// raw FUSE protocol. Additionally packages nodefs and pathfs provide ways to +// implement filesystem at higher levels: +// +// Package github.com/hanwen/go-fuse/fuse/nodefs provides way to implement +// filesystems in terms of inodes. This resembles kernel's idea of what a +// filesystem looks like. +// +// Package github.com/hanwen/go-fuse/fuse/pathfs provides way to implement +// filesystems in terms of path names. Working with path names is somewhat +// easier compared to inodes, however renames can be racy. Do not use pathfs if +// you care about correctness. +package fuse + +// Types for users to implement. + +// The result of Read is an array of bytes, but for performance +// reasons, we can also return data as a file-descriptor/offset/size +// tuple. If the backing store for a file is another filesystem, this +// reduces the amount of copying between the kernel and the FUSE +// server. The ReadResult interface captures both cases. +type ReadResult interface { + // Returns the raw bytes for the read, possibly using the + // passed buffer. The buffer should be larger than the return + // value from Size. + Bytes(buf []byte) ([]byte, Status) + + // Size returns how many bytes this return value takes at most. + Size() int + + // Done() is called after sending the data to the kernel. + Done() +} + +type MountOptions struct { + AllowOther bool + + // Options are passed as -o string to fusermount. + Options []string + + // Default is _DEFAULT_BACKGROUND_TASKS, 12. This numbers + // controls the allowed number of requests that relate to + // async I/O. Concurrency for synchronous I/O is not limited. + MaxBackground int + + // Write size to use. If 0, use default. This number is + // capped at the kernel maximum. + MaxWrite int + + // Max read ahead to use. If 0, use default. This number is + // capped at the kernel maximum. + MaxReadAhead int + + // If IgnoreSecurityLabels is set, all security related xattr + // requests will return NO_DATA without passing through the + // user defined filesystem. You should only set this if you + // file system implements extended attributes, and you are not + // interested in security labels. + IgnoreSecurityLabels bool // ignoring labels should be provided as a fusermount mount option. + + // If RememberInodes is set, we will never forget inodes. + // This may be useful for NFS. + RememberInodes bool + + // Values shown in "df -T" and friends + // First column, "Filesystem" + FsName string + + // Second column, "Type", will be shown as "fuse." + Name + Name string + + // If set, wrap the file system in a single-threaded locking wrapper. + SingleThreaded bool + + // If set, return ENOSYS for Getxattr calls, so the kernel does not issue any + // Xattr operations at all. + DisableXAttrs bool + + // If set, print debugging information. + Debug bool + + // If set, ask kernel to forward file locks to FUSE. If using, + // you must implement the GetLk/SetLk/SetLkw methods. + EnableLocks bool + + // If set, ask kernel not to do automatic data cache invalidation. + // The filesystem is fully responsible for invalidating data cache. + ExplicitDataCacheControl bool +} + +// RawFileSystem is an interface close to the FUSE wire protocol. +// +// Unless you really know what you are doing, you should not implement +// this, but rather the nodefs.Node or pathfs.FileSystem interfaces; the +// details of getting interactions with open files, renames, and threading +// right etc. are somewhat tricky and not very interesting. +// +// Each FUSE request results in a corresponding method called by Server. +// Several calls may be made simultaneously, because the server typically calls +// each method in separate goroutine. +// +// A null implementation is provided by NewDefaultRawFileSystem. +// +// After a successful FUSE API call returns, you may not read input or +// write output data: for performance reasons, memory is reused for +// following requests, and reading/writing the request data will lead +// to race conditions. If you spawn a background routine from a FUSE +// API call, any incoming request data it wants to reference should be +// copied over. +// +// If a FUSE API call is canceled (which is signaled by closing the +// `cancel` channel), the API call should return EINTR. In this case, +// the outstanding request data is not reused, so the API call may +// return EINTR without ensuring that child contexts have successfully +// completed. +type RawFileSystem interface { + String() string + + // If called, provide debug output through the log package. + SetDebug(debug bool) + + // Lookup is called by the kernel when the VFS wants to know + // about a file inside a directory. Many lookup calls can + // occur in parallel, but only one call happens for each (dir, + // name) pair. + Lookup(cancel <-chan struct{}, header *InHeader, name string, out *EntryOut) (status Status) + + // Forget is called when the kernel discards entries from its + // dentry cache. This happens on unmount, and when the kernel + // is short on memory. Since it is not guaranteed to occur at + // any moment, and since there is no return value, Forget + // should not do I/O, as there is no channel to report back + // I/O errors. + Forget(nodeid, nlookup uint64) + + // Attributes. + GetAttr(cancel <-chan struct{}, input *GetAttrIn, out *AttrOut) (code Status) + SetAttr(cancel <-chan struct{}, input *SetAttrIn, out *AttrOut) (code Status) + + // Modifying structure. + Mknod(cancel <-chan struct{}, input *MknodIn, name string, out *EntryOut) (code Status) + Mkdir(cancel <-chan struct{}, input *MkdirIn, name string, out *EntryOut) (code Status) + Unlink(cancel <-chan struct{}, header *InHeader, name string) (code Status) + Rmdir(cancel <-chan struct{}, header *InHeader, name string) (code Status) + Rename(cancel <-chan struct{}, input *RenameIn, oldName string, newName string) (code Status) + Link(cancel <-chan struct{}, input *LinkIn, filename string, out *EntryOut) (code Status) + + Symlink(cancel <-chan struct{}, header *InHeader, pointedTo string, linkName string, out *EntryOut) (code Status) + Readlink(cancel <-chan struct{}, header *InHeader) (out []byte, code Status) + Access(cancel <-chan struct{}, input *AccessIn) (code Status) + + // Extended attributes. + + // GetXAttr reads an extended attribute, and should return the + // number of bytes. If the buffer is too small, return ERANGE, + // with the required buffer size. + GetXAttr(cancel <-chan struct{}, header *InHeader, attr string, dest []byte) (sz uint32, code Status) + + // ListXAttr lists extended attributes as '\0' delimited byte + // slice, and return the number of bytes. If the buffer is too + // small, return ERANGE, with the required buffer size. + ListXAttr(cancel <-chan struct{}, header *InHeader, dest []byte) (uint32, Status) + + // SetAttr writes an extended attribute. + SetXAttr(cancel <-chan struct{}, input *SetXAttrIn, attr string, data []byte) Status + + // RemoveXAttr removes an extended attribute. + RemoveXAttr(cancel <-chan struct{}, header *InHeader, attr string) (code Status) + + // File handling. + Create(cancel <-chan struct{}, input *CreateIn, name string, out *CreateOut) (code Status) + Open(cancel <-chan struct{}, input *OpenIn, out *OpenOut) (status Status) + Read(cancel <-chan struct{}, input *ReadIn, buf []byte) (ReadResult, Status) + Lseek(cancel <-chan struct{}, in *LseekIn, out *LseekOut) Status + + // File locking + GetLk(cancel <-chan struct{}, input *LkIn, out *LkOut) (code Status) + SetLk(cancel <-chan struct{}, input *LkIn) (code Status) + SetLkw(cancel <-chan struct{}, input *LkIn) (code Status) + + Release(cancel <-chan struct{}, input *ReleaseIn) + Write(cancel <-chan struct{}, input *WriteIn, data []byte) (written uint32, code Status) + CopyFileRange(cancel <-chan struct{}, input *CopyFileRangeIn) (written uint32, code Status) + + Flush(cancel <-chan struct{}, input *FlushIn) Status + Fsync(cancel <-chan struct{}, input *FsyncIn) (code Status) + Fallocate(cancel <-chan struct{}, input *FallocateIn) (code Status) + + // Directory handling + OpenDir(cancel <-chan struct{}, input *OpenIn, out *OpenOut) (status Status) + ReadDir(cancel <-chan struct{}, input *ReadIn, out *DirEntryList) Status + ReadDirPlus(cancel <-chan struct{}, input *ReadIn, out *DirEntryList) Status + ReleaseDir(input *ReleaseIn) + FsyncDir(cancel <-chan struct{}, input *FsyncIn) (code Status) + + StatFs(cancel <-chan struct{}, input *InHeader, out *StatfsOut) (code Status) + + // This is called on processing the first request. The + // filesystem implementation can use the server argument to + // talk back to the kernel (through notify methods). + Init(*Server) +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/attr.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/attr.go new file mode 100644 index 000000000..99d171593 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/attr.go @@ -0,0 +1,79 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "os" + "syscall" + "time" +) + +func (a *Attr) IsFifo() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFIFO } + +// IsChar reports whether the FileInfo describes a character special file. +func (a *Attr) IsChar() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFCHR } + +// IsDir reports whether the FileInfo describes a directory. +func (a *Attr) IsDir() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFDIR } + +// IsBlock reports whether the FileInfo describes a block special file. +func (a *Attr) IsBlock() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFBLK } + +// IsRegular reports whether the FileInfo describes a regular file. +func (a *Attr) IsRegular() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFREG } + +// IsSymlink reports whether the FileInfo describes a symbolic link. +func (a *Attr) IsSymlink() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFLNK } + +// IsSocket reports whether the FileInfo describes a socket. +func (a *Attr) IsSocket() bool { return (uint32(a.Mode) & syscall.S_IFMT) == syscall.S_IFSOCK } + +func (a *Attr) SetTimes(access *time.Time, mod *time.Time, chstatus *time.Time) { + if access != nil { + a.Atime = uint64(access.Unix()) + a.Atimensec = uint32(access.Nanosecond()) + } + if mod != nil { + a.Mtime = uint64(mod.Unix()) + a.Mtimensec = uint32(mod.Nanosecond()) + } + if chstatus != nil { + a.Ctime = uint64(chstatus.Unix()) + a.Ctimensec = uint32(chstatus.Nanosecond()) + } +} + +func (a *Attr) ChangeTime() time.Time { + return time.Unix(int64(a.Ctime), int64(a.Ctimensec)) +} + +func (a *Attr) AccessTime() time.Time { + return time.Unix(int64(a.Atime), int64(a.Atimensec)) +} + +func (a *Attr) ModTime() time.Time { + return time.Unix(int64(a.Mtime), int64(a.Mtimensec)) +} + +func ToStatT(f os.FileInfo) *syscall.Stat_t { + s, _ := f.Sys().(*syscall.Stat_t) + if s != nil { + return s + } + return nil +} + +func ToAttr(f os.FileInfo) *Attr { + if f == nil { + return nil + } + s := ToStatT(f) + if s != nil { + a := &Attr{} + a.FromStat(s) + return a + } + return nil +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/attr_darwin.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/attr_darwin.go new file mode 100644 index 000000000..40cb0720f --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/attr_darwin.go @@ -0,0 +1,26 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "syscall" +) + +func (a *Attr) FromStat(s *syscall.Stat_t) { + a.Ino = uint64(s.Ino) + a.Size = uint64(s.Size) + a.Blocks = uint64(s.Blocks) + a.Atime = uint64(s.Atimespec.Sec) + a.Atimensec = uint32(s.Atimespec.Nsec) + a.Mtime = uint64(s.Mtimespec.Sec) + a.Mtimensec = uint32(s.Mtimespec.Nsec) + a.Ctime = uint64(s.Ctimespec.Sec) + a.Ctimensec = uint32(s.Ctimespec.Nsec) + a.Mode = uint32(s.Mode) + a.Nlink = uint32(s.Nlink) + a.Uid = uint32(s.Uid) + a.Gid = uint32(s.Gid) + a.Rdev = uint32(s.Rdev) +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/attr_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/attr_linux.go new file mode 100644 index 000000000..223676a95 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/attr_linux.go @@ -0,0 +1,27 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "syscall" +) + +func (a *Attr) FromStat(s *syscall.Stat_t) { + a.Ino = uint64(s.Ino) + a.Size = uint64(s.Size) + a.Blocks = uint64(s.Blocks) + a.Atime = uint64(s.Atim.Sec) + a.Atimensec = uint32(s.Atim.Nsec) + a.Mtime = uint64(s.Mtim.Sec) + a.Mtimensec = uint32(s.Mtim.Nsec) + a.Ctime = uint64(s.Ctim.Sec) + a.Ctimensec = uint32(s.Ctim.Nsec) + a.Mode = s.Mode + a.Nlink = uint32(s.Nlink) + a.Uid = uint32(s.Uid) + a.Gid = uint32(s.Gid) + a.Rdev = uint32(s.Rdev) + a.Blksize = uint32(s.Blksize) +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/bufferpool.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/bufferpool.go new file mode 100644 index 000000000..a0882c858 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/bufferpool.go @@ -0,0 +1,69 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "os" + "sync" +) + +// bufferPool implements explicit memory management. It is used for +// minimizing the GC overhead of communicating with the kernel. +type bufferPool struct { + lock sync.Mutex + + // For each page size multiple a list of slice pointers. + buffersBySize []*sync.Pool +} + +var pageSize = os.Getpagesize() + +func (p *bufferPool) getPool(pageCount int) *sync.Pool { + p.lock.Lock() + for len(p.buffersBySize) < pageCount+1 { + p.buffersBySize = append(p.buffersBySize, nil) + } + if p.buffersBySize[pageCount] == nil { + p.buffersBySize[pageCount] = &sync.Pool{ + New: func() interface{} { return make([]byte, pageSize*pageCount) }, + } + } + pool := p.buffersBySize[pageCount] + p.lock.Unlock() + return pool +} + +// AllocBuffer creates a buffer of at least the given size. After use, +// it should be deallocated with FreeBuffer(). +func (p *bufferPool) AllocBuffer(size uint32) []byte { + sz := int(size) + if sz < pageSize { + sz = pageSize + } + + if sz%pageSize != 0 { + sz += pageSize + } + pages := sz / pageSize + + b := p.getPool(pages).Get().([]byte) + return b[:size] +} + +// FreeBuffer takes back a buffer if it was allocated through +// AllocBuffer. It is not an error to call FreeBuffer() on a slice +// obtained elsewhere. +func (p *bufferPool) FreeBuffer(slice []byte) { + if slice == nil { + return + } + if cap(slice)%pageSize != 0 || cap(slice) == 0 { + return + } + pages := cap(slice) / pageSize + slice = slice[:cap(slice)] + + p.getPool(pages).Put(slice) +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/constants.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/constants.go new file mode 100644 index 000000000..d8416858f --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/constants.go @@ -0,0 +1,37 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "os" + "syscall" +) + +const ( + FUSE_ROOT_ID = 1 + + FUSE_UNKNOWN_INO = 0xffffffff + + CUSE_UNRESTRICTED_IOCTL = (1 << 0) + + FUSE_LK_FLOCK = (1 << 0) + + FUSE_IOCTL_MAX_IOV = 256 + + FUSE_POLL_SCHEDULE_NOTIFY = (1 << 0) + + CUSE_INIT_INFO_MAX = 4096 + + S_IFDIR = syscall.S_IFDIR + S_IFREG = syscall.S_IFREG + S_IFLNK = syscall.S_IFLNK + S_IFIFO = syscall.S_IFIFO + + CUSE_INIT = 4096 + + O_ANYWRITE = uint32(os.O_WRONLY | os.O_RDWR | os.O_APPEND | os.O_CREATE | os.O_TRUNC) + + logicalBlockSize = 512 +) diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/constants_freebsd.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/constants_freebsd.go new file mode 100644 index 000000000..5fb606919 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/constants_freebsd.go @@ -0,0 +1,9 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +// arbitrary values +const syscall_O_LARGEFILE = 1 << 29 +const syscall_O_NOATIME = 1 << 30 diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/constants_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/constants_linux.go new file mode 100644 index 000000000..29455e4f6 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/constants_linux.go @@ -0,0 +1,12 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "syscall" +) + +const syscall_O_LARGEFILE = syscall.O_LARGEFILE +const syscall_O_NOATIME = syscall.O_NOATIME diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/context.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/context.go new file mode 100644 index 000000000..fbc6a0ae7 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/context.go @@ -0,0 +1,61 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "context" + "time" +) + +// Context passes along cancelation signal and request data (PID, GID, +// UID). The name of this class predates the standard "context" +// package from Go, but it does implement the context.Context +// interface. +// +// When a FUSE request is canceled, the API routine should respond by +// returning the EINTR status code. +type Context struct { + Caller + Cancel <-chan struct{} +} + +func (c *Context) Deadline() (time.Time, bool) { + return time.Time{}, false +} + +func (c *Context) Done() <-chan struct{} { + return c.Cancel +} + +func (c *Context) Err() error { + select { + case <-c.Cancel: + return context.Canceled + default: + return nil + } +} + +type callerKeyType struct{} + +var callerKey callerKeyType + +func FromContext(ctx context.Context) (*Caller, bool) { + v, ok := ctx.Value(callerKey).(*Caller) + return v, ok +} + +func NewContext(ctx context.Context, caller *Caller) context.Context { + return context.WithValue(ctx, callerKey, caller) +} + +func (c *Context) Value(key interface{}) interface{} { + if key == callerKey { + return &c.Caller + } + return nil +} + +var _ = context.Context((*Context)(nil)) diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/defaultraw.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/defaultraw.go new file mode 100644 index 000000000..df109bdcf --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/defaultraw.go @@ -0,0 +1,168 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "os" +) + +// NewDefaultRawFileSystem returns ENOSYS (not implemented) for all +// operations. +func NewDefaultRawFileSystem() RawFileSystem { + return (*defaultRawFileSystem)(nil) +} + +type defaultRawFileSystem struct{} + +func (fs *defaultRawFileSystem) Init(*Server) { +} + +func (fs *defaultRawFileSystem) String() string { + return os.Args[0] +} + +func (fs *defaultRawFileSystem) SetDebug(dbg bool) { +} + +func (fs *defaultRawFileSystem) StatFs(cancel <-chan struct{}, header *InHeader, out *StatfsOut) Status { + return ENOSYS +} + +func (fs *defaultRawFileSystem) Lookup(cancel <-chan struct{}, header *InHeader, name string, out *EntryOut) (code Status) { + return ENOSYS +} + +func (fs *defaultRawFileSystem) Forget(nodeID, nlookup uint64) { +} + +func (fs *defaultRawFileSystem) GetAttr(cancel <-chan struct{}, input *GetAttrIn, out *AttrOut) (code Status) { + return ENOSYS +} + +func (fs *defaultRawFileSystem) Open(cancel <-chan struct{}, input *OpenIn, out *OpenOut) (status Status) { + return OK +} + +func (fs *defaultRawFileSystem) SetAttr(cancel <-chan struct{}, input *SetAttrIn, out *AttrOut) (code Status) { + return ENOSYS +} + +func (fs *defaultRawFileSystem) Readlink(cancel <-chan struct{}, header *InHeader) (out []byte, code Status) { + return nil, ENOSYS +} + +func (fs *defaultRawFileSystem) Mknod(cancel <-chan struct{}, input *MknodIn, name string, out *EntryOut) (code Status) { + return ENOSYS +} + +func (fs *defaultRawFileSystem) Mkdir(cancel <-chan struct{}, input *MkdirIn, name string, out *EntryOut) (code Status) { + return ENOSYS +} + +func (fs *defaultRawFileSystem) Unlink(cancel <-chan struct{}, header *InHeader, name string) (code Status) { + return ENOSYS +} + +func (fs *defaultRawFileSystem) Rmdir(cancel <-chan struct{}, header *InHeader, name string) (code Status) { + return ENOSYS +} + +func (fs *defaultRawFileSystem) Symlink(cancel <-chan struct{}, header *InHeader, pointedTo string, linkName string, out *EntryOut) (code Status) { + return ENOSYS +} + +func (fs *defaultRawFileSystem) Rename(cancel <-chan struct{}, input *RenameIn, oldName string, newName string) (code Status) { + return ENOSYS +} + +func (fs *defaultRawFileSystem) Link(cancel <-chan struct{}, input *LinkIn, name string, out *EntryOut) (code Status) { + return ENOSYS +} + +func (fs *defaultRawFileSystem) GetXAttr(cancel <-chan struct{}, header *InHeader, attr string, dest []byte) (size uint32, code Status) { + return 0, ENOSYS +} + +func (fs *defaultRawFileSystem) SetXAttr(cancel <-chan struct{}, input *SetXAttrIn, attr string, data []byte) Status { + return ENOSYS +} + +func (fs *defaultRawFileSystem) ListXAttr(cancel <-chan struct{}, header *InHeader, dest []byte) (n uint32, code Status) { + return 0, ENOSYS +} + +func (fs *defaultRawFileSystem) RemoveXAttr(cancel <-chan struct{}, header *InHeader, attr string) Status { + return ENOSYS +} + +func (fs *defaultRawFileSystem) Access(cancel <-chan struct{}, input *AccessIn) (code Status) { + return ENOSYS +} + +func (fs *defaultRawFileSystem) Create(cancel <-chan struct{}, input *CreateIn, name string, out *CreateOut) (code Status) { + return ENOSYS +} + +func (fs *defaultRawFileSystem) OpenDir(cancel <-chan struct{}, input *OpenIn, out *OpenOut) (status Status) { + return ENOSYS +} + +func (fs *defaultRawFileSystem) Read(cancel <-chan struct{}, input *ReadIn, buf []byte) (ReadResult, Status) { + return nil, ENOSYS +} + +func (fs *defaultRawFileSystem) GetLk(cancel <-chan struct{}, in *LkIn, out *LkOut) (code Status) { + return ENOSYS +} + +func (fs *defaultRawFileSystem) SetLk(cancel <-chan struct{}, in *LkIn) (code Status) { + return ENOSYS +} + +func (fs *defaultRawFileSystem) SetLkw(cancel <-chan struct{}, in *LkIn) (code Status) { + return ENOSYS +} + +func (fs *defaultRawFileSystem) Release(cancel <-chan struct{}, input *ReleaseIn) { +} + +func (fs *defaultRawFileSystem) Write(cancel <-chan struct{}, input *WriteIn, data []byte) (written uint32, code Status) { + return 0, ENOSYS +} + +func (fs *defaultRawFileSystem) Flush(cancel <-chan struct{}, input *FlushIn) Status { + return OK +} + +func (fs *defaultRawFileSystem) Fsync(cancel <-chan struct{}, input *FsyncIn) (code Status) { + return ENOSYS +} + +func (fs *defaultRawFileSystem) ReadDir(cancel <-chan struct{}, input *ReadIn, l *DirEntryList) Status { + return ENOSYS +} + +func (fs *defaultRawFileSystem) ReadDirPlus(cancel <-chan struct{}, input *ReadIn, l *DirEntryList) Status { + return ENOSYS +} + +func (fs *defaultRawFileSystem) ReleaseDir(input *ReleaseIn) { +} + +func (fs *defaultRawFileSystem) FsyncDir(cancel <-chan struct{}, input *FsyncIn) (code Status) { + return ENOSYS +} + +func (fs *defaultRawFileSystem) Fallocate(cancel <-chan struct{}, in *FallocateIn) (code Status) { + return ENOSYS +} + +func (fs *defaultRawFileSystem) CopyFileRange(cancel <-chan struct{}, input *CopyFileRangeIn) (written uint32, code Status) { + return 0, ENOSYS +} + +func (fs *defaultRawFileSystem) Lseek(cancel <-chan struct{}, in *LseekIn, out *LseekOut) Status { + return ENOSYS +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/direntry.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/direntry.go new file mode 100644 index 000000000..45eca7275 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/direntry.go @@ -0,0 +1,118 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +// all of the code for DirEntryList. + +import ( + "fmt" + "unsafe" +) + +var eightPadding [8]byte + +const direntSize = int(unsafe.Sizeof(_Dirent{})) + +// DirEntry is a type for PathFileSystem and NodeFileSystem to return +// directory contents in. +type DirEntry struct { + // Mode is the file's mode. Only the high bits (eg. S_IFDIR) + // are considered. + Mode uint32 + + // Name is the basename of the file in the directory. + Name string + + // Ino is the inode number. + Ino uint64 +} + +func (d DirEntry) String() string { + return fmt.Sprintf("%o: %q ino=%d", d.Mode, d.Name, d.Ino) +} + +// DirEntryList holds the return value for READDIR and READDIRPLUS +// opcodes. +type DirEntryList struct { + buf []byte + size int + offset uint64 +} + +// NewDirEntryList creates a DirEntryList with the given data buffer +// and offset. +func NewDirEntryList(data []byte, off uint64) *DirEntryList { + return &DirEntryList{ + buf: data[:0], + size: len(data), + offset: off, + } +} + +// AddDirEntry tries to add an entry, and reports whether it +// succeeded. +func (l *DirEntryList) AddDirEntry(e DirEntry) bool { + return l.Add(0, e.Name, e.Ino, e.Mode) +} + +// Add adds a direntry to the DirEntryList, returning whether it +// succeeded. +func (l *DirEntryList) Add(prefix int, name string, inode uint64, mode uint32) bool { + if inode == 0 { + inode = FUSE_UNKNOWN_INO + } + padding := (8 - len(name)&7) & 7 + delta := padding + direntSize + len(name) + prefix + oldLen := len(l.buf) + newLen := delta + oldLen + + if newLen > l.size { + return false + } + l.buf = l.buf[:newLen] + oldLen += prefix + dirent := (*_Dirent)(unsafe.Pointer(&l.buf[oldLen])) + dirent.Off = l.offset + 1 + dirent.Ino = inode + dirent.NameLen = uint32(len(name)) + dirent.Typ = (mode & 0170000) >> 12 + oldLen += direntSize + copy(l.buf[oldLen:], name) + oldLen += len(name) + + if padding > 0 { + copy(l.buf[oldLen:], eightPadding[:padding]) + } + + l.offset = dirent.Off + return true +} + +// AddDirLookupEntry is used for ReadDirPlus. It serializes a DirEntry +// and returns the space for entry. If no space is left, returns a nil +// pointer. +func (l *DirEntryList) AddDirLookupEntry(e DirEntry) *EntryOut { + lastStart := len(l.buf) + ok := l.Add(int(unsafe.Sizeof(EntryOut{})), e.Name, + e.Ino, e.Mode) + if !ok { + return nil + } + result := (*EntryOut)(unsafe.Pointer(&l.buf[lastStart])) + *result = EntryOut{} + return result +} + +// FixMode overrides the mode of the last direntry that was added. This can +// be needed when a directory changes while READDIRPLUS is running. +func (l *DirEntryList) FixMode(mode uint32) { + oldLen := len(l.buf) - int(unsafe.Sizeof(_Dirent{})) + dirent := (*_Dirent)(unsafe.Pointer(&l.buf[oldLen])) + dirent.Typ = (mode & 0170000) >> 12 +} + +func (l *DirEntryList) bytes() []byte { + return l.buf +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/misc.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/misc.go new file mode 100644 index 000000000..5e93972bb --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/misc.go @@ -0,0 +1,100 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Random odds and ends. + +package fuse + +import ( + "fmt" + "log" + "os" + "reflect" + "syscall" + "time" + "unsafe" +) + +func (code Status) String() string { + if code <= 0 { + return []string{ + "OK", + "NOTIFY_POLL", + "NOTIFY_INVAL_INODE", + "NOTIFY_INVAL_ENTRY", + "NOTIFY_STORE_CACHE", + "NOTIFY_RETRIEVE_CACHE", + "NOTIFY_DELETE", + }[-code] + } + return fmt.Sprintf("%d=%v", int(code), syscall.Errno(code)) +} + +func (code Status) Ok() bool { + return code == OK +} + +// ToStatus extracts an errno number from Go error objects. If it +// fails, it logs an error and returns ENOSYS. +func ToStatus(err error) Status { + switch err { + case nil: + return OK + case os.ErrPermission: + return EPERM + case os.ErrExist: + return Status(syscall.EEXIST) + case os.ErrNotExist: + return ENOENT + case os.ErrInvalid: + return EINVAL + } + + switch t := err.(type) { + case syscall.Errno: + return Status(t) + case *os.SyscallError: + return Status(t.Err.(syscall.Errno)) + case *os.PathError: + return ToStatus(t.Err) + case *os.LinkError: + return ToStatus(t.Err) + } + log.Println("can't convert error type:", err) + return ENOSYS +} + +func toSlice(dest *[]byte, ptr unsafe.Pointer, byteCount uintptr) { + h := (*reflect.SliceHeader)(unsafe.Pointer(dest)) + *h = reflect.SliceHeader{ + Data: uintptr(ptr), + Len: int(byteCount), + Cap: int(byteCount), + } +} + +func CurrentOwner() *Owner { + return &Owner{ + Uid: uint32(os.Getuid()), + Gid: uint32(os.Getgid()), + } +} + +const _UTIME_OMIT = ((1 << 30) - 2) + +// UtimeToTimespec converts a "Time" pointer as passed to Utimens to a +// "Timespec" that can be passed to the utimensat syscall. +// A nil pointer is converted to the special UTIME_OMIT value. +func UtimeToTimespec(t *time.Time) (ts syscall.Timespec) { + if t == nil { + ts.Nsec = _UTIME_OMIT + } else { + ts = syscall.NsecToTimespec(t.UnixNano()) + // Go bug https://github.com/golang/go/issues/12777 + if ts.Nsec < 0 { + ts.Nsec = 0 + } + } + return ts +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_darwin.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_darwin.go new file mode 100644 index 000000000..6efb52889 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_darwin.go @@ -0,0 +1,97 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "syscall" +) + +func openFUSEDevice() (*os.File, error) { + fs, err := filepath.Glob("/dev/osxfuse*") + if err != nil { + return nil, err + } + if len(fs) == 0 { + bin := oldLoadBin + if _, err := os.Stat(newLoadBin); err == nil { + bin = newLoadBin + } + + cmd := exec.Command(bin) + if err := cmd.Run(); err != nil { + return nil, err + } + fs, err = filepath.Glob("/dev/osxfuse*") + if err != nil { + return nil, err + } + } + + for _, fn := range fs { + f, err := os.OpenFile(fn, os.O_RDWR, 0) + if err != nil { + continue + } + return f, nil + } + + return nil, fmt.Errorf("all FUSE devices busy") +} + +const oldLoadBin = "/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs" +const newLoadBin = "/Library/Filesystems/osxfuse.fs/Contents/Resources/load_osxfuse" + +const oldMountBin = "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs" +const newMountBin = "/Library/Filesystems/osxfuse.fs/Contents/Resources/mount_osxfuse" + +func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) { + f, err := openFUSEDevice() + if err != nil { + return 0, err + } + + bin := oldMountBin + if _, err := os.Stat(newMountBin); err == nil { + bin = newMountBin + } + + cmd := exec.Command(bin, "-o", strings.Join(opts.optionsStrings(), ","), "-o", fmt.Sprintf("iosize=%d", opts.MaxWrite), "3", mountPoint) + cmd.ExtraFiles = []*os.File{f} + cmd.Env = append(os.Environ(), "MOUNT_FUSEFS_CALL_BY_LIB=", "MOUNT_OSXFUSE_CALL_BY_LIB=", + "MOUNT_OSXFUSE_DAEMON_PATH="+os.Args[0], + "MOUNT_FUSEFS_DAEMON_PATH="+os.Args[0]) + + var out, errOut bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &errOut + + if err := cmd.Start(); err != nil { + f.Close() + return 0, err + } + go func() { + err := cmd.Wait() + if err != nil { + err = fmt.Errorf("mount_osxfusefs failed: %v. Stderr: %s, Stdout: %s", err, errOut.String(), out.String()) + } + + ready <- err + close(ready) + }() + + // The finalizer for f will close its fd so we return a dup. + defer f.Close() + return syscall.Dup(int(f.Fd())) +} + +func unmount(dir string) error { + return syscall.Unmount(dir, 0) +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_linux.go new file mode 100644 index 000000000..5c18e6186 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_linux.go @@ -0,0 +1,143 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "path" + "strings" + "syscall" + "unsafe" +) + +func unixgramSocketpair() (l, r *os.File, err error) { + fd, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_SEQPACKET, 0) + if err != nil { + return nil, nil, os.NewSyscallError("socketpair", + err.(syscall.Errno)) + } + l = os.NewFile(uintptr(fd[0]), "socketpair-half1") + r = os.NewFile(uintptr(fd[1]), "socketpair-half2") + return +} + +// Create a FUSE FS on the specified mount point. The returned +// mount point is always absolute. +func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) { + local, remote, err := unixgramSocketpair() + if err != nil { + return + } + + defer local.Close() + defer remote.Close() + + bin, err := fusermountBinary() + if err != nil { + return 0, err + } + + cmd := []string{bin, mountPoint} + if s := opts.optionsStrings(); len(s) > 0 { + cmd = append(cmd, "-o", strings.Join(s, ",")) + } + proc, err := os.StartProcess(bin, + cmd, + &os.ProcAttr{ + Env: []string{"_FUSE_COMMFD=3"}, + Files: []*os.File{os.Stdin, os.Stdout, os.Stderr, remote}}) + + if err != nil { + return + } + + w, err := proc.Wait() + if err != nil { + return + } + if !w.Success() { + err = fmt.Errorf("fusermount exited with code %v\n", w.Sys()) + return + } + + fd, err = getConnection(local) + if err != nil { + return -1, err + } + + // golang sets CLOEXEC on file descriptors when they are + // acquired through normal operations (e.g. open). + // Buf for fd, we have to set CLOEXEC manually + syscall.CloseOnExec(fd) + + close(ready) + return fd, err +} + +func unmount(mountPoint string) (err error) { + bin, err := fusermountBinary() + if err != nil { + return err + } + errBuf := bytes.Buffer{} + cmd := exec.Command(bin, "-u", mountPoint) + cmd.Stderr = &errBuf + err = cmd.Run() + if errBuf.Len() > 0 { + return fmt.Errorf("%s (code %v)\n", + errBuf.String(), err) + } + return err +} + +func getConnection(local *os.File) (int, error) { + var data [4]byte + control := make([]byte, 4*256) + + // n, oobn, recvflags, from, errno - todo: error checking. + _, oobn, _, _, + err := syscall.Recvmsg( + int(local.Fd()), data[:], control[:], 0) + if err != nil { + return 0, err + } + + message := *(*syscall.Cmsghdr)(unsafe.Pointer(&control[0])) + fd := *(*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(&control[0])) + syscall.SizeofCmsghdr)) + + if message.Type != 1 { + return 0, fmt.Errorf("getConnection: recvmsg returned wrong control type: %d", message.Type) + } + if oobn <= syscall.SizeofCmsghdr { + return 0, fmt.Errorf("getConnection: too short control message. Length: %d", oobn) + } + if fd < 0 { + return 0, fmt.Errorf("getConnection: fd < 0: %d", fd) + } + return int(fd), nil +} + +// lookPathFallback - search binary in PATH and, if that fails, +// in fallbackDir. This is useful if PATH is possible empty. +func lookPathFallback(file string, fallbackDir string) (string, error) { + binPath, err := exec.LookPath(file) + if err == nil { + return binPath, nil + } + + abs := path.Join(fallbackDir, file) + return exec.LookPath(abs) +} + +func fusermountBinary() (string, error) { + return lookPathFallback("fusermount", "/bin") +} + +func umountBinary() (string, error) { + return lookPathFallback("umount", "/bin") +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/opcode.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/opcode.go new file mode 100644 index 000000000..3dba45605 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/opcode.go @@ -0,0 +1,804 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "bytes" + "fmt" + "log" + "reflect" + "runtime" + "time" + "unsafe" +) + +const ( + _OP_LOOKUP = uint32(1) + _OP_FORGET = uint32(2) + _OP_GETATTR = uint32(3) + _OP_SETATTR = uint32(4) + _OP_READLINK = uint32(5) + _OP_SYMLINK = uint32(6) + _OP_MKNOD = uint32(8) + _OP_MKDIR = uint32(9) + _OP_UNLINK = uint32(10) + _OP_RMDIR = uint32(11) + _OP_RENAME = uint32(12) + _OP_LINK = uint32(13) + _OP_OPEN = uint32(14) + _OP_READ = uint32(15) + _OP_WRITE = uint32(16) + _OP_STATFS = uint32(17) + _OP_RELEASE = uint32(18) + _OP_FSYNC = uint32(20) + _OP_SETXATTR = uint32(21) + _OP_GETXATTR = uint32(22) + _OP_LISTXATTR = uint32(23) + _OP_REMOVEXATTR = uint32(24) + _OP_FLUSH = uint32(25) + _OP_INIT = uint32(26) + _OP_OPENDIR = uint32(27) + _OP_READDIR = uint32(28) + _OP_RELEASEDIR = uint32(29) + _OP_FSYNCDIR = uint32(30) + _OP_GETLK = uint32(31) + _OP_SETLK = uint32(32) + _OP_SETLKW = uint32(33) + _OP_ACCESS = uint32(34) + _OP_CREATE = uint32(35) + _OP_INTERRUPT = uint32(36) + _OP_BMAP = uint32(37) + _OP_DESTROY = uint32(38) + _OP_IOCTL = uint32(39) + _OP_POLL = uint32(40) + _OP_NOTIFY_REPLY = uint32(41) + _OP_BATCH_FORGET = uint32(42) + _OP_FALLOCATE = uint32(43) // protocol version 19. + _OP_READDIRPLUS = uint32(44) // protocol version 21. + _OP_RENAME2 = uint32(45) // protocol version 23. + _OP_LSEEK = uint32(46) // protocol version 24 + _OP_COPY_FILE_RANGE = uint32(47) // protocol version 28. + + // The following entries don't have to be compatible across Go-FUSE versions. + _OP_NOTIFY_INVAL_ENTRY = uint32(100) + _OP_NOTIFY_INVAL_INODE = uint32(101) + _OP_NOTIFY_STORE_CACHE = uint32(102) + _OP_NOTIFY_RETRIEVE_CACHE = uint32(103) + _OP_NOTIFY_DELETE = uint32(104) // protocol version 18 + + _OPCODE_COUNT = uint32(105) +) + +//////////////////////////////////////////////////////////////// + +func doInit(server *Server, req *request) { + input := (*InitIn)(req.inData) + if input.Major != _FUSE_KERNEL_VERSION { + log.Printf("Major versions does not match. Given %d, want %d\n", input.Major, _FUSE_KERNEL_VERSION) + req.status = EIO + return + } + if input.Minor < _MINIMUM_MINOR_VERSION { + log.Printf("Minor version is less than we support. Given %d, want at least %d\n", input.Minor, _MINIMUM_MINOR_VERSION) + req.status = EIO + return + } + + server.reqMu.Lock() + server.kernelSettings = *input + server.kernelSettings.Flags = input.Flags & (CAP_ASYNC_READ | CAP_BIG_WRITES | CAP_FILE_OPS | + CAP_READDIRPLUS | CAP_NO_OPEN_SUPPORT | CAP_PARALLEL_DIROPS) + + if server.opts.EnableLocks { + server.kernelSettings.Flags |= CAP_FLOCK_LOCKS | CAP_POSIX_LOCKS + } + + dataCacheMode := input.Flags & CAP_AUTO_INVAL_DATA + if server.opts.ExplicitDataCacheControl { + // we don't want CAP_AUTO_INVAL_DATA even if we cannot go into fully explicit mode + dataCacheMode = 0 + + explicit := input.Flags & CAP_EXPLICIT_INVAL_DATA + if explicit != 0 { + dataCacheMode = explicit + } + } + server.kernelSettings.Flags |= dataCacheMode + + if input.Minor >= 13 { + server.setSplice() + } + server.reqMu.Unlock() + + out := (*InitOut)(req.outData()) + *out = InitOut{ + Major: _FUSE_KERNEL_VERSION, + Minor: _OUR_MINOR_VERSION, + MaxReadAhead: input.MaxReadAhead, + Flags: server.kernelSettings.Flags, + MaxWrite: uint32(server.opts.MaxWrite), + CongestionThreshold: uint16(server.opts.MaxBackground * 3 / 4), + MaxBackground: uint16(server.opts.MaxBackground), + } + + if server.opts.MaxReadAhead != 0 && uint32(server.opts.MaxReadAhead) < out.MaxReadAhead { + out.MaxReadAhead = uint32(server.opts.MaxReadAhead) + } + if out.Minor > input.Minor { + out.Minor = input.Minor + } + + if out.Minor <= 22 { + tweaked := *req.handler + + // v8-v22 don't have TimeGran and further fields. + tweaked.OutputSize = 24 + req.handler = &tweaked + } + + req.status = OK +} + +func doOpen(server *Server, req *request) { + out := (*OpenOut)(req.outData()) + status := server.fileSystem.Open(req.cancel, (*OpenIn)(req.inData), out) + req.status = status + if status != OK { + return + } +} + +func doCreate(server *Server, req *request) { + out := (*CreateOut)(req.outData()) + status := server.fileSystem.Create(req.cancel, (*CreateIn)(req.inData), req.filenames[0], out) + req.status = status +} + +func doReadDir(server *Server, req *request) { + in := (*ReadIn)(req.inData) + buf := server.allocOut(req, in.Size) + out := NewDirEntryList(buf, uint64(in.Offset)) + + code := server.fileSystem.ReadDir(req.cancel, in, out) + req.flatData = out.bytes() + req.status = code +} + +func doReadDirPlus(server *Server, req *request) { + in := (*ReadIn)(req.inData) + buf := server.allocOut(req, in.Size) + out := NewDirEntryList(buf, uint64(in.Offset)) + + code := server.fileSystem.ReadDirPlus(req.cancel, in, out) + req.flatData = out.bytes() + req.status = code +} + +func doOpenDir(server *Server, req *request) { + out := (*OpenOut)(req.outData()) + status := server.fileSystem.OpenDir(req.cancel, (*OpenIn)(req.inData), out) + req.status = status +} + +func doSetattr(server *Server, req *request) { + out := (*AttrOut)(req.outData()) + req.status = server.fileSystem.SetAttr(req.cancel, (*SetAttrIn)(req.inData), out) +} + +func doWrite(server *Server, req *request) { + n, status := server.fileSystem.Write(req.cancel, (*WriteIn)(req.inData), req.arg) + o := (*WriteOut)(req.outData()) + o.Size = n + req.status = status +} + +func doNotifyReply(server *Server, req *request) { + reply := (*NotifyRetrieveIn)(req.inData) + server.retrieveMu.Lock() + reading := server.retrieveTab[reply.Unique] + delete(server.retrieveTab, reply.Unique) + server.retrieveMu.Unlock() + + badf := func(format string, argv ...interface{}) { + log.Printf("notify reply: "+format, argv...) + } + + if reading == nil { + badf("unexpected unique - ignoring") + return + } + + reading.n = 0 + reading.st = EIO + defer close(reading.ready) + + if reading.nodeid != reply.NodeId { + badf("inode mismatch: expected %s, got %s", reading.nodeid, reply.NodeId) + return + } + + if reading.offset != reply.Offset { + badf("offset mismatch: expected @%d, got @%d", reading.offset, reply.Offset) + return + } + + if len(reading.dest) < len(req.arg) { + badf("too much data: requested %db, got %db (will use only %db)", len(reading.dest), len(req.arg), len(reading.dest)) + } + + reading.n = copy(reading.dest, req.arg) + reading.st = OK +} + +const _SECURITY_CAPABILITY = "security.capability" +const _SECURITY_ACL = "system.posix_acl_access" +const _SECURITY_ACL_DEFAULT = "system.posix_acl_default" + +func doGetXAttr(server *Server, req *request) { + if server.opts.DisableXAttrs { + req.status = ENOSYS + return + } + + if server.opts.IgnoreSecurityLabels && req.inHeader.Opcode == _OP_GETXATTR { + fn := req.filenames[0] + if fn == _SECURITY_CAPABILITY || fn == _SECURITY_ACL_DEFAULT || + fn == _SECURITY_ACL { + req.status = ENOATTR + return + } + } + + input := (*GetXAttrIn)(req.inData) + + req.flatData = server.allocOut(req, input.Size) + out := (*GetXAttrOut)(req.outData()) + + var n uint32 + switch req.inHeader.Opcode { + case _OP_GETXATTR: + n, req.status = server.fileSystem.GetXAttr(req.cancel, req.inHeader, req.filenames[0], req.flatData) + case _OP_LISTXATTR: + n, req.status = server.fileSystem.ListXAttr(req.cancel, req.inHeader, req.flatData) + default: + req.status = ENOSYS + } + + if input.Size == 0 && req.status == ERANGE { + // For input.size==0, returning ERANGE is an error. + req.status = OK + out.Size = n + } else if req.status.Ok() { + req.flatData = req.flatData[:n] + out.Size = n + } else { + req.flatData = req.flatData[:0] + } +} + +func doGetAttr(server *Server, req *request) { + out := (*AttrOut)(req.outData()) + s := server.fileSystem.GetAttr(req.cancel, (*GetAttrIn)(req.inData), out) + req.status = s +} + +// doForget - forget one NodeId +func doForget(server *Server, req *request) { + if !server.opts.RememberInodes { + server.fileSystem.Forget(req.inHeader.NodeId, (*ForgetIn)(req.inData).Nlookup) + } +} + +// doBatchForget - forget a list of NodeIds +func doBatchForget(server *Server, req *request) { + in := (*_BatchForgetIn)(req.inData) + wantBytes := uintptr(in.Count) * unsafe.Sizeof(_ForgetOne{}) + if uintptr(len(req.arg)) < wantBytes { + // We have no return value to complain, so log an error. + log.Printf("Too few bytes for batch forget. Got %d bytes, want %d (%d entries)", + len(req.arg), wantBytes, in.Count) + } + + h := &reflect.SliceHeader{ + Data: uintptr(unsafe.Pointer(&req.arg[0])), + Len: int(in.Count), + Cap: int(in.Count), + } + + forgets := *(*[]_ForgetOne)(unsafe.Pointer(h)) + for i, f := range forgets { + if server.opts.Debug { + log.Printf("doBatchForget: rx %d %d/%d: FORGET i%d {Nlookup=%d}", + req.inHeader.Unique, i+1, len(forgets), f.NodeId, f.Nlookup) + } + if f.NodeId == pollHackInode { + continue + } + server.fileSystem.Forget(f.NodeId, f.Nlookup) + } +} + +func doReadlink(server *Server, req *request) { + req.flatData, req.status = server.fileSystem.Readlink(req.cancel, req.inHeader) +} + +func doLookup(server *Server, req *request) { + out := (*EntryOut)(req.outData()) + s := server.fileSystem.Lookup(req.cancel, req.inHeader, req.filenames[0], out) + req.status = s +} + +func doMknod(server *Server, req *request) { + out := (*EntryOut)(req.outData()) + + req.status = server.fileSystem.Mknod(req.cancel, (*MknodIn)(req.inData), req.filenames[0], out) +} + +func doMkdir(server *Server, req *request) { + out := (*EntryOut)(req.outData()) + req.status = server.fileSystem.Mkdir(req.cancel, (*MkdirIn)(req.inData), req.filenames[0], out) +} + +func doUnlink(server *Server, req *request) { + req.status = server.fileSystem.Unlink(req.cancel, req.inHeader, req.filenames[0]) +} + +func doRmdir(server *Server, req *request) { + req.status = server.fileSystem.Rmdir(req.cancel, req.inHeader, req.filenames[0]) +} + +func doLink(server *Server, req *request) { + out := (*EntryOut)(req.outData()) + req.status = server.fileSystem.Link(req.cancel, (*LinkIn)(req.inData), req.filenames[0], out) +} + +func doRead(server *Server, req *request) { + in := (*ReadIn)(req.inData) + buf := server.allocOut(req, in.Size) + + req.readResult, req.status = server.fileSystem.Read(req.cancel, in, buf) + if fd, ok := req.readResult.(*readResultFd); ok { + req.fdData = fd + req.flatData = nil + } else if req.readResult != nil && req.status.Ok() { + req.flatData, req.status = req.readResult.Bytes(buf) + } +} + +func doFlush(server *Server, req *request) { + req.status = server.fileSystem.Flush(req.cancel, (*FlushIn)(req.inData)) +} + +func doRelease(server *Server, req *request) { + server.fileSystem.Release(req.cancel, (*ReleaseIn)(req.inData)) +} + +func doFsync(server *Server, req *request) { + req.status = server.fileSystem.Fsync(req.cancel, (*FsyncIn)(req.inData)) +} + +func doReleaseDir(server *Server, req *request) { + server.fileSystem.ReleaseDir((*ReleaseIn)(req.inData)) +} + +func doFsyncDir(server *Server, req *request) { + req.status = server.fileSystem.FsyncDir(req.cancel, (*FsyncIn)(req.inData)) +} + +func doSetXAttr(server *Server, req *request) { + splits := bytes.SplitN(req.arg, []byte{0}, 2) + req.status = server.fileSystem.SetXAttr(req.cancel, (*SetXAttrIn)(req.inData), string(splits[0]), splits[1]) +} + +func doRemoveXAttr(server *Server, req *request) { + req.status = server.fileSystem.RemoveXAttr(req.cancel, req.inHeader, req.filenames[0]) +} + +func doAccess(server *Server, req *request) { + req.status = server.fileSystem.Access(req.cancel, (*AccessIn)(req.inData)) +} + +func doSymlink(server *Server, req *request) { + out := (*EntryOut)(req.outData()) + req.status = server.fileSystem.Symlink(req.cancel, req.inHeader, req.filenames[1], req.filenames[0], out) +} + +func doRename(server *Server, req *request) { + in1 := (*Rename1In)(req.inData) + in := RenameIn{ + InHeader: in1.InHeader, + Newdir: in1.Newdir, + } + req.status = server.fileSystem.Rename(req.cancel, &in, req.filenames[0], req.filenames[1]) +} + +func doRename2(server *Server, req *request) { + req.status = server.fileSystem.Rename(req.cancel, (*RenameIn)(req.inData), req.filenames[0], req.filenames[1]) +} + +func doStatFs(server *Server, req *request) { + out := (*StatfsOut)(req.outData()) + req.status = server.fileSystem.StatFs(req.cancel, req.inHeader, out) + if req.status == ENOSYS && runtime.GOOS == "darwin" { + // OSX FUSE requires Statfs to be implemented for the + // mount to succeed. + *out = StatfsOut{} + req.status = OK + } +} + +func doIoctl(server *Server, req *request) { + req.status = ENOSYS +} + +func doDestroy(server *Server, req *request) { + req.status = OK +} + +func doFallocate(server *Server, req *request) { + req.status = server.fileSystem.Fallocate(req.cancel, (*FallocateIn)(req.inData)) +} + +func doGetLk(server *Server, req *request) { + req.status = server.fileSystem.GetLk(req.cancel, (*LkIn)(req.inData), (*LkOut)(req.outData())) +} + +func doSetLk(server *Server, req *request) { + req.status = server.fileSystem.SetLk(req.cancel, (*LkIn)(req.inData)) +} + +func doSetLkw(server *Server, req *request) { + req.status = server.fileSystem.SetLkw(req.cancel, (*LkIn)(req.inData)) +} + +func doLseek(server *Server, req *request) { + in := (*LseekIn)(req.inData) + out := (*LseekOut)(req.outData()) + req.status = server.fileSystem.Lseek(req.cancel, in, out) +} + +func doCopyFileRange(server *Server, req *request) { + in := (*CopyFileRangeIn)(req.inData) + out := (*WriteOut)(req.outData()) + + out.Size, req.status = server.fileSystem.CopyFileRange(req.cancel, in) +} + +func doInterrupt(server *Server, req *request) { + input := (*InterruptIn)(req.inData) + server.reqMu.Lock() + defer server.reqMu.Unlock() + + // This is slow, but this operation is rare. + for _, inflight := range server.reqInflight { + if input.Unique == inflight.inHeader.Unique && !inflight.interrupted { + close(inflight.cancel) + inflight.interrupted = true + req.status = OK + return + } + } + + // not found; wait for a bit + time.Sleep(10 * time.Microsecond) + req.status = EAGAIN +} + +//////////////////////////////////////////////////////////////// + +type operationFunc func(*Server, *request) +type castPointerFunc func(unsafe.Pointer) interface{} + +type operationHandler struct { + Name string + Func operationFunc + InputSize uintptr + OutputSize uintptr + DecodeIn castPointerFunc + DecodeOut castPointerFunc + FileNames int + FileNameOut bool +} + +var operationHandlers []*operationHandler + +func operationName(op uint32) string { + h := getHandler(op) + if h == nil { + return "unknown" + } + return h.Name +} + +func getHandler(o uint32) *operationHandler { + if o >= _OPCODE_COUNT { + return nil + } + return operationHandlers[o] +} + +var maxInputSize uintptr + +func init() { + operationHandlers = make([]*operationHandler, _OPCODE_COUNT) + for i := range operationHandlers { + operationHandlers[i] = &operationHandler{Name: fmt.Sprintf("OPCODE-%d", i)} + } + + fileOps := []uint32{_OP_READLINK, _OP_NOTIFY_INVAL_ENTRY, _OP_NOTIFY_DELETE} + for _, op := range fileOps { + operationHandlers[op].FileNameOut = true + } + + maxInputSize = 0 + for op, sz := range map[uint32]uintptr{ + _OP_FORGET: unsafe.Sizeof(ForgetIn{}), + _OP_BATCH_FORGET: unsafe.Sizeof(_BatchForgetIn{}), + _OP_GETATTR: unsafe.Sizeof(GetAttrIn{}), + _OP_SETATTR: unsafe.Sizeof(SetAttrIn{}), + _OP_MKNOD: unsafe.Sizeof(MknodIn{}), + _OP_MKDIR: unsafe.Sizeof(MkdirIn{}), + _OP_RENAME: unsafe.Sizeof(Rename1In{}), + _OP_LINK: unsafe.Sizeof(LinkIn{}), + _OP_OPEN: unsafe.Sizeof(OpenIn{}), + _OP_READ: unsafe.Sizeof(ReadIn{}), + _OP_WRITE: unsafe.Sizeof(WriteIn{}), + _OP_RELEASE: unsafe.Sizeof(ReleaseIn{}), + _OP_FSYNC: unsafe.Sizeof(FsyncIn{}), + _OP_SETXATTR: unsafe.Sizeof(SetXAttrIn{}), + _OP_GETXATTR: unsafe.Sizeof(GetXAttrIn{}), + _OP_LISTXATTR: unsafe.Sizeof(GetXAttrIn{}), + _OP_FLUSH: unsafe.Sizeof(FlushIn{}), + _OP_INIT: unsafe.Sizeof(InitIn{}), + _OP_OPENDIR: unsafe.Sizeof(OpenIn{}), + _OP_READDIR: unsafe.Sizeof(ReadIn{}), + _OP_RELEASEDIR: unsafe.Sizeof(ReleaseIn{}), + _OP_FSYNCDIR: unsafe.Sizeof(FsyncIn{}), + _OP_GETLK: unsafe.Sizeof(LkIn{}), + _OP_SETLK: unsafe.Sizeof(LkIn{}), + _OP_SETLKW: unsafe.Sizeof(LkIn{}), + _OP_ACCESS: unsafe.Sizeof(AccessIn{}), + _OP_CREATE: unsafe.Sizeof(CreateIn{}), + _OP_INTERRUPT: unsafe.Sizeof(InterruptIn{}), + _OP_BMAP: unsafe.Sizeof(_BmapIn{}), + _OP_IOCTL: unsafe.Sizeof(_IoctlIn{}), + _OP_POLL: unsafe.Sizeof(_PollIn{}), + _OP_NOTIFY_REPLY: unsafe.Sizeof(NotifyRetrieveIn{}), + _OP_FALLOCATE: unsafe.Sizeof(FallocateIn{}), + _OP_READDIRPLUS: unsafe.Sizeof(ReadIn{}), + _OP_RENAME2: unsafe.Sizeof(RenameIn{}), + _OP_LSEEK: unsafe.Sizeof(LseekIn{}), + _OP_COPY_FILE_RANGE: unsafe.Sizeof(CopyFileRangeIn{}), + } { + operationHandlers[op].InputSize = sz + if sz > maxInputSize { + maxInputSize = sz + } + } + + for op, sz := range map[uint32]uintptr{ + _OP_LOOKUP: unsafe.Sizeof(EntryOut{}), + _OP_GETATTR: unsafe.Sizeof(AttrOut{}), + _OP_SETATTR: unsafe.Sizeof(AttrOut{}), + _OP_SYMLINK: unsafe.Sizeof(EntryOut{}), + _OP_MKNOD: unsafe.Sizeof(EntryOut{}), + _OP_MKDIR: unsafe.Sizeof(EntryOut{}), + _OP_LINK: unsafe.Sizeof(EntryOut{}), + _OP_OPEN: unsafe.Sizeof(OpenOut{}), + _OP_WRITE: unsafe.Sizeof(WriteOut{}), + _OP_STATFS: unsafe.Sizeof(StatfsOut{}), + _OP_GETXATTR: unsafe.Sizeof(GetXAttrOut{}), + _OP_LISTXATTR: unsafe.Sizeof(GetXAttrOut{}), + _OP_INIT: unsafe.Sizeof(InitOut{}), + _OP_OPENDIR: unsafe.Sizeof(OpenOut{}), + _OP_GETLK: unsafe.Sizeof(LkOut{}), + _OP_CREATE: unsafe.Sizeof(CreateOut{}), + _OP_BMAP: unsafe.Sizeof(_BmapOut{}), + _OP_IOCTL: unsafe.Sizeof(_IoctlOut{}), + _OP_POLL: unsafe.Sizeof(_PollOut{}), + _OP_NOTIFY_INVAL_ENTRY: unsafe.Sizeof(NotifyInvalEntryOut{}), + _OP_NOTIFY_INVAL_INODE: unsafe.Sizeof(NotifyInvalInodeOut{}), + _OP_NOTIFY_STORE_CACHE: unsafe.Sizeof(NotifyStoreOut{}), + _OP_NOTIFY_RETRIEVE_CACHE: unsafe.Sizeof(NotifyRetrieveOut{}), + _OP_NOTIFY_DELETE: unsafe.Sizeof(NotifyInvalDeleteOut{}), + _OP_LSEEK: unsafe.Sizeof(LseekOut{}), + _OP_COPY_FILE_RANGE: unsafe.Sizeof(WriteOut{}), + } { + operationHandlers[op].OutputSize = sz + } + + for op, v := range map[uint32]string{ + _OP_LOOKUP: "LOOKUP", + _OP_FORGET: "FORGET", + _OP_BATCH_FORGET: "BATCH_FORGET", + _OP_GETATTR: "GETATTR", + _OP_SETATTR: "SETATTR", + _OP_READLINK: "READLINK", + _OP_SYMLINK: "SYMLINK", + _OP_MKNOD: "MKNOD", + _OP_MKDIR: "MKDIR", + _OP_UNLINK: "UNLINK", + _OP_RMDIR: "RMDIR", + _OP_RENAME: "RENAME", + _OP_LINK: "LINK", + _OP_OPEN: "OPEN", + _OP_READ: "READ", + _OP_WRITE: "WRITE", + _OP_STATFS: "STATFS", + _OP_RELEASE: "RELEASE", + _OP_FSYNC: "FSYNC", + _OP_SETXATTR: "SETXATTR", + _OP_GETXATTR: "GETXATTR", + _OP_LISTXATTR: "LISTXATTR", + _OP_REMOVEXATTR: "REMOVEXATTR", + _OP_FLUSH: "FLUSH", + _OP_INIT: "INIT", + _OP_OPENDIR: "OPENDIR", + _OP_READDIR: "READDIR", + _OP_RELEASEDIR: "RELEASEDIR", + _OP_FSYNCDIR: "FSYNCDIR", + _OP_GETLK: "GETLK", + _OP_SETLK: "SETLK", + _OP_SETLKW: "SETLKW", + _OP_ACCESS: "ACCESS", + _OP_CREATE: "CREATE", + _OP_INTERRUPT: "INTERRUPT", + _OP_BMAP: "BMAP", + _OP_DESTROY: "DESTROY", + _OP_IOCTL: "IOCTL", + _OP_POLL: "POLL", + _OP_NOTIFY_REPLY: "NOTIFY_REPLY", + _OP_NOTIFY_INVAL_ENTRY: "NOTIFY_INVAL_ENTRY", + _OP_NOTIFY_INVAL_INODE: "NOTIFY_INVAL_INODE", + _OP_NOTIFY_STORE_CACHE: "NOTIFY_STORE", + _OP_NOTIFY_RETRIEVE_CACHE: "NOTIFY_RETRIEVE", + _OP_NOTIFY_DELETE: "NOTIFY_DELETE", + _OP_FALLOCATE: "FALLOCATE", + _OP_READDIRPLUS: "READDIRPLUS", + _OP_RENAME2: "RENAME2", + _OP_LSEEK: "LSEEK", + _OP_COPY_FILE_RANGE: "COPY_FILE_RANGE", + } { + operationHandlers[op].Name = v + } + + for op, v := range map[uint32]operationFunc{ + _OP_OPEN: doOpen, + _OP_READDIR: doReadDir, + _OP_WRITE: doWrite, + _OP_OPENDIR: doOpenDir, + _OP_CREATE: doCreate, + _OP_SETATTR: doSetattr, + _OP_GETXATTR: doGetXAttr, + _OP_LISTXATTR: doGetXAttr, + _OP_GETATTR: doGetAttr, + _OP_FORGET: doForget, + _OP_BATCH_FORGET: doBatchForget, + _OP_READLINK: doReadlink, + _OP_INIT: doInit, + _OP_LOOKUP: doLookup, + _OP_MKNOD: doMknod, + _OP_MKDIR: doMkdir, + _OP_UNLINK: doUnlink, + _OP_RMDIR: doRmdir, + _OP_LINK: doLink, + _OP_READ: doRead, + _OP_FLUSH: doFlush, + _OP_RELEASE: doRelease, + _OP_FSYNC: doFsync, + _OP_RELEASEDIR: doReleaseDir, + _OP_FSYNCDIR: doFsyncDir, + _OP_SETXATTR: doSetXAttr, + _OP_REMOVEXATTR: doRemoveXAttr, + _OP_GETLK: doGetLk, + _OP_SETLK: doSetLk, + _OP_SETLKW: doSetLkw, + _OP_ACCESS: doAccess, + _OP_SYMLINK: doSymlink, + _OP_RENAME: doRename, + _OP_STATFS: doStatFs, + _OP_IOCTL: doIoctl, + _OP_DESTROY: doDestroy, + _OP_NOTIFY_REPLY: doNotifyReply, + _OP_FALLOCATE: doFallocate, + _OP_READDIRPLUS: doReadDirPlus, + _OP_RENAME2: doRename2, + _OP_INTERRUPT: doInterrupt, + _OP_COPY_FILE_RANGE: doCopyFileRange, + _OP_LSEEK: doLseek, + } { + operationHandlers[op].Func = v + } + + // Outputs. + for op, f := range map[uint32]castPointerFunc{ + _OP_LOOKUP: func(ptr unsafe.Pointer) interface{} { return (*EntryOut)(ptr) }, + _OP_OPEN: func(ptr unsafe.Pointer) interface{} { return (*OpenOut)(ptr) }, + _OP_OPENDIR: func(ptr unsafe.Pointer) interface{} { return (*OpenOut)(ptr) }, + _OP_GETATTR: func(ptr unsafe.Pointer) interface{} { return (*AttrOut)(ptr) }, + _OP_CREATE: func(ptr unsafe.Pointer) interface{} { return (*CreateOut)(ptr) }, + _OP_LINK: func(ptr unsafe.Pointer) interface{} { return (*EntryOut)(ptr) }, + _OP_SETATTR: func(ptr unsafe.Pointer) interface{} { return (*AttrOut)(ptr) }, + _OP_INIT: func(ptr unsafe.Pointer) interface{} { return (*InitOut)(ptr) }, + _OP_MKDIR: func(ptr unsafe.Pointer) interface{} { return (*EntryOut)(ptr) }, + _OP_NOTIFY_INVAL_ENTRY: func(ptr unsafe.Pointer) interface{} { return (*NotifyInvalEntryOut)(ptr) }, + _OP_NOTIFY_INVAL_INODE: func(ptr unsafe.Pointer) interface{} { return (*NotifyInvalInodeOut)(ptr) }, + _OP_NOTIFY_STORE_CACHE: func(ptr unsafe.Pointer) interface{} { return (*NotifyStoreOut)(ptr) }, + _OP_NOTIFY_RETRIEVE_CACHE: func(ptr unsafe.Pointer) interface{} { return (*NotifyRetrieveOut)(ptr) }, + _OP_NOTIFY_DELETE: func(ptr unsafe.Pointer) interface{} { return (*NotifyInvalDeleteOut)(ptr) }, + _OP_STATFS: func(ptr unsafe.Pointer) interface{} { return (*StatfsOut)(ptr) }, + _OP_SYMLINK: func(ptr unsafe.Pointer) interface{} { return (*EntryOut)(ptr) }, + _OP_GETLK: func(ptr unsafe.Pointer) interface{} { return (*LkOut)(ptr) }, + _OP_LSEEK: func(ptr unsafe.Pointer) interface{} { return (*LseekOut)(ptr) }, + _OP_COPY_FILE_RANGE: func(ptr unsafe.Pointer) interface{} { return (*WriteOut)(ptr) }, + } { + operationHandlers[op].DecodeOut = f + } + + // Inputs. + for op, f := range map[uint32]castPointerFunc{ + _OP_FLUSH: func(ptr unsafe.Pointer) interface{} { return (*FlushIn)(ptr) }, + _OP_GETATTR: func(ptr unsafe.Pointer) interface{} { return (*GetAttrIn)(ptr) }, + _OP_SETXATTR: func(ptr unsafe.Pointer) interface{} { return (*SetXAttrIn)(ptr) }, + _OP_GETXATTR: func(ptr unsafe.Pointer) interface{} { return (*GetXAttrIn)(ptr) }, + _OP_LISTXATTR: func(ptr unsafe.Pointer) interface{} { return (*GetXAttrIn)(ptr) }, + _OP_SETATTR: func(ptr unsafe.Pointer) interface{} { return (*SetAttrIn)(ptr) }, + _OP_INIT: func(ptr unsafe.Pointer) interface{} { return (*InitIn)(ptr) }, + _OP_IOCTL: func(ptr unsafe.Pointer) interface{} { return (*_IoctlIn)(ptr) }, + _OP_OPEN: func(ptr unsafe.Pointer) interface{} { return (*OpenIn)(ptr) }, + _OP_MKNOD: func(ptr unsafe.Pointer) interface{} { return (*MknodIn)(ptr) }, + _OP_CREATE: func(ptr unsafe.Pointer) interface{} { return (*CreateIn)(ptr) }, + _OP_READ: func(ptr unsafe.Pointer) interface{} { return (*ReadIn)(ptr) }, + _OP_WRITE: func(ptr unsafe.Pointer) interface{} { return (*WriteIn)(ptr) }, + _OP_READDIR: func(ptr unsafe.Pointer) interface{} { return (*ReadIn)(ptr) }, + _OP_ACCESS: func(ptr unsafe.Pointer) interface{} { return (*AccessIn)(ptr) }, + _OP_FORGET: func(ptr unsafe.Pointer) interface{} { return (*ForgetIn)(ptr) }, + _OP_BATCH_FORGET: func(ptr unsafe.Pointer) interface{} { return (*_BatchForgetIn)(ptr) }, + _OP_LINK: func(ptr unsafe.Pointer) interface{} { return (*LinkIn)(ptr) }, + _OP_MKDIR: func(ptr unsafe.Pointer) interface{} { return (*MkdirIn)(ptr) }, + _OP_RELEASE: func(ptr unsafe.Pointer) interface{} { return (*ReleaseIn)(ptr) }, + _OP_RELEASEDIR: func(ptr unsafe.Pointer) interface{} { return (*ReleaseIn)(ptr) }, + _OP_FALLOCATE: func(ptr unsafe.Pointer) interface{} { return (*FallocateIn)(ptr) }, + _OP_NOTIFY_REPLY: func(ptr unsafe.Pointer) interface{} { return (*NotifyRetrieveIn)(ptr) }, + _OP_READDIRPLUS: func(ptr unsafe.Pointer) interface{} { return (*ReadIn)(ptr) }, + _OP_RENAME: func(ptr unsafe.Pointer) interface{} { return (*Rename1In)(ptr) }, + _OP_GETLK: func(ptr unsafe.Pointer) interface{} { return (*LkIn)(ptr) }, + _OP_SETLK: func(ptr unsafe.Pointer) interface{} { return (*LkIn)(ptr) }, + _OP_SETLKW: func(ptr unsafe.Pointer) interface{} { return (*LkIn)(ptr) }, + _OP_RENAME2: func(ptr unsafe.Pointer) interface{} { return (*RenameIn)(ptr) }, + _OP_INTERRUPT: func(ptr unsafe.Pointer) interface{} { return (*InterruptIn)(ptr) }, + _OP_LSEEK: func(ptr unsafe.Pointer) interface{} { return (*LseekIn)(ptr) }, + _OP_COPY_FILE_RANGE: func(ptr unsafe.Pointer) interface{} { return (*CopyFileRangeIn)(ptr) }, + } { + operationHandlers[op].DecodeIn = f + } + + // File name args. + for op, count := range map[uint32]int{ + _OP_CREATE: 1, + _OP_SETXATTR: 1, + _OP_GETXATTR: 1, + _OP_LINK: 1, + _OP_LOOKUP: 1, + _OP_MKDIR: 1, + _OP_MKNOD: 1, + _OP_REMOVEXATTR: 1, + _OP_RENAME: 2, + _OP_RENAME2: 2, + _OP_RMDIR: 1, + _OP_SYMLINK: 2, + _OP_UNLINK: 1, + } { + operationHandlers[op].FileNames = count + } + + var r request + sizeOfOutHeader := unsafe.Sizeof(OutHeader{}) + for code, h := range operationHandlers { + if h.OutputSize+sizeOfOutHeader > unsafe.Sizeof(r.outBuf) { + log.Panicf("request output buffer too small: code %v, sz %d + %d %v", code, h.OutputSize, sizeOfOutHeader, h) + } + } +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/poll.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/poll.go new file mode 100644 index 000000000..d158a142c --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/poll.go @@ -0,0 +1,34 @@ +package fuse + +// Go 1.9 introduces polling for file I/O. The implementation causes +// the runtime's epoll to take up the last GOMAXPROCS slot, and if +// that happens, we won't have any threads left to service FUSE's +// _OP_POLL request. Prevent this by forcing _OP_POLL to happen, so we +// can say ENOSYS and prevent further _OP_POLL requests. +const pollHackName = ".go-fuse-epoll-hack" +const pollHackInode = ^uint64(0) + +func doPollHackLookup(ms *Server, req *request) { + switch req.inHeader.Opcode { + case _OP_CREATE: + out := (*CreateOut)(req.outData()) + out.EntryOut = EntryOut{ + NodeId: pollHackInode, + Attr: Attr{ + Ino: pollHackInode, + Mode: S_IFREG | 0644, + Nlink: 1, + }, + } + out.OpenOut = OpenOut{ + Fh: pollHackInode, + } + req.status = OK + case _OP_LOOKUP: + out := (*EntryOut)(req.outData()) + *out = EntryOut{} + req.status = ENOENT + default: + req.status = EIO + } +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/poll_darwin.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/poll_darwin.go new file mode 100644 index 000000000..84cc980b0 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/poll_darwin.go @@ -0,0 +1,49 @@ +package fuse + +import ( + "path/filepath" + "syscall" + "unsafe" +) + +type pollFd struct { + Fd int32 + Events int16 + Revents int16 +} + +func sysPoll(fds []pollFd, timeout int) (n int, err error) { + r0, _, e1 := syscall.Syscall(syscall.SYS_POLL, uintptr(unsafe.Pointer(&fds[0])), + uintptr(len(fds)), uintptr(timeout)) + n = int(r0) + if e1 != 0 { + err = syscall.Errno(e1) + } + return n, err +} + +func pollHack(mountPoint string) error { + const ( + POLLIN = 0x1 + POLLPRI = 0x2 + POLLOUT = 0x4 + POLLRDHUP = 0x2000 + POLLERR = 0x8 + POLLHUP = 0x10 + ) + + fd, err := syscall.Open(filepath.Join(mountPoint, pollHackName), syscall.O_CREAT|syscall.O_TRUNC|syscall.O_RDWR, 0644) + if err != nil { + return err + } + pollData := []pollFd{{ + Fd: int32(fd), + Events: POLLIN | POLLPRI | POLLOUT, + }} + + // Trigger _OP_POLL, so we can say ENOSYS. We don't care about + // the return value. + sysPoll(pollData, 0) + syscall.Close(fd) + return nil +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/poll_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/poll_linux.go new file mode 100644 index 000000000..6b8924a29 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/poll_linux.go @@ -0,0 +1,25 @@ +package fuse + +import ( + "path/filepath" + "syscall" + + "golang.org/x/sys/unix" +) + +func pollHack(mountPoint string) error { + fd, err := syscall.Creat(filepath.Join(mountPoint, pollHackName), syscall.O_CREAT) + if err != nil { + return err + } + pollData := []unix.PollFd{{ + Fd: int32(fd), + Events: unix.POLLIN | unix.POLLPRI | unix.POLLOUT, + }} + + // Trigger _OP_POLL, so we can say ENOSYS. We don't care about + // the return value. + unix.Poll(pollData, 0) + syscall.Close(fd) + return nil +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/print.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/print.go new file mode 100644 index 000000000..d0a0223d6 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/print.go @@ -0,0 +1,305 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "fmt" + "os" + "strings" + "syscall" +) + +var ( + writeFlagNames = map[int64]string{ + WRITE_CACHE: "CACHE", + WRITE_LOCKOWNER: "LOCKOWNER", + } + readFlagNames = map[int64]string{ + READ_LOCKOWNER: "LOCKOWNER", + } + initFlagNames = map[int64]string{ + CAP_ASYNC_READ: "ASYNC_READ", + CAP_POSIX_LOCKS: "POSIX_LOCKS", + CAP_FILE_OPS: "FILE_OPS", + CAP_ATOMIC_O_TRUNC: "ATOMIC_O_TRUNC", + CAP_EXPORT_SUPPORT: "EXPORT_SUPPORT", + CAP_BIG_WRITES: "BIG_WRITES", + CAP_DONT_MASK: "DONT_MASK", + CAP_SPLICE_WRITE: "SPLICE_WRITE", + CAP_SPLICE_MOVE: "SPLICE_MOVE", + CAP_SPLICE_READ: "SPLICE_READ", + CAP_FLOCK_LOCKS: "FLOCK_LOCKS", + CAP_IOCTL_DIR: "IOCTL_DIR", + CAP_AUTO_INVAL_DATA: "AUTO_INVAL_DATA", + CAP_READDIRPLUS: "READDIRPLUS", + CAP_READDIRPLUS_AUTO: "READDIRPLUS_AUTO", + CAP_ASYNC_DIO: "ASYNC_DIO", + CAP_WRITEBACK_CACHE: "WRITEBACK_CACHE", + CAP_NO_OPEN_SUPPORT: "NO_OPEN_SUPPORT", + CAP_PARALLEL_DIROPS: "PARALLEL_DIROPS", + CAP_POSIX_ACL: "POSIX_ACL", + CAP_HANDLE_KILLPRIV: "HANDLE_KILLPRIV", + CAP_ABORT_ERROR: "ABORT_ERROR", + CAP_MAX_PAGES: "MAX_PAGES", + CAP_CACHE_SYMLINKS: "CACHE_SYMLINKS", + CAP_NO_OPENDIR_SUPPORT: "NO_OPENDIR_SUPPORT", + CAP_EXPLICIT_INVAL_DATA: "EXPLICIT_INVAL_DATA", + } + releaseFlagNames = map[int64]string{ + RELEASE_FLUSH: "FLUSH", + } + openFlagNames = map[int64]string{ + int64(os.O_WRONLY): "WRONLY", + int64(os.O_RDWR): "RDWR", + int64(os.O_APPEND): "APPEND", + int64(syscall.O_ASYNC): "ASYNC", + int64(os.O_CREATE): "CREAT", + int64(os.O_EXCL): "EXCL", + int64(syscall.O_NOCTTY): "NOCTTY", + int64(syscall.O_NONBLOCK): "NONBLOCK", + int64(os.O_SYNC): "SYNC", + int64(os.O_TRUNC): "TRUNC", + + int64(syscall.O_CLOEXEC): "CLOEXEC", + int64(syscall.O_DIRECTORY): "DIRECTORY", + } + fuseOpenFlagNames = map[int64]string{ + FOPEN_DIRECT_IO: "DIRECT", + FOPEN_KEEP_CACHE: "CACHE", + FOPEN_NONSEEKABLE: "NONSEEK", + FOPEN_CACHE_DIR: "CACHE_DIR", + FOPEN_STREAM: "STREAM", + } + accessFlagName = map[int64]string{ + X_OK: "x", + W_OK: "w", + R_OK: "r", + } +) + +func flagString(names map[int64]string, fl int64, def string) string { + s := []string{} + for k, v := range names { + if fl&k != 0 { + s = append(s, v) + fl ^= k + } + } + if len(s) == 0 && def != "" { + s = []string{def} + } + if fl != 0 { + s = append(s, fmt.Sprintf("0x%x", fl)) + } + + return strings.Join(s, ",") +} + +func (in *ForgetIn) string() string { + return fmt.Sprintf("{Nlookup=%d}", in.Nlookup) +} + +func (in *_BatchForgetIn) string() string { + return fmt.Sprintf("{Count=%d}", in.Count) +} + +func (in *MkdirIn) string() string { + return fmt.Sprintf("{0%o (0%o)}", in.Mode, in.Umask) +} + +func (in *Rename1In) string() string { + return fmt.Sprintf("{i%d}", in.Newdir) +} + +func (in *RenameIn) string() string { + return fmt.Sprintf("{i%d %x}", in.Newdir, in.Flags) +} + +func (in *SetAttrIn) string() string { + s := []string{} + if in.Valid&FATTR_MODE != 0 { + s = append(s, fmt.Sprintf("mode 0%o", in.Mode)) + } + if in.Valid&FATTR_UID != 0 { + s = append(s, fmt.Sprintf("uid %d", in.Uid)) + } + if in.Valid&FATTR_GID != 0 { + s = append(s, fmt.Sprintf("gid %d", in.Gid)) + } + if in.Valid&FATTR_SIZE != 0 { + s = append(s, fmt.Sprintf("size %d", in.Size)) + } + if in.Valid&FATTR_ATIME != 0 { + s = append(s, fmt.Sprintf("atime %d.%09d", in.Atime, in.Atimensec)) + } + if in.Valid&FATTR_MTIME != 0 { + s = append(s, fmt.Sprintf("mtime %d.%09d", in.Mtime, in.Mtimensec)) + } + if in.Valid&FATTR_FH != 0 { + s = append(s, fmt.Sprintf("fh %d", in.Fh)) + } + // TODO - FATTR_ATIME_NOW = (1 << 7), FATTR_MTIME_NOW = (1 << 8), FATTR_LOCKOWNER = (1 << 9) + return fmt.Sprintf("{%s}", strings.Join(s, ", ")) +} + +func (in *ReleaseIn) string() string { + return fmt.Sprintf("{Fh %d %s %s L%d}", + in.Fh, flagString(openFlagNames, int64(in.Flags), ""), + flagString(releaseFlagNames, int64(in.ReleaseFlags), ""), + in.LockOwner) +} + +func (in *OpenIn) string() string { + return fmt.Sprintf("{%s}", flagString(openFlagNames, int64(in.Flags), "O_RDONLY")) +} + +func (in *OpenOut) string() string { + return fmt.Sprintf("{Fh %d %s}", in.Fh, + flagString(fuseOpenFlagNames, int64(in.OpenFlags), "")) +} + +func (in *InitIn) string() string { + return fmt.Sprintf("{%d.%d Ra 0x%x %s}", + in.Major, in.Minor, in.MaxReadAhead, + flagString(initFlagNames, int64(in.Flags), "")) +} + +func (o *InitOut) string() string { + return fmt.Sprintf("{%d.%d Ra 0x%x %s %d/%d Wr 0x%x Tg 0x%x}", + o.Major, o.Minor, o.MaxReadAhead, + flagString(initFlagNames, int64(o.Flags), ""), + o.CongestionThreshold, o.MaxBackground, o.MaxWrite, + o.TimeGran) +} + +func (s *FsyncIn) string() string { + return fmt.Sprintf("{Fh %d Flags %x}", s.Fh, s.FsyncFlags) +} + +func (in *SetXAttrIn) string() string { + return fmt.Sprintf("{sz %d f%o}", in.Size, in.Flags) +} + +func (in *GetXAttrIn) string() string { + return fmt.Sprintf("{sz %d}", in.Size) +} + +func (o *GetXAttrOut) string() string { + return fmt.Sprintf("{sz %d}", o.Size) +} + +func (in *AccessIn) string() string { + return fmt.Sprintf("{u=%d g=%d %s}", + in.Uid, + in.Gid, + flagString(accessFlagName, int64(in.Mask), "")) +} + +func (in *FlushIn) string() string { + return fmt.Sprintf("{Fh %d}", in.Fh) +} + +func (o *AttrOut) string() string { + return fmt.Sprintf( + "{tA=%gs %v}", + ft(o.AttrValid, o.AttrValidNsec), &o.Attr) +} + +// ft converts (seconds , nanoseconds) -> float(seconds) +func ft(tsec uint64, tnsec uint32) float64 { + return float64(tsec) + float64(tnsec)*1E-9 +} + +// Returned by LOOKUP +func (o *EntryOut) string() string { + return fmt.Sprintf("{i%d g%d tE=%gs tA=%gs %v}", + o.NodeId, o.Generation, ft(o.EntryValid, o.EntryValidNsec), + ft(o.AttrValid, o.AttrValidNsec), &o.Attr) +} + +func (o *CreateOut) string() string { + return fmt.Sprintf("{i%d g%d %v %v}", o.NodeId, o.Generation, &o.EntryOut, &o.OpenOut) +} + +func (o *StatfsOut) string() string { + return fmt.Sprintf( + "{blocks (%d,%d)/%d files %d/%d bs%d nl%d frs%d}", + o.Bfree, o.Bavail, o.Blocks, o.Ffree, o.Files, + o.Bsize, o.NameLen, o.Frsize) +} + +func (o *NotifyInvalEntryOut) string() string { + return fmt.Sprintf("{parent i%d sz %d}", o.Parent, o.NameLen) +} + +func (o *NotifyInvalInodeOut) string() string { + return fmt.Sprintf("{i%d [%d +%d)}", o.Ino, o.Off, o.Length) +} + +func (o *NotifyInvalDeleteOut) string() string { + return fmt.Sprintf("{parent i%d ch i%d sz %d}", o.Parent, o.Child, o.NameLen) +} + +func (o *NotifyStoreOut) string() string { + return fmt.Sprintf("{i%d [%d +%d)}", o.Nodeid, o.Offset, o.Size) +} + +func (o *NotifyRetrieveOut) string() string { + return fmt.Sprintf("{> %d: i%d [%d +%d)}", o.NotifyUnique, o.Nodeid, o.Offset, o.Size) +} + +func (i *NotifyRetrieveIn) string() string { + return fmt.Sprintf("{[%d +%d)}", i.Offset, i.Size) +} + +func (f *FallocateIn) string() string { + return fmt.Sprintf("{Fh %d [%d +%d) mod 0%o}", + f.Fh, f.Offset, f.Length, f.Mode) +} + +func (f *LinkIn) string() string { + return fmt.Sprintf("{Oldnodeid: %d}", f.Oldnodeid) +} + +func (o *WriteOut) string() string { + return fmt.Sprintf("{%db }", o.Size) + +} +func (i *CopyFileRangeIn) string() string { + return fmt.Sprintf("{Fh %d [%d +%d) => i%d Fh %d [%d, %d)}", + i.FhIn, i.OffIn, i.Len, i.NodeIdOut, i.FhOut, i.OffOut, i.Len) +} + +func (in *InterruptIn) string() string { + return fmt.Sprintf("{ix %d}", in.Unique) +} + +var seekNames = map[uint32]string{ + 0: "SET", + 1: "CUR", + 2: "END", + 3: "DATA", + 4: "HOLE", +} + +func (in *LseekIn) string() string { + return fmt.Sprintf("{Fh %d [%s +%d)}", in.Fh, + seekNames[in.Whence], in.Offset) +} + +func (o *LseekOut) string() string { + return fmt.Sprintf("{%d}", o.Offset) +} + +// Print pretty prints FUSE data types for kernel communication +func Print(obj interface{}) string { + t, ok := obj.(interface { + string() string + }) + if ok { + return t.string() + } + return fmt.Sprintf("%T: %v", obj, obj) +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/print_darwin.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/print_darwin.go new file mode 100644 index 000000000..38fbe5dbc --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/print_darwin.go @@ -0,0 +1,54 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "fmt" +) + +func init() { + initFlagNames[CAP_XTIMES] = "XTIMES" + initFlagNames[CAP_VOL_RENAME] = "VOL_RENAME" + initFlagNames[CAP_CASE_INSENSITIVE] = "CASE_INSENSITIVE" +} + +func (a *Attr) string() string { + return fmt.Sprintf( + "{M0%o SZ=%d L=%d "+ + "%d:%d "+ + "%d %d:%d "+ + "A %f "+ + "M %f "+ + "C %f}", + a.Mode, a.Size, a.Nlink, + a.Uid, a.Gid, + a.Blocks, + a.Rdev, a.Ino, ft(a.Atime, a.Atimensec), ft(a.Mtime, a.Mtimensec), + ft(a.Ctime, a.Ctimensec)) +} + +func (me *CreateIn) string() string { + return fmt.Sprintf( + "{0%o [%s]}", me.Mode, + flagString(openFlagNames, int64(me.Flags), "O_RDONLY")) +} + +func (me *GetAttrIn) string() string { return "" } + +func (me *MknodIn) string() string { + return fmt.Sprintf("{0%o, %d}", me.Mode, me.Rdev) +} + +func (me *ReadIn) string() string { + return fmt.Sprintf("{Fh %d [%d +%d) %s}", + me.Fh, me.Offset, me.Size, + flagString(readFlagNames, int64(me.ReadFlags), "")) +} + +func (me *WriteIn) string() string { + return fmt.Sprintf("{Fh %d [%d +%d) %s}", + me.Fh, me.Offset, me.Size, + flagString(writeFlagNames, int64(me.WriteFlags), "")) +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/print_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/print_linux.go new file mode 100644 index 000000000..4eb637e03 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/print_linux.go @@ -0,0 +1,61 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "fmt" + "syscall" +) + +func init() { + openFlagNames[syscall.O_DIRECT] = "DIRECT" + openFlagNames[syscall.O_LARGEFILE] = "LARGEFILE" + openFlagNames[syscall_O_NOATIME] = "NOATIME" +} + +func (a *Attr) string() string { + return fmt.Sprintf( + "{M0%o SZ=%d L=%d "+ + "%d:%d "+ + "B%d*%d i%d:%d "+ + "A %f "+ + "M %f "+ + "C %f}", + a.Mode, a.Size, a.Nlink, + a.Uid, a.Gid, + a.Blocks, a.Blksize, + a.Rdev, a.Ino, ft(a.Atime, a.Atimensec), ft(a.Mtime, a.Mtimensec), + ft(a.Ctime, a.Ctimensec)) +} + +func (in *CreateIn) string() string { + return fmt.Sprintf( + "{0%o [%s] (0%o)}", in.Mode, + flagString(openFlagNames, int64(in.Flags), "O_RDONLY"), in.Umask) +} + +func (in *GetAttrIn) string() string { + return fmt.Sprintf("{Fh %d}", in.Fh_) +} + +func (in *MknodIn) string() string { + return fmt.Sprintf("{0%o (0%o), %d}", in.Mode, in.Umask, in.Rdev) +} + +func (in *ReadIn) string() string { + return fmt.Sprintf("{Fh %d [%d +%d) %s L %d %s}", + in.Fh, in.Offset, in.Size, + flagString(readFlagNames, int64(in.ReadFlags), ""), + in.LockOwner, + flagString(openFlagNames, int64(in.Flags), "RDONLY")) +} + +func (in *WriteIn) string() string { + return fmt.Sprintf("{Fh %d [%d +%d) %s L %d %s}", + in.Fh, in.Offset, in.Size, + flagString(writeFlagNames, int64(in.WriteFlags), ""), + in.LockOwner, + flagString(openFlagNames, int64(in.Flags), "RDONLY")) +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/read.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/read.go new file mode 100644 index 000000000..1612679b9 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/read.go @@ -0,0 +1,75 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "io" + "syscall" +) + +// ReadResultData is the read return for returning bytes directly. +type readResultData struct { + // Raw bytes for the read. + Data []byte +} + +func (r *readResultData) Size() int { + return len(r.Data) +} + +func (r *readResultData) Done() { +} + +func (r *readResultData) Bytes(buf []byte) ([]byte, Status) { + return r.Data, OK +} + +func ReadResultData(b []byte) ReadResult { + return &readResultData{b} +} + +func ReadResultFd(fd uintptr, off int64, sz int) ReadResult { + return &readResultFd{fd, off, sz} +} + +// ReadResultFd is the read return for zero-copy file data. +type readResultFd struct { + // Splice from the following file. + Fd uintptr + + // Offset within Fd, or -1 to use current offset. + Off int64 + + // Size of data to be loaded. Actual data available may be + // less at the EOF. + Sz int +} + +// Reads raw bytes from file descriptor if necessary, using the passed +// buffer as storage. +func (r *readResultFd) Bytes(buf []byte) ([]byte, Status) { + sz := r.Sz + if len(buf) < sz { + sz = len(buf) + } + + n, err := syscall.Pread(int(r.Fd), buf[:sz], r.Off) + if err == io.EOF { + err = nil + } + + if n < 0 { + n = 0 + } + + return buf[:n], ToStatus(err) +} + +func (r *readResultFd) Size() int { + return r.Sz +} + +func (r *readResultFd) Done() { +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/request.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/request.go new file mode 100644 index 000000000..1fab11231 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/request.go @@ -0,0 +1,254 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "bytes" + "fmt" + "log" + "strings" + "time" + "unsafe" +) + +var sizeOfOutHeader = unsafe.Sizeof(OutHeader{}) +var zeroOutBuf [outputHeaderSize]byte + +type request struct { + inflightIndex int + + cancel chan struct{} + + // written under Server.reqMu + interrupted bool + + inputBuf []byte + + // These split up inputBuf. + inHeader *InHeader // generic header + inData unsafe.Pointer // per op data + arg []byte // flat data. + + filenames []string // filename arguments + + // Output data. + status Status + flatData []byte + fdData *readResultFd + + // In case of read, keep read result here so we can call + // Done() on it. + readResult ReadResult + + // Start timestamp for timing info. + startTime time.Time + + // All information pertaining to opcode of this request. + handler *operationHandler + + // Request storage. For large inputs and outputs, use data + // obtained through bufferpool. + bufferPoolInputBuf []byte + bufferPoolOutputBuf []byte + + // For small pieces of data, we use the following inlines + // arrays: + // + // Output header and structured data. + outBuf [outputHeaderSize]byte + + // Input, if small enough to fit here. + smallInputBuf [128]byte +} + +func (r *request) clear() { + r.inputBuf = nil + r.inHeader = nil + r.inData = nil + r.arg = nil + r.filenames = nil + r.status = OK + r.flatData = nil + r.fdData = nil + r.startTime = time.Time{} + r.handler = nil + r.readResult = nil +} + +func (r *request) InputDebug() string { + val := "" + if r.handler != nil && r.handler.DecodeIn != nil { + val = fmt.Sprintf("%v ", Print(r.handler.DecodeIn(r.inData))) + } + + names := "" + if r.filenames != nil { + names = fmt.Sprintf("%q", r.filenames) + } + + if len(r.arg) > 0 { + names += fmt.Sprintf(" %db", len(r.arg)) + } + + return fmt.Sprintf("rx %d: %s i%d %s%s", + r.inHeader.Unique, operationName(r.inHeader.Opcode), r.inHeader.NodeId, + val, names) +} + +func (r *request) OutputDebug() string { + var dataStr string + if r.handler != nil && r.handler.DecodeOut != nil && r.handler.OutputSize > 0 { + dataStr = Print(r.handler.DecodeOut(r.outData())) + } + + max := 1024 + if len(dataStr) > max { + dataStr = dataStr[:max] + fmt.Sprintf(" ...trimmed") + } + + flatStr := "" + if r.flatDataSize() > 0 { + if r.handler != nil && r.handler.FileNameOut { + s := strings.TrimRight(string(r.flatData), "\x00") + flatStr = fmt.Sprintf(" %q", s) + } else { + spl := "" + if r.fdData != nil { + spl = " (fd data)" + } else { + l := len(r.flatData) + s := "" + if l > 8 { + l = 8 + s = "..." + } + spl = fmt.Sprintf(" %q%s", r.flatData[:l], s) + } + flatStr = fmt.Sprintf(" %db data%s", r.flatDataSize(), spl) + } + } + + extraStr := dataStr + flatStr + if extraStr != "" { + extraStr = ", " + extraStr + } + return fmt.Sprintf("tx %d: %v%s", + r.inHeader.Unique, r.status, extraStr) +} + +// setInput returns true if it takes ownership of the argument, false if not. +func (r *request) setInput(input []byte) bool { + if len(input) < len(r.smallInputBuf) { + copy(r.smallInputBuf[:], input) + r.inputBuf = r.smallInputBuf[:len(input)] + return false + } + r.inputBuf = input + r.bufferPoolInputBuf = input[:cap(input)] + + return true +} + +func (r *request) parseHeader() Status { + if len(r.inputBuf) < int(unsafe.Sizeof(InHeader{})) { + log.Printf("Short read for input header: %v", r.inputBuf) + return EINVAL + } + + r.inHeader = (*InHeader)(unsafe.Pointer(&r.inputBuf[0])) + return OK +} + +func (r *request) parse() { + r.arg = r.inputBuf[:] + r.handler = getHandler(r.inHeader.Opcode) + if r.handler == nil { + log.Printf("Unknown opcode %d", r.inHeader.Opcode) + r.status = ENOSYS + return + } + + if len(r.arg) < int(r.handler.InputSize) { + log.Printf("Short read for %v: %v", operationName(r.inHeader.Opcode), r.arg) + r.status = EIO + return + } + + if r.handler.InputSize > 0 { + r.inData = unsafe.Pointer(&r.arg[0]) + r.arg = r.arg[r.handler.InputSize:] + } else { + r.arg = r.arg[unsafe.Sizeof(InHeader{}):] + } + + count := r.handler.FileNames + if count > 0 { + if count == 1 && r.inHeader.Opcode == _OP_SETXATTR { + // SETXATTR is special: the only opcode with a file name AND a + // binary argument. + splits := bytes.SplitN(r.arg, []byte{0}, 2) + r.filenames = []string{string(splits[0])} + } else if count == 1 { + r.filenames = []string{string(r.arg[:len(r.arg)-1])} + } else { + names := bytes.SplitN(r.arg[:len(r.arg)-1], []byte{0}, count) + r.filenames = make([]string, len(names)) + for i, n := range names { + r.filenames[i] = string(n) + } + if len(names) != count { + log.Println("filename argument mismatch", names, count) + r.status = EIO + } + } + } + + copy(r.outBuf[:r.handler.OutputSize+sizeOfOutHeader], + zeroOutBuf[:r.handler.OutputSize+sizeOfOutHeader]) + +} + +func (r *request) outData() unsafe.Pointer { + return unsafe.Pointer(&r.outBuf[sizeOfOutHeader]) +} + +// serializeHeader serializes the response header. The header points +// to an internal buffer of the receiver. +func (r *request) serializeHeader(flatDataSize int) (header []byte) { + var dataLength uintptr + + if r.handler != nil { + dataLength = r.handler.OutputSize + } + if r.status > OK { + // only do this for positive status; negative status + // is used for notification. + dataLength = 0 + } + + // [GET|LIST]XATTR is two opcodes in one: get/list xattr size (return + // structured GetXAttrOut, no flat data) and get/list xattr data + // (return no structured data, but only flat data) + if r.inHeader.Opcode == _OP_GETXATTR || r.inHeader.Opcode == _OP_LISTXATTR { + if (*GetXAttrIn)(r.inData).Size != 0 { + dataLength = 0 + } + } + + header = r.outBuf[:sizeOfOutHeader+dataLength] + o := (*OutHeader)(unsafe.Pointer(&header[0])) + o.Unique = r.inHeader.Unique + o.Status = int32(-r.status) + o.Length = uint32( + int(sizeOfOutHeader) + int(dataLength) + flatDataSize) + return header +} + +func (r *request) flatDataSize() int { + if r.fdData != nil { + return r.fdData.Size() + } + return len(r.flatData) +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/request_darwin.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/request_darwin.go new file mode 100644 index 000000000..634462481 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/request_darwin.go @@ -0,0 +1,13 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +const outputHeaderSize = 200 + +const ( + _FUSE_KERNEL_VERSION = 7 + _MINIMUM_MINOR_VERSION = 8 + _OUR_MINOR_VERSION = 8 +) diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/request_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/request_linux.go new file mode 100644 index 000000000..b93eef44e --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/request_linux.go @@ -0,0 +1,13 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +const outputHeaderSize = 160 + +const ( + _FUSE_KERNEL_VERSION = 7 + _MINIMUM_MINOR_VERSION = 12 + _OUR_MINOR_VERSION = 28 +) diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/server.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/server.go new file mode 100644 index 000000000..fdb319025 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/server.go @@ -0,0 +1,858 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "fmt" + "log" + "math" + "os" + "path/filepath" + "runtime" + "strings" + "sync" + "syscall" + "time" + "unsafe" +) + +const ( + // The kernel caps writes at 128k. + MAX_KERNEL_WRITE = 128 * 1024 +) + +// Server contains the logic for reading from the FUSE device and +// translating it to RawFileSystem interface calls. +type Server struct { + // Empty if unmounted. + mountPoint string + fileSystem RawFileSystem + + // writeMu serializes close and notify writes + writeMu sync.Mutex + + // I/O with kernel and daemon. + mountFd int + + latencies LatencyMap + + opts *MountOptions + + // Pools for []byte + buffers bufferPool + + // Pool for request structs. + reqPool sync.Pool + + // Pool for raw requests data + readPool sync.Pool + reqMu sync.Mutex + reqReaders int + reqInflight []*request + kernelSettings InitIn + + // in-flight notify-retrieve queries + retrieveMu sync.Mutex + retrieveNext uint64 + retrieveTab map[uint64]*retrieveCacheRequest // notifyUnique -> retrieve request + + singleReader bool + canSplice bool + loops sync.WaitGroup + + ready chan error + + // for implementing single threaded processing. + requestProcessingMu sync.Mutex +} + +// SetDebug is deprecated. Use MountOptions.Debug instead. +func (ms *Server) SetDebug(dbg bool) { + // This will typically trigger the race detector. + ms.opts.Debug = dbg +} + +// KernelSettings returns the Init message from the kernel, so +// filesystems can adapt to availability of features of the kernel +// driver. The message should not be altered. +func (ms *Server) KernelSettings() *InitIn { + ms.reqMu.Lock() + s := ms.kernelSettings + ms.reqMu.Unlock() + + return &s +} + +const _MAX_NAME_LEN = 20 + +// This type may be provided for recording latencies of each FUSE +// operation. +type LatencyMap interface { + Add(name string, dt time.Duration) +} + +// RecordLatencies switches on collection of timing for each request +// coming from the kernel.P assing a nil argument switches off the +func (ms *Server) RecordLatencies(l LatencyMap) { + ms.latencies = l +} + +// Unmount calls fusermount -u on the mount. This has the effect of +// shutting down the filesystem. After the Server is unmounted, it +// should be discarded. +func (ms *Server) Unmount() (err error) { + if ms.mountPoint == "" { + return nil + } + delay := time.Duration(0) + for try := 0; try < 5; try++ { + err = unmount(ms.mountPoint) + if err == nil { + break + } + + // Sleep for a bit. This is not pretty, but there is + // no way we can be certain that the kernel thinks all + // open files have already been closed. + delay = 2*delay + 5*time.Millisecond + time.Sleep(delay) + } + if err != nil { + return + } + // Wait for event loops to exit. + ms.loops.Wait() + ms.mountPoint = "" + return err +} + +// NewServer creates a server and attaches it to the given directory. +func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server, error) { + if opts == nil { + opts = &MountOptions{ + MaxBackground: _DEFAULT_BACKGROUND_TASKS, + } + } + o := *opts + + if o.MaxWrite < 0 { + o.MaxWrite = 0 + } + if o.MaxWrite == 0 { + o.MaxWrite = 1 << 16 + } + if o.MaxWrite > MAX_KERNEL_WRITE { + o.MaxWrite = MAX_KERNEL_WRITE + } + if o.Name == "" { + name := fs.String() + l := len(name) + if l > _MAX_NAME_LEN { + l = _MAX_NAME_LEN + } + o.Name = strings.Replace(name[:l], ",", ";", -1) + } + + for _, s := range o.optionsStrings() { + if strings.Contains(s, ",") { + return nil, fmt.Errorf("found ',' in option string %q", s) + } + } + + ms := &Server{ + fileSystem: fs, + opts: &o, + retrieveTab: make(map[uint64]*retrieveCacheRequest), + // OSX has races when multiple routines read from the + // FUSE device: on unmount, sometime some reads do not + // error-out, meaning that unmount will hang. + singleReader: runtime.GOOS == "darwin", + ready: make(chan error, 1), + } + ms.reqPool.New = func() interface{} { + return &request{ + cancel: make(chan struct{}), + } + } + ms.readPool.New = func() interface{} { + buf := make([]byte, o.MaxWrite+int(maxInputSize)+logicalBlockSize) + buf = alignSlice(buf, unsafe.Sizeof(WriteIn{}), logicalBlockSize, uintptr(o.MaxWrite)+maxInputSize) + return buf + } + mountPoint = filepath.Clean(mountPoint) + if !filepath.IsAbs(mountPoint) { + cwd, err := os.Getwd() + if err != nil { + return nil, err + } + mountPoint = filepath.Clean(filepath.Join(cwd, mountPoint)) + } + fd, err := mount(mountPoint, &o, ms.ready) + if err != nil { + return nil, err + } + + ms.mountPoint = mountPoint + ms.mountFd = fd + + if code := ms.handleInit(); !code.Ok() { + syscall.Close(fd) + // TODO - unmount as well? + return nil, fmt.Errorf("init: %s", code) + } + return ms, nil +} + +func (o *MountOptions) optionsStrings() []string { + var r []string + r = append(r, o.Options...) + + if o.AllowOther { + r = append(r, "allow_other") + } + + if o.FsName != "" { + r = append(r, "fsname="+o.FsName) + } + if o.Name != "" { + r = append(r, "subtype="+o.Name) + } + + return r +} + +// DebugData returns internal status information for debugging +// purposes. +func (ms *Server) DebugData() string { + var r int + ms.reqMu.Lock() + r = ms.reqReaders + ms.reqMu.Unlock() + + return fmt.Sprintf("readers: %d", r) +} + +// What is a good number? Maybe the number of CPUs? +const _MAX_READERS = 2 + +// handleEINTR retries the given function until it doesn't return syscall.EINTR. +// This is similar to the HANDLE_EINTR() macro from Chromium ( see +// https://code.google.com/p/chromium/codesearch#chromium/src/base/posix/eintr_wrapper.h +// ) and the TEMP_FAILURE_RETRY() from glibc (see +// https://www.gnu.org/software/libc/manual/html_node/Interrupted-Primitives.html +// ). +// +// Don't use handleEINTR() with syscall.Close(); see +// https://code.google.com/p/chromium/issues/detail?id=269623 . +func handleEINTR(fn func() error) (err error) { + for { + err = fn() + if err != syscall.EINTR { + break + } + } + return +} + +// Returns a new request, or error. In case exitIdle is given, returns +// nil, OK if we have too many readers already. +func (ms *Server) readRequest(exitIdle bool) (req *request, code Status) { + req = ms.reqPool.Get().(*request) + dest := ms.readPool.Get().([]byte) + + ms.reqMu.Lock() + if ms.reqReaders > _MAX_READERS { + ms.reqMu.Unlock() + return nil, OK + } + ms.reqReaders++ + ms.reqMu.Unlock() + + var n int + err := handleEINTR(func() error { + var err error + n, err = syscall.Read(ms.mountFd, dest) + return err + }) + if err != nil { + code = ToStatus(err) + ms.reqPool.Put(req) + ms.reqMu.Lock() + ms.reqReaders-- + ms.reqMu.Unlock() + return nil, code + } + + if ms.latencies != nil { + req.startTime = time.Now() + } + gobbled := req.setInput(dest[:n]) + + ms.reqMu.Lock() + defer ms.reqMu.Unlock() + // Must parse request.Unique under lock + if status := req.parseHeader(); !status.Ok() { + return nil, status + } + req.inflightIndex = len(ms.reqInflight) + ms.reqInflight = append(ms.reqInflight, req) + if !gobbled { + ms.readPool.Put(dest) + dest = nil + } + ms.reqReaders-- + if !ms.singleReader && ms.reqReaders <= 0 { + ms.loops.Add(1) + go ms.loop(true) + } + + return req, OK +} + +// returnRequest returns a request to the pool of unused requests. +func (ms *Server) returnRequest(req *request) { + ms.reqMu.Lock() + this := req.inflightIndex + last := len(ms.reqInflight) - 1 + + if last != this { + ms.reqInflight[this] = ms.reqInflight[last] + ms.reqInflight[this].inflightIndex = this + } + ms.reqInflight = ms.reqInflight[:last] + interrupted := req.interrupted + ms.reqMu.Unlock() + + ms.recordStats(req) + if interrupted { + // Don't reposses data, because someone might still + // be looking at it + return + } + + if req.bufferPoolOutputBuf != nil { + ms.buffers.FreeBuffer(req.bufferPoolOutputBuf) + req.bufferPoolOutputBuf = nil + } + + req.clear() + + if p := req.bufferPoolInputBuf; p != nil { + req.bufferPoolInputBuf = nil + ms.readPool.Put(p) + } + ms.reqPool.Put(req) +} + +func (ms *Server) recordStats(req *request) { + if ms.latencies != nil { + dt := time.Now().Sub(req.startTime) + opname := operationName(req.inHeader.Opcode) + ms.latencies.Add(opname, dt) + } +} + +// Serve initiates the FUSE loop. Normally, callers should run Serve() +// and wait for it to exit, but tests will want to run this in a +// goroutine. +// +// Each filesystem operation executes in a separate goroutine. +func (ms *Server) Serve() { + ms.loops.Add(1) + ms.loop(false) + ms.loops.Wait() + + ms.writeMu.Lock() + syscall.Close(ms.mountFd) + ms.writeMu.Unlock() + + // shutdown in-flight cache retrieves. + // + // It is possible that umount comes in the middle - after retrieve + // request was sent to kernel, but corresponding kernel reply has not + // yet been read. We unblock all such readers and wake them up with ENODEV. + ms.retrieveMu.Lock() + rtab := ms.retrieveTab + // retrieve attempts might be erroneously tried even after close + // we have to keep retrieveTab !nil not to panic. + ms.retrieveTab = make(map[uint64]*retrieveCacheRequest) + ms.retrieveMu.Unlock() + for _, reading := range rtab { + reading.n = 0 + reading.st = ENODEV + close(reading.ready) + } +} + +// Wait waits for the serve loop to exit +func (ms *Server) Wait() { + ms.loops.Wait() +} + +func (ms *Server) handleInit() Status { + // The first request should be INIT; read it synchronously, + // and don't spawn new readers. + orig := ms.singleReader + ms.singleReader = true + req, errNo := ms.readRequest(false) + ms.singleReader = orig + + if errNo != OK || req == nil { + return errNo + } + if code := ms.handleRequest(req); !code.Ok() { + return code + } + + // INIT is handled. Init the file system, but don't accept + // incoming requests, so the file system can setup itself. + ms.fileSystem.Init(ms) + return OK +} + +func (ms *Server) loop(exitIdle bool) { + defer ms.loops.Done() +exit: + for { + req, errNo := ms.readRequest(exitIdle) + switch errNo { + case OK: + if req == nil { + break exit + } + case ENOENT: + continue + case ENODEV: + // unmount + if ms.opts.Debug { + log.Printf("received ENODEV (unmount request), thread exiting") + } + break exit + default: // some other error? + log.Printf("Failed to read from fuse conn: %v", errNo) + break exit + } + + if ms.singleReader { + go ms.handleRequest(req) + } else { + ms.handleRequest(req) + } + } +} + +func (ms *Server) handleRequest(req *request) Status { + if ms.opts.SingleThreaded { + ms.requestProcessingMu.Lock() + defer ms.requestProcessingMu.Unlock() + } + + req.parse() + if req.handler == nil { + req.status = ENOSYS + } + + if req.status.Ok() && ms.opts.Debug { + log.Println(req.InputDebug()) + } + + if req.inHeader.NodeId == pollHackInode { + // We want to avoid switching off features through our + // poll hack, so don't use ENOSYS + req.status = EIO + if req.inHeader.Opcode == _OP_POLL { + req.status = ENOSYS + } + } else if req.inHeader.NodeId == FUSE_ROOT_ID && len(req.filenames) > 0 && req.filenames[0] == pollHackName { + doPollHackLookup(ms, req) + } else if req.status.Ok() && req.handler.Func == nil { + log.Printf("Unimplemented opcode %v", operationName(req.inHeader.Opcode)) + req.status = ENOSYS + } else if req.status.Ok() { + req.handler.Func(ms, req) + } + + errNo := ms.write(req) + if errNo != 0 { + log.Printf("writer: Write/Writev failed, err: %v. opcode: %v", + errNo, operationName(req.inHeader.Opcode)) + } + ms.returnRequest(req) + return Status(errNo) +} + +// alignSlice ensures that the byte at alignedByte is aligned with the +// given logical block size. The input slice should be at least (size +// + blockSize) +func alignSlice(buf []byte, alignedByte, blockSize, size uintptr) []byte { + misaligned := uintptr(unsafe.Pointer(&buf[alignedByte])) & (blockSize - 1) + buf = buf[blockSize-misaligned:] + return buf[:size] +} + +func (ms *Server) allocOut(req *request, size uint32) []byte { + if cap(req.bufferPoolOutputBuf) >= int(size) { + req.bufferPoolOutputBuf = req.bufferPoolOutputBuf[:size] + return req.bufferPoolOutputBuf + } + if req.bufferPoolOutputBuf != nil { + ms.buffers.FreeBuffer(req.bufferPoolOutputBuf) + req.bufferPoolOutputBuf = nil + } + // As this allocated a multiple of the page size, very likely + // this is aligned to logicalBlockSize too, which is smaller. + req.bufferPoolOutputBuf = ms.buffers.AllocBuffer(size) + return req.bufferPoolOutputBuf +} + +func (ms *Server) write(req *request) Status { + // Forget/NotifyReply do not wait for reply from filesystem server. + switch req.inHeader.Opcode { + case _OP_FORGET, _OP_BATCH_FORGET, _OP_NOTIFY_REPLY: + return OK + case _OP_INTERRUPT: + if req.status.Ok() { + return OK + } + } + + header := req.serializeHeader(req.flatDataSize()) + if ms.opts.Debug { + log.Println(req.OutputDebug()) + } + + if header == nil { + return OK + } + + s := ms.systemWrite(req, header) + return s +} + +// InodeNotify invalidates the information associated with the inode +// (ie. data cache, attributes, etc.) +func (ms *Server) InodeNotify(node uint64, off int64, length int64) Status { + if !ms.kernelSettings.SupportsNotify(NOTIFY_INVAL_INODE) { + return ENOSYS + } + + req := request{ + inHeader: &InHeader{ + Opcode: _OP_NOTIFY_INVAL_INODE, + }, + handler: operationHandlers[_OP_NOTIFY_INVAL_INODE], + status: NOTIFY_INVAL_INODE, + } + + entry := (*NotifyInvalInodeOut)(req.outData()) + entry.Ino = node + entry.Off = off + entry.Length = length + + // Protect against concurrent close. + ms.writeMu.Lock() + result := ms.write(&req) + ms.writeMu.Unlock() + + if ms.opts.Debug { + log.Println("Response: INODE_NOTIFY", result) + } + return result +} + +// InodeNotifyStoreCache tells kernel to store data into inode's cache. +// +// This call is similar to InodeNotify, but instead of only invalidating a data +// region, it gives updated data directly to the kernel. +func (ms *Server) InodeNotifyStoreCache(node uint64, offset int64, data []byte) Status { + if !ms.kernelSettings.SupportsNotify(NOTIFY_STORE_CACHE) { + return ENOSYS + } + + for len(data) > 0 { + size := len(data) + if size > math.MaxInt32 { + // NotifyStoreOut has only uint32 for size. + // we check for max(int32), not max(uint32), because on 32-bit + // platforms int has only 31-bit for positive range. + size = math.MaxInt32 + } + + st := ms.inodeNotifyStoreCache32(node, offset, data[:size]) + if st != OK { + return st + } + + data = data[size:] + offset += int64(size) + } + + return OK +} + +// inodeNotifyStoreCache32 is internal worker for InodeNotifyStoreCache which +// handles data chunks not larger than 2GB. +func (ms *Server) inodeNotifyStoreCache32(node uint64, offset int64, data []byte) Status { + req := request{ + inHeader: &InHeader{ + Opcode: _OP_NOTIFY_STORE_CACHE, + }, + handler: operationHandlers[_OP_NOTIFY_STORE_CACHE], + status: NOTIFY_STORE_CACHE, + } + + store := (*NotifyStoreOut)(req.outData()) + store.Nodeid = node + store.Offset = uint64(offset) // NOTE not int64, as it is e.g. in NotifyInvalInodeOut + store.Size = uint32(len(data)) + + req.flatData = data + + // Protect against concurrent close. + ms.writeMu.Lock() + result := ms.write(&req) + ms.writeMu.Unlock() + + if ms.opts.Debug { + log.Printf("Response: INODE_NOTIFY_STORE_CACHE: %v", result) + } + return result +} + +// InodeRetrieveCache retrieves data from kernel's inode cache. +// +// InodeRetrieveCache asks kernel to return data from its cache for inode at +// [offset:offset+len(dest)) and waits for corresponding reply. If kernel cache +// has fewer consecutive data starting at offset, that fewer amount is returned. +// In particular if inode data at offset is not cached (0, OK) is returned. +// +// The kernel returns ENOENT if it does not currently have entry for this inode +// in its dentry cache. +func (ms *Server) InodeRetrieveCache(node uint64, offset int64, dest []byte) (n int, st Status) { + // the kernel won't send us in one go more then what we negotiated as MaxWrite. + // retrieve the data in chunks. + // TODO spawn some number of readahead retrievers in parallel. + ntotal := 0 + for { + chunkSize := len(dest) + if chunkSize > ms.opts.MaxWrite { + chunkSize = ms.opts.MaxWrite + } + n, st = ms.inodeRetrieveCache1(node, offset, dest[:chunkSize]) + if st != OK || n == 0 { + break + } + + ntotal += n + offset += int64(n) + dest = dest[n:] + } + + // if we could retrieve at least something - it is ok. + // if ntotal=0 - st will be st returned from first inodeRetrieveCache1. + if ntotal > 0 { + st = OK + } + return ntotal, st +} + +// inodeRetrieveCache1 is internal worker for InodeRetrieveCache which +// actually talks to kernel and retrieves chunks not larger than ms.opts.MaxWrite. +func (ms *Server) inodeRetrieveCache1(node uint64, offset int64, dest []byte) (n int, st Status) { + if !ms.kernelSettings.SupportsNotify(NOTIFY_RETRIEVE_CACHE) { + return 0, ENOSYS + } + + req := request{ + inHeader: &InHeader{ + Opcode: _OP_NOTIFY_RETRIEVE_CACHE, + }, + handler: operationHandlers[_OP_NOTIFY_RETRIEVE_CACHE], + status: NOTIFY_RETRIEVE_CACHE, + } + + // retrieve up to 2GB not to overflow uint32 size in NotifyRetrieveOut. + // see InodeNotifyStoreCache in similar place for why it is only 2GB, not 4GB. + // + // ( InodeRetrieveCache calls us with chunks not larger than + // ms.opts.MaxWrite, but MaxWrite is int, so let's be extra cautious ) + size := len(dest) + if size > math.MaxInt32 { + size = math.MaxInt32 + } + dest = dest[:size] + + q := (*NotifyRetrieveOut)(req.outData()) + q.Nodeid = node + q.Offset = uint64(offset) // not int64, as it is e.g. in NotifyInvalInodeOut + q.Size = uint32(len(dest)) + + reading := &retrieveCacheRequest{ + nodeid: q.Nodeid, + offset: q.Offset, + dest: dest, + ready: make(chan struct{}), + } + + ms.retrieveMu.Lock() + q.NotifyUnique = ms.retrieveNext + ms.retrieveNext++ + ms.retrieveTab[q.NotifyUnique] = reading + ms.retrieveMu.Unlock() + + // Protect against concurrent close. + ms.writeMu.Lock() + result := ms.write(&req) + ms.writeMu.Unlock() + + if ms.opts.Debug { + log.Printf("Response: NOTIFY_RETRIEVE_CACHE: %v", result) + } + if result != OK { + ms.retrieveMu.Lock() + r := ms.retrieveTab[q.NotifyUnique] + if r == reading { + delete(ms.retrieveTab, q.NotifyUnique) + } else if r == nil { + // ok - might be dequeued by umount + } else { + // although very unlikely, it is possible that kernel sends + // unexpected NotifyReply with our notifyUnique, then + // retrieveNext wraps, makes full cycle, and another + // retrieve request is made with the same notifyUnique. + log.Printf("W: INODE_RETRIEVE_CACHE: request with notifyUnique=%d mutated", q.NotifyUnique) + } + ms.retrieveMu.Unlock() + return 0, result + } + + // NotifyRetrieveOut sent to the kernel successfully. Now the kernel + // have to return data in a separate write-style NotifyReply request. + // Wait for the result. + <-reading.ready + return reading.n, reading.st +} + +// retrieveCacheRequest represents in-flight cache retrieve request. +type retrieveCacheRequest struct { + nodeid uint64 + offset uint64 + dest []byte + + // reply status + n int + st Status + ready chan struct{} +} + +// DeleteNotify notifies the kernel that an entry is removed from a +// directory. In many cases, this is equivalent to EntryNotify, +// except when the directory is in use, eg. as working directory of +// some process. You should not hold any FUSE filesystem locks, as that +// can lead to deadlock. +func (ms *Server) DeleteNotify(parent uint64, child uint64, name string) Status { + if ms.kernelSettings.Minor < 18 { + return ms.EntryNotify(parent, name) + } + + req := request{ + inHeader: &InHeader{ + Opcode: _OP_NOTIFY_DELETE, + }, + handler: operationHandlers[_OP_NOTIFY_DELETE], + status: NOTIFY_DELETE, + } + + entry := (*NotifyInvalDeleteOut)(req.outData()) + entry.Parent = parent + entry.Child = child + entry.NameLen = uint32(len(name)) + + // Many versions of FUSE generate stacktraces if the + // terminating null byte is missing. + nameBytes := make([]byte, len(name)+1) + copy(nameBytes, name) + nameBytes[len(nameBytes)-1] = '\000' + req.flatData = nameBytes + + // Protect against concurrent close. + ms.writeMu.Lock() + result := ms.write(&req) + ms.writeMu.Unlock() + + if ms.opts.Debug { + log.Printf("Response: DELETE_NOTIFY: %v", result) + } + return result +} + +// EntryNotify should be used if the existence status of an entry +// within a directory changes. You should not hold any FUSE filesystem +// locks, as that can lead to deadlock. +func (ms *Server) EntryNotify(parent uint64, name string) Status { + if !ms.kernelSettings.SupportsNotify(NOTIFY_INVAL_ENTRY) { + return ENOSYS + } + req := request{ + inHeader: &InHeader{ + Opcode: _OP_NOTIFY_INVAL_ENTRY, + }, + handler: operationHandlers[_OP_NOTIFY_INVAL_ENTRY], + status: NOTIFY_INVAL_ENTRY, + } + entry := (*NotifyInvalEntryOut)(req.outData()) + entry.Parent = parent + entry.NameLen = uint32(len(name)) + + // Many versions of FUSE generate stacktraces if the + // terminating null byte is missing. + nameBytes := make([]byte, len(name)+1) + copy(nameBytes, name) + nameBytes[len(nameBytes)-1] = '\000' + req.flatData = nameBytes + + // Protect against concurrent close. + ms.writeMu.Lock() + result := ms.write(&req) + ms.writeMu.Unlock() + + if ms.opts.Debug { + log.Printf("Response: ENTRY_NOTIFY: %v", result) + } + return result +} + +// SupportsVersion returns true if the kernel supports the given +// protocol version or newer. +func (in *InitIn) SupportsVersion(maj, min uint32) bool { + return in.Major > maj || (in.Major == maj && in.Minor >= min) +} + +// SupportsNotify returns whether a certain notification type is +// supported. Pass any of the NOTIFY_* types as argument. +func (in *InitIn) SupportsNotify(notifyType int) bool { + switch notifyType { + case NOTIFY_INVAL_ENTRY: + return in.SupportsVersion(7, 12) + case NOTIFY_INVAL_INODE: + return in.SupportsVersion(7, 12) + case NOTIFY_STORE_CACHE, NOTIFY_RETRIEVE_CACHE: + return in.SupportsVersion(7, 15) + case NOTIFY_DELETE: + return in.SupportsVersion(7, 18) + } + return false +} + +// WaitMount waits for the first request to be served. Use this to +// avoid racing between accessing the (empty or not yet mounted) +// mountpoint, and the OS trying to setup the user-space mount. +func (ms *Server) WaitMount() error { + err := <-ms.ready + if err != nil { + return err + } + return pollHack(ms.mountPoint) +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/server_darwin.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/server_darwin.go new file mode 100644 index 000000000..b4df8daef --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/server_darwin.go @@ -0,0 +1,32 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "syscall" +) + +func (ms *Server) systemWrite(req *request, header []byte) Status { + if req.flatDataSize() == 0 { + err := handleEINTR(func() error { + _, err := syscall.Write(ms.mountFd, header) + return err + }) + return ToStatus(err) + } + + if req.fdData != nil { + sz := req.flatDataSize() + buf := ms.allocOut(req, uint32(sz)) + req.flatData, req.status = req.fdData.Bytes(buf) + header = req.serializeHeader(len(req.flatData)) + } + + _, err := writev(int(ms.mountFd), [][]byte{header, req.flatData}) + if req.readResult != nil { + req.readResult.Done() + } + return ToStatus(err) +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/server_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/server_linux.go new file mode 100644 index 000000000..f5058f68b --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/server_linux.go @@ -0,0 +1,42 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "log" + "syscall" +) + +func (ms *Server) systemWrite(req *request, header []byte) Status { + if req.flatDataSize() == 0 { + err := handleEINTR(func() error { + _, err := syscall.Write(ms.mountFd, header) + return err + }) + return ToStatus(err) + } + + if req.fdData != nil { + if ms.canSplice { + err := ms.trySplice(header, req, req.fdData) + if err == nil { + req.readResult.Done() + return OK + } + log.Println("trySplice:", err) + } + + sz := req.flatDataSize() + buf := ms.allocOut(req, uint32(sz)) + req.flatData, req.status = req.fdData.Bytes(buf) + header = req.serializeHeader(len(req.flatData)) + } + + _, err := writev(ms.mountFd, [][]byte{header, req.flatData}) + if req.readResult != nil { + req.readResult.Done() + } + return ToStatus(err) +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/splice_darwin.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/splice_darwin.go new file mode 100644 index 000000000..dc4774af3 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/splice_darwin.go @@ -0,0 +1,17 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "fmt" +) + +func (s *Server) setSplice() { + s.canSplice = false +} + +func (ms *Server) trySplice(header []byte, req *request, fdData *readResultFd) error { + return fmt.Errorf("unimplemented") +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/splice_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/splice_linux.go new file mode 100644 index 000000000..70f836a24 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/splice_linux.go @@ -0,0 +1,97 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "fmt" + "os" + + "github.com/hanwen/go-fuse/v2/splice" +) + +func (s *Server) setSplice() { + s.canSplice = splice.Resizable() +} + +// trySplice: Zero-copy read from fdData.Fd into /dev/fuse +// +// This is a four-step process: +// +// 1) Splice data form fdData.Fd into the "pair1" pipe buffer --> pair1: [payload] +// Now we know the actual payload length and can +// construct the reply header +// 2) Write header into the "pair2" pipe buffer --> pair2: [header] +// 4) Splice data from "pair1" into "pair2" --> pair2: [header][payload] +// 3) Splice the data from "pair2" into /dev/fuse +// +// This dance is neccessary because header and payload cannot be split across +// two splices and we cannot seek in a pipe buffer. +func (ms *Server) trySplice(header []byte, req *request, fdData *readResultFd) error { + var err error + + // Get a pair of connected pipes + pair1, err := splice.Get() + if err != nil { + return err + } + defer splice.Done(pair1) + + // Grow buffer pipe to requested size + one extra page + // Without the extra page the kernel will block once the pipe is almost full + pair1Sz := fdData.Size() + os.Getpagesize() + if err := pair1.Grow(pair1Sz); err != nil { + return err + } + + // Read data from file + payloadLen, err := pair1.LoadFromAt(fdData.Fd, fdData.Size(), fdData.Off) + + if err != nil { + // TODO - extract the data from splice. + return err + } + + // Get another pair of connected pipes + pair2, err := splice.Get() + if err != nil { + return err + } + defer splice.Done(pair2) + + // Grow pipe to header + actually read size + one extra page + // Without the extra page the kernel will block once the pipe is almost full + header = req.serializeHeader(payloadLen) + total := len(header) + payloadLen + pair2Sz := total + os.Getpagesize() + if err := pair2.Grow(pair2Sz); err != nil { + return err + } + + // Write header into pair2 + n, err := pair2.Write(header) + if err != nil { + return err + } + if n != len(header) { + return fmt.Errorf("Short write into splice: wrote %d, want %d", n, len(header)) + } + + // Write data into pair2 + n, err = pair2.LoadFrom(pair1.ReadFd(), payloadLen) + if err != nil { + return err + } + if n != payloadLen { + return fmt.Errorf("Short splice: wrote %d, want %d", n, payloadLen) + } + + // Write header + data to /dev/fuse + _, err = pair2.WriteTo(uintptr(ms.mountFd), total) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/syscall_darwin.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/syscall_darwin.go new file mode 100644 index 000000000..71ede66f5 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/syscall_darwin.go @@ -0,0 +1,139 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "bytes" + "os" + "syscall" + "unsafe" +) + +// TODO - move these into Go's syscall package. + +func sys_writev(fd int, iovecs *syscall.Iovec, cnt int) (n int, err error) { + n1, _, e1 := syscall.Syscall( + syscall.SYS_WRITEV, + uintptr(fd), uintptr(unsafe.Pointer(iovecs)), uintptr(cnt)) + n = int(n1) + if e1 != 0 { + err = syscall.Errno(e1) + } + return +} + +func writev(fd int, packet [][]byte) (n int, err error) { + iovecs := make([]syscall.Iovec, 0, len(packet)) + + for _, v := range packet { + if len(v) == 0 { + continue + } + vec := syscall.Iovec{ + Base: &v[0], + } + vec.SetLen(len(v)) + iovecs = append(iovecs, vec) + } + + sysErr := handleEINTR(func() error { + var err error + n, err = sys_writev(fd, &iovecs[0], len(iovecs)) + return err + }) + if sysErr != nil { + err = os.NewSyscallError("writev", sysErr) + } + return n, err +} + +func getxattr(path string, attr string, dest []byte) (sz int, errno int) { + pathBs := syscall.StringBytePtr(path) + attrBs := syscall.StringBytePtr(attr) + size, _, errNo := syscall.Syscall6( + syscall.SYS_GETXATTR, + uintptr(unsafe.Pointer(pathBs)), + uintptr(unsafe.Pointer(attrBs)), + uintptr(unsafe.Pointer(&dest[0])), + uintptr(len(dest)), + 0, 0) + return int(size), int(errNo) +} + +func GetXAttr(path string, attr string, dest []byte) (value []byte, errno int) { + sz, errno := getxattr(path, attr, dest) + + for sz > cap(dest) && errno == 0 { + dest = make([]byte, sz) + sz, errno = getxattr(path, attr, dest) + } + + if errno != 0 { + return nil, errno + } + + return dest[:sz], errno +} + +func listxattr(path string, dest []byte) (sz int, errno int) { + pathbs := syscall.StringBytePtr(path) + var destPointer unsafe.Pointer + if len(dest) > 0 { + destPointer = unsafe.Pointer(&dest[0]) + } + size, _, errNo := syscall.Syscall( + syscall.SYS_LISTXATTR, + uintptr(unsafe.Pointer(pathbs)), + uintptr(destPointer), + uintptr(len(dest))) + + return int(size), int(errNo) +} + +func ListXAttr(path string) (attributes []string, errno int) { + dest := make([]byte, 0) + sz, errno := listxattr(path, dest) + if errno != 0 { + return nil, errno + } + + for sz > cap(dest) && errno == 0 { + dest = make([]byte, sz) + sz, errno = listxattr(path, dest) + } + + // -1 to drop the final empty slice. + dest = dest[:sz-1] + attributesBytes := bytes.Split(dest, []byte{0}) + attributes = make([]string, len(attributesBytes)) + for i, v := range attributesBytes { + attributes[i] = string(v) + } + return attributes, errno +} + +func Setxattr(path string, attr string, data []byte, flags int) (errno int) { + pathbs := syscall.StringBytePtr(path) + attrbs := syscall.StringBytePtr(attr) + _, _, errNo := syscall.Syscall6( + syscall.SYS_SETXATTR, + uintptr(unsafe.Pointer(pathbs)), + uintptr(unsafe.Pointer(attrbs)), + uintptr(unsafe.Pointer(&data[0])), + uintptr(len(data)), + uintptr(flags), 0) + + return int(errNo) +} + +func Removexattr(path string, attr string) (errno int) { + pathbs := syscall.StringBytePtr(path) + attrbs := syscall.StringBytePtr(attr) + _, _, errNo := syscall.Syscall( + syscall.SYS_REMOVEXATTR, + uintptr(unsafe.Pointer(pathbs)), + uintptr(unsafe.Pointer(attrbs)), 0) + return int(errNo) +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/syscall_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/syscall_linux.go new file mode 100644 index 000000000..f1a8cac4d --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/syscall_linux.go @@ -0,0 +1,49 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "os" + "syscall" + "unsafe" +) + +// TODO - move these into Go's syscall package. + +func sys_writev(fd int, iovecs *syscall.Iovec, cnt int) (n int, err error) { + n1, _, e1 := syscall.Syscall( + syscall.SYS_WRITEV, + uintptr(fd), uintptr(unsafe.Pointer(iovecs)), uintptr(cnt)) + n = int(n1) + if e1 != 0 { + err = syscall.Errno(e1) + } + return n, err +} + +func writev(fd int, packet [][]byte) (n int, err error) { + iovecs := make([]syscall.Iovec, 0, len(packet)) + + for _, v := range packet { + if len(v) == 0 { + continue + } + vec := syscall.Iovec{ + Base: &v[0], + } + vec.SetLen(len(v)) + iovecs = append(iovecs, vec) + } + + sysErr := handleEINTR(func() error { + var err error + n, err = sys_writev(fd, &iovecs[0], len(iovecs)) + return err + }) + if sysErr != nil { + err = os.NewSyscallError("writev", sysErr) + } + return n, err +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/typeprint.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/typeprint.go new file mode 100644 index 000000000..dda4a9ff2 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/typeprint.go @@ -0,0 +1,9 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +func (a *Attr) String() string { + return Print(a) +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/types.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/types.go new file mode 100644 index 000000000..69e124975 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/types.go @@ -0,0 +1,676 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "io" + "syscall" + "time" +) + +const ( + _DEFAULT_BACKGROUND_TASKS = 12 +) + +// Status is the errno number that a FUSE call returns to the kernel. +type Status int32 + +const ( + OK = Status(0) + + // EACCESS Permission denied + EACCES = Status(syscall.EACCES) + + // EBUSY Device or resource busy + EBUSY = Status(syscall.EBUSY) + + // EAGAIN Resource temporarily unavailable + EAGAIN = Status(syscall.EAGAIN) + + // EINTR Call was interrupted + EINTR = Status(syscall.EINTR) + + // EINVAL Invalid argument + EINVAL = Status(syscall.EINVAL) + + // EIO I/O error + EIO = Status(syscall.EIO) + + // ENOENT No such file or directory + ENOENT = Status(syscall.ENOENT) + + // ENOSYS Function not implemented + ENOSYS = Status(syscall.ENOSYS) + + // ENODATA No data available + ENODATA = Status(syscall.ENODATA) + + // ENOTDIR Not a directory + ENOTDIR = Status(syscall.ENOTDIR) + + // ENOTSUP Not supported + ENOTSUP = Status(syscall.ENOTSUP) + + // EISDIR Is a directory + EISDIR = Status(syscall.EISDIR) + + // EPERM Operation not permitted + EPERM = Status(syscall.EPERM) + + // ERANGE Math result not representable + ERANGE = Status(syscall.ERANGE) + + // EXDEV Cross-device link + EXDEV = Status(syscall.EXDEV) + + // EBADF Bad file number + EBADF = Status(syscall.EBADF) + + // ENODEV No such device + ENODEV = Status(syscall.ENODEV) + + // EROFS Read-only file system + EROFS = Status(syscall.EROFS) +) + +type ForgetIn struct { + InHeader + + Nlookup uint64 +} + +// batch forget is handled internally. +type _ForgetOne struct { + NodeId uint64 + Nlookup uint64 +} + +// batch forget is handled internally. +type _BatchForgetIn struct { + InHeader + Count uint32 + Dummy uint32 +} + +type MkdirIn struct { + InHeader + + // The mode for the new directory. The calling process' umask + // is already factored into the mode. + Mode uint32 + Umask uint32 +} + +type Rename1In struct { + InHeader + Newdir uint64 +} + +type RenameIn struct { + InHeader + Newdir uint64 + Flags uint32 + Padding uint32 +} + +type LinkIn struct { + InHeader + Oldnodeid uint64 +} + +type Owner struct { + Uid uint32 + Gid uint32 +} + +const ( // SetAttrIn.Valid + FATTR_MODE = (1 << 0) + FATTR_UID = (1 << 1) + FATTR_GID = (1 << 2) + FATTR_SIZE = (1 << 3) + FATTR_ATIME = (1 << 4) + FATTR_MTIME = (1 << 5) + FATTR_FH = (1 << 6) + FATTR_ATIME_NOW = (1 << 7) + FATTR_MTIME_NOW = (1 << 8) + FATTR_LOCKOWNER = (1 << 9) + FATTR_CTIME = (1 << 10) +) + +type SetAttrInCommon struct { + InHeader + + Valid uint32 + Padding uint32 + Fh uint64 + Size uint64 + LockOwner uint64 + Atime uint64 + Mtime uint64 + Ctime uint64 + Atimensec uint32 + Mtimensec uint32 + Ctimensec uint32 + Mode uint32 + Unused4 uint32 + Owner + Unused5 uint32 +} + +// GetFh returns the file handle if available, or 0 if undefined. +func (s *SetAttrInCommon) GetFh() (uint64, bool) { + if s.Valid&FATTR_FH != 0 { + return s.Fh, true + } + return 0, false +} + +func (s *SetAttrInCommon) GetMode() (uint32, bool) { + if s.Valid&FATTR_MODE != 0 { + return s.Mode & 07777, true + } + return 0, false +} + +func (s *SetAttrInCommon) GetUID() (uint32, bool) { + if s.Valid&FATTR_UID != 0 { + return s.Uid, true + } + return ^uint32(0), false +} + +func (s *SetAttrInCommon) GetGID() (uint32, bool) { + if s.Valid&FATTR_GID != 0 { + return s.Gid, true + } + return ^uint32(0), false +} + +func (s *SetAttrInCommon) GetSize() (uint64, bool) { + if s.Valid&FATTR_SIZE != 0 { + return s.Size, true + } + return 0, false +} + +func (s *SetAttrInCommon) GetMTime() (time.Time, bool) { + var t time.Time + if s.Valid&FATTR_MTIME != 0 { + if s.Valid&FATTR_MTIME_NOW != 0 { + t = time.Now() + } else { + t = time.Unix(int64(s.Mtime), int64(s.Mtimensec)) + } + return t, true + } + + return t, false +} + +func (s *SetAttrInCommon) GetATime() (time.Time, bool) { + var t time.Time + if s.Valid&FATTR_ATIME != 0 { + if s.Valid&FATTR_ATIME_NOW != 0 { + t = time.Now() + } else { + t = time.Unix(int64(s.Atime), int64(s.Atimensec)) + } + return t, true + } + + return t, false +} + +func (s *SetAttrInCommon) GetCTime() (time.Time, bool) { + var t time.Time + if s.Valid&FATTR_CTIME != 0 { + t = time.Unix(int64(s.Ctime), int64(s.Ctimensec)) + return t, true + } + + return t, false +} + +const RELEASE_FLUSH = (1 << 0) + +type ReleaseIn struct { + InHeader + Fh uint64 + Flags uint32 + ReleaseFlags uint32 + LockOwner uint64 +} + +type OpenIn struct { + InHeader + Flags uint32 + Mode uint32 +} + +const ( + // OpenOut.Flags + FOPEN_DIRECT_IO = (1 << 0) + FOPEN_KEEP_CACHE = (1 << 1) + FOPEN_NONSEEKABLE = (1 << 2) + FOPEN_CACHE_DIR = (1 << 3) + FOPEN_STREAM = (1 << 4) +) + +type OpenOut struct { + Fh uint64 + OpenFlags uint32 + Padding uint32 +} + +// To be set in InitIn/InitOut.Flags. +const ( + CAP_ASYNC_READ = (1 << 0) + CAP_POSIX_LOCKS = (1 << 1) + CAP_FILE_OPS = (1 << 2) + CAP_ATOMIC_O_TRUNC = (1 << 3) + CAP_EXPORT_SUPPORT = (1 << 4) + CAP_BIG_WRITES = (1 << 5) + CAP_DONT_MASK = (1 << 6) + CAP_SPLICE_WRITE = (1 << 7) + CAP_SPLICE_MOVE = (1 << 8) + CAP_SPLICE_READ = (1 << 9) + CAP_FLOCK_LOCKS = (1 << 10) + CAP_IOCTL_DIR = (1 << 11) + CAP_AUTO_INVAL_DATA = (1 << 12) + CAP_READDIRPLUS = (1 << 13) + CAP_READDIRPLUS_AUTO = (1 << 14) + CAP_ASYNC_DIO = (1 << 15) + CAP_WRITEBACK_CACHE = (1 << 16) + CAP_NO_OPEN_SUPPORT = (1 << 17) + CAP_PARALLEL_DIROPS = (1 << 18) + CAP_HANDLE_KILLPRIV = (1 << 19) + CAP_POSIX_ACL = (1 << 20) + CAP_ABORT_ERROR = (1 << 21) + CAP_MAX_PAGES = (1 << 22) + CAP_CACHE_SYMLINKS = (1 << 23) + CAP_NO_OPENDIR_SUPPORT = (1 << 24) + CAP_EXPLICIT_INVAL_DATA = (1 << 25) +) + +type InitIn struct { + InHeader + + Major uint32 + Minor uint32 + MaxReadAhead uint32 + Flags uint32 +} + +type InitOut struct { + Major uint32 + Minor uint32 + MaxReadAhead uint32 + Flags uint32 + MaxBackground uint16 + CongestionThreshold uint16 + MaxWrite uint32 + TimeGran uint32 + MaxPages uint16 + Padding uint16 + Unused [8]uint32 +} + +type _CuseInitIn struct { + InHeader + Major uint32 + Minor uint32 + Unused uint32 + Flags uint32 +} + +type _CuseInitOut struct { + Major uint32 + Minor uint32 + Unused uint32 + Flags uint32 + MaxRead uint32 + MaxWrite uint32 + DevMajor uint32 + DevMinor uint32 + Spare [10]uint32 +} + +type InterruptIn struct { + InHeader + Unique uint64 +} + +type _BmapIn struct { + InHeader + Block uint64 + Blocksize uint32 + Padding uint32 +} + +type _BmapOut struct { + Block uint64 +} + +const ( + FUSE_IOCTL_COMPAT = (1 << 0) + FUSE_IOCTL_UNRESTRICTED = (1 << 1) + FUSE_IOCTL_RETRY = (1 << 2) +) + +type _IoctlIn struct { + InHeader + Fh uint64 + Flags uint32 + Cmd uint32 + Arg uint64 + InSize uint32 + OutSize uint32 +} + +type _IoctlOut struct { + Result int32 + Flags uint32 + InIovs uint32 + OutIovs uint32 +} + +type _PollIn struct { + InHeader + Fh uint64 + Kh uint64 + Flags uint32 + Padding uint32 +} + +type _PollOut struct { + Revents uint32 + Padding uint32 +} + +type _NotifyPollWakeupOut struct { + Kh uint64 +} + +type WriteOut struct { + Size uint32 + Padding uint32 +} + +type GetXAttrOut struct { + Size uint32 + Padding uint32 +} + +type FileLock struct { + Start uint64 + End uint64 + Typ uint32 + Pid uint32 +} + +type LkIn struct { + InHeader + Fh uint64 + Owner uint64 + Lk FileLock + LkFlags uint32 + Padding uint32 +} + +type LkOut struct { + Lk FileLock +} + +// For AccessIn.Mask. +const ( + X_OK = 1 + W_OK = 2 + R_OK = 4 + F_OK = 0 +) + +type AccessIn struct { + InHeader + Mask uint32 + Padding uint32 +} + +type FsyncIn struct { + InHeader + Fh uint64 + FsyncFlags uint32 + Padding uint32 +} + +type OutHeader struct { + Length uint32 + Status int32 + Unique uint64 +} + +type NotifyInvalInodeOut struct { + Ino uint64 + Off int64 + Length int64 +} + +type NotifyInvalEntryOut struct { + Parent uint64 + NameLen uint32 + Padding uint32 +} + +type NotifyInvalDeleteOut struct { + Parent uint64 + Child uint64 + NameLen uint32 + Padding uint32 +} + +type NotifyStoreOut struct { + Nodeid uint64 + Offset uint64 + Size uint32 + Padding uint32 +} + +type NotifyRetrieveOut struct { + NotifyUnique uint64 + Nodeid uint64 + Offset uint64 + Size uint32 + Padding uint32 +} + +type NotifyRetrieveIn struct { + InHeader + Dummy1 uint64 + Offset uint64 + Size uint32 + Dummy2 uint32 + Dummy3 uint64 + Dummy4 uint64 +} + +const ( + // NOTIFY_POLL = -1 // notify kernel that a poll waiting for IO on a file handle should wake up + NOTIFY_INVAL_INODE = -2 // notify kernel that an inode should be invalidated + NOTIFY_INVAL_ENTRY = -3 // notify kernel that a directory entry should be invalidated + NOTIFY_STORE_CACHE = -4 // store data into kernel cache of an inode + NOTIFY_RETRIEVE_CACHE = -5 // retrieve data from kernel cache of an inode + NOTIFY_DELETE = -6 // notify kernel that a directory entry has been deleted + +// NOTIFY_CODE_MAX = -6 +) + +type FlushIn struct { + InHeader + Fh uint64 + Unused uint32 + Padding uint32 + LockOwner uint64 +} + +type LseekIn struct { + InHeader + Fh uint64 + Offset uint64 + Whence uint32 + Padding uint32 +} + +type LseekOut struct { + Offset uint64 +} + +type CopyFileRangeIn struct { + InHeader + FhIn uint64 + OffIn uint64 + NodeIdOut uint64 + FhOut uint64 + OffOut uint64 + Len uint64 + Flags uint64 +} + +// EntryOut holds the result of a (directory,name) lookup. It has two +// TTLs, one for the (directory, name) lookup itself, and one for the +// attributes (eg. size, mode). The entry TTL also applies if the +// lookup result is ENOENT ("negative entry lookup") +type EntryOut struct { + NodeId uint64 + Generation uint64 + EntryValid uint64 + AttrValid uint64 + EntryValidNsec uint32 + AttrValidNsec uint32 + Attr +} + +// EntryTimeout returns entry timeout currently +func (o *EntryOut) EntryTimeout() time.Duration { + return time.Duration(uint64(o.EntryValidNsec) + o.EntryValid*1e9) +} + +func (o *EntryOut) AttrTimeout() time.Duration { + return time.Duration(uint64(o.AttrValidNsec) + o.AttrValid*1e9) +} + +func (o *EntryOut) SetEntryTimeout(dt time.Duration) { + ns := int64(dt) + o.EntryValidNsec = uint32(ns % 1e9) + o.EntryValid = uint64(ns / 1e9) +} + +func (o *EntryOut) SetAttrTimeout(dt time.Duration) { + ns := int64(dt) + o.AttrValidNsec = uint32(ns % 1e9) + o.AttrValid = uint64(ns / 1e9) +} + +type AttrOut struct { + AttrValid uint64 + AttrValidNsec uint32 + Dummy uint32 + Attr +} + +func (o *AttrOut) Timeout() time.Duration { + return time.Duration(uint64(o.AttrValidNsec) + o.AttrValid*1e9) +} + +func (o *AttrOut) SetTimeout(dt time.Duration) { + ns := int64(dt) + o.AttrValidNsec = uint32(ns % 1e9) + o.AttrValid = uint64(ns / 1e9) +} + +type CreateOut struct { + EntryOut + OpenOut +} + +// Caller has data on the process making the FS call. +// +// The UID and GID are effective UID/GID, except for the ACCESS +// opcode, where UID and GID are the real UIDs +type Caller struct { + Owner + Pid uint32 +} + +type InHeader struct { + Length uint32 + Opcode uint32 + Unique uint64 + NodeId uint64 + Caller + Padding uint32 +} + +type StatfsOut struct { + Blocks uint64 + Bfree uint64 + Bavail uint64 + Files uint64 + Ffree uint64 + Bsize uint32 + NameLen uint32 + Frsize uint32 + Padding uint32 + Spare [6]uint32 +} + +// _Dirent is what we send to the kernel, but we offer DirEntry and +// DirEntryList to the user. +type _Dirent struct { + Ino uint64 + Off uint64 + NameLen uint32 + Typ uint32 +} + +const ( + READ_LOCKOWNER = (1 << 1) +) + +const ( + WRITE_CACHE = (1 << 0) + WRITE_LOCKOWNER = (1 << 1) +) + +type FallocateIn struct { + InHeader + Fh uint64 + Offset uint64 + Length uint64 + Mode uint32 + Padding uint32 +} + +func (lk *FileLock) ToFlockT(flockT *syscall.Flock_t) { + flockT.Start = int64(lk.Start) + if lk.End == (1<<63)-1 { + flockT.Len = 0 + } else { + flockT.Len = int64(lk.End - lk.Start + 1) + } + flockT.Whence = int16(io.SeekStart) + flockT.Type = int16(lk.Typ) +} + +func (lk *FileLock) FromFlockT(flockT *syscall.Flock_t) { + lk.Typ = uint32(flockT.Type) + if flockT.Type != syscall.F_UNLCK { + lk.Start = uint64(flockT.Start) + if flockT.Len == 0 { + lk.End = (1 << 63) - 1 + } else { + lk.End = uint64(flockT.Start + flockT.Len - 1) + } + } + lk.Pid = uint32(flockT.Pid) +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/types_darwin.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/types_darwin.go new file mode 100644 index 000000000..693fa69fe --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/types_darwin.go @@ -0,0 +1,166 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "syscall" +) + +const ( + ENOATTR = Status(syscall.ENOATTR) // ENOATTR is not defined for all GOOS. + + // EREMOTEIO is not supported on Darwin. + EREMOTEIO = Status(syscall.EIO) +) + +type Attr struct { + Ino uint64 + Size uint64 + Blocks uint64 + Atime uint64 + Mtime uint64 + Ctime uint64 + Crtime_ uint64 // OS X + Atimensec uint32 + Mtimensec uint32 + Ctimensec uint32 + Crtimensec_ uint32 // OS X + Mode uint32 + Nlink uint32 + Owner + Rdev uint32 + Flags_ uint32 // OS X +} + +const ( + FATTR_CRTIME = (1 << 28) + FATTR_CHGTIME = (1 << 29) + FATTR_BKUPTIME = (1 << 30) + FATTR_FLAGS = (1 << 31) +) + +type SetAttrIn struct { + SetAttrInCommon + + // OS X only + Bkuptime_ uint64 + Chgtime_ uint64 + Crtime uint64 + BkuptimeNsec uint32 + ChgtimeNsec uint32 + CrtimeNsec uint32 + Flags_ uint32 // see chflags(2) +} + +const ( + FOPEN_PURGE_ATTR = (1 << 30) + FOPEN_PURGE_UBC = (1 << 31) +) + +// compat with linux. +const ( + // Mask for GetAttrIn.Flags. If set, GetAttrIn has a file handle set. + FUSE_GETATTR_FH = (1 << 0) +) + +type GetAttrIn struct { + InHeader +} + +func (g *GetAttrIn) Flags() uint32 { + return 0 +} + +func (g *GetAttrIn) Fh() uint64 { + return 0 +} + +// Uses OpenIn struct for create. +type CreateIn struct { + InHeader + + Flags uint32 + Mode uint32 +} + +type MknodIn struct { + InHeader + + Mode uint32 + Rdev uint32 +} + +type ReadIn struct { + InHeader + + Fh uint64 + Offset uint64 + Size uint32 + ReadFlags uint32 +} + +type WriteIn struct { + InHeader + Fh uint64 + Offset uint64 + Size uint32 + WriteFlags uint32 +} + +type SetXAttrIn struct { + InHeader + Size uint32 + Flags uint32 + Position uint32 + Padding uint32 +} + +type GetXAttrIn struct { + InHeader + Size uint32 + Padding uint32 + Position uint32 + Padding2 uint32 +} + +const ( + CAP_CASE_INSENSITIVE = (1 << 29) + CAP_VOL_RENAME = (1 << 30) + CAP_XTIMES = (1 << 31) +) + +type GetxtimesOut struct { + Bkuptime uint64 + Crtime uint64 + Bkuptimensec uint32 + Crtimensec uint32 +} + +type ExchangeIn struct { + InHeader + Olddir uint64 + Newdir uint64 + Options uint64 +} + +func (s *StatfsOut) FromStatfsT(statfs *syscall.Statfs_t) { + s.Blocks = statfs.Blocks + s.Bfree = statfs.Bfree + s.Bavail = statfs.Bavail + s.Files = statfs.Files + s.Ffree = statfs.Ffree + s.Bsize = uint32(statfs.Iosize) // Iosize translates to Bsize: the optimal transfer size. + s.Frsize = s.Bsize // Bsize translates to Frsize: the minimum transfer size. + + // The block counts are in units of statfs.Bsize. + // If s.Bsize != statfs.Bsize, we have to recalculate the block counts + // accordingly (s.Bsize is usually 256*statfs.Bsize). + if s.Bsize > statfs.Bsize { + adj := uint64(s.Bsize / statfs.Bsize) + s.Blocks /= adj + s.Bfree /= adj + s.Bavail /= adj + } +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/fuse/types_linux.go b/vendor/github.com/hanwen/go-fuse/v2/fuse/types_linux.go new file mode 100644 index 000000000..b1e088f52 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/fuse/types_linux.go @@ -0,0 +1,134 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "syscall" +) + +const ( + ENOATTR = Status(syscall.ENODATA) // On Linux, ENOATTR is an alias for ENODATA. + + // EREMOTEIO Remote I/O error + EREMOTEIO = Status(syscall.EREMOTEIO) +) + +type Attr struct { + Ino uint64 + Size uint64 + + // Blocks is the number of 512-byte blocks that the file occupies on disk. + Blocks uint64 + Atime uint64 + Mtime uint64 + Ctime uint64 + Atimensec uint32 + Mtimensec uint32 + Ctimensec uint32 + Mode uint32 + Nlink uint32 + Owner + Rdev uint32 + + // Blksize is the preferred size for file system operations. + Blksize uint32 + Padding uint32 +} + +type SetAttrIn struct { + SetAttrInCommon +} + +const ( + // Mask for GetAttrIn.Flags. If set, GetAttrIn has a file handle set. + FUSE_GETATTR_FH = (1 << 0) +) + +type GetAttrIn struct { + InHeader + + Flags_ uint32 + Dummy uint32 + Fh_ uint64 +} + +// Flags accesses the flags. This is a method, because OSXFuse does not +// have GetAttrIn flags. +func (g *GetAttrIn) Flags() uint32 { + return g.Flags_ +} + +// Fh accesses the file handle. This is a method, because OSXFuse does not +// have GetAttrIn flags. +func (g *GetAttrIn) Fh() uint64 { + return g.Fh_ +} + +type CreateIn struct { + InHeader + Flags uint32 + + // Mode for the new file; already takes Umask into account. + Mode uint32 + + // Umask used for this create call. + Umask uint32 + Padding uint32 +} + +type MknodIn struct { + InHeader + + // Mode to use, including the Umask value + Mode uint32 + Rdev uint32 + Umask uint32 + Padding uint32 +} + +type ReadIn struct { + InHeader + Fh uint64 + Offset uint64 + Size uint32 + ReadFlags uint32 + LockOwner uint64 + Flags uint32 + Padding uint32 +} + +type WriteIn struct { + InHeader + Fh uint64 + Offset uint64 + Size uint32 + WriteFlags uint32 + LockOwner uint64 + Flags uint32 + Padding uint32 +} + +type SetXAttrIn struct { + InHeader + Size uint32 + Flags uint32 +} + +type GetXAttrIn struct { + InHeader + Size uint32 + Padding uint32 +} + +func (s *StatfsOut) FromStatfsT(statfs *syscall.Statfs_t) { + s.Blocks = statfs.Blocks + s.Bsize = uint32(statfs.Bsize) + s.Bfree = statfs.Bfree + s.Bavail = statfs.Bavail + s.Files = statfs.Files + s.Ffree = statfs.Ffree + s.Frsize = uint32(statfs.Frsize) + s.NameLen = uint32(statfs.Namelen) +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/internal/access.go b/vendor/github.com/hanwen/go-fuse/v2/internal/access.go new file mode 100644 index 000000000..1d865070e --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/internal/access.go @@ -0,0 +1,60 @@ +// Copyright 2019 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package internal + +import ( + "os/user" + "strconv" +) + +// HasAccess tests if a caller can access a file with permissions +// `perm` in mode `mask` +func HasAccess(callerUid, callerGid, fileUid, fileGid uint32, perm uint32, mask uint32) bool { + if callerUid == 0 { + // root can do anything. + return true + } + mask = mask & 7 + if mask == 0 { + return true + } + + if callerUid == fileUid { + if perm&(mask<<6) != 0 { + return true + } + } + if callerGid == fileGid { + if perm&(mask<<3) != 0 { + return true + } + } + if perm&mask != 0 { + return true + } + + // Check other groups. + if perm&(mask<<3) == 0 { + // avoid expensive lookup if it's not allowed anyway + return false + } + + u, err := user.LookupId(strconv.Itoa(int(callerUid))) + if err != nil { + return false + } + gs, err := u.GroupIds() + if err != nil { + return false + } + + fileGidStr := strconv.Itoa(int(fileGid)) + for _, gidStr := range gs { + if gidStr == fileGidStr { + return true + } + } + return false +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/internal/utimens/utimens_darwin.go b/vendor/github.com/hanwen/go-fuse/v2/internal/utimens/utimens_darwin.go new file mode 100644 index 000000000..c7a216e98 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/internal/utimens/utimens_darwin.go @@ -0,0 +1,40 @@ +// Copyright 2018 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package utimens + +import ( + "syscall" + "time" + + "github.com/hanwen/go-fuse/v2/fuse" +) + +// timeToTimeval converts time.Time to syscall.Timeval +func timeToTimeval(t *time.Time) syscall.Timeval { + // Note: This does not use syscall.NsecToTimespec because + // that does not work properly for times before 1970, + // see https://github.com/golang/go/issues/12777 + var tv syscall.Timeval + tv.Usec = int32(t.Nanosecond() / 1000) + tv.Sec = t.Unix() + return tv +} + +// Fill converts a and m to a syscall.Timeval slice that can be passed +// to syscall.Utimes. Missing values (if any) are taken from attr +func Fill(a *time.Time, m *time.Time, attr *fuse.Attr) []syscall.Timeval { + if a == nil { + a2 := time.Unix(int64(attr.Atime), int64(attr.Atimensec)) + a = &a2 + } + if m == nil { + m2 := time.Unix(int64(attr.Mtime), int64(attr.Mtimensec)) + m = &m2 + } + tv := make([]syscall.Timeval, 2) + tv[0] = timeToTimeval(a) + tv[1] = timeToTimeval(m) + return tv +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/internal/utimens/utimens_linux.go b/vendor/github.com/hanwen/go-fuse/v2/internal/utimens/utimens_linux.go new file mode 100644 index 000000000..7132090a5 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/internal/utimens/utimens_linux.go @@ -0,0 +1,7 @@ +// Copyright 2018 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package utimens + +// placeholder file so this package exists on all platforms. diff --git a/vendor/github.com/hanwen/go-fuse/v2/splice/copy.go b/vendor/github.com/hanwen/go-fuse/v2/splice/copy.go new file mode 100644 index 000000000..e50ca0d12 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/splice/copy.go @@ -0,0 +1,70 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package splice + +import ( + "io" + "os" +) + +func SpliceCopy(dst *os.File, src *os.File, p *Pair) (int64, error) { + total := int64(0) + + for { + n, err := p.LoadFrom(src.Fd(), p.size) + if err != nil { + return total, err + } + if n == 0 { + break + } + m, err := p.WriteTo(dst.Fd(), n) + total += int64(m) + if err != nil { + return total, err + } + if m < n { + return total, err + } + if int(n) < p.size { + break + } + } + + return total, nil +} + +// Argument ordering follows io.Copy. +func CopyFile(dstName string, srcName string, mode int) error { + src, err := os.Open(srcName) + if err != nil { + return err + } + defer src.Close() + + dst, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(mode)) + if err != nil { + return err + } + defer dst.Close() + + return CopyFds(dst, src) +} + +func CopyFds(dst *os.File, src *os.File) (err error) { + p, err := splicePool.get() + if p != nil { + p.Grow(256 * 1024) + _, err := SpliceCopy(dst, src, p) + splicePool.done(p) + return err + } else { + _, err = io.Copy(dst, src) + } + if err == io.EOF { + err = nil + } + return err +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/splice/pair.go b/vendor/github.com/hanwen/go-fuse/v2/splice/pair.go new file mode 100644 index 000000000..d7d7a42a0 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/splice/pair.go @@ -0,0 +1,68 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package splice + +import ( + "fmt" + "syscall" +) + +type Pair struct { + r, w int + size int +} + +func (p *Pair) MaxGrow() { + for p.Grow(2*p.size) == nil { + } +} + +func (p *Pair) Grow(n int) error { + if n <= p.size { + return nil + } + if !resizable { + return fmt.Errorf("splice: want %d bytes, but not resizable", n) + } + if n > maxPipeSize { + return fmt.Errorf("splice: want %d bytes, max pipe size %d", n, maxPipeSize) + } + + newsize, errNo := fcntl(uintptr(p.r), F_SETPIPE_SZ, n) + if errNo != 0 { + return fmt.Errorf("splice: fcntl returned %v", errNo) + } + p.size = newsize + return nil +} + +func (p *Pair) Cap() int { + return p.size +} + +func (p *Pair) Close() error { + err1 := syscall.Close(p.r) + err2 := syscall.Close(p.w) + if err1 != nil { + return err1 + } + return err2 +} + +func (p *Pair) Read(d []byte) (n int, err error) { + return syscall.Read(p.r, d) +} + +func (p *Pair) Write(d []byte) (n int, err error) { + return syscall.Write(p.w, d) +} + +func (p *Pair) ReadFd() uintptr { + return uintptr(p.r) +} + +func (p *Pair) WriteFd() uintptr { + return uintptr(p.w) +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/splice/pair_darwin.go b/vendor/github.com/hanwen/go-fuse/v2/splice/pair_darwin.go new file mode 100644 index 000000000..8780bb1a2 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/splice/pair_darwin.go @@ -0,0 +1,22 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package splice + +import () + +func (p *Pair) LoadFromAt(fd uintptr, sz int, off int64) (int, error) { + panic("not implemented") + return 0, nil +} + +func (p *Pair) LoadFrom(fd uintptr, sz int) (int, error) { + panic("not implemented") + return 0, nil +} + +func (p *Pair) WriteTo(fd uintptr, n int) (int, error) { + panic("not implemented") + return 0, nil +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/splice/pair_linux.go b/vendor/github.com/hanwen/go-fuse/v2/splice/pair_linux.go new file mode 100644 index 000000000..f8923e307 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/splice/pair_linux.go @@ -0,0 +1,54 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package splice + +import ( + "fmt" + "log" + "os" + "syscall" +) + +func (p *Pair) LoadFromAt(fd uintptr, sz int, off int64) (int, error) { + n, err := syscall.Splice(int(fd), &off, p.w, nil, sz, 0) + return int(n), err +} + +func (p *Pair) LoadFrom(fd uintptr, sz int) (int, error) { + if sz > p.size { + return 0, fmt.Errorf("LoadFrom: not enough space %d, %d", + sz, p.size) + } + + n, err := syscall.Splice(int(fd), nil, p.w, nil, sz, 0) + if err != nil { + err = os.NewSyscallError("Splice load from", err) + } + return int(n), err +} + +func (p *Pair) WriteTo(fd uintptr, n int) (int, error) { + m, err := syscall.Splice(p.r, nil, int(fd), nil, int(n), 0) + if err != nil { + err = os.NewSyscallError("Splice write", err) + } + return int(m), err +} + +const _SPLICE_F_NONBLOCK = 0x2 + +func (p *Pair) discard() { + _, err := syscall.Splice(p.r, nil, int(devNullFD), nil, int(p.size), _SPLICE_F_NONBLOCK) + if err == syscall.EAGAIN { + // all good. + } else if err != nil { + errR := syscall.Close(p.r) + errW := syscall.Close(p.w) + + // This can happen if something closed our fd + // inadvertently (eg. double close) + log.Panicf("splicing into /dev/null: %v (close R %d '%v', close W %d '%v')", err, p.r, errR, p.w, errW) + } +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/splice/pool.go b/vendor/github.com/hanwen/go-fuse/v2/splice/pool.go new file mode 100644 index 000000000..93598959e --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/splice/pool.go @@ -0,0 +1,105 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package splice + +import ( + "sync" +) + +var splicePool *pairPool + +type pairPool struct { + sync.Mutex + unused []*Pair + usedCount int +} + +func ClearSplicePool() { + splicePool.clear() +} + +func Get() (*Pair, error) { + return splicePool.get() +} + +func Total() int { + return splicePool.total() +} + +func Used() int { + return splicePool.used() +} + +// Done returns the pipe pair to pool. +func Done(p *Pair) { + splicePool.done(p) +} + +// Closes and discards pipe pair. +func Drop(p *Pair) { + splicePool.drop(p) +} + +func newSplicePairPool() *pairPool { + return &pairPool{} +} + +func (pp *pairPool) clear() { + pp.Lock() + for _, p := range pp.unused { + p.Close() + } + pp.unused = pp.unused[:0] + pp.Unlock() +} + +func (pp *pairPool) used() (n int) { + pp.Lock() + n = pp.usedCount + pp.Unlock() + + return n +} + +func (pp *pairPool) total() int { + pp.Lock() + n := pp.usedCount + len(pp.unused) + pp.Unlock() + return n +} + +func (pp *pairPool) drop(p *Pair) { + p.Close() + pp.Lock() + pp.usedCount-- + pp.Unlock() +} + +func (pp *pairPool) get() (p *Pair, err error) { + pp.Lock() + defer pp.Unlock() + + pp.usedCount++ + l := len(pp.unused) + if l > 0 { + p := pp.unused[l-1] + pp.unused = pp.unused[:l-1] + return p, nil + } + + return newSplicePair() +} + +func (pp *pairPool) done(p *Pair) { + p.discard() + pp.Lock() + pp.usedCount-- + pp.unused = append(pp.unused, p) + pp.Unlock() +} + +func init() { + splicePool = newSplicePairPool() +} diff --git a/vendor/github.com/hanwen/go-fuse/v2/splice/splice.go b/vendor/github.com/hanwen/go-fuse/v2/splice/splice.go new file mode 100644 index 000000000..cbb20b4e0 --- /dev/null +++ b/vendor/github.com/hanwen/go-fuse/v2/splice/splice.go @@ -0,0 +1,97 @@ +// Copyright 2016 the Go-FUSE Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package splice + +// Routines for efficient file to file copying. + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "syscall" +) + +var maxPipeSize int +var resizable bool + +func Resizable() bool { + return resizable +} + +func MaxPipeSize() int { + return maxPipeSize +} + +// From manpage on ubuntu Lucid: +// +// Since Linux 2.6.11, the pipe capacity is 65536 bytes. +const DefaultPipeSize = 16 * 4096 + +// We empty pipes by splicing to /dev/null. +var devNullFD uintptr + +func init() { + content, err := ioutil.ReadFile("/proc/sys/fs/pipe-max-size") + if err != nil { + maxPipeSize = DefaultPipeSize + } else { + fmt.Sscan(string(content), &maxPipeSize) + } + + r, w, err := os.Pipe() + if err != nil { + log.Panicf("cannot create pipe: %v", err) + } + sz, errNo := fcntl(r.Fd(), F_GETPIPE_SZ, 0) + resizable = (errNo == 0) + _, errNo = fcntl(r.Fd(), F_SETPIPE_SZ, 2*sz) + resizable = resizable && (errNo == 0) + r.Close() + w.Close() + + fd, err := syscall.Open("/dev/null", os.O_WRONLY, 0) + if err != nil { + log.Panicf("splice: %v", err) + } + + devNullFD = uintptr(fd) +} + +// copy & paste from syscall. +func fcntl(fd uintptr, cmd int, arg int) (val int, errno syscall.Errno) { + r0, _, e1 := syscall.Syscall(syscall.SYS_FCNTL, fd, uintptr(cmd), uintptr(arg)) + val = int(r0) + errno = syscall.Errno(e1) + return +} + +const F_SETPIPE_SZ = 1031 +const F_GETPIPE_SZ = 1032 + +func osPipe() (int, int, error) { + var fds [2]int + err := syscall.Pipe2(fds[:], syscall.O_NONBLOCK) + return fds[0], fds[1], err +} + +func newSplicePair() (p *Pair, err error) { + p = &Pair{} + p.r, p.w, err = osPipe() + if err != nil { + return nil, err + } + var errNo syscall.Errno + p.size, errNo = fcntl(uintptr(p.r), F_GETPIPE_SZ, 0) + if err == syscall.EINVAL { + p.size = DefaultPipeSize + return p, nil + } + if errNo != 0 { + p.Close() + return nil, fmt.Errorf("fcntl getsize: %v", errNo) + } + return p, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 78511eb93..029017e48 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -96,6 +96,12 @@ github.com/golang/protobuf/ptypes/timestamp github.com/google/go-querystring/query # github.com/googleapis/gax-go/v2 v2.0.5 github.com/googleapis/gax-go/v2 +# github.com/hanwen/go-fuse/v2 v2.0.3-0.20191108143333-152e6ac32d54 +github.com/hanwen/go-fuse/v2/fs +github.com/hanwen/go-fuse/v2/fuse +github.com/hanwen/go-fuse/v2/internal +github.com/hanwen/go-fuse/v2/internal/utimens +github.com/hanwen/go-fuse/v2/splice # github.com/inconshreveable/mousetrap v1.0.0 github.com/inconshreveable/mousetrap # github.com/jlaffaye/ftp v0.0.0-20191025175106-a59fe673c9b2